pax_global_header00006660000000000000000000000064125541735530014524gustar00rootroot0000000000000052 comment=dde6c19bc05a4236ee7ec418ed88b1cc0ac1d595 workbench-1.1.1/000077500000000000000000000000001255417355300135065ustar00rootroot00000000000000workbench-1.1.1/.gitignore000066400000000000000000000001331255417355300154730ustar00rootroot00000000000000OLD.Makefile *.kdev4 CMakeLists.txt.user* vs* build release debug qt* *.kate-swp .DS_Store workbench-1.1.1/Doxyfile000066400000000000000000000015371255417355300152220ustar00rootroot00000000000000PROJECT_NAME = Caret7 OUTPUT_DIRECTORY = ../documentation OUTPUT_LANGUAGE = English EXTRACT_ALL = YES EXTRACT_PRIVATE = YES HAVE_DOT = YES DOT_PATH = /usr/local/graphviz-2.14/bin CLASS_GRAPH = YES CALL_GRAPH = NO CALLER_GRAPH = NO COLLABORATION_GRAPH = NO DIRECTORY_GRAPH = YES SOURCE_BROWSER = YES ALPHABETICAL_INDEX = YES JAVADOC_AUTOBRIEF = YES GENERATE_HTML = YES GENERATE_LATEX = NO GENERATE_MAN = NO GENERATE_RTF = NO CREATE_SUBDIRS = YES SUBGROUPING = YES INPUT = \ src/Algorithms \ src/Brain \ src/Cifti \ src/CommandLine \ src/Commands \ src/Common \ src/Desktop \ src/Files \ src/FilesBase \ src/Gifti \ src/GuiQt \ src/Nifti \ src/Operations \ src/OperationsBase \ src/Palette \ src/Quazip \ src/Qwt \ src/Scenes \ src/Tests \ src/Xml FILE_PATTERNS = *.cxx *.h workbench-1.1.1/LICENSE000066400000000000000000000432541255417355300145230ustar00rootroot00000000000000 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. workbench-1.1.1/README000066400000000000000000000070271255417355300143740ustar00rootroot00000000000000Connectome Workbench requires QT4 to compile. Recommended is the latest 4.8.x release. The following configure options were used on linux for our builds: -system-zlib -webkit -qt-libmng -qt-libpng -qt-libtiff -qt-libjpeg -nomake demos -nomake examples -no-qt3support -no-dbus -opensource -exceptions -stl -no-multimedia -no-phonon -no-audio-backend -openssl Optionally, it can use OSMesa, which allows the -show-scene command to work. Use it by setting OSMESA_DIR as an environment variable, such that $OSMESA_DIR/include/GL/osmesa.h exists, before running cmake. It is compiled using cmake, on linux do: mkdir build cd build cmake ../src #OR cmake-gui ../src make [-j cores] For other OSes, see http://www.cmake.org/cmake/help/runningcmake.html You may want to set CMAKE_BUILD_TYPE to Release or Debug before building, as the default build behavior may be non-optimized and without debug symbols. This produces 3 executables, 2 of which are useful to the end user (Desktop/wb_view, CommandLine/wb_command), and one for running internal tests (Tests/test_driver). To run the (few) tests available: ctest #OR make test To install wb_view and wb_command with the prefix configured in CMAKE_INSTALL_PREFIX: make install It should be noted that wb_import, provided in the HCP binary releases of Connectome Workbench, is actually part of caret5 (http://brainvis.wustl.edu/wiki/index.php/Caret:Download). Connectome Workbench itself is copyright 2014-2015 Washington University School of Medicine, licensed under GPLv2 or later, see LICENSE file Some included code/files are from third party sources, with the following licenses: Quazip/*: QuaZIP 0.6, http://quazip.sourceforge.net/ Copyright (C) 2005-2012 Sergey A. Tachenov Copyright (C) 1998-2010 Gilles Vollant Copyright (C) 2009-2010 Mathias Svensson Copyright (C) 2007-2008 Even Rouault Copyright (c) 1990-2000 Info-ZIP licensed under LGPLv2 and zlib, see Quazip/COPYING, Quazip/quazip.h, Quazip/zip.h and Quazip/unzip.c Qwt/*: Copyright (C) 1997 Josef Wilgen Copyright (C) 2002 Uwe Rathmann Qwt 6.0.1, http://qwt.sourceforge.net/ licensed under Qwt license v1.0 (LGPLv2.1, with exceptions), see src/Qwt/COPYING some unneeded files removed FtglFont/*: FTGL library Copyright (C) 2001-2004 Henry Maddocks Copyright (C) 2008 Daniel Remenak Copyright (C) 2008 Éric Beets Copyright (C) 2008 Sam Hocevar Copyright (C) 2008 Sean Morrison licensed under Expat, see FtglFont/COPYING Common/Base64.*, Common/DataCompressZLib.*, Common/MathFunctions.*, Nifti/Matrix4x4.cxx: use code from VTK, http://www.kitware.com/opensource/vtk.html or http://www.vtk.org/ Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen originally licensed under BSD 3-clause, see http://www.kitware.com/Copyright.htm or http://www.vtk.org/VTK/project/license.html GuiQt/WuQDialog.cxx, Brain/FtglFontTextRenderer.cxx: copied some code from from QT4, https://qt-project.org/ Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). originally licensed LGPLv2.1 (or GPLv3, or a commercial license) modified to change some UI behaviors otherwise hardcoded into QT classes Files/SignedDistanceHelper.cxx, Files/RibbonMappingHelper.cxx: make use of PNPOLY, http://www.ecse.rpi.edu/~wrf/Research/Short_Notes/pnpoly.html Copyright (c) 1970-2003, Wm. Randolph Franklin originally licensed with 3-clause BSD/MIT license, see files in question rewritten for different argument types, modified Resources/FtglFonts/Vera*.ttf Copyright (c) 2003 Bitstream, Inc. workbench-1.1.1/build_scripts/000077500000000000000000000000001255417355300163545ustar00rootroot00000000000000workbench-1.1.1/build_scripts/commandhtml.sh000077500000000000000000000102631255417355300212200ustar00rootroot00000000000000#!/bin/bash exe=wb_command outDir='.' function command_to_page_name () { echo "command""$1"".html" } #make a page containing just the text output, no links, substituting special characters as needed function make_basic_command_page () { local commandName="$1" local outPage="$outDir/`command_to_page_name $commandName`" echo '' > "$outPage" echo '' >> "$outPage" echo "wb_command $commandName help information" >> "$outPage" echo '
' >> "$outPage"
    #body
    echo "`$exe $commandName`" | sed 's//\>/g' >> "$outPage"
    #end page
    echo '
' >> "$outPage" echo '' >> "$outPage" } #start main page - note that this assumes a particular order of the listed info commands, change and add as needed initialText=`$exe` startPage="wb_command_help.html" #page header echo '' > "$outDir/$startPage" echo '' >> "$outDir/$startPage" echo 'wb_command help information' >> "$outDir/$startPage" echo '
' >> "$outDir/$startPage"
#body
infoLine=`echo "$initialText" | grep -n Information | cut -f1 -d:`
#include -help line as plain text
echo -n "$initialText" | head -n $((infoLine + 1)) >> "$outDir/$startPage"
#-arguments-help
echo -n '' >> "$outDir/$startPage"
echo "$initialText" | grep -- -arguments-help >> "$outDir/$startPage"
echo -n '' >> "$outDir/$startPage"
#-version
echo "$initialText" | grep -- -version >> "$outDir/$startPage"
#-list-commands
echo -n '' >> "$outDir/$startPage"
echo "$initialText" | grep -- -list-commands >> "$outDir/$startPage"
echo -n '' >> "$outDir/$startPage"
#-list-deprecated-commands
echo -n '' >> "$outDir/$startPage"
echo "$initialText" | grep -- -list-deprecated-commands >> "$outDir/$startPage"
echo -n '' >> "$outDir/$startPage"
#-all-commands-help - takes 2 lines!
echo -n '' >> "$outDir/$startPage"
echo "$initialText" | grep -A 1 -- -all-commands-help >> "$outDir/$startPage"
echo -n '' >> "$outDir/$startPage"
#remainder of help info
allCommandsLine=`echo "$initialText" | grep -n -- -all-commands-help | cut -f1 -d:`
echo "$initialText" | tail -n +$((allCommandsLine+2)) >> "$outDir/$startPage"
#end page
echo '
' >> "$outDir/$startPage" echo '' >> "$outDir/$startPage" #-arguments-help page make_basic_command_page "-arguments-help" #-list-commands page, and its subpages outPage="$outDir/`command_to_page_name -list-commands`" #header echo '' > "$outPage" echo '' >> "$outPage" echo 'wb_command -list-commands help information' >> "$outPage" echo '
' >> "$outPage"
#body
readarray -t lines < <($exe -list-commands)
for ((i = 0; i < ${#lines[@]}; ++i))
do
    thisCommand=`echo ${lines[$i]} | cut -f1 -d' '`
    make_basic_command_page "$thisCommand"
    echo ''"${lines[$i]}"'' >> "$outPage"
done

#-list-deprecated-commands page, and its subpages
outPage="$outDir/`command_to_page_name -list-deprecated-commands`"
#header
echo '' > "$outPage"
echo '' >> "$outPage"
echo 'wb_command -list-deprecated-commands help information' >> "$outPage"
echo '
' >> "$outPage"
#body
readarray -t lines < <($exe -list-deprecated-commands)
for ((i = 0; i < ${#lines[@]}; ++i))
do
    thisCommand=`echo ${lines[$i]} | cut -f1 -d' '`
    make_basic_command_page "$thisCommand"
    echo ''"${lines[$i]}"'' >> "$outPage"
done

#end main page
echo '
' >> "$outPage" echo '' >> "$outPage" #-all-commands-help page make_basic_command_page "-all-commands-help" workbench-1.1.1/build_scripts/doxygen.sh000077500000000000000000000012741255417355300203740ustar00rootroot00000000000000#!/bin/sh # # This file is executed by (via "launchd") # /System/Library/LaunchDaemons/edu.wustl.caret7.doxygen.plist # # # File for capturing standard error # BUILD_ROOT_DIR=/mnt/myelin/caret7_documentation ERROR_FILE=${BUILD_ROOT_DIR}/result_doxygen.txt rm -f ${ERROR_FILE} # # If something fails, keep going # set noon # # Run the build script # echo "Starting doxygen generation" >> ${ERROR_FILE} 2>&1 ${BUILD_ROOT_DIR}/caret7_source/build_scripts/doxygen_aux.sh >> ${ERROR_FILE} 2>&1 echo "Finished doxygen generation" >> ${ERROR_FILE} 2>&1 # # Send output as email # cat ${ERROR_FILE} | mail -v -s 'Caret7 Doxygen Build Result' john@brainvis.wustl.edu tsc5yc@mst.edu echo "Sent mail" workbench-1.1.1/build_scripts/doxygen_aux.sh000077500000000000000000000015301255417355300212440ustar00rootroot00000000000000#!/bin/sh # # This file is executed by (via "launchd") # /System/Library/LaunchDaemons/edu.wustl.caret7.doxygen.plist # # (1) Update source code from repository # (2) Build doxygen documentation # # # Go to correct directory # MAIN_DIR=/mnt/myelin/caret7_documentation SOURCE_DIR=${MAIN_DIR}/caret7_source DOCUMENTATION_DIR=${MAIN_DIR}/documentation BUILD_DIR=${MAIN_DIR}/build # # Remove existing documentation directory # rm -rf ${DOCUMENTATION_DIR} # # Go to the source directory which contains Doxyfile # Doxyfile is read by doxygen and contains configuration information # cd ${SOURCE_DIR} # # Update source from repository # echo "UPDATING SOURCE FROM GIT REPOSITORY" git pull -u # # Generate doxygen documentation # echo "Generating Documentation" /Applications/Doxygen.app/Contents/Resources/doxygen echo "SCRIPT COMPLETED SUCCESSFULLY" workbench-1.1.1/build_scripts/linux32_remote.sh000077500000000000000000000005121255417355300215700ustar00rootroot00000000000000#!/bin/sh BUILD=linux32 BUILD_SERVER=linuxbuild BUILD_DIR=/home/caret/${BUILD}/caret7_dev ssh -v caret@${BUILD_SERVER} "/bin/bash -c \"cd ${BUILD_DIR};./build32.sh caret\"" > $PWD/remote_launch_${BUILD}.txt 2>&1 cat $PWD/remote_launch_${BUILD}.txt | mailx -s 'Caret7 Linux 32 Build Result' john@brainvis.wustl.edu tsc5yc@mst.edu workbench-1.1.1/build_scripts/linux64_remote.sh000077500000000000000000000005131255417355300215760ustar00rootroot00000000000000#!/bin/sh BUILD=linux64 BUILD_SERVER=linuxbuild BUILD_DIR=/home/caret/${BUILD}/caret7_dev ssh -v caret@${BUILD_SERVER} "/bin/bash -c \"cd ${BUILD_DIR};./build64.sh caret\"" > $PWD/remote_launch_${BUILD}.txt 2>&1 cat $PWD/remote_launch_${BUILD}.txt | mailx -s 'Caret7 Linux 64 Build Result' john@brainvis.wustl.edu tsc5yc@mst.edu workbench-1.1.1/build_scripts/linux64_rh.sh000077500000000000000000000015671255417355300207260ustar00rootroot00000000000000#!/bin/sh # # This file is executed by myelin1. # # # (1) Update source code from repository # (2) Build Caret programs # (3) Copy executables into the distribution # # # File for capturing standard error # BUILD_ROOT_DIR=/home/caret/caret7_dev ERROR_FILE=${BUILD_ROOT_DIR}/result_build_linux64_rh.txt rm -f ${ERROR_FILE} # # If something fails, keep going # set noon # # Run the build script # echo "Starting linux64 Redhat aux script" >> ${ERROR_FILE} 2>&1 ${BUILD_ROOT_DIR}/caret7_source/build_scripts/linux64_rh_aux.sh >> ${ERROR_FILE} 2>&1 echo "Finished linux64 Redhat script" >> ${ERROR_FILE} 2>&1 # # Send output as email # MAIL_ERROR_FILE=${BUILD_ROOT_DIR}/mail_result_build_linux64_rh.txt rm -f ${MAIL_ERROR_FILE} cat ${ERROR_FILE} | mail -v -s 'Caret7 Linux 64 Redhat Build Result' john@brainvis.wustl.edu tsc5yc@mst.edu >> ${MAIL_ERROR_FILE} 2>&1 echo "Sent mail" workbench-1.1.1/build_scripts/linux64_rh_aux.sh000077500000000000000000000017661255417355300216040ustar00rootroot00000000000000#!/bin/sh # # This file is executed on the redhat build machine # # # (1) Update source code from repository # (2) Build Caret programs # (3) Copy executables into the distribution # # Based off Tim Coalson's "buildbot" script # BUILD_ROOT_DIR=/home/caret/caret7_dev GIT_ROOT_DIR=${BUILD_ROOT_DIR}/caret7_source BUILD_DIR=${BUILD_ROOT_DIR}/build cd ${BUILD_ROOT_DIR} echo "BUILD_DIR: ${BUILD_DIR}" # # Go into git checkout directory # cd ${GIT_ROOT_DIR} # # Update source from repository # echo "UPDATING SOURCE FROM GIT REPOSITORY" git fetch git reset --hard origin/master # # Build, assume the build directory is already set up as desired # echo "BUILDING SOURCE" cd ${BUILD_DIR} make -j2 # # Copy to distribution directory # echo "COPYING PROGRAMS" DIST_DIR=/mainpool/storage/distribution/caret7_distribution/workbench/exe_rh_linux64 scp -rv Desktop/wb_view caret@myelin1.wustl.edu:${DIST_DIR} scp -rv CommandLine/wb_command caret@myelin1.wustl.edu:${DIST_DIR} echo "SCRIPT COMPLETED SUCCESSFULLY" workbench-1.1.1/build_scripts/linux64_rh_remote.sh000077500000000000000000000004071255417355300222710ustar00rootroot00000000000000#!/bin/sh BUILD=linux64_rh BUILD_SERVER=login1.chpc.wustl.edu #BUILD_DIR=/home/caret/caret7_dev/${BUILD} BUILD_DIR=/home/caret/caret7_dev ssh -v caret@${BUILD_SERVER} ${BUILD_DIR}/caret7_source/build_scripts/${BUILD}.sh $1 > $PWD/remote_launch_${BUILD}.txt 2>&1 workbench-1.1.1/build_scripts/mac32.sh000077500000000000000000000016461255417355300176270ustar00rootroot00000000000000#!/bin/sh # # This file is executed by (via "launchd") # /System/Library/LaunchDaemons/edu.wustl.caret.build.mac32.plist # # (1) Update source code from repository # (2) Build Caret programs # (3) Copy executables into the distribution # # # File for capturing standard error # BUILD_ROOT_DIR=/Users/caret/caret7_development/mac32 ERROR_FILE=${BUILD_ROOT_DIR}/result_build_mac32.txt rm -f ${ERROR_FILE} # # If something fails, keep going # set noon # # Run the build script # echo "Starting mac32 aux script" >> ${ERROR_FILE} 2>&1 ${BUILD_ROOT_DIR}/caret7_source/build_scripts/mac32_aux.sh >> ${ERROR_FILE} 2>&1 echo "Finished mac32 aux script" >> ${ERROR_FILE} 2>&1 # # Send output as email # MAIL_ERROR_FILE=${BUILD_ROOT_DIR}/mail_result_build_mac32.txt rm -f ${MAIL_ERROR_FILE} cat ${ERROR_FILE} | mail -v -s 'Caret7 Mac 32 Build Result' john@brainvis.wustl.edu tsc5yc@mst.edu >> ${MAIL_ERROR_FILE} 2>&1 echo "Sent mail" workbench-1.1.1/build_scripts/mac32_aux.sh000077500000000000000000000030751255417355300205020ustar00rootroot00000000000000#!/bin/sh # # This file is executed by (via "launchd") # /System/Library/LaunchDaemons/edu.wustl.caret.build.mac32.plist # # (1) Update source code from repository # (2) Build Caret programs # (3) Copy executables into the distribution # # Based off Tim Coalson's "buildbot" script # # # Go to correct directory # BUILD_ROOT_DIR=/Users/caret/caret7_development/mac32 GIT_ROOT_DIR=${BUILD_ROOT_DIR}/caret7_source SRC_DIR=${GIT_ROOT_DIR}/src BUILD_DIR=${BUILD_ROOT_DIR}/build cd ${BUILD_ROOT_DIR} echo "BUILD_DIR: ${BUILD_DIR}" # # Use Qt SDK # QTDIR=/Users/caret/QtSDK/Desktop/Qt/474/gcc export QTDIR echo "QTDIR: ${QTDIR}" PATH=${QTDIR}/bin:${PATH} export PATH QMAKE_PROG=`which qmake` echo "QMAKE: ${QMAKE_PROG}" # # Go into git checkout directory # cd ${GIT_ROOT_DIR} # # Update source from repository # echo "UPDATING SOURCE FROM GIT REPOSITORY" git reset --hard HEAD git pull -u # # Configure and build as release. # Build twice so that if the build fails, the second build will # contain just the output that shows the errors. # Catch output and echo to screen. # echo "BUILDING SOURCE" mkdir -p ${BUILD_DIR} cd ${BUILD_DIR} cmake ${SRC_DIR} make -j2 make -j2 # # Run 'macdeployqt' on the App so that frameworks are copied # echo "RUNNING MACDEPLOYQT" macdeployqt Desktop/wb_view.app # # Copy to distribution directory # echo "COPYING PROGRAMS" DIST_DIR=/mainpool/storage/distribution/caret7_distribution/workbench/macosx32_apps scp -rv Desktop/wb_view.app caret@myelin1:${DIST_DIR} scp -v CommandLine/wb_command caret@myelin1:${DIST_DIR} echo "SCRIPT COMPLETED SUCCESSFULLY" workbench-1.1.1/build_scripts/mac32_remote.sh000077500000000000000000000003511255417355300211720ustar00rootroot00000000000000#!/bin/sh BUILD=mac32 BUILD_SERVER=hippocampus.wustl.edu BUILD_DIR=/Users/caret/caret7_development/${BUILD} ssh -v caret@${BUILD_SERVER} ${BUILD_DIR}/caret7_source/build_scripts/${BUILD}.sh $1 > $PWD/remote_launch_${BUILD}.txt 2>&1 workbench-1.1.1/build_scripts/mac64.sh000077500000000000000000000016461255417355300176340ustar00rootroot00000000000000#!/bin/sh # # This file is executed by (via "launchd") # /System/Library/LaunchDaemons/edu.wustl.caret.build.mac64.plist # # (1) Update source code from repository # (2) Build Caret programs # (3) Copy executables into the distribution # # # File for capturing standard error # BUILD_ROOT_DIR=/Users/caret/caret7_development/mac64 ERROR_FILE=${BUILD_ROOT_DIR}/result_build_mac64.txt rm -f ${ERROR_FILE} # # If something fails, keep going # set noon # # Run the build script # echo "Starting mac64 aux script" >> ${ERROR_FILE} 2>&1 ${BUILD_ROOT_DIR}/caret7_source/build_scripts/mac64_aux.sh >> ${ERROR_FILE} 2>&1 echo "Finished mac64 aux script" >> ${ERROR_FILE} 2>&1 # # Send output as email # MAIL_ERROR_FILE=${BUILD_ROOT_DIR}/mail_result_build_mac64.txt rm -f ${MAIL_ERROR_FILE} cat ${ERROR_FILE} | mail -v -s 'Caret7 Mac 64 Build Result' john@brainvis.wustl.edu tsc5yc@mst.edu >> ${MAIL_ERROR_FILE} 2>&1 echo "Sent mail" workbench-1.1.1/build_scripts/mac64_aux.sh000077500000000000000000000051071255417355300205050ustar00rootroot00000000000000#!/bin/sh # # This file is executed by (via "launchd") # /System/Library/LaunchDaemons/edu.wustl.caret.build.mac64.plist # # (1) Update source code from repository # (2) Build Caret programs # (3) Copy executables into the distribution # # Based off Tim Coalson's "buildbot" script # # # Go to correct directory # BUILD_ROOT_DIR=/Users/caret/caret7_development/mac64 GIT_ROOT_DIR=${BUILD_ROOT_DIR}/caret7_source SRC_DIR=${GIT_ROOT_DIR}/src BUILD_DIR=${BUILD_ROOT_DIR}/build cd ${BUILD_ROOT_DIR} echo "BUILD_DIR: ${BUILD_DIR}" # # Setup Environment # # # Use Qt SDK # #QTDIR=/Users/caret/QtSDK/Desktop/Qt/474/gcc # has SSL linked QTDIR=/opt/caret64_sdk/install/qt-4.8.3 export QTDIR echo "QTDIR: ${QTDIR}" PATH=${QTDIR}/bin:${PATH} export PATH QMAKE_PROG=`which qmake` echo "QMAKE: ${QMAKE_PROG}" # # Go into git checkout directory # cd ${GIT_ROOT_DIR} # # Update source from repository # echo "UPDATING SOURCE FROM GIT REPOSITORY" git reset --hard HEAD git pull -u # # Configure and build as release. # Build twice so that if the build fails, the second build will # contain just the output that shows the errors. # Catch output and echo to screen. # #CC_COMPILER=/usr/local/clang-llvm/clang+llvm-3.2-x86_64-apple-darwin11/bin/clang #CXX_COMPILER=/usr/local/clang-llvm/clang+llvm-3.2-x86_64-apple-darwin11/bin/clang++ #CC_COMPILER=/usr/bin/gcc #CXX_COMPILER=/usr/bin/g++ #CC_COMPILER=/usr/local/gcc-4.9.0/bin/gcc #CXX_COMPILER=/usr/local/gcc-4.9.0/bin/g++ # # Clang compiler with OpenMP # CC_COMPILER=/usr/local/clang-openmp-opt/llvm/build/Release/bin/clang2 CXX_COMPILER=/usr/local/clang-openmp-opt/llvm/build/Release/bin/clang2++ OPENMP_COMPILE_OPTION=-fopenmp export OPENMP_COMPILE_OPTION OPENMP_HEADER_DIR=/usr/local/clang-openmp-opt/llvm/build/Release/include export OPENMP_HEADER_DIR OPENMP_LIB_DIR=/usr/local/clang-openmp-opt/llvm/build/Release/lib export OPENMP_LIB_DIR echo "BUILDING SOURCE" mkdir -p ${BUILD_DIR} cd ${BUILD_DIR} cmake \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_C_COMPILER=${CC_COMPILER} \ -DCMAKE_CXX_COMPILER=${CXX_COMPILER} \ -DCMAKE_VERBOSE_MAKEFILE=FALSE \ ${SRC_DIR} make -j2 make -j2 # # Run 'macdeployqt' on the Apps so that frameworks are copied # echo "RUNNING MACDEPLOYQT" macdeployqt Desktop/wb_view.app -verbose=0 macdeployqt CommandLine/wb_command.app -verbose=0 # # Copy to distribution directory # echo "COPYING PROGRAMS" DIST_DIR=/mainpool/storage/distribution/caret7_distribution/workbench/macosx64_apps scp -rv Desktop/wb_view.app caret@myelin1:${DIST_DIR} scp -rv CommandLine/wb_command.app caret@myelin1:${DIST_DIR} echo "SCRIPT COMPLETED SUCCESSFULLY" workbench-1.1.1/build_scripts/mac64_remote.sh000077500000000000000000000003511255417355300211770ustar00rootroot00000000000000#!/bin/sh BUILD=mac64 BUILD_SERVER=hippocampus.wustl.edu BUILD_DIR=/Users/caret/caret7_development/${BUILD} ssh -v caret@${BUILD_SERVER} ${BUILD_DIR}/caret7_source/build_scripts/${BUILD}.sh $1 > $PWD/remote_launch_${BUILD}.txt 2>&1 workbench-1.1.1/build_scripts/windows32.bat000077500000000000000000000013321255417355300207050ustar00rootroot00000000000000@echo=off set CMD=c:\Windows\system32\cmd.exe REM REM Go to correct directory REM set BUILD_DIR=c:\dev7\windows32 cd %BUILD_DIR% REM REM Go into source directory REM cd caret7_source echo "Caret7 Windows32 Build Result" REM REM Grab the latest Sources REM c:\cygwin\bin\git.exe reset --hard HEAD c:\cygwin\bin\git.exe fetch c:\cygwin\bin\git.exe reset --hard origin/master REM REM Build caret REM cd build_scripts/windows32 %CMD% /c build.bat cd .. cd .. set DIST_DIR=caret@myelin1:/mainpool/storage/distribution/caret7_distribution/workbench/bin_windows32 echo "Copying Files" c:\cygwin\bin\scp build/Desktop/wb_view.exe %DIST_DIR% c:\cygwin\bin\scp build/CommandLine/wb_command.exe %DIST_DIR% echo "Finished Copying Files" workbench-1.1.1/build_scripts/windows32/000077500000000000000000000000001255417355300202135ustar00rootroot00000000000000workbench-1.1.1/build_scripts/windows32/README.txt000077500000000000000000000002461255417355300217160ustar00rootroot00000000000000setup_env.bat -sets up the build environment build.bat -sets up environment, runs cmake, and builds the release version of caret c7env32.bat - sets up environment workbench-1.1.1/build_scripts/windows32/build.bat000077500000000000000000000010741255417355300220070ustar00rootroot00000000000000@echo off if [%MACHINE%]==[] call setup_env.bat set OLD_DIR=%CD% REM rd /q/s %DIR%\caret7_source\build md %DIR%\caret7_source\build cd /d %DIR%\caret7_source\build REMcmake -G "NMake Makefiles JOM" -DCMAKE_BUILD_TYPE=Release ../src cmake -DFREETYPE_INCLUDE_DIR_freetype2=C:\dev32\install\FreeType-2.4.8\include\freetype2 -DFREETYPE_INCLUDE_DIR_ft2build=C:\dev32\install\FreeType-2.4.8\include -DFREETYPE_LIBRARY=C:\dev32\install\FreeType-2.4.8\lib\freetype.lib -G "NMake Makefiles JOM" -DCMAKE_BUILD_TYPE=Release ../src jom -j8 cd /d %OLD_DIR% set OLD_DIR= workbench-1.1.1/build_scripts/windows32/c7env32.bat000077500000000000000000000003421255417355300220740ustar00rootroot00000000000000set PATH=C:\dev32\install\Qt\bin;C:\QtSDK\QtCreator\bin;C:\Program Files (x86)\CMake 2.8\bin;%PATH% set QTDIR=C:\dev32\install\Qt set ZLIB_LIB_DIR=C:\dev32\install\zlib\lib set ZLIB_INCLUDE_DIR=C:\dev32\install\zlib\includeworkbench-1.1.1/build_scripts/windows32/c7env32mgw.bat000077500000000000000000000004151255417355300226100ustar00rootroot00000000000000set PATH=C:\QtSDK\Desktop\Qt\4.7.4\mingw\bin;C:\QtSDK\QtCreator\bin;C:\QtSDK\mingw\bin;C:\Program Files (x86)\CMake 2.8\bin;%PATH% set QTDIR=C:\QtSDK\Desktop\Qt\4.7.4\mingw set ZLIB_LIB_DIR=C:\dev32\install\zlib\lib set ZLIB_INCLUDE_DIR=C:\dev32\install\zlib\includeworkbench-1.1.1/build_scripts/windows32/env.bat000077500000000000000000000006141255417355300214770ustar00rootroot00000000000000@echo off rem isn't batch awesome, no else statments... IF [%1]==[] ( set DIR "." ) IF NOT [%1]==[] ( set DIR=%1 ) IF [%2]==[] ( set MACHINE X86 ) IF NOT [%2]==[] ( set MACHINE=%2 ) IF NOT [%INITVS%]==[%MACHINE%] ( echo "Setting %MACHINE% bit compiler variables" set INITVS=%MACHINE% call "c:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\vcvarsall.bat" %MACHINE% )workbench-1.1.1/build_scripts/windows32/launchcmdmgw.bat000077500000000000000000000000361255417355300233560ustar00rootroot00000000000000call c7env32mgw.bat cmd.exe workbench-1.1.1/build_scripts/windows32/launchcmdvs.bat000077500000000000000000000001011255417355300232050ustar00rootroot00000000000000call c7env32.bat call env.bat "c:\dev7\windows32" x86 cmd.exe workbench-1.1.1/build_scripts/windows32/launchqtcreatorvs.bat000077500000000000000000000001071255417355300244540ustar00rootroot00000000000000call c7env32.bat call env.bat "c:\dev7\windows32" x86 qtcreator.exe workbench-1.1.1/build_scripts/windows32/launchvs.bat000077500000000000000000000001041255417355300225240ustar00rootroot00000000000000call c7env32.bat call env.bat "c:\dev7\windows32" x86 devenv.exe workbench-1.1.1/build_scripts/windows32/luanchqtcreatormgw.bat000077500000000000000000000000421255417355300246140ustar00rootroot00000000000000call c7env32mgw.bat qtcreator.exeworkbench-1.1.1/build_scripts/windows32/setup_env.bat000077500000000000000000000000661255417355300227200ustar00rootroot00000000000000call c7env32.bat call env.bat "c:\dev7\windows32" x86workbench-1.1.1/build_scripts/windows32_remote.sh000077500000000000000000000004031255417355300221220ustar00rootroot00000000000000#!/bin/sh ssh -v caret@windowsbuild 'c:\dev7\windows32\caret7_source\build_scripts\windows32.bat' > $PWD/remote_launch_windows32.txt 2>&1 cat $PWD/remote_launch_windows32.txt | mailx -s 'Caret7 Windows 32 Build Result' john@brainvis.wustl.edu tsc5yc@mst.edu workbench-1.1.1/build_scripts/windows64.bat000077500000000000000000000013321255417355300207120ustar00rootroot00000000000000@echo=off set CMD=c:\Windows\system32\cmd.exe REM REM Go to correct directory REM set BUILD_DIR=c:\dev7\windows64 cd %BUILD_DIR% REM REM Go into source directory REM cd caret7_source echo "Caret7 Windows64 Build Result" REM REM Grab the latest Sources REM c:\cygwin\bin\git.exe reset --hard HEAD c:\cygwin\bin\git.exe fetch c:\cygwin\bin\git.exe reset --hard origin/master REM REM Build caret REM cd build_scripts/windows64 %CMD% /c build.bat cd .. cd .. set DIST_DIR=caret@myelin1:/mainpool/storage/distribution/caret7_distribution/workbench/bin_windows64 echo "Copying Files" c:\cygwin\bin\scp build/Desktop/wb_view.exe %DIST_DIR% c:\cygwin\bin\scp build/CommandLine/wb_command.exe %DIST_DIR% echo "Finished Copying Files" workbench-1.1.1/build_scripts/windows64/000077500000000000000000000000001255417355300202205ustar00rootroot00000000000000workbench-1.1.1/build_scripts/windows64/README.txt000066400000000000000000000002461255417355300217200ustar00rootroot00000000000000setup_env.bat -sets up the build environment build.bat -sets up environment, runs cmake, and builds the release version of caret c7env64.bat - sets up environment workbench-1.1.1/build_scripts/windows64/build.bat000077500000000000000000000012011255417355300220040ustar00rootroot00000000000000@echo off if [%MACHINE%]==[] call setup_env.bat set OLD_DIR=%CD% REM rd /q/s %DIR%\caret7_source\build md %DIR%\caret7_source\build cd /d %DIR%\caret7_source\build REM cmake -DFREETYPE_LIBRARY=C:\dev64\install\FreeType\lib\freetype240MT_D.lib -G "NMake Makefiles JOM" -DCMAKE_BUILD_TYPE=Release ../src cmake -DFREETYPE_INCLUDE_DIR_freetype2=C:\dev64\install\FreeType-2.4.8\include\freetype2 -DFREETYPE_INCLUDE_DIR_ft2build=C:\dev64\install\FreeType-2.4.8\include -DFREETYPE_LIBRARY=C:\dev64\install\FreeType-2.4.8\lib\freetype.lib -G "NMake Makefiles JOM" -DCMAKE_BUILD_TYPE=Release ../src jom -j8 cd /d %OLD_DIR% set OLD_DIR= workbench-1.1.1/build_scripts/windows64/build_debug.bat000077500000000000000000000004461255417355300231640ustar00rootroot00000000000000@echo off if [%MACHINE%]==[] call setup_env.bat set OLD_DIR=%CD% REM rd /q/s %DIR%\caret7_source\build_debug md %DIR%\caret7_source\build_debug cd /d %DIR%\caret7_source\build_debug cmake -G "NMake Makefiles JOM" -DCMAKE_BUILD_TYPE=Debug ../src jom -j8 cd /d %OLD_DIR% set OLD_DIR= workbench-1.1.1/build_scripts/windows64/c7env64.bat000077500000000000000000000003601255417355300221060ustar00rootroot00000000000000set PATH=C:\dev64\install\Qt481\bin;C:\Qt\qtcreator-2.4.1\bin;%PATH% set QTDIR=C:\dev64\install\Qt481 set ZLIB_LIB_DIR=C:\dev64\install\zlib\lib set ZLIB_INC_DIR=C:\dev64\install\zlib\include set FREETYPE_DIR=C:\dev64\install\FreeType workbench-1.1.1/build_scripts/windows64/c7env64.vbs000077500000000000000000000011731255417355300221350ustar00rootroot00000000000000Set objShell = WScript.CreateObject("WScript.Shell") Set colUsrEnvVars = objShell.Environment("USER") QtPath = "C:\dev64\install\Qt\bin" Path = colUsrEnvVars("OLDPATH") If Len(Path) = 0 Then colUsrEnvVars("OLDPATH") = colUsrEnvVars("PATH") 'objShell.ExpandEnvironmentStrings("%PATH%") WScript.Echo colUsrEnvVars("OLDPATH") Path = colUsrEnvVars("OLDPATH") End If NewPath = (QtPath & ";" & Path) colUsrEnvVars("PATH") = (NewPath) colUsrEnvVars("QTDIR") = "C:\dev64\install\Qt" colUsrEnvVars("ZLIB_LIB_DIR") = "C:\dev64\install\zlib\lib" colUsrEnvVars("ZLIB_INC_DIR") = "C:\dev64\install\zlib\include"workbench-1.1.1/build_scripts/windows64/env.bat000077500000000000000000000006321255417355300215040ustar00rootroot00000000000000@echo off rem isn't batch awesome, no else statments... IF [%1]==[] ( set DIR "." ) IF NOT [%1]==[] ( set DIR=%1 ) IF [%2]==[] ( set MACHINE X86 ) IF NOT [%2]==[] ( set MACHINE=%2 ) IF NOT [%INITVS%]==[%MACHINE%] ( echo "Setting %MACHINE% bit compiler variables" set INITVS=%MACHINE% call "c:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\vcvarsall.bat" %MACHINE% CD %DIR% )workbench-1.1.1/build_scripts/windows64/launchcmdvs.bat000077500000000000000000000001101255417355300232120ustar00rootroot00000000000000call c7env64.bat call env.bat "c:\dev\git\caret7_source" x64 cmd.exe workbench-1.1.1/build_scripts/windows64/launchqtcreatorvs.bat000077500000000000000000000001501255417355300244570ustar00rootroot00000000000000call c7env64.bat call env.bat "c:\dev\git\caret7_source" x64 C:\Qt\qtcreator-2.7.1\bin\qtcreator.exe workbench-1.1.1/build_scripts/windows64/launchvs.bat000077500000000000000000000001041255417355300225310ustar00rootroot00000000000000call c7env64.bat call env.bat "c:\dev7\windows64" x64 devenv.exe workbench-1.1.1/build_scripts/windows64/setup_env.bat000077500000000000000000000000701255417355300227200ustar00rootroot00000000000000call c7env64.bat call env.bat "c:\dev7\windows64" AMD64workbench-1.1.1/build_scripts/windows6411/000077500000000000000000000000001255417355300203625ustar00rootroot00000000000000workbench-1.1.1/build_scripts/windows6411/README.txt000066400000000000000000000002461255417355300220620ustar00rootroot00000000000000setup_env.bat -sets up the build environment build.bat -sets up environment, runs cmake, and builds the release version of caret c7env64.bat - sets up environment workbench-1.1.1/build_scripts/windows6411/build.bat000077500000000000000000000004241255417355300221540ustar00rootroot00000000000000@echo off if [%MACHINE%]==[] call setup_env.bat set OLD_DIR=%CD% REM rd /q/s %DIR%\caret7_source\build md %DIR%\caret7_source\build cd /d %DIR%\caret7_source\build cmake -G "NMake Makefiles JOM" -DCMAKE_BUILD_TYPE=Release ../src jom -j8 cd /d %OLD_DIR% set OLD_DIR=workbench-1.1.1/build_scripts/windows6411/c7env64.bat000077500000000000000000000003121255417355300222450ustar00rootroot00000000000000set PATH=C:\dev6411\install\Qt482\bin;C:\Qt\qtcreator-2.4.1\bin;%PATH% set QTDIR=C:\dev6411\install\Qt482 set ZLIB_LIB_DIR=C:\dev6411\install\zlib\lib set ZLIB_INC_DIR=C:\dev6411\install\zlib\includeworkbench-1.1.1/build_scripts/windows6411/c7env64.vbs000077500000000000000000000012031255417355300222710ustar00rootroot00000000000000Set objShell = WScript.CreateObject("WScript.Shell") Set colUsrEnvVars = objShell.Environment("USER") QtPath = "C:\dev6411\install\Qt\bin" Path = colUsrEnvVars("OLDPATH") If Len(Path) = 0 Then colUsrEnvVars("OLDPATH") = colUsrEnvVars("PATH") 'objShell.ExpandEnvironmentStrings("%PATH%") WScript.Echo colUsrEnvVars("OLDPATH") Path = colUsrEnvVars("OLDPATH") End If NewPath = (QtPath & ";" & Path) colUsrEnvVars("PATH") = (NewPath) colUsrEnvVars("QTDIR") = "C:\dev6411\install\Qt" colUsrEnvVars("ZLIB_LIB_DIR") = "C:\dev6411\install\zlib\lib" colUsrEnvVars("ZLIB_INC_DIR") = "C:\dev6411\install\zlib\include"workbench-1.1.1/build_scripts/windows6411/ctp.bat000077500000000000000000000002661255417355300216470ustar00rootroot00000000000000set PATH=c:\Program Files (x86)\Microsoft Visual C++ Compiler Nov 2012 CTP\bin;%PATH% set INCLUDE=c:\Program Files (x86)\Microsoft Visual C++ Compiler Nov 2012 CTP\include;%INCLUDE%workbench-1.1.1/build_scripts/windows6411/env.bat000077500000000000000000000006141255417355300216460ustar00rootroot00000000000000@echo off rem isn't batch awesome, no else statments... IF [%1]==[] ( set DIR "." ) IF NOT [%1]==[] ( set DIR=%1 ) IF [%2]==[] ( set MACHINE X86 ) IF NOT [%2]==[] ( set MACHINE=%2 ) IF NOT [%INITVS%]==[%MACHINE%] ( echo "Setting %MACHINE% bit compiler variables" set INITVS=%MACHINE% call "c:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\vcvarsall.bat" %MACHINE% )workbench-1.1.1/build_scripts/windows6411/launchcmdvs.bat000077500000000000000000000001101255417355300233540ustar00rootroot00000000000000call c7env64.bat call env.bat "c:\dev\git\caret7_source" x64 cmd.exe workbench-1.1.1/build_scripts/windows6411/launchqtcreatorvs.bat000077500000000000000000000001071255417355300246230ustar00rootroot00000000000000call c7env32.bat call env.bat "c:\dev7\windows32" x64 qtcreator.exe workbench-1.1.1/build_scripts/windows6411/launchvs.bat000077500000000000000000000001041255417355300226730ustar00rootroot00000000000000call c7env64.bat call env.bat "c:\dev7\windows64" x64 devenv.exe workbench-1.1.1/build_scripts/windows6411/setup_env.bat000077500000000000000000000000701255417355300230620ustar00rootroot00000000000000call c7env64.bat call env.bat "c:\dev7\windows64" AMD64workbench-1.1.1/build_scripts/windows6412/000077500000000000000000000000001255417355300203635ustar00rootroot00000000000000workbench-1.1.1/build_scripts/windows6412/README.txt000077500000000000000000000002461255417355300220660ustar00rootroot00000000000000setup_env.bat -sets up the build environment build.bat -sets up environment, runs cmake, and builds the release version of caret c7env64.bat - sets up environment workbench-1.1.1/build_scripts/windows6412/build.bat000077500000000000000000000004241255417355300221550ustar00rootroot00000000000000@echo off if [%MACHINE%]==[] call setup_env.bat set OLD_DIR=%CD% REM rd /q/s %DIR%\caret7_source\build md %DIR%\caret7_source\build cd /d %DIR%\caret7_source\build cmake -G "NMake Makefiles JOM" -DCMAKE_BUILD_TYPE=Release ../src jom -j8 cd /d %OLD_DIR% set OLD_DIR=workbench-1.1.1/build_scripts/windows6412/c7env64.bat000077500000000000000000000003121255417355300222460ustar00rootroot00000000000000set PATH=C:\dev6412\install\Qt486\bin;C:\Qt\qtcreator-2.4.1\bin;%PATH% set QTDIR=C:\dev6412\install\Qt486 set ZLIB_LIB_DIR=C:\dev6412\install\zlib\lib set ZLIB_INC_DIR=C:\dev6412\install\zlib\includeworkbench-1.1.1/build_scripts/windows6412/c7env64.vbs000077500000000000000000000012031255417355300222720ustar00rootroot00000000000000Set objShell = WScript.CreateObject("WScript.Shell") Set colUsrEnvVars = objShell.Environment("USER") QtPath = "C:\dev6411\install\Qt\bin" Path = colUsrEnvVars("OLDPATH") If Len(Path) = 0 Then colUsrEnvVars("OLDPATH") = colUsrEnvVars("PATH") 'objShell.ExpandEnvironmentStrings("%PATH%") WScript.Echo colUsrEnvVars("OLDPATH") Path = colUsrEnvVars("OLDPATH") End If NewPath = (QtPath & ";" & Path) colUsrEnvVars("PATH") = (NewPath) colUsrEnvVars("QTDIR") = "C:\dev6411\install\Qt" colUsrEnvVars("ZLIB_LIB_DIR") = "C:\dev6411\install\zlib\lib" colUsrEnvVars("ZLIB_INC_DIR") = "C:\dev6411\install\zlib\include"workbench-1.1.1/build_scripts/windows6412/ctp.bat000077500000000000000000000002661255417355300216500ustar00rootroot00000000000000set PATH=c:\Program Files (x86)\Microsoft Visual C++ Compiler Nov 2012 CTP\bin;%PATH% set INCLUDE=c:\Program Files (x86)\Microsoft Visual C++ Compiler Nov 2012 CTP\include;%INCLUDE%workbench-1.1.1/build_scripts/windows6412/env.bat000077500000000000000000000006141255417355300216470ustar00rootroot00000000000000@echo off rem isn't batch awesome, no else statments... IF [%1]==[] ( set DIR "." ) IF NOT [%1]==[] ( set DIR=%1 ) IF [%2]==[] ( set MACHINE X86 ) IF NOT [%2]==[] ( set MACHINE=%2 ) IF NOT [%INITVS%]==[%MACHINE%] ( echo "Setting %MACHINE% bit compiler variables" set INITVS=%MACHINE% call "c:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\vcvarsall.bat" %MACHINE% )workbench-1.1.1/build_scripts/windows6412/launchcmdvs.bat000077500000000000000000000001101255417355300233550ustar00rootroot00000000000000call c7env64.bat call env.bat "c:\dev\git\caret7_source" x64 cmd.exe workbench-1.1.1/build_scripts/windows6412/launchqtcreatorvs.bat000077500000000000000000000001071255417355300246240ustar00rootroot00000000000000call c7env32.bat call env.bat "c:\dev7\windows32" x64 qtcreator.exe workbench-1.1.1/build_scripts/windows6412/launchvs.bat000077500000000000000000000001041255417355300226740ustar00rootroot00000000000000call c7env64.bat call env.bat "c:\dev7\windows64" x64 devenv.exe workbench-1.1.1/build_scripts/windows6412/setup_env.bat000077500000000000000000000000701255417355300230630ustar00rootroot00000000000000call c7env64.bat call env.bat "c:\dev7\windows64" AMD64workbench-1.1.1/build_scripts/windows64_debug.bat000077500000000000000000000013701255417355300220620ustar00rootroot00000000000000@echo=off set CMD=c:\Windows\system32\cmd.exe REM REM Go to correct directory REM set BUILD_DIR=c:\dev7\windows64 cd %BUILD_DIR% REM REM Go into source directory REM cd caret7_source echo "Caret7 Windows64 DEBUG Build Result" REM REM Grab the latest Sources REM c:\cygwin\bin\git.exe reset --hard HEAD c:\cygwin\bin\git.exe fetch c:\cygwin\bin\git.exe reset --hard origin/master REM REM Build caret REM cd build_scripts/windows64 %CMD% /c build_debug.bat cd .. cd .. set DIST_DIR=caret@myelin1:/mainpool/storage/distribution/caret7_distribution/workbench/bin_windows64 REM echo "Copying Files" REM c:\cygwin\bin\scp build/Desktop/workbench.exe %DIST_DIR% REM c:\cygwin\bin\scp build/CommandLine/wb_command.exe %DIST_DIR% REM echo "Finished Copying Files" workbench-1.1.1/build_scripts/windows64_remote.sh000077500000000000000000000003711255417355300221330ustar00rootroot00000000000000#!/bin/sh ssh -v caret@windowsbuild 'c:\dev7\windows64\caret7_source\build_scripts\windows64.bat' > remote_launch_windows64.txt 2>&1 cat remote_launch_windows64.txt | mailx -s 'Caret7 Windows 64 Build Result' john@brainvis.wustl.edu tsc5yc@mst.edu workbench-1.1.1/build_scripts/windows64_remote_debug.sh000077500000000000000000000004211255417355300232750ustar00rootroot00000000000000#!/bin/sh ssh -v caret@windowsbuild 'c:\dev7\windows64\caret7_source\build_scripts\windows64_debug.bat' > remote_launch_windows64_debug.txt 2>&1 cat remote_launch_windows64_debug.txt | mailx -s 'Caret7 Windows 64 DEBUG Build Result' john@brainvis.wustl.edu tsc5yc@mst.edu workbench-1.1.1/icons/000077500000000000000000000000001255417355300146215ustar00rootroot00000000000000workbench-1.1.1/icons/linux/000077500000000000000000000000001255417355300157605ustar00rootroot00000000000000workbench-1.1.1/icons/linux/workbench_128x128x32.png000066400000000000000000000614301255417355300220260ustar00rootroot00000000000000PNG  IHDR>a IDATxXUɂ.̊ QDs9$ 9IR QD%'IbE9mZK;owϝ=}<-R_?O,??O,??O,??O,?7 R}GƟ;c ڎ}c3٘)ǙL3Ik1Ul{J*>߾}؇<,䵓/*,g6@*9taRۿ$Rة}#cQ>0l ~qwB/g,^n:Ɗ+WfZO),Vd_v5WZJcfͻeu$MOJoعo5lYfʕ+Y.]ؼy˒E5#-V._7|׭~ikVr +u$"K˲z} r\ƍ܌GFF_nL4޲0Ϗ>e%e?/fW  XQfdVVXL}Wn޼9GGWwcs{h:ACK&07C@ʺ?\qqMLԩSmnn.]?Tc42eʔllt=MbcNfca-Zgf`;O1Hbdfvu\;++ $\;;;u|_hHhxA -eLIE}ʏÌV hhx3 CI3dR1 SUc1w-6lI q(*)~ցNA=Pr,#1C.ʌa=zPZR(,(=9-#k <-۶yN:m#Lf`P,%.SQQWJKy-D߸+xc~)QTd#9u)Q`,2eyqBa@Ξ)GV❆& !y ؘHJeF c0}|ёJ<5 0h}j T~46Sv}K,?M;P ,৫_&֋`G{[,x{1[ 2Br3` #i!%-+ &!U%(ShiEα 83%EPJ 13(AJ=|Y\VƬoW[kqb﷒ʑ}PTPh9kwJ2?0 =ӧW tW0&΢׭ͲwsGc\ _T恢 L kvvI@T\ *jcV HmGMU)bc㙲mBL M-281KOkEKm1 NBfZĉ,l7Tm'N ]|r,a6o˘S>^z\heM bnQ><}D0XئP0uT''|ߗtzVLp|njkv&ŰPl߶*P5 , Wp*X VdUJ74 `iəA_KJ*Ӄ/RQӥhi>N\EUMR`ai+3x␗\AwbLbn=.]<tƷy&cq:IXW m8rrrHƇh | dbea>u ׂĶʒC.'" }+~.bC^NPKcbICP `]fywhk{<=?&'3QRr Ms wjw y}< P(R3}HEѱ||[ f0`ώcַXq;]} #ǘj_e}ٔ tfOFL! 6_ gggfb=Bjב%7.,8 WqU>/9iȇi-}+Q Yy,lxظlaS[axbkkKcxxe z<23Pt4.՝è 6BuS }"A3tahKviRV=zqUwMq֭[,02::wI7( w(6"Fj& Y%yˉ"ӝSSҠ#N Jr]ئPՎ=Ζ*u^8dOu@m>fkq"Gvu`C{^;yHVg{) 0G2k`ka_`aINc#6ou aᡣv 2ecwu m`x,^c (~}[Ev|nIؙA] "y1LQ|C'y'uq#y/d`mcMAEEҲ PP)3;PQTUW L/BH]tr*E቏&磷i>ث f|wZ42\3ᡱ!R㢐9i? S`Xm$Dރ)n 1v$ήcW;QxvNxx*M%5V)c-~?/~G;i;d7MX\q\N|~3N-}2̇ dʏ>tgspfj062$@LcZ'=g+SXlLPX3ڏkCEYBE  ާˏ,^4M5׈#傩: PQUHK)(AFF 48\]I$ ٳX aXNn @zV;j=uRHVY/[>-B(-Ab8?Jy\=&sqOı *N>Ut*/ENx 89ԂA<1wU"^7#]X[[? 'g?T{g5Uh#<$E!Əy0\ƅ>5(LNFqal eEA \e6\lZv 1f/TL;cLFW&uOss<[1 RXh $g}K=˒_t7&nSOfco@z׫^ܫ#8nYҲZTמG]k/zky0-<ɍ^\$ҌkhN UW2 pE܁8UT/ц(.v9hұȶ 2=Ÿ4xOݴ}6zxQcCi0bh؋x9.4wti*VV#1*]3HVF>̢(8^0;۶n +ei'͘_ c'و4Tl+=fbܺ`111KHBIF ڢ@__/:ۚނF4661nbཽ:;TWKhm?j' o@kz"iܴ؍*?$ed`db+K 0`Y &+]FMe 1 *ƿ)OacL#bnr~ ]쨷 MFəӤG|,ϐ3;UE n߁mWCsىf*Ź Tt`~، 'Q^߈bJJQSY=$Pp.$I(CNg'_E[^m-?`gwdJ ~0Y%Ogy $;NH턞-+a$F%DY+j"EB/7B9xĉ%[1Aa:9;8 [p[ԪIBH;DG8Q%@b ! "T#S6 4gҝ 2Ig~Ű/=]pbz?K];ZÑu{ ~j8+;'x [ uw0ੀÁ>hF3?AN!Zr75;pkxpJhI&2Jy60ȃ PPֆ fp! ANae HMz*puNH\cEhpKLLBzZ:>|}#ܼyD7 'hi/$Z gსOE\L/ aeOt)Wk^󥒓iifjXEAۥ5ϻNGt;܀SCj5D?#@jP`Dž+pYgm17+BGe ܹ÷#2yԿ,g{,NGU2T~02X8wbF}pXA‰o ]`U™3'B32VQS}C+H ".lƊ́h$ Q#Tx}?~{w t06G@= ` H[ܳ |xe+GyѸ)c`=I@ k 3ʚH5\DF ^@W[+ЕK6r8{ e8]}Q nՁku჉c\ܻ'~_+{ ;dLsc/#Â!60{ ߏ0^-fj߂kP.%Q˪ lP߽ mR5~eÇL$o|%ɍ0p}ׯx-.T@hMh$'>GוABU],EtX(RRq)7Ỹ\.0. `dj z...$f*{(0P 1ݗ_~zԍs$FcI î8nҘ]1xk.|Lcd=?fΘ/GߴpϜ4*ë¯#fׅS-d> <BT\_;q~ \spp8oω׹qxx}o`l#nfCm4wŋx<= Gб%=B@|f$zT{)(AYN`/y('OGOB;"=!%"-Ғ`nnKKK4B ,09 O2 7`1qa~:FQ-8a*1oQT\Yit4;8 S+7X@Oz";X|\.1?R|ȉƝG0RA`L7|R0RHxyA@c7BF' b5;)\3lW2q1H }( CƽHŎ吓<4 I$l`bbj pvv1y&Btyxʅ0R͋z[%'hp!)^[*-'ߒ'&?`l{7gfib悰3mII*Bh-jkΈGL,K^c}}X[!8^8@ߧTa IDATد xDMܬ 7ޚmlo5(*Ex̍KGqsx ;1i{I@bln— iE=t[aaK%2:q.5S܎;`,,$$ -UUUhjةO=c9,4ހKD>ŋ f\`x.7fb+ssfAj"Zok;w} J@GrllqJnXc/3,КS$aGn!Nς_`bcIY#/Rt/ڣH~ `ō3$&yB= G9 'ģ\ [K{\Sě(Bafع4jxt4##)F/  8u ǡ,Mb Rqq8x$Ez`mT|؈% q11hiiS3}t#%Atj=5>Ko0BJM\/- /=\xv>b(ӓ/!;DΥhkmQRRJ4!'P OiCw{3Fmz أ[c\;oāw1h6ih،s$p>:]<ƒ/qzĴ[~$ Fs1Ń]s>Ƌ8ކmظ8dJV Ư'pR , 7Ѧز~ 6m!AAP$vp_ 1Wx_M7] 1_dp w (GdW_X,VEltXzɓSRIxxfb 0P[T"M.4x/tسn \7z!a44 (T;@kǣ8e֛_PEpnr|)PÃ`~WpKɆasNδ n)3.O\t|*>[&p\ٙj>|[R-aم8Ń%ܱ"EFw#RaJwÅxn5Z^kHsůȚQ]t[p-z(jkn+י7neվ$r:qLݧV.\G碵hI; apsfXL94Vի8x Ciap&4Dz@(<뾃h .^3P22p_f*>3ùE@ZpC4^Sڼo'ɓC"T>|5{L%g6< qO LWagKʰH5‚hk -y @u*)!UrTHHÐ T>B2YQƅ3pd,db+&[<ōW qcb ZBqDϻwnk 3@G { Gv@n`zijX&$`2~ q;~r9k>?F m Ma4[wQZ|⪮p?BpV:\7`x~2HrEҕ rS%M'vdkttuBB;GD޼yC7Q^Vb<#$&Ŏ:Mk6^[ō; 6/k\|2i ˰B@" N?y뿗/b#юڪ2,ABK;o3w$=| rr ^ZpB$I0&Ԕ#'Ht^›ׯ믿bp`uu1z[ 7xTčx|۞ /3"pMT9E(=Uk!B1 n7Q% '(̏қMuGȷ@4Js!MRDLr@\2‰ œc" 9=O%ܻ{]]052b_\mHgNgr,fƫ켇;gl|l 1fX<_tLD 2uy}ZD)i@JJ&\5CM  ݻwx5>x8VZ Zj1 N2ڬv}x^qW҄h;߂ޫqRvt(5 r 8#r!lnJx Lb.=gEQ'=2妥 ٪,.&Ld3 C؈ + M7SHCCCЈZn7mڬ_{, ##H|<䧆C?^:465SeeD1zX~v*3a=Ð? c.JcO?gOxM%ډaıcHIWkG\n7M18/'K]$|e8))HNL@Ol12#\Im8f '8kWrpm A/0xpc؇wDG᪬@cC=\ļo#PBWp .P حs0[ 0Ӏ*nU_7-I\^q~*.'F^nx1s{qG,)A.6aXr=V\Ea J렌ߓcb`2 >SK%wx>|{|pb7.twrȶ+% g`(+h%?{n" ހ4x]-S;!m3|I0OP~_o/ vՐcUSE'O|7E͛Wx5>{ H/}yvbHz.>EO=‚(ЇQ0߃~Œ]ތ}a .p$=|pn T [QRZC7 ɋ|r ͮK89s>TM^@A?5p3 ;ElHċ @6555tϿцL" >r0j oلF1 ˔R`g =##ثɢO }Ds%Xtt!K73y \n>FUŢX,m۱s=6/M]«p}\F_Z.+,Bjhyr%2gqg**۞B2=h%U#x`% #-G5n+ "y׮S5u=h gb20ذyfYU XS+`%/yd六q_WuLS{٤M\A*,:9F7$v fjEbr8`llu]d/hU_O#]@Wp9|%B= p~6nm00uIG.&Fq:#!= e0m!"[Kd7kL$!,&Sh 061=By.j5%BTGx%<{Dcx>֦ė<`VĨVwG\wMk1&w7<}*/B.i@Zxq" ={I| ]>a :D~~.֢p j -9`׮Rmu0JO$I(<B&H 2/Mw@D7 ;V˘#<8Qpv-`BPOqGane 9ⅭBd\s"( "f2郩7 ОW7|.vX,aǰq lL(ISEԳRR۱az[7m05M7JTTo'D<~=W zĜ^"~ȔL _ 0dWH6T@uC|1탷q!WfIs1 `?(<4v?7HyENn&,L-iܔ6Wu& GF3\ >4HK <p}e(ػh+#)-K:0O@\dʣht 8XkN87ژ6 ~iY{\=69=nbGg>!vP^ \r"\l}4hwM/^J5+wY-شa%v***`4DDEcl۶ܹƍf^ׯ~FFm>zϟއa(UXbbT9g3AH~wׅ8CǺ[+޿%ꨇXп{&J7ݓz?.C{8qDܺ7-cIևR7˘gOCe}hFv6֡s`qLaȩjx(Jmh,T]4 .$F!F zmw=]3*IWK6 򩰰q0.Ɩ~<lamm]#"{ zQh6€tZ'@|$Z{56ԡ=+fHM?I)$P?7O a !{#Z4fvf#IW{zF9M#yT淯 9=(\+Ή̃Ȓ4H/ƣ`3\}:ZQU]I|1WC u𽇅 YP ޻*"Bh D5eeWt6 P7A^|7MMyy$ D~ O]#44FF4(K ; F%%%A\͛7 lY0؏5pť8c i FZ°Wޅu$A5XT"P#f9#q"625M璒ĸOj\x1M֗/_(233:B\R‚s9%YT$ A%wdehI+qů*|@||ME9xlkm8֯[!c7nŭ\9O޿@as8~8aJ/e'KKpH+Us C I==K$0D? L&22znķxKo3>4ϔW vjE+L ݳ L qCQp􇥭]ijgi:DLL_p$BETrO.1NmRV|LLLct? S~O!*"!$$A dHNoQǦ<9 )>p^RH1_47b# I-)TBb0Px>^Ԝg[vyԐA !17(zH]UXv3J1+*Oǩ}5?I?<GG26HBRp׈*j2&*II8wFt27"}aMl Da{$!,4Spt1Z ^&. 7ŋ T6њ/Bhz׌ e*s@;sBzY+?8=D}Uܾ=L ^G0\XFthӯҢ:9:m uh_[ JpV ΏBK~Ċ^F0-i!r#% 9i"+ Pm :{/mm/UAP GƑLClE܈ƽG]E(A IDAT ,x3<|sx o$z՛[Cz/oo=,++ÙӧL=tTY@}yaDw‹虨fh&`RX'bO(,G}]9&tp{/]kvzX#O <{JƠ |/WT v@@܀OO/buugq0`~ #R"ЕoddL/WOZohlB`Jy:</x /qFW!WUTكkP,IRr&o$>4$Yc;(c=yQO30p KFޅ֖o[ah`?1;OPUeѦ [lmC;0D%sDPPH0 A(mٶ plgf0Zν9U_ծi4e$߰~42ޢp{`ƈCw[(cy1blHx3-`vqEPr" oc{u\PEf"Wxm$\E}[degsլ ,7^}BNi&lԹ˙+V!riBGxzf.僿N33Œ?gD#]g Xw 445`|(D$ yÅTy {yuy6żqj5/B4&샫P\\7?ěG())sY͸w4c1/ǘ1OÆ SFʨjB6=( ;Tx*yͼ6,ZTg /vv)4ZK C@ 6{b"9ԹkJڵkx9_ \eF0[}KewpE.'w,.n ~WζO kz ZXm>]>?vTG=ؼe r B&u\?l=v2=m(rc3?b<&TX;?=ntth|wJ*>e@Fhn}4y֬{"ù~UGp\6ϵXcciֲ7D 0Zچ̽kƍX,oVVV2WAOO:nfJH;'NJ'\1޺u# V/L-5u!qW4@E\'![^mS׊$n\ǯBKqFQ-Iiq *ZLLߦ l7É9b4CEn362"Xmi"EȻ"CfY5 Pr!QY%p3R3"^z }x 0*_m`!͡} rZ䓎H$'DCgW-hxx߽4v>q3<]{XzlfS@*eʴ_pp0N Cud tfz YYYs0iS_ q!$RQl`|`.Z$y!$x=KNݏOQqlV/fZR+]+Kq[RЊL0H ;G7l3>p"xēבu9B_ % ӹK=1D"Gݞ <1ikKOjjAP^O ִpɡ_mȀV˚+//;sw x%CuM->xKpH.RR!(ht = =/6w ^]ǘ-Llb!ͺWog30$ lBk;&jjf1ؼjo݁w o#1wPm]n e7bc) V]ا K`k>U܁)+AEMmeו3[xt2)m@;Zb&(Η@@ @P|*#BS<UNjؗ@eu`Z,zj(Hx-HR=yJXMͧM Q~E]:@Ieo'#*2B}BdT@7gq]\'RٹHbLf2wCobU075$]35J͋$-u"=e-syJ;X0s1 &[c[6dCy;F;w|3fX ! ,.|T; =ؗ LEv?WvC83/Tέ<' =RA=]I4 1-MUXy`c7036~3!)qBD |f &&iRp $Ξ)y# c0oV;'Mhk!S*$C _vck  e=:X|e\.y+g:PWGBbq(ATbn^.=~za ܀oC2 0g*%_k#NʷC2/mG;k%442j)20j;#_5Ġ ?4T2 Qx Ʀ]냍ۢn5lM_B͕pP7J9_{ϲH`dI >PWbfL : If" VS'i>|4BYi )=[L5[L&t'/\^|ߠ.8t=ջJJcz1?>ǛjEFF5g/ZBN^vHݽd $$ǚ `@_,ff3$׿TuH2GL X$A-r+(+ڇ4t6y )GTA"۷dY!lcIQHKX0X%C"0^g+yFPwu?u8ʪֵ{x(%[HO@pqqf==bP6[Ƹ3 //ـJK˘säD!4d9ӺUQ)ʙ-cZ7\+qP^&!-%9Dko|lf[.$$&gΪ+,,lU8 qVTC~^-2F`̙4GG{,Ƭ٪P6öZȯ |_SSK';ZA1>s^_ ҷ #{?FdctfU "zGux)JObwNxvrC&akcŻ3gY0荗L&Ap7٣^~(CT?~" 9^zm݀`y}{2 q(r}vI p W{[X/Р:gkk!h]BBDF]º~O}E&FutJoktttEUis%Sh/Sb٘:C3bJfT#=yό^# Wbk,*mM{yS6֌YsdfJ15x54]  ߄?5w< )}|xb*2:8:1`xccJ'F/\1pdR<}e,r 't1# `db Kkx9#=3X]k0ZbH53586An\4MƪIڨ%0Vq@B[!h=E~v' ÙM2PTT7njZv#G-N|+WbQ۶ 7nTM#1b-&ͫ7!.6Xn*D]Vg{ &72f!55=9nCH;foq#x?Oo^;Οp_3g@v Ձ5DnnFxSSy(xP`ee%RRLiHu*.j]E\h3MV!-G"na$&bXE,3t1j+f,4i\q-Ǝlj}yv}[[Qoa䶒e>}a 8h`HD))9YHMWnd5Hܛ60ja!1_\sxΞ=\N&^~!f^%x5o/zEG!us1!)W1_ieJދHf>.lDBL[ǺV8BbRS_Nkܓ\nS}Ρ >.&ee(*,r3L`$E/&/rleڄs5=`h!?!4ǘ&V+7gqW`IDweUbރ.ˇQ@ H*i5÷#$4)['))O' G颯ӞWjjhbʈ0\N' 7Pn\MVCJw|Gjkֿ\5!C8' PRv pz0)Y{+>iϚ"߀W`yy(6noٰ#&%,4[mg`!,qW ~Bxx+1AHHGC8@ Vd+9q;{XƋ d(E7Ly9#fq~^UhM<-b 3yi7}`GQq H?Bs}~g@p sOsз}bth- Qu46z`Ҥi j`FJ>\m? aÆ͛7?->2;)9:׿m߾ۣoA?w뺍5O41B=7X FfU-PlF>¢Bދ U  G`Ӭ0ݶܮ)$"B`ukxܝf1S4Z$&&@ UV"˞s)fҕXR|۷4 (QxDT:P= [4`).VüY͛>} 8eW޽W*(|Bjku "8.A$N M)6+N n\1;!-!VG;gF"3555-7@iisr?v̙%gjkxlc\MA h`|%b5`_jPN(K :Tys0! RBt7`|C!3=Œ{3PfN37g3}CV䪙 W-6}昿D0@4p@> Qݺutdh1ND{7iku2c:u& /fۺN=>ố)j_Z A> dHmˢ f =讓0ؽՑPQVi" GhX3;f/B6Vwa:c([]ܺyJEIk`&s ܴ]n]&ƿyFiHĿОLh/)n/0)/֗ONF]ػqcYߌ1z\hL51bq 'kw\ |bg](?#z1覙 U˝BrU ƍ9#gP &5dW_Msޕ߉]d}}=IdIqLM5- . !'۫JY|9h`qVLIct&չS, g {qG u_ c[どM)@afWw_x @);X2%ȴw^ZoQGsEG:Ww?+>h]%mQ0@ɟ˴WZV^ax>K}Saڲ|,LNm Os: coc܇$=>y#,ajb*̜1qQaq$7G:wF8䔔T޵F I7\XeGr""¹H_TTā@3y ?&'' a]]m!mN[ٷrKxpEK3K ào0\ɬi>a"tiv=v( L\(MF݈ FhP@f u-- pt~]>o1Y\PLlPl VVS(-QHwp/P?^Q`iܸqm5mw#nh߀!F5T0'5:L]N+/] oQ ߇'󠯯_Ѝ~\ݏ/(JII1iv_]ـ=UЀHIDAT3>1oԑlVϔ ŧJqPP)dgghcc-[lʔ)222ee[wtk?|t:u$jD2uȤdd~=l}C##Dnڹ$'!5=r/OOX[YASK40WH()) >md5vwYZc}5_s܈:+ppvlps}@cc*@mQRV?~|3jqEK-`na#X0lP U* 4)_~鳔+LS]5~|~A1GA~TӷeoGi&牉 Rq;cuCirTٳwoטXQlL,{O ;oGLCkȯk? xa8?ҿxﶘ 4+޽{Iĭ[ϭ^A9tm,{}3" ɯ?!tH:@tC:!tH:@| ĪIENDB`workbench-1.1.1/icons/linux/workbench_32x32x32.png000066400000000000000000000050471255417355300216540ustar00rootroot00000000000000PNG  IHDR szz IDATXW TSW~/l*Gt,QD;jE:H."[Hb@YB$@A@FQ*E=:vvlvdI'xVy)t(z!'djW?rB5j(V!#Bi4y\z!ݒ]<0UMyg4e2~ t:۞;w":'꘶)DC[ןڏuknicsҒRRA4׫- +/_fMXN?688`ccc!i{΀coen.fjbnggb'"þ m>s+= 'f=v-:Z d0"<<0}tO?m¥/͙"7z%BtٔCl͎ONJUc;Q.) 9dFH$bhɐ</$yf[)Esu9kFZ[]7T1#F/p7g͑@lS@Q^Z+xR^LNKx@HH0g26#@,[l!َuĘ⋣_OYd`H+Ts" Rƚj *tg1{mPA^הlm 46a-G'`5_N, ,T|vzl~҂g=؄x@[8iJ9nBRJʓp4o@\kA-[\Hŧ =mp$]u4 y&^Yd}g֑m9 ;@QJw;b'p?7rPI~ )v'rlA l7gQAneKg(q@ ݼBQ?;N>$re3KaVMۄUF:+(ڝHcV"j\TFz`"\p<]2RY _}U #VCPwZӐy0t$TW1XVe!ݰqAnی93-AJ17{B([e L\jY1Jk)8 *0zak! @36V4I:UXCq:K![Ġ[aEHv+yh| I?٧4J3b=v{)ϔ@5fںҢ2kt FUƺ~xپ^>XcDǵ8}?t5\a%ކ!\D.=&EڎjO%5bO(+p6[%Yc PdƒۅX+[ CmjU8/lޅPGVЌmejuj+3q"pR1㑟~`yP nz¦ljFyDyd,Rh7ֿ`dGQU"<:\=(w/+}S+a1֞ IqEz͏LNfs@ r`\)%"x ? BT4PNVw'60e>boTG?W _r*SB]Npqp6< ]?gKӳ.^C+1jrstSPx&,"ha=ҿATI.\ Yf[~Y[Pm[V 1{`IlsxK,ul!4L 6-_LɑTp?{T͸3jF{'gɧԑeTf\>8{ |aD?//9Gh/xЍs(ꗌSSScġ8YVVA9gߑz粌ԸIEhiPQ;;`~R[)/WNH^on=Ƃ=wrpqW)7#ky*p8yM3ΘقE^dU)aD\rhvԸ{A܂/ssLCC^f+f-3;w[م9^|=F4U w7mTŊ&|r~PvoaG,STX8Y}JQ25AأY[!TX,lBnnF 0z8GR ^O,LOO'% M+!9B,E"J(dRRY@C$&$p /oALlM3q6|}e$~t{hhcܒ >sSnFO_oo/f?zIENDB`workbench-1.1.1/icons/mac/000077500000000000000000000000001255417355300153615ustar00rootroot00000000000000workbench-1.1.1/icons/mac/data_file.icns000066400000000000000000006737521255417355300201730ustar00rootroot00000000000000icnswTOC His32s8mkil32ul8mkit32ct8mk@ic08Jic09xis32Ɓ+:_No{5 sYfvY@vxxulnze!jo}u_uIp]bfUu炀ijvCu^ysy{O[wY0\~X0 ,+̀rM+=_No{5 sYfvY@vxxulnze!jo}u_uIp]bfUv炀ijwCu^ysy{O[w[0\~X0 ,+̀rM+:_No{5 sYfvY@vxxulnze!jo}u_uIp]bfUu炀ijvCu^ysy{O[wY0\~X0 ,+̀rMs8mkSš͛0i*\J6!1dER Sil32uů +G& J@ldukTpMvly9*6bn\{G)Lbe7lL= VI{bʺua{Rb`dž`Byv~{^Qk~;j_tfuHLft9_]y[a]zӃFzZo= rohMZ4݌cnGRr`Z]7)ZܤُsʄoySbjI[JXK9/0q~pgW]SKAH:b_4r^inyPBhqq^MWVyfx}l~p^UWjWl`dovNqЯwhwfn~iqڏZ]{P.5wƄz`֤zl`k]mB}~j_\T!&PW\u`<жÊ EV5Uů +G( J@ldukTpMvly9*6bo\{G)Lbe7lL= VI{bʻva{Rbadž`Byv~{^Qk~;j_ufuHLft9_]y[b]zӃFzZo= rohMZ4ݍcoGRraZ]7)ZܤُsʄoySbjI[JXK9/0q~pgW]SKAI:c_4r^inyPChqq^MWVyfxl~q^UWjWl`eovNqѰwhwfn~iqڏZ]{P.5xƄz`פzl`k]mB}~j_\T!&PW]u`<жÊ EV5Uů +G& J@ldukTpMvly9*6bn\{G)Lbe7lL= VI{bʺua{Rb`dž`Byv~{^Qk~;j_tfuHLft9_]y[a]zӃFzZo= rohMZ4݌cnGRr`Z]7)ZܤُsʄoySbjI[JXK9/0q~pgW]SKAH:b_4r^inyPBhqq^MWVyfx}l~p^UWjWl`dovNqЯwhwfn~iqڏZ]{P.5wƄz`֤zl`k]mB}~j_\T!&PW\u`<жÊ EV5Ul8mk*;BKlU 7Gfcj@# " +x]Z x  \u hJ  bʞv56N  :BUKit32c        f8 f9 # mS\\by `y^W[SkF3 $s< } \Fw+N*+oOn^_oM)`HVfammogYaL)'';) >bYZ`fL+ ( HhXH3=I9$ ?cclwYOg{n  (  ?cs[9Jg]@$>QX\rVCfy5 ,PWH;;[tyI9gaM;#-@TUCQnjNCVpųp3 .>^jkWcwyAEXON?.5APYDGyoIDGNhse: 23fder]qb4ENXU@14DRQ:YD12>\vkZF=VkY^\QgytI6I_dfzR!8NFB\iboL:[e]D:JX]D.ICDkQ1FDUP>A @%Ymx}6D[_M;HJVqOIMKkx|`[s}ƴiN% 3iK E"6Nd}ˁ=JbSAJEIHuPDMUi̹\cdir~rjk7*j<H/9>=7HTsw6ZVMKFB@^pbOZkyĭVLae›yiRJJfbmHH00.1CKFIKeQKcVLHFHPas][XbvoXW@StpºTniehta(P >[6u?DrzTNWjVL^slQM}°zvЮp>S@¯tsfuM9JNpĴʽȍCHQ^oPTb~cIۦy}ː{ewIT snbeN;81Ľȵ`>KW`uJ]f^wku˺ȨpVmɳVYZǐyy[C*Hɺƣ]CQYpzyQ\[xvQ[m}ѿcLùYZAx[Ck̯ӆCLSazwYV]czɢb:]s~}uɦUXʖeVX(tlþΡgu]̢~yqܫ\IRVqu~Ga^g}LHkqpitֻy[^~uV׶K\ bȚb{{mϜs~wlޣRNW`z|iOianoHWghhjqzήccaZ`ݗ,_;qWPVk~ӗusi]NWYs}ORkkv͜_H_abfhov|żu~wɚ> [ s^Kj|tvY[xҏonVyࣃgP_e{eKstˀER[\acfmrttyæoӍ*_7r|Rfvxlyq`ڍhil趉nKeewԾ_Fs}ƾ~GOY[`adjnnq{|eo_IY~yjcWowvrrpRo`i绔vEiowSBv۳U=PU[^^`eikt}‡fȩZaR^UzJ{^[idi^ahĀjatj~߶}{;et}8Vt]@FGQUYYZ`flwƌsiw#a%ginz`uUqPPi{d|ln|Ɓq7\t~ϬXAbxTJTDADJS\^cjov{xq˜cMb SttrxvRuξ{|VA]qtj~jo¸xBTo}ĮP\sFIhbE117I[dilsxttжѳlg_5lbmleH?|ؐn}}a:[`pmlnxjltuhWdv˟isEJieF0=TK@UvzxxvطĂNwRiýfblVE_fv}e;Rbztafjhza`wpĒpisą†}vFNcMioyri}nN{e\TGJdڿ~}ƴɶb>YhVUxyvhcn{Sg֣Lb\HU2$tT m{rmsrIla;DєlfĨg9GKA@Moģ{ݯ[C^i`hvmm4TwuZhY< RWv B "8ôddQia@9}opl?:G?:8KrѶuԳ\Jkfhwpq˷rLYy]mwpT&>/x^fkfgĽZmvYQf`G8\snN4E@921Go׮n̼fKqgoΫα~kWhjsT>` x al]W~rɅN`cQb`IH7g؜go9?>6.&(@]nPxqxħǪ~tȺ{c}{ru&=y`5Wyrg~ĿsS^aR[aNQI5FkЇuÙTHO>-+%&0@S~→uX͖j|qwõwkrS?Ew7u3 ddPxȿvUXfh[YM\bR]fU>Z|UTO37DKGB97K`vԎL˖›߾pknjsыV`A1dl]jy x li]asf^WfͭaJSdS^mGI\dVZlrlbM>DZgx˃AK60Qdw۔WԙuӨwknke{w\>?a|mgs !zWf"X{ghZXspb\VgnFG[bZZeqtnbUIHNnk<3+2?KMF>91.--,+1>aܝ]ܯmt׷mdl_cҾu8Glo~'?z[n._s|jhZ]zƭ^kslidLC\d[Xajoqja]QGKfi<+%1FJD@815:;71*'"Bުs¾~yTŶw{hPo];TyŰmzYk(c_eegZci̻YarkV@XaVPRTU]hmscI9?`uO1/IJA826<>=842/#<۸©xyprVN{ǹPYW:\ٻm| zYl&VYcgmZior׼]Inh\\[XIGQLAIepXNAATqฒ{`84CC:28ADC>:525.GÒͣ{tlb8TνdKq؝dBZr~z\g;LOaijVixyۺd6OY|y}gUWEXz~eA5\td\UFAKaɽ|X>745?FIHD@<9<:Ko]A/,ɻN\Μs]c˿yx`;`UDPcmiqߣp9Psj{]]di:Fed\VJCCOgКH+9?FKMKGCBDGDIZhŃyx[?1:6\Ż]Oǭu[z)NJy bKjg?@a||BNykypzT<[bXQJFBDPfqM43IOTQNLKLNQOU^tjWWD>A@ABopLnZ{p" R rOho;HN>m~mʥׅHQ]RLJIFECHd•d=;OW[YTTWXY`t|eTQLG"EFHAMӺQc{yWƉ?X-z \Ghd6zY@a{uս˵͟[aTLZLKLLF>8@RcqphΗPRRZ]a_`dghorbMJOQRQLJMNLCHm[\xpilygg|4Fz`;`M@|a@Ynij}xacf`KQKTJNOLGDHU]dr֘{tqdZbhkqsqkc[TTVVUPMPTUQJ=lʛf]bnprtU`}DŽy z\iCCFjBWkmdX\aadPktWigTX6PNOSTX\^dhosͮ`fqv~}{re[XZZXSQV[]UUCGТqfYsmtxWhz`ǀy xRl'8U³wosKXtxr`eknm\]dQ[mbE=>QNUW_ggn|~~uΥҏ[nx~sicb_[XX_cb[XCLѣzXLvhuiJgl)y bv1[οvds|W[|~c\gpul_TPZl\`D:QPVZ^gb]{`aaw~޳ŐXs~rkgc_^cijdZ_A`ɢRJsmnugbe-Aw6D+SЮrϱg^py{ucg]OtQ@LXZX\lhenimjݦSƅ֊bwtke`bkol^aXMſ^Rd^rb[| r+hֳݼoUata`Ylj\JUTVewl{wgde~p}ޕi{}ofabjnc[Z\Ԧ`qZGVMgI{L &uQ6BkeA˷sKYhssp|zy`\d~MycGLMPUOI]iaU_jTlޠn~pf`_cbXS[ogQYyw˹h5rc1Z.w tok^;I̷UVwbmdjw{`VijsrrZ]eZOVXC7HXTMONSMlxzqf^\[WMRopguc\ɼU4eLv s3dWFH? FfMĞob[^u}oY\iZ͟rd`_wʹaakSHEMQh}轀yypf\ZTQLjjvvUôxK;L?,jAtҿrta?gwtv_^gpcؼpj_jo{ӬhTTftz|~Ɇkxna]XNQSvy]fX}ɲcG>/o7[ujZPA<2Rq`fn{awugwpģjiϪ{\`ny|wƇfc_ZSKVbp|TOƵc`~cK<+`>;9?KPQWXZ^futaBYŗudl~qr٨vafx{{՛^XUNLbu]´Z>PXC2"]+B_yMdθɃsx]qx]ٮgmpqߒLPJQuvIXu\GA@HL?.Y#O_~xo[PYO\f[Tرfdlsa8Ifpqj̤p#5DNUftswpsxXjs{UQZghUMR¾gE,B;Y9D1w؁ .<ay  (0Rd_UPeĽ%T@b (9b{n=wĻwWa|"-UkudȹVrh' 4S\a|̽{y||}xlK$+3>Uz|cP?7:6+  DWirkZIIJ?'   ) >bYZ`fL+ ( HhXH3=I9$ >cclwYOg{n  (  ?cs\9Jg]@$>QX]sVCfy5 ,PWH;;[tyI9gaM;#.@TUCQnjNCVqųq4 .=^jkWcwzAEXON?.5APZEGzoIDGNise: 23fder]qc5ENXV@14DSQ:YD12>\vkZF=VkY]\QgytI6I_df{R!8NEB\jbpL:[e]D:IX]D.IDDkQ2FDUP>A @%Ynx}6D[_N;IJVqOIMKkx|`[s}ǵiN% 3iK E"6Od}ˁ=JbSAKEJGvPDNVi͸\cdir~qjk7*j<H/9>=7HTrw6ZWMJGB@^pbPZkyŭVLae›ziRJJfbmHH00.1CKFIKeQKcVLHFHPas][XbvoXW@TtpúTnjehta(P =[6u?Dr{UNWjVL^slQM}°{wЮp?S@°tsfuM9JNpĴʽɍCHR^oPTb}cIܦy~ː|ewIT snbeN;81Ľȵ`>LW`uJ]f^wju̺ɨpVmȳVYZǐyy[C*Hɺƣ]CQYpzyQ\[x³vQ[m}ҿcLùZZAx[Ck̯ӆCLSazwYV]c{ɢb:]s~}uɦUXʖeVX(tlþ΢gu]̢~yqܫ\ISVqv~Ga^g}LHkqpiuֻy[^~uV׷L\ cțc{{mϜs~wlޣRNWaz|iOianoHWghgkqzήccaZ`ݘ+_;rWQVk~җusi]NWZt}ORlkv͜_H_abehov|żv~wɚ> \ s^Kk}tvY\xҏonVyᢃgP_f|fKst̀ER[\acfmrttyçoӍ*_7r|Rfvxlyq`ڍhil鷉nKefwԾ_Fs}Ǿ~GOY[`adjnnq{|en _IYyjcWowvrrpRo`i輔wEiowSBv۳U=PU[^^`eikt}‡fȪZaR^UzJ{^[idi^ahŀjatj~ස}z:et}8Vt]@FGQUYYZ`fkwnjtiv#a%ginzauUqPPhzc|mn|Ɓq7\t~ЬXAcxTJTDADJS\^cjov{xq̜dMb TttrxvRuξ||VA]quj~jo¸xBTo}ŮQ[sFIhbE117I[dilsxttжѳlg^6lcnleH?|ؐn}}a:[`pllnxkltuhWev˟irEJifF0=TK@UvzxxwطłNxRiľfclVE_fv}e;Rbztafkiza`wpĒpisą†}vFMcMhozri}nN{eYhVTxyvhcn{Sg֣Lb\HU2$tT m{rmtrIla/x^fkfgĽZmvYQf`G8\smN4E@921Go׮nͽfKqgoΫα~kWijrT>` x al\W~rɅM`cQb`IH7fלgo9?>6.&(@]nPxqxħǪ~tȺ{c}{ru&=y`5Wyrg~ĿtS_bR[aNQI5FkЇuÙTGO>-+%&0@S~→uY͖j|qwõvkrS?Ew7u4 ddPxȿvVXgi[YM\bR]fU>QdwܔWԚuӨwkoke{w\>?a|mgs !zWg&Y{ghZXspb]VfnFG[bZZeqtnbUIHNnk<3+2?KMF>91.--,+1>aܜ]ܰmtطmdl_cҾu8Glo~'?z[n-_s|jhZ]zƭ]ktlidMC\d[Xbjoqka]QGLfi<+%1FJD@815:;71*'"Bߪsþ~yTŶw{hPo];TzưmzYk(c`degZciͻYarlW@XaVPRTU^imscI9?`uO2/IJA826<>=842/#<ܹéxyqrVN{ǹPYW:]ڻm| zYl&VYchnZior׼]Inh\\[XJGQLAIepXN@ASqฒ{`84CC:28ADC>:526.GÒΣ{tlb8TνdKq؝eBZr~z\g;LOaijVixyܻd6OZ|y|gUWEXz~eA5\td\UFAKaɽ|X>745?EIHD@<9<:Kp]A0,ɻN\Νt]c̿xx`;aVDPcmiq~ߤp9Psjz]]ci:Fed\VJCCOgКG+9?FKMKGCBDGDIZhŃyx[?1:6\Ż]Oǭu[y)NJy bKkg?@a||CNykyqzT=[aXQJFBDPfqL43IOTQNLKLNQOU^tjWWD>A@BCoqLnY{p" R rOhn;HN>mnʥׅHQ]RLJIFECHd–d=;OW[YTTWXY`t|eTQLG"EFHBNӺQc{yWƉ?X-z \Gge7zZ@a{tս˵͟[aTLZLKLLF>8@RcqpiΗPRRZ]a_`dghorbMJOQRQLJMNLCHm[]woilygg–|4Fz`;`MA|a@Ynik}xacf`KQKTJNOLGDHU]dr֘{tqdZbhkqsrlc[TTUVUPMPTUQJ=lʛe]bnprtU`}DŽy z\iCCFjCWkmdX\`bdPktWifTX6PNOSTX\^dhosͮ`fqv~}{re[XZZXSQV[]UUCGТpfZsmtxWhz`ǀy xRk+9T³xpsKYtxr`ekom\]dQ[mbE=>QNUW_ggn|~~uΦҏ[nx~sicb_[XX_cb[XCLѣzXLviuiJgl)y bv2[οvds|W[|~c\gpul_TPZl\`D:QPVZ^gb]{`abx~ݳƐXs}rkgc_^cijdZ_A`ɢRJsmnvhbe-Aw6D+SЮrϲg^qy{ucg]OtQ@LXZX\lhenimjݦSƅ֊bwtke`bkol^bXMſ^Rd^rc[| r+hִ޽oUatb`Ylj\JUTVewl{wgdfq~Œޕi{}ofabjnc[Z\Ԧ`qZGVMgJ{M &uQ6BjeA˷sKZhssp|zy`\d~MzcGLMPUOI]iaU_jUlޠn~pf`_cbXS[ogRYyw˹h6sc1Z.w tok^;I̷VVwbmdjw{`VijsrrZ]eZOVXC7HXTMONTNlxzqf^\[WMRopgud\ɼU4eLv s3dWFH? GgMŞob[_v~pY\iZ͟rda_xʹaakSHEMRi}轀yypf\ZTQLjjvvUôxL;L?,j@tҿrta?gwtv`^gpcػpj_jo{ӬhTUgt{|}Ɇkxna]XNQSwy]fX}ɲdH>/o7[vjZPA<1Rq`fn{avugwpţjiϪ|\`ny}xƇfc_ZSKVbq}TOƵc`dK<,`>;9?KQRWXZ^futaBYŗvdl~qqبvafx{{՛^XUNLbu\´Z>PXC2#]+B_yMdθɃsx]qx]ٯgmorߒLPJQuvIXu\GA@HL?.Y#O_~xoZPYO\f[Tرfdlsa8Ifpqj̤p"6ENVfttwpsxXjs{UQ[ghUMR¾gE,B;Y9D1w؁ .<ay  (0Rd_UPeĽ%T@b (:b{n=wĻwXa|"-UkudȹVuh' 4S\a|̽{z|xlK$+3>Uz|cP?7:6+ !EWirlZIIJ?'  ;I7BH-SBSer+c5UW@x}bLqg-B#ǶeYz L/75yޔ;`!  ^6/; B$          f8 f9 # mS\\by `y^W[SkF3 $s< } \Fw+N*+oOn^_oM)`HVfammogYaL)'';) >bYZ`fL+ ( HhXH3=I9$ ?cclwYOg{n  (  ?cs[9Jg]@$>QX\rVCfy5 ,PWH;;[tyI9gaM;#-@TUCQnjNCVpųp3 .>^jkWcwyAEXON?.5APYDGyoIDGNhse: 23fder]qb4ENXU@14DRQ:YD12>\vkZF=VkY]\QgytI6I_dfzR!8NFB\iboL:[e]D:JX]D.ICDkQ1FDUP>A @%Ymx}6D[_M;HJVqOIMKkx|`[s}ƴiN% 3iK E"6Nd}ˁ=JbSAJEIHuPDMUi̹\cdir~rjk7*j<H/9>=7HTsw6ZVMKFB@^pbOZkyĭVLae›yiRJJfbmHH00.1CKFIKeQKcVLHFHPas][XbvoXW@StpºTniehta(P >[6u?DrzTNWjVL^slQM}°zvЮp>S@¯tsfuM9JNpĴʽȍCHR^oPTb~cIۦy}ː{ewIT snbeN;81Ľȵ`>KW`uJ]f^wku˺ȨpVmɳVYZǐyy[C*Hɺƣ]CQYpzyQ\[xvQ[m}ѿcLùYZAx[Ck̯ӆCLSazwYV]czɢb:]s~}uɦUXʖeVX(tlþΡgu]̢~yqܫ\IRVqu~Ga^g}LHkqpitֻy[^~uV׶K\ bȚb{{mϜs~wlޣRNW`z|iOianoHWghhjqzήccaZ`ݗ,_;qWPVk~җusi]NWYs}ORkkv͜_H_abfhov|żu~wɚ> [ s^Kj|tvY[xҏonVyࣃgP_e{eKstˀER[\acfmrttyæoӍ*_7r|Rfvxlyq`ڍhil趉nKeewԾ_Fs}ƾ~GOY[`adjnnq{|eo_IY~yjcWowvrrpRo`i缔vEiowSBv۳U=PU[^^`eikt}‡fȩZaR^UzJ{^[idi^ahĀjatj~߶}{;et}8Vt]@FGQUYYZ`flwƌsiw#a%ginz`uUqPPi{c|ln|Ɓq7\t~ϬXAbxTJTDADJS\^cjov{xq˜cMb SttrxvRuξ{|VA]qtj~jo¸xBTo}ĮP\sFIhbE117I[dilsxttжѳlg_5lbmleH?|ؐn}}a:[`pmlnxjltuhWdv˟isEJieF0=TK@UvzxxvطĂNwRiýfblVE_fv}e;Rbztafjhza`wpĒpisą†}vFNcMioyri}nN{e\TGJdڿ~}ƴɶb>YhVUxyvhcn{Sg֣Lb\HU2$tT m{rmsrIla;DєlfĨg9GKA@Moģ{ݯ[C^i`hvmm4TwuZhY< RWv B "8ôddQia@9}opl?:G?:8KrѶuԳ\Jkfhwpq˷rLYy]mwpT&>/x^fkfgĽZmvYQf`G8\snN4E@921Go׮n̼fKqgoΫα~kWhjsT>` x al]W~rɅN`cQb`IH7g؜go9?>6.&(@]nPxqxħǪ~tȺ{c}{ru&=y`5Wyrg~ĿsS^aR[aNQI5FkЇuÙTHO>-+%&0@S~→uX͖j|qwõwkrS?Ew7u3 ddPxȿvUXfh[YM\bR]fU>Z|UTO37DKGB97K`vԎL˖›߾pknjsыV`A1dl]jy x li]asf^WfͭaJSdS^mGI\dVZlrlbM>DZgx˃AK60Qdw۔WԙuӨwknke{w\>?a|mgs !zWf"X{ghZXspb\VgnFG[bZZeqtnbUIHNnk<3+2?KMF>91.--,+1>aܝ]ܯmt׷mdl_cҾu8Glo~'?z[n._s|jhZ]zƭ^kslidLC\d[Xajoqja]QGKfi<+%1FJD@815:;71*'"Bުs¾~yTŶw{hPo];TyŰmzYk(c_eegZci̻YarkV@XaVPRTU]hmscI9?`uO1/IJA826<>=842/#<۸©xyprVN{ǹPYW:\ٻm| zYl&VYcgmZior׼]Inh\\[XIGQLAIepXNABTqฒ{`84CC:28ADC>:525.GÒͣ{tlb8TνdKq؝dBZr~z\g;LOaijVixyۺd6OY|y}gUWEXz~eA5\td\UFAKaɽ|X>745?FIHD@<9<:Ko]A/,ɻN\Μs]c˿xx`;`UDPcmiqߣp9Psj{]]di:Fed\VJCCOgКH+9?FKMKGCBDGDIZhŃyx[?1:6\Ż]Oǭu[z)NJy bKjg?@a||BNykypzT<[bXQJFBDPfqM43IOTQNLKLNQOU^tjWWD>A@ABopLnZ{p" R rOho;HN>m~mɥׅHQ]RLJIFECHd•d=;OW[YTTWXY`t|eTQLG"EFHAMӺQc{yWƉ?X-z \Ghd6zY@a{uս˵͟[aTLZLKLLF>8@RcqphΗPRRZ]a_`dghorbMJOQRQLJMNLCHm[\xpilygg|4Fz`;`M@|a@Ynij}xacf`KQKTJNOLGDHU]dr֘{tqdZbhkqsqkc[TTUVUPMPTUQJ=lʛf]bnprtU`}DŽy z\iCCFjBWkmdX\aadPktWigTX6PNOSTX\^dhosͮ`fqv~}{re[XZZXSQV[]UUCGТqfYsmtxWhz`ǀy xRl'8U³wosKXtxr`eknm\]dQ[mbE=>QNUW_ggn|~~uΥҏ[nx~sicb_[XX_cb[XCLѣzXLviuiJgl)y bv1[οvds|W[|~c\gpul_TPZl\`D:QPVZ^gb]{`abw~޲ŐXs~rkgc_^cijdZ_A`ɢRJsmnugbe-Aw6D+SЮrϱg^py{ucg]OtQ@LXZX\lhenimjݦSƅ֊bwtke`bkol^aXMſ^Rd^rb[| r+hֳݼoUata`Ylj\JUTVewl{wgde~p}ޕi{}ofabjnc[Z\Ԧ`qZGVMgI{L &uQ6BkeA˷sKYhssp|zy`\d~MycGLMPUOI]iaU_jTlޠn~pf`_cbXS[ogQYyw˹h5rc1Z.w tok^;I̷UVwbmdjw{`VijsrrZ]eZOVXC7HXTMONSMlxzqf^\[WMRopguc\ɼU4eLv s3dWFH? FfMĞob[^u}oY\iZ͟rd`_wʹaakSHEMQh}轀yypf\ZTQLjjvvUôxK;L?,jAtҿrta?gwtv_^gpcؼpi_jo{ӬhTTftz|~Ɇkxna]XNQSvy]fX}ɲcG>/o7[ujZPA<2Rq`fn{awugwpģjiϪ{\`ny|wƇfc_ZSKVbp|TOƵc`~cK<+`>;9?KPQWXZ^futaBYŗudl~qr٨vafx{{՛^XUNLbu]´Z>PXC2"]+B_yMdθɃsx]qx]ٮgmpqߒLPJQuvIXu\GA@HL?.Y#O_~xo[PYO\f[Tرfdlsa8Ifpqj̤p#5DNUftswpsxXjs{UQZghUMR¾gE,B;Y9D1w؁ .<ay  (0Rd_UPeĽ%T@b (:b{n=wĻwWa|"-UkudȹVrh' 4S\a|̽{y||}xlK$+3>Uz|cP?7:6+  DWirkZIIJ?'   Xf-!JL;1(7ӸRsQ |䶆V#,Q6àq2]ϞO[ lDݒ/ Hz|oV=(lקq!:{ Iyn dg-lFQZu x(d6! \::5}=_)7yJ*^2uQRE%9Yq3D+F:D:B?CHA`SofnVlDZ2F(i@{\LK:_%l6kc  &HyB'_lNb# Auc 8[߳yK3MvΨC""2W{2LA4(U|.>BoR? XGůUS[IJ*$:7鶪O{7@Hkk?<kktq݋m6nƶد-mR;`zv x#=\% oYRڱ#&?>ҹЪn_;j;$}*}+(}'}/LtY"$].9⦅%{_a݊]hk5'SN{<_ t jM{-4%TńtY۟R6#v\喊x:'HO3^&0::m,L%3:qVE t]~Iv6Wٯ) |ʸ2]G4(6w‹$"AEv m[D;Vh[}چN|3HS:KtxU'D;77;_"e?Yqxl+@IDATx ]W}&sߠ'˖yb-π0CB* N;Uݽ:ԪUITHXI36b01`#yy7~(lٱ,ѹ {}asnR@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!3gn p8p0 NjfkeBAl[km9gkW[b$-|>dzdݰ 9>sl[5.j@!"}gϺ\ͮ牭]l}2__3zc7>{ 6Z~ .w.??O>ڷVsY/{m_aֲ",fυ|g+VXcw?SO?wsr%;^{ͬ_~{YK0.>+!V<"Y|ի^??222z]wuv.%Kt !fF vagc83F w~O_K7<|&1E!jh;{;?#\n݂u?<{/ˮBauwuWaÆ._666nҥ>{S>x>rs{衇 <|.uĂ\`02{ǺGwÍ7[x㍟emVm,}*!c;ukד{=sOp!O:3`Ń[ouvmAA`fff7phDX]pvk֬!=wy`vvvs#%sFƉL{oJv ]oibφdKb,0`ٲecI CK+l7:2:3Iƒƒ%4  !~x纔-\h^kA lbx=wwwG1s#3 ~~wޞ{|386!aHd?HO.3+:u3: Ve]eARD;Ymc׼5]Db~w-][7n1kXxgE`n{[n@5ݢ]u 7N?[xp+Wx ʘ9F>S6Iǯ}oGG/3&Ab'YJvbZKm?Yd滑ёފ8w}|x߹xtIvwK/x&1܇B.܀j$~xko2߭_ [Ff"ÓN:3<ʋ/c^{E C-Qψ9 ڐ3l<6/7܈?>.?jْ?]|n.u4Vxj*qzքgd3ccݺ5O|&dxMPM8Ѽ24%D/[If@|VD!QnੵO 7!HQG5aWjʕʹ+ܔã9.4 t /.M ew-; L/OroUGbYX8ah<_k_.d2 h~uSSmRr'n5'ky vO=T;N(> wBv(;~ %FDpAF?i'%?0w+X}._@r>>\X?٪Y{ˮ׉bMD{ssSFG`Y.x__2KYdH]w}vI57>"B9q[(!qxW6]<ēL )\|.00#YooeC?4<}i;҆57|ww\>ѫza xw.6\C99V}< 2IhpUWRNKR&8taCB",JyfN$ ($0@ Ǹu}_S6ld !  زv$DF ' ^q A=+GVhvrN̟P%:ɾ&By-٘\)ȭ|be@,F?qcI6n5|X"agMiÏGl-%8,,0N~?)1Cs"Yr I!q6\b#eKŻ-nonDGd8#joո`|&@P2?%A#]]K*  ]+qHhc#o10'!od|'H<{0^{.N~}uI4}p1lh ,hPlJ?:3!o{. QBX6#暛ౚ-@et?6&c@T֟PX zKB}^Ww.Na>s3-${}n U6!0@PM|A?A)r!_nr ΋(4K^!C%a{/%gX .+ynm.v4$#ykxX[O6^1'y`\w:Szsù6V [P!4!vmg'ȜuQ{i6Ǘ7XY R!'U%װO^E|9aSXB`{!;oam6^xW3K )ɺN9.sﺻ+~94#W;s;c-y'k׽Yl׍ ~/\t9Gpq2Y{cou] ;{ԑGr>{e ģ=چwf h /GQ9SޮN^`&W҆He;!PݬXnq'C<8K?u}?fy4R"lY} q-mP6h>~}iC0]BRxA02"=sWR`-/1%/1)- o=bק_s5C1?wguD>df! $ "|FRZ,N4zrJ"&k+h(x"?>qY,Q2kibf!"|}B%\2}vںrP`xO+'-%/co~> )qg";V376 Xa]uU+Wd͢c;ِS`Sz cr ZI@gJ7ƒ2o}[[ALdFEha2Q H,s;r 9Ϲ!߀껕'"ы O|ι‚;<|ZDjY :?c ?vOV7Cua^ޙYtDcm+m{ug%g[#'4H%1N6~D;=DDn$FZeu]Ku =ķ_Fo %EFܓpۏu`3`Y%Dk9Vp(x=tw NI3{o,C[G~yrr{ի_\>6lq4DKMHGwd.xÍMD H!KoAlDt-r!-BD;:K&E E PC=up?ȣMشEChޏ0xje,B>q}^b_O.ܧZ^KL̙`!.BV]mz{ّEE*5܇:?k|G;@>#Kɜ{3qv",gFZ kD57\ ;p,]X|SP[  wË@rh}'fFzG$,pW=0s2O^PT/h)xAp=>=I0@(Ƣ=b#L?w^'ؿYf @@d)XOV7\ s<q7QOג,2ʱ V8('Kyg+_۽:^r.LG.XGb N+^WCx!a~ތaAA%Qo~̧{"ZGeR9߂`齲os"Pl`tabl}ƬG1!G{'+0腞,:U'5KśȎ(hYw"2!xV@͖Ev "%'F$4hBvJ.*Pe_0ٕcIH^j/a⼈&^^_8OY61Ɉς x~FGZ៧Zx~m^JM'nOʞs\q7j8ӳSAO1YX|dԾ+6#+BdΕۻeY k\lx7+i}Fzow_ju:=k^O{"v-!#d&fa|V Dm6̨N!@K&b;}A:h萉[,.vmHҊ}H8ʖLPHhf8r]CH2|1u]m^,9n:nM|&8oÅ#ܿ{“q0=> QVOHz KC6 PD!F"繇EBg!εl#5`rfc'8Xe!H_#,W}Zg=S@~$ @$! ЬC#@==OtXIeCԹ9sݖX򈁺x$^B6JnPFq\FTy ֜7Ax7= _rC) fgc`2"03ߪi[F`˸tA6$$>kS!xsï}kaYYq!.B! ȂEnYf!k+';bq-c&>(I艽Jy,*1ǟx;Sۆ_կ~ooM }5/$ä# h"#6}E5i9`~71u%?#O2pvlB4Z^74$P]fX5VZgF(d@t~״WǸ&yG6 !2ŋ4Xqh<99 |Uk,_uҪJ=G`r8sQgm[eZ b X f~˚<ԓlJdM֢NE.x;Zˆ 1G={Ai BWqR_^ c)xpCxK~ghLQ!›27|߾ e wY:̥֑o&@dnDf)/fI{D!h6! ^-^8R*T#;DxDC.˵sm7<H[u;} Oh1m쓟9 E;YxOщCg"R #D!D $ۉ wN}&%k6$~Kx:pa:0m$LVqunR9nc,x#MDYKXHG>yđ-g`ODXr wcrr qAV^zu $yԑ]U y+\}r˭/ FmB(ˌ>aby%CD1ӅrLvjgDW @hRWyѫK-j&;u N5:fou\ulnmbm #i$tVַSb:\ $ k~ͪBDa8 7O!QPG_. l^'|&^LZud$ V7Ɉp-L2wg "b+"|7 s瀘-R{ lc3a,YI,~'a,Jȳ-o$9":=b~YguH *6VމMJ`Xb> 7~maCH+i?q/v$!In tzS! o~ \;GiE~E" d<$zkef',mltO<b~a?(dv ا]zxw/r/F?l{@Y'lo ![m.ZhcweQŷ~G#NqN'g\TH8cғf!͐(}׼l rzQQ'ru9泲wrLێ+/()4vh{:_}yrb6$"W@(tDBfD77[{xV G;|O;QfdnicnDCoK6G?˹uJ9D\'.2y]ez~,eGq5!LH䘉7|VRrWBa]kp|B~Qf8|2F&$FP|rdK"#_F rzrICp3-B) 2.`</{SQς8&7Uۭ"PUh| ۯvuH {Pߗn%Q"=HNf ukŹ}ADCڠ ?*Epv-hUmc#!".*ӂ;cY !/:-<좃˚+'BϜgɮy`M +$鈍i+s&LNN6˾#^{܃X'@^=# =- 'F+ OQؐiì='n!B > oyx`|-v©\Vj5JOoXe,h",:87i\?%Ugh3OWU'G49YL5hd5:"J B!F OϫXfU&2/ķyH"@l_,OOtG= oy>k4qh '"\@RD"> p0pC/rظ?gB%kL#3}zqdͦJVv5<:N3HE cy2Om=+g鑝ֹ2K` ^FKB>w_w}s{"B|D@9Uuų17@۴lWu>[lU[{Wg>K<mYmwb=S[qE3!Cݏ"}Qcla woZl6|N"9R':Yz!`Dm^疨B,qeuv^+ЁC.φոo{Z1/!m/>׼ר#׹ xBB":$#@<p"! Mn!׶>p-pr%jDCPKdݿ| 2!$KO_`Q#aR'hDfS/F q";1}*D ު YyN yI>l" "Ba |%ؐgSӞTYa@Y< tIywC#-r@Swuhķ2B:D+͜[6ן5w#H~?6w" kK:$(4Gе2:bno<+j1O@羉Lڣ.;^cVj;apos\Ir@~u-Ɍ_j4&Ϲ'n=:ίۖdYufmF! :2#gw,}!| _hx־dRWO@\gq/H9{vK@S,s}'Ѳ{Cb;c&`靀ic VL{3Ѧܞ&'hC3hMґ:9R Թۈ&isviur0l h숡 $ԁ1<SeќkYCt "_+'[=ܫϠφZ\iA&}pb{g1V^=0{U^mlL͝L=BB96dtPޭ5)<~V1wyWX:̒*qpR|梯cu#wŵK9bwQr==ܣŋE:6e$KmCt!Z#{Ͽ[y#Շ<[Ohv,"b{'Zk?Plx]J0@"Op3mkdy=D`E wR,8Y-ګPd#Q2IK>^r9 _#`8f"T@)-%χ3RN\~{>tHn#$b\`4"8;e4CqY559Ӯs@>YkP'"xHmUz 9%:F2l^x%'+IP1 Un1!Rbz$w 1^1qۯ0|O( o(ґ[9[y< d#c'~NǝH'w0`u7Z!<z-8f"q>D{ s/oeAnfX=$6Vnt=][WgBuW/NL=dxP}##ܹK1WyxU,\DžYN{ >#Cx?ljD!5#)cm|8ӕYaӶ:e`$Vt|%b\ꉸ g: LL,HV,n!ɀ5FBMRK.& }-F6!\@DD:?!)ZP$gu?N>FD9eMfC~eXKA=HNE.sɅ[~\̈́˽ÆG`8?-[ڟ WIeu*zO DyJXi|fq]Cc*%ڎ֑$ wfnֱĪZpĕ! kǺy #"N?I|6)|1 d5M{Wr+cH&WDq:%=Ȅ=,BTX3Gv}_8ϓ9>f@^N\ U`!Ey'gyF<'KF7+;=+=0!\qQ3.FNn7  ӀYLq}/G^VxfK`awHkkfg'(ȇu%$݋VOndW0Cݼ 2ܣEC~O# 6 Z8fhBJ, Nrt$M_e7mECzklkBc /!ٷbdxLzY+WN7IHe_`,.[3Jb-^~=T$c\3?tL;FT^M?lC~;V`eІPFAFN=1 <ȣ+GS\O5 "δNd%ȿ#_ky>Jv^aKJǛjֆKKroSS7L-I8'aр!lkBq* ի_$GPY|9m@ qR^> 7 ЁC :aAzޅ߽]/ϧ6I>lBAvm˿s rR<3=GpFƫs*V_onJ^(b~~/:y!n~!pXw_]^quUWX)##}%Љm7,'ɶW/_y-gb[q@||^{v$04@hPB7(S_9r)zn4xƒp<~,:D>0 \Q9MMq "@,}ǔQn k]ƺ7re~MwzbWR !L\‚CpH(f}MC  <a=۴ySCv%pHp%×ނlvi2"@Oy~o\Yzn^Xh)-챜S[ˀ@Yu\;ү9n,;}hM,2Y'>:uI"xEaH+O0͆lA,4-_pyu_M<§dFr$L4+Q(kcj|ӟnp\9anaE&PǪL.1Y{'\g$e" ILP ~Yss@/⺺%@*g@1m2h Jݱoɺ{\}yF>򈭧#z"t,\<4j=V&!:lE`kP.wA4!Bw;n> .@ĸOEG{>Ofxs2_LH<#:PytXwpl\]6! !j.IqO N0e2pdU醉rvigB!}8B@ނ\ϼA0g$ E@2){6^ɢ}Hd೹7/`lm v f״;W`r?B}њX"dO!lb QW~K>.By XtPO iČ?!iC28@S\Do>xT=;\X͖;Ȗ4?[7]/ᡬWrʕNŏOL?aFzA^#6ӄ&4OΝGn$g3I n qsy~s -96Dy;hn}2;f)1Z7{ㅽ ٖD[?GFδscᇙ3DC 6iYyf>[aH/ 40y1EIDAT 8'^Issz";v)ر7^ ]V)8Lf7g&龫2f6naqӋ3w4On]oD Cmgr [E\v %%y#Hs꿝 <wxswYNE~?O.ɾhck"s;m.@8}! o8'pu}vG` g zq腠϶UQ~[$,H~.CvW]uhG0ʅ+fr2@48H@ p{u^u@lcٞ8T[A[gtw& >l&''ߓ/ayX 桞6d#' hxyy yڰ$ IĄy-yqxWT]ˎD; Uˏ@wGD^b'"y/"?n$"E nOr$a'iN5eF߿2<ޗ!şM`xB/yy)9mj4~,΋3/6Cgו  ۆ l}_/<]pT&ѧ[fRҥ !~?!r^_^/,U΄@=35vl[z! 9!X+917th,H0P2pl "$S0H2ugJ"r˛?i(i;WC6x#^tE>G^X_q>yʏࡠFͯBx"}g0+ߖ|65+gMDy[f[C?Fu[@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P@!P?k @>jIIENDB`ic09xډPNG  IHDRx$iCCPICC Profile8UoT>oR? XGůUS[IJ*$:7鶪O{7@Hkk?<kktq݋m6nƶد-mR;`zv x#=\% oYRڱ#&?>ҹЪn_;j;$}*}+(}'}/LtY"$].9⦅%{_a݊]hk5'SN{<_ t jM{-4%TńtY۟R6#v\喊x:'HO3^&0::m,L%3:qVE t]~Iv6Wٯ) |ʸ2]G4(6w‹$"AEv m[D;Vh[}چN|3HS:KtxU'D;77;_"e?Yqxl+@IDATxْG ¾7fL V&;bz^GsWhL ]F#8dwsþH|,ib"2GDFf0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 @` 0 #8;rO諦Os : /b^Rβs^5}Ǒrp @EG`Ep7B򏙱1?VN?i qpyI;4"NmU9?>O秐eF`x6>v B!4gS ,yUS; #0nz^߇v?88 ~w99QS};yڎ zIiU.҃>G9$GWehLXy^9=~t=qpX_pGɷt.]~TR6HN\?ydn5:<~ݻw߿ƍoRUR G<#jrv<iq :Х/^ٵï/2(~!6Fk3lzFnx'mZe*ˊ)]*8 #pԏq ^Q!5=9vVE98 l'q;r7N~'N}{ooo+[ۋ )n$lnnd'{zׯ w濻yyBɟ|>Vvconoo9^8qwA}$f ޽{?~(=VI-K)Z[@EtV鞖jDhl` 0x:C|z]qded=|3(www;v=i3~ƶot^Yc})c641#]\&:l>};9ybT8JD88q3S>k5 f_'}B7ް N\GTǚO ;SOG29}E3[dzֶ6%os/iŬz΀_mz俞zZ0^ pqun00 at:kAS^@;#a DknVuϒS;3 3MKzZ9 |w/Xp<᩸S&D8)r)[[9xUsyؾέϟY :h#ЭcOYV qEs6;ﮯ;amcs`kcӽq7z_Oz=]͌kCsW~ww/nwH;&[W 2:XY>i4_4@E`1#pԵe(!S<̕pSۛg66NIŅۋC?Ԍfq~S@H ̠,Nwqd8ߧIO̞;8̖.*O&N'?gϞ"3S\>=*Lv8nhh :Xxoͤ׃x;:4x'^D'7'EOiS3jRmp;n2~,tuOvzv!}zFF6;~x؃{y)75Mo\6𝥷Kztl~GׂGjڇy'8qDsyd<@x23*_o ^pqg}VeZ._s8S>v*M3o7@qK#zr*}mi@?Lċqboljfx9tS~*}kY_:` 4ۙrA JA{8˭4^b![TW$;wΝ:v}ۉ|:x29{zxtP|-ұUҕY:S6uOg#=gD쥸[)jk׮~ۿn^ 6sNF}I;?|s1xmO]qa\mW#\i>{vֻqP9gvٹx8P!ojf)vi{[1~ì}ʼ <6kqqt^>l^o;Yߎ,#ǙtJ<lKs>fyҠ[Ro;\^hԃʇg؅a$[I7ʾ7&Y]'nͤ7<;B{D<+9tNU &BwqT_c^ 8M<~^ 7ݕ̂q.ge{÷lxaWqg~ b9ôd 0<5imTPwmm&Q~09p{;?L"h#dF6|) OTy-ހ=v?vu#zĽzٿ̓AnJt qX:\aV9,f?ܴ#vʕ<>3f,ގ@JO%1}>Fk}vG$qx:6 hr@6 `2g@vg%(rfoJb lo7݌w2@^󸡁üHa^H_gՠnN(vx "0}!k)oU@d3@Q^YtofI|X>噼̈́q((&+y);q8:[_` ?efUΞՁ֭fiAOHqKG="G=y%^.r~e;K~)]+la Xe)\H'4t-Yᢼ}P@Bv3=RJ-aN&)ۏܛ9[k@V?nLz#ݽu7f|S{Cn@jȱlGזs%e@kL:@G` ^S+Fm}P 6'ȩ/f1 V%rhlS|+6C2-G89ɚg)A@Uyɼ'o^u?N%A@9vI7̶2GXB`@wۓ Wr/_'ZBˉEFw/m#_rfr 'Ǔ|ưX(9 j&ocYL9¿zOy |he2?8+4n <# ưErh9g]rk_:{v^N}B_QE}ߦ9z6(|>z`v]o7s~7881'yfٿgyÌY;Y,D\ӗ >{\38⑴{ONï{dl^rIml >y#//7}9q~DmKx+.wڨlT*%ܱ'WF;Mܵ,Yz#C,p¦eK-Oek"ļ4)|b BXq> Ǎ\rW\9d"}y:õs#=ȣ;q&(̾x }vHcZ?sc:]-?{̙yVB 111ۜKӘݹ'lh8Gfw8̷tϰAMxt$fb^ToNy}c[y;;kXg4('c]-gyʑql\+MoޓBhcܙ`7fޅnYrrL29 ]rVᡍ|+ikFtU:w7 ? 0΍G(&&NDC.,_{pzV7v=`sdog3{nkO%!"u<d2@zmv v/rͪFv #0?9x$X5Nw?GqfZ]GCQci<_L>vwfeӾr6FC_H#_9C\}ˠf [f}Q>s-rٰF?/~*OtM~'Q^1{:-4|lԳa).Ӿ6Ł.oOзԇ[;ü)gVhţ+a3D镆d x- E 9Vx=>Os{ɓ!zL >WDE;|eiY04Y^9/$s'_gݼe<8+gi^`9YNd9)u֦8Ksd^z# 8܌J9?93 }9(hnrJ%ny 961eI֏%9^fwKzr|,dd-矼/)| zjBCGi[mzQ0g8sG)?imf[˺ Dn_,d`>8yn3_ʭQu>Ux+oTVsݛ=8u"{{7`tVJjf q?' ~'f.0t@Xl.F~ #0/)zdoɨ7w}o\t8f/8U+3+#1^9 @|_z<-7Fuxp=.ǡ xjl;rx4@SmӆZ-BﴘE졋aUat6@튷O`b-Lq/LХ}96;M5{n9tӥ1&\vNeu@?p[ӓ>zp!Gy|).̀`\wNk0%:!OhԼa܇Ɠ'ASNI_{V̤szMdJ20o3E;k61x1Ë,ܘQ\t_?ۣyggϓ"J8c1ޡo^96 TCaE4f'Z!Wr޴{5s4~p9-39(QRj,WRNd̉ÛD&sfh_:T%~A/`H 9Úg]9F޹{gA ίH7L}+m]`m׮UW.|s6   eo_2ҋ  8?V [ν > 9 4j88ZNR\W/"*G#z%0O.'S9n#j]v]>ҁ1Jvx1lm WQ8/y#=Q  Xt-[#rqhN+Mݫ~~Yp84̈́rqO~.N7#~q5\_:T|RxVREy%p=ZԥYMs`gڮ&8zLdHZBCY"d$p;:F[jgsLք@|ҮU+YmJ#gOrN6? R/>r 2o'Mdl~S##S?܂:vԷAGP(u}lPJYmW@"0W`eؖ7+\=[ou%gqn򘕚ՙ  ~Z9ԛ'߁Yp,iwsXb ML*EN::ƫSt<L" 4Uo zHS;MG+~XKfA4񚩫s;#38SthS1n/Owڼã[VkMb=,r褉o {]nREKw3 8,NXlJ}Tw3(xgB˲|Q]0GѮÁOtؿ,=ύQ2q}J?mzP`/2q+(Gb9i8,ł65i>7_[2^g%`FVֿ-O ɊQ{Rub)36Mg^mcp4聀G >{wbqvtpҾǗ%堬gu>+!x6yCsxDIŬ?9,r_$$h$%8ޮ η:ߋi?yoYWy"9n T#+%wR1ü|?1 zvʌ/l /]Sz/L[Y{Ћ-©cSY;.r23vnpvJG훏2܎9<8n*o_-ǡ_λmK('a#w?S>y)tJMqTMvпg;r Wie0_OE?\ ^EѿC: }l&`TH>m8KbEށqEMO#O[Nb伜ϲݴ)+O~)޷_NP/E8?(dOKš/ ۫~ay#y;޵,'fq]*FnMq9U$X93yTVm3IN^)*?Gpr-0>Қwn׾lu/\Tfl[T T&LmLeXzQ*}h\ kߘ(+bҡS>V@c~ mٸ5}o |e-bȒo*@ Ȅz"b:5\MJvt߳)KrfV|b7vi  g3X:Wz!u_B_4/ENǔ#7 KOA*' p>6]m2E kkGrPQMs9?<9ZNi^Ur Exp^‰OνUfD;/cS dE!,x'RO[u,ܵ8i93}"AιF7eaЇ\-t~}{# Ylv+L8*'LOۅ|s9^zUeFdӭbi* Wh;An,|]xu9#_2L; S͛^7_򫯾 b|g;]2+aR]:.@#0җa#$_5FM ?Kbl/@N{-ir>'wX35d9m&rscD}ho\ 쏮fS-DH~.M3SW[}%_:ʼn&Xryn%,[bd{9p~(-T@GcyWڃkwnvܺyk[kqtkk\Ouέ22y㟴'WT+m¢Y=#)<Ԭ_}Fc² u]}>S@9Ӻ֢KHqM/&Z:<|w^_'g0mOj鞖.F( cg4&ϠQtҎo,/V1Lc&xn/Ks 1uOZ"K2ɕ1JrӠq4K:2ZPN6J#B f0Çztw1Ý1$>ɤ :V/o+Bhr>ћgY_e'1qiԏURߡL>nwٝ;o3|׮]c3{sՀ6 _INzW}kr `?1x6>I3}q\V(N7r qmP΃ъa$_0\m+␕azPof)U.t&uQVCVA:M'+x-s"98mk!rԫ~>aONq/V$:NNbд ?۝!W%xi A^m2\nu {&UF8L-tl;O XhrE H zyaazO2lΞ;wlw`5÷Bn/h:& iy}]#<7A2U㳪{+lAz:x2F|쬷1f≒d[n܉!/_j/^s~kS\`k 8!ǜ\})X^j!}Z@}׬t>7W2+.r>Z|"$}B@VcyEN@g<►OS%~މ^;n/K=SA@gi/04 ,sе!\ucS*R^xh_(頮Mה@.+~D?<3x&ofUp/'|^o%𴕁>.Ē,KE7+U; ~cz HOwS(gf.g:Yh F?B%2d6oKY}{CKڗq)iEޒy zQ3o~h:O6fd/~-'Lrٌh# eȤ=XiOCtKYq- EI\ 3ˣD!,VEȜsZO`*]&oꆚqd{V[$#k|/Uocd?c`/5V=i]N-kWw;arec8Gp hˡű3:4"a[+ r29~VЗQnsZ[v90#m6 6ă8{8S>{k׿@`ňND#K} G0FxeІ6W^=K}6c<{XbV.5+Lg{˿(g3fvN;.cmMvL]g!uDFYG}v{ev9t4w}um鰔uF OC$3R7Na>DW}$ lLulUtETC9."$f;L^T6 L 2>O \PNw$H;kAZv@` :`,`t 6(AaXX K8Bϻ/h^,t Dsfy/ Y-Cce1auQgk0YCjvKYT9 %]^ֆļ|pN++q|v'zi2g&Y#کAma_<l:o;`8]9I7|ѫ.sNˍf\j?P;:ttyG::v j!(f޴10 xM#Ô뚊U\nIlek5uڃMv'<Ξ=UН ߷/??{Z;>{\x{J^ 躾8گۿۦq?`䰍  ƨKyŴ8yȣn1c͊=#hY }*2F,-Fv3x~xOarJ8^>|l=ԣWzGF^̮(9awcnWSǏ>:۪ZN>;gU ^bAocز9m=g9w<'~rrjumtt𴁕u9{#]ϗg> p RT8tXt%?޹mr`ʢIj_0@M;sӰ=Φ{/SfRVٷ*R@W7/^<x<5D'px*xWK?5bOYo#zò̆0K<|6dcG ;Xs]8qh`ѕry,9NkppPihʼnS;A62#{as"|txbf/3oAY>u9:-Ԇ,2ѻOKiw6#r 2|i]] +@B_˟5N9ǵ|*/!c3u "|UԾ[qq}@#]-[XQ}w;C\ٱUJt˧{P#J-WВ>"n{w>38{ }*迸ȞJ1*^'u:ۏ{־ ci1rW\O\?Y>Еwb8H jxȰ3\r>xS^!mAq"f ZeewY j9t6oiu|ؖ< T&-vTD Tk9Xr<=v9_tW.o~4Oio*=~lħeP> .U橼"cul diɜK!ɰsmi/kwѪjK|b1uR.<s9cDϩF[0=HgA6HKY:_.uK`=DV0k>@;mC[c{**ݎ?sg~?!Y1p'B2 A%籀7oxsHc8/|Δ$U~T @Ym\G,*‚xy*Ckg?gwv?o^8|ERnc̕I} `Yxvs;{eE'5nU7>{l]bFL(QkS_N篜2SU8x-xlM_ u3b^NLj\y'Wb?L|/S 3֓qU01:v#<]y+R#Ѷz} dbώ[rXjUnC7L]߱[xn64;"7hqy}:3)Y Tweڠ/?>괇Wpq2omq!ؓr9Or-r-`&}6&tIN1ZkIox4]ܒyTr~+XpM᳑kg/K.qis#oZ `): l+E0fGN9JKW>}_^FE` ^Sbm9:xPKo@k@IDAT ο`29ldD>\硇WАyj'4[N9S}9%sGeG(^: =>^9xUyfFo7 r> O62^l &'" NG(+zor}y…?şRכfEi 𬍃^}H:K` q G-+RHe?\f6L{1NeɈyV V V w{nRNpp?x;D>k )G O6Ȱ3TH]]Y>:{%txlU7N|l9^Z8L6D=@Z ;5 J>8V81A?-gjۘJ瘖RFMV7VvM"k&:8G69<8FKs.3uMMty:IGwҖ#2ۨZ(wME+Y,ͦG' ^U"K9kߙL\2 Gh WӵkO?G,Yx׏~(8?V8~;5 X!:c8-/ىIQ/㿝(=.q7n?~.x1ѦzO7Rye+X٦u9 dimJ\XrG~2%9 i#Eǜv%'YM˵S{:Of3v@J֦7Q.̪(Ӧe *9m6v<=pn@+7AOlդ|X[Y8XpN+ B l`B']h³ }׳]}eV<~퍜?=,29m NZ΋ڊn~W>KVk)x=r^o'`w pSHNſ_*u +WV?8cj|Kqgw~g-[8t2dOJq,HeEbɑ!&.׶ڤrRNCSedk: 62蓓덈x,Jw2,Y\~Nh^nЁNf{fxMn: {^te&`^Osh+p6hhsqKd7 U HkX\}mA|iyzzP\NǺi)NR8z+r4BpBWe"k_?}뛌i_# @dGZj0RZ2ce3.&7#۝\賓cNVnH yM `naPW6wR/0,2e }xV-~xgIt--ʩꋕV,f8Q3ϲƓK_"4ɼ[}~kYX^X:ās0w)A s=g4RrLuClW̌@MیnړWW=YŰ#^^-CcF>A'ֵ_L.:jWOؠ>y*G+WelYzqh'עlMVY7{'O][*7 >]e ,Ƌ6ӆvK);>#&qI;|sg}7_{'p>íüaod )_쿎j_` cjqw:7oycoyw%96?N; ?Vl|hˠ&*gXJit6gE@v`6̙}{8 |Ihsis C؁\);ȷsgcȳ<\pWG$/7(շx5m [ip1xK*I@q~y~}渂09zyq9Mꋳ^z~X }pkOO1*O^RA|F:+z'ˏ~Э{o{l {{ݤoߋ!4K_EYߙa*#Ű l=:L;4 KxK;2ě^ TN%Ϩa9/c~<3h@ޖ?.<٣%Y;6~Q5v7_r)sr 5-=ToӗՉy ѣ1An׼.oͣ.VZA ]Go[F/8lE))Ќ6ósϹU$zOznp5H.+gG2Ec8f}22xp+1J:yrz;/cn\Ϯ?mo׿ŒҗebT?W 1xNhhIZ4)3d'cPɏ?Lf7S}7csv!y}-c8X#2V^X:3>N/mcij/@z1qtiYdmw[pk,j``o~?jAxvrщ\yt7gen^un~A`D~~/Y8HeE͙~l}~?=NIx9ѲG}>;e˴c-Gޏ^urjoj䋙TPXލ&U&2bm˲YGY^?t uʥffLΊf*B-ͣy6.h'-9k7 MM~z4v bw0h~ t8}DT z-sc6wܡIkFl{C*vh2h)ʷGYغ4oœ2l;Uֳq|s#`n\{n{YbR{:a^sMG^Jk3 ~[qwԭ3}nكPZ7 X:~&/Mdrwo7.Y@h%8'SE2'ґWW^'(bԿP u:~00~gLC~ G1N?mx28c59#Nս|Ƭ>ێ }# / *6ehjPj$;s%,sgV}dVksx.rn޸vZЖLz]qKխy|2Ulՙ{?ݗ)/SN߇m8$n]&E/yAZ:4z:ExBTx4MAwiwhy[ǹuMC^])vݧG7?fnI_ 3@-6Agkċ=dr|9%Dtdr!uL+ i0hON\5³gCTrkk'O| 74((<Wc/G3+'t?Zѿ!#b[1^J<ót2D5g6X _r)aOM;~ORKl,1Gm`2 '+'a?d\]Q.ɛҒ[_дOKђV4-iLV+5p 017>} #_'!gkYm_rE䁻(r ώrt2D7_;͊9ι㖃ds} A~9 M|8}!{`q,:yA;kBӀ+K^z|ك`uΩ"K2H8싱^˥]?vA'ȏuqr_\ԋA@_ Tppy9<-,2rM7!ky6!s1F}l7rڑzm/2NeF1LFkRACK8' 6?XF zFC{i͍|;vL~:3֖V^ճkU.Hɀ4dt.J未C;uYe=984|+/}2ㅮχi]룼nAO&H2>nZrM:E}]89ђk쭛-TٽNXsN $u~&?<&~52J^xN,62!]ӹ7n7LTۺt]`p/sqy2qۿ˭OD'W?ʳxxզ X)G7 'hi?XXE#uўaL>"#}F(4FJ-؀k˜ŐT6!/שDaQ)&,/,<,c{h2p9KF-2l-oGK[oƾuWCغp c='#R݇hsS|COjg<ѳeЦ:#mlWcXYbA8wdny.y~:GZi]˪+γt=Z:d]F>7R})/} ]-b idi|] /B`Xպ. r^^oxKvcbOb&}`Ty?/ϣI1@v%}%汋]x1E0y!t;)Z2[~ȡLzRx6/xtҺ5Rtxh#AoyV^ Do [{BI6-my^];e y;mK}}v׆ J=W{r[ft-u #G!p&S '`_o®V7D/y!~Cl;%gJ#ASe=*Jakgh1F!ƳeӞ%Kf,7V}6ѭBA= H ,"̟U]?1xANL̊*G^?NSTosNO%^̒;,b0|觌bFl:h cԴ1Z5Z!CZe⯜a|SZ#AcEb ,iļ<d+ՆhcPgr*w^.坢)KdϏF: Fzp0!(-1?ndd*B;XQ]re7D~#=o3GAYXoF Uڕa ڨ1Sз7KD0G>ȡ-^'R6]YC6v);me̦8^:hGY.Z9.#'YqM,R|K[y?O "C{gc}<58cF}*z]q_b=o ow;_ ܌L_f_N2y]Gx,` kS],F2|޿Wb~o@\I/y 'a2e,cpdhmcШcV&3N!azuͻ T@q%:DȉۆvRǫW}G4fΗ軼䟖0弄ykh,ZƣŞ|W:%O' sqxN]*Ҧ->$ӎ0K91:?Kt$CחkBtL\! 896xiK2_|BaKlKoOǮoz}:ɯ/{r `T'X-#^;xJP{ mސχֳyqQ>mdIxx7 dڞU`5S7,~ۧy`ԕV#qzGu=Kt+|)1,q8?K/81I0){bI2hW(qSuc14a͌v-y˰MQ^b}޿rri,@vͧP2(8ySVYj^/{O4m]=LNZ Gϒ}|hRvrODc~1`F;VX3hloE-śc,wuwNΟ fΥ/@M =4:CAxHɂO|rVub_q]u]Z`0a_x49:2=R2€!"eDv{ w=֝ޓdx/LA9;H_^v.t'Zv& .D;*g> B_HFS 0?O9qz<*4t^<<dgw}vw[[21굟Nk'q=\zxeH7I;GN>7[v9R'[~)~xmYՠ!&fu7KtvYaNl>+)j&CtKW_7LJvL}ڭ {@zs }xepp{I؊gdSY pKqOWGAIeIG<㞈Gb!0/x;AL;sO?{w>~#qc?C"2> 6H < ȨzC]G6P3Pݖ?P3nh]:!xW |:K?CȌ-d7P?Q4rd*(9rʴyemN菵t^:oxJTL |c!}#z} tikG<a'&81ǹQF߮CR599{}_^)FKX8vY`7^h*uMLӮcu/Sl%FQ|H[o@w5>9Neu?jaw)3DӶl$E\yP6މ;9jV Ŏl[W_}[EW]RaH/)EC` ~3ChsQ8}<ҏ~16*#P qh6J1:¨m۫{ˌ}9m\b105r@]fc2!Xffd3@tf`/z ߦ78xW>w8tN+֒>oaE T,9 r >Y;I</vޓ&X8^Ń^GXL-_NYE bɖsB=@{k+8*sG_[UU9>Oƭ:bt?> .|AqT5>\0ГnҾ4࡟ 2 熎%CRpչ2A=xWk <-~f+A磌G 9t'6xwr*7_|gW+.L OT]Іt 1iF`A y~f8l&+ǽ7b < g\81<ۀ0k cŠ3F0Ӫ]O'1bLgl=Z#Ά@F}drA\PmPr匘i;+].m]Lɣ.:vppPN/rɹ9xBm\LKŋ'eZ:.Tq~):Y%uǻ;wF?sө<C׎n_4GbЂ^}ֳp5qɈtN[_@1Y02V Z_z JjZ}sT1߇(|A]׮^+~r ċx}>wpތ n P ŖxE\dUL]J/n?$y?F0+R",@Ym~&?+u/T~+qs |.tw~jӏc(ψ.ݝzyIxTq$dXp C`J!SÃlHvD//]FS}i+xO$G)6RUjp1÷drԟRܥď~P΍-M c Kp#m:iT$ѯ|NƤS5;RD#vf0-Y{Ϳe2 &ǔHT98#^9ѕWjg07v-g ?wxwݗ2dk-W%#&/ pUY킯AZ;.tShϢ /}Gf8uo^iNWqW!#&=9CzG7 :X%Gh/\ȽN _Aw Zӌ߾Z2Q:םk9qW92^ ܻF9G428/ïVBoGap96`Swg%Ws*JWj\3qa ,.J֖`_X 6xMٮ>>c[>11"f?oo tZQ4# hɃVOv$2.5vUGgper34K| EO?DoWsr_ۊ(d?:Q1|a{';]TЏ.< Q8smdrUnmt;9rkK:uH N4bq~#6J/d,K? Jv]gԟnM'n5ge!~br` TpƇU^]OEw fg~E+?-Xq3ck[/_át: \ruTo*&,xb p88LwG4);(e;¡& ~41Pvt`wᄅſOHw)Z48`2C7xt h S7wxfa {CZ95p城}6ܧǓzA^{* R|׏k4o>Q??}pN@PЀ.ypƶ)q=nͼBl!链 +Uf#{suUV&Y鯛W/m,d\ldN|IE k梍Cn=\$\%>s0?!}<:3d? ?+8,j=c1N c7lNc2 |RF#= hlNaڿկpRB1/|ɇr`,Z63.sT^é'B]-̢pW<ow  ˢ)4ѭFmH90ySQ77emozv}8kLmÇW|9g+xi,/Qソ} MۧO}t {ڦO Y*Dn[,"L N]m-(p`DKi@ הwZh)gOlbqGrm[^3xp2kCOTV4/{[^rbG1d!`T^n;{ʥ;OBk zq,yye0x򐱺\^Ǐ]La`у8 >T |[V_ \*¯_g_?j 7|fKq꿓(}>vQrC OAky]_F/r쭛|^W8_w &Ԝ8{9 8:$Nfqu xҦ|z?_y=pZp+}8 X89M}65mW_#vVT8ṚOW|䘫,*…Gl,=uxwgbF[ w|>_u[Phtݺ!ED}K:'@׹0Lho6n*3}WSmNq|%×LWBYi=z޶I70{⚼1ކwN7#ɃC<2?Y鍞9 )y˟:z235 M0i[k@'}1|~ۅ۵</y&WTD\7dBӽx:4pk4_|ś1׳8%kYoݼqFlc71D_+{si3!~7/޽{/_=G)ZL8:]NA8"'k)NNE?|G^PpW</ dȀ/>"aea}lGxOKGw}gvwdN+wVƫ2CtR8 K_=om'l+?|D.u}WnӷcYr? ztM?h~*^G,?JwEn_6`8blo>Df@? \HoN x&z2V ݌ M)L n%F~D6!OֳSSSQ'xAz&$ߺ}94@٩wxN~.mbq1r/Y {& z\?xp(m->:fu:~bW=U4f1 K""G mwʹ`[]Suv9-<=ux=}6Wߟ'ǜ q G6T`YWNtNjnQn7S-+z{zN&~5w:p12 T^YnN 'թ'Ǥf1ʂ<1cStN3zx0VūN 64mƭ kolP;q-}aqSGl%`C(y cṀ[q'y ;,ѿ$N;~Sr2gׯ _( Zw>u/Ņ'EkW-C#H2|vcΓ8. =WuE^qT8}th8&;x縻 O:?BfWMp_,-t':/м6yr~ZPzɉŷ(wmD0?״K tYdy־_aMKt| ڌkP><<\]X>dY O ^\x־3=8s1'3uζmzr`NWOqyC!?wyE> 1H^lYlmWh7}Q}KNuBEmקDw y; 09Uz=/+L!~A>vN,%MI~cz˧x~8:|BDoTL|X8>c bT/9_z>'WH7 f:#i 7;T8f_m +.W`u0~|;D@guvN_ MsZCӨSûջ<{oWV./0!W"+H?xS咮uGVGO]G:lt F}\aӆK!82EWWxpx1,͉nt!6/mՅzrbn<⵼x;=0~|!z n:NK`ϧ<ʋ` J0C9q3[3GK~XWS'#\pbpg2wḴ+;?t{~UЭo 8@"a*PT_6fa{zk['{w]oogBy-Z_T 9C:9t}cO3ȁU^1W^9-8YW~bVC8%W1"P]|x.}ׅqqt'z6 &`6@w6u=xwl¸VWf܍s;<]Zs%j CҎΏSn|x[#|bCr]D{JG y`ʫh«]0Nxi(_Mvf>d+m RD#}:}uX޻Ilx pnCxT=X|v;h>C[fOŒa@g՝$G@IDATtZw@x,@9$ܞ[ _+!'WH[7߼+_z{oqW }[ de+u67vT ^ENmc@8#E-{J:q!:Wk: >BS{hqw(V[և+c }%COEk< udh$fJ q*=K hd\l!jo^tۼ> Gqv_>ТFr 蔷ªe mlR[eJ۶U귋 g$YzmFcUz/lrays =P躲&><omF1x8Egsu>s?}/]tGi73&)FDBm$_6R;y% &:ݓb|/?BF ӯsbGwr&̆ZP--hsF,}W&ǀX`Vޕ x_gLӢ~h4:Bt!ZESg9W)cPaޯ 'YX#EiTO|sOs%NM;mC9m tE'9y2|>`<)R|[8vM츐oz;~mz-?3Z -A_~lT=SVWw*YXcaW)|Ui0anEF7~ge|4ȉwYS5|CȖT@ .s5y͘xS̅՟?Сڹpʓ~mKuwMxL݉Ǽeid84sග9 F[dڵܕWw#*8b=@ef~N_hWvfqmJxG< ah f M+|R?h*ryoyʰx&OCk#_xiϦr`d·fn:+wdN:>.\tIQQ;WC1FN8}N 8;S}>M m>Mm>:Ib,gBh܍nja9c Ճ8n8VfԢcfsCc1/6jЮYh+xRrtѺ: ԢOx.s5^Iq@OkTWh}iћ]Wh/nx,6ů^ԧcP~{5T{.H,[I8!U6E8WqUNfr_x:1ƫj]3ްUJݰ# d OduBPݫFlj[姌w ӆ/xVlfn9eKG9 i"ݡ[6V|ZgѠG7vocA^az&U@N? 'q}&`lOQgOQOAeUK8ޗb6~i|쇱 }O ϼ'j1Yx -o{E319X7Bw,΢pifw8"o1Sǟ.< 6) =- Q ;"8aN  ݭtJwK3m{bk#O:a= 9 0+C}syѣ Qt-nh+ʟ=b'L`'x~?ܧ./NKoq<\Iz +iǩia\=W{>PO@[hY k*`3A?y}&ux-t? Wk7uƼ2ҫ<8Ӟ} ^tG+o%W|)_Nekoh <홀cg\O`r׳/^!=p#ENN:q2y/ v9boQ=8ΒCr4u*[85' Oݨ1pQv#8exDi«gSfqw>v磭Jk*gd;+/^?,= @Kz| ` O|W芞byȿ߆IbQ߉v߷dyd0>*sgXm=4ۮYg&{yd@m%?8WR p,1 m>O¤l@%X?j{/dav8Hvpֈ9aGϼ{đt!Wh 6,0vGAA[Db.4Е kxʅ|[š]Y>Bĩ>f$3474oi/5ƶ'@E/t+_5.=S_}> |q(4~vStVFG-9{@(<1:Ȏ}M>Gx\ce ~SkF@ yn %ŒO?0//y5mdtNۂV9A]=raTb߼cɜ>ƃ}pgwXPuݸ⟫lbѣzq1_d<8_ ϒ[#IdOHRY5pU1'jB$螋 _~Em0ɉI}d'e蜓~^a*05X)|m3Ї3P9mʜ)q~ \97N=yMh+r.f>E=Bv^Bi#0ꄡ O âV_ӏ;ȅ7D{7 mD:k7ϓmt]ݢ!] 'ֲ|6WӿxOzq+~"2|u:ECfA~xO0gmΓI@*|T@r~`O>* U!G stSY/gϟ?s -/\Flr%VNqvGgDY4p3xu7j{կ~7 8osql_b~^| ZAg[lc,ٚg;-0\*b8*nOr +rphznNAPS8rԡ8Z>b#%tZmSFOouɃՆ!GW\3ϐpToCeT,<mСsم yl6%/x @Nws/ 'omr*Š2ThI_*β'8͙h&{*R+^|\ ,^peHP(?#ItgPT`E_ W@Ѩ_Ț`V'O^%s+(oC~t:4DW~ w:_\hIE8VNbnQ2+qHWiJǟ)@e..,@/=gM{x_ÅV /0n'1^̓i)m 3+73WȧJmՏ 4sI~#\TXئxm)|t( ^O()\ F:xH?FOOt&I7Q@p/C[ tScu>lR: x !`u9Ib7ɞ;gm)6՜[gO\'^' xoo۾;r7<0uv{4uuWf a'WUS \ ZVÁQ >.ur8EW6b7)! .<t2/~|N}Хc8|}9o߁׿1u+N /<ocDFCO \ ^a:V6e8)Girxwθ8|eES?>"|pՏ,Yԭr |ڔ©'7^V}JӱɎoŤ3ע>Q]ѷ+{7] 鋏G66#]tx)|0m녀 S% 9|M| 3NIt ??9qdl_Kn leOa Op6Vâ$މ9~o߉^dbџY/3Nq5(Xz![śa*3:JYk㐪asW_RbB#:`uR?'-Z导|."7}=,EF}lmn/NU~ Dzz-nK?Nq)'DB8/ˇj/(ORM&੼KW}wJWAj>Y~72wyOg#`g~ *HN':Ɨ0c6RcOa0< EkRNLlpSy/IKgaS8y¤rOmڷ2mmdahS聿,.SE 6H :ŋ^jںAGN&Ջ>zl31oA^?:z.ء*mW6#xēW?~Fc~2Lj}y_E8-B:G25[e9lYK) r㭼oY{#Ct=ߥȸ1WoPN!X|2z3:*۠ͅˇ)@ʎ6 p>g0'yF<|w]po-Itd$7G5l<=|:M>_D3+1DEt0*#'cl邜u >wqU;J1rm<|5:1y')gYۨ\=^=ٺiS=`ӎMΧ0mClչviU]iSޥ{CS8|(kKNn:.edvkN>i#0Nz_tc ݂< ІG2 _و / 뭑ey_[ ڎ7edOva~hȱl64+SʇT/ gF>: +ntd3׺^aյ){tgSQ^;}+smR8H裫lX=noc?21'Cl~/E#_6ʫ|λ~`rpY 08: o8ϗLߊt' EΖ0c ;g>uTπc1ELH^QBc?خ8o|;}}WP9|K:Wm.xM dVȨ-M|@+:4oM [>7';G3zY+B}Ճ# E)ڻܕJ|BK >ӯW̮~ͽ`uGS2@FwV|Ճ6 Bi:߈.V:%/a Ń^iT?|K?5GPބXS8VwkT~Ɍuƶm'j]|m˗=0sxsB7o^uGYLJiO`M_ ؙ6֗Eݿvڽ~ό'n&)ɿ 5܆ 7:gg˳f2[5Nva3dϷÊ{a;F++8i}9:A^P\| DAkȬy1OS1<9m6En88z4 xD.MHh}8dFH:u">o0"E4N]|GG.|P'*/8lcdB=8pLqx y?~G>pn~U5->:M<=ʂ9FŁ־(U6m |qN9] fľ{t~[q~?tMՉġzow6#G6oz8J(n)#\m"ݾpO\y`fɐKQpQp>//ˡ9_Ey{ݺ~&oh3(pɞ8_RYT>C///fB873ľCT'~Pjcl aKpo:Q0kpqCe Y٩gӡNP9`;UR02\ߺ']X},Vf8e.<>V'h~5}O,FE q`~@} NWWW*o6_Y/[_ȴk(lex*GૼN!|"ofTp\o.n_ʋ;:j;BVǫ[Dxmmkk]SQ,S\2xW@|n0I0=A~ӺO)<< ~)WLvuꤼKkXю8e :%8ʦN`ݹu8T|oo{&vs /jҳ m~iN<6LZֻ[qoV^+>;73/0q|Az/͕f!za:cL5*8:8r9^p0q\YdG}##\: <8Ҕ_ⅳK˅{?WGOkt~/zt>ρ ud#lF\큯|M]%=:]n*Se'^NiWn2!Ah#`/}9ljYTOuYՃ/13o6pس>tlА7"J-|f6z`Uʩ˭<|;D9??!L]f)kZ-6e#_ضhO~QZY^՟v|ڷб'ytg|[զ`w|?ϝahyy&^>~ C6}؍@f7NsПLDK/'~)[of9nuƕ1MQ(2Ocz Sc&F.RF({?O_c֟2C}a8aU#A\>ʭSW^xGSX5{ 2ωju::Íbbr#߾kB[>W 3f U+|ONg'9 RtGpws3-nϼ4T״cX=8GGyt5ɑ1YR*G]2f|'cT<УrhOFr*7x7YͶϿWG}'Yo{Jg_xPY Ӿ7|`*|qÁVul?se{WMM>aX|3CPE> N~] 3wn߹y[1/?uGW <9h u7|lZ^7{ыķ\u0Lv m LRx Ă(ku[p}KWxŊcPXeF1sfP>GQgP6>x_@_C i 28~ I^<\A!>k`ҋДn%=G۱0Wݸ!u'CP|Fu>O ~|fu#x ̏?8w~qKY|s"(DA]:`V_sUqpe .{+4FY2^o|O@[{ܼ68Ut}\ =qi-$Fu]w}GOMH0 +NՇyȏHA+Qx};:bl`|Oz08.ܽ{-/{|^~/>$tJ) >̝ M7w5V|&|,u_rʛW\R H@vsuCG1#3xWZx SWCa5"u5Z91q=QTkw0 Ő[MҩS'9[/#^/Wiho*#i([д[x}|A`8$muNhU{s7o<m?t\>8[ݥ 'e?̥O@ \=\ʳx&`s/}Bm嵮 :K-Běٜd>CՇqK^DܢC5O+Cy߱I{1Fstuqnso2ֆ:6 OȤ=KȬ>)Lej3 k ˟_4ha ڇMxܔ/UtBCģohcW7rb eGWVO!2&ƻ$='}s{80Y9W}8n*/hGS /^Dt6Z€;QI7hHu)yb?qtӱNOymNϱ{LM^ǃ1FqCM@ݚE}X EnMlrnWث :odEI`։KW_vK׮_{!9EUL9$, a4u;سp/0|R8t ѾvTGPccCe}9WW^@5z1lKd}i,ҫi/L~1u~>t.O|lpA_c[{1r;<񽡍50K&sH^lF—WlRS)=?ERa^u1(>ǂl#z?7H~hX |nx= Wxsnn-NB95#e4x.5vKt=0|OC_^յ,mY>rV8@˟Q8o7-߅S_ZtuaX\\~io@9iϳN!SCuTzp|ƀy'I6}<8M@R UW~Wn\-fN-.b ʈ|S)߭:Up|mD":0wUbm?NVZ'&}|Oc3ugc/a^]~lpm†ظ1*d˷\_ڀ'#U'o+0hqKw[6)W/ v ?G6 ; y(X<8fE +|>~_qƽ}k/Lan t`Rsb3¦߽lG51iOjb!cV*I1&澬8[0^GXQP'ŏU' #mڷ8 iœWk<o}ly\+#=w-|s\J_`WZ:ѡ آhƩp{IS_z)V?ߧnߍGk|\7 PgnW>sChd~lbvn{>\b͹]asz֩楋 ~_AvN _4}3|h/|amDE:SWx<0g,ct%?82G$Q4>Eͣ߀wm%~t}h6t: ,~n y ' S6 N6<TݹL@^Qd`*lO]jRA28h4 PnTǠ⯎n05(m舥Xe[׾oMO> ڧ}΍`*kucs=a%cÁRG? :`Fs(q<e܍/D¦By]A.XHWCy_?_[yŸh~u6 t\% '6u~hl& RC>5zs=pjܒ/iۦ<^{ŞL+-/dXխ46uq൭Ks E<,ͪBo 8=/קGǵi?u0<3^JZ&12I%Yվ4+Zze񷐨curu&p MuO_1P\W7 p[tHCQCq vcK2ֈ `KW]*}MҠkX°Fiyxw> h:jcR^],ByV5z$4fF+4W&mn> 㦷6GsOal7\eQ`ps}˳rȻ6c+|86{B\ѯQt_h^XjSh5,DoÇҕ >nUko"\~OK+OS0I~+ EdMs:b^85q˒營@IDATϰ7y4R Lь\έ˙wׂ~G{ `-86ͷ|1DPۼ ȕ\|.;◲IlvW3+di$9#X#g_ݛ')m?\fQZOgÓt5b'~Gi/>rq凃4*'W.[L{,x'ޔ] u%x^}''ʥT7mP#Ìat /e _x;n YCN+ (4]4_/֩+.ci(O]3ޡ'XS_71{E_@z5 xeE%hcT:4ӑ[cplD3Us y+G6kl ^7um__'$Iv5fx>y:z0bU&i||giw[cKW<&7L~ƀ 5j;gIc[蔋5bh v;sE'9XCx #ʫo4Oi+EyuV =W{Nu{5kgOw{/+W|ȴLg6-xzn LL̓-ݻȼ-sm7L(jOΜ>gâ! N'ν䜰[YЛ`a:-Ϧ䱴)w2i*2 ;#zc~暷:S%e;O>+0 `|:Nmק|˅}Z/,\9= O)[m"N 9L; % OƳ }m?|akQxeţ~ a>䳐k7A1M<ܳ> of{R~>JEzPqЁ>=][vɠ:濕|' q>Flyϙ} 6e§H0xA%h .c-ŒD^(Ed_p+ߠmg8h?&rS`S~plu _U]T ߓ+,Fނ\ l~V<(/F<~*4<3nS"r:-NiI_qBQ~^ i+ܠGiLi<;.N`a4F>{Q?d \{gx6-_6tnH9^c;%㞷k)W'Wha>`N!V[/@ʩX xz~ڪ lDm.顱|E8sySy0? Q5g=P qov_Tizyoo>?W˿tKa??qFDYnpnyn&NL;Ϭ3y?V;nsaT /2c6yMxʃ1[W€:jyW >iTɢ}9&˫|Q:Ys_W/Շr X= VڀhkbD}j)7ё~Ԍ~uE4s\]/^\= zeo?0Ս|iFeڿ`OpgukN:Kf=|sz@v~[P'/5LeVGOB.{OmZzr Y~ayouZYko&LM*=c[:rmM7\.6m~zU/_^4OIBq;Uyto9Ut\~̇ұ9a1."Go{ut<&R7A_xwB߯щ8O> 2OdA'x/L<̤L`FW |"$T )AYp2q4:&?`˛vah򍎮Jy3,eQPͶ}KA]ofH1̾ U*+Kʃ~dm嵿|:'7pQa2Rg _~k᥏oЈ)CoGS7g / F[_2*W}Н62?NF[[@4#a ` Ret?ce2*Xu.RJ:iO.mephd&OG%),j3kgy_4k+ce4SϮ}*x~~/Ek|> p ϥ;}/zW/hiBnoKOg+|7Gs&T* *^${)n&ߕLw Iy%,ڳ=ebU'm)ͤfL5N0klq3: ?tF>hh,]2u` X,NFxC"@ц>2fџ2 E૬x/W,|W':*CG1 = 媐›E7cN:Vgp]xmkrNyz=x,TrWgs.WGO޹o:xՕ؁h٠5'Ӯ <+pa/~m)>iW.i? *ʍתU?mM6L欹f|~Â_WoqU~A-XB' cQ@_V/s qC̹ 2Q4*մj OF2?u-?ݏm8xyOrە5[< _A2w2۰pWAliT;<<ӑ ѵCS|Mjd z6`y*K).Gޝ,[v]uBYWJI-tm=^</ !Z pږRy73s;es|Δg<Xkvm`wjwLDVq%!{wWӺJ:vl/KK߁s%_eSvL-1a }t N]R\)4OLسKl逧MD^k'B̉ ޲v#F6iw\).5??t _)y;{ %@;C_ og71 Dov ܙHhwiB}PڒdrX)eg!H&Է@{?c m _]NwoKFkvA]T2 .!NpYpqG$믮~&A%/㇔W*3k;WDc>j)$DRS =f'l/G$>`:]mf pܙ19Y6WmX[-mv}ƪw9߸Z>sN`ߎ[?IH,1|Ny1c5p8cL^ڇnRxo B& s|$L' Akb]0M2ĐָS&hDx')ilTY0lVsKvз36r\ܝ8=l͂tlIƽQNTVukٱoUp&qlٝ6Y~w@o "泯_綿dR`59<rulqA/u"f5W܂nb|%}GmhJ:D<N6\}X;hg> JgW_x7-2v](Єgc|g[XldjڼpGށ:d+Md>@JSh+6޲&qy6]{?&[7yS,:O25Vs+U>0͵Γ'#⩍VYY=nRelWyђ 3pmw(}W[-c:9z:sHe]1X9ۏΕy3/%2_>;| C KmmL3 k^4o^~ȺQx|'O3߃I%j%G }.Gy҈ G ?LpL04-iBliѦl/L7~b9wS#UK]2a; ` FerkYtMP0t784$}2|/YLNx,?6\eo*ʡkbхBxtK-=k?6>-VIff2]q\+Xc&ѡ}J=<)Gةo#@SK}i}syy^!.>YBC1+sU髐Ihd@{xSߡ-&І<"_?FϸGV'4WC-pad > naj7x;zw:8SSۧ9ss)+vlmM0b/ڏqp$wepКS/3-jwm d~uyS d.? Fp|L,-kNO :eG6L/4''W&,6GH!'3 .\EݿwW7]FI&&6N[HY `@&)Z4AـĶ}ԻHY" .Oku46LS/U0xI~PxޕCgi|a9dXn|g3Ǔ_)&p-h٦ fd\:Wim!y,vG!?RG QtR`,6`~O>ߜp t$.٫w&/![N __ q=~oq tcI%8f/u~mw9d!eޣ'Y邉UL'^߇ES[ Ӎ9Bzh%覆[t&J2IKMLcG0Tl_,R7gБJ4G'U?AX/^_ml.|sU nox3gWY)G诎/:+]أ'^д˵>plF w y.w/w6WXdNN69$Mfq+]~%?wi_#KJk{:}ƿ) eJ~ 0r&; Il¦2;Xy$6 $c܋C9YI3?؟ǟsH uNvt~9*M#3NT6sU݇w?HIOKI{>):?] Xu4jlvYvBO8~yPJLyZ\8uqi;u-^4g6E~u~0]מ&_ ^Cp<0z''N_[FC?Z˹>Ckʆ0C6y8/AOO|.?:s>g+Meq˯4}/kK ;n^d׾o\FВw:iOv,{Nu*RHd2*Æ:<+qN*O.]d \ %yVn땣 mɆ?mnna{_i*ksTm0zqy6c<29Tg~iF.d7=w7|̃ľu5x󅋭yQUua}n>Nl~~U| uޱ׾c;pb3g!o[{'Gw*'M&hH^&{ `d >czJtdl&lAqK$2YMtp }M*1+~e3) _obxW΋TʍʡF`#DyU>pQKdV\k NY>G_ົ3](m",/jii|^QՎgAwƖhU s[ hko3SN^&_~vP1#׌k}=bTVGWi~_e_.,/T\ޞӷ+>>1G렃+ Щ 7 bw[ڬ<h `$upr킎|H"#6Ӗ" 'ާYC? _<0aģY6Ν{Ƙ(Pr S;ҽ&?)]?Ɇ9>]m<6t]{ЭF  .S'݉Qx wBPp?$Yl>2 S΢hV87'lWSV^>g eajR?lacߌo؜YxF[fՖW_ݖ.p,vU%./ўm妝ڬu!Wʆ:hG=o.X?7C7}D3$Sp+Glܿ5<;b`JyV_^.pk2WtwÔR N5> Li[el8r>Oer鬃֖_M;g~fwJu}$:ӏ M+7865n} qw鬃 ݹ+ȗÀ>~GYwIV F|y_q_x}H{Hx?xsXo |ʽOy/vabK7&~rZEY[Rv$m,Jt p:2޿~{Dք3dfmcK)37kxcO9צ F6ym.0hGwxSr}Г_%J &%8tJ I V@€+ldyapKΙTuhBF'ѫ6&u ޞKkµ_&/:h'mЬ\e(Y4bʋ+IS:Jmr!NzZ4[^t)d{.xxX;D^ &Z@dG%8hOFO%GiVf9FuS_ǷG>:8|^vE lOSҭ1CW8]h&үƗrМclm_uriӹî`C]ܥ]|19#~d~kX};hyWo'Bnd+BCMk0}}o`y0ݽ#"dݔџ߫." h9Kpz/Q͏DA"7 {@`r&-K{nPv"vDbRt"u)Ip.nm27g:neܕ0PbJ>: M8+oH6o3L/Qi !îGۥ'W?k7X~_Gةm273~<$ÞsZќ( wރo´ډP?}ʙO6n[>插[ݱ!GmSƛʈ7:9c訃i-esi7L,Vq|:,ҞA=_|- _k&+ӝ gy/<[3Ǔ 䞻StE/7<$oyId1zFpQ2ҕxҷP:%)śOrn> ^ w9n{4XL%(wOOϜ+uLhMy_ :P4l6*Ƶ1n31L Sq?6`͡WIh@돱”Lc+=:Oc{rׯ2lKh3ZfaJ Zrvu.yfe13n wʥwɏ)cc@ W g k >>+(/ě2al@J'JONF}ڧf 5J+w#EGstx_a^zJ nK}xQx.]mmywʝFy.tԶ1[94mW:'Q[[T)[]M%BoP3ݤʬl,]G'2Qwpw_\,'e:͂WD'!|Ll?jц_vb_?ۺysxj_l{tW`FhғlN*%J>l6.{>7|h:.|ӱ^VpFԄ|]WyW8=@ڀlTցZFļ;EV+p.lx@,Y;wtL?ņ`[oa-9LGccWYA-@(Nvky$%;k j+֎l` r?GmའCE'/qa#dk^?xճus_h/ ,x5Wқuljg1;߼1 2_?4/ G's׿Cp|fJ5PIm>'GٟO~?H0f+y \{$ft06.KnE yL OѷnV d1ydvRv"xU}~Sʩ,Ne"s'i` SjoNFnsWxxbիr GT=|~NFG!])@ΧtqNK4TlF*:[ŭv8fw㜭%`Е(t_їʏhhWٔ105-: ?J\ͅZږ9&&̀|ه~xӟ4I/LcYNO`zIxϽW0+Ӧ/КY@CD+,LNA~`+uE W`w ><49LW6ڥkݳm$5/2y zHEkK`CUӽ|,O02<8[h5iY/9Ap. ?Ix*<:IdWG gr1^9g;6dK4ʬo6Iެ7UZm섳pcwc.*;|xH;}0=jUx`x|VX9젙x;vB+;7)Z[t8"my0XWѐksj[t ukz9L kvi&_qЮ<1Ӄu#Sօ&{ٻEd޻vYfšV}`P_? :xw޹Ay{PWFs=d/տ=#KGNdYƧ{FOTWͼUJǻ9dyWxK ~)\foE\8KqHGJ+Ki8%‘:, $K7"w[⩣%莆10]>4dß[~Ęo\j˻5f/+vSVv83r̉]\e>m 0,ȿӹ:SO[ۓ~7 2䫍mh0#68Ɇvmӻx]P.:+H-ysfP_ '<ql^d Oߨ__sQ,baA t_]~Y Lmebj|.>Q;yF \>`@]m׬ՉrG^M:+l _g.mߤwcRa\I͋?tG՝LD_֖tY?XIB8q;=k(]wIM0)=[!tY, 3*2JwX^v2TkVca6yˉ]~/+WZ|n-ʚ)ݺ6&|P= 'ѯQ.V>4ڙ-:IZyy[`Nygn~gw_H9㭧˜՘CjF~P@zqO}3*ڥ,?I<0N^2I%Hpe:@@@g&Բ/>+'#Lf勞TM|e> (;@_Yg LWtE ah?c_?<]^;ŝ.,0lǦ=!HxHpJ߸1=xѧYv:{}rN|ۼa Ӽw"<3g?+mTLi69՝c O*#p{ 郜h4޲yc4LZ']W v@IDATJ0ʿsQ=^S/ OsF-_ᯍV)柸feK = X= u%?~c||NBkzbN]e4//<{>ҋ~lr+JR6E ^jy% J:w~Po'gd6|u&1.=%z]-Ȝ4 pA cBi ~f2 vԫUANT: o^ . =2@Ahv st Ƞއf![xO—r,JNh3V oyW矹b8~*L, i7~*.k?/},AMl[`L;{|2!+Mteo2dsgS <bĢ+7>J&y|nԕ6~xxqrW{[:E6ܣY`OOl[ ;SE&<˗-Г%8s.6d7rCmr)7zᓙtS98h߃&u|jH/6 >KJ7edwY>?덡XoeO;p JW\^>pKkl8i.ԯI4'3LL]K+gB lA<}3:l yqɼU] > L&m}ܶv_RJ5qvuR臎N?8Ȗvۛ~;tz{zd/qгIЛ+8]|١gCRdy+>h ݦϪ}|:w*O~A[2|6ޛ[_Mzn]Ά憘n[YCVlSEGs%#Kڕs|RǮ6>\s%/R+.?qAgc >=,Y\Nd1NYz+Ƌ }/[ÊgN+#d26Zb0*ou!]Q: RfRöWc@u݅ c$nx|WBA{B\,GpC5JEw_NN8Ruw{,+`lNinD) >sȮ0,:&0s%ł GOE&5U&z蒕1\8#FrDXzNwedsbY9|q-aujwPGL-2MVcc2~?z/7N{h]=L?C16Zxe=_iol8K|sXyi]IcogCjʭdڲ2gHcv&3MAss`!:ƀy&=/y2_6s_EiUf.>sl^>tKËC~g/iӥm}S0< mOt-N GxWJXs]Ax2+˗Q呂u V2 -cKcoͼ6g8ʰe~/Y{pxೋg>]1~G?OQ*:ix`yoK|7W% 3~EP Z3k$k`вb2xicM¯='c'[gk.RgʓOAM=cѝndVG2IJtȴ?Ol-=rظsu³~~6 o *=}x -~dhY;pK>}m֞ݒCy@+ePx kwS㓗RGmHވ~ѧS;,Pf& ,''ICP`(7>z8Osu}R`Dmԓ"ut~W MoS`ʓn6 L rwX8{c{j8haG8y8tGl8ld!mم|>Xv=ӿ M_2_2MC7p@Ck~)f<7rLrQR],>z.L77_YՋ#㊅o恬>6sWhooj&hF0GFn SߒA9فu03^m™6xSGʯ.lJi/KՇ =z.xEzG@ï/:]rhdܽ!4G/}+!?¡.ZpM:S4&O)DdEK{?dkzBۑY^ Yh2κ~l )BNդ>O7/W^Ck f6ko( G [K}JH3m/- 5u%`mns@[Pۀg1q ^I,@s#tQ2:rzYtd|襾6kCReXii2OLF4Ygg҇ޒ\6<#:6.[p"`ي}I 4v}E/Óit]AC]"ԱG;ذ<=^-p~vuhr[ѵea+W˓| < |U'bޢ\K9t%ؖaOgie]&̎?2?+9w*&W9s<-Nr;pLXGߚxFSJ$u*}2{󉃷n ZC,dZotx֚#>pEÁ7r:\7kUӱ'5֔El9c/#+K? f V^k^4yH9 Τ4N/^ڀh-hBnm[mv|p^'Mu0ּx1e&L #!U3бpzfk̤&+*6oE³ƒm~74_„J kq0+*)FȌ.xH{7A ;1|Y-Gc䳩=v,d[Z>pxwӕ|$`qc<:晇7XV3t?m6ДF{Ͷr''NkBݺ[]NmV]* ޶/E:Z*cn|47g|.6U=Ay+s]/|ןgAΫ\׃S4|++7jQF.^qT` |4]1үP@G;32~rɥ-|ۉn7]84]7ҁO+o.+[ҫ|޽wŏp')}́s >0^O9\8s֝_wL]C7l |T p%{]]-<zy&ƅ!(w+c2uIEqȂnpW<]Łmpw<͝P3g&a@2&!o>4!|DAaۇ} V6ۋ\]]IuhI`k!o\σx^Goѿ~ `+ՙK#ܣJ}-q8ܐ/~]˦qOO >ǙO護ߺwͼy_gό39 /.&]_\f3#(0_ ΤԶ2d˝kSpDֵס—hՅmyW>{]J'ei\~%tYLM7F߼!)jFٝUkQ&ɧܦc\Oǂ^Yᗷ>&;>_y,(ثQ'Gzi]Y:J =fᳵp gIVemEߑ+tb|*+:n֟3"6;{vi[6L~teyf+vXI< hMg}X[ڥ 9FApZ/0X5ӡK$`~hzO暸mFVxr7yV颇婼nWg2y6]䙇9+hR-|\z~Jp,yz BK^}_ :zF&[pF緒^ 7z0s9Vd:+؍uf1? mƮA9|N&ғs yG*Hsu|ί IY; n6id{FEhY:wl=lѕL*0ҩ}"ޝG-6_ɍ{x٬k^614ᱹqûzp5{:;ks'k{~j>z袯<1`KyXhc|H l: ]'^yLu+]9~ rA印}{(w~ӗP&@jzp! #R2 \S};|7N&_H,b,@e ,fY:AOJ&EL r^#OAL~x;-cPJ lqu%`Kxӹw,w9=wx>z3cg[$/fV.նd|(8AL89KM0|$;E/Kxavx0tͫƦpvyʭk'ӓg^9GTy6`Ӫ_݅/̭]5|vm8 yw\ |(:X_{*qγq84; bHLQt5꠮oi+C]#ǎmp9 dDnx7^v}#._!B}hCN.?a>9s? :\: ~7*>y^^EwLcWW|/7Nx}䗟 ӭL>8{p6 &Ca|Ra;L@P"69:L ʴ`z/[f=- n,di&OIhYH}8cC)A.4`717u6{Bl&dM=RѓXu,TY$~2?㤃>#`cٻ,2gə1<346GG=laIgl ^,˻rV94蟗kiX'NDmj ~My2mk3:Kz({8D8-`o|؁32-=wtҖmTS  )1߾@>LX*~mVYx8wKY. cxbR}>AO_䘯H~Z鸛7~!+nAQ+lUye/ov恿˿Eqw$Łq`j&A,<'yYX/ϋ}CZDBCONd^WK 'b|QH#).szm*66NݜԞdQN8I `HoCǹܶrY$ *btH5h,)KS+\hGـLJR)Mn 0;aw9.»G6}. gldY#y8ʜۘ9+ JkY38',Ix*r,LM#hGv~^rUՍUۯ+)e}xO6} ?klh̦ ŢOľ1U`5WN|Owz4Y4\q߫nKW94f]io2Wl$C|;;K}{@ǿb͓lF>}±͸8}ye|bL6jY}ry%M`cste ϗN+O$ϦTͯpheԫkԛ#~%Y')\ d OrV6}i0t,cMikx8! WCx|?׽CoaɬT_7<8E ']Wfyx? 5EiLgD^7CVp߉eɿ)^f|n;ZبT?1 $;}Ivfys5zM,P|( )X,qŵ7_<6HC&Gq94aw| _WXD;Dcx&glTmGG1 說X+gLp~I.l3;| > >AC٠\WU<m\ÉV-viAZW‡Q>GHMT\hSl[>X&Ћ}e )S="SLW An낌^nyLCGyqLW4#/@LŃu望8Fw"oa@oʌi:7̂}7Zf҇C6bIa[~lven)ɥ u=kA.{Q‡lWE]=g,:~}G3=[sA~|&6ukQ } /8FOEM_ ^ -=1%lk^ԩwUS_vbf2RW6??s9DV^XW;rhy\];*N‚Cei<8'7 og)<&>;':hd`pq@V`@49g򞚿ۿ¤Z0_?`7Ʈ7'lcu`XN8Xa K|  <`H $n72֕{ gp57\>o_<4G-'|;՚>лlsq`SLn,ٛW2w{UNpΣvVOe58yI<~U #~tZxa?~vo׏C3:rKRX}[Y/>ˆcc$uqVcNr˫6oWFtwS)ʓ,~{ǯroldט;Ew2~͵uc-:vǟ|S^^SpJOO>3)9x7m͡fКy(#;9~|.^4)2G7~̫G$9>/q?N@zK 79#QgnZn=_gh6(-iJ=7=ME>8-KG\MppEf=S9sl]0(Φ-}39o>fWG4N]:|~.npgV[eee6NE_ÎGjU[5ɊzմMe#3V_%xx|{³H~{|4WM,ZÅ;/'>M^W;۹/OtK߸Z|k>Jór-I{xtW.wV4wqPYM֬.\~S§:*7ed֠ʫ WyW_Q/>Nu/r|~2~//Pyk᫠-|^'n5 8q{s+-[Nhyh&`捀P <@|0Ƶ3=`IЉ`xPJbMTi?;ëiSë]ζi3KsNa[^_ll#2m]yu j|=F..ngCQhW:' ^M}VyjɾduJ{d\L:nI6mwӷUeeCl&=9d}~x~u`Jcdy2pק'eB% su>TG$Q9cSA4jl JƴON֍z M}/VcG^h4z8<ش{y]uvY7W7Cľ^JG[*^ŸxZ6NFvVG|k'>9bf7r ǟd<~q.a?3sɷ|490˥ ~nE[gy'xփ)P;Fx0< كO8OjފSi=: W,`@ڲܱKcP`J\igK򙀮TM|Ξل,K)1JW+ 1IZ"!Ϸ2k6)1ɕ$>4F7 =~`aC.wkS ?7:D=ޱR,Wt #6w[S{MS'}Vl@o:K}<蠔K67=|#CObF?R|CoduIdg[%MS[o/ʿt+}u^á{m^;M̿fi+іɯb}<;l3&O(V΍=w{7Ɇ(K$zkW奬VF.URȻ왘N 6bH6/yO~2~/s18|.=u<0«g[|ϛ~;N_u$oA?Aͩr$^7uBvݾ@K^58$}J:xN3c /tGlY~C/WlsG)|KײA'+>[ }?)m0]^^yEjm9ʛLih>dvu!X|eWRq?p'ڹ꯬dTFm݀=IeV;/{ ćCjY}I;&C7ptT'/[̎QJ`ƿ7T7쎷Ӂd3aXi/Eu'GԘP6VWwo@aRCK]4uCW3vΫyw/ݽ9_zf BIƭ!pQY+o3~/rCKyC,oNPn_߾(#R6Tw*0:ьq_7ox1Ó?#oq"w:ീ &4fL0זZNc¶I[n 24_&dWY.ENz? ɢv} We3ODf>Sy,>rl_M~ux\?#+ϥNeDϰ';gi|%Y߅-wKr3~=ݢx%VgGTl ǎٌ9#Rq#|`$pѲtgcv%K\W5&! yI^GTSCeUb=v6Cvw%p<ӷO6XEp~6p/S\*Œ^t+#y/3gN NشvI\>t38Kh/%kn-n@O:X24躭><){s5meK0}H.Տ>bO*xEݳNW.j/?\^1_ܓmҠx~b}N2Zf8%x.|.|N^!Hu`[¿_|Mz^rI2ftN0*4@ gv 9rlo㛌`+RֽUV>0]9[\Z/p~eMƴe7v#'yCwo/E-Ɇ|mu86igS%W|b8L `Jm.ą:zۻ-᝭Eq'Ks֦ԯLp.Q }Vy3_&5{6pr>6MSMWrm9rXϋƒLᑻqiI}+XuGf(ieQ$~V/{T>/6Ƶה{Pb.츴ז1$˛[nM_W9?+wqVB_"<>䗍5-%X|m1uQ_9x{xzI[η>tU3W/{ }ݟ}}'nMJύi+?uzNtmq#oQI~}BY6~ޱ/yА^6a`F﵍mϑ&w4K|[8ngK䒒 1ptN IK}tI]d];(a/|/ގC~ Iǒߘ~9U/Yv3>2`Kϸٜ٠7t ߙВ3{-:Ob-~J8+N'9^J}vΎo6nupM0g=['<ٔ@{-S୧)}訫h꿯Bu5ȼqjv0<Toc'w 6H =\8؞AG`IwH\e[d,+ >s2O%S, 0=/#kr$S=˰%G~rҋm_ܡ.upM{!۝&;mY3#^;os[)nϬkh9l+e}Ɵ2+C]]C:ܿ$.>ԃ{G+?46vcıGXf4`_Mpr3_N~=y_\`7q7w!``ȥɸ,P2jS)"/ Z-}\dJ)^#o/ #\̙o! &&>c t\WxBM8\ݗvaNW0/|K7DXOY獯o1\B^ǝ~h6)3`"xmp"E"jCɎ@.qWsPZ-m> VF?{TfneYk=sċ4Л$3M=3RVЋ:{zX ) /5^|1tƽ$ b;kN~N_%/(]FOɴ|./>e3W;c?YE LP/y_~;NuqiN/&t霪$?Pl^yOGqb@IDAT.lA 8khH>:r-ɥJ?0%8ˮ__>sz wi+!F o>ۯKxgW[Yi[ԤL/_vK6 Jmp,zJ髝ȯ䕒RƓ.g>Hlmp(>l=tUʮqK^Ͼ~h'3~|";|DֻG?zDnLEL]E+ vcn5NVog|\x 6\3Bs=&g";Mh&3t_ׁ<|`lQiaWS"|LF}Go ^Y_&3 zJh$9_O~zͫݷ]wum^ Ve;%ͦ&`WFw>D kE?W4Nv8D_Ύ`EFyd69 dž`x[|4wqK曝{M_˨ _ҮSir'.1hrtYt3-oL LЯfr|q)hZ ݽŋyջ )X{{zf3^_]_(@e~>xۂ-h-BMx{ʤ^v}erC>p&pv-}bM9$lbj^v/[pvȆ5dWfs d_DdY1ꯏپ.p>G^ 0]%i=⃓2I.)=ƿ//Gc,)w_sr1xɒqBIݒ>,n~!Y_^H`]![C@&,pLXtK=3@m8&}ɍVvO0M. %}EIYV֣^.:ݫE{~7q(W7^INBʃ-m,;zeG|Ğ6!2g|!MIJ?T“յ᠇יn2)L#{U=4|Nn,:/k]لMfwah÷t7'01 }t[1s0x{weqqfQF)v{qqʝ+}È^v?&8tuDnZ?'W;ٌ?;cdKV1a.i&9>%YҎStw/>?vz4'ݘ҈d/=_G6GXKDϙ~TchgS?82O~r GL\;i3Ms9vI^Vw远S 6.hW{L6:@juE(u__#3ɣ_Ϣ7y+ nwl2:utT6I[_٢z,K|A&0Ÿ6~N`xJ.WglD#^hJ ?]z*<'66+p2}iV&1JX8G &smxYSQ7}; 7DזDҽ-Ğwxm#t{MJ}ѭIk+`bٝy=֐Tt>ϾxbFl"U~8}}x#ꋯyvꋎ˻t]?{}ԥ_0y1~?'\0o 5˥m{wȭ$WK>ƣ৯K;dv|~7MqDn)/+οѱSܿG~4^Y7^{bg\S6.`ts2}7)XƇn0,( J *4vBl&eK$hz<{w񜫓^>Nt-%<9=-^tBq% 2H/ 8|MHJki-h#ɬ+~lp>=Hd-:/7=uJ`e&xXлka-}[FGaxmp,w րO>GƦm*W[ݸ_QdD Qg1<}Vݮl.,C ;b*z쌮D/ӯam:;{Ҡ:{d36|/%WZ[Ncf8WLƣY[3[#lY0tX"=Zmݯbn'中3yѸ2O2MC8Gsڣx7^#?3+?0򳗿_;?חB6˩{}:wf+xߏ 娟$~wpLȵ<_2' FBNjHrhRxD2Kgxh+]2dS lLOv5!GY fW<|:C٬ ?؜%rVWv%;^-Lq6[ dW2+/'%KJ`޽_>pó{𳹹R7f[\uK']ugbc|d\&? }҇ qֺ `yҩ|̟:F|TAs.i pve+G;ctKvLvcW4S<]6$[QP2;29'd9gȮp⫣S#9}<`쭾px&Y15"Yًy7o}Ye9쵥d4γL}|qL -,Ywgnx.R2`2J"xAv^eڴ// XO]k1$7u `4VnL)-N2f=tv// /|ޢ}sC>o-~Z+`h%'=O] 7m981u96ȍ釟}*?K\kWg2[#g_}+;(ҝ ?>*֘`FGRkJQ((fI x $>J8[ KySjnq QO(xԧLn:Ѐ/,S"ӓWy)l^qwgCvzq{LNvX`f`O'K`{9mO_X`c+\ow#zv.HԸ __.]aF+$ >ulp%kc|n@YFGVgfkTwa\(;J#c_}}^J~d$1z4gĎ~03JuznWMk]);%}9*֚/YȆ?IZG:骤+u^ /)+dKxdEGQOszɞpЊ~k[24Yz:!m<~zmcdŇS8ܢcz'Hy_O+'1XyY@pJi`pt6EJڙgVa/ R UG[H6`\=xED~f}^4 w I<ػnSƠ>-铢/Lp}$2}6[p|Bj܂jسoqW{iiaQ2+Y, PB2q5jn17N:gl|}ڏ~ W_N7r=eG=d28 8_؊ݲ񯾥=r9O>>'d7k-?~9lgL62zo`sR"}49q8kKӦ ?rŧrl+M%e8,9r蠇6y[.z/7sט|:w ɀg:'6o 9ׇt5Gn6G(?c_E8͸ )v~n5 pҀK3!Q 6и ^6&`dIXn .֗EM"ɈQ]͋ۏv W+L;f+e ai"[Kt"S6Zƃ|>K`7tO~?pDkҮqc>}pdDӸ\ҏ1MT?>"ď;HxW o&u 0]&2qԕxm Hן4̩L 0:WMk߈-g٩V 2xC[gA/^N1pn\1ps,4=,ϡ8`^ƞK.Wu4K|wR_fsɮ%`-&>`œO%#?ƕCaOzW)a|OƳc9IS>J6G1|&?b'6n>coQh<0l.3_o]]%WZ?,w.Y#K/f(X1s\Iv9nɇN98ɀ>ze\)Z^'?hd{ӜQt;mV\%cq'ګ !7>3cؼ?F{/&iPb>4gKcW3/Gl0M3\@wRTF\[zo\J-WLݞCG09%$g mtK`dJ({3:$\FIF-:^a_]{_qgNoned;=<03=*scJd¡4V3l,)odgLBx٢G |ia_{UlG-y֋1t8W5h5f~:Kw8GW[oĜǩ3$N6&n{_"s+Y/kSt+0O\ӳy>bMc͝8/>r5}q 89^A]pl^xh@NFi$Xݪ|6 &\O.}Z?eVZ$x19 vrtK?^[yOxJkGcy_U!E'[Ƴqm (y೹1 Mn`d{Ƌ>9/Op,\Rϖ/[3.Stȕ ƪ?W/eg W<}v~OLShu0섎yxkW ^Zyޜ[^Íg//1[/;oίNC7^V,s&C"7~XzkNA>3Q\><'ya􄫝Ɍ6ɸ^" 7]Oh|F'w5%9@n:^^LKw.:@>xA:$@=24ޏ?Dmy]9NxA;0_c`2~AsfphLdPoYږ@$ EH' dNk'3<᠋g v7y{-` ^+k? _?p*í4xEcCfq }h'8!O#.wm-7ņR/#9_<8m_K=+ų'Gzǟ͜j?~d?0Rd8b/2'?7>&"D>+a#s=ً%`ѐߜ)[v|F=k T~)_?q79s7 }mN6ce^u)W3?Y[6SƷNҁaqF_c'W%N*߮_{͚_7)Cof-^_#_^??_{_k;IۄTLJrAc,JBX=<{ OAn4dxN^Wn < r0N>kuL6dϕ [͘x~<]N[&a8c]E\2'~6L&=G*>oah򤉞~]łɧ>0OnQlVjIg<ꋮvO;\anc) etϱ餿2 Ǹl9RHLRw;yW}cgW<~l`%ragO6pmnL _wuel]Ʒ6x Џ/ F#0R<-08Kl8I ]ԅ,Ʌn8хyn6Xeu$̟S_gFg\&3kоp$J?cI^ɂJ}3w}3JRz Qpk$<6/՗0-H$pِwx& pϿ.酪Ol94i尵6^eCvS;6 [‰&)Y0>2V2~.pm6-'^SFKFƦ3xgFs|,YCGW{]c% W^|ks)h~alam\6+^nqxbļG |8 kbsFU8r/5pV+}ߣSNbds/͠Or!Ժ<.}7%؁Ś6Oli ~pp%l%oWk2ċx([[ ֆ~[Y>`Ó)O GG;uu3qp uI&Z+Щo3KYp PY6V"(GrgQLl N/3NtQCE]mem~w}=J2IykW±`/C^K1dx' O[`ϕAne{K64֜;m_ۇ@pdclh@o}c 9RbSx9>˼fC -ק|\^XN鋦>4"9%2 E8>N>@\I2%q\-xDga>G+a{e'.91Yz~{E~s{󟌷-v#};(=ϔD~o!`ϼƮ+qndxl{b# yzg37;ۯe_ O }6ZI}$J?쫯o>:px:7v ׄ04ڴČX4J_L&+t*/OphQf*3>z٧`̷#`.-Cx{֐;l|;/MޙCe 8؏ O}%*Csc]O0N0qP9N2?.&=%e`α?C5`<4Z /Jb~絩d'Yg??\Cy5(bǬE^7]3V)z_yͦo& ?2;S6`'YX'g''=-:~3'Z##83? 6}wXfo^Yseu O@G"{v`r)9Atos ʑ)τxK=3_ؼjR.hv͏K;Ѧg"Nm~Aӣ$'/z m4 2/=8Wpc߂~NƤ9L2 $gNDKf"EvLR&rǙ2[]8h{2c N_%9[GS?If̸,=sK ^[Ѫǧ>S=XW_?C!~ x+(6Y8gf=B,b60GT;3 .pt&>pJOWj=im%r-߾}t~6޽+\' 9ߢ"$eѠ>67?]{6|Ћ6My;(pVY`4IoPOOd4Ž>՗)h~$7:JIN;l ]vOŢd1ތǧ !@7d/o߬Cc$+RHɉ6L3rEIJقƨ6D0b|o`n}&c}9Y[M o{&2lE_'L-+/ЍSd۝CD+؛,D 9R}ɐK&;Azʎ`/%Ke 2?{u唁,&M|ȟ~NO"bzm-~ʅ~ELd#\o'`oۿ,dYT(Vwgpɓ|d~me8 9#6NgKvlPƔȥ}w.xk[Xqus9[0xƣЙ J[fu0ĔoܲeF^p[Ϧ#fh&K:S6o2}}`W ^_@C۸he9KbMV"1Sʆg';s9516k\vz̿oܱ;Μ3ia_9[+Yi`$}CcOYF?>Y?H/~_s/'1}8m\p:'oR]'nL>6&O^&\)VrÍfY#RRNll&\}=N^h$<Y|vD9{E\D-Zyil7307YWF4-rbnrg;v%O~m ΄iWӋ|:97z2զ;mOo` l~x"|*1+\ȖU̒a9^#_IgS}*?|ၟqYK pݍ`o~\9Бo٭p:t0O؏(}JWWq֛qƛ)_M0#qY0MFq;q׷a.HqƑ#`|Edma,ƃqm҂[C|w'gY&EK6 |jI.xy?;B}!&-zo> ]m`-ف,ɓgwanz'VV'񃲶 |qğLЖM|xÃml(6;-GSY}?'$mO_I;>q%Ә]~ep{+f{1Z|h5f#sEOl/=؋[,|z8d0爵F Ot޼QwepK-V?ٯXYR_bgHgC)G}cӖ~F-\8 DW~w:kICjmNӎƏT;uƸ)4Aūqq388`ڜ1I_@VJnT` Fxc_Zٕ-&'È,C_rMЂerwr:`yG)d[.Z찓"8xW?bM˧,r-jp,Yg_֟g<=/M:ܗn#[X6iwPC+9؇x>cLwǖCf#MRȞ쪭g#[ƯMj@eXg?g{ןt[:;;u bWͣ)hI Ƴo%{EF}`ЀBgR: 18ƔbS)C)wַXj//]w Vɗ|Sx=rtW?W9\}m+Q?&Psׯ޼wq|빅?mAW`4hXp \xe}dl{x[19 ;oS3&@M_r}+R}1 ze0&DBxo냇Ot}.@IDATQ>zgF^g'&W>цZZ-Sl3#1SG)_| //F/"D'ʡѱw~?0l 13c]_! ߾~7߽`?ׁ^FPguޖYC+a.t~oEŸ@ESJ&e>3'r~bC?&Kw9ql<k"S63;U&8x6Af]IKOCTnA?{&-#ݷld-;Qk:]d|S2dU]0,w"NRB6X&_ҵsgF}6?r+7^`ҋEݟ|~S<n\aOR| _ܒ?.+;#u7+Ϋv]mhH^Ռ~9>ni7ܿqP7?O7sKo_f9N\| 9WJe4xy̵㶪i[IW0m&im%hKɛNɥȀ| '}ƾS|E.ec0Mwn=]?xetH`p!n{џ=2~_\y s`(eiO/b~{f+Ƅ2+mOғp':(>ɰX wSw+'8x2|eVCa$c|6UG}҆~E?}N_VG[^Z/ׇƳ2Wt[;*೓zP>sm;.Xj>&V6?91 vRJMƥ;|̡a"]^..S~o&3@??`H}`0/1U6az5?70 K)S98_m@ϩbW^>y|`Hھ< h6)q06{~VvϰJGlJ"'_Rc:\9-Hݨ^LBAǹ'i;dn._LS#*KT斲q-dV‹V|2/Ny껗HCH>vIleM#]K}}1P$/KTC[LdH'g][޼T_:tk/.]bpd!'X;>fq†$-㫼ÙL v]'O[694~hD <~[m dA BS`̋עߕ}|w\v7w#wӜqr\mVm$n?\2|YJl̉g/!Kk鐽bL;d/kPwS<yK;<ݢkMW}4!Ң/!_oȜ-ټz6:/'R'X| d.dG2d4N&)_mcRJN;05 7RֽƆ7 lW=؆]ٍm C>(=l02f?!a6vWlk}u2+I69J7+r6>|=66&i;->W~n=\F9EIes,;zP@Y.I']uJ+>Rnӟoo6+$qoËז択O\~5tՃ3K,h1٢kL@/-ld(6v6dI:&!m#%]QMɘLcdk~e%:ltiޗ &7|ѧ]; &:Z͘Gy7k>R}JYlL~ze6aqel[d&"w%3;UF?GWT,'wp$m,[ G=ѭ~]Y̐]Ch _e `d;pZh>fcɨLN'vi~u8!9  sA> \hlZ.6T/{k[o%KlʶxJIJrhG~s0.v:Ll Щw)QgQ~wCҒ9J&RS,,^+Fes L "?eAtߎA<anL_&(=6O@/ %YϬ/yQ;y&?6%3woow/Z[eJ0Mk~reӖ.頄MSziz__d*ݺ HLxG%%x`|&v@l>|vژC)U/(!iyݚ3̍wC'ח'} _p3Ә6_w^qqy弤&0NQE~ "Ɣpd0ĦQظldA Wr]sfe7G2I %^Aout >%|7?W6)U66Wf`B ]ʖ ^c g{: i 2[v&?[k1Yel㳴^|?4FH`̏7~?L=J3_}`'}`uwOg2. qxm?|3dPkD9^:ӏΜo8j ߅ML0:jo,~o=~:Xωf2GDhi,#OɫW%Њvv6W}F{{dšqWA;<|l\Ru&I}k+WԮ\ 2 dHl[`.[\ u`gŏh)W<>S?9;uV.Ͻ vd>m.^/K6`$)Z-9u8<RDT&_7)Q ysv5mC6o4 ;$YW6c3wS]ņϣ>5KN9={upͫ|>ÌR9twwCw35?O]X呂?q3Dd2޳Mzj_lA]E 6CkstM}U%Ad!,xI]9'JhHƥ1x?ã>rtV؀ks1rӦnWl6/C t맛03y=/xI_٘z9MXyx=rxhbl 4__퓏:>| \cm2ڿ|n\֦KOw.^AEWcX 7_v^P^2NRm0x38.l͉߾+$:CC!z7woT?M?OO@qnS}ycnWr 8fu՟D7ɇ~A& =, lIv2X4?O\W${GbINgަ=soGs'ln ?ꏞ. ~/%M*uթɆ%kjxC_>u` ;(Z`#YQG]\pljz?WNw?ȇl/߽`\4e+I֧\{<<#oʘ.e2g|;ŝ?$񅋟33ؓ}=ZlgH@X KN|ɀl< lQ+/w ( ?N+ &{5).o,̉Jd̲7OprƦNvyackષK\տ }9rD7 #y.xwcZ/oÀ[[q(’=382~ P`t~&K Ab䎹 X}MB#tҷ[~^ɗEàxYE=ӡ^NO:M`]آ8%ig|'xRFɈ 2zְ<:)<2Uo8h Q_s?~m"Bq2<{4Ϝ]uaQi[/1_dy=џ䡥YN9t5oxuLPbV:.XѹUS~uDaDmݴ ', G_įuN n|90ڕLcdtV =]>|xw&` BfƣG FTBeG7mgYYj`]<fy{)W |WC;_6,T=e~4շp~6I}@/6p^uﶙf {eو`ٱzT x|p6Yhy)m ҳ(ۄyxs&e4\ɇhNcmX }&wLCq:{u!'M׸&%P^+Nv//>+ ]U'N]5Z`dR}1*Y$0"I& DM/Xq9rR2gY) ʯ3^P s}hw8] ~Si#FJZ*Fh e7I!cKg0.dž&nllLtU8'GS vU' „a5P k 2ɫ7or@ x |y'@f<{6 Zil^[aršӡ+u&8 '-_SᤅkAWse?UH?Wm~H׆UoU_-ǣ:TҔjk|M>9Uy9V>E/%_~k]Ҷ+↽aSlOZjKkQ߯2WYxhkHW&Ń]/)m[YZ R\y_6 ld^Ж { r̟ ʹ~B+lS+FB,ka>$8#Wכt39t2>64XtVԷ/+|9#~Mu+Th}S=d=,0@ʗA 60 +NkQ @4lD  6:he58a,+ y;^;Dn&E|jt*8nW2^T] ۺ֣E}ȳ&^5+}~g7O`an7!Vu@xthvg_+OUi|_+lXcuoyvd)z)N\q܈\aEN8ի8֦l,w]~7n*O娼-7纸^}Η}ʟQAtG7F*uJgs-#'n+pB"Si˅x'~Iap)}ϥt^|Ey':Rp:sup:Ο\%OلOJhvgW>`ʯW֑dkSVvcye4[~7|++X#:91D=:-ZAȏx"mKM.I=:腷: ų[& ,~V.ֆ҆TY[Bu+٥}6BntP'_~׾cyߢ>W9pK۸V]7IsyѠt|W;[67Xx`l畵G=Cf̓Ym+4V0ԋXPK^Kc.KU&t/Jq/|]5}GO^ye>SloGC+ ᥝW8/ l i(E]4 1^۸7/ U_);?m5_8u̓k}yim_a+ᱟv#?q׳rO[*cn/ ].'oO5Ǐ}W|r/z e'C[RdN+=etأӃA:q,ҙ#9!uY|u ᚖNhc&/ <ڕi8#Q{߅I9>Gz ZPn(m2'Ke\6@<~+M407}9~@CSlKKjgf32CG@@sm?x 偞j:Wjkl}ᚖ>8/y kz+9v\_ K:io,)?OƳM;ZYm-?iy>NXq#~ 6&V|vJ:a2} ߯qrev9=u(W3fGv:L}~avoBL򇘲vt֖I(R' g'ۄ.?p20^:4FmCv?:D}CtG]N -LSKW{WFB`6G."e9WqGAo;T^R n k!?GRm Mm9r2-WkAnodrߋxm e' ez&ԧ_0ƪ@}ҝ2: Z6tUzRt`Q: gR{{aZ?tVzCh,By⨓/Lտ)+p+1]V¶̭;:)Z]:gJ/Y߰>4uO _?nx p>_;|7Sy=||y[h;pYW9ú)yyv-ʯj1FvWQ ~~:ӳ9MGf~I9FJQ@'8pp}p=:T8N?KpK戥KX헃^+U>֚2;ʗ4IE`-Q6A~>U&J ^ELh6MX;o΍lo]S,bv?Դx&g dZm3Sk5mT>OiKu/lxТ7<6_!ݝN NHl&}=ݍzx MzPWL0?nL邠.ExEykyUj3$sn_Xao}6m[SA7M⸍BeZIBU ʫoXнE=rf!,#lG@W:iuvVOrp6>=پ?zpಽ<* V8 xct@UNO)ҒA\],Le]Sm|H_:j?SRepGů6a}\͚|y`Λ ].$O8yN|^$z@Uޜe/E/_[+qn"05=_gx>@3d1eP3ZCwe'fS0Ҽq2v1 3zW%hq :|V^,|6FeS0ɭEKOZ+-4/.=!VʥC6:yYR7ooH_YvQٛjnU%BӶ +"Ǝd"ga2X # Zmht5Y&XQ0m\M 62<O?_0rWNiݦ[|mxE,z" >]=é+4XˢNE8YixMFuG6anڧ Ԝ2ZYYoq^ZLJ|UnisįFS~=rVw᫋nYNMSܢ8K>.|nϢǽJ_Bm!˵X8.i:,657{)XX>Lh>߮d8GM~-/J_ddÒe7htpIW5iw`Th ep^ n`S DvWyۆ| WmSrgQtR=ѤeHu al(*mM[_-ko:^fI?`V[$?'[Y'?o61/Z,heh/^z>)j{W;t 6J—zn=^诲/jN*{LVʛY`Pk4<6jo7=7{\EW6f:bYVN[\k O,Usś=xYߜ#}u{nl~0t-N7ar"\zNŏ~gty 3ݍ&}e"W^ .^zũ1OE?;:gȠ[q/gpu>g}d)yeAycDZ8YuXJS=vy8BFЃ~\V30hT6YmSXy.ʁ1ـu:VnW4;AG;vGWJ^ŧrm{iCqVmS'^xmlD6vi[>4 d&3l-JKtՒE_۷O8Jo'&.OplV_iB3ƘI}:vV'y/M˃kTP'|`]ZH˫[66iִ4ִW[whQu_m|}6}NFRݥ>?6ǖ nշ4?HFrˣ!T>>!I߉oJ(Ǖiԅ^\yi4q|mi 2y睛oۿn˟2סK|65o,wg#A&`:^vΗtn>s&qMM@̄{i{ۍenxO_$<hfDƐr7/5{o~ynx#q  ,:Xut8T#w =:tuZz5ZGëدGbw]sV[*kyW_AꀆWJoM|钉>՗F/tlo~|VjsydQk.yXݵɰKmӺ"qrhUǕnWZL/^'+}RڷvB_諯`(?6Tg/HzVW|L-ߛ]]}a-?E nħ'zuMk['k8]Zݠ,)T+ ſ4v'--4++6Iux;[Ȯ6l-g\zG^ģ|7T/oT(⢏^®4*SBh3,WuK8"_i% xGGy R&o:U*7Ɲ7]Agoy> 8G^ 1q꽳:?11W5[Ÿ׷.K 'mlM9B/mYT.bRoԗv<~mM`J-h嫬^2Wy$*dͷsh%華]>_'t/l嗘~|ŧN;s?8tl˜0_Oө-wfaK_p}Br:vqkx Jt'u "R[Ydʓo@xGcFff! :틗.mGVWRul|jd rI"q$ouW/ܱ.iqxE2=(O.rrl~bmª_ջǣXXmd*plFn`mE@IDATՍ pOY~yE[o57^ے<{W~^~|㳻yZ?>PYC' ,1Aڠ~O_.gԲڶu_ih%oeԞG幦)~iHE|XsG}%%3R86E?Xg`ΪO]LJ>W6i8Y\#6V? ,:kzx?,0|_e Wl lCв[G/'9GGo&`}E^ r$=ٟt#^M@:qt*gҡ"GqT;iꄻ#fAvetqxNd 4-{9+I# ]J&o^]gD2)Gg9+;z>٢Y =]0;DNmYaxt@l(-0v::)Ekw{Ȳ?(\>&.](J˄fa1]yk4#GS87<0ՇNk?#7=~3MS=kgUG)`.+o}ϖ'.8;6sNs<|\m>\e:IV[q'oaZH~6xBg|2 O̓UhϏD'!:'`uä@NG;x78u t8Ǹ5O L<:y\qơ;rNl0)s"yf܏ 96$!&WT'ty !J{h7⥭Aَ]yLy{Yy:մ͝Ϫ{}P? [=Yq˯.-z6Y]MnMVi E>U9+KA曂Փԑ:C|M{1C? O}sQ$ 320P:r\,gHe[:,y[?u?qW7Ӂ7MPKu>g9К Bdh>S?mũ:.}xlu0g,=,6sB >=g.ꔷM6xwEpJ uM+_nafX2ErW)r)D`ќ{~U.]݉:m:hZ 蕦MџEro\GhS N,;IZ,E[$m6wLʷK#ͪEdGHZ]>6 |8=0ɺJ]Cg 4lL؂\ƜXe~w>C[zӾDvnJW~[X]`_[^iy+LK W;CnIҷw~p~`3cB\7hw T5ײY -_8=O_v_j𡏍p0~&7^"m'jpdn$f mNײ7_\x7m-NC IvOaOґԷ?HU֩um8y @#|B s8$-3: j1g@xx*Geh`O[=nv_C\Ţ} E~tOfvmWw]u+YI?m ^c_eXۘuO_lc`;δ+=I)Ao&L*T&G[*K:N ˓}YiMՉd+hcLjW/?1.7ຸ:goo?|P/ʅȘM+.&l|RxFhK;[_u^?W_L#䞿hӤ N7􍋼~#^X1fc;pЍ?lbj?b+P.-0x4{+Oj3v~3i\_ xq8!1Iv1>Wvy2,9Cm-6ڜkUC&f+^vr dpJ*[m(oǽ{mM ReL6*OsT6'_Ջ].S.],l}6 ~l >_}+ T~+_qSXb ."]<>=;G<&yYt,8߫P.Zbn]hg*㠌o闙&ٛI~u:6y1?̉Kf7nIt\5KH9@`⪟>?˫7;*-͓I-.M=ŕD=m+G=96&fà^ }g8…+vW~|P5?|ce|}tL)P;4͆5V78yKmD\7cxc9*U՞䫿ˣ yWvCO/lTױ[6[O^nN b֘3>0a29E':mFm:`n ^^]쾎Iƛ:W0$N9r[G_ɯ]8I&GƯ liN0[77'և⽿io]8_` ]:B|6b:mp;O㜛O?9ϓ^u4 qlNB7l.xuRAGD ^Ʃٴ:vށgptp|Ve't0Aw.{ ~W :`+}]񩟔1]!|<¯^hpL`,$f5 x&cNOA^H($Դ顎- Hʸ ,JmuR#_ee;zt =[ Av 󂅮:<ʁ<tlSf-"9V}.([B(nt,ƩoƹpBSuʷ0iA:r6V^QZ_`=21"@n.f_J ^|y䷱gpyt}xٲB3"^^.M_Z5a<٤O2~+[otLL'^pp8L`yYC>rr5BCk 0 px'Z88uV@#Lt=78xl|wH`ώ_@cR{6`l +=votyRjp\OoF} y_pzGm.l!ʫ u( ?W27_ڳ( ێ >P9vևsWv ֤8P'z&^2 K.xuC#ܫFVN`gdE|{KCOd'ڬ{c,S٤hgw}u3?H^\gB5%(m"Ǯ=V9>"_V|zcy[v, ~YW9ʻzM Ypr0hL' >kW ޕ|Fsiԡ13'IKFu@*[ \Q.oOge]' Oiufa%rF@C,S?dwI'.bSVuފO?_]h*?fV{(O^Y&s75 Khg7r2'@}Lc}Ѯ..YRxgn5v<&]zK FK=Ջx7?G^M0fG&7UWi,/Š[k:+ʟ◧tԭ0|_{;}9I6sJd1_>I=s9%|oy\6D!YQ]ї_iI_gt0jl#b8ȷ,4:I5܃iN,42dT'g`h/͵yBDI O~$70l֕# ~%:37rUP1Ƣx/pWhz2 RQ~6峳Ї-]ߑ+:7o̤F-h#9? ^eܞivR,6 }A Fp\Dt7;ˁDgZFD>LO 芫/"y}&TBLw 2_'_:`Lyg ʫ](uD8uYOQ{y/]W\6ѽ<̙} cA__yWIˣu)z+K?o\Uk.ӟ/Ky{?΀q_BgO}gx}硶f(W~m6qnZxS?NK=Iyױ:\LxOv~A~»Iwt'>v Ѩ&~0d>?Y؁m_=}D=.t3Yj^/ |] Y E⫟n?ScK)v<$;ern&&Se1QMEx=&ΛiHgS}aL J80^64vS}lEN\ Y&[~kZ| ۞_:i^3g#>(FuGOL:rKk+zpэmm@Ξl !&A_@~ Ly& n4 N x#}ѮSe.&Ƀ&2p3d!0&=J[:4w[bI~tta#VRtE\L۞Ng?lؔ,B`+Ȅ%a0ɣPٳVXA̟؈ڊF࣎`M?yC`1̀4B[5Pmk G;9Ы-.K8ut!/~WDNڦ6mVԹZNT>_jcbmZrqԣ=rZCb6's+8 (ٮ {uDqr3:S;~y6Wmkqbi:t⿦y?Nވÿ:Ouޏ#xϲ{ggyPN辜8YtSoPDq̴Ҁp6![`WJ9&'Nvڶyh+q/&ka&p/9"WD%M[PMḼwUr7>N'(/8I4| w_ Ϳ0mYN{)~& on&_'_>@-F޾[`/d6;Ƴ3#DV>M=çW8E%{&}ФySF@3g ˀ("z_qg#gn:5W/'"/oI>YM,N1 br("ī}0'N|#7zcGP'/ʋ8l"㭇/> ,Rݾ!-\S}h doy /ӏĴq胢oKt/~6dX Vd/ Cj۶p +6-%?Ћ>/_KS}jkOF6;ȳsRxkC1@6rym[*;c2ɭoq񥠏ៅ 8,@~~44վ G '$l*&݊WW ]<ҡ?NDn^AgE{98ʕ}R">Z;̖ݜkV0IpgpqzkuKȄjg9|o1 ;9qrnxY ]F'z9w~@x 8!8p'Ky2GШ.M.Ա3MH넘ÜوXC'|23 W"}kb;|Ϸlxߕ 7'

l.Vfm]ա=',4ǫ|e B rCut`O:Sl|w| *r]ΉЦ]Y&!X_#,?˓]/X|Zև(ANE? {Ì.$Ojvd_d]˽7#q'0t0 ę_:, ;}uV8;gNV&8Ee6dc=O_㠛m !G[g4 :p@A`vAi8K-"6qwH; nYlrUO\dT.GU0tD}[gTDFwBBx,_Gq0 ymH׷mdm{>WGbo79v4qMC{ mrQ߹ãWvך~tx:AM]jKiM6yqt>c&'NCcѫ z-Km/Ly6m=xrEE6uBP}Gխ-pৎ݄[U=v?g`;4smIk+]R~ֹA=T;}m6|v* cp[-l\dtimu`i_aj#XݤՍ̚]@)>ln"OL}dɂ3n"O/}`{Xȭyᯇ{!j/26\k_3N>ev;Kp{Ngq|*6BO8~&oe 'q?@K%i>q Df32D _  *ITk&ǀQ fP{;uIMr/UnDBvζ&&ocaQucˆs[.?rCND KLRz§5NW"hMsLQRc,#gŗ ô}H?)k=Sv/.ۄliEӯW_\j? O77Ȃd+[ L U'-.&Mnni4G&Q؎ :ѧ&ud)W>gBOG]!Gl!ZFd/LxM:}@5GMT%O2[F?ȃ[Cje)vmQB茚9"d_խA5M *o~؀r1LF&/}R=m mKL<+/<XP^_4O[8x2nlŸ];C*W󵧲x2<ؔ`l%V)|m69xMENB>U{v/:gpٻiX>я~t1 So:w6騟"Zg6ݞ>J1gD_%42^qMY㤳) l΀ߟhhsnmpVtC0}5X:yg׀w3045L0iz)D"g7.7.pzV5!2C#ڀ=6<o.1a.6GxDr Weo֜>xpШMՑ1[ 9j9U [=2:NxʍY"{kJ7qzm!pH}|]E[6npsc3^OA8ґ'u˿hX{_ug6cџLn _#;gLW~N\ZUɆ h,W76p~,mp01E_$a|*xbv⧷0פ<{o߇F$Iï3&=?{Ԯ#`a\~-NrldžWX2[{S,4q/9# %j&i`tpp:P8 к|kFZtAk"b0Kcg>a0KNfeWyйnj6,8g $nh'ַ M<=$\N`-z) LL&=&rH,HW6p7pڿhnώI&0h٤y`0 .n=0# VO!Щ<rlHɭa(7Ym:2 `5Tx ) fhXtx` 'e jpԟybC -}ö[6]4j<K#qkWtuƶ-j,[&^z#c kubѡ>͏S7tv]c?r;#[3g1|}\\{tNLCR?g/Mtxv,t$"r^>B-lʴt.yY"~7#tPp]]G*Z9N n~q&2O|#yMp' K't>)d2&8 \ok3/NMB`0& 9CCL#.vy`7XC ,:=*@)O@+)_ml*j{'ZHMx&lrYJ&߈ouXaɳ}vﰸ'LU~6膞ڤ~vZ ]dj~ZoRm3ˌGGоF:0+7IC7FZ G[-چ|A~>]z@z5r|QM[쑼>|i*h~@󹴟,Iq#is+߾(WwW 9yx["k^@ ]-B`D!wboЉR]1CޤNFe|: 5\,`2V_m=h۸; LE ]ܢjn6y6;$?zɆ@]y -ˣ 9ő ={PIA6 h򱽠\ S۴i_B^dʫ9"x)QFi֖@,\]0xvKyKHwZ+z |YMO37=I4GiJ1lA'OOl:RmC?< Ѧ{giC8>oy7o%#Py- rmN8 z'?Of`q% ^ͤ|𥔽jRonyoˉ/u2IzK^e@xL̇A ÄN : L.WてYĉq>S&a^- &nRC:l&U>b&5)k,SY(}Z6rty:G+|- &J}кbf` x x5X;Y8b?~dWO.xw_"Xv_7r2i+~iT5?=;gpE6+]c oe *) r!>6Nԡ| z+tsTy +ox_,ltLs6վg|"kbf3Af&.]}cC 3>K!zr}Q>D,⾈3_sTpHR:s tI.8nM:utEg?wzۅSἐm3 )Zs7m2-!_Nf3> E[q7%}=0o^ӽ&M>7此m MK@^`|k$0)fίf6+`CuMv<~k,߽ntˠf ܄PϠ4՛d֏`DX"Du娛.NB33PsUe2%a|.Eik2^.[&j @sȯSB"cpsc8qhv} P?9|Myv~'XmP. ҅CwNxqa;0:iWךּ;._Ai?t;x?@|0f==2g01<OqߝE:hoޕ9en+? :/A&xH{Bz0|M].alm>)߆O]A$>'&/w ~Y=~P֟X>¬L](Qf[f;yeS ɻB^Ksu/f]=A^r]p}%,у@3k&08˄`I n exӐ,_~ir].c3͊UD%{kuÑvTa h2mVF𩣜@FЗx+:)lNNo`8!>8jGʵ>G ExWn`Lczr[n <3 d[@IDATsՇl!^s~ m6!NuyQ\>\ `m%]۫#3.~a?#[cNu ( \ݷ`ן~Vz>tt>itj#:cٿgj:i׮4_<6t`?#J) .qɡ8 ^mA2{uSdRyK)!DHJ]ϟ%Q$uh!ӳhPv~Ӣ{֩DhMמjV"w5gjiClZԝ|g 2ƫa@)g\g"g98=cxoS~N[[//+g^tGa>g?KuK_~dG rg~zYl_zY槲~CT/|zg&^-Qy,O 4z)r7eA}0J.yZKVB"uiN02/Q"p|T~x<I0!%f B#\H%,#ަ : /B o~x t}G'Ol`jv pʃ-^;Չ$V%zf/tP;w2|hwQ?gY}NkF#o5wݭ`PVKU'a\եy7PXxgsqmYo.U w?Q (FF0g|v,vURGypm1G;0n@S}*lW;oãG/I4܌Ǹ=wW^I J4{?;4yy){?Ɵ0eҟ|C#Ohwa{NJv( &H =@uٺ_2ŏFD ';|%Vy~eY_v T?fdN7@a^v PdROq DHX17D) 2v[Vn\)n=jptr2 AUTaB8Ou( +PC[h-^ͧ|t-k{fM8˧ѹwﭛh:I~,HK`Zvv[u(S^fY1Cpw})ʺo鋏e=t7}^XqvJKxs|{F6O-On>'ӻ4~Ø$C=9wnS?Ki8җz~dёO-\p o=`[cy>|mh<]i4>qFAqf߂FR~VAHO?wh͏|Ni%Gf3+W |}~拤ж|a L/28e \IJ^kI;+p.0B|\.`_0'E'!=. [A<)T}B #XZ0Eo:-D|beʺ~#m^-t18] BNiOWŽSv 6s]# V\xV&>)uZ m2*|E >Nw^|+G+|é^(]47r/|BW@)u-x p_Y~F_E8e }Op,u_9]2m& Yo5wͰy݇FZ/ƾ  :?1ReŷrO34?k27=xe~N?I_|>wȏwf'ѩ;!}2H/EtYNov~FymK,.%C3kyv ]ɀrtEN f_k`=2 fr3ش0h3MW>j otx/= 1 gC'ݴ~AKc\pg|t3y-ډsVb/*|n(YKC~O z)oUy 'JxۊEj FzƗ}?OhoV]a}URq(oDp^?{~ 2J f: >bu`U~$EZˀ nºC~ l h^8Fa6WqAAxiГVfRPxh(4՗tK@:DCM[k4@גk]]x/t =Γ=_!g.e/`K@p҄`J&8tKPNηI9ycǗQ `(ćx)duF.~sfdBg c8^P'=>khuؼu:3]>[~Xx0_kw(g8/\s¦+mɋMF> NI=j A`3#/oہ%,uHH$qSUֳ~On8Ӌn*ea ?LA'ɻs9 2븖zF;QNofyBFKXYٷ->JfᩕЩt Щ% :aRgA@ԫx3ko)/g{V L5"P)V4#X ,/R.{|f2‰_+% 'ƫOVG=8[x PVÕ5mGc?oQpxj_yt nsGnM2g+o4\<:Lv[4 JpO͢ XiryZ'w)~]8zp'MxzE{s7[Kh!HuAW3~IoB,}yshțy4N}Xb{t |`\K._bm'-y^yvz +z[xN )eW\Lyǒ&ߏ钤lKq+py⪕+nݿ]~Ru5O'ԆVƙ>:|<~n髂ǰ 0w0GGvz׸IV~3wo,00. $7\2Ɉ|PafK7m 0(e~k7iN9\^)W|zס~i/(+l?xQܶXY-x/</F d|Z 8K'5_nɧxf,LMoY8,ȼdNIrb PA: t !»gvM:7RG (x)4g.&SJD) yߴ-@[!!eD@lޮz+X'a< )Y8` "m^)}8mVmiz8+^<qXBk;ƻDo[wۘ^9zwq 3ZY'Wvxw xNFms{ȩn.kzp`/Cu3>xGdȬKeWS^S?ŗXukE dڤgec!77^s~|dӻ8xRNJغ~OߊML7)2{~߷Q5M^ȫz/kzepc/-?/vy (o6j* Xm7[D=p!rN_@J^FxMp5Q sgxߝ>B4E(i@~p!Dp$X\xYLBjB?}ǭ6 {ʧfW#᥷Ho/M5Xy"kfW[3ߚQ:(, CG 7^i:1 nmi9 OVB^0+f:hXuxᄣw}ĕ{̄K ~5_ͣPj x{hk׺o\`t;y<]>#n+N=ԣ}}s3RRi5pgi[-iގj 'Bh{)t:ӻW+Xf n[L>ͳV۷no`#۹~Rap9d]"2bf3x3l4S7!BRӆQf0$+[h%$KE@Oxg*NsAs8x |ڭXP>fRJp3uv@xo,|+| xlOV2U-ڝxP?08B^ihۭ>Oa;a+xv{y/O]!P$0N+k|xv px@<}< HH<ΏWu6tQp?F~{QNN{?S~e{暈Rz*̃龐Ż_y_. ooq[2>K<2ڋվOO މg߈P!3/ٳ}p.rp!Wd0?`'{gRtۜ#P9L\{ww:KSz 8 Zؤp5S #aC9&w*W&o݃Q|wCdziӶ&T+8Ʋ[7ӯe'y7~зO8@S:}>`rq>zM~$?(6e5zs^hVDr(giz g ?y~/E3fky +\?;/y F `C8x;Qg#n.泸ssYb%p)ܥC#dG1rUݻpR9\}h Soh+E+ܔ 0%BpIQ #(k[Ċ2 NCo}%-$mrH| b y+} jlG|h_q_pm68n}_z!+!|X;MQ_Fղsm3j(Ex͓Kvf G'54/=z=*M߻8jk"1Djw;7\G]FSw7}q+MpHvnψo|+ۃ[g }Eq~ċ ̽s?8w#3oZ:䭶5V3uC\F( Q4ݢASH3BUCxY>Ok m;vQ{y')uUpI;ION^r1:;*oU|q!_}~0~W[VM= ~AoMP >ͤG(i3~hGX|U ?rNޘ1TWI`-*WxjyH_nc}ahB?sͼ_g ۗw?}?nLJ2*{dEhi߽U4w]yKnF dK_a~෿-^s\^ .U_T[끥s:䧖| ՃDև5|"ճpI8?S6ilM)xRc`\ʬ|{E_V+𶺇 (e='G*/ ]JYPQU)М]F0#`m y ~KFȺh6M\uY%m3]X~TT#}qYiSCJ p)?f%de]Bi~%©%\駟n|gRQg6pl;_è:5,<݆vuY17ߖg؇#}Ln4 ۩o``&_$o?+,DŽA@[^}Sc:c\x M޷SNs(0L֙!Yp~f}wzۻ2wep9/ElѬw `a C-%Ll?L9*JٯDhm /%Ҥ]ס%tmK0(u)(Cs2\:moRtK!3^JRW>KѢd/]Mċ֜v oeaou؆SO[Gx74pݳ GG gc_3m}{!({ߧ{1/xx .otTO31*+FA>< oKsiжv&Os$1r~$axy 8YxQw_(kw3yMtgxd)σ5ygxMI֑ MF| g ~Ҿ&W `1^:U a\ ! 끸}˃C4 Vf67xD3|;TSF>.'F]LT.yW"^Kx5w൰(0{ d8N L'T͂0RS O}>-VN2Ń0VR".c5E CQQȄ;x݅_~xhh^3P`hB q4 |p@8!Npq e|=0Rwz+xe8-[EW~P_<t~-_˔J̓08'8b}!,ߙKڗDWlZfaW.E^2kAX †Cgvf#kT2Nvodg .vMLmeSަLYimUe.oy\PkK\'NSD]Y0G iV eQ< :MO/xSozMqw[X0c8u'!'DoV57x| U1Ks`y8'v}<<| |11+݀/=&ٔA/b0~z_3vmy#g^ü l9Azn}wb?0zƞu't=j<ňH{qԴJ{9=/Ű,qG`d} EOd.X߇QO"e!AߋneF03|WaQP+I[a؉/"j>>k |+ gR1W"\;/> A~8(`=V*:}pY ᥔ_ep9j `O|;BCaf_M) KQ 'h'}SV┏( '9될|gfhH8's%euWgF)_pvs.BQfFJ hCy;ݾzÔ3\[[F H[wͬ}:d\am+pͦct^`?nsQGQZG2\`_0g;`3#LHW/Ji7WJDW EL3.J[fgB'({!FT8Wy¹\]FS-eW{ڌT[ф_Wn+~GXu^=]_zp{,rvuoBgR9X?Tnc0wN OwÿN1o31.KxIs 1⹣$M$9w —2'JL]LsgzQ~ Ԭvbpv."J{@dbT8ĭ৔*Ax Q_"H3 |2(#~QzfH~"W:Bw`|߫p(*X[ ]ʈRo:U|:O~F^{yx Nmlty+fJR'/nV8ŹcxxHO]zp%O_f~]HXe'gB'+Hcz`>?5bܼʿ醓eOx,ϺOoa5t4؏ h;a z"uN?@ "FA+xY ?!NQxCgp Il?RJ^I|B;Jb'<8mK piqkVt2I>fJPHgeWt?%T=<%#_!e&%UR\F%vJmN\]<'e*|AShZB]e=w_*Gm/+` `Pa<8>yWhk+\<}`s~t*/l8͊cW~holȪhlO ޯ@NKN:wBtO|}%GQtW\' VP>Ia=Aquhyxޯ6sff2f;ɻ/E>9IÃ2p+g>݉?uQ 71ʧn6vY95ݾ_L OKaN)Ju6 <%IjߴOKr |]jN)G Qdd,u,dL&B|* +6)b$PWCuRߘSP[6G(ryxjʻQmDsMhi c䷍xdKG{OW\G>e׊R}y V^>Nm<.vuۄ<<1F _J:0+ x|hr7pt=FS>} lŽo7m$ ͠9zRamx3IìGL43Ga]ݮ3IDAT`Ͼg_n OE# Q'Sy|J*{ \W[k !yt<Ѻ .̮b|WrHϏ!Y 6I~%m[3Y%-˪_ȵ~ 2v5Jj7ogfiBfQ|%Y08"KE@-FEeG צO}؇(5u(dx =Ց7ߊ YMV^Ό|ek@ +LVۚ&`;荼;ITOƖ+ Z꣫NCHB.gܔ|;&) Uf_ ì]:v(y,+2% ' 'M;|.eҭŻq [Aw4oMؾj[~ `3KN#%Qu9>+[ع(k.2p/;}|˽~mZAQFy%D@<72GsR߹x(73Y9(eXoJPVQjG^9ytw)G9pGZy$tÂxvS9E,Oo>4l5/hvmlt ozxO5p˥l3R|8A^V$lK!9Da>竭{ ?O)vwW~I4ʏ#uwԀZPu8TT~vYGڵKp?9hx-ފbyC`}s܊w6H!ۿD+*e$sh&uG䛩RT`98)7ʙRb\p)Pyy'>·Rn߰`Цl;q+^샀 𓒤GQ u߼_rw\}i0.HaIh J=RCW ,KS2=ä}Un^&Px 7a+n SH礴<ǓFS>tM`u^P~~*fVlˁS}Pg~ \_|7+^\Cr{`1~`e jO57Ʈ|Vz~CYdhM+QToĿ%bFZ(L̈́|_)ۢh(e G,_ֽǏ j}n[R2Q<) npڷxޒ7%u@}Xi$RK^q7 Mt[A 7癞vd>/W^m+O ꛨH~̿#47RuCBw9/?X {%_ uL'k“Xq#̓ˏ<"˟؉3߈U<@1$pT(V6O|p P@A.xNSjPxzhIѴ4qN Ro8F^;`:NհUmSBv9RWmZ/ۡ2i+xM2nkhZc,Qb@$Y_o3lw9 ੼?gFƲzC.K<_N;%Fa^i'!?6[֯u&y|sa;os/l))}!n5Lx:B+ʫ0+R?E ;4Ji'=:ԓ/M2ѧyՓ+Z֘UO#M]wY/VnG! װ$W1W|מei${'ȅl;:pfg xX 蟱V"]SHc1Wc9)=a=η:ApK^}x+ }F7^RU( Rv> lKYdն@)3 MJ?o Yr=ۭk [Z u~)YiIi2 R}`?O獡wӡ_[,xr7bK< VNTѪ@e_34fD=uL{톙mƳul ; se[:~w.=7yz`1^4Lgz yMo,?3'uݡ.֪]Ht0((SE)N[; s+LfgMPz5o?`7GRa@sn:@n,%;)xRt +9]ʖ.@:=x;MRK[(!XF:-nh14:Q2³65a+5q.ӷ;>[o%B\WF|puǏwTr%brp|B_>3{?!HL]z给߉oSN۹Bٿ+x=⿉\|:ie%V_"[Ig B*稔_[ a:VeXAnJ_Z|FXJ?z@蔫NjqVG^T6u~?æ xyi<¡<ӡHqy.}u>Ә0.J}0!qtPz]z`t釥,~ɝkQhynj}We{drws~ߏҿV)s+RMV)س;W`k`7QXu3୬͹J#s G*w`nP`^2^y`s5]!n,Γzp/ N_ѰAVfNd'}#~l?.Z sue;=I1Ǐ7?M[SQ?'V1|#7,eZWgB磜gwe@Y?d(sma){NB,ۅn)8ixx,<)e)@uqyiŌ^>hr{< ll|y +;n,k#s (t &fS^s\¥FsWYi^M{u|4,g!NVGߌr,񏲧Zހ~e^k{(' M7{vo޼Y?*DG@AM+c ]"Gɢ%sSfgUGZw 6:^Gea;6< .ƷP9LYbH U;j-9;j{e{`ce4>z#yya'zBK`{ [IU٨h MlV]'}Zey#ckئ%5N\xnʰג~Tm|`w"Q([?RV]SzNw탍>+'g0n[]z;l i=J]l87݇OAx:[9JVգG6,:-5<<{{8y#]GtfI7? װ/+KGl):F,~`,ϾNR/Q&y>l`y¬&뙮ة(|/ʇayfoD_Rz-JyQ.2?eMy}(.-ud[yj^09y,+#:-2uwO׽VV.8!xG6_'z;W>d$3we;/e3zݖn.=Cz`1~H-u_ d F~F@٤MրVnd&.&(r>ߊz7I^wc\) ,uU&8'˷ipol)*P/ +neoVh7f`OeŁo忻=ݷn4-܆|7lu(hP>iѫd$N4 +v:KKzxZ=p&T-;ӬW>rҏ[կ\(עάοLar smEa氙3A_Chi53 }v'q/1,m(LF. o(/8(85y¦'V>/u\w2wuXta'LVKuh~^}댉نZ¥g,츥?R#B~ٟ~5m8Ūt k? hPkUV@{$0(Ѡ8' [V & `%`FpLBZ Fӓϩ~ `Mg n^~ ԉgP3LU{?~?m>Lek*;]KX gROZiJp:s1gcs(K7uzWѡK><')X؈B:BЁA>@ X 0;fbN(“[SJf&Ʃ.lz`11K?R/H%0דh 弟Y(ϢT>soR_okLENYwyPlQf8 0 6ܹSwΊAvQINʸnLSY^qn;@9ɧOW%|!%?dw_S<4a1?#_MaL֡_{R1ο/6] <\K<],=Gr"; uqL')Ol &nI3\}ʛ_DiߌMcl&b 3JZoX{V(l ԗ1z 8U~ q58_٬뇁*?2*lBqt|T?'a}p}Ye#;FlwwߍM֊woKbHo-K4z+4kU& LgQQފ2t%KIV_d0%XtR77(qyWH6_yvhЯ[x )}ax^3> e+l✰pt\5\KF*| wV]s)F`WDz%z`1;K?4q;OQ*ɸߔ$-?BvfEFd%V8J"=E_AZ.%hu* ׿V8SpA+2(\3R ;5xoZѽLl4Ȃt(c3ui`b_Apf~R҇c68F óك6r#߭KXv pIbz`1~1ziϤGJt曖2yssQG(/_ gj )LÀɧ(=Fi&; 4{AR ս):**ggTfYKKyK  )x ?!O}A*c6=ҳk1t!Es^rq?v:y3'}=I>ﻙ.G}7oAe({Q/F9?x&S̻s& y5qvaD[!"o"N3R~+6,f@2,Ggf$񧾨h%۩s3<{m?&c`?t j%\z{`1^@Z?O(9xZ}߮wW_GAe?^ ?LS~5qP&_Z54S^3 V[,__Ƴ鄿- eD̳R @ a<#`S<nQ8Ȍ}7?uF(ݷ%%,%jN:տ:$~"⌅RE8GƒxCa:' W[ U0>;-wQgɗǯ-Zq>h?R%xZx{`nǛH|R(Wq O>ߝYwNe(_Gގ܋#@ wRv[BwṚ3 bn*u 1< +Q=_@ڷ!||Ǔ>M[>(GaJo_o_wݟDxɳNdT]zg)6:lc ,K޷guΣЃ~/ηSQc 8H?̅QȵwE[fjUa.~qTS7k 7uʁ=;{L駟~XLU9K=?j.ȗ 0~o-s{_W223O5Z?L~c%`uJ^)|a|45]-2L򞃇uQlGHoo%&g_ \' Î?ʒx9`Yzϫp t/p.QTolڕofU _(f[.OQ'¥Vj2(zWq*?u`Pq3wÏO }oR5eq3nJ-~xk-'Gzy;j-x=/CtKb=ЊfaNowZyS߼dZey :!FҦy^y1d)Aemn 4ao#<'໛)>_z0kcGR/ޒX.>l[KpMRIn dXpK+uԫԥxcBAEО&-&EYph2@@Upr$v~}1 cׯ_#gu{=}ۓX-u_orG v ڪQ{i}Z=tA+^<ht2*C*O{u.hK_#{NPt4qF(X*Ջt9zw}]O LhLv;# -`UTvIuoUP-UNhVż:Cc"r>.Qy1 @ѝX  @;d坺ԣsUuE_zo:epIsڃÄr :XM~[(p.[խ{4..?=V> ڣ4]v4<"n]Q#BHV@% 0y;h0iw6F~;:u|`K ue,S.+[izhs}k/+?HY TAٳ̷ܻwy~0gcWǺ*G/ Qީ"ݼy߽W_[lʾ~A .u)|.jS{>*WV'}*9~w4ܶr|:{2y{߿~®YubL"p?VKm  57z}p˗i"8>} 죗)z;w:_р)ݛO+;ZBKwl-//o((7ěy֭\-Zkwm,کiY?'!Pf #e`:ޓk*=rgcbqqqJA}Jf:寥wlhzj{?n>y䌂}_+N on<Ew)۵M^~ڀT|S Um( z- ; 8]3==$}uD }4_,RYT/aov 0yuAtuZ;Cr~&8cu~5dmד&kR~tZڲkd g}4X VV^9oš{;ҡ{ϻvZGi i+TD(J+*F"#JX)@P0Uց{c вN<0TT}TZJAIk j@X#aW˺imNL,@ ~?~mB`Z<{["~2y^*uWeHHb: "0U$MT :ey`~s{SPCE{QlVS}a P%PtWeWꊂ5uFRZ u7# l]iy>PמyB@U@ dGﵘҺԕ9|Xk-BJ#.HlLi]QAAn x0t?-  H u$=t_)}9T v!P Uͧ3PBި pn@HxO`-B@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|?#VIIENDB`icnV Cworkbench-1.1.1/icons/mac/spec_file.icns000066400000000000000000003275751255417355300202130ustar00rootroot00000000000000icns}TOC His32s8mkil32 l8mkit32et8mk@ic08pic09@is32 '#@MHi~:' PZ4Y|}Xvj·`!ԃ]աR(\pﺐ柜5ok2y)d@SReh/*WwvyY|Y31!zzJ (!;`No{4( p\fmx\9tvrx!iitfgpoq}SjFk_Z|gNpzwmhxFoV{zy}}P6]yV)\}U/ ,- q~N ( 9|Uw}2' "SKk ^uxXdϜ2nǂeIhai#)y0lt|s;DqFbä85y:ZZhrm~XR4ty=}kѠdU+]qh* "?nxP s8mkSš͛0i*\J6!1dER Sil32  ˠŬh 61 I/ng l l/MNptcѡ-h #KxgʪȴHi4?}v>oe]j!e4q^U=̠tL'f~Ha^Y͋zn$rh jy]۝QqvĴ$eeR]tyyaFe޵TVRta^}iΓȱqT"i4Bj+עᢙO~U:_-zбⵈfA[YnxƯH701qԮ~i90ZD~.Q2aDΰpJTX/V2ilcMUT`8y}p7RYD tȀjeUTRTtϨc4 Jg/r6qIҖQe9GŬ\(h 5y18 մsya ˣe8yo/Is~dXI7EWrS =͎ĊILW6|x ˠĭh 'I( IAld l rkWtLvmo:h '6epRdwGi (M\sc5mJy?!fTzGveƷ 'g ecjNtdc|ƅ#k6t{r{z]Qjt#fk`eratBMgg1[V_yZY~Yps9pHdy7)pkiOW5tވKxiGOudXMz8(Rݢׅōcs8dmD[>OL:/0qa}mZR`|RMBG_ss,fʔh  dD C>{w(&{g!h #KK~?w/|ڑ 'g>ꋝHK5]e`sƒ !h'xŌ)B[efpTfPv#e0i͕^o7IX3o{'ScZ9i dSXJXOxl9]&T8./S[LCB-u$s:S?aĹ^1Ik&M=I|ib60MU0HI  &=-g|HI]U=LE2P?R{>?4RLsǿʳ4  &A ;]4OM?M#.z>WJVxmƵ`dt| &A 1;Qy8 GM4 |5/S^rͬsWp͖Ϻ &C(+8*:Kmq7ROIE"de>Ucp{l|ᶹp ֒&C4+1GEgG]cSIJD2ðtj}}΃rtpoY(e5s9&I67$ȥf&GE:FS7ΗnK[IJGF!Dplyo{ʰmwTW|̲*&Gje76 RL[ռ]WL=>BDE[m)Lqjz*slBI˭]_I/4U=&Nem+~\@ڱCPqjMQXm4^Xy%ֿ̯/%2&R d>?JSþĽƶOGS^wyKZb{)แw追ů~5X>&VY#FM=2QŻƱؠAOUj}\WZk8칀hp˞w!)׺;a& 򄧹+evtۍŞx\S>e&dtƴ*'bqоԛӿGV\lHiZl{BSmetײwdSd@Af&=ʷPS[ Wv}ξoLYd~|LmbpE[ckrkpyƲbMwϿ/&fһZ .}y›OZ`pZPqmzԂJc\adgnuyz}Ǻʭ|dٛd&3ۏ^ mlwsX[_nVMtv[O[Z`belqqryȴڣw&& LmWyr{sro剁<׸iTjk~JKtτBRX[_abhllrǼSp&2gyT3Lgq]D˙wwlMrr(Nu֘`FGWZ]\\chku섒äp&g}:K&$:t_Wc}CWntCovf1`rܤ`|]CMQWX[agnubop&uZ]>ÀxkDUrTJlHfxUd_dB?DR__eiryʉhֻ"p&HɺI~[Gt~xHErCpᆴųVZwxZOiwXZ0.8F\h}notϤZjp'ػ0> ruqxR6wry~ϕߢj_g~ӓozL~A;\nK3uvߴpNp' t"tn/ h_~xW5k3 mVm于uoԭML;YQ;tʵ΀LzFp& WztJPׯ?kzU6gЩ"NjUlױ~῝wekPTwܲ}gPDPrkؚNX^o$Ff$W`nwyxøL~R7d\}s~FS]ONuնƲl`{ޢ|zo񟐩]Pe>g2p%|opt~S 0qS4bʮG xBG[KCIlǡýiGǬpEl͑Cw^HPp)*}~z$bsR5YtzK6HC@-sR=D-EA>86Kqʸ\«Ͷych~uyKpClĵ&pQFCiQ6J<7*&FkȲYʽ|Ͱ¿u)p 1aȿKaXDIMn/-44+"&7eڼƳ`رǿwsv˛Tp,GW{µzY) OZfjWbc#/-))-RE¼f˵Ͼȩul{nоzp)j7d}Ơ "fVg͟[U>37DC62LuI}mɱλkmܳAJ`b*p,Ea\s \S +k[OqMUw+49GMMH:17F[wuϿכʯtfv+6|U_ȭAo>\w΂;`,h\Y[uk_`lew -0517;;9410&6ՓdῼþZRw߬X:ќ5p@pzʵzPss3jjmbMNUxaOG[Z: 3MH>63:@A=73/1-<٢ޖvY˾uK`dEܧ3pDTnͶI8hZm}wc\P=A?HuueD31AĴsHiTz̯@qt5Vtdt]#Dkajվx@Leb[UKEBI\w8=CHMOMIEFGKKByъ`F68B8z;PY݂\ôOAzBAJzg. [;]^UNJHCDL[zi ?KTVRPO,SPyĭgEACABF=Ơ`P}i^Y(t\Css= !ו AW[QKCGB53XqDk9EPZ_[WZYaU?NLMMLGHJJEB}ӫrKvr:}Ի(~Yopa^觀Jmĝ`ի(WETUKKLL ?W^n dYY[_cadkdbRRSUSQMKOPLKBb۴SiZ/m\Cq^KᚈP2"wI n;TOMNSH#0I^dktwўiZgiquztjdXSTVXUOOTWVNP:w་\gh"KEvޕ]%VrW g5RLRRXbglmmxw˴ƎVoq|sg^\\[WSU\^^VS )OLRU[_iTnR]Tuwsiec_\Z^eeaXZ9t߿j̎څ/FϺ̟pMRJ 2IRVW\]n%q' ^Zvskgb_bjmi^][<ߵqV{M+(BǦɫxi:lPkVBSYAcs {P@7ڋb~sjc`eone[fLaz j"&4etȧԸX TddW"KQORYo`9vgxld`cjh[\TxFt#'{Nb 4!%~;/˵N&g2<\q\'h=\G9EJ>/ *V9|Ķp֢  n"  Ơ0KҴZfo|bB|oCGdijcd[=&N#%Gæ#=eix{` 60Jia*рxǦS>ftwIKiEG*$'&'T&%%'*&%!P-^nQlZNrs_jj~ߋ[Ǚ{]FqwdWhY<" ')'&%(%L  Lnahowům@ADqnϹVs}̑lLIF& L3ESatZ 57wFg|{ !'<`|jSF?>;)  SioxucN;7<=,   CUfg]WUR;   QK< ۗ  G*$'&'&)'!-^P9$RklIYxY- &%+\um=8beM,,SUKVwlUðɲH &=*n[q:8__WA?NZhE8Q?S{gS?OSyWw~; &A 7ef2LYgH@KH_bFRLVwͷabxs|EvXTP9є\X*&A 5;X^݆4S`MFLCICe>GP_q˷ƮrUYCBTmf}a¬Og3 &A'3?G=:PLY?[WOHGACoo^ap{Y}{=CdWUgbTwLq\=֒&C38-3FSMJFo֦>]aTIIF9T~³sj}|qN^EUmRpOkwj8AKeg|t\jd9&G;KH0<\O;/\rDdULEHB6a{l~wuuJ^jYYePOtYeg`fȦg&G /?Q:V3D[K/]ӺYNZHHED:OmÑOtq|̼WFlYX/Dl[dx̮~)&L VA::1]K8l3UҟPTMADHDHZmoybnk}ĚBLeV;>\xh`~n^b;&N g>12D`&Ckv;J867BGN]sX[\dmTWI>1Pf`mqUHs}]vP{M &M muA:Vc-qpf_phKT:?IQbvysVXczMaMM~~NfIнva\wc_&OccCPd7EnBi~·ǘ@D=ÿ^Rgùj:OPcp`D\a^J[uݩhijq_o@&T,ugxv^I4-Hż`˘6DLZcsiSYXlipyYeq~ȤmNsӷ?&V qttv`71qűϝHIR[uyDbMZ[^yOʣfBYm~}Ѵ^F˜=&XSHe}TX˿eS[DDPUdLpq@bYh|eW~ǽV6jq}SEŝzYPXTƽ>&YPdoyĵxkfoѿ`q{dk^[ڌ8MUXy^k]Kh\hmMppCRipXPv}ײzhYjjab$d&!c]blY~v}پ|fyTdiMn>XUfgɷDYjcpnfElbE\cecjpyǰ|M|eceze&MM_>kcZUn~߾~shl:hݢ{K\[zqyzmSPrmweψAO`^bdgnuyz}ȗd}ӑe&4[pDw{wb^S|wvs~xdF=QG_]lTOuwھTBRY]`belqqryȦxʻjmYvp& PhR_kZs^xx~{tHoi{lsg޴^:`doyNFuq;TU\_abhllrD_zZgoDǾRp&4P;vRo[Zxe}hobctWa[{b|`]GTosOcx4Mupޗ5CNUY]\]cgkvyaճPh˿#f&nQcDeO`wP_WaҋupRqbl^hYoKLKXApuwȌi:\syaA>:GMQWX[`gkwo[ֆJg_աWp&;dFak_{kDSxϗvGbyhecڅDrP\7lsyՍHKh}jy2MI=;>DR__flowsSNĿQV`-p&.^`}X}h4kuxHDuÅ]rEmvkZҝPg{[folB_qyZ߫Tiwa/Gm^.+/6F[ekpx{^M{_jݟcEo`Vp'ԬL}THIuq~xR6wakrQbVŗ`M_Gu}Ek]jypcҐpz`R8ERG5<[uI:MYfizv[֙q۴oSn`o"p'PBzoSDw_}xW4ld_slDDă_RQL[Sf亏up|]ۚ]̮|kg/HA>@WwD):NW\`e\Е`|STvV0p&ş=e~zqYb٬MrxV4hϼqgs_JذiRٙ_ƙHs)DC>OsxQn@++BIEJ_f]kOUaoU p%*SЍ3X`oyvwĵ?bxU4fχeezhBlBWdPJFԹefū^gת̚SwR5EB:Cr8dVC@BpE`~ZN_Aj1p'L}d`|ooui@]nU3cȼ{`gUzCEXJDNl¯q`bŒھzBFTKDHnUlqloCPsx5Z͑@y[EQp*.Ox~{WS]kV3ZQQij\/LA@9MpYyRth|?G[ISSuĐhQRe.KwfbjZ&p*bkõsSqTZmU2*%%98G\wcUɲgħncرiQOrfwl[i^/9YbVPDo@XzmAI4c̘PXUe>n_KNc`V`jmmbL987;|VSl@C-1Yɼ~ndeS\waCO_`X\jttmcKEKAlf90,1@LJC=52574-('+J΀H݅^VfmH|ij}527;;9410&6ӘpnNLRVM9zWwʻt\QwkJ53;@A=73/1-<ԬYpHv^XEGn|ȪuK`oH8Nyۨ2oGFQFi\:xZcǽJILhnADH3K`O=A@Ew~LۆWucB+55mȳsHhboY}˯@qpK?Cnquy۽KaY;qihqeiտ}6=ha\UKEBI\x36:CHMOMIEFGKKD@H֙CmgE8X[PYw]aôOqyU6Jz[YKn6]w\]9^^UNJHCDKZzmX(=KTVRPONQPUSJM`a=DF@DABCE~Ơ`P{HY*wW,tLetU|=QvҩaAW[QKCIE>?Xqo9EPZ_[WY[\bTKRGDFKIMLGHJKCFӫqMrqepXTXor>BMen_HJpkƬƅүiSRETUKKML?;8HU_n]tbYX[^eafgnaXZYSRTUSQMKOPNIAbܴTfqhvkSX[Eo_/ZWdm[ODlpd\ldWjUFkNzSD;TPLOQPKKR^ckuОjXfjpvuvreYSUVXUOOTXVOP:wཊ]eRbo{D[N2oN-^{iatjZDkxxb\nttnXygYsbWHf6QMRSW`egloww˶œXnr|{rg_\][WSU\^^US=ckbV]WYdDUo;'{í~k@hU{hHotXjl|r[XWAi{WQ)OLRU\aidu[]ɇNJWuwsiec_\Z^eeaXZ9so?Qp[bmr>p5%sлnFVoWi`as}~mR[ehQ2IQVX[_jWT^drKIұu[vskgb_bjmi^][=ݶuJ^yYmp{g"p.#jȤ{tѨxiY{blNZsUBSZYXdthwkvZztZϊUމd}sjc`eoneZfL~ˏ{MRUX]@hup'$n}ɦѸ`Ikzv{k\de߀Q}POORYon]eEOyVђlfxld`cjhZ[TwòO:mzETZSϠ7RXp$qG8ʷaI\jlkthxX\mh?gvFNOOLH6BI^`L[]LgZsm{lc]_`ZPSmqbdkQj2Ev:p%`W=8˱zN_uddZthW]nRєWYhm^a}q8DXYLDGLNP}~xeq~ymb\ZWQJey]Hvȼd8=cp';7չYcgPU[u\Xeg^ckGgOʫx_MGPXp|oQQ|y~tl`[XORRTYböS@;>p&/o̹xpXE1_tfXhpw`VuX۶kuơz^^krwvTRM{kwd_ZSLWZiSHWºnL<1 p&IbdeglpzzeQD>DB0 *V9}·q׬k|auLBGPT\QOďpz_nѷA-OT,Okwb@HckceUOA&L%?cǥzȿSLGMfjw{nA8C?Wsȇ\d|3M{R+CL:!H_W,G*$'&'V&%%(*'#)T\fm]Xrjg}[McƞJMo^A89:Ob\;""&)'&'&%(%L29Zkbhpwȴilvqʽ\l[spdXWS]NDB!% L3ES^uֈlsmt]Pwg|&.CZSA1#!%$ 4 3PliMecXogwȼu;LPeio~ 7'(')+7:.8[NbŶ{lZShLz  *!;::AHfmJǻY>Kmw}ν. +5RH=EMPijOW|x||ʴ? *6icB.YͽyvK\v}S #VvE,jʶ?qY %)hr[;ST #UsJ5 !aw[Ƿ~[   0[Rqó|somkh\> !(;aG}{iTF?>;)   5RrvvbO<7<=,   9[gf_RRS;   *5+ ۗ G*$'&'%& )'/EZ][7%(&&(%!0HOQI3#&*)($%&'(&'#-& A~}WDX2 /UgjUX`N% ***lgFaR9$Sjm{|avT$ &%*bo}}>]aM+.RVIYricg9 '/(`' KhwSOF#,NoIMDbNHLpu[r{$Zw-K;&!{ǐ4֒& 2VGSSG=30\]VJFP~Ĺtj{|O)*4g}zt:&EJxEAXJ-FcVKHE^oúw{xxQq&-Jmpȧj&G %Ilx^0&SYIGCGmdg֓ntzA",  Em}̫w&&L %>AgyG*VMBGJIQYoxknzդog|8&P +2S]%#K#-CFM^pZfjD%#(]~Jøb|Q &N 7Pa, =jM!7'?JRbujUb~uLO|q(¶vӒ_&O'Vd:!&OeiսȌ$@IWe|˝RZkړp~6| ǿ⧙t &Q*uԻe@1H̴n`RӾ~&JK[mYdP^tȰRAB6̾+8ĸL,SNdmQOrlx:gS^_ccgnuyz}͇FҎe&53`ٟ|q{2 !@b^m\UOwwݹ1:UX]`belqqryʙVȺ}JEDS__flmur( N`;2p&y}ۯtxHCxg"oՕ/D\:aol jdyB!(06F[dft{}C  Df%"Rp& vsrxP9o{%" sǖZ!3k\kxL  ;˒oz6!#1;[vH;B-R~s/ +ٴpS$`+p&}w`{xV5l@ " ^~c*HE幏up{F&˰R&$>CVu@$# .DG1zVCY56p& {yyqzݧmuY1kmB04~ObAU8װTƘ95LsX+i3$211.]? IOUceTp& "MUboynuðm[0kֆg AXfQH6ԹP!ש ,+"/~GPC.s'uYN[Am2p' 7)W|nozpV1e堎d=DFWJDOlR 7ܽ¿T H;J. V_Zy4WΏ?zZDQ p*/N{ϧmV4YXs"[].M??9Mqņ5[3H# ^ 3Pc#@wf`k[+p+fuIij֩kV)ĸiD/b¼qZE"2"y]s48DB>B=CuNVرڃ'hnlˬc  @3o7`__ADG͔{b\U? *UL#/ }S0׫; )sfwןW)-GoBV{a!Qˏqy`bS`ghndF|Wfd6/=JQJB:4+*.:FSfj RHqXjaݘa,*sbPoLKO$GʺҜl_ra`XZkuulf=e’w_bP%4?LJC=52574-('+J́6}h ,rUgӁW'"8.<]{~Z: 3MI>33;?A=73/2-<ӯv#hN:4F&>ɪuK`PA+Xۨ2oH?'bG |C=ƿNtd(DaN<<"P_PE>Qo༡vD<@A;1;BEEA=9484HĬ@konI('ŲXN{ʧH8[ի7oWL$u>({joӇPnzH:9*ywD"?d\VH@EXwοiC,3;@HJIDA>=A@Dzm'ubA*1<$ɳsHi&1`{˯@omn40slq{߰4X]΅yfl18ia\UKEBI]xÄ06DGMOMIEFGLJE,biD9>>@IPYI>s´OHv{9H{dwmM͗]9^^UNKHDCJYz?:LSVRPONQOWJ#8F@D@CAGtƠ`Pyt UXqxc#tv{HAW[QKKILLTXXs;DP[^[WY\Ze?3HFIHMLGHJLCGӬoQe ǾXqt,7#$IETUKKMO^Q_p`ZV[_eaferQ>NUSQTUSQMKOONI@bܴ}X[- l߼JqbG6-PzٽHbmaeMKF &M+tͥqǿpfnuzxfcs溨љy'nS#3=" GOP$G*$'&'V&%%(*'%:`}ίogvy̙fb|Ц{Ġ+ [_?$Ka]:"#&)'&'&%(%L"3fvhegpw˼‡v}iY(>VPDA#% L3ER]uٶe}(LT@1"!%% 4 ,Jjo~ 7'(')3AHRdpĵxz  *5-6;bŲDz}zyʴ? ),hdH-EʽX%Ew|S (UBeƷqZY &%r`eT # On\5 !$dtZǶ~[   1[Rpų{somkh\> !(;a.t{iSF?>;)  "+JpuwbP<7==,   5gkd`POS:    */% ۗ t8mk@   U+k`% %o^l6 F  Fn` \q La0 >T& XJ5v) 5"1A Ts @ u 4<* aD^BN3# W{ (io9 y /Fg q r | v>D. U &L62  Q )]zF8!SpI7|@Et|sG  6r¿S Q  J|?  k[  dy =Ch"jYu!l!A-7]/5Gic08pPNG  IHDR\rf$iCCPICC Profile8UoT>oR? XGůUS[IJ*$:7鶪O{7@Hkk?<kktq݋m6nƶد-mR;`zv x#=\% oYRڱ#&?>ҹЪn_;j;$}*}+(}'}/LtY"$].9⦅%{_a݊]hk5'SN{<_ t jM{-4%TńtY۟R6#v\喊x:'HO3^&0::m,L%3:qVE t]~Iv6Wٯ) |ʸ2]G4(6w‹$"AEv m[D;Vh[}چN|3HS:KtxU'D;77;_"e?Yqxl+@IDATxWu&~ss,MPI#!$$]wm؋5xm^d, ! %$i&L]9530dltOuW{Ϲ'=))ꔀ:o[޵4rH Tŗ.% RU,ixK H @K@*^|yR= %P_޺4rH Tŗ.% RU,ixK H @K@*^|yR= %P_޺4rH Tŗ.% RU,ixK H @K@*^|yR= %P_޺4rH Tŗ.% RU,ixK H @K@*^|yR= %P_޺4rH Tŗ.% RU,ixK H @K@*^|yR= %P_޺4rH Tŗ.% RU,ixK H @K@*^|yR= %P_޺4rH Tŗ.% RU,ixK H @K@*^|yR= %P_޺4rH Tŗ.% RU,ixK H @K@*^|yR= %P_޺4rH Tŗ.% RU,ixK H @K@*^|yR= %P_޺4rH Tŗ.% RU,ixK H @K@*^|yR= %PUܺ*7_o~J@+ws E۫Oo .8PTUYrz^fZhEiԲZ %k߼XbkEQşW^%~UKē \o&Q|ǐ41>EE/ZJfղua (T~ϥ^.(SZPLqMo~Ƨ7\)i.Sdܵkaِ+s`+]˨\8+'򥿤T|NVvŠTBtqS곖_PKRX,d`0vzL6 GщBP2L0t¸KeP* Oh* qY-N{', ~?oG.h[#eu|rr333t-6ei|ҩ-NeN9@`hм}:E5gF`VsרjFFFB/zghhzz{4Z#z%fd2b RYi^b ]lb:I@pj Z 'D)罍kls=Xe{<4q^1t5_۹OZ uNze_#WXK(Z7y_̡ǟ?=%)`03(9޷*_?/׼!~O"'?xk!Y߅EM t[<.@<)(xO_Yc->m!]afP[6 AE)U* {JM}U&VKgK);N&\j!h ٠+V~Wګ G|詳,EcPw*wP0]1G׹6:/j/Oh4:Ot/v.h.SE]2 yS Z=u =nhľF.)WՑ}P՚3ʆZސD"z*ulḮi"\ÊG-KϦRbZCfMo~pzr'ۻo_AXJQu"Ԡb CP.\9 &(L HpK˿_k~x"|Ɔ߰5ż!Qԕ,IC;2+z`7qz:ڕ({PMaҶ隬j],ֲ^^4ƢupZu]h8q[?z%j(SlR,f3rTΩzWϓ3߉S'œLZ+B*\qWl;xdu-俿E]@h 7 57UOVNPE)j-m\'|2dJ"Tr"jV@g)5H4 zX,z شƀ3Y/"=% WkG`R$UN#!r:vZM8|:_q? k`t٦yqukTgC#º>,ba1X{#^ܾ:O 5֥Jǟ%Ow7q)x#x-i~*NbP7Ԙ"6Fmi.B2(G )/@q,23ȕ-pcPt4ՂN_vݨSOD8Tl\XKOuUܵ#vlZ¿>b,Z!4֠ۇ&l5JӹgAq9,++tYi.LBݰq5ۮЃ>G\:g1  BQ64>ً/2tռqoMfڍ^M[󊉖G0"( qEPyF/!7OoaJ܂}>)NCՋlt*s#pHk\Z@ZvZuߤHﻚOh+W }7-CId|%=oI9$#Z#ih>~iV`Q-xЩB0ıy ڦ$vbO!In',7ERCk|TP?q3 b YIaUOHzK5W*7kj1`^GnY@󮻾wEY xJ@ EjS$^AS HSQ?p Ln]Sm6Wg*-A_4L i.#aУU`7m>t#VA&^B]s3=,XCG,!!Єs5.l^xދHGsߨmpAjoVMz:=+8=Esa:kWTzmۛ=ZL&}OOw{~GsSAl9+"•PK]ߜKﳖ/gdw]f8s|=E͐,{07~Rm㖺r!0> C:ȓLZVR1xvl$e*O2ܚ%xuNxmYfᵰU} j6gۦQK .bAnԄh 3Ǒ..c-js,!&? nyb,:l4 SvҶs5b9p̸Jk47}=yIk?v/*0y P^e@ﳦ4|ht?SsFuΛ]?jQ*m"e؄:|2WSd`LgSbCn8Y_nHãF1_VC"W@FeN.\P  3n ވSa 4%#I KK!AO`!AR6oϣeSwՠ%{5c/Ar2IP%\BomصYyiR|?Pe5Jc'#SrCW۷nhE6 Ǜ,iTW6Md.uݳBd(%3Ih֣k4+p0[FkO-8}TyVK9=Yxj44Zl? O i52l<j d(Kyfg]d'V!Jo'x L`9C-ks6`Mpy ɂCp߯}^w(?#;Ckֽln'N@7^HxS態2O0nb(: x$U;"AB.E6méګW )4"sHt'@yt܏dĮf @(⪡$O0؈"rg3 $2Bhjn5zw-ce AyTW[J"KK})ur[wXr2;3ҴMg*yLBzN;GKYAnZ7B`ع烩׮kf⮅To=fzOCHt4j߽8c ph!/Zѣ+܎3%Y=pBs4d΁4Wd:42igb!Fd!1c:-Ύeڻ;X(ݚq}gjz~29X/|7 xi׭sGbMR_JKQmM}u·v7ce.33<.]nMGZ֣=BPui٬*GKq*_HiPxvfg Nl<%n6z/޳4c<s!4&RlhmGc_Edl30-0| Kz.k:͡7Z`b5u] ! |mp,GDaiTM:ZzJ5c/3#24o~97Xrӊ٬2_g37A QcN.aGqmȔo<Յh+Y X[arv`W_-X߄ŪKJoqKPl/}j[ώx6PcJ4T3"QtVYQ:܂,)z~:]do> .]c5BTH}Bwڌۋȧb$*/MMbqq 50?m(B;}wKA4ۗsXFW>Dw> ckn]>2D+ta"L..N0̮CGch@1ɯ|/PR{ݼ}z[\@} P<*Wsܯz{&+H`)ժK?m-n }㶂+?bLx 6g  Di;t*H n܎U}UiF*hQYhW fBX.?h8r< $0K_Z oSc^5)&6bO=N'J8kd/l!Bp` _XiuE dF%8 Ňb"$JH &\y-C E=}#Ε Tyce#cI4 G^k8H<^V'?-IVWt6|m}jljÇTCVObo*Fzto:46D#A,,cϞa". ZV'h}JJ--1ZfM/WlƻlbBIEᲧ#̲?kj)= |/PD8`/B~?{d߲9nC]| a/ٽ}F1Lb"8DYGpLXK/kkbc'hS7?bn'vkEVkh䂉\7lABA|i7괪VQ7 Meg6ޫwzܻYY53濡a m^jP9Tq3sOT [ 6 ?0:0JV?ԇ;xaۋ]$Aza^~2=q]o6~DwEh) BO"ü@i$Jiv$ (kJ!\9=?x?"wqڀP% b$=xH:*<x\𒝨p׷`ϡcO :Ma-[4Z퓟5 _"2GOB,J#n_\Q 6PR ;7C'oLl~97֓˯{_ߊ:nO͡2ʭ-hjjF'Tۑ # 9MXX^`*VmB}k<  g<ݴ 9=eg#<9G h.74nF0ueg΁)sPUf61gf^2+4z+Ƀ9@$T xĶ܈Ʒ!tZ)GaQ[+/._|/t*U Ԁ\0oN/ôaM =3);}L]+uM?rY *r# en/!׫|xq{cdepCQ~2Z3Ng2PocrބgDǸ9./c]wF|+&eB̸4XQk͍-h> 7;?Onw#8I\d@29Wg1muFt,e|IDY˷r4;p\`\u2i(mדVܝXD0aJ$8ė8_xI0F'&0=ډ#`+ro(ݏUcwg#1(ܠ}r)4RYtY L>~ HKeRSGǓ-k[ޣ>z3 &힯cT,i#rNX$J^ğ_> E?4icEF8&T}Y3{>,ԣ#2,˵gF`*H DQ'J-맲!Gġ'6 |Ɇi[=O} /~C|o'V9,z ZG͓F_H, 2@ <A%^(0bg!"!!a?r`񯢿m+60ϱ SRnjz-7#Lv ;my?JpzWV?&dMSs$X.ѡ2LTp@Aqqt|#r [+rl(l؉OlCvԫ` Z lfe,A 둀4GZ?ZT-,O5;7^w|J)rJ&{9JO4\3,X3 $FqE{fq}< A 7 Ksdxbyu"D`>X" G3F_n$t COL= v-(\ewjŽc6TF`q`~zcboM?Bmd/" 6H c8"X~r ҃ W(xǨhebNtD3p$NN>Q[|EצkPӷo 3& BF?G_^ƞՂ݃_ݙ+CqԬ#Lcsq- haL4А#R*r"yzn[ 4 xmLH,(ʰdÍ~σAhC;RXrJ`ҊHkQdai*3qS{o˧$8[-M.L?UL;ލ@gR H QpnAL~PcI*V8БBe0M'p \ :_4<EjN P)e|2iyamP}^՜9b^ZyMS@\xH/UB<04&h0[Uwźe6'Vpt_$c$$YvʥI?ڞuC +sTP.v-v.:I G-l) Z\F"ӽ0Z&q6Ha fT"JJڣO?,nW V+Y9Xf;Q(aCC\k oc"vGP{u#v'?B0lWu¼D={b49hdan?Z*izI&&43ϡy ?q+Ć8>"v J@X^uKW'F \>tK>o&ZWVŤ 1Н `o"v*qwOGe_c4|3831~XlBȮ!ǥ"OIYt&?aAA`Ίv&튈בhD_ށl** +c}[ϥǜUz!Ѯ(vn p͋&2jjeL +Y?O/bIEc-Z1MW]]I~ON11τ]a_YG7 ӰғPZ[j;naҒ.D" d.@\~EDUNߺ_,2\DY<ݹi)* sTk.\ & 누knE8,K>*^5m"sn@C %faDyR%~S͏pN_ЂeD:6"ÿm`!R`r(]ksZM(:QJ攂9SdMܚ,Ie[ݒ_@'|0a<=:_w&hc{%H;dl HNNn1ؓAI%^/aA>)e$I Z;3.$/N떦$d?>-g6o'[i3alq# TFې\@+ l+O4x:ԓЗ@Ó:ZC@6iDD,x?{0As{}H=ۚxt~vxk,7qEŘJ2unX_ ?:"pzӏ亻jآ^ ,S,1YƬ>q,/-,TвiMp_OLH2Xr4afO-lDIcyd{,= 䭘\\WR`_^޲HvaA߈.)!м P׿ \L Hsוu4MLy}7dַݎuX;(!&iɚdEGW"{E(Y~e-RJMMT.RBaR'W#}jo14HaQֳ/b`+j)d.NGŧ1NPOS/*fnRxv6=?#g IAIF rİ{k ޏcc{id؂Y*l g:r52}OrI`,?dC<+'f )'CmN>S&ʣ(nCm54E?@?E>`C;PY"mDyr\4Ш0.WɧׯCcbDV@,BAơKz^mW級b![\{m*USI/qi~p~Wrhh_m&>![uѢd:ԐU<xS@`sָȊRU+&ش`eY]G|!aЌo>pY^-~ʊ=awD5f&q1_)81[fĆP$~j*-?z˖d90d G)_ۘu[WAFL. j)J[&UF ?Gk5pD@u(u4׾5=h#:{6ȹNBF5462'I{(0b2НȜ*:d)m>;-W|X3 mo'F _?Y~d5ڹ6;8Yfr/+2D"̒&yq2BK/+w!NuI=68X3_Oȯr[Khily|e&1 q ;˰7s(=*˸9 ^79Tglf Nϓ)#+E#SlbJd T(>s}y,UIN"C.+n`@#-FgXN0|iJ2,m}.ō6/NzhY(3,!U i^{k4߿]]-<:JYg;Uo#F+ꗙOsZF;66#muz+tsEkF 6aK -#عK_f&!֐V\!-9xғTY,DYOF@$a.}酧p¥^"ܹs9 q0$g.CXLNZfr,A2q-'EY\;rC/.,k=1qg`rP-@&u}n4x5>8 2rmFx] %Cw~wYA5 ^C@~-J&A󹜒>ؾE0n:8/n1p gL\÷^Th,47!7Jl`a#q],~ ~ GNcܘFvz~0]) }5T87Y{u6Hȱ)nx=H5O4\ˤ$Ic֯[X蹎.zILOOرclݍŦ"3^V9@T%ۗEOd/xBŗ0FxZg'p z60u J1xLЈCފ9(I le' l&]"JdKK ۥg+m R +5w UF2Ábs׿}- e JE^=Qڦyνodly kK 7ab ,xIDATLyOfME#<Ś&ںls1ϱFVU9y3s8Q|nd,YB/v~A82G4zp" ژʾvs@V5ǩ - eH{.`V\O(5)eRDe'',-[ t=wH1=#6EJ c =?&k;k+!3ym ^\z+H]7g?G0ĸcb|`'`]&xS0>1A\ 74Oen~!|i7wcPw?ː 'u:f _D"M|Eqa芋x1.8FK60#xaK: = =!͖ɕX`3OI(.FǨ^bl]ỵ`:SHx?Ng?3IA%Zq3IWtpn zi-GmC?'Ɓ'6gf\.304X z^lsd&(bMTCQH892]0D+^k89,F Qr$5Cf!5Js}qْ,b:_2ūģNJғO=sgadw*93rThXɒ:Vh`F.!Tl׵IA,ep,V$jG=ڕ<4Ls*[u.s0a쇓6sal5p֘L٨ ;g j; {m%b@*16`O&(% i5^\7Q.S7p3=Ċl K4鿏=V/BV*0' 󵊨 P_JJ y/Ax0n !t10aH%*lǛ0Z ؂7CuB'bJJy n߳Fwȁ|(xHx)|/|xSԩȶeTl\c2z@gR Q.D6 L Co]\ K]h5?r>p݁wv (3gK#`cy-nl|g 5-? 5`⽈ENdL VX agyOĕx4:ߵTL ٫6lan^0\vC3>8N<;p}4r$lnjOIxTK&BB//[  huF F}n?03%,j*hӸ[!@kŽ\ixIdST1>+-mU?' k/@T\vɹR&W&KK怶Waלi+wBnlUڈu'6L{fMf;= ҀF̴m@ bThr4Q-gmXk`0mG dL`0<' NS erدPqgm)(tM z$b-Bx0t~p̈́dן? [v#G?0<:f eRIE~)'Z2@T.|煬Qk֛ =~O=Y&)7,cJh dr`&τ?>H?g!N*Dr,ZW_AK>.Gܸ%f"GutSHF"oF:J7}FFbI)I)IY` IFHnǖm)f4kqx33Fo{_kE /bF@Pᕹcw>v `lG@c2c t1X' =1BЮj 'lk)-_KaF* C 饁H L4,ں~H,$ td>X~:;$ t%4֢h ϑr--_x}q Ꭓ3O$qmMPad\Լlno9|ͅ`8.XqLcI߸b|,G@-(QF[ўAut|$6гr[*V†t]!8I%Aor AH77bQlA7݀Ⱥ$Jv'_@S*/g!y\t>BNDbxϪ>%"[V-.璟W 5s " #!+<P-"[>\S W7VGSV%otf҅ib+] _A@k9i2Gc*,*}?mGoۭI6pu*clg[Qq*;*ugV5+nE.vb;F)Puy4A 0)QTʩ_Gw'AS| uj aOa"ZQ j2RsrH3>&i\~ipC'N@L> #:w':8u#%3܀]w=\x @ rn?@JcTY fgMLA`/B9IvbEt 6EJbJ">:s7d?|"No˜YoD<;:Nq7]n4Ljl6G|8-#_D1z+dRheHEYl, P{Ԇ=]% maN)v080Z $=y)NViutE_QI Mg.CC4kaM"Pmk(% >K@ d_!:=8,@G^uh۶m@1fDy>"̙gn+T6Yabd68t\rWfPl[mIX09,)~Qp-"--Vm^-ΧozA~U}U SYKX˰ [$ԐXTt p(M=lṾzQA6PA?Ґ 1L|>@=ل G w J rΎ^E.^uʗ_mBaR!?ܯn{Qݗ d?1j|$XP9Ci^+޼7S \(Zim|c>#K̈́p+& e:l琋}B@k]H[A^>w._4򐚓6%lwZӾWTЍRb՘hS8< h F17\vxR! Ĩ~0L[#^MQ(; DY{AŤ:OhUmꑒ&m8с/^vwNz:\e1x+]{{{.e9@K H^y]G8_Gߨ\+w $LJrO |t ^U*J ŢLb@ t8u_`LpeO`!$! p:ů,x |z'r wlbKo665fnƦ]W?KE<5(4('حf&Ix,FtJݽ!0vF1 S>rX9IzZPf=Y̾;xNeYTHзBe;ZJv"Y*#9e;*^ͽ|ŷme0تiuӰ "sM5>12rݕTVSgZG8ڑyX˾N9ڄqv8;N~An˫)@; ܕ8xÛ,Goa؎eNݰbqp/}uyT[zhutaai q4tFLG=w+6[ ɋ # f;\n…5XYQi])T QkVVEZnL<'|Tw ,6+UVjAe+'ƃ=DkEXmU5#{eZC=)kS5ټ 9ZQ:5g uOhl8hLyXfT5&'f3c ]ry1:W M@GMa9f(G` O;DEMm҉TeVEW5Cj8QP(C;)ݞ]\W!P*NŒVȕޱI73 h&8؇JL"_-0h!A\ca;v\-*!e"  l`J.7Dbg&ǟȧjd-c|Njm Ӝ:㎏͛B`F`Êm G3` Z}S|y|b?oCePragLmR4Ir_77ݹ͜ 7146J`' td =8܆K. [~A#J&`MTrlpTfv?J8]%2vT\S-"%CY}= gՀ4R8Qs9 2WD;HBC@6"zrZg59?}5Whɲ<#ٽǞHYb^1wfž -DuTrNnbF=C 9'~3䠊ɀ4fGI3]Xmg;{= Bmpg#mW]{;*j"|2d%* F1- /Bgo&:3nslZZZ:QAAͷ;ҿ1gۋܤVa2u&{;|.^Ӌ~ڼdi&Ꚕ2j֨J۟W| )(pIrLzdy[!mN`Vq  +jI8 i^aǦ҇cdʡ}i窍*_:CAg7 (`PQ8N H&Cn\م9OvG~" "_N+>5[p:޳ fj7v_>\I' AO`'ҩPp <i8Q}-|=U>uc $ܳQm9~smM+Cyh={x0qÇfMlĵkJ\;Q8د˒=@ƓWBsLggV`Y`>OWrifVgm'0Z99Mh-s-WD4 3isp_rx;O^UxWSAs}apȻ;<5ijJ.{^KV«k*".UjfV ʰ> sZK2@IcMt^q Ϋ= #Dž c)Wq6fYsH_x 𑻸S'}.9.DpBD8=fM9 ! @i  tSC@*&h =TM0" 2MA@zH `Dd"P6A 0@K7=D"mA`n zCE  - A@AZ)! @i  tSC@*&h =TM0" 2MA@zH `Dd"P6A 0@K7=D"mA`n zCE  - A@AZ)! @i  tSC@*&h =TM0" 2MA@zH `Dd"P6A 0@K7=D"mA`n zCE  - A@AZ)! @i  tSC@*&h =TM0" 2MA@zH `Dd"P6A 0@K7=D"mA`n zCE  - A@AZ)! @i  tSC@*&h =TM0" 2MA@zH `Dd"P6A 0@K7=D"mA`n zCE _h8IENDB`ic09@PNG  IHDRx$iCCPICC Profile8UoT>oR? XGůUS[IJ*$:7鶪O{7@Hkk?<kktq݋m6nƶد-mR;`zv x#=\% oYRڱ#&?>ҹЪn_;j;$}*}+(}'}/LtY"$].9⦅%{_a݊]hk5'SN{<_ t jM{-4%TńtY۟R6#v\喊x:'HO3^&0::m,L%3:qVE t]~Iv6Wٯ) |ʸ2]G4(6w‹$"AEv m[D;Vh[}چN|3HS:KtxU'D;77;_"e?Yqxl+@IDATx\yo\9w&0gQH[lix[=4h %Rf&A htU]9ٷHxZLJdZѩ=90Mz؋^hE@D@D@,Z" " "`C6t YD@D@$D@D@D$l8Hh  Hp5dk" " " 5 " " 6$ `IאE@D@D@@k@D@D@lH@!րؐ ']C ! N," " Z" " "`C6t YD@D@$D@D@D$l8Hh  Hp5dk" " " 5 " " 6$ `IאE@D@D@@k@D@D@lH@!րؐ ']C ! N," " Z" " "`C6t YD@D@$D@D@D$l8Hh  Hp5dk" " " 5 " " 6$ `IאE@D@D@@k@D@D@lH@!րؐ ']C ! N," " Z" " "`C6t YD@D@$D@D@D$l8Hh  Hp5dk" " " 5 " " 6$ `IאE@D@D@@k@D@D@lH@!րؐ ']C ! N," " Z" " "`C6t YD@D@$D@D@D$l8Hh  Hp5dk" " " 5 " " 6$ `IאE@D@D@@k@D@D@lH@!րؐ ']C ! N," " Z" " "`C6t YD@D@$D@D@D$l8Hh  Hp5dk" " " 5 " " 6$ `IאE@D@D@@k@D@D@lH@!րؐ ']C ! N," " Z" " "`C6t YD@D@$D@D@D$l8Hh  Hp5dk" " " 5 " " 6$ `IאE@D@D@@k@D@D@lH@!րؐ ']C ! N," " Z" " "`C6t YD@D@$D@D@D$l8Hh  Hp5dk" " " 5 " " 6$ `IאE@D@D@@k@D@D@lH@!րؐ ']C ! N," " Z" " "`C6t YD@D@$D@D@D$l8Hh  Hp5dk" " " 5 " " 6$ `IאE@D@D@@k@D@D@lH@!րؐ ']C ! N," " Z" " "`C6t YD@D@$D@D@D$l8Hh  Hp5dk" " " 5 " " 6$ `IאE@D@D@@k@D@D@lH@!րؐ ']C ! N," " Z" " "`C6t YD@D@$D@D@D$l8Hh  Hp5dk" " " 5 " " 6$ `IאE@D@D@@k@D@D@lH@!րؐ ']C ! N," " Z" " "`C6t YD@D@$D@D@D$l8Hh  Hp5dk" " " 5 " " 6$ `IאE@D@D@@k@D@D@lH@!րؐ ']C ! N," " Z" " "`C6t YD@D@$D@D@D$l8Hh  Hp5dk" " " 5 " " 6$ `IאE@D@D@@k@D@D@lH@!րؐ ']C ! Ǭ!}c~[?a^._\$.Y=,C>}?ϸ.=Ո| wt?nZ4k0a8|ìUeYu^h4טϲ~U*SZo^$ OO~t"Wtk'7 fͲt.ׯs5[V5Qyw:aGͿ)XװxrWD'E@'E^{YvovO{AX[\Z}+Xq.w6x*K~inʭSkoK힞 ve^' 3gξ^KyC֭wx|ɉZq-QO8ysB3\zO^|?,%/{䏾ߏ|>|OE&$l2C2l1u ]{\U.} aJ5?m^L}0ԸJ!2]xڶ~eMg)V7|EWwmqۓ;/;ٕo.,,(F=ix~s||XWECD 󠻸LXla}e`nIXpMjiLpFV-WRw\/b|\>Z l\:X|y5իt8/+t{aIhhJ&~^r;{L?tGO<0$_uOPXq;Pփ17A@4&. w~3򪑑_+d`9X͟M͸ʵ')y<>Qv٣X;`VkxmΙ:_bW57{ܞҒ7B'ԁİKf]5 w!5PTrz*^MuFGS{gNBRҾvKԽ % ^v]FI@Q.K^zY< +uuܶ?֔)EӕW5!fCh}KacX.Ϝ; omy kn^wey\qv"O_Dy`2Fk:m&KtJJ,3 BXM5?cG1(9u ?xXWDG" #ӋEu#`YH d6߆Z[:1pZ0*΀ۙ2g-K:_j?%44Č2ۼ7%vh/ı B(6Z n9~_drKQcP˥jƌ7D{v_zZ?tߚŋie(Vg%&|FM HBx]Xzԃ,W:=]񳅬+XԜUG@xi<2h|-qϝڈ[G1Ԙ3iϙ"1|dڝ )@2=>x&_±r pv>dq畭hsJqƌ?^I͔gVk\yg7өz.eXc^\l|\:rx=kmF5ˍFf/-f1\a<gc?B?i1Q@K<9kD++$'Qta'ո6|Ha,4V|ܴ1-a*;$Hbfz t57a4vj2:O=B*/'`zwe^in߱k兯|OGrSP%#^mjm~,$,3q\vhHz͒ey =pm9̻aoсΫKeI,f+paTӌC=ӄ߿Ӆ[6˵n<75tlZHS-7!6bt?C'sYw_K^h(y棥jaR3r4$ T{^lz_V7 7tj0Mz͂a8^wc,^һeeҩ겧,j`ӻًYzht7W~YqT.v5x0MƧ? ?P^X*dpEFT7ڊ|7f.brbI4@+ |1q{jM 3պ +YP[̤W77Wd)w{]Wv,4]j9p͞nU$!\^hx}vZn b})_|DT:3<2+0 yU8:.ftPeRD++p 0ྈtxlpj =.\Hh, w:}y|jI݂WKgkW~3/@^ w/P"yx,ݢ[ 0`=D@@-ϬgwZ_#"r\;b;K5ۉVt&cxo11];s,؎m=8J ۅt.'`XHɩ$f1306M2p)\"^-|nObU^"McwzW t%cQ)X jhl!Ԉލe46>/ 2=6xMqݕi8h@f07=D@@o \]F*[;3W\YnU=L><[h4Mk5`*dz*fh M>E77KrqP 8q,7{~ S\@V"`i| LLZ}+n2湃_dҘOZD HOpbև`.+X=eke^GlFx͋-CyWчLcϣ3[qԪVc۵g 7^{|[epƷb$$t#ؔw4b0\Xn ]uK!ҰQ; 9W溮0ض {\p`5</ȣX2^`yڍgpln[wl[0|Ax&!T%چ +,@s~ H`fY4V1XJ$\b"C?Ʒ!ubЉ,:} c܆Z18+p/2#`{wp$&[=[؁ ?g D&rb/^(^}k1zٹ'gb$e>Ҿ'S(58Sjg}-ƭh ya44#22J6 3ֆ~?=.a~~N4Q;#hzo7sy&H-0J\eT5WFq&3q`b9^V,Qc Ҡa .?nĉs /=sUp3x *ޕog?wx'>:Ƴ&g茍 hȈ=]mߠf0D1-p0n]B|f܎:YxGe/r/}ftưkMj/ǿ?//^Kim)" (/EVVw%4iSUgȱc:ۢW#xv y`0CTsiz73dg&rMH,f>GC>ȍ|85a/.fj@{i 0YW wNpNYppݝH9٭B`aGN0Bowc?مv|cӈyf]nmo{[9 2})" H@<6z 5C׶ͅ؛2+[n@w]7ob ~D,dsٹ)ԂQttu@cc>.WA!AL KM8q]J3|X`.SB)J<L7O⧺XE0au3s მ&">,#.\Eber KI,׋ hd%NO=j\8 C ?fƍ3fP5{Ͽ{׆֯LYѳE@G>sLD;0(^Ix3ߙvvmz˪uw\Sxe|9vchZDٙ:iBw% N,"lvnlܺkbee^.˪ˋXucb:$]7~Y "wv 0J: ^̞:)Ǧ@(`[>٧LWq˶%&{Yε@On] f]<0wz=q ZPI/!CQQa l{syپ Tōʳpń2rŚngq3gbfeN|XshjjWY؜<6_Ge?M^hJފDG1~ o7z{B|V4utGg^j `KLyoCs455hYC w]Q6 b@ -}ni ,^58*|/\Ari|K(f>ͻY7D_GׄOcEXJ8ǣ%^3~bV=Y,\K;(W]$ò?Ow@b.p4@OBqOo-p5}Yu>x ?"c cȱ m,DIxA8R'хoc54IeUvWej Tjv*@E^u HW{rfr1wTͿMtS [U:؁Nmv+ -gyr%\6 /'bMЂ΢%5{;܋> Ui a֎[?{W;Eu[-Y|  f?g,DV<}旧`\9e;(ī8̪>_m$F|xSxnUpp]r VcGxgj! *c<<ھP#7܆ނ/)_K#ÀbW:0k7P Ϋѳ0Se}WDDGN\~ ':vaA{E{qKSS g3~FAVƁe|[_gi-@1 g/bzlEkF(|g,<ݏ?@bp><o?s#]-" kw0jֻ-C]v|}g> h]g1 4xsįlwe  W*02Y$=?ucùy}1[<<&V!oJ_oazEV r^eD~|!}y >6q~M ]toӛZY}\y1pך~z< ߹OQ$|"ֶp"ʼnGN#<{ BW2R{V̌[Q;J ~ ߂S [Aٽ0[0e^k%_?Al/_ Ozqq dYfuf'@Ex^{ 0`Sa2,` ,P@UVKP!sm};؍7x[h3{ǟyt\^Q+s߻\~(; zZ'elL1=!zvBQF[ދSF>g<f缮\՘7yoy,0=ăv 4g˸o~y0ޅ g"I jC"?AQ. ?\L$jX$e ؛;ا8E,N8 4ko=EˋAQPF>Y@P(f@?\ArK689z~j-~j3`WFGr;$~ R/ٵ]ssnX=)Ui)C?<~iw &<]oV!+_rswPi9< X!=oq"^!Ȃ>l~+, ̎(M|0劉6WhYw*S{?T!H>C\oẮ"?U/K~>c )Ll ?K +&G'џ,,hdǪcbq")>fPDa,T,0@)vQzXDc5Ƴ{2ǟwό p CX凼w"/ HKN^ G,ZL8&ߴ\YlapWyX{Ye+.g_*ơqu{ +-ʝr8;t/3wꔗ̰P'@+?U|XF],`ö8ZE8NmZM,8|ai k5! "ø@*АCtl&3 iZLJqW_Nt#P{І-77߳gPzdb*+4/0pr^~ݿq9z2 4L;8cTx|sW"z'ܻD'"pokqcuaլ`=GH@@@^n^s)~:*wiFW ob&d@@Ģx^O'UC;kemxįrpB1O@e Q̂?1'yz w>ҝN7uLX b*Hz9. ܽZlaS2xr)lZ}-W]!Bu &b}s2nf/s|ox9܅c,(2(fsnxlh'G/2>&~'O}$C oی*DϧQeR= F17KU7bbͭl0l4w VJWG6Ati'>x kZO/ νF#`ſNgn[KFkf+r$4Qܹy,M`rrmՃd2/o`t雵 ks`s9A9ւH''OW1OP2]H/GhTHL{>QRz7ӋUVł|4%BdSD,0gpӃv<[ ZteWb3ENy\ ``#zX` صqzn|5˘Ggg5~z:6\ܷ e`{ g) !ut f ef\>0Kr9s["XYZ~eڌ" e-/]:J,:Tqz-Bb[zfҹ a=?EWyF_~,PQOgg{e_;76a0~ER I8 R?W`}j0z &qmV 13v#sۏu/`]vVL+?EcBsK,Ȳ ,7LU^ECtבo#fcp G3\xi6i@&D^0C!dGP0 kjc8xHzVYN}i-1t!kߋ4x~jAx{g12ZFճ lJf<أ>kG k֬l}ˣw?e"`+n G$oxGәI*z\k6)lD1zC4鋬{v4fKab܀/SUܻ|oTF1_yUDxLCw&z'Xbq,N>?Xu;; a4>$Xb?\ȻZ^=ƌ(j|~) q} 7p3aMZ{=?Q]nLG,|!\shۏ;9 a#ጹ\yB mσK㈻V`LFBzY`x+_5V[c䞾_>qǙn89v>S=Go\$ӫ,o'nV\3z"MwEB+ /HԄ(kmHnùxN,?kG (g6!k`glx|9ޅ7cVw(S4,T7e1s@%c(Ow܎3F=9c`"ë27mnݲ8sa3sgWVfP8O9{׀.%o*o`8/wWm=и}Ʀo=n!6a$?װE k32nvCLKSa*X6= I(:ܷ4> ssLLc$">R&P ;;ބM?+̫ySrY1XQc~nrZ. #؉X,ʠ@h`=jr?Y)fyR# {ٓFVݜUڗ^2smث1l Ѐ0+<˻Al?øiN5J 'KөP?V߀({Na%= >H Q1b陯c$qb}{ 'd?0:z/B` /ؼqBW˚e=}zZhWP.G|ȨfynnKkikn[i,ǯr цf]9Ÿ<6ba,S,8+OWMcW ;P$Jh_M?ۆډ=X!^<ׁ)?= UX[`|%N*M[ݛ+{Z+EYN `ʠkJlm~=cWB56)ZQe`OqQ>f} ΢s XA*Ҍ#H!,AaQ@m䇢D4ccZ;kV, 0kXiT)e"(h8(e#2IR.T8p;yP=5\H6 ot?2jJiBbY4:FBXi:V"6nCCЍ+G1 : ˵c< siۻ0yd>w֙ 6qlc s~=5"2/|/"} ~ஃDe@$ fTXЖAÓfKbՏs }?ue=f"q ejk }x.lΝG@gbfxTXJ`GkF+ÍdpG@X,ҽnD]L yx&YXclp{A}lSv"Yjc_ ̡@IDATsN/Ƴ~ҧY`'@;jweᘤ*ᖮ4p* G[?%.(HRt??fKpn$?6| V-0Ujr<`8`Z s.昻n!?kmpetGoFzn:yc W`A*Ī7^{;S7EK8%f]8܃+жƖ++J&ȃ wເ[hF (U@$ڲnm|]V! "]63rլ4ar&f4Gs ,CxE=h@gͳ^,cMԃ[81L-u#WqX[CP1TŸ ť q^9~8Ņmy߈y?Ǡ$м81 ̱|U*xX#7ps͌8w/4= s3= U'zgx%OVHM@^c.NoB>9Zwȏrd8h8Q:(5o`]eoUg(śQ mk TXo07jYO /4m1 0%8s<? quNs}\ss( w qאK(< 9Е3ϳt {l9 ~qtuvL(S8 hE7EqEcȇ7“E{5xvۑhAQ^Z4DsXN2+ (1pHHH ^=M21``~$\Ɔ`kk0ΡJ[zz 4t줟}v[h@82Q҇bSw4ϠHpsv ' ,(`q!0u.9h#xh܅mc@ w ߆I};0>%ncO3]O֛It2?]4s''q*k^lٲLыc>`VD/MZ9g~!.ojr2k>eFlH@!&Ҩ ~ʷ ./@&l[c$Le"_(c}<#sK H0/]hqgۮ,\ cajFXx٤(Z @ Hpcj>߻NG}#JAnjls5yX9V b+fJ3-`{K\p[W :u1n0!BS,$|Q8ٮPh"Sx?#2j[a$`})z>l]݀_8r5"Iav!>y^8=|iv##͸C ޣ*F&]<ۯb& ,cKTC'̓D]w VS,-iܫhl>"[NOOt0e_;-o-P@0>Aq?0<|;x(hΆFOPgmtS> <ֽKtӐtX4(ǣj~d ݿeK(xYǪOppUנ/yTbxa[1#L}l|N)d~!8;fae,dnKINRhaQezVXppmʬ#N&˪yrcZ"Lk>Yk![Bv0b ssgCQ[{Xߖz ˃P{7l@[s+xq#+~'yI?} "ܟ%Y! k^b<`jj/bڵذ~=3`|^l޺?>Zv1hK^+0WelLH@s&'Vh+gPbzދ\8; [ЙBȸfKP0"z ,:py,,+)Yh^EzB̓X6&G1ل#n*/rht59LX'8z|~}+ENs0R=_HH5ڻ踏N $;E -*V;'|i󒻔%K=;vΎH-5˔e lA^%#ʲ~@` v377ZfyjT`ʿ gd9qorh{:Ɲqr Ԧ<. J{{tv-0 i;[뮔wxXN,{AQZW{ ؜J%dG1>66.4.QǕ\@ݎ2]#ڗÅD8CuWڍ`p&oQ$جy{!G"Ndn4,TfD(ߏ >(nEK$ ՍjA5P'X֨E%MXTؓBlDC҄z;Ϊ"6 :e@}Oߓ\+6'_#PhL sV'ߊe\NNsr gF_Z[I8܈KweIGZ$_8Y#+XKO~FǠ2.6߯Rۘs'_o4I n5,W$a%@DC2⨒lF@_`35bv *}~$=In?sH]併巏kw54YL١Yl.\ x0YPC]X,SwzY-pUc#[šl6[k]8B^trZv$ D'%Rndy2=S%at7 Q/zi F*]t-)y_>*g.yȩl[nAӕizγn;Ȥw"s/B-f&x&%k|> ;؅>){i/ka6"PfŇDG*b4Wȸ~oGKn_},,YS3&?ZI4сWAG4F@dGt$VZ/e!\4ʝvwYds27F>jV׷noM؞\gq**>@>"P?^@"X| \am3fcz| m$A0+%s>WK^\CZf&ĕɷdwODPjponMȋ^S_ pzAi 1PO}݇xu lL#X_w\ ף%DB8'G|&ֆeEwX.TVni4WRiw?[9:8!a5R _8cerZp01+LT:|5+'T j~5LTn&YvmeoZfa YP>݃m"Y9]%g%~VRk|HUde 6w{-(r ^NS`^`~ _KR7qlSS+@,}tBҸM/Z!l8kBW)Lx`FI1uXYZ K!: EKp 5Y]ur|';GG_ERR/Z׌v:^ns%S qoE㴔s2;Kpak[/lĥ<ȶUq:Pݻ;Pw/]s"pa:>,S'FBLs 0s*3'NY8<nb?GfRRՅa)GaV[{QwAd߅ )kO P% ^B'(RUeչEbո ձ;,$@'wz;#էH~UP܃S\VF\V6izWXA\72HWU^-/,,An Kٸr՚Yta\{:{_ڽR}nw%k`1ӎ|A>vDa9ImISO])G/D~457_8V#V%dS+K:q_@ #|bNU¦Dya Ty 5QW)19,A`IE@vBrkii #`Z);|RMǢ,_MA h 4)p9 bj}._re+;Lc[g#9-9 A.^ƻ% [ƯK7Y[+lǍ<(c=;d;GFrk r)cm8V~uKSKp՚ڬvЅBGl6\d>K%GfVB/Sg?囻7+V˯1'=,)w祐ƦӸ5pQ^5|j6BON^CWUnP#Pji%ĴhZ86,W9**DwrZNoS"HӉA`;꺩νz9OYT^E@ o^(pA%P wXV@.;ySL.I&+,ܚgG]N+B ػEJCCG௒TpJtK8_9I1-3}r)yh:jtm|I>;|^T}tLMpY;Ђ$?-E)rr<$ŀ7J*6{a4ゞaCbF%9 ͸7{P~_o?Jf@':i^O|S 0G?X!M;w~|uxo8ptlڼuV GF _={KU86[Z̓M򾿨/>/| 6b!oq 8=Sь=o;?*g3.kdj<35=6<O䟭`QG'^" D*s\՛}.77wlaYxw-2fmہk^*'g@tfd9DfSv,ʁj9JK9ˎ7Idz1rOJNJ5:t@~ί$bSgD]1m<k۱9q 7N,[{7I+" 7pnFE{RB,0S,M"v92P.'r*|)<2t7kj/}+/^w̃㨛mcs[ on#6|b۔ܺU c HvEbwJ qՇE&< JKjY)|xKV=3'oOô,µĭf&"7@͏U66@g;+WKӳcrs $0jiDo^ž#gl .,rw܉#|Ͳk׮_X8Tǯ>qD0}, \v907@4'x'); Kω&)3?m{2V.+b97p,S ^jg( ʣD8bO܂|w3Ƚ}\oK{o.w+ѿ cQWgm^>-k/!K2,F;&p_N6Kٍ+K.LA$JJ9CyyB rqyb`ΗξC~+mM;+oV~YcV~a<&e0^+|fGc2c>R@Ngz4%SJ8!ٶm>Ca5:|jc I-]"睵"}8+DddM[(~o%Ct*b,B~I;25+s#ORB)x'"/fȫx0R .')pytfز/lYFqO}b.;7 *~s EǮ) vIdt,z"|t8 ݵI"!/v\-XF @^R Hu>zt!O[s|d㻲(z%|h!@}M6 A1Zz+I'$ٺ\%Ab @(b_]?_|<*A[:"r%}#fFA9^Zj\wjd!@"$Jedg䱃b{5c?#R1K'^<7-jNPBR`p>EVʖ&7dcǎ؁nZcϠG+mnO;$ðԊF$OaG q/Fe\|Lz.#*_RwI3n t(FXW|Y2,BiE }.l!qPQ_+)0p#^K6u'H:ٸjgg>Lg {8w~tEOц%SQ\C;A*y{#O=T*_u:> PIjHb|B2w˃/(:j%t N%O(#oVo9|&nL-r<8?)زP^ߡTVhn~~oF+bz6|tcr2n7vTW*CKr:{u!]2J_DӃ2Iʶ ~XLv@>xXBת~Zb~%sq`AFj[/.ӋZz9Ӗ|q)K.t,Ux'-G/!\㫒 ᒞBm0 pC" u{_ 1D"$?&32 UW"UYC#'R6,p ` nȴ2鏦dddv)lܢ 6ZbE%PԞ>(@ X+ %BoE['*'kwÚ5k5o[[mbI܅.j h}Շ%_Ekq;nK[Ce. {YgM=- ixYZٹ⼬'S pt7#{J\&?[ekV\UN8.'M',_Nyiܙ:}X'(WO媎tJuR" ߰A1Ǧƒȓk/(};2Oˎ=X~>-:$wP /]eWᶿIC4lS&VaGD$"'1}mѹl57s˚de+8w"G 8Oya_u=T( ^و?A g*P:} ]Դ={h={=~ki꥿ֿjyvUm!zEi3Xbnn[L|yfvS m–9-Dgщp-GJ_^v|dg'e}p䑊-\+q<0.]W >/꒎%Ke ۧ/e >jJb=Tmh ˊkwHˣ@v\{l-ÉՃ=3>ؾ ;nNwr=ylW=}z?=] Qj>(@W`F \XdIewVKf~ـOWCT8.%tK\&񷞑owMϮyp~C'7n^-X[esŚ 8;L:y46-Y{FېdABˤC#֔2 Dܲ$ȸhIi^V"}_*$)?8q`CZYc21yR" 5Q'!w?\fE0(Һtx[v9KPzoLs:Ӷ;f}u܉;l{|h띞-U?1(@/M?އߥk%,VTΠ|6|s{hs{ҍ3kmWyܸ$diݜȔ,a{}-~oSc܈[qr|~Lk#۞%Ml;gA7Fd{MK=ܭmջUYy},uMAïA ЈS 2Y[/tI wS:[ Py.C\|^[`wa9?}Iu{adO߿<1KRybj՚jJ+.. .'^y0YW!*:@jVT%d~PKS;' T;^=v]}} 褯-a.)>FǝoYO?YHP.q`r9_`wmETWcZ^U#h\z$d I9;RiL%>D#yy-S~Cc -pjǿ'Qz,w}e/nR:uJ33|>PXryV JK 3pf$e5_I 0? ArZpN]\S.ՂË L<7/輵fn|Ou<#\6v:v֧B$͞vȲZUĥA{ĥ^fcMjBo~iVGK/T" uMɖiɞ,_)o/IQ%A`#.I8I3ȇ?T Ψq C-+ʲ 49ş$gb_k"Й- __尒t&3GƧ﩯 [OS5|Onv͊_cbH ބ22?\$~_:;:'=,by^ZifN%^ ;w@A*YBU7YSFepŎ#_\}~M J|wCa;Fʘ!U UKmS+EC4<rwK׵ͅt{zPozкUvWGH8ϒTc|^鍲s[PC찷3I-RDZ~ޛʯX^*gըCc\,TWYyψg<._oO P`F(5(ЗHY<+ j+fz0'yzh_NNߖ-muW\ws&_F]=_;{>ap8E>ܱ&)mMin⌴<$` Xl]B+oAGaI#J@:Vظz}ѿz H* P `Q( \q.䗣/ W. q붭qG?Sw5kE~UH]5#3V2>yV+永2n!6Qůr#-pⲣy,Po7 (. _|ʒTgy1.ONMjnUa'};'k5r`7N)3fi/%pK"Sٌ5C_D|Po%o{k+`@%FX9U`Y'N}vx֛>aӶ;僧{`-ks)A?zv?fk!MsC ߤϋʱAuO*oBj桒T|ajxmF $ ~% wDĞD}'?U+{O-;ۼy˒:gF>iD 61c :~֯R";|K=^c~v޽gϜ95E~jzMwOI:(T.#g"ٯ}K^ao[94k7l̷܁nk1l;q aX85q?+Z:<>ߝL&CCC!ƛTi>(@@#-P@ }(@b=)fѺ[U^d7]tj˾w5Wl/o ]. Xx/H3f6k0(-Xxsv Do[A 58h P*l3~C(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@!&IDAT*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@*Rt(@Ȭ"(@ P@`L P0@~h׽IENDB`icnV Cworkbench-1.1.1/icons/mac/wb_view.icns000066400000000000000000001311551255417355300177070ustar00rootroot00000000000000icnsmTOC (il32 l8mkit32et8mk@il32  ˠŬh 61 I/ng l l/MNptcѡ-h #KxgʪȴHi4?}v>oe]j!e4q^U=̠tL'f~Ha^Y͋zn$rh jy]۝QqvĴ$eeR]tyyaFe޵TVRta^}iΓȱqT"i4Bj+עᢙO~U:_-zбⵈfA[YnxƯH701qԮ~i90ZD~.Q2aDΰpJTX/V2ilcMUT`8y}p7RYD tȀjeUTRTtϨc4 Jg/r6qIҖQe9GŬ\(h 5y18 մsya ˣe8yo/Is~dXI7EWrS =͎ĊILW6|x ˠĭh 'I( IAld l rkWtLvmo:h '6epRdwGi (M\sc5mJy?!fTzGveƷ 'g ecjNtdc|ƅ#k6t{r{z]Qjt#fk`eratBMgg1[V_yZY~Yps9pHdy7)pkiOW5tވKxiGOudXMz8(Rݢׅōcs8dmD[>OL:/0qa}mZR`|RMBG_ss,fʔh  dD C>{w(&{g!h #KK~?w/|ڑ 'g>ꋝHK5]e`sƒ !h'xŌ)B[efpTfPv#e0i͕^o7IX3o{'ScZ9i dSXJXOxl9]&T8./S[LCB-u$s:S?aĹ^1Ik&M=I|ib60MU0HI  &=-g|HI]U=LE2P?R{>?4RLsǿʳ4  &A ;]4OM?M#.z>WJVxmƵ`dt| &A 1;Qy8 GM4 |5/S^rͬsWp͖Ϻ &C(+8*:Kmq7ROIE"de>Ucp{l|ᶹp ֒&C4+1GEgG]cSIJD2ðtj}}΃rtpoY(e5s9&I67$ȥf&GE:FS7ΗnK[IJGF!Dplyo{ʰmwTW|̲*&Gje76 RL[ռ]WL=>BDE[m)Lqjz*slBI˭]_I/4U=&Nem+~\@ڱCPqjMQXm4^Xy%ֿ̯/%2&R d>?JSþĽƶOGS^wyKZb{)แw追ů~5X>&VY#FM=2QŻƱؠAOUj}\WZk8칀hp˞w!)׺;a& 򄧹+evtۍŞx\S>e&dtƴ*'bqоԛӿGV\lHiZl{BSmetײwdSd@Af&=ʷPS[ Wv}ξoLYd~|LmbpE[ckrkpyƲbMwϿ/&fһZ .}y›OZ`pZPqmzԂJc\adgnuyz}Ǻʭ|dٛd&3ۏ^ mlwsX[_nVMtv[O[Z`belqqryȴڣw&& LmWyr{sro剁<׸iTjk~JKtτBRX[_abhllrǼSp&2gyT3Lgq]D˙wwlMrr(Nu֘`FGWZ]\\chku섒äp&g}:K&$:t_Wc}CWntCovf1`rܤ`|]CMQWX[agnubop&uZ]>ÀxkDUrTJlHfxUd_dB?DR__eiryʉhֻ"p&HɺI~[Gt~xHErCpᆴųVZwxZOiwXZ0.8F\h}notϤZjp'ػ0> ruqxR6wry~ϕߢj_g~ӓozL~A;\nK3uvߴpNp' t"tn/ h_~xW5k3 mVm于uoԭML;YQ;tʵ΀LzFp& WztJPׯ?kzU6gЩ"NjUlױ~῝wekPTwܲ}gPDPrkؚNX^o$Ff$W`nwyxøL~R7d\}s~FS]ONuնƲl`{ޢ|zo񟐩]Pe>g2p%|opt~S 0qS4bʮG xBG[KCIlǡýiGǬpEl͑Cw^HPp)*}~z$bsR5YtzK6HC@-sR=D-EA>86Kqʸ\«Ͷych~uyKpClĵ&pQFCiQ6J<7*&FkȲYʽ|Ͱ¿u)p 1aȿKaXDIMn/-44+"&7eڼƳ`رǿwsv˛Tp,GW{µzY) OZfjWbc#/-))-RE¼f˵Ͼȩul{nоzp)j7d}Ơ "fVg͟[U>37DC62LuI}mɱλkmܳAJ`b*p,Ea\s \S +k[OqMUw+49GMMH:17F[wuϿכʯtfv+6|U_ȭAo>\w΂;`,h\Y[uk_`lew -0517;;9410&6ՓdῼþZRw߬X:ќ5p@pzʵzPss3jjmbMNUxaOG[Z: 3MH>63:@A=73/1-<٢ޖvY˾uK`dEܧ3pDTnͶI8hZm}wc\P=A?HuueD31AĴsHiTz̯@qt5Vtdt]#Dkajվx@Leb[UKEBI\w8=CHMOMIEFGKKByъ`F68B8z;PY݂\ôOAzBAJzg. [;]^UNJHCDL[zi ?KTVRPO,SPyĭgEACABF=Ơ`P}i^Y(t\Css= !ו AW[QKCGB53XqDk9EPZ_[WZYaU?NLMMLGHJJEB}ӫrKvr:}Ի(~Yopa^觀Jmĝ`ի(WETUKKLL ?W^n dYY[_cadkdbRRSUSQMKOPLKBb۴SiZ/m\Cq^KᚈP2"wI n;TOMNSH#0I^dktwўiZgiquztjdXSTVXUOOTWVNP:w་\gh"KEvޕ]%VrW g5RLRRXbglmmxw˴ƎVoq|sg^\\[WSU\^^VS )OLRU[_iTnR]Tuwsiec_\Z^eeaXZ9t߿j̎څ/FϺ̟pMRJ 2IRVW\]n%q' ^Zvskgb_bjmi^][<ߵqV{M+(BǦɫxi:lPkVBSYAcs {P@7ڋb~sjc`eone[fLaz j"&4etȧԸX TddW"KQORYo`9vgxld`cjh[\TxFt#'{Nb 4!%~;/˵N&g2<\q\'h=\G9EJ>/ *V9|Ķp֢  n"  Ơ0KҴZfo|bB|oCGdijcd[=&N#%Gæ#=eix{` 60Jia*рxǦS>ftwIKiEG*$'&'T&%%'*&%!P-^nQlZNrs_jj~ߋ[Ǚ{]FqwdWhY<" ')'&%(%L  Lnahowům@ADqnϹVs}̑lLIF& L3ESatZ 57wFg|{ !'<`|jSF?>;)  SioxucN;7<=,   CUfg]WUR;   QK< ۗ  G*$'&'&)'!-^P9$RklIYxY- &%+\um=8beM,,SUKVwlUðɲH &=*n[q:8__WA?NZhE8Q?S{gS?OSyWw~; &A 7ef2LYgH@KH_bFRLVwͷabxs|EvXTP9є\X*&A 5;X^݆4S`MFLCICe>GP_q˷ƮrUYCBTmf}a¬Og3 &A'3?G=:PLY?[WOHGACoo^ap{Y}{=CdWUgbTwLq\=֒&C38-3FSMJFo֦>]aTIIF9T~³sj}|qN^EUmRpOkwj8AKeg|t\jd9&G;KH0<\O;/\rDdULEHB6a{l~wuuJ^jYYePOtYeg`fȦg&G /?Q:V3D[K/]ӺYNZHHED:OmÑOtq|̼WFlYX/Dl[dx̮~)&L VA::1]K8l3UҟPTMADHDHZmoybnk}ĚBLeV;>\xh`~n^b;&N g>12D`&Ckv;J867BGN]sX[\dmTWI>1Pf`mqUHs}]vP{M &M muA:Vc-qpf_phKT:?IQbvysVXczMaMM~~NfIнva\wc_&OccCPd7EnBi~·ǘ@D=ÿ^Rgùj:OPcp`D\a^J[uݩhijq_o@&T,ugxv^I4-Hż`˘6DLZcsiSYXlipyYeq~ȤmNsӷ?&V qttv`71qűϝHIR[uyDbMZ[^yOʣfBYm~}Ѵ^F˜=&XSHe}TX˿eS[DDPUdLpq@bYh|eW~ǽV6jq}SEŝzYPXTƽ>&YPdoyĵxkfoѿ`q{dk^[ڌ8MUXy^k]Kh\hmMppCRipXPv}ײzhYjjab$d&!c]blY~v}پ|fyTdiMn>XUfgɷDYjcpnfElbE\cecjpyǰ|M|eceze&MM_>kcZUn~߾~shl:hݢ{K\[zqyzmSPrmweψAO`^bdgnuyz}ȗd}ӑe&4[pDw{wb^S|wvs~xdF=QG_]lTOuwھTBRY]`belqqryȦxʻjmYvp& PhR_kZs^xx~{tHoi{lsg޴^:`doyNFuq;TU\_abhllrD_zZgoDǾRp&4P;vRo[Zxe}hobctWa[{b|`]GTosOcx4Mupޗ5CNUY]\]cgkvyaճPh˿#f&nQcDeO`wP_WaҋupRqbl^hYoKLKXApuwȌi:\syaA>:GMQWX[`gkwo[ֆJg_աWp&;dFak_{kDSxϗvGbyhecڅDrP\7lsyՍHKh}jy2MI=;>DR__flowsSNĿQV`-p&.^`}X}h4kuxHDuÅ]rEmvkZҝPg{[folB_qyZ߫Tiwa/Gm^.+/6F[ekpx{^M{_jݟcEo`Vp'ԬL}THIuq~xR6wakrQbVŗ`M_Gu}Ek]jypcҐpz`R8ERG5<[uI:MYfizv[֙q۴oSn`o"p'PBzoSDw_}xW4ld_slDDă_RQL[Sf亏up|]ۚ]̮|kg/HA>@WwD):NW\`e\Е`|STvV0p&ş=e~zqYb٬MrxV4hϼqgs_JذiRٙ_ƙHs)DC>OsxQn@++BIEJ_f]kOUaoU p%*SЍ3X`oyvwĵ?bxU4fχeezhBlBWdPJFԹefū^gת̚SwR5EB:Cr8dVC@BpE`~ZN_Aj1p'L}d`|ooui@]nU3cȼ{`gUzCEXJDNl¯q`bŒھzBFTKDHnUlqloCPsx5Z͑@y[EQp*.Ox~{WS]kV3ZQQij\/LA@9MpYyRth|?G[ISSuĐhQRe.KwfbjZ&p*bkõsSqTZmU2*%%98G\wcUɲgħncرiQOrfwl[i^/9YbVPDo@XzmAI4c̘PXUe>n_KNc`V`jmmbL987;|VSl@C-1Yɼ~ndeS\waCO_`X\jttmcKEKAlf90,1@LJC=52574-('+J΀H݅^VfmH|ij}527;;9410&6ӘpnNLRVM9zWwʻt\QwkJ53;@A=73/1-<ԬYpHv^XEGn|ȪuK`oH8Nyۨ2oGFQFi\:xZcǽJILhnADH3K`O=A@Ew~LۆWucB+55mȳsHhboY}˯@qpK?Cnquy۽KaY;qihqeiտ}6=ha\UKEBI\x36:CHMOMIEFGKKD@H֙CmgE8X[PYw]aôOqyU6Jz[YKn6]w\]9^^UNJHCDKZzmX(=KTVRPONQPUSJM`a=DF@DABCE~Ơ`P{HY*wW,tLetU|=QvҩaAW[QKCIE>?Xqo9EPZ_[WY[\bTKRGDFKIMLGHJKCFӫqMrqepXTXor>BMen_HJpkƬƅүiSRETUKKML?;8HU_n]tbYX[^eafgnaXZYSRTUSQMKOPNIAbܴTfqhvkSX[Eo_/ZWdm[ODlpd\ldWjUFkNzSD;TPLOQPKKR^ckuОjXfjpvuvreYSUVXUOOTXVOP:wཊ]eRbo{D[N2oN-^{iatjZDkxxb\nttnXygYsbWHf6QMRSW`egloww˶œXnr|{rg_\][WSU\^^US=ckbV]WYdDUo;'{í~k@hU{hHotXjl|r[XWAi{WQ)OLRU\aidu[]ɇNJWuwsiec_\Z^eeaXZ9so?Qp[bmr>p5%sлnFVoWi`as}~mR[ehQ2IQVX[_jWT^drKIұu[vskgb_bjmi^][=ݶuJ^yYmp{g"p.#jȤ{tѨxiY{blNZsUBSZYXdthwkvZztZϊUމd}sjc`eoneZfL~ˏ{MRUX]@hup'$n}ɦѸ`Ikzv{k\de߀Q}POORYon]eEOyVђlfxld`cjhZ[TwòO:mzETZSϠ7RXp$qG8ʷaI\jlkthxX\mh?gvFNOOLH6BI^`L[]LgZsm{lc]_`ZPSmqbdkQj2Ev:p%`W=8˱zN_uddZthW]nRєWYhm^a}q8DXYLDGLNP}~xeq~ymb\ZWQJey]Hvȼd8=cp';7չYcgPU[u\Xeg^ckGgOʫx_MGPXp|oQQ|y~tl`[XORRTYböS@;>p&/o̹xpXE1_tfXhpw`VuX۶kuơz^^krwvTRM{kwd_ZSLWZiSHWºnL<1 p&IbdeglpzzeQD>DB0 *V9}·q׬k|auLBGPT\QOďpz_nѷA-OT,Okwb@HckceUOA&L%?cǥzȿSLGMfjw{nA8C?Wsȇ\d|3M{R+CL:!H_W,G*$'&'V&%%(*'#)T\fm]Xrjg}[McƞJMo^A89:Ob\;""&)'&'&%(%L29Zkbhpwȴilvqʽ\l[spdXWS]NDB!% L3ES^uֈlsmt]Pwg|&.CZSA1#!%$ 4 3PliMecXogwȼu;LPeio~ 7'(')+7:.8[NbŶ{lZShLz  *!;::AHfmJǻY>Kmw}ν. +5RH=EMPijOW|x||ʴ? *6icB.YͽyvK\v}S #VvE,jʶ?qY %)hr[;ST #UsJ5 !aw[Ƿ~[   0[Rqó|somkh\> !(;aG}{iTF?>;)   5RrvvbO<7<=,   9[gf_RRS;   *5+ ۗ G*$'&'%& )'/EZ][7%(&&(%!0HOQI3#&*)($%&'(&'#-& A~}WDX2 /UgjUX`N% ***lgFaR9$Sjm{|avT$ &%*bo}}>]aM+.RVIYricg9 '/(`' KhwSOF#,NoIMDbNHLpu[r{$Zw-K;&!{ǐ4֒& 2VGSSG=30\]VJFP~Ĺtj{|O)*4g}zt:&EJxEAXJ-FcVKHE^oúw{xxQq&-Jmpȧj&G %Ilx^0&SYIGCGmdg֓ntzA",  Em}̫w&&L %>AgyG*VMBGJIQYoxknzդog|8&P +2S]%#K#-CFM^pZfjD%#(]~Jøb|Q &N 7Pa, =jM!7'?JRbujUb~uLO|q(¶vӒ_&O'Vd:!&OeiսȌ$@IWe|˝RZkړp~6| ǿ⧙t &Q*uԻe@1H̴n`RӾ~&JK[mYdP^tȰRAB6̾+8ĸL,SNdmQOrlx:gS^_ccgnuyz}͇FҎe&53`ٟ|q{2 !@b^m\UOwwݹ1:UX]`belqqryʙVȺ}JEDS__flmur( N`;2p&y}ۯtxHCxg"oՕ/D\:aol jdyB!(06F[dft{}C  Df%"Rp& vsrxP9o{%" sǖZ!3k\kxL  ;˒oz6!#1;[vH;B-R~s/ +ٴpS$`+p&}w`{xV5l@ " ^~c*HE幏up{F&˰R&$>CVu@$# .DG1zVCY56p& {yyqzݧmuY1kmB04~ObAU8װTƘ95LsX+i3$211.]? IOUceTp& "MUboynuðm[0kֆg AXfQH6ԹP!ש ,+"/~GPC.s'uYN[Am2p' 7)W|nozpV1e堎d=DFWJDOlR 7ܽ¿T H;J. V_Zy4WΏ?zZDQ p*/N{ϧmV4YXs"[].M??9Mqņ5[3H# ^ 3Pc#@wf`k[+p+fuIij֩kV)ĸiD/b¼qZE"2"y]s48DB>B=CuNVرڃ'hnlˬc  @3o7`__ADG͔{b\U? *UL#/ }S0׫; )sfwןW)-GoBV{a!Qˏqy`bS`ghndF|Wfd6/=JQJB:4+*.:FSfj RHqXjaݘa,*sbPoLKO$GʺҜl_ra`XZkuulf=e’w_bP%4?LJC=52574-('+J́6}h ,rUgӁW'"8.<]{~Z: 3MI>33;?A=73/2-<ӯv#hN:4F&>ɪuK`PA+Xۨ2oH?'bG |C=ƿNtd(DaN<<"P_PE>Qo༡vD<@A;1;BEEA=9484HĬ@konI('ŲXN{ʧH8[ի7oWL$u>({joӇPnzH:9*ywD"?d\VH@EXwοiC,3;@HJIDA>=A@Dzm'ubA*1<$ɳsHi&1`{˯@omn40slq{߰4X]΅yfl18ia\UKEBI]xÄ06DGMOMIEFGLJE,biD9>>@IPYI>s´OHv{9H{dwmM͗]9^^UNKHDCJYz?:LSVRPONQOWJ#8F@D@CAGtƠ`Pyt UXqxc#tv{HAW[QKKILLTXXs;DP[^[WY\Ze?3HFIHMLGHJLCGӬoQe ǾXqt,7#$IETUKKMO^Q_p`ZV[_eaferQ>NUSQTUSQMKOONI@bܴ}X[- l߼JqbG6-PzٽHbmaeMKF &M+tͥqǿpfnuzxfcs溨љy'nS#3=" GOP$G*$'&'V&%%(*'%:`}ίogvy̙fb|Ц{Ġ+ [_?$Ka]:"#&)'&'&%(%L"3fvhegpw˼‡v}iY(>VPDA#% L3ER]uٶe}(LT@1"!%% 4 ,Jjo~ 7'(')3AHRdpĵxz  *5-6;bŲDz}zyʴ? ),hdH-EʽX%Ew|S (UBeƷqZY &%r`eT # On\5 !$dtZǶ~[   1[Rpų{somkh\> !(;a.t{iSF?>;)  "+JpuwbP<7==,   5gkd`POS:    */% ۗ t8mk@   U+k`% %o^l6 F  Fn` \q La0 >T& XJ5v) 5"1A Ts @ u 4<* aD^BN3# W{ (io9 y /Fg q r | v>D. U &L62  Q )]zF8!SpI7|@Et|sG  6r¿S Q  J|?  k[  dy =Ch"jYu!l!A-7]/5GicnV Cworkbench-1.1.1/icons/mac/workbench.icns000066400000000000000000001311551255417355300202270ustar00rootroot00000000000000icnsmTOC (il32 l8mkit32et8mk@il32  ˠŬh 61 I/ng l l/MNptcѡ-h #KxgʪȴHi4?}v>oe]j!e4q^U=̠tL'f~Ha^Y͋zn$rh jy]۝QqvĴ$eeR]tyyaFe޵TVRta^}iΓȱqT"i4Bj+עᢙO~U:_-zбⵈfA[YnxƯH701qԮ~i90ZD~.Q2aDΰpJTX/V2ilcMUT`8y}p7RYD tȀjeUTRTtϨc4 Jg/r6qIҖQe9GŬ\(h 5y18 մsya ˣe8yo/Is~dXI7EWrS =͎ĊILW6|x ˠĭh 'I( IAld l rkWtLvmo:h '6epRdwGi (M\sc5mJy?!fTzGveƷ 'g ecjNtdc|ƅ#k6t{r{z]Qjt#fk`eratBMgg1[V_yZY~Yps9pHdy7)pkiOW5tވKxiGOudXMz8(Rݢׅōcs8dmD[>OL:/0qa}mZR`|RMBG_ss,fʔh  dD C>{w(&{g!h #KK~?w/|ڑ 'g>ꋝHK5]e`sƒ !h'xŌ)B[efpTfPv#e0i͕^o7IX3o{'ScZ9i dSXJXOxl9]&T8./S[LCB-u$s:S?aĹ^1Ik&M=I|ib60MU0HI  &=-g|HI]U=LE2P?R{>?4RLsǿʳ4  &A ;]4OM?M#.z>WJVxmƵ`dt| &A 1;Qy8 GM4 |5/S^rͬsWp͖Ϻ &C(+8*:Kmq7ROIE"de>Ucp{l|ᶹp ֒&C4+1GEgG]cSIJD2ðtj}}΃rtpoY(e5s9&I67$ȥf&GE:FS7ΗnK[IJGF!Dplyo{ʰmwTW|̲*&Gje76 RL[ռ]WL=>BDE[m)Lqjz*slBI˭]_I/4U=&Nem+~\@ڱCPqjMQXm4^Xy%ֿ̯/%2&R d>?JSþĽƶOGS^wyKZb{)แw追ů~5X>&VY#FM=2QŻƱؠAOUj}\WZk8칀hp˞w!)׺;a& 򄧹+evtۍŞx\S>e&dtƴ*'bqоԛӿGV\lHiZl{BSmetײwdSd@Af&=ʷPS[ Wv}ξoLYd~|LmbpE[ckrkpyƲbMwϿ/&fһZ .}y›OZ`pZPqmzԂJc\adgnuyz}Ǻʭ|dٛd&3ۏ^ mlwsX[_nVMtv[O[Z`belqqryȴڣw&& LmWyr{sro剁<׸iTjk~JKtτBRX[_abhllrǼSp&2gyT3Lgq]D˙wwlMrr(Nu֘`FGWZ]\\chku섒äp&g}:K&$:t_Wc}CWntCovf1`rܤ`|]CMQWX[agnubop&uZ]>ÀxkDUrTJlHfxUd_dB?DR__eiryʉhֻ"p&HɺI~[Gt~xHErCpᆴųVZwxZOiwXZ0.8F\h}notϤZjp'ػ0> ruqxR6wry~ϕߢj_g~ӓozL~A;\nK3uvߴpNp' t"tn/ h_~xW5k3 mVm于uoԭML;YQ;tʵ΀LzFp& WztJPׯ?kzU6gЩ"NjUlױ~῝wekPTwܲ}gPDPrkؚNX^o$Ff$W`nwyxøL~R7d\}s~FS]ONuնƲl`{ޢ|zo񟐩]Pe>g2p%|opt~S 0qS4bʮG xBG[KCIlǡýiGǬpEl͑Cw^HPp)*}~z$bsR5YtzK6HC@-sR=D-EA>86Kqʸ\«Ͷych~uyKpClĵ&pQFCiQ6J<7*&FkȲYʽ|Ͱ¿u)p 1aȿKaXDIMn/-44+"&7eڼƳ`رǿwsv˛Tp,GW{µzY) OZfjWbc#/-))-RE¼f˵Ͼȩul{nоzp)j7d}Ơ "fVg͟[U>37DC62LuI}mɱλkmܳAJ`b*p,Ea\s \S +k[OqMUw+49GMMH:17F[wuϿכʯtfv+6|U_ȭAo>\w΂;`,h\Y[uk_`lew -0517;;9410&6ՓdῼþZRw߬X:ќ5p@pzʵzPss3jjmbMNUxaOG[Z: 3MH>63:@A=73/1-<٢ޖvY˾uK`dEܧ3pDTnͶI8hZm}wc\P=A?HuueD31AĴsHiTz̯@qt5Vtdt]#Dkajվx@Leb[UKEBI\w8=CHMOMIEFGKKByъ`F68B8z;PY݂\ôOAzBAJzg. [;]^UNJHCDL[zi ?KTVRPO,SPyĭgEACABF=Ơ`P}i^Y(t\Css= !ו AW[QKCGB53XqDk9EPZ_[WZYaU?NLMMLGHJJEB}ӫrKvr:}Ի(~Yopa^觀Jmĝ`ի(WETUKKLL ?W^n dYY[_cadkdbRRSUSQMKOPLKBb۴SiZ/m\Cq^KᚈP2"wI n;TOMNSH#0I^dktwўiZgiquztjdXSTVXUOOTWVNP:w་\gh"KEvޕ]%VrW g5RLRRXbglmmxw˴ƎVoq|sg^\\[WSU\^^VS )OLRU[_iTnR]Tuwsiec_\Z^eeaXZ9t߿j̎څ/FϺ̟pMRJ 2IRVW\]n%q' ^Zvskgb_bjmi^][<ߵqV{M+(BǦɫxi:lPkVBSYAcs {P@7ڋb~sjc`eone[fLaz j"&4etȧԸX TddW"KQORYo`9vgxld`cjh[\TxFt#'{Nb 4!%~;/˵N&g2<\q\'h=\G9EJ>/ *V9|Ķp֢  n"  Ơ0KҴZfo|bB|oCGdijcd[=&N#%Gæ#=eix{` 60Jia*рxǦS>ftwIKiEG*$'&'T&%%'*&%!P-^nQlZNrs_jj~ߋ[Ǚ{]FqwdWhY<" ')'&%(%L  Lnahowům@ADqnϹVs}̑lLIF& L3ESatZ 57wFg|{ !'<`|jSF?>;)  SioxucN;7<=,   CUfg]WUR;   QK< ۗ  G*$'&'&)'!-^P9$RklIYxY- &%+\um=8beM,,SUKVwlUðɲH &=*n[q:8__WA?NZhE8Q?S{gS?OSyWw~; &A 7ef2LYgH@KH_bFRLVwͷabxs|EvXTP9є\X*&A 5;X^݆4S`MFLCICe>GP_q˷ƮrUYCBTmf}a¬Og3 &A'3?G=:PLY?[WOHGACoo^ap{Y}{=CdWUgbTwLq\=֒&C38-3FSMJFo֦>]aTIIF9T~³sj}|qN^EUmRpOkwj8AKeg|t\jd9&G;KH0<\O;/\rDdULEHB6a{l~wuuJ^jYYePOtYeg`fȦg&G /?Q:V3D[K/]ӺYNZHHED:OmÑOtq|̼WFlYX/Dl[dx̮~)&L VA::1]K8l3UҟPTMADHDHZmoybnk}ĚBLeV;>\xh`~n^b;&N g>12D`&Ckv;J867BGN]sX[\dmTWI>1Pf`mqUHs}]vP{M &M muA:Vc-qpf_phKT:?IQbvysVXczMaMM~~NfIнva\wc_&OccCPd7EnBi~·ǘ@D=ÿ^Rgùj:OPcp`D\a^J[uݩhijq_o@&T,ugxv^I4-Hż`˘6DLZcsiSYXlipyYeq~ȤmNsӷ?&V qttv`71qűϝHIR[uyDbMZ[^yOʣfBYm~}Ѵ^F˜=&XSHe}TX˿eS[DDPUdLpq@bYh|eW~ǽV6jq}SEŝzYPXTƽ>&YPdoyĵxkfoѿ`q{dk^[ڌ8MUXy^k]Kh\hmMppCRipXPv}ײzhYjjab$d&!c]blY~v}پ|fyTdiMn>XUfgɷDYjcpnfElbE\cecjpyǰ|M|eceze&MM_>kcZUn~߾~shl:hݢ{K\[zqyzmSPrmweψAO`^bdgnuyz}ȗd}ӑe&4[pDw{wb^S|wvs~xdF=QG_]lTOuwھTBRY]`belqqryȦxʻjmYvp& PhR_kZs^xx~{tHoi{lsg޴^:`doyNFuq;TU\_abhllrD_zZgoDǾRp&4P;vRo[Zxe}hobctWa[{b|`]GTosOcx4Mupޗ5CNUY]\]cgkvyaճPh˿#f&nQcDeO`wP_WaҋupRqbl^hYoKLKXApuwȌi:\syaA>:GMQWX[`gkwo[ֆJg_աWp&;dFak_{kDSxϗvGbyhecڅDrP\7lsyՍHKh}jy2MI=;>DR__flowsSNĿQV`-p&.^`}X}h4kuxHDuÅ]rEmvkZҝPg{[folB_qyZ߫Tiwa/Gm^.+/6F[ekpx{^M{_jݟcEo`Vp'ԬL}THIuq~xR6wakrQbVŗ`M_Gu}Ek]jypcҐpz`R8ERG5<[uI:MYfizv[֙q۴oSn`o"p'PBzoSDw_}xW4ld_slDDă_RQL[Sf亏up|]ۚ]̮|kg/HA>@WwD):NW\`e\Е`|STvV0p&ş=e~zqYb٬MrxV4hϼqgs_JذiRٙ_ƙHs)DC>OsxQn@++BIEJ_f]kOUaoU p%*SЍ3X`oyvwĵ?bxU4fχeezhBlBWdPJFԹefū^gת̚SwR5EB:Cr8dVC@BpE`~ZN_Aj1p'L}d`|ooui@]nU3cȼ{`gUzCEXJDNl¯q`bŒھzBFTKDHnUlqloCPsx5Z͑@y[EQp*.Ox~{WS]kV3ZQQij\/LA@9MpYyRth|?G[ISSuĐhQRe.KwfbjZ&p*bkõsSqTZmU2*%%98G\wcUɲgħncرiQOrfwl[i^/9YbVPDo@XzmAI4c̘PXUe>n_KNc`V`jmmbL987;|VSl@C-1Yɼ~ndeS\waCO_`X\jttmcKEKAlf90,1@LJC=52574-('+J΀H݅^VfmH|ij}527;;9410&6ӘpnNLRVM9zWwʻt\QwkJ53;@A=73/1-<ԬYpHv^XEGn|ȪuK`oH8Nyۨ2oGFQFi\:xZcǽJILhnADH3K`O=A@Ew~LۆWucB+55mȳsHhboY}˯@qpK?Cnquy۽KaY;qihqeiտ}6=ha\UKEBI\x36:CHMOMIEFGKKD@H֙CmgE8X[PYw]aôOqyU6Jz[YKn6]w\]9^^UNJHCDKZzmX(=KTVRPONQPUSJM`a=DF@DABCE~Ơ`P{HY*wW,tLetU|=QvҩaAW[QKCIE>?Xqo9EPZ_[WY[\bTKRGDFKIMLGHJKCFӫqMrqepXTXor>BMen_HJpkƬƅүiSRETUKKML?;8HU_n]tbYX[^eafgnaXZYSRTUSQMKOPNIAbܴTfqhvkSX[Eo_/ZWdm[ODlpd\ldWjUFkNzSD;TPLOQPKKR^ckuОjXfjpvuvreYSUVXUOOTXVOP:wཊ]eRbo{D[N2oN-^{iatjZDkxxb\nttnXygYsbWHf6QMRSW`egloww˶œXnr|{rg_\][WSU\^^US=ckbV]WYdDUo;'{í~k@hU{hHotXjl|r[XWAi{WQ)OLRU\aidu[]ɇNJWuwsiec_\Z^eeaXZ9so?Qp[bmr>p5%sлnFVoWi`as}~mR[ehQ2IQVX[_jWT^drKIұu[vskgb_bjmi^][=ݶuJ^yYmp{g"p.#jȤ{tѨxiY{blNZsUBSZYXdthwkvZztZϊUމd}sjc`eoneZfL~ˏ{MRUX]@hup'$n}ɦѸ`Ikzv{k\de߀Q}POORYon]eEOyVђlfxld`cjhZ[TwòO:mzETZSϠ7RXp$qG8ʷaI\jlkthxX\mh?gvFNOOLH6BI^`L[]LgZsm{lc]_`ZPSmqbdkQj2Ev:p%`W=8˱zN_uddZthW]nRєWYhm^a}q8DXYLDGLNP}~xeq~ymb\ZWQJey]Hvȼd8=cp';7չYcgPU[u\Xeg^ckGgOʫx_MGPXp|oQQ|y~tl`[XORRTYböS@;>p&/o̹xpXE1_tfXhpw`VuX۶kuơz^^krwvTRM{kwd_ZSLWZiSHWºnL<1 p&IbdeglpzzeQD>DB0 *V9}·q׬k|auLBGPT\QOďpz_nѷA-OT,Okwb@HckceUOA&L%?cǥzȿSLGMfjw{nA8C?Wsȇ\d|3M{R+CL:!H_W,G*$'&'V&%%(*'#)T\fm]Xrjg}[McƞJMo^A89:Ob\;""&)'&'&%(%L29Zkbhpwȴilvqʽ\l[spdXWS]NDB!% L3ES^uֈlsmt]Pwg|&.CZSA1#!%$ 4 3PliMecXogwȼu;LPeio~ 7'(')+7:.8[NbŶ{lZShLz  *!;::AHfmJǻY>Kmw}ν. +5RH=EMPijOW|x||ʴ? *6icB.YͽyvK\v}S #VvE,jʶ?qY %)hr[;ST #UsJ5 !aw[Ƿ~[   0[Rqó|somkh\> !(;aG}{iTF?>;)   5RrvvbO<7<=,   9[gf_RRS;   *5+ ۗ G*$'&'%& )'/EZ][7%(&&(%!0HOQI3#&*)($%&'(&'#-& A~}WDX2 /UgjUX`N% ***lgFaR9$Sjm{|avT$ &%*bo}}>]aM+.RVIYricg9 '/(`' KhwSOF#,NoIMDbNHLpu[r{$Zw-K;&!{ǐ4֒& 2VGSSG=30\]VJFP~Ĺtj{|O)*4g}zt:&EJxEAXJ-FcVKHE^oúw{xxQq&-Jmpȧj&G %Ilx^0&SYIGCGmdg֓ntzA",  Em}̫w&&L %>AgyG*VMBGJIQYoxknzդog|8&P +2S]%#K#-CFM^pZfjD%#(]~Jøb|Q &N 7Pa, =jM!7'?JRbujUb~uLO|q(¶vӒ_&O'Vd:!&OeiսȌ$@IWe|˝RZkړp~6| ǿ⧙t &Q*uԻe@1H̴n`RӾ~&JK[mYdP^tȰRAB6̾+8ĸL,SNdmQOrlx:gS^_ccgnuyz}͇FҎe&53`ٟ|q{2 !@b^m\UOwwݹ1:UX]`belqqryʙVȺ}JEDS__flmur( N`;2p&y}ۯtxHCxg"oՕ/D\:aol jdyB!(06F[dft{}C  Df%"Rp& vsrxP9o{%" sǖZ!3k\kxL  ;˒oz6!#1;[vH;B-R~s/ +ٴpS$`+p&}w`{xV5l@ " ^~c*HE幏up{F&˰R&$>CVu@$# .DG1zVCY56p& {yyqzݧmuY1kmB04~ObAU8װTƘ95LsX+i3$211.]? IOUceTp& "MUboynuðm[0kֆg AXfQH6ԹP!ש ,+"/~GPC.s'uYN[Am2p' 7)W|nozpV1e堎d=DFWJDOlR 7ܽ¿T H;J. V_Zy4WΏ?zZDQ p*/N{ϧmV4YXs"[].M??9Mqņ5[3H# ^ 3Pc#@wf`k[+p+fuIij֩kV)ĸiD/b¼qZE"2"y]s48DB>B=CuNVرڃ'hnlˬc  @3o7`__ADG͔{b\U? *UL#/ }S0׫; )sfwןW)-GoBV{a!Qˏqy`bS`ghndF|Wfd6/=JQJB:4+*.:FSfj RHqXjaݘa,*sbPoLKO$GʺҜl_ra`XZkuulf=e’w_bP%4?LJC=52574-('+J́6}h ,rUgӁW'"8.<]{~Z: 3MI>33;?A=73/2-<ӯv#hN:4F&>ɪuK`PA+Xۨ2oH?'bG |C=ƿNtd(DaN<<"P_PE>Qo༡vD<@A;1;BEEA=9484HĬ@konI('ŲXN{ʧH8[ի7oWL$u>({joӇPnzH:9*ywD"?d\VH@EXwοiC,3;@HJIDA>=A@Dzm'ubA*1<$ɳsHi&1`{˯@omn40slq{߰4X]΅yfl18ia\UKEBI]xÄ06DGMOMIEFGLJE,biD9>>@IPYI>s´OHv{9H{dwmM͗]9^^UNKHDCJYz?:LSVRPONQOWJ#8F@D@CAGtƠ`Pyt UXqxc#tv{HAW[QKKILLTXXs;DP[^[WY\Ze?3HFIHMLGHJLCGӬoQe ǾXqt,7#$IETUKKMO^Q_p`ZV[_eaferQ>NUSQTUSQMKOONI@bܴ}X[- l߼JqbG6-PzٽHbmaeMKF &M+tͥqǿpfnuzxfcs溨љy'nS#3=" GOP$G*$'&'V&%%(*'%:`}ίogvy̙fb|Ц{Ġ+ [_?$Ka]:"#&)'&'&%(%L"3fvhegpw˼‡v}iY(>VPDA#% L3ER]uٶe}(LT@1"!%% 4 ,Jjo~ 7'(')3AHRdpĵxz  *5-6;bŲDz}zyʴ? ),hdH-EʽX%Ew|S (UBeƷqZY &%r`eT # On\5 !$dtZǶ~[   1[Rpų{somkh\> !(;a.t{iSF?>;)  "+JpuwbP<7==,   5gkd`POS:    */% ۗ t8mk@   U+k`% %o^l6 F  Fn` \q La0 >T& XJ5v) 5"1A Ts @ u 4<* aD^BN3# W{ (io9 y /Fg q r | v>D. U &L62  Q )]zF8!SpI7|@Et|sG  6r¿S Q  J|?  k[  dy =Ch"jYu!l!A-7]/5GicnV Cworkbench-1.1.1/icons/windows/000077500000000000000000000000001255417355300163135ustar00rootroot00000000000000workbench-1.1.1/icons/windows/workbench.ico000066400000000000000000002143661255417355300210050ustar00rootroot00000000000000  & (( @   ggg777BBB<<;===<=>=?@??@<<<333LNVUMNQK=<<//0: OLI B<<< hVM\oJIE@@AIIJ 6N / P@ev95dgglkk.-+DCC ]]] hhg``_URR2'!b -38?61`dlzG)&UC9VTTw||DW7,emkAHg5 vZ[`5aeh_\Zsts###J ,01\B=sg@6 gF>RXtG-9k[M%%,1!9BBE .--h>;7џodW0 z$ 3P#O#(c>+7 C*L&#N@@DN !4 <wpi 03>\TQROTdOgO9)(9.I dUs%\-z@XNE76:=(&%!4"cGR*/4|u ?DQWXU>D[;(,E:3oc[##$Q:mJCCG2+&&; W3z# /vJ-1 IHP$MPg IB <#3_`\Z?GiC?>w|kH5  ;.Plo-+)8O&8Vp[ JQax R/>|sWFN '  ( 0mtyrm ***pv|L4lLy$'5OHA]PWMW[lUB5Zvz}ZM- | G>z2<%  2BDOC\p>BO1 ]$? ?1-_NCL45$)[06JR&Pvlp , *)WH O 1>N}_[W !"!+;z$%!7MQV@-!o)O +=IFJ1)-1IA=3H #!#Wi PJLvx#%+ #0-B39%C?9pk+p9O[:312T!:w"Nh)7V"%*<7.ep-t.LzQOV ##" !m9Xl5B VVX_c%w}}Ta - !;; ,3UUUR:ZZZggg``accc`aaacc_]Z[VR}pd*|yv;ZZZmlkBkkiQPQVW\`ckolkVWWg%o P  /3H;(   OOO\\\VVVXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXZZZZZZXXXXXXZ[[XXZTVWZZZghp5opwGccaUUTUTU[[[\\\XXXXXXXXXZZZXXXWWW\\\OOOXXX]  /XXX]]]- #%#" "# 7]]]XXX $)+)-.0/.-  AXXXZZZl L-4Vd`V_`dkigOOO333$$% !ZZZXXXu)*+XXVHR|vzsssUUUBBB333,,,)))'''%%%### !XXXXXXY -/0::9stswyy]]]EEE888555;;;EEELLLIIIGGGJJJXXXXXX"=<=(,.A;8vwyvvwgggPPP>>>777>>>HHHWWWcccddd```VVVLLL j]]]XXXh+#!G+! e}|||FW`cl]\XLMOBAA999;;;III[[[lllmmmiiihhhZZZ[[[]]]XXXC6.+8!%-sA.toywworphmvvo`*< *FC:9:>>=;@@@JJJ\\\sss}}}sss```dddVVVXXX]]]XXX= #$' GwyzgddV\]1dE3.zd/.-345?>=JLL\\\tttcccVVV___XXX\\\]]]XXX  d   \yrstZWVEDEr73340-231135898GHHZZ[ttttttFFFZZZ]]]______ y]]] XXXk     "#N'FdegIFE08:P ]<'!9/->40EIJWVUsss>>>III___ccceddQRR[\\\XXX J|   )TwZQR5vwwa``NNN<;<0338&#TgV#m[E>_]\wvt222???[[[aaakiihhhFEE?XZ\  XXX Q %&'@BCMMMHHHGFFEEEL$U[!a r)#/<N2}cehONNJJJGGF=??9646@EI-#v w!$ )))555[[[___llkoooprp?@A\ZU  [[[ 6r---JIHkhhzww?&E ,>A'- wkgeGHI>>>EEEMMMHHHCDDECCMPQdG?wB/N>/}!##433XXXZZZeedpppttt;83 211 SRQQE"A .#%(!###)))///799MNOeikopp}yytmhep<'|$ & .A*>FL.*(467GFFVVVUUULLLPPP[[[hiiy||&y,$),>;:WZZQQQ]]]gghvvtvto 4} H& t|sG 7|489L>97m:(F"JJJZ[\ikipstprmdmmC,6Q[`7CH"%%#%345LJJ___ddcXXX\\\lll}}¼]Ba3 HLMQOOMMNXXX___oop}|zZWQZ)kC>3  * / OMJNNO?>=# @p- V k_[``a*26dpwrvw`[Z_N9) ""!(%$-/023307 QJJEFEHHIVVV`__kkk|||1370(3a@@= " - /?@Ba`X>EaF$I! 343rlk}ywi]+*)|wi\%A3\X_OVWBCC>-(MMC>PdvkdBFZ|)2(:3@BJ 8 %9-/3)  '%$ %!!   SF()(CCC``_eggehkiggokhhca%sQUX__]iggwwwRTT--1!P 81=m|hotzs]Wp`[||}tvv`a`CCI!'6%-   z Q !!"##"'&')))222221 !!   , kUmO%h0,/2=EI3hI %#)< haooo* 'WBW887*))023E?0  JTw|zr@@@ %%#(*3mki}vv6"yvoRUUM#P 5 $%)    ,)'BBBQRQZ[Ztstv|}TNMH0(?BC())3eD:gM-"*# $)/z/΅lȅpO6;%-tL?my|wN%;-O(V[]/23$%&++*.//686#.c@P333%%%/// =A@ze]$ka|r}r=,UCBGF>)} 6ir333111555---&&&3>AA%<8zro|||zwv[__IEF 8   &". HGGNOOHHGPPQttthprN2+t[O- -:9L#P  <("X*L59$O#%') 445P*O /errzC40?B@859OM?26d@0!vvv***555CCC@@@111'''!#18N9.dloga`hiiosv|}}ki_ H  L %*5  LNOyww___IJJOONoop}yvLUZDr%D#&%-#0>L[7L &'*:#E# # v./  3}t}g#aC9\deZW\NXN<>v,lLVXZZ''':::EEELLLIII333&&& 0'%*  %%R[_LHFDIJTA9XTTdehvmIL] /./  16<:(!L4-aacJIIGHLgegyzzedd=BB0|%2.3@PPQX[T%/ "! T[caD7w)4:$))O(<!18eOImlmrmI.|gc7&-EĪNMI!"#999AAALLLOOOBBB000&&&  %%%###./0rO 'ZekCAA>2-|}miNZ 3@@A %H\TItty\_aNME-3F-rcdhZZX/00%$$3Zc[B8;RWO8\ #&&_3;CE>,% 4-,#/%F3.E92 wo- !Am}@@B 445<<-(1=BA:7@@?T>8=e<>PA;---0//>??BBA@@?667,,,$$$!!! !!!!!! *,-¼IJL1)%M B)E6UW'EoI+ <vFwsm}}|acdHHD137B>.$w- %URMBBC.r%|00(-}-v(mE1+L#v,eZ "# !##&%&',)'-/00//9AE]WVdTwwth]MJE'(),+*333997778236++,### GFG#%%(GA ;V9 >7spoz||}}gggJLI=<=>>@  'UTR>>?2g&) & %D/%2C(opp X?% %%%/--CDEtB/mVWWWDCDC?=;;:%%$!"#%%$))*..---2..--+%!!  ///@AB!#:9-)}3`#*I$@ -+)  lllrpoTUUFFFGJP!(FMc;:78v)7%J'iV<iQmh$9vvtF R @  )((@@VQPkw|ZH)ACm6% ! ZD 874kllihh)*+!+.i:+* !_)48UN5ϭ.123 322NMMLLI=<>GFFUZe 9/;v'(# X?/\FOUX979667IGHieezw 222]\\Ծ]$'  1kX8\a A #    -5QlzZZZ134]UOZNPaA-=H7$}:rc`}}쵳')-( -,( &*8*-;31-ABCrpgo PNI v*>P$#PBNDDD(#\Cp-!2<:4 - # 2113MMMJLJ//-  g t  ///QRROPP$#"    2/-'5pM=?AE--- !   B'sst]]],,,$#$LLLNNNlmi)|342ypmmmm  | Tp$<E Z30(d ľwkha:::- :#;@V# (' Q_4/  8 )(%GGGrrr[ZZMMM...     ??>ipz $a%2d<92))((% O.ҺrstIII334MMJMNOc|B ']wzyhhi   r)p z eH Q#&24ywv[]`112/,,yL,%/Q7%% ' : 0 @   338ZZ`pml566     ?@Ami\.8`@)#.NJ r .(N 3gge---@AAGGGadc·)   1ddc q3dsa MF N_rvvVVV*('2/-B*1w#7J% ! !%%%)('!#) l 7 +,IIHwtsr!%'    ;;;¾QPJ )P(tX %2i/cw|CQ-=?E///JIIOQQ}|%i  UUU g&PMF#ac ORMVv}5UD=&() !=!/_+D %%$--*---&')"  2. 8!&:WVRIa/J"$     MOO87?mi< "'`431rs|#4 l+*)##"A@@TVWh`]֜lX8F[ Q[`k}}sRRO  F32/'F oc T WvP9F *!d (IUB18 ##%-#'7('%  & ! P t3>i3e}"&/ [     """<<:84PQRyyycZiF;GJOp:*?EI*#"#  X  QPrvB=*1:v mw,      --3AQQUhhi8[4'OD3pppidXZ[[U]|:M#)A('%&&'EDCgik}Ȋs5 )iso %C ;wVB Wy }(.4://3;:;QRRtttmtv[VTrB25"Zp#FsJ " 8 % wi W m!    G)0 %tw4mNy ",NH &zzv\aso$<0ؼ e>:-##&543\\]}dPi_imlEFL(,FP`'%%29o$B<=$#?BCRQOrrs}vyze& W o3# (31&ZW V 3    ! s# Gr-G}Īlms R>Qpy|*&%4|%.M43/)*,JIHtttytpr|twycch\[T#. yF-$-T(`#_Q#H5*&NTZihd|zzzzzyvODA#iO&&) $mw &&3h#hMTy0    ('%IN\2# [_rX} Z,agl-/BO_\heeeFED543:9:```mmoXWTIIHDEGPNM`\[iko]ZO 8 [84-i" O3-6%9DJ?:ckrtyzz||svv_[[:EIQ,XB* eM%', he_7"#t #; p$:  ***__`AQ+ )ļALz Qh[/BļQkcHgohhhEEH<==MMNwwwtvw431  #765JLMddassv@@= -zX-/1 WQ[zt0LL<4i]\|ywpooPPP755332CBC=c_lJc'%,  z||>-d,>m=72Z@$%}   *))``a|=N;1- #3 ")MZ9BR-G#FUydEFGJIEZZZ !# !//0MMMggezzyprp""!#CCG%%%{ C4 BD79=EIIABB333()))))2--865V$ye\mdcC \)(* ! l3Z=0EH? #=Pcz 27:I&&&TTTsm[*iG CVr2$ Tp>z(&*&)d|,hkiIMXVX\iii10) &997LLIkllFHH 21/]JDM))(11/(.1-/0;ADovz8 H'05 %" >!!=24=@6##,A@>EA5 "  -;=E||wQRXQOB!}"hT#:Oegrih`adm6R/& vc 3@53GB2GC8ABC,))[-5XPNG_`e! '%#3BXk6 3!158122**-2>OMMcim' 8+%-02 %##րe*#',GN ; %pt557!% N & RV[ooopLLI89=A>5$ikQN}PQVOOJPRX}mBXc,/!z % ,,/p0r$(4 #+>U% "iG%d_RmotWWW !)A)#-|hA<2-478/)(AUM/#rz}HHH345000 &&%7 H9' ,&_89t5:B 'Hd"|}}FFE---)))336@A8WRolk==?>>>NMM]_a3%#Fh J  ONHU\w-/9   -}v!dM6F}t2372.  BFgii;=<=.46C Z_d9+,--+**756000 )//_V%3++V=T1,F}NOP5M-_4vʼRU\%%%%%#015GC9)I] IGJ)))222DDBPRXv\o ] T`5 .-( -"$y52.,.:JQ*`hwttt)))()\N4k]}1?@B445;#_ m%mll---6656660000-+<#+9'.X%))>MVl#3@s")̾&V )*/=:0&1hQhBcA% $$1//BBAJLQk=T`zgG'D8  !#"%4-)(30)43-yw3%ĸVX_" U)% - hdFT+D><7DJF%l wU7iMQ]d530>AB;;;%%%50+O=#.<h1g#&! %<D_7+C\C (&"-,0==8:;R1D`<`]&# 346CBBRRTehv%h}#1g<@  ""!&&$')+-/1+,7EO>VQa?@@#]g: ^D%(#4 ew% R!EAAa/ G-CORGA>LLM876 @:4Bz-):?*Fy(&e#)CUƼ- ))).-.:<=;ABJvlR1 ķAH_  3 ###%%(0/-666EB;)oZ=#EJ_O[) # `.1Q)]z0!>wihMQTWXZNRU346%##%)*(V D-Q 6#/;h/3J=W|34F@G> 0ti /--=>=FEDVTR_ai)gtĺNOP  ###%%%..-=<;FHMa]U196w}s#;PNIid_a*#V)%'lw,8J0pd\iXR\;1>0+1584330-,_rgt}$)++$)3p&@,8#BX_t9PzJg $! %*)%015QPC-E#J_yy.--987AAAILPdc_ah}4L%*; ###&&&&&&+++567FECORWwk$> 2#R)WQE> DHQ8V)FC/3G/?X3'FLND><3:>O/&sky*./3,,5v0_ <P[eg B]hr '&(=<8DGl9/--//.67=JIFddc}z| !!!&&&******+++111:::RQOstyQa0}t4A%' ]M@:-./4VAQ-M %D `D(dio`ggGHI554?CCtwy3512,:7h#&T G#Vos<\@aļ(3Z22)$*\12 '++*&''0/2JG;JOe !=Zı#? 9   ###(((---111222444:::FFF}>O okkomaWae<93P4 EHT<  cly<_&yaZC;TBpd5LLL/..544hhh@?:23J"1 $mm([vsgֺ!: ) " =:5#y 3$%' *))<<:("&_ =P  !% +%%%)))111778>>>AAAEEEDDDppomorXXT+3U\.38+!4 !W/2u   8I)21-ty||vt|`U0%EOV%"&)*WVWGGLONL*-3T % VVU`dl(C d01&m%r #$##&>;3'w))>XD`!$',)! cp..-346?>=FEENMNTTT\\\ZZZrrrIJL62/%# .% % kOhiz z+LVc!_QIByy|z}||zeE4 16?DB@ZZZ`ad>IyRRWWUJOQQ! FIZda[tts " 366)* ##!336NNH !5\v #%!)*.74-?@DGHHLMMVVVa``lklvvvVVW320FFHzyw @*5D7R(-;ehcst|-Iz. " **.opptttrmgaac[\c_`gdgmhhdDLhGLZ[[WlloFV '/-()1UT31/CCFl_QN\L]"F '''357HE<'4o,:yke\acica]kkivvwttyEC@ 8gRHt||yvU0lP|  -6 #}GA7egl|zw0.L  tst||zwwrrtr(@R`}piiimlhrsy:P Z  %GC4, $ap)&%DLOTDC$ }v )1mI| 7#!#)*)556CDCPQVec\trhgishoe_W'/N,ghlwtrmy} sC);RQHkmtrh,#  OOR[e #z|}%w  ')/e`N TscM1::;_>3yyEE|+-888/99?GFFIMLJLNU5emz)@y}|mevy|wsm3> =th : elrzehk 3a($W5B!BP$JW6@FZRO|sloyCP 2 !!"333X\]V!%! >IMy7%J)56:|iGRO) ONUURJ3@s?UdQ;)hki9@GQ$ WO)cZ_QTQDBC---T1'@rk- ) M p=+VW[  *N)"|W}Hdyl8_ &%)-TRQ# s - -..GDCtvw1, % 521O]cG1-358*5h]o"GGa\[M7Bp:Qw. .>E=/[R I?A98]psJ6a" %')HDApy|aQ3" G"vA]'skdO5Am*zvNAF,34I.c336")% ! #1 Z&LMQaNwX  )''::;WXWzkk)10%(*(%%247WQNzR2_G!,&  8E\ms)0kccTOP[dadzywz}}popoig_TN9##|zz5(s 3 5 &0  9O:FD\R}('  #'):::UWZwtpH&J(-1-*)233ADGpoko kB&'7/&I} lZURPONWZZ_ltiehmihiedXVV9DI'034@Ehlr/58v X 0 9 "# !2F[ic+G  : )789VOLvwv}D3-/&%3530/3JG=0 2-!!l# 3-% @,687   )B  '7_*A \ ) C>) QakJ8E!) - +))333C?=QPPeeht|4w/38 &3 Np># /w|37Z]\Uc|G)6}I*G  ILM&>  1Fo=; a4 B[`<3s! )**+=>?P\`gllhymli++,)a'XWN\[h"4BR}i[Ivw##TXXX "!"@:v6; 2r#% /`zsii}e?2 //0IFE}:'meyvIDB /0-#,L)3g.3E #:L3\WWW)(#;*[83  8  E#XImvyCAA  333QNL# M8izd`_MMP:  !?1,_``ypZ-@R[ 7/5,HityysD><  XXXa%% 01 #&$A(#  #g&|8)?JM  433dilzr[WU2$%    +@EIUTVooi}Zozsrla]M:960XXX+,3;,37/94,3>GI607  , Ug I  321_gkg:)O   11/===AEF79:21/0/-003;=?HE<332)),$%'LXXX\7 V#I =024A;9=CF-  JPA!    #!AGL:  /& ')+><;UVVdaamlmikl__a\[XPOM?@D557q XXXn'F)#$"/347)#>%P8  %%%))''>1#243POOgegvvs|psvGIN###` ZZZF )-/4'#4  !!# +/0?&C $ "%653OPRvtry|}_dr#'5 FXXX^&J-d#  l%%%'&&369=863.00JIHZ[_OOI#6 [[[ 54 %#%oXXX U   + k `% lll   ?g3@3Ȁ"? /@@~P^wpq"0Ӏ/P@ PPPPPPPPPPPPPPP ++W@?PP +W_@ PKO(L3O@1 ;workbench-1.1.1/icons/windows/workbench.rc000066400000000000000000000000601255417355300206170ustar00rootroot00000000000000IDI_ICON1 ICON DISCARDABLE "workbench.ico" workbench-1.1.1/mac_plist/000077500000000000000000000000001255417355300154615ustar00rootroot00000000000000workbench-1.1.1/mac_plist/MacOSXBundleInfo.plist.in000066400000000000000000000041571255417355300222120ustar00rootroot00000000000000 CFBundleDocumentTypes CFBundleTypeExtensions scene spec CFBundleTypeIconFile spec_file.icns CFBundleTypeName Workbench Spec File CFBundleTypeRole Viewer CFBundleTypeRole Viewer CFBundleTypeExtensions border foci gii nii palette CFBundleTypeIconFile data_file.icns CFBundleTypeName Workbench Data File CFBundleTypeRole Viewer CFBundleTypeRole Viewer CFBundleDevelopmentRegion English CFBundleExecutable ${MACOSX_BUNDLE_EXECUTABLE_NAME} CFBundleGetInfoString ${MACOSX_BUNDLE_INFO_STRING} CFBundleIconFile ${MACOSX_BUNDLE_ICON_FILE} CFBundleIdentifier ${MACOSX_BUNDLE_GUI_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleLongVersionString ${MACOSX_BUNDLE_LONG_VERSION_STRING} CFBundleName ${MACOSX_BUNDLE_BUNDLE_NAME} CFBundlePackageType APPL CFBundleShortVersionString ${MACOSX_BUNDLE_SHORT_VERSION_STRING} CFBundleSignature ???? CFBundleVersion ${MACOSX_BUNDLE_BUNDLE_VERSION} CSResourcesFileMapped LSRequiresCarbon NSHumanReadableCopyright ${MACOSX_BUNDLE_COPYRIGHT} workbench-1.1.1/notes/000077500000000000000000000000001255417355300146365ustar00rootroot00000000000000workbench-1.1.1/notes/images.txt000066400000000000000000000007551255417355300166530ustar00rootroot00000000000000 Use a PNG image and place the image in the directory src/Desktop/images directory. In the same directory, edit the file resources.qrc and add the image. In the code, to load the image, use one of the "load" methods in WuQtUtilities. In the load methods, prefix the file name with ":/" which tells Qt that the image should be loaded from resources. Delete Desktop from your build directory, rerun cmake, and then build. This step must be done after images.qrc has been edited or touched. workbench-1.1.1/notes/mac_app_icon.txt000066400000000000000000000004151255417355300200070ustar00rootroot00000000000000 * Create an image file in photoshop or other program with a transparent background that is 128x128. * Save as TIFF. * Start Icon Composer (/Developer/Utilities/Icon Composer). * Using Finder, drag the TIFF image and drop it into the 128x128 box. * Save the ICNS file. workbench-1.1.1/notes/qwt_notes.txt000066400000000000000000000027701255417355300174300ustar00rootroot00000000000000 Setting up QWT for use with workbench In QWT's 'src' directory: Examine src.pro A configuration item (QWT_CONFIG) allow selective building of items. There are some HEADERS and SOURCES that we need. These are the HEADERS and SOURCES not controlled with QWT_CONFIG, and QwtPlot. QwtSvg and QwtWidgets are NOT needed to copy part of the src.pro file to create a command for deleting these unneeded files. Something like this: #!/bin/sh rm \ qwt_plot_svgitem.h \ qwt_plot_svgitem.cpp rm \ qwt_abstract_slider.h \ qwt_abstract_scale.h \ qwt_arrow_button.h \ qwt_analog_clock.h \ qwt_compass.h \ qwt_compass_rose.h \ qwt_counter.h \ qwt_dial.h \ qwt_dial_needle.h \ qwt_double_range.h \ qwt_knob.h \ qwt_slider.h \ qwt_thermo.h \ qwt_wheel.h rm \ qwt_abstract_slider.cpp \ qwt_abstract_scale.cpp \ qwt_arrow_button.cpp \ qwt_analog_clock.cpp \ qwt_compass.cpp \ qwt_compass_rose.cpp \ qwt_counter.cpp \ qwt_dial.cpp \ qwt_dial_needle.cpp \ qwt_double_range.cpp \ qwt_knob.cpp \ qwt_slider.cpp \ qwt_thermo.cpp \ qwt_wheel.cpp Next, copy the src.pro to CMakeLists.txt. Run "grep --files-with-matches Q_OBJECT *.h" to find the files that need to go in the MOC_INPUT_HEADER_FILES section of the CMakeLists.txt file. Next place all headers and CPP files into the SOURCE_FILES section. workbench-1.1.1/src/000077500000000000000000000000001255417355300142755ustar00rootroot00000000000000workbench-1.1.1/src/Algorithms/000077500000000000000000000000001255417355300164065ustar00rootroot00000000000000workbench-1.1.1/src/Algorithms/AbstractAlgorithm.cxx000066400000000000000000000033041255417355300225440ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" using namespace std; using namespace caret; AbstractAlgorithm::AbstractAlgorithm(ProgressObject* myProgressObject) { m_progObj = myProgressObject; m_finish = true; if (m_progObj == NULL) { m_finish = false; return; } myProgressObject->algorithmStartSentinel(); if (myProgressObject->isDisabled()) { m_finish = false;//don't let a subalgorithm finish the progress bar if the main algorithm ignores it } } float AbstractAlgorithm::getAlgorithmInternalWeight() { return 1.0f; } float AbstractAlgorithm::getSubAlgorithmWeight() { return 0.0f; } float AbstractAlgorithm::getAlgorithmWeight() { return getAlgorithmInternalWeight() + getSubAlgorithmWeight(); } AbstractAlgorithm::~AbstractAlgorithm() { if ((m_progObj != NULL) && m_finish) { m_progObj->forceFinish(); } } workbench-1.1.1/src/Algorithms/AbstractAlgorithm.h000066400000000000000000000041701255417355300221730ustar00rootroot00000000000000#ifndef __ABSTRACT_ALGORITHM_H__ #define __ABSTRACT_ALGORITHM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ //make it easy to use these in an algorithm class, don't just forward declare them #include "ProgressObject.h" #include "CaretAssert.h" #include "OperationParameters.h" #include "AbstractOperation.h" namespace caret { ///the constructor for algorithms does the processing, because constructor/execute cycles don't make sense for something this simple class AbstractAlgorithm : public AbstractOperation { ProgressObject* m_progObj;//so that the destructor can make sure the bar finishes bool m_finish; AbstractAlgorithm();//prevent default construction protected: ///override this with the weights of the algorithms this algorithm will call static float getSubAlgorithmWeight();//protected so that people don't try to use them to set algorithm weights in progress objects ///override this with the amount of work the algorithm does internally, outside of calls to other algorithms static float getAlgorithmInternalWeight(); AbstractAlgorithm(ProgressObject* myProgressObject); virtual ~AbstractAlgorithm(); public: ///use this to set the weight parameter of a ProgressObject static float getAlgorithmWeight(); }; } #endif //__ABSTRACT_ALGORITHM_H__ workbench-1.1.1/src/Algorithms/AlgorithmBorderResample.cxx000066400000000000000000000170761255417355300237220ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmBorderResample.h" #include "AlgorithmException.h" #include "Border.h" #include "BorderFile.h" #include "GiftiLabelTable.h" #include "GiftiMetaData.h" #include "SurfaceFile.h" #include "SurfaceProjectedItem.h" #include "SurfaceProjectionBarycentric.h" #include "SignedDistanceHelper.h" using namespace caret; using namespace std; AString AlgorithmBorderResample::getCommandSwitch() { return "-border-resample"; } AString AlgorithmBorderResample::getShortDescription() { return "RESAMPLE A BORDER FILE TO A DIFFERENT MESH"; } OperationParameters* AlgorithmBorderResample::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addBorderParameter(1, "border-in", "the border file to resample"); ret->addSurfaceParameter(2, "current-sphere", "a sphere surface with the mesh that the metric is currently on"); ret->addSurfaceParameter(3, "new-sphere", "a sphere surface that is in register with and has the desired output mesh"); ret->addBorderOutputParameter(4, "border-out", "the output border file"); ret->setHelpText( AString("Resamples a border file, given two spherical surfaces that are in register. ") + "Only borders that have the same structure as current-sphere will be resampled." ); return ret; } void AlgorithmBorderResample::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { BorderFile* borderIn = myParams->getBorder(1); SurfaceFile* curSphere = myParams->getSurface(2); SurfaceFile* newSphere = myParams->getSurface(3); BorderFile* borderOut = myParams->getOutputBorder(4); AlgorithmBorderResample(myProgObj, borderIn, curSphere, newSphere, borderOut); } AlgorithmBorderResample::AlgorithmBorderResample(ProgressObject* myProgObj, const BorderFile* borderIn, const SurfaceFile* curSphere, const SurfaceFile* newSphere, BorderFile* borderOut) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); SurfaceFile curAdjust, newAdjust; adjustRadius(curSphere, curAdjust); adjustRadius(newSphere, newAdjust); borderOut->clear(); *(borderOut->getClassColorTable()) = *(borderIn->getClassColorTable()); *(borderOut->getNameColorTable()) = *(borderIn->getNameColorTable()); *(borderOut->getFileMetaData()) = *(borderIn->getFileMetaData()); borderOut->setStructure(newSphere->getStructure()); borderOut->setNumberOfNodes(newSphere->getNumberOfNodes()); int numBorderMDKeys = borderIn->getNumberOfBorderMetadataKeys(); for (int m = 0; m < numBorderMDKeys; ++m) { borderOut->addBorderMetadataKey(borderIn->getBorderMetadataKey(m));//rely on the keys being in order added } int numBorders = borderIn->getNumberOfBorders(); CaretPointer myHelp = newAdjust.getSignedDistanceHelper(); for (int i = 0; i < numBorders; ++i) { const Border* inputBorder = borderIn->getBorder(i); if (inputBorder->getStructure() != curSphere->getStructure()) continue; CaretPointer outputBorder(new Border());//in case something throws outputBorder->setName(inputBorder->getName()); outputBorder->setClassName(inputBorder->getClassName()); outputBorder->setClosed(inputBorder->isClosed()); int numPoints = inputBorder->getNumberOfPoints(); for (int j = 0; j < numPoints; ++j) { CaretPointer outPoint(new SurfaceProjectedItem());//ditto float coord[3]; const SurfaceProjectedItem* myItem = inputBorder->getPoint(j); if (!myItem->getBarycentricProjection()->isValid()) throw AlgorithmException("input file has a border point without barycentric projection");//because we never want to use van essen projection or straight coords bool valid = myItem->getBarycentricProjection()->unprojectToSurface(curAdjust, coord, 0.0f, true);//should really be "from" surface - "true" makes it not use the signed distance above surface, if present if (!valid) throw AlgorithmException("input file has a border point that is invalid for the current sphere"); BarycentricInfo myBaryInfo; myHelp->barycentricWeights(coord, myBaryInfo); outPoint->setStructure(newSphere->getStructure()); outPoint->getBarycentricProjection()->setTriangleNodes(myBaryInfo.nodes); outPoint->getBarycentricProjection()->setTriangleAreas(myBaryInfo.baryWeights); outPoint->getBarycentricProjection()->setProjectionSurfaceNumberOfNodes(newSphere->getNumberOfNodes()); outPoint->getBarycentricProjection()->setValid(true); outputBorder->addPoint(outPoint.releasePointer());//NOTE: addPoint currently takes ownership of a RAW POINTER - shared_ptr won't release the pointer, so this function would need to be deprecated } borderOut->addBorder(outputBorder.releasePointer());//NOTE: again, ownership of RAW POINTER for (int m = 0; m < numBorderMDKeys; ++m)//HACK: will do this repeatedly for multi-part borders, but oh well { AString value = borderIn->getBorderMetadataValue(inputBorder->getName(), inputBorder->getClassName(), m); if (value != "") borderOut->setBorderMetadataValue(inputBorder->getName(), inputBorder->getClassName(), m, value); } } } void AlgorithmBorderResample::adjustRadius(const SurfaceFile* in, SurfaceFile& out) { int numNodes = in->getNumberOfNodes(); out = *in;//easiest way to set topology, etc CaretAssert(numNodes > 1); int numNodes3 = numNodes * 3; const float* coordData = in->getCoordinateData(); Vector3D tempData = coordData;//gets the first 3 floats float mindist = tempData.length(); if (mindist != mindist) throw CaretException("found NaN coordinate in an input sphere"); float maxdist = mindist; out.setCoordinate(0, tempData / mindist * 100.0f); const float TOLERANCE = 1.001f; for (int i = 3; i < numNodes3; i += 3) { tempData = coordData + i; float tempf = tempData.length(); if (tempf != tempf) throw CaretException("found NaN coordinate in an input sphere"); if (tempf < mindist) { mindist = tempf; } if (tempf > maxdist) { maxdist = tempf; } out.setCoordinate(i / 3, tempData / tempf * 100.0f); } if (mindist * TOLERANCE <= maxdist) { throw AlgorithmException("input surfaces must be spheres"); } } float AlgorithmBorderResample::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmBorderResample::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmBorderResample.h000066400000000000000000000034271255417355300233420ustar00rootroot00000000000000#ifndef __ALGORITHM_BORDER_RESAMPLE_H__ #define __ALGORITHM_BORDER_RESAMPLE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmBorderResample : public AbstractAlgorithm { AlgorithmBorderResample(); void adjustRadius(const SurfaceFile* in, SurfaceFile& out); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmBorderResample(ProgressObject* myProgObj, const BorderFile* borderIn, const SurfaceFile* curSphere, const SurfaceFile* newSphere, BorderFile* borderOut); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmBorderResample; } #endif //__ALGORITHM_BORDER_RESAMPLE_H__ workbench-1.1.1/src/Algorithms/AlgorithmBorderToVertices.cxx000066400000000000000000000320031255417355300242240ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmBorderToVertices.h" #include "AlgorithmException.h" #include "Border.h" #include "BorderFile.h" #include "CaretAssert.h" #include "GeodesicHelper.h" #include "MetricFile.h" #include "SurfaceFile.h" #include "SurfaceProjectedItem.h" #include "SurfaceProjectionBarycentric.h" #include "Vector3D.h" #include #include using namespace caret; using namespace std; AString AlgorithmBorderToVertices::getCommandSwitch() { return "-border-to-vertices"; } AString AlgorithmBorderToVertices::getShortDescription() { return "DRAW BORDERS AS VERTICES IN A METRIC FILE"; } OperationParameters* AlgorithmBorderToVertices::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceParameter(1, "surface", "the surface the borders are drawn on"); ret->addBorderParameter(2, "border-file", "the border file"); ret->addMetricOutputParameter(3, "metric-out", "the output metric file"); OptionalParameter* borderOpt = ret->createOptionalParameter(4, "-border", "create ROI for only one border"); borderOpt->addStringParameter(1, "name", "the name of the border"); ret->setHelpText( AString("Outputs a metric with 1s on vertices that follow a border, and 0s elsewhere. ") + "By default, a separate metric column is created for each border." ); return ret; } void AlgorithmBorderToVertices::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { SurfaceFile* mySurf = myParams->getSurface(1); BorderFile* myBorderFile = myParams->getBorder(2); MetricFile* myMetricOut = myParams->getOutputMetric(3); bool borderSelectMode = false; AString borderName; OptionalParameter* borderOpt = myParams->getOptionalParameter(4); if (borderOpt->m_present) { borderName = borderOpt->getString(1); borderSelectMode = true; } if (borderSelectMode) { AlgorithmBorderToVertices(myProgObj, mySurf, myBorderFile, myMetricOut, borderName); } else { AlgorithmBorderToVertices(myProgObj, mySurf, myBorderFile, myMetricOut); } } AlgorithmBorderToVertices::AlgorithmBorderToVertices(ProgressObject* myProgObj, const SurfaceFile* mySurf, const BorderFile* myBorderFile, MetricFile* myMetricOut) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); //TODO: check structure against surface set > borderCounter;//"multi-part border" behavior int numBorderParts = myBorderFile->getNumberOfBorders();//total number of parts for (int i = 0; i < numBorderParts; ++i) { const Border* thisBorderPart = myBorderFile->getBorder(i); borderCounter.insert(make_pair(thisBorderPart->getName(), thisBorderPart->getClassName())); } int numBorders = (int)borderCounter.size(); myMetricOut->setNumberOfNodesAndColumns(mySurf->getNumberOfNodes(), numBorders); myMetricOut->setStructure(mySurf->getStructure()); CaretPointer myGeoHelp = mySurf->getGeodesicHelper(); vector used(numBorderParts, false);//tracking for "multi-part border" behavior int curBorder = 0; for (int i = 0; i < numBorderParts; ++i)//visit borders in file order (of first piece), not set sorted order { if (used[i]) continue;//skip previously used parts vector colScratch(mySurf->getNumberOfNodes(), 0.0f); const Border* refBorderPart = myBorderFile->getBorder(i); AString borderName = refBorderPart->getName(), className = refBorderPart->getClassName(); myMetricOut->setColumnName(curBorder, borderName); for (int j = i; j < numBorderParts; ++j)//we could store the matching info in the counting set instead { const Border* thisBorderPart = myBorderFile->getBorder(j); if (thisBorderPart->getName() == borderName && thisBorderPart->getClassName() == className) { used[j] = true;//now we can actually do stuff with the border part int numBorderPoints = thisBorderPart->getNumberOfPoints(); if (numBorderPoints < 1) continue; const SurfaceProjectionBarycentric* thisBary = thisBorderPart->getPoint(0)->getBarycentricProjection(); CaretAssert(thisBary->isValid()); const float* thisWeights = thisBary->getTriangleAreas(); const int32_t* thisNodes = thisBary->getTriangleNodes(); int useNode = 0; Vector3D curPos;//initializes to 0 float weightSum = 0.0f; for (int n = 0; n < 3; ++n) { if (thisWeights[n] > 0.0f) { curPos += thisWeights[n] * Vector3D(mySurf->getCoordinate(thisNodes[n])); weightSum += thisWeights[n]; } if (thisWeights[n] > thisWeights[useNode]) useNode = n;//get closest node to point } curPos /= weightSum; int curNode = thisBary->getTriangleNodes()[useNode]; colScratch[curNode] = 1.0f;//if there is only one border point, make sure it leaves a mark for (int k = 1; k < numBorderPoints; ++k) { thisBary = thisBorderPart->getPoint(k)->getBarycentricProjection(); CaretAssert(thisBary->isValid()); thisWeights = thisBary->getTriangleAreas(); thisNodes = thisBary->getTriangleNodes(); useNode = 0; weightSum = 0.0f; Vector3D nextPos;//initializes to 0 for (int n = 0; n < 3; ++n) { if (thisWeights[n] > 0.0f) { nextPos += thisWeights[n] * Vector3D(mySurf->getCoordinate(thisNodes[n])); weightSum += thisWeights[n]; } if (thisWeights[n] > thisWeights[useNode]) useNode = n;//get closest node to point } nextPos /= weightSum; int nextNode = thisBary->getTriangleNodes()[useNode]; vector pathNodes; vector pathDists;//not used, but mandatory output myGeoHelp->getPathAlongLineSegment(curNode, nextNode, curPos, nextPos, pathNodes, pathDists); int pathLength = (int)pathNodes.size(); for (int m = 0; m < pathLength; ++m) { colScratch[pathNodes[m]] = 1.0f; } curNode = nextNode; curPos = nextPos; } if (thisBorderPart->isClosed()) { thisBary = thisBorderPart->getPoint(0)->getBarycentricProjection(); CaretAssert(thisBary->isValid()); thisWeights = thisBary->getTriangleAreas(); thisNodes = thisBary->getTriangleNodes(); useNode = 0; weightSum = 0.0f; Vector3D nextPos;//initializes to 0 for (int n = 0; n < 3; ++n) { if (thisWeights[n] > 0.0f) { nextPos += thisWeights[n] * Vector3D(mySurf->getCoordinate(thisNodes[n])); weightSum += thisWeights[n]; } if (thisWeights[n] > thisWeights[useNode]) useNode = n;//get closest node to point } nextPos /= weightSum; int nextNode = thisBary->getTriangleNodes()[useNode]; vector pathNodes; vector pathDists;//not used, but mandatory output myGeoHelp->getPathAlongLineSegment(curNode, nextNode, curPos, nextPos, pathNodes, pathDists); int pathLength = (int)pathNodes.size(); for (int m = 0; m < pathLength; ++m) { colScratch[pathNodes[m]] = 1.0f; } } } } myMetricOut->setValuesForColumn(curBorder, colScratch.data()); ++curBorder; } } AlgorithmBorderToVertices::AlgorithmBorderToVertices(ProgressObject* myProgObj, const SurfaceFile* mySurf, const BorderFile* myBorderFile, MetricFile* myMetricOut, const AString& borderName) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); //TODO: check structure against surface bool first = true; AString className; int numBorderParts = myBorderFile->getNumberOfBorders();//total number of parts myMetricOut->setNumberOfNodesAndColumns(mySurf->getNumberOfNodes(), 1); myMetricOut->setStructure(mySurf->getStructure()); myMetricOut->setColumnName(0, borderName); CaretPointer myGeoHelp = mySurf->getGeodesicHelper(); vector colScratch(mySurf->getNumberOfNodes(), 0.0f); for (int i = 0; i < numBorderParts; ++i) { const Border* thisBorderPart = myBorderFile->getBorder(i); if (thisBorderPart->getName() == borderName) { if (first) { className = thisBorderPart->getClassName(); } else { if (thisBorderPart->getClassName() != className) throw AlgorithmException("border name matches borders with different classes"); } } int numBorderPoints = thisBorderPart->getNumberOfPoints(); if (numBorderPoints < 1) continue; const SurfaceProjectionBarycentric* thisBary = thisBorderPart->getPoint(0)->getBarycentricProjection(); CaretAssert(thisBary->isValid()); const float* thisWeights = thisBary->getTriangleAreas(); const int32_t* thisNodes = thisBary->getTriangleNodes(); int useNode = 0; Vector3D curPos;//initializes to 0 float weightSum = 0.0f; for (int n = 0; n < 3; ++n) { if (thisWeights[n] > 0.0f) { curPos += thisWeights[n] * Vector3D(mySurf->getCoordinate(thisNodes[n])); weightSum += thisWeights[n]; } if (thisWeights[n] > thisWeights[useNode]) useNode = n;//get closest node to point } curPos /= weightSum; int curNode = thisBary->getTriangleNodes()[useNode]; colScratch[curNode] = 1.0f;//if there is only one border point, make sure it leaves a mark for (int k = 1; k < numBorderPoints; ++k) { thisBary = thisBorderPart->getPoint(k)->getBarycentricProjection(); CaretAssert(thisBary->isValid()); thisWeights = thisBary->getTriangleAreas(); thisNodes = thisBary->getTriangleNodes(); useNode = 0; weightSum = 0.0f; Vector3D nextPos;//initializes to 0 for (int n = 0; n < 3; ++n) { if (thisWeights[n] > 0.0f) { nextPos += thisWeights[n] * Vector3D(mySurf->getCoordinate(thisNodes[n])); weightSum += thisWeights[n]; } if (thisWeights[n] > thisWeights[useNode]) useNode = n;//get closest node to point } nextPos /= weightSum; int nextNode = thisBary->getTriangleNodes()[useNode]; vector pathNodes; vector pathDists;//not used, but mandatory output myGeoHelp->getPathAlongLineSegment(curNode, nextNode, curPos, nextPos, pathNodes, pathDists); int pathLength = (int)pathNodes.size(); for (int m = 0; m < pathLength; ++m) { colScratch[pathNodes[m]] = 1.0f; } curNode = nextNode; curPos = nextPos; } } myMetricOut->setValuesForColumn(0, colScratch.data()); } float AlgorithmBorderToVertices::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmBorderToVertices::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmBorderToVertices.h000066400000000000000000000036011255417355300236530ustar00rootroot00000000000000#ifndef __ALGORITHM_BORDER_TO_VERTEX_PATH_H__ #define __ALGORITHM_BORDER_TO_VERTEX_PATH_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmBorderToVertices : public AbstractAlgorithm { AlgorithmBorderToVertices(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmBorderToVertices(ProgressObject* myProgObj, const SurfaceFile* mySurf, const BorderFile* myBorderFile, MetricFile* myMetricOut); AlgorithmBorderToVertices(ProgressObject* myProgObj, const SurfaceFile* mySurf, const BorderFile* myBorderFile, MetricFile* myMetricOut, const AString& borderName); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmBorderToVertices; } #endif //__ALGORITHM_BORDER_TO_VERTEX_PATH_H__ workbench-1.1.1/src/Algorithms/AlgorithmCiftiAllLabelsToROIs.cxx000066400000000000000000000123701255417355300246560ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmCiftiAllLabelsToROIs.h" #include "AlgorithmException.h" #include "CiftiFile.h" #include "GiftiLabelTable.h" #include #include #include using namespace caret; using namespace std; AString AlgorithmCiftiAllLabelsToROIs::getCommandSwitch() { return "-cifti-all-labels-to-rois"; } AString AlgorithmCiftiAllLabelsToROIs::getShortDescription() { return "MAKE ROIS FROM ALL LABELS IN A CIFTI LABEL MAP"; } OperationParameters* AlgorithmCiftiAllLabelsToROIs::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiParameter(1, "label-in", "the input cifti label file"); ret->addStringParameter(2, "map", "the number or name of the label map to use"); ret->addCiftiOutputParameter(3, "cifti-out", "the output cifti file"); ret->setHelpText( AString("The output cifti file has a column for each label in the specified input map, other than the ??? label, ") + "each of which contains an ROI of all brainordinates that are set to the corresponding label." ); return ret; } void AlgorithmCiftiAllLabelsToROIs::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { CiftiFile* myLabel = myParams->getCifti(1); AString mapID = myParams->getString(2); if (myLabel->getCiftiXMLOld().getMappingType(CiftiXMLOld::ALONG_ROW) != CIFTI_INDEX_TYPE_LABELS) { throw AlgorithmException("mapping type along row must be labels");//because we need to translate map ID to index before algorithm, but want a more useful error if the reason it fails is mapping type } int whichMap = myLabel->getCiftiXMLOld().getMapIndexFromNameOrNumber(CiftiXMLOld::ALONG_ROW, mapID); if (whichMap == -1) { throw AlgorithmException("invalid map number or name specified"); } CiftiFile* myCiftiOut = myParams->getOutputCifti(3); AlgorithmCiftiAllLabelsToROIs(myProgObj, myLabel, whichMap, myCiftiOut); } AlgorithmCiftiAllLabelsToROIs::AlgorithmCiftiAllLabelsToROIs(ProgressObject* myProgObj, const CiftiFile* myLabel, const int& whichMap, CiftiFile* myCiftiOut) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); const CiftiXMLOld& myXML = myLabel->getCiftiXMLOld(); if (myXML.getMappingType(CiftiXMLOld::ALONG_ROW) != CIFTI_INDEX_TYPE_LABELS) { throw AlgorithmException("mapping type along row must be labels");//check again, in case it is used from somewhere other than useParameters() } const GiftiLabelTable* myTable = myXML.getLabelTableForRowIndex(whichMap); int32_t unusedKey = myTable->getUnassignedLabelKey();//WARNING: this actually MODIFIES the label table if the ??? key doesn't exist set myKeys = myTable->getKeys(); int numKeys = (int)myKeys.size(); if (numKeys < 2) { throw AlgorithmException("label table doesn't contain any keys besides the ??? key"); } map keyToMap;//lookup from keys to column CiftiXMLOld outXML = myXML; outXML.resetDirectionToScalars(CiftiXMLOld::ALONG_ROW, numKeys - 1); int counter = 0; for (set::iterator iter = myKeys.begin(); iter != myKeys.end(); ++iter) { if (*iter == unusedKey) continue;//skip the ??? key keyToMap[*iter] = counter; outXML.setMapNameForIndex(CiftiXMLOld::ALONG_ROW, counter, myTable->getLabelName(*iter)); ++counter; } myCiftiOut->setCiftiXML(outXML); vector outRowScratch(numKeys - 1, 0.0f), inRowScratch(myXML.getNumberOfColumns()); int64_t numRows = myXML.getNumberOfRows(); for (int64_t i = 0; i < numRows; ++i) { myLabel->getRow(inRowScratch.data(), i); int32_t thisKey = (int32_t)floor(inRowScratch[whichMap] + 0.5f); map::iterator iter = keyToMap.find(thisKey); if (iter != keyToMap.end()) { outRowScratch[iter->second] = 1.0f;//set the single element for the correct map } myCiftiOut->setRow(outRowScratch.data(), i); if (iter != keyToMap.end()) { outRowScratch[iter->second] = 0.0f;//and rezero it to get ready for the next row } } } float AlgorithmCiftiAllLabelsToROIs::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmCiftiAllLabelsToROIs::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmCiftiAllLabelsToROIs.h000066400000000000000000000033431255417355300243030ustar00rootroot00000000000000#ifndef __ALGORITHM_CIFTI_ALL_LABELS_TO_ROIS_H__ #define __ALGORITHM_CIFTI_ALL_LABELS_TO_ROIS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmCiftiAllLabelsToROIs : public AbstractAlgorithm { AlgorithmCiftiAllLabelsToROIs(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmCiftiAllLabelsToROIs(ProgressObject* myProgObj, const CiftiFile* myLabel, const int& whichMap, CiftiFile* myCiftiOut); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmCiftiAllLabelsToROIs; } #endif //__ALGORITHM_CIFTI_ALL_LABELS_TO_ROIS_H__ workbench-1.1.1/src/Algorithms/AlgorithmCiftiAverage.cxx000066400000000000000000000247121255417355300233400ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmCiftiAverage.h" #include "AlgorithmException.h" #include "CaretAssert.h" #include "CaretLogger.h" #include "CaretPointer.h" #include "CiftiFile.h" #include "MathFunctions.h" #include using namespace caret; using namespace std; AString AlgorithmCiftiAverage::getCommandSwitch() { return "-cifti-average"; } AString AlgorithmCiftiAverage::getShortDescription() { return "AVERAGE CIFTI FILES"; } OperationParameters* AlgorithmCiftiAverage::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiOutputParameter(1, "cifti-out", "output cifti file"); OptionalParameter* excludeOpt = ret->createOptionalParameter(2, "-exclude-outliers", "exclude outliers by standard deviation of each element across files"); excludeOpt->addDoubleParameter(1, "sigma-below", "number of standard deviations below the mean to include"); excludeOpt->addDoubleParameter(2, "sigma-above", "number of standard deviations above the mean to include"); ParameterComponent* ciftiOpt = ret->createRepeatableParameter(3, "-cifti", "specify an input file"); ciftiOpt->addCiftiParameter(1, "cifti-in", "the input cifti file"); OptionalParameter* weightOpt = ciftiOpt->createOptionalParameter(1, "-weight", "give a weight for this file"); weightOpt->addDoubleParameter(1, "weight", "the weight to use"); ret->setHelpText( AString("Averages cifti files together. ") + "Files without -weight specified are given a weight of 1. " + "If -exclude-outliers is specified, at each element, the data across all files is taken as a set, its unweighted mean and sample standard deviation are found, " + "and values outside the specified number of standard deviations are excluded from the (potentially weighted) average at that element." ); return ret; } void AlgorithmCiftiAverage::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { CiftiFile* ciftiOut = myParams->getOutputCifti(1); vector ciftiList;//this is just so that it can pass them to the algorithm vector weights; const vector& myInstances = *(myParams->getRepeatableParameterInstances(3)); for (int i = 0; i < (int)myInstances.size(); ++i) { ciftiList.push_back(myInstances[i]->getCifti(1)); OptionalParameter* weightOpt = myInstances[i]->getOptionalParameter(1); if (weightOpt->m_present) { weights.push_back((float)weightOpt->getDouble(1)); } else { weights.push_back(1.0f); } } OptionalParameter* excludeOpt = myParams->getOptionalParameter(2); if (excludeOpt->m_present) { AlgorithmCiftiAverage(myProgObj, ciftiList, excludeOpt->getDouble(1), excludeOpt->getDouble(2), ciftiOut, &weights); } else { AlgorithmCiftiAverage(myProgObj, ciftiList, ciftiOut, &weights); } } AlgorithmCiftiAverage::AlgorithmCiftiAverage(ProgressObject* myProgObj, const vector& ciftiList, CiftiFile* ciftiOut, const vector* weightsPtr) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); if (ciftiList.size() == 0) { throw AlgorithmException("no files specified"); } if (weightsPtr != NULL && ciftiList.size() != weightsPtr->size()) { throw AlgorithmException("number of weights doesn't match number of input cifti files"); } CaretAssert(ciftiList[0] != NULL); CiftiXML baseXML = ciftiList[0]->getCiftiXML(); if (baseXML.getNumberOfDimensions() != 2) throw AlgorithmException("cifti average currently only supports 2D files"); int numRows = baseXML.getDimensionLength(CiftiXML::ALONG_COLUMN), rowSize = baseXML.getDimensionLength(CiftiXML::ALONG_ROW), numFiles = (int)ciftiList.size(); for (int i = 1; i < numFiles; ++i) { CaretAssert(ciftiList[i] != NULL); if (!baseXML.approximateMatch(ciftiList[i]->getCiftiXML()))//requires at least length to match, often more restrictive { throw AlgorithmException("cifti file '" + ciftiList[i]->getFileName() + "' does not match earlier inputs"); } } ciftiOut->setCiftiXML(baseXML); vector accum(rowSize); vector myrow(rowSize); for (int i = 0; i < numRows; ++i) { vector weightaccum(rowSize, 0.0); for (int j = 0; j < rowSize; ++j) { accum[j] = 0.0; } for (int j = 0; j < numFiles; ++j) { ciftiList[j]->getRow(myrow.data(), i); if (weightsPtr == NULL) { for (int k = 0; k < rowSize; ++k) { if (MathFunctions::isNumeric(myrow[k])) { weightaccum[k] += 1.0; accum[k] += myrow[k]; } } } else { float weight = (*weightsPtr)[j]; for (int k = 0; k < rowSize; ++k) { if (MathFunctions::isNumeric(myrow[k])) { weightaccum[k] += weight; accum[k] += myrow[k] * weight; } } } } for (int k = 0; k < rowSize; ++k) { if (weightaccum[k] != 0.0) { myrow[k] = accum[k] / weightaccum[k]; } else { myrow[k] = 0.0f; } } ciftiOut->setRow(myrow.data(), i); } } AlgorithmCiftiAverage::AlgorithmCiftiAverage(ProgressObject* myProgObj, const vector& ciftiList, const float& sigmaBelow, const float& sigmaAbove, CiftiFile* ciftiOut, const std::vector* weightsPtr): AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); if (ciftiList.size() < 2) { throw AlgorithmException("fewer than 2 files specified with outlier exclusion"); } if (weightsPtr != NULL && ciftiList.size() != weightsPtr->size()) { throw AlgorithmException("number of weights doesn't match number of input cifti files"); } CaretAssert(ciftiList[0] != NULL); CiftiXML baseXML = ciftiList[0]->getCiftiXML(); if (baseXML.getNumberOfDimensions() != 2) throw AlgorithmException("cifti average currently only supports 2D files"); int numRows = baseXML.getDimensionLength(CiftiXML::ALONG_COLUMN), rowSize = baseXML.getDimensionLength(CiftiXML::ALONG_ROW), numFiles = (int)ciftiList.size(); for (int i = 1; i < numFiles; ++i) { CaretAssert(ciftiList[i] != NULL); if (!baseXML.approximateMatch(ciftiList[i]->getCiftiXML())) { throw AlgorithmException("cifti files do not match"); } } bool haveWarned = false; ciftiOut->setCiftiXML(baseXML); vector > myrows(numFiles, vector(rowSize)); for (int i = 0; i < numRows; ++i) { for (int j = 0; j < numFiles; ++j) { ciftiList[j]->getRow(myrows[j].data(), i); } vector outrow(rowSize); for (int k = 0; k < rowSize; ++k) { double accum = 0.0; double weightaccum = 0.0; int nonnumeric = 0; for (int j = 0; j < numFiles; ++j) { if (MathFunctions::isNumeric(myrows[j][k])) { accum += myrows[j][k]; } else { ++nonnumeric; } } if (nonnumeric >= numFiles - 1) { if (!haveWarned) { CaretLogWarning("found element where less than 2 files have numeric values"); haveWarned = true; } outrow[k] = 0.0f; } else { float mean = accum / (numFiles - nonnumeric); accum = 0.0; for (int j = 0; j < numFiles; ++j) { if (MathFunctions::isNumeric(myrows[j][k])) { float temp = myrows[j][k] - mean; accum += temp * temp; } } float stdev = sqrt(accum / (numFiles - 1 - nonnumeric)); float cutoffLow = mean - sigmaBelow * stdev; float cutoffHigh = mean + sigmaAbove * stdev; accum = 0.0; for (int j = 0; j < numFiles; ++j) { if (myrows[j][k] > cutoffLow && myrows[j][k] < cutoffHigh)//implicitly excludes NaN and inf { if (weightsPtr != NULL) { float weight = (*weightsPtr)[j]; accum += myrows[j][k] * weight; weightaccum += weight; } else { accum += myrows[j][k]; weightaccum += 1.0; } } } if (weightaccum != 0.0) { outrow[k] = accum / weightaccum; } else { outrow[k] = 0.0f; } } } ciftiOut->setRow(outrow.data(), i); } } float AlgorithmCiftiAverage::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmCiftiAverage::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmCiftiAverage.h000066400000000000000000000036661255417355300227720ustar00rootroot00000000000000#ifndef __ALGORITHM_CIFTI_AVERAGE_H__ #define __ALGORITHM_CIFTI_AVERAGE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" #include namespace caret { class AlgorithmCiftiAverage : public AbstractAlgorithm { AlgorithmCiftiAverage(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmCiftiAverage(ProgressObject* myProgObj, const std::vector& ciftiList, CiftiFile* ciftiOut, const std::vector* weightsPtr = NULL); AlgorithmCiftiAverage(ProgressObject* myProgObj, const std::vector& ciftiList, const float& sigmaBelow, const float& sigmaAbove, CiftiFile* ciftiOut, const std::vector* weightsPtr = NULL); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmCiftiAverage; } #endif //__ALGORITHM_CIFTI_AVERAGE_H__ workbench-1.1.1/src/Algorithms/AlgorithmCiftiAverageDenseROI.cxx000066400000000000000000000662731255417355300247010ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmCiftiAverageDenseROI.h" #include "AlgorithmException.h" #include "CaretLogger.h" #include "CiftiFile.h" #include "FileInformation.h" #include "SurfaceFile.h" #include "MetricFile.h" #include "VolumeFile.h" #include #include #include using namespace caret; using namespace std; AString AlgorithmCiftiAverageDenseROI::getCommandSwitch() { return "-cifti-average-dense-roi"; } AString AlgorithmCiftiAverageDenseROI::getShortDescription() { return "AVERAGE CIFTI ROWS ACROSS SUBJECTS BY ROI"; } OperationParameters* AlgorithmCiftiAverageDenseROI::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiOutputParameter(1, "cifti-out", "output cifti dscalar file"); OptionalParameter* ciftiRoiOpt = ret->createOptionalParameter(2, "-cifti-roi", "cifti file containing combined weights"); ciftiRoiOpt->addCiftiParameter(1, "roi-cifti", "the roi cifti file"); ciftiRoiOpt->createOptionalParameter(2, "-in-memory", "cache the roi in memory so that it isn't re-read for each input cifti"); OptionalParameter* leftRoiOpt = ret->createOptionalParameter(3, "-left-roi", "weights to use for left hempsphere"); leftRoiOpt->addMetricParameter(1, "roi-metric", "the left roi as a metric file"); OptionalParameter* rightRoiOpt = ret->createOptionalParameter(4, "-right-roi", "weights to use for right hempsphere"); rightRoiOpt->addMetricParameter(1, "roi-metric", "the right roi as a metric file"); OptionalParameter* cerebRoiOpt = ret->createOptionalParameter(5, "-cerebellum-roi", "weights to use for cerebellum surface"); cerebRoiOpt->addMetricParameter(1, "roi-metric", "the cerebellum roi as a metric file"); OptionalParameter* volRoiOpt = ret->createOptionalParameter(6, "-vol-roi", "voxel weights to use"); volRoiOpt->addVolumeParameter(1, "roi-vol", "the roi volume file"); OptionalParameter* leftAreaSurfOpt = ret->createOptionalParameter(7, "-left-area-surf", "specify the left surface for vertex area correction"); leftAreaSurfOpt->addSurfaceParameter(1, "left-surf", "the left surface file"); OptionalParameter* rightAreaSurfOpt = ret->createOptionalParameter(8, "-right-area-surf", "specify the right surface for vertex area correction"); rightAreaSurfOpt->addSurfaceParameter(1, "right-surf", "the right surface file"); OptionalParameter* cerebAreaSurfOpt = ret->createOptionalParameter(9, "-cerebellum-area-surf", "specify the cerebellum surface for vertex area correction"); cerebAreaSurfOpt->addSurfaceParameter(1, "cerebellum-surf", "the cerebellum surface file"); ParameterComponent* ciftiOpt = ret->createRepeatableParameter(10, "-cifti", "specify an input cifti file"); ciftiOpt->addCiftiParameter(1, "cifti-in", "a cifti file to average across"); ret->setHelpText( AString("Averages rows for each map of the ROI(s), across all files. ") + "ROI maps are treated as weighting functions, including negative values. " + "For efficiency, ensure that everything that is not intended to be used is zero in the ROI map. " + "If -cifti-roi is specified, -left-roi, -right-roi, -cerebellum-roi, and -vol-roi must not be specified. " + "If multiple non-cifti ROI files are specified, they must have the same number of columns." ); return ret; } void AlgorithmCiftiAverageDenseROI::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { CiftiFile* ciftiOut = myParams->getOutputCifti(1); CiftiFile* ciftiROI = NULL; OptionalParameter* ciftiRoiOpt = myParams->getOptionalParameter(2); if (ciftiRoiOpt->m_present) { ciftiROI = ciftiRoiOpt->getCifti(1); if (ciftiRoiOpt->getOptionalParameter(2)->m_present) ciftiROI->convertToInMemory(); } MetricFile* leftROI = NULL; OptionalParameter* leftRoiOpt = myParams->getOptionalParameter(3); if (leftRoiOpt->m_present) { if (ciftiROI != NULL) throw AlgorithmException("-cifti-roi cannot be used with any other ROI option"); leftROI = leftRoiOpt->getMetric(1); } MetricFile* rightROI = NULL; OptionalParameter* rightRoiOpt = myParams->getOptionalParameter(4); if (rightRoiOpt->m_present) { if (ciftiROI != NULL) throw AlgorithmException("-cifti-roi cannot be used with any other ROI option"); rightROI = rightRoiOpt->getMetric(1); } MetricFile* cerebROI = NULL; OptionalParameter* cerebRoiOpt = myParams->getOptionalParameter(5); if (cerebRoiOpt->m_present) { if (ciftiROI != NULL) throw AlgorithmException("-cifti-roi cannot be used with any other ROI option"); cerebROI = cerebRoiOpt->getMetric(1); } VolumeFile* volROI = NULL; OptionalParameter* volRoiOpt = myParams->getOptionalParameter(6); if (volRoiOpt->m_present) { if (ciftiROI != NULL) throw AlgorithmException("-cifti-roi cannot be used with any other ROI option"); volROI = volRoiOpt->getVolume(1); } SurfaceFile* leftAreaSurf = NULL; OptionalParameter* leftAreaSurfOpt = myParams->getOptionalParameter(7); if (leftAreaSurfOpt->m_present) { leftAreaSurf = leftAreaSurfOpt->getSurface(1); } SurfaceFile* rightAreaSurf = NULL; OptionalParameter* rightAreaSurfOpt = myParams->getOptionalParameter(8); if (rightAreaSurfOpt->m_present) { rightAreaSurf = rightAreaSurfOpt->getSurface(1); } SurfaceFile* cerebAreaSurf = NULL; OptionalParameter* cerebAreaSurfOpt = myParams->getOptionalParameter(9); if (cerebAreaSurfOpt->m_present) { cerebAreaSurf = cerebAreaSurfOpt->getSurface(1); } vector ciftiList; const vector& ciftiInstances = *(myParams->getRepeatableParameterInstances(10)); for (int i = 0; i < (int)ciftiInstances.size(); ++i) { ciftiList.push_back(ciftiInstances[i]->getCifti(1)); } if (ciftiROI != NULL) { AlgorithmCiftiAverageDenseROI(myProgObj, ciftiList, ciftiOut, ciftiROI, leftAreaSurf, rightAreaSurf, cerebAreaSurf); } else { AlgorithmCiftiAverageDenseROI(myProgObj, ciftiList, ciftiOut, leftROI, rightROI, cerebROI, volROI, leftAreaSurf, rightAreaSurf, cerebAreaSurf); } } AlgorithmCiftiAverageDenseROI::AlgorithmCiftiAverageDenseROI(ProgressObject* myProgObj, const vector& ciftiList, CiftiFile* ciftiOut, const MetricFile* leftROI, const MetricFile* rightROI, const MetricFile* cerebROI, const VolumeFile* volROI, const SurfaceFile* leftAreaSurf, const SurfaceFile* rightAreaSurf, const SurfaceFile* cerebAreaSurf) : AbstractAlgorithm(myProgObj) { CaretAssert(ciftiOut != NULL); LevelProgress myProgress(myProgObj); int numCifti = (int)ciftiList.size(); if (numCifti < 1) throw AlgorithmException("no cifti files specified to average"); const CiftiXML& baseXML = ciftiList[0]->getCiftiXML(); if (baseXML.getNumberOfDimensions() != 2) throw AlgorithmException("this operation currently only supports 2D cifti"); if (baseXML.getMappingType(CiftiXML::ALONG_COLUMN) != CiftiMappingType::BRAIN_MODELS) throw AlgorithmException("inputs must have brain models along columns"); int rowSize = baseXML.getDimensionLength(CiftiXML::ALONG_ROW); bool first = true; const CaretMappableDataFile* nameFile = NULL; int numMaps = -1; vector leftAreaData, rightAreaData, cerebAreaData; float* leftAreaPointer = NULL, *rightAreaPointer = NULL, *cerebAreaPointer = NULL; if (leftROI != NULL) { if (leftAreaSurf != NULL) { if (leftROI->getNumberOfNodes() != leftAreaSurf->getNumberOfNodes()) throw AlgorithmException("left area surface and left roi have different number of nodes"); leftAreaSurf->computeNodeAreas(leftAreaData); leftAreaPointer = leftAreaData.data(); } first = false; numMaps = leftROI->getNumberOfMaps(); nameFile = leftROI; } if (rightROI != NULL) { if (rightAreaSurf != NULL) { if (rightROI->getNumberOfNodes() != rightAreaSurf->getNumberOfNodes()) throw AlgorithmException("right area surface and right roi have different number of nodes"); rightAreaSurf->computeNodeAreas(rightAreaData); rightAreaPointer = rightAreaData.data(); } if (first) { first = false; numMaps = rightROI->getNumberOfMaps(); nameFile = rightROI; } else { if (rightROI->getNumberOfMaps() != numMaps) throw AlgorithmException("right roi has a different number of maps"); } } if (cerebROI != NULL) { if (cerebAreaSurf != NULL) { if (cerebROI->getNumberOfNodes() != cerebAreaSurf->getNumberOfNodes()) throw AlgorithmException("cerebellum area surface and cerebellum roi have different number of nodes"); cerebAreaSurf->computeNodeAreas(cerebAreaData); cerebAreaPointer = cerebAreaData.data(); } if (first) { first = false; numMaps = cerebROI->getNumberOfMaps(); nameFile = cerebROI; } else { if (cerebROI->getNumberOfMaps() != numMaps) throw AlgorithmException("cerebellum roi has a different number of maps"); } } if (volROI != NULL) { if (first) { first = false; numMaps = volROI->getNumberOfMaps(); nameFile = volROI; } else { if (volROI->getNumberOfMaps() != numMaps) throw AlgorithmException("volume roi has a different number of maps"); } } if (first) throw AlgorithmException("no roi files provided"); for (int i = 0; i < numCifti; ++i) { const CiftiXML& thisXML = ciftiList[i]->getCiftiXML(); if (!thisXML.approximateMatch(baseXML)) throw AlgorithmException("cifti files do not match between #1 and #" + AString::number(i + 1)); } if (leftROI != NULL) { verifySurfaceComponent(ciftiList[0], StructureEnum::CORTEX_LEFT, leftROI); } if (rightROI != NULL) { verifySurfaceComponent(ciftiList[0], StructureEnum::CORTEX_RIGHT, rightROI); } if (cerebROI != NULL) { verifySurfaceComponent(ciftiList[0], StructureEnum::CEREBELLUM, cerebROI); } if (volROI != NULL) { verifyVolumeComponent(ciftiList[0], volROI); } vector > accum(numMaps, vector(rowSize, 0.0)); vector denom(numMaps, 0.0); for (int i = 0; i < numCifti; ++i) { if (leftROI != NULL) { processSurfaceComponent(accum, denom, ciftiList[i], StructureEnum::CORTEX_LEFT, leftROI, leftAreaPointer); } if (rightROI != NULL) { processSurfaceComponent(accum, denom, ciftiList[i], StructureEnum::CORTEX_RIGHT, rightROI, rightAreaPointer); } if (cerebROI != NULL) { processSurfaceComponent(accum, denom, ciftiList[i], StructureEnum::CEREBELLUM, cerebROI, cerebAreaPointer); } if (volROI != NULL) { processVolumeComponent(accum, denom, ciftiList[i], volROI); } } CiftiXML newXml; newXml.setNumberOfDimensions(2); newXml.setMap(CiftiXML::ALONG_COLUMN, *(baseXML.getMap(CiftiXML::ALONG_ROW))); CiftiScalarsMap rowMap; rowMap.setLength(numMaps); for (int i = 0; i < numMaps; ++i) { rowMap.setMapName(i, nameFile->getMapName(i)); } newXml.setMap(CiftiXML::ALONG_ROW, rowMap); ciftiOut->setCiftiXML(newXml); vector outRow(numMaps); for (int j = 0; j < numMaps; ++j) { if (denom[j] == 0.0) throw AlgorithmException("no data matched one of the ROI(s)"); } for (int i = 0; i < rowSize; ++i) { for (int j = 0; j < numMaps; ++j) { outRow[j] = accum[j][i] / denom[j]; } ciftiOut->setRow(outRow.data(), i); } } AlgorithmCiftiAverageDenseROI::AlgorithmCiftiAverageDenseROI(ProgressObject* myProgObj, const vector& ciftiList, CiftiFile* ciftiOut, const CiftiFile* ciftiROI, const SurfaceFile* leftAreaSurf, const SurfaceFile* rightAreaSurf, const SurfaceFile* cerebAreaSurf): AbstractAlgorithm(myProgObj) { CaretAssert(ciftiOut != NULL); LevelProgress myProgress(myProgObj); int numCifti = (int)ciftiList.size(); if (numCifti < 1) throw AlgorithmException("no cifti files specified to average"); const CiftiXML& baseXML = ciftiList[0]->getCiftiXML(), &roiXML = ciftiROI->getCiftiXML(); if (baseXML.getNumberOfDimensions() != 2 || roiXML.getNumberOfDimensions() != 2) throw AlgorithmException("this operation currently only supports 2D cifti"); if (baseXML.getMappingType(CiftiXML::ALONG_COLUMN) != CiftiMappingType::BRAIN_MODELS) throw AlgorithmException("inputs must have brain models along columns"); const CiftiBrainModelsMap& baseBrainModels = baseXML.getBrainModelsMap(CiftiXML::ALONG_COLUMN); if (baseBrainModels != *(roiXML.getMap(CiftiXML::ALONG_COLUMN))) throw AlgorithmException("cifti roi mapping along columns doesn't match input cifti"); int rowSize = baseXML.getDimensionLength(CiftiXML::ALONG_ROW); vector leftAreaData, rightAreaData, cerebAreaData; float* leftAreaPointer = NULL, *rightAreaPointer = NULL, *cerebAreaPointer = NULL; if (leftAreaSurf != NULL) { if (baseBrainModels.getSurfaceNumberOfNodes(StructureEnum::CORTEX_LEFT) != leftAreaSurf->getNumberOfNodes()) { throw AlgorithmException("left area surface and left cortex cifti structure have different number of nodes"); } leftAreaSurf->computeNodeAreas(leftAreaData); leftAreaPointer = leftAreaData.data(); } if (rightAreaSurf != NULL) { if (baseBrainModels.getSurfaceNumberOfNodes(StructureEnum::CORTEX_RIGHT) != rightAreaSurf->getNumberOfNodes()) { throw AlgorithmException("right area surface and right cortex cifti structure have different number of nodes"); } rightAreaSurf->computeNodeAreas(rightAreaData); rightAreaPointer = rightAreaData.data(); } if (cerebAreaSurf != NULL) { if (baseBrainModels.getSurfaceNumberOfNodes(StructureEnum::CEREBELLUM) != cerebAreaSurf->getNumberOfNodes()) { throw AlgorithmException("cerebellum area surface and cerebellum cortex cifti structure have different number of nodes"); } cerebAreaSurf->computeNodeAreas(cerebAreaData); cerebAreaPointer = cerebAreaData.data(); } for (int i = 1; i < numCifti; ++i) { const CiftiXML& thisXML = ciftiList[i]->getCiftiXML(); if (!thisXML.approximateMatch(baseXML)) throw AlgorithmException("cifti files do not match between #1 and #" + AString::number(i + 1)); } int numMaps = roiXML.getDimensionLength(CiftiXML::ALONG_ROW); vector > accum(numMaps, vector(rowSize, 0.0)); vector denom(numMaps, 0.0); for (int i = 0; i < numCifti; ++i) { processCifti(accum, denom, ciftiList[i], ciftiROI, leftAreaPointer, rightAreaPointer, cerebAreaPointer); } CiftiXML newXml; newXml.setNumberOfDimensions(2); newXml.setMap(CiftiXML::ALONG_COLUMN, *(baseXML.getMap(CiftiXML::ALONG_ROW))); CiftiScalarsMap rowMap; rowMap.setLength(numMaps); const CiftiMappingType& nameMap = *(roiXML.getMap(CiftiXML::ALONG_ROW)); for (int i = 0; i < numMaps; ++i) { rowMap.setMapName(i, nameMap.getIndexName(i)); } newXml.setMap(CiftiXML::ALONG_ROW, rowMap); ciftiOut->setCiftiXML(newXml); vector outRow(numMaps); for (int j = 0; j < numMaps; ++j) { if (denom[j] == 0.0) throw AlgorithmException("no data matched one of the ROI(s)"); } for (int i = 0; i < rowSize; ++i) { for (int j = 0; j < numMaps; ++j) { outRow[j] = accum[j][i] / denom[j]; } ciftiOut->setRow(outRow.data(), i); } } void AlgorithmCiftiAverageDenseROI::verifySurfaceComponent(const CiftiFile* myCifti, const StructureEnum::Enum& myStruct, const MetricFile* myRoi) { const CiftiXML& myXml = myCifti->getCiftiXML(); CaretAssert(myXml.getMappingType(CiftiXML::ALONG_COLUMN) == CiftiMappingType::BRAIN_MODELS);//should be checked in the algorithm constructor const CiftiBrainModelsMap& brainModelsMap = myXml.getBrainModelsMap(CiftiXML::ALONG_COLUMN); if (!brainModelsMap.hasSurfaceData(myStruct)) { CaretLogWarning("cifti files are missing structure " + StructureEnum::toName(myStruct)); return; } if (myRoi->getNumberOfNodes() != brainModelsMap.getSurfaceNumberOfNodes(myStruct)) { throw AlgorithmException("cifti number of vertices does not match roi for structure " + StructureEnum::toName(myStruct)); } } void AlgorithmCiftiAverageDenseROI::processSurfaceComponent(vector >& accum, vector& denom, const CiftiFile* myCifti, const StructureEnum::Enum& myStruct, const MetricFile* myRoi, const float* myAreas) { const CiftiXML& myXml = myCifti->getCiftiXML(); CaretAssert(myXml.getMappingType(CiftiXML::ALONG_COLUMN) == CiftiMappingType::BRAIN_MODELS);//should be checked in the algorithm constructor const CiftiBrainModelsMap& brainModelsMap = myXml.getBrainModelsMap(CiftiXML::ALONG_COLUMN); if (!brainModelsMap.hasSurfaceData(myStruct)) { return; } if (myRoi->getNumberOfNodes() != brainModelsMap.getSurfaceNumberOfNodes(myStruct)) throw AlgorithmException("cifti number of vertices does not match roi"); int rowSize = myXml.getDimensionLength(CiftiXML::ALONG_ROW); vector rowScratch(rowSize); CaretAssert(rowScratch.size() == accum[0].size()); vector myMap = brainModelsMap.getSurfaceMap(myStruct); int mapSize = (int)myMap.size(); int numMaps = myRoi->getNumberOfMaps(); if (myAreas == NULL) { for (int i = 0; i < mapSize; ++i) { const int& myNode = myMap[i].m_surfaceNode; bool dataLoaded = false; for (int m = 0; m < numMaps; ++m) { const float roiVal = myRoi->getValue(myNode, m); if (roiVal != 0.0f) { if (!dataLoaded) { myCifti->getRow(rowScratch.data(), myMap[i].m_ciftiIndex); dataLoaded = true; } for (int j = 0; j < rowSize; ++j) { accum[m][j] += rowScratch[j] * roiVal; } denom[m] += roiVal; } } } } else { for (int i = 0; i < mapSize; ++i) { const int& myNode = myMap[i].m_surfaceNode; bool dataLoaded = false; for (int m = 0; m < numMaps; ++m) { const float& roiVal = myRoi->getValue(myNode, m); if (roiVal != 0.0f) { const float weight = roiVal * myAreas[myMap[i].m_surfaceNode]; if (!dataLoaded) { myCifti->getRow(rowScratch.data(), myMap[i].m_ciftiIndex); dataLoaded = true; } for (int j = 0; j < rowSize; ++j) { accum[m][j] += rowScratch[j] * weight; } denom[m] += weight; } } } } } void AlgorithmCiftiAverageDenseROI::verifyVolumeComponent(const CiftiFile* myCifti, const VolumeFile* volROI) { const CiftiXML& myXml = myCifti->getCiftiXML(); CaretAssert(myXml.getMappingType(CiftiXML::ALONG_COLUMN) == CiftiMappingType::BRAIN_MODELS);//should be checked in the algorithm constructor const CiftiBrainModelsMap& brainModelsMap = myXml.getBrainModelsMap(CiftiXML::ALONG_COLUMN); if (!volROI->matchesVolumeSpace(brainModelsMap.getVolumeSpace())) throw AlgorithmException("cifti files don't match the ROI volume's space"); } void AlgorithmCiftiAverageDenseROI::processVolumeComponent(vector >& accum, vector& denom, const CiftiFile* myCifti, const VolumeFile* volROI) { const CiftiXML& myXml = myCifti->getCiftiXML(); CaretAssert(myXml.getMappingType(CiftiXML::ALONG_COLUMN) == CiftiMappingType::BRAIN_MODELS);//should be checked in the algorithm constructor const CiftiBrainModelsMap& brainModelsMap = myXml.getBrainModelsMap(CiftiXML::ALONG_COLUMN); if (!volROI->matchesVolumeSpace(brainModelsMap.getVolumeSpace())) throw AlgorithmException("cifti files don't match the ROI volume's space"); int rowSize = myXml.getDimensionLength(CiftiXML::ALONG_ROW); vector rowScratch(rowSize); CaretAssert(rowScratch.size() == accum[0].size()); vector myMap = brainModelsMap.getFullVolumeMap(); int mapSize = (int)myMap.size(); int numMaps = volROI->getNumberOfMaps(); for (int i = 0; i < mapSize; ++i) { bool dataLoaded = false; if (!volROI->indexValid(myMap[i].m_ijk)) throw AlgorithmException("cifti file lists invalid voxels"); for (int m = 0; m < numMaps; ++m) { const float& roiVal = volROI->getValue(myMap[i].m_ijk, m); if (roiVal != 0.0f) { if (!dataLoaded) { myCifti->getRow(rowScratch.data(), myMap[i].m_ciftiIndex); dataLoaded = true; } for (int j = 0; j < rowSize; ++j) { accum[m][j] += rowScratch[j] * roiVal; } denom[m] += roiVal; } } } } void AlgorithmCiftiAverageDenseROI::processCifti(vector >& accum, vector& denom, const CiftiFile* myCifti, const CiftiFile* ciftiROI, const float* leftAreas, const float* rightAreas, const float* cerebAreas) { const CiftiXML& myXml = myCifti->getCiftiXML();//same along columns for data and roi, we already checked CaretAssert(myXml.getMappingType(CiftiXML::ALONG_COLUMN) == CiftiMappingType::BRAIN_MODELS);//should be checked in the algorithm constructor const CiftiBrainModelsMap& brainModelsMap = myXml.getBrainModelsMap(CiftiXML::ALONG_COLUMN); int rowSize = myXml.getDimensionLength(CiftiXML::ALONG_ROW); vector surfList = brainModelsMap.getSurfaceStructureList(); int numMaps = ciftiROI->getNumberOfColumns(); vector dataScratch(rowSize), roiScratch(numMaps); for (int s = 0; s < (int)surfList.size(); ++s) { const float* myAreas = NULL; switch (surfList[s]) { case StructureEnum::CORTEX_LEFT: myAreas = leftAreas; break; case StructureEnum::CORTEX_RIGHT: myAreas = rightAreas; break; case StructureEnum::CEREBELLUM: myAreas = cerebAreas; break; default: throw AlgorithmException("found unexpected surface structure in cifti files: " + StructureEnum::toName(surfList[s])); } vector myMap = brainModelsMap.getSurfaceMap(surfList[s]); int mapSize = (int)myMap.size(); if (myAreas != NULL) { for (int i = 0; i < mapSize; ++i) { ciftiROI->getRow(roiScratch.data(), myMap[i].m_ciftiIndex); const float& thisArea = myAreas[myMap[i].m_surfaceNode]; bool dataLoaded = false; for (int m = 0; m < numMaps; ++m)//ROI maps, not cifti mapping { const float& roiValue = roiScratch[m]; if (roiValue != 0.0f) { if (!dataLoaded) { myCifti->getRow(dataScratch.data(), i); dataLoaded = true; } const float weight = roiValue * thisArea; for (int j = 0; j < rowSize; ++j) { accum[m][j] += dataScratch[j] * weight; } denom[m] += weight; } } } } else { for (int i = 0; i < mapSize; ++i) { ciftiROI->getRow(roiScratch.data(), myMap[i].m_ciftiIndex); bool dataLoaded = false; for (int m = 0; m < numMaps; ++m)//ROI maps, not cifti mapping { const float& roiValue = roiScratch[m]; if (roiValue != 0.0f) { if (!dataLoaded) { myCifti->getRow(dataScratch.data(), i); dataLoaded = true; } for (int j = 0; j < rowSize; ++j) { accum[m][j] += dataScratch[j] * roiValue; } denom[m] += roiValue; } } } } } vector myMap = brainModelsMap.getFullVolumeMap();//again, we know columns match between ROI and data int mapSize = (int)myMap.size(); for (int i = 0; i < mapSize; ++i) { ciftiROI->getRow(roiScratch.data(), myMap[i].m_ciftiIndex); bool dataLoaded = false; for (int m = 0; m < numMaps; ++m)//ROI maps, not cifti mapping { const float& roiValue = roiScratch[m]; if (roiValue != 0.0f) { if (!dataLoaded) { myCifti->getRow(dataScratch.data(), i); dataLoaded = true; } for (int j = 0; j < rowSize; ++j) { accum[m][j] += dataScratch[j] * roiValue; } denom[m] += roiValue; } } } } float AlgorithmCiftiAverageDenseROI::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmCiftiAverageDenseROI::getSubAlgorithmWeight() { return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmCiftiAverageDenseROI.h000066400000000000000000000064251255417355300243170ustar00rootroot00000000000000#ifndef __ALGORITHM_CIFTI_AVERAGE_DENSE_ROI_H__ #define __ALGORITHM_CIFTI_AVERAGE_DENSE_ROI_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" #include "StructureEnum.h" #include namespace caret { class AlgorithmCiftiAverageDenseROI : public AbstractAlgorithm { AlgorithmCiftiAverageDenseROI(); void verifySurfaceComponent(const CiftiFile* myCifti, const StructureEnum::Enum& myStruct, const MetricFile* myRoi); void processSurfaceComponent(std::vector >& accum, std::vector& denom, const CiftiFile* myCifti, const StructureEnum::Enum& myStruct, const MetricFile* myRoi, const float* myAreas); void verifyVolumeComponent(const CiftiFile* myCifti, const VolumeFile* volROI); void processVolumeComponent(std::vector >& accum, std::vector& denom, const CiftiFile* myCifti, const VolumeFile* volROI); void processCifti(std::vector >& accum, std::vector& denom, const CiftiFile* myCifti, const CiftiFile* ciftiROI, const float* leftAreas, const float* rightAreas, const float* cerebAreas); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmCiftiAverageDenseROI(ProgressObject* myProgObj, const std::vector& ciftiList, CiftiFile* ciftiOut, const CiftiFile* ciftiROI, const SurfaceFile* leftAreaSurf = NULL, const SurfaceFile* rightAreaSurf = NULL, const SurfaceFile* cerebAreaSurf = NULL); AlgorithmCiftiAverageDenseROI(ProgressObject* myProgObj, const std::vector& ciftiList, CiftiFile* ciftiOut, const MetricFile* leftROI = NULL, const MetricFile* rightROI = NULL, const MetricFile* cerebROI = NULL, const VolumeFile* volROI = NULL, const SurfaceFile* leftAreaSurf = NULL, const SurfaceFile* rightAreaSurf = NULL, const SurfaceFile* cerebAreaSurf = NULL); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmCiftiAverageDenseROI; } #endif //__ALGORITHM_CIFTI_AVERAGE_DENSE_ROI_H__ workbench-1.1.1/src/Algorithms/AlgorithmCiftiAverageROICorrelation.cxx000066400000000000000000000740111255417355300261110ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmCiftiAverageROICorrelation.h" #include "AlgorithmException.h" #include "CaretLogger.h" #include "CaretOMP.h" #include "CiftiFile.h" #include "FileInformation.h" #include "MetricFile.h" #include "SurfaceFile.h" #include "VolumeFile.h" #include #include #include #include using namespace caret; using namespace std; AString AlgorithmCiftiAverageROICorrelation::getCommandSwitch() { return "-cifti-average-roi-correlation"; } AString AlgorithmCiftiAverageROICorrelation::getShortDescription() { return "CORRELATE ROI AVERAGE WITH ALL ROWS THEN AVERAGE ACROSS SUBJECTS"; } OperationParameters* AlgorithmCiftiAverageROICorrelation::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiOutputParameter(1, "cifti-out", "output cifti file"); OptionalParameter* ciftiRoiOpt = ret->createOptionalParameter(2, "-cifti-roi", "cifti file containing combined weights"); ciftiRoiOpt->addCiftiParameter(1, "roi-cifti", "the roi cifti file"); ciftiRoiOpt->createOptionalParameter(2, "-in-memory", "cache the roi in memory so that it isn't re-read for each input cifti"); OptionalParameter* leftRoiOpt = ret->createOptionalParameter(3, "-left-roi", "weights to use for left hempsphere"); leftRoiOpt->addMetricParameter(1, "roi-metric", "the left roi as a metric file"); OptionalParameter* rightRoiOpt = ret->createOptionalParameter(4, "-right-roi", "weights to use for right hempsphere"); rightRoiOpt->addMetricParameter(1, "roi-metric", "the right roi as a metric file"); OptionalParameter* cerebRoiOpt = ret->createOptionalParameter(5, "-cerebellum-roi", "weights to use for cerebellum surface"); cerebRoiOpt->addMetricParameter(1, "roi-metric", "the cerebellum roi as a metric file"); OptionalParameter* volRoiOpt = ret->createOptionalParameter(6, "-vol-roi", "voxel weights to use"); volRoiOpt->addVolumeParameter(1, "roi-vol", "the roi volume file"); OptionalParameter* leftAreaSurfOpt = ret->createOptionalParameter(7, "-left-area-surf", "specify the left surface for vertex area correction"); leftAreaSurfOpt->addSurfaceParameter(1, "left-surf", "the left surface file"); OptionalParameter* rightAreaSurfOpt = ret->createOptionalParameter(8, "-right-area-surf", "specify the right surface for vertex area correction"); rightAreaSurfOpt->addSurfaceParameter(1, "right-surf", "the right surface file"); OptionalParameter* cerebAreaSurfOpt = ret->createOptionalParameter(9, "-cerebellum-area-surf", "specify the cerebellum surface for vertex area correction"); cerebAreaSurfOpt->addSurfaceParameter(1, "cerebellum-surf", "the cerebellum surface file"); ParameterComponent* ciftiOpt = ret->createRepeatableParameter(10, "-cifti", "specify an input cifti file"); ciftiOpt->addCiftiParameter(1, "cifti-in", "a cifti file to average across"); ret->setHelpText( AString("Averages rows for each map of the ROI(s), takes the correlation of each ROI average to the rest of the rows in the same file, then averages the results across all files. ") + "ROIs are always treated as weighting functions, including negative values. " + "For efficiency, ensure that everything that is not intended to be used is zero in the ROI map. " + "If -cifti-roi is specified, -left-roi, -right-roi, -cerebellum-roi, and -vol-roi must not be specified. " + "If multiple non-cifti ROI files are specified, they must have the same number of columns." ); return ret; } void AlgorithmCiftiAverageROICorrelation::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { CiftiFile* ciftiOut = myParams->getOutputCifti(1); CiftiFile* ciftiROI = NULL; OptionalParameter* ciftiRoiOpt = myParams->getOptionalParameter(2); if (ciftiRoiOpt->m_present) { ciftiROI = ciftiRoiOpt->getCifti(1); if (ciftiRoiOpt->getOptionalParameter(2)->m_present) ciftiROI->convertToInMemory(); } MetricFile* leftROI = NULL; OptionalParameter* leftRoiOpt = myParams->getOptionalParameter(3); if (leftRoiOpt->m_present) { if (ciftiROI != NULL) throw AlgorithmException("-cifti-roi cannot be used with any other ROI option"); leftROI = leftRoiOpt->getMetric(1); } MetricFile* rightROI = NULL; OptionalParameter* rightRoiOpt = myParams->getOptionalParameter(4); if (rightRoiOpt->m_present) { if (ciftiROI != NULL) throw AlgorithmException("-cifti-roi cannot be used with any other ROI option"); rightROI = rightRoiOpt->getMetric(1); } MetricFile* cerebROI = NULL; OptionalParameter* cerebRoiOpt = myParams->getOptionalParameter(5); if (cerebRoiOpt->m_present) { if (ciftiROI != NULL) throw AlgorithmException("-cifti-roi cannot be used with any other ROI option"); cerebROI = cerebRoiOpt->getMetric(1); } VolumeFile* volROI = NULL; OptionalParameter* volRoiOpt = myParams->getOptionalParameter(6); if (volRoiOpt->m_present) { if (ciftiROI != NULL) throw AlgorithmException("-cifti-roi cannot be used with any other ROI option"); volROI = volRoiOpt->getVolume(1); } SurfaceFile* leftAreaSurf = NULL; OptionalParameter* leftAreaSurfOpt = myParams->getOptionalParameter(7); if (leftAreaSurfOpt->m_present) { leftAreaSurf = leftAreaSurfOpt->getSurface(1); } SurfaceFile* rightAreaSurf = NULL; OptionalParameter* rightAreaSurfOpt = myParams->getOptionalParameter(8); if (rightAreaSurfOpt->m_present) { rightAreaSurf = rightAreaSurfOpt->getSurface(1); } SurfaceFile* cerebAreaSurf = NULL; OptionalParameter* cerebAreaSurfOpt = myParams->getOptionalParameter(9); if (cerebAreaSurfOpt->m_present) { cerebAreaSurf = cerebAreaSurfOpt->getSurface(1); } vector ciftiList; const vector& ciftiInputs = *(myParams->getRepeatableParameterInstances(10)); if (ciftiInputs.size() == 0) throw AlgorithmException("at least one -cifti input is required"); for (int i = 0; i < (int)ciftiInputs.size(); ++i) { ciftiList.push_back(ciftiInputs[i]->getCifti(1)); } if (ciftiROI != NULL) { AlgorithmCiftiAverageROICorrelation(myProgObj, ciftiList, ciftiOut, ciftiROI, leftAreaSurf, rightAreaSurf, cerebAreaSurf); } else { AlgorithmCiftiAverageROICorrelation(myProgObj, ciftiList, ciftiOut, leftROI, rightROI, cerebROI, volROI, leftAreaSurf, rightAreaSurf, cerebAreaSurf); } } AlgorithmCiftiAverageROICorrelation::AlgorithmCiftiAverageROICorrelation(ProgressObject* myProgObj, const vector& ciftiList, CiftiFile* ciftiOut, const MetricFile* leftROI, const MetricFile* rightROI, const MetricFile* cerebROI, const VolumeFile* volROI, const SurfaceFile* leftAreaSurf, const SurfaceFile* rightAreaSurf, const SurfaceFile* cerebAreaSurf) : AbstractAlgorithm(myProgObj) { CaretAssert(ciftiOut != NULL); LevelProgress myProgress(myProgObj); int numCifti = (int)ciftiList.size(); if (numCifti < 1) throw AlgorithmException("no cifti files specified to average"); const CiftiXMLOld& baseXML = ciftiList[0]->getCiftiXMLOld(); int rowSize = baseXML.getNumberOfColumns(); int colSize = baseXML.getNumberOfRows(); bool first = true; const CaretMappableDataFile* nameFile = NULL; int numMaps = -1; vector leftAreaData, rightAreaData, cerebAreaData; float* leftAreaPointer = NULL, *rightAreaPointer = NULL, *cerebAreaPointer = NULL; if (leftROI != NULL) { if (leftAreaSurf != NULL) { if (leftROI->getNumberOfNodes() != leftAreaSurf->getNumberOfNodes()) throw AlgorithmException("left area surface and left roi have different number of nodes"); leftAreaSurf->computeNodeAreas(leftAreaData); leftAreaPointer = leftAreaData.data(); } first = false; numMaps = leftROI->getNumberOfMaps(); nameFile = leftROI; } if (rightROI != NULL) { if (rightAreaSurf != NULL) { if (rightROI->getNumberOfNodes() != rightAreaSurf->getNumberOfNodes()) throw AlgorithmException("right area surface and right roi have different number of nodes"); rightAreaSurf->computeNodeAreas(rightAreaData); rightAreaPointer = rightAreaData.data(); } if (first) { first = false; numMaps = rightROI->getNumberOfMaps(); nameFile = rightROI; } else { if (rightROI->getNumberOfMaps() != numMaps) throw AlgorithmException("right roi has a different number of maps"); } } if (cerebROI != NULL) { if (cerebAreaSurf != NULL) { if (cerebROI->getNumberOfNodes() != cerebAreaSurf->getNumberOfNodes()) throw AlgorithmException("cerebellum area surface and cerebellum roi have different number of nodes"); cerebAreaSurf->computeNodeAreas(cerebAreaData); cerebAreaPointer = cerebAreaData.data(); } if (first) { first = false; numMaps = cerebROI->getNumberOfMaps(); nameFile = cerebROI; } else { if (cerebROI->getNumberOfMaps() != numMaps) throw AlgorithmException("cerebellum roi has a different number of maps"); } } if (volROI != NULL) { if (first) { first = false; numMaps = volROI->getNumberOfMaps(); nameFile = volROI; } else { if (volROI->getNumberOfMaps() != numMaps) throw AlgorithmException("volume roi has a different number of maps"); } } if (first) throw AlgorithmException("no roi files provided"); for (int i = 0; i < numCifti; ++i) { const CiftiXMLOld& thisXML = ciftiList[i]->getCiftiXMLOld(); if (!thisXML.mappingMatches(CiftiXMLOld::ALONG_COLUMN, baseXML, CiftiXMLOld::ALONG_COLUMN)) throw AlgorithmException("cifti space does not match between cifti #1 and #" + AString::number(i + 1)); if (thisXML.getNumberOfColumns() != rowSize) throw AlgorithmException("row length doesn't match between cifti #1 and #" + AString::number(i + 1)); if (leftROI != NULL) { verifySurfaceComponent(i, ciftiList[i], StructureEnum::CORTEX_LEFT, leftROI); } if (rightROI != NULL) { verifySurfaceComponent(i, ciftiList[i], StructureEnum::CORTEX_RIGHT, rightROI); } if (cerebROI != NULL) { verifySurfaceComponent(i, ciftiList[i], StructureEnum::CEREBELLUM, cerebROI); } if (volROI != NULL) { verifyVolumeComponent(i, ciftiList[i], volROI); } } vector > tempresult(colSize, vector(numMaps)); CiftiXMLOld newXml = baseXML; newXml.resetRowsToScalars(numMaps); for (int i = 0; i < numMaps; ++i) { newXml.setMapNameForIndex(CiftiXMLOld::ALONG_ROW, i, nameFile->getMapName(i)); } ciftiOut->setCiftiXML(newXml); if (numCifti > 1)//skip averaging in single subject case { vector > accum(colSize, vector(numMaps, 0.0)); for (int i = 0; i < numCifti; ++i) { processCifti(ciftiList[i], tempresult, leftROI, rightROI, cerebROI, volROI, numMaps, leftAreaPointer, rightAreaPointer, cerebAreaPointer); for (int j = 0; j < colSize; ++j) { for (int myMap = 0; myMap < numMaps; ++myMap) { accum[j][myMap] += tempresult[j][myMap]; } } } for (int i = 0; i < colSize; ++i) { for (int myMap = 0; myMap < numMaps; ++myMap) { tempresult[i][myMap] = accum[i][myMap] / numCifti; } ciftiOut->setRow(tempresult[i].data(), i); } } else { processCifti(ciftiList[0], tempresult, leftROI, rightROI, cerebROI, volROI, numMaps, leftAreaPointer, rightAreaPointer, cerebAreaPointer); for (int i = 0; i < colSize; ++i) { ciftiOut->setRow(tempresult[i].data(), i); } } } AlgorithmCiftiAverageROICorrelation::AlgorithmCiftiAverageROICorrelation(ProgressObject* myProgObj, const vector& ciftiList, CiftiFile* ciftiOut, const CiftiFile* ciftiROI, const SurfaceFile* leftAreaSurf, const SurfaceFile* rightAreaSurf, const SurfaceFile* cerebAreaSurf): AbstractAlgorithm(myProgObj) { CaretAssert(ciftiOut != NULL); LevelProgress myProgress(myProgObj); int numCifti = (int)ciftiList.size(); if (numCifti < 1) throw AlgorithmException("no cifti files specified to average"); const CiftiXMLOld baseXML = ciftiList[0]->getCiftiXMLOld(), roiXML = ciftiROI->getCiftiXMLOld(); int rowSize = baseXML.getNumberOfColumns(); int colSize = baseXML.getNumberOfRows(); int numMaps = ciftiROI->getNumberOfColumns(); if (!baseXML.mappingMatches(CiftiXMLOld::ALONG_COLUMN, roiXML, CiftiXMLOld::ALONG_COLUMN)) throw AlgorithmException("cifti roi doesn't match cifti space of data"); for (int i = 1; i < numCifti; ++i) { if (!baseXML.mappingMatches(CiftiXMLOld::ALONG_COLUMN, ciftiList[i]->getCiftiXMLOld(), CiftiXMLOld::ALONG_COLUMN)) throw AlgorithmException("cifti space does not match between cifti #1 and #" + AString::number(i + 1)); if (ciftiList[i]->getNumberOfColumns() != rowSize) throw AlgorithmException("row length doesn't match between cifti #1 and #" + AString::number(i + 1)); } vector leftAreaData, rightAreaData, cerebAreaData; float* leftAreaPointer = NULL, *rightAreaPointer = NULL, *cerebAreaPointer = NULL; if (leftAreaSurf != NULL) { if (baseXML.getSurfaceNumberOfNodes(CiftiXMLOld::ALONG_COLUMN, StructureEnum::CORTEX_LEFT) != leftAreaSurf->getNumberOfNodes()) { throw AlgorithmException("left area surface and left cortex cifti structure have different number of nodes"); } leftAreaSurf->computeNodeAreas(leftAreaData); leftAreaPointer = leftAreaData.data(); } if (rightAreaSurf != NULL) { if (baseXML.getSurfaceNumberOfNodes(CiftiXMLOld::ALONG_COLUMN, StructureEnum::CORTEX_RIGHT) != rightAreaSurf->getNumberOfNodes()) { throw AlgorithmException("right area surface and right cortex cifti structure have different number of nodes"); } rightAreaSurf->computeNodeAreas(rightAreaData); rightAreaPointer = rightAreaData.data(); } if (cerebAreaSurf != NULL) { if (baseXML.getSurfaceNumberOfNodes(CiftiXMLOld::ALONG_COLUMN, StructureEnum::CEREBELLUM) != cerebAreaSurf->getNumberOfNodes()) { throw AlgorithmException("cerebellum area surface and cerebellum cortex cifti structure have different number of nodes"); } cerebAreaSurf->computeNodeAreas(cerebAreaData); cerebAreaPointer = cerebAreaData.data(); } vector > tempresult(colSize, vector(numMaps)); CiftiXMLOld newXml = baseXML; newXml.resetRowsToScalars(numMaps); for (int i = 0; i < numMaps; ++i) { newXml.setMapNameForIndex(CiftiXMLOld::ALONG_ROW, i, roiXML.getMapNameForRowIndex(i)); } ciftiOut->setCiftiXML(newXml); if (numCifti > 1)//skip averaging in single subject case { vector > accum(colSize, vector(numMaps, 0.0)); for (int i = 0; i < numCifti; ++i) { processCifti(ciftiList[i], tempresult, ciftiROI, numMaps, leftAreaPointer, rightAreaPointer, cerebAreaPointer); for (int j = 0; j < colSize; ++j) { for (int myMap = 0; myMap < numMaps; ++myMap) { accum[j][myMap] += tempresult[j][myMap]; } } } for (int i = 0; i < colSize; ++i) { for (int myMap = 0; myMap < numMaps; ++myMap) { tempresult[i][myMap] = accum[i][myMap] / numCifti; } ciftiOut->setRow(tempresult[i].data(), i); } } else { processCifti(ciftiList[0], tempresult, ciftiROI, numMaps, leftAreaPointer, rightAreaPointer, cerebAreaPointer); for (int i = 0; i < colSize; ++i) { ciftiOut->setRow(tempresult[i].data(), i); } } } void AlgorithmCiftiAverageROICorrelation::verifySurfaceComponent(const int& index, const CiftiFile* myCifti, const StructureEnum::Enum& myStruct, const MetricFile* myRoi) { const CiftiXMLOld& myXml = myCifti->getCiftiXMLOld(); if (!myXml.hasColumnSurfaceData(myStruct)) { CaretLogWarning("cifti file #" + AString::number(index + 1) + " missing structure " + StructureEnum::toName(myStruct)); return; } if (myRoi->getNumberOfNodes() != myXml.getColumnSurfaceNumberOfNodes(myStruct)) throw AlgorithmException("cifti #" + AString::number(index + 1) + " number of vertices does not match roi"); } void AlgorithmCiftiAverageROICorrelation::verifyVolumeComponent(const int& index, const CiftiFile* myCifti, const VolumeFile* volROI) { const CiftiXMLOld& myXml = myCifti->getCiftiXMLOld(); int64_t dims[3]; vector > sform; myXml.getVolumeDimsAndSForm(dims, sform); if (!volROI->matchesVolumeSpace(dims, sform)) throw AlgorithmException("cifti file #" + AString::number(index + 1) + " doesn't match the ROI volume's space"); vector myMap; myXml.getVolumeMapForColumns(myMap); int mapSize = (int)myMap.size(); for (int i = 0; i < mapSize; ++i) { if (!volROI->indexValid(myMap[i].m_ijk)) throw AlgorithmException("cifti file #" + AString::number(index + 1) + " lists invalid voxels"); } } void AlgorithmCiftiAverageROICorrelation::processCifti(const CiftiFile* myCifti, vector >& output, const MetricFile* leftROI, const MetricFile* rightROI,const MetricFile* cerebROI, const VolumeFile* volROI, const int& numMaps, const float* leftAreas, const float* rightAreas, const float* cerebAreas) { int rowSize = myCifti->getNumberOfColumns(); int colSize = myCifti->getNumberOfRows(); vector > average(numMaps, vector(rowSize)); vector rrs(numMaps); for (int myMap = 0; myMap < numMaps; ++myMap) { vector accumarray(rowSize, 0.0); addSurface(myCifti, StructureEnum::CORTEX_LEFT, accumarray, leftROI, myMap, leftAreas);//we don't need to keep track of the kernel sums because we are correlating addSurface(myCifti, StructureEnum::CORTEX_RIGHT, accumarray, rightROI, myMap, rightAreas); addSurface(myCifti, StructureEnum::CEREBELLUM, accumarray, cerebROI, myMap, cerebAreas); addVolume(myCifti, accumarray, volROI, myMap); double accum = 0.0; for (int i = 0; i < rowSize; ++i) { accum += accumarray[i]; } double mean = accum / rowSize; accum = 0.0; for (int i = 0; i < rowSize; ++i) { average[myMap][i] = accumarray[i] - mean;//remove the mean from the average timeseries to optimize the correlation, and change back to float for possible speed improvement accum += average[myMap][i] * average[myMap][i]; } rrs[myMap] = sqrt(accum);//compute this only once } int curRow = 0; #pragma omp CARET_PAR { vector rowscratch(rowSize); #pragma omp CARET_FOR schedule(dynamic) for (int i = 0; i < colSize; ++i) { int myRow; #pragma omp critical { myRow = curRow;//force sequential reading ++curRow; myCifti->getRow(rowscratch.data(), myRow);//and never read multiple rows at once from the same file } double tempaccum = 0.0;//compute mean of new row for (int j = 0; j < rowSize; ++j) { tempaccum += rowscratch[j]; } float thismean = tempaccum / rowSize; tempaccum = 0.0; for (int j = 0; j < rowSize; ++j) { rowscratch[j] -= thismean;//demean tempaccum += rowscratch[j] * rowscratch[j];//precompute rrs } float thisrrs = sqrt(tempaccum); for (int myMap = 0; myMap < numMaps; ++myMap) { double corraccum = 0.0;//correlate for (int j = 0; j < rowSize; ++j) { corraccum += rowscratch[j] * average[myMap][j];//gather the correlation } corraccum /= rrs[myMap] * thisrrs; if (corraccum > 0.999999) corraccum = 0.999999; if (corraccum < -0.999999) corraccum = -0.999999; output[myRow][myMap] = 0.5 * log((1 + corraccum) / (1 - corraccum));//fisher z transform, needed for averaging } } } } void AlgorithmCiftiAverageROICorrelation::processCifti(const CiftiFile* myCifti, vector >& output, const CiftiFile* ciftiROI, const int& numMaps, const float* leftAreas, const float* rightAreas, const float* cerebAreas) { int rowSize = myCifti->getNumberOfColumns(); int colSize = myCifti->getNumberOfRows(); vector > average(numMaps, vector(rowSize)); vector rrs(numMaps); const CiftiXMLOld& roiXML = ciftiROI->getCiftiXMLOld(); vector surfStructures, ignored; roiXML.getStructureLists(CiftiXMLOld::ALONG_COLUMN, surfStructures, ignored); vector roiScratch(numMaps), dataScratch(rowSize); { vector > accumarray(numMaps, vector(rowSize, 0.0)); for (int whichStruct = 0; whichStruct < (int)surfStructures.size(); ++whichStruct) { const float* areaPtr = NULL; switch (surfStructures[whichStruct]) { case StructureEnum::CORTEX_LEFT: areaPtr = leftAreas; break; case StructureEnum::CORTEX_RIGHT: areaPtr = rightAreas; break; case StructureEnum::CEREBELLUM: areaPtr = cerebAreas; break; default: break; } vector myMap; roiXML.getSurfaceMap(CiftiXMLOld::ALONG_COLUMN, myMap, surfStructures[whichStruct]); for (int i = 0; i < (int)myMap.size(); ++i) { bool dataLoaded = false; ciftiROI->getRow(roiScratch.data(), myMap[i].m_ciftiIndex); for (int j = 0; j < numMaps; ++j) { if (roiScratch[j] != 0.0f) { if (!dataLoaded) { myCifti->getRow(dataScratch.data(), myMap[i].m_ciftiIndex); dataLoaded = true; } if (areaPtr != NULL) { for (int k = 0; k < rowSize; ++k) { accumarray[j][k] += dataScratch[k] * roiScratch[j] * areaPtr[myMap[i].m_surfaceNode]; } } else { for (int k = 0; k < rowSize; ++k) { accumarray[j][k] += dataScratch[k] * roiScratch[j]; } } } } } } vector myMap; roiXML.getVolumeMap(CiftiXMLOld::ALONG_COLUMN, myMap); for (int i = 0; i < (int)myMap.size(); ++i) { bool dataLoaded = false; ciftiROI->getRow(roiScratch.data(), myMap[i].m_ciftiIndex); for (int j = 0; j < numMaps; ++j) { if (roiScratch[j] != 0.0f) { if (!dataLoaded) { myCifti->getRow(dataScratch.data(), myMap[i].m_ciftiIndex); dataLoaded = true; } for (int k = 0; k < rowSize; ++k) { accumarray[j][k] += dataScratch[k] * roiScratch[j]; } } } } for (int i = 0; i < numMaps; ++i) { double accum = 0.0; for (int j = 0; j < rowSize; ++j) { accum += accumarray[i][j]; } float mean = accum / rowSize; accum = 0.0; for (int j = 0; j < rowSize; ++j) { average[i][j] = accumarray[i][j] - mean; accum += average[i][j] * average[i][j]; } vector().swap(accumarray[i]);//hack to free memory before it goes out of scope rrs[i] = sqrt(accum); } } int curRow = 0; #pragma omp CARET_PAR { vector rowscratch(rowSize); #pragma omp CARET_FOR schedule(dynamic) for (int i = 0; i < colSize; ++i) { int myRow; #pragma omp critical { myRow = curRow;//force sequential reading ++curRow; myCifti->getRow(rowscratch.data(), myRow);//and never read multiple rows at once from the same file } double tempaccum = 0.0;//compute mean of new row for (int j = 0; j < rowSize; ++j) { tempaccum += rowscratch[j]; } float thismean = tempaccum / rowSize; tempaccum = 0.0; for (int j = 0; j < rowSize; ++j) { rowscratch[j] -= thismean;//demean tempaccum += rowscratch[j] * rowscratch[j];//precompute rrs } float thisrrs = sqrt(tempaccum); for (int myMap = 0; myMap < numMaps; ++myMap) { double corraccum = 0.0;//correlate for (int j = 0; j < rowSize; ++j) { corraccum += rowscratch[j] * average[myMap][j];//gather the correlation } corraccum /= rrs[myMap] * thisrrs; if (corraccum > 0.999999) corraccum = 0.999999; if (corraccum < -0.999999) corraccum = -0.999999; output[myRow][myMap] = 0.5 * log((1 + corraccum) / (1 - corraccum));//fisher z transform, needed for averaging } } } } void AlgorithmCiftiAverageROICorrelation::addSurface(const CiftiFile* myCifti, StructureEnum::Enum myStruct, vector& accum, const MetricFile* myRoi, const int& myMap, const float* myAreas) { if (myRoi == NULL) return; vector surfaceMap = myCifti->getCiftiXML().getBrainModelsMap(CiftiXML::ALONG_COLUMN).getSurfaceMap(myStruct); int mapSize = (int)surfaceMap.size(); int rowSize = myCifti->getNumberOfColumns(); vector rowscratch(rowSize); if (myAreas != NULL) { for (int i = 0; i < mapSize; ++i) { float value = myRoi->getValue(surfaceMap[i].m_surfaceNode, myMap); if (value != 0.0f) { float thisArea = myAreas[surfaceMap[i].m_surfaceNode]; myCifti->getRow(rowscratch.data(), surfaceMap[i].m_ciftiIndex); for (int j = 0; j < rowSize; ++j) { accum[j] += rowscratch[j] * value * thisArea; } } } } else { for (int i = 0; i < mapSize; ++i) { float value = myRoi->getValue(surfaceMap[i].m_surfaceNode, myMap); if (value != 0.0f) { myCifti->getRow(rowscratch.data(), surfaceMap[i].m_ciftiIndex); for (int j = 0; j < rowSize; ++j) { accum[j] += rowscratch[j] * value; } } } } } void AlgorithmCiftiAverageROICorrelation::addVolume(const CiftiFile* myCifti, vector& accum, const VolumeFile* myRoi, const int& myMap) { if (myRoi == NULL) return; vector volMap = myCifti->getCiftiXML().getBrainModelsMap(CiftiXML::ALONG_COLUMN).getFullVolumeMap(); int mapSize = (int)volMap.size(); int rowSize = myCifti->getNumberOfColumns(); vector rowscratch(rowSize); for (int i = 0; i < mapSize; ++i) { if (myRoi->getValue(volMap[i].m_ijk, myMap) > 0.0f) { myCifti->getRow(rowscratch.data(), volMap[i].m_ciftiIndex); for (int j = 0; j < rowSize; ++j) { accum[j] += rowscratch[j]; } } } } float AlgorithmCiftiAverageROICorrelation::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmCiftiAverageROICorrelation::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmCiftiAverageROICorrelation.h000066400000000000000000000070631255417355300255410ustar00rootroot00000000000000#ifndef __ALGORITHM_CIFTI_AVERAGE_ROI_CORRELATION_H__ #define __ALGORITHM_CIFTI_AVERAGE_ROI_CORRELATION_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" #include "StructureEnum.h" #include namespace caret { class AlgorithmCiftiAverageROICorrelation : public AbstractAlgorithm { AlgorithmCiftiAverageROICorrelation(); void verifySurfaceComponent(const int& index, const CiftiFile* myCifti, const StructureEnum::Enum& myStruct, const MetricFile* myRoi); void verifyVolumeComponent(const int& index, const CiftiFile* myCifti, const VolumeFile* volROI); void processCifti(const CiftiFile* myCifti, std::vector >& output, const MetricFile* leftROI, const MetricFile* rightROI, const MetricFile* cerebROI, const VolumeFile* volROI, const int& numMaps, const float* leftAreas, const float* rightAreas, const float* cerebAreas); void processCifti(const CiftiFile* myCifti, std::vector >& output, const CiftiFile* ciftiROI, const int& numMaps, const float* leftAreas, const float* rightAreas, const float* cerebAreas); void addSurface(const CiftiFile* myCifti, StructureEnum::Enum myStruct, std::vector& accum, const MetricFile* myRoi, const int& myMap, const float* myAreas); void addVolume(const CiftiFile* myCifti, std::vector& accum, const VolumeFile* myRoi, const int& myMap); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmCiftiAverageROICorrelation(ProgressObject* myProgObj, const std::vector& ciftiList, CiftiFile* ciftiOut, const MetricFile* leftROI = NULL, const MetricFile* rightROI = NULL, const MetricFile* cerebROI = NULL, const VolumeFile* volROI = NULL, const SurfaceFile* leftAreaSurf = NULL, const SurfaceFile* rightAreaSurf = NULL, const SurfaceFile* cerebAreaSurf = NULL); AlgorithmCiftiAverageROICorrelation(ProgressObject* myProgObj, const std::vector& ciftiList, CiftiFile* ciftiOut, const CiftiFile* ciftiROI, const SurfaceFile* leftAreaSurf = NULL, const SurfaceFile* rightAreaSurf = NULL, const SurfaceFile* cerebAreaSurf = NULL); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmCiftiAverageROICorrelation; } #endif //__ALGORITHM_CIFTI_AVERAGE_ROI_CORRELATION_H__ workbench-1.1.1/src/Algorithms/AlgorithmCiftiCorrelation.cxx000066400000000000000000001027241255417355300242470ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmCiftiCorrelation.h" #include "AlgorithmException.h" #include "AlgorithmCiftiSeparate.h" #include "CiftiFile.h" #include "MetricFile.h" #include "VolumeFile.h" #include "CaretLogger.h" #include "MathFunctions.h" #include "CaretOMP.h" #include "FileInformation.h" #include "CaretPointer.h" #include #include #include using namespace caret; using namespace std; AString AlgorithmCiftiCorrelation::getCommandSwitch() { return "-cifti-correlation"; } AString AlgorithmCiftiCorrelation::getShortDescription() { return "GENERATE CORRELATION OF ROWS IN A CIFTI FILE"; } OperationParameters* AlgorithmCiftiCorrelation::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiParameter(1, "cifti", "input cifti file"); ret->addCiftiOutputParameter(2, "cifti-out", "output cifti file"); OptionalParameter* roiOverrideOpt = ret->createOptionalParameter(3, "-roi-override", "perform correlation from a subset of rows to all rows"); OptionalParameter* leftRoiOpt = roiOverrideOpt->createOptionalParameter(1, "-left-roi", "use an roi for left hempsphere"); leftRoiOpt->addMetricParameter(1, "roi-metric", "the left roi as a metric file"); OptionalParameter* rightRoiOpt = roiOverrideOpt->createOptionalParameter(2, "-right-roi", "use an roi for right hempsphere"); rightRoiOpt->addMetricParameter(1, "roi-metric", "the right roi as a metric file"); OptionalParameter* cerebRoiOpt = roiOverrideOpt->createOptionalParameter(3, "-cerebellum-roi", "use an roi for cerebellum"); cerebRoiOpt->addMetricParameter(1, "roi-metric", "the cerebellum roi as a metric file"); OptionalParameter* volRoiOpt = roiOverrideOpt->createOptionalParameter(4, "-vol-roi", "use an roi for volume"); volRoiOpt->addVolumeParameter(1, "roi-vol", "the volume roi file"); OptionalParameter* ciftiRoiOpt = roiOverrideOpt->createOptionalParameter(5, "-cifti-roi", "use a cifti file for combined rois"); ciftiRoiOpt->addCiftiParameter(1, "roi-cifti", "the cifti roi file"); OptionalParameter* weightsOpt = ret->createOptionalParameter(4, "-weights", "specify column weights"); weightsOpt->addStringParameter(1, "weight-file", "text file containing one weight per column"); ret->createOptionalParameter(5, "-fisher-z", "apply fisher small z transform (ie, artanh) to correlation"); ret->createOptionalParameter(7, "-no-demean", "instead of correlation, do dot product of rows, then normalize by diagonal"); ret->createOptionalParameter(8, "-covariance", "compute covariance instead of correlation"); OptionalParameter* memLimitOpt = ret->createOptionalParameter(6, "-mem-limit", "restrict memory usage"); memLimitOpt->addDoubleParameter(1, "limit-GB", "memory limit in gigabytes"); ret->setHelpText( AString("For each row (or each row inside an roi if -roi-override is specified), correlate to all other rows. ") + "The -cifti-roi suboption to -roi-override may not be specified with any other -*-roi suboption, but you may specify the other -*-roi suboptions together.\n\n" + "When using the -fisher-z option, the output is NOT a Z-score, it is artanh(r), to do further math on this output, consider using -cifti-math.\n\n" + "Restricting the memory usage will make it calculate the output in chunks, and if the input file size is more than 70% of the memory limit, " + "it will also read through the input file as rows are required, resulting in several passes through the input file (once per chunk). " + "Memory limit does not need to be an integer, you may also specify 0 to calculate a single output row at a time (this may be very slow)." ); return ret; } void AlgorithmCiftiCorrelation::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { CiftiFile* myCifti = myParams->getCifti(1); CiftiFile* myCiftiOut = myParams->getOutputCifti(2); OptionalParameter* roiOverrideOpt = myParams->getOptionalParameter(3); bool roiOverrideMode = roiOverrideOpt->m_present; MetricFile* leftRoi = NULL, *rightRoi = NULL, *cerebRoi = NULL; VolumeFile* volRoi = NULL; CiftiFile* ciftiRoi = NULL; bool ciftiRoiMode = true; if (roiOverrideMode) { OptionalParameter* leftRoiOpt = roiOverrideOpt->getOptionalParameter(1); if (leftRoiOpt->m_present) { leftRoi = leftRoiOpt->getMetric(1); ciftiRoiMode = false; } OptionalParameter* rightRoiOpt = roiOverrideOpt->getOptionalParameter(2); if (rightRoiOpt->m_present) { rightRoi = rightRoiOpt->getMetric(1); ciftiRoiMode = false; } OptionalParameter* cerebRoiOpt = roiOverrideOpt->getOptionalParameter(3); if (cerebRoiOpt->m_present) { cerebRoi = cerebRoiOpt->getMetric(1); ciftiRoiMode = false; } OptionalParameter* volRoiOpt = roiOverrideOpt->getOptionalParameter(4); if (volRoiOpt->m_present) { volRoi = volRoiOpt->getVolume(1); ciftiRoiMode = false; } OptionalParameter* ciftiRoiOpt = roiOverrideOpt->getOptionalParameter(5); if (ciftiRoiOpt->m_present) { if (!ciftiRoiMode) throw AlgorithmException("-cifti-roi cannot be specified with any other -*-roi option"); ciftiRoi = ciftiRoiOpt->getCifti(1); } else { if (ciftiRoiMode) throw AlgorithmException("-roi-override requires a -*-roi suboption"); } } OptionalParameter* weightsOpt = myParams->getOptionalParameter(4); vector* weights = NULL, realweights;//NOTE: realweights is NOT a pointer if (weightsOpt->m_present) { weights = &realweights;//point it to the actual vector to signify the option is present AString weightFileName = weightsOpt->getString(1); FileInformation textFileInfo(weightFileName); if (!textFileInfo.exists()) { throw AlgorithmException("weight list file doesn't exist"); } fstream weightListFile(weightFileName.toLocal8Bit().constData(), fstream::in); if (!weightListFile.good()) { throw AlgorithmException("error reading weight list file"); } while (weightListFile.good()) { float weight; if (!(weightListFile >> weight))//yes, this is how you check fstream for successfully extracted output. seriously. { break; } realweights.push_back(weight); } } bool fisherZ = myParams->getOptionalParameter(5)->m_present; float memLimitGB = -1.0f; OptionalParameter* memLimitOpt = myParams->getOptionalParameter(6); if (memLimitOpt->m_present) { memLimitGB = (float)memLimitOpt->getDouble(1); if (memLimitGB < 0.0f) { throw AlgorithmException("memory limit cannot be negative"); } } bool noDemean = myParams->getOptionalParameter(7)->m_present; bool covariance = myParams->getOptionalParameter(8)->m_present; if (roiOverrideMode) { if (ciftiRoiMode) { AlgorithmCiftiCorrelation(myProgObj, myCifti, myCiftiOut, ciftiRoi, weights, fisherZ, memLimitGB, noDemean); } else { AlgorithmCiftiCorrelation(myProgObj, myCifti, myCiftiOut, leftRoi, rightRoi, cerebRoi, volRoi, weights, fisherZ, memLimitGB, noDemean); } } else { AlgorithmCiftiCorrelation(myProgObj, myCifti, myCiftiOut, weights, fisherZ, memLimitGB, noDemean, covariance); } } AlgorithmCiftiCorrelation::AlgorithmCiftiCorrelation(ProgressObject* myProgObj, const CiftiFile* myCifti, CiftiFile* myCiftiOut, const vector* weights, const bool& fisherZ, const float& memLimitGB, const bool& noDemean, const bool& covariance) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); if (covariance) { if (fisherZ) throw AlgorithmException("cannot apply fisher z transformation to covariance"); } init(myCifti, weights, noDemean, covariance); int numRows = myCifti->getNumberOfRows(); CiftiXMLOld newXML = myCifti->getCiftiXMLOld(); newXML.applyColumnMapToRows(); myCiftiOut->setCiftiXML(newXML); int numCacheRows; bool cacheFullInput = true; if (memLimitGB >= 0.0f) { numCacheRows = numRowsForMem(memLimitGB, cacheFullInput); } else { numCacheRows = numRows; } if (numCacheRows > numRows) numCacheRows = numRows; if (cacheFullInput) { if (numCacheRows != numRows) CaretLogInfo("computing " + AString::number(numCacheRows) + " rows at a time"); } else { CaretLogInfo("computing " + AString::number(numCacheRows) + " rows at a time, reading rows as needed during processing"); } vector > outRows; if (cacheFullInput) { for (int i = 0; i < numRows; ++i) { cacheRow(i); } } for (int startrow = 0; startrow < numRows; startrow += numCacheRows) { int endrow = startrow + numCacheRows; if (endrow > numRows) endrow = numRows; outRows.resize(endrow - startrow); for (int i = startrow; i < endrow; ++i) { if (!cacheFullInput) { cacheRow(i);//preload the rows in a range which we will reuse as much as possible during one row by row scan } if (outRows[i - startrow].size() != numRows) { outRows[i - startrow] = CaretArray(numRows); } } int curRow = 0;//because we can't trust the order threads hit the critical section #pragma omp CARET_PARFOR schedule(dynamic) for (int i = 0; i < numRows; ++i) { float movingRrs; int myrow; const float* movingRow; #pragma omp critical {//CiftiFile may explode if we request multiple rows concurrently (needs mutexes), but we should force sequential requests anyway myrow = curRow;//so, manually force it to read sequentially ++curRow; movingRow = getRow(myrow, movingRrs); } for (int j = startrow; j < endrow; ++j) { if (myrow >= startrow && myrow < endrow)//check whether we are in the output memory area { if (j >= myrow)//if so, only compute one half, and store both places { float cacheRrs; const float* cacheRow = getRow(j, cacheRrs, true); outRows[j - startrow][myrow] = correlate(movingRow, movingRrs, cacheRow, cacheRrs, fisherZ); outRows[myrow - startrow][j] = outRows[j - startrow][myrow]; } } else { float cacheRrs; const float* cacheRow = getRow(j, cacheRrs, true); outRows[j - startrow][myrow] = correlate(movingRow, movingRrs, cacheRow, cacheRrs, fisherZ); } } } for (int i = startrow; i < endrow; ++i) { myCiftiOut->setRow(outRows[i - startrow], i); } if (!cacheFullInput) { clearCache();//tell the cache we are going to preload a different set of rows now } } if (cacheFullInput) { clearCache();//don't currently need to do this, its just for completeness } } AlgorithmCiftiCorrelation::AlgorithmCiftiCorrelation(ProgressObject* myProgObj, const CiftiFile* myCifti, CiftiFile* myCiftiOut, const MetricFile* leftRoi, const MetricFile* rightRoi, const MetricFile* cerebRoi, const VolumeFile* volRoi, const vector* weights, const bool& fisherZ, const float& memLimitGB, const bool& noDemean, const bool& covariance) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); if (covariance) { if (fisherZ) throw AlgorithmException("cannot apply fisher z transformation to covariance"); } init(myCifti, weights, noDemean, covariance); const CiftiXMLOld& origXML = myCifti->getCiftiXMLOld(); if (origXML.getColumnMappingType() != CIFTI_INDEX_TYPE_BRAIN_MODELS) { throw AlgorithmException("cannot use ROIs on this cifti, columns are not brain models"); } CiftiXMLOld newXML = origXML; vector surfList, volList; origXML.getStructureListsForColumns(surfList, volList); newXML.applyColumnMapToRows(); newXML.resetColumnsToBrainModels(); vector > ciftiIndexList; int newCiftiIndex = 0; for (int i = 0; i < (int)surfList.size(); ++i) { const MetricFile* myRoi = NULL; switch (surfList[i]) { case StructureEnum::CORTEX_LEFT: myRoi = leftRoi; break; case StructureEnum::CORTEX_RIGHT: myRoi = rightRoi; break; case StructureEnum::CEREBELLUM: myRoi = cerebRoi; break; default: break; } if (myRoi != NULL) { if (myRoi->getNumberOfNodes() != origXML.getColumnSurfaceNumberOfNodes(surfList[i])) { throw AlgorithmException("surface roi has the wrong number of vertices for structure " + StructureEnum::toName(surfList[i])); } const CiftiBrainModelsMap& myDenseMap = myCifti->getCiftiXML().getBrainModelsMap(CiftiXML::ALONG_COLUMN); vector myMap = myDenseMap.getSurfaceMap(surfList[i]); int numNodes = myDenseMap.getSurfaceNumberOfNodes(surfList[i]); int mapsize = (int)myMap.size(); vector tempNodeList; for (int j = 0; j < mapsize; ++j) { int myNode = myMap[j].m_surfaceNode; if (myRoi->getValue(myNode, 0) > 0.0f) { tempNodeList.push_back(myNode); ciftiIndexList.push_back(std::pair(myMap[j].m_ciftiIndex, newCiftiIndex)); ++newCiftiIndex; } } if (tempNodeList.size() > 0)//don't add it if it is empty { newXML.addSurfaceModelToColumns(numNodes, surfList[i], tempNodeList); } } } if (volRoi != NULL) { int64_t origDims[3]; vector > origSForm; origXML.getVolumeDimsAndSForm(origDims, origSForm); if (!volRoi->matchesVolumeSpace(origDims, origSForm)) { throw AlgorithmException("roi volume space doesn't match cifti volume space"); } for (int i = 0; i < (int)volList.size(); ++i) { vector myMap; origXML.getVolumeStructureMapForColumns(myMap, volList[i]); vector tempVoxList; int64_t numVoxels = (int64_t)myMap.size(); for (int64_t j = 0; j < numVoxels; ++j) { if (volRoi->getValue(myMap[j].m_ijk) > 0.0f) { tempVoxList.push_back(myMap[j].m_ijk[0]); tempVoxList.push_back(myMap[j].m_ijk[1]); tempVoxList.push_back(myMap[j].m_ijk[2]); ciftiIndexList.push_back(std::pair(myMap[j].m_ciftiIndex, newCiftiIndex)); ++newCiftiIndex; } } if (tempVoxList.size() > 0) { newXML.addVolumeModelToColumns(tempVoxList, volList[i]); } } } myCiftiOut->setCiftiXML(newXML); int numSelected = (int)ciftiIndexList.size(), numRows = myCifti->getNumberOfRows(); int numCacheRows; bool cacheFullInput = true; if (memLimitGB >= 0.0f) { numCacheRows = numRowsForMem(memLimitGB, cacheFullInput); } else { numCacheRows = numSelected; } if (numCacheRows > numSelected) numCacheRows = numSelected; if (cacheFullInput) { if (numCacheRows != numSelected) CaretLogInfo("computing " + AString::number(numCacheRows) + " rows at a time"); } else { CaretLogInfo("computing " + AString::number(numCacheRows) + " rows at a time, reading rows as needed during processing"); } vector > outRows; if (cacheFullInput) { for (int i = 0; i < numRows; ++i) { cacheRow(i); } } CaretArray indexReverse(numRows, -1); for (int startrow = 0; startrow < numSelected; startrow += numCacheRows) { int endrow = startrow + numCacheRows; if (endrow > numSelected) endrow = numSelected; outRows.resize(endrow - startrow); int curRow = 0;//because we can't trust the order threads hit the critical section for (int i = startrow; i < endrow; ++i) { if (!cacheFullInput) { cacheRow(ciftiIndexList[i].first);//preload the rows in a range which we will reuse as much as possible during one row by row scan } if (outRows[i - startrow].size() != numRows) { outRows[i - startrow] = CaretArray(numRows); } indexReverse[ciftiIndexList[i].first] = i; } #pragma omp CARET_PARFOR schedule(dynamic) for (int i = 0; i < numRows; ++i) { float movingRrs; int myrow; const float* movingRow; #pragma omp critical {//CiftiFile may explode if we request multiple rows concurrently (needs mutexes), but we should force sequential requests anyway myrow = curRow;//so, manually force it to read sequentially ++curRow; movingRow = getRow(myrow, movingRrs); } for (int j = startrow; j < endrow; ++j) { if (indexReverse[myrow] != -1)//check if we are on a row that is in the output memory range { if (indexReverse[myrow] <= j)//if so, only compute one of the elements, then store it both places { float cacheRrs; const float* cacheRow = getRow(ciftiIndexList[j].first, cacheRrs, true); outRows[j - startrow][myrow] = correlate(movingRow, movingRrs, cacheRow, cacheRrs, fisherZ); outRows[indexReverse[myrow] - startrow][ciftiIndexList[j].first] = outRows[j - startrow][myrow]; } } else { float cacheRrs; const float* cacheRow = getRow(ciftiIndexList[j].first, cacheRrs, true); outRows[j - startrow][myrow] = correlate(movingRow, movingRrs, cacheRow, cacheRrs, fisherZ); } } } for (int i = startrow; i < endrow; ++i) { myCiftiOut->setRow(outRows[i - startrow], ciftiIndexList[i].second); indexReverse[ciftiIndexList[i].first] = -1; } if (!cacheFullInput) { clearCache();//tell the cache we are going to preload a different set of rows now } } if (cacheFullInput) { clearCache();//don't currently need to do this, its just for completeness } } AlgorithmCiftiCorrelation::AlgorithmCiftiCorrelation(ProgressObject* myProgObj, const CiftiFile* myCifti, CiftiFile* myCiftiOut, const CiftiFile* ciftiRoi, const vector* weights, const bool& fisherZ, const float& memLimitGB, const bool& noDemean, const bool& covariance): AbstractAlgorithm(NULL)//HACK: get around the sentinel by passing a null, because this implementation calls another { const CiftiXML& roiXML = ciftiRoi->getCiftiXML();//roi is not optional in this variant if (roiXML.getMappingType(CiftiXML::ALONG_COLUMN) != CiftiMappingType::BRAIN_MODELS) throw AlgorithmException("cifti roi does not have brain models mapping along column"); const CiftiBrainModelsMap myDenseMap = roiXML.getBrainModelsMap(CiftiXML::ALONG_COLUMN); MetricFile leftRoi, rightRoi, cerebRoi; MetricFile* leftRoiPtr = NULL, *rightRoiPtr = NULL, *cerebRoiPtr = NULL; VolumeFile volRoi; VolumeFile* volRoiPtr = NULL; vector surfStructs = myDenseMap.getSurfaceStructureList(); for (int i = 0; i < (int)surfStructs.size(); ++i) { MetricFile* thisRoi = NULL; switch (surfStructs[i]) { case StructureEnum::CORTEX_LEFT: thisRoi = &leftRoi; leftRoiPtr = thisRoi; break; case StructureEnum::CORTEX_RIGHT: thisRoi = &rightRoi; rightRoiPtr = thisRoi; break; case StructureEnum::CEREBELLUM: thisRoi = &cerebRoi; cerebRoiPtr = thisRoi; break; default: throw AlgorithmException("structure not supported for surface type: " + StructureEnum::toName(surfStructs[i])); } AlgorithmCiftiSeparate(NULL, ciftiRoi, CiftiXML::ALONG_COLUMN, surfStructs[i], thisRoi); } if (myDenseMap.hasVolumeData()) { int64_t offsetOut[3]; AlgorithmCiftiSeparate(NULL, ciftiRoi, CiftiXML::ALONG_COLUMN, &volRoi, offsetOut, NULL, false);//don't crop, because it needs to match the original volume space in the input volRoiPtr = &volRoi; } AlgorithmCiftiCorrelation(myProgObj, myCifti, myCiftiOut, leftRoiPtr, rightRoiPtr, cerebRoiPtr, volRoiPtr, weights, fisherZ, memLimitGB, noDemean, covariance);//HACK: pass through our progress object } float AlgorithmCiftiCorrelation::correlate(const float* row1, const float& rrs1, const float* row2, const float& rrs2, const bool& fisherZ) { double r; if (row1 == row2 && !m_covariance) { r = 1.0;//short circuit for same row } else { if (m_weightedMode) { int numWeights = (int)m_weightIndexes.size(); double accum = 0.0; for (int i = 0; i < numWeights; ++i)//because we compacted the data in the row to not include any zero weights { accum += row1[i] * row2[i];//these have already had the weighted row means subtracted out, and weights applied } if (m_covariance) { if (m_binaryWeights) { r = accum / numWeights; } else { r = accum / rrs1;//NOTE: will equal rrs2 as it only depends on weights, and is not square root } } else { r = accum / (rrs1 * rrs2);//as do these } } else { double accum = 0.0; for (int i = 0; i < m_numCols; ++i) { accum += row1[i] * row2[i];//these have already had the row means subtracted out } if (m_covariance) { r = accum / m_numCols; } else { r = accum / (rrs1 * rrs2); } } } if (!m_covariance) { if (fisherZ) { if (r > 0.999999) r = 0.999999;//prevent inf if (r < -0.999999) r = -0.999999;//prevent -inf r = 0.5 * log((1 + r) / (1 - r)); } else { if (r > 1.0) r = 1.0;//don't output anything silly if (r < -1.0) r = -1.0; } } return r; } void AlgorithmCiftiCorrelation::init(const CiftiFile* input, const vector* weights, const bool& noDemean, const bool& covariance) { m_noDemean = noDemean; m_covariance = covariance; m_inputCifti = input; m_rowInfo.resize(m_inputCifti->getNumberOfRows()); m_cacheUsed = 0; m_numCols = m_inputCifti->getNumberOfColumns(); if (weights != NULL) { m_weightedMode = true; int numWeights = (int)weights->size(); if (numWeights != m_numCols) { throw AlgorithmException("number of weights doesn't match length of a row, number of weights given: " + AString::number(weights->size())); } m_binaryWeights = true; for (int i = 0; i < numWeights; ++i) { float val = (*weights)[i]; if (val != 0.0f) { if (val < 0.0f) { throw AlgorithmException("weights cannot be negative"); } m_weights.push_back(val); m_weightIndexes.push_back(i); if (val != 1.0f) { m_binaryWeights = false; } } } if (m_binaryWeights && m_weights.size() == weights->size()) { m_weightedMode = false;//all weights were 1, so switch back to normal mode } } else { m_weightedMode = false; } } void AlgorithmCiftiCorrelation::cacheRow(const int& ciftiIndex) { CaretAssertVectorIndex(m_rowInfo, ciftiIndex); if (m_rowInfo[ciftiIndex].m_cacheIndex != -1) return;//shouldn't happen, but hey if (m_cacheUsed >= (int)m_rowCache.size()) { m_rowCache.push_back(CacheRow()); m_rowCache[m_cacheUsed].m_row.resize(m_numCols); } m_rowCache[m_cacheUsed].m_ciftiIndex = ciftiIndex; float* myPtr = m_rowCache[m_cacheUsed].m_row.data(); m_inputCifti->getRow(myPtr, ciftiIndex); if (!m_rowInfo[ciftiIndex].m_haveCalculated) { computeRowStats(myPtr, m_rowInfo[ciftiIndex].m_mean, m_rowInfo[ciftiIndex].m_rootResidSqr); m_rowInfo[ciftiIndex].m_haveCalculated = true; } doSubtract(myPtr, m_rowInfo[ciftiIndex].m_mean); m_rowInfo[ciftiIndex].m_cacheIndex = m_cacheUsed; ++m_cacheUsed; } void AlgorithmCiftiCorrelation::clearCache() { for (int i = 0; i < m_cacheUsed; ++i) { m_rowInfo[m_rowCache[i].m_ciftiIndex].m_cacheIndex = -1; } m_cacheUsed = 0; } const float* AlgorithmCiftiCorrelation::getRow(const int& ciftiIndex, float& rootResidSqr, const bool& mustBeCached) { float* ret; CaretAssertVectorIndex(m_rowInfo, ciftiIndex); if (m_rowInfo[ciftiIndex].m_cacheIndex != -1) { ret = m_rowCache[m_rowInfo[ciftiIndex].m_cacheIndex].m_row.data(); } else { CaretAssert(!mustBeCached); if (mustBeCached)//largely so it doesn't give warning about unused when compiled in release { throw AlgorithmException("something very bad happened, notify the developers"); } ret = getTempRow(); m_inputCifti->getRow(ret, ciftiIndex); if (!m_rowInfo[ciftiIndex].m_haveCalculated) { computeRowStats(ret, m_rowInfo[ciftiIndex].m_mean, m_rowInfo[ciftiIndex].m_rootResidSqr); m_rowInfo[ciftiIndex].m_haveCalculated = true; } doSubtract(ret, m_rowInfo[ciftiIndex].m_mean); } rootResidSqr = m_rowInfo[ciftiIndex].m_rootResidSqr; return ret; } void AlgorithmCiftiCorrelation::computeRowStats(const float* row, float& mean, float& rootResidSqr) { double accum = 0.0;//double, for numerical stability if (m_noDemean) { mean = 0.0f; } else { if (m_weightedMode) { int weightsize = (int)m_weightIndexes.size(); if (m_binaryWeights)//because should be a little faster without multiplies or a second sum { for (int i = 0; i < weightsize; ++i) { accum += row[m_weightIndexes[i]]; } mean = accum / weightsize; } else { double accum2 = 0.0; for (int i = 0; i < weightsize; ++i) { float weight = m_weights[i]; accum += row[m_weightIndexes[i]] * weight; accum2 += weight; } mean = accum / accum2; } } else { for (int i = 0; i < m_numCols; ++i)//two pass, for numerical stability { accum += row[i]; } mean = accum / m_numCols; } } accum = 0.0; if (m_covariance) { int weightsize = (int)m_weightIndexes.size(); rootResidSqr = 0.0f; if (m_weightedMode && !m_binaryWeights) { for (int i = 0; i < weightsize; ++i) { accum += m_weights[i]; } rootResidSqr = accum;//repurpose this variable to store the weight sum - NOTE: don't take sqrt in case negative sum (whatever that means), so must not divide by both in correlate() in covariance mode } } else { if (m_weightedMode) { int weightsize = (int)m_weightIndexes.size(); if (m_binaryWeights) { for (int i = 0; i < weightsize; ++i) { float tempf = row[m_weightIndexes[i]] - mean; accum += tempf * tempf; } rootResidSqr = sqrt(accum); } else { for (int i = 0; i < weightsize; ++i) { float tempf = row[m_weightIndexes[i]] - mean; accum += tempf * tempf * m_weights[i]; } rootResidSqr = sqrt(accum); } } else { for (int i = 0; i < m_numCols; ++i) { float tempf = row[i] - mean; accum += tempf * tempf; } rootResidSqr = sqrt(accum); } } } void AlgorithmCiftiCorrelation::doSubtract(float* row, const float& mean) { if (m_noDemean) return;//skip subtracting zero from everything if (m_weightedMode) { int weightsize = (int)m_weightIndexes.size(); if (m_binaryWeights) { for (int i = 0; i < weightsize; ++i) { row[i] = row[m_weightIndexes[i]] - mean; } } else { for (int i = 0; i < weightsize; ++i) { row[i] = sqrt(m_weights[i]) * (row[m_weightIndexes[i]] - mean);//multiply by square root of weight, so that the numerator of correlation doesn't get the square of the weight } } } else { for (int i = 0; i < m_numCols; ++i) { row[i] -= mean; } } } float* AlgorithmCiftiCorrelation::getTempRow() { #ifdef CARET_OMP int oldsize = (int)m_tempRows.size(); int threadNum = omp_get_thread_num(); if (threadNum >= oldsize) { m_tempRows.resize(threadNum + 1); for (int i = oldsize; i <= threadNum; ++i) { m_tempRows[i] = CaretArray(m_numCols); } } return m_tempRows[threadNum].getArray(); #else if (m_tempRows.size() == 0) { m_tempRows.resize(1); m_tempRows[0] = CaretArray(m_numCols); } return m_tempRows[0].getArray(); #endif } int AlgorithmCiftiCorrelation::numRowsForMem(const float& memLimitGB, bool& cacheFullInput) { int numRows = m_inputCifti->getNumberOfRows(); int inrowBytes = m_numCols * sizeof(float), outrowBytes = numRows * sizeof(float); int64_t targetBytes = (int64_t)(memLimitGB * 1024 * 1024 * 1024); if (m_inputCifti->isInMemory()) targetBytes -= numRows * m_numCols * 4;//count in-memory input against the total too #ifdef CARET_OMP targetBytes -= inrowBytes * omp_get_max_threads(); #else targetBytes -= inrowBytes;//1 row in memory that isn't a reference to cache #endif targetBytes -= numRows * sizeof(RowInfo);//storage for mean, stdev, and info about caching int64_t perRowBytes = inrowBytes + outrowBytes;//cache and memory collation for output rows if (numRows * m_numCols * 4 < targetBytes * 0.7f)//if caching the entire input file would take less than 70% of remaining allotted memory, do it to reduce IO { cacheFullInput = true;//precache the entire input file, rather than caching it synchronously with the in-memory output rows targetBytes -= numRows * m_numCols * 4;//reduce the remaining total by the memory used perRowBytes = outrowBytes;//don't need to count input rows against the remaining memory total } else { cacheFullInput = false; } if (perRowBytes == 0) return 1;//protect against integer div by zero int ret = targetBytes / perRowBytes;//integer divide rounds down if (ret < 1) return 1;//always return at least one return ret; } float AlgorithmCiftiCorrelation::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmCiftiCorrelation::getSubAlgorithmWeight() { return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmCiftiCorrelation.h000066400000000000000000000104751255417355300236750ustar00rootroot00000000000000#ifndef __ALGORITHM_CIFTI_CORRELATION_H__ #define __ALGORITHM_CIFTI_CORRELATION_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "AbstractAlgorithm.h" #include "CaretPointer.h" namespace caret { class AlgorithmCiftiCorrelation : public AbstractAlgorithm { AlgorithmCiftiCorrelation(); struct CacheRow { int m_ciftiIndex; std::vector m_row; }; struct RowInfo { bool m_haveCalculated; float m_mean, m_rootResidSqr; int m_cacheIndex; RowInfo() { m_haveCalculated = false; m_cacheIndex = -1; } }; std::vector m_rowCache; std::vector m_rowInfo; std::vector > m_tempRows;//reuse return values in getRow instead of reallocating std::vector m_weights; std::vector m_weightIndexes; bool m_binaryWeights, m_weightedMode, m_noDemean, m_covariance; int m_cacheUsed;//reuse cache entries instead of reallocating them int m_numCols; const CiftiFile* m_inputCifti;//so that accesses work through the cache functions void cacheRow(const int& ciftiIndex); void computeRowStats(const float* row, float& mean, float& rootResidSqr); void doSubtract(float* row, const float& mean); void clearCache(); const float* getRow(const int& ciftiIndex, float& rootResidSqr, const bool& mustBeCached = false); float* getTempRow(); float correlate(const float* row1, const float& rrs1, const float* row2, const float& rrs2, const bool& fisherZ); void init(const CiftiFile* input, const std::vector* weights, const bool& noDemean, const bool& covariance); int numRowsForMem(const float& memLimitGB, bool& cacheFullInput); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmCiftiCorrelation(ProgressObject* myProgObj, const CiftiFile* myCifti, CiftiFile* myCiftiOut, const std::vector* weights = NULL, const bool& fisherZ = false, const float& memLimitGB = -1.0f, const bool& noDemean = false, const bool& covariance = false); AlgorithmCiftiCorrelation(ProgressObject* myProgObj, const CiftiFile* myCifti, CiftiFile* myCiftiOut, const MetricFile* leftRoi, const MetricFile* rightRoi = NULL, const MetricFile* cerebRoi = NULL, const VolumeFile* volRoi = NULL, const std::vector* weights = NULL, const bool& fisherZ = false, const float& memLimitGB = -1.0f, const bool& noDemean = false, const bool& covariance = false); AlgorithmCiftiCorrelation(ProgressObject* myProgObj, const CiftiFile* myCifti, CiftiFile* myCiftiOut, const CiftiFile* ciftiRoi, const std::vector* weights = NULL, const bool& fisherZ = false, const float& memLimitGB = -1.0f, const bool& noDemean = false, const bool& covariance = false); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmCiftiCorrelation; } #endif //__ALGORITHM_CIFTI_CORRELATION_H__ workbench-1.1.1/src/Algorithms/AlgorithmCiftiCorrelationGradient.cxx000066400000000000000000001407021255417355300257230ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmCiftiCorrelationGradient.h" #include "AlgorithmException.h" #include "AlgorithmMetricGradient.h" #include "MetricSmoothingObject.h" #include "AlgorithmVolumeGradient.h" #include "CaretLogger.h" #include "CaretOMP.h" #include "CiftiFile.h" #include "GeodesicHelper.h" #include "MetricFile.h" #include "SurfaceFile.h" #include "Vector3D.h" #include "VolumeFile.h" #include using namespace caret; using namespace std; AString AlgorithmCiftiCorrelationGradient::getCommandSwitch() { return "-cifti-correlation-gradient"; } AString AlgorithmCiftiCorrelationGradient::getShortDescription() { return "CORRELATE CIFTI ROWS AND TAKE GRADIENT"; } OperationParameters* AlgorithmCiftiCorrelationGradient::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiParameter(1, "cifti", "the input cifti"); ret->addCiftiOutputParameter(2, "cifti-out", "the output cifti"); OptionalParameter* leftSurfOpt = ret->createOptionalParameter(3, "-left-surface", "specify the left surface to use"); leftSurfOpt->addSurfaceParameter(1, "surface", "the left surface file"); OptionalParameter* leftCorrAreasOpt = leftSurfOpt->createOptionalParameter(2, "-left-corrected-areas", "vertex areas to use instead of computing them from the left surface"); leftCorrAreasOpt->addMetricParameter(1, "area-metric", "the corrected vertex areas, as a metric"); OptionalParameter* rightSurfOpt = ret->createOptionalParameter(4, "-right-surface", "specify the right surface to use"); rightSurfOpt->addSurfaceParameter(1, "surface", "the right surface file"); OptionalParameter* rightCorrAreasOpt = rightSurfOpt->createOptionalParameter(2, "-right-corrected-areas", "vertex areas to use instead of computing them from the right surface"); rightCorrAreasOpt->addMetricParameter(1, "area-metric", "the corrected vertex areas, as a metric"); OptionalParameter* cerebSurfaceOpt = ret->createOptionalParameter(5, "-cerebellum-surface", "specify the cerebellum surface to use"); cerebSurfaceOpt->addSurfaceParameter(1, "surface", "the cerebellum surface file"); OptionalParameter* cerebCorrAreasOpt = cerebSurfaceOpt->createOptionalParameter(2, "-cerebellum-corrected-areas", "vertex areas to use instead of computing them from the cerebellum surface"); cerebCorrAreasOpt->addMetricParameter(1, "area-metric", "the corrected vertex areas, as a metric"); OptionalParameter* presmoothSurfOpt = ret->createOptionalParameter(6, "-surface-presmooth", "smooth on the surface before computing the gradient"); presmoothSurfOpt->addDoubleParameter(1, "surface-kernel", "the sigma for the gaussian surface smoothing kernel, in mm"); OptionalParameter* presmoothVolOpt = ret->createOptionalParameter(7, "-volume-presmooth", "smooth the volume before computing the gradient"); presmoothVolOpt->addDoubleParameter(1, "volume-kernel", "the sigma for the gaussian volume smoothing kernel, in mm"); ret->createOptionalParameter(8, "-undo-fisher-z", "apply the inverse fisher small z transform to the input"); ret->createOptionalParameter(12, "-fisher-z", "apply the fisher small z transform to the correlations before taking the gradient"); OptionalParameter* surfaceExcludeOpt = ret->createOptionalParameter(9, "-surface-exclude", "exclude vertices near each seed vertex from computation"); surfaceExcludeOpt->addDoubleParameter(1, "distance", "geodesic distance from seed vertex for the exclusion zone, in mm"); OptionalParameter* volumeExcludeOpt = ret->createOptionalParameter(10, "-volume-exclude", "exclude voxels near each seed voxel from computation"); volumeExcludeOpt->addDoubleParameter(1, "distance", "distance from seed voxel for the exclusion zone, in mm"); ret->createOptionalParameter(13, "-covariance", "compute covariance instead of correlation"); OptionalParameter* memLimitOpt = ret->createOptionalParameter(11, "-mem-limit", "restrict memory usage"); memLimitOpt->addDoubleParameter(1, "limit-GB", "memory limit in gigabytes"); ret->setHelpText( AString("For each structure, compute the correlation of the rows in the structure, and take the gradients of ") + "the resulting rows, then average them. " + "Memory limit does not need to be an integer, you may also specify 0 to use as little memory as possible (this may be very slow)." ); return ret; } void AlgorithmCiftiCorrelationGradient::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { CiftiFile* myCifti = myParams->getCifti(1); CiftiFile* myCiftiOut = myParams->getOutputCifti(2); SurfaceFile* myLeftSurf = NULL, *myRightSurf = NULL, *myCerebSurf = NULL; MetricFile* myLeftAreas = NULL, *myRightAreas = NULL, *myCerebAreas = NULL; OptionalParameter* leftSurfOpt = myParams->getOptionalParameter(3); if (leftSurfOpt->m_present) { myLeftSurf = leftSurfOpt->getSurface(1); OptionalParameter* leftCorrAreasOpt = leftSurfOpt->getOptionalParameter(2); if (leftCorrAreasOpt->m_present) { myLeftAreas = leftCorrAreasOpt->getMetric(1); } } OptionalParameter* rightSurfOpt = myParams->getOptionalParameter(4); if (rightSurfOpt->m_present) { myRightSurf = rightSurfOpt->getSurface(1); OptionalParameter* rightCorrAreasOpt = rightSurfOpt->getOptionalParameter(2); if (rightCorrAreasOpt->m_present) { myRightAreas = rightCorrAreasOpt->getMetric(1); } } OptionalParameter* cerebSurfOpt = myParams->getOptionalParameter(5); if (cerebSurfOpt->m_present) { myCerebSurf = cerebSurfOpt->getSurface(1); OptionalParameter* cerebCorrAreasOpt = cerebSurfOpt->getOptionalParameter(2); if (cerebCorrAreasOpt->m_present) { myCerebAreas = cerebCorrAreasOpt->getMetric(1); } } float surfKern = -1.0f; OptionalParameter* presmoothSurfOpt = myParams->getOptionalParameter(6); if (presmoothSurfOpt->m_present) { surfKern = (float)presmoothSurfOpt->getDouble(1); } float volKern = -1.0f; OptionalParameter* presmoothVolOpt = myParams->getOptionalParameter(7); if (presmoothVolOpt->m_present) { volKern = (float)presmoothVolOpt->getDouble(1); } bool undoFisherInput = myParams->getOptionalParameter(8)->m_present; bool applyFisher = myParams->getOptionalParameter(12)->m_present; float surfaceExclude = -1.0f; OptionalParameter* surfaceExcludeOpt = myParams->getOptionalParameter(9); if (surfaceExcludeOpt->m_present) { surfaceExclude = (float)surfaceExcludeOpt->getDouble(1); if (surfaceExclude < 0.0f) { throw AlgorithmException("surface exclude distance cannot be negative"); } } float volumeExclude = -1.0f; OptionalParameter* volumeExcludeOpt = myParams->getOptionalParameter(10); if (volumeExcludeOpt->m_present) { volumeExclude = (float)volumeExcludeOpt->getDouble(1); if (volumeExclude < 0.0f) { throw AlgorithmException("volume exclude distance cannot be negative"); } } float memLimitGB = -1.0f; OptionalParameter* memLimitOpt = myParams->getOptionalParameter(11); if (memLimitOpt->m_present) { memLimitGB = (float)memLimitOpt->getDouble(1); if (memLimitGB < 0.0f) { throw AlgorithmException("memory limit cannot be negative"); } } bool covariance = myParams->getOptionalParameter(13)->m_present; AlgorithmCiftiCorrelationGradient(myProgObj, myCifti, myCiftiOut, myLeftSurf, myRightSurf, myCerebSurf, myLeftAreas, myRightAreas, myCerebAreas, surfKern, volKern, undoFisherInput, applyFisher, surfaceExclude, volumeExclude, covariance, memLimitGB); } AlgorithmCiftiCorrelationGradient::AlgorithmCiftiCorrelationGradient(ProgressObject* myProgObj, const CiftiFile* myCifti, CiftiFile* myCiftiOut, SurfaceFile* myLeftSurf, SurfaceFile* myRightSurf, SurfaceFile* myCerebSurf, const MetricFile* myLeftAreas, const MetricFile* myRightAreas, const MetricFile* myCerebAreas, const float& surfKern, const float& volKern, const bool& undoFisherInput, const bool& applyFisher, const float& surfaceExclude, const float& volumeExclude, const bool& covariance, const float& memLimitGB) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); init(myCifti, undoFisherInput, applyFisher, covariance); const CiftiXMLOld& myXML = myCifti->getCiftiXMLOld(); CiftiXMLOld myNewXML = myXML; myNewXML.resetDirectionToScalars(CiftiXMLOld::ALONG_ROW, 1); myNewXML.setMapNameForIndex(CiftiXMLOld::ALONG_ROW, 0, "gradient"); myCiftiOut->setCiftiXML(myNewXML); vector surfaceList, volumeList; myXML.getStructureListsForColumns(surfaceList, volumeList); for (int whichStruct = 0; whichStruct < (int)surfaceList.size(); ++whichStruct) {//sanity check surfaces SurfaceFile* mySurf = NULL; const MetricFile* myAreas = NULL; AString surfType; switch (surfaceList[whichStruct]) { case StructureEnum::CORTEX_LEFT: mySurf = myLeftSurf; myAreas = myLeftAreas; surfType = "left"; break; case StructureEnum::CORTEX_RIGHT: mySurf = myRightSurf; myAreas = myRightAreas; surfType = "right"; break; case StructureEnum::CEREBELLUM: mySurf = myCerebSurf; myAreas = myCerebAreas; surfType = "cerebellum"; break; default: throw AlgorithmException("found surface model with incorrect type: " + StructureEnum::toName(surfaceList[whichStruct])); break; } if (mySurf == NULL) { throw AlgorithmException(surfType + " surface required but not provided"); } if (mySurf->getNumberOfNodes() != myCifti->getCiftiXML().getBrainModelsMap(CiftiXML::ALONG_COLUMN).getSurfaceNumberOfNodes(surfaceList[whichStruct])) { throw AlgorithmException(surfType + " surface has the wrong number of vertices"); } if (myAreas != NULL && myAreas->getNumberOfNodes() != mySurf->getNumberOfNodes()) { throw AlgorithmException(surfType + " corrected vertex areas metric has the wrong number of vertices"); } } for (int whichStruct = 0; whichStruct < (int)surfaceList.size(); ++whichStruct) { SurfaceFile* mySurf = NULL; const MetricFile* myAreas = NULL; switch (surfaceList[whichStruct]) { case StructureEnum::CORTEX_LEFT: mySurf = myLeftSurf; myAreas = myLeftAreas; break; case StructureEnum::CORTEX_RIGHT: mySurf = myRightSurf; myAreas = myRightAreas; break; case StructureEnum::CEREBELLUM: mySurf = myCerebSurf; myAreas = myCerebAreas; break; default: break; } if (surfaceExclude > 0.0f) { processSurfaceComponent(surfaceList[whichStruct], surfKern, surfaceExclude, memLimitGB, mySurf, myAreas); } else { processSurfaceComponent(surfaceList[whichStruct], surfKern, memLimitGB, mySurf, myAreas); } } for (int whichStruct = 0; whichStruct < (int)volumeList.size(); ++whichStruct) { if (volumeExclude > 0.0f) { processVolumeComponent(volumeList[whichStruct], volKern, volumeExclude, memLimitGB); } else { processVolumeComponent(volumeList[whichStruct], volKern, memLimitGB); } } myCiftiOut->setColumn(m_outColumn.data(), 0); } void AlgorithmCiftiCorrelationGradient::processSurfaceComponent(StructureEnum::Enum& myStructure, const float& surfKern, const float& memLimitGB, SurfaceFile* mySurf, const MetricFile* myAreas) { const CiftiXMLOld& myXML = m_inputCifti->getCiftiXMLOld(); vector myMap; myXML.getSurfaceMapForColumns(myMap, myStructure); int mapSize = (int)myMap.size(); vector accum(mapSize, 0.0); int numCacheRows = mapSize; bool cacheFullInput = true; if (memLimitGB >= 0.0f) { numCacheRows = numRowsForMem(memLimitGB, m_numCols * sizeof(float), (mySurf->getNumberOfNodes() * (sizeof(float) * 8 + 1)) / 8, mapSize, cacheFullInput); } if (numCacheRows > mapSize) { cacheFullInput = true; numCacheRows = mapSize; } if (cacheFullInput) { if (numCacheRows != mapSize) CaretLogInfo("computing " + AString::number(numCacheRows) + " rows at a time"); } else { CaretLogInfo("computing " + AString::number(numCacheRows) + " rows at a time, reading rows as needed during processing"); } const float* areaData = NULL; if (myAreas != NULL) { areaData = myAreas->getValuePointerForColumn(0); } MetricFile myRoi; myRoi.setNumberOfNodesAndColumns(mySurf->getNumberOfNodes(), 1); myRoi.initializeColumn(0); vector rowsToCache; for (int i = 0; i < mapSize; ++i) { myRoi.setValue(myMap[i].m_surfaceNode, 0, 1.0f); if (cacheFullInput) { rowsToCache.push_back(myMap[i].m_ciftiIndex); } } if (cacheFullInput) { cacheRows(rowsToCache); } CaretPointer mySmooth; if (surfKern > 0.0f) { mySmooth.grabNew(new MetricSmoothingObject(mySurf, surfKern, &myRoi, MetricSmoothingObject::GEO_GAUSS_AREA, areaData));//computes the smoothing weights only once per surface } for (int startpos = 0; startpos < mapSize; startpos += numCacheRows) { int endpos = startpos + numCacheRows; if (endpos > mapSize) endpos = mapSize; if (!cacheFullInput) { rowsToCache.clear(); for (int i = startpos; i < endpos; ++i) { rowsToCache.push_back(myMap[i].m_ciftiIndex); } cacheRows(rowsToCache); } int curRow = 0;//because we can't trust the order threads hit the critical section MetricFile computeMetric; computeMetric.setNumberOfNodesAndColumns(mySurf->getNumberOfNodes(), endpos - startpos); #pragma omp CARET_PARFOR schedule(dynamic) for (int i = 0; i < mapSize; ++i) { float movingRrs; const float* movingRow; int myrow; #pragma omp critical {//CiftiFile may explode if we request multiple rows concurrently (needs mutexes), but we should force sequential requests anyway myrow = curRow;//so, manually force it to read sequentially ++curRow; movingRow = getRow(myMap[myrow].m_ciftiIndex, movingRrs); } for (int j = startpos; j < endpos; ++j) { if (myrow >= startpos && myrow < endpos) { if (j >= myrow) { float cacheRrs; const float* cacheRow = getRow(myMap[j].m_ciftiIndex, cacheRrs, true); float result = correlate(movingRow, movingRrs, cacheRow, cacheRrs); computeMetric.setValue(myMap[myrow].m_surfaceNode, j - startpos, result); computeMetric.setValue(myMap[j].m_surfaceNode, myrow - startpos, result); } } else { float cacheRrs; const float* cacheRow = getRow(myMap[j].m_ciftiIndex, cacheRrs, true); float result = correlate(movingRow, movingRrs, cacheRow, cacheRrs); computeMetric.setValue(myMap[myrow].m_surfaceNode, j - startpos, result); } } } int numMetricCols = endpos - startpos; MetricFile outputMetric, outputMetric2; for (int j = 0; j < numMetricCols; ++j) { const float* myCol; if (surfKern > 0.0f) { mySmooth->smoothColumn(&computeMetric, j, &outputMetric); AlgorithmMetricGradient(NULL, mySurf, &outputMetric, &outputMetric2, NULL, -1.0f, &myRoi, false, -1, myAreas); myCol = outputMetric2.getValuePointerForColumn(0); } else { AlgorithmMetricGradient(NULL, mySurf, &computeMetric, &outputMetric, NULL, -1.0f, &myRoi, false, j, myAreas); myCol = outputMetric.getValuePointerForColumn(0); } for (int i = 0; i < mapSize; ++i) { const float* roiColumn = myRoi.getValuePointerForColumn(0); if (roiColumn[myMap[i].m_surfaceNode] > 0.0f) { accum[i] += myCol[myMap[i].m_surfaceNode]; } } } } for (int i = 0; i < mapSize; ++i) { m_outColumn[myMap[i].m_ciftiIndex] = accum[i] / mapSize; } } void AlgorithmCiftiCorrelationGradient::processSurfaceComponent(StructureEnum::Enum& myStructure, const float& surfKern, const float& surfExclude, const float& memLimitGB, SurfaceFile* mySurf, const MetricFile* myAreas) { const CiftiXMLOld& myXML = m_inputCifti->getCiftiXMLOld(); vector myMap; myXML.getSurfaceMapForColumns(myMap, myStructure); int mapSize = (int)myMap.size(); vector accum(mapSize, 0.0); vector accumCount(mapSize, 0); int numCacheRows = mapSize; bool cacheFullInput = true; if (memLimitGB >= 0.0f) { numCacheRows = numRowsForMem(memLimitGB, m_numCols * sizeof(float), (mySurf->getNumberOfNodes() * (sizeof(float) * 8 + 1)) / 8, mapSize, cacheFullInput); } if (numCacheRows > mapSize) { cacheFullInput = true; numCacheRows = mapSize; } if (cacheFullInput) { if (numCacheRows != mapSize) CaretLogInfo("computing " + AString::number(numCacheRows) + " rows at a time"); } else { CaretLogInfo("computing " + AString::number(numCacheRows) + " rows at a time, reading rows as needed during processing"); } const float* areaData = NULL; if (myAreas != NULL) { areaData = myAreas->getValuePointerForColumn(0); } CaretPointer myGeoBase(new GeodesicHelperBase(mySurf, areaData));//can't really have SurfaceFile cache ones with corrected areas MetricFile myRoi; myRoi.setNumberOfNodesAndColumns(mySurf->getNumberOfNodes(), 1); myRoi.initializeColumn(0); vector > roiLookup(numCacheRows);//this gets bit compressed vector origRoi(mySurf->getNumberOfNodes()); vector > excludeNodes(numCacheRows); vector rowsToCache; for (int i = 0; i < mapSize; ++i) { myRoi.setValue(myMap[i].m_surfaceNode, 0, 1.0f); if (cacheFullInput) { rowsToCache.push_back(myMap[i].m_ciftiIndex); } } if (cacheFullInput) { cacheRows(rowsToCache); } CaretPointer mySmooth; if (surfKern > 0.0f) { mySmooth.grabNew(new MetricSmoothingObject(mySurf, surfKern, &myRoi, MetricSmoothingObject::GEO_GAUSS_AREA, areaData));//computes the smoothing weights only once per surface } for (int startpos = 0; startpos < mapSize; startpos += numCacheRows) { int endpos = startpos + numCacheRows; if (endpos > mapSize) endpos = mapSize; if (!cacheFullInput) { rowsToCache.clear(); for (int i = startpos; i < endpos; ++i) { rowsToCache.push_back(myMap[i].m_ciftiIndex); } cacheRows(rowsToCache); } int numSurfNodes = mySurf->getNumberOfNodes(); #pragma omp CARET_PAR { vector distances; CaretPointer myGeoHelp(new GeodesicHelper(myGeoBase)); #pragma omp CARET_FOR for (int i = startpos; i < endpos; ++i) { vector& excludeRef = excludeNodes[i - startpos]; myGeoHelp->getNodesToGeoDist(myMap[i].m_surfaceNode, surfExclude, excludeRef, distances); vector& lookupRef = roiLookup[i - startpos]; lookupRef.resize(numSurfNodes); for (int j = 0; j < numSurfNodes; ++j) { lookupRef[j] = (myRoi.getValue(j, 0) > 0.0f); } int numExclude = excludeRef.size(); for (int j = 0; j < numExclude; ++j) { lookupRef[excludeRef[j]] = false; } } } int curRow = 0;//because we can't trust the order threads hit the critical section MetricFile computeMetric; computeMetric.setNumberOfNodesAndColumns(mySurf->getNumberOfNodes(), endpos - startpos); #pragma omp CARET_PARFOR schedule(dynamic) for (int i = 0; i < mapSize; ++i) { float movingRrs; const float* movingRow; int myrow; #pragma omp critical {//CiftiFile may explode if we request multiple rows concurrently (needs mutexes), but we should force sequential requests anyway myrow = curRow;//so, manually force it to read sequentially ++curRow; movingRow = getRow(myMap[myrow].m_ciftiIndex, movingRrs); } for (int j = startpos; j < endpos; ++j) { if (roiLookup[j - startpos][myMap[myrow].m_surfaceNode]) { if (myrow >= startpos && myrow < endpos) { if (j >= myrow) { float cacheRrs; const float* cacheRow = getRow(myMap[j].m_ciftiIndex, cacheRrs, true); float result = correlate(movingRow, movingRrs, cacheRow, cacheRrs); computeMetric.setValue(myMap[myrow].m_surfaceNode, j - startpos, result); computeMetric.setValue(myMap[j].m_surfaceNode, myrow - startpos, result); } } else { float cacheRrs; const float* cacheRow = getRow(myMap[j].m_ciftiIndex, cacheRrs, true); float result = correlate(movingRow, movingRrs, cacheRow, cacheRrs); computeMetric.setValue(myMap[myrow].m_surfaceNode, j - startpos, result); } } } } int numMetricCols = endpos - startpos; MetricFile outputMetric, outputMetric2; MetricFile excludeRoi = myRoi; for (int j = 0; j < numMetricCols; ++j) { int numExclude = (int)excludeNodes[j].size(); const float* myCol; for (int k = 0; k < numExclude; ++k) { excludeRoi.setValue(excludeNodes[j][k], 0, 0.0f);//exclude the nodes near the seed node } if (surfKern > 0.0f) { mySmooth->smoothColumn(&computeMetric, j, &outputMetric, &excludeRoi); AlgorithmMetricGradient(NULL, mySurf, &outputMetric, &outputMetric2, NULL, -1.0f, &excludeRoi, false, -1, myAreas); myCol = outputMetric2.getValuePointerForColumn(0); } else { AlgorithmMetricGradient(NULL, mySurf, &computeMetric, &outputMetric, NULL, -1.0f, &excludeRoi, false, j, myAreas); myCol = outputMetric.getValuePointerForColumn(0); } for (int i = 0; i < mapSize; ++i) { const float* roiColumn = excludeRoi.getValuePointerForColumn(0); if (roiColumn[myMap[i].m_surfaceNode] > 0.0f) { accum[i] += myCol[myMap[i].m_surfaceNode]; accumCount[i] += 1;//less dubious looking than ++accumCount[i] } } for (int k = 0; k < numExclude; ++k) { excludeRoi.setValue(excludeNodes[j][k], 0, myRoi.getValue(excludeNodes[j][k], 0));//and set them back to original roi afterwards, instead of a full reinitialize } } } for (int i = 0; i < mapSize; ++i) { if (accumCount[i] != 0) { m_outColumn[myMap[i].m_ciftiIndex] = accum[i] / accumCount[i]; } else { m_outColumn[myMap[i].m_ciftiIndex] = 0.0f; } } } void AlgorithmCiftiCorrelationGradient::processVolumeComponent(StructureEnum::Enum& myStructure, const float& volKern, const float& memLimitGB) { const CiftiXMLOld& myXML = m_inputCifti->getCiftiXMLOld(); vector myMap; myXML.getVolumeStructureMapForColumns(myMap, myStructure); int mapSize = (int)myMap.size(); vector accum(mapSize, 0.0); int numCacheRows = mapSize; bool cacheFullInput = true; vector newdims; int64_t offset[3]; if (mapSize > 0) {//make a voxel bounding box to minimize memory usage int extrema[6] = { myMap[0].m_ijk[0], myMap[0].m_ijk[0], myMap[0].m_ijk[1], myMap[0].m_ijk[1], myMap[0].m_ijk[2], myMap[0].m_ijk[2] }; for (int64_t i = 1; i < mapSize; ++i) { if (myMap[i].m_ijk[0] < extrema[0]) extrema[0] = myMap[i].m_ijk[0]; if (myMap[i].m_ijk[0] > extrema[1]) extrema[1] = myMap[i].m_ijk[0]; if (myMap[i].m_ijk[1] < extrema[2]) extrema[2] = myMap[i].m_ijk[1]; if (myMap[i].m_ijk[1] > extrema[3]) extrema[3] = myMap[i].m_ijk[1]; if (myMap[i].m_ijk[2] < extrema[4]) extrema[4] = myMap[i].m_ijk[2]; if (myMap[i].m_ijk[2] > extrema[5]) extrema[5] = myMap[i].m_ijk[2]; } newdims.push_back(extrema[1] - extrema[0] + 1); newdims.push_back(extrema[3] - extrema[2] + 1); newdims.push_back(extrema[5] - extrema[4] + 1); offset[0] = extrema[0]; offset[1] = extrema[2]; offset[2] = extrema[4]; } else { return; } if (memLimitGB >= 0.0f) { numCacheRows = numRowsForMem(memLimitGB, m_numCols * sizeof(float), newdims[0] * newdims[1] * newdims[2] * sizeof(float), mapSize, cacheFullInput); } if (numCacheRows > mapSize) { cacheFullInput = true; numCacheRows = mapSize; } if (cacheFullInput) { if (numCacheRows != mapSize) CaretLogInfo("computing " + AString::number(numCacheRows) + " rows at a time"); } else { CaretLogInfo("computing " + AString::number(numCacheRows) + " rows at a time, reading rows as needed during processing"); } int64_t ciftiDims[3]; vector > ciftiSform; myXML.getVolumeDimsAndSForm(ciftiDims, ciftiSform); VolumeFile volRoi(newdims, ciftiSform); volRoi.setValueAllVoxels(0.0f); vector rowsToCache; for (int i = 0; i < mapSize; ++i) { volRoi.setValue(1.0f, myMap[i].m_ijk[0] - offset[0], myMap[i].m_ijk[1] - offset[1], myMap[i].m_ijk[2] - offset[2]); if (cacheFullInput) { rowsToCache.push_back(myMap[i].m_ciftiIndex); } } if (cacheFullInput) { cacheRows(rowsToCache); } for (int startpos = 0; startpos < mapSize; startpos += numCacheRows) { int endpos = startpos + numCacheRows; if (endpos > mapSize) endpos = mapSize; if (!cacheFullInput) { rowsToCache.clear(); for (int i = startpos; i < endpos; ++i) { rowsToCache.push_back(myMap[i].m_ciftiIndex); } cacheRows(rowsToCache); } int curRow = 0;//because we can't trust the order threads hit the critical section vector computeDims = newdims; computeDims.push_back(endpos - startpos); VolumeFile computeVol(computeDims, ciftiSform); #pragma omp CARET_PARFOR schedule(dynamic) for (int i = 0; i < mapSize; ++i) { float movingRrs; const float* movingRow; int myrow; #pragma omp critical {//CiftiFile may explode if we request multiple rows concurrently (needs mutexes), but we should force sequential requests anyway myrow = curRow;//so, manually force it to read sequentially ++curRow; movingRow = getRow(myMap[myrow].m_ciftiIndex, movingRrs); } for (int j = startpos; j < endpos; ++j) { if (myrow >= startpos && myrow < endpos) { if (j >= myrow) { float cacheRrs; const float* cacheRow = getRow(myMap[j].m_ciftiIndex, cacheRrs, true); float result = correlate(movingRow, movingRrs, cacheRow, cacheRrs); computeVol.setValue(result, myMap[myrow].m_ijk[0] - offset[0], myMap[myrow].m_ijk[1] - offset[1], myMap[myrow].m_ijk[2] - offset[2], j - startpos); computeVol.setValue(result, myMap[j].m_ijk[0] - offset[0], myMap[j].m_ijk[1] - offset[1], myMap[j].m_ijk[2] - offset[2], myrow - startpos); } } else { float cacheRrs; const float* cacheRow = getRow(myMap[j].m_ciftiIndex, cacheRrs, true); float result = correlate(movingRow, movingRrs, cacheRow, cacheRrs); computeVol.setValue(result, myMap[myrow].m_ijk[0] - offset[0], myMap[myrow].m_ijk[1] - offset[1], myMap[myrow].m_ijk[2] - offset[2], j - startpos); } } } VolumeFile outputVol; int numSubvols = endpos - startpos; for (int j = 0; j < numSubvols; ++j) { AlgorithmVolumeGradient(NULL, &computeVol, &outputVol, volKern, &volRoi, NULL, j); for (int i = 0; i < mapSize; ++i) { accum[i] += outputVol.getValue(myMap[i].m_ijk[0] - offset[0], myMap[i].m_ijk[1] - offset[1], myMap[i].m_ijk[2] - offset[2]); } } } for (int i = 0; i < mapSize; ++i) { m_outColumn[myMap[i].m_ciftiIndex] = accum[i] / mapSize; } } void AlgorithmCiftiCorrelationGradient::processVolumeComponent(StructureEnum::Enum& myStructure, const float& volKern, const float& volExclude, const float& memLimitGB) { const CiftiXMLOld& myXML = m_inputCifti->getCiftiXMLOld(); vector myMap; myXML.getVolumeStructureMapForColumns(myMap, myStructure); int mapSize = (int)myMap.size(); vector accum(mapSize, 0.0); vector accumCount(mapSize, 0); int numCacheRows = mapSize; bool cacheFullInput = true; vector newdims; int64_t offset[3]; if (mapSize > 0) {//make a voxel bounding box to minimize memory usage int extrema[6] = { myMap[0].m_ijk[0], myMap[0].m_ijk[0], myMap[0].m_ijk[1], myMap[0].m_ijk[1], myMap[0].m_ijk[2], myMap[0].m_ijk[2] }; for (int64_t i = 1; i < mapSize; ++i) { if (myMap[i].m_ijk[0] < extrema[0]) extrema[0] = myMap[i].m_ijk[0]; if (myMap[i].m_ijk[0] > extrema[1]) extrema[1] = myMap[i].m_ijk[0]; if (myMap[i].m_ijk[1] < extrema[2]) extrema[2] = myMap[i].m_ijk[1]; if (myMap[i].m_ijk[1] > extrema[3]) extrema[3] = myMap[i].m_ijk[1]; if (myMap[i].m_ijk[2] < extrema[4]) extrema[4] = myMap[i].m_ijk[2]; if (myMap[i].m_ijk[2] > extrema[5]) extrema[5] = myMap[i].m_ijk[2]; } newdims.push_back(extrema[1] - extrema[0] + 1); newdims.push_back(extrema[3] - extrema[2] + 1); newdims.push_back(extrema[5] - extrema[4] + 1); offset[0] = extrema[0]; offset[1] = extrema[2]; offset[2] = extrema[4]; } else { return; } if (memLimitGB >= 0.0f) { numCacheRows = numRowsForMem(memLimitGB, m_numCols * sizeof(float), newdims[0] * newdims[1] * newdims[2] * sizeof(float), mapSize, cacheFullInput); } if (numCacheRows > mapSize) { cacheFullInput = true; numCacheRows = mapSize; } if (cacheFullInput) { if (numCacheRows != mapSize) CaretLogInfo("computing " + AString::number(numCacheRows) + " rows at a time"); } else { CaretLogInfo("computing " + AString::number(numCacheRows) + " rows at a time, reading rows as needed during processing"); } int64_t ciftiDims[3]; vector > ciftiSform; myXML.getVolumeDimsAndSForm(ciftiDims, ciftiSform); VolumeFile volRoi(newdims, ciftiSform); volRoi.setValueAllVoxels(0.0f); vector rowsToCache; for (int i = 0; i < mapSize; ++i) { volRoi.setValue(1.0f, myMap[i].m_ijk[0] - offset[0], myMap[i].m_ijk[1] - offset[1], myMap[i].m_ijk[2] - offset[2]); if (cacheFullInput) { rowsToCache.push_back(myMap[i].m_ciftiIndex); } } if (cacheFullInput) { cacheRows(rowsToCache); } for (int startpos = 0; startpos < mapSize; startpos += numCacheRows) { int endpos = startpos + numCacheRows; if (endpos > mapSize) endpos = mapSize; if (!cacheFullInput) { rowsToCache.clear(); for (int i = startpos; i < endpos; ++i) { rowsToCache.push_back(myMap[i].m_ciftiIndex); } cacheRows(rowsToCache); } int curRow = 0;//because we can't trust the order threads hit the critical section vector computeDims = newdims; computeDims.push_back(endpos - startpos); VolumeFile computeVol(computeDims, ciftiSform); #pragma omp CARET_PARFOR schedule(dynamic) for (int i = 0; i < mapSize; ++i) { float movingRrs; const float* movingRow; int myrow; #pragma omp critical {//CiftiFile may explode if we request multiple rows concurrently (needs mutexes), but we should force sequential requests anyway myrow = curRow;//so, manually force it to read sequentially ++curRow; movingRow = getRow(myMap[myrow].m_ciftiIndex, movingRrs); } Vector3D movingLoc; volRoi.indexToSpace(myMap[myrow].m_ijk, movingLoc);//NOTE: this is outside the cropped volume, but matches the real location in the full volume, because we didn't fix the center for (int j = startpos; j < endpos; ++j) { Vector3D seedLoc; volRoi.indexToSpace(myMap[j].m_ijk, seedLoc);//ditto if ((movingLoc - seedLoc).length() > volExclude)//don't correlate if closer than the exclude range { if (myrow >= startpos && myrow < endpos) { if (j >= myrow) { float cacheRrs; const float* cacheRow = getRow(myMap[j].m_ciftiIndex, cacheRrs, true); float result = correlate(movingRow, movingRrs, cacheRow, cacheRrs); computeVol.setValue(result, myMap[myrow].m_ijk[0] - offset[0], myMap[myrow].m_ijk[1] - offset[1], myMap[myrow].m_ijk[2] - offset[2], j - startpos); computeVol.setValue(result, myMap[j].m_ijk[0] - offset[0], myMap[j].m_ijk[1] - offset[1], myMap[j].m_ijk[2] - offset[2], myrow - startpos); } } else { float cacheRrs; const float* cacheRow = getRow(myMap[j].m_ciftiIndex, cacheRrs, true); float result = correlate(movingRow, movingRrs, cacheRow, cacheRrs); computeVol.setValue(result, myMap[myrow].m_ijk[0] - offset[0], myMap[myrow].m_ijk[1] - offset[1], myMap[myrow].m_ijk[2] - offset[2], j - startpos); } } } } VolumeFile outputVol, excludeRoi(newdims, ciftiSform); excludeRoi.setFrame(volRoi.getFrame()); int numSubvols = endpos - startpos; for (int j = 0; j < numSubvols; ++j) { Vector3D seedLoc; volRoi.indexToSpace(myMap[j + startpos].m_ijk, seedLoc); for (int i = 0; i < mapSize; ++i) { Vector3D otherLoc; volRoi.indexToSpace(myMap[i].m_ijk, otherLoc); if ((otherLoc - seedLoc).length() <= volExclude) { excludeRoi.setValue(0.0f, myMap[i].m_ijk[0] - offset[0], myMap[i].m_ijk[1] - offset[1], myMap[i].m_ijk[2] - offset[2]); } } AlgorithmVolumeGradient(NULL, &computeVol, &outputVol, volKern, &excludeRoi, NULL, j); for (int i = 0; i < mapSize; ++i) { if (excludeRoi.getValue(myMap[i].m_ijk[0] - offset[0], myMap[i].m_ijk[1] - offset[1], myMap[i].m_ijk[2] - offset[2]) > 0.0f) { float val = outputVol.getValue(myMap[i].m_ijk[0] - offset[0], myMap[i].m_ijk[1] - offset[1], myMap[i].m_ijk[2] - offset[2]); accum[i] += val; accumCount[i] += 1; } else { excludeRoi.setValue(1.0f, myMap[i].m_ijk[0] - offset[0], myMap[i].m_ijk[1] - offset[1], myMap[i].m_ijk[2] - offset[2]);//reset the ROI } } } } for (int i = 0; i < mapSize; ++i) { if (accumCount[i] != 0) { m_outColumn[myMap[i].m_ciftiIndex] = accum[i] / accumCount[i]; } else { m_outColumn[myMap[i].m_ciftiIndex] = 0.0f; } } } float AlgorithmCiftiCorrelationGradient::correlate(const float* row1, const float& rrs1, const float* row2, const float& rrs2) { double r; if (row1 == row2 && !m_covariance) { r = 1.0;//short circuit for same row } else { double accum = 0.0; for (int i = 0; i < m_numCols; ++i) { accum += row1[i] * row2[i];//these have already had the row means subtracted out } if (m_covariance) { r = accum / m_numCols; } else { r = accum / (rrs1 * rrs2); } } if (!m_covariance) { if (m_applyFisher) { if (r > 0.999999) r = 0.999999;//prevent inf if (r < -0.999999) r = -0.999999;//prevent -inf r = 0.5 * log((1 + r) / (1 - r)); } else { if (r > 1.0) r = 1.0;//don't output anything silly if (r < -1.0) r = -1.0; } } return r; } void AlgorithmCiftiCorrelationGradient::init(const CiftiFile* input, const bool& undoFisherInput, const bool& applyFisher, const bool& covariance) { if (input->getCiftiXML().getMappingType(CiftiXML::ALONG_COLUMN) != CiftiMappingType::BRAIN_MODELS) throw AlgorithmException("input cifti file must have brain models mapping along column"); if (covariance) { if (applyFisher) throw AlgorithmException("cannot apply fisher z transformation to covariance"); } m_undoFisherInput = undoFisherInput; m_applyFisher = applyFisher; m_covariance = covariance; m_inputCifti = input; m_rowInfo.resize(m_inputCifti->getNumberOfRows()); m_cacheUsed = 0; m_numCols = m_inputCifti->getNumberOfColumns(); m_outColumn.resize(m_inputCifti->getNumberOfRows()); } void AlgorithmCiftiCorrelationGradient::cacheRows(const vector& ciftiIndices) { clearCache();//clear first, to be sure we never keep a cache around too long int curIndex = 0, numIndices = (int)ciftiIndices.size();//manually in-order m_rowCache.reserve(m_cacheUsed + numIndices);//so that pointers to members don't change #pragma omp CARET_PAR { int myIndex; float* myPtr; #pragma omp CARET_FOR schedule(dynamic) for (int i = 0; i < numIndices; ++i) { myPtr = NULL; #pragma omp critical { myIndex = curIndex; ++curIndex; CaretAssertVectorIndex(m_rowInfo, ciftiIndices[myIndex]); if (m_rowInfo[ciftiIndices[myIndex]].m_cacheIndex == -1) { if (m_cacheUsed >= (int)m_rowCache.size()) { m_rowCache.push_back(CacheRow()); m_rowCache[m_cacheUsed].m_row.resize(m_numCols); } m_rowCache[m_cacheUsed].m_ciftiIndex = ciftiIndices[myIndex]; myPtr = m_rowCache[m_cacheUsed].m_row.data(); m_inputCifti->getRow(myPtr, ciftiIndices[myIndex]); m_rowInfo[ciftiIndices[myIndex]].m_cacheIndex = m_cacheUsed; ++m_cacheUsed; } }//end critical, now compute while the next thread reads if (myPtr != NULL) { adjustRow(myPtr, ciftiIndices[myIndex]); } } } } void AlgorithmCiftiCorrelationGradient::clearCache() { for (int i = 0; i < m_cacheUsed; ++i) { m_rowInfo[m_rowCache[i].m_ciftiIndex].m_cacheIndex = -1; } m_cacheUsed = 0; } const float* AlgorithmCiftiCorrelationGradient::getRow(const int& ciftiIndex, float& rootResidSqr, const bool& mustBeCached) { float* ret; CaretAssertVectorIndex(m_rowInfo, ciftiIndex); if (m_rowInfo[ciftiIndex].m_cacheIndex != -1) { ret = m_rowCache[m_rowInfo[ciftiIndex].m_cacheIndex].m_row.data(); } else { CaretAssert(!mustBeCached); if (mustBeCached)//largely so it doesn't give warning about unused when compiled in release { throw AlgorithmException("something very bad happened, notify the developers"); } ret = getTempRow(); m_inputCifti->getRow(ret, ciftiIndex); adjustRow(ret, ciftiIndex); } rootResidSqr = m_rowInfo[ciftiIndex].m_rootResidSqr; return ret; } void AlgorithmCiftiCorrelationGradient::adjustRow(float* rowOut, const int& ciftiIndex) { if (m_undoFisherInput) { for (int i = 0; i < m_numCols; ++i) { double temp = exp(2 * rowOut[i]); rowOut[i] = (float)((temp - 1)/(temp + 1)); } } if (!m_rowInfo[ciftiIndex].m_haveCalculated) { double accum = 0.0;//double, for numerical stability for (int i = 0; i < m_numCols; ++i)//two pass, for numerical stability { accum += rowOut[i]; } float mean = accum / m_numCols; float rootResidSqr = 0.0f;//not used in covariance if (!m_covariance) { accum = 0.0; for (int i = 0; i < m_numCols; ++i) { float tempf = rowOut[i] - mean; accum += tempf * tempf; } rootResidSqr = sqrt(accum); } m_rowInfo[ciftiIndex].m_mean = mean; m_rowInfo[ciftiIndex].m_rootResidSqr = rootResidSqr; m_rowInfo[ciftiIndex].m_haveCalculated = true; } float mean = m_rowInfo[ciftiIndex].m_mean; for (int i = 0; i < m_numCols; ++i) { rowOut[i] -= mean; } } float* AlgorithmCiftiCorrelationGradient::getTempRow() { #ifdef CARET_OMP int oldsize = (int)m_tempRows.size(); int threadNum = omp_get_thread_num(); if (threadNum >= oldsize) { m_tempRows.resize(threadNum + 1); for (int i = oldsize; i <= threadNum; ++i) { m_tempRows[i] = CaretArray(m_numCols); } } return m_tempRows[threadNum].getArray(); #else if (m_tempRows.size() == 0) { m_tempRows.resize(1); m_tempRows[0] = CaretArray(m_numCols); } return m_tempRows[0].getArray(); #endif } int AlgorithmCiftiCorrelationGradient::numRowsForMem(const float& memLimitGB, const int64_t& inrowBytes, const int64_t& outrowBytes, const int& numRows, bool& cacheFullInput) { int64_t targetBytes = (int64_t)(memLimitGB * 1024 * 1024 * 1024); if (m_inputCifti->isInMemory()) targetBytes -= numRows * inrowBytes;//count in-memory input against the total too targetBytes -= numRows * sizeof(RowInfo) + 2 * outrowBytes;//storage for mean, stdev, and info about caching, output structures if (targetBytes < 1) { cacheFullInput = false;//the most memory conservation possible, though it will take a LOT of time and do a LOT of IO return 1; } if (inrowBytes * numRows < targetBytes)//if we can cache the full input, compute the number of passes needed and compare { int64_t div = max(outrowBytes, (int64_t)1);//make sure it is never zero or negative int64_t numRowsFull = (targetBytes - inrowBytes * numRows) / div; if (numRowsFull < 1) numRowsFull = 1; int64_t fullPasses = numRows / numRowsFull; int64_t fullCorrSkip = (fullPasses * numRowsFull * (numRowsFull - 1) + (numRows - fullPasses * numRowsFull) * (numRows - fullPasses * numRowsFull - 1)) / 2; #ifdef CARET_OMP targetBytes -= inrowBytes * omp_get_max_threads(); #else targetBytes -= inrowBytes;//1 row in memory that isn't a reference to cache #endif int64_t numPassesPartial = ((outrowBytes + inrowBytes) * numRows + targetBytes - 1) / targetBytes;//break the partial cached passes up equally, to use less memory, and so we don't get an anemic pass at the end if (numPassesPartial < 1) { numPassesPartial = 1; CaretLogWarning("memory usage calculation found zero/negative pass solution, report to developers (it may use a lot of memory this run)"); } int64_t numRowsPartial = (numRows + numPassesPartial - 1) / numPassesPartial; fullPasses = numPassesPartial - 1; int64_t partialCorrSkip = (fullPasses * numRowsPartial * (numRowsPartial - 1) + (numRows - fullPasses * numRowsPartial) * (numRows - fullPasses * numRowsPartial - 1)) / 2; int ret; if (partialCorrSkip > fullCorrSkip * 1.05f)//prefer full caching slightly - include a bias factor in options? {//assume IO and row adjustment (unfisher, subtract mean) won't be the limiting factor, since IO should be balanced during correlation due to evenly sized passes when not fully cached cacheFullInput = false; ret = numRowsPartial; } else { cacheFullInput = true; ret = numRowsFull; } if (ret < 1) ret = 1;//sanitize, just in case if (ret > numRows) ret = numRows; return ret; } else {//if we can't cache the whole thing, split passes evenly cacheFullInput = false; int64_t div = max((int64_t)1, (outrowBytes + inrowBytes) * numRows); #ifdef CARET_OMP targetBytes -= inrowBytes * omp_get_max_threads(); #else targetBytes -= inrowBytes;//1 row in memory that isn't a reference to cache #endif int64_t numPassesPartial = (targetBytes + div - 1) / targetBytes; int ret = (numRows + numPassesPartial - 1) / numPassesPartial; if (ret < 1) ret = 1;//sanitize, just in case if (ret > numRows) ret = numRows; return ret; } } float AlgorithmCiftiCorrelationGradient::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmCiftiCorrelationGradient::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmCiftiCorrelationGradient.h000066400000000000000000000120461255417355300253470ustar00rootroot00000000000000#ifndef __ALGORITHM_CIFTI_CORRELATION_GRADIENT_H__ #define __ALGORITHM_CIFTI_CORRELATION_GRADIENT_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" #include "CaretPointer.h" #include "StructureEnum.h" namespace caret { class AlgorithmCiftiCorrelationGradient : public AbstractAlgorithm { AlgorithmCiftiCorrelationGradient(); struct CacheRow { int m_ciftiIndex; std::vector m_row; }; struct RowInfo { bool m_haveCalculated; float m_mean, m_rootResidSqr; int m_cacheIndex; RowInfo() { m_haveCalculated = false; m_cacheIndex = -1; } }; std::vector m_rowCache; std::vector m_rowInfo; std::vector > m_tempRows;//reuse return values in getRow instead of reallocating std::vector m_outColumn; int m_cacheUsed;//reuse cache entries instead of reallocating them int m_numCols; bool m_undoFisherInput, m_applyFisher, m_covariance; const CiftiFile* m_inputCifti;//so that accesses work through the cache functions void cacheRows(const std::vector& ciftiIndices);//grabs the rows and does whatever it needs to, using as much IO bandwidth and CPU resources as available/needed void clearCache(); const float* getRow(const int& ciftiIndex, float& rootResidSqr, const bool& mustBeCached = false); void adjustRow(float* rowOut, const int& ciftiIndex);//does the reverse fisher transform, computes stuff, subtracts mean float* getTempRow(); float correlate(const float* row1, const float& rrs1, const float* row2, const float& rrs2); void init(const CiftiFile* input, const bool& undoFisherInput, const bool& applyFisher, const bool& covariance); int numRowsForMem(const float& memLimitGB, const int64_t& inrowBytes, const int64_t& outrowBytes, const int& numRows, bool& cacheFullInput); //void processSurfaceComponentLocal(StructureEnum::Enum& myStructure, const float& surfKern, const float& memLimitGB, SurfaceFile* mySurf); void processSurfaceComponent(StructureEnum::Enum& myStructure, const float& surfKern, const float& memLimitGB, SurfaceFile* mySurf, const MetricFile* myAreas); void processSurfaceComponent(StructureEnum::Enum& myStructure, const float& surfKern, const float& surfExclude, const float& memLimitGB, SurfaceFile* mySurf, const MetricFile* myAreas); //void processVolumeComponentLocal(StructureEnum::Enum& myStructure, const float& volKern, const float& memLimitGB); void processVolumeComponent(StructureEnum::Enum& myStructure, const float& volKern, const float& memLimitGB); void processVolumeComponent(StructureEnum::Enum& myStructure, const float& volKern, const float& volExclude, const float& memLimitGB); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmCiftiCorrelationGradient(ProgressObject* myProgObj, const CiftiFile* myCifti, CiftiFile* myCiftiOut, SurfaceFile* myLeftSurf = NULL, SurfaceFile* myRightSurf = NULL, SurfaceFile* myCerebSurf = NULL, const MetricFile* myLeftAreas = NULL, const MetricFile* myRightAreas = NULL, const MetricFile* myCerebAreas = NULL, const float& surfKern = -1.0f, const float& volKern = -1.0f, const bool& undoFisherInput = false, const bool& applyFisher = false, const float& surfaceExclude = -1.0f, const float& volumeExclude = -1.0f, const bool& covariance = false, const float& memLimitGB = -1.0f); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmCiftiCorrelationGradient; } #endif //__ALGORITHM_CIFTI_CORRELATION_GRADIENT_H__ workbench-1.1.1/src/Algorithms/AlgorithmCiftiCreateDenseScalar.cxx000066400000000000000000000241111255417355300252670ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmCiftiCreateDenseScalar.h" #include "AlgorithmException.h" #include "AlgorithmCiftiCreateDenseTimeseries.h" //for making the dense mapping from metric files #include "CaretAssert.h" #include "CiftiFile.h" #include "CaretAssert.h" #include "GiftiLabelTable.h" #include "MetricFile.h" #include "StructureEnum.h" #include "VolumeFile.h" #include #include #include using namespace caret; using namespace std; AString AlgorithmCiftiCreateDenseScalar::getCommandSwitch() { return "-cifti-create-dense-scalar"; } AString AlgorithmCiftiCreateDenseScalar::getShortDescription() { return "CREATE A CIFTI DENSE SCALAR FILE"; } OperationParameters* AlgorithmCiftiCreateDenseScalar::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiOutputParameter(1, "cifti-out", "the output cifti file"); OptionalParameter* volumeOpt = ret->createOptionalParameter(2, "-volume", "volume component"); volumeOpt->addVolumeParameter(1, "volume-data", "volume file containing all voxel data for all volume structures"); volumeOpt->addVolumeParameter(2, "label-volume", "label volume file containing labels for cifti structures"); OptionalParameter* leftMetricOpt = ret->createOptionalParameter(3, "-left-metric", "metric for left surface"); leftMetricOpt->addMetricParameter(1, "metric", "the metric file"); OptionalParameter* leftRoiOpt = leftMetricOpt->createOptionalParameter(2, "-roi-left", "roi of vertices to use from left surface"); leftRoiOpt->addMetricParameter(1, "roi-metric", "the ROI as a metric file"); OptionalParameter* rightMetricOpt = ret->createOptionalParameter(4, "-right-metric", "metric for left surface"); rightMetricOpt->addMetricParameter(1, "metric", "the metric file"); OptionalParameter* rightRoiOpt = rightMetricOpt->createOptionalParameter(2, "-roi-right", "roi of vertices to use from right surface"); rightRoiOpt->addMetricParameter(1, "roi-metric", "the ROI as a metric file"); OptionalParameter* cerebMetricOpt = ret->createOptionalParameter(5, "-cerebellum-metric", "metric for the cerebellum"); cerebMetricOpt->addMetricParameter(1, "metric", "the metric file"); OptionalParameter* cerebRoiOpt = cerebMetricOpt->createOptionalParameter(2, "-roi-cerebellum", "roi of vertices to use from right surface"); cerebRoiOpt->addMetricParameter(1, "roi-metric", "the ROI as a metric file"); AString myText = AString("All input files must have the same number of columns/subvolumes. Only the specified components will be in the output cifti. ") + "Map names will be taken from one of the input files. " + "At least one component must be specified. The label volume should have some of the label names from this list, all other label names will be ignored:\n"; vector myStructureEnums; StructureEnum::getAllEnums(myStructureEnums); for (int i = 0; i < (int)myStructureEnums.size(); ++i) { myText += "\n" + StructureEnum::toName(myStructureEnums[i]); } ret->setHelpText(myText); return ret; } void AlgorithmCiftiCreateDenseScalar::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { VolumeFile* myVol = NULL, *myVolLabel = NULL; CiftiFile* myCiftiOut = myParams->getOutputCifti(1); OptionalParameter* volumeOpt = myParams->getOptionalParameter(2); if (volumeOpt->m_present) { myVol = volumeOpt->getVolume(1); myVolLabel = volumeOpt->getVolume(2); } MetricFile* leftData = NULL, *leftRoi = NULL, *rightData = NULL, *rightRoi = NULL, *cerebData = NULL, *cerebRoi = NULL; OptionalParameter* leftMetricOpt = myParams->getOptionalParameter(3); if (leftMetricOpt->m_present) { leftData = leftMetricOpt->getMetric(1); OptionalParameter* leftRoiOpt = leftMetricOpt->getOptionalParameter(2); if (leftRoiOpt->m_present) { leftRoi = leftRoiOpt->getMetric(1); } } OptionalParameter* rightMetricOpt = myParams->getOptionalParameter(4); if (rightMetricOpt->m_present) { rightData = rightMetricOpt->getMetric(1); OptionalParameter* rightRoiOpt = rightMetricOpt->getOptionalParameter(2); if (rightRoiOpt->m_present) { rightRoi = rightRoiOpt->getMetric(1); } } OptionalParameter* cerebMetricOpt = myParams->getOptionalParameter(5); if (cerebMetricOpt->m_present) { cerebData = cerebMetricOpt->getMetric(1); OptionalParameter* cerebRoiOpt = cerebMetricOpt->getOptionalParameter(2); if (cerebRoiOpt->m_present) { cerebRoi = cerebRoiOpt->getMetric(1); } } AlgorithmCiftiCreateDenseScalar(myProgObj, myCiftiOut, myVol, myVolLabel, leftData, leftRoi, rightData, rightRoi, cerebData, cerebRoi);//executes the algorithm } AlgorithmCiftiCreateDenseScalar::AlgorithmCiftiCreateDenseScalar(ProgressObject* myProgObj, CiftiFile* myCiftiOut, const VolumeFile* myVol, const VolumeFile* myVolLabel, const MetricFile* leftData, const MetricFile* leftRoi, const MetricFile* rightData, const MetricFile* rightRoi, const MetricFile* cerebData, const MetricFile* cerebRoi) : AbstractAlgorithm(myProgObj) { CaretAssert(myCiftiOut != NULL); LevelProgress myProgress(myProgObj); CiftiBrainModelsMap denseMap = AlgorithmCiftiCreateDenseTimeseries::makeDenseMapping(myVol, myVolLabel, leftData, leftRoi, rightData, rightRoi, cerebData, cerebRoi); CiftiXML myXML; myXML.setNumberOfDimensions(2); myXML.setMap(CiftiXML::ALONG_COLUMN, denseMap); int numMaps = -1; const CaretMappableDataFile* nameFile = NULL; if (leftData != NULL) { numMaps = leftData->getNumberOfMaps(); nameFile = leftData; } if (rightData != NULL) { if (numMaps == -1) { numMaps = rightData->getNumberOfMaps(); nameFile = rightData; } else { if (numMaps != rightData->getNumberOfMaps()) { throw AlgorithmException("right and left surface data have a different number of maps"); } } } if (cerebData != NULL) { if (numMaps == -1) { numMaps = cerebData->getNumberOfMaps(); nameFile = cerebData; } else { if (numMaps != cerebData->getNumberOfMaps()) { throw AlgorithmException("cerebellum surface data has a different number of maps"); } } } if (myVol != NULL) { if (numMaps == -1) { numMaps = myVol->getNumberOfMaps(); nameFile = myVol; } else { if (numMaps != myVol->getNumberOfMaps()) { throw AlgorithmException("volume data has a different number of maps"); } } } if (numMaps == -1)//doubles as checking nameFile for being NULL { throw AlgorithmException("no models specified"); } CiftiScalarsMap scalarMap; scalarMap.setLength(numMaps); for (int i = 0; i < numMaps; ++i) { scalarMap.setMapName(i, nameFile->getMapName(i));//copy map names } myXML.setMap(CiftiXML::ALONG_ROW, scalarMap); myCiftiOut->setCiftiXML(myXML); CaretArray temprow(numMaps); const CiftiBrainModelsMap& myDenseMap = myXML.getBrainModelsMap(CiftiXML::ALONG_COLUMN); vector surfStructs = myDenseMap.getSurfaceStructureList(); for (int whichStruct = 0; whichStruct < (int)surfStructs.size(); ++whichStruct) { vector surfMap = myDenseMap.getSurfaceMap(surfStructs[whichStruct]); const MetricFile* dataMetric = NULL; switch (surfStructs[whichStruct]) { case StructureEnum::CORTEX_LEFT: dataMetric = leftData; break; case StructureEnum::CORTEX_RIGHT: dataMetric = rightData; break; case StructureEnum::CEREBELLUM: dataMetric = cerebData; break; default: CaretAssert(false); } for (int64_t i = 0; i < (int)surfMap.size(); ++i) { for (int t = 0; t < numMaps; ++t) { temprow[t] = dataMetric->getValue(surfMap[i].m_surfaceNode, t); } myCiftiOut->setRow(temprow, surfMap[i].m_ciftiIndex); } } vector volMap = myDenseMap.getFullVolumeMap();//we don't need to know which voxel is from which structure for (int64_t i = 0; i < (int)volMap.size(); ++i) { for (int t = 0; t < numMaps; ++t) { temprow[t] = myVol->getValue(volMap[i].m_ijk, t); } myCiftiOut->setRow(temprow, volMap[i].m_ciftiIndex); } } float AlgorithmCiftiCreateDenseScalar::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmCiftiCreateDenseScalar::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmCiftiCreateDenseScalar.h000066400000000000000000000041211255417355300247130ustar00rootroot00000000000000#ifndef __ALGORITHM_CIFTI_CREATE_DENSE_SCALAR_H__ #define __ALGORITHM_CIFTI_CREATE_DENSE_SCALAR_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmCiftiCreateDenseScalar : public AbstractAlgorithm { AlgorithmCiftiCreateDenseScalar(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmCiftiCreateDenseScalar(ProgressObject* myProgObj, CiftiFile* myCiftiOut, const VolumeFile* myVol = NULL, const VolumeFile* myVolLabel = NULL, const MetricFile* leftData = NULL, const MetricFile* leftRoi = NULL, const MetricFile* rightData = NULL, const MetricFile* rightRoi = NULL, const MetricFile* cerebData = NULL, const MetricFile* cerebRoi = NULL); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmCiftiCreateDenseScalar; } #endif //__ALGORITHM_CIFTI_CREATE_DENSE_SCALAR_H__ workbench-1.1.1/src/Algorithms/AlgorithmCiftiCreateDenseTimeseries.cxx000066400000000000000000000410551255417355300262010ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmCiftiCreateDenseTimeseries.h" #include "AlgorithmException.h" #include "CaretAssert.h" #include "StructureEnum.h" #include "VolumeFile.h" #include "MetricFile.h" #include "CiftiFile.h" #include "CaretAssert.h" #include "GiftiLabelTable.h" #include "CaretPointer.h" #include #include #include using namespace caret; using namespace std; AString AlgorithmCiftiCreateDenseTimeseries::getCommandSwitch() { return "-cifti-create-dense-timeseries"; } AString AlgorithmCiftiCreateDenseTimeseries::getShortDescription() { return "CREATE A CIFTI DENSE TIMESERIES"; } OperationParameters* AlgorithmCiftiCreateDenseTimeseries::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiOutputParameter(1, "cifti-out", "the output cifti file"); OptionalParameter* volumeOpt = ret->createOptionalParameter(2, "-volume", "volume component"); volumeOpt->addVolumeParameter(1, "volume-data", "volume file containing all voxel data for all volume structures"); volumeOpt->addVolumeParameter(2, "label-volume", "label volume file containing labels for cifti structures"); OptionalParameter* leftMetricOpt = ret->createOptionalParameter(3, "-left-metric", "metric for left surface"); leftMetricOpt->addMetricParameter(1, "metric", "the metric file"); OptionalParameter* leftRoiOpt = leftMetricOpt->createOptionalParameter(2, "-roi-left", "roi of vertices to use from left surface"); leftRoiOpt->addMetricParameter(1, "roi-metric", "the ROI as a metric file"); OptionalParameter* rightMetricOpt = ret->createOptionalParameter(4, "-right-metric", "metric for left surface"); rightMetricOpt->addMetricParameter(1, "metric", "the metric file"); OptionalParameter* rightRoiOpt = rightMetricOpt->createOptionalParameter(2, "-roi-right", "roi of vertices to use from right surface"); rightRoiOpt->addMetricParameter(1, "roi-metric", "the ROI as a metric file"); OptionalParameter* cerebMetricOpt = ret->createOptionalParameter(5, "-cerebellum-metric", "metric for the cerebellum"); cerebMetricOpt->addMetricParameter(1, "metric", "the metric file"); OptionalParameter* cerebRoiOpt = cerebMetricOpt->createOptionalParameter(2, "-roi-cerebellum", "roi of vertices to use from right surface"); cerebRoiOpt->addMetricParameter(1, "roi-metric", "the ROI as a metric file"); OptionalParameter* timestepOpt = ret->createOptionalParameter(6, "-timestep", "set the timestep"); timestepOpt->addDoubleParameter(1, "interval", "the timestep, in seconds (default 1.0)"); OptionalParameter* timestartOpt = ret->createOptionalParameter(7, "-timestart", "set the start time"); timestartOpt->addDoubleParameter(1, "start", "the time at the first frame, in seconds (default 0.0)"); OptionalParameter* unitOpt = ret->createOptionalParameter(8, "-unit", "use a unit other than time"); unitOpt->addStringParameter(1, "unit", "unit identifier (default SECOND)"); AString myText = AString("All input files must have the same number of columns/subvolumes. Only the specified components will be in the output cifti. ") + "At least one component must be specified. The label volume should have some of the label names from this list, all other label names will be ignored:\n"; vector myStructureEnums; StructureEnum::getAllEnums(myStructureEnums); for (int i = 0; i < (int)myStructureEnums.size(); ++i) { myText += "\n" + StructureEnum::toName(myStructureEnums[i]); } myText += "\n\nThe -unit option accepts these values:\n"; vector units = CiftiSeriesMap::getAllUnits(); for (int i = 0; i < (int)units.size(); ++i) { myText += "\n" + CiftiSeriesMap::unitToString(units[i]); } ret->setHelpText(myText); return ret; } void AlgorithmCiftiCreateDenseTimeseries::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { VolumeFile* myVol = NULL, *myVolLabel = NULL; CiftiFile* myCiftiOut = myParams->getOutputCifti(1); OptionalParameter* volumeOpt = myParams->getOptionalParameter(2); if (volumeOpt->m_present) { myVol = volumeOpt->getVolume(1); myVolLabel = volumeOpt->getVolume(2); } MetricFile* leftData = NULL, *leftRoi = NULL, *rightData = NULL, *rightRoi = NULL, *cerebData = NULL, *cerebRoi = NULL; OptionalParameter* leftMetricOpt = myParams->getOptionalParameter(3); if (leftMetricOpt->m_present) { leftData = leftMetricOpt->getMetric(1); OptionalParameter* leftRoiOpt = leftMetricOpt->getOptionalParameter(2); if (leftRoiOpt->m_present) { leftRoi = leftRoiOpt->getMetric(1); } } OptionalParameter* rightMetricOpt = myParams->getOptionalParameter(4); if (rightMetricOpt->m_present) { rightData = rightMetricOpt->getMetric(1); OptionalParameter* rightRoiOpt = rightMetricOpt->getOptionalParameter(2); if (rightRoiOpt->m_present) { rightRoi = rightRoiOpt->getMetric(1); } } OptionalParameter* cerebMetricOpt = myParams->getOptionalParameter(5); if (cerebMetricOpt->m_present) { cerebData = cerebMetricOpt->getMetric(1); OptionalParameter* cerebRoiOpt = cerebMetricOpt->getOptionalParameter(2); if (cerebRoiOpt->m_present) { cerebRoi = cerebRoiOpt->getMetric(1); } } float timestep = 1.0f; OptionalParameter* timestepOpt = myParams->getOptionalParameter(6); if (timestepOpt->m_present) { timestep = (float)timestepOpt->getDouble(1); } float timestart = 0.0f; OptionalParameter* timestartOpt = myParams->getOptionalParameter(7); if (timestartOpt->m_present) { timestart = (float)timestartOpt->getDouble(1); } CiftiSeriesMap::Unit myUnit = CiftiSeriesMap::SECOND; OptionalParameter* unitOpt = myParams->getOptionalParameter(8); if (unitOpt->m_present) { AString unitName = unitOpt->getString(1); bool ok = false; myUnit = CiftiSeriesMap::stringToUnit(unitName, ok); if (!ok) { throw AlgorithmException("unrecognized unit name: '" + unitName + "'"); } } AlgorithmCiftiCreateDenseTimeseries(myProgObj, myCiftiOut, myVol, myVolLabel, leftData, leftRoi, rightData, rightRoi, cerebData, cerebRoi, timestep, timestart, myUnit); } AlgorithmCiftiCreateDenseTimeseries::AlgorithmCiftiCreateDenseTimeseries(ProgressObject* myProgObj, CiftiFile* myCiftiOut, const VolumeFile* myVol, const VolumeFile* myVolLabel, const MetricFile* leftData, const MetricFile* leftRoi, const MetricFile* rightData, const MetricFile* rightRoi, const MetricFile* cerebData, const MetricFile* cerebRoi, const float& timestep, const float& timestart, const CiftiSeriesMap::Unit& myUnit) : AbstractAlgorithm(myProgObj) { CaretAssert(myCiftiOut != NULL); LevelProgress myProgress(myProgObj); CiftiBrainModelsMap denseMap = makeDenseMapping(myVol, myVolLabel, leftData, leftRoi, rightData, rightRoi, cerebData, cerebRoi); CiftiXML myXML; myXML.setNumberOfDimensions(2); myXML.setMap(CiftiXML::ALONG_COLUMN, denseMap); int numMaps = -1; if (leftData != NULL) { numMaps = leftData->getNumberOfMaps(); } if (rightData != NULL) { if (numMaps == -1) { numMaps = rightData->getNumberOfMaps(); } else { if (numMaps != rightData->getNumberOfMaps()) { throw AlgorithmException("right and left surface data have a different number of maps"); } } } if (cerebData != NULL) { if (numMaps == -1) { numMaps = cerebData->getNumberOfMaps(); } else { if (numMaps != cerebData->getNumberOfMaps()) { throw AlgorithmException("cerebellum surface data has a different number of maps"); } } } if (myVol != NULL) { if (numMaps == -1) { numMaps = myVol->getNumberOfMaps(); } else { if (numMaps != myVol->getNumberOfMaps()) { throw AlgorithmException("volume data has a different number of maps"); } } } if (numMaps == -1) { throw AlgorithmException("no models specified"); } CiftiSeriesMap seriesMap; seriesMap.setUnit(myUnit); seriesMap.setStart(timestart); seriesMap.setStep(timestep); seriesMap.setLength(numMaps); myXML.setMap(CiftiXML::ALONG_ROW, seriesMap); myCiftiOut->setCiftiXML(myXML); CaretArray temprow(numMaps); const CiftiBrainModelsMap& myDenseMap = myXML.getBrainModelsMap(CiftiXML::ALONG_COLUMN); vector surfStructs = myDenseMap.getSurfaceStructureList(); for (int whichStruct = 0; whichStruct < (int)surfStructs.size(); ++whichStruct) { vector surfMap = myDenseMap.getSurfaceMap(surfStructs[whichStruct]); const MetricFile* dataMetric = NULL; switch (surfStructs[whichStruct]) { case StructureEnum::CORTEX_LEFT: dataMetric = leftData; break; case StructureEnum::CORTEX_RIGHT: dataMetric = rightData; break; case StructureEnum::CEREBELLUM: dataMetric = cerebData; break; default: CaretAssert(false); } for (int64_t i = 0; i < (int)surfMap.size(); ++i) { for (int t = 0; t < numMaps; ++t) { temprow[t] = dataMetric->getValue(surfMap[i].m_surfaceNode, t); } myCiftiOut->setRow(temprow, surfMap[i].m_ciftiIndex); } } vector volMap = myDenseMap.getFullVolumeMap();//we don't need to know which voxel is from which structure for (int64_t i = 0; i < (int)volMap.size(); ++i) { for (int t = 0; t < numMaps; ++t) { temprow[t] = myVol->getValue(volMap[i].m_ijk, t); } myCiftiOut->setRow(temprow, volMap[i].m_ciftiIndex); } } CiftiBrainModelsMap AlgorithmCiftiCreateDenseTimeseries::makeDenseMapping(const VolumeFile* myVol, const VolumeFile* myVolLabel, const MetricFile* leftData, const MetricFile* leftRoi, const MetricFile* rightData, const MetricFile* rightRoi, const MetricFile* cerebData, const MetricFile* cerebRoi) { bool noData = true; CiftiBrainModelsMap denseMap; if (leftData != NULL) { noData = false; if (leftRoi == NULL) { denseMap.addSurfaceModel(leftData->getNumberOfNodes(), StructureEnum::CORTEX_LEFT); } else { if (leftRoi->getNumberOfNodes() != leftData->getNumberOfNodes()) { throw AlgorithmException("left surface ROI and data have different vertex counts"); } denseMap.addSurfaceModel(leftData->getNumberOfNodes(), StructureEnum::CORTEX_LEFT, leftRoi->getValuePointerForColumn(0)); } } if (rightData != NULL) { noData = false; if (rightRoi == NULL) { denseMap.addSurfaceModel(rightData->getNumberOfNodes(), StructureEnum::CORTEX_RIGHT); } else { if (rightRoi->getNumberOfNodes() != rightData->getNumberOfNodes()) { throw AlgorithmException("right surface ROI and data have different vertex counts"); } denseMap.addSurfaceModel(rightRoi->getNumberOfNodes(), StructureEnum::CORTEX_RIGHT, rightRoi->getValuePointerForColumn(0)); } } if (cerebData != NULL) { noData = false; if (cerebRoi == NULL) { denseMap.addSurfaceModel(cerebData->getNumberOfNodes(), StructureEnum::CEREBELLUM); } else { if (cerebRoi->getNumberOfNodes() != cerebData->getNumberOfNodes()) { throw AlgorithmException("cerebellum surface ROI and data have different vertex counts"); } denseMap.addSurfaceModel(cerebRoi->getNumberOfNodes(), StructureEnum::CEREBELLUM, cerebRoi->getValuePointerForColumn(0)); } } if (myVol != NULL) { CaretAssert(myVolLabel != NULL); if (myVolLabel == NULL) { throw AlgorithmException("making a dense mapping with voxels requires a label volume"); } if (!myVol->matchesVolumeSpace(myVolLabel)) { throw AlgorithmException("label volume has a different volume space than data volume"); } if (myVolLabel->getType() != SubvolumeAttributes::LABEL) { throw AlgorithmException("parcel volume is not of type label"); } noData = false; map labelMap;//maps label values to structures vector > voxelLists;//voxel lists for each volume component map componentMap;//maps structures to indexes in voxelLists const GiftiLabelTable* myLabelTable = myVolLabel->getMapLabelTable(0); vector labelKeys; myLabelTable->getKeys(labelKeys); int count = 0; for (int i = 0; i < (int)labelKeys.size(); ++i) { bool ok = false; StructureEnum::Enum thisStructure = StructureEnum::fromName(myLabelTable->getLabelName(labelKeys[i]), &ok); if (ok) { labelMap[labelKeys[i]] = thisStructure; if (componentMap.find(thisStructure) == componentMap.end())//make sure we don't already have this structure from another label { componentMap[thisStructure] = count; ++count; } } } voxelLists.resize(count); vector mydims; myVolLabel->getDimensions(mydims); for (int64_t k = 0; k < mydims[2]; ++k) { for (int64_t j = 0; j < mydims[1]; ++j) { for (int64_t i = 0; i < mydims[0]; ++i) { int myval = (int)floor(myVolLabel->getValue(i, j, k) + 0.5f); map::iterator myiter = labelMap.find(myval); if (myiter != labelMap.end()) { int whichList = componentMap.find(myiter->second)->second;//this should always find one, so we don't need to check for being end voxelLists[whichList].push_back(i); voxelLists[whichList].push_back(j); voxelLists[whichList].push_back(k); } } } } denseMap.setVolumeSpace(VolumeSpace(mydims.data(), myVol->getSform())); for (map::iterator myiter = componentMap.begin(); myiter != componentMap.end(); ++myiter) { denseMap.addVolumeModel(myiter->first, voxelLists[myiter->second]); } } if (noData) { throw AlgorithmException("no models specified"); } return denseMap; } float AlgorithmCiftiCreateDenseTimeseries::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmCiftiCreateDenseTimeseries::getSubAlgorithmWeight() { return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmCiftiCreateDenseTimeseries.h000066400000000000000000000055451255417355300256320ustar00rootroot00000000000000#ifndef __ALGORITHM_CIFTI_CREATE_DENSE_TIMESERIES_H__ #define __ALGORITHM_CIFTI_CREATE_DENSE_TIMESERIES_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" #include "CiftiBrainModelsMap.h" #include "CiftiSeriesMap.h" namespace caret { class AlgorithmCiftiCreateDenseTimeseries : public AbstractAlgorithm { AlgorithmCiftiCreateDenseTimeseries(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmCiftiCreateDenseTimeseries(ProgressObject* myProgObj, CiftiFile* myCiftiOut, const VolumeFile* myVol = NULL, const VolumeFile* myVolLabel = NULL, const MetricFile* leftData = NULL, const MetricFile* leftRoi = NULL, const MetricFile* rightData = NULL, const MetricFile* rightRoi = NULL, const MetricFile* cerebData = NULL, const MetricFile* cerebRoi = NULL, const float& timestep = 1.0f, const float& timestart = 0.0f, const CiftiSeriesMap::Unit& myUnit = CiftiSeriesMap::SECOND); static CiftiBrainModelsMap makeDenseMapping(const VolumeFile* myVol = NULL, const VolumeFile* myVolLabel = NULL, const MetricFile* leftData = NULL, const MetricFile* leftRoi = NULL, const MetricFile* rightData = NULL, const MetricFile* rightRoi = NULL, const MetricFile* cerebData = NULL, const MetricFile* cerebRoi = NULL);//where should this go? should also have version that accepts LabelFile static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmCiftiCreateDenseTimeseries; } #endif //__ALGORITHM_CIFTI_CREATE_DENSE_TIMESERIES_H__ workbench-1.1.1/src/Algorithms/AlgorithmCiftiCreateLabel.cxx000066400000000000000000000415111255417355300241250ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmCiftiCreateLabel.h" #include "AlgorithmException.h" #include "CaretAssert.h" #include "CaretPointer.h" #include "CiftiFile.h" #include "GiftiLabel.h" #include "GiftiLabelTable.h" #include "LabelFile.h" #include "MetricFile.h" #include "StructureEnum.h" #include "VolumeFile.h" #include #include #include using namespace caret; using namespace std; AString AlgorithmCiftiCreateLabel::getCommandSwitch() { return "-cifti-create-label"; } AString AlgorithmCiftiCreateLabel::getShortDescription() { return "CREATE A CIFTI LABEL FILE"; } OperationParameters* AlgorithmCiftiCreateLabel::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiOutputParameter(1, "cifti-out", "the output cifti file"); OptionalParameter* volumeOpt = ret->createOptionalParameter(2, "-volume", "volume component"); volumeOpt->addVolumeParameter(1, "label-volume", "volume file containing the label data"); volumeOpt->addVolumeParameter(2, "parcel-volume", "label volume file with cifti structure names to define the volume parcels"); OptionalParameter* leftLabelOpt = ret->createOptionalParameter(3, "-left-label", "label file for left surface"); leftLabelOpt->addLabelParameter(1, "label", "the label file"); OptionalParameter* leftRoiOpt = leftLabelOpt->createOptionalParameter(2, "-roi-left", "roi of vertices to use from left surface"); leftRoiOpt->addMetricParameter(1, "roi-metric", "the ROI as a metric file"); OptionalParameter* rightLabelOpt = ret->createOptionalParameter(4, "-right-label", "label for left surface"); rightLabelOpt->addLabelParameter(1, "label", "the label file"); OptionalParameter* rightRoiOpt = rightLabelOpt->createOptionalParameter(2, "-roi-right", "roi of vertices to use from right surface"); rightRoiOpt->addMetricParameter(1, "roi-metric", "the ROI as a metric file"); OptionalParameter* cerebLabelOpt = ret->createOptionalParameter(5, "-cerebellum-label", "label for the cerebellum"); cerebLabelOpt->addLabelParameter(1, "label", "the label file"); OptionalParameter* cerebRoiOpt = cerebLabelOpt->createOptionalParameter(2, "-roi-cerebellum", "roi of vertices to use from right surface"); cerebRoiOpt->addMetricParameter(1, "roi-metric", "the ROI as a metric file"); AString myText = AString("All input files must have the same number of columns/subvolumes. Only the specified components will be in the output cifti. ") + "At least one component must be specified.\n\n" + "The -volume option of -cifti-create-label requires two volume arguments, " + "the label-volume argument contains all labels you want to display (e.g. nuclei of the thalamus), " + "whereas the parcel-volume argument includes all CIFTI structures you want to include data within (e.g. THALAMUS_LEFT, THALAMUS_RIGHT). " + "If you just want the labels in voxels to be the structure names, you may use the same file for both arguments. " + "The parcel-volume must use some of the label names from this list, all other label names in the parcel-volume will be ignored:\n"; vector myStructureEnums; StructureEnum::getAllEnums(myStructureEnums); for (int i = 0; i < (int)myStructureEnums.size(); ++i) { myText += "\n" + StructureEnum::toName(myStructureEnums[i]); } ret->setHelpText(myText); return ret; } void AlgorithmCiftiCreateLabel::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { VolumeFile* myVol = NULL, *myVolLabel = NULL; CiftiFile* myCiftiOut = myParams->getOutputCifti(1); OptionalParameter* volumeOpt = myParams->getOptionalParameter(2); if (volumeOpt->m_present) { myVol = volumeOpt->getVolume(1); myVolLabel = volumeOpt->getVolume(2); } LabelFile* leftData = NULL, *rightData = NULL, *cerebData = NULL; MetricFile* leftRoi = NULL, *rightRoi = NULL, *cerebRoi = NULL; OptionalParameter* leftLabelOpt = myParams->getOptionalParameter(3); if (leftLabelOpt->m_present) { leftData = leftLabelOpt->getLabel(1); OptionalParameter* leftRoiOpt = leftLabelOpt->getOptionalParameter(2); if (leftRoiOpt->m_present) { leftRoi = leftRoiOpt->getMetric(1); } } OptionalParameter* rightLabelOpt = myParams->getOptionalParameter(4); if (rightLabelOpt->m_present) { rightData = rightLabelOpt->getLabel(1); OptionalParameter* rightRoiOpt = rightLabelOpt->getOptionalParameter(2); if (rightRoiOpt->m_present) { rightRoi = rightRoiOpt->getMetric(1); } } OptionalParameter* cerebLabelOpt = myParams->getOptionalParameter(5); if (cerebLabelOpt->m_present) { cerebData = cerebLabelOpt->getLabel(1); OptionalParameter* cerebRoiOpt = cerebLabelOpt->getOptionalParameter(2); if (cerebRoiOpt->m_present) { cerebRoi = cerebRoiOpt->getMetric(1); } } AlgorithmCiftiCreateLabel(myProgObj, myCiftiOut, myVol, myVolLabel, leftData, leftRoi, rightData, rightRoi, cerebData, cerebRoi); } AlgorithmCiftiCreateLabel::AlgorithmCiftiCreateLabel(ProgressObject* myProgObj, CiftiFile* myCiftiOut, const VolumeFile* myVol, const VolumeFile* myVolLabel, const LabelFile* leftData, const MetricFile* leftRoi, const LabelFile* rightData, const MetricFile* rightRoi, const LabelFile* cerebData, const MetricFile* cerebRoi) : AbstractAlgorithm(myProgObj) { CaretAssert(myCiftiOut != NULL); LevelProgress myProgress(myProgObj); CiftiXMLOld myXML; myXML.resetColumnsToBrainModels(); int numMaps = -1; if (leftData != NULL) { numMaps = leftData->getNumberOfMaps(); if (leftRoi == NULL) { myXML.addSurfaceModelToColumns(leftData->getNumberOfNodes(), StructureEnum::CORTEX_LEFT); } else { if (leftRoi->getNumberOfNodes() != leftData->getNumberOfNodes()) { throw AlgorithmException("left surface ROI and data have different vertex counts"); } myXML.addSurfaceModelToColumns(leftData->getNumberOfNodes(), StructureEnum::CORTEX_LEFT, leftRoi->getValuePointerForColumn(0)); } } if (rightData != NULL) { if (numMaps == -1) { numMaps = rightData->getNumberOfMaps(); } else { if (numMaps != rightData->getNumberOfMaps()) { throw AlgorithmException("right and left surface data have a different number of maps"); } } if (rightRoi == NULL) { myXML.addSurfaceModelToColumns(rightData->getNumberOfNodes(), StructureEnum::CORTEX_RIGHT); } else { if (rightRoi->getNumberOfNodes() != rightData->getNumberOfNodes()) { throw AlgorithmException("right surface ROI and data have different vertex counts"); } myXML.addSurfaceModelToColumns(rightRoi->getNumberOfNodes(), StructureEnum::CORTEX_RIGHT, rightRoi->getValuePointerForColumn(0)); } } if (cerebData != NULL) { if (numMaps == -1) { numMaps = cerebData->getNumberOfMaps(); } else { if (numMaps != cerebData->getNumberOfMaps()) { throw AlgorithmException("cerebellum surface data has a different number of maps"); } } if (cerebRoi == NULL) { myXML.addSurfaceModelToColumns(cerebData->getNumberOfNodes(), StructureEnum::CEREBELLUM); } else { if (cerebRoi->getNumberOfNodes() != cerebData->getNumberOfNodes()) { throw AlgorithmException("cerebellum surface ROI and data have different vertex counts"); } myXML.addSurfaceModelToColumns(cerebRoi->getNumberOfNodes(), StructureEnum::CEREBELLUM, cerebRoi->getValuePointerForColumn(0)); } } if (myVol != NULL) { CaretAssert(myVolLabel != NULL); if (myVol->getType() != SubvolumeAttributes::LABEL) { throw AlgorithmException("input volume is not a label volume"); } if (!myVol->matchesVolumeSpace(myVolLabel)) { throw AlgorithmException("label volume has a different volume space than data volume"); } if (myVolLabel->getType() != SubvolumeAttributes::LABEL) { throw AlgorithmException("parcel volume is not a label volume"); } if (numMaps == -1) { numMaps = myVol->getNumberOfMaps(); } else { if (numMaps != myVol->getNumberOfMaps()) { throw AlgorithmException("volume data has a different number of maps"); } } map labelMap;//maps label values to structures vector > voxelLists;//voxel lists for each volume component map componentMap;//maps structures to indexes in voxelLists const GiftiLabelTable* myLabelTable = myVolLabel->getMapLabelTable(0); vector labelKeys; myLabelTable->getKeys(labelKeys); int count = 0; for (int i = 0; i < (int)labelKeys.size(); ++i) { bool ok = false; StructureEnum::Enum thisStructure = StructureEnum::fromName(myLabelTable->getLabelName(labelKeys[i]), &ok); if (ok) { if (componentMap.find(thisStructure) == componentMap.end())//make sure we don't already have this structure from another label { labelMap[labelKeys[i]] = thisStructure; componentMap[thisStructure] = count; ++count; } } } voxelLists.resize(count); vector mydims; myVolLabel->getDimensions(mydims); for (int64_t k = 0; k < mydims[2]; ++k) { for (int64_t j = 0; j < mydims[1]; ++j) { for (int64_t i = 0; i < mydims[0]; ++i) { int myval = (int)floor(myVolLabel->getValue(i, j, k) + 0.5f); map::iterator myiter = labelMap.find(myval); if (myiter != labelMap.end()) { int whichList = componentMap.find(myiter->second)->second;//this should always find one, so we don't need to check for being end voxelLists[whichList].push_back(i); voxelLists[whichList].push_back(j); voxelLists[whichList].push_back(k); } } } } int64_t ciftiVolDims[3]; ciftiVolDims[0] = mydims[0]; ciftiVolDims[1] = mydims[1]; ciftiVolDims[2] = mydims[2]; myXML.setVolumeDimsAndSForm(ciftiVolDims, myVol->getSform()); for (map::iterator myiter = componentMap.begin(); myiter != componentMap.end(); ++myiter) { myXML.addVolumeModelToColumns(voxelLists[myiter->second], myiter->first); } } if (numMaps == -1) { throw AlgorithmException("no models specified"); } myXML.resetRowsToLabels(numMaps); vector > surfLeftConvert(numMaps), surfRightConvert(numMaps), surfCerebConvert(numMaps), volConvert(numMaps);//surfLeftConvert could just be a set, but for consistency... for (int i = 0; i < numMaps; ++i) { GiftiLabelTable mapTable;//NOTE: this relies on GiftiLabelTable::append doing the right thing bool first = true; if (leftData != NULL) { surfLeftConvert[i] = mapTable.append(*(leftData->getMapLabelTable(i)));//in case label files ever move to one table per map if (first) { first = false; myXML.setMapNameForRowIndex(i, leftData->getColumnName(i)); } } if (rightData != NULL) { surfRightConvert[i] = mapTable.append(*(rightData->getMapLabelTable(i))); if (first) { first = false; myXML.setMapNameForRowIndex(i, rightData->getColumnName(i)); } } if (cerebData != NULL) { surfCerebConvert[i] = mapTable.append(*(cerebData->getMapLabelTable(i))); if (first) { first = false; myXML.setMapNameForRowIndex(i, cerebData->getColumnName(i)); } } if (myVol != NULL) { volConvert[i] = mapTable.append(*(myVol->getMapLabelTable(i))); if (first) { first = false; myXML.setMapNameForRowIndex(i, myVol->getMapName(i)); } } myXML.setLabelTableForRowIndex(i, mapTable); } myCiftiOut->setCiftiXML(myXML); CaretArray temprow(numMaps); vector surfMap; if (myXML.getSurfaceMapForColumns(surfMap, StructureEnum::CORTEX_LEFT)) { for (int64_t i = 0; i < (int)surfMap.size(); ++i) { for (int t = 0; t < numMaps; ++t) { map::iterator iter = surfLeftConvert[t].find(leftData->getLabelKey(surfMap[i].m_surfaceNode, t)); if (iter == surfLeftConvert[t].end()) { temprow[t] = 0;//NOTE: this relies on 0 being the unused label, from the default constructor of the label table, and not being overwritten by append } else { temprow[t] = iter->second; } } myCiftiOut->setRow(temprow, surfMap[i].m_ciftiIndex); } } if (myXML.getSurfaceMapForColumns(surfMap, StructureEnum::CORTEX_RIGHT)) { for (int64_t i = 0; i < (int)surfMap.size(); ++i) { for (int t = 0; t < numMaps; ++t) { map::iterator iter = surfRightConvert[t].find(rightData->getLabelKey(surfMap[i].m_surfaceNode, t)); if (iter == surfRightConvert[t].end()) { temprow[t] = 0; } else { temprow[t] = iter->second; } } myCiftiOut->setRow(temprow, surfMap[i].m_ciftiIndex); } } if (myXML.getSurfaceMapForColumns(surfMap, StructureEnum::CEREBELLUM)) { for (int64_t i = 0; i < (int)surfMap.size(); ++i) { for (int t = 0; t < numMaps; ++t) { map::iterator iter = surfCerebConvert[t].find(cerebData->getLabelKey(surfMap[i].m_surfaceNode, t)); if (iter == surfCerebConvert[t].end()) { temprow[t] = 0; } else { temprow[t] = iter->second; } } myCiftiOut->setRow(temprow, surfMap[i].m_ciftiIndex); } } vector volMap; if (myXML.getVolumeMapForColumns(volMap))//we don't need to know which voxel is from which parcel { for (int64_t i = 0; i < (int)volMap.size(); ++i) { for (int t = 0; t < numMaps; ++t) { map::iterator iter = volConvert[t].find(myVol->getValue(volMap[i].m_ijk, t)); if (iter == volConvert[t].end()) { temprow[t] = 0; } else { temprow[t] = iter->second; } } myCiftiOut->setRow(temprow, volMap[i].m_ciftiIndex); } } } float AlgorithmCiftiCreateLabel::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmCiftiCreateLabel::getSubAlgorithmWeight() { return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmCiftiCreateLabel.h000066400000000000000000000037211255417355300235530ustar00rootroot00000000000000#ifndef __ALGORITHM_CIFTI_CREATE_LABEL_H__ #define __ALGORITHM_CIFTI_CREATE_LABEL_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmCiftiCreateLabel : public AbstractAlgorithm { AlgorithmCiftiCreateLabel(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmCiftiCreateLabel(ProgressObject* myProgObj, CiftiFile* myCiftiOut, const VolumeFile* myVol, const VolumeFile* myVolLabel, const LabelFile* leftData, const MetricFile* leftRoi, const LabelFile* rightData, const MetricFile* rightRoi, const LabelFile* cerebData, const MetricFile* cerebRoi); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmCiftiCreateLabel; } #endif //__ALGORITHM_CIFTI_CREATE_LABEL_H__ workbench-1.1.1/src/Algorithms/AlgorithmCiftiCrossCorrelation.cxx000066400000000000000000000406571255417355300252670ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmCiftiCrossCorrelation.h" #include "AlgorithmException.h" #include "CaretAssert.h" #include "CaretLogger.h" #include "CaretOMP.h" #include "CiftiFile.h" #include "FileInformation.h" #include #include using namespace caret; using namespace std; AString AlgorithmCiftiCrossCorrelation::getCommandSwitch() { return "-cifti-cross-correlation"; } AString AlgorithmCiftiCrossCorrelation::getShortDescription() { return "CORRELATE A CIFTI FILE WITH ANOTHER CIFTI FILE"; } OperationParameters* AlgorithmCiftiCrossCorrelation::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiParameter(1, "cifti-a", "first input cifti file"); ret->addCiftiParameter(2, "cifti-b", "second input cifti file"); ret->addCiftiOutputParameter(3, "cifti-out", "output cifti file"); OptionalParameter* weightsOpt = ret->createOptionalParameter(4, "-weights", "specify column weights"); weightsOpt->addStringParameter(1, "weight-file", "text file containing one weight per column"); ret->createOptionalParameter(5, "-fisher-z", "apply fisher small z transform (ie, artanh) to correlation"); OptionalParameter* memLimitOpt = ret->createOptionalParameter(6, "-mem-limit", "restrict memory usage"); memLimitOpt->addDoubleParameter(1, "limit-GB", "memory limit in gigabytes"); ret->setHelpText( AString("Correlates every rown in with every row in . ") + "The mapping along columns in becomes the mapping along rows in the output.\n\n" + "When using the -fisher-z option, the output is NOT a Z-score, it is artanh(r), to do further math on this output, consider using -cifti-math.\n\n" + "Restricting the memory usage will make it calculate the output in chunks, by reading through multiple times." ); return ret; } void AlgorithmCiftiCrossCorrelation::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { CiftiFile* myCiftiA = myParams->getCifti(1); CiftiFile* myCiftiB = myParams->getCifti(2); CiftiFile* myCiftiOut = myParams->getOutputCifti(3); OptionalParameter* weightsOpt = myParams->getOptionalParameter(4); vector* weights = NULL, realweights;//NOTE: realweights is not a pointer if (weightsOpt->m_present) { weights = &realweights;//point it to the actual vector to signify the option is present AString weightFileName = weightsOpt->getString(1); FileInformation textFileInfo(weightFileName); if (!textFileInfo.exists()) { throw AlgorithmException("weight list file doesn't exist"); } fstream weightListFile(weightFileName.toLocal8Bit().constData(), fstream::in); if (!weightListFile.good()) { throw AlgorithmException("error reading weight list file"); } while (weightListFile.good()) { float weight; if (!(weightListFile >> weight))//yes, this is how you check fstream for successfully extracted output. seriously. { break; } realweights.push_back(weight); } } bool fisherZ = myParams->getOptionalParameter(5)->m_present; float memLimitGB = -1.0f; OptionalParameter* memLimitOpt = myParams->getOptionalParameter(6); if (memLimitOpt->m_present) { memLimitGB = (float)memLimitOpt->getDouble(1); if (memLimitGB < 0.0f) { throw AlgorithmException("memory limit cannot be negative"); } } AlgorithmCiftiCrossCorrelation(myProgObj, myCiftiA, myCiftiB, myCiftiOut, weights, fisherZ, memLimitGB); } AlgorithmCiftiCrossCorrelation::AlgorithmCiftiCrossCorrelation(ProgressObject* myProgObj, const CiftiFile* myCiftiA, const CiftiFile* myCiftiB, CiftiFile* myCiftiOut, const vector* weights, const bool& fisherZ, const float& memLimitGB) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); init(myCiftiA, myCiftiB, myCiftiOut, weights); CiftiXMLOld outXML = myCiftiA->getCiftiXMLOld(); outXML.copyMapping(CiftiXMLOld::ALONG_ROW, myCiftiB->getCiftiXMLOld(), CiftiXMLOld::ALONG_COLUMN);//(try to) copy B's along column mapping to output's along row mapping myCiftiOut->setCiftiXML(outXML); int64_t chunkSize = m_numRowsA; if (memLimitGB >= 0.0f) { chunkSize = numRowsForMem(memLimitGB); } vector > outscratch(chunkSize, vector(m_numRowsB));//allocate output rows for (int64_t chunkStart = 0; chunkStart < m_numRowsA; chunkStart += chunkSize) { int64_t chunkEnd = chunkStart + chunkSize; if (chunkEnd > m_numRowsA) chunkEnd = m_numRowsA; cacheRowsA(chunkStart, chunkEnd); int64_t counter = 0; #pragma omp CARET_PARFOR schedule(dynamic) for (int64_t i = 0; i < m_numRowsB; ++i) { float rrsB; int64_t indB; const float* rowB; #pragma omp critical { indB = counter;//manually in-order rows because we need to read them from disk, and can't request more than one at a time ++counter; rowB = getRowB(indB, rrsB); } for (int indA = chunkStart; indA < chunkEnd; ++indA) { float rrsA; const float* rowA = getCachedRowA(indA, rrsA); outscratch[indA - chunkStart][indB] = correlate(rowA, rrsA, rowB, rrsB, fisherZ); } } for (int64_t indA = chunkStart; indA < chunkEnd; ++indA) { myCiftiOut->setRow(outscratch[indA - chunkStart].data(), indA); } } } void AlgorithmCiftiCrossCorrelation::init(const CiftiFile* myCiftiA, const CiftiFile* myCiftiB, const CiftiFile* myCiftiOut, const vector* weights) { m_numCols = myCiftiA->getNumberOfColumns(); if (myCiftiB->getNumberOfColumns() != m_numCols) throw AlgorithmException("input cifti files have different row lengths"); m_numRowsA = myCiftiA->getNumberOfRows(); m_numRowsB = myCiftiB->getNumberOfRows(); m_ciftiA = myCiftiA; m_ciftiB = myCiftiB; m_ciftiOut = myCiftiOut; m_rowInfoA.resize(m_numRowsA);//calls default constructors, setting m_haveCalculated and m_cacheIndex m_rowInfoB.resize(m_numRowsB); if (weights != NULL) { m_weightSum = 0.0; m_weightedMode = true; int64_t numWeights = (int64_t)weights->size(); if (numWeights != m_numCols) throw AlgorithmException("input weights do not match row length"); m_binaryWeights = true; for (int i = 0; i < numWeights; ++i) { float val = (*weights)[i]; if (val != 0.0f) { if (val < 0.0f) { throw AlgorithmException("weights cannot be negative"); } m_weightSum += val; m_weights.push_back(val); m_weightIndexes.push_back(i); if (val != 1.0f) { m_binaryWeights = false; } } } if (m_binaryWeights && m_weights.size() == weights->size()) { m_weightedMode = false;//all weights were 1, so switch back to normal mode } } else { m_weightedMode = false; } } int64_t AlgorithmCiftiCrossCorrelation::numRowsForMem(const float& memLimitGB) { int64_t targetBytes = (int64_t)(memLimitGB * 1024 * 1024 * 1024); if (m_ciftiOut->isInMemory()) targetBytes -= sizeof(float) * m_numRowsA * m_numRowsB;//count only in-memory output against total, the only time inputs might be in memory is in the GUI int64_t bytesPerInputRow = sizeof(float) * m_numCols;//this means we expect the user to give "current free memory" as the limit int64_t bytesPerOutputRow = sizeof(float) * m_numRowsB; #ifdef CARET_OMP targetBytes -= bytesPerInputRow * omp_get_max_threads();//subtract the temporary row memory #else targetBytes -= bytesPerInputRow; #endif int64_t ret = 1; if (targetBytes < 1) { ret = 1;//assume the user knows what they are doing when they request minimum memory usage, even if we do yell at them } else { int64_t numRowsMax = targetBytes / (bytesPerInputRow + bytesPerOutputRow);//calculate max that can fit in given memory if (numRowsMax < 1) numRowsMax = 1; int64_t numPasses = (m_numRowsA - 1) / numRowsMax + 1;//figure the number of passes that makes if (numPasses < 1) { CaretLogWarning("memory usage calculation found zero/negative pass solution, report to developers (it may use a lot of memory this run)"); numPasses = 1; } ret = (m_numRowsA - 1) / numPasses + 1;//and then figure the most even distribution for that number of passes } if (ret == 1) { if (!m_ciftiA->isInMemory() || !m_ciftiB->isInMemory()) { CaretLogWarning("requested memory usage is too low, and at least one input is not in memory, using only 1 row at a time - this may be extremely slow"); } } return ret; } float AlgorithmCiftiCrossCorrelation::correlate(const float* row1, const float& rrs1, const float* row2, const float& rrs2, const bool& fisherZ) { double r; if (m_weightedMode) { int numWeights = (int)m_weightIndexes.size(); double accum = 0.0; for (int i = 0; i < numWeights; ++i)//because we compacted the data in the row to not include any zero weights { accum += row1[i] * row2[i];//these have already had the weighted row means subtracted out, and weights applied } r = accum / (rrs1 * rrs2);//as do these } else { double accum = 0.0; for (int i = 0; i < m_numCols; ++i) { accum += row1[i] * row2[i];//these have already had the row means subtracted out } r = accum / (rrs1 * rrs2); } if (fisherZ) { if (r > 0.999999) r = 0.999999;//prevent inf if (r < -0.999999) r = -0.999999;//prevent -inf return 0.5 * log((1 + r) / (1 - r)); } else { if (r > 1.0) r = 1.0;//don't output anything silly if (r < -1.0) r = -1.0; return r; } } const float* AlgorithmCiftiCrossCorrelation::getCachedRowA(const int64_t& ciftiIndex, float& rootResidSqr) { CaretAssertVectorIndex(m_rowInfoA, ciftiIndex); CaretAssert(m_rowInfoA[ciftiIndex].m_cacheIndex != -1); rootResidSqr = m_rowInfoA[ciftiIndex].m_rootResidSqr; return m_rowCacheA[m_rowInfoA[ciftiIndex].m_cacheIndex].m_row.data(); } const float* AlgorithmCiftiCrossCorrelation::getRowB(const int64_t& ciftiIndex, float& rootResidSqr) { CaretAssertVectorIndex(m_rowInfoB, ciftiIndex); float* ret = getTempRowB();//purely allocation, one array per thread m_ciftiB->getRow(ret, ciftiIndex); adjustRow(ret, m_rowInfoB[ciftiIndex]); rootResidSqr = m_rowInfoB[ciftiIndex].m_rootResidSqr;//NOTE: must do this AFTER adjustRow, because it is not computed before it on the first chunk return ret; } float* AlgorithmCiftiCrossCorrelation::getTempRowB() { #ifdef CARET_OMP int oldsize = (int)m_tempRowsB.size(); int threadNum = omp_get_thread_num(); if (threadNum >= oldsize) { m_tempRowsB.resize(threadNum + 1); for (int i = oldsize; i <= threadNum; ++i) { m_tempRowsB[i] = CaretArray(m_numCols); } } return m_tempRowsB[threadNum].getArray(); #else if (m_tempRowsB.size() == 0) { m_tempRowsB.resize(1); m_tempRowsB[0] = CaretArray(m_numCols); } return m_tempRowsB[0].getArray(); #endif } void AlgorithmCiftiCrossCorrelation::cacheRowsA(const int64_t& begin, const int64_t& end) { CaretAssert(begin > -1); CaretAssert(end <= m_numRowsA); CaretAssert(begin < end);//takes care of end <= 0 and being >= numrows int64_t cacheSize = (int64_t)m_rowCacheA.size();//clear the existing cache references for (int64_t i = 0; i < cacheSize; ++i) { m_rowInfoA[m_rowCacheA[i].m_ciftiIndex].m_cacheIndex = -1; } m_rowCacheA.resize(end - begin);//set to exactly the size needed int64_t counter = begin;//force in-order row reading via critical and counter #pragma omp CARET_PARFOR schedule(dynamic) for (int64_t i = begin; i < end; ++i) { int64_t myindex; #pragma omp critical { myindex = counter; ++counter; m_rowCacheA[myindex - begin].m_row.resize(m_numCols); m_ciftiA->getRow(m_rowCacheA[myindex - begin].m_row.data(), myindex); } CacheRow& myRow = m_rowCacheA[myindex - begin]; myRow.m_ciftiIndex = myindex; m_rowInfoA[myindex].m_cacheIndex = myindex - begin; adjustRow(myRow.m_row.data(), m_rowInfoA[myindex]); } } void AlgorithmCiftiCrossCorrelation::adjustRow(float* row, RowInfo& info) { if (!info.m_haveCalculated)//ensure statistics are calculated { info.m_haveCalculated = true; double accum = 0.0; if (m_weightedMode) { int64_t mycount = (int64_t)m_weightIndexes.size(); if (m_binaryWeights) { for (int64_t i = 0; i < mycount; ++i) { accum += row[m_weightIndexes[i]]; } info.m_mean = accum / mycount; accum = 0.0; for (int64_t i = 0; i < mycount; ++i) { float tempf = row[m_weightIndexes[i]] - info.m_mean; accum += tempf * tempf; } info.m_rootResidSqr = sqrt(accum); } else { for (int64_t i = 0; i < mycount; ++i) { accum += m_weights[i] * row[m_weightIndexes[i]]; } info.m_mean = accum / m_weightSum; accum = 0.0; for (int64_t i = 0; i < mycount; ++i) { float tempf = row[m_weightIndexes[i]] - info.m_mean; accum += m_weights[i] * tempf * tempf; } info.m_rootResidSqr = sqrt(accum); } } else { for (int64_t i = 0; i < m_numCols; ++i) { accum += row[i]; } info.m_mean = accum / m_numCols; accum = 0.0; for (int64_t i = 0; i < m_numCols; ++i) { float tempf = row[i] - info.m_mean; accum += tempf * tempf; } info.m_rootResidSqr = sqrt(accum); } } if (m_weightedMode)//COMPACT data, subtract mean, multiply by square root of weights if applicable { int64_t mycount = (int64_t)m_weightIndexes.size(); if (m_binaryWeights) { for (int64_t i = 0; i < mycount; ++i) { row[i] = row[m_weightIndexes[i]] - info.m_mean; } } else { for (int64_t i = 0; i < mycount; ++i) { row[i] = sqrt(m_weights[i]) * (row[m_weightIndexes[i]] - info.m_mean);//this is so the numerator doesn't get squared weights applied, since this happens to both rows } } } else { for (int64_t i = 0; i < m_numCols; ++i) { row[i] -= info.m_mean; } } } float AlgorithmCiftiCrossCorrelation::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmCiftiCrossCorrelation::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmCiftiCrossCorrelation.h000066400000000000000000000072261255417355300247070ustar00rootroot00000000000000#ifndef __ALGORITHM_CIFTI_CROSS_CORRELATION_H__ #define __ALGORITHM_CIFTI_CROSS_CORRELATION_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" #include "CaretPointer.h" #include namespace caret { class AlgorithmCiftiCrossCorrelation : public AbstractAlgorithm { struct CacheRow { int m_ciftiIndex; std::vector m_row; }; struct RowInfo { bool m_haveCalculated; float m_mean, m_rootResidSqr; int m_cacheIndex; RowInfo() { m_haveCalculated = false; m_cacheIndex = -1; } }; int64_t m_numCols, m_numRowsA, m_numRowsB; const CiftiFile* m_ciftiA, *m_ciftiB, *m_ciftiOut;//output is really only to check if it is in-memory for numRowsForMem std::vector m_rowCacheA;//we only cache from cifti A std::vector m_rowInfoA, m_rowInfoB; std::vector > m_tempRowsB;//reuse return values in getRowB instead of reallocating std::vector m_weights; std::vector m_weightIndexes; bool m_binaryWeights, m_weightedMode; double m_weightSum; AlgorithmCiftiCrossCorrelation(); void init(const CiftiFile* myCiftiA, const CiftiFile* myCiftiB, const CiftiFile* myCiftiOut, const std::vector* weights); int64_t numRowsForMem(const float& memLimitGB);//call after init() float* getTempRowB();//only used for getRowB, A rows are pulled from cache const float* getCachedRowA(const int64_t& ciftiIndex, float& rootResidSqr);//retrieve already cached rows const float* getRowB(const int64_t& ciftiIndex, float& rootResidSqr); void adjustRow(float* row, RowInfo& info); float correlate(const float* row1, const float& rrs1, const float* row2, const float& rrs2, const bool& fisherZ); void cacheRowsA(const int64_t& begin, const int64_t& end);//grabs the rows and does whatever it needs to, using as much IO bandwidth and CPU resources as available/needed protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmCiftiCrossCorrelation(ProgressObject* myProgObj, const CiftiFile* myCiftiA, const CiftiFile* myCiftiB, CiftiFile* myCiftiOut, const std::vector* weights, const bool& fisherZ, const float& memLimitGB); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmCiftiCrossCorrelation; } #endif //__ALGORITHM_CIFTI_CROSS_CORRELATION_H__ workbench-1.1.1/src/Algorithms/AlgorithmCiftiDilate.cxx000066400000000000000000000335271255417355300231740ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmCiftiDilate.h" #include "AlgorithmException.h" #include "AlgorithmCiftiReplaceStructure.h" #include "AlgorithmCiftiSeparate.h" #include "AlgorithmLabelDilate.h" #include "AlgorithmMetricDilate.h" #include "AlgorithmVolumeDilate.h" #include "CiftiFile.h" #include "LabelFile.h" #include "MetricFile.h" #include "SurfaceFile.h" #include "VolumeFile.h" using namespace caret; using namespace std; AString AlgorithmCiftiDilate::getCommandSwitch() { return "-cifti-dilate"; } AString AlgorithmCiftiDilate::getShortDescription() { return "DILATE A CIFTI FILE"; } OperationParameters* AlgorithmCiftiDilate::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiParameter(1, "cifti-in", "the input cifti file"); ret->addStringParameter(2, "direction", "which dimension to dilate along, ROW or COLUMN"); ret->addDoubleParameter(3, "surface-distance", "the distance to dilate on surfaces, in mm"); ret->addDoubleParameter(4, "volume-distance", "the distance to dilate in the volume, in mm"); ret->addCiftiOutputParameter(5, "cifti-out", "the output cifti file"); OptionalParameter* leftSurfOpt = ret->createOptionalParameter(6, "-left-surface", "specify the left surface to use"); leftSurfOpt->addSurfaceParameter(1, "surface", "the left surface file"); OptionalParameter* leftCorrAreasOpt = leftSurfOpt->createOptionalParameter(2, "-left-corrected-areas", "vertex areas to use instead of computing them from the left surface"); leftCorrAreasOpt->addMetricParameter(1, "area-metric", "the corrected vertex areas, as a metric"); OptionalParameter* rightSurfOpt = ret->createOptionalParameter(7, "-right-surface", "specify the right surface to use"); rightSurfOpt->addSurfaceParameter(1, "surface", "the right surface file"); OptionalParameter* rightCorrAreasOpt = rightSurfOpt->createOptionalParameter(2, "-right-corrected-areas", "vertex areas to use instead of computing them from the right surface"); rightCorrAreasOpt->addMetricParameter(1, "area-metric", "the corrected vertex areas, as a metric"); OptionalParameter* cerebSurfOpt = ret->createOptionalParameter(8, "-cerebellum-surface", "specify the cerebellum surface to use"); cerebSurfOpt->addSurfaceParameter(1, "surface", "the cerebellum surface file"); OptionalParameter* cerebCorrAreasOpt = cerebSurfOpt->createOptionalParameter(2, "-cerebellum-corrected-areas", "vertex areas to use instead of computing them from the cerebellum surface"); cerebCorrAreasOpt->addMetricParameter(1, "area-metric", "the corrected vertex areas, as a metric"); OptionalParameter* roiOpt = ret->createOptionalParameter(9, "-bad-brainordinate-roi", "specify an roi of brainordinates to overwrite, rather than zeros"); roiOpt->addCiftiParameter(1, "roi-cifti", "cifti dscalar or dtseries file, positive values denote brainordinates to have their values replaced"); ret->createOptionalParameter(10, "-nearest", "use nearest value when dilating non-label data"); ret->createOptionalParameter(11, "-merged-volume", "treat volume components as if they were a single component"); ret->setHelpText( AString("For all data values designated as bad, if they neighbor a good value or are within the specified distance of a good value in the same kind of model, ") + "replace the value with a distance weighted average of nearby good values, otherwise set the value to zero. " + "If -nearest is specified, it will use the value from the closest good value within range instead of a weighted average.\n\n." + "The -*-corrected-areas options are intended for dilating on group average surfaces, but it is only an approximate correction " + "for the reduction of structure in a group average surface.\n\n" + "If -bad-brainordinate-roi is specified, all values, including those with value zero, are good, except for locations with a positive value in the ROI. " + "If it is not specified, only values equal to zero are bad." ); return ret; } void AlgorithmCiftiDilate::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { CiftiFile* myCifti = myParams->getCifti(1); AString directionName = myParams->getString(2); int myDir; if (directionName == "ROW") { myDir = CiftiXMLOld::ALONG_ROW; } else if (directionName == "COLUMN") { myDir = CiftiXMLOld::ALONG_COLUMN; } else { throw AlgorithmException("incorrect string for direction, use ROW or COLUMN"); } float surfDist = (float)myParams->getDouble(3); float volDist = (float)myParams->getDouble(4); CiftiFile* myCiftiOut = myParams->getOutputCifti(5); SurfaceFile* myLeftSurf = NULL, *myRightSurf = NULL, *myCerebSurf = NULL; MetricFile* myLeftAreas = NULL, *myRightAreas = NULL, *myCerebAreas = NULL; OptionalParameter* leftSurfOpt = myParams->getOptionalParameter(6); if (leftSurfOpt->m_present) { myLeftSurf = leftSurfOpt->getSurface(1); OptionalParameter* leftCorrAreasOpt = leftSurfOpt->getOptionalParameter(2); if (leftCorrAreasOpt->m_present) { myLeftAreas = leftCorrAreasOpt->getMetric(1); } } OptionalParameter* rightSurfOpt = myParams->getOptionalParameter(7); if (rightSurfOpt->m_present) { myRightSurf = rightSurfOpt->getSurface(1); OptionalParameter* rightCorrAreasOpt = rightSurfOpt->getOptionalParameter(2); if (rightCorrAreasOpt->m_present) { myRightAreas = rightCorrAreasOpt->getMetric(1); } } OptionalParameter* cerebSurfOpt = myParams->getOptionalParameter(8); if (cerebSurfOpt->m_present) { myCerebSurf = cerebSurfOpt->getSurface(1); OptionalParameter* cerebCorrAreasOpt = cerebSurfOpt->getOptionalParameter(2); if (cerebCorrAreasOpt->m_present) { myCerebAreas = cerebCorrAreasOpt->getMetric(1); } } CiftiFile* myRoi = NULL; OptionalParameter* roiOpt = myParams->getOptionalParameter(9); if (roiOpt->m_present) { myRoi = roiOpt->getCifti(1); } bool nearest = myParams->getOptionalParameter(10)->m_present; bool mergedVolume = myParams->getOptionalParameter(11)->m_present; AlgorithmCiftiDilate(myProgObj, myCifti, myDir, surfDist, volDist, myCiftiOut, myLeftSurf, myRightSurf, myCerebSurf, myLeftAreas, myRightAreas, myCerebAreas, myRoi, nearest, mergedVolume); } AlgorithmCiftiDilate::AlgorithmCiftiDilate(ProgressObject* myProgObj, const CiftiFile* myCifti, const int& myDir, const float& surfDist, const float& volDist, CiftiFile* myCiftiOut, const SurfaceFile* myLeftSurf, const SurfaceFile* myRightSurf, const SurfaceFile* myCerebSurf, const MetricFile* myLeftAreas, const MetricFile* myRightAreas, const MetricFile* myCerebAreas, const CiftiFile* myBadRoi, const bool& nearest, const bool& mergedVolume) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); CiftiXMLOld myXML = myCifti->getCiftiXMLOld(); vector surfaceList, volumeList; if (myDir != CiftiXMLOld::ALONG_ROW && myDir != CiftiXMLOld::ALONG_COLUMN) throw AlgorithmException("direction not supported by cifti dilate"); if (myBadRoi != NULL && !myXML.mappingMatches(myDir, myBadRoi->getCiftiXMLOld(), CiftiXMLOld::ALONG_COLUMN)) throw AlgorithmException("bad brainordinate roi has different brainordinate space than input"); if (!myXML.getStructureLists(myDir, surfaceList, volumeList)) { throw AlgorithmException("specified direction does not contain brainordinates"); } for (int whichStruct = 0; whichStruct < (int)surfaceList.size(); ++whichStruct) {//sanity check surfaces const SurfaceFile* mySurf = NULL; AString surfType; switch (surfaceList[whichStruct]) { case StructureEnum::CORTEX_LEFT: mySurf = myLeftSurf; surfType = "left"; break; case StructureEnum::CORTEX_RIGHT: mySurf = myRightSurf; surfType = "right"; break; case StructureEnum::CEREBELLUM: mySurf = myCerebSurf; surfType = "cerebellum"; break; default: throw AlgorithmException("found surface model with incorrect type: " + StructureEnum::toName(surfaceList[whichStruct])); break; } if (mySurf == NULL) { throw AlgorithmException(surfType + " surface required but not provided"); } if (mySurf->getNumberOfNodes() != myXML.getSurfaceNumberOfNodes(myDir, surfaceList[whichStruct])) { throw AlgorithmException(surfType + " surface has the wrong number of vertices"); } } myCiftiOut->setCiftiXML(myXML); for (int whichStruct = 0; whichStruct < (int)surfaceList.size(); ++whichStruct) { const SurfaceFile* mySurf = NULL; const MetricFile* myCorrAreas = NULL; switch (surfaceList[whichStruct]) { case StructureEnum::CORTEX_LEFT: mySurf = myLeftSurf; myCorrAreas = myLeftAreas; break; case StructureEnum::CORTEX_RIGHT: mySurf = myRightSurf; myCorrAreas = myRightAreas; break; case StructureEnum::CEREBELLUM: mySurf = myCerebSurf; myCorrAreas = myCerebAreas; break; default: break; } MetricFile badRoiMetric, dataRoiMetric; MetricFile* badRoiPtr = NULL; if (myBadRoi != NULL) { AlgorithmCiftiSeparate(NULL, myBadRoi, CiftiXMLOld::ALONG_COLUMN, surfaceList[whichStruct], &badRoiMetric); badRoiPtr = &badRoiMetric; } if (myXML.getMappingType(1 - myDir) == CIFTI_INDEX_TYPE_LABELS) { LabelFile myLabel, myLabelOut; AlgorithmCiftiSeparate(NULL, myCifti, myDir, surfaceList[whichStruct], &myLabel); AlgorithmLabelDilate(NULL, &myLabel, mySurf, surfDist, &myLabelOut, badRoiPtr, -1, myCorrAreas); AlgorithmCiftiReplaceStructure(NULL, myCiftiOut, myDir, surfaceList[whichStruct], &myLabelOut); } else { MetricFile myMetric, myMetricOut; AlgorithmCiftiSeparate(NULL, myCifti, myDir, surfaceList[whichStruct], &myMetric, &dataRoiMetric); AlgorithmMetricDilate(NULL, &myMetric, mySurf, surfDist, &myMetricOut, badRoiPtr, &dataRoiMetric, -1, nearest, false, 2.0f, myCorrAreas); AlgorithmCiftiReplaceStructure(NULL, myCiftiOut, myDir, surfaceList[whichStruct], &myMetricOut); } } if (mergedVolume) { if (myCifti->getCiftiXMLOld().hasVolumeData(myDir)) { VolumeFile myVol, roiVol, myVolOut; VolumeFile* roiPtr = NULL; int64_t offset[3]; AlgorithmVolumeDilate::Method myMethod = AlgorithmVolumeDilate::WEIGHTED; if (nearest || myXML.getMappingType(1 - myDir) == CIFTI_INDEX_TYPE_LABELS) { myMethod = AlgorithmVolumeDilate::NEAREST; } if (myBadRoi != NULL) { AlgorithmCiftiSeparate(NULL, myBadRoi, CiftiXMLOld::ALONG_COLUMN, &roiVol, offset, NULL, true); roiPtr = &roiVol; } AlgorithmCiftiSeparate(NULL, myCifti, myDir, &myVol, offset, NULL, true); AlgorithmVolumeDilate(NULL, &myVol, volDist, myMethod, &myVolOut, roiPtr); AlgorithmCiftiReplaceStructure(NULL, myCiftiOut, myDir, &myVolOut, true); } } else { AlgorithmVolumeDilate::Method myMethod = AlgorithmVolumeDilate::WEIGHTED; if (nearest || myXML.getMappingType(1 - myDir) == CIFTI_INDEX_TYPE_LABELS) { myMethod = AlgorithmVolumeDilate::NEAREST; } for (int whichStruct = 0; whichStruct < (int)volumeList.size(); ++whichStruct) { VolumeFile myVol, badRoiVol, myVolOut, dataRoiVol; VolumeFile* roiPtr = NULL; int64_t offset[3]; if (myBadRoi != NULL) { AlgorithmCiftiSeparate(NULL, myBadRoi, CiftiXMLOld::ALONG_COLUMN, volumeList[whichStruct], &badRoiVol, offset, NULL, true); roiPtr = &badRoiVol; } AlgorithmCiftiSeparate(NULL, myCifti, myDir, volumeList[whichStruct], &myVol, offset, &dataRoiVol, true); AlgorithmVolumeDilate(NULL, &myVol, volDist, myMethod, &myVolOut, roiPtr, &dataRoiVol); AlgorithmCiftiReplaceStructure(NULL, myCiftiOut, myDir, volumeList[whichStruct], &myVolOut, true); } } } float AlgorithmCiftiDilate::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmCiftiDilate::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmCiftiDilate.h000066400000000000000000000041311255417355300226060ustar00rootroot00000000000000#ifndef __ALGORITHM_CIFTI_DILATE_H__ #define __ALGORITHM_CIFTI_DILATE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmCiftiDilate : public AbstractAlgorithm { AlgorithmCiftiDilate(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmCiftiDilate(ProgressObject* myProgObj, const CiftiFile* myCifti, const int& myDir, const float& surfDist, const float& volDist, CiftiFile* myCiftiOut, const SurfaceFile* myLeftSurf = NULL, const SurfaceFile* myRightSurf = NULL, const SurfaceFile* myCerebSurf = NULL, const MetricFile* myLeftAreas = NULL, const MetricFile* myRightAreas = NULL, const MetricFile* myCerebAreas = NULL, const CiftiFile* myRoi = NULL, const bool& nearest = false, const bool& mergedVolume = false); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmCiftiDilate; } #endif //__ALGORITHM_CIFTI_DILATE_H__ workbench-1.1.1/src/Algorithms/AlgorithmCiftiExtrema.cxx000066400000000000000000000332411255417355300233700ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmCiftiExtrema.h" #include "AlgorithmException.h" #include "AlgorithmCiftiSeparate.h" #include "AlgorithmCiftiReplaceStructure.h" #include "AlgorithmMetricExtrema.h" #include "AlgorithmVolumeExtrema.h" #include "CiftiFile.h" #include "MetricFile.h" #include "SurfaceFile.h" #include "VolumeFile.h" using namespace caret; using namespace std; AString AlgorithmCiftiExtrema::getCommandSwitch() { return "-cifti-extrema"; } AString AlgorithmCiftiExtrema::getShortDescription() { return "FIND EXTREMA IN A CIFTI FILE"; } OperationParameters* AlgorithmCiftiExtrema::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiParameter(1, "cifti", "the input cifti"); ret->addDoubleParameter(2, "surface-distance", "the minimum distance between extrema of the same type, for surface components"); ret->addDoubleParameter(3, "volume-distance", "the minimum distance between extrema of the same type, for volume components"); ret->addStringParameter(4, "direction", "which dimension to find extrema along, ROW or COLUMN"); ret->addCiftiOutputParameter(5, "cifti-out", "the output cifti"); OptionalParameter* leftSurfOpt = ret->createOptionalParameter(6, "-left-surface", "specify the left surface to use"); leftSurfOpt->addSurfaceParameter(1, "surface", "the left surface file"); OptionalParameter* rightSurfOpt = ret->createOptionalParameter(7, "-right-surface", "specify the right surface to use"); rightSurfOpt->addSurfaceParameter(1, "surface", "the right surface file"); OptionalParameter* cerebSurfaceOpt = ret->createOptionalParameter(8, "-cerebellum-surface", "specify the cerebellum surface to use"); cerebSurfaceOpt->addSurfaceParameter(1, "surface", "the cerebellum surface file"); OptionalParameter* presmoothSurfOpt = ret->createOptionalParameter(9, "-surface-presmooth", "smooth on the surface before finding extrema"); presmoothSurfOpt->addDoubleParameter(1, "surface-kernel", "the sigma for the gaussian surface smoothing kernel, in mm"); OptionalParameter* presmoothVolOpt = ret->createOptionalParameter(10, "-volume-presmooth", "smooth volume components before finding extrema"); presmoothVolOpt->addDoubleParameter(1, "volume-kernel", "the sigma for the gaussian volume smoothing kernel, in mm"); OptionalParameter* thresholdOpt = ret->createOptionalParameter(11, "-threshold", "ignore small extrema"); thresholdOpt->addDoubleParameter(1, "low", "the largest value to consider for being a minimum"); thresholdOpt->addDoubleParameter(2, "high", "the smallest value to consider for being a maximum"); ret->createOptionalParameter(12, "-merged-volume", "treat volume components as if they were a single component"); ret->createOptionalParameter(13, "-sum-maps", "output the sum of the extrema maps instead of each map separately"); ret->createOptionalParameter(14, "-consolidate-mode", "use consolidation of local minima instead of a large neighborhood"); ret->createOptionalParameter(15, "-only-maxima", "only find the maxima"); ret->createOptionalParameter(16, "-only-minima", "only find the minima"); ret->setHelpText( AString("Finds spatial locations in a cifti file that have more extreme values than all nearby locations in the same component (surface or volume structure). ") + "The input cifti file must have a brain models mapping along the specified direction. " + "COLUMN is the direction that works on dtseries and dscalar. For dconn, if it is symmetric use COLUMN, otherwise use ROW." ); return ret; } void AlgorithmCiftiExtrema::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { CiftiFile* myCifti = myParams->getCifti(1); float surfDist = (float)myParams->getDouble(2); float volDist = (float)myParams->getDouble(3); AString directionName = myParams->getString(4); int myDir; if (directionName == "ROW") { myDir = CiftiXMLOld::ALONG_ROW; } else if (directionName == "COLUMN") { myDir = CiftiXMLOld::ALONG_COLUMN; } else { throw AlgorithmException("incorrect string for direction, use ROW or COLUMN"); } CiftiFile* myCiftiOut = myParams->getOutputCifti(5); SurfaceFile* myLeftSurf = NULL, *myRightSurf = NULL, *myCerebSurf = NULL; OptionalParameter* leftSurfOpt = myParams->getOptionalParameter(6); if (leftSurfOpt->m_present) { myLeftSurf = leftSurfOpt->getSurface(1); } OptionalParameter* rightSurfOpt = myParams->getOptionalParameter(7); if (rightSurfOpt->m_present) { myRightSurf = rightSurfOpt->getSurface(1); } OptionalParameter* cerebSurfOpt = myParams->getOptionalParameter(8); if (cerebSurfOpt->m_present) { myCerebSurf = cerebSurfOpt->getSurface(1); } float surfPresmooth = -1.0f; OptionalParameter* presmoothSurfOpt = myParams->getOptionalParameter(9); if (presmoothSurfOpt->m_present) { surfPresmooth = (float)presmoothSurfOpt->getDouble(1); } float volPresmooth = -1.0f; OptionalParameter* presmoothVolOpt = myParams->getOptionalParameter(10); if (presmoothVolOpt->m_present) { volPresmooth = (float)presmoothVolOpt->getDouble(1); } OptionalParameter* thresholdOpt = myParams->getOptionalParameter(11); bool thresholdMode = false; float lowThresh = 0.0f, highThresh = 0.0f; if (thresholdOpt->m_present) { thresholdMode = true; lowThresh = (float)thresholdOpt->getDouble(1); highThresh = (float)thresholdOpt->getDouble(2); } bool mergedVolume = myParams->getOptionalParameter(12)->m_present; bool sumMaps = myParams->getOptionalParameter(13)->m_present; bool consolidateMode = myParams->getOptionalParameter(14)->m_present; bool ignoreMinima = myParams->getOptionalParameter(15)->m_present; bool ignoreMaxima = myParams->getOptionalParameter(16)->m_present; if (ignoreMinima && ignoreMaxima) throw AlgorithmException("you may not specify both -only-maxima and -only-minima"); AlgorithmCiftiExtrema(myProgObj, myCifti, surfDist, volDist, myDir, myCiftiOut, myLeftSurf, myRightSurf, myCerebSurf, surfPresmooth, volPresmooth, thresholdMode, lowThresh, highThresh, mergedVolume, sumMaps, consolidateMode, ignoreMinima, ignoreMaxima); } AlgorithmCiftiExtrema::AlgorithmCiftiExtrema(ProgressObject* myProgObj, const CiftiFile* myCifti, const float& surfDist, const float& volDist, const int& myDir, CiftiFile* myCiftiOut, const SurfaceFile* myLeftSurf, const SurfaceFile* myRightSurf, const SurfaceFile* myCerebSurf, const float& surfPresmooth, const float& volPresmooth, const bool& thresholdMode, const float& lowThresh, const float& highThresh, const bool& mergedVolume, const bool& sumMaps, const bool& consolidateMode, const bool& ignoreMinima, const bool& ignoreMaxima) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); CiftiXMLOld myXML = myCifti->getCiftiXMLOld(), myOutXML; myOutXML = myXML; vector surfaceList, volumeList; if (myDir == CiftiXMLOld::ALONG_COLUMN) { if (!myXML.getStructureListsForColumns(surfaceList, volumeList)) { throw AlgorithmException("specified direction does not contain brainordinates"); } } else { if (!myXML.getStructureListsForRows(surfaceList, volumeList)) { throw AlgorithmException("specified direction does not contain brainordinates"); } } for (int whichStruct = 0; whichStruct < (int)surfaceList.size(); ++whichStruct) {//sanity check surfaces const SurfaceFile* mySurf = NULL; AString surfType; switch (surfaceList[whichStruct]) { case StructureEnum::CORTEX_LEFT: mySurf = myLeftSurf; surfType = "left"; break; case StructureEnum::CORTEX_RIGHT: mySurf = myRightSurf; surfType = "right"; break; case StructureEnum::CEREBELLUM: mySurf = myCerebSurf; surfType = "cerebellum"; break; default: throw AlgorithmException("found surface model with incorrect type: " + StructureEnum::toName(surfaceList[whichStruct])); break; } if (mySurf == NULL) { throw AlgorithmException(surfType + " surface required but not provided"); } if (myDir == CiftiXMLOld::ALONG_COLUMN) { if (mySurf->getNumberOfNodes() != myXML.getColumnSurfaceNumberOfNodes(surfaceList[whichStruct])) { throw AlgorithmException(surfType + " surface has the wrong number of vertices"); } } else { if (mySurf->getNumberOfNodes() != myXML.getRowSurfaceNumberOfNodes(surfaceList[whichStruct])) { throw AlgorithmException(surfType + " surface has the wrong number of vertices"); } } } int outDir = myDir; if (sumMaps) { if (myDir == CiftiXMLOld::ALONG_ROW)//NOTE: when using along row and -sum-maps, make a dscalar, not a scalard { myOutXML.copyMapping(CiftiXMLOld::ALONG_COLUMN, myXML, CiftiXMLOld::ALONG_ROW);//so, transpose the maps if we are using dense along row myOutXML.copyMapping(CiftiXMLOld::ALONG_ROW, myXML, CiftiXMLOld::ALONG_COLUMN); } outDir = CiftiXMLOld::ALONG_COLUMN; myOutXML.resetDirectionToScalars(CiftiXMLOld::ALONG_ROW, 1); myOutXML.setMapNameForIndex(CiftiXMLOld::ALONG_ROW, 0, "sum of extrema"); } myCiftiOut->setCiftiXML(myOutXML); for (int whichStruct = 0; whichStruct < (int)surfaceList.size(); ++whichStruct) { const SurfaceFile* mySurf = NULL; switch (surfaceList[whichStruct]) { case StructureEnum::CORTEX_LEFT: mySurf = myLeftSurf; break; case StructureEnum::CORTEX_RIGHT: mySurf = myRightSurf; break; case StructureEnum::CEREBELLUM: mySurf = myCerebSurf; break; default: break; } MetricFile myMetric, myRoi, myMetricOut; AlgorithmCiftiSeparate(NULL, myCifti, myDir, surfaceList[whichStruct], &myMetric, &myRoi); if (thresholdMode) { AlgorithmMetricExtrema(NULL, mySurf, &myMetric, surfDist, &myMetricOut, lowThresh, highThresh, &myRoi, surfPresmooth, sumMaps, consolidateMode, ignoreMinima, ignoreMaxima); } else { AlgorithmMetricExtrema(NULL, mySurf, &myMetric, surfDist, &myMetricOut, &myRoi, surfPresmooth, sumMaps, consolidateMode, ignoreMinima, ignoreMaxima); } AlgorithmCiftiReplaceStructure(NULL, myCiftiOut, outDir, surfaceList[whichStruct], &myMetricOut); } if (mergedVolume) { if (myCifti->getCiftiXMLOld().hasVolumeData(myDir)) { VolumeFile myVol, myRoi, myVolOut; int64_t offset[3]; AlgorithmCiftiSeparate(NULL, myCifti, myDir, &myVol, offset, &myRoi, true); if (thresholdMode) { AlgorithmVolumeExtrema(NULL, &myVol, volDist, &myVolOut, lowThresh, highThresh, &myRoi, volPresmooth, sumMaps, consolidateMode, ignoreMinima, ignoreMaxima); } else { AlgorithmVolumeExtrema(NULL, &myVol, volDist, &myVolOut, &myRoi, volPresmooth, sumMaps, consolidateMode, ignoreMinima, ignoreMaxima); } AlgorithmCiftiReplaceStructure(NULL, myCiftiOut, outDir, &myVolOut, true); } } else { for (int whichStruct = 0; whichStruct < (int)volumeList.size(); ++whichStruct) { VolumeFile myVol, myRoi, myVolOut; int64_t offset[3]; AlgorithmCiftiSeparate(NULL, myCifti, myDir, volumeList[whichStruct], &myVol, offset, &myRoi, true); if (thresholdMode) { AlgorithmVolumeExtrema(NULL, &myVol, volDist, &myVolOut, lowThresh, highThresh, &myRoi, volPresmooth, sumMaps, consolidateMode, ignoreMinima, ignoreMaxima); } else { AlgorithmVolumeExtrema(NULL, &myVol, volDist, &myVolOut, &myRoi, volPresmooth, sumMaps, consolidateMode, ignoreMinima, ignoreMaxima); } AlgorithmCiftiReplaceStructure(NULL, myCiftiOut, outDir, volumeList[whichStruct], &myVolOut, true); } } } float AlgorithmCiftiExtrema::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmCiftiExtrema::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmCiftiExtrema.h000066400000000000000000000044371255417355300230220ustar00rootroot00000000000000#ifndef __ALGORITHM_CIFTI_EXTREMA_H__ #define __ALGORITHM_CIFTI_EXTREMA_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmCiftiExtrema : public AbstractAlgorithm { AlgorithmCiftiExtrema(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmCiftiExtrema(ProgressObject* myProgObj, const CiftiFile* myCifti, const float& surfDist, const float& volDist, const int& myDir, CiftiFile* myCiftiOut, const SurfaceFile* myLeftSurf = NULL, const SurfaceFile* myRightSurf = NULL, const SurfaceFile* myCerebSurf = NULL, const float& surfPresmooth = -1.0f, const float& volPresmooth = -1.0f, const bool& thresholdMode = false, const float& lowThresh = 0.0f, const float& highThresh = 0.0f, const bool& mergedVolume = false, const bool& sumMaps = false, const bool& consolidateMode = false, const bool& ignoreMinima = false, const bool& ignoreMaxima = false); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmCiftiExtrema; } #endif //__ALGORITHM_CIFTI_EXTREMA_H__ workbench-1.1.1/src/Algorithms/AlgorithmCiftiFalseCorrelation.cxx000066400000000000000000000236411255417355300252220ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmCiftiFalseCorrelation.h" #include "AlgorithmException.h" #include "AlgorithmCiftiReplaceStructure.h" #include "AlgorithmCiftiSeparate.h" #include "AlgorithmMetricFalseCorrelation.h" #include "CiftiFile.h" #include "SurfaceFile.h" #include "MetricFile.h" #include "VolumeFile.h" using namespace caret; using namespace std; AString AlgorithmCiftiFalseCorrelation::getCommandSwitch() { return "-cifti-false-correlation"; } AString AlgorithmCiftiFalseCorrelation::getShortDescription() { return "COMPARE CORRELATION LOCALLY AND ACROSS/THROUGH SULCI/GYRI"; } OperationParameters* AlgorithmCiftiFalseCorrelation::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiParameter(1, "cifti-in", "the cifti file to use for correlation"); ret->addDoubleParameter(2, "3D-dist", "maximum 3D distance to check around each vertex"); ret->addDoubleParameter(3, "geo-outer", "maximum geodesic distance to use for neighboring correlation"); ret->addDoubleParameter(4, "geo-inner", "minimum geodesic distance to use for neighboring correlation"); ret->addCiftiOutputParameter(5, "cifti-out", "the output cifti dscalar file"); OptionalParameter* leftSurfOpt = ret->createOptionalParameter(6, "-left-surface", "specify the left surface to use"); leftSurfOpt->addSurfaceParameter(1, "surface", "the left surface file"); OptionalParameter* dumpLeftOpt = leftSurfOpt->createOptionalParameter(2, "-dump-text", "dump the raw measures used to a text file"); dumpLeftOpt->addStringParameter(1, "text-out", "the output text file"); OptionalParameter* rightSurfOpt = ret->createOptionalParameter(7, "-right-surface", "specify the right surface to use"); rightSurfOpt->addSurfaceParameter(1, "surface", "the right surface file"); OptionalParameter* dumpRightOpt = rightSurfOpt->createOptionalParameter(2, "-dump-text", "dump the raw measures used to a text file"); dumpRightOpt->addStringParameter(1, "text-out", "the output text file"); OptionalParameter* cerebSurfOpt = ret->createOptionalParameter(8, "-cerebellum-surface", "specify the cerebellum surface to use"); cerebSurfOpt->addSurfaceParameter(1, "surface", "the cerebellum surface file"); OptionalParameter* dumpCerebOpt = cerebSurfOpt->createOptionalParameter(2, "-dump-text", "dump the raw measures used to a text file"); dumpCerebOpt->addStringParameter(1, "text-out", "the output text file"); ret->setHelpText( AString("For each vertex, compute the average correlation within a range of geodesic distances that don't cross a sulcus/gyrus, and the correlation to the closest vertex crossing a sulcus/gyrus. ") + "A vertex is considered to cross a sulcus/gyrus if the 3D distance is less than a third of the geodesic distance. " + "The output file contains the ratio between these correlations, and some additional maps to help explain the ratio." ); return ret; } void AlgorithmCiftiFalseCorrelation::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { CiftiFile* myCiftiIn = myParams->getCifti(1); float max3D = (float)myParams->getDouble(2); float maxgeo = (float)myParams->getDouble(3); float mingeo = (float)myParams->getDouble(4); CiftiFile* myCiftiOut = myParams->getOutputCifti(5); SurfaceFile* myLeftSurf = NULL, *myRightSurf = NULL, *myCerebSurf = NULL; AString leftDumpName, rightDumpName, cerebDumpName; OptionalParameter* leftSurfOpt = myParams->getOptionalParameter(6); if (leftSurfOpt->m_present) { myLeftSurf = leftSurfOpt->getSurface(1); OptionalParameter* dumpLeftOpt = leftSurfOpt->getOptionalParameter(2); if (dumpLeftOpt->m_present) { leftDumpName = dumpLeftOpt->getString(1); } } OptionalParameter* rightSurfOpt = myParams->getOptionalParameter(7); if (rightSurfOpt->m_present) { myRightSurf = rightSurfOpt->getSurface(1); OptionalParameter* dumpRightOpt = rightSurfOpt->getOptionalParameter(2); if (dumpRightOpt->m_present) { rightDumpName = dumpRightOpt->getString(1); } } OptionalParameter* cerebSurfOpt = myParams->getOptionalParameter(8); if (cerebSurfOpt->m_present) { myCerebSurf = cerebSurfOpt->getSurface(1); OptionalParameter* dumpCerebOpt = cerebSurfOpt->getOptionalParameter(2); if (dumpCerebOpt->m_present) { cerebDumpName = dumpCerebOpt->getString(1); } } AlgorithmCiftiFalseCorrelation(myProgObj, myCiftiIn, max3D, maxgeo, mingeo, myCiftiOut, myLeftSurf, leftDumpName, myRightSurf, rightDumpName, myCerebSurf, cerebDumpName); } AlgorithmCiftiFalseCorrelation::AlgorithmCiftiFalseCorrelation(ProgressObject* myProgObj, const CiftiFile* myCiftiIn, const float& max3D, const float& maxgeo, const float& mingeo, CiftiFile* myCiftiOut, const SurfaceFile* myLeftSurf, const AString& leftDumpName, const SurfaceFile* myRightSurf, const AString& rightDumpName, const SurfaceFile* myCerebSurf, const AString& cerebDumpName) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); const CiftiXMLOld& myXML = myCiftiIn->getCiftiXMLOld(); CiftiXMLOld myNewXML = myXML; vector surfaceList, volumeList; if (!myXML.getStructureLists(CiftiXMLOld::ALONG_COLUMN, surfaceList, volumeList)) { throw AlgorithmException("input cifti does not contain brainordinates along columns"); } for (int whichStruct = 0; whichStruct < (int)surfaceList.size(); ++whichStruct) {//sanity check surfaces const SurfaceFile* mySurf = NULL; AString surfType; switch (surfaceList[whichStruct]) { case StructureEnum::CORTEX_LEFT: mySurf = myLeftSurf; surfType = "left"; break; case StructureEnum::CORTEX_RIGHT: mySurf = myRightSurf; surfType = "right"; break; case StructureEnum::CEREBELLUM: mySurf = myCerebSurf; surfType = "cerebellum"; break; default: throw AlgorithmException("found surface model with incorrect type: " + StructureEnum::toName(surfaceList[whichStruct])); break; } if (mySurf == NULL) { throw AlgorithmException(surfType + " surface required but not provided"); } if (mySurf->getNumberOfNodes() != myXML.getSurfaceNumberOfNodes(CiftiXMLOld::ALONG_COLUMN, surfaceList[whichStruct])) { throw AlgorithmException(surfType + " surface has the wrong number of vertices"); } } myNewXML.resetRowsToScalars(5); myNewXML.setMapNameForIndex(CiftiXMLOld::ALONG_ROW, 0, "correlation ratio"); myNewXML.setMapNameForIndex(CiftiXMLOld::ALONG_ROW, 1, "non-neighborhood correlation"); myNewXML.setMapNameForIndex(CiftiXMLOld::ALONG_ROW, 2, "average neighborhood correlation"); myNewXML.setMapNameForIndex(CiftiXMLOld::ALONG_ROW, 3, "3D distance to non-neighborhood vertex"); myNewXML.setMapNameForIndex(CiftiXMLOld::ALONG_ROW, 4, "non-neighborhood vertex number"); myCiftiOut->setCiftiXML(myNewXML); for (int whichStruct = 0; whichStruct < (int)surfaceList.size(); ++whichStruct) { const SurfaceFile* mySurf = NULL; AString dumpName; switch (surfaceList[whichStruct]) { case StructureEnum::CORTEX_LEFT: mySurf = myLeftSurf; dumpName = leftDumpName; break; case StructureEnum::CORTEX_RIGHT: mySurf = myRightSurf; dumpName = rightDumpName; break; case StructureEnum::CEREBELLUM: mySurf = myCerebSurf; dumpName = cerebDumpName; break; default: break; } MetricFile myMetric, myRoi, myMetricOut; AlgorithmCiftiSeparate(NULL, myCiftiIn, CiftiXML::ALONG_COLUMN, surfaceList[whichStruct], &myMetric, &myRoi); AlgorithmMetricFalseCorrelation(NULL, mySurf, &myMetric, &myMetricOut, max3D, maxgeo, mingeo, &myRoi, dumpName); AlgorithmCiftiReplaceStructure(NULL, myCiftiOut, CiftiXML::ALONG_COLUMN, surfaceList[whichStruct], &myMetricOut); } int64_t offset[3]; vector dims(3); vector > sform; AlgorithmCiftiSeparate::getCroppedVolSpaceAll(myCiftiIn, CiftiXML::ALONG_COLUMN, dims.data(), sform, offset); dims.push_back(5); VolumeFile zeros(dims, sform); zeros.setValueAllVoxels(0.0f); AlgorithmCiftiReplaceStructure(NULL, myCiftiOut, CiftiXML::ALONG_COLUMN, &zeros, true); } float AlgorithmCiftiFalseCorrelation::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmCiftiFalseCorrelation::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmCiftiFalseCorrelation.h000066400000000000000000000040261255417355300246430ustar00rootroot00000000000000#ifndef __ALGORITHM_CIFTI_FALSE_CORRELATION_H__ #define __ALGORITHM_CIFTI_FALSE_CORRELATION_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmCiftiFalseCorrelation : public AbstractAlgorithm { AlgorithmCiftiFalseCorrelation(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmCiftiFalseCorrelation(ProgressObject* myProgObj, const CiftiFile* myCiftiIn, const float& max3D, const float& maxgeo, const float& mingeo, CiftiFile* myCiftiOut, const SurfaceFile* myLeftSurf, const AString& leftDumpName, const SurfaceFile* myRightSurf, const AString& rightDumpName, const SurfaceFile* myCerebSurf, const AString& cerebDumpName); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmCiftiFalseCorrelation; } #endif //__ALGORITHM_CIFTI_FALSE_CORRELATION_H__ workbench-1.1.1/src/Algorithms/AlgorithmCiftiFindClusters.cxx000066400000000000000000000407541255417355300243770ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmCiftiFindClusters.h" #include "AlgorithmException.h" #include "AlgorithmCiftiSeparate.h" #include "AlgorithmCiftiReplaceStructure.h" #include "AlgorithmMetricFindClusters.h" #include "AlgorithmVolumeFindClusters.h" #include "CiftiFile.h" #include "MetricFile.h" #include "SurfaceFile.h" #include "VolumeFile.h" using namespace caret; using namespace std; AString AlgorithmCiftiFindClusters::getCommandSwitch() { return "-cifti-find-clusters"; } AString AlgorithmCiftiFindClusters::getShortDescription() { return "FILTER CLUSTERS BY AREA/VOLUME"; } OperationParameters* AlgorithmCiftiFindClusters::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiParameter(1, "cifti", "the input cifti"); ret->addDoubleParameter(2, "surface-value-threshold", "threshold for surface data values"); ret->addDoubleParameter(3, "surface-minimum-area", "threshold for surface cluster area, in mm^2"); ret->addDoubleParameter(4, "volume-value-threshold", "threshold for volume data values"); ret->addDoubleParameter(5, "volume-minimum-size", "threshold for volume cluster size, in mm^3"); ret->addStringParameter(6, "direction", "which dimension to use for spatial information, ROW or COLUMN"); ret->addCiftiOutputParameter(7, "cifti-out", "the output cifti"); ret->createOptionalParameter(8, "-less-than", "find values less than , rather than greater"); OptionalParameter* leftSurfOpt = ret->createOptionalParameter(9, "-left-surface", "specify the left surface to use"); leftSurfOpt->addSurfaceParameter(1, "surface", "the left surface file"); OptionalParameter* leftCorrAreasOpt = leftSurfOpt->createOptionalParameter(2, "-corrected-areas", "vertex areas to use instead of computing them from the surface"); leftCorrAreasOpt->addMetricParameter(1, "area-metric", "the corrected vertex areas, as a metric"); OptionalParameter* rightSurfOpt = ret->createOptionalParameter(10, "-right-surface", "specify the right surface to use"); rightSurfOpt->addSurfaceParameter(1, "surface", "the right surface file"); OptionalParameter* rightCorrAreasOpt = rightSurfOpt->createOptionalParameter(2, "-corrected-areas", "vertex areas to use instead of computing them from the surface"); rightCorrAreasOpt->addMetricParameter(1, "area-metric", "the corrected vertex areas, as a metric"); OptionalParameter* cerebSurfaceOpt = ret->createOptionalParameter(11, "-cerebellum-surface", "specify the cerebellum surface to use"); cerebSurfaceOpt->addSurfaceParameter(1, "surface", "the cerebellum surface file"); OptionalParameter* cerebCorrAreasOpt = cerebSurfaceOpt->createOptionalParameter(2, "-corrected-areas", "vertex areas to use instead of computing them from the surface"); cerebCorrAreasOpt->addMetricParameter(1, "area-metric", "the corrected vertex areas, as a metric"); OptionalParameter* roiOpt = ret->createOptionalParameter(12, "-cifti-roi", "search only within regions of interest"); roiOpt->addCiftiParameter(1, "roi-cifti", "the regions to search within, as a cifti file"); ret->createOptionalParameter(13, "-merged-volume", "treat volume components as if they were a single component"); OptionalParameter* sizeRatioOpt = ret->createOptionalParameter(15, "-size-ratio", "ignore clusters smaller than a given fraction of the largest cluster in the structure"); sizeRatioOpt->addDoubleParameter(1, "surface-ratio", "fraction of the structure's largest cluster area"); sizeRatioOpt->addDoubleParameter(2, "volume-ratio", "fraction of the structure's largest cluster volume"); OptionalParameter* distanceOpt = ret->createOptionalParameter(16, "-distance", "ignore clusters further than a given distance from the largest cluster in the structure"); distanceOpt->addDoubleParameter(1, "surface-distance", "how far from the largest cluster a cluster can be, edge to edge, in mm"); distanceOpt->addDoubleParameter(2, "volume-distance", "how far from the largest cluster a cluster can be, edge to edge, in mm"); OptionalParameter* startOpt = ret->createOptionalParameter(14, "-start", "start labeling clusters from a value other than 1"); startOpt->addIntegerParameter(1, "startval", "the value to give the first cluster found"); ret->setHelpText( AString("Outputs a cifti file with nonzero integers for all brainordinates within a large enough cluster, and zeros elsewhere. ") + "The integers denote cluster membership (by default, first cluster found will use value 1, second cluster 2, etc). " + "The input cifti file must have a brain models mapping on the chosen dimension, columns for .dtseries, and either for .dconn. " + "The ROI should have a brain models mapping along columns, exactly matching the mapping of the chosen direction in the input file. " + "Data outside the ROI is ignored." ); return ret; } void AlgorithmCiftiFindClusters::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { CiftiFile* myCifti = myParams->getCifti(1); float surfThresh = (float)myParams->getDouble(2); float surfSize = (float)myParams->getDouble(3); float volThresh = (float)myParams->getDouble(4); float volSize = (float)myParams->getDouble(5); AString directionName = myParams->getString(6); int myDir; if (directionName == "ROW") { myDir = CiftiXML::ALONG_ROW; } else if (directionName == "COLUMN") { myDir = CiftiXML::ALONG_COLUMN; } else { throw AlgorithmException("incorrect string for direction, use ROW or COLUMN"); } CiftiFile* myCiftiOut = myParams->getOutputCifti(7); bool lessThan = myParams->getOptionalParameter(8)->m_present; SurfaceFile* myLeftSurf = NULL, *myRightSurf = NULL, *myCerebSurf = NULL; MetricFile* myLeftAreas = NULL, *myRightAreas = NULL, *myCerebAreas = NULL; OptionalParameter* leftSurfOpt = myParams->getOptionalParameter(9); if (leftSurfOpt->m_present) { myLeftSurf = leftSurfOpt->getSurface(1); OptionalParameter* leftCorrAreasOpt = leftSurfOpt->getOptionalParameter(2); if (leftCorrAreasOpt->m_present) { myLeftAreas = leftCorrAreasOpt->getMetric(1); } } OptionalParameter* rightSurfOpt = myParams->getOptionalParameter(10); if (rightSurfOpt->m_present) { myRightSurf = rightSurfOpt->getSurface(1); OptionalParameter* rightCorrAreasOpt = rightSurfOpt->getOptionalParameter(2); if (rightCorrAreasOpt->m_present) { myRightAreas = rightCorrAreasOpt->getMetric(1); } } OptionalParameter* cerebSurfOpt = myParams->getOptionalParameter(11); if (cerebSurfOpt->m_present) { myCerebSurf = cerebSurfOpt->getSurface(1); OptionalParameter* cerebCorrAreasOpt = cerebSurfOpt->getOptionalParameter(2); if (cerebCorrAreasOpt->m_present) { myCerebAreas = cerebCorrAreasOpt->getMetric(1); } } CiftiFile* roiCifti = NULL; OptionalParameter* roiOpt = myParams->getOptionalParameter(12); if (roiOpt->m_present) { roiCifti = roiOpt->getCifti(1); } bool mergedVol = myParams->getOptionalParameter(13)->m_present; OptionalParameter* startOpt = myParams->getOptionalParameter(14); int startVal = 1; if (startOpt->m_present) { startVal = (int)startOpt->getInteger(1); } OptionalParameter* sizeRatioOpt = myParams->getOptionalParameter(15); float surfSizeRatio = -1.0f, volSizeRatio = -1.0f; if (sizeRatioOpt->m_present) { surfSizeRatio = sizeRatioOpt->getDouble(1); volSizeRatio = sizeRatioOpt->getDouble(2); if (surfSizeRatio <= 0.0f && volSizeRatio <= 0.0f) { throw AlgorithmException("at least one size ratio must be positive"); } } OptionalParameter* distanceOpt = myParams->getOptionalParameter(16); float surfDistCutoff = -1.0f, volDistCutoff = -1.0f; if (distanceOpt->m_present) { surfDistCutoff = distanceOpt->getDouble(1); volDistCutoff = distanceOpt->getDouble(2); if (surfDistCutoff <= 0.0f && volDistCutoff <= 0.0f) { throw AlgorithmException("at least one distance cutoff must be positive"); } } AlgorithmCiftiFindClusters(myProgObj, myCifti, surfThresh, surfSize, volThresh, volSize, myDir, myCiftiOut, lessThan, myLeftSurf, myLeftAreas, myRightSurf, myRightAreas, myCerebSurf, myCerebAreas, roiCifti, mergedVol, startVal, NULL, surfSizeRatio, volSizeRatio, surfDistCutoff, volDistCutoff); } AlgorithmCiftiFindClusters::AlgorithmCiftiFindClusters(ProgressObject* myProgObj, const CiftiFile* myCifti, const float& surfThresh, const float& surfSize, const float& volThresh, const float& volSize, const int& myDir, CiftiFile* myCiftiOut, const bool& lessThan, const SurfaceFile* myLeftSurf, const MetricFile* myLeftAreas, const SurfaceFile* myRightSurf, const MetricFile* myRightAreas, const SurfaceFile* myCerebSurf, const MetricFile* myCerebAreas, const CiftiFile* roiCifti, const bool& mergedVol, const int& startVal, int* endVal, const float& surfSizeRatio, const float& volSizeRatio, const float& surfDistCutoff, const float& volDistCutoff) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); if (startVal == 0) { throw AlgorithmException("0 is not a valid cluster marking start value"); } const CiftiXML& myXML = myCifti->getCiftiXML(); if (myXML.getNumberOfDimensions() != 2) throw AlgorithmException("cifti separate only supported on 2D cifti"); if (myDir >= myXML.getNumberOfDimensions() || myDir < 0) throw AlgorithmException("direction invalid for input cifti"); if (myXML.getMappingType(myDir) != CiftiMappingType::BRAIN_MODELS) { throw AlgorithmException("specified direction does not contain brainordinates"); } const CiftiBrainModelsMap& myBrainMap = myXML.getBrainModelsMap(myDir); if (roiCifti != NULL && myBrainMap != *(roiCifti->getCiftiXML().getMap(CiftiXML::ALONG_COLUMN))) { throw AlgorithmException("along-column mapping of roi cifti does not match the smoothing direction of the input cifti"); } vector surfaceList = myBrainMap.getSurfaceStructureList(); int markVal = startVal; for (int whichStruct = 0; whichStruct < (int)surfaceList.size(); ++whichStruct) {//sanity check surfaces const SurfaceFile* mySurf = NULL; const MetricFile* myAreas = NULL; AString surfType; switch (surfaceList[whichStruct]) { case StructureEnum::CORTEX_LEFT: mySurf = myLeftSurf; myAreas = myLeftAreas; surfType = "left"; break; case StructureEnum::CORTEX_RIGHT: mySurf = myRightSurf; myAreas = myRightAreas; surfType = "right"; break; case StructureEnum::CEREBELLUM: mySurf = myCerebSurf; myAreas = myCerebAreas; surfType = "cerebellum"; break; default: throw AlgorithmException("found surface model with incorrect type: " + StructureEnum::toName(surfaceList[whichStruct])); break; } if (mySurf == NULL) { throw AlgorithmException(surfType + " surface required but not provided"); } if (mySurf->getNumberOfNodes() != myBrainMap.getSurfaceNumberOfNodes(surfaceList[whichStruct])) { throw AlgorithmException(surfType + " surface has the wrong number of vertices"); } if (myAreas != NULL && myAreas->getNumberOfNodes() != mySurf->getNumberOfNodes()) { throw AlgorithmException(surfType + " corrected areas metric has the wrong number of vertices"); } } myCiftiOut->setCiftiXML(myXML); for (int whichStruct = 0; whichStruct < (int)surfaceList.size(); ++whichStruct) { const SurfaceFile* mySurf = NULL; const MetricFile* myAreas = NULL; switch (surfaceList[whichStruct]) { case StructureEnum::CORTEX_LEFT: mySurf = myLeftSurf; myAreas = myLeftAreas; break; case StructureEnum::CORTEX_RIGHT: mySurf = myRightSurf; myAreas = myRightAreas; break; case StructureEnum::CEREBELLUM: mySurf = myCerebSurf; myAreas = myCerebAreas; break; default: break; } MetricFile myMetric, myRoi, myMetricOut; AlgorithmCiftiSeparate(NULL, myCifti, myDir, surfaceList[whichStruct], &myMetric, &myRoi); if (roiCifti != NULL) {//due to above testing, we know the structure mask is the same, so just overwrite the ROI from the mask AlgorithmCiftiSeparate(NULL, roiCifti, CiftiXML::ALONG_COLUMN, surfaceList[whichStruct], &myRoi); } AlgorithmMetricFindClusters(NULL, mySurf, &myMetric, surfThresh, surfSize, &myMetricOut, lessThan, &myRoi, myAreas, -1, markVal, &markVal, surfSizeRatio, surfDistCutoff); AlgorithmCiftiReplaceStructure(NULL, myCiftiOut, myDir, surfaceList[whichStruct], &myMetricOut); } if (mergedVol) { if (myBrainMap.hasVolumeData()) { VolumeFile myVol, myRoi, myVolOut; int64_t offset[3]; AlgorithmCiftiSeparate(NULL, myCifti, myDir, &myVol, offset, &myRoi, true); if (roiCifti != NULL) {//due to above testing, we know the structure mask is the same, so just overwrite the ROI from the mask AlgorithmCiftiSeparate(NULL, roiCifti, CiftiXMLOld::ALONG_COLUMN, &myRoi, offset, NULL, true); } AlgorithmVolumeFindClusters(NULL, &myVol, volThresh, volSize, &myVolOut, lessThan, &myRoi, -1, markVal, &markVal, volSizeRatio, volDistCutoff); AlgorithmCiftiReplaceStructure(NULL, myCiftiOut, myDir, &myVolOut, true); } } else { vector volumeList = myBrainMap.getVolumeStructureList(); for (int whichStruct = 0; whichStruct < (int)volumeList.size(); ++whichStruct) { VolumeFile myVol, myRoi, myVolOut; int64_t offset[3]; AlgorithmCiftiSeparate(NULL, myCifti, myDir, volumeList[whichStruct], &myVol, offset, &myRoi, true); if (roiCifti != NULL) {//due to above testing, we know the structure mask is the same, so just overwrite the ROI from the mask AlgorithmCiftiSeparate(NULL, roiCifti, CiftiXML::ALONG_COLUMN, volumeList[whichStruct], &myRoi, offset, NULL, true); } AlgorithmVolumeFindClusters(NULL, &myVol, volThresh, volSize, &myVolOut, lessThan, &myRoi, -1, markVal, &markVal, volSizeRatio, volDistCutoff); AlgorithmCiftiReplaceStructure(NULL, myCiftiOut, myDir, volumeList[whichStruct], &myVolOut, true); } } if (endVal != NULL) *endVal = markVal; } float AlgorithmCiftiFindClusters::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmCiftiFindClusters::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmCiftiFindClusters.h000066400000000000000000000050311255417355300240110ustar00rootroot00000000000000#ifndef __ALGORITHM_CIFTI_FIND_CLUSTERS_H__ #define __ALGORITHM_CIFTI_FIND_CLUSTERS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmCiftiFindClusters : public AbstractAlgorithm { AlgorithmCiftiFindClusters(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmCiftiFindClusters(ProgressObject* myProgObj, const CiftiFile* myCifti, const float& surfThresh, const float& surfSize, const float& volThresh, const float& volSize, const int& myDir, CiftiFile* myCiftiOut, const bool& lessThan = false, const SurfaceFile* myLeftSurf = NULL, const MetricFile* myLeftAreas = NULL, const SurfaceFile* myRightSurf = NULL, const MetricFile* myRightAreas = NULL, const SurfaceFile* myCerebSurf = NULL, const MetricFile* myCerebAreas = NULL, const CiftiFile* roiCifti = NULL, const bool& mergedVol = false, const int& startVal = 1, int* endVal = NULL, const float& surfSizeRatio = -1.0f, const float& volSizeRatio = -1.0f, const float& surfDistCutoff = -1.0f, const float& volDistCutoff = -1.0f); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmCiftiFindClusters; } #endif //__ALGORITHM_CIFTI_FIND_CLUSTERS_H__ workbench-1.1.1/src/Algorithms/AlgorithmCiftiGradient.cxx000066400000000000000000000401541255417355300235210ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmCiftiGradient.h" #include "AlgorithmException.h" #include "AlgorithmMetricGradient.h" #include "AlgorithmVolumeGradient.h" #include "CiftiFile.h" #include "MetricFile.h" #include "VolumeFile.h" #include "SurfaceFile.h" #include "AlgorithmCiftiSeparate.h" #include "AlgorithmCiftiReplaceStructure.h" #include using namespace caret; using namespace std; AString AlgorithmCiftiGradient::getCommandSwitch() { return "-cifti-gradient"; } AString AlgorithmCiftiGradient::getShortDescription() { return "TAKE GRADIENT OF A CIFTI FILE"; } OperationParameters* AlgorithmCiftiGradient::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiParameter(1, "cifti", "the input cifti"); ret->addStringParameter(2, "direction", "which dimension to take the gradient along, ROW or COLUMN"); ret->addCiftiOutputParameter(3, "cifti-out", "the output cifti"); OptionalParameter* leftSurfOpt = ret->createOptionalParameter(4, "-left-surface", "specify the left surface to use"); leftSurfOpt->addSurfaceParameter(1, "surface", "the left surface file"); OptionalParameter* leftCorrAreasOpt = leftSurfOpt->createOptionalParameter(2, "-left-corrected-areas", "vertex areas to use instead of computing them from the left surface"); leftCorrAreasOpt->addMetricParameter(1, "area-metric", "the corrected vertex areas, as a metric"); OptionalParameter* rightSurfOpt = ret->createOptionalParameter(5, "-right-surface", "specify the right surface to use"); rightSurfOpt->addSurfaceParameter(1, "surface", "the right surface file"); OptionalParameter* rightCorrAreasOpt = rightSurfOpt->createOptionalParameter(2, "-right-corrected-areas", "vertex areas to use instead of computing them from the right surface"); rightCorrAreasOpt->addMetricParameter(1, "area-metric", "the corrected vertex areas, as a metric"); OptionalParameter* cerebSurfOpt = ret->createOptionalParameter(6, "-cerebellum-surface", "specify the cerebellum surface to use"); cerebSurfOpt->addSurfaceParameter(1, "surface", "the cerebellum surface file"); OptionalParameter* cerebCorrAreasOpt = cerebSurfOpt->createOptionalParameter(2, "-cerebellum-corrected-areas", "vertex areas to use instead of computing them from the cerebellum surface"); cerebCorrAreasOpt->addMetricParameter(1, "area-metric", "the corrected vertex areas, as a metric"); OptionalParameter* presmoothSurfOpt = ret->createOptionalParameter(7, "-surface-presmooth", "smooth on the surface before computing the gradient"); presmoothSurfOpt->addDoubleParameter(1, "surface-kernel", "the sigma for the gaussian surface smoothing kernel, in mm"); OptionalParameter* presmoothVolOpt = ret->createOptionalParameter(8, "-volume-presmooth", "smooth on the surface before computing the gradient"); presmoothVolOpt->addDoubleParameter(1, "volume-kernel", "the sigma for the gaussian volume smoothing kernel, in mm"); ret->createOptionalParameter(9, "-average-output", "output the average of the gradient magnitude maps instead of each gradient map separately"); OptionalParameter* vectorOpt = ret->createOptionalParameter(10, "-vectors", "output gradient vectors"); vectorOpt->addCiftiOutputParameter(1, "vectors-out", "the vectors, as a dscalar file"); ret->setHelpText( AString("Performs gradient calculation on each component of the cifti file, and optionally averages the resulting gradients. ") + "The -vectors and -average-output options may not be used together. " + "You must specify a surface for each surface structure in the cifti file. The COLUMN direction should be faster, and is the " + "direction that works on dtseries. For dconn, you probably want ROW, unless you are using -average-output." ); return ret; } void AlgorithmCiftiGradient::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { CiftiFile* myCifti = myParams->getCifti(1); AString directionName = myParams->getString(2); int myDir; if (directionName == "ROW") { myDir = CiftiXMLOld::ALONG_ROW; } else if (directionName == "COLUMN") { myDir = CiftiXMLOld::ALONG_COLUMN; } else { throw AlgorithmException("incorrect string for direction, use ROW or COLUMN"); } CiftiFile* myCiftiOut = myParams->getOutputCifti(3); SurfaceFile* myLeftSurf = NULL, *myRightSurf = NULL, *myCerebSurf = NULL; MetricFile* myLeftAreas = NULL, *myRightAreas = NULL, *myCerebAreas = NULL; OptionalParameter* leftSurfOpt = myParams->getOptionalParameter(4); if (leftSurfOpt->m_present) { myLeftSurf = leftSurfOpt->getSurface(1); OptionalParameter* leftCorrAreasOpt = leftSurfOpt->getOptionalParameter(2); if (leftCorrAreasOpt->m_present) { myLeftAreas = leftCorrAreasOpt->getMetric(1); } } OptionalParameter* rightSurfOpt = myParams->getOptionalParameter(5); if (rightSurfOpt->m_present) { myRightSurf = rightSurfOpt->getSurface(1); OptionalParameter* rightCorrAreasOpt = rightSurfOpt->getOptionalParameter(2); if (rightCorrAreasOpt->m_present) { myRightAreas = rightCorrAreasOpt->getMetric(1); } } OptionalParameter* cerebSurfOpt = myParams->getOptionalParameter(6); if (cerebSurfOpt->m_present) { myCerebSurf = cerebSurfOpt->getSurface(1); OptionalParameter* cerebCorrAreasOpt = cerebSurfOpt->getOptionalParameter(2); if (cerebCorrAreasOpt->m_present) { myCerebAreas = cerebCorrAreasOpt->getMetric(1); } } float surfKern = -1.0f; OptionalParameter* presmoothSurfOpt = myParams->getOptionalParameter(7); if (presmoothSurfOpt->m_present) { surfKern = (float)presmoothSurfOpt->getDouble(1); } float volKern = -1.0f; OptionalParameter* presmoothVolOpt = myParams->getOptionalParameter(8); if (presmoothVolOpt->m_present) { volKern = (float)presmoothVolOpt->getDouble(1); } bool outputAverage = myParams->getOptionalParameter(9)->m_present; CiftiFile* ciftiVectorsOut = NULL; OptionalParameter* vectorOpt = myParams->getOptionalParameter(10); if (vectorOpt->m_present) { ciftiVectorsOut = vectorOpt->getOutputCifti(1); } AlgorithmCiftiGradient(myProgObj, myCifti, myDir, myCiftiOut, surfKern, volKern, myLeftSurf, myRightSurf, myCerebSurf, outputAverage, myLeftAreas, myRightAreas, myCerebAreas, ciftiVectorsOut); } AlgorithmCiftiGradient::AlgorithmCiftiGradient(ProgressObject* myProgObj, const CiftiFile* myCifti, const int& myDir, CiftiFile* myCiftiOut, const float& surfKern, const float& volKern, SurfaceFile* myLeftSurf, SurfaceFile* myRightSurf, SurfaceFile* myCerebSurf, bool outputAverage, const MetricFile* myLeftAreas, const MetricFile* myRightAreas, const MetricFile* myCerebAreas, CiftiFile* ciftiVectorsOut) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); const CiftiXML& myXML = myCifti->getCiftiXML(); CiftiXML myNewXML = myXML, myVecXML = myXML; if (myXML.getNumberOfDimensions() != 2) { throw AlgorithmException("cifti gradient only supports 2D cifti"); } if (myXML.getMappingType(myDir) != CiftiMappingType::BRAIN_MODELS) { throw AlgorithmException("specified direction does not contain brainordinates"); } if (outputAverage && ciftiVectorsOut != NULL) { throw AlgorithmException("outputting gradient vectors while averaging gradient magnitude is not supported"); } const CiftiBrainModelsMap& myDenseMap = myXML.getBrainModelsMap(myDir); vector surfaceList = myDenseMap.getSurfaceStructureList(), volumeList = myDenseMap.getVolumeStructureList(); if (outputAverage) { if (myDir == CiftiXML::ALONG_ROW) {//dscalar always has brainordinates on columns, so flip the mappings myNewXML.setMap(CiftiXML::ALONG_COLUMN, *(myNewXML.getMap(CiftiXML::ALONG_ROW))); } CiftiScalarsMap magMap; magMap.setLength(1); magMap.setMapName(0, "gradient average"); myNewXML.setMap(CiftiXML::ALONG_ROW, magMap); } else { if (myDir == CiftiXML::ALONG_ROW) {//dscalar always has brainordinates on columns, so flip the mappings myVecXML.setMap(CiftiXML::ALONG_COLUMN, *(myVecXML.getMap(CiftiXML::ALONG_ROW))); } CiftiScalarsMap vecMap; vecMap.setLength(3 * myXML.getDimensionLength(1 - myDir)); myVecXML.setMap(CiftiXML::ALONG_ROW, vecMap); } for (int whichStruct = 0; whichStruct < (int)surfaceList.size(); ++whichStruct) {//sanity check surfaces SurfaceFile* mySurf = NULL; const MetricFile* myAreas = NULL; AString surfType; switch (surfaceList[whichStruct]) { case StructureEnum::CORTEX_LEFT: mySurf = myLeftSurf; myAreas = myLeftAreas; surfType = "left"; break; case StructureEnum::CORTEX_RIGHT: mySurf = myRightSurf; myAreas = myRightAreas; surfType = "right"; break; case StructureEnum::CEREBELLUM: mySurf = myCerebSurf; myAreas = myCerebAreas; surfType = "cerebellum"; break; default: throw AlgorithmException("found surface model with incorrect type: " + StructureEnum::toName(surfaceList[whichStruct])); break; } if (mySurf == NULL) { throw AlgorithmException(surfType + " surface required but not provided"); } if (mySurf->getNumberOfNodes() != myDenseMap.getSurfaceNumberOfNodes(surfaceList[whichStruct])) { throw AlgorithmException(surfType + " surface has the wrong number of vertices"); } checkStructureMatch(mySurf, surfaceList[whichStruct], "surface file", "the argument expects"); if (myAreas != NULL) { if (myAreas->getNumberOfNodes() != mySurf->getNumberOfNodes()) { throw AlgorithmException(surfType + " surface and vertex area metric have different number of vertices"); } checkStructureMatch(myAreas, surfaceList[whichStruct], "vertex area metric", "the argument expects"); } } myCiftiOut->setCiftiXML(myNewXML); if (ciftiVectorsOut != NULL) { ciftiVectorsOut->setCiftiXML(myVecXML); } for (int whichStruct = 0; whichStruct < (int)surfaceList.size(); ++whichStruct) { SurfaceFile* mySurf = NULL; const MetricFile* myAreas = NULL; switch (surfaceList[whichStruct]) { case StructureEnum::CORTEX_LEFT: mySurf = myLeftSurf; myAreas = myLeftAreas; break; case StructureEnum::CORTEX_RIGHT: mySurf = myRightSurf; myAreas = myRightAreas; break; case StructureEnum::CEREBELLUM: mySurf = myCerebSurf; myAreas = myCerebAreas; break; default: break; } MetricFile myMetric, myRoi, myMetricOut, vectorsOut, *vectorPtr = NULL; if (ciftiVectorsOut != NULL) vectorPtr = &vectorsOut; AlgorithmCiftiSeparate(NULL, myCifti, myDir, surfaceList[whichStruct], &myMetric, &myRoi); AlgorithmMetricGradient(NULL, mySurf, &myMetric, &myMetricOut, vectorPtr, surfKern, &myRoi, false, -1, myAreas); if (outputAverage) { int numNodes = myMetricOut.getNumberOfNodes(), numCols = myMetricOut.getNumberOfColumns(); vector accum(numNodes, 0.0);//use double for numerical stability for (int i = 0; i < numCols; ++i) { const float* column = myMetricOut.getValuePointerForColumn(i); for (int j = 0; j < numNodes; ++j) { accum[j] += column[j]; } } vector temparray(numNodes);//copy result into float array so it can be put into a metric, and then into cifti (yes, really) for (int i = 0; i < numNodes; ++i) { temparray[i] = (float)(accum[i] / numCols); } myMetricOut.setNumberOfNodesAndColumns(numNodes, 1); myMetricOut.setValuesForColumn(0, temparray.data()); AlgorithmCiftiReplaceStructure(NULL, myCiftiOut, CiftiXML::ALONG_COLUMN, surfaceList[whichStruct], &myMetricOut);//average always outputs a dscalar, so always along column } else { AlgorithmCiftiReplaceStructure(NULL, myCiftiOut, myDir, surfaceList[whichStruct], &myMetricOut); if (ciftiVectorsOut != NULL) {//is always a dscalar, so always use column AlgorithmCiftiReplaceStructure(NULL, ciftiVectorsOut, CiftiXML::ALONG_COLUMN, surfaceList[whichStruct], &vectorsOut); } } } for (int whichStruct = 0; whichStruct < (int)volumeList.size(); ++whichStruct) { VolumeFile myVol, myRoi, myVolOut, vecVolOut, *vecVolPtr = NULL; if (ciftiVectorsOut != NULL) vecVolPtr = &vecVolOut; int64_t offset[3]; AlgorithmCiftiSeparate(NULL, myCifti, myDir, volumeList[whichStruct], &myVol, offset, &myRoi, true); AlgorithmVolumeGradient(NULL, &myVol, &myVolOut, volKern, &myRoi, vecVolPtr); if (outputAverage) { vector myDims; myVolOut.getDimensions(myDims); int64_t frameSize = myDims[0] * myDims[1] * myDims[2]; vector accum(frameSize, 0.0); for (int64_t i = 0; i < myDims[3]; ++i) { const float* myFrame = myVolOut.getFrame(i); for (int64_t j = 0; j < frameSize; ++j) { accum[j] += myFrame[j]; } } vector temparray(frameSize); for (int64_t i = 0; i < frameSize; ++i) { temparray[i] = (float)(accum[i] / myDims[3]); } vector newDims = myDims; newDims.resize(3); myVolOut.reinitialize(newDims, myVol.getSform()); myVolOut.setFrame(temparray.data()); AlgorithmCiftiReplaceStructure(NULL, myCiftiOut, CiftiXML::ALONG_COLUMN, volumeList[whichStruct], &myVolOut, true); } else { AlgorithmCiftiReplaceStructure(NULL, myCiftiOut, myDir, volumeList[whichStruct], &myVolOut, true); if (ciftiVectorsOut != NULL) { AlgorithmCiftiReplaceStructure(NULL, ciftiVectorsOut, CiftiXML::ALONG_COLUMN, volumeList[whichStruct], &vecVolOut, true); } } } } float AlgorithmCiftiGradient::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmCiftiGradient::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmCiftiGradient.h000066400000000000000000000042141255417355300231430ustar00rootroot00000000000000#ifndef __ALGORITHM_CIFTI_GRADIENT_H__ #define __ALGORITHM_CIFTI_GRADIENT_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmCiftiGradient : public AbstractAlgorithm { AlgorithmCiftiGradient(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmCiftiGradient(ProgressObject* myProgObj, const CiftiFile* myCifti, const int& myDir, CiftiFile* myCiftiOut, const float& surfKern = -1.0f, const float& volKern = -1.0f, SurfaceFile* myLeftSurf = NULL, SurfaceFile* myRightSurf = NULL, SurfaceFile* myCerebSurf = NULL, bool outputAverage = false, const MetricFile* myLeftAreas = NULL, const MetricFile* myRightAreas = NULL, const MetricFile* myCerebAreas = NULL, CiftiFile* ciftiVectorsOut = NULL); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmCiftiGradient; } #endif //__ALGORITHM_CIFTI_GRADIENT_H__ workbench-1.1.1/src/Algorithms/AlgorithmCiftiLabelAdjacency.cxx000066400000000000000000000251041255417355300246030ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmCiftiLabelAdjacency.h" #include "AlgorithmException.h" #include "AlgorithmCiftiParcellate.h" #include "AlgorithmCiftiSeparate.h" //for cropped volume space #include "CiftiFile.h" #include "SurfaceFile.h" #include "TopologyHelper.h" using namespace caret; using namespace std; AString AlgorithmCiftiLabelAdjacency::getCommandSwitch() { return "-cifti-label-adjacency"; } AString AlgorithmCiftiLabelAdjacency::getShortDescription() { return "MAKE ADJACENCY MATRIX OF A CIFTI LABEL FILE"; } OperationParameters* AlgorithmCiftiLabelAdjacency::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiParameter(1, "label-in", "the input cifti label file"); ret->addCiftiOutputParameter(2, "adjacency-out", "the output cifti pconn adjacency matrix"); OptionalParameter* leftSurfOpt = ret->createOptionalParameter(3, "-left-surface", "specify the left surface to use"); leftSurfOpt->addSurfaceParameter(1, "surface", "the left surface file"); OptionalParameter* rightSurfOpt = ret->createOptionalParameter(4, "-right-surface", "specify the right surface to use"); rightSurfOpt->addSurfaceParameter(1, "surface", "the right surface file"); OptionalParameter* cerebSurfaceOpt = ret->createOptionalParameter(5, "-cerebellum-surface", "specify the cerebellum surface to use"); cerebSurfaceOpt->addSurfaceParameter(1, "surface", "the cerebellum surface file"); ret->setHelpText( AString("Find face-adjacent voxels and connected vertices that have different label values, and count them for each pair. ") + "Put the resulting counts into a parcellated connectivity file, with the diagonal being zero. " + "This gives a rough estimate of how long or expansive the border between two labels is." ); return ret; } void AlgorithmCiftiLabelAdjacency::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { CiftiFile* myLabelIn = myParams->getCifti(1); CiftiFile* myAdjOut = myParams->getOutputCifti(2); SurfaceFile* myLeftSurf = NULL; OptionalParameter* leftSurfOpt = myParams->getOptionalParameter(3); if (leftSurfOpt->m_present) { myLeftSurf = leftSurfOpt->getSurface(1); } SurfaceFile* myRightSurf = NULL; OptionalParameter* rightSurfOpt = myParams->getOptionalParameter(4); if (rightSurfOpt->m_present) { myRightSurf = rightSurfOpt->getSurface(1); } SurfaceFile* myCerebSurf = NULL; OptionalParameter* cerebSurfOpt = myParams->getOptionalParameter(5); if (cerebSurfOpt->m_present) { myCerebSurf = cerebSurfOpt->getSurface(1); } AlgorithmCiftiLabelAdjacency(myProgObj, myLabelIn, myAdjOut, myLeftSurf, myRightSurf, myCerebSurf); } AlgorithmCiftiLabelAdjacency::AlgorithmCiftiLabelAdjacency(ProgressObject* myProgObj, const CiftiFile* myLabelIn, CiftiFile* myAdjOut, const SurfaceFile* myLeftSurf, const SurfaceFile* myRightSurf, const SurfaceFile* myCerebSurf) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); const CiftiXML& myLabelXML = myLabelIn->getCiftiXML(); if (myLabelXML.getNumberOfDimensions() != 2 || myLabelXML.getMappingType(CiftiXML::ALONG_ROW) != CiftiMappingType::LABELS || myLabelXML.getMappingType(CiftiXML::ALONG_COLUMN) != CiftiMappingType::BRAIN_MODELS) { throw AlgorithmException("input cifti label file has the wrong mapping types"); } const CiftiBrainModelsMap& myDenseMap = myLabelXML.getBrainModelsMap(CiftiXML::ALONG_COLUMN); vector surfaceList = myDenseMap.getSurfaceStructureList(); for (int whichStruct = 0; whichStruct < (int)surfaceList.size(); ++whichStruct) {//sanity check surfaces const SurfaceFile* mySurf = NULL; AString surfType; switch (surfaceList[whichStruct]) { case StructureEnum::CORTEX_LEFT: mySurf = myLeftSurf; surfType = "left"; break; case StructureEnum::CORTEX_RIGHT: mySurf = myRightSurf; surfType = "right"; break; case StructureEnum::CEREBELLUM: mySurf = myCerebSurf; surfType = "cerebellum"; break; default: throw AlgorithmException("found surface model with unexpected type: " + StructureEnum::toName(surfaceList[whichStruct])); break; } if (mySurf == NULL) { throw AlgorithmException(surfType + " surface required but not provided"); } if (mySurf->getNumberOfNodes() != myDenseMap.getSurfaceNumberOfNodes(surfaceList[whichStruct])) { throw AlgorithmException(surfType + " surface has the wrong number of vertices"); } } vector indexToParcel; CiftiParcelsMap myParcelMap = AlgorithmCiftiParcellate::parcellateMapping(myLabelIn, myDenseMap, indexToParcel); int numParcels = myParcelMap.getLength(); if (numParcels == 0) throw AlgorithmException("no labels found, output would be empty"); CiftiXML outXML; outXML.setNumberOfDimensions(2); outXML.setMap(CiftiXML::ALONG_ROW, myParcelMap); outXML.setMap(CiftiXML::ALONG_COLUMN, myParcelMap); myAdjOut->setCiftiXML(outXML); vector > adjCount(numParcels, vector(numParcels, 0));//count as int, translate back to float when writing for (int whichStruct = 0; whichStruct < (int)surfaceList.size(); ++whichStruct) { const SurfaceFile* mySurf = NULL; switch (surfaceList[whichStruct]) { case StructureEnum::CORTEX_LEFT: mySurf = myLeftSurf; break; case StructureEnum::CORTEX_RIGHT: mySurf = myRightSurf; break; case StructureEnum::CEREBELLUM: mySurf = myCerebSurf; break; default: throw AlgorithmException("found surface model with unexpected type: " + StructureEnum::toName(surfaceList[whichStruct])); break; } CaretPointer myHelp = mySurf->getTopologyHelper(); int numNodes = mySurf->getNumberOfNodes(); for (int i = 0; i < numNodes - 1; ++i)//to avoid double counting, only count pairs that are in ascending order - this means the last node won't have any valid edges { int64_t baseIndex = myDenseMap.getIndexForNode(i, surfaceList[whichStruct]); if (baseIndex < 0) continue; int baseLabel = indexToParcel[baseIndex];//translate on the fly, to do separate we would need to put indexToParcel into a temporary CiftiFile if (baseLabel < 0) continue; const vector& neighbors = myHelp->getNodeNeighbors(i); int numNeighbors = (int)neighbors.size(); for (int j = 0; j < numNeighbors; ++j) { if (neighbors[j] > i) { int64_t neighIndex = myDenseMap.getIndexForNode(neighbors[j], surfaceList[whichStruct]); if (neighIndex < 0) continue; int neighLabel = indexToParcel[neighIndex]; if (neighLabel < 0) continue; if (baseLabel != neighLabel) { ++adjCount[baseLabel][neighLabel]; ++adjCount[neighLabel][baseLabel]; } } } } } if (myDenseMap.hasVolumeData()) { int64_t dims[3], offset[3];//all we really want is offset and dims, to avoid scanning the original FOV vector > sform; AlgorithmCiftiSeparate::getCroppedVolSpaceAll(myLabelIn, CiftiXML::ALONG_COLUMN, dims, sform, offset); int64_t stencil[9] = {1, 0, 0, 0, 1, 0, 0, 0, 1};//only forward differences, to avoid double counting int64_t ijk[3]; for (ijk[2] = offset[2]; ijk[2] < offset[2] + dims[2]; ++ijk[2]) { for (ijk[1] = offset[1]; ijk[1] < offset[1] + dims[1]; ++ijk[1]) { for (ijk[0] = offset[0]; ijk[0] < offset[0] + dims[0]; ++ijk[0]) { int64_t baseIndex = myDenseMap.getIndexForVoxel(ijk); if (baseIndex < 0) continue; int baseLabel = indexToParcel[baseIndex]; if (baseLabel < 0) continue; for (int neighbor = 0; neighbor < 9; neighbor += 3) { int64_t neighIndex = myDenseMap.getIndexForVoxel(ijk[0] + stencil[neighbor], ijk[1] + stencil[neighbor + 1], ijk[2] + stencil[neighbor + 2]); if (neighIndex < 0) continue; int neighLabel = indexToParcel[neighIndex]; if (neighLabel < 0) continue; if (baseLabel != neighLabel) { ++adjCount[baseLabel][neighLabel]; ++adjCount[neighLabel][baseLabel]; } } } } } } vector tempRow(numParcels); for (int i = 0; i < numParcels; ++i) { for (int j = 0; j < numParcels; ++j) { tempRow[j] = adjCount[i][j]; } myAdjOut->setRow(tempRow.data(), i); } } float AlgorithmCiftiLabelAdjacency::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmCiftiLabelAdjacency::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmCiftiLabelAdjacency.h000066400000000000000000000035311255417355300242300ustar00rootroot00000000000000#ifndef __ALGORITHM_CIFTI_LABEL_ADJACENCY_H__ #define __ALGORITHM_CIFTI_LABEL_ADJACENCY_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmCiftiLabelAdjacency : public AbstractAlgorithm { AlgorithmCiftiLabelAdjacency(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmCiftiLabelAdjacency(ProgressObject* myProgObj, const CiftiFile* myLabelIn, CiftiFile* myAdjOut, const SurfaceFile* myLeftSurf = NULL, const SurfaceFile* myRightSurf = NULL, const SurfaceFile* myCerebSurf = NULL); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmCiftiLabelAdjacency; } #endif //__ALGORITHM_CIFTI_LABEL_ADJACENCY_H__ workbench-1.1.1/src/Algorithms/AlgorithmCiftiLabelToROI.cxx000066400000000000000000000265451255417355300236700ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmCiftiLabelToROI.h" #include "AlgorithmException.h" #include "CaretLogger.h" #include "CiftiFile.h" #include "GiftiLabel.h" #include "GiftiLabelTable.h" #include using namespace caret; using namespace std; AString AlgorithmCiftiLabelToROI::getCommandSwitch() { return "-cifti-label-to-roi"; } AString AlgorithmCiftiLabelToROI::getShortDescription() { return "MAKE A CIFTI LABEL INTO AN ROI"; } OperationParameters* AlgorithmCiftiLabelToROI::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiParameter(1, "label-in", "the input cifti label file"); ret->addCiftiOutputParameter(2, "scalar-out", "the output cifti scalar file"); OptionalParameter* nameOpt = ret->createOptionalParameter(3, "-name", "select label by name"); nameOpt->addStringParameter(1, "label-name", "the label name that you want an roi of"); OptionalParameter* keyOpt = ret->createOptionalParameter(4, "-key", "select label by key"); keyOpt->addIntegerParameter(1, "label-key", "the label key that you want an roi of"); OptionalParameter* mapOpt = ret->createOptionalParameter(5, "-map", "select a single label map to use"); mapOpt->addStringParameter(1, "map", "the map number or name"); ret->setHelpText( AString("For each map in , a map is created in where all locations labeled with or with a key of are given a value of 1, and all other locations are given 0. ") + "Exactly one of -name and -key must be specified. " + "Specify -map to use only one map from ." ); return ret; } void AlgorithmCiftiLabelToROI::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { CiftiFile* myCifti = myParams->getCifti(1); CiftiFile* myCiftiOut = myParams->getOutputCifti(2); bool nameMode = false; AString labelName; OptionalParameter* nameOpt = myParams->getOptionalParameter(3); if (nameOpt->m_present) { nameMode = true; labelName = nameOpt->getString(1); } int32_t labelKey; OptionalParameter* keyOpt = myParams->getOptionalParameter(4); if (keyOpt->m_present) { if (nameMode) throw AlgorithmException("-name and -key cannot be specified together"); labelKey = (int32_t)keyOpt->getInteger(1); } else { if (!nameMode) throw AlgorithmException("you must specify one of -name or -key"); } int64_t whichMap = -1; OptionalParameter* mapOpt = myParams->getOptionalParameter(5); if (mapOpt->m_present) { AString mapID = mapOpt->getString(1); whichMap = myCifti->getCiftiXMLOld().getMapIndexFromNameOrNumber(CiftiXMLOld::ALONG_ROW, mapID); if (whichMap == -1) { throw AlgorithmException("invalid map number or name specified"); } } if (nameMode) { AlgorithmCiftiLabelToROI(myProgObj, myCifti, labelName, myCiftiOut, whichMap); } else { AlgorithmCiftiLabelToROI(myProgObj, myCifti, labelKey, myCiftiOut, whichMap); } } AlgorithmCiftiLabelToROI::AlgorithmCiftiLabelToROI(ProgressObject* myProgObj, const CiftiFile* myCifti, const AString& labelName, CiftiFile* myCiftiOut, const int64_t& whichMap) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); const CiftiXMLOld& myXml = myCifti->getCiftiXMLOld(); CiftiXMLOld outXml = myXml; int64_t numRows = myXml.getNumberOfRows(); int64_t numMaps = myXml.getDimensionLength(CiftiXMLOld::ALONG_ROW); if (whichMap < -1 || whichMap >= numMaps) { throw AlgorithmException("invalid map index specified"); } if (myXml.getMappingType(CiftiXMLOld::ALONG_ROW) != CIFTI_INDEX_TYPE_LABELS) { throw AlgorithmException("input cifti must have labels along rows"); } if (whichMap == -1) { outXml.resetDirectionToScalars(CiftiXMLOld::ALONG_ROW, numMaps); vector matchKey(numMaps, -1); vector haveKey(numMaps, false);//-1 is actually a valid key, track with a second variable bool shouldThrow = true; for (int64_t i = 0; i < numMaps; ++i) { outXml.setMapNameForIndex(CiftiXMLOld::ALONG_ROW, i, myXml.getMapName(CiftiXMLOld::ALONG_ROW, i)); const GiftiLabelTable* myTable = myXml.getLabelTableForRowIndex(i); int thisKey = myTable->getLabelKeyFromName(labelName); if (thisKey != GiftiLabel::getInvalidLabelKey()) { matchKey[i] = thisKey; haveKey[i] = true; shouldThrow = false; } else { CaretLogWarning("label name '" + labelName + "' not found in map #" + AString::number(i + 1)); } } if (shouldThrow) { throw AlgorithmException("label name was not found in any map"); } shouldThrow = true; myCiftiOut->setCiftiXML(outXml); vector rowScratch(numMaps); for (int64_t row = 0; row < numRows; ++row) { myCifti->getRow(rowScratch.data(), row); for (int64_t i = 0; i < numMaps; ++i) { if (haveKey[i]) { int input = (int)floor(rowScratch[i] + 0.5f); if (input == matchKey[i]) { rowScratch[i] = 1.0f; shouldThrow = false; } else { rowScratch[i] = 0.0f; } } else { rowScratch[i] = 0.0f; } } myCiftiOut->setRow(rowScratch.data(), row); } if (shouldThrow) { throw AlgorithmException("no data matched the specified label name"); } } else { float outScratch; outXml.resetDirectionToScalars(CiftiXMLOld::ALONG_ROW, 1); outXml.setMapNameForIndex(CiftiXMLOld::ALONG_ROW, 0, myXml.getMapName(CiftiXMLOld::ALONG_ROW, whichMap)); myCiftiOut->setCiftiXML(outXml); const GiftiLabelTable* myTable = myXml.getLabelTableForRowIndex(whichMap); int matchKey = myTable->getLabelKeyFromName(labelName); if (matchKey == GiftiLabel::getInvalidLabelKey()) { throw AlgorithmException("label name '" + labelName + "' not found in specified map"); } bool shouldThrow = true; vector rowScratch(numMaps); for (int64_t row = 0; row < numRows; ++row) { myCifti->getRow(rowScratch.data(), row); int input = (int)floor(rowScratch[whichMap] + 0.5f); if (input == matchKey) { outScratch = 1.0f; shouldThrow = false; } else { outScratch = 0.0f; } myCiftiOut->setRow(&outScratch, row); } if (shouldThrow) { throw AlgorithmException("no data matched the specified label name in the specified map"); } } } AlgorithmCiftiLabelToROI::AlgorithmCiftiLabelToROI(ProgressObject* myProgObj, const CiftiFile* myCifti, const int32_t& labelKey, CiftiFile* myCiftiOut, const int64_t& whichMap) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); const CiftiXMLOld& myXml = myCifti->getCiftiXMLOld(); CiftiXMLOld outXml = myXml; int64_t numRows = myXml.getNumberOfRows(); int64_t numMaps = myXml.getDimensionLength(CiftiXMLOld::ALONG_ROW); if (whichMap < -1 || whichMap >= numMaps) { throw AlgorithmException("invalid map index specified"); } if (myXml.getMappingType(CiftiXMLOld::ALONG_ROW) != CIFTI_INDEX_TYPE_LABELS) { throw AlgorithmException("input cifti must have labels along rows"); } if (whichMap == -1) { outXml.resetDirectionToScalars(CiftiXMLOld::ALONG_ROW, numMaps); for (int64_t i = 0; i < numMaps; ++i) { outXml.setMapNameForIndex(CiftiXMLOld::ALONG_ROW, i, myXml.getMapName(CiftiXMLOld::ALONG_ROW, i)); const GiftiLabelTable* myTable = myXml.getLabelTableForRowIndex(i); if (myTable->getLabel(labelKey) == NULL) { CaretLogWarning("label key " + AString::number(labelKey) + " not found in map #" + AString::number(i + 1)); } } bool shouldThrow = true; myCiftiOut->setCiftiXML(outXml); vector rowScratch(numMaps); for (int64_t row = 0; row < numRows; ++row) { myCifti->getRow(rowScratch.data(), row); for (int64_t i = 0; i < numMaps; ++i) { int input = (int)floor(rowScratch[i] + 0.5f); if (input == labelKey) { rowScratch[i] = 1.0f; shouldThrow = false; } else { rowScratch[i] = 0.0f; } } myCiftiOut->setRow(rowScratch.data(), row); } if (shouldThrow) { throw AlgorithmException("no data matched the specified label key"); } } else { float outScratch; outXml.resetDirectionToScalars(CiftiXMLOld::ALONG_ROW, 1); outXml.setMapNameForIndex(CiftiXMLOld::ALONG_ROW, 0, myXml.getMapName(CiftiXMLOld::ALONG_ROW, whichMap)); myCiftiOut->setCiftiXML(outXml); const GiftiLabelTable* myTable = myXml.getLabelTableForRowIndex(whichMap); if (myTable->getLabel(labelKey) == NULL) { CaretLogWarning("label key " + AString::number(labelKey) + " not found in specified map"); } bool shouldThrow = true; vector rowScratch(numMaps); for (int64_t row = 0; row < numRows; ++row)//try anyway, in case label table is incomplete { myCifti->getRow(rowScratch.data(), row); int input = (int)floor(rowScratch[whichMap] + 0.5f); if (input == labelKey) { outScratch = 1.0f; shouldThrow = false; } else { outScratch = 0.0f; } myCiftiOut->setRow(&outScratch, row); } if (shouldThrow) { throw AlgorithmException("no data matched the specified label key in the specified map"); } } } float AlgorithmCiftiLabelToROI::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmCiftiLabelToROI::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmCiftiLabelToROI.h000066400000000000000000000036001255417355300233000ustar00rootroot00000000000000#ifndef __ALGORITHM_CIFTI_LABEL_TO_ROI_H__ #define __ALGORITHM_CIFTI_LABEL_TO_ROI_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmCiftiLabelToROI : public AbstractAlgorithm { AlgorithmCiftiLabelToROI(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmCiftiLabelToROI(ProgressObject* myProgObj, const CiftiFile* myCifti, const AString& labelName, CiftiFile* myCiftiOut, const int64_t& whichMap = -1); AlgorithmCiftiLabelToROI(ProgressObject* myProgObj, const CiftiFile* myCifti, const int32_t& labelKey, CiftiFile* myCiftiOut, const int64_t& whichMap = -1); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmCiftiLabelToROI; } #endif //__ALGORITHM_CIFTI_LABEL_TO_ROI_H__ workbench-1.1.1/src/Algorithms/AlgorithmCiftiMergeDense.cxx000066400000000000000000000310051255417355300237750ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmCiftiMergeDense.h" #include "AlgorithmException.h" #include "AlgorithmCiftiReplaceStructure.h" #include "AlgorithmCiftiSeparate.h" #include "CiftiFile.h" #include "LabelFile.h" #include "MetricFile.h" #include "VolumeFile.h" using namespace caret; using namespace std; AString AlgorithmCiftiMergeDense::getCommandSwitch() { return "-cifti-merge-dense"; } AString AlgorithmCiftiMergeDense::getShortDescription() { return "MERGE CIFTI FILES ALONG DENSE DIMENSION"; } OperationParameters* AlgorithmCiftiMergeDense::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addStringParameter(1, "direction", "which dimension to merge along, ROW or COLUMN"); ret->addCiftiOutputParameter(2, "cifti-out", "the output cifti file"); ParameterComponent* ciftiOpt = ret->createRepeatableParameter(3, "-cifti", "specify an input cifti file"); ciftiOpt->addCiftiParameter(1, "cifti-in", "a cifti file to merge"); ret->setHelpText( AString("The input cifti files must have matching mappings along the direction not specified, and the mapping along the specified direction must be brain models.") ); return ret; } void AlgorithmCiftiMergeDense::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { AString directionName = myParams->getString(1); int myDir; if (directionName == "ROW") { myDir = CiftiXMLOld::ALONG_ROW; } else if (directionName == "COLUMN") { myDir = CiftiXMLOld::ALONG_COLUMN; } else { throw AlgorithmException("incorrect string for direction, use ROW or COLUMN"); } CiftiFile* myCiftiOut = myParams->getOutputCifti(2); const vector& myInstances = *(myParams->getRepeatableParameterInstances(3)); vector ciftiList; int numCifti = (int)myInstances.size(); for (int i = 0; i < numCifti; ++i) { ciftiList.push_back(myInstances[i]->getCifti(1)); } AlgorithmCiftiMergeDense(myProgObj, myDir, ciftiList, myCiftiOut); } AlgorithmCiftiMergeDense::AlgorithmCiftiMergeDense(ProgressObject* myProgObj, const int& myDir, const vector& ciftiList, CiftiFile* myCiftiOut) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); if (ciftiList.size() == 0) throw AlgorithmException("no files specified"); CaretAssert(ciftiList[0] != NULL); if (myDir != CiftiXMLOld::ALONG_ROW && myDir != CiftiXMLOld::ALONG_COLUMN) throw AlgorithmException("direction not supported by cifti merge dense"); int otherDir = 1 - myDir;//find the other direction const CiftiXMLOld& baseXML = ciftiList[0]->getCiftiXMLOld(); if (baseXML.getMappingType(myDir) != CIFTI_INDEX_TYPE_BRAIN_MODELS) throw AlgorithmException("mapping type along specified dimension is not brain models"); bool isLabel = (baseXML.getMappingType(otherDir) == CIFTI_INDEX_TYPE_LABELS); CiftiXMLOld outXML = baseXML; VolumeSpace baseSpace; bool haveVolSpace = false; if (baseXML.hasVolumeData(myDir)) { haveVolSpace = true; baseXML.getVolumeSpace(baseSpace); } vector sourceCifti(baseXML.getNumberOfBrainModels(myDir), 0); for (int i = 1; i < (int)ciftiList.size(); ++i) { CaretAssert(ciftiList[i] != NULL); const CiftiXMLOld& otherXML = ciftiList[i]->getCiftiXMLOld(); if (!ciftiList[0]->getCiftiXML().getMap(otherDir)->approximateMatch(*(ciftiList[i]->getCiftiXML().getMap(otherDir)))) { throw AlgorithmException("mappings along other dimension do not match"); } if (otherXML.hasVolumeData(myDir)) { if (haveVolSpace) { VolumeSpace otherSpace; otherXML.getVolumeSpace(otherSpace); if (!baseSpace.matches(otherSpace)) throw AlgorithmException("input cifti files have non-matching volume spaces"); } else { haveVolSpace = true; otherXML.getVolumeSpace(baseSpace); outXML.setVolumeDimsAndSForm(baseSpace.getDims(), baseSpace.getSform());//need to set the output vol space if the first input didn't have volume data } } if (otherXML.getMappingType(myDir) != CIFTI_INDEX_TYPE_BRAIN_MODELS) throw AlgorithmException("mapping type along specified dimension is not brain models"); if ((otherXML.getMappingType(otherDir) == CIFTI_INDEX_TYPE_LABELS) != isLabel) throw AlgorithmException("input files to cifti merge dense mix label and non-label data"); int numModels = otherXML.getNumberOfBrainModels(myDir); for (int j = 0; j < numModels; ++j) { sourceCifti.push_back(i); CiftiBrainModelInfo myInfo = otherXML.getBrainModelInfo(myDir, j); switch (myInfo.m_type) { case CIFTI_MODEL_TYPE_SURFACE: { vector myMap; otherXML.getSurfaceMap(myDir, myMap, myInfo.m_structure); vector nodeList(myMap.size()); for (int64_t k = 0; k < (int64_t)myMap.size(); ++k) { nodeList[k] = myMap[k].m_surfaceNode; } if (!outXML.addSurfaceModel(myDir, otherXML.getSurfaceNumberOfNodes(myDir, myInfo.m_structure), myInfo.m_structure, nodeList)) { throw AlgorithmException("duplicate surface structure " + StructureEnum::toName(myInfo.m_structure) + " found in input to cifti merge dense"); } break; } case CIFTI_MODEL_TYPE_VOXELS: { vector myMap; otherXML.getVolumeStructureMap(myDir, myMap, myInfo.m_structure); vector voxelList(myMap.size() * 3); for (int64_t k = 0; k < (int64_t)myMap.size(); ++k) { int64_t k3 = k * 3; voxelList[k3] = myMap[k].m_ijk[0]; voxelList[k3 + 1] = myMap[k].m_ijk[1]; voxelList[k3 + 2] = myMap[k].m_ijk[2]; } if (!outXML.addVolumeModel(myDir, voxelList, myInfo.m_structure)) { throw AlgorithmException("duplicate volume structure " + StructureEnum::toName(myInfo.m_structure) + " found in input to cifti merge dense"); } break; } default: throw AlgorithmException("encountered unknown model type in cifti merge dense"); } } } CaretAssert((int)sourceCifti.size() == outXML.getNumberOfBrainModels(myDir)); myCiftiOut->setCiftiXML(outXML); for (int i = 0; i < (int)sourceCifti.size(); ++i) { CiftiBrainModelInfo myInfo = outXML.getBrainModelInfo(myDir, i); switch (myInfo.m_type) { case CIFTI_MODEL_TYPE_SURFACE: { if (isLabel) { LabelFile tempFile; AlgorithmCiftiSeparate(NULL, ciftiList[sourceCifti[i]], myDir, myInfo.m_structure, &tempFile);//using this because dealing with label tables is nasty, but doesn't happen on large files AlgorithmCiftiReplaceStructure(NULL, myCiftiOut, myDir, myInfo.m_structure, &tempFile); } else {//for everything else, just use rows directly, because making large metric files in-memory is problematic vector inMap, outMap; const CiftiXMLOld& otherXML = ciftiList[sourceCifti[i]]->getCiftiXMLOld(); outXML.getSurfaceMap(myDir, outMap, myInfo.m_structure); otherXML.getSurfaceMap(myDir, inMap, myInfo.m_structure); CaretAssert(inMap.size() == outMap.size()); vector rowscratch(outXML.getNumberOfColumns()), otherscratch(otherXML.getNumberOfColumns()); if (myDir == CiftiXMLOld::ALONG_ROW) { for (int j = 0; j < outXML.getNumberOfRows(); ++j) { myCiftiOut->getRow(rowscratch.data(), j, true); ciftiList[sourceCifti[i]]->getRow(otherscratch.data(), j); for (int k = 0; k < (int)inMap.size(); ++k) { CaretAssert(inMap[k].m_surfaceNode == outMap[k].m_surfaceNode); rowscratch[outMap[k].m_ciftiIndex] = otherscratch[inMap[k].m_ciftiIndex]; } myCiftiOut->setRow(rowscratch.data(), j); } } else { for (int k = 0; k < (int)inMap.size(); ++k) { CaretAssert(inMap[k].m_surfaceNode == outMap[k].m_surfaceNode); ciftiList[sourceCifti[i]]->getRow(otherscratch.data(), inMap[k].m_ciftiIndex); myCiftiOut->setRow(otherscratch.data(), outMap[k].m_ciftiIndex); } } } break; } case CIFTI_MODEL_TYPE_VOXELS: { vector inMap, outMap; const CiftiXMLOld& otherXML = ciftiList[sourceCifti[i]]->getCiftiXMLOld(); outXML.getVolumeStructureMap(myDir, outMap, myInfo.m_structure); otherXML.getVolumeStructureMap(myDir, inMap, myInfo.m_structure); CaretAssert(inMap.size() == outMap.size()); vector rowscratch(outXML.getNumberOfColumns()), otherscratch(otherXML.getNumberOfColumns()); if (myDir == CiftiXMLOld::ALONG_ROW) { for (int j = 0; j < outXML.getNumberOfRows(); ++j) { myCiftiOut->getRow(rowscratch.data(), j, true); ciftiList[sourceCifti[i]]->getRow(otherscratch.data(), j); for (int k = 0; k < (int)inMap.size(); ++k) { CaretAssert(inMap[k].m_ijk[0] == outMap[k].m_ijk[0]); CaretAssert(inMap[k].m_ijk[1] == outMap[k].m_ijk[1]); CaretAssert(inMap[k].m_ijk[2] == outMap[k].m_ijk[2]); rowscratch[outMap[k].m_ciftiIndex] = otherscratch[inMap[k].m_ciftiIndex]; } myCiftiOut->setRow(rowscratch.data(), j); } } else { for (int k = 0; k < (int)inMap.size(); ++k) { CaretAssert(inMap[k].m_ijk[0] == outMap[k].m_ijk[0]); CaretAssert(inMap[k].m_ijk[1] == outMap[k].m_ijk[1]); CaretAssert(inMap[k].m_ijk[2] == outMap[k].m_ijk[2]); ciftiList[sourceCifti[i]]->getRow(otherscratch.data(), inMap[k].m_ciftiIndex); myCiftiOut->setRow(otherscratch.data(), outMap[k].m_ciftiIndex); } } break; } default: throw AlgorithmException("encountered unknown model type in cifti merge dense"); } } } float AlgorithmCiftiMergeDense::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmCiftiMergeDense::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmCiftiMergeDense.h000066400000000000000000000033561255417355300234320ustar00rootroot00000000000000#ifndef __ALGORITHM_CIFTI_MERGE_DENSE_H__ #define __ALGORITHM_CIFTI_MERGE_DENSE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" #include #include namespace caret { class AlgorithmCiftiMergeDense : public AbstractAlgorithm { AlgorithmCiftiMergeDense(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmCiftiMergeDense(ProgressObject* myProgObj, const int& myDir, const std::vector& ciftiList, CiftiFile* myCiftiOut); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmCiftiMergeDense; } #endif //__ALGORITHM_CIFTI_MERGE_DENSE_H__ workbench-1.1.1/src/Algorithms/AlgorithmCiftiPairwiseCorrelation.cxx000066400000000000000000000117441255417355300257540ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmCiftiPairwiseCorrelation.h" #include "AlgorithmException.h" #include "CaretAssert.h" #include "CaretLogger.h" #include "CaretOMP.h" #include "CiftiFile.h" #include "FileInformation.h" #include using namespace caret; using namespace std; AString AlgorithmCiftiPairwiseCorrelation::getCommandSwitch() { return "-cifti-pairwise-correlation"; } AString AlgorithmCiftiPairwiseCorrelation::getShortDescription() { return "CORRELATE PAIRED ROWS BETWEEN TWO CIFTI FILES"; } OperationParameters* AlgorithmCiftiPairwiseCorrelation::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiParameter(1, "cifti-a", "first input cifti file"); ret->addCiftiParameter(2, "cifti-b", "second input cifti file"); ret->addCiftiOutputParameter(3, "cifti-out", "output cifti file"); ret->createOptionalParameter(4, "-fisher-z", "apply fisher small z transform (ie, artanh) to correlation"); ret->setHelpText( AString("For each row in , correlate it with the same row in , and put the result in the same row of , which has only one column.") ); return ret; } void AlgorithmCiftiPairwiseCorrelation::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { CiftiFile* myCiftiA = myParams->getCifti(1); CiftiFile* myCiftiB = myParams->getCifti(2); CiftiFile* myCiftiOut = myParams->getOutputCifti(3); bool fisherZ = myParams->getOptionalParameter(4)->m_present; AlgorithmCiftiPairwiseCorrelation(myProgObj, myCiftiA, myCiftiB, myCiftiOut, fisherZ); } AlgorithmCiftiPairwiseCorrelation::AlgorithmCiftiPairwiseCorrelation(ProgressObject* myProgObj, const CiftiFile* myCiftiA, const CiftiFile* myCiftiB, CiftiFile* myCiftiOut, const bool& fisherZ) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); CiftiXMLOld outXML = myCiftiA->getCiftiXMLOld(); int64_t numRows = myCiftiA->getNumberOfRows(), rowLength = myCiftiA->getNumberOfColumns(); if (!outXML.matchesForColumns(myCiftiB->getCiftiXMLOld())) throw AlgorithmException("mapping along columns must match between the input files"); if (rowLength != myCiftiB->getNumberOfColumns()) throw AlgorithmException("row length must match between the input files"); outXML.resetRowsToScalars(1); outXML.setMapNameForRowIndex(0, "pairwise correlation"); myCiftiOut->setCiftiXML(outXML); vector rowA(rowLength), rowB(rowLength), columnOut(numRows); for (int64_t i = 0; i < numRows; ++i) { float rrsA, rrsB; myCiftiA->getRow(rowA.data(), i); myCiftiB->getRow(rowB.data(), i); preprocess(rowA.data(), rowLength, rrsA); preprocess(rowB.data(), rowLength, rrsB); columnOut[i] = correlate(rowA.data(), rrsA, rowB.data(), rrsB, rowLength, fisherZ); } myCiftiOut->setColumn(columnOut.data(), 0); } void AlgorithmCiftiPairwiseCorrelation::preprocess(float* row, const int64_t length, float& rrsOut) { double accum = 0.0; for (int64_t i = 0; i < length; ++i) { accum += row[i]; } float mean = accum / length; accum = 0.0; for (int64_t i = 0; i < length; ++i) { row[i] -= mean; accum += row[i] * row[i]; } rrsOut = sqrt(accum); } float AlgorithmCiftiPairwiseCorrelation::correlate(const float* rowA, const float& rrsA, const float* rowB, const float& rrsB, const int64_t& length, const bool& fisherZ) { double r = 0.0; for (int64_t i = 0; i < length; ++i) { r += rowA[i] * rowB[i]; } r /= rrsA * rrsB; if (fisherZ) { if (r > 0.999999) r = 0.999999;//prevent inf if (r < -0.999999) r = -0.999999;//prevent -inf return 0.5 * log((1 + r) / (1 - r)); } else { if (r > 1.0) r = 1.0;//don't output anything silly if (r < -1.0) r = -1.0; return r; } } float AlgorithmCiftiPairwiseCorrelation::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmCiftiPairwiseCorrelation::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmCiftiPairwiseCorrelation.h000066400000000000000000000037741255417355300254050ustar00rootroot00000000000000#ifndef __ALGORITHM_CIFTI_PAIRWISE_CORRELATION_H__ #define __ALGORITHM_CIFTI_PAIRWISE_CORRELATION_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmCiftiPairwiseCorrelation : public AbstractAlgorithm { AlgorithmCiftiPairwiseCorrelation(); void preprocess(float* row, const int64_t length, float& rrsOut); float correlate(const float* rowA, const float& rrsA, const float* rowB, const float& rrsB, const int64_t& length, const bool& fisherZ); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmCiftiPairwiseCorrelation(ProgressObject* myProgObj, const CiftiFile* myCiftiA, const CiftiFile* myCiftiB, CiftiFile* myCiftiOut, const bool& fisherZ = false); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmCiftiPairwiseCorrelation; } #endif //__ALGORITHM_CIFTI_PAIRWISE_CORRELATION_H__ workbench-1.1.1/src/Algorithms/AlgorithmCiftiParcelMappingToLabel.cxx000066400000000000000000000135151255417355300257520ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmCiftiParcelMappingToLabel.h" #include "AlgorithmException.h" #include "CiftiFile.h" #include using namespace caret; using namespace std; AString AlgorithmCiftiParcelMappingToLabel::getCommandSwitch() { return "-cifti-parcel-mapping-to-label"; } AString AlgorithmCiftiParcelMappingToLabel::getShortDescription() { return "CREATE DLABEL FROM PARCELLATED FILE"; } OperationParameters* AlgorithmCiftiParcelMappingToLabel::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiParameter(1, "cifti-in", "the input parcellated file"); ret->addStringParameter(2, "direction", "which dimension to take the parcel map from, ROW or COLUMN"); ret->addCiftiParameter(3, "template-cifti", "a cifti file with the desired dense mapping along column"); ret->addCiftiOutputParameter(4, "dlabel-out", "the output dense label file"); ret->setHelpText( AString("This command will output a dlabel file, useful for doing the same parcellation to another dense file.\n\n") + "For ptseries, pscalar, plabel, pconn, and pdconn, using ROW for will work." ); return ret; } void AlgorithmCiftiParcelMappingToLabel::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { CiftiFile* ciftiIn = myParams->getCifti(1); AString dirString = myParams->getString(2); int direction = -1; if (dirString == "ROW") { direction = CiftiXML::ALONG_ROW; } else if (dirString == "COLUMN") { direction = CiftiXML::ALONG_COLUMN; } else { throw AlgorithmException("unrecognized direction string, use ROW or COLUMN"); } CiftiFile* templateCifti = myParams->getCifti(3); CiftiFile* ciftiOut = myParams->getOutputCifti(4); const CiftiXML& parcelXML = ciftiIn->getCiftiXML(); if (direction >= parcelXML.getNumberOfDimensions()) { throw AlgorithmException("specified direction does not exist in input cifti file"); } if (parcelXML.getMappingType(direction) != CiftiMappingType::PARCELS) { throw AlgorithmException("input cifti file does not have parcels mapping on specified direction"); } const CiftiXML& denseXML = templateCifti->getCiftiXML(); if (denseXML.getNumberOfDimensions() < 2) { throw AlgorithmException("template cifti file does not have an along column dimension"); } if (denseXML.getMappingType(CiftiXML::ALONG_COLUMN) != CiftiMappingType::BRAIN_MODELS) { throw AlgorithmException("template cifti file does not have a dense mapping along column, try using a dscalar, dtseries, dlabel, or dconn"); } AlgorithmCiftiParcelMappingToLabel(myProgObj, parcelXML.getParcelsMap(direction), denseXML.getBrainModelsMap(CiftiXML::ALONG_COLUMN), ciftiOut); } AlgorithmCiftiParcelMappingToLabel::AlgorithmCiftiParcelMappingToLabel(ProgressObject* myProgObj, const CiftiParcelsMap& parcelMap, const CiftiBrainModelsMap& denseMap, CiftiFile* ciftiOut) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); CiftiXML outXML; outXML.setNumberOfDimensions(2); outXML.setMap(CiftiXML::ALONG_COLUMN, denseMap); vector surfStructures = parcelMap.getParcelSurfaceStructures(); CiftiLabelsMap outLabelMap; outLabelMap.setLength(1); GiftiLabelTable* outTable = outLabelMap.getMapLabelTable(0); vector parcelToKey(parcelMap.getLength()); for (int i = 0; i < (int)parcelMap.getLength(); ++i) { parcelToKey[i] = outTable->addLabel(parcelMap.getIndexName(i), rand() & 255, rand() & 255, rand() & 255, 255); } outXML.setMap(CiftiXML::ALONG_ROW, outLabelMap); vector scratchCol(denseMap.getLength(), outTable->getUnassignedLabelKey()); vector surfStructs = denseMap.getSurfaceStructureList(); for (int i = 0; i < (int)surfStructs.size(); ++i) { vector surfMap = denseMap.getSurfaceMap(surfStructs[i]); for (int j = 0; j < (int)surfMap.size(); ++j) { int whichParcel = parcelMap.getIndexForNode(surfMap[j].m_surfaceNode, surfStructs[i]); if (whichParcel >= 0) { scratchCol[surfMap[j].m_ciftiIndex] = parcelToKey[whichParcel]; } } } vector volMap = denseMap.getFullVolumeMap(); for (int i = 0; i < (int)volMap.size(); ++i) { int whichParcel = parcelMap.getIndexForVoxel(volMap[i].m_ijk); if (whichParcel >= 0) { scratchCol[volMap[i].m_ciftiIndex] = parcelToKey[whichParcel]; } } ciftiOut->setCiftiXML(outXML); ciftiOut->setColumn(scratchCol.data(), 0); } float AlgorithmCiftiParcelMappingToLabel::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmCiftiParcelMappingToLabel::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmCiftiParcelMappingToLabel.h000066400000000000000000000035401255417355300253740ustar00rootroot00000000000000#ifndef __ALGORITHM_CIFTI_PARCEL_MAPPING_TO_LABEL_H__ #define __ALGORITHM_CIFTI_PARCEL_MAPPING_TO_LABEL_H__ /*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class CiftiParcelsMap; class CiftiBrainModelsMap; class AlgorithmCiftiParcelMappingToLabel : public AbstractAlgorithm { AlgorithmCiftiParcelMappingToLabel(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmCiftiParcelMappingToLabel(ProgressObject* myProgObj, const CiftiParcelsMap& parcelMap, const CiftiBrainModelsMap& denseMap, CiftiFile* ciftiOut); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmCiftiParcelMappingToLabel; } #endif //__ALGORITHM_CIFTI_PARCEL_MAPPING_TO_LABEL_H__ workbench-1.1.1/src/Algorithms/AlgorithmCiftiParcellate.cxx000066400000000000000000000517471255417355300240520ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmCiftiParcellate.h" #include "AlgorithmException.h" #include "CiftiFile.h" #include "GiftiLabel.h" #include "GiftiLabelTable.h" #include "MultiDimIterator.h" #include "ReductionOperation.h" #include #include using namespace caret; using namespace std; AString AlgorithmCiftiParcellate::getCommandSwitch() { return "-cifti-parcellate"; } AString AlgorithmCiftiParcellate::getShortDescription() { return "PARCELLATE A CIFTI FILE"; } OperationParameters* AlgorithmCiftiParcellate::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiParameter(1, "cifti-in", "the cifti file to parcellate"); ret->addCiftiParameter(2, "cifti-label", "a cifti label file to use for the parcellation"); ret->addStringParameter(3, "direction", "which mapping to parcellate, ROW or COLUMN"); ret->addCiftiOutputParameter(4, "cifti-out", "output cifti file"); ret->setHelpText( AString("Each label in the cifti label file will be treated as a parcel, and all rows or columns within the parcel are averaged together to form the output ") + "row or column. " + "If ROW is specified, then the input mapping along rows must be brainordinates, and the output mapping along rows will be parcels, meaning columns will be averaged together. " + "For dtseries or dscalar, use COLUMN." ); return ret; } void AlgorithmCiftiParcellate::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { CiftiFile* myCiftiIn = myParams->getCifti(1); CiftiFile* myCiftiLabel = myParams->getCifti(2); AString dirString = myParams->getString(3); int direction = -1; if (dirString == "ROW") { direction = CiftiXML::ALONG_ROW; } else { if (dirString == "COLUMN") { direction = CiftiXML::ALONG_COLUMN; } else { throw AlgorithmException("unrecognized direction string, use ROW or COLUMN"); } } CiftiFile* myCiftiOut = myParams->getOutputCifti(4); AlgorithmCiftiParcellate(myProgObj, myCiftiIn, myCiftiLabel, direction, myCiftiOut); } AlgorithmCiftiParcellate::AlgorithmCiftiParcellate(ProgressObject* myProgObj, const CiftiFile* myCiftiIn, const CiftiFile* myCiftiLabel, const int& direction, CiftiFile* myCiftiOut) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); const CiftiXML& myInputXML = myCiftiIn->getCiftiXML(); const CiftiXML& myLabelXML = myCiftiLabel->getCiftiXML(); vector dims = myInputXML.getDimensions(); if (direction >= (int)dims.size()) throw AlgorithmException("specified direction doesn't exist in input file"); if (myInputXML.getMappingType(direction) != CiftiMappingType::BRAIN_MODELS) { throw AlgorithmException("input cifti file does not have brain models mapping type in specified direction"); } if (myLabelXML.getNumberOfDimensions() != 2 || myLabelXML.getMappingType(CiftiXML::ALONG_ROW) != CiftiMappingType::LABELS || myLabelXML.getMappingType(CiftiXML::ALONG_COLUMN) != CiftiMappingType::BRAIN_MODELS) { throw AlgorithmException("input cifti label file has the wrong mapping types"); } const CiftiBrainModelsMap& inputDense = myInputXML.getBrainModelsMap(direction); const CiftiBrainModelsMap& labelDense = myLabelXML.getBrainModelsMap(CiftiXML::ALONG_COLUMN); if (inputDense.hasVolumeData()) {//don't check volume space if direction doesn't have volume data if (labelDense.hasVolumeData() && !inputDense.getVolumeSpace().matches(labelDense.getVolumeSpace())) { throw AlgorithmException("input cifti files must have the same volume space"); } } vector indexToParcel; CiftiXML myOutXML = myInputXML; CiftiParcelsMap outParcelMap = parcellateMapping(myCiftiLabel, inputDense, indexToParcel); int numParcels = outParcelMap.getLength(); if (numParcels < 1) { throw AlgorithmException("no parcels found, output file would be empty, aborting"); } myOutXML.setMap(direction, outParcelMap); myCiftiOut->setCiftiXML(myOutXML); int64_t numCols = myInputXML.getDimensionLength(CiftiXML::ALONG_ROW); vector scratchRow(numCols); vector parcelCounts(numParcels, 0); for (int64_t j = 0; j < (int64_t)indexToParcel.size(); ++j) { int parcel = indexToParcel[j]; CaretAssert(parcel > -2 && parcel < numParcels); if (parcel != -1) { ++parcelCounts[parcel]; } } bool isLabel = false; int labelDir = -1; for (int i = 0; i < (int)dims.size(); ++i) { if (myInputXML.getMappingType(i) == CiftiMappingType::LABELS) { isLabel = true; labelDir = i; break;//there should never be more than one dimension with LABEL type, and if there is, just use the first one, i guess... } } if (direction == CiftiXML::ALONG_ROW) { vector scratchOutRow(numParcels); if (isLabel) { vector > parcelData(numParcels);//float so we can use ReductionOperation (when not considering vertex area, etc) for (int j = 0; j < numParcels; ++j) { parcelData[j].reserve(parcelCounts[j]); } for (MultiDimIterator iter(vector(dims.begin() + 1, dims.end())); !iter.atEnd(); ++iter) { for (int j = 0; j < numParcels; ++j) { parcelData[j].clear();//doesn't change allocation } myCiftiIn->getRow(scratchRow.data(), *iter); for (int64_t j = 0; j < numCols; ++j) { int parcel = indexToParcel[j]; if (parcel != -1) { parcelData[parcel].push_back(floor(scratchRow[j] + 0.5f));//round to nearest integer to be safe } } for (int j = 0; j < numParcels; ++j) { CaretAssert(parcelCounts[j] == (int64_t)parcelData[j].size()); if (parcelCounts[j] > 0) { scratchOutRow[j] = ReductionOperation::reduce(parcelData[j].data(), parcelData[j].size(), ReductionEnum::MODE); } else {//labelDir can't be 0 (row) because we are parcellating along row, so row must be dense scratchOutRow[j] = myOutXML.getLabelsMap(labelDir).getMapLabelTable((*iter)[labelDir - 1])->getUnassignedLabelKey(); } } myCiftiOut->setRow(scratchOutRow.data(), *iter); } } else { for (MultiDimIterator iter(vector(dims.begin() + 1, dims.end())); !iter.atEnd(); ++iter) { vector scratchAccum(numParcels, 0.0); myCiftiIn->getRow(scratchRow.data(), *iter); for (int64_t j = 0; j < numCols; ++j) { int parcel = indexToParcel[j]; if (parcel != -1) { scratchAccum[parcel] += scratchRow[j]; } } for (int j = 0; j < numParcels; ++j) { if (parcelCounts[j] > 0) { scratchOutRow[j] = scratchAccum[j] / parcelCounts[j]; } else { scratchOutRow[j] = 0.0f; } } myCiftiOut->setRow(scratchOutRow.data(), *iter); } } } else { vector scratchOutRow(numCols); vector otherDims = dims; otherDims.erase(otherDims.begin() + direction);//direction being parcellated otherDims.erase(otherDims.begin());//row if (isLabel) { vector > > parcelData(numParcels, vector >(numCols));//float so we can use ReductionOperation (when not considering vertex area, etc) for (int i = 0; i < numParcels; ++i) { for (int j = 0; j < numCols; ++j) { parcelData[i][j].reserve(parcelCounts[i]); } } for (MultiDimIterator iter(otherDims); !iter.atEnd(); ++iter) { vector indices(dims.size() - 1);//we need to add the parcellated direction index back into the index list to use it in getRow/setRow for (int i = 0; i < (int)otherDims.size(); ++i) { if (i < direction - 1) { indices[i] = (*iter)[i]; } else { indices[i + 1] = (*iter)[i]; } }//indices[direction - 1] is uninitialized, as it is the dimension to be parcellated for (int i = 0; i < numParcels; ++i) { for (int j = 0; j < numCols; ++j) { parcelData[i][j].clear();//doesn't change allocation } } for (int64_t i = 0; i < dims[direction]; ++i) { int parcel = indexToParcel[i]; if (parcel != -1) { indices[direction - 1] = i; myCiftiIn->getRow(scratchRow.data(), indices); vector >& parcelRef = parcelData[parcel]; for (int j = 0; j < numCols; ++j) { parcelRef[j].push_back(floor(scratchRow[j] + 0.5f)); } } } for (int i = 0; i < numParcels; ++i) { indices[direction - 1] = i; int64_t count = parcelCounts[i]; vector >& parcelRef = parcelData[i]; if (count > 0) { for (int j = 0; j < numCols; ++j) { CaretAssert((int64_t)parcelRef[j].size() == count); scratchOutRow[j] = ReductionOperation::reduce(parcelRef[j].data(), parcelRef[j].size(), ReductionEnum::MODE); } } else { for (int j = 0; j < numCols; ++j) { CaretAssert((int64_t)parcelRef[j].size() == count); if (labelDir == CiftiXML::ALONG_ROW) { scratchOutRow[j] = myOutXML.getLabelsMap(CiftiXML::ALONG_ROW).getMapLabelTable(j)->getUnassignedLabelKey(); } else { scratchOutRow[j] = myOutXML.getLabelsMap(labelDir).getMapLabelTable(indices[labelDir - 1])->getUnassignedLabelKey(); } } } myCiftiOut->setRow(scratchOutRow.data(), indices); } } } else { for (MultiDimIterator iter(otherDims); !iter.atEnd(); ++iter) { vector indices(dims.size() - 1);//we need to add the parcellated direction index back into the index list to use it in getRow/setRow for (int i = 0; i < (int)otherDims.size(); ++i) { if (i < direction - 1) { indices[i] = (*iter)[i]; } else { indices[i + 1] = (*iter)[i]; } }//indices[direction - 1] is uninitialized, as it is the dimension to be parcellated vector > accumRows(numParcels, vector(numCols, 0.0f)); for (int64_t i = 0; i < dims[direction]; ++i) { int parcel = indexToParcel[i]; if (parcel != -1) { indices[direction - 1] = i; myCiftiIn->getRow(scratchRow.data(), indices); vector& parcelRowRef = accumRows[parcel]; for (int64_t j = 0; j < numCols; ++j) { parcelRowRef[j] += scratchRow[j]; } } } for (int i = 0; i < numParcels; ++i) { indices[direction - 1] = i; int64_t count = parcelCounts[i]; if (count > 0) { vector& parcelRowRef = accumRows[i]; for (int64_t j = 0; j < numCols; ++j) { scratchOutRow[j] = parcelRowRef[j] / count; } } else { for (int64_t j = 0; j < numCols; ++j) { scratchOutRow[j] = 0.0f; } } myCiftiOut->setRow(scratchOutRow.data(), indices); } } } } } CiftiParcelsMap AlgorithmCiftiParcellate::parcellateMapping(const CiftiFile* myCiftiLabel, const CiftiBrainModelsMap& toParcellate, vector& indexToParcelOut) { const CiftiXML& myLabelXML = myCiftiLabel->getCiftiXML(); if (myLabelXML.getNumberOfDimensions() != 2 || myLabelXML.getMappingType(CiftiXML::ALONG_ROW) != CiftiMappingType::LABELS || myLabelXML.getMappingType(CiftiXML::ALONG_COLUMN) != CiftiMappingType::BRAIN_MODELS) { throw AlgorithmException("AlgorithmCiftiParcellate::parcellateMapping requires a cifti dlabel file as input"); } const CiftiLabelsMap& myLabelsMap = myLabelXML.getLabelsMap(CiftiXML::ALONG_ROW); const CiftiBrainModelsMap& myDenseMap = myLabelXML.getBrainModelsMap(CiftiXML::ALONG_COLUMN); CiftiParcelsMap ret; if (toParcellate.hasVolumeData() && myDenseMap.hasVolumeData()) { if(!toParcellate.getVolumeSpace().matches(myDenseMap.getVolumeSpace())) { throw AlgorithmException("AlgorithmCiftiParcellate::parcellateMapping requires matching volume space between dlabel and dense mapping to parcellate"); } ret.setVolumeSpace(toParcellate.getVolumeSpace()); } const GiftiLabelTable* myLabelTable = myLabelsMap.getMapLabelTable(0); vector labelData(myLabelXML.getDimensionLength(CiftiXML::ALONG_COLUMN)); int unusedKey = myLabelTable->getUnassignedLabelKey(); myCiftiLabel->getColumn(labelData.data(), 0); map > usedKeys;//the keys from the label table that actually overlap with data in the input file indexToParcelOut.clear(); indexToParcelOut.resize(toParcellate.getLength(), -1); vector surfList = toParcellate.getSurfaceStructureList(); vector volList = toParcellate.getVolumeStructureList(); for (int i = 0; i < (int)surfList.size(); ++i) { StructureEnum::Enum myStruct = surfList[i]; if (myDenseMap.hasSurfaceData(myStruct) && toParcellate.hasSurfaceData(myStruct)) { if (myDenseMap.getSurfaceNumberOfNodes(myStruct) != toParcellate.getSurfaceNumberOfNodes(myStruct)) { throw AlgorithmException("mismatch in number of surface vertices between input and dlabel for structure " + StructureEnum::toName(myStruct)); } ret.addSurface(toParcellate.getSurfaceNumberOfNodes(myStruct), myStruct); vector surfMap = toParcellate.getSurfaceMap(myStruct); int64_t mapSize = (int64_t)surfMap.size(); for (int64_t j = 0; j < mapSize; ++j) { int64_t labelIndex = myDenseMap.getIndexForNode(surfMap[j].m_surfaceNode, myStruct); if (labelIndex != -1) { int labelKey = (int)floor(labelData[labelIndex] + 0.5f); if (labelKey != unusedKey) { int tempVal = -1; map >::iterator iter = usedKeys.find(labelKey); if (iter == usedKeys.end()) { const GiftiLabel* myLabel = myLabelTable->getLabel(labelKey); if (myLabel != NULL)//ignore values that aren't in the label table { tempVal = usedKeys.size(); CiftiParcelsMap::Parcel tempParcel; tempParcel.m_name = myLabel->getName(); tempParcel.m_surfaceNodes[myStruct].insert(surfMap[j].m_surfaceNode); usedKeys[labelKey] = pair(tempParcel, tempVal); } } else { tempVal = iter->second.second; CiftiParcelsMap::Parcel& tempParcel = iter->second.first; tempParcel.m_surfaceNodes[myStruct].insert(surfMap[j].m_surfaceNode); } indexToParcelOut[surfMap[j].m_ciftiIndex] = tempVal;//we will remap these to be in order of label keys later } } } } } vector volMap = toParcellate.getFullVolumeMap(); int64_t mapSize = (int64_t)volMap.size(); for (int64_t i = 0; i < mapSize; ++i) { int64_t labelIndex = myDenseMap.getIndexForVoxel(volMap[i].m_ijk); if (labelIndex != -1) { int labelKey = (int)floor(labelData[labelIndex] + 0.5f); if (labelKey != unusedKey) { int tempVal = -1; map >::iterator iter = usedKeys.find(labelKey); if (iter == usedKeys.end()) { const GiftiLabel* myLabel = myLabelTable->getLabel(labelKey); if (myLabel != NULL)//ignore values that aren't in the label table { tempVal = usedKeys.size(); CiftiParcelsMap::Parcel tempParcel; tempParcel.m_name = myLabel->getName(); tempParcel.m_voxelIndices.insert(VoxelIJK(volMap[i].m_ijk)); usedKeys[labelKey] = pair(tempParcel, tempVal); } } else { tempVal = iter->second.second; CiftiParcelsMap::Parcel& tempParcel = iter->second.first; tempParcel.m_voxelIndices.insert(VoxelIJK(volMap[i].m_ijk)); } indexToParcelOut[volMap[i].m_ciftiIndex] = tempVal;//we will remap these to be in order of label keys later } } } int numParcels = (int)usedKeys.size(); vector valRemap(numParcels, -1); int count = 0; for (map >::const_iterator iter = usedKeys.begin(); iter != usedKeys.end(); ++iter) { valRemap[iter->second.second] = count;//build a lookup from temp values to label key rank ret.addParcel(iter->second.first); ++count; } int64_t lookupSize = (int64_t)indexToParcelOut.size(); for (int64_t i = 0; i < lookupSize; ++i)//finally, remap the temporary values to the key order of the labels { if (indexToParcelOut[i] != -1) { indexToParcelOut[i] = valRemap[indexToParcelOut[i]]; } } return ret; } float AlgorithmCiftiParcellate::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmCiftiParcellate::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmCiftiParcellate.h000066400000000000000000000037021255417355300234630ustar00rootroot00000000000000#ifndef __ALGORITHM_CIFTI_PARCELLATE_H__ #define __ALGORITHM_CIFTI_PARCELLATE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" #include "CiftiBrainModelsMap.h" #include "CiftiParcelsMap.h" #include namespace caret { class AlgorithmCiftiParcellate : public AbstractAlgorithm { AlgorithmCiftiParcellate(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmCiftiParcellate(ProgressObject* myProgObj, const CiftiFile* myCiftiIn, const CiftiFile* myCiftiLabel, const int& direction, CiftiFile* myCiftiOut); static CiftiParcelsMap parcellateMapping(const CiftiFile* myCiftiLabel, const CiftiBrainModelsMap& toParcellate, std::vector& indexToParcelOut); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmCiftiParcellate; } #endif //__ALGORITHM_CIFTI_PARCELLATE_H__ workbench-1.1.1/src/Algorithms/AlgorithmCiftiROIsFromExtrema.cxx000066400000000000000000000465451255417355300247640ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmCiftiROIsFromExtrema.h" #include "AlgorithmException.h" #include "AlgorithmCiftiSeparate.h" #include "AlgorithmMetricROIsFromExtrema.h" #include "AlgorithmVolumeROIsFromExtrema.h" #include "CaretAssert.h" #include "CaretPointer.h" #include "CiftiFile.h" #include "MetricFile.h" #include "VolumeFile.h" #include "OverlapLogicEnum.h" #include "SurfaceFile.h" #include #include using namespace caret; using namespace std; AString AlgorithmCiftiROIsFromExtrema::getCommandSwitch() { return "-cifti-rois-from-extrema"; } AString AlgorithmCiftiROIsFromExtrema::getShortDescription() { return "CREATE CIFTI ROI MAPS FROM EXTREMA MAPS"; } OperationParameters* AlgorithmCiftiROIsFromExtrema::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiParameter(1, "cifti", "the input cifti"); ret->addDoubleParameter(2, "surf-limit", "geodesic distance limit from vertex, in mm"); ret->addDoubleParameter(3, "vol-limit", "euclidean distance limit from voxel center, in mm"); ret->addStringParameter(4, "direction", "which dimension an extrema map is along, ROW or COLUMN"); ret->addCiftiOutputParameter(5, "cifti-out", "the output cifti"); OptionalParameter* leftSurfOpt = ret->createOptionalParameter(6, "-left-surface", "specify the left surface to use"); leftSurfOpt->addSurfaceParameter(1, "surface", "the left surface file"); OptionalParameter* rightSurfOpt = ret->createOptionalParameter(7, "-right-surface", "specify the right surface to use"); rightSurfOpt->addSurfaceParameter(1, "surface", "the right surface file"); OptionalParameter* cerebSurfaceOpt = ret->createOptionalParameter(8, "-cerebellum-surface", "specify the cerebellum surface to use"); cerebSurfaceOpt->addSurfaceParameter(1, "surface", "the cerebellum surface file"); OptionalParameter* gaussOpt = ret->createOptionalParameter(9, "-gaussian", "generate gaussian kernels instead of flat ROIs"); gaussOpt->addDoubleParameter(1, "surf-sigma", "the sigma for the surface gaussian kernel, in mm"); gaussOpt->addDoubleParameter(2, "vol-sigma", "the sigma for the volume gaussian kernel, in mm"); OptionalParameter* overlapOpt = ret->createOptionalParameter(10, "-overlap-logic", "how to handle overlapping ROIs, default ALLOW"); overlapOpt->addStringParameter(1, "method", "the method of resolving overlaps"); ret->createOptionalParameter(11, "-merged-volume", "treat volume components as if they were a single component"); ret->setHelpText( AString("For each nonzero value in each map, make a map with an ROI around that location. ") + "If the -gaussian option is specified, then normalized gaussian kernels are output instead of ROIs. " + "The argument to -overlap-logic must be one of ALLOW, CLOSEST, or EXCLUDE. " + "ALLOW is the default, and means that ROIs are treated independently and may overlap. " + "CLOSEST means that ROIs may not overlap, and that no ROI contains vertices that are closer to a different seed vertex. " + "EXCLUDE means that ROIs may not overlap, and that any vertex within range of more than one ROI does not belong to any ROI." ); return ret; } void AlgorithmCiftiROIsFromExtrema::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { CiftiFile* myCifti = myParams->getCifti(1); float surfLimit = (float)myParams->getDouble(2); float volLimit = (float)myParams->getDouble(3); AString directionName = myParams->getString(4); int myDir; if (directionName == "ROW") { myDir = CiftiXMLOld::ALONG_ROW; } else if (directionName == "COLUMN") { myDir = CiftiXMLOld::ALONG_COLUMN; } else { throw AlgorithmException("incorrect string for direction, use ROW or COLUMN"); } CiftiFile* myCiftiOut = myParams->getOutputCifti(5); SurfaceFile* myLeftSurf = NULL, *myRightSurf = NULL, *myCerebSurf = NULL; OptionalParameter* leftSurfOpt = myParams->getOptionalParameter(6); if (leftSurfOpt->m_present) { myLeftSurf = leftSurfOpt->getSurface(1); } OptionalParameter* rightSurfOpt = myParams->getOptionalParameter(7); if (rightSurfOpt->m_present) { myRightSurf = rightSurfOpt->getSurface(1); } OptionalParameter* cerebSurfOpt = myParams->getOptionalParameter(8); if (cerebSurfOpt->m_present) { myCerebSurf = cerebSurfOpt->getSurface(1); } float surfSigma = -1.0f, volSigma = -1.0f; OptionalParameter* gaussOpt = myParams->getOptionalParameter(9); if (gaussOpt->m_present) { surfSigma = (float)gaussOpt->getDouble(1); volSigma = (float)gaussOpt->getDouble(2); if (surfSigma <= 0.0f || volSigma <= 0.0f) { throw AlgorithmException("sigma values must be positive"); } } OverlapLogicEnum::Enum myLogic = OverlapLogicEnum::ALLOW; OptionalParameter* overlapOpt = myParams->getOptionalParameter(10); if (overlapOpt->m_present) { bool ok = false; myLogic = OverlapLogicEnum::fromName(overlapOpt->getString(1), &ok); if (!ok) { throw AlgorithmException("unrecognized overlap method"); } } bool mergedVolume = myParams->getOptionalParameter(11)->m_present; AlgorithmCiftiROIsFromExtrema(myProgObj, myCifti, surfLimit, volLimit, myDir, myCiftiOut, myLeftSurf, myRightSurf, myCerebSurf, surfSigma, volSigma, myLogic, mergedVolume); } AlgorithmCiftiROIsFromExtrema::AlgorithmCiftiROIsFromExtrema(ProgressObject* myProgObj, const CiftiFile* myCifti, const float& surfLimit, const float& volLimit, const int& myDir, CiftiFile* myCiftiOut, const SurfaceFile* myLeftSurf, const SurfaceFile* myRightSurf, const SurfaceFile* myCerebSurf, const float& surfSigma, const float& volSigma, const OverlapLogicEnum::Enum& myLogic, const bool& mergedVolume) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); CiftiXMLOld myXML = myCifti->getCiftiXMLOld(); vector surfaceList, volumeList; if (myDir != CiftiXMLOld::ALONG_ROW && myDir != CiftiXMLOld::ALONG_COLUMN) throw AlgorithmException("direction not supported by cifti rois from extrema"); if (!myXML.getStructureLists(myDir, surfaceList, volumeList)) { throw AlgorithmException("columns do not contain brainordinates"); } for (int whichStruct = 0; whichStruct < (int)surfaceList.size(); ++whichStruct) {//sanity check surfaces const SurfaceFile* mySurf = NULL; AString surfType; switch (surfaceList[whichStruct]) { case StructureEnum::CORTEX_LEFT: mySurf = myLeftSurf; surfType = "left"; break; case StructureEnum::CORTEX_RIGHT: mySurf = myRightSurf; surfType = "right"; break; case StructureEnum::CEREBELLUM: mySurf = myCerebSurf; surfType = "cerebellum"; break; default: throw AlgorithmException("found surface model with incorrect type: " + StructureEnum::toName(surfaceList[whichStruct])); break; } if (mySurf == NULL) { throw AlgorithmException(surfType + " surface required but not provided"); } if (mySurf->getNumberOfNodes() != myXML.getColumnSurfaceNumberOfNodes(surfaceList[whichStruct])) { throw AlgorithmException(surfType + " surface has the wrong number of vertices"); } } vector > surfROIs; vector > volROIs; vector volOffsets; int64_t mapCount = 0; for (int whichStruct = 0; whichStruct < (int)surfaceList.size(); ++whichStruct) { const SurfaceFile* mySurf = NULL; switch (surfaceList[whichStruct]) { case StructureEnum::CORTEX_LEFT: mySurf = myLeftSurf; break; case StructureEnum::CORTEX_RIGHT: mySurf = myRightSurf; break; case StructureEnum::CEREBELLUM: mySurf = myCerebSurf; break; default: break; } MetricFile myMetric, myRoi, myMetricOut; AlgorithmCiftiSeparate(NULL, myCifti, myDir, surfaceList[whichStruct], &myMetric, &myRoi); surfROIs.push_back(CaretPointer(new MetricFile())); AlgorithmMetricROIsFromExtrema(NULL, mySurf, &myMetric, surfLimit, surfROIs.back(), surfSigma, &myRoi, myLogic); mapCount += surfROIs.back()->getNumberOfMaps(); } if (mergedVolume) { if (myCifti->getCiftiXMLOld().hasVolumeData(myDir)) { VolumeFile myVol, myRoi, myVolOut; volOffsets.resize(3); AlgorithmCiftiSeparate(NULL, myCifti, myDir, &myVol, volOffsets.data(), &myRoi, true);//without structure, it returns a combined volume volROIs.push_back(CaretPointer(new VolumeFile())); AlgorithmVolumeROIsFromExtrema(NULL, &myVol, volLimit, volROIs.back(), volSigma, &myRoi, myLogic); vector tempDims; volROIs.back()->getDimensions(tempDims); mapCount += tempDims[3];//because getNumberOfMaps only returns int32 } } else { volOffsets.resize(volumeList.size() * 3); for (int whichStruct = 0; whichStruct < (int)volumeList.size(); ++whichStruct) { VolumeFile myVol, myRoi, myVolOut; AlgorithmCiftiSeparate(NULL, myCifti, myDir, volumeList[whichStruct], &myVol, volOffsets.data() + 3 * whichStruct, &myRoi, true); volROIs.push_back(CaretPointer(new VolumeFile())); AlgorithmVolumeROIsFromExtrema(NULL, &myVol, volLimit, volROIs.back(), volSigma, &myRoi, myLogic); vector tempDims; volROIs.back()->getDimensions(tempDims); mapCount += tempDims[3];//because getNumberOfMaps only returns int32 } } if (mapCount > numeric_limits::max()) throw AlgorithmException("result has too many ROIs"); myXML.resetDirectionToScalars(1 - myDir, mapCount); myCiftiOut->setCiftiXML(myXML); if (myDir == CiftiXMLOld::ALONG_ROW) { int64_t curRow = 0; for (int whichStruct = 0; whichStruct < (int)surfaceList.size(); ++whichStruct) { vector scratchrow(myXML.getNumberOfColumns(), 0.0f);//so that it is initially zeroed for each structure vector myMap; myXML.getSurfaceMap(myDir, myMap, surfaceList[whichStruct]); for (int i = 0; i < surfROIs[whichStruct]->getNumberOfColumns(); ++i) { const float* data = surfROIs[whichStruct]->getValuePointerForColumn(i); for (int j = 0; j < (int)myMap.size(); ++j) { scratchrow[myMap[j].m_ciftiIndex] = data[myMap[j].m_surfaceNode]; } myCiftiOut->setRow(scratchrow.data(), curRow); ++curRow; } } if (mergedVolume) { if (myXML.hasVolumeData(myDir)) { vector scratchrow(myXML.getNumberOfColumns(), 0.0f); vector myMap; myXML.getVolumeMap(myDir, myMap); vector tempDims; volROIs[0]->getDimensions(tempDims); for (int64_t b = 0; b < tempDims[3]; ++b) { for (int64_t j = 0; j < (int64_t)myMap.size(); ++j) { int64_t myijk[3] = { myMap[j].m_ijk[0] - volOffsets[0], myMap[j].m_ijk[1] - volOffsets[1], myMap[j].m_ijk[2] - volOffsets[2] }; scratchrow[myMap[j].m_ciftiIndex] = volROIs[0]->getValue(myijk, b); } myCiftiOut->setRow(scratchrow.data(), curRow); ++curRow; } } } else { for (int whichStruct = 0; whichStruct < (int)volumeList.size(); ++whichStruct) { vector scratchrow(myXML.getNumberOfColumns(), 0.0f); vector myMap; myXML.getVolumeStructureMapForRows(myMap, volumeList[whichStruct]); vector tempDims; volROIs[whichStruct]->getDimensions(tempDims); for (int64_t b = 0; b < tempDims[3]; ++b) { for (int64_t j = 0; j < (int64_t)myMap.size(); ++j) { int64_t myijk[3] = { myMap[j].m_ijk[0] - volOffsets[whichStruct * 3], myMap[j].m_ijk[1] - volOffsets[whichStruct * 3 + 1], myMap[j].m_ijk[2] - volOffsets[whichStruct * 3 + 2] }; scratchrow[myMap[j].m_ciftiIndex] = volROIs[whichStruct]->getValue(myijk, b); } myCiftiOut->setRow(scratchrow.data(), curRow); ++curRow; } } } } else { vector surfaceStart, volumeStart; int64_t mystart = 0; for (int whichStruct = 0; whichStruct < (int)surfaceList.size(); ++whichStruct) { surfaceStart.push_back(mystart); mystart += surfROIs[whichStruct]->getNumberOfMaps(); } if (mergedVolume) { volumeStart.push_back(mystart); } else { for (int whichStruct = 0; whichStruct < (int)volumeList.size(); ++whichStruct) { volumeStart.push_back(mystart); vector tempDims; volROIs[whichStruct]->getDimensions(tempDims); mystart += tempDims[3]; } } int64_t curRow = 0; while (curRow < myXML.getNumberOfRows()) { bool found = false; for (int whichStruct = 0; whichStruct < (int)surfaceList.size(); ++whichStruct) { vector myMap; myXML.getSurfaceMap(myDir, myMap, surfaceList[whichStruct]); if (myMap.size() > 0 && myMap[0].m_ciftiIndex == curRow)//NOTE: cifti indexes in structure maps are always linear ascending { vector scratchrow(myXML.getNumberOfColumns(), 0.0f); for (int64_t j = 0; j < (int64_t)myMap.size(); ++j) { CaretAssert(curRow == myMap[j].m_ciftiIndex); for (int k = 0; k < surfROIs[whichStruct]->getNumberOfColumns(); ++k) { scratchrow[surfaceStart[whichStruct] + k] = surfROIs[whichStruct]->getValue(myMap[j].m_surfaceNode, k); } myCiftiOut->setRow(scratchrow.data(), curRow); ++curRow; } found = true; break; } } if (!found) { if (mergedVolume) { for (int whichStruct = 0; whichStruct < (int)volumeList.size(); ++whichStruct)//NOTE: merged map may not be in ascending order, or contiguous, so don't use it { vector myMap; myXML.getVolumeStructureMapForColumns(myMap, volumeList[whichStruct]); if (myMap.size() > 0 && myMap[0].m_ciftiIndex == curRow) { vector tempDims; volROIs[0]->getDimensions(tempDims); vector scratchrow(myXML.getNumberOfColumns(), 0.0f); for (int64_t j = 0; j < (int64_t)myMap.size(); ++j) { CaretAssert(curRow == myMap[j].m_ciftiIndex); int64_t myijk[3] = { myMap[j].m_ijk[0] - volOffsets[0], myMap[j].m_ijk[1] - volOffsets[1], myMap[j].m_ijk[2] - volOffsets[2] }; for (int64_t b = 0; b < tempDims[3]; ++b) { scratchrow[volumeStart[0] + b] = volROIs[0]->getValue(myijk, b); } myCiftiOut->setRow(scratchrow.data(), curRow); ++curRow; } found = true; break; } } } else { for (int whichStruct = 0; whichStruct < (int)volumeList.size(); ++whichStruct) { vector myMap; myXML.getVolumeStructureMapForColumns(myMap, volumeList[whichStruct]); vector tempDims; volROIs[whichStruct]->getDimensions(tempDims); if (myMap.size() > 0 && myMap[0].m_ciftiIndex == curRow) { vector scratchrow(myXML.getNumberOfColumns(), 0.0f); for (int64_t j = 0; j < (int64_t)myMap.size(); ++j) { CaretAssert(curRow == myMap[j].m_ciftiIndex); int64_t myijk[3] = { myMap[j].m_ijk[0] - volOffsets[whichStruct * 3], myMap[j].m_ijk[1] - volOffsets[whichStruct * 3 + 1], myMap[j].m_ijk[2] - volOffsets[whichStruct * 3 + 2] }; for (int64_t b = 0; b < tempDims[3]; ++b) { scratchrow[volumeStart[whichStruct] + b] = volROIs[whichStruct]->getValue(myijk, b); } myCiftiOut->setRow(scratchrow.data(), curRow); ++curRow; } found = true; break; } } } } CaretAssert(found); } } } float AlgorithmCiftiROIsFromExtrema::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmCiftiROIsFromExtrema::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmCiftiROIsFromExtrema.h000066400000000000000000000042151255417355300243750ustar00rootroot00000000000000#ifndef __ALGORITHM_CIFTI_ROIS_FROM_EXTREMA_H__ #define __ALGORITHM_CIFTI_ROIS_FROM_EXTREMA_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" #include "OverlapLogicEnum.h" namespace caret { class AlgorithmCiftiROIsFromExtrema : public AbstractAlgorithm { AlgorithmCiftiROIsFromExtrema(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmCiftiROIsFromExtrema(ProgressObject* myProgObj, const CiftiFile* myCifti, const float& surfLimit, const float& volLimit, const int& myDir, CiftiFile* myCiftiOut, const SurfaceFile* myLeftSurf = NULL, const SurfaceFile* myRightSurf = NULL, const SurfaceFile* myCerebSurf = NULL, const float& surfSigma = -1.0f, const float& volSigma = -1.0f, const OverlapLogicEnum::Enum& myLogic = OverlapLogicEnum::ALLOW, const bool& mergedVolume = false); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmCiftiROIsFromExtrema; } #endif //__ALGORITHM_CIFTI_ROIS_FROM_EXTREMA_H__ workbench-1.1.1/src/Algorithms/AlgorithmCiftiReduce.cxx000066400000000000000000000130121255417355300231640ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmCiftiReduce.h" #include "AlgorithmException.h" #include "CiftiFile.h" #include "ReductionOperation.h" #include using namespace caret; using namespace std; AString AlgorithmCiftiReduce::getCommandSwitch() { return "-cifti-reduce"; } AString AlgorithmCiftiReduce::getShortDescription() { return "PERFORM REDUCTION OPERATION ALONG CIFTI ROWS"; } OperationParameters* AlgorithmCiftiReduce::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiParameter(1, "cifti-in", "the cifti file to reduce"); ret->addStringParameter(2, "operation", "the reduction operator to use"); ret->addCiftiOutputParameter(3, "cifti-out", "the output cifti file"); OptionalParameter* excludeOpt = ret->createOptionalParameter(4, "-exclude-outliers", "exclude outliers from each row by standard deviation"); excludeOpt->addDoubleParameter(1, "sigma-below", "number of standard deviations below the mean to include"); excludeOpt->addDoubleParameter(2, "sigma-above", "number of standard deviations above the mean to include"); ret->setHelpText( AString("For each cifti row, takes the data along a row as a vector, and performs the specified reduction on it, putting the result ") + "into the single output column in that row. The reduction operators are as follows:\n\n" + ReductionOperation::getHelpInfo() ); return ret; } void AlgorithmCiftiReduce::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { CiftiFile* ciftiIn = myParams->getCifti(1); AString opString = myParams->getString(2); CiftiFile* ciftiOut = myParams->getOutputCifti(3); OptionalParameter* excludeOpt = myParams->getOptionalParameter(4); bool ok = false; ReductionEnum::Enum myReduce = ReductionEnum::fromName(opString, &ok); if (!ok) throw AlgorithmException("unrecognized operation string '" + opString + "'"); if (excludeOpt->m_present) { AlgorithmCiftiReduce(myProgObj, ciftiIn, myReduce, ciftiOut, excludeOpt->getDouble(1), excludeOpt->getDouble(2)); } else { AlgorithmCiftiReduce(myProgObj, ciftiIn, myReduce, ciftiOut); } } AlgorithmCiftiReduce::AlgorithmCiftiReduce(ProgressObject* myProgObj, const CiftiFile* ciftiIn, const ReductionEnum::Enum& myReduce, CiftiFile* ciftiOut) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); int64_t numRows = ciftiIn->getNumberOfRows(); int64_t numCols = ciftiIn->getNumberOfColumns(); if (numCols < 1 || numRows < 1) throw AlgorithmException("input must have at least 1 column and 1 row"); CiftiXML myOutXML = ciftiIn->getCiftiXML(); if (myOutXML.getNumberOfDimensions() != 2) { throw AlgorithmException("cifti reduce only supports 2D cifti"); } CiftiScalarsMap newMap; newMap.setLength(1); newMap.setMapName(0, ReductionEnum::toName(myReduce)); myOutXML.setMap(CiftiXML::ALONG_ROW, newMap); ciftiOut->setCiftiXML(myOutXML); vector scratchRow(numCols), outCol(numRows); for (int64_t i = 0; i < numRows; ++i) { ciftiIn->getRow(scratchRow.data(), i); outCol[i] = ReductionOperation::reduce(scratchRow.data(), numCols, myReduce); } ciftiOut->setColumn(outCol.data(), 0); } AlgorithmCiftiReduce::AlgorithmCiftiReduce(ProgressObject* myProgObj, const CiftiFile* ciftiIn, const ReductionEnum::Enum& myReduce, CiftiFile* ciftiOut, const float& sigmaBelow, const float& sigmaAbove) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); int64_t numRows = ciftiIn->getNumberOfRows(); int64_t numCols = ciftiIn->getNumberOfColumns(); if (numCols < 1 || numRows < 1) throw AlgorithmException("input must have at least 1 column and 1 row"); CiftiXML myOutXML = ciftiIn->getCiftiXML(); if (myOutXML.getNumberOfDimensions() != 2) { throw AlgorithmException("cifti reduce only supports 2D cifti"); } CiftiScalarsMap newMap; newMap.setLength(1); newMap.setMapName(0, ReductionEnum::toName(myReduce)); myOutXML.setMap(CiftiXML::ALONG_ROW, newMap); ciftiOut->setCiftiXML(myOutXML); vector scratchRow(numCols), outCol(numRows); for (int64_t i = 0; i < numRows; ++i) { ciftiIn->getRow(scratchRow.data(), i); outCol[i] = ReductionOperation::reduceExcludeDev(scratchRow.data(), numCols, myReduce, sigmaBelow, sigmaAbove); } ciftiOut->setColumn(outCol.data(), 0); } float AlgorithmCiftiReduce::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmCiftiReduce::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmCiftiReduce.h000066400000000000000000000035721255417355300226230ustar00rootroot00000000000000#ifndef __ALGORITHM_CIFTI_REDUCE_H__ #define __ALGORITHM_CIFTI_REDUCE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" #include "ReductionEnum.h" namespace caret { class AlgorithmCiftiReduce : public AbstractAlgorithm { AlgorithmCiftiReduce(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmCiftiReduce(ProgressObject* myProgObj, const CiftiFile* ciftiIn, const ReductionEnum::Enum& myReduce, CiftiFile* ciftiOut); AlgorithmCiftiReduce(ProgressObject* myProgObj, const CiftiFile* ciftiIn, const ReductionEnum::Enum& myReduce, CiftiFile* ciftiOut, const float& sigmaBelow, const float& sigmaAbove); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmCiftiReduce; } #endif //__ALGORITHM_CIFTI_REDUCE_H__ workbench-1.1.1/src/Algorithms/AlgorithmCiftiReorder.cxx000066400000000000000000000172611255417355300233710ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmCiftiReorder.h" #include "AlgorithmException.h" #include "CiftiFile.h" #include "FileInformation.h" #include "GiftiLabelTable.h" #include "PaletteColorMapping.h" #include using namespace caret; using namespace std; AString AlgorithmCiftiReorder::getCommandSwitch() { return "-cifti-reorder"; } AString AlgorithmCiftiReorder::getShortDescription() { return "REORDER THE PARCELS OR SCALAR/LABEL MAPS IN A CIFTI FILE"; } OperationParameters* AlgorithmCiftiReorder::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiParameter(1, "cifti-in", "input parcellated cifti file"); ret->addStringParameter(2, "direction", "which dimension to reorder along, ROW or COLUMN"); ret->addStringParameter(3, "reorder-list", "a text file containing the desired order transformation"); ret->addCiftiOutputParameter(4, "cifti-out", "the reordered cifti file"); ret->setHelpText( AString("The mapping along the specified direction must be parcels, scalars, or labels. ") + "For pscalar or ptseries, use COLUMN to reorder the parcels. " + "For dlabel, use ROW. " + "The file must contain 1-based indices separated by whitespace (spaces, newlines, tabs, etc), with as many indices as has along the specified dimension. " + "These indices specify which current index should end up in that position, for instance, if the current order is 'A B C D', and the desired order is 'D A B C', the text file " + "should contain '4 1 2 3'." ); return ret; } void AlgorithmCiftiReorder::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { CiftiFile* myCifti = myParams->getCifti(1); AString directionName = myParams->getString(2); int myDir; if (directionName == "ROW") { myDir = CiftiXMLOld::ALONG_ROW; } else if (directionName == "COLUMN") { myDir = CiftiXMLOld::ALONG_COLUMN; } else { throw AlgorithmException("incorrect string for direction, use ROW or COLUMN"); } AString listFileName = myParams->getString(3); CiftiFile* myCiftiOut = myParams->getOutputCifti(4); FileInformation textFileInfo(listFileName); if (!textFileInfo.exists()) { throw AlgorithmException("name list file doesn't exist"); } fstream listFile(listFileName.toLocal8Bit().constData(), fstream::in); if (!listFile.good()) { throw AlgorithmException("error reading name list file"); } vector reorder; int64_t temp; while (listFile >> temp) { reorder.push_back(temp - 1); } AlgorithmCiftiReorder(myProgObj, myCifti, myDir, reorder, myCiftiOut); } AlgorithmCiftiReorder::AlgorithmCiftiReorder(ProgressObject* myProgObj, const CiftiFile* myCifti, const int& myDir, const vector& reorder, CiftiFile* myCiftiOut) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); const CiftiXMLOld& myXML = myCifti->getCiftiXMLOld(); CiftiXMLOld myOutXML = myXML; int64_t rowSize = myXML.getNumberOfColumns(), colSize = myXML.getNumberOfRows(), myDirSize; if (myDir == CiftiXMLOld::ALONG_ROW) { myDirSize = rowSize; } else if (myDir == CiftiXMLOld::ALONG_COLUMN) { myDirSize = colSize; } else { throw AlgorithmException("direction not supported by cifti reorder"); } if ((int64_t)reorder.size() != myDirSize) throw AlgorithmException("reorder list has incorrect size, expected " + AString::number(myDirSize) + ", got " + AString::number(reorder.size())); vector used(myDirSize, false); for (int64_t i = 0; i < myDirSize; ++i) { int64_t val = reorder[i]; if (val < 0 || val >= myDirSize) throw AlgorithmException("invalid index in reorder list"); if (used[val]) throw AlgorithmException("index specified more than once in reorder list"); used[val] = true; } IndicesMapToDataType myType = myXML.getMappingType(myDir); switch (myType) { case CIFTI_INDEX_TYPE_SCALARS: { for (int64_t i = 0; i < myDirSize; ++i) { myOutXML.setMapNameForIndex(myDir, i, myXML.getMapName(myDir, reorder[i])); *(myOutXML.getMapPalette(myDir, i)) = *(myXML.getMapPalette(myDir, reorder[i])); *(myOutXML.getMapMetadata(myDir, i)) = *(myXML.getMapMetadata(myDir, reorder[i])); } break; } case CIFTI_INDEX_TYPE_LABELS: { for (int64_t i = 0; i < myDirSize; ++i) { myOutXML.setMapNameForIndex(myDir, i, myXML.getMapName(myDir, reorder[i])); *(myOutXML.getMapLabelTable(myDir, i)) = *(myXML.getMapLabelTable(myDir, reorder[i])); *(myOutXML.getMapMetadata(myDir, i)) = *(myXML.getMapMetadata(myDir, reorder[i])); } break; } case CIFTI_INDEX_TYPE_PARCELS: { myOutXML.resetDirectionToParcels(myDir); vector mySurfs; myXML.getParcelSurfaceStructures(myDir, mySurfs); for (int i = 0; i < (int)mySurfs.size(); ++i) { myOutXML.addParcelSurface(myDir, myXML.getSurfaceNumberOfNodes(myDir, mySurfs[i]), mySurfs[i]); } vector origParcels; myXML.getParcels(myDir, origParcels); for (int64_t i = 0; i < myDirSize; ++i) { myOutXML.addParcel(myDir, origParcels[reorder[i]]); } break; } default: throw AlgorithmException("mapping along specified direction must be scalars, labels, or parcels"); } myCiftiOut->setCiftiXML(myOutXML); vector scratchrow(rowSize); switch (myDir) { case CiftiXMLOld::ALONG_ROW: { vector scratchrow2(rowSize); for (int64_t i = 0; i < colSize; ++i) { myCifti->getRow(scratchrow.data(), i); for (int64_t j = 0; j < rowSize; ++j) { scratchrow2[j] = scratchrow[reorder[j]]; } myCiftiOut->setRow(scratchrow2.data(), i); } break; } case CiftiXMLOld::ALONG_COLUMN: { for (int64_t i = 0; i < colSize; ++i) { myCifti->getRow(scratchrow.data(), reorder[i]); myCiftiOut->setRow(scratchrow.data(), i); } break; } default: CaretAssert(false); } } float AlgorithmCiftiReorder::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmCiftiReorder::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmCiftiReorder.h000066400000000000000000000033171255417355300230130ustar00rootroot00000000000000#ifndef __ALGORITHM_CIFTI_REORDER_H__ #define __ALGORITHM_CIFTI_REORDER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" #include namespace caret { class AlgorithmCiftiReorder : public AbstractAlgorithm { AlgorithmCiftiReorder(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmCiftiReorder(ProgressObject* myProgObj, const CiftiFile* myCifti, const int& myDir, const std::vector& reorder, CiftiFile* myCiftiOut); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmCiftiReorder; } #endif //__ALGORITHM_CIFTI_REORDER_H__ workbench-1.1.1/src/Algorithms/AlgorithmCiftiReplaceStructure.cxx000066400000000000000000001066221255417355300252630ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmCiftiReplaceStructure.h" //for computing the cropped volume space #include "AlgorithmCiftiSeparate.h" #include "AlgorithmException.h" #include "CaretPointer.h" #include "CiftiFile.h" #include "GiftiLabelTable.h" #include "LabelFile.h" #include "MetricFile.h" #include "Vector3D.h" #include "VolumeFile.h" #include #include using namespace caret; using namespace std; AString AlgorithmCiftiReplaceStructure::getCommandSwitch() { return "-cifti-replace-structure"; } AString AlgorithmCiftiReplaceStructure::getShortDescription() { return "REPLACE DATA IN A STRUCTURE IN A CIFTI FILE"; } OperationParameters* AlgorithmCiftiReplaceStructure::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addStringParameter(1, "cifti", "the cifti to modify");//see useParameters for why this is a string ret->addStringParameter(2, "direction", "which dimension to interpret as a single map, ROW or COLUMN"); ParameterComponent* labelOpt = ret->createRepeatableParameter(3, "-label", "replace the data in a surface label component"); labelOpt->addStringParameter(1, "structure", "the structure to replace the data of"); labelOpt->addLabelParameter(2, "label", "the input label file"); ParameterComponent* metricOpt = ret->createRepeatableParameter(4, "-metric", "replace the data in a surface component"); metricOpt->addStringParameter(1, "structure", "the structure to replace the data of"); metricOpt->addMetricParameter(2, "metric", "the input metric"); ParameterComponent* volumeOpt = ret->createRepeatableParameter(5, "-volume", "replace the data in a volume component"); volumeOpt->addStringParameter(1, "structure", "the structure to replace the data of"); volumeOpt->addVolumeParameter(2, "volume", "the input volume"); volumeOpt->createOptionalParameter(3, "-from-cropped", "the input is cropped to the size of the component"); OptionalParameter* volumeAllOpt = ret->createOptionalParameter(6, "-volume-all", "replace the data in all volume components"); volumeAllOpt->addVolumeParameter(1, "volume", "the input volume"); volumeAllOpt->createOptionalParameter(2, "-from-cropped", "the input is cropped to the size of the data"); ret->createOptionalParameter(7, "-discard-unused-labels", "when operating on a dlabel file, drop any unused label keys from the label table"); AString helpText = AString("You must specify at least one of -metric, -label, -volume, or -volume-all for this command to do anything. ") + "Input volumes must line up with the output of -cifti-separate. " + "For dtseries/dscalar, use COLUMN, and if your matrix will be fully symmetric, COLUMN is more efficient. " + "The structure argument must be one of the following:\n"; vector myStructureEnums; StructureEnum::getAllEnums(myStructureEnums); for (int i = 0; i < (int)myStructureEnums.size(); ++i) { helpText += "\n" + StructureEnum::toName(myStructureEnums[i]); } ret->setHelpText(helpText); return ret; } void AlgorithmCiftiReplaceStructure::useParameters(OperationParameters* myParams, ProgressObject* /*myProgObj*/) { AString ciftiName = myParams->getString(1); CiftiFile myCifti(ciftiName);//CiftiFile currently doesn't expose the filename it is using, and we need to call writeFile manually (since the parsing framework doesn't expect an input to be modified) AString dirName = myParams->getString(2);//as for why it needs to modify an input, that is the only way it is useful for things like -cifti-smoothing int myDir; if (dirName == "ROW") { myDir = CiftiXML::ALONG_ROW; } else if (dirName == "COLUMN") { myDir = CiftiXML::ALONG_COLUMN; } else { throw AlgorithmException("incorrect string for direction, use ROW or COLUMN"); } bool discardUnusedLabels = myParams->getOptionalParameter(7)->m_present; const vector& labelInst = *(myParams->getRepeatableParameterInstances(3)); for (int i = 0; i < (int)labelInst.size(); ++i) { AString structName = labelInst[i]->getString(1); bool ok = false; StructureEnum::Enum myStruct = StructureEnum::fromName(structName, &ok); if (!ok) { throw AlgorithmException("unrecognized structure name"); } LabelFile* labelIn = labelInst[i]->getLabel(2); AlgorithmCiftiReplaceStructure(NULL, &myCifti, myDir, myStruct, labelIn, discardUnusedLabels); } const vector& metricInst = *(myParams->getRepeatableParameterInstances(4)); for (int i = 0; i < (int)metricInst.size(); ++i) { AString structName = metricInst[i]->getString(1); bool ok = false; StructureEnum::Enum myStruct = StructureEnum::fromName(structName, &ok); if (!ok) { throw AlgorithmException("unrecognized structure name"); } MetricFile* metricIn = metricInst[i]->getMetric(2); AlgorithmCiftiReplaceStructure(NULL, &myCifti, myDir, myStruct, metricIn); } const vector& volumeInst = *(myParams->getRepeatableParameterInstances(5)); for (int i = 0; i < (int)volumeInst.size(); ++i) { AString structName = volumeInst[i]->getString(1); bool ok = false; StructureEnum::Enum myStruct = StructureEnum::fromName(structName, &ok); if (!ok) { throw AlgorithmException("unrecognized structure name"); } VolumeFile* volIn = volumeInst[i]->getVolume(2); bool fromCropVol = volumeInst[i]->getOptionalParameter(3)->m_present; AlgorithmCiftiReplaceStructure(NULL, &myCifti, myDir, myStruct, volIn, fromCropVol, discardUnusedLabels); } OptionalParameter* volumeAllOpt = myParams->getOptionalParameter(6); if (volumeAllOpt->m_present) { VolumeFile* volIn = volumeAllOpt->getVolume(1); bool fromCropVol = volumeAllOpt->getOptionalParameter(2)->m_present; AlgorithmCiftiReplaceStructure(NULL, &myCifti, myDir, volIn, fromCropVol, discardUnusedLabels); } myCifti.writeFile(ciftiName);//and write the modified file } AlgorithmCiftiReplaceStructure::AlgorithmCiftiReplaceStructure(ProgressObject* myProgObj, CiftiFile* ciftiInOut, const int& myDir, const StructureEnum::Enum& myStruct, const MetricFile* metricIn) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); const CiftiXML& myXML = ciftiInOut->getCiftiXML(); if (myXML.getNumberOfDimensions() != 2) throw AlgorithmException("replace structure only supported on 2D cifti"); if (myXML.getMappingType(myDir) != CiftiMappingType::BRAIN_MODELS) throw AlgorithmException("specified dimension must contain brain models"); const CiftiBrainModelsMap& myDenseMap = myXML.getBrainModelsMap(myDir); vector myMap; int rowSize = ciftiInOut->getNumberOfColumns(), colSize = ciftiInOut->getNumberOfRows(); if (myDir == CiftiXML::ALONG_COLUMN) { myMap = myDenseMap.getSurfaceMap(myStruct); int64_t numNodes = myDenseMap.getSurfaceNumberOfNodes(myStruct); if (metricIn->getNumberOfNodes() != numNodes) { throw AlgorithmException("input metric has the wrong number of vertices"); } if (metricIn->getNumberOfColumns() != rowSize) { throw AlgorithmException("input metric has the wrong number of columns"); } int mapSize = (int)myMap.size(); CaretArray rowScratch(rowSize); for (int i = 0; i < mapSize; ++i) { for (int j = 0; j < rowSize; ++j) { rowScratch[j] = metricIn->getValue(myMap[i].m_surfaceNode, j); } ciftiInOut->setRow(rowScratch, myMap[i].m_ciftiIndex); } } else { if (myDir != CiftiXML::ALONG_ROW) throw AlgorithmException("unsupported cifti direction"); myMap = myDenseMap.getSurfaceMap(myStruct); int64_t numNodes = myDenseMap.getSurfaceNumberOfNodes(myStruct); if (metricIn->getNumberOfNodes() != numNodes) { throw AlgorithmException("input metric has the wrong number of vertices"); } if (metricIn->getNumberOfColumns() != colSize) { throw AlgorithmException("input metric has the wrong number of columns"); } int mapSize = (int)myMap.size(); CaretArray rowScratch(rowSize); for (int i = 0; i < colSize; ++i) { ciftiInOut->getRow(rowScratch, i, true); for (int j = 0; j < mapSize; ++j) { rowScratch[myMap[j].m_ciftiIndex] = metricIn->getValue(myMap[j].m_surfaceNode, i); } ciftiInOut->setRow(rowScratch, i); } } } AlgorithmCiftiReplaceStructure::AlgorithmCiftiReplaceStructure(ProgressObject* myProgObj, CiftiFile* ciftiInOut, const int& myDir, const StructureEnum::Enum& myStruct, const LabelFile* labelIn, const bool& discardUnusedLabels) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); const CiftiXML& myXML = ciftiInOut->getCiftiXML(); if (myXML.getNumberOfDimensions() != 2) throw AlgorithmException("replace structure only supported on 2D cifti"); if (myXML.getMappingType(myDir) != CiftiMappingType::BRAIN_MODELS) throw AlgorithmException("specified dimension must contain brain models"); const CiftiBrainModelsMap& myDenseMap = myXML.getBrainModelsMap(myDir); ciftiInOut->convertToInMemory();//so that writing it can change the header vector myMap; int64_t rowSize = ciftiInOut->getNumberOfColumns(), colSize = ciftiInOut->getNumberOfRows(); if (myDir == CiftiXML::ALONG_COLUMN) { if (myXML.getMappingType(CiftiXML::ALONG_ROW) != CiftiMappingType::LABELS) throw AlgorithmException("label separate requested on non-label cifti"); const CiftiLabelsMap& myLabelsMap = myXML.getLabelsMap(CiftiXML::ALONG_ROW); myMap = myDenseMap.getSurfaceMap(myStruct); int64_t numNodes = myDenseMap.getSurfaceNumberOfNodes(myStruct); if (labelIn->getNumberOfNodes() != numNodes) { throw AlgorithmException("input label file has the wrong number of vertices"); } if (labelIn->getNumberOfColumns() != rowSize) { throw AlgorithmException("input label file has the wrong number of columns"); } int64_t mapSize = (int64_t)myMap.size(); CaretArray rowScratch(rowSize, 0.0f);//initialize to the default "unused" value in case we get short reads due to unallocated on-disk cifti vector > usedArray(rowSize); vector > remapArray(rowSize); for (int64_t j = 0; j < rowSize; ++j) { GiftiLabelTable myTable = *(labelIn->getLabelTable());//we remap the old label table values so that the new label table keys are unmolested remapArray[j] = myTable.append(*(myLabelsMap.getMapLabelTable(j))); *(myLabelsMap.getMapLabelTable(j)) = myTable; } set writeRows; for (int64_t i = 0; i < mapSize; ++i) { writeRows.insert(myMap[i].m_ciftiIndex); } for (int64_t i = 0; i < colSize; ++i)//we have to remap all old data since we are changing he keys of the old label table { if (writeRows.find(i) == writeRows.end()) { ciftiInOut->getRow(rowScratch, i, true); bool rowChanged = false; for (int64_t j = 0; j < rowSize; ++j) { int32_t tempKey = (int32_t)floor(rowScratch[j] + 0.5f); map::iterator iter = remapArray[j].find(tempKey); if (iter != remapArray[j].end()) { if (iter->first != iter->second) { rowChanged = true; rowScratch[j] = iter->second;//remap the key if its value changes usedArray[j].insert(iter->second); } else { usedArray[j].insert(tempKey); } } else { rowChanged = true; int32_t unusedLabel = myLabelsMap.getMapLabelTable(j)->getUnassignedLabelKey(); rowScratch[j] = unusedLabel; usedArray[j].insert(unusedLabel); } } if (rowChanged) ciftiInOut->setRow(rowScratch, i); } } for (int64_t i = 0; i < mapSize; ++i)//set the new rows { for (int64_t j = 0; j < rowSize; ++j) { int32_t tempKey = labelIn->getLabelKey(myMap[i].m_surfaceNode, j); usedArray[j].insert(tempKey); rowScratch[j] = tempKey; } ciftiInOut->setRow(rowScratch, myMap[i].m_ciftiIndex); } if (discardUnusedLabels) { for (int64_t i = 0; i < rowSize; ++i)//delete unused labels { myLabelsMap.getMapLabelTable(i)->deleteUnusedLabels(usedArray[i]); } } } else { if (myDir != CiftiXML::ALONG_ROW) throw AlgorithmException("unsupported cifti direction"); if (myXML.getMappingType(CiftiXML::ALONG_COLUMN) != CiftiMappingType::LABELS) throw AlgorithmException("label separate requested on non-label cifti"); const CiftiLabelsMap& myLabelsMap = myXML.getLabelsMap(CiftiXML::ALONG_COLUMN); myMap = myDenseMap.getSurfaceMap(myStruct); { throw AlgorithmException("structure not found in specified dimension"); } int64_t numNodes = myDenseMap.getSurfaceNumberOfNodes(myStruct); if (labelIn->getNumberOfNodes() != numNodes) { throw AlgorithmException("input label file has the wrong number of vertices"); } if (labelIn->getNumberOfColumns() != colSize) { throw AlgorithmException("input label file has the wrong number of columns"); } int64_t mapSize = (int64_t)myMap.size(); CaretArray rowScratch(rowSize, 0.0f); set writeCols; for (int64_t i = 0; i < mapSize; ++i) { writeCols.insert(myMap[i].m_ciftiIndex); } for (int64_t i = 0; i < colSize; ++i) { GiftiLabelTable myTable = *(labelIn->getLabelTable());//we remap the old label table values so that the new label table keys are unmolested map remap = myTable.append(*(myLabelsMap.getMapLabelTable(i))); *(myLabelsMap.getMapLabelTable(i)) = myTable; ciftiInOut->getRow(rowScratch, i, true); set used; int32_t unusedKey = myTable.getUnassignedLabelKey(); for(int64_t j = 0; j < rowSize; ++j) { if (writeCols.find(j) == writeCols.end()) { int32_t tempKey = (int32_t)floor(rowScratch[j] + 0.5f); map::iterator iter = remap.find(tempKey); if (iter != remap.end()) { rowScratch[j] = iter->second; used.insert(iter->second); } else { rowScratch[j] = unusedKey; used.insert(unusedKey); } } } for (int64_t j = 0; j < mapSize; ++j) { int32_t tempKey = labelIn->getLabelKey(myMap[j].m_surfaceNode, i); rowScratch[myMap[j].m_ciftiIndex] = tempKey; used.insert(tempKey); } if (discardUnusedLabels) { myLabelsMap.getMapLabelTable(i)->deleteUnusedLabels(used); } ciftiInOut->setRow(rowScratch, i); } } } AlgorithmCiftiReplaceStructure::AlgorithmCiftiReplaceStructure(ProgressObject* myProgObj, CiftiFile* ciftiInOut, const int& myDir, const StructureEnum::Enum& myStruct, const VolumeFile* volIn, const bool& fromCropped, const bool& discardUnusedLabels) : AbstractAlgorithm(myProgObj) { const CiftiXML& myXML = ciftiInOut->getCiftiXML(); if (myDir != CiftiXML::ALONG_ROW && myDir != CiftiXML::ALONG_COLUMN) throw AlgorithmException("direction not supported in cifti replace structure"); if (myXML.getNumberOfDimensions() != 2) throw AlgorithmException("replace structure only supported on 2D cifti"); LevelProgress myProgress(myProgObj); int64_t rowSize = ciftiInOut->getNumberOfColumns(), colSize = ciftiInOut->getNumberOfRows(); if (myXML.getMappingType(myDir) != CiftiMappingType::BRAIN_MODELS) throw AlgorithmException("specified direction does not contain brain models"); const CiftiBrainModelsMap& myBrainMap = myXML.getBrainModelsMap(myDir); const int64_t* myDims = myBrainMap.getVolumeSpace().getDims(); vector > mySform = myBrainMap.getVolumeSpace().getSform(); vector myMap = myBrainMap.getVolumeStructureMap(myStruct); int64_t numVoxels = (int64_t)myMap.size(); int64_t offset[3]; vector newdims; if (fromCropped) { newdims.resize(3); AlgorithmCiftiSeparate::getCroppedVolSpace(ciftiInOut, myDir, myStruct, newdims.data(), mySform, offset); } else { newdims.push_back(myDims[0]); newdims.push_back(myDims[1]); newdims.push_back(myDims[2]); offset[0] = 0; offset[1] = 0; offset[2] = 0; } if (!volIn->matchesVolumeSpace(newdims.data(), mySform)) { throw AlgorithmException("input volume doesn't match volume space and dimensions in CIFTI"); } CaretArray rowScratch(rowSize, 0.0f);//initialize with default unused label in case dlabel and short reads due to uninitialized on-disk cifti vector volDims; volIn->getDimensions(volDims); if (myDir == CiftiXML::ALONG_COLUMN) { if (volDims[3] != rowSize) { throw AlgorithmException("volume has the wrong number of subvolumes"); } if (myXML.getMappingType(CiftiXML::ALONG_ROW) == CiftiMappingType::LABELS) { if (volIn->getType() != SubvolumeAttributes::LABEL) throw AlgorithmException("replace structure called on cifti label file with non-label volume"); ciftiInOut->convertToInMemory();//so that writing can change the XML const CiftiLabelsMap& myLabelsMap = myXML.getLabelsMap(CiftiXML::ALONG_ROW); vector > remapArray(rowSize); vector > usedArray(rowSize); for (int64_t i = 0; i < rowSize; ++i) { GiftiLabelTable myTable = *(volIn->getMapLabelTable(i));//we remap the old label table values so that the new label table keys are unmolested remapArray[i] = myTable.append(*(myLabelsMap.getMapLabelTable(i))); *(myLabelsMap.getMapLabelTable(i)) = myTable; } set writeRows; for (int64_t i = 0; i < numVoxels; ++i) { writeRows.insert(myMap[i].m_ciftiIndex); } for (int64_t i = 0; i < colSize; ++i) { if (writeRows.find(i) == writeRows.end()) { ciftiInOut->getRow(rowScratch, i, true); bool rowChanged = false; for (int64_t j = 0; j < rowSize; ++j) { int32_t tempKey = (int32_t)floor(rowScratch[j] + 0.5f); map::iterator iter = remapArray[j].find(tempKey); if (iter != remapArray[j].end()) { if (iter->first != iter->second) { rowChanged = true; rowScratch[j] = iter->second;//remap the key if its value changes usedArray[j].insert(iter->second); } else { usedArray[j].insert(tempKey); } } else { rowChanged = true; int32_t unusedLabel = myLabelsMap.getMapLabelTable(j)->getUnassignedLabelKey(); rowScratch[j] = unusedLabel; usedArray[j].insert(unusedLabel); } } if (rowChanged) ciftiInOut->setRow(rowScratch, i); } } for (int64_t i = 0; i < numVoxels; ++i) { int64_t thisvoxel[3] = { myMap[i].m_ijk[0] - offset[0], myMap[i].m_ijk[1] - offset[1], myMap[i].m_ijk[2] - offset[2] }; for (int j = 0; j < rowSize; ++j) { int32_t tempKey = (int32_t)floor(volIn->getValue(thisvoxel, j) + 0.5f); usedArray[j].insert(tempKey); rowScratch[j] = tempKey; } ciftiInOut->setRow(rowScratch, myMap[i].m_ciftiIndex); } if (discardUnusedLabels) { for (int64_t i = 0; i < rowSize; ++i)//delete unused labels { myLabelsMap.getMapLabelTable(i)->deleteUnusedLabels(usedArray[i]); } } } else { for (int64_t i = 0; i < numVoxels; ++i) { int64_t thisvoxel[3] = { myMap[i].m_ijk[0] - offset[0], myMap[i].m_ijk[1] - offset[1], myMap[i].m_ijk[2] - offset[2] }; for (int j = 0; j < rowSize; ++j) { rowScratch[j] = volIn->getValue(thisvoxel, j); } ciftiInOut->setRow(rowScratch, myMap[i].m_ciftiIndex); } } } else { if (volDims[3] != colSize) { throw AlgorithmException("volume has the wrong number of subvolumes"); } if (myXML.getMappingType(CiftiXML::ALONG_COLUMN) == CiftiMappingType::LABELS) { if (volIn->getType() != SubvolumeAttributes::LABEL) throw AlgorithmException("replace structure called on cifti label file with non-label volume"); ciftiInOut->convertToInMemory(); const CiftiLabelsMap& myLabelsMap = myXML.getLabelsMap(CiftiXML::ALONG_COLUMN); set writeCols; for (int64_t i = 0; i < numVoxels; ++i) { writeCols.insert(myMap[i].m_ciftiIndex); } for (int64_t i = 0; i < colSize; ++i) { GiftiLabelTable myTable = *(volIn->getMapLabelTable(i));//we remap the old label table values so that the new label table keys are unmolested map remap = myTable.append(*(myLabelsMap.getMapLabelTable(i))); *(myLabelsMap.getMapLabelTable(i)) = myTable; ciftiInOut->getRow(rowScratch, i, true); set used; int32_t unusedKey = myTable.getUnassignedLabelKey(); for(int64_t j = 0; j < rowSize; ++j) { if (writeCols.find(j) == writeCols.end()) { int32_t tempKey = (int32_t)floor(rowScratch[j] + 0.5f); map::iterator iter = remap.find(tempKey); if (iter != remap.end()) { rowScratch[j] = iter->second; used.insert(iter->second); } else { rowScratch[j] = unusedKey; used.insert(unusedKey); } } } for (int64_t j = 0; j < numVoxels; ++j) { int64_t thisvoxel[3] = { myMap[j].m_ijk[0] - offset[0], myMap[j].m_ijk[1] - offset[1], myMap[j].m_ijk[2] - offset[2] }; int32_t tempKey = (int32_t)floor(volIn->getValue(thisvoxel, i) + 0.5f); rowScratch[myMap[j].m_ciftiIndex] = tempKey; used.insert(tempKey); } if (discardUnusedLabels) { myLabelsMap.getMapLabelTable(i)->deleteUnusedLabels(used); } ciftiInOut->setRow(rowScratch, i); } } else { for (int64_t i = 0; i < colSize; ++i) { ciftiInOut->getRow(rowScratch, i, true);//the on-disk cifti file may not have been allocated yet, so short reads are okay for (int64_t j = 0; j < numVoxels; ++j) { int64_t thisvoxel[3] = { myMap[j].m_ijk[0] - offset[0], myMap[j].m_ijk[1] - offset[1], myMap[j].m_ijk[2] - offset[2] }; rowScratch[myMap[j].m_ciftiIndex] = volIn->getValue(thisvoxel, i); } ciftiInOut->setRow(rowScratch, i); } } } } AlgorithmCiftiReplaceStructure::AlgorithmCiftiReplaceStructure(ProgressObject* myProgObj, CiftiFile* ciftiInOut, const int& myDir, const VolumeFile* volIn, const bool& fromCropped, const bool& discardUnusedLabels): AbstractAlgorithm(myProgObj) { const CiftiXML& myXML = ciftiInOut->getCiftiXML(); if (myXML.getNumberOfDimensions() != 2) throw AlgorithmException("replace structure only supported on 2D cifti"); LevelProgress myProgress(myProgObj); if (myDir != CiftiXML::ALONG_ROW && myDir != CiftiXML::ALONG_COLUMN) throw AlgorithmException("direction not supported in cifti replace structure"); if (myXML.getMappingType(myDir) != CiftiMappingType::BRAIN_MODELS) throw AlgorithmException("specified direction does not contain brain models"); const CiftiBrainModelsMap& myBrainMap = myXML.getBrainModelsMap(myDir); const int64_t* myDims = myBrainMap.getVolumeSpace().getDims(); vector > mySform = myBrainMap.getVolumeSpace().getSform(); vector myMap = myBrainMap.getFullVolumeMap(); int64_t numVoxels = (int64_t)myMap.size(); int64_t rowSize = ciftiInOut->getNumberOfColumns(), colSize = ciftiInOut->getNumberOfRows(); vector newdims; int64_t offset[3]; if (fromCropped) { newdims.resize(3); AlgorithmCiftiSeparate::getCroppedVolSpaceAll(ciftiInOut, myDir, newdims.data(), mySform, offset); } else { newdims.push_back(myDims[0]); newdims.push_back(myDims[1]); newdims.push_back(myDims[2]); offset[0] = 0; offset[1] = 0; offset[2] = 0; } if (!volIn->matchesVolumeSpace(newdims.data(), mySform)) { throw AlgorithmException("input volume doesn't match volume space and dimensions in CIFTI"); } CaretArray rowScratch(rowSize, 0.0f);//initialize with default unused label in case dlabel and short reads due to uninitialized on-disk cifti vector volDims; volIn->getDimensions(volDims); if (myDir == CiftiXML::ALONG_COLUMN) { if (volDims[3] != rowSize) { throw AlgorithmException("volume has the wrong number of subvolumes"); } if (myXML.getMappingType(CiftiXML::ALONG_ROW) == CiftiMappingType::LABELS) { const CiftiLabelsMap& myLabelsMap = myXML.getLabelsMap(CiftiXML::ALONG_ROW); if (volIn->getType() != SubvolumeAttributes::LABEL) throw AlgorithmException("replace structure called on cifti label file with non-label volume"); ciftiInOut->convertToInMemory(); vector > remapArray(rowSize); vector > usedArray(rowSize); for (int64_t i = 0; i < rowSize; ++i) { GiftiLabelTable myTable = *(volIn->getMapLabelTable(i));//we remap the old label table values so that the new label table keys are unmolested remapArray[i] = myTable.append(*(myLabelsMap.getMapLabelTable(i))); *(myLabelsMap.getMapLabelTable(i)) = myTable; } set writeRows; for (int64_t i = 0; i < numVoxels; ++i) { writeRows.insert(myMap[i].m_ciftiIndex); } for (int64_t i = 0; i < colSize; ++i) { if (writeRows.find(i) == writeRows.end()) { ciftiInOut->getRow(rowScratch, i, true); bool rowChanged = false; for (int64_t j = 0; j < rowSize; ++j) { int32_t tempKey = (int32_t)floor(rowScratch[j] + 0.5f); map::iterator iter = remapArray[j].find(tempKey); if (iter != remapArray[j].end()) { if (iter->first != iter->second) { rowChanged = true; rowScratch[j] = iter->second;//remap the key if its value changes usedArray[j].insert(iter->second); } else { usedArray[j].insert(tempKey); } } else { rowChanged = true; int32_t unusedLabel = myLabelsMap.getMapLabelTable(j)->getUnassignedLabelKey(); rowScratch[j] = unusedLabel; usedArray[j].insert(unusedLabel); } } if (rowChanged) ciftiInOut->setRow(rowScratch, i); } } for (int64_t i = 0; i < numVoxels; ++i) { int64_t thisvoxel[3] = { myMap[i].m_ijk[0] - offset[0], myMap[i].m_ijk[1] - offset[1], myMap[i].m_ijk[2] - offset[2] }; for (int j = 0; j < rowSize; ++j) { int32_t tempKey = (int32_t)floor(volIn->getValue(thisvoxel, j) + 0.5f); usedArray[j].insert(tempKey); rowScratch[j] = tempKey; } ciftiInOut->setRow(rowScratch, myMap[i].m_ciftiIndex); } if (discardUnusedLabels) { for (int64_t i = 0; i < rowSize; ++i)//delete unused labels { myLabelsMap.getMapLabelTable(i)->deleteUnusedLabels(usedArray[i]); } } } else { for (int64_t i = 0; i < numVoxels; ++i) { int64_t thisvoxel[3] = { myMap[i].m_ijk[0] - offset[0], myMap[i].m_ijk[1] - offset[1], myMap[i].m_ijk[2] - offset[2] }; for (int j = 0; j < rowSize; ++j) { rowScratch[j] = volIn->getValue(thisvoxel, j); } ciftiInOut->setRow(rowScratch, myMap[i].m_ciftiIndex); } } } else { if (volDims[3] != colSize) { throw AlgorithmException("volume has the wrong number of subvolumes"); } if (myXML.getMappingType(CiftiXML::ALONG_COLUMN) == CiftiMappingType::LABELS) { const CiftiLabelsMap& myLabelsMap = myXML.getLabelsMap(CiftiXML::ALONG_COLUMN); if (volIn->getType() != SubvolumeAttributes::LABEL) throw AlgorithmException("replace structure called on cifti label file with non-label volume"); ciftiInOut->convertToInMemory(); set writeCols; for (int64_t i = 0; i < numVoxels; ++i) { writeCols.insert(myMap[i].m_ciftiIndex); } for (int64_t i = 0; i < colSize; ++i) { GiftiLabelTable myTable = *(volIn->getMapLabelTable(i));//we remap the old label table values so that the new label table keys are unmolested map remap = myTable.append(*(myLabelsMap.getMapLabelTable(i))); *(myLabelsMap.getMapLabelTable(i)) = myTable; ciftiInOut->getRow(rowScratch, i, true); set used; int32_t unusedKey = myTable.getUnassignedLabelKey(); for(int64_t j = 0; j < rowSize; ++j) { if (writeCols.find(j) == writeCols.end()) { int32_t tempKey = (int32_t)floor(rowScratch[j] + 0.5f); map::iterator iter = remap.find(tempKey); if (iter != remap.end()) { rowScratch[j] = iter->second; used.insert(iter->second); } else { rowScratch[j] = unusedKey; used.insert(unusedKey); } } } for (int64_t j = 0; j < numVoxels; ++j) { int64_t thisvoxel[3] = { myMap[j].m_ijk[0] - offset[0], myMap[j].m_ijk[1] - offset[1], myMap[j].m_ijk[2] - offset[2] }; int32_t tempKey = (int32_t)floor(volIn->getValue(thisvoxel, i) + 0.5f); rowScratch[myMap[j].m_ciftiIndex] = tempKey; used.insert(tempKey); } if (discardUnusedLabels) { myLabelsMap.getMapLabelTable(i)->deleteUnusedLabels(used); } ciftiInOut->setRow(rowScratch, i); } } else { for (int64_t i = 0; i < colSize; ++i) { ciftiInOut->getRow(rowScratch, i, true);//the on-disk cifti file may not have been allocated yet, so short reads are okay for (int64_t j = 0; j < numVoxels; ++j) { int64_t thisvoxel[3] = { myMap[j].m_ijk[0] - offset[0], myMap[j].m_ijk[1] - offset[1], myMap[j].m_ijk[2] - offset[2] }; rowScratch[myMap[j].m_ciftiIndex] = volIn->getValue(thisvoxel, i); } ciftiInOut->setRow(rowScratch, i); } } } } float AlgorithmCiftiReplaceStructure::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmCiftiReplaceStructure::getSubAlgorithmWeight() { return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmCiftiReplaceStructure.h000066400000000000000000000051071255417355300247040ustar00rootroot00000000000000#ifndef __ALGORITHM_CIFTI_REPLACE_STRUCTURE_H__ #define __ALGORITHM_CIFTI_REPLACE_STRUCTURE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" #include "StructureEnum.h" namespace caret { class AlgorithmCiftiReplaceStructure : public AbstractAlgorithm { AlgorithmCiftiReplaceStructure(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmCiftiReplaceStructure(ProgressObject* myProgObj, CiftiFile* ciftiInOut, const int& myDir, const StructureEnum::Enum& myStruct, const MetricFile* metricIn); AlgorithmCiftiReplaceStructure(ProgressObject* myProgObj, CiftiFile* ciftiInOut, const int& myDir, const StructureEnum::Enum& myStruct, const LabelFile* labelIn, const bool& discardUnusedLabels = false); AlgorithmCiftiReplaceStructure(ProgressObject* myProgObj, CiftiFile* ciftiInOut, const int& myDir, const StructureEnum::Enum& myStruct, const VolumeFile* volIn, const bool& fromCropped, const bool& discardUnusedLabels = false); AlgorithmCiftiReplaceStructure(ProgressObject* myProgObj, CiftiFile* ciftiInOut, const int& myDir, const VolumeFile* volIn, const bool& fromCropped, const bool& discardUnusedLabels = false); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmCiftiReplaceStructure; } #endif //__ALGORITHM_CIFTI_REPLACE_STRUCTURE_H__ workbench-1.1.1/src/Algorithms/AlgorithmCiftiResample.cxx000066400000000000000000001652101255417355300235350ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmCiftiResample.h" #include "AlgorithmException.h" #include "AffineFile.h" #include "AlgorithmCiftiSeparate.h" #include "AlgorithmCiftiReplaceStructure.h" #include "AlgorithmLabelDilate.h" #include "AlgorithmLabelResample.h" #include "AlgorithmMetricDilate.h" #include "AlgorithmMetricResample.h" #include "AlgorithmVolumeAffineResample.h" #include "AlgorithmVolumeDilate.h" #include "AlgorithmVolumeWarpfieldResample.h" #include "CiftiFile.h" #include "LabelFile.h" #include "MetricFile.h" #include "SurfaceFile.h" #include "SurfaceResamplingHelper.h" #include "VolumeFile.h" #include "VolumePaddingHelper.h" #include "WarpfieldFile.h" #include #include using namespace caret; using namespace std; AString AlgorithmCiftiResample::getCommandSwitch() { return "-cifti-resample"; } AString AlgorithmCiftiResample::getShortDescription() { return "RESAMPLE A CIFTI FILE TO A NEW CIFTI SPACE"; } OperationParameters* AlgorithmCiftiResample::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiParameter(1, "cifti-in", "the cifti file to resample"); ret->addStringParameter(2, "direction", "the direction of the input that should be resampled"); ret->addCiftiParameter(3, "cifti-template", "a cifti file containing the cifti space to resample to"); ret->addStringParameter(4, "template-direction", "the direction of the template to use as the resampling space"); ret->addStringParameter(5, "surface-method", "specify a surface resampling method"); ret->addStringParameter(6, "volume-method", "specify a volume interpolation method"); ret->addCiftiOutputParameter(7, "cifti-out", "the output cifti file"); ret->createOptionalParameter(8, "-surface-largest", "use largest weight instead of weighted average when doing surface resampling"); OptionalParameter* volDilateOpt = ret->createOptionalParameter(9, "-volume-predilate", "dilate the volume components before resampling"); volDilateOpt->addDoubleParameter(1, "dilate-mm", "distance, in mm, to dilate"); OptionalParameter* surfDilateOpt = ret->createOptionalParameter(10, "-surface-postdilate", "dilate the surface components after resampling"); surfDilateOpt->addDoubleParameter(1, "dilate-mm", "distance, in mm, to dilate"); OptionalParameter* affineOpt = ret->createOptionalParameter(11, "-affine", "use an affine transformation on the volume components"); affineOpt->addStringParameter(1, "affine-file", "the affine file to use"); OptionalParameter* flirtOpt = affineOpt->createOptionalParameter(2, "-flirt", "MUST be used if affine is a flirt affine"); flirtOpt->addStringParameter(1, "source-volume", "the source volume used when generating the affine"); flirtOpt->addStringParameter(2, "target-volume", "the target volume used when generating the affine"); OptionalParameter* warpfieldOpt = ret->createOptionalParameter(12, "-warpfield", "use a warpfield on the volume components"); warpfieldOpt->addStringParameter(1, "warpfield", "the warpfield to use"); OptionalParameter* fnirtOpt = warpfieldOpt->createOptionalParameter(2, "-fnirt", "MUST be used if using a fnirt warpfield"); fnirtOpt->addStringParameter(1, "source-volume", "the source volume used when generating the warpfield"); OptionalParameter* leftSpheresOpt = ret->createOptionalParameter(13, "-left-spheres", "specify spheres for left surface resampling"); leftSpheresOpt->addSurfaceParameter(1, "current-sphere", "a sphere with the same mesh as the current left surface"); leftSpheresOpt->addSurfaceParameter(2, "new-sphere", "a sphere with the new left mesh that is in register with the current sphere"); OptionalParameter* leftAreaSurfsOpt = leftSpheresOpt->createOptionalParameter(3, "-left-area-surfs", "specify left surfaces to do vertex area correction based on"); leftAreaSurfsOpt->addSurfaceParameter(1, "current-area", "a relevant left anatomical surface with current mesh"); leftAreaSurfsOpt->addSurfaceParameter(2, "new-area", "a relevant left anatomical surface with new mesh"); OptionalParameter* leftAreaMetricsOpt = leftSpheresOpt->createOptionalParameter(4, "-left-area-metrics", "specify left vertex area metrics to do area correction based on"); leftAreaMetricsOpt->addMetricParameter(1, "current-area", "a metric file with vertex areas for the current mesh"); leftAreaMetricsOpt->addMetricParameter(2, "new-area", "a metric file with vertex areas for the new mesh"); OptionalParameter* rightSpheresOpt = ret->createOptionalParameter(14, "-right-spheres", "specify spheres for right surface resampling"); rightSpheresOpt->addSurfaceParameter(1, "current-sphere", "a sphere with the same mesh as the current right surface"); rightSpheresOpt->addSurfaceParameter(2, "new-sphere", "a sphere with the new right mesh that is in register with the current sphere"); OptionalParameter* rightAreaSurfsOpt = rightSpheresOpt->createOptionalParameter(3, "-right-area-surfs", "specify right surfaces to do vertex area correction based on"); rightAreaSurfsOpt->addSurfaceParameter(1, "current-area", "a relevant right anatomical surface with current mesh"); rightAreaSurfsOpt->addSurfaceParameter(2, "new-area", "a relevant right anatomical surface with new mesh"); OptionalParameter* rightAreaMetricsOpt = rightSpheresOpt->createOptionalParameter(4, "-right-area-metrics", "specify right vertex area metrics to do area correction based on"); rightAreaMetricsOpt->addMetricParameter(1, "current-area", "a metric file with vertex areas for the current mesh"); rightAreaMetricsOpt->addMetricParameter(2, "new-area", "a metric file with vertex areas for the new mesh"); OptionalParameter* cerebSpheresOpt = ret->createOptionalParameter(15, "-cerebellum-spheres", "specify spheres for cerebellum surface resampling"); cerebSpheresOpt->addSurfaceParameter(1, "current-sphere", "a sphere with the same mesh as the current cerebellum surface"); cerebSpheresOpt->addSurfaceParameter(2, "new-sphere", "a sphere with the new cerebellum mesh that is in register with the current sphere"); OptionalParameter* cerebAreaSurfsOpt = cerebSpheresOpt->createOptionalParameter(3, "-cerebellum-area-surfs", "specify cerebellum surfaces to do vertex area correction based on"); cerebAreaSurfsOpt->addSurfaceParameter(1, "current-area", "a relevant cerebellum anatomical surface with current mesh"); cerebAreaSurfsOpt->addSurfaceParameter(2, "new-area", "a relevant cerebellum anatomical surface with new mesh"); OptionalParameter* cerebAreaMetricsOpt = cerebSpheresOpt->createOptionalParameter(4, "-cerebellum-area-metrics", "specify cerebellum vertex area metrics to do area correction based on"); cerebAreaMetricsOpt->addMetricParameter(1, "current-area", "a metric file with vertex areas for the current mesh"); cerebAreaMetricsOpt->addMetricParameter(2, "new-area", "a metric file with vertex areas for the new mesh"); AString myHelpText = AString("Resample cifti data to a different brainordinate space. Use COLUMN for the direction to resample dscalar, dlabel, or dtseries. ") + "Resampling both dimensions of a dconn requires running this command twice, once with COLUMN and once with ROW. " + "If you are resampling a dconn and your machine has a large amount of memory, you might consider using -cifti-resample-dconn-memory to avoid writing and rereading an intermediate file. " + "If spheres are not specified for a surface structure which exists in the cifti files, its data is copied without resampling or dilation. " + "Dilation is done with the 'nearest' method, and is done on for surface data. " + "Volume components are padded before dilation so that dilation doesn't run into the edge of the component bounding box.\n\n" + "The recommended resampling methods are ADAP_BARY_AREA and CUBIC (cubic spline), except for label data which should use ADAP_BARY_AREA and ENCLOSING_VOXEL.\n\n" + "The argument must be one of the following:\n\n" + "CUBIC\nENCLOSING_VOXEL\nTRILINEAR\n\n" + "The argument must be one of the following:\n\n"; vector allEnums; SurfaceResamplingMethodEnum::getAllEnums(allEnums); for (int i = 0; i < (int)allEnums.size(); ++i) { myHelpText += SurfaceResamplingMethodEnum::toName(allEnums[i]) + "\n"; } ret->setHelpText(myHelpText); return ret; } void AlgorithmCiftiResample::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { CiftiFile* myCiftiIn = myParams->getCifti(1); AString myDirString = myParams->getString(2); int direction = -1; if (myDirString == "ROW") { direction = CiftiXML::ALONG_ROW; } else { if (myDirString == "COLUMN") { direction = CiftiXML::ALONG_COLUMN; } else { throw AlgorithmException("unrecognized direction string, use ROW or COLUMN"); } } CiftiFile* myTemplate = myParams->getCifti(3); AString myTemplDirString = myParams->getString(4); int templateDir = -1; if (myTemplDirString == "ROW") { templateDir = CiftiXML::ALONG_ROW; } else { if (myTemplDirString == "COLUMN") { templateDir = CiftiXML::ALONG_COLUMN; } else { throw AlgorithmException("unrecognized template direction string, use ROW or COLUMN"); } } bool ok = false; SurfaceResamplingMethodEnum::Enum mySurfMethod = SurfaceResamplingMethodEnum::fromName(myParams->getString(5), &ok); if (!ok) { throw AlgorithmException("invalid surface resampling method name"); } AString myVolMethodString = myParams->getString(6); VolumeFile::InterpType myVolMethod = VolumeFile::CUBIC; if (myVolMethodString == "CUBIC") { myVolMethod = VolumeFile::CUBIC; } else if (myVolMethodString == "TRILINEAR") { myVolMethod = VolumeFile::TRILINEAR; } else if (myVolMethodString == "ENCLOSING_VOXEL") { myVolMethod = VolumeFile::ENCLOSING_VOXEL; } else { throw AlgorithmException("unrecognized volume interpolation method"); } CiftiFile* myCiftiOut = myParams->getOutputCifti(7); bool surfLargest = myParams->getOptionalParameter(8)->m_present; float voldilatemm = -1.0f, surfdilatemm = -1.0f; OptionalParameter* volDilateOpt = myParams->getOptionalParameter(9); if (volDilateOpt->m_present) { voldilatemm = (float)volDilateOpt->getDouble(1); if (voldilatemm <= 0.0f) throw AlgorithmException("dilation amount must be positive"); } OptionalParameter* surfDilateOpt = myParams->getOptionalParameter(10); if (surfDilateOpt->m_present) { surfdilatemm = (float)surfDilateOpt->getDouble(1); if (surfdilatemm <= 0.0f) throw AlgorithmException("dilation amount must be positive"); } OptionalParameter* affineOpt = myParams->getOptionalParameter(11); OptionalParameter* warpfieldOpt = myParams->getOptionalParameter(12); if (affineOpt->m_present && warpfieldOpt->m_present) throw AlgorithmException("you cannot specify both -affine and -warpfield"); AffineFile myAffine; WarpfieldFile myWarpfield; if (affineOpt->m_present) { OptionalParameter* flirtOpt = affineOpt->getOptionalParameter(2); if (flirtOpt->m_present) { myAffine.readFlirt(affineOpt->getString(1), flirtOpt->getString(1), flirtOpt->getString(2)); } else { myAffine.readWorld(affineOpt->getString(1)); } } if (warpfieldOpt->m_present) { OptionalParameter* fnirtOpt = warpfieldOpt->getOptionalParameter(2); if (fnirtOpt->m_present) { myWarpfield.readFnirt(warpfieldOpt->getString(1), fnirtOpt->getString(1)); } else { myWarpfield.readWorld(warpfieldOpt->getString(1)); } } SurfaceFile* curLeftSphere = NULL, *newLeftSphere = NULL; MetricFile* curLeftAreas = NULL, *newLeftAreas = NULL; MetricFile curLeftAreasTemp, newLeftAreasTemp; OptionalParameter* leftSpheresOpt = myParams->getOptionalParameter(13); if (leftSpheresOpt->m_present) { curLeftSphere = leftSpheresOpt->getSurface(1); newLeftSphere = leftSpheresOpt->getSurface(2); OptionalParameter* leftAreaSurfsOpt = leftSpheresOpt->getOptionalParameter(3); if (leftAreaSurfsOpt->m_present) { SurfaceFile* curAreaSurf = leftAreaSurfsOpt->getSurface(1); SurfaceFile* newAreaSurf = leftAreaSurfsOpt->getSurface(2); vector nodeAreasTemp; curAreaSurf->computeNodeAreas(nodeAreasTemp); curLeftAreasTemp.setNumberOfNodesAndColumns(curAreaSurf->getNumberOfNodes(), 1); curLeftAreasTemp.setValuesForColumn(0, nodeAreasTemp.data()); curLeftAreas = &curLeftAreasTemp; newAreaSurf->computeNodeAreas(nodeAreasTemp); newLeftAreasTemp.setNumberOfNodesAndColumns(newAreaSurf->getNumberOfNodes(), 1); newLeftAreasTemp.setValuesForColumn(0, nodeAreasTemp.data()); newLeftAreas = &newLeftAreasTemp; } OptionalParameter* leftAreaMetricsOpt = leftSpheresOpt->getOptionalParameter(4); if (leftAreaMetricsOpt->m_present) { if (leftAreaSurfsOpt->m_present) { throw AlgorithmException("only one of -left-area-surfs and -left-area-metrics can be specified"); } curLeftAreas = leftAreaMetricsOpt->getMetric(1); newLeftAreas = leftAreaMetricsOpt->getMetric(2); } } SurfaceFile* curRightSphere = NULL, *newRightSphere = NULL; MetricFile* curRightAreas = NULL, *newRightAreas = NULL; MetricFile curRightAreasTemp, newRightAreasTemp; OptionalParameter* rightSpheresOpt = myParams->getOptionalParameter(14); if (rightSpheresOpt->m_present) { curRightSphere = rightSpheresOpt->getSurface(1); newRightSphere = rightSpheresOpt->getSurface(2); OptionalParameter* rightAreaSurfsOpt = rightSpheresOpt->getOptionalParameter(3); if (rightAreaSurfsOpt->m_present) { SurfaceFile* curAreaSurf = rightAreaSurfsOpt->getSurface(1); SurfaceFile* newAreaSurf = rightAreaSurfsOpt->getSurface(2); vector nodeAreasTemp; curAreaSurf->computeNodeAreas(nodeAreasTemp); curRightAreasTemp.setNumberOfNodesAndColumns(curAreaSurf->getNumberOfNodes(), 1); curRightAreasTemp.setValuesForColumn(0, nodeAreasTemp.data()); curRightAreas = &curRightAreasTemp; newAreaSurf->computeNodeAreas(nodeAreasTemp); newRightAreasTemp.setNumberOfNodesAndColumns(newAreaSurf->getNumberOfNodes(), 1); newRightAreasTemp.setValuesForColumn(0, nodeAreasTemp.data()); newRightAreas = &newRightAreasTemp; } OptionalParameter* rightAreaMetricsOpt = rightSpheresOpt->getOptionalParameter(4); if (rightAreaMetricsOpt->m_present) { if (rightAreaSurfsOpt->m_present) { throw AlgorithmException("only one of -right-area-surfs and -right-area-metrics can be specified"); } curRightAreas = rightAreaMetricsOpt->getMetric(1); newRightAreas = rightAreaMetricsOpt->getMetric(2); } } SurfaceFile* curCerebSphere = NULL, *newCerebSphere = NULL; MetricFile* curCerebAreas = NULL, *newCerebAreas = NULL; MetricFile curCerebAreasTemp, newCerebAreasTemp; OptionalParameter* cerebSpheresOpt = myParams->getOptionalParameter(15); if (cerebSpheresOpt->m_present) { curCerebSphere = cerebSpheresOpt->getSurface(1); newCerebSphere = cerebSpheresOpt->getSurface(2); OptionalParameter* cerebAreaSurfsOpt = cerebSpheresOpt->getOptionalParameter(3); if (cerebAreaSurfsOpt->m_present) { SurfaceFile* curAreaSurf = cerebAreaSurfsOpt->getSurface(1); SurfaceFile* newAreaSurf = cerebAreaSurfsOpt->getSurface(2); vector nodeAreasTemp; curAreaSurf->computeNodeAreas(nodeAreasTemp); curCerebAreasTemp.setNumberOfNodesAndColumns(curAreaSurf->getNumberOfNodes(), 1); curCerebAreasTemp.setValuesForColumn(0, nodeAreasTemp.data()); curCerebAreas = &curCerebAreasTemp; newAreaSurf->computeNodeAreas(nodeAreasTemp); newCerebAreasTemp.setNumberOfNodesAndColumns(newAreaSurf->getNumberOfNodes(), 1); newCerebAreasTemp.setValuesForColumn(0, nodeAreasTemp.data()); newCerebAreas = &newCerebAreasTemp; } OptionalParameter* cerebAreaMetricsOpt = cerebSpheresOpt->getOptionalParameter(4); if (cerebAreaMetricsOpt->m_present) { if (cerebAreaSurfsOpt->m_present) { throw AlgorithmException("only one of -cerebellum-area-surfs and -cerebellum-area-metrics can be specified"); } curCerebAreas = cerebAreaMetricsOpt->getMetric(1); newCerebAreas = cerebAreaMetricsOpt->getMetric(2); } } if (warpfieldOpt->m_present) { AlgorithmCiftiResample(myProgObj, myCiftiIn, direction, myTemplate, templateDir, mySurfMethod, myVolMethod, myCiftiOut, surfLargest, voldilatemm, surfdilatemm, myWarpfield.getWarpfield(), curLeftSphere, newLeftSphere, curLeftAreas, newLeftAreas, curRightSphere, newRightSphere, curRightAreas, newRightAreas, curCerebSphere, newCerebSphere, curCerebAreas, newCerebAreas); } else {//rely on AffineFile() being the identity transform for if neither option is specified AlgorithmCiftiResample(myProgObj, myCiftiIn, direction, myTemplate, templateDir, mySurfMethod, myVolMethod, myCiftiOut, surfLargest, voldilatemm, surfdilatemm, myAffine.getMatrix(), curLeftSphere, newLeftSphere, curLeftAreas, newLeftAreas, curRightSphere, newRightSphere, curRightAreas, newRightAreas, curCerebSphere, newCerebSphere, curCerebAreas, newCerebAreas); } } pair AlgorithmCiftiResample::checkForErrors(const CiftiFile* myCiftiIn, const int& direction, const CiftiFile* myTemplate, const int& templateDir, const SurfaceResamplingMethodEnum::Enum& mySurfMethod, const SurfaceFile* curLeftSphere, const SurfaceFile* newLeftSphere, const MetricFile* curLeftAreas, const MetricFile* newLeftAreas, const SurfaceFile* curRightSphere, const SurfaceFile* newRightSphere, const MetricFile* curRightAreas, const MetricFile* newRightAreas, const SurfaceFile* curCerebSphere, const SurfaceFile* newCerebSphere, const MetricFile* curCerebAreas, const MetricFile* newCerebAreas) { if (direction > 1) return make_pair(true, AString("unsupported mapping direction for cifti resample")); const CiftiXML& myInputXML = myCiftiIn->getCiftiXML(); if (myInputXML.getNumberOfDimensions() != 2) return make_pair(true, AString("cifti resample only supports 2D cifti")); if (myInputXML.getMappingType(direction) != CiftiMappingType::BRAIN_MODELS) return make_pair(true, AString("direction for input must contain brain models")); const CiftiBrainModelsMap& inModels = myInputXML.getBrainModelsMap(direction); const CiftiXML& myTemplateXML = myTemplate->getCiftiXML(); if (templateDir < 0 || templateDir >= myTemplateXML.getNumberOfDimensions()) return make_pair(true, AString("specified template direction does not exist in template file")); if (myTemplateXML.getMappingType(templateDir) != CiftiMappingType::BRAIN_MODELS) return make_pair(true, AString("direction for template must contain brain models")); const CiftiBrainModelsMap& outModels = myTemplate->getCiftiXML().getBrainModelsMap(templateDir); vector surfList = outModels.getSurfaceStructureList(), volList = outModels.getVolumeStructureList(); for (int i = 0; i < (int)surfList.size(); ++i)//ensure existence of resampling spheres before doing any computation { if (!inModels.hasSurfaceData(surfList[i])) return make_pair(true, AString("input cifti missing surface information for structure: " + StructureEnum::toGuiName(surfList[i]))); const SurfaceFile* curSphere = NULL, *newSphere = NULL; const MetricFile* curAreas = NULL, *newAreas = NULL; AString structName; switch (surfList[i]) { case StructureEnum::CORTEX_LEFT: curSphere = curLeftSphere; newSphere = newLeftSphere; curAreas = curLeftAreas; newAreas = newLeftAreas; structName = "left"; break; case StructureEnum::CORTEX_RIGHT: curSphere = curRightSphere; newSphere = newRightSphere; curAreas = curRightAreas; newAreas = newRightAreas; structName = "right"; break; case StructureEnum::CEREBELLUM: curSphere = curCerebSphere; newSphere = newCerebSphere; curAreas = curCerebAreas; newAreas = newCerebAreas; structName = "cerebellum"; break; default: return make_pair(true, AString("unsupported surface structure: " + StructureEnum::toGuiName(surfList[i]))); break; } if (curSphere != NULL)//resampling { if (newSphere == NULL) return make_pair(true, AString("missing " + structName + " new sphere")); if (curSphere->getNumberOfNodes() != inModels.getSurfaceNumberOfNodes(surfList[i])) return make_pair(true, AString(structName + " current sphere doesn't match input cifti")); if (newSphere->getNumberOfNodes() != outModels.getSurfaceNumberOfNodes(surfList[i])) return make_pair(true, AString(structName + " new sphere doesn't match input cifti")); switch (mySurfMethod) { case SurfaceResamplingMethodEnum::ADAP_BARY_AREA: if (curAreas == NULL || newAreas == NULL) return make_pair(true, AString(structName + " area data is missing")); if (curAreas->getNumberOfNodes() != curSphere->getNumberOfNodes()) return make_pair(true, AString(structName + " current area data has the wrong number of vertices")); if (newAreas->getNumberOfNodes() != newSphere->getNumberOfNodes()) return make_pair(true, AString(structName + " new area data has the wrong number of vertices")); break; default: break; } } else {//copying if (inModels.getSurfaceNumberOfNodes(surfList[i]) != outModels.getSurfaceNumberOfNodes(surfList[i])) return make_pair(true, AString(structName + " structure requires resampling spheres, does not match template")); } } for (int i = 0; i < (int)volList.size(); ++i) { if (!inModels.hasVolumeData(volList[i])) { return make_pair(true, AString(StructureEnum::toGuiName(volList[i]) + " volume model missing from input cifti")); } } return make_pair(false, AString()); } namespace {//so that we don't need these in the header file struct ResampleCache {//a place to stuff anything that can be precomputed or reused for applying to the same structure in multiple maps SurfaceResamplingHelper surfResamp; VolumePaddingHelper volPadding; const SurfaceFile* curSphere, *newSphere; MetricFile tempMetric1, tempMetric2, surfDilateRoi; LabelFile tempLabel1, tempLabel2; CaretPointer tempVol1, tempVol2, tempVol3, volDilateRoi; vector inSurfMap, outSurfMap; vector inVolMap, outVolMap; vector floatScratch1, floatScratch2; vector intScratch1, intScratch2; vector inOffset; int64_t refDims[3], refOffset[3]; vector > refSform; bool copyMode; }; void setupRowResampling(map& surfCache, map& volCache, const CiftiFile* myCiftiIn, CiftiFile* myCiftiOut, const SurfaceResamplingMethodEnum::Enum& mySurfMethod, const float& voldilatemm, const SurfaceFile* curLeftSphere, const SurfaceFile* newLeftSphere, const MetricFile* curLeftAreas, const MetricFile* newLeftAreas, const SurfaceFile* curRightSphere, const SurfaceFile* newRightSphere, const MetricFile* curRightAreas, const MetricFile* newRightAreas, const SurfaceFile* curCerebSphere, const SurfaceFile* newCerebSphere, const MetricFile* curCerebAreas, const MetricFile* newCerebAreas) { const CiftiXML& myInputXML = myCiftiIn->getCiftiXML(), &myOutXML = myCiftiOut->getCiftiXML(); bool labelMode = (myInputXML.getMappingType(CiftiXML::ALONG_COLUMN) == CiftiMappingType::LABELS); const CiftiBrainModelsMap& inModels = myInputXML.getBrainModelsMap(CiftiXML::ALONG_ROW), &outModels = myOutXML.getBrainModelsMap(CiftiXML::ALONG_ROW); vector surfList = outModels.getSurfaceStructureList(), volList = outModels.getVolumeStructureList(); int numSurfStructs = (int)surfList.size(), numVolStructs = (int)volList.size(); for (int i = 0; i < numSurfStructs; ++i)//initialize reusables { const SurfaceFile* curSphere = NULL, *newSphere = NULL; const MetricFile* curAreas = NULL, *newAreas = NULL; switch (surfList[i]) { case StructureEnum::CORTEX_LEFT: curSphere = curLeftSphere; newSphere = newLeftSphere; curAreas = curLeftAreas; newAreas = newLeftAreas; break; case StructureEnum::CORTEX_RIGHT: curSphere = curRightSphere; newSphere = newRightSphere; curAreas = curRightAreas; newAreas = newRightAreas; break; case StructureEnum::CEREBELLUM: curSphere = curCerebSphere; newSphere = newCerebSphere; curAreas = curCerebAreas; newAreas = newCerebAreas; break; default: throw AlgorithmException("unsupported surface structure: " + StructureEnum::toGuiName(surfList[i])); break; } ResampleCache& myCache = surfCache[surfList[i]]; myCache.inSurfMap = inModels.getSurfaceMap(surfList[i]); myCache.outSurfMap = outModels.getSurfaceMap(surfList[i]); if (curSphere == NULL) { myCache.copyMode = true; myCache.floatScratch1.resize(inModels.getSurfaceNumberOfNodes(surfList[i]), 0.0f); continue; } myCache.copyMode = false; myCache.curSphere = curSphere; myCache.newSphere = newSphere; const float* curAreasPtr = NULL, *newAreasPtr = NULL; if (curAreas != NULL && newAreas != NULL) { curAreasPtr = curAreas->getValuePointerForColumn(0); newAreasPtr = newAreas->getValuePointerForColumn(0); } vector tempRoi(curSphere->getNumberOfNodes(), 0.0f); for (int j = 0; j < (int)myCache.inSurfMap.size(); ++j) { tempRoi[myCache.inSurfMap[j].m_surfaceNode] = 1.0f; } myCache.surfResamp = SurfaceResamplingHelper(mySurfMethod, curSphere, newSphere, curAreasPtr, newAreasPtr, tempRoi.data());//resampling is already a helper, so use it as such tempRoi.resize(newSphere->getNumberOfNodes()); myCache.surfResamp.getResampleValidROI(tempRoi.data()); myCache.surfDilateRoi.setNumberOfNodesAndColumns(newSphere->getNumberOfNodes(), 1); for (int j = 0; j < (int)tempRoi.size(); ++j) { myCache.surfDilateRoi.setValue(j, 0, (tempRoi[j] > 0.0f ? 0.0f : 1.0f)); } if (labelMode) { myCache.intScratch1.resize(curSphere->getNumberOfNodes(), 0); myCache.intScratch2.resize(newSphere->getNumberOfNodes(), 0); myCache.tempLabel1.setNumberOfNodesAndColumns(newSphere->getNumberOfNodes(), 1); } else { myCache.floatScratch1.resize(curSphere->getNumberOfNodes(), 0.0f); myCache.floatScratch2.resize(newSphere->getNumberOfNodes(), 0.0f); myCache.tempMetric1.setNumberOfNodesAndColumns(newSphere->getNumberOfNodes(), 1); } } for (int i = 0; i < numVolStructs; ++i) { ResampleCache& myCache = volCache[volList[i]]; myCache.inVolMap = inModels.getVolumeStructureMap(volList[i]); myCache.outVolMap = outModels.getVolumeStructureMap(volList[i]); vector > sform; vector inDims(3); myCache.inOffset.resize(3); myCache.floatScratch1.resize(inDims[0] * inDims[1] * inDims[2]); AlgorithmCiftiSeparate::getCroppedVolSpace(myCiftiIn, CiftiXML::ALONG_ROW, volList[i], inDims.data(), sform, myCache.inOffset.data()); AlgorithmCiftiSeparate::getCroppedVolSpace(myCiftiOut, CiftiXML::ALONG_ROW, volList[i], myCache.refDims, myCache.refSform, myCache.refOffset); if (labelMode) { myCache.tempVol1.grabNew(new VolumeFile(inDims, sform, 1, SubvolumeAttributes::LABEL)); } else { myCache.tempVol1.grabNew(new VolumeFile(inDims, sform)); myCache.tempVol1->setValueAllVoxels(0.0f); } myCache.tempVol2.grabNew(new VolumeFile(inDims, sform));//temporarily use to make the dilation roi, if needed if (voldilatemm > 0.0f) { myCache.volPadding = VolumePaddingHelper::padMM(myCache.tempVol1, voldilatemm); myCache.volDilateRoi.grabNew(new VolumeFile()); myCache.tempVol3.grabNew(new VolumeFile()); myCache.tempVol2->setValueAllVoxels(1.0f); for (int j = 0; j < (int)myCache.inVolMap.size(); ++j) { myCache.tempVol2->setValue(0.0f, myCache.inVolMap[j].m_ijk[0] - myCache.inOffset[0], myCache.inVolMap[j].m_ijk[1] - myCache.inOffset[1], myCache.inVolMap[j].m_ijk[2] - myCache.inOffset[2]); } myCache.volPadding.doPadding(myCache.tempVol2, myCache.volDilateRoi, 1.0f); } } } void processRowSurface(ResampleCache& myCache, const vector& inRow, vector& outRow, const CiftiXML& myInputXML, const float& surfdilatemm, const bool& surfLargest, const int& unassignedLabelKey, const int64_t& row) { bool labelMode = (myInputXML.getMappingType(CiftiXML::ALONG_COLUMN) == CiftiMappingType::LABELS); int inMapSize = (int)myCache.inSurfMap.size(), outMapSize = (int)myCache.outSurfMap.size(); if (myCache.copyMode)//copy { for (int j = 0; j < inMapSize; ++j) { myCache.floatScratch1[myCache.inSurfMap[j].m_surfaceNode] = inRow[myCache.inSurfMap[j].m_ciftiIndex]; } for (int j = 0; j < outMapSize; ++j) { outRow[myCache.outSurfMap[j].m_ciftiIndex] = myCache.floatScratch1[myCache.outSurfMap[j].m_surfaceNode]; } } else { if (labelMode) { const CiftiLabelsMap& myLabelMap = myInputXML.getLabelsMap(CiftiXML::ALONG_COLUMN); for (int j = 0; j < inMapSize; ++j) { myCache.intScratch1[myCache.inSurfMap[j].m_surfaceNode] = (int)floor(inRow[myCache.inSurfMap[j].m_ciftiIndex] + 0.5f); } if (surfLargest) { myCache.surfResamp.resampleLargest(myCache.intScratch1.data(), myCache.intScratch2.data(), unassignedLabelKey); } else { myCache.surfResamp.resamplePopular(myCache.intScratch1.data(), myCache.intScratch2.data(), unassignedLabelKey); } *(myCache.tempLabel1.getLabelTable()) = *(myLabelMap.getMapLabelTable(row)); myCache.tempLabel1.setLabelKeysForColumn(0, myCache.intScratch2.data()); LabelFile* toUse = &(myCache.tempLabel1); if (surfdilatemm > 0.0f) { AlgorithmLabelDilate(NULL, toUse, myCache.newSphere, surfdilatemm, &(myCache.tempLabel2), &(myCache.surfDilateRoi), 0); toUse = &(myCache.tempLabel2); } const int32_t* outData = toUse->getLabelKeyPointerForColumn(0); for (int j = 0; j < outMapSize; ++j) { outRow[myCache.outSurfMap[j].m_ciftiIndex] = outData[myCache.outSurfMap[j].m_surfaceNode]; } } else { for (int j = 0; j < inMapSize; ++j) { myCache.floatScratch1[myCache.inSurfMap[j].m_surfaceNode] = inRow[myCache.inSurfMap[j].m_ciftiIndex]; } if (surfLargest) { myCache.surfResamp.resampleLargest(myCache.floatScratch1.data(), myCache.floatScratch2.data()); } else { myCache.surfResamp.resampleNormal(myCache.floatScratch1.data(), myCache.floatScratch2.data()); } myCache.tempMetric1.setValuesForColumn(0, myCache.floatScratch2.data()); MetricFile* toUse = &(myCache.tempMetric1); if (surfdilatemm > 0.0f) { AlgorithmMetricDilate(NULL, toUse, myCache.newSphere, surfdilatemm, &(myCache.tempMetric2), &(myCache.surfDilateRoi), NULL, 0, true); toUse = &(myCache.tempMetric2); } const float* outData = toUse->getValuePointerForColumn(0); for (int j = 0; j < outMapSize; ++j) { outRow[myCache.outSurfMap[j].m_ciftiIndex] = outData[myCache.outSurfMap[j].m_surfaceNode]; } } } } } AlgorithmCiftiResample::AlgorithmCiftiResample(ProgressObject* myProgObj, const CiftiFile* myCiftiIn, const int& direction, const CiftiFile* myTemplate, const int& templateDir, const SurfaceResamplingMethodEnum::Enum& mySurfMethod, const VolumeFile::InterpType& myVolMethod, CiftiFile* myCiftiOut, const bool& surfLargest, const float& voldilatemm, const float& surfdilatemm, const VolumeFile* warpfield, const SurfaceFile* curLeftSphere, const SurfaceFile* newLeftSphere, const MetricFile* curLeftAreas, const MetricFile* newLeftAreas, const SurfaceFile* curRightSphere, const SurfaceFile* newRightSphere, const MetricFile* curRightAreas, const MetricFile* newRightAreas, const SurfaceFile* curCerebSphere, const SurfaceFile* newCerebSphere, const MetricFile* curCerebAreas, const MetricFile* newCerebAreas) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); pair myError = checkForErrors(myCiftiIn, direction, myTemplate, templateDir, mySurfMethod, curLeftSphere, newLeftSphere, curLeftAreas, newLeftAreas, curRightSphere, newRightSphere, curRightAreas, newRightAreas, curCerebSphere, newCerebSphere, curCerebAreas, newCerebAreas); if (myError.first) throw AlgorithmException(myError.second); const CiftiXML& myInputXML = myCiftiIn->getCiftiXML(); CiftiXML myOutXML = myInputXML; myOutXML.setMap(direction, *(myTemplate->getCiftiXML().getMap(templateDir))); bool labelMode = (myInputXML.getMappingType(CiftiXML::ALONG_COLUMN) == CiftiMappingType::LABELS); const CiftiBrainModelsMap& outModels = myOutXML.getBrainModelsMap(direction); vector surfList = outModels.getSurfaceStructureList(), volList = outModels.getVolumeStructureList(); myCiftiOut->setCiftiXML(myOutXML); if (direction == CiftiXML::ALONG_COLUMN) { for (int i = 0; i < (int)surfList.size(); ++i)//and now, resampling { const SurfaceFile* curSphere = NULL, *newSphere = NULL; const MetricFile* curAreas = NULL, *newAreas = NULL; switch (surfList[i]) { case StructureEnum::CORTEX_LEFT: curSphere = curLeftSphere; newSphere = newLeftSphere; curAreas = curLeftAreas; newAreas = newLeftAreas; break; case StructureEnum::CORTEX_RIGHT: curSphere = curRightSphere; newSphere = newRightSphere; curAreas = curRightAreas; newAreas = newRightAreas; break; case StructureEnum::CEREBELLUM: curSphere = curCerebSphere; newSphere = newCerebSphere; curAreas = curCerebAreas; newAreas = newCerebAreas; break; default: throw AlgorithmException("unsupported surface structure: " + StructureEnum::toGuiName(surfList[i])); break; } processSurfaceComponent(myCiftiIn, direction, surfList[i], mySurfMethod, myCiftiOut, surfLargest, surfdilatemm, curSphere, newSphere, curAreas, newAreas); } for (int i = 0; i < (int)volList.size(); ++i) { processVolumeWarpfield(myCiftiIn, direction, volList[i], myVolMethod, myCiftiOut, voldilatemm, warpfield); } } else {//avoid cifti separate/replace with ALONG_ROW vector surfList = outModels.getSurfaceStructureList(), volList = outModels.getVolumeStructureList(); int numSurfStructs = (int)surfList.size(), numVolStructs = (int)volList.size(); vector unassignedLabelKey; if (myInputXML.getMappingType(CiftiXML::ALONG_COLUMN) == CiftiMappingType::LABELS) { const CiftiLabelsMap& myLabelMap = myInputXML.getLabelsMap(CiftiXML::ALONG_COLUMN); unassignedLabelKey.resize(myLabelMap.getLength()); for (int i = 0; i < myLabelMap.getLength(); ++i) { unassignedLabelKey[i] = myLabelMap.getMapLabelTable(i)->getUnassignedLabelKey(); } } map surfCache, volCache;//could make them different types, but whatever - two variables in case of structure overlap in surface and volume, as some members may get used by both setupRowResampling(surfCache, volCache, myCiftiIn, myCiftiOut, mySurfMethod, voldilatemm, curLeftSphere, newLeftSphere, curLeftAreas, newLeftAreas, curRightSphere, newRightSphere, curRightAreas, newRightAreas, curCerebSphere, newCerebSphere, curCerebAreas, newCerebAreas); int64_t numRows = myInputXML.getDimensionLength(CiftiXML::ALONG_COLUMN); vector inRow(myInputXML.getDimensionLength(CiftiXML::ALONG_ROW)), outRow(myOutXML.getDimensionLength(CiftiXML::ALONG_ROW)); for (int64_t row = 0; row < numRows; ++row) { myCiftiIn->getRow(inRow.data(), row); for (int i = 0; i < numSurfStructs; ++i) { map::iterator iter = surfCache.find(surfList[i]); CaretAssert(iter != surfCache.end()); processRowSurface(iter->second, inRow, outRow, myInputXML, surfdilatemm, surfLargest, unassignedLabelKey[row], row); } for (int i = 0; i < numVolStructs; ++i) { map::iterator iter = volCache.find(volList[i]); CaretAssert(iter != volCache.end()); ResampleCache& myCache = iter->second; if (labelMode)//gets initialized to 0 when not using labels { myCache.tempVol1->setValueAllVoxels(unassignedLabelKey[row]); } int inMapSize = (int)myCache.inVolMap.size(), outMapSize = (int)myCache.outVolMap.size(); for (int j = 0; j < inMapSize; ++j) { myCache.tempVol1->setValue(inRow[myCache.inVolMap[j].m_ciftiIndex], myCache.inVolMap[j].m_ijk[0] - myCache.inOffset[0], myCache.inVolMap[j].m_ijk[1] - myCache.inOffset[1], myCache.inVolMap[j].m_ijk[2] - myCache.inOffset[2]); } const VolumeFile* toResample = myCache.tempVol1; if (voldilatemm > 0.0f) { myCache.volPadding.doPadding(myCache.tempVol1, myCache.tempVol2); AlgorithmVolumeDilate(NULL, myCache.tempVol2, voldilatemm, AlgorithmVolumeDilate::NEAREST, myCache.tempVol3, myCache.volDilateRoi); toResample = myCache.tempVol3; } AlgorithmVolumeWarpfieldResample(NULL, toResample, warpfield, myCache.refDims, myCache.refSform, myVolMethod, myCache.tempVol2); for (int j = 0; j < outMapSize; ++j) { outRow[myCache.outVolMap[j].m_ciftiIndex] = myCache.tempVol2->getValue(myCache.outVolMap[j].m_ijk[0] - myCache.refOffset[0], myCache.outVolMap[j].m_ijk[1] - myCache.refOffset[1], myCache.outVolMap[j].m_ijk[2] - myCache.refOffset[2]); } } myCiftiOut->setRow(outRow.data(), row); } } } AlgorithmCiftiResample::AlgorithmCiftiResample(ProgressObject* myProgObj, const CiftiFile* myCiftiIn, const int& direction, const CiftiFile* myTemplate, const int& templateDir, const SurfaceResamplingMethodEnum::Enum& mySurfMethod, const VolumeFile::InterpType& myVolMethod, CiftiFile* myCiftiOut, const bool& surfLargest, const float& voldilatemm, const float& surfdilatemm, const FloatMatrix& affine, const SurfaceFile* curLeftSphere, const SurfaceFile* newLeftSphere, const MetricFile* curLeftAreas, const MetricFile* newLeftAreas, const SurfaceFile* curRightSphere, const SurfaceFile* newRightSphere, const MetricFile* curRightAreas, const MetricFile* newRightAreas, const SurfaceFile* curCerebSphere, const SurfaceFile* newCerebSphere, const MetricFile* curCerebAreas, const MetricFile* newCerebAreas) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); pair myError = checkForErrors(myCiftiIn, direction, myTemplate, templateDir, mySurfMethod, curLeftSphere, newLeftSphere, curLeftAreas, newLeftAreas, curRightSphere, newRightSphere, curRightAreas, newRightAreas, curCerebSphere, newCerebSphere, curCerebAreas, newCerebAreas); if (myError.first) throw AlgorithmException(myError.second); const CiftiXML& myInputXML = myCiftiIn->getCiftiXML(); CiftiXML myOutXML = myInputXML; myOutXML.setMap(direction, *(myTemplate->getCiftiXML().getMap(templateDir))); const CiftiBrainModelsMap& outModels = myOutXML.getBrainModelsMap(direction); vector surfList = outModels.getSurfaceStructureList(), volList = outModels.getVolumeStructureList(); myCiftiOut->setCiftiXML(myOutXML); if (direction == CiftiXML::ALONG_COLUMN) { for (int i = 0; i < (int)surfList.size(); ++i)//and now, resampling { const SurfaceFile* curSphere = NULL, *newSphere = NULL; const MetricFile* curAreas = NULL, *newAreas = NULL; switch (surfList[i]) { case StructureEnum::CORTEX_LEFT: curSphere = curLeftSphere; newSphere = newLeftSphere; curAreas = curLeftAreas; newAreas = newLeftAreas; break; case StructureEnum::CORTEX_RIGHT: curSphere = curRightSphere; newSphere = newRightSphere; curAreas = curRightAreas; newAreas = newRightAreas; break; case StructureEnum::CEREBELLUM: curSphere = curCerebSphere; newSphere = newCerebSphere; curAreas = curCerebAreas; newAreas = newCerebAreas; break; default: throw AlgorithmException("unsupported surface structure: " + StructureEnum::toGuiName(surfList[i])); break; } processSurfaceComponent(myCiftiIn, direction, surfList[i], mySurfMethod, myCiftiOut, surfLargest, surfdilatemm, curSphere, newSphere, curAreas, newAreas); } for (int i = 0; i < (int)volList.size(); ++i) { processVolumeAffine(myCiftiIn, direction, volList[i], myVolMethod, myCiftiOut, voldilatemm, affine); } } else {//avoid cifti separate/replace with ALONG_ROW vector surfList = outModels.getSurfaceStructureList(), volList = outModels.getVolumeStructureList(); int numSurfStructs = (int)surfList.size(), numVolStructs = (int)volList.size(); vector unassignedLabelKey; if (myInputXML.getMappingType(CiftiXML::ALONG_COLUMN) == CiftiMappingType::LABELS) { const CiftiLabelsMap& myLabelMap = myInputXML.getLabelsMap(CiftiXML::ALONG_COLUMN); unassignedLabelKey.resize(myLabelMap.getLength()); for (int i = 0; i < myLabelMap.getLength(); ++i) { unassignedLabelKey[i] = myLabelMap.getMapLabelTable(i)->getUnassignedLabelKey(); } } map surfCache, volCache;//could make them different types, but whatever - two variables in case of structure overlap in surface and volume, as some members may get used by both setupRowResampling(surfCache, volCache, myCiftiIn, myCiftiOut, mySurfMethod, voldilatemm, curLeftSphere, newLeftSphere, curLeftAreas, newLeftAreas, curRightSphere, newRightSphere, curRightAreas, newRightAreas, curCerebSphere, newCerebSphere, curCerebAreas, newCerebAreas); int64_t numRows = myInputXML.getDimensionLength(CiftiXML::ALONG_COLUMN); vector inRow(myInputXML.getDimensionLength(CiftiXML::ALONG_ROW)), outRow(myOutXML.getDimensionLength(CiftiXML::ALONG_ROW)); for (int64_t row = 0; row < numRows; ++row) { myCiftiIn->getRow(inRow.data(), row); for (int i = 0; i < numSurfStructs; ++i) { map::iterator iter = surfCache.find(surfList[i]); CaretAssert(iter != surfCache.end()); processRowSurface(iter->second, inRow, outRow, myInputXML, surfdilatemm, surfLargest, unassignedLabelKey[row], row); } for (int i = 0; i < numVolStructs; ++i) { map::iterator iter = volCache.find(volList[i]); CaretAssert(iter != volCache.end()); ResampleCache& myCache = iter->second; int inMapSize = (int)myCache.inVolMap.size(), outMapSize = (int)myCache.outVolMap.size(); for (int j = 0; j < inMapSize; ++j) { myCache.tempVol1->setValue(inRow[myCache.inVolMap[j].m_ciftiIndex], myCache.inVolMap[j].m_ijk[0] - myCache.inOffset[0], myCache.inVolMap[j].m_ijk[1] - myCache.inOffset[1], myCache.inVolMap[j].m_ijk[2] - myCache.inOffset[2]); } const VolumeFile* toResample = myCache.tempVol1; if (voldilatemm > 0.0f) { myCache.volPadding.doPadding(myCache.tempVol1, myCache.tempVol2); AlgorithmVolumeDilate(NULL, myCache.tempVol2, voldilatemm, AlgorithmVolumeDilate::NEAREST, myCache.tempVol3, myCache.volDilateRoi); toResample = myCache.tempVol3; } AlgorithmVolumeAffineResample(NULL, toResample, affine, myCache.refDims, myCache.refSform, myVolMethod, myCache.tempVol2); for (int j = 0; j < outMapSize; ++j) { outRow[myCache.outVolMap[j].m_ciftiIndex] = myCache.tempVol2->getValue(myCache.outVolMap[j].m_ijk[0] - myCache.refOffset[0], myCache.outVolMap[j].m_ijk[1] - myCache.refOffset[1], myCache.outVolMap[j].m_ijk[2] - myCache.refOffset[2]); } } myCiftiOut->setRow(outRow.data(), row); } } } void AlgorithmCiftiResample::processSurfaceComponent(const CiftiFile* myCiftiIn, const int& direction, const StructureEnum::Enum& myStruct, const SurfaceResamplingMethodEnum::Enum& mySurfMethod, CiftiFile* myCiftiOut, const bool& surfLargest, const float& surfdilatemm, const SurfaceFile* curSphere, const SurfaceFile* newSphere, const MetricFile* curAreas, const MetricFile* newAreas) { const CiftiXML& myInputXML = myCiftiIn->getCiftiXML(); if (myInputXML.getMappingType(1 - direction) == CiftiMappingType::LABELS) { LabelFile origLabel; MetricFile origRoi, resampleROI; AlgorithmCiftiSeparate(NULL, myCiftiIn, direction, myStruct, &origLabel, &origRoi); LabelFile newLabel, newDilate, *newUse = &newLabel; if (curSphere != NULL) { AlgorithmLabelResample(NULL, &origLabel, curSphere, newSphere, mySurfMethod, &newLabel, curAreas, newAreas, &origRoi, &resampleROI, surfLargest); origLabel.clear();//delete the data we no longer need to keep memory use down if (surfdilatemm > 0.0f) { MetricFile invertResampleROI; invertResampleROI.setNumberOfNodesAndColumns(resampleROI.getNumberOfNodes(), 1); for (int j = 0; j < resampleROI.getNumberOfNodes(); ++j) { float tempf = (resampleROI.getValue(j, 0) > 0.0f) ? 0.0f : 1.0f;//make an inverse ROI invertResampleROI.setValue(j, 0, tempf); } AlgorithmLabelDilate(NULL, &newLabel, newSphere, surfdilatemm, &newDilate, &invertResampleROI); newLabel.clear();//ditto newUse = &newDilate; } } else { newUse = &origLabel; } AlgorithmCiftiReplaceStructure(NULL, myCiftiOut, direction, myStruct, newUse); } else { MetricFile origMetric, origROI; AlgorithmCiftiSeparate(NULL, myCiftiIn, direction, myStruct, &origMetric, &origROI); MetricFile newMetric, newDilate, resampleROI, *newUse = &newMetric; if (curSphere != NULL) { AlgorithmMetricResample(NULL, &origMetric, curSphere, newSphere, mySurfMethod, &newMetric, curAreas, newAreas, &origROI, &resampleROI, surfLargest); origMetric.clear();//ditto if (surfdilatemm > 0.0f) { MetricFile invertResampleROI; invertResampleROI.setNumberOfNodesAndColumns(resampleROI.getNumberOfNodes(), 1); for (int j = 0; j < resampleROI.getNumberOfNodes(); ++j) { float tempf = (resampleROI.getValue(j, 0) > 0.0f) ? 0.0f : 1.0f;//make an inverse ROI invertResampleROI.setValue(j, 0, tempf); } AlgorithmMetricDilate(NULL, &newMetric, newSphere, surfdilatemm, &newDilate, &invertResampleROI, NULL, -1, true);//we could get the data roi from the template cifti and use it here newMetric.clear(); newUse = &newDilate; } } else { newUse = &origMetric; } AlgorithmCiftiReplaceStructure(NULL, myCiftiOut, direction, myStruct, newUse); } } void AlgorithmCiftiResample::processVolumeWarpfield(const CiftiFile* myCiftiIn, const int& direction, const StructureEnum::Enum& myStruct, const VolumeFile::InterpType& myVolMethod, CiftiFile* myCiftiOut, const float& voldilatemm, const VolumeFile* warpfield) { VolumeFile origData, origROI, origDilate, *origProcess; origProcess = &origData; int64_t offset[3]; AlgorithmCiftiSeparate(NULL, myCiftiIn, direction, myStruct, &origData, offset, &origROI, true); if (voldilatemm > 0.0f) { VolumeFile invertROI; invertROI.reinitialize(origROI.getOriginalDimensions(), origROI.getSform()); vector dims; origROI.getDimensions(dims); int64_t frameSize = dims[0] * dims[1] * dims[2]; const float* roiFrame = origROI.getFrame(); vector invertFrame(frameSize); for (int64_t j = 0; j < frameSize; ++j) { invertFrame[j] = (roiFrame[j] > 0.0f) ? 0.0f : 1.0f; } invertROI.setFrame(invertFrame.data()); VolumePaddingHelper mypadding = VolumePaddingHelper::padMM(&origData, voldilatemm); VolumeFile origPad, invertROIPad; mypadding.doPadding(&origData, &origPad); origData.clear();//delete data we no longer need to keep memory use down mypadding.doPadding(&invertROI, &invertROIPad, 1.0f);//pad with ones since this is an inverted ROI AlgorithmVolumeDilate(NULL, &origPad, voldilatemm, AlgorithmVolumeDilate::NEAREST, &origDilate, &invertROIPad); origPad.clear();//ditto origProcess = &origDilate; } VolumeFile newVolume; int64_t refdims[3], refoffset[3]; vector > refsform; AlgorithmCiftiSeparate::getCroppedVolSpace(myCiftiOut, direction, myStruct, refdims, refsform, refoffset); AlgorithmVolumeWarpfieldResample(NULL, origProcess, warpfield, refdims, refsform, myVolMethod, &newVolume); origProcess->clear();//ditto AlgorithmCiftiReplaceStructure(NULL, myCiftiOut, direction, myStruct, &newVolume, true); } void AlgorithmCiftiResample::processVolumeAffine(const CiftiFile* myCiftiIn, const int& direction, const StructureEnum::Enum& myStruct, const VolumeFile::InterpType& myVolMethod, CiftiFile* myCiftiOut, const float& voldilatemm, const FloatMatrix& affine) { VolumeFile origData, origROI, origDilate, *origProcess; origProcess = &origData; int64_t offset[3]; AlgorithmCiftiSeparate(NULL, myCiftiIn, direction, myStruct, &origData, offset, &origROI, true); if (voldilatemm > 0.0f) { VolumeFile invertROI; invertROI.reinitialize(origROI.getOriginalDimensions(), origROI.getSform()); vector dims; origROI.getDimensions(dims); int64_t frameSize = dims[0] * dims[1] * dims[2]; const float* roiFrame = origROI.getFrame(); vector invertFrame(frameSize); for (int64_t j = 0; j < frameSize; ++j) { invertFrame[j] = (roiFrame[j] > 0.0f) ? 0.0f : 1.0f; } invertROI.setFrame(invertFrame.data()); VolumePaddingHelper mypadding = VolumePaddingHelper::padMM(&origData, voldilatemm); VolumeFile origPad, invertROIPad; mypadding.doPadding(&origData, &origPad); origData.clear();//delete data we no longer need to keep memory use down mypadding.doPadding(&invertROI, &invertROIPad, 1.0f);//pad with ones since this is an inverted ROI AlgorithmVolumeDilate(NULL, &origPad, voldilatemm, AlgorithmVolumeDilate::NEAREST, &origDilate, &invertROIPad); origPad.clear();//ditto origProcess = &origDilate; } VolumeFile newVolume; int64_t refdims[3], refoffset[3]; vector > refsform; AlgorithmCiftiSeparate::getCroppedVolSpace(myCiftiOut, direction, myStruct, refdims, refsform, refoffset); AlgorithmVolumeAffineResample(NULL, origProcess, affine, refdims, refsform, myVolMethod, &newVolume); origProcess->clear();//ditto AlgorithmCiftiReplaceStructure(NULL, myCiftiOut, direction, myStruct, &newVolume, true); } float AlgorithmCiftiResample::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmCiftiResample::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmCiftiResample.h000066400000000000000000000130231255417355300231540ustar00rootroot00000000000000#ifndef __ALGORITHM_CIFTI_RESAMPLE_H__ #define __ALGORITHM_CIFTI_RESAMPLE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" #include "FloatMatrix.h" #include "StructureEnum.h" #include "SurfaceResamplingMethodEnum.h" #include "VolumeFile.h" #include //for pair namespace caret { class AlgorithmCiftiResample : public AbstractAlgorithm { AlgorithmCiftiResample(); void processSurfaceComponent(const CiftiFile* myCiftiIn, const int& direction, const StructureEnum::Enum& myStruct, const SurfaceResamplingMethodEnum::Enum& mySurfMethod, CiftiFile* myCiftiOut, const bool& surfLargest, const float& surfdilatemm, const SurfaceFile* curSphere, const SurfaceFile* newSphere, const MetricFile* curAreas, const MetricFile* newAreas); void processVolumeWarpfield(const CiftiFile* myCiftiIn, const int& direction, const StructureEnum::Enum& myStruct, const VolumeFile::InterpType& myVolMethod, CiftiFile* myCiftiOut, const float& voldilatemm, const VolumeFile* warpfield); void processVolumeAffine(const CiftiFile* myCiftiIn, const int& direction, const StructureEnum::Enum& myStruct, const VolumeFile::InterpType& myVolMethod, CiftiFile* myCiftiOut, const float& voldilatemm, const FloatMatrix& affine); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: //so that other code that uses it more than once can pre-check things - returns true for error present! static std::pair checkForErrors(const CiftiFile* myCiftiIn, const int& direction, const CiftiFile* myTemplate, const int& templateDir, const SurfaceResamplingMethodEnum::Enum& mySurfMethod, const SurfaceFile* curLeftSphere, const SurfaceFile* newLeftSphere, const MetricFile* curLeftAreas, const MetricFile* newLeftAreas, const SurfaceFile* curRightSphere, const SurfaceFile* newRightSphere, const MetricFile* curRightAreas, const MetricFile* newRightAreas, const SurfaceFile* curCerebSphere, const SurfaceFile* newCerebSphere, const MetricFile* curCerebAreas, const MetricFile* newCerebAreas); AlgorithmCiftiResample(ProgressObject* myProgObj, const CiftiFile* myCiftiIn, const int& direction, const CiftiFile* myTemplate, const int& templateDir, const SurfaceResamplingMethodEnum::Enum& mySurfMethod, const VolumeFile::InterpType& myVolMethod, CiftiFile* myCiftiOut, const bool& surfLargest, const float& voldilatemm, const float& surfdilatemm, const VolumeFile* warpfield, const SurfaceFile* curLeftSphere, const SurfaceFile* newLeftSphere, const MetricFile* curLeftAreas, const MetricFile* newLeftAreas, const SurfaceFile* curRightSphere, const SurfaceFile* newRightSphere, const MetricFile* curRightAreas, const MetricFile* newRightAreas, const SurfaceFile* curCerebSphere, const SurfaceFile* newCerebSphere, const MetricFile* curCerebAreas, const MetricFile* newCerebAreas); AlgorithmCiftiResample(ProgressObject* myProgObj, const CiftiFile* myCiftiIn, const int& direction, const CiftiFile* myTemplate, const int& templateDir, const SurfaceResamplingMethodEnum::Enum& mySurfMethod, const VolumeFile::InterpType& myVolMethod, CiftiFile* myCiftiOut, const bool& surfLargest, const float& voldilatemm, const float& surfdilatemm, const FloatMatrix& affine, const SurfaceFile* curLeftSphere, const SurfaceFile* newLeftSphere, const MetricFile* curLeftAreas, const MetricFile* newLeftAreas, const SurfaceFile* curRightSphere, const SurfaceFile* newRightSphere, const MetricFile* curRightAreas, const MetricFile* newRightAreas, const SurfaceFile* curCerebSphere, const SurfaceFile* newCerebSphere, const MetricFile* curCerebAreas, const MetricFile* newCerebAreas); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmCiftiResample; } #endif //__ALGORITHM_CIFTI_RESAMPLE_H__ workbench-1.1.1/src/Algorithms/AlgorithmCiftiRestrictDenseMap.cxx000066400000000000000000000420601255417355300251760ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmCiftiRestrictDenseMap.h" #include "AlgorithmException.h" #include "CaretLogger.h" #include "CiftiFile.h" #include "MetricFile.h" #include "MultiDimIterator.h" #include "VolumeFile.h" #include using namespace caret; using namespace std; AString AlgorithmCiftiRestrictDenseMap::getCommandSwitch() { return "-cifti-restrict-dense-map"; } AString AlgorithmCiftiRestrictDenseMap::getShortDescription() { return "EXCLUDE BRAINORDINATES FROM A CIFTI FILE"; } OperationParameters* AlgorithmCiftiRestrictDenseMap::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiParameter(1, "cifti-in", "the input cifti"); ret->addStringParameter(2, "direction", "which dimension to change the mapping on, ROW or COLUMN"); ret->addCiftiOutputParameter(3, "cifti-out", "the output cifti"); OptionalParameter* ciftiRoiOpt = ret->createOptionalParameter(4, "-cifti-roi", "cifti file containing combined rois"); ciftiRoiOpt->addCiftiParameter(1, "roi-cifti", "the rois as a cifti file"); OptionalParameter* leftRoiOpt = ret->createOptionalParameter(5, "-left-roi", "vertices to use from left hemisphere"); leftRoiOpt->addMetricParameter(1, "roi-metric", "the left roi as a metric file"); OptionalParameter* rightRoiOpt = ret->createOptionalParameter(6, "-right-roi", "vertices to use from right hemisphere"); rightRoiOpt->addMetricParameter(1, "roi-metric", "the right roi as a metric file"); OptionalParameter* cerebRoiOpt = ret->createOptionalParameter(7, "-cerebellum-roi", "vertices to use from cerebellum"); cerebRoiOpt->addMetricParameter(1, "roi-metric", "the cerebellum roi as a metric file"); OptionalParameter* volRoiOpt = ret->createOptionalParameter(8, "-vol-roi", "voxels to use"); volRoiOpt->addVolumeParameter(1, "roi-vol", "the roi volume file"); ret->setHelpText( AString("Writes a modified version of , where all brainordinates outside the specified roi(s) are removed from the file. ") + "If -cifti-roi is specified, no other -*-roi option may be specified. " + "If not using -cifti-roi, any -*-roi options not present will discard the relevant structure, if present in the input file." ); return ret; } void AlgorithmCiftiRestrictDenseMap::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { const CiftiFile* ciftiIn = myParams->getCifti(1); AString dirString = myParams->getString(2); int direction = -1; if (dirString == "ROW") { direction = CiftiXML::ALONG_ROW; } else if (dirString == "COLUMN") { direction = CiftiXML::ALONG_COLUMN; } else { throw AlgorithmException("unrecognized direction string, use ROW or COLUMN"); } CiftiFile* ciftiOut = myParams->getOutputCifti(3); OptionalParameter* ciftiRoiOpt = myParams->getOptionalParameter(4); OptionalParameter* leftRoiOpt = myParams->getOptionalParameter(5); OptionalParameter* rightRoiOpt = myParams->getOptionalParameter(6); OptionalParameter* cerebRoiOpt = myParams->getOptionalParameter(7); OptionalParameter* volRoiOpt = myParams->getOptionalParameter(8); if (ciftiRoiOpt->m_present) { if (leftRoiOpt->m_present || rightRoiOpt->m_present || cerebRoiOpt->m_present || volRoiOpt->m_present) { throw AlgorithmException("-cifti-roi cannot be specified with any other roi option"); } const CiftiFile* ciftiRoi = ciftiRoiOpt->getCifti(1); AlgorithmCiftiRestrictDenseMap(myProgObj, ciftiIn, direction, ciftiOut, ciftiRoi); } else { if (!(leftRoiOpt->m_present || rightRoiOpt->m_present || cerebRoiOpt->m_present || volRoiOpt->m_present)) { throw AlgorithmException("you must specify at least one roi option"); } const MetricFile* leftRoi = NULL, *rightRoi = NULL, *cerebRoi = NULL; const VolumeFile* volRoi = NULL; if (leftRoiOpt->m_present) leftRoi = leftRoiOpt->getMetric(1); if (rightRoiOpt->m_present) rightRoi = rightRoiOpt->getMetric(1); if (cerebRoiOpt->m_present) cerebRoi = cerebRoiOpt->getMetric(1); if (volRoiOpt->m_present) volRoi = volRoiOpt->getVolume(1); AlgorithmCiftiRestrictDenseMap(myProgObj, ciftiIn, direction, ciftiOut, leftRoi, rightRoi, cerebRoi, volRoi); } } AlgorithmCiftiRestrictDenseMap::AlgorithmCiftiRestrictDenseMap(ProgressObject* myProgObj, const CiftiFile* ciftiIn, const int& direction, CiftiFile* ciftiOut, const CiftiFile* ciftiRoi) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); const CiftiXML& inXML = ciftiIn->getCiftiXML(), &roiXML = ciftiRoi->getCiftiXML(); if (direction < 0 || direction >= inXML.getNumberOfDimensions()) { throw AlgorithmException("invalid direction for input file"); } if (inXML.getMappingType(direction) != CiftiMappingType::BRAIN_MODELS) { throw AlgorithmException("input does not have a brain models mapping along specified direction"); } if (roiXML.getMappingType(CiftiXML::ALONG_COLUMN) != CiftiMappingType::BRAIN_MODELS) { throw AlgorithmException("input does not have a brain models mapping along specified direction"); } const CiftiBrainModelsMap& inMap = inXML.getBrainModelsMap(direction); if (!inMap.approximateMatch(roiXML.getBrainModelsMap(CiftiXML::ALONG_COLUMN))) {//for now, require that the brain models mappings use the same vertices/voxels, but allow for unusual surface structures not available in the metric/volume version throw AlgorithmException("roi cifti brain models mapping doesn't match the input cifti"); }//alternatively, could separate and call the other version if they don't match, but could be more confusing vector roiData(roiXML.getDimensionLength(CiftiXML::ALONG_COLUMN)); ciftiRoi->getColumn(roiData.data(), 0);//we only need 1 column from the roi CiftiBrainModelsMap outMap; if (inMap.hasVolumeData()) { outMap.setVolumeSpace(inMap.getVolumeSpace()); } vector translate; vector modelInfo = inMap.getModelInfo(); for (int i = 0; i < (int)modelInfo.size(); ++i) { switch (modelInfo[i].m_type) { case CiftiBrainModelsMap::SURFACE: { vector surfMap = inMap.getSurfaceMap(modelInfo[i].m_structure); vector usedNodes; for (size_t j = 0; j < surfMap.size(); ++j) { if (roiData[surfMap[j].m_ciftiIndex] > 0.0f) { usedNodes.push_back(surfMap[j].m_surfaceNode); translate.push_back(surfMap[j].m_ciftiIndex); } } if (!usedNodes.empty()) { outMap.addSurfaceModel(inMap.getSurfaceNumberOfNodes(modelInfo[i].m_structure), modelInfo[i].m_structure, usedNodes); } break; } case CiftiBrainModelsMap::VOXELS: { vector volMap = inMap.getVolumeStructureMap(modelInfo[i].m_structure); vector usedVoxels; for (size_t j = 0; j < volMap.size(); ++j) { if (roiData[volMap[j].m_ciftiIndex] > 0.0f) { usedVoxels.push_back(volMap[j].m_ijk[0]); usedVoxels.push_back(volMap[j].m_ijk[1]); usedVoxels.push_back(volMap[j].m_ijk[2]); translate.push_back(volMap[j].m_ciftiIndex); } } if (!usedVoxels.empty()) { outMap.addVolumeModel(modelInfo[i].m_structure, usedVoxels); } break; } } } if (outMap.getLength() == 0) { throw AlgorithmException("output mapping would contain no brainordinates"); } CiftiXML outXML = inXML; outXML.setMap(direction, outMap); ciftiOut->setCiftiXML(outXML); vector outDims = outXML.getDimensions(); vector scratchRow(inXML.getDimensionLength(CiftiXML::ALONG_ROW)); if (direction == CiftiXML::ALONG_ROW) { vector outRow(outMap.getLength()); for (MultiDimIterator iter(vector(outDims.begin() + 1, outDims.end())); !iter.atEnd(); ++iter) { ciftiIn->getRow(scratchRow.data(), *iter); for (size_t i = 0; i < translate.size(); ++i) { outRow[i] = scratchRow[translate[i]]; } ciftiOut->setRow(outRow.data(), *iter); } } else { for (MultiDimIterator iter(vector(outDims.begin() + 1, outDims.end())); !iter.atEnd(); ++iter) { vector indices = *iter; indices[direction - 1] = translate[(*iter)[direction - 1]]; ciftiIn->getRow(scratchRow.data(), indices); ciftiOut->setRow(scratchRow.data(), *iter); } } } AlgorithmCiftiRestrictDenseMap::AlgorithmCiftiRestrictDenseMap(ProgressObject* myProgObj, const CiftiFile* ciftiIn, const int& direction, CiftiFile* ciftiOut, const MetricFile* leftRoi, const MetricFile* rightRoi, const MetricFile* cerebRoi, const VolumeFile* volRoi) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); const CiftiXML& inXML = ciftiIn->getCiftiXML(); if (direction < 0 || direction >= inXML.getNumberOfDimensions()) { throw AlgorithmException("invalid direction for input file"); } if (inXML.getMappingType(direction) != CiftiMappingType::BRAIN_MODELS) { throw AlgorithmException("input does not have a brain models mapping along specified direction"); } const CiftiBrainModelsMap& inMap = inXML.getBrainModelsMap(direction); if (leftRoi != NULL) { if (!inMap.hasSurfaceData(StructureEnum::CORTEX_LEFT)) { throw AlgorithmException("left surface roi provided, but input has no left surface data"); } if (leftRoi->getNumberOfNodes() != inMap.getSurfaceNumberOfNodes(StructureEnum::CORTEX_LEFT)) { throw AlgorithmException("left surface roi has different number of vertices than input cifti"); } } if (rightRoi != NULL) { if (!inMap.hasSurfaceData(StructureEnum::CORTEX_RIGHT)) { throw AlgorithmException("right surface roi provided, but input has no left surface data"); } if (rightRoi->getNumberOfNodes() != inMap.getSurfaceNumberOfNodes(StructureEnum::CORTEX_RIGHT)) { throw AlgorithmException("right surface roi has different number of vertices than input cifti"); } } if (cerebRoi != NULL) { if (!inMap.hasSurfaceData(StructureEnum::CEREBELLUM)) { throw AlgorithmException("cerebellum surface roi provided, but input has no left surface data"); } if (cerebRoi->getNumberOfNodes() != inMap.getSurfaceNumberOfNodes(StructureEnum::CEREBELLUM)) { throw AlgorithmException("cerebellum surface roi has different number of vertices than input cifti"); } } if (volRoi != NULL) { if (!inMap.hasVolumeData()) throw AlgorithmException("volume roi provided, but input has no volume data"); if (!inMap.getVolumeSpace().matches(volRoi->getVolumeSpace())) throw AlgorithmException("roi volume has different volume space than input cifti"); } CiftiBrainModelsMap outMap; if (inMap.hasVolumeData() && volRoi != NULL) { outMap.setVolumeSpace(inMap.getVolumeSpace()); } vector translate; vector modelInfo = inMap.getModelInfo(); for (int i = 0; i < (int)modelInfo.size(); ++i) { switch (modelInfo[i].m_type) { case CiftiBrainModelsMap::SURFACE: { const MetricFile* useRoi = NULL; switch (modelInfo[i].m_structure) { case StructureEnum::CORTEX_LEFT: useRoi = leftRoi; break; case StructureEnum::CORTEX_RIGHT: useRoi = rightRoi; break; case StructureEnum::CEREBELLUM: useRoi = cerebRoi; break; default: CaretLogWarning("removing unsupported surface structure: '" + StructureEnum::toName(modelInfo[i].m_structure) + "'"); break;//remove other surface structures, but warn } if (useRoi != NULL) { const float* roiData = useRoi->getValuePointerForColumn(0); vector surfMap = inMap.getSurfaceMap(modelInfo[i].m_structure); vector usedNodes; for (size_t j = 0; j < surfMap.size(); ++j) { if (roiData[surfMap[j].m_surfaceNode] > 0.0f) { usedNodes.push_back(surfMap[j].m_surfaceNode); translate.push_back(surfMap[j].m_ciftiIndex); } } if (!usedNodes.empty()) { outMap.addSurfaceModel(inMap.getSurfaceNumberOfNodes(modelInfo[i].m_structure), modelInfo[i].m_structure, usedNodes); } } break; } case CiftiBrainModelsMap::VOXELS: { if (volRoi != NULL) { vector volMap = inMap.getVolumeStructureMap(modelInfo[i].m_structure); vector usedVoxels; for (size_t j = 0; j < volMap.size(); ++j) { if (volRoi->getValue(volMap[j].m_ijk) > 0.0f) { usedVoxels.push_back(volMap[j].m_ijk[0]); usedVoxels.push_back(volMap[j].m_ijk[1]); usedVoxels.push_back(volMap[j].m_ijk[2]); translate.push_back(volMap[j].m_ciftiIndex); } } if (!usedVoxels.empty()) { outMap.addVolumeModel(modelInfo[i].m_structure, usedVoxels); } } break; } } } if (outMap.getLength() == 0) { throw AlgorithmException("output mapping would contain no brainordinates"); } CiftiXML outXML = inXML; outXML.setMap(direction, outMap); ciftiOut->setCiftiXML(outXML); vector outDims = outXML.getDimensions(); vector scratchRow(inXML.getDimensionLength(CiftiXML::ALONG_ROW)); if (direction == CiftiXML::ALONG_ROW) { vector outRow(outMap.getLength()); for (MultiDimIterator iter(vector(outDims.begin() + 1, outDims.end())); !iter.atEnd(); ++iter) { ciftiIn->getRow(scratchRow.data(), *iter); for (size_t i = 0; i < translate.size(); ++i) { outRow[i] = scratchRow[translate[i]]; } ciftiOut->setRow(outRow.data(), *iter); } } else { for (MultiDimIterator iter(vector(outDims.begin() + 1, outDims.end())); !iter.atEnd(); ++iter) { vector indices = *iter; indices[direction - 1] = translate[(*iter)[direction - 1]]; ciftiIn->getRow(scratchRow.data(), indices); ciftiOut->setRow(scratchRow.data(), *iter); } } } float AlgorithmCiftiRestrictDenseMap::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmCiftiRestrictDenseMap::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmCiftiRestrictDenseMap.h000066400000000000000000000041131255417355300246200ustar00rootroot00000000000000#ifndef __ALGORITHM_CIFTI_RESTRICT_DENSE_MAP_H__ #define __ALGORITHM_CIFTI_RESTRICT_DENSE_MAP_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmCiftiRestrictDenseMap : public AbstractAlgorithm { AlgorithmCiftiRestrictDenseMap(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmCiftiRestrictDenseMap(ProgressObject* myProgObj, const CiftiFile* ciftiIn, const int& direction, CiftiFile* ciftiOut, const CiftiFile* ciftiRoi); AlgorithmCiftiRestrictDenseMap(ProgressObject* myProgObj, const CiftiFile* ciftiIn, const int& direction, CiftiFile* ciftiOut, const MetricFile* leftRoi, const MetricFile* rightRoi = NULL, const MetricFile* cerebRoi = NULL, const VolumeFile* volRoi = NULL); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmCiftiRestrictDenseMap; } #endif //__ALGORITHM_CIFTI_RESTRICT_DENSE_MAP_H__ workbench-1.1.1/src/Algorithms/AlgorithmCiftiSeparate.cxx000066400000000000000000001026401255417355300235270ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmCiftiSeparate.h" #include "AlgorithmException.h" #include "CaretLogger.h" #include "CaretPointer.h" #include "CiftiFile.h" #include "GiftiLabelTable.h" #include "LabelFile.h" #include "MetricFile.h" #include "Vector3D.h" #include "VolumeFile.h" #include #include using namespace caret; using namespace std; AString AlgorithmCiftiSeparate::getCommandSwitch() { return "-cifti-separate"; } AString AlgorithmCiftiSeparate::getShortDescription() { return "WRITE A CIFTI STRUCTURE AS METRIC, LABEL OR VOLUME"; } OperationParameters* AlgorithmCiftiSeparate::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiParameter(1, "cifti-in", "the cifti to separate a component of"); ret->addStringParameter(2, "direction", "which direction to separate into components, ROW or COLUMN"); ParameterComponent* labelOpt = ret->createRepeatableParameter(3, "-label", "separate a surface model into a surface label file"); labelOpt->addStringParameter(1, "structure", "the structure to output"); labelOpt->addLabelOutputParameter(2, "label-out", "the output label file"); OptionalParameter* labelRoiOpt = labelOpt->createOptionalParameter(3, "-roi", "also output the roi of which vertices have data"); labelRoiOpt->addMetricOutputParameter(1, "roi-out", "the roi output metric"); ParameterComponent* metricOpt = ret->createRepeatableParameter(4, "-metric", "separate a surface model into a metric file"); metricOpt->addStringParameter(1, "structure", "the structure to output"); metricOpt->addMetricOutputParameter(2, "metric-out", "the output metric"); OptionalParameter* metricRoiOpt = metricOpt->createOptionalParameter(3, "-roi", "also output the roi of which vertices have data"); metricRoiOpt->addMetricOutputParameter(1, "roi-out", "the roi output metric"); ParameterComponent* volumeOpt = ret->createRepeatableParameter(5, "-volume", "separate a volume structure into a volume file"); volumeOpt->addStringParameter(1, "structure", "the structure to output"); volumeOpt->addVolumeOutputParameter(2, "volume-out", "the output volume"); OptionalParameter* volumeRoiOpt = volumeOpt->createOptionalParameter(3, "-roi", "also output the roi of which voxels have data"); volumeRoiOpt->addVolumeOutputParameter(1, "roi-out", "the roi output volume"); volumeOpt->createOptionalParameter(4, "-crop", "crop volume to the size of the component rather than using the original volume size"); OptionalParameter* volumeAllOpt = ret->createOptionalParameter(6, "-volume-all", "separate all volume structures into a volume file"); volumeAllOpt->addVolumeOutputParameter(1, "volume-out", "the output volume"); OptionalParameter* volumeAllRoiOpt = volumeAllOpt->createOptionalParameter(2, "-roi", "also output the roi of which voxels have data"); volumeAllRoiOpt->addVolumeOutputParameter(1, "roi-out", "the roi output volume"); OptionalParameter* volumeAllLabelOpt = volumeAllOpt->createOptionalParameter(4, "-label", "output a volume label file indicating the location of structures"); volumeAllLabelOpt->addVolumeOutputParameter(1, "label-out", "the label output volume"); volumeAllOpt->createOptionalParameter(3, "-crop", "crop volume to the size of the data rather than using the original volume size"); AString helpText = AString("For dtseries, dscalar, and dlabel, use COLUMN for , and if you have a symmetric dconn, COLUMN is more efficient.\n\n") + "You must specify at least one of -metric, -volume-all, -volume, or -label for this command to do anything. " + "Output volumes will spatially line up with their original positions, whether or not they are cropped.\n\n" + "For each argument, use one of the following strings:\n"; vector myStructureEnums; StructureEnum::getAllEnums(myStructureEnums); for (int i = 0; i < (int)myStructureEnums.size(); ++i) { helpText += "\n" + StructureEnum::toName(myStructureEnums[i]); } ret->setHelpText(helpText); return ret; } void AlgorithmCiftiSeparate::useParameters(OperationParameters* myParams, ProgressObject* /*myProgObj*/) {//ignore the progress object for now, and allow specifying multiple options at once CiftiFile* ciftiIn = myParams->getCifti(1); AString dirName = myParams->getString(2); int myDir; if (dirName == "ROW") { myDir = CiftiXML::ALONG_ROW; } else if (dirName == "COLUMN") { myDir = CiftiXML::ALONG_COLUMN; } else { throw AlgorithmException("incorrect string for direction, use ROW or COLUMN"); } bool outputRequested = false; const vector& labelInstances = *(myParams->getRepeatableParameterInstances(3)); for (int i = 0; i < (int)labelInstances.size(); ++i) { outputRequested = true; AString structName = labelInstances[i]->getString(1); bool ok = false; StructureEnum::Enum myStruct = StructureEnum::fromName(structName, &ok); if (!ok) { throw AlgorithmException("unrecognized structure type"); } LabelFile* labelOut = labelInstances[i]->getOutputLabel(2); MetricFile* roiOut = NULL; OptionalParameter* labelRoiOpt = labelInstances[i]->getOptionalParameter(3); if (labelRoiOpt->m_present) { roiOut = labelRoiOpt->getOutputMetric(1); } AlgorithmCiftiSeparate(NULL, ciftiIn, myDir, myStruct, labelOut, roiOut); } const vector& metricInstances = *(myParams->getRepeatableParameterInstances(4)); for (int i = 0; i < (int)metricInstances.size(); ++i) { outputRequested = true; AString structName = metricInstances[i]->getString(1); bool ok = false; StructureEnum::Enum myStruct = StructureEnum::fromName(structName, &ok); if (!ok) { throw AlgorithmException("unrecognized structure type"); } MetricFile* metricOut = metricInstances[i]->getOutputMetric(2); MetricFile* roiOut = NULL; OptionalParameter* metricRoiOpt = metricInstances[i]->getOptionalParameter(3); if (metricRoiOpt->m_present) { roiOut = metricRoiOpt->getOutputMetric(1); } AlgorithmCiftiSeparate(NULL, ciftiIn, myDir, myStruct, metricOut, roiOut); } const vector& volumeInstances = *(myParams->getRepeatableParameterInstances(5)); for (int i = 0; i < (int)volumeInstances.size(); ++i) { outputRequested = true; AString structName = volumeInstances[i]->getString(1); bool ok = false; StructureEnum::Enum myStruct = StructureEnum::fromName(structName, &ok); if (!ok) { throw AlgorithmException("unrecognized structure type"); } VolumeFile* volOut = volumeInstances[i]->getOutputVolume(2); VolumeFile* roiOut = NULL; OptionalParameter* volumeRoiOpt = volumeInstances[i]->getOptionalParameter(3); if (volumeRoiOpt->m_present) { roiOut = volumeRoiOpt->getOutputVolume(1); } bool cropVol = volumeInstances[i]->getOptionalParameter(4)->m_present; int64_t offset[3]; AlgorithmCiftiSeparate(NULL, ciftiIn, myDir, myStruct, volOut, offset, roiOut, cropVol); } OptionalParameter* volumeAllOpt = myParams->getOptionalParameter(6); if (volumeAllOpt->m_present) { outputRequested = true; VolumeFile* volOut = volumeAllOpt->getOutputVolume(1); VolumeFile* roiOut = NULL; OptionalParameter* volumeAllRoiOpt = volumeAllOpt->getOptionalParameter(2); if (volumeAllRoiOpt->m_present) { roiOut = volumeAllRoiOpt->getOutputVolume(1); } bool cropVol = volumeAllOpt->getOptionalParameter(3)->m_present; VolumeFile* labelOut = NULL; OptionalParameter* volumeAllLabelOpt = volumeAllOpt->getOptionalParameter(4); if (volumeAllLabelOpt->m_present) { labelOut = volumeAllLabelOpt->getOutputVolume(1); } int64_t offset[3]; AlgorithmCiftiSeparate(NULL, ciftiIn, myDir, volOut, offset, roiOut, cropVol, labelOut); } if (!outputRequested) { CaretLogWarning("no output requested from -cifti-separate, operation will do nothing"); } } AlgorithmCiftiSeparate::AlgorithmCiftiSeparate(ProgressObject* myProgObj, const CiftiFile* ciftiIn, const int& myDir, const StructureEnum::Enum& myStruct, MetricFile* metricOut, MetricFile* roiOut) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); const CiftiXML& myXML = ciftiIn->getCiftiXML(); if (myXML.getNumberOfDimensions() != 2) throw AlgorithmException("cifti separate only supported on 2D cifti"); if (myDir >= myXML.getNumberOfDimensions() || myDir < 0) throw AlgorithmException("direction invalid for input cifti"); if (myXML.getMappingType(myDir) != CiftiMappingType::BRAIN_MODELS) throw AlgorithmException("specified direction does not contain brain models"); if (myXML.getMappingType(1 - myDir) == CiftiMappingType::LABELS) CaretLogWarning("creating a metric file from cifti label data"); const CiftiBrainModelsMap& myBrainModelsMap = myXML.getBrainModelsMap(myDir); vector myMap = myBrainModelsMap.getSurfaceMap(myStruct); int rowSize = ciftiIn->getNumberOfColumns(), colSize = ciftiIn->getNumberOfRows(); if (myDir == CiftiXML::ALONG_COLUMN) { int64_t numNodes = myBrainModelsMap.getSurfaceNumberOfNodes(myStruct); metricOut->setNumberOfNodesAndColumns(numNodes, rowSize); metricOut->setStructure(myStruct); const CiftiMappingType& myNamesMap = *(myXML.getMap(1 - myDir)); for (int j = 0; j < rowSize; ++j) { metricOut->setMapName(j, myNamesMap.getIndexName(j)); } if (roiOut != NULL) { roiOut->setNumberOfNodesAndColumns(numNodes, 1); roiOut->setStructure(myStruct); } int mapSize = (int)myMap.size(); CaretArray rowScratch(rowSize); CaretArray nodeUsed(numNodes, 0.0f); for (int i = 0; i < mapSize; ++i) { ciftiIn->getRow(rowScratch, myMap[i].m_ciftiIndex); nodeUsed[myMap[i].m_surfaceNode] = 1.0f; for (int j = 0; j < rowSize; ++j) { metricOut->setValue(myMap[i].m_surfaceNode, j, rowScratch[j]); } } if (roiOut != NULL) { roiOut->setValuesForColumn(0, nodeUsed); } for (int i = 0; i < numNodes; ++i)//zero unused columns { if (nodeUsed[i] == 0.0f) { for (int j = 0; j < rowSize; ++j) { metricOut->setValue(i, j, 0.0f); } } } } else { int64_t numNodes = myBrainModelsMap.getSurfaceNumberOfNodes(myStruct); metricOut->setNumberOfNodesAndColumns(numNodes, colSize); metricOut->setStructure(myStruct); const CiftiMappingType& myNamesMap = *(myXML.getMap(1 - myDir)); for (int j = 0; j < rowSize; ++j) { metricOut->setMapName(j, myNamesMap.getIndexName(j)); } if (roiOut != NULL) { roiOut->setNumberOfNodesAndColumns(numNodes, 1); roiOut->setStructure(myStruct); } int mapSize = (int)myMap.size(); CaretArray rowScratch(rowSize), metricScratch(numNodes, 0.0f); if (roiOut != NULL) { CaretArray nodeUsed(numNodes, 0.0f); for (int i = 0; i < mapSize; ++i) { nodeUsed[myMap[i].m_surfaceNode] = 1.0f; } roiOut->setValuesForColumn(0, nodeUsed); } for (int i = 0; i < colSize; ++i) { ciftiIn->getRow(rowScratch, i); for (int j = 0; j < mapSize; ++j) { metricScratch[myMap[j].m_surfaceNode] = rowScratch[myMap[j].m_ciftiIndex]; } metricOut->setValuesForColumn(i, metricScratch); } } } AlgorithmCiftiSeparate::AlgorithmCiftiSeparate(ProgressObject* myProgObj, const CiftiFile* ciftiIn, const int& myDir, const StructureEnum::Enum& myStruct, LabelFile* labelOut, MetricFile* roiOut) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); const CiftiXML& myXML = ciftiIn->getCiftiXML(); if (myXML.getNumberOfDimensions() != 2) throw AlgorithmException("cifti separate only supported on 2D cifti"); if (myDir >= myXML.getNumberOfDimensions() || myDir < 0) throw AlgorithmException("direction invalid for input cifti"); if (myXML.getMappingType(myDir) != CiftiMappingType::BRAIN_MODELS) throw AlgorithmException("specified direction does not contain brain models"); if (myXML.getMappingType(1 - myDir) != CiftiMappingType::LABELS) throw AlgorithmException("label separate requested on non-label cifti"); const CiftiBrainModelsMap& myBrainModelsMap = myXML.getBrainModelsMap(myDir); const CiftiLabelsMap& myLabelsMap = myXML.getLabelsMap(1 - myDir); vector myMap = myBrainModelsMap.getSurfaceMap(myStruct); int64_t rowSize = ciftiIn->getNumberOfColumns(), colSize = ciftiIn->getNumberOfRows(); if (myDir == CiftiXML::ALONG_COLUMN) { int64_t numNodes = myBrainModelsMap.getSurfaceNumberOfNodes(myStruct); labelOut->setNumberOfNodesAndColumns(numNodes, rowSize); labelOut->setStructure(myStruct); const CiftiMappingType& myNamesMap = *(myXML.getMap(1 - myDir)); for (int j = 0; j < rowSize; ++j) { labelOut->setMapName(j, myNamesMap.getIndexName(j)); } if (roiOut != NULL) { roiOut->setNumberOfNodesAndColumns(numNodes, 1); roiOut->setStructure(myStruct); } int64_t mapSize = (int64_t)myMap.size(); CaretArray rowScratch(rowSize); CaretArray nodeUsed(numNodes, 0.0f); GiftiLabelTable myTable; map cumulativeRemap; for (int64_t i = 0; i < rowSize; ++i) { map thisRemap = myTable.append(*(myLabelsMap.getMapLabelTable(i))); cumulativeRemap.insert(thisRemap.begin(), thisRemap.end()); } for (int64_t i = 0; i < mapSize; ++i) { ciftiIn->getRow(rowScratch, myMap[i].m_ciftiIndex); nodeUsed[myMap[i].m_surfaceNode] = 1.0f; for (int j = 0; j < rowSize; ++j) { int32_t inVal = (int32_t)floor(rowScratch[j] + 0.5f); map::const_iterator iter = cumulativeRemap.find(inVal); if (iter == cumulativeRemap.end()) { labelOut->setLabelKey(myMap[i].m_surfaceNode, j, inVal); } else { labelOut->setLabelKey(myMap[i].m_surfaceNode, j, iter->second); } } } *(labelOut->getLabelTable()) = myTable; int32_t unusedLabel = myTable.getUnassignedLabelKey(); if (roiOut != NULL) { roiOut->setValuesForColumn(0, nodeUsed); } for (int i = 0; i < numNodes; ++i)//set unused columns to unassigned { if (nodeUsed[i] == 0.0f) { for (int j = 0; j < rowSize; ++j) { labelOut->setLabelKey(i, j, unusedLabel); } } } } else { int64_t numNodes = myBrainModelsMap.getSurfaceNumberOfNodes(myStruct); labelOut->setNumberOfNodesAndColumns(numNodes, colSize); labelOut->setStructure(myStruct); const CiftiMappingType& myNamesMap = *(myXML.getMap(1 - myDir)); for (int j = 0; j < rowSize; ++j) { labelOut->setMapName(j, myNamesMap.getIndexName(j)); } if (roiOut != NULL) { roiOut->setNumberOfNodesAndColumns(numNodes, 1); roiOut->setStructure(myStruct); } int64_t mapSize = (int)myMap.size(); if (roiOut != NULL) { CaretArray nodeUsed(numNodes, 0.0f); for (int i = 0; i < mapSize; ++i) { nodeUsed[myMap[i].m_surfaceNode] = 1.0f; } roiOut->setValuesForColumn(0, nodeUsed); } CaretArray rowScratch(rowSize); CaretArray nodeUsed(numNodes, 0); for (int64_t j = 0; j < mapSize; ++j) { nodeUsed[myMap[j].m_surfaceNode] = 1; } GiftiLabelTable myTable; map cumulativeRemap; for (int64_t i = 0; i < colSize; ++i) { map thisRemap = myTable.append(*(myLabelsMap.getMapLabelTable(i))); cumulativeRemap.insert(thisRemap.begin(), thisRemap.end()); } *(labelOut->getLabelTable()) = myTable; int32_t unusedLabel = myTable.getUnassignedLabelKey(); for (int64_t i = 0; i < colSize; ++i) { ciftiIn->getRow(rowScratch, i); for (int64_t j = 0; j < mapSize; ++j) { int32_t inVal = (int32_t)floor(rowScratch[j] + 0.5f); map::const_iterator iter = cumulativeRemap.find(inVal); if (iter == cumulativeRemap.end()) { labelOut->setLabelKey(myMap[j].m_surfaceNode, i, inVal); } else { labelOut->setLabelKey(myMap[j].m_surfaceNode, i, iter->second); } } for (int64_t j = 0; j < numNodes; ++j)//set unused columns to unassigned { if (nodeUsed[j] == 0) { labelOut->setLabelKey(j, i, unusedLabel); } } } } } AlgorithmCiftiSeparate::AlgorithmCiftiSeparate(ProgressObject* myProgObj, const CiftiFile* ciftiIn, const int& myDir, const StructureEnum::Enum& myStruct, VolumeFile* volOut, int64_t offsetOut[3], VolumeFile* roiOut, const bool& cropVol) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); const CiftiXML& myXML = ciftiIn->getCiftiXML(); if (myXML.getNumberOfDimensions() != 2) throw AlgorithmException("cifti separate only supported on 2D cifti"); if (myDir >= ciftiIn->getCiftiXML().getNumberOfDimensions() || myDir < 0) throw AlgorithmException("direction invalid for input cifti"); if (myXML.getMappingType(myDir) != CiftiMappingType::BRAIN_MODELS) throw AlgorithmException("specified direction does not contain brain models"); int rowSize = ciftiIn->getNumberOfColumns(), colSize = ciftiIn->getNumberOfRows(); const CiftiBrainModelsMap& myBrainMap = myXML.getBrainModelsMap(myDir); const int64_t* myDims = myBrainMap.getVolumeSpace().getDims(); vector > mySform = myBrainMap.getVolumeSpace().getSform(); vector myMap = myBrainMap.getVolumeStructureMap(myStruct); vector newdims; int64_t numVoxels = (int64_t)myMap.size(); if (cropVol) { newdims.resize(3); getCroppedVolSpace(ciftiIn, myDir, myStruct, newdims.data(), mySform, offsetOut); } else { newdims.push_back(myDims[0]); newdims.push_back(myDims[1]); newdims.push_back(myDims[2]); offsetOut[0] = 0; offsetOut[1] = 0; offsetOut[2] = 0; } if (roiOut != NULL) { roiOut->reinitialize(newdims, mySform); roiOut->setValueAllVoxels(0.0f); } CaretArray rowScratch(rowSize); if (myDir == CiftiXML::ALONG_COLUMN) { if (rowSize > 1) newdims.push_back(rowSize); volOut->reinitialize(newdims, mySform); volOut->setValueAllVoxels(0.0f); const CiftiMappingType& myNamesMap = *(myXML.getMap(1 - myDir)); for (int j = 0; j < rowSize; ++j) { volOut->setMapName(j, myNamesMap.getIndexName(j)); } if (myXML.getMappingType(CiftiXML::ALONG_ROW) == CiftiMappingType::LABELS) { const CiftiLabelsMap& myLabelsMap = myXML.getLabelsMap(CiftiXML::ALONG_ROW); volOut->setType(SubvolumeAttributes::LABEL); for (int j = 0; j < rowSize; ++j) { *(volOut->getMapLabelTable(j)) = *(myLabelsMap.getMapLabelTable(j)); } } for (int64_t i = 0; i < numVoxels; ++i) { int64_t thisvoxel[3] = { myMap[i].m_ijk[0] - offsetOut[0], myMap[i].m_ijk[1] - offsetOut[1], myMap[i].m_ijk[2] - offsetOut[2] }; if (roiOut != NULL) { roiOut->setValue(1.0f, thisvoxel); } ciftiIn->getRow(rowScratch, myMap[i].m_ciftiIndex); for (int j = 0; j < rowSize; ++j) { volOut->setValue(rowScratch[j], thisvoxel, j); } } } else { if (colSize > 1) newdims.push_back(colSize); volOut->reinitialize(newdims, mySform); volOut->setValueAllVoxels(0.0f); const CiftiMappingType& myNamesMap = *(myXML.getMap(1 - myDir)); for (int j = 0; j < rowSize; ++j) { volOut->setMapName(j, myNamesMap.getIndexName(j)); } if (myXML.getMappingType(CiftiXML::ALONG_COLUMN) == CiftiMappingType::LABELS) { const CiftiLabelsMap& myLabelsMap = myXML.getLabelsMap(CiftiXML::ALONG_COLUMN); volOut->setType(SubvolumeAttributes::LABEL); for (int j = 0; j < colSize; ++j) { *(volOut->getMapLabelTable(j)) = *(myLabelsMap.getMapLabelTable(j)); } } for (int64_t i = 0; i < colSize; ++i) { ciftiIn->getRow(rowScratch, i); for (int64_t j = 0; j < numVoxels; ++j) { int64_t thisvoxel[3] = { myMap[j].m_ijk[0] - offsetOut[0], myMap[j].m_ijk[1] - offsetOut[1], myMap[j].m_ijk[2] - offsetOut[2] }; if (i == 0 && roiOut != NULL) { roiOut->setValue(1.0f, thisvoxel); } volOut->setValue(rowScratch[myMap[j].m_ciftiIndex], thisvoxel, i); } } } } AlgorithmCiftiSeparate::AlgorithmCiftiSeparate(ProgressObject* myProgObj, const CiftiFile* ciftiIn, const int& myDir, VolumeFile* volOut, int64_t offsetOut[3], VolumeFile* roiOut, const bool& cropVol, VolumeFile* labelOut): AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); const CiftiXML& myXML = ciftiIn->getCiftiXML(); if (myXML.getNumberOfDimensions() != 2) throw AlgorithmException("cifti separate only supported on 2D cifti"); if (myDir >= ciftiIn->getCiftiXML().getNumberOfDimensions() || myDir < 0) throw AlgorithmException("direction invalid for input cifti"); if (myXML.getMappingType(myDir) != CiftiMappingType::BRAIN_MODELS) throw AlgorithmException("specified direction does not contain brain models"); int rowSize = ciftiIn->getNumberOfColumns(), colSize = ciftiIn->getNumberOfRows(); const CiftiBrainModelsMap& myBrainMap = myXML.getBrainModelsMap(myDir); const int64_t* myDims = myBrainMap.getVolumeSpace().getDims(); vector > mySform = myBrainMap.getVolumeSpace().getSform(); vector newdims; if (cropVol) { newdims.resize(3); getCroppedVolSpaceAll(ciftiIn, myDir, newdims.data(), mySform, offsetOut); } else { newdims.push_back(myDims[0]); newdims.push_back(myDims[1]); newdims.push_back(myDims[2]); offsetOut[0] = 0; offsetOut[1] = 0; offsetOut[2] = 0; } if (labelOut != NULL) { labelOut->reinitialize(newdims, mySform, 1, SubvolumeAttributes::LABEL); labelOut->setValueAllVoxels(0.0f);//unlabeled key defaults to 0 vector volStructs = myBrainMap.getVolumeStructureList(); GiftiLabelTable structureTable; for (int i = 0; i < (int)volStructs.size(); ++i) { const int32_t structKey = structureTable.addLabel(StructureEnum::toName(volStructs[i]), rand() & 255, rand() & 255, rand() & 255, 255); const vector& voxelList = myBrainMap.getVoxelList(volStructs[i]); int64_t structVoxels = (int64_t)voxelList.size(); for (int64_t j = 0; j < structVoxels; j += 3) { labelOut->setValue(structKey, voxelList[j] - offsetOut[0], voxelList[j + 1] - offsetOut[1], voxelList[j + 2] - offsetOut[2]); } } *(labelOut->getMapLabelTable(0)) = structureTable; } if (roiOut != NULL) { roiOut->reinitialize(newdims, mySform); roiOut->setValueAllVoxels(0.0f); } vector myMap = myBrainMap.getFullVolumeMap(); int64_t numVoxels = (int64_t)myMap.size(); CaretArray rowScratch(rowSize); if (myDir == CiftiXML::ALONG_COLUMN) { if (rowSize > 1) newdims.push_back(rowSize); volOut->reinitialize(newdims, mySform); volOut->setValueAllVoxels(0.0f); const CiftiMappingType& myNamesMap = *(myXML.getMap(1 - myDir)); for (int j = 0; j < rowSize; ++j) { volOut->setMapName(j, myNamesMap.getIndexName(j)); } if (myXML.getMappingType(CiftiXML::ALONG_ROW) == CiftiMappingType::LABELS) { const CiftiLabelsMap& myLabelsMap = myXML.getLabelsMap(CiftiXML::ALONG_ROW); volOut->setType(SubvolumeAttributes::LABEL); for (int j = 0; j < rowSize; ++j) { *(volOut->getMapLabelTable(j)) = *(myLabelsMap.getMapLabelTable(j)); } } for (int64_t i = 0; i < numVoxels; ++i) { int64_t thisvoxel[3] = { myMap[i].m_ijk[0] - offsetOut[0], myMap[i].m_ijk[1] - offsetOut[1], myMap[i].m_ijk[2] - offsetOut[2] }; if (roiOut != NULL) { roiOut->setValue(1.0f, thisvoxel); } ciftiIn->getRow(rowScratch, myMap[i].m_ciftiIndex); for (int j = 0; j < rowSize; ++j) { volOut->setValue(rowScratch[j], thisvoxel, j); } } } else { if (colSize > 1) newdims.push_back(colSize); volOut->reinitialize(newdims, mySform); volOut->setValueAllVoxels(0.0f); const CiftiMappingType& myNamesMap = *(myXML.getMap(1 - myDir)); for (int j = 0; j < rowSize; ++j) { volOut->setMapName(j, myNamesMap.getIndexName(j)); } if (myXML.getMappingType(CiftiXML::ALONG_COLUMN) == CiftiMappingType::LABELS) { const CiftiLabelsMap& myLabelsMap = myXML.getLabelsMap(CiftiXML::ALONG_COLUMN); volOut->setType(SubvolumeAttributes::LABEL); for (int j = 0; j < colSize; ++j) { *(volOut->getMapLabelTable(j)) = *(myLabelsMap.getMapLabelTable(j)); } } for (int64_t i = 0; i < colSize; ++i) { ciftiIn->getRow(rowScratch, i); for (int64_t j = 0; j < numVoxels; ++j) { int64_t thisvoxel[3] = { myMap[j].m_ijk[0] - offsetOut[0], myMap[j].m_ijk[1] - offsetOut[1], myMap[j].m_ijk[2] - offsetOut[2] }; if (i == 0 && roiOut != NULL) { roiOut->setValue(1.0f, thisvoxel); } volOut->setValue(rowScratch[myMap[j].m_ciftiIndex], thisvoxel, i); } } } } void AlgorithmCiftiSeparate::getCroppedVolSpace(const CiftiFile* ciftiIn, const int& myDir, const StructureEnum::Enum& myStruct, int64_t dimsOut[3], vector >& sformOut, int64_t offsetOut[3]) { const CiftiXML& myXML = ciftiIn->getCiftiXML(); if (myXML.getMappingType(myDir) != CiftiMappingType::BRAIN_MODELS) throw AlgorithmException("specified direction does not contain brain models"); const CiftiBrainModelsMap& myBrainMap = myXML.getBrainModelsMap(myDir); sformOut = myBrainMap.getVolumeSpace().getSform(); vector myMap = myBrainMap.getVolumeStructureMap(myStruct); int64_t numVoxels = (int64_t)myMap.size(); if (numVoxels > 0) {//make a voxel bounding box to minimize memory usage int extrema[6] = { myMap[0].m_ijk[0], myMap[0].m_ijk[0], myMap[0].m_ijk[1], myMap[0].m_ijk[1], myMap[0].m_ijk[2], myMap[0].m_ijk[2] }; for (int64_t i = 1; i < numVoxels; ++i) { if (myMap[i].m_ijk[0] < extrema[0]) extrema[0] = myMap[i].m_ijk[0]; if (myMap[i].m_ijk[0] > extrema[1]) extrema[1] = myMap[i].m_ijk[0]; if (myMap[i].m_ijk[1] < extrema[2]) extrema[2] = myMap[i].m_ijk[1]; if (myMap[i].m_ijk[1] > extrema[3]) extrema[3] = myMap[i].m_ijk[1]; if (myMap[i].m_ijk[2] < extrema[4]) extrema[4] = myMap[i].m_ijk[2]; if (myMap[i].m_ijk[2] > extrema[5]) extrema[5] = myMap[i].m_ijk[2]; } dimsOut[0] = extrema[1] - extrema[0] + 1; dimsOut[1] = extrema[3] - extrema[2] + 1; dimsOut[2] = extrema[5] - extrema[4] + 1; offsetOut[0] = extrema[0]; offsetOut[1] = extrema[2]; offsetOut[2] = extrema[4]; Vector3D ivec, jvec, kvec, shift; ivec[0] = sformOut[0][0]; ivec[1] = sformOut[1][0]; ivec[2] = sformOut[2][0]; jvec[0] = sformOut[0][1]; jvec[1] = sformOut[1][1]; jvec[2] = sformOut[2][1]; kvec[0] = sformOut[0][2]; kvec[1] = sformOut[1][2]; kvec[2] = sformOut[2][2]; shift = offsetOut[0] * ivec + offsetOut[1] * jvec + offsetOut[2] * kvec; sformOut[0][3] += shift[0];//fix the sform to align to the old position with the new dimensions sformOut[1][3] += shift[1]; sformOut[2][3] += shift[2]; } else { throw AlgorithmException("cropped volume requested, but no voxels exist in this structure"); } } void AlgorithmCiftiSeparate::getCroppedVolSpaceAll(const CiftiFile* ciftiIn, const int& myDir, int64_t dimsOut[3], vector >& sformOut, int64_t offsetOut[3]) { const CiftiXML& myXML = ciftiIn->getCiftiXML(); if (myXML.getMappingType(myDir) != CiftiMappingType::BRAIN_MODELS) throw AlgorithmException("specified direction does not contain brain models"); const CiftiBrainModelsMap& myBrainMap = myXML.getBrainModelsMap(myDir); sformOut = myBrainMap.getVolumeSpace().getSform(); vector myMap = myBrainMap.getFullVolumeMap(); int64_t numVoxels = (int64_t)myMap.size(); if (numVoxels > 0) {//make a voxel bounding box to minimize memory usage int extrema[6] = { myMap[0].m_ijk[0], myMap[0].m_ijk[0], myMap[0].m_ijk[1], myMap[0].m_ijk[1], myMap[0].m_ijk[2], myMap[0].m_ijk[2] }; for (int64_t i = 1; i < numVoxels; ++i) { if (myMap[i].m_ijk[0] < extrema[0]) extrema[0] = myMap[i].m_ijk[0]; if (myMap[i].m_ijk[0] > extrema[1]) extrema[1] = myMap[i].m_ijk[0]; if (myMap[i].m_ijk[1] < extrema[2]) extrema[2] = myMap[i].m_ijk[1]; if (myMap[i].m_ijk[1] > extrema[3]) extrema[3] = myMap[i].m_ijk[1]; if (myMap[i].m_ijk[2] < extrema[4]) extrema[4] = myMap[i].m_ijk[2]; if (myMap[i].m_ijk[2] > extrema[5]) extrema[5] = myMap[i].m_ijk[2]; } dimsOut[0] = extrema[1] - extrema[0] + 1; dimsOut[1] = extrema[3] - extrema[2] + 1; dimsOut[2] = extrema[5] - extrema[4] + 1; offsetOut[0] = extrema[0]; offsetOut[1] = extrema[2]; offsetOut[2] = extrema[4]; Vector3D ivec, jvec, kvec, shift; ivec[0] = sformOut[0][0]; ivec[1] = sformOut[1][0]; ivec[2] = sformOut[2][0]; jvec[0] = sformOut[0][1]; jvec[1] = sformOut[1][1]; jvec[2] = sformOut[2][1]; kvec[0] = sformOut[0][2]; kvec[1] = sformOut[1][2]; kvec[2] = sformOut[2][2]; shift = offsetOut[0] * ivec + offsetOut[1] * jvec + offsetOut[2] * kvec; sformOut[0][3] += shift[0];//fix the sform to align to the old position with the new dimensions sformOut[1][3] += shift[1]; sformOut[2][3] += shift[2]; } else { throw AlgorithmException("cropped volume requested, but no voxels exist in the specified direction"); } } float AlgorithmCiftiSeparate::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmCiftiSeparate::getSubAlgorithmWeight() { return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmCiftiSeparate.h000066400000000000000000000060421255417355300231530ustar00rootroot00000000000000#ifndef __ALGORITHM_CIFTI_SEPARATE_H__ #define __ALGORITHM_CIFTI_SEPARATE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" #include "StructureEnum.h" #include namespace caret { class AlgorithmCiftiSeparate : public AbstractAlgorithm { AlgorithmCiftiSeparate(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmCiftiSeparate(ProgressObject* myProgObj, const CiftiFile* ciftiIn, const int& myDir, const StructureEnum::Enum& myStruct, MetricFile* metricOut, MetricFile* roiOut = NULL); AlgorithmCiftiSeparate(ProgressObject* myProgObj, const CiftiFile* ciftiIn, const int& myDir, const StructureEnum::Enum& myStruct, LabelFile* labelOut, MetricFile* roiOut = NULL); AlgorithmCiftiSeparate(ProgressObject* myProgObj, const CiftiFile* ciftiIn, const int& myDir, const StructureEnum::Enum& myStruct, VolumeFile* volOut, int64_t offsetOut[3], VolumeFile* roiOut = NULL, const bool& cropVol = true); AlgorithmCiftiSeparate(ProgressObject* myProgObj, const CiftiFile* ciftiIn, const int& myDir, VolumeFile* volOut, int64_t offsetOut[3], VolumeFile* roiOut = NULL, const bool& cropVol = true, VolumeFile* labelOut = NULL); static void getCroppedVolSpace(const CiftiFile* ciftiIn, const int& myDir, const StructureEnum::Enum& myStruct, int64_t dimsOut[3], std::vector >& sformOut, int64_t offsetOut[3]); static void getCroppedVolSpaceAll(const CiftiFile* ciftiIn, const int& myDir, int64_t dimsOut[3], std::vector >& sformOut, int64_t offsetOut[3]); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmCiftiSeparate; } #endif //__ALGORITHM_CIFTI_SEPARATE_H__ workbench-1.1.1/src/Algorithms/AlgorithmCiftiSmoothing.cxx000066400000000000000000000342631255417355300237370ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmCiftiSmoothing.h" #include "AlgorithmException.h" #include "AlgorithmMetricSmoothing.h" #include "AlgorithmVolumeSmoothing.h" #include "CiftiFile.h" #include "MetricFile.h" #include "VolumeFile.h" #include "SurfaceFile.h" #include "AlgorithmCiftiSeparate.h" #include "AlgorithmCiftiReplaceStructure.h" using namespace caret; using namespace std; AString AlgorithmCiftiSmoothing::getCommandSwitch() { return "-cifti-smoothing"; } AString AlgorithmCiftiSmoothing::getShortDescription() { return "SMOOTH A CIFTI FILE"; } OperationParameters* AlgorithmCiftiSmoothing::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiParameter(1, "cifti", "the input cifti"); ret->addDoubleParameter(2, "surface-kernel", "the sigma for the gaussian surface smoothing kernel, in mm"); ret->addDoubleParameter(3, "volume-kernel", "the sigma for the gaussian volume smoothing kernel, in mm"); ret->addStringParameter(4, "direction", "which dimension to smooth along, ROW or COLUMN"); ret->addCiftiOutputParameter(5, "cifti-out", "the output cifti"); OptionalParameter* leftSurfOpt = ret->createOptionalParameter(6, "-left-surface", "specify the left surface to use"); leftSurfOpt->addSurfaceParameter(1, "surface", "the left surface file"); OptionalParameter* leftCorrAreasOpt = leftSurfOpt->createOptionalParameter(2, "-left-corrected-areas", "vertex areas to use instead of computing them from the left surface"); leftCorrAreasOpt->addMetricParameter(1, "area-metric", "the corrected vertex areas, as a metric"); OptionalParameter* rightSurfOpt = ret->createOptionalParameter(7, "-right-surface", "specify the right surface to use"); rightSurfOpt->addSurfaceParameter(1, "surface", "the right surface file"); OptionalParameter* rightCorrAreasOpt = rightSurfOpt->createOptionalParameter(2, "-right-corrected-areas", "vertex areas to use instead of computing them from the right surface"); rightCorrAreasOpt->addMetricParameter(1, "area-metric", "the corrected vertex areas, as a metric"); OptionalParameter* cerebSurfOpt = ret->createOptionalParameter(8, "-cerebellum-surface", "specify the cerebellum surface to use"); cerebSurfOpt->addSurfaceParameter(1, "surface", "the cerebellum surface file"); OptionalParameter* cerebCorrAreasOpt = cerebSurfOpt->createOptionalParameter(2, "-cerebellum-corrected-areas", "vertex areas to use instead of computing them from the cerebellum surface"); cerebCorrAreasOpt->addMetricParameter(1, "area-metric", "the corrected vertex areas, as a metric"); OptionalParameter* roiOpt = ret->createOptionalParameter(9, "-cifti-roi", "smooth only within regions of interest"); roiOpt->addCiftiParameter(1, "roi-cifti", "the regions to smooth within, as a cifti file"); ret->createOptionalParameter(10, "-fix-zeros-volume", "treat values of zero in the volume as missing data"); ret->createOptionalParameter(11, "-fix-zeros-surface", "treat values of zero on the surface as missing data"); ret->createOptionalParameter(12, "-merged-volume", "smooth across subcortical structure boundaries"); ret->setHelpText( AString("The input cifti file must have a brain models mapping on the chosen dimension, columns for .dtseries, and either for .dconn. ") + "By default, data in different structures is smoothed independently (i.e., \"parcel constrained\" smoothing), so volume structures that touch do not smooth across this boundary. " + "Specify -merged-volume to ignore these boundaries. " + "Surface smoothing uses the GEO_GAUSS_AREA smoothing method.\n\n" + "The -*-corrected-areas options are intended for when it is unavoidable to smooth on group average surfaces, it is only an approximate correction " + "for the reduction of structure in a group average surface. It is better to smooth the data on individuals before averaging, when feasible.\n\n" + "The -fix-zeros-* options will treat values of zero as lack of data, and not use that value when generating the smoothed values, but will fill zeros with extrapolated values. " + "The ROI should have a brain models mapping along columns, exactly matching the mapping of the chosen direction in the input file. " + "Data outside the ROI is ignored." ); return ret; } void AlgorithmCiftiSmoothing::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { CiftiFile* myCifti = myParams->getCifti(1); float surfKern = (float)myParams->getDouble(2); float volKern = (float)myParams->getDouble(3); AString directionName = myParams->getString(4); int myDir; if (directionName == "ROW") { myDir = CiftiXMLOld::ALONG_ROW; } else if (directionName == "COLUMN") { myDir = CiftiXMLOld::ALONG_COLUMN; } else { throw AlgorithmException("incorrect string for direction, use ROW or COLUMN"); } CiftiFile* myCiftiOut = myParams->getOutputCifti(5); SurfaceFile* myLeftSurf = NULL, *myRightSurf = NULL, *myCerebSurf = NULL; MetricFile* myLeftAreas = NULL, *myRightAreas = NULL, *myCerebAreas = NULL; OptionalParameter* leftSurfOpt = myParams->getOptionalParameter(6); if (leftSurfOpt->m_present) { myLeftSurf = leftSurfOpt->getSurface(1); OptionalParameter* leftCorrAreasOpt = leftSurfOpt->getOptionalParameter(2); if (leftCorrAreasOpt->m_present) { myLeftAreas = leftCorrAreasOpt->getMetric(1); } } OptionalParameter* rightSurfOpt = myParams->getOptionalParameter(7); if (rightSurfOpt->m_present) { myRightSurf = rightSurfOpt->getSurface(1); OptionalParameter* rightCorrAreasOpt = rightSurfOpt->getOptionalParameter(2); if (rightCorrAreasOpt->m_present) { myRightAreas = rightCorrAreasOpt->getMetric(1); } } OptionalParameter* cerebSurfOpt = myParams->getOptionalParameter(8); if (cerebSurfOpt->m_present) { myCerebSurf = cerebSurfOpt->getSurface(1); OptionalParameter* cerebCorrAreasOpt = cerebSurfOpt->getOptionalParameter(2); if (cerebCorrAreasOpt->m_present) { myCerebAreas = cerebCorrAreasOpt->getMetric(1); } } CiftiFile* roiCifti = NULL; OptionalParameter* roiOpt = myParams->getOptionalParameter(9); if (roiOpt->m_present) { roiCifti = roiOpt->getCifti(1); } bool fixZerosVol = myParams->getOptionalParameter(10)->m_present; bool fixZerosSurf = myParams->getOptionalParameter(11)->m_present; bool mergedVolume = myParams->getOptionalParameter(12)->m_present; AlgorithmCiftiSmoothing(myProgObj, myCifti, surfKern, volKern, myDir, myCiftiOut, myLeftSurf, myRightSurf, myCerebSurf, roiCifti, fixZerosVol, fixZerosSurf, myLeftAreas, myRightAreas, myCerebAreas, mergedVolume); } AlgorithmCiftiSmoothing::AlgorithmCiftiSmoothing(ProgressObject* myProgObj, const CiftiFile* myCifti, const float& surfKern, const float& volKern, const int& myDir, CiftiFile* myCiftiOut, const SurfaceFile* myLeftSurf, const SurfaceFile* myRightSurf, const SurfaceFile* myCerebSurf, const CiftiFile* roiCifti, bool fixZerosVol, bool fixZerosSurf, const MetricFile* myLeftAreas, const MetricFile* myRightAreas, const MetricFile* myCerebAreas, const bool& mergedVolume) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); const CiftiXMLOld& myXML = myCifti->getCiftiXMLOld(); vector surfaceList, volumeList; if (myDir == CiftiXMLOld::ALONG_COLUMN) { if (!myXML.getStructureListsForColumns(surfaceList, volumeList)) { throw AlgorithmException("specified direction does not contain brainordinates"); } } else { if (myDir != CiftiXMLOld::ALONG_ROW) throw AlgorithmException("direction not supported in AlgorithmCiftiSmoothing"); if (!myXML.getStructureListsForRows(surfaceList, volumeList)) { throw AlgorithmException("specified direction does not contain brainordinates"); } } if (roiCifti != NULL && !myXML.mappingMatches(myDir, roiCifti->getCiftiXMLOld(), CiftiXMLOld::ALONG_COLUMN)) { throw AlgorithmException("along-column mapping of roi cifti does not match the smoothing direction of the input cifti"); } for (int whichStruct = 0; whichStruct < (int)surfaceList.size(); ++whichStruct) {//sanity check surfaces const SurfaceFile* mySurf = NULL; const MetricFile* myAreas = NULL; AString surfType; switch (surfaceList[whichStruct]) { case StructureEnum::CORTEX_LEFT: mySurf = myLeftSurf; myAreas = myLeftAreas; surfType = "left"; break; case StructureEnum::CORTEX_RIGHT: mySurf = myRightSurf; myAreas = myRightAreas; surfType = "right"; break; case StructureEnum::CEREBELLUM: mySurf = myCerebSurf; myAreas = myCerebAreas; surfType = "cerebellum"; break; default: throw AlgorithmException("found surface model with incorrect type: " + StructureEnum::toName(surfaceList[whichStruct])); break; } if (mySurf == NULL) { throw AlgorithmException(surfType + " surface required but not provided"); } if (myDir == CiftiXMLOld::ALONG_COLUMN) { if (mySurf->getNumberOfNodes() != myXML.getColumnSurfaceNumberOfNodes(surfaceList[whichStruct])) { throw AlgorithmException(surfType + " surface has the wrong number of vertices"); } } else { if (mySurf->getNumberOfNodes() != myXML.getRowSurfaceNumberOfNodes(surfaceList[whichStruct])) { throw AlgorithmException(surfType + " surface has the wrong number of vertices"); } } if (myAreas != NULL && myAreas->getNumberOfNodes() != mySurf->getNumberOfNodes()) { throw AlgorithmException(surfType + " surface and vertex area metric have different number of vertices"); } } myCiftiOut->setCiftiXML(myXML); for (int whichStruct = 0; whichStruct < (int)surfaceList.size(); ++whichStruct) { const SurfaceFile* mySurf = NULL; const MetricFile* myAreas = NULL; switch (surfaceList[whichStruct]) { case StructureEnum::CORTEX_LEFT: mySurf = myLeftSurf; myAreas = myLeftAreas; break; case StructureEnum::CORTEX_RIGHT: mySurf = myRightSurf; myAreas = myRightAreas; break; case StructureEnum::CEREBELLUM: mySurf = myCerebSurf; myAreas = myCerebAreas; break; default: break; } MetricFile myMetric, myRoi, myMetricOut; AlgorithmCiftiSeparate(NULL, myCifti, myDir, surfaceList[whichStruct], &myMetric, &myRoi); if (roiCifti != NULL) {//due to above testing, we know the structure mask is the same, so just overwrite the ROI from the mask AlgorithmCiftiSeparate(NULL, roiCifti, CiftiXMLOld::ALONG_COLUMN, surfaceList[whichStruct], &myRoi); } AlgorithmMetricSmoothing(NULL, mySurf, &myMetric, surfKern, &myMetricOut, &myRoi, false, fixZerosSurf, -1, myAreas); AlgorithmCiftiReplaceStructure(NULL, myCiftiOut, myDir, surfaceList[whichStruct], &myMetricOut); } if (mergedVolume) { VolumeFile myVol, myRoi, myVolOut; int64_t offset[3]; AlgorithmCiftiSeparate(NULL, myCifti, myDir, &myVol, offset, &myRoi, true); if (roiCifti != NULL) {//due to above testing, we know the structure mask is the same, so just overwrite the ROI from the mask AlgorithmCiftiSeparate(NULL, roiCifti, CiftiXMLOld::ALONG_COLUMN, &myRoi, offset, NULL, true); } AlgorithmVolumeSmoothing(NULL, &myVol, volKern, &myVolOut, &myRoi, fixZerosVol); AlgorithmCiftiReplaceStructure(NULL, myCiftiOut, myDir, &myVolOut, true); } else { for (int whichStruct = 0; whichStruct < (int)volumeList.size(); ++whichStruct) { VolumeFile myVol, myRoi, myVolOut; int64_t offset[3]; AlgorithmCiftiSeparate(NULL, myCifti, myDir, volumeList[whichStruct], &myVol, offset, &myRoi, true); if (roiCifti != NULL) {//due to above testing, we know the structure mask is the same, so just overwrite the ROI from the mask AlgorithmCiftiSeparate(NULL, roiCifti, CiftiXMLOld::ALONG_COLUMN, volumeList[whichStruct], &myRoi, offset, NULL, true); } AlgorithmVolumeSmoothing(NULL, &myVol, volKern, &myVolOut, &myRoi, fixZerosVol); AlgorithmCiftiReplaceStructure(NULL, myCiftiOut, myDir, volumeList[whichStruct], &myVolOut, true); } } } float AlgorithmCiftiSmoothing::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmCiftiSmoothing::getSubAlgorithmWeight() { return 0.0f;//if you use a subalgorithm } workbench-1.1.1/src/Algorithms/AlgorithmCiftiSmoothing.h000066400000000000000000000042211255417355300233530ustar00rootroot00000000000000#ifndef __ALGORITHM_CIFTI_SMOOTHING_H__ #define __ALGORITHM_CIFTI_SMOOTHING_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmCiftiSmoothing : public AbstractAlgorithm { AlgorithmCiftiSmoothing(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmCiftiSmoothing(ProgressObject* myProgObj, const CiftiFile* myCifti, const float& surfKern, const float& volKern, const int& myDir, CiftiFile* myCiftiOut, const SurfaceFile* myLeftSurf = NULL, const SurfaceFile* myRightSurf = NULL, const SurfaceFile* myCerebSurf = NULL, const CiftiFile* roiCifti = NULL, bool fixZerosVol = false, bool fixZerosSurf = false, const MetricFile* myLeftAreas = NULL, const MetricFile* myRightAreas = NULL, const MetricFile* myCerebAreas = NULL, const bool& mergedVolume = false); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmCiftiSmoothing; } #endif //__ALGORITHM_CIFTI_SMOOTHING_H__ workbench-1.1.1/src/Algorithms/AlgorithmCiftiTranspose.cxx000066400000000000000000000105231255417355300237370ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmCiftiTranspose.h" #include "AlgorithmException.h" #include "CiftiFile.h" using namespace caret; using namespace std; AString AlgorithmCiftiTranspose::getCommandSwitch() { return "-cifti-transpose"; } AString AlgorithmCiftiTranspose::getShortDescription() { return "TRANSPOSE A CIFTI FILE"; } OperationParameters* AlgorithmCiftiTranspose::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiParameter(1, "cifti-in", "the input cifti file"); ret->addCiftiOutputParameter(2, "cifti-out", "the output cifti file"); OptionalParameter* memLimitOpt = ret->createOptionalParameter(3, "-mem-limit", "restrict memory usage"); memLimitOpt->addDoubleParameter(1, "limit-GB", "memory limit in gigabytes"); ret->setHelpText( AString("The input must be a 2-dimensional cifti file. ") + "The output is a cifti file where every row in the input is a column in the output." ); return ret; } void AlgorithmCiftiTranspose::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { CiftiFile* ciftiIn = myParams->getCifti(1); CiftiFile* ciftiOut = myParams->getOutputCifti(2); OptionalParameter* memLimitOpt = myParams->getOptionalParameter(3); float memLimitGB = -1.0f; if (memLimitOpt->m_present) { memLimitGB = (float)memLimitOpt->getDouble(1); if (memLimitGB < 0.0f) { throw AlgorithmException("memory limit cannot be negative"); } } AlgorithmCiftiTranspose(myProgObj, ciftiIn, ciftiOut, memLimitGB); } AlgorithmCiftiTranspose::AlgorithmCiftiTranspose(ProgressObject* myProgObj, const CiftiFile* ciftiIn, CiftiFile* ciftiOut, const float& memLimitGB) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); const CiftiXML& inXML = ciftiIn->getCiftiXML(); if (inXML.getNumberOfDimensions() != 2) { throw AlgorithmException("cifti transpose only supports 2D cifti"); } CiftiXML outXML; outXML.setNumberOfDimensions(2); outXML.setMap(0, *(inXML.getMap(1))); outXML.setMap(1, *(inXML.getMap(0))); ciftiOut->setCiftiXML(outXML); int rowSize = outXML.getDimensionLength(CiftiXML::ALONG_ROW), colSize = outXML.getDimensionLength(CiftiXML::ALONG_COLUMN); int64_t outRowBytes = rowSize * sizeof(float); int numCacheRows = colSize; if (memLimitGB >= 0.0f) { numCacheRows = memLimitGB * 1024 * 1024 * 1024 / outRowBytes; if (numCacheRows < 1) numCacheRows = 1; if (numCacheRows > colSize) numCacheRows = colSize; } vector > cacheRows(numCacheRows, vector(rowSize)); vector scratchInRow(colSize); for (int i = 0; i < colSize; i += numCacheRows)//loop through cache chunks { int end = i + numCacheRows; if (end > colSize) end = colSize; for (int j = 0; j < rowSize; ++j)//loop through all input rows { ciftiIn->getRow(scratchInRow.data(), j); for (int k = i; k < end; ++k) { cacheRows[k - i][j] = scratchInRow[k]; } } for (int k = i; k < end; ++k) { ciftiOut->setRow(cacheRows[k - i].data(), k); } } } float AlgorithmCiftiTranspose::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmCiftiTranspose::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmCiftiTranspose.h000066400000000000000000000032641255417355300233700ustar00rootroot00000000000000#ifndef __ALGORITHM_CIFTI_TRANSPOSE_H__ #define __ALGORITHM_CIFTI_TRANSPOSE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmCiftiTranspose : public AbstractAlgorithm { AlgorithmCiftiTranspose(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmCiftiTranspose(ProgressObject* myProgObj, const CiftiFile* ciftiIn, CiftiFile* ciftiOut, const float& memLimitGB = -1.0f); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmCiftiTranspose; } #endif //__ALGORITHM_CIFTI_TRANSPOSE_H__ workbench-1.1.1/src/Algorithms/AlgorithmCiftiVectorOperation.cxx000066400000000000000000000172171255417355300251130ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmCiftiVectorOperation.h" #include "AlgorithmException.h" #include "CiftiFile.h" using namespace caret; using namespace std; AString AlgorithmCiftiVectorOperation::getCommandSwitch() { return "-cifti-vector-operation"; } AString AlgorithmCiftiVectorOperation::getShortDescription() { return "DO A VECTOR OPERATION ON CIFTI FILES"; } OperationParameters* AlgorithmCiftiVectorOperation::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiParameter(1, "vectors-a", "first vector input file"); ret->addCiftiParameter(2, "vectors-b", "second vector input file"); ret->addStringParameter(3, "operation", "what vector operation to do"); ret->addCiftiOutputParameter(4, "cifti-out", "the output file"); ret->createOptionalParameter(5, "-normalize-a", "normalize vectors of first input"); ret->createOptionalParameter(6, "-normalize-b", "normalize vectors of second input"); ret->createOptionalParameter(7, "-normalize-output", "normalize output vectors (not valid for dot product)"); ret->createOptionalParameter(8, "-magnitude", "output the magnitude of the result (not valid for dot product)"); AString myText = AString("Does a vector operation on two cifti files (that must have a multiple of 3 columns). ") + "Either of the inputs may have multiple vectors (more than 3 columns), but not both (at least one must have exactly 3 columns). " + "The -magnitude and -normalize-output options may not be specified together, or with an operation that returns a scalar (dot product). " + "The parameter must be one of the following:\n"; vector opList = VectorOperation::getAllOperations(); for (int i = 0; i < (int)opList.size(); ++i) { myText += "\n" + VectorOperation::operationToString(opList[i]); } ret->setHelpText(myText); return ret; } void AlgorithmCiftiVectorOperation::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { CiftiFile* ciftiA = myParams->getCifti(1); CiftiFile* ciftiB = myParams->getCifti(2); AString operString = myParams->getString(3); bool ok = false; VectorOperation::Operation myOper = VectorOperation::stringToOperation(operString, ok); if (!ok) throw AlgorithmException("unrecognized operation string: " + operString); CiftiFile* myCiftiOut = myParams->getOutputCifti(4); bool normA = myParams->getOptionalParameter(5)->m_present; bool normB = myParams->getOptionalParameter(6)->m_present; bool normOut = myParams->getOptionalParameter(7)->m_present; bool magOut = myParams->getOptionalParameter(8)->m_present; AlgorithmCiftiVectorOperation(myProgObj, ciftiA, ciftiB, myOper, myCiftiOut, normA, normB, normOut, magOut); } AlgorithmCiftiVectorOperation::AlgorithmCiftiVectorOperation(ProgressObject* myProgObj, const CiftiFile* ciftiA, const CiftiFile* ciftiB, const VectorOperation::Operation& myOper, CiftiFile* myCiftiOut, const bool& normA, const bool& normB, const bool& normOut, const bool& magOut) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); const CiftiXML& xmlA = ciftiA->getCiftiXML(), &xmlB = ciftiB->getCiftiXML(); if (xmlA.getNumberOfDimensions() != 2 || xmlB.getNumberOfDimensions() != 2) { throw AlgorithmException("cifti vector operation only supports 2D cifti"); } if (!xmlA.getMap(CiftiXML::ALONG_COLUMN)->approximateMatch(*xmlB.getMap(CiftiXML::ALONG_COLUMN))) { throw AlgorithmException("input cifti files have non-matching mappings along column"); } int64_t numColA = xmlA.getDimensionLength(CiftiXML::ALONG_ROW), numColB = xmlB.getDimensionLength(CiftiXML::ALONG_ROW); if (numColA % 3 != 0) throw AlgorithmException("number of columns of first input is not a multiple of 3"); if (numColB % 3 != 0) throw AlgorithmException("number of columns of second input is not a multiple of 3"); int numVecA = numColA / 3, numVecB = numColB / 3; if (numVecA > 1 && numVecB > 1) throw AlgorithmException("both inputs have more than 3 columns (more than 1 vector)"); if (normOut && magOut) throw AlgorithmException("normalizing the output and taking the magnitude is meaningless"); bool opScalarResult = VectorOperation::operationReturnsScalar(myOper); if (opScalarResult && (normOut || magOut)) throw AlgorithmException("cannot normalize or take magnitude of a scalar result (such as a dot product)"); bool swapped = false; const CiftiFile* multiVec = ciftiA, *singleVec = ciftiB; int numOutVecs = numVecA; if (numVecB > 1) { multiVec = ciftiB; singleVec = ciftiA; numOutVecs = numVecB; swapped = true; } CiftiXML outXML = multiVec->getCiftiXML(); int numColsOut = numOutVecs * 3; if (opScalarResult || magOut) { numColsOut = numOutVecs; CiftiScalarsMap outRowMap; outRowMap.setLength(numColsOut); outXML.setMap(CiftiXML::ALONG_ROW, outRowMap); } myCiftiOut->setCiftiXML(outXML); vector outRow(numColsOut), multiRow(numOutVecs * 3); int64_t numRows = xmlA.getDimensionLength(CiftiXML::ALONG_COLUMN); for (int64_t row = 0; row < numRows; ++row) { multiVec->getRow(multiRow.data(), row); Vector3D vecSingle; singleVec->getRow(vecSingle, row); for (int64_t v = 0; v < numOutVecs; ++v) { Vector3D vecA, vecB; if (swapped) { vecA = multiRow.data() + v * 3; vecB = vecSingle; } else { vecA = vecSingle; vecB = multiRow.data() + v * 3; } if (normA) vecA = vecA.normal(); if (normB) vecB = vecB.normal(); if (opScalarResult) { outRow[v] = VectorOperation::doScalarOperation(vecA, vecB, myOper); } else { Vector3D tempVec = VectorOperation::doVectorOperation(vecA, vecB, myOper); if (normOut) tempVec = tempVec.normal(); if (magOut) { outRow[v] = tempVec.length(); } else { outRow[v * 3] = tempVec[0]; outRow[v * 3 + 1] = tempVec[1]; outRow[v * 3 + 2] = tempVec[2]; } } } myCiftiOut->setRow(outRow.data(), row); } } float AlgorithmCiftiVectorOperation::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmCiftiVectorOperation::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmCiftiVectorOperation.h000066400000000000000000000037431255417355300245370ustar00rootroot00000000000000#ifndef __ALGORITHM_CIFTI_VECTOR_OPERATION_H__ #define __ALGORITHM_CIFTI_VECTOR_OPERATION_H__ /*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" #include "VectorOperation.h" namespace caret { class AlgorithmCiftiVectorOperation : public AbstractAlgorithm { AlgorithmCiftiVectorOperation(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmCiftiVectorOperation(ProgressObject* myProgObj, const CiftiFile* ciftiA, const CiftiFile* ciftiB, const VectorOperation::Operation& myOper, CiftiFile* myCiftiOut, const bool& normA = false, const bool& normB = false, const bool& normOut = false, const bool& magOut = false); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmCiftiVectorOperation; } #endif //__ALGORITHM_CIFTI_VECTOR_OPERATION_H__ workbench-1.1.1/src/Algorithms/AlgorithmCreateSignedDistanceVolume.cxx000066400000000000000000000636231255417355300262130ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmCreateSignedDistanceVolume.h" #include "AlgorithmException.h" #include "VolumeFile.h" #include "CaretOMP.h" #include "CaretHeap.h" #include "MathFunctions.h" #include "SurfaceFile.h" #include #include #include using namespace caret; using namespace std; AString AlgorithmCreateSignedDistanceVolume::getCommandSwitch() { return "-create-signed-distance-volume"; } AString AlgorithmCreateSignedDistanceVolume::getShortDescription() { return "CREATE SIGNED DISTANCE VOLUME FROM SURFACE"; } OperationParameters* AlgorithmCreateSignedDistanceVolume::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceParameter(1, "surface", "the input surface"); ret->addStringParameter(2, "refspace", "a volume in the desired output space (dims, spacing, origin)"); ret->addVolumeOutputParameter(3, "outvol", "the output volume"); OptionalParameter* roiOutOpt = ret->createOptionalParameter(9, "-roi-out", "output an roi volume of where the output has a computed value"); roiOutOpt->addVolumeOutputParameter(1, "roi-vol", "the output roi volume"); OptionalParameter* fillValOpt = ret->createOptionalParameter(4, "-fill-value", "specify a value to put in all voxels that don't get assigned a distance"); fillValOpt->addDoubleParameter(1, "value", "value to fill with (default 0)"); OptionalParameter* exactDistOpt = ret->createOptionalParameter(5, "-exact-limit", "specify distance for exact output"); exactDistOpt->addDoubleParameter(1, "dist", "distance in mm (default 5)"); OptionalParameter* approxDistOpt = ret->createOptionalParameter(6, "-approx-limit", "specify distance for approximate output"); approxDistOpt->addDoubleParameter(1, "dist", "distance in mm (default 20)"); OptionalParameter* approxNeighborhoodOpt = ret->createOptionalParameter(7, "-approx-neighborhood", "voxel neighborhood for approximate calculation"); approxNeighborhoodOpt->addIntegerParameter(1, "num", "size of neighborhood cube measured from center to face, in voxels (default 2 = 5x5x5)"); OptionalParameter* windingMethodOpt = ret->createOptionalParameter(8, "-winding", "winding method for point inside surface test"); windingMethodOpt->addStringParameter(1, "method", "name of the method (default EVEN_ODD)"); ret->setHelpText( AString("Computes the signed distance function of the surface. Exact distance is calculated by finding the closest point on any surface triangle ") + "to the center of the voxel. Approximate distance is calculated starting with these distances, using dijkstra's method with a neighborhood of voxels. " + "Specifying too small of an exact distance may produce unexpected results. Valid specifiers for winding methods are as follows:\n\n" + "EVEN_ODD (default)\nNEGATIVE\nNONZERO\nNORMALS\n\nThe NORMALS method uses the normals of triangles and edges, or the closest triangle hit by a ray from the point. " + "This method may be slightly faster, but is only reliable for a closed surface that does not cross through itself. All other methods count entry (positive) and " + "exit (negative) crossings of a vertical ray from the point, then counts as inside if the total is odd, negative, or nonzero, respectively." ); return ret; } void AlgorithmCreateSignedDistanceVolume::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { SurfaceFile* mySurf = myParams->getSurface(1); AString myRefName = myParams->getString(2); vector > volSpace; vector volDims; { VolumeFile myRefSpace; myRefSpace.readFile(myRefName); volSpace = myRefSpace.getSform(); myRefSpace.getDimensions(volDims); } volDims.resize(3); VolumeFile* myVolOut = myParams->getOutputVolume(3); myVolOut->reinitialize(volDims, volSpace); float fillValue = 0.0f; OptionalParameter* fillValOpt = myParams->getOptionalParameter(4); if (fillValOpt->m_present) { fillValue = (float)fillValOpt->getDouble(1); } float exactLim = 5.0f; OptionalParameter* exactDistOpt = myParams->getOptionalParameter(5); if (exactDistOpt->m_present) { exactLim = (float)exactDistOpt->getDouble(1); } float approxLim = 20.0f; OptionalParameter* approxDistOpt = myParams->getOptionalParameter(6); if (approxDistOpt->m_present) { approxLim = (float)approxDistOpt->getDouble(1);//don't sanity check it, less than exact limit simply turns it off, specify extremely large to do entire volume } int approxNeighborhood = 2; OptionalParameter* approxNeighborhoodOpt = myParams->getOptionalParameter(7); if (approxNeighborhoodOpt->m_present) { approxNeighborhood = (int)approxNeighborhoodOpt->getInteger(1); } SignedDistanceHelper::WindingLogic myWinding = SignedDistanceHelper::EVEN_ODD; OptionalParameter* windingMethodOpt = myParams->getOptionalParameter(8); if (windingMethodOpt->m_present) { AString methodName = windingMethodOpt->getString(1); if (methodName == "EVEN_ODD") { myWinding = SignedDistanceHelper::EVEN_ODD; } else if (methodName == "NEGATIVE") { myWinding = SignedDistanceHelper::NEGATIVE; } else if (methodName == "NONZERO") { myWinding = SignedDistanceHelper::NONZERO; } else if (methodName == "NORMALS") { myWinding = SignedDistanceHelper::NORMALS; } else { throw AlgorithmException("unrecognized winding method"); } } VolumeFile* myRoiOut = NULL; OptionalParameter* roiOutOpt = myParams->getOptionalParameter(9); if (roiOutOpt->m_present) { myRoiOut = roiOutOpt->getOutputVolume(1); } AlgorithmCreateSignedDistanceVolume(myProgObj, mySurf, myVolOut, myRoiOut, fillValue, exactLim, approxLim, approxNeighborhood, myWinding); } AlgorithmCreateSignedDistanceVolume::AlgorithmCreateSignedDistanceVolume(ProgressObject* myProgObj, const SurfaceFile* mySurf, VolumeFile* myVolOut, VolumeFile* myRoiOut, const float& fillValue, const float& exactLim, const float& approxLim, const int& approxNeighborhood, const SignedDistanceHelper::WindingLogic& myWinding) : AbstractAlgorithm(myProgObj) { if (exactLim <= 0.0f) { throw AlgorithmException("exact limit must be positive"); } if (approxNeighborhood < 1) { throw AlgorithmException("approximate neighborhood must be at least 1"); } int32_t numNodes = mySurf->getNumberOfNodes(); float markweight = 0.1f, exactweight = 5.0f * exactLim, approxweight = 0.2f * (approxLim - exactLim); if (approxweight < 0.0f) approxweight = 0.0f; LevelProgress myProgress(myProgObj, markweight + exactweight + approxweight); vector > myVolSpace; myVolSpace = myVolOut->getSform(); Vector3D ivec, jvec, kvec; ivec[0] = myVolSpace[0][0]; ivec[1] = myVolSpace[1][0]; ivec[2] = myVolSpace[2][0]; jvec[0] = myVolSpace[0][1]; jvec[1] = myVolSpace[1][1]; jvec[2] = myVolSpace[2][1]; kvec[0] = myVolSpace[0][2]; kvec[1] = myVolSpace[1][2]; kvec[2] = myVolSpace[2][2]; Vector3D iOrthHat = jvec.cross(kvec);//these "orth" vectors are used to find the index extremes of a sphere, adding any amount of the other index vectors increases their length iOrthHat = iOrthHat.normal(); if (iOrthHat.dot(ivec) < 0) iOrthHat = -iOrthHat;//make sure it lies with rather than against the i vector Vector3D jOrthHat = ivec.cross(kvec); jOrthHat = jOrthHat.normal(); if (jOrthHat.dot(jvec) < 0) jOrthHat = -jOrthHat; Vector3D kOrthHat = ivec.cross(jvec); kOrthHat = kOrthHat.normal(); if (kOrthHat.dot(kvec) < 0) kOrthHat = -kOrthHat; vector myDims; myVolOut->getDimensions(myDims); myVolOut->setValueAllVoxels(fillValue); //list all voxels to be exactly computed int64_t frameSize = myDims[0] * myDims[1] * myDims[2]; CaretArray volMarked(frameSize, 0); myProgress.setTask("marking voxel to be calculated exactly"); //compare expected runtimes of kernel based and locator based marking methods if (2.9 * myDims[0] * myDims[1] * myDims[2] < (numNodes * exactLim * exactLim * exactLim / iOrthHat.dot(ivec) / jOrthHat.dot(jvec) / kOrthHat.dot(kvec))) { #pragma omp CARET_PARFOR schedule(dynamic) for (int64_t k = 0; k < myDims[2]; ++k) { for (int64_t j = 0; j < myDims[1]; ++j) { for (int64_t i = 0; i < myDims[0]; ++i) { Vector3D voxCoord; myVolOut->indexToSpace(i, j, k, voxCoord); int32_t ret = mySurf->closestNode(voxCoord, exactLim); if (ret != -1) { volMarked[myVolOut->getIndex(i, j, k)] = 1; } } } } } else { #pragma omp CARET_PARFOR schedule(dynamic) for (int node = 0; node < numNodes; ++node) { int64_t ijk[3]; Vector3D nodeCoord = mySurf->getCoordinate(node), tempvec; float tempf, tempf2, tempf3; tempvec = nodeCoord - iOrthHat * exactLim; myVolOut->spaceToIndex(tempvec, tempf, tempf2, tempf3);//compute bounding box once rather than doing a convoluted sphere loop construct int64_t imin = (int64_t)ceil(tempf); if (imin < 0) imin = 0; tempvec = nodeCoord + iOrthHat * exactLim; myVolOut->spaceToIndex(tempvec, tempf, tempf2, tempf3); int64_t imax = (int64_t)floor(tempf) + 1; if (imax > myDims[0]) imax = myDims[0]; tempvec = nodeCoord - jOrthHat * exactLim; myVolOut->spaceToIndex(tempvec, tempf, tempf2, tempf3); int64_t jmin = (int64_t)ceil(tempf2); if (jmin < 0) jmin = 0; tempvec = nodeCoord + jOrthHat * exactLim; myVolOut->spaceToIndex(tempvec, tempf, tempf2, tempf3); int64_t jmax = (int64_t)floor(tempf2) + 1; if (jmax > myDims[1]) jmax = myDims[1]; tempvec = nodeCoord - kOrthHat * exactLim; myVolOut->spaceToIndex(tempvec, tempf, tempf2, tempf3); int64_t kmin = (int64_t)ceil(tempf3); if (kmin < 0) kmin = 0; tempvec = nodeCoord + kOrthHat * exactLim; myVolOut->spaceToIndex(tempvec, tempf, tempf2, tempf3); int64_t kmax = (int64_t)floor(tempf3) + 1; if (kmax > myDims[2]) kmax = myDims[2]; for (ijk[2] = kmin; ijk[2] < kmax; ++ijk[2]) { for (ijk[1] = jmin; ijk[1] < jmax; ++ijk[1]) { for (ijk[0] = imin; ijk[0] < imax; ++ijk[0]) { myVolOut->indexToSpace(ijk, tempvec); tempvec -= nodeCoord; if (tempvec.length() <= exactLim) { volMarked[myVolOut->getIndex(ijk)] = 1; } } } } } } vector exactVoxelList; int64_t ijk[3]; for (ijk[2] = 0; ijk[2] < myDims[2]; ++ijk[2]) { for (ijk[1] = 0; ijk[1] < myDims[1]; ++ijk[1]) { for (ijk[0] = 0; ijk[0] < myDims[0]; ++ijk[0]) { if (volMarked[myVolOut->getIndex(ijk)] == 1) { exactVoxelList.push_back(ijk[0]); exactVoxelList.push_back(ijk[1]); exactVoxelList.push_back(ijk[2]); } } } } myProgress.reportProgress(markweight); myProgress.setTask("computing exact distances"); #pragma omp CARET_PAR { CaretPointer myDist = mySurf->getSignedDistanceHelper(); int numExact = (int)exactVoxelList.size(); Vector3D thisCoord; #pragma omp CARET_FOR schedule(dynamic) for (int i = 0; i < numExact; i += 3) { myVolOut->indexToSpace(exactVoxelList.data() + i, thisCoord); myVolOut->setValue(myDist->dist(thisCoord, myWinding), exactVoxelList.data() + i); volMarked[myVolOut->getIndex(exactVoxelList.data() + i)] |= 22;//set marked to have valid value (positive and negative), and frozen } } myProgress.reportProgress(markweight + exactweight); if (approxLim > exactLim) { myProgress.setTask("approximating distances in extended region"); int faceNeigh[] = { 1, 0, 0, -1, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 1, 0, 0, -1 }; vector neighborhood;//this will contain ONLY the shortest voxel offsets with unique 3d slopes within the neighborhood DistVoxOffset tempIndex; Vector3D tempvec; for (int i = -approxNeighborhood; i <= approxNeighborhood; ++i) { tempIndex.m_offset[0] = i; for (int j = -approxNeighborhood; j <= approxNeighborhood; ++j) { tempIndex.m_offset[1] = j; for (int k = -approxNeighborhood; k <= approxNeighborhood; ++k) { tempIndex.m_offset[2] = k; tempvec = ivec * i + jvec * j + kvec * k; tempIndex.m_dist = tempvec.length(); int low, med, high; low = min(min(abs(i), abs(j)), abs(k));//stupid sort high = max(max(abs(i), abs(j)), abs(k)); if (abs(i) != low && abs(i) != high) { med = abs(i); } else if (abs(j) != low && abs(j) != high) { med = abs(j); } else { med = abs(k); } if (low == 0) { if (med == 0) { if (high == 1) { neighborhood.push_back(tempIndex);//face neighbors } } else { if (MathFunctions::gcd(med, high) == 1) { neighborhood.push_back(tempIndex);//unique in-plane } } } else { if (MathFunctions::gcd(MathFunctions::gcd(low, med), high) == 1) { neighborhood.push_back(tempIndex);//unique out of plane } } } } }//positives float maxFaceDist = max(max(ivec.length(), jvec.length()), kvec.length()) * 1.01f;//add a fudge factor to make sure rounding error doesn't remove a cardinal direction int neighSize = neighborhood.size();//this is provably correct for volumes where there is no diagonal shorter than the longest index vector, so we test this explicitly just in case CaretMinHeap posHeap; CaretArray heapIndexes(frameSize); int numExact = (int)exactVoxelList.size(); for (int i = 0; i < numExact; i += 3) { int64_t* thisVoxel = exactVoxelList.data() + i; float tempf = myVolOut->getValue(thisVoxel); for (int neigh = 0; neigh < neighSize; ++neigh) { int64_t tempijk[3]; tempijk[0] = thisVoxel[0] + neighborhood[neigh].m_offset[0]; tempijk[1] = thisVoxel[1] + neighborhood[neigh].m_offset[1]; tempijk[2] = thisVoxel[2] + neighborhood[neigh].m_offset[2]; if (myVolOut->indexValid(tempijk)) { int64_t tempindex = myVolOut->getIndex(tempijk); float tempf2 = tempf + neighborhood[neigh].m_dist; if (abs(tempf2) <= approxLim && (volMarked[tempindex] & 4) == 0 && ((volMarked[tempindex] & 2) == 0 || tempf2 < myVolOut->getValue(tempijk))) {//within approxlim (so no stragglers outside limit), not frozen, and either no value or worse value volMarked[tempindex] |= 2; myVolOut->setValue(tempf2, tempijk); } } } if (tempf > 0.0f) {//start only from positive values //check face neighbors for being unmarked for (int neigh = 0; neigh < 18; neigh += 3) { int64_t tempijk[3]; tempijk[0] = thisVoxel[0] + faceNeigh[neigh]; tempijk[1] = thisVoxel[1] + faceNeigh[neigh + 1]; tempijk[2] = thisVoxel[2] + faceNeigh[neigh + 2]; if (myVolOut->indexValid(tempijk)) { int64_t tempIndex = myVolOut->getIndex(tempijk); if ((volMarked[tempIndex] & 1) == 0) {//only add this to the heap if it has unmarked face neighbors posHeap.push(VoxelIndex(thisVoxel), tempf);//don't need to store the index to change the key, value is frozen break; } } } } }//initialization done, now (sort of) dijkstras - in order to not go to the "inside" (and to run faster), it only adds things that are FACE neighbors to the heap for cubic voxels while (!posHeap.isEmpty()) { float curDist; VoxelIndex curVoxel = posHeap.pop(&curDist); int64_t curIndex = myVolOut->getIndex(curVoxel.m_ijk); volMarked[curIndex] |= 4;//frozen volMarked[curIndex] &= ~8;//no longer in the heap, don't try to modify by the index it used to have for (int neigh = 0; neigh < neighSize; ++neigh) { int64_t tempijk[3]; tempijk[0] = curVoxel.m_ijk[0] + neighborhood[neigh].m_offset[0]; tempijk[1] = curVoxel.m_ijk[1] + neighborhood[neigh].m_offset[1]; tempijk[2] = curVoxel.m_ijk[2] + neighborhood[neigh].m_offset[2]; if (myVolOut->indexValid(tempijk)) { float tempf = curDist + neighborhood[neigh].m_dist; int tempindex = myVolOut->getIndex(tempijk); int& tempmark = volMarked[tempindex]; if (abs(tempf) <= approxLim && (tempmark & 4) == 0 && ((tempmark & 2) == 0 || myVolOut->getValue(tempijk) > tempf)) {//within range, not frozen, no value or current value is worse tempmark |= 2;//valid value myVolOut->setValue(tempf, tempijk); if ((tempmark & 8) != 0)//if it is already in the heap, we must update its key (log time worst case, but changes should generally be small) { posHeap.changekey(heapIndexes[tempindex], tempf); } } if ((tempmark & 12) == 0 && (tempmark & 2) != 0 && neighborhood[neigh].m_dist <= maxFaceDist) {//this neatly handles both face neighbors and any other needed neighbors to maintain dijkstra correctness under extreme scenarios heapIndexes[tempindex] = posHeap.push(VoxelIndex(tempijk), myVolOut->getValue(tempijk)); tempmark |= 8;//has a heap index } } } }//negatives myProgress.reportProgress(markweight + exactweight + approxweight * 0.5f); CaretMaxHeap negHeap; for (int i = 0; i < numExact; i += 3) { int64_t* thisVoxel = exactVoxelList.data() + i; float tempf = myVolOut->getValue(thisVoxel); for (int neigh = 0; neigh < neighSize; ++neigh) { int64_t tempijk[3]; tempijk[0] = thisVoxel[0] + neighborhood[neigh].m_offset[0]; tempijk[1] = thisVoxel[1] + neighborhood[neigh].m_offset[1]; tempijk[2] = thisVoxel[2] + neighborhood[neigh].m_offset[2]; if (myVolOut->indexValid(tempijk)) { int64_t tempindex = myVolOut->getIndex(tempijk); float tempf2 = tempf - neighborhood[neigh].m_dist; if (abs(tempf2) <= approxLim && (volMarked[tempindex] & 4) == 0 && ((volMarked[tempindex] & 16) == 0 || tempf2 > myVolOut->getValue(tempijk))) {//within approxlim (so no stragglers outside limit), not frozen, and either no value or worse value volMarked[tempindex] |= 16; myVolOut->setValue(tempf2, tempijk); } } } if (tempf < 0.0f) {//start only from negative values //check face neighbors for being unmarked for (int neigh = 0; neigh < 18; neigh += 3) { int64_t tempijk[3]; tempijk[0] = thisVoxel[0] + faceNeigh[neigh]; tempijk[1] = thisVoxel[1] + faceNeigh[neigh + 1]; tempijk[2] = thisVoxel[2] + faceNeigh[neigh + 2]; if (myVolOut->indexValid(tempijk)) { int64_t tempIndex = myVolOut->getIndex(tempijk); if ((volMarked[tempIndex] & 1) == 0) {//only add this to the heap if it has unmarked face neighbors negHeap.push(VoxelIndex(thisVoxel), tempf);//don't need to store the index to change the key, value is frozen break; } } } } } while (!negHeap.isEmpty()) { float curDist; VoxelIndex curVoxel = negHeap.pop(&curDist); int64_t curIndex = myVolOut->getIndex(curVoxel.m_ijk); volMarked[curIndex] |= 4;//frozen volMarked[curIndex] &= ~8;//no longer in the heap, don't try to modify by the index it used to have for (int neigh = 0; neigh < neighSize; ++neigh) { int64_t tempijk[3]; tempijk[0] = curVoxel.m_ijk[0] + neighborhood[neigh].m_offset[0]; tempijk[1] = curVoxel.m_ijk[1] + neighborhood[neigh].m_offset[1]; tempijk[2] = curVoxel.m_ijk[2] + neighborhood[neigh].m_offset[2]; if (myVolOut->indexValid(tempijk)) { float tempf = curDist - neighborhood[neigh].m_dist; int tempindex = myVolOut->getIndex(tempijk); int& tempmark = volMarked[tempindex]; if (abs(tempf) <= approxLim && (tempmark & 4) == 0 && ((tempmark & 16) == 0 || myVolOut->getValue(tempijk) < tempf)) {//within range, not frozen, no value or current value is worse tempmark |= 16;//valid value myVolOut->setValue(tempf, tempijk); if ((tempmark & 8) != 0)//if it is already in the heap, we must update its key (log time worst case, but changes should generally be small) { negHeap.changekey(heapIndexes[tempindex], tempf); } } if ((tempmark & 12) == 0 && (tempmark & 16) != 0 && neighborhood[neigh].m_dist <= maxFaceDist) {//this neatly handles both face neighbors and any other needed neighbors to maintain dijkstra correctness under extreme scenarios heapIndexes[tempindex] = negHeap.push(VoxelIndex(tempijk), myVolOut->getValue(tempijk)); tempmark |= 8;//has a heap index } } } } }//now make the roi volume if (myRoiOut != NULL) { myDims.resize(3); myRoiOut->reinitialize(myDims, myVolOut->getSform()); for (ijk[2] = 0; ijk[2] < myDims[2]; ++ijk[2]) { for (ijk[1] = 0; ijk[1] < myDims[1]; ++ijk[1]) { for (ijk[0] = 0; ijk[0] < myDims[0]; ++ijk[0]) { if ((volMarked[myVolOut->getIndex(ijk)] & 4) == 0)//only mark "frozen" (4) as valid, though "have value" (2) should now be the same { myRoiOut->setValue(0.0f, ijk); } else { myRoiOut->setValue(1.0f, ijk); } } } } } } float AlgorithmCreateSignedDistanceVolume::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmCreateSignedDistanceVolume::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmCreateSignedDistanceVolume.h000066400000000000000000000050331255417355300256270ustar00rootroot00000000000000#ifndef __ALGORITHM_CREATE_SIGNED_DISTANCE_VOLUME_H__ #define __ALGORITHM_CREATE_SIGNED_DISTANCE_VOLUME_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" #include "Vector3D.h" #include "CaretMutex.h" #include "OctTree.h" #include "SignedDistanceHelper.h" #include namespace caret { struct DistVoxOffset { float m_dist; int m_offset[3]; }; struct VoxelIndex {//needed in order to contain the data in a heap, operator= doesn't work on a static array VoxelIndex(int64_t ijk[3]) { m_ijk[0] = ijk[0]; m_ijk[1] = ijk[1]; m_ijk[2] = ijk[2]; } int64_t m_ijk[3]; }; class AlgorithmCreateSignedDistanceVolume : public AbstractAlgorithm { AlgorithmCreateSignedDistanceVolume(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmCreateSignedDistanceVolume(ProgressObject* myProgObj, const SurfaceFile* mySurf, VolumeFile* myVolOut, VolumeFile* myRoiOut = NULL, const float& fillValue = 0.0f, const float& exactLim = 5.0f, const float& approxLim = 20.0f, const int& approxNeighborhood = 2, const SignedDistanceHelper::WindingLogic& myWinding = SignedDistanceHelper::EVEN_ODD); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmCreateSignedDistanceVolume; } #endif //__ALGORITHM_CREATE_SIGNED_DISTANCE_VOLUME_H__ workbench-1.1.1/src/Algorithms/AlgorithmException.cxx000066400000000000000000000042431255417355300227420ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmException.h" #include using namespace caret; /** * Constructor. * */ AlgorithmException::AlgorithmException() : CaretException() { this->initializeMembersAlgorithmException(); } /** * Constructor that uses stack trace from the exception * passed in as a parameter. * * @param e Any exception whose stack trace becomes * this exception's stack trace. * */ AlgorithmException::AlgorithmException( const CaretException& e) : CaretException(e) { this->initializeMembersAlgorithmException(); } /** * Constructor. * * @param s Description of the exception. * */ AlgorithmException::AlgorithmException(const AString& s) : CaretException(s) { this->initializeMembersAlgorithmException(); } /** * Copy Constructor. * @param e * Exception that is copied. */ AlgorithmException::AlgorithmException(const AlgorithmException& e) : CaretException(e) { } /** * Assignment operator. * @param e * Exception that is copied. * @return * Copy of the exception. */ AlgorithmException& AlgorithmException::operator=(const AlgorithmException& e) { if (this != &e) { CaretException::operator=(e); } return *this; } /** * Destructor */ AlgorithmException::~AlgorithmException() throw() { } void AlgorithmException::initializeMembersAlgorithmException() { } workbench-1.1.1/src/Algorithms/AlgorithmException.h000066400000000000000000000027421255417355300223710ustar00rootroot00000000000000#ifndef __ALGORITHM_EXCEPTION_H__ #define __ALGORITHM_EXCEPTION_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretException.h" namespace caret { /// An exception thrown during command processing. class AlgorithmException : public CaretException { public: AlgorithmException(); AlgorithmException(const CaretException& e); AlgorithmException(const AString& s); AlgorithmException(const AlgorithmException& e); AlgorithmException& operator=(const AlgorithmException& e); virtual ~AlgorithmException() throw(); private: void initializeMembersAlgorithmException(); }; } // namespace #endif // __ALGORITHM_EXCEPTION_H__ workbench-1.1.1/src/Algorithms/AlgorithmFiberDotProducts.cxx000066400000000000000000000171411255417355300242270ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmFiberDotProducts.h" #include "AlgorithmException.h" #include "CiftiFile.h" #include "CaretPointLocator.h" #include "MetricFile.h" #include "SignedDistanceHelper.h" #include "SurfaceFile.h" #include #include using namespace caret; using namespace std; AString AlgorithmFiberDotProducts::getCommandSwitch() { return "-fiber-dot-products"; } AString AlgorithmFiberDotProducts::getShortDescription() { return "COMPUTE DOT PRODUCTS OF FIBER ORIENTATIONS WITH SURFACE NORMALS"; } OperationParameters* AlgorithmFiberDotProducts::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceParameter(1, "white-surf", "the white/gray boundary surface"); ret->addCiftiParameter(2, "fiber-file", "the fiber orientation file"); ret->addDoubleParameter(3, "max-dist", "the maximum distance from any surface node a fiber population may be, in mm"); ret->addStringParameter(6, "direction", "test against surface for whether a fiber population should be used"); ret->addMetricOutputParameter(4, "dot-metric", "the metric of dot products"); ret->addMetricOutputParameter(5, "f-metric", "a metric of the f values of the fiber distributions"); ret->setHelpText( AString("For each vertex, this command finds the closest fiber population that satisfies the test, ") + "and computes the absolute value of the dot product of the surface normal and the normalized mean direction of each fiber. " + "The test must be one of INSIDE, OUTSIDE, or ANY, which causes the command to only use fiber populations " + "that are inside the surface, outside the surface, or to not care which direction it is from the surface. " + "Each fiber population is output in a separate metric column." ); return ret; } void AlgorithmFiberDotProducts::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { SurfaceFile* mySurf = myParams->getSurface(1); CiftiFile* myFibers = myParams->getCifti(2); float maxDist = (float)myParams->getDouble(3); MetricFile* myDotProdOut = myParams->getOutputMetric(4); MetricFile* myFSampOut = myParams->getOutputMetric(5); AString direction = myParams->getString(6); Direction myTest = INSIDE; if (direction == "INSIDE") { myTest = INSIDE; } else if (direction == "OUTSIDE") { myTest = OUTSIDE; } else if (direction == "ANY") { myTest = ANY; } else { throw AlgorithmException("unrecognized direction test"); } AlgorithmFiberDotProducts(myProgObj, mySurf, myFibers, maxDist, myTest, myDotProdOut, myFSampOut); } AlgorithmFiberDotProducts::AlgorithmFiberDotProducts(ProgressObject* myProgObj, const SurfaceFile* mySurf, const CiftiFile* myFibers, const float& maxDist, const Direction& myTest, MetricFile* myDotProdOut, MetricFile* myFSampOut) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); CaretPointer mySignedHelp = mySurf->getSignedDistanceHelper(); int rowSize = myFibers->getNumberOfColumns(); if ((rowSize - 3) % 7 != 0) throw AlgorithmException("input is not a fiber orientation file"); int numFibers = (rowSize - 3) / 7; int64_t numRows = myFibers->getNumberOfRows(); vector coordsInside; vector coordIndices; vector rowScratch(rowSize); for (int64_t i = 0; i < numRows; ++i) { myFibers->getRow(rowScratch.data(), i); int closeNode = mySurf->closestNode(rowScratch.data(), maxDist); if (closeNode == -1) continue;//skip samples that aren't close to a surface node, for speed (signed distance is kinda slow) if (myTest == ANY) { coordsInside.push_back(rowScratch[0]);//add point to vector for locator coordsInside.push_back(rowScratch[1]); coordsInside.push_back(rowScratch[2]); coordIndices.push_back(i);//and save its cifti index } else { float signedDist = mySignedHelp->dist(rowScratch.data(), SignedDistanceHelper::EVEN_ODD);//the first 3 floats are xyz coords if ((myTest == INSIDE) == (signedDist <= 0.0f))//test for inside/outside surface { coordsInside.push_back(rowScratch[0]);//add point to vector for locator coordsInside.push_back(rowScratch[1]); coordsInside.push_back(rowScratch[2]); coordIndices.push_back(i);//and save its cifti index } } } if (coordIndices.size() == 0) throw AlgorithmException("no fiber samples passed the and tests"); CaretPointLocator myLocator(coordsInside.data(), coordIndices.size());//build the locator int numNodes = mySurf->getNumberOfNodes(); myDotProdOut->setNumberOfNodesAndColumns(numNodes, numFibers); myFSampOut->setNumberOfNodesAndColumns(numNodes, numFibers); myDotProdOut->setStructure(mySurf->getStructure()); myFSampOut->setStructure(mySurf->getStructure()); for (int i = 0; i < numFibers; ++i) { myDotProdOut->setColumnName(i, "Fiber " + AString::number(i + 1) + " dot products"); myFSampOut->setColumnName(i, "Fiber " + AString::number(i + 1) + " population mean f"); } const float* coordData = mySurf->getCoordinateData(); for (int i = 0; i < numNodes; ++i) { int closest = myLocator.closestPoint(coordData + i * 3); if (closest != -1) { myFibers->getRow(rowScratch.data(), coordIndices[closest]); Vector3D myNormal = mySurf->getNormalVector(i); for (int j = 0; j < numFibers; ++j) { int base = 3 + j * 7; float fmean = rowScratch[base]; float theta = rowScratch[base + 2]; float phi = rowScratch[base + 3]; Vector3D direction; direction[0] = -sin(theta) * cos(phi);//NOTE: theta, phi are polar coordinates for a RADIOLOGICAL coordinate system, so flip x so that +x = right direction[1] = sin(theta) * sin(phi); direction[2] = cos(theta); float dotProd = abs(myNormal.dot(direction)); myDotProdOut->setValue(i, j, dotProd); myFSampOut->setValue(i, j, fmean); } } else { for (int j = 0; j < numFibers; ++j) { myDotProdOut->setValue(i, j, 0.0f); myFSampOut->setValue(i, j, 0.0f); } } } } float AlgorithmFiberDotProducts::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmFiberDotProducts::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmFiberDotProducts.h000066400000000000000000000035631255417355300236570ustar00rootroot00000000000000#ifndef __ALGORITHM_FIBER_DOT_PRODUCTS_H__ #define __ALGORITHM_FIBER_DOT_PRODUCTS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmFiberDotProducts : public AbstractAlgorithm { AlgorithmFiberDotProducts(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: enum Direction { INSIDE, OUTSIDE, ANY }; AlgorithmFiberDotProducts(ProgressObject* myProgObj, const SurfaceFile* mySurf, const CiftiFile* myFibers, const float& maxDist, const Direction& myTest, MetricFile* myDotProdOut, MetricFile* myFSampOut); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmFiberDotProducts; } #endif //__ALGORITHM_FIBER_DOT_PRODUCTS_H__ workbench-1.1.1/src/Algorithms/AlgorithmFociResample.cxx000066400000000000000000000213001255417355300233460ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmFociResample.h" #include "AlgorithmException.h" #include "FociFile.h" #include "Focus.h" #include "GiftiLabelTable.h" #include "GiftiMetaData.h" #include "SurfaceFile.h" #include "SurfaceProjector.h" using namespace caret; using namespace std; AString AlgorithmFociResample::getCommandSwitch() { return "-foci-resample"; } AString AlgorithmFociResample::getShortDescription() { return "PROJECT FOCI TO A DIFFERENT SURFACE"; } OperationParameters* AlgorithmFociResample::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addFociParameter(1, "foci-in", "the input foci file"); ret->addFociOutputParameter(2, "foci-out", "the output foci file"); OptionalParameter* leftSurfaceOpt = ret->createOptionalParameter(3, "-left-surfaces", "the left surfaces for resampling"); leftSurfaceOpt->addSurfaceParameter(1, "current-surf", "the surface the foci are currently projected on"); leftSurfaceOpt->addSurfaceParameter(2, "new-surf", "the surface to project the foci onto"); OptionalParameter* rightSurfaceOpt = ret->createOptionalParameter(4, "-right-surfaces", "the right surfaces for resampling"); rightSurfaceOpt->addSurfaceParameter(1, "current-surf", "the surface the foci are currently projected on"); rightSurfaceOpt->addSurfaceParameter(2, "new-surf", "the surface to project the foci onto"); OptionalParameter* cerebSurfaceOpt = ret->createOptionalParameter(5, "-cerebellum-surfaces", "the cerebellum surfaces for resampling"); cerebSurfaceOpt->addSurfaceParameter(1, "current-surf", "the surface the foci are currently projected on"); cerebSurfaceOpt->addSurfaceParameter(2, "new-surf", "the surface to project the foci onto"); ret->createOptionalParameter(6, "-discard-distance-from-surface", "ignore the distance the foci are above or below the current surface"); ret->createOptionalParameter(7, "-restore-xyz", "put the original xyz coordinates into the foci, rather than the coordinates obtained from unprojection"); ret->setHelpText(AString("Unprojects foci from the for the structure, then projects them to . ") + "If the foci have meaningful distances above or below the surface, use anatomical surfaces. " + "If the foci should be on the surface, use registered spheres and the options -discard-distance-from-surface and -restore-xyz."); return ret; } void AlgorithmFociResample::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { FociFile* fociIn = myParams->getFoci(1); FociFile* fociOut = myParams->getOutputFoci(2); SurfaceFile* leftCurSurf = NULL, *leftNewSurf = NULL; OptionalParameter* leftSurfaceOpt = myParams->getOptionalParameter(3); if (leftSurfaceOpt->m_present) { leftCurSurf = leftSurfaceOpt->getSurface(1); leftNewSurf = leftSurfaceOpt->getSurface(2); } SurfaceFile* rightCurSurf = NULL, *rightNewSurf = NULL; OptionalParameter* rightSurfaceOpt = myParams->getOptionalParameter(4); if (rightSurfaceOpt->m_present) { rightCurSurf = rightSurfaceOpt->getSurface(1); rightNewSurf = rightSurfaceOpt->getSurface(2); } SurfaceFile* cerebCurSurf = NULL, *cerebNewSurf = NULL; OptionalParameter* cerebSurfaceOpt = myParams->getOptionalParameter(5); if (cerebSurfaceOpt->m_present) { cerebCurSurf = cerebSurfaceOpt->getSurface(1); cerebNewSurf = cerebSurfaceOpt->getSurface(2); } bool discardNormDist = myParams->getOptionalParameter(6)->m_present; bool restoryXyz = myParams->getOptionalParameter(7)->m_present; AlgorithmFociResample(myProgObj, fociIn, fociOut, leftCurSurf, leftNewSurf, rightCurSurf, rightNewSurf, cerebCurSurf, cerebNewSurf, discardNormDist, restoryXyz); } AlgorithmFociResample::AlgorithmFociResample(ProgressObject* myProgObj, const FociFile* fociIn, FociFile* fociOut, const SurfaceFile* leftCurSurf, const SurfaceFile* leftNewSurf, const SurfaceFile* rightCurSurf, const SurfaceFile* rightNewSurf, const SurfaceFile* cerebCurSurf, const SurfaceFile* cerebNewSurf, const bool& discardNormDist, const bool& restoryXyz) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); checkStructureMatch(leftCurSurf, StructureEnum::CORTEX_LEFT, "current left surface", "-left-surfaces option expects"); checkStructureMatch(leftNewSurf, StructureEnum::CORTEX_LEFT, "new left surface", "-left-surfaces option expects"); checkStructureMatch(rightCurSurf, StructureEnum::CORTEX_RIGHT, "current right surface", "-right-surfaces option expects"); checkStructureMatch(rightNewSurf, StructureEnum::CORTEX_RIGHT, "new right surface", "-right-surfaces option expects"); checkStructureMatch(cerebCurSurf, StructureEnum::CEREBELLUM, "current cerebellum surface", "-cerebellum-surfaces option expects"); checkStructureMatch(cerebNewSurf, StructureEnum::CEREBELLUM, "new cerebellum surface", "-cerebellum-surfaces option expects"); CaretPointer leftProj, rightProj, cerebProj; if (leftNewSurf != NULL) leftProj.grabNew(new SurfaceProjector(leftNewSurf)); if (rightNewSurf != NULL) rightProj.grabNew(new SurfaceProjector(rightNewSurf)); if (cerebNewSurf != NULL) cerebProj.grabNew(new SurfaceProjector(cerebNewSurf)); *(fociOut->getClassColorTable()) = *(fociIn->getClassColorTable()); *(fociOut->getNameColorTable()) = *(fociIn->getNameColorTable()); *(fociOut->getFileMetaData()) = *(fociIn->getFileMetaData()); for (int i = 0; i < fociIn->getNumberOfFoci(); ++i) { const Focus* thisFocus = fociIn->getFocus(i); if (thisFocus->getNumberOfProjections() < 1) { throw AlgorithmException("focus '" + thisFocus->getName() + "' has no surface projection"); } SurfaceProjector* myProj = NULL; const SurfaceFile* unprojFrom = NULL; switch (thisFocus->getProjection(0)->getStructure()) { case StructureEnum::CORTEX_LEFT: myProj = leftProj; unprojFrom = leftCurSurf; break; case StructureEnum::CORTEX_RIGHT: myProj = rightProj; unprojFrom = rightCurSurf; break; case StructureEnum::CEREBELLUM: myProj = cerebProj; unprojFrom = cerebCurSurf; break; default: throw AlgorithmException("focus '" + thisFocus->getName() + "' has unsupported structure " + StructureEnum::toName(thisFocus->getProjection(0)->getStructure())); } if (unprojFrom == NULL || myProj == NULL) throw AlgorithmException("focus '" + thisFocus->getName() + "' has structure " + StructureEnum::toName(thisFocus->getProjection(0)->getStructure()) + ", but surfaces for that structure were not specified"); CaretPointer newFocus(new Focus(*thisFocus));//start with a copy float xyz[3]; bool result = thisFocus->getProjection(0)->getProjectedPosition(*unprojFrom, xyz, discardNormDist); if (!result) throw AlgorithmException("failed to unproject focus '" + thisFocus->getName() + "'"); newFocus->getProjection(0)->setStereotaxicXYZ(xyz); myProj->projectFocus(i, newFocus); if (restoryXyz) { newFocus->getProjection(0)->setStereotaxicXYZ(thisFocus->getProjection(0)->getStereotaxicXYZ()); } fociOut->addFocus(newFocus.releasePointer()); } } float AlgorithmFociResample::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmFociResample::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmFociResample.h000066400000000000000000000040341255417355300230000ustar00rootroot00000000000000#ifndef __ALGORITHM_FOCI_RESAMPLE_H__ #define __ALGORITHM_FOCI_RESAMPLE_H__ /*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmFociResample : public AbstractAlgorithm { AlgorithmFociResample(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmFociResample(ProgressObject* myProgObj, const FociFile* fociIn, FociFile* fociOut, const SurfaceFile* leftCurSurf, const SurfaceFile* leftNewSurf, const SurfaceFile* rightCurSurf = NULL, const SurfaceFile* rightNewSurf = NULL, const SurfaceFile* cerebCurSurf = NULL, const SurfaceFile* cerebNewSurf = NULL, const bool& discardNormDist = false, const bool& restoryXyz = false); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmFociResample; } #endif //__ALGORITHM_FOCI_RESAMPLE_H__ workbench-1.1.1/src/Algorithms/AlgorithmGiftiAllLabelsToROIs.cxx000066400000000000000000000106721255417355300246650ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmGiftiAllLabelsToROIs.h" #include "AlgorithmException.h" #include "GiftiLabelTable.h" #include "LabelFile.h" #include "MetricFile.h" #include using namespace caret; using namespace std; AString AlgorithmGiftiAllLabelsToROIs::getCommandSwitch() { return "-gifti-all-labels-to-rois"; } AString AlgorithmGiftiAllLabelsToROIs::getShortDescription() { return "MAKE ROIS FROM ALL LABELS IN A GIFTI COLUMN"; } OperationParameters* AlgorithmGiftiAllLabelsToROIs::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addLabelParameter(1, "label-in", "the input gifti label file"); ret->addStringParameter(2, "map", "the number or name of the label map to use"); ret->addMetricOutputParameter(3, "metric-out", "the output metric file"); ret->setHelpText( AString("The output metric file has a column for each label in the specified input map, other than the ??? label, ") + "each of which contains an ROI of all vertices that are set to the corresponding label." ); return ret; } void AlgorithmGiftiAllLabelsToROIs::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LabelFile* myLabel = myParams->getLabel(1); AString mapID = myParams->getString(2); int whichMap = myLabel->getMapIndexFromNameOrNumber(mapID); if (whichMap == -1) { throw AlgorithmException("invalid map number or name specified"); } MetricFile* myMetricOut = myParams->getOutputMetric(3); AlgorithmGiftiAllLabelsToROIs(myProgObj, myLabel, whichMap, myMetricOut); } AlgorithmGiftiAllLabelsToROIs::AlgorithmGiftiAllLabelsToROIs(ProgressObject* myProgObj, const LabelFile* myLabel, const int& whichMap, MetricFile* myMetricOut) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); if (whichMap < 0 || whichMap >= myLabel->getNumberOfMaps()) { throw AlgorithmException("invalid map index specified"); } const GiftiLabelTable* myTable = myLabel->getMapLabelTable(whichMap); int32_t unusedKey = myTable->getUnassignedLabelKey();//WARNING: this actually MODIFIES the label table if the ??? key doesn't exist set myKeys = myTable->getKeys(); int numKeys = (int)myKeys.size(); if (numKeys < 2) { throw AlgorithmException("label table doesn't contain any keys besides the ??? key"); } int numNodes = myLabel->getNumberOfNodes(); map keyToMap;//lookup from keys to column myMetricOut->setNumberOfNodesAndColumns(numNodes, numKeys - 1);//skip the ??? label myMetricOut->setStructure(myLabel->getStructure()); for (int i = 0; i < numKeys - 1; ++i) { myMetricOut->initializeColumn(i, 0.0f); } int counter = 0; for (set::iterator iter = myKeys.begin(); iter != myKeys.end(); ++iter) { if (*iter == unusedKey) continue;//skip the ??? key keyToMap[*iter] = counter; myMetricOut->setMapName(counter, myTable->getLabelName(*iter)); ++counter; } const int32_t* myColumn = myLabel->getLabelKeyPointerForColumn(whichMap); for (int i = 0; i < numNodes; ++i) { map::iterator iter = keyToMap.find(myColumn[i]); if (iter != keyToMap.end()) { myMetricOut->setValue(i, iter->second, 1.0f); } } } float AlgorithmGiftiAllLabelsToROIs::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmGiftiAllLabelsToROIs::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmGiftiAllLabelsToROIs.h000066400000000000000000000033451255417355300243110ustar00rootroot00000000000000#ifndef __ALGORITHM_GIFTI_ALL_LABELS_TO_ROIS_H__ #define __ALGORITHM_GIFTI_ALL_LABELS_TO_ROIS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmGiftiAllLabelsToROIs : public AbstractAlgorithm { AlgorithmGiftiAllLabelsToROIs(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmGiftiAllLabelsToROIs(ProgressObject* myProgObj, const LabelFile* myLabel, const int& whichMap, MetricFile* myMetricOut); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmGiftiAllLabelsToROIs; } #endif //__ALGORITHM_GIFTI_ALL_LABELS_TO_ROIS_H__ workbench-1.1.1/src/Algorithms/AlgorithmGiftiLabelAddPrefix.cxx000066400000000000000000000067311255417355300246010ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmGiftiLabelAddPrefix.h" #include "AlgorithmException.h" #include "GiftiLabelTable.h" #include "LabelFile.h" using namespace caret; using namespace std; AString AlgorithmGiftiLabelAddPrefix::getCommandSwitch() { return "-gifti-label-add-prefix"; } AString AlgorithmGiftiLabelAddPrefix::getShortDescription() { return "ADD PREFIX TO ALL LABEL NAMES IN A GIFTI LABEL FILE"; } OperationParameters* AlgorithmGiftiLabelAddPrefix::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addLabelParameter(1, "label-in", "the input label file"); ret->addStringParameter(2, "prefix", "the prefix string to add"); ret->addLabelOutputParameter(3, "label-out", "the output label file"); ret->setHelpText( AString("For each label other than '?\?\?', prepend to the label name."));//bleh, trigraphs return ret; } void AlgorithmGiftiLabelAddPrefix::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LabelFile* labelIn = myParams->getLabel(1); AString prefix = myParams->getString(2); LabelFile* labelOut = myParams->getOutputLabel(3); AlgorithmGiftiLabelAddPrefix(myProgObj, labelIn, prefix, labelOut); } AlgorithmGiftiLabelAddPrefix::AlgorithmGiftiLabelAddPrefix(ProgressObject* myProgObj, const LabelFile* labelIn, const AString& prefix, LabelFile* labelOut) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); int numColumns = labelIn->getNumberOfColumns(); GiftiLabelTable outTable = *(labelIn->getLabelTable()); vector keys; outTable.getKeys(keys); int numKeys = (int)keys.size(); int32_t unassigned = GiftiLabelTable(outTable).getUnassignedLabelKey();//make a copy so getUnassignedLabelKey() doesn't modify the one we are using for (int i = 0; i < numKeys; ++i) { if (keys[i] != unassigned) { outTable.setLabelName(keys[i], prefix + outTable.getLabelName(keys[i])); } } labelOut->setNumberOfNodesAndColumns(labelIn->getNumberOfNodes(), numColumns); labelOut->setStructure(labelIn->getStructure()); *(labelOut->getLabelTable()) = outTable; for (int i = 0; i < numColumns; ++i) { labelOut->setMapName(i, labelIn->getMapName(i)); labelOut->setLabelKeysForColumn(i, labelIn->getLabelKeyPointerForColumn(i)); } } float AlgorithmGiftiLabelAddPrefix::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmGiftiLabelAddPrefix::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmGiftiLabelAddPrefix.h000066400000000000000000000033301255417355300242160ustar00rootroot00000000000000#ifndef __ALGORITHM_GIFTI_LABEL_ADD_PREFIX_H__ #define __ALGORITHM_GIFTI_LABEL_ADD_PREFIX_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmGiftiLabelAddPrefix : public AbstractAlgorithm { AlgorithmGiftiLabelAddPrefix(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmGiftiLabelAddPrefix(ProgressObject* myProgObj, const LabelFile* labelIn, const AString& prefix, LabelFile* labelOut); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmGiftiLabelAddPrefix; } #endif //__ALGORITHM_GIFTI_LABEL_ADD_PREFIX_H__ workbench-1.1.1/src/Algorithms/AlgorithmGiftiLabelToROI.cxx000066400000000000000000000221231255417355300236600ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmGiftiLabelToROI.h" #include "AlgorithmException.h" #include "CaretLogger.h" #include "GiftiLabel.h" #include "GiftiLabelTable.h" #include "LabelFile.h" #include "MetricFile.h" #include #include using namespace caret; using namespace std; AString AlgorithmGiftiLabelToROI::getCommandSwitch() { return "-gifti-label-to-roi"; } AString AlgorithmGiftiLabelToROI::getShortDescription() { return "MAKE A GIFTI LABEL INTO AN ROI METRIC"; } OperationParameters* AlgorithmGiftiLabelToROI::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addLabelParameter(1, "label-in", "the input gifti label file"); ret->addMetricOutputParameter(2, "metric-out", "the output metric file"); OptionalParameter* nameOpt = ret->createOptionalParameter(3, "-name", "select label by name"); nameOpt->addStringParameter(1, "label-name", "the label name that you want an roi of"); OptionalParameter* keyOpt = ret->createOptionalParameter(4, "-key", "select label by key"); keyOpt->addIntegerParameter(1, "label-key", "the label key that you want an roi of"); OptionalParameter* mapOpt = ret->createOptionalParameter(5, "-map", "select a single label map to use"); mapOpt->addStringParameter(1, "map", "the map number or name"); ret->setHelpText( AString("For each map in , a map is created in where all locations labeled with or with a key of are given a value of 1, and all other locations are given 0. ") + "Exactly one of -name and -key must be specified. " + "Specify -map to use only one map from ." ); return ret; } void AlgorithmGiftiLabelToROI::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LabelFile* myLabel = myParams->getLabel(1); MetricFile* myMetricOut = myParams->getOutputMetric(2); bool nameMode = false; AString labelName; OptionalParameter* nameOpt = myParams->getOptionalParameter(3); if (nameOpt->m_present) { nameMode = true; labelName = nameOpt->getString(1); } int32_t labelKey; OptionalParameter* keyOpt = myParams->getOptionalParameter(4); if (keyOpt->m_present) { if (nameMode) throw AlgorithmException("-name and -key cannot be specified together"); labelKey = (int32_t)keyOpt->getInteger(1); } else { if (!nameMode) throw AlgorithmException("you must specify one of -name or -key"); } int whichMap = -1; OptionalParameter* mapOpt = myParams->getOptionalParameter(5); if (mapOpt->m_present) { AString mapID = mapOpt->getString(1); whichMap = myLabel->getMapIndexFromNameOrNumber(mapID); if (whichMap == -1) { throw AlgorithmException("invalid map number or name specified"); } } if (nameMode) { AlgorithmGiftiLabelToROI(myProgObj, myLabel, labelName, myMetricOut, whichMap); } else { AlgorithmGiftiLabelToROI(myProgObj, myLabel, labelKey, myMetricOut, whichMap); } } AlgorithmGiftiLabelToROI::AlgorithmGiftiLabelToROI(ProgressObject* myProgObj, const LabelFile* myLabel, const AString& labelName, MetricFile* myMetricOut, const int& whichMap) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); int64_t numNodes = myLabel->getNumberOfNodes(); int64_t numMaps = myLabel->getNumberOfMaps(); if (whichMap < -1 || whichMap >= numMaps) { throw AlgorithmException("invalid map index specified"); } const GiftiLabelTable* myTable = myLabel->getLabelTable(); int32_t matchKey = myTable->getLabelKeyFromName(labelName); if (matchKey == GiftiLabel::getInvalidLabelKey()) { throw AlgorithmException("label name '" + labelName + "' not found in label file"); } vector scratchCol(numNodes); if (whichMap == -1) { myMetricOut->setNumberOfNodesAndColumns(numNodes, numMaps); myMetricOut->setStructure(myLabel->getStructure()); bool shouldThrow = true; for (int thisMap = 0; thisMap < numMaps; ++thisMap) { myMetricOut->setColumnName(thisMap, myLabel->getColumnName(thisMap)); const int32_t* labelColumn = myLabel->getLabelKeyPointerForColumn(thisMap); for (int i = 0; i < numNodes; ++i) { if (labelColumn[i] == matchKey) { scratchCol[i] = 1.0f; shouldThrow = false; } else { scratchCol[i] = 0.0f; } } myMetricOut->setValuesForColumn(thisMap, scratchCol.data()); } if (shouldThrow) { throw AlgorithmException("no data matched the specified label name"); } } else { myMetricOut->setNumberOfNodesAndColumns(numNodes, 1); myMetricOut->setStructure(myLabel->getStructure()); myMetricOut->setColumnName(0, myLabel->getColumnName(whichMap)); const int32_t* labelColumn = myLabel->getLabelKeyPointerForColumn(whichMap); bool shouldThrow = true; for (int i = 0; i < numNodes; ++i) { if (labelColumn[i] == matchKey) { scratchCol[i] = 1.0f; shouldThrow = false; } else { scratchCol[i] = 0.0f; } } if (shouldThrow) { throw AlgorithmException("no data matched the specified label name in the specified map"); } myMetricOut->setValuesForColumn(0, scratchCol.data()); } } AlgorithmGiftiLabelToROI::AlgorithmGiftiLabelToROI(ProgressObject* myProgObj, const LabelFile* myLabel, const int32_t& labelKey, MetricFile* myMetricOut, const int& whichMap) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); int64_t numNodes = myLabel->getNumberOfNodes(); int64_t numMaps = myLabel->getNumberOfMaps(); if (whichMap < -1 || whichMap >= numMaps) { throw AlgorithmException("invalid map index specified"); } const GiftiLabelTable* myTable = myLabel->getLabelTable(); if (myTable->getLabel(labelKey) == NULL) { CaretLogWarning("label key " + AString::number(labelKey) + " not found in label file"); } vector scratchCol(numNodes); if (whichMap == -1) { myMetricOut->setNumberOfNodesAndColumns(numNodes, numMaps); myMetricOut->setStructure(myLabel->getStructure()); bool shouldThrow = true; for (int thisMap = 0; thisMap < numMaps; ++thisMap) { myMetricOut->setColumnName(thisMap, myLabel->getColumnName(thisMap)); const int32_t* labelColumn = myLabel->getLabelKeyPointerForColumn(thisMap); for (int i = 0; i < numNodes; ++i) { if (labelColumn[i] == labelKey) { scratchCol[i] = 1.0f; shouldThrow = false; } else { scratchCol[i] = 0.0f; } } myMetricOut->setValuesForColumn(thisMap, scratchCol.data()); } if (shouldThrow) { throw AlgorithmException("no data matched the specified label key"); } } else { myMetricOut->setNumberOfNodesAndColumns(numNodes, 1); myMetricOut->setStructure(myLabel->getStructure()); myMetricOut->setColumnName(0, myLabel->getColumnName(whichMap)); const int32_t* labelColumn = myLabel->getLabelKeyPointerForColumn(whichMap); bool shouldThrow = true; for (int i = 0; i < numNodes; ++i) { if (labelColumn[i] == labelKey) { scratchCol[i] = 1.0f; } else { scratchCol[i] = 0.0f; } } if (shouldThrow) { throw AlgorithmException("no data matched the specified label key in the specified map"); } myMetricOut->setValuesForColumn(0, scratchCol.data()); } } float AlgorithmGiftiLabelToROI::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmGiftiLabelToROI::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmGiftiLabelToROI.h000066400000000000000000000035741255417355300233160ustar00rootroot00000000000000#ifndef __ALGORITHM_GIFTI_LABEL_TO_ROI_H__ #define __ALGORITHM_GIFTI_LABEL_TO_ROI_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmGiftiLabelToROI : public AbstractAlgorithm { AlgorithmGiftiLabelToROI(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmGiftiLabelToROI(ProgressObject* myProgObj, const LabelFile* myLabel, const AString& labelName, MetricFile* myMetricOut, const int& whichMap = -1); AlgorithmGiftiLabelToROI(ProgressObject* myProgObj, const LabelFile* myLabel, const int32_t& labelKey, MetricFile* myMetricOut, const int& whichMap = -1); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmGiftiLabelToROI; } #endif //__ALGORITHM_GIFTI_LABEL_TO_ROI_H__ workbench-1.1.1/src/Algorithms/AlgorithmLabelDilate.cxx000066400000000000000000000324571255417355300231560ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmLabelDilate.h" #include "AlgorithmException.h" #include "CaretOMP.h" #include "GeodesicHelper.h" #include "LabelFile.h" #include "MetricFile.h" #include "GiftiLabelTable.h" #include "SurfaceFile.h" #include "TopologyHelper.h" using namespace caret; using namespace std; AString AlgorithmLabelDilate::getCommandSwitch() { return "-label-dilate"; } AString AlgorithmLabelDilate::getShortDescription() { return "DILATE A LABEL FILE"; } OperationParameters* AlgorithmLabelDilate::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addLabelParameter(1, "label", "the input label"); ret->addSurfaceParameter(2, "surface", "the surface to dilate on"); ret->addDoubleParameter(3, "dilate-dist", "distance in mm to dilate the labels"); ret->addLabelOutputParameter(4, "label-out", "the output label file"); OptionalParameter* roiOpt = ret->createOptionalParameter(5, "-bad-vertex-roi", "specify an roi of vertices to overwrite, rather than vertices with the unlabeled key"); roiOpt->addMetricParameter(1, "roi-metric", "metric file, positive values denote vertices to have their values replaced"); OptionalParameter* columnSelect = ret->createOptionalParameter(6, "-column", "select a single column to dilate"); columnSelect->addStringParameter(1, "column", "the column number or name"); OptionalParameter* corrAreaOpt = ret->createOptionalParameter(7, "-corrected-areas", "vertex areas to use instead of computing them from the surface"); corrAreaOpt->addMetricParameter(1, "area-metric", "the corrected vertex areas, as a metric"); ret->setHelpText( AString("Fills in label information for all vertices designated as bad, up to the specified distance away from other labels. ") + "If -bad-vertex-roi is specified, all vertices, including those with the unlabeled key, are good, except for vertices with a positive value in the ROI. " + "If it is not specified, only vertices with the unlabeled key are bad." ); return ret; } void AlgorithmLabelDilate::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LabelFile* myLabel = myParams->getLabel(1); SurfaceFile* mySurf = myParams->getSurface(2); float myDist = (float)myParams->getDouble(3); LabelFile* myLabelOut = myParams->getOutputLabel(4); OptionalParameter* roiOpt = myParams->getOptionalParameter(5); MetricFile* badNodeRoi = NULL; if (roiOpt->m_present) { badNodeRoi = roiOpt->getMetric(1); } OptionalParameter* columnSelect = myParams->getOptionalParameter(6); int columnNum = -1; if (columnSelect->m_present) {//set up to use the single column columnNum = (int)myLabel->getMapIndexFromNameOrNumber(columnSelect->getString(1)); if (columnNum < 0) { throw AlgorithmException("invalid column specified"); } } OptionalParameter* corrAreaOpt = myParams->getOptionalParameter(7); MetricFile* corrAreas = NULL; if (corrAreaOpt->m_present) { corrAreas = corrAreaOpt->getMetric(1); } AlgorithmLabelDilate(myProgObj, myLabel, mySurf, myDist, myLabelOut, badNodeRoi, columnNum, corrAreas); } AlgorithmLabelDilate::AlgorithmLabelDilate(ProgressObject* myProgObj, const LabelFile* myLabel, const SurfaceFile* mySurf, float myDist, LabelFile* myLabelOut, const MetricFile* badNodeRoi, int columnNum, const MetricFile* corrAreas) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); int32_t unusedLabel = myLabel->getLabelTable()->getUnassignedLabelKey(); int numColumns = myLabel->getNumberOfColumns(); if (myDist < 0.0f) { throw AlgorithmException("invalid distance specified"); } if (columnNum < -1 || columnNum >= numColumns) { throw AlgorithmException("invalid column specified"); } int numNodes = myLabel->getNumberOfNodes(); if (mySurf->getNumberOfNodes() != numNodes) { throw AlgorithmException("surface has wrong number of vertices for this label file"); } if (corrAreas != NULL && corrAreas->getNumberOfNodes() != numNodes) { throw AlgorithmException("corrected areas metric number of vertices does not match"); } CaretArray colScratch(numNodes); CaretArray markArray(numNodes); if (badNodeRoi != NULL) { const float* myRoiData = badNodeRoi->getValuePointerForColumn(0); for (int i = 0; i < numNodes; ++i) { if (myRoiData[i] > 0.0f) { markArray[i] = 0; } else { markArray[i] = 1; } } } CaretPointer myCorrBase; if (corrAreas != NULL) { myCorrBase.grabNew(new GeodesicHelperBase(mySurf, corrAreas->getValuePointerForColumn(0))); } if (columnNum == -1) { myLabelOut->setNumberOfNodesAndColumns(numNodes, myLabel->getNumberOfColumns()); *(myLabelOut->getLabelTable()) = *(myLabel->getLabelTable()); myLabelOut->setStructure(mySurf->getStructure()); for (int thisCol = 0; thisCol < myLabel->getNumberOfColumns(); ++thisCol) { const int32_t* myInputData = myLabel->getLabelKeyPointerForColumn(thisCol); myLabelOut->setColumnName(thisCol, myLabel->getColumnName(thisCol) + " dilated"); if (badNodeRoi == NULL) { for (int i = 0; i < numNodes; ++i) { if (myInputData[i] == unusedLabel) { markArray[i] = 0; } else { markArray[i] = 1; } } } #pragma omp CARET_PAR { CaretPointer myTopoHelp = mySurf->getTopologyHelper(); CaretPointer myGeoHelp; if (corrAreas == NULL) { myGeoHelp = mySurf->getGeodesicHelper(); } else { myGeoHelp.grabNew(new GeodesicHelper(myCorrBase)); } #pragma omp CARET_FOR schedule(dynamic) for (int i = 0; i < numNodes; ++i) { if (markArray[i] == 0) { vector nodeList; vector distList; myGeoHelp->getNodesToGeoDist(i, myDist, nodeList, distList); int numInRange = (int)nodeList.size(); bool first = true; float bestDist = -1.0f; int32_t bestLabel = unusedLabel; for (int j = 0; j < numInRange; ++j) { if (markArray[nodeList[j]] == 1) { if (first || distList[j] < bestDist) { first = false; bestDist = distList[j]; bestLabel = myInputData[nodeList[j]]; } } } if (!first) { colScratch[i] = bestLabel; } else { nodeList = myTopoHelp->getNodeNeighbors(i); nodeList.push_back(i); myGeoHelp->getGeoToTheseNodes(i, nodeList, distList);//ok, its a little silly to do this numInRange = (int)nodeList.size(); for (int j = 0; j < numInRange; ++j) { if (markArray[nodeList[j]] == 1) { if (first || distList[j] < bestDist) { first = false; bestDist = distList[j]; bestLabel = myInputData[nodeList[j]]; } } } if (!first) { colScratch[i] = bestLabel; } else { colScratch[i] = unusedLabel; } } } else { colScratch[i] = myInputData[i]; } } } myLabelOut->setLabelKeysForColumn(thisCol, colScratch.getArray()); } } else { myLabelOut->setNumberOfNodesAndColumns(numNodes, 1); *(myLabelOut->getLabelTable()) = *(myLabel->getLabelTable()); myLabelOut->setStructure(mySurf->getStructure()); const int32_t* myInputData = myLabel->getLabelKeyPointerForColumn(columnNum); myLabelOut->setColumnName(0, myLabel->getColumnName(columnNum) + " dilated"); if (badNodeRoi == NULL) { for (int i = 0; i < numNodes; ++i) { if (myInputData[i] == unusedLabel) { markArray[i] = 0; } else { markArray[i] = 1; } } } #pragma omp CARET_PAR { CaretPointer myTopoHelp = mySurf->getTopologyHelper(); CaretPointer myGeoHelp; if (corrAreas == NULL) { myGeoHelp = mySurf->getGeodesicHelper(); } else { myGeoHelp.grabNew(new GeodesicHelper(myCorrBase)); } #pragma omp CARET_FOR schedule(dynamic) for (int i = 0; i < numNodes; ++i) { if (markArray[i] == 0) { vector nodeList; vector distList; myGeoHelp->getNodesToGeoDist(i, myDist, nodeList, distList); int numInRange = (int)nodeList.size(); bool first = true; float bestDist = -1.0f; int32_t bestLabel = unusedLabel; for (int j = 0; j < numInRange; ++j) { if (markArray[nodeList[j]] == 1) { if (first || distList[j] < bestDist) { first = false; bestDist = distList[j]; bestLabel = myInputData[nodeList[j]]; } } } if (!first) { colScratch[i] = bestLabel; } else { nodeList = myTopoHelp->getNodeNeighbors(i); nodeList.push_back(i); myGeoHelp->getGeoToTheseNodes(i, nodeList, distList);//ok, its a little silly to do this numInRange = (int)nodeList.size(); for (int j = 0; j < numInRange; ++j) { if (markArray[nodeList[j]] == 1) { if (first || distList[j] < bestDist) { first = false; bestDist = distList[j]; bestLabel = myInputData[nodeList[j]]; } } } if (!first) { colScratch[i] = bestLabel; } else { colScratch[i] = unusedLabel; } } } else { colScratch[i] = myInputData[i]; } } } myLabelOut->setLabelKeysForColumn(0, colScratch.getArray()); } } float AlgorithmLabelDilate::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmLabelDilate::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmLabelDilate.h000066400000000000000000000034401255417355300225710ustar00rootroot00000000000000#ifndef __ALGORITHM_LABEL_DILATE_H__ #define __ALGORITHM_LABEL_DILATE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmLabelDilate : public AbstractAlgorithm { AlgorithmLabelDilate(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmLabelDilate(ProgressObject* myProgObj, const LabelFile* myLabel, const SurfaceFile* mySurf, float myDist, LabelFile* myLabelOut, const MetricFile* badNodeRoi = NULL, int columnNum = -1, const MetricFile* corrAreas = NULL); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmLabelDilate; } #endif //__ALGORITHM_LABEL_DILATE_H__ workbench-1.1.1/src/Algorithms/AlgorithmLabelModifyKeys.cxx000066400000000000000000000242051255417355300240270ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmLabelModifyKeys.h" #include "AlgorithmException.h" #include "FileInformation.h" #include "GiftiLabel.h" #include "GiftiLabelTable.h" #include "LabelFile.h" #include #include using namespace caret; using namespace std; AString AlgorithmLabelModifyKeys::getCommandSwitch() { return "-label-modify-keys"; } AString AlgorithmLabelModifyKeys::getShortDescription() { return "CHANGE KEY VALUES IN A LABEL FILE"; } OperationParameters* AlgorithmLabelModifyKeys::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addLabelParameter(1, "label-in", "the input label file"); ret->addStringParameter(2, "remap-file", "text file with old and new key values"); ret->addLabelOutputParameter(3, "label-out", "output label file"); OptionalParameter* columnOpt = ret->createOptionalParameter(4, "-column", "select a single column to use"); columnOpt->addStringParameter(1, "column", "the column number or name"); ret->setHelpText( AString(" should have lines of the form 'oldkey newkey', like so:\n\n") + "3 5\n5 8\n8 2\n\n" + "This would change the current label with key '3' to use the key '5' instead, 5 would use 8, and 8 would use 2. " + "Any collision in key values results in the label that was not specified in the remap file getting remapped to an otherwise unused key. " + "Remapping more than one key to the same new key, or the same key to more than one new key, results in an error. " + "This will not change the appearance of the file when displayed, it will change the keys in the data at the same time." ); return ret; } void AlgorithmLabelModifyKeys::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LabelFile* labelIn = myParams->getLabel(1); AString remapName = myParams->getString(2); LabelFile* labelOut = myParams->getOutputLabel(3); int column = -1; OptionalParameter* columnOpt = myParams->getOptionalParameter(4); if (columnOpt->m_present) { column = labelIn->getMapIndexFromNameOrNumber(columnOpt->getString(1)); if (column < 0) throw AlgorithmException("invalid column specified"); } FileInformation textFileInfo(remapName); if (!textFileInfo.exists()) { throw AlgorithmException("label list file doesn't exist"); } fstream remapFile(remapName.toLocal8Bit().constData(), fstream::in); if (!remapFile.good()) { throw AlgorithmException("error reading label list file"); } map remap; int32_t oldkey, newkey; while (remapFile >> oldkey >> newkey) { if (remap.find(oldkey) != remap.end()) throw AlgorithmException("remapping tried to duplicate label " + AString::number(oldkey)); remap[oldkey] = newkey; } AlgorithmLabelModifyKeys(myProgObj, labelIn, remap, labelOut, column); } AlgorithmLabelModifyKeys::AlgorithmLabelModifyKeys(ProgressObject* myProgObj, const LabelFile* labelIn, const map& remap, LabelFile* labelOut, const int& column) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); const GiftiLabelTable* oldTable = labelIn->getLabelTable(); int32_t oldUnlabeled = oldTable->getUnassignedLabelKey();//because GiftiLabelTable is quirky, we need to check if the unlabeled value ends up as something other than 0 GiftiLabelTable newTable;//beware: key 0 already has the "???" label bool setZero = false;//because of this, we need to track if we overwrote it, so that we can pretend it isn't there for (map::const_iterator iter = remap.begin(); iter != remap.end(); ++iter) { const GiftiLabel* oldLabel = oldTable->getLabel(iter->first); if (oldLabel == NULL) throw AlgorithmException("label " + AString::number(iter->first) + " does not exist in the input label file"); GiftiLabel newLabel(*oldLabel); newLabel.setKey(iter->second); if (iter->first == oldUnlabeled) { if (iter->second != 0)//if it isn't the default unlabeled value, then we have to do something { if (newTable.getLabel(iter->second) != NULL) throw AlgorithmException("remapping tried to set label " + AString::number(iter->second) + " more than once"); newTable.deleteLabel(0);//delete the default, since we don't know what overwrites it, if anything newTable.insertLabel(&newLabel);//insert forces it to use the key in the label, even if it causes a duplicate name (which the original might theoretically have) } else {//otherwise, just error checking if (setZero) throw AlgorithmException("remapping tried to set label " + AString::number(iter->second) + " more than once"); setZero = true;//we do have to track that zero now contains something that can't be overwritten } } else { if (iter->second == 0)//if it remaps to the default unlabeled key, we have to check it differently { if (setZero) throw AlgorithmException("remapping tried to set label " + AString::number(iter->second) + " more than once"); setZero = true; newTable.insertLabel(&newLabel);//insert forces it to use the key in the label, so it will overwrite the existing default 0 key } else {//finally, the simple case if (newTable.getLabel(iter->second) != NULL) throw AlgorithmException("remapping tried to set label " + AString::number(iter->second) + " more than once"); newTable.insertLabel(&newLabel);//insert forces it to use the key in the label, even if it causes a duplicate name (which the original might theoretically have) } } } set keys = oldTable->getKeys(), collisions; for (set::const_iterator iter = keys.begin(); iter != keys.end(); ++iter) { if (remap.find(*iter) == remap.end())//skip if it was remapped { if (*iter == 0)//again with the special default 0 key { if (setZero)//check for collision { collisions.insert(*iter); } else { setZero = true; if (*iter != oldUnlabeled)//if its merely the unassigned label already, we can just keep the existing default { newTable.insertLabel(oldTable->getLabel(*iter)); } } } else { if (newTable.getLabel(*iter) == NULL) { newTable.insertLabel(oldTable->getLabel(*iter)); } else {//collision collisions.insert(*iter); } } } } map valueChanges = remap;//start with the specified changes, then add the collision changes for (set::const_iterator iter = collisions.begin(); iter != collisions.end(); ++iter) {//now deal with collisions int32_t newKey = newTable.generateUnusedKey(); GiftiLabel newLabel(*(oldTable->getLabel(*iter))); newLabel.setKey(newKey); newTable.insertLabel(&newLabel);//insert forces it to use the key in the label, even if it causes a duplicate name (which the original might theoretically have) valueChanges[*iter] = newKey; } int numNodes = labelIn->getNumberOfNodes(), numColumns = labelIn->getNumberOfColumns(); vector scratchCol(numNodes); if (column == -1) { labelOut->setNumberOfNodesAndColumns(numNodes, numColumns); labelOut->setStructure(labelIn->getStructure()); *(labelOut->getLabelTable()) = newTable; for (int i = 0; i < numColumns; ++i) { labelOut->setColumnName(i, labelIn->getColumnName(i)); const int32_t* inCol = labelIn->getLabelKeyPointerForColumn(i); for (int j = 0; j < numNodes; ++j) { map::const_iterator iter = valueChanges.find(inCol[j]); if (iter == valueChanges.end()) { scratchCol[j] = inCol[j]; } else { scratchCol[j] = iter->second; } } labelOut->setLabelKeysForColumn(i, scratchCol.data()); } } else { labelOut->setNumberOfNodesAndColumns(numNodes, 1); labelOut->setStructure(labelIn->getStructure()); *(labelOut->getLabelTable()) = newTable; labelOut->setColumnName(0, labelIn->getColumnName(column)); const int32_t* inCol = labelIn->getLabelKeyPointerForColumn(column); for (int j = 0; j < numNodes; ++j) { map::const_iterator iter = valueChanges.find(inCol[j]); if (iter == valueChanges.end()) { scratchCol[j] = inCol[j]; } else { scratchCol[j] = iter->second; } } labelOut->setLabelKeysForColumn(0, scratchCol.data()); } } float AlgorithmLabelModifyKeys::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmLabelModifyKeys::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmLabelModifyKeys.h000066400000000000000000000033571255417355300234610ustar00rootroot00000000000000#ifndef __ALGORITHM_LABEL_MODIFY_KEYS_H__ #define __ALGORITHM_LABEL_MODIFY_KEYS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" #include namespace caret { class AlgorithmLabelModifyKeys : public AbstractAlgorithm { AlgorithmLabelModifyKeys(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmLabelModifyKeys(ProgressObject* myProgObj, const LabelFile* labelIn, const std::map& remap, LabelFile* labelOut, const int& column = -1); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmLabelModifyKeys; } #endif //__ALGORITHM_LABEL_MODIFY_KEYS_H__ workbench-1.1.1/src/Algorithms/AlgorithmLabelResample.cxx000066400000000000000000000245521255417355300235210ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmLabelResample.h" #include "AlgorithmException.h" #include "CaretLogger.h" #include "GiftiLabelTable.h" #include "LabelFile.h" #include "MetricFile.h" #include "SurfaceFile.h" #include "SurfaceResamplingHelper.h" using namespace caret; using namespace std; AString AlgorithmLabelResample::getCommandSwitch() { return "-label-resample"; } AString AlgorithmLabelResample::getShortDescription() { return "RESAMPLE A LABEL FILE TO A DIFFERENT MESH"; } OperationParameters* AlgorithmLabelResample::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addLabelParameter(1, "label-in", "the label file to resample"); ret->addSurfaceParameter(2, "current-sphere", "a sphere surface with the mesh that the label file is currently on"); ret->addSurfaceParameter(3, "new-sphere", "a sphere surface that is in register with and has the desired output mesh"); ret->addStringParameter(4, "method", "the method name"); ret->addLabelOutputParameter(5, "label-out", "the output label file"); OptionalParameter* areaSurfsOpt = ret->createOptionalParameter(6, "-area-surfs", "specify surfaces to do vertex area correction based on"); areaSurfsOpt->addSurfaceParameter(1, "current-area", "a relevant anatomical surface with mesh"); areaSurfsOpt->addSurfaceParameter(2, "new-area", "a relevant anatomical surface with mesh"); OptionalParameter* areaMetricsOpt = ret->createOptionalParameter(7, "-area-metrics", "specify vertex area metrics to do area correction based on"); areaMetricsOpt->addMetricParameter(1, "current-area", "a metric file with vertex areas for mesh"); areaMetricsOpt->addMetricParameter(2, "new-area", "a metric file with vertex areas for mesh"); OptionalParameter* roiOpt = ret->createOptionalParameter(8, "-current-roi", "use an input roi on the current mesh to exclude non-data vertices"); roiOpt->addMetricParameter(1, "roi-metric", "the roi, as a metric file"); OptionalParameter* validRoiOutOpt = ret->createOptionalParameter(9, "-valid-roi-out", "output the ROI of vertices that got data from valid source vertices"); validRoiOutOpt->addMetricOutputParameter(1, "roi-out", "the output roi as a metric"); ret->createOptionalParameter(10, "-largest", "use only the label of the vertex with the largest weight"); AString myHelpText = AString("Resamples a label file, given two spherical surfaces that are in register. ") + "If the method does area correction, exactly one of -area-surfs or -area-metrics must be specified.\n\n" + "The -largest option results in nearest vertex behavior when used with BARYCENTRIC, it uses the value of the source vertex that has the largest weight. " + "When -largest is not specified, the vertex weights are summed according to which label they correspond to, and the label with the largest sum is used.\n\n" + "The argument must be one of the following:\n\n"; vector allEnums; SurfaceResamplingMethodEnum::getAllEnums(allEnums); for (int i = 0; i < (int)allEnums.size(); ++i) { myHelpText += SurfaceResamplingMethodEnum::toName(allEnums[i]) + "\n"; } myHelpText += "\nThe ADAP_BARY_AREA method is recommended for label data, because it should be better at resolving vertices that are near multiple labels, or in case of downsampling."; ret->setHelpText(myHelpText); return ret; } void AlgorithmLabelResample::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LabelFile* labelIn = myParams->getLabel(1); SurfaceFile* curSphere = myParams->getSurface(2); SurfaceFile* newSphere = myParams->getSurface(3); bool ok = false; SurfaceResamplingMethodEnum::Enum myMethod = SurfaceResamplingMethodEnum::fromName(myParams->getString(4), &ok); if (!ok) { throw AlgorithmException("invalid method name"); } LabelFile* labelOut = myParams->getOutputLabel(5); MetricFile* curAreas = NULL, *newAreas = NULL; MetricFile curAreasTemp, newAreasTemp; OptionalParameter* areaSurfsOpt = myParams->getOptionalParameter(6); if (areaSurfsOpt->m_present) { switch(myMethod) { case SurfaceResamplingMethodEnum::BARYCENTRIC: CaretLogInfo("This method does not use area correction, -area-surfs is not needed"); break; default: break; } vector nodeAreasTemp; SurfaceFile* curAreaSurf = areaSurfsOpt->getSurface(1); SurfaceFile* newAreaSurf = areaSurfsOpt->getSurface(2); curAreaSurf->computeNodeAreas(nodeAreasTemp); curAreasTemp.setNumberOfNodesAndColumns(curAreaSurf->getNumberOfNodes(), 1); curAreasTemp.setValuesForColumn(0, nodeAreasTemp.data()); curAreas = &curAreasTemp; newAreaSurf->computeNodeAreas(nodeAreasTemp); newAreasTemp.setNumberOfNodesAndColumns(newAreaSurf->getNumberOfNodes(), 1); newAreasTemp.setValuesForColumn(0, nodeAreasTemp.data()); newAreas = &newAreasTemp; } OptionalParameter* areaMetricsOpt = myParams->getOptionalParameter(7); if (areaMetricsOpt->m_present) { if (areaSurfsOpt->m_present) { throw AlgorithmException("only one of -area-surfs and -area-metrics can be specified"); } switch(myMethod) { case SurfaceResamplingMethodEnum::BARYCENTRIC: CaretLogInfo("This method does not use area correction, -area-metrics is not needed"); break; default: break; } curAreas = areaMetricsOpt->getMetric(1); newAreas = areaMetricsOpt->getMetric(2); } OptionalParameter* roiOpt = myParams->getOptionalParameter(8); MetricFile* currentRoi = NULL; if (roiOpt->m_present) { currentRoi = roiOpt->getMetric(1); } MetricFile* validRoiOut = NULL; OptionalParameter* validRoiOutOpt = myParams->getOptionalParameter(9); if (validRoiOutOpt->m_present) { validRoiOut = validRoiOutOpt->getOutputMetric(1); } bool largest = myParams->getOptionalParameter(10)->m_present; AlgorithmLabelResample(myProgObj, labelIn, curSphere, newSphere, myMethod, labelOut, curAreas, newAreas, currentRoi, validRoiOut, largest); } AlgorithmLabelResample::AlgorithmLabelResample(ProgressObject* myProgObj, const LabelFile* labelIn, const SurfaceFile* curSphere, const SurfaceFile* newSphere, const SurfaceResamplingMethodEnum::Enum& myMethod, LabelFile* labelOut, const MetricFile* curAreas, const MetricFile* newAreas, const MetricFile* currentRoi, MetricFile* validRoiOut, const bool& largest) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); if (labelIn->getNumberOfNodes() != curSphere->getNumberOfNodes()) throw AlgorithmException("input label file has different number of nodes than input sphere"); const float* curAreaData = NULL, *newAreaData = NULL; switch (myMethod) { case SurfaceResamplingMethodEnum::BARYCENTRIC: break; default: if (curAreas == NULL || newAreas == NULL) throw AlgorithmException("specified method does area correction, but no vertex area data given"); if (curSphere->getNumberOfNodes() != curAreas->getNumberOfNodes()) throw AlgorithmException("current vertex area data has different number of nodes than current sphere"); if (newSphere->getNumberOfNodes() != newAreas->getNumberOfNodes()) throw AlgorithmException("new vertex area data has different number of nodes than new sphere"); curAreaData = curAreas->getValuePointerForColumn(0); newAreaData = newAreas->getValuePointerForColumn(0); } int numColumns = labelIn->getNumberOfColumns(), numNewNodes = newSphere->getNumberOfNodes(); labelOut->setNumberOfNodesAndColumns(numNewNodes, numColumns); labelOut->setStructure(newSphere->getStructure()); *labelOut->getLabelTable() = *labelIn->getLabelTable(); int32_t unusedLabel = labelIn->getLabelTable()->getUnassignedLabelKey(); vector colScratch(numNewNodes, unusedLabel); const float* roiCol = NULL; if (currentRoi != NULL) roiCol = currentRoi->getValuePointerForColumn(0); SurfaceResamplingHelper myHelp(myMethod, curSphere, newSphere, curAreaData, newAreaData, roiCol); if (validRoiOut != NULL) { validRoiOut->setNumberOfNodesAndColumns(numNewNodes, 1); validRoiOut->setStructure(newSphere->getStructure()); vector scratch(numNewNodes); myHelp.getResampleValidROI(scratch.data()); validRoiOut->setValuesForColumn(0, scratch.data()); } for (int i = 0; i < numColumns; ++i) { labelOut->setColumnName(i, labelIn->getColumnName(i)); if (largest) { myHelp.resampleLargest(labelIn->getLabelKeyPointerForColumn(i), colScratch.data(), unusedLabel); } else { myHelp.resamplePopular(labelIn->getLabelKeyPointerForColumn(i), colScratch.data(), unusedLabel); } labelOut->setLabelKeysForColumn(i, colScratch.data()); } } float AlgorithmLabelResample::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmLabelResample::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmLabelResample.h000066400000000000000000000040111255417355300231320ustar00rootroot00000000000000#ifndef __ALGORITHM_LABEL_RESAMPLE_H__ #define __ALGORITHM_LABEL_RESAMPLE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" #include "SurfaceResamplingMethodEnum.h" namespace caret { class AlgorithmLabelResample : public AbstractAlgorithm { AlgorithmLabelResample(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmLabelResample(ProgressObject* myProgObj, const LabelFile* labelIn, const SurfaceFile* curSphere, const SurfaceFile* newSphere, const SurfaceResamplingMethodEnum::Enum& myMethod, LabelFile* labelOut, const MetricFile* curAreas = NULL, const MetricFile* newAreas = NULL, const MetricFile* currentRoi = NULL, MetricFile* validRoiOut = NULL, const bool& largest = false); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmLabelResample; } #endif //__ALGORITHM_LABEL_RESAMPLE_H__ workbench-1.1.1/src/Algorithms/AlgorithmLabelToBorder.cxx000066400000000000000000000150361255417355300234660ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmLabelToBorder.h" #include "AlgorithmException.h" #include "Border.h" #include "BorderFile.h" #include "BorderTracingHelper.h" #include "GiftiLabelTable.h" #include "LabelFile.h" #include "SurfaceFile.h" using namespace caret; using namespace std; AString AlgorithmLabelToBorder::getCommandSwitch() { return "-label-to-border"; } AString AlgorithmLabelToBorder::getShortDescription() { return "DRAW BORDERS AROUND LABELS"; } OperationParameters* AlgorithmLabelToBorder::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceParameter(1, "surface", "the surface to use for neighbor information"); ret->addLabelParameter(2, "label-in", "the input label file"); ret->addBorderOutputParameter(3, "border-out", "the output border file"); OptionalParameter* placeOpt = ret->createOptionalParameter(4, "-placement", "set how far along the edge border points are drawn"); placeOpt->addDoubleParameter(1, "fraction", "fraction along edge from inside vertex (default 0.33)"); OptionalParameter* columnSelect = ret->createOptionalParameter(5, "-column", "select a single column"); columnSelect->addStringParameter(1, "column", "the column number or name"); ret->setHelpText( AString("For each label, finds all edges on the mesh that cross the boundary of the label, and draws borders through them. ") + "By default, this is done on all columns in the input file, using the map name as the class name for the border." ); return ret; } void AlgorithmLabelToBorder::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { SurfaceFile* mySurf = myParams->getSurface(1); LabelFile* myLabel = myParams->getLabel(2); BorderFile* myBorderOut = myParams->getOutputBorder(3); float placement = 0.33f; OptionalParameter* placeOpt = myParams->getOptionalParameter(4); if (placeOpt->m_present) { placement = (float)placeOpt->getDouble(1); } OptionalParameter* columnSelect = myParams->getOptionalParameter(5); int columnNum = -1; if (columnSelect->m_present) {//set up to use the single column columnNum = (int)myLabel->getMapIndexFromNameOrNumber(columnSelect->getString(1)); if (columnNum < 0) { throw AlgorithmException("invalid column specified"); } } AlgorithmLabelToBorder(myProgObj, mySurf, myLabel, myBorderOut, placement, columnNum); } AlgorithmLabelToBorder::AlgorithmLabelToBorder(ProgressObject* myProgObj, const SurfaceFile* mySurf, const LabelFile* myLabel, BorderFile* myBorderOut, const float& placement, const int& columnNum) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); if (mySurf->getNumberOfNodes() != myLabel->getNumberOfNodes()) throw AlgorithmException("label file does not match surface file number of vertices"); if (placement < 0.0f || placement > 1.0f || placement != placement) throw AlgorithmException("placement must be between 0 and 1"); if (columnNum < -1 || columnNum > myLabel->getNumberOfColumns()) throw AlgorithmException("invalid column specified"); myBorderOut->setStructure(mySurf->getStructure()); myBorderOut->setNumberOfNodes(mySurf->getNumberOfNodes()); BorderTracingHelper myHelper(mySurf); if (columnNum == -1) { for (int col = 0; col < myLabel->getNumberOfColumns(); ++col) { const GiftiLabelTable* myTable = myLabel->getLabelTable(); set myKeys = myTable->getKeys(); int32_t unassignedKey = myTable->getUnassignedLabelKey(); for (set::iterator iter = myKeys.begin(); iter != myKeys.end(); ++iter) { if (*iter == unassignedKey) continue; vector > result = myHelper.traceData(myLabel->getLabelKeyPointerForColumn(col), BorderTracingHelper::LabelSelect(*iter), placement); AString borderName = myTable->getLabelName(*iter); myBorderOut->getNameColorTable()->addLabel(myTable->getLabel(*iter)); for (int i = 0; i < (int)result.size(); ++i) { result[i]->setClassName(myLabel->getMapName(col)); result[i]->setName(borderName); myBorderOut->addBorder(result[i].releasePointer());//NOTE: addBorder takes ownership of a RAW POINTER, shared_ptr won't release the pointer } } } } else { const GiftiLabelTable* myTable = myLabel->getLabelTable(); set myKeys = myTable->getKeys(); int32_t unassignedKey = myTable->getUnassignedLabelKey(); for (set::iterator iter = myKeys.begin(); iter != myKeys.end(); ++iter) { if (*iter == unassignedKey) continue; vector > result = myHelper.traceData(myLabel->getLabelKeyPointerForColumn(columnNum), BorderTracingHelper::LabelSelect(*iter), placement); AString borderName = myTable->getLabelName(*iter); myBorderOut->getNameColorTable()->addLabel(myTable->getLabel(*iter)); for (int i = 0; i < (int)result.size(); ++i) { result[i]->setClassName(myLabel->getMapName(columnNum)); result[i]->setName(borderName); myBorderOut->addBorder(result[i].releasePointer());//ditto } } } } float AlgorithmLabelToBorder::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmLabelToBorder::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmLabelToBorder.h000066400000000000000000000034071255417355300231120ustar00rootroot00000000000000#ifndef __ALGORITHM_LABEL_TO_BORDER_H__ #define __ALGORITHM_LABEL_TO_BORDER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmLabelToBorder : public AbstractAlgorithm { AlgorithmLabelToBorder(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmLabelToBorder(ProgressObject* myProgObj, const SurfaceFile* mySurf, const LabelFile* myLabel, BorderFile* myBorderOut, const float& placement = 0.33f, const int& columnNum = -1); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmLabelToBorder; } #endif //__ALGORITHM_LABEL_TO_BORDER_H__ workbench-1.1.1/src/Algorithms/AlgorithmMetricDilate.cxx000066400000000000000000000776411255417355300233660ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmMetricDilate.h" #include "AlgorithmException.h" #include "CaretAssert.h" #include "CaretOMP.h" #include "GeodesicHelper.h" #include "MetricFile.h" #include "PaletteColorMapping.h" #include "SurfaceFile.h" #include "TopologyHelper.h" #include "Vector3D.h" #include #include using namespace caret; using namespace std; AString AlgorithmMetricDilate::getCommandSwitch() { return "-metric-dilate"; } AString AlgorithmMetricDilate::getShortDescription() { return "DILATE A METRIC FILE"; } OperationParameters* AlgorithmMetricDilate::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addMetricParameter(1, "metric", "the metric to dilate"); ret->addSurfaceParameter(2, "surface", "the surface to compute on"); ret->addDoubleParameter(3, "distance", "distance in mm to dilate"); ret->addMetricOutputParameter(4, "metric-out", "the output metric"); OptionalParameter* badRoiOpt = ret->createOptionalParameter(5, "-bad-vertex-roi", "specify an roi of vertices to overwrite, rather than vertices with value zero"); badRoiOpt->addMetricParameter(1, "roi-metric", "metric file, positive values denote vertices to have their values replaced"); OptionalParameter* dataRoiOpt = ret->createOptionalParameter(9, "-data-roi", "specify an roi of where there is data"); dataRoiOpt->addMetricParameter(1, "roi-metric", "metric file, positive values denote vertices that have data"); OptionalParameter* columnSelect = ret->createOptionalParameter(6, "-column", "select a single column to dilate"); columnSelect->addStringParameter(1, "column", "the column number or name"); ret->createOptionalParameter(7, "-nearest", "use the nearest good value instead of a weighted average"); ret->createOptionalParameter(10, "-linear", "fill in values with linear interpolation along strongest gradient"); OptionalParameter* exponentOpt = ret->createOptionalParameter(8, "-exponent", "use a different exponent in the weighting function"); exponentOpt->addDoubleParameter(1, "exponent", "exponent 'n' to use in (area / (distance ^ n)) as the weighting function (default 2)"); OptionalParameter* corrAreaOpt = ret->createOptionalParameter(11, "-corrected-areas", "vertex areas to use instead of computing them from the surface"); corrAreaOpt->addMetricParameter(1, "area-metric", "the corrected vertex areas, as a metric"); ret->setHelpText( AString("For all metric vertices that are designated as bad, if they neighbor a non-bad vertex with data or are within the specified distance of such a vertex, ") + "replace the value with a distance weighted average of nearby non-bad vertices that have data, otherwise set the value to zero. " + "No matter how small is, dilation will always use at least the immediate neighbor vertices. " + "If -nearest is specified, it will use the value from the closest non-bad vertex with data within range instead of a weighted average.\n\n" + "If -bad-vertex-roi is specified, only vertices with a positive value in the ROI are bad. " + "If it is not specified, only vertices that have data, with a value of zero, are bad. " + "If -data-roi is not specified, all vertices are assumed to have data.\n\n" + "Note that the -corrected-areas option uses an approximate correction for the change in distances along a group average surface." ); return ret; } void AlgorithmMetricDilate::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { MetricFile* myMetric = myParams->getMetric(1); SurfaceFile* mySurf = myParams->getSurface(2); float distance = (float)myParams->getDouble(3); MetricFile* myMetricOut = myParams->getOutputMetric(4); OptionalParameter* badRoiOpt = myParams->getOptionalParameter(5); MetricFile* badNodeRoi = NULL; if (badRoiOpt->m_present) { badNodeRoi = badRoiOpt->getMetric(1); } OptionalParameter* dataRoiOpt = myParams->getOptionalParameter(9); MetricFile* dataRoi = NULL; if (dataRoiOpt->m_present) { dataRoi = dataRoiOpt->getMetric(1); } OptionalParameter* columnSelect = myParams->getOptionalParameter(6); int columnNum = -1; if (columnSelect->m_present) {//set up to use the single column columnNum = (int)myMetric->getMapIndexFromNameOrNumber(columnSelect->getString(1)); if (columnNum < 0) { throw AlgorithmException("invalid column specified"); } } bool nearest = myParams->getOptionalParameter(7)->m_present; bool linear = myParams->getOptionalParameter(10)->m_present; if (nearest && linear) throw AlgorithmException("-nearest and -linear may not be specified together"); float exponent = 2.0f; OptionalParameter* exponentOpt = myParams->getOptionalParameter(8); if (exponentOpt->m_present) { exponent = (float)exponentOpt->getDouble(1); } OptionalParameter* corrAreaOpt = myParams->getOptionalParameter(11); MetricFile* corrAreas = NULL; if (corrAreaOpt->m_present) { corrAreas = corrAreaOpt->getMetric(1); } AlgorithmMetricDilate(myProgObj, myMetric, mySurf, distance, myMetricOut, badNodeRoi, dataRoi, columnNum, nearest, linear, exponent, corrAreas); } AlgorithmMetricDilate::AlgorithmMetricDilate(ProgressObject* myProgObj, const MetricFile* myMetric, const SurfaceFile* mySurf, const float& distance, MetricFile* myMetricOut, const MetricFile* badNodeRoi, const MetricFile* dataRoi, const int& columnNum, const bool& nearest, const bool& linear, const float& exponent, const MetricFile* corrAreas) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); if (nearest && linear) throw AlgorithmException("cannot dilate in nearest and linear modes together"); int numNodes = mySurf->getNumberOfNodes(); if (numNodes != myMetric->getNumberOfNodes()) { throw AlgorithmException("surface and metric number of vertices do not match"); } if (badNodeRoi != NULL && badNodeRoi->getNumberOfNodes() != numNodes) { throw AlgorithmException("bad vertex roi number of vertices does not match"); } if (dataRoi != NULL && dataRoi->getNumberOfNodes() != numNodes) { throw AlgorithmException("data roi number of vertices does not match"); } if (corrAreas != NULL && corrAreas->getNumberOfNodes() != numNodes) { throw AlgorithmException("corrected areas metric number of vertices does not match"); } if (columnNum < -1 || columnNum >= myMetric->getNumberOfColumns()) { throw AlgorithmException("invalid column specified"); } if (distance < 0.0f) { throw AlgorithmException("invalid distance specified"); } myMetricOut->setStructure(mySurf->getStructure()); vector > myStencils;//because we need to iterate over it in parallel vector > myNearest; vector colScratch(numNodes); vector myAreasData; const float* myAreas = NULL; if (corrAreas == NULL) { mySurf->computeNodeAreas(myAreasData); myAreas = myAreasData.data(); } else { myAreas = corrAreas->getValuePointerForColumn(0); } if (!linear && badNodeRoi != NULL) { if (nearest) { precomputeNearest(myNearest, mySurf, badNodeRoi, dataRoi, corrAreas, distance); } else { precomputeStencils(myStencils, mySurf, myAreas, badNodeRoi, dataRoi, corrAreas, distance, exponent); } } if (columnNum == -1) { myMetricOut->setNumberOfNodesAndColumns(numNodes, myMetric->getNumberOfColumns()); for (int thisCol = 0; thisCol < myMetric->getNumberOfColumns(); ++thisCol) { *(myMetricOut->getMapPaletteColorMapping(thisCol)) = *(myMetric->getMapPaletteColorMapping(thisCol)); const float* myInputData = myMetric->getValuePointerForColumn(thisCol); myMetricOut->setColumnName(thisCol, myMetric->getColumnName(thisCol)); if (linear || badNodeRoi == NULL) { processColumn(colScratch.data(), myInputData, mySurf, myAreas, badNodeRoi, dataRoi, corrAreas, distance, nearest, linear, exponent); } else { if (nearest) { processColumn(colScratch.data(), numNodes, myInputData, myNearest); } else { processColumn(colScratch.data(), numNodes, myInputData, myStencils); } } myMetricOut->setValuesForColumn(thisCol, colScratch.data()); } } else { myMetricOut->setNumberOfNodesAndColumns(numNodes, 1); *(myMetricOut->getMapPaletteColorMapping(0)) = *(myMetric->getMapPaletteColorMapping(columnNum)); const float* myInputData = myMetric->getValuePointerForColumn(columnNum); myMetricOut->setColumnName(0, myMetric->getColumnName(columnNum)); if (linear || badNodeRoi == NULL) { processColumn(colScratch.data(), myInputData, mySurf, myAreas, badNodeRoi, dataRoi, corrAreas, distance, nearest, linear, exponent); } else { if (nearest) { processColumn(colScratch.data(), numNodes, myInputData, myNearest); } else { processColumn(colScratch.data(), numNodes, myInputData, myStencils); } } myMetricOut->setValuesForColumn(0, colScratch.data()); } } void AlgorithmMetricDilate::processColumn(float* colScratch, const int& numNodes, const float* myInputData, vector > myNearest) { for (int i = 0; i < numNodes; ++i) { colScratch[i] = myInputData[i];//precopy so that the parallel part doesn't have to worry about vertices that don't get dilated to } int numStencils = (int)myNearest.size(); #pragma omp CARET_PARFOR schedule(dynamic) for (int i = 0; i < numStencils; ++i)//no idea if parallel actually helps here { const int& node = myNearest[i].first; const int& nearest = myNearest[i].second; if (nearest != -1) { colScratch[node] = myInputData[nearest]; } else { colScratch[node] = 0.0f; } } } void AlgorithmMetricDilate::processColumn(float* colScratch, const int& numNodes, const float* myInputData, vector > myStencils) { for (int i = 0; i < numNodes; ++i) { colScratch[i] = myInputData[i];//precopy so that the parallel part doesn't have to worry about vertices that don't get dilated to } int numStencils = (int)myStencils.size(); #pragma omp CARET_PARFOR schedule(dynamic) for (int i = 0; i < numStencils; ++i)//does parallel help here? { const int& node = myStencils[i].first; const StencilElem& stencil = myStencils[i].second; int numWeights = (int)stencil.m_weightlist.size(); if (numWeights > 0) { double accum = 0.0; for (int j = 0; j < numWeights; ++j) { accum += myInputData[stencil.m_weightlist[j].first] * stencil.m_weightlist[j].second; } colScratch[node] = accum / stencil.m_weightsum; } else { colScratch[node] = 0.0f; } } } void AlgorithmMetricDilate::processColumn(float* colScratch, const float* myInputData, const SurfaceFile* mySurf, const float* myAreas, const MetricFile* badNodeRoi, const MetricFile* dataRoi, const MetricFile* corrAreas, const float& distance, const bool& nearest, const bool& linear, const float& exponent) { float cutoffRatio = 1.5f, test = pow(10.0f, 1.0f / exponent);//find what cutoff ratio corresponds to a tenth of weight, but don't use more than a 1.5 * nearest cutoff if (test > 1.0f && test < cutoffRatio)//if it is less than 1, the exponent is weird, so simply ignore it and use default { if (test > 1.1f) { cutoffRatio = test; } else { cutoffRatio = 1.1f; } } int numNodes = mySurf->getNumberOfNodes(); vector charRoi(numNodes); const float* dataRoiVals = NULL; if (dataRoi != NULL) { dataRoiVals = dataRoi->getValuePointerForColumn(0); for (int i = 0; i < numNodes; ++i) { if (dataRoiVals[i] > 0.0f && myInputData[i] != 0.0f) { charRoi[i] = 1; } else { charRoi[i] = 0; } } } else { for (int i = 0; i < numNodes; ++i) { if (myInputData[i] != 0.0f) { charRoi[i] = 1; } else { charRoi[i] = 0; } } } const float* badRoiData = NULL; if (badNodeRoi != NULL) badRoiData = badNodeRoi->getValuePointerForColumn(0); CaretPointer correctedBase; if (corrAreas != NULL) { correctedBase.grabNew(new GeodesicHelperBase(mySurf, corrAreas->getValuePointerForColumn(0)));//NOTE: myAreas also points to this when applicable } #pragma omp CARET_PAR { CaretPointer myTopoHelp = mySurf->getTopologyHelper(); CaretPointer myGeoHelp; if (corrAreas == NULL) { myGeoHelp = mySurf->getGeodesicHelper(); } else { myGeoHelp.grabNew(new GeodesicHelper(correctedBase)); } #pragma omp CARET_FOR schedule(dynamic) for (int i = 0; i < numNodes; ++i) { bool badNode; if (badRoiData != NULL) { badNode = (badRoiData[i] > 0.0f); } else { badNode = (myInputData[i] == 0.0f); } if ((dataRoiVals == NULL || dataRoiVals[i] > 0.0f) && badNode) { float closestDist; int closestNode = myGeoHelp->getClosestNodeInRoi(i, charRoi.data(), distance, closestDist); if (closestNode == -1)//check neighbors, to ensure we dilate by at least one node everywhere { const vector& nodeList = myTopoHelp->getNodeNeighbors(i); vector distList; myGeoHelp->getGeoToTheseNodes(i, nodeList, distList);//ok, its a little silly to do this const int numInRange = (int)nodeList.size(); for (int j = 0; j < numInRange; ++j) { if (charRoi[nodeList[j]] != 0 && (closestNode == -1 || distList[j] < closestDist)) { closestNode = nodeList[j]; closestDist = distList[j]; } } } if (closestNode == -1) { colScratch[i] = 0.0f; } else { if (nearest) { colScratch[i] = myInputData[closestNode]; } else { vector nodeList; vector distList; if (linear) { myGeoHelp->getNodesToGeoDist(i, distance, nodeList, distList); int numInRange = (int)nodeList.size(); Vector3D center = mySurf->getCoordinate(i); vector blockDists; vector blockPath; vector usableNodes; vector usableDists; for (int j = 0; j < numInRange; ++j)//prescan what is usable, and also exclude things that are through a valid node { if (badRoiData != NULL) { badNode = (badRoiData[nodeList[j]] > 0.0f); } else { badNode = (myInputData[nodeList[j]] == 0.0f); } if ((dataRoiVals == NULL || dataRoiVals[nodeList[j]] > 0.0f) && !badNode) { myGeoHelp->getPathAlongLineSegment(i, nodeList[j], center, mySurf->getCoordinate(nodeList[j]), blockPath, blockDists); CaretAssert(blockPath.size() > 0 && blockPath[0] == i);//we already know that i is "bad", skip it bool usable = true; for (int k = 1; k < (int)blockPath.size() - 1; ++k)//and don't test the endpoint { if (dataRoiVals == NULL || dataRoiVals[blockPath[k]] > 0.0f) { if (badRoiData != NULL) { if (!(badRoiData[blockPath[k]] > 0.0f))//"not greater than" to trap NaNs { usable = false; break; } } else { if (myInputData[blockPath[k]] != 0.0f) { usable = false; break; } } } } if (usable) { usableNodes.push_back(nodeList[j]); usableDists.push_back(distList[j]); } } } int numUsable = (int)usableNodes.size(); float bestGradient = -1.0f; int bestj = -1, bestk = -1; for (int j = 0; j < numUsable; ++j) { int node1 = usableNodes[j]; for (int k = j + 1; k < numUsable; ++k) { int node2 = usableNodes[k]; float grad = abs(myInputData[node1] - myInputData[node2]) / (usableDists[j] + usableDists[k]); if (grad > bestGradient) { bestGradient = grad; bestj = j; bestk = k; } } } if (bestj == -1) { colScratch[i] = myInputData[closestNode]; } else { int node1 = usableNodes[bestj], node2 = usableNodes[bestk]; colScratch[i] = myInputData[node1] + (myInputData[node2] - myInputData[node1]) * usableDists[bestj] / (usableDists[bestj] + usableDists[bestk]); } } else { myGeoHelp->getNodesToGeoDist(i, closestDist * cutoffRatio, nodeList, distList); int numInRange = (int)nodeList.size(); float totalWeight = 0.0f, weightedSum = 0.0f; for (int j = 0; j < numInRange; ++j) { if (charRoi[nodeList[j]] != 0) { float weight; const float tolerance = 0.9f;//distances should NEVER be less than closestDist, for obvious reasons float divdist = distList[j] / closestDist; if (divdist > tolerance)//tricky: if closestDist is zero, this filters between NaN and inf, resulting in a straight average between nodes with 0 distance { weight = myAreas[nodeList[j]] / pow(divdist, exponent);//NOTE: myAreas has already been pointed to the right data with -corrected-areas } else { weight = myAreas[nodeList[j]] / pow(tolerance, exponent); } totalWeight += weight; weightedSum += myInputData[nodeList[j]] * weight; } } if (totalWeight != 0.0f) { colScratch[i] = weightedSum / totalWeight; } else { colScratch[i] = 0.0f; } } } } } else { colScratch[i] = myInputData[i]; } } } } void AlgorithmMetricDilate::precomputeStencils(vector >& myStencils, const SurfaceFile* mySurf, const float* myAreas, const MetricFile* badNodeRoi, const MetricFile* dataRoi, const MetricFile* corrAreas, const float& distance, const float& exponent) { CaretAssert(badNodeRoi != NULL);//because it should never be called if we don't know exactly what nodes we are replacing const float* badNodeData = badNodeRoi->getValuePointerForColumn(0); float cutoffRatio = 1.5f, test = pow(10.0f, 1.0f / exponent);//find what cutoff ratio corresponds to a tenth of weight, but don't use more than a 1.5 * nearest cutoff if (test > 1.0f && test < cutoffRatio)//if it is less than 1, the exponent is weird, so simply ignore it and use default { if (test > 1.1f) { cutoffRatio = test; } else { cutoffRatio = 1.1f; } } int numNodes = mySurf->getNumberOfNodes(); vector charRoi(numNodes); const float* dataRoiVals = NULL; int badCount = 0; if (dataRoi != NULL) { dataRoiVals = dataRoi->getValuePointerForColumn(0); for (int i = 0; i < numNodes; ++i) { if (!(badNodeData[i] > 0.0f))//in case some clown uses NaN as "bad" in the ROI { if (dataRoiVals[i] > 0.0f) { charRoi[i] = 1; } else { charRoi[i] = 0; } } else { if (dataRoiVals[i] > 0.0f) { ++badCount; } charRoi[i] = 0; } } } else { for (int i = 0; i < numNodes; ++i) { if (!(badNodeData[i] > 0.0f)) { charRoi[i] = 1; } else { ++badCount; charRoi[i] = 0; } } } myStencils.resize(badCount);//initializes all stencils to have empty lists badCount = 0; CaretPointer correctedBase; if (corrAreas != NULL) { correctedBase.grabNew(new GeodesicHelperBase(mySurf, corrAreas->getValuePointerForColumn(0)));//NOTE: myAreas also points to this when applicable } #pragma omp CARET_PAR { CaretPointer myTopoHelp = mySurf->getTopologyHelper(); CaretPointer myGeoHelp; if (corrAreas == NULL) { myGeoHelp = mySurf->getGeodesicHelper(); } else { myGeoHelp.grabNew(new GeodesicHelper(correctedBase)); } #pragma omp CARET_FOR schedule(dynamic) for (int i = 0; i < numNodes; ++i) { if (badNodeData[i] > 0.0f && (dataRoiVals == NULL || dataRoiVals[i] > 0.0f)) { int myIndex; #pragma omp critical { myIndex = badCount; ++badCount; } myStencils[myIndex].first = i; StencilElem& myElem = myStencils[myIndex].second; float closestDist; int closestNode = myGeoHelp->getClosestNodeInRoi(i, charRoi.data(), distance, closestDist); if (closestNode == -1)//check neighbors, to ensure we dilate by at least one node everywhere { const vector& nodeList = myTopoHelp->getNodeNeighbors(i); vector distList; myGeoHelp->getGeoToTheseNodes(i, nodeList, distList);//ok, its a little silly to do this const int numInRange = (int)nodeList.size(); for (int j = 0; j < numInRange; ++j) { if (charRoi[nodeList[j]] != 0 && (closestNode == -1 || distList[j] < closestDist)) { closestNode = nodeList[j]; closestDist = distList[j]; } } } if (closestNode != -1) { vector nodeList; vector distList; myGeoHelp->getNodesToGeoDist(i, closestDist * cutoffRatio, nodeList, distList); int numInRange = (int)nodeList.size(); myElem.m_weightsum = 0.0f; for (int j = 0; j < numInRange; ++j) { if (charRoi[nodeList[j]] != 0) { float weight; const float tolerance = 0.9f;//distances should NEVER be less than closestDist, for obvious reasons float divdist = distList[j] / closestDist; if (divdist > tolerance)//tricky: if closestDist is zero, this filters between NaN and inf, resulting in a straight average between nodes with 0 distance { weight = myAreas[nodeList[j]] / pow(divdist, exponent);//NOTE: myAreas has already been pointed to the right data with -corrected-areas } else { weight = myAreas[nodeList[j]] / pow(tolerance, exponent); } myElem.m_weightsum += weight; myElem.m_weightlist.push_back(pair(nodeList[j], weight)); } } if (myElem.m_weightsum == 0.0f)//set list to empty instead of making NaNs { myElem.m_weightlist.clear(); } } } } } } void AlgorithmMetricDilate::precomputeNearest(vector >& myNearest, const SurfaceFile* mySurf, const MetricFile* badNodeRoi, const MetricFile* dataRoi, const MetricFile* corrAreas, const float& distance) { CaretAssert(badNodeRoi != NULL);//because it should never be called if we don't know exactly what nodes we are replacing const float* badNodeData = badNodeRoi->getValuePointerForColumn(0); int numNodes = mySurf->getNumberOfNodes(); vector charRoi(numNodes); const float* dataRoiVals = NULL; int badCount = 0; if (dataRoi != NULL) { dataRoiVals = dataRoi->getValuePointerForColumn(0); for (int i = 0; i < numNodes; ++i) { if (!(badNodeData[i] > 0.0f))//in case some clown uses NaN as "bad" in the ROI { if (dataRoiVals[i] > 0.0f) { charRoi[i] = 1; } else { charRoi[i] = 0; } } else { if (dataRoiVals[i] > 0.0f) { ++badCount; } charRoi[i] = 0; } } } else { for (int i = 0; i < numNodes; ++i) { if (!(badNodeData[i] > 0.0f)) { charRoi[i] = 1; } else { ++badCount; charRoi[i] = 0; } } } myNearest.resize(badCount); badCount = 0; CaretPointer correctedBase; if (corrAreas != NULL) { correctedBase.grabNew(new GeodesicHelperBase(mySurf, corrAreas->getValuePointerForColumn(0)));//NOTE: myAreas also points to this when applicable } #pragma omp CARET_PAR { CaretPointer myTopoHelp = mySurf->getTopologyHelper(); CaretPointer myGeoHelp; if (corrAreas == NULL) { myGeoHelp = mySurf->getGeodesicHelper(); } else { myGeoHelp.grabNew(new GeodesicHelper(correctedBase)); } #pragma omp CARET_FOR schedule(dynamic) for (int i = 0; i < numNodes; ++i) { if (badNodeData[i] > 0.0f && (dataRoiVals == NULL || dataRoiVals[i] > 0.0f)) { int myIndex; #pragma omp critical { myIndex = badCount; ++badCount; } myNearest[myIndex].first = i; float closestDist; int closestNode = myGeoHelp->getClosestNodeInRoi(i, charRoi.data(), distance, closestDist); if (closestNode == -1)//check neighbors, to ensure we dilate by at least one node everywhere { const vector& nodeList = myTopoHelp->getNodeNeighbors(i); vector distList; myGeoHelp->getGeoToTheseNodes(i, nodeList, distList);//ok, its a little silly to do this const int numInRange = (int)nodeList.size(); for (int j = 0; j < numInRange; ++j) { if (charRoi[nodeList[j]] != 0 && (closestNode == -1 || distList[j] < closestDist)) { closestNode = nodeList[j]; closestDist = distList[j]; } } } myNearest[myIndex].second = closestNode; } } } } float AlgorithmMetricDilate::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmMetricDilate::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmMetricDilate.h000066400000000000000000000064371255417355300230060ustar00rootroot00000000000000#ifndef __ALGORITHM_METRIC_DILATE_H__ #define __ALGORITHM_METRIC_DILATE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmMetricDilate : public AbstractAlgorithm { struct StencilElem { std::vector > m_weightlist; float m_weightsum; }; AlgorithmMetricDilate(); void precomputeStencils(std::vector >& myStencils, const SurfaceFile* mySurf, const float* myAreas, const MetricFile* badNodeRoi, const MetricFile* dataRoi, const MetricFile* corrAreas, const float& distance, const float& exponent); void precomputeNearest(std::vector >& myNearest, const SurfaceFile* mySurf, const MetricFile* badNodeRoi, const MetricFile* dataRoi, const MetricFile* corrAreas, const float& distance); void processColumn(float* colScratch, const int& numNodes, const float* myInputData, std::vector > myNearest); void processColumn(float* colScratch, const int& numNodes, const float* myInputData, std::vector > myStencils); void processColumn(float* colScratch, const float* myInputData, const SurfaceFile* mySurf, const float* myAreas, const MetricFile* badNodeRoi, const MetricFile* dataRoi, const MetricFile* corrAreas, const float& distance, const bool& nearest, const bool& linear, const float& exponent); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmMetricDilate(ProgressObject* myProgObj, const MetricFile* myMetric, const SurfaceFile* mySurf, const float& distance, MetricFile* myMetricOut, const MetricFile* badNodeRoi = NULL, const MetricFile* dataRoi = NULL, const int& columnNum = -1, const bool& nearest = false, const bool& linear = false, const float& exponent = 2.0f, const MetricFile* corrAreas = NULL); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmMetricDilate; } #endif //__ALGORITHM_METRIC_DILATE_H__ workbench-1.1.1/src/Algorithms/AlgorithmMetricEstimateFWHM.cxx000066400000000000000000000136251255417355300244110ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmMetricEstimateFWHM.h" #include "AlgorithmException.h" #include "DescriptiveStatistics.h" #include "MetricFile.h" #include "SurfaceFile.h" #include "TopologyHelper.h" #include using namespace caret; using namespace std; AString AlgorithmMetricEstimateFWHM::getCommandSwitch() { return "-metric-estimate-fwhm"; } AString AlgorithmMetricEstimateFWHM::getShortDescription() { return "ESTIMATE FWHM SMOOTHNESS OF A METRIC FILE"; } OperationParameters* AlgorithmMetricEstimateFWHM::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceParameter(1, "surface", "the surface to use for distance and neighbor information"); ret->addMetricParameter(2, "metric-in", "the input metric"); OptionalParameter* roiOpt = ret->createOptionalParameter(3, "-roi", "use only data within an ROI"); roiOpt->addMetricParameter(1, "roi-metric", "the metric file to use as an ROI"); OptionalParameter* columnSelect = ret->createOptionalParameter(4, "-column", "select a single column to estimate smoothness of"); columnSelect->addStringParameter(1, "column", "the column number or name"); ret->setHelpText( AString("Estimates the smoothness of the metric columns, printing the estimates to standard output. ") + "These estimates ignore variation in vertex spacing." ); return ret; } void AlgorithmMetricEstimateFWHM::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); SurfaceFile* mySurf = myParams->getSurface(1); MetricFile* myMetric = myParams->getMetric(2); OptionalParameter* roiOpt = myParams->getOptionalParameter(3); MetricFile* roi = NULL; if (roiOpt->m_present) { roi = roiOpt->getMetric(1); } OptionalParameter* columnSelect = myParams->getOptionalParameter(4); int columnNum = -1; int numColumns = myMetric->getNumberOfMaps(); if (columnSelect->m_present) { columnNum = (int)myMetric->getMapIndexFromNameOrNumber(columnSelect->getString(1)); if (columnNum < 0 || columnNum >= numColumns) { throw AlgorithmException("invalid column specified"); } } if (columnNum == -1) { for (int i = 0; i < numColumns; ++i) { float result = estimateFWHM(mySurf, myMetric, roi, i); if (numColumns > 1) cout << "column " << i + 1 << " "; cout << "FWHM: " << result << endl; } } else { float result = estimateFWHM(mySurf, myMetric, roi, columnNum); if (numColumns > 1) cout << "column " << columnNum + 1 << " "; cout << "FWHM: " << result << endl; } } float AlgorithmMetricEstimateFWHM::estimateFWHM(const SurfaceFile* mySurf, const MetricFile* input, const MetricFile* roi, const int64_t& column) { CaretAssert(column >= 0 && column < input->getNumberOfColumns()); int numNodes = input->getNumberOfNodes(); const float* inCol = input->getValuePointerForColumn(column); const float* roiCol = NULL; if (roi != NULL) { if (roi->getNumberOfNodes() != numNodes) { throw AlgorithmException("roi metric has a different number of nodes than the input metric"); } roiCol = roi->getValuePointerForColumn(0); } DescriptiveStatistics nodeSpacingStats; mySurf->getNodesSpacingStatistics(nodeSpacingStats);//this will be slow since it recomputes - should change it to returning a const reference, and make it a lazy member double globalAccum = 0.0, localAccum = 0.0; int64_t globalCount = 0, localCount = 0; CaretPointer myHelp = mySurf->getTopologyHelper(); for (int i = 0; i < numNodes; ++i)//the local difference mean will be zero, as we don't have directionality, so don't bother collecting it { if (roi == NULL || roiCol[i] > 0.0f) { float center = inCol[i]; globalAccum += center; ++globalCount; } } float globalMean = globalAccum / globalCount; globalAccum = 0.0; for (int i = 0; i < numNodes; ++i) { if (roi == NULL || roiCol[i] > 0.0f) { float center = inCol[i]; float tempf = center - globalMean; globalAccum += tempf * tempf;//don't need to recalculate count const vector& neighbors = myHelp->getNodeNeighbors(i); for (int j = 0; j < (int)neighbors.size(); ++j) { if (neighbors[j] > i && (roi == NULL || roiCol[neighbors[j]] > 0.0f))//collect lopsided to get correct degrees of freedom (if n-1 denom is desired), mean is assumed zero so it works out { tempf = center - inCol[neighbors[j]]; localAccum += tempf * tempf; ++localCount; } } } } float globalVariance = globalAccum / globalCount; float localVariance = localAccum / localCount; float ret = nodeSpacingStats.getMean() * sqrt(-2.0f * log(2.0f) / log(1.0f - localVariance / (2.0f * globalVariance))); return ret; } workbench-1.1.1/src/Algorithms/AlgorithmMetricEstimateFWHM.h000066400000000000000000000031011255417355300240220ustar00rootroot00000000000000#ifndef __ALGORITHM_METRIC_ESTIMATE_FWHM_H__ #define __ALGORITHM_METRIC_ESTIMATE_FWHM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmMetricEstimateFWHM : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); static float estimateFWHM(const SurfaceFile* mySurf, const MetricFile* input, const MetricFile* roi = NULL, const int64_t& column = 0); }; typedef TemplateAutoOperation AutoAlgorithmMetricEstimateFWHM; } #endif //__ALGORITHM_METRIC_ESTIMATE_FWHM_H__ workbench-1.1.1/src/Algorithms/AlgorithmMetricExtrema.cxx000066400000000000000000001062561255417355300235640ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmMetricExtrema.h" #include "AlgorithmException.h" #include "AlgorithmMetricSmoothing.h" #include "CaretHeap.h" #include "CaretOMP.h" #include "GeodesicHelper.h" #include "MetricFile.h" #include "SurfaceFile.h" #include "TopologyHelper.h" #include #include #include using namespace caret; using namespace std; AString AlgorithmMetricExtrema::getCommandSwitch() { return "-metric-extrema"; } AString AlgorithmMetricExtrema::getShortDescription() { return "FIND EXTREMA IN A METRIC FILE"; } OperationParameters* AlgorithmMetricExtrema::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceParameter(1, "surface", "the surface to use for distance information"); ret->addMetricParameter(2, "metric-in", "the metric to find the extrema of"); ret->addDoubleParameter(3, "distance", "the minimum distance between identified extrema of the same type"); ret->addMetricOutputParameter(4, "metric-out", "the output extrema metric"); OptionalParameter* presmoothOpt = ret->createOptionalParameter(5, "-presmooth", "smooth the metric before finding extrema"); presmoothOpt->addDoubleParameter(1, "kernel", "the sigma for the gaussian smoothing kernel, in mm"); OptionalParameter* roiOpt = ret->createOptionalParameter(6, "-roi", "ignore values outside the selected area"); roiOpt->addMetricParameter(1, "roi-metric", "the area to find extrema in, as a metric"); OptionalParameter* thresholdOpt = ret->createOptionalParameter(7, "-threshold", "ignore small extrema"); thresholdOpt->addDoubleParameter(1, "low", "the largest value to consider for being a minimum"); thresholdOpt->addDoubleParameter(2, "high", "the smallest value to consider for being a maximum"); ret->createOptionalParameter(8, "-sum-columns", "output the sum of the extrema columns instead of each column separately"); ret->createOptionalParameter(9, "-consolidate-mode", "use consolidation of local minima instead of a large neighborhood"); ret->createOptionalParameter(11, "-only-maxima", "only find the maxima"); ret->createOptionalParameter(12, "-only-minima", "only find the minima"); OptionalParameter* columnSelect = ret->createOptionalParameter(10, "-column", "select a single column to find extrema in"); columnSelect->addStringParameter(1, "column", "the column number or name"); ret->setHelpText( AString("Finds extrema in a metric file, such that no two extrema of the same type are within of each other. ") + "The extrema are labeled as -1 for minima, 1 for maxima, 0 otherwise. " + "If -only-maxima or -only-minima is specified, then it will ignore extrema not of the specified type. These options are mutually exclusive.\n\n" + "If -roi is specified, not only is data outside the roi not used, but any vertex on the edge of the ROI will never be counted as an extrema, " + "in case the ROI cuts across a gradient, which would otherwise generate extrema where there should be none.\n\n" + "If -sum-columns is specified, these extrema columns are summed, and the output has a single column with this result.\n\n" + "By default, a datapoint is an extrema only if it is more extreme than every other datapoint that is within from it. " + "If -consolidate-mode is used, it instead starts by finding all datapoints that are more extreme than their immediate neighbors, " + "then while there are any extrema within of each other, take the two extrema closest to each other and merge them into one by a weighted average " + "based on how many original extrema have been merged into each.\n\n" + "By default, all input columns are used with no smoothing, use -column to specify a single column to use, and -presmooth to smooth the input before " + "finding the extrema." ); return ret; } void AlgorithmMetricExtrema::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { SurfaceFile* mySurf = myParams->getSurface(1); MetricFile* myMetric = myParams->getMetric(2); float distance = (float)myParams->getDouble(3); MetricFile* myMetricOut = myParams->getOutputMetric(4); OptionalParameter* presmoothOpt = myParams->getOptionalParameter(5); float presmooth = -1.0f; if (presmoothOpt->m_present) { presmooth = (float)presmoothOpt->getDouble(1); if (presmooth <= 0.0f) { throw AlgorithmException("smoothing kernel must be positive"); } } OptionalParameter* roiOpt = myParams->getOptionalParameter(6); MetricFile* myRoi = NULL; if (roiOpt->m_present) { myRoi = roiOpt->getMetric(1); } OptionalParameter* thresholdOpt = myParams->getOptionalParameter(7); bool thresholdMode = false; float lowThresh = 0.0f, highThresh = 0.0f; if (thresholdOpt->m_present) { thresholdMode = true; lowThresh = (float)thresholdOpt->getDouble(1); highThresh = (float)thresholdOpt->getDouble(2); } bool sumColumns = myParams->getOptionalParameter(8)->m_present; bool consolidateMode = myParams->getOptionalParameter(9)->m_present; OptionalParameter* columnSelect = myParams->getOptionalParameter(10); int columnNum = -1; if (columnSelect->m_present) {//set up to use the single column columnNum = (int)myMetric->getMapIndexFromNameOrNumber(columnSelect->getString(1)); if (columnNum < 0) { throw AlgorithmException("invalid column specified"); } } bool ignoreMinima = myParams->getOptionalParameter(11)->m_present; bool ignoreMaxima = myParams->getOptionalParameter(12)->m_present; if (ignoreMinima && ignoreMaxima) throw AlgorithmException("you may not specify both -only-maxima and -only-minima"); if (thresholdMode) { AlgorithmMetricExtrema(myProgObj, mySurf, myMetric, distance, myMetricOut, lowThresh, highThresh, myRoi, presmooth, sumColumns, consolidateMode, ignoreMinima, ignoreMaxima, columnNum); } else { AlgorithmMetricExtrema(myProgObj, mySurf, myMetric, distance, myMetricOut, myRoi, presmooth, sumColumns, consolidateMode, ignoreMinima, ignoreMaxima, columnNum); } } AlgorithmMetricExtrema::AlgorithmMetricExtrema(ProgressObject* myProgObj, const SurfaceFile* mySurf,const MetricFile* myMetric, const float& distance, MetricFile* myMetricOut, const MetricFile* myRoi, const float& presmooth, const bool& sumColumns, const bool& consolidateMode, const bool& ignoreMinima, const bool& ignoreMaxima, const int& columnNum) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); if (ignoreMinima && ignoreMaxima) throw AlgorithmException("AlgorithmMetricExtrema called with ignoreMinima and ignoreMaxima both true"); int numNodes = mySurf->getNumberOfNodes(); if (myMetric->getNumberOfNodes() != numNodes) { throw AlgorithmException("input metric has different number of vertices than input surface"); } const float* roiColumn = NULL; if (myRoi != NULL) { if (myRoi->getNumberOfNodes() != numNodes) { throw AlgorithmException("roi metric has a different number of nodes than input surface"); } roiColumn = myRoi->getValuePointerForColumn(0); } int numCols = myMetric->getNumberOfColumns(); if (columnNum < -1 || columnNum >= numCols) { throw AlgorithmException("invalid column number"); } int useCol = columnNum; const MetricFile* toProcess = myMetric; MetricFile tempMetric; if (presmooth > 0.0f) { AlgorithmMetricSmoothing(NULL, mySurf, myMetric, presmooth, &tempMetric, myRoi, false, false, columnNum); toProcess = &tempMetric; if (columnNum != -1) { useCol = 0; } } vector > neighborhoods; if (!consolidateMode) { precomputeNeighborhoods(mySurf, roiColumn, distance, neighborhoods); } if (columnNum == -1) { vector scratchcol(numNodes, 0.0f); vector minima, maxima; if (sumColumns) { myMetricOut->setNumberOfNodesAndColumns(numNodes, 1); myMetricOut->setColumnName(0, "sum of extrema"); } else { myMetricOut->setNumberOfNodesAndColumns(numNodes, numCols); } myMetricOut->setStructure(mySurf->getStructure()); for (int i = 0; i < numCols; ++i) { if (consolidateMode) { findExtremaConsolidate(mySurf, toProcess->getValuePointerForColumn(i), roiColumn, distance, false, 0.0f, 0.0f, ignoreMinima, ignoreMaxima, minima, maxima); } else { findExtremaNeighborhoods(toProcess->getValuePointerForColumn(i), neighborhoods, false, 0.0f, 0.0f, ignoreMinima, ignoreMaxima, minima, maxima); } if (sumColumns) { int numelems = (int)minima.size(); for (int j = 0; j < numelems; ++j) { scratchcol[minima[j]] -= 1.0f; } numelems = (int)maxima.size(); for (int j = 0; j < numelems; ++j) { scratchcol[maxima[j]] += 1.0f; } } else { int numelems = (int)minima.size(); for (int j = 0; j < numelems; ++j) { scratchcol[minima[j]] = -1.0f; } numelems = (int)maxima.size(); for (int j = 0; j < numelems; ++j) { scratchcol[maxima[j]] = 1.0f; } myMetricOut->setColumnName(i, "extrema of " + myMetric->getColumnName(i));//don't include the whatever smoothing adds to the names, i guess myMetricOut->setValuesForColumn(i, scratchcol.data()); numelems = (int)minima.size(); for (int j = 0; j < numelems; ++j) { scratchcol[minima[j]] = 0.0f; } numelems = (int)maxima.size(); for (int j = 0; j < numelems; ++j) { scratchcol[maxima[j]] = 0.0f; } } } if (sumColumns) { myMetricOut->setValuesForColumn(0, scratchcol.data()); } } else { vector scratchcol(numNodes, 0.0f); vector minima, maxima; myMetricOut->setNumberOfNodesAndColumns(numNodes, 1); myMetricOut->setStructure(mySurf->getStructure()); if (consolidateMode) { findExtremaConsolidate(mySurf, toProcess->getValuePointerForColumn(useCol), roiColumn, distance, false, 0.0f, 0.0f, ignoreMinima, ignoreMaxima, minima, maxima); } else { findExtremaNeighborhoods(toProcess->getValuePointerForColumn(useCol), neighborhoods, false, 0.0f, 0.0f, ignoreMinima, ignoreMaxima, minima, maxima); } int numelems = (int)minima.size(); for (int j = 0; j < numelems; ++j) { scratchcol[minima[j]] = -1.0f; } numelems = (int)maxima.size(); for (int j = 0; j < numelems; ++j) { scratchcol[maxima[j]] = 1.0f; } myMetricOut->setColumnName(0, "extrema of" + myMetric->getColumnName(columnNum));//ditto myMetricOut->setValuesForColumn(0, scratchcol.data()); } } AlgorithmMetricExtrema::AlgorithmMetricExtrema(ProgressObject* myProgObj, const SurfaceFile* mySurf,const MetricFile* myMetric, const float& distance, MetricFile* myMetricOut, const float& lowThresh, const float& highThresh, const MetricFile* myRoi, const float& presmooth, const bool& sumColumns, const bool& consolidateMode, const bool& ignoreMinima, const bool& ignoreMaxima, const int& columnNum) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); if (ignoreMinima && ignoreMaxima) throw AlgorithmException("AlgorithmMetricExtrema called with ignoreMinima and ignoreMaxima both true"); int numNodes = mySurf->getNumberOfNodes(); if (myMetric->getNumberOfNodes() != numNodes) { throw AlgorithmException("input metric has different number of vertices than input surface"); } const float* roiColumn = NULL; if (myRoi != NULL) { if (myRoi->getNumberOfNodes() != numNodes) { throw AlgorithmException("roi metric has a different number of nodes than input surface"); } roiColumn = myRoi->getValuePointerForColumn(0); } int numCols = myMetric->getNumberOfColumns(); if (columnNum < -1 || columnNum >= numCols) { throw AlgorithmException("invalid column number"); } int useCol = columnNum; const MetricFile* toProcess = myMetric; MetricFile tempMetric; if (presmooth > 0.0f) { AlgorithmMetricSmoothing(NULL, mySurf, myMetric, presmooth, &tempMetric, myRoi, false, false, columnNum); toProcess = &tempMetric; if (columnNum != -1) { useCol = 0; } } vector > neighborhoods; if (!consolidateMode) { precomputeNeighborhoods(mySurf, roiColumn, distance, neighborhoods); } if (columnNum == -1) { vector scratchcol(numNodes, 0.0f); vector minima, maxima; if (sumColumns) { myMetricOut->setNumberOfNodesAndColumns(numNodes, 1); } else { myMetricOut->setNumberOfNodesAndColumns(numNodes, numCols); } for (int i = 0; i < numCols; ++i) { if (consolidateMode) { findExtremaConsolidate(mySurf, toProcess->getValuePointerForColumn(i), roiColumn, distance, true, lowThresh, highThresh, ignoreMinima, ignoreMaxima, minima, maxima); } else { findExtremaNeighborhoods(toProcess->getValuePointerForColumn(i), neighborhoods, true, lowThresh, highThresh, ignoreMinima, ignoreMaxima, minima, maxima); } if (sumColumns) { int numelems = (int)minima.size(); for (int j = 0; j < numelems; ++j) { scratchcol[minima[j]] -= 1.0f; } numelems = (int)maxima.size(); for (int j = 0; j < numelems; ++j) { scratchcol[maxima[j]] += 1.0f; } } else { int numelems = (int)minima.size(); for (int j = 0; j < numelems; ++j) { scratchcol[minima[j]] = -1.0f; } numelems = (int)maxima.size(); for (int j = 0; j < numelems; ++j) { scratchcol[maxima[j]] = 1.0f; } myMetricOut->setValuesForColumn(i, scratchcol.data()); numelems = (int)minima.size(); for (int j = 0; j < numelems; ++j) { scratchcol[minima[j]] = 0.0f; } numelems = (int)maxima.size(); for (int j = 0; j < numelems; ++j) { scratchcol[maxima[j]] = 0.0f; } } } if (sumColumns) { myMetricOut->setValuesForColumn(0, scratchcol.data()); } } else { vector scratchcol(numNodes, 0.0f); vector minima, maxima; myMetricOut->setNumberOfNodesAndColumns(numNodes, 1); if (consolidateMode) { findExtremaConsolidate(mySurf, toProcess->getValuePointerForColumn(useCol), roiColumn, distance, true, lowThresh, highThresh, ignoreMinima, ignoreMaxima, minima, maxima); } else { findExtremaNeighborhoods(toProcess->getValuePointerForColumn(useCol), neighborhoods, true, lowThresh, highThresh, ignoreMinima, ignoreMaxima, minima, maxima); } int numelems = (int)minima.size(); for (int j = 0; j < numelems; ++j) { scratchcol[minima[j]] = -1.0f; } numelems = (int)maxima.size(); for (int j = 0; j < numelems; ++j) { scratchcol[maxima[j]] = 1.0f; } myMetricOut->setValuesForColumn(0, scratchcol.data()); } } void AlgorithmMetricExtrema::precomputeNeighborhoods(const SurfaceFile* mySurf, const float* roiColumn, const float& distance, vector >& neighborhoods) { int numNodes = mySurf->getNumberOfNodes(); neighborhoods.clear(); neighborhoods.resize(numNodes); CaretPointer myTopoHelp = mySurf->getTopologyHelper();//can share this, we will only use 1-hop neighbors CaretPointer myGeoHelp = mySurf->getGeodesicHelper();//must be thread-private vector junk;//also thread-private for (int i = 0; i < numNodes; ++i) { if (roiColumn == NULL || roiColumn[i] > 0.0f) { if (roiColumn != NULL) { const vector& neighbors = myTopoHelp->getNodeNeighbors(i); int numNeigh = (int)neighbors.size(); bool good = true; for (int j = 0; j < numNeigh; ++j) { if (roiColumn[neighbors[j]] <= 0.0f) { good = false; break; } } if (!good) { neighborhoods[i].push_back(-1);//use a single neighbor of -1 to denote "do not use due to being on the edge of the ROI" - a bit of a hack, but means we don't need a second array, and still separates it from "no neighbors" continue; } } myGeoHelp->getNodesToGeoDist(i, distance, neighborhoods[i], junk); int numelems = (int)neighborhoods[i].size(); if (numelems < 7) { neighborhoods[i] = myTopoHelp->getNodeNeighbors(i); if (roiColumn != NULL) { numelems = (int)neighborhoods[i].size(); for (int j = 0; j < numelems; ++j) { if (roiColumn[neighborhoods[i][j]] <= 0.0f) { neighborhoods[i].erase(neighborhoods[i].begin() + j);//erase it --j;//don't skip any or walk off the vector --numelems; } } } } else { if (roiColumn == NULL) { for (int j = 0; j < numelems; ++j) { if (neighborhoods[i][j] == i) { neighborhoods[i].erase(neighborhoods[i].begin() + j);//we don't want the node itself, so erase it break; } } } else { for (int j = 0; j < numelems; ++j) { if (neighborhoods[i][j] == i || roiColumn[neighborhoods[i][j]] <= 0.0f)//don't want the node itself, or anything outside the roi { neighborhoods[i].erase(neighborhoods[i].begin() + j);//erase it --j;//don't skip any or walk off the vector --numelems; } } } } } } } void AlgorithmMetricExtrema::findExtremaNeighborhoods(const float* data, const vector >& neighborhoods, const bool& threshMode, const float& lowThresh, const float& highThresh, const bool& ignoreMinima, const bool& ignoreMaxima, vector& minima, vector& maxima) { int numNodes = (int)neighborhoods.size(); minima.clear(); maxima.clear(); vector minPos(numNodes, 1), maxPos(numNodes, 1);//mark off things that fail a comparison to reduce the work - these are just used as booleans, but we don't want bitwise packing slowing us down or dropping parallel writes #pragma omp CARET_PARFOR schedule(dynamic) for (int i = 0; i < numNodes; ++i) { bool canBeMin = minPos[i] && !ignoreMinima, canBeMax = maxPos[i] && !ignoreMaxima; if (canBeMin || canBeMax) { const vector& myneighbors = neighborhoods[i]; int numNeigh = (int)myneighbors.size(); if (numNeigh == 0) continue;//don't count isolated nodes as minima or maxima if (numNeigh == 1 && myneighbors[0] == -1) continue;//ignore nodes on the edge of the ROI, because this often generates artificial extrema float myval = data[i]; if (threshMode) { if (myval > lowThresh) canBeMin = false;//check thresholds if (myval < highThresh) canBeMax = false; } int j = 0; if (canBeMin && canBeMax)//avoid the double-test unless both options are on the table {//should be fairly rare, and doesn't need to loop int32_t neighNode = myneighbors[0];//NOTE: the equals case should set one of these to false, so that only one of the two loops needs to execute float otherval = data[neighNode]; if (myval < otherval) { minPos[neighNode] = 0; } else { canBeMin = false;//center being equal or greater means it is not a minimum, so stop testing that } if (myval > otherval) { maxPos[neighNode] = 0; } else { canBeMax = false; } j = 1;//don't test 0 again if we did the double test } if (canBeMax) { for (; j < numNeigh; ++j) { int32_t neighNode = myneighbors[j]; float otherval = data[neighNode]; if (myval > otherval) { maxPos[neighNode] = 0;//TODO: test if performing an intelligent comparison here is faster than doing unneeded stores } else { canBeMax = false; break; } } } if (canBeMin) { for (; j < numNeigh; ++j) { int32_t neighNode = myneighbors[j]; float otherval = data[neighNode]; if (myval < otherval) { minPos[neighNode] = 0;//ditto } else { canBeMin = false; break; } } } if (canBeMax) { #pragma omp critical { maxima.push_back(i); } } if (canBeMin) { #pragma omp critical { minima.push_back(i); } } } } } void AlgorithmMetricExtrema::findExtremaConsolidate(const SurfaceFile* mySurf, const float* data, const float* roiColumn, const float& distance, const bool& threshMode, const float& lowThresh, const float& highThresh, const bool& ignoreMinima, const bool& ignoreMaxima, vector& minima, vector& maxima) { int numNodes = mySurf->getNumberOfNodes(); minima.clear(); maxima.clear(); vector > tempExtrema[2]; vector minPos(numNodes, 1), maxPos(numNodes, 1);//mark off things that fail a comparison to reduce the work - these are just used as booleans, but we don't want bitwise packing slowing us down or dropping writes CaretPointer myTopoHelp = mySurf->getTopologyHelper(); #pragma omp CARET_PARFOR schedule(dynamic) for (int i = 0; i < numNodes; ++i) { bool canBeMin = minPos[i] && !ignoreMinima, canBeMax = maxPos[i] && !ignoreMaxima; if (canBeMin || canBeMax) { const vector& myneighbors = myTopoHelp->getNodeNeighbors(i); int numNeigh = (int)myneighbors.size(); if (numNeigh == 0) continue;//don't count isolated nodes as minima or maxima float myval = data[i]; if (threshMode) { if (myval > lowThresh) canBeMin = false;//check thresholds if (myval < highThresh) canBeMax = false; } if (canBeMin && canBeMax)//avoid the double-test unless both options are on the table {//NOTE: the equals case should set one of these to false, so that only one of the two below loops needs to execute int32_t neighNode = myneighbors[0]; if (roiColumn == NULL || roiColumn[neighNode] > 0.0f) { float otherval = data[neighNode]; if (myval < otherval) { minPos[neighNode] = 0; } else { canBeMin = false;//center being equal or greater means it is not a minimum, so stop testing that } if (myval > otherval) { maxPos[neighNode] = 0; } else { canBeMax = false; } } else { canBeMin = false;//if we neighbor something outside the ROI, do not allow counting as max or min canBeMax = false; } } int j = 1;//don't retest the first neighbor if (canBeMax) { for (; j < numNeigh; ++j) { int32_t neighNode = myneighbors[j]; if (roiColumn == NULL || roiColumn[neighNode] > 0.0f) { float otherval = data[neighNode]; if (myval > otherval) { maxPos[neighNode] = 0;//TODO: test if performing an intelligent comparison here is faster than doing unneeded stores } else { canBeMax = false; break; } } else { canBeMax = false;//if we neighbor something outside the ROI, do not allow counting as max or min break; } } } if (canBeMin) { for (; j < numNeigh; ++j) { int32_t neighNode = myneighbors[j]; if (roiColumn == NULL || roiColumn[neighNode] > 0.0f) { float otherval = data[neighNode]; if (myval < otherval) { minPos[neighNode] = 0;//ditto } else { canBeMin = false; break; } } else { canBeMin = false;//if we neighbor something outside the ROI, do not allow counting as max or min break; } } } if (canBeMax) { #pragma omp critical { tempExtrema[0].push_back(pair(i, 1)); } } if (canBeMin) { #pragma omp critical { tempExtrema[1].push_back(pair(i, 1)); } } } } consolidateStep(mySurf, distance, tempExtrema, minima, maxima); } void AlgorithmMetricExtrema::consolidateStep(const SurfaceFile* mySurf, const float& distance, vector > initExtrema[2], vector& minima, vector& maxima) { int numNodes = mySurf->getNumberOfNodes(); vector scratchDist(numNodes, -1.0f); for (int sign = 0; sign < 2; ++sign) { int numInitExtrema = (int)initExtrema[sign].size(); vector removed(numInitExtrema, false);//track which extrema locations are dropped during consolidation - the one that isn't dropped in a merge has its node number changed vector > heapIDmatrix(numInitExtrema, vector(numInitExtrema, -1)); CaretMinHeap, float> myDistHeap; vector dists; vector neighbors; CaretPointer myGeoHelp = mySurf->getGeodesicHelper(); for (int i = 0; i < numInitExtrema - 1; ++i) { myGeoHelp->getNodesToGeoDist(initExtrema[sign][i].first, distance, neighbors, dists);//use smooth distance to get whether they are close enough int numInDist = (int)dists.size(); for (int j = 0; j < numInDist; ++j) { scratchDist[neighbors[j]] = dists[j]; } for (int j = i + 1; j < numInitExtrema; ++j) { float tempf = scratchDist[initExtrema[sign][j].first]; if (tempf > -0.5f) { int64_t tempID = myDistHeap.push(pair(i, j), tempf); heapIDmatrix[i][j] = tempID; heapIDmatrix[j][i] = tempID; } } for (int j = 0; j < numInDist; ++j) { scratchDist[neighbors[j]] = -1.0f; } }//initial distance matrix computed, now we iterate while (!myDistHeap.isEmpty()) { pair toMerge = myDistHeap.pop();//we don't need to know the key int extr1 = toMerge.first; int extr2 = toMerge.second; heapIDmatrix[extr1][extr2] = -1; heapIDmatrix[extr2][extr1] = -1; int weight1 = initExtrema[sign][extr1].second; int weight2 = initExtrema[sign][extr2].second; if (weight2 > weight1)//swap so weight1 is always bigger { int temp = weight2; weight2 = weight1; weight1 = temp; temp = extr2; extr2 = extr1; extr1 = temp; } int node1 = initExtrema[sign][extr1].first; int node2 = initExtrema[sign][extr2].first; removed[extr2] = true;//drop the one that has less weight, and modify the one that has more weight for (int j = 0; j < numInitExtrema; ++j) { if (!removed[j]) { int64_t tempID = heapIDmatrix[extr2][j]; if (tempID != -1) { myDistHeap.remove(tempID); heapIDmatrix[extr2][j] = -1; heapIDmatrix[j][extr2] = -1; } } } vector pathnodes; vector pathdists; myGeoHelp->getPathToNode(node1, node2, pathnodes, pathdists, false); if (pathdists.size() != 0) { float distToWalk = (pathdists.back() * weight2) / (weight1 + weight2); int walk = 1, maxSteps = (int)pathnodes.size(); float prevdiff = abs(distToWalk - pathdists[0]); while (walk < maxSteps) { float newdiff = abs(distToWalk - pathdists[walk]); if (newdiff > prevdiff) break; ++walk; } int newnode = pathnodes[walk - 1]; initExtrema[sign][extr1].first = newnode; initExtrema[sign][extr1].second += weight2;//add the weights together myGeoHelp->getNodesToGeoDist(newnode, distance, neighbors, dists); int numInDist = (int)dists.size(); for (int j = 0; j < numInDist; ++j) { scratchDist[neighbors[j]] = dists[j]; } for (int j = 0; j < numInitExtrema; ++j) { if (!removed[j]) { float tempf = scratchDist[initExtrema[sign][j].first]; int64_t tempID = heapIDmatrix[extr1][j]; if (tempf > -0.5f) { if (tempID != -1) { myDistHeap.changekey(tempID, tempf); } else { tempID = myDistHeap.push(pair(extr1, j), tempf); heapIDmatrix[extr1][j] = tempID; heapIDmatrix[j][extr1] = tempID; } } else { if (tempID != -1) { myDistHeap.remove(tempID); heapIDmatrix[extr1][j] = -1; heapIDmatrix[j][extr1] = -1; } } } } for (int j = 0; j < numInDist; ++j) { scratchDist[neighbors[j]] = -1.0f; } } } if (sign == 0) { for (int i = 0; i < numInitExtrema; ++i) { if (!removed[i]) { maxima.push_back(initExtrema[sign][i].first); } } } else { for (int i = 0; i < numInitExtrema; ++i) { if (!removed[i]) { minima.push_back(initExtrema[sign][i].first); } } } } } float AlgorithmMetricExtrema::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmMetricExtrema::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmMetricExtrema.h000066400000000000000000000071461255417355300232070ustar00rootroot00000000000000#ifndef __ALGORITHM_METRIC_EXTREMA_H__ #define __ALGORITHM_METRIC_EXTREMA_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" #include #include namespace caret { class AlgorithmMetricExtrema : public AbstractAlgorithm { void precomputeNeighborhoods(const SurfaceFile* mySurf, const float* roiColumn, const float& distance, std::vector >& neighborhoods); void findExtremaConsolidate(const SurfaceFile* mySurf, const float* data, const float* roiColumn, const float& distance, const bool& threshMode, const float& lowThresh, const float& highThresh, const bool& ignoreMinima, const bool& ignoreMaxima, std::vector& minima, std::vector& maxima); void findExtremaNeighborhoods(const float* data, const std::vector >& neighborhoods, const bool& threshMode, const float& lowThresh, const float& highThresh, const bool& ignoreMinima, const bool& ignoreMaxima, std::vector& minima, std::vector& maxima); void consolidateStep(const SurfaceFile* mySurf, const float& distance, std::vector > initExtrema[2], std::vector& minima, std::vector& maxima); AlgorithmMetricExtrema(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmMetricExtrema(ProgressObject* myProgObj, const SurfaceFile* mySurf,const MetricFile* myMetric, const float& distance, MetricFile* myMetricOut, const MetricFile* myRoi = NULL, const float& presmooth = -1.0f, const bool& sumColumns = false, const bool& consolidateMode = false, const bool& ignoreMinima = false, const bool& ignoreMaxima = false, const int& columnNum = -1); AlgorithmMetricExtrema(ProgressObject* myProgObj, const SurfaceFile* mySurf,const MetricFile* myMetric, const float& distance, MetricFile* myMetricOut, const float& lowThresh, const float& highThresh, const MetricFile* myRoi = NULL, const float& presmooth = -1.0f, const bool& sumColumns = false, const bool& consolidateMode = false, const bool& ignoreMinima = false, const bool& ignoreMaxima = false, const int& columnNum = -1); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmMetricExtrema; } #endif //__ALGORITHM_METRIC_EXTREMA_H__ workbench-1.1.1/src/Algorithms/AlgorithmMetricFalseCorrelation.cxx000066400000000000000000000340551255417355300254100ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmMetricFalseCorrelation.h" #include "AlgorithmException.h" #include "CaretOMP.h" #include "CaretPointLocator.h" #include "GeodesicHelper.h" #include "SurfaceFile.h" #include "MetricFile.h" #include #include using namespace caret; using namespace std; AString AlgorithmMetricFalseCorrelation::getCommandSwitch() { return "-metric-false-correlation"; } AString AlgorithmMetricFalseCorrelation::getShortDescription() { return "COMPARE CORRELATION LOCALLY AND ACROSS/THROUGH SULCI/GYRI"; } OperationParameters* AlgorithmMetricFalseCorrelation::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceParameter(1, "surface", "the surface to compute geodesic and 3D distance with"); ret->addMetricParameter(2, "metric-in", "the metric to correlate"); ret->addDoubleParameter(3, "3D-dist", "maximum 3D distance to check around each vertex"); ret->addDoubleParameter(4, "geo-outer", "maximum geodesic distance to use for neighboring correlation"); ret->addDoubleParameter(5, "geo-inner", "minimum geodesic distance to use for neighboring correlation"); ret->addMetricOutputParameter(6, "metric-out", "the output metric"); OptionalParameter* roiOpt = ret->createOptionalParameter(7, "-roi", "select a region of interest that has data"); roiOpt->addMetricParameter(1, "roi-metric", "the region, as a metric file"); OptionalParameter* dumpTextOpt = ret->createOptionalParameter(8, "-dump-text", "dump the raw measures used to a text file"); dumpTextOpt->addStringParameter(1, "text-out", "the output text file"); ret->setHelpText( AString("For each vertex, compute the average correlation within a range of geodesic distances that don't cross a sulcus/gyrus, and the correlation to the closest vertex crossing a sulcus/gyrus. ") + "A vertex is considered to cross a sulcus/gyrus if the 3D distance is less than a third of the geodesic distance. " + "The output file contains the ratio between these correlations, and some additional maps to help explain the ratio." ); return ret; } void AlgorithmMetricFalseCorrelation::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { SurfaceFile* mySurf = myParams->getSurface(1); MetricFile* myMetric = myParams->getMetric(2); float max3D = (float)myParams->getDouble(3); float maxgeo = (float)myParams->getDouble(4); float mingeo = (float)myParams->getDouble(5); MetricFile* myMetricOut = myParams->getOutputMetric(6); MetricFile* myRoi = NULL; OptionalParameter* roiOpt = myParams->getOptionalParameter(7); if (roiOpt->m_present) { myRoi = roiOpt->getMetric(1); } AString textName = ""; OptionalParameter* dumpTextOpt = myParams->getOptionalParameter(8); if (dumpTextOpt->m_present) { textName = dumpTextOpt->getString(1); } AlgorithmMetricFalseCorrelation(myProgObj, mySurf, myMetric, myMetricOut, max3D, maxgeo, mingeo, myRoi, textName); } AlgorithmMetricFalseCorrelation::AlgorithmMetricFalseCorrelation(ProgressObject* myProgObj, const SurfaceFile* mySurf, const MetricFile* myMetric, MetricFile* myMetricOut, const float& max3D, const float& maxgeo, const float& mingeo, const MetricFile* myRoi, const AString& textName) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); if (max3D <= 0.0f || maxgeo <= 0.0f || mingeo < 0.0f) throw AlgorithmException("distance limits must not be negative, and maximums must be positive"); int numNodes = mySurf->getNumberOfNodes(); if (myMetric->getNumberOfNodes() != numNodes) throw AlgorithmException("surface and metric have different number of vertices"); const float* roiCol = NULL; if (myRoi != NULL) { if (myRoi->getNumberOfNodes() != numNodes) throw AlgorithmException("surface and roi metric have different number of vertices"); roiCol = myRoi->getValuePointerForColumn(0); } if (myMetric->getNumberOfColumns() < 3) throw AlgorithmException("input metric must have more than 2 columns for correlation to be meaningful"); bool dumpRaw = false; ofstream rawOut; if (textName != "") { dumpRaw = true; rawOut.open(textName.toLocal8Bit().constData()); if (!rawOut) throw AlgorithmException("failed to open text file for output"); } m_toCorr = myMetric;//so we can do fancy correlation caching without rewriting this part, if we want setupCorr(roiCol); myMetricOut->setNumberOfNodesAndColumns(numNodes, 5); myMetricOut->setStructure(mySurf->getStructure()); myMetricOut->setColumnName(0, "correlation ratio"); myMetricOut->setColumnName(1, "non-neighborhood correlation"); myMetricOut->setColumnName(2, "average neighborhood correlation"); myMetricOut->setColumnName(3, "3D distance to non-neighborhood vertex"); myMetricOut->setColumnName(4, "non-neighborhood vertex number"); const AString sep1 = ",", sep2 = ";\n"; float distRatioCutoff = 3.0f; CaretPointer myLocator = mySurf->getPointLocator(); #pragma omp CARET_PAR { CaretPointer myGeo = mySurf->getGeodesicHelper(); #pragma omp CARET_FOR schedule(dynamic) for (int n = 0; n < numNodes; ++n) { int crossingNode = -1, neighCount = 0; float crossingDist = -1.0f, neighAccum = 0.0f; if (roiCol == NULL || roiCol[n] > 0.0f) { AString rawDumpString;//build the entire string for a single node, then write it in one call within #pragma omp critical Vector3D myCoord = mySurf->getCoordinate(n); set inRange = myLocator->pointsInRange(myCoord, max3D); int numInterested = (int)inRange.size(); vector interested(numInterested); int counter = 0; for (set::iterator iter = inRange.begin(); iter != inRange.end(); ++iter) { interested[counter] = iter->index; ++counter; } vector geoDists; myGeo->getGeoToTheseNodes(n, interested, geoDists); counter = 0; for (set::iterator iter = inRange.begin(); iter != inRange.end(); ++iter) { if (roiCol == NULL || (roiCol[iter->index] > 0.0f)) { float dist3D = (myCoord - iter->coords).length(); if (iter->index == n || geoDists[counter] / dist3D < distRatioCutoff) { if (maxgeo <= max3D)//otherwise, we can't trust the 3D test picking up all the points we want { if (dumpRaw) { float thiscorr = correlate(n, iter->index); rawDumpString += AString::number(n) + sep1 + AString::number(iter->index) + sep1 + AString::number(thiscorr) + sep1 + AString::number(geoDists[counter]) + sep1 + AString::number(dist3D) + sep2; if (geoDists[counter] <= maxgeo && geoDists[counter] >= mingeo) { neighAccum += thiscorr; ++neighCount; } } else { if (geoDists[counter] <= maxgeo && geoDists[counter] >= mingeo) { float thiscorr = correlate(n, iter->index); neighAccum += thiscorr; ++neighCount; } } } } else { if (dist3D < crossingDist || crossingNode == -1) { crossingDist = dist3D; crossingNode = iter->index; } if (dumpRaw) { float thiscorr = correlate(n, iter->index); rawDumpString += AString::number(n) + sep1 + AString::number(iter->index) + sep1 + AString::number(thiscorr) + sep1 + AString::number(geoDists[counter]) + sep1 + AString::number(dist3D) + sep2; } } } ++counter; } if (maxgeo > max3D)//so, we have to run geodesic separately { myGeo->getNodesToGeoDist(n, maxgeo, interested, geoDists);//reuse interested, we don't need its previous contents int numInRange = (int)interested.size(); for (int i = 0; i < numInRange; ++i) { if (roiCol != NULL && !(roiCol[interested[i]] > 0.0f)) continue; float dist3D = (myCoord - Vector3D(mySurf->getCoordinate(interested[i]))).length(); if ((interested[i] == n || geoDists[i] / dist3D < distRatioCutoff)) { if (dumpRaw) { float thiscorr = correlate(n, interested[i]); rawDumpString += AString::number(n) + sep1 + AString::number(interested[i]) + sep1 + AString::number(thiscorr) + sep1 + AString::number(geoDists[i]) + sep1 + AString::number(dist3D) + sep2; if (geoDists[i] >= mingeo)//we already know it is not greater than maxgeo { neighAccum += thiscorr; ++neighCount; } } else { if (geoDists[i] >= mingeo) { float thiscorr = correlate(n, interested[i]); neighAccum += thiscorr; ++neighCount; } } } } } if (dumpRaw) { #pragma omp critical { rawOut << rawDumpString; } } } if (crossingNode != -1 && neighCount != 0) { float longCorr = correlate(n, crossingNode); float closeAvg = neighAccum / neighCount; myMetricOut->setValue(n, 0, longCorr / closeAvg); myMetricOut->setValue(n, 1, longCorr); myMetricOut->setValue(n, 2, closeAvg); myMetricOut->setValue(n, 3, crossingDist); myMetricOut->setValue(n, 4, crossingNode); } else { myMetricOut->setValue(n, 0, 0.0f); myMetricOut->setValue(n, 1, 0.0f); myMetricOut->setValue(n, 2, 0.0f); myMetricOut->setValue(n, 3, 0.0f); myMetricOut->setValue(n, 4, -1); } } } } float AlgorithmMetricFalseCorrelation::correlate(int first, int second) { CaretAssert((int)m_demeanedRows[first].size() == m_rowSize); CaretAssert((int)m_demeanedRows[second].size() == m_rowSize); double accum = 0.0; for (int i = 0; i < m_rowSize; ++i) { accum += m_demeanedRows[first][i] * m_demeanedRows[second][i]; } return accum / (m_rrs[first] * m_rrs[second]); } void AlgorithmMetricFalseCorrelation::setupCorr(const float* roiCol) { m_rowSize = m_toCorr->getNumberOfColumns(); int numRows = m_toCorr->getNumberOfNodes(); m_demeanedRows.resize(numRows); m_rrs.resize(numRows); for (int i = 0; i < numRows; ++i) { if (roiCol != NULL && !(roiCol[i] > 0.0f)) continue; double accum = 0.0f; m_demeanedRows[i].resize(m_rowSize); for (int j = 0; j < m_rowSize; ++j) { float tempf = m_toCorr->getValue(i, j); m_demeanedRows[i][j] = tempf;//first, transpose the indexing - in MetricFile, a column is contiguous in memory accum += tempf; } float mean = accum / m_rowSize; accum = 0.0; for (int j = 0; j < m_rowSize; ++j) { float tempf = m_demeanedRows[i][j] - mean;//remove mean, calculate rrs for correlation m_demeanedRows[i][j] = tempf; accum += tempf * tempf; } m_rrs[i] = sqrt(accum); } } float AlgorithmMetricFalseCorrelation::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmMetricFalseCorrelation::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmMetricFalseCorrelation.h000066400000000000000000000042411255417355300250270ustar00rootroot00000000000000#ifndef __ALGORITHM_METRIC_FALSE_CORRELATION_H__ #define __ALGORITHM_METRIC_FALSE_CORRELATION_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" #include namespace caret { class AlgorithmMetricFalseCorrelation : public AbstractAlgorithm { AlgorithmMetricFalseCorrelation(); float correlate(int first, int second); const MetricFile* m_toCorr; int m_rowSize; std::vector > m_demeanedRows; std::vector m_rrs; void setupCorr(const float* roiCol); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmMetricFalseCorrelation(ProgressObject* myProgObj, const SurfaceFile* mySurf, const MetricFile* myMetric, MetricFile* myMetricOut, const float& max3D, const float& maxgeo, const float& mingeo, const MetricFile* myRoi = NULL, const AString& textName = ""); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmMetricFalseCorrelation; } #endif //__ALGORITHM_METRIC_FALSE_CORRELATION_H__ workbench-1.1.1/src/Algorithms/AlgorithmMetricFillHoles.cxx000066400000000000000000000150071255417355300240310ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmMetricFillHoles.h" #include "AlgorithmException.h" #include "MetricFile.h" #include "SurfaceFile.h" #include "TopologyHelper.h" #include #include using namespace caret; using namespace std; AString AlgorithmMetricFillHoles::getCommandSwitch() { return "-metric-fill-holes"; } AString AlgorithmMetricFillHoles::getShortDescription() { return "FILL HOLES IN AN ROI METRIC"; } OperationParameters* AlgorithmMetricFillHoles::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceParameter(1, "surface", "the surface to use for neighbor information"); ret->addMetricParameter(2, "metric-in", "the input ROI metric"); ret->addMetricOutputParameter(3, "metric-out", "the output ROI metric"); OptionalParameter* corrAreaOpt = ret->createOptionalParameter(4, "-corrected-areas", "vertex areas to use instead of computing them from the surface"); corrAreaOpt->addMetricParameter(1, "area-metric", "the corrected vertex areas, as a metric"); ret->setHelpText( AString("Finds all connected areas that are not included in the ROI, and writes ones into all but the largest one, in terms of surface area.") ); return ret; } void AlgorithmMetricFillHoles::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { SurfaceFile* mySurf = myParams->getSurface(1); MetricFile* myMetric = myParams->getMetric(2); MetricFile* myMetricOut = myParams->getOutputMetric(3); MetricFile* corrAreaMetric = NULL; OptionalParameter* corrAreaOpt = myParams->getOptionalParameter(4); if (corrAreaOpt->m_present) { corrAreaMetric = corrAreaOpt->getMetric(1); } AlgorithmMetricFillHoles(myProgObj, mySurf, myMetric, myMetricOut, corrAreaMetric); } AlgorithmMetricFillHoles::AlgorithmMetricFillHoles(ProgressObject* myProgObj, const SurfaceFile* mySurf, const MetricFile* myMetric, MetricFile* myMetricOut, const MetricFile* corrAreaMetric) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); int numNodes = mySurf->getNumberOfNodes(); if (myMetric->getNumberOfNodes() != numNodes) throw AlgorithmException("metric file has different number of nodes than the surface"); vector surfAreaData; const float* areaData = NULL; if (corrAreaMetric != NULL) { if (corrAreaMetric->getNumberOfNodes() != numNodes) throw AlgorithmException("corrected vertex area metric file has different number of nodes than the surface"); areaData = corrAreaMetric->getValuePointerForColumn(0); } else { mySurf->computeNodeAreas(surfAreaData); areaData = surfAreaData.data(); } int numCols = myMetric->getNumberOfColumns(); myMetricOut->setNumberOfNodesAndColumns(numNodes, numCols); myMetricOut->setStructure(myMetric->getStructure()); for (int col = 0; col < numCols; ++col) { vector > > areas; vector used(numNodes, 0); CaretPointer myHelp = mySurf->getTopologyHelper(); const float* roiData = myMetric->getValuePointerForColumn(col); myMetricOut->setColumnName(col, myMetric->getColumnName(col)); for (int i = 0; i < numNodes; ++i) { if (used[i] == 0 && !(roiData[i] > 0.0f))//use "not greater than" in case someone uses NaNs in their ROI { areas.push_back(make_pair(0.0f, vector())); vector& thisAreaMembers = areas.back().second; float& thisSurfArea = areas.back().first; thisAreaMembers.push_back(i); thisSurfArea += areaData[i]; used[i] = 1; vector mystack; mystack.push_back(i); while (!mystack.empty()) { int curnode = mystack.back(); mystack.pop_back(); const vector& neighbors = myHelp->getNodeNeighbors(curnode); int numNeigh = (int)neighbors.size(); for (int j = 0; j < numNeigh; ++j) { int thisneigh = neighbors[j]; if (used[thisneigh] == 0 && !(roiData[thisneigh] > 0.0f)) { used[thisneigh] = 1; thisAreaMembers.push_back(thisneigh); thisSurfArea += areaData[thisneigh]; mystack.push_back(thisneigh); } } } } } vector outscratch(numNodes, 1.0f); int numAreas = (int)areas.size(); if (numAreas > 0) { int bestIndex = 0; float bestArea = areas[0].first; for (int i = 1; i < numAreas; ++i) { float thisArea = (int)areas[i].first; if (thisArea > bestArea) { bestIndex = i; bestArea = thisArea; } } const vector& thisArea = areas[bestIndex].second; int numAreaNodes = (int)thisArea.size(); for (int i = 0; i < numAreaNodes; ++i) { outscratch[thisArea[i]] = 0.0f;//make it into a simple 0/1 metric, even if it wasn't before } } myMetricOut->setValuesForColumn(col, outscratch.data()); } } float AlgorithmMetricFillHoles::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmMetricFillHoles::getSubAlgorithmWeight() { return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmMetricFillHoles.h000066400000000000000000000034111255417355300234520ustar00rootroot00000000000000#ifndef __ALGORITHM_METRIC_FILL_HOLES_H__ #define __ALGORITHM_METRIC_FILL_HOLES_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmMetricFillHoles : public AbstractAlgorithm { AlgorithmMetricFillHoles(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmMetricFillHoles(ProgressObject* myProgObj, const SurfaceFile* mySurf, const MetricFile* myMetric, MetricFile* myMetricOut, const MetricFile* corrAreaMetric = NULL); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmMetricFillHoles; } #endif //__ALGORITHM_METRIC_FILL_HOLES_H__ workbench-1.1.1/src/Algorithms/AlgorithmMetricFindClusters.cxx000066400000000000000000000352021255417355300245540ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmMetricFindClusters.h" #include "AlgorithmException.h" #include "CaretLogger.h" #include "GeodesicHelper.h" #include "MetricFile.h" #include "SurfaceFile.h" #include "TopologyHelper.h" #include using namespace caret; using namespace std; AString AlgorithmMetricFindClusters::getCommandSwitch() { return "-metric-find-clusters"; } AString AlgorithmMetricFindClusters::getShortDescription() { return "FILTER CLUSTERS BY SURFACE AREA"; } OperationParameters* AlgorithmMetricFindClusters::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceParameter(1, "surface", "the surface to compute on"); ret->addMetricParameter(2, "metric-in", "the input metric"); ret->addDoubleParameter(3, "value-threshold", "threshold for data values"); ret->addDoubleParameter(4, "minimum-area", "threshold for cluster area, in mm^2"); ret->addMetricOutputParameter(5, "metric-out", "the output metric"); ret->createOptionalParameter(6, "-less-than", "find values less than , rather than greater"); OptionalParameter* roiOption = ret->createOptionalParameter(7, "-roi", "select a region of interest"); roiOption->addMetricParameter(1, "roi-metric", "the roi, as a metric"); OptionalParameter* corrAreasOpt = ret->createOptionalParameter(8, "-corrected-areas", "vertex areas to use instead of computing them from the surface"); corrAreasOpt->addMetricParameter(1, "area-metric", "the corrected vertex areas, as a metric"); OptionalParameter* columnSelect = ret->createOptionalParameter(9, "-column", "select a single column"); columnSelect->addStringParameter(1, "column", "the column number or name"); OptionalParameter* sizeRatioOpt = ret->createOptionalParameter(11, "-size-ratio", "ignore clusters smaller than a given fraction of the largest cluster in map"); sizeRatioOpt->addDoubleParameter(1, "ratio", "fraction of the largest cluster's area"); OptionalParameter* distanceOpt = ret->createOptionalParameter(12, "-distance", "ignore clusters further than a given distance from the largest cluster"); distanceOpt->addDoubleParameter(1, "distance", "how far from the largest cluster a cluster can be, edge to edge, in mm"); OptionalParameter* startOpt = ret->createOptionalParameter(10, "-start", "start labeling clusters from a value other than 1"); startOpt->addIntegerParameter(1, "startval", "the value to give the first cluster found"); ret->setHelpText( AString("Outputs a metric with nonzero integers for all vertices within a large enough cluster, and zeros elsewhere. ") + "The integers denote cluster membership (by default, first cluster found will use value 1, second cluster 2, etc). " + "By default, values greater than are considered to be in a cluster, use -less-than to test for values less than the threshold. " + "To apply this as a mask to the data, or to do more complicated thresholding, see -metric-math." ); return ret; } void AlgorithmMetricFindClusters::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { SurfaceFile* mySurf = myParams->getSurface(1); MetricFile* myMetric = myParams->getMetric(2); float threshVal = (float)myParams->getDouble(3); float minArea = (float)myParams->getDouble(4); MetricFile* myMetricOut = myParams->getOutputMetric(5); bool lessThan = myParams->getOptionalParameter(6)->m_present; MetricFile* myRoi = NULL; OptionalParameter* roiOption = myParams->getOptionalParameter(7); if (roiOption->m_present) { myRoi = roiOption->getMetric(1); } MetricFile* myAreas = NULL; OptionalParameter* corrAreasOpt = myParams->getOptionalParameter(8); if (corrAreasOpt->m_present) { myAreas = corrAreasOpt->getMetric(1); } OptionalParameter* columnSelect = myParams->getOptionalParameter(9); int columnNum = -1; if (columnSelect->m_present) {//set up to use the single column columnNum = (int)myMetric->getMapIndexFromNameOrNumber(columnSelect->getString(1)); if (columnNum < 0) { throw AlgorithmException("invalid column specified"); } } OptionalParameter* startOpt = myParams->getOptionalParameter(10); int startVal = 1; if (startOpt->m_present) { startVal = (int)startOpt->getInteger(1); } OptionalParameter* sizeRatioOpt = myParams->getOptionalParameter(11); float sizeRatio = -1.0f; if (sizeRatioOpt->m_present) { sizeRatio = sizeRatioOpt->getDouble(1); if (sizeRatio <= 0.0f) { throw AlgorithmException("area ratio must be positive"); } } OptionalParameter* distanceOpt = myParams->getOptionalParameter(12); float distanceCutoff = -1.0f; if (distanceOpt->m_present) { distanceCutoff = distanceOpt->getDouble(1); if (distanceCutoff <= 0.0f) { throw AlgorithmException("distance cutoff must be positive"); } } AlgorithmMetricFindClusters(myProgObj, mySurf, myMetric, threshVal, minArea, myMetricOut, lessThan, myRoi, myAreas, columnNum, startVal, NULL, sizeRatio, distanceCutoff); } namespace { struct Cluster { Cluster() { area = 0.0; } vector members; double area; }; void processColumn(const float* data, const float* roiData, const float* nodeAreas, TopologyHelper* myTopoHelp, GeodesicHelper* myGeoHelp, const float& threshVal, const float& minArea, const bool& lessThan, const float& areaRatio, const float& distanceCutoff, float* outData, int& markVal) { int numNodes = myTopoHelp->getNumberOfNodes(); vector marked(numNodes, 0); if (lessThan) { for (int i = 0; i < numNodes; ++i) { if ((roiData == NULL || roiData[i] > 0.0f) && data[i] < threshVal) { marked[i] = 1; } } } else { for (int i = 0; i < numNodes; ++i) { if ((roiData == NULL || roiData[i] > 0.0f) && data[i] > threshVal) { marked[i] = 1; } } } vector clusters; float biggestSize = 0.0f; int biggestCluster = -1; for (int i = 0; i < numNodes; ++i) { if (marked[i]) { Cluster newCluster; newCluster.members.push_back(i); marked[i] = 0;//unmark it when added to list to prevent multiples for (int index = 0; index < (int)newCluster.members.size(); ++index)//NOTE: vector grows inside loop { int node = newCluster.members[index];//keep list around so we can put it into the output immediately if it is large enough newCluster.area += nodeAreas[node]; const vector& neighbors = myTopoHelp->getNodeNeighbors(node); int numNeigh = (int)neighbors.size(); for (int n = 0; n < numNeigh; ++n) { const int32_t& neighbor = neighbors[n]; if (marked[neighbor]) { newCluster.members.push_back(neighbor); marked[neighbor] = 0; } } } if (newCluster.area > minArea) { if (newCluster.area > biggestSize) { biggestSize = newCluster.area; biggestCluster = (int)clusters.size(); } clusters.push_back(newCluster); } } } vector pathScratch; vector distScratch; if (!clusters.empty() && biggestCluster == -1) CaretLogWarning("clusters found, but none have positive area, check your vertex areas for negatives"); if (biggestCluster != -1 && (distanceCutoff > 0.0f || areaRatio > 0.0f)) { for (size_t i = 0; i < clusters.size(); ++i) { if ((int)i != biggestCluster) { bool erase = false; if (areaRatio > 0.0f) { if ((clusters[i].area / biggestSize) < areaRatio) { erase = true; } } if (!erase && distanceCutoff > 0.0f) { CaretAssert(myGeoHelp != NULL); myGeoHelp->getPathBetweenNodeLists(clusters[i].members, clusters[biggestCluster].members, distanceCutoff, pathScratch, distScratch, true); if (pathScratch.empty())//empty path means no path found { erase = true; } } if (erase) { clusters.erase(clusters.begin() + i);//remove it --i;//don't skip a cluster if (biggestCluster > (int)i) --biggestCluster;//don't lose track of the biggest cluster } } } } for (size_t i = 0; i < clusters.size(); ++i) { if (markVal == 0) { CaretLogInfo("skipping 0 for cluster marking"); ++markVal; } float tempVal = markVal; if ((int)tempVal != markVal) throw AlgorithmException("too many clusters, unable to mark them uniquely"); int numMembers = (int)clusters[i].members.size(); for (int index = 0; index < numMembers; ++index) { outData[clusters[i].members[index]] = tempVal; } ++markVal; } } } AlgorithmMetricFindClusters::AlgorithmMetricFindClusters(ProgressObject* myProgObj, const SurfaceFile* mySurf, const MetricFile* myMetric, const float& threshVal, const float& minArea, MetricFile* myMetricOut, const bool& lessThan, const MetricFile* myRoi, const MetricFile* myAreas, const int& columnNum, const int& startVal, int* endVal, const float& areaRatio, const float& distanceCutoff) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); if (startVal == 0) { throw AlgorithmException("0 is not a valid cluster marking start value"); } int numNodes = mySurf->getNumberOfNodes(); if (myMetric->getNumberOfNodes() != numNodes) throw AlgorithmException("metric does not match surface in number of vertices"); const float* roiData = NULL; if (myRoi != NULL) { if (myRoi->getNumberOfNodes() != numNodes) throw AlgorithmException("roi metric does not match surface in number of vertices"); roiData = myRoi->getValuePointerForColumn(0); } if (myAreas != NULL && myAreas->getNumberOfNodes() != mySurf->getNumberOfNodes()) { throw AlgorithmException("corrected area metric does not match surface in number of vertices"); } int numCols = myMetric->getNumberOfColumns(); if (columnNum < -1 || columnNum >= numCols) { throw AlgorithmException("invalid column number"); } vector nodeAreasVec; const float* nodeAreas = NULL; if (myAreas == NULL) { mySurf->computeNodeAreas(nodeAreasVec); nodeAreas = nodeAreasVec.data(); } else { nodeAreas = myAreas->getValuePointerForColumn(0); } CaretPointer myTopoHelp = mySurf->getTopologyHelper(); CaretPointer myGeoHelp; CaretPointer myGeoBase; if (distanceCutoff > 0.0f)//geodesic is only needed for distance cutoff { if (myAreas == NULL) { myGeoHelp = mySurf->getGeodesicHelper(); } else { myGeoBase.grabNew(new GeodesicHelperBase(mySurf, myAreas->getValuePointerForColumn(0))); myGeoHelp.grabNew(new GeodesicHelper(myGeoBase)); } } vector toSearch; int markVal = startVal;//give each cluster a different value, including across maps if (columnNum == -1) { myMetricOut->setNumberOfNodesAndColumns(numNodes, numCols); myMetricOut->setStructure(mySurf->getStructure()); for (int c = 0; c < numCols; ++c) { myMetricOut->setColumnName(c, myMetric->getColumnName(c)); vector outData(numNodes, 0.0f); const float* data = myMetric->getValuePointerForColumn(c); processColumn(data, roiData, nodeAreas, myTopoHelp, myGeoHelp, threshVal, minArea, lessThan, areaRatio, distanceCutoff, outData.data(), markVal); myMetricOut->setValuesForColumn(c, outData.data()); } } else { myMetricOut->setNumberOfNodesAndColumns(numNodes, 1); myMetricOut->setStructure(mySurf->getStructure()); myMetricOut->setColumnName(0, myMetric->getColumnName(columnNum)); vector outData(numNodes, 0.0f); const float* data = myMetric->getValuePointerForColumn(columnNum); processColumn(data, roiData, nodeAreas, myTopoHelp, myGeoHelp, threshVal, minArea, lessThan, areaRatio, distanceCutoff, outData.data(), markVal); myMetricOut->setValuesForColumn(0, outData.data()); } if (endVal != NULL) *endVal = markVal; } float AlgorithmMetricFindClusters::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmMetricFindClusters::getSubAlgorithmWeight() { return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmMetricFindClusters.h000066400000000000000000000040671255417355300242060ustar00rootroot00000000000000#ifndef __ALGORITHM_METRIC_FIND_CLUSTERS_H__ #define __ALGORITHM_METRIC_FIND_CLUSTERS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmMetricFindClusters : public AbstractAlgorithm { AlgorithmMetricFindClusters(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmMetricFindClusters(ProgressObject* myProgObj, const SurfaceFile* mySurf, const MetricFile* myMetric, const float& minVal, const float& minArea, MetricFile* myMetricOut, const bool& lessThan = false, const MetricFile* myRoi = NULL, const MetricFile* myAreas = NULL, const int& columnNum = -1, const int& startVal = 1, int* endVal = NULL, const float& areaRatio = -1.0f, const float& distanceCutoff = -1.0f); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmMetricFindClusters; } #endif //__ALGORITHM_METRIC_FIND_CLUSTERS_H__ workbench-1.1.1/src/Algorithms/AlgorithmMetricGradient.cxx000066400000000000000000001035741255417355300237140ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "AlgorithmMetricGradient.h" #include "AlgorithmMetricSmoothing.h" #include "AlgorithmException.h" #include "CaretOMP.h" #include "CaretLogger.h" #include "FloatMatrix.h" #include "MathFunctions.h" #include "MetricFile.h" #include "PaletteColorMapping.h" #include "SurfaceFile.h" #include "TopologyHelper.h" #include "Vector3D.h" #include using namespace caret; using namespace std; AString AlgorithmMetricGradient::getCommandSwitch() { return "-metric-gradient"; } AString AlgorithmMetricGradient::getShortDescription() { return "SURFACE GRADIENT OF A METRIC FILE"; } OperationParameters* AlgorithmMetricGradient::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceParameter(1, "surface", "the surface to compute the gradient on"); ret->addMetricParameter(2, "metric-in", "the metric to compute the gradient of"); ret->addMetricOutputParameter(3, "metric-out", "the magnitude of the gradient"); OptionalParameter* presmooth = ret->createOptionalParameter(4, "-presmooth", "smooth the metric before computing the gradient"); presmooth->addDoubleParameter(1, "kernel", "the sigma for the gaussian smoothing kernel, in mm"); OptionalParameter* roiOption = ret->createOptionalParameter(5, "-roi", "select a region of interest to take the gradient of"); roiOption->addMetricParameter(1, "roi-metric", "the area to take the gradient within, as a metric"); roiOption->createOptionalParameter(2, "-match-columns", "for each input column, use the corresponding column from the roi"); OptionalParameter* vecOut = ret->createOptionalParameter(6, "-vectors", "output gradient vectors"); vecOut->addMetricOutputParameter(1, "vector-metric-out", "the vectors as a metric file"); OptionalParameter* columnSelect = ret->createOptionalParameter(7, "-column", "select a single column to compute the gradient of"); columnSelect->addStringParameter(1, "column", "the column number or name"); OptionalParameter* corrAreaOpt = ret->createOptionalParameter(8, "-corrected-areas", "vertex areas to use instead of computing them from the surface"); corrAreaOpt->addMetricParameter(1, "area-metric", "the corrected vertex areas, as a metric"); ret->createOptionalParameter(9, "-average-normals", "average the normals of each vertex with its neighbors before using them to compute the gradient"); //that option has no parameters to take, so don't store the return value ret->setHelpText( AString("At each vertex, the immediate neighbors are unfolded onto a plane tangent to the surface at the vertex (specifically, perpendicular to the normal). ") + "The gradient is computed using a regression between the unfolded positions of the vertices and their values. " + "The gradient is then given by the slopes of the regression, and reconstructed as a 3D gradient vector. " + "By default, takes the gradient of all columns, with no presmoothing, across the whole surface, without averaging the normals of the surface among neighbors.\n\n" + "When using -corrected-areas, note that it is an approximate correction. " + "Doing smoothing on individual surfaces before averaging/gradient is preferred, when possible, in order to make use of the original surface structure.\n\n" + "Specifying an ROI will restrict the gradient to only use data from where the ROI metric is positive, and output zeros anywhere the ROI metric is not positive.\n\n" + "By default, the first column of the roi metric is used for all input columns. " + "When -match-columns is specified to the -roi option, the input and roi metrics must have the same number of columns, " + "and for each input column's index, the same column index is used in the roi metric. " + "If the -match-columns option to -roi is used while the -column option is also used, the number of columns of the roi metric must match the input metric, " + "and it will use the roi column with the index of the selected input column.\n\n" + "The vector output metric is organized such that the X, Y, and Z components from a single input column are consecutive columns." ); return ret; } void AlgorithmMetricGradient::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { SurfaceFile* mySurf = myParams->getSurface(1); MetricFile* myMetricIn = myParams->getMetric(2); MetricFile* myMetricOut = myParams->getOutputMetric(3); float myPresmooth = -1.0f;//negative or zero means no smoothing OptionalParameter* presmooth = myParams->getOptionalParameter(4); if (presmooth->m_present) { myPresmooth = (float)presmooth->getDouble(1); if (myPresmooth <= 0.0f) throw AlgorithmException("presmooth kernel size must be positive"); } MetricFile* myRoi = NULL; bool matchRoiColumns = false; OptionalParameter* roiOption = myParams->getOptionalParameter(5); if (roiOption->m_present) { myRoi = roiOption->getMetric(1); matchRoiColumns = roiOption->getOptionalParameter(2)->m_present; } MetricFile* myVectorsOut = NULL; OptionalParameter* vecOut = myParams->getOptionalParameter(6); if (vecOut->m_present) { myVectorsOut = vecOut->getOutputMetric(1); } int32_t myColumn = -1; OptionalParameter* columnSelect = myParams->getOptionalParameter(7); if (columnSelect->m_present) { myColumn = (int)myMetricIn->getMapIndexFromNameOrNumber(columnSelect->getString(1)); if (myColumn < 0) { throw AlgorithmException("invalid column specified"); } } MetricFile* corrAreaMetric = NULL; OptionalParameter* corrAreaOpt = myParams->getOptionalParameter(8); if (corrAreaOpt->m_present) { corrAreaMetric = corrAreaOpt->getMetric(1); } OptionalParameter* avgNormals = myParams->getOptionalParameter(9); bool myAvgNormals = avgNormals->m_present; AlgorithmMetricGradient(myProgObj, mySurf, myMetricIn, myMetricOut, myVectorsOut, myPresmooth, myRoi, myAvgNormals, myColumn, corrAreaMetric, matchRoiColumns);//executes the algorithm } AlgorithmMetricGradient::AlgorithmMetricGradient(ProgressObject* myProgObj, SurfaceFile* mySurf, const MetricFile* myMetricIn, MetricFile* myMetricOut, MetricFile* myVectorsOut, const float myPresmooth, const MetricFile* myRoi, const bool myAvgNormals, const int32_t myColumn, const MetricFile* corrAreaMetric, const bool matchRoiColumns) : AbstractAlgorithm(myProgObj) { ProgressObject* smoothProgress = NULL; if (myProgObj != NULL && myPresmooth > 0.0f) { smoothProgress = myProgObj->addAlgorithm(AlgorithmMetricSmoothing::getAlgorithmWeight()); } LevelProgress myProgress(myProgObj); int32_t numNodes = mySurf->getNumberOfNodes(); if (myMetricIn->getNumberOfNodes() != numNodes) {//TODO: write these as static functions into an AlgorithmHelper class? throw AlgorithmException("metric does not match surface in number of vertices"); } if (myRoi != NULL && myRoi->getNumberOfNodes() != numNodes) { throw AlgorithmException("roi metric does not match surface in number of vertices"); } if (corrAreaMetric != NULL && corrAreaMetric->getNumberOfNodes() != numNodes) { throw AlgorithmException("corrected areas metric does not match surface in number of vertices"); } int32_t numColumns = myMetricIn->getNumberOfColumns(); if (myColumn < -1 || myColumn >= numColumns) { throw AlgorithmException("invalid column number"); } if (myRoi != NULL && matchRoiColumns && numColumns != myRoi->getNumberOfColumns()) { throw AlgorithmException("match roi columns specified, but roi metric has the wrong number of columns"); } const MetricFile* toProcess = myMetricIn; MetricFile processTemp; int32_t useColumn = myColumn; if (myPresmooth > 0.0f) { AlgorithmMetricSmoothing(smoothProgress, mySurf, myMetricIn, myPresmooth, &processTemp, myRoi, matchRoiColumns, false, myColumn, corrAreaMetric, MetricSmoothingObject::GEO_GAUSS_AREA); toProcess = &processTemp; if (myColumn != -1) { useColumn = 0; } } const float* myNormals = NULL; vector avgNormalStorage; if (myAvgNormals) { avgNormalStorage = mySurf->computeAverageNormals(); myNormals = avgNormalStorage.data(); } else { mySurf->computeNormals(); myNormals = mySurf->getNormalData(); } vector sqrtCorrAreas;//same logic as GeodesicHelper vector sqrtVertAreas; const float* vertAreas = NULL; vector areaData; if (corrAreaMetric != NULL) { sqrtCorrAreas.resize(numNodes); mySurf->computeNodeAreas(sqrtVertAreas); const float* corrAreaData = corrAreaMetric->getValuePointerForColumn(0); for (int i = 0; i < numNodes; ++i) { sqrtCorrAreas[i] = sqrt(corrAreaData[i]); sqrtVertAreas[i] = sqrt(sqrtVertAreas[i]); } vertAreas = corrAreaData; } else { mySurf->computeNodeAreas(areaData); vertAreas = areaData.data(); } const float* myCoords = mySurf->getCoordinateData(); bool haveWarned = false, haveFailed = false;//print warning or failure messages only once if (myColumn == -1) { myMetricOut->setNumberOfNodesAndColumns(numNodes, numColumns); myMetricOut->setStructure(mySurf->getStructure()); float* myVecScratch = NULL; if (myVectorsOut != NULL) { myVectorsOut->setNumberOfNodesAndColumns(numNodes, numColumns * 3); myVectorsOut->setStructure(mySurf->getStructure()); myVecScratch = new float[numNodes * 3]; } float* myScratch = new float[numNodes]; for (int32_t col = 0; col < numColumns; ++col) { const float* myRoiColumn = NULL; if (myRoi != NULL) { if (matchRoiColumns) { myRoiColumn = myRoi->getValuePointerForColumn(col); } else { myRoiColumn = myRoi->getValuePointerForColumn(0); } } const float* myMetricColumn = toProcess->getValuePointerForColumn(col); myMetricOut->setColumnName(col, toProcess->getColumnName(col) + ", gradient"); *(myMetricOut->getPaletteColorMapping(col)) = *(toProcess->getPaletteColorMapping(col));//copy the palette settings if (myVectorsOut != NULL) { myVectorsOut->setColumnName(col * 3, toProcess->getColumnName(col) + ", gradient vector X"); myVectorsOut->setColumnName(col * 3 + 1, toProcess->getColumnName(col) + ", gradient vector Y"); myVectorsOut->setColumnName(col * 3 + 2, toProcess->getColumnName(col) + ", gradient vector Z"); } #pragma omp CARET_PAR { Vector3D somevec, xhat, yhat; float sanity = 0.0f; CaretPointer myTopoHelp = mySurf->getTopologyHelper();//this stores and reuses helpers, so it isn't really a problem to call inside the loop #pragma omp CARET_FOR schedule(dynamic) for (int32_t i = 0; i < numNodes; ++i) { if (myRoiColumn != NULL && myRoiColumn[i] <= 0.0f) { myScratch[i] = 0.0f; if (myVecScratch != NULL) { myVecScratch[i] = 0.0f; myVecScratch[i + numNodes] = 0.0f; myVecScratch[i + numNodes * 2] = 0.0f; } continue; } int32_t numNeigh; int32_t i3 = i * 3; const int32_t* myNeighbors = myTopoHelp->getNodeNeighbors(i, numNeigh);//one function call isn't that bad, most time spent is floating point math anyway Vector3D myNormal = Vector3D(myNormals + i3).normal();//should already be normalized, but just in case Vector3D myCoord = myCoords + i3; float nodeValue = myMetricColumn[i]; somevec[2] = 0.0; if (myNormal[0] > myNormal[1]) {//generate a vector not parallel to normal somevec[0] = 0.0; somevec[1] = 1.0; } else { somevec[0] = 1.0; somevec[1] = 0.0; } xhat = myNormal.cross(somevec).normal(); yhat = myNormal.cross(xhat).normal();//xhat, yhat are orthogonal unit vectors describing a coord system with k = surface normal int neighCount = 0;//count within-roi neighbors, not simply surface neighbors if (numNeigh >= 2) { FloatMatrix myRegress = FloatMatrix::zeros(3, 4); for (int32_t j = 0; j < numNeigh; ++j) { int32_t whichNode = myNeighbors[j]; int32_t whichNode3 = whichNode * 3; if (myRoiColumn == NULL || myRoiColumn[whichNode] > 0.0f) { ++neighCount; float tempf = myMetricColumn[whichNode] - nodeValue; Vector3D neighCoord = myCoords + whichNode3; somevec = neighCoord - myCoord; float origMag = somevec.length();//save the original length float unrollMag = origMag; float opposite = somevec.dot(myNormal);//check for division by close to zero if (abs(opposite) > 0.035f * origMag)//do not do unrolling on very small angles - this is ~2 degrees { unrollMag = origMag * asin(opposite / origMag) * origMag / opposite; } if (corrAreaMetric != NULL) { unrollMag *= (sqrtCorrAreas[i] + sqrtCorrAreas[whichNode]) / (sqrtVertAreas[i] + sqrtVertAreas[whichNode]); } float xmag = xhat.dot(somevec);//dot product to get the direction in 2d float ymag = yhat.dot(somevec); float mag2d = sqrt(xmag * xmag + ymag * ymag);//get the new magnitude, to divide out xmag *= unrollMag / mag2d;//normalize the 2d vector and multiply by unrolled length ymag *= unrollMag / mag2d; myRegress[0][0] += xmag * xmag * vertAreas[whichNode];//gather A'A and A'b sums for regression, weighted by vertex area myRegress[0][1] += xmag * ymag * vertAreas[whichNode]; myRegress[0][2] += xmag * vertAreas[whichNode]; myRegress[1][1] += ymag * ymag * vertAreas[whichNode]; myRegress[1][2] += ymag * vertAreas[whichNode]; myRegress[2][2] += vertAreas[whichNode]; myRegress[0][3] += xmag * tempf * vertAreas[whichNode]; myRegress[1][3] += ymag * tempf * vertAreas[whichNode]; myRegress[2][3] += tempf * vertAreas[whichNode]; } } if (neighCount >= 2) { myRegress[1][0] = myRegress[0][1];//complete the symmetric elements myRegress[2][0] = myRegress[0][2]; myRegress[2][1] = myRegress[1][2]; myRegress[2][2] += vertAreas[i];//include center (metric and coord differences will be zero, so this is all that is needed) FloatMatrix myRref = myRegress.reducedRowEchelon(); somevec = xhat * myRref[0][3] + yhat * myRref[1][3];//somevec is now our surface gradient sanity = somevec[0] + somevec[1] + somevec[2]; } } if (neighCount > 0 && (neighCount < 2 || sanity != sanity)) { if (!haveWarned && myRoi == NULL) {//don't issue this warning with an ROI, because it is somewhat expected haveWarned = true; CaretLogWarning("WARNING: gradient calculation found a NaN/inf with regression method for at least vertex " + AString::number(i)); } float xgrad = 0.0f, ygrad = 0.0f, totalWeight = 0.0f; for (int32_t j = 0; j < numNeigh; ++j) { int32_t whichNode = myNeighbors[j]; int32_t whichNode3 = whichNode * 3; if (myRoiColumn == NULL || myRoiColumn[whichNode] > 0.0f) { float tempf = myMetricColumn[whichNode] - nodeValue; Vector3D neighCoord = myCoords + whichNode3; somevec = neighCoord - myCoord; float origMag = somevec.length();//save the original length float unrollMag = origMag; float opposite = somevec.dot(myNormal);//check for division by close to zero if (abs(opposite) > 0.035f * origMag)//do not do unrolling on very small angles - this is ~2 degrees { unrollMag = origMag * asin(opposite / origMag) * origMag / opposite; } if (corrAreaMetric != NULL) { unrollMag *= (sqrtCorrAreas[i] + sqrtCorrAreas[whichNode]) / (sqrtVertAreas[i] + sqrtVertAreas[whichNode]); } float xmag = xhat.dot(somevec);//dot product to get the direction in 2d float ymag = yhat.dot(somevec); float mag2d = sqrt(xmag * xmag + ymag * ymag);//get the new magnitude, to divide out tempf /= unrollMag * mag2d;//difference divided by distance gives point estimate of gradient magnitude, also divide by magnitude of 2d vector to normalize the next step at the same time xgrad += xmag * tempf * vertAreas[whichNode];//point estimate of gradient magnitude times normalized projected direction gives 2d estimate of gradient ygrad += ymag * tempf * vertAreas[whichNode];//average point estimates for each neighbor to estimate local gradient totalWeight += vertAreas[whichNode]; } } xgrad /= totalWeight;//weighted average ygrad /= totalWeight; somevec = xhat * xgrad + yhat * ygrad;//unproject back into 3d sanity = somevec[0] + somevec[1] + somevec[2]; } if (neighCount <= 0 || sanity != sanity) { if (!haveFailed && myRoiColumn == NULL) {//don't warn with an roi, they can be strange haveFailed = true; CaretLogWarning("Failed to compute gradient for at least vertex " + AString::number(i) + " with standard and fallback methods, outputting ZERO, check your surface for disconnected vertices or other strangeness"); } somevec[0] = 0.0f; somevec[1] = 0.0f; somevec[2] = 0.0f; } if (myVecScratch != NULL) { myVecScratch[i] = somevec[0];//split them up far, so that they can be set to columns easily myVecScratch[numNodes + i] = somevec[1]; myVecScratch[numNodes * 2 + i] = somevec[2]; } myScratch[i] = MathFunctions::vectorLength(somevec); } } if (myVectorsOut != NULL) { myVectorsOut->setValuesForColumn(col * 3, myVecScratch); myVectorsOut->setValuesForColumn(col * 3 + 1, myVecScratch + numNodes); myVectorsOut->setValuesForColumn(col * 3 + 2, myVecScratch + (numNodes * 2)); } myMetricOut->setValuesForColumn(col, myScratch); myProgress.reportProgress(((float)col + 1) / numColumns); } delete[] myScratch; if (myVecScratch != NULL) { delete[] myVecScratch; } } else { myMetricOut->setNumberOfNodesAndColumns(numNodes, 1); myMetricOut->setStructure(mySurf->getStructure()); float* myVecScratch = NULL; if (myVectorsOut != NULL) { myVectorsOut->setNumberOfNodesAndColumns(numNodes, 3); myVectorsOut->setStructure(mySurf->getStructure()); myVectorsOut->setColumnName(0, toProcess->getColumnName(useColumn) + ", gradient vector X"); myVectorsOut->setColumnName(1, toProcess->getColumnName(useColumn) + ", gradient vector Y"); myVectorsOut->setColumnName(2, toProcess->getColumnName(useColumn) + ", gradient vector Z"); myVecScratch = new float[numNodes * 3]; } float* myScratch = new float[numNodes]; const float* myRoiColumn = NULL; if (myRoi != NULL) { if (matchRoiColumns) { myRoiColumn = myRoi->getValuePointerForColumn(myColumn);//use the ORIGINAL column number, not the one that has been modified due to a presmoothing step that generated a new single column metric } else { myRoiColumn = myRoi->getValuePointerForColumn(0); } } const float* myMetricColumn = toProcess->getValuePointerForColumn(useColumn); myMetricOut->setColumnName(0, toProcess->getColumnName(useColumn) + ", gradient"); *(myMetricOut->getPaletteColorMapping(0)) = *(toProcess->getPaletteColorMapping(useColumn));//copy the palette settings #pragma omp CARET_PAR { Vector3D somevec, xhat, yhat; float sanity = 0.0f; CaretPointer myTopoHelp = mySurf->getTopologyHelper(); #pragma omp CARET_FOR schedule(dynamic) for (int32_t i = 0; i < numNodes; ++i) { if (myRoiColumn != NULL && myRoiColumn[i] <= 0.0f) { myScratch[i] = 0.0f; if (myVecScratch != NULL) { myVecScratch[i] = 0.0f; myVecScratch[i + numNodes] = 0.0f; myVecScratch[i + numNodes * 2] = 0.0f; } continue; } int32_t numNeigh; int32_t i3 = i * 3; const int32_t* myNeighbors = myTopoHelp->getNodeNeighbors(i, numNeigh); Vector3D myNormal = Vector3D(myNormals + i3).normal();//should already be normalized, but just in case Vector3D myCoord = myCoords + i3; float nodeValue = myMetricColumn[i]; somevec[2] = 0.0; if (myNormal[0] > myNormal[1]) {//generate a vector not parallel to normal somevec[0] = 0.0; somevec[1] = 1.0; } else { somevec[0] = 1.0; somevec[1] = 0.0; } xhat = myNormal.cross(somevec).normal(); yhat = myNormal.cross(xhat).normal();//xhat, yhat are orthogonal unit vectors describing a coord system with k = surface normal int neighCount = 0;//count within-roi neighbors, not simply surface neighbors if (numNeigh >= 2) { FloatMatrix myRegress = FloatMatrix::zeros(3, 4); for (int32_t j = 0; j < numNeigh; ++j) { int32_t whichNode = myNeighbors[j]; int32_t whichNode3 = whichNode * 3; if (myRoiColumn == NULL || myRoiColumn[whichNode] > 0.0f) { ++neighCount; float tempf = myMetricColumn[whichNode] - nodeValue; Vector3D neighCoord = myCoords + whichNode3; somevec = neighCoord - myCoord; float origMag = somevec.length();//save the original length float unrollMag = origMag; float opposite = somevec.dot(myNormal);//check for division by close to zero if (abs(opposite) > 0.035f * origMag)//do not do unrolling on very small angles - this is ~2 degrees { unrollMag = origMag * asin(opposite / origMag) * origMag / opposite; } if (corrAreaMetric != NULL) { unrollMag *= (sqrtCorrAreas[i] + sqrtCorrAreas[whichNode]) / (sqrtVertAreas[i] + sqrtVertAreas[whichNode]); } float xmag = xhat.dot(somevec);//dot product to get the direction in 2d float ymag = yhat.dot(somevec); float mag2d = sqrt(xmag * xmag + ymag * ymag);//get the new magnitude, to divide out xmag *= unrollMag / mag2d;//normalize the 2d vector and multiply by unrolled length ymag *= unrollMag / mag2d; myRegress[0][0] += xmag * xmag * vertAreas[whichNode];//gather A'A and A'b sums for regression, weighted by vertex area myRegress[0][1] += xmag * ymag * vertAreas[whichNode]; myRegress[0][2] += xmag * vertAreas[whichNode]; myRegress[1][1] += ymag * ymag * vertAreas[whichNode]; myRegress[1][2] += ymag * vertAreas[whichNode]; myRegress[2][2] += vertAreas[whichNode]; myRegress[0][3] += xmag * tempf * vertAreas[whichNode]; myRegress[1][3] += ymag * tempf * vertAreas[whichNode]; myRegress[2][3] += tempf * vertAreas[whichNode]; } } if (neighCount >= 2) { myRegress[1][0] = myRegress[0][1];//complete the symmetric elements myRegress[2][0] = myRegress[0][2]; myRegress[2][1] = myRegress[1][2]; myRegress[2][2] += vertAreas[i];//include center (metric and coord differences will be zero, so this is all that is needed) FloatMatrix myRref = myRegress.reducedRowEchelon(); somevec = xhat * myRref[0][3] + yhat * myRref[1][3];//somevec is now our surface gradient sanity = somevec[0] + somevec[1] + somevec[2]; } } if (neighCount > 0 && (neighCount < 2 || sanity != sanity)) { if (!haveWarned && myRoi == NULL) {//don't issue this warning with an ROI, because it is somewhat expected haveWarned = true; CaretLogWarning("WARNING: gradient calculation found a NaN/inf with regression method for at least vertex " + AString::number(i)); } float xgrad = 0.0f, ygrad = 0.0f, totalWeight = 0.0f; for (int32_t j = 0; j < numNeigh; ++j) { int32_t whichNode = myNeighbors[j]; int32_t whichNode3 = whichNode * 3; if (myRoiColumn == NULL || myRoiColumn[whichNode] > 0.0f) { float tempf = myMetricColumn[whichNode] - nodeValue; Vector3D neighCoord = myCoords + whichNode3; somevec = neighCoord - myCoord; float origMag = somevec.length();//save the original length float unrollMag = origMag; float opposite = somevec.dot(myNormal);//check for division by close to zero if (abs(opposite) > 0.035f * origMag)//do not do unrolling on very small angles - this is ~2 degrees { unrollMag = origMag * asin(opposite / origMag) * origMag / opposite; } if (corrAreaMetric != NULL) { unrollMag *= (sqrtCorrAreas[i] + sqrtCorrAreas[whichNode]) / (sqrtVertAreas[i] + sqrtVertAreas[whichNode]); } float xmag = xhat.dot(somevec);//dot product to get the direction in 2d float ymag = yhat.dot(somevec); float mag2d = sqrt(xmag * xmag + ymag * ymag);//get the new magnitude, to divide out tempf /= unrollMag * mag2d;//difference divided by distance gives point estimate of gradient magnitude, also divide by magnitude of 2d vector to normalize the next step at the same time xgrad += xmag * tempf * vertAreas[whichNode];//point estimate of gradient magnitude times normalized projected direction gives 2d estimate of gradient ygrad += ymag * tempf * vertAreas[whichNode];//average point estimates for each neighbor to estimate local gradient totalWeight += vertAreas[whichNode]; } } xgrad /= totalWeight;//weighted average ygrad /= totalWeight; somevec = xhat * xgrad + yhat * ygrad;//unproject back into 3d sanity = somevec[0] + somevec[1] + somevec[2]; } if (neighCount <= 0 || sanity != sanity) { if (!haveFailed && myRoiColumn == NULL) {//don't warn with an roi, they can be strange haveFailed = true; CaretLogWarning("Failed to compute gradient for at least vertex " + AString::number(i) + " with standard and fallback methods, outputting ZERO, check your surface for disconnected vertices or other strangeness"); } somevec[0] = 0.0f; somevec[1] = 0.0f; somevec[2] = 0.0f; } if (myVecScratch != NULL) { myVecScratch[i] = somevec[0];//split them up far, so that they can be set to columns easily myVecScratch[numNodes + i] = somevec[1]; myVecScratch[numNodes * 2 + i] = somevec[2]; } myScratch[i] = MathFunctions::vectorLength(somevec); } } if (myVectorsOut != NULL) { myVectorsOut->setValuesForColumn(0, myVecScratch); myVectorsOut->setValuesForColumn(1, myVecScratch + numNodes); myVectorsOut->setValuesForColumn(2, myVecScratch + (numNodes * 2)); } myMetricOut->setValuesForColumn(0, myScratch); delete[] myScratch; if (myVecScratch != NULL) { delete[] myVecScratch; } } } float AlgorithmMetricGradient::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmMetricGradient::getSubAlgorithmWeight() { return AlgorithmMetricSmoothing::getAlgorithmWeight();//if you use a subalgorithm } workbench-1.1.1/src/Algorithms/AlgorithmMetricGradient.h000066400000000000000000000043271255417355300233350ustar00rootroot00000000000000#ifndef __ALGORITHM_METRIC_GRADIENT_H__ #define __ALGORITHM_METRIC_GRADIENT_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmMetricGradient : public AbstractAlgorithm { AlgorithmMetricGradient(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmMetricGradient(ProgressObject* myProgObj, SurfaceFile* mySurf, const MetricFile* myMetricIn, MetricFile* myMetricOut, MetricFile* myVectorsOut = NULL, const float myPresmooth = -1.0f, const MetricFile* myRoi = NULL, const bool myAvgNormals = false, const int32_t myColumn = -1, const MetricFile* corrAreaMetric = NULL, bool matchRoiColumns = false); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmMetricGradient; } #endif //__ALGORITHM_METRIC_GRADIENT_H__ workbench-1.1.1/src/Algorithms/AlgorithmMetricROIsFromExtrema.cxx000066400000000000000000000340751255417355300251440ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmMetricROIsFromExtrema.h" #include "AlgorithmException.h" #include "CaretAssert.h" #include "GeodesicHelper.h" #include "MetricFile.h" #include "SurfaceFile.h" #include using namespace caret; using namespace std; AString AlgorithmMetricROIsFromExtrema::getCommandSwitch() { return "-metric-rois-from-extrema"; } AString AlgorithmMetricROIsFromExtrema::getShortDescription() { return "CREATE METRIC ROI MAPS FROM EXTREMA MAPS"; } OperationParameters* AlgorithmMetricROIsFromExtrema::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceParameter(1, "surface", "the surface to use for geodesic distance"); ret->addMetricParameter(2, "metric", "the input metric file"); ret->addDoubleParameter(3, "limit", "geodesic distance limit from vertex, in mm"); ret->addMetricOutputParameter(4, "metric-out", "the output metric file"); OptionalParameter* gaussOpt = ret->createOptionalParameter(5, "-gaussian", "generate a gaussian kernel instead of a flat ROI"); gaussOpt->addDoubleParameter(1, "sigma", "the sigma for the gaussian kernel, in mm"); OptionalParameter* roiOption = ret->createOptionalParameter(6, "-roi", "select a region of interest to use"); roiOption->addMetricParameter(1, "roi-metric", "the area to use, as a metric"); OptionalParameter* overlapOpt = ret->createOptionalParameter(7, "-overlap-logic", "how to handle overlapping ROIs, default ALLOW"); overlapOpt->addStringParameter(1, "method", "the method of resolving overlaps"); OptionalParameter* columnSelect = ret->createOptionalParameter(8, "-column", "select a single input column to use"); columnSelect->addStringParameter(1, "column", "the column number or name"); ret->setHelpText( AString("For each nonzero value in each map, make a map with an ROI around that location. ") + "If the -gaussian option is specified, then normalized gaussian kernels are output instead of ROIs. " + "The argument to -overlap-logic must be one of ALLOW, CLOSEST, or EXCLUDE. " + "ALLOW is the default, and means that ROIs are treated independently and may overlap. " + "CLOSEST means that ROIs may not overlap, and that no ROI contains vertices that are closer to a different seed vertex. " + "EXCLUDE means that ROIs may not overlap, and that any vertex within range of more than one ROI does not belong to any ROI." ); return ret; } void AlgorithmMetricROIsFromExtrema::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { SurfaceFile* mySurf = myParams->getSurface(1); MetricFile* myMetric = myParams->getMetric(2); float limit = (float)myParams->getDouble(3); MetricFile* myMetricOut = myParams->getOutputMetric(4); OptionalParameter* gaussOpt = myParams->getOptionalParameter(5); float sigma = -1.0f; if (gaussOpt->m_present) {//set up to use a gaussian function sigma = (float)gaussOpt->getDouble(1); if (sigma <= 0.0f) { throw AlgorithmException("invalid sigma specified"); } } MetricFile* myRoi = NULL; OptionalParameter* roiOption = myParams->getOptionalParameter(6); if (roiOption->m_present) { myRoi = roiOption->getMetric(1); } OverlapLogicEnum::Enum overlapType = OverlapLogicEnum::ALLOW; OptionalParameter* overlapOpt = myParams->getOptionalParameter(7); if (overlapOpt->m_present) { bool ok = false; overlapType = OverlapLogicEnum::fromName(overlapOpt->getString(1), &ok); if (!ok) throw AlgorithmException("unrecognized overlap method: " + overlapOpt->getString(1)); } int myColumn = -1; OptionalParameter* columnSelect = myParams->getOptionalParameter(8); if (columnSelect->m_present) { myColumn = (int)myMetric->getMapIndexFromNameOrNumber(columnSelect->getString(1)); if (myColumn < 0) { throw AlgorithmException("invalid column specified"); } } AlgorithmMetricROIsFromExtrema(myProgObj, mySurf, myMetric, limit, myMetricOut, sigma, myRoi, overlapType, myColumn); } AlgorithmMetricROIsFromExtrema::AlgorithmMetricROIsFromExtrema(ProgressObject* myProgObj, const SurfaceFile* mySurf, const MetricFile* myMetric, const float& limit, MetricFile* myMetricOut, const float& sigma, const MetricFile* myRoi, const OverlapLogicEnum::Enum& overlapType, const int& myColumn) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); int numNodes = mySurf->getNumberOfNodes(); if (myMetric->getNumberOfNodes() != numNodes) throw AlgorithmException("surface and metric files have different number of nodes"); const float* roiData = NULL; if (myRoi != NULL) { if (myRoi->getNumberOfNodes() != numNodes) throw AlgorithmException("roi metric has a different number of nodes"); roiData = myRoi->getValuePointerForColumn(0); } int numCols = myMetric->getNumberOfColumns(); if (myColumn < -1 || myColumn >= numCols) { throw AlgorithmException("invalid column number"); } int64_t extremaCount = 0; if (myColumn == -1) { if (roiData == NULL) { for (int i = 0; i < numCols; ++i) { const float* data = myMetric->getValuePointerForColumn(i); for (int j = 0; j < numNodes; ++j) { if (data[j] != 0.0f) ++extremaCount; } } } else { for (int i = 0; i < numCols; ++i) { const float* data = myMetric->getValuePointerForColumn(i); for (int j = 0; j < numNodes; ++j) { if (roiData[j] > 0.0f && data[j] != 0.0f) ++extremaCount; } } } } else { if (roiData == NULL) { const float* data = myMetric->getValuePointerForColumn(0); for (int j = 0; j < numNodes; ++j) { if (data[j] != 0.0f) ++extremaCount; } } else { const float* data = myMetric->getValuePointerForColumn(0); for (int j = 0; j < numNodes; ++j) { if (roiData[j] > 0.0f && data[j] != 0.0f) ++extremaCount; } } } if (extremaCount >= (1LL<<31)) throw AlgorithmException("too many output maps for a metric file");//hopefully this is never needed int32_t mapsOut = (int32_t)extremaCount; vector excludeDists(numNodes, -1.0f); vector excludeSources(numNodes, -1); vector > roiLists(mapsOut); CaretPointer myHelp = mySurf->getGeodesicHelper(); int64_t mapCounter = 0; if (myColumn == -1) { for (int i = 0; i < numCols; ++i) { const float* data = myMetric->getValuePointerForColumn(i); processMap(data, excludeDists, excludeSources, roiLists, mapCounter, myHelp, limit, roiData, overlapType, numNodes); } } else { const float* data = myMetric->getValuePointerForColumn(0); processMap(data, excludeDists, excludeSources, roiLists, mapCounter, myHelp, limit, roiData, overlapType, numNodes); } CaretAssert(mapCounter == extremaCount); myMetricOut->setNumberOfNodesAndColumns(numNodes, mapsOut); myMetricOut->setStructure(mySurf->getStructure()); vector tempCol(numNodes, 0.0f); if (sigma > 0.0f) { float gaussDenom = -0.5f / sigma / sigma; for (int32_t i = 0; i < mapsOut; ++i) { double accum = 0.0; map::iterator myEnd = roiLists[i].end(); for (map::iterator iter = roiLists[i].begin(); iter != myEnd; ++iter) { float gaussVal = exp(iter->second * iter->second * gaussDenom); accum += gaussVal; tempCol[iter->first] = gaussVal;//initial kernel value } for (map::iterator iter = roiLists[i].begin(); iter != myEnd; ++iter) { tempCol[iter->first] /= accum;//normalize } myMetricOut->setValuesForColumn(i, tempCol.data()); for (map::iterator iter = roiLists[i].begin(); iter != myEnd; ++iter) { tempCol[iter->first] = 0.0f;//rezero changed values for next map } } } else { for (int32_t i = 0; i < mapsOut; ++i) { map::iterator myEnd = roiLists[i].end(); for (map::iterator iter = roiLists[i].begin(); iter != myEnd; ++iter) { tempCol[iter->first] = 1.0f;//make roi } myMetricOut->setValuesForColumn(i, tempCol.data()); for (map::iterator iter = roiLists[i].begin(); iter != myEnd; ++iter) { tempCol[iter->first] = 0.0f;//rezero changed values for next map } } } } void AlgorithmMetricROIsFromExtrema::processMap(const float* data, vector& excludeDists, vector excludeSources, vector >& roiLists, int64_t& mapCounter, CaretPointer& myHelp, const float& limit, const float* roiData, const OverlapLogicEnum::Enum& overlapType, const int& numNodes) { for (int j = 0; j < numNodes; ++j) { if ((roiData == NULL || roiData[j] > 0.0f) && data[j] != 0.0f) { vector nodeList; vector distList; myHelp->getNodesToGeoDist(j, limit, nodeList, distList); int listNum = (int)nodeList.size(); switch (overlapType) { case OverlapLogicEnum::ALLOW: if (roiData == NULL) { for (int k = 0; k < listNum; ++k) { const int32_t& thisNode = nodeList[k]; roiLists[mapCounter][thisNode] = distList[k]; } } else { for (int k = 0; k < listNum; ++k) { const int32_t& thisNode = nodeList[k]; if (roiData[thisNode] > 0.0f) { roiLists[mapCounter][thisNode] = distList[k]; } } } break; case OverlapLogicEnum::CLOSEST: for (int k = 0; k < listNum; ++k) { const int32_t& thisNode = nodeList[k]; if (roiData == NULL || roiData[thisNode] > 0.0f) { const float& thisDist = distList[k]; if (excludeDists[thisNode] < 0.0f) { excludeDists[thisNode] = thisDist; excludeSources[thisNode] = mapCounter; roiLists[mapCounter][thisNode] = thisDist; } else { if (excludeDists[thisNode] > thisDist) { roiLists[excludeSources[thisNode]].erase(thisNode); } excludeDists[thisNode] = thisDist; excludeSources[thisNode] = mapCounter; roiLists[mapCounter][thisNode] = thisDist; } } } break; case OverlapLogicEnum::EXCLUDE: for (int k = 0; k < listNum; ++k) { const int32_t& thisNode = nodeList[k]; if (roiData == NULL || roiData[thisNode] > 0.0f) { const float& thisDist = distList[k]; if (excludeDists[thisNode] < 0.0f) { excludeDists[thisNode] = thisDist; excludeSources[thisNode] = mapCounter; roiLists[mapCounter][thisNode] = thisDist; } else { if (excludeSources[thisNode] != -1) { roiLists[excludeSources[thisNode]].erase(thisNode); excludeSources[thisNode] = -1; } } } } break; } ++mapCounter; } } } float AlgorithmMetricROIsFromExtrema::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmMetricROIsFromExtrema::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmMetricROIsFromExtrema.h000066400000000000000000000046111255417355300245620ustar00rootroot00000000000000#ifndef __ALGORITHM_METRIC_ROIS_FROM_EXTREMA_H__ #define __ALGORITHM_METRIC_ROIS_FROM_EXTREMA_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" #include "OverlapLogicEnum.h" #include #include namespace caret { class GeodesicHelper; class AlgorithmMetricROIsFromExtrema : public AbstractAlgorithm { AlgorithmMetricROIsFromExtrema(); void processMap(const float* data, std::vector& excludeDists, std::vector excludeSouces, std::vector >& roiLists, int64_t& mapCounter, CaretPointer& myHelp, const float& limit, const float* roiData, const OverlapLogicEnum::Enum& overlapType, const int& numNodes); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmMetricROIsFromExtrema(ProgressObject* myProgObj, const SurfaceFile* mySurf, const MetricFile* myMetric, const float& limit, MetricFile* myMetricOut, const float& sigma = -1.0f, const MetricFile* myRoi = NULL, const OverlapLogicEnum::Enum& overlapType = OverlapLogicEnum::ALLOW, const int& myColumn = -1); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmMetricROIsFromExtrema; } #endif //__ALGORITHM_METRIC_ROIS_FROM_EXTREMA_H__ workbench-1.1.1/src/Algorithms/AlgorithmMetricROIsToBorder.cxx000066400000000000000000000141351255417355300244260ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmMetricROIsToBorder.h" #include "AlgorithmException.h" #include "Border.h" #include "BorderFile.h" #include "BorderTracingHelper.h" #include "GiftiLabelTable.h" #include "MetricFile.h" #include "SurfaceFile.h" #include using namespace caret; using namespace std; AString AlgorithmMetricROIsToBorder::getCommandSwitch() { return "-metric-rois-to-border"; } AString AlgorithmMetricROIsToBorder::getShortDescription() { return "DRAW BORDERS AROUND METRIC ROIS"; } OperationParameters* AlgorithmMetricROIsToBorder::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceParameter(1, "surface", "the surface to use for neighbor information"); ret->addMetricParameter(2, "metric", "the input metric containing ROIs"); ret->addStringParameter(3, "class-name", "the name to use for the class of the output borders"); ret->addBorderOutputParameter(4, "border-out", "the output border file"); OptionalParameter* placeOpt = ret->createOptionalParameter(5, "-placement", "set how far along the edge border points are drawn"); placeOpt->addDoubleParameter(1, "fraction", "fraction along edge from inside vertex (default 0.33)"); OptionalParameter* columnSelect = ret->createOptionalParameter(6, "-column", "select a single column"); columnSelect->addStringParameter(1, "column", "the column number or name"); ret->setHelpText( AString("For each ROI column, finds all edges on the mesh that cross the boundary of the ROI, and draws borders through them. ") + "By default, this is done on all columns in the input file, using the map name as the name for the border." ); return ret; } void AlgorithmMetricROIsToBorder::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { SurfaceFile* mySurf = myParams->getSurface(1); MetricFile* myMetric = myParams->getMetric(2); AString className = myParams->getString(3); BorderFile* myBorderOut = myParams->getOutputBorder(4); float placement = 0.33f; OptionalParameter* placeOpt = myParams->getOptionalParameter(5); if (placeOpt->m_present) { placement = (float)placeOpt->getDouble(1); } OptionalParameter* columnSelect = myParams->getOptionalParameter(6); int columnNum = -1; if (columnSelect->m_present) {//set up to use the single column columnNum = (int)myMetric->getMapIndexFromNameOrNumber(columnSelect->getString(1)); if (columnNum < 0) { throw AlgorithmException("invalid column specified"); } } AlgorithmMetricROIsToBorder(myProgObj, mySurf, myMetric, className, myBorderOut, placement, columnNum); } AlgorithmMetricROIsToBorder::AlgorithmMetricROIsToBorder(ProgressObject* myProgObj, const SurfaceFile* mySurf, const MetricFile* myMetric, const AString& className, BorderFile* myBorderOut, const float& placement, const int& columnNum) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); if (mySurf->getNumberOfNodes() != myMetric->getNumberOfNodes()) throw AlgorithmException("label file does not match surface file number of vertices"); if (placement < 0.0f || placement > 1.0f || placement != placement) throw AlgorithmException("placement must be between 0 and 1"); if (columnNum < -1 || columnNum > myMetric->getNumberOfColumns()) throw AlgorithmException("invalid column specified"); myBorderOut->setStructure(mySurf->getStructure()); myBorderOut->setNumberOfNodes(mySurf->getNumberOfNodes()); BorderTracingHelper myHelper(mySurf); GiftiLabelTable myNameTable; if (columnNum == -1) { for (int col = 0; col < myMetric->getNumberOfColumns(); ++col) { vector > result = myHelper.traceData(myMetric->getValuePointerForColumn(col), BorderTracingHelper::GreaterThan(0.0f, false), placement); myNameTable.addLabel(myMetric->getMapName(col), rand() & 255, rand() & 255, rand() & 255); for (int i = 0; i < (int)result.size(); ++i) { result[i]->setClassName(className); result[i]->setName(myMetric->getMapName(col)); myBorderOut->addBorder(result[i].releasePointer());//NOTE: addBorder takes ownership of a RAW POINTER, shared_ptr won't release the pointer } } } else { myNameTable.addLabel(myMetric->getMapName(columnNum), rand() & 255, rand() & 255, rand() & 255); vector > result = myHelper.traceData(myMetric->getValuePointerForColumn(columnNum), BorderTracingHelper::GreaterThan(0.0f, false), placement); for (int i = 0; i < (int)result.size(); ++i) { result[i]->setClassName(className); result[i]->setName(myMetric->getMapName(columnNum)); myBorderOut->addBorder(result[i].releasePointer()); } } *(myBorderOut->getNameColorTable()) = myNameTable; } float AlgorithmMetricROIsToBorder::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmMetricROIsToBorder::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmMetricROIsToBorder.h000066400000000000000000000035231255417355300240520ustar00rootroot00000000000000#ifndef __ALGORITHM_METRIC_ROIS_TO_BORDER_H__ #define __ALGORITHM_METRIC_ROIS_TO_BORDER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmMetricROIsToBorder : public AbstractAlgorithm { AlgorithmMetricROIsToBorder(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmMetricROIsToBorder(ProgressObject* myProgObj, const SurfaceFile* mySurf, const MetricFile* myMetric, const AString& className, BorderFile* myBorderOut, const float& placement = 0.33f, const int& columnNum = -1); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmMetricROIsToBorder; } #endif //__ALGORITHM_METRIC_ROIS_TO_BORDER_H__ workbench-1.1.1/src/Algorithms/AlgorithmMetricReduce.cxx000066400000000000000000000123601255417355300233560ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmMetricReduce.h" #include "AlgorithmException.h" #include "MetricFile.h" #include "ReductionOperation.h" #include using namespace caret; using namespace std; AString AlgorithmMetricReduce::getCommandSwitch() { return "-metric-reduce"; } AString AlgorithmMetricReduce::getShortDescription() { return "PERFORM REDUCTION OPERATION ACROSS METRIC COLUMNS"; } OperationParameters* AlgorithmMetricReduce::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addMetricParameter(1, "metric-in", "the metric to reduce"); ret->addStringParameter(2, "operation", "the reduction operator to use"); ret->addMetricOutputParameter(3, "metric-out", "the output metric"); OptionalParameter* excludeOpt = ret->createOptionalParameter(4, "-exclude-outliers", "exclude outliers from each vector by standard deviation"); excludeOpt->addDoubleParameter(1, "sigma-below", "number of standard deviations below the mean to include"); excludeOpt->addDoubleParameter(2, "sigma-above", "number of standard deviations above the mean to include"); ret->setHelpText( AString("For each surface vertex, takes the data across columns as a vector, and performs the specified reduction on it, putting the result ") + "into the single output column at that vertex. The reduction operators are as follows:\n\n" + ReductionOperation::getHelpInfo() ); return ret; } void AlgorithmMetricReduce::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { MetricFile* metricIn = myParams->getMetric(1); AString opString = myParams->getString(2); MetricFile* metricOut = myParams->getOutputMetric(3); OptionalParameter* excludeOpt = myParams->getOptionalParameter(4); bool ok = false; ReductionEnum::Enum myReduce = ReductionEnum::fromName(opString, &ok); if (!ok) throw AlgorithmException("unrecognized operation string '" + opString + "'"); if (excludeOpt->m_present) { AlgorithmMetricReduce(myProgObj, metricIn, myReduce, metricOut, excludeOpt->getDouble(1), excludeOpt->getDouble(2)); } else { AlgorithmMetricReduce(myProgObj, metricIn, myReduce, metricOut); } } AlgorithmMetricReduce::AlgorithmMetricReduce(ProgressObject* myProgObj, const MetricFile* metricIn, const ReductionEnum::Enum& myReduce, MetricFile* metricOut) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); int numNodes = metricIn->getNumberOfNodes(); int numCols = metricIn->getNumberOfColumns(); if (numCols < 1 || numNodes < 1) throw AlgorithmException("input must have at least 1 column and 1 vertex"); metricOut->setNumberOfNodesAndColumns(numNodes, 1); metricOut->setStructure(metricIn->getStructure()); metricOut->setColumnName(0, ReductionEnum::toName(myReduce)); vector scratch(numCols); for (int node = 0; node < numNodes; ++node) { for (int col = 0; col < numCols; ++col) { scratch[col] = metricIn->getValue(node, col); } metricOut->setValue(node, 0, ReductionOperation::reduce(scratch.data(), numCols, myReduce)); } } AlgorithmMetricReduce::AlgorithmMetricReduce(ProgressObject* myProgObj, const MetricFile* metricIn, const ReductionEnum::Enum& myReduce, MetricFile* metricOut, const float& sigmaBelow, const float& sigmaAbove) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); int numNodes = metricIn->getNumberOfNodes(); int numCols = metricIn->getNumberOfColumns(); if (numCols < 1 || numNodes < 1) throw AlgorithmException("input must have at least 1 column and 1 vertex"); metricOut->setNumberOfNodesAndColumns(numNodes, 1); metricOut->setStructure(metricIn->getStructure()); metricOut->setColumnName(0, ReductionEnum::toName(myReduce)); vector scratch(numCols); for (int node = 0; node < numNodes; ++node) { for (int col = 0; col < numCols; ++col) { scratch[col] = metricIn->getValue(node, col); } metricOut->setValue(node, 0, ReductionOperation::reduceExcludeDev(scratch.data(), numCols, myReduce, sigmaBelow, sigmaAbove)); } } float AlgorithmMetricReduce::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmMetricReduce::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmMetricReduce.h000066400000000000000000000036131255417355300230040ustar00rootroot00000000000000#ifndef __ALGORITHM_METRIC_REDUCE_H__ #define __ALGORITHM_METRIC_REDUCE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" #include "ReductionEnum.h" namespace caret { class AlgorithmMetricReduce : public AbstractAlgorithm { AlgorithmMetricReduce(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmMetricReduce(ProgressObject* myProgObj, const MetricFile* metricIn, const ReductionEnum::Enum& myReduce, MetricFile* metricOut); AlgorithmMetricReduce(ProgressObject* myProgObj, const MetricFile* metricIn, const ReductionEnum::Enum& myReduce, MetricFile* metricOut, const float& sigmaBelow, const float& sigmaAbove); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmMetricReduce; } #endif //__ALGORITHM_METRIC_REDUCE_H__ workbench-1.1.1/src/Algorithms/AlgorithmMetricRegression.cxx000066400000000000000000000340571255417355300242760ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmMetricRegression.h" #include "AlgorithmException.h" #include "FloatMatrix.h" #include "MetricFile.h" #include "PaletteColorMapping.h" using namespace caret; using namespace std; AString AlgorithmMetricRegression::getCommandSwitch() { return "-metric-regression"; } AString AlgorithmMetricRegression::getShortDescription() { return "REGRESS METRICS OUT OF A METRIC FILE"; } OperationParameters* AlgorithmMetricRegression::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addMetricParameter(1, "metric-in", "the metric to regress from"); ret->addMetricOutputParameter(2, "metric-out", "the output metric"); OptionalParameter* roiOpt = ret->createOptionalParameter(3, "-roi", "only regress inside an roi"); roiOpt->addMetricParameter(1, "roi-metric", "the area to use for regression, as a metric"); OptionalParameter* columnOpt = ret->createOptionalParameter(4, "-column", "select a single column to regress from"); columnOpt->addStringParameter(1, "column", "the column number or name"); ParameterComponent* removeOpt = ret->createRepeatableParameter(5, "-remove", "specify a metric to regress out"); removeOpt->addMetricParameter(1, "metric", "the metric file to use"); OptionalParameter* removeColOpt = removeOpt->createOptionalParameter(2, "-remove-column", "select a column to use, rather than all"); removeColOpt->addStringParameter(1, "column", "the column number or name"); ParameterComponent* keepOpt = ret->createRepeatableParameter(6, "-keep", "specify a metric to include in regression, but not remove"); keepOpt->addMetricParameter(1, "metric", "the metric file to use"); OptionalParameter* keepColOpt = keepOpt->createOptionalParameter(2, "-keep-column", "select a column to use, rather than all"); keepColOpt->addStringParameter(1, "column", "the column number or name"); ret->setHelpText( AString("For each regressor, its mean across the surface is subtracted from its data. ") + "Each input map is then regressed against these, and a constant term. The resulting regressed slopes of all regressors specified with -remove " + "are multiplied with their respective regressor maps, and these are subtracted from the input map." ); return ret; } void AlgorithmMetricRegression::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { MetricFile* myMetricIn = myParams->getMetric(1); MetricFile* myMetricOut = myParams->getOutputMetric(2); MetricFile* myRoi = NULL; OptionalParameter* roiOpt = myParams->getOptionalParameter(3); if (roiOpt->m_present) { myRoi = roiOpt->getMetric(1); } int32_t myColumn = -1; OptionalParameter* columnOpt = myParams->getOptionalParameter(4); if (columnOpt->m_present) { myColumn = (int)myMetricIn->getMapIndexFromNameOrNumber(columnOpt->getString(1)); if (myColumn < 0) { throw AlgorithmException("invalid column specified"); } } vector > remove, keep; const vector& removeInstances = *(myParams->getRepeatableParameterInstances(5)); int numRemove = (int)removeInstances.size(); if (numRemove == 0) throw AlgorithmException("you must specify at least one 'remove' metric"); for (int i = 0; i < numRemove; ++i) { MetricFile* toRemove = removeInstances[i]->getMetric(1); int removeCol = -1; OptionalParameter* removeColOpt = removeInstances[i]->getOptionalParameter(2); if (removeColOpt->m_present) { removeCol = toRemove->getMapIndexFromNameOrNumber(removeColOpt->getString(1)); if (removeCol < 0) { throw AlgorithmException("invalid column specified for 'remove' metric '" + toRemove->getFileName() + "'"); } } remove.push_back(pair(toRemove, removeCol)); } const vector& keepInstances = *(myParams->getRepeatableParameterInstances(6)); int numKeep = (int)keepInstances.size(); for (int i = 0; i < numKeep; ++i) { MetricFile* toKeep = keepInstances[i]->getMetric(1); int keepCol = -1; OptionalParameter* keepColOpt = keepInstances[i]->getOptionalParameter(2); if (keepColOpt->m_present) { keepCol = toKeep->getMapIndexFromNameOrNumber(keepColOpt->getString(1)); if (keepCol < 0) { throw AlgorithmException("invalid column specified for 'keep' metric '" + toKeep->getFileName() + "'"); } } keep.push_back(pair(toKeep, keepCol)); } AlgorithmMetricRegression(myProgObj, myMetricIn, myMetricOut, remove, keep, myColumn, myRoi); } AlgorithmMetricRegression::AlgorithmMetricRegression(ProgressObject* myProgObj, const MetricFile* myMetricIn, MetricFile* myMetricOut, const vector >& remove, const vector >& keep, const int& myColumn, const MetricFile* myRoi) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); vector > regressCols;//because we are going to de-mean the input data int removeCount = 0; int numNodes = myMetricIn->getNumberOfNodes(); int numColumns = myMetricIn->getNumberOfColumns(); if (myColumn < -1 || myColumn >= numColumns) { throw AlgorithmException("invalid column number"); } int numRemove = (int)remove.size(); if (numRemove == 0) throw AlgorithmException("empty remove list in AlgorithmMetricRegression"); const float* roiData = NULL; int numUsedNodes = numNodes; if (myRoi != NULL) { if (myRoi->getNumberOfNodes() != numNodes) throw AlgorithmException("roi metric has different number of nodes"); roiData = myRoi->getValuePointerForColumn(0); numUsedNodes = 0; for (int i = 0; i < numNodes; ++i) { if (roiData[i] > 0.0f) ++numUsedNodes; } } for (int i = 0; i < numRemove; ++i) { const MetricFile* thisMetric = remove[i].first; if (thisMetric->getNumberOfNodes() != numNodes) { throw AlgorithmException("'remove' metric '" + thisMetric->getFileName() + "' has a different number of nodes than the input metric"); } int thisCol = remove[i].second; if (thisCol == -1) { int endCol = thisMetric->getNumberOfColumns(); removeCount += endCol; for (int j = 0; j < endCol; ++j) { regressCols.push_back(vector()); demeanCol(thisMetric->getValuePointerForColumn(j), numNodes, roiData, regressCols.back()); } } else { if (thisCol < 0 || thisCol >= thisMetric->getNumberOfColumns()) throw AlgorithmException("invalid column specified for metric '" + thisMetric->getFileName() + "'"); ++removeCount; regressCols.push_back(vector()); demeanCol(thisMetric->getValuePointerForColumn(thisCol), numNodes, roiData, regressCols.back()); } } int numKeep = (int)keep.size();//repeat, without increasing removeCount - this separates what gets removed after regression for (int i = 0; i < numKeep; ++i) { const MetricFile* thisMetric = keep[i].first; if (thisMetric->getNumberOfNodes() != numNodes) { throw AlgorithmException("'keep' metric '" + thisMetric->getFileName() + "' has a different number of nodes than the input metric"); } int thisCol = keep[i].second; if (thisCol == -1) { int endCol = thisMetric->getNumberOfColumns(); for (int j = 0; j < endCol; ++j) { regressCols.push_back(vector()); demeanCol(thisMetric->getValuePointerForColumn(j), numNodes, roiData, regressCols.back()); } } else { if (thisCol < 0 || thisCol >= thisMetric->getNumberOfColumns()) throw AlgorithmException("invalid column specified for metric '" + thisMetric->getFileName() + "'"); regressCols.push_back(vector()); demeanCol(thisMetric->getValuePointerForColumn(thisCol), numNodes, roiData, regressCols.back()); } } FloatMatrix xtrans(regressCols);//use naive matrix math - not particularly efficient, but it works regressCols.clear();//don't need this any more, should call destructor on each member vector and release the memory xtrans = xtrans.concatVert(FloatMatrix::ones(1, numUsedNodes));//add constant term FloatMatrix toInvert = xtrans * xtrans.transpose(); int invertSize = toInvert.getNumberOfRows(); //a bit of a hack, but it should work, though this causes the work of inversion to be done twice if (toInvert.reducedRowEchelon()[invertSize - 1][invertSize - 1] != 1.0f) throw AlgorithmException("regression encountered a non-invertible matrix, check your inputs for linear independence"); FloatMatrix solver = toInvert.inverse() * xtrans;//do most of the math in temporaries if (myColumn == -1) { myMetricOut->setNumberOfNodesAndColumns(numNodes, numColumns); myMetricOut->setStructure(myMetricIn->getStructure()); for (int i = 0; i < numColumns; ++i) { myMetricOut->setColumnName(i, myMetricIn->getColumnName(i) + " regressed"); *(myMetricOut->getPaletteColorMapping(i)) = *(myMetricIn->getPaletteColorMapping(i)); FloatMatrix y(numUsedNodes, 1);//create column vector for input - note that column vectors are extremely inefficient currently, as is copying the data... const float* data = myMetricIn->getValuePointerForColumn(i); int m = 0; for (int j = 0; j < numNodes; ++j) { if (roiData == NULL || roiData[j] > 0.0f) { y[m][0] = data[j]; ++m; } } FloatMatrix regressed = solver * y; vector outscratch(numNodes); m = 0; for (int j = 0; j < numNodes; ++j) { if (roiData == NULL || roiData[j] > 0.0f) { outscratch[j] = data[j]; for (int k = 0; k < removeCount; ++k) { outscratch[j] -= regressed[k][0] * xtrans[k][m]; } ++m; } else { outscratch[j] = 0.0f; } } myMetricOut->setValuesForColumn(i, outscratch.data()); } } else { myMetricOut->setNumberOfNodesAndColumns(numNodes, 1); myMetricOut->setStructure(myMetricIn->getStructure()); myMetricOut->setColumnName(0, myMetricIn->getColumnName(myColumn) + " regressed"); *(myMetricOut->getPaletteColorMapping(0)) = *(myMetricIn->getPaletteColorMapping(myColumn)); FloatMatrix y(numUsedNodes, 1);//create column vector for input - note that column vectors are extremely inefficient currently, as is copying the data... const float* data = myMetricIn->getValuePointerForColumn(myColumn); int m = 0; for (int j = 0; j < numNodes; ++j) { if (roiData == NULL || roiData[j] > 0.0f) { y[m][0] = data[j]; ++m; } } FloatMatrix regressed = solver * y; vector outscratch(numNodes); m = 0; for (int j = 0; j < numNodes; ++j) { if (roiData == NULL || roiData[j] > 0.0f) { outscratch[j] = data[j]; for (int k = 0; k < removeCount; ++k) { outscratch[j] -= regressed[k][0] * xtrans[k][m]; } ++m; } else { outscratch[j] = 0.0f; } } myMetricOut->setValuesForColumn(0, outscratch.data()); } } void AlgorithmMetricRegression::demeanCol(const float* data, const int& count, const float* roiData, std::vector< float >& out) { if (roiData == NULL) { double accum = 0.0; for (int i = 0; i < count; ++i) { accum += data[i]; } accum /= count; out.resize(count); for (int i = 0; i < count; ++i) { out[i] = data[i] - accum; } } else { int usedCount = 0; double accum = 0.0; for (int i = 0; i < count; ++i) { if (roiData[i] > 0.0f) { accum += data[i]; ++usedCount; } } accum /= usedCount; out.resize(usedCount); int m = 0; for (int i = 0; i < count; ++i) { if (roiData[i] > 0.0f) { out[m] = data[i] - accum; ++m; } } } } float AlgorithmMetricRegression::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmMetricRegression::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmMetricRegression.h000066400000000000000000000040251255417355300237130ustar00rootroot00000000000000#ifndef __ALGORITHM_METRIC_REGRESSION_H__ #define __ALGORITHM_METRIC_REGRESSION_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" #include #include namespace caret { class AlgorithmMetricRegression : public AbstractAlgorithm { AlgorithmMetricRegression(); void demeanCol(const float* data, const int& count, const float* roiData, std::vector& out); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmMetricRegression(ProgressObject* myProgObj, const MetricFile* myMetricIn, MetricFile* myMetricOut, const std::vector >& remove, const std::vector >& keep, const int& myColumn = -1, const MetricFile* myRoi = NULL); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmMetricRegression; } #endif //__ALGORITHM_METRIC_REGRESSION_H__ workbench-1.1.1/src/Algorithms/AlgorithmMetricRemoveIslands.cxx000066400000000000000000000147431255417355300247310ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmMetricRemoveIslands.h" #include "AlgorithmException.h" #include "MetricFile.h" #include "SurfaceFile.h" #include "TopologyHelper.h" #include #include using namespace caret; using namespace std; AString AlgorithmMetricRemoveIslands::getCommandSwitch() { return "-metric-remove-islands"; } AString AlgorithmMetricRemoveIslands::getShortDescription() { return "REMOVE ISLANDS FROM AN ROI METRIC"; } OperationParameters* AlgorithmMetricRemoveIslands::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceParameter(1, "surface", "the surface to use for neighbor information"); ret->addMetricParameter(2, "metric-in", "the input ROI metric"); ret->addMetricOutputParameter(3, "metric-out", "the output ROI metric"); OptionalParameter* corrAreaOpt = ret->createOptionalParameter(4, "-corrected-areas", "vertex areas to use instead of computing them from the surface"); corrAreaOpt->addMetricParameter(1, "area-metric", "the corrected vertex areas, as a metric"); ret->setHelpText( AString("Finds all connected areas in the ROI, and zeros out all but the largest one, in terms of surface area.") ); return ret; } void AlgorithmMetricRemoveIslands::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { SurfaceFile* mySurf = myParams->getSurface(1); MetricFile* myMetric = myParams->getMetric(2); MetricFile* myMetricOut = myParams->getOutputMetric(3); MetricFile* corrAreaMetric = NULL; OptionalParameter* corrAreaOpt = myParams->getOptionalParameter(4); if (corrAreaOpt->m_present) { corrAreaMetric = corrAreaOpt->getMetric(1); } AlgorithmMetricRemoveIslands(myProgObj, mySurf, myMetric, myMetricOut, corrAreaMetric); } AlgorithmMetricRemoveIslands::AlgorithmMetricRemoveIslands(ProgressObject* myProgObj, const SurfaceFile* mySurf, const MetricFile* myMetric, MetricFile* myMetricOut, const MetricFile* corrAreaMetric) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); int numNodes = mySurf->getNumberOfNodes(); if (myMetric->getNumberOfNodes() != numNodes) throw AlgorithmException("metric file has different number of nodes than the surface"); vector surfAreaData; const float* areaData = NULL; if (corrAreaMetric != NULL) { if (corrAreaMetric->getNumberOfNodes() != numNodes) throw AlgorithmException("corrected vertex area metric file has different number of nodes than the surface"); areaData = corrAreaMetric->getValuePointerForColumn(0); } else { mySurf->computeNodeAreas(surfAreaData); areaData = surfAreaData.data(); } int numCols = myMetric->getNumberOfColumns(); myMetricOut->setNumberOfNodesAndColumns(numNodes, numCols); myMetricOut->setStructure(myMetric->getStructure()); for (int col = 0; col < numCols; ++col) { vector > > areas; vector used(numNodes, 0); CaretPointer myHelp = mySurf->getTopologyHelper(); const float* roiData = myMetric->getValuePointerForColumn(col); myMetricOut->setColumnName(col, myMetric->getColumnName(col)); for (int i = 0; i < numNodes; ++i) { if (used[i] == 0 && roiData[i] > 0.0f) { areas.push_back(make_pair(0.0f, vector())); vector& thisAreaMembers = areas.back().second; float& thisSurfArea = areas.back().first; thisAreaMembers.push_back(i); thisSurfArea += areaData[i]; used[i] = 1; vector mystack; mystack.push_back(i); while (!mystack.empty()) { int curnode = mystack.back(); mystack.pop_back(); const vector& neighbors = myHelp->getNodeNeighbors(curnode); int numNeigh = (int)neighbors.size(); for (int j = 0; j < numNeigh; ++j) { int thisneigh = neighbors[j]; if (used[thisneigh] == 0 && roiData[thisneigh] > 0.0f) { used[thisneigh] = 1; thisAreaMembers.push_back(thisneigh); thisSurfArea += areaData[thisneigh]; mystack.push_back(thisneigh); } } } } } vector outscratch(numNodes, 0.0f); int numAreas = (int)areas.size(); if (numAreas > 0) { int bestIndex = 0; float bestArea = areas[0].first; for (int i = 1; i < numAreas; ++i) { float thisArea = (int)areas[i].first; if (thisArea > bestArea) { bestIndex = i; bestArea = thisArea; } } const vector& thisArea = areas[bestIndex].second; int numAreaNodes = (int)thisArea.size(); for (int i = 0; i < numAreaNodes; ++i) { outscratch[thisArea[i]] = 1.0f;//make it into a simple 0/1 metric, even if it wasn't before } } myMetricOut->setValuesForColumn(col, outscratch.data()); } } float AlgorithmMetricRemoveIslands::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmMetricRemoveIslands::getSubAlgorithmWeight() { return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmMetricRemoveIslands.h000066400000000000000000000034551255417355300243540ustar00rootroot00000000000000#ifndef __ALGORITHM_METRIC_REMOVE_ISLANDS_H__ #define __ALGORITHM_METRIC_REMOVE_ISLANDS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmMetricRemoveIslands : public AbstractAlgorithm { AlgorithmMetricRemoveIslands(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmMetricRemoveIslands(ProgressObject* myProgObj, const SurfaceFile* mySurf, const MetricFile* myMetric, MetricFile* myMetricOut, const MetricFile* corrAreaMetric = NULL); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmMetricRemoveIslands; } #endif //__ALGORITHM_METRIC_REMOVE_ISLANDS_H__ workbench-1.1.1/src/Algorithms/AlgorithmMetricResample.cxx000066400000000000000000000251121255417355300237160ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmMetricResample.h" #include "AlgorithmException.h" #include "CaretLogger.h" #include "MetricFile.h" #include "PaletteColorMapping.h" #include "SurfaceFile.h" #include "SurfaceResamplingHelper.h" using namespace caret; using namespace std; AString AlgorithmMetricResample::getCommandSwitch() { return "-metric-resample"; } AString AlgorithmMetricResample::getShortDescription() { return "RESAMPLE A METRIC FILE TO A DIFFERENT MESH"; } OperationParameters* AlgorithmMetricResample::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addMetricParameter(1, "metric-in", "the metric file to resample"); ret->addSurfaceParameter(2, "current-sphere", "a sphere surface with the mesh that the metric is currently on"); ret->addSurfaceParameter(3, "new-sphere", "a sphere surface that is in register with and has the desired output mesh"); ret->addStringParameter(4, "method", "the method name"); ret->addMetricOutputParameter(5, "metric-out", "the output metric"); OptionalParameter* areaSurfsOpt = ret->createOptionalParameter(6, "-area-surfs", "specify surfaces to do vertex area correction based on"); areaSurfsOpt->addSurfaceParameter(1, "current-area", "a relevant anatomical surface with mesh"); areaSurfsOpt->addSurfaceParameter(2, "new-area", "a relevant anatomical surface with mesh"); OptionalParameter* areaMetricsOpt = ret->createOptionalParameter(7, "-area-metrics", "specify vertex area metrics to do area correction based on"); areaMetricsOpt->addMetricParameter(1, "current-area", "a metric file with vertex areas for mesh"); areaMetricsOpt->addMetricParameter(2, "new-area", "a metric file with vertex areas for mesh"); OptionalParameter* roiOpt = ret->createOptionalParameter(8, "-current-roi", "use an input roi on the current mesh to exclude non-data vertices"); roiOpt->addMetricParameter(1, "roi-metric", "the roi, as a metric file"); OptionalParameter* validRoiOutOpt = ret->createOptionalParameter(9, "-valid-roi-out", "output the ROI of vertices that got data from valid source vertices"); validRoiOutOpt->addMetricOutputParameter(1, "roi-out", "the output roi as a metric"); ret->createOptionalParameter(10, "-largest", "use only the value of the vertex with the largest weight"); AString myHelpText = AString("Resamples a metric file, given two spherical surfaces that are in register. ") + "If the method does area correction, exactly one of -area-surfs or -area-metrics must be specified.\n\n" + "The -current-roi option only masks the input, the output may be slightly dilated in comparison, consider using -metric-mask on the output " + "when using -current-roi.\n\n" + "The -largest option results in nearest vertex behavior when used with BARYCENTRIC, instead of doing a weighted average, it uses the value " + "of the source vertex that has the largest weight for each target vertex. This is mainly intended for resampling ROI metrics.\n\n" + "The argument must be one of the following:\n\n"; vector allEnums; SurfaceResamplingMethodEnum::getAllEnums(allEnums); for (int i = 0; i < (int)allEnums.size(); ++i) { myHelpText += SurfaceResamplingMethodEnum::toName(allEnums[i]) + "\n"; } myHelpText += "\nThe ADAP_BARY_AREA method is recommended for ordinary metric data, because it should use all data while downsampling, unlike BARYCENTRIC."; ret->setHelpText(myHelpText); return ret; } void AlgorithmMetricResample::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { MetricFile* metricIn = myParams->getMetric(1); SurfaceFile* curSphere = myParams->getSurface(2); SurfaceFile* newSphere = myParams->getSurface(3); bool ok = false; SurfaceResamplingMethodEnum::Enum myMethod = SurfaceResamplingMethodEnum::fromName(myParams->getString(4), &ok); if (!ok) { throw AlgorithmException("invalid method name"); } MetricFile* metricOut = myParams->getOutputMetric(5); MetricFile* curAreas = NULL, *newAreas = NULL; MetricFile curAreasTemp, newAreasTemp; OptionalParameter* areaSurfsOpt = myParams->getOptionalParameter(6); if (areaSurfsOpt->m_present) { switch(myMethod) { case SurfaceResamplingMethodEnum::BARYCENTRIC: CaretLogInfo("This method does not use area correction, -area-surfs is not needed"); break; default: break; } vector nodeAreasTemp; SurfaceFile* curAreaSurf = areaSurfsOpt->getSurface(1); SurfaceFile* newAreaSurf = areaSurfsOpt->getSurface(2); curAreaSurf->computeNodeAreas(nodeAreasTemp); curAreasTemp.setNumberOfNodesAndColumns(curAreaSurf->getNumberOfNodes(), 1); curAreasTemp.setValuesForColumn(0, nodeAreasTemp.data()); curAreas = &curAreasTemp; newAreaSurf->computeNodeAreas(nodeAreasTemp); newAreasTemp.setNumberOfNodesAndColumns(newAreaSurf->getNumberOfNodes(), 1); newAreasTemp.setValuesForColumn(0, nodeAreasTemp.data()); newAreas = &newAreasTemp; } OptionalParameter* areaMetricsOpt = myParams->getOptionalParameter(7); if (areaMetricsOpt->m_present) { if (areaSurfsOpt->m_present) { throw AlgorithmException("only one of -area-surfs and -area-metrics can be specified"); } switch(myMethod) { case SurfaceResamplingMethodEnum::BARYCENTRIC: CaretLogInfo("This method does not use area correction, -area-metrics is not needed"); break; default: break; } curAreas = areaMetricsOpt->getMetric(1); newAreas = areaMetricsOpt->getMetric(2); } OptionalParameter* roiOpt = myParams->getOptionalParameter(8); MetricFile* currentRoi = NULL; if (roiOpt->m_present) { currentRoi = roiOpt->getMetric(1); } MetricFile* validRoiOut = NULL; OptionalParameter* validRoiOutOpt = myParams->getOptionalParameter(9); if (validRoiOutOpt->m_present) { validRoiOut = validRoiOutOpt->getOutputMetric(1); } bool largest = myParams->getOptionalParameter(10)->m_present; AlgorithmMetricResample(myProgObj, metricIn, curSphere, newSphere, myMethod, metricOut, curAreas, newAreas, currentRoi, validRoiOut, largest); } AlgorithmMetricResample::AlgorithmMetricResample(ProgressObject* myProgObj, const MetricFile* metricIn, const SurfaceFile* curSphere, const SurfaceFile* newSphere, const SurfaceResamplingMethodEnum::Enum& myMethod, MetricFile* metricOut, const MetricFile* curAreas, const MetricFile* newAreas, const MetricFile* currentRoi, MetricFile* validRoiOut, const bool& largest) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); if (metricIn->getNumberOfNodes() != curSphere->getNumberOfNodes()) throw AlgorithmException("input metric has different number of nodes than input sphere"); if (currentRoi != NULL && currentRoi->getNumberOfNodes() != curSphere->getNumberOfNodes()) throw AlgorithmException("roi metric has different number of nodes than input sphere"); const float* curAreaData = NULL, *newAreaData = NULL; switch (myMethod) { case SurfaceResamplingMethodEnum::BARYCENTRIC: break; default: if (curAreas == NULL || newAreas == NULL) throw AlgorithmException("specified method does area correction, but no vertex area data given"); if (curSphere->getNumberOfNodes() != curAreas->getNumberOfNodes()) throw AlgorithmException("current vertex area data has different number of nodes than current sphere"); if (newSphere->getNumberOfNodes() != newAreas->getNumberOfNodes()) throw AlgorithmException("new vertex area data has different number of nodes than new sphere"); curAreaData = curAreas->getValuePointerForColumn(0); newAreaData = newAreas->getValuePointerForColumn(0); } int numColumns = metricIn->getNumberOfColumns(), numNewNodes = newSphere->getNumberOfNodes(); metricOut->setNumberOfNodesAndColumns(numNewNodes, numColumns); metricOut->setStructure(newSphere->getStructure()); vector colScratch(numNewNodes, 0.0f); const float* roiCol = NULL; if (currentRoi != NULL) roiCol = currentRoi->getValuePointerForColumn(0); SurfaceResamplingHelper myHelp(myMethod, curSphere, newSphere, curAreaData, newAreaData, roiCol); if (validRoiOut != NULL) { validRoiOut->setNumberOfNodesAndColumns(numNewNodes, 1); validRoiOut->setStructure(newSphere->getStructure()); vector scratch(numNewNodes); myHelp.getResampleValidROI(scratch.data()); validRoiOut->setValuesForColumn(0, scratch.data()); } for (int i = 0; i < numColumns; ++i) { metricOut->setColumnName(i, metricIn->getColumnName(i)); *metricOut->getPaletteColorMapping(i) = *metricIn->getPaletteColorMapping(i); if (largest) { myHelp.resampleLargest(metricIn->getValuePointerForColumn(i), colScratch.data()); } else { myHelp.resampleNormal(metricIn->getValuePointerForColumn(i), colScratch.data()); } metricOut->setValuesForColumn(i, colScratch.data()); } } float AlgorithmMetricResample::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmMetricResample::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmMetricResample.h000066400000000000000000000040271255417355300233450ustar00rootroot00000000000000#ifndef __ALGORITHM_METRIC_RESAMPLE_H__ #define __ALGORITHM_METRIC_RESAMPLE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" #include "SurfaceResamplingMethodEnum.h" namespace caret { class AlgorithmMetricResample : public AbstractAlgorithm { AlgorithmMetricResample(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmMetricResample(ProgressObject* myProgObj, const MetricFile* metricIn, const SurfaceFile* curSphere, const SurfaceFile* newSphere, const SurfaceResamplingMethodEnum::Enum& myMethod, MetricFile* metricOut, const MetricFile* curAreas = NULL, const MetricFile* newAreas = NULL, const MetricFile* currentRoi = NULL, MetricFile* validRoiOut = NULL, const bool& largest = false); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmMetricResample; } #endif //__ALGORITHM_METRIC_RESAMPLE_H__ workbench-1.1.1/src/Algorithms/AlgorithmMetricSmoothing.cxx000066400000000000000000000273701255417355300241250ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmMetricSmoothing.h" #include "AlgorithmException.h" #include "CaretOMP.h" #include "CaretPointer.h" #include "GeodesicHelper.h" #include "MetricFile.h" #include "PaletteColorMapping.h" #include "SurfaceFile.h" #include "TopologyHelper.h" #include using namespace caret; using namespace std; AString AlgorithmMetricSmoothing::getCommandSwitch() { return "-metric-smoothing"; } AString AlgorithmMetricSmoothing::getShortDescription() { return "SMOOTH A METRIC FILE"; } OperationParameters* AlgorithmMetricSmoothing::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceParameter(1, "surface", "the surface to smooth on"); ret->addMetricParameter(2, "metric-in", "the metric to smooth"); ret->addDoubleParameter(3, "smoothing-kernel", "the sigma for the gaussian kernel function, in mm"); ret->addMetricOutputParameter(4, "metric-out", "the output metric"); OptionalParameter* roiOption = ret->createOptionalParameter(5, "-roi", "select a region of interest to smooth"); roiOption->addMetricParameter(1, "roi-metric", "the roi to smooth within, as a metric"); roiOption->createOptionalParameter(2, "-match-columns", "for each input column, use the corresponding column from the roi"); ret->createOptionalParameter(6, "-fix-zeros", "treat zero values as not being data"); OptionalParameter* columnSelect = ret->createOptionalParameter(7, "-column", "select a single column to smooth"); columnSelect->addStringParameter(1, "column", "the column number or name"); OptionalParameter* corrAreaOpt = ret->createOptionalParameter(8, "-corrected-areas", "vertex areas to use instead of computing them from the surface"); corrAreaOpt->addMetricParameter(1, "area-metric", "the corrected vertex areas, as a metric"); OptionalParameter* methodSelect = ret->createOptionalParameter(9, "-method", "select smoothing method, default GEO_GAUSS_AREA"); methodSelect->addStringParameter(1, "method", "the name of the smoothing method"); ret->setHelpText( AString("Smooth a metric file on a surface. ") + "By default, smooths all input columns on the entire surface, specify -column to use only one input column, and -roi to smooth only where " + "the roi metric is greater than 0, outputting zeros elsewhere.\n\n" + "When using -roi, input data outside the ROI is not used to compute the smoothed values. " + "By default, the first column of the roi metric is used for all input columns. " + "When -match-columns is specified to the -roi option, the input and roi metrics must have the same number of columns, " + "and for each input column's index, the same column index is used in the roi metric. " + "If the -match-columns option to -roi is used while the -column option is also used, the number of columns must match between the roi and input metric, " + "and it will use the roi column with the index of the selected input column.\n\n" + "The -fix-zeros option causes the smoothing to not use an input value if it is zero, but still write a smoothed value to the vertex. " + "This is useful for zeros that indicate lack of information, preventing them from pulling down the intensity of nearby vertices, while " + "giving the zero an extrapolated value.\n\n" + "The -corrected-areas option is intended for when it is unavoidable to smooth on a group average surface, it is only an approximate correction " + "for the reduction of structure in a group average surface. It is better to smooth the data on individuals before averaging, when feasible.\n\n" + "Valid values for are:\n\n" + "GEO_GAUSS_AREA - uses a geodesic gaussian kernel, and normalizes based on vertex area in order to work more reliably on irregular surfaces\n\n" + "GEO_GAUSS_EQUAL - uses a geodesic gaussian kernel, and normalizes assuming each vertex has equal importance\n\n" + "GEO_GAUSS - matches geodesic gaussian smoothing from caret5, but does not check kernels for having unequal importance\n\n" + "The GEO_GAUSS_AREA method is the default because it is usually the correct choice. " + "GEO_GAUSS_EQUAL may be the correct choice when the sum of vertex values is more meaningful then the surface integral (sum of values .* areas), " + "for instance when smoothing vertex areas (the sum is the total surface area, while the surface integral is the sum of squares of the vertex areas). " + "The GEO_GAUSS method is not recommended, it exists mainly to replicate methods of studies done with caret5's geodesic smoothing." ); return ret; } void AlgorithmMetricSmoothing::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { SurfaceFile* mySurf = myParams->getSurface(1); MetricFile* myMetric = myParams->getMetric(2); double myKernel = myParams->getDouble(3); MetricFile* myMetricOut = myParams->getOutputMetric(4); MetricFile* myRoi = NULL; bool matchRoiColumns = false; OptionalParameter* roiOption = myParams->getOptionalParameter(5); if (roiOption->m_present) { myRoi = roiOption->getMetric(1); matchRoiColumns = roiOption->getOptionalParameter(2)->m_present; } OptionalParameter* fixZerosOpt = myParams->getOptionalParameter(6); bool fixZeros = fixZerosOpt->m_present; int64_t columnNum = -1; OptionalParameter* columnSelect = myParams->getOptionalParameter(7); if (columnSelect->m_present) { columnNum = (int)myMetric->getMapIndexFromNameOrNumber(columnSelect->getString(1)); if (columnNum < 0) { throw AlgorithmException("invalid column specified"); } } MetricFile* corrAreaMetric = NULL; OptionalParameter* corrAreaOpt = myParams->getOptionalParameter(8); if (corrAreaOpt->m_present) { corrAreaMetric = corrAreaOpt->getMetric(1); } MetricSmoothingObject::Method myMethod = MetricSmoothingObject::GEO_GAUSS_AREA; OptionalParameter* methodSelect = myParams->getOptionalParameter(9); if (methodSelect->m_present) { AString methodName = methodSelect->getString(1); if (methodName == "GEO_GAUSS_AREA") { myMethod = MetricSmoothingObject::GEO_GAUSS_AREA; } else if (methodName == "GEO_GAUSS_EQUAL") { myMethod = MetricSmoothingObject::GEO_GAUSS_EQUAL; } else if (methodName == "GEO_GAUSS") { myMethod = MetricSmoothingObject::GEO_GAUSS; } else { throw AlgorithmException("unknown smoothing method name"); } } AlgorithmMetricSmoothing(myProgObj, mySurf, myMetric, myKernel, myMetricOut, myRoi, matchRoiColumns, fixZeros, columnNum, corrAreaMetric, myMethod); } AlgorithmMetricSmoothing::AlgorithmMetricSmoothing(ProgressObject* myProgObj, const SurfaceFile* mySurf, const MetricFile* myMetric, const double myKernel, MetricFile* myMetricOut, const MetricFile* myRoi, const bool matchRoiColumns, const bool fixZeros, const int64_t columnNum, const MetricFile* corrAreaMetric, const MetricSmoothingObject::Method myMethod) : AbstractAlgorithm(myProgObj) { float precomputeWeightWork = 5.0f;//TODO: adjust this based on number of columns to smooth, if we ever end up using progress indicators LevelProgress myProgress(myProgObj, 1.0f + precomputeWeightWork); int32_t numNodes = mySurf->getNumberOfNodes(); if (numNodes != myMetric->getNumberOfNodes()) { throw AlgorithmException("metric does not match surface in number of vertices"); } if (myRoi != NULL && myRoi->getNumberOfNodes() != numNodes) { throw AlgorithmException("roi metric does not match surface in number of vertices"); } int32_t numCols = myMetric->getNumberOfColumns(); if (columnNum < -1 || columnNum >= numCols) { throw AlgorithmException("invalid column number"); } if (myKernel <= 0.0) { throw AlgorithmException("invalid kernel size"); } if (myRoi != NULL && matchRoiColumns && numCols != myRoi->getNumberOfColumns()) { throw AlgorithmException("match roi columns specified, but roi metric has the wrong number of columns"); } CaretPointer mySmoothObj; const float* areaData = NULL; if (corrAreaMetric != NULL) { if (corrAreaMetric->getNumberOfNodes() != numNodes) { throw AlgorithmException("corrected vertex areas metric does not match surface in number of vertices"); } areaData = corrAreaMetric->getValuePointerForColumn(0); } myProgress.setTask("Precomputing Smoothing Weights"); if (matchRoiColumns) { mySmoothObj.grabNew(new MetricSmoothingObject(mySurf, myKernel, NULL, myMethod, areaData));//don't use an ROI to build weights when the ROI changes each time } else { mySmoothObj.grabNew(new MetricSmoothingObject(mySurf, myKernel, myRoi, myMethod, areaData)); } myProgress.reportProgress(precomputeWeightWork); if (columnNum == -1) { myMetricOut->setNumberOfNodesAndColumns(numNodes, myMetric->getNumberOfColumns()); myMetricOut->setStructure(mySurf->getStructure()); for (int32_t col = 0; col < numCols; ++col) { myProgress.setTask("Smoothing Column " + AString::number(col)); myMetricOut->setColumnName(col, myMetric->getColumnName(col) + ", smooth " + AString::number(myKernel)); *(myMetricOut->getPaletteColorMapping(col)) = *(myMetric->getPaletteColorMapping(col));//copy the palette settings if (myRoi != NULL && matchRoiColumns) { mySmoothObj->smoothColumn(myMetric, col, myMetricOut, col, myRoi, col, fixZeros); } else { mySmoothObj->smoothColumn(myMetric, col, myMetricOut, col, myRoi, 0, fixZeros); } myProgress.reportProgress(precomputeWeightWork + ((float)col + 1) / numCols); } } else { myMetricOut->setNumberOfNodesAndColumns(numNodes, 1); myMetricOut->setStructure(mySurf->getStructure()); myMetricOut->setColumnName(0, myMetric->getColumnName(columnNum) + ", smooth " + AString::number(myKernel)); *(myMetricOut->getPaletteColorMapping(0)) = *(myMetric->getPaletteColorMapping(columnNum));//copy the palette settings myProgress.setTask("Smoothing Column " + AString::number(columnNum)); if (myRoi != NULL && matchRoiColumns) { mySmoothObj->smoothColumn(myMetric, columnNum, myMetricOut, 0, myRoi, columnNum, fixZeros); } else { mySmoothObj->smoothColumn(myMetric, columnNum, myMetricOut, 0, myRoi, 0, fixZeros); } } } float AlgorithmMetricSmoothing::getAlgorithmInternalWeight() { return 1.0f; } float AlgorithmMetricSmoothing::getSubAlgorithmWeight() { return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmMetricSmoothing.h000066400000000000000000000041651255417355300235470ustar00rootroot00000000000000#ifndef __ALGORITHM_METRIC_SMOOTHING_H__ #define __ALGORITHM_METRIC_SMOOTHING_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" #include "MetricSmoothingObject.h" #include "stdint.h" #include namespace caret { class AlgorithmMetricSmoothing : public AbstractAlgorithm { public: private: AlgorithmMetricSmoothing(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmMetricSmoothing(ProgressObject* myProgObj, const SurfaceFile* mySurf, const MetricFile* myMetric, const double myKernel, MetricFile* myMetricOut, const MetricFile* myRoi = NULL, const bool matchRoiColumns = false, const bool fixZeros = false, const int64_t columnNum = -1, const MetricFile* corrAreaMetric = NULL, const MetricSmoothingObject::Method myMethod = MetricSmoothingObject::GEO_GAUSS_AREA); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmMetricSmoothing; } #endif //__ALGORITHM_METRIC_SMOOTHING_H__ workbench-1.1.1/src/Algorithms/AlgorithmMetricTFCE.cxx000066400000000000000000000440531255417355300226740ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmMetricTFCE.h" #include "AlgorithmException.h" #include "AlgorithmMetricSmoothing.h" #include "CaretAssert.h" #include "CaretHeap.h" #include "CaretOMP.h" #include "MetricFile.h" #include "SurfaceFile.h" #include "TopologyHelper.h" #include #include #include using namespace caret; using namespace std; AString AlgorithmMetricTFCE::getCommandSwitch() { return "-metric-tfce"; } AString AlgorithmMetricTFCE::getShortDescription() { return "DO TFCE ON A METRIC FILE"; } OperationParameters* AlgorithmMetricTFCE::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceParameter(1, "surface", "the surface to compute on"); ret->addMetricParameter(2, "metric-in", "the metric to run TFCE on"); ret->addMetricOutputParameter(3, "metric-out", "the output metric"); OptionalParameter* presmoothOpt = ret->createOptionalParameter(4, "-presmooth", "smooth the metric before running TFCE"); presmoothOpt->addDoubleParameter(1, "kernel", "the sigma for the gaussian smoothing kernel, in mm"); OptionalParameter* roiOpt = ret->createOptionalParameter(5, "-roi", "select a region of interest to run TFCE on"); roiOpt->addMetricParameter(1, "roi-metric", "the area to run TFCE on, as a metric"); OptionalParameter* paramsOpt = ret->createOptionalParameter(6, "-parameters", "set parameters for TFCE integral"); paramsOpt->addDoubleParameter(1, "E", "exponent for cluster area (default 1.0)"); paramsOpt->addDoubleParameter(2, "H", "exponent for threshold value (default 2.0)"); OptionalParameter* columnSelect = ret->createOptionalParameter(7, "-column", "select a single column"); columnSelect->addStringParameter(1, "column", "the column number or name"); OptionalParameter* corrAreaOpt = ret->createOptionalParameter(8, "-corrected-areas", "vertex areas to use instead of computing them from the surface"); corrAreaOpt->addMetricParameter(1, "area-metric", "the corrected vertex areas, as a metric"); ret->setHelpText( AString("Threshold-free cluster enhancement is a method to increase the relative value of regions that would form clusters in a standard thresholding test. ") + "This is accomplished by evaluating the integral of:\n\n" + "e(h, p)^E * h^H * dh\n\n" + "at each vertex p, where h ranges from 0 to the maximum value in the data, and e(h, p) is the extent of the cluster containing vertex p at threshold h. " + "Negative values are similarly enhanced by negating the data, running the same process, and negating the result.\n\n" + "When using -presmooth with -corrected-areas, note that it is an approximate correction within the smoothing algorithm (the TFCE correction is exact). " + "Doing smoothing on individual surfaces before averaging/TFCE is preferred, when possible, in order to better tie the smoothing kernel size to the original feature size.\n\n" + "The TFCE method is explained in: Smith SM, Nichols TE., \"Threshold-free cluster enhancement: addressing problems of smoothing, threshold dependence and localisation in cluster inference.\" Neuroimage. 2009 Jan 1;44(1):83-98. PMID: 18501637" ); return ret; } void AlgorithmMetricTFCE::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { SurfaceFile* mySurf = myParams->getSurface(1); MetricFile* myMetric = myParams->getMetric(2); MetricFile* myMetricOut = myParams->getOutputMetric(3); float presmooth = 0.0f; OptionalParameter* presmoothOpt = myParams->getOptionalParameter(4); if (presmoothOpt->m_present) { presmooth = (float)presmoothOpt->getDouble(1); if (presmooth <= 0.0f) throw AlgorithmException("presmooth kernel size must be positive"); } MetricFile* myRoi = NULL; OptionalParameter* roiOpt = myParams->getOptionalParameter(5); if (roiOpt->m_present) { myRoi = roiOpt->getMetric(1); } float param_e = 1.0f, param_h = 2.0; OptionalParameter* paramsOpt = myParams->getOptionalParameter(6); if (paramsOpt->m_present) { param_e = (float)paramsOpt->getDouble(1); param_h = (float)paramsOpt->getDouble(2); } OptionalParameter* columnSelect = myParams->getOptionalParameter(7); int columnNum = -1; if (columnSelect->m_present) {//set up to use the single column columnNum = (int)myMetric->getMapIndexFromNameOrNumber(columnSelect->getString(1)); if (columnNum < 0) { throw AlgorithmException("invalid column specified"); } } MetricFile* corrAreaMetric = NULL; OptionalParameter* corrAreaOpt = myParams->getOptionalParameter(8); if (corrAreaOpt->m_present) { corrAreaMetric = corrAreaOpt->getMetric(1); } AlgorithmMetricTFCE(myProgObj, mySurf, myMetric, myMetricOut, presmooth, myRoi, param_e, param_h, columnNum, corrAreaMetric); } AlgorithmMetricTFCE::AlgorithmMetricTFCE(ProgressObject* myProgObj, const SurfaceFile* mySurf, const MetricFile* myMetric, MetricFile* myMetricOut, const float& presmooth, const MetricFile* myRoi, const float& param_e, const float& param_h, const int& columnNum, const MetricFile* corrAreaMetric) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); if (mySurf->getNumberOfNodes() != myMetric->getNumberOfNodes()) throw AlgorithmException("metric and surface have different number of vertices"); if (myRoi != NULL && mySurf->getNumberOfNodes() != myRoi->getNumberOfNodes()) throw AlgorithmException("roi metric and surface have different number of vertices"); if (corrAreaMetric != NULL && mySurf->getNumberOfNodes() != corrAreaMetric->getNumberOfNodes()) throw AlgorithmException("corrected area metric and surface have different number of vertices"); if (columnNum < -1 || columnNum >= myMetric->getNumberOfColumns()) throw AlgorithmException("invalid column specified"); const float* roiData = NULL, *areaData = NULL; vector surfAreaData; if (corrAreaMetric == NULL) { mySurf->computeNodeAreas(surfAreaData); areaData = surfAreaData.data(); } else { areaData = corrAreaMetric->getValuePointerForColumn(0); } if (myRoi != NULL) roiData = myRoi->getValuePointerForColumn(0); if (columnNum == -1) { const MetricFile* toUse = myMetric; MetricFile postSmooth; if (presmooth > 0.0f) { AlgorithmMetricSmoothing(NULL, mySurf, myMetric, presmooth, &postSmooth, myRoi, false, false, -1, corrAreaMetric); toUse = &postSmooth; } int numCols = myMetric->getNumberOfColumns(); myMetricOut->setNumberOfNodesAndColumns(mySurf->getNumberOfNodes(), numCols); myMetricOut->setStructure(mySurf->getStructure()); #pragma omp CARET_PAR { vector outcol(mySurf->getNumberOfNodes(), 0.0f); #pragma omp CARET_FOR for (int col = 0; col < numCols; ++col) { processColumn(mySurf, toUse->getValuePointerForColumn(col), outcol.data(), roiData, param_e, param_h, areaData); myMetricOut->setValuesForColumn(col, outcol.data()); myMetricOut->setMapName(col, myMetric->getMapName(col)); } } } else { const MetricFile* toUse = myMetric; int useCol = columnNum; MetricFile postSmooth; if (presmooth > 0.0f) { AlgorithmMetricSmoothing(NULL, mySurf, myMetric, presmooth, &postSmooth, myRoi, false, false, columnNum, corrAreaMetric); toUse = &postSmooth; useCol = 0; } myMetricOut->setNumberOfNodesAndColumns(mySurf->getNumberOfNodes(), 1); myMetricOut->setStructure(mySurf->getStructure()); vector outcol(mySurf->getNumberOfNodes(), 0.0f); processColumn(mySurf, toUse->getValuePointerForColumn(useCol), outcol.data(), roiData, param_e, param_h, areaData); myMetricOut->setValuesForColumn(0, outcol.data()); myMetricOut->setMapName(0, myMetric->getMapName(columnNum)); } } void AlgorithmMetricTFCE::processColumn(const SurfaceFile* mySurf, const float* colData, float* outData, const float* roiData, const float& param_e, const float& param_h, const float* areaData) { int numNodes = mySurf->getNumberOfNodes(); vector accum(numNodes, 0.0); CaretPointer myHelper = mySurf->getTopologyHelper(); tfce_pos(myHelper, colData, accum.data(), roiData, param_e, param_h, areaData); vector negData(numNodes); for (int i = 0; i < numNodes; ++i) { negData[i] = -colData[i]; } tfce_pos(myHelper, negData.data(), accum.data(), roiData, param_e, param_h, areaData);//negatives and positives don't overlap, so reuse the accum array for (int i = 0; i < numNodes; ++i) { if (roiData == NULL || roiData[i] > 0.0f) { if (colData[i] < 0.0f) { outData[i] = (float)-accum[i]; } else { outData[i] = (float)accum[i]; } } else { outData[i] = 0.0f; } } } namespace {//hidden namespace just to make sure things don't collide struct Cluster { double accumVal, totalArea; vector members; float lastVal; bool first; Cluster() { first = true; accumVal = 0.0; totalArea = 0.0; } void addMember(const int& node, const float& val, const float& area, const float& param_e, const float& param_h) { update(val, param_e, param_h); members.push_back(node); totalArea += area; } void update(const float& bottomVal, const float& param_e, const float& param_h) { if (first) { lastVal = bottomVal; first = false; } else { if (bottomVal != lastVal)//skip computing if there is no difference { CaretAssert(bottomVal < lastVal); double integrated_h = param_h + 1.0f;//integral(x^h) = (x^(h + 1))/(h + 1) + C double newSlice = pow(totalArea, (double)param_e) * (pow((double)lastVal, integrated_h) - pow((double)bottomVal, integrated_h)) / integrated_h; accumVal += newSlice; lastVal = bottomVal;//computing in double precision, with float for inputs, puts the smallest difference between values far greater than the instability of the computation } } } }; int allocCluster(vector& clusterList, set& deadClusters) { if (deadClusters.empty()) { clusterList.push_back(Cluster()); return (int)(clusterList.size() - 1); } else { set::iterator iter = deadClusters.begin(); int ret = *iter; deadClusters.erase(iter); clusterList[ret] = Cluster();//reinitialize return ret; } } } void AlgorithmMetricTFCE::tfce_pos(TopologyHelper* myHelper, const float* colData, double* accumData, const float* roiData, const float& param_e, const float& param_h, const float* areaData) { int numNodes = myHelper->getNumberOfNodes(); vector membership(numNodes, -1);//int is enough as long as numNodes is fine as an int, for obvious reasons vector clusterList; set deadClusters;//to allow reallocation without changing indices CaretSimpleMaxHeap nodeHeap; for (int i = 0; i < numNodes; ++i) { if ((roiData == NULL || roiData[i] > 0.0f) && colData[i] > 0.0f) { nodeHeap.push(i, colData[i]); } } while (!nodeHeap.isEmpty()) { float value; int node = nodeHeap.pop(&value); const vector& neighbors = myHelper->getNodeNeighbors(node); int numNeigh = (int)neighbors.size(); set touchingClusters; for (int i = 0; i < numNeigh; ++i) { if (membership[neighbors[i]] != -1) { touchingClusters.insert(membership[neighbors[i]]); } } int numTouching = (int)touchingClusters.size(); switch (numTouching) { case 0://make new cluster { int newCluster = allocCluster(clusterList, deadClusters); clusterList[newCluster].addMember(node, value, areaData[node], param_e, param_h); membership[node] = newCluster; break; } case 1://add to cluster { int whichCluster = *(touchingClusters.begin()); clusterList[whichCluster].addMember(node, value, areaData[node], param_e, param_h); membership[node] = whichCluster; accumData[node] -= clusterList[whichCluster].accumVal;//the accum value is the current amount less than the peak value that the edge of the cluster has (this node is on the edge) break;//so, when the cluster merges or reaches 0, we add the accum value to every member and zero the accum value of the merged cluster, and we get the correct value in the end (with far fewer flops than integrating everywhere) } default://merge all touching clusters { int mergedIndex = -1, biggestSize = 0;//find the biggest cluster (in number of members) and use as merged cluster, for optimization purposes for (set::iterator iter = touchingClusters.begin(); iter != touchingClusters.end(); ++iter) { if ((int)clusterList[*iter].members.size() > biggestSize) { mergedIndex = *iter; biggestSize = (int)clusterList[*iter].members.size(); } } CaretAssertVectorIndex(clusterList, mergedIndex); Cluster& mergedCluster = clusterList[mergedIndex]; mergedCluster.update(value, param_e, param_h);//recalculate to align cluster bottoms for (set::iterator iter = touchingClusters.begin(); iter != touchingClusters.end(); ++iter) { if (*iter != mergedIndex)//if we are the largest cluster, don't modify the per-vertex accum for members, so merges between small and large clusters are cheap { Cluster& thisCluster = clusterList[*iter]; thisCluster.update(value, param_e, param_h);//recalculate to align cluster bottoms int numMembers = (int)thisCluster.members.size(); double correctionVal = thisCluster.accumVal - mergedCluster.accumVal;//fix the accum values in the side cluster so we can add the merged cluster's accum to everything at the end for (int j = 0; j < numMembers; ++j)//add the correction value to every member so that we have the current integrated values correct { accumData[thisCluster.members[j]] += correctionVal;//apply the correction membership[thisCluster.members[j]] = mergedIndex;//change the membership lookup } mergedCluster.members.insert(mergedCluster.members.end(), thisCluster.members.begin(), thisCluster.members.end());//copy all members mergedCluster.totalArea += thisCluster.totalArea; deadClusters.insert(*iter);//kill it vector().swap(clusterList[*iter].members);//also try to deallocate member list } } mergedCluster.addMember(node, value, areaData[node], param_e, param_h);//will not trigger recomputation, we already recomputed at this value accumData[node] -= mergedCluster.accumVal;//the vertex they merge on must not get the peak value of the cluster, obviously, so again, record its difference from peak membership[node] = mergedIndex; break;//NOTE: do not reset the accum value of the merged cluster, we specifically avoided modifying the per-vertex accum for its members, so the cluster accum is still in play } } } int listSize = (int)clusterList.size();//final cleanup of accum values for (int i = 0; i < listSize; ++i) { if (deadClusters.find(i) != deadClusters.end()) continue;//ignore clusters that don't exist Cluster& thisCluster = clusterList[i]; thisCluster.update(0.0f, param_e, param_h);//update to include the to-zero slice int numMembers = (int)thisCluster.members.size(); for (int j = 0; j < numMembers; ++j) { accumData[thisCluster.members[j]] += thisCluster.accumVal;//add the resulting slice to all members - their stored data contains the offset between the cluster peak and their corect value } } } float AlgorithmMetricTFCE::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmMetricTFCE::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmMetricTFCE.h000066400000000000000000000043651255417355300223230ustar00rootroot00000000000000#ifndef __ALGORITHM_METRIC_TFCE_H__ #define __ALGORITHM_METRIC_TFCE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class TopologyHelper; class AlgorithmMetricTFCE : public AbstractAlgorithm { AlgorithmMetricTFCE(); void processColumn(const SurfaceFile* mySurf, const float* colData, float* outData, const float* roiData, const float& param_e, const float& param_h, const float* areaData); void tfce_pos(TopologyHelper* myHelper, const float* colData, double* accumData, const float* roiData, const float& param_e, const float& param_h, const float* areaData); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmMetricTFCE(ProgressObject* myProgObj, const SurfaceFile* mySurf, const MetricFile* myMetric, MetricFile* myMetricOut, const float& presmooth = 0.0f, const MetricFile* myRoi = NULL, const float& param_e = 1.0f, const float& param_h = 2.0f, const int& columnNum = -1, const MetricFile* corrAreaMetric = NULL); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmMetricTFCE; } #endif //__ALGORITHM_METRIC_TFCE_H__ workbench-1.1.1/src/Algorithms/AlgorithmMetricToVolumeMapping.cxx000066400000000000000000000253201255417355300252350ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmMetricToVolumeMapping.h" #include "AlgorithmException.h" #include "CaretAssert.h" #include "CaretOMP.h" #include "CaretPointLocator.h" #include "MetricFile.h" #include "RibbonMappingHelper.h" #include "SurfaceFile.h" #include "VolumeFile.h" #include "VoxelIJK.h" #include using namespace caret; using namespace std; AString AlgorithmMetricToVolumeMapping::getCommandSwitch() { return "-metric-to-volume-mapping"; } AString AlgorithmMetricToVolumeMapping::getShortDescription() { return "MAP METRIC FILE TO VOLUME"; } OperationParameters* AlgorithmMetricToVolumeMapping::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addMetricParameter(1, "metric", "the input metric file"); ret->addSurfaceParameter(2, "surface", "the surface to use coordinates from"); ret->addVolumeParameter(3, "volume-space", "a volume file in the desired output volume space"); ret->addVolumeOutputParameter(4, "volume-out", "the output volume file"); OptionalParameter* nearestVertOpt = ret->createOptionalParameter(5, "-nearest-vertex", "use the value from the vertex closest to the voxel center"); nearestVertOpt->addDoubleParameter(1, "distance", "how far from the surface to map values to voxels, in mm"); OptionalParameter* ribbonOpt = ret->createOptionalParameter(6, "-ribbon-constrained", "use ribbon constrained mapping algorithm"); ribbonOpt->addSurfaceParameter(1, "inner-surf", "the inner surface of the ribbon"); ribbonOpt->addSurfaceParameter(2, "outer-surf", "the outer surface of the ribbon"); OptionalParameter* ribbonSubdivOpt = ribbonOpt->createOptionalParameter(3, "-voxel-subdiv", "voxel divisions while estimating voxel weights"); ribbonSubdivOpt->addIntegerParameter(1, "subdiv-num", "number of subdivisions, default 3"); ret->setHelpText( AString("Maps values from a metric file into a volume file. ") + "You must specify exactly one mapping method option. " + "The -nearest-vertex method uses the value from the vertex closest to the voxel center (useful for integer values). " + "The -ribbon-constrained method uses the same method as in -volume-to-surface-mapping, then uses the weights in reverse. " ); return ret; } void AlgorithmMetricToVolumeMapping::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { MetricFile* myMetric = myParams->getMetric(1); SurfaceFile* mySurf = myParams->getSurface(2); VolumeFile* myTemplateVol = myParams->getVolume(3); VolumeFile* myVolOut = myParams->getOutputVolume(4); enum Method { INVALID, NEAREST, RIBBON }; bool haveMethod = false; Method myMethod = INVALID; float nearDist = -1.0f; OptionalParameter* nearestVertOpt = myParams->getOptionalParameter(5); if (nearestVertOpt->m_present) { myMethod = NEAREST; haveMethod = true; nearDist = (float)nearestVertOpt->getDouble(1); if (nearDist < 0.0f) { throw AlgorithmException("invalid distance specified"); } } SurfaceFile* innerSurf = NULL, *outerSurf = NULL; int subDivs = 3; OptionalParameter* ribbonOpt = myParams->getOptionalParameter(6); if (ribbonOpt->m_present) { if (haveMethod) { throw AlgorithmException("more than one mapping method specified"); } myMethod = RIBBON; haveMethod = true; innerSurf = ribbonOpt->getSurface(1); outerSurf = ribbonOpt->getSurface(2); OptionalParameter* ribbonSubdivOpt = ribbonOpt->getOptionalParameter(3); if (ribbonSubdivOpt->m_present) { subDivs = (int)ribbonSubdivOpt->getInteger(1); if (subDivs < 1) { throw AlgorithmException("invalid number of subdivisions specified"); } } } if (!haveMethod) { throw AlgorithmException("no mapping method specified"); } switch (myMethod) { case NEAREST: AlgorithmMetricToVolumeMapping(myProgObj, myMetric, mySurf, myTemplateVol->getVolumeSpace(), myVolOut, nearDist); break; case RIBBON: AlgorithmMetricToVolumeMapping(myProgObj, myMetric, mySurf, myTemplateVol->getVolumeSpace(), myVolOut, innerSurf, outerSurf, subDivs); break; case INVALID: CaretAssert(0); throw AlgorithmException("internal error, tell the developers what you just tried to do"); } } AlgorithmMetricToVolumeMapping::AlgorithmMetricToVolumeMapping(ProgressObject* myProgObj, const MetricFile* myMetric, const SurfaceFile* mySurf, const VolumeSpace& myVolSpace, VolumeFile* myVolOut, const float& nearDist) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); if (myMetric->getNumberOfNodes() != mySurf->getNumberOfNodes()) { throw AlgorithmException("input surface and metric have different number of vertices"); } if (nearDist < 0.0f) { throw AlgorithmException("invalid distance specified in surface to volume mapping"); } checkStructureMatch(myMetric, mySurf->getStructure(), "input metric file", "the surface has"); int numCols = myMetric->getNumberOfColumns(); myVolOut->reinitialize(myVolSpace, numCols); const int64_t* dims = myVolSpace.getDims(); const int64_t frameSize = dims[0] * dims[1] * dims[2]; vector voxelToVertex(frameSize); CaretPointer myLocator = mySurf->getPointLocator(); #pragma omp CARET_PARFOR schedule(dynamic) for (int64_t k = 0; k < dims[2]; ++k) { for (int64_t j = 0; j < dims[1]; ++j) { for (int64_t i = 0; i < dims[0]; ++i) { float voxelCoord[3]; myVolSpace.indexToSpace(i, j, k, voxelCoord); voxelToVertex[myVolSpace.getIndex(i, j, k)] = myLocator->closestPointLimited(voxelCoord, nearDist); } } } vector scratchFrame(frameSize, 0.0f); for (int i = 0; i < numCols; ++i) { const float* metricData = myMetric->getValuePointerForColumn(i); for (int64_t v = 0; v < frameSize; ++v) { if (voxelToVertex[v] >= 0) { scratchFrame[v] = metricData[voxelToVertex[v]]; } } myVolOut->setFrame(scratchFrame.data(), i); myVolOut->setMapName(i, myMetric->getMapName(i)); } } AlgorithmMetricToVolumeMapping::AlgorithmMetricToVolumeMapping(ProgressObject* myProgObj, const MetricFile* myMetric, const SurfaceFile* mySurf, const VolumeSpace& myVolSpace, VolumeFile* myVolOut, const SurfaceFile* innerSurf, const SurfaceFile* outerSurf, const int& subDivs) : AbstractAlgorithm(myProgObj) { int numNodes = mySurf->getNumberOfNodes(); if (myMetric->getNumberOfNodes() != numNodes) { throw AlgorithmException("metric and input surfaces have different number of vertices"); } if (!mySurf->hasNodeCorrespondence(*outerSurf) || !mySurf->hasNodeCorrespondence(*innerSurf)) { throw AlgorithmException("all surfaces must have vertex correspondence"); } if (subDivs < 0.0f) { throw AlgorithmException("invalid number of subdivisions specified in surface to volume mapping"); } checkStructureMatch(myMetric, mySurf->getStructure(), "input metric file", "the surface has"); checkStructureMatch(innerSurf, myMetric->getStructure(), "inner surface file", "the metric file has"); checkStructureMatch(outerSurf, myMetric->getStructure(), "outer surface file", "the metric file has"); int numCols = myMetric->getNumberOfColumns(); myVolOut->reinitialize(myVolSpace, numCols); vector > forwardWeights; RibbonMappingHelper::computeWeightsRibbon(forwardWeights, myVolSpace, innerSurf, outerSurf, NULL, subDivs); map > > reverseWeights; for (int i = 0; i < numNodes; ++i) { for (int v = 0; v < (int)forwardWeights[i].size(); ++v) { map > >::iterator iter = reverseWeights.find(VoxelIJK(forwardWeights[i][v].ijk)); if (iter == reverseWeights.end()) { reverseWeights[VoxelIJK(forwardWeights[i][v].ijk)] = vector >(1, pair(i, forwardWeights[i][v].weight)); } else { iter->second.push_back(pair(i, forwardWeights[i][v].weight)); } } } const int64_t* dims = myVolSpace.getDims(); const int64_t frameSize = dims[0] * dims[1] * dims[2]; vector scratchFrame(frameSize, 0.0f); for (int m = 0; m < numCols; ++m) { const float* colData = myMetric->getValuePointerForColumn(m); for (map > >::const_iterator iter = reverseWeights.begin(); iter != reverseWeights.end(); ++iter) { double accum = 0.0, totalWeight = 0.0; for (int i = 0; i < (int)iter->second.size(); ++i) { totalWeight += iter->second[i].second; accum += colData[iter->second[i].first] * iter->second[i].second; } CaretAssert(totalWeight > 0.0);//ribbon mapping should never add weights of 0 to lists scratchFrame[myVolSpace.getIndex(iter->first.m_ijk)] = accum / totalWeight; } myVolOut->setFrame(scratchFrame.data(), m); myVolOut->setMapName(m, myMetric->getMapName(m)); } } float AlgorithmMetricToVolumeMapping::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmMetricToVolumeMapping::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmMetricToVolumeMapping.h000066400000000000000000000043121255417355300246600ustar00rootroot00000000000000#ifndef __ALGORITHM_METRIC_TO_VOLUME_MAPPING_H__ #define __ALGORITHM_METRIC_TO_VOLUME_MAPPING_H__ /*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class VolumeSpace; class AlgorithmMetricToVolumeMapping : public AbstractAlgorithm { AlgorithmMetricToVolumeMapping(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: ///nearest vertex AlgorithmMetricToVolumeMapping(ProgressObject* myProgObj, const MetricFile* myMetric, const SurfaceFile* mySurf, const VolumeSpace& myVolSpace, VolumeFile* myVolOut, const float& nearDist); ///ribbon constrained AlgorithmMetricToVolumeMapping(ProgressObject* myProgObj, const MetricFile* myMetric, const SurfaceFile* mySurf, const VolumeSpace& myVolSpace, VolumeFile* myVolOut, const SurfaceFile* innerSurf, const SurfaceFile* outerSurf, const int& subDivs = 3); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmMetricToVolumeMapping; } #endif //__ALGORITHM_METRIC_TO_VOLUME_MAPPING_H__ workbench-1.1.1/src/Algorithms/AlgorithmMetricVectorOperation.cxx000066400000000000000000000203041255417355300252670ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmMetricVectorOperation.h" #include "AlgorithmException.h" #include "CaretLogger.h" #include "MetricFile.h" #include "Vector3D.h" using namespace caret; using namespace std; AString AlgorithmMetricVectorOperation::getCommandSwitch() { return "-metric-vector-operation"; } AString AlgorithmMetricVectorOperation::getShortDescription() { return "DO A VECTOR OPERATION ON METRIC FILES"; } OperationParameters* AlgorithmMetricVectorOperation::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addMetricParameter(1, "vectors-a", "first vector input file"); ret->addMetricParameter(2, "vectors-b", "second vector input file"); ret->addStringParameter(3, "operation", "what vector operation to do"); ret->addMetricOutputParameter(4, "metric-out", "the output file"); ret->createOptionalParameter(5, "-normalize-a", "normalize vectors of first input"); ret->createOptionalParameter(6, "-normalize-b", "normalize vectors of second input"); ret->createOptionalParameter(7, "-normalize-output", "normalize output vectors (not valid for dot product)"); ret->createOptionalParameter(8, "-magnitude", "output the magnitude of the result (not valid for dot product)"); AString myText = AString("Does a vector operation on two metric files (that must have a multiple of 3 columns). ") + "Either of the inputs may have multiple vectors (more than 3 columns), but not both (at least one must have exactly 3 columns). " + "The -magnitude and -normalize-output options may not be specified together, or with an operation that returns a scalar (dot product). " + "The parameter must be one of the following:\n"; vector opList = VectorOperation::getAllOperations(); for (int i = 0; i < (int)opList.size(); ++i) { myText += "\n" + VectorOperation::operationToString(opList[i]); } ret->setHelpText(myText); return ret; } void AlgorithmMetricVectorOperation::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { MetricFile* metricA = myParams->getMetric(1); MetricFile* metricB = myParams->getMetric(2); AString operString = myParams->getString(3); bool ok = false; VectorOperation::Operation myOper = VectorOperation::stringToOperation(operString, ok); if (!ok) throw AlgorithmException("unrecognized operation string: " + operString); MetricFile* myMetricOut = myParams->getOutputMetric(4); bool normA = myParams->getOptionalParameter(5)->m_present; bool normB = myParams->getOptionalParameter(6)->m_present; bool normOut = myParams->getOptionalParameter(7)->m_present; bool magOut = myParams->getOptionalParameter(8)->m_present; AlgorithmMetricVectorOperation(myProgObj, metricA, metricB, myOper, myMetricOut, normA, normB, normOut, magOut); } AlgorithmMetricVectorOperation::AlgorithmMetricVectorOperation(ProgressObject* myProgObj, const MetricFile* metricA, const MetricFile* metricB, const VectorOperation::Operation& myOper, MetricFile* myMetricOut, const bool& normA, const bool& normB, const bool& normOut, const bool& magOut) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); StructureEnum::Enum checkStruct = metricA->getStructure(); int numNodes = metricA->getNumberOfNodes(); if (numNodes != metricB->getNumberOfNodes()) throw AlgorithmException("inputs have different numbers of nodes"); int numColA = metricA->getNumberOfColumns(), numColB = metricB->getNumberOfColumns(); if (numColA % 3 != 0) throw AlgorithmException("number of columns of first input is not a multiple of 3"); if (numColB % 3 != 0) throw AlgorithmException("number of columns of second input is not a multiple of 3"); int numVecA = numColA / 3, numVecB = numColB / 3; if (numVecA > 1 && numVecB > 1) throw AlgorithmException("both inputs have more than 3 columns (more than 1 vector)"); if (normOut && magOut) throw AlgorithmException("normalizing the output and taking the magnitude is meaningless"); bool opScalarResult = VectorOperation::operationReturnsScalar(myOper); if (opScalarResult && (normOut || magOut)) throw AlgorithmException("cannot normalize or take magnitude of a scalar result (such as a dot product)"); if (checkStruct == StructureEnum::INVALID) { CaretLogWarning("first input vector file has INVALID structure"); } else { checkStructureMatch(metricB, checkStruct, "second input vector file", "the first has"); } bool swapped = false; const MetricFile* multiVec = metricA, *singleVec = metricB; int numOutVecs = numVecA; if (numVecB > 1) { multiVec = metricB; singleVec = metricA; numOutVecs = numVecB; swapped = true; } int numColsOut = numOutVecs * 3; if (opScalarResult || magOut) numColsOut = numOutVecs; myMetricOut->setNumberOfNodesAndColumns(numNodes, numColsOut); myMetricOut->setStructure(checkStruct); vector outCols[3];//let the scalar result case overallocate outCols[0].resize(numNodes); outCols[1].resize(numNodes); outCols[2].resize(numNodes); const float* xColSingle = singleVec->getValuePointerForColumn(0); const float* yColSingle = singleVec->getValuePointerForColumn(1); const float* zColSingle = singleVec->getValuePointerForColumn(2); for (int v = 0; v < numOutVecs; ++v) { const float* xColMulti = multiVec->getValuePointerForColumn(v * 3); const float* yColMulti = multiVec->getValuePointerForColumn(v * 3 + 1); const float* zColMulti = multiVec->getValuePointerForColumn(v * 3 + 2); for (int i = 0; i < numNodes; ++i) { Vector3D vecA(xColMulti[i], yColMulti[i], zColMulti[i]); Vector3D vecB(xColSingle[i], yColSingle[i], zColSingle[i]); if (swapped) { Vector3D tempVec = vecA; vecA = vecB; vecB = tempVec; } if (normA) vecA = vecA.normal(); if (normB) vecB = vecB.normal(); if (opScalarResult) { outCols[0][i] = VectorOperation::doScalarOperation(vecA, vecB, myOper); } else { Vector3D tempVec = VectorOperation::doVectorOperation(vecA, vecB, myOper); if (normOut) tempVec = tempVec.normal(); if (magOut) { outCols[0][i] = tempVec.length(); } else { outCols[0][i] = tempVec[0]; outCols[1][i] = tempVec[1]; outCols[2][i] = tempVec[2]; } } } if (opScalarResult || magOut) { myMetricOut->setValuesForColumn(v, outCols[0].data()); } else { myMetricOut->setValuesForColumn(v * 3, outCols[0].data()); myMetricOut->setValuesForColumn(v * 3 + 1, outCols[1].data()); myMetricOut->setValuesForColumn(v * 3 + 2, outCols[2].data()); } } } float AlgorithmMetricVectorOperation::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmMetricVectorOperation::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmMetricVectorOperation.h000066400000000000000000000037141255417355300247220ustar00rootroot00000000000000#ifndef __ALGORITHM_METRIC_VECTOR_OPERATION_H__ #define __ALGORITHM_METRIC_VECTOR_OPERATION_H__ /*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" #include "VectorOperation.h" namespace caret { class AlgorithmMetricVectorOperation : public AbstractAlgorithm { AlgorithmMetricVectorOperation(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmMetricVectorOperation(ProgressObject* myProgObj, const MetricFile* metricA, const MetricFile* metricB, const VectorOperation::Operation& myOper, MetricFile* myMetricOut, const bool& normA = false, const bool& normB = false, const bool& normOut = false, const bool& magOut = false); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmMetricVectorOperation; } #endif //__ALGORITHM_METRIC_VECTOR_OPERATION_H__ workbench-1.1.1/src/Algorithms/AlgorithmMetricVectorTowardROI.cxx000066400000000000000000000133731255417355300251510ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmMetricVectorTowardROI.h" #include "AlgorithmException.h" #include "CaretOMP.h" #include "GeodesicHelper.h" #include "MetricFile.h" #include "SurfaceFile.h" #include "Vector3D.h" #include using namespace caret; using namespace std; AString AlgorithmMetricVectorTowardROI::getCommandSwitch() { return "-metric-vector-toward-roi"; } AString AlgorithmMetricVectorTowardROI::getShortDescription() { return "FIND IF VECTORS POINT TOWARD AN ROI"; } OperationParameters* AlgorithmMetricVectorTowardROI::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceParameter(1, "surface", "the surface to compute on"); ret->addMetricParameter(2, "target-roi", "the roi to find the shortest path to"); ret->addMetricOutputParameter(3, "metric-out", "the output metric"); OptionalParameter* roiOpt = ret->createOptionalParameter(4, "-roi", "don't compute for vertices outside an roi"); roiOpt->addMetricParameter(1, "roi-metric", "the region to compute inside, as a metric"); ret->setHelpText( AString("At each vertex, compute the vector along the start of the shortest path to the ROI.") ); return ret; } void AlgorithmMetricVectorTowardROI::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { SurfaceFile* mySurf = myParams->getSurface(1); MetricFile* targetRoi = myParams->getMetric(2); MetricFile* myMetricOut = myParams->getOutputMetric(3); MetricFile* computeRoi = NULL; OptionalParameter* roiOpt = myParams->getOptionalParameter(4); if (roiOpt->m_present) { computeRoi = roiOpt->getMetric(1); } AlgorithmMetricVectorTowardROI(myProgObj, mySurf, targetRoi, myMetricOut, computeRoi); } AlgorithmMetricVectorTowardROI::AlgorithmMetricVectorTowardROI(ProgressObject* myProgObj, SurfaceFile* mySurf, const MetricFile* targetRoi, MetricFile* myMetricOut, const MetricFile* computeRoi) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); int numNodes = mySurf->getNumberOfNodes(); if (targetRoi->getNumberOfNodes() != numNodes) throw AlgorithmException("target roi and surface have different number of vertices"); const float* computeRoiCol = NULL; if (computeRoi != NULL) { if (computeRoi->getNumberOfNodes() != numNodes) throw AlgorithmException("compute roi and surface have different number of vertices"); computeRoiCol = computeRoi->getValuePointerForColumn(0); } mySurf->computeNormals(); const float* myNormals = mySurf->getNormalData(); myMetricOut->setNumberOfNodesAndColumns(numNodes, 3); myMetricOut->setStructure(mySurf->getStructure()); vector roiConvert(numNodes, 0); const float* coordData = mySurf->getCoordinateData(); const float* targetData = targetRoi->getValuePointerForColumn(0); bool haveTarget = false; vector outCol[3]; outCol[0].resize(numNodes, 0.0f); outCol[1].resize(numNodes, 0.0f); outCol[2].resize(numNodes, 0.0f); for (int i = 0; i < numNodes; ++i) { if (targetData[i] > 0.0f) { roiConvert[i] = 1; haveTarget = true; } } if (!haveTarget) throw AlgorithmException("target roi is empty"); #pragma omp CARET_PAR { CaretPointer myGeoHelp = mySurf->getGeodesicHelper(); #pragma omp CARET_FOR schedule(dynamic) for (int i = 0; i < numNodes; ++i) { if (computeRoiCol == NULL || computeRoiCol[i] > 0.0f) { Vector3D normal = Vector3D(myNormals + i * 3).normal();//ensure unit length, just in case vector pathNodes; vector pathDists; myGeoHelp->getClosestNodeInRoi(i, roiConvert.data(), pathNodes, pathDists, true);//not using smooth will make noisier results, project via normal to avoid curvature problems if (pathNodes.size() < 2)//no path found, or node is inside the target roi { continue;//will already be zero } Vector3D pathVec = Vector3D(coordData + pathNodes[1] * 3) - Vector3D(coordData + i * 3); Vector3D pathProject = (pathVec - pathVec.dot(normal) * normal).normal();//the path vector is a direction only, just normalize after projection outCol[0][i] = pathProject[0]; outCol[1][i] = pathProject[1]; outCol[2][i] = pathProject[2]; } } } myMetricOut->setValuesForColumn(0, outCol[0].data()); myMetricOut->setValuesForColumn(1, outCol[1].data()); myMetricOut->setValuesForColumn(2, outCol[2].data()); } float AlgorithmMetricVectorTowardROI::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmMetricVectorTowardROI::getSubAlgorithmWeight() { return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmMetricVectorTowardROI.h000066400000000000000000000034711255417355300245740ustar00rootroot00000000000000#ifndef __ALGORITHM_METRIC_VECTOR_TOWARD_ROI_H__ #define __ALGORITHM_METRIC_VECTOR_TOWARD_ROI_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmMetricVectorTowardROI : public AbstractAlgorithm { AlgorithmMetricVectorTowardROI(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmMetricVectorTowardROI(ProgressObject* myProgObj, SurfaceFile* mySurf, const MetricFile* targetRoi, MetricFile* myMetricOut, const MetricFile* computeRoi = NULL); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmMetricVectorTowardROI; } #endif //__ALGORITHM_METRIC_VECTOR_TOWARD_ROI_H__ workbench-1.1.1/src/Algorithms/AlgorithmNodesInsideBorder.cxx000066400000000000000000001020231255417355300243410ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #include #include #include "AlgorithmNodesInsideBorder.h" #include "AlgorithmException.h" #include "Border.h" #include "BorderFile.h" #include "CaretAssert.h" #include "CaretOMP.h" #include "CiftiBrainordinateLabelFile.h" #include "CiftiBrainordinateScalarFile.h" #include "CiftiFile.h" #include "GeodesicHelper.h" #include "LabelFile.h" #include "MathFunctions.h" #include "MetricFile.h" #include "SurfaceFile.h" #include "SurfaceProjectedItem.h" #include "TopologyHelper.h" using namespace caret; using namespace std; AString AlgorithmNodesInsideBorder::getCommandSwitch() { return "-border-to-rois";//maybe the command should be in a separate file, and the nodes inside border code should be a helper class? } AString AlgorithmNodesInsideBorder::getShortDescription() { return "MAKE METRIC ROIS FROM BORDERS"; } OperationParameters* AlgorithmNodesInsideBorder::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceParameter(1, "surface", "the surface the borders are drawn on"); ret->addBorderParameter(2, "border-file", "the border file"); ret->addMetricOutputParameter(3, "metric-out", "the output metric file"); OptionalParameter* borderOpt = ret->createOptionalParameter(4, "-border", "create ROI for only one border"); borderOpt->addStringParameter(1, "name", "the name of the border"); ret->createOptionalParameter(5, "-inverse", "use inverse selection (outside border)"); ret->setHelpText( AString("By default, draws ROIs inside all borders in the border file, as separate metric columns.") ); return ret; } void AlgorithmNodesInsideBorder::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); SurfaceFile* mySurf = myParams->getSurface(1); BorderFile* myBorderFile = myParams->getBorder(2); MetricFile* myMetricOut = myParams->getOutputMetric(3); OptionalParameter* borderOpt = myParams->getOptionalParameter(4); bool inverse = myParams->getOptionalParameter(5)->m_present; int numNodes = mySurf->getNumberOfNodes(); if (mySurf->getStructure() == StructureEnum::INVALID) throw AlgorithmException("surface file needs a valid structure to find the correct borders in the file"); if (myBorderFile->getNumberOfNodes() > 0)//-1 number of nodes is the signal that this is an old-format, potentially multistructure border file { if (myBorderFile->getStructure() != mySurf->getStructure()) throw AlgorithmException("border file and surface file have different structures"); if (myBorderFile->getNumberOfNodes() != numNodes) throw AlgorithmException("border file and surface file have different number of vertices"); } if (borderOpt->m_present) { AString findName = borderOpt->getString(1); int numBorders = myBorderFile->getNumberOfBorders(); vector scratchCol(numNodes, (inverse ? 1.0f : 0.0f)); bool found = false; for (int i = 0; i < numBorders; ++i) { const Border* thisBorder = myBorderFile->getBorder(i); if (thisBorder->getName() == findName && thisBorder->getStructure() == mySurf->getStructure()) { found = true;//don't use inverse in this call, because we need to merge results, and having them inverted first would just make things more confusing vector insideNodes = findNodesInsideBorder(mySurf, thisBorder, false); for (int index = 0; index < (int)insideNodes.size(); ++index) { scratchCol[insideNodes[index]] = (inverse ? 0.0f : 1.0f); } } } if (!found) throw AlgorithmException("border name not found for surface structure"); myMetricOut->setNumberOfNodesAndColumns(mySurf->getNumberOfNodes(), 1); myMetricOut->setStructure(mySurf->getStructure()); myMetricOut->setColumnName(0, findName); myMetricOut->setValuesForColumn(0, scratchCol.data()); } else { int numBorderParts = myBorderFile->getNumberOfBorders(); map, int> borderCounter;//for grouping border parts with this structure into borders, with indexes into borderPartGroups vector > borderPartGroups;//stores the border parts of borders, in order of first part encountered in file for (int i = 0; i < numBorderParts; ++i) { const Border* thisBorder = myBorderFile->getBorder(i); if (thisBorder->getStructure() == mySurf->getStructure()) { map, int>::iterator iter = borderCounter.find(make_pair(thisBorder->getName(), thisBorder->getClassName())); if (iter == borderCounter.end()) { borderCounter[make_pair(thisBorder->getName(), thisBorder->getClassName())] = (int)borderPartGroups.size(); borderPartGroups.push_back(vector(1, i)); } else { borderPartGroups[iter->second].push_back(i); } } } int numBorders = (int)borderPartGroups.size(); if (numBorders == 0) throw AlgorithmException("no borders match the structure of the surface"); myMetricOut->setNumberOfNodesAndColumns(numNodes, numBorders); myMetricOut->setStructure(mySurf->getStructure()); #pragma omp CARET_PARFOR schedule(dynamic) for (int i = 0; i < numBorders; ++i)//parallel mainly because the method used to be inefficient (repeatedly uses geodesic to full surface) { myMetricOut->setColumnName(i, myBorderFile->getBorder(borderPartGroups[i][0])->getName());//should be safe to do in parallel vector scratchCol(numNodes, (inverse ? 1.0f : 0.0f)); int numParts = (int)borderPartGroups[i].size(); for (int j = 0; j < numParts; ++j) { const Border* thisBorder = myBorderFile->getBorder(borderPartGroups[i][j]); vector insideNodes = findNodesInsideBorder(mySurf, thisBorder, false); for (int index = 0; index < (int)insideNodes.size(); ++index) { scratchCol[insideNodes[index]] = (inverse ? 0.0f : 1.0f); } } myMetricOut->setValuesForColumn(i, scratchCol.data());//ditto } } } /** * \class caret::AlgorithmNodesInsideBorder * \brief Assign attribute values to nodes within a closed border. * * Identify nodes within a closed border and assign scalar or * label values to those nodes. * * Identifying nodes within a border can be troublesome for several reasons. * (1) Some borders are very narrow and opposite sides of a border may * be adjacent (like a figure eight) such that there are multiple closed * regions. (2) Borders may self intersect. (3) One end of a border may * overlay the other end of the border. (4) Combinations of 1, 2, and/or 3. * * The original implementation of this algorithm assumed the border was * oriented in a counter-clockwise orientation: * (1) Identified a node to the left (inside) the border and filled * the region using the border as a boundary. * (2) If the number of selected nodes was greater than half then number * of nodes in the surface, it indicated that the border must have been * a clockwise orientation and the node selection was inverted. * * Corrections were made for a couple of the problems listed above but * when combinations of the problems were present the algorithm failed. * * Since finding nodes inside a border was too challenging, it was realized * that finding nodes outside a border was much easier. So, the algorithm * was re-written to identify nodes outside the border and then the selection * is inverted. * (1) Find the center of gravity (average coordinate) of the border. * (2) Find the surface node furthest from the border's center of gravity. * (3) Perform a connected fill operation but stopping anytime a border * node is encountered. * (4) Invert the selection (verifying that the selected number of nodes * is greater than half the number of nodes in the surface). */ /** * Constructor. * * @param myProgObj * * @param surfaceFile * Surface file for nodes inside border. * @param border * Border for which nodes inside are found. * @param isInverseSelection * Invert the selection. * @param assignToMetricMapIndex * Map index in metric file to which assigments are made for * nodes inside the border. * @param assignMetricValue * Metric value assigned to nodes within the border. * @param metricFileInOut * Metric file that has map set with nodes inside border. */ AlgorithmNodesInsideBorder::AlgorithmNodesInsideBorder(ProgressObject* myProgObj, const SurfaceFile* surfaceFile, const Border* border, const bool isInverseSelection, const int32_t assignToMetricMapIndex, const float assignMetricValue, MetricFile* metricFileInOut) : AbstractAlgorithm(myProgObj) { CaretAssert(surfaceFile); CaretAssert(border); CaretAssert(metricFileInOut); if (surfaceFile->getNumberOfNodes() != metricFileInOut->getNumberOfNodes()) throw AlgorithmException("metric file must be initialized to same number of nodes");//a method that requires this really shouldn't be public std::vector nodesInsideBorder = findNodesInsideBorder(surfaceFile, border, isInverseSelection); const int32_t numberOfNodesInsideBorder = static_cast(nodesInsideBorder.size()); for (int32_t i = 0; i < numberOfNodesInsideBorder; i++) { const int32_t nodeNumber = nodesInsideBorder[i]; metricFileInOut->setValue(nodeNumber, assignToMetricMapIndex, assignMetricValue); } } /** * Constructor. * * @param myProgObj * * @param surfaceFile * Surface file for nodes inside border. * @param border * Border for which nodes inside are found. * @param isInverseSelection * Invert the selection. * @param assignToLabelMapIndex * Map index in label file to which assigments are made for * nodes inside the border. * @param assignLabelKey * Label key assigned to nodes within the border. * @param labelFileInOut * Label file that has map set with nodes inside border. */ AlgorithmNodesInsideBorder::AlgorithmNodesInsideBorder(ProgressObject* myProgObj, const SurfaceFile* surfaceFile, const Border* border, const bool isInverseSelection, const int32_t assignToLabelMapIndex, const int32_t assignLabelKey, LabelFile* labelFileInOut) : AbstractAlgorithm(myProgObj) { CaretAssert(surfaceFile); CaretAssert(border); CaretAssert(labelFileInOut); if (surfaceFile->getNumberOfNodes() != labelFileInOut->getNumberOfNodes()) throw AlgorithmException("metric file must be initialized to same number of nodes");//a method that requires this really shouldn't be public std::vector nodesInsideBorder = findNodesInsideBorder(surfaceFile, border, isInverseSelection); const int32_t numberOfNodesInsideBorder = static_cast(nodesInsideBorder.size()); for (int32_t i = 0; i < numberOfNodesInsideBorder; i++) { const int32_t nodeNumber = nodesInsideBorder[i]; labelFileInOut->setLabelKey(nodeNumber, assignToLabelMapIndex, assignLabelKey); } } /** * Constructor. * * @param myProgObj * * @param surfaceFile * Surface file for nodes inside border. * @param border * Border for which nodes inside are found. * @param isInverseSelection * Invert the selection. * @param assignToCiftiScalarMapIndex * Map index in cifti scalar file to which assigments are made for * nodes inside the border. * @param assignScalarValue * Scalar value assigned to nodes within the border. * @param ciftiScalarFileInOut * CIFTI scalar file that has map set with nodes inside border. */ AlgorithmNodesInsideBorder::AlgorithmNodesInsideBorder(ProgressObject* myProgObj, const SurfaceFile* surfaceFile, const Border* border, const bool isInverseSelection, const int32_t assignToCiftiScalarMapIndex, const float assignScalarValue, CiftiBrainordinateScalarFile* ciftiScalarFileInOut) : AbstractAlgorithm(myProgObj) { CaretAssert(surfaceFile); CaretAssert(border); CaretAssert(ciftiScalarFileInOut); std::vector nodesInsideBorder = findNodesInsideBorder(surfaceFile, border, isInverseSelection); std::vector surfaceMapData(surfaceFile->getNumberOfNodes(), 0.0); const int32_t numberOfNodesInsideBorder = static_cast(nodesInsideBorder.size()); for (int32_t i = 0; i < numberOfNodesInsideBorder; i++) { const int32_t nodeIndex = nodesInsideBorder[i]; CaretAssertVectorIndex(surfaceMapData, nodeIndex); surfaceMapData[nodeIndex] = assignScalarValue; } ciftiScalarFileInOut->setMapDataForSurface(assignToCiftiScalarMapIndex, surfaceFile->getStructure(), surfaceMapData); } /** * Constructor. * * @param myProgObj * * @param surfaceFile * Surface file for nodes inside border. * @param border * Border for which nodes inside are found. * @param isInverseSelection * Invert the selection. * @param assignToCiftiScalarMapIndex * Map index in cifti scalar file to which assigments are made for * nodes inside the border. * @param assignLabelKey * Label key assigned to nodes within the border. * @param ciftiLabelFileInOut * CIFTI label file that has map set with nodes inside border. */ AlgorithmNodesInsideBorder::AlgorithmNodesInsideBorder(ProgressObject* myProgObj, const SurfaceFile* surfaceFile, const Border* border, const bool isInverseSelection, const int32_t assignToCiftiLabelMapIndex, const int32_t assignLabelKey, CiftiBrainordinateLabelFile* ciftiLabelFileInOut) : AbstractAlgorithm(myProgObj) { CaretAssert(surfaceFile); CaretAssert(border); CaretAssert(ciftiLabelFileInOut); std::vector nodesInsideBorder = this->findNodesInsideBorder(surfaceFile, border, isInverseSelection); std::vector surfaceMapData(surfaceFile->getNumberOfNodes(), 0.0); const int32_t numberOfNodesInsideBorder = static_cast(nodesInsideBorder.size()); for (int32_t i = 0; i < numberOfNodesInsideBorder; i++) { const int32_t nodeIndex = nodesInsideBorder[i]; CaretAssertVectorIndex(surfaceMapData, nodeIndex); surfaceMapData[nodeIndex] = assignLabelKey; } ciftiLabelFileInOut->setMapDataForSurface(assignToCiftiLabelMapIndex, surfaceFile->getStructure(), surfaceMapData); } /** * Constructor for getting a vector containing indices of nodes inside * the border. * * @param myProgObj * * @param surfaceFile * Surface file for nodes inside border. * @param border * Border for which nodes inside are found. * @param isInverseSelection * Invert the selection. * @param assignToCiftiScalarMapIndex * Map index in cifti scalar file to which assigments are made for * nodes inside the border. * @param assignLabelKey * Label key assigned to nodes within the border. * @param ciftiLabelFileInOut * CIFTI label file that has map set with nodes inside border. */ AlgorithmNodesInsideBorder::AlgorithmNodesInsideBorder(ProgressObject* myProgObj, const SurfaceFile* surfaceFile, const Border* border, const bool isInverseSelection, std::vector& nodesInsideBorderOut) : AbstractAlgorithm(myProgObj) { CaretAssert(surfaceFile); CaretAssert(border); nodesInsideBorderOut = this->findNodesInsideBorder(surfaceFile, border, isInverseSelection); } std::vector AlgorithmNodesInsideBorder::findNodesInsideBorder(const SurfaceFile* mySurf, const Border* myBorder, const bool& inverse) { std::vector nodesInsideBorderOut; if (myBorder->getNumberOfPoints() == 0) return nodesInsideBorderOut;//we could throw, but whatever /* * Move border points to the nearest nodes. */ std::vector unconnectedNodesPath = moveBorderPointsToNearestNodes(mySurf, myBorder); /* * Convert the unconnected nodes path into a connected nodes path */ std::vector connectedNodesPath = createConnectedNodesPath(mySurf, myBorder, unconnectedNodesPath); int32_t numberOfNodesInConnectedPath = static_cast(connectedNodesPath.size()); if (numberOfNodesInConnectedPath < 1) { throw AlgorithmException("Connected path contains no vertices."); } /* * Find nodes that are OUTSIDE the connected path */ std::vector nodesOutsidePath = findNodesOutsideOfConnectedPath(mySurf, connectedNodesPath); /* * Identify nodes INSIDE the connected path */ const int32_t numberOfSurfaceNodes = mySurf->getNumberOfNodes(); std::vector nodeInsideRegionFlags(numberOfSurfaceNodes, true); const int32_t numberOfNodesOutsidePath = static_cast(nodesOutsidePath.size()); for (int32_t i = 0; i < numberOfNodesOutsidePath; i++) { const int32_t nodeIndex = nodesOutsidePath[i]; CaretAssertVectorIndex(nodeInsideRegionFlags, nodeIndex); nodeInsideRegionFlags[nodeIndex] = false; } /* * Handle inverse selection or possibility outside selection was * accidentally inside selection */ bool doInverseFlag = inverse; const int32_t halfNumberOfSurfaceNodes = numberOfSurfaceNodes / 2; if (numberOfNodesOutsidePath < halfNumberOfSurfaceNodes) { doInverseFlag = ( ! doInverseFlag); } const CaretPointer th = mySurf->getTopologyHelper(); /* * Identify nodes */ for (int32_t i = 0; i < numberOfSurfaceNodes; i++) { bool insideFlag = nodeInsideRegionFlags[i]; if (doInverseFlag) { insideFlag = ( ! insideFlag); } if (th->getNodeHasNeighbors(i)) { if (std::find(connectedNodesPath.begin(), connectedNodesPath.end(), i) != connectedNodesPath.end()) { insideFlag = false; } } else { insideFlag = false; } if (insideFlag) { nodesInsideBorderOut.push_back(i); } } return nodesInsideBorderOut; } std::vector AlgorithmNodesInsideBorder::moveBorderPointsToNearestNodes(const SurfaceFile* mySurf, const Border* myBorder) { std::vector nodeIndicesFollowingBorder; /* * Move border points to the nearest nodes. */ const int32_t originalNumberOfBorderPoints = myBorder->getNumberOfPoints(); for (int32_t i = 0; i < originalNumberOfBorderPoints; i++) { const SurfaceProjectedItem* spi = myBorder->getPoint(i); float xyz[3]; if (spi->getProjectedPosition(*mySurf, xyz, true)) { const int32_t nearestNode = mySurf->closestNode(xyz); if (nearestNode >= 0) { nodeIndicesFollowingBorder.push_back(nearestNode); } } } CaretAssert(myBorder->getNumberOfPoints() == (int)nodeIndicesFollowingBorder.size());//required for the connecting code to use following line segment logic return nodeIndicesFollowingBorder; } std::vector AlgorithmNodesInsideBorder::createConnectedNodesPath(const SurfaceFile* mySurf, const Border* myBorder, const std::vector& unconnectedNodesPath) { std::vector connectedNodesPathOut; /* * Geodesic helper for surface */ CaretPointer geodesicHelper = mySurf->getGeodesicHelper(); /* * Get the topology helper for the surface */ CaretPointer th = mySurf->getTopologyHelper(); /* * Find connected path along node neighbors */ const int32_t numberOfNodesInUnconnectedPath = static_cast(unconnectedNodesPath.size()); for (int32_t i = 0; i < numberOfNodesInUnconnectedPath; i++) { const int node = unconnectedNodesPath[i]; connectedNodesPathOut.push_back(node); int nextIndex = -1; const bool lastNodeFlag = (i >= (numberOfNodesInUnconnectedPath - 1)); if (lastNodeFlag) { nextIndex = 0; } else { nextIndex = i + 1; } int nextNode = unconnectedNodesPath[nextIndex]; /*if (node != nextNode) { bool doGeodesicSearch = true; const std::vector neighbors = th->getNodeNeighbors(node); if (std::find(neighbors.begin(), neighbors.end(), nextNode) != neighbors.end()) { connectedNodesPathOut.push_back(nextNode); doGeodesicSearch = false; } if (doGeodesicSearch) { std::vector distances; std::vector parentNodes; geodesicHelper->getGeoFromNode(node, distances, parentNodes, false); bool doneFlag = false; std::vector pathFromNextNodeToNode; int32_t pathNode = nextNode; while (doneFlag == false) { int32_t nextPathNode = parentNodes[pathNode]; if (nextPathNode >= 0) { if (nextPathNode == node) { doneFlag = true; } else { pathFromNextNodeToNode.push_back(nextPathNode); } } else { doneFlag = true; } if (doneFlag == false) { pathNode = nextPathNode; } } const int32_t numberOfPathNodes = static_cast(pathFromNextNodeToNode.size()); for (int32_t i = (numberOfPathNodes - 1); i >= 0; i--) { connectedNodesPathOut.push_back(pathFromNextNodeToNode[i]); } } }//*/ vector path; vector dists; Vector3D nodePos, nextPos; myBorder->getPoint(i)->getProjectedPosition(*mySurf, nodePos, true);//really means unproject myBorder->getPoint(nextIndex)->getProjectedPosition(*mySurf, nextPos, true); geodesicHelper->getPathAlongLineSegment(node, nextNode, nodePos, nextPos, path, dists); connectedNodesPathOut.insert(connectedNodesPathOut.end(), path.begin(), path.end()); } /* * Remove duplicates. */ cleanNodePath(connectedNodesPathOut); /* * Valid that the path nodes are connected. */ validateConnectedNodesPath(mySurf, connectedNodesPathOut); return connectedNodesPathOut; } /** * Find the nodes OUTSIDE the given connected path. * * @param connectedNodesPath * Connected path for which nodes inside are found. * @param nodesOutsidePathOut * Vector into which nodes OUTSIDE connected path are loaded. */ std::vector AlgorithmNodesInsideBorder::findNodesOutsideOfConnectedPath(const SurfaceFile* mySurf, const std::vector& connectedNodesPath) { std::vector nodesOutsidePathOut; /* * Track nodes that are found inside and/or have been visited. */ const int32_t numberOfNodes = mySurf->getNumberOfNodes(); std::vector nodeSearchStatus(numberOfNodes, 0); std::vector insideBorderFlag(numberOfNodes, false); /* * Mark all nodes in connected path as visited. */ const int32_t numberOfNodesInConnectedPath = static_cast(connectedNodesPath.size()); for (int32_t i = 0; i < numberOfNodesInConnectedPath; i++) { CaretAssertVectorIndex(nodeSearchStatus, connectedNodesPath[i]); nodeSearchStatus[connectedNodesPath[i]] = 1; } /* * Get the topology helper for the surface with neighbors sorted. */ CaretPointer th = mySurf->getTopologyHelper(true); /* * Find the node that is furthest from the connected path's * center of gravity (average coordinate) */ int32_t startNode = findNodeFurthestFromConnectedPathCenterOfGravity(mySurf, connectedNodesPath, nodeSearchStatus); if (startNode < 0) { throw AlgorithmException("Failed to find vertex that is not inside of the connected path."); } /* * Mark the starting node as 'inside'. */ CaretAssertVectorIndex(insideBorderFlag, startNode); insideBorderFlag[startNode] = true; /* * Use a stack to help with search. */ std::stack stack; stack.push(startNode); /* * Search until no more to search. */ while (stack.empty() == false) { const int32_t nodeNumber = stack.top(); stack.pop(); /* * Has node been visited? */ CaretAssertVectorIndex(nodeSearchStatus, nodeNumber); if (nodeSearchStatus[nodeNumber] == 0) { nodeSearchStatus[nodeNumber] = 1; /* * Set node as inside */ CaretAssertVectorIndex(insideBorderFlag, nodeNumber); insideBorderFlag[nodeNumber] = true; /* * Get neighbors of this node */ int numNeighbors = 0; const int* neighbors = th->getNodeNeighbors(nodeNumber, numNeighbors); /* * add neighbors to search */ for (int i = 0; i < numNeighbors; i++) { const int neighborNode = neighbors[i]; CaretAssertVectorIndex(nodeSearchStatus, neighborNode); if (nodeSearchStatus[neighborNode] != 1) { stack.push(neighborNode); } } } } /* * Return nodes inside the path */ int32_t insideCount = 0; for (int32_t i = 0; i < numberOfNodes; i++) { CaretAssertVectorIndex(insideBorderFlag, i); if (insideBorderFlag[i]) { nodesOutsidePathOut.push_back(i); insideCount++; } } return nodesOutsidePathOut; } /** * Find an unvisited node that is FURTHEST from the center-of-gravity of * the the nodes in the connected path. * * @param connectedNodesPath * Path of connected nodes. * @param nodeSearchStatus * Search status of the nodes. */ int32_t AlgorithmNodesInsideBorder::findNodeFurthestFromConnectedPathCenterOfGravity(const SurfaceFile* mySurf, const std::vector& connectedNodesPath, std::vector& nodeSearchStatus) { double sumX = 0.0; double sumY = 0.0; double sumZ = 0.0; double sumCount = 0.0; const int32_t numberOfNodesInConnectedPath = static_cast(connectedNodesPath.size()); for (int32_t i = 1; i < (numberOfNodesInConnectedPath - 1); i++) { const float* xyz = mySurf->getCoordinate(connectedNodesPath[i]); sumX += xyz[0]; sumY += xyz[1]; sumZ += xyz[2]; sumCount++; } if (sumCount >= 1.0) { const float cog[3] = { sumX / sumCount, sumY / sumCount, sumZ / sumCount }; /* * Get the topology helper for the surface with neighbors sorted. */ CaretPointer topologyHelper = mySurf->getTopologyHelper(true); float maxDist = -1.0; int32_t maxDistNodeIndex = -1; const int32_t numberOfNodes = mySurf->getNumberOfNodes(); for (int32_t i = 0; i < numberOfNodes; i++) { if (nodeSearchStatus[i] == 0) { if (topologyHelper->getNodeHasNeighbors(i)) { const float* coordXYZ = mySurf->getCoordinate(i); const float distSQ = MathFunctions::distanceSquared3D(cog, coordXYZ); if (distSQ > maxDist) { maxDist = distSQ; maxDistNodeIndex = i; } } } } return maxDistNodeIndex; } return -1; } /** * Clean the path by removing any consecutive nodes that are identical. * * @param nodePath * Path that is cleaned. */ void AlgorithmNodesInsideBorder::cleanNodePath(std::vector& nodePath) { std::vector path = nodePath; nodePath.clear(); /* * Unique copy will remove consecutive identical elements */ std::unique_copy(path.begin(), path.end(), back_inserter(nodePath)); } /** * Verify that the connect nodes path is fully connected. * * @param connectedNodesPath * Path that is validated. */ void AlgorithmNodesInsideBorder::validateConnectedNodesPath(const SurfaceFile* mySurf, const std::vector& connectedNodesPath) { /* * Get the topology helper for the surface with neighbors sorted. */ CaretPointer th = mySurf->getTopologyHelper(false); /* * Check the path to see that each pair of nodes are connected. */ const int32_t numberOfNodesInPath = static_cast(connectedNodesPath.size()); for (int32_t i = 0; i < numberOfNodesInPath; i++) { int numNeighbors; const int node = connectedNodesPath[i]; const int* neighbors = th->getNodeNeighbors(node, numNeighbors); int32_t nextNode = -1; if (i >= (numberOfNodesInPath - 1)) { nextNode = connectedNodesPath[0]; } else { nextNode = connectedNodesPath[i + 1]; } if (node != nextNode) { bool foundIt = false; for (int j = 0; j < numNeighbors; j++) { if (neighbors[j] == nextNode) { foundIt = true; break; } } if (foundIt == false) { throw AlgorithmException("Validation of vertex path along border failed. Vertex " + AString::number(node) + " should be connected to " + AString::number(nextNode) + " but it is not."); } } } } workbench-1.1.1/src/Algorithms/AlgorithmNodesInsideBorder.h000066400000000000000000000120311255417355300237650ustar00rootroot00000000000000#ifndef __ALGORITHM_NODES_INSIDE_BORDER__H_ #define __ALGORITHM_NODES_INSIDE_BORDER__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "AbstractAlgorithm.h" namespace caret { class Border; class BorderFile; class CiftiBrainordinateLabelFile; class CiftiBrainordinateScalarFile; class MetricFile; class LabelFile; class SurfaceFile; class TopologyHelper; class AlgorithmNodesInsideBorder : public AbstractAlgorithm { public: AlgorithmNodesInsideBorder(ProgressObject* myProgObj, const SurfaceFile* surfaceFile, const Border* border, const bool isInverseSelection, const int32_t assignToMetricMapIndex, const float assignMetricValue, MetricFile* metricFileInOut); AlgorithmNodesInsideBorder(ProgressObject* myProgObj, const SurfaceFile* surfaceFile, const Border* border, const bool isInverseSelection, const int32_t assignToCiftiScalarMapIndex, const float assignScalarValue, CiftiBrainordinateScalarFile* ciftiScalarFileInOut); AlgorithmNodesInsideBorder(ProgressObject* myProgObj, const SurfaceFile* surfaceFile, const Border* border, const bool isInverseSelection, const int32_t assignToCiftiLabelMapIndex, const int32_t assignLabelKey, CiftiBrainordinateLabelFile* ciftiLabelFileInOut); AlgorithmNodesInsideBorder(ProgressObject* myProgObj, const SurfaceFile* surfaceFile, const Border* border, const bool isInverseSelection, const int32_t assignToLabelMapIndex, const int32_t assignLabelKey, LabelFile* labelFileInOut); AlgorithmNodesInsideBorder(ProgressObject* myProgObj, const SurfaceFile* surfaceFile, const Border* border, const bool isInverseSelection, std::vector& nodesInsideBorderOut); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); static const BorderFile* getDebugBorderFile() { return NULL; } private: static std::vector findNodesInsideBorder(const SurfaceFile* mySurf, const Border* myBorder, const bool& inverse); static std::vector findNodesOutsideOfConnectedPath(const SurfaceFile* mySurf, const std::vector& connectedNodesPath); static int32_t findNodeFurthestFromConnectedPathCenterOfGravity(const SurfaceFile* mySurf, const std::vector& connectedNodesPath, std::vector& nodeSearchStatus); static std::vector createConnectedNodesPath(const SurfaceFile* mySurf, const Border* myBorder, const std::vector& unconnectedNodesPath); static void cleanNodePath(std::vector& nodePath); static std::vector moveBorderPointsToNearestNodes(const SurfaceFile* mySurf, const Border* myBorder); static void validateConnectedNodesPath(const SurfaceFile* mySurf, const std::vector& connectedNodesPath); }; typedef TemplateAutoOperation AutoAlgorithmNodesInsideBorder; } // namespace #endif //__ALGORITHM_NODES_INSIDE_BORDER__H_ workbench-1.1.1/src/Algorithms/AlgorithmSignedDistanceToSurface.cxx000066400000000000000000000121071255417355300255020ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmSignedDistanceToSurface.h" #include "AlgorithmException.h" #include "CaretOMP.h" #include "MetricFile.h" #include "SurfaceFile.h" using namespace caret; using namespace std; AString AlgorithmSignedDistanceToSurface::getCommandSwitch() { return "-signed-distance-to-surface"; } AString AlgorithmSignedDistanceToSurface::getShortDescription() { return "COMPUTE SIGNED DISTANCE FROM ONE SURFACE TO ANOTHER"; } OperationParameters* AlgorithmSignedDistanceToSurface::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceParameter(1, "surface-comp", "the comparison surface to measure the signed distance on"); ret->addSurfaceParameter(2, "surface-ref", "the reference surface that defines the signed distance function"); ret->addMetricOutputParameter(3, "metric", "the output metric"); OptionalParameter* windingMethodOpt = ret->createOptionalParameter(4, "-winding", "winding method for point inside surface test"); windingMethodOpt->addStringParameter(1, "method", "name of the method (default EVEN_ODD)"); ret->setHelpText( AString("Compute the signed distance function of the reference surface at every vertex on the comparison surface. ") + "NOTE: this relation is NOT symmetric, the line from a vertex to the closest point on the 'ref' surface " + "(the one that defines the signed distance function) will only align with the normal of the 'ref' surface. Valid specifiers for winding methods are as follows:\n\n" + "EVEN_ODD (default)\nNEGATIVE\nNONZERO\nNORMALS\n\nThe NORMALS method uses the normals of triangles and edges, or the closest triangle hit by a ray from the point. " + "This method may be slightly faster, but is only reliable for a closed surface that does not cross through itself. All other methods count entry (positive) and " + "exit (negative) crossings of a vertical ray from the point, then counts as inside if the total is odd, negative, or nonzero, respectively." ); return ret; } void AlgorithmSignedDistanceToSurface::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { SurfaceFile* testSurf = myParams->getSurface(1); SurfaceFile* levelSetSurf = myParams->getSurface(2); MetricFile* myMetricOut = myParams->getOutputMetric(3); SignedDistanceHelper::WindingLogic myWinding = SignedDistanceHelper::EVEN_ODD; OptionalParameter* windingMethodOpt = myParams->getOptionalParameter(4); if (windingMethodOpt->m_present) { AString methodName = windingMethodOpt->getString(1); if (methodName == "EVEN_ODD") { myWinding = SignedDistanceHelper::EVEN_ODD; } else if (methodName == "NEGATIVE") { myWinding = SignedDistanceHelper::NEGATIVE; } else if (methodName == "NONZERO") { myWinding = SignedDistanceHelper::NONZERO; } else if (methodName == "NORMALS") { myWinding = SignedDistanceHelper::NORMALS; } else { throw AlgorithmException("unrecognized winding method"); } } AlgorithmSignedDistanceToSurface(myProgObj, testSurf, levelSetSurf, myMetricOut, myWinding); } AlgorithmSignedDistanceToSurface::AlgorithmSignedDistanceToSurface(ProgressObject* myProgObj, const SurfaceFile* testSurf, const SurfaceFile* levelSetSurf, MetricFile* myMetricOut, SignedDistanceHelper::WindingLogic myWinding) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); int numNodes = testSurf->getNumberOfNodes(); myMetricOut->setNumberOfNodesAndColumns(numNodes, 1); myMetricOut->setStructure(testSurf->getStructure()); #pragma omp CARET_PAR { CaretPointer myHelp = levelSetSurf->getSignedDistanceHelper(); #pragma omp CARET_FOR schedule(dynamic) for (int i = 0; i < numNodes; ++i) { myMetricOut->setValue(i, 0, myHelp->dist(testSurf->getCoordinate(i), myWinding)); } } } float AlgorithmSignedDistanceToSurface::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmSignedDistanceToSurface::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmSignedDistanceToSurface.h000066400000000000000000000035721255417355300251350ustar00rootroot00000000000000#ifndef __ALGORITHM_SIGNED_DISTANCE_TO_SURFACE_H__ #define __ALGORITHM_SIGNED_DISTANCE_TO_SURFACE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" #include "SignedDistanceHelper.h" namespace caret { class AlgorithmSignedDistanceToSurface : public AbstractAlgorithm { AlgorithmSignedDistanceToSurface(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmSignedDistanceToSurface(ProgressObject* myProgObj, const SurfaceFile* testSurf, const SurfaceFile* levelSetSurf, MetricFile* myMetricOut, SignedDistanceHelper::WindingLogic myWinding = SignedDistanceHelper::EVEN_ODD); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmSignedDistanceToSurface; } #endif //__ALGORITHM_SIGNED_DISTANCE_TO_SURFACE_H__ workbench-1.1.1/src/Algorithms/AlgorithmSurfaceAffineRegression.cxx000066400000000000000000000107411255417355300255460ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmSurfaceAffineRegression.h" #include "AlgorithmException.h" #include "AffineFile.h" #include "SurfaceFile.h" //in order to do rref on double #include "MatrixFunctions.h" using namespace caret; using namespace std; AString AlgorithmSurfaceAffineRegression::getCommandSwitch() { return "-surface-affine-regression"; } AString AlgorithmSurfaceAffineRegression::getShortDescription() { return "REGRESS THE AFFINE TRANSFORM BETWEEN SURFACES ON THE SAME MESH"; } OperationParameters* AlgorithmSurfaceAffineRegression::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceParameter(1, "source", "the surface to warp"); ret->addSurfaceParameter(2, "target", "the surface to match the coordinates of"); ret->addStringParameter(3, "affine-out", "output - the output affine file"); ret->setHelpText( AString("Use linear regression to compute an affine that minimizes the sum of squares of the coordinate differences between the target surface and the warped source surface. ") + "Note that this has a bias to shrink the surface that is being warped. " + "The output is written as a NIFTI 'world' matrix, see -convert-affine to convert it for use in other software." ); return ret; } void AlgorithmSurfaceAffineRegression::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { SurfaceFile* sourceSurf = myParams->getSurface(1); SurfaceFile* targetSurf = myParams->getSurface(2); AString affineOutName = myParams->getString(3); FloatMatrix affineMatOut; AlgorithmSurfaceAffineRegression(myProgObj, sourceSurf, targetSurf, affineMatOut); AffineFile affineOut; affineOut.setMatrix(affineMatOut); affineOut.writeWorld(affineOutName); } AlgorithmSurfaceAffineRegression::AlgorithmSurfaceAffineRegression(ProgressObject* myProgObj, const SurfaceFile* sourceSurf, const SurfaceFile* targetSurf, FloatMatrix& affineMatOut) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); if (!targetSurf->hasNodeCorrespondence(*sourceSurf)) throw AlgorithmException("input surfaces must have vertex correspondence"); affineMatOut = FloatMatrix::identity(4); vector > indep(4, vector(4, 0.0)), dep(4, vector(3, 0.0)); int numNodes = targetSurf->getNumberOfNodes(); int coordSize = 3 * numNodes; const float* targetData = targetSurf->getCoordinateData(); const float* sourceData = sourceSurf->getCoordinateData(); for (int i = 0; i < coordSize; i += 3) { const float* targetCoord = targetData + i; const float* sourceCoord = sourceData + i; for (int j = 0; j < 3; ++j)//compute X' * X and X' * Y { for (int k = 0; k < 3; ++k) { indep[j][k] += sourceCoord[j] * sourceCoord[k]; dep[k][j] += targetCoord[j] * sourceCoord[k]; } indep[j][3] += sourceCoord[j]; indep[3][j] += sourceCoord[j]; dep[3][j] += targetCoord[j]; } indep[3][3] += 1.0; } vector > rrefMat; MatrixFunctions::horizCat(indep, dep, rrefMat); MatrixFunctions::rref(rrefMat); for (int i = 0; i < 3; ++i) { for (int j = 0; j < 4; ++j) { affineMatOut[i][j] = rrefMat[j][4 + i]; } } } float AlgorithmSurfaceAffineRegression::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmSurfaceAffineRegression::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmSurfaceAffineRegression.h000066400000000000000000000034411255417355300251720ustar00rootroot00000000000000#ifndef __ALGORITHM_SURFACE_AFFINE_REGRESSION_H__ #define __ALGORITHM_SURFACE_AFFINE_REGRESSION_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" #include "FloatMatrix.h" namespace caret { class AlgorithmSurfaceAffineRegression : public AbstractAlgorithm { AlgorithmSurfaceAffineRegression(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmSurfaceAffineRegression(ProgressObject* myProgObj, const SurfaceFile* sourceSurf, const SurfaceFile* targetSurf, FloatMatrix& affineMatOut); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmSurfaceAffineRegression; } #endif //__ALGORITHM_SURFACE_AFFINE_REGRESSION_H__ workbench-1.1.1/src/Algorithms/AlgorithmSurfaceApplyAffine.cxx000066400000000000000000000102451255417355300245120ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmSurfaceApplyAffine.h" #include "AlgorithmException.h" #include "AffineFile.h" #include "SurfaceFile.h" #include "Vector3D.h" using namespace caret; using namespace std; AString AlgorithmSurfaceApplyAffine::getCommandSwitch() { return "-surface-apply-affine"; } AString AlgorithmSurfaceApplyAffine::getShortDescription() { return "APPLY AFFINE TRANSFORM TO SURFACE FILE"; } OperationParameters* AlgorithmSurfaceApplyAffine::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceParameter(1, "in-surf", "the surface to transform"); ret->addStringParameter(2, "affine", "the affine file"); ret->addSurfaceOutputParameter(3, "out-surf", "the output transformed surface"); OptionalParameter* flirtOpt = ret->createOptionalParameter(4, "-flirt", "MUST be used if affine is a flirt affine"); flirtOpt->addStringParameter(1, "source-volume", "the source volume used when generating the affine"); flirtOpt->addStringParameter(2, "target-volume", "the target volume used when generating the affine"); ret->setHelpText( AString("For flirt matrices, you must use the -flirt option, because flirt matrices are not a complete description of the coordinate transform they represent. ") + "If the -flirt option is not present, the affine must be a nifti 'world' affine, which can be obtained with the -convert-affine command, or aff_conv from the 4dfp suite." ); return ret; } void AlgorithmSurfaceApplyAffine::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { SurfaceFile* mySurf = myParams->getSurface(1); AString affineName = myParams->getString(2); SurfaceFile* mySurfOut = myParams->getOutputSurface(3); OptionalParameter* flirtOpt = myParams->getOptionalParameter(4); AffineFile myAffine; if (flirtOpt->m_present) { myAffine.readFlirt(affineName, flirtOpt->getString(1), flirtOpt->getString(2)); } else { myAffine.readWorld(affineName); } AlgorithmSurfaceApplyAffine(myProgObj, mySurf, myAffine.getMatrix(), mySurfOut); } AlgorithmSurfaceApplyAffine::AlgorithmSurfaceApplyAffine(ProgressObject* myProgObj, const SurfaceFile* mySurf, const FloatMatrix& myMatrix, SurfaceFile* mySurfOut) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); *mySurfOut = *mySurf;//copy rather than initialize, don't currently have much in the way of modification functions Vector3D xvec, yvec, zvec, translate; myMatrix.getAffineVectors(xvec, yvec, zvec, translate); int numNodes = mySurf->getNumberOfNodes(); const float* coordData = mySurf->getCoordinateData(); vector coordsOut(numNodes * 3); for (int i = 0; i < numNodes; ++i) { int base = i * 3; Vector3D transformed = coordData[base] * xvec + coordData[base + 1] * yvec + coordData[base + 2] * zvec + translate; coordsOut[base] = transformed[0]; coordsOut[base + 1] = transformed[1]; coordsOut[base + 2] = transformed[2]; } mySurfOut->setCoordinates(coordsOut.data()); } float AlgorithmSurfaceApplyAffine::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmSurfaceApplyAffine::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmSurfaceApplyAffine.h000066400000000000000000000033601255417355300241370ustar00rootroot00000000000000#ifndef __ALGORITHM_SURFACE_APPLY_AFFINE_H__ #define __ALGORITHM_SURFACE_APPLY_AFFINE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" #include "FloatMatrix.h" namespace caret { class AlgorithmSurfaceApplyAffine : public AbstractAlgorithm { AlgorithmSurfaceApplyAffine(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmSurfaceApplyAffine(ProgressObject* myProgObj, const SurfaceFile* mySurf, const FloatMatrix& myMatrix, SurfaceFile* mySurfOut); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmSurfaceApplyAffine; } #endif //__ALGORITHM_SURFACE_APPLY_AFFINE_H__ workbench-1.1.1/src/Algorithms/AlgorithmSurfaceApplyWarpfield.cxx000066400000000000000000000111741255417355300252410ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmSurfaceApplyWarpfield.h" #include "AlgorithmException.h" #include "SurfaceFile.h" #include "Vector3D.h" #include "WarpfieldFile.h" using namespace caret; using namespace std; AString AlgorithmSurfaceApplyWarpfield::getCommandSwitch() { return "-surface-apply-warpfield"; } AString AlgorithmSurfaceApplyWarpfield::getShortDescription() { return "APPLY WARPFIELD TO SURFACE FILE"; } OperationParameters* AlgorithmSurfaceApplyWarpfield::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceParameter(1, "in-surf", "the surface to transform"); ret->addStringParameter(2, "warpfield", "the INVERSE warpfield"); ret->addSurfaceOutputParameter(3, "out-surf", "the output transformed surface"); OptionalParameter* fnirtOpt = ret->createOptionalParameter(4, "-fnirt", "MUST be used if using a fnirt warpfield"); fnirtOpt->addStringParameter(1, "forward-warp", "the forward warpfield"); ret->setHelpText( AString("NOTE: warping a surface requires the INVERSE of the warpfield used to warp the volume it lines up with. ") + "The header of the forward warp is needed by the -fnirt option in order to correctly interpret the displacements in the fnirt warpfield.\n\n" + "If the -fnirt option is not present, the warpfield must be a nifti 'world' warpfield, which can be obtained with the -convert-warpfield command." ); return ret; } void AlgorithmSurfaceApplyWarpfield::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { SurfaceFile* mySurf = myParams->getSurface(1); AString warpName = myParams->getString(2); SurfaceFile* mySurfOut = myParams->getOutputSurface(3); OptionalParameter* flirtOpt = myParams->getOptionalParameter(4); WarpfieldFile myWarp; if (flirtOpt->m_present) { myWarp.readFnirt(warpName, flirtOpt->getString(1)); } else { myWarp.readWorld(warpName); } AlgorithmSurfaceApplyWarpfield(myProgObj, mySurf, myWarp.getWarpfield(), mySurfOut); } AlgorithmSurfaceApplyWarpfield::AlgorithmSurfaceApplyWarpfield(ProgressObject* myProgObj, const SurfaceFile* mySurf, const VolumeFile* warpfield, SurfaceFile* mySurfOut) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); vector warpDims; warpfield->getDimensions(warpDims); if (warpDims[3] != 3 || warpDims[4] != 1) throw AlgorithmException("provided warpfield volume has wrong number of subvolumes or components"); *mySurfOut = *mySurf;//copy rather than initialize, don't currently have much in the way of modification functions int numNodes = mySurf->getNumberOfNodes(); const float* coordData = mySurf->getCoordinateData(); vector coordsOut(numNodes * 3); for (int i = 0; i < numNodes; ++i) { int base = i * 3; Vector3D sample = coordData + base; Vector3D displacement; bool valid = false; displacement[0] = warpfield->interpolateValue(sample, VolumeFile::TRILINEAR, &valid, 0); if (!valid) throw AlgorithmException("surface exceeds the bounding box of the warpfield"); displacement[1] = warpfield->interpolateValue(sample, VolumeFile::TRILINEAR, NULL, 1); displacement[2] = warpfield->interpolateValue(sample, VolumeFile::TRILINEAR, NULL, 2); Vector3D newCoord = sample + displacement; coordsOut[base] = newCoord[0]; coordsOut[base + 1] = newCoord[1]; coordsOut[base + 2] = newCoord[2]; } mySurfOut->setCoordinates(coordsOut.data()); } float AlgorithmSurfaceApplyWarpfield::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmSurfaceApplyWarpfield::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmSurfaceApplyWarpfield.h000066400000000000000000000033571255417355300246720ustar00rootroot00000000000000#ifndef __ALGORITHM_SURFACE_APPLY_WARPFIELD_H__ #define __ALGORITHM_SURFACE_APPLY_WARPFIELD_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmSurfaceApplyWarpfield : public AbstractAlgorithm { AlgorithmSurfaceApplyWarpfield(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmSurfaceApplyWarpfield(ProgressObject* myProgObj, const SurfaceFile* mySurf, const VolumeFile* warpfield, SurfaceFile* mySurfOut); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmSurfaceApplyWarpfield; } #endif //__ALGORITHM_SURFACE_APPLY_WARPFIELD_H__ workbench-1.1.1/src/Algorithms/AlgorithmSurfaceAverage.cxx000066400000000000000000000152051255417355300236670ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmSurfaceAverage.h" #include "AlgorithmException.h" #include "MetricFile.h" #include "SurfaceFile.h" #include "Vector3D.h" #include using namespace caret; using namespace std; AString AlgorithmSurfaceAverage::getCommandSwitch() { return "-surface-average"; } AString AlgorithmSurfaceAverage::getShortDescription() { return "AVERAGE SURFACE FILES TOGETHER"; } OperationParameters* AlgorithmSurfaceAverage::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceOutputParameter(1, "surface-out", "the output averaged surface"); ParameterComponent* surfOpt = ret->createRepeatableParameter(2, "-surf", "specify a surface to include in the average"); surfOpt->addSurfaceParameter(1, "surface", "a surface file to average"); OptionalParameter* stdevOpt = ret->createOptionalParameter(3, "-stddev", "compute 3D sample standard deviation"); stdevOpt->addMetricOutputParameter(1, "stddev-metric-out", "the output metric for 3D sample standard deviation"); OptionalParameter* uncertaintyOpt = ret->createOptionalParameter(4, "-uncertainty", "compute caret5 'uncertainty'"); uncertaintyOpt->addMetricOutputParameter(1, "uncert-metric-out", "the output metric for uncertainty"); ret->setHelpText( AString("The 3D sample standard deviation is computed as 'sqrt(sum(squaredlength(xyz - mean(xyz)))/(n - 1))'.\n\n") + "Uncertainty is a legacy measure used in caret5, and is computed as 'sum(length(xyz - mean(xyz)))/n'." ); return ret; } void AlgorithmSurfaceAverage::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { SurfaceFile* myAvgOut = myParams->getOutputSurface(1); vector inputSurfs; const vector& surfOpts = *(myParams->getRepeatableParameterInstances(2)); int numSurfs = (int)surfOpts.size(); for (int i = 0; i < numSurfs; ++i) { inputSurfs.push_back(surfOpts[i]->getSurface(1)); } MetricFile* stdevOut = NULL; OptionalParameter* stdevOpt = myParams->getOptionalParameter(3); if (stdevOpt->m_present) { stdevOut = stdevOpt->getOutputMetric(1); } MetricFile* uncertOut = NULL; OptionalParameter* uncertaintyOpt = myParams->getOptionalParameter(4); if (uncertaintyOpt->m_present) { uncertOut = uncertaintyOpt->getOutputMetric(1); } AlgorithmSurfaceAverage(myProgObj, myAvgOut, inputSurfs, stdevOut, uncertOut); } AlgorithmSurfaceAverage::AlgorithmSurfaceAverage(ProgressObject* myProgObj, SurfaceFile* myAvgOut, const vector& inputSurfs, MetricFile* stdevOut, MetricFile* uncertOut) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); int numSurfs = (int)inputSurfs.size(); if (numSurfs == 0) throw AlgorithmException("no input surfaces specified in AlgorithmSurfaceAverage"); int numNodes = inputSurfs[0]->getNumberOfNodes(); for (int i = 1; i < numSurfs; ++i) { if (!inputSurfs[i]->hasNodeCorrespondence(*(inputSurfs[0]))) throw AlgorithmException("surface '" + inputSurfs[i]->getFileName() + "' does not have node correspondence to surface '" + inputSurfs[0]->getFileName() + "'"); } *myAvgOut = *(inputSurfs[0]); if (uncertOut != NULL) { uncertOut->setNumberOfNodesAndColumns(numNodes, 1); uncertOut->setStructure(inputSurfs[0]->getStructure()); uncertOut->setColumnName(0, "SHAPE_STANDARD_UNCERTAINTY"); } if (stdevOut != NULL) { stdevOut->setNumberOfNodesAndColumns(numNodes, 1); stdevOut->setStructure(inputSurfs[0]->getStructure()); stdevOut->setColumnName(0, "3D sample standard deviation"); } //loop across nodes first so that we can use doubles for accumulation easily for (int i = 0; i < numNodes; ++i) { double accumpoint[3] = {0.0, 0.0, 0.0}; for (int j = 0; j < numSurfs; ++j) { Vector3D inpoint = inputSurfs[j]->getCoordinate(i); accumpoint[0] += inpoint[0]; accumpoint[1] += inpoint[1]; accumpoint[2] += inpoint[2]; } Vector3D avgpoint; avgpoint[0] = accumpoint[0] / numSurfs; avgpoint[1] = accumpoint[1] / numSurfs; avgpoint[2] = accumpoint[2] / numSurfs; myAvgOut->setCoordinate(i, avgpoint); if (uncertOut != NULL || stdevOut != NULL) { if (numSurfs == 1) { if (uncertOut != NULL) { uncertOut->setValue(i, 0, 0.0f); } if (stdevOut != NULL) { stdevOut->setValue(i, 0, 0.0f); } } else { double distAccum = 0.0;//for caret5 uncertainty double dist2Accum = 0.0;//for sample stdev for (int j = 0; j < numSurfs; ++j) { Vector3D inpoint = inputSurfs[j]->getCoordinate(i); float dist2 = (avgpoint - inpoint).lengthsquared(); dist2Accum += dist2; distAccum += sqrt(dist2); } if (uncertOut != NULL) { uncertOut->setValue(i, 0, distAccum / numSurfs); } if (stdevOut != NULL) { stdevOut->setValue(i, 0, sqrt(dist2Accum / (numSurfs - 1))); } } } } } float AlgorithmSurfaceAverage::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmSurfaceAverage::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmSurfaceAverage.h000066400000000000000000000033741255417355300233200ustar00rootroot00000000000000#ifndef __ALGORITHM_SURFACE_AVERAGE_H__ #define __ALGORITHM_SURFACE_AVERAGE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" #include namespace caret { class AlgorithmSurfaceAverage : public AbstractAlgorithm { AlgorithmSurfaceAverage(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmSurfaceAverage(ProgressObject* myProgObj, SurfaceFile* myAvgOut, const std::vector& inputSurfs, MetricFile* stdevOut = NULL, MetricFile* uncertOut = NULL); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmSurfaceAverage; } #endif //__ALGORITHM_SURFACE_AVERAGE_H__ workbench-1.1.1/src/Algorithms/AlgorithmSurfaceCortexLayer.cxx000066400000000000000000000403361255417355300245610ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmSurfaceCortexLayer.h" #include "AlgorithmException.h" #include "MathFunctions.h" #include "MetricFile.h" #include "SurfaceFile.h" #include "TopologyHelper.h" #include "Vector3D.h" #include using namespace caret; using namespace std; AString AlgorithmSurfaceCortexLayer::getCommandSwitch() { return "-surface-cortex-layer"; } AString AlgorithmSurfaceCortexLayer::getShortDescription() { return "CREATE SURFACE APPROXIMATING A CORTICAL LAYER"; } OperationParameters* AlgorithmSurfaceCortexLayer::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceParameter(1, "white-surface", "the white matter surface"); ret->addSurfaceParameter(2, "pial-surface", "the pial surface"); ret->addDoubleParameter(3, "location", "what volume fraction to place the layer at"); ret->addSurfaceOutputParameter(4, "out-surface", "the output surface"); OptionalParameter* metricOpt = ret->createOptionalParameter(5, "-placement-out", "output the placement as a distance fraction from pial to white"); metricOpt->addMetricOutputParameter(1, "placement-metric", "output metric"); ret->createOptionalParameter(6, "-untwist", "temporary option for comparing methods, specify to use old method"); ret->setHelpText( AString("The input surfaces must have vertex correspondence. ") + "The output surface is generated by placing vertices between the two surfaces such that the enclosed volume within any small patch of the new and white surfaces " + "is the given fraction of the volume of the same patch between the pial and white surfaces " + "(i.e., specifying 0 would give the white surface, 1 would give the pial surface). " ); return ret; } void AlgorithmSurfaceCortexLayer::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { SurfaceFile* myWhiteSurf = myParams->getSurface(1); SurfaceFile* myPialSurf = myParams->getSurface(2); float myVolFrac = myParams->getDouble(3); SurfaceFile* myOutSurf = myParams->getOutputSurface(4); MetricFile* myMetricOut = NULL; OptionalParameter* metricOpt = myParams->getOptionalParameter(5); if (metricOpt->m_present) { myMetricOut = metricOpt->getOutputMetric(1); } bool untwistMode = myParams->getOptionalParameter(6)->m_present; AlgorithmSurfaceCortexLayer(myProgObj, myWhiteSurf, myPialSurf, myVolFrac, myOutSurf, myMetricOut, untwistMode); } AlgorithmSurfaceCortexLayer::AlgorithmSurfaceCortexLayer(ProgressObject* myProgObj, const SurfaceFile* myWhiteSurf, const SurfaceFile* myPialSurf, const float& myVolFrac, SurfaceFile* myOutSurf, MetricFile* myMetricOut, const bool& untwistMode) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); int numNodes = myWhiteSurf->getNumberOfNodes(); if (!myWhiteSurf->hasNodeCorrespondence(*myPialSurf)) { throw AlgorithmException("input surfaces don't have node correspondence"); } *myOutSurf = *myWhiteSurf;//copy topology myOutSurf->setSecondaryType(SecondarySurfaceTypeEnum::MIDTHICKNESS);//??? if (myMetricOut != NULL) { myMetricOut->setNumberOfNodesAndColumns(numNodes, 1); myMetricOut->setStructure(myWhiteSurf->getStructure()); myMetricOut->setColumnName(0, "distance fraction"); } const float* whiteCoords = myWhiteSurf->getCoordinateData(); const float* pialCoords = myPialSurf->getCoordinateData(); bool outside = false; bool above = false; if (myVolFrac < 0 || myVolFrac > 1) { outside = true; if (myVolFrac > 1) above = true; } CaretPointer myTopoHelp = myWhiteSurf->getTopologyHelper();//we checked that the input surfaces have the same effective topology for (int i = 0; i < numNodes; ++i) { Vector3D whiteCenter = whiteCoords + i * 3; Vector3D pialCenter = pialCoords + i * 3; float distFrac = 0.0f; if (untwistMode) { float d1; Vector3D axisHat = (pialCenter - whiteCenter).normal(&d1); const vector& neighbors = myTopoHelp->getNodeNeighbors(i); int numNeigh = (int)neighbors.size(); for (int j = 0; j < numNeigh; ++j) { Vector3D whiteNeighbor = whiteCoords + neighbors[j] * 3; Vector3D pialNeighbor = pialCoords + neighbors[j] * 3; float R0 = axisHat.cross(whiteCenter - whiteNeighbor).length(); float RF = axisHat.cross(pialCenter - pialNeighbor).length(); float Rd = RF - R0; float h0 = axisHat.dot(whiteCenter - whiteNeighbor); float hF = axisHat.dot(pialCenter - pialNeighbor); float hd = hF - h0; float a = d1 * Rd * Rd;//coefficient for t^3 float b = Rd * (3 * d1 * R0 + h0 * Rd - hd * R0);//t^2 float c = R0 * (3 * d1 * R0 + 2 * h0 * Rd - 2 * hd * R0);//t float fullVol = c + b + a;//trivial to evaluate for t = 1, and t = 0 gives 0 float target = fullVol * myVolFrac; float lowt = 0, lowval = 0, hight = 1, highval = fullVol; if (outside) { if (above) { if (Rd / R0 < 0) { hight = min(-R0 / Rd, 1 + (myVolFrac - 1) * 3);//because conical volumes are 1/3 the equivalent cylinder } else { hight = myVolFrac;//because the longest result here is cylinder } highval = hight * (c + hight * (b + hight * a)); } else { if (Rd / R0 > 0) { lowt = max(-R0 / Rd, myVolFrac * 3);//ditto } else { lowt = myVolFrac; } lowval = lowt * (c + lowt * (b + lowt * a)); } }//these ranges SHOULD make the function monotonic const float TOLER = 0.0001f;//stop when high and low bounds are this close const int MAX_ITERS = 100;//but don't take too long trying to get there int iter = 0; while (abs(hight - lowt) > TOLER && iter < MAX_ITERS) { ++iter; float highdiff = (highval - target); float lowdiff = (target - lowval); float guess = (lowt * highdiff + hight * lowdiff) / (highdiff + lowdiff);//weighted average to get guess float highcap = (lowt + 9 * hight) / 10.0f;//make guess lie in the middle 80%, to make sure it always moves nontrivially float lowcap = (9 * lowt + hight) / 10.0f; if (guess < lowcap) guess = lowcap; if (guess > highcap) guess = highcap; float guessval = guess * (c + guess * (b + guess * a)); if ((guessval < target) == (lowval < highval))//in case positive t produces negative volume { lowt = guess; lowval = guessval; } else { hight = guess; highval = guessval; } } float highdiff = (highval - target);//one more iteration to get the value to add to accum float lowdiff = (target - lowval); float guess = (lowt * highdiff + hight * lowdiff) / (highdiff + lowdiff); float highcap = (lowt + 9 * hight) / 10.0f; float lowcap = (9 * lowt + hight) / 10.0f; if (guess < lowcap) guess = lowcap; if (guess > highcap) guess = highcap; distFrac += guess; } distFrac /= numNeigh; } else { float a = 0.0f, b = 0.0f, c = 0.0f;//constants for the cubic function that will give the volume const vector& myTiles = myTopoHelp->getNodeTiles(i); int numTiles = (int)myTiles.size(); for (int j = 0; j < numTiles; ++j) { const int32_t* whiteTile = myWhiteSurf->getTriangle(myTiles[j]); Vector3D whitePoints[3], pialPoints[3], layerDelta[3]; for (int k = 0; k < 3; ++k) { whitePoints[k] = whiteCoords + whiteTile[k] * 3; pialPoints[k] = pialCoords + whiteTile[k] * 3; layerDelta[k] = pialPoints[k] - whitePoints[k]; }//calculate volume with vector field trick: http://en.wikipedia.org/wiki/Polyhedron#Volume Vector3D refPoint = whitePoints[0];//use an "origin" for the vector field that is near the polyhedron for numerical stability - also, this makes several sides have zero contribution //NOTE: the white surface triangle has zero contribution due to choice of reference point, would otherwise contribute to both volumes //layerVol += (layerPoints[0] - refPoint).dot((layerPoints[1] - layerPoints[0]).cross(layerPoints[2] - layerPoints[1])); // (t * (pialPoints[0] - whitePoints[0])).dot((t * (pialPoints[1] - pialPoints[0] - (whitePoints[1] - whitePoints[0])) + whitePoints[1] - whitePoints[0]).cross(...) Vector3D edge10delta = pialPoints[1] - pialPoints[0] - whitePoints[1] + whitePoints[0]; Vector3D edge21delta = pialPoints[2] - pialPoints[1] - whitePoints[2] + whitePoints[1]; // (t * layerDelta[0]).dot((t * edge10delta + whitePoints[1] - whitePoints[0]).cross(t * edge21delta + whitePoints[2] - whitepoints[1])); a += layerDelta[0].dot(edge10delta.cross(edge21delta)); b += layerDelta[0].dot((whitePoints[1] - whitePoints[0]).cross(edge21delta) + edge10delta.cross(whitePoints[2] - whitePoints[1])); c += layerDelta[0].dot((whitePoints[1] - whitePoints[0]).cross(whitePoints[2] - whitePoints[1])); int m = 2; for (int k = 0; k < 3; ++k)//walk the side quadrilaterals as 2->0, 0->1, 1->2 {//add the average of the two triangulations of the quadrilateral Vector3D mref = whitePoints[m] - refPoint, kref = whitePoints[k] - refPoint;//precalculate reference points on the white surface for use in both volumes Vector3D whiteEdge = whitePoints[k] - whitePoints[m];//precalculate this frequently used edge also if (k != 0 && m != 0) //layerVol += 0.5f * mref.dot(whiteEdge.cross(layerPoints[k] - whitePoints[k])); { c += 0.5f * mref.dot(whiteEdge.cross(layerDelta[k])); } if (m != 0) //layerVol += 0.5f * mref.dot((layerPoints[m] - whitePoints[m]).cross(whitePoints[m] - layerPoints[k])); { // 0.5f * mref.dot((t * layerDelta[m]).cross(whitePoints[m] - (whitePoints[k] + t * layerDelta[k]))); b += 0.5f * mref.dot(layerDelta[m].cross(-layerDelta[k])); c += 0.5f * mref.dot(layerDelta[m].cross(-whiteEdge)); } if (k != 0 && m != 0) //layerVol += 0.5f * kref.dot(whiteEdge.cross(layerPoints[m] - whitePoints[k])); { // 0.5f * kref.dot(whiteEdge.cross(layerPoints[m] - whitePoints[m]));//use a formula where t = 0 gives a zero length edge c += 0.5f * kref.dot(whiteEdge.cross(layerDelta[m])); } if (k != 0) //layerVol += 0.5f * kref.dot((layerPoints[k] - whitePoints[k]).cross(layerPoints[m] - layerPoints[k])); { // 0.5f * kref.dot((t * layerDelta[k]).cross((whitePoints[m] + t * layerDelta[m]) - (whitePoints[k] + t * layerDelta[k]))); b += 0.5f * kref.dot(layerDelta[k].cross(layerDelta[m] - layerDelta[k])); c += 0.5f * kref.dot(layerDelta[k].cross(-whiteEdge)); } m = k; } } float fullVol = a + b + c;//trivial to evaluate for t = 1, and t = 0 gives 0 float target = fullVol * myVolFrac; float lowt = 0, lowval = 0, hight = 1, highval = fullVol; if (outside) { if (above) { hight = 1 + (myVolFrac - 1) * 3;//set a limit at 3 times further out than the requested fraction, and just hope that it is monotonic highval = hight * (c + hight * (b + hight * a)); } else { lowt = myVolFrac * 3;//ditto lowval = lowt * (c + lowt * (b + lowt * a)); } }//these ranges SHOULD make the function monotonic const float TOLER = 0.0001f;//stop when high and low bounds are this close const int MAX_ITERS = 100;//but don't take too long trying to get there int iter = 0; while (abs(hight - lowt) > TOLER && iter < MAX_ITERS) { ++iter; float highdiff = (highval - target); float lowdiff = (target - lowval); float guess = (lowt * highdiff + hight * lowdiff) / (highdiff + lowdiff);//weighted average to get guess float highcap = (lowt + 9 * hight) / 10.0f;//make guess lie in the middle 80%, to make sure it always moves nontrivially float lowcap = (9 * lowt + hight) / 10.0f; if (guess < lowcap) guess = lowcap; if (guess > highcap) guess = highcap; float guessval = guess * (c + guess * (b + guess * a)); if ((guessval < target) == (lowval < highval))//in case positive t produces negative volume { lowt = guess; lowval = guessval; } else { hight = guess; highval = guessval; } } float highdiff = (highval - target);//one more iteration to get the value to add to accum float lowdiff = (target - lowval); float guess = (lowt * highdiff + hight * lowdiff) / (highdiff + lowdiff); float highcap = (lowt + 9 * hight) / 10.0f; float lowcap = (9 * lowt + hight) / 10.0f; if (guess < lowcap) guess = lowcap; if (guess > highcap) guess = highcap; distFrac = guess; } if (!MathFunctions::isNumeric(distFrac)) { distFrac = 0.5f;//non-numeric should only happen when pial and white coords are identical, so average should be fine } myOutSurf->setCoordinate(i, distFrac * (pialCenter - whiteCenter) + whiteCenter); if (myMetricOut != NULL) { myMetricOut->setValue(i, 0, distFrac); } } } float AlgorithmSurfaceCortexLayer::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmSurfaceCortexLayer::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmSurfaceCortexLayer.h000066400000000000000000000035331255417355300242040ustar00rootroot00000000000000#ifndef __ALGORITHM_SURFACE_CORTEX_LAYER_H__ #define __ALGORITHM_SURFACE_CORTEX_LAYER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmSurfaceCortexLayer : public AbstractAlgorithm { AlgorithmSurfaceCortexLayer(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmSurfaceCortexLayer(ProgressObject* myProgObj, const SurfaceFile* myWhiteSurf, const SurfaceFile* myPialSurf, const float& myVolFrac, SurfaceFile* myOutSurf, MetricFile* myMetricOut = NULL, const bool& untwistMode = false); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmSurfaceCortexLayer; } #endif //__ALGORITHM_SURFACE_CORTEX_LAYER_H__ workbench-1.1.1/src/Algorithms/AlgorithmSurfaceCreateSphere.cxx000066400000000000000000000301421255417355300246640ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmSurfaceCreateSphere.h" #include "AlgorithmException.h" #include "CaretLogger.h" #include "SurfaceFile.h" #include "Vector3D.h" #include using namespace caret; using namespace std; AString AlgorithmSurfaceCreateSphere::getCommandSwitch() { return "-surface-create-sphere"; } AString AlgorithmSurfaceCreateSphere::getShortDescription() { return "GENERATE A SPHERE WITH CONSISTENT VERTEX AREAS"; } OperationParameters* AlgorithmSurfaceCreateSphere::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addIntegerParameter(1, "num-vertices", "desired number of vertices"); ret->addSurfaceOutputParameter(2, "sphere-out", "the output sphere"); ret->setHelpText( AString("Generates a sphere by regularly dividing the triangles of an icosahedron, to come as close to the desired number of vertices as possible, ") + "and modifying it to have very similar vertex areas for all vertices. " + "To generate a pair of vertex-matched left and right spheres, use this command, then -surface-flip-lr to generate the other sphere, then -set-structure on each. " + "For example:\n\n" + "$ wb_command -surface-create-sphere 6000 Sphere.6k.R.surf.gii\n" + "$ wb_command -surface-flip-lr Sphere.6k.R.surf.gii Sphere.6k.L.surf.gii\n" + "$ wb_command -set-structure Sphere.6k.R.surf.gii CORTEX_RIGHT\n" + "$ wb_command -set-structure Sphere.6k.L.surf.gii CORTEX_LEFT" ); return ret; } void AlgorithmSurfaceCreateSphere::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { int numVertices = (int)myParams->getInteger(1); SurfaceFile* mySurfOut = myParams->getOutputSurface(2); AlgorithmSurfaceCreateSphere(myProgObj, numVertices, mySurfOut); } AlgorithmSurfaceCreateSphere::AlgorithmSurfaceCreateSphere(ProgressObject* myProgObj, const int& numVertices, SurfaceFile* mySurfOut) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); if (numVertices < 1) throw AlgorithmException("desired number of vertices must be positive"); int lastnodes, mynodes, mytris; m_numDivisions = -1; getNumberOfNodesAndTrianglesFromDivisions(0, lastnodes, mytris); for (int i = 1; i < 10000; ++i)//10363 divisions overflows an int32 in number of triangles, tighten this sanity check? { getNumberOfNodesAndTrianglesFromDivisions(i, mynodes, mytris); if (abs(numVertices - lastnodes) < abs(numVertices - mynodes)) { m_numDivisions = i - 1; CaretLogFine("Closest divided icosahedron has " + AString::number(lastnodes) + " nodes."); break; } lastnodes = mynodes; } if (m_numDivisions == -1) throw AlgorithmException("too many vertices specified"); m_surface = mySurfOut; getNumberOfNodesAndTrianglesFromDivisions(m_numDivisions, mynodes, mytris); m_surface->setNumberOfNodesAndTriangles(mynodes, mytris); m_surface->setSurfaceType(SurfaceTypeEnum::SPHERICAL); m_surface->setSecondaryType(SecondarySurfaceTypeEnum::INVALID); SurfaceFile initSurf; initSurf.setNumberOfNodesAndTriangles(12, 20); const double phi = (1.0 + sqrt(5.0)) / 2.0; initSurf.setCoordinate(0, -phi, 0.0, 1.0);//initial icosahedron initSurf.setCoordinate(1, 0, -1.0, phi); initSurf.setCoordinate(2, phi, 0.0, 1.0); initSurf.setCoordinate(3, 0, 1.0, phi); initSurf.setCoordinate(4, -1.0, -phi, 0.0); initSurf.setCoordinate(5, 1.0, -phi, 0.0); initSurf.setCoordinate(6, 1.0, phi, 0.0); initSurf.setCoordinate(7, -1.0, phi, 0.0); initSurf.setCoordinate(8, -phi, 0.0, -1.0); initSurf.setCoordinate(9, 0, -1.0, -phi); initSurf.setCoordinate(10, phi, 0.0, -1.0); initSurf.setCoordinate(11, 0, 1.0, -phi); initSurf.setTriangle(0, 0, 7, 8); initSurf.setTriangle(1, 0, 3, 7); initSurf.setTriangle(2, 0, 1, 3); initSurf.setTriangle(3, 1, 2, 3); initSurf.setTriangle(4, 1, 5, 2); initSurf.setTriangle(5, 5, 9, 10); initSurf.setTriangle(6, 10, 11, 6); initSurf.setTriangle(7, 4, 1, 0); initSurf.setTriangle(8, 4, 5, 1); initSurf.setTriangle(9, 4, 9, 5); initSurf.setTriangle(10, 5, 10, 2); initSurf.setTriangle(11, 10, 6, 2); initSurf.setTriangle(12, 6, 11, 7); initSurf.setTriangle(13, 11, 9, 8); initSurf.setTriangle(14, 9, 4, 8); initSurf.setTriangle(15, 8, 4, 0); initSurf.setTriangle(16, 11, 8, 7); initSurf.setTriangle(17, 6, 7, 3); initSurf.setTriangle(18, 6, 3, 2); initSurf.setTriangle(19, 11, 10, 9); Vector3D tempcoord; for (int i = 0; i < 12; ++i) { tempcoord = initSurf.getCoordinate(i); tempcoord *= 100.0f / tempcoord.length();//make it directly into a radius 100 sphere rather than taking a second pass m_surface->setCoordinate(i, tempcoord); } m_curTiles = 0; m_curNodes = 12; m_edgenodes.resize(11);//stores by low node # vector edge1(m_numDivisions + 1), edge2(m_numDivisions + 1), edge3(m_numDivisions + 1); vector > facenodes(m_numDivisions + 1); for (int i = 0; i <= m_numDivisions; ++i) {// first index is row from bottom, second index is column from left facenodes[i].resize(m_numDivisions - i + 1); } for (int i = 0; i < 20; ++i) { const int32_t* nodes = initSurf.getTriangle(i); //convention for visualization: nodes[0] is bottom left, nodes[1] is bottom right, nodes[2] is top //tile generation also follows this convention, with the consequence that tile orientation is preserved getEdge(nodes[0], nodes[1], edge1.data());//bottom edge, left to right getEdge(nodes[0], nodes[2], edge2.data());//left edge, bottom to top getEdge(nodes[1], nodes[2], edge3.data());//right edge, bottom to top const float* coord1 = initSurf.getCoordinate(nodes[0]); const float* coord2 = initSurf.getCoordinate(nodes[1]); const float* coord3 = initSurf.getCoordinate(nodes[2]); for (int j = 0; j <= m_numDivisions; ++j) {//copy edge nodes into the face array facenodes[0][j] = edge1[j]; facenodes[j][0] = edge2[j]; facenodes[j][m_numDivisions - j] = edge3[j]; } for (int j = 1; j < m_numDivisions - 1; ++j) {//generate interior coordinates int intCols = m_numDivisions - j; for (int k = 1; k < intCols; ++k) { interp3(coord1, coord2, coord3, j, k, tempcoord); tempcoord *= 100.0f / tempcoord.length();//make it directly into a radius 100 sphere rather than taking a second pass m_surface->setCoordinate(m_curNodes, tempcoord); facenodes[j][k] = m_curNodes; ++m_curNodes; } } for (int j = 0; j < m_numDivisions; ++j) {//generate tiles for (int k = 0; k < m_numDivisions - j - 1; ++k) {//pairs for trapezoidal pieces m_surface->setTriangle(m_curTiles, facenodes[j][k], facenodes[j][k + 1], facenodes[j + 1][k]); m_surface->setTriangle(m_curTiles + 1, facenodes[j + 1][k], facenodes[j][k + 1], facenodes[j + 1][k + 1]); m_curTiles += 2; }//and one more m_surface->setTriangle(m_curTiles, facenodes[j][m_numDivisions - j - 1], facenodes[j][m_numDivisions - j], facenodes[j + 1][m_numDivisions - j - 1]); ++m_curTiles; } } } void AlgorithmSurfaceCreateSphere::getNumberOfNodesAndTrianglesFromDivisions(const int& divisions, int& numNodesOut, int& numTrianglesOut) { int div2 = divisions * divisions; numNodesOut = 2 + 10 * div2; numTrianglesOut = 20 * div2;//yes, its really that simple, the sum of the two triangulars is square } void AlgorithmSurfaceCreateSphere::interp3(const float coord1[3], const float coord2[3], const float coord3[3], const int& row, const int& col, float out[3]) {//this is the function to change if you want different spacing float weight2 = ((float)col) / m_numDivisions;//start with flat interpolation weights float weight3 = ((float)row) / m_numDivisions; float weight1 = 1.0f - weight2 - weight3; //polynomial for each weight - should map 0 to 0 and 1 to 1 const float quintweight = 0.0537206f;//WEIGHTS TUNED FOR ICOSAHEDRON VIA GENETIC ALGORITHM, ADJUST FOR OTHER INITIAL POLYGONS const float quartweight = -0.174466f;//this polynomial should be highly dependent on size of the triangle being interpolated const float cubeweight = 0.292547f; const float quadweight = -0.456351f; const float linweight = 1.0f - quintweight - quartweight - cubeweight - quadweight;//make sure it maps 0 to 0 and 1 to 1 weight1 = ((((quintweight * weight1 + quartweight) * weight1 + cubeweight) * weight1 + quadweight) * weight1 + linweight) * weight1;//quintic approximation of great arc equal area weight transformation function weight2 = ((((quintweight * weight2 + quartweight) * weight2 + cubeweight) * weight2 + quadweight) * weight2 + linweight) * weight2; weight3 = ((((quintweight * weight3 + quartweight) * weight3 + cubeweight) * weight3 + quadweight) * weight3 + linweight) * weight3;//three weights no longer sum to 1, but thats ok out[0] = coord1[0] * weight1 + coord2[0] * weight2 + coord3[0] * weight3; out[1] = coord1[1] * weight1 + coord2[1] * weight2 + coord3[1] * weight3; out[2] = coord1[2] * weight1 + coord2[2] * weight2 + coord3[2] * weight3; } void AlgorithmSurfaceCreateSphere::getEdge(int node1, int node2, int* out) { bool reverse = false; int i, index, edgesize = m_numDivisions + 1; if (node1 > node2) { reverse = true; i = node1; node1 = node2; node2 = i; } bool found = false; for (i = 0; i < (int)m_edgenodes[node1].size(); ++i) { if (m_edgenodes[node1][i][m_numDivisions] == node2) { found = true; index = i; break; } } if (!found) { float coord3[3] = {0.0f, 0.0f, 0.0f}; Vector3D tempcoord; const float* coord1, *coord2; coord1 = m_surface->getCoordinate(node1); coord2 = m_surface->getCoordinate(node2); std::vector tempvec; tempvec.resize(edgesize); tempvec[0] = node1; tempvec[m_numDivisions] = node2; for (i = 1; i < m_numDivisions; ++i) { interp3(coord1, coord2, coord3, 0, i, tempcoord);//use 0 as dummy node, with row 0 it is unused tempvec[i] = m_curNodes; tempcoord *= 100.0f / tempcoord.length(); m_surface->setCoordinate(m_curNodes, tempcoord); ++m_curNodes; } index = m_edgenodes[node1].size(); m_edgenodes[node1].push_back(tempvec); } if (reverse) { for (i = 0; i < edgesize; ++i) { out[i] = m_edgenodes[node1][index][edgesize - i - 1]; } } else { for (i = 0; i < edgesize; ++i) { out[i] = m_edgenodes[node1][index][i]; } } } float AlgorithmSurfaceCreateSphere::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmSurfaceCreateSphere::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmSurfaceCreateSphere.h000066400000000000000000000042441255417355300243150ustar00rootroot00000000000000#ifndef __ALGORITHM_SURFACE_CREATE_SPHERE_H__ #define __ALGORITHM_SURFACE_CREATE_SPHERE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" #include namespace caret { class AlgorithmSurfaceCreateSphere : public AbstractAlgorithm { AlgorithmSurfaceCreateSphere(); void interp3(const float coord1[3], const float coord2[3], const float coord3[3], const int& row, const int& col, float out[3]); void getEdge(int node1, int node2, int* out); int m_numDivisions, m_curNodes, m_curTiles; SurfaceFile* m_surface; std::vector > > m_edgenodes; static void getNumberOfNodesAndTrianglesFromDivisions(const int& divisions, int& numNodesOut, int& numTrianglesOut); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmSurfaceCreateSphere(ProgressObject* myProgObj, const int& numVertices, SurfaceFile* mySurfOut); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmSurfaceCreateSphere; } #endif //__ALGORITHM_SURFACE_CREATE_SPHERE_H__ workbench-1.1.1/src/Algorithms/AlgorithmSurfaceDistortion.cxx000066400000000000000000000261221255417355300244530ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmSurfaceDistortion.h" #include "AlgorithmException.h" #include "AlgorithmMetricSmoothing.h" #include "MathFunctions.h" #include "MetricFile.h" #include "SurfaceFile.h" #include "TopologyHelper.h" #include "Vector3D.h" #include #include using namespace caret; using namespace std; AString AlgorithmSurfaceDistortion::getCommandSwitch() { return "-surface-distortion"; } AString AlgorithmSurfaceDistortion::getShortDescription() { return "MEASURE DISTORTION BETWEEN SURFACES"; } OperationParameters* AlgorithmSurfaceDistortion::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceParameter(1, "surface-reference", "the reference surface"); ret->addSurfaceParameter(2, "surface-distorted", "the distorted surface"); ret->addMetricOutputParameter(3, "metric-out", "the output distortion metric"); OptionalParameter* smoothOpt = ret->createOptionalParameter(4, "-smooth", "smooth the area data"); smoothOpt->addDoubleParameter(1, "sigma", "the smoothing kernel sigma in mm"); ret->createOptionalParameter(5, "-caret5-method", "use the surface distortion method from caret5"); ret->createOptionalParameter(6, "-edge-method", "calculate distortion of edge lengths rather than areas"); ret->setHelpText( AString("This command, when not using -caret5-method or -edge-method, is equivalent to using -surface-vertex-areas on each surface, ") + "smoothing both output metrics with the GEO_GAUSS_EQUAL method on the surface they came from if -smooth is specified, and then using the formula " + "'ln(distorted/reference)/ln(2)' on the smoothed results.\n\n" + "When using -caret5-method, it uses the surface distortion method from caret5, which takes the base 2 log of the ratio of tile areas, " + "then averages those results at each vertex, and then smooths the result on the reference surface.\n\n" + "When using -edge-method, the -smooth option is ignored, and the output at each vertex is the average of 'abs(ln(refEdge/distortEdge)/ln(2))' over all edges " + "connected to the vertex." ); return ret; } void AlgorithmSurfaceDistortion::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { SurfaceFile* referenceSurf = myParams->getSurface(1); SurfaceFile* distortedSurf = myParams->getSurface(2); MetricFile* myMetricOut = myParams->getOutputMetric(3); float smooth = -1.0f; OptionalParameter* smoothOpt = myParams->getOptionalParameter(4); if (smoothOpt->m_present) { smooth = (float)smoothOpt->getDouble(1); if (smooth <= 0.0f) throw AlgorithmException("smoothing kernel must be positive if specified"); } bool caret5method = myParams->getOptionalParameter(5)->m_present; bool edgeMethod = myParams->getOptionalParameter(6)->m_present; if (caret5method && edgeMethod) throw AlgorithmException("you may not specify both -caret5-method and -edge-method"); AlgorithmSurfaceDistortion(myProgObj, referenceSurf, distortedSurf, myMetricOut, smooth, caret5method, edgeMethod); } AlgorithmSurfaceDistortion::AlgorithmSurfaceDistortion(ProgressObject* myProgObj, const SurfaceFile* referenceSurf, const SurfaceFile* distortedSurf, MetricFile* myMetricOut, const float& smooth, const bool& caret5method, const bool& edgeMethod) : AbstractAlgorithm(myProgObj) { if (caret5method && edgeMethod) throw AlgorithmException("you may not use both caret5 and edge method flags"); ProgressObject* smoothRef = NULL, *smoothDistort = NULL, *caret5Smooth = NULL;//uncomment these if you use another algorithm inside here if (myProgObj != NULL) { if (smooth > 0.0f && !edgeMethod) { if (caret5method) { caret5Smooth = myProgObj->addAlgorithm(AlgorithmMetricSmoothing::getAlgorithmWeight()); } else { smoothRef = myProgObj->addAlgorithm(AlgorithmMetricSmoothing::getAlgorithmWeight()); smoothDistort = myProgObj->addAlgorithm(AlgorithmMetricSmoothing::getAlgorithmWeight()); } } } LevelProgress myProgress(myProgObj); if (!referenceSurf->hasNodeCorrespondence(*distortedSurf)) throw AlgorithmException("input surfaces must have node correspondence"); int numNodes = referenceSurf->getNumberOfNodes(); myMetricOut->setNumberOfNodesAndColumns(numNodes, 1); myMetricOut->setStructure(referenceSurf->getStructure()); if (caret5method) { myMetricOut->setColumnName(0, "area distortion (caret5)"); int numTiles = referenceSurf->getNumberOfTriangles(); vector tilescratch(numTiles), nodescratch(numNodes); const float* refCoords = referenceSurf->getCoordinateData(), *distortCoords = distortedSurf->getCoordinateData(); for (int i = 0; i < numTiles; ++i) { const int32_t* myTile = referenceSurf->getTriangle(i); int32_t offset[3] = { myTile[0] * 3, myTile[1] * 3, myTile[2] * 3 }; float refarea = MathFunctions::triangleArea(refCoords + offset[0], refCoords + offset[1], refCoords + offset[2]); float distortarea = MathFunctions::triangleArea(distortCoords + offset[0], distortCoords + offset[1], distortCoords + offset[2]); if (refarea == 0.0f) { if (distortarea == 0.0f) { tilescratch[i] = 0.0f; } else { tilescratch[i] = log(10000.0) / log(2.0);//thats what it says } } else { if (distortarea / refarea < 0.00000001) { tilescratch[i] = log(0.00000001) / log(2.0); } else { tilescratch[i] = log(distortarea / refarea) / log(2.0); } } } CaretPointer myhelp = referenceSurf->getTopologyHelper(); for (int i = 0; i < numNodes; ++i) { const vector& myTiles = myhelp->getNodeTiles(i); int tileCount = (int)myTiles.size(); double accum = 0.0; for (int j = 0; j < tileCount; ++j) { accum += tilescratch[myTiles[j]]; } if (tileCount == 0) { nodescratch[i] = 0.0f;//actually, caret5 uses 1 here, but that is wrong, so fix it } else { nodescratch[i] = accum / tileCount; } } if (smooth > 0.0f) { MetricFile tempResult; tempResult.setNumberOfNodesAndColumns(numNodes, 1); tempResult.setValuesForColumn(0, nodescratch.data());//TSC: not sure what the "best" smoothing method here is, but there isn't a "correct" one because this method has flaws AlgorithmMetricSmoothing(caret5Smooth, referenceSurf, &tempResult, smooth, myMetricOut, NULL, false, false, -1, NULL, MetricSmoothingObject::GEO_GAUSS_AREA); myMetricOut->setStructure(referenceSurf->getStructure());//just in case we change where metric smoothing gets structure from myMetricOut->setColumnName(0, "area distortion (caret5)"); } else { myMetricOut->setValuesForColumn(0, nodescratch.data()); } } else if (edgeMethod) { myMetricOut->setColumnName(0, "edge distortion"); CaretPointer myhelp = referenceSurf->getTopologyHelper(); const float* refCoords = referenceSurf->getCoordinateData(), *distortCoords = distortedSurf->getCoordinateData(); for (int i = 0; i < numNodes; ++i) { Vector3D refCenter = refCoords + i * 3; Vector3D distortCenter = distortCoords + i * 3; const vector& neighbors = myhelp->getNodeNeighbors(i); int numNeigh = (int)neighbors.size(); float accum = 0.0f; for (int j = 0; j < numNeigh; ++j) { Vector3D refNeigh = refCoords + neighbors[j] * 3; Vector3D distortNeigh = distortCoords + neighbors[j] * 3; float refLength = (refNeigh - refCenter).length(); float distortLength = (distortNeigh - distortCenter).length(); float ratio; if (refLength < distortLength) { ratio = distortLength / refLength; } else { ratio = refLength / distortLength; } accum += log(ratio) / log(2.0f); } myMetricOut->setValue(i, 0, accum / numNeigh); } } else { myMetricOut->setColumnName(0, "area distortion"); MetricFile refAreas, distortAreas; refAreas.setNumberOfNodesAndColumns(numNodes, 1); distortAreas.setNumberOfNodesAndColumns(numNodes, 1); vector scratch; referenceSurf->computeNodeAreas(scratch); refAreas.setValuesForColumn(0, scratch.data()); distortedSurf->computeNodeAreas(scratch); distortAreas.setValuesForColumn(0, scratch.data()); MetricFile refSmoothed, distortSmoothed; const float* refData = refAreas.getValuePointerForColumn(0), *distortData = distortAreas.getValuePointerForColumn(0); if (smooth > 0.0f) { AlgorithmMetricSmoothing(smoothRef, referenceSurf, &refAreas, smooth, &refSmoothed, NULL, false, false, -1, NULL, MetricSmoothingObject::GEO_GAUSS_EQUAL); AlgorithmMetricSmoothing(smoothDistort, distortedSurf, &distortAreas, smooth, &distortSmoothed, NULL, false, false, -1, NULL, MetricSmoothingObject::GEO_GAUSS_EQUAL); refData = refSmoothed.getValuePointerForColumn(0); distortData = distortSmoothed.getValuePointerForColumn(0); } for (int i = 0; i < numNodes; ++i) { scratch[i] = log(distortData[i] / refData[i]) / log(2.0f);//should we sanity check these? } myMetricOut->setValuesForColumn(0, scratch.data()); } } float AlgorithmSurfaceDistortion::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmSurfaceDistortion::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmSurfaceDistortion.h000066400000000000000000000035331255417355300241010ustar00rootroot00000000000000#ifndef __ALGORITHM_SURFACE_DISTORTION_H__ #define __ALGORITHM_SURFACE_DISTORTION_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmSurfaceDistortion : public AbstractAlgorithm { AlgorithmSurfaceDistortion(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmSurfaceDistortion(ProgressObject* myProgObj, const SurfaceFile* referenceSurf, const SurfaceFile* distortedSurf, MetricFile* myMetricOut, const float& smooth = -1.0f, const bool& caret5method = false, const bool& edgeMethod = false); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmSurfaceDistortion; } #endif //__ALGORITHM_SURFACE_DISTORTION_H__ workbench-1.1.1/src/Algorithms/AlgorithmSurfaceFlipLR.cxx000066400000000000000000000064771255417355300234600ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmSurfaceFlipLR.h" #include "AlgorithmException.h" #include "SurfaceFile.h" using namespace caret; using namespace std; AString AlgorithmSurfaceFlipLR::getCommandSwitch() { return "-surface-flip-lr"; } AString AlgorithmSurfaceFlipLR::getShortDescription() { return "MIRROR A SURFACE THROUGH THE YZ PLANE"; } OperationParameters* AlgorithmSurfaceFlipLR::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceParameter(1, "surface", "the surface to flip"); ret->addSurfaceOutputParameter(2, "surface-out", "the output flipped surface"); ret->setHelpText( AString("This command negates the x coordinate of each vertex, and flips the surface normals, so that you have a surface of ") + "opposite handedness with the same features and node correspondence, with normals consistent with the original surface. " + "That is, if the input surface has normals facing outward, the output surface will also have normals facing outward." ); return ret; } void AlgorithmSurfaceFlipLR::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { SurfaceFile* mySurf = myParams->getSurface(1); SurfaceFile* mySurfOut = myParams->getOutputSurface(2); AlgorithmSurfaceFlipLR(myProgObj, mySurf, mySurfOut); } AlgorithmSurfaceFlipLR::AlgorithmSurfaceFlipLR(ProgressObject* myProgObj, const SurfaceFile* mySurf, SurfaceFile* mySurfOut) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); mySurfOut->setNumberOfNodesAndTriangles(mySurf->getNumberOfNodes(), mySurf->getNumberOfTriangles()); mySurfOut->setSurfaceType(mySurf->getSurfaceType()); mySurfOut->setSecondaryType(mySurf->getSecondaryType()); int numNodes = mySurf->getNumberOfNodes(); float tempcoord[3]; for (int i = 0; i < numNodes; ++i) { mySurf->getCoordinate(i, tempcoord); tempcoord[0] = -tempcoord[0]; mySurfOut->setCoordinate(i, tempcoord); } int numTiles = mySurf->getNumberOfTriangles(); for (int i = 0; i < numTiles; ++i) { const int32_t* temptile = mySurf->getTriangle(i); mySurfOut->setTriangle(i, temptile[1], temptile[0], temptile[2]); } } float AlgorithmSurfaceFlipLR::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmSurfaceFlipLR::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmSurfaceFlipLR.h000066400000000000000000000032221255417355300230660ustar00rootroot00000000000000#ifndef __ALGORITHM_SURFACE_FLIP_LR_H__ #define __ALGORITHM_SURFACE_FLIP_LR_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmSurfaceFlipLR : public AbstractAlgorithm { AlgorithmSurfaceFlipLR(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmSurfaceFlipLR(ProgressObject* myProgObj, const SurfaceFile* mySurf, SurfaceFile* mySurfOut); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmSurfaceFlipLR; } #endif //__ALGORITHM_SURFACE_FLIP_LR_H__ workbench-1.1.1/src/Algorithms/AlgorithmSurfaceGenerateInflated.cxx000066400000000000000000000223211255417355300255130ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretAssert.h" #include "CaretLogger.h" #include "AlgorithmSurfaceGenerateInflated.h" #include "AlgorithmSurfaceInflation.h" #include "AlgorithmException.h" #include "SurfaceFile.h" using namespace caret; /** * \class caret::AlgorithmSurfaceGenerateInflated * \brief SURFACE GENERATE INFLATED * * */ /** * @return Command line switch */ AString AlgorithmSurfaceGenerateInflated::getCommandSwitch() { return "-surface-generate-inflated"; } /** * @return Short description of algorithm */ AString AlgorithmSurfaceGenerateInflated::getShortDescription() { return "SURFACE GENERATE INFLATED"; } /** * @return Parameters for algorithm */ OperationParameters* AlgorithmSurfaceGenerateInflated::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceParameter(1, "anatomical-surface-in", "the anatomical surface"); ret->addSurfaceOutputParameter(2, "inflated-surface-out", "the output inflated surface"); ret->addSurfaceOutputParameter(3, "very-inflated-surface-out", "the output very inflated surface"); OptionalParameter* weightOpt = ret->createOptionalParameter(4, "-iterations-scale", "optional iterations scaling"); weightOpt->addDoubleParameter(1, "iterations-scale-value", "iterations-scale value"); AString helpText = ("Generate inflated and very inflated surfaces. The output surfaces are " "\'matched\' (have same XYZ range) to the anatomcal surface. " "In most cases, an iterations-scale of 1.0 (default) is sufficient. However, if " "the surface contains a large number of nodes (150,000), try an " "iterations-scale of 2.5."); ret->setHelpText(helpText); return ret; } /** * Use Parameters and perform algorithm * @param myParams * Parameters for algorithm * @param myProgObj * The progress object * @throws * AlgorithmException if errors */ void AlgorithmSurfaceGenerateInflated::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { const SurfaceFile* anatomicalSurfaceIn = myParams->getSurface(1); SurfaceFile* inflatedSurfaceFile = myParams->getOutputSurface(2); SurfaceFile* veryInflatedSurfaceFile = myParams->getOutputSurface(3); float iterationsScale = 1.0; OptionalParameter* iterationsScaleOpt = myParams->getOptionalParameter(4); if (iterationsScaleOpt->m_present) { iterationsScale = iterationsScaleOpt->getDouble(1); } /* * Constructs and executes the algorithm */ AlgorithmSurfaceGenerateInflated(myProgObj, anatomicalSurfaceIn, inflatedSurfaceFile, veryInflatedSurfaceFile, iterationsScale); } /** * Constructor * * Calling the constructor will execute the algorithm * * @param myProgObj * Parameters for algorithm * @param anatomicalSurfaceFile * The anatomical surface. * @param inflatedSurfaceFileOut * Inflated surface that is generated. * @param veryInflatedSurfaceFileOut * Very inflated surface that is generated. * @param iterationsScaleIn * Iterations scale used for surface with large numbers of nodes. */ AlgorithmSurfaceGenerateInflated::AlgorithmSurfaceGenerateInflated(ProgressObject* myProgObj, const SurfaceFile* anatomicalSurfaceFile, SurfaceFile* inflatedSurfaceFileOut, SurfaceFile* veryInflatedSurfaceFileOut, const float iterationsScaleIn) : AbstractAlgorithm(myProgObj) { ProgressObject* lowProgress = NULL, *inflatedProgress = NULL, *veryInfProgress = NULL; if (myProgObj != NULL) { lowProgress = myProgObj->addAlgorithm(AlgorithmSurfaceInflation::getAlgorithmWeight()); inflatedProgress = myProgObj->addAlgorithm(AlgorithmSurfaceInflation::getAlgorithmWeight() * 2); veryInfProgress = myProgObj->addAlgorithm(AlgorithmSurfaceInflation::getAlgorithmWeight() * 4); } /* * Sets the algorithm up to use the progress object, and will * finish the progress object automatically when the algorithm terminates */ LevelProgress myProgress(myProgObj, 1.0f, 0.1f);//low internal weight because this function doesn't do much non-subalgorithm stuff const float iterationsScale = ((iterationsScaleIn > 0.0) ? iterationsScaleIn : 1.0); /* * Generate low-smooth surface which is an intermediate surface * between anatomical and inflated. */ SurfaceFile lowSmoothSurface(*anatomicalSurfaceFile); const int32_t lowSmoothCycles = 1; const float lowSmoothStrength = 0.2; const int32_t lowSmoothIterations = static_cast(50 * iterationsScale); const float lowSmoothInflationFactor = 1.0; myProgress.setTask("Generating Low-smooth Surface"); AlgorithmSurfaceInflation(lowProgress, anatomicalSurfaceFile, &lowSmoothSurface, &lowSmoothSurface, lowSmoothCycles, lowSmoothStrength, lowSmoothIterations, lowSmoothInflationFactor); /* * Generation the inflated surface */ *inflatedSurfaceFileOut = lowSmoothSurface; const int32_t inflatedSmoothCycles = 2; const float inflatedSmoothStrength = 1.0; const int32_t inflatedSmoothIterations = static_cast(30 * iterationsScale); const float inflatedSmoothInflationFactor = 1.4; myProgress.setTask("Generating Inflated Surface"); AlgorithmSurfaceInflation(inflatedProgress, anatomicalSurfaceFile, inflatedSurfaceFileOut, inflatedSurfaceFileOut, inflatedSmoothCycles, inflatedSmoothStrength, inflatedSmoothIterations, inflatedSmoothInflationFactor); inflatedSurfaceFileOut->setSurfaceType(SurfaceTypeEnum::INFLATED); /* * Generation the inflated surface */ *veryInflatedSurfaceFileOut = *inflatedSurfaceFileOut; const int32_t veryInflatedSmoothCycles = 4; const float veryInflatedSmoothStrength = 1.0; const int32_t veryInflatedSmoothIterations = static_cast(30 * iterationsScale); const float veryInflatedSmoothInflationFactor = 1.1; myProgress.setTask("Generating Very Inflated Surface"); AlgorithmSurfaceInflation(veryInfProgress, anatomicalSurfaceFile, veryInflatedSurfaceFileOut, veryInflatedSurfaceFileOut, veryInflatedSmoothCycles, veryInflatedSmoothStrength, veryInflatedSmoothIterations, veryInflatedSmoothInflationFactor); veryInflatedSurfaceFileOut->setSurfaceType(SurfaceTypeEnum::VERY_INFLATED); myProgress.setTask("Matching Bounding Boxes"); inflatedSurfaceFileOut->matchSurfaceBoundingBox(anatomicalSurfaceFile); myProgress.reportProgress(0.5);//report progress of non-subalgorithm computation only veryInflatedSurfaceFileOut->matchSurfaceBoundingBox(anatomicalSurfaceFile); myProgress.reportProgress(1.0);//this isn't really needed, happens automatically when algorithm ends, but for clarity } /** * @return Algorithm internal weight */ float AlgorithmSurfaceGenerateInflated::getAlgorithmInternalWeight() { /* * override this if needed, if the progress bar isn't smooth */ return 0.1f;//we very little inside this algorithm except call other algorithms } /** * @return Algorithm sub-algorithm weight */ float AlgorithmSurfaceGenerateInflated::getSubAlgorithmWeight() { /* * If you use a subalgorithm */ return AlgorithmSurfaceInflation::getAlgorithmWeight() * 7;//7 cycles in total - may deserve factoring in default number of iterations } workbench-1.1.1/src/Algorithms/AlgorithmSurfaceGenerateInflated.h000066400000000000000000000041061255417355300251410ustar00rootroot00000000000000#ifndef __ALGORITHM_SURFACE_GENERATE_INFLATED_H__ #define __ALGORITHM_SURFACE_GENERATE_INFLATED_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class SurfaceFile; class AlgorithmSurfaceGenerateInflated : public AbstractAlgorithm { private: AlgorithmSurfaceGenerateInflated(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmSurfaceGenerateInflated(ProgressObject* myProgObj, const SurfaceFile* anatomicalSurfaceFile, SurfaceFile* inflatedSurfaceFileOut, SurfaceFile* veryInflatedSurfaceFileOut, const float iterationsScale); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmSurfaceGenerateInflated; } // namespace #endif //__ALGORITHM_SURFACE_GENERATE_INFLATED_H__ workbench-1.1.1/src/Algorithms/AlgorithmSurfaceInflation.cxx000066400000000000000000000166351255417355300242500ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "AlgorithmSurfaceInflation.h" #include "AlgorithmSurfaceSmoothing.h" #include "AlgorithmException.h" #include "BoundingBox.h" #include "CaretAssert.h" #include "CaretLogger.h" #include "SurfaceFile.h" using namespace caret; /** * \class caret::AlgorithmSurfaceInflation * \brief SURFACE INFLATION * * Perfom surface inflation. */ /** * @return Command line switch */ AString AlgorithmSurfaceInflation::getCommandSwitch() { return "-surface-inflation"; } /** * @return Short description of algorithm */ AString AlgorithmSurfaceInflation::getShortDescription() { return "SURFACE INFLATION"; } /** * @return Parameters for algorithm */ OperationParameters* AlgorithmSurfaceInflation::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceParameter(1, "anatomical-surface-in", "the anatomical surface"); ret->addSurfaceParameter(2, "surface-in", "the surface file to inflate"); ret->addIntegerParameter(3, "number-of-smoothing-cycles", "number of smoothing cycles"); ret->addDoubleParameter(4, "smoothing-strength", "smoothing strength (ranges [0.0 - 1.0])"); ret->addIntegerParameter(5, "smoothing-iterations", "smoothing iterations"); ret->addDoubleParameter(6, "inflation-factor", "inflation factor"); ret->addSurfaceOutputParameter(7, "surface-out", "output surface file"); AString helpText = ("Inflate a surface by performing cycles that consist of smoothing " " followed by inflation (to correct shrinkage caused by smoothing)."); ret->setHelpText(helpText); return ret; } /** * Use Parameters and perform algorithm * @param myParams * Parameters for algorithm * @param myProgObj * The progress object * @throws * AlgorithmException if errors */ void AlgorithmSurfaceInflation::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { SurfaceFile* anatomicalSurfaceIn = myParams->getSurface(1); SurfaceFile* surfaceIn = myParams->getSurface(2); const int32_t cycles = myParams->getInteger(3); const float strength = myParams->getDouble(4); const int32_t iterations = myParams->getInteger(5); const float inflationFactor = myParams->getDouble(6); SurfaceFile* surfaceOut = myParams->getOutputSurface(7); /* * Constructs and executes the algorithm */ AlgorithmSurfaceInflation(myProgObj, anatomicalSurfaceIn, surfaceIn, surfaceOut, cycles, strength, iterations, inflationFactor); } /** * Constructor * * Calling the constructor will execute the algorithm * * @param myProgObj * Parameters for algorithm */ AlgorithmSurfaceInflation::AlgorithmSurfaceInflation(ProgressObject* myProgObj, const SurfaceFile* anatomicalSurfaceFile, const SurfaceFile* inputSurfaceFile, SurfaceFile* outputSurfaceFile, const int32_t cycles, const float strength, const int32_t iterations, const float inflationFactorIn) : AbstractAlgorithm(myProgObj) { std::vector subAlgProgress; if (myProgObj != NULL) { subAlgProgress.resize(cycles); for (int32_t i = 0; i < cycles; ++i) { subAlgProgress[i] = myProgObj->addAlgorithm(AlgorithmSurfaceSmoothing::getAlgorithmWeight()); } } /* * Sets the algorithm up to use the progress object, and will * finish the progress object automatically when the algorithm terminates */ LevelProgress myProgress(myProgObj, 1.0f, 0.1f);//lower the internal weight const float inflationFactor = inflationFactorIn - 1.0; *outputSurfaceFile = *inputSurfaceFile; outputSurfaceFile->translateToCenterOfMass(); const BoundingBox* anatomicalBoundingBox = anatomicalSurfaceFile->getBoundingBox(); const float anatomicalRangeX = anatomicalBoundingBox->getDifferenceX(); const float anatomicalRangeY = anatomicalBoundingBox->getDifferenceY(); const float anatomicalRangeZ = anatomicalBoundingBox->getDifferenceZ(); const int32_t numberOfNodes = outputSurfaceFile->getNumberOfNodes(); for (int iCycle = 0; iCycle < cycles; iCycle++) { /* * Smooth */ ProgressObject* subProgress = NULL; if (myProgObj != NULL) { subProgress = subAlgProgress[iCycle]; } AlgorithmSurfaceSmoothing(subProgress, outputSurfaceFile, outputSurfaceFile, strength, iterations); /* * Inflate */ float xyz[3]; for (int32_t iNode = 0; iNode < numberOfNodes; iNode++) { outputSurfaceFile->getCoordinate(iNode, xyz); const float x = xyz[0] / anatomicalRangeX; const float y = xyz[1] / anatomicalRangeY; const float z = xyz[2] / anatomicalRangeZ; const float radius = std::sqrt(x*x + y*y + z*z); const float scale = 1.0 + inflationFactor * (1.0 - radius); xyz[0] *= scale; xyz[1] *= scale; xyz[2] *= scale; outputSurfaceFile->setCoordinate(iNode, xyz); } myProgress.reportProgress(static_cast(iCycle +1) / static_cast(cycles)); } outputSurfaceFile->computeNormals(); } /** * @return Algorithm internal weight */ float AlgorithmSurfaceInflation::getAlgorithmInternalWeight() { /* * override this if needed, if the progress bar isn't smooth */ return 0.1f;//all this does internally is rescale the coordinates, lower the internal weight } /** * @return Algorithm sub-algorithm weight */ float AlgorithmSurfaceInflation::getSubAlgorithmWeight() { /* * If you use a subalgorithm */ return AlgorithmSurfaceSmoothing::getAlgorithmWeight(); } workbench-1.1.1/src/Algorithms/AlgorithmSurfaceInflation.h000066400000000000000000000041671255417355300236720ustar00rootroot00000000000000#ifndef __ALGORITHM_SURFACE_INFLATION_H__ #define __ALGORITHM_SURFACE_INFLATION_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmSurfaceInflation : public AbstractAlgorithm { private: AlgorithmSurfaceInflation(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmSurfaceInflation(ProgressObject* myProgObj, const SurfaceFile* anatomicalSurfaceFile, const SurfaceFile* inputSurfaceFile, SurfaceFile* outputSurfaceFile, const int32_t cycles, const float strength, const int32_t iterations, const float inflationFactorIn); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmSurfaceInflation; } // namespace #endif //__ALGORITHM_SURFACE_INFLATION_H__ workbench-1.1.1/src/Algorithms/AlgorithmSurfaceMatch.cxx000066400000000000000000000124731255417355300233550ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretAssert.h" #include "CaretLogger.h" #include "AlgorithmSurfaceMatch.h" #include "AlgorithmException.h" #include "DataFileException.h" #include "SurfaceFile.h" using namespace caret; /** * \class caret::AlgorithmSurfaceMatch * \brief surface match * * */ /** * @return Command line switch */ AString AlgorithmSurfaceMatch::getCommandSwitch() { return "-surface-match"; } /** * @return Short description of algorithm */ AString AlgorithmSurfaceMatch::getShortDescription() { return "SURFACE MATCH"; } /** * @return Parameters for algorithm */ OperationParameters* AlgorithmSurfaceMatch::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceParameter(1, "Match Surface File", "Match (Reference) Surface"); ret->addSurfaceParameter(2, "Input Surface File", "File containing surface that will be transformed"); ret->addStringParameter(3, "Output Surface Name", "Surface File after transformation"); AString helpText = ("The Input Surface File will be transformed so that its coordinate " "ranges (bounding box) match that of the Match Surface File"); ret->setHelpText(helpText); return ret; } /** * Use Parameters and perform algorithm * @param myParams * Parameters for algorithm * @param myProgObj * The progress object * @throws * AlgorithmException if errors */ void AlgorithmSurfaceMatch::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { try { SurfaceFile* matchSurfaceFile = myParams->getSurface(1); SurfaceFile* surfaceFile = myParams->getSurface(2); const AString outputSurfaceFileName = myParams->getString(3); /* * Example parameter processing: * * Gets the surface with key 1 * SurfaceFile* mySurf = myParams->getSurface(1); * * Gets the output metric with key 2 * MetricFile* myMetricOut = myParams->getOutputMetric(2); * * Gets optional parameter with key 3 * OptionalParameter* columnSelect = myParams->getOptionalParameter(3); * int columnNum = -1; * if (columnSelect->m_present) { * columnNum = (int)myMetric->getMapIndexFromNameOrNumber(columnSelect->getString(1)); * if (columnNum < 0) { * throw AlgorithmException("invalid column specified"); * } * } */ /* * Constructs and executes the algorithm */ AlgorithmSurfaceMatch(myProgObj, matchSurfaceFile, surfaceFile); surfaceFile->writeFile(outputSurfaceFileName); } catch (const DataFileException& dfe) { throw AlgorithmException(dfe); } } /** * Constructor * * Calling the constructor will execute the algorithm * * @param myProgObj * Parameters for algorithm */ AlgorithmSurfaceMatch::AlgorithmSurfaceMatch(ProgressObject* myProgObj, const SurfaceFile* matchSurfaceFile, SurfaceFile* surfaceFile) : AbstractAlgorithm(myProgObj) { /* * Uncomment these if you use another algorithm inside here * * ProgressObject* subAlgProgress1 = NULL; * if (myProgObj != NULL) { * subAlgProgress1 = myProgObj->addAlgorithm(AlgorithmInsertNameHere::getAlgorithmWeight()); * } */ /* * Sets the algorithm up to use the progress object, and will * finish the progress object automatically when the algorithm terminates */ LevelProgress myProgress(myProgObj); surfaceFile->matchSurfaceBoundingBox(matchSurfaceFile); /* * How you say you are halfway done with the INTERNAL work of the algorithm * will report finished automatically when this function ends (myProgress goes out of scope, destructor triggers finish */ //myProgress.reportProgress(0.5f); } /** * @return Algorithm internal weight */ float AlgorithmSurfaceMatch::getAlgorithmInternalWeight() { /* * override this if needed, if the progress bar isn't smooth */ return 1.0f; } /** * @return Algorithm sub-algorithm weight */ float AlgorithmSurfaceMatch::getSubAlgorithmWeight() { /* * If you use a subalgorithm */ //return AlgorithmInsertNameHere::getAlgorithmWeight() return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmSurfaceMatch.h000066400000000000000000000034211255417355300227730ustar00rootroot00000000000000#ifndef __ALGORITHM_SURFACE_MATCH_H__ #define __ALGORITHM_SURFACE_MATCH_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmSurfaceMatch : public AbstractAlgorithm { private: AlgorithmSurfaceMatch(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmSurfaceMatch(ProgressObject* myProgObj, const SurfaceFile* matchSurfaceFile, SurfaceFile* surfaceFile); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmSurfaceMatch; } // namespace #endif //__ALGORITHM_SURFACE_MATCH_H__ workbench-1.1.1/src/Algorithms/AlgorithmSurfaceModifySphere.cxx000066400000000000000000000135541255417355300247200ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmSurfaceModifySphere.h" #include "AlgorithmException.h" #include "BoundingBox.h" #include "CaretLogger.h" #include "SurfaceFile.h" #include "Vector3D.h" using namespace caret; using namespace std; AString AlgorithmSurfaceModifySphere::getCommandSwitch() { return "-surface-modify-sphere"; } AString AlgorithmSurfaceModifySphere::getShortDescription() { return "CHANGE RADIUS AND OPTIONALLY RECENTER A SPHERE"; } OperationParameters* AlgorithmSurfaceModifySphere::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceParameter(1, "sphere-in", "the sphere to modify"); ret->addDoubleParameter(2, "radius", "the radius the output sphere should have"); ret->addSurfaceOutputParameter(3, "sphere-out", "the output sphere"); ret->createOptionalParameter(4, "-recenter", "recenter the sphere by means of the bounding box"); //TODO: add option to supress warnings? ret->setHelpText( AString("This command may be useful if you have used -surface-resample to resample a sphere, which can suffer from problems generally not present in ") + "-surface-sphere-project-unproject. If the sphere should already be centered around the origin, using -recenter may still shift it slightly before changing the radius, " + "which is likely to be undesireable.\n\n" + "If is not close to spherical, or not centered around the origin and -recenter is not used, a warning is printed." ); return ret; } void AlgorithmSurfaceModifySphere::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { SurfaceFile* mySphere = myParams->getSurface(1); float newRadius = (float)myParams->getDouble(2); SurfaceFile* outSphere = myParams->getOutputSurface(3); bool recenter = myParams->getOptionalParameter(4)->m_present; AlgorithmSurfaceModifySphere(myProgObj, mySphere, newRadius, outSphere, recenter); } AlgorithmSurfaceModifySphere::AlgorithmSurfaceModifySphere(ProgressObject* myProgObj, const SurfaceFile* mySphere, const float& newRadius, SurfaceFile* outSphere, const bool& recenter) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); bool originVertexWarned = false, nanWarned = false; Vector3D center;//initializes to the origin if (recenter) { const BoundingBox* mybb = mySphere->getBoundingBox(); mybb->getCenter(center); } int numNodes = mySphere->getNumberOfNodes(); CaretAssert(numNodes > 1); int numNodes3 = numNodes * 3; const float* coordData = mySphere->getCoordinateData(); float mindist = 0.0f, maxdist = 0.0f; bool first = true; const float TOLERANCE = 1.05f;//5% is a very loose tolerance, we expect some odd spheres *outSphere = *mySphere; vector outCoords(numNodes3); for (int i = 0; i < numNodes3; i += 3) { Vector3D recenterCoord = Vector3D(coordData + i) - center; float tempf = recenterCoord.length(); if (tempf == 0.0f) { outCoords[i] = newRadius; outCoords[i + 1] = 0.0f; outCoords[i + 2] = 0.0f; if (!originVertexWarned) { CaretLogWarning("found at least one vertex at origin, moved to +x position"); originVertexWarned = true; } } else if (tempf != tempf) { outCoords[i] = newRadius; outCoords[i + 1] = 0.0f; outCoords[i + 2] = 0.0f; if (!nanWarned) { CaretLogWarning("found at least one NaN vertex, moved to +x position"); nanWarned = true; } } else { Vector3D outCoord = recenterCoord * (newRadius / tempf); outCoords[i] = outCoord[0]; outCoords[i + 1] = outCoord[1]; outCoords[i + 2] = outCoord[2]; if (first) { first = false; mindist = tempf; maxdist = tempf; } else { if (tempf < mindist) { mindist = tempf; } if (tempf > maxdist) { maxdist = tempf; } } } } if (first) { throw AlgorithmException("all vertices were located at the origin, aborting"); } else { if (mindist * TOLERANCE < maxdist) { if (recenter) { CaretLogWarning("input sphere is unusually irregular, inspect the input"); } else { CaretLogWarning("input sphere is either unusually irregular or not centered, inspect the input"); } } } outSphere->setCoordinates(outCoords.data()); } float AlgorithmSurfaceModifySphere::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmSurfaceModifySphere::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmSurfaceModifySphere.h000066400000000000000000000033721255417355300243420ustar00rootroot00000000000000#ifndef __ALGORITHM_SURFACE_MODIFY_SPHERE_H__ #define __ALGORITHM_SURFACE_MODIFY_SPHERE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmSurfaceModifySphere : public AbstractAlgorithm { AlgorithmSurfaceModifySphere(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmSurfaceModifySphere(ProgressObject* myProgObj, const SurfaceFile* mySphere, const float& newRadius, SurfaceFile* outSphere, const bool& recenter = false); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmSurfaceModifySphere; } #endif //__ALGORITHM_SURFACE_MODIFY_SPHERE_H__ workbench-1.1.1/src/Algorithms/AlgorithmSurfaceResample.cxx000066400000000000000000000214571255417355300240730ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmSurfaceResample.h" #include "AlgorithmException.h" #include "CaretLogger.h" #include "GiftiMetaData.h" #include "MetricFile.h" #include "SurfaceFile.h" #include "SurfaceResamplingHelper.h" using namespace caret; using namespace std; AString AlgorithmSurfaceResample::getCommandSwitch() { return "-surface-resample"; } AString AlgorithmSurfaceResample::getShortDescription() { return "RESAMPLE A SURFACE TO A DIFFERENT MESH"; } OperationParameters* AlgorithmSurfaceResample::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceParameter(1, "surface-in", "the surface file to resample"); ret->addSurfaceParameter(2, "current-sphere", "a sphere surface with the mesh that the input surface is currently on"); ret->addSurfaceParameter(3, "new-sphere", "a sphere surface that is in register with and has the desired output mesh"); ret->addStringParameter(4, "method", "the method name"); ret->addSurfaceOutputParameter(5, "surface-out", "the output surface file"); OptionalParameter* areaSurfsOpt = ret->createOptionalParameter(6, "-area-surfs", "specify surfaces to do vertex area correction based on"); areaSurfsOpt->addSurfaceParameter(1, "current-area", "a relevant surface with mesh"); areaSurfsOpt->addSurfaceParameter(2, "new-area", "a relevant surface with mesh"); OptionalParameter* areaMetricsOpt = ret->createOptionalParameter(7, "-area-metrics", "specify vertex area metrics to do area correction based on"); areaMetricsOpt->addMetricParameter(1, "current-area", "a metric file with vertex areas for mesh"); areaMetricsOpt->addMetricParameter(2, "new-area", "a metric file with vertex areas for mesh"); AString myHelpText = AString("Resamples a surface file, given two spherical surfaces that are in register. ") + "If the method does area correction, exactly one of -area-surfs or -area-metrics must be specified. " + "This option is not used in normal circumstances, but is provided for completeness.\n\n" + "The argument must be one of the following:\n\n"; vector allEnums; SurfaceResamplingMethodEnum::getAllEnums(allEnums); for (int i = 0; i < (int)allEnums.size(); ++i) { myHelpText += SurfaceResamplingMethodEnum::toName(allEnums[i]) + "\n"; } myHelpText += AString("\nThe BARYCENTRIC method is recommended for anatomical surfaces, unless they are fairly rough, in order to minimize smoothing.\n\n") + "For cut surfaces (including flatmaps), use -surface-cut-resample.\n\n" + "Instead of resampling a spherical surface, the -surface-sphere-project-unproject command is recommended."; ret->setHelpText(myHelpText); return ret; } void AlgorithmSurfaceResample::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { SurfaceFile* surfaceIn = myParams->getSurface(1); SurfaceFile* curSphere = myParams->getSurface(2); SurfaceFile* newSphere = myParams->getSurface(3); bool ok = false; SurfaceResamplingMethodEnum::Enum myMethod = SurfaceResamplingMethodEnum::fromName(myParams->getString(4), &ok); if (!ok) { throw AlgorithmException("invalid method name"); } SurfaceFile* surfaceOut = myParams->getOutputSurface(5); MetricFile* curAreas = NULL, *newAreas = NULL; MetricFile curAreasTemp, newAreasTemp; OptionalParameter* areaSurfsOpt = myParams->getOptionalParameter(6); if (areaSurfsOpt->m_present) { switch(myMethod) { case SurfaceResamplingMethodEnum::BARYCENTRIC: CaretLogInfo("This method does not use area correction, -area-surfs is not needed"); break; default: break; } vector nodeAreasTemp; SurfaceFile* curAreaSurf = areaSurfsOpt->getSurface(1); SurfaceFile* newAreaSurf = areaSurfsOpt->getSurface(2); curAreaSurf->computeNodeAreas(nodeAreasTemp); curAreasTemp.setNumberOfNodesAndColumns(curAreaSurf->getNumberOfNodes(), 1); curAreasTemp.setValuesForColumn(0, nodeAreasTemp.data()); curAreas = &curAreasTemp; newAreaSurf->computeNodeAreas(nodeAreasTemp); newAreasTemp.setNumberOfNodesAndColumns(newAreaSurf->getNumberOfNodes(), 1); newAreasTemp.setValuesForColumn(0, nodeAreasTemp.data()); newAreas = &newAreasTemp; } OptionalParameter* areaMetricsOpt = myParams->getOptionalParameter(7); if (areaMetricsOpt->m_present) { if (areaSurfsOpt->m_present) { throw AlgorithmException("only one of -area-surfs and -area-metrics can be specified"); } switch(myMethod) { case SurfaceResamplingMethodEnum::BARYCENTRIC: CaretLogInfo("This method does not use area correction, -area-metrics is not needed"); break; default: break; } curAreas = areaMetricsOpt->getMetric(1); newAreas = areaMetricsOpt->getMetric(2); } AlgorithmSurfaceResample(myProgObj, surfaceIn, curSphere, newSphere, myMethod, surfaceOut, curAreas, newAreas); } AlgorithmSurfaceResample::AlgorithmSurfaceResample(ProgressObject* myProgObj, const SurfaceFile* surfaceIn, const SurfaceFile* curSphere, const SurfaceFile* newSphere, const SurfaceResamplingMethodEnum::Enum& myMethod, SurfaceFile* surfaceOut, const MetricFile* curAreas, const MetricFile* newAreas) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); if (surfaceIn->getNumberOfNodes() != curSphere->getNumberOfNodes()) throw AlgorithmException("input surface has different number of nodes than input sphere"); const float* curAreaData = NULL, *newAreaData = NULL; switch (myMethod) { case SurfaceResamplingMethodEnum::BARYCENTRIC: break; default: if (curAreas == NULL || newAreas == NULL) throw AlgorithmException("specified method does area correction, but no vertex area data given"); if (curSphere->getNumberOfNodes() != curAreas->getNumberOfNodes()) throw AlgorithmException("current vertex area data has different number of nodes than current sphere"); if (newSphere->getNumberOfNodes() != newAreas->getNumberOfNodes()) throw AlgorithmException("new vertex area data has different number of nodes than new sphere"); curAreaData = curAreas->getValuePointerForColumn(0); newAreaData = newAreas->getValuePointerForColumn(0); } int numNewNodes = newSphere->getNumberOfNodes(); GiftiMetaData savedMD; GiftiMetaData* provenanceMD = surfaceOut->getFileMetaData();//save provenance, since we are using operator= on surface as a hack around setting the topology if (provenanceMD != NULL) { savedMD = *provenanceMD;//put it into a local instance, because operator= will probably change the one the pointer refers to } *surfaceOut = *newSphere; provenanceMD = surfaceOut->getFileMetaData();//in case the pointer changes anyway if (provenanceMD != NULL) { *provenanceMD = savedMD;//put it back } surfaceOut->setStructure(newSphere->getStructure()); surfaceOut->setSecondaryType(surfaceIn->getSecondaryType()); surfaceOut->setSurfaceType(surfaceIn->getSurfaceType()); vector coordScratch(numNewNodes * 3, 0.0f); SurfaceResamplingHelper myHelp(myMethod, curSphere, newSphere, curAreaData, newAreaData); myHelp.resample3DCoord(surfaceIn->getCoordinateData(), coordScratch.data()); surfaceOut->setCoordinates(coordScratch.data()); } float AlgorithmSurfaceResample::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmSurfaceResample::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmSurfaceResample.h000066400000000000000000000036501255417355300235130ustar00rootroot00000000000000#ifndef __ALGORITHM_SURFACE_RESAMPLE_H__ #define __ALGORITHM_SURFACE_RESAMPLE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" #include "SurfaceResamplingMethodEnum.h" namespace caret { class AlgorithmSurfaceResample : public AbstractAlgorithm { AlgorithmSurfaceResample(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmSurfaceResample(ProgressObject* myProgObj, const SurfaceFile* surfaceIn, const SurfaceFile* curSphere, const SurfaceFile* newSphere, const SurfaceResamplingMethodEnum::Enum& myMethod, SurfaceFile* surfaceOut, const MetricFile* curAreaSurf = NULL, const MetricFile* newAreaSurf = NULL); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmSurfaceResample; } #endif //__ALGORITHM_SURFACE_RESAMPLE_H__ workbench-1.1.1/src/Algorithms/AlgorithmSurfaceSmoothing.cxx000066400000000000000000000237461255417355300242750ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretAssert.h" #include "CaretLogger.h" #include "AlgorithmSurfaceSmoothing.h" #include "AlgorithmException.h" #include "MathFunctions.h" #include "SurfaceFile.h" #include "TopologyHelper.h" using namespace caret; /** * \class caret::AlgorithmSurfaceSmoothing * \brief SURFACE SMOOTHING * * Smooths a surface. */ /** * @return Command line switch */ AString AlgorithmSurfaceSmoothing::getCommandSwitch() { return "-surface-smoothing"; } /** * @return Short description of algorithm */ AString AlgorithmSurfaceSmoothing::getShortDescription() { return "SURFACE SMOOTHING"; } /** * @return Parameters for algorithm */ OperationParameters* AlgorithmSurfaceSmoothing::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceParameter(1, "surface-in", "the surface file to smooth"); ret->addDoubleParameter(2, "smoothing-strength", "smoothing strength (ranges [0.0 - 1.0])"); ret->addIntegerParameter(3, "smoothing-iterations", "smoothing iterations"); ret->addSurfaceOutputParameter(4, "surface-out", "output surface file"); AString helpText = ("Smooths a surface by averaging nodes with their neighbors."); ret->setHelpText(helpText); return ret; } /** * Use Parameters and perform algorithm * @param myParams * Parameters for algorithm * @param myProgObj * The progress object * @throws * AlgorithmException if errors */ void AlgorithmSurfaceSmoothing::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { SurfaceFile* surfaceIn = myParams->getSurface(1); const float strength = myParams->getDouble(2); const int32_t iterations = myParams->getInteger(3); SurfaceFile* surfaceOut = myParams->getOutputSurface(4); /* * Constructs and executes the algorithm */ AlgorithmSurfaceSmoothing(myProgObj, surfaceIn, surfaceOut, strength, iterations); } /** * Constructor * * Calling the constructor will execute the algorithm * * @param myProgObj * Parameters for algorithm */ AlgorithmSurfaceSmoothing::AlgorithmSurfaceSmoothing(ProgressObject* myProgObj, const SurfaceFile* inputSurfaceFile, SurfaceFile* outputSurfaceFile, const float strength, const int32_t iterations) : AbstractAlgorithm(myProgObj) { if ((strength < 0.0) || (strength > 1.0)) { throw AlgorithmException("Invalid smoothing strength outside [0.0, 1.0]: " + QString::number(strength, 'f', 5)); } if (iterations <= 0) { throw AlgorithmException("Invalid iterations value [1, infinity]: " + QString::number(iterations)); } /* * Sets the algorithm up to use the progress object, and will * finish the progress object automatically when the algorithm terminates */ LevelProgress myProgress(myProgObj); *outputSurfaceFile = *inputSurfaceFile; CaretPointer myTopoHelp = outputSurfaceFile->getTopologyHelper(true); const int32_t numNodes = outputSurfaceFile->getNumberOfNodes(); if (numNodes <= 0) { return; } /* * Storage for coordinates, input and output of each iteration */ std::vector coordsIn(numNodes * 3); std::vector coordsOut(numNodes * 3); /* * Copy coordinates from surface */ for (int32_t i = 0; i < numNodes; i++) { const float* xyz = outputSurfaceFile->getCoordinate(i); const int32_t i3 = i * 3; coordsIn[i3] = xyz[0]; coordsIn[i3+1] = xyz[1]; coordsIn[i3+2] = xyz[2]; coordsOut[i3] = xyz[0]; coordsOut[i3+1] = xyz[1]; coordsOut[i3+2] = xyz[2]; } std::vector triangleAreas(100); std::vector triangleCenters(100*3); const float inverseStrength = 1.0 - strength; /* * Perform the requested number of iterations */ for (int32_t iter = 1; iter <= iterations; iter++) { /* * Copy coordinates from previous iteration */ if (iter > 1) { coordsIn = coordsOut; } /* * Process each node */ for (int32_t iNode = 0; iNode < numNodes; iNode++) { /* * Get node's neighbors */ int32_t numNeighbors = 0; const int32_t* neighbors = myTopoHelp->getNodeNeighbors(iNode, numNeighbors); if (numNeighbors < 2) { coordsOut[iNode*3] = coordsIn[iNode*3]; coordsOut[iNode*3+1] = coordsIn[iNode*3+1]; coordsOut[iNode*3+2] = coordsIn[iNode*3+2]; } else { /* * Ensure adequate space for triangle areas and center coordinate */ if (numNeighbors > static_cast(triangleAreas.size())) { triangleAreas.resize(numNeighbors); triangleCenters.resize(numNeighbors * 3); } double totalArea = 0.0; /* * Average node with its neighbors */ for (int jn = 0; jn < numNeighbors; jn++) { /* * Get two consecutive neighbors */ const int32_t n1 = neighbors[jn]; int nextNeighborIndex = jn + 1; if (nextNeighborIndex >= numNeighbors) { nextNeighborIndex = 0; } const int32_t n2 = neighbors[nextNeighborIndex]; /* * Coordinates of nodes and neighbors */ const float* c1 = &coordsIn[iNode*3]; const float* c2 = &coordsIn[n1*3]; const float* c3 = &coordsIn[n2*3]; const float area = MathFunctions::triangleArea(c1, c2, c3); /* * Area of triangle formed by node and neighbors */ triangleAreas[jn] = area; totalArea += area; /* * Average of nodes that form triangle */ for (int32_t k = 0; k < 3; k++) { triangleCenters[jn*3+k] = (c1[k] + c2[k] + c3[k]) / 3.0; } } /* * Influence of neighbors */ float neighborAverageX = 0.0; float neighborAverageY = 0.0; float neighborAverageZ = 0.0; for (int j = 0; j < numNeighbors; j++) { if (triangleAreas[j] > 0.0) { const float weight = triangleAreas[j] / totalArea; neighborAverageX += (weight * triangleCenters[j*3]); neighborAverageY += (weight * triangleCenters[j*3+1]); neighborAverageZ += (weight * triangleCenters[j*3+2]); } } /* * Update coordinates */ coordsOut[iNode*3] = ((coordsIn[iNode*3] * inverseStrength) + (neighborAverageX * strength)); coordsOut[iNode*3+1] = ((coordsIn[iNode*3+1] * inverseStrength) + (neighborAverageY * strength)); coordsOut[iNode*3+2] = ((coordsIn[iNode*3+2] * inverseStrength) + (neighborAverageZ * strength)); } } /* * Update progress */ const float percentDone = (static_cast(iter) / static_cast(iterations)); myProgress.reportProgress(percentDone);//give continuous updates, if it slows things down we can reduce the resolution in the progress framework } /* * Copy coordinates into surface */ outputSurfaceFile->setCoordinates(&coordsOut[0]); myProgress.reportProgress(1.0f); } /** * @return Algorithm internal weight */ float AlgorithmSurfaceSmoothing::getAlgorithmInternalWeight() { /* * override this if needed, if the progress bar isn't smooth */ return 1.0f; } /** * @return Algorithm sub-algorithm weight */ float AlgorithmSurfaceSmoothing::getSubAlgorithmWeight() { /* * If you use a subalgorithm */ //return AlgorithmInsertNameHere::getAlgorithmWeight() return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmSurfaceSmoothing.h000066400000000000000000000036631255417355300237160ustar00rootroot00000000000000#ifndef __ALGORITHM_SURFACE_SMOOTHING_H__ #define __ALGORITHM_SURFACE_SMOOTHING_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmSurfaceSmoothing : public AbstractAlgorithm { private: AlgorithmSurfaceSmoothing(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmSurfaceSmoothing(ProgressObject* myProgObj, const SurfaceFile* inputSurfaceFile, SurfaceFile* outputSurfaceFile, const float strength, const int32_t iterations); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmSurfaceSmoothing; } // namespace #endif //__ALGORITHM_SURFACE_SMOOTHING_H__ workbench-1.1.1/src/Algorithms/AlgorithmSurfaceSphereProjectUnproject.cxx000066400000000000000000000151531255417355300267660ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmSurfaceSphereProjectUnproject.h" #include "AlgorithmException.h" #include "CaretOMP.h" #include "SignedDistanceHelper.h" #include "SurfaceFile.h" #include "Vector3D.h" #include using namespace caret; using namespace std; AString AlgorithmSurfaceSphereProjectUnproject::getCommandSwitch() { return "-surface-sphere-project-unproject"; } AString AlgorithmSurfaceSphereProjectUnproject::getShortDescription() { return "DEFORM A SPHERE ACCORDING TO A REGISTRATION"; } OperationParameters* AlgorithmSurfaceSphereProjectUnproject::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceParameter(1, "sphere-in", "the sphere with the desired output mesh"); ret->addSurfaceParameter(2, "sphere-project-to", "a sphere that aligns with sphere-in"); ret->addSurfaceParameter(3, "sphere-unproject-from", "sphere-project-to deformed to the output space"); ret->addSurfaceOutputParameter(4, "sphere-out", "the output sphere"); ret->setHelpText( AString("Each vertex of is projected to to obtain barycentric weights, which are then used to unproject ") + "from . This results in a sphere with the topology of , but coordinates shifted by the deformation between " + " and . and must have the same topology as each other, " + "but may have different topology." ); return ret; } void AlgorithmSurfaceSphereProjectUnproject::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { SurfaceFile* sphereIn = myParams->getSurface(1); SurfaceFile* projectSphere = myParams->getSurface(2); SurfaceFile* unprojectSphere = myParams->getSurface(3); SurfaceFile* sphereOut = myParams->getOutputSurface(4); AlgorithmSurfaceSphereProjectUnproject(myProgObj, sphereIn, projectSphere, unprojectSphere, sphereOut);//executes the algorithm } AlgorithmSurfaceSphereProjectUnproject::AlgorithmSurfaceSphereProjectUnproject(ProgressObject* myProgObj, const SurfaceFile* sphereIn, const SurfaceFile* projectSphere, const SurfaceFile* unprojectSphere, SurfaceFile* sphereOut) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); if (!projectSphere->hasNodeCorrespondence(*unprojectSphere)) throw AlgorithmException("projection sphere and unprojection sphere do not have vertex correspondence"); if (!checkSphere(sphereIn) || !checkSphere(projectSphere) || !checkSphere(unprojectSphere)) throw AlgorithmException("all inputs must be spheres centered around the origin"); SurfaceFile inMod = *sphereIn, projectMod = *projectSphere, unprojectMod = *unprojectSphere; changeRadius(100.0f, &inMod); changeRadius(100.0f, &projectMod); changeRadius(100.0f, &unprojectMod); const float* unprojectCoords = unprojectMod.getCoordinateData(); const float* inCoords = inMod.getCoordinateData(); int numNodes = sphereIn->getNumberOfNodes(); vector outCoords(numNodes * 3); *sphereOut = *sphereIn; sphereOut->setStructure(unprojectSphere->getStructure()); CaretPointer myHelper = projectMod.getSignedDistanceHelper(); for (int i = 0; i < numNodes; ++i) { int i3 = i * 3; BarycentricInfo myInfo; myHelper->barycentricWeights(inCoords + i3, myInfo); Vector3D outCoord = myInfo.baryWeights[0] * Vector3D(unprojectCoords + myInfo.nodes[0] * 3) + myInfo.baryWeights[1] * Vector3D(unprojectCoords + myInfo.nodes[1] * 3) + myInfo.baryWeights[2] * Vector3D(unprojectCoords + myInfo.nodes[2] * 3); outCoords[i3] = outCoord[0]; outCoords[i3 + 1] = outCoord[1]; outCoords[i3 + 2] = outCoord[2]; } sphereOut->setCoordinates(outCoords.data()); changeRadius(100.0f, sphereOut); } bool AlgorithmSurfaceSphereProjectUnproject::checkSphere(const SurfaceFile* surface) { int numNodes = surface->getNumberOfNodes(); CaretAssert(numNodes > 1); int numNodes3 = numNodes * 3; const float* coordData = surface->getCoordinateData(); float mindist = Vector3D(coordData).length(); if (mindist != mindist) throw CaretException("found NaN coordinate in an input sphere"); float maxdist = mindist; const float TOLERANCE = 1.001f; for (int i = 3; i < numNodes3; i += 3) { float tempf = Vector3D(coordData + i).length(); if (tempf != tempf) throw CaretException("found NaN coordinate in an input sphere"); if (tempf < mindist) { mindist = tempf; } if (tempf > maxdist) { maxdist = tempf; } } return (mindist * TOLERANCE > maxdist); } void AlgorithmSurfaceSphereProjectUnproject::changeRadius(const float& radius, SurfaceFile* inout) { int numNodes = inout->getNumberOfNodes(); int numNodes3 = numNodes * 3; vector newCoordData(numNodes3); const float* oldCoordData = inout->getCoordinateData(); #pragma omp CARET_PARFOR schedule(dynamic) for (int i = 0; i < numNodes3; i += 3) { Vector3D tempvec = Vector3D(oldCoordData + i).normal() * radius; newCoordData[i] = tempvec[0]; newCoordData[i + 1] = tempvec[1]; newCoordData[i + 2] = tempvec[2]; } inout->setCoordinates(newCoordData.data()); } float AlgorithmSurfaceSphereProjectUnproject::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmSurfaceSphereProjectUnproject::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmSurfaceSphereProjectUnproject.h000066400000000000000000000040061255417355300264060ustar00rootroot00000000000000#ifndef __ALGORITHM_SURFACE_SPHERE_PROJECT_UNPROJECT_H__ #define __ALGORITHM_SURFACE_SPHERE_PROJECT_UNPROJECT_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmSurfaceSphereProjectUnproject : public AbstractAlgorithm { AlgorithmSurfaceSphereProjectUnproject(); bool checkSphere(const SurfaceFile* surface); void changeRadius(const float& radius, SurfaceFile* inout); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmSurfaceSphereProjectUnproject(ProgressObject* myProgObj, const SurfaceFile* sphereIn, const SurfaceFile* projectSphere, const SurfaceFile* unprojectSphere, SurfaceFile* sphereOut); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmSurfaceSphereProjectUnproject; } #endif //__ALGORITHM_SURFACE_SPHERE_PROJECT_UNPROJECT_H__ workbench-1.1.1/src/Algorithms/AlgorithmSurfaceToSurface3dDistance.cxx000066400000000000000000000105451255417355300261140ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmSurfaceToSurface3dDistance.h" #include "AlgorithmException.h" #include "MetricFile.h" #include "SurfaceFile.h" #include "Vector3D.h" using namespace caret; using namespace std; AString AlgorithmSurfaceToSurface3dDistance::getCommandSwitch() { return "-surface-to-surface-3d-distance"; } AString AlgorithmSurfaceToSurface3dDistance::getShortDescription() { return "COMPUTE DISTANCE BETWEEN CORRESPONDING VERTICES"; } OperationParameters* AlgorithmSurfaceToSurface3dDistance::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceParameter(1, "surface-comp", "the surface to compare to the reference"); ret->addSurfaceParameter(2, "surface-ref", "the surface to use as the reference"); ret->addMetricOutputParameter(3, "dists-out", "the output distances"); OptionalParameter* vectorOpt = ret->createOptionalParameter(4, "-vectors", "output the displacement vectors"); vectorOpt->addMetricOutputParameter(1, "vectors-out", "the output vectors"); ret->setHelpText( AString("Computes the vector difference between the vertices of each surface with the same index, as (comp - ref), ") + "and output the magnitudes, and optionally the displacement vectors." ); return ret; } void AlgorithmSurfaceToSurface3dDistance::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { SurfaceFile* myCompSurf = myParams->getSurface(1); SurfaceFile* myRefSurf = myParams->getSurface(2); MetricFile* distsOut = myParams->getOutputMetric(3); MetricFile* vectorsOut = NULL; OptionalParameter* vectorOpt = myParams->getOptionalParameter(4); if (vectorOpt->m_present) { vectorsOut = vectorOpt->getOutputMetric(1); } AlgorithmSurfaceToSurface3dDistance(myProgObj, myCompSurf, myRefSurf, distsOut, vectorsOut); } AlgorithmSurfaceToSurface3dDistance::AlgorithmSurfaceToSurface3dDistance(ProgressObject* myProgObj, const SurfaceFile* myCompSurf, const SurfaceFile* myRefSurf, MetricFile* distsOut, MetricFile* vectorsOut) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); if (!myCompSurf->hasNodeCorrespondence(*myRefSurf)) { throw AlgorithmException("input surfaces must have node correspondence"); } int numNodes = myCompSurf->getNumberOfNodes(); distsOut->setNumberOfNodesAndColumns(numNodes, 1); distsOut->setStructure(myCompSurf->getStructure()); distsOut->setMapName(0, "distance"); if (vectorsOut != NULL) { vectorsOut->setNumberOfNodesAndColumns(numNodes, 3); vectorsOut->setStructure(myCompSurf->getStructure()); vectorsOut->setMapName(0, "x displacement"); vectorsOut->setMapName(1, "y displacement"); vectorsOut->setMapName(2, "z displacement"); } for (int i = 0; i < numNodes; ++i) { Vector3D ref = myRefSurf->getCoordinate(i); Vector3D comp = myCompSurf->getCoordinate(i); Vector3D diff = comp - ref; distsOut->setValue(i, 0, diff.length()); if (vectorsOut != NULL) { vectorsOut->setValue(i, 0, diff[0]); vectorsOut->setValue(i, 1, diff[1]); vectorsOut->setValue(i, 2, diff[2]); } } } float AlgorithmSurfaceToSurface3dDistance::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmSurfaceToSurface3dDistance::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmSurfaceToSurface3dDistance.h000066400000000000000000000034771255417355300255470ustar00rootroot00000000000000#ifndef __ALGORITHM_SURFACE_TO_SURFACE_3D_DISTANCE_H__ #define __ALGORITHM_SURFACE_TO_SURFACE_3D_DISTANCE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmSurfaceToSurface3dDistance : public AbstractAlgorithm { AlgorithmSurfaceToSurface3dDistance(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmSurfaceToSurface3dDistance(ProgressObject* myProgObj, const SurfaceFile* myCompSurf, const SurfaceFile* myRefSurf, MetricFile* distsOut, MetricFile* vectorsOut = NULL); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmSurfaceToSurface3dDistance; } #endif //__ALGORITHM_SURFACE_TO_SURFACE_3D_DISTANCE_H__ workbench-1.1.1/src/Algorithms/AlgorithmSurfaceWedgeVolume.cxx000066400000000000000000000127731255417355300245470ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmSurfaceWedgeVolume.h" #include "AlgorithmException.h" #include "MetricFile.h" #include "SurfaceFile.h" #include "Vector3D.h" #include using namespace caret; using namespace std; AString AlgorithmSurfaceWedgeVolume::getCommandSwitch() { return "-surface-wedge-volume"; } AString AlgorithmSurfaceWedgeVolume::getShortDescription() { return "MEASURE PER-VERTEX VOLUME BETWEEN SURFACES"; } OperationParameters* AlgorithmSurfaceWedgeVolume::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceParameter(1, "inner-surface", "the inner surface"); ret->addSurfaceParameter(2, "outer-surface", "the outer surface"); ret->addMetricOutputParameter(3, "metric", "the output metric"); ret->setHelpText( AString("Compute the volume of each vertex's area from one surface to another. ") + "The surfaces must have node correspondence." ); return ret; } void AlgorithmSurfaceWedgeVolume::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { SurfaceFile* innerSurf = myParams->getSurface(1); SurfaceFile* outerSurf = myParams->getSurface(2); MetricFile* myMetricOut = myParams->getOutputMetric(3); AlgorithmSurfaceWedgeVolume(myProgObj, innerSurf, outerSurf, myMetricOut); } AlgorithmSurfaceWedgeVolume::AlgorithmSurfaceWedgeVolume(ProgressObject* myProgObj, const SurfaceFile* innerSurf, const SurfaceFile* outerSurf, MetricFile* myMetricOut) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); int numNodes = innerSurf->getNumberOfNodes(); if (numNodes != outerSurf->getNumberOfNodes()) { throw AlgorithmException("input surfaces have different number of nodes"); } myMetricOut->setNumberOfNodesAndColumns(numNodes, 1); myMetricOut->setStructure(innerSurf->getStructure()); myMetricOut->setColumnName(0, "per-vertex volume between surfaces"); vector tempCol(numNodes, 0.0f); const float* innerCoords = innerSurf->getCoordinateData(); const float* outerCoords = outerSurf->getCoordinateData(); int numTiles = innerSurf->getNumberOfTriangles(); for (int i = 0; i < numTiles; ++i) { const int32_t* innerTile = innerSurf->getTriangle(i); Vector3D innerPoints[3], outerPoints[3]; for (int j = 0; j < 3; ++j) { innerPoints[j] = innerCoords + innerTile[j] * 3; outerPoints[j] = outerCoords + innerTile[j] * 3; } Vector3D refPoint = innerPoints[0];//use an "origin" for the vector field that is near the polyhedron for numerical stability - also, this makes several sides have zero contribution float volume = 0.0f;//I am ignoring the 1/3 in the vector field and the 1/2 for area of triangle for now, because they apply to everything so we can multiple later //NOTE: the inner surface triangle has zero contribution due to choice of reference point volume += (outerPoints[0] - refPoint).dot((outerPoints[1] - outerPoints[0]).cross(outerPoints[2] - outerPoints[1]));//we must be VERY careful with orientations int m = 2; for (int k = 0; k < 3; ++k)//walk the side quadrilaterals as 2->0, 0->1, 1->2 {//add the average of the two triangulations of the quadrilateral Vector3D mref = innerPoints[m] - refPoint, kref = innerPoints[k] - refPoint;//precalculate reference points on the white surface for use in both volumes Vector3D whiteEdge = innerPoints[k] - innerPoints[m];//precalculate this frequently used edge also if (k != 0 && m != 0) volume += 0.5f * mref.dot(whiteEdge.cross(outerPoints[k] - innerPoints[k]));//don't calculate them when their contribution is 0, due to containing the reference point if (m != 0) volume += 0.5f * mref.dot((outerPoints[m] - innerPoints[m]).cross(innerPoints[m] - outerPoints[k])); if (k != 0 && m != 0) volume += 0.5f * kref.dot(whiteEdge.cross(outerPoints[m] - innerPoints[k])); if (k != 0) volume += 0.5f * kref.dot((outerPoints[k] - innerPoints[k]).cross(outerPoints[m] - outerPoints[k])); m = k; } volume /= 18.0f;//1/3 for vector field, 1/2 for triangle, and 1/3 to split the tile volume among its 3 vertices for (int j = 0; j < 3; ++j) { tempCol[innerTile[j]] += volume; } } myMetricOut->setValuesForColumn(0, tempCol.data()); } float AlgorithmSurfaceWedgeVolume::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmSurfaceWedgeVolume::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmSurfaceWedgeVolume.h000066400000000000000000000033341255417355300241650ustar00rootroot00000000000000#ifndef __ALGORITHM_SURFACE_WEDGE_VOLUME_H__ #define __ALGORITHM_SURFACE_WEDGE_VOLUME_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmSurfaceWedgeVolume : public AbstractAlgorithm { AlgorithmSurfaceWedgeVolume(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmSurfaceWedgeVolume(ProgressObject* myProgObj, const SurfaceFile* innerSurf, const SurfaceFile* outerSurf, MetricFile* myMetricOut); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmSurfaceWedgeVolume; } #endif //__ALGORITHM_SURFACE_WEDGE_VOLUME_H__ workbench-1.1.1/src/Algorithms/AlgorithmTemplate.cxx.txt000066400000000000000000000075001255417355300233740ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmName.h" #include "AlgorithmException.h" using namespace caret; using namespace std; AString AlgorithmName::getCommandSwitch() { return "-command-switch"; } AString AlgorithmName::getShortDescription() { return "SHORT DESCRIPTION"; } OperationParameters* AlgorithmName::getParameters() { OperationParameters* ret = new OperationParameters(); //ret->addSurfaceParameter(1, "surface", "the surface to compute on"); //ret->addMetricOutputParameter(2, "metric-out", "the output metric"); //OptionalParameter* columnSelect = ret->createOptionalParameter(3, "-column", "select a single column"); //columnSelect->addStringParameter(1, "column", "the column number or name"); ret->setHelpText( AString("This is where you set the help text. ") + "DO NOT add the info about what the command line format is, and do not give the command switch, " + "short description, or the short descriptions of parameters. " + "Do not indent, manually break long lines, or format the text in any way " + "other than to separate paragraphs within the help text prose, usually with two newlines." ); return ret; } void AlgorithmName::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { //SurfaceFile* mySurf = myParams->getSurface(1);//gets the surface with key 1 //MetricFile* myMetricOut = myParams->getOutputMetric(2);//gets the output metric with key 2 /*OptionalParameter* columnSelect = myParams->getOptionalParameter(3);//gets optional parameter with key 3 int columnNum = -1; if (columnSelect->m_present) {//set up to use the single column columnNum = (int)myMetric->getMapIndexFromNameOrNumber(columnSelect->getString(1)); if (columnNum < 0) { throw AlgorithmException("invalid column specified"); } }//*/ AlgorithmName(myProgObj /*INSERT PARAMETERS HERE*/);//executes the algorithm } AlgorithmName::AlgorithmName(ProgressObject* myProgObj /*INSERT PARAMETERS HERE*/) : AbstractAlgorithm(myProgObj) { /*ProgressObject* subAlgProgress1 = NULL;//uncomment these if you use another algorithm inside here if (myProgObj != NULL) { subAlgProgress1 = myProgObj->addAlgorithm(AlgorithmInsertNameHere::getAlgorithmWeight()); }//*/ LevelProgress myProgress(myProgObj);//this line sets the algorithm up to use the progress object, and will finish the progress object automatically when the algorithm terminates //myProgress.reportProgress(0.5f);//this is how you say you are halfway done with the INTERNAL work of the algorithm //will report finished automatically when this function ends (myProgress goes out of scope, destructor triggers finish) } float AlgorithmName::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmName::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmTemplate.h.txt000066400000000000000000000054501255417355300230230ustar00rootroot00000000000000#ifndef __ALGORITHM_NAME_H__ #define __ALGORITHM_NAME_H__ /*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ /* file->save as... and enter what you will name the class, plus .h find and replace these strings in plain text mode (not "whole word only"): AlgorithmName : algorithm name, in CamelCase, with initial capital, same as what you saved the header file to ALGORITHM_NAME : uppercase of algorithm name, with underscore between words, used in #ifdef guards next, make AlgorithmName.cxx from AlgorithmTemplate.cxx.txt via one of the following (depending on working directory): cat AlgorithmTemplate.cxx.txt | sed 's/[A]lgorithmName/AlgorithmName/g' > AlgorithmName.cxx cat Algorithms/AlgorithmTemplate.cxx.txt | sed 's/[A]lgorithmName/AlgorithmName/g' > Algorithms/AlgorithmName.cxx cat src/Algorithms/AlgorithmTemplate.cxx.txt | sed 's/[A]lgorithmName/AlgorithmName/g' > src/Algorithms/AlgorithmName.cxx or manually copy and replace next, implement its functions - the algorithm work goes in the CONSTRUCTOR add these into Algorithms/CMakeLists.txt: AlgorithmName.h AlgorithmName.cxx place the following lines into Commands/CommandOperationManager.cxx: #include "AlgorithmName.h" //near the top this->commandOperations.push_back(new CommandParser(new AutoAlgorithmName())); //in CommandOperationManager() finally, remove this block comment */ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmName : public AbstractAlgorithm { AlgorithmName(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmName(ProgressObject* myProgObj /*INSERT PARAMETERS HERE//*/); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmName; } #endif //__ALGORITHM_NAME_H__ workbench-1.1.1/src/Algorithms/AlgorithmVolumeAffineResample.cxx000066400000000000000000000163441255417355300250620ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmVolumeAffineResample.h" #include "AffineFile.h" #include "AlgorithmException.h" #include "CaretLogger.h" #include "CaretOMP.h" #include "Vector3D.h" using namespace caret; using namespace std; AString AlgorithmVolumeAffineResample::getCommandSwitch() { return "-volume-affine-resample"; } AString AlgorithmVolumeAffineResample::getShortDescription() { return "RESAMPLE VOLUME USING AFFINE TRANSFORM"; } OperationParameters* AlgorithmVolumeAffineResample::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addVolumeParameter(1, "volume-in", "volume to resample"); ret->addStringParameter(2, "affine", "the affine file to apply"); ret->addVolumeParameter(3, "volume-space", "a volume file in the volume space you want for the output"); ret->addStringParameter(4, "method", "the resampling method"); ret->addVolumeOutputParameter(5, "volume-out", "the output volume"); OptionalParameter* flirtOpt = ret->createOptionalParameter(6, "-flirt", "MUST be used if affine is a flirt affine"); flirtOpt->addStringParameter(1, "source-volume", "the source volume used when generating the affine"); flirtOpt->addStringParameter(2, "target-volume", "the target volume used when generating the affine"); ret->setHelpText( AString("Resample a volume file with an affine transformation. ") + "The recommended methods are CUBIC (cubic spline) for most data, and ENCLOSING_VOXEL for label data. " "The parameter must be one of:\n\n" + "CUBIC\nENCLOSING_VOXEL\nTRILINEAR" ); return ret; } void AlgorithmVolumeAffineResample::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { VolumeFile* inVol = myParams->getVolume(1); AString affName = myParams->getString(2); VolumeFile* refSpace = myParams->getVolume(3); AString method = myParams->getString(4); VolumeFile* outVol = myParams->getOutputVolume(5); OptionalParameter* flirtOpt = myParams->getOptionalParameter(6); AffineFile myAffine; if (flirtOpt->m_present) { myAffine.readFlirt(affName, flirtOpt->getString(1), flirtOpt->getString(2)); } else { myAffine.readWorld(affName); } VolumeFile::InterpType myMethod = VolumeFile::CUBIC; if (method == "CUBIC") { myMethod = VolumeFile::CUBIC; } else if (method == "TRILINEAR") { myMethod = VolumeFile::TRILINEAR; } else if (method == "ENCLOSING_VOXEL") { myMethod = VolumeFile::ENCLOSING_VOXEL; } else { throw AlgorithmException("unrecognized interpolation method"); } FloatMatrix affMat = FloatMatrix(myAffine.getMatrix()); vector refDims; refSpace->getDimensions(refDims); AlgorithmVolumeAffineResample(myProgObj, inVol, affMat, refDims.data(), refSpace->getSform(), myMethod, outVol); } AlgorithmVolumeAffineResample::AlgorithmVolumeAffineResample(ProgressObject* myProgObj, const VolumeFile* inVol, const FloatMatrix& myAffine, const int64_t refDims[3], const vector >& refSform, const VolumeFile::InterpType& myMethod, VolumeFile* outVol) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); int64_t affRows, affColumns; myAffine.getDimensions(affRows, affColumns); if (affRows < 3 || affRows > 4 || affColumns != 4) throw AlgorithmException("input matrix is not an affine matrix"); vector outDims = inVol->getOriginalDimensions(); if (outDims.size() < 3) throw AlgorithmException("input must have 3 spatial dimensions"); outDims[0] = refDims[0]; outDims[1] = refDims[1]; outDims[2] = refDims[2]; int64_t numMaps = inVol->getNumberOfMaps(), numComponents = inVol->getNumberOfComponents(); outVol->reinitialize(outDims, refSform, numComponents, inVol->getType()); FloatMatrix targetToSource = myAffine; targetToSource.resize(4, 4); targetToSource[3][0] = 0.0f; targetToSource[3][1] = 0.0f; targetToSource[3][2] = 0.0f; targetToSource[3][3] = 1.0f; targetToSource = targetToSource.inverse(); Vector3D xvec, yvec, zvec, offset; xvec[0] = targetToSource[0][0]; xvec[1] = targetToSource[1][0]; xvec[2] = targetToSource[2][0]; yvec[0] = targetToSource[0][1]; yvec[1] = targetToSource[1][1]; yvec[2] = targetToSource[2][1]; zvec[0] = targetToSource[0][2]; zvec[1] = targetToSource[1][2]; zvec[2] = targetToSource[2][2]; offset[0] = targetToSource[0][3]; offset[1] = targetToSource[1][3]; offset[2] = targetToSource[2][3]; if (inVol->isMappedWithLabelTable()) { if (myMethod != VolumeFile::ENCLOSING_VOXEL) { CaretLogWarning("using interpolation type other than ENCLOSING_VOXEL on a label volume"); } for (int64_t i = 0; i < numMaps; ++i) { *(outVol->getMapLabelTable(i)) = *(inVol->getMapLabelTable(i)); } } for (int64_t c = 0; c < numComponents; ++c) { for (int64_t b = 0; b < numMaps; ++b) { if (myMethod == VolumeFile::CUBIC) { inVol->validateSpline(b, c);//because deconvolve is parallel, but won't execute parallel if we are already in a parallel section } #pragma omp CARET_PARFOR schedule(dynamic) for (int64_t k = 0; k < outDims[2]; ++k) { for (int64_t j = 0; j < outDims[1]; ++j) { for (int64_t i = 0; i < outDims[0]; ++i) { Vector3D outCoord, inCoord; outVol->indexToSpace(i, j, k, outCoord); inCoord = xvec * outCoord[0] + yvec * outCoord[1] + zvec * outCoord[2] + offset; float interpVal = inVol->interpolateValue(inCoord, myMethod, NULL, b, c); outVol->setValue(interpVal, i, j, k, b, c); } } } if (myMethod == VolumeFile::CUBIC) { inVol->freeSpline(b, c);//release memory we no longer need, if we allocated it } } } } float AlgorithmVolumeAffineResample::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmVolumeAffineResample::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmVolumeAffineResample.h000066400000000000000000000036541255417355300245070ustar00rootroot00000000000000#ifndef __ALGORITHM_VOLUME_AFFINE_RESAMPLE_H__ #define __ALGORITHM_VOLUME_AFFINE_RESAMPLE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" #include "FloatMatrix.h" #include "VolumeFile.h" namespace caret { class AlgorithmVolumeAffineResample : public AbstractAlgorithm { AlgorithmVolumeAffineResample(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmVolumeAffineResample(ProgressObject* myProgObj, const VolumeFile* inVol, const FloatMatrix& myAffine, const int64_t refDims[3], const std::vector >& refSform, const VolumeFile::InterpType& myMethod, VolumeFile* outVol); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmVolumeAffineResample; } #endif //__ALGORITHM_VOLUME_AFFINE_RESAMPLE_H__ workbench-1.1.1/src/Algorithms/AlgorithmVolumeAllLabelsToROIs.cxx000066400000000000000000000115601255417355300250670ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmVolumeAllLabelsToROIs.h" #include "AlgorithmException.h" #include "GiftiLabelTable.h" #include "VolumeFile.h" #include #include #include using namespace caret; using namespace std; AString AlgorithmVolumeAllLabelsToROIs::getCommandSwitch() { return "-volume-all-labels-to-rois"; } AString AlgorithmVolumeAllLabelsToROIs::getShortDescription() { return "MAKE ROIS FROM ALL LABELS IN A VOLUME FRAME"; } OperationParameters* AlgorithmVolumeAllLabelsToROIs::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addVolumeParameter(1, "label-in", "the input volume label file"); ret->addStringParameter(2, "map", "the number or name of the label map to use"); ret->addVolumeOutputParameter(3, "volume-out", "the output volume file"); ret->setHelpText( AString("The output volume has a frame for each label in the specified input frame, other than the ??? label, ") + "each of which contains an ROI of all voxels that are set to the corresponding label." ); return ret; } void AlgorithmVolumeAllLabelsToROIs::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { VolumeFile* myLabel = myParams->getVolume(1); AString mapID = myParams->getString(2); int whichMap = myLabel->getMapIndexFromNameOrNumber(mapID); if (whichMap == -1) { throw AlgorithmException("invalid map number or name specified"); } VolumeFile* myVolOut = myParams->getOutputVolume(3); AlgorithmVolumeAllLabelsToROIs(myProgObj, myLabel, whichMap, myVolOut); } AlgorithmVolumeAllLabelsToROIs::AlgorithmVolumeAllLabelsToROIs(ProgressObject* myProgObj, const VolumeFile* myLabel, const int& whichMap, VolumeFile* myVolOut) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); if (myLabel->getType() != SubvolumeAttributes::LABEL) { throw AlgorithmException("input volume must be a label volume"); } if (whichMap < 0 || whichMap >= myLabel->getNumberOfMaps()) { throw AlgorithmException("invalid map index specified"); } const GiftiLabelTable* myTable = myLabel->getMapLabelTable(whichMap); int32_t unusedKey = myTable->getUnassignedLabelKey();//WARNING: this actually MODIFIES the label table if the ??? key doesn't exist set myKeys = myTable->getKeys(); int numKeys = (int)myKeys.size(); if (numKeys < 2) { throw AlgorithmException("label table doesn't contain any keys besides the ??? key"); } map keyToMap;//lookup from keys to subvolume vector outDims = myLabel->getOriginalDimensions(); outDims.resize(4); outDims[3] = numKeys - 1;//don't include the ??? key myVolOut->reinitialize(outDims, myLabel->getSform()); myVolOut->setValueAllVoxels(0.0f); int counter = 0; for (set::iterator iter = myKeys.begin(); iter != myKeys.end(); ++iter) { if (*iter == unusedKey) continue;//skip the ??? key keyToMap[*iter] = counter; myVolOut->setMapName(counter, myTable->getLabelName(*iter)); ++counter; } for (int64_t k = 0; k < outDims[2]; ++k)//because we need to set voxels in the output, rather than in a temporary frame, for single pass without duplicating the memory { for (int64_t j = 0; j < outDims[1]; ++j) { for (int64_t i = 0; i < outDims[0]; ++i) { int32_t thisKey = (int32_t)floor(myLabel->getValue(i, j, k, whichMap) + 0.5f); map::iterator iter = keyToMap.find(thisKey); if (iter != keyToMap.end()) { myVolOut->setValue(1.0f, i, j, k, iter->second); } } } } } float AlgorithmVolumeAllLabelsToROIs::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmVolumeAllLabelsToROIs::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmVolumeAllLabelsToROIs.h000066400000000000000000000033531255417355300245150ustar00rootroot00000000000000#ifndef __ALGORITHM_VOLUME_ALL_LABELS_TO_ROIS_H__ #define __ALGORITHM_VOLUME_ALL_LABELS_TO_ROIS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmVolumeAllLabelsToROIs : public AbstractAlgorithm { AlgorithmVolumeAllLabelsToROIs(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmVolumeAllLabelsToROIs(ProgressObject* myProgObj, const VolumeFile* myLabel, const int& whichMap, VolumeFile* myVolOut); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmVolumeAllLabelsToROIs; } #endif //__ALGORITHM_VOLUME_ALL_LABELS_TO_ROIS_H__ workbench-1.1.1/src/Algorithms/AlgorithmVolumeDilate.cxx000066400000000000000000000427301255417355300234010ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmVolumeDilate.h" #include "AlgorithmException.h" #include "CaretHeap.h" #include "CaretLogger.h" #include "CaretOMP.h" #include "FloatMatrix.h" #include "Vector3D.h" #include "VolumeFile.h" #include "VoxelIJK.h" #include using namespace caret; using namespace std; AString AlgorithmVolumeDilate::getCommandSwitch() { return "-volume-dilate"; } AString AlgorithmVolumeDilate::getShortDescription() { return "DILATE A VOLUME FILE"; } OperationParameters* AlgorithmVolumeDilate::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addVolumeParameter(1, "volume", "the volume to dilate"); ret->addDoubleParameter(2, "distance", "distance in mm to dilate"); ret->addStringParameter(3, "method", "dilation method to use"); ret->addVolumeOutputParameter(4, "volume-out", "the output volume"); OptionalParameter* badRoiOpt = ret->createOptionalParameter(5, "-bad-voxel-roi", "specify an roi of voxels to overwrite, rather than voxels with value zero"); badRoiOpt->addVolumeParameter(1, "roi-volume", "volume file, positive values denote voxels to have their values replaced"); OptionalParameter* dataRoiOpt = ret->createOptionalParameter(7, "-data-roi", "specify an roi of where there is data"); dataRoiOpt->addVolumeParameter(1, "roi-volume", "volume file, positive values denote voxels that have data"); OptionalParameter* subvolSelect = ret->createOptionalParameter(6, "-subvolume", "select a single subvolume to dilate"); subvolSelect->addStringParameter(1, "subvol", "the subvolume number or name"); ret->setHelpText( AString("For all voxels that are designated as bad, if they neighbor a non-bad voxel with data or are within the specified distance of such a voxel, ") + "replace the value in the bad voxel with a value calculated from nearby non-bad voxels that have data, otherwise set the value to zero. " + "No matter how small is, dilation will always use at least the face neighbor voxels.\n\n" + "By default, voxels that have data with the value 0 are bad, specify -bad-voxel-roi to only count voxels as bad if they are selected by the roi. " + "If -data-roi is not specified, all voxels are assumed to have data.\n\n" + "Valid values for are:\n\n" + "NEAREST - use the value from the nearest good voxel\n" + "WEIGHTED - use a weighted average based on distance" ); return ret; } void AlgorithmVolumeDilate::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { VolumeFile* volIn = myParams->getVolume(1); float distance = (float)myParams->getDouble(2); AString methodName = myParams->getString(3); Method myMethod = NEAREST; if (methodName == "NEAREST") { myMethod = NEAREST; } else if (methodName == "WEIGHTED") { myMethod = WEIGHTED; } else { throw AlgorithmException("invalid method specified, use NEAREST or WEIGHTED"); } VolumeFile* volOut = myParams->getOutputVolume(4); OptionalParameter* badRoiOpt = myParams->getOptionalParameter(5); VolumeFile* badRoi = NULL; if (badRoiOpt->m_present) { badRoi = badRoiOpt->getVolume(1); } OptionalParameter* dataRoiOpt = myParams->getOptionalParameter(7); VolumeFile* dataRoi = NULL; if (dataRoiOpt->m_present) { dataRoi = dataRoiOpt->getVolume(1); } OptionalParameter* subvolSelect = myParams->getOptionalParameter(6); int subvol = -1; if (subvolSelect->m_present) { subvol = volIn->getMapIndexFromNameOrNumber(subvolSelect->getString(1)); if (subvol < 0) throw AlgorithmException("invalid subvolume specified"); } AlgorithmVolumeDilate(myProgObj, volIn, distance, myMethod, volOut, badRoi, dataRoi, subvol); } AlgorithmVolumeDilate::AlgorithmVolumeDilate(ProgressObject* myProgObj, const VolumeFile* volIn, const float& distance, const Method& myMethod, VolumeFile* volOut, const VolumeFile* badRoi, const VolumeFile* dataRoi, const int& subvol) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); vector myDims; volIn->getDimensions(myDims); if (subvol < -1 || subvol >= myDims[3]) { throw AlgorithmException("invalid subvolume specified"); } if (distance <= 0.0f) { throw AlgorithmException("distance too small"); } if (badRoi != NULL && !volIn->matchesVolumeSpace(badRoi)) { throw AlgorithmException("volume bad voxel roi space does not match input volume"); } if (dataRoi != NULL && !volIn->matchesVolumeSpace(dataRoi)) { throw AlgorithmException("volume data roi space does not match input volume"); } if (volIn->getType() == SubvolumeAttributes::LABEL && myMethod == WEIGHTED) { CaretLogWarning("dilating a volume label file with weighted method, expect strangeness"); } vector > volSpace = volIn->getSform(); Vector3D ivec, jvec, kvec, origin, ijorth, jkorth, kiorth; FloatMatrix(volSpace).getAffineVectors(ivec, jvec, kvec, origin); ijorth = ivec.cross(jvec).normal();//find the bounding box that encloses a sphere of radius kernBox jkorth = jvec.cross(kvec).normal(); kiorth = kvec.cross(ivec).normal(); int irange = (int)floor(abs(distance / ivec.dot(jkorth))); int jrange = (int)floor(abs(distance / jvec.dot(kiorth))); int krange = (int)floor(abs(distance / kvec.dot(ijorth))); if (irange < 1) irange = 1;//don't underflow if (jrange < 1) jrange = 1; if (krange < 1) krange = 1; Vector3D kscratch, jscratch, iscratch; vector stencil; vector stenWeights; for (int k = -krange; k <= krange; ++k) { kscratch = kvec * k; for (int j = -jrange; j <= jrange; ++j) { jscratch = kscratch + jvec * j; for (int i = -irange; i <= irange; ++i) { if (k == 0 && j == 0 && i == 0) continue; iscratch = jscratch + ivec * i; float tempf = iscratch.length(); if (tempf <= distance || abs(i) + abs(j) + abs(k) == 1) { stencil.push_back(i); stencil.push_back(j); stencil.push_back(k); switch (myMethod) { case NEAREST: stenWeights.push_back(tempf); break; case WEIGHTED: if (tempf == 0.0f) throw AlgorithmException("volume space is degenerate, aborting"); stenWeights.push_back(1.0f / (tempf * tempf)); break; } } } } } if (myMethod == NEAREST) {//sort the stencil by distance, so we can stop early CaretSimpleMinHeap myHeap; int stencilSize = (int)stenWeights.size(); myHeap.reserve(stencilSize); for (int i = 0; i < stencilSize; ++i) { myHeap.push(VoxelIJK(stencil.data() + i * 3), stenWeights[i]); } stencil.clear(); stenWeights.clear(); while (!myHeap.isEmpty()) { float tempf; VoxelIJK myTriple = myHeap.pop(&tempf); stenWeights.push_back(tempf); stencil.push_back(myTriple.m_ijk[0]); stencil.push_back(myTriple.m_ijk[1]); stencil.push_back(myTriple.m_ijk[2]); } } if (subvol == -1) { volOut->reinitialize(volIn->getOriginalDimensions(), volIn->getSform(), volIn->getNumberOfComponents(), volIn->getType()); for (int i = 0; i < myDims[3]; ++i) { if (volIn->getType() == SubvolumeAttributes::LABEL) { *(volOut->getMapLabelTable(i)) = *(volIn->getMapLabelTable(i)); } else { *(volOut->getMapPaletteColorMapping(i)) = *(volIn->getMapPaletteColorMapping(i)); } volOut->setMapName(i, volIn->getMapName(i) + " dilate " + AString::number(distance)); } } else { vector outDims = myDims; outDims.resize(3); volOut->reinitialize(outDims, volIn->getSform(), volIn->getNumberOfComponents(), volIn->getType()); if (volIn->getType() == SubvolumeAttributes::LABEL) { *(volOut->getMapLabelTable(0)) = *(volIn->getMapLabelTable(subvol)); } else { *(volOut->getMapPaletteColorMapping(0)) = *(volIn->getMapPaletteColorMapping(subvol)); } volOut->setMapName(0, volIn->getMapName(subvol) + " dilate " + AString::number(distance)); } if (subvol == -1) { for (int s = 0; s < myDims[3]; ++s) { for (int c = 0; c < myDims[4]; ++c) { dilateFrame(volIn, s, c, volOut, s, badRoi, dataRoi, myMethod, stencil, stenWeights); } } } else { for (int c = 0; c < myDims[4]; ++c) { dilateFrame(volIn, subvol, c, volOut, 0, badRoi, dataRoi, myMethod, stencil, stenWeights); } } } void AlgorithmVolumeDilate::dilateFrame(const VolumeFile* volIn, const int& insubvol, const int& component, VolumeFile* volOut, const int& outsubvol, const VolumeFile* badRoi, const VolumeFile* dataRoi, const Method& myMethod, const vector& stencil, const vector& stenWeights) { vector myDims; volIn->getDimensions(myDims); int stensize = (int)stenWeights.size(); #pragma omp CARET_PARFOR schedule(dynamic) for (int k = 0; k < myDims[2]; ++k) { for (int j = 0; j < myDims[1]; ++j) { for (int i = 0; i < myDims[0]; ++i) { bool copy = true; if (badRoi == NULL) { copy = volIn->getValue(i, j, k, insubvol, component) != 0.0f || (dataRoi != NULL && !(dataRoi->getValue(i, j, k) > 0.0f)); } else { copy = !(badRoi->getValue(i, j, k) > 0.0f);//in case some clown uses NaNs as bad in an roi } if (copy) { volOut->setValue(volIn->getValue(i, j, k, insubvol, component), i, j, k, outsubvol, component); } else { Vector3D voxcoord; volIn->indexToSpace(i, j, k, voxcoord); switch (myMethod) { case NEAREST: { int best = -1; if (badRoi == NULL) { for (int stenind = 0; stenind < stensize; ++stenind) { int base = stenind * 3; int64_t tempindex[3]; tempindex[0] = stencil[base] + i; tempindex[1] = stencil[base + 1] + j; tempindex[2] = stencil[base + 2] + k; if (volIn->indexValid(tempindex)) { if ((dataRoi == NULL || dataRoi->getValue(tempindex) > 0.0f) && volIn->getValue(tempindex, insubvol, component) != 0.0f) { best = stenind; break; } } } } else { for (int stenind = 0; stenind < stensize; ++stenind) { int base = stenind * 3; int64_t tempindex[3]; tempindex[0] = stencil[base] + i; tempindex[1] = stencil[base + 1] + j; tempindex[2] = stencil[base + 2] + k; if (volIn->indexValid(tempindex)) { if ((dataRoi == NULL || dataRoi->getValue(tempindex) > 0.0f) && !(badRoi->getValue(tempindex) > 0.0f)) { best = stenind; break; } } } } if (best == -1) { volOut->setValue(0.0f, i, j, k, outsubvol, component); } else { int base = best * 3; int64_t tempindex[3]; tempindex[0] = stencil[base] + i; tempindex[1] = stencil[base + 1] + j; tempindex[2] = stencil[base + 2] + k; volOut->setValue(volIn->getValue(tempindex, insubvol, component), i, j, k, outsubvol, component); } break; } case WEIGHTED: { double sum = 0.0, weightsum = 0.0; if (badRoi == NULL) { for (int stenind = 0; stenind < stensize; ++stenind) { int base = stenind * 3; int64_t tempindex[3]; tempindex[0] = stencil[base] + i; tempindex[1] = stencil[base + 1] + j; tempindex[2] = stencil[base + 2] + k; if (volIn->indexValid(tempindex)) { float tempf = volIn->getValue(tempindex, insubvol, component); if ((dataRoi == NULL || dataRoi->getValue(tempindex) > 0.0f) && tempf != 0.0f) { float weight = stenWeights[stenind]; sum += weight * tempf; weightsum += weight; } } } } else { for (int stenind = 0; stenind < stensize; ++stenind) { int base = stenind * 3; int64_t tempindex[3]; tempindex[0] = stencil[base] + i; tempindex[1] = stencil[base + 1] + j; tempindex[2] = stencil[base + 2] + k; if (volIn->indexValid(tempindex)) { if ((dataRoi == NULL || dataRoi->getValue(tempindex) > 0.0f) && !(badRoi->getValue(tempindex) > 0.0f)) { float tempf = volIn->getValue(tempindex, insubvol, component); float weight = stenWeights[stenind]; sum += weight * tempf; weightsum += weight; } } } } if (weightsum != 0.0) { volOut->setValue(sum / weightsum, i, j, k, outsubvol, component); } else { volOut->setValue(0.0f, i, j, k, outsubvol, component); } break; } } } } } } } float AlgorithmVolumeDilate::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmVolumeDilate::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmVolumeDilate.h000066400000000000000000000042771255417355300230320ustar00rootroot00000000000000#ifndef __ALGORITHM_VOLUME_DILATE_H__ #define __ALGORITHM_VOLUME_DILATE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmVolumeDilate : public AbstractAlgorithm { AlgorithmVolumeDilate(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: enum Method { NEAREST, WEIGHTED }; AlgorithmVolumeDilate(ProgressObject* myProgObj, const VolumeFile* volIn, const float& distance, const Method& myMethod, VolumeFile* volOut, const VolumeFile* badRoi = NULL, const VolumeFile* dataRoi = NULL, const int& subvol = -1); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); private: void dilateFrame(const VolumeFile* volIn, const int& insubvol, const int& component, VolumeFile* volOut, const int& outsubvol, const VolumeFile* badRoi, const VolumeFile* dataRoi, const Method& myMethod, const std::vector& stencil, const std::vector& stenWeights); }; typedef TemplateAutoOperation AutoAlgorithmVolumeDilate; } #endif //__ALGORITHM_VOLUME_DILATE_H__ workbench-1.1.1/src/Algorithms/AlgorithmVolumeEstimateFWHM.cxx000066400000000000000000000230671255417355300244360ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmVolumeEstimateFWHM.h" #include "AlgorithmException.h" #include #include using namespace caret; using namespace std; AString AlgorithmVolumeEstimateFWHM::getCommandSwitch() { return "-volume-estimate-fwhm"; } AString AlgorithmVolumeEstimateFWHM::getShortDescription() { return "ESTIMATE FWHM SMOOTHNESS OF A VOLUME"; } OperationParameters* AlgorithmVolumeEstimateFWHM::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addVolumeParameter(1, "volume", "the input volume"); OptionalParameter* roiVolOpt = ret->createOptionalParameter(2, "-roi", "use only data within an ROI"); roiVolOpt->addVolumeParameter(1, "roivol", "the volume to use as an ROI"); OptionalParameter* subvolSelect = ret->createOptionalParameter(3, "-subvolume", "select a single subvolume to estimate smoothness of"); subvolSelect->addStringParameter(1, "subvol", "the subvolume number or name"); ret->setHelpText( AString("Estimates the smoothness of the input volume in X, Y, and Z directions separately, printing the estimates to standard output. ") + "If -subvolume is not specified, each subvolume is estimated and displayed separately." ); return ret; } void AlgorithmVolumeEstimateFWHM::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); VolumeFile* myVol = myParams->getVolume(1); OptionalParameter* roiVolOpt = myParams->getOptionalParameter(2); VolumeFile* roiVol = NULL; if (roiVolOpt->m_present) { roiVol = roiVolOpt->getVolume(1); if (!roiVol->matchesVolumeSpace(myVol)) { throw AlgorithmException("roi volume does not match the space of the input volume"); } } OptionalParameter* subvolSelect = myParams->getOptionalParameter(3); int subvolNum = -1; if (subvolSelect->m_present) { subvolNum = (int)myVol->getMapIndexFromNameOrNumber(subvolSelect->getString(1)); if (subvolNum < 0) { throw AlgorithmException("invalid subvolume specified"); } } vector dims; myVol->getDimensions(dims); if (subvolNum == -1) { for (int64_t s = 0; s < dims[3]; ++s) { for (int64_t c = 0; c < dims[4]; ++c) { Vector3D result = estimateFWHM(myVol, roiVol, s, c); if (dims[3] != 1) cout << "subvol " << s + 1 << " "; if (dims[4] != 1) cout << "component " << c + 1 << " "; cout << "FWHM: " << result[0] << ", " << result[1] << ", " << result[2] << endl; } } } else { for (int64_t c = 0; c < dims[4]; ++c) { Vector3D result = estimateFWHM(myVol, roiVol, subvolNum, c); if (dims[3] != 1) cout << "subvol " << subvolNum + 1 << " "; if (dims[4] != 1) cout << "component " << c + 1 << " "; cout << "FWHM: " << result[0] << ", " << result[1] << ", " << result[2] << endl; } } } Vector3D AlgorithmVolumeEstimateFWHM::estimateFWHM(const VolumeFile* input, const VolumeFile* roi, const int64_t& brickIndex, const int64_t& component) { if (roi != NULL && !roi->matchesVolumeSpace(input)) { throw AlgorithmException("roi volume does not match the space of the input volume"); } vector dims; input->getDimensions(dims); double globalaccum = 0.0; int64_t globalCount = 0; double diraccum[3] = {0.0, 0.0, 0.0}; int64_t dirCount[3] = {0, 0, 0}; for (int64_t k = 0; k < dims[2]; ++k)//2 pass method, first find means of values and of FORWARD differences only - this removes global gradient effects {//for derivation, see Forman, S.D., Cohen, J.D., Fitzgerald, M., Eddy, W.F., Mintun, M.A., Noll, D.C., 1995. for (int64_t j = 0; j < dims[1]; ++j)//Improved assessment of significant activation in functional magnetic resonance imaging (fMRI): use of a cluster-size threshold. {//Magn. Reson. Med. 33, 636–647. for (int64_t i = 0; i < dims[0]; ++i) { if (roi == NULL || roi->getValue(i, j, k) > 0.0f) { float center = input->getValue(i, j, k, brickIndex, component); globalaccum += center; ++globalCount; if (i + 1 < dims[0] && (roi == NULL || roi->getValue(i + 1, j, k) > 0.0f))//use ONLY forward differences, to avoid double counting { float diff = center - input->getValue(i + 1, j, k, brickIndex, component); diraccum[0] += diff; ++dirCount[0]; } if (j + 1 < dims[1] && (roi == NULL || roi->getValue(i, j + 1, k) > 0.0f))//use ONLY forward differences, to avoid double counting { float diff = center - input->getValue(i, j + 1, k, brickIndex, component); diraccum[1] += diff; ++dirCount[1]; } if (k + 1 < dims[2] && (roi == NULL || roi->getValue(i, j, k + 1) > 0.0f))//use ONLY forward differences, to avoid double counting { float diff = center - input->getValue(i, j, k + 1, brickIndex, component); diraccum[2] += diff; ++dirCount[2]; } } } } } if (globalCount == 0) throw AlgorithmException("ROI is empty or volume file has no voxels"); float dirmean[3]; for (int i = 0; i < 3; ++i) { dirmean[i] = diraccum[i] / dirCount[i];//we will fix NaNs later diraccum[i] = 0.0; } float globalmean = globalaccum / globalCount; globalaccum = 0; for (int64_t k = 0; k < dims[2]; ++k)//now, find the variances { for (int64_t j = 0; j < dims[1]; ++j) { for (int64_t i = 0; i < dims[0]; ++i) { if (roi == NULL || roi->getValue(i, j, k) > 0.0f) { float center = input->getValue(i, j, k, brickIndex, component); float tempf = center - globalmean; globalaccum += tempf * tempf; if (i + 1 < dims[0] && (roi == NULL || roi->getValue(i + 1, j, k) > 0.0f))//use ONLY forward differences, to avoid double counting { float diff = center - input->getValue(i + 1, j, k, brickIndex, component); tempf = diff - dirmean[0]; diraccum[0] += tempf * tempf; } if (j + 1 < dims[1] && (roi == NULL || roi->getValue(i, j + 1, k) > 0.0f))//use ONLY forward differences, to avoid double counting { float diff = center - input->getValue(i, j + 1, k, brickIndex, component); tempf = diff - dirmean[1]; diraccum[1] += tempf * tempf; } if (k + 1 < dims[2] && (roi == NULL || roi->getValue(i, j, k + 1) > 0.0f))//use ONLY forward differences, to avoid double counting { float diff = center - input->getValue(i, j, k + 1, brickIndex, component); tempf = diff - dirmean[2]; diraccum[2] += tempf * tempf; } } } } } float dirvariance[3]; float fwhms[3]; float globalvariance = globalaccum / globalCount; const VolumeSpace& volSpace = input->getVolumeSpace(); Vector3D spacingVecs[4]; volSpace.getSpacingVectors(spacingVecs[0], spacingVecs[1], spacingVecs[2], spacingVecs[3]); for (int i = 0; i < 3; ++i) { if (dirCount[i] > 0) { dirvariance[i] = diraccum[i] / dirCount[i]; } else { dirvariance[i] = 0.0;//avoid NaNs } fwhms[i] = spacingVecs[i].length() * sqrt(-2.0f * log(2.0f) / log(1.0f - dirvariance[i] / (2.0f * globalvariance))); } Vector3D ret; VolumeSpace::OrientTypes myorient[3]; volSpace.getOrientation(myorient); for (int i = 0; i < 3; ++i) { switch (myorient[i]) { case VolumeSpace::LEFT_TO_RIGHT: case VolumeSpace::RIGHT_TO_LEFT: ret[0] = fwhms[i]; break; case VolumeSpace::POSTERIOR_TO_ANTERIOR: case VolumeSpace::ANTERIOR_TO_POSTERIOR: ret[1] = fwhms[i]; break; case VolumeSpace::INFERIOR_TO_SUPERIOR: case VolumeSpace::SUPERIOR_TO_INFERIOR: ret[2] = fwhms[i]; break; } } return ret; } workbench-1.1.1/src/Algorithms/AlgorithmVolumeEstimateFWHM.h000066400000000000000000000032021255417355300240500ustar00rootroot00000000000000#ifndef __ALGORITHM_VOLUME_ESTIMATE_FWHM_H__ #define __ALGORITHM_VOLUME_ESTIMATE_FWHM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" #include "Vector3D.h" #include "VolumeFile.h" namespace caret { class AlgorithmVolumeEstimateFWHM : public AbstractAlgorithm { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); static Vector3D estimateFWHM(const VolumeFile* input, const VolumeFile* roi = NULL, const int64_t& brickIndex = 0, const int64_t& component = 0); }; typedef TemplateAutoOperation AutoAlgorithmVolumeEstimateFWHM; } #endif //__ALGORITHM_VOLUME_ESTIMATE_FWHM_H__ workbench-1.1.1/src/Algorithms/AlgorithmVolumeExtrema.cxx000066400000000000000000001514441255417355300236070ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmVolumeExtrema.h" #include "AlgorithmException.h" #include "AlgorithmVolumeSmoothing.h" #include "CaretHeap.h" #include "CaretLogger.h" #include "CaretOMP.h" #include "VolumeFile.h" #include #include #include using namespace caret; using namespace std; AString AlgorithmVolumeExtrema::getCommandSwitch() { return "-volume-extrema"; } AString AlgorithmVolumeExtrema::getShortDescription() { return "FIND EXTREMA IN A VOLUME FILE"; } OperationParameters* AlgorithmVolumeExtrema::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addVolumeParameter(1, "volume-in", "volume file to find the extrema of"); ret->addDoubleParameter(2, "distance", "the minimum distance between identified extrema of the same type"); ret->addVolumeOutputParameter(3, "volume-out", "the output extrema volume"); OptionalParameter* presmoothOpt = ret->createOptionalParameter(4, "-presmooth", "smooth the volume before finding extrema"); presmoothOpt->addDoubleParameter(1, "kernel", "the sigma for the gaussian smoothing kernel, in mm"); OptionalParameter* roiOpt = ret->createOptionalParameter(5, "-roi", "ignore values outside the selected area"); roiOpt->addVolumeParameter(1, "roi-volume", "the area to find extrema in"); OptionalParameter* thresholdOpt = ret->createOptionalParameter(6, "-threshold", "ignore small extrema"); thresholdOpt->addDoubleParameter(1, "low", "the largest value to consider for being a minimum"); thresholdOpt->addDoubleParameter(2, "high", "the smallest value to consider for being a maximum"); ret->createOptionalParameter(7, "-sum-subvols", "output the sum of the extrema subvolumes instead of each subvolume separately"); ret->createOptionalParameter(8, "-consolidate-mode", "use consolidation of local minima instead of a large neighborhood"); ret->createOptionalParameter(10, "-only-maxima", "only find the maxima"); ret->createOptionalParameter(11, "-only-minima", "only find the minima"); OptionalParameter* subvolOpt = ret->createOptionalParameter(9, "-subvolume", "select a single subvolume to find extrema in"); subvolOpt->addStringParameter(1, "subvolume", "the subvolume number or name"); ret->setHelpText( AString("Finds extrema in a volume file, such that no two extrema of the same type are within of each other. ") + "The extrema are labeled as -1 for minima, 1 for maxima, 0 otherwise. " + "If -only-maxima or -only-minima is specified, then it will ignore extrema not of the specified type. These options are mutually exclusive.\n\n" + "If -sum-subvols is specified, these extrema subvolumes are summed, and the output has a single subvolume with this result.\n\n" + "By default, a datapoint is an extrema only if it is more extreme than every other datapoint that is within from it. " + "If -consolidate-mode is used, it instead starts by finding all datapoints that are more extreme than their immediate neighbors, " + "then while there are any extrema within of each other, take the two extrema closest to each other and merge them into one by a weighted average " + "based on how many original extrema have been merged into each.\n\n" + "By default, all input subvolumes are used with no smoothing, use -subvolume to specify a single subvolume to use, and -presmooth to smooth the input before " + "finding the extrema." ); return ret; } void AlgorithmVolumeExtrema::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { VolumeFile* myVolIn = myParams->getVolume(1); float distance = (float)myParams->getDouble(2); VolumeFile* myVolOut = myParams->getOutputVolume(3); OptionalParameter* presmoothOpt = myParams->getOptionalParameter(4); float presmooth = -1.0f; if (presmoothOpt->m_present) { presmooth = presmoothOpt->getDouble(1); if (presmooth <= 0.0f) { throw AlgorithmException("smoothing kernel must be positive"); } } OptionalParameter* roiOpt = myParams->getOptionalParameter(5); VolumeFile* myRoi = NULL; if (roiOpt->m_present) { myRoi = roiOpt->getVolume(1); } OptionalParameter* thresholdOpt = myParams->getOptionalParameter(6); bool thresholdMode = false; float lowThresh = 0.0f, highThresh = 0.0f; if (thresholdOpt->m_present) { thresholdMode = true; lowThresh = (float)thresholdOpt->getDouble(1); highThresh = (float)thresholdOpt->getDouble(2); } bool sumSubvols = myParams->getOptionalParameter(7)->m_present; bool consolidateMode = myParams->getOptionalParameter(8)->m_present; OptionalParameter* subvolOpt = myParams->getOptionalParameter(9); int subvol = -1; if (subvolOpt->m_present) {//set up to use the single column subvol = (int)myVolIn->getMapIndexFromNameOrNumber(subvolOpt->getString(1)); if (subvol < 0) { throw AlgorithmException("invalid subvolume specified"); } } bool ignoreMinima = myParams->getOptionalParameter(10)->m_present; bool ignoreMaxima = myParams->getOptionalParameter(11)->m_present; if (ignoreMinima && ignoreMaxima) throw AlgorithmException("you may not specify both -only-maxima and -only-minima"); if (thresholdMode) { AlgorithmVolumeExtrema(myProgObj, myVolIn, distance, myVolOut, lowThresh, highThresh, myRoi, presmooth, sumSubvols, consolidateMode, ignoreMinima, ignoreMaxima, subvol); } else { AlgorithmVolumeExtrema(myProgObj, myVolIn, distance, myVolOut, myRoi, presmooth, sumSubvols, consolidateMode, ignoreMinima, ignoreMaxima, subvol); } } AlgorithmVolumeExtrema::AlgorithmVolumeExtrema(ProgressObject* myProgObj, const VolumeFile* myVolIn, const float& distance, VolumeFile* myVolOut, const VolumeFile* myRoi, const float& presmooth, const bool& sumSubvols, const bool& consolidateMode, bool ignoreMinima, bool ignoreMaxima, const int& subvol) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); if (ignoreMinima && ignoreMaxima) throw AlgorithmException("AlgorithmVolumeExtrema called with ignoreMinima and ignoreMaxima both true"); if (myRoi != NULL && !myVolIn->matchesVolumeSpace(myRoi)) { throw AlgorithmException("roi doesn't match input volume space"); } vector myDims; myVolIn->getDimensions(myDims); if (subvol < -1 || subvol >= myDims[3]) { throw AlgorithmException("invalid subvolume specified"); } int useSubvol = subvol; const VolumeFile* toProcess = myVolIn; VolumeFile tempVol; if (presmooth > 0.0f) { AlgorithmVolumeSmoothing(NULL, myVolIn, presmooth, &tempVol, myRoi, false, subvol); toProcess = &tempVol; if (subvol != -1) { useSubvol = 0; } } if (!consolidateMode) { precomputeStencil(myVolIn, distance); } if (subvol == -1) { vector minima, maxima; vector outDims = myDims; outDims.resize(4); if (sumSubvols) { outDims[3] = 1; } myVolOut->reinitialize(outDims, myVolIn->getSform(), myDims[4]); if (sumSubvols) { myVolOut->setMapName(0, "sum of extrema"); } myVolOut->setValueAllVoxels(0.0f); for (int s = 0; s < myDims[3]; ++s) { for (int c = 0; c < myDims[4]; ++c) { if (consolidateMode) { findExtremaConsolidate(toProcess, s, c, myRoi, distance, false, 0.0f, 0.0f, ignoreMinima, ignoreMaxima, minima, maxima); } else { findExtremaStencils(toProcess, s, c, myRoi, false, 0.0f, 0.0f, ignoreMinima, ignoreMaxima, minima, maxima); } if (sumSubvols) { int64_t numElems = (int64_t)minima.size(); for (int64_t i = 0; i < numElems; ++i) { myVolOut->setValue(myVolOut->getValue(minima[i].m_ijk, 0, c) - 1.0f, minima[i].m_ijk, 0, c); } numElems = (int64_t)maxima.size(); for (int64_t i = 0; i < numElems; ++i) { myVolOut->setValue(myVolOut->getValue(maxima[i].m_ijk, 0, c) + 1.0f, maxima[i].m_ijk, 0, c); } } else { if (c == 0) myVolOut->setMapName(s, "extrema of " + myVolIn->getMapName(s)); int64_t numElems = (int64_t)minima.size(); for (int64_t i = 0; i < numElems; ++i) { myVolOut->setValue(-1.0f, minima[i].m_ijk, s, c); } numElems = (int64_t)maxima.size(); for (int64_t i = 0; i < numElems; ++i) { myVolOut->setValue(1.0f, maxima[i].m_ijk, s, c); } } } } } else { vector minima, maxima; vector outDims = myDims; outDims.resize(3); myVolOut->reinitialize(outDims, myVolIn->getSform(), myDims[4]); myVolOut->setMapName(0, "extrema of " + myVolIn->getMapName(subvol)); myVolOut->setValueAllVoxels(0.0f); for (int c = 0; c < myDims[4]; ++c) { if (consolidateMode) { findExtremaConsolidate(toProcess, useSubvol, c, myRoi, distance, false, 0.0f, 0.0f, ignoreMinima, ignoreMaxima, minima, maxima); } else { findExtremaStencils(toProcess, useSubvol, c, myRoi, false, 0.0f, 0.0f, ignoreMinima, ignoreMaxima, minima, maxima); } int64_t numElems = (int64_t)minima.size(); for (int64_t i = 0; i < numElems; ++i) { myVolOut->setValue(-1.0f, minima[i].m_ijk, 0, c); } numElems = (int64_t)maxima.size(); for (int64_t i = 0; i < numElems; ++i) { myVolOut->setValue(1.0f, maxima[i].m_ijk, 0, c); } } } } AlgorithmVolumeExtrema::AlgorithmVolumeExtrema(ProgressObject* myProgObj, const VolumeFile* myVolIn, const float& distance, VolumeFile* myVolOut, const float& lowThresh, const float& highThresh, const VolumeFile* myRoi, const float& presmooth, const bool& sumSubvols, const bool& consolidateMode, bool ignoreMinima, bool ignoreMaxima, const int& subvol) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); if (ignoreMinima && ignoreMaxima) throw AlgorithmException("AlgorithmVolumeExtrema called with ignoreMinima and ignoreMaxima both true"); if (myRoi != NULL && !myVolIn->matchesVolumeSpace(myRoi)) { throw AlgorithmException("roi doesn't match input volume space"); } vector myDims; myVolIn->getDimensions(myDims); if (subvol < -1 || subvol >= myDims[3]) { throw AlgorithmException("invalid subvolume specified"); } int useSubvol = subvol; const VolumeFile* toProcess = myVolIn; VolumeFile tempVol; if (presmooth > 0.0f) { AlgorithmVolumeSmoothing(NULL, myVolIn, presmooth, &tempVol, myRoi, false, subvol); toProcess = &tempVol; if (subvol != -1) { useSubvol = 0; } } if (!consolidateMode) { precomputeStencil(myVolIn, distance); } if (subvol == -1) { vector minima, maxima; vector outDims = myDims; outDims.resize(4); if (sumSubvols) { outDims[3] = 1; } myVolOut->reinitialize(outDims, myVolIn->getSform(), myDims[4]); if (sumSubvols) { myVolOut->setMapName(0, "sum of extrema"); } myVolOut->setValueAllVoxels(0.0f); for (int s = 0; s < myDims[3]; ++s) { for (int c = 0; c < myDims[4]; ++c) { if (consolidateMode) { findExtremaConsolidate(toProcess, s, c, myRoi, distance, true, lowThresh, highThresh, ignoreMinima, ignoreMaxima, minima, maxima); } else { findExtremaStencils(toProcess, s, c, myRoi, true, lowThresh, highThresh, ignoreMinima, ignoreMaxima, minima, maxima); } if (sumSubvols) { int64_t numElems = (int64_t)minima.size(); for (int64_t i = 0; i < numElems; ++i) { myVolOut->setValue(myVolOut->getValue(minima[i].m_ijk, 0, c) - 1.0f, minima[i].m_ijk, 0, c); } numElems = (int64_t)maxima.size(); for (int64_t i = 0; i < numElems; ++i) { myVolOut->setValue(myVolOut->getValue(maxima[i].m_ijk, 0, c) + 1.0f, maxima[i].m_ijk, 0, c); } } else { if (c == 0) myVolOut->setMapName(s, "extrema of " + myVolIn->getMapName(s)); int64_t numElems = (int64_t)minima.size(); for (int64_t i = 0; i < numElems; ++i) { myVolOut->setValue(-1.0f, minima[i].m_ijk, s, c); } numElems = (int64_t)maxima.size(); for (int64_t i = 0; i < numElems; ++i) { myVolOut->setValue(1.0f, maxima[i].m_ijk, s, c); } } } } } else { vector minima, maxima; vector outDims = myDims; outDims.resize(3); myVolOut->reinitialize(outDims, myVolIn->getSform(), myDims[4]); myVolOut->setMapName(0, "extrema of " + myVolIn->getMapName(subvol)); myVolOut->setValueAllVoxels(0.0f); for (int c = 0; c < myDims[4]; ++c) { if (consolidateMode) { findExtremaConsolidate(toProcess, useSubvol, c, myRoi, distance, true, lowThresh, highThresh, ignoreMinima, ignoreMaxima, minima, maxima); } else { findExtremaStencils(toProcess, useSubvol, c, myRoi, true, lowThresh, highThresh, ignoreMinima, ignoreMaxima, minima, maxima); } int64_t numElems = (int64_t)minima.size(); for (int64_t i = 0; i < numElems; ++i) { myVolOut->setValue(-1.0f, minima[i].m_ijk, 0, c); } numElems = (int64_t)maxima.size(); for (int64_t i = 0; i < numElems; ++i) { myVolOut->setValue(1.0f, maxima[i].m_ijk, 0, c); } } } } void AlgorithmVolumeExtrema::precomputeStencil(const VolumeFile* myVolIn, const float& distance) { m_stencil.clear(); const vector >& volSpace = myVolIn->getSform(); Vector3D ivec, jvec, kvec, origin; ivec[0] = volSpace[0][0]; jvec[0] = volSpace[0][1]; kvec[0] = volSpace[0][2]; origin[0] = volSpace[0][3]; ivec[1] = volSpace[1][0]; jvec[1] = volSpace[1][1]; kvec[1] = volSpace[1][2]; origin[1] = volSpace[1][3]; ivec[2] = volSpace[2][0]; jvec[2] = volSpace[2][1]; kvec[2] = volSpace[2][2]; origin[2] = volSpace[2][3]; Vector3D ijorth = ivec.cross(jvec).normal();//find the bounding box that encloses a sphere of radius kernBox Vector3D jkorth = jvec.cross(kvec).normal(); Vector3D kiorth = kvec.cross(ivec).normal(); m_irange = (int)floor(abs(distance / ivec.dot(jkorth)));//these are member variables so we can avoid testing for things outside the volume bounding box when the stencil is m_jrange = (int)floor(abs(distance / jvec.dot(kiorth)));// guaranteed within the bounding box m_krange = (int)floor(abs(distance / kvec.dot(ijorth))); if (m_irange < 1) m_irange = 1;//don't underflow if (m_jrange < 1) m_jrange = 1; if (m_krange < 1) m_krange = 1; float dist2 = distance * distance;//don't require square root calls VoxelIJK tempVox; bool ichange = false, jchange = false, kchange = false;//ensure that stencil is 3D, not degenerate for (int k = -m_krange; k <= m_krange; ++k)//generate the stencil list in the same index order as the volume file { Vector3D kpart = k * kvec; tempVox.m_ijk[2] = k; for (int j = -m_jrange; j <= m_jrange; ++j) { Vector3D jpart = (j * jvec) + kpart;//I could do an extra test here, or recompute the range, but we only do this once, so whatever tempVox.m_ijk[1] = j; for (int i = -m_irange; i <= m_irange; ++i) { if (k == 0 && j == 0 && i == 0) continue;//skip the center voxel if ((jpart + (i * ivec)).lengthsquared() <= dist2) { tempVox.m_ijk[0] = i; m_stencil.push_back(tempVox); if (k != 0) kchange = true; if (j != 0) jchange = true; if (i != 0) ichange = true; } } } } if (!ichange || !jchange || !kchange) { CaretLogWarning("distance too small, stencil did not use all 3 dimensions, substituting in 6-neighbor stencil"); m_irange = 1; m_jrange = 1; m_krange = 1; m_stencil.clear(); m_stencil.push_back(VoxelIJK(-1, 0, 0)); m_stencil.push_back(VoxelIJK(0, -1, 0)); m_stencil.push_back(VoxelIJK(0, 0, -1)); m_stencil.push_back(VoxelIJK(0, 0, 1)); m_stencil.push_back(VoxelIJK(0, 1, 0)); m_stencil.push_back(VoxelIJK(1, 0, 0)); } } void AlgorithmVolumeExtrema::findExtremaStencils(const VolumeFile* toProcess, const int& s, const int& c, const VolumeFile* myRoi, const bool& threshMode, const float& lowThresh, const float& highThresh, bool ignoreMinima, bool ignoreMaxima, vector& minima, vector& maxima) { minima.clear(); maxima.clear(); vector myDims; toProcess->getDimensions(myDims); int64_t frameSize = myDims[0] * myDims[1] * myDims[2]; int stencilSize = (int)m_stencil.size(); vector minPos(frameSize, 1), maxPos(frameSize, 1);//mark things off that fail a comparison, to reduce redundant comparisons const float* dataFrame = toProcess->getFrame(s, c); const float* roiFrame = NULL; if (myRoi != NULL) { roiFrame = myRoi->getFrame(); } #pragma omp CARET_PARFOR schedule(dynamic) for (int64_t k = 0; k < myDims[2]; ++k) { bool ksafe = (k >= m_krange && k < myDims[2] - m_krange);//so we can avoid performing certain bounds checks in the inner loop for (int64_t j = 0; j < myDims[1]; ++j) { bool jsafe = (j >= m_jrange && j < myDims[1] - m_jrange); for (int64_t i = 0; i < myDims[0]; ++i) { int64_t myindex = toProcess->getIndex(i, j, k); if (roiFrame == NULL || roiFrame[myindex] > 0.0f) { bool canBeMin = minPos[myindex] && !ignoreMinima; bool canBeMax = maxPos[myindex] && !ignoreMaxima; float myval = dataFrame[myindex]; if (threshMode) { if (myval > lowThresh) canBeMin = false;//check thresholds if (myval < highThresh) canBeMax = false; } if (canBeMin || canBeMax) { bool isafe = (i >= m_jrange && i < myDims[0] - m_irange); if (isafe && jsafe && ksafe)//special case out the inner loops to avoid branching when possible { if (roiFrame == NULL) { int v = 0; if (canBeMin && canBeMax)//no ROI and safe means we can just use the first element in stencil to break the double test { VoxelIJK& offset = m_stencil[0]; int64_t testVox[3] = { i + offset.m_ijk[0], j + offset.m_ijk[1], k + offset.m_ijk[2] }; int64_t otherindex = toProcess->getIndex(testVox); float otherval = dataFrame[otherindex]; if (myval < otherval) { minPos[otherindex] = 0; } else { canBeMin = false; } if (myval > otherval) { maxPos[otherindex] = 0; } else { canBeMax = false; } v = 1; } if (canBeMin) { for (; v < stencilSize; ++v) { VoxelIJK& offset = m_stencil[v]; int64_t testVox[3] = { i + offset.m_ijk[0], j + offset.m_ijk[1], k + offset.m_ijk[2] }; int64_t otherindex = toProcess->getIndex(testVox); float otherval = dataFrame[otherindex]; if (myval < otherval) { minPos[otherindex] = 0; } else { canBeMin = false; break; } } } if (canBeMax) { for (; v < stencilSize; ++v) { VoxelIJK& offset = m_stencil[v]; int64_t testVox[3] = { i + offset.m_ijk[0], j + offset.m_ijk[1], k + offset.m_ijk[2] }; int64_t otherindex = toProcess->getIndex(testVox); float otherval = dataFrame[otherindex]; if (myval > otherval) { maxPos[otherindex] = 0; } else { canBeMax = false; break; } } } } else { int v = 0; if (canBeMin && canBeMax)//we have an roi, so we may need to loop before the double test hits a valid neighbor { for (; v < stencilSize; ++v) { VoxelIJK& offset = m_stencil[v]; int64_t testVox[3] = { i + offset.m_ijk[0], j + offset.m_ijk[1], k + offset.m_ijk[2] }; int64_t otherindex = toProcess->getIndex(testVox); if (roiFrame[otherindex] > 0.0f) { float otherval = dataFrame[otherindex]; if (myval < otherval) { minPos[otherindex] = 0; } else { canBeMin = false; } if (myval > otherval) { maxPos[otherindex] = 0; } else { canBeMax = false; } ++v;//don't test the same voxel again break;//we have eliminated one possibility, so we can move to a loop with fewer tests } else { if (abs(offset.m_ijk[0]) + abs(offset.m_ijk[1]) + abs(offset.m_ijk[2]) == 1) { canBeMax = false;//if we find a face neighbor outside the roi, don't count this as an extrema canBeMin = false; break; } } } } if (canBeMin) { for (; v < stencilSize; ++v) { VoxelIJK& offset = m_stencil[v]; int64_t testVox[3] = { i + offset.m_ijk[0], j + offset.m_ijk[1], k + offset.m_ijk[2] }; int64_t otherindex = toProcess->getIndex(testVox); if (roiFrame[otherindex] > 0.0f) { float otherval = dataFrame[otherindex]; if (myval < otherval) { minPos[otherindex] = 0; } else { canBeMin = false; break; } } else { if (abs(offset.m_ijk[0]) + abs(offset.m_ijk[1]) + abs(offset.m_ijk[2])) { canBeMin = false;//if we find a face neighbor outside the roi, don't count this as an extrema break; } } } } if (canBeMax) { for (; v < stencilSize; ++v) { VoxelIJK& offset = m_stencil[v]; int64_t testVox[3] = { i + offset.m_ijk[0], j + offset.m_ijk[1], k + offset.m_ijk[2] }; int64_t otherindex = toProcess->getIndex(testVox); if (roiFrame[otherindex] > 0.0f) { float otherval = dataFrame[otherindex]; if (myval > otherval) { maxPos[otherindex] = 0; } else { canBeMax = false; break; } } else { if (abs(offset.m_ijk[0]) + abs(offset.m_ijk[1]) + abs(offset.m_ijk[2]) == 1) { canBeMax = false;//if we find a face neighbor outside the roi, don't count this as an extrema break; } } } } if (canBeMin && canBeMax)//only way for this to happen is if there are no neighbors in the roi { canBeMax = false;//don't count isolated voxels as extrema canBeMin = false; } } } else {//if we are near an edge, we have to check all the neighbor indexes if (i < 1 || i >= myDims[0] - 1 || j < 1 || j >= myDims[1] - 1 || k < 1 || k >= myDims[2] - 1) { canBeMax = false;//but if we are on the edge of the volume, that is effectively the same as on the edge of the roi canBeMin = false;//so, don't count any extrema here } else { for (int v = 0; v < stencilSize; ++v) { VoxelIJK& offset = m_stencil[v]; int64_t testVox[3] = { i + offset.m_ijk[0], j + offset.m_ijk[1], k + offset.m_ijk[2] }; if (!ksafe && (testVox[2] < 0 || testVox[2] >= myDims[2])) continue; if (!jsafe && (testVox[1] < 0 || testVox[1] >= myDims[1])) continue; if (!isafe && (testVox[0] < 0 || testVox[0] >= myDims[0])) continue; int64_t otherindex = toProcess->getIndex(testVox); if (roiFrame != NULL && roiFrame[otherindex] <= 0.0f) { if (abs(offset.m_ijk[0]) + abs(offset.m_ijk[1]) + abs(offset.m_ijk[2]) == 1) { canBeMax = false; canBeMin = false; break; } else { continue; } } float otherval = dataFrame[otherindex]; if (myval < otherval)//since we are checking index bounds anyway, just do the double test to make the code simpler { minPos[otherindex] = 0; } else { canBeMin = false; if (!canBeMax) break; } if (myval > otherval) { maxPos[otherindex] = 0; } else { canBeMax = false; if (!canBeMin) break; } } } /*if (canBeMin && canBeMax)//this can't happen anymore { canBeMax = false;//don't count isolated voxels as extrema canBeMin = false; }//*/ } if (canBeMin) { #pragma omp critical { minima.push_back(VoxelIJK(i, j, k)); } } if (canBeMax) { #pragma omp critical { maxima.push_back(VoxelIJK(i, j, k)); } } } } } } } } void AlgorithmVolumeExtrema::findExtremaConsolidate(const VolumeFile* toProcess, const int& s, const int& c, const VolumeFile* myRoi, const float& distance, const bool& threshMode, const float& lowThresh, const float& highThresh, bool ignoreMinima, bool ignoreMaxima, vector& minima, vector& maxima) { const int stencil[18] = {-1, 0, 0, 0, -1, 0, 0, 0, -1, 0, 0, 1, 0, 1, 0, 1, 0, 0}; const int STENCIL_SIZE = 18; minima.clear(); maxima.clear(); vector myDims; toProcess->getDimensions(myDims); int64_t frameSize = myDims[0] * myDims[1] * myDims[2]; vector minPos(frameSize, 1), maxPos(frameSize, 1);//mark things off that fail a comparison, to reduce redundant comparisons const float* dataFrame = toProcess->getFrame(s, c); const float* roiFrame = NULL; vector > tempExtrema[2]; if (myRoi != NULL) { roiFrame = myRoi->getFrame(); } #pragma omp CARET_PARFOR schedule(dynamic) for (int64_t k = 0; k < myDims[2]; ++k) { bool ksafe = (k > 0 && k < myDims[2] - 1);//so we can avoid performing certain bounds checks in the inner loop for (int64_t j = 0; j < myDims[1]; ++j) { bool jsafe = (j > 0 && j < myDims[1] - 1); for (int64_t i = 0; i < myDims[0]; ++i) { int64_t myindex = toProcess->getIndex(i, j, k); if (roiFrame == NULL || roiFrame[myindex] > 0.0f) { bool canBeMin = minPos[myindex] && !ignoreMinima; bool canBeMax = maxPos[myindex] && !ignoreMaxima; float myval = dataFrame[myindex]; if (threshMode) { if (myval > lowThresh) canBeMin = false;//check thresholds if (myval < highThresh) canBeMax = false; } if (canBeMin || canBeMax) { bool isafe = (i > 0 && i < myDims[0] - 1); if (isafe && jsafe && ksafe)//special case out the inner loops to avoid branching when possible { if (roiFrame == NULL) { int v = 0; if (canBeMin && canBeMax)//no ROI and safe means we can just use the first element in stencil to break the double test { int64_t testVox[3] = { i + stencil[0], j + stencil[1], k + stencil[2] }; int64_t otherindex = toProcess->getIndex(testVox); float otherval = dataFrame[otherindex]; if (myval < otherval) { minPos[otherindex] = 0; } else { canBeMin = false; } if (myval > otherval) { maxPos[otherindex] = 0; } else { canBeMax = false; } v = 3; } if (canBeMin) { for (; v < STENCIL_SIZE; v += 3) { int64_t testVox[3] = { i + stencil[v], j + stencil[v + 1], k + stencil[v + 2] }; int64_t otherindex = toProcess->getIndex(testVox); float otherval = dataFrame[otherindex]; if (myval < otherval) { minPos[otherindex] = 0; } else { canBeMin = false; break; } } } if (canBeMax) { for (; v < STENCIL_SIZE; v += 3) { int64_t testVox[3] = { i + stencil[v], j + stencil[v + 1], k + stencil[v + 2] }; int64_t otherindex = toProcess->getIndex(testVox); float otherval = dataFrame[otherindex]; if (myval > otherval) { maxPos[otherindex] = 0; } else { canBeMax = false; break; } } } } else { if (canBeMin && canBeMax)//we have an roi, but if a neighbor falls outside it, we are done anyway, we don't count extrema on the edge { int64_t testVox[3] = { i + stencil[0], j + stencil[1], k + stencil[2] }; int64_t otherindex = toProcess->getIndex(testVox); if (roiFrame[otherindex] > 0.0f) { float otherval = dataFrame[otherindex]; if (myval < otherval) { minPos[otherindex] = 0; } else { canBeMin = false; } if (myval > otherval) { maxPos[otherindex] = 0; } else { canBeMax = false; } } else { canBeMax = false;//we are next to an roi edge, do not count this as an extrema canBeMin = false; } } int v = 3; if (canBeMin) { for (; v < STENCIL_SIZE; v += 3) { int64_t testVox[3] = { i + stencil[v], j + stencil[v + 1], k + stencil[v + 2] }; int64_t otherindex = toProcess->getIndex(testVox); if (roiFrame[otherindex] > 0.0f) { float otherval = dataFrame[otherindex]; if (myval < otherval) { minPos[otherindex] = 0; } else { canBeMin = false; break; } } else { canBeMin = false;//we are next to an roi edge, do not count this as an extrema break; } } } if (canBeMax) { for (; v < STENCIL_SIZE; v += 3) { int64_t testVox[3] = { i + stencil[v], j + stencil[v + 1], k + stencil[v + 2] }; int64_t otherindex = toProcess->getIndex(testVox); if (roiFrame[otherindex] > 0.0f) { float otherval = dataFrame[otherindex]; if (myval > otherval) { maxPos[otherindex] = 0; } else { canBeMax = false; break; } } else { canBeMax = false;//we are next to an roi edge, do not count this as an extrema } } } /*if (canBeMin && canBeMax)//this can't happen anymore { canBeMax = false;//don't count isolated voxels as extrema canBeMin = false; }//*/ } } else {//if we are near an edge, we have to check all the neighbor indexes for (int v = 0; v < STENCIL_SIZE; v += 3) { int64_t testVox[3] = { i + stencil[v], j + stencil[v + 1], k + stencil[v + 2] }; if (!ksafe && (testVox[2] < 0 || testVox[2] >= myDims[2])) continue; if (!jsafe && (testVox[1] < 0 || testVox[1] >= myDims[1])) continue; if (!isafe && (testVox[0] < 0 || testVox[0] >= myDims[0])) continue; int64_t otherindex = toProcess->getIndex(testVox); if (roiFrame != NULL && roiFrame[otherindex] <= 0.0f) { canBeMax = false;//neighbor outside the roi means on the roi edge, don't count as extrema canBeMin = false; break; } float otherval = dataFrame[otherindex]; if (myval < otherval)//since we are checking index bounds anyway, just do the double test to make the code simpler { minPos[otherindex] = 0; } else { canBeMin = false; if (!canBeMax) break; } if (myval > otherval) { maxPos[otherindex] = 0; } else { canBeMax = false; if (!canBeMin) break; } } /*if (canBeMin && canBeMax)//this can't happen anymore { canBeMax = false;//don't count isolated voxels as extrema canBeMin = false; }//*/ } if (canBeMax) { Vector3D tempvec; toProcess->indexToSpace(i, j, k, tempvec); #pragma omp critical { tempExtrema[0].push_back(pair(tempvec, 1)); } } if (canBeMin) { Vector3D tempvec; toProcess->indexToSpace(i, j, k, tempvec); #pragma omp critical { tempExtrema[1].push_back(pair(tempvec, 1)); } } } } } } } consolidateStep(toProcess, distance, tempExtrema, minima, maxima); } void AlgorithmVolumeExtrema::consolidateStep(const VolumeFile* toProcess, const float& distance, vector > initExtrema[2], vector& minima, vector& maxima) { for (int sign = 0; sign < 2; ++sign) { int numInitExtrema = (int)initExtrema[sign].size(); vector removed(numInitExtrema, false);//track which extrema locations are dropped during consolidation - the one that isn't dropped in a merge has its node number changed vector > heapIDmatrix(numInitExtrema, vector(numInitExtrema, -1)); CaretMinHeap, float> myDistHeap; for (int i = 0; i < numInitExtrema - 1; ++i) { for (int j = i + 1; j < numInitExtrema; ++j) { float tempf = (initExtrema[sign][i].first - initExtrema[sign][j].first).length(); if (tempf < distance) { int64_t tempID = myDistHeap.push(pair(i, j), tempf); heapIDmatrix[i][j] = tempID; heapIDmatrix[j][i] = tempID; } } }//initial distance matrix computed, now we iterate while (!myDistHeap.isEmpty()) { pair toMerge = myDistHeap.pop();//we don't need to know the key int extr1 = toMerge.first; int extr2 = toMerge.second; heapIDmatrix[extr1][extr2] = -1; heapIDmatrix[extr2][extr1] = -1; int weight1 = initExtrema[sign][extr1].second; int weight2 = initExtrema[sign][extr2].second; if (weight2 > weight1)//swap so weight1 is always bigger { int temp = weight2; weight2 = weight1; weight1 = temp; temp = extr2; extr2 = extr1; extr1 = temp; } Vector3D point1 = initExtrema[sign][extr1].first; Vector3D point2 = initExtrema[sign][extr2].first; removed[extr2] = true;//drop the one that has less weight, and modify the one that has more weight for (int j = 0; j < numInitExtrema; ++j) { if (!removed[j]) { int64_t tempID = heapIDmatrix[extr2][j]; if (tempID != -1) { myDistHeap.remove(tempID); heapIDmatrix[extr2][j] = -1; heapIDmatrix[j][extr2] = -1; } } } Vector3D newPoint = (point1 * weight1 + point2 * weight2) / (weight1 + weight2); initExtrema[sign][extr1].first = newPoint; for (int j = 0; j < numInitExtrema; ++j) { if (!removed[j]) { float tempf = (newPoint - initExtrema[sign][j].first).length(); int64_t tempID = heapIDmatrix[extr1][j]; if (tempf < distance) { if (tempID != -1) { myDistHeap.changekey(tempID, tempf); } else { tempID = myDistHeap.push(pair(extr1, j), tempf); heapIDmatrix[extr1][j] = tempID; heapIDmatrix[j][extr1] = tempID; } } else { if (tempID != -1) { myDistHeap.remove(tempID); heapIDmatrix[extr1][j] = -1; heapIDmatrix[j][extr1] = -1; } } } } } if (sign == 0) { for (int i = 0; i < numInitExtrema; ++i) { if (!removed[i]) { VoxelIJK tempijk; toProcess->enclosingVoxel(initExtrema[sign][i].first, tempijk.m_ijk); maxima.push_back(tempijk); } } } else { for (int i = 0; i < numInitExtrema; ++i) { if (!removed[i]) { VoxelIJK tempijk; toProcess->enclosingVoxel(initExtrema[sign][i].first, tempijk.m_ijk); minima.push_back(tempijk); } } } } } float AlgorithmVolumeExtrema::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmVolumeExtrema::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmVolumeExtrema.h000066400000000000000000000066371255417355300232370ustar00rootroot00000000000000#ifndef __ALGORITHM_VOLUME_EXTREMA_H__ #define __ALGORITHM_VOLUME_EXTREMA_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" #include "Vector3D.h" #include "VoxelIJK.h" #include namespace caret { class VolumeFile; class AlgorithmVolumeExtrema : public AbstractAlgorithm { std::vector m_stencil; int64_t m_irange, m_jrange, m_krange; AlgorithmVolumeExtrema(); void precomputeStencil(const VolumeFile* myVolIn, const float& distance); void findExtremaConsolidate(const VolumeFile* toProcess, const int& s, const int& c, const VolumeFile* myRoi, const float& distance, const bool& threshMode, const float& lowThresh, const float& highThresh, bool ignoreMinima, bool ignoreMaxima, std::vector& minima, std::vector& maxima); void findExtremaStencils(const VolumeFile* toProcess, const int& s, const int& c, const VolumeFile* myRoi, const bool& threshMode, const float& lowThresh, const float& highThresh, bool ignoreMinima, bool ignoreMaxima, std::vector& minima, std::vector& maxima); void consolidateStep(const VolumeFile* toProcess, const float& distance, std::vector > tempExtrema[2], std::vector& minima, std::vector& maxima); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmVolumeExtrema(ProgressObject* myProgObj, const VolumeFile* myVolIn, const float& distance, VolumeFile* myVolOut, const float& lowThresh, const float& highThresh, const VolumeFile* myRoi = NULL, const float& presmooth = -1.0f, const bool& sumSubvols = false, const bool& consolidateMode = false, bool ignoreMinima = false, bool ignoreMaxima = false, const int& subvol = -1); AlgorithmVolumeExtrema(ProgressObject* myProgObj, const VolumeFile* myVolIn, const float& distance, VolumeFile* myVolOut, const VolumeFile* myRoi = NULL, const float& presmooth = -1.0f, const bool& sumSubvols = false, const bool& consolidateMode = false, bool ignoreMinima = false, bool ignoreMaxima = false, const int& subvol = -1); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmVolumeExtrema; } #endif //__ALGORITHM_VOLUME_EXTREMA_H__ workbench-1.1.1/src/Algorithms/AlgorithmVolumeFillHoles.cxx000066400000000000000000000155151255417355300240610ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmVolumeFillHoles.h" #include "AlgorithmException.h" #include "VolumeFile.h" #include using namespace caret; using namespace std; AString AlgorithmVolumeFillHoles::getCommandSwitch() { return "-volume-fill-holes"; } AString AlgorithmVolumeFillHoles::getShortDescription() { return "FILL HOLES IN AN ROI VOLUME"; } OperationParameters* AlgorithmVolumeFillHoles::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addVolumeParameter(1, "volume-in", "the input ROI volume"); ret->addVolumeOutputParameter(2, "volume-out", "the output ROI volume"); ret->setHelpText( AString("Finds all face-connected parts that are not included in the ROI, and fills all but the largest one with ones.") ); return ret; } void AlgorithmVolumeFillHoles::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { VolumeFile* myVolIn = myParams->getVolume(1); VolumeFile* myVolOut = myParams->getOutputVolume(2); AlgorithmVolumeFillHoles(myProgObj, myVolIn, myVolOut); } AlgorithmVolumeFillHoles::AlgorithmVolumeFillHoles(ProgressObject* myProgObj, const VolumeFile* myVolIn, VolumeFile* myVolOut) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); const int STENCIL_SIZE = 18;//the easy way, and prepare for different stencils if we ever need them const int stencil[STENCIL_SIZE] = { 0, 0, -1, 0, -1, 0, -1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1 }; vector dims; myVolIn->getDimensions(dims); myVolOut->reinitialize(myVolIn->getOriginalDimensions(), myVolIn->getSform(), myVolIn->getNumberOfComponents(), myVolIn->getType()); for (int s = 0; s < dims[3]; ++s) { myVolOut->setMapName(s, myVolIn->getMapName(s)); for (int c = 0; c < dims[4]; ++c) { int64_t ijk[3]; vector > parts; vector used(dims[0] * dims[1] * dims[2], 0); const float* frame = myVolIn->getFrame(s, c); for (ijk[2] = 0; ijk[2] < dims[2]; ++ijk[2]) { for (ijk[1] = 0; ijk[1] < dims[1]; ++ijk[1]) { for (ijk[0] = 0; ijk[0] < dims[0]; ++ijk[0]) { int64_t index = myVolIn->getIndex(ijk); if (used[index] == 0 && !(frame[index] > 0.0f))//use "not greater than" in case someone uses NaNs in their ROI { parts.push_back(vector()); vector& thispart = parts.back(); thispart.push_back(ijk[0]); thispart.push_back(ijk[1]); thispart.push_back(ijk[2]); used[index] = 1; vector mystack; mystack.push_back(ijk[0]); mystack.push_back(ijk[1]); mystack.push_back(ijk[2]); while (!mystack.empty()) { int64_t curSize = (int64_t)mystack.size(); int64_t curvox[3] = { mystack[curSize - 3], mystack[curSize - 2], mystack[curSize - 1] }; mystack.resize(curSize - 3);//basically pop_back for (int i = 0; i < STENCIL_SIZE; i += 3) { int64_t neighbor[3] = { curvox[0] + stencil[i], curvox[1] + stencil[i + 1], curvox[2] + stencil[i + 2] }; if (myVolIn->indexValid(neighbor)) { int64_t neighindex = myVolIn->getIndex(neighbor); if (used[neighindex] == 0 && !(frame[neighindex] > 0.0f)) { thispart.push_back(neighbor[0]); thispart.push_back(neighbor[1]); thispart.push_back(neighbor[2]); used[neighindex] = 1; mystack.push_back(neighbor[0]); mystack.push_back(neighbor[1]); mystack.push_back(neighbor[2]); } } } } } } } } int64_t bestCount = -1, bestPart = -1, numParts = (int64_t)parts.size(); for (int64_t i = 0; i < numParts; ++i) { int64_t thisCount = (int64_t)parts[i].size(); if (thisCount > bestCount) { bestCount = thisCount; bestPart = i; } } vector outFrame(dims[0] * dims[1] * dims[2], 1.0f); if (bestPart != -1) { vector& myPart = parts[bestPart]; for (int64_t i = 0; i < bestCount; i += 3) { int64_t myIndex = myVolIn->getIndex(myPart.data() + i); outFrame[myIndex] = 0.0f;//make it a simple 0/1 volume, even if it wasn't before } } myVolOut->setFrame(outFrame.data(), s, c); } } } float AlgorithmVolumeFillHoles::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmVolumeFillHoles::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmVolumeFillHoles.h000066400000000000000000000032401255417355300234760ustar00rootroot00000000000000#ifndef __ALGORITHM_VOLUME_FILL_HOLES_H__ #define __ALGORITHM_VOLUME_FILL_HOLES_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmVolumeFillHoles : public AbstractAlgorithm { AlgorithmVolumeFillHoles(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmVolumeFillHoles(ProgressObject* myProgObj, const VolumeFile* myVolIn, VolumeFile* myVolOut); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmVolumeFillHoles; } #endif //__ALGORITHM_VOLUME_FILL_HOLES_H__ workbench-1.1.1/src/Algorithms/AlgorithmVolumeFindClusters.cxx000066400000000000000000000347351255417355300246120ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmVolumeFindClusters.h" #include "AlgorithmException.h" #include "CaretAssert.h" #include "CaretLogger.h" #include "CaretPointer.h" #include "CaretPointLocator.h" #include "VolumeFile.h" #include "VoxelIJK.h" #include #include using namespace caret; using namespace std; AString AlgorithmVolumeFindClusters::getCommandSwitch() { return "-volume-find-clusters"; } AString AlgorithmVolumeFindClusters::getShortDescription() { return "FILTER CLUSTERS BY VOLUME"; } OperationParameters* AlgorithmVolumeFindClusters::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addVolumeParameter(1, "volume-in", "the input volume"); ret->addDoubleParameter(2, "value-threshold", "threshold for data values"); ret->addDoubleParameter(3, "minimum-volume", "threshold for cluster volume, in mm^3"); ret->addVolumeOutputParameter(4, "volume-out", "the output volume"); ret->createOptionalParameter(5, "-less-than", "find values less than , rather than greater"); OptionalParameter* roiOption = ret->createOptionalParameter(6, "-roi", "select a region of interest"); roiOption->addMetricParameter(1, "roi-volume", "the roi, as a volume file"); OptionalParameter* subvolSelect = ret->createOptionalParameter(7, "-subvolume", "select a single subvolume"); subvolSelect->addStringParameter(1, "subvol", "the subvolume number or name"); OptionalParameter* sizeRatioOpt = ret->createOptionalParameter(9, "-size-ratio", "ignore clusters smaller than a given fraction of the largest cluster in map"); sizeRatioOpt->addDoubleParameter(1, "ratio", "fraction of the largest cluster's volume"); OptionalParameter* distanceOpt = ret->createOptionalParameter(10, "-distance", "ignore clusters further than a given distance from the largest cluster"); distanceOpt->addDoubleParameter(1, "distance", "how far from the largest cluster a cluster can be, edge to edge, in mm"); OptionalParameter* startOpt = ret->createOptionalParameter(8, "-start", "start labeling clusters from a value other than 1"); startOpt->addIntegerParameter(1, "startval", "the value to give the first cluster found"); ret->setHelpText( AString("Outputs a volume with nonzero integers for all voxels within a large enough cluster, and zeros elsewhere. ") + "The integers denote cluster membership (by default, first cluster found will use value 1, second cluster 2, etc). " + "By default, values greater than are considered to be in a cluster, use -less-than to test for values less than the threshold. " + "To apply this as a mask to the data, or to do more complicated thresholding, see -volume-math." ); return ret; } void AlgorithmVolumeFindClusters::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { VolumeFile* volIn = myParams->getVolume(1); float threshValue = (float)myParams->getDouble(2); float minVolume = (float)myParams->getDouble(3); VolumeFile* volOut = myParams->getOutputVolume(4); bool lessThan = myParams->getOptionalParameter(5)->m_present; VolumeFile* myRoi = NULL; OptionalParameter* roiOption = myParams->getOptionalParameter(6); if (roiOption->m_present) { myRoi = roiOption->getVolume(1); } OptionalParameter* subvolSelect = myParams->getOptionalParameter(7); int subvolNum = -1; if (subvolSelect->m_present) { subvolNum = (int)volIn->getMapIndexFromNameOrNumber(subvolSelect->getString(1)); if (subvolNum < 0) { throw AlgorithmException("invalid subvolume specified"); } } OptionalParameter* startOpt = myParams->getOptionalParameter(8); int startVal = 1; if (startOpt->m_present) { startVal = (int)startOpt->getInteger(1); } OptionalParameter* sizeRatioOpt = myParams->getOptionalParameter(9); float sizeRatio = -1.0f; if (sizeRatioOpt->m_present) { sizeRatio = sizeRatioOpt->getDouble(1); if (sizeRatio <= 0.0f) { throw AlgorithmException("volume ratio must be positive"); } } OptionalParameter* distanceOpt = myParams->getOptionalParameter(10); float distaceCutoff = -1.0f; if (distanceOpt->m_present) { distaceCutoff = distanceOpt->getDouble(1); if (distaceCutoff <= 0.0f) { throw AlgorithmException("distance cutoff must be positive"); } } AlgorithmVolumeFindClusters(myProgObj, volIn, threshValue, minVolume, volOut, lessThan, myRoi, subvolNum, startVal, NULL, sizeRatio, distaceCutoff); } namespace { void processSubvol(const float* inFrame, VolumeFile* volOut, const int64_t& outSubvol, const int64_t& outComponent, const float& threshValue, const float& minVolume, const bool& lessThan, const float* roiFrame, const float& sizeRatio, const float& distanceCutoff, int& markVal) { vector dims = volOut->getDimensions(); int64_t frameSize = dims[0] * dims[1] * dims[2]; const VolumeSpace& mySpace = volOut->getVolumeSpace(); Vector3D ivec, jvec, kvec, origin; mySpace.getSpacingVectors(ivec, jvec, kvec, origin); float voxelVolume = abs(ivec.dot(jvec.cross(kvec))); int64_t minVoxels = (int64_t)ceil(minVolume / voxelVolume); vector neighbors; neighbors.push_back(VoxelIJK(1, 0, 0)); neighbors.push_back(VoxelIJK(-1, 0, 0)); neighbors.push_back(VoxelIJK(0, 1, 0)); neighbors.push_back(VoxelIJK(0, -1, 0)); neighbors.push_back(VoxelIJK(0, 0, 1)); neighbors.push_back(VoxelIJK(0, 0, -1)); vector marked(frameSize, 0); if (lessThan) { for (int64_t i = 0; i < frameSize; ++i) { if ((roiFrame == NULL || roiFrame[i] > 0.0f) && inFrame[i] < threshValue) { marked[i] = 1; } } } else { for (int64_t i = 0; i < frameSize; ++i) { if ((roiFrame == NULL || roiFrame[i] > 0.0f) && inFrame[i] > threshValue) { marked[i] = 1; } } } vector > clusters; size_t biggestCount = 0; int64_t biggestCluster = -1; for (int64_t k = 0; k < dims[2]; ++k) { for (int64_t j = 0; j < dims[1]; ++j) { for (int64_t i = 0; i < dims[0]; ++i) { int64_t myIndex = mySpace.getIndex(i, j, k); if (marked[myIndex]) { vector voxelList; voxelList.push_back(VoxelIJK(i, j, k)); marked[myIndex] = 0;//don't let voxels get duplicated in the list for (int64_t index = 0; index < (int64_t)voxelList.size(); ++index)//NOTE: vector grows inside loop { const VoxelIJK& thisVoxel = voxelList[index]; for (int n = 0; n < (int)neighbors.size(); ++n) { VoxelIJK neighVox(thisVoxel.m_ijk[0] + neighbors[n].m_ijk[0], thisVoxel.m_ijk[1] + neighbors[n].m_ijk[1], thisVoxel.m_ijk[2] + neighbors[n].m_ijk[2]); if (mySpace.indexValid(neighVox.m_ijk)) { int64_t neighIndex = mySpace.getIndex(neighVox.m_ijk); if (marked[neighIndex]) { voxelList.push_back(neighVox); marked[neighIndex] = 0; } } } } if ((int64_t)voxelList.size() >= minVoxels) { if (voxelList.size() > biggestCount) { biggestCount = voxelList.size(); biggestCluster = (int64_t)clusters.size(); } clusters.push_back(voxelList); } } } } } if (!clusters.empty()) CaretAssert(biggestCluster != -1); if (biggestCluster != -1 && (distanceCutoff > 0.0f || sizeRatio > 0.0f)) { CaretPointer myLocator; if (distanceCutoff > 0.0f) { vector biggestCoords;//gather coordinates of biggest cluster voxels biggestCoords.reserve(biggestCount * 3); for (size_t i = 0; i < clusters[biggestCluster].size(); ++i) { float thisCoord[3]; mySpace.indexToSpace(clusters[biggestCluster][i].m_ijk, thisCoord); biggestCoords.push_back(thisCoord[0]); biggestCoords.push_back(thisCoord[1]); biggestCoords.push_back(thisCoord[2]); } myLocator.grabNew(new CaretPointLocator(biggestCoords.data(), biggestCoords.size())); } for (size_t i = 0; i < clusters.size(); ++i) { if ((int64_t)i != biggestCluster) { bool erase = false; if (sizeRatio > 0.0f && ((float)clusters[i].size()) / biggestCount < sizeRatio) { erase = true; } if (!erase && distanceCutoff > 0.0f) { erase = true;//erase unless we find a point close enough to the biggest cluster for (size_t j = 0; j < clusters[i].size(); ++j) { float thisCoord[3]; mySpace.indexToSpace(clusters[i][j].m_ijk, thisCoord); int32_t ret = myLocator->closestPointLimited(thisCoord, distanceCutoff); if (ret == -1) { erase = false; break; } } } if (erase) { clusters.erase(clusters.begin() + i);//remove it --i;//don't skip a cluster if (biggestCluster > (int64_t)i) --biggestCluster;//don't lose track of the biggest cluster } } } } for (size_t i = 0; i < clusters.size(); ++i) { if (markVal == 0) { CaretLogInfo("skipping 0 for cluster marking"); ++markVal; } float tempVal = markVal; if ((int)tempVal != markVal) throw AlgorithmException("too many clusters, unable to mark them uniquely"); for (size_t index = 0; index < clusters[i].size(); ++index) { volOut->setValue(tempVal, clusters[i][index].m_ijk, outSubvol, outComponent); } ++markVal; } } } AlgorithmVolumeFindClusters::AlgorithmVolumeFindClusters(ProgressObject* myProgObj, const VolumeFile* volIn, const float& threshValue, const float& minVolume, VolumeFile* volOut, const bool& lessThan, const VolumeFile* myRoi, const int& subvolNum, const int& startVal, int* endVal, const float& sizeRatio, const float& distanceCutoff) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); if (startVal == 0) { throw AlgorithmException("0 is not a valid cluster marking start value"); } const VolumeSpace& mySpace = volIn->getVolumeSpace(); const float* roiFrame = NULL; if (myRoi != NULL) { if (!mySpace.matches(myRoi->getVolumeSpace())) throw AlgorithmException("roi volume space does not match input"); roiFrame = myRoi->getFrame(); } vector dims = volIn->getDimensions(); int markVal = startVal; if (subvolNum == -1) { volOut->reinitialize(volIn->getOriginalDimensions(), volIn->getSform(), dims[4]); volOut->setValueAllVoxels(0.0f); for (int64_t c = 0; c < dims[4]; ++c) { for (int64_t s = 0; s < dims[3]; ++s) { const float* inFrame = volIn->getFrame(s, c); processSubvol(inFrame, volOut, s, c, threshValue, minVolume, lessThan, roiFrame, sizeRatio, distanceCutoff, markVal); } } } else { vector outDims = volIn->getOriginalDimensions(); outDims.resize(3); volOut->reinitialize(outDims, volIn->getSform(), dims[4]); volOut->setValueAllVoxels(0.0f); for (int64_t c = 0; c < dims[4]; ++c) { const float* inFrame = volIn->getFrame(subvolNum, c); processSubvol(inFrame, volOut, 0, c, threshValue, minVolume, lessThan, roiFrame, sizeRatio, distanceCutoff, markVal); } } if (endVal != NULL) *endVal = markVal; } float AlgorithmVolumeFindClusters::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmVolumeFindClusters::getSubAlgorithmWeight() { return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmVolumeFindClusters.h000066400000000000000000000037711255417355300242330ustar00rootroot00000000000000#ifndef __ALGORITHM_VOLUME_FIND_CLUSTERS_H__ #define __ALGORITHM_VOLUME_FIND_CLUSTERS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmVolumeFindClusters : public AbstractAlgorithm { AlgorithmVolumeFindClusters(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmVolumeFindClusters(ProgressObject* myProgObj, const VolumeFile* volIn, const float& threshValue, const float& minVolume, VolumeFile* volOut, const bool& lessThan = false, const VolumeFile* myRoi = NULL, const int& subvolNum = -1, const int& startVal = 1, int* endVal = NULL, const float& sizeRatio = -1.0f, const float& distanceCutoff = -1.0f); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmVolumeFindClusters; } #endif //__ALGORITHM_VOLUME_FIND_CLUSTERS_H__ workbench-1.1.1/src/Algorithms/AlgorithmVolumeGradient.cxx000066400000000000000000001166201255417355300237340ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmException.h" #include "AlgorithmVolumeGradient.h" #include "AlgorithmVolumeSmoothing.h" #include "CaretOMP.h" #include "FloatMatrix.h" #include "MathFunctions.h" #include "Vector3D.h" #include "VolumeFile.h" #include #include using namespace caret; using namespace std; AString AlgorithmVolumeGradient::getCommandSwitch() { return "-volume-gradient"; } AString AlgorithmVolumeGradient::getShortDescription() { return "GRADIENT OF A VOLUME FILE"; } OperationParameters* AlgorithmVolumeGradient::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addVolumeParameter(1, "volume-in", "the input volume"); ret->addVolumeOutputParameter(2, "volume-out", "the output gradient magnitude volume"); OptionalParameter* presmoothOpt = ret->createOptionalParameter(3, "-presmooth", "smooth the volume before computing the gradient"); presmoothOpt->addDoubleParameter(1, "kernel", "sigma for gaussian weighting function, in mm"); OptionalParameter* roiOption = ret->createOptionalParameter(4, "-roi", "select a region of interest to take the gradient of"); roiOption->addVolumeParameter(1, "roi-volume", "the region to take the gradient within"); OptionalParameter* vecOption = ret->createOptionalParameter(5, "-vectors", "output vectors"); vecOption->addVolumeOutputParameter(1, "vector-volume-out", "the vectors as a volume file"); OptionalParameter* subvolSelect = ret->createOptionalParameter(6, "-subvolume", "select a single subvolume to take the gradient of"); subvolSelect->addStringParameter(1, "subvol", "the subvolume number or name"); ret->setHelpText( AString("Computes the gradient of the volume by doing linear regressions for each voxel, considering only its face neighbors unless too few face neighbors exist. ") + "The gradient vector is constructed from the partial derivatives of the resulting linear function, and the magnitude of this vector is the output. " + "If specified, the volume vector output is arranged with the x, y, and z components from a subvolume as consecutive subvolumes." ); return ret; } void AlgorithmVolumeGradient::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { VolumeFile* volIn = myParams->getVolume(1); VolumeFile* volOut = myParams->getOutputVolume(2); float presmooth = -1.0f; OptionalParameter* presmoothOpt = myParams->getOptionalParameter(3); if (presmoothOpt->m_present) { presmooth = (float)presmoothOpt->getDouble(1); } VolumeFile* myRoi = NULL; OptionalParameter* roiOption = myParams->getOptionalParameter(4); if (roiOption->m_present) { myRoi = roiOption->getVolume(1); } VolumeFile* vectorsOut = NULL; OptionalParameter* vecOption = myParams->getOptionalParameter(5); if (vecOption->m_present) { vectorsOut = vecOption->getOutputVolume(1); } OptionalParameter* subvolSelect = myParams->getOptionalParameter(6); int subvolNum = -1; if (subvolSelect->m_present) { subvolNum = (int)volIn->getMapIndexFromNameOrNumber(subvolSelect->getString(1)); if (subvolNum < 0) { throw AlgorithmException("invalid subvolume specified"); } } AlgorithmVolumeGradient(myProgObj, volIn, volOut, presmooth, myRoi, vectorsOut, subvolNum); } AlgorithmVolumeGradient::AlgorithmVolumeGradient(ProgressObject* myProgObj, const VolumeFile* volIn, VolumeFile* volOut, const float& presmooth, const VolumeFile* myRoi, VolumeFile* vectorsOut, const int& subvolNum) : AbstractAlgorithm(myProgObj) { ProgressObject* smoothProgress = NULL; if (myProgObj != NULL && presmooth > 0.0f) { smoothProgress = myProgObj->addAlgorithm(AlgorithmVolumeSmoothing::getAlgorithmWeight()); } LevelProgress myProgress(myProgObj); if (myRoi != NULL && !volIn->matchesVolumeSpace(myRoi)) { throw AlgorithmException("roi volume space does not match input"); } if (subvolNum < -1 || subvolNum > volIn->getNumberOfMaps()) { throw AlgorithmException("invalid subvolume specified"); } VolumeFile smoothVol; const VolumeFile* processVol = volIn; int useSubvol = subvolNum; if (presmooth > 0.0f) { AlgorithmVolumeSmoothing(smoothProgress, volIn, presmooth, &smoothVol, myRoi, false, subvolNum); processVol = &smoothVol; if (subvolNum != -1) { useSubvol = 0; } } vector origDims = volIn->getOriginalDimensions(), myDims; volIn->getDimensions(myDims); int stencil[] = { 0, 0, 1, 0, 0, -1, 0, 1, 0, 0, -1, 0, 1, 0, 0, -1, 0, 0 }; vector > volSpace = volIn->getSform(); Vector3D ivec, jvec, kvec, origin; ivec[0] = volSpace[0][0]; jvec[0] = volSpace[0][1]; kvec[0] = volSpace[0][2]; origin[0] = volSpace[0][3]; ivec[1] = volSpace[1][0]; jvec[1] = volSpace[1][1]; kvec[1] = volSpace[1][2]; origin[1] = volSpace[1][3]; ivec[2] = volSpace[2][0]; jvec[2] = volSpace[2][1]; kvec[2] = volSpace[2][2]; origin[2] = volSpace[2][3];//TODO: special case orthogonal volumes (central difference)? const float* roiFrame = NULL; if (myRoi != NULL) { roiFrame = myRoi->getFrame(); } if (subvolNum == -1) { volOut->reinitialize(origDims, volIn->getSform(), myDims[4], volIn->getType()); if (vectorsOut != NULL) { while (origDims.size() < 4) { origDims.push_back(1); } origDims[3] *= 3; vectorsOut->reinitialize(origDims, volIn->getSform(), myDims[4], volIn->getType()); } for (int c = 0; c < myDims[4]; ++c) { for (int s = 0; s < myDims[3]; ++s) { const float* inFrame = processVol->getFrame(s, c); #pragma omp CARET_PARFOR schedule(dynamic) for (int k = 0; k < myDims[2]; ++k) { for (int j = 0; j < myDims[1]; ++j) { for (int i = 0; i < myDims[0]; ++i) { float magnitude; Vector3D gradient; if (myRoi == NULL || myRoi->getValue(i, j, k) > 0.0f) { float curval = processVol->getValue(i, j, k, s, c); FloatMatrix regress = FloatMatrix::zeros(4, 5); regress[3][3] = 1;//count the center voxel in case neighbors are missing (displacement and valdiff are zero, cancelling all other terms) int dircheck = 0; for (int neighbase = 0; neighbase < 18; neighbase += 3) { int ikern = i + stencil[neighbase]; int jkern = j + stencil[neighbase + 1]; int kkern = k + stencil[neighbase + 2]; if (volIn->indexValid(ikern, jkern, kkern)) { int64_t kernIndex = volIn->getIndex(ikern, jkern, kkern); if (roiFrame == NULL || roiFrame[kernIndex] > 0.0f) { dircheck |= 1<<(neighbase / 6);//uses the ordering of the neighbors to map neighbor to direction float valdiff = inFrame[kernIndex] - curval; Vector3D displacement = ivec * stencil[neighbase] + jvec * stencil[neighbase + 1] + kvec * stencil[neighbase + 2]; regress[0][0] += displacement[0] * displacement[0];//note: this is the generic code, built to handle strange volumes, to get more speed, test regress[0][1] += displacement[0] * displacement[1];// for orthogonal volume, and use central differences (if no ROI, could special case for even more speed) regress[0][2] += displacement[0] * displacement[2];//but, seems reasonably fast anyway regress[0][3] += displacement[0]; regress[0][4] += displacement[0] * valdiff; regress[1][1] += displacement[1] * displacement[1]; regress[1][2] += displacement[1] * displacement[2]; regress[1][3] += displacement[1]; regress[1][4] += displacement[1] * valdiff; regress[2][2] += displacement[2] * displacement[2]; regress[2][3] += displacement[2]; regress[2][4] += displacement[2] * valdiff; regress[3][3] += 1; regress[3][4] += valdiff; } } } if (dircheck == 7)//have at least one neighbor in every index axis, continue { regress[1][0] = regress[0][1];//finish the symmetric part of the matrix regress[2][0] = regress[0][2]; regress[2][1] = regress[1][2]; regress[3][0] = regress[0][3]; regress[3][1] = regress[1][3]; regress[3][2] = regress[2][3]; FloatMatrix result = regress.reducedRowEchelon(); gradient[0] = result[0][4]; gradient[1] = result[1][4]; gradient[2] = result[2][4];//[3][4] is the constant part of the regression magnitude = gradient.length(); if (!MathFunctions::isNumeric(magnitude)) { magnitude = 0.0f; gradient[0] = 0.0f; gradient[1] = 0.0f; gradient[2] = 0.0f; } } else {//fallback 1: regression with 26-neighbors Vector3D directions[3];//track the displacements in index space for simplicity int dirUsed = 0; if (dircheck & 1) { directions[dirUsed][0] = 1; ++dirUsed; } if (dircheck & 2) { directions[dirUsed][1] = 1; ++dirUsed; } if (dircheck & 4) { directions[dirUsed][2] = 1; ++dirUsed; } int imin = max(i - 1, 0), jmin = max(j - 1, 0), kmin = max(k - 1, 0); int imax = min(i + 2, (int)myDims[0]), jmax = min(j + 2, (int)myDims[1]), kmax = min(k + 2, (int)myDims[2]); Vector3D voxelDir; for (int kkern = kmin; kkern < kmax; ++kkern) { voxelDir[2] = kkern - k; int kabs = abs(kkern - k); int64_t kindpart = kkern * myDims[1]; for (int jkern = jmin; jkern < jmax; ++jkern) { int jabs = abs(jkern - j) + kabs; if (jabs > 0)//skip inner loop if it won't get any new neighbors { int64_t jindpart = (kindpart + jkern) * myDims[0]; voxelDir[1] = jkern - j; for (int ikern = imin; ikern < imax; ++ikern) { int64_t kernIndex = jindpart + ikern; if (jabs + abs(ikern - i) > 1 && (roiFrame == NULL || roiFrame[kernIndex] > 0.0f))//only add non-face neighbors { if (dirUsed < 3)//check for singularity via base vectors being dependent { bool newDir = true; switch (dirUsed) { case 0: default: break; case 1: if (voxelDir.cross(directions[0]).length() < 0.01f) newDir = false; break; case 2: if (voxelDir.cross(directions[0]).cross(voxelDir.cross(directions[1])).length() < 0.01f) newDir = false; break; } if (newDir) { directions[dirUsed] = voxelDir; ++dirUsed; } } voxelDir[0] = ikern - i; Vector3D displacement = ivec * voxelDir[0] + jvec * voxelDir[1] + kvec * voxelDir[2]; float valdiff = inFrame[kernIndex] - curval; regress[0][0] += displacement[0] * displacement[0]; regress[0][1] += displacement[0] * displacement[1]; regress[0][2] += displacement[0] * displacement[2]; regress[0][3] += displacement[0]; regress[0][4] += displacement[0] * valdiff; regress[1][1] += displacement[1] * displacement[1]; regress[1][2] += displacement[1] * displacement[2]; regress[1][3] += displacement[1]; regress[1][4] += displacement[1] * valdiff; regress[2][2] += displacement[2] * displacement[2]; regress[2][3] += displacement[2]; regress[2][4] += displacement[2] * valdiff; regress[3][3] += 1; regress[3][4] += valdiff; } } } } } if (dirUsed == 3) { regress[1][0] = regress[0][1];//finish the symmetric part of the matrix regress[2][0] = regress[0][2]; regress[2][1] = regress[1][2]; regress[3][0] = regress[0][3]; regress[3][1] = regress[1][3]; regress[3][2] = regress[2][3]; FloatMatrix result = regress.reducedRowEchelon(); gradient[0] = result[0][4]; gradient[1] = result[1][4]; gradient[2] = result[2][4];//[3][4] is the constant part of the regression magnitude = gradient.length(); if (!MathFunctions::isNumeric(magnitude)) { magnitude = 0.0f; gradient[0] = 0.0f; gradient[1] = 0.0f; gradient[2] = 0.0f; } } else {//fallback 2: average forward differences in 26-neighborhood Vector3D accum; int accumCount = 0; for (int kkern = kmin; kkern < kmax; ++kkern) { voxelDir[2] = kkern - k; int64_t kindpart = kkern * myDims[1]; for (int jkern = jmin; jkern < jmax; ++jkern) { int64_t jindpart = (kindpart + jkern) * myDims[0]; voxelDir[1] = jkern - j; for (int ikern = imin; ikern < imax; ++ikern) { int64_t kernIndex = jindpart + ikern; if (roiFrame == NULL || roiFrame[kernIndex] > 0.0f) { float valdiff = inFrame[kernIndex] - curval; voxelDir[0] = ikern - i; Vector3D displacement = ivec * voxelDir[0] + jvec * voxelDir[1] + kvec * voxelDir[2]; float length = displacement.length(); if (length > 0.0f) { accum += displacement * (valdiff / (length * length));//once to normalize vector, and once to find gradient magnitude ++accumCount; } } } } } if (accumCount > 0) { gradient = accum / accumCount; magnitude = gradient.length(); if (!MathFunctions::isNumeric(magnitude)) { magnitude = 0.0f; gradient[0] = 0.0f; gradient[1] = 0.0f; gradient[2] = 0.0f; } } else { magnitude = 0.0f; gradient[0] = 0.0f; gradient[1] = 0.0f; gradient[2] = 0.0f; } } } } else { magnitude = 0.0f; gradient[0] = 0.0f; gradient[1] = 0.0f; gradient[2] = 0.0f; } volOut->setValue(magnitude, i, j, k, s, c); if (vectorsOut != NULL) { int subvolbase = s * 3; vectorsOut->setValue(gradient[0], i, j, k, subvolbase, c); vectorsOut->setValue(gradient[1], i, j, k, subvolbase + 1, c); vectorsOut->setValue(gradient[2], i, j, k, subvolbase + 2, c); } } } } } } } else { origDims.resize(3); volOut->reinitialize(origDims, volIn->getSform(), myDims[4], volIn->getType()); if (vectorsOut != NULL) { origDims.push_back(3); vectorsOut->reinitialize(origDims, volIn->getSform(), myDims[4], volIn->getType()); } for (int c = 0; c < myDims[4]; ++c) { const float* inFrame = processVol->getFrame(useSubvol, c); #pragma omp CARET_PARFOR schedule(dynamic) for (int k = 0; k < myDims[2]; ++k) { for (int j = 0; j < myDims[1]; ++j) { for (int i = 0; i < myDims[0]; ++i) { float magnitude; Vector3D gradient; if (myRoi == NULL || myRoi->getValue(i, j, k) > 0.0f) { float curval = processVol->getValue(i, j, k, useSubvol, c); FloatMatrix regress = FloatMatrix::zeros(4, 5); regress[3][3] = 1;//count the center voxel in case neighbors are missing (displacement and valdiff are zero, cancelling all other terms) int dircheck = 0; for (int neighbase = 0; neighbase < 18; neighbase += 3) { int ikern = i + stencil[neighbase]; int jkern = j + stencil[neighbase + 1]; int kkern = k + stencil[neighbase + 2]; if (volIn->indexValid(ikern, jkern, kkern)) { int64_t kernIndex = volIn->getIndex(ikern, jkern, kkern); if (roiFrame == NULL || roiFrame[kernIndex] > 0.0f) { dircheck |= 1<<(neighbase / 6);//uses the ordering of the neighbors to map neighbor to direction float valdiff = inFrame[kernIndex] - curval; Vector3D displacement = ivec * stencil[neighbase] + jvec * stencil[neighbase + 1] + kvec * stencil[neighbase + 2]; regress[0][0] += displacement[0] * displacement[0];//note: this is the generic code, built to handle strange volumes, to get more speed, test regress[0][1] += displacement[0] * displacement[1];// for orthogonal volume, and use central differences (if no ROI, could special case for even more speed) regress[0][2] += displacement[0] * displacement[2]; regress[0][3] += displacement[0]; regress[0][4] += displacement[0] * valdiff; regress[1][1] += displacement[1] * displacement[1]; regress[1][2] += displacement[1] * displacement[2]; regress[1][3] += displacement[1]; regress[1][4] += displacement[1] * valdiff; regress[2][2] += displacement[2] * displacement[2]; regress[2][3] += displacement[2]; regress[2][4] += displacement[2] * valdiff; regress[3][3] += 1; regress[3][4] += valdiff; } } } if (dircheck == 7)//have at least one neighbor in every index axis, continue { regress[1][0] = regress[0][1];//finish the symmetric part of the matrix regress[2][0] = regress[0][2]; regress[2][1] = regress[1][2]; regress[3][0] = regress[0][3]; regress[3][1] = regress[1][3]; regress[3][2] = regress[2][3]; FloatMatrix result = regress.reducedRowEchelon(); gradient[0] = result[0][4]; gradient[1] = result[1][4]; gradient[2] = result[2][4];//[3][4] is the constant part of the regression magnitude = gradient.length(); if (!MathFunctions::isNumeric(magnitude)) { magnitude = 0.0f; gradient[0] = 0.0f; gradient[1] = 0.0f; gradient[2] = 0.0f; } } else {//fallback 1: regression with 26-neighbors Vector3D directions[3];//track the displacements in index space for simplicity int dirUsed = 0; if (dircheck & 1) { directions[dirUsed][0] = 1; ++dirUsed; } if (dircheck & 2) { directions[dirUsed][1] = 1; ++dirUsed; } if (dircheck & 4) { directions[dirUsed][2] = 1; ++dirUsed; } int imin = max(i - 1, 0), jmin = max(j - 1, 0), kmin = max(k - 1, 0); int imax = min(i + 2, (int)myDims[0]), jmax = min(j + 2, (int)myDims[1]), kmax = min(k + 2, (int)myDims[2]); Vector3D voxelDir; for (int kkern = kmin; kkern < kmax; ++kkern) { voxelDir[2] = kkern - k; int kabs = abs(kkern - k); int64_t kindpart = kkern * myDims[1]; for (int jkern = jmin; jkern < jmax; ++jkern) { int jabs = abs(jkern - j) + kabs; if (jabs > 0)//skip inner loop if it won't get any new neighbors { int64_t jindpart = (kindpart + jkern) * myDims[0]; voxelDir[1] = jkern - j; for (int ikern = imin; ikern < imax; ++ikern) { int64_t kernIndex = jindpart + ikern; if (jabs + abs(ikern - i) > 1 && (roiFrame == NULL || roiFrame[kernIndex] > 0.0f))//only add non-face neighbors { if (dirUsed < 3)//check for singularity via base vectors being dependent { bool newDir = true; switch (dirUsed) { case 0: default: break; case 1: if (voxelDir.cross(directions[0]).length() < 0.01f) newDir = false; break; case 2: if (voxelDir.cross(directions[0]).cross(voxelDir.cross(directions[1])).length() < 0.01f) newDir = false; break; } if (newDir) { directions[dirUsed] = voxelDir; ++dirUsed; } } voxelDir[0] = ikern - i; Vector3D displacement = ivec * voxelDir[0] + jvec * voxelDir[1] + kvec * voxelDir[2]; float valdiff = inFrame[kernIndex] - curval; regress[0][0] += displacement[0] * displacement[0]; regress[0][1] += displacement[0] * displacement[1]; regress[0][2] += displacement[0] * displacement[2]; regress[0][3] += displacement[0]; regress[0][4] += displacement[0] * valdiff; regress[1][1] += displacement[1] * displacement[1]; regress[1][2] += displacement[1] * displacement[2]; regress[1][3] += displacement[1]; regress[1][4] += displacement[1] * valdiff; regress[2][2] += displacement[2] * displacement[2]; regress[2][3] += displacement[2]; regress[2][4] += displacement[2] * valdiff; regress[3][3] += 1; regress[3][4] += valdiff; } } } } } if (dirUsed == 3) { regress[1][0] = regress[0][1];//finish the symmetric part of the matrix regress[2][0] = regress[0][2]; regress[2][1] = regress[1][2]; regress[3][0] = regress[0][3]; regress[3][1] = regress[1][3]; regress[3][2] = regress[2][3]; FloatMatrix result = regress.reducedRowEchelon(); gradient[0] = result[0][4]; gradient[1] = result[1][4]; gradient[2] = result[2][4];//[3][4] is the constant part of the regression magnitude = gradient.length(); if (!MathFunctions::isNumeric(magnitude)) { magnitude = 0.0f; gradient[0] = 0.0f; gradient[1] = 0.0f; gradient[2] = 0.0f; } } else {//fallback 2: average forward differences in 26-neighborhood Vector3D accum; int accumCount = 0; for (int kkern = kmin; kkern < kmax; ++kkern) { voxelDir[2] = kkern - k; int64_t kindpart = kkern * myDims[1]; for (int jkern = jmin; jkern < jmax; ++jkern) { int64_t jindpart = (kindpart + jkern) * myDims[0]; voxelDir[1] = jkern - j; for (int ikern = imin; ikern < imax; ++ikern) { int64_t kernIndex = jindpart + ikern; if (roiFrame == NULL || roiFrame[kernIndex] > 0.0f) { float valdiff = inFrame[kernIndex] - curval; voxelDir[0] = ikern - i; Vector3D displacement = ivec * voxelDir[0] + jvec * voxelDir[1] + kvec * voxelDir[2]; float length = displacement.length(); if (length > 0.0f) { accum += displacement * (valdiff / (length * length));//once to normalize vector, and once to find gradient magnitude ++accumCount; } } } } } if (accumCount > 0) { gradient = accum / accumCount; magnitude = gradient.length(); if (!MathFunctions::isNumeric(magnitude)) { magnitude = 0.0f; gradient[0] = 0.0f; gradient[1] = 0.0f; gradient[2] = 0.0f; } } else { magnitude = 0.0f; gradient[0] = 0.0f; gradient[1] = 0.0f; gradient[2] = 0.0f; } } } } else { magnitude = 0.0f; gradient[0] = 0.0f; gradient[1] = 0.0f; gradient[2] = 0.0f; } volOut->setValue(magnitude, i, j, k, 0, c); if (vectorsOut != NULL) { vectorsOut->setValue(gradient[0], i, j, k, 0, c); vectorsOut->setValue(gradient[1], i, j, k, 1, c); vectorsOut->setValue(gradient[2], i, j, k, 2, c); } } } } } } } float AlgorithmVolumeGradient::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmVolumeGradient::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmVolumeGradient.h000066400000000000000000000034571255417355300233640ustar00rootroot00000000000000#ifndef __ALGORITHM_VOLUME_GRADIENT_H__ #define __ALGORITHM_VOLUME_GRADIENT_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmVolumeGradient : public AbstractAlgorithm { AlgorithmVolumeGradient(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmVolumeGradient(ProgressObject* myProgObj, const VolumeFile* volIn, VolumeFile* volOut, const float& presmooth = -1.0f, const VolumeFile* myRoi = NULL, VolumeFile* vectorsOut = NULL, const int& subvolNum = -1); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmVolumeGradient; } #endif //__ALGORITHM_VOLUME_GRADIENT_H__ workbench-1.1.1/src/Algorithms/AlgorithmVolumeLabelToROI.cxx000066400000000000000000000246601255417355300240750ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmVolumeLabelToROI.h" #include "AlgorithmException.h" #include "CaretLogger.h" #include "GiftiLabel.h" #include "GiftiLabelTable.h" #include "VolumeFile.h" #include #include using namespace caret; using namespace std; AString AlgorithmVolumeLabelToROI::getCommandSwitch() { return "-volume-label-to-roi"; } AString AlgorithmVolumeLabelToROI::getShortDescription() { return "MAKE A VOLUME LABEL INTO AN ROI VOLUME"; } OperationParameters* AlgorithmVolumeLabelToROI::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addVolumeParameter(1, "label-in", "the input volume label file"); ret->addVolumeOutputParameter(2, "volume-out", "the output volume file"); OptionalParameter* nameOpt = ret->createOptionalParameter(3, "-name", "select label by name"); nameOpt->addStringParameter(1, "label-name", "the label name that you want an roi of"); OptionalParameter* keyOpt = ret->createOptionalParameter(4, "-key", "select label by key"); keyOpt->addIntegerParameter(1, "label-key", "the label key that you want an roi of"); OptionalParameter* mapOpt = ret->createOptionalParameter(5, "-map", "select a single label map to use"); mapOpt->addStringParameter(1, "map", "the map number or name"); ret->setHelpText( AString("For each map in , a map is created in where all locations labeled with or with a key of are given a value of 1, and all other locations are given 0. ") + "Exactly one of -name and -key must be specified. " + "Specify -map to use only one map from ." ); return ret; } void AlgorithmVolumeLabelToROI::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { VolumeFile* myLabel = myParams->getVolume(1); VolumeFile* myVolumeOut = myParams->getOutputVolume(2); bool nameMode = false; AString labelName; OptionalParameter* nameOpt = myParams->getOptionalParameter(3); if (nameOpt->m_present) { nameMode = true; labelName = nameOpt->getString(1); } int32_t labelKey; OptionalParameter* keyOpt = myParams->getOptionalParameter(4); if (keyOpt->m_present) { if (nameMode) throw AlgorithmException("-name and -key cannot be specified together"); labelKey = (int32_t)keyOpt->getInteger(1); } else { if (!nameMode) throw AlgorithmException("you must specify one of -name or -key"); } int whichMap = -1; OptionalParameter* mapOpt = myParams->getOptionalParameter(5); if (mapOpt->m_present) { AString mapID = mapOpt->getString(1); whichMap = myLabel->getMapIndexFromNameOrNumber(mapID); if (whichMap == -1) { throw AlgorithmException("invalid map number or name specified"); } } if (nameMode) { AlgorithmVolumeLabelToROI(myProgObj, myLabel, labelName, myVolumeOut, whichMap); } else { AlgorithmVolumeLabelToROI(myProgObj, myLabel, labelKey, myVolumeOut, whichMap); } } AlgorithmVolumeLabelToROI::AlgorithmVolumeLabelToROI(ProgressObject* myProgObj, const VolumeFile* myLabel, const AString& labelName, VolumeFile* myVolumeOut, const int& whichMap) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); int numMaps = myLabel->getNumberOfMaps(); if (whichMap < -1 || whichMap >= numMaps) { throw AlgorithmException("invalid map index specified"); } if (myLabel->getType() != SubvolumeAttributes::LABEL) { throw AlgorithmException("input volume is not a label volume"); } vector dims, origDims = myLabel->getOriginalDimensions(); myLabel->getDimensions(dims); if (dims[4] != 1) { throw AlgorithmException("input label volume has multi-component type"); } int64_t frameSize = dims[0] * dims[1] * dims[2]; vector scratchFrame(frameSize); if (whichMap == -1) { myVolumeOut->reinitialize(origDims, myLabel->getSform()); bool shouldThrow = true; for (int thisMap = 0; thisMap < numMaps; ++thisMap) { const GiftiLabelTable* myTable = myLabel->getMapLabelTable(thisMap); int matchKey = myTable->getLabelKeyFromName(labelName); if (matchKey == GiftiLabel::getInvalidLabelKey()) { CaretLogWarning("label name '" + labelName + "' not found in map #" + AString::number(thisMap + 1)); for (int64_t i = 0; i < frameSize; ++i) { scratchFrame[i] = 0.0f; } } else { const float* labelFrame = myLabel->getFrame(thisMap); for (int64_t i = 0; i < frameSize; ++i) { int thisKey = (int)floor(labelFrame[i] + 0.5f); if (thisKey == matchKey) { scratchFrame[i] = 1.0f; shouldThrow = false; } else { scratchFrame[i] = 0.0f; } } } myVolumeOut->setFrame(scratchFrame.data(), thisMap); } if (shouldThrow) { throw AlgorithmException("no data matched the specified label name"); } } else { origDims.resize(3); myVolumeOut->reinitialize(origDims, myLabel->getSform()); const GiftiLabelTable* myTable = myLabel->getMapLabelTable(whichMap); int matchKey = myTable->getLabelKeyFromName(labelName); if (matchKey == GiftiLabel::getInvalidLabelKey()) { throw AlgorithmException("label name '" + labelName + "' not found in specified map"); } const float* labelFrame = myLabel->getFrame(whichMap); bool shouldThrow = true; for (int64_t i = 0; i < frameSize; ++i) { int thisKey = (int)floor(labelFrame[i] + 0.5f); if (thisKey == matchKey) { scratchFrame[i] = 1.0f; shouldThrow = false; } else { scratchFrame[i] = 0.0f; } } if (shouldThrow) { throw AlgorithmException("no data matched the specified label name in the specified map"); } myVolumeOut->setFrame(scratchFrame.data(), 0); } } AlgorithmVolumeLabelToROI::AlgorithmVolumeLabelToROI(ProgressObject* myProgObj, const VolumeFile* myLabel, const int32_t& labelKey, VolumeFile* myVolumeOut, const int& whichMap) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); int numMaps = myLabel->getNumberOfMaps(); if (whichMap < -1 || whichMap >= numMaps) { throw AlgorithmException("invalid map index specified"); } if (myLabel->getType() != SubvolumeAttributes::LABEL) { throw AlgorithmException("input volume is not a label volume"); } vector dims, origDims = myLabel->getOriginalDimensions(); myLabel->getDimensions(dims); if (dims[4] != 1) { throw AlgorithmException("input label volume has multi-component type"); } int64_t frameSize = dims[0] * dims[1] * dims[2]; vector scratchFrame(frameSize); if (whichMap == -1) { myVolumeOut->reinitialize(origDims, myLabel->getSform()); bool shouldThrow = true; for (int thisMap = 0; thisMap < numMaps; ++thisMap) { const GiftiLabelTable* myTable = myLabel->getMapLabelTable(thisMap); if (myTable->getLabel(labelKey) == NULL) { CaretLogWarning("label key " + AString::number(labelKey) + " not found in map #" + AString::number(thisMap + 1)); } const float* labelFrame = myLabel->getFrame(thisMap);//try anyway, in case label table is incomplete for (int64_t i = 0; i < frameSize; ++i) { int thisKey = (int)floor(labelFrame[i] + 0.5f); if (thisKey == labelKey) { scratchFrame[i] = 1.0f; shouldThrow = false; } else { scratchFrame[i] = 0.0f; } } myVolumeOut->setFrame(scratchFrame.data(), thisMap); } if (shouldThrow) { throw AlgorithmException("no data matched the specified label key"); } } else { origDims.resize(3); myVolumeOut->reinitialize(origDims, myLabel->getSform()); const GiftiLabelTable* myTable = myLabel->getMapLabelTable(whichMap); if (myTable->getLabel(labelKey) == NULL) { CaretLogWarning("label key " + AString::number(labelKey) + " not found in specified map"); } const float* labelFrame = myLabel->getFrame(whichMap); bool shouldThrow = true; for (int64_t i = 0; i < frameSize; ++i) { int thisKey = (int)floor(labelFrame[i] + 0.5f); if (thisKey == labelKey) { scratchFrame[i] = 1.0f; shouldThrow = false; } else { scratchFrame[i] = 0.0f; } } if (shouldThrow) { throw AlgorithmException("no data matched the specified label key in the specified map"); } myVolumeOut->setFrame(scratchFrame.data(), 0); } } float AlgorithmVolumeLabelToROI::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmVolumeLabelToROI::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmVolumeLabelToROI.h000066400000000000000000000036071255417355300235200ustar00rootroot00000000000000#ifndef __ALGORITHM_VOLUME_LABEL_TO_ROI_H__ #define __ALGORITHM_VOLUME_LABEL_TO_ROI_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmVolumeLabelToROI : public AbstractAlgorithm { AlgorithmVolumeLabelToROI(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmVolumeLabelToROI(ProgressObject* myProgObj, const VolumeFile* myLabel, const AString& labelName, VolumeFile* myVolumeOut, const int& whichMap = -1); AlgorithmVolumeLabelToROI(ProgressObject* myProgObj, const VolumeFile* myLabel, const int32_t& labelKey, VolumeFile* myVolumeOut, const int& whichMap = -1); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmVolumeLabelToROI; } #endif //__ALGORITHM_VOLUME_LABEL_TO_ROI_H__ workbench-1.1.1/src/Algorithms/AlgorithmVolumeLabelToSurfaceMapping.cxx000066400000000000000000000161601255417355300263440ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmVolumeLabelToSurfaceMapping.h" #include "AlgorithmException.h" #include "GiftiLabelTable.h" #include "LabelFile.h" #include "SurfaceFile.h" #include "VolumeFile.h" #include using namespace caret; using namespace std; AString AlgorithmVolumeLabelToSurfaceMapping::getCommandSwitch() { return "-volume-label-to-surface-mapping"; } AString AlgorithmVolumeLabelToSurfaceMapping::getShortDescription() { return "MAP A LABEL VOLUME TO A SURFACE LABEL FILE"; } OperationParameters* AlgorithmVolumeLabelToSurfaceMapping::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addVolumeParameter(1, "volume", "the volume to map data from"); ret->addSurfaceParameter(2, "surface", "the surface to map the data onto"); ret->addLabelOutputParameter(3, "label-out", "the output gifti label file"); OptionalParameter* subvolumeSelect = ret->createOptionalParameter(4, "-subvol-select", "select a single subvolume to map"); subvolumeSelect->addStringParameter(1, "subvol", "the subvolume number or name"); ret->setHelpText( AString("Uses the enclosing voxel mapping method to map label data to a gifti label file.") ); return ret; } void AlgorithmVolumeLabelToSurfaceMapping::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { VolumeFile* myVolume = myParams->getVolume(1); SurfaceFile* mySurface = myParams->getSurface(2); LabelFile* myLabelOut = myParams->getOutputLabel(3); int64_t mySubVol = -1; OptionalParameter* subvolumeSelect = myParams->getOptionalParameter(4); if (subvolumeSelect->m_present) { mySubVol = (int)myVolume->getMapIndexFromNameOrNumber(subvolumeSelect->getString(1)); if (mySubVol < 0) { throw AlgorithmException("invalid column specified"); } } AlgorithmVolumeLabelToSurfaceMapping(myProgObj, myVolume, mySurface, myLabelOut, mySubVol); } AlgorithmVolumeLabelToSurfaceMapping::AlgorithmVolumeLabelToSurfaceMapping(ProgressObject* myProgObj, const VolumeFile* myVolume, const SurfaceFile* mySurface, LabelFile* myLabelOut, const int& mySubVol) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); if (myVolume->getType() != SubvolumeAttributes::LABEL) { throw AlgorithmException("input volume must be a label volume"); } vector myVolDims; myVolume->getDimensions(myVolDims); if (mySubVol >= myVolDims[3] || mySubVol < -1) { throw AlgorithmException("invalid subvolume specified"); } int64_t numColumns; if (mySubVol == -1) { numColumns = myVolDims[3] * myVolDims[4]; } else { numColumns = myVolDims[4]; } int64_t numNodes = mySurface->getNumberOfNodes(); myLabelOut->setNumberOfNodesAndColumns(numNodes, numColumns); myLabelOut->setStructure(mySurface->getStructure()); vector myArray(numNodes); if (mySubVol == -1) { GiftiLabelTable cumulativeTable = *(myVolume->getMapLabelTable(0));//so we don't run into append issues with the "???" label that gets made by GiftiLabelTable's constructor map cumulativeRemap; for (int64_t i = 0; i < myVolDims[3]; ++i) { if (i > 0) { const GiftiLabelTable* tempTable = myVolume->getMapLabelTable(i); if (tempTable == NULL) { throw AlgorithmException("a subvolume is missing a label table"); } map newMap = cumulativeTable.append(*tempTable); cumulativeRemap.insert(newMap.begin(), newMap.end()); } for (int64_t j = 0; j < myVolDims[4]; ++j) { AString mapName = myVolume->getMapName(i); if (myVolDims[4] != 1) { mapName += " component " + AString::number(j); } int64_t thisCol = i * myVolDims[4] + j; myLabelOut->setColumnName(thisCol, mapName); #pragma omp CARET_PARFOR for (int64_t node = 0; node < numNodes; ++node) { int32_t tempKey = (int32_t)floor(myVolume->interpolateValue(mySurface->getCoordinate(node), VolumeFile::ENCLOSING_VOXEL, NULL, i, j) + 0.5f); map::iterator iter = cumulativeRemap.find(tempKey); if (iter != cumulativeRemap.end()) { myArray[node] = iter->second; } else { myArray[node] = tempKey;//for simplicity, assume all values in the volume file are in the label table } } myLabelOut->setLabelKeysForColumn(thisCol, myArray.data()); } } *(myLabelOut->getLabelTable()) = cumulativeTable; } else { const GiftiLabelTable* tempTable = myVolume->getMapLabelTable(mySubVol); if (tempTable == NULL) { throw AlgorithmException("specified subvolume is missing a label table"); } *(myLabelOut->getLabelTable()) = *tempTable; for (int64_t j = 0; j < myVolDims[4]; ++j) { AString mapName = myVolume->getMapName(mySubVol); if (myVolDims[4] != 1) { mapName += " component " + AString::number(j); } int64_t thisCol = j; myLabelOut->setColumnName(thisCol, mapName); #pragma omp CARET_PARFOR for (int64_t node = 0; node < numNodes; ++node) {//for simplicity, assume all values in the volume file are in the label table myArray[node] = (int32_t)floor(myVolume->interpolateValue(mySurface->getCoordinate(node), VolumeFile::ENCLOSING_VOXEL, NULL, mySubVol, j) + 0.5f); } myLabelOut->setLabelKeysForColumn(thisCol, myArray.data()); } } } float AlgorithmVolumeLabelToSurfaceMapping::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmVolumeLabelToSurfaceMapping::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmVolumeLabelToSurfaceMapping.h000066400000000000000000000035551255417355300257750ustar00rootroot00000000000000#ifndef __ALGORITHM_VOLUME_LABEL_TO_SURFACE_MAPPING_H__ #define __ALGORITHM_VOLUME_LABEL_TO_SURFACE_MAPPING_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmVolumeLabelToSurfaceMapping : public AbstractAlgorithm { AlgorithmVolumeLabelToSurfaceMapping(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmVolumeLabelToSurfaceMapping(ProgressObject* myProgObj, const VolumeFile* myVolume, const SurfaceFile* mySurface, LabelFile* myLabelOut, const int& mySubVol = -1); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmVolumeLabelToSurfaceMapping; } #endif //__ALGORITHM_VOLUME_LABEL_TO_SURFACE_MAPPING_H__ workbench-1.1.1/src/Algorithms/AlgorithmVolumeParcelResampling.cxx000066400000000000000000001276761255417355300254440ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmVolumeParcelResampling.h" #include "AlgorithmException.h" #include "VolumeFile.h" #include "AlgorithmVolumeSmoothing.h" #include "CaretAssert.h" #include "CaretLogger.h" #include "Vector3D.h" #include #include #include #include using namespace caret; using namespace std; const int FIX_ZEROS_POST_ITERATIONS = 10;//number of times to do the "find remaining zeros and try to fill" code before giving up when -fix-zeros is specified AString AlgorithmVolumeParcelResampling::getCommandSwitch() { return "-volume-parcel-resampling"; } AString AlgorithmVolumeParcelResampling::getShortDescription() { return "SMOOTH AND RESAMPLE VOLUME PARCELS"; } OperationParameters* AlgorithmVolumeParcelResampling::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addVolumeParameter(1, "volume-in", "the input data volume"); ret->addVolumeParameter(2, "cur-parcels", "label volume of where the parcels currently are"); ret->addVolumeParameter(3, "new-parcels", "label volume of where the parcels should be"); ret->addDoubleParameter(4, "kernel", "gaussian kernel sigma to smooth by during resampling"); ret->addVolumeOutputParameter(5, "volume-out", "output volume"); ret->createOptionalParameter(6, "-fix-zeros", "treat zero values as not being data"); OptionalParameter* subvolSelect = ret->createOptionalParameter(7, "-subvolume", "select a single subvolume as input"); subvolSelect->addStringParameter(1, "subvol", "the subvolume number or name"); ret->setHelpText( AString("Smooths and resamples the region inside each label in cur-parcels to the region of the same label name in new-parcels. ") + "Any voxels in the output label region but outside the input label region will be extrapolated from nearby data. " + "The -fix-zeros option causes the smoothing to not use an input value if it is zero, but still write a smoothed value to the voxel, and after smoothing " + "is complete, it will check for any remaining values of zero, and fill them in with extrapolated values.\n\nNote: all volumes must have " + "the same dimensions and spacing. To use a different output space, see -volume-parcel-resampling-generic." ); return ret; } void AlgorithmVolumeParcelResampling::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { VolumeFile* inVol = myParams->getVolume(1); VolumeFile* curLabel = myParams->getVolume(2); VolumeFile* newLabel = myParams->getVolume(3); float kernel = (float)myParams->getDouble(4); VolumeFile* outVol = myParams->getOutputVolume(5); bool fixZeros = false; if (myParams->getOptionalParameter(6)->m_present) { fixZeros = true; } OptionalParameter* subvolSelect = myParams->getOptionalParameter(7); int subvolNum = -1; if (subvolSelect->m_present) { subvolNum = (int)inVol->getMapIndexFromNameOrNumber(subvolSelect->getString(1)); if (subvolNum < 0) { throw AlgorithmException("invalid subvolume specified"); } } AlgorithmVolumeParcelResampling(myProgObj, inVol, curLabel, newLabel, kernel, outVol, fixZeros, subvolNum); } AlgorithmVolumeParcelResampling::AlgorithmVolumeParcelResampling(ProgressObject* myProgObj, const VolumeFile* inVol, const VolumeFile* curLabel, const VolumeFile* newLabel, const float& kernel, VolumeFile* outVol, const bool& fixZeros, const int& subvolNum) : AbstractAlgorithm(myProgObj) { CaretAssert(inVol != NULL); CaretAssert(curLabel != NULL); CaretAssert(newLabel != NULL); CaretAssert(outVol != NULL); if (!inVol->matchesVolumeSpace(curLabel) || !inVol->matchesVolumeSpace(newLabel)) { throw AlgorithmException("volume spacing or dimension mismatch"); } if (curLabel->getType() != SubvolumeAttributes::LABEL || newLabel->getType() != SubvolumeAttributes::LABEL) { throw AlgorithmException("parcel volumes are not of type label"); } if (subvolNum < -1 || subvolNum >= inVol->getNumberOfMaps()) { throw AlgorithmException("invalid subvolume specified"); } vector > matchedLabels; matchLabels(curLabel, newLabel, matchedLabels); if (matchedLabels.size() == 0) { throw AlgorithmException("no matching labels"); } vector > voxelLists; generateVoxelLists(matchedLabels, curLabel, newLabel, voxelLists); /*ProgressObject* subAlgProgress = NULL; if (myProgObj != NULL)//TODO: create a vector of progress objects based on number of matched labels, and possibly counts of voxels { subAlgProgress = myProgObj->addAlgorithm(AlgorithmVolumeParcelSmoothing::getAlgorithmWeight()); }//*/ LevelProgress myProgress(myProgObj); if (fixZeros) { resampleFixZeros(myProgress, matchedLabels, voxelLists, inVol, curLabel, newLabel, kernel, outVol, subvolNum); } else { resample(myProgress, matchedLabels, voxelLists, inVol, curLabel, newLabel, kernel, outVol, subvolNum); } } void AlgorithmVolumeParcelResampling::matchLabels(const VolumeFile* curLabel, const VolumeFile* newLabel, vector >& matchedLabels) { const GiftiLabelTable* curTable = curLabel->getMapLabelTable(0), *newTable = newLabel->getMapLabelTable(0); vector curKeys; curTable->getKeys(curKeys); int32_t curUnused = curTable->getUnassignedLabelKey(); for (int i = 0; i < (int)curKeys.size(); ++i) { if (curKeys[i] == curUnused) continue;//always skip the unassigned label if (newTable->getLabel(curKeys[i]) != NULL && newTable->getLabelName(curKeys[i]) == curTable->getLabelName(curKeys[i])) {//do the obvious check first matchedLabels.push_back(make_pair(curKeys[i], curKeys[i])); } else { int32_t newKey = newTable->getLabelKeyFromName(curTable->getLabelName(curKeys[i])); if (newKey != -1) { matchedLabels.push_back(make_pair(curKeys[i], newKey)); } } } } void AlgorithmVolumeParcelResampling::generateVoxelLists(const vector >& matchedLabels, const VolumeFile* curLabel, const VolumeFile* newLabel, vector >& voxelLists) { map curLabelReverse, newLabelReverse; for (int i = 0; i < (int)matchedLabels.size(); ++i) { curLabelReverse[matchedLabels[i].first] = i; newLabelReverse[matchedLabels[i].second] = i; } voxelLists.resize(matchedLabels.size()); vector myDims; curLabel->getDimensions(myDims); for (int64_t k = 0; k < myDims[2]; ++k) { for (int64_t j = 0; j < myDims[1]; ++j) { for (int64_t i = 0; i < myDims[0]; ++i) { int curValue = (int)floor(curLabel->getValue(i, j, k) + 0.5f); int newValue = (int)floor(newLabel->getValue(i, j, k) + 0.5f); map::iterator curiter = curLabelReverse.find(curValue), newiter = newLabelReverse.find(newValue); if (curiter != curLabelReverse.end()) { voxelLists[curiter->second].push_back(i); voxelLists[curiter->second].push_back(j); voxelLists[curiter->second].push_back(k); } if (newiter != newLabelReverse.end() && (curiter == curLabelReverse.end() || newiter->second != curiter->second)) { voxelLists[newiter->second].push_back(i); voxelLists[newiter->second].push_back(j); voxelLists[newiter->second].push_back(k); } } } } } void AlgorithmVolumeParcelResampling::resample(LevelProgress& myProgress, const vector >& matchedLabels, const vector >& voxelLists, const VolumeFile* inVol, const VolumeFile* curLabel, const VolumeFile* newLabel, const float& kernel, VolumeFile* outVol, const int& subvolNum) { float kernBox = kernel * 3.0f; vector > volSpace = inVol->getSform();//copied from volume smoothing, perhaps this should be in a convenience method in VolumeFile Vector3D ivec, jvec, kvec, origin, ijorth, jkorth, kiorth; ivec[0] = volSpace[0][0]; jvec[0] = volSpace[0][1]; kvec[0] = volSpace[0][2]; origin[0] = volSpace[0][3];//needs to be this verbose because the axis and origin vectors are column vectors ivec[1] = volSpace[1][0]; jvec[1] = volSpace[1][1]; kvec[1] = volSpace[1][2]; origin[1] = volSpace[1][3];//while vector > is a column of row vectors ivec[2] = volSpace[2][0]; jvec[2] = volSpace[2][1]; kvec[2] = volSpace[2][2]; origin[2] = volSpace[2][3]; ijorth = ivec.cross(jvec).normal();//find the bounding box that encloses a sphere of radius kernBox jkorth = jvec.cross(kvec).normal(); kiorth = kvec.cross(ivec).normal(); int irange = (int)floor(abs(kernBox / ivec.dot(jkorth))); int jrange = (int)floor(abs(kernBox / jvec.dot(kiorth))); int krange = (int)floor(abs(kernBox / kvec.dot(ijorth))); if (irange < 1) irange = 1;//don't underflow if (jrange < 1) jrange = 1; if (krange < 1) krange = 1; map curLabelReverse, newLabelReverse; int numLabels = (int)matchedLabels.size(); for (int i = 0; i < numLabels; ++i) { curLabelReverse[matchedLabels[i].first] = i; newLabelReverse[matchedLabels[i].second] = i; } vector myDims; inVol->getDimensions(myDims); if (subvolNum == -1) { outVol->reinitialize(inVol->getOriginalDimensions(), inVol->getSform(), myDims[4], inVol->getType()); } else { vector newDims = inVol->getOriginalDimensions(); newDims.resize(3);//discard nonspatial dimentions outVol->reinitialize(newDims, inVol->getSform(), myDims[4], inVol->getType()); } outVol->setValueAllVoxels(0.0f); const GiftiLabelTable* curTable = curLabel->getMapLabelTable(0); for (int whichList = 0; whichList < numLabels; ++whichList) { int curLabelValue = matchedLabels[whichList].first; int newLabelValue = matchedLabels[whichList].second; myProgress.reportProgress(((float)whichList) / numLabels); myProgress.setTask("Processing parcel " + curTable->getLabelName(curLabelValue)); const vector& thisList = voxelLists[whichList]; int64_t listSize = (int64_t)thisList.size(); if (listSize > 2)//NOTE: this should NEVER be something other than a multiple of 3, but check against what we will actually access anyway { int extrema[6] = { thisList[0], thisList[0], thisList[1], thisList[1], thisList[2], thisList[2] }; for (int64_t base = 3; base < listSize; base += 3) { if (thisList[base] < extrema[0]) extrema[0] = thisList[base]; if (thisList[base] > extrema[1]) extrema[1] = thisList[base]; if (thisList[base + 1] < extrema[2]) extrema[2] = thisList[base + 1]; if (thisList[base + 1] > extrema[3]) extrema[3] = thisList[base + 1]; if (thisList[base + 2] < extrema[4]) extrema[4] = thisList[base + 2]; if (thisList[base + 2] > extrema[5]) extrema[5] = thisList[base + 2]; } vector boxdims; boxdims.push_back(extrema[1] - extrema[0] + 1); boxdims.push_back(extrema[3] - extrema[2] + 1); boxdims.push_back(extrema[5] - extrema[4] + 1); VolumeFile roibox(boxdims, inVol->getSform()); roibox.setValueAllVoxels(0.0f); vector curList, newList;//make the 2 separate lists, so we don't need to test the label volume for (int64_t base = 0; base < listSize; base += 3)//could do this when we make the merged list...maybe use member variables to cut the argument clutter { int curvalue = (int)floor(curLabel->getValue(thisList[base], thisList[base + 1], thisList[base + 2]) + 0.5f); int newvalue = (int)floor(newLabel->getValue(thisList[base], thisList[base + 1], thisList[base + 2]) + 0.5f); if (curvalue == curLabelValue) { curList.push_back(thisList[base]); curList.push_back(thisList[base + 1]); curList.push_back(thisList[base + 2]); } if (newvalue == newLabelValue) { newList.push_back(thisList[base]); newList.push_back(thisList[base + 1]); newList.push_back(thisList[base + 2]); } } int64_t curListSize = (int64_t)curList.size(); int64_t newListSize = (int64_t)newList.size(); if (subvolNum == -1) { boxdims.push_back(myDims[3]); VolumeFile inbox(boxdims, inVol->getSform(), myDims[4]), outbox; for (int c = 0; c < myDims[4]; ++c) { for (int s = 0; s < myDims[3]; ++s) { for (int64_t base = 0; base < curListSize; base += 3) { if (s == 0 && c == 0)//first, smooth within only the current label { roibox.setValue(1.0f, curList[base] - extrema[0], curList[base + 1] - extrema[2], curList[base + 2] - extrema[4]); } inbox.setValue(inVol->getValue(curList[base], curList[base + 1], curList[base + 2], s, c), curList[base] - extrema[0], curList[base + 1] - extrema[2], curList[base + 2] - extrema[4], s, c); } } } AlgorithmVolumeSmoothing(NULL, &inbox, kernel, &outbox, &roibox, false); float kernelMult = -1.0f / kernel / kernel / 2.0f;//precompute the part of the kernel function that doesn't change for (int c = 0; c < myDims[4]; ++c) { for (int s = 0; s < myDims[3]; ++s) { const float* inFrame = inVol->getFrame(s, c); const float* labelFrame = curLabel->getFrame(); for (int64_t base = 0; base < newListSize; base += 3) { int myCurVal = (int)floor(curLabel->getValue(newList[base], newList[base + 1], newList[base + 2]) + 0.5f); if (myCurVal == curLabelValue) { outVol->setValue(outbox.getValue(newList[base] - extrema[0], newList[base + 1] - extrema[2], newList[base + 2] - extrema[4], s, c), newList[base], newList[base + 1], newList[base + 2], s, c); } else { float sum = 0.0f, weightsum = 0.0f;//coded in-place for now, its a special restricted case of volume dilate, copied out of nonorth volume smoothing int i = newList[base], j = newList[base + 1], k = newList[base + 2];//special casing orthogonal would be faster, but harder to follow/debug, and more code int imin = i - irange, imax = i + irange + 1;//one-after array size convention if (imin < 0) imin = 0; if (imax > myDims[0]) imax = myDims[0]; int jmin = j - jrange, jmax = j + jrange + 1; if (jmin < 0) jmin = 0; if (jmax > myDims[1]) jmax = myDims[1]; int kmin = k - krange, kmax = k + krange + 1; if (kmin < 0) kmin = 0; if (kmax > myDims[2]) kmax = myDims[2]; Vector3D kscratch, jscratch, iscratch; for (int kkern = kmin; kkern < kmax; ++kkern) { kscratch = kvec * (kkern - k); int64_t kindpart = kkern * myDims[1]; for (int jkern = jmin; jkern < jmax; ++jkern) { jscratch = kscratch + jvec * (jkern - j); int64_t jindpart = (kindpart + jkern) * myDims[0]; for (int ikern = imin; ikern < imax; ++ikern) { int64_t thisIndex = jindpart + ikern;//somewhat optimized index computation, could remove some integer multiplies, but there aren't that many int curVal = (int)floor(labelFrame[thisIndex] + 0.5f); if (curVal == curLabelValue) { iscratch = jscratch + ivec * (ikern - i); float tempf = iscratch.length(); float weight = exp(tempf * tempf * kernelMult); sum += weight * inFrame[thisIndex]; weightsum += weight; } } } } if (weightsum != 0.0f) { outVol->setValue(sum / weightsum, newList[base], newList[base + 1], newList[base + 2], s, c); }//should already be 0, so don't need to handle the else } } } } } else { VolumeFile inbox(boxdims, inVol->getSform(), myDims[4]), outbox; for (int c = 0; c < myDims[4]; ++c) { for (int64_t base = 0; base < curListSize; base += 3) { int myvalue = (int)floor(curLabel->getValue(curList[base], curList[base + 1], curList[base + 2]) + 0.5f); if (myvalue == matchedLabels[whichList].first) {//first, only smooth within the ROI of the current label, otherwise we would smooth in some zeros that aren't data if (c == 0) { roibox.setValue(1.0f, curList[base] - extrema[0], curList[base + 1] - extrema[2], curList[base + 2] - extrema[4]); } inbox.setValue(inVol->getValue(curList[base], curList[base + 1], curList[base + 2], subvolNum, c), curList[base] - extrema[0], curList[base + 1] - extrema[2], curList[base + 2] - extrema[4], 0, c); } } } AlgorithmVolumeSmoothing(NULL, &inbox, kernel, &outbox, &roibox, false); float kernelMult = -1.0f / kernel / kernel / 2.0f;//precompute the part of the kernel function that doesn't change for (int c = 0; c < myDims[4]; ++c) { const float* inFrame = inVol->getFrame(subvolNum, c); const float* labelFrame = curLabel->getFrame(); for (int64_t base = 0; base < newListSize; base += 3) { int myCurVal = (int)floor(curLabel->getValue(newList[base], newList[base + 1], newList[base + 2]) + 0.5f); if (myCurVal == curLabelValue) { outVol->setValue(outbox.getValue(newList[base] - extrema[0], newList[base + 1] - extrema[2], newList[base + 2] - extrema[4], 0, c), newList[base], newList[base + 1], newList[base + 2], 0, c); } else { float sum = 0.0f, weightsum = 0.0f;//coded in-place for now, its a special restricted case of volume dilate, copied out of nonorth volume smoothing int i = newList[base], j = newList[base + 1], k = newList[base + 2];//special casing orthogonal would be faster, but harder to follow/debug, and more code int imin = i - irange, imax = i + irange + 1;//one-after array size convention if (imin < 0) imin = 0; if (imax > myDims[0]) imax = myDims[0]; int jmin = j - jrange, jmax = j + jrange + 1; if (jmin < 0) jmin = 0; if (jmax > myDims[1]) jmax = myDims[1]; int kmin = k - krange, kmax = k + krange + 1; if (kmin < 0) kmin = 0; if (kmax > myDims[2]) kmax = myDims[2]; Vector3D kscratch, jscratch, iscratch; for (int kkern = kmin; kkern < kmax; ++kkern) { kscratch = kvec * (kkern - k); int64_t kindpart = kkern * myDims[1]; for (int jkern = jmin; jkern < jmax; ++jkern) { jscratch = kscratch + jvec * (jkern - j); int64_t jindpart = (kindpart + jkern) * myDims[0]; for (int ikern = imin; ikern < imax; ++ikern) { int64_t thisIndex = jindpart + ikern;//somewhat optimized index computation, could remove some integer multiplies, but there aren't that many int curVal = (int)floor(labelFrame[thisIndex] + 0.5f); if (curVal == curLabelValue) { iscratch = jscratch + ivec * (ikern - i); float tempf = iscratch.length(); float weight = exp(tempf * tempf * kernelMult); sum += weight * inFrame[thisIndex]; weightsum += weight; } } } } if (weightsum != 0.0f) { outVol->setValue(sum / weightsum, newList[base], newList[base + 1], newList[base + 2], 0, c); }//should already be 0, so don't need to handle the else } } } } } } } void AlgorithmVolumeParcelResampling::resampleFixZeros(LevelProgress& myProgress, const vector >& matchedLabels, const vector >& voxelLists, const VolumeFile* inVol, const VolumeFile* curLabel, const VolumeFile* newLabel, const float& kernel, VolumeFile* outVol, const int& subvolNum) { float kernBox = kernel * 3.0f; vector > volSpace = inVol->getSform();//copied from volume smoothing, perhaps this should be in a convenience method in VolumeFile Vector3D ivec, jvec, kvec, origin, ijorth, jkorth, kiorth; ivec[0] = volSpace[0][0]; jvec[0] = volSpace[0][1]; kvec[0] = volSpace[0][2]; origin[0] = volSpace[0][3];//needs to be this verbose because the axis and origin vectors are column vectors ivec[1] = volSpace[1][0]; jvec[1] = volSpace[1][1]; kvec[1] = volSpace[1][2]; origin[1] = volSpace[1][3];//while vector > is a column of row vectors ivec[2] = volSpace[2][0]; jvec[2] = volSpace[2][1]; kvec[2] = volSpace[2][2]; origin[2] = volSpace[2][3]; ijorth = ivec.cross(jvec).normal();//find the bounding box that encloses a sphere of radius kernBox jkorth = jvec.cross(kvec).normal(); kiorth = kvec.cross(ivec).normal(); int irange = (int)floor(abs(kernBox / ivec.dot(jkorth))); int jrange = (int)floor(abs(kernBox / jvec.dot(kiorth))); int krange = (int)floor(abs(kernBox / kvec.dot(ijorth))); if (irange < 1) irange = 1;//don't underflow if (jrange < 1) jrange = 1; if (krange < 1) krange = 1; map curLabelReverse, newLabelReverse; int numLabels = (int)matchedLabels.size(); for (int i = 0; i < numLabels; ++i) { curLabelReverse[matchedLabels[i].first] = i; newLabelReverse[matchedLabels[i].second] = i; } vector myDims; inVol->getDimensions(myDims); if (subvolNum == -1) { outVol->reinitialize(inVol->getOriginalDimensions(), inVol->getSform(), myDims[4], inVol->getType()); } else { vector newDims = inVol->getOriginalDimensions(); newDims.resize(3);//discard nonspatial dimentions outVol->reinitialize(newDims, inVol->getSform(), myDims[4], inVol->getType()); } outVol->setValueAllVoxels(0.0f); const GiftiLabelTable* curTable = curLabel->getMapLabelTable(0); for (int whichList = 0; whichList < numLabels; ++whichList) { int curLabelValue = matchedLabels[whichList].first; int newLabelValue = matchedLabels[whichList].second; myProgress.reportProgress(((float)whichList) / numLabels); myProgress.setTask("Processing parcel " + curTable->getLabelName(curLabelValue)); const vector& thisList = voxelLists[whichList]; int64_t listSize = (int64_t)thisList.size(); if (listSize > 2)//NOTE: this should NEVER be something other than a multiple of 3, but check against what we will actually access anyway { int extrema[6] = { thisList[0], thisList[0], thisList[1], thisList[1], thisList[2], thisList[2] }; for (int64_t base = 3; base < listSize; base += 3) { if (thisList[base] < extrema[0]) extrema[0] = thisList[base]; if (thisList[base] > extrema[1]) extrema[1] = thisList[base]; if (thisList[base + 1] < extrema[2]) extrema[2] = thisList[base + 1]; if (thisList[base + 1] > extrema[3]) extrema[3] = thisList[base + 1]; if (thisList[base + 2] < extrema[4]) extrema[4] = thisList[base + 2]; if (thisList[base + 2] > extrema[5]) extrema[5] = thisList[base + 2]; } vector boxdims; boxdims.push_back(extrema[1] - extrema[0] + 1); boxdims.push_back(extrema[3] - extrema[2] + 1); boxdims.push_back(extrema[5] - extrema[4] + 1); VolumeFile roibox(boxdims, inVol->getSform()); roibox.setValueAllVoxels(0.0f); vector newList;//make the separate new list, so we don't need to test the label volume for (int64_t base = 0; base < listSize; base += 3)//could do this when we make the merged list...maybe use member variables to cut the argument clutter { int newvalue = (int)floor(newLabel->getValue(thisList[base], thisList[base + 1], thisList[base + 2]) + 0.5f); if (newvalue == newLabelValue) { newList.push_back(thisList[base]); newList.push_back(thisList[base + 1]); newList.push_back(thisList[base + 2]); } } int64_t newListSize = (int64_t)newList.size(); if (subvolNum == -1) { boxdims.push_back(myDims[3]); VolumeFile inbox(boxdims, inVol->getSform(), myDims[4]), outbox; for (int c = 0; c < myDims[4]; ++c) { for (int s = 0; s < myDims[3]; ++s) { for (int64_t base = 0; base < listSize; base += 3) { if (s == 0 && c == 0)//smooth within BOTH labels, but only copy in from current label { roibox.setValue(1.0f, thisList[base] - extrema[0], thisList[base + 1] - extrema[2], thisList[base + 2] - extrema[4]); } int curValue = (int)floor(curLabel->getValue(thisList[base], thisList[base + 1], thisList[base + 2]) + 0.5f); if (curValue == curLabelValue) { inbox.setValue(inVol->getValue(thisList[base], thisList[base + 1], thisList[base + 2], s, c), thisList[base] - extrema[0], thisList[base + 1] - extrema[2], thisList[base + 2] - extrema[4], s, c); } else { inbox.setValue(0.0f, thisList[base] - extrema[0], thisList[base + 1] - extrema[2], thisList[base + 2] - extrema[4], s, c); } } } } AlgorithmVolumeSmoothing(NULL, &inbox, kernel, &outbox, &roibox, true); float kernelMult = -1.0f / kernel / kernel / 2.0f;//precompute the part of the kernel function that doesn't change VolumeFile* current = &outbox, *next = &inbox, *tempvol;//reuse inbox as scratch space for iterated dilation const float* labelFrame = newLabel->getFrame(); int fixIter; for (fixIter = 0; fixIter < FIX_ZEROS_POST_ITERATIONS; ++fixIter) { bool again = false; for (int c = 0; c < myDims[4]; ++c)//since fix zeros smoothing doesn't consider components as multidimensional datatypes, neither should this { for (int s = 0; s < myDims[3]; ++s) { for (int64_t base = 0; base < newListSize; base += 3) { float curVal = current->getValue(newList[base] - extrema[0], newList[base + 1] - extrema[2], newList[base + 2] - extrema[4], s, c); if (curVal == 0.0f) { float sum = 0.0f, weightsum = 0.0f;//coded in-place for now, its a special restricted case of volume dilate, copied out of nonorth volume smoothing int i = newList[base], j = newList[base + 1], k = newList[base + 2];//special casing orthogonal would be faster, but harder to follow/debug, and more code int imin = i - irange, imax = i + irange + 1;//one-after array size convention if (imin < 0) imin = 0; if (imax > myDims[0]) imax = myDims[0]; int jmin = j - jrange, jmax = j + jrange + 1; if (jmin < 0) jmin = 0; if (jmax > myDims[1]) jmax = myDims[1]; int kmin = k - krange, kmax = k + krange + 1; if (kmin < 0) kmin = 0; if (kmax > myDims[2]) kmax = myDims[2]; Vector3D kscratch, jscratch, iscratch; for (int kkern = kmin; kkern < kmax; ++kkern) { kscratch = kvec * (kkern - k); int64_t kindpart = kkern * myDims[1]; for (int jkern = jmin; jkern < jmax; ++jkern) { jscratch = kscratch + jvec * (jkern - j); int64_t jindpart = (kindpart + jkern) * myDims[0]; for (int ikern = imin; ikern < imax; ++ikern) { int64_t thisIndex = jindpart + ikern;//somewhat optimized index computation, could remove some integer multiplies, but there aren't that many int tempi = (int)floor(labelFrame[thisIndex] + 0.5f); if (tempi == curLabelValue) { float dataVal = current->getValue(ikern - extrema[0], jkern - extrema[2], kkern - extrema[4], s, c); if (dataVal != 0.0f) { iscratch = jscratch + ivec * (ikern - i); float tempf = iscratch.length(); float weight = exp(tempf * tempf * kernelMult); sum += weight * dataVal; weightsum += weight; } } } } } if (weightsum != 0.0f) { next->setValue(sum / weightsum, newList[base] - extrema[0], newList[base + 1] - extrema[2], newList[base + 2] - extrema[4], s, c); } else { again = true; next->setValue(0.0f, newList[base] - extrema[0], newList[base + 1] - extrema[2], newList[base + 2] - extrema[4], s, c); } } else { next->setValue(curVal, newList[base] - extrema[0], newList[base + 1] - extrema[2], newList[base + 2] - extrema[4], s, c); } } } } tempvol = current; current = next; next = tempvol; if (!again) break; } if (fixIter == FIX_ZEROS_POST_ITERATIONS) { const GiftiLabelTable* curLabelTable = curLabel->getMapLabelTable(0); CaretLogWarning("unable to fix all zeros in parcel " + curLabelTable->getLabelName(curLabelValue)); } for (int c = 0; c < myDims[4]; ++c)//copy the final result into the output valume { for (int s = 0; s < myDims[3]; ++s) { for (int64_t base = 0; base < newListSize; base += 3) { outVol->setValue(current->getValue(newList[base] - extrema[0], newList[base + 1] - extrema[2], newList[base + 2] - extrema[4], s, c), newList[base], newList[base + 1], newList[base + 2], s, c); } } } } else { VolumeFile inbox(boxdims, inVol->getSform(), myDims[4]), outbox; for (int c = 0; c < myDims[4]; ++c) { for (int64_t base = 0; base < listSize; base += 3) { if (c == 0)//smooth within BOTH labels, but only copy in from current label { roibox.setValue(1.0f, thisList[base] - extrema[0], thisList[base + 1] - extrema[2], thisList[base + 2] - extrema[4]); } int curValue = (int)floor(curLabel->getValue(thisList[base], thisList[base + 1], thisList[base + 2]) + 0.5f); if (curValue == curLabelValue) { inbox.setValue(inVol->getValue(thisList[base], thisList[base + 1], thisList[base + 2], subvolNum, c), thisList[base] - extrema[0], thisList[base + 1] - extrema[2], thisList[base + 2] - extrema[4], 0, c); } } } AlgorithmVolumeSmoothing(NULL, &inbox, kernel, &outbox, &roibox, true); float kernelMult = -1.0f / kernel / kernel / 2.0f;//precompute the part of the kernel function that doesn't change int fixIter; VolumeFile* current = &outbox, *next = &inbox, *tempvol;//reuse inbox as scratch space for iterated dilation const float* labelFrame = newLabel->getFrame(); for (fixIter = 0; fixIter < FIX_ZEROS_POST_ITERATIONS; ++fixIter) { bool again = false; for (int c = 0; c < myDims[4]; ++c)//since fix zeros smoothing doesn't consider components as multidimensional datatypes, neither should this { for (int64_t base = 0; base < newListSize; base += 3) { float curVal = current->getValue(newList[base] - extrema[0], newList[base + 1] - extrema[2], newList[base + 2] - extrema[4], 0, c); if (curVal == 0.0f) { float sum = 0.0f, weightsum = 0.0f;//coded in-place for now, its a special restricted case of volume dilate, copied out of nonorth volume smoothing int i = newList[base], j = newList[base + 1], k = newList[base + 2];//special casing orthogonal would be faster, but harder to follow/debug, and more code int imin = i - irange, imax = i + irange + 1;//one-after array size convention if (imin < 0) imin = 0; if (imax > myDims[0]) imax = myDims[0]; int jmin = j - jrange, jmax = j + jrange + 1; if (jmin < 0) jmin = 0; if (jmax > myDims[1]) jmax = myDims[1]; int kmin = k - krange, kmax = k + krange + 1; if (kmin < 0) kmin = 0; if (kmax > myDims[2]) kmax = myDims[2]; Vector3D kscratch, jscratch, iscratch; for (int kkern = kmin; kkern < kmax; ++kkern) { kscratch = kvec * (kkern - k); int64_t kindpart = kkern * myDims[1]; for (int jkern = jmin; jkern < jmax; ++jkern) { jscratch = kscratch + jvec * (jkern - j); int64_t jindpart = (kindpart + jkern) * myDims[0]; for (int ikern = imin; ikern < imax; ++ikern) { int64_t thisIndex = jindpart + ikern;//somewhat optimized index computation, could remove some integer multiplies, but there aren't that many int tempi = (int)floor(labelFrame[thisIndex] + 0.5f); if (tempi == curLabelValue) { float dataVal = current->getValue(ikern - extrema[0], jkern - extrema[2], kkern - extrema[4], 0, c); if (dataVal != 0.0f) { iscratch = jscratch + ivec * (ikern - i); float tempf = iscratch.length(); float weight = exp(tempf * tempf * kernelMult); sum += weight * dataVal; weightsum += weight; } } } } } if (weightsum != 0.0f) { next->setValue(sum / weightsum, newList[base] - extrema[0], newList[base + 1] - extrema[2], newList[base + 2] - extrema[4], 0, c); } else { again = true; next->setValue(0.0f, newList[base] - extrema[0], newList[base + 1] - extrema[2], newList[base + 2] - extrema[4], 0, c); } } else { next->setValue(curVal, newList[base] - extrema[0], newList[base + 1] - extrema[2], newList[base + 2] - extrema[4], 0, c); } } } tempvol = current; current = next; next = tempvol; if (!again) break; } if (fixIter == FIX_ZEROS_POST_ITERATIONS) { const GiftiLabelTable* curLabelTable = curLabel->getMapLabelTable(0); CaretLogWarning("unable to fix all zeros in parcel " + curLabelTable->getLabelName(curLabelValue)); } for (int c = 0; c < myDims[4]; ++c)//copy the final result into the output valume { for (int64_t base = 0; base < newListSize; base += 3) { outVol->setValue(current->getValue(newList[base] - extrema[0], newList[base + 1] - extrema[2], newList[base + 2] - extrema[4], 0, c), newList[base], newList[base + 1], newList[base + 2], 0, c); } } } } } } float AlgorithmVolumeParcelResampling::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmVolumeParcelResampling::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmVolumeParcelResampling.h000066400000000000000000000054431255417355300250540ustar00rootroot00000000000000#ifndef __ALGORITHM_VOLUME_PARCEL_RESAMPLING_H__ #define __ALGORITHM_VOLUME_PARCEL_RESAMPLING_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmVolumeParcelResampling : public AbstractAlgorithm { AlgorithmVolumeParcelResampling(); void matchLabels(const caret::VolumeFile* curLabel, const caret::VolumeFile* newLabel, std::vector >& matchedLabels); void generateVoxelLists(const std::vector >& matchedLabels, const VolumeFile* curLabel, const VolumeFile* newLabel, std::vector >& voxelLists); void resampleFixZeros(LevelProgress& myProgress, const std::vector >& matchedLabels, const std::vector >& voxelLists, const VolumeFile* inVol, const VolumeFile* curLabel, const caret::VolumeFile* newLabel, const float& kernel, VolumeFile* outVol, const int& subvolNum); void resample(LevelProgress& myProgress, const std::vector >& matchedLabels, const std::vector >& voxelLists, const VolumeFile* inVol, const VolumeFile* curLabel, const caret::VolumeFile* newLabel, const float& kernel, VolumeFile* outVol, const int& subvolNum); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmVolumeParcelResampling(ProgressObject* myProgObj, const VolumeFile* inVol, const VolumeFile* curLabel, const VolumeFile* newLabel, const float& kernel, VolumeFile* outVol, const bool& fixZeros = false, const int& subvolNum = -1); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmVolumeParcelResampling; } #endif //__ALGORITHM_VOLUME_PARCEL_RESAMPLING_H__ workbench-1.1.1/src/Algorithms/AlgorithmVolumeParcelResamplingGeneric.cxx000066400000000000000000000664521255417355300267330ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmVolumeParcelResamplingGeneric.h" #include "AlgorithmException.h" #include "VolumeFile.h" #include "CaretAssert.h" #include "CaretLogger.h" #include "Vector3D.h" #include "CaretOMP.h" #include #include #include #include using namespace caret; using namespace std; const int FIX_ZEROS_POST_ITERATIONS = 10;//number of times to do the "find remaining zeros and try to fill" code before giving up when -fix-zeros is specified AString AlgorithmVolumeParcelResamplingGeneric::getCommandSwitch() { return "-volume-parcel-resampling-generic"; } AString AlgorithmVolumeParcelResamplingGeneric::getShortDescription() { return "SMOOTH AND RESAMPLE VOLUME PARCELS FROM DIFFERENT VOLUME SPACE"; } OperationParameters* AlgorithmVolumeParcelResamplingGeneric::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addVolumeParameter(1, "volume-in", "the input data volume"); ret->addVolumeParameter(2, "cur-parcels", "label volume of where the parcels currently are"); ret->addVolumeParameter(3, "new-parcels", "label volume of where the parcels should be"); ret->addDoubleParameter(4, "kernel", "gaussian kernel sigma to smooth by during resampling"); ret->addVolumeOutputParameter(5, "volume-out", "output volume"); ret->createOptionalParameter(6, "-fix-zeros", "treat zero values as not being data"); OptionalParameter* subvolSelect = ret->createOptionalParameter(7, "-subvolume", "select a single subvolume as input"); subvolSelect->addStringParameter(1, "subvol", "the subvolume number or name"); ret->setHelpText( AString("Smooths and resamples the region inside each label in cur-parcels to the region of the same label name in new-parcels. ") + "Any voxels in the output label region but outside the input label region will be extrapolated from nearby data. " + "The -fix-zeros option causes the smoothing to not use an input value if it is zero, but still write a smoothed value to the voxel, and after smoothing " + "is complete, it will check for any remaining values of zero, and fill them in with extrapolated values. " + "The output volume will use the volume space of new-parcels, which does not need to be in the same volume space as the input." ); return ret; } void AlgorithmVolumeParcelResamplingGeneric::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { VolumeFile* inVol = myParams->getVolume(1); VolumeFile* curLabel = myParams->getVolume(2); VolumeFile* newLabel = myParams->getVolume(3); float kernel = (float)myParams->getDouble(4); VolumeFile* outVol = myParams->getOutputVolume(5); bool fixZeros = false; if (myParams->getOptionalParameter(6)->m_present) { fixZeros = true; } OptionalParameter* subvolSelect = myParams->getOptionalParameter(7); int subvolNum = -1; if (subvolSelect->m_present) { subvolNum = (int)inVol->getMapIndexFromNameOrNumber(subvolSelect->getString(1)); if (subvolNum < 0) { throw AlgorithmException("invalid subvolume specified"); } } AlgorithmVolumeParcelResamplingGeneric(myProgObj, inVol, curLabel, newLabel, kernel, outVol, fixZeros, subvolNum); } AlgorithmVolumeParcelResamplingGeneric::AlgorithmVolumeParcelResamplingGeneric(ProgressObject* myProgObj, const VolumeFile* inVol, const VolumeFile* curLabel, const VolumeFile* newLabel, const float& kernel, VolumeFile* outVol, const bool& fixZeros, const int& subvolNum) : AbstractAlgorithm(myProgObj) { CaretAssert(inVol != NULL); CaretAssert(curLabel != NULL); CaretAssert(newLabel != NULL); CaretAssert(outVol != NULL); if (!inVol->matchesVolumeSpace(curLabel)) { throw AlgorithmException("input label volume must be in the same space as input data volume"); } if (curLabel->getType() != SubvolumeAttributes::LABEL || newLabel->getType() != SubvolumeAttributes::LABEL) { throw AlgorithmException("parcel volumes are not of type label"); } if (subvolNum < -1 || subvolNum >= inVol->getNumberOfMaps()) { throw AlgorithmException("invalid subvolume specified"); } vector > matchedLabels; matchLabels(curLabel, newLabel, matchedLabels); if (matchedLabels.size() == 0) { throw AlgorithmException("no matching labels"); } vector > voxelLists; generateVoxelLists(matchedLabels, newLabel, voxelLists); int voxelListsSize = (int)voxelLists.size(); LevelProgress myProgress(myProgObj); float kernBox = kernel * 3.0f; float kernelMult = -1.0f / kernel / kernel / 2.0f;//precompute the part of the kernel function that doesn't change vector > newVolSpace = newLabel->getSform();//copied from volume smoothing, perhaps this should be in a convenience method in VolumeFile Vector3D ivecnew, jvecnew, kvecnew, ijorthnew, jkorthnew, kiorthnew; ivecnew[0] = newVolSpace[0][0]; jvecnew[0] = newVolSpace[0][1]; kvecnew[0] = newVolSpace[0][2]; ivecnew[1] = newVolSpace[1][0]; jvecnew[1] = newVolSpace[1][1]; kvecnew[1] = newVolSpace[1][2]; ivecnew[2] = newVolSpace[2][0]; jvecnew[2] = newVolSpace[2][1]; kvecnew[2] = newVolSpace[2][2]; ijorthnew = ivecnew.cross(jvecnew).normal();//find the bounding box that encloses a sphere of radius kernBox jkorthnew = jvecnew.cross(kvecnew).normal(); kiorthnew = kvecnew.cross(ivecnew).normal(); float irangenew = abs(kernBox / ivecnew.dot(jkorthnew));//note, these are in index space float jrangenew = abs(kernBox / jvecnew.dot(kiorthnew)); float krangenew = abs(kernBox / kvecnew.dot(ijorthnew)); if (irangenew < 1.0f) irangenew = 1.0f;//don't underflow, always use at least a 3x3x3 box if (jrangenew < 1.0f) jrangenew = 1.0f; if (krangenew < 1.0f) krangenew = 1.0f; vector > volSpace = inVol->getSform();//copied from volume smoothing, perhaps this should be in a convenience method in VolumeFile Vector3D ivec, jvec, kvec, ijorth, jkorth, kiorth; ivec[0] = volSpace[0][0]; jvec[0] = volSpace[0][1]; kvec[0] = volSpace[0][2];//needs to be this verbose because the axis and origin vectors are column vectors ivec[1] = volSpace[1][0]; jvec[1] = volSpace[1][1]; kvec[1] = volSpace[1][2];//while vector > is a column of row vectors ivec[2] = volSpace[2][0]; jvec[2] = volSpace[2][1]; kvec[2] = volSpace[2][2]; ijorth = ivec.cross(jvec).normal();//find the bounding box that encloses a sphere of radius kernBox jkorth = jvec.cross(kvec).normal(); kiorth = kvec.cross(ivec).normal(); float irange = abs(kernBox / ivec.dot(jkorth));//note, these are in index space float jrange = abs(kernBox / jvec.dot(kiorth)); float krange = abs(kernBox / kvec.dot(ijorth)); if (irange < 1.0f) irange = 1.0f;//don't underflow, always use at least a 3x3x3 box if (jrange < 1.0f) jrange = 1.0f; if (krange < 1.0f) krange = 1.0f; vector myDims; inVol->getDimensions(myDims); vector newDims; newLabel->getDimensions(newDims); if (subvolNum == -1) { vector outDims = newLabel->getOriginalDimensions(), inDims = inVol->getOriginalDimensions(); outDims.resize(3); for (int i = 3; i < (int)inDims.size(); ++i) { outDims.push_back(inDims[i]); } outVol->reinitialize(outDims, newLabel->getSform(), myDims[4], inVol->getType()); } else { vector outDims = newLabel->getOriginalDimensions(); outDims.resize(3);//discard nonspatial dimentions outVol->reinitialize(outDims, newLabel->getSform(), myDims[4], inVol->getType()); } outVol->setValueAllVoxels(0.0f); const float* labelFrame = curLabel->getFrame(); const float* newLabelFrame = newLabel->getFrame(); CaretArray scratchFrame(newDims[0] * newDims[1] * newDims[2]), scratchFrame2(newDims[0] * newDims[1] * newDims[2]), tempFrame; for (int whichList = 0; whichList < voxelListsSize; ++whichList) { int curLabelValue = matchedLabels[whichList].first; int newLabelValue = matchedLabels[whichList].second; vector& thisList = voxelLists[whichList]; int64_t listSize = (int64_t)thisList.size(); if (subvolNum == -1) { for (int c = 0; c < myDims[4]; ++c) { for (int s = 0; s < myDims[3]; ++s) { const float* inFrame = inVol->getFrame(s, c); //#pragma omp CARET_PARFOR schedule(dynamic) for (int64_t base = 0; base < listSize; base += 3) { float sum = 0.0f, weightsum = 0.0f; int i = thisList[base], j = thisList[base + 1], k = thisList[base + 2]; float xyz[3], curijk[3]; newLabel->indexToSpace(i, j, k, xyz); inVol->spaceToIndex(xyz, curijk); int imin = (int)ceil(curijk[0] - irange), imax = (int)floor(curijk[0] + irange) + 1; if (imin < 0) imin = 0; if (imax > myDims[0]) imax = myDims[0]; int jmin = (int)ceil(curijk[1] - jrange), jmax = (int)floor(curijk[1] + jrange) + 1; if (jmin < 0) jmin = 0; if (jmax > myDims[1]) jmax = myDims[1]; int kmin = (int)ceil(curijk[2] - krange), kmax = (int)floor(curijk[2] + krange) + 1; if (kmin < 0) kmin = 0; if (kmax > myDims[2]) kmax = myDims[2]; for (int kkern = kmin; kkern < kmax; ++kkern) { Vector3D kscratch = kvec * (kkern - curijk[2]); int64_t kindpart = kkern * myDims[1]; for (int jkern = jmin; jkern < jmax; ++jkern) { Vector3D jscratch = kscratch + jvec * (jkern - curijk[1]); int64_t jindpart = (kindpart + jkern) * myDims[0]; for (int ikern = imin; ikern < imax; ++ikern) { int64_t thisIndex = jindpart + ikern;//somewhat optimized index computation, could remove some integer multiplies, but there aren't that many int curVal = (int)floor(labelFrame[thisIndex] + 0.5f); float dataVal = inFrame[thisIndex]; if (curVal == curLabelValue && (!fixZeros || dataVal != 0.0f)) { Vector3D iscratch = jscratch + ivec * (ikern - curijk[0]); float tempf = iscratch.length(); float weight = exp(tempf * tempf * kernelMult); sum += weight * dataVal; weightsum += weight; } } } } if (weightsum != 0.0f) { scratchFrame[outVol->getIndex(i, j, k)] = sum / weightsum; } else { scratchFrame[outVol->getIndex(i, j, k)] = 0.0f; } } if (fixZeros) { int fixIter; for (fixIter = 0; fixIter < FIX_ZEROS_POST_ITERATIONS; ++fixIter) { bool again = false; //#pragma omp CARET_PARFOR schedule(dynamic) for (int64_t base = 0; base < listSize; base += 3) { int i = thisList[base], j = thisList[base + 1], k = thisList[base + 2]; int64_t outIndex = outVol->getIndex(i, j, k); float dataVal = scratchFrame[outIndex]; if (dataVal == 0.0f) { float sum = 0.0f, weightsum = 0.0f; int imin = (int)ceil(i - irangenew), imax = (int)floor(i + irangenew) + 1; if (imin < 0) imin = 0; if (imax > newDims[0]) imax = newDims[0]; int jmin = (int)ceil(j - jrangenew), jmax = (int)floor(j + jrangenew) + 1; if (jmin < 0) jmin = 0; if (jmax > newDims[1]) jmax = newDims[1]; int kmin = (int)ceil(k - krangenew), kmax = (int)floor(k + krangenew) + 1; if (kmin < 0) kmin = 0; if (kmax > newDims[2]) kmax = newDims[2]; for (int kkern = kmin; kkern < kmax; ++kkern) { Vector3D kscratch = kvecnew * (kkern - k); int64_t kindpart = kkern * newDims[1]; for (int jkern = jmin; jkern < jmax; ++jkern) { Vector3D jscratch = kscratch + jvecnew * (jkern - j); int64_t jindpart = (kindpart + jkern) * newDims[0]; for (int ikern = imin; ikern < imax; ++ikern) { int64_t thisIndex = jindpart + ikern;//somewhat optimized index computation, could remove some integer multiplies, but there aren't that many int newVal = (int)floor(newLabelFrame[thisIndex] + 0.5f); float dataVal = scratchFrame[thisIndex]; if (newVal == newLabelValue && (!fixZeros || dataVal != 0.0f)) { Vector3D iscratch = jscratch + ivecnew * (ikern - i); float tempf = iscratch.length(); float weight = exp(tempf * tempf * kernelMult); sum += weight * dataVal; weightsum += weight; } } } } if (weightsum != 0.0f) { scratchFrame2[outIndex] = sum / weightsum; } else { again = true; scratchFrame2[outIndex] = 0.0f; } } else { scratchFrame2[outIndex] = scratchFrame[outIndex]; } } tempFrame = scratchFrame;//this is just pointer swapping, CaretArray is not like vector scratchFrame = scratchFrame2; scratchFrame2 = tempFrame; if (!again) break; } if (fixIter == FIX_ZEROS_POST_ITERATIONS) { const GiftiLabelTable* curLabelTable = curLabel->getMapLabelTable(0); CaretLogWarning("unable to fix all zeros in parcel " + curLabelTable->getLabelName(curLabelValue)); } } for (int64_t base = 0; base < listSize; base += 3) { int i = thisList[base], j = thisList[base + 1], k = thisList[base + 2]; int64_t outIndex = outVol->getIndex(i, j, k); outVol->setValue(scratchFrame[outIndex], i, j, k, s, c); } } } } else { for (int c = 0; c < myDims[4]; ++c) { const float* inFrame = inVol->getFrame(subvolNum, c); #pragma omp CARET_PARFOR schedule(dynamic) for (int64_t base = 0; base < listSize; base += 3) { float sum = 0.0f, weightsum = 0.0f; int i = thisList[base], j = thisList[base + 1], k = thisList[base + 2]; float xyz[3], curijk[3]; newLabel->indexToSpace(i, j, k, xyz); inVol->spaceToIndex(xyz, curijk); int imin = (int)ceil(curijk[0] - irange), imax = (int)floor(curijk[0] + irange) + 1; if (imin < 0) imin = 0; if (imax > myDims[0]) imax = myDims[0]; int jmin = (int)ceil(curijk[1] - jrange), jmax = (int)floor(curijk[1] + jrange) + 1; if (jmin < 0) jmin = 0; if (jmax > myDims[1]) jmax = myDims[1]; int kmin = (int)ceil(curijk[2] - krange), kmax = (int)floor(curijk[2] + krange) + 1; if (kmin < 0) kmin = 0; if (kmax > myDims[2]) kmax = myDims[2]; for (int kkern = kmin; kkern < kmax; ++kkern) { Vector3D kscratch = kvec * (kkern - curijk[2]); int64_t kindpart = kkern * myDims[1]; for (int jkern = jmin; jkern < jmax; ++jkern) { Vector3D jscratch = kscratch + jvec * (jkern - curijk[1]); int64_t jindpart = (kindpart + jkern) * myDims[0]; for (int ikern = imin; ikern < imax; ++ikern) { int64_t thisIndex = jindpart + ikern;//somewhat optimized index computation, could remove some integer multiplies, but there aren't that many int newVal = (int)floor(newLabelFrame[thisIndex] + 0.5f); float dataVal = inFrame[thisIndex]; if (newVal == newLabelValue && (!fixZeros || dataVal != 0.0f)) { Vector3D iscratch = jscratch + ivec * (ikern - curijk[0]); float tempf = iscratch.length(); float weight = exp(tempf * tempf * kernelMult); sum += weight * dataVal; weightsum += weight; } } } } if (weightsum != 0.0f) { scratchFrame[outVol->getIndex(i, j, k)] = sum / weightsum; } else { scratchFrame[outVol->getIndex(i, j, k)] = 0.0f; } } if (fixZeros) { int fixIter; for (fixIter = 0; fixIter < FIX_ZEROS_POST_ITERATIONS; ++fixIter) { bool again = false; #pragma omp CARET_PARFOR schedule(dynamic) for (int64_t base = 0; base < listSize; base += 3) { int i = thisList[base], j = thisList[base + 1], k = thisList[base + 2]; int64_t outIndex = outVol->getIndex(i, j, k); float dataVal = scratchFrame[outIndex]; if (dataVal == 0.0f) { float sum = 0.0f, weightsum = 0.0f; int imin = (int)ceil(i - irangenew), imax = (int)floor(i + irangenew) + 1; if (imin < 0) imin = 0; if (imax > newDims[0]) imax = newDims[0]; int jmin = (int)ceil(j - jrangenew), jmax = (int)floor(j + jrangenew) + 1; if (jmin < 0) jmin = 0; if (jmax > newDims[1]) jmax = newDims[1]; int kmin = (int)ceil(k - krangenew), kmax = (int)floor(k + krangenew) + 1; if (kmin < 0) kmin = 0; if (kmax > newDims[2]) kmax = newDims[2]; for (int kkern = kmin; kkern < kmax; ++kkern) { Vector3D kscratch = kvecnew * (kkern - k); int64_t kindpart = kkern * newDims[1]; for (int jkern = jmin; jkern < jmax; ++jkern) { Vector3D jscratch = kscratch + jvecnew * (jkern - j); int64_t jindpart = (kindpart + jkern) * newDims[0]; for (int ikern = imin; ikern < imax; ++ikern) { int64_t thisIndex = jindpart + ikern;//somewhat optimized index computation, could remove some integer multiplies, but there aren't that many int curVal = (int)floor(labelFrame[thisIndex] + 0.5f); float dataVal = scratchFrame[thisIndex]; if (curVal == curLabelValue && (!fixZeros || dataVal != 0.0f)) { Vector3D iscratch = jscratch + ivecnew * (ikern - i); float tempf = iscratch.length(); float weight = exp(tempf * tempf * kernelMult); sum += weight * dataVal; weightsum += weight; } } } } if (weightsum != 0.0f) { scratchFrame2[outIndex] = sum / weightsum; } else { again = true; scratchFrame2[outIndex] = 0.0f; } } else { scratchFrame2[outIndex] = scratchFrame[outIndex]; } } tempFrame = scratchFrame;//this is just pointer swapping, CaretArray is not like vector scratchFrame = scratchFrame2; scratchFrame2 = tempFrame; if (!again) break; } if (fixIter == FIX_ZEROS_POST_ITERATIONS) { const GiftiLabelTable* curLabelTable = curLabel->getMapLabelTable(0); CaretLogWarning("unable to fix all zeros in parcel " + curLabelTable->getLabelName(curLabelValue)); } } for (int64_t base = 0; base < listSize; base += 3) { int i = thisList[base], j = thisList[base + 1], k = thisList[base + 2]; int64_t outIndex = outVol->getIndex(i, j, k); outVol->setValue(scratchFrame[outIndex], i, j, k, 0, c); } } } } } void AlgorithmVolumeParcelResamplingGeneric::matchLabels(const VolumeFile* curLabel, const VolumeFile* newLabel, vector >& matchedLabels) { const GiftiLabelTable* curTable = curLabel->getMapLabelTable(0), *newTable = newLabel->getMapLabelTable(0); vector curKeys; curTable->getKeys(curKeys); int32_t curUnused = curTable->getUnassignedLabelKey(); for (int i = 0; i < (int)curKeys.size(); ++i) { if (curKeys[i] == curUnused) continue;//always skip the unassigned label if (newTable->getLabel(curKeys[i]) != NULL && newTable->getLabelName(curKeys[i]) == curTable->getLabelName(curKeys[i])) {//do the obvious check first matchedLabels.push_back(make_pair(curKeys[i], curKeys[i])); } else { int32_t newKey = newTable->getLabelKeyFromName(curTable->getLabelName(curKeys[i])); if (newKey != -1) { matchedLabels.push_back(make_pair(curKeys[i], newKey)); } } } } void AlgorithmVolumeParcelResamplingGeneric::generateVoxelLists(const vector >& matchedLabels, const VolumeFile* newLabel, vector >& voxelLists) { map newLabelReverse; for (int i = 0; i < (int)matchedLabels.size(); ++i) { newLabelReverse[matchedLabels[i].second] = i; } voxelLists.resize(matchedLabels.size()); vector myDims; newLabel->getDimensions(myDims); for (int64_t k = 0; k < myDims[2]; ++k) { for (int64_t j = 0; j < myDims[1]; ++j) { for (int64_t i = 0; i < myDims[0]; ++i) { int newValue = (int)floor(newLabel->getValue(i, j, k) + 0.5f); map::iterator newiter = newLabelReverse.find(newValue); if (newiter != newLabelReverse.end()) { voxelLists[newiter->second].push_back(i); voxelLists[newiter->second].push_back(j); voxelLists[newiter->second].push_back(k); } } } } } float AlgorithmVolumeParcelResamplingGeneric::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmVolumeParcelResamplingGeneric::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmVolumeParcelResamplingGeneric.h000066400000000000000000000043141255417355300263450ustar00rootroot00000000000000#ifndef __ALGORITHM_VOLUME_PARCEL_RESAMPLING_GENERIC_H__ #define __ALGORITHM_VOLUME_PARCEL_RESAMPLING_GENERIC_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmVolumeParcelResamplingGeneric : public AbstractAlgorithm { AlgorithmVolumeParcelResamplingGeneric(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); void matchLabels(const caret::VolumeFile* curLabel, const caret::VolumeFile* newLabel, std::vector >& matchedLabels); void generateVoxelLists(const std::vector >& matchedLabels, const VolumeFile* newLabel, std::vector >& voxelLists); public: AlgorithmVolumeParcelResamplingGeneric(ProgressObject* myProgObj, const VolumeFile* inVol, const VolumeFile* curLabel, const VolumeFile* newLabel, const float& kernel, VolumeFile* outVol, const bool& fixZeros = false, const int& subvolNum = -1); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmVolumeParcelResamplingGeneric; } #endif //__ALGORITHM_VOLUME_PARCEL_RESAMPLING_GENERIC_H__ workbench-1.1.1/src/Algorithms/AlgorithmVolumeParcelSmoothing.cxx000066400000000000000000000315351255417355300252760ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmVolumeParcelSmoothing.h" #include "AlgorithmException.h" #include "VolumeFile.h" #include "AlgorithmVolumeSmoothing.h" #include #include using namespace caret; using namespace std; AString AlgorithmVolumeParcelSmoothing::getCommandSwitch() { return "-volume-parcel-smoothing"; } AString AlgorithmVolumeParcelSmoothing::getShortDescription() { return "SMOOTH PARCELS IN A VOLUME SEPARATELY"; } OperationParameters* AlgorithmVolumeParcelSmoothing::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addVolumeParameter(1, "data-volume", "the volume to smooth"); ret->addVolumeParameter(2, "label-volume", "a label volume containing the parcels to smooth"); ret->addDoubleParameter(3, "kernel", "the gaussian smoothing kernel sigma, in mm"); ret->addVolumeOutputParameter(4, "volume-out", "the output volume"); ret->createOptionalParameter(5, "-fix-zeros", "treat zero values as not being data"); OptionalParameter* subvolSelect = ret->createOptionalParameter(6, "-subvolume", "select a single subvolume to smooth"); subvolSelect->addStringParameter(1, "subvol", "the subvolume number or name"); ret->setHelpText( AString("The volume is smoothed within each label in the label volume using data only from within the label. Equivalent to ") + "running volume smoothing with ROIs matching each label separately, then adding the resulting volumes, but faster." ); return ret; } void AlgorithmVolumeParcelSmoothing::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { VolumeFile* myVol = myParams->getVolume(1); VolumeFile* myLabelVol = myParams->getVolume(2); float myKernel = (float)myParams->getDouble(3); VolumeFile* myOutVol = myParams->getOutputVolume(4); OptionalParameter* fixZerosOpt = myParams->getOptionalParameter(5); bool fixZeros = fixZerosOpt->m_present; OptionalParameter* subvolSelect = myParams->getOptionalParameter(6); int subvolNum = -1; if (subvolSelect->m_present) { subvolNum = (int)myVol->getMapIndexFromNameOrNumber(subvolSelect->getString(1)); if (subvolNum < 0) { throw AlgorithmException("invalid subvolume specified"); } } AlgorithmVolumeParcelSmoothing(myProgObj, myVol, myLabelVol, myKernel, myOutVol, fixZeros, subvolNum); } AlgorithmVolumeParcelSmoothing::AlgorithmVolumeParcelSmoothing(ProgressObject* myProgObj, const VolumeFile* myVol, const VolumeFile* myLabelVol, const float& myKernel, VolumeFile* myOutVol, const bool& fixZeros, const int& subvolNum) : AbstractAlgorithm(myProgObj) { CaretAssert(myVol != NULL); CaretAssert(myOutVol != NULL); CaretAssert(myLabelVol != NULL); if (myLabelVol->getType() != SubvolumeAttributes::LABEL) { throw AlgorithmException("label volume doesn't contain label data"); } if (!myLabelVol->matchesVolumeSpace(myVol)) { throw AlgorithmException("input volume and label volume have different spacing"); } const GiftiLabelTable* myLabels = myLabelVol->getMapLabelTable(0); vector myKeys; myLabels->getKeys(myKeys); int numLabels = (int)myKeys.size(); map labelLookup;//reverse lookup, used to compact labels into the list of voxel lists for (int i = 0; i < numLabels; ++i) { labelLookup[myKeys[i]] = i; } int32_t unusedLabel = myLabels->getUnassignedLabelKey(); /*ProgressObject* subAlgProgress1 = NULL;//uncomment these if you use another algorithm inside here if (myProgObj != NULL) { subAlgProgress1 = myProgObj->addAlgorithm(AlgorithmInsertNameHere::getAlgorithmWeight());//TODO: set a vector of objects up via number of labels }//*/ LevelProgress myProgress(myProgObj);//this line sets the algorithm up to use the progress object, and will finish the progress object automatically when the algorithm terminates vector myDims; myVol->getDimensions(myDims); if (subvolNum < -1 || subvolNum >= myDims[3]) { throw AlgorithmException("invalid subvolume specified"); } vector > voxelLists;//build all lists in a single pass, allows some short circuits and less conversion to label integers voxelLists.resize(numLabels); for (int k = 0; k < myDims[2]; ++k) { for (int j = 0; j < myDims[1]; ++j) { for (int i = 0; i < myDims[0]; ++i) { int myValue = (int)floor(myLabelVol->getValue(i, j, k) + 0.5f); if (myValue == unusedLabel) continue;//skip the ??? label, whether it is included in the keys or not map::iterator search = labelLookup.find(myValue); if (search != labelLookup.end()) { int keyIndex = search->second; voxelLists[keyIndex].push_back(i); voxelLists[keyIndex].push_back(j); voxelLists[keyIndex].push_back(k); } } } } if (subvolNum == -1) { myOutVol->reinitialize(myVol->getOriginalDimensions(), myVol->getSform(), myDims[4], myVol->getType()); myOutVol->setValueAllVoxels(0.0f); for (int whichList = 0; whichList < numLabels; ++whichList) { const vector& thisList = voxelLists[whichList]; int64_t listSize = (int64_t)thisList.size(); if (listSize > 2)//NOTE: this should NEVER be something other than a multiple of 3, but check against what we will actually access anyway { int extrema[6] = { thisList[0], thisList[0], thisList[1], thisList[1], thisList[2], thisList[2] }; for (int64_t base = 3; base < listSize; base += 3) { if (thisList[base] < extrema[0]) extrema[0] = thisList[base]; if (thisList[base] > extrema[1]) extrema[1] = thisList[base]; if (thisList[base + 1] < extrema[2]) extrema[2] = thisList[base + 1]; if (thisList[base + 1] > extrema[3]) extrema[3] = thisList[base + 1]; if (thisList[base + 2] < extrema[4]) extrema[4] = thisList[base + 2]; if (thisList[base + 2] > extrema[5]) extrema[5] = thisList[base + 2]; } vector boxdims; boxdims.push_back(extrema[1] - extrema[0] + 1); boxdims.push_back(extrema[3] - extrema[2] + 1); boxdims.push_back(extrema[5] - extrema[4] + 1); VolumeFile roibox(boxdims, myVol->getSform()); boxdims.push_back(myDims[3]); VolumeFile inbox(boxdims, myVol->getSform(), myDims[4]), outbox; roibox.setValueAllVoxels(0.0f); for (int c = 0; c < myDims[4]; ++c) { for (int s = 0; s < myDims[3]; ++s) { for (int64_t base = 0; base < listSize; base += 3) { if (s == 0 && c == 0) roibox.setValue(1.0f, thisList[base] - extrema[0], thisList[base + 1] - extrema[2], thisList[base + 2] - extrema[4]); inbox.setValue(myVol->getValue(thisList[base], thisList[base + 1], thisList[base + 2], s, c), thisList[base] - extrema[0], thisList[base + 1] - extrema[2], thisList[base + 2] - extrema[4], s, c); } } } AlgorithmVolumeSmoothing(NULL, &inbox, myKernel, &outbox, &roibox, fixZeros); for (int c = 0; c < myDims[4]; ++c) { for (int s = 0; s < myDims[3]; ++s) { for (int64_t base = 0; base < listSize; base += 3) { myOutVol->setValue(outbox.getValue(thisList[base] - extrema[0], thisList[base + 1] - extrema[2], thisList[base + 2] - extrema[4], s, c), thisList[base], thisList[base + 1], thisList[base + 2], s, c); } } } } } for (int s = 0; s < myDims[3]; ++s) { myOutVol->setMapName(s, myVol->getMapName(s) + ", parcel smoothed " + AString::number(myKernel)); } } else { vector newDims = myVol->getOriginalDimensions(); newDims.resize(3);//discard non-spatial extra dimensions myOutVol->reinitialize(newDims, myVol->getSform(), myDims[4], myVol->getType());//keep components myOutVol->setValueAllVoxels(0.0f); for (int whichList = 0; whichList < numLabels; ++whichList) { const vector& thisList = voxelLists[whichList]; int64_t listSize = (int64_t)thisList.size(); if (listSize > 2)//NOTE: this should NEVER be something other than a multiple of 3, but check against what we will actually access anyway { int extrema[6] = { thisList[0], thisList[0], thisList[1], thisList[1], thisList[2], thisList[2] }; for (int64_t base = 3; base < listSize; base += 3) { if (thisList[base] < extrema[0]) extrema[0] = thisList[base]; if (thisList[base] > extrema[1]) extrema[1] = thisList[base]; if (thisList[base + 1] < extrema[2]) extrema[2] = thisList[base + 1]; if (thisList[base + 1] > extrema[3]) extrema[3] = thisList[base + 1]; if (thisList[base + 2] < extrema[4]) extrema[4] = thisList[base + 2]; if (thisList[base + 2] > extrema[5]) extrema[5] = thisList[base + 2]; } vector boxdims; boxdims.push_back(extrema[1] - extrema[0] + 1); boxdims.push_back(extrema[3] - extrema[2] + 1); boxdims.push_back(extrema[5] - extrema[4] + 1); VolumeFile inbox(boxdims, myVol->getSform(), myDims[4]), roibox(boxdims, myVol->getSform()), outbox; roibox.setValueAllVoxels(0.0f); for (int c = 0; c < myDims[4]; ++c) { for (int64_t base = 0; base < listSize; base += 3) { if (c == 0) roibox.setValue(1.0f, thisList[base] - extrema[0], thisList[base + 1] - extrema[2], thisList[base + 2] - extrema[4]); inbox.setValue(myVol->getValue(thisList[base], thisList[base + 1], thisList[base + 2], subvolNum, c), thisList[base] - extrema[0], thisList[base + 1] - extrema[2], thisList[base + 2] - extrema[4], 0, c); } } AlgorithmVolumeSmoothing(NULL, &inbox, myKernel, &outbox, &roibox, fixZeros); for (int c = 0; c < myDims[4]; ++c) { for (int64_t base = 0; base < listSize; base += 3) { myOutVol->setValue(outbox.getValue(thisList[base] - extrema[0], thisList[base + 1] - extrema[2], thisList[base + 2] - extrema[4], 0, c), thisList[base], thisList[base + 1], thisList[base + 2], 0, c); } } } } myOutVol->setMapName(0, myVol->getMapName(subvolNum) + ", parcel smoothed " + AString::number(myKernel)); } } float AlgorithmVolumeParcelSmoothing::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmVolumeParcelSmoothing::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmVolumeParcelSmoothing.h000066400000000000000000000034741255417355300247240ustar00rootroot00000000000000#ifndef __ALGORITHM_VOLUME_PARCEL_SMOOTHING_H__ #define __ALGORITHM_VOLUME_PARCEL_SMOOTHING_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmVolumeParcelSmoothing : public AbstractAlgorithm { AlgorithmVolumeParcelSmoothing(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmVolumeParcelSmoothing(ProgressObject* myProgObj, const VolumeFile* myVol, const VolumeFile* myLabelVol, const float& myKernel, VolumeFile* myOutVol, const bool& fixZeros = false, const int& subvolNum = -1); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmVolumeParcelSmoothing; } #endif //__ALGORITHM_VOLUME_PARCEL_SMOOTHING_H__ workbench-1.1.1/src/Algorithms/AlgorithmVolumeROIsFromExtrema.cxx000066400000000000000000000420551255417355300251650ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmVolumeROIsFromExtrema.h" #include "AlgorithmException.h" #include "FloatMatrix.h" #include "Vector3D.h" #include "VolumeFile.h" #include using namespace caret; using namespace std; AString AlgorithmVolumeROIsFromExtrema::getCommandSwitch() { return "-volume-rois-from-extrema"; } AString AlgorithmVolumeROIsFromExtrema::getShortDescription() { return "CREATE VOLUME ROI MAPS FROM EXTREMA MAPS"; } OperationParameters* AlgorithmVolumeROIsFromExtrema::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addVolumeParameter(1, "volume-in", "the input volume"); ret->addDoubleParameter(2, "limit", "distance limit from voxel center, in mm"); ret->addVolumeOutputParameter(3, "volume-out", "the output volume"); OptionalParameter* gaussOpt = ret->createOptionalParameter(4, "-gaussian", "generate a gaussian kernel instead of a flat ROI"); gaussOpt->addDoubleParameter(1, "sigma", "the sigma for the gaussian kernel, in mm"); OptionalParameter* roiOption = ret->createOptionalParameter(5, "-roi", "select a region of interest to use"); roiOption->addVolumeParameter(1, "roi-volume", "the region to use"); OptionalParameter* overlapOpt = ret->createOptionalParameter(6, "-overlap-logic", "how to handle overlapping ROIs, default ALLOW"); overlapOpt->addStringParameter(1, "method", "the method of resolving overlaps"); OptionalParameter* subvolSelect = ret->createOptionalParameter(7, "-subvolume", "select a single subvolume to take the gradient of"); subvolSelect->addStringParameter(1, "subvol", "the subvolume number or name"); ret->setHelpText( AString("For each nonzero value in each map, make a map with an ROI around that location. ") + "If the -gaussian option is specified, then normalized gaussian kernels are output instead of ROIs. " + "The argument to -overlap-logic must be one of ALLOW, CLOSEST, or EXCLUDE. " + "ALLOW is the default, and means that ROIs are treated independently and may overlap. " + "CLOSEST means that ROIs may not overlap, and that no ROI contains vertices that are closer to a different seed vertex. " + "EXCLUDE means that ROIs may not overlap, and that any vertex within range of more than one ROI does not belong to any ROI." ); return ret; } void AlgorithmVolumeROIsFromExtrema::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { VolumeFile* myVol = myParams->getVolume(1); float limit = (float)myParams->getDouble(2); VolumeFile* myVolOut = myParams->getOutputVolume(3); float sigma = -1.0f; OptionalParameter* gaussOpt = myParams->getOptionalParameter(4); if (gaussOpt->m_present) { sigma = (float)gaussOpt->getDouble(1); if (sigma <= 0.0f) { throw AlgorithmException("invalid sigma specified"); } } VolumeFile* myRoi = NULL; OptionalParameter* roiOption = myParams->getOptionalParameter(5); if (roiOption->m_present) { myRoi = roiOption->getVolume(1); } OverlapLogicEnum::Enum overlapType = OverlapLogicEnum::ALLOW; OptionalParameter* overlapOpt = myParams->getOptionalParameter(6); if (overlapOpt->m_present) { bool ok = false; overlapType = OverlapLogicEnum::fromName(overlapOpt->getString(1), &ok); if (!ok) throw AlgorithmException("unrecognized overlap method: " + overlapOpt->getString(1)); } OptionalParameter* subvolSelect = myParams->getOptionalParameter(7); int subvolNum = -1; if (subvolSelect->m_present) { subvolNum = (int)myVol->getMapIndexFromNameOrNumber(subvolSelect->getString(1)); if (subvolNum < 0) { throw AlgorithmException("invalid subvolume specified"); } } AlgorithmVolumeROIsFromExtrema(myProgObj, myVol, limit, myVolOut, sigma, myRoi, overlapType, subvolNum); } AlgorithmVolumeROIsFromExtrema::AlgorithmVolumeROIsFromExtrema(ProgressObject* myProgObj, const VolumeFile* myVol, const float& limit, VolumeFile* myVolOut, const float& sigma, const VolumeFile* myRoi, const OverlapLogicEnum::Enum& overlapType, const int& subvolNum) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); const float* roiFrame = NULL; if (myRoi != NULL) { if (!myRoi->matchesVolumeSpace(myVol)) throw AlgorithmException("roi volume has a different volume space"); roiFrame = myRoi->getFrame(); } int64_t extremaCount = 0; vector myDims; myVol->getDimensions(myDims); int64_t frameSize = myDims[0] * myDims[1] * myDims[2]; if (subvolNum == -1) { if (roiFrame == NULL) { for (int c = 0; c < myDims[4]; ++c) { for (int b = 0; b < myDims[3]; ++b) { const float* frame = myVol->getFrame(b, c); for (int64_t index = 0; index < frameSize; ++index) { if (frame[index] != 0.0f) ++extremaCount; } } } } else { for (int c = 0; c < myDims[4]; ++c) { for (int b = 0; b < myDims[3]; ++b) { const float* frame = myVol->getFrame(b, c); for (int64_t index = 0; index < frameSize; ++index) { if (roiFrame[index] > 0.0f && frame[index] != 0.0f) ++extremaCount; } } } } } else { if (roiFrame == NULL) { for (int c = 0; c < myDims[4]; ++c) { const float* frame = myVol->getFrame(subvolNum, c); for (int64_t index = 0; index < frameSize; ++index) { if (frame[index] != 0.0f) ++extremaCount; } } } else { for (int c = 0; c < myDims[4]; ++c) { const float* frame = myVol->getFrame(subvolNum, c); for (int64_t index = 0; index < frameSize; ++index) { if (roiFrame[index] > 0.0f && frame[index] != 0.0f) ++extremaCount; } } } } Vector3D ivec, jvec, kvec, offset, ijorth, jkorth, kiorth; FloatMatrix(myVol->getVolumeSpace().getSform()).getAffineVectors(ivec, jvec, kvec, offset);//this should probably be made more accessible ijorth = ivec.cross(jvec).normal();//find the bounding box that encloses a sphere of radius limit jkorth = jvec.cross(kvec).normal(); kiorth = kvec.cross(ivec).normal(); int irange = (int)floor(abs(limit / ivec.dot(jkorth))); int jrange = (int)floor(abs(limit / jvec.dot(kiorth))); int krange = (int)floor(abs(limit / kvec.dot(ijorth))); Vector3D iscratch, jscratch, kscratch; vector stencil; vector stencildist; for (int k = -krange; k <= krange; ++k) { kscratch = kvec * k; for (int j = -jrange; j <= jrange; ++j) { jscratch = kscratch + jvec * j; for (int i = -irange; i <= irange; ++i) { iscratch = jscratch + ivec * i; float tempf = iscratch.length(); if (tempf <= limit) { stencil.push_back(i); stencil.push_back(j); stencil.push_back(k); stencildist.push_back(tempf); } } } } vector excludeDists(frameSize, -1.0f); vector excludeSources(frameSize, -1); vector > roiLists(extremaCount); int64_t mapCounter = 0; if (subvolNum == -1) { for (int c = 0; c < myDims[4]; ++c) { for (int b = 0; b < myDims[3]; ++b) { const float* data = myVol->getFrame(b, c); processFrame(data, excludeDists, excludeSources, roiLists, mapCounter, stencil, stencildist, roiFrame, overlapType, myVol->getVolumeSpace()); } } } else { for (int c = 0; c < myDims[4]; ++c) { const float* data = myVol->getFrame(subvolNum, c); processFrame(data, excludeDists, excludeSources, roiLists, mapCounter, stencil, stencildist, roiFrame, overlapType, myVol->getVolumeSpace()); } } CaretAssert(mapCounter == extremaCount); vector outDims = myDims; outDims.resize(4); outDims[3] = extremaCount; myVolOut->reinitialize(outDims, myVol->getSform()); vector tempFrame(frameSize, 0.0f); if (sigma > 0.0f) { float gaussDenom = -0.5f / sigma / sigma; for (int64_t i = 0; i < mapCounter; ++i) { double accum = 0.0; map::iterator myEnd = roiLists[i].end(); for (map::iterator iter = roiLists[i].begin(); iter != myEnd; ++iter) { float gaussVal = exp(iter->second * iter->second * gaussDenom); accum += gaussVal; tempFrame[iter->first] = gaussVal;//initial kernel value } for (map::iterator iter = roiLists[i].begin(); iter != myEnd; ++iter) { tempFrame[iter->first] /= accum;//normalize } myVolOut->setFrame(tempFrame.data(), i); for (map::iterator iter = roiLists[i].begin(); iter != myEnd; ++iter) { tempFrame[iter->first] = 0.0f;//rezero changed values for next map } } } else { for (int64_t i = 0; i < mapCounter; ++i) { map::iterator myEnd = roiLists[i].end(); for (map::iterator iter = roiLists[i].begin(); iter != myEnd; ++iter) { tempFrame[iter->first] = 1.0f;//make roi } myVolOut->setFrame(tempFrame.data(), i); for (map::iterator iter = roiLists[i].begin(); iter != myEnd; ++iter) { tempFrame[iter->first] = 0.0f;//rezero changed values for next map } } } } void AlgorithmVolumeROIsFromExtrema::processFrame(const float* data, vector& excludeDists, vector& excludeSources, vector >& roiLists, int64_t& mapCounter, const vector& stencil, const vector& stencildist, const float* roiFrame, const OverlapLogicEnum::Enum& overlapType, const VolumeSpace& mySpace) { const int64_t* myDims = mySpace.getDims(); int stencilSize = (int)stencildist.size(); for (int k = 0; k < myDims[2]; ++k) { for (int j = 0; j < myDims[1]; ++j) { for (int i = 0; i < myDims[0]; ++i) { int64_t myIndex = mySpace.getIndex(i, j, k); if ((roiFrame == NULL || roiFrame[myIndex] > 0.0f) && data[myIndex] != 0.0f) { switch (overlapType) { case OverlapLogicEnum::ALLOW: if (roiFrame == NULL) { for (int s = 0; s < stencilSize; ++s) { int s3 = s * 3; const int thisVoxel[3] = { i + stencil[s3], j + stencil[s3 + 1], k + stencil[s3 + 2] }; if (mySpace.indexValid(thisVoxel)) { roiLists[mapCounter][mySpace.getIndex(thisVoxel)] = stencildist[s]; } } } else { for (int s = 0; s < stencilSize; ++s) { int s3 = s * 3; const int thisVoxel[3] = { i + stencil[s3], j + stencil[s3 + 1], k + stencil[s3 + 2] }; if (mySpace.indexValid(thisVoxel) && roiFrame[mySpace.getIndex(thisVoxel)] > 0.0f) { roiLists[mapCounter][mySpace.getIndex(thisVoxel)] = stencildist[s]; } } } break; case OverlapLogicEnum::CLOSEST: for (int s = 0; s < stencilSize; ++s) { int s3 = s * 3; const int thisVoxel[3] = { i + stencil[s3], j + stencil[s3 + 1], k + stencil[s3 + 2] }; if (mySpace.indexValid(thisVoxel) && (roiFrame == NULL || roiFrame[mySpace.getIndex(thisVoxel)] > 0.0f)) { int64_t thisIndex = mySpace.getIndex(thisVoxel); const float& thisDist = stencildist[s]; roiLists[mapCounter][thisIndex] = thisDist; if (excludeDists[thisIndex] < 0.0f) { excludeDists[thisIndex] = thisDist; excludeSources[thisIndex] = mapCounter; roiLists[mapCounter][thisIndex] = thisDist; } else { if (excludeDists[thisIndex] > thisDist) { roiLists[excludeSources[thisIndex]].erase(thisIndex); } excludeDists[thisIndex] = thisDist; excludeSources[thisIndex] = mapCounter; roiLists[mapCounter][thisIndex] = thisDist; } } } break; case OverlapLogicEnum::EXCLUDE: for (int s = 0; s < stencilSize; ++s) { int s3 = s * 3; const int thisVoxel[3] = { i + stencil[s3], j + stencil[s3 + 1], k + stencil[s3 + 2] }; if (mySpace.indexValid(thisVoxel) && (roiFrame == NULL || roiFrame[mySpace.getIndex(thisVoxel)] > 0.0f)) { int64_t thisIndex = mySpace.getIndex(thisVoxel); const float& thisDist = stencildist[s]; roiLists[mapCounter][thisIndex] = thisDist; if (excludeDists[thisIndex] < 0.0f) { excludeDists[thisIndex] = thisDist; excludeSources[thisIndex] = mapCounter; roiLists[mapCounter][thisIndex] = thisDist; } else { if (excludeSources[thisIndex] != -1) { roiLists[excludeSources[thisIndex]].erase(thisIndex); excludeSources[thisIndex] = -1; } } } } break; } ++mapCounter; } } } } } float AlgorithmVolumeROIsFromExtrema::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmVolumeROIsFromExtrema::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmVolumeROIsFromExtrema.h000066400000000000000000000046271255417355300246150ustar00rootroot00000000000000#ifndef __ALGORITHM_VOLUME_ROIS_FROM_EXTREMA_H__ #define __ALGORITHM_VOLUME_ROIS_FROM_EXTREMA_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" #include "OverlapLogicEnum.h" #include "VolumeSpace.h" #include #include namespace caret { class AlgorithmVolumeROIsFromExtrema : public AbstractAlgorithm { AlgorithmVolumeROIsFromExtrema(); void processFrame(const float* data, std::vector& excludeDists, std::vector& excludeSouces, std::vector >& roiLists, int64_t& mapCounter, const std::vector& stencil, const std::vector& stencildist, const float* roiFrame, const OverlapLogicEnum::Enum& overlapType, const VolumeSpace& myDims); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmVolumeROIsFromExtrema(ProgressObject* myProgObj, const VolumeFile* myVol, const float& limit, VolumeFile* myVolOut, const float& sigma = -1.0f, const VolumeFile* myRoi = NULL, const OverlapLogicEnum::Enum& overlapType = OverlapLogicEnum::ALLOW, const int& subvolNum = -1); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmVolumeROIsFromExtrema; } #endif //__ALGORITHM_VOLUME_ROIS_FROM_EXTREMA_H__ workbench-1.1.1/src/Algorithms/AlgorithmVolumeReduce.cxx000066400000000000000000000137301255417355300234040ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmVolumeReduce.h" #include "AlgorithmException.h" #include "CaretLogger.h" #include "GiftiLabelTable.h" #include "ReductionOperation.h" #include "VolumeFile.h" #include using namespace caret; using namespace std; AString AlgorithmVolumeReduce::getCommandSwitch() { return "-volume-reduce"; } AString AlgorithmVolumeReduce::getShortDescription() { return "PERFORM REDUCTION OPERATION ACROSS SUBVOLUMES"; } OperationParameters* AlgorithmVolumeReduce::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addVolumeParameter(1, "volume-in", "the volume file to reduce"); ret->addStringParameter(2, "operation", "the reduction operator to use"); ret->addVolumeOutputParameter(3, "volume-out", "the output volume"); OptionalParameter* excludeOpt = ret->createOptionalParameter(4, "-exclude-outliers", "exclude outliers from each timeseries by standard deviation"); excludeOpt->addDoubleParameter(1, "sigma-below", "number of standard deviations below the mean to include"); excludeOpt->addDoubleParameter(2, "sigma-above", "number of standard deviations above the mean to include"); ret->setHelpText( AString("For each voxel, takes the data across subvolumes as a vector, and performs the specified reduction on it, putting the result ") + "into the single output volume at that voxel. The reduction operators are as follows:\n\n" + ReductionOperation::getHelpInfo() ); return ret; } void AlgorithmVolumeReduce::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { VolumeFile* volumeIn = myParams->getVolume(1); AString opString = myParams->getString(2); VolumeFile* volumeOut = myParams->getOutputVolume(3); OptionalParameter* excludeOpt = myParams->getOptionalParameter(4); bool ok = false; ReductionEnum::Enum myReduce = ReductionEnum::fromName(opString, &ok); if (!ok) throw AlgorithmException("unrecognized operation string '" + opString + "'"); if (excludeOpt->m_present) { AlgorithmVolumeReduce(myProgObj, volumeIn, myReduce, volumeOut, excludeOpt->getDouble(1), excludeOpt->getDouble(2)); } else { AlgorithmVolumeReduce(myProgObj, volumeIn, myReduce, volumeOut); } } AlgorithmVolumeReduce::AlgorithmVolumeReduce(ProgressObject* myProgObj, const VolumeFile* volumeIn, const ReductionEnum::Enum& myReduce, VolumeFile* volumeOut) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); vector myDims, newDims = volumeIn->getOriginalDimensions(); newDims.resize(3, 1);//have only one subvolume volumeIn->getDimensions(myDims); volumeOut->reinitialize(newDims, volumeIn->getSform(), myDims[4], volumeIn->getType()); if (volumeIn->getType() == SubvolumeAttributes::LABEL) { CaretLogWarning("reduction operation performed on label volume"); *(volumeOut->getMapLabelTable(0)) = *(volumeIn->getMapLabelTable(0)); } int64_t frameSize = myDims[0] * myDims[1] * myDims[2]; vector scratchArray(myDims[3]), outFrame(frameSize); for (int c = 0; c < myDims[4]; ++c) { for (int64_t i = 0; i < frameSize; ++i) { for (int b = 0; b < myDims[3]; ++b) { const float* tempFrame = volumeIn->getFrame(b, c); scratchArray[b] = tempFrame[i]; } outFrame[i] = ReductionOperation::reduce(scratchArray.data(), myDims[3], myReduce); } volumeOut->setFrame(outFrame.data(), 0, c); } } AlgorithmVolumeReduce::AlgorithmVolumeReduce(ProgressObject* myProgObj, const VolumeFile* volumeIn, const ReductionEnum::Enum& myReduce, VolumeFile* volumeOut, const float& sigmaBelow, const float& sigmaAbove) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); vector myDims, newDims = volumeIn->getOriginalDimensions(); newDims.resize(3, 1);//have only one subvolume volumeIn->getDimensions(myDims); volumeOut->reinitialize(newDims, volumeIn->getSform(), myDims[4], volumeIn->getType()); if (volumeIn->getType() == SubvolumeAttributes::LABEL) { CaretLogWarning("reduction operation performed on label volume"); *(volumeOut->getMapLabelTable(0)) = *(volumeIn->getMapLabelTable(0)); } int64_t frameSize = myDims[0] * myDims[1] * myDims[2]; vector scratchArray(myDims[3]), outFrame(frameSize); for (int c = 0; c < myDims[4]; ++c) { for (int64_t i = 0; i < frameSize; ++i) { for (int b = 0; b < myDims[3]; ++b) { const float* tempFrame = volumeIn->getFrame(b, c); scratchArray[b] = tempFrame[i]; } outFrame[i] = ReductionOperation::reduceExcludeDev(scratchArray.data(), myDims[3], myReduce, sigmaBelow, sigmaAbove); } volumeOut->setFrame(outFrame.data(), 0, c); } } float AlgorithmVolumeReduce::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmVolumeReduce::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmVolumeReduce.h000066400000000000000000000036131255417355300230300ustar00rootroot00000000000000#ifndef __ALGORITHM_VOLUME_REDUCE_H__ #define __ALGORITHM_VOLUME_REDUCE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" #include "ReductionEnum.h" namespace caret { class AlgorithmVolumeReduce : public AbstractAlgorithm { AlgorithmVolumeReduce(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmVolumeReduce(ProgressObject* myProgObj, const VolumeFile* volumeIn, const ReductionEnum::Enum& myReduce, VolumeFile* volumeOut); AlgorithmVolumeReduce(ProgressObject* myProgObj, const VolumeFile* volumeIn, const ReductionEnum::Enum& myReduce, VolumeFile* volumeOut, const float& sigmaBelow, const float& sigmaAbove); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmVolumeReduce; } #endif //__ALGORITHM_VOLUME_REDUCE_H__ workbench-1.1.1/src/Algorithms/AlgorithmVolumeRemoveIslands.cxx000066400000000000000000000154361255417355300247550ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmVolumeRemoveIslands.h" #include "AlgorithmException.h" #include "VolumeFile.h" #include using namespace caret; using namespace std; AString AlgorithmVolumeRemoveIslands::getCommandSwitch() { return "-volume-remove-islands"; } AString AlgorithmVolumeRemoveIslands::getShortDescription() { return "REMOVE ISLANDS FROM AN ROI VOLUME"; } OperationParameters* AlgorithmVolumeRemoveIslands::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addVolumeParameter(1, "volume-in", "the input ROI volume"); ret->addVolumeOutputParameter(2, "volume-out", "the output ROI volume"); ret->setHelpText( AString("Finds all face-connected parts of the ROI, and zeros out all but the largest one.") ); return ret; } void AlgorithmVolumeRemoveIslands::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { VolumeFile* myVolIn = myParams->getVolume(1); VolumeFile* myVolOut = myParams->getOutputVolume(2); AlgorithmVolumeRemoveIslands(myProgObj, myVolIn, myVolOut); } AlgorithmVolumeRemoveIslands::AlgorithmVolumeRemoveIslands(ProgressObject* myProgObj, const VolumeFile* myVolIn, VolumeFile* myVolOut) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); const int STENCIL_SIZE = 18;//the easy way, and prepare for different stencils if we ever need them const int stencil[STENCIL_SIZE] = { 0, 0, -1, 0, -1, 0, -1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1 }; vector dims; myVolIn->getDimensions(dims); myVolOut->reinitialize(myVolIn->getOriginalDimensions(), myVolIn->getSform(), myVolIn->getNumberOfComponents(), myVolIn->getType()); for (int s = 0; s < dims[3]; ++s) { myVolOut->setMapName(s, myVolIn->getMapName(s)); for (int c = 0; c < dims[4]; ++c) { int64_t ijk[3]; vector > parts; vector used(dims[0] * dims[1] * dims[2], 0); const float* frame = myVolIn->getFrame(s, c); for (ijk[2] = 0; ijk[2] < dims[2]; ++ijk[2]) { for (ijk[1] = 0; ijk[1] < dims[1]; ++ijk[1]) { for (ijk[0] = 0; ijk[0] < dims[0]; ++ijk[0]) { int64_t index = myVolIn->getIndex(ijk); if (used[index] == 0 && frame[index] > 0.0f) { parts.push_back(vector()); vector& thispart = parts.back(); thispart.push_back(ijk[0]); thispart.push_back(ijk[1]); thispart.push_back(ijk[2]); used[index] = 1; vector mystack; mystack.push_back(ijk[0]); mystack.push_back(ijk[1]); mystack.push_back(ijk[2]); while (!mystack.empty()) { int64_t curSize = (int64_t)mystack.size(); int64_t curvox[3] = { mystack[curSize - 3], mystack[curSize - 2], mystack[curSize - 1] }; mystack.resize(curSize - 3);//basically pop_back for (int i = 0; i < STENCIL_SIZE; i += 3) { int64_t neighbor[3] = { curvox[0] + stencil[i], curvox[1] + stencil[i + 1], curvox[2] + stencil[i + 2] }; if (myVolIn->indexValid(neighbor)) { int64_t neighindex = myVolIn->getIndex(neighbor); if (used[neighindex] == 0 && frame[neighindex] > 0.0f) { thispart.push_back(neighbor[0]); thispart.push_back(neighbor[1]); thispart.push_back(neighbor[2]); used[neighindex] = 1; mystack.push_back(neighbor[0]); mystack.push_back(neighbor[1]); mystack.push_back(neighbor[2]); } } } } } } } } int64_t bestCount = -1, bestPart = -1, numParts = (int64_t)parts.size(); for (int64_t i = 0; i < numParts; ++i) { int64_t thisCount = (int64_t)parts[i].size(); if (thisCount > bestCount) { bestCount = thisCount; bestPart = i; } } vector outFrame(dims[0] * dims[1] * dims[2], 0.0f); if (bestPart != -1) { vector& myPart = parts[bestPart]; for (int64_t i = 0; i < bestCount; i += 3) { int64_t myIndex = myVolIn->getIndex(myPart.data() + i); outFrame[myIndex] = 1.0f;//make it a simple 0/1 volume, even if it wasn't before } } myVolOut->setFrame(outFrame.data(), s, c); } } } float AlgorithmVolumeRemoveIslands::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmVolumeRemoveIslands::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmVolumeRemoveIslands.h000066400000000000000000000033001255417355300243650ustar00rootroot00000000000000#ifndef __ALGORITHM_VOLUME_REMOVE_ISLANDS_H__ #define __ALGORITHM_VOLUME_REMOVE_ISLANDS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmVolumeRemoveIslands : public AbstractAlgorithm { AlgorithmVolumeRemoveIslands(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmVolumeRemoveIslands(ProgressObject* myProgObj, const VolumeFile* myVolIn, VolumeFile* myVolOut); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmVolumeRemoveIslands; } #endif //__ALGORITHM_VOLUME_REMOVE_ISLANDS_H__ workbench-1.1.1/src/Algorithms/AlgorithmVolumeSmoothing.cxx000066400000000000000000000772151255417355300241540ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmVolumeSmoothing.h" #include "AlgorithmException.h" #include "VolumeFile.h" #include "Vector3D.h" #include "CaretLogger.h" #include "CaretOMP.h" #include "CaretAssert.h" #include using namespace caret; using namespace std; //makes the program issue warning only once per launch, prevents repeated calls by other algorithms from spamming bool AlgorithmVolumeSmoothing::haveWarned = false; AString AlgorithmVolumeSmoothing::getCommandSwitch() { return "-volume-smoothing"; } AString AlgorithmVolumeSmoothing::getShortDescription() { return "SMOOTH A VOLUME FILE"; } OperationParameters* AlgorithmVolumeSmoothing::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addVolumeParameter(1, "volume-in", "the volume to smooth"); ret->addDoubleParameter(2, "kernel", "the gaussian smoothing kernel sigma, in mm"); ret->addVolumeOutputParameter(3, "volume-out", "the output volume"); OptionalParameter* roiVolOpt = ret->createOptionalParameter(4, "-roi", "smooth only from data within an ROI"); roiVolOpt->addVolumeParameter(1, "roivol", "the volume to use as an ROI"); ret->createOptionalParameter(5, "-fix-zeros", "treat zero values as not being data"); OptionalParameter* subvolSelect = ret->createOptionalParameter(6, "-subvolume", "select a single subvolume to smooth"); subvolSelect->addStringParameter(1, "subvol", "the subvolume number or name"); ret->setHelpText( AString("Gaussian smoothing for volumes. By default, smooths all subvolumes with no ROI, if ROI is given, only ") + "positive voxels in the ROI volume have their values used, and all other voxels are set to zero. Smoothing a non-orthogonal volume will " + "be significantly slower, because the operation cannot be separated into 1-dimensional smoothings without distorting the kernel shape.\n\n" + "The -fix-zeros option causes the smoothing to not use an input value if it is zero, but still write a smoothed value to the voxel. " + "This is useful for zeros that indicate lack of information, preventing them from pulling down the intensity of nearby voxels, while " + "giving the zero an extrapolated value." ); return ret; } void AlgorithmVolumeSmoothing::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { VolumeFile* myVol = myParams->getVolume(1); float myKernel = (float)myParams->getDouble(2); VolumeFile* myOutVol = myParams->getOutputVolume(3); OptionalParameter* roiVolOpt = myParams->getOptionalParameter(4); VolumeFile* roiVol = NULL; if (roiVolOpt->m_present) { roiVol = roiVolOpt->getVolume(1); } OptionalParameter* fixZerosOpt = myParams->getOptionalParameter(5); bool fixZeros = fixZerosOpt->m_present; OptionalParameter* subvolSelect = myParams->getOptionalParameter(6); int subvolNum = -1; if (subvolSelect->m_present) { subvolNum = (int)myVol->getMapIndexFromNameOrNumber(subvolSelect->getString(1)); if (subvolNum < 0) { throw AlgorithmException("invalid subvolume specified"); } } AlgorithmVolumeSmoothing(myProgObj, myVol, myKernel, myOutVol, roiVol, fixZeros, subvolNum); } AlgorithmVolumeSmoothing::AlgorithmVolumeSmoothing(ProgressObject* myProgObj, const VolumeFile* inVol, const float& kernel, VolumeFile* outVol, const VolumeFile* roiVol, const bool& fixZeros, const int& subvol) : AbstractAlgorithm(myProgObj) { CaretAssert(inVol != NULL); CaretAssert(outVol != NULL); LevelProgress myProgress(myProgObj); if (roiVol != NULL && !inVol->matchesVolumeSpace(roiVol)) { throw AlgorithmException("volume roi space does not match input volume"); } vector myDims; inVol->getDimensions(myDims); if (subvol < -1 || subvol >= myDims[3]) { throw AlgorithmException("invalid subvolume specified"); } if (kernel <= 0.0f) { throw AlgorithmException("kernel too small"); } CaretArray scratchFrame(myDims[0] * myDims[1] * myDims[2]);//it could be faster to preinitialize with zeros, then generate a usable voxels list if there is a small ROI... float kernBox = kernel * 3.0f; vector > volSpace = inVol->getSform(); Vector3D ivec, jvec, kvec, origin, ijorth, jkorth, kiorth; ivec[0] = volSpace[0][0]; jvec[0] = volSpace[0][1]; kvec[0] = volSpace[0][2]; origin[0] = volSpace[0][3]; ivec[1] = volSpace[1][0]; jvec[1] = volSpace[1][1]; kvec[1] = volSpace[1][2]; origin[1] = volSpace[1][3]; ivec[2] = volSpace[2][0]; jvec[2] = volSpace[2][1]; kvec[2] = volSpace[2][2]; origin[2] = volSpace[2][3]; const float ORTH_TOLERANCE = 0.001f;//tolerate this much deviation from orthogonal (dot product divided by product of lengths) to use orthogonal assumptions to smooth if (abs(ivec.dot(jvec.normal())) / ivec.length() < ORTH_TOLERANCE && abs(jvec.dot(kvec.normal())) / jvec.length() < ORTH_TOLERANCE && abs(kvec.dot(ivec.normal())) / kvec.length() < ORTH_TOLERANCE) {//if our axes are orthogonal, optimize by doing three 1-dimensional smoothings for O(voxels * (ki + kj + kk)) instead of O(voxels * (ki * kj * kk)) CaretArray scratchFrame2(myDims[0] * myDims[1] * myDims[2]), scratchWeights(myDims[0] * myDims[1] * myDims[2]), scratchWeights2(myDims[0] * myDims[1] * myDims[2]), scratchFrame3; if (roiVol != NULL) { scratchFrame3 = CaretArray (myDims[0] * myDims[1] * myDims[2]); } float ispace = ivec.length(), jspace = jvec.length(), kspace = kvec.length(); int irange = (int)floor(kernBox / ispace); int jrange = (int)floor(kernBox / jspace); int krange = (int)floor(kernBox / kspace); if (irange < 1) irange = 1;//don't underflow if (jrange < 1) jrange = 1; if (krange < 1) krange = 1; int isize = irange * 2 + 1;//and construct a precomputed kernel in the box int jsize = jrange * 2 + 1; int ksize = krange * 2 + 1; CaretArray iweights(isize), jweights(jsize), kweights(ksize); for (int i = 0; i < isize; ++i) { float tempf = ispace * (i - irange) / kernel; iweights[i] = exp(-tempf * tempf / 2.0f); } for (int j = 0; j < jsize; ++j) { float tempf = jspace * (j - jrange) / kernel; jweights[j] = exp(-tempf * tempf / 2.0f); } for (int k = 0; k < ksize; ++k) { float tempf = kspace * (k - krange) / kernel; kweights[k] = exp(-tempf * tempf / 2.0f); } if (subvol == -1) { vector origDims = inVol->getOriginalDimensions(); outVol->reinitialize(origDims, volSpace, myDims[4]); vector lists[3]; for (int s = 0; s < myDims[3]; ++s) { outVol->setMapName(s, inVol->getMapName(s) + ", smooth " + AString::number(kernel)); for (int c = 0; c < myDims[4]; ++c) { const float* inFrame = inVol->getFrame(s, c); if (roiVol == NULL) { smoothFrame(inFrame, myDims, scratchFrame, scratchFrame2, scratchWeights, scratchWeights2, inVol, iweights, jweights, kweights, irange, jrange, krange, fixZeros); } else { smoothFrameROI(inFrame, myDims, scratchFrame, scratchFrame2, scratchFrame3, scratchWeights, scratchWeights2, lists, inVol, roiVol, iweights, jweights, kweights, irange, jrange, krange, fixZeros); } outVol->setFrame(scratchFrame, s, c); } } } else { vector origDims = inVol->getOriginalDimensions(), newDims; newDims.resize(3); newDims[0] = origDims[0]; newDims[1] = origDims[1]; newDims[2] = origDims[2]; outVol->reinitialize(newDims, volSpace, myDims[4]); vector lists[3]; outVol->setMapName(0, inVol->getMapName(subvol) + ", smooth " + AString::number(kernel)); for (int c = 0; c < myDims[4]; ++c) { const float* inFrame = inVol->getFrame(subvol, c); if (roiVol == NULL) { smoothFrame(inFrame, myDims, scratchFrame, scratchFrame2, scratchWeights, scratchWeights2, inVol, iweights, jweights, kweights, irange, jrange, krange, fixZeros); } else { smoothFrameROI(inFrame, myDims, scratchFrame, scratchFrame2, scratchFrame3, scratchWeights, scratchWeights2, lists, inVol, roiVol, iweights, jweights, kweights, irange, jrange, krange, fixZeros); } outVol->setFrame(scratchFrame, 0, c); } } } else { if (!haveWarned) { CaretLogWarning("input volume is not orthogonal, smoothing will take longer"); haveWarned = true; } ijorth = ivec.cross(jvec).normal();//find the bounding box that encloses a sphere of radius kernBox jkorth = jvec.cross(kvec).normal(); kiorth = kvec.cross(ivec).normal(); int irange = (int)floor(abs(kernBox / ivec.dot(jkorth))); int jrange = (int)floor(abs(kernBox / jvec.dot(kiorth))); int krange = (int)floor(abs(kernBox / kvec.dot(ijorth))); if (irange < 1) irange = 1;//don't underflow if (jrange < 1) jrange = 1; if (krange < 1) krange = 1; int isize = irange * 2 + 1;//and construct a precomputed kernel in the box int jsize = jrange * 2 + 1; int ksize = krange * 2 + 1; CaretArray weights(ksize);//so I don't need to explicitly delete[] if I throw CaretArray weights2(ksize * jsize);//construct flat arrays and index them into 3D CaretArray weights3(ksize * jsize * isize);//index i comes last because that is linear for volume frames Vector3D kscratch, jscratch, iscratch; for (int k = 0; k < ksize; ++k) { kscratch = kvec * (k - krange); weights[k] = weights2 + k * jsize; for (int j = 0; j < jsize; ++j) { jscratch = kscratch + jvec * (j - jrange); weights[k][j] = weights3 + ((k * jsize) + j) * isize; for (int i = 0; i < isize; ++i) { iscratch = jscratch + ivec * (i - irange); float tempf = iscratch.length(); if (tempf > kernBox) { weights[k][j][i] = 0.0f;//test for zero to avoid some multiplies/adds, cheaper or cleaner than checking bounds on indexes from an index list } else { weights[k][j][i] = exp(-tempf * tempf / kernel / kernel / 2.0f);//optimization here isn't critical } } } } if (subvol == -1) { vector origDims = inVol->getOriginalDimensions(); outVol->reinitialize(origDims, volSpace, myDims[4]); for (int s = 0; s < myDims[3]; ++s) { outVol->setMapName(s, inVol->getMapName(s) + ", smooth " + AString::number(kernel)); for (int c = 0; c < myDims[4]; ++c) { const float* inFrame = inVol->getFrame(s, c); smoothFrameNonOrth(inFrame, myDims, scratchFrame, inVol, roiVol, weights, irange, jrange, krange, fixZeros); outVol->setFrame(scratchFrame, s, c); } } } else { vector origDims = inVol->getOriginalDimensions(), newDims; newDims.resize(3); newDims[0] = origDims[0]; newDims[1] = origDims[1]; newDims[2] = origDims[2]; outVol->reinitialize(newDims, volSpace, myDims[4]); outVol->setMapName(0, inVol->getMapName(subvol) + ", smooth " + AString::number(kernel)); for (int c = 0; c < myDims[4]; ++c) { const float* inFrame = inVol->getFrame(subvol, c); smoothFrameNonOrth(inFrame, myDims, scratchFrame, inVol, roiVol, weights, irange, jrange, krange, fixZeros); outVol->setFrame(scratchFrame, 0, c); } } } } void AlgorithmVolumeSmoothing::smoothFrame(const float* inFrame, vector myDims, CaretArray scratchFrame, CaretArray scratchFrame2, CaretArray scratchWeights, CaretArray scratchWeights2, const VolumeFile* inVol, CaretArray iweights, CaretArray jweights, CaretArray kweights, int irange, int jrange, int krange, const bool& fixZeros) {//this function should ONLY get invoked when the volume is orthogonal (axes are perpendicular, not necessarily aligned with x, y, z, and not necessarily equal spacing) #pragma omp CARET_PARFOR schedule(dynamic) for (int k = 0; k < myDims[2]; ++k)//smooth along i axis { for (int j = 0; j < myDims[1]; ++j) { for (int i = 0; i < myDims[0]; ++i) { int imin = i - irange, imax = i + irange + 1;//one-after array size convention if (imin < 0) imin = 0; if (imax > myDims[0]) imax = myDims[0]; float sum = 0.0f, weightsum = 0.0f; int64_t baseInd = inVol->getIndex(0, j, k, 0);//extra 0 on a default parameter is to prevent int->pointer vs int->int64 conversion ambiguity int64_t curInd = baseInd + i; for (int ikern = imin; ikern < imax; ++ikern) { int64_t thisIndex = baseInd + ikern; if ((!fixZeros || inFrame[thisIndex] != 0.0f)) { float weight = iweights[ikern - i + irange]; weightsum += weight; sum += weight * inFrame[thisIndex]; } } scratchWeights[curInd] = weightsum; scratchFrame[curInd] = sum;//don't divide yet, we will divide later after we gather the weighted sums of the weighted sums of the weight sums (yes, that repetition is right) } } } #pragma omp CARET_PARFOR schedule(dynamic) for (int k = 0; k < myDims[2]; ++k)//now j { for (int i = 0; i < myDims[0]; ++i) { for (int j = 0; j < myDims[1]; ++j)//step along the dimension being smoothed last for best cache coherence { int jmin = j - jrange, jmax = j + jrange + 1;//one-after array size convention if (jmin < 0) jmin = 0; if (jmax > myDims[1]) jmax = myDims[1]; float sum = 0.0f, weightsum = 0.0f; int64_t baseInd = inVol->getIndex(i, 0, k); int64_t curInd = baseInd + j * myDims[0]; for (int jkern = jmin; jkern < jmax; ++jkern) { int64_t thisIndex = baseInd + jkern * myDims[0]; float weight = jweights[jkern - j + jrange]; weightsum += weight * scratchWeights[thisIndex]; sum += weight * scratchFrame[thisIndex]; } scratchWeights2[curInd] = weightsum; scratchFrame2[curInd] = sum;//we now have the weighted sum of the weight sums } } } #pragma omp CARET_PARFOR schedule(dynamic) for (int j = 0; j < myDims[1]; ++j)//and finally k { for (int i = 0; i < myDims[0]; ++i) { for (int k = 0; k < myDims[2]; ++k)//ditto { int64_t baseInd = inVol->getIndex(i, j, 0); int64_t curInd = baseInd + k * myDims[0] * myDims[1]; int kmin = k - krange, kmax = k + krange + 1;//one-after array size convention if (kmin < 0) kmin = 0; if (kmax > myDims[2]) kmax = myDims[2]; float sum = 0.0f, weightsum = 0.0f; for (int kkern = kmin; kkern < kmax; ++kkern) { int64_t thisIndex = baseInd + kkern * myDims[0] * myDims[1]; float weight = kweights[kkern - k + krange]; weightsum += weight * scratchWeights2[thisIndex]; sum += weight * scratchFrame2[thisIndex]; } if (weightsum != 0.0f) { scratchFrame[curInd] = sum / weightsum;//NOW we can divide } else { scratchFrame[curInd] = 0.0f; } } } } } void AlgorithmVolumeSmoothing::smoothFrameROI(const float* inFrame, vector myDims, CaretArray scratchFrame, CaretArray scratchFrame2, CaretArray scratchFrame3, CaretArray scratchWeights, CaretArray scratchWeights2, vector lists[3], const VolumeFile* inVol, const VolumeFile* roiVol, CaretArray iweights, CaretArray jweights, CaretArray kweights, int irange, int jrange, int krange, const bool& fixZeros) {//optimized for orthogonal, plus lists of voxels for ROI smoothing if (lists[0].size() == 0) {//this is our first time into this function, we must populate the lists const float* roiFrame = roiVol->getFrame(); CaretArray markROI(myDims[0] * myDims[1] * myDims[2], 0);//need a temporary array to sort out ROI zeros from -fix-zeros zeros #pragma omp CARET_PARFOR for (int k = 0; k < myDims[2]; ++k)//smooth along i axis { for (int j = 0; j < myDims[1]; ++j) { for (int i = 0; i < myDims[0]; ++i)//don't test whether intermediate voxel is insode ROI, or we lose some data { int imin = i - irange, imax = i + irange + 1;//one-after array size convention if (imin < 0) imin = 0; if (imax > myDims[0]) imax = myDims[0]; float sum = 0.0f, weightsum = 0.0f; int64_t baseInd = inVol->getIndex(0, j, k, 0); int64_t curInd = baseInd + i; bool used = false; for (int ikern = imin; ikern < imax; ++ikern) { int64_t thisIndex = baseInd + ikern; if (roiFrame[thisIndex] > 0.0f)//only test source and final voxels for being in ROI { if (!used)//keep a list of voxels whose i-kernel intersects the ROI { used = true; markROI[curInd] = 1; #pragma omp critical { lists[0].push_back(i); lists[0].push_back(j); lists[0].push_back(k); } } if (!fixZeros || inFrame[thisIndex] != 0.0f) { float weight = iweights[ikern - i + irange]; weightsum += weight; sum += weight * inFrame[thisIndex]; } } } scratchWeights[curInd] = weightsum; scratchFrame3[curInd] = sum;//don't divide yet, we will divide later after we gather the weighted sums of the weighted sums of the weight sums (yes, that repetition is right) } } } #pragma omp CARET_PARFOR for (int k = 0; k < myDims[2]; ++k)//now j { for (int i = 0; i < myDims[0]; ++i) { for (int j = 0; j < myDims[1]; ++j)//step along the dimension being smoothed last for best cache coherence { int jmin = j - jrange, jmax = j + jrange + 1;//one-after array size convention if (jmin < 0) jmin = 0; if (jmax > myDims[1]) jmax = myDims[1]; float sum = 0.0f, weightsum = 0.0f; int64_t baseInd = inVol->getIndex(i, 0, k); int64_t curInd = baseInd + j * myDims[0]; bool used = false; for (int jkern = jmin; jkern < jmax; ++jkern) { int64_t thisIndex = baseInd + jkern * myDims[0];//DO NOT test for this source voxel being outside ROI, or you will skip good data if ((markROI[thisIndex] & 1) == 1) {//skip voxels whose i-kernels don't touch the ROI, they will always have 0/0 if (!used) { used = true; markROI[curInd] |= 2;//bitwise so i can track all 3 lists separately in one array #pragma omp critical { lists[1].push_back(i); lists[1].push_back(j); lists[1].push_back(k); } } float weight = jweights[jkern - j + jrange]; weightsum += weight * scratchWeights[thisIndex]; sum += weight * scratchFrame3[thisIndex]; } } scratchWeights2[curInd] = weightsum; scratchFrame2[curInd] = sum;//we now have the weighted sum of the weight sums } } } #pragma omp CARET_PARFOR for (int j = 0; j < myDims[1]; ++j)//and finally k { for (int i = 0; i < myDims[0]; ++i) { for (int k = 0; k < myDims[2]; ++k)//ditto { int64_t baseInd = inVol->getIndex(i, j, 0); int64_t curInd = baseInd + k * myDims[0] * myDims[1]; if (roiFrame[curInd] > 0.0f) { #pragma omp critical { lists[2].push_back(i);//third list is a little different, since we don't output stuff outside the ROI, we can drop the voxels that "grew" from the ROI lists[2].push_back(j);//we do need to calculate those grown voxels, though, since we use some of them within the k-kernel lists[2].push_back(k); } int kmin = k - krange, kmax = k + krange + 1;//one-after array size convention if (kmin < 0) kmin = 0; if (kmax > myDims[2]) kmax = myDims[2]; float sum = 0.0f, weightsum = 0.0f; for (int kkern = kmin; kkern < kmax; ++kkern) { int64_t thisIndex = baseInd + kkern * myDims[0] * myDims[1];//ditto float weight = kweights[kkern - k + krange]; weightsum += weight * scratchWeights2[thisIndex]; sum += weight * scratchFrame2[thisIndex]; } if (weightsum != 0.0f) { scratchFrame[curInd] = sum / weightsum;//NOW we can divide } else { scratchFrame[curInd] = 0.0f; } } else { scratchFrame[curInd] = 0.0f; } } } } if (lists[0].size() == 0) { lists[0].push_back(-1);//to keep it from scanning the ROI again when the ROI has no voxels, slightly hacky } } else {//lists already made, use them const float* roiFrame = roiVol->getFrame(); int64_t ibasesize = (int64_t)lists[0].size(); if (ibasesize < 3) return;//handle the case of empty ROI here (ibasesize will be 1) int64_t jbasesize = (int64_t)lists[1].size(); int64_t kbasesize = (int64_t)lists[2].size(); #pragma omp CARET_PARFOR schedule(dynamic) for (int ibase = 0; ibase < ibasesize; ibase += 3) { int i = lists[0][ibase]; int j = lists[0][ibase + 1]; int k = lists[0][ibase + 2]; int imin = i - irange, imax = i + irange + 1;//one-after array size convention if (imin < 0) imin = 0; if (imax > myDims[0]) imax = myDims[0]; float sum = 0.0f, weightsum = 0.0f; int64_t baseInd = inVol->getIndex(0, j, k, 0); int64_t curInd = baseInd + i; for (int ikern = imin; ikern < imax; ++ikern) { int64_t thisIndex = baseInd + ikern; if (roiFrame[thisIndex] > 0.0f && (!fixZeros || inFrame[thisIndex] != 0.0f)) { float weight = iweights[ikern - i + irange]; weightsum += weight; sum += weight * inFrame[thisIndex]; } } scratchWeights[curInd] = weightsum; scratchFrame3[curInd] = sum;//don't divide yet, we will divide later after we gather the weighted sums of the weighted sums of the weight sums (yes, that repetition is right) } #pragma omp CARET_PARFOR schedule(dynamic) for (int jbase = 0; jbase < jbasesize; jbase += 3) { int i = lists[1][jbase]; int j = lists[1][jbase + 1]; int k = lists[1][jbase + 2]; int jmin = j - jrange, jmax = j + jrange + 1;//one-after array size convention if (jmin < 0) jmin = 0; if (jmax > myDims[1]) jmax = myDims[1]; float sum = 0.0f, weightsum = 0.0f; int64_t baseInd = inVol->getIndex(i, 0, k); int64_t curInd = baseInd + j * myDims[0]; for (int jkern = jmin; jkern < jmax; ++jkern) { int64_t thisIndex = baseInd + jkern * myDims[0];//DO NOT test for this source voxel having zero data, or it will skip good data float weight = jweights[jkern - j + jrange]; weightsum += weight * scratchWeights[thisIndex]; sum += weight * scratchFrame3[thisIndex]; } scratchWeights2[curInd] = weightsum; scratchFrame2[curInd] = sum;//we now have the weighted sum of the weight sums } #pragma omp CARET_PARFOR schedule(dynamic) for (int kbase = 0; kbase < kbasesize; kbase += 3) { int i = lists[2][kbase]; int j = lists[2][kbase + 1]; int k = lists[2][kbase + 2]; int64_t baseInd = inVol->getIndex(i, j, 0); int64_t curInd = baseInd + k * myDims[0] * myDims[1]; int kmin = k - krange, kmax = k + krange + 1;//one-after array size convention if (kmin < 0) kmin = 0; if (kmax > myDims[2]) kmax = myDims[2]; float sum = 0.0f, weightsum = 0.0f; for (int kkern = kmin; kkern < kmax; ++kkern) { int64_t thisIndex = baseInd + kkern * myDims[0] * myDims[1];//ditto float weight = kweights[kkern - k + krange]; weightsum += weight * scratchWeights2[thisIndex]; sum += weight * scratchFrame2[thisIndex]; } if (weightsum != 0.0f) { scratchFrame[curInd] = sum / weightsum;//NOW we can divide } else { scratchFrame[curInd] = 0.0f; } }//the frame should be zeroed outside the ROI already due to the first time through } } void AlgorithmVolumeSmoothing::smoothFrameNonOrth(const float* inFrame, const vector& myDims, CaretArray& scratchFrame, const VolumeFile* inVol, const VolumeFile* roiVol, const CaretArray& weights, const int& irange, const int& jrange, const int& krange, const bool& fixZeros) { const float* roiFrame = NULL; if (roiVol != NULL) { roiFrame = roiVol->getFrame(); } #pragma omp CARET_PARFOR schedule(dynamic) for (int k = 0; k < myDims[2]; ++k) { for (int j = 0; j < myDims[1]; ++j) { for (int i = 0; i < myDims[0]; ++i) { if (roiVol == NULL || roiVol->getValue(i, j, k) > 0.0f) { int imin = i - irange, imax = i + irange + 1;//one-after array size convention if (imin < 0) imin = 0; if (imax > myDims[0]) imax = myDims[0]; int jmin = j - jrange, jmax = j + jrange + 1; if (jmin < 0) jmin = 0; if (jmax > myDims[1]) jmax = myDims[1]; int kmin = k - krange, kmax = k + krange + 1; if (kmin < 0) kmin = 0; if (kmax > myDims[2]) kmax = myDims[2]; float sum = 0.0f, weightsum = 0.0f; for (int kkern = kmin; kkern < kmax; ++kkern) { int64_t kindpart = kkern * myDims[1]; int kkernpart = kkern - k + krange; for (int jkern = jmin; jkern < jmax; ++jkern) { int64_t jindpart = (kindpart + jkern) * myDims[0]; int jkernpart = jkern - j + jrange; for (int ikern = imin; ikern < imax; ++ikern) { int64_t thisIndex = jindpart + ikern;//somewhat optimized index computation, could remove some integer multiplies, but there aren't that many float weight = weights[kkernpart][jkernpart][ikern - i + irange]; if (weight != 0.0f && (roiVol == NULL || roiFrame[thisIndex] > 0.0f) && (!fixZeros || inFrame[thisIndex] != 0.0f)) { weightsum += weight; sum += weight * inFrame[thisIndex]; } } } } if (weightsum != 0.0f) { scratchFrame[inVol->getIndex(i, j, k)] = sum / weightsum; } else { scratchFrame[inVol->getIndex(i, j, k)] = 0.0f; } } else { scratchFrame[inVol->getIndex(i, j, k)] = 0.0f; } } } } } float AlgorithmVolumeSmoothing::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmVolumeSmoothing::getSubAlgorithmWeight() { return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmVolumeSmoothing.h000066400000000000000000000061611255417355300235710ustar00rootroot00000000000000#ifndef __ALGORITHM_VOLUME_SMOOTHING_H__ #define __ALGORITHM_VOLUME_SMOOTHING_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmVolumeSmoothing : public AbstractAlgorithm { AlgorithmVolumeSmoothing(); static bool haveWarned; protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); void smoothFrame(const float* inFrame, std::vector myDims, CaretArray scratchFrame, CaretArray scratchFrame2, CaretArray scratchWeights, CaretArray scratchWeights2, const VolumeFile* inVol, CaretArray iweights, CaretArray jweights, CaretArray kweights, int irange, int jrange, int krange, const bool& fixZeros); void smoothFrameROI(const float* inFrame, std::vector myDims, CaretArray scratchFrame, CaretArray scratchFrame2, CaretArray scratchFrame3, CaretArray scratchWeights, CaretArray scratchWeights2, std::vector lists[3], const VolumeFile* inVol, const VolumeFile* roiVol, CaretArray iweights, CaretArray jweights, CaretArray kweights, int irange, int jrange, int krange, const bool& fixZeros); void smoothFrameNonOrth(const float* inFrame, const std::vector& myDims, CaretArray& scratchFrame, const VolumeFile* inVol, const VolumeFile* roiVol, const CaretArray& weights, const int& irange, const int& jrange, const int& krange, const bool& fixZeros); public: AlgorithmVolumeSmoothing(ProgressObject* myProgObj, const VolumeFile* inVol, const float& kernel, VolumeFile* outVol, const VolumeFile* roiVol = NULL, const bool& fixZeros = false, const int& subvol = -1); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmVolumeSmoothing; } #endif //__ALGORITHM_VOLUME_SMOOTHING_H__ workbench-1.1.1/src/Algorithms/AlgorithmVolumeTFCE.cxx000066400000000000000000000435351255417355300227240ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmVolumeTFCE.h" #include "AlgorithmException.h" #include "AlgorithmVolumeSmoothing.h" #include "CaretAssert.h" #include "CaretHeap.h" #include "CaretOMP.h" #include "VolumeFile.h" #include "VoxelIJK.h" #include #include #include using namespace caret; using namespace std; AString AlgorithmVolumeTFCE::getCommandSwitch() { return "-volume-tfce"; } AString AlgorithmVolumeTFCE::getShortDescription() { return "DO TFCE ON A VOLUME FILE"; } OperationParameters* AlgorithmVolumeTFCE::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addVolumeParameter(1, "volume-in", "the volume to run TFCE on"); ret->addVolumeOutputParameter(2, "volume-out", "the output volume"); OptionalParameter* presmoothOpt = ret->createOptionalParameter(3, "-presmooth", "smooth the volume before running TFCE"); presmoothOpt->addDoubleParameter(1, "kernel", "the sigma for the gaussian smoothing kernel, in mm"); OptionalParameter* roiOpt = ret->createOptionalParameter(4, "-roi", "select a region of interest to run TFCE on"); roiOpt->addVolumeParameter(1, "roi-volume", "the area to run TFCE on, as a volume"); OptionalParameter* paramsOpt = ret->createOptionalParameter(5, "-parameters", "set parameters for TFCE integral"); paramsOpt->addDoubleParameter(1, "E", "exponent for cluster volume (default 0.5)"); paramsOpt->addDoubleParameter(2, "H", "exponent for threshold value (default 2.0)"); OptionalParameter* subvolSelect = ret->createOptionalParameter(6, "-subvolume", "select a single subvolume"); subvolSelect->addStringParameter(1, "subvolume", "the subvolume number or name"); ret->setHelpText( AString("Threshold-free cluster enhancement is a method to increase the relative value of regions that would form clusters in a standard thresholding test. ") + "This is accomplished by evaluating the integral of:\n\n" + "e(h, p)^E * h^H * dh\n\n" + "at each vertex p, where h ranges from 0 to the maximum value in the data, and e(h, p) is the extent of the cluster containing vertex p at threshold h. " + "Negative values are similarly enhanced by negating the data, running the same process, and negating the result.\n\n" + "This method is explained in: Smith SM, Nichols TE., \"Threshold-free cluster enhancement: addressing problems of smoothing, threshold dependence and localisation in cluster inference.\" Neuroimage. 2009 Jan 1;44(1):83-98. PMID: 18501637" ); return ret; } void AlgorithmVolumeTFCE::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { VolumeFile* myVol = myParams->getVolume(1); VolumeFile* myVolOut = myParams->getOutputVolume(2); float presmooth = 0.0f; OptionalParameter* presmoothOpt = myParams->getOptionalParameter(3); if (presmoothOpt->m_present) { presmooth = (float)presmoothOpt->getDouble(1); if (presmooth <= 0.0f) throw AlgorithmException("presmooth kernel size must be positive"); } VolumeFile* myRoi = NULL; OptionalParameter* roiOpt = myParams->getOptionalParameter(4); if (roiOpt->m_present) { myRoi = roiOpt->getVolume(1); } float param_e = 0.5f, param_h = 2.0; OptionalParameter* paramsOpt = myParams->getOptionalParameter(5); if (paramsOpt->m_present) { param_e = (float)paramsOpt->getDouble(1); param_h = (float)paramsOpt->getDouble(2); } OptionalParameter* subvolSelect = myParams->getOptionalParameter(6); int64_t subvolNum = -1; if (subvolSelect->m_present) {//set up to use the single column subvolNum = myVol->getMapIndexFromNameOrNumber(subvolSelect->getString(1)); if (subvolNum < 0) { throw AlgorithmException("invalid subvolume specified"); } } AlgorithmVolumeTFCE(myProgObj, myVol, myVolOut, presmooth, myRoi, param_e, param_h, subvolNum); } AlgorithmVolumeTFCE::AlgorithmVolumeTFCE(ProgressObject* myProgObj, const VolumeFile* myVol, VolumeFile* myVolOut, const float& presmooth, const VolumeFile* myRoi, const float& param_e, const float& param_h, const int64_t& subvolNum) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); if (myRoi != NULL && !myVol->getVolumeSpace().matches(myRoi->getVolumeSpace())) throw AlgorithmException("roi volume has different volume space than input"); if (subvolNum < -1 || subvolNum >= myVol->getNumberOfMaps()) throw AlgorithmException("invalid subvolume specified"); vector dims = myVol->getDimensions(); const float* roiFrame = NULL; if (myRoi != NULL) roiFrame = myRoi->getFrame(); if (subvolNum == -1) { myVolOut->reinitialize(myVol->getOriginalDimensions(), myVol->getSform(), dims[4]); const VolumeFile* toUse = myVol; VolumeFile smoothed; if (presmooth > 0.0f) { AlgorithmVolumeSmoothing(NULL, myVol, presmooth, &smoothed, myRoi); toUse = &smoothed; } #pragma omp CARET_PAR { vector outframe(dims[0] * dims[1] * dims[2]); #pragma omp CARET_FOR for (int64_t b = 0; b < dims[3]; ++b) { for (int64_t c = 0; c < dims[4]; ++c) { processFrame(toUse, b, c, outframe.data(), roiFrame, param_e, param_h); myVolOut->setFrame(outframe.data(), b, c); } } } } else { vector outDims = dims; outDims.resize(3); myVolOut->reinitialize(outDims, myVol->getSform(), dims[4]); const VolumeFile* toUse = myVol; int useFrame = subvolNum; VolumeFile smoothed; if (presmooth > 0.0f) { AlgorithmVolumeSmoothing(NULL, myVol, presmooth, &smoothed, myRoi, false, subvolNum); toUse = &smoothed; useFrame = 0; } vector outframe(dims[0] * dims[1] * dims[2]); for (int64_t c = 0; c < dims[4]; ++c) { processFrame(toUse, useFrame, c, outframe.data(), roiFrame, param_e, param_h); myVolOut->setFrame(outframe.data(), 0, c); } } } void AlgorithmVolumeTFCE::processFrame(const VolumeFile* inVol, const int64_t& b, const int64_t& c, float* outData, const float* roiData, const float& param_e, const float& param_h) { vector dims = inVol->getDimensions(); int64_t frameSize = dims[0] * dims[1] * dims[2]; vector accum(frameSize, 0.0); tfce(inVol, b, c, accum.data(), roiData, param_e, param_h, false);//don't negate - positives tfce(inVol, b, c, accum.data(), roiData, param_e, param_h, true);//negate - negatives - NOTE: output is still positive!!! const float* inData = inVol->getFrame(b, c); for (int64_t i = 0; i < frameSize; ++i) { if (inData[i] > 0.0f)//negate the results from negative inputs { outData[i] = accum[i]; } else {//the areas outside the roi will have zeros, so we don't have to worry about them outData[i] = -accum[i]; } } } namespace {//hidden namespace just to make sure things don't collide struct Cluster { double accumVal, totalVolume; vector members; float lastVal; bool first; Cluster() { first = true; accumVal = 0.0; totalVolume = 0.0; } void addMember(const VoxelIJK& voxel, const float& val, const float& voxel_volume, const float& param_e, const float& param_h) { update(val, param_e, param_h); members.push_back(voxel); totalVolume += voxel_volume; } void update(const float& bottomVal, const float& param_e, const float& param_h) { if (first) { lastVal = bottomVal; first = false; } else { if (bottomVal != lastVal)//skip computing if there is no difference { CaretAssert(bottomVal < lastVal); double integrated_h = param_h + 1.0f;//integral(x^h) = (x^(h + 1))/(h + 1) + C double newSlice = pow(totalVolume, (double)param_e) * (pow((double)lastVal, integrated_h) - pow((double)bottomVal, integrated_h)) / integrated_h; accumVal += newSlice; lastVal = bottomVal;//computing in double precision, with float for inputs, puts the smallest difference between values far greater than the instability of the computation } } } }; int64_t allocCluster(vector& clusterList, set& deadClusters) { if (deadClusters.empty()) { clusterList.push_back(Cluster()); return (int64_t)(clusterList.size() - 1); } else { set::iterator iter = deadClusters.begin(); int64_t ret = *iter; deadClusters.erase(iter); clusterList[ret] = Cluster();//reinitialize return ret; } } } void AlgorithmVolumeTFCE::tfce(const VolumeFile* inVol, const int64_t& b, const int64_t& c, double* accumData, const float* roiData, const float& param_e, const float& param_h, const bool& negate) { vector dims = inVol->getDimensions(); Vector3D ivec, jvec, kvec, origin;//compute the volume of a voxel so different resolutions have comparable values - as if it matters, but hey inVol->getVolumeSpace().getSpacingVectors(ivec, jvec, kvec, origin);//who knows, maybe we'll have distortion correction in volume someday float voxelVolume = abs(ivec.dot(jvec.cross(kvec))); const int64_t frameSize = dims[0] * dims[1] * dims[2]; const float* frameData = inVol->getFrame(b, c); vector membership(frameSize, -1);//use int64_t just in case we get an absurd number of clusters vector clusterList; set deadClusters;//to allow reallocation without changing indices CaretSimpleMaxHeap voxelHeap; for (int64_t i = 0; i < dims[0]; ++i) { for (int64_t j = 0; j < dims[1]; ++j) { for (int64_t k = 0; k < dims[2]; ++k) { int64_t index = inVol->getIndex(i, j, k); if ((roiData == NULL || roiData[index] > 0.0f)) { if (negate) { if (frameData[index] < 0.0f) { voxelHeap.push(VoxelIJK(i, j, k), -frameData[index]); } } else { if (frameData[index] > 0.0f) { voxelHeap.push(VoxelIJK(i, j, k), frameData[index]); } } } } } } const int STENCIL_SIZE = 18; int64_t stencil[STENCIL_SIZE] = { 0, 0, -1, 0, -1, 0, -1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1 }; while (!voxelHeap.isEmpty()) { float value; VoxelIJK voxel = voxelHeap.pop(&value); int64_t voxelIndex = inVol->getIndex(voxel.m_ijk); set touchingClusters; for (int i = 0; i < STENCIL_SIZE; i += 3) { VoxelIJK neighVoxel(voxel.m_ijk[0] + stencil[i], voxel.m_ijk[1] + stencil[i + 1], voxel.m_ijk[2] + stencil[i + 2]); if (inVol->indexValid(neighVoxel.m_ijk)) { int64_t neighIndex = inVol->getIndex(neighVoxel.m_ijk); if (membership[neighIndex] != -1) { touchingClusters.insert(membership[neighIndex]); } } } int numTouching = (int)touchingClusters.size(); switch (numTouching) { case 0://make new cluster { int64_t newCluster = allocCluster(clusterList, deadClusters); clusterList[newCluster].addMember(voxel, value, voxelVolume, param_e, param_h); membership[voxelIndex] = newCluster; break; } case 1://add to cluster { int64_t whichCluster = *(touchingClusters.begin()); clusterList[whichCluster].addMember(voxel, value, voxelVolume, param_e, param_h); membership[voxelIndex] = whichCluster; accumData[voxelIndex] -= clusterList[whichCluster].accumVal;//the accum value is the current amount less than the peak value that the edge of the cluster has (this node is on the edge) break;//so, when the cluster merges or reaches 0, we add the accum value to every member and zero the accum value of the merged cluster, and we get the correct value in the end (with far fewer flops than integrating everywhere) } default://merge all touching clusters { int64_t mergedIndex = -1, biggestSize = 0;//find the biggest cluster (in number of members) and use as merged cluster, for optimization purposes for (set::iterator iter = touchingClusters.begin(); iter != touchingClusters.end(); ++iter) { if ((int64_t)clusterList[*iter].members.size() > biggestSize) { mergedIndex = *iter; biggestSize = (int64_t)clusterList[*iter].members.size(); } } CaretAssertVectorIndex(clusterList, mergedIndex); Cluster& mergedCluster = clusterList[mergedIndex]; mergedCluster.update(value, param_e, param_h);//recalculate to align cluster bottoms for (set::iterator iter = touchingClusters.begin(); iter != touchingClusters.end(); ++iter) { if (*iter != mergedIndex)//if we are the largest cluster, don't modify the per-voxel accum for members, so merges between small and large clusters are cheap { Cluster& thisCluster = clusterList[*iter]; thisCluster.update(value, param_e, param_h); int64_t numMembers = (int64_t)thisCluster.members.size(); double correctionVal = thisCluster.accumVal - mergedCluster.accumVal;//fix the accum values in the side cluster so we can add the merged cluster's accum to everything at the end for (int64_t j = 0; j < numMembers; ++j)//add the correction value to every member so that we have the current integrated values correct { int64_t memberIndex = inVol->getIndex(thisCluster.members[j].m_ijk); accumData[memberIndex] += correctionVal;//apply the correction membership[memberIndex] = mergedIndex;//and update membership } mergedCluster.members.insert(mergedCluster.members.end(), thisCluster.members.begin(), thisCluster.members.end());//copy all members mergedCluster.totalVolume += thisCluster.totalVolume; deadClusters.insert(*iter);//kill it vector().swap(clusterList[*iter].members);//also try to deallocate member list } } mergedCluster.addMember(voxel, value, voxelVolume, param_e, param_h);//will not trigger recomputation, we already recomputed at this value accumData[voxelIndex] -= mergedCluster.accumVal;//the voxel they merge on must not get the peak value of the cluster, obviously, so again, record its difference from peak membership[voxelIndex] = mergedIndex; break;//NOTE: do not reset the accum value of the merged cluster, we specifically avoided modifying the per-voxel accum for its members, so the cluster accum is still in play } } } int listSize = (int)clusterList.size();//final cleanup of accum values for (int i = 0; i < listSize; ++i) { if (deadClusters.find(i) != deadClusters.end()) continue;//ignore clusters that don't exist Cluster& thisCluster = clusterList[i]; thisCluster.update(0.0f, param_e, param_h);//update to include the to-zero slice int numMembers = (int)thisCluster.members.size(); for (int j = 0; j < numMembers; ++j) { accumData[inVol->getIndex(thisCluster.members[j].m_ijk)] += thisCluster.accumVal;//add the resulting slice to all members - their stored data contains the offset between the cluster peak and their corect value } } } float AlgorithmVolumeTFCE::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmVolumeTFCE::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmVolumeTFCE.h000066400000000000000000000042121255417355300223360ustar00rootroot00000000000000#ifndef __ALGORITHM_VOLUME_TFCE_H__ #define __ALGORITHM_VOLUME_TFCE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" namespace caret { class AlgorithmVolumeTFCE : public AbstractAlgorithm { AlgorithmVolumeTFCE(); void processFrame(const VolumeFile* inVol, const int64_t& b, const int64_t& c, float* outData, const float* roiData, const float& param_e, const float& param_h); void tfce(const VolumeFile* inVol, const int64_t& b, const int64_t& c, double* accumData, const float* roiData, const float& param_e, const float& param_h, const bool& negate); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmVolumeTFCE(ProgressObject* myProgObj, const VolumeFile* myVol, VolumeFile* myVolOut, const float& presmooth = 0.0f, const VolumeFile* myRoi = NULL, const float& param_e = 0.5f, const float& param_h = 2.0f, const int64_t& subvolNum = -1); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmVolumeTFCE; } #endif //__ALGORITHM_VOLUME_TFCE_H__ workbench-1.1.1/src/Algorithms/AlgorithmVolumeToSurfaceMapping.cxx000066400000000000000000000627551255417355300254170ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmVolumeToSurfaceMapping.h" #include "AlgorithmException.h" #include "CaretOMP.h" #include "FloatMatrix.h" #include "MathFunctions.h" #include "MetricFile.h" #include "SurfaceFile.h" #include "TopologyHelper.h" #include "Vector3D.h" #include "VolumeFile.h" #include using namespace caret; using namespace std; AString AlgorithmVolumeToSurfaceMapping::getCommandSwitch() { return "-volume-to-surface-mapping"; } AString AlgorithmVolumeToSurfaceMapping::getShortDescription() { return "MAP VOLUME TO SURFACE"; } OperationParameters* AlgorithmVolumeToSurfaceMapping::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addVolumeParameter(1, "volume", "the volume to map data from"); ret->addSurfaceParameter(2, "surface", "the surface to map the data onto"); ret->addMetricOutputParameter(3, "metric-out", "the output metric file"); ret->createOptionalParameter(4, "-trilinear", "use trilinear volume interpolation"); ret->createOptionalParameter(5, "-enclosing", "use value of the enclosing voxel"); ret->createOptionalParameter(8, "-cubic", "use cubic splines"); OptionalParameter* ribbonOpt = ret->createOptionalParameter(6, "-ribbon-constrained", "use ribbon constrained mapping algorithm"); ribbonOpt->addSurfaceParameter(1, "inner-surf", "the inner surface of the ribbon"); ribbonOpt->addSurfaceParameter(2, "outer-surf", "the outer surface of the ribbon"); OptionalParameter* roiVol = ribbonOpt->createOptionalParameter(3, "-volume-roi", "use a volume roi"); roiVol->addVolumeParameter(1, "roi-volume", "the volume file"); OptionalParameter* ribbonSubdiv = ribbonOpt->createOptionalParameter(4, "-voxel-subdiv", "voxel divisions while estimating voxel weights"); ribbonSubdiv->addIntegerParameter(1, "subdiv-num", "number of subdivisions, default 3"); OptionalParameter* ribbonWeights = ribbonOpt->createOptionalParameter(5, "-output-weights", "write the voxel weights for a vertex to a volume file"); ribbonWeights->addIntegerParameter(1, "vertex", "the vertex number to get the voxel weights for, 0-based"); ribbonWeights->addVolumeOutputParameter(2, "weights-out", "volume to write the weights to"); OptionalParameter* myelinStyleOpt = ret->createOptionalParameter(9, "-myelin-style", "use the method from myelin mapping"); myelinStyleOpt->addVolumeParameter(1, "ribbon-roi", "an roi volume of the cortical ribbon for this hemisphere"); myelinStyleOpt->addMetricParameter(2, "thickness", "a metric file of cortical thickness"); myelinStyleOpt->addDoubleParameter(3, "sigma", "guassian kernel in mm for weighting voxels within range"); OptionalParameter* subvolumeSelect = ret->createOptionalParameter(7, "-subvol-select", "select a single subvolume to map"); subvolumeSelect->addStringParameter(1, "subvol", "the subvolume number or name"); ret->setHelpText( AString("You must specify exactly one mapping method. Enclosing voxel uses the value from the voxel the vertex lies inside, while trilinear does a 3D ") + "linear interpolation based on the voxels immediately on each side of the vertex's position.\n\n" + "The ribbon mapping method constructs a polyhedron from the vertex's neighbors on each " + "surface, and estimates the amount of this polyhedron's volume that falls inside any nearby voxels, to use as the weights for sampling. " + "The volume ROI is useful to exclude partial volume effects of voxels the surfaces pass through, and will cause the mapping to ignore " + "voxels that don't have a positive value in the mask. The subdivision number specifies how it approximates the amount of the volume the polyhedron " + "intersects, by splitting each voxel into NxNxN pieces, and checking whether the center of each piece is inside the polyhedron. If you have very large " + "voxels, consider increasing this if you get zeros in your output.\n\n" + "The myelin style method uses part of the caret5 myelin mapping command to do the mapping: for each surface vertex, take all voxels closer than the thickness at the vertex " + "that are within the ribbon ROI, and less than half the thickness value away from the vertex along the direction of the surface normal, and apply a gaussian kernel " + "with the specified sigma to them to get the weights to use." ); return ret; } void AlgorithmVolumeToSurfaceMapping::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { VolumeFile* myVolume = myParams->getVolume(1); SurfaceFile* mySurface = myParams->getSurface(2); MetricFile* myMetricOut = myParams->getOutputMetric(3); OptionalParameter* trilinearOpt = myParams->getOptionalParameter(4); OptionalParameter* enclosingOpt = myParams->getOptionalParameter(5); OptionalParameter* cubicOpt = myParams->getOptionalParameter(8); OptionalParameter* ribbonOpt = myParams->getOptionalParameter(6); OptionalParameter* myelinStyleOpt = myParams->getOptionalParameter(9); int64_t mySubVol = -1; OptionalParameter* subvolumeSelect = myParams->getOptionalParameter(7); if (subvolumeSelect->m_present) { mySubVol = (int)myVolume->getMapIndexFromNameOrNumber(subvolumeSelect->getString(1)); if (mySubVol < 0) { throw AlgorithmException("invalid column specified"); } } bool haveMethod = false; Method myMethod = CUBIC;//this tracks which constructor to call VolumeFile::InterpType volInterpMethod = VolumeFile::CUBIC; if (trilinearOpt->m_present) { haveMethod = true; myMethod = TRILINEAR; volInterpMethod = VolumeFile::TRILINEAR; } if (enclosingOpt->m_present) { if (haveMethod) { throw AlgorithmException("more than one mapping method specified"); } haveMethod = true; myMethod = ENCLOSING_VOXEL; volInterpMethod = VolumeFile::ENCLOSING_VOXEL; } if (ribbonOpt->m_present) { if (haveMethod) { throw AlgorithmException("more than one mapping method specified"); } haveMethod = true; myMethod = RIBBON_CONSTRAINED; } if (cubicOpt->m_present) { if (haveMethod) { throw AlgorithmException("more than one mapping method specified"); } haveMethod = true; myMethod = CUBIC; volInterpMethod = VolumeFile::CUBIC; } if (myelinStyleOpt->m_present) { if (haveMethod) { throw AlgorithmException("more than one mapping method specified"); } haveMethod = true; myMethod = MYELIN_STYLE; } if (!haveMethod) { throw AlgorithmException("no mapping method specified"); } switch (myMethod) { case TRILINEAR: case ENCLOSING_VOXEL: case CUBIC: AlgorithmVolumeToSurfaceMapping(myProgObj, myVolume, mySurface, myMetricOut, volInterpMethod, mySubVol);//because we have separate constructors break; case RIBBON_CONSTRAINED: { SurfaceFile* innerSurf = ribbonOpt->getSurface(1); SurfaceFile* outerSurf = ribbonOpt->getSurface(2); OptionalParameter* roiVol = ribbonOpt->getOptionalParameter(3); VolumeFile* myRoiVol = NULL; if (roiVol->m_present) { myRoiVol = roiVol->getVolume(1); } int32_t subdivisions = 3; OptionalParameter* ribbonSubdiv = ribbonOpt->getOptionalParameter(4); if (ribbonSubdiv->m_present) { subdivisions = ribbonSubdiv->getInteger(1); if (subdivisions < 1) { throw AlgorithmException("invalid number of subdivisions specified"); } } int weightsOutVertex = -1; VolumeFile* weightsOut = NULL; OptionalParameter* ribbonWeights = ribbonOpt->getOptionalParameter(5); if (ribbonWeights->m_present) { weightsOutVertex = (int)ribbonWeights->getInteger(1); weightsOut = ribbonWeights->getOutputVolume(2); } AlgorithmVolumeToSurfaceMapping(myProgObj, myVolume, mySurface, myMetricOut, innerSurf, outerSurf, myRoiVol, subdivisions, mySubVol, weightsOutVertex, weightsOut); break; } case MYELIN_STYLE: { VolumeFile* roi = myelinStyleOpt->getVolume(1); MetricFile* thickness = myelinStyleOpt->getMetric(2); float sigma = (float)myelinStyleOpt->getDouble(3); AlgorithmVolumeToSurfaceMapping(myProgObj, myVolume, mySurface, myMetricOut, roi, thickness, sigma, mySubVol); break; } default: throw AlgorithmException("this method not yet implemented"); } } //interpolation mapping AlgorithmVolumeToSurfaceMapping::AlgorithmVolumeToSurfaceMapping(ProgressObject* myProgObj, const VolumeFile* myVolume, const SurfaceFile* mySurface, MetricFile* myMetricOut, const VolumeFile::InterpType& myMethod, const int64_t& mySubVol) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); vector myVolDims; myVolume->getDimensions(myVolDims); if (mySubVol >= myVolDims[3] || mySubVol < -1) { throw AlgorithmException("invalid subvolume specified"); } int64_t numColumns; if (mySubVol == -1) { numColumns = myVolDims[3] * myVolDims[4]; } else { numColumns = myVolDims[4]; } int64_t numNodes = mySurface->getNumberOfNodes(); myMetricOut->setNumberOfNodesAndColumns(numNodes, numColumns); myMetricOut->setStructure(mySurface->getStructure()); vector myArray(numNodes); AString methodName; switch (myMethod) { case VolumeFile::CUBIC: methodName = " cubic"; break; case VolumeFile::TRILINEAR: methodName = " trilinear"; break; case VolumeFile::ENCLOSING_VOXEL: methodName = " enclosing voxel"; break; } if (mySubVol == -1) { for (int64_t i = 0; i < myVolDims[3]; ++i) { for (int64_t j = 0; j < myVolDims[4]; ++j) { if (myMethod == VolumeFile::CUBIC) { myVolume->validateSpline(i, j);//to do spline deconvolution in parallel } AString metricLabel = myVolume->getMapName(i); if (myVolDims[4] != 1) { metricLabel += " component " + AString::number(j); } metricLabel += methodName; int64_t thisCol = i * myVolDims[4] + j; myMetricOut->setColumnName(thisCol, metricLabel); #pragma omp CARET_PARFOR for (int64_t node = 0; node < numNodes; ++node) { myArray[node] = myVolume->interpolateValue(mySurface->getCoordinate(node), myMethod, NULL, i, j); } if (myMethod == VolumeFile::CUBIC) { myVolume->freeSpline(i, j);//release memory we no longer need, if we allocated it } myMetricOut->setValuesForColumn(thisCol, myArray.data()); } } } else { for (int64_t j = 0; j < myVolDims[4]; ++j) { if (myMethod == VolumeFile::CUBIC) { myVolume->validateSpline(mySubVol, j);//to do spline deconvolution in parallel } AString metricLabel = myVolume->getMapName(mySubVol); if (myVolDims[4] != 1) { metricLabel += " component " + AString::number(j); } metricLabel += methodName; int64_t thisCol = j; myMetricOut->setColumnName(thisCol, metricLabel); #pragma omp CARET_PARFOR for (int64_t node = 0; node < numNodes; ++node) { myArray[node] = myVolume->interpolateValue(mySurface->getCoordinate(node), myMethod, NULL, mySubVol, j); } if (myMethod == VolumeFile::CUBIC) { myVolume->freeSpline(mySubVol, j);//release memory we no longer need, if we allocated it } myMetricOut->setValuesForColumn(thisCol, myArray.data()); } } } //ribbon mapping AlgorithmVolumeToSurfaceMapping::AlgorithmVolumeToSurfaceMapping(ProgressObject* myProgObj, const VolumeFile* myVolume, const SurfaceFile* mySurface, MetricFile* myMetricOut, const SurfaceFile* innerSurf, const SurfaceFile* outerSurf, const VolumeFile* roiVol, const int32_t& subdivisions, const int64_t& mySubVol, const int& weightsOutVertex, VolumeFile* weightsOut) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); vector myVolDims; myVolume->getDimensions(myVolDims); if (mySubVol >= myVolDims[3] || mySubVol < -1) { throw AlgorithmException("invalid subvolume specified"); } int64_t numColumns; if (mySubVol == -1) { numColumns = myVolDims[3] * myVolDims[4]; } else { numColumns = myVolDims[4]; } int64_t numNodes = mySurface->getNumberOfNodes(); myMetricOut->setNumberOfNodesAndColumns(numNodes, numColumns); myMetricOut->setStructure(mySurface->getStructure()); if (!mySurface->hasNodeCorrespondence(*outerSurf) || !mySurface->hasNodeCorrespondence(*innerSurf)) { throw AlgorithmException("all surfaces must have vertex correspondence"); } if (roiVol != NULL && !roiVol->matchesVolumeSpace(myVolume)) { throw AlgorithmException("roi volume is not in the same volume space as input volume"); } if (weightsOut != NULL) { if (weightsOutVertex < 0 || weightsOutVertex >= numNodes) throw AlgorithmException("invalid vertex for voxel weights output"); vector weightDims = myVolDims; weightDims.resize(3); weightsOut->reinitialize(weightDims, myVolume->getSform()); } vector > myWeights; const float* roiFrame = NULL; if (roiVol != NULL) roiFrame = roiVol->getFrame(); RibbonMappingHelper::computeWeightsRibbon(myWeights, myVolume->getVolumeSpace(), innerSurf, outerSurf, roiFrame, subdivisions); if (weightsOut != NULL) { weightsOut->setValueAllVoxels(0.0f); const vector& vertexWeights = myWeights[weightsOutVertex]; int numWeights = (int)vertexWeights.size(); for (int i = 0; i < numWeights; ++i) { weightsOut->setValue(vertexWeights[i].weight, vertexWeights[i].ijk); } } CaretArray myScratchArray(numNodes); float* myScratch = myScratchArray.getArray(); if (mySubVol == -1) { for (int64_t i = 0; i < myVolDims[3]; ++i) { for (int64_t j = 0; j < myVolDims[4]; ++j) { int64_t thisCol = i * myVolDims[4] + j; AString metricLabel = myVolume->getMapName(i); if (myVolDims[4] != 1) { metricLabel += " component " + AString::number(j); } metricLabel += " ribbon constrained"; myMetricOut->setColumnName(thisCol, metricLabel); #pragma omp CARET_PARFOR schedule(dynamic) for (int64_t node = 0; node < numNodes; ++node) { myScratch[node] = 0.0f; float totalWeight = 0.0f; int numVoxels = (int)myWeights[node].size(); for (int voxel = 0; voxel < numVoxels; ++voxel) { float thisWeight = myWeights[node][voxel].weight; totalWeight += thisWeight; myScratch[node] += thisWeight * myVolume->getValue(myWeights[node][voxel].ijk, i, j); } if (totalWeight != 0.0f) { myScratch[node] /= totalWeight; } else { myScratch[node] = 0.0f; } } myMetricOut->setValuesForColumn(thisCol, myScratch); } } } else { for (int64_t j = 0; j < myVolDims[4]; ++j) { AString metricLabel = myVolume->getMapName(mySubVol); if (myVolDims[4] != 1) { metricLabel += " component " + AString::number(j); } metricLabel += " ribbon constrained"; int64_t thisCol = j; myMetricOut->setColumnName(thisCol, metricLabel); #pragma omp CARET_PARFOR schedule(dynamic) for (int64_t node = 0; node < numNodes; ++node) { myScratch[node] = 0.0f; float totalWeight = 0.0f; int numVoxels = (int)myWeights[node].size(); for (int voxel = 0; voxel < numVoxels; ++voxel) { float thisWeight = myWeights[node][voxel].weight; totalWeight += thisWeight; myScratch[node] += thisWeight * myVolume->getValue(myWeights[node][voxel].ijk, mySubVol, j); } if (totalWeight != 0.0f) { myScratch[node] /= totalWeight; } else { myScratch[node] = 0.0f; } } myMetricOut->setValuesForColumn(thisCol, myScratch); } } } //myelin style mapping AlgorithmVolumeToSurfaceMapping::AlgorithmVolumeToSurfaceMapping(ProgressObject* myProgObj, const VolumeFile* myVolume, const SurfaceFile* mySurface, MetricFile* myMetricOut, const VolumeFile* roiVol, const MetricFile* thickness, const float& sigma, const int64_t& mySubVol): AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); vector myVolDims; myVolume->getDimensions(myVolDims); if (mySubVol >= myVolDims[3] || mySubVol < -1) { throw AlgorithmException("invalid subvolume specified"); } if (!roiVol->matchesVolumeSpace(myVolume)) { throw AlgorithmException("roi volume is not in the same volume space as input volume"); } int64_t numColumns; if (mySubVol == -1) { numColumns = myVolDims[3] * myVolDims[4]; } else { numColumns = myVolDims[4]; } int64_t numNodes = mySurface->getNumberOfNodes(); myMetricOut->setNumberOfNodesAndColumns(numNodes, numColumns); myMetricOut->setStructure(mySurface->getStructure()); vector > myWeights; precomputeWeightsMyelin(myWeights, mySurface, roiVol, thickness, sigma); vector myScratch(numNodes); if (mySubVol == -1) { for (int64_t i = 0; i < myVolDims[3]; ++i) { for (int64_t j = 0; j < myVolDims[4]; ++j) { int64_t thisCol = i * myVolDims[4] + j; AString metricLabel = myVolume->getMapName(i); if (myVolDims[4] != 1) { metricLabel += " component " + AString::number(j); } metricLabel += " ribbon constrained"; myMetricOut->setColumnName(thisCol, metricLabel); #pragma omp CARET_PARFOR schedule(dynamic) for (int64_t node = 0; node < numNodes; ++node) { double accum = 0.0; int numVoxels = (int)myWeights[node].size(); for (int voxel = 0; voxel < numVoxels; ++voxel) { accum += myWeights[node][voxel].weight * myVolume->getValue(myWeights[node][voxel].ijk, i, j);//weights have already been normalized in precompute, for this method } myScratch[node] = accum; } myMetricOut->setValuesForColumn(thisCol, myScratch.data()); } } } else { for (int64_t j = 0; j < myVolDims[4]; ++j) { AString metricLabel = myVolume->getMapName(mySubVol); if (myVolDims[4] != 1) { metricLabel += " component " + AString::number(j); } metricLabel += " ribbon constrained"; int64_t thisCol = j; myMetricOut->setColumnName(thisCol, metricLabel); #pragma omp CARET_PARFOR schedule(dynamic) for (int64_t node = 0; node < numNodes; ++node) { double accum = 0.0; int numVoxels = (int)myWeights[node].size(); for (int voxel = 0; voxel < numVoxels; ++voxel) { accum += myWeights[node][voxel].weight * myVolume->getValue(myWeights[node][voxel].ijk, mySubVol, j);//weights have already been normalized in precompute, for this method } myScratch[node] = accum; } myMetricOut->setValuesForColumn(thisCol, myScratch.data()); } } } void AlgorithmVolumeToSurfaceMapping::precomputeWeightsMyelin(vector >& myWeights, const SurfaceFile* mySurface, const VolumeFile* roiVol, const MetricFile* thickness, const float& sigma) { int64_t numNodes = mySurface->getNumberOfNodes(); myWeights.clear(); myWeights.resize(numNodes); vector myDims; roiVol->getDimensions(myDims); vector > volSpace = roiVol->getSform();//same as input volume Vector3D ivec, jvec, kvec, origin, ijorth, jkorth, kiorth; ivec[0] = volSpace[0][0]; jvec[0] = volSpace[0][1]; kvec[0] = volSpace[0][2]; origin[0] = volSpace[0][3]; ivec[1] = volSpace[1][0]; jvec[1] = volSpace[1][1]; kvec[1] = volSpace[1][2]; origin[1] = volSpace[1][3]; ivec[2] = volSpace[2][0]; jvec[2] = volSpace[2][1]; kvec[2] = volSpace[2][2]; origin[2] = volSpace[2][3]; ijorth = ivec.cross(jvec).normal();//find the box in index space that encloses a sphere of radius 1 jkorth = jvec.cross(kvec).normal(); kiorth = kvec.cross(ivec).normal(); float range[3]; range[0] = abs(1.0f / ivec.dot(jkorth));//we can multiply these by thickness to get the range of voxels to check range[1] = abs(1.0f / jvec.dot(kiorth)); range[2] = abs(1.0f / kvec.dot(ijorth)); const float* thicknessData = thickness->getValuePointerForColumn(0); const float* normals = mySurface->getNormalData(); float invnegsigmasquaredx2 = -1.0f / (2.0f * sigma * sigma); #pragma omp CARET_PARFOR schedule(dynamic) for (int node = 0; node < numNodes; ++node) { Vector3D nodeCoord = mySurface->getCoordinate(node), nodeIndices, nodenormal = normals + (node * 3); roiVol->spaceToIndex(nodeCoord, nodeIndices); float nodeThick = thicknessData[node]; int64_t min[3], max[3]; for (int i = 0; i < 3; ++i) { min[i] = (int)ceil(nodeIndices[i] - range[i] * nodeThick); if (min[i] < 0) min[i] = 0; max[i] = (int)floor(nodeIndices[i] + range[i] * nodeThick) + 1; if (max[i] > myDims[i]) max[i] = myDims[i]; } double weightTotal = 0.0; int64_t ijk[3]; for (ijk[2] = min[2]; ijk[2] < max[2]; ++ijk[2]) { for (ijk[1] = min[1]; ijk[1] < max[1]; ++ijk[1]) { for (ijk[0] = min[0]; ijk[0] < max[0]; ++ijk[0]) { Vector3D voxelCoord; roiVol->indexToSpace(ijk, voxelCoord); Vector3D delta = voxelCoord - nodeCoord; if (abs(nodenormal.dot(delta)) < 0.5f * nodeThick && roiVol->getValue(ijk) > 0.0f) { float weight = exp(delta.lengthsquared() * invnegsigmasquaredx2); myWeights[node].push_back(VoxelWeight(weight, ijk)); weightTotal += weight; } } } } int numWeights = (int)myWeights[node].size(); for (int i = 0; i < numWeights; ++i) { myWeights[node][i].weight /= weightTotal;//normalize weights to eliminate the need to divide } } } float AlgorithmVolumeToSurfaceMapping::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmVolumeToSurfaceMapping::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmVolumeToSurfaceMapping.h000066400000000000000000000060451255417355300250320ustar00rootroot00000000000000#ifndef __ALGORITHM_VOLUME_TO_SURFACE_MAPPING_H__ #define __ALGORITHM_VOLUME_TO_SURFACE_MAPPING_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" #include "RibbonMappingHelper.h" #include "Vector3D.h" #include "VolumeFile.h" #include namespace caret { class AlgorithmVolumeToSurfaceMapping : public AbstractAlgorithm { AlgorithmVolumeToSurfaceMapping(); void precomputeWeightsMyelin(std::vector >& myWeights, const SurfaceFile* mySurface, const VolumeFile* roiVol, const MetricFile* thickness, const float& sigma); enum Method { TRILINEAR, ENCLOSING_VOXEL, RIBBON_CONSTRAINED, CUBIC, MYELIN_STYLE }; protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmVolumeToSurfaceMapping(ProgressObject* myProgObj, const VolumeFile* myVolume, const SurfaceFile* mySurface, MetricFile* myMetricOut, const VolumeFile::InterpType& myMethod, const int64_t& mySubVol = -1); AlgorithmVolumeToSurfaceMapping(ProgressObject* myProgObj, const VolumeFile* myVolume, const SurfaceFile* mySurface, MetricFile* myMetricOut, const SurfaceFile* innerSurf, const SurfaceFile* outerSurf, const VolumeFile* roiVol = NULL, const int32_t& subdivisions = 3, const int64_t& mySubVol = -1, const int& weightsOutVertex = -1, VolumeFile* weightsOut = NULL); AlgorithmVolumeToSurfaceMapping(ProgressObject* myProgObj, const VolumeFile* myVolume, const SurfaceFile* mySurface, MetricFile* myMetricOut, const VolumeFile* roiVol, const MetricFile* thickness, const float& sigma, const int64_t& mySubVol = -1); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmVolumeToSurfaceMapping; } #endif //__ALGORITHM_VOLUME_TO_SURFACE_MAPPING_H__ workbench-1.1.1/src/Algorithms/AlgorithmVolumeVectorOperation.cxx000066400000000000000000000202241255417355300253140ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmVolumeVectorOperation.h" #include "AlgorithmException.h" #include "VolumeFile.h" using namespace caret; using namespace std; AString AlgorithmVolumeVectorOperation::getCommandSwitch() { return "-volume-vector-operation"; } AString AlgorithmVolumeVectorOperation::getShortDescription() { return "DO A VECTOR OPERATION ON VOLUME FILES"; } OperationParameters* AlgorithmVolumeVectorOperation::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addVolumeParameter(1, "vectors-a", "first vector input file"); ret->addVolumeParameter(2, "vectors-b", "second vector input file"); ret->addStringParameter(3, "operation", "what vector operation to do"); ret->addVolumeOutputParameter(4, "volume-out", "the output file"); ret->createOptionalParameter(5, "-normalize-a", "normalize vectors of first input"); ret->createOptionalParameter(6, "-normalize-b", "normalize vectors of second input"); ret->createOptionalParameter(7, "-normalize-output", "normalize output vectors (not valid for dot product)"); ret->createOptionalParameter(8, "-magnitude", "output the magnitude of the result (not valid for dot product)"); AString myText = AString("Does a vector operation on two volume files (that must have a multiple of 3 subvolumes). ") + "Either of the inputs may have multiple vectors (more than 3 subvolumes), but not both (at least one must have exactly 3 subvolumes). " + "The -magnitude and -normalize-output options may not be specified together, or with the DOT operation. " + "The parameter must be one of the following:\n"; vector opList = VectorOperation::getAllOperations(); for (int i = 0; i < (int)opList.size(); ++i) { myText += "\n" + VectorOperation::operationToString(opList[i]); } ret->setHelpText(myText); return ret; } void AlgorithmVolumeVectorOperation::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { VolumeFile* volumeA = myParams->getVolume(1); VolumeFile* volumeB = myParams->getVolume(2); AString operString = myParams->getString(3); bool ok = false; VectorOperation::Operation myOper = VectorOperation::stringToOperation(operString, ok); if (!ok) throw AlgorithmException("unrecognized operation string: " + operString); VolumeFile* myVolumeOut = myParams->getOutputVolume(4); bool normA = myParams->getOptionalParameter(5)->m_present; bool normB = myParams->getOptionalParameter(6)->m_present; bool normOut = myParams->getOptionalParameter(7)->m_present; bool magOut = myParams->getOptionalParameter(8)->m_present; AlgorithmVolumeVectorOperation(myProgObj, volumeA, volumeB, myOper, myVolumeOut, normA, normB, normOut, magOut); } AlgorithmVolumeVectorOperation::AlgorithmVolumeVectorOperation(ProgressObject* myProgObj, const VolumeFile* volumeA, const VolumeFile* volumeB, const VectorOperation::Operation& myOper, VolumeFile* myVolumeOut, const bool& normA, const bool& normB, const bool& normOut, const bool& magOut) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); const VolumeSpace& checkSpace = volumeA->getVolumeSpace(); if (!volumeB->matchesVolumeSpace(checkSpace)) throw AlgorithmException("input files have different volume spaces"); const vector dimA = volumeA->getDimensions(), dimB = volumeB->getDimensions(); if (dimA[4] != 1 || dimB[4] != 1) throw AlgorithmException("volumes with multiple components (complex or rgb datatypes) are not supported in vector operations"); if (dimA[3] % 3 != 0) throw AlgorithmException("number of subvolumes in first input is not a multiple of 3"); if (dimB[3] % 3 != 0) throw AlgorithmException("number of subvolumes in first input is not a multiple of 3"); int64_t numVecA = dimA[3] / 3, numVecB = dimB[3] / 3; if (numVecA > 1 && numVecB > 1) throw AlgorithmException("both inputs have more than 3 subvolumes (more than 1 vector)"); if (normOut && magOut) throw AlgorithmException("normalizing the output and taking the magnitude is meaningless"); bool opScalarResult = VectorOperation::operationReturnsScalar(myOper); if (opScalarResult && (normOut || magOut)) throw AlgorithmException("cannot normalize or take magnitude of a scalar result (such as a dot product)"); bool swapped = false; const VolumeFile* multiVec = volumeA, *singleVec = volumeB; int64_t numOutVecs = numVecA; if (numVecB > 1) { multiVec = volumeB; singleVec = volumeA; numOutVecs = numVecB; swapped = true; } int64_t numSubvolsOut = numOutVecs * 3; if (opScalarResult || magOut) numSubvolsOut = numOutVecs; vector outDims = dimA; outDims.resize(3); if (numSubvolsOut > 1) outDims.push_back(numSubvolsOut); myVolumeOut->reinitialize(outDims, checkSpace.getSform()); int64_t frameSize = dimA[0] * dimA[1] * dimA[2]; vector outFrames[3];//let the scalar result case overallocate outFrames[0].resize(frameSize); outFrames[1].resize(frameSize); outFrames[2].resize(frameSize); const float* xFrameSingle = singleVec->getFrame(0); const float* yFrameSingle = singleVec->getFrame(1); const float* zFrameSingle = singleVec->getFrame(2); for (int64_t v = 0; v < numOutVecs; ++v) { const float* xFrameMulti = multiVec->getFrame(v * 3); const float* yFrameMulti = multiVec->getFrame(v * 3 + 1); const float* zFrameMulti = multiVec->getFrame(v * 3 + 2); for (int64_t i = 0; i < frameSize; ++i) { Vector3D vecA(xFrameMulti[i], yFrameMulti[i], zFrameMulti[i]); Vector3D vecB(xFrameSingle[i], yFrameSingle[i], zFrameSingle[i]); if (swapped) { Vector3D tempVec = vecA; vecA = vecB; vecB = tempVec; } if (normA) vecA = vecA.normal(); if (normB) vecB = vecB.normal(); if (opScalarResult) { outFrames[0][i] = VectorOperation::doScalarOperation(vecA, vecB, myOper); } else { Vector3D tempVec = VectorOperation::doVectorOperation(vecA, vecB, myOper); if (normOut) tempVec = tempVec.normal(); if (magOut) { outFrames[0][i] = tempVec.length(); } else { outFrames[0][i] = tempVec[0]; outFrames[1][i] = tempVec[1]; outFrames[2][i] = tempVec[2]; } } } if (opScalarResult || magOut) { myVolumeOut->setFrame(outFrames[0].data(), v); } else { myVolumeOut->setFrame(outFrames[0].data(), v * 3); myVolumeOut->setFrame(outFrames[1].data(), v * 3 + 1); myVolumeOut->setFrame(outFrames[2].data(), v * 3 + 2); } } } float AlgorithmVolumeVectorOperation::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmVolumeVectorOperation::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmVolumeVectorOperation.h000066400000000000000000000037631255417355300247520ustar00rootroot00000000000000#ifndef __ALGORITHM_VOLUME_VECTOR_OPERATION_H__ #define __ALGORITHM_VOLUME_VECTOR_OPERATION_H__ /*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" #include "VectorOperation.h" namespace caret { class AlgorithmVolumeVectorOperation : public AbstractAlgorithm { AlgorithmVolumeVectorOperation(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmVolumeVectorOperation(ProgressObject* myProgObj, const VolumeFile* volumeA, const VolumeFile* volumeB, const VectorOperation::Operation& myOper, VolumeFile* myVolumeOut, const bool& normA = false, const bool& normB = false, const bool& normOut = false, const bool& magOut = false); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmVolumeVectorOperation; } #endif //__ALGORITHM_VOLUME_VECTOR_OPERATION_H__ workbench-1.1.1/src/Algorithms/AlgorithmVolumeWarpfieldResample.cxx000066400000000000000000000160361255417355300256050ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmVolumeWarpfieldResample.h" #include "AlgorithmException.h" #include "CaretLogger.h" #include "CaretOMP.h" #include "Vector3D.h" #include "WarpfieldFile.h" using namespace caret; using namespace std; AString AlgorithmVolumeWarpfieldResample::getCommandSwitch() { return "-volume-warpfield-resample"; } AString AlgorithmVolumeWarpfieldResample::getShortDescription() { return "RESAMPLE VOLUME USING WARPFIELD"; } OperationParameters* AlgorithmVolumeWarpfieldResample::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addVolumeParameter(1, "volume-in", "volume to resample"); ret->addStringParameter(2, "warpfield", "the warpfield to apply"); ret->addVolumeParameter(3, "volume-space", "a volume file in the volume space you want for the output"); ret->addStringParameter(4, "method", "the resampling method"); ret->addVolumeOutputParameter(5, "volume-out", "the output volume"); OptionalParameter* fnirtOpt = ret->createOptionalParameter(6, "-fnirt", "MUST be used if using a fnirt warpfield"); fnirtOpt->addStringParameter(1, "source-volume", "the source volume used when generating the warpfield"); ret->setHelpText( AString("Resample a volume file with a warpfield. ") + "The recommended methods are CUBIC (cubic spline) for most data, and ENCLOSING_VOXEL for label data. " "The parameter must be one of:\n\n" + "CUBIC\nENCLOSING_VOXEL\nTRILINEAR" ); return ret; } void AlgorithmVolumeWarpfieldResample::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { VolumeFile* inVol = myParams->getVolume(1); AString warpName = myParams->getString(2); VolumeFile* refSpace = myParams->getVolume(3); AString method = myParams->getString(4); VolumeFile* outVol = myParams->getOutputVolume(5); OptionalParameter* fnirtOpt = myParams->getOptionalParameter(6); WarpfieldFile myWarpfield; if (fnirtOpt->m_present) { myWarpfield.readFnirt(warpName, fnirtOpt->getString(1)); } else { myWarpfield.readWorld(warpName); } VolumeFile::InterpType myMethod = VolumeFile::CUBIC; if (method == "CUBIC") { myMethod = VolumeFile::CUBIC; } else if (method == "TRILINEAR") { myMethod = VolumeFile::TRILINEAR; } else if (method == "ENCLOSING_VOXEL") { myMethod = VolumeFile::ENCLOSING_VOXEL; } else { throw AlgorithmException("unrecognized interpolation method"); } vector refDims; refSpace->getDimensions(refDims); AlgorithmVolumeWarpfieldResample(myProgObj, inVol, myWarpfield.getWarpfield(), refDims.data(), refSpace->getSform(), myMethod, outVol); } AlgorithmVolumeWarpfieldResample::AlgorithmVolumeWarpfieldResample(ProgressObject* myProgObj, const VolumeFile* inVol, const VolumeFile* warpfield, const int64_t refDims[3], const vector >& refSform, const VolumeFile::InterpType& myMethod, VolumeFile* outVol) : AbstractAlgorithm(myProgObj) { LevelProgress myProgress(myProgObj); vector warpDims; warpfield->getDimensions(warpDims); if (warpDims[3] != 3 || warpDims[4] != 1) throw AlgorithmException("provided warpfield volume has wrong number of subvolumes or components"); vector outDims = inVol->getOriginalDimensions(); if (outDims.size() < 3) throw AlgorithmException("input must have 3 spatial dimensions"); outDims[0] = refDims[0]; outDims[1] = refDims[1]; outDims[2] = refDims[2]; int64_t numMaps = inVol->getNumberOfMaps(), numComponents = inVol->getNumberOfComponents(); outVol->reinitialize(outDims, refSform, numComponents, inVol->getType()); if (inVol->isMappedWithLabelTable()) { if (myMethod != VolumeFile::ENCLOSING_VOXEL) { CaretLogWarning("using interpolation type other than ENCLOSING_VOXEL on a label volume"); } for (int64_t i = 0; i < numMaps; ++i) { *(outVol->getMapLabelTable(i)) = *(inVol->getMapLabelTable(i)); } } for (int64_t c = 0; c < numComponents; ++c) { for (int64_t b = 0; b < numMaps; ++b) { if (myMethod == VolumeFile::CUBIC) { inVol->validateSpline(b, c);//because deconvolve is parallel, but won't execute parallel if we are already in a parallel section } #pragma omp CARET_PARFOR schedule(dynamic) for (int64_t k = 0; k < outDims[2]; ++k) { for (int64_t j = 0; j < outDims[1]; ++j) { for (int64_t i = 0; i < outDims[0]; ++i) { Vector3D outCoord, inCoord, displacement; outVol->indexToSpace(i, j, k, outCoord); bool validDisplacement = false; displacement[0] = warpfield->interpolateValue(outCoord, VolumeFile::TRILINEAR, &validDisplacement, 0); if (validDisplacement) { displacement[1] = warpfield->interpolateValue(outCoord, VolumeFile::TRILINEAR, NULL, 1); displacement[2] = warpfield->interpolateValue(outCoord, VolumeFile::TRILINEAR, NULL, 2); inCoord = outCoord + displacement; float interpVal = inVol->interpolateValue(inCoord, myMethod, NULL, b, c); outVol->setValue(interpVal, i, j, k, b, c); } else { outVol->setValue(VolumeFile::INVALID_INTERP_VALUE, i, j, k, b, c); } } } } if (myMethod == VolumeFile::CUBIC) { inVol->freeSpline(b, c);//release memory we no longer need, if we allocated it } } } } float AlgorithmVolumeWarpfieldResample::getAlgorithmInternalWeight() { return 1.0f;//override this if needed, if the progress bar isn't smooth } float AlgorithmVolumeWarpfieldResample::getSubAlgorithmWeight() { //return AlgorithmInsertNameHere::getAlgorithmWeight();//if you use a subalgorithm return 0.0f; } workbench-1.1.1/src/Algorithms/AlgorithmVolumeWarpfieldResample.h000066400000000000000000000036561255417355300252360ustar00rootroot00000000000000#ifndef __ALGORITHM_VOLUME_WARPFIELD_RESAMPLE_H__ #define __ALGORITHM_VOLUME_WARPFIELD_RESAMPLE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractAlgorithm.h" #include "VolumeFile.h" namespace caret { class AlgorithmVolumeWarpfieldResample : public AbstractAlgorithm { AlgorithmVolumeWarpfieldResample(); protected: static float getSubAlgorithmWeight(); static float getAlgorithmInternalWeight(); public: AlgorithmVolumeWarpfieldResample(ProgressObject* myProgObj, const VolumeFile* inVol, const VolumeFile* warpfield, const int64_t refDims[3], const std::vector >& refSform, const VolumeFile::InterpType& myMethod, VolumeFile* outVol); static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoAlgorithmVolumeWarpfieldResample; } #endif //__ALGORITHM_VOLUME_WARPFIELD_RESAMPLE_H__ workbench-1.1.1/src/Algorithms/CMakeLists.txt000066400000000000000000000153651255417355300211600ustar00rootroot00000000000000# # Name of project # PROJECT (Algorithms) # # Need XML from Qt # SET(QT_DONT_USE_QTGUI) # # Add QT for includes # INCLUDE (${QT_USE_FILE}) # # Create the helper library # ADD_LIBRARY(Algorithms AbstractAlgorithm.h AlgorithmBorderResample.h AlgorithmBorderToVertices.h AlgorithmCiftiAllLabelsToROIs.h AlgorithmCiftiAverage.h AlgorithmCiftiAverageDenseROI.h AlgorithmCiftiAverageROICorrelation.h AlgorithmCiftiCorrelation.h AlgorithmCiftiCorrelationGradient.h AlgorithmCiftiCreateDenseScalar.h AlgorithmCiftiCreateDenseTimeseries.h AlgorithmCiftiCreateLabel.h AlgorithmCiftiCrossCorrelation.h AlgorithmCiftiDilate.h AlgorithmCiftiExtrema.h AlgorithmCiftiFalseCorrelation.h AlgorithmCiftiFindClusters.h AlgorithmCiftiGradient.h AlgorithmCiftiLabelAdjacency.h AlgorithmCiftiLabelToROI.h AlgorithmCiftiMergeDense.h AlgorithmCiftiPairwiseCorrelation.h AlgorithmCiftiParcellate.h AlgorithmCiftiParcelMappingToLabel.h AlgorithmCiftiReduce.h AlgorithmCiftiReorder.h AlgorithmCiftiReplaceStructure.h AlgorithmCiftiResample.h AlgorithmCiftiRestrictDenseMap.h AlgorithmCiftiROIsFromExtrema.h AlgorithmCiftiSeparate.h AlgorithmCiftiSmoothing.h AlgorithmCiftiTranspose.h AlgorithmCiftiVectorOperation.h AlgorithmCreateSignedDistanceVolume.h AlgorithmException.h AlgorithmFiberDotProducts.h AlgorithmFociResample.h AlgorithmGiftiAllLabelsToROIs.h AlgorithmGiftiLabelAddPrefix.h AlgorithmGiftiLabelToROI.h AlgorithmLabelDilate.h AlgorithmLabelModifyKeys.h AlgorithmLabelResample.h AlgorithmLabelToBorder.h AlgorithmMetricDilate.h AlgorithmMetricEstimateFWHM.h AlgorithmMetricExtrema.h AlgorithmMetricFalseCorrelation.h AlgorithmMetricFillHoles.h AlgorithmMetricFindClusters.h AlgorithmMetricGradient.h AlgorithmMetricReduce.h AlgorithmMetricRegression.h AlgorithmMetricRemoveIslands.h AlgorithmMetricResample.h AlgorithmMetricROIsFromExtrema.h AlgorithmMetricROIsToBorder.h AlgorithmMetricSmoothing.h AlgorithmMetricTFCE.h AlgorithmMetricToVolumeMapping.h AlgorithmMetricVectorOperation.h AlgorithmMetricVectorTowardROI.h AlgorithmNodesInsideBorder.h AlgorithmSignedDistanceToSurface.h AlgorithmSurfaceAffineRegression.h AlgorithmSurfaceApplyAffine.h AlgorithmSurfaceApplyWarpfield.h AlgorithmSurfaceAverage.h AlgorithmSurfaceCortexLayer.h AlgorithmSurfaceCreateSphere.h AlgorithmSurfaceDistortion.h AlgorithmSurfaceFlipLR.h AlgorithmSurfaceGenerateInflated.h AlgorithmSurfaceInflation.h AlgorithmSurfaceMatch.h AlgorithmSurfaceModifySphere.h AlgorithmSurfaceResample.h AlgorithmSurfaceSmoothing.h AlgorithmSurfaceSphereProjectUnproject.h AlgorithmSurfaceToSurface3dDistance.h AlgorithmSurfaceWedgeVolume.h AlgorithmVolumeAffineResample.h AlgorithmVolumeAllLabelsToROIs.h AlgorithmVolumeDilate.h AlgorithmVolumeEstimateFWHM.h AlgorithmVolumeExtrema.h AlgorithmVolumeFillHoles.h AlgorithmVolumeFindClusters.h AlgorithmVolumeGradient.h AlgorithmVolumeLabelToROI.h AlgorithmVolumeLabelToSurfaceMapping.h AlgorithmVolumeParcelResampling.h AlgorithmVolumeParcelResamplingGeneric.h AlgorithmVolumeParcelSmoothing.h AlgorithmVolumeReduce.h AlgorithmVolumeRemoveIslands.h AlgorithmVolumeROIsFromExtrema.h AlgorithmVolumeSmoothing.h AlgorithmVolumeTFCE.h AlgorithmVolumeToSurfaceMapping.h AlgorithmVolumeVectorOperation.h AlgorithmVolumeWarpfieldResample.h OverlapLogicEnum.h AbstractAlgorithm.cxx AlgorithmBorderResample.cxx AlgorithmBorderToVertices.cxx AlgorithmCiftiAllLabelsToROIs.cxx AlgorithmCiftiAverage.cxx AlgorithmCiftiAverageDenseROI.cxx AlgorithmCiftiAverageROICorrelation.cxx AlgorithmCiftiCorrelation.cxx AlgorithmCiftiCorrelationGradient.cxx AlgorithmCiftiCreateDenseScalar.cxx AlgorithmCiftiCreateDenseTimeseries.cxx AlgorithmCiftiCreateLabel.cxx AlgorithmCiftiCrossCorrelation.cxx AlgorithmCiftiDilate.cxx AlgorithmCiftiExtrema.cxx AlgorithmCiftiFalseCorrelation.cxx AlgorithmCiftiFindClusters.cxx AlgorithmCiftiGradient.cxx AlgorithmCiftiLabelAdjacency.cxx AlgorithmCiftiLabelToROI.cxx AlgorithmCiftiMergeDense.cxx AlgorithmCiftiPairwiseCorrelation.cxx AlgorithmCiftiParcellate.cxx AlgorithmCiftiParcelMappingToLabel.cxx AlgorithmCiftiReduce.cxx AlgorithmCiftiReorder.cxx AlgorithmCiftiReplaceStructure.cxx AlgorithmCiftiResample.cxx AlgorithmCiftiRestrictDenseMap.cxx AlgorithmCiftiROIsFromExtrema.cxx AlgorithmCiftiSeparate.cxx AlgorithmCiftiSmoothing.cxx AlgorithmCiftiTranspose.cxx AlgorithmCiftiVectorOperation.cxx AlgorithmCreateSignedDistanceVolume.cxx AlgorithmException.cxx AlgorithmFiberDotProducts.cxx AlgorithmFociResample.cxx AlgorithmGiftiAllLabelsToROIs.cxx AlgorithmGiftiLabelAddPrefix.cxx AlgorithmGiftiLabelToROI.cxx AlgorithmLabelDilate.cxx AlgorithmLabelModifyKeys.cxx AlgorithmLabelResample.cxx AlgorithmLabelToBorder.cxx AlgorithmMetricDilate.cxx AlgorithmMetricEstimateFWHM.cxx AlgorithmMetricExtrema.cxx AlgorithmMetricFalseCorrelation.cxx AlgorithmMetricFillHoles.cxx AlgorithmMetricFindClusters.cxx AlgorithmMetricGradient.cxx AlgorithmMetricReduce.cxx AlgorithmMetricRegression.cxx AlgorithmMetricRemoveIslands.cxx AlgorithmMetricResample.cxx AlgorithmMetricROIsFromExtrema.cxx AlgorithmMetricROIsToBorder.cxx AlgorithmMetricSmoothing.cxx AlgorithmMetricTFCE.cxx AlgorithmMetricToVolumeMapping.cxx AlgorithmMetricVectorOperation.cxx AlgorithmMetricVectorTowardROI.cxx AlgorithmNodesInsideBorder.cxx AlgorithmSignedDistanceToSurface.cxx AlgorithmSurfaceAffineRegression.cxx AlgorithmSurfaceApplyAffine.cxx AlgorithmSurfaceApplyWarpfield.cxx AlgorithmSurfaceAverage.cxx AlgorithmSurfaceCortexLayer.cxx AlgorithmSurfaceCreateSphere.cxx AlgorithmSurfaceDistortion.cxx AlgorithmSurfaceFlipLR.cxx AlgorithmSurfaceGenerateInflated.cxx AlgorithmSurfaceInflation.cxx AlgorithmSurfaceMatch.cxx AlgorithmSurfaceModifySphere.cxx AlgorithmSurfaceResample.cxx AlgorithmSurfaceSmoothing.cxx AlgorithmSurfaceSphereProjectUnproject.cxx AlgorithmSurfaceToSurface3dDistance.cxx AlgorithmSurfaceWedgeVolume.cxx AlgorithmVolumeAffineResample.cxx AlgorithmVolumeAllLabelsToROIs.cxx AlgorithmVolumeDilate.cxx AlgorithmVolumeEstimateFWHM.cxx AlgorithmVolumeExtrema.cxx AlgorithmVolumeFillHoles.cxx AlgorithmVolumeFindClusters.cxx AlgorithmVolumeGradient.cxx AlgorithmVolumeLabelToROI.cxx AlgorithmVolumeLabelToSurfaceMapping.cxx AlgorithmVolumeParcelResampling.cxx AlgorithmVolumeParcelResamplingGeneric.cxx AlgorithmVolumeParcelSmoothing.cxx AlgorithmVolumeReduce.cxx AlgorithmVolumeRemoveIslands.cxx AlgorithmVolumeROIsFromExtrema.cxx AlgorithmVolumeSmoothing.cxx AlgorithmVolumeTFCE.cxx AlgorithmVolumeToSurfaceMapping.cxx AlgorithmVolumeVectorOperation.cxx AlgorithmVolumeWarpfieldResample.cxx OverlapLogicEnum.cxx ) # # Find Headers # INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/Algorithms ${CMAKE_SOURCE_DIR}/OperationsBase ${CMAKE_SOURCE_DIR}/FilesBase ${CMAKE_SOURCE_DIR}/Files ${CMAKE_SOURCE_DIR}/Gifti ${CMAKE_SOURCE_DIR}/Cifti ${CMAKE_SOURCE_DIR}/Palette ${CMAKE_SOURCE_DIR}/Nifti ${CMAKE_SOURCE_DIR}/Scenes ${CMAKE_SOURCE_DIR}/Xml ${CMAKE_SOURCE_DIR}/Charting ${CMAKE_SOURCE_DIR}/Common ) workbench-1.1.1/src/Algorithms/OverlapLogicEnum.cxx000066400000000000000000000223161255417355300223510ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __OVERLAP_LOGIC_ENUM_DECLARE__ #include "OverlapLogicEnum.h" #undef __OVERLAP_LOGIC_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::OverlapLogicEnum * \brief Enumeration for how to handle overlapping areas * * Created to be used as a parameter to ROIsFromExtrema algorithms */ /** * Constructor. * * @param enumValue * An enumerated value. * @param integerCode * Integer code for this enumerated value. * * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ OverlapLogicEnum::OverlapLogicEnum(const Enum enumValue, const int32_t integerCode, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCode; this->name = name; this->guiName = guiName; } /** * Destructor. */ OverlapLogicEnum::~OverlapLogicEnum() { } /** * Initialize the enumerated metadata. */ void OverlapLogicEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(OverlapLogicEnum(ALLOW, 0, "ALLOW", "Allow")); enumData.push_back(OverlapLogicEnum(CLOSEST, 1, "CLOSEST", "Closest")); enumData.push_back(OverlapLogicEnum(EXCLUDE, 2, "EXCLUDE", "Exclude")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const OverlapLogicEnum* OverlapLogicEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const OverlapLogicEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString OverlapLogicEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const OverlapLogicEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ OverlapLogicEnum::Enum OverlapLogicEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = ALLOW; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const OverlapLogicEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type OverlapLogicEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString OverlapLogicEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const OverlapLogicEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ OverlapLogicEnum::Enum OverlapLogicEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = ALLOW; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const OverlapLogicEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type OverlapLogicEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t OverlapLogicEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const OverlapLogicEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ OverlapLogicEnum::Enum OverlapLogicEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = ALLOW; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const OverlapLogicEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type OverlapLogicEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void OverlapLogicEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void OverlapLogicEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(OverlapLogicEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void OverlapLogicEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(OverlapLogicEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } workbench-1.1.1/src/Algorithms/OverlapLogicEnum.h000066400000000000000000000057771255417355300220120ustar00rootroot00000000000000#ifndef __OVERLAP_LOGIC_ENUM_H__ #define __OVERLAP_LOGIC_ENUM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class OverlapLogicEnum { public: /** * Enumerated values. */ enum Enum { /** Allow overlap */ ALLOW, /** Use closer center to resolve overlaps */ CLOSEST, /** Contested areas are unused */ EXCLUDE }; ~OverlapLogicEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); private: OverlapLogicEnum(const Enum enumValue, const int32_t integerCode, const AString& name, const AString& guiName); static const OverlapLogicEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; #ifdef __OVERLAP_LOGIC_ENUM_DECLARE__ std::vector OverlapLogicEnum::enumData; bool OverlapLogicEnum::initializedFlag = false; #endif // __OVERLAP_LOGIC_ENUM_DECLARE__ } // namespace #endif //__OVERLAP_LOGIC_ENUM_H__ workbench-1.1.1/src/Brain/000077500000000000000000000000001255417355300153305ustar00rootroot00000000000000workbench-1.1.1/src/Brain/BorderDrawingTypeEnum.cxx000066400000000000000000000227341255417355300223040ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __BORDER_DRAWING_TYPE_ENUM_DECLARE__ #include "BorderDrawingTypeEnum.h" #undef __BORDER_DRAWING_TYPE_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::BorderDrawingTypeEnum * \brief * * */ /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ BorderDrawingTypeEnum::BorderDrawingTypeEnum(const Enum enumValue, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCodeCounter++; this->name = name; this->guiName = guiName; } /** * Destructor. */ BorderDrawingTypeEnum::~BorderDrawingTypeEnum() { } /** * Initialize the enumerated metadata. */ void BorderDrawingTypeEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(BorderDrawingTypeEnum(DRAW_AS_LINES, "DRAW_AS_LINES", "Lines")); enumData.push_back(BorderDrawingTypeEnum(DRAW_AS_POINTS_SPHERES, "DRAW_AS_POINTS_SPHERES", "Spheres")); enumData.push_back(BorderDrawingTypeEnum(DRAW_AS_POINTS_SQUARES, "DRAW_AS_POINTS_SQUARES", "Squares")); enumData.push_back(BorderDrawingTypeEnum(DRAW_AS_POINTS_AND_LINES, "DRAW_AS_POINTS_AND_LINES", "Spheres and Lines")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const BorderDrawingTypeEnum* BorderDrawingTypeEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const BorderDrawingTypeEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString BorderDrawingTypeEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const BorderDrawingTypeEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ BorderDrawingTypeEnum::Enum BorderDrawingTypeEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = DRAW_AS_LINES; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const BorderDrawingTypeEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type BorderDrawingTypeEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString BorderDrawingTypeEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const BorderDrawingTypeEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ BorderDrawingTypeEnum::Enum BorderDrawingTypeEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = DRAW_AS_LINES; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const BorderDrawingTypeEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type BorderDrawingTypeEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t BorderDrawingTypeEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const BorderDrawingTypeEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ BorderDrawingTypeEnum::Enum BorderDrawingTypeEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = DRAW_AS_LINES; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const BorderDrawingTypeEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type BorderDrawingTypeEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void BorderDrawingTypeEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void BorderDrawingTypeEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(BorderDrawingTypeEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void BorderDrawingTypeEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(BorderDrawingTypeEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } workbench-1.1.1/src/Brain/BorderDrawingTypeEnum.h000066400000000000000000000064331255417355300217270ustar00rootroot00000000000000#ifndef __BORDER_DRAWING_TYPE_ENUM__H_ #define __BORDER_DRAWING_TYPE_ENUM__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class BorderDrawingTypeEnum { public: /** * Enumerated values for border drawing types. */ enum Enum { /** draw as lines */ DRAW_AS_LINES, /** draw as spherical points */ DRAW_AS_POINTS_SPHERES, /** draw as square points */ DRAW_AS_POINTS_SQUARES, /** draw as points and lines */ DRAW_AS_POINTS_AND_LINES }; ~BorderDrawingTypeEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); private: BorderDrawingTypeEnum(const Enum enumValue, const AString& name, const AString& guiName); static const BorderDrawingTypeEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; #ifdef __BORDER_DRAWING_TYPE_ENUM_DECLARE__ std::vector BorderDrawingTypeEnum::enumData; bool BorderDrawingTypeEnum::initializedFlag = false; int32_t BorderDrawingTypeEnum::integerCodeCounter = 0; #endif // __BORDER_DRAWING_TYPE_ENUM_DECLARE__ } // namespace #endif //__BORDER_DRAWING_TYPE_ENUM__H_ workbench-1.1.1/src/Brain/Brain.cxx000066400000000000000000006701461255417355300171250ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include "CaretAssert.h" #include "Border.h" #include "BorderFile.h" #include "BorderPointFromSearch.h" #include "Brain.h" #include "BrainordinateRegionOfInterest.h" #include "BrainStructure.h" #include "BrowserTabContent.h" #include "CaretDataFileHelper.h" #include "CaretLogger.h" #include "CaretPreferences.h" #include "ChartingDataManager.h" #include "ChartableLineSeriesBrainordinateInterface.h" #include "CiftiBrainordinateDataSeriesFile.h" #include "CiftiBrainordinateLabelFile.h" #include "CiftiBrainordinateScalarFile.h" #include "CiftiConnectivityMatrixDenseFile.h" #include "CiftiConnectivityMatrixDenseParcelFile.h" #include "CiftiFiberOrientationFile.h" #include "CiftiFiberTrajectoryFile.h" #include "CiftiConnectivityMatrixParcelFile.h" #include "CiftiConnectivityMatrixParcelDenseFile.h" #include "CiftiParcelLabelFile.h" #include "CiftiParcelSeriesFile.h" #include "CiftiParcelScalarFile.h" #include "CiftiScalarDataSeriesFile.h" #include "DisplayPropertiesBorders.h" #include "DisplayPropertiesFiberOrientation.h" #include "DisplayPropertiesFoci.h" #include "DisplayPropertiesImages.h" #include "DisplayPropertiesLabels.h" #include "DisplayPropertiesSurface.h" #include "DisplayPropertiesVolume.h" #include "ElapsedTimer.h" #include "EventBrainReset.h" #include "EventBrowserTabGetAll.h" #include "EventCaretMappableDataFilesGet.h" #include "EventDataFileAdd.h" #include "EventDataFileDelete.h" #include "EventDataFileRead.h" #include "EventDataFileReload.h" #include "EventGetDisplayedDataFiles.h" #include "EventModelAdd.h" #include "EventModelDelete.h" #include "EventModelGetAll.h" #include "EventPaletteGetByName.h" #include "EventProgressUpdate.h" #include "EventSpecFileReadDataFiles.h" #include "EventManager.h" #include "FiberOrientationSamplesLoader.h" #include "FileInformation.h" #include "FociFile.h" #include "GroupAndNameHierarchyModel.h" #include "IdentificationManager.h" #include "ImageFile.h" #include "MathFunctions.h" #include "MetricFile.h" #include "ModelChart.h" #include "ModelSurface.h" #include "ModelSurfaceMontage.h" #include "ModelVolume.h" #include "ModelWholeBrain.h" #include "LabelFile.h" #include "Overlay.h" #include "OverlaySet.h" #include "PaletteFile.h" #include "RgbaFile.h" #include "SceneAttributes.h" #include "SceneClass.h" #include "SceneClassArray.h" #include "SceneClassAssistant.h" #include "SceneFile.h" #include "SelectionManager.h" #include "SessionManager.h" #include "SpecFile.h" #include "SpecFileDataFile.h" #include "SpecFileDataFileTypeGroup.h" #include "Surface.h" #include "SurfaceProjectedItem.h" #include "SystemUtilities.h" #include "VolumeFile.h" #include "VolumeSurfaceOutlineSetModel.h" using namespace caret; /** * Constructor. */ Brain::Brain() { m_chartingDataManager = new ChartingDataManager(this); m_fiberOrientationSamplesLoader = new FiberOrientationSamplesLoader(); m_paletteFile = new PaletteFile(); m_paletteFile->setFileName(convertFilePathNameToAbsolutePathName(m_paletteFile->getFileName())); m_paletteFile->clearModified(); m_specFile = new SpecFile(); m_specFile->setFileName(""); m_specFile->clearModified(); m_modelChart = NULL; m_surfaceMontageModel = NULL; m_volumeSliceModel = NULL; m_wholeBrainModel = NULL; m_displayPropertiesBorders = new DisplayPropertiesBorders(); m_displayProperties.push_back(m_displayPropertiesBorders); m_displayPropertiesFiberOrientation = new DisplayPropertiesFiberOrientation(); m_displayProperties.push_back(m_displayPropertiesFiberOrientation); m_displayPropertiesFoci = new DisplayPropertiesFoci(); m_displayProperties.push_back(m_displayPropertiesFoci); m_displayPropertiesImages = new DisplayPropertiesImages(this); m_displayProperties.push_back(m_displayPropertiesImages); m_displayPropertiesLabels = new DisplayPropertiesLabels(); m_displayProperties.push_back(m_displayPropertiesLabels); m_displayPropertiesSurface = new DisplayPropertiesSurface(); m_displayProperties.push_back(m_displayPropertiesSurface); m_displayPropertiesVolume = new DisplayPropertiesVolume(); m_displayProperties.push_back(m_displayPropertiesVolume); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_DATA_FILE_ADD); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_DATA_FILE_DELETE); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_DATA_FILE_READ); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_DATA_FILE_RELOAD); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_CARET_MAPPABLE_DATA_FILES_GET); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_GET_DISPLAYED_DATA_FILES); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_SPEC_FILE_READ_DATA_FILES); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_PALETTE_GET_BY_NAME); m_isSpecFileBeingRead = false; m_sceneAssistant = new SceneClassAssistant(); m_sceneAssistant->add("displayPropertiesBorders", "DisplayPropertiesBorders", m_displayPropertiesBorders); m_sceneAssistant->add("displayPropertiesFiberOrientation", "DisplayPropertiesFiberOrientation", m_displayPropertiesFiberOrientation); m_sceneAssistant->add("displayPropertiesFoci", "DisplayPropertiesFoci", m_displayPropertiesFoci); m_sceneAssistant->add("m_displayPropertiesImages", "DisplayPropertiesImages", m_displayPropertiesImages); m_sceneAssistant->add("m_displayPropertiesLabels", "DisplayPropertiesLabels", m_displayPropertiesLabels); m_sceneAssistant->add("m_displayPropertiesSurface", "DisplayPropertiesSurface", m_displayPropertiesSurface); m_sceneAssistant->add("displayPropertiesVolume", "DisplayPropertiesVolume", m_displayPropertiesVolume); m_selectionManager = new SelectionManager(); m_identificationManager = new IdentificationManager(); m_brainordinateHighlightRegionOfInterest = new BrainordinateRegionOfInterest(); updateChartModel(); } /** * Destructor. */ Brain::~Brain() { EventManager::get()->removeAllEventsFromListener(this); delete m_sceneAssistant; for (std::vector::iterator iter = m_displayProperties.begin(); iter != m_displayProperties.end(); iter++) { delete *iter; } m_displayProperties.clear(); resetBrain(); delete m_specFile; delete m_chartingDataManager; delete m_fiberOrientationSamplesLoader; delete m_paletteFile; if (m_modelChart != NULL) { delete m_modelChart; } if (m_surfaceMontageModel != NULL) { delete m_surfaceMontageModel; } if (m_volumeSliceModel != NULL) { delete m_volumeSliceModel; } if (m_wholeBrainModel != NULL) { delete m_wholeBrainModel; } delete m_selectionManager; delete m_identificationManager; delete m_brainordinateHighlightRegionOfInterest; } /** * Get number of brain structures. * * @return * Number of brain structure. */ int Brain::getNumberOfBrainStructures() const { return static_cast(m_brainStructures.size()); } /** * Add a brain structure. * * @param brainStructure * Brain structure to add. */ void Brain::addBrainStructure(BrainStructure* brainStructure) { m_brainStructures.push_back(brainStructure); } /** * Get a brain structure at specified index. * * @param indx * Index of brain structure. * @return * Pointer to brain structure at index. */ BrainStructure* Brain::getBrainStructure(const int32_t indx) { CaretAssertVectorIndex(m_brainStructures, indx); return m_brainStructures[indx]; } /** * Get a brain structure at specified index. * * @param indx * Index of brain structure. * @return * Pointer to brain structure at index. */ const BrainStructure* Brain::getBrainStructure(const int32_t indx) const { CaretAssertVectorIndex(m_brainStructures, indx); return m_brainStructures[indx]; } /** * Find, and possibly create, a brain structure that * models the specified structure. * * @param structure * The desired structure. * @param createIfNotFound * If there is not a matching brain structure, create one. * @return * Pointer to brain structure or NULL if no match. */ BrainStructure* Brain::getBrainStructure(StructureEnum::Enum structure, bool createIfNotFound) { for (std::vector::iterator iter = m_brainStructures.begin(); iter != m_brainStructures.end(); iter++) { BrainStructure* bs = *iter; if (bs->getStructure() == structure) { return bs; } } if (createIfNotFound) { BrainStructure* bs = new BrainStructure(this, structure); m_brainStructures.push_back(bs); return bs; } return NULL; } /** * Increment and return the duplicate counter for the given data file type. * * @param dataFileType * Type of data file. * @return * Next duplicate counter for the file type. */ int32_t Brain::getDuplicateFileNameCounterForFileType(const DataFileTypeEnum::Enum dataFileType) { int32_t counterValue = 0; std::map::iterator duplicateCounterIter = m_duplicateFileNameCounter.find(dataFileType); if (duplicateCounterIter != m_duplicateFileNameCounter.end()) { counterValue = duplicateCounterIter->second; } /* * Extremely unlikely that that this will happen */ if (counterValue == std::numeric_limits::max()) { counterValue = 0; } ++counterValue; m_duplicateFileNameCounter[dataFileType] = counterValue; // m_duplicateFileNameCounter.insert(std::make_pair(dataFileType, // counterValue)); return counterValue; } /** * Reset the duplicate file name counter for all data file types. In some * instances, the scene file counter is not altered and needs to be * preserved. * * @param preserveSceneFileCounter * If true, do not reset the scene file duplicate counter. */ void Brain::resetDuplicateFileNameCounter(const bool preserveSceneFileCounter) { int32_t sceneDuplicateCounter = -1; if (preserveSceneFileCounter) { std::map::iterator sceneDuplicateIter = m_duplicateFileNameCounter.find(DataFileTypeEnum::SCENE); if (sceneDuplicateIter != m_duplicateFileNameCounter.end()) { sceneDuplicateCounter = sceneDuplicateIter->second; } } m_duplicateFileNameCounter.clear(); if (sceneDuplicateCounter > 0) { m_duplicateFileNameCounter.insert(std::make_pair(DataFileTypeEnum::SCENE, sceneDuplicateCounter)); } } /** * Reset the brain structure. * @param keepSceneFiles * Status of keeping scene files. * @param keepSpecFile * Status of keeping spec file. */ void Brain::resetBrain(const ResetBrainKeepSceneFiles keepSceneFiles, const ResetBrainKeepSpecFile keepSpecFile) { m_isSpecFileBeingRead = false; /* * Clear the counters used to prevent duplicate file names. */ resetDuplicateFileNameCounter(keepSceneFiles); int num = getNumberOfBrainStructures(); for (int32_t i = 0; i < num; i++) { delete m_brainStructures[i]; } for (std::vector::iterator vfi = m_volumeFiles.begin(); vfi != m_volumeFiles.end(); vfi++) { VolumeFile* vf = *vfi; delete vf; } m_volumeFiles.clear(); m_brainStructures.clear(); for (std::vector::iterator bfi = m_borderFiles.begin(); bfi != m_borderFiles.end(); bfi++) { BorderFile* bf = *bfi; delete bf; } m_borderFiles.clear(); for (std::vector::iterator ffi = m_fociFiles.begin(); ffi != m_fociFiles.end(); ffi++) { FociFile* ff = *ffi; delete ff; } m_fociFiles.clear(); for (std::vector::iterator ifi = m_imageFiles.begin(); ifi != m_imageFiles.end(); ifi++) { ImageFile* img = *ifi; delete img; } m_imageFiles.clear(); for (std::vector::iterator cdsfi = m_connectivityDataSeriesFiles.begin(); cdsfi != m_connectivityDataSeriesFiles.end(); cdsfi++) { CiftiBrainordinateDataSeriesFile* cbdsf = *cdsfi; delete cbdsf; } m_connectivityDataSeriesFiles.clear(); for (std::vector::iterator clfi = m_connectivityDenseLabelFiles.begin(); clfi != m_connectivityDenseLabelFiles.end(); clfi++) { CiftiBrainordinateLabelFile* clf = *clfi; delete clf; } m_connectivityDenseLabelFiles.clear(); for (std::vector::iterator clfi = m_connectivityMatrixDenseFiles.begin(); clfi != m_connectivityMatrixDenseFiles.end(); clfi++) { CiftiConnectivityMatrixDenseFile* clf = *clfi; delete clf; } m_connectivityMatrixDenseFiles.clear(); for (std::vector::iterator clfi = m_connectivityMatrixDenseParcelFiles.begin(); clfi != m_connectivityMatrixDenseParcelFiles.end(); clfi++) { CiftiConnectivityMatrixDenseParcelFile* clf = *clfi; delete clf; } m_connectivityMatrixDenseParcelFiles.clear(); for (std::vector::iterator clfi = m_connectivityDenseScalarFiles.begin(); clfi != m_connectivityDenseScalarFiles.end(); clfi++) { CiftiBrainordinateScalarFile* clf = *clfi; delete clf; } m_connectivityDenseScalarFiles.clear(); for (std::vector::iterator clfi = m_connectivityParcelSeriesFiles.begin(); clfi != m_connectivityParcelSeriesFiles.end(); clfi++) { CiftiParcelSeriesFile* pdsf = *clfi; delete pdsf; } m_connectivityParcelSeriesFiles.clear(); for (std::vector::iterator cpfi = m_connectivityParcelLabelFiles.begin(); cpfi != m_connectivityParcelLabelFiles.end(); cpfi++) { CiftiParcelLabelFile* plf = *cpfi; delete plf; } m_connectivityParcelLabelFiles.clear(); for (std::vector::iterator clfi = m_connectivityParcelScalarFiles.begin(); clfi != m_connectivityParcelScalarFiles.end(); clfi++) { CiftiParcelScalarFile* psf = *clfi; delete psf; } m_connectivityParcelScalarFiles.clear(); for (std::vector::iterator clfi = m_connectivityScalarDataSeriesFiles.begin(); clfi != m_connectivityScalarDataSeriesFiles.end(); clfi++) { CiftiScalarDataSeriesFile* psf = *clfi; delete psf; } m_connectivityScalarDataSeriesFiles.clear(); for (std::vector::iterator clfi = m_connectivityFiberOrientationFiles.begin(); clfi != m_connectivityFiberOrientationFiles.end(); clfi++) { CiftiFiberOrientationFile* clf = *clfi; delete clf; } m_connectivityFiberOrientationFiles.clear(); for (std::vector::iterator clfi = m_connectivityFiberTrajectoryFiles.begin(); clfi != m_connectivityFiberTrajectoryFiles.end(); clfi++) { CiftiFiberTrajectoryFile* clf = *clfi; delete clf; } m_connectivityFiberTrajectoryFiles.clear(); for (std::vector::iterator clfi = m_connectivityMatrixParcelFiles.begin(); clfi != m_connectivityMatrixParcelFiles.end(); clfi++) { CiftiConnectivityMatrixParcelFile* clf = *clfi; delete clf; } m_connectivityMatrixParcelFiles.clear(); for (std::vector::iterator clfi = m_connectivityMatrixParcelDenseFiles.begin(); clfi != m_connectivityMatrixParcelDenseFiles.end(); clfi++) { CiftiConnectivityMatrixParcelDenseFile* clf = *clfi; delete clf; } m_connectivityMatrixParcelDenseFiles.clear(); if (m_paletteFile != NULL) { delete m_paletteFile; } m_paletteFile = new PaletteFile(); m_paletteFile->setFileName(convertFilePathNameToAbsolutePathName(m_paletteFile->getFileName())); m_paletteFile->clearModified(); m_fiberOrientationSamplesLoader->reset(); switch (keepSceneFiles) { case RESET_BRAIN_KEEP_SCENE_FILES_NO: for (std::vector::iterator sfi = m_sceneFiles.begin(); sfi != m_sceneFiles.end(); sfi++) { SceneFile* sf = *sfi; delete sf; } m_sceneFiles.clear(); break; case RESET_BRAIN_KEEP_SCENE_FILES_YES: break; } switch (keepSpecFile) { case RESET_BRAIN_KEEP_SPEC_FILE_NO: m_specFile->clear(); m_specFile->setFileName(""); m_specFile->clearModified(); break; case RESET_BRAIN_KEEP_SPEC_FILE_YES: break; } for (std::vector::iterator iter = m_displayProperties.begin(); iter != m_displayProperties.end(); iter++) { (*iter)->reset(); } m_identificationManager->removeAllIdentifiedItems(); m_selectionManager->reset(); m_selectionManager->setLastSelectedItem(NULL); m_brainordinateHighlightRegionOfInterest->clear(); if (m_modelChart != NULL) { m_modelChart->reset(); } updateAfterFilesAddedOrRemoved(); EventManager::get()->sendEvent(EventBrainReset(this).getPointer()); } /** * Reset the brain structure. */ void Brain::resetBrain() { resetBrain(RESET_BRAIN_KEEP_SCENE_FILES_NO, RESET_BRAIN_KEEP_SPEC_FILE_NO); } /** * Reset the brain structure but keep spec and scene files. */ void Brain::resetBrainKeepSceneFiles() { /* * Save all of the non-modified files so that loading * of them can be avoided later */ std::vector allFiles; getAllDataFiles(allFiles); m_nonModifiedFilesForRestoringScene.clear(); for (std::vector::iterator iter = allFiles.begin(); iter != allFiles.end(); iter++) { CaretDataFile* caretDataFile = *iter; if (caretDataFile->isModified()) { continue; } const DataFileTypeEnum::Enum dataFileType = caretDataFile->getDataFileType(); bool keepFileFlag = true; switch (dataFileType) { case DataFileTypeEnum::BORDER: break; case DataFileTypeEnum::CONNECTIVITY_DENSE: break; case DataFileTypeEnum::CONNECTIVITY_DENSE_LABEL: break; case DataFileTypeEnum::CONNECTIVITY_DENSE_PARCEL: break; case DataFileTypeEnum::CONNECTIVITY_DENSE_SCALAR: break; case DataFileTypeEnum::CONNECTIVITY_DENSE_TIME_SERIES: break; case DataFileTypeEnum::CONNECTIVITY_FIBER_ORIENTATIONS_TEMPORARY: break; case DataFileTypeEnum::CONNECTIVITY_FIBER_TRAJECTORY_TEMPORARY: break; case DataFileTypeEnum::CONNECTIVITY_PARCEL: break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_DENSE: break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_LABEL: break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_SCALAR: break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_SERIES: break; case DataFileTypeEnum::CONNECTIVITY_SCALAR_DATA_SERIES: break; case DataFileTypeEnum::FOCI: break; case DataFileTypeEnum::IMAGE: break; case DataFileTypeEnum::LABEL: break; case DataFileTypeEnum::METRIC: break; case DataFileTypeEnum::PALETTE: keepFileFlag = false; break; case DataFileTypeEnum::RGBA: break; case DataFileTypeEnum::SCENE: keepFileFlag = false; break; case DataFileTypeEnum::SPECIFICATION: keepFileFlag = false; break; case DataFileTypeEnum::SURFACE: break; case DataFileTypeEnum::UNKNOWN: keepFileFlag = false; break; case DataFileTypeEnum::VOLUME: break; } if (keepFileFlag) { if (removeWithoutDeleteDataFile(caretDataFile)) { m_nonModifiedFilesForRestoringScene.push_back(caretDataFile); } } } resetBrain(RESET_BRAIN_KEEP_SCENE_FILES_YES, RESET_BRAIN_KEEP_SPEC_FILE_NO); } /** * Copy all display properties from the source tab to the target tab. * @param sourceTabIndex * Index of source tab. * @param targetTabIndex * Index of target tab. */ void Brain::copyDisplayProperties(const int32_t sourceTabIndex, const int32_t targetTabIndex) { for (std::vector::iterator iter = m_displayProperties.begin(); iter != m_displayProperties.end(); iter++) { (*iter)->copyDisplayProperties(sourceTabIndex, targetTabIndex); } } /** * Read a surface file. * * @param fileMode * Mode for file adding, reading, or reloading. * @param caretDataFile * File that is added or reloaded (MUST NOT BE NULL). If NULL, * the mode must be READING. * @param filename * Name of the file. * @param structureIn * Structure of label file. * @throws DataFileException * If reading failed. * @return Pointer to file that was read. */ Surface* Brain::addReadOrReloadSurfaceFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename, const StructureEnum::Enum structureIn, const bool markDataFileAsModified) { Surface* surface = NULL; StructureEnum::Enum structure = StructureEnum::INVALID; if (caretDataFile != NULL) { surface = dynamic_cast(caretDataFile); CaretAssert(surface); /* * Need structure in case file reloading fails */ structure = surface->getStructure(); } else { surface = new Surface(); } bool addFlag = false; bool readFlag = false; bool reloadFlag = false; switch (fileMode) { case FILE_MODE_ADD: addFlag = true; break; case FILE_MODE_READ: addFlag = true; readFlag = true; break; case FILE_MODE_RELOAD: readFlag = true; reloadFlag = true; break; } if (readFlag) { try { try { surface->readFile(filename); } catch (const std::bad_alloc&) { /* * This DataFileException will be caught * in the outer try/catch and it will * clean up to avoid memory leaks. */ throw DataFileException(filename, CaretDataFileHelper::createBadAllocExceptionMessage(filename)); } } catch (const DataFileException& dfe) { if (reloadFlag) { BrainStructure* bs = getBrainStructure(structure, true); if (bs != NULL) { bs->removeWithoutDeleteDataFile(surface); } } delete surface; throw dfe; } } if (addFlag) { } bool fileIsModified = false; if (structureIn != StructureEnum::INVALID) { if (surface->getStructure() != structureIn) { surface->setStructure(structureIn); fileIsModified = markDataFileAsModified; } } structure = surface->getStructure(); if (structure == StructureEnum::INVALID) { if (caretDataFile == NULL) { delete surface; } DataFileException e(filename, "Structure is not valid."); e.setErrorInvalidStructure(true); CaretLogThrowing(e); throw e; } const bool brainStructureExists = (getBrainStructure(structure, false) != NULL); BrainStructure* bs = getBrainStructure(structure, true); if (bs != NULL) { /* * Initialize the overlays if the brain structure did NOT exist * AND a spec file is NOT being read */ bool initializeOverlaysFlag = false; if (brainStructureExists == false) { if (m_isSpecFileBeingRead == false) { initializeOverlaysFlag = true; } } if (addFlag) { std::vector allSurfaces; bs->getSurfaces(allSurfaces); updateDataFileNameIfDuplicate(allSurfaces, surface); } bs->addSurface(surface, addFlag, initializeOverlaysFlag); } else { if (caretDataFile == NULL) { delete surface; } AString message = "Failed to create a BrainStructure for surface with structure " + StructureEnum::toGuiName(structure) + "."; DataFileException e(filename, message); CaretLogThrowing(e); throw e; } surface->clearModified(); if (fileIsModified) { surface->setModified(); } return surface; } /** * Read a label file. * * @param fileMode * Mode for file adding, reading, or reloading. * @param caretDataFile * File that is added or reloaded (MUST NOT BE NULL). If NULL, * the mode must be READING. * @param filename * Name of the file. * @param structureIn * Structure of label file. * @throws DataFileException * If reading failed. * @return Pointer to file that was read. */ LabelFile* Brain::addReadOrReloadLabelFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename, const StructureEnum::Enum structureIn, const bool markDataFileAsModified) { LabelFile* labelFile = NULL; StructureEnum::Enum structure = StructureEnum::INVALID; if (caretDataFile != NULL) { labelFile = dynamic_cast(caretDataFile); CaretAssert(labelFile); /* * Need structure in case file reloading fails */ structure = labelFile->getStructure(); } else { labelFile = new LabelFile(); } bool addFlag = false; bool readFlag = false; bool reloadFlag = false; switch (fileMode) { case FILE_MODE_ADD: addFlag = true; break; case FILE_MODE_READ: addFlag = true; readFlag = true; break; case FILE_MODE_RELOAD: readFlag = true; reloadFlag = true; break; } if (readFlag) { try { try { labelFile->readFile(filename); } catch (const std::bad_alloc&) { /* * This DataFileException will be caught * in the outer try/catch and it will * clean up to avoid memory leaks. */ throw DataFileException(filename, CaretDataFileHelper::createBadAllocExceptionMessage(filename)); } } catch (const DataFileException& dfe) { if (reloadFlag) { BrainStructure* bs = getBrainStructure(structure, true); if (bs != NULL) { bs->removeWithoutDeleteDataFile(labelFile); } } delete labelFile; throw dfe; } } if (addFlag) { } bool fileIsModified = false; if (structureIn != StructureEnum::INVALID) { if (labelFile->getStructure() != structureIn) { labelFile->setStructure(structureIn); fileIsModified = markDataFileAsModified; } } structure = labelFile->getStructure(); if (structure == StructureEnum::INVALID) { if (caretDataFile == NULL) { delete labelFile; } DataFileException e(filename, "Structure is not valid."); e.setErrorInvalidStructure(true); CaretLogThrowing(e); throw e; } BrainStructure* bs = getBrainStructure(structure, false); if (bs != NULL) { try { if (addFlag) { std::vector allLabelFiles; bs->getLabelFiles(allLabelFiles); updateDataFileNameIfDuplicate(allLabelFiles, labelFile); } bs->addLabelFile(labelFile, addFlag); } catch (const DataFileException& e) { if (caretDataFile == NULL) { delete labelFile; } throw e; } } else { if (caretDataFile == NULL) { delete labelFile; } AString message = "Must read a surface with structure " + StructureEnum::toGuiName(structure) + " before reading its label files."; DataFileException e(filename, message); CaretLogThrowing(e); throw e; } labelFile->clearModified(); if (fileIsModified) { labelFile->setModified(); } return labelFile; } /** * Read a metric file. * * @param fileMode * Mode for file adding, reading, or reloading. * @param caretDataFile * File that is added or reloaded (MUST NOT BE NULL). If NULL, * the mode must be READING. * @param filename * Name of the file. * @param structureIn * Structure of label file. * @throws DataFileException * If reading failed. * @return Pointer to file that was read. */ MetricFile* Brain::addReadOrReloadMetricFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename, const StructureEnum::Enum structureIn, const bool markDataFileAsModified) { MetricFile* metricFile = NULL; StructureEnum::Enum structure = StructureEnum::INVALID; if (caretDataFile != NULL) { metricFile = dynamic_cast(caretDataFile); CaretAssert(metricFile); /* * Need structure in case file reloading fails */ structure = metricFile->getStructure(); } else { metricFile = new MetricFile(); } bool addFlag = false; bool readFlag = false; bool reloadFlag = false; switch (fileMode) { case FILE_MODE_ADD: addFlag = true; break; case FILE_MODE_READ: addFlag = true; readFlag = true; break; case FILE_MODE_RELOAD: readFlag = true; reloadFlag = true; break; } if (readFlag) { try { try { metricFile->readFile(filename); } catch (const std::bad_alloc&) { /* * This DataFileException will be caught * in the outer try/catch and it will * clean up to avoid memory leaks. */ throw DataFileException(filename, CaretDataFileHelper::createBadAllocExceptionMessage(filename)); } } catch (const DataFileException& dfe) { if (reloadFlag) { BrainStructure* bs = getBrainStructure(structure, true); if (bs != NULL) { bs->removeWithoutDeleteDataFile(metricFile); } } delete metricFile; throw dfe; } } if (addFlag) { } bool fileIsModified = false; if (structureIn != StructureEnum::INVALID) { if (metricFile->getStructure() != structureIn) { metricFile->setStructure(structureIn); fileIsModified = markDataFileAsModified; } } structure = metricFile->getStructure(); if (structure == StructureEnum::INVALID) { if (caretDataFile == NULL) { delete metricFile; } DataFileException e(filename, "Structure is not valid."); e.setErrorInvalidStructure(true); CaretLogThrowing(e); throw e; } BrainStructure* bs = getBrainStructure(structure, false); if (bs != NULL) { try { if (addFlag) { std::vector allMetricFiles; bs->getMetricFiles(allMetricFiles); updateDataFileNameIfDuplicate(allMetricFiles, metricFile); } bs->addMetricFile(metricFile, addFlag); } catch (const DataFileException& e) { if (caretDataFile == NULL) { delete metricFile; } throw e; } } else { if (caretDataFile == NULL) { delete metricFile; } AString message = "Must read a surface with structure " + StructureEnum::toGuiName(structure) + " before reading its metric files."; DataFileException e(filename, message); CaretLogThrowing(e); throw e; } metricFile->clearModified(); if (fileIsModified) { metricFile->setModified(); } return metricFile; } /** * Read an RGBA file. * * @param fileMode * Mode for file adding, reading, or reloading. * @param caretDataFile * File that is added or reloaded (MUST NOT BE NULL). If NULL, * the mode must be READING. * @param filename * Name of the file. * @param structureIn * Structure of label file. * @throws DataFileException * If reading failed. * @return Pointer to file that was read. */ RgbaFile* Brain::addReadOrReloadRgbaFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename, const StructureEnum::Enum structureIn, const bool markDataFileAsModified) { RgbaFile* rgbaFile = NULL; StructureEnum::Enum structure = StructureEnum::INVALID; if (caretDataFile != NULL) { rgbaFile = dynamic_cast(caretDataFile); CaretAssert(rgbaFile); /* * Need structure in case file reloading fails */ structure = rgbaFile->getStructure(); } else { rgbaFile = new RgbaFile(); } bool addFlag = false; bool readFlag = false; bool reloadFlag = false; switch (fileMode) { case FILE_MODE_ADD: addFlag = true; break; case FILE_MODE_READ: addFlag = true; readFlag = true; break; case FILE_MODE_RELOAD: readFlag = true; reloadFlag = true; break; } if (readFlag) { try { try { rgbaFile->readFile(filename); } catch (const std::bad_alloc&) { /* * This DataFileException will be caught * in the outer try/catch and it will * clean up to avoid memory leaks. */ throw DataFileException(filename, CaretDataFileHelper::createBadAllocExceptionMessage(filename)); } } catch (const DataFileException& dfe) { if (reloadFlag) { BrainStructure* bs = getBrainStructure(structure, true); if (bs != NULL) { bs->removeWithoutDeleteDataFile(rgbaFile); } } delete rgbaFile; throw dfe; } } if (addFlag) { } bool fileIsModified = false; if (structureIn != StructureEnum::INVALID) { if (rgbaFile->getStructure() != structureIn) { rgbaFile->setStructure(structureIn); fileIsModified = markDataFileAsModified; } } structure = rgbaFile->getStructure(); if (structure == StructureEnum::INVALID) { if (caretDataFile == NULL) { delete rgbaFile; } DataFileException e(filename, "Structure is not valid."); e.setErrorInvalidStructure(true); CaretLogThrowing(e); throw e; } BrainStructure* bs = getBrainStructure(structure, false); if (bs != NULL) { try { if (addFlag) { std::vector allRgbaFiles; bs->getRgbaFiles(allRgbaFiles); updateDataFileNameIfDuplicate(allRgbaFiles, rgbaFile); } bs->addRgbaFile(rgbaFile, addFlag); } catch (const DataFileException& e) { if (caretDataFile == NULL) { delete rgbaFile; } throw e; } } else { if (caretDataFile == NULL) { delete rgbaFile; } AString message = "Must read a surface with structure " + StructureEnum::toGuiName(structure) + " before reading its RGBA files."; DataFileException e(filename, message); CaretLogThrowing(e); throw e; } rgbaFile->clearModified(); if (fileIsModified) { rgbaFile->setModified(); } return rgbaFile; } /** * Read a volume file. * * @param fileMode * Mode for file adding, reading, or reloading. * @param caretDataFile * File that is added or reloaded (MUST NOT BE NULL). If NULL, * the mode must be READING. * @param filename * Name of the file. * @return * File that was read. * @throws DataFileException * If reading failed. */ VolumeFile* Brain::addReadOrReloadVolumeFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename) { VolumeFile* vf = NULL; if (caretDataFile != NULL) { vf = dynamic_cast(caretDataFile); CaretAssert(vf); } else { vf = new VolumeFile(); } bool addFlag = false; bool readFlag = false; switch (fileMode) { case FILE_MODE_ADD: addFlag = true; break; case FILE_MODE_READ: addFlag = true; readFlag = true; break; case FILE_MODE_RELOAD: readFlag = true; break; } if (readFlag) { try { try { vf->readFile(filename); } catch (const std::bad_alloc&) { /* * This DataFileException will be caught * in the outer try/catch and it will * clean up to avoid memory leaks. */ throw DataFileException(filename, CaretDataFileHelper::createBadAllocExceptionMessage(filename)); } } catch (const DataFileException& e) { if (caretDataFile != NULL) { removeAndDeleteDataFile(caretDataFile); } else { delete vf; } throw e; } } vf->clearModified(); ElapsedTimer timer; timer.start(); vf->updateScalarColoringForAllMaps(m_paletteFile); CaretLogInfo("Time to color volume data is " + AString::number(timer.getElapsedTimeSeconds(), 'f', 3) + " seconds."); if (addFlag) { updateDataFileNameIfDuplicate(m_volumeFiles, vf); m_volumeFiles.push_back(vf); } return vf; } /** * @return Number of volume files. */ int32_t Brain::getNumberOfVolumeFiles() const { return m_volumeFiles.size(); } /** * Get the volume file at the given index. * @param volumeFileIndex * Index of the volume file. * @return * Volume file at the given index. */ VolumeFile* Brain::getVolumeFile(const int32_t volumeFileIndex) { CaretAssertVectorIndex(m_volumeFiles, volumeFileIndex); return m_volumeFiles[volumeFileIndex]; } /** * Get the volume file at the given index. * @param volumeFileIndex * Index of the volume file. * @return * Volume file at the given index. */ const VolumeFile* Brain::getVolumeFile(const int32_t volumeFileIndex) const { CaretAssertVectorIndex(m_volumeFiles, volumeFileIndex); return m_volumeFiles[volumeFileIndex]; } /** * Read a border file. * * @param fileMode * Mode for file adding, reading, or reloading. * @param caretDataFile * File that is added or reloaded (MUST NOT BE NULL). If NULL, * the mode must be READING. * @param filename * Name of the file. * @return * File that was read. * @throws DataFileException * If reading failed. */ BorderFile* Brain::addReadOrReloadBorderFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename) { BorderFile* bf = NULL; if (caretDataFile != NULL) { bf = dynamic_cast(caretDataFile); CaretAssert(bf); } else { bf = new BorderFile(); } bool addFlag = false; bool readFlag = false; switch (fileMode) { case FILE_MODE_ADD: addFlag = true; break; case FILE_MODE_READ: addFlag = true; readFlag = true; break; case FILE_MODE_RELOAD: readFlag = true; break; } if (readFlag) { try { try { bf->readFile(filename); } catch (const std::bad_alloc&) { /* * This DataFileException will be caught * in the outer try/catch and it will * clean up to avoid memory leaks. */ throw DataFileException(filename, CaretDataFileHelper::createBadAllocExceptionMessage(filename)); } /* * Create a map of structure to number of nodes */ std::map structureToNodeCountMap; for (std::vector::iterator bsIter = m_brainStructures.begin(); bsIter != m_brainStructures.end(); bsIter++) { const BrainStructure* bs = *bsIter; CaretAssert(bs); structureToNodeCountMap.insert(std::make_pair(bs->getStructure(), bs->getNumberOfNodes())); } bf->updateNumberOfNodesIfSingleStructure(structureToNodeCountMap); } catch (DataFileException& dfe) { if (caretDataFile != NULL) { removeAndDeleteDataFile(caretDataFile); } else { delete bf; } throw dfe; } } if (addFlag) { updateDataFileNameIfDuplicate(m_borderFiles, bf); m_borderFiles.push_back(bf); } return bf; } /** * Read a foci file. * * @param fileMode * Mode for file adding, reading, or reloading. * @param caretDataFile * File that is added or reloaded (MUST NOT BE NULL). If NULL, * the mode must be READING. * @param filename * Name of the file. * @throws DataFileException * If reading failed. */ FociFile* Brain::addReadOrReloadFociFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename) { FociFile* ff = NULL; if (caretDataFile != NULL) { ff = dynamic_cast(caretDataFile); CaretAssert(ff); } else { ff = new FociFile(); } bool addFlag = false; bool readFlag = false; switch (fileMode) { case FILE_MODE_ADD: addFlag = true; break; case FILE_MODE_READ: addFlag = true; readFlag = true; break; case FILE_MODE_RELOAD: readFlag = true; break; } if (readFlag) { try { try { ff->readFile(filename); } catch (const std::bad_alloc&) { /* * This DataFileException will be caught * in the outer try/catch and it will * clean up to avoid memory leaks. */ throw DataFileException(filename, CaretDataFileHelper::createBadAllocExceptionMessage(filename)); } } catch (DataFileException& dfe) { if (caretDataFile != NULL) { removeAndDeleteDataFile(caretDataFile); } else { delete ff; } throw dfe; } } if (addFlag) { updateDataFileNameIfDuplicate(m_fociFiles, ff); m_fociFiles.push_back(ff); } return ff; } /** * Read a foci file. * * @param fileMode * Mode for file adding, reading, or reloading. * @param caretDataFile * File that is added or reloaded (MUST NOT BE NULL). If NULL, * the mode must be READING. * @param filename * Name of the file. * @throws DataFileException * If reading failed. */ ImageFile* Brain::addReadOrReloadImageFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename) { ImageFile* imageFile = NULL; if (caretDataFile != NULL) { imageFile = dynamic_cast(caretDataFile); CaretAssert(imageFile); } else { imageFile = new ImageFile(); } bool addFlag = false; bool readFlag = false; switch (fileMode) { case FILE_MODE_ADD: addFlag = true; break; case FILE_MODE_READ: addFlag = true; readFlag = true; break; case FILE_MODE_RELOAD: readFlag = true; break; } if (readFlag) { try { try { imageFile->readFile(filename); } catch (const std::bad_alloc&) { /* * This DataFileException will be caught * in the outer try/catch and it will * clean up to avoid memory leaks. */ throw DataFileException(filename, CaretDataFileHelper::createBadAllocExceptionMessage(filename)); } } catch (DataFileException& dfe) { if (caretDataFile != NULL) { removeAndDeleteDataFile(caretDataFile); } else { delete imageFile; } throw dfe; } } if (addFlag) { updateDataFileNameIfDuplicate(m_imageFiles, imageFile); m_imageFiles.push_back(imageFile); } return imageFile; } /** * Validate a CIFTI Mappable Data File. * A file is valid if its surface mappings match the loaded surfaces. * * @param ciftiMapFile * File examined for validity. * @throws DataFileException * If the file is found to be incompatible with the loaded surfaces. */ void Brain::validateCiftiMappableDataFile(const CiftiMappableDataFile* ciftiMapFile) const { const int32_t numBrainStructures = getNumberOfBrainStructures(); for (int32_t i = 0; i < numBrainStructures; i++) { const StructureEnum::Enum structure = getBrainStructure(i)->getStructure(); const int numNodes = getBrainStructure(i)->getNumberOfNodes(); const int numConnNodes = ciftiMapFile->getMappingSurfaceNumberOfNodes(structure); if (numConnNodes > 0) { if (numNodes != numConnNodes) { AString msg = ("The CIFTI file contains " + AString::number(numConnNodes) + " nodes for structure " + StructureEnum::toGuiName(structure) + " but the corresponding surface brain structure contains " + AString::number(numNodes) + " nodes."); throw DataFileException(ciftiMapFile->getFileName(), msg); } } } } /** * Read a connectivity matrix dense file. * * @param fileMode * Mode for file adding, reading, or reloading. * @param caretDataFile * File that is added or reloaded (MUST NOT BE NULL). If NULL, * the mode must be READING. * @param filename * Name of the file. * @return * File that was read. * @throws DataFileException * If reading failed. */ CiftiConnectivityMatrixDenseFile* Brain::addReadOrReloadConnectivityDenseFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename) { CiftiConnectivityMatrixDenseFile* cmdf = NULL; if (caretDataFile != NULL) { cmdf = dynamic_cast(caretDataFile); CaretAssert(cmdf); } else { cmdf = new CiftiConnectivityMatrixDenseFile(); } bool addFlag = false; bool readFlag = false; switch (fileMode) { case FILE_MODE_ADD: addFlag = true; break; case FILE_MODE_READ: addFlag = true; readFlag = true; break; case FILE_MODE_RELOAD: readFlag = true; break; } if (readFlag) { try { try { cmdf->readFile(filename); } catch (const std::bad_alloc&) { /* * This DataFileException will be caught * in the outer try/catch and it will * clean up to avoid memory leaks. */ throw DataFileException(filename, CaretDataFileHelper::createBadAllocExceptionMessage(filename)); } cmdf->clearModified(); validateCiftiMappableDataFile(cmdf); } catch (const DataFileException& dfe) { if (caretDataFile != NULL) { removeAndDeleteDataFile(caretDataFile); } else { delete cmdf; } throw dfe; } } if (addFlag) { updateDataFileNameIfDuplicate(m_connectivityMatrixDenseFiles, cmdf); m_connectivityMatrixDenseFiles.push_back(cmdf); } return cmdf; } /** * Read a connectivity dense label file. * * @param fileMode * Mode for file adding, reading, or reloading. * @param caretDataFile * File that is added or reloaded (MUST NOT BE NULL). If NULL, * the mode must be READING. * @param filename * Name of the file. * @return * File that was read. * @throws DataFileException * If reading failed. */ CiftiBrainordinateLabelFile* Brain::addReadOrReloadConnectivityDenseLabelFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename) { CiftiBrainordinateLabelFile* file = NULL; if (caretDataFile != NULL) { file = dynamic_cast(caretDataFile); CaretAssert(file); } else { file = new CiftiBrainordinateLabelFile(); } bool addFlag = false; bool readFlag = false; switch (fileMode) { case FILE_MODE_ADD: addFlag = true; break; case FILE_MODE_READ: addFlag = true; readFlag = true; break; case FILE_MODE_RELOAD: readFlag = true; break; } if (readFlag) { try { try { file->readFile(filename); } catch (const std::bad_alloc&) { /* * This DataFileException will be caught * in the outer try/catch and it will * clean up to avoid memory leaks. */ throw DataFileException(filename, CaretDataFileHelper::createBadAllocExceptionMessage(filename)); } validateCiftiMappableDataFile(file); } catch (const DataFileException& dfe) { if (caretDataFile != NULL) { removeAndDeleteDataFile(caretDataFile); } else { delete file; } throw dfe; } } if (addFlag) { updateDataFileNameIfDuplicate(m_connectivityDenseLabelFiles, file); m_connectivityDenseLabelFiles.push_back(file); } return file; } /** * Read a connectivity dense parcel file. * * @param fileMode * Mode for file adding, reading, or reloading. * @param caretDataFile * File that is added or reloaded (MUST NOT BE NULL). If NULL, * the mode must be READING. * @param filename * Name of the file. * @throws DataFileException * If reading failed. */ CiftiConnectivityMatrixDenseParcelFile* Brain::addReadOrReloadConnectivityMatrixDenseParcelFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename) { CiftiConnectivityMatrixDenseParcelFile* file = NULL; if (caretDataFile != NULL) { file = dynamic_cast(caretDataFile); CaretAssert(file); } else { file = new CiftiConnectivityMatrixDenseParcelFile(); } bool addFlag = false; bool readFlag = false; switch (fileMode) { case FILE_MODE_ADD: addFlag = true; break; case FILE_MODE_READ: addFlag = true; readFlag = true; break; case FILE_MODE_RELOAD: readFlag = true; break; } if (readFlag) { try { try { file->readFile(filename); } catch (const std::bad_alloc&) { /* * This DataFileException will be caught * in the outer try/catch and it will * clean up to avoid memory leaks. */ throw DataFileException(filename, CaretDataFileHelper::createBadAllocExceptionMessage(filename)); } validateCiftiMappableDataFile(file); } catch (const DataFileException& dfe) { if (caretDataFile != NULL) { removeAndDeleteDataFile(caretDataFile); } else { delete file; } throw dfe; } } if (addFlag) { updateDataFileNameIfDuplicate(m_connectivityMatrixDenseParcelFiles, file); m_connectivityMatrixDenseParcelFiles.push_back(file); } return file; } /** * Update the fiber orientation files assigned to matching * fiber trajectory files. This is typically called after * files are added or removed. */ void Brain::updateFiberTrajectoryMatchingFiberOrientationFiles() { for (std::vector::iterator iter = m_connectivityFiberTrajectoryFiles.begin(); iter != m_connectivityFiberTrajectoryFiles.end(); iter++) { CiftiFiberTrajectoryFile* trajFile = *iter; trajFile->updateMatchingFiberOrientationFileFromList(m_connectivityFiberOrientationFiles); } } /** * Read a connectivity dense scalar file. * * @param fileMode * Mode for file adding, reading, or reloading. * @param caretDataFile * File that is added or reloaded (MUST NOT BE NULL). If NULL, * the mode must be READING. * @param filename * Name of the file. * @throws DataFileException * If reading failed. */ CiftiBrainordinateScalarFile* Brain::addReadOrReloadConnectivityDenseScalarFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename) { CiftiBrainordinateScalarFile* clf = NULL; if (caretDataFile != NULL) { clf = dynamic_cast(caretDataFile); CaretAssert(clf); } else { clf = new CiftiBrainordinateScalarFile(); } bool addFlag = false; bool readFlag = false; switch (fileMode) { case FILE_MODE_ADD: addFlag = true; break; case FILE_MODE_READ: addFlag = true; readFlag = true; break; case FILE_MODE_RELOAD: readFlag = true; break; } if (readFlag) { try { try { clf->readFile(filename); } catch (const std::bad_alloc&) { /* * This DataFileException will be caught * in the outer try/catch and it will * clean up to avoid memory leaks. */ throw DataFileException(filename, CaretDataFileHelper::createBadAllocExceptionMessage(filename)); } validateCiftiMappableDataFile(clf); } catch (const DataFileException& dfe) { if (caretDataFile != NULL) { removeAndDeleteDataFile(caretDataFile); } else { delete clf; } throw dfe; } } if (addFlag) { updateDataFileNameIfDuplicate(m_connectivityDenseScalarFiles, clf); m_connectivityDenseScalarFiles.push_back(clf); } return clf; } /** * Read a connectivity parcel data series file. * * @param fileMode * Mode for file adding, reading, or reloading. * @param caretDataFile * File that is added or reloaded (MUST NOT BE NULL). If NULL, * the mode must be READING. * @param filename * Name of the file. * @throws DataFileException * If reading failed. */ CiftiParcelSeriesFile* Brain::addReadOrReloadConnectivityParcelSeriesFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename) { CiftiParcelSeriesFile* clf = NULL; if (caretDataFile != NULL) { clf = dynamic_cast(caretDataFile); CaretAssert(clf); } else { clf = new CiftiParcelSeriesFile(); } bool addFlag = false; bool readFlag = false; switch (fileMode) { case FILE_MODE_ADD: addFlag = true; break; case FILE_MODE_READ: addFlag = true; readFlag = true; break; case FILE_MODE_RELOAD: readFlag = true; break; } if (readFlag) { try { try { clf->readFile(filename); } catch (const std::bad_alloc&) { /* * This DataFileException will be caught * in the outer try/catch and it will * clean up to avoid memory leaks. */ throw DataFileException(filename, CaretDataFileHelper::createBadAllocExceptionMessage(filename)); } validateCiftiMappableDataFile(clf); } catch (const DataFileException& dfe) { if (caretDataFile != NULL) { removeAndDeleteDataFile(caretDataFile); } else { delete clf; } throw dfe; } } if (addFlag) { updateDataFileNameIfDuplicate(m_connectivityParcelSeriesFiles, clf); m_connectivityParcelSeriesFiles.push_back(clf); } return clf; } /** * Read a connectivity parcel label file. * * @param fileMode * Mode for file adding, reading, or reloading. * @param caretDataFile * File that is added or reloaded (MUST NOT BE NULL). If NULL, * the mode must be READING. * @param filename * Name of the file. * @throws DataFileException * If reading failed. */ CiftiParcelLabelFile* Brain::addReadOrReloadConnectivityParcelLabelFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename) { CiftiParcelLabelFile* clf = NULL; if (caretDataFile != NULL) { clf = dynamic_cast(caretDataFile); CaretAssert(clf); } else { clf = new CiftiParcelLabelFile(); } bool addFlag = false; bool readFlag = false; switch (fileMode) { case FILE_MODE_ADD: addFlag = true; break; case FILE_MODE_READ: addFlag = true; readFlag = true; break; case FILE_MODE_RELOAD: readFlag = true; break; } if (readFlag) { try { try { clf->readFile(filename); } catch (const std::bad_alloc&) { /* * This DataFileException will be caught * in the outer try/catch and it will * clean up to avoid memory leaks. */ throw DataFileException(filename, CaretDataFileHelper::createBadAllocExceptionMessage(filename)); } validateCiftiMappableDataFile(clf); } catch (const DataFileException& dfe) { if (caretDataFile != NULL) { removeAndDeleteDataFile(caretDataFile); } else { delete clf; } throw dfe; } } if (addFlag) { updateDataFileNameIfDuplicate(m_connectivityParcelLabelFiles, clf); m_connectivityParcelLabelFiles.push_back(clf); } return clf; } /** * Read a connectivity parcel scalar file. * * @param fileMode * Mode for file adding, reading, or reloading. * @param caretDataFile * File that is added or reloaded (MUST NOT BE NULL). If NULL, * the mode must be READING. * @param filename * Name of the file. * @throws DataFileException * If reading failed. */ CiftiParcelScalarFile* Brain::addReadOrReloadConnectivityParcelScalarFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename) { CiftiParcelScalarFile* clf = NULL; if (caretDataFile != NULL) { clf = dynamic_cast(caretDataFile); CaretAssert(clf); } else { clf = new CiftiParcelScalarFile(); } bool addFlag = false; bool readFlag = false; switch (fileMode) { case FILE_MODE_ADD: addFlag = true; break; case FILE_MODE_READ: addFlag = true; readFlag = true; break; case FILE_MODE_RELOAD: readFlag = true; break; } if (readFlag) { try { try { clf->readFile(filename); } catch (const std::bad_alloc&) { /* * This DataFileException will be caught * in the outer try/catch and it will * clean up to avoid memory leaks. */ throw DataFileException(filename, CaretDataFileHelper::createBadAllocExceptionMessage(filename)); } validateCiftiMappableDataFile(clf); } catch (const DataFileException& dfe) { if (caretDataFile != NULL) { removeAndDeleteDataFile(caretDataFile); } else { delete clf; } throw dfe; } } if (addFlag) { updateDataFileNameIfDuplicate(m_connectivityParcelScalarFiles, clf); m_connectivityParcelScalarFiles.push_back(clf); } return clf; } /** * Read a connectivity scalar data series file. * * @param fileMode * Mode for file adding, reading, or reloading. * @param caretDataFile * File that is added or reloaded (MUST NOT BE NULL). If NULL, * the mode must be READING. * @param filename * Name of the file. * @throws DataFileException * If reading failed. */ CiftiScalarDataSeriesFile* Brain::addReadOrReloadConnectivityScalarDataSeriesFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename) { CiftiScalarDataSeriesFile* clf = NULL; if (caretDataFile != NULL) { clf = dynamic_cast(caretDataFile); CaretAssert(clf); } else { clf = new CiftiScalarDataSeriesFile(); } bool addFlag = false; bool readFlag = false; switch (fileMode) { case FILE_MODE_ADD: addFlag = true; break; case FILE_MODE_READ: addFlag = true; readFlag = true; break; case FILE_MODE_RELOAD: readFlag = true; break; } if (readFlag) { try { try { clf->readFile(filename); } catch (const std::bad_alloc&) { /* * This DataFileException will be caught * in the outer try/catch and it will * clean up to avoid memory leaks. */ throw DataFileException(filename, CaretDataFileHelper::createBadAllocExceptionMessage(filename)); } validateCiftiMappableDataFile(clf); } catch (const DataFileException& dfe) { if (caretDataFile != NULL) { removeAndDeleteDataFile(caretDataFile); } else { delete clf; } throw dfe; } } if (addFlag) { updateDataFileNameIfDuplicate(m_connectivityScalarDataSeriesFiles, clf); m_connectivityScalarDataSeriesFiles.push_back(clf); } return clf; } /** * Find a cifti scalar file containing shape information. * * @param ciftiScalarShapeFileOut * Output CIFTI Scalar file that meets requirements (NULL if no matches. * @param shapeMapIndexOut * Output Index of map meeting requirements. */ void Brain::getCiftiShapeMap(CiftiBrainordinateScalarFile* &ciftiScalarShapeFileOut, int32_t& ciftiScalarhapeFileMapIndexOut, std::vector& ciftiScalarNotShapeFilesOut) const { ciftiScalarShapeFileOut = NULL; ciftiScalarhapeFileMapIndexOut = -1; ciftiScalarNotShapeFilesOut.clear(); CiftiBrainordinateScalarFile* depthMetricFile = NULL; int32_t depthMapIndex = -1; CiftiBrainordinateScalarFile* depthNamedMetricFile = NULL; CiftiBrainordinateScalarFile* curvatureMetricFile = NULL; int32_t curvatureMapIndex = -1; CiftiBrainordinateScalarFile* curvatureNamedMetricFile = NULL; CiftiBrainordinateScalarFile* shapeNamedMetricFile = NULL; CiftiBrainordinateScalarFile* sulcMetricFile = NULL; int32_t sulcMapIndex = -1; CiftiBrainordinateScalarFile* sulcNamedMetricFile = NULL; const int numFiles = static_cast(m_connectivityDenseScalarFiles.size()); for (int32_t i = 0; i < numFiles; i++) { CiftiBrainordinateScalarFile* cf = m_connectivityDenseScalarFiles[i]; const AString filename = cf->getFileNameNoPath().toLower(); const int32_t numMaps = cf->getNumberOfMaps(); for (int32_t j = 0; j < numMaps; j++) { const AString mapName = cf->getMapName(j).toLower(); if (mapName.contains("sulc")) { if (sulcMetricFile == NULL) { sulcMetricFile = cf; sulcMapIndex = j; } } else if (mapName.contains("depth")) { if (depthMetricFile == NULL) { depthMetricFile = cf; depthMapIndex = j; } } else if (mapName.contains("curv")) { if (curvatureMetricFile == NULL) { curvatureMetricFile = cf; curvatureMapIndex = j; } } } if (filename.contains("sulc")) { if (numMaps > 0) { sulcNamedMetricFile = cf; } } if (filename.contains("shape")) { if (numMaps > 0) { shapeNamedMetricFile = cf; } } if (filename.contains("curv")) { if (numMaps > 0) { curvatureNamedMetricFile = cf; } } if (filename.contains("depth")) { if (numMaps > 0) { depthNamedMetricFile = cf; } } } if (sulcMetricFile != NULL) { ciftiScalarShapeFileOut = sulcMetricFile; ciftiScalarhapeFileMapIndexOut = sulcMapIndex; } else if (depthMetricFile != NULL) { ciftiScalarShapeFileOut = depthMetricFile; ciftiScalarhapeFileMapIndexOut = depthMapIndex; } else if (curvatureMetricFile != NULL) { ciftiScalarShapeFileOut = curvatureMetricFile; ciftiScalarhapeFileMapIndexOut = curvatureMapIndex; } else if (sulcNamedMetricFile != NULL) { ciftiScalarShapeFileOut = sulcNamedMetricFile; ciftiScalarhapeFileMapIndexOut = 0; } else if (depthNamedMetricFile != NULL) { ciftiScalarShapeFileOut = depthNamedMetricFile; ciftiScalarhapeFileMapIndexOut = 0; } else if (curvatureNamedMetricFile != NULL) { ciftiScalarShapeFileOut = curvatureNamedMetricFile; ciftiScalarhapeFileMapIndexOut = 0; } else if (shapeNamedMetricFile != NULL) { ciftiScalarShapeFileOut = shapeNamedMetricFile; ciftiScalarhapeFileMapIndexOut = 0; } /* * Get all shape type files (NULLs okay) */ std::vector ciftiShapeFiles; ciftiShapeFiles.push_back(sulcMetricFile); ciftiShapeFiles.push_back(depthMetricFile); ciftiShapeFiles.push_back(curvatureMetricFile); ciftiShapeFiles.push_back(sulcNamedMetricFile); ciftiShapeFiles.push_back(depthNamedMetricFile); ciftiShapeFiles.push_back(curvatureNamedMetricFile); ciftiShapeFiles.push_back(shapeNamedMetricFile); /* * Find files that are NOT shape files */ for (int32_t i = 0; i < numFiles; i++) { CiftiBrainordinateScalarFile* cf = m_connectivityDenseScalarFiles[i]; if (std::find(ciftiShapeFiles.begin(), ciftiShapeFiles.end(), cf) == ciftiShapeFiles.end()) { ciftiScalarNotShapeFilesOut.push_back(cf); } } } /** * Read a connectivity fiber orientation file. * * @param fileMode * Mode for file adding, reading, or reloading. * @param caretDataFile * File that is added or reloaded (MUST NOT BE NULL). If NULL, * the mode must be READING. * @param filename * Name of the file. * @throws DataFileException * If reading failed. */ CiftiFiberOrientationFile* Brain::addReadOrReloadConnectivityFiberOrientationFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename) { CiftiFiberOrientationFile* cfof = NULL; if (caretDataFile != NULL) { cfof = dynamic_cast(caretDataFile); CaretAssert(cfof); } else { cfof = new CiftiFiberOrientationFile(); } bool addFlag = false; bool readFlag = false; switch (fileMode) { case FILE_MODE_ADD: addFlag = true; break; case FILE_MODE_READ: addFlag = true; readFlag = true; break; case FILE_MODE_RELOAD: readFlag = true; break; } if (readFlag) { try { try { cfof->readFile(filename); } catch (const std::bad_alloc&) { /* * This DataFileException will be caught * in the outer try/catch and it will * clean up to avoid memory leaks. */ throw DataFileException(filename, CaretDataFileHelper::createBadAllocExceptionMessage(filename)); } } catch (const DataFileException& dfe) { if (caretDataFile != NULL) { removeAndDeleteDataFile(caretDataFile); } else { delete cfof; } throw dfe; } } /* * If first fiber orientation file, default the above and below limits * to +/- one-half voxel size. */ if (m_connectivityFiberOrientationFiles.empty()) { float voxelSizes[3]; cfof->getVolumeSpacing(voxelSizes); float maxVoxelSize = std::max(std::fabs(voxelSizes[0]), std::max(std::fabs(voxelSizes[1]), std::fabs(voxelSizes[2]))); if (maxVoxelSize <= 0.0) { maxVoxelSize = 1.0; } const float aboveLimit = maxVoxelSize; const float belowLimit = -maxVoxelSize; m_displayPropertiesFiberOrientation->setAboveAndBelowLimitsForAll(aboveLimit, belowLimit); } if (addFlag) { updateDataFileNameIfDuplicate(m_connectivityFiberOrientationFiles, cfof); m_connectivityFiberOrientationFiles.push_back(cfof); } return cfof; } /** * Read a connectivity fiber trajectory file. * * @param fileMode * Mode for file adding, reading, or reloading. * @param caretDataFile * File that is added or reloaded (MUST NOT BE NULL). If NULL, * the mode must be READING. * @param filename * Name of the file. * @throws DataFileException * If reading failed. */ CiftiFiberTrajectoryFile* Brain::addReadOrReloadConnectivityFiberTrajectoryFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename) { CiftiFiberTrajectoryFile* cftf = NULL; if (caretDataFile != NULL) { cftf = dynamic_cast(caretDataFile); CaretAssert(cftf); } else { cftf = new CiftiFiberTrajectoryFile(); } bool addFlag = false; bool readFlag = false; switch (fileMode) { case FILE_MODE_ADD: addFlag = true; break; case FILE_MODE_READ: addFlag = true; readFlag = true; break; case FILE_MODE_RELOAD: readFlag = true; break; } if (readFlag) { try { try { cftf->readFile(filename); } catch (const std::bad_alloc&) { /* * This DataFileException will be caught * in the outer try/catch and it will * clean up to avoid memory leaks. */ throw DataFileException(filename, CaretDataFileHelper::createBadAllocExceptionMessage(filename)); } } catch (const DataFileException& dfe) { if (caretDataFile != NULL) { removeAndDeleteDataFile(caretDataFile); } else { delete cftf; } throw dfe; } } if (addFlag) { updateDataFileNameIfDuplicate(m_connectivityFiberTrajectoryFiles, cftf); m_connectivityFiberTrajectoryFiles.push_back(cftf); } return cftf; } /** * Read a connectivity parcel file. * * @param fileMode * Mode for file adding, reading, or reloading. * @param caretDataFile * File that is added or reloaded (MUST NOT BE NULL). If NULL, * the mode must be READING. * @param filename * Name of the file. * @throws DataFileException * If reading failed. */ CiftiConnectivityMatrixParcelFile* Brain::addReadOrReloadConnectivityMatrixParcelFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename) { CiftiConnectivityMatrixParcelFile* file = NULL; if (caretDataFile != NULL) { file = dynamic_cast(caretDataFile); CaretAssert(file); } else { file = new CiftiConnectivityMatrixParcelFile(); } bool addFlag = false; bool readFlag = false; switch (fileMode) { case FILE_MODE_ADD: addFlag = true; break; case FILE_MODE_READ: addFlag = true; readFlag = true; break; case FILE_MODE_RELOAD: readFlag = true; break; } if (readFlag) { try { try { file->readFile(filename); } catch (const std::bad_alloc&) { /* * This DataFileException will be caught * in the outer try/catch and it will * clean up to avoid memory leaks. */ throw DataFileException(filename, CaretDataFileHelper::createBadAllocExceptionMessage(filename)); } validateCiftiMappableDataFile(file); } catch (const DataFileException& dfe) { if (caretDataFile != NULL) { removeAndDeleteDataFile(caretDataFile); } else { delete file; } throw dfe; } } if (addFlag) { updateDataFileNameIfDuplicate(m_connectivityMatrixParcelFiles, file); m_connectivityMatrixParcelFiles.push_back(file); } return file; } /** * Read a connectivity parcel dense file. * * @param fileMode * Mode for file adding, reading, or reloading. * @param caretDataFile * File that is added or reloaded (MUST NOT BE NULL). If NULL, * the mode must be READING. * @param filename * Name of the file. * @throws DataFileException * If reading failed. */ CiftiConnectivityMatrixParcelDenseFile* Brain::addReadOrReloadConnectivityMatrixParcelDenseFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename) { CiftiConnectivityMatrixParcelDenseFile* file = NULL; if (caretDataFile != NULL) { file = dynamic_cast(caretDataFile); CaretAssert(file); } else { file = new CiftiConnectivityMatrixParcelDenseFile(); } bool addFlag = false; bool readFlag = false; switch (fileMode) { case FILE_MODE_ADD: addFlag = true; break; case FILE_MODE_READ: addFlag = true; readFlag = true; break; case FILE_MODE_RELOAD: readFlag = true; break; } if (readFlag) { try { try { file->readFile(filename); } catch (const std::bad_alloc&) { /* * This DataFileException will be caught * in the outer try/catch and it will * clean up to avoid memory leaks. */ throw DataFileException(filename, CaretDataFileHelper::createBadAllocExceptionMessage(filename)); } validateCiftiMappableDataFile(file); } catch (const DataFileException& dfe) { if (caretDataFile != NULL) { removeAndDeleteDataFile(caretDataFile); } else { delete file; } throw dfe; } } if (addFlag) { updateDataFileNameIfDuplicate(m_connectivityMatrixParcelDenseFiles, file); m_connectivityMatrixParcelDenseFiles.push_back(file); } return file; } /** * Read a connectivity data series file. * * @param fileMode * Mode for file adding, reading, or reloading. * @param caretDataFile * File that is added or reloaded (MUST NOT BE NULL). If NULL, * the mode must be READING. * @param filename * Name of the file. * @throws DataFileException * If reading failed. */ CiftiBrainordinateDataSeriesFile* Brain::addReadOrReloadConnectivityDataSeriesFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename) { CiftiBrainordinateDataSeriesFile* file = NULL; if (caretDataFile != NULL) { file = dynamic_cast(caretDataFile); CaretAssert(file); } else { file = new CiftiBrainordinateDataSeriesFile(); } bool addFlag = false; bool readFlag = false; switch (fileMode) { case FILE_MODE_ADD: addFlag = true; break; case FILE_MODE_READ: addFlag = true; readFlag = true; break; case FILE_MODE_RELOAD: readFlag = true; break; } if (readFlag) { try { try { file->readFile(filename); } catch (const std::bad_alloc&) { /* * This DataFileException will be caught * in the outer try/catch and it will * clean up to avoid memory leaks. */ throw DataFileException(filename, CaretDataFileHelper::createBadAllocExceptionMessage(filename)); } validateCiftiMappableDataFile(file); } catch (const DataFileException& dfe) { if (caretDataFile != NULL) { removeAndDeleteDataFile(caretDataFile); } else { delete file; } throw dfe; } } if (addFlag) { updateDataFileNameIfDuplicate(m_connectivityDataSeriesFiles, file); m_connectivityDataSeriesFiles.push_back(file); } return file; } /** * Read a palette file. * * @param fileMode * Mode for file adding, reading, or reloading. * @param caretDataFile * File that is added or reloaded (MUST NOT BE NULL). If NULL, * the mode must be READING. * @param filename * Name of the file. * @throws DataFileException * If reading failed. */ PaletteFile* Brain::addReadOrReloadPaletteFile(const FileModeAddReadReload fileMode, CaretDataFile* /*caretDataFile*/, const AString& filename) { bool addFlag = false; bool readFlag = false; switch (fileMode) { case FILE_MODE_ADD: addFlag = true; break; case FILE_MODE_READ: addFlag = true; readFlag = true; break; case FILE_MODE_RELOAD: readFlag = true; break; } if (readFlag) { } if (addFlag) { } throw DataFileException(filename, "Reading not implemented for: palette"); return NULL; } /** * Read a scene file. * * @param fileMode * Mode for file adding, reading, or reloading. * @param caretDataFile * File that is added or reloaded (MUST NOT BE NULL). If NULL, * the mode must be READING. * @param filename * Name of the file. * @throws DataFileException * If reading failed. */ SceneFile* Brain::addReadOrReloadSceneFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename) { SceneFile* sf = NULL; if (caretDataFile != NULL) { sf = dynamic_cast(caretDataFile); CaretAssert(sf); } else { sf = new SceneFile(); } bool addFlag = false; bool readFlag = false; switch (fileMode) { case FILE_MODE_ADD: addFlag = true; break; case FILE_MODE_READ: addFlag = true; readFlag = true; break; case FILE_MODE_RELOAD: readFlag = true; break; } if (readFlag) { try { try { sf->readFile(filename); } catch (const std::bad_alloc&) { /* * This DataFileException will be caught * in the outer try/catch and it will * clean up to avoid memory leaks. */ throw DataFileException(filename, CaretDataFileHelper::createBadAllocExceptionMessage(filename)); } } catch (DataFileException& dfe) { if (caretDataFile != NULL) { removeAndDeleteDataFile(caretDataFile); } else { delete sf; } throw dfe; } } if (addFlag) { updateDataFileNameIfDuplicate(m_sceneFiles, sf); m_sceneFiles.push_back(sf); } return sf; } /** * @return Number of connectivity matrix dense files. */ int32_t Brain::getNumberOfConnectivityMatrixDenseFiles() const { return m_connectivityMatrixDenseFiles.size(); } /** * Get the connectivity dense file at the given index. * @param indx * Index of file. * @return Conectivity dense file at index. */ CiftiConnectivityMatrixDenseFile* Brain::getConnectivityMatrixDenseFile(int32_t indx) { CaretAssertVectorIndex(m_connectivityMatrixDenseFiles, indx); return m_connectivityMatrixDenseFiles[indx]; } /** * Get the connectivity dense file at the given index. * @param indx * Index of file. * @return Conectivity dense file at index. */ const CiftiConnectivityMatrixDenseFile* Brain::getConnectivityMatrixDenseFile(int32_t indx) const { CaretAssertVectorIndex(m_connectivityMatrixDenseFiles, indx); return m_connectivityMatrixDenseFiles[indx]; } /** * Get ALL connectivity matrix dense files. * @param connectivityFilesOut * Contains all connectivity dense files on exit. */ void Brain::getConnectivityMatrixDenseFiles(std::vector& connectivityDenseFilesOut) const { connectivityDenseFilesOut = m_connectivityMatrixDenseFiles; } /** * @return Number of connectivity dense label files. */ int32_t Brain::getNumberOfConnectivityDenseLabelFiles() const { return m_connectivityDenseLabelFiles.size(); } /** * Get the connectivity dense label file at the given index. * @param indx * Index of file. * @return Conectivity dense label file at index. */ CiftiBrainordinateLabelFile* Brain::getConnectivityDenseLabelFile(int32_t indx) { CaretAssertVectorIndex(m_connectivityDenseLabelFiles, indx); return m_connectivityDenseLabelFiles[indx]; } /** * Get the connectivity dense label file at the given index. * @param indx * Index of file. * @return Conectivity dense label file at index. */ const CiftiBrainordinateLabelFile* Brain::getConnectivityDenseLabelFile(int32_t indx) const { CaretAssertVectorIndex(m_connectivityDenseLabelFiles, indx); return m_connectivityDenseLabelFiles[indx]; } /** * Get ALL connectivity dense label files. * @param connectivityDenseLabelFilesOut * Contains all connectivity dense labelfiles on exit. */ void Brain::getConnectivityDenseLabelFiles(std::vector& connectivityDenseLabelFilesOut) const { connectivityDenseLabelFilesOut = m_connectivityDenseLabelFiles; } /** * Get all of the CIFTI Mappable Data Files * @param allCiftiMappableDataFilesOut * Contains all CIFTI Mappable Data files upon exit. */ void Brain::getAllCiftiMappableDataFiles(std::vector& allCiftiMappableDataFilesOut) const { allCiftiMappableDataFilesOut.clear(); std::vector allFiles; getAllDataFiles(allFiles); for (std::vector::iterator iter = allFiles.begin(); iter != allFiles.end(); iter++) { CiftiMappableDataFile* cmdf = dynamic_cast(*iter); if (cmdf != NULL) { allCiftiMappableDataFilesOut.push_back(cmdf); } } } /** * Get all of the Brainordinate Chartable Data Files. Only files that implement the * ChartableLineSeriesBrainordinateInterface AND return true for ChartableLineSeriesBrainordinateInterface::isLineSeriesChartDataTypeSupported() * are included in the returned files. * * @param chartableDataFilesOut * Contains all chartable data files upon exit. */ void Brain::getAllChartableBrainordinateDataFiles(std::vector& chartableDataFilesOut) const { chartableDataFilesOut.clear(); std::vector allFiles; getAllDataFiles(allFiles); for (std::vector::iterator iter = allFiles.begin(); iter != allFiles.end(); iter++) { ChartableLineSeriesBrainordinateInterface* chartFile = dynamic_cast(*iter); if (chartFile != NULL) { if (chartFile->isLineSeriesChartingSupported()) { chartableDataFilesOut.push_back(chartFile); } } } } /** * Get all of the Line Series Chartable Data Files. Only files that implement the * ChartableLineSeriesInterface AND return true for ChartableLineSeriesInterface::isLineSeriesChartDataTypeSupported() * are included in the returned files. * * @param chartableDataFilesOut * Contains all chartable data files upon exit. */ void Brain::getAllChartableLineSeriesDataFiles(std::vector& chartableDataFilesOut) const { chartableDataFilesOut.clear(); std::vector allFiles; getAllDataFiles(allFiles); for (std::vector::iterator iter = allFiles.begin(); iter != allFiles.end(); iter++) { ChartableLineSeriesInterface* chartFile = dynamic_cast(*iter); if (chartFile != NULL) { if (chartFile->isLineSeriesChartingSupported()) { chartableDataFilesOut.push_back(chartFile); } } } } /** * Get all of the Line Series Chartable Data Files. Only files that implement the * ChartableLineSeriesInterface AND return true for ChartableLineSeriesInterface::isLineSeriesChartDataTypeSupported() * and support a chart of the given data type are included in the returned files. * * @param chartDataType * Desired chart data type. * @param chartableDataFilesOut * Contains all chartable data files upon exit. */ void Brain::getAllChartableLineSeriesDataFilesForChartDataType(const ChartDataTypeEnum::Enum chartDataType, std::vector& chartableDataFilesOut) const { chartableDataFilesOut.clear(); std::vector chartFiles; getAllChartableLineSeriesDataFiles(chartFiles); for (std::vector::iterator iter = chartFiles.begin(); iter != chartFiles.end(); iter++) { ChartableLineSeriesInterface* chartFile = *iter; if (chartFile->isLineSeriesChartDataTypeSupported(chartDataType)) { chartableDataFilesOut.push_back(chartFile); } } } /** * Get all of the Brainordinate Chartable Data Files. Only files that implement the * ChartableLineSeriesBrainordinateInterface, return true for ChartableLineSeriesBrainordinateInterface::isChartingSupported(), * AND return true for ChartableLineSeriesBrainordinateInterface::isChartingEnabled() for any tab index * are included in the returned files. * * @param chartableDataFilesOut * Contains all chartable data files upon exit. */ void Brain::getAllChartableBrainordinateDataFilesWithChartingEnabled(std::vector& chartableDataFilesOut) const { chartableDataFilesOut.clear(); std::vector allFiles; getAllDataFiles(allFiles); EventBrowserTabGetAll allTabsEvent; EventManager::get()->sendEvent(allTabsEvent.getPointer()); const std::vector tabIndices = allTabsEvent.getBrowserTabIndices(); const int32_t numTabs = static_cast(tabIndices.size()); for (std::vector::iterator iter = allFiles.begin(); iter != allFiles.end(); iter++) { ChartableLineSeriesBrainordinateInterface* chartFile = dynamic_cast(*iter); if (chartFile != NULL) { if (chartFile->isLineSeriesChartingSupported()) { for (int32_t iTab = 0; iTab < numTabs; iTab++) { const int32_t tabIndex = tabIndices[iTab]; if (chartFile->isLineSeriesChartingEnabled(tabIndex)) { chartableDataFilesOut.push_back(chartFile); break; } } } } } } /** * Get all of the Chartable Matrix Data Files. Only files that implement the * ChartableMatrixInterface AND return true for ChartableMatrixInterface::isChartingSupported() * are included in the returned files. * * @param chartableDataFilesOut * Contains all chartable data files upon exit. */ void Brain::getAllChartableMatrixDataFiles(std::vector& chartableDataFilesOut) const { chartableDataFilesOut.clear(); std::vector allFiles; getAllDataFiles(allFiles); for (std::vector::iterator iter = allFiles.begin(); iter != allFiles.end(); iter++) { ChartableMatrixInterface* chartFile = dynamic_cast(*iter); if (chartFile != NULL) { if (chartFile->isMatrixChartingSupported()) { chartableDataFilesOut.push_back(chartFile); } } } } /** * Get all of the Chartable Matrix Data Files. Only files that implement the * ChartableMatrixInterface AND return true for ChartableMatrixInterface::isChartingSupported() * and support a chart of the given data type are included in the returned files. * * @param chartDataType * Desired chart data type. * @param chartableDataFilesOut * Contains all chartable data files upon exit. */ void Brain::getAllChartableMatrixDataFilesForChartDataType(const ChartDataTypeEnum::Enum chartDataType, std::vector& chartableDataFilesOut) const { chartableDataFilesOut.clear(); std::vector chartFiles; getAllChartableMatrixDataFiles(chartFiles); for (std::vector::iterator iter = chartFiles.begin(); iter != chartFiles.end(); iter++) { ChartableMatrixInterface* chartFile = *iter; if (chartFile->isMatrixChartDataTypeSupported(chartDataType)) { chartableDataFilesOut.push_back(chartFile); } } } /** * @return Number of cifti dense parcel files. */ int32_t Brain::getNumberOfConnectivityMatrixDenseParcelFiles() const { return m_connectivityMatrixDenseParcelFiles.size(); } /** * Get the cifti dense parcel file at the given index. * @param indx * Index of file. * @return cifti dense parcel file at index. */ CiftiConnectivityMatrixDenseParcelFile* Brain::getConnectivityMatrixDenseParcelFile(int32_t indx) { CaretAssertVectorIndex(m_connectivityMatrixDenseParcelFiles, indx); return m_connectivityMatrixDenseParcelFiles[indx]; } /** * Get the connectivity dense parcel file at the given index. * @param indx * Index of file. * @return cifti dense parcel file at index. */ const CiftiConnectivityMatrixDenseParcelFile* Brain::getConnectivityMatrixDenseParcelFile(int32_t indx) const { CaretAssertVectorIndex(m_connectivityMatrixDenseParcelFiles, indx); return m_connectivityMatrixDenseParcelFiles[indx]; } /** * Get ALL cifti dense parcel files. * @param connectivityFilesOut * Contains all cifti dense parcel files on exit. */ void Brain::getConnectivityMatrixDenseParcelFiles(std::vector& connectivityDenseParcelFilesOut) const { connectivityDenseParcelFilesOut = m_connectivityMatrixDenseParcelFiles; } /** * @return Number of connectivity dense scalar files. */ int32_t Brain::getNumberOfConnectivityDenseScalarFiles() const { return m_connectivityDenseScalarFiles.size(); } /** * Get the connectivity dense scalar file at the given index. * @param indx * Index of file. * @return Conectivity dense scalar file at index. */ CiftiBrainordinateScalarFile* Brain::getConnectivityDenseScalarFile(int32_t indx) { CaretAssertVectorIndex(m_connectivityDenseScalarFiles, indx); return m_connectivityDenseScalarFiles[indx]; } /** * Get the connectivity dense scalar file at the given index. * @param indx * Index of file. * @return Conectivity dense scalar file at index. */ const CiftiBrainordinateScalarFile* Brain::getConnectivityDenseScalarFile(int32_t indx) const { CaretAssertVectorIndex(m_connectivityDenseScalarFiles, indx); return m_connectivityDenseScalarFiles[indx]; } /** * Get ALL connectivity dense scalr files. * @param connectivityDenseScalarFilesOut * Contains all connectivity dense files on exit. */ void Brain::getConnectivityDenseScalarFiles(std::vector& connectivityDenseScalarFilesOut) const { connectivityDenseScalarFilesOut = m_connectivityDenseScalarFiles; } /** * @return Number of connectivity parcel label files. */ int32_t Brain::getNumberOfConnectivityParcelLabelFiles() const { return m_connectivityParcelLabelFiles.size(); } /** * Get the connectivity parcel label file at the given index. * @param indx * Index of file. * @return Connectivity parcel label file at index. */ CiftiParcelLabelFile* Brain::getConnectivityParcelLabelFile(int32_t indx) { CaretAssertVectorIndex(m_connectivityParcelLabelFiles, indx); return m_connectivityParcelLabelFiles[indx]; } /** * Get the connectivity parcel label file at the given index. * @param indx * Index of file. * @return Connectivity parcel label file at index. */ const CiftiParcelLabelFile* Brain::getConnectivityParcelLabelFile(int32_t indx) const { CaretAssertVectorIndex(m_connectivityParcelLabelFiles, indx); return m_connectivityParcelLabelFiles[indx]; } /** * Get ALL connectivity parcel label files. * @param connectivityParcelLabelFilesOut * Contains all connectivity parcel label files on exit. */ void Brain::getConnectivityParcelLabelFiles(std::vector& connectivityParcelLabelFilesOut) const { connectivityParcelLabelFilesOut = m_connectivityParcelLabelFiles; } /** * @return Number of connectivity parcel scalar files. */ int32_t Brain::getNumberOfConnectivityParcelScalarFiles() const { return m_connectivityParcelScalarFiles.size(); } /** * Get the connectivity parcel scalar file at the given index. * @param indx * Index of file. * @return Connectivity parcel scalar file at index. */ CiftiParcelScalarFile* Brain::getConnectivityParcelScalarFile(int32_t indx) { CaretAssertVectorIndex(m_connectivityParcelScalarFiles, indx); return m_connectivityParcelScalarFiles[indx]; } /** * Get the connectivity parcel scalar file at the given index. * @param indx * Index of file. * @return Connectivity parcel scalar file at index. */ const CiftiParcelScalarFile* Brain::getConnectivityParcelScalarFile(int32_t indx) const { CaretAssertVectorIndex(m_connectivityParcelScalarFiles, indx); return m_connectivityParcelScalarFiles[indx]; } /** * Get ALL connectivity parcel scalar files. * @param connectivityParcelScalarFilesOut * Contains all connectivity parcel files on exit. */ void Brain::getConnectivityParcelScalarFiles(std::vector& connectivityParcelScalarFilesOut) const { connectivityParcelScalarFilesOut = m_connectivityParcelScalarFiles; } /** * @return Number of connectivity parcel scalar files. */ int32_t Brain::getNumberOfConnectivityScalarDataSeriesFiles() const { return m_connectivityScalarDataSeriesFiles.size(); } /** * Get the connectivity parcel scalar file at the given index. * @param indx * Index of file. * @return Connectivity parcel scalar file at index. */ CiftiScalarDataSeriesFile* Brain::getConnectivityScalarDataSeriesFile(int32_t indx) { CaretAssertVectorIndex(m_connectivityScalarDataSeriesFiles, indx); return m_connectivityScalarDataSeriesFiles[indx]; } /** * Get the connectivity parcel scalar file at the given index. * @param indx * Index of file. * @return Connectivity parcel scalar file at index. */ const CiftiScalarDataSeriesFile* Brain::getConnectivityScalarDataSeriesFile(int32_t indx) const { CaretAssertVectorIndex(m_connectivityScalarDataSeriesFiles, indx); return m_connectivityScalarDataSeriesFiles[indx]; } /** * Get ALL connectivity parcel scalar files. * @param connectivityScalarDataSeriesFilesOut * Contains all connectivity parcel files on exit. */ void Brain::getConnectivityScalarDataSeriesFiles(std::vector& connectivityScalarDataSeriesFilesOut) const { connectivityScalarDataSeriesFilesOut = m_connectivityScalarDataSeriesFiles; } /** * @return Number of connectivity parcel data series files. */ int32_t Brain::getNumberOfConnectivityParcelSeriesFiles() const { return m_connectivityParcelSeriesFiles.size(); } /** * Get the connectivity parcel data series file at the given index. * @param indx * Index of file. * @return Connectivity parcel data series file at index. */ CiftiParcelSeriesFile* Brain::getConnectivityParcelSeriesFile(int32_t indx) { CaretAssertVectorIndex(m_connectivityParcelSeriesFiles, indx); return m_connectivityParcelSeriesFiles[indx]; } /** * Get the connectivity parcel data series file at the given index. * @param indx * Index of file. * @return Connectivity parcel data series file at index. */ const CiftiParcelSeriesFile* Brain::getConnectivityParcelSeriesFile(int32_t indx) const { CaretAssertVectorIndex(m_connectivityParcelSeriesFiles, indx); return m_connectivityParcelSeriesFiles[indx]; } /** * Get ALL connectivity parcel data series files. * @param connectivityParcelSeriesFilesOut * Contains all connectivity parcel files on exit. */ void Brain::getConnectivityParcelSeriesFiles(std::vector& connectivityParcelSeriesFilesOut) const { connectivityParcelSeriesFilesOut = m_connectivityParcelSeriesFiles; } /** * @return Number of connectivity fiber orientation files. */ int32_t Brain::getNumberOfConnectivityFiberOrientationFiles() const { return m_connectivityFiberOrientationFiles.size(); } /** * Get the connectivity fiber orientation file at the given index. * @param indx * Index of file. * @return Conectivity fiber orientation file at index. */ CiftiFiberOrientationFile* Brain::getConnectivityFiberOrientationFile(int32_t indx) { CaretAssertVectorIndex(m_connectivityFiberOrientationFiles, indx); return m_connectivityFiberOrientationFiles[indx]; } /** * Get the connectivity fiber orientation file at the given index. * @param indx * Index of file. * @return Conectivity fiber orientation file at index. */ const CiftiFiberOrientationFile* Brain::getConnectivityFiberOrientationFile(int32_t indx) const { CaretAssertVectorIndex(m_connectivityFiberOrientationFiles, indx); return m_connectivityFiberOrientationFiles[indx]; } /** * Get ALL connectivity fiber orientation files. * @param connectivityFiberOrientationFilesOut * Contains all connectivity fiber orientation files on exit. */ void Brain::getConnectivityFiberOrientationFiles(std::vector& connectivityFiberOrientationFilesOut) const { connectivityFiberOrientationFilesOut = m_connectivityFiberOrientationFiles; } /** * @return Number of connectivity fiber trajectory files. */ int32_t Brain::getNumberOfConnectivityFiberTrajectoryFiles() const { return m_connectivityFiberTrajectoryFiles.size(); } /** * Get the connectivity fiber trajectory file at the given index. * @param indx * Index of file. * @return Conectivity fiber trajectory file at index. */ CiftiFiberTrajectoryFile* Brain::getConnectivityFiberTrajectoryFile(int32_t indx) { CaretAssertVectorIndex(m_connectivityFiberTrajectoryFiles, indx); return m_connectivityFiberTrajectoryFiles[indx]; } /** * Get the connectivity fiber trajectory file at the given index. * @param indx * Index of file. * @return Conectivity fiber trajectory file at index. */ const CiftiFiberTrajectoryFile* Brain::getConnectivityFiberTrajectoryFile(int32_t indx) const { CaretAssertVectorIndex(m_connectivityFiberTrajectoryFiles, indx); return m_connectivityFiberTrajectoryFiles[indx]; } /** * Get ALL connectivity fiber trajectory files. * @param connectivityFiberTrajectoryFilesOut * Contains all connectivity fiber trajectory files on exit. */ void Brain::getConnectivityFiberTrajectoryFiles(std::vector& connectivityFiberTrajectoryFilesOut) const { connectivityFiberTrajectoryFilesOut = m_connectivityFiberTrajectoryFiles; } /** * @return Number of cifti parcel files. */ int32_t Brain::getNumberOfConnectivityMatrixParcelFiles() const { return m_connectivityMatrixParcelFiles.size(); } /** * Get the cifti parcel file at the given index. * @param indx * Index of file. * @return cifti parcel file at index. */ CiftiConnectivityMatrixParcelFile* Brain::getConnectivityMatrixParcelFile(int32_t indx) { CaretAssertVectorIndex(m_connectivityMatrixParcelFiles, indx); return m_connectivityMatrixParcelFiles[indx]; } /** * Get the connectivity parcel file at the given index. * @param indx * Index of file. * @return cifti parcel file at index. */ const CiftiConnectivityMatrixParcelFile* Brain::getConnectivityMatrixParcelFile(int32_t indx) const { CaretAssertVectorIndex(m_connectivityMatrixParcelFiles, indx); return m_connectivityMatrixParcelFiles[indx]; } /** * Get ALL cifti parcel files. * @param connectivityFilesOut * Contains all cifti parcel files on exit. */ void Brain::getConnectivityMatrixParcelFiles(std::vector& connectivityParcelFilesOut) const { connectivityParcelFilesOut = m_connectivityMatrixParcelFiles; } /** * @return Number of cifti parcel dense files. */ int32_t Brain::getNumberOfConnectivityMatrixParcelDenseFiles() const { return m_connectivityMatrixParcelDenseFiles.size(); } /** * Get the cifti parcel dense file at the given index. * @param indx * Index of file. * @return cifti parcel dense file at index. */ CiftiConnectivityMatrixParcelDenseFile* Brain::getConnectivityMatrixParcelDenseFile(int32_t indx) { CaretAssertVectorIndex(m_connectivityMatrixParcelDenseFiles, indx); return m_connectivityMatrixParcelDenseFiles[indx]; } /** * Get the connectivity parcel dense file at the given index. * @param indx * Index of file. * @return cifti parcel dense file at index. */ const CiftiConnectivityMatrixParcelDenseFile* Brain::getConnectivityMatrixParcelDenseFile(int32_t indx) const { CaretAssertVectorIndex(m_connectivityMatrixParcelDenseFiles, indx); return m_connectivityMatrixParcelDenseFiles[indx]; } /** * Get ALL cifti parcel dense files. * @param connectivityFilesOut * Contains all cifti parcel dense files on exit. */ void Brain::getConnectivityMatrixParcelDenseFiles(std::vector& connectivityParcelDenseFilesOut) const { connectivityParcelDenseFilesOut = m_connectivityMatrixParcelDenseFiles; } /** * @return Number of cifti data series files. */ int32_t Brain::getNumberOfConnectivityDataSeriesFiles() const { return m_connectivityDataSeriesFiles.size(); } /** * Get the cifti data series file at the given index. * @param indx * Index of file. * @return cifti data series file at index. */ CiftiBrainordinateDataSeriesFile* Brain::getConnectivityDataSeriesFile(int32_t indx) { CaretAssertVectorIndex(m_connectivityDataSeriesFiles, indx); return m_connectivityDataSeriesFiles[indx]; } /** * Get the connectivity data series file at the given index. * @param indx * Index of file. * @return cifti data series file at index. */ const CiftiBrainordinateDataSeriesFile* Brain::getConnectivityDataSeriesFile(int32_t indx) const { CaretAssertVectorIndex(m_connectivityDataSeriesFiles, indx); return m_connectivityDataSeriesFiles[indx]; } /** * Get ALL cifti data series files. * @param connectivityDataSeriesFilesOut * Contains all cifti data series files on exit. */ void Brain::getConnectivityDataSeriesFiles(std::vector& connectivityDataSeriesFilesOut) const { connectivityDataSeriesFilesOut = m_connectivityDataSeriesFiles; } /** * Get all of the cifti connectivity type data files. * * param allCiftiConnectivityMatrixFiles * Will contain the files upon exit. */ void Brain::getAllCiftiConnectivityMatrixFiles(std::vector& allCiftiConnectivityMatrixFiles) const { allCiftiConnectivityMatrixFiles.clear(); std::vector allFiles; getAllDataFiles(allFiles); for (std::vector::iterator iter = allFiles.begin(); iter != allFiles.end(); iter++) { CiftiMappableConnectivityMatrixDataFile* cmdf = dynamic_cast(*iter); if (cmdf != NULL) { allCiftiConnectivityMatrixFiles.push_back(cmdf); } } } /** * Add a data file to the brain. * * This will add the file to its corresponding data file type and add the * file into the spec file. * * @param caretDataFile * The caret data file. * @throw DataFileException * If there is an error. */ void Brain::addDataFile(CaretDataFile* caretDataFile) { CaretAssert(caretDataFile); caretDataFile->setFileName(convertFilePathNameToAbsolutePathName(caretDataFile->getFileName())); const StructureEnum::Enum structure = caretDataFile->getStructure(); BrainStructure* brainStructure = getBrainStructure(structure, false); const DataFileTypeEnum::Enum dataFileType = caretDataFile->getDataFileType(); switch (dataFileType) { case DataFileTypeEnum::BORDER: { BorderFile* file = dynamic_cast(caretDataFile); CaretAssert(file); m_borderFiles.push_back(file); } break; case DataFileTypeEnum::CONNECTIVITY_DENSE: { CiftiConnectivityMatrixDenseFile* file = dynamic_cast(caretDataFile); CaretAssert(file); m_connectivityMatrixDenseFiles.push_back(file); } break; case DataFileTypeEnum::CONNECTIVITY_DENSE_LABEL: { CiftiBrainordinateLabelFile* file = dynamic_cast(caretDataFile); CaretAssert(file); m_connectivityDenseLabelFiles.push_back(file); } break; case DataFileTypeEnum::CONNECTIVITY_DENSE_PARCEL: { CiftiConnectivityMatrixDenseParcelFile* file = dynamic_cast(caretDataFile); CaretAssert(file); m_connectivityMatrixDenseParcelFiles.push_back(file); } break; case DataFileTypeEnum::CONNECTIVITY_DENSE_SCALAR: { CiftiBrainordinateScalarFile* file = dynamic_cast(caretDataFile); CaretAssert(file); m_connectivityDenseScalarFiles.push_back(file); } break; case DataFileTypeEnum::CONNECTIVITY_DENSE_TIME_SERIES: { CiftiBrainordinateDataSeriesFile* file = dynamic_cast(caretDataFile); CaretAssert(file); m_connectivityDataSeriesFiles.push_back(file); } break; case DataFileTypeEnum::CONNECTIVITY_FIBER_ORIENTATIONS_TEMPORARY: { CiftiFiberOrientationFile* file = dynamic_cast(caretDataFile); CaretAssert(file); m_connectivityFiberOrientationFiles.push_back(file); } break; case DataFileTypeEnum::CONNECTIVITY_FIBER_TRAJECTORY_TEMPORARY: { CiftiFiberTrajectoryFile* file = dynamic_cast(caretDataFile); CaretAssert(file); m_connectivityFiberTrajectoryFiles.push_back(file); } break; case DataFileTypeEnum::CONNECTIVITY_PARCEL: { CiftiConnectivityMatrixParcelFile* file = dynamic_cast(caretDataFile); CaretAssert(file); m_connectivityMatrixParcelFiles.push_back(file); } break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_DENSE: { CiftiConnectivityMatrixParcelDenseFile* file = dynamic_cast(caretDataFile); CaretAssert(file); m_connectivityMatrixParcelDenseFiles.push_back(file); } break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_LABEL: { CiftiParcelLabelFile* file = dynamic_cast(caretDataFile); CaretAssert(file); m_connectivityParcelLabelFiles.push_back(file); } break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_SCALAR: { CiftiParcelScalarFile* file = dynamic_cast(caretDataFile); CaretAssert(file); m_connectivityParcelScalarFiles.push_back(file); } break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_SERIES: { CiftiParcelSeriesFile* file = dynamic_cast(caretDataFile); CaretAssert(file); m_connectivityParcelSeriesFiles.push_back(file); } break; case DataFileTypeEnum::CONNECTIVITY_SCALAR_DATA_SERIES: { CiftiScalarDataSeriesFile* file = dynamic_cast(caretDataFile); CaretAssert(file); m_connectivityScalarDataSeriesFiles.push_back(file); } break; case DataFileTypeEnum::FOCI: { FociFile* file = dynamic_cast(caretDataFile); CaretAssert(file); m_fociFiles.push_back(file); } break; case DataFileTypeEnum::IMAGE: { ImageFile* file = dynamic_cast(caretDataFile); CaretAssert(file); m_imageFiles.push_back(file); } break; case DataFileTypeEnum::LABEL: { LabelFile* file = dynamic_cast(caretDataFile); CaretAssert(file); if (structure == StructureEnum::INVALID) { throw DataFileException(file->getFileName(), "Structure in label file is INVALID."); } if (brainStructure == NULL) { throw DataFileException(file->getFileName(), "Must load surface(s) with matching structure prior to label files"); } brainStructure->addLabelFile(file, true); } break; case DataFileTypeEnum::METRIC: { MetricFile* file = dynamic_cast(caretDataFile); CaretAssert(file); if (structure == StructureEnum::INVALID) { throw DataFileException(file->getFileName(), "Structure in metric file is INVALID."); } if (brainStructure == NULL) { throw DataFileException(file->getFileName(), "Must load surface(s) with matching structure prior to metric files"); } brainStructure->addMetricFile(file, true); } break; case DataFileTypeEnum::PALETTE: { throw DataFileException(caretDataFile->getFileName(), "Adding palette files not supported at this time."); } break; case DataFileTypeEnum::RGBA: { RgbaFile* file = dynamic_cast(caretDataFile); CaretAssert(file); if (structure == StructureEnum::INVALID) { throw DataFileException(file->getFileName(), "Structure in rgba file is INVALID."); } if (brainStructure == NULL) { throw DataFileException(file->getFileName(), "Must load surface(s) with matching structure prior to label files"); } brainStructure->addRgbaFile(file, true); } break; case DataFileTypeEnum::SCENE: { SceneFile* file = dynamic_cast(caretDataFile); CaretAssert(file); m_sceneFiles.push_back(file); } break; case DataFileTypeEnum::SPECIFICATION: CaretLogSevere("PROGRAM ERROR: Reading spec file should never call Brain::addReadOrReloadDataFile()"); throw DataFileException(caretDataFile->getFileName(), "PROGRAM ERROR: Reading spec file should never call Brain::addReadOrReloadDataFile()"); break; case DataFileTypeEnum::SURFACE: { Surface* file = dynamic_cast(caretDataFile); if (structure == StructureEnum::INVALID) { throw DataFileException(file->getFileName(), "Structure in surface file is INVALID."); } if (file == NULL) { throw DataFileException(file->getFileName(), "Cannot add SurfaceFile but can add a Surface."); } if (brainStructure == NULL) { brainStructure = getBrainStructure(structure, true); } brainStructure->addSurface(file, true, true); } break; case DataFileTypeEnum::UNKNOWN: throw DataFileException(caretDataFile->getFileName(), "Unable to read files of type UNKNOWN. Filename extension may be invalid."); break; case DataFileTypeEnum::VOLUME: { VolumeFile* file = dynamic_cast(caretDataFile); CaretAssert(file); m_volumeFiles.push_back(file); } break; } m_specFile->addCaretDataFile(caretDataFile); } /** * @return Number of border files. */ int32_t Brain::getNumberOfBorderFiles() const { return m_borderFiles.size(); } /** * @return The border file. * @param indx Index of the border file. */ BorderFile* Brain::getBorderFile(const int32_t indx) { CaretAssertVectorIndex(m_borderFiles, indx); return m_borderFiles[indx]; } /** * @return The border file. * @param indx Index of the border file. */ const BorderFile* Brain::getBorderFile(const int32_t indx) const { CaretAssertVectorIndex(m_borderFiles, indx); return m_borderFiles[indx]; } /** * @return Number of foci files. */ int32_t Brain::getNumberOfFociFiles() const { return m_fociFiles.size(); } /** * @return The foci file. * @param indx Index of the foci file. */ FociFile* Brain::getFociFile(const int32_t indx) { CaretAssertVectorIndex(m_fociFiles, indx); return m_fociFiles[indx]; } /** * @return The foci file. * @param indx Index of the foci file. */ const FociFile* Brain::getFociFile(const int32_t indx) const { CaretAssertVectorIndex(m_fociFiles, indx); return m_fociFiles[indx]; } /** * @return All image files. */ const std::vector Brain::getAllImagesFiles() const { return m_imageFiles; } /** * @return Number of image files. */ int32_t Brain::getNumberOfImageFiles() const { return m_imageFiles.size(); } /** * @return The image file. * @param indx Index of the image file. */ ImageFile* Brain::getImageFile(const int32_t indx) { CaretAssertVectorIndex(m_imageFiles, indx); return m_imageFiles[indx]; } /** * @return The image file. * @param indx Index of the image file. */ const ImageFile* Brain::getImageFile(const int32_t indx) const { CaretAssertVectorIndex(m_imageFiles, indx); return m_imageFiles[indx]; } /** * @return Number of scene files. */ int32_t Brain::getNumberOfSceneFiles() const { return m_sceneFiles.size(); } /** * @return The scene file. * @param indx Index of the scene file. */ SceneFile* Brain::getSceneFile(const int32_t indx) { CaretAssertVectorIndex(m_sceneFiles, indx); return m_sceneFiles[indx]; } /** * @return The scene file. * @param indx Index of the scene file. */ const SceneFile* Brain::getSceneFile(const int32_t indx) const { CaretAssertVectorIndex(m_sceneFiles, indx); return m_sceneFiles[indx]; } /** * @return The Spec File. */ const SpecFile* Brain::getSpecFile() const { return m_specFile; } /** * @return The Spec File. */ SpecFile* Brain::getSpecFile() { return m_specFile; } /* * @return The palette file. */ PaletteFile* Brain::getPaletteFile() { return m_paletteFile; } /* * @return The palette file. */ const PaletteFile* Brain::getPaletteFile() const { return m_paletteFile; } /** * Find the surface with the given name. * @param surfaceFileName * Name of surface. * @param useAbsolutePath * If true the given surfaceFileName is an absolute path. * If false, the given surfaceFileName is just the file * name without any path. */ Surface* Brain::getSurfaceWithName(const AString& surfaceFileName, const bool useAbsolutePath) { for (std::vector::iterator iter = m_brainStructures.begin(); iter != m_brainStructures.end(); iter++) { BrainStructure* bs = *iter; Surface* surface = bs->getSurfaceWithName(surfaceFileName, useAbsolutePath); if (surface != NULL) { return surface; } } return NULL; } /** * @return The primary anatomical surfaces from all brain structures. */ std::vector Brain::getPrimaryAnatomicalSurfaces() const { std::vector surfaces; const int32_t numBrainStructures = getNumberOfBrainStructures(); for (int32_t i = 0; i < numBrainStructures; i++) { surfaces.push_back(m_brainStructures[i]->getPrimaryAnatomicalSurface()); } return surfaces; } /** * @return The primary anatomical surfaces from all brain structures. */ std::vector Brain::getPrimaryAnatomicalSurfaceFiles() const { std::vector surfaces = getPrimaryAnatomicalSurfaces(); std::vector surfaceFiles; surfaceFiles.insert(surfaceFiles.end(), surfaces.begin(), surfaces.end()); return surfaceFiles; } /** * Get the primary anatomical surface for the given structure. * * @param structure * Structure for which a primary anatomical surface is requested. * @return * The primary anatomical surface corresonding to the given structure. * NULL may be returned if a surface is not available. */ const Surface* Brain::getPrimaryAnatomicalSurfaceForStructure(const StructureEnum::Enum structure) const { const int32_t numBrainStructures = getNumberOfBrainStructures(); for (int32_t i = 0; i < numBrainStructures; i++) { if (m_brainStructures[i]->getStructure() == structure) { return m_brainStructures[i]->getPrimaryAnatomicalSurface(); } } return NULL; } /** * Get the primary anatomical surface nearest the given coordinate and * within the given tolerance. * * @param xyz * The coordinate. * @param tolerance * The tolerance (if negative tolerance is ignored). * @return * Nearest surface or NULL if nearest surface not within tolerance. */ Surface* Brain::getPrimaryAnatomicalSurfaceNearestCoordinate(const float xyz[3], const float tolerance) { Surface* nearestSurface = NULL; float nearestDistance = ((tolerance > 0.0) ? tolerance : std::numeric_limits::max()); const int32_t numBrainStructures = getNumberOfBrainStructures(); for (int32_t i = 0; i < numBrainStructures; i++) { Surface* surface = m_brainStructures[i]->getPrimaryAnatomicalSurface(); const int32_t nodeIndex = surface->closestNode(xyz, tolerance); if (nodeIndex >= 0) { const float dist = MathFunctions::distanceSquared3D(xyz, surface->getCoordinate(nodeIndex)); if (dist < nearestDistance) { nearestDistance = dist; nearestSurface = surface; } } } return nearestSurface; } /** * Update the chart model. */ void Brain::updateChartModel() { std::vector chartableBrainordinateFiles; getAllChartableBrainordinateDataFiles(chartableBrainordinateFiles); std::vector chartableMatrixFiles; getAllChartableMatrixDataFiles(chartableMatrixFiles); const int32_t numberOfChartableFiles = (chartableBrainordinateFiles.size() + chartableMatrixFiles.size()); if (numberOfChartableFiles > 0) { if (m_modelChart == NULL) { m_modelChart = new ModelChart(this); EventModelAdd eventAddModel(m_modelChart); EventManager::get()->sendEvent(eventAddModel.getPointer()); if (m_isSpecFileBeingRead == false) { m_modelChart->initializeOverlays(); } } } else { if (m_modelChart != NULL) { EventModelDelete eventDeleteModel(m_modelChart); EventManager::get()->sendEvent(eventDeleteModel.getPointer()); delete m_modelChart; m_modelChart = NULL; } } } /** * Update the volume slice model. */ void Brain::updateVolumeSliceModel() { bool isValid = false; std::vector allCaretMappableDataFiles; getAllMappableDataFiles(allCaretMappableDataFiles); for (std::vector::iterator iter = allCaretMappableDataFiles.begin(); iter != allCaretMappableDataFiles.end(); iter++) { CaretMappableDataFile* cmdf = *iter; if (cmdf->isVolumeMappable()) { if (cmdf->isEmpty() == false) { isValid = true; break; } } } if (isValid) { if (m_volumeSliceModel == NULL) { m_volumeSliceModel = new ModelVolume(this); EventModelAdd eventAddModel(m_volumeSliceModel); EventManager::get()->sendEvent(eventAddModel.getPointer()); if (m_isSpecFileBeingRead == false) { m_volumeSliceModel->initializeOverlays(); } } } else { if (m_volumeSliceModel != NULL) { EventModelDelete eventDeleteModel(m_volumeSliceModel); EventManager::get()->sendEvent(eventDeleteModel.getPointer()); delete m_volumeSliceModel; m_volumeSliceModel = NULL; } } } /** * Update the whole brain model. */ void Brain::updateWholeBrainModel() { bool isValid = false; if ((getNumberOfBrainStructures() > 0) || (getNumberOfVolumeFiles() > 0)) { isValid = true; } if (isValid) { if (m_wholeBrainModel == NULL) { m_wholeBrainModel = new ModelWholeBrain(this); EventModelAdd eventAddModel(m_wholeBrainModel); EventManager::get()->sendEvent(eventAddModel.getPointer()); if (m_isSpecFileBeingRead == false) { m_wholeBrainModel->initializeOverlays(); } } m_wholeBrainModel->updateModel(); } else { if (m_wholeBrainModel != NULL) { EventModelDelete eventDeleteModel(m_wholeBrainModel); EventManager::get()->sendEvent(eventDeleteModel.getPointer()); delete m_wholeBrainModel; m_wholeBrainModel = NULL; } } } /** * Update the surface montage model */ void Brain::updateSurfaceMontageModel() { bool isValid = false; if (getNumberOfBrainStructures() > 0) { isValid = true; } if (isValid) { if (m_surfaceMontageModel == NULL) { m_surfaceMontageModel = new ModelSurfaceMontage(this); EventModelAdd eventAddModel(m_surfaceMontageModel); EventManager::get()->sendEvent(eventAddModel.getPointer()); if (m_isSpecFileBeingRead == false) { m_surfaceMontageModel->initializeOverlays(); } } } else { if (m_surfaceMontageModel != NULL) { EventModelDelete eventDeleteModel(m_surfaceMontageModel); EventManager::get()->sendEvent(eventDeleteModel.getPointer()); delete m_surfaceMontageModel; m_surfaceMontageModel = NULL; } } } /** * Process a reload data file event. * @param reloadDataFileEvent * Event containing file for reloading and my be updated with error messages. */ void Brain::processReloadDataFileEvent(EventDataFileReload* reloadDataFileEvent) { /* * Verify that file is already in memory. */ std::vector allDataFiles; getAllDataFiles(allDataFiles); CaretDataFile* caretDataFile = reloadDataFileEvent->getCaretDataFile(); CaretAssert(caretDataFile); if (std::find(allDataFiles.begin(), allDataFiles.end(), caretDataFile) == allDataFiles.end()) { reloadDataFileEvent->setErrorMessage("ERROR: " + caretDataFile->getFileNameNoPath() + " was not found as a loaded file."); return; } CaretDataFile::setFileReadingUsernameAndPassword(reloadDataFileEvent->getUsername(), reloadDataFileEvent->getPassword()); try { addReadOrReloadDataFile(FILE_MODE_RELOAD, caretDataFile, caretDataFile->getDataFileType(), caretDataFile->getStructure(), caretDataFile->getFileName(), false); } catch (const DataFileException& dfe) { reloadDataFileEvent->setErrorMessage(dfe.whatString()); } updateAfterFilesAddedOrRemoved(); } /** * Process a read data file event. * @param readDataFileEvent * Event describing file for reading and may be updated with error messages. */ void Brain::processReadDataFileEvent(EventDataFileRead* readDataFileEvent) { CaretDataFile::setFileReadingUsernameAndPassword(readDataFileEvent->getUsername(), readDataFileEvent->getPassword()); const int32_t numberOfFilesToRead = readDataFileEvent->getNumberOfDataFilesToRead(); EventProgressUpdate progressEvent(0, numberOfFilesToRead, 0, "Starting to read data file(s)"); EventManager::get()->sendEvent(progressEvent.getPointer()); AString eventErrorMessage; for (int32_t i = 0; i < numberOfFilesToRead; i++) { const AString filename = readDataFileEvent->getDataFileName(i); const DataFileTypeEnum::Enum dataFileType = readDataFileEvent->getDataFileType(i); const StructureEnum::Enum structure = readDataFileEvent->getStructure(i); const bool setFileModifiedStatus = readDataFileEvent->isFileToBeMarkedModified(i); const AString shortName = FileInformation(filename).getFileName(); progressEvent.setProgress(i, ("Reading " + shortName)); EventManager::get()->sendEvent(progressEvent.getPointer()); if (progressEvent.isCancelled()) { eventErrorMessage.appendWithNewLine("File reading cancelled."); break; } try { CaretDataFile* fileRead = readDataFile(dataFileType, structure, filename, setFileModifiedStatus); readDataFileEvent->setDataFileRead(i, fileRead); } catch (const DataFileException& e) { if (e.isErrorInvalidStructure()) { readDataFileEvent->setFileErrorInvalidStructure(i, e.isErrorInvalidStructure()); } else { eventErrorMessage.appendWithNewLine(e.whatString()); readDataFileEvent->setFileErrorMessage(i, e.whatString()); } } } readDataFileEvent->setErrorMessage(eventErrorMessage); CaretDataFile::setFileReadingUsernameAndPassword("", ""); } /** * Read, or reload a data file. * * @param fileMode * Mode for file adding, reading, or reloading. * @param caretDataFile * File that is added or reloaded (MUST NOT BE NULL). If NULL, * the mode must be READING. * @param dataFileType * Type of data file to read. * @param structure * Struture of file (used if not invalid) * @param dataFileNameIn * Name of data file to read. * @param markDataFileAsModified * If file has invalid structure and settings structure, mark file modified * @return * In some cases this will return a pointer to the file that was read so * beware that this value may be NULL. * @throws DataFileException * If there is an error reading the file. */ CaretDataFile* Brain::addReadOrReloadDataFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const DataFileTypeEnum::Enum dataFileType, const StructureEnum::Enum structure, const AString& dataFileNameIn, const bool markDataFileAsModified) { AString dataFileName = dataFileNameIn; CaretDataFile* caretDataFileRead = NULL; switch (fileMode) { case FILE_MODE_ADD: CaretAssert(caretDataFile != NULL); break; case FILE_MODE_READ: CaretAssert(caretDataFile == NULL); break; case FILE_MODE_RELOAD: CaretAssert(caretDataFile != NULL); break; } try { ElapsedTimer et; et.start(); switch (dataFileType) { case DataFileTypeEnum::BORDER: caretDataFileRead = addReadOrReloadBorderFile(fileMode, caretDataFile, dataFileName); break; case DataFileTypeEnum::CONNECTIVITY_DENSE: caretDataFileRead = addReadOrReloadConnectivityDenseFile(fileMode, caretDataFile, dataFileName); break; case DataFileTypeEnum::CONNECTIVITY_DENSE_LABEL: caretDataFileRead = addReadOrReloadConnectivityDenseLabelFile(fileMode, caretDataFile, dataFileName); break; case DataFileTypeEnum::CONNECTIVITY_DENSE_PARCEL: caretDataFileRead = addReadOrReloadConnectivityMatrixDenseParcelFile(fileMode, caretDataFile, dataFileName); break; case DataFileTypeEnum::CONNECTIVITY_DENSE_SCALAR: caretDataFileRead = addReadOrReloadConnectivityDenseScalarFile(fileMode, caretDataFile, dataFileName); break; case DataFileTypeEnum::CONNECTIVITY_DENSE_TIME_SERIES: caretDataFileRead = addReadOrReloadConnectivityDataSeriesFile(fileMode, caretDataFile, dataFileName); break; case DataFileTypeEnum::CONNECTIVITY_FIBER_ORIENTATIONS_TEMPORARY: caretDataFileRead = addReadOrReloadConnectivityFiberOrientationFile(fileMode, caretDataFile, dataFileName); break; case DataFileTypeEnum::CONNECTIVITY_FIBER_TRAJECTORY_TEMPORARY: caretDataFileRead = addReadOrReloadConnectivityFiberTrajectoryFile(fileMode, caretDataFile, dataFileName); break; case DataFileTypeEnum::CONNECTIVITY_PARCEL: caretDataFileRead = addReadOrReloadConnectivityMatrixParcelFile(fileMode, caretDataFile, dataFileName); break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_DENSE: caretDataFileRead = addReadOrReloadConnectivityMatrixParcelDenseFile(fileMode, caretDataFile, dataFileName); break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_LABEL: caretDataFileRead = addReadOrReloadConnectivityParcelLabelFile(fileMode, caretDataFile, dataFileName); break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_SCALAR: caretDataFileRead = addReadOrReloadConnectivityParcelScalarFile(fileMode, caretDataFile, dataFileName); break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_SERIES: caretDataFileRead = addReadOrReloadConnectivityParcelSeriesFile(fileMode, caretDataFile, dataFileName); break; case DataFileTypeEnum::CONNECTIVITY_SCALAR_DATA_SERIES: caretDataFileRead = addReadOrReloadConnectivityScalarDataSeriesFile(fileMode, caretDataFile, dataFileName); break; case DataFileTypeEnum::FOCI: caretDataFileRead = addReadOrReloadFociFile(fileMode, caretDataFile, dataFileName); break; case DataFileTypeEnum::IMAGE: caretDataFileRead = addReadOrReloadImageFile(fileMode, caretDataFile, dataFileName); break; case DataFileTypeEnum::LABEL: caretDataFileRead = addReadOrReloadLabelFile(fileMode, caretDataFile, dataFileName, structure, markDataFileAsModified); break; case DataFileTypeEnum::METRIC: caretDataFileRead = addReadOrReloadMetricFile(fileMode, caretDataFile, dataFileName, structure, markDataFileAsModified); break; case DataFileTypeEnum::PALETTE: caretDataFileRead = addReadOrReloadPaletteFile(fileMode, caretDataFile, dataFileName); break; case DataFileTypeEnum::RGBA: caretDataFileRead = addReadOrReloadRgbaFile(fileMode, caretDataFile, dataFileName, structure, markDataFileAsModified); break; case DataFileTypeEnum::SCENE: caretDataFileRead = addReadOrReloadSceneFile(fileMode, caretDataFile, dataFileName); break; case DataFileTypeEnum::SPECIFICATION: CaretLogSevere("PROGRAM ERROR: Reading spec file should never call Brain::addReadOrReloadDataFile()"); throw DataFileException(dataFileName, "PROGRAM ERROR: Reading spec file should never call Brain::addReadOrReloadDataFile()"); break; case DataFileTypeEnum::SURFACE: caretDataFileRead = addReadOrReloadSurfaceFile(fileMode, caretDataFile, dataFileName, structure, markDataFileAsModified); break; case DataFileTypeEnum::UNKNOWN: throw DataFileException(dataFileName, "Unable to read files of type UNKNOWN. May have invalid filename extenson."); break; case DataFileTypeEnum::VOLUME: caretDataFileRead = addReadOrReloadVolumeFile(fileMode, caretDataFile, dataFileName); break; } if (caretDataFileRead != NULL) { /* * NOTE: Name may have changed if it is a duplicate file name * for the data type. */ dataFileName = caretDataFileRead->getFileName(); m_specFile->addCaretDataFile(caretDataFileRead); } m_specFile->addDataFile(dataFileType, structure, dataFileName, true, false, false); AString msg = ("Time to read " + dataFileName + " was " + AString::number(et.getElapsedTimeSeconds()) + " seconds."); CaretLogInfo(msg); } catch (DataFileException& dfe) { /* * If "caretDataFile" is not NULL, then we were trying to * RELOAD a file so remove it from the "loaded files" */ if (caretDataFile != NULL) { m_specFile->removeCaretDataFile(caretDataFile); } else { if (caretDataFileRead != NULL) { delete caretDataFileRead; caretDataFileRead = NULL; } } throw dfe; } updateAfterFilesAddedOrRemoved(); return caretDataFileRead; } /** * Read a data file. * * @param fileMode * Mode for file adding, reading, or reloading. * @param caretDataFile * File that is added or reloaded (MUST NOT BE NULL). If NULL, * the mode must be READING. * @param dataFileType * Type of data file to read. * @param structure * Struture of file (used if not invalid) * @param dataFileNameIn * Name of data file to read. * @param markDataFileAsModified * If file has invalid structure and settings structure, mark file modified * @throws DataFileException * If there is an error reading the file. * @return * Pointer to file that was read, if no errors. */ CaretDataFile* Brain::readDataFile(const DataFileTypeEnum::Enum dataFileType, const StructureEnum::Enum structure, const AString& dataFileNameIn, const bool markDataFileAsModified) { AString dataFileName = dataFileNameIn; /* * If possible, update path so that is absolute */ dataFileName = convertFilePathNameToAbsolutePathName(dataFileName); /* * Since file is being read, it must exist */ if (DataFile::isFileOnNetwork(dataFileName) == false) { FileInformation fileInfoFullPath(dataFileName); if (fileInfoFullPath.exists() == false) { throw DataFileException(dataFileName, "File does not exist!"); } } CaretDataFile* caretDataFileRead = addReadOrReloadDataFile(FILE_MODE_READ, NULL, dataFileType, structure, dataFileName, markDataFileAsModified); return caretDataFileRead; } /** * Processing performed after adding or removing a data file. */ void Brain::updateAfterFilesAddedOrRemoved() { updateChartModel(); updateVolumeSliceModel(); updateWholeBrainModel(); updateSurfaceMontageModel(); updateFiberTrajectoryMatchingFiberOrientationFiles(); } /** * Load the data files selected in a spec file. * @param readSpecFileDataFilesEvent * Event containing the spec file. */ void Brain::loadFilesSelectedInSpecFile(EventSpecFileReadDataFiles* readSpecFileDataFilesEvent) { ElapsedTimer timer; timer.start(); AString errorMessage; const SpecFile* sf = readSpecFileDataFilesEvent->getSpecFile(); CaretAssert(sf); resetBrain(); try { m_specFile->clear(); *m_specFile = *sf; } catch (const DataFileException& e) { CaretLogSevere("SPEC FILE TODO: " + e.whatString()); } m_isSpecFileBeingRead = true; CaretDataFile::setFileReadingUsernameAndPassword(readSpecFileDataFilesEvent->getUsername(), readSpecFileDataFilesEvent->getPassword()); FileInformation fileInfo(sf->getFileName()); setCurrentDirectory(fileInfo.getPathName()); const int32_t numberOfFilesToRead = sf->getNumberOfFilesSelectedForLoading(); int32_t fileReadCounter = 0; EventProgressUpdate progressUpdate(0, numberOfFilesToRead, fileReadCounter, "Starting to read selected files"); EventManager::get()->sendEvent(progressUpdate.getPointer()); /* * Note: Need to read palette first since some of the individual file * reading routines update palette coloring when file is read */ const int32_t numFileGroups = sf->getNumberOfDataFileTypeGroups(); for (int32_t ig = -1; ig < numFileGroups; ig++) { const SpecFileDataFileTypeGroup* group = ((ig == -1) ? sf->getDataFileTypeGroupByType(DataFileTypeEnum::PALETTE) : sf->getDataFileTypeGroupByIndex(ig)); const DataFileTypeEnum::Enum dataFileType = group->getDataFileType(); if (ig >= 0) { if (dataFileType == DataFileTypeEnum::PALETTE) { continue; } } const int32_t numFiles = group->getNumberOfFiles(); for (int32_t iFile = 0; iFile < numFiles; iFile++) { const SpecFileDataFile* dataFileInfo = group->getFileInformation(iFile); if (dataFileInfo->isLoadingSelected()) { const AString filename = dataFileInfo->getFileName(); const StructureEnum::Enum structure = dataFileInfo->getStructure(); /* * Send event indicating progress of file reading */ FileInformation fileInfo(dataFileInfo->getFileName()); progressUpdate.setProgress(fileReadCounter, ("Reading " + fileInfo.getFileName())); EventManager::get()->sendEvent(progressUpdate.getPointer()); /* * If user cancelled, reset brain and get out! */ if (progressUpdate.isCancelled()) { resetBrain(); return; } try { readDataFile(dataFileType, structure, filename, false); } catch (const DataFileException& e) { if (errorMessage.isEmpty() == false) { errorMessage += "\n"; } errorMessage += e.whatString(); } fileReadCounter++; } } } m_specFile->clearModified(); const AString specFileName = sf->getFileName(); if (DataFile::isFileOnNetwork(specFileName)) { CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); prefs->addToPreviousSpecFiles(specFileName); } else { FileInformation specFileInfo(specFileName); if (specFileInfo.exists() && specFileInfo.isAbsolute()) { CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); prefs->addToPreviousSpecFiles(specFileName); } } if (errorMessage.isEmpty() == false) { readSpecFileDataFilesEvent->setErrorMessage(errorMessage); } m_paletteFile->setFileName(convertFilePathNameToAbsolutePathName(m_paletteFile->getFileNameNoPath())); m_paletteFile->clearModified(); /* * Reset the primary anatomical surfaces since they can get set * incorrectly when loading files */ for (std::vector::iterator bsi = m_brainStructures.begin(); bsi != m_brainStructures.end(); bsi++) { BrainStructure* bs = *bsi; bs->setPrimaryAnatomicalSurface(NULL); } /* * Initialize the overlay for ALL models */ EventModelGetAll getAllModels; EventManager::get()->sendEvent(getAllModels.getPointer()); std::vector allModels = getAllModels.getModels(); for (std::vector::iterator iter = allModels.begin(); iter != allModels.end(); iter++) { Model* mdc = *iter; mdc->initializeSelectedSurfaces(); mdc->initializeOverlays(); } /* * Initialize overlays for brain structures */ for (std::vector::iterator iter = m_brainStructures.begin(); iter != m_brainStructures.end(); iter++) { BrainStructure* bs = *iter; bs->initializeOverlays(); } CaretLogInfo("Time to read files from spec file (in Brain) \"" + sf->getFileNameNoPath() + "\" was " + AString::number(timer.getElapsedTimeSeconds()) + " seconds."); m_isSpecFileBeingRead = false; CaretDataFile::setFileReadingUsernameAndPassword("", ""); } /** * Load files from the given spec file. * @param specFileToLoad * Spec file from which selected files are read. * @param keepSceneFiles * Controls clearing of scene files * @param keepSpecFile * Controls clearing of spec file */ void Brain::loadSpecFileFromScene(const SceneAttributes* sceneAttributes, SpecFile* specFileToLoad, const ResetBrainKeepSceneFiles keepSceneFiles, const ResetBrainKeepSpecFile keepSpecFile) { CaretAssert(specFileToLoad); EventProgressUpdate progressEvent(-1, -1, -1, "Resetting brain"); EventManager::get()->sendEvent(progressEvent.getPointer()); resetBrainKeepSceneFiles(); /* * Try to set to current directory */ const AString previousSpecFileName = m_specFile->getFileName(); delete m_specFile; m_specFile = new SpecFile(*specFileToLoad); FileInformation newSpecFileInfo(m_specFile->getFileName()); if (newSpecFileInfo.isAbsolute()) { setCurrentDirectory(newSpecFileInfo.getPathName()); } else { if (previousSpecFileName.endsWith(m_specFile->getFileName()) == false) { FileInformation oldSpecFileInfo(previousSpecFileName); setCurrentDirectory(oldSpecFileInfo.getPathName()); } } /* * Check to see if existing spec file exists */ FileInformation specFileInfo(specFileToLoad->getFileName()); const bool specFileValid = specFileInfo.exists(); /* * Apply spec file pulled from scene */ m_isSpecFileBeingRead = true; /* * Set current directory to directory containing scene file * but only if there is NOT a valid spec file */ const AString sceneFileName = sceneAttributes->getSceneFileName(); const bool sceneFileOnNetwork = DataFile::isFileOnNetwork(sceneFileName); if (specFileValid == false) { FileInformation sceneFileInfo(sceneFileName); if (sceneFileInfo.exists()) { setCurrentDirectory(sceneFileInfo.getPathName()); } } progressEvent.setProgressMessage("Loading data files"); EventManager::get()->sendEvent(progressEvent.getPointer()); if (progressEvent.isCancelled()) { resetBrain(keepSceneFiles, keepSpecFile); return; } /* * To speed file loading, non-modified files that were in memory * prior to restoring the scene are saved. This map matches * an entry of a selected file to one of the non-modified in * memory data files. */ std::map specFilesEntryToNonModifiedFile; /* * Find non-modified files that match, by name, files that are to be * loaded from the spec file and associate them for later use. */ if ( ! m_nonModifiedFilesForRestoringScene.empty()) { const int32_t numFileGroups = specFileToLoad->getNumberOfDataFileTypeGroups(); for (int32_t ig = 0; ig < numFileGroups; ig++) { const SpecFileDataFileTypeGroup* group = specFileToLoad->getDataFileTypeGroupByIndex(ig); const int32_t numFiles = group->getNumberOfFiles(); for (int32_t iFile = 0; iFile < numFiles; iFile++) { const SpecFileDataFile* fileInfo = group->getFileInformation(iFile); if (fileInfo->isLoadingSelected()) { AString filename = fileInfo->getFileName(); for (std::vector::iterator iter = m_nonModifiedFilesForRestoringScene.begin(); iter != m_nonModifiedFilesForRestoringScene.end(); iter++) { CaretDataFile* caretDataFile = *iter; if (caretDataFile != NULL) { const AString nonModifiedFileName = caretDataFile->getFileName(); if (nonModifiedFileName == filename) { specFilesEntryToNonModifiedFile.insert(std::make_pair(fileInfo, caretDataFile)); *iter = NULL; CaretLogFine("Scene loading matched previous file: " + filename); } } } } } } } /* * Delete any of the files that were in memory prior to loading the scene * that are not part of the scene being loaded. */ for (std::vector::iterator iter = m_nonModifiedFilesForRestoringScene.begin(); iter != m_nonModifiedFilesForRestoringScene.end(); iter++) { CaretDataFile* caretDataFile = *iter; if (caretDataFile != NULL) { CaretLogFine("Scene loading removing previous file not needed: " + caretDataFile->getFileName()); delete caretDataFile; } } m_nonModifiedFilesForRestoringScene.clear(); /* * Load new files and add existing files that were previously loaded. */ const int32_t numFileGroups = specFileToLoad->getNumberOfDataFileTypeGroups(); for (int32_t ig = 0; ig < numFileGroups; ig++) { const SpecFileDataFileTypeGroup* group = specFileToLoad->getDataFileTypeGroupByIndex(ig); const DataFileTypeEnum::Enum dataFileType = group->getDataFileType(); const int32_t numFiles = group->getNumberOfFiles(); for (int32_t iFile = 0; iFile < numFiles; iFile++) { const SpecFileDataFile* fileInfo = group->getFileInformation(iFile); if (fileInfo->isLoadingSelected()) { try { AString filename = fileInfo->getFileName(); std::map::iterator specToFileIter = specFilesEntryToNonModifiedFile.find(fileInfo); if (specToFileIter != specFilesEntryToNonModifiedFile.end()) { const QString msg = ("Adding previous file " + FileInformation(filename).getFileName()); progressEvent.setProgressMessage(msg); EventManager::get()->sendEvent(progressEvent.getPointer()); if (progressEvent.isCancelled()) { resetBrain(keepSceneFiles, keepSpecFile); return; } CaretDataFile* caretDataFile = specToFileIter->second; addReadOrReloadDataFile(FILE_MODE_ADD, caretDataFile, caretDataFile->getDataFileType(), caretDataFile->getStructure(), filename, false); } else { const StructureEnum::Enum structure = fileInfo->getStructure(); const QString msg = ("Loading " + FileInformation(filename).getFileName()); progressEvent.setProgressMessage(msg); EventManager::get()->sendEvent(progressEvent.getPointer()); if (progressEvent.isCancelled()) { resetBrain(keepSceneFiles, keepSpecFile); return; } if (sceneFileOnNetwork) { if (DataFile::isFileOnNetwork(filename) == false) { const int32_t lastSlashIndex = sceneFileName.lastIndexOf("/"); if (lastSlashIndex >= 0) { const AString newName = (sceneFileName.left(lastSlashIndex) + "/" + filename); filename = newName; } } } readDataFile(dataFileType, structure, filename, false); } } catch (const DataFileException& e) { sceneAttributes->addToErrorMessage(e.whatString()); } } } } if (m_paletteFile != NULL) { delete m_paletteFile; } m_paletteFile = new PaletteFile(); m_paletteFile->setFileName(convertFilePathNameToAbsolutePathName(m_paletteFile->getFileNameNoPath())); m_paletteFile->clearModified(); progressEvent.setProgressMessage("Initializing Overlays"); EventManager::get()->sendEvent(progressEvent.getPointer()); if (progressEvent.isCancelled()) { resetBrain(keepSceneFiles, keepSpecFile); return; } /* * Initialize the overlay for ALL models */ EventModelGetAll getAllModels; EventManager::get()->sendEvent(getAllModels.getPointer()); std::vector allModels = getAllModels.getModels(); for (std::vector::iterator iter = allModels.begin(); iter != allModels.end(); iter++) { Model* mdc = *iter; mdc->initializeOverlays(); } /* * Initialize overlays for brain structures */ for (std::vector::iterator iter = m_brainStructures.begin(); iter != m_brainStructures.end(); iter++) { BrainStructure* bs = *iter; bs->initializeOverlays(); } } /** * If the file is NOT an absolute path, the name of the file path is updated * to include the current directory. * * @param caretDataFile * File that may have its name updated. */ void Brain::convertDataFilePathNameToAbsolutePathName(CaretDataFile* caretDataFile) const { CaretAssert(caretDataFile); const AString newFileName = convertFilePathNameToAbsolutePathName(caretDataFile->getFileName()); caretDataFile->setFileName(newFileName); } /** * Exampine the file path name to determine if it is an * absolute or relative path. If it is a relative * path, convert it to an absolute path. * * If the file is on the network (starts with "http:"), that is considered * an absolute path and the file name is not changed. * * @param filename * Name of file. * @return * If input filename was absolute path, it is returned with * no changes. Otherwise, the name is returned after * updating it to an absolute path. */ AString Brain::convertFilePathNameToAbsolutePathName(const AString& filename) const { /* * If file is on network, is is considered an absolute path */ if (DataFile::isFileOnNetwork(filename)) { return filename; } FileInformation fileInfo(filename); if (fileInfo.isAbsolute()) { return filename; } if (m_currentDirectory.isEmpty()) { return filename; } FileInformation pathFileInfo(m_currentDirectory, filename); AString fullPathName = pathFileInfo.getAbsoluteFilePath(); return fullPathName; } /** * Receive events from the event manager. * * @param event * The event. */ void Brain::receiveEvent(Event* event) { if (event->getEventType() == EventTypeEnum::EVENT_DATA_FILE_ADD) { EventDataFileAdd* addDataFileEvent = dynamic_cast(event); CaretAssert(addDataFileEvent); try { addDataFile(addDataFileEvent->getCaretDataFile()); } catch (const DataFileException& dfe) { addDataFileEvent->setErrorMessage(dfe.whatString()); } addDataFileEvent->setEventProcessed(); } else if (event->getEventType() == EventTypeEnum::EVENT_DATA_FILE_DELETE) { EventDataFileDelete* deleteDataFileEvent = dynamic_cast(event); CaretAssert(deleteDataFileEvent); removeAndDeleteDataFile(deleteDataFileEvent->getCaretDataFile()); deleteDataFileEvent->setEventProcessed(); } else if (event->getEventType() == EventTypeEnum::EVENT_DATA_FILE_READ) { EventDataFileRead* readDataFileEvent = dynamic_cast(event); CaretAssert(readDataFileEvent); /* * Make sure event is for this brain */ if (readDataFileEvent->getLoadIntoBrain() == this) { readDataFileEvent->setEventProcessed(); processReadDataFileEvent(readDataFileEvent); } } else if (event->getEventType() == EventTypeEnum::EVENT_DATA_FILE_RELOAD) { EventDataFileReload* reloadDataFileEvent = dynamic_cast(event); CaretAssert(reloadDataFileEvent); if (reloadDataFileEvent->getBrain() == this) { reloadDataFileEvent->setEventProcessed(); processReloadDataFileEvent(reloadDataFileEvent); } } else if (event->getEventType() == EventTypeEnum::EVENT_CARET_MAPPABLE_DATA_FILES_GET) { EventCaretMappableDataFilesGet* dataFilesEvent = dynamic_cast(event); CaretAssert(dataFilesEvent); std::vector allCaretMappableFiles; getAllMappableDataFiles(allCaretMappableFiles); for (std::vector::iterator iter = allCaretMappableFiles.begin(); iter != allCaretMappableFiles.end(); iter++) { dataFilesEvent->addFile(*iter); } dataFilesEvent->setEventProcessed(); } else if (event->getEventType() == EventTypeEnum::EVENT_SPEC_FILE_READ_DATA_FILES) { EventSpecFileReadDataFiles* readSpecFileDataFilesEvent = dynamic_cast(event); CaretAssert(readSpecFileDataFilesEvent); /* * Make sure event is for this brain */ if (readSpecFileDataFilesEvent->getLoadIntoBrain() == this) { readSpecFileDataFilesEvent->setEventProcessed(); loadFilesSelectedInSpecFile(readSpecFileDataFilesEvent); } } else if (event->getEventType() == EventTypeEnum::EVENT_GET_DISPLAYED_DATA_FILES) { EventGetDisplayedDataFiles* displayedFilesEvent = dynamic_cast(event); CaretAssert(displayedFilesEvent); /* * Get all visible browser tabs. */ EventBrowserTabGetAll getAllTabsEvent; EventManager::get()->sendEvent(getAllTabsEvent.getPointer()); std::set dataFilesDisplayedInTabs; /* * Get files displayed in each tab. */ const int32_t numberOfTabs = getAllTabsEvent.getNumberOfBrowserTabs(); for (int32_t i = 0; i < numberOfTabs; i++) { BrowserTabContent* btc = getAllTabsEvent.getBrowserTab(i); const int32_t tabIndex = btc->getTabNumber(); if (displayedFilesEvent->isTestForDisplayedDataFileInTabIndex(tabIndex)) { std::vector tabDataFiles; btc->getFilesDisplayedInTab(tabDataFiles); dataFilesDisplayedInTabs.insert(tabDataFiles.begin(), tabDataFiles.end()); } } /* * See if any palette mappable files are displayed */ bool havePaletteMappableFiles = false; for (std::set::const_iterator iter = dataFilesDisplayedInTabs.begin(); iter != dataFilesDisplayedInTabs.end(); iter++) { const CaretMappableDataFile* mappableFile = dynamic_cast(*iter); if (mappableFile != NULL) { if (mappableFile->isMappedWithPalette()) { havePaletteMappableFiles = true; break; } } } /* * If there are ANY palette mappable data files, add the * palette file. */ if (havePaletteMappableFiles) { dataFilesDisplayedInTabs.insert(m_paletteFile); } for (std::set::const_iterator iter = dataFilesDisplayedInTabs.begin(); iter != dataFilesDisplayedInTabs.end(); iter++) { displayedFilesEvent->addDisplayedDataFile(*iter); } } else if (event->getEventType() == EventTypeEnum::EVENT_PALETTE_GET_BY_NAME) { EventPaletteGetByName* paletteGetByName = dynamic_cast(event); CaretAssert(paletteGetByName); if (m_paletteFile != NULL) { Palette* palette = m_paletteFile->getPaletteByName(paletteGetByName->getPaletteName()); if (palette != NULL) { paletteGetByName->setPalette(palette); paletteGetByName->setEventProcessed(); } } } } /** * @return The chart model (warning may be NULL!) */ ModelChart* Brain::getChartModel() { return m_modelChart; } /** * @return The chart model (warning may be NULL!) */ const ModelChart* Brain::getChartModel() const { return m_modelChart; } /** * @return The charting data manager. */ ChartingDataManager* Brain::getChartingDataManager() { return m_chartingDataManager; } /** * @return The charting data manager. */ const ChartingDataManager* Brain::getChartingDataManager() const { return m_chartingDataManager; } /** * @return The current directory. */ AString Brain::getCurrentDirectory() const { if (m_currentDirectory.isEmpty()) { m_currentDirectory = SystemUtilities::systemCurrentDirectory(); } return m_currentDirectory; } /** * Set the current directory. * @param currentDirectory * New value for current directory. */ void Brain::setCurrentDirectory(const AString& currentDirectory) { m_currentDirectory = currentDirectory; } /** * Get All CaretMappableDataFiles. * * @param allCaretMappableDataFilesOut * Will contain instance of CaretMappableDataFiles upon exit. */ void Brain::getAllMappableDataFiles(std::vector& allCaretMappableDataFilesOut) const { allCaretMappableDataFilesOut.clear(); std::vector allDataFiles; getAllDataFiles(allDataFiles); for (std::vector::iterator iter = allDataFiles.begin(); iter != allDataFiles.end(); iter++) { CaretDataFile* cdf = *iter; CaretMappableDataFile* cmdf = dynamic_cast(cdf); if (cmdf != NULL) { allCaretMappableDataFilesOut.push_back(cmdf); } } } /** * Get All CaretMappableDataFiles of the given data file type. * * @param dataFileType * Type of data file. * @param caretMappableDataFilesOut * Contains CaretMappableDataFiles matching data file type upon exit. */ void Brain::getAllMappableDataFileWithDataFileType(const DataFileTypeEnum::Enum dataFileType, std::vector& caretMappableDataFilesOut) const { caretMappableDataFilesOut.clear(); std::vector allFiles; getAllMappableDataFiles(allFiles); for (std::vector::iterator iter = allFiles.begin(); iter != allFiles.end(); iter++) { CaretMappableDataFile* cmdf = *iter; if (cmdf->getDataFileType() == dataFileType) { caretMappableDataFilesOut.push_back(cmdf); } } } /** * Get All CaretMappableDataFiles of the given data file types. * * @param dataFileType * Type of data file. * @param caretMappableDataFilesOut * Contains CaretMappableDataFiles matching data file type upon exit. */ void Brain::getAllMappableDataFileWithDataFileTypes(const std::vector& dataFileTypes, std::vector& caretMappableDataFilesOut) const { caretMappableDataFilesOut.clear(); std::vector allFiles; getAllMappableDataFiles(allFiles); for (std::vector::iterator iter = allFiles.begin(); iter != allFiles.end(); iter++) { CaretMappableDataFile* cmdf = *iter; if (std::find(dataFileTypes.begin(), dataFileTypes.end(), cmdf->getDataFileType()) != dataFileTypes.end()) { caretMappableDataFilesOut.push_back(cmdf); } } } /** * Get all CaretDataFiles of the given data file types. * * @param dataFileTypes * Types of data files. * @param caretDataFilesOut * Data file of the given data file type that were found. */ void Brain::getAllDataFilesWithDataFileTypes(const std::vector& dataFileTypes, std::vector& caretDataFilesOut) const { caretDataFilesOut.clear(); std::vector allDataFiles; getAllDataFiles(allDataFiles, true); for (std::vector::iterator iter = allDataFiles.begin(); iter != allDataFiles.end(); iter++) { CaretDataFile* cdf = *iter; if (std::find(dataFileTypes.begin(), dataFileTypes.end(), cdf->getDataFileType()) != dataFileTypes.end()) { caretDataFilesOut.push_back(cdf); } } } /** * Get all CaretDataFiles of the given data file type. * * @param dataFileType * Type of data file. * @param caretDataFilesOut * Data file of the given data file type that were found. */ void Brain::getAllDataFilesWithDataFileType(const DataFileTypeEnum::Enum dataFileType, std::vector& caretDataFilesOut) const { std::vector dataFileTypes; dataFileTypes.push_back(dataFileType); getAllDataFilesWithDataFileTypes(dataFileTypes, caretDataFilesOut); // caretDataFilesOut.clear(); // // std::vector allDataFiles; // getAllDataFiles(allDataFiles, // true); // // for (std::vector::iterator iter = allDataFiles.begin(); // iter != allDataFiles.end(); // iter++) { // CaretDataFile* cdf = *iter; // if (cdf->getDataFileType() == dataFileType) { // caretDataFilesOut.push_back(cdf); // } // } } /** * Get all loaded data files. * @param allDataFilesOut * Data files are loaded into this parameter. * @param includeSpecFile * If true, the spec file is included as the first file. */ void Brain::getAllDataFiles(std::vector& allDataFilesOut, const bool includeSpecFile) const { allDataFilesOut.clear(); if (includeSpecFile) { if (m_specFile->isEmpty() == false) { allDataFilesOut.push_back(m_specFile); } } const int32_t numBrainStructures = getNumberOfBrainStructures(); for (int32_t i = 0; i < numBrainStructures; i++) { getBrainStructure(i)->getAllDataFiles(allDataFilesOut); } allDataFilesOut.insert(allDataFilesOut.end(), m_borderFiles.begin(), m_borderFiles.end()); allDataFilesOut.insert(allDataFilesOut.end(), m_fociFiles.begin(), m_fociFiles.end()); allDataFilesOut.insert(allDataFilesOut.end(), m_imageFiles.begin(), m_imageFiles.end()); allDataFilesOut.insert(allDataFilesOut.end(), m_connectivityDenseScalarFiles.begin(), m_connectivityDenseScalarFiles.end()); allDataFilesOut.insert(allDataFilesOut.end(), m_connectivityMatrixDenseFiles.begin(), m_connectivityMatrixDenseFiles.end()); allDataFilesOut.insert(allDataFilesOut.end(), m_connectivityDataSeriesFiles.begin(), m_connectivityDataSeriesFiles.end()); allDataFilesOut.insert(allDataFilesOut.end(), m_connectivityDenseLabelFiles.begin(), m_connectivityDenseLabelFiles.end()); allDataFilesOut.insert(allDataFilesOut.end(), m_connectivityMatrixDenseParcelFiles.begin(), m_connectivityMatrixDenseParcelFiles.end()); allDataFilesOut.insert(allDataFilesOut.end(), m_connectivityFiberOrientationFiles.begin(), m_connectivityFiberOrientationFiles.end()); allDataFilesOut.insert(allDataFilesOut.end(), m_connectivityFiberTrajectoryFiles.begin(), m_connectivityFiberTrajectoryFiles.end()); allDataFilesOut.insert(allDataFilesOut.end(), m_connectivityMatrixParcelFiles.begin(), m_connectivityMatrixParcelFiles.end()); allDataFilesOut.insert(allDataFilesOut.end(), m_connectivityMatrixParcelDenseFiles.begin(), m_connectivityMatrixParcelDenseFiles.end()); allDataFilesOut.insert(allDataFilesOut.end(), m_connectivityParcelLabelFiles.begin(), m_connectivityParcelLabelFiles.end()); allDataFilesOut.insert(allDataFilesOut.end(), m_connectivityParcelScalarFiles.begin(), m_connectivityParcelScalarFiles.end()); allDataFilesOut.insert(allDataFilesOut.end(), m_connectivityParcelSeriesFiles.begin(), m_connectivityParcelSeriesFiles.end()); allDataFilesOut.insert(allDataFilesOut.end(), m_connectivityScalarDataSeriesFiles.begin(), m_connectivityScalarDataSeriesFiles.end()); allDataFilesOut.push_back(m_paletteFile); allDataFilesOut.insert(allDataFilesOut.end(), m_sceneFiles.begin(), m_sceneFiles.end()); allDataFilesOut.insert(allDataFilesOut.end(), m_volumeFiles.begin(), m_volumeFiles.end()); } /** * Determine if a file is still valid (pointer is for an existing data * of the same DataFileType. */ bool Brain::isFileValid(const CaretDataFile* caretDataFile) const { std::vector allDataFiles; getAllDataFiles(allDataFiles); for (std::vector::iterator iter = allDataFiles.begin(); iter != allDataFiles.end(); iter++) { const CaretDataFile* cdf = *iter; if (caretDataFile == cdf) { if (caretDataFile->getDataFileType() == cdf->getDataFileType()) { return true; } } } return false; } /** * Get all of the modified files excluding the given data file types. * * @param excludeTheseDataTypes * Data types of files that excluded. * @param modifiedDataFilesOut * Output containing the modified files. * */ void Brain::getAllModifiedFiles(const std::vector& excludeTheseDataTypes, std::vector& modifiedDataFilesOut) const { modifiedDataFilesOut.clear(); if (std::find(excludeTheseDataTypes.begin(), excludeTheseDataTypes.end(), DataFileTypeEnum::SPECIFICATION) == excludeTheseDataTypes.end()) { if (m_specFile->isModified()) { modifiedDataFilesOut.push_back(m_specFile); } } std::vector dataFiles; getAllDataFiles(dataFiles); for (std::vector::iterator iter = dataFiles.begin(); iter != dataFiles.end(); iter++) { CaretDataFile* cdf = *iter; /** * Ignore files whose data type is excluded. */ if (std::find(excludeTheseDataTypes.begin(), excludeTheseDataTypes.end(), cdf->getDataFileType()) == excludeTheseDataTypes.end()) { if (cdf->isModified()) { modifiedDataFilesOut.push_back(cdf); } } } } ///** // * Are any data files modified (including spec file)? // * @param excludeTheseDataTypes // * Do not check the modification status of any data files whose // * data type is contained in this parameter. // */ //bool //Brain::areFilesModified(const std::vector& excludeTheseDataTypes) //{ // if (std::find(excludeTheseDataTypes.begin(), // excludeTheseDataTypes.end(), // DataFileTypeEnum::SPECIFICATION) == excludeTheseDataTypes.end()) { // if (m_specFile->isModified()) { // return true; // } // } // // std::vector dataFiles; // getAllDataFiles(dataFiles); // // for (std::vector::iterator iter = dataFiles.begin(); // iter != dataFiles.end(); // iter++) { // CaretDataFile* cdf = *iter; // // /** // * Ignore files whose data type is excluded. // */ // if (std::find(excludeTheseDataTypes.begin(), // excludeTheseDataTypes.end(), // cdf->getDataFileType()) == excludeTheseDataTypes.end()) { // if (cdf->isModified()) { // return true; // } // } // } // // return false; //} /** * Write a data file. * @param caretDataFile * Data file to write. * @return * true if file was written, else false. * @throw * DataFileException if there was an error writing the file. */ void Brain::writeDataFile(CaretDataFile* caretDataFile) { AString dataFileName = caretDataFile->getFileName(); /* * If file is on network, it cannot be written ! */ if (DataFile::isFileOnNetwork(dataFileName)) { throw DataFileException(dataFileName, "Cannot write a file with a network path. " "To write the file, its name must be changed to " "a path on your computer. This can be done using " "the \"More\" icon on the Manage Files Dialog " "(File Menu->Save/Manage Files)."); } /* * If file is relative path, update path using current directory */ dataFileName = convertFilePathNameToAbsolutePathName(dataFileName); caretDataFile->setFileName(dataFileName); caretDataFile->writeFile(caretDataFile->getFileName()); caretDataFile->clearModified(); } /** * Remove the data file from memory but DO NOT delete it. * * @param caretDataFile * Caret file that is removed from the Brain. After calling this method * and the file was removed( true was returned), the caller is responsible * for deleting the file when it is no longer needed. * @return * True if the file was removed, else false. */ bool Brain::removeWithoutDeleteDataFile(const CaretDataFile* caretDataFile) { if (caretDataFile == NULL) { return false; } const bool wasRemoved = removeWithoutDeleteDataFilePrivate(caretDataFile); if (wasRemoved) { m_specFile->removeCaretDataFile(caretDataFile); updateAfterFilesAddedOrRemoved(); } else { CaretLogSevere("Software bug: failed to remove file type=" + DataFileTypeEnum::toName(caretDataFile->getDataFileType()) + " name=" + caretDataFile->getFileName()); } return wasRemoved; } /** * Remove the data file from memory but DO NOT delete it. * * @param caretDataFile * Caret file that is removed from the Brain. After calling this method * and the file was removed( true was returned), the caller is responsible * for deleting the file when it is no longer needed. * @return * True if the file was removed, else false. */ bool Brain::removeWithoutDeleteDataFilePrivate(const CaretDataFile* caretDataFile) { const int32_t numBrainStructures = getNumberOfBrainStructures(); for (int32_t i = 0; i < numBrainStructures; i++) { if (getBrainStructure(i)->removeWithoutDeleteDataFile(caretDataFile)) { return true; } } std::vector::iterator borderIterator = std::find(m_borderFiles.begin(), m_borderFiles.end(), caretDataFile); if (borderIterator != m_borderFiles.end()) { m_borderFiles.erase(borderIterator); return true; } std::vector::iterator dataSeriesIterator = std::find(m_connectivityDataSeriesFiles.begin(), m_connectivityDataSeriesFiles.end(), caretDataFile); if (dataSeriesIterator != m_connectivityDataSeriesFiles.end()) { m_connectivityDataSeriesFiles.erase(dataSeriesIterator); return true; } std::vector::iterator connLabelIterator = std::find(m_connectivityDenseLabelFiles.begin(), m_connectivityDenseLabelFiles.end(), caretDataFile); if (connLabelIterator != m_connectivityDenseLabelFiles.end()) { m_connectivityDenseLabelFiles.erase(connLabelIterator); return true; } std::vector::iterator connDenseIterator = std::find(m_connectivityMatrixDenseFiles.begin(), m_connectivityMatrixDenseFiles.end(), caretDataFile); if (connDenseIterator != m_connectivityMatrixDenseFiles.end()) { m_connectivityMatrixDenseFiles.erase(connDenseIterator); return true; } std::vector::iterator connDenseParcelIterator = std::find(m_connectivityMatrixDenseParcelFiles.begin(), m_connectivityMatrixDenseParcelFiles.end(), caretDataFile); if (connDenseParcelIterator != m_connectivityMatrixDenseParcelFiles.end()) { m_connectivityMatrixDenseParcelFiles.erase(connDenseParcelIterator); return true; } std::vector::iterator connScalarIterator = std::find(m_connectivityDenseScalarFiles.begin(), m_connectivityDenseScalarFiles.end(), caretDataFile); if (connScalarIterator != m_connectivityDenseScalarFiles.end()) { m_connectivityDenseScalarFiles.erase(connScalarIterator); return true; } std::vector::iterator connParcelSeriesIterator = std::find(m_connectivityParcelSeriesFiles.begin(), m_connectivityParcelSeriesFiles.end(), caretDataFile); if (connParcelSeriesIterator != m_connectivityParcelSeriesFiles.end()) { m_connectivityParcelSeriesFiles.erase(connParcelSeriesIterator); return true; } std::vector::iterator connParcelLabelIterator = std::find(m_connectivityParcelLabelFiles.begin(), m_connectivityParcelLabelFiles.end(), caretDataFile); if (connParcelLabelIterator != m_connectivityParcelLabelFiles.end()) { m_connectivityParcelLabelFiles.erase(connParcelLabelIterator); return true; } std::vector::iterator connParcelScalarIterator = std::find(m_connectivityParcelScalarFiles.begin(), m_connectivityParcelScalarFiles.end(), caretDataFile); if (connParcelScalarIterator != m_connectivityParcelScalarFiles.end()) { m_connectivityParcelScalarFiles.erase(connParcelScalarIterator); return true; } std::vector::iterator connScalarDataSeriesIterator = std::find(m_connectivityScalarDataSeriesFiles.begin(), m_connectivityScalarDataSeriesFiles.end(), caretDataFile); if (connScalarDataSeriesIterator != m_connectivityScalarDataSeriesFiles.end()) { m_connectivityScalarDataSeriesFiles.erase(connScalarDataSeriesIterator); return true; } std::vector::iterator connFiberOrientationIterator = std::find(m_connectivityFiberOrientationFiles.begin(), m_connectivityFiberOrientationFiles.end(), caretDataFile); if (connFiberOrientationIterator != m_connectivityFiberOrientationFiles.end()) { m_connectivityFiberOrientationFiles.erase(connFiberOrientationIterator); return true; } std::vector::iterator connFiberTrajectoryIterator = std::find(m_connectivityFiberTrajectoryFiles.begin(), m_connectivityFiberTrajectoryFiles.end(), caretDataFile); if (connFiberTrajectoryIterator != m_connectivityFiberTrajectoryFiles.end()) { m_connectivityFiberTrajectoryFiles.erase(connFiberTrajectoryIterator); return true; } std::vector::iterator connParcelIterator = std::find(m_connectivityMatrixParcelFiles.begin(), m_connectivityMatrixParcelFiles.end(), caretDataFile); if (connParcelIterator != m_connectivityMatrixParcelFiles.end()) { m_connectivityMatrixParcelFiles.erase(connParcelIterator); return true; } std::vector::iterator connParcelDenseIterator = std::find(m_connectivityMatrixParcelDenseFiles.begin(), m_connectivityMatrixParcelDenseFiles.end(), caretDataFile); if (connParcelDenseIterator != m_connectivityMatrixParcelDenseFiles.end()) { m_connectivityMatrixParcelDenseFiles.erase(connParcelDenseIterator); return true; } std::vector::iterator fociIterator = std::find(m_fociFiles.begin(), m_fociFiles.end(), caretDataFile); if (fociIterator != m_fociFiles.end()) { m_fociFiles.erase(fociIterator); return true; } std::vector::iterator imageIterator = std::find(m_imageFiles.begin(), m_imageFiles.end(), caretDataFile); if (imageIterator != m_imageFiles.end()) { m_imageFiles.erase(imageIterator); return true; } if (m_paletteFile == caretDataFile) { if (m_paletteFile != NULL) { CaretLogSevere("Cannot remove PaletteFile at this time."); } } std::vector::iterator sceneIterator = std::find(m_sceneFiles.begin(), m_sceneFiles.end(), caretDataFile); if (sceneIterator != m_sceneFiles.end()) { m_sceneFiles.erase(sceneIterator); return true; } std::vector::iterator volumeIterator = std::find(m_volumeFiles.begin(), m_volumeFiles.end(), caretDataFile); if (volumeIterator != m_volumeFiles.end()) { m_volumeFiles.erase(volumeIterator); return true; } return false; } /** * Remove AND DELETE a data file from memory (does NOT delete file on disk.) * Searches all of the loaded files for given file, and, when found * deletes the file. * * @param caretDataFile * Data file to remove. After calling this method and the file was * deleted (true was returned) this pointer is no longer valid. * @return * true if file was removed, else false. */ bool Brain::removeAndDeleteDataFile(CaretDataFile* caretDataFile) { if (removeWithoutDeleteDataFile(caretDataFile)) { delete caretDataFile; return true; } return false; } /** * @return The border display properties. */ DisplayPropertiesBorders* Brain::getDisplayPropertiesBorders() { return m_displayPropertiesBorders; } /** * @return The border display properties. */ const DisplayPropertiesBorders* Brain::getDisplayPropertiesBorders() const { return m_displayPropertiesBorders; } /** * @return The fiber orientation display properties. */ DisplayPropertiesFiberOrientation* Brain::getDisplayPropertiesFiberOrientation() { return m_displayPropertiesFiberOrientation; } /** * @return The fiber orientation display properties. */ const DisplayPropertiesFiberOrientation* Brain::getDisplayPropertiesFiberOrientation() const { return m_displayPropertiesFiberOrientation; } /** * @return The foci display properties. */ DisplayPropertiesFoci* Brain::getDisplayPropertiesFoci() { return m_displayPropertiesFoci; } /** * @return The foci display properties. */ const DisplayPropertiesFoci* Brain::getDisplayPropertiesFoci() const { return m_displayPropertiesFoci; } /** * @return The label display properties. */ DisplayPropertiesImages* Brain::getDisplayPropertiesImages() { return m_displayPropertiesImages; } /** * @return The label display properties. */ const DisplayPropertiesImages* Brain::getDisplayPropertiesImages() const { return m_displayPropertiesImages; } /** * @return The label display properties. */ DisplayPropertiesLabels* Brain::getDisplayPropertiesLabels() { return m_displayPropertiesLabels; } /** * @return The label display properties. */ const DisplayPropertiesLabels* Brain::getDisplayPropertiesLabels() const { return m_displayPropertiesLabels; } /** * @return The volume display properties. */ DisplayPropertiesVolume* Brain::getDisplayPropertiesVolume() { return m_displayPropertiesVolume; } /** * @return The volume display properties. */ const DisplayPropertiesVolume* Brain::getDisplayPropertiesVolume() const { return m_displayPropertiesVolume; } /** * @return The surface display properties. */ DisplayPropertiesSurface* Brain::getDisplayPropertiesSurface() { return m_displayPropertiesSurface; } /** * @return The volume display properties. */ const DisplayPropertiesSurface* Brain::getDisplayPropertiesSurface() const { return m_displayPropertiesSurface; } /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ SceneClass* Brain::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { bool isSaveSpecFile = false; switch (sceneAttributes->getSceneType()) { case SceneTypeEnum::SCENE_TYPE_FULL: isSaveSpecFile = true; break; case SceneTypeEnum::SCENE_TYPE_GENERIC: break; } SceneClass* sceneClass = new SceneClass(instanceName, "Brain", 1); /* * Get all data files */ std::vector allCaretDataFiles; getAllDataFiles(allCaretDataFiles); /* * Save data files into an array. * Note that data file's saveToScene returns NULL if no data for saving. */ std::vector allCaretDataFileScenes; for (std::vector::iterator iter = allCaretDataFiles.begin(); iter != allCaretDataFiles.end(); iter++) { CaretDataFile* cdf = *iter; const AString caretDataFileName = cdf->getFileName(); // use full path 7/16/2015 cdf->getFileNameNoPath(); SceneClass* caretDataFileSceneClass = cdf->saveToScene(sceneAttributes, caretDataFileName); if (caretDataFileSceneClass != NULL) { allCaretDataFileScenes.push_back(caretDataFileSceneClass); } } if (allCaretDataFileScenes.empty() == false) { SceneClassArray* caretDataFileSceneArray = new SceneClassArray("allCaretDataFiles", allCaretDataFileScenes); sceneClass->addChild(caretDataFileSceneArray); } if (isSaveSpecFile) { SpecFile sf; sf.setFileName(m_specFile->getFileName()); for (std::vector::iterator iter = allCaretDataFiles.begin(); iter != allCaretDataFiles.end(); iter++) { CaretDataFile* cdf = *iter; sf.addCaretDataFile(cdf); } sceneClass->addClass(sf.saveToScene(sceneAttributes, "specFile")); } m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); /* * Save all models */ std::vector modelClassVector; EventModelGetAll getAllModels; EventManager::get()->sendEvent(getAllModels.getPointer()); std::vector allModels = getAllModels.getModels(); for (std::vector::iterator iter = allModels.begin(); iter != allModels.end(); iter++) { Model* mdc = *iter; modelClassVector.push_back(mdc->saveToScene(sceneAttributes, "models")); } SceneClassArray* modelsClassArray = new SceneClassArray("models", modelClassVector); sceneClass->addChild(modelsClassArray); /* * Save all brain structures */ const int32_t numBrainStructures = getNumberOfBrainStructures(); SceneClassArray* brainStructureClassArray = new SceneClassArray("m_brainStructures", numBrainStructures); for (int32_t i = 0; i < numBrainStructures; i++) { const AString name = ("m_brainStructures[" + AString::number(i) + "]"); brainStructureClassArray->setClassAtIndex(i, m_brainStructures[i]->saveToScene(sceneAttributes, name)); } sceneClass->addChild(brainStructureClassArray); /* * Save Group/Name Selection Hierarchies */ for (std::vector::iterator borderIter = m_borderFiles.begin(); borderIter != m_borderFiles.end(); borderIter++) { BorderFile* bf = *borderIter; sceneClass->addClass(bf->getGroupAndNameHierarchyModel()->saveToScene(sceneAttributes, bf->getFileNameNoPath())); } for (std::vector::iterator fociIter = m_fociFiles.begin(); fociIter != m_fociFiles.end(); fociIter++) { FociFile* ff = *fociIter; sceneClass->addClass(ff->getGroupAndNameHierarchyModel()->saveToScene(sceneAttributes, ff->getFileNameNoPath())); } sceneClass->addClass(m_identificationManager->saveToScene(sceneAttributes, "m_identificationManager")); sceneClass->addClass(m_brainordinateHighlightRegionOfInterest->saveToScene(sceneAttributes, "m_brainordinateHighlightRegionOfInterest")); return sceneClass; } /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass for the instance of a class that implements * this interface. May be NULL for some types of scenes. */ void Brain::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } bool isLoadFiles = false; switch (sceneAttributes->getSceneType()) { case SceneTypeEnum::SCENE_TYPE_FULL: isLoadFiles = true; break; case SceneTypeEnum::SCENE_TYPE_GENERIC: break; } if (isLoadFiles) { SpecFile specFile; specFile.restoreFromScene(sceneAttributes, sceneClass->getClass("specFile")); loadSpecFileFromScene(sceneAttributes, &specFile, RESET_BRAIN_KEEP_SCENE_FILES_YES, RESET_BRAIN_KEEP_SPEC_FILE_YES); } /* * Add all scene files to the spec file (but not a member of * the spec file) since a scene file may not be in the spec file. */ const bool specFileModStatus = m_specFile->isModified(); for (std::vector::iterator sceneIter = m_sceneFiles.begin(); sceneIter != m_sceneFiles.end(); sceneIter++) { m_specFile->addCaretDataFile(*sceneIter); } if (specFileModStatus == false) { m_specFile->clearModified(); } /* * Get all data files */ std::vector allCaretDataFiles; getAllDataFiles(allCaretDataFiles); /* * Restore data files */ const SceneClassArray* caretDataFileSceneArray = sceneClass->getClassArray("allCaretDataFiles"); if (caretDataFileSceneArray != NULL) { for (std::vector::iterator iter = allCaretDataFiles.begin(); iter != allCaretDataFiles.end(); iter++) { CaretDataFile* caretDataFile = *iter; CaretAssert(caretDataFile); const AString caretDataFileNameNoPath = caretDataFile->getFileNameNoPath(); const AString caretDataFileNameFullPath = caretDataFile->getFileName(); SceneClass* bestMatchingSceneClass = NULL; int64_t bestMatchingCount = 0; const int32_t numCaretDataFileScenes = caretDataFileSceneArray->getNumberOfArrayElements(); for (int32_t i = 0; i < numCaretDataFileScenes; i++) { const SceneClass* fileSceneClass = caretDataFileSceneArray->getClassAtIndex(i); const AString fileNameFullPath = fileSceneClass->getName(); const FileInformation fileInfo(fileNameFullPath); const AString fileNameNoPath = fileInfo.getFileName(); if (caretDataFileNameNoPath == fileNameNoPath) { const int64_t matchCount = caretDataFileNameFullPath.countMatchingCharactersFromEnd(fileNameFullPath); if (matchCount > bestMatchingCount) { bestMatchingSceneClass = const_cast(fileSceneClass); bestMatchingCount = matchCount; } // caretDataFile->restoreFromScene(sceneAttributes, // fileSceneClass); } } if (bestMatchingCount > 0) { CaretAssert(bestMatchingSceneClass); caretDataFile->restoreFromScene(sceneAttributes, bestMatchingSceneClass); } } } /* * Fiber trajectory files need special handling after restoring a scene. */ updateFiberTrajectoryMatchingFiberOrientationFiles(); for (std::vector::iterator iter = m_connectivityFiberTrajectoryFiles.begin(); iter != m_connectivityFiberTrajectoryFiles.end(); iter++) { CiftiFiberTrajectoryFile* trajFile = *iter; trajFile->finishRestorationOfScene(); } /* * Restore members */ m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); /* * Need to color all connectivity matrix files */ std::vector ciftiMatrixFiles; getAllCiftiConnectivityMatrixFiles(ciftiMatrixFiles); for (std::vector::iterator iter = ciftiMatrixFiles.begin(); iter != ciftiMatrixFiles.end(); iter++) { CiftiMappableConnectivityMatrixDataFile* cmf = *iter; if (cmf->isEmpty() == false) { const int32_t mapIndex = 0; cmf->updateScalarColoringForMap(mapIndex, getPaletteFile()); } } /* * Restore all models */ EventModelGetAll getAllModels; EventManager::get()->sendEvent(getAllModels.getPointer()); std::vector allModels = getAllModels.getModels(); const SceneClassArray* modelsClassArray = sceneClass->getClassArray("models"); const int32_t numModelClasses = modelsClassArray->getNumberOfArrayElements(); for (int32_t i = 0; i < numModelClasses; i++) { /* * Apply to all models. Each model will only use the saved * scene information that is applicable. */ for (std::vector::iterator iter = allModels.begin(); iter != allModels.end(); iter++) { Model* mdc = *iter; mdc->restoreFromScene(sceneAttributes, modelsClassArray->getClassAtIndex(i)); } } /* * Apply each of the saved brain structures to each of the loaded brain structures. * The loaded brain structure will examine the structure and number of nodes in each * saved brain structure to determine the proper one to use for restoration */ const SceneClassArray* brainStructureClassArray = sceneClass->getClassArray("m_brainStructures"); const int32_t numSavedBrainStructures = brainStructureClassArray->getNumberOfArrayElements(); const int32_t numValidBrainStructures = getNumberOfBrainStructures(); for (int32_t i = 0; i < numValidBrainStructures; i++) { BrainStructure* bs = m_brainStructures[i]; for (int32_t j = 0; j < numSavedBrainStructures; j++) { bs->restoreFromScene(sceneAttributes, brainStructureClassArray->getClassAtIndex(j)); } } switch (sceneAttributes->getSceneType()) { case SceneTypeEnum::SCENE_TYPE_FULL: break; case SceneTypeEnum::SCENE_TYPE_GENERIC: break; } /* * Restore Group/Name Selection Hierarchies */ for (std::vector::iterator borderIter = m_borderFiles.begin(); borderIter != m_borderFiles.end(); borderIter++) { BorderFile* bf = *borderIter; const SceneClass* borderScene = sceneClass->getClass(bf->getFileNameNoPath()); if (borderScene != NULL) { /* * WB-533 Default State of Borders in Scenes * * When there is scene information for a border file, disable the display * of all classes and names prior to restoring the class/name hierarchy. * The purpose of this is to prevent the display of borders that have been * added to the border file AFTER the scene was created. */ GroupAndNameHierarchyModel* groupAndNameModel = bf->getGroupAndNameHierarchyModel(); groupAndNameModel->setAllSelected(false); bf->getGroupAndNameHierarchyModel()->restoreFromScene(sceneAttributes, borderScene); } } for (std::vector::iterator fociIter = m_fociFiles.begin(); fociIter != m_fociFiles.end(); fociIter++) { FociFile* ff = *fociIter; ff->getGroupAndNameHierarchyModel()->restoreFromScene(sceneAttributes, sceneClass->getClass(ff->getFileNameNoPath())); } m_identificationManager->restoreFromScene(sceneAttributes, sceneClass->getClass("m_identificationManager")); m_brainordinateHighlightRegionOfInterest->restoreFromScene(sceneAttributes, sceneClass->getClass("m_brainordinateHighlightRegionOfInterest")); } /** * @return The selection manager. */ SelectionManager* Brain::getSelectionManager() { return m_selectionManager; } /** * @return The identification manager. */ IdentificationManager* Brain::getIdentificationManager() { return m_identificationManager; } /** Region of interest for highlighting brainordinates */ BrainordinateRegionOfInterest* Brain::getBrainordinateHighlightRegionOfInterest() { return m_brainordinateHighlightRegionOfInterest; } const BrainordinateRegionOfInterest* Brain::getBrainordinateHighlightRegionOfInterest() const { return m_brainordinateHighlightRegionOfInterest; } /** * Get the fiber orientation sample vectors for display on a sphere. * * @param xVectors * Vectors for X-orientation. * @param yVectors * Vectors for Y-orientation. * @param zVectors * Vectors for Z-orientation. * @param fiberOrientation * The nearby fiber orientation * @param errorMessageOut * Will contain any error messages. * This error message will only be set in some cases when there is an * error. * @return * True if data is valid, else false. */ bool Brain::getFiberOrientationSphericalSamplesVectors(std::vector& xVectors, std::vector& yVectors, std::vector& zVectors, FiberOrientation* &fiberOrientationOut, AString& errorMessageOut) { CaretAssert(m_fiberOrientationSamplesLoader); return m_fiberOrientationSamplesLoader->getFiberOrientationSphericalSamplesVectors(this, xVectors, yVectors, zVectors, fiberOrientationOut, errorMessageOut); } workbench-1.1.1/src/Brain/Brain.h000066400000000000000000001063031255417355300165370ustar00rootroot00000000000000#ifndef __BRAIN_H__ #define __BRAIN_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "CaretObject.h" #include "ChartDataTypeEnum.h" #include "DataFileTypeEnum.h" #include "DisplayGroupEnum.h" #include "EventListenerInterface.h" #include "FiberOrientationSamplesLoader.h" #include "FiberOrientationSamplesVector.h" #include "FileInformation.h" #include "SceneableInterface.h" #include "StructureEnum.h" #include "VolumeFile.h" namespace caret { class Border; class BorderFile; class BorderPointFromSearch; class BrainordinateRegionOfInterest; class FociFile; class BrainStructure; class CaretDataFile; class CaretMappableDataFile; class ChartingDataManager; class ChartableLineSeriesBrainordinateInterface; class ChartableMatrixInterface; class CiftiBrainordinateDataSeriesFile; class CiftiBrainordinateLabelFile; class CiftiBrainordinateScalarFile; class CiftiConnectivityMatrixDenseFile; class CiftiConnectivityMatrixDenseParcelFile; class CiftiConnectivityMatrixParcelFile; class CiftiConnectivityMatrixParcelDenseFile; class CiftiFiberOrientationFile; class CiftiFiberTrajectoryFile; class CiftiMappableDataFile; class CiftiMappableConnectivityMatrixDataFile; class CiftiParcelLabelFile; class CiftiParcelSeriesFile; class CiftiParcelScalarFile; class CiftiScalarDataSeriesFile; class DisplayProperties; class DisplayPropertiesBorders; class DisplayPropertiesFiberOrientation; class DisplayPropertiesFoci; class DisplayPropertiesImages; class DisplayPropertiesLabels; class DisplayPropertiesSurface; class DisplayPropertiesVolume; class EventDataFileRead; class EventDataFileReload; class EventSpecFileReadDataFiles; class IdentificationManager; class ImageFile; class LabelFile; class MetricFile; class ModelChart; class ModelSurfaceMontage; class ModelVolume; class ModelWholeBrain; class PaletteFile; class RgbaFile; class SceneClassAssistant; class SceneFile; class SelectionManager; class SpecFile; class Surface; class SurfaceFile; class SurfaceProjectedItem; class VolumeFile; class Brain : public CaretObject, public EventListenerInterface, public SceneableInterface { public: Brain(); ~Brain(); private: Brain(const Brain&); Brain& operator=(const Brain&); public: int getNumberOfBrainStructures() const; void addBrainStructure(BrainStructure* brainStructure); BrainStructure* getBrainStructure(const int32_t indx); const BrainStructure* getBrainStructure(const int32_t indx) const; BrainStructure* getBrainStructure(StructureEnum::Enum structure, bool createIfNotFound); int32_t getNumberOfBorderFiles() const; BorderFile* getBorderFile(const int32_t indx); const BorderFile* getBorderFile(const int32_t indx) const; int32_t getNumberOfFociFiles() const; FociFile* getFociFile(const int32_t indx); const FociFile* getFociFile(const int32_t indx) const; const std::vector getAllImagesFiles() const; int32_t getNumberOfImageFiles() const; ImageFile* getImageFile(const int32_t indx); const ImageFile* getImageFile(const int32_t indx) const; PaletteFile* getPaletteFile(); const PaletteFile* getPaletteFile() const; int32_t getNumberOfSceneFiles() const; SceneFile* getSceneFile(const int32_t indx); const SceneFile* getSceneFile(const int32_t indx) const; const SpecFile* getSpecFile() const; SpecFile* getSpecFile(); Surface* getSurfaceWithName(const AString& surfaceFileName, const bool useAbsolutePath); const Surface* getPrimaryAnatomicalSurfaceForStructure(const StructureEnum::Enum structure) const; Surface* getPrimaryAnatomicalSurfaceNearestCoordinate(const float xyz[3], const float tolerance); std::vector getPrimaryAnatomicalSurfaces() const; std::vector getPrimaryAnatomicalSurfaceFiles() const; int32_t getNumberOfVolumeFiles() const; VolumeFile* getVolumeFile(const int32_t volumeFileIndex); const VolumeFile* getVolumeFile(const int32_t volumeFileIndex) const; void resetBrain(); void resetBrainKeepSceneFiles(); void receiveEvent(Event* event); ModelChart* getChartModel(); const ModelChart* getChartModel() const; ChartingDataManager* getChartingDataManager(); const ChartingDataManager* getChartingDataManager() const; void getAllCiftiMappableDataFiles(std::vector& allCiftiMappableDataFilesOut) const; int32_t getNumberOfConnectivityMatrixDenseFiles() const; CiftiConnectivityMatrixDenseFile* getConnectivityMatrixDenseFile(int32_t indx); const CiftiConnectivityMatrixDenseFile* getConnectivityMatrixDenseFile(int32_t indx) const; void getConnectivityMatrixDenseFiles(std::vector& connectivityDenseFilesOut) const; int32_t getNumberOfConnectivityDenseLabelFiles() const; CiftiBrainordinateLabelFile* getConnectivityDenseLabelFile(int32_t indx); const CiftiBrainordinateLabelFile* getConnectivityDenseLabelFile(int32_t indx) const; void getConnectivityDenseLabelFiles(std::vector& connectivityDenseLabelFilesOut) const; int32_t getNumberOfConnectivityMatrixDenseParcelFiles() const; CiftiConnectivityMatrixDenseParcelFile* getConnectivityMatrixDenseParcelFile(int32_t indx); const CiftiConnectivityMatrixDenseParcelFile* getConnectivityMatrixDenseParcelFile(int32_t indx) const; void getConnectivityMatrixDenseParcelFiles(std::vector& connectivityDenseParcelFilesOut) const; int32_t getNumberOfConnectivityDenseScalarFiles() const; CiftiBrainordinateScalarFile* getConnectivityDenseScalarFile(int32_t indx); const CiftiBrainordinateScalarFile* getConnectivityDenseScalarFile(int32_t indx) const; void getConnectivityDenseScalarFiles(std::vector& connectivityDenseScalarFilesOut) const; int32_t getNumberOfConnectivityParcelLabelFiles() const; CiftiParcelLabelFile* getConnectivityParcelLabelFile(int32_t indx); const CiftiParcelLabelFile* getConnectivityParcelLabelFile(int32_t indx) const; void getConnectivityParcelLabelFiles(std::vector& connectivityParcelLabelFilesOut) const; int32_t getNumberOfConnectivityParcelScalarFiles() const; CiftiParcelScalarFile* getConnectivityParcelScalarFile(int32_t indx); const CiftiParcelScalarFile* getConnectivityParcelScalarFile(int32_t indx) const; void getConnectivityParcelScalarFiles(std::vector& connectivityParcelScalarFilesOut) const; int32_t getNumberOfConnectivityScalarDataSeriesFiles() const; CiftiScalarDataSeriesFile* getConnectivityScalarDataSeriesFile(int32_t indx); const CiftiScalarDataSeriesFile* getConnectivityScalarDataSeriesFile(int32_t indx) const; void getConnectivityScalarDataSeriesFiles(std::vector& connectivityScalarDataSeriesFilesOut) const; int32_t getNumberOfConnectivityParcelSeriesFiles() const; CiftiParcelSeriesFile* getConnectivityParcelSeriesFile(int32_t indx); const CiftiParcelSeriesFile* getConnectivityParcelSeriesFile(int32_t indx) const; void getConnectivityParcelSeriesFiles(std::vector& connectivityParcelSeriesFilesOut) const; int32_t getNumberOfConnectivityFiberOrientationFiles() const; CiftiFiberOrientationFile* getConnectivityFiberOrientationFile(int32_t indx); const CiftiFiberOrientationFile* getConnectivityFiberOrientationFile(int32_t indx) const; void getConnectivityFiberOrientationFiles(std::vector& connectivityFiberOrientationFilesOut) const; bool getFiberOrientationSphericalSamplesVectors(std::vector& xVectors, std::vector& yVectors, std::vector& zVectors, FiberOrientation* &fiberOrientationOut, AString& errorMessageOut); int32_t getNumberOfConnectivityFiberTrajectoryFiles() const; CiftiFiberTrajectoryFile* getConnectivityFiberTrajectoryFile(int32_t indx); const CiftiFiberTrajectoryFile* getConnectivityFiberTrajectoryFile(int32_t indx) const; void getConnectivityFiberTrajectoryFiles(std::vector& ciftiFiberTrajectoryFilesOut) const; int32_t getNumberOfConnectivityMatrixParcelFiles() const; CiftiConnectivityMatrixParcelFile* getConnectivityMatrixParcelFile(int32_t indx); const CiftiConnectivityMatrixParcelFile* getConnectivityMatrixParcelFile(int32_t indx) const; void getConnectivityMatrixParcelFiles(std::vector& connectivityParcelFilesOut) const; int32_t getNumberOfConnectivityMatrixParcelDenseFiles() const; CiftiConnectivityMatrixParcelDenseFile* getConnectivityMatrixParcelDenseFile(int32_t indx); const CiftiConnectivityMatrixParcelDenseFile* getConnectivityMatrixParcelDenseFile(int32_t indx) const; void getConnectivityMatrixParcelDenseFiles(std::vector& connectivityParcelDenseFilesOut) const; int32_t getNumberOfConnectivityTimeSeriesFiles() const; void getAllCiftiConnectivityMatrixFiles(std::vector& allCiftiConnectivityMatrixFiles) const; int32_t getNumberOfConnectivityDataSeriesFiles() const; CiftiBrainordinateDataSeriesFile* getConnectivityDataSeriesFile(int32_t indx); const CiftiBrainordinateDataSeriesFile* getConnectivityDataSeriesFile(int32_t indx) const; void getConnectivityDataSeriesFiles(std::vector& connectivityDataSeriesFilesOut) const; void getAllChartableBrainordinateDataFiles(std::vector& chartableDataFilesOut) const; void getAllChartableLineSeriesDataFiles(std::vector& chartableDataFilesOut) const; void getAllChartableLineSeriesDataFilesForChartDataType(const ChartDataTypeEnum::Enum chartDataType, std::vector& chartableDataFilesOut) const; void getAllChartableBrainordinateDataFilesWithChartingEnabled(std::vector& chartableDataFilesOut) const; void getAllChartableMatrixDataFiles(std::vector& chartableDataFilesOut) const; void getAllChartableMatrixDataFilesForChartDataType(const ChartDataTypeEnum::Enum chartDataType, std::vector& chartableDataFilesOut) const; AString getCurrentDirectory() const; void setCurrentDirectory(const AString& currentDirectory); void convertDataFilePathNameToAbsolutePathName(CaretDataFile* caretDataFile) const; void getAllDataFiles(std::vector& allDataFilesOut, const bool includeSpecFile = false) const; void getAllDataFilesWithDataFileTypes(const std::vector& dataFileTypes, std::vector& caretDataFilesOut) const; void getAllDataFilesWithDataFileType(const DataFileTypeEnum::Enum dataFileType, std::vector& caretDataFilesOut) const; void getAllMappableDataFiles(std::vector& allCaretMappableDataFilesOut) const; void getAllMappableDataFileWithDataFileType(const DataFileTypeEnum::Enum dataFileType, std::vector& caretMappableDataFilesOut) const; void getAllMappableDataFileWithDataFileTypes(const std::vector& dataFileTypes, std::vector& caretMappableDataFilesOut) const; bool isFileValid(const CaretDataFile* caretDataFile) const; void getAllModifiedFiles(const std::vector& excludeTheseDataTypes, std::vector& modifiedDataFilesOut) const; void writeDataFile(CaretDataFile* caretDataFile); DisplayPropertiesBorders* getDisplayPropertiesBorders(); const DisplayPropertiesBorders* getDisplayPropertiesBorders() const; DisplayPropertiesFiberOrientation* getDisplayPropertiesFiberOrientation(); const DisplayPropertiesFiberOrientation* getDisplayPropertiesFiberOrientation() const; DisplayPropertiesFoci* getDisplayPropertiesFoci(); const DisplayPropertiesFoci* getDisplayPropertiesFoci() const; DisplayPropertiesVolume* getDisplayPropertiesVolume(); const DisplayPropertiesVolume* getDisplayPropertiesVolume() const; DisplayPropertiesSurface* getDisplayPropertiesSurface(); const DisplayPropertiesSurface* getDisplayPropertiesSurface() const; DisplayPropertiesImages* getDisplayPropertiesImages(); const DisplayPropertiesImages* getDisplayPropertiesImages() const; DisplayPropertiesLabels* getDisplayPropertiesLabels(); const DisplayPropertiesLabels* getDisplayPropertiesLabels() const; void copyDisplayProperties(const int32_t sourceTabIndex, const int32_t targetTabIndex); virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); IdentificationManager* getIdentificationManager(); SelectionManager* getSelectionManager(); BrainordinateRegionOfInterest* getBrainordinateHighlightRegionOfInterest(); const BrainordinateRegionOfInterest* getBrainordinateHighlightRegionOfInterest() const; void getCiftiShapeMap(CiftiBrainordinateScalarFile* &ciftiScalarShapeFileOut, int32_t& ciftiScalarhapeFileMapIndexOut, std::vector& ciftiScalarNotShapeFilesOut) const; private: /** * Reset the brain scene file mode */ enum ResetBrainKeepSceneFiles { /** Do NOT keep scene files when resetting the brain*/ RESET_BRAIN_KEEP_SCENE_FILES_NO, /** Do keep scene files when resetting the brain*/ RESET_BRAIN_KEEP_SCENE_FILES_YES }; /** * Reset the brain spec file mode */ enum ResetBrainKeepSpecFile { /** Do NOT keep spec files when resetting the brain*/ RESET_BRAIN_KEEP_SPEC_FILE_NO, /** Do keep spec files when resetting the brain*/ RESET_BRAIN_KEEP_SPEC_FILE_YES }; /** * Mode of file reading */ enum FileModeAddReadReload { /** Add the file */ FILE_MODE_ADD, /** Read the file */ FILE_MODE_READ, /** Reload the file */ FILE_MODE_RELOAD }; void addDataFile(CaretDataFile* caretDataFile); bool removeWithoutDeleteDataFile(const CaretDataFile* caretDataFile); bool removeWithoutDeleteDataFilePrivate(const CaretDataFile* caretDataFile); bool removeAndDeleteDataFile(CaretDataFile* caretDataFile); void loadFilesSelectedInSpecFile(EventSpecFileReadDataFiles* readSpecFileDataFilesEvent); void loadSpecFileFromScene(const SceneAttributes* sceneAttributes, SpecFile* specFile, const ResetBrainKeepSceneFiles keepSceneFile, const ResetBrainKeepSpecFile keepSpecFile); void resetBrain(const ResetBrainKeepSceneFiles keepSceneFiles, const ResetBrainKeepSpecFile keepSpecFile); void processReadDataFileEvent(EventDataFileRead* readDataFileEvent); void processReloadDataFileEvent(EventDataFileReload* reloadDataFileEvent); CaretDataFile* readDataFile(const DataFileTypeEnum::Enum dataFileType, const StructureEnum::Enum structure, const AString& dataFileName, const bool markDataFileAsModified); /** * Is the data file with the given name already loaded? * * @param loadedDataFiles * All files of a particular data type that are loaded. * @param fileName * File name for matching. */ template static bool dataFileWithNameIsLoaded(const std::vector& loadedDataFiles, const AString& fileName) { typename std::vector::const_iterator iter; for (iter = loadedDataFiles.begin(); iter != loadedDataFiles.end(); iter++) { const DFT* file = *iter; if (file->getFileName() == fileName) { return true; } } return false; } /** * If needed, update the name of a data file so that there are no two files * of the same type with the same name. If a duplicate needs to be created, * an underscore followed by a number is placed in the file name just * before the extension. * * @param loadedDataFiles * All files of a particular data type that are loaded. * @param newDataFile * New data file whose name may get changed if it duplicates * a currently loaded file. */ template void updateDataFileNameIfDuplicate(const std::vector& loadedDataFiles, DFT* newDataFile) { AString newFileName = newDataFile->getFileName(); const DataFileTypeEnum::Enum dataFileType = newDataFile->getDataFileType(); /* * Is there a file of the same name? */ if (dataFileWithNameIsLoaded(loadedDataFiles, newFileName)) { FileInformation fileInfo(newFileName); AString path; AString name; AString extension; fileInfo.getFileComponents(path, name, extension); /* * Modify the filename with a number (duplicate counter) * at the end of the file's name but before the extension's * dot. */ bool done = false; while ( ! done) { const int32_t duplicateCounter = getDuplicateFileNameCounterForFileType(dataFileType); AString versionName = (name + "_" + AString::number(duplicateCounter)); const AString versionFullName = FileInformation::assembleFileComponents(path, versionName, extension); if ( ! dataFileWithNameIsLoaded(loadedDataFiles, versionFullName)) { /* * Update name of file with version number, * Set the file modified (name changed), * and exit loop */ newDataFile->setFileName(versionFullName); newDataFile->setModified(); done = true; } } } } CaretDataFile* addReadOrReloadDataFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const DataFileTypeEnum::Enum dataFileType, const StructureEnum::Enum structure, const AString& dataFileName, const bool markDataFileAsModified); void updateAfterFilesAddedOrRemoved(); LabelFile* addReadOrReloadLabelFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename, const StructureEnum::Enum structure, const bool markDataFileAsModified); MetricFile* addReadOrReloadMetricFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename, const StructureEnum::Enum structure, const bool markDataFileAsModified); RgbaFile* addReadOrReloadRgbaFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename, const StructureEnum::Enum structure, const bool markDataFileAsModified); Surface* addReadOrReloadSurfaceFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename, const StructureEnum::Enum structure, const bool markDataFileAsModified); VolumeFile* addReadOrReloadVolumeFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename); BorderFile* addReadOrReloadBorderFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename); CiftiConnectivityMatrixDenseFile* addReadOrReloadConnectivityDenseFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename); CiftiBrainordinateLabelFile* addReadOrReloadConnectivityDenseLabelFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename); CiftiConnectivityMatrixDenseParcelFile* addReadOrReloadConnectivityMatrixDenseParcelFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename); CiftiBrainordinateScalarFile* addReadOrReloadConnectivityDenseScalarFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename); CiftiParcelLabelFile* addReadOrReloadConnectivityParcelLabelFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename); CiftiParcelScalarFile* addReadOrReloadConnectivityParcelScalarFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename); CiftiParcelSeriesFile* addReadOrReloadConnectivityParcelSeriesFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename); CiftiScalarDataSeriesFile* addReadOrReloadConnectivityScalarDataSeriesFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename); CiftiFiberOrientationFile* addReadOrReloadConnectivityFiberOrientationFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename); CiftiFiberTrajectoryFile* addReadOrReloadConnectivityFiberTrajectoryFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename); CiftiConnectivityMatrixParcelFile* addReadOrReloadConnectivityMatrixParcelFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename); CiftiConnectivityMatrixParcelDenseFile* addReadOrReloadConnectivityMatrixParcelDenseFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename); CiftiBrainordinateDataSeriesFile* addReadOrReloadConnectivityDataSeriesFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename); FociFile* addReadOrReloadFociFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename); ImageFile* addReadOrReloadImageFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename); PaletteFile* addReadOrReloadPaletteFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename); SceneFile* addReadOrReloadSceneFile(const FileModeAddReadReload fileMode, CaretDataFile* caretDataFile, const AString& filename); AString convertFilePathNameToAbsolutePathName(const AString& filename) const; void updateChartModel(); void updateVolumeSliceModel(); void updateWholeBrainModel(); void updateSurfaceMontageModel(); void updateFiberTrajectoryMatchingFiberOrientationFiles(); void validateCiftiMappableDataFile(const CiftiMappableDataFile* ciftiMapFile) const; int32_t getDuplicateFileNameCounterForFileType(const DataFileTypeEnum::Enum dataFileType); void resetDuplicateFileNameCounter(const bool preserveSceneFileCounter); std::vector m_brainStructures; std::vector m_borderFiles; std::vector m_fociFiles; std::vector m_imageFiles; std::vector m_sceneFiles; PaletteFile* m_paletteFile; std::vector m_connectivityMatrixDenseFiles; std::vector m_connectivityDenseLabelFiles; std::vector m_connectivityMatrixDenseParcelFiles; std::vector m_connectivityDenseScalarFiles; std::vector m_connectivityParcelLabelFiles; std::vector m_connectivityParcelSeriesFiles; std::vector m_connectivityParcelScalarFiles; std::vector m_connectivityScalarDataSeriesFiles; std::vector m_connectivityFiberOrientationFiles; std::vector m_connectivityFiberTrajectoryFiles; std::vector m_connectivityMatrixParcelFiles; std::vector m_connectivityMatrixParcelDenseFiles; std::vector m_connectivityDataSeriesFiles; std::vector m_nonModifiedFilesForRestoringScene; mutable AString m_currentDirectory; SpecFile* m_specFile; std::vector m_volumeFiles; ModelChart* m_modelChart; ModelVolume* m_volumeSliceModel; ModelWholeBrain* m_wholeBrainModel; ModelSurfaceMontage* m_surfaceMontageModel; ChartingDataManager* m_chartingDataManager; /** contains all display properties */ std::vector m_displayProperties; /** * Display properties for volume - DO NOT delete since this * is also in the displayProperties std::vector. */ DisplayPropertiesVolume* m_displayPropertiesVolume; /** * Display properties for surface - DO NOT delete since this * is also in the displayProperties std::vector. */ DisplayPropertiesSurface* m_displayPropertiesSurface; /** * Display properties for image - DO NOT delete since this * is also in the displayProperties std::vector. */ DisplayPropertiesImages* m_displayPropertiesImages; /** * Display properties for labels - DO NOT delete since this * is also in the displayProperties std::vector. */ DisplayPropertiesLabels* m_displayPropertiesLabels; /** * Display properties for borders - DO NOT delete since this * is also in the displayProperties std::vector. */ DisplayPropertiesBorders* m_displayPropertiesBorders; /** * Display properties for fiber orientation - DO NOT delete since this * is also in the displayProperties std::vector. */ DisplayPropertiesFiberOrientation* m_displayPropertiesFiberOrientation; /** * Display properties for foci - DO NOT delete since this * is also in the displayProperties std::vector. */ DisplayPropertiesFoci* m_displayPropertiesFoci; /** true when a spec file is being read */ bool m_isSpecFileBeingRead; SceneClassAssistant* m_sceneAssistant; /** Selection manager */ SelectionManager* m_selectionManager; /** Identification Manager */ IdentificationManager* m_identificationManager; /** The loader of fiber orientation samples */ FiberOrientationSamplesLoader* m_fiberOrientationSamplesLoader; /** Region of interest for highlighting brainordinates */ BrainordinateRegionOfInterest* m_brainordinateHighlightRegionOfInterest; std::map m_duplicateFileNameCounter; }; } // namespace #endif // __BRAIN_H__ workbench-1.1.1/src/Brain/BrainOpenGL.cxx000066400000000000000000000526351255417355300201670ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #define __BRAIN_OPENGL_DEFINE_H #include "BrainOpenGL.h" #undef __BRAIN_OPENGL_DEFINE_H #include "BrainOpenGLTextRenderInterface.h" #include "CaretAssert.h" #include "CaretLogger.h" #include "CaretPreferences.h" #include "SessionManager.h" using namespace caret; /** * Constructor. * * @param textRenderer * The text renderer is used for text rendering. * This parameter may be NULL in which case no text * rendering is performed. */ BrainOpenGL::BrainOpenGL(BrainOpenGLTextRenderInterface* textRenderer) { this->textRenderer = textRenderer; this->borderBeingDrawn = NULL; m_drawHighlightedEndPoints = false; } /** * Destructor. */ BrainOpenGL::~BrainOpenGL() { } /** * Get the minimum and maximum values for the size of a point. * @param minPointSizeOut * Gets minimum size of point. * @param maxPointSizeOut * Gets maximum size of point. */ void BrainOpenGL::getMinMaxPointSize(float& minPointSizeOut, float& maxPointSizeOut) { minPointSizeOut = BrainOpenGL::s_minPointSize; maxPointSizeOut = BrainOpenGL::s_maxPointSize; } /** * Get the minimum and maximum values for the width of a line. * @param minLineWidthOut * Gets minimum line width. * @param maxLineWidthOut * Gets maximum line width. */ void BrainOpenGL::getMinMaxLineWidth(float& minLineWidthOut, float& maxLineWidthOut) { minLineWidthOut = BrainOpenGL::s_minLineWidth; maxLineWidthOut = BrainOpenGL::s_maxLineWidth; } /** * Set the border being drawn by the user. If not NULL then * subclasses should draw the border. * * @param borderBeingDrawn * Pointer to border that is being drawn. */ void BrainOpenGL::setBorderBeingDrawn(Border* borderBeingDrawn) { this->borderBeingDrawn = borderBeingDrawn; } /** * @return Should border end points be highlighted? */ bool BrainOpenGL::isDrawHighlightedEndPoints() const { return m_drawHighlightedEndPoints; } /** * Set border end points should be highlighted. */ void BrainOpenGL::setDrawHighlightedEndPoints(const bool drawHighlightedEndPoints) { m_drawHighlightedEndPoints = drawHighlightedEndPoints; } /** * Determine if the given version of OpenGL is supported at runtime. * OpenGL is continually updated and this method is used to test for * support of a given runtime version of OpenGL so that OpenGL functions * in the given version may be used. For example, if a function in OpenGL * 2.1 is called on a system that it is OpenGL 1.1, a crash will likely occur. * * The OpenGL runtime version is two or three numbers separated by a period, * possibly followed by a space and then text. * The first number is the major version, the second number is the minor * version, and the optional third number is the release of the major/minor * version. * * A version of of OpenGL is supported when it is less than or equal to * the runtime version of the OpenGL library. However, there may be exceptions as * OpenGL is deprecating functionality from OpenGL 1.x and 2.x in versions * 3.1 and later. At this time, an extension is provided by all vendors so * that OpenGL 1.x and 2.x is available in 3.1 and later. THIS MAY CHANGE. * This method does not check for this potential missing, deprecated capability. * * @param versionOfOpenGL * Version of OpenGL in the form X.Y.Z (eg: 1.1, 2.1, 3.0, 3.0.0, 3.1, etc.) * for which support is tested. * * @return true if the given version of OpenGL is less than the runtime version. */ bool BrainOpenGL::testForVersionOfOpenGLSupported(const AString& versionOfOpenGL) { AString desiredMajorVersion; AString desiredMinorVersion; getOpenGLMajorMinorVersions(versionOfOpenGL, desiredMajorVersion, desiredMinorVersion); /* * If desired MAJOR version is LESS THAN the runtime MAJOR version * then is it supported. */ if (desiredMajorVersion.toInt() < s_runtimeLibraryMajorVersionOfOpenGL.toInt()) { return true; } /* * Is the desired MAJOR version THE SAME as the runtime MAJOR version */ if (s_runtimeLibraryMajorVersionOfOpenGL.toInt() == desiredMajorVersion.toInt()) { /* * If the desired MINOR version is LESS THAN OR EQUAL to the * runtime MINOR version, then it is supported. */ if (desiredMinorVersion.toInt() <= s_runtimeLibraryMinorVersionOfOpenGL.toInt()) { return true; } } return false; } /** * Extract the major and minor versions from an OpenGL version string. * @param versionString * The OpenGL version string which is ".[.optionalVendorInfo]. * Usually strings such as 1.1, 1.2, 2.1, 3.0, etc. * @param majorVersionOut * Output containing the major version. * @param minorVersionOut * Output containing the minor version. */ void BrainOpenGL::getOpenGLMajorMinorVersions(const AString& versionString, AString& majorVersionOut, AString& minorVersionOut) { /* * Major and minor version are separated by a period. * Vendor information may follow and begin with whitespace. */ const QStringList sl = versionString.split(QRegExp("[\\s|\\.]")); if (sl.count() >= 2) { minorVersionOut = sl.at(1); } else { minorVersionOut = "1"; } if (sl.count() >= 1) { majorVersionOut = sl.at(0); } else { majorVersionOut = "1"; } } /** * Initialize the drawing mode using the most optimal drawing given * the compile time and run time constraints. */ void BrainOpenGL::initializeOpenGL() { AString compileVersions = "OpenGL Header File Versions Supported: "; #ifdef GL_VERSION_1_1 compileVersions += " 1.1"; #endif #ifdef GL_VERSION_1_2 compileVersions += " 1.2"; #endif #ifdef GL_VERSION_1_3 compileVersions += " 1.3"; #endif #ifdef GL_VERSION_1_4 compileVersions += " 1.4"; #endif #ifdef GL_VERSION_1_5 compileVersions += " 1.5"; #endif #ifdef GL_VERSION_2_0 compileVersions += " 2.0"; #endif #ifdef GL_VERSION_2_1 compileVersions += " 2.1"; #endif #ifdef GL_VERSION_3_0 compileVersions += " 3.0"; #endif #ifdef GL_VERSION_3_1 compileVersions += " 3.1"; #endif #ifdef GL_VERSION_3_2 compileVersions += " 3.2"; #endif #ifdef GL_VERSION_3_3 compileVersions += " 3.3"; #endif #ifdef GL_VERSION_4_0 compileVersions += " 4.0"; #endif #ifdef GL_VERSION_4_1 compileVersions += " 4.1"; #endif #ifdef GL_VERSION_4_2 compileVersions += " 4.2"; #endif #ifdef GL_VERSION_4_3 compileVersions += " 4.3"; #endif #ifdef GL_VERSION_4_4 compileVersions += " 4.4"; #endif #ifdef GL_VERSION_4_4 compileVersions += " 4.4"; #endif #ifdef GL_VERSION_4_5 compileVersions += " 4.5"; #endif #ifdef GL_VERSION_5_0 compileVersions += " 5.0"; #endif #ifdef GL_OES_VERSION_1_0 compileVersions += " ES_1.0"; #endif #ifdef GL_ES_VERSION_2_0 compileVersions += " ES_2.0"; #endif #ifdef GL_ES_VERSION_3_0 compileVersions += " ES_3.0"; #endif s_runtimeLibraryVersionOfOpenGL = QLatin1String(reinterpret_cast(glGetString(GL_VERSION))); if (s_runtimeLibraryVersionOfOpenGL.isEmpty()) { s_runtimeLibraryVersionOfOpenGL = "1.1"; } getOpenGLMajorMinorVersions(s_runtimeLibraryVersionOfOpenGL, s_runtimeLibraryMajorVersionOfOpenGL, s_runtimeLibraryMinorVersionOfOpenGL); // // Note: The version string might be something like 1.2.4. std::atof() // will get just the 1.2 which is okay. // const char* vendorStr = (char*)(glGetString(GL_VENDOR)); const char* renderStr = (char*)(glGetString(GL_RENDERER)); AString lineInfo = (compileVersions + "\nOpenGL Runtime Version: " + s_runtimeLibraryVersionOfOpenGL + "\nMajor Runtime Version: " + BrainOpenGL::s_runtimeLibraryMajorVersionOfOpenGL + "\nMinor Runtime Version: " + BrainOpenGL::s_runtimeLibraryMinorVersionOfOpenGL + "\nOpenGL Vendor: " + AString(vendorStr) + "\nOpenGL Renderer: " + AString(renderStr)); lineInfo += "\n"; lineInfo += ("\nFont Renderer: " + this->textRenderer->getName()); lineInfo += "\n"; #ifdef GL_VERSION_2_0 if (testForVersionOfOpenGLSupported("2.0")) { GLfloat values[2]; glGetFloatv (GL_ALIASED_LINE_WIDTH_RANGE, values); const AString aliasedLineWidthRange = ("GL_ALIASED_LINE_WIDTH_RANGE value is " + AString::fromNumbers(values, 2, ", ")); glGetFloatv (GL_SMOOTH_LINE_WIDTH_RANGE, values); const AString smoothLineWidthRange = ("GL_SMOOTH_LINE_WIDTH_RANGE value is " + AString::fromNumbers(values, 2, ", ")); glGetFloatv (GL_SMOOTH_LINE_WIDTH_GRANULARITY, values); const AString smoothLineWidthGranularity = ("GL_SMOOTH_LINE_WIDTH_GRANULARITY value is " + AString::number(values[0])); lineInfo += ("\n" + aliasedLineWidthRange + "\n" + smoothLineWidthRange + "\n" + smoothLineWidthGranularity); } #endif // GL_VERSION_2_0 //#else // GL_VERSION_2_0 GLfloat values[2]; glGetFloatv (GL_LINE_WIDTH_RANGE, values); const AString lineWidthRange = ("GL_LINE_WIDTH_RANGE value is " + AString::fromNumbers(values, 2, ", ")); glGetFloatv (GL_LINE_WIDTH_GRANULARITY, values); const AString lineWidthGranularity = ("GL_LINE_WIDTH_GRANULARITY value is " + AString::number(values[0])); lineInfo += ("\n" + lineWidthRange + "\n" + lineWidthGranularity); //#endif // GL_VERSION_2_0 float sizes[2]; glGetFloatv(GL_POINT_SIZE_RANGE, sizes); s_minPointSize = sizes[0]; s_maxPointSize = sizes[1]; glGetFloatv(GL_LINE_WIDTH_RANGE, sizes); s_minLineWidth = sizes[0]; s_maxLineWidth = sizes[1]; s_supportsDisplayLists = false; s_supportsImmediateMode = false; s_supportsVertexBuffers = false; GLint maximumNumberOfClipPlanes; glGetIntegerv(GL_MAX_CLIP_PLANES, & maximumNumberOfClipPlanes); lineInfo += ("\n\nMaximum number of clipping planes is " + AString::number(maximumNumberOfClipPlanes)); GLint redBits, greenBits, blueBits, alphaBits; glGetIntegerv(GL_RED_BITS, &redBits); glGetIntegerv(GL_GREEN_BITS, &greenBits); glGetIntegerv(GL_BLUE_BITS, &blueBits); glGetIntegerv(GL_ALPHA_BITS, &alphaBits); lineInfo += ("\n\nBuffer bits red/green/blue/apha: (" + AString::number(redBits) + ", " + AString::number(greenBits) + ", " + AString::number(blueBits) + ", " + AString::number(alphaBits) + ")"); /* * Get the OpenGL Extensions. */ bool haveARBCompatibility = false; const QString extensionsString((char*)glGetString(GL_EXTENSIONS)); const QStringList extensionsList = extensionsString.split(QChar(' ')); QStringListIterator extensionsIterator(extensionsList); AString extInfo = ("\n\nOpenGL Extensions:"); while (extensionsIterator.hasNext()) { const QString ext = extensionsIterator.next(); extInfo += ("\n " + ext); if (ext == "GL_ARB_compatibility") { haveARBCompatibility = true; } } if (testForVersionOfOpenGLSupported("3.1")) { if (haveARBCompatibility == false) { CaretLogSevere("OpenGL 3.1 or later and ARB compatibilty extensions not found.\n" "OpenGL may fail."); } } #if BRAIN_OPENGL_INFO_SUPPORTS_IMMEDIATE s_supportsImmediateMode = true; #endif // BRAIN_OPENGL_INFO_SUPPORTS_IMMEDIATE #if BRAIN_OPENGL_INFO_SUPPORTS_DISPLAY_LISTS s_supportsDisplayLists = true; #endif // BRAIN_OPENGL_INFO_SUPPORTS_DISPLAY_LISTS #ifdef BRAIN_OPENGL_INFO_SUPPORTS_VERTEX_BUFFERS s_supportsVertexBuffers = true; #endif // BRAIN_OPENGL_INFO_SUPPORTS_VERTEX_BUFFERS lineInfo += ("\n\nBest Drawing Mode: " + BrainOpenGL::getBestDrawingModeName()); lineInfo += ("\nDisplay Lists Supported: " + AString::fromBool(s_supportsDisplayLists)); lineInfo += ("\nImmediate Mode Supported: " + AString::fromBool(s_supportsImmediateMode)); lineInfo += ("\nVertex Buffers Supported: " + AString::fromBool(s_supportsVertexBuffers)); lineInfo += extInfo; CaretLogConfig(lineInfo); m_openGLInformation = lineInfo; /* * Call to validate the draw mode selection logic. */ getBestDrawingMode(); } /** * @return String containing information about OpenGL. */ AString BrainOpenGL::getOpenGLInformation() { return m_openGLInformation; } /** * @return The best drawing mode given the limitations of * the compile and run-time systems. */ BrainOpenGL::DrawMode BrainOpenGL::getBestDrawingMode() { BrainOpenGL::DrawMode drawMode = BrainOpenGL::DRAW_MODE_INVALID; const OpenGLDrawingMethodEnum::Enum userPrefDrawMode = SessionManager::get()->getCaretPreferences()->getOpenDrawingMethod(); bool useVertexBuffersFlag = false; switch (userPrefDrawMode) { case OpenGLDrawingMethodEnum::DRAW_WITH_VERTEX_BUFFERS_OFF: break; case OpenGLDrawingMethodEnum::DRAW_WITH_VERTEX_BUFFERS_ON: if (s_supportsVertexBuffers) { useVertexBuffersFlag = true; } break; } if (s_supportsVertexBuffers && useVertexBuffersFlag) { drawMode = DRAW_MODE_VERTEX_BUFFERS; } else if (s_supportsDisplayLists) { drawMode = DRAW_MODE_DISPLAY_LISTS; } else if (s_supportsImmediateMode) { drawMode = DRAW_MODE_IMMEDIATE; } else if (s_supportsVertexBuffers) { drawMode = DRAW_MODE_VERTEX_BUFFERS; } else { CaretAssertMessage(0, "OpenGL does not appear to support any valid drawing modes, should never occur." " Or, OpenGL was not initialized (failed to call BrainOpenGL::initializeOpenGL())."); } return drawMode; } /** * @return Text string describing the drawing mode for shapes. */ QString BrainOpenGL::getBestDrawingModeName() { QString modeName = "Invalid"; switch (getBestDrawingMode()) { case BrainOpenGL::DRAW_MODE_DISPLAY_LISTS: modeName = "Display Lists"; break; case BrainOpenGL::DRAW_MODE_IMMEDIATE: modeName = "Immediate"; break; case BrainOpenGL::DRAW_MODE_INVALID: modeName = "Invalid"; break; case BrainOpenGL::DRAW_MODE_VERTEX_BUFFERS: modeName = "Vertex Buffers"; break; } return modeName; } /** * @return A string containing the state of OpenGL (depth testing, lighting, etc.) */ AString BrainOpenGL::getStateOfOpenGL() const { AString s; return s; } /** * Get the status of an OpenGL enum (enabled/disabled) as text in form * "name=true/false". * * @param enumName * Text name of the enumerated value. * @param enumValue * The OpenGl enum value. * @return * String with "enumName=true/false". */ AString BrainOpenGL::getOpenGLEnabledEnumAsText(const AString& enumName, const GLenum enumValue) const { /* * Reset error status */ glGetError(); GLboolean boolValue= glIsEnabled(enumValue); AString errorText; const GLenum errorCode = glGetError(); if (errorCode != GL_NO_ERROR) { const GLubyte* errorChars = gluErrorString(errorCode); if (errorChars != NULL) { errorText = ("ERROR = " + AString((char*)errorChars)); } } const AString s = (enumName + "=" + ((boolValue == GL_TRUE) ? "true" : "false") + " " + errorText); return s; } /** * Get the status of an OpenGL boolean as text in form * "name=true/false". * * @param enumName * Text name of the enumerated value. * @param enumValue * The OpenGl enum value. * @return * String with "enumName=true/false". */ AString BrainOpenGL::getOpenGLBooleanAsText(const AString& enumName, const GLenum enumValue) const { /* * Reset error status */ glGetError(); GLboolean boolValue = GL_FALSE; glGetBooleanv(enumValue, &boolValue); AString errorText; const GLenum errorCode = glGetError(); if (errorCode != GL_NO_ERROR) { const GLubyte* errorChars = gluErrorString(errorCode); if (errorChars != NULL) { errorText = ("ERROR = " + AString((char*)errorChars)); } } const AString s = (enumName + "=" + ((boolValue == GL_TRUE) ? "true" : "false") + " " + errorText); return s; } /** * Get the status of an OpenGL enum (enabled/disabled) as text in form * "name=true/false". * * @param enumName * Text name of the enumerated value. * @param enumValue * The OpenGl enum value. * @param numberOfValues * Number of floating pointer values associated with enumName. * @return * String with "enumName=(1, 2, 3,..)". */ AString BrainOpenGL::getOpenGLFloatAsText(const AString& enumName, const GLenum enumValue, const int32_t numberOfValues) const { /* * Reset error status */ glGetError(); AString valuesString; if (numberOfValues > 0) { std::vector valuesVector(numberOfValues, 0.0); GLfloat* values = &valuesVector[0]; glGetFloatv(enumValue, values); const GLenum errorCode = glGetError(); if (errorCode != GL_NO_ERROR) { const GLubyte* errorChars = gluErrorString(errorCode); if (errorChars != NULL) { valuesString = ("ERROR = " + AString((char*)errorChars)); } } else { valuesString = ("(" + AString::fromNumbers(valuesVector, ",") + ")"); } } const AString s = (enumName + "=" + valuesString); return s; } /** * Get the status of an OpenGL enum (enabled/disabled) as text in form * "name=true/false". * * @param enumName * Text name of the enumerated value. * @param enumValue * The OpenGl enum value. * @param enumValue * The OpenGl enum value. * @param numberOfValues * Number of floating pointer values associated with enumName. * @return * String with "enumName=(1, 2, 3,..)". */ AString BrainOpenGL::getOpenGLLightAsText(const AString& enumName, const GLenum lightEnum, const GLenum enumValue, const int32_t numberOfValues) const { /* * Reset error status */ glGetError(); AString valuesString; if (numberOfValues > 0) { std::vector valuesVector(numberOfValues, 0.0); GLfloat* values = &valuesVector[0]; glGetLightfv(lightEnum, enumValue, values); const GLenum errorCode = glGetError(); if (errorCode != GL_NO_ERROR) { const GLubyte* errorChars = gluErrorString(errorCode); if (errorChars != NULL) { valuesString = ("ERROR = " + AString((char*)errorChars)); } } else { valuesString = ("(" + AString::fromNumbers(valuesVector, ",") + ")"); } } const AString s = (enumName + "=" + valuesString); return s; } /** * Get the background color. * * @param backgroundColor * Output containing RGB components [0, 255]. */ void BrainOpenGL::getBackgroundColor(uint8_t backgroundColor[3]) const { backgroundColor[0] = m_backgroundColorByte[0]; backgroundColor[1] = m_backgroundColorByte[1]; backgroundColor[2] = m_backgroundColorByte[2]; } workbench-1.1.1/src/Brain/BrainOpenGL.h000066400000000000000000000224561255417355300176120ustar00rootroot00000000000000 #ifndef __BRAIN_OPENGL_H__ #define __BRAIN_OPENGL_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "CaretOpenGLInclude.h" #include "CaretObject.h" #undef BRAIN_OPENGL_INFO_SUPPORTS_DISPLAY_LISTS #undef BRAIN_OPENGL_INFO_SUPPORTS_IMMEDIATE #undef BRAIN_OPENGL_INFO_SUPPORTS_VERTEX_BUFFERS #define BRAIN_OPENGL_INFO_SUPPORTS_DISPLAY_LISTS 1 #define BRAIN_OPENGL_INFO_SUPPORTS_IMMEDIATE 1 //#define BRAIN_OPENGL_INFO_SUPPORTS_VERTEX_BUFFERS 1 //#ifdef GL_VERSION_1_1 //#define BRAIN_OPENGL_INFO_SUPPORTS_IMMEDIATE 1 //#define BRAIN_OPENGL_INFO_SUPPORTS_DISPLAY_LISTS 1 //#endif // GL_VERSION_1_1 // #ifdef GL_VERSION_2_1 #define BRAIN_OPENGL_INFO_SUPPORTS_VERTEX_BUFFERS 1 #endif // GL_VERSION_2_1 #ifdef GL_VERSION_3_0 #define BRAIN_OPENGL_INFO_SUPPORTS_VERTEX_BUFFERS 1 #endif // GL_VERSION_3_0 #ifdef GL_VERSION_4_0 #define BRAIN_OPENGL_INFO_SUPPORTS_VERTEX_BUFFERS 1 #endif // GL_VERSION_4_0 #include "CaretObject.h" namespace caret { class Border; class BrainOpenGLTextRenderInterface; class BrainOpenGLViewportContent; class SurfaceProjectedItem; /** * Performs drawing of graphics using OpenGL. */ class BrainOpenGL : public CaretObject { protected: BrainOpenGL(BrainOpenGLTextRenderInterface* textRenderer); public: /** Mode for how the shape is drawn, set to most optimal for compilation and runtime systems */ enum DrawMode { /** Draw using display lists */ DRAW_MODE_DISPLAY_LISTS, /** Draw using immediate mode */ DRAW_MODE_IMMEDIATE, /** Invalid */ DRAW_MODE_INVALID, /** Draw using vertex buffers */ DRAW_MODE_VERTEX_BUFFERS }; virtual ~BrainOpenGL(); /** * Initialize the OpenGL system. */ virtual void initializeOpenGL(); /** * Draw models in their respective viewports. * * @param viewportContents * Viewport info for drawing. */ virtual void drawModels(std::vector& viewportContents) = 0; /** * Selection on a model. * * @param viewportContent * Viewport content in which mouse was clicked * @param mouseX * X position of mouse click * @param mouseY * Y position of mouse click * @param applySelectionBackgroundFiltering * If true (which is in most cases), if there are multiple items * selected, those items "behind" other items are not reported. * For example, suppose a focus is selected and there is a node * the focus. If this parameter is true, the node will NOT be * selected. If this parameter is false, the node will be * selected. */ virtual void selectModel(BrainOpenGLViewportContent* viewportContent, const int32_t mouseX, const int32_t mouseY, const bool applySelectionBackgroundFiltering) = 0; /** * Project the given window coordinate to the active models. * If the projection is successful, The 'original' XYZ * coordinate in 'projectionOut' will be valid. In addition, * the barycentric coordinate may also be valid in 'projectionOut'. * * @param viewportContent * Viewport content in which mouse was clicked * @param mouseX * X position of mouse click * @param mouseY * Y position of mouse click */ virtual void projectToModel(BrainOpenGLViewportContent* viewportContent, const int32_t mouseX, const int32_t mouseY, SurfaceProjectedItem& projectionOut) = 0; /** * @return Half-size of the model window height. */ static float getModelViewingHalfWindowHeight() { return 90.0f; } void setBorderBeingDrawn(Border* borderBeingDrawn); bool isDrawHighlightedEndPoints() const; void setDrawHighlightedEndPoints(const bool drawHighlightedEndPoints); void getBackgroundColor(uint8_t backgroundColor[3]) const; static void getMinMaxPointSize(float& minPointSizeOut, float& maxPointSizeOut); static void getMinMaxLineWidth(float& minLineWidthOut, float& maxLineWidthOut); static bool testForVersionOfOpenGLSupported(const AString& versionOfOpenGL); static QString getBestDrawingModeName(); AString getOpenGLInformation(); static DrawMode getBestDrawingMode(); /** * @return True if display list drawing is supported. */ inline static bool isDisplayListsSupported() { return s_supportsDisplayLists; } /** * @return True if immediate mode drawing is supported. */ inline static bool isImmediateSupported() { return s_supportsImmediateMode; } /** * @return True if vertex buffer drawing is supported. */ inline static bool isVertexBuffersSupported() { return s_supportsVertexBuffers; } virtual AString getStateOfOpenGL() const; private: BrainOpenGL(const BrainOpenGL&); BrainOpenGL& operator=(const BrainOpenGL&); protected: AString getOpenGLEnabledEnumAsText(const AString& enumName, const GLenum enumValue) const; AString getOpenGLBooleanAsText(const AString& enumName, const GLenum enumValue) const; AString getOpenGLFloatAsText(const AString& enumName, const GLenum enumValue, const int32_t numberOfValues) const; AString getOpenGLLightAsText(const AString& enumName, const GLenum lightEnum, const GLenum enumValue, const int32_t numberOfValues) const; /** Optional text rendering (if not null) */ BrainOpenGLTextRenderInterface* textRenderer; Border* borderBeingDrawn; bool m_drawHighlightedEndPoints; uint8_t m_foregroundColorByte[4]; float m_foregroundColorFloat[4]; uint8_t m_backgroundColorByte[4]; float m_backgroundColorFloat[4]; /** minimum point size */ static float s_minPointSize; /** maximum point size */ static float s_maxPointSize; /** minimum line width */ static float s_minLineWidth; /** maximum line width */ static float s_maxLineWidth; private: static void getOpenGLMajorMinorVersions(const AString& versionString, AString& majorVersionOut, AString& minorVersionOut); /** runtime library version of OpenGL */ static AString s_runtimeLibraryVersionOfOpenGL; /** runtime library major version number of OpenGL */ static AString s_runtimeLibraryMajorVersionOfOpenGL; /** runtime library minor version number of OpenGL */ static AString s_runtimeLibraryMinorVersionOfOpenGL; static bool s_supportsDisplayLists; static bool s_supportsImmediateMode; static bool s_supportsVertexBuffers; AString m_openGLInformation; }; #ifdef __BRAIN_OPENGL_DEFINE_H AString BrainOpenGL::s_runtimeLibraryVersionOfOpenGL = ""; AString BrainOpenGL::s_runtimeLibraryMajorVersionOfOpenGL = ""; AString BrainOpenGL::s_runtimeLibraryMinorVersionOfOpenGL = ""; float BrainOpenGL::s_minPointSize = 1.0f; float BrainOpenGL::s_maxPointSize = 10.0f; float BrainOpenGL::s_minLineWidth = 1.0f; float BrainOpenGL::s_maxLineWidth = 10.0f; bool BrainOpenGL::s_supportsDisplayLists = false; bool BrainOpenGL::s_supportsImmediateMode = false; bool BrainOpenGL::s_supportsVertexBuffers = false; #endif //__BRAIN_OPENGL_DEFINE_H } // namespace #endif // __BRAIN_OPENGL_H__ workbench-1.1.1/src/Brain/BrainOpenGLChartDrawingFixedPipeline.cxx000066400000000000000000002064171255417355300251320ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __BRAIN_OPEN_G_L_CHART_DRAWING_FIXED_PIPELINE_DECLARE__ #include "BrainOpenGLChartDrawingFixedPipeline.h" #undef __BRAIN_OPEN_G_L_CHART_DRAWING_FIXED_PIPELINE_DECLARE__ #include "CaretOpenGLInclude.h" #include "BrainOpenGLFixedPipeline.h" #include "BrainOpenGLTextRenderInterface.h" #include "CaretAssert.h" #include "ChartAxis.h" #include "ChartAxisCartesian.h" #include "ChartData.h" #include "ChartDataCartesian.h" #include "CaretLogger.h" #include "ChartMatrixDisplayProperties.h" #include "ChartModelDataSeries.h" #include "ChartModelFrequencySeries.h" #include "ChartModelTimeSeries.h" #include "ChartableMatrixInterface.h" #include "CaretPreferences.h" #include "ChartPoint.h" #include "CiftiMappableConnectivityMatrixDataFile.h" #include "CiftiParcelLabelFile.h" #include "CiftiParcelScalarFile.h" #include "CiftiScalarDataSeriesFile.h" #include "Brain.h" #include "ConnectivityDataLoaded.h" #include "EventCaretMappableDataFileMapsViewedInOverlays.h" #include "EventManager.h" #include "IdentificationWithColor.h" #include "SelectionItemChartDataSeries.h" #include "SelectionItemChartFrequencySeries.h" #include "SelectionItemChartMatrix.h" #include "SelectionItemChartTimeSeries.h" #include "SelectionManager.h" #include "SessionManager.h" using namespace caret; /** * \class caret::BrainOpenGLChartDrawingFixedPipeline * \brief Chart drawing using OpenGL's fixed pipeline. * \ingroup Brain */ /** * Constructor. */ BrainOpenGLChartDrawingFixedPipeline::BrainOpenGLChartDrawingFixedPipeline() : BrainOpenGLChartDrawingInterface() { m_brain = NULL; m_fixedPipelineDrawing = NULL; m_identificationModeFlag = false; } /** * Destructor. */ BrainOpenGLChartDrawingFixedPipeline::~BrainOpenGLChartDrawingFixedPipeline() { } /** * Draw a cartesian chart in the given viewport. * * @param brain * Brain. * @param fixedPipelineDrawing * The fixed pipeline OpenGL drawing. * @param viewport * Viewport for the chart. * @param textRenderer * Text rendering. * @param cartesianChart * Cartesian Chart that is drawn. * @param selectionItemDataType * Selected data type. * @param tabIndex * Index of the tab. */ void BrainOpenGLChartDrawingFixedPipeline::drawCartesianChart(Brain* brain, BrainOpenGLFixedPipeline* fixedPipelineDrawing, const int32_t viewport[4], BrainOpenGLTextRenderInterface* textRenderer, ChartModelCartesian* cartesianChart, const SelectionItemDataTypeEnum::Enum selectionItemDataType, const int32_t tabIndex) { m_brain = brain; m_fixedPipelineDrawing = fixedPipelineDrawing; m_tabIndex = tabIndex; m_chartModelDataSeriesBeingDrawnForIdentification = dynamic_cast(cartesianChart); m_chartModelFrequencySeriesBeingDrawnForIdentification = dynamic_cast(cartesianChart); m_chartModelTimeSeriesBeingDrawnForIdentification = dynamic_cast(cartesianChart); m_chartCartesianSelectionTypeForIdentification = selectionItemDataType; m_chartableMatrixInterfaceBeingDrawnForIdentification = NULL; CaretAssert(cartesianChart); if (cartesianChart->isEmpty()) { return; } saveStateOfOpenGL(); SelectionItemChartDataSeries* chartDataSeriesID = m_brain->getSelectionManager()->getChartDataSeriesIdentification(); SelectionItemChartFrequencySeries* chartFrequencySeriesID = m_brain->getSelectionManager()->getChartFrequencySeriesIdentification(); SelectionItemChartTimeSeries* chartTimeSeriesID = m_brain->getSelectionManager()->getChartTimeSeriesIdentification(); /* * Check for a 'selection' type mode */ m_identificationModeFlag = false; switch (m_fixedPipelineDrawing->mode) { case BrainOpenGLFixedPipeline::MODE_DRAWING: break; case BrainOpenGLFixedPipeline::MODE_IDENTIFICATION: if (chartDataSeriesID->isEnabledForSelection() || chartFrequencySeriesID->isEnabledForSelection() || chartTimeSeriesID->isEnabledForSelection()) { m_identificationModeFlag = true; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } else { return; } break; case BrainOpenGLFixedPipeline::MODE_PROJECTION: return; break; } resetIdentification(); // const CaretPreferences* preferences = SessionManager::get()->getCaretPreferences(); // uint8_t foregroundByte[3]; // preferences->getColorForegroundChartView(foregroundByte); // CaretPreferences::byteRgbToFloatRgb(foregroundByte, // m_foregroundColor); // m_foregroundColor[3] = 1.0; const int32_t vpX = viewport[0]; const int32_t vpY = viewport[1]; const int32_t vpWidth = viewport[2]; const int32_t vpHeight = viewport[3]; int32_t chartGraphicsDrawingViewport[4] = { vpX, vpY, vpWidth, vpHeight }; /* * Margin is region around the chart in which * the axes legends, values, and ticks are drawn. */ const int32_t marginSize = 30; Margins margins(marginSize); int32_t width, height; estimateCartesianChartAxisLegendsWidthHeight(textRenderer, cartesianChart->getLeftAxis(), width, height); margins.m_left = std::max(margins.m_left, width); estimateCartesianChartAxisLegendsWidthHeight(textRenderer, cartesianChart->getRightAxis(), width, height); margins.m_right = std::max(margins.m_right, width); estimateCartesianChartAxisLegendsWidthHeight(textRenderer, cartesianChart->getTopAxis(), width, height); margins.m_top = std::max(margins.m_top, height); estimateCartesianChartAxisLegendsWidthHeight(textRenderer, cartesianChart->getBottomAxis(), width, height); margins.m_bottom = std::max(margins.m_bottom, height); if (margins.m_left > marginSize) margins.m_left += 10; if (margins.m_right > marginSize) margins.m_right += 10; /* * Ensure that there is sufficient space for the axes data display. */ if ((vpWidth > (marginSize * 3)) && (vpHeight > (marginSize * 3))) { /* Draw legends and grids */ drawChartAxis(vpX, vpY, vpWidth, vpHeight, margins, textRenderer, cartesianChart->getLeftAxis()); drawChartAxis(vpX, vpY, vpWidth, vpHeight, margins, textRenderer, cartesianChart->getRightAxis()); drawChartAxis(vpX, vpY, vpWidth, vpHeight, margins, textRenderer, cartesianChart->getBottomAxis()); drawChartAxis(vpX, vpY, vpWidth, vpHeight, margins, textRenderer, cartesianChart->getTopAxis()); drawChartGraphicsBoxAndSetViewport(vpX, vpY, vpWidth, vpHeight, margins, chartGraphicsDrawingViewport); } glViewport(chartGraphicsDrawingViewport[0], chartGraphicsDrawingViewport[1], chartGraphicsDrawingViewport[2], chartGraphicsDrawingViewport[3]); drawChartGraphicsLineSeries(textRenderer, cartesianChart); /* * Process selection */ if (m_identificationModeFlag) { processIdentification(); } restoreStateOfOpenGL(); } /** * Draw a matrix chart in the given viewport. * * @param brain * Brain. * @param fixedPipelineDrawing * The fixed pipeline OpenGL drawing. * @param viewport * Viewport for the chart. * @param textRenderer * Text rendering. * @param chartMatrixInterface * Chart matrix interface containing matrix data. * @param scalarDataSeriesMapIndex * Selected map in scalar data series file. * @param selectionItemDataType * Selected data type. * @param tabIndex * Index of the tab. */ void BrainOpenGLChartDrawingFixedPipeline::drawMatrixChart(Brain* brain, BrainOpenGLFixedPipeline* fixedPipelineDrawing, const int32_t viewport[4], BrainOpenGLTextRenderInterface* textRenderer, ChartableMatrixInterface* chartMatrixInterface, const int32_t scalarDataSeriesMapIndex, const SelectionItemDataTypeEnum::Enum selectionItemDataType, const int32_t tabIndex) { m_brain = brain; m_fixedPipelineDrawing = fixedPipelineDrawing; m_tabIndex = tabIndex; m_chartModelDataSeriesBeingDrawnForIdentification = NULL; m_chartModelFrequencySeriesBeingDrawnForIdentification = NULL; m_chartModelTimeSeriesBeingDrawnForIdentification = NULL; m_chartableMatrixInterfaceBeingDrawnForIdentification = chartMatrixInterface; m_chartableMatrixSelectionTypeForIdentification = selectionItemDataType; CaretAssert(chartMatrixInterface); // int32_t numberOfRows = 0; // int32_t numberOfColumns = 0; // std::vector matrixRGBA; // chartMatrixInterface->getMatrixDataRGBA(numberOfRows, // numberOfColumns, // matrixRGBA); // if ((numberOfRows <= 0) // || (numberOfColumns <= 0)) { // return; // } saveStateOfOpenGL(); SelectionItemChartMatrix* chartMatrixID = m_brain->getSelectionManager()->getChartMatrixIdentification(); /* * Check for a 'selection' type mode */ m_identificationModeFlag = false; switch (m_fixedPipelineDrawing->mode) { case BrainOpenGLFixedPipeline::MODE_DRAWING: break; case BrainOpenGLFixedPipeline::MODE_IDENTIFICATION: if (chartMatrixID->isEnabledForSelection()) { m_identificationModeFlag = true; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } else { return; } break; case BrainOpenGLFixedPipeline::MODE_PROJECTION: return; break; } resetIdentification(); // const int32_t vpX = viewport[0]; // const int32_t vpY = viewport[1]; // const int32_t vpWidth = viewport[2]; // const int32_t vpHeight = viewport[3]; ChartMatrixDisplayProperties* matrixProperties = chartMatrixInterface->getChartMatrixDisplayProperties(m_tabIndex); const int32_t paletteHeight = (matrixProperties->isColorBarDisplayed() ? 40 : 0); /* * Viewport is X, Y, Width, Height */ const int32_t chartGraphicsDrawingViewport[4] = { viewport[0], viewport[1] + paletteHeight, viewport[2], viewport[3] - paletteHeight }; /* * Margin is region around the chart in which * the axes legends, values, and ticks are drawn. */ //const int32_t marginSize = 30; glViewport(chartGraphicsDrawingViewport[0], chartGraphicsDrawingViewport[1], chartGraphicsDrawingViewport[2], chartGraphicsDrawingViewport[3]); drawChartGraphicsMatrix(chartGraphicsDrawingViewport, textRenderer, chartMatrixInterface, scalarDataSeriesMapIndex); /* * Process selection */ if (m_identificationModeFlag) { processIdentification(); } restoreStateOfOpenGL(); } /** * Draw the chart axes grid/box * * @param vpX * Viewport X for all chart content * @param vpY * Viewport Y for all chart content * @param vpWidth * Viewport width for all chart content * @param vpHeight * Viewport height for all chart content * @param margins * Margin around graphics region. The margin corresponding to the * axis may be changed so that all text in the axis is visible * (and not cut off). * @param textRenderer * Text rendering. * @param axis * Axis that is drawn. */ void BrainOpenGLChartDrawingFixedPipeline::drawChartAxis(const float vpX, const float vpY, const float vpWidth, const float vpHeight, Margins& margins, BrainOpenGLTextRenderInterface* textRenderer, ChartAxis* axis) { if (axis == NULL) { return; } if ( ! axis->isVisible()) { return; } switch (axis->getAxisType()) { case ChartAxisTypeEnum::CHART_AXIS_TYPE_CARTESIAN: drawChartAxisCartesian(vpX, vpY, vpWidth, vpHeight, margins, textRenderer, dynamic_cast(axis)); break; case ChartAxisTypeEnum::CHART_AXIS_TYPE_NONE: break; } } /** * Draw the chart axes grid/box * * @param vpX * Viewport X for all chart content * @param vpY * Viewport Y for all chart content * @param vpWidth * Viewport width for all chart content * @param vpHeight * Viewport height for all chart content * @param margins * Margin around graphics region. The margin corresponding to the * axis may be changed so that all text in the axis is visible * (and not cut off). * @param textRenderer * Text rendering. * @param chartModelCartesian * The chart cartesian model. * @param axis * Axis that is drawn. */ void BrainOpenGLChartDrawingFixedPipeline::drawChartAxisCartesian(const float vpX, const float vpY, const float vpWidth, const float vpHeight, Margins& margins, BrainOpenGLTextRenderInterface* textRenderer, ChartAxisCartesian* axis) { CaretAssert(axis); const float fontSizeInPixels = 14; float axisLength = 0.0; switch (axis->getAxisLocation()) { case ChartAxisLocationEnum::CHART_AXIS_LOCATION_BOTTOM: case ChartAxisLocationEnum::CHART_AXIS_LOCATION_TOP: axisLength = vpWidth - (margins.m_left + margins.m_right); break; case ChartAxisLocationEnum::CHART_AXIS_LOCATION_LEFT: case ChartAxisLocationEnum::CHART_AXIS_LOCATION_RIGHT: axisLength = vpHeight - (margins.m_top + margins.m_bottom); break; } std::vector labelOffsetInPixels; std::vector labelTexts; axis->getLabelsAndPositions(axisLength, fontSizeInPixels, labelOffsetInPixels, labelTexts); const int32_t numLabelsToDraw = static_cast(labelTexts.size()); if (numLabelsToDraw > 0) { float labelX = 0.0; float labelY = 0.0; float labelOffsetMultiplierX = 0.0; float labelOffsetMultiplierY = 0.0; BrainOpenGLTextRenderInterface::TextAlignmentX labelAlignmentX = BrainOpenGLTextRenderInterface::X_CENTER; BrainOpenGLTextRenderInterface::TextAlignmentY labelAlignmentY = BrainOpenGLTextRenderInterface::Y_CENTER; /* * Viewport for axis name and numeric values */ int32_t axisVpX = vpX; int32_t axisVpY = vpY; int32_t axisVpWidth = vpWidth; int32_t axisVpHeight = vpHeight; float tickDeltaXY[2] = { 0.0, 0.0 }; const float tickLength = 5.0; switch (axis->getAxisLocation()) { case ChartAxisLocationEnum::CHART_AXIS_LOCATION_BOTTOM: axisVpX = vpX; axisVpY = vpY; axisVpWidth = vpWidth; axisVpHeight = margins.m_bottom; labelX = margins.m_left; labelY = margins.m_bottom; labelOffsetMultiplierX = 1.0; labelOffsetMultiplierY = 0.0; labelAlignmentX = BrainOpenGLTextRenderInterface::X_CENTER; labelAlignmentY = BrainOpenGLTextRenderInterface::Y_TOP; tickDeltaXY[0] = 0.0; tickDeltaXY[1] = -tickLength; break; case ChartAxisLocationEnum::CHART_AXIS_LOCATION_TOP: axisVpX = vpX; axisVpY = vpY + vpHeight - margins.m_top; axisVpWidth = vpWidth; axisVpHeight = margins.m_top; labelX = margins.m_left; labelY = 0.0; labelOffsetMultiplierX = 1.0; labelOffsetMultiplierY = 0.0; labelAlignmentX = BrainOpenGLTextRenderInterface::X_CENTER; labelAlignmentY = BrainOpenGLTextRenderInterface::Y_BOTTOM; tickDeltaXY[0] = 0.0; tickDeltaXY[1] = tickLength; break; case ChartAxisLocationEnum::CHART_AXIS_LOCATION_LEFT: axisVpX = vpX; axisVpY = vpY; axisVpWidth = margins.m_left; axisVpHeight = vpHeight; labelX = margins.m_left; labelY = margins.m_bottom; labelOffsetMultiplierX = 0.0; labelOffsetMultiplierY = 1.0; labelAlignmentX = BrainOpenGLTextRenderInterface::X_RIGHT; labelAlignmentY = BrainOpenGLTextRenderInterface::Y_CENTER; tickDeltaXY[0] = -tickLength; tickDeltaXY[1] = 0.0; break; case ChartAxisLocationEnum::CHART_AXIS_LOCATION_RIGHT: axisVpX = vpX + vpWidth - margins.m_right; axisVpY = vpY; axisVpWidth = margins.m_right; axisVpHeight = vpHeight; labelX = 0.0; labelY = margins.m_bottom; labelOffsetMultiplierX = 0.0; labelOffsetMultiplierY = 1.0; labelAlignmentX = BrainOpenGLTextRenderInterface::X_LEFT; labelAlignmentY = BrainOpenGLTextRenderInterface::Y_CENTER; tickDeltaXY[0] = tickLength; tickDeltaXY[1] = 0.0; break; } /* * Viewport for axis text and numeric values */ const int viewport[4] = { axisVpX, axisVpY, axisVpWidth, axisVpHeight }; glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0, axisVpWidth, 0, axisVpHeight, -1.0, 1.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glColor3fv(m_fixedPipelineDrawing->m_foregroundColorFloat); for (int32_t i = 0; i < numLabelsToDraw; i++) { const float tickStartX = labelX + labelOffsetInPixels[i] * labelOffsetMultiplierX; const float tickStartY = labelY + labelOffsetInPixels[i] * labelOffsetMultiplierY; const float tickEndX = tickStartX + tickDeltaXY[0]; const float tickEndY = tickStartY + tickDeltaXY[1]; glBegin(GL_LINES); glVertex2f(tickStartX, tickStartY); glVertex2f(tickEndX, tickEndY); glEnd(); const float textX = tickEndX; const float textY = tickEndY; textRenderer->drawTextAtWindowCoords(viewport, textX, textY, labelTexts[i], labelAlignmentX, labelAlignmentY); } const AString axisText = axis->getText(); if ( ! axisText.isEmpty()) { // bool drawAxisTextVerticalFlag = false; // float axisTextCenterX = 0.0; // float axisTextCenterY = 0.0; // switch (axis->getAxisLocation()) { // case ChartAxisLocationEnum::CHART_AXIS_LOCATION_BOTTOM: // axisTextCenterX = (vpWidth / 2.0); // axisTextCenterY = (margins.m_bottom / 2.0); // break; // case ChartAxisLocationEnum::CHART_AXIS_LOCATION_TOP: // axisTextCenterX = (vpWidth / 2.0); // axisTextCenterY = (margins.m_top / 2.0); // break; // case ChartAxisLocationEnum::CHART_AXIS_LOCATION_LEFT: // axisTextCenterX = (margins.m_left / 2.0); // axisTextCenterY = (vpHeight / 2.0); // drawAxisTextVerticalFlag = true; // break; // case ChartAxisLocationEnum::CHART_AXIS_LOCATION_RIGHT: // axisTextCenterX = (margins.m_right / 2.0); // axisTextCenterY = (vpHeight / 2.0); // drawAxisTextVerticalFlag = true; // break; // } BrainOpenGLTextRenderInterface::TextAlignmentX textAlignX = BrainOpenGLTextRenderInterface::X_CENTER; BrainOpenGLTextRenderInterface::TextAlignmentY textAlignY = BrainOpenGLTextRenderInterface::Y_CENTER; bool drawAxisTextVerticalFlag = false; float axisTextCenterX = axisVpWidth / 2.0; float axisTextCenterY = axisVpHeight / 2.0; const float textMarginOffset = 5.0; switch (axis->getAxisLocation()) { case ChartAxisLocationEnum::CHART_AXIS_LOCATION_BOTTOM: axisTextCenterX = (vpWidth / 2.0); axisTextCenterY = textMarginOffset; textAlignY = BrainOpenGLTextRenderInterface::Y_BOTTOM; break; case ChartAxisLocationEnum::CHART_AXIS_LOCATION_TOP: axisTextCenterX = (vpWidth / 2.0); axisTextCenterY = margins.m_top - textMarginOffset; textAlignY = BrainOpenGLTextRenderInterface::Y_TOP; break; case ChartAxisLocationEnum::CHART_AXIS_LOCATION_LEFT: axisTextCenterX = textMarginOffset; axisTextCenterY = (vpHeight / 2.0); textAlignX = BrainOpenGLTextRenderInterface::X_LEFT; drawAxisTextVerticalFlag = true; break; case ChartAxisLocationEnum::CHART_AXIS_LOCATION_RIGHT: axisTextCenterX = margins.m_right - textMarginOffset; axisTextCenterY = (vpHeight / 2.0); textAlignX = BrainOpenGLTextRenderInterface::X_RIGHT; drawAxisTextVerticalFlag = true; break; } if (drawAxisTextVerticalFlag) { textRenderer->drawVerticalTextAtWindowCoords(viewport, axisTextCenterX, axisTextCenterY, axisText, textAlignX, textAlignY, BrainOpenGLTextRenderInterface::NORMAL, 14); } else { textRenderer->drawTextAtWindowCoords(viewport, axisTextCenterX, axisTextCenterY, axisText, textAlignX, textAlignY); } } } } /** * Estimate the size of the axis' text. * * @param textRenderer * Text rendering. * @param axis * The axis. * @param widthOut * Width of text out. * @param heightOut * Heigh of text out. */ void BrainOpenGLChartDrawingFixedPipeline::estimateCartesianChartAxisLegendsWidthHeight(BrainOpenGLTextRenderInterface* textRenderer, ChartAxis* axis, int32_t& widthOut, int32_t& heightOut) { widthOut = 0; heightOut = 0; if (axis == NULL) { return; } ChartAxisCartesian* cartesianAxis = dynamic_cast(axis); if ( ! cartesianAxis->isVisible()) { return; } const float fontSizeInPixels = 14; const float axisLength = 1000.0; std::vector labelOffsetInPixels; std::vector labelTexts; cartesianAxis->getLabelsAndPositions(axisLength, fontSizeInPixels, labelOffsetInPixels, labelTexts); for (std::vector::iterator iter = labelTexts.begin(); iter != labelTexts.end(); iter++) { const AString text = *iter; if ( ! text.isEmpty()) { int32_t textWidth = 0; int32_t textHeight = 0; textRenderer->getTextBoundsInPixels(textWidth, textHeight, text, BrainOpenGLTextRenderInterface::NORMAL, 14); widthOut = std::max(widthOut, textWidth); heightOut = std::max(heightOut, textHeight); } } } /** * Draw the chart graphics surrounding box and set the graphics viewport. * drawChartGraphicsBoxAndSetViewport * @param vpX * Viewport X * @param vpY * Viewport Y * @param vpWidth * Viewport width * @param vpHeight * Viewport height * @param marginSize * Margin around grid/box * @param chartGraphicsDrawingViewportOut * Output containing viewport for drawing chart graphics within * the box/grid that is adjusted for the box's line thickness. */ void BrainOpenGLChartDrawingFixedPipeline::drawChartGraphicsBoxAndSetViewport(const float vpX, const float vpY, const float vpWidth, const float vpHeight, const Margins& margins, int32_t chartGraphicsDrawingViewportOut[4]) { const float gridLineWidth = 2; const float halfGridLineWidth = gridLineWidth / 2.0; const float gridLeft = vpX + margins.m_left; const float gridRight = vpX + vpWidth - margins.m_right; const float gridBottom = vpY + margins.m_bottom; const float gridTop = vpY + vpHeight - margins.m_top; glViewport(vpX, vpY, vpWidth, vpHeight); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(vpX, (vpX + vpWidth), vpY, (vpY + vpHeight), -1.0, 1.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glLineWidth(gridLineWidth); glColor3fv(m_fixedPipelineDrawing->m_foregroundColorFloat); glBegin(GL_LINES); /* bottom line */ glVertex3f(gridLeft, gridBottom + halfGridLineWidth, 0.0); glVertex3f(gridRight, gridBottom + halfGridLineWidth, 0.0); /* right line */ glVertex3f(gridRight - halfGridLineWidth, gridBottom, 0.0); glVertex3f(gridRight - halfGridLineWidth, gridTop, 0.0); /* top line */ glVertex3f(gridRight, gridTop - halfGridLineWidth, 0.0); glVertex3f(gridLeft, gridTop - halfGridLineWidth, 0.0); /* left line */ glVertex3f(gridLeft + halfGridLineWidth, gridTop, 0.0); glVertex3f(gridLeft + halfGridLineWidth, gridBottom, 0.0); glEnd(); /* * Region inside the grid's box */ const int32_t graphicsLeft = static_cast(gridLeft + std::ceil(gridLineWidth + 1.0)); const int32_t graphicsRight = static_cast(gridRight - std::floor(gridLineWidth + 1.0)); const int32_t graphicsBottom = static_cast(gridBottom + std::ceil(gridLineWidth + 1.0)); const int32_t graphicsTop = static_cast(gridTop - std::floor(gridLineWidth + 1.0)); const int32_t graphicsWidth = graphicsRight - graphicsLeft; const int32_t graphicsHeight = graphicsTop - graphicsBottom; chartGraphicsDrawingViewportOut[0] = graphicsLeft; chartGraphicsDrawingViewportOut[1] = graphicsBottom; chartGraphicsDrawingViewportOut[2] = graphicsWidth; chartGraphicsDrawingViewportOut[3] = graphicsHeight; } /** * Draw graphics for the given line series chart. * * @param textRenderer * Text rendering. * @param chart * Chart that is drawn. */ void BrainOpenGLChartDrawingFixedPipeline::drawChartGraphicsLineSeries(BrainOpenGLTextRenderInterface* /*textRenderer*/, ChartModelCartesian* chart) { CaretAssert(chart); //std::vector chartVector = chart->getAllSelectedChartDatas(m_tabIndex); std::vector chartVector = chart->getAllChartDatas(); if (chartVector.empty()) { return; } m_fixedPipelineDrawing->enableLineAntiAliasing(); const ChartAxisCartesian* leftAxis = dynamic_cast(chart->getLeftAxis()); CaretAssert(leftAxis); const ChartAxisCartesian* bottomAxis = dynamic_cast(chart->getBottomAxis()); CaretAssert(bottomAxis); float xMin = bottomAxis->getMinimumValue(); float xMax = bottomAxis->getMaximumValue(); float yMin = leftAxis->getMinimumValue(); float yMax = leftAxis->getMaximumValue(); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(xMin, xMax, yMin, yMax, -1.0, 1.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); const float lineWidth = chart->getLineWidth(); /* * Start at the oldest chart and end with the newest chart. */ const int32_t numChartData = static_cast(chartVector.size()); for (int32_t chartDataIndex = (numChartData - 1); chartDataIndex >= 0; chartDataIndex--) { const ChartData* chartData = chartVector[chartDataIndex]; if (chartData->isSelected(m_tabIndex)) { const ChartDataCartesian* chartDataCart = dynamic_cast(chartData); CaretAssert(chartDataCart); CaretColorEnum::Enum color = chartDataCart->getColor(); drawChartDataCartesian(chartDataIndex, chartDataCart, lineWidth, CaretColorEnum::toRGB(color)); } } if (chart->isAverageChartDisplaySelected()) { const ChartData* chartData = chart->getAverageChartDataForDisplay(m_tabIndex); if (chartData != NULL) { const ChartDataCartesian* chartDataCart = dynamic_cast(chartData); CaretAssert(chartDataCart); drawChartDataCartesian(-1, chartDataCart, lineWidth, m_fixedPipelineDrawing->m_foregroundColorFloat); } } m_fixedPipelineDrawing->disableLineAntiAliasing(); } /** * Draw the cartesian data with the given color. * * @param chartDataIndex * Index of chart data * @param chartDataCartesian * Cartesian data that is drawn. * @param lineWidth * Width of lines. * @param color * Color for the data. */ void BrainOpenGLChartDrawingFixedPipeline::drawChartDataCartesian(const int32_t chartDataIndex, const ChartDataCartesian* chartDataCartesian, const float lineWidth, const float rgb[3]) { if (lineWidth <= 0.0) { return; } glColor3fv(rgb); glLineWidth(lineWidth); if (m_identificationModeFlag) { glLineWidth(5.0); } glBegin(GL_LINE_STRIP); const int32_t numPoints = chartDataCartesian->getNumberOfPoints(); for (int32_t i = 0; i < numPoints; i++) { const ChartPoint* point = chartDataCartesian->getPointAtIndex(i); if (m_identificationModeFlag) { uint8_t rgbaForID[4]; addToChartLineIdentification(chartDataIndex, i, rgbaForID); glColor4ubv(rgbaForID); } glVertex2fv(point->getXY()); } glEnd(); } /** * Draw graphics for the matrix chart.. * * @param viewport * The viewport. * @param textRenderer * Text rendering. * @param chartMatrixInterface * Chart that is drawn. * @param scalarDataSeriesMapIndex * Selected map for scalar data series file. */ void BrainOpenGLChartDrawingFixedPipeline::drawChartGraphicsMatrix(const int32_t viewport[4], BrainOpenGLTextRenderInterface* /*textRenderer*/, ChartableMatrixInterface* chartMatrixInterface, const int32_t scalarDataSeriesMapIndex) { CaretAssert(chartMatrixInterface); CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); uint8_t highlightRGBByte[3]; //prefs->getColorChartMatrixGridLines(highlightRGBByte); prefs->getColorForegroundChartView(highlightRGBByte); const float highlightRGB[3] = { highlightRGBByte[0] / 255.0, highlightRGBByte[1] / 255.0, highlightRGBByte[2] / 255.0 }; int32_t numberOfRows = 0; int32_t numberOfColumns = 0; std::vector matrixRGBA; if (chartMatrixInterface->getMatrixDataRGBA(numberOfRows, numberOfColumns, matrixRGBA)) { std::set selectedColumnIndices; std::set selectedRowIndices; CiftiMappableConnectivityMatrixDataFile* connMapFile = dynamic_cast(chartMatrixInterface); if (connMapFile != NULL) { const ConnectivityDataLoaded* connDataLoaded = connMapFile->getConnectivityDataLoaded(); if (connDataLoaded != NULL) { int64_t loadedRowIndex = -1; int64_t loadedColumnIndex = -1; connDataLoaded->getRowColumnLoading(loadedRowIndex, loadedColumnIndex); if (loadedRowIndex >= 0) { selectedRowIndices.insert(loadedRowIndex); } else if (loadedColumnIndex >= 0) { selectedColumnIndices.insert(loadedColumnIndex); } } } CiftiParcelScalarFile* parcelScalarFile = dynamic_cast(chartMatrixInterface); if (parcelScalarFile != NULL) { EventCaretMappableDataFileMapsViewedInOverlays mapOverlayEvent(parcelScalarFile); EventManager::get()->sendEvent(mapOverlayEvent.getPointer()); selectedColumnIndices = mapOverlayEvent.getSelectedMapIndices(); } CiftiParcelLabelFile* parcelLabelFile = dynamic_cast(chartMatrixInterface); if (parcelLabelFile != NULL) { EventCaretMappableDataFileMapsViewedInOverlays mapOverlayEvent(parcelLabelFile); EventManager::get()->sendEvent(mapOverlayEvent.getPointer()); selectedColumnIndices = mapOverlayEvent.getSelectedMapIndices(); } CiftiScalarDataSeriesFile* scalarDataSeriesFile = dynamic_cast(chartMatrixInterface); if (scalarDataSeriesFile != NULL) { if (scalarDataSeriesMapIndex >= 0) { //loadedRowIndex = scalarDataSeriesMapIndex; selectedRowIndices.insert(scalarDataSeriesMapIndex); // 12/29/2014 JWH selectedColumnIndices.insert(scalarDataSeriesMapIndex); } } bool applyTransformationsFlag = false; float panningXY[2] = { 0.0, 0.0 }; float zooming = 1.0; float cellWidth = 1.0; float cellHeight = 1.0; /* * Setup width/height of area in which matrix is drawn with a * small margin along all of the edges */ float margin = 10.0; if ((viewport[2] < (margin * 3.0)) || (viewport[3] < (margin * 3.0))) { margin = 0.0; } const float graphicsWidth = viewport[2] - (margin * 2.0); const float graphicsHeight = viewport[3] - (margin * 2.0); /* * Set the width and neight of each matrix cell. */ ChartMatrixDisplayProperties* matrixProperties = chartMatrixInterface->getChartMatrixDisplayProperties(m_tabIndex); CaretAssert(matrixProperties); const ChartMatrixScaleModeEnum::Enum scaleMode = matrixProperties->getScaleMode(); switch (scaleMode) { case ChartMatrixScaleModeEnum::CHART_MATRIX_SCALE_AUTO: /* * Auto scale 'fills' the matrix region * and updates the width and height in the * matrix properties for use in manual mode. * There is NO zooming or panning for Auto scale. */ cellWidth = graphicsWidth / numberOfColumns; cellHeight = graphicsHeight / numberOfRows; matrixProperties->setCellWidth(cellWidth); matrixProperties->setCellHeight(cellHeight); break; case ChartMatrixScaleModeEnum::CHART_MATRIX_SCALE_MANUAL: /* * Use the cell width and height for manual mode * and allow both panning and zooming. */ cellWidth = matrixProperties->getCellWidth(); cellHeight = matrixProperties->getCellHeight(); matrixProperties->getViewPanning(panningXY); zooming = matrixProperties->getViewZooming(); applyTransformationsFlag = true; break; } const bool highlightSelectedRowColumnFlag = ( ( ! m_identificationModeFlag) && matrixProperties->isSelectedRowColumnHighlighted() ); const bool displayGridLinesFlag = ( ( ! m_identificationModeFlag) && matrixProperties->isGridLinesDisplayed() ); /* * Set the coordinates for the area in which the matrix is drawn. */ const float xMin = -margin; const float xMax = graphicsWidth + margin; const float yMin = -margin; const float yMax = graphicsHeight + margin; glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(xMin, xMax, yMin, yMax, -1.0, 1.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); if (applyTransformationsFlag) { glTranslatef(panningXY[0], panningXY[1], 0.0); const float chartWidth = cellWidth * numberOfColumns; const float chartHeight = cellHeight * numberOfRows; const float halfWidth = chartWidth / 2.0; const float halfHeight = chartHeight / 2.0; glTranslatef(halfWidth, halfHeight, 0.0); glScalef(zooming, zooming, 1.0); glTranslatef(-halfWidth, -halfHeight, 0.0); } int32_t rgbaOffset = 0; std::vector quadVerticesXYZ; quadVerticesXYZ.reserve(numberOfRows * numberOfColumns * 3); std::vector quadVerticesFloatRGBA; quadVerticesFloatRGBA.reserve(numberOfRows * numberOfColumns * 4); std::vector quadVerticesByteRGBA; quadVerticesByteRGBA.reserve(numberOfRows * numberOfColumns * 4); float cellY = (numberOfRows - 1) * cellHeight; for (int32_t rowIndex = 0; rowIndex < numberOfRows; rowIndex++) { float cellX = 0; for (int32_t columnIndex = 0; columnIndex < numberOfColumns; columnIndex++) { CaretAssertVectorIndex(matrixRGBA, rgbaOffset+3); const float* rgba = &matrixRGBA[rgbaOffset]; rgbaOffset += 4; uint8_t idRGBA[4]; if (m_identificationModeFlag) { addToChartMatrixIdentification(rowIndex, columnIndex, idRGBA); } if (m_identificationModeFlag) { quadVerticesByteRGBA.push_back(idRGBA[0]); quadVerticesByteRGBA.push_back(idRGBA[1]); quadVerticesByteRGBA.push_back(idRGBA[2]); quadVerticesByteRGBA.push_back(idRGBA[3]); } else { quadVerticesFloatRGBA.push_back(rgba[0]); quadVerticesFloatRGBA.push_back(rgba[1]); quadVerticesFloatRGBA.push_back(rgba[2]); quadVerticesFloatRGBA.push_back(rgba[3]); } quadVerticesXYZ.push_back(cellX); quadVerticesXYZ.push_back(cellY); quadVerticesXYZ.push_back(0.0); if (m_identificationModeFlag) { quadVerticesByteRGBA.push_back(idRGBA[0]); quadVerticesByteRGBA.push_back(idRGBA[1]); quadVerticesByteRGBA.push_back(idRGBA[2]); quadVerticesByteRGBA.push_back(idRGBA[3]); } else { quadVerticesFloatRGBA.push_back(rgba[0]); quadVerticesFloatRGBA.push_back(rgba[1]); quadVerticesFloatRGBA.push_back(rgba[2]); quadVerticesFloatRGBA.push_back(rgba[3]); } quadVerticesXYZ.push_back(cellX + cellWidth); quadVerticesXYZ.push_back(cellY); quadVerticesXYZ.push_back(0.0); if (m_identificationModeFlag) { quadVerticesByteRGBA.push_back(idRGBA[0]); quadVerticesByteRGBA.push_back(idRGBA[1]); quadVerticesByteRGBA.push_back(idRGBA[2]); quadVerticesByteRGBA.push_back(idRGBA[3]); } else { quadVerticesFloatRGBA.push_back(rgba[0]); quadVerticesFloatRGBA.push_back(rgba[1]); quadVerticesFloatRGBA.push_back(rgba[2]); quadVerticesFloatRGBA.push_back(rgba[3]); } quadVerticesXYZ.push_back(cellX + cellWidth); quadVerticesXYZ.push_back(cellY + cellHeight); quadVerticesXYZ.push_back(0.0); if (m_identificationModeFlag) { quadVerticesByteRGBA.push_back(idRGBA[0]); quadVerticesByteRGBA.push_back(idRGBA[1]); quadVerticesByteRGBA.push_back(idRGBA[2]); quadVerticesByteRGBA.push_back(idRGBA[3]); } else { quadVerticesFloatRGBA.push_back(rgba[0]); quadVerticesFloatRGBA.push_back(rgba[1]); quadVerticesFloatRGBA.push_back(rgba[2]); quadVerticesFloatRGBA.push_back(rgba[3]); } quadVerticesXYZ.push_back(cellX); quadVerticesXYZ.push_back(cellY + cellHeight); quadVerticesXYZ.push_back(0.0); cellX += cellWidth; } cellY -= cellHeight; } /* * Draw the matrix elements. */ if (m_identificationModeFlag) { CaretAssert((quadVerticesXYZ.size() / 3) == (quadVerticesByteRGBA.size() / 4)); const int32_t numberQuadVertices = static_cast(quadVerticesXYZ.size() / 3); glBegin(GL_QUADS); for (int32_t i = 0; i < numberQuadVertices; i++) { CaretAssertVectorIndex(quadVerticesByteRGBA, i*4 + 3); glColor4ubv(&quadVerticesByteRGBA[i*4]); CaretAssertVectorIndex(quadVerticesXYZ, i*3 + 2); glVertex3fv(&quadVerticesXYZ[i*3]); } glEnd(); } else { /* * Enable alpha blending so voxels that are not drawn from higher layers * allow voxels from lower layers to be seen. */ glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); CaretAssert((quadVerticesXYZ.size() / 3) == (quadVerticesFloatRGBA.size() / 4)); const int32_t numberQuadVertices = static_cast(quadVerticesXYZ.size() / 3); glBegin(GL_QUADS); for (int32_t i = 0; i < numberQuadVertices; i++) { CaretAssertVectorIndex(quadVerticesFloatRGBA, i*4 + 3); glColor4fv(&quadVerticesFloatRGBA[i*4]); CaretAssertVectorIndex(quadVerticesXYZ, i*3 + 2); glVertex3fv(&quadVerticesXYZ[i*3]); } glEnd(); glDisable(GL_BLEND); /* * Drawn an outline around the matrix elements. */ if (displayGridLinesFlag) { uint8_t gridLineColorBytes[3]; prefs->getColorChartMatrixGridLines(gridLineColorBytes); float gridLineColorFloats[4]; CaretPreferences::byteRgbToFloatRgb(gridLineColorBytes, gridLineColorFloats); gridLineColorFloats[3] = 1.0; std::vector outlineRGBA; outlineRGBA.reserve(numberQuadVertices * 4); for (int32_t i = 0; i < numberQuadVertices; i++) { outlineRGBA.push_back(gridLineColorFloats[0]); outlineRGBA.push_back(gridLineColorFloats[1]); outlineRGBA.push_back(gridLineColorFloats[2]); outlineRGBA.push_back(gridLineColorFloats[3]); } glPolygonMode(GL_FRONT, GL_LINE); glLineWidth(1.0); glBegin(GL_QUADS); for (int32_t i = 0; i < numberQuadVertices; i++) { CaretAssertVectorIndex(outlineRGBA, i*4 + 3); glColor4fv(&outlineRGBA[i*4]); CaretAssertVectorIndex(quadVerticesXYZ, i*3 + 2); glVertex3fv(&quadVerticesXYZ[i*3]); } glEnd(); } if ( (! selectedRowIndices.empty()) && highlightSelectedRowColumnFlag) { std::vector rowXYZ; std::vector rowRGBA; for (std::set::iterator rowIter = selectedRowIndices.begin(); rowIter != selectedRowIndices.end(); rowIter ++) { const float rowIndex = * rowIter; const float rowY = (numberOfRows - rowIndex - 1) * cellHeight; rowXYZ.push_back(0.0); rowXYZ.push_back(rowY); rowXYZ.push_back(0.0); rowRGBA.push_back(highlightRGB[0]); rowRGBA.push_back(highlightRGB[1]); rowRGBA.push_back(highlightRGB[2]); rowRGBA.push_back(1.0); rowXYZ.push_back(0.0); rowXYZ.push_back(rowY + cellHeight); rowXYZ.push_back(0.0); rowRGBA.push_back(highlightRGB[0]); rowRGBA.push_back(highlightRGB[1]); rowRGBA.push_back(highlightRGB[2]); rowRGBA.push_back(1.0); rowXYZ.push_back(numberOfColumns * cellWidth); rowXYZ.push_back(rowY + cellHeight); rowXYZ.push_back(0.0); rowRGBA.push_back(highlightRGB[0]); rowRGBA.push_back(highlightRGB[1]); rowRGBA.push_back(highlightRGB[2]); rowRGBA.push_back(1.0); rowXYZ.push_back(numberOfColumns * cellWidth); rowXYZ.push_back(rowY); rowXYZ.push_back(0.0); rowRGBA.push_back(highlightRGB[0]); rowRGBA.push_back(highlightRGB[1]); rowRGBA.push_back(highlightRGB[2]); rowRGBA.push_back(1.0); } CaretAssert((rowXYZ.size() / 3) == (rowRGBA.size() / 4)); const int32_t numberOfVertices = static_cast(rowXYZ.size() / 3); const int32_t numberOfQuads = numberOfVertices / 4; CaretAssert((numberOfQuads * 4) == numberOfVertices); /* * As cells get larger, increase linewidth for selected row */ const float highlightLineWidth = std::max(((cellHeight * zooming) * 0.20), 3.0); glLineWidth(highlightLineWidth); for (int32_t iQuad = 0; iQuad < numberOfQuads; iQuad++) { glBegin(GL_LINE_LOOP); for (int32_t iVert = 0; iVert < 4; iVert++) { const int32_t rgbaOffset = (iQuad * 16) + (iVert * 4); CaretAssertVectorIndex(rowRGBA, rgbaOffset + 3); glColor4fv(&rowRGBA[rgbaOffset]); const int32_t xyzOffset = (iQuad * 12) + (iVert * 3); CaretAssertVectorIndex(rowXYZ, xyzOffset + 2); glVertex3fv(&rowXYZ[xyzOffset]); } glEnd(); } glLineWidth(1.0); } if ( (! selectedColumnIndices.empty()) && highlightSelectedRowColumnFlag) { std::vector columnXYZ; std::vector columnRGBA; for (std::set::iterator colIter = selectedColumnIndices.begin(); colIter != selectedColumnIndices.end(); colIter++) { const float columnIndex = *colIter; const float colX = columnIndex * cellWidth; columnXYZ.push_back(colX); columnXYZ.push_back(0.0); columnXYZ.push_back(0.0); columnRGBA.push_back(highlightRGB[0]); columnRGBA.push_back(highlightRGB[1]); columnRGBA.push_back(highlightRGB[2]); columnRGBA.push_back(1.0); columnXYZ.push_back(colX + cellWidth); columnXYZ.push_back(0.0); columnXYZ.push_back(0.0); columnRGBA.push_back(highlightRGB[0]); columnRGBA.push_back(highlightRGB[1]); columnRGBA.push_back(highlightRGB[2]); columnRGBA.push_back(1.0); columnXYZ.push_back(colX + cellWidth); columnXYZ.push_back(numberOfRows * cellHeight); columnXYZ.push_back(0.0); columnRGBA.push_back(highlightRGB[0]); columnRGBA.push_back(highlightRGB[1]); columnRGBA.push_back(highlightRGB[2]); columnRGBA.push_back(1.0); columnXYZ.push_back(colX); columnXYZ.push_back(numberOfRows * cellHeight); columnXYZ.push_back(0.0); columnRGBA.push_back(highlightRGB[0]); columnRGBA.push_back(highlightRGB[1]); columnRGBA.push_back(highlightRGB[2]); columnRGBA.push_back(1.0); } CaretAssert((columnXYZ.size() / 3) == (columnRGBA.size() / 4)); const int32_t numberOfVertices = static_cast(columnXYZ.size() / 3); const int32_t numberOfQuads = numberOfVertices / 4; CaretAssert((numberOfQuads * 4) == numberOfVertices); /* * As cells get larger, increase linewidth for selected row */ const float highlightLineWidth = std::max(((cellHeight * zooming) * 0.20), 3.0); glLineWidth(highlightLineWidth); for (int32_t iQuad = 0; iQuad < numberOfQuads; iQuad++) { glBegin(GL_LINE_LOOP); for (int32_t iVert = 0; iVert < 4; iVert++) { const int32_t rgbaOffset = (iQuad * 16) + (iVert * 4); CaretAssertVectorIndex(columnRGBA, rgbaOffset + 3); glColor4fv(&columnRGBA[rgbaOffset]); const int32_t xyzOffset = (iQuad * 12) + (iVert * 3); CaretAssertVectorIndex(columnXYZ, xyzOffset + 2); glVertex3fv(&columnXYZ[xyzOffset]); } glEnd(); } glLineWidth(1.0); } glPolygonMode(GL_FRONT, GL_FILL); } } } /** * Save the state of OpenGL. * Copied from Qt's qgl.cpp, qt_save_gl_state(). */ void BrainOpenGLChartDrawingFixedPipeline::saveStateOfOpenGL() { glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS); glPushAttrib(GL_ALL_ATTRIB_BITS); glMatrixMode(GL_TEXTURE); glPushMatrix(); glLoadIdentity(); glMatrixMode(GL_PROJECTION); glPushMatrix(); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glShadeModel(GL_FLAT); // glDisable(GL_CULL_FACE); glDisable(GL_LIGHTING); glDisable(GL_STENCIL_TEST); glDisable(GL_DEPTH_TEST); glDisable(GL_BLEND); } /** * Restore the state of OpenGL. * Copied from Qt's qgl.cpp, qt_restore_gl_state(). */ void BrainOpenGLChartDrawingFixedPipeline::restoreStateOfOpenGL() { glMatrixMode(GL_TEXTURE); glPopMatrix(); glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); glPopAttrib(); glPopClientAttrib(); } /** * Add an item for line identification. * * @param chartDataIndex * Index of the chart data. * @param lineIndex * Index of the line. * @param rgbaForColorIdentificationOut * Encoded identification in RGBA color OUTPUT */ void BrainOpenGLChartDrawingFixedPipeline::addToChartLineIdentification(const int32_t chartDataIndex, const int32_t chartLineIndex, uint8_t rgbaForColorIdentificationOut[4]) { const int32_t idIndex = m_identificationIndices.size() / IDENTIFICATION_INDICES_PER_CHART_LINE; m_fixedPipelineDrawing->colorIdentification->addItem(rgbaForColorIdentificationOut, m_chartCartesianSelectionTypeForIdentification, idIndex); rgbaForColorIdentificationOut[3] = 255; /* * If these items change, need to update reset and * processing of identification. */ m_identificationIndices.push_back(chartDataIndex); m_identificationIndices.push_back(chartLineIndex); } /** * Add an item for matrix identification. * * @param matrixRowIndex * Index of the row * @param matrixColumnIndex * Index of the column * @param rgbaForColorIdentificationOut * Encoded identification in RGBA color OUTPUT */ void BrainOpenGLChartDrawingFixedPipeline::addToChartMatrixIdentification(const int32_t matrixRowIndex, const int32_t matrixColumnIndex, uint8_t rgbaForColorIdentificationOut[4]) { const int32_t idIndex = m_identificationIndices.size() / IDENTIFICATION_INDICES_PER_MATRIX_ELEMENT; m_fixedPipelineDrawing->colorIdentification->addItem(rgbaForColorIdentificationOut, m_chartableMatrixSelectionTypeForIdentification, idIndex); rgbaForColorIdentificationOut[3] = 255; /* * If these items change, need to update reset and * processing of identification. */ m_identificationIndices.push_back(matrixRowIndex); m_identificationIndices.push_back(matrixColumnIndex); } /** * Reset identification. */ void BrainOpenGLChartDrawingFixedPipeline::resetIdentification() { m_identificationIndices.clear(); if (m_identificationModeFlag) { const int32_t estimatedNumberOfItems = 1000; m_identificationIndices.reserve(estimatedNumberOfItems); } } /** * Process identification. */ void BrainOpenGLChartDrawingFixedPipeline::processIdentification() { int32_t identifiedItemIndex; float depth = -1.0; if (m_chartModelDataSeriesBeingDrawnForIdentification != NULL) { m_fixedPipelineDrawing->getIndexFromColorSelection(m_chartCartesianSelectionTypeForIdentification, m_fixedPipelineDrawing->mouseX, m_fixedPipelineDrawing->mouseY, identifiedItemIndex, depth); if (identifiedItemIndex >= 0) { const int32_t idIndex = identifiedItemIndex * IDENTIFICATION_INDICES_PER_CHART_LINE; const int32_t chartDataIndex = m_identificationIndices[idIndex]; const int32_t chartLineIndex = m_identificationIndices[idIndex + 1]; SelectionItemChartDataSeries* chartDataSeriesID = m_brain->getSelectionManager()->getChartDataSeriesIdentification(); if (chartDataSeriesID->isOtherScreenDepthCloserToViewer(depth)) { ChartDataCartesian* chartDataCartesian = dynamic_cast(m_chartModelDataSeriesBeingDrawnForIdentification->getChartDataAtIndex(chartDataIndex)); CaretAssert(chartDataCartesian); chartDataSeriesID->setChart(m_chartModelDataSeriesBeingDrawnForIdentification, chartDataCartesian, chartLineIndex); const ChartPoint* chartPoint = chartDataCartesian->getPointAtIndex(chartLineIndex); const float lineXYZ[3] = { chartPoint->getX(), chartPoint->getY(), 0.0 }; m_fixedPipelineDrawing->setSelectedItemScreenXYZ(chartDataSeriesID, lineXYZ); } } } else if (m_chartModelFrequencySeriesBeingDrawnForIdentification != NULL) { m_fixedPipelineDrawing->getIndexFromColorSelection(m_chartCartesianSelectionTypeForIdentification, m_fixedPipelineDrawing->mouseX, m_fixedPipelineDrawing->mouseY, identifiedItemIndex, depth); if (identifiedItemIndex >= 0) { const int32_t idIndex = identifiedItemIndex * IDENTIFICATION_INDICES_PER_CHART_LINE; const int32_t chartDataIndex = m_identificationIndices[idIndex]; const int32_t chartLineIndex = m_identificationIndices[idIndex + 1]; SelectionItemChartFrequencySeries* chartFrequencySeriesID = m_brain->getSelectionManager()->getChartFrequencySeriesIdentification(); if (chartFrequencySeriesID->isOtherScreenDepthCloserToViewer(depth)) { ChartDataCartesian* chartDataCartesian = dynamic_cast(m_chartModelFrequencySeriesBeingDrawnForIdentification->getChartDataAtIndex(chartDataIndex)); CaretAssert(chartDataCartesian); chartFrequencySeriesID->setChart(m_chartModelFrequencySeriesBeingDrawnForIdentification, chartDataCartesian, chartLineIndex); const ChartPoint* chartPoint = chartDataCartesian->getPointAtIndex(chartLineIndex); const float lineXYZ[3] = { chartPoint->getX(), chartPoint->getY(), 0.0 }; m_fixedPipelineDrawing->setSelectedItemScreenXYZ(chartFrequencySeriesID, lineXYZ); } } } else if (m_chartModelTimeSeriesBeingDrawnForIdentification != NULL) { m_fixedPipelineDrawing->getIndexFromColorSelection(m_chartCartesianSelectionTypeForIdentification, m_fixedPipelineDrawing->mouseX, m_fixedPipelineDrawing->mouseY, identifiedItemIndex, depth); if (identifiedItemIndex >= 0) { const int32_t idIndex = identifiedItemIndex * IDENTIFICATION_INDICES_PER_CHART_LINE; const int32_t chartDataIndex = m_identificationIndices[idIndex]; const int32_t chartLineIndex = m_identificationIndices[idIndex + 1]; SelectionItemChartTimeSeries* chartTimeSeriesID = m_brain->getSelectionManager()->getChartTimeSeriesIdentification(); if (chartTimeSeriesID->isOtherScreenDepthCloserToViewer(depth)) { ChartDataCartesian* chartDataCartesian = dynamic_cast(m_chartModelTimeSeriesBeingDrawnForIdentification->getChartDataAtIndex(chartDataIndex)); CaretAssert(chartDataCartesian); chartTimeSeriesID->setChart(m_chartModelTimeSeriesBeingDrawnForIdentification, chartDataCartesian, chartLineIndex); const ChartPoint* chartPoint = chartDataCartesian->getPointAtIndex(chartLineIndex); const float lineXYZ[3] = { chartPoint->getX(), chartPoint->getY(), 0.0 }; m_fixedPipelineDrawing->setSelectedItemScreenXYZ(chartTimeSeriesID, lineXYZ); } } } else if (m_chartableMatrixInterfaceBeingDrawnForIdentification != NULL) { m_fixedPipelineDrawing->getIndexFromColorSelection(m_chartableMatrixSelectionTypeForIdentification, m_fixedPipelineDrawing->mouseX, m_fixedPipelineDrawing->mouseY, identifiedItemIndex, depth); if (identifiedItemIndex >= 0) { const int32_t idIndex = identifiedItemIndex * IDENTIFICATION_INDICES_PER_MATRIX_ELEMENT; const int32_t rowIndex = m_identificationIndices[idIndex]; const int32_t columnIndex = m_identificationIndices[idIndex + 1]; SelectionItemChartMatrix* chartMatrixID = m_brain->getSelectionManager()->getChartMatrixIdentification(); if (chartMatrixID->isOtherScreenDepthCloserToViewer(depth)) { chartMatrixID->setChartMatrix(m_chartableMatrixInterfaceBeingDrawnForIdentification, rowIndex, columnIndex); } } } } workbench-1.1.1/src/Brain/BrainOpenGLChartDrawingFixedPipeline.h000066400000000000000000000170101255417355300245440ustar00rootroot00000000000000#ifndef __BRAIN_OPEN_G_L_CHART_DRAWING_FIXED_PIPELINE_H__ #define __BRAIN_OPEN_G_L_CHART_DRAWING_FIXED_PIPELINE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "BrainOpenGLChartDrawingInterface.h" #include "CaretColorEnum.h" namespace caret { class Brain; class BrainOpenGLFixedPipeline; class ChartAxis; class ChartAxisCartesian; class ChartDataCartesian; class ChartModelCartesian; class ChartModelDataSeries; class ChartModelFrequencySeries; class ChartModelTimeSeries; class ChartableMatrixInterface; class BrainOpenGLChartDrawingFixedPipeline : public BrainOpenGLChartDrawingInterface { public: BrainOpenGLChartDrawingFixedPipeline(); virtual ~BrainOpenGLChartDrawingFixedPipeline(); virtual void drawCartesianChart(Brain* brain, BrainOpenGLFixedPipeline* fixedPipelineDrawing, const int32_t viewport[4], BrainOpenGLTextRenderInterface* textRenderer, ChartModelCartesian* cartesianChart, const SelectionItemDataTypeEnum::Enum selectionItemDataType, const int32_t tabIndex); virtual void drawMatrixChart(Brain* brain, BrainOpenGLFixedPipeline* fixedPipelineDrawing, const int32_t viewport[4], BrainOpenGLTextRenderInterface* textRenderer, ChartableMatrixInterface* chartMatrixInterface, const int32_t scalarDataSeriesMapIndex, const SelectionItemDataTypeEnum::Enum selectionItemDataType, const int32_t tabIndex); private: class Margins { public: Margins(const int32_t defaultSize) { m_bottom = defaultSize; m_left = defaultSize; m_right = defaultSize; m_top = defaultSize; } int32_t m_bottom; int32_t m_left; int32_t m_right; int32_t m_top; }; BrainOpenGLChartDrawingFixedPipeline(const BrainOpenGLChartDrawingFixedPipeline&); BrainOpenGLChartDrawingFixedPipeline& operator=(const BrainOpenGLChartDrawingFixedPipeline&); void drawChartGraphicsLineSeries(BrainOpenGLTextRenderInterface* textRenderer, ChartModelCartesian* chart); void drawChartGraphicsMatrix(const int32_t viewport[4], BrainOpenGLTextRenderInterface* textRenderer, ChartableMatrixInterface* chartMatrixInterface, const int32_t scalarDataSeriesMapIndex); void drawChartGraphicsBoxAndSetViewport(const float vpX, const float vpY, const float vpWidth, const float vpHeight, const Margins& margins, int32_t chartGraphicsDrawingViewportOut[4]); void drawChartAxis(const float vpX, const float vpY, const float vpWidth, const float vpHeight, Margins& margins, BrainOpenGLTextRenderInterface* textRenderer, ChartAxis* axis); void drawChartAxisCartesian(const float vpX, const float vpY, const float vpWidth, const float vpHeight, Margins& margins, BrainOpenGLTextRenderInterface* textRenderer, ChartAxisCartesian* axis); void drawChartDataCartesian(const int32_t chartDataIndex, const ChartDataCartesian* chartDataCartesian, const float lineWidth, const float rgb[3]); void estimateCartesianChartAxisLegendsWidthHeight(BrainOpenGLTextRenderInterface* textRenderer, ChartAxis* axis, int32_t& widthOut, int32_t& heightOut); void restoreStateOfOpenGL(); void saveStateOfOpenGL(); void addToChartLineIdentification(const int32_t chartDataIndex, const int32_t lineIndex, uint8_t rgbaForColorIdentification[4]); void addToChartMatrixIdentification(const int32_t matrixRowIndex, const int32_t matrixColumnIndex, uint8_t rgbaForColorIdentification[4]); void resetIdentification(); void processIdentification(); public: // ADD_NEW_METHODS_HERE private: Brain* m_brain; BrainOpenGLFixedPipeline* m_fixedPipelineDrawing; ChartModelDataSeries* m_chartModelDataSeriesBeingDrawnForIdentification; ChartModelFrequencySeries* m_chartModelFrequencySeriesBeingDrawnForIdentification; ChartModelTimeSeries* m_chartModelTimeSeriesBeingDrawnForIdentification; SelectionItemDataTypeEnum::Enum m_chartCartesianSelectionTypeForIdentification; ChartableMatrixInterface* m_chartableMatrixInterfaceBeingDrawnForIdentification; SelectionItemDataTypeEnum::Enum m_chartableMatrixSelectionTypeForIdentification; int32_t m_tabIndex; std::vector m_identificationIndices; bool m_identificationModeFlag; // ADD_NEW_MEMBERS_HERE static const int32_t IDENTIFICATION_INDICES_PER_CHART_LINE; static const int32_t IDENTIFICATION_INDICES_PER_MATRIX_ELEMENT; }; #ifdef __BRAIN_OPEN_G_L_CHART_DRAWING_FIXED_PIPELINE_DECLARE__ const int32_t BrainOpenGLChartDrawingFixedPipeline::IDENTIFICATION_INDICES_PER_CHART_LINE = 2; const int32_t BrainOpenGLChartDrawingFixedPipeline::IDENTIFICATION_INDICES_PER_MATRIX_ELEMENT = 2; #endif // __BRAIN_OPEN_G_L_CHART_DRAWING_FIXED_PIPELINE_DECLARE__ } // namespace #endif //__BRAIN_OPEN_G_L_CHART_DRAWING_FIXED_PIPELINE_H__ workbench-1.1.1/src/Brain/BrainOpenGLChartDrawingInterface.h000066400000000000000000000105741255417355300237270ustar00rootroot00000000000000#ifndef __BRAIN_OPEN_G_L_CHART_DRAWING_INTERFACE_H__ #define __BRAIN_OPEN_G_L_CHART_DRAWING_INTERFACE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AString.h" #include "SelectionItemDataTypeEnum.h" /** * \class caret::BrainOpenGLTextRenderInterface * \brief Interface for drawing charts with OpenGL. * \ingroup Brain */ namespace caret { class Brain; class BrainOpenGLFixedPipeline; class BrainOpenGLTextRenderInterface; class ChartModelCartesian; class ChartableMatrixInterface; class BrainOpenGLChartDrawingInterface { public: BrainOpenGLChartDrawingInterface() { } virtual ~BrainOpenGLChartDrawingInterface() { } /** * Draw a cartesian chart in the given viewport. * * @param brain * Brain. * @param fixedPipelineDrawing * The fixed pipeline OpenGL drawing. * @param viewport * Viewport for the chart. * @param textRenderer * Text rendering. * @param cartesianChart * Cartesian Chart that is drawn. * @param selectionItemDataType * Selected data type. * @param tabIndex * Index of the tab. */ virtual void drawCartesianChart(Brain* brain, BrainOpenGLFixedPipeline* fixedPipelineDrawing, const int32_t viewport[4], BrainOpenGLTextRenderInterface* textRenderer, ChartModelCartesian* cartesianChart, const SelectionItemDataTypeEnum::Enum selectionItemDataType, const int32_t tabIndex) = 0; /** * Draw a matrix chart in the given viewport. * * @param brain * Brain. * @param fixedPipelineDrawing * The fixed pipeline OpenGL drawing. * @param viewport * Viewport for the chart. * @param textRenderer * Text rendering. * @param chartMatrixInterface * Chart matrix interface containing matrix data. * @param scalarDataSeriesMapIndex * Scalar data series selected map index. * @param selectionItemDataType * Selected data type. * @param tabIndex * Index of the tab. */ virtual void drawMatrixChart(Brain* brain, BrainOpenGLFixedPipeline* fixedPipelineDrawing, const int32_t viewport[4], BrainOpenGLTextRenderInterface* textRenderer, ChartableMatrixInterface* chartMatrixInterface, const int32_t scalarDataSeriesMapIndex, const SelectionItemDataTypeEnum::Enum selectionItemDataType, const int32_t tabIndex) = 0; private: BrainOpenGLChartDrawingInterface(const BrainOpenGLChartDrawingInterface&); BrainOpenGLChartDrawingInterface& operator=(const BrainOpenGLChartDrawingInterface&); public: // ADD_NEW_METHODS_HERE private: // ADD_NEW_MEMBERS_HERE }; #ifdef __BRAIN_OPEN_G_L_CHART_DRAWING_INTERFACE_DECLARE__ // #endif // __BRAIN_OPEN_G_L_CHART_DRAWING_INTERFACE_DECLARE__ } // namespace #endif //__BRAIN_OPEN_G_L_CHART_DRAWING_INTERFACE_H__ workbench-1.1.1/src/Brain/BrainOpenGLFixedPipeline.cxx000066400000000000000000007205271255417355300226370ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretOpenGLInclude.h" #define __BRAIN_OPENGL_FIXED_PIPELINE_DEFINE_H #include "BrainOpenGLFixedPipeline.h" #undef __BRAIN_OPENGL_FIXED_PIPELINE_DEFINE_H #include #include #include #include #include #include "Border.h" #include "BorderFile.h" #include "Brain.h" #include "BrainOpenGLChartDrawingFixedPipeline.h" #include "BrainOpenGLPrimitiveDrawing.h" #include "BrainOpenGLVolumeObliqueSliceDrawing.h" #include "BrainOpenGLVolumeSliceDrawing.h" #include "BrainOpenGLShapeCone.h" #include "BrainOpenGLShapeCube.h" #include "BrainOpenGLShapeCylinder.h" #include "BrainOpenGLShapeSphere.h" #include "BrainOpenGLViewportContent.h" #include "BrainStructure.h" #include "BrowserTabContent.h" #include "BoundingBox.h" #include "CaretAssert.h" #include "CaretDataFileSelectionModel.h" #include "CaretLogger.h" #include "CaretMappableDataFile.h" #include "CaretMappableDataFileAndMapSelectionModel.h" #include "CaretPreferences.h" #include "ChartableMatrixInterface.h" #include "ChartableMatrixSeriesInterface.h" #include "ChartModelDataSeries.h" #include "ChartModelFrequencySeries.h" #include "ChartModelTimeSeries.h" #include "CiftiBrainordinateLabelFile.h" #include "CiftiFiberOrientationFile.h" #include "CiftiFiberTrajectoryFile.h" #include "ClippingPlaneGroup.h" #include "DeveloperFlagsEnum.h" #include "DisplayGroupEnum.h" #include "DisplayPropertiesBorders.h" #include "DisplayPropertiesFiberOrientation.h" #include "DisplayPropertiesFoci.h" #include "DisplayPropertiesImages.h" #include "DisplayPropertiesLabels.h" #include "DisplayPropertiesSurface.h" #include "DisplayPropertiesVolume.h" #include "ElapsedTimer.h" #include "EventManager.h" #include "EventModelSurfaceGet.h" #include "EventNodeIdentificationColorsGetFromCharts.h" #include "FastStatistics.h" #include "Fiber.h" #include "FiberOrientation.h" #include "FiberOrientationTrajectory.h" #include "FiberTrajectoryMapProperties.h" #include "FociFile.h" #include "Focus.h" #include "GiftiLabel.h" #include "GiftiLabelTable.h" #include "GroupAndNameHierarchyModel.h" #include "IdentifiedItemNode.h" #include "IdentificationManager.h" #include "ImageFile.h" #include "Matrix4x4.h" #include "SelectionItemBorderSurface.h" #include "SelectionItemFocusSurface.h" #include "SelectionItemFocusVolume.h" #include "SelectionItemSurfaceNode.h" #include "SelectionItemSurfaceNodeIdentificationSymbol.h" #include "SelectionItemSurfaceTriangle.h" #include "SelectionItemVoxel.h" #include "SurfaceMontageConfigurationCerebellar.h" #include "SurfaceMontageConfigurationCerebral.h" #include "SurfaceMontageConfigurationFlatMaps.h" #include "IdentificationWithColor.h" #include "SelectionManager.h" #include "MathFunctions.h" #include "ModelChart.h" #include "ModelSurface.h" #include "ModelSurfaceMontage.h" #include "ModelVolume.h" #include "ModelWholeBrain.h" #include "NodeAndVoxelColoring.h" #include "Overlay.h" #include "OverlaySet.h" #include "Palette.h" #include "PaletteColorMapping.h" #include "PaletteFile.h" #include "PaletteScalarAndColor.h" #include "Plane.h" #include "SessionManager.h" #include "Surface.h" #include "SurfaceMontageViewport.h" #include "SurfaceNodeColoring.h" #include "SurfaceProjectedItem.h" #include "SurfaceProjectionBarycentric.h" #include "SurfaceProjectionVanEssen.h" #include "SurfaceSelectionModel.h" #include "TopologyHelper.h" #include "VolumeFile.h" #include "VolumeMappableInterface.h" #include "VolumeSurfaceOutlineColorOrTabModel.h" #include "VolumeSurfaceOutlineModel.h" #include "VolumeSurfaceOutlineSetModel.h" using namespace caret; //static const bool USE_OLD_VOLUME_SLICE_DRAWING_FLAG = false; /** * Constructor. * * @param parentGLWidget * The optional text renderer is used for text rendering. * This parameter may be NULL in which case no text * rendering is performed. */ BrainOpenGLFixedPipeline::BrainOpenGLFixedPipeline(BrainOpenGLTextRenderInterface* textRenderer) : BrainOpenGL(textRenderer) { this->initializeMembersBrainOpenGL(); this->colorIdentification = new IdentificationWithColor(); m_shapeSphere = NULL; m_shapeCone = NULL; m_shapeCylinder = NULL; m_shapeCube = NULL; m_shapeCubeRounded = NULL; this->surfaceNodeColoring = new SurfaceNodeColoring(); m_brain = NULL; m_clippingPlaneGroup = NULL; } /** * Destructor. */ BrainOpenGLFixedPipeline::~BrainOpenGLFixedPipeline() { if (m_shapeSphere != NULL) { delete m_shapeSphere; m_shapeSphere = NULL; } if (m_shapeCone != NULL) { delete m_shapeCone; m_shapeCone = NULL; } if (m_shapeCylinder != NULL) { delete m_shapeCylinder; m_shapeCylinder = NULL; } if (m_shapeCube != NULL) { delete m_shapeCube; m_shapeCube = NULL; } if (m_shapeCubeRounded != NULL) { delete m_shapeCubeRounded; m_shapeCubeRounded = NULL; } if (this->surfaceNodeColoring != NULL) { delete this->surfaceNodeColoring; this->surfaceNodeColoring = NULL; } delete this->colorIdentification; this->colorIdentification = NULL; } /** * Selection on a model. * * @param viewportConent * Viewport content in which mouse was clicked * @param mouseX * X position of mouse click * @param mouseY * Y position of mouse click * @param applySelectionBackgroundFiltering * If true (which is in most cases), if there are multiple items * selected, those items "behind" other items are not reported. * For example, suppose a focus is selected and there is a node * the focus. If this parameter is true, the node will NOT be * selected. If this parameter is false, the node will be * selected. */ void BrainOpenGLFixedPipeline::selectModel(BrainOpenGLViewportContent* viewportContent, const int32_t mouseX, const int32_t mouseY, const bool applySelectionBackgroundFiltering) { m_brain = viewportContent->getBrain(); CaretAssert(m_brain); m_clippingPlaneGroup = NULL; this->inverseRotationMatrixValid = false; /* * For identification, set the background * to white. */ glClearColor(1.0, 1.0, 1.0, 0.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); this->mouseX = mouseX; this->mouseY = mouseY; this->colorIdentification->reset(); this->drawModelInternal(MODE_IDENTIFICATION, viewportContent); m_brain->getSelectionManager()->filterSelections(applySelectionBackgroundFiltering); m_brain = NULL; } /** * Project the given window coordinate to the active models. * If the projection is successful, The 'original' XYZ * coordinate in 'projectionOut' will be valid. In addition, * the barycentric coordinate may also be valid in 'projectionOut'. * * @param viewportContent * Viewport content in which mouse was clicked * @param mouseX * X position of mouse click * @param mouseY * Y position of mouse click */ void BrainOpenGLFixedPipeline::projectToModel(BrainOpenGLViewportContent* viewportContent, const int32_t mouseX, const int32_t mouseY, SurfaceProjectedItem& projectionOut) { m_brain = viewportContent->getBrain(); CaretAssert(m_brain); m_clippingPlaneGroup = NULL; m_brain->getSelectionManager()->reset(); this->modeProjectionData = &projectionOut; this->modeProjectionData->reset(); this->modeProjectionScreenDepth = std::numeric_limits::max(); /* * For projection which uses colors for finding triangles, * set the background to white. */ glClearColor(1.0, 1.0, 1.0, 0.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); this->mouseX = mouseX; this->mouseY = mouseY; this->colorIdentification->reset(); this->drawModelInternal(MODE_PROJECTION, viewportContent); this->modeProjectionData = NULL; m_brain = NULL; } /** * Update the foreground and background colors using the model in * the given viewport content. */ void BrainOpenGLFixedPipeline::updateForegroundAndBackgroundColors(BrainOpenGLViewportContent* vpContent) { /* * Default to colors for surface */ CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); prefs->getColorForegroundSurfaceView(m_foregroundColorByte); m_foregroundColorByte[3] = 255; prefs->getColorBackgroundSurfaceView(m_backgroundColorByte); m_backgroundColorByte[3] = 255; if (vpContent != NULL) { const BrowserTabContent* btc = vpContent->getBrowserTabContent(); if (btc != NULL) { const Model* model = btc->getModelForDisplay(); if (model != NULL) { switch (model->getModelType()) { case ModelTypeEnum::MODEL_TYPE_CHART: prefs->getColorForegroundChartView(m_foregroundColorByte); prefs->getColorBackgroundChartView(m_backgroundColorByte); break; case ModelTypeEnum::MODEL_TYPE_INVALID: break; case ModelTypeEnum::MODEL_TYPE_SURFACE: prefs->getColorForegroundSurfaceView(m_foregroundColorByte); prefs->getColorBackgroundSurfaceView(m_backgroundColorByte); break; case ModelTypeEnum::MODEL_TYPE_SURFACE_MONTAGE: prefs->getColorForegroundSurfaceView(m_foregroundColorByte); prefs->getColorBackgroundSurfaceView(m_backgroundColorByte); break; case ModelTypeEnum::MODEL_TYPE_VOLUME_SLICES: prefs->getColorForegroundVolumeView(m_foregroundColorByte); prefs->getColorBackgroundVolumeView(m_backgroundColorByte); break; case ModelTypeEnum::MODEL_TYPE_WHOLE_BRAIN: prefs->getColorForegroundAllView(m_foregroundColorByte); prefs->getColorBackgroundAllView(m_backgroundColorByte); break; } } } m_foregroundColorByte[3] = 255; m_backgroundColorByte[3] = 255; } CaretPreferences::byteRgbToFloatRgb(m_backgroundColorByte, m_backgroundColorFloat); m_backgroundColorFloat[3] = 1.0; CaretPreferences::byteRgbToFloatRgb(m_foregroundColorByte, m_foregroundColorFloat); m_foregroundColorFloat[3] = 1.0; } /** * Draw models in their respective viewports. * * @param viewportContents * Viewport info for drawing. */ void BrainOpenGLFixedPipeline::drawModels(std::vector& viewportContents) { this->inverseRotationMatrixValid = false; m_clippingPlaneGroup = NULL; this->checkForOpenGLError(NULL, "At beginning of drawModels()"); /* * Default the background colors to first model * NOTE: If there are no models, the surface background color is used */ if (viewportContents.empty()) { updateForegroundAndBackgroundColors(NULL); } else { updateForegroundAndBackgroundColors(viewportContents[0]); } /* * Use the background color as the clear color. */ uint8_t clearColorByte[3] = { m_backgroundColorByte[0], m_backgroundColorByte[1], m_backgroundColorByte[2], }; float clearColorFloat[3]; CaretPreferences::byteRgbToFloatRgb(clearColorByte, clearColorFloat); glClearColor(clearColorFloat[0], clearColorFloat[1], clearColorFloat[2], 1.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); this->checkForOpenGLError(NULL, "At middle of drawModels()"); for (int32_t i = 0; i < static_cast(viewportContents.size()); i++) { /* * Viewport of window. */ BrainOpenGLViewportContent* vpContent = viewportContents[i]; const int* windowVP = vpContent->getWindowViewport(); glViewport(windowVP[0], windowVP[1], windowVP[2], windowVP[3]); /* * Update foreground and background colors for model */ updateForegroundAndBackgroundColors(vpContent); /* * If this is NOT the first viewport content, * AND the background color for this viewport content is * different that the clear color, THEN * draw a rectangle with the background color. */ if (i > 0) { if ((m_backgroundColorByte[0] != clearColorByte[0]) || (m_backgroundColorByte[1] != clearColorByte[1]) || (m_backgroundColorByte[2] != clearColorByte[2])) { glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0.0, 1.0, 0.0, 1.0, -1.0, 1.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glColor3ubv(m_backgroundColorByte); glBegin(GL_POLYGON); glVertex2f(0.0, 0.0); glVertex2f(1.0, 0.0); glVertex2f(1.0, 1.0); glVertex2f(0.0, 1.0); glEnd(); } } drawBackgroundImage(vpContent); // CaretLogFinest("Drawing Model " // + AString::number(i) // + ": " // + AString::fromNumbers(vpContent->getModelViewport(), 4, ", ")); m_brain = vpContent->getBrain(); CaretAssert(m_brain); this->drawModelInternal(MODE_DRAWING, vpContent); /* * Draw border in foreground color around tab that is highlighted * in Tile Tabs when user selects a tab. */ if (vpContent->isTabHighlighted()) { glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); const float width = windowVP[2]; const float height = windowVP[3]; glOrtho(0.0, windowVP[2], 0.0, windowVP[3], -100.0, 100.0); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); glColor3fv(m_foregroundColorFloat); const float thickness = 10; /* * Left Side */ glBegin(GL_QUADS); glVertex3f(0.0, 0.0, 0.0); glVertex3f(thickness, 0.0, 0.0); glVertex3f(thickness, height, 0.0); glVertex3f(0.0, height, 0.0); glEnd(); /* * Right Side */ glBegin(GL_QUADS); glVertex3f(width - thickness, 0.0, 0.0); glVertex3f(width, 0.0, 0.0); glVertex3f(width, height, 0.0); glVertex3f(width - thickness, height, 0.0); glEnd(); /* * Bottom Side */ glBegin(GL_QUADS); glVertex3f(0.0, 0.0, 0.0); glVertex3f(width, 0.0, 0.0); glVertex3f(width, thickness, 0.0); glVertex3f(0.0, thickness, 0.0); glEnd(); /* * Top Side */ glBegin(GL_QUADS); glVertex3f(0.0, height - thickness, 0.0); glVertex3f(width, height - thickness, 0.0); glVertex3f(width, height, 0.0); glVertex3f(0.0, height, 0.0); glEnd(); glPopMatrix(); glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); } m_brain = NULL; } this->checkForOpenGLError(NULL, "At end of drawModels()"); } /** * Draw a model. * * @param mode * The mode of operations (draw, select, etc). * @param viewportContent * Viewport contents for drawing. */ void BrainOpenGLFixedPipeline::drawModelInternal(Mode mode, BrainOpenGLViewportContent* viewportContent) { ElapsedTimer et; et.start(); this->browserTabContent = viewportContent->getBrowserTabContent(); m_mirroredClippingEnabled = false; Model* model = NULL; if (this->browserTabContent != NULL) { m_clippingPlaneGroup = const_cast(this->browserTabContent->getClippingPlaneGroup()); CaretAssert(m_clippingPlaneGroup); Model* model = this->browserTabContent->getModelForDisplay(); this->windowTabIndex = this->browserTabContent->getTabNumber(); int viewport[4]; viewportContent->getModelViewport(viewport); this->mode = mode; this->checkForOpenGLError(model, "At beginning of drawModelInternal()"); if(model != NULL) { CaretAssert((this->windowTabIndex >= 0) && (this->windowTabIndex < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS)); bool modelAllowsPalettes = true; ModelChart* modelChart = dynamic_cast(model); ModelSurface* surfaceModel = dynamic_cast(model); ModelSurfaceMontage* surfaceMontageModel = dynamic_cast(model); ModelVolume* volumeModel = dynamic_cast(model); ModelWholeBrain* wholeBrainModel = dynamic_cast(model); if (modelChart != NULL) { drawChartData(browserTabContent, modelChart, viewport); modelAllowsPalettes = true; } else if (surfaceModel != NULL) { m_mirroredClippingEnabled = true; this->drawSurfaceModel(surfaceModel, viewport); } else if (surfaceMontageModel != NULL) { m_mirroredClippingEnabled = true; this->drawSurfaceMontageModel(browserTabContent, surfaceMontageModel, viewport); } else if (volumeModel != NULL) { this->drawVolumeModel(browserTabContent, volumeModel, viewport); } else if (wholeBrainModel != NULL) { this->drawWholeBrainModel(browserTabContent, wholeBrainModel, viewport); } else { modelAllowsPalettes = false; CaretAssertMessage(0, "Unknown type of model for drawing"); } if (modelAllowsPalettes) { int viewport[4]; viewportContent->getModelViewport(viewport); glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); this->drawAllPalettes(model->getBrain()); } } } glFlush(); this->checkForOpenGLError(model, "At end of drawModelInternal()"); if (model != NULL) { CaretLogFine("Time to draw " + model->getNameForGUI(false) + " was " + AString::number(et.getElapsedTimeSeconds()) + " seconds"); } } /** * Set the viewport. * * @param viewport * Values for viewport (x, y, x-size, y-size) * @param projectionType * Type of view projection. */ void BrainOpenGLFixedPipeline::setViewportAndOrthographicProjection(const int32_t viewport[4], const ProjectionViewTypeEnum::Enum projectionType) { glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); glMatrixMode(GL_PROJECTION); glLoadIdentity(); this->setOrthographicProjection(viewport, projectionType); glMatrixMode(GL_MODELVIEW); } /** * Set the viewport for a volume. * * @param viewport * Values for viewport (x, y, x-size, y-size) * @param projectionType * Type of view projection. * @param volume * Volume for use in setting orthographic projection. */ void BrainOpenGLFixedPipeline::setViewportAndOrthographicProjectionForWholeBrainVolume(const int32_t viewport[4], const ProjectionViewTypeEnum::Enum projectionType, const VolumeMappableInterface* volume) { CaretAssert(volume); glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); BoundingBox boundingBox; volume->getVoxelSpaceBoundingBox(boundingBox); glMatrixMode(GL_PROJECTION); glLoadIdentity(); setOrthographicProjectionForWithBoundingBox(viewport, projectionType, &boundingBox); glMatrixMode(GL_MODELVIEW); } /** * Set the viewport for a surface file. * * @param viewport * Values for viewport (x, y, x-size, y-size) * @param projectionType * Type of view projection. * @param surfaceFile * Surface file for use in setting orthographic projection. */ void BrainOpenGLFixedPipeline::setViewportAndOrthographicProjectionForSurfaceFile(const int32_t viewport[4], const ProjectionViewTypeEnum::Enum projectionType, const SurfaceFile* surfaceFile) { CaretAssert(surfaceFile); glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); glMatrixMode(GL_PROJECTION); glLoadIdentity(); setOrthographicProjectionForWithBoundingBox(viewport, projectionType, surfaceFile->getBoundingBox()); glMatrixMode(GL_MODELVIEW); } /** * Disable clipping planes. */ void BrainOpenGLFixedPipeline::disableClippingPlanes() { glDisable(GL_CLIP_PLANE0); glDisable(GL_CLIP_PLANE1); glDisable(GL_CLIP_PLANE2); glDisable(GL_CLIP_PLANE3); glDisable(GL_CLIP_PLANE4); glDisable(GL_CLIP_PLANE5); } /** * Apply the clipping planes for the given data type and structure. * * @param clippingDataType * Type of data that is being clipped. * @param structureIn * The structure. */ void BrainOpenGLFixedPipeline::applyClippingPlanes(const ClippingDataType clippingDataType, const StructureEnum::Enum structureIn) { disableClippingPlanes(); StructureEnum::Enum structure = StructureEnum::CORTEX_LEFT; if (m_mirroredClippingEnabled) { structure = structureIn; } if (browserTabContent == NULL) { return; } CaretAssert(m_clippingPlaneGroup); float rotation[3]; m_clippingPlaneGroup->getRotationAngles(rotation); float panning[3]; m_clippingPlaneGroup->getTranslationForStructure(structure, panning); float rotationAngles[3]; m_clippingPlaneGroup->getRotationAngles(rotationAngles); float thickness[3]; m_clippingPlaneGroup->getThickness(thickness); float minX = -thickness[0] / 2.0; float maxX = thickness[0] / 2.0; float minY = -thickness[1] / 2.0; float maxY = thickness[1] / 2.0; float minZ = -thickness[2] / 2.0; float maxZ = thickness[2] / 2.0; GLfloat rotMatrix[16]; m_clippingPlaneGroup->getRotationMatrixForStructure(structure).getMatrixForOpenGL(rotMatrix); if (m_clippingPlaneGroup->isDisplayClippingBoxSelected()) { glColor3f(1.0, 0.0, 0.0); glLineWidth(2.0); glPushMatrix(); glTranslatef(panning[0], panning[1], panning[2]); glMultMatrixf(rotMatrix); glBegin(GL_LINE_LOOP); glVertex3f(minX, minY, minZ); glVertex3f(maxX, minY, minZ); glVertex3f(maxX, maxY, minZ); glVertex3f(minX, maxY, minZ); glEnd(); glBegin(GL_LINE_LOOP); glVertex3f(minX, minY, maxZ); glVertex3f(maxX, minY, maxZ); glVertex3f(maxX, maxY, maxZ); glVertex3f(minX, maxY, maxZ); glEnd(); glBegin(GL_LINES); glVertex3f(minX, minY, minZ); glVertex3f(minX, minY, maxZ); glVertex3f(maxX, minY, minZ); glVertex3f(maxX, minY, maxZ); glVertex3f(maxX, maxY, minZ); glVertex3f(maxX, maxY, maxZ); glVertex3f(minX, maxY, minZ); glVertex3f(minX, maxY, maxZ); glEnd(); glPopMatrix(); } switch (clippingDataType) { case CLIPPING_DATA_TYPE_FEATURES: if (! m_clippingPlaneGroup->isFeaturesSelected()) { return; } break; case CLIPPING_DATA_TYPE_SURFACE: if (! m_clippingPlaneGroup->isSurfaceSelected()) { return; } break; case CLIPPING_DATA_TYPE_VOLUME: if (! m_clippingPlaneGroup->isVolumeSelected()) { return; } break; } std::vector planes = m_clippingPlaneGroup->getActiveClippingPlanesForStructure(structure); const int32_t numPlanes = static_cast(planes.size()); for (int32_t i = 0; i < numPlanes; i++) { const Plane* p = planes[i]; double a, b, c, d; p->getPlane(a, b, c, d); const GLdouble abcd[4] = { a, b, c, d }; glClipPlane(GL_CLIP_PLANE0 + i, abcd); glEnable(GL_CLIP_PLANE0 + i); } } /** * Is the coordinate inside the clipping planes? * * If a clipping plane for an axis is off, the coordinate is considered * to be inside the clipping plane. * * @param structureIn * The structure. Note that right and left hemispheres are mirror flipped. * @param xyz * The coordinate. * * @return * True if inside the clipping planes, else false. */ bool BrainOpenGLFixedPipeline::isCoordinateInsideClippingPlanesForStructure(const StructureEnum::Enum structureIn, const float xyz[3]) const { CaretAssert(m_clippingPlaneGroup); StructureEnum::Enum structure = StructureEnum::CORTEX_LEFT; if (m_mirroredClippingEnabled) { structure = structureIn; } return m_clippingPlaneGroup->isCoordinateInsideClippingPlanesForStructure(structure, xyz); } /** * @return Is clipping of features enabled? */ bool BrainOpenGLFixedPipeline::isFeatureClippingEnabled() const { CaretAssert(m_clippingPlaneGroup); return m_clippingPlaneGroup->isFeaturesAndAnyAxisSelected(); } /** * Apply the viewing transformations for the content of the browser tab. * * @param objectCenterXYZ * If not NULL, contains center of object about * which rotation should take place. * @param projectionViewType * Projection view type. */ void BrainOpenGLFixedPipeline::applyViewingTransformations(const float objectCenterXYZ[3], const ProjectionViewTypeEnum::Enum projectionViewType) { glMatrixMode(GL_MODELVIEW); glLoadIdentity(); float eyeX = 0.0; float eyeY = 0.0; float eyeZ = 0.0; float centerX = 0.0; float centerY = 0.0; float centerZ = 0.0; float upX = 0.0; float upY = 0.0; float upZ = 0.0; bool useGluLookAt = false; switch (projectionViewType) { case ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_ANTERIOR: case ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_DORSAL: case ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_POSTERIOR: case ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_VENTRAL: eyeZ = 5.0; upY = 1.0; useGluLookAt = true; break; case ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_FLAT_SURFACE: eyeZ = 5.0; upY = 1.0; useGluLookAt = true; break; case ProjectionViewTypeEnum::PROJECTION_VIEW_LEFT_LATERAL: break; case ProjectionViewTypeEnum::PROJECTION_VIEW_LEFT_MEDIAL: break; case ProjectionViewTypeEnum::PROJECTION_VIEW_LEFT_FLAT_SURFACE: break; case ProjectionViewTypeEnum::PROJECTION_VIEW_RIGHT_LATERAL: break; case ProjectionViewTypeEnum::PROJECTION_VIEW_RIGHT_MEDIAL: break; case ProjectionViewTypeEnum::PROJECTION_VIEW_RIGHT_FLAT_SURFACE: break; } if (useGluLookAt) { gluLookAt(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ); } float translation[3]; double rotationMatrixElements[16]; float scaling; browserTabContent->getTransformationsForOpenGLDrawing(projectionViewType, translation, rotationMatrixElements, scaling); glTranslatef(translation[0], translation[1], translation[2]); glMultMatrixd(rotationMatrixElements); /* * Save the inverse rotation matrix which may be used * later by some drawing functions. */ Matrix4x4 inverseMatrix; inverseMatrix.setMatrixFromOpenGL(rotationMatrixElements); this->inverseRotationMatrixValid = inverseMatrix.invert(); if (this->inverseRotationMatrixValid) { inverseMatrix.getMatrixForOpenGL(this->inverseRotationMatrix); } glScalef(scaling, scaling, scaling); if (objectCenterXYZ != NULL) { /* * Place center of surface at origin. */ glTranslatef(-objectCenterXYZ[0], -objectCenterXYZ[1], -objectCenterXYZ[2]); } } /** * For a volume, get translation and scaling so that the volume 'fills' * the window. * * @param volume * The volume. * @param sliceViewPlane * The slice viewing plane. * @param orthographicExtent * The orthographic bounds * @param translationOut * Output of translation. * @param scalingOut * Output of scaling. * */ void BrainOpenGLFixedPipeline::getVolumeFitToWindowScalingAndTranslation(const VolumeMappableInterface* volume, const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const double orthographicExtent[6], float translationOut[3], float& scalingOut) const { /* * Apply some scaling and translation so that the volume slice, by default * is not larger than the window in which it is being viewed. */ scalingOut = 1.0; translationOut[0] = 0.0; translationOut[1] = 0.0; translationOut[2] = 0.0; if (volume != NULL) { BoundingBox boundingBox; volume->getVoxelSpaceBoundingBox(boundingBox); int64_t dimI, dimJ, dimK, numMaps, numComponents; volume->getDimensions(dimI, dimJ, dimK, numMaps, numComponents); if ((dimI > 2) && (dimJ > 2) && (dimK > 2)) { float volumeCenter[3] = { (boundingBox.getMinX() + boundingBox.getMaxX()) / 2, (boundingBox.getMinY() + boundingBox.getMaxY()) / 2, (boundingBox.getMinZ() + boundingBox.getMaxZ()) / 2 }; /* * Translate so that the center voxel (by dimenisons) * is at the center of the screen. */ translationOut[0] = -volumeCenter[0]; translationOut[1] = -volumeCenter[1]; translationOut[2] = -volumeCenter[2]; switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: break; case VolumeSliceViewPlaneEnum::AXIAL: translationOut[2] = 0.0; break; case VolumeSliceViewPlaneEnum::CORONAL: translationOut[1] = 0.0; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: translationOut[0] = 0.0; break; } /* * Scale so volume fills, but does not extend out of window. */ const float xExtent = (boundingBox.getMaxX() - boundingBox.getMinX()) / 2; const float yExtent = (boundingBox.getMaxY() - boundingBox.getMinY()) / 2; const float zExtent = (boundingBox.getMaxZ() - boundingBox.getMinZ()) / 2; const float orthoExtentX = std::min(std::fabs(orthographicExtent[0]), std::fabs(orthographicExtent[1])); const float orthoExtentY = std::min(std::fabs(orthographicExtent[2]), std::fabs(orthographicExtent[3])); float temp; float scaleWindowX = (orthoExtentX / xExtent); temp = (orthoExtentX / yExtent);//parasaggital y is screen x if (temp < scaleWindowX) scaleWindowX = temp; float scaleWindowY = (orthoExtentY / zExtent); temp = (orthoExtentY / yExtent);//axial y is screen y if (temp < scaleWindowY) scaleWindowY = temp; scalingOut = std::min(scaleWindowX, scaleWindowY); scalingOut *= 0.98; } } } void BrainOpenGLFixedPipeline::initializeMembersBrainOpenGL() { this->initializedOpenGLFlag = false; this->modeProjectionData = NULL; } /** * Initialize OpenGL. */ void BrainOpenGLFixedPipeline::initializeOpenGL() { if (s_staticInitialized == false) { s_staticInitialized = true; BrainOpenGL::initializeOpenGL(); } glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); glClearDepth(1.0); glFrontFace(GL_CCW); /* * As normal vectors are multiplied by transformation matrices, their * lengths may no longer be one and cause drawing errors. * GL_NORMALIZE will rescale normal vectors to one to prevent this problem. * GL_RESCALE_NORMAL was added in later versions of OpenGL and * is reported to be more efficient. However, GL_RESCALE_NORMAL * does not seem to work with OpenGL 4.2 on Linux, whereas GL_NORMALIZE * seems to work on all operating systems and versions of OpenGL. */ glEnable(GL_NORMALIZE); // // Avoid drawing backfacing polygons // glCullFace(GL_BACK); glEnable(GL_CULL_FACE); glShadeModel(GL_SMOOTH); glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE); glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_FALSE); float lightColor[] = { 0.9f, 0.9f, 0.9f, 1.0f }; glLightfv(GL_LIGHT0, GL_DIFFUSE, lightColor); glLightfv(GL_LIGHT1, GL_DIFFUSE, lightColor); glEnable(GL_LIGHT0); glDisable(GL_LIGHT1); float materialColor[] = { 0.8f, 0.8f, 0.8f, 1.0f }; glMaterialfv(GL_FRONT, GL_DIFFUSE, materialColor); glColorMaterial(GL_FRONT, GL_DIFFUSE); float ambient[] = { 0.8f, 0.8f, 0.8f, 1.0f }; glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient); if (m_shapeSphere == NULL) { m_shapeSphere = new BrainOpenGLShapeSphere(5, 0.5); } if (m_shapeCone == NULL) { m_shapeCone = new BrainOpenGLShapeCone(8); } if (m_shapeCylinder == NULL) { m_shapeCylinder = new BrainOpenGLShapeCylinder(8); } if (m_shapeCube == NULL) { m_shapeCube = new BrainOpenGLShapeCube(1.0, BrainOpenGLShapeCube::NORMAL); } if (m_shapeCubeRounded == NULL) { m_shapeCubeRounded = new BrainOpenGLShapeCube(1.0, BrainOpenGLShapeCube::ROUNDED); } if (this->initializedOpenGLFlag) { return; } this->initializedOpenGLFlag = true; /* * Remaining items need to executed only once. */ } /** * Enable lighting based upon the current mode. */ void BrainOpenGLFixedPipeline::enableLighting() { float lightPosition[] = { 0.0f, 0.0f, 1000.0f, 0.0f }; switch (this->mode) { case MODE_DRAWING: glPushMatrix(); glLoadIdentity(); glLightfv(GL_LIGHT0, GL_POSITION, lightPosition); glEnable(GL_LIGHT0); // // Light 1 position is opposite of light 0 // lightPosition[0] = -lightPosition[0]; lightPosition[1] = -lightPosition[1]; lightPosition[2] = -lightPosition[2]; glLightfv(GL_LIGHT1, GL_POSITION, lightPosition); glEnable(GL_LIGHT1); glPopMatrix(); glEnable(GL_LIGHTING); glEnable(GL_COLOR_MATERIAL); break; case MODE_IDENTIFICATION: case MODE_PROJECTION: this->disableLighting(); break; } } /** * Disable lighting. */ void BrainOpenGLFixedPipeline::disableLighting() { glDisable(GL_LIGHTING); glDisable(GL_COLOR_MATERIAL); } /** * Enable line anti-aliasing (line smoothing) which also required blending. */ void BrainOpenGLFixedPipeline::enableLineAntiAliasing() { glEnable(GL_LINE_SMOOTH); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); } /** * Disable line anti-aliasing (line smoothing) which also required blending. */ void BrainOpenGLFixedPipeline::disableLineAntiAliasing() { glDisable(GL_LINE_SMOOTH); glDisable(GL_BLEND); } /** * Draw contents of a surface model. * @param surfaceModel * Model that is drawn. * @param viewport * Viewport for drawing region. */ void BrainOpenGLFixedPipeline::drawSurfaceModel(ModelSurface* surfaceModel, const int32_t viewport[4]) { Surface* surface = surfaceModel->getSurface(); float center[3]; surface->getBoundingBox()->getCenter(center); this->setViewportAndOrthographicProjectionForSurfaceFile(viewport, browserTabContent->getProjectionViewType(), surface); this->applyViewingTransformations(center, browserTabContent->getProjectionViewType()); const float* nodeColoringRGBA = this->surfaceNodeColoring->colorSurfaceNodes(surfaceModel, surface, this->windowTabIndex); this->drawSurface(surface, nodeColoringRGBA); // Disable on individual surface for Matt's NX crash // if (surface->getSurfaceType() == SurfaceTypeEnum::ANATOMICAL) { // this->drawSurfaceFiberOrientations(); // } } /** * Draw the surface axes. */ void BrainOpenGLFixedPipeline::drawSurfaceAxes() { const float bigNumber = 1000000.0; glPushMatrix(); glColor3f(1.0, 0.0, 0.0); glBegin(GL_LINES); glVertex3f(-bigNumber, 0.0, 0.0); glVertex3f( bigNumber, 0.0, 0.0); glVertex3f(0.0, -bigNumber, 0.0); glVertex3f(0.0, bigNumber, 0.0); glVertex3f(0.0, 0.0, -bigNumber); glVertex3f(0.0, 0.0, bigNumber); glEnd(); glPopMatrix(); } /** * Draw a surface. * * @param surface * Surface that is drawn. * @param nodeColoringRGBA * RGBA coloring for the nodes. */ void BrainOpenGLFixedPipeline::drawSurface(Surface* surface, const float* nodeColoringRGBA) { const DisplayPropertiesSurface* dps = m_brain->getDisplayPropertiesSurface(); glMatrixMode(GL_MODELVIEW); glEnable(GL_DEPTH_TEST); applyClippingPlanes(BrainOpenGLFixedPipeline::CLIPPING_DATA_TYPE_SURFACE, surface->getStructure()); this->enableLighting(); const SurfaceDrawingTypeEnum::Enum drawingType = dps->getSurfaceDrawingType(); switch (this->mode) { case MODE_DRAWING: switch (drawingType) { case SurfaceDrawingTypeEnum::DRAW_HIDE: break; case SurfaceDrawingTypeEnum::DRAW_AS_LINKS: /* * Draw first as triangles without coloring which uses * the background color. This prevents edges on back * from being seen. */ glPolygonOffset(1.0, 1.0); glEnable(GL_POLYGON_OFFSET_FILL); disableLighting(); this->drawSurfaceTrianglesWithVertexArrays(surface, NULL); glDisable(GL_POLYGON_OFFSET_FILL); /* * Now draw as polygon but outline only, do not fill. */ enableLighting(); setLineWidth(dps->getLinkSize()); glPolygonMode(GL_FRONT, GL_LINE); this->drawSurfaceTrianglesWithVertexArrays(surface, nodeColoringRGBA); glPolygonMode(GL_FRONT, GL_FILL); break; case SurfaceDrawingTypeEnum::DRAW_AS_NODES: this->drawSurfaceNodes(surface, nodeColoringRGBA); break; case SurfaceDrawingTypeEnum::DRAW_AS_TRIANGLES: /* * Enable alpha blending so that surface transparency * (using first overlay opacity) will function. */ GLboolean blendingEnabled = false; glGetBooleanv(GL_BLEND, &blendingEnabled); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // std::cout << "Surface " << surface->getFileNameNoPath() << std::endl; // std::cout << qPrintable(getStateOfOpenGL()) << std::endl; this->drawSurfaceTrianglesWithVertexArrays(surface, nodeColoringRGBA); if (blendingEnabled == false) { glDisable(GL_BLEND); } break; } this->disableClippingPlanes(); if (dps->isDisplayNormalVectors()) { drawSurfaceNormalVectors(surface); } this->drawSurfaceBorders(surface); this->drawSurfaceFoci(surface); this->drawSurfaceNodeAttributes(surface); this->drawSurfaceBorderBeingDrawn(surface); break; case MODE_IDENTIFICATION: /* * Disable shading since ID info is encoded in rgba coloring */ glShadeModel(GL_FLAT); if (drawingType != SurfaceDrawingTypeEnum::DRAW_HIDE) { this->drawSurfaceNodes(surface, nodeColoringRGBA); this->drawSurfaceTriangles(surface, nodeColoringRGBA); } this->disableClippingPlanes(); if (dps->isDisplayNormalVectors()) { drawSurfaceNormalVectors(surface); } this->drawSurfaceBorders(surface); this->drawSurfaceFoci(surface); this->drawSurfaceNodeAttributes(surface); /* * Re-enable shading since ID info is encoded in rgba coloring */ glShadeModel(GL_SMOOTH); break; case MODE_PROJECTION: /* * Disable shading since ID info is encoded in rgba coloring */ glShadeModel(GL_FLAT); if (drawingType != SurfaceDrawingTypeEnum::DRAW_HIDE) { this->drawSurfaceTriangles(surface, nodeColoringRGBA); } /* * Re-enable shading since ID info is encoded in rgba coloring */ glShadeModel(GL_SMOOTH); break; } this->disableLighting(); this->disableClippingPlanes(); } /** * Draw a surface as individual triangles. * @param surface * Surface that is drawn. * @param nodeColoringRGBA * RGBA coloring for the nodes. */ void BrainOpenGLFixedPipeline::drawSurfaceTriangles(Surface* surface, const float* nodeColoringRGBA) { const int numTriangles = surface->getNumberOfTriangles(); const int32_t* triangles = surface->getTriangle(0); const float* coordinates = surface->getCoordinate(0); const float* normals = surface->getNormalVector(0); SelectionItemSurfaceTriangle* triangleID = NULL; /* * Check for a 'selection' type mode */ bool isSelect = false; bool isProjection = false; switch (this->mode) { case MODE_DRAWING: break; case MODE_IDENTIFICATION: triangleID = m_brain->getSelectionManager()->getSurfaceTriangleIdentification(); if (triangleID->isEnabledForSelection()) { isSelect = true; } else { return; } break; case MODE_PROJECTION: isSelect = true; isProjection = true; break; } if (isSelect) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } uint8_t rgba[4]; glBegin(GL_TRIANGLES); for (int32_t i = 0; i < numTriangles; i++) { const int32_t i3 = i * 3; const int32_t n1 = triangles[i3]; const int32_t n2 = triangles[i3+1]; const int32_t n3 = triangles[i3+2]; if (isSelect) { this->colorIdentification->addItem(rgba, SelectionItemDataTypeEnum::SURFACE_TRIANGLE, i); glColor3ubv(rgba); glNormal3fv(&normals[n1*3]); glVertex3fv(&coordinates[n1*3]); glNormal3fv(&normals[n2*3]); glVertex3fv(&coordinates[n2*3]); glNormal3fv(&normals[n3*3]); glVertex3fv(&coordinates[n3*3]); } else { glColor4fv(&nodeColoringRGBA[n1*4]); glNormal3fv(&normals[n1*3]); glVertex3fv(&coordinates[n1*3]); glColor4fv(&nodeColoringRGBA[n2*4]); glNormal3fv(&normals[n2*3]); glVertex3fv(&coordinates[n2*3]); glColor4fv(&nodeColoringRGBA[n3*4]); glNormal3fv(&normals[n3*3]); glVertex3fv(&coordinates[n3*3]); } } glEnd(); if (isSelect) { int32_t triangleIndex = -1; float depth = -1.0; this->getIndexFromColorSelection(SelectionItemDataTypeEnum::SURFACE_TRIANGLE, this->mouseX, this->mouseY, triangleIndex, depth); if (triangleIndex >= 0) { bool isTriangleIdAccepted = false; if (triangleID != NULL) { if (triangleID->isOtherScreenDepthCloserToViewer(depth)) { triangleID->setBrain(surface->getBrainStructure()->getBrain()); triangleID->setSurface(surface); triangleID->setTriangleNumber(triangleIndex); const int32_t* triangleNodeIndices = surface->getTriangle(triangleIndex); triangleID->setNearestNode(triangleNodeIndices[0]); triangleID->setScreenDepth(depth); isTriangleIdAccepted = true; CaretLogFine("Selected Triangle: " + triangleID->toString()); } else { CaretLogFine("Rejecting Selected Triangle but still using: " + triangleID->toString()); } } /* * Node indices */ const int32_t n1 = triangles[triangleIndex*3]; const int32_t n2 = triangles[triangleIndex*3 + 1]; const int32_t n3 = triangles[triangleIndex*3 + 2]; /* * Node coordinates */ const float* c1 = &coordinates[n1*3]; const float* c2 = &coordinates[n2*3]; const float* c3 = &coordinates[n3*3]; const float average[3] = { c1[0] + c2[0] + c3[0], c1[1] + c2[1] + c3[1], c1[2] + c2[2] + c3[2] }; if (triangleID != NULL) { if (isTriangleIdAccepted) { this->setSelectedItemScreenXYZ(triangleID, average); } } GLdouble selectionModelviewMatrix[16]; glGetDoublev(GL_MODELVIEW_MATRIX, selectionModelviewMatrix); GLdouble selectionProjectionMatrix[16]; glGetDoublev(GL_PROJECTION_MATRIX, selectionProjectionMatrix); GLint selectionViewport[4]; glGetIntegerv(GL_VIEWPORT, selectionViewport); /* * Window positions of each coordinate */ double dc1[3] = { c1[0], c1[1], c1[2] }; double dc2[3] = { c2[0], c2[1], c2[2] }; double dc3[3] = { c3[0], c3[1], c3[2] }; double wc1[3], wc2[3], wc3[3]; if (gluProject(dc1[0], dc1[1], dc1[2], selectionModelviewMatrix, selectionProjectionMatrix, selectionViewport, &wc1[0], &wc1[1], &wc1[2]) && gluProject(dc2[0], dc2[1], dc2[2], selectionModelviewMatrix, selectionProjectionMatrix, selectionViewport, &wc2[0], &wc2[1], &wc2[2]) && gluProject(dc3[0], dc3[1], dc3[2], selectionModelviewMatrix, selectionProjectionMatrix, selectionViewport, &wc3[0], &wc3[1], &wc3[2])) { const double d1 = MathFunctions::distanceSquared2D(wc1[0], wc1[1], this->mouseX, this->mouseY); const double d2 = MathFunctions::distanceSquared2D(wc2[0], wc2[1], this->mouseX, this->mouseY); const double d3 = MathFunctions::distanceSquared2D(wc3[0], wc3[1], this->mouseX, this->mouseY); if (triangleID != NULL) { if (isTriangleIdAccepted) { triangleID->setNearestNode(n3); triangleID->setNearestNodeScreenXYZ(wc3); triangleID->setNearestNodeModelXYZ(dc3); if ((d1 < d2) && (d1 < d3)) { triangleID->setNearestNode(n1); triangleID->setNearestNodeScreenXYZ(wc1); triangleID->setNearestNodeModelXYZ(dc1); } else if ((d2 < d1) && (d2 < d3)) { triangleID->setNearestNode(n2); triangleID->setNearestNodeScreenXYZ(wc2); triangleID->setNearestNodeModelXYZ(dc2); } } } /* * Getting projected position? */ if (isProjection) { /* * Place window coordinates of triangle's nodes * onto the screen by setting Z-coordinate to zero */ wc1[2] = 0.0; wc2[2] = 0.0; wc3[2] = 0.0; /* * Area of triangle when projected to display */ const double triangleDisplayArea = MathFunctions::triangleArea(wc1, wc2, wc3); /* * If area of triangle on display is small, * use a coordinate from the triangle */ if (triangleDisplayArea < 0.001) { //this->modeProjectionData->setStereotaxicXYZ(c1); float barycentricAreas[3] = { 1.0, 0.0, 0.0 }; int barycentricNodes[3] = { n1, n1, n1 }; this->setProjectionModeData(depth, c1, surface->getStructure(), barycentricAreas, barycentricNodes, surface->getNumberOfNodes()); } else { /* * Determine position in triangle using barycentric coordinates */ double displayXYZ[3] = { this->mouseX, this->mouseY, 0.0 }; const double areaU = (MathFunctions::triangleArea(displayXYZ, wc2, wc3) / triangleDisplayArea); const double areaV = (MathFunctions::triangleArea(displayXYZ, wc3, wc1) / triangleDisplayArea); const double areaW = (MathFunctions::triangleArea(displayXYZ, wc1, wc2) / triangleDisplayArea); double totalArea = areaU + areaV + areaW; if (totalArea <= 0) { totalArea = 1.0; } if ((areaU < 0.0) || (areaV < 0.0) || (areaW < 0.0)) { CaretLogWarning("Invalid tile area: less than zero when projecting to surface."); } else { /* * Convert to surface coordinates */ const float projectedXYZ[3] = { (dc1[0]*areaU + dc2[0]*areaV + dc3[0]*areaW) / totalArea, (dc1[1]*areaU + dc2[1]*areaV + dc3[1]*areaW) / totalArea, (dc1[2]*areaU + dc2[2]*areaV + dc3[2]*areaW) / totalArea }; //this->modeProjectionData->setStereotaxicXYZ(projectedXYZ); const float barycentricAreas[3] = { areaU, areaV, areaW }; const int32_t barycentricNodes[3] = { n1, n2, n3 }; this->setProjectionModeData(depth, projectedXYZ, surface->getStructure(), barycentricAreas, barycentricNodes, surface->getNumberOfNodes()); } } } } CaretLogFine("Selected Triangle: " + QString::number(triangleIndex)); } } } /** * During projection mode, set the projected data. If the * projection data is already set, it will be overridden * if the new data is closer, in screen depth, to the user. * * @param screenDepth * Screen depth of data. * @param xyz * Stereotaxic coordinate of projected position. * @param structure * Structure to which data projects. * @param barycentricAreas * Barycentric areas of projection, if to surface with valid structure. * @param barycentricNodes * Barycentric nodes of projection, if to surface with valid structure * @param numberOfNodes * Number of nodes in surface, if to surface with valid structure. */ void BrainOpenGLFixedPipeline::setProjectionModeData(const float screenDepth, const float xyz[3], const StructureEnum::Enum structure, const float barycentricAreas[3], const int barycentricNodes[3], const int numberOfNodes) { CaretAssert(this->modeProjectionData); if (screenDepth < this->modeProjectionScreenDepth) { this->modeProjectionScreenDepth = screenDepth; this->modeProjectionData->setStructure(structure); this->modeProjectionData->setStereotaxicXYZ(xyz); SurfaceProjectionBarycentric* barycentric = this->modeProjectionData->getBarycentricProjection(); barycentric->setProjectionSurfaceNumberOfNodes(numberOfNodes); barycentric->setTriangleAreas(barycentricAreas); barycentric->setTriangleNodes(barycentricNodes); barycentric->setValid(true); CaretLogFiner("Projected to surface " + StructureEnum::toName(structure) + " with depth " + screenDepth); } } /** * Draw a surface as individual nodes. * @param surface * Surface that is drawn. * @param nodeColoringRGBA * RGBA coloring for the nodes. */ void BrainOpenGLFixedPipeline::drawSurfaceNodes(Surface* surface, const float* nodeColoringRGBA) { const DisplayPropertiesSurface* dps = m_brain->getDisplayPropertiesSurface(); const int numNodes = surface->getNumberOfNodes(); const float* coordinates = surface->getCoordinate(0); const float* normals = surface->getNormalVector(0); SelectionItemSurfaceNode* nodeID = m_brain->getSelectionManager()->getSurfaceNodeIdentification(); /* * Check for a 'selection' type mode */ bool isSelect = false; switch (this->mode) { case MODE_DRAWING: break; case MODE_IDENTIFICATION: if (nodeID->isEnabledForSelection()) { isSelect = true; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } else { return; } break; case MODE_PROJECTION: break; } uint8_t rgba[4]; float pointSize = dps->getNodeSize(); if (isSelect) { if (pointSize < 2.0) { pointSize = 2.0; } } setPointSize(pointSize); glBegin(GL_POINTS); for (int32_t i = 0; i < numNodes; i++) { const int32_t i3 = i * 3; if (isSelect) { this->colorIdentification->addItem(rgba, SelectionItemDataTypeEnum::SURFACE_NODE, i); glColor3ubv(rgba); glNormal3fv(&normals[i3]); glVertex3fv(&coordinates[i3]); } else { glColor4fv(&nodeColoringRGBA[i*4]); glNormal3fv(&normals[i3]); glVertex3fv(&coordinates[i3]); } } glEnd(); if (isSelect) { int nodeIndex = -1; float depth = -1.0; this->getIndexFromColorSelection(SelectionItemDataTypeEnum::SURFACE_NODE, this->mouseX, this->mouseY, nodeIndex, depth); if (nodeIndex >= 0) { if (nodeID->isOtherScreenDepthCloserToViewer(depth)) { nodeID->setBrain(surface->getBrainStructure()->getBrain()); nodeID->setSurface(surface); nodeID->setNodeNumber(nodeIndex); nodeID->setScreenDepth(depth); this->setSelectedItemScreenXYZ(nodeID, &coordinates[nodeIndex * 3]); CaretLogFine("Selected Vertex: " + nodeID->toString()); } else { CaretLogFine("Rejecting Selected Vertex: " + nodeID->toString()); } } } } /** * Draw a surface triangles with vertex arrays. * @param surface * Surface that is drawn. * @param nodeColoringRGBA * RGBA coloring for the nodes. */ void BrainOpenGLFixedPipeline::drawSurfaceTrianglesWithVertexArrays(const Surface* surface, const float* nodeColoringRGBA) { glEnableClientState(GL_VERTEX_ARRAY); if (nodeColoringRGBA != NULL) { glEnableClientState(GL_COLOR_ARRAY); } glEnableClientState(GL_NORMAL_ARRAY); glVertexPointer(3, GL_FLOAT, 0, reinterpret_cast(surface->getCoordinate(0))); if (nodeColoringRGBA != NULL) { glColorPointer(4, GL_FLOAT, 0, reinterpret_cast(nodeColoringRGBA)); } else { glColor3fv(m_backgroundColorFloat); } glNormalPointer(GL_FLOAT, 0, reinterpret_cast(surface->getNormalVector(0))); const int numTriangles = surface->getNumberOfTriangles(); glDrawElements(GL_TRIANGLES, (3 * numTriangles), GL_UNSIGNED_INT, reinterpret_cast(surface->getTriangle(0))); glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_COLOR_ARRAY); glDisableClientState(GL_NORMAL_ARRAY); } /** * Draw a surface's normal vectors. * @param surface * Surface on which normal vectors are drawn. */ void BrainOpenGLFixedPipeline::drawSurfaceNormalVectors(const Surface* surface) { disableLighting(); const StructureEnum::Enum structure = surface->getStructure(); const float length = 10.0; CaretPointer topoHelper = surface->getTopologyHelper(); setLineWidth(1.0); glColor3f(1.0, 0.0, 0.0); const int32_t numNodes = surface->getNumberOfNodes(); glBegin(GL_LINES); for (int32_t i = 0; i < numNodes; i++) { if (topoHelper->getNodeHasNeighbors(i)) { const float* xyz = surface->getCoordinate(i); if (m_clippingPlaneGroup->isSurfaceSelected()) { if ( ! isCoordinateInsideClippingPlanesForStructure(structure, xyz)) { continue; } } const float* normal = surface->getNormalVector(i); float vector[3] = { xyz[0] + length * normal[0], xyz[1] + length * normal[1], xyz[2] + length * normal[2] }; glVertex3fv(xyz); glVertex3fv(vector); } } glEnd(); enableLighting(); } /** * Draw attributes for the given surface. * @param surface * Surface for which attributes are drawn. */ void BrainOpenGLFixedPipeline::drawSurfaceNodeAttributes(Surface* surface) { BrainStructure* brainStructure = surface->getBrainStructure(); CaretAssert(brainStructure); Brain* brain = brainStructure->getBrain(); CaretAssert(brain); const StructureEnum::Enum structure = surface->getStructure(); const int numNodes = surface->getNumberOfNodes(); const float* coordinates = surface->getCoordinate(0); IdentificationManager* idManager = brain->getIdentificationManager(); SelectionItemSurfaceNodeIdentificationSymbol* symbolID = m_brain->getSelectionManager()->getSurfaceNodeIdentificationSymbol(); const std::vector identifiedNodes = idManager->getNodeIdentifiedItemsForSurface(structure, numNodes); std::vector identifiedNodeIndices; for (std::vector::const_iterator iter = identifiedNodes.begin(); iter != identifiedNodes.end(); iter++) { const IdentifiedItemNode& nodeID = *iter; identifiedNodeIndices.push_back(nodeID.getNodeIndex()); } EventNodeIdentificationColorsGetFromCharts colorsFromChartsEvent(structure, this->windowTabIndex, identifiedNodeIndices); /* * Check for a 'selection' type mode */ bool isSelect = false; switch (this->mode) { case MODE_DRAWING: EventManager::get()->sendEvent(colorsFromChartsEvent.getPointer()); break; case MODE_IDENTIFICATION: if (symbolID->isEnabledForSelection()) { isSelect = true; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } else { return; } break; case MODE_PROJECTION: return; break; } uint8_t idRGBA[4]; for (std::vector::const_iterator iter = identifiedNodes.begin(); iter != identifiedNodes.end(); iter++) { const IdentifiedItemNode& nodeID = *iter; /* * Show symbol for node ID? */ if ( ! nodeID.isShowIdentificationSymbol()) { continue; } const int32_t nodeIndex = nodeID.getNodeIndex(); const int32_t i3 = nodeIndex * 3; const float* xyz = &coordinates[i3]; if (m_clippingPlaneGroup->isSurfaceSelected()) { if ( ! isCoordinateInsideClippingPlanesForStructure(structure, xyz)) { continue; } } const float symbolDiameter = nodeID.getSymbolSize(); if (isSelect) { this->colorIdentification->addItem(idRGBA, SelectionItemDataTypeEnum::SURFACE_NODE_IDENTIFICATION_SYMBOL, nodeIndex); } else { if (structure == nodeID.getStructure()) { nodeID.getSymbolRGBA(idRGBA); colorsFromChartsEvent.applyChartColorToNode(nodeIndex, idRGBA); } else { nodeID.getContralateralSymbolRGB(idRGBA); } } idRGBA[3] = 255; glPushMatrix(); glTranslatef(xyz[0], xyz[1], xyz[2]); this->drawSphereWithDiameter(idRGBA, symbolDiameter); glPopMatrix(); } if (isSelect) { int nodeIndex = -1; float depth = -1.0; this->getIndexFromColorSelection(SelectionItemDataTypeEnum::SURFACE_NODE_IDENTIFICATION_SYMBOL, this->mouseX, this->mouseY, nodeIndex, depth); if (nodeIndex >= 0) { if (symbolID->isOtherScreenDepthCloserToViewer(depth)) { symbolID->setBrain(surface->getBrainStructure()->getBrain()); symbolID->setSurface(surface); symbolID->setNodeNumber(nodeIndex); symbolID->setScreenDepth(depth); this->setSelectedItemScreenXYZ(symbolID, &coordinates[nodeIndex * 3]); CaretLogFine("Selected Vertex Identification Symbol: " + QString::number(nodeIndex)); } } } } /** * Draw a border on a surface. The color must be set prior * to calling this method. * * @param borderDrawInfo * Info about border being drawn. * @param border * Border that is drawn on the surface. * @param borderFileIndex * Index of border file. * @param borderIndex * Index of border. * @param isSelect * Selection mode is active. */ void BrainOpenGLFixedPipeline::drawBorder(const BorderDrawInfo& borderDrawInfo) { CaretAssert(borderDrawInfo.surface); CaretAssert(borderDrawInfo.border); const StructureEnum::Enum surfaceStructure = borderDrawInfo.surface->getStructure(); const StructureEnum::Enum contralateralSurfaceStructure = StructureEnum::getContralateralStructure(surfaceStructure); const int32_t numBorderPoints = borderDrawInfo.border->getNumberOfPoints(); const bool isHighlightEndPoints = borderDrawInfo.isHighlightEndPoints; float pointDiameter = 2.0; float lineWidth = 2.0; BorderDrawingTypeEnum::Enum drawType = BorderDrawingTypeEnum::DRAW_AS_POINTS_SPHERES; if (borderDrawInfo.borderFileIndex >= 0) { const BrainStructure* bs = borderDrawInfo.surface->getBrainStructure(); const Brain* brain = bs->getBrain(); const DisplayPropertiesBorders* dpb = brain->getDisplayPropertiesBorders(); const DisplayGroupEnum::Enum displayGroup = dpb->getDisplayGroupForTab(this->windowTabIndex); pointDiameter = dpb->getPointSize(displayGroup, this->windowTabIndex); lineWidth = dpb->getLineWidth(displayGroup, this->windowTabIndex); drawType = dpb->getDrawingType(displayGroup, this->windowTabIndex); } bool drawSphericalPoints = false; bool drawSquarePoints = false; bool drawLines = false; switch (drawType) { case BorderDrawingTypeEnum::DRAW_AS_LINES: drawLines = true; break; case BorderDrawingTypeEnum::DRAW_AS_POINTS_SPHERES: drawSphericalPoints = true; break; case BorderDrawingTypeEnum::DRAW_AS_POINTS_SQUARES: drawSquarePoints = true; break; case BorderDrawingTypeEnum::DRAW_AS_POINTS_AND_LINES: drawLines = true; drawSphericalPoints = true; break; } const bool flatSurfaceFlag = (borderDrawInfo.surface->getSurfaceType() == SurfaceTypeEnum::FLAT); bool flatSurfaceDrawUnstretchedLinesFlag = false; float unstretchedLinesLength = -1.0; if (flatSurfaceFlag) { if ((borderDrawInfo.anatomicalSurface != NULL) && (borderDrawInfo.unstretchedLinesLength > 0.0)) { flatSurfaceDrawUnstretchedLinesFlag = true; unstretchedLinesLength = borderDrawInfo.unstretchedLinesLength; } } const float drawAtDistanceAboveSurface = 0.0; std::vector pointXYZ; std::vector pointAnatomicalXYZ; std::vector pointIndex; const CaretPointer th = borderDrawInfo.surface->getTopologyHelper(); const std::vector& nodesBoundaryEdgeCount = th->getNumberOfBoundaryEdgesForAllNodes(); CaretAssert(static_cast(nodesBoundaryEdgeCount.size()) == borderDrawInfo.surface->getNumberOfNodes()); /* * Find points valid for this surface */ for (int32_t i = 0; i < numBorderPoints; i++) { const SurfaceProjectedItem* p = borderDrawInfo.border->getPoint(i); /* * If surface structure does not match the point's structure, * check to see if contralateral display is enabled and * compare contralateral surface structure to point's structure. */ const StructureEnum::Enum pointStructure = p->getStructure(); bool structureMatches = true; if (surfaceStructure != pointStructure) { structureMatches = false; if (borderDrawInfo.isContralateralEnabled) { if (contralateralSurfaceStructure == pointStructure) { structureMatches = true; } } } if (structureMatches == false) { continue; } float xyz[3]; bool isXyzValid = p->getProjectedPositionAboveSurface(*borderDrawInfo.surface, xyz, drawAtDistanceAboveSurface); if (isXyzValid) { /* * On a flat surface, do not draw border points that are attached to all edge nodes * as they will likely result in points outside of the flat surface * (near cuts and medial wall) */ if (flatSurfaceDrawUnstretchedLinesFlag){ if (p->getBarycentricProjection()->isValid()) { const int32_t* baryNodes = p->getBarycentricProjection()->getTriangleNodes(); if (baryNodes != NULL) { int32_t edgeNodeCount = 0; if (nodesBoundaryEdgeCount[baryNodes[0]] > 0) edgeNodeCount++; if (nodesBoundaryEdgeCount[baryNodes[1]] > 0) edgeNodeCount++; if (nodesBoundaryEdgeCount[baryNodes[2]] > 0) edgeNodeCount++; if (edgeNodeCount >= 3) { isXyzValid = false; } } } } } if (isXyzValid) { if (flatSurfaceDrawUnstretchedLinesFlag) { float anatXYZ[3]; const bool isAnatXyzValid = p->getProjectedPositionAboveSurface(*borderDrawInfo.anatomicalSurface, anatXYZ, drawAtDistanceAboveSurface); if (isAnatXyzValid) { pointXYZ.push_back(xyz[0]); pointXYZ.push_back(xyz[1]); pointXYZ.push_back(xyz[2]); pointAnatomicalXYZ.push_back(anatXYZ[0]); pointAnatomicalXYZ.push_back(anatXYZ[1]); pointAnatomicalXYZ.push_back(anatXYZ[2]); pointIndex.push_back(i); } } else { pointXYZ.push_back(xyz[0]); pointXYZ.push_back(xyz[1]); pointXYZ.push_back(xyz[2]); pointIndex.push_back(i); } } } const bool doClipping = isFeatureClippingEnabled(); const int32_t numPointsToDraw = static_cast(pointXYZ.size() / 3); if (flatSurfaceDrawUnstretchedLinesFlag) { CaretAssert(pointXYZ.size() == pointAnatomicalXYZ.size()); } /* * Draw points */ if (drawSphericalPoints || drawSquarePoints) { for (int32_t i = 0; i < numPointsToDraw; i++) { const int32_t i3 = i * 3; const float* xyz = &pointXYZ[i3]; if (doClipping) { if ( ! isCoordinateInsideClippingPlanesForStructure(surfaceStructure, xyz)) { continue; } } glPushMatrix(); glTranslatef(xyz[0], xyz[1], xyz[2]); if (borderDrawInfo.isSelect) { uint8_t idRGBA[4]; this->colorIdentification->addItem(idRGBA, SelectionItemDataTypeEnum::BORDER_SURFACE, borderDrawInfo.borderFileIndex, borderDrawInfo.borderIndex, pointIndex[i]); idRGBA[3] = 255; if (drawSphericalPoints) { this->drawSphereWithDiameter(idRGBA, pointDiameter); } else { this->drawSquare(idRGBA, pointDiameter); } } else { float rgba[4] = { borderDrawInfo.rgba[0], borderDrawInfo.rgba[1], borderDrawInfo.rgba[2], borderDrawInfo.rgba[3], }; if (isHighlightEndPoints) { if (i == 0) { rgba[0] = 0.0; rgba[1] = 1.0; rgba[2] = 0.0; rgba[3] = 1.0; } else if (i == (numPointsToDraw - 1)) { rgba[0] = 0.0; rgba[1] = 0.75; rgba[2] = 0.0; rgba[3] = 1.0; } } if (drawSphericalPoints) { this->drawSphereWithDiameter(rgba, pointDiameter); } else { this->drawSquare(rgba, pointDiameter); } } glPopMatrix(); } } /* * Draw lines */ if (drawLines && (numPointsToDraw > 1)) { this->setLineWidth(lineWidth); this->disableLighting(); if (borderDrawInfo.isSelect) { /* * Start at one, since need two points for each line */ for (int32_t i = 1; i < numPointsToDraw; i++) { /* * On a flat surface, do not draw a line segment if it is * from non-consecutive border points. This occurs when * a border point does not project to the flat surface * due to a cut or removal of the medial wall. If helps * prevent long border lines stretching from one edge of the * surface to a far away edge. */ if (flatSurfaceDrawUnstretchedLinesFlag) { if (pointIndex[i] != (pointIndex[i-1] + 1)) { continue; } } const int32_t i3 = i * 3; uint8_t idRGBA[4]; this->colorIdentification->addItem(idRGBA, SelectionItemDataTypeEnum::BORDER_SURFACE, borderDrawInfo.borderFileIndex, borderDrawInfo.borderIndex, pointIndex[i]); glColor3ubv(idRGBA); CaretAssertVectorIndex(pointXYZ, i3 + 2); const float* xyz1 = &pointXYZ[i3 - 3]; const float* xyz2 = &pointXYZ[i3]; bool drawIt = true; if (doClipping) { if (isCoordinateInsideClippingPlanesForStructure(surfaceStructure, xyz1) && (isCoordinateInsideClippingPlanesForStructure(surfaceStructure, xyz2)) ) { /* nothing */ } else { drawIt = false; } } if (drawIt) { if (flatSurfaceDrawUnstretchedLinesFlag) { CaretAssertVectorIndex(pointAnatomicalXYZ, i3 + 2); if (unstretchedBorderLineTest(xyz1, xyz2, &pointAnatomicalXYZ[i3], &pointAnatomicalXYZ[i3-3], unstretchedLinesLength)) { drawIt = false; } } } if (drawIt) { glBegin(GL_LINES); glVertex3fv(xyz1); glVertex3fv(xyz2); glEnd(); } } } else { glColor3fv(borderDrawInfo.rgba); /* * Start at one, since need two points for each line */ glBegin(GL_LINES); for (int32_t i = 1; i < numPointsToDraw; i++) { /* * On a flat surface, do not draw a line segment if it is * from non-consecutive border points. This occurs when * a border point does not project to the flat surface * due to a cut or removal of the medial wall. If helps * prevent long border lines stretching from one edge of the * surface to a far away edge. */ if (flatSurfaceDrawUnstretchedLinesFlag) { if (pointIndex[i] != (pointIndex[i-1] + 1)) { continue; } } const int32_t i3 = i * 3; CaretAssertVectorIndex(pointXYZ, i3 + 2); const float* xyz1 = &pointXYZ[i3 - 3]; const float* xyz2 = &pointXYZ[i3]; bool drawIt = true; if (doClipping) { if (isCoordinateInsideClippingPlanesForStructure(surfaceStructure, xyz1) && (isCoordinateInsideClippingPlanesForStructure(surfaceStructure, xyz2)) ) { /* nothing */ } else { drawIt = false; } } if (drawIt) { if (flatSurfaceDrawUnstretchedLinesFlag) { CaretAssertVectorIndex(pointAnatomicalXYZ, i3 + 2); if (unstretchedBorderLineTest(xyz1, xyz2, &pointAnatomicalXYZ[i3], &pointAnatomicalXYZ[i3-3], unstretchedLinesLength)) { drawIt = false; } } } if (drawIt) { glVertex3fv(xyz1); glVertex3fv(xyz2); } } glEnd(); } this->enableLighting(); } } /** * Determine if the ratio if border length over anatomical border length * is greater than the unstretched lines factor. * * @param p1 * Position of border point in surface. * @param p1 * Position of next border point in surface. * @param anat1 * Position of border point in anatomical surface. * @param anat2 * Position of next border point in anatomical surface. * @param unstretchedLinesFactor * The unstretched lines factor. * @return * True if the border is too long and should NOT be drawn, else false. */ bool BrainOpenGLFixedPipeline::unstretchedBorderLineTest(const float p1[3], const float p2[3], const float anat1[3], const float anat2[3], const float unstretchedLinesFactor) const { const float dist = MathFunctions::distance3D(p1, p2); const float anatDist = MathFunctions::distance3D(anat1, anat2); if (anatDist > 0.0) { const float ratio = dist / anatDist; if (ratio > unstretchedLinesFactor) { return true; } } return false; } /** * Set the OpenGL line width. Value is clamped * to minimum and maximum values to prevent * OpenGL error caused by invalid line width. */ void BrainOpenGLFixedPipeline::setLineWidth(const float lineWidth) { if (lineWidth > s_maxLineWidth) { glLineWidth(s_maxLineWidth); } else if (lineWidth < s_minLineWidth) { glLineWidth(s_minLineWidth); } else { glLineWidth(lineWidth); } } /** * Set the OpenGL point size. Value is clamped * to minimum and maximum values to prevent * OpenGL error caused by invalid point size. */ void BrainOpenGLFixedPipeline::setPointSize(const float pointSize) { if (pointSize > s_maxPointSize) { glPointSize(s_maxPointSize); } else if (pointSize < s_minPointSize) { glPointSize(s_minPointSize); } else { glPointSize(pointSize); } } /** * Draw foci on a surface. * @param surface * Surface on which foci are drawn. */ void BrainOpenGLFixedPipeline::drawSurfaceFoci(Surface* surface) { SelectionItemFocusSurface* idFocus = m_brain->getSelectionManager()->getSurfaceFocusIdentification(); /* * Check for a 'selection' type mode */ bool isSelect = false; switch (this->mode) { case MODE_DRAWING: break; case MODE_IDENTIFICATION: if (idFocus->isEnabledForSelection()) { isSelect = true; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } else { return; } break; case MODE_PROJECTION: return; break; } Brain* brain = surface->getBrainStructure()->getBrain(); const DisplayPropertiesFoci* fociDisplayProperties = brain->getDisplayPropertiesFoci(); const DisplayGroupEnum::Enum displayGroup = fociDisplayProperties->getDisplayGroupForTab(this->windowTabIndex); if (fociDisplayProperties->isDisplayed(displayGroup, this->windowTabIndex) == false) { return; } const float focusDiameter = fociDisplayProperties->getFociSize(displayGroup, this->windowTabIndex); const FeatureColoringTypeEnum::Enum fociColoringType = fociDisplayProperties->getColoringType(displayGroup, this->windowTabIndex); const StructureEnum::Enum surfaceStructure = surface->getStructure(); const StructureEnum::Enum surfaceContralateralStructure = StructureEnum::getContralateralStructure(surfaceStructure); const bool doClipping = isFeatureClippingEnabled(); bool drawAsSpheres = false; switch (fociDisplayProperties->getDrawingType(displayGroup, this->windowTabIndex)) { case FociDrawingTypeEnum::DRAW_AS_SPHERES: drawAsSpheres = true; break; case FociDrawingTypeEnum::DRAW_AS_SQUARES: break; } const bool isPasteOntoSurface = fociDisplayProperties->isPasteOntoSurface(displayGroup, this->windowTabIndex); const bool isContralateralEnabled = fociDisplayProperties->isContralateralDisplayed(displayGroup, this->windowTabIndex); const int32_t numFociFiles = brain->getNumberOfFociFiles(); for (int32_t i = 0; i < numFociFiles; i++) { FociFile* fociFile = brain->getFociFile(i); const GroupAndNameHierarchyModel* classAndNameSelection = fociFile->getGroupAndNameHierarchyModel(); if (classAndNameSelection->isSelected(displayGroup, this->windowTabIndex) == false) { continue; } const GiftiLabelTable* classColorTable = fociFile->getClassColorTable(); const GiftiLabelTable* nameColorTable = fociFile->getNameColorTable(); const int32_t numFoci = fociFile->getNumberOfFoci(); for (int32_t j = 0; j < numFoci; j++) { Focus* focus = fociFile->getFocus(j); float rgba[4] = { 0.0, 0.0, 0.0, 1.0 }; const GroupAndNameHierarchyItem* nameItem = focus->getGroupNameSelectionItem(); if (nameItem != NULL) { if (nameItem->isSelected(displayGroup, this->windowTabIndex) == false) { continue; } } switch (fociColoringType) { case FeatureColoringTypeEnum::FEATURE_COLORING_TYPE_CLASS: if (focus->isClassRgbaValid() == false) { const GiftiLabel* colorLabel = classColorTable->getLabelBestMatching(focus->getClassName()); if (colorLabel != NULL) { colorLabel->getColor(rgba); focus->setClassRgba(rgba); } else { focus->setClassRgba(rgba); } } focus->getClassRgba(rgba); break; case FeatureColoringTypeEnum::FEATURE_COLORING_TYPE_NAME: if (focus->isNameRgbaValid() == false) { const GiftiLabel* colorLabel = nameColorTable->getLabelBestMatching(focus->getName()); if (colorLabel != NULL) { colorLabel->getColor(rgba); focus->setNameRgba(rgba); } else { focus->setNameRgba(rgba); } } focus->getNameRgba(rgba); break; } glColor3fv(rgba); const int32_t numProjections = focus->getNumberOfProjections(); for (int32_t k = 0; k < numProjections; k++) { const SurfaceProjectedItem* spi = focus->getProjection(k); float xyz[3]; if (spi->getProjectedPosition(*surface, xyz, isPasteOntoSurface)) { const StructureEnum::Enum focusStructure = spi->getStructure(); bool drawIt = false; if (focusStructure == surfaceStructure) { drawIt = true; } else if (focusStructure == StructureEnum::INVALID) { drawIt = true; } else if (isContralateralEnabled) { if (focusStructure == surfaceContralateralStructure) { drawIt = true; } } if (doClipping) { if ( ! isCoordinateInsideClippingPlanesForStructure(surfaceStructure, xyz)) { drawIt = false; } } if (drawIt) { glPushMatrix(); glTranslatef(xyz[0], xyz[1], xyz[2]); if (isSelect) { uint8_t idRGBA[4]; this->colorIdentification->addItem(idRGBA, SelectionItemDataTypeEnum::FOCUS_SURFACE, i, // file index j, // focus index k);// projection index idRGBA[3] = 255; if (drawAsSpheres) { this->drawSphereWithDiameter(idRGBA, focusDiameter); } else { this->drawSquare(idRGBA, focusDiameter); } } else { if (drawAsSpheres) { this->drawSphereWithDiameter(rgba, focusDiameter); } else { this->drawSquare(rgba, focusDiameter); } } glPopMatrix(); } } } } } if (isSelect) { int32_t fociFileIndex = -1; int32_t focusIndex = -1; int32_t focusProjectionIndex = -1; float depth = -1.0; this->getIndexFromColorSelection(SelectionItemDataTypeEnum::FOCUS_SURFACE, this->mouseX, this->mouseY, fociFileIndex, focusIndex, focusProjectionIndex, depth); if (fociFileIndex >= 0) { if (idFocus->isOtherScreenDepthCloserToViewer(depth)) { Focus* focus = brain->getFociFile(fociFileIndex)->getFocus(focusIndex); idFocus->setBrain(brain); idFocus->setFocus(focus); idFocus->setFociFile(brain->getFociFile(fociFileIndex)); idFocus->setFocusIndex(focusIndex); idFocus->setFocusProjectionIndex(focusProjectionIndex); idFocus->setSurface(surface); idFocus->setScreenDepth(depth); float xyz[3]; const SurfaceProjectedItem* spi = focus->getProjection(focusProjectionIndex); spi->getProjectedPosition(*surface, xyz, false); this->setSelectedItemScreenXYZ(idFocus, xyz); CaretLogFine("Selected Focus Identification Symbol: " + QString::number(focusIndex)); } } } } /** * Draw borders on a surface. * @param surface * Surface on which borders are drawn. */ void BrainOpenGLFixedPipeline::drawSurfaceBorders(Surface* surface) { SelectionItemBorderSurface* idBorder = m_brain->getSelectionManager()->getSurfaceBorderIdentification(); /* * Check for a 'selection' type mode */ bool isSelect = false; switch (this->mode) { case MODE_DRAWING: break; case MODE_IDENTIFICATION: if (idBorder->isEnabledForSelection()) { isSelect = true; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } else { return; } break; case MODE_PROJECTION: return; break; } Brain* brain = surface->getBrainStructure()->getBrain(); const DisplayPropertiesBorders* borderDisplayProperties = brain->getDisplayPropertiesBorders(); const DisplayGroupEnum::Enum displayGroup = borderDisplayProperties->getDisplayGroupForTab(this->windowTabIndex); if (borderDisplayProperties->isDisplayed(displayGroup, this->windowTabIndex) == false) { return; } float unstretchedLinesLength = -1.0; if (borderDisplayProperties->isUnstretchedLinesEnabled(displayGroup, this->windowTabIndex)) { unstretchedLinesLength = borderDisplayProperties->getUnstretchedLinesLength(displayGroup, this->windowTabIndex); } const FeatureColoringTypeEnum::Enum borderColoringType = borderDisplayProperties->getColoringType(displayGroup, this->windowTabIndex); const bool isContralateralEnabled = borderDisplayProperties->isContralateralDisplayed(displayGroup, this->windowTabIndex); const int32_t numBorderFiles = brain->getNumberOfBorderFiles(); for (int32_t i = 0; i < numBorderFiles; i++) { BorderFile* borderFile = brain->getBorderFile(i); const GroupAndNameHierarchyModel* classAndNameSelection = borderFile->getGroupAndNameHierarchyModel(); if (classAndNameSelection->isSelected(displayGroup, this->windowTabIndex) == false) { continue; } const GiftiLabelTable* classColorTable = borderFile->getClassColorTable(); const GiftiLabelTable* nameColorTable = borderFile->getNameColorTable(); const int32_t numBorders = borderFile->getNumberOfBorders(); for (int32_t j = 0; j < numBorders; j++) { Border* border = borderFile->getBorder(j); if (borderFile->isBorderDisplayed(displayGroup, this->windowTabIndex, border) == false) { continue; } float rgba[4] = { 0.0, 0.0, 0.0, 1.0 }; switch (borderColoringType) { case FeatureColoringTypeEnum::FEATURE_COLORING_TYPE_CLASS: if (border->isClassRgbaValid() == false) { const GiftiLabel* colorLabel = classColorTable->getLabelBestMatching(border->getClassName()); if (colorLabel != NULL) { colorLabel->getColor(rgba); border->setClassRgba(rgba); } else { border->setClassRgba(rgba); } } border->getClassRgba(rgba); break; case FeatureColoringTypeEnum::FEATURE_COLORING_TYPE_NAME: if (border->isNameRgbaValid() == false) { const GiftiLabel* colorLabel = nameColorTable->getLabelBestMatching(border->getName()); if (colorLabel != NULL) { colorLabel->getColor(rgba); border->setNameRgba(rgba); } else { border->setNameRgba(rgba); } } border->getNameRgba(rgba); break; } glColor3fv(rgba); BorderDrawInfo borderDrawInfo; borderDrawInfo.surface = surface; borderDrawInfo.border = border; borderDrawInfo.rgba[0] = rgba[0]; borderDrawInfo.rgba[1] = rgba[1]; borderDrawInfo.rgba[2] = rgba[2]; borderDrawInfo.rgba[3] = rgba[3]; borderDrawInfo.borderFileIndex = i; borderDrawInfo.borderIndex = j; borderDrawInfo.isSelect = isSelect; borderDrawInfo.isContralateralEnabled = isContralateralEnabled; borderDrawInfo.isHighlightEndPoints = m_drawHighlightedEndPoints; borderDrawInfo.anatomicalSurface = NULL; borderDrawInfo.unstretchedLinesLength = unstretchedLinesLength; BrainStructure* bs = brain->getBrainStructure(border->getStructure(), false); if (bs != NULL) { borderDrawInfo.anatomicalSurface = bs->getPrimaryAnatomicalSurface(); } this->drawBorder(borderDrawInfo); } } if (isSelect) { int32_t borderFileIndex = -1; int32_t borderIndex = -1; int32_t borderPointIndex; float depth = -1.0; this->getIndexFromColorSelection(SelectionItemDataTypeEnum::BORDER_SURFACE, this->mouseX, this->mouseY, borderFileIndex, borderIndex, borderPointIndex, depth); if (borderFileIndex >= 0) { if (idBorder->isOtherScreenDepthCloserToViewer(depth)) { Border* border = brain->getBorderFile(borderFileIndex)->getBorder(borderIndex); idBorder->setBrain(brain); idBorder->setBorder(border); idBorder->setBorderFile(brain->getBorderFile(borderFileIndex)); idBorder->setBorderIndex(borderIndex); idBorder->setBorderPointIndex(borderPointIndex); idBorder->setSurface(surface); idBorder->setScreenDepth(depth); float xyz[3]; border->getPoint(borderPointIndex)->getProjectedPosition(*surface, xyz, false); this->setSelectedItemScreenXYZ(idBorder, xyz); CaretLogFine("Selected Border Identification Symbol: " + QString::number(borderIndex)); } } } } /** * Draw the border that is begin drawn. * @param surface * Surface on which border is being drawn. */ void BrainOpenGLFixedPipeline::drawSurfaceBorderBeingDrawn(const Surface* surface) { glColor3f(1.0, 0.0, 0.0); if (this->borderBeingDrawn != NULL) { BorderDrawInfo borderDrawInfo; borderDrawInfo.surface = const_cast(surface); borderDrawInfo.border = this->borderBeingDrawn; borderDrawInfo.rgba[0] = 1.0; borderDrawInfo.rgba[1] = 0.0; borderDrawInfo.rgba[2] = 0.0; borderDrawInfo.rgba[3] = 1.0; borderDrawInfo.borderFileIndex = -1; borderDrawInfo.borderIndex = -1; borderDrawInfo.isSelect = false; borderDrawInfo.isContralateralEnabled = false; borderDrawInfo.isHighlightEndPoints = false; borderDrawInfo.anatomicalSurface = NULL; borderDrawInfo.unstretchedLinesLength = -1.0; this->drawBorder(borderDrawInfo); } } /** * Setup volume drawing information for an overlay set. * * @param browserTabContent * Content in the browser tab. * @param paletteFile * File from which palette is obtained. * @param volumeDrawInfoOut * Output containing information for volume drawing. */ void BrainOpenGLFixedPipeline::setupVolumeDrawInfo(BrowserTabContent* browserTabContent, Brain* brain, std::vector& volumeDrawInfoOut) { volumeDrawInfoOut.clear(); PaletteFile* paletteFile = brain->getPaletteFile(); OverlaySet* overlaySet = browserTabContent->getOverlaySet(); const int32_t numberOfOverlays = overlaySet->getNumberOfDisplayedOverlays(); for (int32_t iOver = (numberOfOverlays - 1); iOver >= 0; iOver--) { Overlay* overlay = overlaySet->getOverlay(iOver); if (overlay->isEnabled()) { CaretMappableDataFile* mapFile; int32_t mapIndex; overlay->getSelectionData(mapFile, mapIndex); if (mapFile != NULL) { if (mapFile->isVolumeMappable()) { VolumeMappableInterface* vf = dynamic_cast(mapFile); if (vf != NULL) { float opacity = overlay->getOpacity(); if (volumeDrawInfoOut.empty()) { opacity = 1.0; } WholeBrainVoxelDrawingMode::Enum wholeBrainVoxelDrawingMode = overlay->getWholeBrainVoxelDrawingMode(); if (mapFile->isMappedWithPalette()) { FastStatistics* statistics = NULL; switch (mapFile->getPaletteNormalizationMode()) { case PaletteNormalizationModeEnum::NORMALIZATION_ALL_MAP_DATA: statistics = const_cast(mapFile->getFileFastStatistics()); break; case PaletteNormalizationModeEnum::NORMALIZATION_SELECTED_MAP_DATA: statistics = const_cast(mapFile->getMapFastStatistics(mapIndex)); break; } //CaretAssert(statistics); PaletteColorMapping* paletteColorMapping = mapFile->getMapPaletteColorMapping(mapIndex); Palette* palette = paletteFile->getPaletteByName(paletteColorMapping->getSelectedPaletteName()); if (palette != NULL) { /* * Statistics may be NULL for a dense connectome file * that does not have any data loaded by user * clicking on surface/volume. */ if (statistics != NULL) { bool useIt = true; if (volumeDrawInfoOut.empty() == false) { /* * If previous volume is the same as this * volume, there is no need to draw it twice. */ const VolumeDrawInfo& vdi = volumeDrawInfoOut[volumeDrawInfoOut.size() - 1]; if ((vdi.volumeFile == vf) && (opacity >= 1.0) && (mapIndex == vdi.mapIndex) && (*paletteColorMapping == *vdi.paletteColorMapping)) { useIt = false; } } if (useIt) { VolumeDrawInfo vdi(mapFile, vf, brain, paletteColorMapping, statistics, wholeBrainVoxelDrawingMode, mapIndex, opacity); volumeDrawInfoOut.push_back(vdi); } } } else { CaretLogWarning("No valid palette for drawing volume file: " + mapFile->getFileNameNoPath()); } } else { VolumeDrawInfo vdi(mapFile, vf, brain, NULL, NULL, wholeBrainVoxelDrawingMode, mapIndex, opacity); volumeDrawInfoOut.push_back(vdi); } } } } } } } /** * Draw the volume slices. * @param browserTabContent * Content of the window. * @param volumeModel * Model for slices. * @param viewport * Region of drawing. */ void BrainOpenGLFixedPipeline::drawVolumeModel(BrowserTabContent* browserTabContent, ModelVolume* volumeModel, const int32_t viewport[4]) { /* * Determine volumes that are to be drawn */ const int32_t tabNumber = browserTabContent->getTabNumber(); volumeModel->updateModel(tabNumber); Brain* brain = volumeModel->getBrain(); std::vector volumeDrawInfo; this->setupVolumeDrawInfo(browserTabContent, brain, volumeDrawInfo); VolumeSliceDrawingTypeEnum::Enum sliceDrawingType = browserTabContent->getSliceDrawingType(); VolumeSliceProjectionTypeEnum::Enum sliceProjectionType = browserTabContent->getSliceProjectionType(); /* * There is/was a flaw in volume drawing in that it does not "center" * correctly when the voxel corresponding to the coordinate (0, 0, 0) * is not within the volume. It seems to be fixed for orthogonal * drawing but oblique drawing probably needs a new algorithm to * fix the problem. */ bool useNewDrawingFlag = false; switch (sliceProjectionType) { case VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_OBLIQUE: break; case VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_ORTHOGONAL: useNewDrawingFlag = true; break; } if (useNewDrawingFlag) { BrainOpenGLVolumeSliceDrawing volumeSliceDrawing; volumeSliceDrawing.draw(this, browserTabContent, volumeDrawInfo, sliceDrawingType, sliceProjectionType, viewport); } else { BrainOpenGLVolumeObliqueSliceDrawing obliqueVolumeSliceDrawing; obliqueVolumeSliceDrawing.draw(this, browserTabContent, volumeDrawInfo, sliceDrawingType, sliceProjectionType, viewport); } } /** * Draw volumes a voxel cubes for whole brain view. * * @param volumeDrawInfoIn * Describes volumes that are drawn. */ void BrainOpenGLFixedPipeline::drawVolumeVoxelsAsCubesWholeBrain(std::vector& volumeDrawInfoIn) { /* * Filter volumes for drawing and only draw those volumes that * are to be drawn as 3D Voxel Cubes. */ std::vector volumeDrawInfo; for (std::vector::iterator iter = volumeDrawInfoIn.begin(); iter != volumeDrawInfoIn.end(); iter++) { bool useIt = false; VolumeDrawInfo& vdi = *iter; switch (vdi.wholeBrainVoxelDrawingMode) { case WholeBrainVoxelDrawingMode::DRAW_VOXELS_AS_THREE_D_CUBES: case WholeBrainVoxelDrawingMode::DRAW_VOXELS_AS_ROUNDED_THREE_D_CUBES: useIt = true; break; case WholeBrainVoxelDrawingMode::DRAW_VOXELS_ON_TWO_D_SLICES: break; } if (useIt) { volumeDrawInfo.push_back(vdi); } } const int32_t numberOfVolumesToDraw = static_cast(volumeDrawInfo.size()); if (numberOfVolumesToDraw <= 0) { return; } SelectionItemVoxel* voxelID = m_brain->getSelectionManager()->getVoxelIdentification(); /* * Check for a 'selection' type mode */ bool isSelect = false; switch (this->mode) { case MODE_DRAWING: break; case MODE_IDENTIFICATION: if (voxelID->isEnabledForSelection()) { isSelect = true; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } else { return; } break; case MODE_PROJECTION: return; break; } /* * When selecting turn on lighting and shading since * colors are used for identification. */ if (isSelect) { this->disableLighting(); glShadeModel(GL_FLAT); } else { this->enableLighting(); glEnable(GL_CULL_FACE); glShadeModel(GL_SMOOTH); } glEnable(GL_CULL_FACE); const bool doClipping = isFeatureClippingEnabled(); const DisplayPropertiesLabels* dsl = m_brain->getDisplayPropertiesLabels(); const DisplayGroupEnum::Enum displayGroup = dsl->getDisplayGroupForTab(this->windowTabIndex); /* * For identification, five items per voxel * 1) volume index * 2) map index * 3) index I * 4) index J * 5) index K */ const int32_t idPerVoxelCount = 5; std::vector identificationIndices; if (isSelect) { identificationIndices.reserve(10000 * idPerVoxelCount); } PaletteFile* paletteFile = m_brain->getPaletteFile(); for (int32_t iVol = 0; iVol < numberOfVolumesToDraw; iVol++) { VolumeDrawInfo& volInfo = volumeDrawInfo[iVol]; if (volInfo.opacity < 1.0) { glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } else { glDisable(GL_BLEND); } const VolumeMappableInterface* volumeFile = volInfo.volumeFile; int64_t dimI, dimJ, dimK, numMaps, numComponents; volumeFile->getDimensions(dimI, dimJ, dimK, numMaps, numComponents); float originX, originY, originZ; float x1, y1, z1; float lastX, lastY, lastZ; volumeFile->indexToSpace(0, 0, 0, originX, originY, originZ); volumeFile->indexToSpace(1, 1, 1, x1, y1, z1); volumeFile->indexToSpace(dimI - 1, dimJ - 1, dimK - 1, lastX, lastY, lastZ); const float dx = x1 - originX; const float dy = y1 - originY; const float dz = z1 - originZ; /* * Cube size for voxel drawing. Some volumes may have a right to left * orientation in which case dx may be negative. */ const float cubeSizeDX = std::fabs(dx); const float cubeSizeDY = std::fabs(dy); const float cubeSizeDZ = std::fabs(dz); std::vector labelMapData; const CiftiBrainordinateLabelFile* ciftiLabelFile = dynamic_cast(volumeFile); if (ciftiLabelFile != NULL) { ciftiLabelFile->getMapData(volInfo.mapIndex, labelMapData); } uint8_t rgba[4]; for (int64_t iVoxel = 0; iVoxel < dimI; iVoxel++) { for (int64_t jVoxel = 0; jVoxel < dimJ; jVoxel++) { for (int64_t kVoxel = 0; kVoxel < dimK; kVoxel++) { if (ciftiLabelFile != NULL) { ciftiLabelFile->getVoxelColorInMapForLabelData(paletteFile, labelMapData, iVoxel, jVoxel, kVoxel, volInfo.mapIndex, displayGroup, this->windowTabIndex, rgba); } else { volumeFile->getVoxelColorInMap(paletteFile, iVoxel, jVoxel, kVoxel, volInfo.mapIndex, displayGroup, this->windowTabIndex, rgba); } if (rgba[3] > 0) { if (volInfo.opacity < 1.0) { rgba[3] *= volInfo.opacity; } if (rgba[3] > 0) { if (isSelect) { const int32_t idIndex = identificationIndices.size() / idPerVoxelCount; this->colorIdentification->addItem(rgba, SelectionItemDataTypeEnum::VOXEL, idIndex); identificationIndices.push_back(iVol); identificationIndices.push_back(volInfo.mapIndex); identificationIndices.push_back(iVoxel); identificationIndices.push_back(jVoxel); identificationIndices.push_back(kVoxel); } const float x = iVoxel * dx + originX; const float y = jVoxel * dy + originY; const float z = kVoxel * dz + originZ; bool drawIt = true; if (doClipping) { const float xyz[3] = { x, y, z }; if ( ! isCoordinateInsideClippingPlanesForStructure(StructureEnum::ALL, xyz)) { drawIt = false; } } if (drawIt) { glPushMatrix(); glTranslatef(x, y, z); switch (volInfo.wholeBrainVoxelDrawingMode) { case WholeBrainVoxelDrawingMode::DRAW_VOXELS_AS_THREE_D_CUBES: drawCuboid(rgba, cubeSizeDX, cubeSizeDY, cubeSizeDZ); break; case WholeBrainVoxelDrawingMode::DRAW_VOXELS_AS_ROUNDED_THREE_D_CUBES: drawRoundedCuboid(rgba, cubeSizeDX, cubeSizeDY, cubeSizeDZ); break; case WholeBrainVoxelDrawingMode::DRAW_VOXELS_ON_TWO_D_SLICES: break; } glPopMatrix(); } } } } } } } if (isSelect) { int32_t identifiedItemIndex; float depth = -1.0; this->getIndexFromColorSelection(SelectionItemDataTypeEnum::VOXEL, this->mouseX, this->mouseY, identifiedItemIndex, depth); if (identifiedItemIndex >= 0) { const int32_t idIndex = identifiedItemIndex * idPerVoxelCount; const int32_t volDrawInfoIndex = identificationIndices[idIndex]; CaretAssertVectorIndex(volumeDrawInfo, volDrawInfoIndex); VolumeMappableInterface* vf = volumeDrawInfo[volDrawInfoIndex].volumeFile; //const int32_t mapIndex = identificationIndices[idIndex + 1]; const int64_t voxelIndices[3] = { identificationIndices[idIndex + 2], identificationIndices[idIndex + 3], identificationIndices[idIndex + 4] }; if (voxelID->isOtherScreenDepthCloserToViewer(depth)) { voxelID->setVoxelIdentification(m_brain, vf, voxelIndices, depth); float voxelCoordinates[3]; vf->indexToSpace(voxelIndices[0], voxelIndices[1], voxelIndices[2], voxelCoordinates[0], voxelCoordinates[1], voxelCoordinates[2]); this->setSelectedItemScreenXYZ(voxelID, voxelCoordinates); CaretLogFine("Selected Voxel (3D): " + AString::fromNumbers(voxelIndices, 3, ",")); } } } glShadeModel(GL_SMOOTH); glDisable(GL_BLEND); } void BrainOpenGLFixedPipeline::setFiberOrientationDisplayInfo(const DisplayPropertiesFiberOrientation* dpfo, const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, Plane* plane, const StructureEnum::Enum structure, FiberTrajectoryColorModel::Item* colorSource, FiberOrientationDisplayInfo& dispInfo) { dispInfo.aboveLimit = dpfo->getAboveLimit(displayGroup, tabIndex); dispInfo.belowLimit = dpfo->getBelowLimit(displayGroup, tabIndex); dispInfo.colorSource = colorSource; dispInfo.fiberOrientationColorType = dpfo->getColoringType(displayGroup, tabIndex); dispInfo.fanMultiplier = dpfo->getFanMultiplier(displayGroup, tabIndex); dispInfo.isDrawWithMagnitude = dpfo->isDrawWithMagnitude(displayGroup, tabIndex); dispInfo.minimumMagnitude = dpfo->getMinimumMagnitude(displayGroup, tabIndex); dispInfo.magnitudeMultiplier = dpfo->getLengthMultiplier(displayGroup, tabIndex); dispInfo.plane = plane; dispInfo.structure = structure; dispInfo.symbolType = dpfo->getSymbolType(displayGroup, tabIndex); } /** * Draw fibers for a surface or a volume. * * @param plane * If not NULL, it is the plane of the volume slice being drawn and * only fibers within the above and below limits from the plane will * be drawn. * @param structure * The structure. */ void BrainOpenGLFixedPipeline::drawFiberOrientations(const Plane* plane, const StructureEnum::Enum structure) { const DisplayPropertiesFiberOrientation* dpfo = m_brain->getDisplayPropertiesFiberOrientation(); const DisplayGroupEnum::Enum displayGroup = dpfo->getDisplayGroupForTab(this->windowTabIndex); if (dpfo->isDisplayed(displayGroup, this->windowTabIndex) == false) { return; } const FiberOrientationSymbolTypeEnum::Enum symbolType = dpfo->getSymbolType(displayGroup, this->windowTabIndex); /* * Save status of clipping and disable clipping. * For fibers, the entire fiber symbol is displayed if its * origin is within the clipping planes which is tested below. */ GLboolean clipPlanesEnabled[6] = { glIsEnabled(GL_CLIP_PLANE0), glIsEnabled(GL_CLIP_PLANE1), glIsEnabled(GL_CLIP_PLANE2), glIsEnabled(GL_CLIP_PLANE3), glIsEnabled(GL_CLIP_PLANE4), glIsEnabled(GL_CLIP_PLANE5) }; glDisable(GL_CLIP_PLANE0); glDisable(GL_CLIP_PLANE1); glDisable(GL_CLIP_PLANE2); glDisable(GL_CLIP_PLANE3); glDisable(GL_CLIP_PLANE4); glDisable(GL_CLIP_PLANE5); /* * Fans use lighting but NOT on a volume slice */ disableLighting(); switch (symbolType) { case FiberOrientationSymbolTypeEnum::FIBER_SYMBOL_FANS: if (plane == NULL) { enableLighting(); } break; case FiberOrientationSymbolTypeEnum::FIBER_SYMBOL_LINES: break; } /* * Default constructor is color by fiber orientation settings XYZ/123 as RGB */ FiberTrajectoryColorModel::Item colorUseFiber; FiberOrientationDisplayInfo fiberOrientDispInfo; setFiberOrientationDisplayInfo(dpfo, displayGroup, this->windowTabIndex, const_cast(plane), structure, &colorUseFiber, fiberOrientDispInfo); /* * Draw the vectors from each of the connectivity files */ const int32_t numFiberOrienationFiles = m_brain->getNumberOfConnectivityFiberOrientationFiles(); for (int32_t iFile = 0; iFile < numFiberOrienationFiles; iFile++) { CiftiFiberOrientationFile* cfof = m_brain->getConnectivityFiberOrientationFile(iFile); if (cfof->isDisplayed(displayGroup, this->windowTabIndex)) { /* * Draw each of the fiber orientations which may contain multiple fibers */ const int64_t numberOfFiberOrientations = cfof->getNumberOfFiberOrientations(); for (int64_t i = 0; i < numberOfFiberOrientations; i++) { const FiberOrientation* fiberOrientation = cfof->getFiberOrientations(i); if (fiberOrientation->m_valid == false) { continue; } for (int32_t ifi = 0; ifi < fiberOrientation->m_numberOfFibers; ifi++) { fiberOrientation->m_fibers[ifi]->m_opacityForDrawing = 1.0; } addFiberOrientationForDrawing(&fiberOrientDispInfo, fiberOrientation); } } } drawAllFiberOrientations(&fiberOrientDispInfo, false); /* * Restore status of clipping planes enabled */ if (clipPlanesEnabled[0]) glEnable(GL_CLIP_PLANE0); if (clipPlanesEnabled[1]) glEnable(GL_CLIP_PLANE1); if (clipPlanesEnabled[2]) glEnable(GL_CLIP_PLANE2); if (clipPlanesEnabled[3]) glEnable(GL_CLIP_PLANE3); if (clipPlanesEnabled[4]) glEnable(GL_CLIP_PLANE4); if (clipPlanesEnabled[5]) glEnable(GL_CLIP_PLANE5); } /** * Add fiber orientation for drawing. Note that for alpha blending to * work correctly, the fibers must be sorted by depth and drawn from * furthest to nearest. Some tests will be performed to determine if * the fiber should be drawn prior to adding the fiber to the list * of fibers that will be drawn. * * @param fodi * Parameters controlling the drawing of fiber orientations. * @param fiberOrientation * The fiber orientation that will be drawn. */ void BrainOpenGLFixedPipeline::addFiberOrientationForDrawing(const FiberOrientationDisplayInfo* fodi, const FiberOrientation* fiberOrientation) { /* * Test location of fiber orientation for drawing */ if (fodi->plane != NULL) { const float distToPlane = fodi->plane->signedDistanceToPlane(fiberOrientation->m_xyz); if (distToPlane > fodi->aboveLimit) { return; } if (distToPlane < fodi->belowLimit) { return; } } if (isFeatureClippingEnabled()) { if ( ! isCoordinateInsideClippingPlanesForStructure(fodi->structure, fiberOrientation->m_xyz)) { return; } } m_fiberOrientationsForDrawing.push_back(const_cast(fiberOrientation)); } /* * For comparison when sorting that results in furthest fibers drawn first. */ static bool fiberDepthCompare(FiberOrientation* &f1, FiberOrientation* &f2) { return (f1->m_drawingDepth > f2->m_drawingDepth); } /** * Sort the fiber orientations by depth. */ void BrainOpenGLFixedPipeline::sortFiberOrientationsByDepth() { ElapsedTimer timer; timer.start(); /* * Create transforms model coordinate to a screen coordinate. */ GLdouble modelMatrixOpenGL[16]; glGetDoublev(GL_MODELVIEW_MATRIX, modelMatrixOpenGL); GLdouble projectionMatrixOpenGL[16]; glGetDoublev(GL_PROJECTION_MATRIX, projectionMatrixOpenGL); Matrix4x4 modelMatrix; modelMatrix.setMatrixFromOpenGL(modelMatrixOpenGL); Matrix4x4 projectionMatrix; projectionMatrix.setMatrixFromOpenGL(projectionMatrixOpenGL); Matrix4x4 modelToScreenMatrix; modelToScreenMatrix.setMatrix(projectionMatrix); modelToScreenMatrix.premultiply(modelMatrix); const float m0 = modelToScreenMatrix.getMatrixElement(2, 0); const float m1 = modelToScreenMatrix.getMatrixElement(2, 1); const float m2 = modelToScreenMatrix.getMatrixElement(2, 2); const float m3 = modelToScreenMatrix.getMatrixElement(2, 3); for (std::list::const_iterator iter = m_fiberOrientationsForDrawing.begin(); iter != m_fiberOrientationsForDrawing.end(); iter++) { const FiberOrientation* fiberOrientation = *iter; const float rawDepth =(m0 * fiberOrientation->m_xyz[0] + m1 * fiberOrientation->m_xyz[1] + m2 * fiberOrientation->m_xyz[2] + m3); const float screenDepth = ((rawDepth + 1.0) / 2.0); fiberOrientation->m_drawingDepth = screenDepth; } m_fiberOrientationsForDrawing.sort(fiberDepthCompare); } /** * Draw all of the fiber orienations. * * @param fodi * Parameters controlling the drawing of fiber orientations. */ void BrainOpenGLFixedPipeline::drawAllFiberOrientations(const FiberOrientationDisplayInfo* fodi, const bool isSortFibers) { if (isSortFibers) { sortFiberOrientationsByDepth(); } for (std::list::const_iterator iter = m_fiberOrientationsForDrawing.begin(); iter != m_fiberOrientationsForDrawing.end(); iter++) { const FiberOrientation* fiberOrientation = *iter; /* * Draw each of the fibers */ const int64_t numberOfFibers = fiberOrientation->m_numberOfFibers; for (int64_t j = 0; j < numberOfFibers; j++) { const Fiber* fiber = fiberOrientation->m_fibers[j]; /* * Apply display properties */ bool drawIt = true; if (fiber->m_meanF < fodi->minimumMagnitude) { drawIt = false; } if (drawIt) { float alpha = 1.0; if (j < 3) { alpha = fiber->m_opacityForDrawing; CaretAssertMessage(((alpha >= 0.0) && (alpha <= 1.0)), ("Value=" + AString::number(alpha))); if (alpha <= 0.0) { continue; } } /* * Length of vector */ float vectorLength = fodi->magnitudeMultiplier; if (fodi->isDrawWithMagnitude) { vectorLength *= fiber->m_meanF; } /* * Vector with magnitude */ const float magnitudeVector[3] = { fiber->m_directionUnitVector[0] * vectorLength, fiber->m_directionUnitVector[1] * vectorLength, fiber->m_directionUnitVector[2] * vectorLength }; const float halfMagnitudeVector[3] = { magnitudeVector[0] * 0.5, magnitudeVector[1] * 0.5, magnitudeVector[2] * 0.5, }; /* * Start of vector */ float startXYZ[3] = { fiberOrientation->m_xyz[0], fiberOrientation->m_xyz[1], fiberOrientation->m_xyz[2] }; /* * When drawing lines, start of vector is offset by * have of the vector length since the vector is * bi-directional. */ switch (fodi->symbolType) { case FiberOrientationSymbolTypeEnum::FIBER_SYMBOL_FANS: break; case FiberOrientationSymbolTypeEnum::FIBER_SYMBOL_LINES: startXYZ[0] -= halfMagnitudeVector[0]; startXYZ[1] -= halfMagnitudeVector[1]; startXYZ[2] -= halfMagnitudeVector[2]; break; } /* * End of vector */ float endXYZ[3] = { 0.0, 0.0, 0.0 }; /* * When drawing lines, end point is the start * plus the vector with magnitude. * * When drawing fans, there are two endpoints * with the fans starting in the middle. */ switch (fodi->symbolType) { case FiberOrientationSymbolTypeEnum::FIBER_SYMBOL_FANS: endXYZ[0] = startXYZ[0] + halfMagnitudeVector[0]; endXYZ[1] = startXYZ[1] + halfMagnitudeVector[1]; endXYZ[2] = startXYZ[2] + halfMagnitudeVector[2]; break; case FiberOrientationSymbolTypeEnum::FIBER_SYMBOL_LINES: endXYZ[0] = startXYZ[0] + magnitudeVector[0]; endXYZ[1] = startXYZ[1] + magnitudeVector[1]; endXYZ[2] = startXYZ[2] + magnitudeVector[2]; break; } float fiberRGBA[4] = { 0.0, 0.0, 0.0, 0.0 }; /* * Color of fiber */ switch (fodi->colorSource->getItemType()) { case FiberTrajectoryColorModel::Item::ITEM_TYPE_FIBER_ORIENTATION_COLORING_TYPE: switch (fodi->fiberOrientationColorType) { case FiberOrientationColoringTypeEnum::FIBER_COLORING_FIBER_INDEX_AS_RGB: { const int32_t indx = j % 3; switch (indx) { case 0: // use RED glColor4f(BrainOpenGLFixedPipeline::COLOR_RED[0], BrainOpenGLFixedPipeline::COLOR_RED[1], BrainOpenGLFixedPipeline::COLOR_RED[2], alpha); fiberRGBA[0] = BrainOpenGLFixedPipeline::COLOR_RED[0]; fiberRGBA[1] = BrainOpenGLFixedPipeline::COLOR_RED[1]; fiberRGBA[2] = BrainOpenGLFixedPipeline::COLOR_RED[2]; fiberRGBA[3] = alpha; break; case 1: // use BLUE glColor4f(BrainOpenGLFixedPipeline::COLOR_BLUE[0], BrainOpenGLFixedPipeline::COLOR_BLUE[1], BrainOpenGLFixedPipeline::COLOR_BLUE[2], alpha); fiberRGBA[0] = BrainOpenGLFixedPipeline::COLOR_BLUE[0]; fiberRGBA[1] = BrainOpenGLFixedPipeline::COLOR_BLUE[1]; fiberRGBA[2] = BrainOpenGLFixedPipeline::COLOR_BLUE[2]; fiberRGBA[3] = alpha; break; case 2: // use GREEN glColor4f(BrainOpenGLFixedPipeline::COLOR_GREEN[0], BrainOpenGLFixedPipeline::COLOR_GREEN[1], BrainOpenGLFixedPipeline::COLOR_GREEN[2], alpha); fiberRGBA[0] = BrainOpenGLFixedPipeline::COLOR_GREEN[0]; fiberRGBA[1] = BrainOpenGLFixedPipeline::COLOR_GREEN[1]; fiberRGBA[2] = BrainOpenGLFixedPipeline::COLOR_GREEN[2]; fiberRGBA[3] = alpha; break; } } break; case FiberOrientationColoringTypeEnum::FIBER_COLORING_XYZ_AS_RGB: CaretAssert((fiber->m_directionUnitVectorRGB[0] >= 0.0) && (fiber->m_directionUnitVectorRGB[0] <= 1.0)); CaretAssert((fiber->m_directionUnitVectorRGB[1] >= 0.0) && (fiber->m_directionUnitVectorRGB[1] <= 1.0)); CaretAssert((fiber->m_directionUnitVectorRGB[2] >= 0.0) && (fiber->m_directionUnitVectorRGB[2] <= 1.0)); CaretAssert((alpha >= 0.0) && (alpha <= 1.0)); glColor4f(fiber->m_directionUnitVectorRGB[0], fiber->m_directionUnitVectorRGB[1], fiber->m_directionUnitVectorRGB[2], alpha); fiberRGBA[0] = fiber->m_directionUnitVectorRGB[0]; fiberRGBA[1] = fiber->m_directionUnitVectorRGB[1]; fiberRGBA[2] = fiber->m_directionUnitVectorRGB[2]; fiberRGBA[3] = alpha; break; } break; case FiberTrajectoryColorModel::Item::ITEM_TYPE_CARET_COLOR: { const CaretColorEnum::Enum caretColor = fodi->colorSource->getCaretColor(); const float* rgb = CaretColorEnum::toRGB(caretColor); glColor4f(rgb[0], rgb[1], rgb[2], alpha); fiberRGBA[0] = rgb[0]; fiberRGBA[1] = rgb[1]; fiberRGBA[2] = rgb[2]; fiberRGBA[3] = alpha; } break; } /* * Draw the fiber */ switch (fodi->symbolType) { case FiberOrientationSymbolTypeEnum::FIBER_SYMBOL_FANS: { /* * Draw the cones */ const float radiansToDegrees = 180.0 / M_PI; const float majorAxis = std::min((vectorLength * std::tan(fiber->m_fanningMajorAxisAngle) * fodi->fanMultiplier), vectorLength); const float minorAxis = std::min((vectorLength * std::tan(fiber->m_fanningMinorAxisAngle) * fodi->fanMultiplier), vectorLength); /* * First cone */ glPushMatrix(); glTranslatef(startXYZ[0], startXYZ[1], startXYZ[2]); glRotatef(-fiber->m_phi * radiansToDegrees, 0.0, 0.0, 1.0); glRotatef(-fiber->m_theta * radiansToDegrees, 0.0, 1.0, 0.0); glRotatef(-fiber->m_psi * radiansToDegrees, 0.0, 0.0, 1.0); glScalef(majorAxis * 2.0, minorAxis * 2.0, vectorLength); m_shapeCone->draw(fiberRGBA); glPopMatrix(); /* * Second cone but pointing in opposite direction */ glPushMatrix(); glTranslatef(startXYZ[0], startXYZ[1], startXYZ[2]); glRotatef(-fiber->m_phi * radiansToDegrees, 0.0, 0.0, 1.0); glRotatef(180.0 -fiber->m_theta * radiansToDegrees, 0.0, 1.0, 0.0); glRotatef(fiber->m_psi * radiansToDegrees, 0.0, 0.0, 1.0); glScalef(majorAxis * 2.0, minorAxis * 2.0, vectorLength); m_shapeCone->draw(fiberRGBA); glPopMatrix(); } break; case FiberOrientationSymbolTypeEnum::FIBER_SYMBOL_LINES: { const float radius = 2.0; setLineWidth(radius); glBegin(GL_LINES); glVertex3fv(startXYZ); glVertex3fv(endXYZ); glEnd(); } break; } } } } /* * Now clear the list of fiber orientations for drawing. */ m_fiberOrientationsForDrawing.clear(); } /** * Draw fiber trajectories on a surface. */ void BrainOpenGLFixedPipeline::drawSurfaceFiberTrajectories(const StructureEnum::Enum structure) { drawFiberTrajectories(NULL, structure); } /** * Draw the fiber trajectories. * @param plane * If a volume it is non-NULL and contains the plane of the slice. * @param structure * The structure. */ void BrainOpenGLFixedPipeline::drawFiberTrajectories(const Plane* plane, const StructureEnum::Enum structure) { /* * Save status of clipping and disable clipping. * For fibers, the entire fiber symbol is displayed if its * origin is within the clipping planes which is tested below. */ GLboolean clipPlanesEnabled[6] = { glIsEnabled(GL_CLIP_PLANE0), glIsEnabled(GL_CLIP_PLANE1), glIsEnabled(GL_CLIP_PLANE2), glIsEnabled(GL_CLIP_PLANE3), glIsEnabled(GL_CLIP_PLANE4), glIsEnabled(GL_CLIP_PLANE5) }; glDisable(GL_CLIP_PLANE0); glDisable(GL_CLIP_PLANE1); glDisable(GL_CLIP_PLANE2); glDisable(GL_CLIP_PLANE3); glDisable(GL_CLIP_PLANE4); glDisable(GL_CLIP_PLANE5); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); CaretAssert(this->browserTabContent); OverlaySet* overlaySet = this->browserTabContent->getOverlaySet(); const int32_t numberOfDisplayedOverlays = overlaySet->getNumberOfDisplayedOverlays(); for (int32_t iOver = 0; iOver < numberOfDisplayedOverlays; iOver++) { Overlay* overlay = overlaySet->getOverlay(iOver); if (overlay->isEnabled() == false) { continue; } CaretMappableDataFile* caretMappableDataFile = NULL; int32_t mapIndex = -1; overlay->getSelectionData(caretMappableDataFile, mapIndex); if (caretMappableDataFile == NULL) { continue; } CiftiFiberTrajectoryFile* trajFile = dynamic_cast(caretMappableDataFile); if (trajFile == NULL) { continue; } FiberTrajectoryMapProperties* ftmp = trajFile->getFiberTrajectoryMapProperties(); const float proportionMinimumOpacity = ftmp->getProportionMinimumOpacity(); const float proportionMaximumOpacity = ftmp->getProportionMaximumOpacity(); const float proportionRangeOpacity = proportionMaximumOpacity - proportionMinimumOpacity; const float countMinimumOpacity = ftmp->getCountMinimumOpacity(); const float countMaximumOpacity = ftmp->getCountMaximumOpacity(); const float countRangeOpacity = countMaximumOpacity - countMinimumOpacity; const float distanceMinimumOpacity = ftmp->getDistanceMinimumOpacity(); const float distanceMaximumOpacity = ftmp->getDistanceMaximumOpacity(); const float distanceRangeOpacity = distanceMaximumOpacity - distanceMinimumOpacity; const FiberTrajectoryDisplayModeEnum::Enum displayMode = ftmp->getDisplayMode(); float streamlineThreshold = std::numeric_limits::max(); switch (displayMode) { case FiberTrajectoryDisplayModeEnum::FIBER_TRAJECTORY_DISPLAY_ABSOLUTE: streamlineThreshold = ftmp->getCountStreamline(); if (countRangeOpacity <= 0.0) { continue; } break; case FiberTrajectoryDisplayModeEnum::FIBER_TRAJECTORY_DISPLAY_DISTANCE_WEIGHTED: case FiberTrajectoryDisplayModeEnum::FIBER_TRAJECTORY_DISPLAY_DISTANCE_WEIGHTED_LOG: streamlineThreshold = ftmp->getDistanceStreamline(); if (distanceRangeOpacity <= 0.0) { continue; } break; case FiberTrajectoryDisplayModeEnum::FIBER_TRAJECTORY_DISPLAY_PROPORTION: streamlineThreshold = ftmp->getProportionStreamline(); if (proportionRangeOpacity <= 0.0) { continue; } break; } DisplayPropertiesFiberOrientation* dpfo = m_brain->getDisplayPropertiesFiberOrientation(); const DisplayGroupEnum::Enum displayGroup = dpfo->getDisplayGroupForTab(this->windowTabIndex); const FiberOrientationSymbolTypeEnum::Enum symbolType = dpfo->getSymbolType(displayGroup, this->windowTabIndex); FiberOrientationDisplayInfo fiberOrientDispInfo; setFiberOrientationDisplayInfo(dpfo, displayGroup, this->windowTabIndex, const_cast(plane), structure, ftmp->getFiberTrajectoryColorModel()->getSelectedItem(), fiberOrientDispInfo); /* * Fans use lighting but NOT on a volume slice */ disableLighting(); switch (symbolType) { case FiberOrientationSymbolTypeEnum::FIBER_SYMBOL_FANS: if (plane == NULL) { enableLighting(); } break; case FiberOrientationSymbolTypeEnum::FIBER_SYMBOL_LINES: break; } const std::vector& trajectories = trajFile->getLoadedFiberOrientationTrajectories(); const int64_t numTraj = static_cast(trajectories.size()); for (int64_t iTraj = 0; iTraj < numTraj; iTraj++) { const FiberOrientationTrajectory* fiberTraj = trajectories[iTraj]; const FiberOrientation* orientation = fiberTraj->getFiberOrientation(); const float fiberFractionTotalCount = fiberTraj->getFiberFractionTotalCount(); const std::vector& fiberFractions = fiberTraj->getFiberFractions(); if (fiberFractions.size() != 3) { CaretLogFinest("Fiber Trajectory index=" + AString::number(iTraj) + " has " + AString::number(fiberFractions.size()) + " fibers != 3 from file " + trajFile->getFileNameNoPath()); continue; } else if (fiberFractionTotalCount < streamlineThreshold) { continue; } float fiberOpacities[3] = { 0.0, 0.0, 0.0 }; const float fiberCounts[3] = { fiberFractions[0] * fiberFractionTotalCount, fiberFractions[1] * fiberFractionTotalCount, fiberFractions[2] * fiberFractionTotalCount }; const float fiberFractionDistance = fiberTraj->getFiberFractionDistance(); /* * Set opacities for each fiber using mapping of minimum and * maximum opacities */ switch (displayMode) { case FiberTrajectoryDisplayModeEnum::FIBER_TRAJECTORY_DISPLAY_ABSOLUTE: fiberOpacities[0] = (fiberCounts[0] - countMinimumOpacity) / countRangeOpacity; fiberOpacities[1] = (fiberCounts[1] - countMinimumOpacity) / countRangeOpacity; fiberOpacities[2] = (fiberCounts[2] - countMinimumOpacity) / countRangeOpacity; break; case FiberTrajectoryDisplayModeEnum::FIBER_TRAJECTORY_DISPLAY_DISTANCE_WEIGHTED: fiberOpacities[0] = ((fiberCounts[0] * fiberFractionDistance) - distanceMinimumOpacity) / distanceRangeOpacity; fiberOpacities[1] = ((fiberCounts[1] * fiberFractionDistance) - distanceMinimumOpacity) / distanceRangeOpacity; fiberOpacities[2] = ((fiberCounts[2] * fiberFractionDistance) - distanceMinimumOpacity) / distanceRangeOpacity; break; case FiberTrajectoryDisplayModeEnum::FIBER_TRAJECTORY_DISPLAY_DISTANCE_WEIGHTED_LOG: { const float distanceLog = std::log(fiberFractionDistance); fiberOpacities[0] = ((fiberCounts[0] * distanceLog) - distanceMinimumOpacity) / distanceRangeOpacity; fiberOpacities[1] = ((fiberCounts[1] * distanceLog) - distanceMinimumOpacity) / distanceRangeOpacity; fiberOpacities[2] = ((fiberCounts[2] * distanceLog) - distanceMinimumOpacity) / distanceRangeOpacity; } break; case FiberTrajectoryDisplayModeEnum::FIBER_TRAJECTORY_DISPLAY_PROPORTION: fiberOpacities[0] = (fiberFractions[0] - proportionMinimumOpacity) /proportionRangeOpacity; fiberOpacities[1] = (fiberFractions[1] - proportionMinimumOpacity) /proportionRangeOpacity; fiberOpacities[2] = (fiberFractions[2] - proportionMinimumOpacity) /proportionRangeOpacity; break; } int32_t drawCount = 3; for (int32_t i = 0; i < 3; i++) { if (fiberOpacities[i] > 1.0) { fiberOpacities[i] = 1.0; } else if (fiberOpacities[i] <= 0.0) { fiberOpacities[i] = 0.0; drawCount--; } } if (drawCount > 0) { orientation->m_fibers[0]->m_opacityForDrawing = fiberOpacities[0]; orientation->m_fibers[1]->m_opacityForDrawing = fiberOpacities[1]; orientation->m_fibers[2]->m_opacityForDrawing = fiberOpacities[2]; addFiberOrientationForDrawing(&fiberOrientDispInfo, orientation); } } drawAllFiberOrientations(&fiberOrientDispInfo, true); } glDisable(GL_BLEND); /* * Restore status of clipping planes enabled */ if (clipPlanesEnabled[0]) glEnable(GL_CLIP_PLANE0); if (clipPlanesEnabled[1]) glEnable(GL_CLIP_PLANE1); if (clipPlanesEnabled[2]) glEnable(GL_CLIP_PLANE2); if (clipPlanesEnabled[3]) glEnable(GL_CLIP_PLANE3); if (clipPlanesEnabled[4]) glEnable(GL_CLIP_PLANE4); if (clipPlanesEnabled[5]) glEnable(GL_CLIP_PLANE5); } /** * Draw a cone with an elliptical shape. * @param rgba * Color of cone. * @param baseXYZ * Location of the base (flat wide) part of the cone * @param apexXYZ * Location of the pointed end of the cone * @param baseRadiusScaling * Scale the base radius by this amount * @param baseMajorAngle * Angle for the major axis of the ellipse (units = Radians) * Valid range is [0, Pi/2] * @param baseMinorAngle * Angle for the minor axis of the ellipse (units = Radians) * Valid range is [0, Pi/2] * @param baseRotationAngle (units = Radians) * Rotation of major axis from 'up' * @param backwardsFlag * If true, draw the cone backwards (rotated 180 degrees). */ void BrainOpenGLFixedPipeline::drawEllipticalCone(const float rgba[4], const float baseXYZ[3], const float apexXYZ[3], const float baseRadiusScaling, const float baseMajorAngleIn, const float baseMinorAngleIn, const float baseRotationAngle, const bool backwardsFlag) { float x1 = apexXYZ[0]; float y1 = apexXYZ[1]; float z1 = apexXYZ[2]; float vx = baseXYZ[0] - x1; float vy = baseXYZ[1] - y1; float vz = baseXYZ[2] - z1; float z = (float)std::sqrt( vx*vx + vy*vy + vz*vz ); double ax = 0.0f; const float maxAngle = M_PI_2 * 0.95; float baseMajorAngle = baseMajorAngleIn; if (baseMajorAngle > maxAngle) { baseMajorAngle = maxAngle; } float baseMinorAngle = baseMinorAngleIn; if (baseMinorAngle > maxAngle) { baseMinorAngle = maxAngle; } const float maxWidth = z; const float majorAxis = std::min(z * std::tan(baseMajorAngle) * baseRadiusScaling, maxWidth); const float minorAxis = std::min(z * std::tan(baseMinorAngle) * baseRadiusScaling, maxWidth); double zero = 1.0e-3; if (std::abs(vz) < zero) { ax = 57.2957795*std::acos( vx/z ); // rotation angle in x-y plane if ( vx <= 0.0f ) ax = -ax; } else { ax = 57.2957795*std::acos( vz/z ); // rotation angle if ( vz <= 0.0f ) ax = -ax; } glPushMatrix(); glTranslatef( x1, y1, z1 ); float rx = -vy*vz; float ry = vx*vz; if ((std::abs(vx) < zero) && (std::fabs(vz) < zero)) { if (vy > 0) { ax = 90; } } if (std::abs(vz) < zero) { glRotated(90.0, 0.0, 1.0, 0.0); // Rotate & align with x axis glRotated(ax, -1.0, 0.0, 0.0); // Rotate to point 2 in x-y plane } else { glRotated(ax, rx, ry, 0.0); // Rotate about rotation vector } glPushMatrix(); if (backwardsFlag) { glRotatef(180.0, 0.0, 1.0, 0.0); glRotatef(MathFunctions::toDegrees(-baseRotationAngle), 0.0, 0.0, 1.0); } else { /* * Rotate around Z-axis using the base rotation angle */ glRotatef(MathFunctions::toDegrees(baseRotationAngle), 0.0, 0.0, 1.0); } /* * Draw the cone */ glScalef(majorAxis * 2.0, minorAxis * 2.0, z); m_shapeCone->draw(rgba); glPopMatrix(); glPopMatrix(); } /** * Draw a cone with an elliptical shape. * @param rgba * Color of cone. * @param bottomXYZ * Location of the bottom of the cylinder. * @param topXYZ * Location of the top of the cylinder. * @param radius * Radius of the cylinder. */ void BrainOpenGLFixedPipeline::drawCylinder(const float rgba[4], const float bottomXYZ[3], const float topXYZ[3], const float radius) { float x1 = topXYZ[0]; float y1 = topXYZ[1]; float z1 = topXYZ[2]; float vx = bottomXYZ[0] - x1; float vy = bottomXYZ[1] - y1; float vz = bottomXYZ[2] - z1; float z = (float)std::sqrt( vx*vx + vy*vy + vz*vz ); double ax = 0.0f; double zero = 1.0e-3; if (std::abs(vz) < zero) { ax = 57.2957795*std::acos( vx/z ); // rotation angle in x-y plane if ( vx <= 0.0f ) ax = -ax; } else { ax = 57.2957795*std::acos( vz/z ); // rotation angle if ( vz <= 0.0f ) ax = -ax; } glPushMatrix(); glTranslatef( x1, y1, z1 ); float rx = -vy*vz; float ry = vx*vz; if ((std::abs(vx) < zero) && (std::fabs(vz) < zero)) { if (vy > 0) { ax = 90; } } if (std::abs(vz) < zero) { glRotated(90.0, 0.0, 1.0, 0.0); // Rotate & align with x axis glRotated(ax, -1.0, 0.0, 0.0); // Rotate to point 2 in x-y plane } else { glRotated(ax, rx, ry, 0.0); // Rotate about rotation vector } glPushMatrix(); /* * Draw the cone */ glScalef(radius * 2.0, radius * 2.0, z); m_shapeCylinder->draw(rgba); glPopMatrix(); glPopMatrix(); } /** * Draw fiber orientations on surface models. * * @param structure * The structure. */ void BrainOpenGLFixedPipeline::drawSurfaceFiberOrientations(const StructureEnum::Enum structure) { drawFiberOrientations(NULL, structure); } /** * Draw the surface montage model. * @param browserTabContent * Content of the window. * @param surfaceMontageModel * The surface montage displayed in the window. * @param viewport * Region for drawing. */ void BrainOpenGLFixedPipeline::drawSurfaceMontageModel(BrowserTabContent* browserTabContent, ModelSurfaceMontage* surfaceMontageModel, const int32_t viewport[4]) { const int32_t tabIndex = browserTabContent->getTabNumber(); std::vector montageViewports; surfaceMontageModel->getSurfaceMontageViewportsForDrawing(tabIndex, montageViewports); if (montageViewports.empty()) { return; } GLint savedVP[4]; glGetIntegerv(GL_VIEWPORT, savedVP); int32_t numberOfRows = 0; int32_t numberOfColumns = 0; SurfaceMontageViewport::getNumberOfRowsAndColumns(montageViewports, numberOfRows, numberOfColumns); const int32_t vpSizeX = viewport[2] / numberOfColumns; const int32_t vpSizeY = viewport[3] / numberOfRows; const int32_t numberOfViewports = static_cast(montageViewports.size()); for (int32_t ivp = 0; ivp < numberOfViewports; ivp++) { SurfaceMontageViewport* mvp = montageViewports[ivp]; const float* nodeColoringRGBA = this->surfaceNodeColoring->colorSurfaceNodes(surfaceMontageModel, mvp->getSurface(), this->windowTabIndex); float center[3]; mvp->getSurface()->getBoundingBox()->getCenter(center); const int32_t rowFromTop = mvp->getRow(); const int32_t rowFromBottom = (numberOfRows - rowFromTop - 1); const int32_t column = mvp->getColumn(); const int32_t surfaceViewport[4] = { (viewport[0] + (column * vpSizeX)), (viewport[1] + (rowFromBottom * vpSizeY)), vpSizeX, vpSizeY }; mvp->setViewport(surfaceViewport); this->setViewportAndOrthographicProjectionForSurfaceFile(surfaceViewport, mvp->getProjectionViewType(), mvp->getSurface()); this->applyViewingTransformations(center, mvp->getProjectionViewType()); this->drawSurface(mvp->getSurface(), nodeColoringRGBA); } glViewport(savedVP[0], savedVP[1], savedVP[2], savedVP[3]); } /** * Draw the whole brain. * @param browserTabContent * Content of the window. * @param wholeBrainModel * Model for whole brain. * @param viewport * Region for drawing. */ void BrainOpenGLFixedPipeline::drawWholeBrainModel(BrowserTabContent* browserTabContent, ModelWholeBrain* wholeBrainModel, const int32_t viewport[4]) { const int32_t tabNumberIndex = browserTabContent->getTabNumber(); Surface* leftSurface = wholeBrainModel->getSelectedSurface(StructureEnum::CORTEX_LEFT, tabNumberIndex); Surface* rightSurface = wholeBrainModel->getSelectedSurface(StructureEnum::CORTEX_RIGHT, tabNumberIndex); /* * Center using volume, if it is available * Otherwise, see if surface is available, but a surface is offset * from center so override the X-coordinate to zero. */ float center[3] = { 0.0, 0.0, 0.0 }; VolumeMappableInterface* underlayVolumeFile = wholeBrainModel->getUnderlayVolumeFile(tabNumberIndex); if (underlayVolumeFile != NULL) { BoundingBox volumeBoundingBox; underlayVolumeFile->getVoxelSpaceBoundingBox(volumeBoundingBox); volumeBoundingBox.getCenter(center); } if (leftSurface != NULL) { leftSurface->getBoundingBox()->getCenter(center); center[0] = 0.0; } else { if (rightSurface != NULL) { rightSurface->getBoundingBox()->getCenter(center); center[0] = 0.0; } } /* * Use a surface (if available) to set the orthographic projection size */ Surface* anySurface = NULL; if (leftSurface != NULL) { anySurface = leftSurface; } else if (rightSurface != NULL) { anySurface = rightSurface; } if (anySurface != NULL) { this->setViewportAndOrthographicProjectionForSurfaceFile(viewport, ProjectionViewTypeEnum::PROJECTION_VIEW_LEFT_LATERAL, anySurface); } else if (underlayVolumeFile != NULL) { this->setViewportAndOrthographicProjectionForWholeBrainVolume(viewport, ProjectionViewTypeEnum::PROJECTION_VIEW_LEFT_LATERAL, underlayVolumeFile); } else { this->setViewportAndOrthographicProjection(viewport, ProjectionViewTypeEnum::PROJECTION_VIEW_LEFT_LATERAL); } this->applyViewingTransformations(center, ProjectionViewTypeEnum::PROJECTION_VIEW_LEFT_LATERAL); const SurfaceTypeEnum::Enum surfaceType = wholeBrainModel->getSelectedSurfaceType(tabNumberIndex); Brain* brain = wholeBrainModel->getBrain(); /* * Need depth testing for drawing slices */ glEnable(GL_DEPTH_TEST); /* * Determine volumes that are to be drawn */ if (underlayVolumeFile != NULL) { std::vector volumeDrawInfo; this->setupVolumeDrawInfo(browserTabContent, brain, volumeDrawInfo); if (volumeDrawInfo.empty() == false) { /* * Voxels as 3D */ drawVolumeVoxelsAsCubesWholeBrain(volumeDrawInfo); /* * Filter volumes for drawing and only draw those volumes that * are to be drawn as 2D volume slices. */ std::vector twoDimSliceDrawVolumeDrawInfo; for (std::vector::iterator iter = volumeDrawInfo.begin(); iter != volumeDrawInfo.end(); iter++) { bool useIt = false; VolumeDrawInfo& vdi = *iter; switch (vdi.wholeBrainVoxelDrawingMode) { case WholeBrainVoxelDrawingMode::DRAW_VOXELS_AS_THREE_D_CUBES: case WholeBrainVoxelDrawingMode::DRAW_VOXELS_AS_ROUNDED_THREE_D_CUBES: break; case WholeBrainVoxelDrawingMode::DRAW_VOXELS_ON_TWO_D_SLICES: useIt = true; break; } if (useIt) { twoDimSliceDrawVolumeDrawInfo.push_back(vdi); } } /* * Check for oblique slice drawing */ VolumeSliceDrawingTypeEnum::Enum sliceDrawingType = browserTabContent->getSliceDrawingType(); VolumeSliceProjectionTypeEnum::Enum sliceProjectionType = browserTabContent->getSliceProjectionType(); BrainOpenGLVolumeSliceDrawing volumeSliceDrawing; volumeSliceDrawing.draw(this, browserTabContent, volumeDrawInfo, sliceDrawingType, sliceProjectionType, viewport); } } drawSurfaceFiberOrientations(StructureEnum::ALL); drawSurfaceFiberTrajectories(StructureEnum::ALL); /* * Draw surfaces last so that opacity works. */ const int32_t numberOfBrainStructures = brain->getNumberOfBrainStructures(); for (int32_t i = 0; i < numberOfBrainStructures; i++) { BrainStructure* brainStructure = brain->getBrainStructure(i); const StructureEnum::Enum structure = brainStructure->getStructure(); Surface* surface = wholeBrainModel->getSelectedSurface(structure, tabNumberIndex); if (surface != NULL) { float dx = 0.0; float dy = 0.0; float dz = 0.0; bool drawIt = false; switch (structure) { case StructureEnum::CORTEX_LEFT: drawIt = browserTabContent->isWholeBrainLeftEnabled(); dx = -browserTabContent->getWholeBrainLeftRightSeparation(); if ((surfaceType != SurfaceTypeEnum::ANATOMICAL) && (surfaceType != SurfaceTypeEnum::RECONSTRUCTION)) { dx -= surface->getBoundingBox()->getMaxX(); } break; case StructureEnum::CORTEX_RIGHT: drawIt = browserTabContent->isWholeBrainRightEnabled(); dx = browserTabContent->getWholeBrainLeftRightSeparation(); if ((surfaceType != SurfaceTypeEnum::ANATOMICAL) && (surfaceType != SurfaceTypeEnum::RECONSTRUCTION)) { dx -= surface->getBoundingBox()->getMinX(); } break; case StructureEnum::CEREBELLUM: drawIt = browserTabContent->isWholeBrainCerebellumEnabled(); dz = browserTabContent->getWholeBrainCerebellumSeparation(); break; default: CaretLogWarning("programmer-issure: Surface type not left/right/cerebellum"); break; } const float* nodeColoringRGBA = this->surfaceNodeColoring->colorSurfaceNodes(wholeBrainModel, surface, this->windowTabIndex); if (drawIt) { glPushMatrix(); glTranslatef(dx, dy, dz); this->drawSurface(surface, nodeColoringRGBA); glPopMatrix(); } } } } /** * Draw a chart model. * * @param browserTabContent * Content of browser tab. * @param chartModel * The chart model. * @param viewport * The viewport (x, y, width, height) */ void BrainOpenGLFixedPipeline::drawChartData(BrowserTabContent* browserTabContent, ModelChart* chartModel, const int32_t viewport[4]) { CaretAssert(browserTabContent); CaretAssert(chartModel); const int32_t tabIndex = browserTabContent->getTabNumber(); ChartModelCartesian* cartesianChart = NULL; ChartableMatrixInterface* matrixChartFile = NULL; const ChartDataTypeEnum::Enum chartDataType = chartModel->getSelectedChartDataType(tabIndex); SelectionItemDataTypeEnum::Enum selectionItemDataType = SelectionItemDataTypeEnum::INVALID; int32_t scalarDataSeriesMapIndex = -1; switch (chartDataType) { case ChartDataTypeEnum::CHART_DATA_TYPE_INVALID: break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_DATA_SERIES: cartesianChart = chartModel->getSelectedDataSeriesChartModel(tabIndex); selectionItemDataType = SelectionItemDataTypeEnum::CHART_DATA_SERIES; break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_FREQUENCY_SERIES: cartesianChart = chartModel->getSelectedFrequencySeriesChartModel(tabIndex); selectionItemDataType = SelectionItemDataTypeEnum::CHART_FREQUENCY_SERIES; break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_TIME_SERIES: cartesianChart = chartModel->getSelectedTimeSeriesChartModel(tabIndex); selectionItemDataType = SelectionItemDataTypeEnum::CHART_TIME_SERIES; break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_LAYER: { CaretDataFileSelectionModel* matrixFileSelector = chartModel->getChartableMatrixParcelFileSelectionModel(tabIndex); matrixChartFile = matrixFileSelector->getSelectedFileOfType(); selectionItemDataType = SelectionItemDataTypeEnum::CHART_MATRIX; } break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_SERIES: { CaretDataFileSelectionModel* fileModel = chartModel->getChartableMatrixSeriesFileSelectionModel(tabIndex); CaretDataFile* caretFile = fileModel->getSelectedFile(); if (caretFile != NULL) { ChartableMatrixSeriesInterface* matrixSeriesFile = dynamic_cast(caretFile); if (matrixSeriesFile != NULL) { matrixChartFile = matrixSeriesFile; selectionItemDataType = SelectionItemDataTypeEnum::CHART_MATRIX; scalarDataSeriesMapIndex = matrixSeriesFile->getSelectedMapIndex(tabIndex); } } } break; } if (cartesianChart != NULL) { BrainOpenGLChartDrawingFixedPipeline chartDrawing; chartDrawing.drawCartesianChart(m_brain, this, viewport, this->textRenderer, cartesianChart, selectionItemDataType, this->windowTabIndex); } else if (matrixChartFile != NULL) { BrainOpenGLChartDrawingFixedPipeline chartDrawing; chartDrawing.drawMatrixChart(m_brain, this, viewport, this->textRenderer, matrixChartFile, scalarDataSeriesMapIndex, selectionItemDataType, this->windowTabIndex); } } /** * Setup the orthographic projection. * @param viewport * The viewport (x, y, width, height) * @param projectionType * Type of view projection. */ void BrainOpenGLFixedPipeline::setOrthographicProjection(const int32_t viewport[4], const ProjectionViewTypeEnum::Enum projectionType) { setOrthographicProjectionWithHeight(viewport, projectionType, getModelViewingHalfWindowHeight()); } /** * Setup the orthographic projection for the given surface file. * * @param viewport * The viewport (x, y, width, height) * @param projectionType * Type of view projection. * @param boundingBox * The bounding box used for maximum spatial extent. */ void BrainOpenGLFixedPipeline::setOrthographicProjectionForWithBoundingBox(const int32_t viewport[4], const ProjectionViewTypeEnum::Enum projectionType, const BoundingBox* boundingBox) { CaretAssert(boundingBox); /* * For a cortical surface, this largest dimension is the Y-Axis. * This worked correctly when the default view was dorsal with * the anterior pole at the top of the display and the posterior * pole at the bottom of the display. */ float modelHalfHeight = std::max(std::max(boundingBox->getDifferenceX(), boundingBox->getDifferenceY()), boundingBox->getDifferenceZ()) / 2.0; /* * The default view was changed to a lateral view and the above * code results in problems during some window resize operations. * But, the Z-difference of a flat surface is zero. * * See also BrowserTabContent::restoreFromScene() that tries to make * old scenes compatible with this new scaling. */ const float zDiff = boundingBox->getDifferenceZ(); if (zDiff != 0.0) { modelHalfHeight = zDiff / 2.0; const float yDiff = boundingBox->getDifferenceY(); if ((yDiff > 0.0) && (viewport[2] > 0.0)) { /* * Note Z is vertical, Y is horizontal when viewed */ const float surfaceAspectRatio = zDiff / yDiff; const float viewportAspectRatio = (static_cast(viewport[3]) / static_cast(viewport[2])); if (viewportAspectRatio > surfaceAspectRatio) { const float modelHalfWidth = yDiff / 2.0; modelHalfHeight = modelHalfWidth * viewportAspectRatio; } } } const float orthoHeight = modelHalfHeight * 1.02; setOrthographicProjectionWithHeight(viewport, projectionType, orthoHeight); } /** * Setup the orthographic projection with the given window height. * * @param viewport * The viewport (x, y, width, height) * @param projectionType * Type of view projection. * @param halfWindowHeight * Half of window height for model. */ void BrainOpenGLFixedPipeline::setOrthographicProjectionWithHeight(const int32_t viewport[4], const ProjectionViewTypeEnum::Enum projectionType, const float halfWindowHeight) { double width = viewport[2]; double height = viewport[3]; double aspectRatio = (width / height); this->orthographicRight = halfWindowHeight * aspectRatio; this->orthographicLeft = -halfWindowHeight * aspectRatio; this->orthographicTop = halfWindowHeight; this->orthographicBottom = -halfWindowHeight; this->orthographicNear = -1000.0; //-500.0; //-10000.0; this->orthographicFar = 1000.0; //500.0; // 10000.0; switch (projectionType) { case ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_ANTERIOR: case ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_DORSAL: case ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_POSTERIOR: case ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_VENTRAL: glOrtho(this->orthographicLeft, this->orthographicRight, this->orthographicBottom, this->orthographicTop, this->orthographicNear, this->orthographicFar); break; case ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_FLAT_SURFACE: glOrtho(this->orthographicLeft, this->orthographicRight, this->orthographicBottom, this->orthographicTop, this->orthographicNear, this->orthographicFar); break; case ProjectionViewTypeEnum::PROJECTION_VIEW_LEFT_LATERAL: case ProjectionViewTypeEnum::PROJECTION_VIEW_LEFT_FLAT_SURFACE: case ProjectionViewTypeEnum::PROJECTION_VIEW_RIGHT_MEDIAL: glOrtho(this->orthographicLeft, this->orthographicRight, this->orthographicBottom, this->orthographicTop, this->orthographicNear, this->orthographicFar); break; case ProjectionViewTypeEnum::PROJECTION_VIEW_RIGHT_LATERAL: case ProjectionViewTypeEnum::PROJECTION_VIEW_LEFT_MEDIAL: case ProjectionViewTypeEnum::PROJECTION_VIEW_RIGHT_FLAT_SURFACE: glOrtho(this->orthographicRight, this->orthographicLeft, this->orthographicBottom, this->orthographicTop, this->orthographicFar, this->orthographicNear); break; } } /** * check for an OpenGL Error. */ void BrainOpenGLFixedPipeline::checkForOpenGLError(const Model* model, const AString& msgIn) { GLenum errorCode = glGetError(); if (errorCode != GL_NO_ERROR) { AString msg; if (msgIn.isEmpty() == false) { msg += (msgIn + "\n"); } msg += ("OpenGL Error: " + AString((char*)gluErrorString(errorCode)) + "\n"); msg += ("OpenGL Version: " + AString((char*)glGetString(GL_VERSION)) + "\n"); msg += ("OpenGL Vendor: " + AString((char*)glGetString(GL_VENDOR)) + "\n"); if (model != NULL) { msg += ("While drawing brain model " + model->getNameForGUI(true) + "\n"); } msg += ("In tab number " + AString::number(this->windowTabIndex) + "\n"); GLint nameStackDepth, modelStackDepth, projStackDepth; glGetIntegerv(GL_PROJECTION_STACK_DEPTH, &projStackDepth); glGetIntegerv(GL_MODELVIEW_STACK_DEPTH, &modelStackDepth); glGetIntegerv(GL_NAME_STACK_DEPTH, &nameStackDepth); msg += ("Projection Matrix Stack Depth " + AString::number(projStackDepth) + "\n"); msg += ("Model Matrix Stack Depth " + AString::number(modelStackDepth) + "\n"); msg += ("Name Matrix Stack Depth " + AString::number(nameStackDepth) + "\n"); CaretLogSevere(msg); } } /** * Analyze color information to extract identification data. * @param dataType * Type of data. * @param x * X-coordinate of identification. * @param y * X-coordinate of identification. * @param indexOut * Index of selected item. * @param depthOut * Depth of selected item. */ void BrainOpenGLFixedPipeline::getIndexFromColorSelection(SelectionItemDataTypeEnum::Enum dataType, const int32_t x, const int32_t y, int32_t& indexOut, float& depthOut) { // Figure out item was picked using color in color buffer // glReadBuffer(GL_BACK); glPixelStorei(GL_PACK_SKIP_ROWS, 0); glPixelStorei(GL_PACK_SKIP_PIXELS, 0); glPixelStorei(GL_PACK_ALIGNMENT, 1); uint8_t pixels[3]; glReadPixels((int)x, (int)y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, pixels); indexOut = -1; depthOut = -1.0; CaretLogFine("ID color is " + QString::number(pixels[0]) + ", " + QString::number(pixels[1]) + ", " + QString::number(pixels[2])); this->colorIdentification->getItem(pixels, dataType, &indexOut); if (indexOut >= 0) { /* * Get depth from depth buffer */ glPixelStorei(GL_PACK_ALIGNMENT, 4); glReadPixels(x, y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depthOut); } this->colorIdentification->reset(); } /** * Analyze color information to extract identification data. * @param dataType * Type of data. * @param x * X-coordinate of identification. * @param y * X-coordinate of identification. * @param indexOut1 * First index of selected item. * @param indexOut2 * Second index of selected item. * @param depthOut * Depth of selected item. */ void BrainOpenGLFixedPipeline::getIndexFromColorSelection(SelectionItemDataTypeEnum::Enum dataType, const int32_t x, const int32_t y, int32_t& index1Out, int32_t& index2Out, float& depthOut) { // Figure out item was picked using color in color buffer // glReadBuffer(GL_BACK); glPixelStorei(GL_PACK_SKIP_ROWS, 0); glPixelStorei(GL_PACK_SKIP_PIXELS, 0); glPixelStorei(GL_PACK_ALIGNMENT, 1); uint8_t pixels[3]; glReadPixels((int)x, (int)y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, pixels); index1Out = -1; index2Out = -1; depthOut = -1.0; CaretLogFine("ID color is " + QString::number(pixels[0]) + ", " + QString::number(pixels[1]) + ", " + QString::number(pixels[2])); this->colorIdentification->getItem(pixels, dataType, &index1Out, &index2Out); if (index1Out >= 0) { /* * Get depth from depth buffer */ glPixelStorei(GL_PACK_ALIGNMENT, 4); glReadPixels(x, y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depthOut); } this->colorIdentification->reset(); } /** * Analyze color information to extract identification data. * @param dataType * Type of data. * @param x * X-coordinate of identification. * @param y * X-coordinate of identification. * @param indexOut1 * First index of selected item. * @param indexOut2 * Second index of selected item. * @param indexOut3 * Third index of selected item. * @param depthOut * Depth of selected item. */ void BrainOpenGLFixedPipeline::getIndexFromColorSelection(SelectionItemDataTypeEnum::Enum dataType, const int32_t x, const int32_t y, int32_t& index1Out, int32_t& index2Out, int32_t& index3Out, float& depthOut) { // Figure out item was picked using color in color buffer // glReadBuffer(GL_BACK); glPixelStorei(GL_PACK_SKIP_ROWS, 0); glPixelStorei(GL_PACK_SKIP_PIXELS, 0); glPixelStorei(GL_PACK_ALIGNMENT, 1); uint8_t pixels[3]; glReadPixels((int)x, (int)y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, pixels); index1Out = -1; index2Out = -1; index3Out = -1; depthOut = -1.0; CaretLogFine("ID color is " + QString::number(pixels[0]) + ", " + QString::number(pixels[1]) + ", " + QString::number(pixels[2])); this->colorIdentification->getItem(pixels, dataType, &index1Out, &index2Out, &index3Out); if (index1Out >= 0) { /* * Get depth from depth buffer */ glPixelStorei(GL_PACK_ALIGNMENT, 4); glReadPixels(x, y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &depthOut); } this->colorIdentification->reset(); } /** * Set the selected item's screen coordinates. * @param item * Item that has screen coordinates set. * @param itemXYZ * Model's coordinate. */ void BrainOpenGLFixedPipeline::setSelectedItemScreenXYZ(SelectionItem* item, const float itemXYZ[3]) { GLdouble selectionModelviewMatrix[16]; glGetDoublev(GL_MODELVIEW_MATRIX, selectionModelviewMatrix); GLdouble selectionProjectionMatrix[16]; glGetDoublev(GL_PROJECTION_MATRIX, selectionProjectionMatrix); GLint selectionViewport[4]; glGetIntegerv(GL_VIEWPORT, selectionViewport); const double modelXYZ[3] = { itemXYZ[0], itemXYZ[1], itemXYZ[2] }; double windowPos[3]; if (gluProject(modelXYZ[0], modelXYZ[1], modelXYZ[2], selectionModelviewMatrix, selectionProjectionMatrix, selectionViewport, &windowPos[0], &windowPos[1], &windowPos[2])) { item->setScreenXYZ(windowPos); item->setModelXYZ(modelXYZ); } } /** * Draw sphere. * * @param rgba * Color for drawing. * @param diameter * Diameter of the sphere. */ void BrainOpenGLFixedPipeline::drawSphereWithDiameter(const float rgba[4], const double diameter) { glPushMatrix(); glScaled(diameter, diameter, diameter); m_shapeSphere->draw(rgba); glPopMatrix(); } /** * Draw sphere. * * @param rgba * Color for drawing. * @param diameter * Diameter of the sphere. */ void BrainOpenGLFixedPipeline::drawSphereWithDiameter(const uint8_t rgba[4], const double diameter) { glPushMatrix(); glScaled(diameter, diameter, diameter); m_shapeSphere->draw(rgba); glPopMatrix(); } /** * Draw cube. * * @param rgba * Color for drawing. * @param cubeSize * Size of the cube (distance from one face to its opposite face). */ void BrainOpenGLFixedPipeline::drawCube(const float rgba[4], const double cubeSize) { glPushMatrix(); glScaled(cubeSize, cubeSize, cubeSize); m_shapeCube->draw(rgba); glPopMatrix(); } /** * Draw a cuboid (3D Box) * * @param rgba * Color for drawing. * @param sizeX * X-Size of the cube (distance from -X face to its +X face). * @param sizeY * Y-Size of the cube (distance from -Y face to its +Y face). * @param sizeZ * Z-Size of the cube (distance from -Z face to its +X face). */ void BrainOpenGLFixedPipeline::drawCuboid(const uint8_t rgba[4], const double sizeX, const double sizeY, const double sizeZ) { glPushMatrix(); glScaled(sizeX, sizeY, sizeZ); m_shapeCube->draw(rgba); glPopMatrix(); } /** * Draw cube. * * @param rgba * Color for drawing. * @param cubeSize * Size of the cube (distance from one face to its opposite face). */ void BrainOpenGLFixedPipeline::drawRoundedCube(const float rgba[4], const double cubeSize) { glPushMatrix(); glScaled(cubeSize, cubeSize, cubeSize); m_shapeCubeRounded->draw(rgba); glPopMatrix(); } /** * Draw a cuboid (3D Box) * * @param rgba * Color for drawing. * @param sizeX * X-Size of the cube (distance from -X face to its +X face). * @param sizeY * Y-Size of the cube (distance from -Y face to its +Y face). * @param sizeZ * Z-Size of the cube (distance from -Z face to its +X face). */ void BrainOpenGLFixedPipeline::drawRoundedCuboid(const uint8_t rgba[4], const double sizeX, const double sizeY, const double sizeZ) { glPushMatrix(); glScaled(sizeX, sizeY, sizeZ); m_shapeCubeRounded->draw(rgba); glPopMatrix(); } /** * Draw a one millimeter square facing the user. * NOTE: This method will alter the current * modelviewing matrices so caller may need * to enclose the call to this method within * glPushMatrix() and glPopMatrix(). * * @param rgba * RGBA coloring ranging 0.0 to 1.0. * @param size * Size of square. */ void BrainOpenGLFixedPipeline::drawSquare(const float rgba[4], const float size) { if (this->inverseRotationMatrixValid) { glColor4fv(rgba); /* * Remove any rotation */ glMultMatrixd(this->inverseRotationMatrix); glScalef(size, size, size); /* * Draw both front and back side since in some instances, * such as surface montage, we are viweing from the far * side (from back of monitor) */ glBegin(GL_QUADS); glNormal3f(0.0, 0.0, 1.0); glVertex3f(-0.5, -0.5, 0.0); glVertex3f( 0.5, -0.5, 0.0); glVertex3f( 0.5, 0.5, 0.0); glVertex3f(-0.5, 0.5, 0.0); glNormal3f(0.0, 0.0, -1.0); glVertex3f(-0.5, -0.5, 0.0); glVertex3f(-0.5, 0.5, 0.0); glVertex3f( 0.5, 0.5, 0.0); glVertex3f( 0.5, -0.5, 0.0); glEnd(); } } /** * Draw a one millimeter square facing the user. * NOTE: This method will alter the current * modelviewing matrices so caller may need * to enclose the call to this method within * glPushMatrix() and glPopMatrix(). * * @param rgba * RGBA coloring ranging 0 to 255. * @param size * Size of square. */ void BrainOpenGLFixedPipeline::drawSquare(const uint8_t rgba[4], const float size) { if (this->inverseRotationMatrixValid) { glColor4ubv(rgba); /* * Remove any rotation */ glMultMatrixd(this->inverseRotationMatrix); glScalef(size, size, size); /* * Draw both front and back side since in some instances, * such as surface montage, we are viweing from the far * side (from back of monitor) */ glBegin(GL_QUADS); glNormal3f(0.0, 0.0, 1.0); glVertex3f(-0.5, -0.5, 0.0); glVertex3f( 0.5, -0.5, 0.0); glVertex3f( 0.5, 0.5, 0.0); glVertex3f(-0.5, 0.5, 0.0); glNormal3f(0.0, 0.0, -1.0); glVertex3f(-0.5, -0.5, 0.0); glVertex3f(-0.5, 0.5, 0.0); glVertex3f( 0.5, 0.5, 0.0); glVertex3f( 0.5, -0.5, 0.0); glEnd(); } } /** * Draw the user's selected image over the background * * vpContent * Viewport content which image is displayed. */ void BrainOpenGLFixedPipeline::drawBackgroundImage(BrainOpenGLViewportContent* vpContent) { BrowserTabContent* btc = vpContent->getBrowserTabContent(); if (btc == NULL) { return; } Brain* brain = vpContent->getBrain(); DisplayPropertiesImages* dpi = brain->getDisplayPropertiesImages(); const int32_t tabIndex = btc->getTabNumber(); const DisplayGroupEnum::Enum displayGroup = dpi->getDisplayGroupForTab(tabIndex); if (dpi->isDisplayed(displayGroup, tabIndex)) { int modelViewport[4]; vpContent->getModelViewport(modelViewport); ImageFile* imageFile = dpi->getSelectedImageFile(displayGroup, tabIndex); if (imageFile != NULL) { drawImage(modelViewport, imageFile); } } } /** * Draw the given QImage in the given viewport. * * @param viewport * The viewport dimensions. * @param image * The QImage that is drawn. */ void BrainOpenGLFixedPipeline::drawImage(const int viewport[4], const ImageFile* imageFile) { int32_t imageWidth = 0; int32_t imageHeight = 0; std::vector imageBytesRGBA; if ( ! imageFile->getImageBytesRGBA(ImageFile::IMAGE_DATA_ORIGIN_AT_BOTTOM, imageBytesRGBA, imageWidth, imageHeight)) { return; } const float xScale = static_cast(viewport[2]) / static_cast(imageWidth); const float yScale = static_cast(viewport[3]) / static_cast(imageHeight); float pixelZoom = 1.0; float xPos = 0.0; float yPos = 0.0; bool centerImageX = false, centerImageY = false; if (xScale < yScale) { pixelZoom = xScale; centerImageY = true; } else { pixelZoom = yScale; centerImageX = true; } if (centerImageY) { // center image vertically const float ySize = imageHeight * pixelZoom; const float margin = viewport[3] - ySize; yPos = margin * 0.5; } if (centerImageX) { // center image horizontally const float xSize = imageWidth * pixelZoom; const float margin = viewport[3] - xSize; xPos = margin * 0.5; } glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); const double nearClip = -1000.0; const double farClip = 1000.0; glOrtho(0, viewport[2], 0, viewport[3], nearClip, farClip); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); // // Draw image near far clipping plane // const float imageZ = 10.0 - farClip; glRasterPos3f(xPos, yPos, imageZ); //-500.0); // set Z so image behind surface glPixelZoom(pixelZoom, pixelZoom); glDrawPixels(imageWidth, imageHeight, GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid*)&imageBytesRGBA[0]); glPopMatrix(); glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); } /** * Draw text at the given window coordinates. * * @param windowX * Window X-coordinate. * @param windowY * Window Y-coordinate. * @param text * Text that is to be drawn. * @param alignmentX * X-alignment of text. * @param alignmentY * Y-alignment of text. * @param textStyle * Style of text. * @param fontHeight * Height of font. If negative, default is used. */ void BrainOpenGLFixedPipeline::drawTextWindowCoords(const int windowX, const int windowY, const QString& text, const BrainOpenGLTextRenderInterface::TextAlignmentX alignmentX, const BrainOpenGLTextRenderInterface::TextAlignmentY alignmentY, const BrainOpenGLTextRenderInterface::TextStyle textStyle, const int fontHeight) { if (this->textRenderer != NULL) { GLint vp[4]; glGetIntegerv(GL_VIEWPORT, vp); int viewport[4] = { vp[0], vp[1], vp[2], vp[3] }; this->textRenderer->drawTextAtWindowCoords(viewport, windowX, windowY, text.trimmed(), alignmentX, alignmentY, textStyle, fontHeight); } } /** * Draw text at the given window coordinates and occlude anything under * the text by drawing the text region with a background. * * @param windowX * Window X-coordinate. * @param windowY * Window Y-coordinate. * @param text * Text that is to be drawn. * @param alignmentX * X-alignment of text. * @param alignmentY * Y-alignment of text. * @param textStyle * Style of text. * @param fontHeight * Height of font. If negative, default is used. */ void BrainOpenGLFixedPipeline::drawTextWindowCoordsWithBackground(const int windowX, const int windowY, const QString& text, const BrainOpenGLTextRenderInterface::TextAlignmentX alignmentX, const BrainOpenGLTextRenderInterface::TextAlignmentY alignmentY, const BrainOpenGLTextRenderInterface::TextStyle textStyle, const int fontHeight) { if (this->textRenderer != NULL) { int32_t textWidth = 0; int32_t textHeight = 0; this->textRenderer->getTextBoundsInPixels(textWidth, textHeight, text, textStyle, fontHeight); //std::cout << "Text bounds: " << qPrintable(text) << ": " << textWidth << ", " << textHeight << std::endl; const int textCenter[2] = { windowX, windowY }; GLint savedViewport[4]; glGetIntegerv(GL_VIEWPORT, savedViewport); const int halfWidth = (textWidth + 3) / 2.0; const int halfHeight = (textHeight + 3) / 2.0; int vpLeftX = savedViewport[0] + textCenter[0] - halfWidth; int vpRightX = savedViewport[0] + textCenter[0] + halfWidth; int vpBottomY = savedViewport[1] + textCenter[1] - halfHeight; int vpTopY = savedViewport[1] + textCenter[1] + halfHeight; MathFunctions::limitRange(vpLeftX, savedViewport[0], savedViewport[0] + savedViewport[2]); MathFunctions::limitRange(vpRightX, savedViewport[0], savedViewport[0] + savedViewport[2]); MathFunctions::limitRange(vpBottomY, savedViewport[1], savedViewport[1] + savedViewport[3]); MathFunctions::limitRange(vpTopY, savedViewport[1], savedViewport[1] + savedViewport[3]); const int vpSizeX = vpRightX - vpLeftX; const int vpSizeY = vpTopY - vpBottomY; glViewport(vpLeftX, vpBottomY, vpSizeX, vpSizeY); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); std::vector rgba; std::vector coords, normals; coords.push_back(-1.0); coords.push_back(-1.0); coords.push_back( 0.0); normals.push_back(0.0); normals.push_back(0.0); normals.push_back(1.0); rgba.push_back(m_backgroundColorByte[0]); rgba.push_back(m_backgroundColorByte[1]); rgba.push_back(m_backgroundColorByte[2]); rgba.push_back(m_backgroundColorByte[3]); coords.push_back( 1.0); coords.push_back(-1.0); coords.push_back( 0.0); normals.push_back(0.0); normals.push_back(0.0); normals.push_back(1.0); rgba.push_back(m_backgroundColorByte[0]); rgba.push_back(m_backgroundColorByte[1]); rgba.push_back(m_backgroundColorByte[2]); rgba.push_back(m_backgroundColorByte[3]); coords.push_back( 1.0); coords.push_back( 1.0); coords.push_back( 0.0); normals.push_back(0.0); normals.push_back(0.0); normals.push_back(1.0); rgba.push_back(m_backgroundColorByte[0]); rgba.push_back(m_backgroundColorByte[1]); rgba.push_back(m_backgroundColorByte[2]); rgba.push_back(m_backgroundColorByte[3]); coords.push_back(-1.0); coords.push_back( 1.0); coords.push_back( 0.0); normals.push_back(0.0); normals.push_back(0.0); normals.push_back(1.0); rgba.push_back(m_backgroundColorByte[0]); rgba.push_back(m_backgroundColorByte[1]); rgba.push_back(m_backgroundColorByte[2]); rgba.push_back(m_backgroundColorByte[3]); BrainOpenGLPrimitiveDrawing::drawQuads(coords, normals, rgba); glPopMatrix(); glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glViewport(savedViewport[0], savedViewport[1], savedViewport[2], savedViewport[3]); drawTextWindowCoords(windowX, windowY, text, alignmentX, alignmentY, textStyle, fontHeight); } } /** * Draw text at the given window coordinates. * @param modelX * Model X-coordinate. * @param modelY * Model Y-coordinate. * @param modelZ * Model Z-coordinate. * @param text * Text that is to be drawn. */ void BrainOpenGLFixedPipeline::drawTextModelCoords(const double modelX, const double modelY, const double modelZ, const QString& text) { if (this->textRenderer != NULL) { this->textRenderer->drawTextAtModelCoords(modelX, modelY, modelZ, text.trimmed(), BrainOpenGLTextRenderInterface::NORMAL, 14); } } /** * Draw text at the given window coordinates. * @param modelXYZ * Model XYZ coordinate. * @param text * Text that is to be drawn. */ void BrainOpenGLFixedPipeline::drawTextModelCoords(const double modelXYZ[3], const QString& text) { drawTextModelCoords(modelXYZ[0], modelXYZ[1], modelXYZ[2], text); } /** * Draw the palettes showing how scalars are mapped * to colors. * @param brain * Brain containing model being drawn. * @param viewport * Viewport for the model. */ void BrainOpenGLFixedPipeline::drawAllPalettes(Brain* brain) { /* * Turn off depth testing */ glDisable(GL_DEPTH_TEST); /* * Save the projection matrix, model matrix, and viewport. */ glMatrixMode(GL_PROJECTION); GLfloat savedProjectionMatrix[16]; glGetFloatv(GL_PROJECTION_MATRIX, savedProjectionMatrix); glMatrixMode(GL_MODELVIEW); GLfloat savedModelviewMatrix[16]; glGetFloatv(GL_MODELVIEW_MATRIX, savedModelviewMatrix); GLint savedViewport[4]; glGetIntegerv(GL_VIEWPORT, savedViewport); CaretAssert(brain); /* * Check for a 'selection' type mode */ bool selectFlag = false; switch (this->mode) { case MODE_DRAWING: break; case MODE_IDENTIFICATION: selectFlag = true; break; case MODE_PROJECTION: return; break; } if (selectFlag) { return; } this->disableLighting(); PaletteFile* paletteFile = brain->getPaletteFile(); CaretAssert(paletteFile); std::vector mapFiles; std::vector mapIndices; this->browserTabContent->getDisplayedPaletteMapFiles(mapFiles, mapIndices); /* * Each map file has a palette drawn to represent the * datas mapping to colors. */ const int32_t numMapFiles = static_cast(mapFiles.size()); for (int32_t i = 0; i < numMapFiles; i++) { const int mapIndex = mapIndices[i]; const PaletteColorMapping* pcm = mapFiles[i]->getMapPaletteColorMapping(mapIndex); if (pcm != NULL) { const AString paletteName = pcm->getSelectedPaletteName(); const Palette* palette = paletteFile->getPaletteByName(paletteName); if (palette != NULL) { FastStatistics* statistics = NULL; switch (mapFiles[i]->getPaletteNormalizationMode()) { case PaletteNormalizationModeEnum::NORMALIZATION_ALL_MAP_DATA: statistics = const_cast(mapFiles[i]->getFileFastStatistics()); break; case PaletteNormalizationModeEnum::NORMALIZATION_SELECTED_MAP_DATA: statistics = const_cast(mapFiles[i]->getMapFastStatistics(mapIndex)); break; } if (statistics != NULL) { this->drawPalette(palette, pcm, statistics, i); } } else { CaretLogWarning("Palette named " + paletteName + " not found in palette file."); } } } /* * Restore the projection matrix, model matrix, and viewport. */ glMatrixMode(GL_PROJECTION); glLoadMatrixf(savedProjectionMatrix); glMatrixMode(GL_MODELVIEW); glLoadMatrixf(savedModelviewMatrix); glViewport(savedViewport[0], savedViewport[1], savedViewport[2], savedViewport[3]); } /** * Draw a palette. * @param palette * Palette that is drawn. * @param paletteColorMapping * Controls mapping of data to colors. * @param statistics * Statistics describing the data that is mapped to the palette. * @param paletteDrawingIndex * Counts number of palettes being drawn for the Y-position */ void BrainOpenGLFixedPipeline::drawPalette(const Palette* palette, const PaletteColorMapping* paletteColorMapping, const FastStatistics* statistics, const int paletteDrawingIndex) { /* * Save viewport. */ GLint modelViewport[4]; glGetIntegerv(GL_VIEWPORT, modelViewport); /* * Create a viewport for drawing the palettes in the * lower left corner of the window. Try to use * 25% of the display's width. */ const GLint colorbarViewportWidth = std::max(static_cast(modelViewport[2] * 0.25), (GLint)120); const GLint colorbarViewportHeight = 35; const GLint colorbarViewportX = modelViewport[0] + 10; const GLint colorbarVerticalSpacing = 10; GLint colorbarViewportY = (modelViewport[1] + colorbarVerticalSpacing + (paletteDrawingIndex * colorbarViewportHeight)); glViewport(colorbarViewportX, colorbarViewportY, colorbarViewportWidth, colorbarViewportHeight); CaretLogFine("Palette " + palette->getName() + " Viewport: (" + AString::number(colorbarViewportX) + ", " + AString::number(colorbarViewportY) + ", " + AString::number(colorbarViewportWidth) + ", " + AString::number(colorbarViewportHeight) + ")\n Model Viewport: " + AString::fromNumbers(modelViewport, 4, ", ")); /* * Types of values for display */ const bool isPositiveDisplayed = paletteColorMapping->isDisplayPositiveDataFlag(); const bool isNegativeDisplayed = paletteColorMapping->isDisplayNegativeDataFlag(); const bool isZeroDisplayed = paletteColorMapping->isDisplayZeroDataFlag(); const bool isPositiveOnly = (isPositiveDisplayed && (isNegativeDisplayed == false)); const bool isNegativeOnly = ((isPositiveDisplayed == false) && isNegativeDisplayed); /* * Create an orthographic projection that ranges in X: * (-1, 1) If negative and positive displayed * (-1, 0) If positive is NOT displayed * (0, 1) If negative is NOT displayed */ const GLdouble halfHeight = static_cast(colorbarViewportHeight / 2); const GLdouble orthoHeight = halfHeight; GLdouble orthoLeft = -1.0; GLdouble orthoRight = 1.0; if (isPositiveOnly) { orthoLeft = 0.0; } else if (isNegativeOnly) { orthoRight = 0.0; } glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(orthoLeft, orthoRight, -orthoHeight, orthoHeight, -1.0, 1.0); glMatrixMode (GL_MODELVIEW); glLoadIdentity(); /* * A little extra so viewport gets filled */ const GLdouble orthoLeftWithExtra = orthoLeft - 0.10; const GLdouble orthoRightWithExtra = orthoRight + 0.10; /* * Fill the palette viewport with the background color * Add a little to left and right so viewport is filled (excess will get clipped) */ glColor3fv(m_backgroundColorFloat); glRectf(orthoLeftWithExtra, -orthoHeight, orthoRightWithExtra, orthoHeight); /* * Always interpolate if the palette has only two colors */ bool interpolateColor = paletteColorMapping->isInterpolatePaletteFlag(); if (palette->getNumberOfScalarsAndColors() <= 2) { interpolateColor = true; } /* * Draw the colorbar starting with the color assigned * to the negative end of the palette. * Colorbar scalars range from -1 to 1. */ const int iStart = palette->getNumberOfScalarsAndColors() - 1; const int iEnd = 1; const int iStep = -1; for (int i = iStart; i >= iEnd; i += iStep) { /* * palette data for 'left' side of a color in the palette. */ const PaletteScalarAndColor* sc = palette->getScalarAndColor(i); float scalar = sc->getScalar(); float rgba[4]; sc->getColor(rgba); /* * palette data for 'right' side of a color in the palette. */ const PaletteScalarAndColor* nextSC = palette->getScalarAndColor(i - 1); float nextScalar = nextSC->getScalar(); float nextRGBA[4]; nextSC->getColor(nextRGBA); const bool isNoneColorFlag = nextSC->isNoneColor(); /* * Exclude negative regions if not displayed. */ if (isNegativeDisplayed == false) { if (nextScalar < 0.0) { continue; } else if (scalar < 0.0) { scalar = 0.0; } } /* * Exclude positive regions if not displayed. */ if (isPositiveDisplayed == false) { if (scalar > 0.0) { continue; } else if (nextScalar > 0.0) { nextScalar = 0.0; } } /* * Normally, the first entry has a scalar value of -1. * If it does not, use the first color draw from * -1 to the first scalar value. */ if (i == iStart) { if (sc->isNoneColor() == false) { if (scalar > -1.0) { const float xStart = -1.0; const float xEnd = scalar; glColor3fv(rgba); glBegin(GL_POLYGON); glVertex3f(xStart, 0.0, 0.0); glVertex3f(xStart, -halfHeight, 0.0); glVertex3f(xEnd, -halfHeight, 0.0); glVertex3f(xEnd, 0.0, 0.0); glEnd(); } } } /* * If the 'next' color is none, drawing * is skipped to let the background show * throw the 'none' region of the palette. */ if (isNoneColorFlag == false) { /* * left and right region of an entry in the palette */ const float xStart = scalar; const float xEnd = nextScalar; /* * Unless interpolating, use the 'next' color. */ float* startRGBA = nextRGBA; float* endRGBA = nextRGBA; if (interpolateColor) { startRGBA = rgba; } /* * Draw the region in the palette. */ glBegin(GL_POLYGON); glColor3fv(startRGBA); glVertex3f(xStart, 0.0, 0.0); glVertex3f(xStart, -halfHeight, 0.0); glColor3fv(endRGBA); glVertex3f(xEnd, -halfHeight, 0.0); glVertex3f(xEnd, 0.0, 0.0); glEnd(); /* * The last scalar value is normally 1.0. If the last * scalar is less than 1.0, then fill in the rest of * the palette from the last scalar to 1.0. */ if (i == iEnd) { if (nextScalar < 1.0) { const float xStart = nextScalar; const float xEnd = 1.0; glColor3fv(nextRGBA); glBegin(GL_POLYGON); glVertex3f(xStart, 0.0, 0.0); glVertex3f(xStart, -halfHeight, 0.0); glVertex3f(xEnd, -halfHeight, 0.0); glVertex3f(xEnd, 0.0, 0.0); glEnd(); } } } } /* * Draw over thresholded regions with background color */ const PaletteThresholdTypeEnum::Enum thresholdType = paletteColorMapping->getThresholdType(); if (thresholdType != PaletteThresholdTypeEnum::THRESHOLD_TYPE_OFF) { const float minMaxThresholds[2] = { paletteColorMapping->getThresholdMinimum(thresholdType), paletteColorMapping->getThresholdMaximum(thresholdType) }; float normalizedThresholds[2]; paletteColorMapping->mapDataToPaletteNormalizedValues(statistics, minMaxThresholds, normalizedThresholds, 2); switch (paletteColorMapping->getThresholdTest()) { case PaletteThresholdTestEnum::THRESHOLD_TEST_SHOW_INSIDE: glColor3fv(m_backgroundColorFloat); glRectf(orthoLeftWithExtra, -orthoHeight, normalizedThresholds[0], orthoHeight); glRectf(normalizedThresholds[1], -orthoHeight, orthoRightWithExtra, orthoHeight); break; case PaletteThresholdTestEnum::THRESHOLD_TEST_SHOW_OUTSIDE: glColor3fv(m_backgroundColorFloat); glRectf(normalizedThresholds[0], -orthoHeight, normalizedThresholds[1], orthoHeight); break; } } /* * If zeros are not displayed, draw a line in the * background color at zero in the palette. */ if (isZeroDisplayed == false) { this->setLineWidth(1.0); glColor3fv(m_backgroundColorFloat); glBegin(GL_LINES); glVertex2f(0.0, -halfHeight); glVertex2f(0.0, 0.0); glEnd(); } AString textLeft; AString textCenter; AString textRight; const bool useNewFormattingFlag = true; if (useNewFormattingFlag) { /* * NEW FORMATTING !!!!! */ paletteColorMapping->getPaletteColorBarScaleText(statistics, textLeft, textCenter, textRight); } else { float minMax[4] = { -1.0, 0.0, 0.0, 1.0 }; switch (paletteColorMapping->getScaleMode()) { case PaletteScaleModeEnum::MODE_AUTO_SCALE: { float dummy; statistics->getNonzeroRanges(minMax[0], dummy, dummy, minMax[3]); } break; case PaletteScaleModeEnum::MODE_AUTO_SCALE_ABSOLUTE_PERCENTAGE: { const float maxPct = paletteColorMapping->getAutoScaleAbsolutePercentageMaximum(); const float minPct = paletteColorMapping->getAutoScaleAbsolutePercentageMinimum(); minMax[0] = -statistics->getApproxAbsolutePercentile(maxPct); minMax[1] = -statistics->getApproxAbsolutePercentile(minPct); minMax[2] = statistics->getApproxAbsolutePercentile(minPct); minMax[3] = statistics->getApproxAbsolutePercentile(maxPct); } break; case PaletteScaleModeEnum::MODE_AUTO_SCALE_PERCENTAGE: { const float negMaxPct = paletteColorMapping->getAutoScalePercentageNegativeMaximum(); const float negMinPct = paletteColorMapping->getAutoScalePercentageNegativeMinimum(); const float posMinPct = paletteColorMapping->getAutoScalePercentagePositiveMinimum(); const float posMaxPct = paletteColorMapping->getAutoScalePercentagePositiveMaximum(); minMax[0] = statistics->getApproxNegativePercentile(negMaxPct); minMax[1] = statistics->getApproxNegativePercentile(negMinPct); minMax[2] = statistics->getApproxPositivePercentile(posMinPct); minMax[3] = statistics->getApproxPositivePercentile(posMaxPct); } break; case PaletteScaleModeEnum::MODE_USER_SCALE: minMax[0] = paletteColorMapping->getUserScaleNegativeMaximum(); minMax[1] = paletteColorMapping->getUserScaleNegativeMinimum(); minMax[2] = paletteColorMapping->getUserScalePositiveMinimum(); minMax[3] = paletteColorMapping->getUserScalePositiveMaximum(); break; } textLeft = AString::number(minMax[0], 'f', 1); AString textCenterNeg = AString::number(minMax[1], 'f', 1); if (textCenterNeg == "-0.0") { textCenterNeg = "0.0"; } AString textCenterPos = AString::number(minMax[2], 'f', 1); textCenter = textCenterPos; if (isNegativeDisplayed && isPositiveDisplayed) { if (textCenterNeg != textCenterPos) { textCenter = textCenterNeg + "/" + textCenterPos; } } else if (isNegativeDisplayed) { textCenter = textCenterNeg; } else if (isPositiveDisplayed) { textCenter = textCenterPos; } textRight = AString::number(minMax[3], 'f', 1); } /* * Reset to the models viewport for drawing text. */ glViewport(modelViewport[0], modelViewport[1], modelViewport[2], modelViewport[3]); /* * Switch to the foreground color. */ glColor3fv(m_foregroundColorFloat); /* * Account for margin around colorbar when calculating text locations */ int textCenterX = colorbarViewportX - modelViewport[0] + (colorbarViewportWidth / 2); const int textLeftX = colorbarViewportX - modelViewport[0]; const int textRightX = (colorbarViewportX - modelViewport[0] + colorbarViewportWidth); if (isPositiveOnly) { textCenterX = textLeftX; } else if (isNegativeOnly) { textCenterX = textRightX; } const int textY = 2 + colorbarViewportY - modelViewport[1] + (colorbarViewportHeight / 2); if (isNegativeDisplayed) { this->drawTextWindowCoords(textLeftX, textY, textLeft, BrainOpenGLTextRenderInterface::X_LEFT, BrainOpenGLTextRenderInterface::Y_BOTTOM, BrainOpenGLTextRenderInterface::NORMAL, 12); } if (isNegativeDisplayed || isZeroDisplayed || isPositiveDisplayed) { BrainOpenGLTextRenderInterface::TextAlignmentX textAlignX = BrainOpenGLTextRenderInterface::X_CENTER; if (isNegativeOnly) { textAlignX = BrainOpenGLTextRenderInterface::X_RIGHT; } else if (isPositiveOnly) { textAlignX = BrainOpenGLTextRenderInterface::X_LEFT; } this->drawTextWindowCoords(textCenterX, textY, textCenter, textAlignX, BrainOpenGLTextRenderInterface::Y_BOTTOM, BrainOpenGLTextRenderInterface::NORMAL, 12); } if (isPositiveDisplayed) { this->drawTextWindowCoords(textRightX, textY, textRight, BrainOpenGLTextRenderInterface::X_RIGHT, BrainOpenGLTextRenderInterface::Y_BOTTOM, BrainOpenGLTextRenderInterface::NORMAL, 12); } return; } /** * @return A string containing the state of OpenGL (depth testing, lighting, etc.) */ AString BrainOpenGLFixedPipeline::getStateOfOpenGL() const { AString s = BrainOpenGL::getStateOfOpenGL(); s.appendWithNewLine("Fixed Pipeline State:"); s.appendWithNewLine(" " + getOpenGLEnabledEnumAsText("GL_BLEND", GL_BLEND)); s.appendWithNewLine(" " + getOpenGLEnabledEnumAsText("GL_CLIP_PLANE0", GL_CLIP_PLANE0)); s.appendWithNewLine(" " + getOpenGLEnabledEnumAsText("GL_CLIP_PLANE1", GL_CLIP_PLANE1)); s.appendWithNewLine(" " + getOpenGLEnabledEnumAsText("GL_CLIP_PLANE2", GL_CLIP_PLANE2)); s.appendWithNewLine(" " + getOpenGLEnabledEnumAsText("GL_CLIP_PLANE3", GL_CLIP_PLANE3)); s.appendWithNewLine(" " + getOpenGLEnabledEnumAsText("GL_CLIP_PLANE4", GL_CLIP_PLANE4)); s.appendWithNewLine(" " + getOpenGLEnabledEnumAsText("GL_CLIP_PLANE5", GL_CLIP_PLANE5)); s.appendWithNewLine(" " + getOpenGLEnabledEnumAsText("GL_COLOR_MATERIAL", GL_COLOR_MATERIAL)); s.appendWithNewLine(" " + getOpenGLEnabledEnumAsText("GL_CULL_FACE", GL_CULL_FACE)); s.appendWithNewLine(" " + getOpenGLEnabledEnumAsText("GL_DEPTH_TEST", GL_DEPTH_TEST)); s.appendWithNewLine(" " + getOpenGLBooleanAsText("GL_LIGHT_MODEL_LOCAL_VIEWER", GL_LIGHT_MODEL_LOCAL_VIEWER)); s.appendWithNewLine(" " + getOpenGLBooleanAsText("GL_LIGHT_MODEL_TWO_SIDE", GL_LIGHT_MODEL_TWO_SIDE)); s.appendWithNewLine(" " + getOpenGLEnabledEnumAsText("GL_LIGHTING", GL_LIGHTING)); s.appendWithNewLine(" " + getOpenGLEnabledEnumAsText("GL_LIGHT0", GL_LIGHT0)); s.appendWithNewLine(" " + getOpenGLEnabledEnumAsText("GL_LIGHT1", GL_LIGHT1)); s.appendWithNewLine(" " + getOpenGLEnabledEnumAsText("GL_LIGHT2", GL_LIGHT2)); s.appendWithNewLine(" " + getOpenGLEnabledEnumAsText("GL_LIGHT3", GL_LIGHT3)); s.appendWithNewLine(" " + getOpenGLEnabledEnumAsText("GL_LIGHT4", GL_LIGHT4)); s.appendWithNewLine(" " + getOpenGLEnabledEnumAsText("GL_LIGHT5", GL_LIGHT5)); s.appendWithNewLine(" " + getOpenGLLightAsText("GL_LIGHT0, GL_DIFFUSE", GL_LIGHT0, GL_DIFFUSE, 4)); s.appendWithNewLine(" " + getOpenGLLightAsText("GL_LIGHT0, GL_POSITION", GL_LIGHT0, GL_POSITION, 4)); s.appendWithNewLine(" " + getOpenGLLightAsText("GL_LIGHT1, GL_DIFFUSE", GL_LIGHT1, GL_DIFFUSE, 4)); s.appendWithNewLine(" " + getOpenGLLightAsText("GL_LIGHT1, GL_POSITION", GL_LIGHT1, GL_POSITION, 4)); s.appendWithNewLine(" " + getOpenGLFloatAsText("GL_LIGHT_MODEL_AMBIENT", GL_LIGHT_MODEL_AMBIENT, 4)); s.appendWithNewLine(" " + getOpenGLEnabledEnumAsText("GL_LINE_SMOOTH", GL_LINE_SMOOTH)); s.appendWithNewLine(" " + getOpenGLEnabledEnumAsText("GL_NORMALIZE", GL_NORMALIZE)); s.appendWithNewLine(" " + getOpenGLEnabledEnumAsText("GL_POLYGON_OFFSET_FILL", GL_POLYGON_OFFSET_FILL)); GLint frontFace; glGetIntegerv(GL_FRONT_FACE, &frontFace); AString frontFaceValue; switch (frontFace) { case GL_CW: frontFaceValue = "GL_CW"; break; case GL_CCW: frontFaceValue = "GL_CCW"; break; default: frontFaceValue = "INVALID"; break; } s.appendWithNewLine(" Front Face " + frontFaceValue); GLint cullFace; glGetIntegerv(GL_FRONT_FACE, &cullFace); AString cullFaceValue; switch (cullFace) { case GL_FRONT: cullFaceValue = "GL_FRONT"; break; case GL_BACK: cullFaceValue = "GL_BACK"; break; case GL_FRONT_AND_BACK: cullFaceValue = "GL_FRONT_AND_BACK"; break; default: cullFaceValue = "INVALID"; break; } s.appendWithNewLine(" Cull Face " + cullFaceValue); return s; } //============================================================================ /** * Constructor. */ BrainOpenGLFixedPipeline::VolumeDrawInfo::VolumeDrawInfo(CaretMappableDataFile* mapFile, VolumeMappableInterface* volumeFile, Brain* brain, PaletteColorMapping* paletteColorMapping, const FastStatistics* statistics, const WholeBrainVoxelDrawingMode::Enum wholeBrainVoxelDrawingMode, const int32_t mapIndex, const float opacity) : statistics(statistics) { this->mapFile = mapFile; this->volumeFile = volumeFile; this->brain = brain; this->paletteColorMapping = paletteColorMapping; this->wholeBrainVoxelDrawingMode = wholeBrainVoxelDrawingMode; this->mapIndex = mapIndex; this->opacity = opacity; } workbench-1.1.1/src/Brain/BrainOpenGLFixedPipeline.h000066400000000000000000000573401255417355300222600ustar00rootroot00000000000000 #ifndef __BRAIN_OPENGL_FIXED_PIPELINE_H__ #define __BRAIN_OPENGL_FIXED_PIPELINE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "BrainConstants.h" #include "BrainOpenGL.h" #include "BrainOpenGLTextRenderInterface.h" #include "CaretVolumeExtension.h" #include "DisplayGroupEnum.h" #include "FiberOrientationColoringTypeEnum.h" #include "FiberOrientationSymbolTypeEnum.h" #include "FiberTrajectoryColorModel.h" #include "ProjectionViewTypeEnum.h" #include "SelectionItemDataTypeEnum.h" #include "StructureEnum.h" #include "SurfaceNodeColoring.h" #include "VolumeSliceViewPlaneEnum.h" #include "WholeBrainVoxelDrawingMode.h" class QGLWidget; class QImage; namespace caret { class BoundingBox; class Brain; class BrainOpenGLShapeCone; class BrainOpenGLShapeCube; class BrainOpenGLShapeCylinder; class BrainOpenGLShapeSphere; class BrainOpenGLViewportContent; class BrowserTabContent; class CaretMappableDataFile; class ClippingPlaneGroup; class FastStatistics; class DisplayPropertiesFiberOrientation; class FiberOrientation; class SelectionItem; class SelectionManager; class IdentificationWithColor; class ImageFile; class Plane; class Surface; class Model; class ModelChart; class ModelSurface; class ModelSurfaceMontage; class ModelVolume; class ModelWholeBrain; class Palette; class PaletteColorMapping; class PaletteFile; class SurfaceFile; class SurfaceMontageConfigurationCerebellar; class SurfaceMontageConfigurationCerebral; class SurfaceMontageConfigurationFlatMaps; class VolumeFile; class VolumeMappableInterface; /** * Performs drawing of graphics using OpenGL. */ class BrainOpenGLFixedPipeline : public BrainOpenGL { private: enum Mode { MODE_DRAWING, MODE_IDENTIFICATION, MODE_PROJECTION }; BrainOpenGLFixedPipeline(const BrainOpenGLFixedPipeline&); BrainOpenGLFixedPipeline& operator=(const BrainOpenGLFixedPipeline&); public: BrainOpenGLFixedPipeline(BrainOpenGLTextRenderInterface* textRenderer); ~BrainOpenGLFixedPipeline(); void drawModels(std::vector& viewportContents); void selectModel(BrainOpenGLViewportContent* viewportContent, const int32_t mouseX, const int32_t mouseY, const bool applySelectionBackgroundFiltering); void projectToModel(BrainOpenGLViewportContent* viewportContent, const int32_t mouseX, const int32_t mouseY, SurfaceProjectedItem& projectionOut); void initializeOpenGL(); virtual AString getStateOfOpenGL() const; private: class VolumeDrawInfo { public: VolumeDrawInfo(CaretMappableDataFile* mapFile, VolumeMappableInterface* volumeFile, Brain* brain, PaletteColorMapping* paletteColorMapping, const FastStatistics* statistics, const WholeBrainVoxelDrawingMode::Enum wholeBrainVoxelDrawingMode, const int32_t mapIndex, const float opacity); Brain* brain; CaretMappableDataFile* mapFile; VolumeMappableInterface* volumeFile; SubvolumeAttributes::VolumeType volumeType; PaletteColorMapping* paletteColorMapping; WholeBrainVoxelDrawingMode::Enum wholeBrainVoxelDrawingMode; const FastStatistics* statistics; int32_t mapIndex; float opacity; }; struct FiberOrientationDisplayInfo { float aboveLimit; float belowLimit; FiberTrajectoryColorModel::Item* colorSource; FiberOrientationColoringTypeEnum::Enum fiberOrientationColorType; float fanMultiplier; bool isDrawWithMagnitude; float minimumMagnitude; float magnitudeMultiplier; Plane* plane; FiberOrientationSymbolTypeEnum::Enum symbolType; StructureEnum::Enum structure; }; void setFiberOrientationDisplayInfo(const DisplayPropertiesFiberOrientation* dpfo, const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, Plane* plane, const StructureEnum::Enum structure, FiberTrajectoryColorModel::Item* colorSource, FiberOrientationDisplayInfo& dispInfo); void drawModelInternal(Mode mode, BrainOpenGLViewportContent* viewportContent); void initializeMembersBrainOpenGL(); void drawChartData(BrowserTabContent* browserTabContent, ModelChart* chartData, const int32_t viewport[4]); void drawSurfaceModel(ModelSurface* surfaceModel, const int32_t viewport[4]); void drawSurface(Surface* surface, const float* nodeColoringRGBA); void drawSurfaceNodes(Surface* surface, const float* nodeColoringRGBA); void drawSurfaceTrianglesWithVertexArrays(const Surface* surface, const float* nodeColoringRGBA); void drawSurfaceTriangles(Surface* surface, const float* nodeColoringRGBA); void drawSurfaceNodeAttributes(Surface* surface); void drawSurfaceBorderBeingDrawn(const Surface* surface); void drawSurfaceBorders(Surface* surface); struct BorderDrawInfo { Surface* anatomicalSurface; Surface* surface; Border* border; int32_t borderFileIndex; int32_t borderIndex; float rgba[4]; bool isSelect; bool isContralateralEnabled; bool isHighlightEndPoints; float unstretchedLinesLength; }; void drawBorder(const BorderDrawInfo& borderDrawInfo); bool unstretchedBorderLineTest(const float p1[3], const float p2[3], const float anat1[3], const float anat2[3], const float unstretchedLinesFactor) const; void drawSurfaceFoci(Surface* surface); void drawSurfaceNormalVectors(const Surface* surface); void drawSurfaceFiberOrientations(const StructureEnum::Enum structure); void drawFiberOrientations(const Plane* plane, const StructureEnum::Enum structure); void addFiberOrientationForDrawing(const FiberOrientationDisplayInfo* fodi, const FiberOrientation* fiberOrientation); void sortFiberOrientationsByDepth(); void drawAllFiberOrientations(const FiberOrientationDisplayInfo* fodi, const bool isSortFibers); void drawSurfaceFiberTrajectories(const StructureEnum::Enum structure); void drawFiberTrajectories(const Plane* plane, const StructureEnum::Enum structure); void drawVolumeModel(BrowserTabContent* browserTabContent, ModelVolume* volumeModel, const int32_t viewport[4]); void drawVolumeAxesCrosshairs( const VolumeSliceViewPlaneEnum::Enum slicePlane, const float voxelXYZ[3]); void drawVolumeAxesLabels(const VolumeSliceViewPlaneEnum::Enum slicePlane, const int32_t viewport[4]); void drawVolumeVoxelsAsCubesWholeBrain(std::vector& volumeDrawInfoIn); void drawVolumeOrthogonalSliceWholeBrain(const VolumeSliceViewPlaneEnum::Enum slicePlane, const int64_t sliceIndex, std::vector& volumeDrawInfoIn); void drawVolumeOrthogonalSliceVolumeViewer(const VolumeSliceViewPlaneEnum::Enum slicePlane, const int64_t sliceIndex, std::vector& volumeDrawInfo); void drawVolumeSurfaceOutlines(Brain* brain, Model* modelDisplayModel, BrowserTabContent* browserTabContent, const VolumeSliceViewPlaneEnum::Enum slicePlane, const int64_t sliceIndex, VolumeMappableInterface* underlayVolume); void drawVolumeFoci(Brain* brain, ModelVolume* modelVolume, BrowserTabContent* browserTabContent, const VolumeSliceViewPlaneEnum::Enum slicePlane, const int64_t sliceIndex, VolumeMappableInterface* underlayVolume); void drawVolumeFibers(Brain* brain, ModelVolume* modelVolume, BrowserTabContent* browserTabContent, const VolumeSliceViewPlaneEnum::Enum slicePlane, const int64_t sliceIndex, VolumeMappableInterface* underlayVolume); void setupVolumeDrawInfo(BrowserTabContent* browserTabContent, Brain* brain, std::vector& volumeDrawInfoOut); void drawWholeBrainModel(BrowserTabContent* browserTabContent, ModelWholeBrain* wholeBrainModel, const int32_t viewport[4]); void drawSurfaceMontageModel(BrowserTabContent* browserTabContent, ModelSurfaceMontage* surfaceMontageModel, const int32_t viewport[4]); void setOrthographicProjection(const int32_t viewport[4], const ProjectionViewTypeEnum::Enum projectionType); void setOrthographicProjectionForWithBoundingBox(const int32_t viewport[4], const ProjectionViewTypeEnum::Enum projectionType, const BoundingBox* boundingBox); void setOrthographicProjectionWithHeight(const int32_t viewport[4], const ProjectionViewTypeEnum::Enum projectionType, const float halfWindowHeight); void checkForOpenGLError(const Model* modelModel, const AString& msg); void enableLighting(); void disableLighting(); void enableLineAntiAliasing(); void disableLineAntiAliasing(); void getIndexFromColorSelection(const SelectionItemDataTypeEnum::Enum dataType, const int32_t x, const int32_t y, int32_t& indexOut, float& depthOut); void getIndexFromColorSelection(const SelectionItemDataTypeEnum::Enum dataType, const int32_t x, const int32_t y, int32_t& index1Out, int32_t& index2Out, float& depthOut); void getIndexFromColorSelection(const SelectionItemDataTypeEnum::Enum dataType, const int32_t x, const int32_t y, int32_t& index1Out, int32_t& index2Out, int32_t& index3Out, float& depthOut); void setSelectedItemScreenXYZ(SelectionItem* item, const float itemXYZ[3]); void setViewportAndOrthographicProjection(const int32_t viewport[4], const ProjectionViewTypeEnum::Enum projectionType); void setViewportAndOrthographicProjectionForWholeBrainVolume(const int32_t viewport[4], const ProjectionViewTypeEnum::Enum projectionType, const VolumeMappableInterface* volume); void setViewportAndOrthographicProjectionForSurfaceFile(const int32_t viewport[4], const ProjectionViewTypeEnum::Enum projectionType, const SurfaceFile* surfaceFile); void applyViewingTransformations(const float objectCenterXYZ[3], const ProjectionViewTypeEnum::Enum projectionViewType); void applyViewingTransformationsVolumeSlice(const ModelVolume* modelVolume, const int32_t tabIndex, const VolumeSliceViewPlaneEnum::Enum viewPlane); void drawSurfaceAxes(); void drawSphereWithDiameter(const uint8_t rgba[4], const double diameter); void drawSphereWithDiameter(const float rgba[4], const double diameter); void drawSquare(const float rgba[4], const float size); void drawSquare(const uint8_t rgba[4], const float size); void drawCube(const float rgba[4], const double cubeSize); void drawCuboid(const uint8_t rgba[4], const double sizeX, const double sizeY, const double sizeZ); void drawRoundedCube(const float rgba[4], const double cubeSize); void drawRoundedCuboid(const uint8_t rgba[4], const double sizeX, const double sizeY, const double sizeZ); void drawCylinder(const float rgba[4], const float bottomXYZ[3], const float topXYZ[3], const float radius); void drawEllipticalCone(const float rgba[4], const float baseXYZ[3], const float apexXYZ[3], const float baseRadiusScaling, const float baseMajorAngle, const float baseMinorAngle, const float baseRotationAngle, const bool backwardsFlag); void drawTextWindowCoords(const int windowX, const int windowY, const QString& text, const BrainOpenGLTextRenderInterface::TextAlignmentX alignmentX, const BrainOpenGLTextRenderInterface::TextAlignmentY alignmentY, const BrainOpenGLTextRenderInterface::TextStyle textStyle, const int fontHeight); void drawTextWindowCoordsWithBackground(const int windowX, const int windowY, const QString& text, const BrainOpenGLTextRenderInterface::TextAlignmentX alignmentX, const BrainOpenGLTextRenderInterface::TextAlignmentY alignmentY, const BrainOpenGLTextRenderInterface::TextStyle textStyle, const int fontHeight); void drawTextModelCoords(const double modelX, const double modelY, const double modelZ, const QString& text); void drawTextModelCoords(const double modelXYZ[3], const QString& text); void drawAllPalettes(Brain* brain); void drawPalette(const Palette* palette, const PaletteColorMapping* paletteColorMapping, const FastStatistics* statistics, const int paletteDrawingIndex); void drawBackgroundImage(BrainOpenGLViewportContent* vpContent); void drawImage(const int viewport[4], const ImageFile* imageFile); void setProjectionModeData(const float screenDepth, const float xyz[3], const StructureEnum::Enum structure, const float barycentricAreas[3], const int barycentricNodes[3], const int numberOfNodes); void setLineWidth(const float lineWidth); void setPointSize(const float pointSize); enum ClippingDataType { CLIPPING_DATA_TYPE_FEATURES, CLIPPING_DATA_TYPE_SURFACE, CLIPPING_DATA_TYPE_VOLUME }; void applyClippingPlanes(const ClippingDataType clippingDataType, const StructureEnum::Enum structureIn); void disableClippingPlanes(); bool isCoordinateInsideClippingPlanesForStructure(const StructureEnum::Enum structureIn, const float xyz[3]) const; bool isFeatureClippingEnabled() const; void getVolumeFitToWindowScalingAndTranslation(const VolumeMappableInterface* volume, const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const double orthographicExtent[6], float translationOut[3], float& scalingOut) const; Plane getPlaneForVolumeSliceIndex(const VolumeMappableInterface* volumeMappable, const VolumeSliceViewPlaneEnum::Enum slicePlane, const int64_t sliceIndex) const; void updateForegroundAndBackgroundColors(BrainOpenGLViewportContent* vpContent); /** Indicates OpenGL has been initialized */ bool initializedOpenGLFlag; /** Content of browser tab being drawn */ BrowserTabContent* browserTabContent; /** Source brain of content being drawn DOES NOT get deleted! */ Brain* m_brain; /** Index of window tab */ int32_t windowTabIndex; /** mode of operation draw/select/etc*/ Mode mode; /** Identification manager */ SelectionManager* selectionManager; int32_t mouseX; int32_t mouseY; /** Clipping plane group active in browser tab */ ClippingPlaneGroup* m_clippingPlaneGroup; /** * When mirrored clipping is enabled, the clipping region is * 'mirror flipped' for right structures and clipping performed * separately for left and right structures. Otherwise, one * clipping is used for all data. * * Model Enabled * ----- ------- * All NO * Montage YES * Surface YES * Volume NO */ bool m_mirroredClippingEnabled; /** Identify using color */ IdentificationWithColor* colorIdentification; SurfaceProjectedItem* modeProjectionData; /** Screen depth when projecting to surface mode */ double modeProjectionScreenDepth; /** Performs node coloring */ SurfaceNodeColoring* surfaceNodeColoring; /** Sphere symbol */ BrainOpenGLShapeSphere* m_shapeSphere; /** Cone symbol */ BrainOpenGLShapeCone* m_shapeCone; /** Cube symbol */ BrainOpenGLShapeCube* m_shapeCube; /** Rounded Cube symbol */ BrainOpenGLShapeCube* m_shapeCubeRounded; /** Cylinder symbol */ BrainOpenGLShapeCylinder* m_shapeCylinder; std::list m_fiberOrientationsForDrawing; double inverseRotationMatrix[16]; bool inverseRotationMatrixValid; double orthographicLeft; double orthographicRight; double orthographicBottom; double orthographicTop; double orthographicFar; double orthographicNear; static bool s_staticInitialized; static float COLOR_RED[3]; static float COLOR_GREEN[3]; static float COLOR_BLUE[3]; friend class BrainOpenGLChartDrawingFixedPipeline; friend class BrainOpenGLVolumeObliqueSliceDrawing; friend class BrainOpenGLVolumeSliceDrawing; friend class OldBrainOpenGLVolumeSliceDrawing; }; #ifdef __BRAIN_OPENGL_FIXED_PIPELINE_DEFINE_H bool BrainOpenGLFixedPipeline::s_staticInitialized = false; float BrainOpenGLFixedPipeline::COLOR_RED[3] = { 1.0, 0.0, 0.0 }; float BrainOpenGLFixedPipeline::COLOR_GREEN[3] = { 0.0, 1.0, 0.0 }; float BrainOpenGLFixedPipeline::COLOR_BLUE[3] = { 0.0, 0.0, 1.0 }; #endif //__BRAIN_OPENGL_FIXED_PIPELINE_DEFINE_H } // namespace #endif // __BRAIN_OPENGL_FIXED_PIPELINE_H__ workbench-1.1.1/src/Brain/BrainOpenGLPrimitiveDrawing.cxx000066400000000000000000000473541255417355300233760ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __BRAIN_OPEN_G_L_PRIMITIVE_DRAWING_DECLARE__ #include "BrainOpenGLPrimitiveDrawing.h" #undef __BRAIN_OPEN_G_L_PRIMITIVE_DRAWING_DECLARE__ #include "BrainOpenGL.h" #include "CaretOpenGLInclude.h" #include "CaretAssert.h" #include "CaretLogger.h" #include "AString.h" using namespace caret; /** * \class caret::BrainOpenGLPrimitiveDrawing * \brief Draws OpenGL Primitives (Triangles, Quads, etc.) * \ingroup Brain * * As OpenGL has evolved, functionality for drawing primitives (triangles, quads, * etc.) has been added. Users of this class only need to provide the * vertices, colors, normal vectors, and type of primitive for drawing. This * class will use the OpenGL primitive drawing most appropriate for the * OpenGL capabilities available at runtime. * * All public methods are static methods. */ /** * Constructor. */ BrainOpenGLPrimitiveDrawing::BrainOpenGLPrimitiveDrawing() { } /** * Destructor. */ BrainOpenGLPrimitiveDrawing::~BrainOpenGLPrimitiveDrawing() { } /** * Draw quads. * * @param coordinates * Coordinates of the quads. * @param normals * Normal vectors for the quads. * @param rgbaColors * RGBA colors for the quads. */ void BrainOpenGLPrimitiveDrawing::drawQuads(const std::vector& coordinates, const std::vector& normals, const std::vector& rgbaColors) { const uint64_t numCoords = coordinates.size() / 3; const uint64_t numNormals = normals.size() / 3; const uint64_t numColors = rgbaColors.size() / 4; const uint64_t numQuads = numCoords / 4; // 4 three-d coords per quad if (numQuads <= 0) { return; } if (numNormals != numCoords) { const AString message = ("Size of normals must equal size of coordinates. " "Coordinate size: " + AString::number(coordinates.size()) + " Normals size: " + AString::number(normals.size())); CaretLogSevere(message); CaretAssertMessage(0, message); return; } if (numColors != numCoords) { const AString message = ("Size of RGBA colors must be 4/3 size of coordinates. " "Coordinate size: " + AString::number(coordinates.size()) + " RGBA size: " + AString::number(rgbaColors.size())); CaretLogSevere(message); CaretAssertMessage(0, message); return; } /* * Use the number of voxels in a 300 x 300 slice as * as the most maximum number of voxels to draw in * in one call to OpenGL drawing. */ const int64_t maximumCoordinatesToDraw = 300 * 300 * 4; CaretAssert(maximumCoordinatesToDraw == ((maximumCoordinatesToDraw / 4) * 4)); //std::cout << "Max vertices to draw: " << maximumCoordinatesToDraw << std::endl; int64_t coordinateOffset = 0; bool done = false; while ( ! done) { const int64_t coordinateToDrawCount = std::min(int64_t(numCoords - coordinateOffset), maximumCoordinatesToDraw); if (coordinateToDrawCount > 0) { //std::cout << "Drawing offset " << coordinateOffset << " count " << coordinateToDrawCount << std::endl; bool wasDrawnWithVertexBuffers = false; #ifdef BRAIN_OPENGL_INFO_SUPPORTS_VERTEX_BUFFERS if (BrainOpenGL::getBestDrawingMode() == BrainOpenGL::DRAW_MODE_VERTEX_BUFFERS) { CaretAssertMessage(0, "See John Harwell"); drawQuadsVertexBuffers(coordinates, normals, rgbaColors, coordinateOffset, coordinateToDrawCount); wasDrawnWithVertexBuffers = true; } #endif // BRAIN_OPENGL_INFO_SUPPORTS_VERTEX_BUFFERS if ( ! wasDrawnWithVertexBuffers) { drawQuadsVertexArrays(coordinates, normals, rgbaColors, coordinateOffset, coordinateToDrawCount); // drawQuadsImmediateMode(coordinates, // normals, // rgbaColors, // coordinateOffset, // coordinateToDrawCount); } coordinateOffset += maximumCoordinatesToDraw; } else { done = true; } } } /** * Draw quads using immediate mode. * * @param coordinates * Coordinates of the quads. * @param normals * Normal vectors for the quads. * @param rgbaColors * RGBA colors for the quads. */ void BrainOpenGLPrimitiveDrawing::drawQuadsImmediateMode(const std::vector& coordinates, const std::vector& normals, const std::vector& rgbaColors, const int64_t coordinateOffset, const int64_t coordinateCount) { // const uint64_t numCoords = coordinates.size() / 3; // const uint64_t numQuads = numCoords / 4; // 4 three-d coords per quad const float* coordPtr = &coordinates[0]; const float* normalPtr = &normals[0]; const uint8_t* colorPtr = &rgbaColors[0]; CaretAssertVectorIndex(coordinates, ((coordinateOffset * 3) + (coordinateCount * 3) - 1)); CaretAssertVectorIndex(normals, ((coordinateOffset * 3) + (coordinateCount * 3) - 1)); CaretAssertVectorIndex(rgbaColors, ((coordinateOffset * 4) + (coordinateCount * 4) - 1)); glBegin(GL_QUADS); uint64_t iColor = coordinateOffset * 4; uint64_t iNormal = coordinateOffset * 3; uint64_t iCoord = coordinateOffset * 3; for (int64_t i = 0; i < coordinateCount; i++) { glNormal3fv(&normalPtr[iNormal]); iNormal += 3; glColor4ubv(&colorPtr[iColor]); iColor += 4; glVertex3fv(&coordPtr[iCoord]); iCoord += 3; } glEnd(); // for (uint64_t i = 0; i < numQuads; i++) { // glNormal3fv(&normalPtr[iNormal]); // iNormal += 3; // glColor4ubv(&colorPtr[iColor]); // iColor += 4; // glVertex3fv(&coordPtr[iCoord]); // iCoord += 3; // // glNormal3fv(&normalPtr[iNormal]); // iNormal += 3; // glColor4ubv(&colorPtr[iColor]); // iColor += 4; // glVertex3fv(&coordPtr[iCoord]); // iCoord += 3; // // glNormal3fv(&normalPtr[iNormal]); // iNormal += 3; // glColor4ubv(&colorPtr[iColor]); // iColor += 4; // glVertex3fv(&coordPtr[iCoord]); // iCoord += 3; // // glNormal3fv(&normalPtr[iNormal]); // iNormal += 3; // glColor4ubv(&colorPtr[iColor]); // iColor += 4; // glVertex3fv(&coordPtr[iCoord]); // iCoord += 3; // } // glEnd(); } /** * Draw quads using vertex arrays. * * @param coordinates * Coordinates of the quads. * @param normals * Normal vectors for the quads. * @param rgbaColors * RGBA colors for the quads. */ void BrainOpenGLPrimitiveDrawing::drawQuadsVertexArrays(const std::vector& coordinates, const std::vector& normals, const std::vector& rgbaColors, const int64_t coordinateOffset, const int64_t coordinateCount) { glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_COLOR_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); glVertexPointer(3, GL_FLOAT, 0, reinterpret_cast(&coordinates[0])); glColorPointer(4, GL_UNSIGNED_BYTE, 0, reinterpret_cast(&rgbaColors[0])); glNormalPointer(GL_FLOAT, 0, reinterpret_cast(&normals[0])); // const int64_t maxVertices = 1000; // const int64_t numQuads = maxVertices / 4; // CaretAssert(maxVertices == (numQuads * 4)); // if (numCoords > maxVertices) { // std::cout << "Max vertices: " << maxVertices << std::endl; // int64_t offset = 0; // bool done = false; // while ( ! done) { // const int64_t count = std::min(int64_t(numCoords - offset), // maxVertices); // if (count > 0) { // std::cout << "Drawing offset " << offset << " count " << count << std::endl; // glDrawArrays(GL_QUADS, // offset, // count); // offset += maxVertices; // } // else { // done = true; // } // } // } // else { // glDrawArrays(GL_QUADS, // 0, // numCoords); // } glDrawArrays(GL_QUADS, coordinateOffset, coordinateCount); glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_COLOR_ARRAY); glDisableClientState(GL_NORMAL_ARRAY); } /** * Draw quads using vertex buffers. * * @param coordinates * Coordinates of the quads. * @param normals * Normal vectors for the quads. * @param rgbaColors * RGBA colors for the quads. */ void BrainOpenGLPrimitiveDrawing::drawQuadsVertexBuffers(const std::vector& coordinates, const std::vector& normals, const std::vector& rgbaColors, const int64_t coordinateOffset, const int64_t coordinateCount) { #ifdef BRAIN_OPENGL_INFO_SUPPORTS_VERTEX_BUFFERS /* * Put vertices (coordinates) into its buffer. */ GLuint vertexBufferID = -1; glGenBuffers(1, &vertexBufferID); glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID); glBufferData(GL_ARRAY_BUFFER, coordinates.size() * sizeof(GLfloat), &coordinates[0], GL_STREAM_DRAW); /* * Put normals into its buffer. */ GLuint normalBufferID = -1; glGenBuffers(1, &normalBufferID); glBindBuffer(GL_ARRAY_BUFFER, normalBufferID); glBufferData(GL_ARRAY_BUFFER, normals.size() * sizeof(GLfloat), &normals[0], GL_STREAM_DRAW); /* * Put colors into its buffer. */ GLuint colorBufferID = -1; glGenBuffers(1, &colorBufferID); glBindBuffer(GL_ARRAY_BUFFER, colorBufferID); glBufferData(GL_ARRAY_BUFFER, rgbaColors.size() * sizeof(uint8_t), &rgbaColors[0], GL_STREAM_DRAW); // /* // * Put triangle strips into its buffer. // */ // GLuint quadsBufferID = -1; // glGenBuffers(1, &quadsBufferID); // glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, // quadsBufferID); // glBufferData(GL_ELEMENT_ARRAY_BUFFER, // quads.size() * sizeof(GLuint), // &quads[0], // GL_STREAM_DRAW); /* * Deselect active buffer. */ glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_COLOR_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); /* * Set the vertices for drawing. */ glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID); glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)0); /* * Set the normal vectors for drawing. */ glBindBuffer(GL_ARRAY_BUFFER, normalBufferID); glNormalPointer(GL_FLOAT, 0, (GLvoid*)0); /* * Set the rgba colors for drawing */ glBindBuffer(GL_ARRAY_BUFFER, colorBufferID); glColorPointer(4, GL_UNSIGNED_BYTE, 0, (GLvoid*)0); /* * Draw the triangle strips. */ glDrawArrays(GL_QUADS, coordinateOffset, coordinateCount); // glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, // quadsBufferID); // glDrawElements(GL_TRIANGLE_STRIP, // quads.size(), // GL_UNSIGNED_INT, // (GLvoid*)0); /* * Deselect active buffer. */ glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_COLOR_ARRAY); glDisableClientState(GL_NORMAL_ARRAY); glDeleteBuffers(1, &vertexBufferID); glDeleteBuffers(1, &normalBufferID); glDeleteBuffers(1, &colorBufferID); #endif // BRAIN_OPENGL_INFO_SUPPORTS_VERTEX_BUFFERS } /** * Draw quads strips * * @param coordinates * Coordinates of the quads. * @param normals * Normal vectors for the quads. * @param rgbaColors * RGBA colors for the quads. * @param quadStripIndices * Indices into the quad strip coordinates. */ void BrainOpenGLPrimitiveDrawing::drawQuadStrips(const std::vector& coordinates, const std::vector& normals, const std::vector& rgbaColors, const std::vector& quadStripIndices) { drawQuadStripsVertexArrays(coordinates, normals, rgbaColors, quadStripIndices); } /** * Draw quads strips * * @param coordinates * Coordinates of the quads. * @param normals * Normal vectors for the quads. * @param rgbaColors * RGBA colors for the quads. * @param quadStripIndices * Indices into the quad strip coordinates. */ void BrainOpenGLPrimitiveDrawing::drawQuadStripsVertexArrays(const std::vector& coordinates, const std::vector& normals, const std::vector& rgbaColors, const std::vector& quadStripIndices) { if (quadStripIndices.empty()) { return; } glVertexPointer(3, GL_FLOAT, 0, reinterpret_cast(&coordinates[0])); glColorPointer(4, GL_UNSIGNED_BYTE, 0, reinterpret_cast(&rgbaColors[0])); glNormalPointer(GL_FLOAT, 0, reinterpret_cast(&normals[0])); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_COLOR_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); glDrawElements(GL_QUAD_STRIP, quadStripIndices.size(), GL_UNSIGNED_INT, &quadStripIndices[0]); glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_COLOR_ARRAY); glDisableClientState(GL_NORMAL_ARRAY); } /** * Draw quad with indices * * @param coordinates * Coordinates of the quads. * @param normals * Normal vectors for the quads. * @param rgbaColors * RGBA colors for the quads. * @param quadIndices * Indices of the quads (4 per quad). */ void BrainOpenGLPrimitiveDrawing::drawQuadIndices(const std::vector& coordinates, const std::vector& normals, const std::vector& rgbaColors, const std::vector& quadIndices) { drawQuadIndicesVertexArrays(coordinates, normals, rgbaColors, quadIndices); } /** * Draw quad with indices * * @param coordinates * Coordinates of the quads. * @param normals * Normal vectors for the quads. * @param rgbaColors * RGBA colors for the quads. * @param quadIndices * Indices of the quads (4 per quad). */ void BrainOpenGLPrimitiveDrawing::drawQuadIndicesVertexArrays(const std::vector& coordinates, const std::vector& normals, const std::vector& rgbaColors, const std::vector& quadIndices) { const int64_t numQuadIndices = static_cast(quadIndices.size()); if (numQuadIndices <= 0) { return; } glVertexPointer(3, GL_FLOAT, 0, reinterpret_cast(&coordinates[0])); glColorPointer(4, GL_UNSIGNED_BYTE, 0, reinterpret_cast(&rgbaColors[0])); glNormalPointer(GL_FLOAT, 0, reinterpret_cast(&normals[0])); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_COLOR_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); glDrawElements(GL_QUADS, quadIndices.size(), GL_UNSIGNED_INT, &quadIndices[0]); glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_COLOR_ARRAY); glDisableClientState(GL_NORMAL_ARRAY); } workbench-1.1.1/src/Brain/BrainOpenGLPrimitiveDrawing.h000066400000000000000000000102051255417355300230040ustar00rootroot00000000000000#ifndef __BRAIN_OPEN_G_L_PRIMITIVE_DRAWING_H__ #define __BRAIN_OPEN_G_L_PRIMITIVE_DRAWING_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "stdint.h" #include namespace caret { class BrainOpenGLPrimitiveDrawing { public: static void drawQuads(const std::vector& coordinates, const std::vector& normals, const std::vector& rgbaColors); static void drawQuadIndices(const std::vector& coordinates, const std::vector& normals, const std::vector& rgbaColors, const std::vector& quadIndices); static void drawQuadStrips(const std::vector& coordinates, const std::vector& normals, const std::vector& rgbaColors, const std::vector& quadStripIndices); private: BrainOpenGLPrimitiveDrawing(); ~BrainOpenGLPrimitiveDrawing(); BrainOpenGLPrimitiveDrawing(const BrainOpenGLPrimitiveDrawing&); BrainOpenGLPrimitiveDrawing& operator=(const BrainOpenGLPrimitiveDrawing&); static void drawQuadsImmediateMode(const std::vector& coordinates, const std::vector& normals, const std::vector& rgbaColors, const int64_t coordinateOffset, const int64_t coordinateCount); static void drawQuadsVertexArrays(const std::vector& coordinates, const std::vector& normals, const std::vector& rgbaColors, const int64_t coordinateOffset, const int64_t coordinateCount); static void drawQuadStripsVertexArrays(const std::vector& coordinates, const std::vector& normals, const std::vector& rgbaColors, const std::vector& quadStripIndices); static void drawQuadsVertexBuffers(const std::vector& coordinates, const std::vector& normals, const std::vector& rgbaColors, const int64_t coordinateOffset, const int64_t coordinateCount); static void drawQuadIndicesVertexArrays(const std::vector& coordinates, const std::vector& normals, const std::vector& rgbaColors, const std::vector& quadIndices); }; #ifdef __BRAIN_OPEN_G_L_PRIMITIVE_DRAWING_DECLARE__ // #endif // __BRAIN_OPEN_G_L_PRIMITIVE_DRAWING_DECLARE__ } // namespace #endif //__BRAIN_OPEN_G_L_PRIMITIVE_DRAWING_H__ workbench-1.1.1/src/Brain/BrainOpenGLShape.cxx000066400000000000000000000416201255417355300211400ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __BRAIN_OPEN_GL_SHAPE_DECLARE__ #include "BrainOpenGLShape.h" #undef __BRAIN_OPEN_GL_SHAPE_DECLARE__ #include "BrainOpenGL.h" #include "CaretAssert.h" #include "CaretLogger.h" using namespace caret; /** * \class caret::BrainOpenGLShape * \brief Abstract class for shapes drawn in OpenGL. * * Subclasses should allocate the */ /** * Constructor. */ BrainOpenGLShape::BrainOpenGLShape() : CaretObject() { m_drawMode = BrainOpenGL::DRAW_MODE_INVALID; m_shapeSetupComplete = false; } /** * Destructor. */ BrainOpenGLShape::~BrainOpenGLShape() { releaseOpenGLAllocations(); } void BrainOpenGLShape::releaseOpenGLAllocations() { /* * Since 'releaseBufferIDInternal()' will alter 'm_bufferIDs' * make a copy of the set. Otherwise, the iterator will get * corrupted and a crash will occur. */ std::set bufferIDCopy = m_bufferIDs; for (std::set::iterator iter = bufferIDCopy.begin(); iter != bufferIDCopy.end(); iter++) { releaseBufferIDInternal(*iter); } m_bufferIDs.clear(); /* * Since 'releaseDisplayListInternal()' will alter 'm_displayLists' * make a copy of the set. Otherwise, the iterator will get * corrupted and a crash will occur. */ std::set displayListsCopy = m_displayLists; for (std::set::iterator iter = displayListsCopy.begin(); iter != displayListsCopy.end(); iter++) { releaseDisplayListInternal(*iter); } m_displayLists.clear(); } /** * Force drawing mode to immediate mode since display lists * do not work during image capture. * @param override * If true, immediate mode is always used until reset by * calling this with false. */ void BrainOpenGLShape::setImmediateModeOverride(const bool override) { s_immediateModeOverride = override; } /** * Draw the shape. * * @param rgba * Color for shape. */ void BrainOpenGLShape::draw(const uint8_t rgba[4]) { createShapeIfNeeded(); if (s_immediateModeOverride) { drawShape(BrainOpenGL::DRAW_MODE_IMMEDIATE, rgba); } else { drawShape(m_drawMode, rgba); } } /** * Create the shape or recreate it if drawing mode changed. */ void BrainOpenGLShape::createShapeIfNeeded() { if (m_drawMode == BrainOpenGL::DRAW_MODE_INVALID) { m_shapeSetupComplete = false; } if (m_drawMode != BrainOpenGL::getBestDrawingMode()) { m_shapeSetupComplete = false; } if (m_shapeSetupComplete == false) { releaseOpenGLAllocations(); m_drawMode = BrainOpenGL::getBestDrawingMode(); setupOpenGLForShape(m_drawMode); m_shapeSetupComplete = true; } } /** * Draw the shape. * * @param rgba * Color for shape. */ void BrainOpenGLShape::draw(const float rgba[4]) { createShapeIfNeeded(); if (s_immediateModeOverride) { drawShape(BrainOpenGL::DRAW_MODE_IMMEDIATE, rgba); } else { drawShape(m_drawMode, rgba); } } /** * @return A new buffer ID for use with OpenGL. * A return value of zero indicates that creation of buffer ID failed. * Values greater than zero are valid buffer IDs. */ GLuint BrainOpenGLShape::createBufferID() { GLuint id = 0; #ifdef BRAIN_OPENGL_INFO_SUPPORTS_VERTEX_BUFFERS glGenBuffers(1, &id); if (id > 0) { m_bufferIDs.insert(id); } else { CaretLogSevere("Failed to create a new OpenGL Vertex Buffer"); } #else // BRAIN_OPENGL_INFO_SUPPORTS_VERTEX_BUFFERS CaretLogSevere("PROGRAM ERROR: Creating OpenGL vertex buffer but vertex buffers not supported."); #endif // BRAIN_OPENGL_INFO_SUPPORTS_VERTEX_BUFFERS return id; } /** * Release an allocated buffer ID. * * @param bufferID * Buffer ID that was previously returned by createBufferID(). */ void BrainOpenGLShape::releaseBufferID(const GLuint bufferID) { releaseBufferIDInternal(bufferID); } /** * Release an allocated buffer ID. * * @param bufferID * Buffer ID that was previously returned by createBufferID(). */ void BrainOpenGLShape::releaseBufferIDInternal(const GLuint bufferID) { #ifdef BRAIN_OPENGL_INFO_SUPPORTS_VERTEX_BUFFERS if (glIsBuffer(bufferID)) { glDeleteBuffers(1, &bufferID); m_bufferIDs.erase(bufferID); } else { CaretLogSevere("PROGRAM ERROR: Attempting to delete invalid OpenGL BufferID=" + AString::number(bufferID)); } #else // BRAIN_OPENGL_INFO_SUPPORTS_VERTEX_BUFFERS CaretLogSevere("PROGRAM ERROR: Releasing OpenGL vertex buffer #" + AString::number(bufferID) + " but vertex buffers not supported."); #endif // BRAIN_OPENGL_INFO_SUPPORTS_VERTEX_BUFFERS } /** * Release an allocated buffer ID. * * @param bufferID * Buffer ID that was previously returned by createBufferID(). * @param isRemoveFromTrackedIDs * If true, remove the ID from the bufferID that are tracked * by this object. */ void BrainOpenGLShape::releaseDisplayListInternal(const GLuint displayList) { #ifdef BRAIN_OPENGL_INFO_SUPPORTS_DISPLAY_LISTS if (BrainOpenGL::isDisplayListsSupported()) { if (glIsList(displayList)) { glDeleteLists(displayList, 1); m_displayLists.erase(displayList); } else { CaretLogSevere("PROGRAM ERROR: Attempting to delete invalid OpenGL DisplayList=" + AString::number(displayList)); } } #else // BRAIN_OPENGL_INFO_SUPPORTS_DISPLAY_LISTS CaretLogSevere("PROGRAM ERROR: Releasing OpenGL display list #" + AString::number(displayList) + " but display lists not supported."); #endif // BRAIN_OPENGL_INFO_SUPPORTS_DISPLAY_LISTS } /** * @return A new display list for use with OpenGL. * A return value of zero indicates that creation of display list failed. * Values greater than zero are valid display list. */ GLuint BrainOpenGLShape::createDisplayList() { GLuint displayList = 0; #ifdef BRAIN_OPENGL_INFO_SUPPORTS_DISPLAY_LISTS if (BrainOpenGL::isDisplayListsSupported()) { displayList = glGenLists(1); if (displayList > 0) { m_displayLists.insert(displayList); } else { CaretLogSevere("Failed to create a new OpenGL Display List"); } } #else // BRAIN_OPENGL_INFO_SUPPORTS_DISPLAY_LISTS CaretLogSevere("PROGRAM ERROR: Creating OpenGL display list but display lists not supported."); #endif // BRAIN_OPENGL_INFO_SUPPORTS_DISPLAY_LISTS return displayList; } /** * Release an allocated display list. * * @param displayList * Display list that was previously returned by createDisplayList(). */ void BrainOpenGLShape::releaseDisplayList(const GLuint displayList) { releaseDisplayListInternal(displayList); } /** * Convert a series of discontinuous triangle strips into one triangle strip. * Between each of the original triangle strips, additional vertices forming * degenerate (arealess) triangles are added. This triangles are not drawn * by OpenGl. * * @see http://www.gamedev.net/page/resources/_/technical/graphics-programming-and-theory/concatenating-triangle-strips-r1871 * @see http://en.wikipedia.org/wiki/Triangle_strip * * @param vertices * The indices to all of the vertices in the input triangle strips. * @param stripStartIndices * A vector containing the starting index of the strip in 'vertices'. * @param stripEndIndices * A vector containing the ending index of the strip in 'vertices'. * @param triangleStripVerticesOut * OUTPUT - A vector containing all of the vertex indices for drawing * all of the input triangles with one triangle strip. */ void BrainOpenGLShape::contatenateTriangleStrips(const std::vector& vertices, const std::vector& stripStartIndices, const std::vector& stripEndIndices, std::vector& triangleStripVerticesOut) const { triangleStripVerticesOut.clear(); CaretAssert(stripStartIndices.size() == stripEndIndices.size()); const int32_t numberOfStrips = static_cast(stripStartIndices.size()); for (int32_t i = 0; i < numberOfStrips; i++) { const int32_t numInStrip = (stripEndIndices[i] - stripStartIndices[i]); if (numInStrip < 2) { continue; } const int32_t startIndex = stripStartIndices[i]; CaretAssertVectorIndex(vertices, startIndex); //const int32_t endIndex = stripEndIndices[i]; //CaretAssertVectorIndex(vertices, endIndex); if (i > 0) { /* * N1 is index of first vertex in the strip * N2 is index of second vertex in the strip */ const GLuint n1 = vertices[startIndex]; CaretAssertVectorIndex(vertices, startIndex+1); const GLuint n2 = vertices[startIndex + 1]; const GLuint prevEndIndex = stripEndIndices[i-1]; const int32_t numInPrevStrip = (prevEndIndex - stripStartIndices[i-1]); if (numInPrevStrip >= 2) { /* * P1 is index of last vertex in previous strip * P2 is index of second to last vertex in previous strip */ CaretAssertVectorIndex(vertices, ((int32_t)prevEndIndex) - 1); const GLuint p1 = vertices[prevEndIndex - 1]; CaretAssertVectorIndex(vertices, ((int32_t)prevEndIndex) - 2); const GLuint p2 = vertices[prevEndIndex - 2]; /* * Add degenerate triangles so that strips can be concatenated. */ triangleStripVerticesOut.push_back(p2); triangleStripVerticesOut.push_back(p1); triangleStripVerticesOut.push_back(p1); triangleStripVerticesOut.push_back(p1); triangleStripVerticesOut.push_back(p1); triangleStripVerticesOut.push_back(n1); triangleStripVerticesOut.push_back(p1); triangleStripVerticesOut.push_back(n1); triangleStripVerticesOut.push_back(n1); triangleStripVerticesOut.push_back(n1); triangleStripVerticesOut.push_back(n1); triangleStripVerticesOut.push_back(n2); triangleStripVerticesOut.push_back(n1); triangleStripVerticesOut.push_back(n2); triangleStripVerticesOut.push_back(n1); triangleStripVerticesOut.push_back(n1); triangleStripVerticesOut.push_back(n1); triangleStripVerticesOut.push_back(n2); } } const int32_t vertexStart = stripStartIndices[i]; const int32_t vertexEnd = stripEndIndices[i]; for (int32_t iVertex = vertexStart; iVertex < vertexEnd; iVertex++) { CaretAssertVectorIndex(vertices, iVertex); const int32_t vertexIndex = vertices[iVertex]; triangleStripVerticesOut.push_back(vertexIndex); } } } /** * Convert a series of discontinuous triangle strips into one triangle strip. * Between each of the original triangle strips, additional vertices forming * degenerate (arealess) triangles are added. This triangles are not drawn * by OpenGl. * * @see http://www.gamedev.net/page/resources/_/technical/graphics-programming-and-theory/concatenating-triangle-strips-r1871 * @see http://en.wikipedia.org/wiki/Triangle_strip * * @param triangleStrips * The triangle strips. Each contains a sequence of vertex indices. * @param triangleStripOut * OUTPUT - A vector containing all of the vertex indices for drawing * all of the input triangles with one triangle strip. */ void BrainOpenGLShape::contatenateTriangleStrips(const std::vector >& triangleStrips, std::vector& triangleStripOut) const { triangleStripOut.clear(); const int32_t numberOfStrips = static_cast(triangleStrips.size()); for (int32_t i = 0; i < numberOfStrips; i++) { const std::vector& strip = triangleStrips[i]; const int32_t numInStrip = static_cast(strip.size()); if (numInStrip < 2) { continue; } if (i > 0) { /* * N1 is index of first vertex in the strip * N2 is index of second vertex in the strip */ CaretAssertVectorIndex(strip, 0); const GLuint n1 = strip[0]; CaretAssertVectorIndex(strip, 1); const GLuint n2 = strip[1]; const std::vector& prevStrip = triangleStrips[i - 1]; const int32_t numInPrevStrip = static_cast(prevStrip.size()); if (numInPrevStrip >= 2) { /* * P1 is index of last vertex in previous strip * P2 is index of second to last vertex in previous strip */ CaretAssertVectorIndex(prevStrip, numInPrevStrip - 1); const GLuint p1 = prevStrip[numInPrevStrip - 1]; CaretAssertVectorIndex(prevStrip, numInPrevStrip - 2); const GLuint p2 = prevStrip[numInPrevStrip - 2]; /* * Add degenerate triangles so that strips can be concatenated. */ triangleStripOut.push_back(p2); triangleStripOut.push_back(p1); triangleStripOut.push_back(p1); triangleStripOut.push_back(p1); triangleStripOut.push_back(p1); triangleStripOut.push_back(n1); triangleStripOut.push_back(p1); triangleStripOut.push_back(n1); triangleStripOut.push_back(n1); triangleStripOut.push_back(n1); triangleStripOut.push_back(n1); triangleStripOut.push_back(n2); triangleStripOut.push_back(n1); triangleStripOut.push_back(n2); triangleStripOut.push_back(n1); triangleStripOut.push_back(n1); triangleStripOut.push_back(n1); triangleStripOut.push_back(n2); } } for (int32_t j = 0; j < numInStrip; j++) { triangleStripOut.push_back(strip[j]); } } } /** * Print the vertices in a triangle strip. Each triplet is contained * within a set of parenthesis. * * @param triangleStrip * Triangle strip that is printed. */ void BrainOpenGLShape::printTriangleStrip(const std::vector& triangleStrip) const { int i3 = 0; for (std::vector::const_iterator iter = triangleStrip.begin(); iter != triangleStrip.end(); iter++) { if (i3 == 0) { std::cout << " ("; } else { std::cout << ","; } std::cout << *iter; if (i3 == 2) { std::cout << ")"; i3 = 0; } else { i3++; } } std::cout << std::endl << std::endl; } /** * Print the vertices in a triangle fan * * @param triangleFan * Triangle fan that is printed. */ void BrainOpenGLShape::printTriangleFan(const std::vector& triangleFan) const { const int32_t numVertices = static_cast(triangleFan.size()); for (int32_t i = 0; i < numVertices; i++) { const GLuint vertexIndex = triangleFan[i]; if (i == 0) { std::cout << "Center ("; } else if (i > 1) { std::cout << ", "; } std::cout << vertexIndex; if (i == 0) { std::cout << ") Perimeter ("; } } if (numVertices > 1) { std::cout << ")"; } std::cout << std::endl << std::endl; } workbench-1.1.1/src/Brain/BrainOpenGLShape.h000066400000000000000000000103701255417355300205630ustar00rootroot00000000000000#ifndef __BRAIN_OPEN_GL_SHAPE__H_ #define __BRAIN_OPEN_GL_SHAPE__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "BrainOpenGL.h" namespace caret { class BrainOpenGLShape : public CaretObject { public: BrainOpenGLShape(); virtual ~BrainOpenGLShape(); // ADD_NEW_METHODS_HERE void draw(const uint8_t rgba[4]); void draw(const float rgba[4]); static void setImmediateModeOverride(const bool override); private: BrainOpenGLShape(const BrainOpenGLShape&); BrainOpenGLShape& operator=(const BrainOpenGLShape&); void releaseBufferIDInternal(const GLuint bufferID); void releaseDisplayListInternal(const GLuint displayList); protected: /** * Setup for drawing the shape with OpenGL. This may include * creating display lists, allocating buffers, and other calls * to the OpenGL API. The base class handles alllocation and * deallocation of OpenGL display lists and buffers. * * @param drawMode * How the shape will be drawn. */ virtual void setupOpenGLForShape(const BrainOpenGL::DrawMode drawMode) = 0; /** * Draw the shape using the given drawing mode. * * @param drawMode * How the shape will be drawn. * @param rgba * RGBA coloring ranging 0 to 255. */ virtual void drawShape(const BrainOpenGL::DrawMode drawMode, const uint8_t rgba[4]) = 0; /** * Draw the shape using the given drawing mode. * * @param drawMode * How the shape will be drawn. * @param rgba * RGBA coloring ranging 0.0 to 1.0. */ virtual void drawShape(const BrainOpenGL::DrawMode drawMode, const float rgba[4]) = 0; GLuint createBufferID(); void releaseBufferID(const GLuint bufferID); GLuint createDisplayList(); void releaseDisplayList(const GLuint displayList); void contatenateTriangleStrips(const std::vector& vertices, const std::vector& stripStartIndices, const std::vector& stripEndIndices, std::vector& triangleStripVerticesOut) const; void printTriangleStrip(const std::vector& triangleStrip) const; void printTriangleFan(const std::vector& triangleFan) const; void contatenateTriangleStrips(const std::vector >& triangleStrips, std::vector& triangleStripOut) const; private: void createShapeIfNeeded(); void releaseOpenGLAllocations(); // ADD_NEW_MEMBERS_HERE std::set m_bufferIDs; std::set m_displayLists; bool m_shapeSetupComplete; BrainOpenGL::DrawMode m_drawMode; static bool s_immediateModeOverride; }; #ifdef __BRAIN_OPEN_GL_SHAPE_DECLARE__ bool BrainOpenGLShape::s_immediateModeOverride = false; #endif // __BRAIN_OPEN_GL_SHAPE_DECLARE__ } // namespace #endif //__BRAIN_OPEN_GL_SHAPE__H_ workbench-1.1.1/src/Brain/BrainOpenGLShapeCone.cxx000066400000000000000000000441261255417355300217510ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __BRAIN_OPEN_GL_SHAPE_CONE_DECLARE__ #include "BrainOpenGLShapeCone.h" #undef __BRAIN_OPEN_GL_SHAPE_CONE_DECLARE__ #include "CaretAssert.h" #include "MathFunctions.h" using namespace caret; /** * \class caret::BrainOpenGLShapeCone * \brief Create a conical shape for use with OpenGL. */ /** * Constructor. * @param numberOfSides * Number of sides in the cone. */ BrainOpenGLShapeCone::BrainOpenGLShapeCone(const int32_t numberOfSides) : BrainOpenGLShape(), m_numberOfSides(numberOfSides) { m_isApplyColoring = true; bool debugFlag = false; /* * Add origin (apex) to points */ const int32_t apexIndex = static_cast(m_coordinates.size() / 3); const float origin[3] = { 0.0, 0.0, 0.0 }; m_coordinates.push_back(origin[0]); m_coordinates.push_back(origin[1]); m_coordinates.push_back(origin[2]); m_sideNormals.push_back(0.0); m_sideNormals.push_back(0.0); m_sideNormals.push_back(-1.0); m_capNormals.push_back(0.0); m_capNormals.push_back(0.0); m_capNormals.push_back(1.0); /* * Setup step size based upon number of points around circle */ // const int32_t numberOfPoints = 8; const float step = (2.0 * M_PI) / m_numberOfSides; /* * Generate points around circle */ const float radius = 0.5; const float z = 1.0; for (int32_t i = 0; i < m_numberOfSides; i++) { const float t = step * i; // const float x = majorRadius * std::cos(t); // const float y = minorRadius * std::sin(t); const float x = radius * std::cos(t); const float y = radius * std::sin(t); m_coordinates.push_back(x); m_coordinates.push_back(y); m_coordinates.push_back(z); m_sideNormals.push_back(0.0); m_sideNormals.push_back(0.0); m_sideNormals.push_back(1.0); m_capNormals.push_back(0.0); m_capNormals.push_back(0.0); m_capNormals.push_back(1.0); } /* * Add cap center */ const int32_t capCenterIndex = static_cast(m_coordinates.size() / 3); m_coordinates.push_back(origin[0]); m_coordinates.push_back(origin[1]); m_coordinates.push_back(origin[2]); m_sideNormals.push_back(0.0); m_sideNormals.push_back(0.0); m_sideNormals.push_back(1.0); m_capNormals.push_back(0.0); m_capNormals.push_back(0.0); m_capNormals.push_back(1.0); /* * Add normal vectors of triangles into normal vector summations * NOTE: Vertex at index zero is the apex */ for (int32_t i = 1; i <= m_numberOfSides; i++) { int32_t nextIndex = i + 1; if (nextIndex > m_numberOfSides) { nextIndex = 1; } /* * Normal of triangle */ const int32_t i3 = i * 3; const int32_t next3 = nextIndex * 3; float triangleNormal[3]; MathFunctions::normalVector(origin, &m_coordinates[i3], &m_coordinates[next3], triangleNormal); CaretAssertVectorIndex(m_sideNormals, i3+2); m_sideNormals[i3] += triangleNormal[0]; m_sideNormals[i3+1] += triangleNormal[1]; m_sideNormals[i3+2] += triangleNormal[2]; CaretAssertVectorIndex(m_sideNormals, next3+2); m_sideNormals[next3] += triangleNormal[0]; m_sideNormals[next3+1] += triangleNormal[1]; m_sideNormals[next3+2] += triangleNormal[2]; } /* * Finish creation of the normal vectors */ for (int32_t i = 1; i <= m_numberOfSides; i++) { const int32_t i3 = i * 3; CaretAssertVectorIndex(m_sideNormals, i3+2); m_sideNormals[i3] /= 2.0; // vertices are shared by two triangles m_sideNormals[i3+1] /= 2.0; m_sideNormals[i3+2] /= 2.0; MathFunctions::normalizeVector(&m_sideNormals[i3]); } /* * Generate vector list for triangles */ m_sidesTriangleFan.push_back(apexIndex); for (int32_t i = m_numberOfSides; i > 0; i--) { m_sidesTriangleFan.push_back(i); } m_sidesTriangleFan.push_back(m_numberOfSides); // closes m_capTriangleFan.push_back(capCenterIndex); for (int32_t i = 1; i <= m_numberOfSides; i++) { m_capTriangleFan.push_back(i); } m_capTriangleFan.push_back(1); if (debugFlag) { CaretAssert(m_coordinates.size() == m_sideNormals.size()); CaretAssert(m_coordinates.size() == m_capNormals.size()); const int32_t numPoints = static_cast(m_coordinates.size() / 3); for (int32_t i = 0; i < numPoints; i++) { const int32_t i3 = i * 3; CaretAssertVectorIndex(m_coordinates, i3+2); std::cout << "points[" << i << "]=(" << AString::fromNumbers(&m_coordinates[i3], 3, ",") << ")" << std::endl; CaretAssertVectorIndex(m_sideNormals, i3+2); std::cout << "side normal[" << i << "]=(" << AString::fromNumbers(&m_sideNormals[i3], 3, ",") << ")" << std::endl; CaretAssertVectorIndex(m_capNormals, i3+2); std::cout << "cap normal[" << i << "]=(" << AString::fromNumbers(&m_capNormals[i3], 3, ",") << ")" << std::endl; } std::cout << "Sides: "; printTriangleFan(m_sidesTriangleFan); std::cout << std::endl; std::cout << "Cap: "; printTriangleFan(m_capTriangleFan); std::cout << std::endl; } // /* // * End Cap // */ // glBegin(GL_POLYGON); // glNormal3f(0.0, 0.0, 1.0); // for (int32_t i = 0; i < m_numberOfSides; i++) { // const int32_t i3 = i * 3; // glVertex3fv(&m_coordinates[i3]); // } // glEnd(); /* * Create storage for colors */ const int64_t numCoords = static_cast(m_coordinates.size()) / 3; const int64_t numRGBA = numCoords * 4; m_rgbaByte.resize(numRGBA * 4, 0); for (GLuint i = 0; i < numCoords; i++) { const int32_t i4 = i * 4; CaretAssertVectorIndex(m_rgbaByte, i4+3); m_rgbaByte[i4] = 0; m_rgbaByte[i4+1] = 0; m_rgbaByte[i4+2] = 0; m_rgbaByte[i4+3] = 255; } } /** * Destructor. */ BrainOpenGLShapeCone::~BrainOpenGLShapeCone() { } void BrainOpenGLShapeCone::setupOpenGLForShape(const BrainOpenGL::DrawMode drawMode) { m_displayList = 0; m_coordinatesBufferID = 0; m_coordinatesRgbaByteBufferID = 0; m_sidesNormalBufferID = 0; m_sidesTriangleFanBufferID = 0; m_capNormalBufferID = 0; m_capTriangleFanBufferID = 0; switch (drawMode) { case BrainOpenGL::DRAW_MODE_DISPLAY_LISTS: { m_displayList = createDisplayList(); if (m_displayList > 0) { glNewList(m_displayList, GL_COMPILE); uint8_t rgbaUnused[4] = { 0, 0, 0, 0 }; m_isApplyColoring = false; drawShape(BrainOpenGL::DRAW_MODE_IMMEDIATE, rgbaUnused); m_isApplyColoring = true; glEndList(); } } break; case BrainOpenGL::DRAW_MODE_IMMEDIATE: { /* nothing to do for this case */ } break; case BrainOpenGL::DRAW_MODE_INVALID: { CaretAssert(0); } break; case BrainOpenGL::DRAW_MODE_VERTEX_BUFFERS: #ifdef BRAIN_OPENGL_INFO_SUPPORTS_VERTEX_BUFFERS /* * Put coordinates into its buffer. */ m_coordinatesBufferID = createBufferID(); glBindBuffer(GL_ARRAY_BUFFER, m_coordinatesBufferID); glBufferData(GL_ARRAY_BUFFER, m_coordinates.size() * sizeof(GLfloat), &m_coordinates[0], GL_STATIC_DRAW); /* * For RGBA coloring */ m_coordinatesRgbaByteBufferID = createBufferID(); glBindBuffer(GL_ARRAY_BUFFER, m_coordinatesRgbaByteBufferID); glBufferData(GL_ARRAY_BUFFER, m_rgbaByte.size() * sizeof(GLubyte), &m_rgbaByte[0], GL_DYNAMIC_DRAW); /* * Put side normals into its buffer. */ m_sidesNormalBufferID = createBufferID(); glBindBuffer(GL_ARRAY_BUFFER, m_sidesNormalBufferID); glBufferData(GL_ARRAY_BUFFER, m_sideNormals.size() * sizeof(GLfloat), &m_sideNormals[0], GL_STATIC_DRAW); /* * Put sides triangle fan for sides into its buffer. */ m_sidesTriangleFanBufferID = createBufferID(); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_sidesTriangleFanBufferID); glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_sidesTriangleFan.size() * sizeof(GLuint), &m_sidesTriangleFan[0], GL_STATIC_DRAW); /* * Put cap normals into its buffer. */ m_capNormalBufferID = createBufferID(); glBindBuffer(GL_ARRAY_BUFFER, m_capNormalBufferID); glBufferData(GL_ARRAY_BUFFER, m_capNormals.size() * sizeof(GLfloat), &m_capNormals[0], GL_STATIC_DRAW); /* * Put cap triangle fan for cap into its buffer. */ m_capTriangleFanBufferID = createBufferID(); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_capTriangleFanBufferID); glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_capTriangleFan.size() * sizeof(GLuint), &m_capTriangleFan[0], GL_STATIC_DRAW); /* * Deselect active buffer. */ glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); #endif // BRAIN_OPENGL_INFO_SUPPORTS_VERTEX_BUFFERS break; } } /** * Draw the shape. * * @param drawMode * How to draw the shape. * @param rgba * RGBA coloring ranging 0.0 to 1.0 */ void BrainOpenGLShapeCone::drawShape(const BrainOpenGL::DrawMode drawMode, const float rgba[4]) { const uint8_t rgbaByte[4] = { static_cast(rgba[0] * 255.0), static_cast(rgba[1] * 255.0), static_cast(rgba[2] * 255.0), static_cast(rgba[3] * 255.0) }; drawShape(drawMode, rgbaByte); } /** * Draw the shape. * * @param drawMode * How to draw the shape. * @param rgba * RGBA coloring ranging 0 to 255. */ void BrainOpenGLShapeCone::drawShape(const BrainOpenGL::DrawMode drawMode, const uint8_t rgba[4]) { switch (drawMode) { case BrainOpenGL::DRAW_MODE_DISPLAY_LISTS: { if (m_displayList > 0) { if (m_isApplyColoring) { glColor4ubv(rgba); } glCallList(m_displayList); } } break; case BrainOpenGL::DRAW_MODE_IMMEDIATE: { if (m_isApplyColoring) { glColor4ubv(rgba); } const int32_t numSideVertices = static_cast(m_sidesTriangleFan.size()); glBegin(GL_TRIANGLE_FAN); for (int32_t j = 0; j < numSideVertices; j++) { const int32_t vertexIndex = m_sidesTriangleFan[j] * 3; CaretAssertVectorIndex(m_sideNormals, vertexIndex+2); CaretAssertVectorIndex(m_coordinates, vertexIndex+2); glNormal3fv(&m_sideNormals[vertexIndex]); glVertex3fv(&m_coordinates[vertexIndex]); } glEnd(); const int32_t numCapVertices = static_cast(m_capTriangleFan.size()); glBegin(GL_TRIANGLE_FAN); for (int32_t j = 0; j < numCapVertices; j++) { const int32_t vertexIndex = m_capTriangleFan[j] * 3; CaretAssertVectorIndex(m_capNormals, vertexIndex+2); CaretAssertVectorIndex(m_coordinates, vertexIndex+2); glNormal3fv(&m_capNormals[vertexIndex]); glVertex3fv(&m_coordinates[vertexIndex]); } glEnd(); } break; case BrainOpenGL::DRAW_MODE_INVALID: { CaretAssert(0); } break; case BrainOpenGL::DRAW_MODE_VERTEX_BUFFERS: #ifdef BRAIN_OPENGL_INFO_SUPPORTS_VERTEX_BUFFERS /* * Enable vertices and normals for buffers */ glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); glEnableClientState(GL_COLOR_ARRAY); /* * Set the vertices for drawing. */ CaretAssert(glIsBuffer(m_coordinatesBufferID) == GL_TRUE); glBindBuffer(GL_ARRAY_BUFFER, m_coordinatesBufferID); glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)0); /* * Put BYTE colors into its buffer */ const int64_t numRGBA = static_cast(m_rgbaByte.size()) / 4; for (int64_t ir = 0; ir < numRGBA; ir++) { const int64_t ir4 = ir * 4; m_rgbaByte[ir4] = rgba[0]; m_rgbaByte[ir4+1] = rgba[1]; m_rgbaByte[ir4+2] = rgba[2]; m_rgbaByte[ir4+3] = rgba[3]; } CaretAssert(glIsBuffer(m_coordinatesRgbaByteBufferID) == GL_TRUE); glBindBuffer(GL_ARRAY_BUFFER, m_coordinatesRgbaByteBufferID); glBufferData(GL_ARRAY_BUFFER, m_rgbaByte.size() * sizeof(GLubyte), &m_rgbaByte[0], GL_DYNAMIC_DRAW); glColorPointer(4, GL_UNSIGNED_BYTE, 0, (GLvoid*)0); /* * Set the side normal vectors for drawing. */ CaretAssert(glIsBuffer(m_sidesNormalBufferID) == GL_TRUE); glBindBuffer(GL_ARRAY_BUFFER, m_sidesNormalBufferID); glNormalPointer(GL_FLOAT, 0, (GLvoid*)0); /* * Draw the side triangle fans. */ CaretAssert(glIsBuffer(m_sidesTriangleFanBufferID) == GL_TRUE); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_sidesTriangleFanBufferID); glDrawElements(GL_TRIANGLE_FAN, m_sidesTriangleFan.size(), GL_UNSIGNED_INT, (GLvoid*)0); /* * Set the cap normal vectors for drawing. */ glBindBuffer(GL_ARRAY_BUFFER, m_capNormalBufferID); glNormalPointer(GL_FLOAT, 0, (GLvoid*)0); /* * Draw the cap triangle fans. */ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_capTriangleFanBufferID); glDrawElements(GL_TRIANGLE_FAN, m_capTriangleFan.size(), GL_UNSIGNED_INT, (GLvoid*)0); /* * Deselect active buffer. */ glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); /* * Disable vertices and normals for buffers. * Otherwise, bad thing will happen. */ glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_NORMAL_ARRAY); glDisableClientState(GL_COLOR_ARRAY); #endif // BRAIN_OPENGL_INFO_SUPPORTS_VERTEX_BUFFERS break; } } workbench-1.1.1/src/Brain/BrainOpenGLShapeCone.h000066400000000000000000000051421255417355300213710ustar00rootroot00000000000000#ifndef __BRAIN_OPEN_GL_SHAPE_CONE_H_ #define __BRAIN_OPEN_GL_SHAPE_CONE_H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "BrainOpenGLShape.h" namespace caret { class BrainOpenGLShapeCone : public BrainOpenGLShape { public: BrainOpenGLShapeCone(const int32_t numberOfSides); virtual ~BrainOpenGLShapeCone(); private: BrainOpenGLShapeCone(const BrainOpenGLShapeCone&); BrainOpenGLShapeCone& operator=(const BrainOpenGLShapeCone&); public: // ADD_NEW_METHODS_HERE protected: void drawShape(const BrainOpenGL::DrawMode drawMode, const uint8_t rgba[4]); void drawShape(const BrainOpenGL::DrawMode drawMode, const float rgba[4]); void setupOpenGLForShape(const BrainOpenGL::DrawMode drawMode); private: // ADD_NEW_MEMBERS_HERE const int32_t m_numberOfSides; GLuint m_coordinatesBufferID; GLuint m_coordinatesRgbaByteBufferID; GLuint m_sidesNormalBufferID; GLuint m_sidesTriangleFanBufferID; GLuint m_capNormalBufferID; GLuint m_capTriangleFanBufferID; GLuint m_displayList; std::vector m_coordinates; std::vector m_rgbaByte; std::vector m_sideNormals; std::vector m_sidesTriangleFan; std::vector m_capTriangleFan; std::vector m_capNormals; bool m_isApplyColoring; }; #ifdef __BRAIN_OPEN_GL_SHAPE_CONE_DECLARE__ // #endif // __BRAIN_OPEN_GL_SHAPE_CONE_DECLARE__ } // namespace #endif //__BRAIN_OPEN_GL_SHAPE_CONE_H_ workbench-1.1.1/src/Brain/BrainOpenGLShapeCube.cxx000066400000000000000000000405021255417355300217350ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __BRAIN_OPEN_G_L_SHAPE_CUBE_DECLARE__ #include "BrainOpenGLShapeCube.h" #undef __BRAIN_OPEN_G_L_SHAPE_CUBE_DECLARE__ #include "AString.h" #include "CaretAssert.h" #include "MathFunctions.h" using namespace caret; /** * \class caret::BrainOpenGLShapeCube * \brief Create a cube shape for use with OpenGL. */ /** * Constructor. * * @param cubeSize * Size of cube from one face to the opposite face. * @param cubeType * Type of cube. */ BrainOpenGLShapeCube::BrainOpenGLShapeCube(const float cubeSize, const CUBE_TYPE cubeType) : BrainOpenGLShape(), m_cubeSize(cubeSize), m_cubeType(cubeType) { m_isApplyColoring = true; GLfloat cubeVertices[8][3] = { { -0.5, -0.5, 0.5 }, // 0: left, near, top { 0.5, -0.5, 0.5 }, // 1: right, near, top { -0.5, 0.5, 0.5 }, // 2: left, far, top { 0.5, 0.5, 0.5 }, // 3: right, far, top { -0.5, -0.5, -0.5 }, // 4: left, near, bottom { 0.5, -0.5, -0.5 }, // 5: right, near, bottom { -0.5, 0.5, -0.5 }, // 6: left, far, bottom { 0.5, 0.5, -0.5 } // 7: right, far, bottom }; const GLint lnt = 0; const GLint rnt = 1; const GLint lft = 2; const GLint rft = 3; const GLint lnb = 4; const GLint rnb = 5; const GLint lfb = 6; const GLint rfb = 7; const GLfloat cubeNormals[6][3] = { { -1.0, 0.0, 0.0 }, { 1.0, 0.0, 0.0 }, { 0.0, -1.0, 0.0 }, { 0.0, 1.0, 0.0 }, { 0.0, 0.0, 1.0 }, { 0.0, 0.0, -1.0 } }; const GLint normalIndexLeftFace = 0; const GLint normalIndexRightFace = 1; const GLint normalIndexNearFace = 2; const GLint normalIndexFarFace = 3; const GLint normalIndexTopFace = 4; const GLint normalIndexBottomFace = 5; /* * Near Face */ addFace(cubeVertices, lnt, lnb, rnb, rnt, cubeNormals[normalIndexNearFace]); /* * Far Face */ addFace(cubeVertices, rfb, lfb, lft, rft, cubeNormals[normalIndexFarFace]); /* * Left Face */ addFace(cubeVertices, lfb, lnb, lnt, lft, cubeNormals[normalIndexLeftFace]); /* * Right Face */ addFace(cubeVertices, rnb, rfb, rft, rnt, cubeNormals[normalIndexRightFace]); /* * Bottom Face */ addFace(cubeVertices, rnb, lnb, lfb, rfb, cubeNormals[normalIndexBottomFace]); /* * Top Face */ addFace(cubeVertices, lnt, rnt, rft, lft, cubeNormals[normalIndexTopFace]); // /* // * Near Face // */ // addFace(cubeVertices, // lnt, lnb, rnb, rnt, // cubeNormals[normalIndexNearFace]); // // /* // * Far Face // */ // addFace(cubeVertices, // rfb, lfb, lft, rft, // cubeNormals[normalIndexFarFace]); // // /* // * Left Face // */ // addFace(cubeVertices, // lfb, lnb, lnt, lft, // cubeNormals[normalIndexLeftFace]); // // /* // * Right Face // */ // addFace(cubeVertices, // rnb, rfb, rft, rnt, // cubeNormals[normalIndexRightFace]); // // /* // * Bottom Face // */ // addFace(cubeVertices, // rnb, lnb, lfb, rfb, // cubeNormals[normalIndexBottomFace]); // // /* // * Top Face // */ // addFace(cubeVertices, // lnt, rnt, rft, lft, // cubeNormals[normalIndexTopFace]); CaretAssert(m_coordinates.size() == m_normals.size()); /* * Create storage for colors */ const int64_t numCoords = static_cast(m_coordinates.size()) / 3; const int64_t numRGBA = numCoords * 4; m_rgbaByte.resize(numRGBA * 4, 0); for (GLuint i = 0; i < numCoords; i++) { const int32_t i4 = i * 4; CaretAssertVectorIndex(m_rgbaByte, i4+3); m_rgbaByte[i4] = 0; m_rgbaByte[i4+1] = 0; m_rgbaByte[i4+2] = 0; m_rgbaByte[i4+3] = 255; } switch (m_cubeType) { case NORMAL: break; case ROUNDED: { const unsigned int numPoints = m_coordinates.size() / 3; for (unsigned int i = 0; i < numPoints; i++) { const unsigned int i3 = i * 3; m_normals[i3] = m_coordinates[i3]; m_normals[i3+1] = m_coordinates[i3+1]; m_normals[i3+2] = m_coordinates[i3+2]; MathFunctions::normalizeVector(&m_normals[i3]); } } break; } } /** * Destructor. */ BrainOpenGLShapeCube::~BrainOpenGLShapeCube() { } /** * Setup the shape for drawing. * * @param drawMode * The drawing mode. */ void BrainOpenGLShapeCube::setupOpenGLForShape(const BrainOpenGL::DrawMode drawMode) { m_displayList = 0; m_vertexBufferID = 0; m_coordinatesRgbaByteBufferID = 0; m_normalBufferID = 0; m_trianglesBufferID = 0; switch (drawMode) { case BrainOpenGL::DRAW_MODE_DISPLAY_LISTS: { m_displayList = createDisplayList(); if (m_displayList > 0) { glNewList(m_displayList, GL_COMPILE); uint8_t rgbaUnused[4] = { 0, 0, 0, 0 }; m_isApplyColoring = false; drawShape(BrainOpenGL::DRAW_MODE_IMMEDIATE, rgbaUnused); m_isApplyColoring = true; glEndList(); } } break; case BrainOpenGL::DRAW_MODE_IMMEDIATE: { /* nothing to do for this case */ } break; case BrainOpenGL::DRAW_MODE_INVALID: { CaretAssert(0); } break; case BrainOpenGL::DRAW_MODE_VERTEX_BUFFERS: #ifdef BRAIN_OPENGL_INFO_SUPPORTS_VERTEX_BUFFERS /* * Put vertices (coordinates) into its buffer. */ m_vertexBufferID = createBufferID(); glBindBuffer(GL_ARRAY_BUFFER, m_vertexBufferID); glBufferData(GL_ARRAY_BUFFER, m_coordinates.size() * sizeof(GLfloat), &m_coordinates[0], GL_STATIC_DRAW); /* * For RGBA coloring */ m_coordinatesRgbaByteBufferID = createBufferID(); glBindBuffer(GL_ARRAY_BUFFER, m_coordinatesRgbaByteBufferID); glBufferData(GL_ARRAY_BUFFER, m_rgbaByte.size() * sizeof(GLubyte), &m_rgbaByte[0], GL_DYNAMIC_DRAW); /* * Put normals into its buffer. */ m_normalBufferID = createBufferID(); glBindBuffer(GL_ARRAY_BUFFER, m_normalBufferID); glBufferData(GL_ARRAY_BUFFER, m_normals.size() * sizeof(GLfloat), &m_normals[0], GL_STATIC_DRAW); /* * Put triangle strips into its buffer. */ m_trianglesBufferID = createBufferID(); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_trianglesBufferID); glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_triangles.size() * sizeof(GLuint), &m_triangles[0], GL_STATIC_DRAW); /* * Deselect active buffer. */ glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); #endif // BRAIN_OPENGL_INFO_SUPPORTS_VERTEX_BUFFERS break; } } /** * Add a face (two triangles) to vertices and normals for drawing. * Vertex indices must be in counter-clockwise order. * * @param vertices * Array of vertices. * @param v1 * First face coordinate index. * @param v2 * Second face coordinate index. * @param v3 * Third face coordinate index. * @param v4 * Fourth face coordinate index. * @param normalVector * Normal vector for the face. */ void BrainOpenGLShapeCube::addFace(const GLfloat coordinates[][3], const GLint v1, const GLint v2, const GLint v3, const GLint v4, const GLfloat normalVector[3]) { m_coordinates.push_back(coordinates[v1][0]); m_coordinates.push_back(coordinates[v1][1]); m_coordinates.push_back(coordinates[v1][2]); m_normals.push_back(normalVector[0]); m_normals.push_back(normalVector[1]); m_normals.push_back(normalVector[2]); m_triangles.push_back(m_coordinates.size() / 3 - 1); m_coordinates.push_back(coordinates[v2][0]); m_coordinates.push_back(coordinates[v2][1]); m_coordinates.push_back(coordinates[v2][2]); m_normals.push_back(normalVector[0]); m_normals.push_back(normalVector[1]); m_normals.push_back(normalVector[2]); m_triangles.push_back(m_coordinates.size() / 3 - 1); m_coordinates.push_back(coordinates[v3][0]); m_coordinates.push_back(coordinates[v3][1]); m_coordinates.push_back(coordinates[v3][2]); m_normals.push_back(normalVector[0]); m_normals.push_back(normalVector[1]); m_normals.push_back(normalVector[2]); m_triangles.push_back(m_coordinates.size() / 3 - 1); m_coordinates.push_back(coordinates[v3][0]); m_coordinates.push_back(coordinates[v3][1]); m_coordinates.push_back(coordinates[v3][2]); m_normals.push_back(normalVector[0]); m_normals.push_back(normalVector[1]); m_normals.push_back(normalVector[2]); m_triangles.push_back(m_coordinates.size() / 3 - 1); m_coordinates.push_back(coordinates[v4][0]); m_coordinates.push_back(coordinates[v4][1]); m_coordinates.push_back(coordinates[v4][2]); m_normals.push_back(normalVector[0]); m_normals.push_back(normalVector[1]); m_normals.push_back(normalVector[2]); m_triangles.push_back(m_coordinates.size() / 3 - 1); m_coordinates.push_back(coordinates[v1][0]); m_coordinates.push_back(coordinates[v1][1]); m_coordinates.push_back(coordinates[v1][2]); m_normals.push_back(normalVector[0]); m_normals.push_back(normalVector[1]); m_normals.push_back(normalVector[2]); m_triangles.push_back(m_coordinates.size() / 3 - 1); } /** * Draw the shape. * * @param drawMode * How to draw the shape. * @param rgba * RGBA coloring ranging 0.0 to 1.0 */ void BrainOpenGLShapeCube::drawShape(const BrainOpenGL::DrawMode drawMode, const float rgba[4]) { const uint8_t rgbaByte[4] = { static_cast(rgba[0] * 255.0), static_cast(rgba[1] * 255.0), static_cast(rgba[2] * 255.0), static_cast(rgba[3] * 255.0) }; drawShape(drawMode, rgbaByte); } /** * Draw the shape. * * @param drawMode * How to draw the shape. * @param rgba * RGBA coloring ranging 0 to 255. */ void BrainOpenGLShapeCube::drawShape(const BrainOpenGL::DrawMode drawMode, const uint8_t rgba[4]) { switch (drawMode) { case BrainOpenGL::DRAW_MODE_DISPLAY_LISTS: { if (m_displayList > 0) { if (m_isApplyColoring) { glColor4ubv(rgba); } glCallList(m_displayList); } } break; case BrainOpenGL::DRAW_MODE_IMMEDIATE: { if (m_isApplyColoring) { glColor4ubv(rgba); } const int32_t numVertices = static_cast(m_coordinates.size() / 3); glBegin(GL_TRIANGLES); for (int32_t j = 0; j < numVertices; j++) { const int32_t vertexIndex = j * 3; CaretAssertVectorIndex(m_normals, vertexIndex); CaretAssertVectorIndex(m_coordinates, vertexIndex); glNormal3fv(&m_normals[vertexIndex]); glVertex3fv(&m_coordinates[vertexIndex]); } glEnd(); } break; case BrainOpenGL::DRAW_MODE_INVALID: { CaretAssert(0); } break; case BrainOpenGL::DRAW_MODE_VERTEX_BUFFERS: #ifdef BRAIN_OPENGL_INFO_SUPPORTS_VERTEX_BUFFERS glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); glEnableClientState(GL_COLOR_ARRAY); /* * Set the vertices for drawing. */ glBindBuffer(GL_ARRAY_BUFFER, m_vertexBufferID); glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)0); /* * Put BYTE colors into its buffer */ const int64_t numRGBA = static_cast(m_rgbaByte.size()) / 4; for (int64_t ir = 0; ir < numRGBA; ir++) { const int64_t ir4 = ir * 4; m_rgbaByte[ir4] = rgba[0]; m_rgbaByte[ir4+1] = rgba[1]; m_rgbaByte[ir4+2] = rgba[2]; m_rgbaByte[ir4+3] = rgba[3]; } CaretAssert(glIsBuffer(m_coordinatesRgbaByteBufferID) == GL_TRUE); glBindBuffer(GL_ARRAY_BUFFER, m_coordinatesRgbaByteBufferID); glBufferData(GL_ARRAY_BUFFER, m_rgbaByte.size() * sizeof(GLubyte), &m_rgbaByte[0], GL_DYNAMIC_DRAW); glColorPointer(4, GL_UNSIGNED_BYTE, 0, (GLvoid*)0); /* * Set the normal vectors for drawing. */ glBindBuffer(GL_ARRAY_BUFFER, m_normalBufferID); glNormalPointer(GL_FLOAT, 0, (GLvoid*)0); /* * Draw the triangles. */ CaretAssert(m_triangles.size() == (m_coordinates.size() / 3)); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_trianglesBufferID); glDrawElements(GL_TRIANGLES, m_triangles.size(), GL_UNSIGNED_INT, (GLvoid*)0); /* * Deselect active buffer. */ glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_NORMAL_ARRAY); glDisableClientState(GL_COLOR_ARRAY); #endif // BRAIN_OPENGL_INFO_SUPPORTS_VERTEX_BUFFERS break; } } workbench-1.1.1/src/Brain/BrainOpenGLShapeCube.h000066400000000000000000000057361255417355300213740ustar00rootroot00000000000000#ifndef __BRAIN_OPEN_G_L_SHAPE_CUBE__H_ #define __BRAIN_OPEN_G_L_SHAPE_CUBE__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "BrainOpenGLShape.h" namespace caret { class BrainOpenGLShapeCube : public BrainOpenGLShape { public: /** * Type of cube */ enum CUBE_TYPE { /** Normal vectors orthogonal to faces */ NORMAL, /** Normal vectors run though vertices which smooths corners */ ROUNDED }; BrainOpenGLShapeCube(const float cubeSize, const CUBE_TYPE cubeType); virtual ~BrainOpenGLShapeCube(); private: BrainOpenGLShapeCube(const BrainOpenGLShapeCube&); BrainOpenGLShapeCube& operator=(const BrainOpenGLShapeCube&); public: // ADD_NEW_METHODS_HERE protected: void drawShape(const BrainOpenGL::DrawMode drawMode, const uint8_t rgba[4]); void drawShape(const BrainOpenGL::DrawMode drawMode, const float rgba[4]); void setupOpenGLForShape(const BrainOpenGL::DrawMode drawMode); private: void addFace(const GLfloat coordinates[][3], const GLint v1, const GLint v2, const GLint v3, const GLint v4, const GLfloat normalVector[3]); // ADD_NEW_MEMBERS_HERE const float m_cubeSize; const CUBE_TYPE m_cubeType; GLuint m_vertexBufferID; GLuint m_coordinatesRgbaByteBufferID; GLuint m_normalBufferID; GLuint m_trianglesBufferID; GLuint m_displayList; std::vector m_coordinates; std::vector m_rgbaByte; std::vector m_normals; std::vector m_triangles; bool m_isApplyColoring; }; #ifdef __BRAIN_OPEN_G_L_SHAPE_CUBE_DECLARE__ // #endif // __BRAIN_OPEN_G_L_SHAPE_CUBE_DECLARE__ } // namespace #endif //__BRAIN_OPEN_G_L_SHAPE_CUBE__H_ workbench-1.1.1/src/Brain/BrainOpenGLShapeCylinder.cxx000066400000000000000000000473441255417355300226430ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __BRAIN_OPEN_G_L_SHAPE_CYLINDER_DECLARE__ #include "BrainOpenGLShapeCylinder.h" #undef __BRAIN_OPEN_G_L_SHAPE_CYLINDER_DECLARE__ #include "CaretAssert.h" #include "MathFunctions.h" using namespace caret; /** * \class caret::BrainOpenGLShapeCylinder * \brief Creates a cylinder shape for use with OpenGL. * \ingroup Brain */ /** * Constructor. */ BrainOpenGLShapeCylinder::BrainOpenGLShapeCylinder(const int32_t numberOfSides) : BrainOpenGLShape(), m_numberOfSides(numberOfSides) { m_isApplyColoring = true; // bool debugFlag = false; /* * Setup step size based upon number of points around circle */ const float step = (2.0 * M_PI) / m_numberOfSides; /* * Z-coordinates at bottom and top of cylinder */ const float zTop = 1.0; const float zBottom = 0.0; std::vector topTriangleStripVertices; std::vector bottomTriangleStripVertices; std::vector sideTriangleStripVertices; /* * Counts vertices */ GLuint vertexCounter = 0; // const float topCenterNormalX = 0.0; // const float topCenterNormalY = 0.0; // const float topCenterNormalZ = 1.0; // // const float topCenterX = 0.0; // const float topCenterY = 0.0; // const float topCenterZ = 1.0; // // const float bottomCenterNormalX = 0.0; // const float bottomCenterNormalY = 0.0; // const float bottomCenterNormalZ = -1.0; // // const float bottomCenterX = 0.0; // const float bottomCenterY = 0.0; // const float bottomCenterZ = -1.0; // /* // * Center of top // */ // m_coordinates.push_back(0.0); // m_coordinates.push_back(0.0); // m_coordinates.push_back(1.0); // m_normals.push_back(topCenterNormalX); // m_normals.push_back(topCenterNormalY); // m_normals.push_back(topCenterNormalZ); // m_normals.push_back(vertexCounter); // vertexCounter++; // // /* // * Center of bottom // */ // m_coordinates.push_back(0.0); // m_coordinates.push_back(0.0); // m_coordinates.push_back(-1.0); // m_normals.push_back(bottomCenterNormalX); // m_normals.push_back(bottomCenterNormalY); // m_normals.push_back(bottomCenterNormalZ); // m_normals.push_back(vertexCounter); // vertexCounter++; const GLuint firstSideVertex = vertexCounter; /* * Generate points around cylinder */ const float radius = 0.5; for (int32_t i = 0; i < m_numberOfSides; i++) { const float t = step * i; const float x = radius * std::cos(t); const float y = radius * std::sin(t); /* * Top of slice */ m_coordinates.push_back(x); m_coordinates.push_back(y); m_coordinates.push_back(zTop); m_normals.push_back(x); m_normals.push_back(y); m_normals.push_back(0.0); sideTriangleStripVertices.push_back(vertexCounter); vertexCounter++; // m_normals.push_back(topCenterNormalX); // m_normals.push_back(topCenterNormalY); // m_normals.push_back(topCenterNormalZ); // m_topTriangleFan.push_back(vertexCounter); m_coordinates.push_back(x); m_coordinates.push_back(y); m_coordinates.push_back(zBottom); m_normals.push_back(x); m_normals.push_back(y); m_normals.push_back(0.0); sideTriangleStripVertices.push_back(vertexCounter); vertexCounter++; // m_bottomNormals.push_back(bottomCenterNormalX); // m_bottomNormals.push_back(bottomCenterNormalY); // m_bottomNormals.push_back(bottomCenterNormalZ); // m_bottomTriangleFan.push_back(vertexCounter); // vertexCounter++; } /* * Finish cylinder by specifying first two coordinates again */ const GLuint firstVertexIndex = firstSideVertex * 3; m_coordinates.push_back(m_coordinates[firstVertexIndex]); m_coordinates.push_back(m_coordinates[firstVertexIndex+1]); m_coordinates.push_back(m_coordinates[firstVertexIndex+2]); m_normals.push_back(m_normals[firstVertexIndex]); m_normals.push_back(m_normals[firstVertexIndex+1]); m_normals.push_back(m_normals[firstVertexIndex+2]); sideTriangleStripVertices.push_back(vertexCounter); vertexCounter++; m_coordinates.push_back(m_coordinates[firstVertexIndex+3]); m_coordinates.push_back(m_coordinates[firstVertexIndex+4]); m_coordinates.push_back(m_coordinates[firstVertexIndex+5]); m_normals.push_back(m_normals[firstVertexIndex+3]); m_normals.push_back(m_normals[firstVertexIndex+4]); m_normals.push_back(m_normals[firstVertexIndex+5]); sideTriangleStripVertices.push_back(vertexCounter); vertexCounter++; CaretAssert((vertexCounter * 3) == static_cast(m_coordinates.size())); CaretAssert((vertexCounter * 3) == static_cast(m_normals.size())); m_triangleStrip = sideTriangleStripVertices; // /* // * Finish top and bottom // * Note bottom vertices need to be reverse so that // * that the vertices are counter clockwise when pointing down. // */ // m_topNormals.push_back(m_topNormals[0]); // m_topNormals.push_back(m_topNormals[1]); // m_topNormals.push_back(m_topNormals[2]); // m_topTriangleFan.push_back(firstSideVertex); // // m_bottomNormals.push_back(m_bottomNormals[0]); // m_bottomNormals.push_back(m_bottomNormals[1]); // m_bottomNormals.push_back(m_bottomNormals[2]); // m_bottomTriangleFan.push_back(firstSideVertex + 1); // std::reverse(m_bottomTriangleFan.begin(), // m_bottomTriangleFan.end()); // // CaretAssert(m_topNormals.size() == (m_topTriangleFan.size() * 3)); // CaretAssert(m_sidesNormals.size() == ((m_sidesTriangleStrip.size() - 2) * 3)); // CaretAssert(m_topNormals.size() == (m_topTriangleFan.size() * 3)); /* * Create storage for colors */ m_rgbaByte.resize(vertexCounter * 4, 0); m_rgbaFloat.resize(vertexCounter * 4, 0.0); for (GLuint i = 0; i < vertexCounter; i++) { const int32_t i4 = i * 4; m_rgbaFloat[i4] = 0.0; m_rgbaFloat[i4+1] = 0.0; m_rgbaFloat[i4+2] = 0.0; m_rgbaFloat[i4+3] = 1.0; m_rgbaByte[i4] = 0; m_rgbaByte[i4+1] = 0; m_rgbaByte[i4+2] = 0; m_rgbaByte[i4+3] = 255; } } /** * Destructor. */ BrainOpenGLShapeCylinder::~BrainOpenGLShapeCylinder() { } void BrainOpenGLShapeCylinder::setupOpenGLForShape(const BrainOpenGL::DrawMode drawMode) { m_displayList = 0; m_coordinatesBufferID = 0; m_coordinatesRgbaByteBufferID = 0; m_coordinatesRgbaFloatBufferID = 0; m_normalsBufferID = 0; m_triangleStripBufferID = 0; switch (drawMode) { case BrainOpenGL::DRAW_MODE_DISPLAY_LISTS: { m_displayList = createDisplayList(); if (m_displayList > 0) { glNewList(m_displayList, GL_COMPILE); uint8_t rgbaUnused[4] = { 0, 0, 0, 0 }; m_isApplyColoring = false; drawShape(BrainOpenGL::DRAW_MODE_IMMEDIATE, rgbaUnused); m_isApplyColoring = true; glEndList(); } } break; case BrainOpenGL::DRAW_MODE_IMMEDIATE: { /* nothing to do for this case */ } break; case BrainOpenGL::DRAW_MODE_INVALID: { CaretAssert(0); } break; case BrainOpenGL::DRAW_MODE_VERTEX_BUFFERS: #ifdef BRAIN_OPENGL_INFO_SUPPORTS_VERTEX_BUFFERS /* * Put coordinates into its buffer. */ m_coordinatesBufferID = createBufferID(); glBindBuffer(GL_ARRAY_BUFFER, m_coordinatesBufferID); glBufferData(GL_ARRAY_BUFFER, m_coordinates.size() * sizeof(GLfloat), &m_coordinates[0], GL_STATIC_DRAW); m_coordinatesRgbaByteBufferID = createBufferID(); glBindBuffer(GL_ARRAY_BUFFER, m_coordinatesRgbaByteBufferID); glBufferData(GL_ARRAY_BUFFER, m_rgbaByte.size() * sizeof(GLubyte), &m_rgbaByte[0], GL_DYNAMIC_DRAW); /* * Put FLOAT colors into its buffer */ m_coordinatesRgbaFloatBufferID = createBufferID(); glBindBuffer(GL_ARRAY_BUFFER, m_coordinatesRgbaFloatBufferID); glBufferData(GL_ARRAY_BUFFER, m_rgbaFloat.size() * sizeof(GLfloat), &m_rgbaFloat[0], GL_DYNAMIC_DRAW); /* * Put side normals into its buffer. */ m_normalsBufferID = createBufferID(); glBindBuffer(GL_ARRAY_BUFFER, m_normalsBufferID); glBufferData(GL_ARRAY_BUFFER, m_normals.size() * sizeof(GLfloat), &m_normals[0], GL_STATIC_DRAW); /* * Put sides triangle strip for sides into its buffer. */ m_triangleStripBufferID = createBufferID(); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_triangleStripBufferID); glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_triangleStrip.size() * sizeof(GLuint), &m_triangleStrip[0], GL_STATIC_DRAW); /* * Deselect active buffer. */ glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); #endif // BRAIN_OPENGL_INFO_SUPPORTS_VERTEX_BUFFERS break; } } /** * Draw the shape. * * @param drawMode * How to draw the shape. * @param rgba * RGBA coloring ranging 0.0 to 1.0 */ void BrainOpenGLShapeCylinder::drawShape(const BrainOpenGL::DrawMode drawMode, const float rgba[4]) { const uint8_t rgbaByte[4] = { static_cast(rgba[0] * 255.0), static_cast(rgba[1] * 255.0), static_cast(rgba[2] * 255.0), static_cast(rgba[3] * 255.0) }; drawShape(drawMode, rgbaByte); } /** * Draw the shape. * * @param drawMode * How to draw the shape. * @param rgba * RGBA coloring ranging 0 to 255. */ /** * Draw the cone. * @param drawMode * How to draw the shape. */ void BrainOpenGLShapeCylinder::drawShape(const BrainOpenGL::DrawMode drawMode, const uint8_t rgba[4]) { switch (drawMode) { case BrainOpenGL::DRAW_MODE_DISPLAY_LISTS: { if (m_displayList > 0) { if (m_isApplyColoring) { glColor4ubv(rgba); } glCallList(m_displayList); } } break; case BrainOpenGL::DRAW_MODE_IMMEDIATE: { if (m_isApplyColoring) { glColor4ubv(rgba); } // /* // * Draw the top // */ // const int32_t numTopVertices = static_cast (m_topTriangleFan.size()); // glBegin(GL_TRIANGLE_FAN); // for (int32_t i = 0; i < numTopVertices; i++) { // CaretAssertVectorIndex(m_topTriangleFan, i); // const int32_t fanIndex = m_topTriangleFan[i]; // const int32_t coordinateOffset = fanIndex * 3; // const int32_t colorOffset = fanIndex * 4; // const int32_t normalOffset = i * 3; // // CaretAssertVectorIndex(m_coordinates, coordinateOffset + 2); // CaretAssertVectorIndex(m_topNormals, normalOffset + 2); // CaretAssertVectorIndex(m_coordinatesRGBA, colorOffset + 3); // // glColor4fv(&m_coordinatesRGBA[colorOffset]); // glNormal3fv(&m_topNormals[normalOffset]); // glVertex3fv(&m_coordinates[coordinateOffset]); // } // glEnd(); // // /* // * Draw the bottom // */ // const int32_t numBottomVertices = static_cast (m_bottomTriangleFan.size()); // glBegin(GL_TRIANGLE_FAN); // for (int32_t i = 0; i < numBottomVertices; i++) { // CaretAssertVectorIndex(m_bottomTriangleFan, i); // const int32_t fanIndex = m_bottomTriangleFan[i]; // const int32_t coordinateOffset = fanIndex * 3; // const int32_t colorOffset = fanIndex * 4; // const int32_t normalOffset = i * 3; // // CaretAssertVectorIndex(m_coordinates, coordinateOffset + 2); // CaretAssertVectorIndex(m_bottomNormals, normalOffset + 2); // CaretAssertVectorIndex(m_coordinatesRGBA, colorOffset + 3); // // glColor4fv(&m_coordinatesRGBA[colorOffset]); // glNormal3fv(&m_bottomNormals[normalOffset]); // glVertex3fv(&m_coordinates[coordinateOffset]); // } // glEnd(); /* * Draw the sides */ const int32_t numSideVertices = static_cast(m_triangleStrip.size()); glBegin(GL_TRIANGLE_STRIP); for (int32_t i = 0; i < numSideVertices; i++) { CaretAssertVectorIndex(m_triangleStrip, i); const int32_t stripIndex = m_triangleStrip[i]; const int32_t coordinateOffset = stripIndex * 3; //const int32_t colorOffset = stripIndex * 4; const int32_t normalOffset = stripIndex * 3; CaretAssertVectorIndex(m_coordinates, coordinateOffset + 2); CaretAssertVectorIndex(m_normals, normalOffset + 2); //CaretAssertVectorIndex(m_rgba, colorOffset + 3); //glColor4ubv(&m_rgba[colorOffset]); glNormal3fv(&m_normals[normalOffset]); glVertex3fv(&m_coordinates[coordinateOffset]); } glEnd(); } break; case BrainOpenGL::DRAW_MODE_INVALID: { CaretAssert(0); } break; case BrainOpenGL::DRAW_MODE_VERTEX_BUFFERS: #ifdef BRAIN_OPENGL_INFO_SUPPORTS_VERTEX_BUFFERS /* * Enable vertices and normals for buffers */ glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); glEnableClientState(GL_COLOR_ARRAY); /* * Set the vertices for drawing. */ glBindBuffer(GL_ARRAY_BUFFER, m_coordinatesBufferID); glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)0); /* * Put BYTE colors into its buffer */ const int64_t numRGBA = static_cast(m_rgbaByte.size()) / 4; for (int64_t ir = 0; ir < numRGBA; ir++) { const int64_t ir4 = ir * 4; m_rgbaByte[ir4] = rgba[0]; m_rgbaByte[ir4+1] = rgba[1]; m_rgbaByte[ir4+2] = rgba[2]; m_rgbaByte[ir4+3] = rgba[3]; } glBindBuffer(GL_ARRAY_BUFFER, m_coordinatesRgbaByteBufferID); glBufferData(GL_ARRAY_BUFFER, m_rgbaByte.size() * sizeof(GLubyte), &m_rgbaByte[0], GL_DYNAMIC_DRAW); glColorPointer(4, GL_UNSIGNED_BYTE, 0, (GLvoid*)0); // /* // * Set FLOAT color components for drawing // */ // glBindBuffer(GL_ARRAY_BUFFER, // m_coordinatesRgbaFloatBufferID); // glColorPointer(4, // GL_FLOAT, // 0, // (GLvoid*)0); /* * Set the side normal vectors for drawing. */ glBindBuffer(GL_ARRAY_BUFFER, m_normalsBufferID); glNormalPointer(GL_FLOAT, 0, (GLvoid*)0); /* * Draw the side triangle strip. */ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_triangleStripBufferID); glDrawElements(GL_TRIANGLE_STRIP, m_triangleStrip.size(), GL_UNSIGNED_INT, (GLvoid*)0); /* * Draw the bottom cap triangle fans. */ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_triangleStripBufferID); glDrawElements(GL_TRIANGLE_STRIP, m_triangleStrip.size(), GL_UNSIGNED_INT, (GLvoid*)0); /* * Deselect active buffer. */ glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); /* * Disable vertices and normals for buffers. * Otherwise, bad thing will happen. */ glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_NORMAL_ARRAY); glDisableClientState(GL_COLOR_ARRAY); #endif // BRAIN_OPENGL_INFO_SUPPORTS_VERTEX_BUFFERS break; } } workbench-1.1.1/src/Brain/BrainOpenGLShapeCylinder.h000066400000000000000000000051111255417355300222520ustar00rootroot00000000000000#ifndef __BRAIN_OPEN_G_L_SHAPE_CYLINDER_H__ #define __BRAIN_OPEN_G_L_SHAPE_CYLINDER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "BrainOpenGLShape.h" namespace caret { class BrainOpenGLShapeCylinder : public BrainOpenGLShape { public: BrainOpenGLShapeCylinder(const int32_t numberOfSides); virtual ~BrainOpenGLShapeCylinder(); private: BrainOpenGLShapeCylinder(const BrainOpenGLShapeCylinder&); BrainOpenGLShapeCylinder& operator=(const BrainOpenGLShapeCylinder&); protected: void drawShape(const BrainOpenGL::DrawMode drawMode, const uint8_t rgba[4]); void drawShape(const BrainOpenGL::DrawMode drawMode, const float rgba[4]); void setupOpenGLForShape(const BrainOpenGL::DrawMode drawMode); public: // ADD_NEW_METHODS_HERE private: // ADD_NEW_MEMBERS_HERE // ADD_NEW_MEMBERS_HERE const int32_t m_numberOfSides; GLuint m_coordinatesBufferID; GLuint m_coordinatesRgbaByteBufferID; GLuint m_coordinatesRgbaFloatBufferID; GLuint m_normalsBufferID; GLuint m_triangleStripBufferID; GLuint m_displayList; std::vector m_coordinates; std::vector m_rgbaByte; std::vector m_rgbaFloat; std::vector m_normals; std::vector m_triangleStrip; bool m_isApplyColoring; }; #ifdef __BRAIN_OPEN_G_L_SHAPE_CYLINDER_DECLARE__ // #endif // __BRAIN_OPEN_G_L_SHAPE_CYLINDER_DECLARE__ } // namespace #endif //__BRAIN_OPEN_G_L_SHAPE_CYLINDER_H__ workbench-1.1.1/src/Brain/BrainOpenGLShapeRing.cxx000066400000000000000000000313701255417355300217610ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __BRAIN_OPEN_GL_SHAPE_RING_DECLARE__ #include "BrainOpenGLShapeRing.h" #undef __BRAIN_OPEN_GL_SHAPE_RING_DECLARE__ #include "CaretAssert.h" #include "MathFunctions.h" using namespace caret; /** * \class caret::BrainOpenGLShapeRing * \brief Create a conical shape for use with OpenGL. */ /** * Constructor. * @param numberOfSides * Number of sides in the ring. */ BrainOpenGLShapeRing::BrainOpenGLShapeRing(const int32_t numberOfSides, const float innerRadius, const float outerRadius) : BrainOpenGLShape(), m_numberOfSides(numberOfSides), m_innerRadius(innerRadius), m_outerRadius(outerRadius) { m_isApplyColoring = true; bool debugFlag = false; /* * Setup step size based upon number of points around circle */ // const int32_t numberOfPoints = 8; const float step = (2.0 * M_PI) / m_numberOfSides; /* * Generate points around ring */ const float z = 0.0; for (int32_t i = 0; i < m_numberOfSides; i++) { const float t = step * i; // const float x = majorRadius * std::cos(t); // const float y = minorRadius * std::sin(t); const float xin = m_innerRadius * std::cos(t); const float yin = m_innerRadius * std::sin(t); const float xout = m_outerRadius * std::cos(t); const float yout = m_outerRadius * std::sin(t); m_triangleStrip.push_back(m_coordinates.size() / 3); m_coordinates.push_back(xin); m_coordinates.push_back(yin); m_coordinates.push_back(z); m_normals.push_back(0.0); m_normals.push_back(0.0); m_normals.push_back(1.0); m_triangleStrip.push_back(m_coordinates.size() / 3); m_coordinates.push_back(xout); m_coordinates.push_back(yout); m_coordinates.push_back(z); m_normals.push_back(0.0); m_normals.push_back(0.0); m_normals.push_back(1.0); } if (m_numberOfSides > 0) { m_triangleStrip.push_back(0); m_triangleStrip.push_back(1); } if (debugFlag) { CaretAssert(m_coordinates.size() == m_normals.size()); const int32_t numPoints = static_cast(m_coordinates.size() / 3); for (int32_t i = 0; i < numPoints; i++) { const int32_t i3 = i * 3; std::cout << "points[" << i << "]=(" << AString::fromNumbers(&m_coordinates[i3], 3, ",") << ")" << std::endl; std::cout << "side normal[" << i << "]=(" << AString::fromNumbers(&m_normals[i3], 3, ",") << ")" << std::endl; } std::cout << "Sides: "; printTriangleStrip(m_triangleStrip); std::cout << std::endl; } // /* // * End Cap // */ // glBegin(GL_POLYGON); // glNormal3f(0.0, 0.0, 1.0); // for (int32_t i = 0; i < m_numberOfSides; i++) { // const int32_t i3 = i * 3; // glVertex3fv(&m_coordinates[i3]); // } // glEnd(); /* * Create storage for colors */ const int64_t numCoords = static_cast(m_coordinates.size()) / 3; const int64_t numRGBA = numCoords * 4; m_rgbaByte.resize(numRGBA * 4, 0); for (GLuint i = 0; i < numCoords; i++) { const int32_t i4 = i * 4; CaretAssertVectorIndex(m_rgbaByte, i4+3); m_rgbaByte[i4] = 0; m_rgbaByte[i4+1] = 0; m_rgbaByte[i4+2] = 0; m_rgbaByte[i4+3] = 255; } } /** * Destructor. */ BrainOpenGLShapeRing::~BrainOpenGLShapeRing() { } void BrainOpenGLShapeRing::setupOpenGLForShape(const BrainOpenGL::DrawMode drawMode) { m_displayList = 0; m_coordinatesBufferID = 0; m_coordinatesRgbaByteBufferID = 0; m_normalBufferID = 0; m_triangleStripBufferID = 0; switch (drawMode) { case BrainOpenGL::DRAW_MODE_DISPLAY_LISTS: { m_displayList = createDisplayList(); if (m_displayList > 0) { glNewList(m_displayList, GL_COMPILE); uint8_t rgbaUnused[4] = { 0, 0, 0, 0 }; m_isApplyColoring = false; drawShape(BrainOpenGL::DRAW_MODE_IMMEDIATE, rgbaUnused); m_isApplyColoring = true; glEndList(); } } break; case BrainOpenGL::DRAW_MODE_IMMEDIATE: { /* nothing to do for this case */ } break; case BrainOpenGL::DRAW_MODE_INVALID: { CaretAssert(0); } break; case BrainOpenGL::DRAW_MODE_VERTEX_BUFFERS: #ifdef BRAIN_OPENGL_INFO_SUPPORTS_VERTEX_BUFFERS /* * Put coordinates into its buffer. */ m_coordinatesBufferID = createBufferID(); glBindBuffer(GL_ARRAY_BUFFER, m_coordinatesBufferID); glBufferData(GL_ARRAY_BUFFER, m_coordinates.size() * sizeof(GLfloat), &m_coordinates[0], GL_STATIC_DRAW); /* * For RGBA coloring */ m_coordinatesRgbaByteBufferID = createBufferID(); glBindBuffer(GL_ARRAY_BUFFER, m_coordinatesRgbaByteBufferID); glBufferData(GL_ARRAY_BUFFER, m_rgbaByte.size() * sizeof(GLubyte), &m_rgbaByte[0], GL_DYNAMIC_DRAW); /* * Put side normals into its buffer. */ m_normalBufferID = createBufferID(); glBindBuffer(GL_ARRAY_BUFFER, m_normalBufferID); glBufferData(GL_ARRAY_BUFFER, m_normals.size() * sizeof(GLfloat), &m_normals[0], GL_STATIC_DRAW); /* * Put sides triangle strip into its buffer. */ m_triangleStripBufferID = createBufferID(); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_triangleStripBufferID); glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_triangleStrip.size() * sizeof(GLuint), &m_triangleStrip[0], GL_STATIC_DRAW); /* * Deselect active buffer. */ glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); #endif // BRAIN_OPENGL_INFO_SUPPORTS_VERTEX_BUFFERS break; } } /** * Draw the shape. * * @param drawMode * How to draw the shape. * @param rgba * RGBA coloring ranging 0.0 to 1.0 */ void BrainOpenGLShapeRing::drawShape(const BrainOpenGL::DrawMode drawMode, const float rgba[4]) { const uint8_t rgbaByte[4] = { static_cast(rgba[0] * 255.0), static_cast(rgba[1] * 255.0), static_cast(rgba[2] * 255.0), static_cast(rgba[3] * 255.0) }; drawShape(drawMode, rgbaByte); } /** * Draw the shape. * * @param drawMode * How to draw the shape. * @param rgba * RGBA coloring ranging 0 to 255. */ void BrainOpenGLShapeRing::drawShape(const BrainOpenGL::DrawMode drawMode, const uint8_t rgba[4]) { switch (drawMode) { case BrainOpenGL::DRAW_MODE_DISPLAY_LISTS: { if (m_displayList > 0) { if (m_isApplyColoring) { glColor4ubv(rgba); } glCallList(m_displayList); } } break; case BrainOpenGL::DRAW_MODE_IMMEDIATE: { if (m_isApplyColoring) { glColor4ubv(rgba); } const int32_t numVertices = static_cast(m_triangleStrip.size()); glBegin(GL_TRIANGLE_STRIP); for (int32_t j = 0; j < numVertices; j++) { const int32_t vertexIndex = m_triangleStrip[j] * 3; CaretAssertVectorIndex(m_normals, vertexIndex); CaretAssertVectorIndex(m_coordinates, vertexIndex); glNormal3fv(&m_normals[vertexIndex]); glVertex3fv(&m_coordinates[vertexIndex]); } glEnd(); } break; case BrainOpenGL::DRAW_MODE_INVALID: { CaretAssert(0); } break; case BrainOpenGL::DRAW_MODE_VERTEX_BUFFERS: #ifdef BRAIN_OPENGL_INFO_SUPPORTS_VERTEX_BUFFERS /* * Enable vertices and normals for buffers */ glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); glEnableClientState(GL_COLOR_ARRAY); /* * Set the vertices for drawing. */ glBindBuffer(GL_ARRAY_BUFFER, m_coordinatesBufferID); glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)0); /* * Put BYTE colors into its buffer */ const int64_t numRGBA = static_cast(m_rgbaByte.size()) / 4; for (int64_t ir = 0; ir < numRGBA; ir++) { const int64_t ir4 = ir * 4; CaretAssertVectorIndex(m_rgbaByte, ir4+3); m_rgbaByte[ir4] = rgba[0]; m_rgbaByte[ir4+1] = rgba[1]; m_rgbaByte[ir4+2] = rgba[2]; m_rgbaByte[ir4+3] = rgba[3]; } CaretAssert(glIsBuffer(m_coordinatesRgbaByteBufferID) == GL_TRUE); glBindBuffer(GL_ARRAY_BUFFER, m_coordinatesRgbaByteBufferID); glBufferData(GL_ARRAY_BUFFER, m_rgbaByte.size() * sizeof(GLubyte), &m_rgbaByte[0], GL_DYNAMIC_DRAW); glColorPointer(4, GL_UNSIGNED_BYTE, 0, (GLvoid*)0); /* * Set the side normal vectors for drawing. */ glBindBuffer(GL_ARRAY_BUFFER, m_normalBufferID); glNormalPointer(GL_FLOAT, 0, (GLvoid*)0); /* * Draw the side triangle fans. */ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_triangleStripBufferID); glDrawElements(GL_TRIANGLE_STRIP, m_triangleStrip.size(), GL_UNSIGNED_INT, (GLvoid*)0); /* * Deselect active buffer. */ glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); /* * Disable vertices and normals for buffers. * Otherwise, bad thing will happen. */ glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_NORMAL_ARRAY); glDisableClientState(GL_COLOR_ARRAY); #endif // BRAIN_OPENGL_INFO_SUPPORTS_VERTEX_BUFFERS break; } } workbench-1.1.1/src/Brain/BrainOpenGLShapeRing.h000066400000000000000000000051461255417355300214100ustar00rootroot00000000000000#ifndef __BRAIN_OPEN_GL_SHAPE_RING_H_ #define __BRAIN_OPEN_GL_SHAPE_RING_H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "BrainOpenGLShape.h" namespace caret { class BrainOpenGLShapeRing : public BrainOpenGLShape { public: BrainOpenGLShapeRing(const int32_t numberOfSides, const float innerRadius, const float outerRadius); virtual ~BrainOpenGLShapeRing(); private: BrainOpenGLShapeRing(const BrainOpenGLShapeRing&); BrainOpenGLShapeRing& operator=(const BrainOpenGLShapeRing&); public: // ADD_NEW_METHODS_HERE protected: void drawShape(const BrainOpenGL::DrawMode drawMode, const uint8_t rgba[4]); void drawShape(const BrainOpenGL::DrawMode drawMode, const float rgba[4]); void setupOpenGLForShape(const BrainOpenGL::DrawMode drawMode); private: // ADD_NEW_MEMBERS_HERE const int32_t m_numberOfSides; const float m_innerRadius; const float m_outerRadius; GLuint m_coordinatesBufferID; GLuint m_coordinatesRgbaByteBufferID; GLuint m_normalBufferID; GLuint m_triangleStripBufferID; GLuint m_displayList; std::vector m_coordinates; std::vector m_rgbaByte; std::vector m_normals; std::vector m_triangleStrip; bool m_isApplyColoring; }; #ifdef __BRAIN_OPEN_GL_SHAPE_RING_DECLARE__ // #endif // __BRAIN_OPEN_GL_SHAPE_RING_DECLARE__ } // namespace #endif //__BRAIN_OPEN_GL_SHAPE_RING_H_ workbench-1.1.1/src/Brain/BrainOpenGLShapeSphere.cxx000066400000000000000000000361661255417355300223200ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __BRAIN_OPEN_G_L_SHAPE_SPHERE_DECLARE__ #include "BrainOpenGLShapeSphere.h" #undef __BRAIN_OPEN_G_L_SHAPE_SPHERE_DECLARE__ #include "AString.h" #include "CaretAssert.h" #include "MathFunctions.h" using namespace caret; /** * \class caret::BrainOpenGLShapeSphere * \brief Create a spherical shape for use with OpenGL. */ /** * Constructor. */ BrainOpenGLShapeSphere::BrainOpenGLShapeSphere(const int32_t numberOfLatitudeAndLongitude, const float radius) : BrainOpenGLShape(), m_numberOfLatitudeAndLongitude(numberOfLatitudeAndLongitude), m_radius(radius) { m_isApplyColoring = true; bool debugFlag = false; const int numLat = m_numberOfLatitudeAndLongitude; const int numLon = m_numberOfLatitudeAndLongitude; const float degToRad = M_PI / 180.0; const float dLat = 180.0 / numLat; for (int iLat = 0; iLat < numLat; iLat++) { const float latDeg = -90.0 + (iLat * dLat); const float latRad = latDeg * degToRad; float z1 = m_radius * std::sin(latRad); const float latDeg2 = -90.0 + ((iLat + 1) * dLat); const float latRad2 = latDeg2 * degToRad; float z2 = m_radius * std::sin(latRad2); std::vector strip; if (debugFlag) std::cout << "Strip Start: " << std::endl; const float dLon = 360.0 / numLon; for (int iLon = 0; iLon <= numLon; iLon++) { const float lonDeg = iLon * dLon; const float lonRad = lonDeg * degToRad; float x1 = m_radius * std::cos(latRad) * std::cos(lonRad); float y1 = m_radius * std::cos(latRad) * std::sin(lonRad); if (iLat == 0) { x1 = 0.0; y1 = 0.0; z1 = -m_radius; } float x2 = m_radius * std::cos(latRad2) * std::cos(lonRad); float y2 = m_radius * std::cos(latRad2) * std::sin(lonRad); if (iLat == (numLat - 1)) { x2 = 0.0; y2 = 0.0; z2 = m_radius; } strip.push_back(static_cast(m_coordinates.size() / 3)); const int32_t indx1 = static_cast(m_coordinates.size() / 3); m_coordinates.push_back(x2); m_coordinates.push_back(y2); m_coordinates.push_back(z2); const float length2 = std::sqrt(x2*x2 + y2*y2 + z2*z2); m_normals.push_back(x2 / length2); m_normals.push_back(y2 / length2); m_normals.push_back(z2 / length2); strip.push_back(static_cast(m_coordinates.size() / 3)); const int32_t indx2 = static_cast(m_coordinates.size() / 3); m_coordinates.push_back(x1); m_coordinates.push_back(y1); m_coordinates.push_back(z1); const float length1 = std::sqrt(x1*x1 + y1*y1 + z1*z1); m_normals.push_back(x1 / length1); m_normals.push_back(y1 / length1); m_normals.push_back(z1 / length1); if (debugFlag) { std::cout << " " << iLat << ", " << iLon << std::endl; std::cout << " " << indx1 << ":" << latDeg << ", " << lonDeg << ", " << x1 << ", " << y1 << ", " << z1 << std::endl; std::cout << " " << indx2 << ":" << latDeg2 << ", " << lonDeg << ", " << x2 << ", " << y2 << ", " << z2 << std::endl; } } if (strip.empty() == false) { m_triangleStrips.push_back(strip); if (debugFlag) { const float* c1 = &m_coordinates[strip[0]* 3]; const float* c2 = &m_coordinates[strip[1]* 3]; const float* c3 = &m_coordinates[strip[2]* 3]; float normal[3]; MathFunctions::normalVector(c1, c2, c3, normal); std::cout << "Normal Vector" << qPrintable(AString::fromNumbers(normal, 3, ",")) << std::endl; } } if (debugFlag) std::cout << std::endl; } // if (debugFlag) { // const int32_t numTriangleStrips = m_triangleStrips.size(); // for (int32_t i = 0; i < numTriangleStrips; i++) { // std::cout << "Strip " << i << ":"; // printTriangleStrip(m_triangleStrips[i]); // } // } /* * Concatente into a single strip */ contatenateTriangleStrips(m_triangleStrips, m_singleTriangleStrip); if (debugFlag) { std::cout << "NEW STRIPS" << std::endl; const int32_t numTriangleStrips = m_triangleStrips.size(); for (int32_t i = 0; i < numTriangleStrips; i++) { std::cout << "Strip " << i << ":"; printTriangleStrip(m_triangleStrips[i]); } std::cout << "SINGLE STRIP: "; printTriangleStrip(m_singleTriangleStrip); } /* * Create storage for colors */ const int64_t numCoords = static_cast(m_coordinates.size()) / 3; const int64_t numRGBA = numCoords * 4; m_rgbaByte.resize(numRGBA * 4, 0); for (GLuint i = 0; i < numCoords; i++) { const int32_t i4 = i * 4; CaretAssertVectorIndex(m_rgbaByte, i4+3); m_rgbaByte[i4] = 0; m_rgbaByte[i4+1] = 0; m_rgbaByte[i4+2] = 0; m_rgbaByte[i4+3] = 255; } } /** * Destructor. */ BrainOpenGLShapeSphere::~BrainOpenGLShapeSphere() { } void BrainOpenGLShapeSphere::setupOpenGLForShape(const BrainOpenGL::DrawMode drawMode) { m_displayList = 0; m_vertexBufferID = 0; switch (drawMode) { case BrainOpenGL::DRAW_MODE_DISPLAY_LISTS: { m_displayList = createDisplayList(); if (m_displayList > 0) { glNewList(m_displayList, GL_COMPILE); uint8_t rgbaUnused[4] = { 0, 0, 0, 0 }; m_isApplyColoring = false; drawShape(BrainOpenGL::DRAW_MODE_IMMEDIATE, rgbaUnused); m_isApplyColoring = true; glEndList(); } } break; case BrainOpenGL::DRAW_MODE_IMMEDIATE: { /* nothing to do for this case */ } break; case BrainOpenGL::DRAW_MODE_INVALID: { CaretAssert(0); } break; case BrainOpenGL::DRAW_MODE_VERTEX_BUFFERS: #ifdef BRAIN_OPENGL_INFO_SUPPORTS_VERTEX_BUFFERS /* * Put vertices (coordinates) into its buffer. */ m_vertexBufferID = createBufferID(); glBindBuffer(GL_ARRAY_BUFFER, m_vertexBufferID); glBufferData(GL_ARRAY_BUFFER, m_coordinates.size() * sizeof(GLfloat), &m_coordinates[0], GL_STATIC_DRAW); /* * For RGBA coloring */ m_vertexRgbaByteBufferID = createBufferID(); glBindBuffer(GL_ARRAY_BUFFER, m_vertexRgbaByteBufferID); glBufferData(GL_ARRAY_BUFFER, m_rgbaByte.size() * sizeof(GLubyte), &m_rgbaByte[0], GL_DYNAMIC_DRAW); /* * Put normals into its buffer. */ m_normalBufferID = createBufferID(); glBindBuffer(GL_ARRAY_BUFFER, m_normalBufferID); glBufferData(GL_ARRAY_BUFFER, m_normals.size() * sizeof(GLfloat), &m_normals[0], GL_STATIC_DRAW); /* * Put triangle strips into its buffer. */ m_triangleStripBufferID = createBufferID(); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_triangleStripBufferID); glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_singleTriangleStrip.size() * sizeof(GLuint), &m_singleTriangleStrip[0], GL_STATIC_DRAW); /* * Deselect active buffer. */ glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); #endif // BRAIN_OPENGL_INFO_SUPPORTS_VERTEX_BUFFERS break; } } /** * Draw the shape. * * @param drawMode * How to draw the shape. * @param rgba * RGBA coloring ranging 0.0 to 1.0 */ void BrainOpenGLShapeSphere::drawShape(const BrainOpenGL::DrawMode drawMode, const float rgba[4]) { const uint8_t rgbaByte[4] = { static_cast(rgba[0] * 255.0), static_cast(rgba[1] * 255.0), static_cast(rgba[2] * 255.0), static_cast(rgba[3] * 255.0) }; drawShape(drawMode, rgbaByte); } /** * Draw the shape. * * @param drawMode * How to draw the shape. * @param rgba * RGBA coloring ranging 0 to 255. */ void BrainOpenGLShapeSphere::drawShape(const BrainOpenGL::DrawMode drawMode, const uint8_t rgba[4]) { if (m_isApplyColoring) { glColor4ubv(rgba); } bool useOneTriangleStrip = true; switch (drawMode) { case BrainOpenGL::DRAW_MODE_DISPLAY_LISTS: { if (m_displayList > 0) { glCallList(m_displayList); } } break; case BrainOpenGL::DRAW_MODE_IMMEDIATE: if (useOneTriangleStrip) { const int32_t numVertices = static_cast(m_singleTriangleStrip.size()); glBegin(GL_TRIANGLE_STRIP); for (int32_t j = 0; j < numVertices; j++) { const int32_t vertexIndex = m_singleTriangleStrip[j] * 3; CaretAssertVectorIndex(m_normals, vertexIndex); CaretAssertVectorIndex(m_coordinates, vertexIndex); glNormal3fv(&m_normals[vertexIndex]); glVertex3fv(&m_coordinates[vertexIndex]); } glEnd(); } else { const int32_t numTriangleStrips = m_triangleStrips.size(); for (int32_t i = 0; i < numTriangleStrips; i++) { const std::vector& strip = m_triangleStrips[i]; const int32_t numVertices = static_cast(strip.size()); glBegin(GL_TRIANGLE_STRIP); for (int32_t j = 0; j < numVertices; j++) { const int32_t vertexIndex = strip[j] * 3; CaretAssertVectorIndex(m_normals, vertexIndex); CaretAssertVectorIndex(m_coordinates, vertexIndex); glNormal3fv(&m_normals[vertexIndex]); glVertex3fv(&m_coordinates[vertexIndex]); } glEnd(); } } break; case BrainOpenGL::DRAW_MODE_INVALID: { CaretAssert(0); } break; case BrainOpenGL::DRAW_MODE_VERTEX_BUFFERS: #ifdef BRAIN_OPENGL_INFO_SUPPORTS_VERTEX_BUFFERS glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); glEnableClientState(GL_COLOR_ARRAY); /* * Set the vertices for drawing. */ glBindBuffer(GL_ARRAY_BUFFER, m_vertexBufferID); glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)0); /* * Put BYTE colors into its buffer */ const int64_t numRGBA = static_cast(m_rgbaByte.size()) / 4; for (int64_t ir = 0; ir < numRGBA; ir++) { const int64_t ir4 = ir * 4; m_rgbaByte[ir4] = rgba[0]; m_rgbaByte[ir4+1] = rgba[1]; m_rgbaByte[ir4+2] = rgba[2]; m_rgbaByte[ir4+3] = rgba[3]; } CaretAssert(glIsBuffer(m_vertexRgbaByteBufferID) == GL_TRUE); glBindBuffer(GL_ARRAY_BUFFER, m_vertexRgbaByteBufferID); glBufferData(GL_ARRAY_BUFFER, m_rgbaByte.size() * sizeof(GLubyte), &m_rgbaByte[0], GL_DYNAMIC_DRAW); glColorPointer(4, GL_UNSIGNED_BYTE, 0, (GLvoid*)0); /* * Set the normal vectors for drawing. */ glBindBuffer(GL_ARRAY_BUFFER, m_normalBufferID); glNormalPointer(GL_FLOAT, 0, (GLvoid*)0); /* * Draw the triangle strips. */ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_triangleStripBufferID); glDrawElements(GL_TRIANGLE_STRIP, m_singleTriangleStrip.size(), GL_UNSIGNED_INT, (GLvoid*)0); /* * Deselect active buffer. */ glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_NORMAL_ARRAY); glDisableClientState(GL_COLOR_ARRAY); #endif // BRAIN_OPENGL_INFO_SUPPORTS_VERTEX_BUFFERS break; } } workbench-1.1.1/src/Brain/BrainOpenGLShapeSphere.h000066400000000000000000000051331255417355300217330ustar00rootroot00000000000000#ifndef __BRAIN_OPEN_G_L_SHAPE_SPHERE__H_ #define __BRAIN_OPEN_G_L_SHAPE_SPHERE__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "BrainOpenGLShape.h" namespace caret { class BrainOpenGLShapeSphere : public BrainOpenGLShape { public: BrainOpenGLShapeSphere(const int32_t numberOfLatitudeAndLongitude, const float radius); virtual ~BrainOpenGLShapeSphere(); private: BrainOpenGLShapeSphere(const BrainOpenGLShapeSphere&); BrainOpenGLShapeSphere& operator=(const BrainOpenGLShapeSphere&); public: // ADD_NEW_METHODS_HERE protected: void drawShape(const BrainOpenGL::DrawMode drawMode, const uint8_t rgba[4]); void drawShape(const BrainOpenGL::DrawMode drawMode, const float rgba[4]); void setupOpenGLForShape(const BrainOpenGL::DrawMode drawMode); private: // ADD_NEW_MEMBERS_HERE const int32_t m_numberOfLatitudeAndLongitude; const float m_radius; GLuint m_vertexBufferID; GLuint m_vertexRgbaByteBufferID; GLuint m_normalBufferID; GLuint m_triangleStripBufferID; GLuint m_displayList; std::vector m_coordinates; std::vector m_normals; std::vector m_rgbaByte; std::vector > m_triangleStrips; std::vector m_singleTriangleStrip; bool m_isApplyColoring; }; #ifdef __BRAIN_OPEN_G_L_SHAPE_SPHERE_DECLARE__ // #endif // __BRAIN_OPEN_G_L_SHAPE_SPHERE_DECLARE__ } // namespace #endif //__BRAIN_OPEN_G_L_SHAPE_SPHERE__H_ workbench-1.1.1/src/Brain/BrainOpenGLTextRenderInterface.h000066400000000000000000000172071255417355300234360ustar00rootroot00000000000000#ifndef __BRAIN_OPEN_G_L_TEXT_RENDER_INTERFACE__H_ #define __BRAIN_OPEN_G_L_TEXT_RENDER_INTERFACE__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" namespace caret { /// An interface for a system that renders text in OpenGL commands class BrainOpenGLTextRenderInterface : public CaretObject { protected: /** * Constructor. */ BrainOpenGLTextRenderInterface() { } public: /** * Style of the text */ enum TextStyle { /** Bold text */ BOLD, /** Normal text */ NORMAL }; /** * Alignment of the text in X */ enum TextAlignmentX { /** Text is centered at X-coordinate */ X_CENTER, /** First character starts at X-coordinate */ X_LEFT, /** Last character ends at X-coordinate */ X_RIGHT }; /** * Alignment of the text in Y */ enum TextAlignmentY { /** Bottom of characters is at Y-coordinate */ Y_BOTTOM, /** Text is centered at Y-coordinate */ Y_CENTER, /** Top of characters is at Y-coordinate */ Y_TOP }; /** * Destructor. */ virtual ~BrainOpenGLTextRenderInterface() { } /** * Draw text at the given window coordinates. * * @param viewport * The current viewport. * @param windowX * X-coordinate in the window of first text character * using the 'alignment' * @param windowY * Y-coordinate in the window at which bottom of text is placed. * @param text * Text that is to be drawn. * @param alignmentX * Alignment of text in X * @param alignmentY * Alignment of text in Y * @param textStyle * Style of the text. * @param fontHeight * Height of the text. */ virtual void drawTextAtWindowCoords(const int viewport[4], const int windowX, const int windowY, const QString& text, const TextAlignmentX alignmentX, const TextAlignmentY alignmentY, const TextStyle textStyle = NORMAL, const int fontHeight = 14) = 0; /** * Draw text at the given window coordinates. * * @param viewport * The current viewport. * @param windowX * X-coordinate in the window of first text character * using the 'alignment' * @param windowY * Y-coordinate in the window at which bottom of text is placed. * @param text * Text that is to be drawn. * @param alignmentX * Alignment of text in X * @param alignmentY * Alignment of text in Y * @param textStyle * Style of the text. * @param fontHeight * Height of the text. */ virtual void drawVerticalTextAtWindowCoords(const int viewport[4], const int windowX, const int windowY, const QString& text, const TextAlignmentX alignmentX, const TextAlignmentY alignmentY, const TextStyle textStyle, const int fontHeight) { const int32_t numChars = text.length(); int y = windowY + ((numChars * fontHeight) / 2.0); for (int32_t i = 0; i < text.length(); i++) { drawTextAtWindowCoords(viewport, windowX, y, text[i], alignmentX, alignmentY, textStyle, fontHeight); y -= fontHeight; } } /** * Draw text at the given model coordinates. * * @param modelX * X-coordinate in model space of first text character * @param modelY * Y-coordinate in model space. * @param modelZ * Z-coordinate in model space. * @param text * Text that is to be drawn. * @param textStyle * Style of the text. * @param fontHeight * Height of the text. */ virtual void drawTextAtModelCoords(const double modelX, const double modelY, const double modelZ, const QString& text, const TextStyle textStyle, const int fontHeight) = 0; /** * Get the bounds of the text (in pixels) using the given text * attributes. * * @param widthOut * Output containing width of text characters. * @param heightOut * Output containing height of text characters. * @param text * Text that is to be drawn. * @param textStyle * Style of the text. * @param fontHeight * Height of the text. */ virtual void getTextBoundsInPixels(int32_t& widthOut, int32_t& heightOut, const QString& text, const TextStyle textStyle, const int fontHeight) = 0; /** * @return The font system is valid. */ virtual bool isValid() const = 0; /** * @return Name of the text renderer. */ virtual AString getName() const = 0; private: BrainOpenGLTextRenderInterface(const BrainOpenGLTextRenderInterface&); BrainOpenGLTextRenderInterface& operator=(const BrainOpenGLTextRenderInterface&); private: }; #ifdef __BRAIN_OPEN_G_L_TEXT_RENDER_INTERFACE_DECLARE__ // #endif // __BRAIN_OPEN_G_L_TEXT_RENDER_INTERFACE_DECLARE__ } // namespace #endif //__BRAIN_OPEN_G_L_TEXT_RENDER_INTERFACE__H_ workbench-1.1.1/src/Brain/BrainOpenGLViewportContent.cxx000066400000000000000000000162551255417355300232600ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __BRAIN_OPEN_G_L_VIEWPORT_CONTENT_DECLARE__ #include "BrainOpenGLViewportContent.h" #undef __BRAIN_OPEN_G_L_VIEWPORT_CONTENT_DECLARE__ #include "BrowserTabContent.h" #include "CaretAssert.h" using namespace caret; /** * \class BrainOpenGLViewportContent * \brief Dimensions and model for a viewport in the graphics window. * * Dimensions and model for a viewport in the graphics window. */ /** * Constructor. * @param windowViewport * Viewport of WINDOW in which drawing takes place. * @param modelViewport * Viewport for MODEL in which drawing takes place. * @param highlightTabFlag * True indicates that the tab is highlighted (used in * Tile Tabs mode so user knows graphics region corresponding * to the tab that was recently selected). * @param brain * Brain that is the source of the data. * @param browserTabContent * Tab's content that is being drawn. */ BrainOpenGLViewportContent::BrainOpenGLViewportContent(const int windowViewport[4], const int modelViewport[4], const bool highlightTabFlag, Brain* brain, BrowserTabContent* browserTabContent) : CaretObject() { m_windowViewport[0] = windowViewport[0]; m_windowViewport[1] = windowViewport[1]; m_windowViewport[2] = windowViewport[2]; m_windowViewport[3] = windowViewport[3]; m_modelViewport[0] = modelViewport[0]; m_modelViewport[1] = modelViewport[1]; m_modelViewport[2] = modelViewport[2]; m_modelViewport[3] = modelViewport[3]; m_brain = brain; m_browserTabContent = browserTabContent; m_highlightTab = highlightTabFlag; } /** * Destructor. */ BrainOpenGLViewportContent::~BrainOpenGLViewportContent() { } /** * @return True indicates that the tab is highlighted (used in * Tile Tabs mode so user knows graphics region corresponding * to the tab that was recently selected). */ bool BrainOpenGLViewportContent::isTabHighlighted() const { return m_highlightTab; } /** * Get the viewport for drawing the model. * @param modelViewport * Output into which model viewport dimensions are loaded. */ void BrainOpenGLViewportContent::getModelViewport(int modelViewport[4]) const { modelViewport[0] = m_modelViewport[0]; modelViewport[1] = m_modelViewport[1]; modelViewport[2] = m_modelViewport[2]; modelViewport[3] = m_modelViewport[3]; } /** * @return Pointer to the viewport for drawing the model. */ const int* BrainOpenGLViewportContent::getModelViewport() const { return m_modelViewport; } /** * @return Pointer to the viewport for the window. */ const int* BrainOpenGLViewportContent::getWindowViewport() const { return m_windowViewport; } Brain* BrainOpenGLViewportContent::getBrain() { return m_brain; } /** * @return Pointer to tab content in viewport. */ BrowserTabContent* BrainOpenGLViewportContent::getBrowserTabContent() { return m_browserTabContent; } /** * Get a description of this object's content. * @return String describing this object's content. */ AString BrainOpenGLViewportContent::toString() const { return "BrainOpenGLViewportContent"; } /** * Create Viewport Contents for the given tab contents, window sizes, and tile sizes. * * @paramj tabContents * Content of each tab. * @param brain * The brain. * @param windowWidth * Width of the window. * @param windowHeight * Height of the window. * @param rowHeights * Height of each row. * @param columnWidths * Width of each column. * @param hightlightTabIndex * Index of tab that is highlighted when selected by user. * @return * Vector containing data for drawing each model. */ std::vector BrainOpenGLViewportContent::createViewportContentForTileTabs(std::vector& tabContents, Brain* brain, const int32_t /*windowWidth*/, const int32_t windowHeight, const std::vector& rowHeights, const std::vector& columnWidths, const int32_t highlightTabIndex) { const int32_t numRows = static_cast(rowHeights.size()); const int32_t numCols = static_cast(columnWidths.size()); const int32_t numTabs = static_cast(tabContents.size()); std::vector viewportContentsOut; /* * Arrange models left-to-right and top-to-bottom. */ int32_t vpX = 0; int32_t vpY = windowHeight; int32_t iTab = 0; for (int32_t i = 0; i < numRows; i++) { const int32_t vpHeight = rowHeights[i]; vpX = 0; vpY -= vpHeight; for (int32_t j = 0; j < numCols; j++) { const int32_t vpWidth = columnWidths[j]; if (iTab < numTabs) { const int modelViewport[4] = { vpX, vpY, vpWidth, vpHeight }; CaretAssertVectorIndex(tabContents, iTab); BrowserTabContent* tabContent = tabContents[iTab]; const bool highlightTab = (highlightTabIndex == tabContent->getTabNumber()); BrainOpenGLViewportContent* vc = new BrainOpenGLViewportContent(modelViewport, modelViewport, highlightTab, brain, tabContent); viewportContentsOut.push_back(vc); } iTab++; vpX += vpWidth; if (iTab >= numTabs) { /* * More cells than models for drawing so set loop * indices so that loops terminate */ j = numCols; i = numRows; } } } return viewportContentsOut; } workbench-1.1.1/src/Brain/BrainOpenGLViewportContent.h000066400000000000000000000064041255417355300227000ustar00rootroot00000000000000#ifndef __BRAIN_OPEN_G_L_VIEWPORT_CONTENT__H_ #define __BRAIN_OPEN_G_L_VIEWPORT_CONTENT__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" namespace caret { class Brain; class BrowserTabContent; class BrainOpenGLViewportContent : public CaretObject { public: BrainOpenGLViewportContent(const int windowViewport[4], const int modelViewport[4], const bool highlightTabFlag, Brain* brain, BrowserTabContent* browserTabContent); ~BrainOpenGLViewportContent(); void getModelViewport(int viewport[4]) const; const int* getModelViewport() const; const int* getWindowViewport() const; BrowserTabContent* getBrowserTabContent(); Brain* getBrain(); bool isTabHighlighted() const; static std::vector createViewportContentForTileTabs(std::vector& tabContents, Brain* brain, const int32_t windowWidth, const int32_t windowHeight, const std::vector& rowHeights, const std::vector& columnWidths, const int32_t highlightTabIndex); private: int m_modelViewport[4]; int m_windowViewport[4]; BrowserTabContent* m_browserTabContent; Brain* m_brain; bool m_highlightTab; BrainOpenGLViewportContent(const BrainOpenGLViewportContent&); BrainOpenGLViewportContent& operator=(const BrainOpenGLViewportContent&); public: virtual AString toString() const; }; #ifdef __BRAIN_OPEN_G_L_VIEWPORT_CONTENT_DECLARE__ // #endif // __BRAIN_OPEN_G_L_VIEWPORT_CONTENT_DECLARE__ } // namespace #endif //__BRAIN_OPEN_G_L_VIEWPORT_CONTENT__H_ workbench-1.1.1/src/Brain/BrainOpenGLVolumeObliqueSliceDrawing.cxx000066400000000000000000007130721255417355300251730ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __BRAIN_OPEN_GL_VOLUME_OBLIQUE_SLICE_DRAWING_DECLARE__ #include "BrainOpenGLVolumeObliqueSliceDrawing.h" #undef __BRAIN_OPEN_GL_VOLUME_OBLIQUE_SLICE_DRAWING_DECLARE__ #include "BoundingBox.h" #include "Brain.h" #include "BrainOpenGLPrimitiveDrawing.h" #include "BrowserTabContent.h" #include "CaretAssert.h" #include "CaretLogger.h" #include "CaretOpenGLInclude.h" #include "CaretPreferences.h" #include "CiftiMappableDataFile.h" #include "DeveloperFlagsEnum.h" #include "DisplayPropertiesFoci.h" #include "DisplayPropertiesLabels.h" #include "ElapsedTimer.h" #include "FociFile.h" #include "Focus.h" #include "GiftiLabel.h" #include "GiftiLabelTable.h" #include "GroupAndNameHierarchyModel.h" #include "IdentificationWithColor.h" #include "LabelDrawingProperties.h" #include "MathFunctions.h" #include "Matrix4x4.h" #include "ModelVolume.h" #include "ModelWholeBrain.h" #include "NodeAndVoxelColoring.h" #include "SelectionItemFocusVolume.h" #include "SelectionItemVoxel.h" #include "SelectionItemVoxelEditing.h" #include "SelectionManager.h" #include "SessionManager.h" #include "Surface.h" #include "VolumeFile.h" #include "VolumeSurfaceOutlineColorOrTabModel.h" #include "VolumeSurfaceOutlineModel.h" #include "VolumeSurfaceOutlineSetModel.h" using namespace caret; static const bool debugFlag = false; /** * \class caret::BrainOpenGLVolumeObliqueSliceDrawing * \brief Draws volume slices using OpenGL * \ingroup Brain */ /** * Constructor. */ BrainOpenGLVolumeObliqueSliceDrawing::BrainOpenGLVolumeObliqueSliceDrawing() : CaretObject() { } /** * Destructor. */ BrainOpenGLVolumeObliqueSliceDrawing::~BrainOpenGLVolumeObliqueSliceDrawing() { } /** * Draw Volume Slices or slices for ALL Stuctures View. * * @param fixedPipelineDrawing * The OpenGL drawing. * @param browserTabContent * Content of browser tab that is to be drawn. * @param volumeDrawInfo * Info on each volume layers for drawing. * @param sliceDrawingType * Type of slice drawing (montage, single) * @param sliceProjectionType * Type of projection for the slice drawing (oblique, orthogonal) * @param viewport * The viewport (region of graphics area) for drawing slices. */ void BrainOpenGLVolumeObliqueSliceDrawing::draw(BrainOpenGLFixedPipeline* fixedPipelineDrawing, BrowserTabContent* browserTabContent, std::vector& volumeDrawInfo, const VolumeSliceDrawingTypeEnum::Enum sliceDrawingType, const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const int32_t viewport[4]) { if (volumeDrawInfo.empty()) { return; } CaretAssert(fixedPipelineDrawing); CaretAssert(browserTabContent); m_browserTabContent = browserTabContent; /* * Initialize class members which help reduce the number of * parameters that are passed to methods. */ m_brain = NULL; m_modelVolume = NULL; m_modelWholeBrain = NULL; if (m_browserTabContent->getDisplayedVolumeModel() != NULL) { m_modelVolume = m_browserTabContent->getDisplayedVolumeModel(); m_brain = m_modelVolume->getBrain(); } else if (m_browserTabContent->getDisplayedWholeBrainModel() != NULL) { m_modelWholeBrain = m_browserTabContent->getDisplayedWholeBrainModel(); m_brain = m_modelWholeBrain->getBrain(); } else { CaretAssertMessage(0, "Invalid model for volume slice drawing."); } CaretAssert(m_brain); m_fixedPipelineDrawing = fixedPipelineDrawing; m_volumeDrawInfo = volumeDrawInfo; if (m_volumeDrawInfo.empty()) { return; } m_underlayVolume = m_volumeDrawInfo[0].volumeFile; m_paletteFile = m_browserTabContent->getModelForDisplay()->getBrain()->getPaletteFile(); CaretAssert(m_paletteFile); const DisplayPropertiesLabels* dsl = m_brain->getDisplayPropertiesLabels(); m_displayGroup = dsl->getDisplayGroupForTab(m_fixedPipelineDrawing->windowTabIndex); m_tabIndex = m_browserTabContent->getTabNumber(); /* * Cifti files are slow at getting individual voxels since they * provide no access to individual voxels. The reason is that * the data may be on a server (Dense data) and accessing a single * voxel would require requesting the entire map. So, for * each Cifti file, get the enter map. This also, eliminate multiple * requests for the same map when drawing an ALL view. */ const int32_t numVolumes = static_cast(m_volumeDrawInfo.size()); for (int32_t i = 0; i < numVolumes; i++) { std::vector ciftiMapData; m_ciftiMappableFileData.push_back(ciftiMapData); const CiftiMappableDataFile* ciftiMapFile = dynamic_cast(m_volumeDrawInfo[i].volumeFile); if (ciftiMapFile != NULL) { ciftiMapFile->getMapData(m_volumeDrawInfo[i].mapIndex, m_ciftiMappableFileData[i]); } } /** END SETUP OF MEMBERS IN PARENT CLASS BrainOpenGLVolumeObliqueSliceDrawing */ if (browserTabContent->getDisplayedVolumeModel() != NULL) { drawVolumeSliceViewPlane(sliceDrawingType, sliceProjectionType, browserTabContent->getSliceViewPlane(), viewport); } else if (browserTabContent->getDisplayedWholeBrainModel() != NULL) { drawVolumeSlicesForAllStructuresView(sliceProjectionType, viewport); } } /** * Draw volume view slices for the given view plane. * * @param sliceDrawingType * Type of slice drawing (montage, single) * @param sliceProjectionType * Type of projection for the slice drawing (oblique, orthogonal) * @param sliceViewPlane * The plane for slice drawing. * @param viewport * The viewport (region of graphics area) for drawing slices. */ void BrainOpenGLVolumeObliqueSliceDrawing::drawVolumeSliceViewPlane(const VolumeSliceDrawingTypeEnum::Enum sliceDrawingType, const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const int32_t viewport[4]) { switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: { const int32_t gap = 2; const int32_t vpHalfX = viewport[2] / 2; const int32_t vpHalfY = viewport[3] / 2; /* * Draw parasagittal slice */ const int32_t paraVP[4] = { viewport[0], viewport[1] + vpHalfY + gap, vpHalfX - gap, vpHalfY - gap }; glPushMatrix(); drawVolumeSliceViewType(sliceDrawingType, sliceProjectionType, VolumeSliceViewPlaneEnum::PARASAGITTAL, paraVP); glPopMatrix(); /* * Draw coronal slice */ const int32_t coronalVP[4] = { viewport[0] + vpHalfX + gap, viewport[1] + vpHalfY + gap, vpHalfX - gap, vpHalfY - gap }; glPushMatrix(); drawVolumeSliceViewType(sliceDrawingType, sliceProjectionType, VolumeSliceViewPlaneEnum::CORONAL, coronalVP); glPopMatrix(); /* * Draw axial slice */ const int32_t axialVP[4] = { viewport[0] + vpHalfX + gap, viewport[1], vpHalfX - gap, vpHalfY - gap }; glPushMatrix(); drawVolumeSliceViewType(sliceDrawingType, sliceProjectionType, VolumeSliceViewPlaneEnum::AXIAL, axialVP); glPopMatrix(); /* * 4th quadrant is used for axis showing orientation */ const int32_t allVP[4] = { viewport[0], viewport[1], vpHalfX - gap, vpHalfY - gap }; switch (sliceProjectionType) { case VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_OBLIQUE: drawOrientationAxes(allVP); break; case VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_ORTHOGONAL: break; } } break; case VolumeSliceViewPlaneEnum::AXIAL: case VolumeSliceViewPlaneEnum::CORONAL: case VolumeSliceViewPlaneEnum::PARASAGITTAL: drawVolumeSliceViewType(sliceDrawingType, sliceProjectionType, sliceViewPlane, viewport); break; } } /** * Draw slices for the all structures view. * * @param sliceProjectionType * Type of projection for the slice drawing (oblique, orthogonal) * @param viewport * The viewport. */ void BrainOpenGLVolumeObliqueSliceDrawing::drawVolumeSlicesForAllStructuresView(const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const int32_t viewport[4]) { m_orthographicBounds[0] = m_fixedPipelineDrawing->orthographicLeft; m_orthographicBounds[1] = m_fixedPipelineDrawing->orthographicRight; m_orthographicBounds[2] = m_fixedPipelineDrawing->orthographicBottom; m_orthographicBounds[3] = m_fixedPipelineDrawing->orthographicTop; m_orthographicBounds[4] = m_fixedPipelineDrawing->orthographicNear; m_orthographicBounds[5] = m_fixedPipelineDrawing->orthographicFar; /* * Enlarge the region */ { const float left = m_fixedPipelineDrawing->orthographicLeft; const float right = m_fixedPipelineDrawing->orthographicRight; const float bottom = m_fixedPipelineDrawing->orthographicBottom; const float top = m_fixedPipelineDrawing->orthographicTop; const float scale = 2.0; const float centerX = (left + right) / 2.0; const float dx = (right - left) / 2.0; const float newLeft = centerX - (dx * scale); const float newRight = centerX + (dx * scale); const float centerY = (bottom + top) / 2.0; const float dy = (top - bottom) / 2.0; const float newBottom = centerY - (dy * scale); const float newTop = centerY + (dy * scale); m_orthographicBounds[0] = newLeft; m_orthographicBounds[1] = newRight; m_orthographicBounds[2] = newBottom; m_orthographicBounds[3] = newTop; } const float sliceCoordinates[3] = { m_browserTabContent->getSliceCoordinateParasagittal(), m_browserTabContent->getSliceCoordinateCoronal(), m_browserTabContent->getSliceCoordinateAxial() }; if (m_browserTabContent->isSliceAxialEnabled()) { glPushMatrix(); drawVolumeSliceViewProjection(VolumeSliceDrawingTypeEnum::VOLUME_SLICE_DRAW_SINGLE, sliceProjectionType, VolumeSliceViewPlaneEnum::AXIAL, sliceCoordinates, viewport); glPopMatrix(); } if (m_browserTabContent->isSliceCoronalEnabled()) { glPushMatrix(); drawVolumeSliceViewProjection(VolumeSliceDrawingTypeEnum::VOLUME_SLICE_DRAW_SINGLE, sliceProjectionType, VolumeSliceViewPlaneEnum::CORONAL, sliceCoordinates, viewport); glPopMatrix(); } if (m_browserTabContent->isSliceParasagittalEnabled()) { glPushMatrix(); drawVolumeSliceViewProjection(VolumeSliceDrawingTypeEnum::VOLUME_SLICE_DRAW_SINGLE, sliceProjectionType, VolumeSliceViewPlaneEnum::PARASAGITTAL, sliceCoordinates, viewport); glPopMatrix(); } } /** * Draw single or montage volume view slices. * * @param sliceDrawingType * Type of slice drawing (montage, single) * @param sliceProjectionType * Type of projection for the slice drawing (oblique, orthogonal) * @param sliceViewPlane * The plane for slice drawing. * @param viewport * The viewport (region of graphics area) for drawing slices. */ void BrainOpenGLVolumeObliqueSliceDrawing::drawVolumeSliceViewType(const VolumeSliceDrawingTypeEnum::Enum sliceDrawingType, const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const int32_t viewport[4]) { switch (sliceDrawingType) { case VolumeSliceDrawingTypeEnum::VOLUME_SLICE_DRAW_MONTAGE: drawVolumeSliceViewTypeMontage(sliceDrawingType, sliceProjectionType, sliceViewPlane, viewport); break; case VolumeSliceDrawingTypeEnum::VOLUME_SLICE_DRAW_SINGLE: { const float sliceCoordinates[3] = { m_browserTabContent->getSliceCoordinateParasagittal(), m_browserTabContent->getSliceCoordinateCoronal(), m_browserTabContent->getSliceCoordinateAxial() }; drawVolumeSliceViewProjection(sliceDrawingType, sliceProjectionType, sliceViewPlane, sliceCoordinates, viewport); } break; } } /** * Draw montage slices. * * @param sliceDrawingType * Type of slice drawing (montage, single) * @param sliceProjectionType * Type of projection for the slice drawing (oblique, orthogonal) * @param sliceViewPlane * The plane for slice drawing. * @param viewport * The viewport (region of graphics area) for drawing slices. */ void BrainOpenGLVolumeObliqueSliceDrawing::drawVolumeSliceViewTypeMontage(const VolumeSliceDrawingTypeEnum::Enum sliceDrawingType, const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const int32_t viewport[4]) { const int32_t numRows = m_browserTabContent->getMontageNumberOfRows(); CaretAssert(numRows > 0); const int32_t numCols = m_browserTabContent->getMontageNumberOfColumns(); CaretAssert(numCols > 0); const CaretPreferences* caretPreferences = SessionManager::get()->getCaretPreferences(); const int32_t montageMargin = caretPreferences->getVolumeMontageGap(); const int32_t montageCoordPrecision = caretPreferences->getVolumeMontageCoordinatePrecision(); const int32_t totalGapX = montageMargin * (numCols - 1); const int32_t vpSizeX = (viewport[2] - totalGapX) / numCols; const int32_t totalGapY = montageMargin * (numRows - 1); const int32_t vpSizeY = (viewport[3] - totalGapY) / numRows; /* * Voxel sizes for underlay volume */ float originX, originY, originZ; float x1, y1, z1; m_underlayVolume->indexToSpace(0, 0, 0, originX, originY, originZ); m_underlayVolume->indexToSpace(1, 1, 1, x1, y1, z1); float sliceThickness = 0.0; float sliceOrigin = 0.0; AString axisLetter = ""; float sliceCoordinates[3] = { m_browserTabContent->getSliceCoordinateParasagittal(), m_browserTabContent->getSliceCoordinateCoronal(), m_browserTabContent->getSliceCoordinateAxial() }; int32_t sliceIndex = -1; int32_t maximumSliceIndex = -1; int64_t dimI, dimJ, dimK, numMaps, numComponents; m_underlayVolume->getDimensions(dimI, dimJ, dimK, numMaps, numComponents); const int32_t sliceStep = m_browserTabContent->getMontageSliceSpacing(); //const VolumeSliceViewPlaneEnum::Enum slicePlane = m_browserTabContent->getSliceViewPlane(); switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: sliceIndex = -1; break; case VolumeSliceViewPlaneEnum::AXIAL: sliceIndex = m_browserTabContent->getSliceIndexAxial(m_underlayVolume); maximumSliceIndex = dimK; sliceThickness = z1 - originZ; sliceOrigin = originZ; axisLetter = "Z"; break; case VolumeSliceViewPlaneEnum::CORONAL: sliceIndex = m_browserTabContent->getSliceIndexCoronal(m_underlayVolume); maximumSliceIndex = dimJ; sliceThickness = y1 - originY; sliceOrigin = originY; axisLetter = "Y"; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: sliceIndex = m_browserTabContent->getSliceIndexParasagittal(m_underlayVolume); maximumSliceIndex = dimI; sliceThickness = x1 - originX; sliceOrigin = originX; axisLetter = "X"; break; } /* * Foreground color for slice coordinate text */ const CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); uint8_t foregroundRGB[3]; prefs->getColorForegroundVolumeView(foregroundRGB); const bool showCoordinates = prefs->isVolumeMontageAxesCoordinatesDisplayed(); /* * Determine a slice offset to selected slices is in * the center of the montage */ const int32_t numSlicesViewed = (numCols * numRows); const int32_t sliceOffset = ((numSlicesViewed / 2) * sliceStep); sliceIndex += sliceOffset; /* * Find first valid slice for montage */ while (sliceIndex >= 0) { if (sliceIndex < maximumSliceIndex) { break; } sliceIndex -= sliceStep; } if (sliceIndex >= 0) { for (int32_t i = 0; i < numRows; i++) { for (int32_t j = 0; j < numCols; j++) { if ((sliceIndex >= 0) && (sliceIndex < maximumSliceIndex)) { const int32_t vpX = (j * (vpSizeX + montageMargin)); const int32_t vpY = ((numRows - i - 1) * (vpSizeY + montageMargin)); int32_t vp[4] = { viewport[0] + vpX, viewport[1] + vpY, vpSizeX, vpSizeY }; if ((vp[2] <= 0) || (vp[3] <= 0)) { continue; } const float sliceCoord = (sliceOrigin + sliceThickness * sliceIndex); switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: break; case VolumeSliceViewPlaneEnum::AXIAL: sliceCoordinates[2] = sliceCoord; break; case VolumeSliceViewPlaneEnum::CORONAL: sliceCoordinates[1] = sliceCoord; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: sliceCoordinates[0] = sliceCoord; break; } drawVolumeSliceViewProjection(sliceDrawingType, sliceProjectionType, sliceViewPlane, sliceCoordinates, vp); if (showCoordinates) { const AString coordText = (axisLetter + "=" + AString::number(sliceCoord, 'f', montageCoordPrecision) + "mm"); glColor3ubv(foregroundRGB); m_fixedPipelineDrawing->drawTextWindowCoords((vpSizeX - 5), 5, coordText, BrainOpenGLTextRenderInterface::X_RIGHT, BrainOpenGLTextRenderInterface::Y_BOTTOM, BrainOpenGLTextRenderInterface::NORMAL, 12); } } sliceIndex -= sliceStep; } } } /* * Draw the axes labels for the montage view */ glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); if (prefs->isVolumeAxesLabelsDisplayed()) { drawAxesCrosshairsOrthoAndOblique(sliceProjectionType, sliceViewPlane, sliceCoordinates, false, true); } } /** * Draw a slice for either projection mode (oblique, orthogonal) * * @param sliceDrawingType * Type of slice drawing (montage, single) * @param sliceProjectionType * Type of projection for the slice drawing (oblique, orthogonal) * @param sliceViewPlane * The plane for slice drawing. * @param sliceCoordinates * Coordinates of the selected slice. * @param viewport * The viewport (region of graphics area) for drawing slices. */ void BrainOpenGLVolumeObliqueSliceDrawing::drawVolumeSliceViewProjection(const VolumeSliceDrawingTypeEnum::Enum sliceDrawingType, const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const float sliceCoordinates[3], const int32_t viewport[4]) { bool twoDimSliceViewFlag = false; if (m_modelVolume != NULL) { twoDimSliceViewFlag = true; } else if (m_modelWholeBrain != NULL) { /* nothing */ } else { CaretAssertMessage(0, "Invalid model type."); } if (twoDimSliceViewFlag) { glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); /* * Set the orthographic projection to fit the slice axis */ setOrthographicProjection(sliceViewPlane, viewport); } /* * Create the plane equation for the slice */ Plane slicePlane; createSlicePlaneEquation(sliceProjectionType, sliceViewPlane, sliceCoordinates, slicePlane); CaretAssert(slicePlane.isValidPlane()); if (slicePlane.isValidPlane() == false) { return; } if (twoDimSliceViewFlag) { /* * Set the viewing transformation (camera position) */ setVolumeSliceViewingAndModelingTransformations(sliceProjectionType, sliceViewPlane, slicePlane, sliceCoordinates); } SelectionItemVoxel* voxelID = m_brain->getSelectionManager()->getVoxelIdentification(); SelectionItemVoxelEditing* voxelEditingID = m_brain->getSelectionManager()->getVoxelEditingIdentification(); m_fixedPipelineDrawing->applyClippingPlanes(BrainOpenGLFixedPipeline::CLIPPING_DATA_TYPE_VOLUME, StructureEnum::ALL); /* * Check for a 'selection' type mode */ m_identificationModeFlag = false; switch (m_fixedPipelineDrawing->mode) { case BrainOpenGLFixedPipeline::MODE_DRAWING: break; case BrainOpenGLFixedPipeline::MODE_IDENTIFICATION: if (voxelID->isEnabledForSelection() || voxelEditingID->isEnabledForSelection()) { m_identificationModeFlag = true; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } else { return; } break; case BrainOpenGLFixedPipeline::MODE_PROJECTION: return; break; } resetIdentification(); /* * Disable culling so that both sides of the triangles/quads are drawn. */ GLboolean cullFaceOn = glIsEnabled(GL_CULL_FACE); glDisable(GL_CULL_FACE); switch (sliceProjectionType) { case VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_ORTHOGONAL: if (m_modelVolume != NULL) { const bool cullingFlag = true; if (cullingFlag) { drawOrthogonalSliceWithCulling(sliceViewPlane, sliceCoordinates, slicePlane); } else { drawOrthogonalSlice(sliceViewPlane, sliceCoordinates, slicePlane); } } else if (m_modelWholeBrain != NULL) { drawOrthogonalSlice(sliceViewPlane, sliceCoordinates, slicePlane); } break; case VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_OBLIQUE: { /* * Create the oblique slice transformation matrix */ Matrix4x4 obliqueTransformationMatrix; createObliqueTransformationMatrix(sliceCoordinates, obliqueTransformationMatrix); drawObliqueSlice(sliceViewPlane, obliqueTransformationMatrix, slicePlane); } break; } if ( ! m_identificationModeFlag) { if (slicePlane.isValidPlane()) { drawLayers(sliceDrawingType, sliceProjectionType, sliceViewPlane, slicePlane, sliceCoordinates); } } m_fixedPipelineDrawing->disableClippingPlanes(); /* * Process selection */ if (m_identificationModeFlag) { processIdentification(); } if (cullFaceOn) { glEnable(GL_CULL_FACE); } } /** * Draw an oblique slice. * * @param sliceViewPlane * The plane for slice drawing. * @param transformationMatrix * The for oblique viewing. * @param plane * Plane equation for the selected slice. */ void BrainOpenGLVolumeObliqueSliceDrawing::drawObliqueSlice(const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, Matrix4x4& transformationMatrix, const Plane& plane) { /* * When performing voxel identification for editing voxels, * we need to draw EVERY voxel since the user may click * regions where the voxels are "off". */ float voxelEditingValue = 1.0; VolumeFile* voxelEditingVolumeFile = NULL; bool volumeEditingDrawAllVoxelsFlag = false; if (m_identificationModeFlag) { SelectionItemVoxelEditing* voxelEditID = m_brain->getSelectionManager()->getVoxelEditingIdentification(); if (voxelEditID->isEnabledForSelection()) { voxelEditingVolumeFile = voxelEditID->getVolumeFileForEditing(); if (voxelEditingVolumeFile != NULL) { volumeEditingDrawAllVoxelsFlag = true; if (voxelEditingVolumeFile->isMappedWithLabelTable()) { if (voxelEditingVolumeFile->getNumberOfMaps() > 0) { voxelEditingValue = voxelEditingVolumeFile->getMapLabelTable(0)->getUnassignedLabelKey(); } } } // const VolumeFile* vf = dynamic_cast(volumeInterface); // if (vf == voxelEditID->getVolumeFileForEditing()) { // volumeEditingDrawAllVoxelsFlag = true; // } } } const bool obliqueSliceModeThreeDimFlag = false; float m[16]; glGetFloatv(GL_MODELVIEW_MATRIX, m); Matrix4x4 tm; tm.setMatrixFromOpenGL(m); // CaretLogFine("Oblique drawing matrix for slice: " // + VolumeSliceViewPlaneEnum::toGuiName(sliceViewPlane) // + tm.toFormattedString(" ")); const int32_t numVolumes = static_cast(m_volumeDrawInfo.size()); /* * Get the maximum bounds of the voxels from all slices * and the smallest voxel spacing */ float voxelBounds[6]; float voxelSpacing[3]; if (false == getVoxelCoordinateBoundsAndSpacing(voxelBounds, voxelSpacing)) { return; } float voxelSize = std::min(voxelSpacing[0], std::min(voxelSpacing[1], voxelSpacing[2])); /* * Use a larger voxel size for the 3D view in volume slice viewing * since it draws all three slices and this takes time */ if (obliqueSliceModeThreeDimFlag) { voxelSize *= 3.0; } /* * Look at point is in center of volume */ float translation[3]; m_browserTabContent->getTranslation(translation); float viewOffsetX = 0.0; float viewOffsetY = 0.0; switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: CaretAssert(0); break; case VolumeSliceViewPlaneEnum::AXIAL: viewOffsetX = (m_lookAtCenter[0] + translation[0]); viewOffsetY = (m_lookAtCenter[1] + translation[1]); break; case VolumeSliceViewPlaneEnum::CORONAL: viewOffsetX = (m_lookAtCenter[0] + translation[0]); viewOffsetY = (m_lookAtCenter[2] + translation[2]); break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: viewOffsetX = (m_lookAtCenter[1] + translation[1]); viewOffsetY = (m_lookAtCenter[2] + translation[2]); break; } float minScreenX = m_orthographicBounds[0] - viewOffsetX; float maxScreenX = m_orthographicBounds[1] - viewOffsetX; float minScreenY = m_orthographicBounds[2] - viewOffsetY; float maxScreenY = m_orthographicBounds[3] - viewOffsetY; /* * Get origin voxel IJK */ const float zeroXYZ[3] = { 0.0, 0.0, 0.0 }; int64_t originIJK[3]; m_volumeDrawInfo[0].volumeFile->enclosingVoxel(zeroXYZ[0], zeroXYZ[1], zeroXYZ[2], originIJK[0], originIJK[1], originIJK[2]); /* * Get XYZ center of origin Voxel */ float originVoxelXYZ[3]; m_volumeDrawInfo[0].volumeFile->indexToSpace(originIJK, originVoxelXYZ); float actualOrigin[3]; m_volumeDrawInfo[0].volumeFile->indexToSpace(originIJK, actualOrigin); float screenOffsetX = 0.0; float screenOffsetY = 0.0; float originOffsetX = 0.0; float originOffsetY = 0.0; switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: CaretAssert(0); break; case VolumeSliceViewPlaneEnum::AXIAL: screenOffsetX = m_lookAtCenter[0]; screenOffsetY = m_lookAtCenter[1]; originOffsetX = actualOrigin[0]; originOffsetY = actualOrigin[1]; break; case VolumeSliceViewPlaneEnum::CORONAL: screenOffsetX = m_lookAtCenter[0]; screenOffsetY = m_lookAtCenter[2]; originOffsetX = actualOrigin[0]; originOffsetY = actualOrigin[2]; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: screenOffsetX = m_lookAtCenter[1]; screenOffsetY = m_lookAtCenter[2]; originOffsetX = actualOrigin[1]; originOffsetY = actualOrigin[2]; break; } const int32_t alignVoxelsFlag = 1; if (alignVoxelsFlag == 1) { /* * Adjust for when selected slices are not at the origin */ const float xOffset = MathFunctions::remainder(screenOffsetX, voxelSize); const float yOffset = MathFunctions::remainder(screenOffsetY, voxelSize); originOffsetX -= xOffset; originOffsetY -= yOffset; const int64_t numVoxelsToLeft = static_cast(MathFunctions::round(minScreenX + originOffsetX) / voxelSize); const int64_t numVoxelsToRight = static_cast(MathFunctions::round(maxScreenX + originOffsetX) / voxelSize); const int64_t numVoxelsToBottom = static_cast(MathFunctions::round(minScreenY + originOffsetY) / voxelSize); const int64_t numVoxelsToTop = static_cast(MathFunctions::round(maxScreenY + originOffsetY)/ voxelSize); const float halfVoxel = voxelSize / 2.0; const float firstVoxelCenterX = (numVoxelsToLeft * voxelSize) + originOffsetX; const float lastVoxelCenterX = (numVoxelsToRight * voxelSize) + originOffsetX; const float firstVoxelCenterY = (numVoxelsToBottom * voxelSize) + originOffsetY; const float lastVoxelCenterY = (numVoxelsToTop * voxelSize) + originOffsetY; float newMinScreenX = firstVoxelCenterX - halfVoxel; float newMaxScreenX = lastVoxelCenterX + halfVoxel; float newMinScreenY = firstVoxelCenterY - halfVoxel; float newMaxScreenY = lastVoxelCenterY + halfVoxel; if (debugFlag) { const AString msg2 = ("Origin Voxel Coordinate: (" + AString::fromNumbers(actualOrigin, 3, ",") + "\n Oblique Screen X: (" + AString::number(minScreenX) + "," + AString::number(maxScreenX) + ") Y: (" + AString::number(minScreenY) + "," + AString::number(maxScreenY) + ")\nNew X: (" + AString::number(newMinScreenX) + "," + AString::number(newMaxScreenX) + ") Y: (" + AString::number(newMinScreenY) + "," + AString::number(newMaxScreenY) + ") Diff: (" + AString::number((newMaxScreenX - newMinScreenX) / voxelSize) + "," + AString::number((newMaxScreenY - newMinScreenY) / voxelSize) + ")"); std::cout << qPrintable(msg2) << std::endl; } minScreenX = newMinScreenX; maxScreenX = newMaxScreenX; minScreenY = newMinScreenY; maxScreenY = newMaxScreenY; } if (alignVoxelsFlag == 2) { // CaretLogFine("Oblique Screen X: (" // + AString::number(minScreenX) // + "," // + AString::number(maxScreenX) // + ") Y: (" // + AString::number(minScreenY) + "," // + AString::number(maxScreenY) // + ")"); const float quarterVoxelSize = voxelSize / 4.0; float newMinScreenX = (static_cast(minScreenX / voxelSize) * voxelSize) + quarterVoxelSize; float newMaxScreenX = (static_cast(maxScreenX / voxelSize) * voxelSize) - quarterVoxelSize; float newMinScreenY = (static_cast(minScreenY / voxelSize) * voxelSize) + quarterVoxelSize; float newMaxScreenY = (static_cast(maxScreenY / voxelSize) * voxelSize) - quarterVoxelSize; // CaretLogFine("NEW Oblique Screen MinX: " // + AString::number(newMinScreenX) + " MaxX: " // + AString::number(newMaxScreenX) + " MinY: " // + AString::number(newMinScreenY) + " MaxY: " // + AString::number(newMaxScreenY)); minScreenX = newMinScreenX; maxScreenX = newMaxScreenX; minScreenY = newMinScreenY; maxScreenY = newMaxScreenY; } /* * Set the corners of the screen for the respective view */ float bottomLeft[3]; float bottomRight[3]; float topRight[3]; float topLeft[3]; switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: CaretAssert(0); break; case VolumeSliceViewPlaneEnum::AXIAL: bottomLeft[0] = minScreenX; bottomLeft[1] = minScreenY; bottomLeft[2] = 0.0; bottomRight[0] = maxScreenX; bottomRight[1] = minScreenY; bottomRight[2] = 0.0; topRight[0] = maxScreenX; topRight[1] = maxScreenY; topRight[2] = 0.0; topLeft[0] = minScreenX; topLeft[1] = maxScreenY; topLeft[2] = 0.0; break; case VolumeSliceViewPlaneEnum::CORONAL: bottomLeft[0] = minScreenX; bottomLeft[1] = 0.0; bottomLeft[2] = minScreenY; bottomRight[0] = maxScreenX; bottomRight[1] = 0.0; bottomRight[2] = minScreenY; topRight[0] = maxScreenX; topRight[1] = 0.0; topRight[2] = maxScreenY; topLeft[0] = minScreenX; topLeft[1] = 0.0; topLeft[2] = maxScreenY; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: bottomLeft[0] = 0.0; bottomLeft[1] = minScreenX; bottomLeft[2] = minScreenY; bottomRight[0] = 0.0; bottomRight[1] = maxScreenX; bottomRight[2] = minScreenY; topRight[0] = 0.0; topRight[1] = maxScreenX; topRight[2] = maxScreenY; topLeft[0] = 0.0; topLeft[1] = minScreenX; topLeft[2] = maxScreenY; break; } /* * Transform the corners of the screen into model coordinates */ transformationMatrix.multiplyPoint3(bottomLeft); transformationMatrix.multiplyPoint3(bottomRight); transformationMatrix.multiplyPoint3(topRight); transformationMatrix.multiplyPoint3(topLeft); if (debugFlag) { const double bottomDist = MathFunctions::distance3D(bottomLeft, bottomRight); const double topDist = MathFunctions::distance3D(topLeft, topRight); const double bottomVoxels = bottomDist / voxelSize; const double topVoxels = topDist / voxelSize; const AString msg = ("Bottom Dist: " + AString::number(bottomDist) + " voxel size: " + AString::number(bottomVoxels) + " Top Dist: " + AString::number(bottomDist) + " voxel size: " + AString::number(topVoxels)); std::cout << qPrintable(msg) << std::endl; } // CaretLogFine("Oblique BL: " + AString::fromNumbers(bottomLeft, 3, ",") // + " BR: " + AString::fromNumbers(bottomRight, 3, ",") // + " TR: " + AString::fromNumbers(topRight, 3, ",") // + " TL: " + AString::fromNumbers(topLeft, 3, ",")); if (debugFlag) { m_fixedPipelineDrawing->setLineWidth(3.0); glColor3f(1.0, 0.0, 0.0); glBegin(GL_LINE_LOOP); glVertex3fv(bottomLeft); glVertex3fv(bottomRight); glVertex3fv(topRight); glVertex3fv(topLeft); glEnd(); } /* * Unit vector and distance in model coords along left side of screen */ double bottomLeftToTopLeftUnitVector[3] = { topLeft[0] - bottomLeft[0], topLeft[1] - bottomLeft[1], topLeft[2] - bottomLeft[2], }; MathFunctions::normalizeVector(bottomLeftToTopLeftUnitVector); const double bottomLeftToTopLeftDistance = MathFunctions::distance3D(bottomLeft, topLeft); /* * Unit vector and distance in model coords along right side of screen */ double bottomRightToTopRightUnitVector[3] = { topRight[0] - bottomRight[0], topRight[1] - bottomRight[1], topRight[2] - bottomRight[2] }; MathFunctions::normalizeVector(bottomRightToTopRightUnitVector); const double bottomRightToTopRightDistance = MathFunctions::distance3D(bottomRight, topRight); /* * For fastest coloring, need to color data values as a group */ std::vector volumeSlices; for (int32_t i = 0; i < numVolumes; i++) { volumeSlices.push_back(VolumeSlice(m_volumeDrawInfo[i].volumeFile, m_volumeDrawInfo[i].mapIndex)); } bool showFirstVoxelCoordFlag = debugFlag; /* * Track voxels that will be drawn */ std::vector voxelsToDraw; if ((bottomLeftToTopLeftDistance > 0) && (bottomRightToTopRightDistance > 0)) { const double bottomLeftToTopLeftStep = voxelSize; const double numLeftSteps = (bottomLeftToTopLeftDistance / bottomLeftToTopLeftStep); const double bottomRightToTopRightStep = (bottomRightToTopRightDistance / numLeftSteps); const double dtVertical = bottomLeftToTopLeftStep / bottomLeftToTopLeftDistance; /* * Voxels are drawn in rows, left to right, across the screen, * starting at the bottom. */ double leftEdgeBottomCoord[3]; double leftEdgeTopCoord[3]; double rightEdgeBottomCoord[3]; double rightEdgeTopCoord[3]; for (double tVertical = 0.0, dLeft = 0.0, dRight = 0.0; tVertical < 1.0; tVertical += dtVertical, dLeft += bottomLeftToTopLeftStep, dRight += bottomRightToTopRightStep) { /* * Coordinate on left edge at BOTTOM of current row */ leftEdgeBottomCoord[0] = bottomLeft[0] + (dLeft * bottomLeftToTopLeftUnitVector[0]); leftEdgeBottomCoord[1] = bottomLeft[1] + (dLeft * bottomLeftToTopLeftUnitVector[1]); leftEdgeBottomCoord[2] = bottomLeft[2] + (dLeft * bottomLeftToTopLeftUnitVector[2]); /* * Coordinate on right edge at BOTTOM of current row */ rightEdgeBottomCoord[0] = bottomRight[0] + (dRight * bottomRightToTopRightUnitVector[0]); rightEdgeBottomCoord[1] = bottomRight[1] + (dRight * bottomRightToTopRightUnitVector[1]); rightEdgeBottomCoord[2] = bottomRight[2] + (dRight * bottomRightToTopRightUnitVector[2]); /* * Coordinate on left edge at TOP of current row */ leftEdgeTopCoord[0] = bottomLeft[0] + ((dLeft + bottomLeftToTopLeftStep) * bottomLeftToTopLeftUnitVector[0]); leftEdgeTopCoord[1] = bottomLeft[1] + ((dLeft + bottomLeftToTopLeftStep) * bottomLeftToTopLeftUnitVector[1]); leftEdgeTopCoord[2] = bottomLeft[2] + ((dLeft + bottomLeftToTopLeftStep) * bottomLeftToTopLeftUnitVector[2]); /* * Coordinate on right edge at TOP of current row */ rightEdgeTopCoord[0] = bottomRight[0] + ((dRight + bottomRightToTopRightStep) * bottomRightToTopRightUnitVector[0]); rightEdgeTopCoord[1] = bottomRight[1] + ((dRight + bottomRightToTopRightStep) * bottomRightToTopRightUnitVector[1]); rightEdgeTopCoord[2] = bottomRight[2] + ((dRight + bottomRightToTopRightStep) * bottomRightToTopRightUnitVector[2]); /* * Determine change in XYZ per voxel along the bottom of the current row */ const double bottomVoxelEdgeDistance = MathFunctions::distance3D(leftEdgeBottomCoord, rightEdgeBottomCoord); double bottomEdgeUnitVector[3]; MathFunctions::createUnitVector(leftEdgeBottomCoord, rightEdgeBottomCoord, bottomEdgeUnitVector); const double numVoxelsInRowFloat = bottomVoxelEdgeDistance / voxelSize; const int64_t numVoxelsInRow = MathFunctions::round(numVoxelsInRowFloat); const double bottomEdgeVoxelSize = bottomVoxelEdgeDistance / numVoxelsInRow; const double bottomVoxelEdgeDX = bottomEdgeVoxelSize * bottomEdgeUnitVector[0]; const double bottomVoxelEdgeDY = bottomEdgeVoxelSize * bottomEdgeUnitVector[1]; const double bottomVoxelEdgeDZ = bottomEdgeVoxelSize * bottomEdgeUnitVector[2]; /* * Determine change in XYZ per voxel along top of the current row */ const double topVoxelEdgeDistance = MathFunctions::distance3D(leftEdgeTopCoord, rightEdgeTopCoord); double topEdgeUnitVector[3]; MathFunctions::createUnitVector(leftEdgeTopCoord, rightEdgeTopCoord, topEdgeUnitVector); const double topEdgeVoxelSize = topVoxelEdgeDistance / numVoxelsInRow; const double topVoxelEdgeDX = topEdgeVoxelSize * topEdgeUnitVector[0]; const double topVoxelEdgeDY = topEdgeVoxelSize * topEdgeUnitVector[1]; const double topVoxelEdgeDZ = topEdgeVoxelSize * topEdgeUnitVector[2]; /* * Initialize bottom and top left coordinate of first voxel in row */ double bottomLeftVoxelCoord[3] = { leftEdgeBottomCoord[0], leftEdgeBottomCoord[1], leftEdgeBottomCoord[2] }; double topLeftVoxelCoord[3] = { leftEdgeTopCoord[0], leftEdgeTopCoord[1], leftEdgeTopCoord[2] }; const bool useInterpolatedVoxel = true; /* * Draw the voxels in the row */ for (int64_t i = 0; i < numVoxelsInRow; i++) { /* * Top right corner of voxel */ const double topRightVoxelCoord[3] = { topLeftVoxelCoord[0] + topVoxelEdgeDX, topLeftVoxelCoord[1] + topVoxelEdgeDY, topLeftVoxelCoord[2] + topVoxelEdgeDZ }; const float voxelCenter[3] = { (bottomLeftVoxelCoord[0] + topRightVoxelCoord[0]) * 0.5, (bottomLeftVoxelCoord[1] + topRightVoxelCoord[1]) * 0.5, (bottomLeftVoxelCoord[2] + topRightVoxelCoord[2]) * 0.5 }; bool printOriginVoxelInfo = false; if (debugFlag) { switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: break; case VolumeSliceViewPlaneEnum::AXIAL: if (showFirstVoxelCoordFlag) { const float dist = voxelCenter[0] - actualOrigin[0]; const AString msg = ("First Voxel Center: " + AString::fromNumbers(voxelCenter, 3, ",") + " Dist from origin voxel in X: " + AString::number(dist) + " Number of voxels between: " + AString::number(dist / voxelSize)); std::cout << qPrintable(msg) << std::endl; showFirstVoxelCoordFlag = false; } if ((bottomLeftVoxelCoord[0] < actualOrigin[0]) && (topRightVoxelCoord[0] > actualOrigin[0])) { if ((bottomLeftVoxelCoord[1] < actualOrigin[1]) && (topRightVoxelCoord[1] > actualOrigin[1])) { printOriginVoxelInfo = true; } } break; case VolumeSliceViewPlaneEnum::CORONAL: if (showFirstVoxelCoordFlag) { const float dist = voxelCenter[0] - actualOrigin[0]; const AString msg = ("First Voxel Center: " + AString::fromNumbers(voxelCenter, 3, ",") + " Dist from origin voxel in X: " + AString::number(dist) + " Number of voxels between: " + AString::number(dist / voxelSize)); std::cout << qPrintable(msg) << std::endl; showFirstVoxelCoordFlag = false; } if ((bottomLeftVoxelCoord[0] < actualOrigin[0]) && (topRightVoxelCoord[0] > actualOrigin[0])) { if ((bottomLeftVoxelCoord[2] < actualOrigin[2]) && (topRightVoxelCoord[2] > actualOrigin[2])) { printOriginVoxelInfo = true; } } break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: if (showFirstVoxelCoordFlag) { const float dist = voxelCenter[1] - actualOrigin[1]; const AString msg = ("First Voxel Center: " + AString::fromNumbers(voxelCenter, 3, ",") + " Dist from origin voxel in Y: " + AString::number(dist) + " Number of voxels between: " + AString::number(dist / voxelSize)); std::cout << qPrintable(msg) << std::endl; showFirstVoxelCoordFlag = false; } if ((bottomLeftVoxelCoord[1] < actualOrigin[1]) && (topRightVoxelCoord[1] > actualOrigin[1])) { if ((bottomLeftVoxelCoord[2] < actualOrigin[2]) && (topRightVoxelCoord[2] > actualOrigin[2])) { printOriginVoxelInfo = true; } } break; } } if (printOriginVoxelInfo) { const AString msg = ("Origin voxel center when drawn is " + AString::fromNumbers(voxelCenter, 3, ",") + " but should be " + AString::fromNumbers(actualOrigin, 3, ",") + " Voxel Corners: (" + AString::fromNumbers(bottomLeftVoxelCoord, 3, ",") + ") (" + AString::fromNumbers(topRightVoxelCoord, 3, ",") + ")"); std::cout << qPrintable(msg) << std::endl; } /* * Loop through the volumes selected as overlays. */ VoxelToDraw* voxelDrawingInfo = NULL; for (int32_t iVol = 0; iVol < numVolumes; iVol++) { const BrainOpenGLFixedPipeline::VolumeDrawInfo& vdi = m_volumeDrawInfo[iVol]; const VolumeMappableInterface* volInter = vdi.volumeFile; const VolumeFile* volumeFile = volumeSlices[iVol].m_volumeFile; float value = 0; bool valueValidFlag = false; bool isPaletteMappedVolumeFile = false; if (volumeFile != NULL) { if (volumeFile->isMappedWithPalette()) { isPaletteMappedVolumeFile = true; } } const CiftiMappableDataFile* ciftiMappableFile = volumeSlices[iVol].m_ciftiMappableDataFile; if (useInterpolatedVoxel && isPaletteMappedVolumeFile) { value = volumeFile->interpolateValue(voxelCenter, VolumeFile::CUBIC, &valueValidFlag, vdi.mapIndex); } else if (ciftiMappableFile != NULL) { const int64_t voxelOffset = ciftiMappableFile->getMapDataOffsetForVoxelAtCoordinate(voxelCenter, vdi.mapIndex); if (voxelOffset >= 0) { CaretAssertVectorIndex(m_ciftiMappableFileData, iVol); const std::vector& data = m_ciftiMappableFileData[iVol]; CaretAssertVectorIndex(data, voxelOffset); value = data[voxelOffset]; valueValidFlag = true; } } else { value = volInter->getVoxelValue(voxelCenter, &valueValidFlag, vdi.mapIndex); } /* * Need to draw all voxels when editing */ if (volumeEditingDrawAllVoxelsFlag) { if (! valueValidFlag) { if (volumeFile != NULL) { if (volumeFile == voxelEditingVolumeFile) { value = voxelEditingValue; valueValidFlag = true; } } } } if (valueValidFlag) { if (voxelDrawingInfo == NULL) { /* * Bottom right corner of voxel */ const double bottomRightVoxelCoord[3] = { bottomLeftVoxelCoord[0] + bottomVoxelEdgeDX, bottomLeftVoxelCoord[1] + bottomVoxelEdgeDY, bottomLeftVoxelCoord[2] + bottomVoxelEdgeDZ }; /* * Top right corner of voxel */ const double topRightVoxelCoord[3] = { topLeftVoxelCoord[0] + topVoxelEdgeDX, topLeftVoxelCoord[1] + topVoxelEdgeDY, topLeftVoxelCoord[2] + topVoxelEdgeDZ }; voxelDrawingInfo = new VoxelToDraw(voxelCenter, bottomLeftVoxelCoord, bottomRightVoxelCoord, topRightVoxelCoord, topLeftVoxelCoord); voxelsToDraw.push_back(voxelDrawingInfo); } const int64_t offset = volumeSlices[iVol].addValue(value); voxelDrawingInfo->addVolumeValue(iVol, offset); } } /* * Move to the next voxel in the row */ bottomLeftVoxelCoord[0] += bottomVoxelEdgeDX; bottomLeftVoxelCoord[1] += bottomVoxelEdgeDY; bottomLeftVoxelCoord[2] += bottomVoxelEdgeDZ; topLeftVoxelCoord[0] += topVoxelEdgeDX; topLeftVoxelCoord[1] += topVoxelEdgeDY; topLeftVoxelCoord[2] += topVoxelEdgeDZ; } } } const int32_t browserTabIndex = m_browserTabContent->getTabNumber(); const DisplayPropertiesLabels* displayPropertiesLabels = m_brain->getDisplayPropertiesLabels(); const DisplayGroupEnum::Enum displayGroup = displayPropertiesLabels->getDisplayGroupForTab(browserTabIndex); /* * Color voxel values */ for (int32_t i = 0; i < numVolumes; i++) { const int64_t numValues = static_cast(volumeSlices[i].m_values.size()); if (numValues > 0) { volumeSlices[i].allocateColors(); VolumeMappableInterface* volume = volumeSlices[i].m_volumeMappableInterface; CaretMappableDataFile* mappableFile = dynamic_cast(volume); VolumeFile* volumeFile = volumeSlices[i].m_volumeFile; CaretAssert(mappableFile); const int32_t mapIndex = volumeSlices[i].m_mapIndex; const float* values = &volumeSlices[i].m_values[0]; uint8_t* rgba = &volumeSlices[i].m_rgba[0]; if (volumeEditingDrawAllVoxelsFlag && (volumeFile == voxelEditingVolumeFile)) { for (int64_t i = 0; i < numValues; i++) { const int64_t i4 = i * 4; rgba[i4] = 255; rgba[i4+1] = 255; rgba[i4+2] = 255; rgba[i4+3] = 255; } } else if (mappableFile->isMappedWithPalette()) { const PaletteColorMapping* paletteColorMapping = mappableFile->getMapPaletteColorMapping(mapIndex); const AString paletteName = paletteColorMapping->getSelectedPaletteName(); const Palette* palette = m_paletteFile->getPaletteByName(paletteName); if (palette != NULL) { CaretAssertVectorIndex(m_volumeDrawInfo, i); NodeAndVoxelColoring::colorScalarsWithPalette(m_volumeDrawInfo[i].statistics, paletteColorMapping, palette, values, values, numValues, rgba); } else { CaretLogWarning("Missing palette named: " + paletteName); } } else if (mappableFile->isMappedWithLabelTable()) { GiftiLabelTable* labelTable = mappableFile->getMapLabelTable(mapIndex); NodeAndVoxelColoring::colorIndicesWithLabelTableForDisplayGroupTab(labelTable, values, numValues, displayGroup, browserTabIndex, rgba); } else { CaretAssert(0); } } } const int64_t numVoxelsToDraw = static_cast(voxelsToDraw.size()); /* * quadCoords is the coordinates for all four corners of a 'quad' * that is used to draw a voxel. quadRGBA is the colors for each * voxel drawn as a 'quad'. */ std::vector quadCoordsVector; std::vector quadNormalsVector; std::vector quadRGBAsVector; /* * Reserve space to avoid reallocations */ const int64_t coordinatesPerQuad = 4; const int64_t componentsPerCoordinate = 3; const int64_t colorComponentsPerCoordinate = 4; quadCoordsVector.resize(numVoxelsToDraw * coordinatesPerQuad * componentsPerCoordinate); quadNormalsVector.resize(quadCoordsVector.size()); quadRGBAsVector.resize(numVoxelsToDraw * coordinatesPerQuad * colorComponentsPerCoordinate); int64_t coordOffset = 0; int64_t normalOffset = 0; int64_t rgbaOffset = 0; float* quadCoords = &quadCoordsVector[0]; float* quadNormals = &quadNormalsVector[0]; uint8_t* quadRGBAs = &quadRGBAsVector[0]; for (int64_t iVox = 0; iVox < numVoxelsToDraw; iVox++) { CaretAssertVectorIndex(voxelsToDraw, iVox); VoxelToDraw* vtd = voxelsToDraw[iVox]; CaretAssert(vtd); uint8_t voxelRGBA[4] = { 0, 0, 0, 0 }; const int32_t numSlicesForVoxel = static_cast(vtd->m_sliceIndices.size()); for (int32_t iSlice = 0; iSlice < numSlicesForVoxel; iSlice++) { CaretAssertVectorIndex(vtd->m_sliceIndices, iSlice); CaretAssertVectorIndex(vtd->m_sliceOffsets, iSlice); const int32_t sliceIndex = vtd->m_sliceIndices[iSlice]; const int64_t voxelOffset = vtd->m_sliceOffsets[iSlice]; const uint8_t* rgba = volumeSlices[sliceIndex].getRgbaForValueByIndex(voxelOffset); if (rgba[3] > 0) { voxelRGBA[0] = rgba[0]; voxelRGBA[1] = rgba[1]; voxelRGBA[2] = rgba[2]; voxelRGBA[3] = rgba[3]; if (m_identificationModeFlag) { VolumeMappableInterface* volMap = volumeSlices[sliceIndex].m_volumeMappableInterface; int64_t voxelI, voxelJ, voxelK; volMap->enclosingVoxel(vtd->m_center[0], vtd->m_center[1], vtd->m_center[2], voxelI, voxelJ, voxelK); if (volMap->indexValid(voxelI, voxelJ, voxelK)) { float diffXYZ[3]; vtd->getDiffXYZ(diffXYZ); addVoxelToIdentification(sliceIndex, volumeSlices[sliceIndex].m_mapIndex, voxelI, voxelJ, voxelK, diffXYZ, voxelRGBA); } } } } if (voxelRGBA[3] > 0) { float sliceNormalVector[3]; plane.getNormalVector(sliceNormalVector); CaretAssertVectorIndex(quadRGBAsVector, rgbaOffset + 3); quadRGBAs[rgbaOffset] = voxelRGBA[0]; quadRGBAs[rgbaOffset+1] = voxelRGBA[1]; quadRGBAs[rgbaOffset+2] = voxelRGBA[2]; quadRGBAs[rgbaOffset+3] = voxelRGBA[3]; rgbaOffset += 4; CaretAssertVectorIndex(quadNormalsVector, normalOffset + 2); quadNormals[normalOffset] = sliceNormalVector[0]; quadNormals[normalOffset+1] = sliceNormalVector[1]; quadNormals[normalOffset+2] = sliceNormalVector[2]; normalOffset += 3; CaretAssertVectorIndex(quadRGBAsVector, rgbaOffset + 3); quadRGBAs[rgbaOffset] = voxelRGBA[0]; quadRGBAs[rgbaOffset+1] = voxelRGBA[1]; quadRGBAs[rgbaOffset+2] = voxelRGBA[2]; quadRGBAs[rgbaOffset+3] = voxelRGBA[3]; rgbaOffset += 4; CaretAssertVectorIndex(quadNormalsVector, normalOffset + 2); quadNormals[normalOffset] = sliceNormalVector[0]; quadNormals[normalOffset+1] = sliceNormalVector[1]; quadNormals[normalOffset+2] = sliceNormalVector[2]; normalOffset += 3; CaretAssertVectorIndex(quadRGBAsVector, rgbaOffset + 3); quadRGBAs[rgbaOffset] = voxelRGBA[0]; quadRGBAs[rgbaOffset+1] = voxelRGBA[1]; quadRGBAs[rgbaOffset+2] = voxelRGBA[2]; quadRGBAs[rgbaOffset+3] = voxelRGBA[3]; rgbaOffset += 4; CaretAssertVectorIndex(quadNormalsVector, normalOffset + 2); quadNormals[normalOffset] = sliceNormalVector[0]; quadNormals[normalOffset+1] = sliceNormalVector[1]; quadNormals[normalOffset+2] = sliceNormalVector[2]; normalOffset += 3; CaretAssertVectorIndex(quadRGBAsVector, rgbaOffset + 3); quadRGBAs[rgbaOffset] = voxelRGBA[0]; quadRGBAs[rgbaOffset+1] = voxelRGBA[1]; quadRGBAs[rgbaOffset+2] = voxelRGBA[2]; quadRGBAs[rgbaOffset+3] = voxelRGBA[3]; rgbaOffset += 4; CaretAssertVectorIndex(quadNormalsVector, normalOffset + 2); quadNormals[normalOffset] = sliceNormalVector[0]; quadNormals[normalOffset+1] = sliceNormalVector[1]; quadNormals[normalOffset+2] = sliceNormalVector[2]; normalOffset += 3; CaretAssertVectorIndex(quadCoordsVector, coordOffset + 11); for (int32_t iq = 0; iq < 12; iq++) { quadCoords[coordOffset + iq] = vtd->m_coordinates[iq]; } coordOffset += 12; } } quadCoordsVector.resize(coordOffset); quadNormalsVector.resize(normalOffset); quadRGBAsVector.resize(rgbaOffset); for (std::vector::iterator iter = voxelsToDraw.begin(); iter != voxelsToDraw.end(); iter++) { VoxelToDraw* vtd = *iter; delete vtd; } voxelsToDraw.clear(); if ( ! quadCoordsVector.empty()) { glPushMatrix(); BrainOpenGLPrimitiveDrawing::drawQuads(quadCoordsVector, quadNormalsVector, quadRGBAsVector); glPopMatrix(); } } /** * Draw an orthogonal slice. * * @param sliceViewPlane * The plane for slice drawing. * @param sliceCoordinates * Coordinates of the selected slice. * @param plane * Plane equation for the selected slice. */ void BrainOpenGLVolumeObliqueSliceDrawing::drawOrthogonalSlice(const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const float sliceCoordinates[3], const Plane& plane) { const int32_t browserTabIndex = m_browserTabContent->getTabNumber(); const DisplayPropertiesLabels* displayPropertiesLabels = m_brain->getDisplayPropertiesLabels(); const DisplayGroupEnum::Enum displayGroup = displayPropertiesLabels->getDisplayGroupForTab(browserTabIndex); // switch (labelDrawingType) { // case LabelDrawingTypeEnum::DRAW_FILLED_LABEL_COLOR: // break; // case LabelDrawingTypeEnum::DRAW_FILLED_BLACK_OUTLINE: // break; // case LabelDrawingTypeEnum::DRAW_FILLED_WHITE_OUTLINE: // break; // case LabelDrawingTypeEnum::DRAW_OUTLINE_LABEL_COLOR: // break; // case LabelDrawingTypeEnum::DRAW_OUTLINE_BLACK: // break; // case LabelDrawingTypeEnum::DRAW_OUTLINE_WHITE: // break; // } // bool isOutlineMode = false; // switch (labelDrawingType) { // case LabelDrawingTypeEnum::DRAW_FILLED: // break; // case LabelDrawingTypeEnum::DRAW_OUTLINE: // isOutlineMode = true; // break; // } /* * Enable alpha blending so voxels that are not drawn from higher layers * allow voxels from lower layers to be seen. */ glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); /* * Flat shading voxels not interpolated */ glShadeModel(GL_FLAT); CaretAssert(plane.isValidPlane()); if (plane.isValidPlane() == false) { return; } /* * Compute coordinate of point in center of first slice */ float selectedSliceCoordinate = 0.0; float sliceNormalVector[3] = { 0.0, 0.0, 0.0 }; plane.getNormalVector(sliceNormalVector); switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: CaretAssert(0); break; case VolumeSliceViewPlaneEnum::AXIAL: selectedSliceCoordinate = sliceCoordinates[2]; break; case VolumeSliceViewPlaneEnum::CORONAL: selectedSliceCoordinate = sliceCoordinates[1]; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: selectedSliceCoordinate = sliceCoordinates[0]; break; } /* * Holds colors for voxels in the slice * Outside of loop to minimize allocations * It is faster to make one call to * NodeAndVoxelColoring::colorScalarsWithPalette() with * all voxels in the slice than it is to call it * separately for each voxel. */ std::vector sliceVoxelsRgbaVector; /* * Draw each of the volumes separately so that each * is drawn with the correct voxel slices. */ const int32_t numberOfVolumesToDraw = static_cast(m_volumeDrawInfo.size()); for (int32_t iVol = 0; iVol < numberOfVolumesToDraw; iVol++) { const BrainOpenGLFixedPipeline::VolumeDrawInfo& volInfo = m_volumeDrawInfo[iVol]; const VolumeMappableInterface* volumeFile = volInfo.volumeFile; int64_t dimI, dimJ, dimK, numMaps, numComponents; volumeFile->getDimensions(dimI, dimJ, dimK, numMaps, numComponents); const int64_t mapIndex = volInfo.mapIndex; float originX, originY, originZ; volumeFile->indexToSpace(0, 0, 0, originX, originY, originZ); float x1, y1, z1; volumeFile->indexToSpace(1, 1, 1, x1, y1, z1); const float voxelStepX = x1 - originX; const float voxelStepY = y1 - originY; const float voxelStepZ = z1 - originZ; /* * Determine index of slice being viewed for the volume */ float coordinateOnSlice[3] = { originX, originY, originZ }; switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: CaretAssert(0); break; case VolumeSliceViewPlaneEnum::AXIAL: coordinateOnSlice[2] = selectedSliceCoordinate; break; case VolumeSliceViewPlaneEnum::CORONAL: coordinateOnSlice[1] = selectedSliceCoordinate; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: coordinateOnSlice[0] = selectedSliceCoordinate; break; } int64_t sliceIndicesForCoordinateOnSlice[3]; volumeFile->enclosingVoxel(coordinateOnSlice[0], coordinateOnSlice[1], coordinateOnSlice[2], sliceIndicesForCoordinateOnSlice[0], sliceIndicesForCoordinateOnSlice[1], sliceIndicesForCoordinateOnSlice[2]); int64_t sliceIndexForDrawing = -1; int64_t numVoxelsInSlice = 0; switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: CaretAssert(0); break; case VolumeSliceViewPlaneEnum::AXIAL: sliceIndexForDrawing = sliceIndicesForCoordinateOnSlice[2]; if ((sliceIndexForDrawing < 0) || (sliceIndexForDrawing >= dimK)) { continue; } numVoxelsInSlice = dimI * dimJ; break; case VolumeSliceViewPlaneEnum::CORONAL: sliceIndexForDrawing = sliceIndicesForCoordinateOnSlice[1]; if ((sliceIndexForDrawing < 0) || (sliceIndexForDrawing >= dimJ)) { continue; } numVoxelsInSlice = dimI * dimK; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: sliceIndexForDrawing = sliceIndicesForCoordinateOnSlice[0]; if ((sliceIndexForDrawing < 0) || (sliceIndexForDrawing >= dimI)) { continue; } numVoxelsInSlice = dimJ * dimK; break; } /* * Stores RGBA values for each voxel. * Use a vector for voxel colors so no worries about memory being freed. */ const int64_t numVoxelsInSliceRGBA = numVoxelsInSlice * 4; if (numVoxelsInSliceRGBA > static_cast(sliceVoxelsRgbaVector.size())) { sliceVoxelsRgbaVector.resize(numVoxelsInSliceRGBA); } uint8_t* sliceVoxelsRGBA = &sliceVoxelsRgbaVector[0]; /* * Get colors for all voxels in the slice. */ const int64_t validVoxelCount = volumeFile->getVoxelColorsForSliceInMap(m_brain->getPaletteFile(), mapIndex, sliceViewPlane, sliceIndexForDrawing, displayGroup, browserTabIndex, sliceVoxelsRGBA); /* * Is label outline mode? */ if (m_volumeDrawInfo[iVol].mapFile->isMappedWithLabelTable()) { int64_t xdim = 0; int64_t ydim = 0; switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: CaretAssert(0); break; case VolumeSliceViewPlaneEnum::AXIAL: xdim = dimI; ydim = dimJ; break; case VolumeSliceViewPlaneEnum::CORONAL: xdim = dimI; ydim = dimK; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: xdim = dimJ; ydim = dimK; break; } LabelDrawingTypeEnum::Enum labelDrawingType = LabelDrawingTypeEnum::DRAW_FILLED; CaretColorEnum::Enum outlineColor = CaretColorEnum::BLACK; const CaretMappableDataFile* mapFile = dynamic_cast(volumeFile); if (mapFile != NULL) { if (mapFile->isMappedWithLabelTable()) { const LabelDrawingProperties* props = mapFile->getLabelDrawingProperties(); labelDrawingType = props->getDrawingType(); outlineColor = props->getOutlineColor(); } } NodeAndVoxelColoring::convertSliceColoringToOutlineMode(sliceVoxelsRGBA, labelDrawingType, outlineColor, xdim, ydim); } int64_t selectedSliceIndices[3]; volumeFile->enclosingVoxel(sliceCoordinates[0], sliceCoordinates[1], sliceCoordinates[2], selectedSliceIndices[0], selectedSliceIndices[1], selectedSliceIndices[2]); const uint8_t volumeDrawingOpacity = static_cast(volInfo.opacity * 255.0); /* * Setup for drawing the voxels in the slice. */ float startCoordinate[3] = { originX - (voxelStepX / 2.0), originY - (voxelStepY / 2.0), originZ - (voxelStepZ / 2.0) }; float rowStep[3] = { 0.0, 0.0, 0.0 }; float columnStep[3] = { 0.0, 0.0, 0.0 }; int64_t numberOfRows = 0; int64_t numberOfColumns = 0; switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: CaretAssert(0); break; case VolumeSliceViewPlaneEnum::AXIAL: startCoordinate[2] = selectedSliceCoordinate; //m_browserTabContent->getSliceCoordinateAxial(); rowStep[1] = voxelStepY; columnStep[0] = voxelStepX; numberOfRows = dimJ; numberOfColumns = dimI; break; case VolumeSliceViewPlaneEnum::CORONAL: startCoordinate[1] = selectedSliceCoordinate; //m_browserTabContent->getSliceCoordinateCoronal(); rowStep[2] = voxelStepZ; columnStep[0] = voxelStepX; numberOfRows = dimK; numberOfColumns = dimI; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: startCoordinate[0] = selectedSliceCoordinate; //m_browserTabContent->getSliceCoordinateParasagittal(); rowStep[2] = voxelStepZ; columnStep[1] = voxelStepY; numberOfRows = dimK; numberOfColumns = dimJ; break; } if (m_modelWholeBrain != NULL) { /* * After the a slice is drawn in ALL view, some layers * (volume surface outline) may be drawn in lines. As the * view is rotated, lines will partially appear and disappear * due to the lines having the same (extremely close) depth * values as the voxel polygons. OpenGL's Polygon Offset * only works with polygons and NOT with lines or points. * So, polygon offset cannot be used to move the depth * values for the lines and points "a little closer" to * the user. Instead, polygon offset is used to push * the underlaying slices "a little bit away" from the * user. * * Resolves WB-414 */ const float inverseSliceIndex = numberOfVolumesToDraw - iVol; //const float factor = 5.0; const float factor = inverseSliceIndex * 1.0 + 1.0; const float units = inverseSliceIndex * 1.0 + 1.0; glEnable(GL_POLYGON_OFFSET_FILL); glPolygonOffset(factor, units); // if (iVol > 0) { // glEnable(GL_POLYGON_OFFSET_FILL); // glPolygonOffset(-1.0, -1.0); // } } /* * Draw the voxels in the slice. */ drawOrthogonalSliceVoxels(sliceNormalVector, startCoordinate, rowStep, columnStep, numberOfColumns, numberOfRows, sliceVoxelsRgbaVector, validVoxelCount, volumeFile, iVol, mapIndex, volumeDrawingOpacity); glDisable(GL_POLYGON_OFFSET_FILL); } glDisable(GL_BLEND); glShadeModel(GL_SMOOTH); } /** * Draw an orthogonal slice with culling to avoid drawing * voxels not visible in the viewport and reduce drawing time. * * @param sliceViewPlane * The plane for slice drawing. * @param sliceCoordinates * Coordinates of the selected slice. * @param plane * Plane equation for the selected slice. */ void BrainOpenGLVolumeObliqueSliceDrawing::drawOrthogonalSliceWithCulling(const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const float sliceCoordinates[3], const Plane& plane) { const int32_t browserTabIndex = m_browserTabContent->getTabNumber(); const DisplayPropertiesLabels* displayPropertiesLabels = m_brain->getDisplayPropertiesLabels(); const DisplayGroupEnum::Enum displayGroup = displayPropertiesLabels->getDisplayGroupForTab(browserTabIndex); /* * Enable alpha blending so voxels that are not drawn from higher layers * allow voxels from lower layers to be seen. */ glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); /* * Flat shading voxels not interpolated */ glShadeModel(GL_FLAT); CaretAssert(plane.isValidPlane()); if (plane.isValidPlane() == false) { return; } /* * Compute coordinate of point in center of first slice */ float selectedSliceCoordinate = 0.0; float sliceNormalVector[3] = { 0.0, 0.0, 0.0 }; plane.getNormalVector(sliceNormalVector); switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: CaretAssert(0); break; case VolumeSliceViewPlaneEnum::AXIAL: selectedSliceCoordinate = sliceCoordinates[2]; break; case VolumeSliceViewPlaneEnum::CORONAL: selectedSliceCoordinate = sliceCoordinates[1]; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: selectedSliceCoordinate = sliceCoordinates[0]; break; } /* * Holds colors for voxels in the slice * Outside of loop to minimize allocations * It is faster to make one call to * NodeAndVoxelColoring::colorScalarsWithPalette() with * all voxels in the slice than it is to call it * separately for each voxel. */ std::vector sliceVoxelsRgbaVector; /* * Draw each of the volumes separately so that each * is drawn with the correct voxel slices. */ const int32_t numberOfVolumesToDraw = static_cast(m_volumeDrawInfo.size()); for (int32_t iVol = 0; iVol < numberOfVolumesToDraw; iVol++) { const BrainOpenGLFixedPipeline::VolumeDrawInfo& volInfo = m_volumeDrawInfo[iVol]; const VolumeMappableInterface* volumeFile = volInfo.volumeFile; int64_t culledFirstVoxelIJK[3]; int64_t culledLastVoxelIJK[3]; float voxelDeltaXYZ[3]; if ( ! getVolumeDrawingViewDependentCulling(sliceViewPlane, selectedSliceCoordinate, volumeFile, culledFirstVoxelIJK, culledLastVoxelIJK, voxelDeltaXYZ)) { CaretLogSevere("BrainOpenGLVolumeObliqueSliceDrawing::getVolumeDrawingViewDependentCulling() failed."); continue; } const int64_t numVoxelsI = std::abs(culledLastVoxelIJK[0] - culledFirstVoxelIJK[0]) + 1; const int64_t numVoxelsJ = std::abs(culledLastVoxelIJK[1] - culledFirstVoxelIJK[1]) + 1; const int64_t numVoxelsK = std::abs(culledLastVoxelIJK[2] - culledFirstVoxelIJK[2]) + 1; int64_t dimIJK[3], numMaps, numComponents; volumeFile->getDimensions(dimIJK[0], dimIJK[1], dimIJK[2], numMaps, numComponents); const int64_t mapIndex = volInfo.mapIndex; float firstVoxelXYZ[3]; volumeFile->indexToSpace(culledFirstVoxelIJK[0], culledFirstVoxelIJK[1], culledFirstVoxelIJK[2], firstVoxelXYZ[0], firstVoxelXYZ[1], firstVoxelXYZ[2]); const float voxelStepX = voxelDeltaXYZ[0]; const float voxelStepY = voxelDeltaXYZ[1]; const float voxelStepZ = voxelDeltaXYZ[2]; int64_t sliceIndexForDrawing = -1; int64_t numVoxelsInSlice = 0; switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: CaretAssert(0); break; case VolumeSliceViewPlaneEnum::AXIAL: sliceIndexForDrawing = culledFirstVoxelIJK[2]; if ((sliceIndexForDrawing < 0) || (sliceIndexForDrawing >= dimIJK[2])) { continue; } numVoxelsInSlice = numVoxelsI * numVoxelsJ; break; case VolumeSliceViewPlaneEnum::CORONAL: sliceIndexForDrawing = culledFirstVoxelIJK[1]; if ((sliceIndexForDrawing < 0) || (sliceIndexForDrawing >= dimIJK[1])) { continue; } numVoxelsInSlice = numVoxelsI * numVoxelsK; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: sliceIndexForDrawing = culledFirstVoxelIJK[0]; if ((sliceIndexForDrawing < 0) || (sliceIndexForDrawing >= dimIJK[0])) { continue; } numVoxelsInSlice = numVoxelsJ * numVoxelsK; break; } /* * Stores RGBA values for each voxel. * Use a vector for voxel colors so no worries about memory being freed. */ const int64_t numVoxelsInSliceRGBA = numVoxelsInSlice * 4; if (numVoxelsInSliceRGBA != static_cast(sliceVoxelsRgbaVector.size())) { sliceVoxelsRgbaVector.resize(numVoxelsInSliceRGBA); } uint8_t* sliceVoxelsRGBA = &sliceVoxelsRgbaVector[0]; /* * Get colors for all voxels in the slice. */ const int64_t voxelCountIJK[3] = { numVoxelsI, numVoxelsJ, numVoxelsK }; const int64_t validVoxelCount = volumeFile->getVoxelColorsForSubSliceInMap(m_brain->getPaletteFile(), mapIndex, sliceViewPlane, sliceIndexForDrawing, culledFirstVoxelIJK, culledLastVoxelIJK, voxelCountIJK, displayGroup, browserTabIndex, sliceVoxelsRGBA); /* * Is label outline mode? */ if (m_volumeDrawInfo[iVol].mapFile->isMappedWithLabelTable()) { int64_t xdim = 0; int64_t ydim = 0; switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: CaretAssert(0); break; case VolumeSliceViewPlaneEnum::AXIAL: xdim = numVoxelsI; ydim = numVoxelsJ; break; case VolumeSliceViewPlaneEnum::CORONAL: xdim = numVoxelsI; ydim = numVoxelsK; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: xdim = numVoxelsJ; ydim = numVoxelsK; break; } LabelDrawingTypeEnum::Enum labelDrawingType = LabelDrawingTypeEnum::DRAW_FILLED; CaretColorEnum::Enum outlineColor = CaretColorEnum::BLACK; const CaretMappableDataFile* mapFile = dynamic_cast(volumeFile); if (mapFile != NULL) { if (mapFile->isMappedWithLabelTable()) { const LabelDrawingProperties* props = mapFile->getLabelDrawingProperties(); labelDrawingType = props->getDrawingType(); outlineColor = props->getOutlineColor(); } } NodeAndVoxelColoring::convertSliceColoringToOutlineMode(sliceVoxelsRGBA, labelDrawingType, outlineColor, xdim, ydim); } const uint8_t volumeDrawingOpacity = static_cast(volInfo.opacity * 255.0); /* * Setup for drawing the voxels in the slice. */ float startCoordinate[3] = { firstVoxelXYZ[0] - (voxelStepX / 2.0), firstVoxelXYZ[1] - (voxelStepY / 2.0), firstVoxelXYZ[2] - (voxelStepZ / 2.0) }; float rowStep[3] = { 0.0, 0.0, 0.0 }; float columnStep[3] = { 0.0, 0.0, 0.0 }; int64_t numberOfRows = 0; int64_t numberOfColumns = 0; switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: CaretAssert(0); break; case VolumeSliceViewPlaneEnum::AXIAL: rowStep[1] = voxelStepY; columnStep[0] = voxelStepX; numberOfRows = numVoxelsJ; numberOfColumns = numVoxelsI; break; case VolumeSliceViewPlaneEnum::CORONAL: rowStep[2] = voxelStepZ; columnStep[0] = voxelStepX; numberOfRows = numVoxelsK; numberOfColumns = numVoxelsI; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: rowStep[2] = voxelStepZ; columnStep[1] = voxelStepY; numberOfRows = numVoxelsK; numberOfColumns = numVoxelsJ; break; } if (m_modelWholeBrain != NULL) { /* * After the a slice is drawn in ALL view, some layers * (volume surface outline) may be drawn in lines. As the * view is rotated, lines will partially appear and disappear * due to the lines having the same (extremely close) depth * values as the voxel polygons. OpenGL's Polygon Offset * only works with polygons and NOT with lines or points. * So, polygon offset cannot be used to move the depth * values for the lines and points "a little closer" to * the user. Instead, polygon offset is used to push * the underlaying slices "a little bit away" from the * user. * * Resolves WB-414 */ const float inverseSliceIndex = numberOfVolumesToDraw - iVol; //const float factor = 5.0; const float factor = inverseSliceIndex * 1.0 + 1.0; const float units = inverseSliceIndex * 1.0 + 1.0; glEnable(GL_POLYGON_OFFSET_FILL); glPolygonOffset(factor, units); } /* * Draw the voxels in the slice. */ drawOrthogonalSliceVoxels(sliceNormalVector, startCoordinate, rowStep, columnStep, numberOfColumns, numberOfRows, sliceVoxelsRgbaVector, validVoxelCount, volumeFile, iVol, mapIndex, volumeDrawingOpacity); glDisable(GL_POLYGON_OFFSET_FILL); } glDisable(GL_BLEND); glShadeModel(GL_SMOOTH); } /** * Create the equation for the slice plane * * @param sliceProjectionType * Type of projection for the slice drawing (oblique, orthogonal) * @param sliceViewPlane * View plane that is displayed. * @param montageSliceIndex * Selected montage slice index * @param planeOut * OUTPUT plane of slice after transforms. */ void BrainOpenGLVolumeObliqueSliceDrawing::createSlicePlaneEquation(const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const float sliceCoordinates[3], Plane& planeOut) { /* * Default the slice normal vector to an orthogonal view */ float sliceNormalVector[3] = { 0.0, 0.0, 0.0 }; switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: case VolumeSliceViewPlaneEnum::AXIAL: sliceNormalVector[2] = 1.0; break; case VolumeSliceViewPlaneEnum::CORONAL: sliceNormalVector[1] = -1.0; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: sliceNormalVector[0] = -1.0; break; } switch (sliceProjectionType) { break; case VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_OBLIQUE: { /* * Transform the slice normal vector by the oblique rotation * matrix so that the normal vector points out of the slice */ const Matrix4x4 obliqueRotationMatrix = m_browserTabContent->getObliqueVolumeRotationMatrix(); obliqueRotationMatrix.multiplyPoint3(sliceNormalVector); MathFunctions::normalizeVector(sliceNormalVector); } break; case VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_ORTHOGONAL: break; } Plane plane(sliceNormalVector, sliceCoordinates); planeOut = plane; // CaretLogFine("Setting plane " // + VolumeSliceViewPlaneEnum::toGuiName(sliceViewPlane) // + "\n Selected Coordinate:" // + AString::number(selectedSliceCoordinate[0]) // + ", " // + AString::number(selectedSliceCoordinate[1]) // + ", " // + AString::number(selectedSliceCoordinate[2]) // + "\n Slice Plane: " // + plane.toString()); m_lookAtCenter[0] = sliceCoordinates[0]; m_lookAtCenter[1] = sliceCoordinates[1]; m_lookAtCenter[2] = sliceCoordinates[2]; } /** * Set the volume slice viewing transformation. This sets the position and * orientation of the camera. * * @param sliceProjectionType * Type of projection for the slice drawing (oblique, orthogonal) * @param sliceViewPlane * View plane that is displayed. * @param plane * Plane equation of selected slice. * @param sliceCoordinates * Coordinates of the selected slices. */ void BrainOpenGLVolumeObliqueSliceDrawing::setVolumeSliceViewingAndModelingTransformations(const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const Plane& plane, const float sliceCoordinates[3]) { /* * Initialize the modelview matrix to the identity matrix * This places the camera at the origin, pointing down the * negative-Z axis with the up vector set to (0,1,0 => * positive-Y is up). */ glMatrixMode(GL_MODELVIEW); glLoadIdentity(); const float* userTranslation = m_browserTabContent->getTranslation(); /* * Move the camera with the user's translation */ float viewTranslationX = 0.0; float viewTranslationY = 0.0; float viewTranslationZ = 0.0; switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: case VolumeSliceViewPlaneEnum::AXIAL: viewTranslationX = sliceCoordinates[0] + userTranslation[0]; viewTranslationY = sliceCoordinates[1] + userTranslation[1]; break; case VolumeSliceViewPlaneEnum::CORONAL: viewTranslationX = sliceCoordinates[0] + userTranslation[0]; viewTranslationY = sliceCoordinates[2] + userTranslation[2]; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: viewTranslationX = -(sliceCoordinates[1] + userTranslation[1]); viewTranslationY = sliceCoordinates[2] + userTranslation[2]; break; } glTranslatef(viewTranslationX, viewTranslationY, viewTranslationZ); glGetDoublev(GL_MODELVIEW_MATRIX, m_viewingMatrix); /* * Since an orthographic projection is used, the camera only needs * to be a little bit from the center along the plane's normal vector. */ double planeNormal[3]; plane.getNormalVector(planeNormal); double cameraXYZ[3] = { m_lookAtCenter[0] + planeNormal[0] * 1.0, m_lookAtCenter[1] + planeNormal[1] * 1.0, m_lookAtCenter[2] + planeNormal[2] * 1.0, }; /* * Set the up vector which indices which way is up (screen Y) */ float up[3] = { 0.0, 0.0, 0.0 }; switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: case VolumeSliceViewPlaneEnum::AXIAL: up[1] = 1.0; break; case VolumeSliceViewPlaneEnum::CORONAL: up[2] = 1.0; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: up[2] = 1.0; break; } /* * For oblique viewing, the up vector needs to be rotated by the * oblique rotation matrix. */ switch (sliceProjectionType) { case VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_OBLIQUE: m_browserTabContent->getObliqueVolumeRotationMatrix().multiplyPoint3(up); break; case VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_ORTHOGONAL: break; } /* * Now set the camera to look at the selected coordinate (center) * with the camera offset a little bit from the center. * This allows the slice's voxels to be drawn in the actual coordinates. */ gluLookAt(cameraXYZ[0], cameraXYZ[1], cameraXYZ[2], m_lookAtCenter[0], m_lookAtCenter[1], m_lookAtCenter[2], up[0], up[1], up[2]); } /** * Draw the layers type data. * * @param sliceDrawingType * Type of slice drawing (montage, single) * @param sliceProjectionType * Type of projection for the slice drawing (oblique, orthogonal) * @param sliceViewPlane * View plane that is displayed. * @param slicePlane * Plane of the slice. * @param sliceCoordinates * Coordinates of the selected slices. */ void BrainOpenGLVolumeObliqueSliceDrawing::drawLayers(const VolumeSliceDrawingTypeEnum::Enum sliceDrawingType, const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const Plane& slicePlane, const float sliceCoordinates[3]) { bool drawCrosshairsFlag = true; bool drawFibersFlag = true; bool drawFociFlag = true; bool drawOutlineFlag = true; if (m_modelWholeBrain != NULL) { drawCrosshairsFlag = false; drawFibersFlag = false; drawFociFlag = false; } if ( ! m_identificationModeFlag) { if (slicePlane.isValidPlane()) { /* * Disable culling so that both sides of the triangles/quads are drawn. */ GLboolean cullFaceOn = glIsEnabled(GL_CULL_FACE); glDisable(GL_CULL_FACE); glPushMatrix(); GLboolean depthBufferEnabled = false; glGetBooleanv(GL_DEPTH_TEST, &depthBufferEnabled); /* * Use some polygon offset that will adjust the depth values of the * layers so that the layers depth values place the layers in front of * the volume slice. */ glEnable(GL_POLYGON_OFFSET_FILL); glPolygonOffset(0.0, 1.0); if (drawOutlineFlag) { drawSurfaceOutline(slicePlane); } if (drawFibersFlag) { glDisable(GL_DEPTH_TEST); m_fixedPipelineDrawing->drawFiberOrientations(&slicePlane, StructureEnum::ALL); m_fixedPipelineDrawing->drawFiberTrajectories(&slicePlane, StructureEnum::ALL); if (depthBufferEnabled) { glEnable(GL_DEPTH_TEST); } else { glDisable(GL_DEPTH_TEST); } } if (drawFociFlag) { glDisable(GL_DEPTH_TEST); drawVolumeSliceFoci(slicePlane); if (depthBufferEnabled) { glEnable(GL_DEPTH_TEST); } else { glDisable(GL_DEPTH_TEST); } } glDisable(GL_POLYGON_OFFSET_FILL); if (drawCrosshairsFlag) { glPushMatrix(); drawAxesCrosshairs(sliceProjectionType, sliceDrawingType, sliceViewPlane, sliceCoordinates); glPopMatrix(); if (depthBufferEnabled) { glEnable(GL_DEPTH_TEST); } else { glDisable(GL_DEPTH_TEST); } } glPopMatrix(); if (cullFaceOn) { glEnable(GL_CULL_FACE); } } } } /** * Draw surface outlines on the volume slices * * @param plane * Plane of the volume slice on which surface outlines are drawn. */ void BrainOpenGLVolumeObliqueSliceDrawing::drawSurfaceOutline(const Plane& plane) { if ( ! plane.isValidPlane()) { return; } // CaretLogFine("\nSurface Outline Plane: " // + plane.toString()); float intersectionPoint1[3]; float intersectionPoint2[3]; m_fixedPipelineDrawing->enableLineAntiAliasing(); VolumeSurfaceOutlineSetModel* outlineSet = m_browserTabContent->getVolumeSurfaceOutlineSet(); /* * Process each surface outline */ const int32_t numberOfOutlines = outlineSet->getNumberOfDislayedVolumeSurfaceOutlines(); for (int io = 0; io < numberOfOutlines; io++) { VolumeSurfaceOutlineModel* outline = outlineSet->getVolumeSurfaceOutlineModel(io); if (outline->isDisplayed()) { Surface* surface = outline->getSurface(); if (surface != NULL) { const float thickness = outline->getThickness(); //const float lineWidth = m_fixedPipelineDrawing->modelSizeToPixelSize(thickness); int numTriangles = surface->getNumberOfTriangles(); CaretColorEnum::Enum outlineColor = CaretColorEnum::BLACK; int32_t colorSourceBrowserTabIndex = -1; VolumeSurfaceOutlineColorOrTabModel* colorOrTabModel = outline->getColorOrTabModel(); VolumeSurfaceOutlineColorOrTabModel::Item* selectedColorOrTabItem = colorOrTabModel->getSelectedItem(); switch (selectedColorOrTabItem->getItemType()) { case VolumeSurfaceOutlineColorOrTabModel::Item::ITEM_TYPE_BROWSER_TAB: colorSourceBrowserTabIndex = selectedColorOrTabItem->getBrowserTabIndex(); break; case VolumeSurfaceOutlineColorOrTabModel::Item::ITEM_TYPE_COLOR: outlineColor = selectedColorOrTabItem->getColor(); break; } const bool surfaceColorFlag = (colorSourceBrowserTabIndex >= 0); float* nodeColoringRGBA = NULL; if (surfaceColorFlag) { nodeColoringRGBA = m_fixedPipelineDrawing->surfaceNodeColoring->colorSurfaceNodes(NULL, surface, colorSourceBrowserTabIndex); } glColor3fv(CaretColorEnum::toRGB(outlineColor)); //m_fixedPipelineDrawing->setLineWidth(lineWidth); m_fixedPipelineDrawing->setLineWidth(thickness); /* * Examine each triangle to see if it intersects the Plane * in which the slice exists. */ glBegin(GL_LINES); for (int it = 0; it < numTriangles; it++) { const int32_t* triangleNodes = surface->getTriangle(it); const float* c1 = surface->getCoordinate(triangleNodes[0]); const float* c2 = surface->getCoordinate(triangleNodes[1]); const float* c3 = surface->getCoordinate(triangleNodes[2]); if (plane.triangleIntersectPlane(c1, c2, c3, intersectionPoint1, intersectionPoint2)) { if (surfaceColorFlag) { /* * Use coloring assigned to the first node in the triangle * but only if Alpha is valid (greater than zero). */ const int64_t colorIndex = triangleNodes[0] * 4; if (nodeColoringRGBA[colorIndex + 3] > 0.0) { glColor3fv(&nodeColoringRGBA[triangleNodes[0] * 4]); } else { continue; } } /* * Draw the line where the triangle intersections the slice */ glVertex3fv(intersectionPoint1); glVertex3fv(intersectionPoint2); } } glEnd(); } } } m_fixedPipelineDrawing->disableLineAntiAliasing(); } /** * Draw foci on volume slice. * * @param plane * Plane of the volume slice on which surface outlines are drawn. */ void BrainOpenGLVolumeObliqueSliceDrawing::drawVolumeSliceFoci(const Plane& plane) { SelectionItemFocusVolume* idFocus = m_brain->getSelectionManager()->getVolumeFocusIdentification(); /* * Check for a 'selection' type mode */ bool isSelect = false; switch (m_fixedPipelineDrawing->mode) { case BrainOpenGLFixedPipeline::MODE_DRAWING: break; case BrainOpenGLFixedPipeline::MODE_IDENTIFICATION: if (idFocus->isEnabledForSelection()) { isSelect = true; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } else { return; } break; case BrainOpenGLFixedPipeline::MODE_PROJECTION: return; break; } VolumeMappableInterface* underlayVolume = m_volumeDrawInfo[0].volumeFile; float minVoxelSpacing; float maxVoxelSpacing; if ( ! getMinMaxVoxelSpacing(underlayVolume, minVoxelSpacing, maxVoxelSpacing)) { return; } const float sliceThickness = maxVoxelSpacing; const float halfSliceThickness = sliceThickness * 0.5; const DisplayPropertiesFoci* fociDisplayProperties = m_brain->getDisplayPropertiesFoci(); const DisplayGroupEnum::Enum displayGroup = fociDisplayProperties->getDisplayGroupForTab(m_fixedPipelineDrawing->windowTabIndex); if (fociDisplayProperties->isDisplayed(displayGroup, m_fixedPipelineDrawing->windowTabIndex) == false) { return; } const float focusDiameter = fociDisplayProperties->getFociSize(displayGroup, m_fixedPipelineDrawing->windowTabIndex); const FeatureColoringTypeEnum::Enum fociColoringType = fociDisplayProperties->getColoringType(displayGroup, m_fixedPipelineDrawing->windowTabIndex); bool drawAsSpheres = false; switch (fociDisplayProperties->getDrawingType(displayGroup, m_fixedPipelineDrawing->windowTabIndex)) { case FociDrawingTypeEnum::DRAW_AS_SPHERES: drawAsSpheres = true; break; case FociDrawingTypeEnum::DRAW_AS_SQUARES: break; } /* * Process each foci file */ const int32_t numberOfFociFiles = m_brain->getNumberOfFociFiles(); for (int32_t iFile = 0; iFile < numberOfFociFiles; iFile++) { FociFile* fociFile = m_brain->getFociFile(iFile); const GroupAndNameHierarchyModel* classAndNameSelection = fociFile->getGroupAndNameHierarchyModel(); if (classAndNameSelection->isSelected(displayGroup, m_fixedPipelineDrawing->windowTabIndex) == false) { continue; } const GiftiLabelTable* classColorTable = fociFile->getClassColorTable(); const GiftiLabelTable* nameColorTable = fociFile->getNameColorTable(); const int32_t numFoci = fociFile->getNumberOfFoci(); for (int32_t j = 0; j < numFoci; j++) { Focus* focus = fociFile->getFocus(j); const GroupAndNameHierarchyItem* groupNameItem = focus->getGroupNameSelectionItem(); if (groupNameItem != NULL) { if (groupNameItem->isSelected(displayGroup, m_fixedPipelineDrawing->windowTabIndex) == false) { continue; } } float rgba[4] = { 0.0, 0.0, 0.0, 1.0 }; switch (fociColoringType) { case FeatureColoringTypeEnum::FEATURE_COLORING_TYPE_CLASS: if (focus->isClassRgbaValid() == false) { const GiftiLabel* colorLabel = classColorTable->getLabelBestMatching(focus->getClassName()); if (colorLabel != NULL) { colorLabel->getColor(rgba); focus->setClassRgba(rgba); } else { focus->setClassRgba(rgba); } } focus->getClassRgba(rgba); break; case FeatureColoringTypeEnum::FEATURE_COLORING_TYPE_NAME: if (focus->isNameRgbaValid() == false) { const GiftiLabel* colorLabel = nameColorTable->getLabelBestMatching(focus->getName()); if (colorLabel != NULL) { colorLabel->getColor(rgba); focus->setNameRgba(rgba); } else { focus->setNameRgba(rgba); } } focus->getNameRgba(rgba); break; } const int32_t numProjections = focus->getNumberOfProjections(); for (int32_t k = 0; k < numProjections; k++) { const SurfaceProjectedItem* spi = focus->getProjection(k); if (spi->isVolumeXYZValid()) { float xyz[3]; spi->getVolumeXYZ(xyz); bool drawIt = false; if (plane.absoluteDistanceToPlane(xyz) < halfSliceThickness) { drawIt = true; } if (drawIt) { glPushMatrix(); glTranslatef(xyz[0], xyz[1], xyz[2]); if (isSelect) { uint8_t idRGBA[4]; m_fixedPipelineDrawing->colorIdentification->addItem(idRGBA, SelectionItemDataTypeEnum::FOCUS_VOLUME, iFile, // file index j, // focus index k);// projection index idRGBA[3] = 255; if (drawAsSpheres) { m_fixedPipelineDrawing->drawSphereWithDiameter(idRGBA, focusDiameter); } else { glColor4ubv(idRGBA); drawSquare(focusDiameter); } } else { if (drawAsSpheres) { m_fixedPipelineDrawing->drawSphereWithDiameter(rgba, focusDiameter); } else { glColor3fv(rgba); drawSquare(focusDiameter); } } glPopMatrix(); } } } } } if (isSelect) { int32_t fociFileIndex = -1; int32_t focusIndex = -1; int32_t focusProjectionIndex = -1; float depth = -1.0; m_fixedPipelineDrawing->getIndexFromColorSelection(SelectionItemDataTypeEnum::FOCUS_VOLUME, m_fixedPipelineDrawing->mouseX, m_fixedPipelineDrawing->mouseY, fociFileIndex, focusIndex, focusProjectionIndex, depth); if (fociFileIndex >= 0) { if (idFocus->isOtherScreenDepthCloserToViewer(depth)) { Focus* focus = m_brain->getFociFile(fociFileIndex)->getFocus(focusIndex); idFocus->setBrain(m_brain); idFocus->setFocus(focus); idFocus->setFociFile(m_brain->getFociFile(fociFileIndex)); idFocus->setFocusIndex(focusIndex); idFocus->setFocusProjectionIndex(focusProjectionIndex); idFocus->setVolumeFile(underlayVolume); idFocus->setScreenDepth(depth); float xyz[3]; const SurfaceProjectedItem* spi = focus->getProjection(focusProjectionIndex); spi->getVolumeXYZ(xyz); m_fixedPipelineDrawing->setSelectedItemScreenXYZ(idFocus, xyz); CaretLogFine("Selected Volume Focus Identification Symbol: " + QString::number(focusIndex)); } } } } /** * Draw the axes crosshairs. * * @param sliceProjectionType * Type of projection for the slice drawing (oblique, orthogonal) * @param sliceDrawingType * Type of slice drawing (montage, single) * @param sliceViewPlane * View plane that is displayed. * @param sliceCoordinates * Coordinates of the selected slices. */ void BrainOpenGLVolumeObliqueSliceDrawing::drawAxesCrosshairs(const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const VolumeSliceDrawingTypeEnum::Enum sliceDrawingType, const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const float sliceCoordinates[3]) { CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); const bool drawCrosshairsFlag = prefs->isVolumeAxesCrosshairsDisplayed(); bool drawCrosshairLabelsFlag = prefs->isVolumeAxesLabelsDisplayed(); switch (sliceDrawingType) { case VolumeSliceDrawingTypeEnum::VOLUME_SLICE_DRAW_MONTAGE: drawCrosshairLabelsFlag = false; break; case VolumeSliceDrawingTypeEnum::VOLUME_SLICE_DRAW_SINGLE: break; } switch (sliceProjectionType) { case VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_ORTHOGONAL: drawAxesCrosshairsOrthoAndOblique(sliceProjectionType, sliceViewPlane, sliceCoordinates, drawCrosshairsFlag, drawCrosshairLabelsFlag); break; case VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_OBLIQUE: { glPushMatrix(); glLoadIdentity(); double mv[16]; glGetDoublev(GL_MODELVIEW_MATRIX, mv); Matrix4x4 mvm; mvm.setMatrixFromOpenGL(mv); float trans[3]; m_browserTabContent->getTranslation(trans); glTranslatef(trans[0], trans[1], trans[2]); drawAxesCrosshairsOrthoAndOblique(sliceProjectionType, sliceViewPlane, sliceCoordinates, drawCrosshairsFlag, drawCrosshairLabelsFlag); glPopMatrix(); } break; } } /** * Draw the axes crosshairs for an orthogonal slice. * * @param sliceProjectionType * Type of projection for the slice drawing (oblique, orthogonal) * @param sliceViewPlane * The slice plane view. * @param sliceCoordinates * Coordinates of the selected slices. * @param drawCrosshairsFlag * If true, draw the crosshairs. * @param drawCrosshairLabelsFlag * If true, draw the crosshair labels. */ void BrainOpenGLVolumeObliqueSliceDrawing::drawAxesCrosshairsOrthoAndOblique(const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const float sliceCoordinates[3], const bool drawCrosshairsFlag, const bool drawCrosshairLabelsFlag) { bool obliqueModeFlag = false; switch (sliceProjectionType) { case VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_OBLIQUE: obliqueModeFlag = true; break; case VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_ORTHOGONAL: break; } GLboolean depthEnabled = GL_FALSE; glGetBooleanv(GL_DEPTH_TEST, &depthEnabled); glDisable(GL_DEPTH_TEST); const float bigValue = 10000.0; float horizontalAxisStartXYZ[3] = { sliceCoordinates[0], sliceCoordinates[1], sliceCoordinates[2] }; float horizontalAxisEndXYZ[3] = { sliceCoordinates[0], sliceCoordinates[1], sliceCoordinates[2] }; float verticalAxisStartXYZ[3] = { sliceCoordinates[0], sliceCoordinates[1], sliceCoordinates[2] }; float verticalAxisEndXYZ[3] = { sliceCoordinates[0], sliceCoordinates[1], sliceCoordinates[2] }; if (obliqueModeFlag) { switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: break; case VolumeSliceViewPlaneEnum::AXIAL: break; case VolumeSliceViewPlaneEnum::CORONAL: horizontalAxisStartXYZ[0] = sliceCoordinates[0]; horizontalAxisStartXYZ[1] = sliceCoordinates[2]; horizontalAxisStartXYZ[2] = sliceCoordinates[1]; horizontalAxisEndXYZ[0] = sliceCoordinates[0]; horizontalAxisEndXYZ[1] = sliceCoordinates[2]; horizontalAxisEndXYZ[2] = sliceCoordinates[1]; verticalAxisStartXYZ[0] = sliceCoordinates[0]; verticalAxisStartXYZ[1] = sliceCoordinates[1]; verticalAxisStartXYZ[2] = sliceCoordinates[2]; verticalAxisEndXYZ[0] = sliceCoordinates[0]; verticalAxisEndXYZ[1] = sliceCoordinates[1]; verticalAxisEndXYZ[2] = sliceCoordinates[2]; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: horizontalAxisStartXYZ[0] = sliceCoordinates[1]; horizontalAxisStartXYZ[1] = sliceCoordinates[2]; horizontalAxisStartXYZ[2] = sliceCoordinates[0]; horizontalAxisEndXYZ[0] = sliceCoordinates[1]; horizontalAxisEndXYZ[1] = sliceCoordinates[2]; horizontalAxisEndXYZ[2] = sliceCoordinates[0]; verticalAxisStartXYZ[0] = -sliceCoordinates[1]; verticalAxisStartXYZ[1] = sliceCoordinates[0]; verticalAxisStartXYZ[2] = sliceCoordinates[2]; verticalAxisEndXYZ[0] = -sliceCoordinates[1]; verticalAxisEndXYZ[1] = sliceCoordinates[0]; verticalAxisEndXYZ[2] = sliceCoordinates[2]; break; } } float axialRGBA[4]; getAxesColor(VolumeSliceViewPlaneEnum::AXIAL, axialRGBA); float coronalRGBA[4]; getAxesColor(VolumeSliceViewPlaneEnum::CORONAL, coronalRGBA); float paraRGBA[4]; getAxesColor(VolumeSliceViewPlaneEnum::PARASAGITTAL, paraRGBA); AString horizontalLeftText = ""; AString horizontalRightText = ""; AString verticalBottomText = ""; AString verticalTopText = ""; float* horizontalAxisRGBA = axialRGBA; float* verticalAxisRGBA = axialRGBA; switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: break; case VolumeSliceViewPlaneEnum::AXIAL: horizontalLeftText = "L"; horizontalRightText = "R"; horizontalAxisRGBA = coronalRGBA; horizontalAxisStartXYZ[0] -= bigValue; horizontalAxisEndXYZ[0] += bigValue; verticalBottomText = "P"; verticalTopText = "A"; verticalAxisRGBA = paraRGBA; verticalAxisStartXYZ[1] -= bigValue; verticalAxisEndXYZ[1] += bigValue; break; case VolumeSliceViewPlaneEnum::CORONAL: horizontalLeftText = "L"; horizontalRightText = "R"; horizontalAxisRGBA = axialRGBA; if (obliqueModeFlag) { horizontalAxisStartXYZ[0] -= bigValue; horizontalAxisEndXYZ[0] += bigValue; } else { horizontalAxisStartXYZ[0] -= bigValue; horizontalAxisEndXYZ[0] += bigValue; } verticalBottomText = "I"; verticalTopText = "S"; verticalAxisRGBA = paraRGBA; if (obliqueModeFlag) { verticalAxisStartXYZ[1] -= bigValue; verticalAxisEndXYZ[1] += bigValue; } else { verticalAxisStartXYZ[2] -= bigValue; verticalAxisEndXYZ[2] += bigValue; } break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: horizontalLeftText = "A"; horizontalRightText = "P"; horizontalAxisRGBA = axialRGBA; if (obliqueModeFlag) { horizontalAxisStartXYZ[0] -= bigValue; horizontalAxisEndXYZ[0] += bigValue; } else { horizontalAxisStartXYZ[1] -= bigValue; horizontalAxisEndXYZ[1] += bigValue; } verticalBottomText = "I"; verticalTopText = "S"; verticalAxisRGBA = coronalRGBA; if (obliqueModeFlag) { verticalAxisStartXYZ[1] -= bigValue; verticalAxisEndXYZ[1] += bigValue; } else { verticalAxisStartXYZ[2] -= bigValue; verticalAxisEndXYZ[2] += bigValue; } break; } GLint viewport[4]; glGetIntegerv(GL_VIEWPORT, viewport); const int textOffset = 15; const int textLeftWindowXY[2] = { textOffset, (viewport[3] / 2) }; const int textRightWindowXY[2] = { viewport[2] - textOffset, (viewport[3] / 2) }; const int textBottomWindowXY[2] = { viewport[2] / 2, textOffset }; const int textTopWindowXY[2] = { (viewport[2] / 2), viewport[3] - textOffset }; /* * Crosshairs */ if (drawCrosshairsFlag) { glLineWidth(1.0); glColor3fv(horizontalAxisRGBA); glBegin(GL_LINES); glVertex3fv(horizontalAxisStartXYZ); glVertex3fv(horizontalAxisEndXYZ); glEnd(); glLineWidth(1.0); glColor3fv(verticalAxisRGBA); glBegin(GL_LINES); glVertex3fv(verticalAxisStartXYZ); glVertex3fv(verticalAxisEndXYZ); glEnd(); } if (drawCrosshairLabelsFlag) { const int fontHeight = 18; const int textCenter[2] = { textLeftWindowXY[0], textLeftWindowXY[1] }; const int halfFontSize = fontHeight / 2; uint8_t backgroundRGBA[4] = { m_fixedPipelineDrawing->m_backgroundColorByte[0], m_fixedPipelineDrawing->m_backgroundColorByte[1], m_fixedPipelineDrawing->m_backgroundColorByte[2], m_fixedPipelineDrawing->m_backgroundColorByte[3] }; GLint savedViewport[4]; glGetIntegerv(GL_VIEWPORT, savedViewport); int vpLeftX = savedViewport[0] + textCenter[0] - halfFontSize; int vpRightX = savedViewport[0] + textCenter[0] + halfFontSize; int vpBottomY = savedViewport[1] + textCenter[1] - halfFontSize; int vpTopY = savedViewport[1] + textCenter[1] + halfFontSize; MathFunctions::limitRange(vpLeftX, savedViewport[0], savedViewport[0] + savedViewport[2]); MathFunctions::limitRange(vpRightX, savedViewport[0], savedViewport[0] + savedViewport[2]); MathFunctions::limitRange(vpBottomY, savedViewport[1], savedViewport[1] + savedViewport[3]); MathFunctions::limitRange(vpTopY, savedViewport[1], savedViewport[1] + savedViewport[3]); const int vpSizeX = vpRightX - vpLeftX; const int vpSizeY = vpTopY - vpBottomY; glViewport(vpLeftX, vpBottomY, vpSizeX, vpSizeY); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); std::vector rgba; std::vector coords, normals; coords.push_back(-1.0); coords.push_back(-1.0); coords.push_back( 0.0); normals.push_back(0.0); normals.push_back(0.0); normals.push_back(1.0); rgba.push_back(backgroundRGBA[0]); rgba.push_back(backgroundRGBA[1]); rgba.push_back(backgroundRGBA[2]); rgba.push_back(backgroundRGBA[3]); coords.push_back( 1.0); coords.push_back(-1.0); coords.push_back( 0.0); normals.push_back(0.0); normals.push_back(0.0); normals.push_back(1.0); rgba.push_back(backgroundRGBA[0]); rgba.push_back(backgroundRGBA[1]); rgba.push_back(backgroundRGBA[2]); rgba.push_back(backgroundRGBA[3]); coords.push_back( 1.0); coords.push_back( 1.0); coords.push_back( 0.0); normals.push_back(0.0); normals.push_back(0.0); normals.push_back(1.0); rgba.push_back(backgroundRGBA[0]); rgba.push_back(backgroundRGBA[1]); rgba.push_back(backgroundRGBA[2]); rgba.push_back(backgroundRGBA[3]); coords.push_back(-1.0); coords.push_back( 1.0); coords.push_back( 0.0); normals.push_back(0.0); normals.push_back(0.0); normals.push_back(1.0); rgba.push_back(backgroundRGBA[0]); rgba.push_back(backgroundRGBA[1]); rgba.push_back(backgroundRGBA[2]); rgba.push_back(backgroundRGBA[3]); BrainOpenGLPrimitiveDrawing::drawQuads(coords, normals, rgba); glPopMatrix(); glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glViewport(savedViewport[0], savedViewport[1], savedViewport[2], savedViewport[3]); glColor4fv(horizontalAxisRGBA); m_fixedPipelineDrawing->drawTextWindowCoordsWithBackground(textLeftWindowXY[0], textLeftWindowXY[1], horizontalLeftText, BrainOpenGLTextRenderInterface::X_CENTER, BrainOpenGLTextRenderInterface::Y_CENTER, BrainOpenGLTextRenderInterface::BOLD, fontHeight); m_fixedPipelineDrawing->drawTextWindowCoordsWithBackground(textRightWindowXY[0], textRightWindowXY[1], horizontalRightText, BrainOpenGLTextRenderInterface::X_CENTER, BrainOpenGLTextRenderInterface::Y_CENTER, BrainOpenGLTextRenderInterface::BOLD, fontHeight); glColor4fv(verticalAxisRGBA); m_fixedPipelineDrawing->drawTextWindowCoordsWithBackground(textBottomWindowXY[0], textBottomWindowXY[1], verticalBottomText, BrainOpenGLTextRenderInterface::X_CENTER, BrainOpenGLTextRenderInterface::Y_CENTER, BrainOpenGLTextRenderInterface::BOLD, fontHeight); m_fixedPipelineDrawing->drawTextWindowCoordsWithBackground(textTopWindowXY[0], textTopWindowXY[1], verticalTopText, BrainOpenGLTextRenderInterface::X_CENTER, BrainOpenGLTextRenderInterface::Y_CENTER, BrainOpenGLTextRenderInterface::BOLD, fontHeight); } if (depthEnabled) { glEnable(GL_DEPTH_TEST); } } /** * Get the RGBA coloring for a slice view plane. * * @param sliceViewPlane * The slice view plane. * @param rgbaOut * Output colors ranging 0.0 to 1.0 */ void BrainOpenGLVolumeObliqueSliceDrawing::getAxesColor(const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, float rgbaOut[4]) const { switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: CaretAssert(0); break; case VolumeSliceViewPlaneEnum::AXIAL: rgbaOut[0] = 0.0; rgbaOut[1] = 0.0; rgbaOut[2] = 1.0; rgbaOut[3] = 1.0; break; case VolumeSliceViewPlaneEnum::CORONAL: rgbaOut[0] = 0.0; rgbaOut[1] = 1.0; rgbaOut[2] = 0.0; rgbaOut[3] = 1.0; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: rgbaOut[0] = 1.0; rgbaOut[1] = 0.0; rgbaOut[2] = 0.0; rgbaOut[3] = 1.0; break; } } /** * Draw a one millimeter square facing the user. * NOTE: This method will alter the current * modelviewing matrices so caller may need * to enclose the call to this method within * glPushMatrix() and glPopMatrix(). * * @param size * Size of square. */ void BrainOpenGLVolumeObliqueSliceDrawing::drawSquare(const float size) { const float length = size * 0.5; /* * Draw both front and back side since in some instances, * such as surface montage, we are viweing from the far * side (from back of monitor) */ glBegin(GL_QUADS); glNormal3f(0.0, 0.0, 1.0); glVertex3f(-length, -length, 0.0); glVertex3f( length, -length, 0.0); glVertex3f( length, length, 0.0); glVertex3f(-length, length, 0.0); glNormal3f(0.0, 0.0, -1.0); glVertex3f(-length, -length, 0.0); glVertex3f(-length, length, 0.0); glVertex3f( length, length, 0.0); glVertex3f( length, -length, 0.0); glEnd(); } /** * Get the minimum and maximum distance between adjacent voxels in all * slices planes. Output spacing value are always non-negative even if * a right-to-left orientation. * * @param volume * Volume for which min/max spacing is requested. * @param minSpacingOut * Output minimum spacing. * @param maxSpacingOut * Output maximum spacing. * @return * True if min and max spacing are greater than zero. */ bool BrainOpenGLVolumeObliqueSliceDrawing::getMinMaxVoxelSpacing(const VolumeMappableInterface* volume, float& minSpacingOut, float& maxSpacingOut) const { CaretAssert(volume); float originX, originY, originZ; float x1, y1, z1; volume->indexToSpace(0, 0, 0, originX, originY, originZ); volume->indexToSpace(1, 1, 1, x1, y1, z1); const float dx = std::fabs(x1 - originX); const float dy = std::fabs(y1 - originY); const float dz = std::fabs(z1 - originZ); minSpacingOut = std::min(std::min(dx, dy), dz); maxSpacingOut = std::max(std::max(dx, dy), dz); if ((minSpacingOut > 0.0) && (maxSpacingOut > 0.0)) { return true; } return false; } /** * Draw orientation axes * * @param viewport * The viewport region for the orientation axes. */ void BrainOpenGLVolumeObliqueSliceDrawing::drawOrientationAxes(const int viewport[4]) { CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); const bool drawCylindersFlag = prefs->isVolumeAxesCrosshairsDisplayed(); const bool drawLabelsFlag = prefs->isVolumeAxesLabelsDisplayed(); /* * Set the viewport */ glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); const double viewportWidth = viewport[2]; const double viewportHeight = viewport[3]; /* * Determine bounds for orthographic projection */ const double maxCoord = 100.0; const double minCoord = -maxCoord; double left = 0.0; double right = 0.0; double top = 0.0; double bottom = 0.0; const double nearDepth = -1000.0; const double farDepth = 1000.0; if (viewportHeight > viewportWidth) { left = minCoord; right = maxCoord; const double aspectRatio = (viewportHeight / viewportWidth); top = maxCoord * aspectRatio; bottom = minCoord * aspectRatio; } else { const double aspectRatio = (viewportWidth / viewportHeight); top = maxCoord; bottom = minCoord; left = minCoord * aspectRatio; right = maxCoord * aspectRatio; } /* * Set the orthographic projection */ glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glOrtho(left, right, bottom, top, nearDepth, farDepth); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); { /* * Set the viewing transformation, places 'eye' so that it looks * at the 'model' which is, in this case, the axes */ double eyeX = 0.0; double eyeY = 0.0; double eyeZ = 100.0; const double centerX = 0; const double centerY = 0; const double centerZ = 0; const double upX = 0; const double upY = 1; const double upZ = 0; gluLookAt(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ); /* * Set the modeling transformation */ const Matrix4x4 obliqueRotationMatrix = m_browserTabContent->getObliqueVolumeRotationMatrix(); double rotationMatrix[16]; obliqueRotationMatrix.getMatrixForOpenGL(rotationMatrix); glMultMatrixd(rotationMatrix); /* * Disable depth buffer. Otherwise, when volume slices are drawn * black regions of the slices may set depth buffer and the occlude * the axes from display. */ GLboolean depthBufferEnabled = false; glGetBooleanv(GL_DEPTH_TEST, &depthBufferEnabled); glDisable(GL_DEPTH_TEST); const float red[4] = { 1.0, 0.0, 0.0, 1.0 }; const float green[4] = { 0.0, 1.0, 0.0, 1.0 }; const float blue[4] = { 0.0, 0.0, 1.0, 1.0 }; const double axisMaxCoord = maxCoord * 0.8; const double axisMinCoord = -axisMaxCoord; const double textMaxCoord = maxCoord * 0.9; const double textMinCoord = -textMaxCoord; const float axialPlaneMin[3] = { 0.0, 0.0, axisMinCoord }; const float axialPlaneMax[3] = { 0.0, 0.0, axisMaxCoord }; const double axialTextMin[3] = { 0.0, 0.0, textMinCoord }; const double axialTextMax[3] = { 0.0, 0.0, textMaxCoord }; const float coronalPlaneMin[3] = { axisMinCoord, 0.0, 0.0 }; const float coronalPlaneMax[3] = { axisMaxCoord, 0.0, 0.0 }; const double coronalTextMin[3] = { textMinCoord, 0.0, 0.0 }; const double coronalTextMax[3] = { textMaxCoord, 0.0, 0.0 }; const float paraPlaneMin[3] = { 0.0, axisMinCoord, 0.0 }; const float paraPlaneMax[3] = { 0.0, axisMaxCoord, 0.0 }; const double paraTextMin[3] = { 0.0, textMinCoord, 0.0 }; const double paraTextMax[3] = { 0.0, textMaxCoord, 0.0 }; const float axesCrosshairRadius = 1.0; if (drawCylindersFlag) { m_fixedPipelineDrawing->drawCylinder(blue, axialPlaneMin, axialPlaneMax, axesCrosshairRadius); } if (drawLabelsFlag) { glColor3fv(blue); m_fixedPipelineDrawing->drawTextModelCoords(axialTextMin, "I"); m_fixedPipelineDrawing->drawTextModelCoords(axialTextMax, "S"); } if (drawCylindersFlag) { m_fixedPipelineDrawing->drawCylinder(green, coronalPlaneMin, coronalPlaneMax, axesCrosshairRadius); } if (drawLabelsFlag) { glColor3fv(green); m_fixedPipelineDrawing->drawTextModelCoords(coronalTextMin, "L"); m_fixedPipelineDrawing->drawTextModelCoords(coronalTextMax, "R"); } if (drawCylindersFlag) { m_fixedPipelineDrawing->drawCylinder(red, paraPlaneMin, paraPlaneMax, axesCrosshairRadius); } if (drawLabelsFlag) { glColor3fv(red); m_fixedPipelineDrawing->drawTextModelCoords(paraTextMin, "P"); m_fixedPipelineDrawing->drawTextModelCoords(paraTextMax, "A"); } } glPopMatrix(); glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); } /** * Set the orthographic projection. * * @param sliceViewPlane * View plane that is displayed. * @param viewport * The viewport. */ void BrainOpenGLVolumeObliqueSliceDrawing::setOrthographicProjection(const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const int viewport[4]) { /* * Determine model size in screen Y when viewed */ BoundingBox boundingBox; m_volumeDrawInfo[0].volumeFile->getVoxelSpaceBoundingBox(boundingBox); /* * Set top and bottom to the min/max coordinate * that runs vertically on the screen */ double modelTop = 200.0; double modelBottom = -200.0; switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: CaretAssertMessage(0, "Should never get here"); break; case VolumeSliceViewPlaneEnum::AXIAL: modelTop = boundingBox.getMaxY(); modelBottom = boundingBox.getMinY(); break; case VolumeSliceViewPlaneEnum::CORONAL: modelTop = boundingBox.getMaxZ(); modelBottom = boundingBox.getMinZ(); break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: modelTop = boundingBox.getMaxZ(); modelBottom = boundingBox.getMinZ(); break; } /* * Scale ratio makes region slightly larger than model */ const double zoom = m_browserTabContent->getScaling(); double scaleRatio = (1.0 / 0.98); if (zoom > 0.0) { scaleRatio /= zoom; } modelTop *= scaleRatio; modelBottom *= scaleRatio; /* * Determine aspect ratio of viewport */ const double viewportWidth = viewport[2]; const double viewportHeight = viewport[3]; const double aspectRatio = (viewportWidth / viewportHeight); /* * Set bounds of orthographic projection */ const double halfModelY = ((modelTop - modelBottom) / 2.0); const double orthoBottom = modelBottom; const double orthoTop = modelTop; const double orthoRight = halfModelY * aspectRatio; const double orthoLeft = -halfModelY * aspectRatio; const double nearDepth = -1000.0; const double farDepth = 1000.0; m_orthographicBounds[0] = orthoLeft; m_orthographicBounds[1] = orthoRight; m_orthographicBounds[2] = orthoBottom; m_orthographicBounds[3] = orthoTop; m_orthographicBounds[4] = nearDepth; m_orthographicBounds[5] = farDepth; glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(m_orthographicBounds[0], m_orthographicBounds[1], m_orthographicBounds[2], m_orthographicBounds[3], m_orthographicBounds[4], m_orthographicBounds[5]); glMatrixMode(GL_MODELVIEW); // CaretLogFine("Orthographic Bounds: " // + AString::fromNumbers(m_orthographicBounds, 6, ",")); } /** * Draw the voxels in an orthogonal slice. * * @param sliceNormalVector * Normal vector of the slice plane. * @param coordinate * Coordinate of first voxel in the slice (bottom left as begin viewed) * @param rowStep * Three-dimensional step to next row. * @param columnStep * Three-dimensional step to next column. * @param numberOfColumns * Number of columns in the slice. * @param numberOfRows * Number of rows in the slice. * @param sliceRGBA * RGBA coloring for voxels in the slice. * @param validVoxelCount * Number of voxels with valid coloring * @param volumeInterface * Index of the volume being drawn. * @param volumeIndex * Selected map in the volume being drawn. * @param mapIndex * Selected map in the volume being drawn. * @param sliceOpacity * Opacity from the overlay. */ void BrainOpenGLVolumeObliqueSliceDrawing::drawOrthogonalSliceVoxels(const float sliceNormalVector[3], const float coordinate[3], const float rowStep[3], const float columnStep[3], const int64_t numberOfColumns, const int64_t numberOfRows, const std::vector& sliceRGBA, const int64_t validVoxelCount, const VolumeMappableInterface* volumeInterface, const int32_t volumeIndex, const int32_t mapIndex, const uint8_t sliceOpacity) { /* * There are two ways to draw the voxels. * * Quad Indices: This method submits each vertex (coordinate, normal, rgba) * one time BUT it submits EVERY vertex in the slice. Note that the vertex * is shared by four voxels (except along an edge). For each valid voxel, * it submits the indices of the voxel's four vertices. This method is * efficient when many voxels are drawn since each vertex is submitted to * OpenGL one time and is shared by up to four voxels. However, when * only a few voxels are drawn, many unused vertices are submitted. * * Single Quads: For each voxel, this method submits the four vertices * (coordinate, normal, rgba) for each voxel drawn. Even though adjacent * voxels share vertices, the vertices are submitted for each voxel. When * only a small number of voxels in the slice are drawn, this method is * efficient since only the needed vertex data is submitted. However, * when many voxels are drawn, many vertices are duplicated making the * drawing inefficient. * * So, estimate the bytes that are passed to OpenGL for each drawing * mode and choose the drawing mode that requires the smallest number * of bytes. */ /* * Each vertex requires 28 bytes * (3 float xyz, 3 float normal xyz + 4 bytes color). * * Single quads uses four vertices per quad. * * Index quads requires four 4-byte ints for the quad's indices. */ const int64_t bytesPerVertex = 28; const int64_t totalVertexBytes = ((numberOfColumns + 1) * (numberOfRows + 1) * bytesPerVertex); const int64_t singleQuadBytes = (validVoxelCount * bytesPerVertex * 4); const int64_t indexQuadBytes = (totalVertexBytes + (16 * validVoxelCount)); bool drawWithQuadIndicesFlag = false; if (indexQuadBytes < singleQuadBytes) { drawWithQuadIndicesFlag = true; } if (drawWithQuadIndicesFlag) { drawOrthogonalSliceVoxelsQuadIndicesAndStrips(sliceNormalVector, coordinate, rowStep, columnStep, numberOfColumns, numberOfRows, sliceRGBA, volumeInterface, volumeIndex, mapIndex, sliceOpacity); } else { drawOrthogonalSliceVoxelsSingleQuads(sliceNormalVector, coordinate, rowStep, columnStep, numberOfColumns, numberOfRows, sliceRGBA, volumeInterface, volumeIndex, mapIndex, sliceOpacity); } } /** * Draw the voxels in an orthogonal slice with single quads. * * Four coordinates, normals, and colors are sent to OpenGL for each * quad that is drawn. This may be efficent when only a few voxels * are drawn but very inefficient when many voxels are drawn. * * @param sliceNormalVector * Normal vector of the slice plane. * @param coordinate * Coordinate of first voxel in the slice (bottom left as begin viewed) * @param rowStep * Three-dimensional step to next row. * @param columnStep * Three-dimensional step to next column. * @param numberOfColumns * Number of columns in the slice. * @param numberOfRows * Number of rows in the slice. * @param sliceRGBA * RGBA coloring for voxels in the slice. * @param volumeInterface * Index of the volume being drawn. * @param volumeIndex * Selected map in the volume being drawn. * @param mapIndex * Selected map in the volume being drawn. * @param sliceOpacity * Opacity from the overlay. */ void BrainOpenGLVolumeObliqueSliceDrawing::drawOrthogonalSliceVoxelsSingleQuads(const float sliceNormalVector[3], const float coordinate[3], const float rowStep[3], const float columnStep[3], const int64_t numberOfColumns, const int64_t numberOfRows, const std::vector& sliceRGBA, const VolumeMappableInterface* volumeInterface, const int32_t volumeIndex, const int32_t mapIndex, const uint8_t sliceOpacity) { /* * When performing voxel identification for editing voxels, * we need to draw EVERY voxel since the user may click * regions where the voxels are "off". */ bool volumeEditingDrawAllVoxelsFlag = false; if (m_identificationModeFlag) { SelectionItemVoxelEditing* voxelEditID = m_brain->getSelectionManager()->getVoxelEditingIdentification(); if (voxelEditID->isEnabledForSelection()) { const VolumeFile* vf = dynamic_cast(volumeInterface); if (vf == voxelEditID->getVolumeFileForEditing()) { volumeEditingDrawAllVoxelsFlag = true; } } } const int64_t numVoxelsInSlice = numberOfColumns * numberOfRows; /* * Allocate for quadrilateral drawing */ const int64_t numQuadCoords = numVoxelsInSlice * 12; const int64_t numQuadRgba = numVoxelsInSlice * 16; std::vector voxelQuadCoordinates; std::vector voxelQuadNormals; std::vector voxelQuadRgba; voxelQuadCoordinates.reserve(numQuadCoords); voxelQuadNormals.reserve(numQuadCoords); voxelQuadRgba.reserve(numQuadRgba); /* * Step to next row or column voxel */ const float rowStepX = rowStep[0]; const float rowStepY = rowStep[1]; const float rowStepZ = rowStep[2]; const float columnStepX = columnStep[0]; const float columnStepY = columnStep[1]; const float columnStepZ = columnStep[2]; /* * Draw each row */ for (int64_t jRow = 0; jRow < numberOfRows; jRow++) { /* * Coordinates on left side of row */ float rowBottomLeft[3] = { coordinate[0] + (jRow * rowStepX), coordinate[1] + (jRow * rowStepY), coordinate[2] + (jRow * rowStepZ) }; float rowTopLeft[3] = { rowBottomLeft[0] + rowStepX, rowBottomLeft[1] + rowStepY, rowBottomLeft[2] + rowStepZ }; /* * Draw each voxel in its column */ for (int64_t iCol = 0; iCol < numberOfColumns; iCol++) { /* * Offset of voxel in coloring. */ const int64_t sliceRgbaOffset = (4 * (iCol + (numberOfColumns * jRow))); const int64_t alphaOffset = sliceRgbaOffset + 3; uint8_t rgba[4] = { 0, 0, 0, 0 }; /* * Negative alpha means do not display */ CaretAssertVectorIndex(sliceRGBA, alphaOffset); if (sliceRGBA[alphaOffset] <= 0) { if (volumeIndex == 0) { /* * For first drawn volume, use an alpha of 255 so * so that black is drawn */ rgba[3] = 255; /* * OVERRIDES BLACK VOXEL ABOVE (255) * For first drawn volume, use an alpha of zero so * that the background shows through */ rgba[3] = 0; //255; } } else { /* * Use overlay's opacity */ rgba[0] = sliceRGBA[sliceRgbaOffset]; rgba[1] = sliceRGBA[sliceRgbaOffset + 1]; rgba[2] = sliceRGBA[sliceRgbaOffset + 2]; rgba[3] = sliceOpacity; } /* * Set coordinates of voxel corners */ float voxelBottomLeft[3] = { rowBottomLeft[0] + (iCol * columnStepX), rowBottomLeft[1] + (iCol * columnStepY), rowBottomLeft[2] + (iCol * columnStepZ) }; float voxelBottomRight[3] = { voxelBottomLeft[0] + columnStepX, voxelBottomLeft[1] + columnStepY, voxelBottomLeft[2] + columnStepZ }; float voxelTopLeft[3] = { rowTopLeft[0] + (iCol * columnStepX), rowTopLeft[1] + (iCol * columnStepY), rowTopLeft[2] + (iCol * columnStepZ) }; float voxelTopRight[3] = { voxelTopLeft[0] + columnStepX, voxelTopLeft[1] + columnStepY, voxelTopLeft[2] + columnStepZ }; /* * Need to draw ALL voxels if performing * identificaiton for voxel editing. */ if (volumeEditingDrawAllVoxelsFlag) { rgba[3] = 255; } /* * Draw voxel based upon opacity */ if (rgba[3] > 0) { if (m_identificationModeFlag) { /* * Add info about voxel for identication. */ int64_t voxelI = 0; int64_t voxelJ = 0; int64_t voxelK = 0; const float voxelCenterX = (voxelBottomLeft[0] + voxelTopRight[0]) / 2.0; const float voxelCenterY = (voxelBottomLeft[1] + voxelTopRight[1]) / 2.0; const float voxelCenterZ = (voxelBottomLeft[2] + voxelTopRight[2]) / 2.0; volumeInterface->enclosingVoxel(voxelCenterX, voxelCenterY, voxelCenterZ, voxelI, voxelJ, voxelK); // switch (sliceViewPlane) { // case VolumeSliceViewPlaneEnum::ALL: // CaretAssert(0); // break; // case VolumeSliceViewPlaneEnum::AXIAL: // voxelI = iCol; // voxelJ = jRow; // voxelK = selectedSliceIndices[2]; // break; // case VolumeSliceViewPlaneEnum::CORONAL: // voxelI = iCol; // voxelJ = selectedSliceIndices[1]; // voxelK = jRow; // break; // case VolumeSliceViewPlaneEnum::PARASAGITTAL: // voxelI = selectedSliceIndices[0]; // voxelJ = iCol; // voxelK = jRow; // break; // } const float voxelDiffXYZ[3] = { voxelTopRight[0] - voxelBottomLeft[0], voxelTopRight[1] - voxelBottomLeft[1], voxelTopRight[2] - voxelBottomLeft[2], }; addVoxelToIdentification(volumeIndex, mapIndex, voxelI, voxelJ, voxelK, voxelDiffXYZ, rgba); } /* * Add voxel to quadrilaterals */ voxelQuadCoordinates.push_back(voxelBottomLeft[0]); voxelQuadCoordinates.push_back(voxelBottomLeft[1]); voxelQuadCoordinates.push_back(voxelBottomLeft[2]); voxelQuadCoordinates.push_back(voxelBottomRight[0]); voxelQuadCoordinates.push_back(voxelBottomRight[1]); voxelQuadCoordinates.push_back(voxelBottomRight[2]); voxelQuadCoordinates.push_back(voxelTopRight[0]); voxelQuadCoordinates.push_back(voxelTopRight[1]); voxelQuadCoordinates.push_back(voxelTopRight[2]); voxelQuadCoordinates.push_back(voxelTopLeft[0]); voxelQuadCoordinates.push_back(voxelTopLeft[1]); voxelQuadCoordinates.push_back(voxelTopLeft[2]); for (int32_t iNormalAndColor = 0; iNormalAndColor < 4; iNormalAndColor++) { voxelQuadRgba.push_back(rgba[0]); voxelQuadRgba.push_back(rgba[1]); voxelQuadRgba.push_back(rgba[2]); voxelQuadRgba.push_back(rgba[3]); voxelQuadNormals.push_back(sliceNormalVector[0]); voxelQuadNormals.push_back(sliceNormalVector[1]); voxelQuadNormals.push_back(sliceNormalVector[2]); } } } } /* * Draw the voxels. */ if (voxelQuadCoordinates.empty() == false) { BrainOpenGLPrimitiveDrawing::drawQuads(voxelQuadCoordinates, voxelQuadNormals, voxelQuadRgba); } } /** * Draw the voxels in an orthogonal slice using quad indices or strips. * * Each vertex (coordinate, its normal vector, and its color) is sent to OpenGL * one time. Index arrays are used to specify the vertices when drawing the * quads. * * This is efficient when many voxels are drawn but may be inefficent * when only a few voxels are drawn. * * @param sliceNormalVector * Normal vector of the slice plane. * @param firstVoxelCoordinate * Coordinate of first voxel in the slice (bottom left as begin viewed) * @param rowStep * Three-dimensional step to next row. * @param columnStep * Three-dimensional step to next column. * @param numberOfColumns * Number of columns in the slice. * @param numberOfRows * Number of rows in the slice. * @param sliceRGBA * RGBA coloring for voxels in the slice. * @param volumeInterface * Index of the volume being drawn. * @param volumeIndex * Selected map in the volume being drawn. * @param mapIndex * Selected map in the volume being drawn. * @param sliceOpacity * Opacity from the overlay. */ void BrainOpenGLVolumeObliqueSliceDrawing::drawOrthogonalSliceVoxelsQuadIndicesAndStrips(const float sliceNormalVector[3], const float firstVoxelCoordinate[3], const float rowStep[3], const float columnStep[3], const int64_t numberOfColumns, const int64_t numberOfRows, const std::vector& sliceRGBA, const VolumeMappableInterface* volumeInterface, const int32_t volumeIndex, const int32_t mapIndex, const uint8_t sliceOpacity) { const bool debugFlag = false; enum DrawType { DRAW_QUADS, DRAW_QUAD_STRIPS }; const DrawType drawType = DRAW_QUADS; /* * When performing voxel identification for editing voxels, * we need to draw EVERY voxel since the user may click * regions where the voxels are "off". */ bool volumeEditingDrawAllVoxelsFlag = false; if (m_identificationModeFlag) { SelectionItemVoxelEditing* voxelEditID = m_brain->getSelectionManager()->getVoxelEditingIdentification(); if (voxelEditID->isEnabledForSelection()) { const VolumeFile* vf = dynamic_cast(volumeInterface); if (vf == voxelEditID->getVolumeFileForEditing()) { volumeEditingDrawAllVoxelsFlag = true; } } } /* * Allocate vectors for quadrilateral drawing */ const int64_t totalCoordElements = (numberOfColumns + 1) * (numberOfRows + 1); const int64_t numQuadStripCoords = totalCoordElements * 3; const int64_t numQuadStripRGBA = totalCoordElements * 4; std::vector voxelQuadCoordinates; std::vector voxelQuadNormals; std::vector voxelQuadRgba; voxelQuadCoordinates.reserve(numQuadStripCoords); voxelQuadNormals.reserve(numQuadStripCoords); voxelQuadRgba.reserve(numQuadStripRGBA); /* * Step to next row or column voxel */ const float rowStepX = rowStep[0]; const float rowStepY = rowStep[1]; const float rowStepZ = rowStep[2]; const float columnStepX = columnStep[0]; const float columnStepY = columnStep[1]; const float columnStepZ = columnStep[2]; const float voxelStepX = rowStepX + columnStepX; const float voxelStepY = rowStepY + columnStepY; const float voxelStepZ = rowStepZ + columnStepZ; const float voxelStepXYZ[3] = { voxelStepX, voxelStepY, voxelStepZ }; const float halfVoxelStepX = (voxelStepX / 2.0); const float halfVoxelStepY = (voxelStepY / 2.0); const float halfVoxelStepZ = (voxelStepZ / 2.0); int64_t numberOfVoxelsToDraw = 0; /* * Loop through column COORDINATES */ float columnBottomCoord[3] = { firstVoxelCoordinate[0], firstVoxelCoordinate[1], firstVoxelCoordinate[2] }; for (int64_t iCol = 0; iCol <= numberOfColumns; iCol++) { if (iCol > 0) { columnBottomCoord[0] += columnStepX; columnBottomCoord[1] += columnStepY; columnBottomCoord[2] += columnStepZ; } // const float columnBottomCoord[3] = { // firstVoxelCoordinate[0] + (iCol * columnStepX), // firstVoxelCoordinate[1] + (iCol * columnStepY), // firstVoxelCoordinate[2] + (iCol * columnStepZ) // }; /* * Loop through the row COORDINATES */ float rowCoord[3] = { columnBottomCoord[0], columnBottomCoord[1], columnBottomCoord[2] }; for (int64_t jRow = 0; jRow <= numberOfRows; jRow++) { if (jRow > 0) { rowCoord[0] += rowStepX; rowCoord[1] += rowStepY; rowCoord[2] += rowStepZ; } // const float coord[3] = { // columnBottomCoord[0] + (jRow * rowStepX), // columnBottomCoord[1] + (jRow * rowStepY), // columnBottomCoord[2] + (jRow * rowStepZ) // }; voxelQuadCoordinates.push_back(rowCoord[0]); voxelQuadCoordinates.push_back(rowCoord[1]); voxelQuadCoordinates.push_back(rowCoord[2]); voxelQuadNormals.push_back(sliceNormalVector[0]); voxelQuadNormals.push_back(sliceNormalVector[1]); voxelQuadNormals.push_back(sliceNormalVector[2]); uint8_t rgba[4] = { 0, 0, 0, 0 }; /* * With FLAT shading: * Quads: Uses top left coordinate for quad coloring * Quad Strip: Uses top right coordinate for quad coloring * So, the color is only set for this coordinate */ int64_t iColRGBA = iCol; int64_t jRowRGBA = jRow; switch (drawType) { case DRAW_QUADS: if (iColRGBA >= numberOfColumns) { iColRGBA = numberOfColumns - 1; } jRowRGBA = jRow - 1; break; case DRAW_QUAD_STRIPS: iColRGBA = iCol - 1; jRowRGBA = jRow - 1; break; } if ((iColRGBA >= 0) && (jRowRGBA >= 0)) { const int64_t voxelOffset = (iColRGBA + (numberOfColumns * jRowRGBA)); if (debugFlag) { std::cout << "col=" << iCol << " row=" << jRow << " voxel-offset=" << voxelOffset << std::endl; } /* * Offset of voxel in coloring. * Note that colors are stored in rows */ int64_t sliceRgbaOffset = (4 * voxelOffset); // int64_t sliceRgbaOffset = (4 * (iColRGBA // + (numberOfColumns * jRowRGBA))); /* * An alpha greater than zero means the voxel is displayed */ const int64_t alphaOffset = sliceRgbaOffset + 3; CaretAssertVectorIndex(sliceRGBA, alphaOffset); if (sliceRGBA[alphaOffset] > 0) { /* * Use overlay's opacity for the voxel */ rgba[0] = sliceRGBA[sliceRgbaOffset]; rgba[1] = sliceRGBA[sliceRgbaOffset + 1]; rgba[2] = sliceRGBA[sliceRgbaOffset + 2]; rgba[3] = sliceOpacity; } } /* * Voxel editing requires drawing of all voxels so that * "off" voxels can be turned "on". */ if (volumeEditingDrawAllVoxelsFlag) { rgba[3] = 255; } /* * Draw voxel if non-zero opacity */ if (rgba[3] > 0) { ++numberOfVoxelsToDraw; if (m_identificationModeFlag) { /* * Identification information is encoded in the * RGBA coloring. */ const float voxelCenterX = rowCoord[0] + halfVoxelStepX; const float voxelCenterY = rowCoord[1] + halfVoxelStepY; const float voxelCenterZ = rowCoord[2] + halfVoxelStepZ; int64_t voxelI = 0; int64_t voxelJ = 0; int64_t voxelK = 0; volumeInterface->enclosingVoxel(voxelCenterX, voxelCenterY, voxelCenterZ, voxelI, voxelJ, voxelK); addVoxelToIdentification(volumeIndex, mapIndex, voxelI, voxelJ, voxelK, voxelStepXYZ, rgba); } } voxelQuadRgba.push_back(rgba[0]); voxelQuadRgba.push_back(rgba[1]); voxelQuadRgba.push_back(rgba[2]); voxelQuadRgba.push_back(rgba[3]); } } const int64_t numberOfCoordinates = voxelQuadCoordinates.size() / 3; if (debugFlag) { std::cout << "Num rows/cols: " << numberOfRows << ", " << numberOfColumns << std::endl; std::cout << "Total, 3, 4 " << totalCoordElements << ", " << numQuadStripCoords << ", " << numQuadStripRGBA << std::endl; std::cout << "Size coords: " << voxelQuadCoordinates.size() << std::endl; std::cout << "Size normals: " << voxelQuadNormals.size() << std::endl; std::cout << "Size rgba: " << voxelQuadRgba.size() << std::endl; std::cout << "Valid voxels: " << numberOfVoxelsToDraw << std::endl; for (int64_t i = 0; i < numberOfCoordinates; i++) { std::cout << i << ": "; CaretAssertVectorIndex(voxelQuadCoordinates, i*3 + 2); std::cout << qPrintable(AString::fromNumbers(&voxelQuadCoordinates[i*3], 3, ",")) << " "; CaretAssertVectorIndex(voxelQuadRgba, i*4 + 3); std::cout << qPrintable(AString::fromNumbers(&voxelQuadRgba[i*4], 4, ",")) << std::endl; } } /* * Setup indices into coordinates/normals/coloring to draw the quads */ switch (drawType) { case DRAW_QUADS: { std::vector quadIndices; quadIndices.reserve(numberOfVoxelsToDraw * 4); for (int64_t iCol = 0; iCol < numberOfColumns; iCol++) { const int64_t columnOffset = (iCol * (numberOfRows + 1)); for (int64_t jRow = 0; jRow < numberOfRows; jRow++) { const int64_t coordBottomLeftIndex = columnOffset + jRow; //(iCol * (numberOfRows + 1) + jRow); const int64_t coordTopLeftIndex = coordBottomLeftIndex + 1; const int64_t rgbaIndex = coordTopLeftIndex * 4; CaretAssert(coordBottomLeftIndex < numberOfCoordinates); CaretAssert(coordTopLeftIndex < numberOfCoordinates); CaretAssertVectorIndex(voxelQuadRgba, rgbaIndex + 3); if (voxelQuadRgba[rgbaIndex + 3] > 0) { /* * For quads: (bottom left, bottom right, top right, top left) * Color with flat shading comes from the top left coordinate */ const int32_t coordBottomRightIndex = coordBottomLeftIndex + (numberOfRows + 1); const int32_t coordTopRightIndex = coordBottomRightIndex + 1; CaretAssert(coordBottomRightIndex < numberOfCoordinates); CaretAssert(coordTopRightIndex < numberOfCoordinates); quadIndices.push_back(coordBottomLeftIndex); quadIndices.push_back(coordBottomRightIndex); quadIndices.push_back(coordTopRightIndex); quadIndices.push_back(coordTopLeftIndex); } } if (debugFlag) { std::cout << "Quad Indices: " << quadIndices.size() << std::endl; for (uint32_t i = 0; i < quadIndices.size(); i++) { std::cout << quadIndices[i] << " ("; const int32_t coordOffset = quadIndices[i] * 3; CaretAssertVectorIndex(voxelQuadCoordinates, coordOffset + 2); std::cout << qPrintable(AString::fromNumbers(&voxelQuadCoordinates[coordOffset], 3, ",")) << ") ("; const int32_t rgbaOffset = quadIndices[i] * 4; CaretAssertVectorIndex(voxelQuadRgba, rgbaOffset + 3); std::cout << qPrintable(AString::fromNumbers(&voxelQuadRgba[rgbaOffset], 4, ",")) << " " << std::endl; } std::cout << std::endl; } } if (debugFlag) { std::cout << "Drawing " << quadIndices.size() / 4 << " quads." << std::endl; } BrainOpenGLPrimitiveDrawing::drawQuadIndices(voxelQuadCoordinates, voxelQuadNormals, voxelQuadRgba, quadIndices); } break; case DRAW_QUAD_STRIPS: { int64_t stripCount = 0; const int64_t maxCoordsPerStrip = numberOfRows * 2 + 2; for (int64_t iCol = 0; iCol < numberOfColumns; iCol++) { std::vector quadIndices; quadIndices.reserve(maxCoordsPerStrip); for (int64_t jRow = 0; jRow < numberOfRows; jRow++) { const int32_t coordBottomLeftIndex = (iCol * (numberOfRows + 1) + jRow); const int32_t coordTopLeftIndex = coordBottomLeftIndex + 1; const int32_t coordBottomRightIndex = coordBottomLeftIndex + (numberOfRows + 1); const int32_t coordTopRightIndex = coordBottomRightIndex + 1; const int64_t rgbaIndex = coordTopRightIndex * 4; CaretAssert(coordBottomLeftIndex < numberOfCoordinates); CaretAssert(coordTopLeftIndex < numberOfCoordinates); CaretAssert(coordBottomRightIndex < numberOfCoordinates); CaretAssert(coordTopRightIndex < numberOfCoordinates); CaretAssertVectorIndex(voxelQuadRgba, rgbaIndex + 3); if (voxelQuadRgba[rgbaIndex + 3] > 0) { /* * For quad strips (bottom left, bottom right, top left, top right) */ if (quadIndices.empty()) { quadIndices.push_back(coordBottomLeftIndex); quadIndices.push_back(coordBottomRightIndex); } quadIndices.push_back(coordTopLeftIndex); quadIndices.push_back(coordTopRightIndex); } else { if ( ! quadIndices.empty()) { if (debugFlag) { std::cout << "Quad Indices: " << quadIndices.size() << std::endl; for (uint32_t i = 0; i < quadIndices.size(); i++) { std::cout << quadIndices[i] << " ("; const int32_t coordOffset = quadIndices[i] * 3; CaretAssertVectorIndex(voxelQuadCoordinates, coordOffset + 2); std::cout << qPrintable(AString::fromNumbers(&voxelQuadCoordinates[coordOffset], 3, ",")) << ") ("; const int32_t rgbaOffset = quadIndices[i] * 4; CaretAssertVectorIndex(voxelQuadRgba, rgbaOffset + 3); std::cout << qPrintable(AString::fromNumbers(&voxelQuadRgba[rgbaOffset], 4, ",")) << " " << std::endl; } std::cout << std::endl; } BrainOpenGLPrimitiveDrawing::drawQuadStrips(voxelQuadCoordinates, voxelQuadNormals, voxelQuadRgba, quadIndices); quadIndices.clear(); stripCount++; } } } if ( ! quadIndices.empty()) { if (debugFlag) { std::cout << "Quad Indices: " << quadIndices.size() << std::endl; for (uint32_t i = 0; i < quadIndices.size(); i++) { std::cout << quadIndices[i] << " ("; const int32_t coordOffset = quadIndices[i] * 3; CaretAssertVectorIndex(voxelQuadCoordinates, coordOffset + 2); std::cout << qPrintable(AString::fromNumbers(&voxelQuadCoordinates[coordOffset], 3, ",")) << ") ("; const int32_t rgbaOffset = quadIndices[i] * 4; CaretAssertVectorIndex(voxelQuadRgba, rgbaOffset + 3); std::cout << qPrintable(AString::fromNumbers(&voxelQuadRgba[rgbaOffset], 4, ",")) << " " << std::endl; } std::cout << std::endl; } BrainOpenGLPrimitiveDrawing::drawQuadStrips(voxelQuadCoordinates, voxelQuadNormals, voxelQuadRgba, quadIndices); quadIndices.clear(); stripCount++; } // if (debugFlag) { // std::cout << "Quad Indices: " << quadIndices.size() << std::endl; // for (uint32_t i = 0; i < quadIndices.size(); i++) { // std::cout << quadIndices[i] << " "; // const int32_t coordOffset = quadIndices[i] * 3; // std::cout << qPrintable(AString::fromNumbers(&voxelQuadCoordinates[coordOffset], 3, ",")) << std::endl; // } // std::cout << std::endl; // } } if (debugFlag) { std::cout << "Strips drawn: " << stripCount << std::endl; } } break; } } ///** // * Draw the voxels in an orthogonal slice using quad indices or strips. // * // * Each vertex (coordinate, its normal vector, and its color) is sent to OpenGL // * one time. Index arrays are used to specify the vertices when drawing the // * quads. // * // * This is efficient when many voxels are drawn but may be inefficent // * when only a few voxels are drawn. // * // * @param sliceNormalVector // * Normal vector of the slice plane. // * @param coordinate // * Coordinate of first voxel in the slice (bottom left as begin viewed) // * @param rowStep // * Three-dimensional step to next row. // * @param columnStep // * Three-dimensional step to next column. // * @param numberOfColumns // * Number of columns in the slice. // * @param numberOfRows // * Number of rows in the slice. // * @param sliceRGBA // * RGBA coloring for voxels in the slice. // * @param volumeInterface // * Index of the volume being drawn. // * @param volumeIndex // * Selected map in the volume being drawn. // * @param mapIndex // * Selected map in the volume being drawn. // * @param sliceOpacity // * Opacity from the overlay. // */ //void //BrainOpenGLVolumeObliqueSliceDrawing::drawOrthogonalSliceVoxelsQuadIndicesAndStrips(const float sliceNormalVector[3], // const float coordinate[3], // const float rowStep[3], // const float columnStep[3], // const int64_t numberOfColumns, // const int64_t numberOfRows, // const std::vector& sliceRGBA, // const VolumeMappableInterface* volumeInterface, // const int32_t volumeIndex, // const int32_t mapIndex, // const uint8_t sliceOpacity) //{ // const bool debugFlag = false; // // enum DrawType { // DRAW_QUADS, // DRAW_QUAD_STRIPS // }; // // const DrawType drawType = DRAW_QUADS; // // /* // * When performing voxel identification for editing voxels, // * we need to draw EVERY voxel since the user may click // * regions where the voxels are "off". // */ // bool volumeEditingDrawAllVoxelsFlag = false; // if (m_identificationModeFlag) { // SelectionItemVoxelEditing* voxelEditID = m_brain->getSelectionManager()->getVoxelEditingIdentification(); // if (voxelEditID->isEnabledForSelection()) { // const VolumeFile* vf = dynamic_cast(volumeInterface); // if (vf == voxelEditID->getVolumeFileForEditing()) { // volumeEditingDrawAllVoxelsFlag = true; // } // } // } // // /* // * Allocate vectors for quadrilateral drawing // */ // const int64_t totalCoordElements = (numberOfColumns + 1) * (numberOfRows + 1); // const int64_t numQuadStripCoords = totalCoordElements * 3; // const int64_t numQuadStripRGBA = totalCoordElements * 4; // std::vector voxelQuadCoordinates; // std::vector voxelQuadNormals; // std::vector voxelQuadRgba; // voxelQuadCoordinates.reserve(numQuadStripCoords); // voxelQuadNormals.reserve(numQuadStripCoords); // voxelQuadRgba.reserve(numQuadStripRGBA); // // /* // * Step to next row or column voxel // */ // const float rowStepX = rowStep[0]; // const float rowStepY = rowStep[1]; // const float rowStepZ = rowStep[2]; // const float columnStepX = columnStep[0]; // const float columnStepY = columnStep[1]; // const float columnStepZ = columnStep[2]; // // const float voxelStepX = rowStepX + columnStepX; // const float voxelStepY = rowStepY + columnStepY; // const float voxelStepZ = rowStepZ + columnStepZ; // const float voxelStepXYZ[3] = { // voxelStepX, // voxelStepY, // voxelStepZ // }; // // const float halfVoxelStepX = (voxelStepX / 2.0); // const float halfVoxelStepY = (voxelStepY / 2.0); // const float halfVoxelStepZ = (voxelStepZ / 2.0); // // int64_t numberOfVoxelsToDraw = 0; // // /* // * Loop through column COORDINATES // */ // for (int64_t iCol = 0; iCol <= numberOfColumns; iCol++) { // const float columnBottomCoord[3] = { // coordinate[0] + (iCol * columnStepX), // coordinate[1] + (iCol * columnStepY), // coordinate[2] + (iCol * columnStepZ) // }; // // /* // * Loop through the row COORDINATES // */ // for (int64_t jRow = 0; jRow <= numberOfRows; jRow++) { // const float coord[3] = { // columnBottomCoord[0] + (jRow * rowStepX), // columnBottomCoord[1] + (jRow * rowStepY), // columnBottomCoord[2] + (jRow * rowStepZ) // }; // // voxelQuadCoordinates.push_back(coord[0]); // voxelQuadCoordinates.push_back(coord[1]); // voxelQuadCoordinates.push_back(coord[2]); // // voxelQuadNormals.push_back(sliceNormalVector[0]); // voxelQuadNormals.push_back(sliceNormalVector[1]); // voxelQuadNormals.push_back(sliceNormalVector[2]); // // uint8_t rgba[4] = { // 0, // 0, // 0, // 0 // }; // // /* // * With FLAT shading: // * Quads: Uses top left coordinate for quad coloring // * Quad Strip: Uses top right coordinate for quad coloring // * So, the color is only set for this coordinate // */ // int64_t iColRGBA = iCol; // int64_t jRowRGBA = jRow; // switch (drawType) { // case DRAW_QUADS: // if (iColRGBA >= numberOfColumns) { // iColRGBA = numberOfColumns - 1; // } // jRowRGBA = jRow - 1; // break; // case DRAW_QUAD_STRIPS: // iColRGBA = iCol - 1; // jRowRGBA = jRow - 1; // break; // } // if ((iColRGBA >= 0) // && (jRowRGBA >= 0)) { // const int64_t voxelOffset = (iColRGBA // + (numberOfColumns * jRowRGBA)); // if (debugFlag) { // std::cout << "col=" << iCol << " row=" << jRow << " voxel-offset=" << voxelOffset << std::endl; // } // // /* // * Offset of voxel in coloring. // * Note that colors are stored in rows // */ // int64_t sliceRgbaOffset = (4 * (iColRGBA // + (numberOfColumns * jRowRGBA))); // // /* // * An alpha greater than zero means the voxel is displayed // */ // const int64_t alphaOffset = sliceRgbaOffset + 3; // CaretAssertVectorIndex(sliceRGBA, alphaOffset); // if (sliceRGBA[alphaOffset] > 0) { // /* // * Use overlay's opacity for the voxel // */ // rgba[0] = sliceRGBA[sliceRgbaOffset]; // rgba[1] = sliceRGBA[sliceRgbaOffset + 1]; // rgba[2] = sliceRGBA[sliceRgbaOffset + 2]; // rgba[3] = sliceOpacity; // } // } // // /* // * Voxel editing requires drawing of all voxels so that // * "off" voxels can be turned "on". // */ // if (volumeEditingDrawAllVoxelsFlag) { // rgba[3] = 255; // } // // /* // * Draw voxel if non-zero opacity // */ // if (rgba[3] > 0) { // // ++numberOfVoxelsToDraw; // // if (m_identificationModeFlag) { // /* // * Identification information is encoded in the // * RGBA coloring. // */ // const float voxelCenterX = coord[0] + halfVoxelStepX; // const float voxelCenterY = coord[1] + halfVoxelStepY; // const float voxelCenterZ = coord[2] + halfVoxelStepZ; // int64_t voxelI = 0; // int64_t voxelJ = 0; // int64_t voxelK = 0; // volumeInterface->enclosingVoxel(voxelCenterX, voxelCenterY, voxelCenterZ, // voxelI, voxelJ, voxelK); // // addVoxelToIdentification(volumeIndex, // mapIndex, // voxelI, // voxelJ, // voxelK, // voxelStepXYZ, // rgba); // } // } // // voxelQuadRgba.push_back(rgba[0]); // voxelQuadRgba.push_back(rgba[1]); // voxelQuadRgba.push_back(rgba[2]); // voxelQuadRgba.push_back(rgba[3]); // } // } // // const int64_t numberOfCoordinates = voxelQuadCoordinates.size() / 3; // if (debugFlag) { // std::cout << "Num rows/cols: " << numberOfRows << ", " << numberOfColumns << std::endl; // std::cout << "Total, 3, 4 " << totalCoordElements << ", " << numQuadStripCoords << ", " << numQuadStripRGBA << std::endl; // std::cout << "Size coords: " << voxelQuadCoordinates.size() << std::endl; // std::cout << "Size normals: " << voxelQuadNormals.size() << std::endl; // std::cout << "Size rgba: " << voxelQuadRgba.size() << std::endl; // std::cout << "Valid voxels: " << numberOfVoxelsToDraw << std::endl; // // for (int64_t i = 0; i < numberOfCoordinates; i++) { // std::cout << i << ": "; // CaretAssertVectorIndex(voxelQuadCoordinates, i*3 + 2); // std::cout << qPrintable(AString::fromNumbers(&voxelQuadCoordinates[i*3], 3, ",")) << " "; // CaretAssertVectorIndex(voxelQuadRgba, i*4 + 3); // std::cout << qPrintable(AString::fromNumbers(&voxelQuadRgba[i*4], 4, ",")) << std::endl; // } // } // // /* // * Setup indices into coordinates/normals/coloring to draw the quads // */ // switch (drawType) { // case DRAW_QUADS: // { // std::vector quadIndices; // quadIndices.reserve(numberOfVoxelsToDraw * 4); // // for (int64_t iCol = 0; iCol < numberOfColumns; iCol++) { // for (int64_t jRow = 0; jRow < numberOfRows; jRow++) { // const int32_t coordBottomLeftIndex = (iCol * (numberOfRows + 1) + jRow); // const int32_t coordTopLeftIndex = coordBottomLeftIndex + 1; // const int64_t rgbaIndex = coordTopLeftIndex * 4; // // CaretAssert(coordBottomLeftIndex < numberOfCoordinates); // CaretAssert(coordTopLeftIndex < numberOfCoordinates); // CaretAssertVectorIndex(voxelQuadRgba, rgbaIndex + 3); // // if (voxelQuadRgba[rgbaIndex + 3] > 0) { // /* // * For quads: (bottom left, bottom right, top right, top left) // * Color with flat shading comes from the top left coordinate // */ // const int32_t coordBottomRightIndex = coordBottomLeftIndex + (numberOfRows + 1); // const int32_t coordTopRightIndex = coordBottomRightIndex + 1; // CaretAssert(coordBottomRightIndex < numberOfCoordinates); // CaretAssert(coordTopRightIndex < numberOfCoordinates); // // quadIndices.push_back(coordBottomLeftIndex); // quadIndices.push_back(coordBottomRightIndex); // quadIndices.push_back(coordTopRightIndex); // quadIndices.push_back(coordTopLeftIndex); // } // } // // if (debugFlag) { // std::cout << "Quad Indices: " << quadIndices.size() << std::endl; // for (uint32_t i = 0; i < quadIndices.size(); i++) { // std::cout << quadIndices[i] << " ("; // const int32_t coordOffset = quadIndices[i] * 3; // CaretAssertVectorIndex(voxelQuadCoordinates, coordOffset + 2); // std::cout << qPrintable(AString::fromNumbers(&voxelQuadCoordinates[coordOffset], 3, ",")) << ") ("; // // const int32_t rgbaOffset = quadIndices[i] * 4; // CaretAssertVectorIndex(voxelQuadRgba, rgbaOffset + 3); // std::cout << qPrintable(AString::fromNumbers(&voxelQuadRgba[rgbaOffset], 4, ",")) << " " << std::endl; // } // std::cout << std::endl; // } // } // // if (debugFlag) { // std::cout << "Drawing " << quadIndices.size() / 4 << " quads." << std::endl; // } // BrainOpenGLPrimitiveDrawing::drawQuadIndices(voxelQuadCoordinates, // voxelQuadNormals, // voxelQuadRgba, // quadIndices); // } // break; // case DRAW_QUAD_STRIPS: // { // int64_t stripCount = 0; // // const int64_t maxCoordsPerStrip = numberOfRows * 2 + 2; // // for (int64_t iCol = 0; iCol < numberOfColumns; iCol++) { // std::vector quadIndices; // quadIndices.reserve(maxCoordsPerStrip); // // for (int64_t jRow = 0; jRow < numberOfRows; jRow++) { // const int32_t coordBottomLeftIndex = (iCol * (numberOfRows + 1) + jRow); // const int32_t coordTopLeftIndex = coordBottomLeftIndex + 1; // const int32_t coordBottomRightIndex = coordBottomLeftIndex + (numberOfRows + 1); // const int32_t coordTopRightIndex = coordBottomRightIndex + 1; // const int64_t rgbaIndex = coordTopRightIndex * 4; // // CaretAssert(coordBottomLeftIndex < numberOfCoordinates); // CaretAssert(coordTopLeftIndex < numberOfCoordinates); // CaretAssert(coordBottomRightIndex < numberOfCoordinates); // CaretAssert(coordTopRightIndex < numberOfCoordinates); // CaretAssertVectorIndex(voxelQuadRgba, rgbaIndex + 3); // // if (voxelQuadRgba[rgbaIndex + 3] > 0) { // /* // * For quad strips (bottom left, bottom right, top left, top right) // */ // if (quadIndices.empty()) { // quadIndices.push_back(coordBottomLeftIndex); // quadIndices.push_back(coordBottomRightIndex); // } // quadIndices.push_back(coordTopLeftIndex); // quadIndices.push_back(coordTopRightIndex); // } // else { // if ( ! quadIndices.empty()) { // if (debugFlag) { // std::cout << "Quad Indices: " << quadIndices.size() << std::endl; // for (uint32_t i = 0; i < quadIndices.size(); i++) { // std::cout << quadIndices[i] << " ("; // const int32_t coordOffset = quadIndices[i] * 3; // CaretAssertVectorIndex(voxelQuadCoordinates, coordOffset + 2); // std::cout << qPrintable(AString::fromNumbers(&voxelQuadCoordinates[coordOffset], 3, ",")) << ") ("; // // const int32_t rgbaOffset = quadIndices[i] * 4; // CaretAssertVectorIndex(voxelQuadRgba, rgbaOffset + 3); // std::cout << qPrintable(AString::fromNumbers(&voxelQuadRgba[rgbaOffset], 4, ",")) << " " << std::endl; // } // std::cout << std::endl; // } // BrainOpenGLPrimitiveDrawing::drawQuadStrips(voxelQuadCoordinates, // voxelQuadNormals, // voxelQuadRgba, // quadIndices); // quadIndices.clear(); // stripCount++; // } // } // // } // if ( ! quadIndices.empty()) { // if (debugFlag) { // std::cout << "Quad Indices: " << quadIndices.size() << std::endl; // for (uint32_t i = 0; i < quadIndices.size(); i++) { // std::cout << quadIndices[i] << " ("; // const int32_t coordOffset = quadIndices[i] * 3; // CaretAssertVectorIndex(voxelQuadCoordinates, coordOffset + 2); // std::cout << qPrintable(AString::fromNumbers(&voxelQuadCoordinates[coordOffset], 3, ",")) << ") ("; // // const int32_t rgbaOffset = quadIndices[i] * 4; // CaretAssertVectorIndex(voxelQuadRgba, rgbaOffset + 3); // std::cout << qPrintable(AString::fromNumbers(&voxelQuadRgba[rgbaOffset], 4, ",")) << " " << std::endl; // } // std::cout << std::endl; // } // BrainOpenGLPrimitiveDrawing::drawQuadStrips(voxelQuadCoordinates, // voxelQuadNormals, // voxelQuadRgba, // quadIndices); // quadIndices.clear(); // stripCount++; // } // //// if (debugFlag) { //// std::cout << "Quad Indices: " << quadIndices.size() << std::endl; //// for (uint32_t i = 0; i < quadIndices.size(); i++) { //// std::cout << quadIndices[i] << " "; //// const int32_t coordOffset = quadIndices[i] * 3; //// std::cout << qPrintable(AString::fromNumbers(&voxelQuadCoordinates[coordOffset], 3, ",")) << std::endl; //// } //// std::cout << std::endl; //// } // } // if (debugFlag) { // std::cout << "Strips drawn: " << stripCount << std::endl; // } // } // break; // } //} /** * Reset for volume identification. * * Clear identification indices and if identification is enabled, * reserve a reasonable amount of space for the indices. */ void BrainOpenGLVolumeObliqueSliceDrawing::resetIdentification() { m_identificationIndices.clear(); if (m_identificationModeFlag) { int64_t estimatedNumberOfItems = 0; std::vector volumeDims; m_volumeDrawInfo[0].volumeFile->getDimensions(volumeDims); if (volumeDims.size() >= 3) { const int64_t maxDim = std::max(volumeDims[0], std::max(volumeDims[1], volumeDims[2])); estimatedNumberOfItems = maxDim * maxDim * IDENTIFICATION_INDICES_PER_VOXEL; } m_identificationIndices.reserve(estimatedNumberOfItems); } } /** * Add a voxel to the identification indices. * * @param volumeIndex * Index of the volume. * @param mapIndex * Index of the volume map. * @param voxelI * Voxel Index I * @param voxelJ * Voxel Index J * @param voxelK * Voxel Index K * @param voxelDiffXYZ * Change in XYZ from voxel bottom left to top right * @param rgbaForColorIdentificationOut * Encoded identification in RGBA color OUTPUT */ void BrainOpenGLVolumeObliqueSliceDrawing::addVoxelToIdentification(const int32_t volumeIndex, const int32_t mapIndex, const int32_t voxelI, const int32_t voxelJ, const int32_t voxelK, const float voxelDiffXYZ[3], uint8_t rgbaForColorIdentificationOut[4]) { const int32_t idIndex = m_identificationIndices.size() / IDENTIFICATION_INDICES_PER_VOXEL; m_fixedPipelineDrawing->colorIdentification->addItem(rgbaForColorIdentificationOut, SelectionItemDataTypeEnum::VOXEL, idIndex); rgbaForColorIdentificationOut[3] = 255; /* * ID stack requires integers to * use an integer pointer to the float values */ CaretAssert(sizeof(float) == sizeof(int32_t)); const int32_t* intPointerDiffXYZ = (int32_t*)voxelDiffXYZ; /* * If these items change, need to update reset and * processing of identification. */ m_identificationIndices.push_back(volumeIndex); m_identificationIndices.push_back(mapIndex); m_identificationIndices.push_back(voxelI); m_identificationIndices.push_back(voxelJ); m_identificationIndices.push_back(voxelK); m_identificationIndices.push_back(intPointerDiffXYZ[0]); m_identificationIndices.push_back(intPointerDiffXYZ[1]); m_identificationIndices.push_back(intPointerDiffXYZ[2]); } /** * Process voxel identification. */ void BrainOpenGLVolumeObliqueSliceDrawing::processIdentification() { int32_t identifiedItemIndex; float depth = -1.0; m_fixedPipelineDrawing->getIndexFromColorSelection(SelectionItemDataTypeEnum::VOXEL, m_fixedPipelineDrawing->mouseX, m_fixedPipelineDrawing->mouseY, identifiedItemIndex, depth); if (identifiedItemIndex >= 0) { const int32_t idIndex = identifiedItemIndex * IDENTIFICATION_INDICES_PER_VOXEL; const int32_t volDrawInfoIndex = m_identificationIndices[idIndex]; CaretAssertVectorIndex(m_volumeDrawInfo, volDrawInfoIndex); VolumeMappableInterface* vf = m_volumeDrawInfo[volDrawInfoIndex].volumeFile; const int64_t voxelIndices[3] = { m_identificationIndices[idIndex + 2], m_identificationIndices[idIndex + 3], m_identificationIndices[idIndex + 4] }; const float* floatDiffXYZ = (float*)&m_identificationIndices[idIndex + 5]; SelectionItemVoxel* voxelID = m_brain->getSelectionManager()->getVoxelIdentification(); if (voxelID->isEnabledForSelection()) { if (voxelID->isOtherScreenDepthCloserToViewer(depth)) { voxelID->setVoxelIdentification(m_brain, vf, voxelIndices, depth); float voxelCoordinates[3]; vf->indexToSpace(voxelIndices[0], voxelIndices[1], voxelIndices[2], voxelCoordinates[0], voxelCoordinates[1], voxelCoordinates[2]); m_fixedPipelineDrawing->setSelectedItemScreenXYZ(voxelID, voxelCoordinates); CaretLogFinest("Selected Voxel (3D): " + AString::fromNumbers(voxelIndices, 3, ",")); } } SelectionItemVoxelEditing* voxelEditID = m_brain->getSelectionManager()->getVoxelEditingIdentification(); if (voxelEditID->isEnabledForSelection()) { if (voxelEditID->getVolumeFileForEditing() == vf) { if (voxelEditID->isOtherScreenDepthCloserToViewer(depth)) { voxelEditID->setVoxelIdentification(m_brain, vf, voxelIndices, depth); voxelEditID->setVoxelDiffXYZ(floatDiffXYZ); float voxelCoordinates[3]; vf->indexToSpace(voxelIndices[0], voxelIndices[1], voxelIndices[2], voxelCoordinates[0], voxelCoordinates[1], voxelCoordinates[2]); m_fixedPipelineDrawing->setSelectedItemScreenXYZ(voxelEditID, voxelCoordinates); CaretLogFinest("Selected Voxel Editing (3D): Indices (" + AString::fromNumbers(voxelIndices, 3, ",") + ") Diff XYZ (" + AString::fromNumbers(floatDiffXYZ, 3, ",") + ")"); } } } } } /** * Get the maximum bounds that enclose the volumes and the minimum * voxel spacing from the volumes. * * @param boundsOut * Bounds of the volumes. * @param spacingOut * Minimum voxel spacing from the volumes. Always positive values (even if * volumes is oriented right to left). * */ bool BrainOpenGLVolumeObliqueSliceDrawing::getVoxelCoordinateBoundsAndSpacing(float boundsOut[6], float spacingOut[3]) { const int32_t numberOfVolumesToDraw = static_cast(m_volumeDrawInfo.size()); if (numberOfVolumesToDraw <= 0) { return false; } /* * Find maximum extent of all voxels and smallest voxel * size in each dimension. */ float minVoxelX = std::numeric_limits::max(); float maxVoxelX = -std::numeric_limits::max(); float minVoxelY = std::numeric_limits::max(); float maxVoxelY = -std::numeric_limits::max(); float minVoxelZ = std::numeric_limits::max(); float maxVoxelZ = -std::numeric_limits::max(); float voxelStepX = std::numeric_limits::max(); float voxelStepY = std::numeric_limits::max(); float voxelStepZ = std::numeric_limits::max(); for (int32_t i = 0; i < numberOfVolumesToDraw; i++) { const VolumeMappableInterface* volumeFile = m_volumeDrawInfo[i].volumeFile; int64_t dimI, dimJ, dimK, numMaps, numComponents; volumeFile->getDimensions(dimI, dimJ, dimK, numMaps, numComponents); float originX, originY, originZ; float x1, y1, z1; float lastX, lastY, lastZ; volumeFile->indexToSpace(0, 0, 0, originX, originY, originZ); volumeFile->indexToSpace(1, 1, 1, x1, y1, z1); volumeFile->indexToSpace(dimI - 1, dimJ - 1, dimK - 1, lastX, lastY, lastZ); const float dx = x1 - originX; const float dy = y1 - originY; const float dz = z1 - originZ; voxelStepX = std::min(voxelStepX, std::fabs(dx)); voxelStepY = std::min(voxelStepY, std::fabs(dy)); voxelStepZ = std::min(voxelStepZ, std::fabs(dz)); minVoxelX = std::min(minVoxelX, std::min(originX, lastX)); maxVoxelX = std::max(maxVoxelX, std::max(originX, lastX)); minVoxelY = std::min(minVoxelY, std::min(originY, lastY)); maxVoxelY = std::max(maxVoxelY, std::max(originY, lastY)); minVoxelZ = std::min(minVoxelZ, std::min(originZ, lastZ)); maxVoxelZ = std::max(maxVoxelZ, std::max(originZ, lastZ)); } boundsOut[0] = minVoxelX; boundsOut[1] = maxVoxelX; boundsOut[2] = minVoxelY; boundsOut[3] = maxVoxelY; boundsOut[4] = minVoxelZ; boundsOut[5] = maxVoxelZ; spacingOut[0] = voxelStepX; spacingOut[1] = voxelStepY; spacingOut[2] = voxelStepZ; bool valid = false; if ((maxVoxelX > minVoxelX) && (maxVoxelY > minVoxelY) && (maxVoxelZ > minVoxelZ) && (voxelStepX > 0.0) && (voxelStepY > 0.0) && (voxelStepZ > 0.0)) { valid = true; } return valid; } /** * Create the oblique transformation matrix. * * @param sliceCoordinates * Slice that is being drawn. * @param obliqueTransformationMatrixOut * OUTPUT transformation matrix for oblique viewing. */ void BrainOpenGLVolumeObliqueSliceDrawing::createObliqueTransformationMatrix(const float sliceCoordinates[3], Matrix4x4& obliqueTransformationMatrixOut) { /* * Initialize the oblique transformation matrix */ obliqueTransformationMatrixOut.identity(); /* * Get the oblique rotation matrix */ Matrix4x4 obliqueRotationMatrix = m_browserTabContent->getObliqueVolumeRotationMatrix(); /* * Create the transformation matrix */ obliqueTransformationMatrixOut.postmultiply(obliqueRotationMatrix); /* * Translate to selected coordinate */ obliqueTransformationMatrixOut.translate(sliceCoordinates[0], sliceCoordinates[1], sliceCoordinates[2]); } /** * Find portion (or all) of slice that fits inside the graphics window. * * @param sliceViewPlane * The orthogonal plane being viewed. * @param selectedSliceCoordinate * Coordinate of the slice being drawn. * @param volumeFile * Volume file that is to be drawn. * @param culledFirstVoxelIJKOut * First (bottom left) voxel that will be drawn for the volume file. * @param culledLastVoxelIJKOut * Last (top right) voxel that will be drawn for the volume file. * @param voxelDeltaXYZOut * Voxel sizes for the volume file. The element corresponding to the * slice plane being drawn will be zero (axial => [2]=0) */ bool BrainOpenGLVolumeObliqueSliceDrawing::getVolumeDrawingViewDependentCulling(const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const float selectedSliceCoordinate, const VolumeMappableInterface* volumeFile, int64_t culledFirstVoxelIJKOut[3], int64_t culledLastVoxelIJKOut[3], float voxelDeltaXYZOut[3]) { GLint viewport[4]; glGetIntegerv(GL_VIEWPORT, viewport); GLdouble projectionMatrix[16]; glGetDoublev(GL_PROJECTION_MATRIX, projectionMatrix); GLdouble modelMatrix[16]; glGetDoublev(GL_MODELVIEW_MATRIX, modelMatrix); const double vpMinX = viewport[0]; const double vpMaxX = viewport[0] + viewport[2]; const double vpMinY = viewport[1]; const double vpMaxY = viewport[1] + viewport[3]; GLdouble bottomLeftWin[3] = { vpMinX, vpMinY, 0.0 }; GLdouble bottomRightWin[3] = { vpMaxX, vpMinY, 0.0 }; GLdouble topRightWin[3] = { vpMaxX, vpMaxY, 0.0 }; GLdouble topLeftWin[3] = { vpMinX, vpMaxY, 0.0 }; GLdouble bottomLeftCoord[3]; int32_t cornersValidCount = 0; if (gluUnProject(bottomLeftWin[0], bottomLeftWin[1], bottomLeftWin[2], modelMatrix, projectionMatrix, viewport, &bottomLeftCoord[0], &bottomLeftCoord[1], &bottomLeftCoord[2])) { cornersValidCount++; } GLdouble bottomRightCoord[3]; if (gluUnProject(bottomRightWin[0], bottomRightWin[1], bottomRightWin[2], modelMatrix, projectionMatrix, viewport, &bottomRightCoord[0], &bottomRightCoord[1], &bottomRightCoord[2])) { cornersValidCount++; } GLdouble topRightCoord[3]; if (gluUnProject(topRightWin[0], topRightWin[1], topRightWin[2], modelMatrix, projectionMatrix, viewport, &topRightCoord[0], &topRightCoord[1], &topRightCoord[2])) { cornersValidCount++; } GLdouble topLeftCoord[3]; if (gluUnProject(topLeftWin[0], topLeftWin[1], topLeftWin[2], modelMatrix, projectionMatrix, viewport, &topLeftCoord[0], &topLeftCoord[1], &topLeftCoord[2])) { cornersValidCount++; } if (cornersValidCount != 4) { return false; } switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: CaretAssert(0); break; case VolumeSliceViewPlaneEnum::AXIAL: bottomLeftCoord[2] = selectedSliceCoordinate; bottomRightCoord[2] = selectedSliceCoordinate; topRightCoord[2] = selectedSliceCoordinate; topLeftCoord[2] = selectedSliceCoordinate; break; case VolumeSliceViewPlaneEnum::CORONAL: bottomLeftCoord[1] = selectedSliceCoordinate; bottomRightCoord[1] = selectedSliceCoordinate; topRightCoord[1] = selectedSliceCoordinate; topLeftCoord[1] = selectedSliceCoordinate; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: bottomLeftCoord[0] = selectedSliceCoordinate; bottomRightCoord[0] = selectedSliceCoordinate; topRightCoord[0] = selectedSliceCoordinate; topLeftCoord[0] = selectedSliceCoordinate; break; } // std::cout << std::endl; // std::cout << "Bottom Left: " << qPrintable(AString::fromNumbers(bottomLeftCoord, 3, ",")) << std::endl; // std::cout << "Bottom Right: " << qPrintable(AString::fromNumbers(bottomRightCoord, 3, ",")) << std::endl; // std::cout << "Top Right: " << qPrintable(AString::fromNumbers(topRightCoord, 3, ",")) << std::endl; // std::cout << "Top Left: " << qPrintable(AString::fromNumbers(topLeftCoord, 3, ",")) << std::endl; BoundingBox boundingBox; volumeFile->getVoxelSpaceBoundingBox(boundingBox); // std::cout << "Bounding Box: " << qPrintable(boundingBox.toString()) << std::endl; boundingBox.limitCoordinateToBoundingBox(bottomLeftCoord); boundingBox.limitCoordinateToBoundingBox(bottomRightCoord); boundingBox.limitCoordinateToBoundingBox(topRightCoord); boundingBox.limitCoordinateToBoundingBox(topLeftCoord); // std::cout << "Limited Bottom Left: " << qPrintable(AString::fromNumbers(bottomLeftCoord, 3, ",")) << std::endl; // std::cout << "Limited Bottom Right: " << qPrintable(AString::fromNumbers(bottomRightCoord, 3, ",")) << std::endl; // std::cout << "Limited Top Right: " << qPrintable(AString::fromNumbers(topRightCoord, 3, ",")) << std::endl; // std::cout << "Limited Top Left: " << qPrintable(AString::fromNumbers(topLeftCoord, 3, ",")) << std::endl; // std::cout << std::endl; /* * Note: Spacing may be negative for some orientations * and positive may be on left or bottom */ float voxelDeltaX, voxelDeltaY, voxelDeltaZ; volumeFile->getVoxelSpacing(voxelDeltaX, voxelDeltaY, voxelDeltaZ); voxelDeltaX = std::fabs(voxelDeltaX); voxelDeltaY = std::fabs(voxelDeltaY); voxelDeltaZ = std::fabs(voxelDeltaZ); if (bottomLeftCoord[0] > topRightCoord[0]) { voxelDeltaX = -voxelDeltaX; } if (bottomLeftCoord[1] > topRightCoord[1]) { voxelDeltaY = -voxelDeltaY; } if (bottomLeftCoord[2] > topRightCoord[2]) { voxelDeltaZ = -voxelDeltaZ; } bool adjustX = false; bool adjustY = false; bool adjustZ = false; switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: CaretAssert(0); break; case VolumeSliceViewPlaneEnum::AXIAL: adjustX = true; adjustY = true; voxelDeltaZ = 0.0; break; case VolumeSliceViewPlaneEnum::CORONAL: adjustX = true; adjustZ = true; voxelDeltaY = 0.0; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: adjustY = true; adjustZ = true; voxelDeltaX = 0.0; break; } /* * Adjust by one voxel to ensure full coverage */ if (adjustX) { bottomLeftCoord[0] -= voxelDeltaX; topRightCoord[0] += voxelDeltaX; } if (adjustY) { bottomLeftCoord[1] -= voxelDeltaY; topRightCoord[1] += voxelDeltaY; } if (adjustZ) { bottomLeftCoord[2] -= voxelDeltaZ; topRightCoord[2] += voxelDeltaZ; } // std::cout << "Adjusted Bottom Left: " << qPrintable(AString::fromNumbers(bottomLeftCoord, 3, ",")) << std::endl; // std::cout << "Adjusted Top Right: " << qPrintable(AString::fromNumbers(topRightCoord, 3, ",")) << std::endl; int64_t bottomLeftIJK[3]; volumeFile->enclosingVoxel(bottomLeftCoord[0], bottomLeftCoord[1], bottomLeftCoord[2], bottomLeftIJK[0], bottomLeftIJK[1], bottomLeftIJK[2]); int64_t topRightIJK[3]; volumeFile->enclosingVoxel(topRightCoord[0], topRightCoord[1], topRightCoord[2], topRightIJK[0], topRightIJK[1], topRightIJK[2]); volumeFile->limitIndicesToValidIndices(bottomLeftIJK[0], bottomLeftIJK[1], bottomLeftIJK[2]); volumeFile->limitIndicesToValidIndices(topRightIJK[0], topRightIJK[1], topRightIJK[2]); // std::cout << "Bottom Left Dimensions: " << qPrintable(AString::fromNumbers(bottomLeftIJK, 3, ",")) << std::endl; // std::cout << "Top Right Dimensions: " << qPrintable(AString::fromNumbers(topRightIJK, 3, ",")) << std::endl; culledFirstVoxelIJKOut[0] = bottomLeftIJK[0]; culledFirstVoxelIJKOut[1] = bottomLeftIJK[1]; culledFirstVoxelIJKOut[2] = bottomLeftIJK[2]; culledLastVoxelIJKOut[0] = topRightIJK[0]; culledLastVoxelIJKOut[1] = topRightIJK[1]; culledLastVoxelIJKOut[2] = topRightIJK[2]; voxelDeltaXYZOut[0] = voxelDeltaX; voxelDeltaXYZOut[1] = voxelDeltaY; voxelDeltaXYZOut[2] = voxelDeltaZ; return true; } /* ======================================================================= */ /** * Constructor * * @param volumeMappableInterface * Volume that contains the data values. * @param mapIndex * Index of selected map. */ BrainOpenGLVolumeObliqueSliceDrawing::VolumeSlice::VolumeSlice(VolumeMappableInterface* volumeMappableInterface, const int32_t mapIndex) { m_volumeMappableInterface = volumeMappableInterface; m_volumeFile = dynamic_cast(m_volumeMappableInterface); m_ciftiMappableDataFile = dynamic_cast(m_volumeMappableInterface); CaretAssert(m_volumeMappableInterface); m_mapIndex = mapIndex; CaretAssert(m_mapIndex >= 0); const int64_t sliceDim = 300; const int64_t numVoxels = sliceDim * sliceDim; m_values.reserve(numVoxels); } /** * Add a value and return its index. * * @param value * Value that is added. * @return * The index for the value. */ int64_t BrainOpenGLVolumeObliqueSliceDrawing::VolumeSlice::addValue(const float value) { const int64_t indx = static_cast(m_values.size()); m_values.push_back(value); return indx; } /** * Return RGBA colors for value using the value's index * returned by addValue(). * * @param indx * Index of the value. * @return * RGBA coloring for value. */ uint8_t* BrainOpenGLVolumeObliqueSliceDrawing::VolumeSlice::getRgbaForValueByIndex(const int64_t indx) { CaretAssertVectorIndex(m_rgba, indx * 4); return &m_rgba[indx*4]; } /** * Allocate colors for the voxel values */ void BrainOpenGLVolumeObliqueSliceDrawing::VolumeSlice::allocateColors() { m_rgba.resize(m_values.size() * 4); } /* ======================================================================= */ /** * Create a voxel for drawing. * * @param center * Center of voxel. * @param leftBottom * Left bottom coordinate of voxel. * @param rightBottom * Right bottom coordinate of voxel. * @param rightTop * Right top coordinate of voxel. * @param leftTop * Left top coordinate of voxel. */ BrainOpenGLVolumeObliqueSliceDrawing::VoxelToDraw::VoxelToDraw(const float center[3], const double leftBottom[3], const double rightBottom[3], const double rightTop[3], const double leftTop[3]) { m_center[0] = center[0]; m_center[1] = center[1]; m_center[2] = center[2]; m_coordinates[0] = leftBottom[0]; m_coordinates[1] = leftBottom[1]; m_coordinates[2] = leftBottom[2]; m_coordinates[3] = rightBottom[0]; m_coordinates[4] = rightBottom[1]; m_coordinates[5] = rightBottom[2]; m_coordinates[6] = rightTop[0]; m_coordinates[7] = rightTop[1]; m_coordinates[8] = rightTop[2]; m_coordinates[9] = leftTop[0]; m_coordinates[10] = leftTop[1]; m_coordinates[11] = leftTop[2]; const int64_t numSlices = 5; m_sliceIndices.reserve(numSlices); m_sliceOffsets.reserve(numSlices); } /** * Get the change in XYZ for the voxel ([top right] minus [bottom left]) * * @param dxyzOut * Change in XYZ from bottom left to top right */ void BrainOpenGLVolumeObliqueSliceDrawing::VoxelToDraw::getDiffXYZ(float dxyzOut[3]) const { dxyzOut[0] = m_coordinates[6] - m_coordinates[0]; dxyzOut[1] = m_coordinates[7] - m_coordinates[1]; dxyzOut[2] = m_coordinates[8] - m_coordinates[2]; } /** * Add a value from a volume slice. * * @param sliceIndex * Index of the slice. * @param sliceOffset * Offset of value in the slice. */ void BrainOpenGLVolumeObliqueSliceDrawing::VoxelToDraw::addVolumeValue(const int64_t sliceIndex, const int64_t sliceOffset) { CaretAssert(sliceIndex >= 0); CaretAssert(sliceOffset >= 0); m_sliceIndices.push_back(sliceIndex); m_sliceOffsets.push_back(sliceOffset); } workbench-1.1.1/src/Brain/BrainOpenGLVolumeObliqueSliceDrawing.h000066400000000000000000000371001255417355300246070ustar00rootroot00000000000000#ifndef __BRAIN_OPEN_GL_VOLUME_OBLIQUE_SLICE_DRAWING_H__ #define __BRAIN_OPEN_GL_VOLUME_OBLIQUE_SLICE_DRAWING_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "BrainOpenGLFixedPipeline.h" #include "CaretObject.h" #include "DisplayGroupEnum.h" #include "VolumeSliceProjectionTypeEnum.h" #include "VolumeSliceDrawingTypeEnum.h" #include "VolumeSliceViewPlaneEnum.h" namespace caret { class Brain; class BrowserTabContent; class CiftiMappableDataFile; class Matrix4x4; class ModelVolume; class ModelWholeBrain; class PaletteFile; class Plane; class VolumeMappableInterface; class BrainOpenGLVolumeObliqueSliceDrawing : public CaretObject { public: BrainOpenGLVolumeObliqueSliceDrawing(); virtual ~BrainOpenGLVolumeObliqueSliceDrawing(); void draw(BrainOpenGLFixedPipeline* fixedPipelineDrawing, BrowserTabContent* browserTabContent, std::vector& volumeDrawInfo, const VolumeSliceDrawingTypeEnum::Enum sliceDrawingType, const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const int32_t viewport[4]); // ADD_NEW_METHODS_HERE private: /** * Holds values in the slice for a volume so that they * can be colored all at once which is more efficient than * colors singles voxels many times */ class VolumeSlice{ public: /** * Constructor * * @param volumeMappableInterface * Volume that contains the data values. */ VolumeSlice(VolumeMappableInterface* volumeMappableInterface, const int32_t mapIndex); /** * Add a value and return its index. * * @param value * Value that is added. * @return * The index for the value. */ int64_t addValue(const float value); /** * Return RGBA colors for value using the value's index * returned by addValue(). * * @param indx * Index of the value. * @return * RGBA coloring for value. */ uint8_t* getRgbaForValueByIndex(const int64_t indx); /** * Allocate colors for the voxel values */ void allocateColors(); /** * Volume containing the values */ VolumeMappableInterface* m_volumeMappableInterface; /** * If not NULL, it is a VolumeFile */ VolumeFile* m_volumeFile; /** * If not NULL, it is a Cifti Mappable Data File */ CiftiMappableDataFile* m_ciftiMappableDataFile; /** * Map index */ int32_t m_mapIndex; /** * The voxel values */ std::vector m_values; /** * Coloring corresponding to the values (4 components per voxel) */ std::vector m_rgba; }; /** * For each voxel, contains offsets to each layer */ class VoxelToDraw { public: VoxelToDraw(const float center[3], const double leftBottom[3], const double rightBottom[3], const double rightTop[3], const double leftTop[3]); void getDiffXYZ(float dxyzOut[3]) const; void addVolumeValue(const int64_t sliceIndex, const int64_t sliceOffset); /** * Center of voxel. */ float m_center[3]; /** * Corners of voxel */ float m_coordinates[12]; /* * Index of volume in VoxelsInSliceForVolume */ std::vector m_sliceIndices; /** * Offset in values in VoxelsInSliceForVolume */ std::vector m_sliceOffsets; }; BrainOpenGLVolumeObliqueSliceDrawing(const BrainOpenGLVolumeObliqueSliceDrawing&); BrainOpenGLVolumeObliqueSliceDrawing& operator=(const BrainOpenGLVolumeObliqueSliceDrawing&); void drawVolumeSlicesForAllStructuresView(const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const int32_t viewport[4]); void drawVolumeSliceViewPlane(const VolumeSliceDrawingTypeEnum::Enum sliceDrawingType, const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const int32_t viewport[4]); void drawVolumeSliceViewType(const VolumeSliceDrawingTypeEnum::Enum sliceDrawingType, const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const int32_t viewport[4]); void drawVolumeSliceViewTypeMontage(const VolumeSliceDrawingTypeEnum::Enum sliceDrawingType, const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const int32_t viewport[4]); void drawVolumeSliceViewProjection(const VolumeSliceDrawingTypeEnum::Enum sliceDrawingType, const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const float sliceCoordinates[3], const int32_t viewport[4]); void drawObliqueSlice(const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, Matrix4x4& transformationMatrix, const Plane& plane); void drawOrthogonalSlice(const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const float sliceCoordinates[3], const Plane& plane); void drawOrthogonalSliceWithCulling(const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const float sliceCoordinates[3], const Plane& plane); void createSlicePlaneEquation(const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const float sliceCoordinates[3], Plane& planeOut); void drawAxesCrosshairsOrthoAndOblique(const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const float sliceCoordinates[3], const bool drawCrosshairsFlag, const bool drawCrosshairLabelsFlag); void setVolumeSliceViewingAndModelingTransformations(const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const Plane& plane, const float sliceCoordinates[3]); void getAxesColor(const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, float rgbaOut[4]) const; void drawLayers(const VolumeSliceDrawingTypeEnum::Enum sliceDrawingType, const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const Plane& slicePlane, const float sliceCoordinates[3]); void drawSurfaceOutline(const Plane& plane); void drawVolumeSliceFoci(const Plane& plane); void drawAxesCrosshairs(const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const VolumeSliceDrawingTypeEnum::Enum sliceDrawingType, const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const float sliceCoordinates[3]); bool getMinMaxVoxelSpacing(const VolumeMappableInterface* volume, float& minSpacingOut, float& maxSpacingOut) const; void drawSquare(const float size); void drawOrientationAxes(const int viewport[4]); void setOrthographicProjection(const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const int viewport[4]); void drawOrthogonalSliceVoxels(const float sliceNormalVector[3], const float coordinate[3], const float rowStep[3], const float columnStep[3], const int64_t numberOfColumns, const int64_t numberOfRows, const std::vector& sliceRGBA, const int64_t validVoxelCount, const VolumeMappableInterface* volumeInterface, const int32_t volumeIndex, const int32_t mapIndex, const uint8_t sliceOpacity); void drawOrthogonalSliceVoxelsSingleQuads(const float sliceNormalVector[3], const float coordinate[3], const float rowStep[3], const float columnStep[3], const int64_t numberOfColumns, const int64_t numberOfRows, const std::vector& sliceRGBA, const VolumeMappableInterface* volumeInterface, const int32_t volumeIndex, const int32_t mapIndex, const uint8_t sliceOpacity); void drawOrthogonalSliceVoxelsQuadIndicesAndStrips(const float sliceNormalVector[3], const float coordinate[3], const float rowStep[3], const float columnStep[3], const int64_t numberOfColumns, const int64_t numberOfRows, const std::vector& sliceRGBA, const VolumeMappableInterface* volumeInterface, const int32_t volumeIndex, const int32_t mapIndex, const uint8_t sliceOpacity); bool getVoxelCoordinateBoundsAndSpacing(float boundsOut[6], float spacingOut[3]); void createObliqueTransformationMatrix(const float sliceCoordinates[3], Matrix4x4& obliqueTransformationMatrixOut); void addVoxelToIdentification(const int32_t volumeIndex, const int32_t mapIndex, const int32_t voxelI, const int32_t voxelJ, const int32_t voxelK, const float voxelDiffXYZ[3], uint8_t rgbaForColorIdentificationOut[4]); bool getVolumeDrawingViewDependentCulling(const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const float selectedSliceCoordinate, const VolumeMappableInterface* volumeFile, int64_t culledFirstVoxelIJKOut[3], int64_t culledLastVoxelIJKOut[3], float voxelDeltaXYZOut[3]); void processIdentification(); void resetIdentification(); ModelVolume* m_modelVolume; ModelWholeBrain* m_modelWholeBrain; VolumeMappableInterface* m_underlayVolume; Brain* m_brain; std::vector > m_ciftiMappableFileData; BrainOpenGLFixedPipeline* m_fixedPipelineDrawing; std::vector m_volumeDrawInfo; BrowserTabContent* m_browserTabContent; PaletteFile* m_paletteFile; DisplayGroupEnum::Enum m_displayGroup; int32_t m_tabIndex; double m_lookAtCenter[3]; double m_viewingMatrix[16]; double m_orthographicBounds[6]; std::vector m_identificationIndices; bool m_identificationModeFlag; static const int32_t IDENTIFICATION_INDICES_PER_VOXEL; // ADD_NEW_MEMBERS_HERE }; #ifdef __BRAIN_OPEN_GL_VOLUME_OBLIQUE_SLICE_DRAWING_DECLARE__ const int32_t BrainOpenGLVolumeObliqueSliceDrawing::IDENTIFICATION_INDICES_PER_VOXEL = 8; #endif // __BRAIN_OPEN_GL_VOLUME_OBLIQUE_SLICE_DRAWING_DECLARE__ } // namespace #endif //__BRAIN_OPEN_GL_VOLUME_OBLIQUE_SLICE_DRAWING_H__ workbench-1.1.1/src/Brain/BrainOpenGLVolumeSliceDrawing.cxx000066400000000000000000007447601255417355300236620ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __BRAIN_OPEN_GL_VOLUME_SLICE_DRAWING_DECLARE__ #include "BrainOpenGLVolumeSliceDrawing.h" #undef __BRAIN_OPEN_GL_VOLUME_SLICE_DRAWING_DECLARE__ #include "BoundingBox.h" #include "Brain.h" #include "BrainOpenGLPrimitiveDrawing.h" #include "BrainordinateRegionOfInterest.h" #include "BrowserTabContent.h" #include "CaretAssert.h" #include "CaretLogger.h" #include "CaretOpenGLInclude.h" #include "CaretPreferences.h" #include "CiftiMappableDataFile.h" #include "DeveloperFlagsEnum.h" #include "DisplayPropertiesFoci.h" #include "DisplayPropertiesLabels.h" #include "ElapsedTimer.h" #include "FociFile.h" #include "Focus.h" #include "GiftiLabel.h" #include "GiftiLabelTable.h" #include "GroupAndNameHierarchyModel.h" #include "IdentificationManager.h" #include "IdentificationWithColor.h" #include "IdentifiedItemVoxel.h" #include "LabelDrawingProperties.h" #include "MathFunctions.h" #include "Matrix4x4.h" #include "ModelVolume.h" #include "ModelWholeBrain.h" #include "NodeAndVoxelColoring.h" #include "SelectionItemFocusVolume.h" #include "SelectionItemVoxel.h" #include "SelectionItemVoxelEditing.h" #include "SelectionItemVoxelIdentificationSymbol.h" #include "SelectionManager.h" #include "SessionManager.h" #include "Surface.h" #include "VolumeFile.h" #include "VolumeSurfaceOutlineColorOrTabModel.h" #include "VolumeSurfaceOutlineModel.h" #include "VolumeSurfaceOutlineSetModel.h" using namespace caret; static const bool debugFlag = false; /** * \class caret::BrainOpenGLVolumeSliceDrawing * \brief Draws volume slices using OpenGL * \ingroup Brain */ /** * Constructor. */ BrainOpenGLVolumeSliceDrawing::BrainOpenGLVolumeSliceDrawing() : CaretObject() { } /** * Destructor. */ BrainOpenGLVolumeSliceDrawing::~BrainOpenGLVolumeSliceDrawing() { } /** * Draw Volume Slices or slices for ALL Stuctures View. * * @param fixedPipelineDrawing * The OpenGL drawing. * @param browserTabContent * Content of browser tab that is to be drawn. * @param volumeDrawInfo * Info on each volume layers for drawing. * @param sliceDrawingType * Type of slice drawing (montage, single) * @param sliceProjectionType * Type of projection for the slice drawing (oblique, orthogonal) * @param viewport * The viewport (region of graphics area) for drawing slices. */ void BrainOpenGLVolumeSliceDrawing::draw(BrainOpenGLFixedPipeline* fixedPipelineDrawing, BrowserTabContent* browserTabContent, std::vector& volumeDrawInfo, const VolumeSliceDrawingTypeEnum::Enum sliceDrawingType, const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const int32_t viewport[4]) { if (volumeDrawInfo.empty()) { return; } CaretAssert(fixedPipelineDrawing); CaretAssert(browserTabContent); m_browserTabContent = browserTabContent; /* * Initialize class members which help reduce the number of * parameters that are passed to methods. */ m_brain = NULL; m_modelVolume = NULL; m_modelWholeBrain = NULL; if (m_browserTabContent->getDisplayedVolumeModel() != NULL) { m_modelVolume = m_browserTabContent->getDisplayedVolumeModel(); m_brain = m_modelVolume->getBrain(); } else if (m_browserTabContent->getDisplayedWholeBrainModel() != NULL) { m_modelWholeBrain = m_browserTabContent->getDisplayedWholeBrainModel(); m_brain = m_modelWholeBrain->getBrain(); } else { CaretAssertMessage(0, "Invalid model for volume slice drawing."); } CaretAssert(m_brain); m_fixedPipelineDrawing = fixedPipelineDrawing; m_volumeDrawInfo = volumeDrawInfo; if (m_volumeDrawInfo.empty()) { return; } m_underlayVolume = m_volumeDrawInfo[0].volumeFile; m_paletteFile = m_browserTabContent->getModelForDisplay()->getBrain()->getPaletteFile(); CaretAssert(m_paletteFile); const DisplayPropertiesLabels* dsl = m_brain->getDisplayPropertiesLabels(); m_displayGroup = dsl->getDisplayGroupForTab(m_fixedPipelineDrawing->windowTabIndex); m_tabIndex = m_browserTabContent->getTabNumber(); /* * Cifti files are slow at getting individual voxels since they * provide no access to individual voxels. The reason is that * the data may be on a server (Dense data) and accessing a single * voxel would require requesting the entire map. So, for * each Cifti file, get the enter map. This also, eliminate multiple * requests for the same map when drawing an ALL view. */ const int32_t numVolumes = static_cast(m_volumeDrawInfo.size()); for (int32_t i = 0; i < numVolumes; i++) { std::vector ciftiMapData; m_ciftiMappableFileData.push_back(ciftiMapData); const CiftiMappableDataFile* ciftiMapFile = dynamic_cast(m_volumeDrawInfo[i].volumeFile); if (ciftiMapFile != NULL) { ciftiMapFile->getMapData(m_volumeDrawInfo[i].mapIndex, m_ciftiMappableFileData[i]); } } /** END SETUP OF MEMBERS IN PARENT CLASS BrainOpenGLVolumeSliceDrawing */ if (browserTabContent->getDisplayedVolumeModel() != NULL) { drawVolumeSliceViewPlane(sliceDrawingType, sliceProjectionType, browserTabContent->getSliceViewPlane(), viewport); } else if (browserTabContent->getDisplayedWholeBrainModel() != NULL) { drawVolumeSlicesForAllStructuresView(sliceProjectionType, viewport); } } /** * Draw volume view slices for the given view plane. * * @param sliceDrawingType * Type of slice drawing (montage, single) * @param sliceProjectionType * Type of projection for the slice drawing (oblique, orthogonal) * @param sliceViewPlane * The plane for slice drawing. * @param viewport * The viewport (region of graphics area) for drawing slices. */ void BrainOpenGLVolumeSliceDrawing::drawVolumeSliceViewPlane(const VolumeSliceDrawingTypeEnum::Enum sliceDrawingType, const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const int32_t viewport[4]) { switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: { const int32_t gap = 2; const int32_t vpHalfX = viewport[2] / 2; const int32_t vpHalfY = viewport[3] / 2; /* * Draw parasagittal slice */ const int32_t paraVP[4] = { viewport[0], viewport[1] + vpHalfY + gap, vpHalfX - gap, vpHalfY - gap }; glPushMatrix(); drawVolumeSliceViewType(sliceDrawingType, sliceProjectionType, VolumeSliceViewPlaneEnum::PARASAGITTAL, paraVP); glPopMatrix(); /* * Draw coronal slice */ const int32_t coronalVP[4] = { viewport[0] + vpHalfX + gap, viewport[1] + vpHalfY + gap, vpHalfX - gap, vpHalfY - gap }; glPushMatrix(); drawVolumeSliceViewType(sliceDrawingType, sliceProjectionType, VolumeSliceViewPlaneEnum::CORONAL, coronalVP); glPopMatrix(); /* * Draw axial slice */ const int32_t axialVP[4] = { viewport[0] + vpHalfX + gap, viewport[1], vpHalfX - gap, vpHalfY - gap }; glPushMatrix(); drawVolumeSliceViewType(sliceDrawingType, sliceProjectionType, VolumeSliceViewPlaneEnum::AXIAL, axialVP); glPopMatrix(); /* * 4th quadrant is used for axis showing orientation */ const int32_t allVP[4] = { viewport[0], viewport[1], vpHalfX - gap, vpHalfY - gap }; switch (sliceProjectionType) { case VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_OBLIQUE: drawOrientationAxes(allVP); break; case VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_ORTHOGONAL: break; } } break; case VolumeSliceViewPlaneEnum::AXIAL: case VolumeSliceViewPlaneEnum::CORONAL: case VolumeSliceViewPlaneEnum::PARASAGITTAL: drawVolumeSliceViewType(sliceDrawingType, sliceProjectionType, sliceViewPlane, viewport); break; } } /** * Draw slices for the all structures view. * * @param sliceProjectionType * Type of projection for the slice drawing (oblique, orthogonal) * @param viewport * The viewport. */ void BrainOpenGLVolumeSliceDrawing::drawVolumeSlicesForAllStructuresView(const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const int32_t viewport[4]) { m_orthographicBounds[0] = m_fixedPipelineDrawing->orthographicLeft; m_orthographicBounds[1] = m_fixedPipelineDrawing->orthographicRight; m_orthographicBounds[2] = m_fixedPipelineDrawing->orthographicBottom; m_orthographicBounds[3] = m_fixedPipelineDrawing->orthographicTop; m_orthographicBounds[4] = m_fixedPipelineDrawing->orthographicNear; m_orthographicBounds[5] = m_fixedPipelineDrawing->orthographicFar; /* * Enlarge the region */ { const float left = m_fixedPipelineDrawing->orthographicLeft; const float right = m_fixedPipelineDrawing->orthographicRight; const float bottom = m_fixedPipelineDrawing->orthographicBottom; const float top = m_fixedPipelineDrawing->orthographicTop; const float scale = 2.0; const float centerX = (left + right) / 2.0; const float dx = (right - left) / 2.0; const float newLeft = centerX - (dx * scale); const float newRight = centerX + (dx * scale); const float centerY = (bottom + top) / 2.0; const float dy = (top - bottom) / 2.0; const float newBottom = centerY - (dy * scale); const float newTop = centerY + (dy * scale); m_orthographicBounds[0] = newLeft; m_orthographicBounds[1] = newRight; m_orthographicBounds[2] = newBottom; m_orthographicBounds[3] = newTop; } const float sliceCoordinates[3] = { m_browserTabContent->getSliceCoordinateParasagittal(), m_browserTabContent->getSliceCoordinateCoronal(), m_browserTabContent->getSliceCoordinateAxial() }; if (m_browserTabContent->isSliceAxialEnabled()) { glPushMatrix(); drawVolumeSliceViewProjection(VolumeSliceDrawingTypeEnum::VOLUME_SLICE_DRAW_SINGLE, sliceProjectionType, VolumeSliceViewPlaneEnum::AXIAL, sliceCoordinates, viewport); glPopMatrix(); } if (m_browserTabContent->isSliceCoronalEnabled()) { glPushMatrix(); drawVolumeSliceViewProjection(VolumeSliceDrawingTypeEnum::VOLUME_SLICE_DRAW_SINGLE, sliceProjectionType, VolumeSliceViewPlaneEnum::CORONAL, sliceCoordinates, viewport); glPopMatrix(); } if (m_browserTabContent->isSliceParasagittalEnabled()) { glPushMatrix(); drawVolumeSliceViewProjection(VolumeSliceDrawingTypeEnum::VOLUME_SLICE_DRAW_SINGLE, sliceProjectionType, VolumeSliceViewPlaneEnum::PARASAGITTAL, sliceCoordinates, viewport); glPopMatrix(); } } /** * Draw single or montage volume view slices. * * @param sliceDrawingType * Type of slice drawing (montage, single) * @param sliceProjectionType * Type of projection for the slice drawing (oblique, orthogonal) * @param sliceViewPlane * The plane for slice drawing. * @param viewport * The viewport (region of graphics area) for drawing slices. */ void BrainOpenGLVolumeSliceDrawing::drawVolumeSliceViewType(const VolumeSliceDrawingTypeEnum::Enum sliceDrawingType, const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const int32_t viewport[4]) { switch (sliceDrawingType) { case VolumeSliceDrawingTypeEnum::VOLUME_SLICE_DRAW_MONTAGE: drawVolumeSliceViewTypeMontage(sliceDrawingType, sliceProjectionType, sliceViewPlane, viewport); break; case VolumeSliceDrawingTypeEnum::VOLUME_SLICE_DRAW_SINGLE: { const float sliceCoordinates[3] = { m_browserTabContent->getSliceCoordinateParasagittal(), m_browserTabContent->getSliceCoordinateCoronal(), m_browserTabContent->getSliceCoordinateAxial() }; drawVolumeSliceViewProjection(sliceDrawingType, sliceProjectionType, sliceViewPlane, sliceCoordinates, viewport); } break; } } /** * Draw montage slices. * * @param sliceDrawingType * Type of slice drawing (montage, single) * @param sliceProjectionType * Type of projection for the slice drawing (oblique, orthogonal) * @param sliceViewPlane * The plane for slice drawing. * @param viewport * The viewport (region of graphics area) for drawing slices. */ void BrainOpenGLVolumeSliceDrawing::drawVolumeSliceViewTypeMontage(const VolumeSliceDrawingTypeEnum::Enum sliceDrawingType, const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const int32_t viewport[4]) { const int32_t numRows = m_browserTabContent->getMontageNumberOfRows(); CaretAssert(numRows > 0); const int32_t numCols = m_browserTabContent->getMontageNumberOfColumns(); CaretAssert(numCols > 0); const CaretPreferences* caretPreferences = SessionManager::get()->getCaretPreferences(); const int32_t montageMargin = caretPreferences->getVolumeMontageGap(); const int32_t montageCoordPrecision = caretPreferences->getVolumeMontageCoordinatePrecision(); const int32_t totalGapX = montageMargin * (numCols - 1); const int32_t vpSizeX = (viewport[2] - totalGapX) / numCols; const int32_t totalGapY = montageMargin * (numRows - 1); const int32_t vpSizeY = (viewport[3] - totalGapY) / numRows; /* * Voxel sizes for underlay volume */ float originX, originY, originZ; float x1, y1, z1; m_underlayVolume->indexToSpace(0, 0, 0, originX, originY, originZ); m_underlayVolume->indexToSpace(1, 1, 1, x1, y1, z1); float sliceThickness = 0.0; float sliceOrigin = 0.0; AString axisLetter = ""; float sliceCoordinates[3] = { m_browserTabContent->getSliceCoordinateParasagittal(), m_browserTabContent->getSliceCoordinateCoronal(), m_browserTabContent->getSliceCoordinateAxial() }; int32_t sliceIndex = -1; int32_t maximumSliceIndex = -1; int64_t dimI, dimJ, dimK, numMaps, numComponents; m_underlayVolume->getDimensions(dimI, dimJ, dimK, numMaps, numComponents); const int32_t sliceStep = m_browserTabContent->getMontageSliceSpacing(); //const VolumeSliceViewPlaneEnum::Enum slicePlane = m_browserTabContent->getSliceViewPlane(); switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: sliceIndex = -1; break; case VolumeSliceViewPlaneEnum::AXIAL: sliceIndex = m_browserTabContent->getSliceIndexAxial(m_underlayVolume); maximumSliceIndex = dimK; sliceThickness = z1 - originZ; sliceOrigin = originZ; axisLetter = "Z"; break; case VolumeSliceViewPlaneEnum::CORONAL: sliceIndex = m_browserTabContent->getSliceIndexCoronal(m_underlayVolume); maximumSliceIndex = dimJ; sliceThickness = y1 - originY; sliceOrigin = originY; axisLetter = "Y"; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: sliceIndex = m_browserTabContent->getSliceIndexParasagittal(m_underlayVolume); maximumSliceIndex = dimI; sliceThickness = x1 - originX; sliceOrigin = originX; axisLetter = "X"; break; } /* * Foreground color for slice coordinate text */ const CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); uint8_t foregroundRGB[3]; prefs->getColorForegroundVolumeView(foregroundRGB); const bool showCoordinates = prefs->isVolumeMontageAxesCoordinatesDisplayed(); /* * Determine a slice offset to selected slices is in * the center of the montage */ const int32_t numSlicesViewed = (numCols * numRows); const int32_t sliceOffset = ((numSlicesViewed / 2) * sliceStep); sliceIndex += sliceOffset; /* * Find first valid slice for montage */ while (sliceIndex >= 0) { if (sliceIndex < maximumSliceIndex) { break; } sliceIndex -= sliceStep; } if (sliceIndex >= 0) { for (int32_t i = 0; i < numRows; i++) { for (int32_t j = 0; j < numCols; j++) { if ((sliceIndex >= 0) && (sliceIndex < maximumSliceIndex)) { const int32_t vpX = (j * (vpSizeX + montageMargin)); const int32_t vpY = ((numRows - i - 1) * (vpSizeY + montageMargin)); int32_t vp[4] = { viewport[0] + vpX, viewport[1] + vpY, vpSizeX, vpSizeY }; if ((vp[2] <= 0) || (vp[3] <= 0)) { continue; } const float sliceCoord = (sliceOrigin + sliceThickness * sliceIndex); switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: break; case VolumeSliceViewPlaneEnum::AXIAL: sliceCoordinates[2] = sliceCoord; break; case VolumeSliceViewPlaneEnum::CORONAL: sliceCoordinates[1] = sliceCoord; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: sliceCoordinates[0] = sliceCoord; break; } drawVolumeSliceViewProjection(sliceDrawingType, sliceProjectionType, sliceViewPlane, sliceCoordinates, vp); if (showCoordinates) { const AString coordText = (axisLetter + "=" + AString::number(sliceCoord, 'f', montageCoordPrecision) + "mm"); glColor3ubv(foregroundRGB); m_fixedPipelineDrawing->drawTextWindowCoords((vpSizeX - 5), 5, coordText, BrainOpenGLTextRenderInterface::X_RIGHT, BrainOpenGLTextRenderInterface::Y_BOTTOM, BrainOpenGLTextRenderInterface::NORMAL, 12); } } sliceIndex -= sliceStep; } } } /* * Draw the axes labels for the montage view */ glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); if (prefs->isVolumeAxesLabelsDisplayed()) { drawAxesCrosshairsOrthoAndOblique(sliceProjectionType, sliceViewPlane, sliceCoordinates, false, true); } } /** * Draw a slice for either projection mode (oblique, orthogonal) * * @param sliceDrawingType * Type of slice drawing (montage, single) * @param sliceProjectionType * Type of projection for the slice drawing (oblique, orthogonal) * @param sliceViewPlane * The plane for slice drawing. * @param sliceCoordinates * Coordinates of the selected slice. * @param viewport * The viewport (region of graphics area) for drawing slices. */ void BrainOpenGLVolumeSliceDrawing::drawVolumeSliceViewProjection(const VolumeSliceDrawingTypeEnum::Enum sliceDrawingType, const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const float sliceCoordinates[3], const int32_t viewport[4]) { bool twoDimSliceViewFlag = false; if (m_modelVolume != NULL) { twoDimSliceViewFlag = true; } else if (m_modelWholeBrain != NULL) { /* nothing */ } else { CaretAssertMessage(0, "Invalid model type."); } if (twoDimSliceViewFlag) { glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); /* * Set the orthographic projection to fit the slice axis */ setOrthographicProjection(sliceViewPlane, viewport); } /* * Create the plane equation for the slice */ Plane slicePlane; createSlicePlaneEquation(sliceProjectionType, sliceViewPlane, sliceCoordinates, slicePlane); CaretAssert(slicePlane.isValidPlane()); if (slicePlane.isValidPlane() == false) { return; } if (twoDimSliceViewFlag) { /* * Set the viewing transformation (camera position) */ setVolumeSliceViewingAndModelingTransformations(sliceProjectionType, sliceViewPlane, slicePlane); } SelectionItemVoxel* voxelID = m_brain->getSelectionManager()->getVoxelIdentification(); SelectionItemVoxelEditing* voxelEditingID = m_brain->getSelectionManager()->getVoxelEditingIdentification(); m_fixedPipelineDrawing->applyClippingPlanes(BrainOpenGLFixedPipeline::CLIPPING_DATA_TYPE_VOLUME, StructureEnum::ALL); /* * Check for a 'selection' type mode */ m_identificationModeFlag = false; switch (m_fixedPipelineDrawing->mode) { case BrainOpenGLFixedPipeline::MODE_DRAWING: break; case BrainOpenGLFixedPipeline::MODE_IDENTIFICATION: if (voxelID->isEnabledForSelection() || voxelEditingID->isEnabledForSelection()) { m_identificationModeFlag = true; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } else { return; } break; case BrainOpenGLFixedPipeline::MODE_PROJECTION: return; break; } resetIdentification(); /* * Disable culling so that both sides of the triangles/quads are drawn. */ GLboolean cullFaceOn = glIsEnabled(GL_CULL_FACE); glDisable(GL_CULL_FACE); switch (sliceProjectionType) { case VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_ORTHOGONAL: if (m_modelVolume != NULL) { const bool cullingFlag = true; if (cullingFlag) { drawOrthogonalSliceWithCulling(sliceViewPlane, sliceCoordinates, slicePlane); } else { drawOrthogonalSlice(sliceViewPlane, sliceCoordinates, slicePlane); } } else if (m_modelWholeBrain != NULL) { drawOrthogonalSlice(sliceViewPlane, sliceCoordinates, slicePlane); } break; case VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_OBLIQUE: { /* * Create the oblique slice transformation matrix */ Matrix4x4 obliqueTransformationMatrix; createObliqueTransformationMatrix(sliceCoordinates, obliqueTransformationMatrix); drawObliqueSlice(sliceViewPlane, obliqueTransformationMatrix, slicePlane); } break; } /* * Process selection */ if (m_identificationModeFlag) { processIdentification(); } drawIdentificationSymbols(slicePlane); if ( ! m_identificationModeFlag) { if (slicePlane.isValidPlane()) { drawLayers(sliceDrawingType, sliceProjectionType, sliceViewPlane, slicePlane, sliceCoordinates); } } m_fixedPipelineDrawing->disableClippingPlanes(); if (cullFaceOn) { glEnable(GL_CULL_FACE); } } /** * Draw an oblique slice. * * @param sliceViewPlane * The plane for slice drawing. * @param transformationMatrix * The for oblique viewing. * @param plane * Plane equation for the selected slice. */ void BrainOpenGLVolumeSliceDrawing::drawObliqueSlice(const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, Matrix4x4& transformationMatrix, const Plane& plane) { /* * When performing voxel identification for editing voxels, * we need to draw EVERY voxel since the user may click * regions where the voxels are "off". */ float voxelEditingValue = 1.0; VolumeFile* voxelEditingVolumeFile = NULL; bool volumeEditingDrawAllVoxelsFlag = false; if (m_identificationModeFlag) { SelectionItemVoxelEditing* voxelEditID = m_brain->getSelectionManager()->getVoxelEditingIdentification(); if (voxelEditID->isEnabledForSelection()) { voxelEditingVolumeFile = voxelEditID->getVolumeFileForEditing(); if (voxelEditingVolumeFile != NULL) { volumeEditingDrawAllVoxelsFlag = true; if (voxelEditingVolumeFile->isMappedWithLabelTable()) { if (voxelEditingVolumeFile->getNumberOfMaps() > 0) { voxelEditingValue = voxelEditingVolumeFile->getMapLabelTable(0)->getUnassignedLabelKey(); } } } // const VolumeFile* vf = dynamic_cast(volumeInterface); // if (vf == voxelEditID->getVolumeFileForEditing()) { // volumeEditingDrawAllVoxelsFlag = true; // } } } const bool obliqueSliceModeThreeDimFlag = false; float m[16]; glGetFloatv(GL_MODELVIEW_MATRIX, m); Matrix4x4 tm; tm.setMatrixFromOpenGL(m); // CaretLogFine("Oblique drawing matrix for slice: " // + VolumeSliceViewPlaneEnum::toGuiName(sliceViewPlane) // + tm.toFormattedString(" ")); const int32_t numVolumes = static_cast(m_volumeDrawInfo.size()); /* * Get the maximum bounds of the voxels from all slices * and the smallest voxel spacing */ float voxelBounds[6]; float voxelSpacing[3]; if (false == getVoxelCoordinateBoundsAndSpacing(voxelBounds, voxelSpacing)) { return; } float voxelSize = std::min(voxelSpacing[0], std::min(voxelSpacing[1], voxelSpacing[2])); /* * Use a larger voxel size for the 3D view in volume slice viewing * since it draws all three slices and this takes time */ if (obliqueSliceModeThreeDimFlag) { voxelSize *= 3.0; } /* * Look at point is in center of volume */ float translation[3]; m_browserTabContent->getTranslation(translation); float viewOffsetX = 0.0; float viewOffsetY = 0.0; switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: CaretAssert(0); break; case VolumeSliceViewPlaneEnum::AXIAL: viewOffsetX = (m_lookAtCenter[0] + translation[0]); viewOffsetY = (m_lookAtCenter[1] + translation[1]); break; case VolumeSliceViewPlaneEnum::CORONAL: viewOffsetX = (m_lookAtCenter[0] + translation[0]); viewOffsetY = (m_lookAtCenter[2] + translation[2]); break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: viewOffsetX = (m_lookAtCenter[1] + translation[1]); viewOffsetY = (m_lookAtCenter[2] + translation[2]); break; } float minScreenX = m_orthographicBounds[0] - viewOffsetX; float maxScreenX = m_orthographicBounds[1] - viewOffsetX; float minScreenY = m_orthographicBounds[2] - viewOffsetY; float maxScreenY = m_orthographicBounds[3] - viewOffsetY; /* * Get origin voxel IJK */ const float zeroXYZ[3] = { 0.0, 0.0, 0.0 }; int64_t originIJK[3]; m_volumeDrawInfo[0].volumeFile->enclosingVoxel(zeroXYZ[0], zeroXYZ[1], zeroXYZ[2], originIJK[0], originIJK[1], originIJK[2]); /* * Get XYZ center of origin Voxel */ float originVoxelXYZ[3]; m_volumeDrawInfo[0].volumeFile->indexToSpace(originIJK, originVoxelXYZ); float actualOrigin[3]; m_volumeDrawInfo[0].volumeFile->indexToSpace(originIJK, actualOrigin); float screenOffsetX = 0.0; float screenOffsetY = 0.0; float originOffsetX = 0.0; float originOffsetY = 0.0; switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: CaretAssert(0); break; case VolumeSliceViewPlaneEnum::AXIAL: screenOffsetX = m_lookAtCenter[0]; screenOffsetY = m_lookAtCenter[1]; originOffsetX = actualOrigin[0]; originOffsetY = actualOrigin[1]; break; case VolumeSliceViewPlaneEnum::CORONAL: screenOffsetX = m_lookAtCenter[0]; screenOffsetY = m_lookAtCenter[2]; originOffsetX = actualOrigin[0]; originOffsetY = actualOrigin[2]; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: screenOffsetX = m_lookAtCenter[1]; screenOffsetY = m_lookAtCenter[2]; originOffsetX = actualOrigin[1]; originOffsetY = actualOrigin[2]; break; } const int32_t alignVoxelsFlag = 1; if (alignVoxelsFlag == 1) { /* * Adjust for when selected slices are not at the origin */ const float xOffset = MathFunctions::remainder(screenOffsetX, voxelSize); const float yOffset = MathFunctions::remainder(screenOffsetY, voxelSize); originOffsetX -= xOffset; originOffsetY -= yOffset; const int64_t numVoxelsToLeft = static_cast(MathFunctions::round(minScreenX + originOffsetX) / voxelSize); const int64_t numVoxelsToRight = static_cast(MathFunctions::round(maxScreenX + originOffsetX) / voxelSize); const int64_t numVoxelsToBottom = static_cast(MathFunctions::round(minScreenY + originOffsetY) / voxelSize); const int64_t numVoxelsToTop = static_cast(MathFunctions::round(maxScreenY + originOffsetY)/ voxelSize); const float halfVoxel = voxelSize / 2.0; const float firstVoxelCenterX = (numVoxelsToLeft * voxelSize) + originOffsetX; const float lastVoxelCenterX = (numVoxelsToRight * voxelSize) + originOffsetX; const float firstVoxelCenterY = (numVoxelsToBottom * voxelSize) + originOffsetY; const float lastVoxelCenterY = (numVoxelsToTop * voxelSize) + originOffsetY; float newMinScreenX = firstVoxelCenterX - halfVoxel; float newMaxScreenX = lastVoxelCenterX + halfVoxel; float newMinScreenY = firstVoxelCenterY - halfVoxel; float newMaxScreenY = lastVoxelCenterY + halfVoxel; if (debugFlag) { const AString msg2 = ("Origin Voxel Coordinate: (" + AString::fromNumbers(actualOrigin, 3, ",") + "\n Oblique Screen X: (" + AString::number(minScreenX) + "," + AString::number(maxScreenX) + ") Y: (" + AString::number(minScreenY) + "," + AString::number(maxScreenY) + ")\nNew X: (" + AString::number(newMinScreenX) + "," + AString::number(newMaxScreenX) + ") Y: (" + AString::number(newMinScreenY) + "," + AString::number(newMaxScreenY) + ") Diff: (" + AString::number((newMaxScreenX - newMinScreenX) / voxelSize) + "," + AString::number((newMaxScreenY - newMinScreenY) / voxelSize) + ")"); std::cout << qPrintable(msg2) << std::endl; } minScreenX = newMinScreenX; maxScreenX = newMaxScreenX; minScreenY = newMinScreenY; maxScreenY = newMaxScreenY; } if (alignVoxelsFlag == 2) { // CaretLogFine("Oblique Screen X: (" // + AString::number(minScreenX) // + "," // + AString::number(maxScreenX) // + ") Y: (" // + AString::number(minScreenY) + "," // + AString::number(maxScreenY) // + ")"); const float quarterVoxelSize = voxelSize / 4.0; float newMinScreenX = (static_cast(minScreenX / voxelSize) * voxelSize) + quarterVoxelSize; float newMaxScreenX = (static_cast(maxScreenX / voxelSize) * voxelSize) - quarterVoxelSize; float newMinScreenY = (static_cast(minScreenY / voxelSize) * voxelSize) + quarterVoxelSize; float newMaxScreenY = (static_cast(maxScreenY / voxelSize) * voxelSize) - quarterVoxelSize; // CaretLogFine("NEW Oblique Screen MinX: " // + AString::number(newMinScreenX) + " MaxX: " // + AString::number(newMaxScreenX) + " MinY: " // + AString::number(newMinScreenY) + " MaxY: " // + AString::number(newMaxScreenY)); minScreenX = newMinScreenX; maxScreenX = newMaxScreenX; minScreenY = newMinScreenY; maxScreenY = newMaxScreenY; } /* * Set the corners of the screen for the respective view */ float bottomLeft[3]; float bottomRight[3]; float topRight[3]; float topLeft[3]; switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: CaretAssert(0); break; case VolumeSliceViewPlaneEnum::AXIAL: bottomLeft[0] = minScreenX; bottomLeft[1] = minScreenY; bottomLeft[2] = 0.0; bottomRight[0] = maxScreenX; bottomRight[1] = minScreenY; bottomRight[2] = 0.0; topRight[0] = maxScreenX; topRight[1] = maxScreenY; topRight[2] = 0.0; topLeft[0] = minScreenX; topLeft[1] = maxScreenY; topLeft[2] = 0.0; break; case VolumeSliceViewPlaneEnum::CORONAL: bottomLeft[0] = minScreenX; bottomLeft[1] = 0.0; bottomLeft[2] = minScreenY; bottomRight[0] = maxScreenX; bottomRight[1] = 0.0; bottomRight[2] = minScreenY; topRight[0] = maxScreenX; topRight[1] = 0.0; topRight[2] = maxScreenY; topLeft[0] = minScreenX; topLeft[1] = 0.0; topLeft[2] = maxScreenY; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: bottomLeft[0] = 0.0; bottomLeft[1] = minScreenX; bottomLeft[2] = minScreenY; bottomRight[0] = 0.0; bottomRight[1] = maxScreenX; bottomRight[2] = minScreenY; topRight[0] = 0.0; topRight[1] = maxScreenX; topRight[2] = maxScreenY; topLeft[0] = 0.0; topLeft[1] = minScreenX; topLeft[2] = maxScreenY; break; } /* * Transform the corners of the screen into model coordinates */ transformationMatrix.multiplyPoint3(bottomLeft); transformationMatrix.multiplyPoint3(bottomRight); transformationMatrix.multiplyPoint3(topRight); transformationMatrix.multiplyPoint3(topLeft); if (debugFlag) { const double bottomDist = MathFunctions::distance3D(bottomLeft, bottomRight); const double topDist = MathFunctions::distance3D(topLeft, topRight); const double bottomVoxels = bottomDist / voxelSize; const double topVoxels = topDist / voxelSize; const AString msg = ("Bottom Dist: " + AString::number(bottomDist) + " voxel size: " + AString::number(bottomVoxels) + " Top Dist: " + AString::number(bottomDist) + " voxel size: " + AString::number(topVoxels)); std::cout << qPrintable(msg) << std::endl; } // CaretLogFine("Oblique BL: " + AString::fromNumbers(bottomLeft, 3, ",") // + " BR: " + AString::fromNumbers(bottomRight, 3, ",") // + " TR: " + AString::fromNumbers(topRight, 3, ",") // + " TL: " + AString::fromNumbers(topLeft, 3, ",")); if (debugFlag) { m_fixedPipelineDrawing->setLineWidth(3.0); glColor3f(1.0, 0.0, 0.0); glBegin(GL_LINE_LOOP); glVertex3fv(bottomLeft); glVertex3fv(bottomRight); glVertex3fv(topRight); glVertex3fv(topLeft); glEnd(); } /* * Unit vector and distance in model coords along left side of screen */ double bottomLeftToTopLeftUnitVector[3] = { topLeft[0] - bottomLeft[0], topLeft[1] - bottomLeft[1], topLeft[2] - bottomLeft[2], }; MathFunctions::normalizeVector(bottomLeftToTopLeftUnitVector); const double bottomLeftToTopLeftDistance = MathFunctions::distance3D(bottomLeft, topLeft); /* * Unit vector and distance in model coords along right side of screen */ double bottomRightToTopRightUnitVector[3] = { topRight[0] - bottomRight[0], topRight[1] - bottomRight[1], topRight[2] - bottomRight[2] }; MathFunctions::normalizeVector(bottomRightToTopRightUnitVector); const double bottomRightToTopRightDistance = MathFunctions::distance3D(bottomRight, topRight); /* * For fastest coloring, need to color data values as a group */ std::vector volumeSlices; for (int32_t i = 0; i < numVolumes; i++) { volumeSlices.push_back(VolumeSlice(m_volumeDrawInfo[i].volumeFile, m_volumeDrawInfo[i].mapIndex)); } bool showFirstVoxelCoordFlag = debugFlag; /* * Track voxels that will be drawn */ std::vector voxelsToDraw; if ((bottomLeftToTopLeftDistance > 0) && (bottomRightToTopRightDistance > 0)) { const double bottomLeftToTopLeftStep = voxelSize; const double numLeftSteps = (bottomLeftToTopLeftDistance / bottomLeftToTopLeftStep); const double bottomRightToTopRightStep = (bottomRightToTopRightDistance / numLeftSteps); const double dtVertical = bottomLeftToTopLeftStep / bottomLeftToTopLeftDistance; /* * Voxels are drawn in rows, left to right, across the screen, * starting at the bottom. */ double leftEdgeBottomCoord[3]; double leftEdgeTopCoord[3]; double rightEdgeBottomCoord[3]; double rightEdgeTopCoord[3]; for (double tVertical = 0.0, dLeft = 0.0, dRight = 0.0; tVertical < 1.0; tVertical += dtVertical, dLeft += bottomLeftToTopLeftStep, dRight += bottomRightToTopRightStep) { /* * Coordinate on left edge at BOTTOM of current row */ leftEdgeBottomCoord[0] = bottomLeft[0] + (dLeft * bottomLeftToTopLeftUnitVector[0]); leftEdgeBottomCoord[1] = bottomLeft[1] + (dLeft * bottomLeftToTopLeftUnitVector[1]); leftEdgeBottomCoord[2] = bottomLeft[2] + (dLeft * bottomLeftToTopLeftUnitVector[2]); /* * Coordinate on right edge at BOTTOM of current row */ rightEdgeBottomCoord[0] = bottomRight[0] + (dRight * bottomRightToTopRightUnitVector[0]); rightEdgeBottomCoord[1] = bottomRight[1] + (dRight * bottomRightToTopRightUnitVector[1]); rightEdgeBottomCoord[2] = bottomRight[2] + (dRight * bottomRightToTopRightUnitVector[2]); /* * Coordinate on left edge at TOP of current row */ leftEdgeTopCoord[0] = bottomLeft[0] + ((dLeft + bottomLeftToTopLeftStep) * bottomLeftToTopLeftUnitVector[0]); leftEdgeTopCoord[1] = bottomLeft[1] + ((dLeft + bottomLeftToTopLeftStep) * bottomLeftToTopLeftUnitVector[1]); leftEdgeTopCoord[2] = bottomLeft[2] + ((dLeft + bottomLeftToTopLeftStep) * bottomLeftToTopLeftUnitVector[2]); /* * Coordinate on right edge at TOP of current row */ rightEdgeTopCoord[0] = bottomRight[0] + ((dRight + bottomRightToTopRightStep) * bottomRightToTopRightUnitVector[0]); rightEdgeTopCoord[1] = bottomRight[1] + ((dRight + bottomRightToTopRightStep) * bottomRightToTopRightUnitVector[1]); rightEdgeTopCoord[2] = bottomRight[2] + ((dRight + bottomRightToTopRightStep) * bottomRightToTopRightUnitVector[2]); /* * Determine change in XYZ per voxel along the bottom of the current row */ const double bottomVoxelEdgeDistance = MathFunctions::distance3D(leftEdgeBottomCoord, rightEdgeBottomCoord); double bottomEdgeUnitVector[3]; MathFunctions::createUnitVector(leftEdgeBottomCoord, rightEdgeBottomCoord, bottomEdgeUnitVector); const double numVoxelsInRowFloat = bottomVoxelEdgeDistance / voxelSize; const int64_t numVoxelsInRow = MathFunctions::round(numVoxelsInRowFloat); const double bottomEdgeVoxelSize = bottomVoxelEdgeDistance / numVoxelsInRow; const double bottomVoxelEdgeDX = bottomEdgeVoxelSize * bottomEdgeUnitVector[0]; const double bottomVoxelEdgeDY = bottomEdgeVoxelSize * bottomEdgeUnitVector[1]; const double bottomVoxelEdgeDZ = bottomEdgeVoxelSize * bottomEdgeUnitVector[2]; /* * Determine change in XYZ per voxel along top of the current row */ const double topVoxelEdgeDistance = MathFunctions::distance3D(leftEdgeTopCoord, rightEdgeTopCoord); double topEdgeUnitVector[3]; MathFunctions::createUnitVector(leftEdgeTopCoord, rightEdgeTopCoord, topEdgeUnitVector); const double topEdgeVoxelSize = topVoxelEdgeDistance / numVoxelsInRow; const double topVoxelEdgeDX = topEdgeVoxelSize * topEdgeUnitVector[0]; const double topVoxelEdgeDY = topEdgeVoxelSize * topEdgeUnitVector[1]; const double topVoxelEdgeDZ = topEdgeVoxelSize * topEdgeUnitVector[2]; /* * Initialize bottom and top left coordinate of first voxel in row */ double bottomLeftVoxelCoord[3] = { leftEdgeBottomCoord[0], leftEdgeBottomCoord[1], leftEdgeBottomCoord[2] }; double topLeftVoxelCoord[3] = { leftEdgeTopCoord[0], leftEdgeTopCoord[1], leftEdgeTopCoord[2] }; const bool useInterpolatedVoxel = true; /* * Draw the voxels in the row */ for (int64_t i = 0; i < numVoxelsInRow; i++) { /* * Top right corner of voxel */ const double topRightVoxelCoord[3] = { topLeftVoxelCoord[0] + topVoxelEdgeDX, topLeftVoxelCoord[1] + topVoxelEdgeDY, topLeftVoxelCoord[2] + topVoxelEdgeDZ }; const float voxelCenter[3] = { (bottomLeftVoxelCoord[0] + topRightVoxelCoord[0]) * 0.5, (bottomLeftVoxelCoord[1] + topRightVoxelCoord[1]) * 0.5, (bottomLeftVoxelCoord[2] + topRightVoxelCoord[2]) * 0.5 }; bool printOriginVoxelInfo = false; if (debugFlag) { switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: break; case VolumeSliceViewPlaneEnum::AXIAL: if (showFirstVoxelCoordFlag) { const float dist = voxelCenter[0] - actualOrigin[0]; const AString msg = ("First Voxel Center: " + AString::fromNumbers(voxelCenter, 3, ",") + " Dist from origin voxel in X: " + AString::number(dist) + " Number of voxels between: " + AString::number(dist / voxelSize)); std::cout << qPrintable(msg) << std::endl; showFirstVoxelCoordFlag = false; } if ((bottomLeftVoxelCoord[0] < actualOrigin[0]) && (topRightVoxelCoord[0] > actualOrigin[0])) { if ((bottomLeftVoxelCoord[1] < actualOrigin[1]) && (topRightVoxelCoord[1] > actualOrigin[1])) { printOriginVoxelInfo = true; } } break; case VolumeSliceViewPlaneEnum::CORONAL: if (showFirstVoxelCoordFlag) { const float dist = voxelCenter[0] - actualOrigin[0]; const AString msg = ("First Voxel Center: " + AString::fromNumbers(voxelCenter, 3, ",") + " Dist from origin voxel in X: " + AString::number(dist) + " Number of voxels between: " + AString::number(dist / voxelSize)); std::cout << qPrintable(msg) << std::endl; showFirstVoxelCoordFlag = false; } if ((bottomLeftVoxelCoord[0] < actualOrigin[0]) && (topRightVoxelCoord[0] > actualOrigin[0])) { if ((bottomLeftVoxelCoord[2] < actualOrigin[2]) && (topRightVoxelCoord[2] > actualOrigin[2])) { printOriginVoxelInfo = true; } } break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: if (showFirstVoxelCoordFlag) { const float dist = voxelCenter[1] - actualOrigin[1]; const AString msg = ("First Voxel Center: " + AString::fromNumbers(voxelCenter, 3, ",") + " Dist from origin voxel in Y: " + AString::number(dist) + " Number of voxels between: " + AString::number(dist / voxelSize)); std::cout << qPrintable(msg) << std::endl; showFirstVoxelCoordFlag = false; } if ((bottomLeftVoxelCoord[1] < actualOrigin[1]) && (topRightVoxelCoord[1] > actualOrigin[1])) { if ((bottomLeftVoxelCoord[2] < actualOrigin[2]) && (topRightVoxelCoord[2] > actualOrigin[2])) { printOriginVoxelInfo = true; } } break; } } if (printOriginVoxelInfo) { const AString msg = ("Origin voxel center when drawn is " + AString::fromNumbers(voxelCenter, 3, ",") + " but should be " + AString::fromNumbers(actualOrigin, 3, ",") + " Voxel Corners: (" + AString::fromNumbers(bottomLeftVoxelCoord, 3, ",") + ") (" + AString::fromNumbers(topRightVoxelCoord, 3, ",") + ")"); std::cout << qPrintable(msg) << std::endl; } /* * Loop through the volumes selected as overlays. */ VoxelToDraw* voxelDrawingInfo = NULL; for (int32_t iVol = 0; iVol < numVolumes; iVol++) { const BrainOpenGLFixedPipeline::VolumeDrawInfo& vdi = m_volumeDrawInfo[iVol]; const VolumeMappableInterface* volInter = vdi.volumeFile; const VolumeFile* volumeFile = volumeSlices[iVol].m_volumeFile; float value = 0; bool valueValidFlag = false; bool isPaletteMappedVolumeFile = false; if (volumeFile != NULL) { if (volumeFile->isMappedWithPalette()) { isPaletteMappedVolumeFile = true; } } const CiftiMappableDataFile* ciftiMappableFile = volumeSlices[iVol].m_ciftiMappableDataFile; if (useInterpolatedVoxel && isPaletteMappedVolumeFile) { value = volumeFile->interpolateValue(voxelCenter, VolumeFile::CUBIC, &valueValidFlag, vdi.mapIndex); } else if (ciftiMappableFile != NULL) { const int64_t voxelOffset = ciftiMappableFile->getMapDataOffsetForVoxelAtCoordinate(voxelCenter, vdi.mapIndex); if (voxelOffset >= 0) { CaretAssertVectorIndex(m_ciftiMappableFileData, iVol); const std::vector& data = m_ciftiMappableFileData[iVol]; CaretAssertVectorIndex(data, voxelOffset); value = data[voxelOffset]; valueValidFlag = true; } } else { value = volInter->getVoxelValue(voxelCenter, &valueValidFlag, vdi.mapIndex); } /* * Need to draw all voxels when editing */ if (volumeEditingDrawAllVoxelsFlag) { if (! valueValidFlag) { if (volumeFile != NULL) { if (volumeFile == voxelEditingVolumeFile) { value = voxelEditingValue; valueValidFlag = true; } } } } if (valueValidFlag) { if (voxelDrawingInfo == NULL) { /* * Bottom right corner of voxel */ const double bottomRightVoxelCoord[3] = { bottomLeftVoxelCoord[0] + bottomVoxelEdgeDX, bottomLeftVoxelCoord[1] + bottomVoxelEdgeDY, bottomLeftVoxelCoord[2] + bottomVoxelEdgeDZ }; /* * Top right corner of voxel */ const double topRightVoxelCoord[3] = { topLeftVoxelCoord[0] + topVoxelEdgeDX, topLeftVoxelCoord[1] + topVoxelEdgeDY, topLeftVoxelCoord[2] + topVoxelEdgeDZ }; voxelDrawingInfo = new VoxelToDraw(voxelCenter, bottomLeftVoxelCoord, bottomRightVoxelCoord, topRightVoxelCoord, topLeftVoxelCoord); voxelsToDraw.push_back(voxelDrawingInfo); } const int64_t offset = volumeSlices[iVol].addValue(value); voxelDrawingInfo->addVolumeValue(iVol, offset); } } /* * Move to the next voxel in the row */ bottomLeftVoxelCoord[0] += bottomVoxelEdgeDX; bottomLeftVoxelCoord[1] += bottomVoxelEdgeDY; bottomLeftVoxelCoord[2] += bottomVoxelEdgeDZ; topLeftVoxelCoord[0] += topVoxelEdgeDX; topLeftVoxelCoord[1] += topVoxelEdgeDY; topLeftVoxelCoord[2] += topVoxelEdgeDZ; } } } const int32_t browserTabIndex = m_browserTabContent->getTabNumber(); const DisplayPropertiesLabels* displayPropertiesLabels = m_brain->getDisplayPropertiesLabels(); const DisplayGroupEnum::Enum displayGroup = displayPropertiesLabels->getDisplayGroupForTab(browserTabIndex); /* * Color voxel values */ for (int32_t i = 0; i < numVolumes; i++) { const int64_t numValues = static_cast(volumeSlices[i].m_values.size()); if (numValues > 0) { volumeSlices[i].allocateColors(); VolumeMappableInterface* volume = volumeSlices[i].m_volumeMappableInterface; CaretMappableDataFile* mappableFile = dynamic_cast(volume); VolumeFile* volumeFile = volumeSlices[i].m_volumeFile; CaretAssert(mappableFile); const int32_t mapIndex = volumeSlices[i].m_mapIndex; const float* values = &volumeSlices[i].m_values[0]; uint8_t* rgba = &volumeSlices[i].m_rgba[0]; if (volumeEditingDrawAllVoxelsFlag && (volumeFile == voxelEditingVolumeFile)) { for (int64_t i = 0; i < numValues; i++) { const int64_t i4 = i * 4; rgba[i4] = 255; rgba[i4+1] = 255; rgba[i4+2] = 255; rgba[i4+3] = 255; } } else if (mappableFile->isMappedWithPalette()) { const PaletteColorMapping* paletteColorMapping = mappableFile->getMapPaletteColorMapping(mapIndex); const AString paletteName = paletteColorMapping->getSelectedPaletteName(); const Palette* palette = m_paletteFile->getPaletteByName(paletteName); if (palette != NULL) { CaretAssertVectorIndex(m_volumeDrawInfo, i); NodeAndVoxelColoring::colorScalarsWithPalette(m_volumeDrawInfo[i].statistics, paletteColorMapping, palette, values, values, numValues, rgba); } else { CaretLogWarning("Missing palette named: " + paletteName); } } else if (mappableFile->isMappedWithLabelTable()) { GiftiLabelTable* labelTable = mappableFile->getMapLabelTable(mapIndex); NodeAndVoxelColoring::colorIndicesWithLabelTableForDisplayGroupTab(labelTable, values, numValues, displayGroup, browserTabIndex, rgba); } else { CaretAssert(0); } } } const int64_t numVoxelsToDraw = static_cast(voxelsToDraw.size()); /* * quadCoords is the coordinates for all four corners of a 'quad' * that is used to draw a voxel. quadRGBA is the colors for each * voxel drawn as a 'quad'. */ std::vector quadCoordsVector; std::vector quadNormalsVector; std::vector quadRGBAsVector; /* * Reserve space to avoid reallocations */ const int64_t coordinatesPerQuad = 4; const int64_t componentsPerCoordinate = 3; const int64_t colorComponentsPerCoordinate = 4; quadCoordsVector.resize(numVoxelsToDraw * coordinatesPerQuad * componentsPerCoordinate); quadNormalsVector.resize(quadCoordsVector.size()); quadRGBAsVector.resize(numVoxelsToDraw * coordinatesPerQuad * colorComponentsPerCoordinate); int64_t coordOffset = 0; int64_t normalOffset = 0; int64_t rgbaOffset = 0; float* quadCoords = &quadCoordsVector[0]; float* quadNormals = &quadNormalsVector[0]; uint8_t* quadRGBAs = &quadRGBAsVector[0]; for (int64_t iVox = 0; iVox < numVoxelsToDraw; iVox++) { CaretAssertVectorIndex(voxelsToDraw, iVox); VoxelToDraw* vtd = voxelsToDraw[iVox]; CaretAssert(vtd); uint8_t voxelRGBA[4] = { 0, 0, 0, 0 }; const int32_t numSlicesForVoxel = static_cast(vtd->m_sliceIndices.size()); for (int32_t iSlice = 0; iSlice < numSlicesForVoxel; iSlice++) { CaretAssertVectorIndex(vtd->m_sliceIndices, iSlice); CaretAssertVectorIndex(vtd->m_sliceOffsets, iSlice); const int32_t sliceIndex = vtd->m_sliceIndices[iSlice]; const int64_t voxelOffset = vtd->m_sliceOffsets[iSlice]; const uint8_t* rgba = volumeSlices[sliceIndex].getRgbaForValueByIndex(voxelOffset); if (rgba[3] > 0) { voxelRGBA[0] = rgba[0]; voxelRGBA[1] = rgba[1]; voxelRGBA[2] = rgba[2]; voxelRGBA[3] = rgba[3]; if (m_identificationModeFlag) { VolumeMappableInterface* volMap = volumeSlices[sliceIndex].m_volumeMappableInterface; int64_t voxelI, voxelJ, voxelK; volMap->enclosingVoxel(vtd->m_center[0], vtd->m_center[1], vtd->m_center[2], voxelI, voxelJ, voxelK); if (volMap->indexValid(voxelI, voxelJ, voxelK)) { float diffXYZ[3]; vtd->getDiffXYZ(diffXYZ); addVoxelToIdentification(sliceIndex, volumeSlices[sliceIndex].m_mapIndex, voxelI, voxelJ, voxelK, diffXYZ, voxelRGBA); } } } } if (voxelRGBA[3] > 0) { float sliceNormalVector[3]; plane.getNormalVector(sliceNormalVector); CaretAssertVectorIndex(quadRGBAsVector, rgbaOffset + 3); quadRGBAs[rgbaOffset] = voxelRGBA[0]; quadRGBAs[rgbaOffset+1] = voxelRGBA[1]; quadRGBAs[rgbaOffset+2] = voxelRGBA[2]; quadRGBAs[rgbaOffset+3] = voxelRGBA[3]; rgbaOffset += 4; CaretAssertVectorIndex(quadNormalsVector, normalOffset + 2); quadNormals[normalOffset] = sliceNormalVector[0]; quadNormals[normalOffset+1] = sliceNormalVector[1]; quadNormals[normalOffset+2] = sliceNormalVector[2]; normalOffset += 3; CaretAssertVectorIndex(quadRGBAsVector, rgbaOffset + 3); quadRGBAs[rgbaOffset] = voxelRGBA[0]; quadRGBAs[rgbaOffset+1] = voxelRGBA[1]; quadRGBAs[rgbaOffset+2] = voxelRGBA[2]; quadRGBAs[rgbaOffset+3] = voxelRGBA[3]; rgbaOffset += 4; CaretAssertVectorIndex(quadNormalsVector, normalOffset + 2); quadNormals[normalOffset] = sliceNormalVector[0]; quadNormals[normalOffset+1] = sliceNormalVector[1]; quadNormals[normalOffset+2] = sliceNormalVector[2]; normalOffset += 3; CaretAssertVectorIndex(quadRGBAsVector, rgbaOffset + 3); quadRGBAs[rgbaOffset] = voxelRGBA[0]; quadRGBAs[rgbaOffset+1] = voxelRGBA[1]; quadRGBAs[rgbaOffset+2] = voxelRGBA[2]; quadRGBAs[rgbaOffset+3] = voxelRGBA[3]; rgbaOffset += 4; CaretAssertVectorIndex(quadNormalsVector, normalOffset + 2); quadNormals[normalOffset] = sliceNormalVector[0]; quadNormals[normalOffset+1] = sliceNormalVector[1]; quadNormals[normalOffset+2] = sliceNormalVector[2]; normalOffset += 3; CaretAssertVectorIndex(quadRGBAsVector, rgbaOffset + 3); quadRGBAs[rgbaOffset] = voxelRGBA[0]; quadRGBAs[rgbaOffset+1] = voxelRGBA[1]; quadRGBAs[rgbaOffset+2] = voxelRGBA[2]; quadRGBAs[rgbaOffset+3] = voxelRGBA[3]; rgbaOffset += 4; CaretAssertVectorIndex(quadNormalsVector, normalOffset + 2); quadNormals[normalOffset] = sliceNormalVector[0]; quadNormals[normalOffset+1] = sliceNormalVector[1]; quadNormals[normalOffset+2] = sliceNormalVector[2]; normalOffset += 3; CaretAssertVectorIndex(quadCoordsVector, coordOffset + 11); for (int32_t iq = 0; iq < 12; iq++) { quadCoords[coordOffset + iq] = vtd->m_coordinates[iq]; } coordOffset += 12; } } quadCoordsVector.resize(coordOffset); quadNormalsVector.resize(normalOffset); quadRGBAsVector.resize(rgbaOffset); for (std::vector::iterator iter = voxelsToDraw.begin(); iter != voxelsToDraw.end(); iter++) { VoxelToDraw* vtd = *iter; delete vtd; } voxelsToDraw.clear(); if ( ! quadCoordsVector.empty()) { glPushMatrix(); BrainOpenGLPrimitiveDrawing::drawQuads(quadCoordsVector, quadNormalsVector, quadRGBAsVector); glPopMatrix(); } } /** * Draw an orthogonal slice. * * @param sliceViewPlane * The plane for slice drawing. * @param sliceCoordinates * Coordinates of the selected slice. * @param plane * Plane equation for the selected slice. */ void BrainOpenGLVolumeSliceDrawing::drawOrthogonalSlice(const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const float sliceCoordinates[3], const Plane& plane) { const int32_t browserTabIndex = m_browserTabContent->getTabNumber(); const DisplayPropertiesLabels* displayPropertiesLabels = m_brain->getDisplayPropertiesLabels(); const DisplayGroupEnum::Enum displayGroup = displayPropertiesLabels->getDisplayGroupForTab(browserTabIndex); // switch (labelDrawingType) { // case LabelDrawingTypeEnum::DRAW_FILLED_LABEL_COLOR: // break; // case LabelDrawingTypeEnum::DRAW_FILLED_BLACK_OUTLINE: // break; // case LabelDrawingTypeEnum::DRAW_FILLED_WHITE_OUTLINE: // break; // case LabelDrawingTypeEnum::DRAW_OUTLINE_LABEL_COLOR: // break; // case LabelDrawingTypeEnum::DRAW_OUTLINE_BLACK: // break; // case LabelDrawingTypeEnum::DRAW_OUTLINE_WHITE: // break; // } // bool isOutlineMode = false; // switch (labelDrawingType) { // case LabelDrawingTypeEnum::DRAW_FILLED: // break; // case LabelDrawingTypeEnum::DRAW_OUTLINE: // isOutlineMode = true; // break; // } /* * Enable alpha blending so voxels that are not drawn from higher layers * allow voxels from lower layers to be seen. */ glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); /* * Flat shading voxels not interpolated */ glShadeModel(GL_FLAT); CaretAssert(plane.isValidPlane()); if (plane.isValidPlane() == false) { return; } /* * Compute coordinate of point in center of first slice */ float selectedSliceCoordinate = 0.0; float sliceNormalVector[3] = { 0.0, 0.0, 0.0 }; plane.getNormalVector(sliceNormalVector); switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: CaretAssert(0); break; case VolumeSliceViewPlaneEnum::AXIAL: selectedSliceCoordinate = sliceCoordinates[2]; break; case VolumeSliceViewPlaneEnum::CORONAL: selectedSliceCoordinate = sliceCoordinates[1]; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: selectedSliceCoordinate = sliceCoordinates[0]; break; } /* * Holds colors for voxels in the slice * Outside of loop to minimize allocations * It is faster to make one call to * NodeAndVoxelColoring::colorScalarsWithPalette() with * all voxels in the slice than it is to call it * separately for each voxel. */ std::vector sliceVoxelsRgbaVector; /* * Draw each of the volumes separately so that each * is drawn with the correct voxel slices. */ const int32_t numberOfVolumesToDraw = static_cast(m_volumeDrawInfo.size()); for (int32_t iVol = 0; iVol < numberOfVolumesToDraw; iVol++) { const BrainOpenGLFixedPipeline::VolumeDrawInfo& volInfo = m_volumeDrawInfo[iVol]; const VolumeMappableInterface* volumeFile = volInfo.volumeFile; int64_t dimI, dimJ, dimK, numMaps, numComponents; volumeFile->getDimensions(dimI, dimJ, dimK, numMaps, numComponents); const int64_t mapIndex = volInfo.mapIndex; float originX, originY, originZ; volumeFile->indexToSpace(0, 0, 0, originX, originY, originZ); float x1, y1, z1; volumeFile->indexToSpace(1, 1, 1, x1, y1, z1); const float voxelStepX = x1 - originX; const float voxelStepY = y1 - originY; const float voxelStepZ = z1 - originZ; /* * Determine index of slice being viewed for the volume */ float coordinateOnSlice[3] = { originX, originY, originZ }; switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: CaretAssert(0); break; case VolumeSliceViewPlaneEnum::AXIAL: coordinateOnSlice[2] = selectedSliceCoordinate; break; case VolumeSliceViewPlaneEnum::CORONAL: coordinateOnSlice[1] = selectedSliceCoordinate; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: coordinateOnSlice[0] = selectedSliceCoordinate; break; } int64_t sliceIndicesForCoordinateOnSlice[3]; volumeFile->enclosingVoxel(coordinateOnSlice[0], coordinateOnSlice[1], coordinateOnSlice[2], sliceIndicesForCoordinateOnSlice[0], sliceIndicesForCoordinateOnSlice[1], sliceIndicesForCoordinateOnSlice[2]); int64_t sliceIndexForDrawing = -1; int64_t numVoxelsInSlice = 0; switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: CaretAssert(0); break; case VolumeSliceViewPlaneEnum::AXIAL: sliceIndexForDrawing = sliceIndicesForCoordinateOnSlice[2]; if ((sliceIndexForDrawing < 0) || (sliceIndexForDrawing >= dimK)) { continue; } numVoxelsInSlice = dimI * dimJ; break; case VolumeSliceViewPlaneEnum::CORONAL: sliceIndexForDrawing = sliceIndicesForCoordinateOnSlice[1]; if ((sliceIndexForDrawing < 0) || (sliceIndexForDrawing >= dimJ)) { continue; } numVoxelsInSlice = dimI * dimK; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: sliceIndexForDrawing = sliceIndicesForCoordinateOnSlice[0]; if ((sliceIndexForDrawing < 0) || (sliceIndexForDrawing >= dimI)) { continue; } numVoxelsInSlice = dimJ * dimK; break; } /* * Stores RGBA values for each voxel. * Use a vector for voxel colors so no worries about memory being freed. */ const int64_t numVoxelsInSliceRGBA = numVoxelsInSlice * 4; if (numVoxelsInSliceRGBA > static_cast(sliceVoxelsRgbaVector.size())) { sliceVoxelsRgbaVector.resize(numVoxelsInSliceRGBA); } uint8_t* sliceVoxelsRGBA = &sliceVoxelsRgbaVector[0]; /* * Get colors for all voxels in the slice. */ const int64_t validVoxelCount = volumeFile->getVoxelColorsForSliceInMap(m_brain->getPaletteFile(), mapIndex, sliceViewPlane, sliceIndexForDrawing, displayGroup, browserTabIndex, sliceVoxelsRGBA); /* * Is label outline mode? */ if (m_volumeDrawInfo[iVol].mapFile->isMappedWithLabelTable()) { int64_t xdim = 0; int64_t ydim = 0; switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: CaretAssert(0); break; case VolumeSliceViewPlaneEnum::AXIAL: xdim = dimI; ydim = dimJ; break; case VolumeSliceViewPlaneEnum::CORONAL: xdim = dimI; ydim = dimK; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: xdim = dimJ; ydim = dimK; break; } LabelDrawingTypeEnum::Enum labelDrawingType = LabelDrawingTypeEnum::DRAW_FILLED; CaretColorEnum::Enum outlineColor = CaretColorEnum::BLACK; const CaretMappableDataFile* mapFile = dynamic_cast(volumeFile); if (mapFile != NULL) { if (mapFile->isMappedWithLabelTable()) { const LabelDrawingProperties* props = mapFile->getLabelDrawingProperties(); labelDrawingType = props->getDrawingType(); outlineColor = props->getOutlineColor(); } } NodeAndVoxelColoring::convertSliceColoringToOutlineMode(sliceVoxelsRGBA, labelDrawingType, outlineColor, xdim, ydim); } int64_t selectedSliceIndices[3]; volumeFile->enclosingVoxel(sliceCoordinates[0], sliceCoordinates[1], sliceCoordinates[2], selectedSliceIndices[0], selectedSliceIndices[1], selectedSliceIndices[2]); const uint8_t volumeDrawingOpacity = static_cast(volInfo.opacity * 255.0); /* * Setup for drawing the voxels in the slice. */ float startCoordinate[3] = { originX - (voxelStepX / 2.0), originY - (voxelStepY / 2.0), originZ - (voxelStepZ / 2.0) }; float rowStep[3] = { 0.0, 0.0, 0.0 }; float columnStep[3] = { 0.0, 0.0, 0.0 }; int64_t numberOfRows = 0; int64_t numberOfColumns = 0; switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: CaretAssert(0); break; case VolumeSliceViewPlaneEnum::AXIAL: startCoordinate[2] = selectedSliceCoordinate; //m_browserTabContent->getSliceCoordinateAxial(); rowStep[1] = voxelStepY; columnStep[0] = voxelStepX; numberOfRows = dimJ; numberOfColumns = dimI; break; case VolumeSliceViewPlaneEnum::CORONAL: startCoordinate[1] = selectedSliceCoordinate; //m_browserTabContent->getSliceCoordinateCoronal(); rowStep[2] = voxelStepZ; columnStep[0] = voxelStepX; numberOfRows = dimK; numberOfColumns = dimI; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: startCoordinate[0] = selectedSliceCoordinate; //m_browserTabContent->getSliceCoordinateParasagittal(); rowStep[2] = voxelStepZ; columnStep[1] = voxelStepY; numberOfRows = dimK; numberOfColumns = dimJ; break; } if (m_modelWholeBrain != NULL) { /* * After the a slice is drawn in ALL view, some layers * (volume surface outline) may be drawn in lines. As the * view is rotated, lines will partially appear and disappear * due to the lines having the same (extremely close) depth * values as the voxel polygons. OpenGL's Polygon Offset * only works with polygons and NOT with lines or points. * So, polygon offset cannot be used to move the depth * values for the lines and points "a little closer" to * the user. Instead, polygon offset is used to push * the underlaying slices "a little bit away" from the * user. * * Resolves WB-414 */ const float inverseSliceIndex = numberOfVolumesToDraw - iVol; //const float factor = 5.0; const float factor = inverseSliceIndex * 1.0 + 1.0; const float units = inverseSliceIndex * 1.0 + 1.0; glEnable(GL_POLYGON_OFFSET_FILL); glPolygonOffset(factor, units); } /* * Draw the voxels in the slice. */ drawOrthogonalSliceVoxels(sliceNormalVector, startCoordinate, rowStep, columnStep, numberOfColumns, numberOfRows, sliceVoxelsRgbaVector, validVoxelCount, volumeFile, iVol, mapIndex, volumeDrawingOpacity); glDisable(GL_POLYGON_OFFSET_FILL); } showBrainordinateHighlightRegionOfInterest(sliceViewPlane, sliceCoordinates, sliceNormalVector); // drawIdentificationSymbols(plane); glDisable(GL_BLEND); glShadeModel(GL_SMOOTH); } /** * Show brainordinate highlighting region of interest for the volume slice. * * @param sliceViewPlane * Slice plane viewed. * @param sliceCoordinates * Coordinates of the slice. * @param sliceNormalVector * Normal vector for the slice. */ void BrainOpenGLVolumeSliceDrawing::showBrainordinateHighlightRegionOfInterest(const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const float sliceCoordinates[3], const float sliceNormalVector[3]) { const BrainordinateRegionOfInterest* roi = m_brain->getBrainordinateHighlightRegionOfInterest(); if ( ! roi->hasVolumeVoxels()) { return; } if ( ! roi->isBrainordinateHighlightingEnabled()) { return; } const std::vector& voxelXYZ = roi->getVolumeVoxelsXYZ(); const int64_t numVoxels = static_cast(voxelXYZ.size() / 3); if (numVoxels <= 0) { return; } float voxelSize[3]; roi->getVolumeVoxelSize(voxelSize); CaretAssert(voxelSize[0] >= 0.0); CaretAssert(voxelSize[1] >= 0.0); CaretAssert(voxelSize[2] >= 0.0); float halfX = voxelSize[0] / 2.0; float halfY = voxelSize[1] / 2.0; float halfZ = voxelSize[2] / 2.0; int64_t axisIndex = 0; float sliceMinCoord = 0.0; float sliceMaxCoord = 0.0; switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: CaretAssert(0); break; case VolumeSliceViewPlaneEnum::AXIAL: sliceMinCoord = sliceCoordinates[2] - halfZ; sliceMaxCoord = sliceCoordinates[2] + halfZ; axisIndex = 2; break; case VolumeSliceViewPlaneEnum::CORONAL: sliceMinCoord = sliceCoordinates[1] - halfY; sliceMaxCoord = sliceCoordinates[1] + halfZ; axisIndex = 1; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: sliceMinCoord = sliceCoordinates[0] - halfX; sliceMaxCoord = sliceCoordinates[0] + halfX; axisIndex = 0; break; } std::vector quadsXYZ; for (int64_t i = 0; i < numVoxels; i++) { const int64_t i3 = i * 3; if ((voxelXYZ[i3 + axisIndex] >= sliceMinCoord) && (voxelXYZ[i3 + axisIndex] <= sliceMaxCoord)) { CaretAssertVectorIndex(voxelXYZ, i3+2); const float x = voxelXYZ[i3]; const float y = voxelXYZ[i3+1]; const float z = voxelXYZ[i3+2]; switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: CaretAssert(0); break; case VolumeSliceViewPlaneEnum::AXIAL: quadsXYZ.push_back(x - halfX); quadsXYZ.push_back(y - halfY); quadsXYZ.push_back(sliceCoordinates[2]); quadsXYZ.push_back(x + halfX); quadsXYZ.push_back(y - halfY); quadsXYZ.push_back(sliceCoordinates[2]); quadsXYZ.push_back(x + halfX); quadsXYZ.push_back(y + halfY); quadsXYZ.push_back(sliceCoordinates[2]); quadsXYZ.push_back(x - halfX); quadsXYZ.push_back(y + halfY); quadsXYZ.push_back(sliceCoordinates[2]); break; case VolumeSliceViewPlaneEnum::CORONAL: quadsXYZ.push_back(x - halfX); quadsXYZ.push_back(sliceCoordinates[1]); quadsXYZ.push_back(z - halfZ); quadsXYZ.push_back(x + halfX); quadsXYZ.push_back(sliceCoordinates[1]); quadsXYZ.push_back(z - halfZ); quadsXYZ.push_back(x + halfX); quadsXYZ.push_back(sliceCoordinates[1]); quadsXYZ.push_back(z + halfZ); quadsXYZ.push_back(x - halfX); quadsXYZ.push_back(sliceCoordinates[1]); quadsXYZ.push_back(z + halfZ); break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: quadsXYZ.push_back(sliceCoordinates[0]); quadsXYZ.push_back(y + halfY); quadsXYZ.push_back(z - halfZ); quadsXYZ.push_back(sliceCoordinates[0]); quadsXYZ.push_back(y - halfY); quadsXYZ.push_back(z - halfZ); quadsXYZ.push_back(sliceCoordinates[0]); quadsXYZ.push_back(y - halfY); quadsXYZ.push_back(z + halfZ); quadsXYZ.push_back(sliceCoordinates[0]); quadsXYZ.push_back(y + halfY); quadsXYZ.push_back(z + halfZ); break; } } } const int64_t numVoxelsToDraw = (quadsXYZ.size() / 12); CaretAssert((numVoxelsToDraw * 12) == static_cast(quadsXYZ.size())); const int64_t numCoords = (quadsXYZ.size()) / 3; std::vector voxelQuadNormals; voxelQuadNormals.reserve(numVoxelsToDraw * 12); std::vector voxelQuadRgba; voxelQuadRgba.reserve(numVoxelsToDraw * 16); CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); uint8_t foregroundColor[4]; prefs->getColorForegroundVolumeView(foregroundColor); for (int32_t iNormalAndColor = 0; iNormalAndColor < numCoords; iNormalAndColor++) { voxelQuadRgba.push_back(foregroundColor[0]); voxelQuadRgba.push_back(foregroundColor[1]); voxelQuadRgba.push_back(foregroundColor[2]); voxelQuadRgba.push_back(255); voxelQuadNormals.push_back(sliceNormalVector[0]); voxelQuadNormals.push_back(sliceNormalVector[1]); voxelQuadNormals.push_back(sliceNormalVector[2]); } CaretAssert(quadsXYZ.size() == voxelQuadNormals.size()); CaretAssert((numVoxelsToDraw * 16) == static_cast(voxelQuadRgba.size())); BrainOpenGLPrimitiveDrawing::drawQuads(quadsXYZ, voxelQuadNormals, voxelQuadRgba); } /** * Draw identification symbols on volume slice with the given plane. * * @param plane * The plane equation. */ void BrainOpenGLVolumeSliceDrawing::drawIdentificationSymbols(const Plane& plane) { IdentificationManager* idManager = m_brain->getIdentificationManager(); SelectionItemVoxelIdentificationSymbol* symbolID = m_brain->getSelectionManager()->getVoxelIdentificationSymbol(); const std::vector voxelIDs = idManager->getIdentifiedItemsForVolume(); /* * Check for a 'selection' type mode */ bool isSelect = false; switch (m_fixedPipelineDrawing->mode) { case BrainOpenGLFixedPipeline::MODE_DRAWING: //EventManager::get()->sendEvent(colorsFromChartsEvent.getPointer()); break; case BrainOpenGLFixedPipeline::MODE_IDENTIFICATION: if (symbolID->isEnabledForSelection()) { isSelect = true; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } else { return; } break; case BrainOpenGLFixedPipeline::MODE_PROJECTION: return; break; } uint8_t rgba[4]; const int32_t numVoxelIdSymbols = static_cast(voxelIDs.size()); for (int32_t iVoxel = 0; iVoxel < numVoxelIdSymbols; iVoxel++) { CaretAssertVectorIndex(voxelIDs, iVoxel); const IdentifiedItemVoxel& voxel = voxelIDs[iVoxel]; /* * Show symbol for node ID? */ if ( ! voxel.isShowIdentificationSymbol()) { continue; } float xyz[3]; voxel.getXYZ(xyz); const float symbolDiameter = voxel.getSymbolSize(); const float halfSymbolSize = symbolDiameter / 2.0; const float dist = plane.signedDistanceToPlane(xyz); if (dist < halfSymbolSize) { if (isSelect) { m_fixedPipelineDrawing->colorIdentification->addItem(rgba, SelectionItemDataTypeEnum::VOXEL_IDENTIFICATION_SYMBOL, iVoxel); rgba[3] = 255; } else { voxel.getSymbolRGBA(rgba); } glPushMatrix(); glTranslatef(xyz[0], xyz[1], xyz[2]); m_fixedPipelineDrawing->drawSphereWithDiameter(rgba, symbolDiameter); glPopMatrix(); } } if (isSelect) { int voxelIdIndex = -1; float depth = -1.0; m_fixedPipelineDrawing->getIndexFromColorSelection(SelectionItemDataTypeEnum::VOXEL_IDENTIFICATION_SYMBOL, m_fixedPipelineDrawing->mouseX, m_fixedPipelineDrawing->mouseY, voxelIdIndex, depth); if (voxelIdIndex >= 0) { if (symbolID->isOtherScreenDepthCloserToViewer(depth)) { CaretAssertVectorIndex(voxelIDs, voxelIdIndex); const IdentifiedItemVoxel& voxel = voxelIDs[voxelIdIndex]; float xyz[3]; voxel.getXYZ(xyz); symbolID->setVoxelXYZ(xyz); symbolID->setBrain(m_brain); symbolID->setScreenDepth(depth); m_fixedPipelineDrawing->setSelectedItemScreenXYZ(symbolID, xyz); CaretLogFine("Selected Vertex Identification Symbol: " + QString::number(voxelIdIndex)); } } } } /** * Draw an orthogonal slice with culling to avoid drawing * voxels not visible in the viewport and reduce drawing time. * * @param sliceViewPlane * The plane for slice drawing. * @param sliceCoordinates * Coordinates of the selected slice. * @param plane * Plane equation for the selected slice. */ void BrainOpenGLVolumeSliceDrawing::drawOrthogonalSliceWithCulling(const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const float sliceCoordinates[3], const Plane& plane) { const int32_t browserTabIndex = m_browserTabContent->getTabNumber(); const DisplayPropertiesLabels* displayPropertiesLabels = m_brain->getDisplayPropertiesLabels(); const DisplayGroupEnum::Enum displayGroup = displayPropertiesLabels->getDisplayGroupForTab(browserTabIndex); /* * Enable alpha blending so voxels that are not drawn from higher layers * allow voxels from lower layers to be seen. */ glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); /* * Flat shading voxels not interpolated */ glShadeModel(GL_FLAT); CaretAssert(plane.isValidPlane()); if (plane.isValidPlane() == false) { return; } /* * Compute coordinate of point in center of first slice */ float selectedSliceCoordinate = 0.0; float sliceNormalVector[3] = { 0.0, 0.0, 0.0 }; plane.getNormalVector(sliceNormalVector); switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: CaretAssert(0); break; case VolumeSliceViewPlaneEnum::AXIAL: selectedSliceCoordinate = sliceCoordinates[2]; break; case VolumeSliceViewPlaneEnum::CORONAL: selectedSliceCoordinate = sliceCoordinates[1]; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: selectedSliceCoordinate = sliceCoordinates[0]; break; } /* * Holds colors for voxels in the slice * Outside of loop to minimize allocations * It is faster to make one call to * NodeAndVoxelColoring::colorScalarsWithPalette() with * all voxels in the slice than it is to call it * separately for each voxel. */ std::vector sliceVoxelsRgbaVector; /* * Draw each of the volumes separately so that each * is drawn with the correct voxel slices. */ const int32_t numberOfVolumesToDraw = static_cast(m_volumeDrawInfo.size()); for (int32_t iVol = 0; iVol < numberOfVolumesToDraw; iVol++) { const BrainOpenGLFixedPipeline::VolumeDrawInfo& volInfo = m_volumeDrawInfo[iVol]; const VolumeMappableInterface* volumeFile = volInfo.volumeFile; int64_t culledFirstVoxelIJK[3]; int64_t culledLastVoxelIJK[3]; float voxelDeltaXYZ[3]; if ( ! getVolumeDrawingViewDependentCulling(sliceViewPlane, selectedSliceCoordinate, volumeFile, culledFirstVoxelIJK, culledLastVoxelIJK, voxelDeltaXYZ)) { CaretLogSevere("BrainOpenGLVolumeSliceDrawing::getVolumeDrawingViewDependentCulling() failed."); continue; } const int64_t numVoxelsI = std::abs(culledLastVoxelIJK[0] - culledFirstVoxelIJK[0]) + 1; const int64_t numVoxelsJ = std::abs(culledLastVoxelIJK[1] - culledFirstVoxelIJK[1]) + 1; const int64_t numVoxelsK = std::abs(culledLastVoxelIJK[2] - culledFirstVoxelIJK[2]) + 1; int64_t dimIJK[3], numMaps, numComponents; volumeFile->getDimensions(dimIJK[0], dimIJK[1], dimIJK[2], numMaps, numComponents); const int64_t mapIndex = volInfo.mapIndex; float firstVoxelXYZ[3]; volumeFile->indexToSpace(culledFirstVoxelIJK[0], culledFirstVoxelIJK[1], culledFirstVoxelIJK[2], firstVoxelXYZ[0], firstVoxelXYZ[1], firstVoxelXYZ[2]); const float voxelStepX = voxelDeltaXYZ[0]; const float voxelStepY = voxelDeltaXYZ[1]; const float voxelStepZ = voxelDeltaXYZ[2]; int64_t sliceIndexForDrawing = -1; int64_t numVoxelsInSlice = 0; switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: CaretAssert(0); break; case VolumeSliceViewPlaneEnum::AXIAL: sliceIndexForDrawing = culledFirstVoxelIJK[2]; if ((sliceIndexForDrawing < 0) || (sliceIndexForDrawing >= dimIJK[2])) { continue; } numVoxelsInSlice = numVoxelsI * numVoxelsJ; break; case VolumeSliceViewPlaneEnum::CORONAL: sliceIndexForDrawing = culledFirstVoxelIJK[1]; if ((sliceIndexForDrawing < 0) || (sliceIndexForDrawing >= dimIJK[1])) { continue; } numVoxelsInSlice = numVoxelsI * numVoxelsK; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: sliceIndexForDrawing = culledFirstVoxelIJK[0]; if ((sliceIndexForDrawing < 0) || (sliceIndexForDrawing >= dimIJK[0])) { continue; } numVoxelsInSlice = numVoxelsJ * numVoxelsK; break; } /* * Stores RGBA values for each voxel. * Use a vector for voxel colors so no worries about memory being freed. */ const int64_t numVoxelsInSliceRGBA = numVoxelsInSlice * 4; if (numVoxelsInSliceRGBA != static_cast(sliceVoxelsRgbaVector.size())) { sliceVoxelsRgbaVector.resize(numVoxelsInSliceRGBA); } uint8_t* sliceVoxelsRGBA = &sliceVoxelsRgbaVector[0]; /* * Get colors for all voxels in the slice. */ const int64_t voxelCountIJK[3] = { numVoxelsI, numVoxelsJ, numVoxelsK }; const int64_t validVoxelCount = volumeFile->getVoxelColorsForSubSliceInMap(m_brain->getPaletteFile(), mapIndex, sliceViewPlane, sliceIndexForDrawing, culledFirstVoxelIJK, culledLastVoxelIJK, voxelCountIJK, displayGroup, browserTabIndex, sliceVoxelsRGBA); /* * Is label outline mode? */ if (m_volumeDrawInfo[iVol].mapFile->isMappedWithLabelTable()) { int64_t xdim = 0; int64_t ydim = 0; switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: CaretAssert(0); break; case VolumeSliceViewPlaneEnum::AXIAL: xdim = numVoxelsI; ydim = numVoxelsJ; break; case VolumeSliceViewPlaneEnum::CORONAL: xdim = numVoxelsI; ydim = numVoxelsK; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: xdim = numVoxelsJ; ydim = numVoxelsK; break; } LabelDrawingTypeEnum::Enum labelDrawingType = LabelDrawingTypeEnum::DRAW_FILLED; CaretColorEnum::Enum outlineColor = CaretColorEnum::BLACK; const CaretMappableDataFile* mapFile = dynamic_cast(volumeFile); if (mapFile != NULL) { if (mapFile->isMappedWithLabelTable()) { const LabelDrawingProperties* props = mapFile->getLabelDrawingProperties(); labelDrawingType = props->getDrawingType(); outlineColor = props->getOutlineColor(); } } NodeAndVoxelColoring::convertSliceColoringToOutlineMode(sliceVoxelsRGBA, labelDrawingType, outlineColor, xdim, ydim); } const uint8_t volumeDrawingOpacity = static_cast(volInfo.opacity * 255.0); /* * Setup for drawing the voxels in the slice. */ float startCoordinate[3] = { firstVoxelXYZ[0] - (voxelStepX / 2.0), firstVoxelXYZ[1] - (voxelStepY / 2.0), firstVoxelXYZ[2] - (voxelStepZ / 2.0) }; float rowStep[3] = { 0.0, 0.0, 0.0 }; float columnStep[3] = { 0.0, 0.0, 0.0 }; int64_t numberOfRows = 0; int64_t numberOfColumns = 0; switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: CaretAssert(0); break; case VolumeSliceViewPlaneEnum::AXIAL: rowStep[1] = voxelStepY; columnStep[0] = voxelStepX; numberOfRows = numVoxelsJ; numberOfColumns = numVoxelsI; break; case VolumeSliceViewPlaneEnum::CORONAL: rowStep[2] = voxelStepZ; columnStep[0] = voxelStepX; numberOfRows = numVoxelsK; numberOfColumns = numVoxelsI; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: rowStep[2] = voxelStepZ; columnStep[1] = voxelStepY; numberOfRows = numVoxelsK; numberOfColumns = numVoxelsJ; break; } if (m_modelWholeBrain != NULL) { /* * After the a slice is drawn in ALL view, some layers * (volume surface outline) may be drawn in lines. As the * view is rotated, lines will partially appear and disappear * due to the lines having the same (extremely close) depth * values as the voxel polygons. OpenGL's Polygon Offset * only works with polygons and NOT with lines or points. * So, polygon offset cannot be used to move the depth * values for the lines and points "a little closer" to * the user. Instead, polygon offset is used to push * the underlaying slices "a little bit away" from the * user. * * Resolves WB-414 */ const float inverseSliceIndex = numberOfVolumesToDraw - iVol; //const float factor = 5.0; const float factor = inverseSliceIndex * 1.0 + 1.0; const float units = inverseSliceIndex * 1.0 + 1.0; glEnable(GL_POLYGON_OFFSET_FILL); glPolygonOffset(factor, units); } /* * Draw the voxels in the slice. */ drawOrthogonalSliceVoxels(sliceNormalVector, startCoordinate, rowStep, columnStep, numberOfColumns, numberOfRows, sliceVoxelsRgbaVector, validVoxelCount, volumeFile, iVol, mapIndex, volumeDrawingOpacity); glDisable(GL_POLYGON_OFFSET_FILL); } showBrainordinateHighlightRegionOfInterest(sliceViewPlane, sliceCoordinates, sliceNormalVector); // drawIdentificationSymbols(plane); glDisable(GL_BLEND); glShadeModel(GL_SMOOTH); } /** * Create the equation for the slice plane * * @param sliceProjectionType * Type of projection for the slice drawing (oblique, orthogonal) * @param sliceViewPlane * View plane that is displayed. * @param montageSliceIndex * Selected montage slice index * @param planeOut * OUTPUT plane of slice after transforms. */ void BrainOpenGLVolumeSliceDrawing::createSlicePlaneEquation(const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const float sliceCoordinates[3], Plane& planeOut) { /* * Default the slice normal vector to an orthogonal view */ float sliceNormalVector[3] = { 0.0, 0.0, 0.0 }; switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: case VolumeSliceViewPlaneEnum::AXIAL: sliceNormalVector[2] = 1.0; break; case VolumeSliceViewPlaneEnum::CORONAL: sliceNormalVector[1] = -1.0; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: sliceNormalVector[0] = -1.0; break; } switch (sliceProjectionType) { break; case VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_OBLIQUE: { /* * Transform the slice normal vector by the oblique rotation * matrix so that the normal vector points out of the slice */ const Matrix4x4 obliqueRotationMatrix = m_browserTabContent->getObliqueVolumeRotationMatrix(); obliqueRotationMatrix.multiplyPoint3(sliceNormalVector); MathFunctions::normalizeVector(sliceNormalVector); } break; case VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_ORTHOGONAL: break; } Plane plane(sliceNormalVector, sliceCoordinates); planeOut = plane; // CaretLogFine("Setting plane " // + VolumeSliceViewPlaneEnum::toGuiName(sliceViewPlane) // + "\n Selected Coordinate:" // + AString::number(selectedSliceCoordinate[0]) // + ", " // + AString::number(selectedSliceCoordinate[1]) // + ", " // + AString::number(selectedSliceCoordinate[2]) // + "\n Slice Plane: " // + plane.toString()); m_lookAtCenter[0] = sliceCoordinates[0]; m_lookAtCenter[1] = sliceCoordinates[1]; m_lookAtCenter[2] = sliceCoordinates[2]; } /** * Set the volume slice viewing transformation. This sets the position and * orientation of the camera. * * @param sliceProjectionType * Type of projection for the slice drawing (oblique, orthogonal) * @param sliceViewPlane * View plane that is displayed. * @param plane * Plane equation of selected slice. */ void BrainOpenGLVolumeSliceDrawing::setVolumeSliceViewingAndModelingTransformations(const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const Plane& plane) { /* * Determine model size in screen Y when viewed */ BoundingBox boundingBox; m_volumeDrawInfo[0].volumeFile->getVoxelSpaceBoundingBox(boundingBox); const double centerX = boundingBox.getCenterX(); const double centerY = boundingBox.getCenterY(); const double centerZ = boundingBox.getCenterZ(); // /* // * Set top and bottom to the min/max coordinate // * that runs vertically on the screen // */ // double modelTop = 200.0; // double modelBottom = -200.0; // switch (sliceViewPlane) { // case VolumeSliceViewPlaneEnum::ALL: // CaretAssertMessage(0, "Should never get here"); // break; // case VolumeSliceViewPlaneEnum::AXIAL: // modelTop = boundingBox.getMaxY(); // modelBottom = boundingBox.getMinY(); // break; // case VolumeSliceViewPlaneEnum::CORONAL: // modelTop = boundingBox.getMaxZ(); // modelBottom = boundingBox.getMinZ(); // break; // case VolumeSliceViewPlaneEnum::PARASAGITTAL: // modelTop = boundingBox.getMaxZ(); // modelBottom = boundingBox.getMinZ(); // break; // } /* * Initialize the modelview matrix to the identity matrix * This places the camera at the origin, pointing down the * negative-Z axis with the up vector set to (0,1,0 => * positive-Y is up). */ glMatrixMode(GL_MODELVIEW); glLoadIdentity(); /* * Translate to place center of volume at origin */ double moveToCenterX = 0.0; double moveToCenterY = 0.0; double moveToCenterZ = 0.0; switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: CaretAssertMessage(0, "Should never get here"); break; case VolumeSliceViewPlaneEnum::AXIAL: moveToCenterX = -centerX; moveToCenterY = -centerY; break; case VolumeSliceViewPlaneEnum::CORONAL: moveToCenterX = -centerX; moveToCenterY = -centerZ; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: moveToCenterX = centerY; moveToCenterY = -centerZ; break; } glTranslated(moveToCenterX, moveToCenterY, moveToCenterZ); /* * Set "look at" to origin */ m_lookAtCenter[0] = 0.0; m_lookAtCenter[1] = 0.0; m_lookAtCenter[2] = 0.0; /* * Since an orthographic projection is used, the camera only needs * to be a little bit from the center along the plane's normal vector. */ double planeNormal[3]; plane.getNormalVector(planeNormal); double cameraXYZ[3] = { m_lookAtCenter[0] + planeNormal[0] * 1.0, m_lookAtCenter[1] + planeNormal[1] * 1.0, m_lookAtCenter[2] + planeNormal[2] * 1.0 }; /* * Set the up vector which indices which way is up (screen Y) */ float up[3] = { 0.0, 0.0, 0.0 }; switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: case VolumeSliceViewPlaneEnum::AXIAL: up[1] = 1.0; break; case VolumeSliceViewPlaneEnum::CORONAL: up[2] = 1.0; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: up[2] = 1.0; break; } // /* // * For oblique viewing, the up vector needs to be rotated by the // * oblique rotation matrix. // */ // switch (sliceProjectionType) { // case VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_OBLIQUE: // m_browserTabContent->getObliqueVolumeRotationMatrix().multiplyPoint3(up); // break; // case VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_ORTHOGONAL: // break; // } /* * Now set the camera to look at the selected coordinate (center) * with the camera offset a little bit from the center. * This allows the slice's voxels to be drawn in the actual coordinates. */ gluLookAt(cameraXYZ[0], cameraXYZ[1], cameraXYZ[2], m_lookAtCenter[0], m_lookAtCenter[1], m_lookAtCenter[2], up[0], up[1], up[2]); const float* userTranslation = m_browserTabContent->getTranslation(); /* * Apply user translation */ glTranslatef(userTranslation[0], userTranslation[1], userTranslation[2]); /* * For oblique viewing, the up vector needs to be rotated by the * oblique rotation matrix. */ switch (sliceProjectionType) { case VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_OBLIQUE: { Matrix4x4 m = m_browserTabContent->getObliqueVolumeRotationMatrix(); m.invert(); double mat4[16]; m.getMatrixForOpenGL(mat4); glMultMatrixd(mat4); } break; case VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_ORTHOGONAL: break; } /* * Apply user scaling */ const float userScaling = m_browserTabContent->getScaling(); glScalef(userScaling, userScaling, userScaling); // // /* // * Move the camera with the user's translation // */ // float viewTranslationX = 0.0; // float viewTranslationY = 0.0; // float viewTranslationZ = 0.0; // // switch (sliceViewPlane) { // case VolumeSliceViewPlaneEnum::ALL: // case VolumeSliceViewPlaneEnum::AXIAL: // viewTranslationX = sliceCoordinates[0] + userTranslation[0]; // viewTranslationY = sliceCoordinates[1] + userTranslation[1]; // break; // case VolumeSliceViewPlaneEnum::CORONAL: // viewTranslationX = sliceCoordinates[0] + userTranslation[0]; // viewTranslationY = sliceCoordinates[2] + userTranslation[2]; // break; // case VolumeSliceViewPlaneEnum::PARASAGITTAL: // viewTranslationX = -(sliceCoordinates[1] + userTranslation[1]); // viewTranslationY = sliceCoordinates[2] + userTranslation[2]; // break; // } // // glTranslatef(viewTranslationX, // viewTranslationY, // viewTranslationZ); glGetDoublev(GL_MODELVIEW_MATRIX, m_viewingMatrix); } /** * Draw the layers type data. * * @param sliceDrawingType * Type of slice drawing (montage, single) * @param sliceProjectionType * Type of projection for the slice drawing (oblique, orthogonal) * @param sliceViewPlane * View plane that is displayed. * @param slicePlane * Plane of the slice. * @param sliceCoordinates * Coordinates of the selected slices. */ void BrainOpenGLVolumeSliceDrawing::drawLayers(const VolumeSliceDrawingTypeEnum::Enum sliceDrawingType, const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const Plane& slicePlane, const float sliceCoordinates[3]) { bool drawCrosshairsFlag = true; bool drawFibersFlag = true; bool drawFociFlag = true; bool drawOutlineFlag = true; if (m_modelWholeBrain != NULL) { drawCrosshairsFlag = false; drawFibersFlag = false; drawFociFlag = false; } if ( ! m_identificationModeFlag) { if (slicePlane.isValidPlane()) { /* * Disable culling so that both sides of the triangles/quads are drawn. */ GLboolean cullFaceOn = glIsEnabled(GL_CULL_FACE); glDisable(GL_CULL_FACE); glPushMatrix(); GLboolean depthBufferEnabled = false; glGetBooleanv(GL_DEPTH_TEST, &depthBufferEnabled); /* * Use some polygon offset that will adjust the depth values of the * layers so that the layers depth values place the layers in front of * the volume slice. */ glEnable(GL_POLYGON_OFFSET_FILL); glPolygonOffset(0.0, 1.0); if (drawOutlineFlag) { drawSurfaceOutline(slicePlane); } if (drawFibersFlag) { glDisable(GL_DEPTH_TEST); m_fixedPipelineDrawing->drawFiberOrientations(&slicePlane, StructureEnum::ALL); m_fixedPipelineDrawing->drawFiberTrajectories(&slicePlane, StructureEnum::ALL); if (depthBufferEnabled) { glEnable(GL_DEPTH_TEST); } else { glDisable(GL_DEPTH_TEST); } } if (drawFociFlag) { glDisable(GL_DEPTH_TEST); drawVolumeSliceFoci(slicePlane); if (depthBufferEnabled) { glEnable(GL_DEPTH_TEST); } else { glDisable(GL_DEPTH_TEST); } } glDisable(GL_POLYGON_OFFSET_FILL); if (drawCrosshairsFlag) { glPushMatrix(); drawAxesCrosshairs(sliceProjectionType, sliceDrawingType, sliceViewPlane, sliceCoordinates); glPopMatrix(); if (depthBufferEnabled) { glEnable(GL_DEPTH_TEST); } else { glDisable(GL_DEPTH_TEST); } } glPopMatrix(); if (cullFaceOn) { glEnable(GL_CULL_FACE); } } } } /** * Draw surface outlines on the volume slices * * @param plane * Plane of the volume slice on which surface outlines are drawn. */ void BrainOpenGLVolumeSliceDrawing::drawSurfaceOutline(const Plane& plane) { if ( ! plane.isValidPlane()) { return; } // CaretLogFine("\nSurface Outline Plane: " // + plane.toString()); float intersectionPoint1[3]; float intersectionPoint2[3]; m_fixedPipelineDrawing->enableLineAntiAliasing(); VolumeSurfaceOutlineSetModel* outlineSet = m_browserTabContent->getVolumeSurfaceOutlineSet(); /* * Process each surface outline */ const int32_t numberOfOutlines = outlineSet->getNumberOfDislayedVolumeSurfaceOutlines(); for (int io = 0; io < numberOfOutlines; io++) { VolumeSurfaceOutlineModel* outline = outlineSet->getVolumeSurfaceOutlineModel(io); if (outline->isDisplayed()) { Surface* surface = outline->getSurface(); if (surface != NULL) { const float thickness = outline->getThickness(); //const float lineWidth = m_fixedPipelineDrawing->modelSizeToPixelSize(thickness); int numTriangles = surface->getNumberOfTriangles(); CaretColorEnum::Enum outlineColor = CaretColorEnum::BLACK; int32_t colorSourceBrowserTabIndex = -1; VolumeSurfaceOutlineColorOrTabModel* colorOrTabModel = outline->getColorOrTabModel(); VolumeSurfaceOutlineColorOrTabModel::Item* selectedColorOrTabItem = colorOrTabModel->getSelectedItem(); switch (selectedColorOrTabItem->getItemType()) { case VolumeSurfaceOutlineColorOrTabModel::Item::ITEM_TYPE_BROWSER_TAB: colorSourceBrowserTabIndex = selectedColorOrTabItem->getBrowserTabIndex(); break; case VolumeSurfaceOutlineColorOrTabModel::Item::ITEM_TYPE_COLOR: outlineColor = selectedColorOrTabItem->getColor(); break; } const bool surfaceColorFlag = (colorSourceBrowserTabIndex >= 0); float* nodeColoringRGBA = NULL; if (surfaceColorFlag) { nodeColoringRGBA = m_fixedPipelineDrawing->surfaceNodeColoring->colorSurfaceNodes(NULL, surface, colorSourceBrowserTabIndex); } glColor3fv(CaretColorEnum::toRGB(outlineColor)); //m_fixedPipelineDrawing->setLineWidth(lineWidth); m_fixedPipelineDrawing->setLineWidth(thickness); /* * Examine each triangle to see if it intersects the Plane * in which the slice exists. */ glBegin(GL_LINES); for (int it = 0; it < numTriangles; it++) { const int32_t* triangleNodes = surface->getTriangle(it); const float* c1 = surface->getCoordinate(triangleNodes[0]); const float* c2 = surface->getCoordinate(triangleNodes[1]); const float* c3 = surface->getCoordinate(triangleNodes[2]); if (plane.triangleIntersectPlane(c1, c2, c3, intersectionPoint1, intersectionPoint2)) { if (surfaceColorFlag) { /* * Use coloring assigned to the first node in the triangle * but only if Alpha is valid (greater than zero). */ const int64_t colorIndex = triangleNodes[0] * 4; if (nodeColoringRGBA[colorIndex + 3] > 0.0) { glColor3fv(&nodeColoringRGBA[triangleNodes[0] * 4]); } else { continue; } } /* * Draw the line where the triangle intersections the slice */ glVertex3fv(intersectionPoint1); glVertex3fv(intersectionPoint2); } } glEnd(); } } } m_fixedPipelineDrawing->disableLineAntiAliasing(); } /** * Draw foci on volume slice. * * @param plane * Plane of the volume slice on which surface outlines are drawn. */ void BrainOpenGLVolumeSliceDrawing::drawVolumeSliceFoci(const Plane& plane) { SelectionItemFocusVolume* idFocus = m_brain->getSelectionManager()->getVolumeFocusIdentification(); /* * Check for a 'selection' type mode */ bool isSelect = false; switch (m_fixedPipelineDrawing->mode) { case BrainOpenGLFixedPipeline::MODE_DRAWING: break; case BrainOpenGLFixedPipeline::MODE_IDENTIFICATION: if (idFocus->isEnabledForSelection()) { isSelect = true; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } else { return; } break; case BrainOpenGLFixedPipeline::MODE_PROJECTION: return; break; } VolumeMappableInterface* underlayVolume = m_volumeDrawInfo[0].volumeFile; float minVoxelSpacing; float maxVoxelSpacing; if ( ! getMinMaxVoxelSpacing(underlayVolume, minVoxelSpacing, maxVoxelSpacing)) { return; } const float sliceThickness = maxVoxelSpacing; const float halfSliceThickness = sliceThickness * 0.5; const DisplayPropertiesFoci* fociDisplayProperties = m_brain->getDisplayPropertiesFoci(); const DisplayGroupEnum::Enum displayGroup = fociDisplayProperties->getDisplayGroupForTab(m_fixedPipelineDrawing->windowTabIndex); if (fociDisplayProperties->isDisplayed(displayGroup, m_fixedPipelineDrawing->windowTabIndex) == false) { return; } const float focusDiameter = fociDisplayProperties->getFociSize(displayGroup, m_fixedPipelineDrawing->windowTabIndex); const FeatureColoringTypeEnum::Enum fociColoringType = fociDisplayProperties->getColoringType(displayGroup, m_fixedPipelineDrawing->windowTabIndex); bool drawAsSpheres = false; switch (fociDisplayProperties->getDrawingType(displayGroup, m_fixedPipelineDrawing->windowTabIndex)) { case FociDrawingTypeEnum::DRAW_AS_SPHERES: drawAsSpheres = true; break; case FociDrawingTypeEnum::DRAW_AS_SQUARES: break; } /* * Process each foci file */ const int32_t numberOfFociFiles = m_brain->getNumberOfFociFiles(); for (int32_t iFile = 0; iFile < numberOfFociFiles; iFile++) { FociFile* fociFile = m_brain->getFociFile(iFile); const GroupAndNameHierarchyModel* classAndNameSelection = fociFile->getGroupAndNameHierarchyModel(); if (classAndNameSelection->isSelected(displayGroup, m_fixedPipelineDrawing->windowTabIndex) == false) { continue; } const GiftiLabelTable* classColorTable = fociFile->getClassColorTable(); const GiftiLabelTable* nameColorTable = fociFile->getNameColorTable(); const int32_t numFoci = fociFile->getNumberOfFoci(); for (int32_t j = 0; j < numFoci; j++) { Focus* focus = fociFile->getFocus(j); const GroupAndNameHierarchyItem* groupNameItem = focus->getGroupNameSelectionItem(); if (groupNameItem != NULL) { if (groupNameItem->isSelected(displayGroup, m_fixedPipelineDrawing->windowTabIndex) == false) { continue; } } float rgba[4] = { 0.0, 0.0, 0.0, 1.0 }; switch (fociColoringType) { case FeatureColoringTypeEnum::FEATURE_COLORING_TYPE_CLASS: if (focus->isClassRgbaValid() == false) { const GiftiLabel* colorLabel = classColorTable->getLabelBestMatching(focus->getClassName()); if (colorLabel != NULL) { colorLabel->getColor(rgba); focus->setClassRgba(rgba); } else { focus->setClassRgba(rgba); } } focus->getClassRgba(rgba); break; case FeatureColoringTypeEnum::FEATURE_COLORING_TYPE_NAME: if (focus->isNameRgbaValid() == false) { const GiftiLabel* colorLabel = nameColorTable->getLabelBestMatching(focus->getName()); if (colorLabel != NULL) { colorLabel->getColor(rgba); focus->setNameRgba(rgba); } else { focus->setNameRgba(rgba); } } focus->getNameRgba(rgba); break; } const int32_t numProjections = focus->getNumberOfProjections(); for (int32_t k = 0; k < numProjections; k++) { const SurfaceProjectedItem* spi = focus->getProjection(k); if (spi->isVolumeXYZValid()) { float xyz[3]; spi->getVolumeXYZ(xyz); bool drawIt = false; if (plane.absoluteDistanceToPlane(xyz) < halfSliceThickness) { drawIt = true; } if (drawIt) { glPushMatrix(); glTranslatef(xyz[0], xyz[1], xyz[2]); if (isSelect) { uint8_t idRGBA[4]; m_fixedPipelineDrawing->colorIdentification->addItem(idRGBA, SelectionItemDataTypeEnum::FOCUS_VOLUME, iFile, // file index j, // focus index k);// projection index idRGBA[3] = 255; if (drawAsSpheres) { m_fixedPipelineDrawing->drawSphereWithDiameter(idRGBA, focusDiameter); } else { glColor4ubv(idRGBA); drawSquare(focusDiameter); } } else { if (drawAsSpheres) { m_fixedPipelineDrawing->drawSphereWithDiameter(rgba, focusDiameter); } else { glColor3fv(rgba); drawSquare(focusDiameter); } } glPopMatrix(); } } } } } if (isSelect) { int32_t fociFileIndex = -1; int32_t focusIndex = -1; int32_t focusProjectionIndex = -1; float depth = -1.0; m_fixedPipelineDrawing->getIndexFromColorSelection(SelectionItemDataTypeEnum::FOCUS_VOLUME, m_fixedPipelineDrawing->mouseX, m_fixedPipelineDrawing->mouseY, fociFileIndex, focusIndex, focusProjectionIndex, depth); if (fociFileIndex >= 0) { if (idFocus->isOtherScreenDepthCloserToViewer(depth)) { Focus* focus = m_brain->getFociFile(fociFileIndex)->getFocus(focusIndex); idFocus->setBrain(m_brain); idFocus->setFocus(focus); idFocus->setFociFile(m_brain->getFociFile(fociFileIndex)); idFocus->setFocusIndex(focusIndex); idFocus->setFocusProjectionIndex(focusProjectionIndex); idFocus->setVolumeFile(underlayVolume); idFocus->setScreenDepth(depth); float xyz[3]; const SurfaceProjectedItem* spi = focus->getProjection(focusProjectionIndex); spi->getVolumeXYZ(xyz); m_fixedPipelineDrawing->setSelectedItemScreenXYZ(idFocus, xyz); CaretLogFine("Selected Volume Focus Identification Symbol: " + QString::number(focusIndex)); } } } } /** * Draw the axes crosshairs. * * @param sliceProjectionType * Type of projection for the slice drawing (oblique, orthogonal) * @param sliceDrawingType * Type of slice drawing (montage, single) * @param sliceViewPlane * View plane that is displayed. * @param sliceCoordinates * Coordinates of the selected slices. */ void BrainOpenGLVolumeSliceDrawing::drawAxesCrosshairs(const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const VolumeSliceDrawingTypeEnum::Enum sliceDrawingType, const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const float sliceCoordinates[3]) { CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); const bool drawCrosshairsFlag = prefs->isVolumeAxesCrosshairsDisplayed(); bool drawCrosshairLabelsFlag = prefs->isVolumeAxesLabelsDisplayed(); switch (sliceDrawingType) { case VolumeSliceDrawingTypeEnum::VOLUME_SLICE_DRAW_MONTAGE: drawCrosshairLabelsFlag = false; break; case VolumeSliceDrawingTypeEnum::VOLUME_SLICE_DRAW_SINGLE: break; } switch (sliceProjectionType) { case VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_ORTHOGONAL: drawAxesCrosshairsOrthoAndOblique(sliceProjectionType, sliceViewPlane, sliceCoordinates, drawCrosshairsFlag, drawCrosshairLabelsFlag); break; case VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_OBLIQUE: { glPushMatrix(); glLoadIdentity(); double mv[16]; glGetDoublev(GL_MODELVIEW_MATRIX, mv); Matrix4x4 mvm; mvm.setMatrixFromOpenGL(mv); float trans[3]; m_browserTabContent->getTranslation(trans); glTranslatef(trans[0], trans[1], trans[2]); drawAxesCrosshairsOrthoAndOblique(sliceProjectionType, sliceViewPlane, sliceCoordinates, drawCrosshairsFlag, drawCrosshairLabelsFlag); glPopMatrix(); } break; } } /** * Draw the axes crosshairs for an orthogonal slice. * * @param sliceProjectionType * Type of projection for the slice drawing (oblique, orthogonal) * @param sliceViewPlane * The slice plane view. * @param sliceCoordinates * Coordinates of the selected slices. * @param drawCrosshairsFlag * If true, draw the crosshairs. * @param drawCrosshairLabelsFlag * If true, draw the crosshair labels. */ void BrainOpenGLVolumeSliceDrawing::drawAxesCrosshairsOrthoAndOblique(const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const float sliceCoordinates[3], const bool drawCrosshairsFlag, const bool drawCrosshairLabelsFlag) { bool obliqueModeFlag = false; switch (sliceProjectionType) { case VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_OBLIQUE: obliqueModeFlag = true; break; case VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_ORTHOGONAL: break; } GLboolean depthEnabled = GL_FALSE; glGetBooleanv(GL_DEPTH_TEST, &depthEnabled); glDisable(GL_DEPTH_TEST); const float bigValue = 10000.0; float horizontalAxisStartXYZ[3] = { sliceCoordinates[0], sliceCoordinates[1], sliceCoordinates[2] }; float horizontalAxisEndXYZ[3] = { sliceCoordinates[0], sliceCoordinates[1], sliceCoordinates[2] }; float verticalAxisStartXYZ[3] = { sliceCoordinates[0], sliceCoordinates[1], sliceCoordinates[2] }; float verticalAxisEndXYZ[3] = { sliceCoordinates[0], sliceCoordinates[1], sliceCoordinates[2] }; if (obliqueModeFlag) { switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: break; case VolumeSliceViewPlaneEnum::AXIAL: break; case VolumeSliceViewPlaneEnum::CORONAL: horizontalAxisStartXYZ[0] = sliceCoordinates[0]; horizontalAxisStartXYZ[1] = sliceCoordinates[2]; horizontalAxisStartXYZ[2] = sliceCoordinates[1]; horizontalAxisEndXYZ[0] = sliceCoordinates[0]; horizontalAxisEndXYZ[1] = sliceCoordinates[2]; horizontalAxisEndXYZ[2] = sliceCoordinates[1]; verticalAxisStartXYZ[0] = sliceCoordinates[0]; verticalAxisStartXYZ[1] = sliceCoordinates[1]; verticalAxisStartXYZ[2] = sliceCoordinates[2]; verticalAxisEndXYZ[0] = sliceCoordinates[0]; verticalAxisEndXYZ[1] = sliceCoordinates[1]; verticalAxisEndXYZ[2] = sliceCoordinates[2]; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: horizontalAxisStartXYZ[0] = sliceCoordinates[1]; horizontalAxisStartXYZ[1] = sliceCoordinates[2]; horizontalAxisStartXYZ[2] = sliceCoordinates[0]; horizontalAxisEndXYZ[0] = sliceCoordinates[1]; horizontalAxisEndXYZ[1] = sliceCoordinates[2]; horizontalAxisEndXYZ[2] = sliceCoordinates[0]; verticalAxisStartXYZ[0] = -sliceCoordinates[1]; verticalAxisStartXYZ[1] = sliceCoordinates[0]; verticalAxisStartXYZ[2] = sliceCoordinates[2]; verticalAxisEndXYZ[0] = -sliceCoordinates[1]; verticalAxisEndXYZ[1] = sliceCoordinates[0]; verticalAxisEndXYZ[2] = sliceCoordinates[2]; break; } } float axialRGBA[4]; getAxesColor(VolumeSliceViewPlaneEnum::AXIAL, axialRGBA); float coronalRGBA[4]; getAxesColor(VolumeSliceViewPlaneEnum::CORONAL, coronalRGBA); float paraRGBA[4]; getAxesColor(VolumeSliceViewPlaneEnum::PARASAGITTAL, paraRGBA); AString horizontalLeftText = ""; AString horizontalRightText = ""; AString verticalBottomText = ""; AString verticalTopText = ""; float* horizontalAxisRGBA = axialRGBA; float* verticalAxisRGBA = axialRGBA; switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: break; case VolumeSliceViewPlaneEnum::AXIAL: horizontalLeftText = "L"; horizontalRightText = "R"; horizontalAxisRGBA = coronalRGBA; horizontalAxisStartXYZ[0] -= bigValue; horizontalAxisEndXYZ[0] += bigValue; verticalBottomText = "P"; verticalTopText = "A"; verticalAxisRGBA = paraRGBA; verticalAxisStartXYZ[1] -= bigValue; verticalAxisEndXYZ[1] += bigValue; break; case VolumeSliceViewPlaneEnum::CORONAL: horizontalLeftText = "L"; horizontalRightText = "R"; horizontalAxisRGBA = axialRGBA; if (obliqueModeFlag) { horizontalAxisStartXYZ[0] -= bigValue; horizontalAxisEndXYZ[0] += bigValue; } else { horizontalAxisStartXYZ[0] -= bigValue; horizontalAxisEndXYZ[0] += bigValue; } verticalBottomText = "I"; verticalTopText = "S"; verticalAxisRGBA = paraRGBA; if (obliqueModeFlag) { verticalAxisStartXYZ[1] -= bigValue; verticalAxisEndXYZ[1] += bigValue; } else { verticalAxisStartXYZ[2] -= bigValue; verticalAxisEndXYZ[2] += bigValue; } break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: horizontalLeftText = "A"; horizontalRightText = "P"; horizontalAxisRGBA = axialRGBA; if (obliqueModeFlag) { horizontalAxisStartXYZ[0] -= bigValue; horizontalAxisEndXYZ[0] += bigValue; } else { horizontalAxisStartXYZ[1] -= bigValue; horizontalAxisEndXYZ[1] += bigValue; } verticalBottomText = "I"; verticalTopText = "S"; verticalAxisRGBA = coronalRGBA; if (obliqueModeFlag) { verticalAxisStartXYZ[1] -= bigValue; verticalAxisEndXYZ[1] += bigValue; } else { verticalAxisStartXYZ[2] -= bigValue; verticalAxisEndXYZ[2] += bigValue; } break; } GLint viewport[4]; glGetIntegerv(GL_VIEWPORT, viewport); const int textOffset = 15; const int textLeftWindowXY[2] = { textOffset, (viewport[3] / 2) }; const int textRightWindowXY[2] = { viewport[2] - textOffset, (viewport[3] / 2) }; const int textBottomWindowXY[2] = { viewport[2] / 2, textOffset }; const int textTopWindowXY[2] = { (viewport[2] / 2), viewport[3] - textOffset }; /* * Crosshairs */ if (drawCrosshairsFlag) { glLineWidth(1.0); glColor3fv(horizontalAxisRGBA); glBegin(GL_LINES); glVertex3fv(horizontalAxisStartXYZ); glVertex3fv(horizontalAxisEndXYZ); glEnd(); glLineWidth(1.0); glColor3fv(verticalAxisRGBA); glBegin(GL_LINES); glVertex3fv(verticalAxisStartXYZ); glVertex3fv(verticalAxisEndXYZ); glEnd(); } if (drawCrosshairLabelsFlag) { const int fontHeight = 18; const int textCenter[2] = { textLeftWindowXY[0], textLeftWindowXY[1] }; const int halfFontSize = fontHeight / 2; uint8_t backgroundRGBA[4] = { m_fixedPipelineDrawing->m_backgroundColorByte[0], m_fixedPipelineDrawing->m_backgroundColorByte[1], m_fixedPipelineDrawing->m_backgroundColorByte[2], m_fixedPipelineDrawing->m_backgroundColorByte[3] }; GLint savedViewport[4]; glGetIntegerv(GL_VIEWPORT, savedViewport); int vpLeftX = savedViewport[0] + textCenter[0] - halfFontSize; int vpRightX = savedViewport[0] + textCenter[0] + halfFontSize; int vpBottomY = savedViewport[1] + textCenter[1] - halfFontSize; int vpTopY = savedViewport[1] + textCenter[1] + halfFontSize; MathFunctions::limitRange(vpLeftX, savedViewport[0], savedViewport[0] + savedViewport[2]); MathFunctions::limitRange(vpRightX, savedViewport[0], savedViewport[0] + savedViewport[2]); MathFunctions::limitRange(vpBottomY, savedViewport[1], savedViewport[1] + savedViewport[3]); MathFunctions::limitRange(vpTopY, savedViewport[1], savedViewport[1] + savedViewport[3]); const int vpSizeX = vpRightX - vpLeftX; const int vpSizeY = vpTopY - vpBottomY; glViewport(vpLeftX, vpBottomY, vpSizeX, vpSizeY); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); std::vector rgba; std::vector coords, normals; coords.push_back(-1.0); coords.push_back(-1.0); coords.push_back( 0.0); normals.push_back(0.0); normals.push_back(0.0); normals.push_back(1.0); rgba.push_back(backgroundRGBA[0]); rgba.push_back(backgroundRGBA[1]); rgba.push_back(backgroundRGBA[2]); rgba.push_back(backgroundRGBA[3]); coords.push_back( 1.0); coords.push_back(-1.0); coords.push_back( 0.0); normals.push_back(0.0); normals.push_back(0.0); normals.push_back(1.0); rgba.push_back(backgroundRGBA[0]); rgba.push_back(backgroundRGBA[1]); rgba.push_back(backgroundRGBA[2]); rgba.push_back(backgroundRGBA[3]); coords.push_back( 1.0); coords.push_back( 1.0); coords.push_back( 0.0); normals.push_back(0.0); normals.push_back(0.0); normals.push_back(1.0); rgba.push_back(backgroundRGBA[0]); rgba.push_back(backgroundRGBA[1]); rgba.push_back(backgroundRGBA[2]); rgba.push_back(backgroundRGBA[3]); coords.push_back(-1.0); coords.push_back( 1.0); coords.push_back( 0.0); normals.push_back(0.0); normals.push_back(0.0); normals.push_back(1.0); rgba.push_back(backgroundRGBA[0]); rgba.push_back(backgroundRGBA[1]); rgba.push_back(backgroundRGBA[2]); rgba.push_back(backgroundRGBA[3]); BrainOpenGLPrimitiveDrawing::drawQuads(coords, normals, rgba); glPopMatrix(); glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glViewport(savedViewport[0], savedViewport[1], savedViewport[2], savedViewport[3]); glColor4fv(horizontalAxisRGBA); m_fixedPipelineDrawing->drawTextWindowCoordsWithBackground(textLeftWindowXY[0], textLeftWindowXY[1], horizontalLeftText, BrainOpenGLTextRenderInterface::X_CENTER, BrainOpenGLTextRenderInterface::Y_CENTER, BrainOpenGLTextRenderInterface::BOLD, fontHeight); m_fixedPipelineDrawing->drawTextWindowCoordsWithBackground(textRightWindowXY[0], textRightWindowXY[1], horizontalRightText, BrainOpenGLTextRenderInterface::X_CENTER, BrainOpenGLTextRenderInterface::Y_CENTER, BrainOpenGLTextRenderInterface::BOLD, fontHeight); glColor4fv(verticalAxisRGBA); m_fixedPipelineDrawing->drawTextWindowCoordsWithBackground(textBottomWindowXY[0], textBottomWindowXY[1], verticalBottomText, BrainOpenGLTextRenderInterface::X_CENTER, BrainOpenGLTextRenderInterface::Y_CENTER, BrainOpenGLTextRenderInterface::BOLD, fontHeight); m_fixedPipelineDrawing->drawTextWindowCoordsWithBackground(textTopWindowXY[0], textTopWindowXY[1], verticalTopText, BrainOpenGLTextRenderInterface::X_CENTER, BrainOpenGLTextRenderInterface::Y_CENTER, BrainOpenGLTextRenderInterface::BOLD, fontHeight); } if (depthEnabled) { glEnable(GL_DEPTH_TEST); } } /** * Get the RGBA coloring for a slice view plane. * * @param sliceViewPlane * The slice view plane. * @param rgbaOut * Output colors ranging 0.0 to 1.0 */ void BrainOpenGLVolumeSliceDrawing::getAxesColor(const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, float rgbaOut[4]) const { switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: CaretAssert(0); break; case VolumeSliceViewPlaneEnum::AXIAL: rgbaOut[0] = 0.0; rgbaOut[1] = 0.0; rgbaOut[2] = 1.0; rgbaOut[3] = 1.0; break; case VolumeSliceViewPlaneEnum::CORONAL: rgbaOut[0] = 0.0; rgbaOut[1] = 1.0; rgbaOut[2] = 0.0; rgbaOut[3] = 1.0; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: rgbaOut[0] = 1.0; rgbaOut[1] = 0.0; rgbaOut[2] = 0.0; rgbaOut[3] = 1.0; break; } } /** * Draw a one millimeter square facing the user. * NOTE: This method will alter the current * modelviewing matrices so caller may need * to enclose the call to this method within * glPushMatrix() and glPopMatrix(). * * @param size * Size of square. */ void BrainOpenGLVolumeSliceDrawing::drawSquare(const float size) { const float length = size * 0.5; /* * Draw both front and back side since in some instances, * such as surface montage, we are viweing from the far * side (from back of monitor) */ glBegin(GL_QUADS); glNormal3f(0.0, 0.0, 1.0); glVertex3f(-length, -length, 0.0); glVertex3f( length, -length, 0.0); glVertex3f( length, length, 0.0); glVertex3f(-length, length, 0.0); glNormal3f(0.0, 0.0, -1.0); glVertex3f(-length, -length, 0.0); glVertex3f(-length, length, 0.0); glVertex3f( length, length, 0.0); glVertex3f( length, -length, 0.0); glEnd(); } /** * Get the minimum and maximum distance between adjacent voxels in all * slices planes. Output spacing value are always non-negative even if * a right-to-left orientation. * * @param volume * Volume for which min/max spacing is requested. * @param minSpacingOut * Output minimum spacing. * @param maxSpacingOut * Output maximum spacing. * @return * True if min and max spacing are greater than zero. */ bool BrainOpenGLVolumeSliceDrawing::getMinMaxVoxelSpacing(const VolumeMappableInterface* volume, float& minSpacingOut, float& maxSpacingOut) const { CaretAssert(volume); float originX, originY, originZ; float x1, y1, z1; volume->indexToSpace(0, 0, 0, originX, originY, originZ); volume->indexToSpace(1, 1, 1, x1, y1, z1); const float dx = std::fabs(x1 - originX); const float dy = std::fabs(y1 - originY); const float dz = std::fabs(z1 - originZ); minSpacingOut = std::min(std::min(dx, dy), dz); maxSpacingOut = std::max(std::max(dx, dy), dz); if ((minSpacingOut > 0.0) && (maxSpacingOut > 0.0)) { return true; } return false; } /** * Draw orientation axes * * @param viewport * The viewport region for the orientation axes. */ void BrainOpenGLVolumeSliceDrawing::drawOrientationAxes(const int viewport[4]) { CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); const bool drawCylindersFlag = prefs->isVolumeAxesCrosshairsDisplayed(); const bool drawLabelsFlag = prefs->isVolumeAxesLabelsDisplayed(); /* * Set the viewport */ glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); const double viewportWidth = viewport[2]; const double viewportHeight = viewport[3]; /* * Determine bounds for orthographic projection */ const double maxCoord = 100.0; const double minCoord = -maxCoord; double left = 0.0; double right = 0.0; double top = 0.0; double bottom = 0.0; const double nearDepth = -1000.0; const double farDepth = 1000.0; if (viewportHeight > viewportWidth) { left = minCoord; right = maxCoord; const double aspectRatio = (viewportHeight / viewportWidth); top = maxCoord * aspectRatio; bottom = minCoord * aspectRatio; } else { const double aspectRatio = (viewportWidth / viewportHeight); top = maxCoord; bottom = minCoord; left = minCoord * aspectRatio; right = maxCoord * aspectRatio; } /* * Set the orthographic projection */ glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glOrtho(left, right, bottom, top, nearDepth, farDepth); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); { /* * Set the viewing transformation, places 'eye' so that it looks * at the 'model' which is, in this case, the axes */ double eyeX = 0.0; double eyeY = 0.0; double eyeZ = 100.0; const double centerX = 0; const double centerY = 0; const double centerZ = 0; const double upX = 0; const double upY = 1; const double upZ = 0; gluLookAt(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ); /* * Set the modeling transformation */ const Matrix4x4 obliqueRotationMatrix = m_browserTabContent->getObliqueVolumeRotationMatrix(); double rotationMatrix[16]; obliqueRotationMatrix.getMatrixForOpenGL(rotationMatrix); glMultMatrixd(rotationMatrix); /* * Disable depth buffer. Otherwise, when volume slices are drawn * black regions of the slices may set depth buffer and the occlude * the axes from display. */ GLboolean depthBufferEnabled = false; glGetBooleanv(GL_DEPTH_TEST, &depthBufferEnabled); glDisable(GL_DEPTH_TEST); const float red[4] = { 1.0, 0.0, 0.0, 1.0 }; const float green[4] = { 0.0, 1.0, 0.0, 1.0 }; const float blue[4] = { 0.0, 0.0, 1.0, 1.0 }; const double axisMaxCoord = maxCoord * 0.8; const double axisMinCoord = -axisMaxCoord; const double textMaxCoord = maxCoord * 0.9; const double textMinCoord = -textMaxCoord; const float axialPlaneMin[3] = { 0.0, 0.0, axisMinCoord }; const float axialPlaneMax[3] = { 0.0, 0.0, axisMaxCoord }; const double axialTextMin[3] = { 0.0, 0.0, textMinCoord }; const double axialTextMax[3] = { 0.0, 0.0, textMaxCoord }; const float coronalPlaneMin[3] = { axisMinCoord, 0.0, 0.0 }; const float coronalPlaneMax[3] = { axisMaxCoord, 0.0, 0.0 }; const double coronalTextMin[3] = { textMinCoord, 0.0, 0.0 }; const double coronalTextMax[3] = { textMaxCoord, 0.0, 0.0 }; const float paraPlaneMin[3] = { 0.0, axisMinCoord, 0.0 }; const float paraPlaneMax[3] = { 0.0, axisMaxCoord, 0.0 }; const double paraTextMin[3] = { 0.0, textMinCoord, 0.0 }; const double paraTextMax[3] = { 0.0, textMaxCoord, 0.0 }; const float axesCrosshairRadius = 1.0; if (drawCylindersFlag) { m_fixedPipelineDrawing->drawCylinder(blue, axialPlaneMin, axialPlaneMax, axesCrosshairRadius); } if (drawLabelsFlag) { glColor3fv(blue); m_fixedPipelineDrawing->drawTextModelCoords(axialTextMin, "I"); m_fixedPipelineDrawing->drawTextModelCoords(axialTextMax, "S"); } if (drawCylindersFlag) { m_fixedPipelineDrawing->drawCylinder(green, coronalPlaneMin, coronalPlaneMax, axesCrosshairRadius); } if (drawLabelsFlag) { glColor3fv(green); m_fixedPipelineDrawing->drawTextModelCoords(coronalTextMin, "L"); m_fixedPipelineDrawing->drawTextModelCoords(coronalTextMax, "R"); } if (drawCylindersFlag) { m_fixedPipelineDrawing->drawCylinder(red, paraPlaneMin, paraPlaneMax, axesCrosshairRadius); } if (drawLabelsFlag) { glColor3fv(red); m_fixedPipelineDrawing->drawTextModelCoords(paraTextMin, "P"); m_fixedPipelineDrawing->drawTextModelCoords(paraTextMax, "A"); } } glPopMatrix(); glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); } /** * Set the orthographic projection. * * @param sliceViewPlane * View plane that is displayed. * @param viewport * The viewport. */ void BrainOpenGLVolumeSliceDrawing::setOrthographicProjection(const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const int viewport[4]) { /* * Determine model size in screen Y when viewed */ BoundingBox boundingBox; m_volumeDrawInfo[0].volumeFile->getVoxelSpaceBoundingBox(boundingBox); /* * Set top and bottom to the min/max coordinate * that runs vertically on the screen */ double modelTop = 200.0; double modelBottom = -200.0; switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: CaretAssertMessage(0, "Should never get here"); break; case VolumeSliceViewPlaneEnum::AXIAL: modelTop = boundingBox.getMaxY(); modelBottom = boundingBox.getMinY(); break; case VolumeSliceViewPlaneEnum::CORONAL: modelTop = boundingBox.getMaxZ(); modelBottom = boundingBox.getMinZ(); break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: modelTop = boundingBox.getMaxZ(); modelBottom = boundingBox.getMinZ(); break; } /* * Scale ratio makes region slightly larger than model */ const double zoom = 1.0; //m_browserTabContent->getScaling(); double scaleRatio = (1.0 / 0.98); if (zoom > 0.0) { scaleRatio /= zoom; } modelTop *= scaleRatio; modelBottom *= scaleRatio; /* * Determine aspect ratio of viewport */ const double viewportWidth = viewport[2]; const double viewportHeight = viewport[3]; const double aspectRatio = (viewportWidth / viewportHeight); /* * Set bounds of orthographic projection */ const double halfModelY = ((modelTop - modelBottom) / 2.0); const double orthoBottom = -halfModelY; const double orthoTop = halfModelY; const double orthoLeft = -halfModelY * aspectRatio; const double orthoRight = halfModelY * aspectRatio; const double nearDepth = -1000.0; const double farDepth = 1000.0; m_orthographicBounds[0] = orthoLeft; m_orthographicBounds[1] = orthoRight; m_orthographicBounds[2] = orthoBottom; m_orthographicBounds[3] = orthoTop; m_orthographicBounds[4] = nearDepth; m_orthographicBounds[5] = farDepth; glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(m_orthographicBounds[0], m_orthographicBounds[1], m_orthographicBounds[2], m_orthographicBounds[3], m_orthographicBounds[4], m_orthographicBounds[5]); glMatrixMode(GL_MODELVIEW); // CaretLogFine("Orthographic Bounds: " // + AString::fromNumbers(m_orthographicBounds, 6, ",")); } /** * Draw the voxels in an orthogonal slice. * * @param sliceNormalVector * Normal vector of the slice plane. * @param coordinate * Coordinate of first voxel in the slice (bottom left as begin viewed) * @param rowStep * Three-dimensional step to next row. * @param columnStep * Three-dimensional step to next column. * @param numberOfColumns * Number of columns in the slice. * @param numberOfRows * Number of rows in the slice. * @param sliceRGBA * RGBA coloring for voxels in the slice. * @param validVoxelCount * Number of voxels with valid coloring * @param volumeInterface * Index of the volume being drawn. * @param volumeIndex * Selected map in the volume being drawn. * @param mapIndex * Selected map in the volume being drawn. * @param sliceOpacity * Opacity from the overlay. */ void BrainOpenGLVolumeSliceDrawing::drawOrthogonalSliceVoxels(const float sliceNormalVector[3], const float coordinate[3], const float rowStep[3], const float columnStep[3], const int64_t numberOfColumns, const int64_t numberOfRows, const std::vector& sliceRGBA, const int64_t validVoxelCount, const VolumeMappableInterface* volumeInterface, const int32_t volumeIndex, const int32_t mapIndex, const uint8_t sliceOpacity) { /* * There are two ways to draw the voxels. * * Quad Indices: This method submits each vertex (coordinate, normal, rgba) * one time BUT it submits EVERY vertex in the slice. Note that the vertex * is shared by four voxels (except along an edge). For each valid voxel, * it submits the indices of the voxel's four vertices. This method is * efficient when many voxels are drawn since each vertex is submitted to * OpenGL one time and is shared by up to four voxels. However, when * only a few voxels are drawn, many unused vertices are submitted. * * Single Quads: For each voxel, this method submits the four vertices * (coordinate, normal, rgba) for each voxel drawn. Even though adjacent * voxels share vertices, the vertices are submitted for each voxel. When * only a small number of voxels in the slice are drawn, this method is * efficient since only the needed vertex data is submitted. However, * when many voxels are drawn, many vertices are duplicated making the * drawing inefficient. * * So, estimate the bytes that are passed to OpenGL for each drawing * mode and choose the drawing mode that requires the smallest number * of bytes. */ /* * Each vertex requires 28 bytes * (3 float xyz, 3 float normal xyz + 4 bytes color). * * Single quads uses four vertices per quad. * * Index quads requires four 4-byte ints for the quad's indices. */ const int64_t bytesPerVertex = 28; const int64_t totalVertexBytes = ((numberOfColumns + 1) * (numberOfRows + 1) * bytesPerVertex); const int64_t singleQuadBytes = (validVoxelCount * bytesPerVertex * 4); const int64_t indexQuadBytes = (totalVertexBytes + (16 * validVoxelCount)); bool drawWithQuadIndicesFlag = false; if (indexQuadBytes < singleQuadBytes) { drawWithQuadIndicesFlag = true; } if (drawWithQuadIndicesFlag) { drawOrthogonalSliceVoxelsQuadIndicesAndStrips(sliceNormalVector, coordinate, rowStep, columnStep, numberOfColumns, numberOfRows, sliceRGBA, volumeInterface, volumeIndex, mapIndex, sliceOpacity); } else { drawOrthogonalSliceVoxelsSingleQuads(sliceNormalVector, coordinate, rowStep, columnStep, numberOfColumns, numberOfRows, sliceRGBA, volumeInterface, volumeIndex, mapIndex, sliceOpacity); } } /** * Draw the voxels in an orthogonal slice with single quads. * * Four coordinates, normals, and colors are sent to OpenGL for each * quad that is drawn. This may be efficent when only a few voxels * are drawn but very inefficient when many voxels are drawn. * * @param sliceNormalVector * Normal vector of the slice plane. * @param coordinate * Coordinate of first voxel in the slice (bottom left as begin viewed) * @param rowStep * Three-dimensional step to next row. * @param columnStep * Three-dimensional step to next column. * @param numberOfColumns * Number of columns in the slice. * @param numberOfRows * Number of rows in the slice. * @param sliceRGBA * RGBA coloring for voxels in the slice. * @param volumeInterface * Index of the volume being drawn. * @param volumeIndex * Selected map in the volume being drawn. * @param mapIndex * Selected map in the volume being drawn. * @param sliceOpacity * Opacity from the overlay. */ void BrainOpenGLVolumeSliceDrawing::drawOrthogonalSliceVoxelsSingleQuads(const float sliceNormalVector[3], const float coordinate[3], const float rowStep[3], const float columnStep[3], const int64_t numberOfColumns, const int64_t numberOfRows, const std::vector& sliceRGBA, const VolumeMappableInterface* volumeInterface, const int32_t volumeIndex, const int32_t mapIndex, const uint8_t sliceOpacity) { /* * When performing voxel identification for editing voxels, * we need to draw EVERY voxel since the user may click * regions where the voxels are "off". */ bool volumeEditingDrawAllVoxelsFlag = false; if (m_identificationModeFlag) { SelectionItemVoxelEditing* voxelEditID = m_brain->getSelectionManager()->getVoxelEditingIdentification(); if (voxelEditID->isEnabledForSelection()) { const VolumeFile* vf = dynamic_cast(volumeInterface); if (vf == voxelEditID->getVolumeFileForEditing()) { volumeEditingDrawAllVoxelsFlag = true; } } } const int64_t numVoxelsInSlice = numberOfColumns * numberOfRows; /* * Allocate for quadrilateral drawing */ const int64_t numQuadCoords = numVoxelsInSlice * 12; const int64_t numQuadRgba = numVoxelsInSlice * 16; std::vector voxelQuadCoordinates; std::vector voxelQuadNormals; std::vector voxelQuadRgba; voxelQuadCoordinates.reserve(numQuadCoords); voxelQuadNormals.reserve(numQuadCoords); voxelQuadRgba.reserve(numQuadRgba); /* * Step to next row or column voxel */ const float rowStepX = rowStep[0]; const float rowStepY = rowStep[1]; const float rowStepZ = rowStep[2]; const float columnStepX = columnStep[0]; const float columnStepY = columnStep[1]; const float columnStepZ = columnStep[2]; /* * Draw each row */ for (int64_t jRow = 0; jRow < numberOfRows; jRow++) { /* * Coordinates on left side of row */ float rowBottomLeft[3] = { coordinate[0] + (jRow * rowStepX), coordinate[1] + (jRow * rowStepY), coordinate[2] + (jRow * rowStepZ) }; float rowTopLeft[3] = { rowBottomLeft[0] + rowStepX, rowBottomLeft[1] + rowStepY, rowBottomLeft[2] + rowStepZ }; /* * Draw each voxel in its column */ for (int64_t iCol = 0; iCol < numberOfColumns; iCol++) { /* * Offset of voxel in coloring. */ const int64_t sliceRgbaOffset = (4 * (iCol + (numberOfColumns * jRow))); const int64_t alphaOffset = sliceRgbaOffset + 3; uint8_t rgba[4] = { 0, 0, 0, 0 }; /* * Negative alpha means do not display */ CaretAssertVectorIndex(sliceRGBA, alphaOffset); if (sliceRGBA[alphaOffset] <= 0) { if (volumeIndex == 0) { /* * For first drawn volume, use an alpha of 255 so * so that black is drawn */ rgba[3] = 255; /* * OVERRIDES BLACK VOXEL ABOVE (255) * For first drawn volume, use an alpha of zero so * that the background shows through */ rgba[3] = 0; //255; } } else { /* * Use overlay's opacity */ rgba[0] = sliceRGBA[sliceRgbaOffset]; rgba[1] = sliceRGBA[sliceRgbaOffset + 1]; rgba[2] = sliceRGBA[sliceRgbaOffset + 2]; rgba[3] = sliceOpacity; } /* * Set coordinates of voxel corners */ float voxelBottomLeft[3] = { rowBottomLeft[0] + (iCol * columnStepX), rowBottomLeft[1] + (iCol * columnStepY), rowBottomLeft[2] + (iCol * columnStepZ) }; float voxelBottomRight[3] = { voxelBottomLeft[0] + columnStepX, voxelBottomLeft[1] + columnStepY, voxelBottomLeft[2] + columnStepZ }; float voxelTopLeft[3] = { rowTopLeft[0] + (iCol * columnStepX), rowTopLeft[1] + (iCol * columnStepY), rowTopLeft[2] + (iCol * columnStepZ) }; float voxelTopRight[3] = { voxelTopLeft[0] + columnStepX, voxelTopLeft[1] + columnStepY, voxelTopLeft[2] + columnStepZ }; /* * Need to draw ALL voxels if performing * identificaiton for voxel editing. */ if (volumeEditingDrawAllVoxelsFlag) { rgba[3] = 255; } /* * Draw voxel based upon opacity */ if (rgba[3] > 0) { if (m_identificationModeFlag) { /* * Add info about voxel for identication. */ int64_t voxelI = 0; int64_t voxelJ = 0; int64_t voxelK = 0; const float voxelCenterX = (voxelBottomLeft[0] + voxelTopRight[0]) / 2.0; const float voxelCenterY = (voxelBottomLeft[1] + voxelTopRight[1]) / 2.0; const float voxelCenterZ = (voxelBottomLeft[2] + voxelTopRight[2]) / 2.0; volumeInterface->enclosingVoxel(voxelCenterX, voxelCenterY, voxelCenterZ, voxelI, voxelJ, voxelK); // switch (sliceViewPlane) { // case VolumeSliceViewPlaneEnum::ALL: // CaretAssert(0); // break; // case VolumeSliceViewPlaneEnum::AXIAL: // voxelI = iCol; // voxelJ = jRow; // voxelK = selectedSliceIndices[2]; // break; // case VolumeSliceViewPlaneEnum::CORONAL: // voxelI = iCol; // voxelJ = selectedSliceIndices[1]; // voxelK = jRow; // break; // case VolumeSliceViewPlaneEnum::PARASAGITTAL: // voxelI = selectedSliceIndices[0]; // voxelJ = iCol; // voxelK = jRow; // break; // } const float voxelDiffXYZ[3] = { voxelTopRight[0] - voxelBottomLeft[0], voxelTopRight[1] - voxelBottomLeft[1], voxelTopRight[2] - voxelBottomLeft[2], }; addVoxelToIdentification(volumeIndex, mapIndex, voxelI, voxelJ, voxelK, voxelDiffXYZ, rgba); } /* * Add voxel to quadrilaterals */ voxelQuadCoordinates.push_back(voxelBottomLeft[0]); voxelQuadCoordinates.push_back(voxelBottomLeft[1]); voxelQuadCoordinates.push_back(voxelBottomLeft[2]); voxelQuadCoordinates.push_back(voxelBottomRight[0]); voxelQuadCoordinates.push_back(voxelBottomRight[1]); voxelQuadCoordinates.push_back(voxelBottomRight[2]); voxelQuadCoordinates.push_back(voxelTopRight[0]); voxelQuadCoordinates.push_back(voxelTopRight[1]); voxelQuadCoordinates.push_back(voxelTopRight[2]); voxelQuadCoordinates.push_back(voxelTopLeft[0]); voxelQuadCoordinates.push_back(voxelTopLeft[1]); voxelQuadCoordinates.push_back(voxelTopLeft[2]); for (int32_t iNormalAndColor = 0; iNormalAndColor < 4; iNormalAndColor++) { voxelQuadRgba.push_back(rgba[0]); voxelQuadRgba.push_back(rgba[1]); voxelQuadRgba.push_back(rgba[2]); voxelQuadRgba.push_back(rgba[3]); voxelQuadNormals.push_back(sliceNormalVector[0]); voxelQuadNormals.push_back(sliceNormalVector[1]); voxelQuadNormals.push_back(sliceNormalVector[2]); } } } } /* * Draw the voxels. */ if (voxelQuadCoordinates.empty() == false) { BrainOpenGLPrimitiveDrawing::drawQuads(voxelQuadCoordinates, voxelQuadNormals, voxelQuadRgba); } } /** * Draw the voxels in an orthogonal slice using quad indices or strips. * * Each vertex (coordinate, its normal vector, and its color) is sent to OpenGL * one time. Index arrays are used to specify the vertices when drawing the * quads. * * This is efficient when many voxels are drawn but may be inefficent * when only a few voxels are drawn. * * @param sliceNormalVector * Normal vector of the slice plane. * @param firstVoxelCoordinate * Coordinate of first voxel in the slice (bottom left as begin viewed) * @param rowStep * Three-dimensional step to next row. * @param columnStep * Three-dimensional step to next column. * @param numberOfColumns * Number of columns in the slice. * @param numberOfRows * Number of rows in the slice. * @param sliceRGBA * RGBA coloring for voxels in the slice. * @param volumeInterface * Index of the volume being drawn. * @param volumeIndex * Selected map in the volume being drawn. * @param mapIndex * Selected map in the volume being drawn. * @param sliceOpacity * Opacity from the overlay. */ void BrainOpenGLVolumeSliceDrawing::drawOrthogonalSliceVoxelsQuadIndicesAndStrips(const float sliceNormalVector[3], const float firstVoxelCoordinate[3], const float rowStep[3], const float columnStep[3], const int64_t numberOfColumns, const int64_t numberOfRows, const std::vector& sliceRGBA, const VolumeMappableInterface* volumeInterface, const int32_t volumeIndex, const int32_t mapIndex, const uint8_t sliceOpacity) { const bool debugFlag = false; enum DrawType { DRAW_QUADS, DRAW_QUAD_STRIPS }; const DrawType drawType = DRAW_QUADS; /* * When performing voxel identification for editing voxels, * we need to draw EVERY voxel since the user may click * regions where the voxels are "off". */ bool volumeEditingDrawAllVoxelsFlag = false; if (m_identificationModeFlag) { SelectionItemVoxelEditing* voxelEditID = m_brain->getSelectionManager()->getVoxelEditingIdentification(); if (voxelEditID->isEnabledForSelection()) { const VolumeFile* vf = dynamic_cast(volumeInterface); if (vf == voxelEditID->getVolumeFileForEditing()) { volumeEditingDrawAllVoxelsFlag = true; } } } /* * Allocate vectors for quadrilateral drawing */ const int64_t totalCoordElements = (numberOfColumns + 1) * (numberOfRows + 1); const int64_t numQuadStripCoords = totalCoordElements * 3; const int64_t numQuadStripRGBA = totalCoordElements * 4; std::vector voxelQuadCoordinates; std::vector voxelQuadNormals; std::vector voxelQuadRgba; voxelQuadCoordinates.reserve(numQuadStripCoords); voxelQuadNormals.reserve(numQuadStripCoords); voxelQuadRgba.reserve(numQuadStripRGBA); /* * Step to next row or column voxel */ const float rowStepX = rowStep[0]; const float rowStepY = rowStep[1]; const float rowStepZ = rowStep[2]; const float columnStepX = columnStep[0]; const float columnStepY = columnStep[1]; const float columnStepZ = columnStep[2]; const float voxelStepX = rowStepX + columnStepX; const float voxelStepY = rowStepY + columnStepY; const float voxelStepZ = rowStepZ + columnStepZ; const float voxelStepXYZ[3] = { voxelStepX, voxelStepY, voxelStepZ }; const float halfVoxelStepX = (voxelStepX / 2.0); const float halfVoxelStepY = (voxelStepY / 2.0); const float halfVoxelStepZ = (voxelStepZ / 2.0); int64_t numberOfVoxelsToDraw = 0; /* * Loop through column COORDINATES */ float columnBottomCoord[3] = { firstVoxelCoordinate[0], firstVoxelCoordinate[1], firstVoxelCoordinate[2] }; for (int64_t iCol = 0; iCol <= numberOfColumns; iCol++) { if (iCol > 0) { columnBottomCoord[0] += columnStepX; columnBottomCoord[1] += columnStepY; columnBottomCoord[2] += columnStepZ; } // const float columnBottomCoord[3] = { // firstVoxelCoordinate[0] + (iCol * columnStepX), // firstVoxelCoordinate[1] + (iCol * columnStepY), // firstVoxelCoordinate[2] + (iCol * columnStepZ) // }; /* * Loop through the row COORDINATES */ float rowCoord[3] = { columnBottomCoord[0], columnBottomCoord[1], columnBottomCoord[2] }; for (int64_t jRow = 0; jRow <= numberOfRows; jRow++) { if (jRow > 0) { rowCoord[0] += rowStepX; rowCoord[1] += rowStepY; rowCoord[2] += rowStepZ; } // const float coord[3] = { // columnBottomCoord[0] + (jRow * rowStepX), // columnBottomCoord[1] + (jRow * rowStepY), // columnBottomCoord[2] + (jRow * rowStepZ) // }; voxelQuadCoordinates.push_back(rowCoord[0]); voxelQuadCoordinates.push_back(rowCoord[1]); voxelQuadCoordinates.push_back(rowCoord[2]); voxelQuadNormals.push_back(sliceNormalVector[0]); voxelQuadNormals.push_back(sliceNormalVector[1]); voxelQuadNormals.push_back(sliceNormalVector[2]); uint8_t rgba[4] = { 0, 0, 0, 0 }; /* * With FLAT shading: * Quads: Uses top left coordinate for quad coloring * Quad Strip: Uses top right coordinate for quad coloring * So, the color is only set for this coordinate */ int64_t iColRGBA = iCol; int64_t jRowRGBA = jRow; switch (drawType) { case DRAW_QUADS: if (iColRGBA >= numberOfColumns) { iColRGBA = numberOfColumns - 1; } jRowRGBA = jRow - 1; break; case DRAW_QUAD_STRIPS: iColRGBA = iCol - 1; jRowRGBA = jRow - 1; break; } if ((iColRGBA >= 0) && (jRowRGBA >= 0)) { const int64_t voxelOffset = (iColRGBA + (numberOfColumns * jRowRGBA)); if (debugFlag) { std::cout << "col=" << iCol << " row=" << jRow << " voxel-offset=" << voxelOffset << std::endl; } /* * Offset of voxel in coloring. * Note that colors are stored in rows */ int64_t sliceRgbaOffset = (4 * voxelOffset); // int64_t sliceRgbaOffset = (4 * (iColRGBA // + (numberOfColumns * jRowRGBA))); /* * An alpha greater than zero means the voxel is displayed */ const int64_t alphaOffset = sliceRgbaOffset + 3; CaretAssertVectorIndex(sliceRGBA, alphaOffset); if (sliceRGBA[alphaOffset] > 0) { /* * Use overlay's opacity for the voxel */ rgba[0] = sliceRGBA[sliceRgbaOffset]; rgba[1] = sliceRGBA[sliceRgbaOffset + 1]; rgba[2] = sliceRGBA[sliceRgbaOffset + 2]; rgba[3] = sliceOpacity; } } /* * Voxel editing requires drawing of all voxels so that * "off" voxels can be turned "on". */ if (volumeEditingDrawAllVoxelsFlag) { rgba[3] = 255; } /* * Draw voxel if non-zero opacity */ if (rgba[3] > 0) { ++numberOfVoxelsToDraw; if (m_identificationModeFlag) { /* * Identification information is encoded in the * RGBA coloring. */ const float voxelCenterX = rowCoord[0] + halfVoxelStepX; const float voxelCenterY = rowCoord[1] + halfVoxelStepY; const float voxelCenterZ = rowCoord[2] + halfVoxelStepZ; int64_t voxelI = 0; int64_t voxelJ = 0; int64_t voxelK = 0; volumeInterface->enclosingVoxel(voxelCenterX, voxelCenterY, voxelCenterZ, voxelI, voxelJ, voxelK); addVoxelToIdentification(volumeIndex, mapIndex, voxelI, voxelJ, voxelK, voxelStepXYZ, rgba); } } voxelQuadRgba.push_back(rgba[0]); voxelQuadRgba.push_back(rgba[1]); voxelQuadRgba.push_back(rgba[2]); voxelQuadRgba.push_back(rgba[3]); } } const int64_t numberOfCoordinates = voxelQuadCoordinates.size() / 3; if (debugFlag) { std::cout << "Num rows/cols: " << numberOfRows << ", " << numberOfColumns << std::endl; std::cout << "Total, 3, 4 " << totalCoordElements << ", " << numQuadStripCoords << ", " << numQuadStripRGBA << std::endl; std::cout << "Size coords: " << voxelQuadCoordinates.size() << std::endl; std::cout << "Size normals: " << voxelQuadNormals.size() << std::endl; std::cout << "Size rgba: " << voxelQuadRgba.size() << std::endl; std::cout << "Valid voxels: " << numberOfVoxelsToDraw << std::endl; for (int64_t i = 0; i < numberOfCoordinates; i++) { std::cout << i << ": "; CaretAssertVectorIndex(voxelQuadCoordinates, i*3 + 2); std::cout << qPrintable(AString::fromNumbers(&voxelQuadCoordinates[i*3], 3, ",")) << " "; CaretAssertVectorIndex(voxelQuadRgba, i*4 + 3); std::cout << qPrintable(AString::fromNumbers(&voxelQuadRgba[i*4], 4, ",")) << std::endl; } } /* * Setup indices into coordinates/normals/coloring to draw the quads */ switch (drawType) { case DRAW_QUADS: { std::vector quadIndices; quadIndices.reserve(numberOfVoxelsToDraw * 4); for (int64_t iCol = 0; iCol < numberOfColumns; iCol++) { const int64_t columnOffset = (iCol * (numberOfRows + 1)); for (int64_t jRow = 0; jRow < numberOfRows; jRow++) { const int64_t coordBottomLeftIndex = columnOffset + jRow; //(iCol * (numberOfRows + 1) + jRow); const int64_t coordTopLeftIndex = coordBottomLeftIndex + 1; const int64_t rgbaIndex = coordTopLeftIndex * 4; CaretAssert(coordBottomLeftIndex < numberOfCoordinates); CaretAssert(coordTopLeftIndex < numberOfCoordinates); CaretAssertVectorIndex(voxelQuadRgba, rgbaIndex + 3); if (voxelQuadRgba[rgbaIndex + 3] > 0) { /* * For quads: (bottom left, bottom right, top right, top left) * Color with flat shading comes from the top left coordinate */ const int32_t coordBottomRightIndex = coordBottomLeftIndex + (numberOfRows + 1); const int32_t coordTopRightIndex = coordBottomRightIndex + 1; CaretAssert(coordBottomRightIndex < numberOfCoordinates); CaretAssert(coordTopRightIndex < numberOfCoordinates); quadIndices.push_back(coordBottomLeftIndex); quadIndices.push_back(coordBottomRightIndex); quadIndices.push_back(coordTopRightIndex); quadIndices.push_back(coordTopLeftIndex); } } if (debugFlag) { std::cout << "Quad Indices: " << quadIndices.size() << std::endl; for (uint32_t i = 0; i < quadIndices.size(); i++) { std::cout << quadIndices[i] << " ("; const int32_t coordOffset = quadIndices[i] * 3; CaretAssertVectorIndex(voxelQuadCoordinates, coordOffset + 2); std::cout << qPrintable(AString::fromNumbers(&voxelQuadCoordinates[coordOffset], 3, ",")) << ") ("; const int32_t rgbaOffset = quadIndices[i] * 4; CaretAssertVectorIndex(voxelQuadRgba, rgbaOffset + 3); std::cout << qPrintable(AString::fromNumbers(&voxelQuadRgba[rgbaOffset], 4, ",")) << " " << std::endl; } std::cout << std::endl; } } if (debugFlag) { std::cout << "Drawing " << quadIndices.size() / 4 << " quads." << std::endl; } BrainOpenGLPrimitiveDrawing::drawQuadIndices(voxelQuadCoordinates, voxelQuadNormals, voxelQuadRgba, quadIndices); } break; case DRAW_QUAD_STRIPS: { int64_t stripCount = 0; const int64_t maxCoordsPerStrip = numberOfRows * 2 + 2; for (int64_t iCol = 0; iCol < numberOfColumns; iCol++) { std::vector quadIndices; quadIndices.reserve(maxCoordsPerStrip); for (int64_t jRow = 0; jRow < numberOfRows; jRow++) { const int32_t coordBottomLeftIndex = (iCol * (numberOfRows + 1) + jRow); const int32_t coordTopLeftIndex = coordBottomLeftIndex + 1; const int32_t coordBottomRightIndex = coordBottomLeftIndex + (numberOfRows + 1); const int32_t coordTopRightIndex = coordBottomRightIndex + 1; const int64_t rgbaIndex = coordTopRightIndex * 4; CaretAssert(coordBottomLeftIndex < numberOfCoordinates); CaretAssert(coordTopLeftIndex < numberOfCoordinates); CaretAssert(coordBottomRightIndex < numberOfCoordinates); CaretAssert(coordTopRightIndex < numberOfCoordinates); CaretAssertVectorIndex(voxelQuadRgba, rgbaIndex + 3); if (voxelQuadRgba[rgbaIndex + 3] > 0) { /* * For quad strips (bottom left, bottom right, top left, top right) */ if (quadIndices.empty()) { quadIndices.push_back(coordBottomLeftIndex); quadIndices.push_back(coordBottomRightIndex); } quadIndices.push_back(coordTopLeftIndex); quadIndices.push_back(coordTopRightIndex); } else { if ( ! quadIndices.empty()) { if (debugFlag) { std::cout << "Quad Indices: " << quadIndices.size() << std::endl; for (uint32_t i = 0; i < quadIndices.size(); i++) { std::cout << quadIndices[i] << " ("; const int32_t coordOffset = quadIndices[i] * 3; CaretAssertVectorIndex(voxelQuadCoordinates, coordOffset + 2); std::cout << qPrintable(AString::fromNumbers(&voxelQuadCoordinates[coordOffset], 3, ",")) << ") ("; const int32_t rgbaOffset = quadIndices[i] * 4; CaretAssertVectorIndex(voxelQuadRgba, rgbaOffset + 3); std::cout << qPrintable(AString::fromNumbers(&voxelQuadRgba[rgbaOffset], 4, ",")) << " " << std::endl; } std::cout << std::endl; } BrainOpenGLPrimitiveDrawing::drawQuadStrips(voxelQuadCoordinates, voxelQuadNormals, voxelQuadRgba, quadIndices); quadIndices.clear(); stripCount++; } } } if ( ! quadIndices.empty()) { if (debugFlag) { std::cout << "Quad Indices: " << quadIndices.size() << std::endl; for (uint32_t i = 0; i < quadIndices.size(); i++) { std::cout << quadIndices[i] << " ("; const int32_t coordOffset = quadIndices[i] * 3; CaretAssertVectorIndex(voxelQuadCoordinates, coordOffset + 2); std::cout << qPrintable(AString::fromNumbers(&voxelQuadCoordinates[coordOffset], 3, ",")) << ") ("; const int32_t rgbaOffset = quadIndices[i] * 4; CaretAssertVectorIndex(voxelQuadRgba, rgbaOffset + 3); std::cout << qPrintable(AString::fromNumbers(&voxelQuadRgba[rgbaOffset], 4, ",")) << " " << std::endl; } std::cout << std::endl; } BrainOpenGLPrimitiveDrawing::drawQuadStrips(voxelQuadCoordinates, voxelQuadNormals, voxelQuadRgba, quadIndices); quadIndices.clear(); stripCount++; } // if (debugFlag) { // std::cout << "Quad Indices: " << quadIndices.size() << std::endl; // for (uint32_t i = 0; i < quadIndices.size(); i++) { // std::cout << quadIndices[i] << " "; // const int32_t coordOffset = quadIndices[i] * 3; // std::cout << qPrintable(AString::fromNumbers(&voxelQuadCoordinates[coordOffset], 3, ",")) << std::endl; // } // std::cout << std::endl; // } } if (debugFlag) { std::cout << "Strips drawn: " << stripCount << std::endl; } } break; } } ///** // * Draw the voxels in an orthogonal slice using quad indices or strips. // * // * Each vertex (coordinate, its normal vector, and its color) is sent to OpenGL // * one time. Index arrays are used to specify the vertices when drawing the // * quads. // * // * This is efficient when many voxels are drawn but may be inefficent // * when only a few voxels are drawn. // * // * @param sliceNormalVector // * Normal vector of the slice plane. // * @param coordinate // * Coordinate of first voxel in the slice (bottom left as begin viewed) // * @param rowStep // * Three-dimensional step to next row. // * @param columnStep // * Three-dimensional step to next column. // * @param numberOfColumns // * Number of columns in the slice. // * @param numberOfRows // * Number of rows in the slice. // * @param sliceRGBA // * RGBA coloring for voxels in the slice. // * @param volumeInterface // * Index of the volume being drawn. // * @param volumeIndex // * Selected map in the volume being drawn. // * @param mapIndex // * Selected map in the volume being drawn. // * @param sliceOpacity // * Opacity from the overlay. // */ //void //BrainOpenGLVolumeSliceDrawing::drawOrthogonalSliceVoxelsQuadIndicesAndStrips(const float sliceNormalVector[3], // const float coordinate[3], // const float rowStep[3], // const float columnStep[3], // const int64_t numberOfColumns, // const int64_t numberOfRows, // const std::vector& sliceRGBA, // const VolumeMappableInterface* volumeInterface, // const int32_t volumeIndex, // const int32_t mapIndex, // const uint8_t sliceOpacity) //{ // const bool debugFlag = false; // // enum DrawType { // DRAW_QUADS, // DRAW_QUAD_STRIPS // }; // // const DrawType drawType = DRAW_QUADS; // // /* // * When performing voxel identification for editing voxels, // * we need to draw EVERY voxel since the user may click // * regions where the voxels are "off". // */ // bool volumeEditingDrawAllVoxelsFlag = false; // if (m_identificationModeFlag) { // SelectionItemVoxelEditing* voxelEditID = m_brain->getSelectionManager()->getVoxelEditingIdentification(); // if (voxelEditID->isEnabledForSelection()) { // const VolumeFile* vf = dynamic_cast(volumeInterface); // if (vf == voxelEditID->getVolumeFileForEditing()) { // volumeEditingDrawAllVoxelsFlag = true; // } // } // } // // /* // * Allocate vectors for quadrilateral drawing // */ // const int64_t totalCoordElements = (numberOfColumns + 1) * (numberOfRows + 1); // const int64_t numQuadStripCoords = totalCoordElements * 3; // const int64_t numQuadStripRGBA = totalCoordElements * 4; // std::vector voxelQuadCoordinates; // std::vector voxelQuadNormals; // std::vector voxelQuadRgba; // voxelQuadCoordinates.reserve(numQuadStripCoords); // voxelQuadNormals.reserve(numQuadStripCoords); // voxelQuadRgba.reserve(numQuadStripRGBA); // // /* // * Step to next row or column voxel // */ // const float rowStepX = rowStep[0]; // const float rowStepY = rowStep[1]; // const float rowStepZ = rowStep[2]; // const float columnStepX = columnStep[0]; // const float columnStepY = columnStep[1]; // const float columnStepZ = columnStep[2]; // // const float voxelStepX = rowStepX + columnStepX; // const float voxelStepY = rowStepY + columnStepY; // const float voxelStepZ = rowStepZ + columnStepZ; // const float voxelStepXYZ[3] = { // voxelStepX, // voxelStepY, // voxelStepZ // }; // // const float halfVoxelStepX = (voxelStepX / 2.0); // const float halfVoxelStepY = (voxelStepY / 2.0); // const float halfVoxelStepZ = (voxelStepZ / 2.0); // // int64_t numberOfVoxelsToDraw = 0; // // /* // * Loop through column COORDINATES // */ // for (int64_t iCol = 0; iCol <= numberOfColumns; iCol++) { // const float columnBottomCoord[3] = { // coordinate[0] + (iCol * columnStepX), // coordinate[1] + (iCol * columnStepY), // coordinate[2] + (iCol * columnStepZ) // }; // // /* // * Loop through the row COORDINATES // */ // for (int64_t jRow = 0; jRow <= numberOfRows; jRow++) { // const float coord[3] = { // columnBottomCoord[0] + (jRow * rowStepX), // columnBottomCoord[1] + (jRow * rowStepY), // columnBottomCoord[2] + (jRow * rowStepZ) // }; // // voxelQuadCoordinates.push_back(coord[0]); // voxelQuadCoordinates.push_back(coord[1]); // voxelQuadCoordinates.push_back(coord[2]); // // voxelQuadNormals.push_back(sliceNormalVector[0]); // voxelQuadNormals.push_back(sliceNormalVector[1]); // voxelQuadNormals.push_back(sliceNormalVector[2]); // // uint8_t rgba[4] = { // 0, // 0, // 0, // 0 // }; // // /* // * With FLAT shading: // * Quads: Uses top left coordinate for quad coloring // * Quad Strip: Uses top right coordinate for quad coloring // * So, the color is only set for this coordinate // */ // int64_t iColRGBA = iCol; // int64_t jRowRGBA = jRow; // switch (drawType) { // case DRAW_QUADS: // if (iColRGBA >= numberOfColumns) { // iColRGBA = numberOfColumns - 1; // } // jRowRGBA = jRow - 1; // break; // case DRAW_QUAD_STRIPS: // iColRGBA = iCol - 1; // jRowRGBA = jRow - 1; // break; // } // if ((iColRGBA >= 0) // && (jRowRGBA >= 0)) { // const int64_t voxelOffset = (iColRGBA // + (numberOfColumns * jRowRGBA)); // if (debugFlag) { // std::cout << "col=" << iCol << " row=" << jRow << " voxel-offset=" << voxelOffset << std::endl; // } // // /* // * Offset of voxel in coloring. // * Note that colors are stored in rows // */ // int64_t sliceRgbaOffset = (4 * (iColRGBA // + (numberOfColumns * jRowRGBA))); // // /* // * An alpha greater than zero means the voxel is displayed // */ // const int64_t alphaOffset = sliceRgbaOffset + 3; // CaretAssertVectorIndex(sliceRGBA, alphaOffset); // if (sliceRGBA[alphaOffset] > 0) { // /* // * Use overlay's opacity for the voxel // */ // rgba[0] = sliceRGBA[sliceRgbaOffset]; // rgba[1] = sliceRGBA[sliceRgbaOffset + 1]; // rgba[2] = sliceRGBA[sliceRgbaOffset + 2]; // rgba[3] = sliceOpacity; // } // } // // /* // * Voxel editing requires drawing of all voxels so that // * "off" voxels can be turned "on". // */ // if (volumeEditingDrawAllVoxelsFlag) { // rgba[3] = 255; // } // // /* // * Draw voxel if non-zero opacity // */ // if (rgba[3] > 0) { // // ++numberOfVoxelsToDraw; // // if (m_identificationModeFlag) { // /* // * Identification information is encoded in the // * RGBA coloring. // */ // const float voxelCenterX = coord[0] + halfVoxelStepX; // const float voxelCenterY = coord[1] + halfVoxelStepY; // const float voxelCenterZ = coord[2] + halfVoxelStepZ; // int64_t voxelI = 0; // int64_t voxelJ = 0; // int64_t voxelK = 0; // volumeInterface->enclosingVoxel(voxelCenterX, voxelCenterY, voxelCenterZ, // voxelI, voxelJ, voxelK); // // addVoxelToIdentification(volumeIndex, // mapIndex, // voxelI, // voxelJ, // voxelK, // voxelStepXYZ, // rgba); // } // } // // voxelQuadRgba.push_back(rgba[0]); // voxelQuadRgba.push_back(rgba[1]); // voxelQuadRgba.push_back(rgba[2]); // voxelQuadRgba.push_back(rgba[3]); // } // } // // const int64_t numberOfCoordinates = voxelQuadCoordinates.size() / 3; // if (debugFlag) { // std::cout << "Num rows/cols: " << numberOfRows << ", " << numberOfColumns << std::endl; // std::cout << "Total, 3, 4 " << totalCoordElements << ", " << numQuadStripCoords << ", " << numQuadStripRGBA << std::endl; // std::cout << "Size coords: " << voxelQuadCoordinates.size() << std::endl; // std::cout << "Size normals: " << voxelQuadNormals.size() << std::endl; // std::cout << "Size rgba: " << voxelQuadRgba.size() << std::endl; // std::cout << "Valid voxels: " << numberOfVoxelsToDraw << std::endl; // // for (int64_t i = 0; i < numberOfCoordinates; i++) { // std::cout << i << ": "; // CaretAssertVectorIndex(voxelQuadCoordinates, i*3 + 2); // std::cout << qPrintable(AString::fromNumbers(&voxelQuadCoordinates[i*3], 3, ",")) << " "; // CaretAssertVectorIndex(voxelQuadRgba, i*4 + 3); // std::cout << qPrintable(AString::fromNumbers(&voxelQuadRgba[i*4], 4, ",")) << std::endl; // } // } // // /* // * Setup indices into coordinates/normals/coloring to draw the quads // */ // switch (drawType) { // case DRAW_QUADS: // { // std::vector quadIndices; // quadIndices.reserve(numberOfVoxelsToDraw * 4); // // for (int64_t iCol = 0; iCol < numberOfColumns; iCol++) { // for (int64_t jRow = 0; jRow < numberOfRows; jRow++) { // const int32_t coordBottomLeftIndex = (iCol * (numberOfRows + 1) + jRow); // const int32_t coordTopLeftIndex = coordBottomLeftIndex + 1; // const int64_t rgbaIndex = coordTopLeftIndex * 4; // // CaretAssert(coordBottomLeftIndex < numberOfCoordinates); // CaretAssert(coordTopLeftIndex < numberOfCoordinates); // CaretAssertVectorIndex(voxelQuadRgba, rgbaIndex + 3); // // if (voxelQuadRgba[rgbaIndex + 3] > 0) { // /* // * For quads: (bottom left, bottom right, top right, top left) // * Color with flat shading comes from the top left coordinate // */ // const int32_t coordBottomRightIndex = coordBottomLeftIndex + (numberOfRows + 1); // const int32_t coordTopRightIndex = coordBottomRightIndex + 1; // CaretAssert(coordBottomRightIndex < numberOfCoordinates); // CaretAssert(coordTopRightIndex < numberOfCoordinates); // // quadIndices.push_back(coordBottomLeftIndex); // quadIndices.push_back(coordBottomRightIndex); // quadIndices.push_back(coordTopRightIndex); // quadIndices.push_back(coordTopLeftIndex); // } // } // // if (debugFlag) { // std::cout << "Quad Indices: " << quadIndices.size() << std::endl; // for (uint32_t i = 0; i < quadIndices.size(); i++) { // std::cout << quadIndices[i] << " ("; // const int32_t coordOffset = quadIndices[i] * 3; // CaretAssertVectorIndex(voxelQuadCoordinates, coordOffset + 2); // std::cout << qPrintable(AString::fromNumbers(&voxelQuadCoordinates[coordOffset], 3, ",")) << ") ("; // // const int32_t rgbaOffset = quadIndices[i] * 4; // CaretAssertVectorIndex(voxelQuadRgba, rgbaOffset + 3); // std::cout << qPrintable(AString::fromNumbers(&voxelQuadRgba[rgbaOffset], 4, ",")) << " " << std::endl; // } // std::cout << std::endl; // } // } // // if (debugFlag) { // std::cout << "Drawing " << quadIndices.size() / 4 << " quads." << std::endl; // } // BrainOpenGLPrimitiveDrawing::drawQuadIndices(voxelQuadCoordinates, // voxelQuadNormals, // voxelQuadRgba, // quadIndices); // } // break; // case DRAW_QUAD_STRIPS: // { // int64_t stripCount = 0; // // const int64_t maxCoordsPerStrip = numberOfRows * 2 + 2; // // for (int64_t iCol = 0; iCol < numberOfColumns; iCol++) { // std::vector quadIndices; // quadIndices.reserve(maxCoordsPerStrip); // // for (int64_t jRow = 0; jRow < numberOfRows; jRow++) { // const int32_t coordBottomLeftIndex = (iCol * (numberOfRows + 1) + jRow); // const int32_t coordTopLeftIndex = coordBottomLeftIndex + 1; // const int32_t coordBottomRightIndex = coordBottomLeftIndex + (numberOfRows + 1); // const int32_t coordTopRightIndex = coordBottomRightIndex + 1; // const int64_t rgbaIndex = coordTopRightIndex * 4; // // CaretAssert(coordBottomLeftIndex < numberOfCoordinates); // CaretAssert(coordTopLeftIndex < numberOfCoordinates); // CaretAssert(coordBottomRightIndex < numberOfCoordinates); // CaretAssert(coordTopRightIndex < numberOfCoordinates); // CaretAssertVectorIndex(voxelQuadRgba, rgbaIndex + 3); // // if (voxelQuadRgba[rgbaIndex + 3] > 0) { // /* // * For quad strips (bottom left, bottom right, top left, top right) // */ // if (quadIndices.empty()) { // quadIndices.push_back(coordBottomLeftIndex); // quadIndices.push_back(coordBottomRightIndex); // } // quadIndices.push_back(coordTopLeftIndex); // quadIndices.push_back(coordTopRightIndex); // } // else { // if ( ! quadIndices.empty()) { // if (debugFlag) { // std::cout << "Quad Indices: " << quadIndices.size() << std::endl; // for (uint32_t i = 0; i < quadIndices.size(); i++) { // std::cout << quadIndices[i] << " ("; // const int32_t coordOffset = quadIndices[i] * 3; // CaretAssertVectorIndex(voxelQuadCoordinates, coordOffset + 2); // std::cout << qPrintable(AString::fromNumbers(&voxelQuadCoordinates[coordOffset], 3, ",")) << ") ("; // // const int32_t rgbaOffset = quadIndices[i] * 4; // CaretAssertVectorIndex(voxelQuadRgba, rgbaOffset + 3); // std::cout << qPrintable(AString::fromNumbers(&voxelQuadRgba[rgbaOffset], 4, ",")) << " " << std::endl; // } // std::cout << std::endl; // } // BrainOpenGLPrimitiveDrawing::drawQuadStrips(voxelQuadCoordinates, // voxelQuadNormals, // voxelQuadRgba, // quadIndices); // quadIndices.clear(); // stripCount++; // } // } // // } // if ( ! quadIndices.empty()) { // if (debugFlag) { // std::cout << "Quad Indices: " << quadIndices.size() << std::endl; // for (uint32_t i = 0; i < quadIndices.size(); i++) { // std::cout << quadIndices[i] << " ("; // const int32_t coordOffset = quadIndices[i] * 3; // CaretAssertVectorIndex(voxelQuadCoordinates, coordOffset + 2); // std::cout << qPrintable(AString::fromNumbers(&voxelQuadCoordinates[coordOffset], 3, ",")) << ") ("; // // const int32_t rgbaOffset = quadIndices[i] * 4; // CaretAssertVectorIndex(voxelQuadRgba, rgbaOffset + 3); // std::cout << qPrintable(AString::fromNumbers(&voxelQuadRgba[rgbaOffset], 4, ",")) << " " << std::endl; // } // std::cout << std::endl; // } // BrainOpenGLPrimitiveDrawing::drawQuadStrips(voxelQuadCoordinates, // voxelQuadNormals, // voxelQuadRgba, // quadIndices); // quadIndices.clear(); // stripCount++; // } // //// if (debugFlag) { //// std::cout << "Quad Indices: " << quadIndices.size() << std::endl; //// for (uint32_t i = 0; i < quadIndices.size(); i++) { //// std::cout << quadIndices[i] << " "; //// const int32_t coordOffset = quadIndices[i] * 3; //// std::cout << qPrintable(AString::fromNumbers(&voxelQuadCoordinates[coordOffset], 3, ",")) << std::endl; //// } //// std::cout << std::endl; //// } // } // if (debugFlag) { // std::cout << "Strips drawn: " << stripCount << std::endl; // } // } // break; // } //} /** * Reset for volume identification. * * Clear identification indices and if identification is enabled, * reserve a reasonable amount of space for the indices. */ void BrainOpenGLVolumeSliceDrawing::resetIdentification() { m_identificationIndices.clear(); if (m_identificationModeFlag) { int64_t estimatedNumberOfItems = 0; std::vector volumeDims; m_volumeDrawInfo[0].volumeFile->getDimensions(volumeDims); if (volumeDims.size() >= 3) { const int64_t maxDim = std::max(volumeDims[0], std::max(volumeDims[1], volumeDims[2])); estimatedNumberOfItems = maxDim * maxDim * IDENTIFICATION_INDICES_PER_VOXEL; } m_identificationIndices.reserve(estimatedNumberOfItems); } } /** * Add a voxel to the identification indices. * * @param volumeIndex * Index of the volume. * @param mapIndex * Index of the volume map. * @param voxelI * Voxel Index I * @param voxelJ * Voxel Index J * @param voxelK * Voxel Index K * @param voxelDiffXYZ * Change in XYZ from voxel bottom left to top right * @param rgbaForColorIdentificationOut * Encoded identification in RGBA color OUTPUT */ void BrainOpenGLVolumeSliceDrawing::addVoxelToIdentification(const int32_t volumeIndex, const int32_t mapIndex, const int32_t voxelI, const int32_t voxelJ, const int32_t voxelK, const float voxelDiffXYZ[3], uint8_t rgbaForColorIdentificationOut[4]) { const int32_t idIndex = m_identificationIndices.size() / IDENTIFICATION_INDICES_PER_VOXEL; m_fixedPipelineDrawing->colorIdentification->addItem(rgbaForColorIdentificationOut, SelectionItemDataTypeEnum::VOXEL, idIndex); rgbaForColorIdentificationOut[3] = 255; /* * ID stack requires integers to * use an integer pointer to the float values */ CaretAssert(sizeof(float) == sizeof(int32_t)); const int32_t* intPointerDiffXYZ = (int32_t*)voxelDiffXYZ; /* * If these items change, need to update reset and * processing of identification. */ m_identificationIndices.push_back(volumeIndex); m_identificationIndices.push_back(mapIndex); m_identificationIndices.push_back(voxelI); m_identificationIndices.push_back(voxelJ); m_identificationIndices.push_back(voxelK); m_identificationIndices.push_back(intPointerDiffXYZ[0]); m_identificationIndices.push_back(intPointerDiffXYZ[1]); m_identificationIndices.push_back(intPointerDiffXYZ[2]); } /** * Process voxel identification. */ void BrainOpenGLVolumeSliceDrawing::processIdentification() { int32_t identifiedItemIndex; float depth = -1.0; m_fixedPipelineDrawing->getIndexFromColorSelection(SelectionItemDataTypeEnum::VOXEL, m_fixedPipelineDrawing->mouseX, m_fixedPipelineDrawing->mouseY, identifiedItemIndex, depth); if (identifiedItemIndex >= 0) { const int32_t idIndex = identifiedItemIndex * IDENTIFICATION_INDICES_PER_VOXEL; const int32_t volDrawInfoIndex = m_identificationIndices[idIndex]; CaretAssertVectorIndex(m_volumeDrawInfo, volDrawInfoIndex); VolumeMappableInterface* vf = m_volumeDrawInfo[volDrawInfoIndex].volumeFile; const int64_t voxelIndices[3] = { m_identificationIndices[idIndex + 2], m_identificationIndices[idIndex + 3], m_identificationIndices[idIndex + 4] }; const float* floatDiffXYZ = (float*)&m_identificationIndices[idIndex + 5]; SelectionItemVoxel* voxelID = m_brain->getSelectionManager()->getVoxelIdentification(); if (voxelID->isEnabledForSelection()) { if (voxelID->isOtherScreenDepthCloserToViewer(depth)) { voxelID->setVoxelIdentification(m_brain, vf, voxelIndices, depth); float voxelCoordinates[3]; vf->indexToSpace(voxelIndices[0], voxelIndices[1], voxelIndices[2], voxelCoordinates[0], voxelCoordinates[1], voxelCoordinates[2]); m_fixedPipelineDrawing->setSelectedItemScreenXYZ(voxelID, voxelCoordinates); CaretLogFinest("Selected Voxel (3D): " + AString::fromNumbers(voxelIndices, 3, ",")); } } SelectionItemVoxelEditing* voxelEditID = m_brain->getSelectionManager()->getVoxelEditingIdentification(); if (voxelEditID->isEnabledForSelection()) { if (voxelEditID->getVolumeFileForEditing() == vf) { if (voxelEditID->isOtherScreenDepthCloserToViewer(depth)) { voxelEditID->setVoxelIdentification(m_brain, vf, voxelIndices, depth); voxelEditID->setVoxelDiffXYZ(floatDiffXYZ); float voxelCoordinates[3]; vf->indexToSpace(voxelIndices[0], voxelIndices[1], voxelIndices[2], voxelCoordinates[0], voxelCoordinates[1], voxelCoordinates[2]); m_fixedPipelineDrawing->setSelectedItemScreenXYZ(voxelEditID, voxelCoordinates); CaretLogFinest("Selected Voxel Editing (3D): Indices (" + AString::fromNumbers(voxelIndices, 3, ",") + ") Diff XYZ (" + AString::fromNumbers(floatDiffXYZ, 3, ",") + ")"); } } } } } /** * Get the maximum bounds that enclose the volumes and the minimum * voxel spacing from the volumes. * * @param boundsOut * Bounds of the volumes. * @param spacingOut * Minimum voxel spacing from the volumes. Always positive values (even if * volumes is oriented right to left). * */ bool BrainOpenGLVolumeSliceDrawing::getVoxelCoordinateBoundsAndSpacing(float boundsOut[6], float spacingOut[3]) { const int32_t numberOfVolumesToDraw = static_cast(m_volumeDrawInfo.size()); if (numberOfVolumesToDraw <= 0) { return false; } /* * Find maximum extent of all voxels and smallest voxel * size in each dimension. */ float minVoxelX = std::numeric_limits::max(); float maxVoxelX = -std::numeric_limits::max(); float minVoxelY = std::numeric_limits::max(); float maxVoxelY = -std::numeric_limits::max(); float minVoxelZ = std::numeric_limits::max(); float maxVoxelZ = -std::numeric_limits::max(); float voxelStepX = std::numeric_limits::max(); float voxelStepY = std::numeric_limits::max(); float voxelStepZ = std::numeric_limits::max(); for (int32_t i = 0; i < numberOfVolumesToDraw; i++) { const VolumeMappableInterface* volumeFile = m_volumeDrawInfo[i].volumeFile; int64_t dimI, dimJ, dimK, numMaps, numComponents; volumeFile->getDimensions(dimI, dimJ, dimK, numMaps, numComponents); float originX, originY, originZ; float x1, y1, z1; float lastX, lastY, lastZ; volumeFile->indexToSpace(0, 0, 0, originX, originY, originZ); volumeFile->indexToSpace(1, 1, 1, x1, y1, z1); volumeFile->indexToSpace(dimI - 1, dimJ - 1, dimK - 1, lastX, lastY, lastZ); const float dx = x1 - originX; const float dy = y1 - originY; const float dz = z1 - originZ; voxelStepX = std::min(voxelStepX, std::fabs(dx)); voxelStepY = std::min(voxelStepY, std::fabs(dy)); voxelStepZ = std::min(voxelStepZ, std::fabs(dz)); minVoxelX = std::min(minVoxelX, std::min(originX, lastX)); maxVoxelX = std::max(maxVoxelX, std::max(originX, lastX)); minVoxelY = std::min(minVoxelY, std::min(originY, lastY)); maxVoxelY = std::max(maxVoxelY, std::max(originY, lastY)); minVoxelZ = std::min(minVoxelZ, std::min(originZ, lastZ)); maxVoxelZ = std::max(maxVoxelZ, std::max(originZ, lastZ)); } boundsOut[0] = minVoxelX; boundsOut[1] = maxVoxelX; boundsOut[2] = minVoxelY; boundsOut[3] = maxVoxelY; boundsOut[4] = minVoxelZ; boundsOut[5] = maxVoxelZ; spacingOut[0] = voxelStepX; spacingOut[1] = voxelStepY; spacingOut[2] = voxelStepZ; bool valid = false; if ((maxVoxelX > minVoxelX) && (maxVoxelY > minVoxelY) && (maxVoxelZ > minVoxelZ) && (voxelStepX > 0.0) && (voxelStepY > 0.0) && (voxelStepZ > 0.0)) { valid = true; } return valid; } /** * Create the oblique transformation matrix. * * @param sliceCoordinates * Slice that is being drawn. * @param obliqueTransformationMatrixOut * OUTPUT transformation matrix for oblique viewing. */ void BrainOpenGLVolumeSliceDrawing::createObliqueTransformationMatrix(const float sliceCoordinates[3], Matrix4x4& obliqueTransformationMatrixOut) { /* * Initialize the oblique transformation matrix */ obliqueTransformationMatrixOut.identity(); /* * Get the oblique rotation matrix */ Matrix4x4 obliqueRotationMatrix = m_browserTabContent->getObliqueVolumeRotationMatrix(); /* * Create the transformation matrix */ obliqueTransformationMatrixOut.postmultiply(obliqueRotationMatrix); /* * Translate to selected coordinate */ obliqueTransformationMatrixOut.translate(sliceCoordinates[0], sliceCoordinates[1], sliceCoordinates[2]); } /** * Find portion (or all) of slice that fits inside the graphics window. * * @param sliceViewPlane * The orthogonal plane being viewed. * @param selectedSliceCoordinate * Coordinate of the slice being drawn. * @param volumeFile * Volume file that is to be drawn. * @param culledFirstVoxelIJKOut * First (bottom left) voxel that will be drawn for the volume file. * @param culledLastVoxelIJKOut * Last (top right) voxel that will be drawn for the volume file. * @param voxelDeltaXYZOut * Voxel sizes for the volume file. The element corresponding to the * slice plane being drawn will be zero (axial => [2]=0) */ bool BrainOpenGLVolumeSliceDrawing::getVolumeDrawingViewDependentCulling(const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const float selectedSliceCoordinate, const VolumeMappableInterface* volumeFile, int64_t culledFirstVoxelIJKOut[3], int64_t culledLastVoxelIJKOut[3], float voxelDeltaXYZOut[3]) { GLint viewport[4]; glGetIntegerv(GL_VIEWPORT, viewport); GLdouble projectionMatrix[16]; glGetDoublev(GL_PROJECTION_MATRIX, projectionMatrix); GLdouble modelMatrix[16]; glGetDoublev(GL_MODELVIEW_MATRIX, modelMatrix); const double vpMinX = viewport[0]; const double vpMaxX = viewport[0] + viewport[2]; const double vpMinY = viewport[1]; const double vpMaxY = viewport[1] + viewport[3]; GLdouble bottomLeftWin[3] = { vpMinX, vpMinY, 0.0 }; GLdouble bottomRightWin[3] = { vpMaxX, vpMinY, 0.0 }; GLdouble topRightWin[3] = { vpMaxX, vpMaxY, 0.0 }; GLdouble topLeftWin[3] = { vpMinX, vpMaxY, 0.0 }; GLdouble bottomLeftCoord[3]; int32_t cornersValidCount = 0; if (gluUnProject(bottomLeftWin[0], bottomLeftWin[1], bottomLeftWin[2], modelMatrix, projectionMatrix, viewport, &bottomLeftCoord[0], &bottomLeftCoord[1], &bottomLeftCoord[2])) { cornersValidCount++; } GLdouble bottomRightCoord[3]; if (gluUnProject(bottomRightWin[0], bottomRightWin[1], bottomRightWin[2], modelMatrix, projectionMatrix, viewport, &bottomRightCoord[0], &bottomRightCoord[1], &bottomRightCoord[2])) { cornersValidCount++; } GLdouble topRightCoord[3]; if (gluUnProject(topRightWin[0], topRightWin[1], topRightWin[2], modelMatrix, projectionMatrix, viewport, &topRightCoord[0], &topRightCoord[1], &topRightCoord[2])) { cornersValidCount++; } GLdouble topLeftCoord[3]; if (gluUnProject(topLeftWin[0], topLeftWin[1], topLeftWin[2], modelMatrix, projectionMatrix, viewport, &topLeftCoord[0], &topLeftCoord[1], &topLeftCoord[2])) { cornersValidCount++; } if (cornersValidCount != 4) { return false; } switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: CaretAssert(0); break; case VolumeSliceViewPlaneEnum::AXIAL: bottomLeftCoord[2] = selectedSliceCoordinate; bottomRightCoord[2] = selectedSliceCoordinate; topRightCoord[2] = selectedSliceCoordinate; topLeftCoord[2] = selectedSliceCoordinate; break; case VolumeSliceViewPlaneEnum::CORONAL: bottomLeftCoord[1] = selectedSliceCoordinate; bottomRightCoord[1] = selectedSliceCoordinate; topRightCoord[1] = selectedSliceCoordinate; topLeftCoord[1] = selectedSliceCoordinate; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: bottomLeftCoord[0] = selectedSliceCoordinate; bottomRightCoord[0] = selectedSliceCoordinate; topRightCoord[0] = selectedSliceCoordinate; topLeftCoord[0] = selectedSliceCoordinate; break; } // std::cout << std::endl; // std::cout << "Bottom Left: " << qPrintable(AString::fromNumbers(bottomLeftCoord, 3, ",")) << std::endl; // std::cout << "Bottom Right: " << qPrintable(AString::fromNumbers(bottomRightCoord, 3, ",")) << std::endl; // std::cout << "Top Right: " << qPrintable(AString::fromNumbers(topRightCoord, 3, ",")) << std::endl; // std::cout << "Top Left: " << qPrintable(AString::fromNumbers(topLeftCoord, 3, ",")) << std::endl; BoundingBox boundingBox; volumeFile->getVoxelSpaceBoundingBox(boundingBox); // std::cout << "Bounding Box: " << qPrintable(boundingBox.toString()) << std::endl; boundingBox.limitCoordinateToBoundingBox(bottomLeftCoord); boundingBox.limitCoordinateToBoundingBox(bottomRightCoord); boundingBox.limitCoordinateToBoundingBox(topRightCoord); boundingBox.limitCoordinateToBoundingBox(topLeftCoord); // std::cout << "Limited Bottom Left: " << qPrintable(AString::fromNumbers(bottomLeftCoord, 3, ",")) << std::endl; // std::cout << "Limited Bottom Right: " << qPrintable(AString::fromNumbers(bottomRightCoord, 3, ",")) << std::endl; // std::cout << "Limited Top Right: " << qPrintable(AString::fromNumbers(topRightCoord, 3, ",")) << std::endl; // std::cout << "Limited Top Left: " << qPrintable(AString::fromNumbers(topLeftCoord, 3, ",")) << std::endl; // std::cout << std::endl; /* * Note: Spacing may be negative for some orientations * and positive may be on left or bottom */ float voxelDeltaX, voxelDeltaY, voxelDeltaZ; volumeFile->getVoxelSpacing(voxelDeltaX, voxelDeltaY, voxelDeltaZ); voxelDeltaX = std::fabs(voxelDeltaX); voxelDeltaY = std::fabs(voxelDeltaY); voxelDeltaZ = std::fabs(voxelDeltaZ); if (bottomLeftCoord[0] > topRightCoord[0]) { voxelDeltaX = -voxelDeltaX; } if (bottomLeftCoord[1] > topRightCoord[1]) { voxelDeltaY = -voxelDeltaY; } if (bottomLeftCoord[2] > topRightCoord[2]) { voxelDeltaZ = -voxelDeltaZ; } bool adjustX = false; bool adjustY = false; bool adjustZ = false; switch (sliceViewPlane) { case VolumeSliceViewPlaneEnum::ALL: CaretAssert(0); break; case VolumeSliceViewPlaneEnum::AXIAL: adjustX = true; adjustY = true; voxelDeltaZ = 0.0; break; case VolumeSliceViewPlaneEnum::CORONAL: adjustX = true; adjustZ = true; voxelDeltaY = 0.0; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: adjustY = true; adjustZ = true; voxelDeltaX = 0.0; break; } /* * Adjust by one voxel to ensure full coverage */ if (adjustX) { bottomLeftCoord[0] -= voxelDeltaX; topRightCoord[0] += voxelDeltaX; } if (adjustY) { bottomLeftCoord[1] -= voxelDeltaY; topRightCoord[1] += voxelDeltaY; } if (adjustZ) { bottomLeftCoord[2] -= voxelDeltaZ; topRightCoord[2] += voxelDeltaZ; } // std::cout << "Adjusted Bottom Left: " << qPrintable(AString::fromNumbers(bottomLeftCoord, 3, ",")) << std::endl; // std::cout << "Adjusted Top Right: " << qPrintable(AString::fromNumbers(topRightCoord, 3, ",")) << std::endl; int64_t bottomLeftIJK[3]; volumeFile->enclosingVoxel(bottomLeftCoord[0], bottomLeftCoord[1], bottomLeftCoord[2], bottomLeftIJK[0], bottomLeftIJK[1], bottomLeftIJK[2]); int64_t topRightIJK[3]; volumeFile->enclosingVoxel(topRightCoord[0], topRightCoord[1], topRightCoord[2], topRightIJK[0], topRightIJK[1], topRightIJK[2]); volumeFile->limitIndicesToValidIndices(bottomLeftIJK[0], bottomLeftIJK[1], bottomLeftIJK[2]); volumeFile->limitIndicesToValidIndices(topRightIJK[0], topRightIJK[1], topRightIJK[2]); // std::cout << "Bottom Left Dimensions: " << qPrintable(AString::fromNumbers(bottomLeftIJK, 3, ",")) << std::endl; // std::cout << "Top Right Dimensions: " << qPrintable(AString::fromNumbers(topRightIJK, 3, ",")) << std::endl; culledFirstVoxelIJKOut[0] = bottomLeftIJK[0]; culledFirstVoxelIJKOut[1] = bottomLeftIJK[1]; culledFirstVoxelIJKOut[2] = bottomLeftIJK[2]; culledLastVoxelIJKOut[0] = topRightIJK[0]; culledLastVoxelIJKOut[1] = topRightIJK[1]; culledLastVoxelIJKOut[2] = topRightIJK[2]; voxelDeltaXYZOut[0] = voxelDeltaX; voxelDeltaXYZOut[1] = voxelDeltaY; voxelDeltaXYZOut[2] = voxelDeltaZ; return true; } /* ======================================================================= */ /** * Constructor * * @param volumeMappableInterface * Volume that contains the data values. * @param mapIndex * Index of selected map. */ BrainOpenGLVolumeSliceDrawing::VolumeSlice::VolumeSlice(VolumeMappableInterface* volumeMappableInterface, const int32_t mapIndex) { m_volumeMappableInterface = volumeMappableInterface; m_volumeFile = dynamic_cast(m_volumeMappableInterface); m_ciftiMappableDataFile = dynamic_cast(m_volumeMappableInterface); CaretAssert(m_volumeMappableInterface); m_mapIndex = mapIndex; CaretAssert(m_mapIndex >= 0); const int64_t sliceDim = 300; const int64_t numVoxels = sliceDim * sliceDim; m_values.reserve(numVoxels); } /** * Add a value and return its index. * * @param value * Value that is added. * @return * The index for the value. */ int64_t BrainOpenGLVolumeSliceDrawing::VolumeSlice::addValue(const float value) { const int64_t indx = static_cast(m_values.size()); m_values.push_back(value); return indx; } /** * Return RGBA colors for value using the value's index * returned by addValue(). * * @param indx * Index of the value. * @return * RGBA coloring for value. */ uint8_t* BrainOpenGLVolumeSliceDrawing::VolumeSlice::getRgbaForValueByIndex(const int64_t indx) { CaretAssertVectorIndex(m_rgba, indx * 4); return &m_rgba[indx*4]; } /** * Allocate colors for the voxel values */ void BrainOpenGLVolumeSliceDrawing::VolumeSlice::allocateColors() { m_rgba.resize(m_values.size() * 4); } /* ======================================================================= */ /** * Create a voxel for drawing. * * @param center * Center of voxel. * @param leftBottom * Left bottom coordinate of voxel. * @param rightBottom * Right bottom coordinate of voxel. * @param rightTop * Right top coordinate of voxel. * @param leftTop * Left top coordinate of voxel. */ BrainOpenGLVolumeSliceDrawing::VoxelToDraw::VoxelToDraw(const float center[3], const double leftBottom[3], const double rightBottom[3], const double rightTop[3], const double leftTop[3]) { m_center[0] = center[0]; m_center[1] = center[1]; m_center[2] = center[2]; m_coordinates[0] = leftBottom[0]; m_coordinates[1] = leftBottom[1]; m_coordinates[2] = leftBottom[2]; m_coordinates[3] = rightBottom[0]; m_coordinates[4] = rightBottom[1]; m_coordinates[5] = rightBottom[2]; m_coordinates[6] = rightTop[0]; m_coordinates[7] = rightTop[1]; m_coordinates[8] = rightTop[2]; m_coordinates[9] = leftTop[0]; m_coordinates[10] = leftTop[1]; m_coordinates[11] = leftTop[2]; const int64_t numSlices = 5; m_sliceIndices.reserve(numSlices); m_sliceOffsets.reserve(numSlices); } /** * Get the change in XYZ for the voxel ([top right] minus [bottom left]) * * @param dxyzOut * Change in XYZ from bottom left to top right */ void BrainOpenGLVolumeSliceDrawing::VoxelToDraw::getDiffXYZ(float dxyzOut[3]) const { dxyzOut[0] = m_coordinates[6] - m_coordinates[0]; dxyzOut[1] = m_coordinates[7] - m_coordinates[1]; dxyzOut[2] = m_coordinates[8] - m_coordinates[2]; } /** * Add a value from a volume slice. * * @param sliceIndex * Index of the slice. * @param sliceOffset * Offset of value in the slice. */ void BrainOpenGLVolumeSliceDrawing::VoxelToDraw::addVolumeValue(const int64_t sliceIndex, const int64_t sliceOffset) { CaretAssert(sliceIndex >= 0); CaretAssert(sliceOffset >= 0); m_sliceIndices.push_back(sliceIndex); m_sliceOffsets.push_back(sliceOffset); } workbench-1.1.1/src/Brain/BrainOpenGLVolumeSliceDrawing.h000066400000000000000000000373611255417355300232770ustar00rootroot00000000000000#ifndef __BRAIN_OPEN_GL_VOLUME_SLICE_DRAWING_H__ #define __BRAIN_OPEN_GL_VOLUME_SLICE_DRAWING_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "BrainOpenGLFixedPipeline.h" #include "CaretObject.h" #include "DisplayGroupEnum.h" #include "VolumeSliceProjectionTypeEnum.h" #include "VolumeSliceDrawingTypeEnum.h" #include "VolumeSliceViewPlaneEnum.h" namespace caret { class Brain; class BrowserTabContent; class CiftiMappableDataFile; class Matrix4x4; class ModelVolume; class ModelWholeBrain; class PaletteFile; class Plane; class VolumeMappableInterface; class BrainOpenGLVolumeSliceDrawing : public CaretObject { public: BrainOpenGLVolumeSliceDrawing(); virtual ~BrainOpenGLVolumeSliceDrawing(); void draw(BrainOpenGLFixedPipeline* fixedPipelineDrawing, BrowserTabContent* browserTabContent, std::vector& volumeDrawInfo, const VolumeSliceDrawingTypeEnum::Enum sliceDrawingType, const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const int32_t viewport[4]); // ADD_NEW_METHODS_HERE private: /** * Holds values in the slice for a volume so that they * can be colored all at once which is more efficient than * colors singles voxels many times */ class VolumeSlice{ public: /** * Constructor * * @param volumeMappableInterface * Volume that contains the data values. */ VolumeSlice(VolumeMappableInterface* volumeMappableInterface, const int32_t mapIndex); /** * Add a value and return its index. * * @param value * Value that is added. * @return * The index for the value. */ int64_t addValue(const float value); /** * Return RGBA colors for value using the value's index * returned by addValue(). * * @param indx * Index of the value. * @return * RGBA coloring for value. */ uint8_t* getRgbaForValueByIndex(const int64_t indx); /** * Allocate colors for the voxel values */ void allocateColors(); /** * Volume containing the values */ VolumeMappableInterface* m_volumeMappableInterface; /** * If not NULL, it is a VolumeFile */ VolumeFile* m_volumeFile; /** * If not NULL, it is a Cifti Mappable Data File */ CiftiMappableDataFile* m_ciftiMappableDataFile; /** * Map index */ int32_t m_mapIndex; /** * The voxel values */ std::vector m_values; /** * Coloring corresponding to the values (4 components per voxel) */ std::vector m_rgba; }; /** * For each voxel, contains offsets to each layer */ class VoxelToDraw { public: VoxelToDraw(const float center[3], const double leftBottom[3], const double rightBottom[3], const double rightTop[3], const double leftTop[3]); void getDiffXYZ(float dxyzOut[3]) const; void addVolumeValue(const int64_t sliceIndex, const int64_t sliceOffset); /** * Center of voxel. */ float m_center[3]; /** * Corners of voxel */ float m_coordinates[12]; /* * Index of volume in VoxelsInSliceForVolume */ std::vector m_sliceIndices; /** * Offset in values in VoxelsInSliceForVolume */ std::vector m_sliceOffsets; }; BrainOpenGLVolumeSliceDrawing(const BrainOpenGLVolumeSliceDrawing&); BrainOpenGLVolumeSliceDrawing& operator=(const BrainOpenGLVolumeSliceDrawing&); void drawVolumeSlicesForAllStructuresView(const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const int32_t viewport[4]); void drawVolumeSliceViewPlane(const VolumeSliceDrawingTypeEnum::Enum sliceDrawingType, const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const int32_t viewport[4]); void drawVolumeSliceViewType(const VolumeSliceDrawingTypeEnum::Enum sliceDrawingType, const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const int32_t viewport[4]); void drawVolumeSliceViewTypeMontage(const VolumeSliceDrawingTypeEnum::Enum sliceDrawingType, const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const int32_t viewport[4]); void drawVolumeSliceViewProjection(const VolumeSliceDrawingTypeEnum::Enum sliceDrawingType, const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const float sliceCoordinates[3], const int32_t viewport[4]); void drawObliqueSlice(const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, Matrix4x4& transformationMatrix, const Plane& plane); void drawOrthogonalSlice(const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const float sliceCoordinates[3], const Plane& plane); void drawOrthogonalSliceWithCulling(const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const float sliceCoordinates[3], const Plane& plane); void createSlicePlaneEquation(const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const float sliceCoordinates[3], Plane& planeOut); void drawAxesCrosshairsOrthoAndOblique(const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const float sliceCoordinates[3], const bool drawCrosshairsFlag, const bool drawCrosshairLabelsFlag); void setVolumeSliceViewingAndModelingTransformations(const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const Plane& plane); void getAxesColor(const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, float rgbaOut[4]) const; void drawLayers(const VolumeSliceDrawingTypeEnum::Enum sliceDrawingType, const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const Plane& slicePlane, const float sliceCoordinates[3]); void drawSurfaceOutline(const Plane& plane); void drawVolumeSliceFoci(const Plane& plane); void drawAxesCrosshairs(const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const VolumeSliceDrawingTypeEnum::Enum sliceDrawingType, const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const float sliceCoordinates[3]); bool getMinMaxVoxelSpacing(const VolumeMappableInterface* volume, float& minSpacingOut, float& maxSpacingOut) const; void drawSquare(const float size); void drawOrientationAxes(const int viewport[4]); void setOrthographicProjection(const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const int viewport[4]); void drawOrthogonalSliceVoxels(const float sliceNormalVector[3], const float coordinate[3], const float rowStep[3], const float columnStep[3], const int64_t numberOfColumns, const int64_t numberOfRows, const std::vector& sliceRGBA, const int64_t validVoxelCount, const VolumeMappableInterface* volumeInterface, const int32_t volumeIndex, const int32_t mapIndex, const uint8_t sliceOpacity); void drawOrthogonalSliceVoxelsSingleQuads(const float sliceNormalVector[3], const float coordinate[3], const float rowStep[3], const float columnStep[3], const int64_t numberOfColumns, const int64_t numberOfRows, const std::vector& sliceRGBA, const VolumeMappableInterface* volumeInterface, const int32_t volumeIndex, const int32_t mapIndex, const uint8_t sliceOpacity); void drawOrthogonalSliceVoxelsQuadIndicesAndStrips(const float sliceNormalVector[3], const float coordinate[3], const float rowStep[3], const float columnStep[3], const int64_t numberOfColumns, const int64_t numberOfRows, const std::vector& sliceRGBA, const VolumeMappableInterface* volumeInterface, const int32_t volumeIndex, const int32_t mapIndex, const uint8_t sliceOpacity); bool getVoxelCoordinateBoundsAndSpacing(float boundsOut[6], float spacingOut[3]); void createObliqueTransformationMatrix(const float sliceCoordinates[3], Matrix4x4& obliqueTransformationMatrixOut); void drawIdentificationSymbols(const Plane& plane); void addVoxelToIdentification(const int32_t volumeIndex, const int32_t mapIndex, const int32_t voxelI, const int32_t voxelJ, const int32_t voxelK, const float voxelDiffXYZ[3], uint8_t rgbaForColorIdentificationOut[4]); bool getVolumeDrawingViewDependentCulling(const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const float selectedSliceCoordinate, const VolumeMappableInterface* volumeFile, int64_t culledFirstVoxelIJKOut[3], int64_t culledLastVoxelIJKOut[3], float voxelDeltaXYZOut[3]); void showBrainordinateHighlightRegionOfInterest(const VolumeSliceViewPlaneEnum::Enum sliceViewPlane, const float sliceCoordinates[3], const float sliceNormalVector[3]); void processIdentification(); void resetIdentification(); ModelVolume* m_modelVolume; ModelWholeBrain* m_modelWholeBrain; VolumeMappableInterface* m_underlayVolume; Brain* m_brain; std::vector > m_ciftiMappableFileData; BrainOpenGLFixedPipeline* m_fixedPipelineDrawing; std::vector m_volumeDrawInfo; BrowserTabContent* m_browserTabContent; PaletteFile* m_paletteFile; DisplayGroupEnum::Enum m_displayGroup; int32_t m_tabIndex; double m_lookAtCenter[3]; double m_viewingMatrix[16]; double m_orthographicBounds[6]; std::vector m_identificationIndices; bool m_identificationModeFlag; static const int32_t IDENTIFICATION_INDICES_PER_VOXEL; // ADD_NEW_MEMBERS_HERE }; #ifdef __BRAIN_OPEN_GL_VOLUME_SLICE_DRAWING_DECLARE__ const int32_t BrainOpenGLVolumeSliceDrawing::IDENTIFICATION_INDICES_PER_VOXEL = 8; #endif // __BRAIN_OPEN_GL_VOLUME_SLICE_DRAWING_DECLARE__ } // namespace #endif //__BRAIN_OPEN_GL_VOLUME_SLICE_DRAWING_H__ workbench-1.1.1/src/Brain/BrainStructure.cxx000066400000000000000000001321041255417355300210310ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #define __BRAIN_STRUCTURE_DEFINE__ #include "BrainStructure.h" #undef __BRAIN_STRUCTURE_DEFINE__ #include "Brain.h" #include "BrainStructureNodeAttributes.h" #include "CaretAssert.h" #include "CaretLogger.h" #include "CaretPointLocator.h" #include "CaretPreferences.h" #include "DataFileException.h" #include "ElapsedTimer.h" #include "EventBrainStructureGetAll.h" #include "EventManager.h" #include "EventNodeDataFilesGet.h" #include "EventModelAdd.h" #include "EventModelDelete.h" #include "EventSurfacesGet.h" #include "EventSurfaceStructuresValidGet.h" #include "GroupAndNameHierarchyModel.h" #include "IdentificationManager.h" #include "SelectionManager.h" #include "LabelFile.h" #include "MathFunctions.h" #include "MetricFile.h" #include "ModelSurface.h" #include "OverlaySet.h" #include "OverlaySetArray.h" #include "RgbaFile.h" #include "SceneClass.h" #include "ScenePathName.h" #include "SessionManager.h" #include "Surface.h" using namespace caret; /** * Constructor. * */ BrainStructure::BrainStructure(Brain* brain, StructureEnum::Enum structure) { m_brainStructureIdentifier = BrainStructure::s_brainStructureIdentifierCounter++; m_brain = brain; m_structure = structure; m_nodeAttributes = new BrainStructureNodeAttributes(); m_primaryAnatomicalSurface = NULL; std::vector overlaySurfaceStructures; overlaySurfaceStructures.push_back(m_structure); m_overlaySetArray = new OverlaySetArray(overlaySurfaceStructures, Overlay::INCLUDE_VOLUME_FILES_NO, "Structure " + StructureEnum::toGuiName(m_structure)); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_BRAIN_STRUCTURE_GET_ALL); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_GET_NODE_DATA_FILES); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_IDENTIFICATION_HIGHLIGHT_LOCATION); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_IDENTIFICATION_SYMBOL_REMOVAL); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_SURFACES_GET); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_SURFACE_STRUCTURES_VALID_GET); } /** * Destructor. */ BrainStructure::~BrainStructure() { EventManager::get()->removeAllEventsFromListener(this); delete m_overlaySetArray; /* * Make a copy of all surface pointers since * deleting surfaces will alter the actual * vector that stores the surfaces. */ std::vector allSurfaces(m_surfaces); for (uint64_t i = 0; i < allSurfaces.size(); i++) { removeAndMaybeDeleteSurface(allSurfaces[i], true); } m_surfaces.clear(); for (uint64_t i = 0; i < m_labelFiles.size(); i++) { delete m_labelFiles[i]; m_labelFiles[i] = NULL; } m_labelFiles.clear(); for (uint64_t i = 0; i < m_metricFiles.size(); i++) { delete m_metricFiles[i]; m_metricFiles[i] = NULL; } m_metricFiles.clear(); for (uint64_t i = 0; i < m_rgbaFiles.size(); i++) { delete m_rgbaFiles[i]; m_rgbaFiles[i] = NULL; } m_rgbaFiles.clear(); delete m_nodeAttributes; } /** * Get the structure for this BrainStructure. * * @return The structure. */ StructureEnum::Enum BrainStructure::getStructure() const { return m_structure; } /** * Add a label file. * * @param labelFile * Label file that is added. * @param addFileToBrainStructure * If true, add the file to the brain structure. This value is false * when a file is reloaded and already part of the brain structure. * @throw DataFileException * If the number of nodes in the label file does not match * the number of nodes in this brain structure. */ void BrainStructure::addLabelFile(LabelFile* labelFile, const bool addFileToBrainStructure) { CaretAssert(labelFile); int32_t numNodes = getNumberOfNodes(); if (numNodes > 0) { if (labelFile->getNumberOfNodes() != numNodes) { AString message = ("Label file contains " + AString::number(labelFile->getNumberOfNodes()) + " vertices but the " + StructureEnum::toGuiName(getStructure()) + " contains " + AString::number(numNodes) + " vertices."); DataFileException e(labelFile->getFileName(), message); CaretLogThrowing(e); throw e; } } if (labelFile->getStructure() != getStructure()) { AString message = ("Trying to add label file with structure \"" + StructureEnum::toGuiName(labelFile->getStructure()) + " to BrainStructure for \"" + StructureEnum::toGuiName(getStructure()) + "\n"); DataFileException e(labelFile->getFileName(), message); CaretLogThrowing(e); throw e; } if (addFileToBrainStructure) { m_labelFiles.push_back(labelFile); } } /** * Add a metric file. * * @param metricFile * Metric file that is added. * @param addFileToBrainStructure * If true, add the file to the brain structure. This value is false * when a file is reloaded and already part of the brain structure. * @throw DataFileException * If the number of nodes in the metric file does not match * the number of nodes in this brain structure. */ void BrainStructure::addMetricFile(MetricFile* metricFile, const bool addFileToBrainStructure) { CaretAssert(metricFile); int32_t numNodes = getNumberOfNodes(); if (numNodes > 0) { if (metricFile->getNumberOfNodes() != numNodes) { AString message = ("Metric file contains " + AString::number(metricFile->getNumberOfNodes()) + " vertices but the " + StructureEnum::toGuiName(getStructure()) + " contains " + AString::number(numNodes) + " vertices."); DataFileException e(metricFile->getFileName(), message); CaretLogThrowing(e); throw e; } } if (metricFile->getStructure() != getStructure()) { AString message = ("Trying to add metric file with structure \"" + StructureEnum::toGuiName(metricFile->getStructure()) + " to BrainStructure for \"" + StructureEnum::toGuiName(getStructure()) + "\n"); DataFileException e(metricFile->getFileName(), message); CaretLogThrowing(e); throw e; } if (addFileToBrainStructure) { m_metricFiles.push_back(metricFile); } } /** * Add an RGBA file. * * @param rgbaFile * RGBA file that is added. * @param addFileToBrainStructure * If true, add the file to the brain structure. This value is false * when a file is reloaded and already part of the brain structure. * @throw DataFileException * If the number of nodes in the RGBA file does not match * the number of nodes in this brain structure. */ void BrainStructure::addRgbaFile(RgbaFile* rgbaFile, const bool addFileToBrainStructure) { CaretAssert(rgbaFile); int32_t numNodes = getNumberOfNodes(); if (numNodes > 0) { if (rgbaFile->getNumberOfNodes() != numNodes) { AString message = ("RGBA File contains " + AString::number(rgbaFile->getNumberOfNodes()) + " vertices but the " + StructureEnum::toGuiName(getStructure()) + " contains " + AString::number(numNodes) + " vertices."); DataFileException e(rgbaFile->getFileName(), message); CaretLogThrowing(e); throw e; } } if (rgbaFile->getStructure() != getStructure()) { AString message = ("Trying to add metric file with structure \"" + StructureEnum::toGuiName(rgbaFile->getStructure()) + " to BrainStructure for \"" + StructureEnum::toGuiName(getStructure()) + "\n"); DataFileException e(rgbaFile->getFileName(), message); CaretLogThrowing(e); throw e; } if (addFileToBrainStructure) { m_rgbaFiles.push_back(rgbaFile); } } /** * Add a surface. * * @param surface * Surface that is added. * @param addFileToBrainStructure * If true, add the file to the brain structure. This value is false * when a file is reloaded and already part of the brain structure. * @throw DataFileException * If the number of nodes in the surface does not match * the number of nodes in this brain structure. */ void BrainStructure::addSurface(Surface* surface, const bool addFileToBrainStructure, const bool initilizeOverlaysFlag) { CaretAssert(surface); int32_t numNodes = getNumberOfNodes(); if (numNodes > 0) { if (surface->getNumberOfNodes() != numNodes) { AString message = ("Surface file contains " + AString::number(surface->getNumberOfNodes()) + " vertices but the " + StructureEnum::toGuiName(getStructure()) + " contains " + AString::number(numNodes) + " vertices."); DataFileException e(surface->getFileName(), message); CaretLogThrowing(e); throw e; } } if (surface->getStructure() != getStructure()) { AString message = ("Trying to add metric file with structure \"" + StructureEnum::toGuiName(surface->getStructure()) + " to BrainStructure for \"" + StructureEnum::toGuiName(getStructure()) + "\n"); DataFileException e(surface->getFileName(), message); CaretLogThrowing(e); throw e; } if (numNodes == 0) { const int32_t numSurfaceNodes = surface->getNumberOfNodes(); m_nodeAttributes->update(numSurfaceNodes); } surface->setBrainStructure(this); if (addFileToBrainStructure) { m_surfaces.push_back(surface); /* * Create a model for the surface. */ ModelSurface* mdcs = new ModelSurface(m_brain, surface); m_surfaceModelMap.insert(std::make_pair(surface, mdcs)); if (initilizeOverlaysFlag) { initializeOverlays(); } /* * Send the model added event. */ EventModelAdd addEvent(mdcs); EventManager::get()->sendEvent(addEvent.getPointer()); } } /** * Remove a surface from this brain structure and the surface * model and maybe delete the surface. * * @param surface * Surface that is removed. * @param deleteSurfaceFile * If true, delete the surface file. If false, surface is removed * but not delete and caller is responsible for deleting the surface. * @return * True if the surface was removed, else false. */ bool BrainStructure::removeAndMaybeDeleteSurface(Surface* surface, const bool deleteSurfaceFile) { CaretAssert(surface); std::vector::iterator iter = std::find(m_surfaces.begin(), m_surfaces.end(), surface); CaretAssertMessage((iter != m_surfaces.end()), "Trying to delete surface not in brain structure."); if (iter == m_surfaces.end()) { CaretLogSevere("Trying to delete surface not in brain structure."); return false; } std::map::iterator modelIter = m_surfaceModelMap.find(surface); CaretAssertMessage((modelIter != m_surfaceModelMap.end()), "Surface does not map to a model"); if (modelIter == m_surfaceModelMap.end()) { CaretLogSevere("Surface does not map to a model"); return false; } ModelSurface* mdcs = modelIter->second; /* * Remove from surface to model map. */ m_surfaceModelMap.erase(modelIter); /* * Remove the surface. */ m_surfaces.erase(iter); /* * Send the model deleted event. */ EventModelDelete deleteEvent(mdcs); EventManager::get()->sendEvent(deleteEvent.getPointer()); /* * Delete the model and the surface. */ delete mdcs; if (deleteSurfaceFile) { delete surface; } return true; } /** * Get the number of surfaces. * * @return * Number of surfaces. */ int BrainStructure::getNumberOfSurfaces() const { return static_cast(m_surfaces.size()); } /** * Get a surface at the specified index. * * @param indx * Index of surface. * @return * Surface at the specified index. */ Surface* BrainStructure::getSurface(int indx) { CaretAssertVectorIndex(m_surfaces, indx); return m_surfaces[indx]; } /** * Get a surface at the specified index. * * @param indx * Index of surface. * @return * Surface at the specified index. */ const Surface* BrainStructure::getSurface(int indx) const { CaretAssertVectorIndex(m_surfaces, indx); return m_surfaces[indx]; } /** * Get all surfaces of the given type in this brain structure. * @param surfaceType * Type of surface * @param surfacesOut * Output that will contain the surfaces. */ void BrainStructure::getSurfacesOfType(const SurfaceTypeEnum::Enum surfaceType, std::vector& surfacesOut) const { surfacesOut.clear(); const int32_t numSurfaces = getNumberOfSurfaces(); for (int32_t i = 0; i < numSurfaces; i++) { if (m_surfaces[i]->getSurfaceType() == surfaceType) { surfacesOut.push_back(m_surfaces[i]); } } } /** * Get all surfaces. * * @param surfaceOut * Output containing all surfaces. */ void BrainStructure::getSurfaces(std::vector& surfacesOut) const { surfacesOut = m_surfaces; } /** * @return The surface used for primary anatomical. * Returns NULL if no anatomical surfaces. */ const Surface* BrainStructure::getPrimaryAnatomicalSurfacePrivate() const { bool valid = false; if (m_primaryAnatomicalSurface != NULL) { const int32_t numSurfaces = getNumberOfSurfaces(); for (int32_t i = 0; i < numSurfaces; i++) { if (m_surfaces[i] == m_primaryAnatomicalSurface) { valid = true; break; } } } if (valid) { return m_primaryAnatomicalSurface; } m_primaryAnatomicalSurface = NULL; /* * Give preference to anatomical surfaces but if there are none * (perhaps the surface types are missing), use all surfaces. */ std::vector primaryAnatomicalSurfaces; getSurfacesOfType(SurfaceTypeEnum::ANATOMICAL, primaryAnatomicalSurfaces); if (primaryAnatomicalSurfaces.empty()) { primaryAnatomicalSurfaces = m_surfaces; } if (primaryAnatomicalSurfaces.empty() == false) { /* * Default to first surface */ m_primaryAnatomicalSurface = primaryAnatomicalSurfaces[0]; /* * Now look for a surface with certain strings in their name */ Surface* midThicknessSurface = NULL; Surface* whiteMatterSurface = NULL; Surface* pialSurface = NULL; Surface* anatomicalSurface = NULL; Surface* fiducialSurface = NULL; const int32_t numSurfaces = static_cast(primaryAnatomicalSurfaces.size()); for (int32_t i = 0; i < numSurfaces; i++) { /* * First, look for anatomical surfaces that are midthickness, * gray/white, and pial. */ if (primaryAnatomicalSurfaces[i]->getSurfaceType() == SurfaceTypeEnum::ANATOMICAL) { const SecondarySurfaceTypeEnum::Enum secondType = primaryAnatomicalSurfaces[i]->getSecondaryType(); const AString name = primaryAnatomicalSurfaces[i]->getFileNameNoPath().toLower(); if (secondType == SecondarySurfaceTypeEnum::MIDTHICKNESS) { if (midThicknessSurface == NULL) { midThicknessSurface = primaryAnatomicalSurfaces[i]; } } if (secondType == SecondarySurfaceTypeEnum::GRAY_WHITE) { if (whiteMatterSurface == NULL) { whiteMatterSurface = primaryAnatomicalSurfaces[i]; } } if (secondType == SecondarySurfaceTypeEnum::PIAL) { if (pialSurface == NULL) { pialSurface = primaryAnatomicalSurfaces[i]; } } } } /* * Since it is possible surfaces may not have valid types, * perform an additional search using name substrings. */ for (int32_t i = 0; i < numSurfaces; i++) { const AString name = primaryAnatomicalSurfaces[i]->getFileNameNoPath().toLower(); if (name.indexOf("midthick") >= 0) { if (midThicknessSurface == NULL) { midThicknessSurface = primaryAnatomicalSurfaces[i]; } } if (name.indexOf("white") >= 0) { if (whiteMatterSurface == NULL) { whiteMatterSurface = primaryAnatomicalSurfaces[i]; } } if (name.indexOf("pial") >= 0) { if (pialSurface == NULL) { pialSurface = primaryAnatomicalSurfaces[i]; } } if (name.indexOf("anatomical") >= 0) { if (anatomicalSurface == NULL) { anatomicalSurface = primaryAnatomicalSurfaces[i]; } } if (name.indexOf("fiducial") >= 0) { if (fiducialSurface == NULL) { fiducialSurface = primaryAnatomicalSurfaces[i]; } } } if (midThicknessSurface != NULL) { m_primaryAnatomicalSurface = midThicknessSurface; } else if (whiteMatterSurface != NULL) { m_primaryAnatomicalSurface = whiteMatterSurface; } else if (pialSurface != NULL) { m_primaryAnatomicalSurface = pialSurface; } else if (anatomicalSurface != NULL) { m_primaryAnatomicalSurface = anatomicalSurface; } else if (fiducialSurface != NULL) { m_primaryAnatomicalSurface = fiducialSurface; } } if (m_primaryAnatomicalSurface != NULL) { CaretLogFiner("Primary Anatomical Surface for " + StructureEnum::toGuiName(m_structure) + ": " + m_primaryAnatomicalSurface->getFileNameNoPath()); } else { CaretLogFiner("Primary Anatomical Surface for " + StructureEnum::toGuiName(m_structure) + " is invalid."); } return m_primaryAnatomicalSurface; } /** * @return The surface used for primary anatomical. * Returns NULL if no anatomical surfaces. */ const Surface* BrainStructure::getPrimaryAnatomicalSurface() const { return getPrimaryAnatomicalSurfacePrivate(); } /** * @return The surface used for primary anatomical. * Returns NULL if no anatomical surfaces. */ Surface* BrainStructure::getPrimaryAnatomicalSurface() { /* * Kludge to avoid duplicated code and ease maintenance */ const Surface* constSurface = getPrimaryAnatomicalSurfacePrivate(); Surface* s = (Surface*)constSurface; return s; } /** * Set the primary anatomical surface. * @param primaryAnatomicalSurface * New primary anatomical surface. */ void BrainStructure::setPrimaryAnatomicalSurface(Surface* primaryAnatomicalSurface) { m_primaryAnatomicalSurface = primaryAnatomicalSurface; } /** * Find and return the first surface encountered that contains the * given text in the name of the surface's filename. Text is searched * in an case-insensitive mode. * * @param text * Text that is to bound in the surface's filenname. * @return * Surface that contains the given text in its filename or * NULL if no surface matches. */ Surface* BrainStructure::getSurfaceContainingTextInName(const AString& text) { /* * Kludge to avoid duplicated code and ease maintenance */ const Surface* constSurface = getSurfaceContainingTextInNamePrivate(text); Surface* s = (Surface*)constSurface; return s; } /** * Find and return the first surface encountered that contains the * given text in the name of the surface's filename. Text is searched * in an case-insensitive mode. * * @param text * Text that is to bound in the surface's filenname. * @return * Surface that contains the given text in its filename or * NULL if no surface matches. */ const Surface* BrainStructure::getSurfaceContainingTextInName(const AString& text) const { return getSurfaceContainingTextInNamePrivate(text); } /** * Find and return the first surface encountered that contains the * given text in the name of the surface's filename. Text is searched * in an case-insensitive mode. * * @param text * Text that is to bound in the surface's filenname. * @return * Surface that contains the given text in its filename or * NULL if no surface matches. */ const Surface* BrainStructure::getSurfaceContainingTextInNamePrivate(const AString& text) const { for (std::vector::const_iterator iter = m_surfaces.begin(); iter != m_surfaces.end(); iter++) { const Surface* surface = *iter; const AString name = surface->getFileNameNoPath(); if (name.indexOf(text, 0, Qt::CaseInsensitive) >= 0) { return surface; } } return NULL; } /** * Is the surface in this brain structure? * @param surface * Surface that is tested for being in this brain structure. * @return Returns true if surface in brain structure, else false. */ bool BrainStructure::containsSurface(const Surface* surface) { CaretAssert(surface); if (std::find(m_surfaces.begin(), m_surfaces.end(), surface) != m_surfaces.end()) { return true; } return false; } /** * Find the surface with the given name. * @param surfaceFileName * Name of surface. * @param useAbsolutePath * If true the given surfaceFileName is an absolute path. * If false, the given surfaceFileName is just the file * name without any path. */ Surface* BrainStructure::getSurfaceWithName(const AString& surfaceFileName, const bool useAbsolutePath) { for (std::vector::iterator iter = m_surfaces.begin(); iter != m_surfaces.end(); iter++) { Surface* surface = *iter; const AString name = (useAbsolutePath ? surface->getFileName() : surface->getFileNameNoPath()); if (surfaceFileName == name) { return surface; } } return NULL; } /** * Get the brain that this brain structure is in. */ Brain* BrainStructure::getBrain() { return m_brain; } /** * Get the brain that this brain structure is in. */ const Brain* BrainStructure::getBrain() const { return m_brain; } /** * Get the number of nodes used by this brain structure. * * @return Number of nodes. */ int32_t BrainStructure::getNumberOfNodes() const { if (m_surfaces.empty() == false) { return m_surfaces[0]->getNumberOfNodes(); } return 0; } /** * Get all of the label files. * @param labelFilesOut * Will contain all label files after this method exits. */ void BrainStructure::getLabelFiles(std::vector& labelFilesOut) const { labelFilesOut.clear(); labelFilesOut.insert(labelFilesOut.end(), m_labelFiles.begin(), m_labelFiles.end()); } /** * Get the number of label files. * @return Number of label files. */ int32_t BrainStructure::getNumberOfLabelFiles() const { return m_labelFiles.size(); } /** * Get a label file at the specified index. * @param fileIndex * Index of the label file. * @return * Metric file at the index. */ LabelFile* BrainStructure::getLabelFile(const int32_t fileIndex) { CaretAssertVectorIndex(m_labelFiles, fileIndex); return m_labelFiles[fileIndex]; } /** * Get a label file at the specified index. * @param fileIndex * Index of the label file. * @return * Metric file at the index. */ const LabelFile* BrainStructure::getLabelFile(const int32_t fileIndex) const { CaretAssertVectorIndex(m_labelFiles, fileIndex); return m_labelFiles[fileIndex]; } /** * Get all of the metric files. * @param metricFilesOut * Will contain all metric files after this method exits. */ void BrainStructure::getMetricFiles(std::vector& metricFilesOut) const { metricFilesOut.clear(); metricFilesOut.insert(metricFilesOut.end(), m_metricFiles.begin(), m_metricFiles.end()); } /** * Get the number of metric files. * @return Number of metric files. */ int32_t BrainStructure::getNumberOfMetricFiles() const { return m_metricFiles.size(); } /** * Get a metric file at the specified index. * @param fileIndex * Index of the metric file. * @return * Metric file at the index. */ MetricFile* BrainStructure::getMetricFile(const int32_t fileIndex) { CaretAssertVectorIndex(m_metricFiles, fileIndex); return m_metricFiles[fileIndex]; } /** * Get a metric file at the specified index. * @param fileIndex * Index of the metric file. * @return * Metric file at the index. */ const MetricFile* BrainStructure::getMetricFile(const int32_t fileIndex) const { CaretAssertVectorIndex(m_metricFiles, fileIndex); return m_metricFiles[fileIndex]; } /** * Get the number of rgba files. * @return Number of rgba files. */ int32_t BrainStructure::getNumberOfRgbaFiles() const { return m_rgbaFiles.size(); } /** * Get a rgba file at the specified index. * @param fileIndex * Index of the rgba file. * @return * Metric file at the index. */ RgbaFile* BrainStructure::getRgbaFile(const int32_t fileIndex) { CaretAssertVectorIndex(m_rgbaFiles, fileIndex); return m_rgbaFiles[fileIndex]; } /** * Get a rgba file at the specified index. * @param fileIndex * Index of the rgba file. * @return * Metric file at the index. */ const RgbaFile* BrainStructure::getRgbaFile(const int32_t fileIndex) const { CaretAssertVectorIndex(m_rgbaFiles, fileIndex); return m_rgbaFiles[fileIndex]; } /** * Get all of the rgba files. * @param rgbaFilesOut * Will contain all rgba files after this method exits. */ void BrainStructure::getRgbaFiles(std::vector& rgbaFilesOut) const { rgbaFilesOut.clear(); rgbaFilesOut.insert(rgbaFilesOut.end(), m_rgbaFiles.begin(), m_rgbaFiles.end()); } /** * Receive events from the event manager. * * @param event * The event. */ void BrainStructure::receiveEvent(Event* event) { if (event->getEventType() == EventTypeEnum::EVENT_BRAIN_STRUCTURE_GET_ALL) { EventBrainStructureGetAll* brainStructureEvent = dynamic_cast(event); CaretAssert(brainStructureEvent); brainStructureEvent->addBrainStructure(this); brainStructureEvent->setEventProcessed(); } else if (event->getEventType() == EventTypeEnum::EVENT_GET_NODE_DATA_FILES) { EventNodeDataFilesGet* dataFilesEvent = dynamic_cast(event); CaretAssert(dataFilesEvent); const Surface* associatedSurface = dataFilesEvent->getSurface(); if (associatedSurface != NULL) { if (containsSurface(associatedSurface) == false) { return; } } for (std::vector::iterator labelIter = m_labelFiles.begin(); labelIter != m_labelFiles.end(); labelIter++) { dataFilesEvent->addFile(*labelIter); } for (std::vector::iterator metricIter = m_metricFiles.begin(); metricIter != m_metricFiles.end(); metricIter++) { dataFilesEvent->addFile(*metricIter); } for (std::vector::iterator rgbaIter = m_rgbaFiles.begin(); rgbaIter != m_rgbaFiles.end(); rgbaIter++) { dataFilesEvent->addFile(*rgbaIter); } dataFilesEvent->setEventProcessed(); } else if (event->getEventType() == EventTypeEnum::EVENT_SURFACES_GET) { EventSurfacesGet* getSurfacesEvent = dynamic_cast(event); CaretAssert(getSurfacesEvent); const int32_t numSurfaces = getNumberOfSurfaces(); for (int32_t i = 0; i < numSurfaces; i++) { getSurfacesEvent->addSurface(getSurface(i)); } getSurfacesEvent->setEventProcessed(); } else if (event->getEventType() == EventTypeEnum::EVENT_SURFACE_STRUCTURES_VALID_GET) { EventSurfaceStructuresValidGet* structEvent = dynamic_cast(event); CaretAssert(structEvent); structEvent->addStructure(m_structure, getNumberOfNodes()); } } /** * @return Return the unique identifier for this brain structure. */ int64_t BrainStructure::getBrainStructureIdentifier() const { return m_brainStructureIdentifier; } /** * Get the attributes for this brain structure. * @return * Attributes for this brain structure. */ BrainStructureNodeAttributes* BrainStructure::getNodeAttributes() { return m_nodeAttributes; } /** * Get the attributes for this brain structure. * @return * Attributes for this brain structure. */ const BrainStructureNodeAttributes* BrainStructure::getNodeAttributes() const { return m_nodeAttributes; } /** * Get all loaded data files. * @param allDataFilesOut * Data files are loaded into this parameter. */ void BrainStructure::getAllDataFiles(std::vector& allDataFilesOut) const { allDataFilesOut.insert(allDataFilesOut.end(), m_surfaces.begin(), m_surfaces.end()); allDataFilesOut.insert(allDataFilesOut.end(), m_labelFiles.begin(), m_labelFiles.end()); allDataFilesOut.insert(allDataFilesOut.end(), m_metricFiles.begin(), m_metricFiles.end()); allDataFilesOut.insert(allDataFilesOut.end(), m_rgbaFiles.begin(), m_rgbaFiles.end()); } /** * Remove the data file from memory but DO NOT delete it. * * @param caretDataFile * Caret file that is removed from the Brain. After calling this method * and the file was removed( true was returned), the caller is responsible * for deleting the file when it is no longer needed. * @return * True if the file was removed, else false. */ bool BrainStructure::removeWithoutDeleteDataFile(const CaretDataFile* caretDataFile) { std::vector::iterator surfaceIterator = std::find(m_surfaces.begin(), m_surfaces.end(), caretDataFile); if (surfaceIterator != m_surfaces.end()) { Surface* s = *surfaceIterator; removeAndMaybeDeleteSurface(s, false); return true; } std::vector::iterator labelIterator = std::find(m_labelFiles.begin(), m_labelFiles.end(), caretDataFile); if (labelIterator != m_labelFiles.end()) { m_labelFiles.erase(labelIterator); return true; } std::vector::iterator metricIterator = std::find(m_metricFiles.begin(), m_metricFiles.end(), caretDataFile); if (metricIterator != m_metricFiles.end()) { m_metricFiles.erase(metricIterator); return true; } std::vector::iterator rgbaIterator = std::find(m_rgbaFiles.begin(), m_rgbaFiles.end(), caretDataFile); if (rgbaIterator != m_rgbaFiles.end()) { m_rgbaFiles.erase(rgbaIterator); return true; } return false; } ///** // * Remove AND DELETE a data file from memory (does NOT delete file on disk.) // * Searches all of the loaded files for given file, and, when found // * deletes the file. // * // * @param caretDataFile // * Data file to remove. After calling this method and the file was // * deleted (true was returned) this pointer is no longer valid. // * @return // * true if file was removed, else false. // */ //bool //BrainStructure::removeAndDeleteDataFile(CaretDataFile* caretDataFile) //{ // if (removeWithoutDeleteDataFile(caretDataFile)) { // delete caretDataFile; // return true; // } // // return false; // std::vector::iterator surfaceIterator = // std::find(m_surfaces.begin(), // m_surfaces.end(), // caretDataFile); // if (surfaceIterator != m_surfaces.end()) { // Surface* s = *surfaceIterator; // removeSurface(s); // return true; // } // // std::vector::iterator labelIterator = // std::find(m_labelFiles.begin(), // m_labelFiles.end(), // caretDataFile); // if (labelIterator != m_labelFiles.end()) { // delete caretDataFile; // m_labelFiles.erase(labelIterator); // return true; // } // // std::vector::iterator metricIterator = // std::find(m_metricFiles.begin(), // m_metricFiles.end(), // caretDataFile); // if (metricIterator != m_metricFiles.end()) { // delete caretDataFile; // m_metricFiles.erase(metricIterator); // return true; // } // // std::vector::iterator rgbaIterator = // std::find(m_rgbaFiles.begin(), // m_rgbaFiles.end(), // caretDataFile); // if (rgbaIterator != m_rgbaFiles.end()) { // delete caretDataFile; // m_rgbaFiles.erase(rgbaIterator); // return true; // } // // return false; //} /** * Find a map in a metric file that contains shape data. * It first looks for a shape NIFTI intent code. If that * is not found it looks for curvature, shape, depth, etc. * @param metricFileOut * Output metric file that contains the shape map. * @param shapeMapIndexOut * Output containing index of shape map. * @return * True if a shape map was found, else false. */ bool BrainStructure::getMetricShapeMap(MetricFile* &shapeMetricFileOut, int32_t& shapeMapIndexOut) const { shapeMetricFileOut = NULL; shapeMapIndexOut = -1; MetricFile* depthMetricFile = NULL; int32_t depthMapIndex = -1; MetricFile* depthNamedMetricFile = NULL; MetricFile* curvatureMetricFile = NULL; int32_t curvatureMapIndex = -1; MetricFile* curvatureNamedMetricFile = NULL; MetricFile* shapeNamedMetricFile = NULL; MetricFile* sulcMetricFile = NULL; int32_t sulcMapIndex = -1; MetricFile* sulcNamedMetricFile = NULL; const int numFiles = m_metricFiles.size(); for (int32_t i = 0; i < numFiles; i++) { MetricFile* mf = m_metricFiles[i]; const AString filename = mf->getFileNameNoPath().toLower(); const int32_t numMaps = mf->getNumberOfMaps(); for (int32_t j = 0; j < numMaps; j++) { const AString mapName = mf->getMapName(j).toLower(); if (mapName.contains("sulc")) { if (sulcMetricFile == NULL) { sulcMetricFile = mf; sulcMapIndex = j; } } else if (mapName.contains("depth")) { if (depthMetricFile == NULL) { depthMetricFile = mf; depthMapIndex = j; } } else if (mapName.contains("curv")) { if (curvatureMetricFile == NULL) { curvatureMetricFile = mf; curvatureMapIndex = j; } } } if (filename.contains("sulc")) { if (numMaps > 0) { sulcNamedMetricFile = mf; } } if (filename.contains("shape")) { if (numMaps > 0) { shapeNamedMetricFile = mf; } } if (filename.contains("curv")) { if (numMaps > 0) { curvatureNamedMetricFile = mf; } } if (filename.contains("depth")) { if (numMaps > 0) { depthNamedMetricFile = mf; } } } if (sulcMetricFile != NULL) { shapeMetricFileOut = sulcMetricFile; shapeMapIndexOut = sulcMapIndex; } else if (depthMetricFile != NULL) { shapeMetricFileOut = depthMetricFile; shapeMapIndexOut = depthMapIndex; } else if (curvatureMetricFile != NULL) { shapeMetricFileOut = curvatureMetricFile; shapeMapIndexOut = curvatureMapIndex; } else if (sulcNamedMetricFile != NULL) { shapeMetricFileOut = sulcNamedMetricFile; shapeMapIndexOut = 0; } else if (depthNamedMetricFile != NULL) { shapeMetricFileOut = depthNamedMetricFile; shapeMapIndexOut = 0; } else if (curvatureNamedMetricFile != NULL) { shapeMetricFileOut = curvatureNamedMetricFile; shapeMapIndexOut = 0; } else if (shapeNamedMetricFile != NULL) { shapeMetricFileOut = shapeNamedMetricFile; shapeMapIndexOut = 0; } if ((shapeMetricFileOut != NULL) && (shapeMapIndexOut >= 0)) { return true; } return false; } /** * Get the overlay set for the given tab. * @param tabIndex * Index of tab. * @return * Overlay set at the given tab index. */ OverlaySet* BrainStructure::getOverlaySet(const int tabIndex) { return m_overlaySetArray->getOverlaySet(tabIndex); } /** * Get the overlay set for the given tab. * @param tabIndex * Index of tab. * @return * Overlay set at the given tab index. */ const OverlaySet* BrainStructure::getOverlaySet(const int tabIndex) const { return m_overlaySetArray->getOverlaySet(tabIndex); } /** * Initilize the overlays for this model. */ void BrainStructure::initializeOverlays() { m_overlaySetArray->initializeOverlaySelections(); } /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ SceneClass* BrainStructure::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "BrainStructure", 1); sceneClass->addInteger("numberOfNodes", getNumberOfNodes()); sceneClass->addEnumeratedType("m_structure", m_structure); sceneClass->addClass(m_nodeAttributes->saveToScene(sceneAttributes, "m_nodeAttributes")); /* * Save Group/Name Selection Hierarchies */ for (std::vector::iterator labelIter = m_labelFiles.begin(); labelIter != m_labelFiles.end(); labelIter++) { LabelFile* lf = *labelIter; sceneClass->addClass(lf->getGroupAndNameHierarchyModel()->saveToScene(sceneAttributes, lf->getFileNameNoPath())); } const Surface* primAnatSurface = getPrimaryAnatomicalSurface(); if (primAnatSurface != NULL) { sceneClass->addPathName("primaryAnatomicalSurface", primAnatSurface->getFileName()); } return sceneClass; } /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * SceneClass containing the state that was previously * saved and should be restored. */ void BrainStructure::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } const int32_t numNodes = sceneClass->getIntegerValue("numberOfNodes", 0); const StructureEnum::Enum structure = sceneClass->getEnumeratedTypeValue("m_structure", StructureEnum::INVALID); /* * Since there may be multiple brain structures in scene, * match by structure-type and number of nodes */ if ((numNodes == getNumberOfNodes()) && (structure == m_structure)) { m_nodeAttributes->restoreFromScene(sceneAttributes, sceneClass->getClass("m_nodeAttributes")); /* * Save Group/Name Selection Hierarchies */ for (std::vector::iterator labelIter = m_labelFiles.begin(); labelIter != m_labelFiles.end(); labelIter++) { LabelFile* lf = *labelIter; const SceneClass* labelClass = sceneClass->getClass(lf->getFileNameNoPath()); lf->getGroupAndNameHierarchyModel()->restoreFromScene(sceneAttributes, labelClass); } } const ScenePathName* primAnatScenePathName = sceneClass->getPathName("primaryAnatomicalSurface"); if (primAnatScenePathName != NULL) { const AString surfaceFileName = primAnatScenePathName->stringValue(); if ( ! surfaceFileName.isEmpty()) { for (std::vector::iterator iter = m_surfaces.begin(); iter != m_surfaces.end(); iter++) { Surface* surface = *iter; CaretAssert(surface); if (surface->getFileName() == surfaceFileName) { setPrimaryAnatomicalSurface(surface); break; } } } } } workbench-1.1.1/src/Brain/BrainStructure.h000066400000000000000000000156321255417355300204640ustar00rootroot00000000000000 #ifndef __BRAIN_STRUCTURE_H__ #define __BRAIN_STRUCTURE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include "BrainConstants.h" #include "CaretObject.h" #include "EventListenerInterface.h" #include "SceneableInterface.h" #include "StructureEnum.h" #include "SurfaceTypeEnum.h" namespace caret { class Brain; class BrainStructureNodeAttributes; class CaretDataFile; class LabelFile; class MetricFile; class ModelSurface; class OverlaySet; class OverlaySetArray; class RgbaFile; class Surface; /** * Maintains view of some type of object. */ class BrainStructure : public CaretObject, public EventListenerInterface, public SceneableInterface { public: BrainStructure(Brain* brain, StructureEnum::Enum structure); ~BrainStructure(); void receiveEvent(Event* event); virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private: BrainStructure(const BrainStructure& s); BrainStructure& operator=(const BrainStructure& s); public: void addLabelFile(LabelFile* labelFile, const bool addFileToBrainStructure); void addMetricFile(MetricFile* metricFile, const bool addFileToBrainStructure); void addRgbaFile(RgbaFile* rgbaFile, const bool addFileToBrainStructure); void addSurface(Surface* surface, const bool addFileToBrainStructure, const bool initilizeOverlaysFlag); int getNumberOfSurfaces() const; Surface* getSurface(const int32_t indx); const Surface* getSurface(const int32_t indx) const; void getSurfacesOfType(const SurfaceTypeEnum::Enum surfaceType, std::vector& surfacesOut) const; bool containsSurface(const Surface* surface); const Surface* getPrimaryAnatomicalSurface() const; Surface* getPrimaryAnatomicalSurface(); const Surface* getSurfaceContainingTextInName(const AString& text) const; Surface* getSurfaceContainingTextInName(const AString& text); Surface* getSurfaceWithName(const AString& surfaceFileName, const bool useAbsolutePath); void getSurfaces(std::vector& surfacesOut) const; void setPrimaryAnatomicalSurface(Surface* surface); Brain* getBrain(); const Brain* getBrain() const; int32_t getNumberOfNodes() const; StructureEnum::Enum getStructure() const; int32_t getNumberOfLabelFiles() const; LabelFile* getLabelFile(const int32_t fileIndex); const LabelFile* getLabelFile(const int32_t fileIndex) const; void getLabelFiles(std::vector& labelFilesOut) const; int32_t getNumberOfMetricFiles() const; MetricFile* getMetricFile(const int32_t fileIndex); const MetricFile* getMetricFile(const int32_t fileIndex) const; void getMetricFiles(std::vector& metricFilesOut) const; int32_t getNumberOfRgbaFiles() const; RgbaFile* getRgbaFile(const int32_t fileIndex); const RgbaFile* getRgbaFile(const int32_t fileIndex) const; void getRgbaFiles(std::vector& labelFilesOut) const; int64_t getBrainStructureIdentifier() const; BrainStructureNodeAttributes* getNodeAttributes(); const BrainStructureNodeAttributes* getNodeAttributes() const; void getAllDataFiles(std::vector& allDataFilesOut) const; bool removeWithoutDeleteDataFile(const CaretDataFile* caretDataFile); //bool removeAndDeleteDataFile(CaretDataFile* caretDataFile); bool getMetricShapeMap(MetricFile* &metricFileOut, int32_t& shapeMapIndexOut) const; OverlaySet* getOverlaySet(const int tabIndex); const OverlaySet* getOverlaySet(const int tabIndex) const; void initializeOverlays(); private: const Surface* getPrimaryAnatomicalSurfacePrivate() const; const Surface* getSurfaceContainingTextInNamePrivate(const AString& text) const; bool removeAndMaybeDeleteSurface(Surface* surface, const bool deleteSurfaceFile); Brain* m_brain; StructureEnum::Enum m_structure; /** Overlays sets for this model and for each tab */ //OverlaySet* m_overlaySet[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; OverlaySetArray* m_overlaySetArray; std::vector m_surfaces; std::vector m_labelFiles; std::vector m_metricFiles; std::vector m_rgbaFiles; /** Maps a surface to its model */ std::map m_surfaceModelMap; /** Unique number assigned to each brain structure. */ int64_t m_brainStructureIdentifier; /** Generates unique number assigned to each brain structure */ static int64_t s_brainStructureIdentifierCounter; BrainStructureNodeAttributes* m_nodeAttributes; mutable Surface* m_primaryAnatomicalSurface; }; #ifdef __BRAIN_STRUCTURE_DEFINE__ int64_t BrainStructure::s_brainStructureIdentifierCounter = 1; #endif // __BRAIN_STRUCTURE_DEFINE__ } // namespace #endif // __BRAIN_STRUCTURE_H__ workbench-1.1.1/src/Brain/BrainStructureNodeAttributes.cxx000066400000000000000000000066721255417355300237200ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __BRAIN_STRUCTURE_NODE_ATTRIBUTE_DECLARE__ #include "BrainStructureNodeAttributes.h" #undef __BRAIN_STRUCTURE_NODE_ATTRIBUTE_DECLARE__ #include "CaretAssert.h" #include "SceneClass.h" using namespace caret; /** * \class caret::BrainStructureNodeAttributes * \brief Contains attributes for all node in a brain structure. * * Contains attributes for all nodes in a brain structure. * If the number of nodes in the brain structure changes, * this class' update() method must be called. */ /** * Constructor. */ BrainStructureNodeAttributes::BrainStructureNodeAttributes() : CaretObject() { this->update(0); } /** * Destructor. */ BrainStructureNodeAttributes::~BrainStructureNodeAttributes() { } /** * Get a description of this object's content. * @return String describing this object's content. */ AString BrainStructureNodeAttributes::toString() const { return ("BrainStructureNodeAttributes"); } void BrainStructureNodeAttributes::update(const int32_t /*numberOfNodes*/) { // if (numberOfNodes > 0) { // m_identificationType.resize(numberOfNodes); // this->setAllIdentificationNone(); // } // else { // m_identificationType.clear(); // } } /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ SceneClass* BrainStructureNodeAttributes::saveToScene(const SceneAttributes* /*sceneAttributes*/, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "BrainStructureNodeAttributes", 1); return sceneClass; } /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * SceneClass containing the state that was previously * saved and should be restored. */ void BrainStructureNodeAttributes::restoreFromScene(const SceneAttributes* /*sceneAttributes*/, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } } workbench-1.1.1/src/Brain/BrainStructureNodeAttributes.h000066400000000000000000000040471255417355300233370ustar00rootroot00000000000000#ifndef __BRAIN_STRUCTURE_NODE_ATTRIBUTE__H_ #define __BRAIN_STRUCTURE_NODE_ATTRIBUTE__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "SceneableInterface.h" namespace caret { class BrainStructureNodeAttributes : public CaretObject, public SceneableInterface { public: BrainStructureNodeAttributes(); virtual ~BrainStructureNodeAttributes(); void update(const int32_t numberOfNodes); virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private: BrainStructureNodeAttributes(const BrainStructureNodeAttributes&); BrainStructureNodeAttributes& operator=(const BrainStructureNodeAttributes&); public: virtual AString toString() const; private: }; #ifdef __BRAIN_STRUCTURE_NODE_ATTRIBUTE_DECLARE__ // #endif // __BRAIN_STRUCTURE_NODE_ATTRIBUTE_DECLARE__ } // namespace #endif //__BRAIN_STRUCTURE_NODE_ATTRIBUTE__H_ workbench-1.1.1/src/Brain/BrowserTabContent.cxx000066400000000000000000003667651255417355300215100ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #define __BROWSER_TAB_CONTENT_DECLARE__ #include "BrowserTabContent.h" #undef __BROWSER_TAB_CONTENT_DECLARE__ #include "BorderFile.h" #include "Brain.h" #include "BrainOpenGLViewportContent.h" #include "BrainStructure.h" #include "CaretAssert.h" #include "CaretDataFileSelectionModel.h" #include "CaretLogger.h" #include "CaretMappableDataFileAndMapSelectionModel.h" #include "ChartData.h" #include "ChartMatrixDisplayProperties.h" #include "CaretPreferences.h" #include "ChartableMatrixInterface.h" #include "ChartModelDataSeries.h" #include "ClippingPlaneGroup.h" #include "GroupAndNameHierarchyGroup.h" #include "GroupAndNameHierarchyModel.h" #include "GroupAndNameHierarchyName.h" #include "DeveloperFlagsEnum.h" #include "DisplayPropertiesBorders.h" #include "DisplayPropertiesFoci.h" #include "EventCaretMappableDataFileMapsViewedInOverlays.h" #include "EventIdentificationHighlightLocation.h" #include "EventModelGetAll.h" #include "EventManager.h" #include "FociFile.h" #include "IdentificationManager.h" #include "LabelFile.h" #include "MathFunctions.h" #include "Matrix4x4.h" #include "ModelChart.h" #include "ModelSurface.h" #include "ModelSurfaceMontage.h" #include "ModelSurfaceSelector.h" #include "ModelTransform.h" #include "ModelVolume.h" #include "ModelWholeBrain.h" #include "Overlay.h" #include "OverlaySet.h" #include "SceneAttributes.h" #include "SceneClass.h" #include "SceneClassAssistant.h" #include "SessionManager.h" #include "Surface.h" #include "SurfaceMontageConfigurationCerebellar.h" #include "SurfaceMontageConfigurationCerebral.h" #include "SurfaceMontageConfigurationFlatMaps.h" #include "SurfaceSelectionModel.h" #include "StructureEnum.h" #include "VolumeFile.h" #include "ViewingTransformations.h" #include "ViewingTransformationsCerebellum.h" #include "ViewingTransformationsVolume.h" #include "VolumeSliceSettings.h" #include "VolumeSurfaceOutlineModel.h" #include "VolumeSurfaceOutlineSetModel.h" #include "WholeBrainSurfaceSettings.h" using namespace caret; /** * Constructor. * @param tabNumber * Number for this tab. */ BrowserTabContent::BrowserTabContent(const int32_t tabNumber) : CaretObject() { isExecutingConstructor = true; s_allBrowserTabContent.insert(this); const CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); m_tabNumber = tabNumber; m_surfaceModelSelector = new ModelSurfaceSelector(); m_selectedModelType = ModelTypeEnum::MODEL_TYPE_INVALID; m_volumeModel = NULL; m_wholeBrainModel = NULL; m_surfaceMontageModel = NULL; m_chartModel = NULL; m_guiName = ""; m_userName = ""; m_volumeSurfaceOutlineSetModel = new VolumeSurfaceOutlineSetModel(); m_yokingGroup = YokingGroupEnum::YOKING_GROUP_OFF; m_identificationUpdatesVolumeSlices = prefs->isVolumeIdentificationDefaultedOn(); m_cerebellumViewingTransformation = new ViewingTransformationsCerebellum(); m_flatSurfaceViewingTransformation = new ViewingTransformations(); m_viewingTransformation = new ViewingTransformations(); m_volumeSliceViewingTransformation = new ViewingTransformationsVolume(); m_wholeBrainSurfaceSettings = new WholeBrainSurfaceSettings(); m_obliqueVolumeRotationMatrix = new Matrix4x4(); leftView(); m_volumeSliceSettings = new VolumeSliceSettings(); m_clippingPlaneGroup = new ClippingPlaneGroup(); m_sceneClassAssistant = new SceneClassAssistant(); m_sceneClassAssistant->add("m_tabNumber", &m_tabNumber); m_sceneClassAssistant->add("m_userName", &m_userName); m_sceneClassAssistant->add("m_selectedModelType", &m_selectedModelType); m_sceneClassAssistant->add("m_surfaceModelSelector", "ModelSurfaceSelector", m_surfaceModelSelector); m_sceneClassAssistant->add("m_volumeSurfaceOutlineSetModel", "VolumeSurfaceOutlineSetModel", m_volumeSurfaceOutlineSetModel); m_sceneClassAssistant->add("m_clippingPlaneGroup", "ClippingPlaneGroup", m_clippingPlaneGroup); m_sceneClassAssistant->add("m_cerebellumViewingTransformation", "ViewingTransformations", m_cerebellumViewingTransformation); m_sceneClassAssistant->add("m_flatSurfaceViewingTransformation", "ViewingTransformations", m_flatSurfaceViewingTransformation); m_sceneClassAssistant->add("m_viewingTransformation", "ViewingTransformations", m_viewingTransformation); m_sceneClassAssistant->add("m_volumeSliceViewingTransformation", "ViewingTransformations", m_volumeSliceViewingTransformation); m_sceneClassAssistant->add("m_volumeSliceSettings", "VolumeSliceSettings", m_volumeSliceSettings); m_sceneClassAssistant->add("m_wholeBrainSurfaceSettings", "WholeBrainSurfaceSettings", m_wholeBrainSurfaceSettings); m_sceneClassAssistant->add("m_identificationUpdatesVolumeSlices", &m_identificationUpdatesVolumeSlices); m_sceneClassAssistant->add("m_yokingGroup", &m_yokingGroup); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_IDENTIFICATION_HIGHLIGHT_LOCATION); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_CARET_MAPPABLE_DATA_FILE_MAPS_VIEWED_IN_OVERLAYS); isExecutingConstructor = false; /* * Need to be done from here */ if (prefs->isYokingDefaultedOn()) { setYokingGroup(YokingGroupEnum::YOKING_GROUP_A); } } /** * Destructor. */ BrowserTabContent::~BrowserTabContent() { EventManager::get()->removeAllEventsFromListener(this); s_allBrowserTabContent.erase(this); delete m_clippingPlaneGroup; delete m_flatSurfaceViewingTransformation; delete m_cerebellumViewingTransformation; delete m_viewingTransformation; delete m_volumeSliceViewingTransformation; delete m_obliqueVolumeRotationMatrix; delete m_surfaceModelSelector; m_surfaceModelSelector = NULL; delete m_volumeSurfaceOutlineSetModel; m_volumeSurfaceOutlineSetModel = NULL; delete m_volumeSliceSettings; delete m_wholeBrainSurfaceSettings; delete m_sceneClassAssistant; m_sceneClassAssistant = NULL; } /** * Clone the contents of the given browser tab. * @param tabToClone * Tab whose contents is cloned. */ void BrowserTabContent::cloneBrowserTabContent(BrowserTabContent* tabToClone) { CaretAssert(tabToClone); m_surfaceModelSelector->setSelectedStructure(tabToClone->m_surfaceModelSelector->getSelectedStructure()); m_surfaceModelSelector->setSelectedSurfaceModel(tabToClone->m_surfaceModelSelector->getSelectedSurfaceModel()); m_selectedModelType = tabToClone->m_selectedModelType; /* * */ EventModelGetAll allModelsEvent; EventManager::get()->sendEvent(allModelsEvent.getPointer()); std::vector allModels = allModelsEvent.getModels(); for (std::vector::iterator modelIter = allModels.begin(); modelIter != allModels.end(); modelIter++) { Model* model = *modelIter; model->copyTabContent(tabToClone->m_tabNumber, m_tabNumber); } *m_clippingPlaneGroup = *tabToClone->m_clippingPlaneGroup; m_yokingGroup = tabToClone->m_yokingGroup; *m_cerebellumViewingTransformation = *tabToClone->m_cerebellumViewingTransformation; *m_flatSurfaceViewingTransformation = *tabToClone->m_flatSurfaceViewingTransformation; *m_viewingTransformation = *tabToClone->m_viewingTransformation; *m_volumeSliceViewingTransformation = *tabToClone->m_volumeSliceViewingTransformation; *m_volumeSliceSettings = *tabToClone->m_volumeSliceSettings; *m_wholeBrainSurfaceSettings = *tabToClone->m_wholeBrainSurfaceSettings; *m_obliqueVolumeRotationMatrix = *tabToClone->m_obliqueVolumeRotationMatrix; m_identificationUpdatesVolumeSlices = tabToClone->m_identificationUpdatesVolumeSlices; Model* model = getModelForDisplay(); if (model != NULL) { Brain* brain = model->getBrain(); brain->copyDisplayProperties(tabToClone->getTabNumber(), getTabNumber()); const int32_t numberOfBrainStructures = brain->getNumberOfBrainStructures(); for (int32_t i = 0; i < numberOfBrainStructures; i++) { BrainStructure* bs = brain->getBrainStructure(i); const int32_t numLabelFiles = bs->getNumberOfLabelFiles(); for (int32_t j = 0; j < numLabelFiles; j++) { LabelFile* labelFile = bs->getLabelFile(j); labelFile->getGroupAndNameHierarchyModel()->copySelections(tabToClone->getTabNumber(), getTabNumber()); } } const int32_t numBorderFiles = brain->getNumberOfBorderFiles(); for (int32_t i = 0; i < numBorderFiles; i++) { BorderFile* bf = brain->getBorderFile(i); bf->getGroupAndNameHierarchyModel()->copySelections(tabToClone->getTabNumber(), getTabNumber()); } const int32_t numFociFiles = brain->getNumberOfFociFiles(); for (int32_t i = 0; i < numFociFiles; i++) { FociFile* ff = brain->getFociFile(i); ff->getGroupAndNameHierarchyModel()->copySelections(tabToClone->getTabNumber(), getTabNumber()); } } m_volumeSurfaceOutlineSetModel->copyVolumeSurfaceOutlineSetModel(tabToClone->getVolumeSurfaceOutlineSet()); } /** * Get the name of this browser tab. * Name priority is (1) name set by user, (2) name set by * user-interface, and (3) the default name. * * @return Name of this tab. */ AString BrowserTabContent::getName() const { AString s = "(" + AString::number(m_tabNumber + 1) + ") "; if (m_userName.isEmpty() == false) { s += m_userName; } else { const Model* displayedModel = getModelForDisplay(); if (displayedModel != NULL) { const AString name = displayedModel->getNameForBrowserTab(); s += name; } } return s; } /** * Set the user name of this tab. The user name * overrides the default naming. * * @param userName * User name for tab. */ void BrowserTabContent::setUserName(const AString& userName) { m_userName = userName; } /** * @return The user name. */ AString BrowserTabContent::getUserName() const { return m_userName; } /** * Get a description of this object's content. * @return String describing this object's content. */ AString BrowserTabContent::toString() const { PlainTextStringBuilder tb; getDescriptionOfContent(tb); return tb.getText(); } /** * Get a text description of the window's content. * * @param descriptionOut * Description of the window's content. */ void BrowserTabContent::getDescriptionOfContent(PlainTextStringBuilder& descriptionOut) const { const int32_t tabIndex = getTabNumber(); descriptionOut.addLine("Browser Tab " + AString::number(tabIndex + 1) + ": "); descriptionOut.pushIndentation(); const Model* model = getModelForDisplay(); if (model != NULL) { bool chartFlag = false; bool surfaceFlag = false; bool surfaceMontageFlag = false; bool wholeBrainFlag = false; bool volumeFlag = false; switch (model->getModelType()) { case ModelTypeEnum::MODEL_TYPE_CHART: chartFlag = true; break; case ModelTypeEnum::MODEL_TYPE_INVALID: break; case ModelTypeEnum::MODEL_TYPE_SURFACE: surfaceFlag = true; break; case ModelTypeEnum::MODEL_TYPE_SURFACE_MONTAGE: surfaceMontageFlag = true; break; case ModelTypeEnum::MODEL_TYPE_VOLUME_SLICES: volumeFlag = true; break; case ModelTypeEnum::MODEL_TYPE_WHOLE_BRAIN: wholeBrainFlag = true; break; } if (chartFlag) { model->getDescriptionOfContent(tabIndex, descriptionOut); } else if (volumeFlag) { descriptionOut.addLine("Volume Slice View"); } else if (wholeBrainFlag) { descriptionOut.addLine("All View"); descriptionOut.pushIndentation(); if (isWholeBrainCerebellumEnabled()) { const Surface* cerebellumSurface = m_wholeBrainModel->getSelectedSurface(StructureEnum::CEREBELLUM, tabIndex); if (cerebellumSurface != NULL) { cerebellumSurface->getDescriptionOfContent(descriptionOut); } } if (isWholeBrainLeftEnabled()) { const Surface* leftSurface = m_wholeBrainModel->getSelectedSurface(StructureEnum::CORTEX_LEFT, tabIndex); if (leftSurface != NULL) { leftSurface->getDescriptionOfContent(descriptionOut); } } if (isWholeBrainRightEnabled()) { const Surface* rightSurface = m_wholeBrainModel->getSelectedSurface(StructureEnum::CORTEX_RIGHT, tabIndex); if (rightSurface != NULL) { rightSurface->getDescriptionOfContent(descriptionOut); } } descriptionOut.popIndentation(); } else if (surfaceFlag) { model->getDescriptionOfContent(tabIndex, descriptionOut); } else if (surfaceMontageFlag) { model->getDescriptionOfContent(tabIndex, descriptionOut); } if (wholeBrainFlag || volumeFlag) { descriptionOut.pushIndentation(); m_volumeSliceSettings->getDescriptionOfContent(model->getModelType(), descriptionOut); descriptionOut.popIndentation(); } if ( ! chartFlag) { getOverlaySet()->getDescriptionOfContent(descriptionOut); } } descriptionOut.popIndentation(); } /** * Get the selected model type. * * @return The selected model type. */ ModelTypeEnum::Enum BrowserTabContent::getSelectedModelType() const { return m_selectedModelType; } /** * Set the selected model type. * * @param selectedModelType * New selected model type. */ void BrowserTabContent::setSelectedModelType(ModelTypeEnum::Enum selectedModelType) { m_selectedModelType = selectedModelType; } /** * Get the model for DISPLAY. * * @return Pointer to displayed model or NULL * if none are available. */ Model* BrowserTabContent::getModelForDisplay() { Model* mdc = NULL; switch (m_selectedModelType) { case ModelTypeEnum::MODEL_TYPE_INVALID: break; case ModelTypeEnum::MODEL_TYPE_SURFACE: mdc = m_surfaceModelSelector->getSelectedSurfaceModel(); break; case ModelTypeEnum::MODEL_TYPE_SURFACE_MONTAGE: mdc = m_surfaceMontageModel; break; case ModelTypeEnum::MODEL_TYPE_VOLUME_SLICES: mdc = m_volumeModel; break; case ModelTypeEnum::MODEL_TYPE_WHOLE_BRAIN: mdc = m_wholeBrainModel; break; case ModelTypeEnum::MODEL_TYPE_CHART: mdc = m_chartModel; break; } return mdc; } /** * Get the model model for DISPLAY. * * @return Pointer to displayed model or NULL * if none are available. */ const Model* BrowserTabContent::getModelForDisplay() const { Model* mdc = NULL; switch (m_selectedModelType) { case ModelTypeEnum::MODEL_TYPE_INVALID: break; case ModelTypeEnum::MODEL_TYPE_SURFACE: mdc = m_surfaceModelSelector->getSelectedSurfaceModel(); break; case ModelTypeEnum::MODEL_TYPE_SURFACE_MONTAGE: mdc = m_surfaceMontageModel; break; case ModelTypeEnum::MODEL_TYPE_VOLUME_SLICES: mdc = m_volumeModel; break; case ModelTypeEnum::MODEL_TYPE_WHOLE_BRAIN: mdc = m_wholeBrainModel; break; case ModelTypeEnum::MODEL_TYPE_CHART: mdc = m_chartModel; break; } return mdc; } /** * Get the displayed chart model. * * @return Pointer to displayed chart model or * NULL if the displayed model is NOT a * chart. */ ModelChart* BrowserTabContent::getDisplayedChartModel() { ModelChart* mc = dynamic_cast(getModelForDisplay()); return mc; } /** * Get the displayed chart model. * * @return Pointer to displayed chart model or * NULL if the displayed model is NOT a * chart. */ const ModelChart* BrowserTabContent::getDisplayedChartModel() const { const ModelChart* mc = dynamic_cast(getModelForDisplay()); return mc; } /** * Get the displayed surface model. * * @return Pointer to displayed surface model or * NULL if the displayed model is NOT a * surface. */ ModelSurface* BrowserTabContent::getDisplayedSurfaceModel() { ModelSurface* mdcs = dynamic_cast(getModelForDisplay()); return mdcs; } /** * Get the displayed surface model. * * @return Pointer to displayed surface model or * NULL if the displayed model is NOT a * surface. */ const ModelSurface* BrowserTabContent::getDisplayedSurfaceModel() const { const ModelSurface* mdcs = dynamic_cast(getModelForDisplay()); return mdcs; } /** * Get the displayed volume model. * * @return Pointer to displayed volume model or * NULL if the displayed model is NOT a * volume. */ ModelVolume* BrowserTabContent::getDisplayedVolumeModel() { ModelVolume* mdcv = dynamic_cast(getModelForDisplay()); return mdcv; } /** * @return True if the displayed model is a cerebellum surface. */ bool BrowserTabContent::isCerebellumDisplayed() const { const ModelSurface* surfaceModel = getDisplayedSurfaceModel(); if (surfaceModel != NULL) { if (surfaceModel->getSurface()->getStructure() == StructureEnum::CEREBELLUM) { return true; } } const ModelSurfaceMontage* montageModel = getDisplayedSurfaceMontageModel(); if (montageModel != NULL) { if (montageModel->getSelectedConfigurationType(getTabNumber()) == SurfaceMontageConfigurationTypeEnum::CEREBELLAR_CORTEX_CONFIGURATION) { return true; } } return false; } /** * @return True if the displayed model is a flat surface. */ bool BrowserTabContent::isFlatSurfaceDisplayed() const { const ModelSurface* surfaceModel = getDisplayedSurfaceModel(); if (surfaceModel != NULL) { if (surfaceModel->getSurface()->getSurfaceType() == SurfaceTypeEnum::FLAT) { return true; } } const ModelSurfaceMontage* montageModel = getDisplayedSurfaceMontageModel(); if (montageModel != NULL) { if (montageModel->getSelectedConfigurationType(getTabNumber()) == SurfaceMontageConfigurationTypeEnum::FLAT_CONFIGURATION) { return true; } } return false; } /** * @return True if the displayed model is a chart */ bool BrowserTabContent::isChartDisplayed() const { const ModelChart* chartModel = getDisplayedChartModel(); if (chartModel != NULL) { return true; } return false; } /** * @return Is the displayed model a volume slice model? */ bool BrowserTabContent::isVolumeSlicesDisplayed() const { const ModelVolume* mdcv = dynamic_cast(getModelForDisplay()); const bool volumeFlag = (mdcv != NULL); return volumeFlag; } /** * @return Is the displayed model the whole brain model (ALL)? */ bool BrowserTabContent::isWholeBrainDisplayed() const { const ModelWholeBrain* mwb = dynamic_cast(getModelForDisplay()); const bool wholeBrainFlag = (mwb != NULL); return wholeBrainFlag; } /** * Get the displayed whole brain model. * * @return Pointer to displayed whole brain model or * NULL if the displayed model is NOT a * whole brain. */ ModelWholeBrain* BrowserTabContent::getDisplayedWholeBrainModel() { ModelWholeBrain* mdcwb = dynamic_cast(getModelForDisplay()); return mdcwb; } /** * @return Pointer to displayed surface montage model * or NULL if the displayed model is not a surface * montage model. */ ModelSurfaceMontage* BrowserTabContent::getDisplayedSurfaceMontageModel() { ModelSurfaceMontage* mdcsm = dynamic_cast(getModelForDisplay()); return mdcsm; } /** * @return Pointer to displayed surface montage model * or NULL if the displayed model is not a surface * montage model. */ const ModelSurfaceMontage* BrowserTabContent::getDisplayedSurfaceMontageModel() const { const ModelSurfaceMontage* mdcsm = dynamic_cast(getModelForDisplay()); return mdcsm; } /** * Get all of the available surface models. * * @return Vector containing all surface models. */ const std::vector BrowserTabContent::getAllSurfaceModels() const { return m_allSurfaceModels; } /** * @return The surface model selector used to * select a surface and structure. */ ModelSurfaceSelector* BrowserTabContent::getSurfaceModelSelector() { return m_surfaceModelSelector; } /** * Get the overlay assignments for this tab. * * @return Overlay assignments for this tab or NULL if no valid model. */ OverlaySet* BrowserTabContent::getOverlaySet() { Model* model = getModelForDisplay(); if (model != NULL) { return model->getOverlaySet(m_tabNumber); } return NULL; } /** * Get the overlay assignments for this tab. * * @return Overlay assignments for this tab or NULL if no valid model. */ const OverlaySet* BrowserTabContent::getOverlaySet() const { const Model* model = getModelForDisplay(); if (model != NULL) { return model->getOverlaySet(m_tabNumber); } return NULL; } /** * Get the tab number for this content. * * @return Tab number. */ int32_t BrowserTabContent::getTabNumber() const { return m_tabNumber; } /** * Update the selected models. */ void BrowserTabContent::update(const std::vector models) { m_surfaceModelSelector->updateSelector(models); const int32_t numModels = static_cast(models.size()); ModelVolume* previousVolumeModel = m_volumeModel; m_allSurfaceModels.clear(); m_surfaceModelSelector->getSelectableSurfaceModels(m_allSurfaceModels); m_volumeModel = NULL; m_wholeBrainModel = NULL; m_surfaceMontageModel = NULL; m_chartModel = NULL; for (int i = 0; i < numModels; i++) { Model* mdc = models[i]; ModelSurface* mdcs = dynamic_cast(mdc); ModelVolume* mdcv = dynamic_cast(mdc); ModelWholeBrain* mdcwb = dynamic_cast(mdc); ModelSurfaceMontage* mdcsm = dynamic_cast(mdc); ModelChart* mdch = dynamic_cast(mdc); if (mdcs != NULL) { /* nothing to do since the surface model selector handles surfaces */ } else if (mdcv != NULL) { CaretAssertMessage((m_volumeModel == NULL), "There is more than one volume model."); m_volumeModel = mdcv; } else if (mdcwb != NULL) { CaretAssertMessage((m_wholeBrainModel == NULL), "There is more than one whole brain model."); m_wholeBrainModel = mdcwb; } else if (mdcsm != NULL) { CaretAssertMessage((m_surfaceMontageModel == NULL), "There is more than one surface montage model."); m_surfaceMontageModel = mdcsm; } else if (mdch != NULL) { CaretAssertMessage((m_chartModel == NULL), "There is more than one surface chart model."); m_chartModel = mdch; } else { CaretAssertMessage(0, (AString("Unknown type of brain model.") + mdc->getNameForGUI(true))); } } switch (m_selectedModelType) { case ModelTypeEnum::MODEL_TYPE_INVALID: break; case ModelTypeEnum::MODEL_TYPE_SURFACE: if (m_surfaceModelSelector->getSelectedSurfaceModel() == NULL) { m_selectedModelType = ModelTypeEnum::MODEL_TYPE_INVALID; } break; case ModelTypeEnum::MODEL_TYPE_SURFACE_MONTAGE: if (m_surfaceMontageModel == NULL) { m_selectedModelType = ModelTypeEnum::MODEL_TYPE_INVALID; } break; case ModelTypeEnum::MODEL_TYPE_VOLUME_SLICES: if (m_volumeModel == NULL) { m_selectedModelType = ModelTypeEnum::MODEL_TYPE_INVALID; } break; case ModelTypeEnum::MODEL_TYPE_WHOLE_BRAIN: if (m_wholeBrainModel == NULL) { m_selectedModelType = ModelTypeEnum::MODEL_TYPE_INVALID; } break; case ModelTypeEnum::MODEL_TYPE_CHART: if (m_chartModel == NULL) { m_selectedModelType = ModelTypeEnum::MODEL_TYPE_INVALID; } break; } if (m_selectedModelType == ModelTypeEnum::MODEL_TYPE_INVALID) { if (m_surfaceModelSelector->getSelectedSurfaceModel() != NULL) { m_selectedModelType = ModelTypeEnum::MODEL_TYPE_SURFACE; } else if (m_volumeModel != NULL) { m_selectedModelType = ModelTypeEnum::MODEL_TYPE_VOLUME_SLICES; } else if (m_wholeBrainModel != NULL) { m_selectedModelType = ModelTypeEnum::MODEL_TYPE_WHOLE_BRAIN; } else if (m_surfaceMontageModel != NULL) { m_selectedModelType = ModelTypeEnum::MODEL_TYPE_SURFACE_MONTAGE; } else if (m_chartModel != NULL) { m_selectedModelType = ModelTypeEnum::MODEL_TYPE_CHART; } } if (m_volumeModel != NULL) { if (m_volumeModel != previousVolumeModel) { VolumeMappableInterface* underlayVolume = m_volumeModel->getOverlaySet(m_tabNumber)->getUnderlayVolume(); if (underlayVolume != NULL) { /* * Set montage slice spacing based upon slices * in the Z-axis. */ std::vector dimensions; underlayVolume->getDimensions(dimensions); if (dimensions.size() >= 3) { const int32_t dimZ = dimensions[2]; if (dimZ > 0) { const int32_t maxZ = static_cast(dimZ * 0.90); const int32_t minZ = static_cast(dimZ * 0.10); const int32_t sliceRange = (maxZ - minZ); int32_t sliceSpacing = 1; if (sliceRange > 0) { const int32_t numSlicesViewed = (m_volumeSliceSettings->getMontageNumberOfRows() * m_volumeSliceSettings->getMontageNumberOfColumns()); sliceSpacing = (sliceRange / numSlicesViewed); } m_volumeSliceSettings->setMontageSliceSpacing(sliceSpacing); } } } } } } /** * Is the chart model selection valid? * * @return bool indicating validity. */ bool BrowserTabContent::isChartModelValid() const { bool valid = (m_chartModel != NULL); return valid; } /** * Is the surface model selection valid? * * @return bool indicating validity. */ bool BrowserTabContent::isSurfaceModelValid() const { bool valid = (m_allSurfaceModels.empty() == false); return valid; } /** * Is the volume model selection valid? * * @return bool indicating validity. */ bool BrowserTabContent::isVolumeSliceModelValid() const { bool valid = (m_volumeModel != NULL); return valid; } /** * Is the whole brain model selection valid? * * @return bool indicating validity. */ bool BrowserTabContent::isWholeBrainModelValid() const { bool valid = (m_wholeBrainModel != NULL); return valid; } /** * Is the surface montage model selection valid? * * @return bool indicating validity. */ bool BrowserTabContent::isSurfaceMontageModelValid() const { bool valid = (m_surfaceMontageModel != NULL); return valid; } /** * Receive an event. * * @param event * The event that the receive can respond to. */ void BrowserTabContent::receiveEvent(Event* event) { if (event->getEventType() == EventTypeEnum::EVENT_IDENTIFICATION_HIGHLIGHT_LOCATION) { EventIdentificationHighlightLocation* idLocationEvent = dynamic_cast(event); CaretAssert(idLocationEvent); Model* model = getModelForDisplay(); if (model == NULL) { return; } if (idLocationEvent->isTabSelected(getTabNumber())) { if (isIdentificationUpdatesVolumeSlices()) { const float* highlighXYZ = idLocationEvent->getXYZ(); for (int32_t windowTabNumber = 0; windowTabNumber < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; windowTabNumber++) { float volumeSliceXYZ[3] = { highlighXYZ[0], highlighXYZ[1], highlighXYZ[2] }; /* * If othogonal/montage viewing, do not alter the slice * coordinate in the axis being viewed */ if (getDisplayedVolumeModel() != NULL) { bool keepSliceCoordinateForSelectedAxis = false; switch (m_volumeSliceSettings->getSliceProjectionType()) { case VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_ORTHOGONAL: break; case VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_OBLIQUE: keepSliceCoordinateForSelectedAxis = true; break; } switch (m_volumeSliceSettings->getSliceDrawingType()) { case VolumeSliceDrawingTypeEnum::VOLUME_SLICE_DRAW_MONTAGE: keepSliceCoordinateForSelectedAxis = true; break; case VolumeSliceDrawingTypeEnum::VOLUME_SLICE_DRAW_SINGLE: break; } if (keepSliceCoordinateForSelectedAxis) { switch (getSliceViewPlane()) { case VolumeSliceViewPlaneEnum::ALL: volumeSliceXYZ[0] = getSliceCoordinateParasagittal(); volumeSliceXYZ[1] = getSliceCoordinateCoronal(); volumeSliceXYZ[2] = getSliceCoordinateAxial(); break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: volumeSliceXYZ[0] = getSliceCoordinateParasagittal(); break; case VolumeSliceViewPlaneEnum::CORONAL: volumeSliceXYZ[1] = getSliceCoordinateCoronal(); break; case VolumeSliceViewPlaneEnum::AXIAL: volumeSliceXYZ[2] = getSliceCoordinateAxial(); break; } } } selectSlicesAtCoordinate(volumeSliceXYZ); //m_volumeSliceSettings->selectSlicesAtCoordinate(volumeSliceXYZ); } } } idLocationEvent->setEventProcessed(); } else if (event->getEventType() == EventTypeEnum::EVENT_CARET_MAPPABLE_DATA_FILE_MAPS_VIEWED_IN_OVERLAYS) { EventCaretMappableDataFileMapsViewedInOverlays* mapOverlayEvent = dynamic_cast(event); CaretAssert(mapOverlayEvent); OverlaySet* overlaySet = getOverlaySet(); const int32_t numOverlays = overlaySet->getNumberOfDisplayedOverlays(); for (int32_t i = (numOverlays - 1); i >= 0; i--) { Overlay* overlay = overlaySet->getOverlay(i); if (overlay->isEnabled()) { CaretMappableDataFile* mapFile; int32_t mapFileIndex; overlay->getSelectionData(mapFile, mapFileIndex); if (mapFile != NULL) { if (mapFile == mapOverlayEvent->getCaretMappableDataFile()) { if ((mapFileIndex >= 0) && (mapFileIndex < mapFile->getNumberOfMaps())) { mapOverlayEvent->addMapIndex(mapFileIndex); } } } } } } } /** * Get the map files for which a palette should be displayed in the * graphcis window. Note that the order of map files and indices starts * with the bottom most layer and ends with the top most overlay. * * @param mapFiles * Outut Map files that should have a palette displayed in the graphics window. * @param mapIndices * Output Indices of the maps in the mapFiles. */ void BrowserTabContent::getDisplayedPaletteMapFiles(std::vector& mapFiles, std::vector& mapIndices) { mapFiles.clear(); mapIndices.clear(); if (getModelForDisplay() == NULL) { return; } bool useOverlayFlag = false; bool useChartsFlag = false; switch (getSelectedModelType()) { case ModelTypeEnum::MODEL_TYPE_INVALID: break; case ModelTypeEnum::MODEL_TYPE_CHART: useChartsFlag = true; break; case ModelTypeEnum::MODEL_TYPE_SURFACE: useOverlayFlag = true; break; case ModelTypeEnum::MODEL_TYPE_SURFACE_MONTAGE: useOverlayFlag = true; break; case ModelTypeEnum::MODEL_TYPE_VOLUME_SLICES: useOverlayFlag = true; break; case ModelTypeEnum::MODEL_TYPE_WHOLE_BRAIN: useOverlayFlag = true; break; } if (useOverlayFlag) { OverlaySet* overlaySet = getOverlaySet(); const int32_t numOverlays = overlaySet->getNumberOfDisplayedOverlays(); for (int32_t i = (numOverlays - 1); i >= 0; i--) { Overlay* overlay = overlaySet->getOverlay(i); if (overlay->isEnabled()) { if (overlay->isPaletteDisplayEnabled()) { CaretMappableDataFile* mapFile; int32_t mapFileIndex; overlay->getSelectionData(mapFile, mapFileIndex); if (mapFile != NULL) { if (mapFile->isMappedWithPalette()) { if ((mapFileIndex >= 0) && (mapFileIndex < mapFile->getNumberOfMaps())) { mapFiles.push_back(mapFile); mapIndices.push_back(mapFileIndex); } } } } } } } if (useChartsFlag) { ModelChart* modelChart = getDisplayedChartModel(); if (modelChart != NULL) { CaretDataFileSelectionModel* fileModel = NULL; switch (modelChart->getSelectedChartDataType(m_tabNumber)) { case ChartDataTypeEnum::CHART_DATA_TYPE_INVALID: break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_DATA_SERIES: break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_FREQUENCY_SERIES: break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_TIME_SERIES: break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_LAYER: if (modelChart->getSelectedChartDataType(m_tabNumber) == ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_LAYER) { fileModel = modelChart->getChartableMatrixParcelFileSelectionModel(m_tabNumber); } break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_SERIES: if (modelChart->getSelectedChartDataType(m_tabNumber) == ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_SERIES) { fileModel = modelChart->getChartableMatrixSeriesFileSelectionModel(m_tabNumber); } } if (fileModel != NULL) { CaretDataFile* caretFile = fileModel->getSelectedFile(); if (caretFile != NULL) { CaretMappableDataFile* mapFile = dynamic_cast(caretFile); if (mapFile != NULL) { ChartableMatrixInterface* matrixFile = dynamic_cast(mapFile); if (matrixFile != NULL) { ChartMatrixDisplayProperties* props = matrixFile->getChartMatrixDisplayProperties(m_tabNumber); if (props->isColorBarDisplayed()) { /* * Matrix contains all file data and always * uses a map index of zero. */ mapFiles.push_back(mapFile); mapIndices.push_back(0); } } } } } } } } /** * @return The volume surface outline model for this tab. */ VolumeSurfaceOutlineSetModel* BrowserTabContent::getVolumeSurfaceOutlineSet() { return m_volumeSurfaceOutlineSetModel; } /** * @return The volume surface outline model for this tab. */ const VolumeSurfaceOutlineSetModel* BrowserTabContent::getVolumeSurfaceOutlineSet() const { return m_volumeSurfaceOutlineSetModel; } /** * Get the data files displayed in this tab. * @param displayedDataFilesOut * Displayed data file are loaded into this parameter. */ void BrowserTabContent::getFilesDisplayedInTab(std::vector& displayedDataFilesOut) { displayedDataFilesOut.clear(); Model* model = getModelForDisplay(); if (model == NULL) { return; } std::set displayedDataFiles; const int32_t tabIndex = getTabNumber(); switch (getSelectedModelType()) { case ModelTypeEnum::MODEL_TYPE_INVALID: break; case ModelTypeEnum::MODEL_TYPE_SURFACE: { ModelSurface* ms = getDisplayedSurfaceModel(); displayedDataFiles.insert(ms->getSurface()); } break; case ModelTypeEnum::MODEL_TYPE_SURFACE_MONTAGE: { ModelSurfaceMontage* msm = getDisplayedSurfaceMontageModel(); switch (msm->getSelectedConfigurationType(tabIndex)) { case SurfaceMontageConfigurationTypeEnum::CEREBELLAR_CORTEX_CONFIGURATION: { SurfaceMontageConfigurationCerebellar* smcc = msm->getCerebellarConfiguration(tabIndex); if (smcc->isFirstSurfaceEnabled()) { displayedDataFiles.insert(smcc->getFirstSurfaceSelectionModel()->getSurface()); } if (smcc->isSecondSurfaceEnabled()) { displayedDataFiles.insert(smcc->getSecondSurfaceSelectionModel()->getSurface()); } } break; case SurfaceMontageConfigurationTypeEnum::CEREBRAL_CORTEX_CONFIGURATION: { SurfaceMontageConfigurationCerebral* smcc = msm->getCerebralConfiguration(tabIndex); if (smcc->isFirstSurfaceEnabled()) { if (smcc->isLeftEnabled()) { displayedDataFiles.insert(smcc->getLeftFirstSurfaceSelectionModel()->getSurface()); } if (smcc->isRightEnabled()) { displayedDataFiles.insert(smcc->getRightFirstSurfaceSelectionModel()->getSurface()); } } if (smcc->isSecondSurfaceEnabled()) { if (smcc->isLeftEnabled()) { displayedDataFiles.insert(smcc->getLeftSecondSurfaceSelectionModel()->getSurface()); } if (smcc->isRightEnabled()) { displayedDataFiles.insert(smcc->getRightSecondSurfaceSelectionModel()->getSurface()); } } } break; case SurfaceMontageConfigurationTypeEnum::FLAT_CONFIGURATION: { SurfaceMontageConfigurationFlatMaps* smcfm = msm->getFlatMapsConfiguration(tabIndex); if (smcfm->isLeftEnabled()) { displayedDataFiles.insert(smcfm->getLeftSurfaceSelectionModel()->getSurface()); } if (smcfm->isRightEnabled()) { displayedDataFiles.insert(smcfm->getRightSurfaceSelectionModel()->getSurface()); } if (smcfm->isCerebellumEnabled()) { displayedDataFiles.insert(smcfm->getCerebellumSurfaceSelectionModel()->getSurface()); } } break; } } break; case ModelTypeEnum::MODEL_TYPE_VOLUME_SLICES: { const int32_t numOutlines = m_volumeSurfaceOutlineSetModel->getNumberOfDislayedVolumeSurfaceOutlines(); for (int32_t i = 0; i < numOutlines; i++) { VolumeSurfaceOutlineModel* model = m_volumeSurfaceOutlineSetModel->getVolumeSurfaceOutlineModel(i); if (model->isDisplayed()) { displayedDataFiles.insert(model->getSurface()); } } } break; case ModelTypeEnum::MODEL_TYPE_WHOLE_BRAIN: { ModelWholeBrain* wbm = getDisplayedWholeBrainModel(); if (isWholeBrainLeftEnabled()) { displayedDataFiles.insert(wbm->getSelectedSurface(StructureEnum::CORTEX_LEFT, tabIndex)); } if (isWholeBrainRightEnabled()) { displayedDataFiles.insert(wbm->getSelectedSurface(StructureEnum::CORTEX_RIGHT, tabIndex)); } if (isWholeBrainCerebellumEnabled()) { displayedDataFiles.insert(wbm->getSelectedSurface(StructureEnum::CEREBELLUM, tabIndex)); } } break; case ModelTypeEnum::MODEL_TYPE_CHART: break; } /* * Check overlay files */ OverlaySet* overlaySet = model->getOverlaySet(tabIndex); const int32_t numOverlays = overlaySet->getNumberOfDisplayedOverlays(); for (int32_t i = 0; i < numOverlays; i++) { Overlay* overlay = overlaySet->getOverlay(i); if (overlay->isEnabled()) { CaretMappableDataFile* overlayDataFile = NULL; int32_t mapIndex; overlay->getSelectionData(overlayDataFile, mapIndex); displayedDataFiles.insert(overlayDataFile); } } /* * Check border files */ Brain* brain = model->getBrain(); const DisplayPropertiesBorders* borderDisplayProperties = brain->getDisplayPropertiesBorders(); const DisplayGroupEnum::Enum borderDisplayGroup = borderDisplayProperties->getDisplayGroupForTab(tabIndex); if (borderDisplayProperties->isDisplayed(borderDisplayGroup, tabIndex)) { const int32_t numBorderFiles = brain->getNumberOfBorderFiles(); for (int32_t i = 0; i < numBorderFiles; i++) { BorderFile* borderFile = brain->getBorderFile(i); const GroupAndNameHierarchyModel* classAndNameSelection = borderFile->getGroupAndNameHierarchyModel(); if (classAndNameSelection->isSelected(borderDisplayGroup, tabIndex)) { displayedDataFilesOut.push_back(borderFile); } } } /* * Check foci files */ const DisplayPropertiesFoci* fociDisplayProperties = brain->getDisplayPropertiesFoci(); const DisplayGroupEnum::Enum fociDisplayGroup = fociDisplayProperties->getDisplayGroupForTab(tabIndex); if (fociDisplayProperties->isDisplayed(fociDisplayGroup, tabIndex)) { const int32_t numFociFiles = brain->getNumberOfFociFiles(); for (int32_t i = 0; i < numFociFiles; i++) { FociFile* fociFile = brain->getFociFile(i); const GroupAndNameHierarchyModel* classAndNameSelection = fociFile->getGroupAndNameHierarchyModel(); if (classAndNameSelection->isSelected(fociDisplayGroup, tabIndex)) { displayedDataFilesOut.push_back(fociFile); } } } /* * Might be NULLs so filter them out and return the results */ for (std::set::iterator iter = displayedDataFiles.begin(); iter != displayedDataFiles.end(); iter++) { CaretDataFile* cdf = *iter; if (cdf != NULL) { displayedDataFilesOut.push_back(cdf); } } } ViewingTransformations* BrowserTabContent::getViewingTransformation() { if (isVolumeSlicesDisplayed()) { return m_volumeSliceViewingTransformation; } else if (isFlatSurfaceDisplayed()) { return m_flatSurfaceViewingTransformation; } else if (isCerebellumDisplayed()) { return m_cerebellumViewingTransformation; } return m_viewingTransformation; } const ViewingTransformations* BrowserTabContent::getViewingTransformation() const { if (isVolumeSlicesDisplayed()) { return m_volumeSliceViewingTransformation; } else if (isFlatSurfaceDisplayed()) { return m_flatSurfaceViewingTransformation; } else if (isCerebellumDisplayed()) { return m_cerebellumViewingTransformation; } return m_viewingTransformation; } /** * @return The viewing translation. */ const float* BrowserTabContent::getTranslation() const { return getViewingTransformation()->getTranslation(); } /** * Get the viewing translation. * * @param translationOut * Translation values output. */ void BrowserTabContent::getTranslation(float translationOut[3]) const { return getViewingTransformation()->getTranslation(translationOut); } /** * Set the viewing translation. * * @param translation * New translation values. */ void BrowserTabContent::setTranslation( const float translation[3]) { getViewingTransformation()->setTranslation(translation); updateYokedBrowserTabs(); } /** * Set the viewing translation. * * @param translationX * New translation X-value. * @param translationY * New translation Y-value. * @param translationZ * New translation Z-value. */ void BrowserTabContent::setTranslation(const float translationX, const float translationY, const float translationZ) { getViewingTransformation()->setTranslation(translationX, translationY, translationZ); updateYokedBrowserTabs(); } /** * @return The viewing scaling. */ float BrowserTabContent::getScaling() const { return getViewingTransformation()->getScaling(); } /** * Set the viewing scaling. * @param scaling * New value for scaling. */ void BrowserTabContent::setScaling(const float scaling) { return getViewingTransformation()->setScaling(scaling); updateYokedBrowserTabs(); } /** * @return The rotation matrix. */ Matrix4x4 BrowserTabContent::getRotationMatrix() const { return getViewingTransformation()->getRotationMatrix(); } /** * Set the rotation matrix. * * @param rotationMatrix * The new rotation matrix. */ void BrowserTabContent::setRotationMatrix(const Matrix4x4& rotationMatrix) { getViewingTransformation()->setRotationMatrix(rotationMatrix); updateYokedBrowserTabs(); } /** * @return The oblique volume rotation matrix. */ Matrix4x4 BrowserTabContent::getObliqueVolumeRotationMatrix() const { return *m_obliqueVolumeRotationMatrix; } /** * Set the oblique rotation matrix. * * @param obliqueRotationMatrix * The new oblique rotation matrix. */ void BrowserTabContent::setObliqueVolumeRotationMatrix(const Matrix4x4& obliqueRotationMatrix) { *m_obliqueVolumeRotationMatrix = obliqueRotationMatrix; updateYokedBrowserTabs(); } /** * Reset the view to the default view. */ void BrowserTabContent::resetView() { getViewingTransformation()->resetView(); if (isVolumeSlicesDisplayed()) { m_obliqueVolumeRotationMatrix->identity(); } updateYokedBrowserTabs(); } /** * Set to a right side view. */ void BrowserTabContent::rightView() { if (isVolumeSlicesDisplayed()) { return; } getViewingTransformation()->rightView(); updateYokedBrowserTabs(); } /** * set to a left side view. */ void BrowserTabContent::leftView() { if (isVolumeSlicesDisplayed()) { return; } getViewingTransformation()->leftView(); updateYokedBrowserTabs(); } /** * set to a anterior view. */ void BrowserTabContent::anteriorView() { if (isVolumeSlicesDisplayed()) { return; } getViewingTransformation()->anteriorView(); updateYokedBrowserTabs(); } /** * set to a posterior view. */ void BrowserTabContent::posteriorView() { if (isVolumeSlicesDisplayed()) { return; } getViewingTransformation()->posteriorView(); updateYokedBrowserTabs(); } /** * set to a dorsal view. */ void BrowserTabContent::dorsalView() { if (isVolumeSlicesDisplayed()) { return; } getViewingTransformation()->dorsalView(); updateYokedBrowserTabs(); } /** * set to a ventral view. */ void BrowserTabContent::ventralView() { if (isVolumeSlicesDisplayed()) { return; } getViewingTransformation()->ventralView(); updateYokedBrowserTabs(); } /* * @return The slice view plane for the given viewport coordinate. * If ALL is returned, is indicates that the given viewport coordinate * is in the bottom left region in which volume slices are not displayed. * * @param viewport * The viewport. * @param mousePressX * X Location of the mouse press. * @param mousePressY * Y Location of the mouse press. */ VolumeSliceViewPlaneEnum::Enum BrowserTabContent::getSliceViewPlaneForVolumeAllSliceView(const int32_t viewport[4], const int32_t mousePressX, const int32_t mousePressY, int32_t sliceViewportOut[4]) const { VolumeSliceViewPlaneEnum::Enum view = VolumeSliceViewPlaneEnum::ALL; const int32_t halfWidth = viewport[2] / 2; const int32_t halfHeight = viewport[3] / 2; const int32_t viewportMousePressedX = mousePressX - viewport[0]; const int32_t viewportMousePressedY = mousePressY - viewport[1]; bool isRight = false; bool isTop = false; if (viewportMousePressedX > halfWidth) { isRight = true; } if (viewportMousePressedY > halfHeight) { isTop = true; } /* * Top Right is Coronal * Top Left is Parasagittal * Bottom Right is Axial * Bottom Left is Empty or Surfaces */ if (isTop) { if (isRight) { view = VolumeSliceViewPlaneEnum::CORONAL; } else { view = VolumeSliceViewPlaneEnum::PARASAGITTAL; } } else { if (isRight) { view = VolumeSliceViewPlaneEnum::AXIAL; } else { } } sliceViewportOut[0] = viewport[0]; sliceViewportOut[1] = viewport[1]; sliceViewportOut[2] = halfWidth; sliceViewportOut[3] = halfHeight; switch (view) { case VolumeSliceViewPlaneEnum::ALL: sliceViewportOut[2] = viewport[2]; sliceViewportOut[3] = viewport[3]; break; case VolumeSliceViewPlaneEnum::AXIAL: sliceViewportOut[0] = halfWidth; break; case VolumeSliceViewPlaneEnum::CORONAL: sliceViewportOut[0] = halfWidth; sliceViewportOut[1] = halfHeight; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: sliceViewportOut[1] = halfHeight; break; } return view; } /** * Apply mouse rotation to the displayed model. * * @param mousePressX * X coordinate of where mouse was pressed. * @param mousePressY * X coordinate of where mouse was pressed. * @param mouseX * X coordinate of mouse. * @param mouseY * Y coordinate of mouse. * @param mouseDeltaX * Change in mouse X coordinate. * @param mouseDeltaY * Change in mouse Y coordinate. */ void BrowserTabContent::applyMouseRotation(BrainOpenGLViewportContent* viewportContent, const int32_t mousePressX, const int32_t mousePressY, const int32_t mouseX, const int32_t mouseY, const int32_t mouseDeltaX, const int32_t mouseDeltaY) { if (isVolumeSlicesDisplayed()) { switch (getSliceProjectionType()) { case VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_OBLIQUE: { int viewport[4]; viewportContent->getModelViewport(viewport); VolumeSliceViewPlaneEnum::Enum slicePlane = this->getSliceViewPlane(); int sliceViewport[4] = { viewport[0], viewport[1], viewport[2], viewport[3] }; if (slicePlane == VolumeSliceViewPlaneEnum::ALL) { slicePlane = getSliceViewPlaneForVolumeAllSliceView(viewport, mousePressX, mousePressY, sliceViewport); } Matrix4x4 rotationMatrix = getObliqueVolumeRotationMatrix(); if (slicePlane == VolumeSliceViewPlaneEnum::ALL) { rotationMatrix.rotateX(-mouseDeltaY); rotationMatrix.rotateY(mouseDeltaX); } else { if ((mouseDeltaX != 0) || (mouseDeltaY != 0)) { const int previousMouseX = mouseX - mouseDeltaX; const int previousMouseY = mouseY - mouseDeltaY; /* * Need to account for the quadrants!!!! */ const float viewportCenter[3] = { sliceViewport[0] + sliceViewport[2] / 2, sliceViewport[1] + sliceViewport[3] / 2, 0.0 }; const float oldPos[3] = { previousMouseX, previousMouseY, 0.0 }; const float newPos[3] = { mouseX, mouseY, 0.0 }; /* * Compute normal vector from viewport center to * old mouse position to new mouse position. * If normal-Z is positive, mouse has been moved * in a counter clockwise motion relative to center. * If normal-Z is negative, mouse has moved clockwise. */ float normalDirection[3]; MathFunctions::normalVectorDirection(viewportCenter, oldPos, newPos, normalDirection); bool isClockwise = false; bool isCounterClockwise = false; if (normalDirection[2] > 0.0) { isCounterClockwise = true; } else if (normalDirection[2] < 0.0) { isClockwise = true; } if (isClockwise || isCounterClockwise) { float mouseDelta = std::sqrt(static_cast((mouseDeltaX * mouseDeltaX) + (mouseDeltaY * mouseDeltaY))); // /* // * Rotation needs to be oppposite for newer // * oblique slice drawing for volumes that // * do not have a voxel corresponding to // * the origin. // */ // mouseDelta = -mouseDelta; switch (slicePlane) { case VolumeSliceViewPlaneEnum::ALL: { CaretAssert(0); } break; case VolumeSliceViewPlaneEnum::AXIAL: { Matrix4x4 rotation; if (isClockwise) { rotation.rotateZ(mouseDelta); } else if (isCounterClockwise) { rotation.rotateZ(-mouseDelta); } rotationMatrix.premultiply(rotation); } break; case VolumeSliceViewPlaneEnum::CORONAL: { Matrix4x4 rotation; if (isClockwise) { rotation.rotateY(-mouseDelta); } else if (isCounterClockwise) { rotation.rotateY(mouseDelta); } rotationMatrix.premultiply(rotation); } break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: { Matrix4x4 rotation; if (isClockwise) { rotation.rotateX(-mouseDelta); } else if (isCounterClockwise) { rotation.rotateX(mouseDelta); } rotationMatrix.premultiply(rotation); } break; } } } } setObliqueVolumeRotationMatrix(rotationMatrix); } break; case VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_ORTHOGONAL: break; /* Orthogonal olume slices are not rotated */ break; } } else if (isChartDisplayed()) { /* no rotation for chart */ } else if (isCerebellumDisplayed()) { const float screenDX = mouseDeltaX; const float screenDY = mouseDeltaY; float rotateDX = 0.0; float rotateDY = 0.0; float rotateDZ = 0.0; ModelSurfaceMontage* montageModel = getDisplayedSurfaceMontageModel(); if (montageModel != NULL) { std::vector montageViewports; montageModel->getSurfaceMontageViewportsForTransformation(getTabNumber(), montageViewports); bool foundMontageViewportFlag = false; const int32_t numViewports = static_cast(montageViewports.size()); for (int32_t ivp = 0; ivp < numViewports; ivp++) { const SurfaceMontageViewport* smv = montageViewports[ivp]; if (smv->isInside(mousePressX, mousePressY)) { switch (smv->getProjectionViewType()) { case ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_ANTERIOR: rotateDX = screenDY; rotateDZ = screenDX; foundMontageViewportFlag = true; break; case ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_DORSAL: rotateDX = -screenDY; rotateDY = screenDX; foundMontageViewportFlag = true; break; case ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_POSTERIOR: rotateDX = -screenDY; rotateDZ = screenDX; foundMontageViewportFlag = true; break; case ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_VENTRAL: rotateDX = -screenDY; rotateDY = -screenDX; foundMontageViewportFlag = true; break; case ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_FLAT_SURFACE: foundMontageViewportFlag = true; break; case ProjectionViewTypeEnum::PROJECTION_VIEW_LEFT_LATERAL: break; case ProjectionViewTypeEnum::PROJECTION_VIEW_LEFT_MEDIAL: break; case ProjectionViewTypeEnum::PROJECTION_VIEW_LEFT_FLAT_SURFACE: break; case ProjectionViewTypeEnum::PROJECTION_VIEW_RIGHT_LATERAL: break; case ProjectionViewTypeEnum::PROJECTION_VIEW_RIGHT_MEDIAL: break; case ProjectionViewTypeEnum::PROJECTION_VIEW_RIGHT_FLAT_SURFACE: break; } } if (foundMontageViewportFlag) { break; } } } else { rotateDX = -screenDY; rotateDY = screenDX; } Matrix4x4 rotationMatrix = m_cerebellumViewingTransformation->getRotationMatrix(); rotationMatrix.rotateX(rotateDX); rotationMatrix.rotateY(rotateDY); rotationMatrix.rotateZ(rotateDZ); m_cerebellumViewingTransformation->setRotationMatrix(rotationMatrix); } else { ViewingTransformations* viewingTransform = getViewingTransformation(); if (getProjectionViewType() == ProjectionViewTypeEnum::PROJECTION_VIEW_RIGHT_LATERAL) { Matrix4x4 rotationMatrix = viewingTransform->getRotationMatrix(); rotationMatrix.rotateX(-mouseDeltaY); rotationMatrix.rotateY(-mouseDeltaX); viewingTransform->setRotationMatrix(rotationMatrix); } else { float dx = mouseDeltaX; float dy = mouseDeltaY; ModelSurfaceMontage* montageModel = getDisplayedSurfaceMontageModel(); if (montageModel != NULL) { std::vector montageViewports; montageModel->getSurfaceMontageViewportsForTransformation(getTabNumber(), montageViewports); bool isValid = false; bool isFlat = false; bool isLeft = false; bool isLateral = true; const int32_t numViewports = static_cast(montageViewports.size()); for (int32_t ivp = 0; ivp < numViewports; ivp++) { const SurfaceMontageViewport* smv = montageViewports[ivp]; if (smv->isInside(mousePressX, mousePressY)) { switch (smv->getProjectionViewType()) { case ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_ANTERIOR: break; case ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_DORSAL: break; case ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_POSTERIOR: break; case ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_VENTRAL: break; case ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_FLAT_SURFACE: break; case ProjectionViewTypeEnum::PROJECTION_VIEW_LEFT_LATERAL: isLeft = true; isLateral = true; break; case ProjectionViewTypeEnum::PROJECTION_VIEW_LEFT_MEDIAL: isLeft = true; isLateral = false; break; case ProjectionViewTypeEnum::PROJECTION_VIEW_LEFT_FLAT_SURFACE: isLeft = true; isFlat = true; break; case ProjectionViewTypeEnum::PROJECTION_VIEW_RIGHT_LATERAL: isLeft = false; isLateral = true; break; case ProjectionViewTypeEnum::PROJECTION_VIEW_RIGHT_MEDIAL: isLeft = false; isLateral = false; break; case ProjectionViewTypeEnum::PROJECTION_VIEW_RIGHT_FLAT_SURFACE: isLeft = false; isFlat = true; break; } isValid = true; } if (isValid) { break; } } if (isFlat) { /* * No rotation. */ dx = 0.0; dy = 0.0; } else if (isValid) { if (isLeft == false) { dx = -dx; } if (isLateral == false) { dy = -dy; } } } Matrix4x4 rotationMatrix = viewingTransform->getRotationMatrix(); rotationMatrix.rotateX(-dy); rotationMatrix.rotateY(dx); viewingTransform->setRotationMatrix(rotationMatrix); } } updateYokedBrowserTabs(); } /** * Apply mouse scaling to the displayed model. * * @param mouseDX * Change in mouse X coordinate. * @param mouseDY * Change in mouse Y coordinate. */ void BrowserTabContent::applyMouseScaling(const int32_t /*mouseDX*/, const int32_t mouseDY) { if (isChartDisplayed()) { ModelChart* modelChart = getDisplayedChartModel(); CaretAssert(modelChart); CaretDataFileSelectionModel* matrixSelectionModel = NULL; if (modelChart->getSelectedChartDataType(m_tabNumber) == ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_LAYER) { matrixSelectionModel = modelChart->getChartableMatrixParcelFileSelectionModel(m_tabNumber); } if (modelChart->getSelectedChartDataType(m_tabNumber) == ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_SERIES) { matrixSelectionModel = modelChart->getChartableMatrixSeriesFileSelectionModel(m_tabNumber); } if (matrixSelectionModel != NULL) { ChartableMatrixInterface* chartableInterface = matrixSelectionModel->getSelectedFileOfType(); if (chartableInterface != NULL) { ChartMatrixDisplayProperties* matrixProperties = chartableInterface->getChartMatrixDisplayProperties(m_tabNumber); matrixProperties->setScaleMode(ChartMatrixScaleModeEnum::CHART_MATRIX_SCALE_MANUAL); float scaling = matrixProperties->getViewZooming(); if (mouseDY != 0.0) { scaling *= (1.0f + (mouseDY * 0.01)); } if (scaling < 0.01) { scaling = 0.01; } matrixProperties->setViewZooming(scaling); } } } else { float scaling = getViewingTransformation()->getScaling(); if (mouseDY != 0.0) { scaling *= (1.0f + (mouseDY * 0.01)); } if (scaling < 0.01) { scaling = 0.01; } getViewingTransformation()->setScaling(scaling); } updateYokedBrowserTabs(); } /** * Apply mouse translation to the displayed model. * * @param viewportContent * Content of the viewport. * @param mousePressX * X coordinate of where mouse was pressed. * @param mousePressY * X coordinate of where mouse was pressed. * @param mouseDX * Change in mouse X coordinate. * @param mouseDY * Change in mouse Y coordinate. */ void BrowserTabContent::applyMouseTranslation(BrainOpenGLViewportContent* viewportContent, const int32_t mousePressX, const int32_t mousePressY, const int32_t mouseDX, const int32_t mouseDY) { const int tabIndex = getTabNumber(); if (isVolumeSlicesDisplayed()) { const float volumeSliceScaling = m_volumeSliceViewingTransformation->getScaling(); ModelVolume* modelVolume = getDisplayedVolumeModel(); VolumeMappableInterface* vf = modelVolume->getUnderlayVolumeFile(tabIndex); BoundingBox mybox; vf->getVoxelSpaceBoundingBox(mybox); float cubesize = std::max(std::max(mybox.getDifferenceX(), mybox.getDifferenceY()), mybox.getDifferenceZ());//factor volume bounding box into slowdown for zoomed in float slowdown = 0.005f * cubesize / volumeSliceScaling;//when zoomed in, make the movements slower to match - still changes based on viewport currently slowdown = 1.0; float dx = 0.0; float dy = 0.0; float dz = 0.0; switch (this->getSliceViewPlane()) { case VolumeSliceViewPlaneEnum::ALL: { int viewport[4]; viewportContent->getModelViewport(viewport); const int32_t halfWidth = viewport[2] / 2; const int32_t halfHeight = viewport[3] / 2; const int32_t viewportMousePressedX = mousePressX - viewport[0]; const int32_t viewportMousePressedY = mousePressY - viewport[1]; bool isRight = false; bool isTop = false; if (viewportMousePressedX > halfWidth) { isRight = true; } if (viewportMousePressedY > halfHeight) { isTop = true; } //CaretLogInfo("right: " + AString::fromBool(isRight) + " top: " + AString::fromBool(isTop)); if (isTop) { if (isRight)//coronal { dx = mouseDX * slowdown; dz = mouseDY * slowdown; } else {//parasaggital dy = -mouseDX * slowdown; dz = mouseDY * slowdown; } } else { if (isRight)//axial { dx = mouseDX * slowdown; dy = mouseDY * slowdown; }//bottom left has no slice } break; } case VolumeSliceViewPlaneEnum::AXIAL: dx = mouseDX * slowdown; dy = mouseDY * slowdown; break; case VolumeSliceViewPlaneEnum::CORONAL: dx = mouseDX * slowdown; dz = mouseDY * slowdown; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: dy = -mouseDX * slowdown; dz = mouseDY * slowdown; break; } float translation[3]; m_volumeSliceViewingTransformation->getTranslation(translation); translation[0] += dx; translation[1] += dy; translation[2] += dz; m_volumeSliceViewingTransformation->setTranslation(translation); } else if (isChartDisplayed()) { ModelChart* modelChart = getDisplayedChartModel(); CaretAssert(modelChart); CaretDataFileSelectionModel* matrixSelectionModel = NULL; if (modelChart->getSelectedChartDataType(m_tabNumber) == ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_LAYER) { matrixSelectionModel = modelChart->getChartableMatrixParcelFileSelectionModel(m_tabNumber); } if (modelChart->getSelectedChartDataType(m_tabNumber) == ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_SERIES) { matrixSelectionModel = modelChart->getChartableMatrixSeriesFileSelectionModel(m_tabNumber); } if (matrixSelectionModel != NULL) { ChartableMatrixInterface* chartableInterface = matrixSelectionModel->getSelectedFileOfType(); if (chartableInterface != NULL) { ChartMatrixDisplayProperties* matrixProperties = chartableInterface->getChartMatrixDisplayProperties(m_tabNumber); matrixProperties->setScaleMode(ChartMatrixScaleModeEnum::CHART_MATRIX_SCALE_MANUAL); float translation[2]; matrixProperties->getViewPanning(translation); translation[0] += mouseDX; translation[1] += mouseDY; matrixProperties->setViewPanning(translation); } } } else if (isCerebellumDisplayed()) { const float screenDX = mouseDX; const float screenDY = mouseDY; float translateDX = 0.0; float translateDY = 0.0; float translateDZ = 0.0; ModelSurfaceMontage* montageModel = getDisplayedSurfaceMontageModel(); if (montageModel != NULL) { std::vector montageViewports; montageModel->getSurfaceMontageViewportsForTransformation(getTabNumber(), montageViewports); bool foundMontageViewportFlag = false; const int32_t numViewports = static_cast(montageViewports.size()); for (int32_t ivp = 0; ivp < numViewports; ivp++) { const SurfaceMontageViewport* smv = montageViewports[ivp]; if (smv->isInside(mousePressX, mousePressY)) { switch (smv->getProjectionViewType()) { case ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_ANTERIOR: translateDX = screenDX; translateDY = screenDY; foundMontageViewportFlag = true; break; case ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_DORSAL: translateDX = screenDX; translateDY = screenDY; foundMontageViewportFlag = true; break; case ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_POSTERIOR: translateDX = screenDX; translateDY = screenDY; foundMontageViewportFlag = true; break; case ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_VENTRAL: translateDX = screenDX; translateDY = screenDY; foundMontageViewportFlag = true; break; case ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_FLAT_SURFACE: translateDX = screenDX; translateDY = screenDY; foundMontageViewportFlag = true; break; case ProjectionViewTypeEnum::PROJECTION_VIEW_LEFT_LATERAL: break; case ProjectionViewTypeEnum::PROJECTION_VIEW_LEFT_MEDIAL: break; case ProjectionViewTypeEnum::PROJECTION_VIEW_LEFT_FLAT_SURFACE: break; case ProjectionViewTypeEnum::PROJECTION_VIEW_RIGHT_LATERAL: break; case ProjectionViewTypeEnum::PROJECTION_VIEW_RIGHT_MEDIAL: break; case ProjectionViewTypeEnum::PROJECTION_VIEW_RIGHT_FLAT_SURFACE: break; } } if (foundMontageViewportFlag) { break; } } } else { translateDX = screenDX; translateDY = screenDY; } float translation[3]; m_cerebellumViewingTransformation->getTranslation(translation); translation[0] += translateDX; translation[1] += translateDY; translation[2] += translateDZ; m_cerebellumViewingTransformation->setTranslation(translation); } else { float dx = mouseDX; float dy = mouseDY; ModelSurfaceMontage* montageModel = getDisplayedSurfaceMontageModel(); if (montageModel != NULL) { std::vector montageViewports; montageModel->getSurfaceMontageViewportsForTransformation(getTabNumber(), montageViewports); bool isValid = false; bool isLeft = true; bool isLateral = false; const int32_t numViewports = static_cast(montageViewports.size()); for (int32_t ivp = 0; ivp < numViewports; ivp++) { const SurfaceMontageViewport* smv = montageViewports[ivp]; if (smv->isInside(mousePressX, mousePressY)) { switch (smv->getProjectionViewType()) { case ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_ANTERIOR: break; case ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_DORSAL: break; case ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_POSTERIOR: break; case ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_VENTRAL: break; case ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_FLAT_SURFACE: break; case ProjectionViewTypeEnum::PROJECTION_VIEW_LEFT_LATERAL: isLeft = true; isLateral = true; break; case ProjectionViewTypeEnum::PROJECTION_VIEW_LEFT_MEDIAL: isLeft = true; isLateral = false; break; case ProjectionViewTypeEnum::PROJECTION_VIEW_LEFT_FLAT_SURFACE: isLeft = true; isLateral = true; break; case ProjectionViewTypeEnum::PROJECTION_VIEW_RIGHT_LATERAL: isLeft = false; isLateral = true; break; case ProjectionViewTypeEnum::PROJECTION_VIEW_RIGHT_MEDIAL: isLeft = false; isLateral = false; break; case ProjectionViewTypeEnum::PROJECTION_VIEW_RIGHT_FLAT_SURFACE: isLeft = false; isLateral = true; break; } isValid = true; } if (isValid) { break; } } if (isValid) { if (isLeft == false) { dx = -dx; } if (isLateral == false) { dx = -dx; } float translation[3]; getViewingTransformation()->getTranslation(translation); translation[0] += dx; translation[1] += dy; getViewingTransformation()->setTranslation(translation); } } else { if (getProjectionViewType() == ProjectionViewTypeEnum::PROJECTION_VIEW_RIGHT_LATERAL) { dx = -dx; } else if (getProjectionViewType() == ProjectionViewTypeEnum::PROJECTION_VIEW_RIGHT_FLAT_SURFACE) { dx = -dx; } float translation[3]; getViewingTransformation()->getTranslation(translation); translation[0] += dx; translation[1] += dy; getViewingTransformation()->setTranslation(translation); } } updateYokedBrowserTabs(); } /** * Get the transformations for drawing a model. * * @param projectionViewType * Type of projection view * @param translationOut * Translation * @param rotationMatrixOut * OpenGL rotation matrix. * @param scalingOut * Scaling. */ void BrowserTabContent::getTransformationsForOpenGLDrawing(const ProjectionViewTypeEnum::Enum projectionViewType, float translationOut[3], double rotationMatrixOut[16], float& scalingOut) const { /* * Check for volume slice viewing */ if (isVolumeSlicesDisplayed()) { m_volumeSliceViewingTransformation->getTranslation(translationOut); Matrix4x4 rotationMatrix = m_volumeSliceViewingTransformation->getRotationMatrix(); rotationMatrix.getMatrixForOpenGL(rotationMatrixOut); scalingOut = m_volumeSliceViewingTransformation->getScaling(); return; } /* * Surfaces may need a modification to the rotation matrix * dependent upon the projection view type. */ Matrix4x4 rotationMatrix = getViewingTransformation()->getRotationMatrix(); getViewingTransformation()->getTranslation(translationOut); scalingOut = getViewingTransformation()->getScaling(); double rotationX, rotationY, rotationZ; rotationMatrix.getRotation(rotationX, rotationY, rotationZ); const double rotationFlippedX = -rotationX; const double rotationFlippedY = 180.0 - rotationY; switch (projectionViewType) { case ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_ANTERIOR: { Matrix4x4 matrixOut = rotationMatrix; Matrix4x4 anteriorMatrix; anteriorMatrix.setRotation(90.0, 0.0, -180.0); matrixOut.postmultiply(anteriorMatrix); matrixOut.getMatrixForOpenGL(rotationMatrixOut); return; } break; case ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_DORSAL: break; case ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_POSTERIOR: { Matrix4x4 matrixOut = rotationMatrix; Matrix4x4 posteriorMatrix; posteriorMatrix.setRotation(-90.0, 0.0, 0.0); matrixOut.postmultiply(posteriorMatrix); matrixOut.getMatrixForOpenGL(rotationMatrixOut); return; } break; case ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_VENTRAL: { Matrix4x4 matrixOut = rotationMatrix; Matrix4x4 ventralMatrix; ventralMatrix.setRotation(0.0, 180.0, 180.0); matrixOut.postmultiply(ventralMatrix); matrixOut.getMatrixForOpenGL(rotationMatrixOut); return; } break; case ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_FLAT_SURFACE: rotationX = 0.0; rotationY = 0.0; rotationZ = 0.0; break; case ProjectionViewTypeEnum::PROJECTION_VIEW_LEFT_LATERAL: break; case ProjectionViewTypeEnum::PROJECTION_VIEW_LEFT_MEDIAL: break; case ProjectionViewTypeEnum::PROJECTION_VIEW_LEFT_FLAT_SURFACE: rotationX = 0.0; rotationY = 0.0; rotationZ = 0.0; break; case ProjectionViewTypeEnum::PROJECTION_VIEW_RIGHT_LATERAL: rotationX = rotationFlippedX; rotationY = rotationFlippedY; break; case ProjectionViewTypeEnum::PROJECTION_VIEW_RIGHT_MEDIAL: rotationX = rotationFlippedX; rotationY = rotationFlippedY; break; case ProjectionViewTypeEnum::PROJECTION_VIEW_RIGHT_FLAT_SURFACE: rotationX = 0.0; rotationY = 180.0; rotationZ = 0.0; break; } Matrix4x4 matrix; matrix.setRotation(rotationX, rotationY, rotationZ); matrix.getMatrixForOpenGL(rotationMatrixOut); } /** * Place the transformations for the given window tab into * the model transform. * @param windowTabNumber * Tab number for transformations. * @param modelTransform * Model transform into which transformations are loaded. */ void BrowserTabContent::getTransformationsInModelTransform(ModelTransform& modelTransform) const { modelTransform.setTranslation(getTranslation()); const Matrix4x4 rotMatrix = getRotationMatrix(); float m[4][4]; rotMatrix.getMatrix(m); modelTransform.setRotation(m); const Matrix4x4 obliqueRotationMatrix = getObliqueVolumeRotationMatrix(); float mob[4][4]; obliqueRotationMatrix.getMatrix(mob); modelTransform.setObliqueRotation(mob); modelTransform.setScaling(getScaling()); } /** * Apply the transformations to the browser tab. * @param modelTransform * Model transform into which transformations are retrieved. */ void BrowserTabContent::setTransformationsFromModelTransform(const ModelTransform& modelTransform) { float translation[3]; modelTransform.getTranslation(translation); const float tx = translation[0]; const float ty = translation[1]; const float tz = translation[2]; setTranslation(tx, ty, tz); float m[4][4]; modelTransform.getRotation(m); Matrix4x4 rotationMatrix; rotationMatrix.setMatrix(m); setRotationMatrix(rotationMatrix); float mob[4][4]; modelTransform.getObliqueRotation(mob); Matrix4x4 obliqueRotationMatrix; obliqueRotationMatrix.setMatrix(mob); setObliqueVolumeRotationMatrix(obliqueRotationMatrix); const float scale = modelTransform.getScaling(); setScaling(scale); updateYokedBrowserTabs(); } /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @param instanceName * Name of the class' instance. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ SceneClass* BrowserTabContent::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "BrowserTabContent", 4); // WB-491, 1/28/2015 //3); // version 3 as of 4/22/2014 float obliqueMatrix[16]; m_obliqueVolumeRotationMatrix->getMatrixForOpenGL(obliqueMatrix); sceneClass->addFloatArray("m_obliqueVolumeRotationMatrix", obliqueMatrix, 16); m_sceneClassAssistant->saveMembers(sceneAttributes, sceneClass); return sceneClass; } /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass for the instance of a class that implements * this interface. May be NULL for some types of scenes. */ void BrowserTabContent::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } m_sceneClassAssistant->restoreMembers(sceneAttributes, sceneClass); m_obliqueVolumeRotationMatrix->identity(); float obliqueMatrix[16]; const int32_t numInObliqueArray = sceneClass->getFloatArrayValue("m_obliqueVolumeRotationMatrix", obliqueMatrix, 16); if (numInObliqueArray == 16) { m_obliqueVolumeRotationMatrix->setMatrixFromOpenGL(obliqueMatrix); } /* * In older version of workbench, transformation were stored in the * model for each tab, so try to restore them. */ if (sceneClass->getVersionNumber() < 2) { float translation[3]; float scaling; float rotationMatrix[4][4]; const Model* model = getModelForDisplay(); if (model != NULL) { const bool valid = model->getOldSceneTransformation(m_tabNumber, translation, scaling, rotationMatrix); if (valid) { setTranslation(translation); setScaling(scaling); Matrix4x4 m; m.setMatrix(rotationMatrix); ModelSurface* ms = getDisplayedSurfaceModel(); if (ms != NULL) { /* * Right hemispheres need rotations changed for * proper viewing. */ const StructureEnum::Enum structure = ms->getSurface()->getStructure(); if (StructureEnum::isRight(structure)) { double rotationX, rotationY, rotationZ; m.getRotation(rotationX, rotationY, rotationZ); //rotationX = -rotationX; //rotationY = 180.0 - rotationY; rotationY = 90 + rotationY; rotationZ = -rotationZ; m.identity(); m.setRotation(rotationX, rotationY, rotationZ); } } setRotationMatrix(m); } } } if (getDisplayedWholeBrainModel() != NULL) { /* * As of 19sep2013 whole brain and volume slice settings were merged * (whole brain slice settings removed). For compatibility, if a * whole brain model is being viewed and whole brain slice settings * are found, allow them to override volume slice settings. */ const SceneClass* wholeBrainVolumeSettings = sceneClass->getClass("m_wholeBrainSliceSettings"); if (wholeBrainVolumeSettings != NULL) { VolumeSliceSettings settings; settings.restoreFromScene(sceneAttributes, wholeBrainVolumeSettings); *m_volumeSliceSettings = settings; } } /** * Check for now obsolete clipping coordinate array. If found it is an * old scene so update the clipping planes. */ const SceneClassArray* oldClippingCoordinateClassArray = sceneClass->getClassArray("m_clippingCoordinate"); if (oldClippingCoordinateClassArray != NULL) { float clipCoords[3]; if (sceneClass->getFloatArrayValue("m_clippingCoordinate", clipCoords, 3) != 3) { clipCoords[0] = 0.0; clipCoords[1] = 0.0; clipCoords[2] = 0.0; } float clipThick[3]; if (sceneClass->getFloatArrayValue("m_clippingThickness", clipThick, 3) != 3) { clipThick[0] = 0.0; clipThick[1] = 0.0; clipThick[2] = 0.0; } bool clipEnabled[3]; if (sceneClass->getBooleanArrayValue("m_clippingEnabled", clipEnabled, 3) != 3) { clipEnabled[0] = false; clipEnabled[1] = false; clipEnabled[2] = false; } m_clippingPlaneGroup->resetToDefaultValues(); m_clippingPlaneGroup->setXAxisSelected(clipEnabled[0]); m_clippingPlaneGroup->setYAxisSelected(clipEnabled[1]); m_clippingPlaneGroup->setZAxisSelected(clipEnabled[2]); m_clippingPlaneGroup->setTranslation(clipCoords); m_clippingPlaneGroup->setThickness(clipThick); } /* * In older version of workbench, there was no flat surface * viewing transformation as it used the same transformations * as other surfaces. */ if (sceneClass->getVersionNumber() < 3) { *m_flatSurfaceViewingTransformation = *m_viewingTransformation; } /* * Prior to WB-491 surface drawing used the maximum dimension for * scaling to fit the window height and this was almost * always the Y-axis. This worked well when the default view was * a dorsal view with the anterior pole was at the top of the display * and the posterior pole at the bottom of the display. However * the default view was changed to be a lateral view so the surface * did not scale to fit the window and there were problems with the * surfaces scaling improperly when the overlay toolbox was changed * in height. * * This code adjusts the surface scaling for older scenes so that * older scenes are restored properly with the newer default scaling * for a lateral view. * * See also: BrainOpenGLFixedPipeline::setOrthographicProjectionForWithBoundingBox() */ if (sceneClass->getVersionNumber() < 4) { Surface* surface = NULL; switch (getSelectedModelType()) { case ModelTypeEnum::MODEL_TYPE_CHART: break; case ModelTypeEnum::MODEL_TYPE_INVALID: break; case ModelTypeEnum::MODEL_TYPE_SURFACE: { ModelSurface* modelSurface = getDisplayedSurfaceModel(); if (modelSurface != NULL) { surface = modelSurface->getSurface(); } } break; case ModelTypeEnum::MODEL_TYPE_SURFACE_MONTAGE: { ModelSurfaceMontage* modelMontage = getDisplayedSurfaceMontageModel(); if (modelMontage != NULL) { std::vector surfaceMontageViewports; modelMontage->getSurfaceMontageViewportsForDrawing(getTabNumber(), surfaceMontageViewports); for (std::vector::iterator iter = surfaceMontageViewports.begin(); iter != surfaceMontageViewports.end(); iter++) { SurfaceMontageViewport* smv = *iter; if (smv->getSurface() != NULL) { surface = smv->getSurface(); break; } } } } break; case ModelTypeEnum::MODEL_TYPE_VOLUME_SLICES: break; case ModelTypeEnum::MODEL_TYPE_WHOLE_BRAIN: { ModelWholeBrain* modelWholeBrain = getDisplayedWholeBrainModel(); if (modelWholeBrain != NULL) { std::vector allSurfaces = modelWholeBrain->getSelectedSurfaces(getTabNumber()); if ( ! allSurfaces.empty()) { surface = allSurfaces[0]; } } } break; } if (surface != NULL) { if (surface->getSurfaceType() != SurfaceTypeEnum::FLAT) { const BoundingBox* boundingBox = surface->getBoundingBox(); const float zDiff = boundingBox->getDifferenceZ(); const float maxDim = std::max(std::max(boundingBox->getDifferenceX(), boundingBox->getDifferenceY()), zDiff); if (zDiff > 0.0) { const float scaleAdjustment = zDiff / maxDim; //maxDim / zDiff; float scaling = getScaling(); scaling *= scaleAdjustment; setScaling(scaling); } } } } } /** * Get the clipping planes enabled attributes * * @param xEnabled * X clipping plane enabled. * @param yEnabled * Y clipping plane enabled. * @param zEnabled * Z clipping plane enabled. * @param surfaceEnabled * Surface clipping enabled. * @param volumeEnabled * Volume clipping enabled. * @param featuresEnabled * Features enabled. * @param displayClippingBox * Display the clipping box. */ void BrowserTabContent::getClippingPlaneEnabled(bool& xEnabled, bool& yEnabled, bool& zEnabled, bool& surfaceEnabled, bool& volumeEnabled, bool& featuresEnabled) const { xEnabled = m_clippingPlaneGroup->isXAxisSelected(); yEnabled = m_clippingPlaneGroup->isYAxisSelected(); zEnabled = m_clippingPlaneGroup->isZAxisSelected(); surfaceEnabled = m_clippingPlaneGroup->isSurfaceSelected(); volumeEnabled = m_clippingPlaneGroup->isVolumeSelected(); featuresEnabled = m_clippingPlaneGroup->isFeaturesSelected(); } /** * Set the clipping planes enabled attributes * * @param xEnabled * X clipping plane enabled. * @param yEnabled * Y clipping plane enabled. * @param zEnabled * Z clipping plane enabled. * @param surfaceEnabled * Surface clipping enabled. * @param volumeEnabled * Volume clipping enabled. * @param featuresEnabled * Features enabled. * @param displayClippingBox * Display the clipping box. */ void BrowserTabContent::setClippingPlaneEnabled(const bool xEnabled, const bool yEnabled, const bool zEnabled, const bool surfaceEnabled, const bool volumeEnabled, const bool featuresEnabled) { m_clippingPlaneGroup->setXAxisSelected(xEnabled); m_clippingPlaneGroup->setYAxisSelected(yEnabled); m_clippingPlaneGroup->setZAxisSelected(zEnabled); m_clippingPlaneGroup->setSurfaceSelected(surfaceEnabled); m_clippingPlaneGroup->setVolumeSelected(volumeEnabled); m_clippingPlaneGroup->setFeaturesSelected(featuresEnabled); updateYokedBrowserTabs(); } /** * Get the clipping planes transformations. * * @param panning * Panning (translation) of the clipping planes. * @param rotation * Rotation of clipping planes. * @param thickness * Thickness of the clipping planes. */ void BrowserTabContent::getClippingPlaneTransformation(float panning[3], float rotation[3], float thickness[3], bool& displayClippingBox) const { m_clippingPlaneGroup->getTranslation(panning); m_clippingPlaneGroup->getRotationAngles(rotation); m_clippingPlaneGroup->getThickness(thickness); displayClippingBox = m_clippingPlaneGroup->isDisplayClippingBoxSelected(); } /** * Set the clipping planes transformations. * * @param panning * Panning (translation) of the clipping planes. * @param rotation * Rotation of clipping planes. * @param thickness * Thickness of the clipping planes. */ void BrowserTabContent::setClippingPlaneTransformation(const float panning[3], const float rotation[3], const float thickness[3], const bool displayClippingBox) { m_clippingPlaneGroup->setTranslation(panning); m_clippingPlaneGroup->setRotationAngles(rotation); m_clippingPlaneGroup->setThickness(thickness); m_clippingPlaneGroup->setDisplayClippingBoxSelected(displayClippingBox); updateYokedBrowserTabs(); } /** * Get the clipping plane group (const method). * * NOTE: Because of yoking, only a const instance of the clipping plane * group is available. To adjust the clipping planes use the methods * in this class so that yoking is properly updated. */ const ClippingPlaneGroup* BrowserTabContent::getClippingPlaneGroup() const { return m_clippingPlaneGroup; } /** * Reset the clipping plane transformations. */ void BrowserTabContent::resetClippingPlaneTransformation() { m_clippingPlaneGroup->resetTransformation(); updateYokedBrowserTabs(); } /** * @return the projection view type (view from left/right) */ ProjectionViewTypeEnum::Enum BrowserTabContent::getProjectionViewType() const { ProjectionViewTypeEnum::Enum projectionViewType = ProjectionViewTypeEnum::PROJECTION_VIEW_LEFT_LATERAL; const ModelSurface* modelSurface = getDisplayedSurfaceModel(); if (modelSurface != NULL) { const SurfaceFile* surfaceFile = modelSurface->getSurface(); if (surfaceFile != NULL) { const StructureEnum::Enum structure = surfaceFile->getStructure(); const SurfaceTypeEnum::Enum surfaceType = surfaceFile->getSurfaceType(); if (StructureEnum::isRight(structure)) { projectionViewType = ProjectionViewTypeEnum::PROJECTION_VIEW_RIGHT_LATERAL; if (surfaceType == SurfaceTypeEnum::FLAT) { projectionViewType = ProjectionViewTypeEnum::PROJECTION_VIEW_RIGHT_FLAT_SURFACE; } } else { projectionViewType = ProjectionViewTypeEnum::PROJECTION_VIEW_LEFT_LATERAL; if (surfaceType == SurfaceTypeEnum::FLAT) { projectionViewType = ProjectionViewTypeEnum::PROJECTION_VIEW_LEFT_FLAT_SURFACE; } } } } return projectionViewType; } /** * @return The slice view plane. * */ VolumeSliceViewPlaneEnum::Enum BrowserTabContent::getSliceViewPlane() const { return m_volumeSliceSettings->getSliceViewPlane(); } /** * Set the slice view plane. * @param windowTabNumber * New value for slice plane. */ void BrowserTabContent::setSliceViewPlane(const VolumeSliceViewPlaneEnum::Enum slicePlane) { m_volumeSliceSettings->setSliceViewPlane(slicePlane); updateYokedBrowserTabs(); } /** * @return Type of slice drawing (single/montage) */ VolumeSliceDrawingTypeEnum::Enum BrowserTabContent::getSliceDrawingType() const { return m_volumeSliceSettings->getSliceDrawingType(); } /** * Set type of slice drawing (single/montage) * * @param sliceDrawingType * New value for slice drawing type. */ void BrowserTabContent::setSliceDrawingType(const VolumeSliceDrawingTypeEnum::Enum sliceDrawingType) { m_volumeSliceSettings->setSliceDrawingType(sliceDrawingType); updateYokedBrowserTabs(); } /** * @return Type of slice projection (oblique/orthogonal) */ VolumeSliceProjectionTypeEnum::Enum BrowserTabContent::getSliceProjectionType() const { return m_volumeSliceSettings->getSliceProjectionType(); } /** * Set type of slice projection (oblique/orthogonal) * * @param sliceProjectionType * New value for slice projection type. */ void BrowserTabContent::setSliceProjectionType(const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType) { m_volumeSliceSettings->setSliceProjectionType(sliceProjectionType); updateYokedBrowserTabs(); } /** * @return the montage number of columns for the given window tab. */ int32_t BrowserTabContent::getMontageNumberOfColumns() const { return m_volumeSliceSettings->getMontageNumberOfColumns(); } /** * Set the montage number of columns in the given window tab. * @param montageNumberOfColumns * New value for montage number of columns */ void BrowserTabContent::setMontageNumberOfColumns(const int32_t montageNumberOfColumns) { m_volumeSliceSettings->setMontageNumberOfColumns(montageNumberOfColumns); updateYokedBrowserTabs(); } /** * @return the montage number of rows for the given window tab. */ int32_t BrowserTabContent::getMontageNumberOfRows() const { return m_volumeSliceSettings->getMontageNumberOfRows(); } /** * Set the montage number of rows. * @param montageNumberOfRows * New value for montage number of rows */ void BrowserTabContent::setMontageNumberOfRows(const int32_t montageNumberOfRows) { m_volumeSliceSettings->setMontageNumberOfRows(montageNumberOfRows); updateYokedBrowserTabs(); } /** * @return the montage slice spacing. */ int32_t BrowserTabContent::getMontageSliceSpacing() const { return m_volumeSliceSettings->getMontageSliceSpacing(); } /** * Set the montage slice spacing. * @param montageSliceSpacing * New value for montage slice spacing */ void BrowserTabContent::setMontageSliceSpacing(const int32_t montageSliceSpacing) { m_volumeSliceSettings->setMontageSliceSpacing(montageSliceSpacing); updateYokedBrowserTabs(); } /** * Set the selected slices to the origin. */ void BrowserTabContent::setSlicesToOrigin() { selectSlicesAtOrigin(); updateYokedBrowserTabs(); } /** * Reset the slices. */ void BrowserTabContent::reset() { if (isVolumeSlicesDisplayed() || isWholeBrainDisplayed()) { m_volumeSliceSettings->reset(); m_obliqueVolumeRotationMatrix->identity(); } updateYokedBrowserTabs(); } /** * Update the slices coordinates so that they are valid for * the given VolumeFile. * @param volumeFile * File for which slice coordinates are made valid. */ void BrowserTabContent::updateForVolumeFile(const VolumeMappableInterface* volumeFile) { m_volumeSliceSettings->updateForVolumeFile(volumeFile); } /** * Set the slice indices so that they are at the origin. */ void BrowserTabContent::selectSlicesAtOrigin() { m_volumeSliceSettings->selectSlicesAtOrigin(); updateYokedBrowserTabs(); } /** * Set the selected slices to the given coordinate. * @param xyz * Coordinate for selected slices. */ void BrowserTabContent::selectSlicesAtCoordinate(const float xyz[3]) { m_volumeSliceSettings->selectSlicesAtCoordinate(xyz); updateYokedBrowserTabs(); } /** * If true, selected volume slices in tab move to location * of the identification operation. */ bool BrowserTabContent::isIdentificationUpdatesVolumeSlices() const { return m_identificationUpdatesVolumeSlices; } /** * Update selected volume slices in tab move to location * of the identification operation. * * @param status * New status. */ void BrowserTabContent::setIdentificationUpdatesVolumeSlices(const bool status) { m_identificationUpdatesVolumeSlices = status; updateYokedBrowserTabs(); } /** * Return the axial slice index. * @return * Axial slice index or negative if invalid */ int64_t BrowserTabContent::getSliceIndexAxial(const VolumeMappableInterface* volumeFile) const { return m_volumeSliceSettings->getSliceIndexAxial(volumeFile); } /** * Set the axial slice index. * @param * New value for axial slice index. */ void BrowserTabContent::setSliceIndexAxial(const VolumeMappableInterface* volumeFile, const int64_t sliceIndexAxial) { m_volumeSliceSettings->setSliceIndexAxial(volumeFile, sliceIndexAxial); updateYokedBrowserTabs(); } /** * Return the coronal slice index. * @return * Coronal slice index. */ int64_t BrowserTabContent::getSliceIndexCoronal(const VolumeMappableInterface* volumeFile) const { return m_volumeSliceSettings->getSliceIndexCoronal(volumeFile); } /** * Set the coronal slice index. * @param sliceIndexCoronal * New value for coronal slice index. */ void BrowserTabContent::setSliceIndexCoronal(const VolumeMappableInterface* volumeFile, const int64_t sliceIndexCoronal) { m_volumeSliceSettings->setSliceIndexCoronal(volumeFile, sliceIndexCoronal); updateYokedBrowserTabs(); } /** * Return the parasagittal slice index. * @return * Parasagittal slice index. */ int64_t BrowserTabContent::getSliceIndexParasagittal(const VolumeMappableInterface* volumeFile) const { return m_volumeSliceSettings->getSliceIndexParasagittal(volumeFile); } /** * Set the parasagittal slice index. * @param sliceIndexParasagittal * New value for parasagittal slice index. */ void BrowserTabContent::setSliceIndexParasagittal(const VolumeMappableInterface* volumeFile, const int64_t sliceIndexParasagittal) { m_volumeSliceSettings->setSliceIndexParasagittal(volumeFile, sliceIndexParasagittal); updateYokedBrowserTabs(); } /** * @return Coordinate of axial slice. */ float BrowserTabContent::getSliceCoordinateAxial() const { return m_volumeSliceSettings->getSliceCoordinateAxial(); } /** * Set the coordinate for the axial slice. * @param z * Z-coordinate for axial slice. */ void BrowserTabContent::setSliceCoordinateAxial(const float z) { m_volumeSliceSettings->setSliceCoordinateAxial(z); updateYokedBrowserTabs(); } /** * @return Coordinate of coronal slice. */ float BrowserTabContent::getSliceCoordinateCoronal() const { return m_volumeSliceSettings->getSliceCoordinateCoronal(); } /** * Set the coordinate for the coronal slice. * @param y * Y-coordinate for coronal slice. */ void BrowserTabContent::setSliceCoordinateCoronal(const float y) { m_volumeSliceSettings->setSliceCoordinateCoronal(y); updateYokedBrowserTabs(); } /** * @return Coordinate of parasagittal slice. */ float BrowserTabContent::getSliceCoordinateParasagittal() const { return m_volumeSliceSettings->getSliceCoordinateParasagittal(); } /** * Set the coordinate for the parasagittal slice. * @param x * X-coordinate for parasagittal slice. */ void BrowserTabContent::setSliceCoordinateParasagittal(const float x) { m_volumeSliceSettings->setSliceCoordinateParasagittal(x); updateYokedBrowserTabs(); } /** * Is the parasagittal slice enabled? * @return * Enabled status of parasagittal slice. */ bool BrowserTabContent::isSliceParasagittalEnabled() const { return m_volumeSliceSettings->isSliceParasagittalEnabled(); } /** * Set the enabled status of the parasagittal slice. * @param sliceEnabledParasagittal * New enabled status. */ void BrowserTabContent::setSliceParasagittalEnabled(const bool sliceEnabledParasagittal) { m_volumeSliceSettings->setSliceParasagittalEnabled(sliceEnabledParasagittal); updateYokedBrowserTabs(); } /** * Is the coronal slice enabled? * @return * Enabled status of coronal slice. */ bool BrowserTabContent::isSliceCoronalEnabled() const { return m_volumeSliceSettings->isSliceCoronalEnabled(); } /** * Set the enabled status of the coronal slice. * @param sliceEnabledCoronal * New enabled status. */ void BrowserTabContent::setSliceCoronalEnabled(const bool sliceEnabledCoronal) { m_volumeSliceSettings->setSliceCoronalEnabled(sliceEnabledCoronal); updateYokedBrowserTabs(); } /** * Is the axial slice enabled? * @return * Enabled status of axial slice. */ bool BrowserTabContent::isSliceAxialEnabled() const { return m_volumeSliceSettings->isSliceAxialEnabled(); } /** * Set the enabled status of the axial slice. * @param sliceEnabledAxial * New enabled status. */ void BrowserTabContent::setSliceAxialEnabled(const bool sliceEnabledAxial) { m_volumeSliceSettings->setSliceAxialEnabled(sliceEnabledAxial); updateYokedBrowserTabs(); } /** * @return Enabled status for left cerebral cortex. */ bool BrowserTabContent::isWholeBrainLeftEnabled() const { return m_wholeBrainSurfaceSettings->isLeftEnabled(); } /** * Set the enabled status for the left hemisphere. * @param windowTabNumber * Index of window tab. * @param enabled * New enabled status. */ void BrowserTabContent::setWholeBrainLeftEnabled(const bool enabled) { m_wholeBrainSurfaceSettings->setLeftEnabled(enabled); updateYokedBrowserTabs(); } /** * @return Enabled status for right cerebral cortex. */ bool BrowserTabContent::isWholeBrainRightEnabled() const { return m_wholeBrainSurfaceSettings->isRightEnabled(); } /** * Set the enabled status for the right hemisphere. * @param enabled * New enabled status. */ void BrowserTabContent::setWholeBrainRightEnabled(const bool enabled) { m_wholeBrainSurfaceSettings->setRightEnabled(enabled); updateYokedBrowserTabs(); } /** * @return Enabled status for cerebellum. */ bool BrowserTabContent::isWholeBrainCerebellumEnabled() const { return m_wholeBrainSurfaceSettings->isCerebellumEnabled(); } /** * Set the enabled status for the cerebellum. * @param enabled * New enabled status. */ void BrowserTabContent::setWholeBrainCerebellumEnabled(const bool enabled) { m_wholeBrainSurfaceSettings->setCerebellumEnabled(enabled); updateYokedBrowserTabs(); } /** * @return The separation between the left and right surfaces. */ float BrowserTabContent::getWholeBrainLeftRightSeparation() const { return m_wholeBrainSurfaceSettings->getLeftRightSeparation(); } /** * Set the separation between the cerebellum and the left/right surfaces. * @param separation * New value for separation. */ void BrowserTabContent::setWholeBrainLeftRightSeparation(const float separation) { m_wholeBrainSurfaceSettings->setLeftRightSeparation(separation); updateYokedBrowserTabs(); } /** * @return The separation between the left/right surfaces. */ float BrowserTabContent::getWholeBrainCerebellumSeparation() const { return m_wholeBrainSurfaceSettings->getCerebellumSeparation(); } /** * Set the separation between the cerebellum and the left and right surfaces. * @param separation * New value for separation. */ void BrowserTabContent::setWholeBrainCerebellumSeparation(const float separation) { m_wholeBrainSurfaceSettings->setCerebellumSeparation(separation); updateYokedBrowserTabs(); } /** * @return Selected yoking group. */ YokingGroupEnum::Enum BrowserTabContent::getYokingGroup() const { return m_yokingGroup; } /** * Set the selected yoking group. * * @param yokingGroup * New value for yoking group. */ void BrowserTabContent::setYokingGroup(const YokingGroupEnum::Enum yokingGroup) { m_yokingGroup = yokingGroup; if (m_yokingGroup == YokingGroupEnum::YOKING_GROUP_OFF) { return; } /* * Find another browser tab using the same yoking as 'me' and copy * yoked data from the other browser tab. */ for (std::set::iterator iter = s_allBrowserTabContent.begin(); iter != s_allBrowserTabContent.end(); iter++) { BrowserTabContent* btc = *iter; if (btc != this) { if (btc->getYokingGroup() == m_yokingGroup) { /* * If anything is added, also need to update updateYokedBrowserTabs() */ *m_viewingTransformation = *btc->m_viewingTransformation; *m_flatSurfaceViewingTransformation = *btc->m_flatSurfaceViewingTransformation; *m_cerebellumViewingTransformation = *btc->m_cerebellumViewingTransformation; *m_volumeSliceViewingTransformation = *btc->m_volumeSliceViewingTransformation; *m_volumeSliceSettings = *btc->m_volumeSliceSettings; *m_obliqueVolumeRotationMatrix = *btc->m_obliqueVolumeRotationMatrix; *m_clippingPlaneGroup = *btc->m_clippingPlaneGroup; m_identificationUpdatesVolumeSlices = btc->m_identificationUpdatesVolumeSlices; break; } } } } /** * @return Is this browser tab yoked? */ bool BrowserTabContent::isYoked() const { const bool yoked = (m_yokingGroup != YokingGroupEnum::YOKING_GROUP_OFF); return yoked; } /** * Update other browser tabs with yoked data. */ void BrowserTabContent::updateYokedBrowserTabs() { if (isExecutingConstructor) { return; } if (m_yokingGroup == YokingGroupEnum::YOKING_GROUP_OFF) { return; } /* * Copy yoked data from 'me' to all other yoked browser tabs */ for (std::set::iterator iter = s_allBrowserTabContent.begin(); iter != s_allBrowserTabContent.end(); iter++) { BrowserTabContent* btc = *iter; if (btc != this) { /* * If anything is added, also need to update setYokingGroup() */ if (btc->getYokingGroup() == m_yokingGroup) { *btc->m_viewingTransformation = *m_viewingTransformation; *btc->m_flatSurfaceViewingTransformation = *m_flatSurfaceViewingTransformation; *btc->m_cerebellumViewingTransformation = *m_cerebellumViewingTransformation; *btc->m_volumeSliceViewingTransformation = *m_volumeSliceViewingTransformation; *btc->m_volumeSliceSettings = *m_volumeSliceSettings; *btc->m_obliqueVolumeRotationMatrix = *m_obliqueVolumeRotationMatrix; *btc->m_clippingPlaneGroup = *m_clippingPlaneGroup; btc->m_identificationUpdatesVolumeSlices = m_identificationUpdatesVolumeSlices; } } } } workbench-1.1.1/src/Brain/BrowserTabContent.h000066400000000000000000000403001255417355300211030ustar00rootroot00000000000000#ifndef __BROWSER_TAB_CONTENT__H_ #define __BROWSER_TAB_CONTENT__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretObject.h" #include "EventListenerInterface.h" #include "Model.h" #include "ModelTypeEnum.h" #include "Plane.h" #include "ProjectionViewTypeEnum.h" #include "SceneableInterface.h" #include "VolumeSliceDrawingTypeEnum.h" #include "VolumeSliceProjectionTypeEnum.h" #include "VolumeSliceViewPlaneEnum.h" #include "YokingGroupEnum.h" namespace caret { class BrainOpenGLViewportContent; class CaretDataFile; class CaretMappableDataFile; class ClippingPlaneGroup; class Matrix4x4; class ModelChart; class ModelSurface; class ModelSurfaceMontage; class ModelSurfaceSelector; class ModelTransform; class ModelVolume; class ModelWholeBrain; class OverlaySet; class Palette; class PlainTextStringBuilder; class SceneClassAssistant; class Surface; class ViewingTransformations; class ViewingTransformationsCerebellum; class ViewingTransformationsVolume; class VolumeMappableInterface; class VolumeSliceSettings; class VolumeSurfaceOutlineSetModel; class WholeBrainSurfaceSettings; /// Maintains content in a brower's tab class BrowserTabContent : public CaretObject, public EventListenerInterface, public SceneableInterface { public: BrowserTabContent(const int32_t tabNumber); virtual ~BrowserTabContent(); void cloneBrowserTabContent(BrowserTabContent* tabToClone); virtual void receiveEvent(Event* event); virtual void getDescriptionOfContent(PlainTextStringBuilder& descriptionOut) const; virtual AString toString() const; AString getName() const; //void setGuiName(const AString& name); AString getUserName() const; void setUserName(const AString& userName); OverlaySet* getOverlaySet(); const OverlaySet* getOverlaySet() const; int32_t getTabNumber() const; ModelTypeEnum::Enum getSelectedModelType() const; void setSelectedModelType(ModelTypeEnum::Enum selectedModelType); const Model* getModelForDisplay() const; Model* getModelForDisplay(); ModelChart* getDisplayedChartModel(); const ModelChart* getDisplayedChartModel() const; ModelSurface* getDisplayedSurfaceModel(); const ModelSurface* getDisplayedSurfaceModel() const; ModelVolume* getDisplayedVolumeModel(); ModelWholeBrain* getDisplayedWholeBrainModel(); ModelSurfaceMontage* getDisplayedSurfaceMontageModel(); const ModelSurfaceMontage* getDisplayedSurfaceMontageModel() const; const std::vector getAllSurfaceModels() const; ModelSurfaceSelector* getSurfaceModelSelector(); bool isCerebellumDisplayed() const; bool isChartDisplayed() const; bool isFlatSurfaceDisplayed() const; bool isVolumeSlicesDisplayed() const; bool isWholeBrainDisplayed() const; void getFilesDisplayedInTab(std::vector& displayedDataFilesOut); void update(const std::vector models); bool isChartModelValid() const; bool isSurfaceModelValid() const; bool isVolumeSliceModelValid() const; bool isWholeBrainModelValid() const; bool isSurfaceMontageModelValid() const; void getDisplayedPaletteMapFiles(std::vector& mapFiles, std::vector& mapIndices); VolumeSurfaceOutlineSetModel* getVolumeSurfaceOutlineSet(); const VolumeSurfaceOutlineSetModel* getVolumeSurfaceOutlineSet() const; void getClippingPlaneEnabled(bool& xEnabled, bool& yEnabled, bool& zEnabled, bool& surfaceEnabled, bool& volumeEnabled, bool& featuresEnabled) const; void setClippingPlaneEnabled(const bool xEnabled, const bool yEnabled, const bool zEnabled, const bool surfaceEnabled, const bool volumeEnabled, const bool featuresEnabled); void getClippingPlaneTransformation(float panning[3], float rotation[3], float thickness[3], bool& displayClippingBox) const; void setClippingPlaneTransformation(const float panning[3], const float rotation[3], const float thickness[3], const bool displayClippingBox); const ClippingPlaneGroup* getClippingPlaneGroup() const; void resetClippingPlaneTransformation(); const float* getTranslation() const; void getTranslation(float translationOut[3]) const; void setTranslation( const float translation[3]); void setTranslation(const float translationX, const float translationY, const float translationZ); float getScaling() const; void setScaling(const float scaling); Matrix4x4 getRotationMatrix() const; void setRotationMatrix(const Matrix4x4& rotationMatrix); Matrix4x4 getObliqueVolumeRotationMatrix() const; void setObliqueVolumeRotationMatrix(const Matrix4x4& obliqueRotationMatrix); ProjectionViewTypeEnum::Enum getProjectionViewType() const; void resetView(); void rightView(); void leftView(); void anteriorView(); void posteriorView(); void dorsalView(); void ventralView(); void applyMouseRotation(BrainOpenGLViewportContent* viewportContent, const int32_t mousePressX, const int32_t mousePressY, const int32_t mouseX, const int32_t mouseY, const int32_t mouseDeltaX, const int32_t mouseDeltaY); void applyMouseScaling(const int32_t mouseDX, const int32_t mouseDY); void applyMouseTranslation(BrainOpenGLViewportContent* viewportContent, const int32_t mousePressX, const int32_t mousePressY, const int32_t mouseDX, const int32_t mouseDY); void getTransformationsForOpenGLDrawing(const ProjectionViewTypeEnum::Enum projectionViewType, float translationOut[3], double rotationMatrixOut[16], float& scalingOut) const; void getTransformationsInModelTransform(ModelTransform& modelTransform) const; void setTransformationsFromModelTransform(const ModelTransform& modelTransform); VolumeSliceViewPlaneEnum::Enum getSliceViewPlane() const; void setSliceViewPlane(VolumeSliceViewPlaneEnum::Enum sliceAxisMode); VolumeSliceDrawingTypeEnum::Enum getSliceDrawingType() const; void setSliceDrawingType(const VolumeSliceDrawingTypeEnum::Enum sliceDrawingType); VolumeSliceProjectionTypeEnum::Enum getSliceProjectionType() const; void setSliceProjectionType(const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType); int32_t getMontageNumberOfColumns() const; void setMontageNumberOfColumns(const int32_t montageNumberOfColumns); int32_t getMontageNumberOfRows() const; void setMontageNumberOfRows(const int32_t montageNumberOfRows); int32_t getMontageSliceSpacing() const; void setMontageSliceSpacing(const int32_t montageSliceSpacing); void setSlicesToOrigin(); float getSliceCoordinateAxial() const; void setSliceCoordinateAxial(const float x); float getSliceCoordinateCoronal() const; void setSliceCoordinateCoronal(const float y); float getSliceCoordinateParasagittal() const; void setSliceCoordinateParasagittal(const float z); int64_t getSliceIndexAxial(const VolumeMappableInterface* volumeFile) const; void setSliceIndexAxial(const VolumeMappableInterface* volumeFile, const int64_t sliceIndexAxial); int64_t getSliceIndexCoronal(const VolumeMappableInterface* volumeFile) const; void setSliceIndexCoronal(const VolumeMappableInterface* volumeFile, const int64_t sliceIndexCoronal); int64_t getSliceIndexParasagittal(const VolumeMappableInterface* volumeFile) const; void setSliceIndexParasagittal(const VolumeMappableInterface* volumeFile, const int64_t sliceIndexParasagittal); bool isSliceParasagittalEnabled() const; void setSliceParasagittalEnabled(const bool sliceEnabledParasagittal); bool isSliceCoronalEnabled() const; void setSliceCoronalEnabled(const bool sliceEnabledCoronal); bool isSliceAxialEnabled() const; void setSliceAxialEnabled(const bool sliceEnabledAxial); void updateForVolumeFile(const VolumeMappableInterface* volumeFile); void selectSlicesAtOrigin(); void selectSlicesAtCoordinate(const float xyz[3]); bool isIdentificationUpdatesVolumeSlices() const; void setIdentificationUpdatesVolumeSlices(const bool status); void reset(); void updateYokedBrowserTabs(); bool isYoked() const; YokingGroupEnum::Enum getYokingGroup() const; void setYokingGroup(const YokingGroupEnum::Enum yokingType); bool isWholeBrainLeftEnabled() const; void setWholeBrainLeftEnabled(const bool enabled); bool isWholeBrainRightEnabled() const; void setWholeBrainRightEnabled(const bool enabled); bool isWholeBrainCerebellumEnabled() const; void setWholeBrainCerebellumEnabled(const bool enabled); float getWholeBrainLeftRightSeparation() const; void setWholeBrainLeftRightSeparation(const float separation); float getWholeBrainCerebellumSeparation() const; void setWholeBrainCerebellumSeparation(const float separation); virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private: BrowserTabContent(const BrowserTabContent&); BrowserTabContent& operator=(const BrowserTabContent&); VolumeSliceViewPlaneEnum::Enum getSliceViewPlaneForVolumeAllSliceView(const int viewport[4], const int32_t mousePressX, const int32_t mousePressY, int sliceViewportOut[4]) const; ViewingTransformations* getViewingTransformation(); const ViewingTransformations* getViewingTransformation() const; /** Number of this tab */ int32_t m_tabNumber; /** Selected surface model */ ModelSurfaceSelector* m_surfaceModelSelector; /** Selected model type */ ModelTypeEnum::Enum m_selectedModelType; /** All surface models */ std::vector m_allSurfaceModels; /** The volume model */ ModelVolume* m_volumeModel; /** The whole brain model */ ModelWholeBrain* m_wholeBrainModel; /** The surface montage model */ ModelSurfaceMontage* m_surfaceMontageModel; /** The chart model */ ModelChart* m_chartModel; /** * Name requested by user interface - reflects contents * such as Surface, Volume Slices, etc */ AString m_guiName; /** * User can set the name of the tab. */ AString m_userName; /** * Clipping planes */ ClippingPlaneGroup* m_clippingPlaneGroup; /** * Rotation matrix for oblique volume viewing */ Matrix4x4* m_obliqueVolumeRotationMatrix; /** Yoking group */ YokingGroupEnum::Enum m_yokingGroup; /** Volume Surface Outlines */ VolumeSurfaceOutlineSetModel* m_volumeSurfaceOutlineSetModel; /** Assists with creating/restoring scenes */ SceneClassAssistant* m_sceneClassAssistant; /** Transformation for cerebellum viewing */ ViewingTransformationsCerebellum* m_cerebellumViewingTransformation; /** Transformation for surface/all viewing */ ViewingTransformations* m_viewingTransformation; /** Transformation for surface/all viewing */ ViewingTransformations* m_flatSurfaceViewingTransformation; /** Transformation for volume slices viewing */ ViewingTransformationsVolume* m_volumeSliceViewingTransformation; /** Volume slice settings for volume slices */ VolumeSliceSettings* m_volumeSliceSettings; /** Whole brain surface settings. */ WholeBrainSurfaceSettings* m_wholeBrainSurfaceSettings; /** * If true, selected volume slices in tab move to location * of the identification operation. */ bool m_identificationUpdatesVolumeSlices; /* * True if constructing an instance */ bool isExecutingConstructor; /** Contains all active browser tab content instances */ static std::set s_allBrowserTabContent; }; #ifdef __BROWSER_TAB_CONTENT_DECLARE__ std::set BrowserTabContent::s_allBrowserTabContent; #endif // __BROWSER_TAB_CONTENT_DECLARE__ } // namespace #endif //__BROWSER_TAB_CONTENT__H_ workbench-1.1.1/src/Brain/CMakeLists.txt000066400000000000000000000173331255417355300200770ustar00rootroot00000000000000# # Name of project # PROJECT (Brain) # # Need XML from Qt # SET(QT_DONT_USE_QTGUI) # # Use OpenGL from QT # #SET(QT_USE_QTOPENGL TRUE) # # Add QT for includes # INCLUDE (${QT_USE_FILE}) # # Need OpenGL # FIND_PACKAGE(OpenGL REQUIRED) IF (OPENGL_FOUND) # # Need help finding includes on Apple # IF (APPLE) # When searching for the include directory, find the location # for the OpenGL framework rather than an individual header file. FIND_PATH(OPENGL_INCLUDE_DIR OpenGL.framework /System/Library/Frameworks /Library/Frameworks ~/Library/Frameworks ) ENDIF (APPLE) # # OpenGL Include Directory # INCLUDE_DIRECTORIES(${OPENGL_INCLUDE_DIR}) ELSE (OPENGL_FOUND) MESSAGE(FATAL_ERROR "OpenGL Libraries were not found") ENDIF (OPENGL_FOUND) # # Create the brain library # ADD_LIBRARY(Brain BorderDrawingTypeEnum.h Brain.h BrainOpenGL.h BrainOpenGLChartDrawingInterface.h BrainOpenGLChartDrawingFixedPipeline.h BrainOpenGLFixedPipeline.h BrainOpenGLPrimitiveDrawing.h BrainOpenGLShape.h BrainOpenGLShapeCone.h BrainOpenGLShapeCube.h BrainOpenGLShapeCylinder.h BrainOpenGLShapeRing.h BrainOpenGLShapeSphere.h BrainOpenGLTextRenderInterface.h BrainOpenGLViewportContent.h BrainOpenGLVolumeObliqueSliceDrawing.h BrainOpenGLVolumeSliceDrawing.h BrainStructure.h BrainStructureNodeAttributes.h BrowserTabContent.h CaretDataFileSelectionModel.h CaretMappableDataFileAndMapSelectionModel.h CaretOpenGLInclude.h ChartingDataManager.h CiftiConnectivityMatrixDataFileManager.h CiftiFiberTrajectoryManager.h ClippingPlaneGroup.h DisplayProperties.h DisplayPropertiesBorders.h DisplayPropertiesFiberOrientation.h DisplayPropertiesFoci.h DisplayPropertiesImages.h DisplayPropertiesLabels.h DisplayPropertiesSurface.h DisplayPropertiesVolume.h DummyFontTextRenderer.h EventBrainReset.h EventBrainStructureGetAll.h EventBrowserTabDelete.h EventBrowserTabGet.h EventBrowserTabGetAll.h EventBrowserTabNew.h EventCaretMappableDataFileMapsViewedInOverlays.h EventDataFileAdd.h EventDataFileDelete.h EventDataFileRead.h EventDataFileReload.h EventIdentificationHighlightLocation.h EventModelAdd.h EventModelDelete.h EventModelGetAll.h EventModelSurfaceGet.h EventNodeDataFilesGet.h EventNodeIdentificationColorsGetFromCharts.h EventOverlayValidate.h EventSpecFileReadDataFiles.h EventSurfacesGet.h FeatureColoringTypeEnum.h FiberOrientationSamplesLoader.h FiberOrientationSamplesVector.h FiberOrientationSymbolTypeEnum.h FociDrawingTypeEnum.h FtglFontTextRenderer.h IdentificationManager.h IdentificationStringBuilder.h IdentificationTextGenerator.h IdentificationWithColor.h IdentifiedItem.h IdentifiedItemNode.h IdentifiedItemVoxel.h Model.h ModelChart.h ModelSurface.h ModelSurfaceMontage.h ModelSurfaceSelector.h ModelTypeEnum.h ModelVolume.h ModelWholeBrain.h Overlay.h OverlaySet.h OverlaySetArray.h ProjectionViewTypeEnum.h SelectionItemDataTypeEnum.h SelectionItem.h SelectionItemBorderSurface.h SelectionItemChartDataSeries.h SelectionItemChartFrequencySeries.h SelectionItemChartMatrix.h SelectionItemChartTimeSeries.h SelectionItemCiftiConnectivityMatrixRowColumn.h SelectionItemFocusSurface.h SelectionItemFocusVolume.h SelectionItemSurfaceNode.h SelectionItemSurfaceNodeIdentificationSymbol.h SelectionItemSurfaceTriangle.h SelectionItemVoxel.h SelectionItemVoxelEditing.h SelectionItemVoxelIdentificationSymbol.h SelectionManager.h SessionManager.h Surface.h SurfaceDrawingTypeEnum.h SurfaceMontageConfigurationAbstract.h SurfaceMontageConfigurationCerebellar.h SurfaceMontageConfigurationCerebral.h SurfaceMontageConfigurationTypeEnum.h SurfaceMontageConfigurationFlatMaps.h SurfaceMontageLayoutOrientationEnum.h SurfaceMontageViewport.h SurfaceNodeColoring.h SurfaceSelectionModel.h ViewingTransformations.h ViewingTransformationsCerebellum.h ViewingTransformationsVolume.h VolumeSliceDrawingTypeEnum.h VolumeSliceSettings.h VolumeSurfaceOutlineColorOrTabModel.h VolumeSurfaceOutlineModel.h VolumeSurfaceOutlineSetModel.h WholeBrainSurfaceSettings.h WholeBrainVoxelDrawingMode.h BorderDrawingTypeEnum.cxx Brain.cxx BrainOpenGL.cxx BrainOpenGLChartDrawingFixedPipeline.cxx BrainOpenGLFixedPipeline.cxx BrainOpenGLPrimitiveDrawing.cxx BrainOpenGLShape.cxx BrainOpenGLShapeCone.cxx BrainOpenGLShapeCube.cxx BrainOpenGLShapeCylinder.cxx BrainOpenGLShapeRing.cxx BrainOpenGLShapeSphere.cxx BrainOpenGLViewportContent.cxx BrainOpenGLVolumeObliqueSliceDrawing.cxx BrainOpenGLVolumeSliceDrawing.cxx BrainStructure.cxx BrainStructureNodeAttributes.cxx BrowserTabContent.cxx CaretDataFileSelectionModel.cxx CaretMappableDataFileAndMapSelectionModel.cxx ChartingDataManager.cxx CiftiConnectivityMatrixDataFileManager.cxx CiftiFiberTrajectoryManager.cxx ClippingPlaneGroup.cxx DisplayProperties.cxx DisplayPropertiesBorders.cxx DisplayPropertiesFiberOrientation.cxx DisplayPropertiesFoci.cxx DisplayPropertiesImages.cxx DisplayPropertiesLabels.cxx DisplayPropertiesSurface.cxx DisplayPropertiesVolume.cxx DummyFontTextRenderer.cxx EventBrainReset.cxx EventBrainStructureGetAll.cxx EventBrowserTabDelete.cxx EventBrowserTabGet.cxx EventBrowserTabGetAll.cxx EventBrowserTabNew.cxx EventCaretMappableDataFileMapsViewedInOverlays.cxx EventDataFileAdd.cxx EventDataFileDelete.cxx EventDataFileRead.cxx EventDataFileReload.cxx EventIdentificationHighlightLocation.cxx EventModelAdd.cxx EventModelDelete.cxx EventModelGetAll.cxx EventModelSurfaceGet.cxx EventNodeDataFilesGet.cxx EventNodeIdentificationColorsGetFromCharts.cxx EventOverlayValidate.cxx EventSpecFileReadDataFiles.cxx EventSurfacesGet.cxx FeatureColoringTypeEnum.cxx FiberOrientationSamplesLoader.cxx FiberOrientationSymbolTypeEnum.cxx FociDrawingTypeEnum.cxx FtglFontTextRenderer.cxx IdentificationManager.cxx IdentificationStringBuilder.cxx IdentificationTextGenerator.cxx IdentificationWithColor.cxx IdentifiedItem.cxx IdentifiedItemNode.cxx IdentifiedItemVoxel.cxx Model.cxx ModelChart.cxx ModelSurface.cxx ModelSurfaceMontage.cxx ModelSurfaceSelector.cxx ModelTypeEnum.cxx ModelVolume.cxx ModelWholeBrain.cxx Overlay.cxx OverlaySet.cxx OverlaySetArray.cxx ProjectionViewTypeEnum.cxx SelectionItemDataTypeEnum.cxx SelectionItem.cxx SelectionItemBorderSurface.cxx SelectionItemChartDataSeries.cxx SelectionItemChartFrequencySeries.cxx SelectionItemChartMatrix.cxx SelectionItemChartTimeSeries.cxx SelectionItemCiftiConnectivityMatrixRowColumn.cxx SelectionItemFocusSurface.cxx SelectionItemFocusVolume.cxx SelectionItemSurfaceNode.cxx SelectionItemSurfaceNodeIdentificationSymbol.cxx SelectionItemSurfaceTriangle.cxx SelectionItemVoxel.cxx SelectionItemVoxelIdentificationSymbol.cxx SelectionItemVoxelEditing.cxx SelectionManager.cxx SessionManager.cxx Surface.cxx SurfaceDrawingTypeEnum.cxx SurfaceMontageConfigurationAbstract.cxx SurfaceMontageConfigurationCerebellar.cxx SurfaceMontageConfigurationCerebral.cxx SurfaceMontageConfigurationTypeEnum.cxx SurfaceMontageConfigurationFlatMaps.cxx SurfaceMontageLayoutOrientationEnum.cxx SurfaceMontageViewport.cxx SurfaceNodeColoring.cxx SurfaceSelectionModel.cxx ViewingTransformations.cxx ViewingTransformationsCerebellum.cxx ViewingTransformationsVolume.cxx VolumeSliceDrawingTypeEnum.cxx VolumeSliceSettings.cxx VolumeSurfaceOutlineColorOrTabModel.cxx VolumeSurfaceOutlineModel.cxx VolumeSurfaceOutlineSetModel.cxx WholeBrainSurfaceSettings.cxx WholeBrainVoxelDrawingMode.cxx ) # # Find Headers # INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/Brain ${CMAKE_SOURCE_DIR}/Charting ${CMAKE_SOURCE_DIR}/Cifti ${CMAKE_SOURCE_DIR}/FilesBase ${CMAKE_SOURCE_DIR}/Files ${CMAKE_SOURCE_DIR}/Palette ${CMAKE_SOURCE_DIR}/Gifti ${CMAKE_SOURCE_DIR}/Nifti ${CMAKE_SOURCE_DIR}/Scenes ${CMAKE_SOURCE_DIR}/Xml ${CMAKE_SOURCE_DIR}/Common ) IF (FREETYPE_FOUND) INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/FtglFont ${CMAKE_SOURCE_DIR}/FtglFont/FTGL ${FREETYPE_INCLUDE_DIR_ft2build} ${FREETYPE_INCLUDE_DIR_freetype2} ) ENDIF (FREETYPE_FOUND) workbench-1.1.1/src/Brain/CaretDataFileSelectionModel.cxx000066400000000000000000000431101255417355300233320ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CARET_DATA_FILE_SELECTION_MODEL_DECLARE__ #include "CaretDataFileSelectionModel.h" #undef __CARET_DATA_FILE_SELECTION_MODEL_DECLARE__ #include "BorderFile.h" #include "Brain.h" #include "CaretAssert.h" #include "CaretDataFile.h" #include "CaretMappableDataFile.h" #include "ChartableMatrixParcelInterface.h" #include "SceneClass.h" #include "SceneClassAssistant.h" using namespace caret; /** * \class caret::CaretDataFileSelectionModel * \brief Selection model for a CaretDataFile. * \ingroup Brain * * Maintains selection of a type of CaretDataFile. Used in the GUI * by CaretDataFileSelectionComboBox. */ /** * Constructor. This constructor is private and one of the static * factory methods should be used to create new instances of * this class. * * @param brain * Brain containing the files. * @param structure * Structure for the files. * @param fileMode * File mode that indicates how files are chosen from the 'Brain'. */ CaretDataFileSelectionModel::CaretDataFileSelectionModel(Brain* brain, const StructureEnum::Enum structure, const FileMode fileMode) : CaretObject(), m_fileMode(fileMode), m_brain(brain), m_structure(structure) { m_overrideOfAvailableFilesValid = false; CaretAssert(brain); m_sceneAssistant = new SceneClassAssistant(); m_dataFileTypes.clear(); } /** * Destructor. */ CaretDataFileSelectionModel::~CaretDataFileSelectionModel() { delete m_sceneAssistant; } /** * Create a new instance of a Caret Data File Selection Model that * selects files of the given Data File Type from the given Brain. * * @param brain * Brain from which files are obtained. * @param dataFileType * Type of the data file. */ CaretDataFileSelectionModel* CaretDataFileSelectionModel::newInstanceForCaretDataFileType(Brain* brain, const DataFileTypeEnum::Enum dataFileType) { CaretDataFileSelectionModel* model = new CaretDataFileSelectionModel(brain, StructureEnum::ALL, FILE_MODE_DATA_FILE_TYPE_ENUM); model->m_dataFileTypes.push_back(dataFileType); return model; } /** * Create a new instance of a Caret Data File Selection Model that * selects files of the given Data File Types from the given Brain. * * @param brain * Brain from which files are obtained. * @param dataFileTypes * Types of the data file. */ CaretDataFileSelectionModel* CaretDataFileSelectionModel::newInstanceForCaretDataFileTypes(Brain* brain, const std::vector& dataFileTypes) { CaretDataFileSelectionModel* model = new CaretDataFileSelectionModel(brain, StructureEnum::ALL, FILE_MODE_DATA_FILE_TYPE_ENUM); model->m_dataFileTypes.insert(model->m_dataFileTypes.end(), dataFileTypes.begin(), dataFileTypes.end()); return model; } /** * Create a new instance of a Caret Data File Selection Model that * selects files of the given Data File Types for the given structure * from the given Brain. * * @param brain * Brain from which files are obtained. * @param structure * Structure for files. Files with the identical structure are used * as well as those files with structure 'ALL'. * @param dataFileTypes * Types of the data file. */ CaretDataFileSelectionModel* CaretDataFileSelectionModel::newInstanceForCaretDataFileTypesInStructure(Brain* brain, const StructureEnum::Enum structure, const std::vector& dataFileTypes) { CaretDataFileSelectionModel* model = new CaretDataFileSelectionModel(brain, structure, FILE_MODE_DATA_FILE_TYPE_ENUM); model->m_dataFileTypes.insert(model->m_dataFileTypes.end(), dataFileTypes.begin(), dataFileTypes.end()); return model; } /** * Create a new instance of a Caret Data File Selection Model that * selects files that implement the chartable matrix parcel interface. * * @param brain * Brain from which files are obtained. */ CaretDataFileSelectionModel* CaretDataFileSelectionModel::newInstanceForChartableMatrixParcelInterface(Brain* brain) { CaretDataFileSelectionModel* model = new CaretDataFileSelectionModel(brain, StructureEnum::ALL, FILE_MODE_CHARTABLE_MATRIX_PARCEL_INTERFACE); return model; } /** * Create a new instance of a Caret Data File Selection Model that * selects files that implement the chartable matrix interface but * NOT the chartable matrix parcel interface. * * @param brain * Brain from which files are obtained. */ CaretDataFileSelectionModel* CaretDataFileSelectionModel::newInstanceForChartableMatrixSeriesInterface(Brain* brain) { CaretDataFileSelectionModel* model = new CaretDataFileSelectionModel(brain, StructureEnum::ALL, FILE_MODE_CHARTABLE_MATRIX_SERIES_INTERFACE); return model; } /** * Create a new instance of a Caret Data File Selection Model that * selectes multi-structure border files. */ CaretDataFileSelectionModel* CaretDataFileSelectionModel::newInstanceForMultiStructureBorderFiles(Brain* brain) { CaretDataFileSelectionModel* model = new CaretDataFileSelectionModel(brain, StructureEnum::ALL, FILE_MODE_MULTI_STRUCTURE_BORDER_FILES); return model; } /** * Copy constructor. * @param obj * Object that is copied. */ CaretDataFileSelectionModel::CaretDataFileSelectionModel(const CaretDataFileSelectionModel& obj) : CaretObject(obj), SceneableInterface(), m_fileMode(obj.m_fileMode), m_brain(obj.m_brain) { this->copyHelperCaretDataFileSelectionModel(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ CaretDataFileSelectionModel& CaretDataFileSelectionModel::operator=(const CaretDataFileSelectionModel& obj) { if (this != &obj) { CaretObject::operator=(obj); this->copyHelperCaretDataFileSelectionModel(obj); } return *this; } /** * Helps with copying an object of this type. * @param obj * Object that is copied. */ void CaretDataFileSelectionModel::copyHelperCaretDataFileSelectionModel(const CaretDataFileSelectionModel& obj) { m_brain = obj.m_brain; m_structure = obj.m_structure; m_dataFileTypes = obj.m_dataFileTypes; m_selectedFile = obj.m_selectedFile; m_overrideOfAvailableFilesValid = obj.m_overrideOfAvailableFilesValid; m_overrideOfAvailableFiles = obj.m_overrideOfAvailableFiles; } /** * @return The selected file or NULL if no file is available. */ CaretDataFile* CaretDataFileSelectionModel::getSelectedFile() { updateSelection(); return m_selectedFile; } /** * @return The selected file or NULL if no file is available. */ const CaretDataFile* CaretDataFileSelectionModel::getSelectedFile() const { updateSelection(); return m_selectedFile; } /** * Set the selected file or NULL if no file is available. * * @param selectedFile * Set the selected file. */ void CaretDataFileSelectionModel::setSelectedFile(CaretDataFile* selectedFile) { m_selectedFile = selectedFile; } /** * @return Files available for selection. */ std::vector CaretDataFileSelectionModel::getAvailableFiles() const { if (m_overrideOfAvailableFilesValid) { return m_overrideOfAvailableFiles; } std::vector caretDataFiles; switch (m_fileMode) { case FILE_MODE_DATA_FILE_TYPE_ENUM: { m_brain->getAllDataFilesWithDataFileTypes(m_dataFileTypes, caretDataFiles); } break; case FILE_MODE_CHARTABLE_MATRIX_PARCEL_INTERFACE: { std::vector chartFiles; m_brain->getAllChartableMatrixDataFiles(chartFiles); for (std::vector::iterator iter = chartFiles.begin(); iter != chartFiles.end(); iter++) { ChartableMatrixInterface* chartFile = *iter; ChartableMatrixParcelInterface* chartParcelFile = dynamic_cast(chartFile); /* * Want files that ARE parcel chartable */ if (chartParcelFile != NULL) { CaretMappableDataFile* mapFile = chartParcelFile->getMatrixChartCaretMappableDataFile(); caretDataFiles.push_back(mapFile); } } } case FILE_MODE_CHARTABLE_MATRIX_SERIES_INTERFACE: { std::vector chartFiles; m_brain->getAllChartableMatrixDataFiles(chartFiles); for (std::vector::iterator iter = chartFiles.begin(); iter != chartFiles.end(); iter++) { ChartableMatrixInterface* chartFile = *iter; ChartableMatrixParcelInterface* chartParcelFile = dynamic_cast(chartFile); /* * Want files that are NOT parcel chartable */ if (chartParcelFile == NULL) { CaretMappableDataFile* mapFile = chartFile->getMatrixChartCaretMappableDataFile(); caretDataFiles.push_back(mapFile); } } } break; case FILE_MODE_MULTI_STRUCTURE_BORDER_FILES: { const int numBorderFiles = m_brain->getNumberOfBorderFiles(); for (int32_t i = 0; i < numBorderFiles; i++) { BorderFile* borderFile = m_brain->getBorderFile(i); if ( ! borderFile->isSingleStructure()) { caretDataFiles.push_back(borderFile); } } } break; } if (m_structure == StructureEnum::ALL) { return caretDataFiles; } std::vector caretDataFilesOut; for (std::vector::iterator iter = caretDataFiles.begin(); iter != caretDataFiles.end(); iter++) { CaretDataFile* cdf = *iter; const StructureEnum::Enum fileStructure = cdf->getStructure(); if ((fileStructure == StructureEnum::ALL) || (fileStructure == StructureEnum::INVALID)) { caretDataFilesOut.push_back(cdf); } else if (fileStructure == m_structure) { caretDataFilesOut.push_back(cdf); } } return caretDataFilesOut; } /** * Override the available files with the given files. Once this method * is called, these will be the available files until this method is called * again. * * @param availableFiles * Files that will be used in this model. */ void CaretDataFileSelectionModel::overrideAvailableDataFiles(std::vector& availableFiles) { m_overrideOfAvailableFiles = availableFiles; m_overrideOfAvailableFilesValid = true; } /** * @return Structure used in this model. */ StructureEnum::Enum CaretDataFileSelectionModel::getStructure() const { return m_structure; } /** * Set the structure used for this model. * * @param structure * New structure for this model. */ void CaretDataFileSelectionModel::setStructure(const StructureEnum::Enum structure) { m_structure = structure; updateSelection(); } /** * Update the selected file. */ void CaretDataFileSelectionModel::updateSelection() const { std::vector caretDataFiles = getAvailableFiles(); if (caretDataFiles.empty()) { m_selectedFile = NULL; return; } if (m_selectedFile != NULL) { if (std::find(caretDataFiles.begin(), caretDataFiles.end(), m_selectedFile) == caretDataFiles.end()) { m_selectedFile = NULL; } } if (m_selectedFile == NULL) { if (! caretDataFiles.empty()) { m_selectedFile = caretDataFiles[0]; } } } /** * Get a description of this object's content. * @return String describing this object's content. */ AString CaretDataFileSelectionModel::toString() const { return "CaretDataFileSelectionModel"; } /** * Save information specific to this type of model to the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @param instanceName * Name of instance in the scene. */ SceneClass* CaretDataFileSelectionModel::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "CaretDataFileSelectionModel", 1); m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); CaretDataFile* caretDataFile = getSelectedFile(); if (caretDataFile != NULL) { sceneClass->addPathName("selectedFileNameWithPath", caretDataFile->getFileName()); sceneClass->addString("selectedFileNameNoPath", caretDataFile->getFileNameNoPath()); } // Uncomment if sub-classes must save to scene //saveSubClassDataToScene(sceneAttributes, // sceneClass); return sceneClass; } /** * Restore information specific to the type of model from the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass from which model specific information is obtained. */ void CaretDataFileSelectionModel::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } setSelectedFile(NULL); m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); bool foundFileFlag = false; const AString selectedFileNameWithPath = sceneClass->getPathNameValue("selectedFileNameWithPath"); if ( ! selectedFileNameWithPath.isEmpty()) { std::vector caretDataFiles = getAvailableFiles(); for (std::vector::iterator iter = caretDataFiles.begin(); iter != caretDataFiles.end(); iter++) { CaretDataFile* cdf = *iter; if (cdf->getFileName() == selectedFileNameWithPath) { setSelectedFile(cdf); foundFileFlag = true; break; } } } if ( ! foundFileFlag) { const AString selectedFileName = sceneClass->getStringValue("selectedFileNameNoPath", ""); if ( ! selectedFileName.isEmpty()) { std::vector caretDataFiles = getAvailableFiles(); for (std::vector::iterator iter = caretDataFiles.begin(); iter != caretDataFiles.end(); iter++) { CaretDataFile* cdf = *iter; if (cdf->getFileNameNoPath() == selectedFileName) { setSelectedFile(cdf); break; } } } } //Uncomment if sub-classes must restore from scene //restoreSubClassDataFromScene(sceneAttributes, // sceneClass); } workbench-1.1.1/src/Brain/CaretDataFileSelectionModel.h000066400000000000000000000132521255417355300227630ustar00rootroot00000000000000#ifndef __CARET_DATA_FILE_SELECTION_MODEL_H__ #define __CARET_DATA_FILE_SELECTION_MODEL_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "DataFileTypeEnum.h" #include "SceneableInterface.h" #include "StructureEnum.h" namespace caret { class Brain; class CaretDataFile; class SceneClassAssistant; class CaretDataFileSelectionModel : public CaretObject, public SceneableInterface { public: static CaretDataFileSelectionModel* newInstanceForCaretDataFileType(Brain* brain, const DataFileTypeEnum::Enum dataFileType); static CaretDataFileSelectionModel* newInstanceForCaretDataFileTypes(Brain* brain, const std::vector& dataFileTypes); static CaretDataFileSelectionModel* newInstanceForCaretDataFileTypesInStructure(Brain* brain, const StructureEnum::Enum structure, const std::vector& dataFileTypes); static CaretDataFileSelectionModel* newInstanceForChartableMatrixParcelInterface(Brain* brain); static CaretDataFileSelectionModel* newInstanceForChartableMatrixSeriesInterface(Brain* brain); static CaretDataFileSelectionModel* newInstanceForMultiStructureBorderFiles(Brain* brain); virtual ~CaretDataFileSelectionModel(); CaretDataFileSelectionModel(const CaretDataFileSelectionModel& obj); CaretDataFileSelectionModel& operator=(const CaretDataFileSelectionModel& obj); CaretDataFile* getSelectedFile(); const CaretDataFile* getSelectedFile() const; StructureEnum::Enum getStructure() const; void setStructure(const StructureEnum::Enum structure); std::vector getAvailableFiles() const; void overrideAvailableDataFiles(std::vector& availableFiles); /** * @return Selected file dynamically cast to the templated * data file type. */ template inline T* getSelectedFileOfType() { CaretDataFile* cdf = getSelectedFile(); if (cdf != NULL) { return dynamic_cast(getSelectedFile()); } return NULL; } void setSelectedFile(CaretDataFile* selectedFile); // ADD_NEW_METHODS_HERE virtual AString toString() const; virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); // If there will be sub-classes of this class that need to save // and restore data from scenes, these pure virtual methods can // be uncommented to force their implemetation by sub-classes. // protected: // virtual void saveSubClassDataToScene(const SceneAttributes* sceneAttributes, // SceneClass* sceneClass) = 0; // // virtual void restoreSubClassDataFromScene(const SceneAttributes* sceneAttributes, // const SceneClass* sceneClass) = 0; private: enum FileMode { FILE_MODE_CHARTABLE_MATRIX_PARCEL_INTERFACE, FILE_MODE_CHARTABLE_MATRIX_SERIES_INTERFACE, FILE_MODE_DATA_FILE_TYPE_ENUM, FILE_MODE_MULTI_STRUCTURE_BORDER_FILES }; CaretDataFileSelectionModel(Brain* brain, const StructureEnum::Enum structure, const FileMode fileMode); void copyHelperCaretDataFileSelectionModel(const CaretDataFileSelectionModel& obj); void updateSelection() const; const FileMode m_fileMode; std::vector m_overrideOfAvailableFiles; bool m_overrideOfAvailableFilesValid; Brain* m_brain; StructureEnum::Enum m_structure; std::vector m_dataFileTypes; mutable CaretDataFile* m_selectedFile; SceneClassAssistant* m_sceneAssistant; // ADD_NEW_MEMBERS_HERE }; #ifdef __CARET_DATA_FILE_SELECTION_MODEL_DECLARE__ // #endif // __CARET_DATA_FILE_SELECTION_MODEL_DECLARE__ } // namespace #endif //__CARET_DATA_FILE_SELECTION_MODEL_H__ workbench-1.1.1/src/Brain/CaretMappableDataFileAndMapSelectionModel.cxx000066400000000000000000000331351255417355300260630ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CARET_MAPPABLE_DATA_FILE_AND_MAP_SELECTION_MODEL_DECLARE__ #include "CaretMappableDataFileAndMapSelectionModel.h" #undef __CARET_MAPPABLE_DATA_FILE_AND_MAP_SELECTION_MODEL_DECLARE__ #include "Brain.h" #include "CaretAssert.h" #include "CaretDataFileSelectionModel.h" #include "CaretLogger.h" #include "CaretMappableDataFile.h" #include "SceneClass.h" #include "SceneClassAssistant.h" using namespace caret; /** * \class caret::CaretMappableDataFileAndMapSelectionModel * \brief Model for selection of a CaretMappableDataFile and map index. * \ingroup Brain */ /** * Constructor. * * @param brain * Brain from which files are obtained. * @param dataFileType * Type of data files available for selection. */ CaretMappableDataFileAndMapSelectionModel::CaretMappableDataFileAndMapSelectionModel(Brain* brain, const DataFileTypeEnum::Enum dataFileType) : CaretObject() { std::vector dataFileTypesVector; dataFileTypesVector.push_back(dataFileType); performConstruction(brain, dataFileTypesVector); } /** * Constructor for multiple types of files. * * @param brain * Brain from which files are obtained. * @param dataFileTypes * Types of data files available for selection. */ CaretMappableDataFileAndMapSelectionModel::CaretMappableDataFileAndMapSelectionModel(Brain* brain, const std::vector& dataFileTypes) : CaretObject() { performConstruction(brain, dataFileTypes); } /** * Destructor. */ CaretMappableDataFileAndMapSelectionModel::~CaretMappableDataFileAndMapSelectionModel() { delete m_caretDataFileSelectionModel; delete m_sceneAssistant; } /** * Finish construction for an instance of this class. * * @param brain * Brain from which files are obtained. * @param dataFileTypes * Types of data files available for selection. */ void CaretMappableDataFileAndMapSelectionModel::performConstruction(Brain* brain, const std::vector& dataFileTypes) { m_brain = brain; m_dataFileTypes = dataFileTypes; for (std::vector::const_iterator typeIter = dataFileTypes.begin(); typeIter != dataFileTypes.end(); typeIter++) { const DataFileTypeEnum::Enum dataFileType = *typeIter; bool isMappableFile = false; switch (dataFileType) { case DataFileTypeEnum::BORDER: break; case DataFileTypeEnum::CONNECTIVITY_DENSE: isMappableFile = true; break; case DataFileTypeEnum::CONNECTIVITY_DENSE_LABEL: isMappableFile = true; break; case DataFileTypeEnum::CONNECTIVITY_DENSE_PARCEL: isMappableFile = true; break; case DataFileTypeEnum::CONNECTIVITY_DENSE_SCALAR: isMappableFile = true; break; case DataFileTypeEnum::CONNECTIVITY_DENSE_TIME_SERIES: isMappableFile = true; break; case DataFileTypeEnum::CONNECTIVITY_FIBER_ORIENTATIONS_TEMPORARY: break; case DataFileTypeEnum::CONNECTIVITY_FIBER_TRAJECTORY_TEMPORARY: isMappableFile = true; break; case DataFileTypeEnum::CONNECTIVITY_PARCEL: isMappableFile = true; break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_DENSE: isMappableFile = true; break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_LABEL: isMappableFile = true; break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_SCALAR: isMappableFile = true; break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_SERIES: isMappableFile = true; break; case DataFileTypeEnum::CONNECTIVITY_SCALAR_DATA_SERIES: isMappableFile = true; break; case DataFileTypeEnum::FOCI: break; case DataFileTypeEnum::IMAGE: break; case DataFileTypeEnum::LABEL: isMappableFile = true; break; case DataFileTypeEnum::METRIC: isMappableFile = true; break; case DataFileTypeEnum::PALETTE: break; case DataFileTypeEnum::RGBA: isMappableFile = true; break; case DataFileTypeEnum::SCENE: break; case DataFileTypeEnum::SPECIFICATION: break; case DataFileTypeEnum::SURFACE: break; case DataFileTypeEnum::UNKNOWN: break; case DataFileTypeEnum::VOLUME: break; } CaretAssert(isMappableFile); if ( ! isMappableFile) { CaretLogSevere(DataFileTypeEnum::toGuiName(dataFileType) + " is not a valid mappable data file."); } } m_caretDataFileSelectionModel = CaretDataFileSelectionModel::newInstanceForCaretDataFileTypes(brain, dataFileTypes); m_selectedMapIndex = -1; m_sceneAssistant = new SceneClassAssistant(); m_sceneAssistant->add("m_caretDataFileSelectionModel", "CaretDataFileSelectionModel", m_caretDataFileSelectionModel); m_sceneAssistant->add("m_selectedMapIndex", &m_selectedMapIndex); } /** * Copy constructor. * @param obj * Object that is copied. */ CaretMappableDataFileAndMapSelectionModel::CaretMappableDataFileAndMapSelectionModel(const CaretMappableDataFileAndMapSelectionModel& obj) : CaretObject(obj), SceneableInterface(obj) { this->copyHelperCaretMappableDataFileAndMapSelectionModel(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ CaretMappableDataFileAndMapSelectionModel& CaretMappableDataFileAndMapSelectionModel::operator=(const CaretMappableDataFileAndMapSelectionModel& obj) { if (this != &obj) { CaretObject::operator=(obj); this->copyHelperCaretMappableDataFileAndMapSelectionModel(obj); } return *this; } /** * Helps with copying an object of this type. * @param obj * Object that is copied. */ void CaretMappableDataFileAndMapSelectionModel::copyHelperCaretMappableDataFileAndMapSelectionModel(const CaretMappableDataFileAndMapSelectionModel& obj) { *m_caretDataFileSelectionModel = *obj.m_caretDataFileSelectionModel; m_selectedMapIndex = obj.m_selectedMapIndex; } /** * @return The data file selection model. */ CaretDataFileSelectionModel* CaretMappableDataFileAndMapSelectionModel::getCaretDataFileSelectionModel() { return m_caretDataFileSelectionModel; } /** * @return the selected file. */ CaretMappableDataFile* CaretMappableDataFileAndMapSelectionModel::getSelectedFile() { return dynamic_cast(m_caretDataFileSelectionModel->getSelectedFile()); } /** * @return the selected file. */ const CaretMappableDataFile* CaretMappableDataFileAndMapSelectionModel::getSelectedFile() const { return dynamic_cast(m_caretDataFileSelectionModel->getSelectedFile()); } /** * @return the selected map index (-1 if no map available). */ int32_t CaretMappableDataFileAndMapSelectionModel::getSelectedMapIndex() const { const CaretMappableDataFile* cmdf = getSelectedFile(); if (cmdf != NULL) { const int32_t numMaps = cmdf->getNumberOfMaps(); if (numMaps > 0) { if (m_selectedMapIndex < 0) { m_selectedMapIndex = 0; } else if (m_selectedMapIndex >= numMaps) { m_selectedMapIndex = numMaps - 1; } } else { m_selectedMapIndex = -1; } } else { m_selectedMapIndex = -1; } return m_selectedMapIndex; } /** * @return the files in this model. */ std::vector CaretMappableDataFileAndMapSelectionModel::getAvailableFiles() const { std::vector caretDataFiles = m_caretDataFileSelectionModel->getAvailableFiles(); std::vector mappableFiles; for (std::vector::iterator iter = caretDataFiles.begin(); iter != caretDataFiles.end(); iter++) { CaretMappableDataFile* cmdf = dynamic_cast(*iter); if (cmdf != NULL) { mappableFiles.push_back(cmdf); } } return mappableFiles; } /** * Override the available files with the given files. Once this method * is called, these will be the available files until this method is called * again. * * @param availableFiles * Files that will be used in this model. */ void CaretMappableDataFileAndMapSelectionModel::overrideAvailableDataFiles(std::vector& availableFiles) { std::vector files(availableFiles.begin(), availableFiles.end()); m_caretDataFileSelectionModel->overrideAvailableDataFiles(files); } /** * Set the selected file * * @param selectedFile * New selected file. */ void CaretMappableDataFileAndMapSelectionModel::setSelectedFile(CaretMappableDataFile* selectedFile) { if (selectedFile != NULL) { const DataFileTypeEnum::Enum fileType = selectedFile->getDataFileType(); if (std::find(m_dataFileTypes.begin(), m_dataFileTypes.end(), fileType) == m_dataFileTypes.end()) { AString validFileTypeNames; for (std::vector::const_iterator typeIter = m_dataFileTypes.begin(); typeIter != m_dataFileTypes.end(); typeIter++) { const DataFileTypeEnum::Enum dataFileType = *typeIter; validFileTypeNames.append(DataFileTypeEnum::toName(dataFileType) + " "); } const AString msg("Attempting to set file that is of type " + DataFileTypeEnum::toGuiName(fileType) + " but model is for type(s) " + validFileTypeNames); CaretAssertMessage(0, msg); CaretLogSevere(msg); return; } } m_caretDataFileSelectionModel->setSelectedFile(selectedFile); } /** * Set the selected map index * * @param mapIndex * New selected map index. */ void CaretMappableDataFileAndMapSelectionModel::setSelectedMapIndex(const int32_t mapIndex) { m_selectedMapIndex = mapIndex; } /** * Save information specific to this type of model to the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @param instanceName * Name of instance in the scene. */ SceneClass* CaretMappableDataFileAndMapSelectionModel::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "CaretMappableDataFileAndMapSelectionModel", 1); m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); // Uncomment if sub-classes must save to scene //saveSubClassDataToScene(sceneAttributes, // sceneClass); return sceneClass; } /** * Restore information specific to the type of model from the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass from which model specific information is obtained. */ void CaretMappableDataFileAndMapSelectionModel::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); //Uncomment if sub-classes must restore from scene //restoreSubClassDataFromScene(sceneAttributes, // sceneClass); } workbench-1.1.1/src/Brain/CaretMappableDataFileAndMapSelectionModel.h000066400000000000000000000112521255417355300255040ustar00rootroot00000000000000#ifndef __CARET_MAPPABLE_DATA_FILE_AND_MAP_SELECTION_MODEL_H__ #define __CARET_MAPPABLE_DATA_FILE_AND_MAP_SELECTION_MODEL_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "DataFileTypeEnum.h" #include "SceneableInterface.h" namespace caret { class Brain; class CaretDataFileSelectionModel; class CaretMappableDataFile; class SceneClassAssistant; class CaretMappableDataFileAndMapSelectionModel : public CaretObject, public SceneableInterface { public: CaretMappableDataFileAndMapSelectionModel(Brain* brain, const DataFileTypeEnum::Enum dataFileType); CaretMappableDataFileAndMapSelectionModel(Brain* brain, const std::vector& dataFileTypes); virtual ~CaretMappableDataFileAndMapSelectionModel(); CaretMappableDataFileAndMapSelectionModel(const CaretMappableDataFileAndMapSelectionModel& obj); CaretMappableDataFileAndMapSelectionModel& operator=(const CaretMappableDataFileAndMapSelectionModel& obj); CaretDataFileSelectionModel* getCaretDataFileSelectionModel(); CaretMappableDataFile* getSelectedFile(); const CaretMappableDataFile* getSelectedFile() const; int32_t getSelectedMapIndex() const; std::vector getAvailableFiles() const; void overrideAvailableDataFiles(std::vector& availableFiles); /** * @return Selected file dynamically cast to the templated * data file type. */ template inline T* getSelectedFileOfType() { CaretMappableDataFile* cmdf = getSelectedFile(); if (cmdf != NULL) { return dynamic_cast(getSelectedFile()); } return NULL; } void setSelectedFile(CaretMappableDataFile* selectedFile); void setSelectedMapIndex(const int32_t mapIndex); // ADD_NEW_METHODS_HERE virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); // If there will be sub-classes of this class that need to save // and restore data from scenes, these pure virtual methods can // be uncommented to force their implemetation by sub-classes. // protected: // virtual void saveSubClassDataToScene(const SceneAttributes* sceneAttributes, // SceneClass* sceneClass) = 0; // // virtual void restoreSubClassDataFromScene(const SceneAttributes* sceneAttributes, // const SceneClass* sceneClass) = 0; private: void copyHelperCaretMappableDataFileAndMapSelectionModel(const CaretMappableDataFileAndMapSelectionModel& obj); void performConstruction(Brain* brain, const std::vector& dataFileTypes); SceneClassAssistant* m_sceneAssistant; Brain* m_brain; std::vector m_dataFileTypes; CaretDataFileSelectionModel* m_caretDataFileSelectionModel; mutable int32_t m_selectedMapIndex; // ADD_NEW_MEMBERS_HERE }; #ifdef __CARET_MAPPABLE_DATA_FILE_AND_MAP_SELECTION_MODEL_DECLARE__ // #endif // __CARET_MAPPABLE_DATA_FILE_AND_MAP_SELECTION_MODEL_DECLARE__ } // namespace #endif //__CARET_MAPPABLE_DATA_FILE_AND_MAP_SELECTION_MODEL_H__ workbench-1.1.1/src/Brain/CaretOpenGLInclude.h000066400000000000000000000024151255417355300211120ustar00rootroot00000000000000#ifndef __CARET_OPEN_GL_INCLUDE_H__ #define __CARET_OPEN_GL_INCLUDE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ /** * \file CaretOpenGLInclude.h * \brief Contains OpenGL include declarations that work for all operating systems. * \ingroup Brain */ #ifdef CARET_OS_WINDOWS #include #endif #ifdef CARET_OS_MACOSX #include #include #else #define GL_GLEXT_PROTOTYPES #include #include #endif #endif //__CARET_OPEN_GL_INCLUDE_H__ workbench-1.1.1/src/Brain/ChartingDataManager.cxx000066400000000000000000000135031255417355300217020ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CHARTING_DATA_MANAGER_DECLARE__ #include "ChartingDataManager.h" #undef __CHARTING_DATA_MANAGER_DECLARE__ #include "Brain.h" #include "CaretAssert.h" #include "EventManager.h" #include "EventMapYokingSelectMap.h" #include "ModelChart.h" #include "SurfaceFile.h" using namespace caret; /** * \class caret::ChartingDataManager * \brief Manages charting data. * \ingroup Brain */ /** * Constructor. */ ChartingDataManager::ChartingDataManager(Brain* brain) : CaretObject(), m_brain(brain) { CaretAssert(brain); /* * Need PROCESSED event (after others have handled the event) */ EventManager::get()->addProcessedEventListener(this, EventTypeEnum::EVENT_MAP_YOKING_SELECT_MAP); } /** * Destructor. */ ChartingDataManager::~ChartingDataManager() { EventManager::get()->removeAllEventsFromListener(this); } /** * Receive an event. * * @param event * The event that the receive can respond to. */ void ChartingDataManager::receiveEvent(Event* event) { if (event->getEventType() == EventTypeEnum::EVENT_MAP_YOKING_SELECT_MAP) { EventMapYokingSelectMap* yokeMapEvent = dynamic_cast(event); CaretAssert(yokeMapEvent); ModelChart* modelChart = m_brain->getChartModel(); if (modelChart != NULL) { modelChart->loadChartDataForYokedCiftiMappableFiles(yokeMapEvent->getMapYokingGroup(), yokeMapEvent->getMapIndex()); } } } /** * Load the average of chart data for a group of surface nodes. * * @param surfaceFile * The surface file * @param nodeIndices * Indices of nodes whose chart data is averaged */ void ChartingDataManager::loadAverageChartForSurfaceNodes(const SurfaceFile* surfaceFile, const std::vector& nodeIndices) const { CaretAssert(surfaceFile); ModelChart* modelChart = m_brain->getChartModel(); if (modelChart != NULL) { modelChart->loadAverageChartDataForSurfaceNodes(surfaceFile->getStructure(), surfaceFile->getNumberOfNodes(), nodeIndices); } } /** * Load chart data for a surface node. * * @param surfaceFile * The surface file * @param nodeIndex * Index of node. */ void ChartingDataManager::loadChartForSurfaceNode(const SurfaceFile* surfaceFile, const int32_t nodeIndex) const { CaretAssert(surfaceFile); ModelChart* modelChart = m_brain->getChartModel(); if (modelChart != NULL) { modelChart->loadChartDataForSurfaceNode(surfaceFile->getStructure(), surfaceFile->getNumberOfNodes(), nodeIndex); } } /** * Load chart data from given file at the given row. * * @param ciftiMapFile * The CIFTI file. * @param rowIndex * Index of row in the file. */ void ChartingDataManager::loadChartForCiftiMappableFileRow(CiftiMappableDataFile* ciftiMapFile, const int32_t rowIndex) const { CaretAssert(ciftiMapFile); ModelChart* modelChart = m_brain->getChartModel(); if (modelChart != NULL) { modelChart->loadChartDataForCiftiMappableFileRow(ciftiMapFile, rowIndex); } } /** * Load chart data for a voxel. * * @param xyz * Coordinate of voxel. */ void ChartingDataManager::loadChartForVoxelAtCoordinate(const float xyz[3]) const { ModelChart* modelChart = m_brain->getChartModel(); if (modelChart != NULL) { modelChart->loadChartDataForVoxelAtCoordinate(xyz); } } /** * @return True if there are charting that retrieve data from the network. */ bool ChartingDataManager::hasNetworkFiles() const { /* * At this time, all 'chartable' files are read in their entirety * so all data is local once the file is read. */ return false; // std::vector chartFiles; // if (requireChartingEnableInFiles) { // m_brain->getAllChartableDataFilesWithChartingEnabled(chartFiles); // } // else { // m_brain->getAllChartableDataFiles(chartFiles); // } // // for (std::vector::iterator fileIter = chartFiles.begin(); // fileIter != chartFiles.end(); // fileIter++) { // ChartableLineSeriesBrainordinateInterface* chartFile = *fileIter; // // CaretDataFile* caretDataFile = dynamic_cast(chartFile); // CaretAssert(caretDataFile); // if (caretDataFile->isEmpty() == false) { // const AString filename = caretDataFile->getFileName(); // if (CaretDataFile::isFileOnNetwork(filename)) { // return true; // } // } // } // // return false; } workbench-1.1.1/src/Brain/ChartingDataManager.h000066400000000000000000000045041255417355300213300ustar00rootroot00000000000000#ifndef __CHARTING_DATA_MANAGER_H__ #define __CHARTING_DATA_MANAGER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "EventListenerInterface.h" namespace caret { class Brain; class CiftiMappableDataFile; class SurfaceFile; class ChartingDataManager : public CaretObject, public EventListenerInterface { public: ChartingDataManager(Brain* brain); virtual ~ChartingDataManager(); void loadAverageChartForSurfaceNodes(const SurfaceFile* surfaceFile, const std::vector& nodeIndices) const; void loadChartForSurfaceNode(const SurfaceFile* surfaceFile, const int32_t nodeIndex) const; void loadChartForVoxelAtCoordinate(const float xyz[3]) const; void loadChartForCiftiMappableFileRow(CiftiMappableDataFile* ciftiMapFile, const int32_t rowIndex) const; bool hasNetworkFiles() const; virtual void receiveEvent(Event* event); private: ChartingDataManager(const ChartingDataManager&); ChartingDataManager& operator=(const ChartingDataManager&); Brain* m_brain; // ADD_NEW_MEMBERS_HERE }; #ifdef __CHARTING_DATA_MANAGER_DECLARE__ // #endif // __CHARTING_DATA_MANAGER_DECLARE__ } // namespace #endif //__CHARTING_DATA_MANAGER_H__ workbench-1.1.1/src/Brain/CiftiConnectivityMatrixDataFileManager.cxx000066400000000000000000000422241255417355300255670ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CIFTI_CONNECTIVITY_MATRIX_DATA_FILE_MANAGER_DECLARE__ #include "CiftiConnectivityMatrixDataFileManager.h" #undef __CIFTI_CONNECTIVITY_MATRIX_DATA_FILE_MANAGER_DECLARE__ #include "Brain.h" #include "CaretAssert.h" #include "CiftiConnectivityMatrixParcelFile.h" #include "CiftiMappableConnectivityMatrixDataFile.h" #include "EventManager.h" #include "EventSurfaceColoringInvalidate.h" #include "SceneAttributes.h" #include "SceneClass.h" #include "SceneClassArray.h" #include "ScenePrimitiveArray.h" #include "Surface.h" #include "SurfaceFile.h" using namespace caret; /** * \class caret::CiftiConnectivityMatrixDataFileManager * \brief Manages loading data from cifti connectivity files * \ingroup Brain */ /** * Constructor. * * @param brain * Brain that uses this instance. */ CiftiConnectivityMatrixDataFileManager::CiftiConnectivityMatrixDataFileManager() : CaretObject() { } /** * Destructor. */ CiftiConnectivityMatrixDataFileManager::~CiftiConnectivityMatrixDataFileManager() { } /** * Load row from the given parcel file. * * @param brain * Brain for which data is loaded. * @param parcelFile * The parcel file. * @param rowIndex * Index of the row. * @param columnIndex * Index of the column. * @param rowColumnInformationOut * Appends one string for each row/column loaded * @return * true if success, else false. */ bool CiftiConnectivityMatrixDataFileManager::loadRowOrColumnFromParcelFile(Brain* brain, CiftiConnectivityMatrixParcelFile* parcelFile, const int32_t rowIndex, const int32_t columnIndex, std::vector& rowColumnInformationOut) { CaretAssert(parcelFile); // ChartableMatrixInterface* matrixFile = dynamic_cast(parcelFile); // CaretAssert(matrixFile); int32_t rowColumnIndexToLoad = -1; switch (parcelFile->getMatrixLoadingDimension()) { case ChartMatrixLoadingDimensionEnum::CHART_MATRIX_LOADING_BY_COLUMN: rowColumnIndexToLoad = columnIndex; break; case ChartMatrixLoadingDimensionEnum::CHART_MATRIX_LOADING_BY_ROW: rowColumnIndexToLoad = rowIndex; break; } /* * If yoked, find other files yoked to the same group */ const YokingGroupEnum::Enum selectedYokingGroup = parcelFile->getYokingGroup(); std::vector parcelFilesToLoadFrom; if (selectedYokingGroup != YokingGroupEnum::YOKING_GROUP_OFF) { for (int32_t i = 0; i < brain->getNumberOfConnectivityMatrixParcelFiles(); i++) { CiftiConnectivityMatrixParcelFile* pf = brain->getConnectivityMatrixParcelFile(i); if (pf->getYokingGroup() == selectedYokingGroup) { parcelFilesToLoadFrom.push_back(pf); } } } else { parcelFilesToLoadFrom.push_back(parcelFile); } // AString rowColumnInfo; PaletteFile* paletteFile = brain->getPaletteFile(); const int32_t mapIndex = 0; /* * Load row/color for the "parcelFile" and any other files with * which it is yoked. */ for (std::vector::iterator iter = parcelFilesToLoadFrom.begin(); iter != parcelFilesToLoadFrom.end(); iter++) { CiftiConnectivityMatrixParcelFile* pf = *iter; switch (pf->getMatrixLoadingDimension()) { case ChartMatrixLoadingDimensionEnum::CHART_MATRIX_LOADING_BY_COLUMN: pf->loadDataForColumnIndex(rowColumnIndexToLoad); pf->updateScalarColoringForMap(mapIndex, paletteFile); rowColumnInformationOut.push_back(pf->getFileNameNoPath() + " column index=" + AString::number(rowColumnIndexToLoad)); break; case ChartMatrixLoadingDimensionEnum::CHART_MATRIX_LOADING_BY_ROW: pf->loadDataForRowIndex(rowColumnIndexToLoad); pf->updateScalarColoringForMap(mapIndex, paletteFile); rowColumnInformationOut.push_back(pf->getFileNameNoPath() + " row index=" + AString::number(rowColumnIndexToLoad)); break; } } return true; } bool CiftiConnectivityMatrixDataFileManager::loadRowOrColumnFromConnectivityMatrixFile(Brain* brain, CiftiMappableConnectivityMatrixDataFile* ciftiConnMatrixFile, const int32_t rowIndex, const int32_t columnIndex, std::vector& rowColumnInformationOut) { PaletteFile* paletteFile = brain->getPaletteFile(); const int32_t mapIndex = 0; if (rowIndex >= 0) { ciftiConnMatrixFile->loadDataForRowIndex(rowIndex); ciftiConnMatrixFile->updateScalarColoringForMap(mapIndex, paletteFile); rowColumnInformationOut.push_back(ciftiConnMatrixFile->getFileNameNoPath() + " row index=" + AString::number(rowIndex)); } else if (columnIndex >= 0) { ciftiConnMatrixFile->loadDataForColumnIndex(columnIndex); ciftiConnMatrixFile->updateScalarColoringForMap(mapIndex, paletteFile); rowColumnInformationOut.push_back(ciftiConnMatrixFile->getFileNameNoPath() + " column index=" + AString::number(columnIndex)); } return true; } /** * Load data for the given surface node index. * @param brain * Brain for which data is loaded. * @param surfaceFile * Surface File that contains the node (uses its structure). * @param nodeIndex * Index of the surface node. * @param rowColumnInformationOut * Appends one string for each row/column loaded * @return * true if any connectivity loaders are active, else false. */ bool CiftiConnectivityMatrixDataFileManager::loadDataForSurfaceNode(Brain* brain, const SurfaceFile* surfaceFile, const int32_t nodeIndex, std::vector& rowColumnInformationOut) { std::vector ciftiMatrixFiles; brain->getAllCiftiConnectivityMatrixFiles(ciftiMatrixFiles); PaletteFile* paletteFile = brain->getPaletteFile(); bool haveData = false; for (std::vector::iterator iter = ciftiMatrixFiles.begin(); iter != ciftiMatrixFiles.end(); iter++) { CiftiMappableConnectivityMatrixDataFile* cmf = *iter; if (cmf->isEmpty() == false) { const int32_t mapIndex = 0; int64_t rowIndex = -1; int64_t columnIndex = -1; cmf->loadMapDataForSurfaceNode(mapIndex, surfaceFile->getNumberOfNodes(), surfaceFile->getStructure(), nodeIndex, rowIndex, columnIndex); cmf->updateScalarColoringForMap(mapIndex, paletteFile); haveData = true; if (rowIndex >= 0) { /* * Get row/column info for node */ rowColumnInformationOut.push_back(cmf->getFileNameNoPath() + " nodeIndex=" + AString::number(nodeIndex) + ", row index= " + AString::number(rowIndex)); } else if (columnIndex >= 0) { /* * Get row/column info for node */ rowColumnInformationOut.push_back(cmf->getFileNameNoPath() + " nodeIndex=" + AString::number(nodeIndex) + ", column index= " + AString::number(columnIndex)); } } } if (haveData) { EventManager::get()->sendEvent(EventSurfaceColoringInvalidate().getPointer()); } return haveData; } /** * Load data for each of the given surface node indices and average the data. * @param brain * Brain for which data is loaded. * @param surfaceFile * Surface File that contains the node (uses its structure). * @param nodeIndices * Indices of the surface nodes. * @return * true if any connectivity loaders are active, else false. */ bool CiftiConnectivityMatrixDataFileManager::loadAverageDataForSurfaceNodes(Brain* brain, const SurfaceFile* surfaceFile, const std::vector& nodeIndices) { std::vector ciftiMatrixFiles; brain->getAllCiftiConnectivityMatrixFiles(ciftiMatrixFiles); PaletteFile* paletteFile = brain->getPaletteFile(); bool haveData = false; for (std::vector::iterator iter = ciftiMatrixFiles.begin(); iter != ciftiMatrixFiles.end(); iter++) { CiftiMappableConnectivityMatrixDataFile* cmf = *iter; if (cmf->isEmpty() == false) { const int32_t mapIndex = 0; cmf->loadMapAverageDataForSurfaceNodes(mapIndex, surfaceFile->getNumberOfNodes(), surfaceFile->getStructure(), nodeIndices); cmf->updateScalarColoringForMap(mapIndex, paletteFile); haveData = true; } } if (haveData) { EventManager::get()->sendEvent(EventSurfaceColoringInvalidate().getPointer()); } return haveData; } /** * Load data for the voxel near the given coordinate. * @param brain * Brain for which data is loaded. * @param xyz * Coordinate of voxel. * @param rowColumnInformationOut * Appends one string for each row/column loaded * @return * true if any connectivity loaders are active, else false. */ bool CiftiConnectivityMatrixDataFileManager::loadDataForVoxelAtCoordinate(Brain* brain, const float xyz[3], std::vector& rowColumnInformationOut) { PaletteFile* paletteFile = brain->getPaletteFile(); std::vector ciftiMatrixFiles; brain->getAllCiftiConnectivityMatrixFiles(ciftiMatrixFiles); bool haveData = false; for (std::vector::iterator iter = ciftiMatrixFiles.begin(); iter != ciftiMatrixFiles.end(); iter++) { CiftiMappableConnectivityMatrixDataFile* cmf = *iter; if (cmf->isEmpty() == false) { const int32_t mapIndex = 0; int64_t rowIndex; int64_t columnIndex; cmf->loadMapDataForVoxelAtCoordinate(mapIndex, xyz, rowIndex, columnIndex); cmf->updateScalarColoringForMap(mapIndex, paletteFile); haveData = true; if (rowIndex >= 0) { /* * Get row/column info for node */ rowColumnInformationOut.push_back(cmf->getFileNameNoPath() + " Voxel XYZ=" + AString::fromNumbers(xyz, 3, ",") + ", row index= " + AString::number(rowIndex)); } else if (columnIndex >= 0) { /* * Get row/column info for node */ rowColumnInformationOut.push_back(cmf->getFileNameNoPath() + " Voxel XYZ=" + AString::fromNumbers(xyz, 3, ",") + ", column index= " + AString::number(columnIndex)); } } } if (haveData) { EventManager::get()->sendEvent(EventSurfaceColoringInvalidate().getPointer()); } return haveData; } /** * Load average data for the given voxel indices. * * @param brain * Brain for which data is loaded. * @param volumeDimensionIJK * Dimensions of the volume. * @param voxelIndices * Indices for averaging of data. * @return * true if any data was loaded, else false. * @throw DataFileException * If an error occurs. */ bool CiftiConnectivityMatrixDataFileManager::loadAverageDataForVoxelIndices(Brain* brain, const int64_t volumeDimensionIJK[3], const std::vector& voxelIndices) { PaletteFile* paletteFile = brain->getPaletteFile(); std::vector ciftiMatrixFiles; brain->getAllCiftiConnectivityMatrixFiles(ciftiMatrixFiles); bool haveData = false; for (std::vector::iterator iter = ciftiMatrixFiles.begin(); iter != ciftiMatrixFiles.end(); iter++) { CiftiMappableConnectivityMatrixDataFile* cmf = *iter; if (cmf->isEmpty() == false) { const int32_t mapIndex = 0; if (cmf->loadMapAverageDataForVoxelIndices(mapIndex, volumeDimensionIJK, voxelIndices)) { } haveData = true; cmf->updateScalarColoringForMap(mapIndex, paletteFile); } } if (haveData) { EventManager::get()->sendEvent(EventSurfaceColoringInvalidate().getPointer()); } return haveData; } /** * @param brain * Brain for containing network files. * * @return True if there are enabled connectivity * files that retrieve data from the network. */ bool CiftiConnectivityMatrixDataFileManager::hasNetworkFiles(Brain* brain) const { std::vector ciftiMatrixFiles; brain->getAllCiftiConnectivityMatrixFiles(ciftiMatrixFiles); for (std::vector::iterator iter = ciftiMatrixFiles.begin(); iter != ciftiMatrixFiles.end(); iter++) { CiftiMappableConnectivityMatrixDataFile* cmdf = *iter; if (cmdf->isEmpty() == false) { if (DataFile::isFileOnNetwork(cmdf->getFileName())) { const int32_t numMaps = cmdf->getNumberOfMaps(); for (int32_t i = 0; i < numMaps; i++) { if (cmdf->isMapDataLoadingEnabled(i)) { return true; } } } } } return false; } workbench-1.1.1/src/Brain/CiftiConnectivityMatrixDataFileManager.h000066400000000000000000000073601255417355300252160ustar00rootroot00000000000000#ifndef __CIFTI_CONNECTIVITY_MATRIX_DATA_FILE_MANAGER_H__ #define __CIFTI_CONNECTIVITY_MATRIX_DATA_FILE_MANAGER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "VoxelIJK.h" namespace caret { class Brain; class CiftiConnectivityMatrixParcelFile; class CiftiMappableConnectivityMatrixDataFile; class SurfaceFile; class CiftiConnectivityMatrixDataFileManager : public CaretObject { public: CiftiConnectivityMatrixDataFileManager(); virtual ~CiftiConnectivityMatrixDataFileManager(); bool loadDataForSurfaceNode(Brain* brain, const SurfaceFile* surfaceFile, const int32_t nodeIndex, std::vector& rowColumnInformationOut); bool loadAverageDataForSurfaceNodes(Brain* brain, const SurfaceFile* surfaceFile, const std::vector& nodeIndices); bool loadDataForVoxelAtCoordinate(Brain* brain, const float xyz[3], std::vector& rowColumnInformationOut); bool loadAverageDataForVoxelIndices(Brain* brain, const int64_t volumeDimensionIJK[3], const std::vector& voxelIndices); bool loadRowOrColumnFromParcelFile(Brain* brain, CiftiConnectivityMatrixParcelFile* ciftiConnMatrixFile, const int32_t rowIndex, const int32_t columnIndex, std::vector& rowColumnInformationOut); bool loadRowOrColumnFromConnectivityMatrixFile(Brain* brain, CiftiMappableConnectivityMatrixDataFile* parcelFile, const int32_t rowIndex, const int32_t columnIndex, std::vector& rowColumnInformationOut); bool hasNetworkFiles(Brain* brain) const; private: CiftiConnectivityMatrixDataFileManager(const CiftiConnectivityMatrixDataFileManager&); CiftiConnectivityMatrixDataFileManager& operator=(const CiftiConnectivityMatrixDataFileManager&); public: // ADD_NEW_METHODS_HERE private: // ADD_NEW_MEMBERS_HERE }; #ifdef __CIFTI_CONNECTIVITY_MATRIX_DATA_FILE_MANAGER_DECLARE__ // #endif // __CIFTI_CONNECTIVITY_MATRIX_DATA_FILE_MANAGER_DECLARE__ } // namespace #endif //__CIFTI_CONNECTIVITY_MATRIX_DATA_FILE_MANAGER_H__ workbench-1.1.1/src/Brain/CiftiFiberTrajectoryManager.cxx000066400000000000000000000165301255417355300234310ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CIFTI_FIBER_TRAJECTORY_MANAGER_DECLARE__ #include "CiftiFiberTrajectoryManager.h" #undef __CIFTI_FIBER_TRAJECTORY_MANAGER_DECLARE__ #include "Brain.h" #include "CaretAssert.h" #include "CiftiFiberOrientationFile.h" #include "CiftiFiberTrajectoryFile.h" #include "SceneAttributes.h" #include "SceneClass.h" #include "SurfaceFile.h" using namespace caret; /** * \class caret::CiftiFiberTrajectoryManager * \brief Manages loading of trajectory data. * \ingroup Brain */ /** * Constructor. */ CiftiFiberTrajectoryManager::CiftiFiberTrajectoryManager() : CaretObject() { } /** * Destructor. */ CiftiFiberTrajectoryManager::~CiftiFiberTrajectoryManager() { } /** * Load data for the given surface node index. * * @param surfaceFile * Surface File that contains the node (uses its structure). * @param nodeIndex * Index of the surface node. * @return * true if any data was loaded, else false. */ bool CiftiFiberTrajectoryManager::loadDataForSurfaceNode(Brain* brain, const SurfaceFile* surfaceFile, const int32_t nodeIndex, std::vector& rowColumnInformationOut) { bool dataWasLoaded = false; /* * Load fiber trajectory data */ const int32_t numTrajFiles = brain->getNumberOfConnectivityFiberTrajectoryFiles(); for (int32_t iTrajFileIndex = 0; iTrajFileIndex < numTrajFiles; iTrajFileIndex++) { CiftiFiberTrajectoryFile* trajFile = brain->getConnectivityFiberTrajectoryFile(iTrajFileIndex); const int64_t rowIndex = trajFile->loadDataForSurfaceNode(surfaceFile->getStructure(), surfaceFile->getNumberOfNodes(), nodeIndex); if (rowIndex >= 0) { /* * Get row/column info for node */ rowColumnInformationOut.push_back(trajFile->getFileNameNoPath() + " nodeIndex=" + AString::number(nodeIndex) + ", row index= " + AString::number(rowIndex)); dataWasLoaded = true; } } return dataWasLoaded; } /** * Load data for the given surface node index. * * @param surfaceFile * Surface File that contains the node (uses its structure). * @param nodeIndex * Index of the surface node. * @return * true if any data was loaded, else false. */ bool CiftiFiberTrajectoryManager::loadDataAverageForSurfaceNodes(Brain* brain, const SurfaceFile* surfaceFile, const std::vector& nodeIndices) { bool dataWasLoaded = false; AString errorMessage; /* * Load fiber trajectory data */ const int32_t numTrajFiles = brain->getNumberOfConnectivityFiberTrajectoryFiles(); for (int32_t iTrajFileIndex = 0; iTrajFileIndex < numTrajFiles; iTrajFileIndex++) { CiftiFiberTrajectoryFile* trajFile = brain->getConnectivityFiberTrajectoryFile(iTrajFileIndex); try { trajFile->loadDataAverageForSurfaceNodes(surfaceFile->getStructure(), surfaceFile->getNumberOfNodes(), nodeIndices); dataWasLoaded = true; } catch (const DataFileException& dfe) { errorMessage.appendWithNewLine(dfe.whatString()); } } if (errorMessage.isEmpty() == false) { throw DataFileException(errorMessage); } return dataWasLoaded; } /** * Load data for the voxel near the given coordinate. * @param xyz * Coordinate of voxel. * @param rowColumnInformationOut * Appends one string for each row/column loaded * @return * true if any connectivity loaders are active, else false. */ bool CiftiFiberTrajectoryManager::loadDataForVoxelAtCoordinate(Brain* brain, const float xyz[3], std::vector& rowColumnInformationOut) { std::vector ciftiTrajFiles; brain->getConnectivityFiberTrajectoryFiles(ciftiTrajFiles); bool haveData = false; for (std::vector::iterator iter = ciftiTrajFiles.begin(); iter != ciftiTrajFiles.end(); iter++) { CiftiFiberTrajectoryFile* trajFile = *iter; if (trajFile->isEmpty() == false) { const int64_t rowIndex = trajFile->loadMapDataForVoxelAtCoordinate(xyz); if (rowIndex >= 0) { /* * Get row/column info for node */ rowColumnInformationOut.push_back(trajFile->getFileNameNoPath() + " Voxel XYZ=" + AString::fromNumbers(xyz, 3, ",") + ", row index= " + AString::number(rowIndex)); haveData = true; } } } return haveData; } /** * Load average data for the given voxel indices. * * @param volumeDimensionIJK * Dimensions of the volume. * @param voxelIndices * Indices for averaging of data. * @return * true if any data was loaded, else false. * @throw DataFileException * If an error occurs. */ bool CiftiFiberTrajectoryManager::loadAverageDataForVoxelIndices(Brain* brain, const int64_t volumeDimensionIJK[3], const std::vector& voxelIndices) { std::vector ciftiTrajFiles; brain->getConnectivityFiberTrajectoryFiles(ciftiTrajFiles); bool haveData = false; for (std::vector::iterator iter = ciftiTrajFiles.begin(); iter != ciftiTrajFiles.end(); iter++) { CiftiFiberTrajectoryFile* trajFile = *iter; if (trajFile->isEmpty() == false) { trajFile->loadMapAverageDataForVoxelIndices(volumeDimensionIJK, voxelIndices); haveData = true; } } return haveData; } workbench-1.1.1/src/Brain/CiftiFiberTrajectoryManager.h000066400000000000000000000051401255417355300230510ustar00rootroot00000000000000#ifndef __CIFTI_FIBER_TRAJECTORY_MANAGER_H__ #define __CIFTI_FIBER_TRAJECTORY_MANAGER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "VoxelIJK.h" namespace caret { class Brain; class SurfaceFile; class CiftiFiberTrajectoryManager : public CaretObject { public: CiftiFiberTrajectoryManager(); virtual ~CiftiFiberTrajectoryManager(); bool loadDataForSurfaceNode(Brain* brain, const SurfaceFile* surfaceFile, const int32_t nodeIndex, std::vector& rowColumnInformationOut); bool loadDataAverageForSurfaceNodes(Brain* brain, const SurfaceFile* surfaceFile, const std::vector& nodeIndices); bool loadDataForVoxelAtCoordinate(Brain* brain, const float xyz[3], std::vector& rowColumnInformationOut); bool loadAverageDataForVoxelIndices(Brain* brain, const int64_t volumeDimensionIJK[3], const std::vector& voxelIndices); // ADD_NEW_METHODS_HERE private: CiftiFiberTrajectoryManager(const CiftiFiberTrajectoryManager&); CiftiFiberTrajectoryManager& operator=(const CiftiFiberTrajectoryManager&); // ADD_NEW_MEMBERS_HERE }; #ifdef __CIFTI_FIBER_TRAJECTORY_MANAGER_DECLARE__ // #endif // __CIFTI_FIBER_TRAJECTORY_MANAGER_DECLARE__ } // namespace #endif //__CIFTI_FIBER_TRAJECTORY_MANAGER_H__ workbench-1.1.1/src/Brain/ClippingPlaneGroup.cxx000066400000000000000000000556121255417355300216270ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CLIPPING_PLANE_GROUP_DECLARE__ #include "ClippingPlaneGroup.h" #undef __CLIPPING_PLANE_GROUP_DECLARE__ #include "CaretAssert.h" #include "Plane.h" #include "SceneClass.h" #include "SceneClassArray.h" #include "SceneClassAssistant.h" using namespace caret; /** * \class caret::ClippingPlaneGroup * \brief A group of clipping plane for clipping to a rectangular region * \ingroup Brain */ /** * Constructor. */ ClippingPlaneGroup::ClippingPlaneGroup() : CaretObject() { invalidateActiveClippingPlainEquations(); resetToDefaultValues(); m_sceneAssistant = new SceneClassAssistant(); m_sceneAssistant->add("m_displayClippingBoxStatus", &m_displayClippingBoxStatus); m_sceneAssistant->addArray("m_translation", m_translation, 3, 0.0); m_sceneAssistant->addArray("m_thickness", m_thickness, 3, 20.0); m_sceneAssistant->add("m_xAxisSelectionStatus", &m_xAxisSelectionStatus); m_sceneAssistant->add("m_yAxisSelectionStatus", &m_yAxisSelectionStatus); m_sceneAssistant->add("m_zAxisSelectionStatus", &m_zAxisSelectionStatus); m_sceneAssistant->add("m_surfaceSelectionStatus", &m_surfaceSelectionStatus); m_sceneAssistant->add("m_volumeSelectionStatus", &m_volumeSelectionStatus); m_sceneAssistant->add("m_featuresSelectionStatus", &m_featuresSelectionStatus); } /** * Destructor. */ ClippingPlaneGroup::~ClippingPlaneGroup() { delete m_sceneAssistant; invalidateActiveClippingPlainEquations(); } /** * Copy constructor. * @param obj * Object that is copied. */ ClippingPlaneGroup::ClippingPlaneGroup(const ClippingPlaneGroup& obj) : CaretObject(obj), SceneableInterface() { this->copyHelperClippingPlaneGroup(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ ClippingPlaneGroup& ClippingPlaneGroup::operator=(const ClippingPlaneGroup& obj) { if (this != &obj) { CaretObject::operator=(obj); this->copyHelperClippingPlaneGroup(obj); } return *this; } /** * Helps with copying an object of this type. * @param obj * Object that is copied. */ void ClippingPlaneGroup::copyHelperClippingPlaneGroup(const ClippingPlaneGroup& obj) { for (int32_t i = 0; i < 3; i++) { m_translation[i] = obj.m_translation[i]; m_thickness[i] = obj.m_thickness[i]; } m_rotationMatrix = obj.m_rotationMatrix; m_xAxisSelectionStatus = obj.m_xAxisSelectionStatus; m_yAxisSelectionStatus = obj.m_yAxisSelectionStatus; m_zAxisSelectionStatus = obj.m_zAxisSelectionStatus; m_surfaceSelectionStatus = obj.m_surfaceSelectionStatus; m_volumeSelectionStatus = obj.m_volumeSelectionStatus; m_featuresSelectionStatus = obj.m_featuresSelectionStatus; m_displayClippingBoxStatus = obj.m_displayClippingBoxStatus; invalidateActiveClippingPlainEquations(); } /** * Reset the transformation. */ void ClippingPlaneGroup::resetTransformation() { for (int32_t i = 0; i < 3; i++) { m_translation[i] = 0.0; } m_thickness[0] = 180.0; m_thickness[1] = 250.0; m_thickness[2] = 220.0; m_rotationMatrix.identity(); invalidateActiveClippingPlainEquations(); } /** * Reset member values. */ void ClippingPlaneGroup::resetToDefaultValues() { resetTransformation(); m_displayClippingBoxStatus = false; m_xAxisSelectionStatus = false; m_yAxisSelectionStatus = false; m_zAxisSelectionStatus = false; m_surfaceSelectionStatus = true; m_volumeSelectionStatus = true; m_featuresSelectionStatus = true; invalidateActiveClippingPlainEquations(); } /** * @return The X-coordinate for the structure. * * @param structure * The structure. Note that right and left hemispheres are mirror flipped. */ float ClippingPlaneGroup::getXCoordinateForStructure(const StructureEnum::Enum structure) const { float x = m_translation[0]; if (StructureEnum::isLeft(structure)) { x = -x; } return x; } /** * Create the plane equation for the given plane identifier. * * @param planeIdentifier * Identifies plane that is created. * @param structure * The structure. Note that right and left hemispheres are mirror flipped. */ Plane* ClippingPlaneGroup::createClippingPlane(const PlaneIdentifier planeIdentifier, const StructureEnum::Enum structure) const { float normalVector[3] = { 0.0, 0.0, 0.0 }; float pointOnPlane[3] = { 0.0, 0.0, 0.0 }; /* * Note: the planes form a rectangular cuboid and we want to * clip what is OUTSIDE this rectangular cuboid. */ switch (planeIdentifier) { case PLANE_MINIMUM_X: /* * X Minimum */ normalVector[0] = 1.0; pointOnPlane[0] = getXCoordinateForStructure(structure) - (m_thickness[0] / 2.0); break; case PLANE_MAXIMUM_X: /* * X Maximum */ normalVector[0] = -1.0; pointOnPlane[0] = getXCoordinateForStructure(structure) + (m_thickness[0] / 2.0); break; case PLANE_MINIMUM_Y: /* * Y Minimum */ normalVector[1] = 1.0; pointOnPlane[1] = m_translation[1] - (m_thickness[1] / 2.0); break; case PLANE_MAXIMUM_Y: /* * Y Maximum */ normalVector[1] = -1.0; pointOnPlane[1] = m_translation[1] + (m_thickness[1] / 2.0); break; case PLANE_MINIMUM_Z: /* * Z Minimum */ normalVector[2] = 1.0; pointOnPlane[2] = m_translation[2] - (m_thickness[2] / 2.0); break; case PLANE_MAXIMUM_Z: /* * Z Maximum */ normalVector[2] = -1.0; pointOnPlane[2] = m_translation[2] + (m_thickness[2] / 2.0); break; default: CaretAssert(0); } Matrix4x4 rotMat = getRotationMatrixForStructure(structure); rotMat.multiplyPoint3(normalVector); rotMat.multiplyPoint3(pointOnPlane); Plane* plane = new Plane(normalVector, pointOnPlane); return plane; } /** * @return Planes representing the active clipping planes for the given * structure. * * @param structure * The structure. Note that right and left hemispheres are mirror flipped. */ std::vector ClippingPlaneGroup::getActiveClippingPlanesForStructure(const StructureEnum::Enum structure) const { updateActiveClippingPlainEquations(); std::vector planes; if (StructureEnum::isRight(structure)) { planes.insert(planes.end(), m_rightStructureActiveClippingPlanes.begin(), m_rightStructureActiveClippingPlanes.end()); } else { planes.insert(planes.end(), m_activeClippingPlanes.begin(), m_activeClippingPlanes.end()); } return planes; } /** * @return The rotation matrix. * * @param structure * Structure for the rotation matrix. A 'right' structure has rotations * 'mirror flipped'. */ Matrix4x4 ClippingPlaneGroup::getRotationMatrixForStructure(const StructureEnum::Enum structure) const { if (StructureEnum::isRight(structure)) { double rotationX, rotationY, rotationZ; m_rotationMatrix.getRotation(rotationX, rotationY, rotationZ); const int flipMode = 2; switch (flipMode) { case 1: rotationY = 180.0 - rotationY; rotationZ = 180.0 - rotationZ; break; case 2: rotationY = -rotationY; rotationZ = -rotationZ; break; case 3: rotationY = 180.0 - rotationY; rotationZ = -rotationZ; break; case 4: rotationY = - rotationY; rotationZ = 180.0 -rotationZ; break; } Matrix4x4 mat; mat.setRotation(rotationX, rotationY, rotationZ); return mat; // const double rotationFlippedY = 180.0 - rotationY; // const double rotationFlippedZ = 180.0 - rotationZ; // Matrix4x4 mat; // mat.setRotation(rotationX, //rotationFlippedX, // rotationFlippedY, // rotationFlippedZ); // return mat; } return m_rotationMatrix; } /** * Replace the rotation matrix. * * @param rotationMatrix * New rotation matrix. */ void ClippingPlaneGroup::setRotationMatrix(const Matrix4x4& rotationMatrix) { m_rotationMatrix = rotationMatrix; invalidateActiveClippingPlainEquations(); } /** * Get the rotation matrix using the given angles. * * @param rotationAngles * The X, Y, and Z rotation angles. */ void ClippingPlaneGroup::getRotationAngles(float rotationAngles[3]) const { double x, y, z; m_rotationMatrix.getRotation(x, y, z); rotationAngles[0] = x; rotationAngles[1] = y; rotationAngles[2] = z; } /** * Set the rotation matrix using the given angles. * * @param rotationAngles * The X, Y, and Z rotation angles. */ void ClippingPlaneGroup::setRotationAngles(const float rotationAngles[3]) { m_rotationMatrix.setRotation(rotationAngles[0], rotationAngles[1], rotationAngles[2]); invalidateActiveClippingPlainEquations(); } /** * Get the thickness values * * @param thickness * The thickness values. */ void ClippingPlaneGroup::getThickness(float thickness[3]) const { thickness[0] = m_thickness[0]; thickness[1] = m_thickness[1]; thickness[2] = m_thickness[2]; } /** * Set the translation values. * * @param translation * The translation values. */ void ClippingPlaneGroup::setTranslation(const float translation[3]) { m_translation[0] = translation[0]; m_translation[1] = translation[1]; m_translation[2] = translation[2]; invalidateActiveClippingPlainEquations(); } /** * Get the translation values * * @param translation * The translation values. */ void ClippingPlaneGroup::getTranslation(float translation[3]) const { translation[0] = m_translation[0]; translation[1] = m_translation[1]; translation[2] = m_translation[2]; } /** * Get the translation values for the given structure. * * @param structure * The structure. Note that right and left hemispheres are mirror flipped. * @param translation * The translation values. */ void ClippingPlaneGroup::getTranslationForStructure(const StructureEnum::Enum structure, float translation[3]) const { getTranslation(translation); translation[0] = getXCoordinateForStructure(structure); } /** * Set the thickness values. * * @param thickness * The thickness values. */ void ClippingPlaneGroup::setThickness(const float thickness[3]) { m_thickness[0] = thickness[0]; m_thickness[1] = thickness[1]; m_thickness[2] = thickness[2]; invalidateActiveClippingPlainEquations(); } /** * @return Is surface selected for clipping? */ bool ClippingPlaneGroup::isSurfaceSelected() const { return m_surfaceSelectionStatus; } /** * Set surface selected * * @param selected * New status. */ void ClippingPlaneGroup::setSurfaceSelected(const bool selected) { m_surfaceSelectionStatus = selected; invalidateActiveClippingPlainEquations(); } /** * @return Is volume selected for clipping? */ bool ClippingPlaneGroup::isVolumeSelected() const { return m_volumeSelectionStatus; } /** * Set volume selected * * @param selected * New status. */ void ClippingPlaneGroup::setVolumeSelected(const bool selected) { m_volumeSelectionStatus = selected; invalidateActiveClippingPlainEquations(); } /** * @return Is features selected for clipping? */ bool ClippingPlaneGroup::isFeaturesSelected() const { return m_featuresSelectionStatus; } /** * Set display clipping box selected * * @param selected * New status. */ void ClippingPlaneGroup::setDisplayClippingBoxSelected(const bool selected) { m_displayClippingBoxStatus = selected; invalidateActiveClippingPlainEquations(); } /** * @return Is display clipping box selected? */ bool ClippingPlaneGroup::isDisplayClippingBoxSelected() const { return m_displayClippingBoxStatus; } /** * @return Is features and any one or more axes selected for clipping? */ bool ClippingPlaneGroup::isFeaturesAndAnyAxisSelected() const { if (m_featuresSelectionStatus) { if (m_xAxisSelectionStatus || m_yAxisSelectionStatus || m_zAxisSelectionStatus) { return true; } } return false; } /** * Set features selected * * @param selected * New status. */ void ClippingPlaneGroup::setFeaturesSelected(const bool selected) { m_featuresSelectionStatus = selected; } /** * @return Is the X clipping axis selected? */ bool ClippingPlaneGroup::isXAxisSelected() const { return m_xAxisSelectionStatus; } /** * @return Is the Y clipping axis selected? */ bool ClippingPlaneGroup::isYAxisSelected() const { return m_yAxisSelectionStatus; } /** * @return Is the Z clipping axis selected? */ bool ClippingPlaneGroup::isZAxisSelected() const { return m_zAxisSelectionStatus; } /** * Set the selection status for the X-axis. * * @param xAxisSelected * New selection status for the X-axis. */ void ClippingPlaneGroup::setXAxisSelected(const bool xAxisSelected) { m_xAxisSelectionStatus = xAxisSelected; invalidateActiveClippingPlainEquations(); } /** * Set the selection status for the Y-axis. * * @param yAxisSelected * New selection status for the Y-axis. */ void ClippingPlaneGroup::setYAxisSelected(const bool yAxisSelected) { m_yAxisSelectionStatus = yAxisSelected; invalidateActiveClippingPlainEquations(); } /** * Set the selection status for the Z-axis. * * @param zAxisSelected * New selection status for the Z-axis. */ void ClippingPlaneGroup::setZAxisSelected(const bool zAxisSelected) { m_zAxisSelectionStatus = zAxisSelected; invalidateActiveClippingPlainEquations(); } /** * Is the coordinate inside the clipping planes? * * If a clipping plane for an axis is off, the coordinate is considered * to be inside the clipping plane. * * @param structure * The structure. Note that right and left hemispheres are mirror flipped. * @param xyz * The coordinate. * * @return * True if inside the clipping planes, else false. */ bool ClippingPlaneGroup::isCoordinateInsideClippingPlanesForStructure(const StructureEnum::Enum structure, const float xyz[3]) const { updateActiveClippingPlainEquations(); if (StructureEnum::isRight(structure)) { for (std::vector::iterator iter = m_rightStructureActiveClippingPlanes.begin(); iter != m_rightStructureActiveClippingPlanes.end(); iter++) { const Plane* plane = *iter; if (plane->signedDistanceToPlane(xyz) < 0.0) { return false; } } return true; } for (std::vector::iterator iter = m_activeClippingPlanes.begin(); iter != m_activeClippingPlanes.end(); iter++) { const Plane* plane = *iter; if (plane->signedDistanceToPlane(xyz) < 0.0) { return false; } } // USE THE CLIPPING PLANES EQUATIONS !!!! // if (m_xAxisSelectionStatus) { // const float x = getXCoordinateForStructure(structure); // const float minX = x - (m_thickness[0] / 2.0); // const float maxX = x + (m_thickness[0] / 2.0); // if (xyz[0] < minX) return false; // if (xyz[0] > maxX) return false; // } // // if (m_yAxisSelectionStatus) { // const float minY = m_translation[1] - (m_thickness[1] / 2.0); // const float maxY = m_translation[1] + (m_thickness[1] / 2.0); // if (xyz[1] < minY) return false; // if (xyz[1] > maxY) return false; // } // // if (m_zAxisSelectionStatus) { // const float minZ = m_translation[2] - (m_thickness[2] / 2.0); // const float maxZ = m_translation[2] + (m_thickness[2] / 2.0); // if (xyz[2] < minZ) return false; // if (xyz[2] > maxZ) return false; // } return true; } /** * Get a description of this object's content. * @return String describing this object's content. */ AString ClippingPlaneGroup::toString() const { return "ClippingPlaneGroup"; } /** * Update the active clipping planes. */ void ClippingPlaneGroup::updateActiveClippingPlainEquations() const { if (m_activeClippingPlanesValid) { return; } if (m_xAxisSelectionStatus) { m_activeClippingPlanes.push_back(createClippingPlane(PLANE_MINIMUM_X, StructureEnum::CORTEX_LEFT)); m_activeClippingPlanes.push_back(createClippingPlane(PLANE_MAXIMUM_X, StructureEnum::CORTEX_LEFT)); m_rightStructureActiveClippingPlanes.push_back(createClippingPlane(PLANE_MINIMUM_X, StructureEnum::CORTEX_RIGHT)); m_rightStructureActiveClippingPlanes.push_back(createClippingPlane(PLANE_MAXIMUM_X, StructureEnum::CORTEX_RIGHT)); } if (m_yAxisSelectionStatus) { m_activeClippingPlanes.push_back(createClippingPlane(PLANE_MINIMUM_Y, StructureEnum::CORTEX_LEFT)); m_activeClippingPlanes.push_back(createClippingPlane(PLANE_MAXIMUM_Y, StructureEnum::CORTEX_LEFT)); m_rightStructureActiveClippingPlanes.push_back(createClippingPlane(PLANE_MINIMUM_Y, StructureEnum::CORTEX_RIGHT)); m_rightStructureActiveClippingPlanes.push_back(createClippingPlane(PLANE_MAXIMUM_Y, StructureEnum::CORTEX_RIGHT)); } if (m_zAxisSelectionStatus) { m_activeClippingPlanes.push_back(createClippingPlane(PLANE_MINIMUM_Z, StructureEnum::CORTEX_LEFT)); m_activeClippingPlanes.push_back(createClippingPlane(PLANE_MAXIMUM_Z, StructureEnum::CORTEX_LEFT)); m_rightStructureActiveClippingPlanes.push_back(createClippingPlane(PLANE_MINIMUM_Z, StructureEnum::CORTEX_RIGHT)); m_rightStructureActiveClippingPlanes.push_back(createClippingPlane(PLANE_MAXIMUM_Z, StructureEnum::CORTEX_RIGHT)); } m_activeClippingPlanesValid = true; } /** * Invalidate and remove all of the active clipping planes. */ void ClippingPlaneGroup::invalidateActiveClippingPlainEquations() { m_activeClippingPlanesValid = false; for (std::vector::iterator iter = m_activeClippingPlanes.begin(); iter != m_activeClippingPlanes.end(); iter++) { delete *iter; } m_activeClippingPlanes.clear(); for (std::vector::iterator iter = m_rightStructureActiveClippingPlanes.begin(); iter != m_rightStructureActiveClippingPlanes.end(); iter++) { delete *iter; } m_rightStructureActiveClippingPlanes.clear(); } /** * Save information specific to this type of model to the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @param instanceName * Name of instance in the scene. */ SceneClass* ClippingPlaneGroup::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "ClippingPlaneGroup", 1); m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); float m[4][4]; m_rotationMatrix.getMatrix(m); sceneClass->addFloatArray("m_rotationMatrix", &m[0][0], 16); // Uncomment if sub-classes must save to scene //saveSubClassDataToScene(sceneAttributes, // sceneClass); return sceneClass; } /** * Restore information specific to the type of model from the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass from which model specific information is obtained. */ void ClippingPlaneGroup::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { resetToDefaultValues(); if (sceneClass == NULL) { return; } m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); float m[4][4]; const int32_t numElem = sceneClass->getFloatArrayValue("m_rotationMatrix", &m[0][0], 16); if (numElem == 16) { m_rotationMatrix.setMatrix(m); } else { m_rotationMatrix.identity(); } //Uncomment if sub-classes must restore from scene //restoreSubClassDataFromScene(sceneAttributes, // sceneClass); } workbench-1.1.1/src/Brain/ClippingPlaneGroup.h000066400000000000000000000141651255417355300212520ustar00rootroot00000000000000#ifndef __CLIPPING_PLANE_GROUP_H__ #define __CLIPPING_PLANE_GROUP_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "Matrix4x4.h" #include "SceneableInterface.h" #include "StructureEnum.h" namespace caret { class Plane; class SceneClassAssistant; class ClippingPlaneGroup : public CaretObject, public SceneableInterface { public: ClippingPlaneGroup(); virtual ~ClippingPlaneGroup(); ClippingPlaneGroup(const ClippingPlaneGroup& obj); ClippingPlaneGroup& operator=(const ClippingPlaneGroup& obj); void resetTransformation(); void resetToDefaultValues(); std::vector getActiveClippingPlanesForStructure(const StructureEnum::Enum structure) const; void getTranslation(float translation[3]) const; void setTranslation(const float translation[3]); void getTranslationForStructure(const StructureEnum::Enum structure, float translation[3]) const; Matrix4x4 getRotationMatrixForStructure(const StructureEnum::Enum structure) const; void setRotationMatrix(const Matrix4x4& rotationMatrix); void getRotationAngles(float rotationAngles[3]) const; void setRotationAngles(const float rotationAngles[3]); void setRotation(const float rotation[4][4]); void getThickness(float thickness[3]) const; void setThickness(const float thickness[3]); bool isXAxisSelected() const; bool isYAxisSelected() const; bool isZAxisSelected() const; void setXAxisSelected(const bool xAxisSelected); void setYAxisSelected(const bool yAxisSelected); void setZAxisSelected(const bool zAxisSelected); bool isDisplayClippingBoxSelected() const; void setDisplayClippingBoxSelected(const bool selected); bool isSurfaceSelected() const; void setSurfaceSelected(const bool selected); bool isVolumeSelected() const; void setVolumeSelected(const bool selected); bool isFeaturesSelected() const; bool isFeaturesAndAnyAxisSelected() const; void setFeaturesSelected(const bool selected); bool isCoordinateInsideClippingPlanesForStructure(const StructureEnum::Enum structure, const float xyz[3]) const; // ADD_NEW_METHODS_HERE virtual AString toString() const; virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); // If there will be sub-classes of this class that need to save // and restore data from scenes, these pure virtual methods can // be uncommented to force their implemetation by sub-classes. // protected: // virtual void saveSubClassDataToScene(const SceneAttributes* sceneAttributes, // SceneClass* sceneClass) = 0; // // virtual void restoreSubClassDataFromScene(const SceneAttributes* sceneAttributes, // const SceneClass* sceneClass) = 0; private: enum PlaneIdentifier { PLANE_MINIMUM_X, PLANE_MAXIMUM_X, PLANE_MINIMUM_Y, PLANE_MAXIMUM_Y, PLANE_MINIMUM_Z, PLANE_MAXIMUM_Z }; void copyHelperClippingPlaneGroup(const ClippingPlaneGroup& obj); Plane* createClippingPlane(const PlaneIdentifier planeIdentifier, const StructureEnum::Enum structure) const; void updateActiveClippingPlainEquations() const; void invalidateActiveClippingPlainEquations(); /** * For all everthing EXCEPT right structures */ mutable std::vector m_activeClippingPlanes; /** * Mirror flipped for RIGHT structure */ mutable std::vector m_rightStructureActiveClippingPlanes; mutable bool m_activeClippingPlanesValid; SceneClassAssistant* m_sceneAssistant; float getXCoordinateForStructure(const StructureEnum::Enum structure) const; float m_translation[3]; Matrix4x4 m_rotationMatrix; float m_thickness[3]; bool m_xAxisSelectionStatus; bool m_yAxisSelectionStatus; bool m_zAxisSelectionStatus; bool m_surfaceSelectionStatus; bool m_volumeSelectionStatus; bool m_featuresSelectionStatus; bool m_displayClippingBoxStatus; // ADD_NEW_MEMBERS_HERE }; #ifdef __CLIPPING_PLANE_GROUP_DECLARE__ // #endif // __CLIPPING_PLANE_GROUP_DECLARE__ } // namespace #endif //__CLIPPING_PLANE_GROUP_H__ workbench-1.1.1/src/Brain/DisplayProperties.cxx000066400000000000000000000030321255417355300215340ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __DISPLAY_PROPERTIES_DECLARE__ #include "DisplayProperties.h" #undef __DISPLAY_PROPERTIES_DECLARE__ #include "SceneClassAssistant.h" using namespace caret; /** * \class DisplayProperties * \brief Base class for all display properties. * * Base class for all display properties. */ /** * Constructor. */ DisplayProperties::DisplayProperties() : CaretObject() { m_sceneAssistant = new SceneClassAssistant(); } /** * Destructor. */ DisplayProperties::~DisplayProperties() { delete m_sceneAssistant; } /** * Get a description of this object's content. * @return String describing this object's content. */ AString DisplayProperties::toString() const { return "DisplayProperties"; } workbench-1.1.1/src/Brain/DisplayProperties.h000066400000000000000000000051421255417355300211650ustar00rootroot00000000000000#ifndef __DISPLAY_PROPERTIES__H_ #define __DISPLAY_PROPERTIES__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "SceneableInterface.h" namespace caret { class SceneClassAssistant; class DisplayProperties : public CaretObject, public SceneableInterface { protected: DisplayProperties(); public: virtual ~DisplayProperties(); /** * Reset all settings to their defaults * and remove any data. */ virtual void reset() = 0; /** * Update due to changes in data. */ virtual void update() = 0; /** * Copy the display properties. * * @param sourceTabIndex * Index of tab from which properties are copied. * @param targetTabIndex * Index of tab to which properties are copied. */ virtual void copyDisplayProperties(const int32_t sourceTabIndex, const int32_t targetTabIndex) = 0; virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) = 0; virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) = 0; private: DisplayProperties(const DisplayProperties&); DisplayProperties& operator=(const DisplayProperties&); public: virtual AString toString() const; protected: SceneClassAssistant* m_sceneAssistant; }; #ifdef __DISPLAY_PROPERTIES_DECLARE__ // #endif // __DISPLAY_PROPERTIES_DECLARE__ } // namespace #endif //__DISPLAY_PROPERTIES__H_ workbench-1.1.1/src/Brain/DisplayPropertiesBorders.cxx000066400000000000000000000671421255417355300230710ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __DISPLAY_PROPERTIES_BORDERS_DECLARE__ #include "DisplayPropertiesBorders.h" #undef __DISPLAY_PROPERTIES_BORDERS_DECLARE__ #include "CaretAssert.h" #include "SceneAttributes.h" #include "SceneClass.h" #include "SceneClassAssistant.h" using namespace caret; /** * \class caret::DisplayPropertiesBorders * \brief Contains display properties for borders. * * Border display properties are available for every tab and also a * few 'display groups'. A number of methods in this class accept * both display group and tab index parameters. When the display * group is set to 'Tab', the tab index is used meaning that the * attribute requeted/sent is for use with a specifc tab. For an * other display group value, the attribute is for a display group * and the tab index is ignored. */ /** * Constructor. */ DisplayPropertiesBorders::DisplayPropertiesBorders() : DisplayProperties() { const float defaultPointSize = 2.0; const float defaultLineSize = 1.0; const BorderDrawingTypeEnum::Enum defaultDrawingType = BorderDrawingTypeEnum::DRAW_AS_POINTS_SPHERES; const FeatureColoringTypeEnum::Enum defaultColoringType = FeatureColoringTypeEnum::FEATURE_COLORING_TYPE_NAME; const float defaultUnstretchedLinesLength = 5.0; const bool defaultUnstretchedLinesSelection = true; for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_displayGroup[i] = DisplayGroupEnum::getDefaultValue(); m_displayStatusInTab[i] = false; m_contralateralDisplayStatusInTab[i] = false; m_lineWidthInTab[i] = defaultLineSize; m_pointSizeInTab[i] = defaultPointSize; m_coloringTypeInTab[i] = defaultColoringType; m_drawingTypeInTab[i] = defaultDrawingType; m_unstretchedLinesLengthInTab[i] = defaultUnstretchedLinesLength; m_unstretchedLinesStatusInTab[i] = defaultUnstretchedLinesSelection; } for (int32_t i = 0; i < DisplayGroupEnum::NUMBER_OF_GROUPS; i++) { m_displayStatusInDisplayGroup[i] = false; m_contralateralDisplayStatusInDisplayGroup[i] = false; m_lineWidthInDisplayGroup[i] = defaultLineSize; m_pointSizeInDisplayGroup[i] = defaultPointSize; m_coloringTypeInDisplayGroup[i] = defaultColoringType; m_drawingTypeInDisplayGroup[i] = defaultDrawingType; m_unstretchedLinesLengthInDisplayGroup[i] = defaultUnstretchedLinesLength; m_unstretchedLinesStatusInDisplayGroup[i] = defaultUnstretchedLinesSelection; } m_sceneAssistant->addTabIndexedEnumeratedTypeArray("m_displayGroup", m_displayGroup); m_sceneAssistant->addTabIndexedBooleanArray("m_displayStatusInTab", m_displayStatusInTab); m_sceneAssistant->addTabIndexedBooleanArray("m_contralateralDisplayStatusInTab", m_contralateralDisplayStatusInTab); m_sceneAssistant->addTabIndexedFloatArray("m_lineWidthInTab", m_lineWidthInTab); m_sceneAssistant->addTabIndexedFloatArray("m_pointSizeInTab", m_pointSizeInTab); m_sceneAssistant->addArray("m_displayStatusInDisplayGroup", m_displayStatusInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, m_displayStatusInDisplayGroup[0]); m_sceneAssistant->addArray("m_contralateralDisplayStatusInDisplayGroup", m_contralateralDisplayStatusInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, m_contralateralDisplayStatusInDisplayGroup[0]); m_sceneAssistant->addArray("m_lineWidthInDisplayGroup", m_lineWidthInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, defaultLineSize); m_sceneAssistant->addArray("m_pointSizeInDisplayGroup", m_pointSizeInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, defaultPointSize); m_sceneAssistant->addArray("m_coloringTypeInDisplayGroup", m_coloringTypeInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, defaultColoringType); m_sceneAssistant->addTabIndexedEnumeratedTypeArray("m_coloringTypeInTab", m_coloringTypeInTab); m_sceneAssistant->addArray("m_drawingTypeInDisplayGroup", m_drawingTypeInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, BorderDrawingTypeEnum::DRAW_AS_POINTS_SPHERES); m_sceneAssistant->addTabIndexedEnumeratedTypeArray("m_drawingTypeInTab", m_drawingTypeInTab); m_sceneAssistant->addTabIndexedFloatArray("m_unstretchedLinesLengthInTab", m_unstretchedLinesLengthInTab); m_sceneAssistant->addTabIndexedBooleanArray("m_unstretchedLinesStatusInTab", m_unstretchedLinesStatusInTab); m_sceneAssistant->addArray("m_unstretchedLinesLengthInDisplayGroup", m_unstretchedLinesLengthInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, defaultUnstretchedLinesLength); m_sceneAssistant->addArray("m_unstretchedLinesStatusInDisplayGroup", m_unstretchedLinesStatusInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, defaultUnstretchedLinesSelection); } /** * Destructor. */ DisplayPropertiesBorders::~DisplayPropertiesBorders() { } /** * Reset all settings to their defaults * and remove any data. */ void DisplayPropertiesBorders::reset() { // for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { // this->displayStatus[i] = false; // this->contralateralDisplayStatus[i] = false; // this->displayGroup[i] = DisplayGroupEnum::DISPLAY_ALL_WINDOWS; // } } /** * Update due to changes in data. */ void DisplayPropertiesBorders::update() { } /** * Copy the border display properties from one tab to another. * @param sourceTabIndex * Index of tab from which properties are copied. * @param targetTabIndex * Index of tab to which properties are copied. */ void DisplayPropertiesBorders::copyDisplayProperties(const int32_t sourceTabIndex, const int32_t targetTabIndex) { const DisplayGroupEnum::Enum displayGroup = this->getDisplayGroupForTab(sourceTabIndex); this->setDisplayGroupForTab(targetTabIndex, displayGroup); this->m_contralateralDisplayStatusInTab[targetTabIndex] = this->m_contralateralDisplayStatusInTab[sourceTabIndex]; this->m_displayStatusInTab[targetTabIndex] = this->m_displayStatusInTab[sourceTabIndex]; this->m_drawingTypeInTab[targetTabIndex] = this->m_drawingTypeInTab[sourceTabIndex]; this->m_lineWidthInTab[targetTabIndex] = this->m_lineWidthInTab[sourceTabIndex]; this->m_pointSizeInTab[targetTabIndex] = this->m_pointSizeInTab[sourceTabIndex]; this->m_unstretchedLinesLengthInTab[targetTabIndex] = this->m_unstretchedLinesLengthInTab[sourceTabIndex]; } /** * @return Display status of borders. * @param displayGroup * The display group. * @param tabIndex * Index of browser tab. */ bool DisplayPropertiesBorders::isDisplayed(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const { CaretAssertArrayIndex(m_displayStatusInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_displayStatusInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_displayStatusInTab[tabIndex]; } return m_displayStatusInDisplayGroup[displayGroup]; } /** * Set the display status for borders for the given display group. * @param displayGroup * The display group. * @param tabIndex * Index of browser tab. * @param displayStatus * New status. */ void DisplayPropertiesBorders::setDisplayed(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const bool displayStatus) { CaretAssertArrayIndex(m_displayStatusInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_displayStatusInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_displayStatusInTab[tabIndex] = displayStatus; } else { m_displayStatusInDisplayGroup[displayGroup] = displayStatus; } } /** * @return Contralateral display status of borders. * @param displayGroup * The display group. * @param tabIndex * Index of browser tab. */ bool DisplayPropertiesBorders::isContralateralDisplayed(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const { CaretAssertArrayIndex(m_contralateralDisplayStatusInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_displayStatusInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_contralateralDisplayStatusInTab[tabIndex]; } return m_contralateralDisplayStatusInDisplayGroup[displayGroup]; } /** * Set the contralateral display status for borders. * @param displayGroup * The display group. * @param tabIndex * Index of browser tab. * @param contralateralDisplayStatus * New status. */ void DisplayPropertiesBorders::setContralateralDisplayed(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const bool contralateralDisplayStatus) { CaretAssertArrayIndex(m_contralateralDisplayStatusInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_displayStatusInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_contralateralDisplayStatusInTab[tabIndex] = contralateralDisplayStatus; } else { m_contralateralDisplayStatusInDisplayGroup[displayGroup] = contralateralDisplayStatus; } } /** * Get the display group for a given browser tab. * @param browserTabIndex * Index of browser tab. */ DisplayGroupEnum::Enum DisplayPropertiesBorders::getDisplayGroupForTab(const int32_t browserTabIndex) const { CaretAssertArrayIndex(this->displayGroup, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, browserTabIndex); return m_displayGroup[browserTabIndex]; } /** * Set the display group for a given browser tab. * @param browserTabIndex * Index of browser tab. * @param displayGroup * New value for display group. */ void DisplayPropertiesBorders::setDisplayGroupForTab(const int32_t browserTabIndex, const DisplayGroupEnum::Enum displayGroup) { CaretAssertArrayIndex(this->displayGroup, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, browserTabIndex); m_displayGroup[browserTabIndex] = displayGroup; } /** * @return The point size. * @param displayGroup * The display group. * @param tabIndex * Index of browser tab. */ float DisplayPropertiesBorders::getPointSize(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const { CaretAssertArrayIndex(m_pointSizeInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_displayStatusInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_pointSizeInTab[tabIndex]; } return m_pointSizeInDisplayGroup[displayGroup]; } /** * Set the point size to the given value. * @param displayGroup * The display group. * @param tabIndex * Index of browser tab. * @param pointSize * New value for point size. */ void DisplayPropertiesBorders::setPointSize(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const float pointSize) { CaretAssertArrayIndex(m_pointSizeInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_displayStatusInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_pointSizeInTab[tabIndex] = pointSize; } else { m_pointSizeInDisplayGroup[displayGroup] = pointSize; } } /** * @return The line width. * @param displayGroup * The display group. * @param tabIndex * Index of browser tab. */ float DisplayPropertiesBorders::getLineWidth(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const { CaretAssertArrayIndex(m_lineWidthInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_displayStatusInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_lineWidthInTab[tabIndex]; } return m_lineWidthInDisplayGroup[displayGroup]; } /** * Set the line width to the given value. * @param displayGroup * The display group. * @param tabIndex * Index of browser tab. * @param lineWidth * New value for line width. */ void DisplayPropertiesBorders::setLineWidth(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const float lineWidth) { CaretAssertArrayIndex(m_lineWidthInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_displayStatusInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_lineWidthInTab[tabIndex] = lineWidth; } else { m_lineWidthInDisplayGroup[displayGroup] = lineWidth; } } /** * @return Is unstretched lines enabled? * * @param displayGroup * The display group. * @param tabIndex * Index of browser tab. */ bool DisplayPropertiesBorders::isUnstretchedLinesEnabled(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const { CaretAssertArrayIndex(m_unstretchedLinesStatusInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_unstretchedLinesStatusInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_unstretchedLinesStatusInTab[tabIndex]; } return m_unstretchedLinesStatusInDisplayGroup[displayGroup]; } /** * Set the unstretched lines status to the given value. * @param displayGroup * The display group. * @param tabIndex * Index of browser tab. * @param unstretchedLinesEnabled * New value for line unstretched lines status. */ void DisplayPropertiesBorders::setUnstretchedLinesEnabled(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const bool unstretchedLinesEnabled) { CaretAssertArrayIndex(m_unstretchedLinesStatusInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_unstretchedLinesStatusInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_unstretchedLinesStatusInTab[tabIndex] = unstretchedLinesEnabled; } else { m_unstretchedLinesStatusInDisplayGroup[displayGroup] = unstretchedLinesEnabled; } } /** * @return Get unstretched lines length * * @param displayGroup * The display group. * @param tabIndex * Index of browser tab. */ float DisplayPropertiesBorders::getUnstretchedLinesLength(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const { CaretAssertArrayIndex(m_unstretchedLinesLengthInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_unstretchedLinesLengthInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_unstretchedLinesLengthInTab[tabIndex]; } return m_unstretchedLinesLengthInDisplayGroup[displayGroup]; } /** * Set the unstretched lines length to the given value. * @param displayGroup * The display group. * @param tabIndex * Index of browser tab. * @param unstretchedLinesLength * New value for unstretched lines length. */ void DisplayPropertiesBorders::setUnstretchedLinesLength(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const float unstretchedLinesLength) { CaretAssertArrayIndex(m_unstretchedLinesLengthInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_unstretchedLinesLengthInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_unstretchedLinesLengthInTab[tabIndex] = unstretchedLinesLength; } else { m_unstretchedLinesLengthInDisplayGroup[displayGroup] = unstretchedLinesLength; } } /** * @return The drawing type. * @param displayGroup * The display group. * @param tabIndex * Index of browser tab. */ BorderDrawingTypeEnum::Enum DisplayPropertiesBorders::getDrawingType(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const { CaretAssertArrayIndex(m_drawingTypeInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_displayStatusInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_drawingTypeInTab[tabIndex]; } return m_drawingTypeInDisplayGroup[displayGroup]; } /** * Set the drawing type to the given value. * @param displayGroup * The display group. * @param tabIndex * Index of browser tab. * @param drawingType * New value for drawing type. */ void DisplayPropertiesBorders::setDrawingType(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const BorderDrawingTypeEnum::Enum drawingType) { CaretAssertArrayIndex(m_drawingTypeInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_displayStatusInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_drawingTypeInTab[tabIndex] = drawingType; } else { m_drawingTypeInDisplayGroup[displayGroup] = drawingType; } } /** * @return The coloring type. * @param displayGroup * Display group. */ FeatureColoringTypeEnum::Enum DisplayPropertiesBorders::getColoringType(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const { CaretAssertArrayIndex(m_coloringTypeInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_coloringTypeInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_coloringTypeInTab[tabIndex]; } return m_coloringTypeInDisplayGroup[displayGroup]; } /** * Set the coloring type. * @param displayGroup * Display group. * @param coloringType * New value for coloring type. */ void DisplayPropertiesBorders::setColoringType(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const FeatureColoringTypeEnum::Enum coloringType) { CaretAssertArrayIndex(m_coloringTypeInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_coloringTypeInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_coloringTypeInTab[tabIndex] = coloringType; } else { m_coloringTypeInDisplayGroup[displayGroup] = coloringType; } } template const std::vector enumArrayToStrings(const ET enumArray[], const std::vector& tabIndices) { std::vector stringVector; const int32_t numTabs = static_cast(tabIndices.size()); for (int32_t i = 0; i < numTabs; i++) { const AString stringValue = ET::nameToString(enumArray[i], false); stringVector.push_back(stringValue); } return stringVector; } template class EnumConvert { public: static std::vector enumArrayToStringsForTabIndices(const ET enumArray[], const std::vector& tabIndices) { std::vector stringVector; const int32_t numTabs = static_cast(tabIndices.size()); for (int32_t i = 0; i < numTabs; i++) { const int32_t tabIndex = tabIndices[i]; const AString stringValue = T::toName(enumArray[tabIndex]); stringVector.push_back(stringValue); } return stringVector; } }; /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ SceneClass* DisplayPropertiesBorders::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "DisplayPropertiesBorders", 1); const std::vector tabIndices = sceneAttributes->getIndicesOfTabsForSavingToScene(); m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); return sceneClass; } /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * SceneClass containing the state that was previously * saved and should be restored. */ void DisplayPropertiesBorders::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); switch (sceneAttributes->getSceneType()) { case SceneTypeEnum::SCENE_TYPE_FULL: break; case SceneTypeEnum::SCENE_TYPE_GENERIC: break; } } workbench-1.1.1/src/Brain/DisplayPropertiesBorders.h000066400000000000000000000156131255417355300225120ustar00rootroot00000000000000#ifndef __DISPLAY_PROPERTIES_BORDERS__H_ #define __DISPLAY_PROPERTIES_BORDERS__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "BrainConstants.h" #include "BorderDrawingTypeEnum.h" #include "DisplayGroupEnum.h" #include "DisplayProperties.h" #include "FeatureColoringTypeEnum.h" namespace caret { class DisplayPropertiesBorders : public DisplayProperties { public: DisplayPropertiesBorders(); virtual ~DisplayPropertiesBorders(); virtual void reset(); virtual void update(); virtual void copyDisplayProperties(const int32_t sourceTabIndex, const int32_t targetTabIndex); bool isDisplayed(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const; void setDisplayed(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const bool displayStatus); bool isContralateralDisplayed(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const; void setContralateralDisplayed(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const bool contralateralDisplayStatus); DisplayGroupEnum::Enum getDisplayGroupForTab(const int32_t browserTabIndex) const; void setDisplayGroupForTab(const int32_t browserTabIndex, const DisplayGroupEnum::Enum displayGroup); float getPointSize(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const; void setPointSize(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const float pointSize); float getLineWidth(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const; void setLineWidth(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const float lineWidth); BorderDrawingTypeEnum::Enum getDrawingType(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const; void setDrawingType(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const BorderDrawingTypeEnum::Enum drawingType); FeatureColoringTypeEnum::Enum getColoringType(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const; void setColoringType(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const FeatureColoringTypeEnum::Enum drawingType); bool isUnstretchedLinesEnabled(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const; void setUnstretchedLinesEnabled(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const bool unstretchedLinesEnabled); float getUnstretchedLinesLength(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const; void setUnstretchedLinesLength(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const float unstretchedLinesLength); virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private: DisplayPropertiesBorders(const DisplayPropertiesBorders&); DisplayPropertiesBorders& operator=(const DisplayPropertiesBorders&); DisplayGroupEnum::Enum m_displayGroup[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; bool m_displayStatusInDisplayGroup[DisplayGroupEnum::NUMBER_OF_GROUPS]; bool m_displayStatusInTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; bool m_contralateralDisplayStatusInDisplayGroup[DisplayGroupEnum::NUMBER_OF_GROUPS]; bool m_contralateralDisplayStatusInTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; float m_pointSizeInDisplayGroup[DisplayGroupEnum::NUMBER_OF_GROUPS]; float m_pointSizeInTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; float m_lineWidthInDisplayGroup[DisplayGroupEnum::NUMBER_OF_GROUPS]; float m_lineWidthInTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; BorderDrawingTypeEnum::Enum m_drawingTypeInDisplayGroup[DisplayGroupEnum::NUMBER_OF_GROUPS]; BorderDrawingTypeEnum::Enum m_drawingTypeInTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; FeatureColoringTypeEnum::Enum m_coloringTypeInDisplayGroup[DisplayGroupEnum::NUMBER_OF_GROUPS]; FeatureColoringTypeEnum::Enum m_coloringTypeInTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; bool m_unstretchedLinesStatusInDisplayGroup[DisplayGroupEnum::NUMBER_OF_GROUPS]; bool m_unstretchedLinesStatusInTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; float m_unstretchedLinesLengthInDisplayGroup[DisplayGroupEnum::NUMBER_OF_GROUPS]; float m_unstretchedLinesLengthInTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; }; #ifdef __DISPLAY_PROPERTIES_BORDERS_DECLARE__ // #endif // __DISPLAY_PROPERTIES_BORDERS_DECLARE__ } // namespace #endif //__DISPLAY_PROPERTIES_BORDERS__H_ workbench-1.1.1/src/Brain/DisplayPropertiesFiberOrientation.cxx000066400000000000000000001006051255417355300247240ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __DISPLAY_PROPERTIES_FIBER_ORIENTATION_DECLARE__ #include "DisplayPropertiesFiberOrientation.h" #undef __DISPLAY_PROPERTIES_FIBER_ORIENTATION_DECLARE__ #include "CaretAssert.h" #include "SceneAttributes.h" #include "SceneClass.h" #include "SceneClassAssistant.h" using namespace caret; /** * \class caret::DisplayPropertiesFiberOrientation * \brief Contains display properties for borders. * * Border display properties are available for every tab and also a * few 'display groups'. A number of methods in this class accept * both display group and tab index parameters. When the display * group is set to 'Tab', the tab index is used meaning that the * attribute requeted/sent is for use with a specifc tab. For an * other display group value, the attribute is for a display group * and the tab index is ignored. */ /** * Constructor. */ DisplayPropertiesFiberOrientation::DisplayPropertiesFiberOrientation() : DisplayProperties() { const float aboveLimit = 0.63; const float belowLimit = -0.63; const float minimumMagnitude = 0.05; const bool drawWithMagnitude = true; const float lengthMultiplier = 6.0; const float fanMultiplier = 3.0; const FiberOrientationColoringTypeEnum::Enum coloringType = FiberOrientationColoringTypeEnum::FIBER_COLORING_XYZ_AS_RGB; const FiberOrientationSymbolTypeEnum::Enum symbolType = FiberOrientationSymbolTypeEnum::FIBER_SYMBOL_LINES; const bool displaySphereOrientions = false; for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_displayGroup[i] = DisplayGroupEnum::getDefaultValue(); m_displayStatusInTab[i] = false; m_aboveLimitInTab[i] = aboveLimit; m_belowLimitInTab[i] = belowLimit; m_minimumMagnitudeInTab[i] = minimumMagnitude; m_drawWithMagnitudeInTab[i] = drawWithMagnitude; m_lengthMultiplierInTab[i] = lengthMultiplier; m_fiberColoringTypeInTab[i] = coloringType; m_fiberSymbolTypeInTab[i] = symbolType; m_fanMultiplierInTab[i] = fanMultiplier; m_displaySphereOrientationsInTab[i] = displaySphereOrientions; } for (int32_t i = 0; i < DisplayGroupEnum::NUMBER_OF_GROUPS; i++) { m_displayStatusInDisplayGroup[i] = false; m_aboveLimitInDisplayGroup[i] = aboveLimit; m_belowLimitInDisplayGroup[i] = belowLimit; m_minimumMagnitudeInDisplayGroup[i] = minimumMagnitude; m_drawWithMagnitudeInDisplayGroup[i] = drawWithMagnitude; m_lengthMultiplierInDisplayGroup[i] = lengthMultiplier; m_fiberColoringTypeInDisplayGroup[i] = coloringType; m_fiberSymbolTypeInDisplayGroup[i] = symbolType; m_fanMultiplierInDisplayGroup[i] = fanMultiplier; m_displaySphereOrientationsInDisplayGroup[i] = displaySphereOrientions; } m_sceneAssistant->addTabIndexedEnumeratedTypeArray("m_displayGroup", m_displayGroup); m_sceneAssistant->addTabIndexedBooleanArray("m_displayStatusInTab", m_displayStatusInTab); m_sceneAssistant->addTabIndexedFloatArray("m_aboveLimitInTab", m_aboveLimitInTab); m_sceneAssistant->addTabIndexedFloatArray("m_belowLimitInTab", m_belowLimitInTab); m_sceneAssistant->addTabIndexedFloatArray("m_minimumMagnitudeInTab", m_minimumMagnitudeInTab); m_sceneAssistant->addTabIndexedBooleanArray("m_drawWithMagnitudeInTab", m_drawWithMagnitudeInTab); m_sceneAssistant->addTabIndexedFloatArray("m_lengthMultiplierInTab", m_lengthMultiplierInTab); m_sceneAssistant->addTabIndexedFloatArray("m_fanMultiplierInTab", m_fanMultiplierInTab); m_sceneAssistant->addTabIndexedBooleanArray("m_displaySphereOrientationsInTab", m_displaySphereOrientationsInTab); m_sceneAssistant->addArray("m_drawingTypeInDisplayGroup", m_fiberColoringTypeInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, FiberOrientationColoringTypeEnum::FIBER_COLORING_XYZ_AS_RGB); m_sceneAssistant->addArray("m_fiberSymbolTypeInDisplayGroup", m_fiberSymbolTypeInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, FiberOrientationSymbolTypeEnum::FIBER_SYMBOL_LINES); m_sceneAssistant->addArray("m_displayStatusInDisplayGroup", m_displayStatusInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, m_displayStatusInDisplayGroup[0]); m_sceneAssistant->addArray("m_aboveLimitInDisplayGroup", m_aboveLimitInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, m_aboveLimitInDisplayGroup[0]); m_sceneAssistant->addArray("m_belowLimitInDisplayGroup", m_belowLimitInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, m_belowLimitInDisplayGroup[0]); m_sceneAssistant->addArray("m_minimumMagnitudeInDisplayGroup", m_minimumMagnitudeInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, m_minimumMagnitudeInDisplayGroup[0]); m_sceneAssistant->addArray("m_drawWithMagnitudeInDisplayGroup", m_drawWithMagnitudeInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, m_drawWithMagnitudeInDisplayGroup[0]); m_sceneAssistant->addArray("m_lengthMultiplierInDisplayGroup", m_lengthMultiplierInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, m_lengthMultiplierInDisplayGroup[0]); m_sceneAssistant->addArray("m_fanMultiplierInDisplayGroup", m_fanMultiplierInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, m_fanMultiplierInDisplayGroup[0]); m_sceneAssistant->addArray("m_displaySphereOrientationsInTab", m_displaySphereOrientationsInTab, DisplayGroupEnum::NUMBER_OF_GROUPS, m_displaySphereOrientationsInTab[0]); m_sceneAssistant->addTabIndexedEnumeratedTypeArray("m_fiberColoringTypeInTab", m_fiberColoringTypeInTab); m_sceneAssistant->addTabIndexedEnumeratedTypeArray("m_fiberSymbolTypeInTab", m_fiberSymbolTypeInTab); } /** * Destructor. */ DisplayPropertiesFiberOrientation::~DisplayPropertiesFiberOrientation() { } /** * Reset all settings to their defaults * and remove any data. */ void DisplayPropertiesFiberOrientation::reset() { } /** * Update due to changes in data. */ void DisplayPropertiesFiberOrientation::update() { } /** * Copy the fiber orientation display properties from one tab to another. * @param sourceTabIndex * Index of tab from which properties are copied. * @param targetTabIndex * Index of tab to which properties are copied. */ void DisplayPropertiesFiberOrientation::copyDisplayProperties(const int32_t sourceTabIndex, const int32_t targetTabIndex) { const DisplayGroupEnum::Enum displayGroup = this->getDisplayGroupForTab(sourceTabIndex); this->setDisplayGroupForTab(targetTabIndex, displayGroup); m_displayStatusInTab[targetTabIndex] = m_displayStatusInTab[sourceTabIndex]; m_aboveLimitInTab[targetTabIndex] = m_aboveLimitInTab[sourceTabIndex]; m_belowLimitInTab[targetTabIndex] = m_belowLimitInTab[sourceTabIndex]; m_minimumMagnitudeInTab[targetTabIndex] = m_minimumMagnitudeInTab[sourceTabIndex]; m_drawWithMagnitudeInTab[targetTabIndex] = m_drawWithMagnitudeInTab[sourceTabIndex]; m_lengthMultiplierInTab[targetTabIndex] = m_lengthMultiplierInTab[sourceTabIndex]; m_fiberColoringTypeInTab[targetTabIndex] = m_fiberColoringTypeInTab[sourceTabIndex]; m_fanMultiplierInTab[targetTabIndex] = m_fanMultiplierInTab[sourceTabIndex]; } /** * @return Display status of fiber orientations. * @param displayGroup * The display group. * @param tabIndex * Index of browser tab. */ bool DisplayPropertiesFiberOrientation::isDisplayed(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const { CaretAssertArrayIndex(m_displayStatusInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_displayStatusInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_displayStatusInTab[tabIndex]; } return m_displayStatusInDisplayGroup[displayGroup]; } /** * Set the display status for fiber orientations for the given display group. * @param displayGroup * The display group. * @param tabIndex * Index of browser tab. * @param displayStatus * New status. */ void DisplayPropertiesFiberOrientation::setDisplayed(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const bool displayStatus) { CaretAssertArrayIndex(m_displayStatusInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_displayStatusInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_displayStatusInTab[tabIndex] = displayStatus; } else { m_displayStatusInDisplayGroup[displayGroup] = displayStatus; } } /** * @return Display status of sphere orientations. * @param displayGroup * The display group. * @param tabIndex * Index of browser tab. */ bool DisplayPropertiesFiberOrientation::isSphereOrientationsDisplayed(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const { CaretAssertArrayIndex(m_displaySphereOrientationsInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_displaySphereOrientationsInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_displaySphereOrientationsInTab[tabIndex]; } return m_displaySphereOrientationsInDisplayGroup[displayGroup]; } /** * Set the display status for sphere orientations for the given display group. * @param displayGroup * The display group. * @param tabIndex * Index of browser tab. * @param displayStatus * New status. */ void DisplayPropertiesFiberOrientation::setSphereOrientationsDisplayed(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const bool displaySphereOrientations) { CaretAssertArrayIndex(m_displaySphereOrientationsInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_displaySphereOrientationsInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_displaySphereOrientationsInTab[tabIndex] = displaySphereOrientations; } else { m_displaySphereOrientationsInDisplayGroup[displayGroup] = displaySphereOrientations; } } /** * Get the display group for a given browser tab. * @param browserTabIndex * Index of browser tab. */ DisplayGroupEnum::Enum DisplayPropertiesFiberOrientation::getDisplayGroupForTab(const int32_t browserTabIndex) const { CaretAssertArrayIndex(this->displayGroup, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, browserTabIndex); return m_displayGroup[browserTabIndex]; } /** * Set the display group for a given browser tab. * @param browserTabIndex * Index of browser tab. * @param displayGroup * New value for display group. */ void DisplayPropertiesFiberOrientation::setDisplayGroupForTab(const int32_t browserTabIndex, const DisplayGroupEnum::Enum displayGroup) { CaretAssertArrayIndex(this->displayGroup, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, browserTabIndex); m_displayGroup[browserTabIndex] = displayGroup; } /** * @return Draw with magnitude status. * @param displayGroup * The display group. * @param tabIndex * Index of browser tab. */ bool DisplayPropertiesFiberOrientation::isDrawWithMagnitude(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const { CaretAssertArrayIndex(m_drawWithMagnitudeInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_drawWithMagnitudeInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_drawWithMagnitudeInTab[tabIndex]; } return m_drawWithMagnitudeInDisplayGroup[displayGroup]; } /** * Set the draw with magnitude status for the given display group. * @param displayGroup * The display group. * @param tabIndex * Index of browser tab. * @param drawWithMagnitude * New status. */ void DisplayPropertiesFiberOrientation::setDrawWithMagnitude(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const bool drawWithMagnitude) { CaretAssertArrayIndex(m_drawWithMagnitudeInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_drawWithMagnitudeInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_drawWithMagnitudeInTab[tabIndex] = drawWithMagnitude; } else { m_drawWithMagnitudeInDisplayGroup[displayGroup] = drawWithMagnitude; } } /** * @return The Above limit. * @param displayGroup * The display group. * @param tabIndex * Index of browser tab. */ float DisplayPropertiesFiberOrientation::getAboveLimit(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const { CaretAssertArrayIndex(m_aboveLimitInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_aboveLimitInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_aboveLimitInTab[tabIndex]; } return m_aboveLimitInDisplayGroup[displayGroup]; } /** * Set the above limit to the given value. * @param displayGroup * The display group. * @param tabIndex * Index of browser tab. * @param aboveLimit * New value for above limit. */ void DisplayPropertiesFiberOrientation::setAboveLimit(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const float aboveLimit) { CaretAssertArrayIndex(m_aboveLimitInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_aboveLimitInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_aboveLimitInTab[tabIndex] = aboveLimit; } else { m_aboveLimitInDisplayGroup[displayGroup] = aboveLimit; } } /** * @return The below limit. * @param displayGroup * The display group. * @param tabIndex * Index of browser tab. */ float DisplayPropertiesFiberOrientation::getBelowLimit(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const { CaretAssertArrayIndex(m_belowLimitInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_belowLimitInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_belowLimitInTab[tabIndex]; } return m_belowLimitInDisplayGroup[displayGroup]; } /** * Set the below limit to the given value. * @param displayGroup * The display group. * @param tabIndex * Index of browser tab. * @param pointSize * New value for below limit. */ void DisplayPropertiesFiberOrientation::setBelowLimit(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const float belowLimit) { CaretAssertArrayIndex(m_belowLimitInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_belowLimitInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_belowLimitInTab[tabIndex] = belowLimit; } else { m_belowLimitInDisplayGroup[displayGroup] = belowLimit; } } /** * Set the above and below limits for all display groups and tabs. * @param * Value for all above limits. * @param * Value for all below limits. */ void DisplayPropertiesFiberOrientation::setAboveAndBelowLimitsForAll(const float aboveLimit, const float belowLimit) { for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_aboveLimitInTab[i] = aboveLimit; m_belowLimitInTab[i] = belowLimit; } for (int32_t i = 0; i < DisplayGroupEnum::NUMBER_OF_GROUPS; i++) { m_aboveLimitInDisplayGroup[i] = aboveLimit; m_belowLimitInDisplayGroup[i] = belowLimit; } } /** * @return The minimum magnitude. * @param displayGroup * The display group. * @param tabIndex * Index of browser tab. */ float DisplayPropertiesFiberOrientation::getMinimumMagnitude(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const { CaretAssertArrayIndex(m_minimumMagnitudeInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_minimumMagnitudeInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_minimumMagnitudeInTab[tabIndex]; } return m_minimumMagnitudeInDisplayGroup[displayGroup]; } /** * Set the minimum magnitude to the given value. * @param displayGroup * The display group. * @param tabIndex * Index of browser tab. * @param minimumMagnitude * New value for minimum magnitude. */ void DisplayPropertiesFiberOrientation::setMinimumMagnitude(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const float minimumMagnitude) { CaretAssertArrayIndex(m_minimumMagnitudeInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_minimumMagnitudeInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_minimumMagnitudeInTab[tabIndex] = minimumMagnitude; } else { m_minimumMagnitudeInDisplayGroup[displayGroup] = minimumMagnitude; } } /** * @return The length multiplier. * @param displayGroup * The display group. * @param tabIndex * Index of browser tab. */ float DisplayPropertiesFiberOrientation::getLengthMultiplier(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const { CaretAssertArrayIndex(m_lengthMultiplierInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_lengthMultiplierInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_lengthMultiplierInTab[tabIndex]; } return m_lengthMultiplierInDisplayGroup[displayGroup]; } /** * Set the length multiplier to the given value. * @param displayGroup * The display group. * @param tabIndex * Index of browser tab. * @param lengthMultiplier * New value for length multiplier */ void DisplayPropertiesFiberOrientation::setLengthMultiplier(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const float lengthMultiplier) { CaretAssertArrayIndex(m_lengthMultiplierInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_lengthMultiplierInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_lengthMultiplierInTab[tabIndex] = lengthMultiplier; } else { m_lengthMultiplierInDisplayGroup[displayGroup] = lengthMultiplier; } } /** * @return The coloring type. * @param displayGroup * The display group. * @param tabIndex * Index of browser tab. */ FiberOrientationColoringTypeEnum::Enum DisplayPropertiesFiberOrientation::getColoringType(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const { CaretAssertArrayIndex(m_fiberColoringTypeInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_fiberColoringTypeInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_fiberColoringTypeInTab[tabIndex]; } return m_fiberColoringTypeInDisplayGroup[displayGroup]; } /** * Set the coloring type to the given value. * @param displayGroup * The display group. * @param tabIndex * Index of browser tab. * @param coloringType * New value for coloring type. */ void DisplayPropertiesFiberOrientation::setColoringType(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const FiberOrientationColoringTypeEnum::Enum coloringType) { CaretAssertArrayIndex(m_fiberColoringTypeInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_fiberColoringTypeInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_fiberColoringTypeInTab[tabIndex] = coloringType; } else { m_fiberColoringTypeInDisplayGroup[displayGroup] = coloringType; } } /** * @return The symbol type. * @param displayGroup * The display group. * @param tabIndex * Index of browser tab. */ FiberOrientationSymbolTypeEnum::Enum DisplayPropertiesFiberOrientation::getSymbolType(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const { CaretAssertArrayIndex(m_fiberSymbolTypeInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_fiberSymbolTypeInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_fiberSymbolTypeInTab[tabIndex]; } return m_fiberSymbolTypeInDisplayGroup[displayGroup]; } /** * Set the symbol type to the given value. * @param displayGroup * The display group. * @param tabIndex * Index of browser tab. * @param symbolType * New value for symbol type. */ void DisplayPropertiesFiberOrientation::setSymbolType(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const FiberOrientationSymbolTypeEnum::Enum symbolType) { CaretAssertArrayIndex(m_fiberSymbolTypeInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_fiberSymbolTypeInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_fiberSymbolTypeInTab[tabIndex] = symbolType; } else { m_fiberSymbolTypeInDisplayGroup[displayGroup] = symbolType; } } /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ SceneClass* DisplayPropertiesFiberOrientation::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "DisplayPropertiesFiberOrientation", 1); m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); return sceneClass; } /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * SceneClass containing the state that was previously * saved and should be restored. */ void DisplayPropertiesFiberOrientation::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); switch (sceneAttributes->getSceneType()) { case SceneTypeEnum::SCENE_TYPE_FULL: break; case SceneTypeEnum::SCENE_TYPE_GENERIC: break; } } /** * @return The fan multiplier. * @param displayGroup * The display group. * @param tabIndex * Index of browser tab. */ float DisplayPropertiesFiberOrientation::getFanMultiplier(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const { CaretAssertArrayIndex(m_fanMultiplierInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_fanMultiplierInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_fanMultiplierInTab[tabIndex]; } return m_fanMultiplierInDisplayGroup[displayGroup]; } /** * Set the fan multiplier to the given value. * @param displayGroup * The display group. * @param tabIndex * Index of browser tab. * @param lengthMultiplier * New value for fan multiplier */ void DisplayPropertiesFiberOrientation::setFanMultiplier(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const float fanMultiplier) { CaretAssertArrayIndex(m_fanMultiplierInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_fanMultiplierInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_fanMultiplierInTab[tabIndex] = fanMultiplier; } else { m_fanMultiplierInDisplayGroup[displayGroup] = fanMultiplier; } } workbench-1.1.1/src/Brain/DisplayPropertiesFiberOrientation.h000066400000000000000000000204331255417355300243510ustar00rootroot00000000000000#ifndef __DISPLAY_PROPERTIES_FIBER_ORIENTATION__H_ #define __DISPLAY_PROPERTIES_FIBER_ORIENTATION__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "BrainConstants.h" #include "DisplayGroupEnum.h" #include "DisplayProperties.h" #include "FiberOrientationColoringTypeEnum.h" #include "FiberOrientationSymbolTypeEnum.h" namespace caret { class DisplayPropertiesFiberOrientation : public DisplayProperties { public: DisplayPropertiesFiberOrientation(); virtual ~DisplayPropertiesFiberOrientation(); virtual void reset(); virtual void update(); virtual void copyDisplayProperties(const int32_t sourceTabIndex, const int32_t targetTabIndex); bool isDisplayed(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const; void setDisplayed(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const bool displayStatus); bool isDrawWithMagnitude(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const; void setDrawWithMagnitude(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const bool drawWithMagnitude); DisplayGroupEnum::Enum getDisplayGroupForTab(const int32_t browserTabIndex) const; void setDisplayGroupForTab(const int32_t browserTabIndex, const DisplayGroupEnum::Enum displayGroup); float getAboveLimit(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const; void setAboveLimit(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const float aboveLimit); void setAboveAndBelowLimitsForAll(const float aboveLimit, const float belowLimit); float getBelowLimit(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const; void setBelowLimit(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const float aboveLimit); float getMinimumMagnitude(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const; void setMinimumMagnitude(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const float minimumMagnitude); float getLengthMultiplier(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const; void setLengthMultiplier(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const float lengthMultiplier); float getFanMultiplier(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const; void setFanMultiplier(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const float fanMultiplier); FiberOrientationColoringTypeEnum::Enum getColoringType(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const; void setColoringType(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const FiberOrientationColoringTypeEnum::Enum coloringType); FiberOrientationSymbolTypeEnum::Enum getSymbolType(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const; void setSymbolType(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const FiberOrientationSymbolTypeEnum::Enum symbolType); bool isSphereOrientationsDisplayed(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const; void setSphereOrientationsDisplayed(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const bool displaySphereOrientations); virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private: DisplayPropertiesFiberOrientation(const DisplayPropertiesFiberOrientation&); DisplayPropertiesFiberOrientation& operator=(const DisplayPropertiesFiberOrientation&); DisplayGroupEnum::Enum m_displayGroup[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; bool m_displayStatusInDisplayGroup[DisplayGroupEnum::NUMBER_OF_GROUPS]; bool m_displayStatusInTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; float m_aboveLimitInDisplayGroup[DisplayGroupEnum::NUMBER_OF_GROUPS]; float m_aboveLimitInTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; float m_belowLimitInDisplayGroup[DisplayGroupEnum::NUMBER_OF_GROUPS]; float m_belowLimitInTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; float m_minimumMagnitudeInDisplayGroup[DisplayGroupEnum::NUMBER_OF_GROUPS]; float m_minimumMagnitudeInTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; bool m_drawWithMagnitudeInDisplayGroup[DisplayGroupEnum::NUMBER_OF_GROUPS]; bool m_drawWithMagnitudeInTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; float m_lengthMultiplierInDisplayGroup[DisplayGroupEnum::NUMBER_OF_GROUPS]; float m_lengthMultiplierInTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; float m_fanMultiplierInDisplayGroup[DisplayGroupEnum::NUMBER_OF_GROUPS]; float m_fanMultiplierInTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; FiberOrientationColoringTypeEnum::Enum m_fiberColoringTypeInDisplayGroup[DisplayGroupEnum::NUMBER_OF_GROUPS]; FiberOrientationColoringTypeEnum::Enum m_fiberColoringTypeInTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; FiberOrientationSymbolTypeEnum::Enum m_fiberSymbolTypeInDisplayGroup[DisplayGroupEnum::NUMBER_OF_GROUPS]; FiberOrientationSymbolTypeEnum::Enum m_fiberSymbolTypeInTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; bool m_displaySphereOrientationsInDisplayGroup[DisplayGroupEnum::NUMBER_OF_GROUPS]; bool m_displaySphereOrientationsInTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; // friend class BrainOpenGLFixedPipeline; }; #ifdef __DISPLAY_PROPERTIES_FIBER_ORIENTATION_DECLARE__ // #endif // __DISPLAY_PROPERTIES_FIBER_ORIENTATION_DECLARE__ } // namespace #endif //__DISPLAY_PROPERTIES_FIBER_ORIENTATION__H_ workbench-1.1.1/src/Brain/DisplayPropertiesFoci.cxx000066400000000000000000000504721255417355300223470ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __DISPLAY_PROPERTIES_FOCI_DECLARE__ #include "DisplayPropertiesFoci.h" #undef __DISPLAY_PROPERTIES_FOCI_DECLARE__ #include "CaretAssert.h" #include "SceneAttributes.h" #include "SceneClass.h" #include "SceneClassAssistant.h" using namespace caret; /** * \class caret::DisplayPropertiesFoci * \brief Contains display properties for foci. */ /** * Constructor. */ DisplayPropertiesFoci::DisplayPropertiesFoci() : DisplayProperties() { for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_displayGroup[i] = DisplayGroupEnum::getDefaultValue(); m_pasteOntoSurfaceInTab[i] = false; m_displayStatusInTab[i] = false; m_contralateralDisplayStatusInTab[i] = false; m_fociSizeInTab[i] = 4.0; m_coloringTypeInTab[i] = FeatureColoringTypeEnum::FEATURE_COLORING_TYPE_NAME; m_drawingTypeInTab[i] = FociDrawingTypeEnum::DRAW_AS_SQUARES; } for (int32_t i = 0; i < DisplayGroupEnum::NUMBER_OF_GROUPS; i++) { m_pasteOntoSurfaceInDisplayGroup[i] = false; m_displayStatusInDisplayGroup[i] = false; m_contralateralDisplayStatusInDisplayGroup[i] = false; m_fociSizeInDisplayGroup[i] = 4.0; m_coloringTypeInDisplayGroup[i] = FeatureColoringTypeEnum::FEATURE_COLORING_TYPE_NAME; m_drawingTypeInDisplayGroup[i] = FociDrawingTypeEnum::DRAW_AS_SQUARES; } m_sceneAssistant->addTabIndexedEnumeratedTypeArray("m_displayGroup", m_displayGroup); m_sceneAssistant->addTabIndexedBooleanArray("m_pasteOntoSurfaceInTab", m_pasteOntoSurfaceInTab); m_sceneAssistant->addTabIndexedBooleanArray("m_displayStatusInTab", m_displayStatusInTab); m_sceneAssistant->addTabIndexedBooleanArray("m_contralateralDisplayStatusInTab", m_contralateralDisplayStatusInTab); m_sceneAssistant->addTabIndexedFloatArray("m_fociSizeInTab", m_fociSizeInTab); m_sceneAssistant->addTabIndexedEnumeratedTypeArray("m_coloringTypeInTab", m_coloringTypeInTab); m_sceneAssistant->addTabIndexedEnumeratedTypeArray("m_drawingTypeInTab", m_drawingTypeInTab); m_sceneAssistant->addArray("m_pasteOntoSurfaceInDisplayGroup", m_pasteOntoSurfaceInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, m_pasteOntoSurfaceInDisplayGroup[0]); m_sceneAssistant->addArray("m_displayStatusInDisplayGroup", m_displayStatusInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, m_displayStatusInDisplayGroup[0]); m_sceneAssistant->addArray("m_contralateralDisplayStatusInDisplayGroup", m_contralateralDisplayStatusInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, m_contralateralDisplayStatusInDisplayGroup[0]); m_sceneAssistant->addArray("m_fociSizeInDisplayGroup", m_fociSizeInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, m_fociSizeInDisplayGroup[0]); m_sceneAssistant->addArray("m_coloringTypeInDisplayGroup", m_coloringTypeInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, FeatureColoringTypeEnum::FEATURE_COLORING_TYPE_NAME); m_sceneAssistant->addArray("m_drawingTypeInDisplayGroup", m_drawingTypeInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, FociDrawingTypeEnum::DRAW_AS_SQUARES); } /** * Destructor. */ DisplayPropertiesFoci::~DisplayPropertiesFoci() { } /** * Copy the border display properties from one tab to another. * @param sourceTabIndex * Index of tab from which properties are copied. * @param targetTabIndex * Index of tab to which properties are copied. */ void DisplayPropertiesFoci::copyDisplayProperties(const int32_t sourceTabIndex, const int32_t targetTabIndex) { const DisplayGroupEnum::Enum displayGroup = this->getDisplayGroupForTab(sourceTabIndex); this->setDisplayGroupForTab(targetTabIndex, displayGroup); m_coloringTypeInTab[targetTabIndex] = m_coloringTypeInTab[sourceTabIndex]; m_contralateralDisplayStatusInTab[targetTabIndex] = m_contralateralDisplayStatusInTab[sourceTabIndex]; m_displayStatusInTab[targetTabIndex] = m_displayStatusInTab[sourceTabIndex]; m_drawingTypeInTab[targetTabIndex] = m_drawingTypeInTab[sourceTabIndex]; m_fociSizeInTab[targetTabIndex] = m_fociSizeInTab[sourceTabIndex]; m_pasteOntoSurfaceInTab[targetTabIndex] = m_pasteOntoSurfaceInTab[sourceTabIndex]; } /** * Reset all settings to their defaults * and remove any data. */ void DisplayPropertiesFoci::reset() { // for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { // m_displayStatus[i] = true; // m_contralateralDisplayStatus[i] = false; // m_displayGroup[i] = DisplayGroupEnum::DISPLAY_ALL_WINDOWS; // m_pasteOntoSurface[i] = false; // } } /** * Update due to changes in data. */ void DisplayPropertiesFoci::update() { } /** * @return Display status of foci. * @param displayGroup * Display group. */ bool DisplayPropertiesFoci::isDisplayed(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const { CaretAssertArrayIndex(m_displayStatusInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_displayStatusInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_displayStatusInTab[tabIndex]; } return m_displayStatusInDisplayGroup[displayGroup]; } /** * Set the display status for foci. * @param displayGroup * Display group. * @param displayStatus * New status. */ void DisplayPropertiesFoci::setDisplayed(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const bool displayStatus) { CaretAssertArrayIndex(m_displayStatusInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_displayStatusInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_displayStatusInTab[tabIndex] = displayStatus; } else { m_displayStatusInDisplayGroup[displayGroup] = displayStatus; } } /** * @return Contralateral display status of foci. * @param displayGroup * Display group. * @param browserTabIndex * Index of browser tab. */ bool DisplayPropertiesFoci::isContralateralDisplayed(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const { CaretAssertArrayIndex(m_contralateralDisplayStatusInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_contralateralDisplayStatusInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_contralateralDisplayStatusInTab[tabIndex]; } return m_contralateralDisplayStatusInDisplayGroup[displayGroup]; } /** * Set the contralateral display status for foci. * @param displayGroup * Display group. * @param contralateralDisplayStatus * New status. */ void DisplayPropertiesFoci::setContralateralDisplayed(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const bool contralateralDisplayStatus) { CaretAssertArrayIndex(m_contralateralDisplayStatusInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_contralateralDisplayStatusInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_contralateralDisplayStatusInTab[tabIndex] = contralateralDisplayStatus; } else { m_contralateralDisplayStatusInDisplayGroup[displayGroup] = contralateralDisplayStatus; } } /** * Get the display group for a given browser tab. * @param browserTabIndex * Index of browser tab. */ DisplayGroupEnum::Enum DisplayPropertiesFoci::getDisplayGroupForTab(const int32_t browserTabIndex) const { CaretAssertArrayIndex(m_displayGroup, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, browserTabIndex); return m_displayGroup[browserTabIndex]; } /** * Set the display group for a given browser tab. * @param browserTabIndex * Index of browser tab. * @param displayGroup * New value for display group. */ void DisplayPropertiesFoci::setDisplayGroupForTab(const int32_t browserTabIndex, const DisplayGroupEnum::Enum displayGroup) { CaretAssertArrayIndex(m_displayGroup, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, browserTabIndex); m_displayGroup[browserTabIndex] = displayGroup; } /** * @return The foci size. * @param displayGroup * Display group. */ float DisplayPropertiesFoci::getFociSize(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const { CaretAssertArrayIndex(m_fociSizeInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_fociSizeInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_fociSizeInTab[tabIndex]; } return m_fociSizeInDisplayGroup[displayGroup]; } /** * Set the foci size to the given value. * @param displayGroup * Display group. * @param fociSize * New value for foci size. */ void DisplayPropertiesFoci::setFociSize(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const float fociSize) { CaretAssertArrayIndex(m_fociSizeInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_fociSizeInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_fociSizeInTab[tabIndex] = fociSize; } else { m_fociSizeInDisplayGroup[displayGroup] = fociSize; } } /** * @return The coloring type. * @param displayGroup * Display group. */ FeatureColoringTypeEnum::Enum DisplayPropertiesFoci::getColoringType(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const { CaretAssertArrayIndex(m_coloringTypeInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_coloringTypeInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_coloringTypeInTab[tabIndex]; } return m_coloringTypeInDisplayGroup[displayGroup]; } /** * Set the coloring type. * @param displayGroup * Display group. * @param coloringType * New value for coloring type. */ void DisplayPropertiesFoci::setColoringType(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const FeatureColoringTypeEnum::Enum coloringType) { CaretAssertArrayIndex(m_coloringTypeInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_coloringTypeInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_coloringTypeInTab[tabIndex] = coloringType; } else { m_coloringTypeInDisplayGroup[displayGroup] = coloringType; } } /** * @param displayGroup * Display group. * @return The drawing type. */ FociDrawingTypeEnum::Enum DisplayPropertiesFoci::getDrawingType(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const { CaretAssertArrayIndex(m_drawingTypeInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_drawingTypeInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_drawingTypeInTab[tabIndex]; } return m_drawingTypeInDisplayGroup[displayGroup]; } /** * Set the drawing type to the given value. * @param displayGroup * Display group. * @param drawingType * New value for drawing type. */ void DisplayPropertiesFoci::setDrawingType(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const FociDrawingTypeEnum::Enum drawingType) { CaretAssertArrayIndex(m_drawingTypeInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_drawingTypeInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_drawingTypeInTab[tabIndex] = drawingType; } else { m_drawingTypeInDisplayGroup[displayGroup] = drawingType; } } /** * Set paste onto surface so the foci are placed directly on the surface. * @param displayGroup * Display group. * @param enabled * True if pasting foci onto surface is enabled. */ void DisplayPropertiesFoci::setPasteOntoSurface(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const bool enabled) { CaretAssertArrayIndex(m_pasteOntoSurfaceInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_pasteOntoSurfaceInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_pasteOntoSurfaceInTab[tabIndex] = enabled; } else { m_pasteOntoSurfaceInDisplayGroup[displayGroup] = enabled; } } /** * @param displayGroup * Display group. * @return True if foci are pasted onto surface. */ bool DisplayPropertiesFoci::isPasteOntoSurface(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const { CaretAssertArrayIndex(m_pasteOntoSurfaceInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_pasteOntoSurfaceInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_pasteOntoSurfaceInTab[tabIndex]; } return m_pasteOntoSurfaceInDisplayGroup[displayGroup]; } /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ SceneClass* DisplayPropertiesFoci::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { const std::vector tabIndices = sceneAttributes->getIndicesOfTabsForSavingToScene(); SceneClass* sceneClass = new SceneClass(instanceName, "DisplayPropertiesFoci", 1); m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); switch (sceneAttributes->getSceneType()) { case SceneTypeEnum::SCENE_TYPE_FULL: break; case SceneTypeEnum::SCENE_TYPE_GENERIC: break; } return sceneClass; } /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * SceneClass containing the state that was previously * saved and should be restored. */ void DisplayPropertiesFoci::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); switch (sceneAttributes->getSceneType()) { case SceneTypeEnum::SCENE_TYPE_FULL: break; case SceneTypeEnum::SCENE_TYPE_GENERIC: break; } } workbench-1.1.1/src/Brain/DisplayPropertiesFoci.h000066400000000000000000000133551255417355300217730ustar00rootroot00000000000000#ifndef __DISPLAY_PROPERTIES_FOCI__H_ #define __DISPLAY_PROPERTIES_FOCI__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "BrainConstants.h" #include "DisplayGroupEnum.h" #include "DisplayProperties.h" #include "FeatureColoringTypeEnum.h" #include "FociDrawingTypeEnum.h" namespace caret { class DisplayPropertiesFoci : public DisplayProperties { public: DisplayPropertiesFoci(); virtual ~DisplayPropertiesFoci(); virtual void reset(); virtual void update(); virtual void copyDisplayProperties(const int32_t sourceTabIndex, const int32_t targetTabIndex); bool isDisplayed(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const; void setDisplayed(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const bool displayStatus); bool isContralateralDisplayed(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const; void setContralateralDisplayed(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const bool contralateralDisplayStatus); DisplayGroupEnum::Enum getDisplayGroupForTab(const int32_t browserTabIndex) const; void setDisplayGroupForTab(const int32_t browserTabIndex, const DisplayGroupEnum::Enum displayGroup); float getFociSize(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const; void setFociSize(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const float pointSize); FeatureColoringTypeEnum::Enum getColoringType(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const; void setColoringType(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const FeatureColoringTypeEnum::Enum coloringType); FociDrawingTypeEnum::Enum getDrawingType(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const; void setDrawingType(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const FociDrawingTypeEnum::Enum drawingType); void setPasteOntoSurface(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const bool enabled); bool isPasteOntoSurface(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const; virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private: DisplayPropertiesFoci(const DisplayPropertiesFoci&); DisplayPropertiesFoci& operator=(const DisplayPropertiesFoci&); DisplayGroupEnum::Enum m_displayGroup[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; bool m_displayStatusInDisplayGroup[DisplayGroupEnum::NUMBER_OF_GROUPS]; bool m_displayStatusInTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; bool m_contralateralDisplayStatusInDisplayGroup[DisplayGroupEnum::NUMBER_OF_GROUPS]; bool m_contralateralDisplayStatusInTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; bool m_pasteOntoSurfaceInDisplayGroup[DisplayGroupEnum::NUMBER_OF_GROUPS]; bool m_pasteOntoSurfaceInTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; float m_fociSizeInDisplayGroup[DisplayGroupEnum::NUMBER_OF_GROUPS]; float m_fociSizeInTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; FeatureColoringTypeEnum::Enum m_coloringTypeInDisplayGroup[DisplayGroupEnum::NUMBER_OF_GROUPS]; FeatureColoringTypeEnum::Enum m_coloringTypeInTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; FociDrawingTypeEnum::Enum m_drawingTypeInDisplayGroup[DisplayGroupEnum::NUMBER_OF_GROUPS]; FociDrawingTypeEnum::Enum m_drawingTypeInTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; }; #ifdef __DISPLAY_PROPERTIES_FOCI_DECLARE__ // #endif // __DISPLAY_PROPERTIES_FOCI_DECLARE__ } // namespace #endif //__DISPLAY_PROPERTIES_FOCI__H_ workbench-1.1.1/src/Brain/DisplayPropertiesImages.cxx000066400000000000000000000374711255417355300227000ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __DISPLAY_PROPERTIES_IMAGES_DECLARE__ #include "DisplayPropertiesImages.h" #undef __DISPLAY_PROPERTIES_IMAGES_DECLARE__ #include "Brain.h" #include "CaretLogger.h" #include "ImageFile.h" #include "SceneAttributes.h" #include "SceneClass.h" #include "SceneClassAssistant.h" #include "ScenePathName.h" #include "ScenePathNameArray.h" using namespace caret; /** * \class caret::DisplayPropertiesImages * \brief Contains display properties for images. * \ingroup Brain */ /** * Constructor. */ DisplayPropertiesImages::DisplayPropertiesImages(Brain* parentBrain) : DisplayProperties(), m_parentBrain(parentBrain) { CaretAssert(parentBrain); for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_displayGroup[i] = DisplayGroupEnum::getDefaultValue(); m_displayStatusInTab[i] = false; m_imageFileInTab[i] = NULL; } for (int32_t i = 0; i < DisplayGroupEnum::NUMBER_OF_GROUPS; i++) { m_displayStatusInDisplayGroup[i] = false; m_imageFileInDisplayGroup[i] = NULL; } m_sceneAssistant->addTabIndexedEnumeratedTypeArray("m_displayGroup", m_displayGroup); m_sceneAssistant->addTabIndexedBooleanArray("m_displayStatusInTab", m_displayStatusInTab); m_sceneAssistant->addArray("m_displayStatusInDisplayGroup", m_displayStatusInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, m_displayStatusInDisplayGroup[0]); } /** * Destructor. */ DisplayPropertiesImages::~DisplayPropertiesImages() { } /** * Copy the border display properties from one tab to another. * @param sourceTabIndex * Index of tab from which properties are copied. * @param targetTabIndex * Index of tab to which properties are copied. */ void DisplayPropertiesImages::copyDisplayProperties(const int32_t sourceTabIndex, const int32_t targetTabIndex) { const DisplayGroupEnum::Enum displayGroup = this->getDisplayGroupForTab(sourceTabIndex); this->setDisplayGroupForTab(targetTabIndex, displayGroup); m_displayStatusInTab[targetTabIndex] = m_displayStatusInTab[sourceTabIndex]; } /** * Reset all settings to their defaults * and remove any data. */ void DisplayPropertiesImages::reset() { // for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { // m_displayStatus[i] = true; // m_contralateralDisplayStatus[i] = false; // m_displayGroup[i] = DisplayGroupEnum::DISPLAY_ALL_WINDOWS; // m_pasteOntoSurface[i] = false; // } } /** * Update due to changes in data. */ void DisplayPropertiesImages::update() { } /** * @return Display status of foci. * @param displayGroup * Display group. */ bool DisplayPropertiesImages::isDisplayed(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const { CaretAssertArrayIndex(m_displayStatusInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_displayStatusInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_displayStatusInTab[tabIndex]; } return m_displayStatusInDisplayGroup[displayGroup]; } /** * Set the display status for foci. * @param displayGroup * Display group. * @param displayStatus * New status. */ void DisplayPropertiesImages::setDisplayed(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const bool displayStatus) { CaretAssertArrayIndex(m_displayStatusInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_displayStatusInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_displayStatusInTab[tabIndex] = displayStatus; } else { m_displayStatusInDisplayGroup[displayGroup] = displayStatus; } } /** * Get the display group for a given browser tab. * @param browserTabIndex * Index of browser tab. */ DisplayGroupEnum::Enum DisplayPropertiesImages::getDisplayGroupForTab(const int32_t browserTabIndex) const { CaretAssertArrayIndex(m_displayGroup, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, browserTabIndex); return m_displayGroup[browserTabIndex]; } /** * Set the display group for a given browser tab. * @param browserTabIndex * Index of browser tab. * @param displayGroup * New value for display group. */ void DisplayPropertiesImages::setDisplayGroupForTab(const int32_t browserTabIndex, const DisplayGroupEnum::Enum displayGroup) { CaretAssertArrayIndex(m_displayGroup, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, browserTabIndex); m_displayGroup[browserTabIndex] = displayGroup; } /** * Get the selected image file. * * @param displayGroup * Display group. * @param tabIndex * The tab index. * @return * The selected image file. */ ImageFile* DisplayPropertiesImages::getSelectedImageFile(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const { ImageFile* imageFile = NULL; CaretAssertArrayIndex(m_imageFileInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_imageFileInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); imageFile = m_imageFileInTab[tabIndex]; } else { const int32_t displayGroupInt = DisplayGroupEnum::toIntegerCode(displayGroup); CaretAssertArrayIndex(m_imageFileInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, displayGroupInt); imageFile = m_imageFileInDisplayGroup[displayGroupInt]; } const std::vector allImageFiles = m_parentBrain->getAllImagesFiles(); if (imageFile != NULL) { if (std::find(allImageFiles.begin(), allImageFiles.end(), imageFile) == allImageFiles.end()) { imageFile = NULL; } } if (imageFile == NULL) { if ( ! allImageFiles.empty()) { imageFile = allImageFiles[0]; } if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_imageFileInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_imageFileInTab[tabIndex] = imageFile; } else { CaretAssertArrayIndex(m_imageFileInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, (int)displayGroup); m_imageFileInDisplayGroup[displayGroup] = imageFile; } } return imageFile; } /** * Set the selected image. * @param displayGroup * Display group. * @param tabIndex * The tab index. * @param imageFile * Newly selected image file. */ void DisplayPropertiesImages::setSelectedImageFile(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, ImageFile* imageFile) { CaretAssertArrayIndex(m_imageFileInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, static_cast(displayGroup)); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_imageFileInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_imageFileInTab[tabIndex] = imageFile; } else { m_imageFileInDisplayGroup[displayGroup] = imageFile; } } /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ SceneClass* DisplayPropertiesImages::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { const std::vector tabIndices = sceneAttributes->getIndicesOfTabsForSavingToScene(); SceneClass* sceneClass = new SceneClass(instanceName, "DisplayPropertiesImages", 1); m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); switch (sceneAttributes->getSceneType()) { case SceneTypeEnum::SCENE_TYPE_FULL: break; case SceneTypeEnum::SCENE_TYPE_GENERIC: break; } std::vector pathNamesTabs; for (int32_t iTab = 0; iTab < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; iTab++) { const ImageFile* imageFile = getSelectedImageFile(DisplayGroupEnum::DISPLAY_GROUP_TAB, iTab); if (imageFile != NULL) { pathNamesTabs.push_back(imageFile->getFileName()); } else { pathNamesTabs.push_back(""); } } ScenePathNameArray* tabArray = new ScenePathNameArray("m_imageFileInTab", pathNamesTabs); sceneClass->addChild(tabArray); std::vector pathNamesDisplayGroup; for (int32_t idg = 0; idg < DisplayGroupEnum::NUMBER_OF_GROUPS; idg++) { DisplayGroupEnum::Enum displayGroup = DisplayGroupEnum::fromIntegerCode(idg, NULL); const ImageFile* imageFile = getSelectedImageFile(displayGroup, 0); if (imageFile != NULL) { pathNamesDisplayGroup.push_back(imageFile->getFileName()); } else { pathNamesDisplayGroup.push_back(""); } } ScenePathNameArray* displayGroupArray = new ScenePathNameArray("m_imageFileInDisplayGroup", pathNamesDisplayGroup); sceneClass->addChild(displayGroupArray); return sceneClass; } /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * SceneClass containing the state that was previously * saved and should be restored. */ void DisplayPropertiesImages::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_imageFileInTab[i] = NULL; } for (int32_t i = 0; i < DisplayGroupEnum::NUMBER_OF_GROUPS; i++) { m_imageFileInDisplayGroup[i] = NULL; } if (sceneClass == NULL) { return; } m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); switch (sceneAttributes->getSceneType()) { case SceneTypeEnum::SCENE_TYPE_FULL: break; case SceneTypeEnum::SCENE_TYPE_GENERIC: break; } const ScenePathNameArray* tabArray = sceneClass->getPathNameArray("m_imageFileInTab"); if (tabArray != NULL) { const int32_t numElements = tabArray->getNumberOfArrayElements(); const int32_t maxElem = std::min(numElements, (int32_t)BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS); for (int32_t iTab = 0; iTab < maxElem; iTab++) { const ScenePathName* spn = tabArray->getScenePathNameAtIndex(iTab); CaretAssert(spn); const AString filename = spn->stringValue(); if ( ! filename.isEmpty()) { m_imageFileInTab[iTab] = findImageFile(filename); } } } const ScenePathNameArray* displayGroupArray = sceneClass->getPathNameArray("m_imageFileInDisplayGroup"); if (displayGroupArray != NULL) { const int32_t numElements = displayGroupArray->getNumberOfArrayElements(); const int32_t maxElem = std::min(numElements, (int32_t)DisplayGroupEnum::NUMBER_OF_GROUPS); for (int32_t idg = 0; idg < maxElem; idg++) { const ScenePathName* spn = displayGroupArray->getScenePathNameAtIndex(idg); CaretAssert(spn); const AString filename = spn->stringValue(); if ( ! filename.isEmpty()) { DisplayGroupEnum::Enum displayGroup = DisplayGroupEnum::fromIntegerCode(idg, NULL); m_imageFileInDisplayGroup[displayGroup] = findImageFile(filename); } } } } /** * Find an image file by matching the full path name to the name of the * image file name. If full path name does not match match to just * the name of the file without any path. * * @param imageFiles * All of the image files. * @param imageFileName * Name of the image file. * @return * Matched image file, otherwise NULL. */ ImageFile* DisplayPropertiesImages::findImageFile(const AString& imageFileName) const { ImageFile* fullPathNameFile = NULL; ImageFile* nameOnlyFile = NULL; FileInformation fileInfo(imageFileName); const AString nameNoPath = fileInfo.getFileName(); const std::vector allImageFiles = m_parentBrain->getAllImagesFiles(); for (std::vector::const_iterator iter = allImageFiles.begin(); iter != allImageFiles.end(); iter++) { ImageFile* imageFile = *iter; if (imageFile->getFileName() == imageFileName) { fullPathNameFile = imageFile; break; } if (imageFile->getFileNameNoPath() == nameNoPath) { nameOnlyFile = imageFile; } } if (fullPathNameFile != NULL) { return fullPathNameFile; } if (nameOnlyFile == NULL) { CaretLogWarning("Unable to find image file: " + imageFileName); } return nameOnlyFile; } workbench-1.1.1/src/Brain/DisplayPropertiesImages.h000066400000000000000000000073361255417355300223220ustar00rootroot00000000000000#ifndef __DISPLAY_PROPERTIES_IMAGES_H__ #define __DISPLAY_PROPERTIES_IMAGES_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "BrainConstants.h" #include "DisplayGroupEnum.h" #include "DisplayProperties.h" namespace caret { class Brain; class ImageFile; class DisplayPropertiesImages : public DisplayProperties { public: DisplayPropertiesImages(Brain* parentBrain); virtual ~DisplayPropertiesImages(); virtual void reset(); virtual void update(); virtual void copyDisplayProperties(const int32_t sourceTabIndex, const int32_t targetTabIndex); bool isDisplayed(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const; void setDisplayed(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const bool displayStatus); DisplayGroupEnum::Enum getDisplayGroupForTab(const int32_t browserTabIndex) const; void setDisplayGroupForTab(const int32_t browserTabIndex, const DisplayGroupEnum::Enum displayGroup); ImageFile* getSelectedImageFile(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const; void setSelectedImageFile(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, ImageFile* imageFile); virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); // ADD_NEW_METHODS_HERE private: DisplayPropertiesImages(const DisplayPropertiesImages&); DisplayPropertiesImages& operator=(const DisplayPropertiesImages&); ImageFile* findImageFile(const AString& imageFileName) const; Brain* m_parentBrain; DisplayGroupEnum::Enum m_displayGroup[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; bool m_displayStatusInDisplayGroup[DisplayGroupEnum::NUMBER_OF_GROUPS]; bool m_displayStatusInTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; mutable ImageFile* m_imageFileInDisplayGroup[DisplayGroupEnum::NUMBER_OF_GROUPS]; mutable ImageFile* m_imageFileInTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; // ADD_NEW_MEMBERS_HERE }; #ifdef __DISPLAY_PROPERTIES_IMAGES_DECLARE__ // #endif // __DISPLAY_PROPERTIES_IMAGES_DECLARE__ } // namespace #endif //__DISPLAY_PROPERTIES_IMAGES_H__ workbench-1.1.1/src/Brain/DisplayPropertiesLabels.cxx000066400000000000000000000127041255417355300226650ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __DISPLAY_PROPERTIES_LABELS_DECLARE__ #include "DisplayPropertiesLabels.h" #include "SceneClassAssistant.h" #include "SceneAttributes.h" #include "SceneClass.h" #undef __DISPLAY_PROPERTIES_LABELS_DECLARE__ using namespace caret; /** * \class caret::DisplayPropertiesLabels * \brief Display properties for labels */ /** * Constructor. */ DisplayPropertiesLabels::DisplayPropertiesLabels() : DisplayProperties() { for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_displayGroup[i] = DisplayGroupEnum::getDefaultValue(); } m_sceneAssistant->addTabIndexedEnumeratedTypeArray("m_displayGroup", m_displayGroup); } /** * Destructor. */ DisplayPropertiesLabels::~DisplayPropertiesLabels() { } /** * Copy the border display properties from one tab to another. * @param sourceTabIndex * Index of tab from which properties are copied. * @param targetTabIndex * Index of tab to which properties are copied. */ void DisplayPropertiesLabels::copyDisplayProperties(const int32_t sourceTabIndex, const int32_t targetTabIndex) { const DisplayGroupEnum::Enum displayGroup = this->getDisplayGroupForTab(sourceTabIndex); this->setDisplayGroupForTab(targetTabIndex, displayGroup); } /** * Reset all settings to their defaults * and remove any data. */ void DisplayPropertiesLabels::reset() { } /** * Update due to changes in data. */ void DisplayPropertiesLabels::update() { } /** * Get the display group for a given browser tab. * @param browserTabIndex * Index of browser tab. */ DisplayGroupEnum::Enum DisplayPropertiesLabels::getDisplayGroupForTab(const int32_t browserTabIndex) const { CaretAssertArrayIndex(m_displayGroup, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, browserTabIndex); return m_displayGroup[browserTabIndex]; } /** * Set the display group for a given browser tab. * @param browserTabIndex * Index of browser tab. * @param displayGroup * New value for display group. */ void DisplayPropertiesLabels::setDisplayGroupForTab(const int32_t browserTabIndex, const DisplayGroupEnum::Enum displayGroup) { CaretAssertArrayIndex(m_displayGroup, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, browserTabIndex); m_displayGroup[browserTabIndex] = displayGroup; } /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ SceneClass* DisplayPropertiesLabels::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { const std::vector tabIndices = sceneAttributes->getIndicesOfTabsForSavingToScene(); SceneClass* sceneClass = new SceneClass(instanceName, "DisplayPropertiesLabels", 1); m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); switch (sceneAttributes->getSceneType()) { case SceneTypeEnum::SCENE_TYPE_FULL: break; case SceneTypeEnum::SCENE_TYPE_GENERIC: break; } return sceneClass; } /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * SceneClass containing the state that was previously * saved and should be restored. */ void DisplayPropertiesLabels::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); switch (sceneAttributes->getSceneType()) { case SceneTypeEnum::SCENE_TYPE_FULL: break; case SceneTypeEnum::SCENE_TYPE_GENERIC: break; } } workbench-1.1.1/src/Brain/DisplayPropertiesLabels.h000066400000000000000000000050461255417355300223130ustar00rootroot00000000000000#ifndef __DISPLAY_PROPERTIES_LABELS__H_ #define __DISPLAY_PROPERTIES_LABELS__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "BrainConstants.h" #include "DisplayGroupEnum.h" #include "DisplayProperties.h" namespace caret { class DisplayPropertiesLabels : public DisplayProperties { public: DisplayPropertiesLabels(); virtual ~DisplayPropertiesLabels(); virtual void reset(); virtual void update(); virtual void copyDisplayProperties(const int32_t sourceTabIndex, const int32_t targetTabIndex); virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); DisplayGroupEnum::Enum getDisplayGroupForTab(const int32_t browserTabIndex) const; void setDisplayGroupForTab(const int32_t browserTabIndex, const DisplayGroupEnum::Enum displayGroup); private: DisplayPropertiesLabels(const DisplayPropertiesLabels&); DisplayPropertiesLabels& operator=(const DisplayPropertiesLabels&); public: // ADD_NEW_METHODS_HERE private: // ADD_NEW_MEMBERS_HERE DisplayGroupEnum::Enum m_displayGroup[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; }; #ifdef __DISPLAY_PROPERTIES_LABELS_DECLARE__ // #endif // __DISPLAY_PROPERTIES_LABELS_DECLARE__ } // namespace #endif //__DISPLAY_PROPERTIES_LABELS__H_ workbench-1.1.1/src/Brain/DisplayPropertiesSurface.cxx000066400000000000000000000144111255417355300230500ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __DISPLAY_PROPERTIES_SURFACE_DECLARE__ #include "DisplayPropertiesSurface.h" #undef __DISPLAY_PROPERTIES_SURFACE_DECLARE__ #include "SceneClassAssistant.h" #include "SceneAttributes.h" #include "SceneClass.h" using namespace caret; /** * \class DisplayPropertiesSurface * \brief Display properties for surface drawing attributes. * * Display properties for surface drawing attributes. */ /** * Constructor. */ DisplayPropertiesSurface::DisplayPropertiesSurface() : DisplayProperties() { m_displayNormalVectors = false; m_linkSize = 2.0; m_nodeSize = 2.0; m_surfaceDrawingType = SurfaceDrawingTypeEnum::DRAW_AS_TRIANGLES; m_opacity = 1.0; m_sceneAssistant->add("m_displayNormalVectors", &m_displayNormalVectors); m_sceneAssistant->add("m_linkSize", &m_linkSize); m_sceneAssistant->add("m_nodeSize", &m_nodeSize); m_sceneAssistant->add("m_opacity", &m_opacity); m_sceneAssistant->add("m_surfaceDrawingType", &m_surfaceDrawingType); } /** * Destructor. */ DisplayPropertiesSurface::~DisplayPropertiesSurface() { } /** * Reset all settings to their defaults * and remove any data. */ void DisplayPropertiesSurface::reset() { } /** * Update due to changes in data. */ void DisplayPropertiesSurface::update() { } /** * Copy the display properties from one tab to another. * @param sourceTabIndex * Index of tab from which properties are copied. * @param targetTabIndex * Index of tab to which properties are copied. */ void DisplayPropertiesSurface::copyDisplayProperties(const int32_t /*sourceTabIndex*/, const int32_t /*targetTabIndex*/) { } /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ SceneClass* DisplayPropertiesSurface::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "DisplayPropertiesSurface", 1); m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); switch (sceneAttributes->getSceneType()) { case SceneTypeEnum::SCENE_TYPE_FULL: break; case SceneTypeEnum::SCENE_TYPE_GENERIC: break; } return sceneClass; } /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * SceneClass containing the state that was previously * saved and should be restored. */ void DisplayPropertiesSurface::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); switch (sceneAttributes->getSceneType()) { case SceneTypeEnum::SCENE_TYPE_FULL: break; case SceneTypeEnum::SCENE_TYPE_GENERIC: break; } } /** * @return The surface drawing type */ SurfaceDrawingTypeEnum::Enum DisplayPropertiesSurface::getSurfaceDrawingType() const { return m_surfaceDrawingType; } /** * Set the surface drawing type. * * @param surfaceDrawingType * The surface drawing type */ void DisplayPropertiesSurface::setSurfaceDrawingType(const SurfaceDrawingTypeEnum::Enum surfaceDrawingType) { m_surfaceDrawingType = surfaceDrawingType; } /** * @return Node size. */ float DisplayPropertiesSurface::getNodeSize() const { return m_nodeSize; } /** * Set node size * * @param nodeSize * New node size. */ void DisplayPropertiesSurface::setNodeSize(const float nodeSize) { m_nodeSize = nodeSize; } /** * @return Link size. */ float DisplayPropertiesSurface::getLinkSize() const { return m_linkSize; } /** * Set link size. * * @param linkSize * New link size. */ void DisplayPropertiesSurface::setLinkSize(const float linkSize) { m_linkSize = linkSize; } /** * @return Display normal vectors. */ bool DisplayPropertiesSurface::isDisplayNormalVectors() const { return m_displayNormalVectors; } /** * Set display normal vectors. * * @param displayNormalVectors * New value for display normal vectors. */ void DisplayPropertiesSurface::setDisplayNormalVectors(const bool displayNormalVectors) { m_displayNormalVectors = displayNormalVectors; } /** * @return The overall surface opacity. */ float DisplayPropertiesSurface::getOpacity() const { return m_opacity; } /** * Set the overall surface opacity. * * @param opacity * New value for opacity. */ void DisplayPropertiesSurface::setOpacity(const float opacity) { m_opacity = opacity; } workbench-1.1.1/src/Brain/DisplayPropertiesSurface.h000066400000000000000000000055151255417355300225020ustar00rootroot00000000000000#ifndef __DISPLAY_PROPERTIES_SURFACE_H_ #define __DISPLAY_PROPERTIES_SURFACE_H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "DisplayProperties.h" #include "SurfaceDrawingTypeEnum.h" namespace caret { class Surface; class DisplayPropertiesSurface : public DisplayProperties { public: DisplayPropertiesSurface(); virtual ~DisplayPropertiesSurface(); void reset(); void update(); virtual void copyDisplayProperties(const int32_t sourceTabIndex, const int32_t targetTabIndex); virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); SurfaceDrawingTypeEnum::Enum getSurfaceDrawingType() const; void setSurfaceDrawingType(const SurfaceDrawingTypeEnum::Enum surfaceDrawingType); float getNodeSize() const; void setNodeSize(const float nodeSize); float getLinkSize() const; void setLinkSize(const float linkSize); bool isDisplayNormalVectors() const; void setDisplayNormalVectors(const bool displayNormalVectors); float getOpacity() const; void setOpacity(const float opacity); private: DisplayPropertiesSurface(const DisplayPropertiesSurface&); DisplayPropertiesSurface& operator=(const DisplayPropertiesSurface&); float m_nodeSize; float m_linkSize; bool m_displayNormalVectors; SurfaceDrawingTypeEnum::Enum m_surfaceDrawingType; float m_opacity; }; #ifdef __DISPLAY_PROPERTIES_SURFACE_DECLARE__ #endif // __DISPLAY_PROPERTIES_SURFACE_DECLARE__ } // namespace #endif //__DISPLAY_PROPERTIES_SURFACE_H_ workbench-1.1.1/src/Brain/DisplayPropertiesVolume.cxx000066400000000000000000000072031255417355300227300ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __DISPLAY_PROPERTIES_VOLUME_DECLARE__ #include "DisplayPropertiesVolume.h" #undef __DISPLAY_PROPERTIES_VOLUME_DECLARE__ #include "SceneAttributes.h" #include "SceneClass.h" using namespace caret; /** * \class DisplayPropertiesVolume * \brief Display properties for volume slices. * * Display properties for volume slices. */ /** * Constructor. */ DisplayPropertiesVolume::DisplayPropertiesVolume() : DisplayProperties() { } /** * Destructor. */ DisplayPropertiesVolume::~DisplayPropertiesVolume() { } /** * Reset all settings to their defaults * and remove any data. */ void DisplayPropertiesVolume::reset() { } /** * Update due to changes in data. */ void DisplayPropertiesVolume::update() { } /** * Copy the display properties from one tab to another. * @param sourceTabIndex * Index of tab from which properties are copied. * @param targetTabIndex * Index of tab to which properties are copied. */ void DisplayPropertiesVolume::copyDisplayProperties(const int32_t /*sourceTabIndex*/, const int32_t /*targetTabIndex*/) { } /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ SceneClass* DisplayPropertiesVolume::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "DisplayPropertiesVolume", 1); switch (sceneAttributes->getSceneType()) { case SceneTypeEnum::SCENE_TYPE_FULL: break; case SceneTypeEnum::SCENE_TYPE_GENERIC: break; } return sceneClass; } /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * SceneClass containing the state that was previously * saved and should be restored. */ void DisplayPropertiesVolume::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } switch (sceneAttributes->getSceneType()) { case SceneTypeEnum::SCENE_TYPE_FULL: break; case SceneTypeEnum::SCENE_TYPE_GENERIC: break; } } workbench-1.1.1/src/Brain/DisplayPropertiesVolume.h000066400000000000000000000037641255417355300223650ustar00rootroot00000000000000#ifndef __DISPLAY_PROPERTIES_VOLUME__H_ #define __DISPLAY_PROPERTIES_VOLUME__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "DisplayProperties.h" namespace caret { class Surface; class DisplayPropertiesVolume : public DisplayProperties { public: DisplayPropertiesVolume(); virtual ~DisplayPropertiesVolume(); void reset(); void update(); virtual void copyDisplayProperties(const int32_t sourceTabIndex, const int32_t targetTabIndex); virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private: DisplayPropertiesVolume(const DisplayPropertiesVolume&); DisplayPropertiesVolume& operator=(const DisplayPropertiesVolume&); }; #ifdef __DISPLAY_PROPERTIES_VOLUME_DECLARE__ #endif // __DISPLAY_PROPERTIES_VOLUME_DECLARE__ } // namespace #endif //__DISPLAY_PROPERTIES_VOLUME__H_ workbench-1.1.1/src/Brain/DummyFontTextRenderer.cxx000066400000000000000000000106551255417355300223410ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __DUMMY_FONT_TEXT_RENDERER_DECLARE__ #include "DummyFontTextRenderer.h" #undef __DUMMY_FONT_TEXT_RENDERER_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::DummyFontTextRenderer * \brief A dummy font text renderer used when no valid text renderer is available. * \ingroup Brain * * This "dummy" text renderer is used when a valid text renderer (Qt, FTGL) is * not available. Without this dummy text renderer, any graphics code that * draws text would need to check that the text renderer is valid. This * dummy text renderer does nothing other than prevent the software from * crashing. */ /** * Constructor. */ DummyFontTextRenderer::DummyFontTextRenderer() : BrainOpenGLTextRenderInterface() { } /** * Destructor. */ DummyFontTextRenderer::~DummyFontTextRenderer() { } /** * @return The font system is valid. */ bool DummyFontTextRenderer::isValid() const { return true; } /** * Draw text at the given window coordinates. * * @param viewport * The current viewport. * @param windowX * X-coordinate in the window of first text character * using the 'alignment' * @param windowY * Y-coordinate in the window at which bottom of text is placed. * @param text * Text that is to be drawn. * @param alignment * Alignment of text * @param textStyle * Style of the text. * @param fontHeight * Height of the text. */ void DummyFontTextRenderer::drawTextAtWindowCoords(const int* /*viewport[4]*/, const int /*windowX*/, const int /*windowY*/, const QString& /*text*/, const TextAlignmentX /*alignmentX*/, const TextAlignmentY /*alignmentY*/, const TextStyle /*textStyle*/, const int /*fontHeight*/) { } /** * Get the bounds of the text (in pixels) using the given text * attributes. * * @param widthOut * Output containing width of text characters. * @param heightOut * Output containing height of text characters. * @param text * Text that is to be drawn. * @param textStyle * Style of the text. * @param fontHeight * Height of the text. */ void DummyFontTextRenderer::getTextBoundsInPixels(int32_t& /*widthOut*/, int32_t& /*heightOut*/, const QString& /*text*/, const TextStyle /*textStyle*/, const int /*fontHeight*/) { } /** * Draw text at the given model coordinates. * * @param modelX * X-coordinate in model space of first text character * @param modelY * Y-coordinate in model space. * @param modelZ * Z-coordinate in model space. * @param text * Text that is to be drawn. * @param textStyle * Style of the text. * @param fontHeight * Height of the text. */ void DummyFontTextRenderer::drawTextAtModelCoords(const double /*modelX*/, const double /*modelY*/, const double /*modelZ*/, const QString& /*text*/, const TextStyle /*textStyle*/, const int /*fontHeight*/) { } /** * @return Name of the text renderer. */ AString DummyFontTextRenderer::getName() const { return "Dummy (No OpenGL font system)"; } workbench-1.1.1/src/Brain/DummyFontTextRenderer.h000066400000000000000000000054131255417355300217620ustar00rootroot00000000000000#ifndef __DUMMY_FONT_TEXT_RENDERER_H__ #define __DUMMY_FONT_TEXT_RENDERER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "BrainOpenGLTextRenderInterface.h" namespace caret { class DummyFontTextRenderer : public BrainOpenGLTextRenderInterface { public: DummyFontTextRenderer(); virtual ~DummyFontTextRenderer(); bool isValid() const; void drawTextAtWindowCoords(const int viewport[4], const int windowX, const int windowY, const QString& text, const TextAlignmentX alignmentX, const TextAlignmentY alignmentY, const TextStyle textStyle, const int fontHeight); void drawTextAtModelCoords(const double modelX, const double modelY, const double modelZ, const QString& text, const TextStyle textStyle, const int fontHeight); void getTextBoundsInPixels(int32_t& widthOut, int32_t& heightOut, const QString& text, const TextStyle textStyle, const int fontHeight); virtual AString getName() const; // ADD_NEW_METHODS_HERE private: DummyFontTextRenderer(const DummyFontTextRenderer&); DummyFontTextRenderer& operator=(const DummyFontTextRenderer&); // ADD_NEW_MEMBERS_HERE }; #ifdef __DUMMY_FONT_TEXT_RENDERER_DECLARE__ // #endif // __DUMMY_FONT_TEXT_RENDERER_DECLARE__ } // namespace #endif //__DUMMY_FONT_TEXT_RENDERER_H__ workbench-1.1.1/src/Brain/EventBrainReset.cxx000066400000000000000000000026741255417355300211250ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __EVENT_BRAIN_RESET_DECLARE__ #include "EventBrainReset.h" #undef __EVENT_BRAIN_RESET_DECLARE__ #include "CaretAssert.h" #include "EventTypeEnum.h" using namespace caret; /** * \class caret::EventBrainReset * \brief Event issued when brain is reset (new spec or scene loaded). * \ingroup Brain */ /** * Constructor. */ EventBrainReset::EventBrainReset(Brain* brain) : Event(EventTypeEnum::EVENT_BRAIN_RESET), m_brain(brain) { } /** * Destructor. */ EventBrainReset::~EventBrainReset() { } /** * @return Brain that was reset. */ Brain* EventBrainReset::getBrain() const { return m_brain; } workbench-1.1.1/src/Brain/EventBrainReset.h000066400000000000000000000030421255417355300205400ustar00rootroot00000000000000#ifndef __EVENT_BRAIN_RESET_H__ #define __EVENT_BRAIN_RESET_H__ /*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "Event.h" namespace caret { class Brain; class EventBrainReset : public Event { public: EventBrainReset(Brain* brain); virtual ~EventBrainReset(); Brain* getBrain() const; // ADD_NEW_METHODS_HERE private: EventBrainReset(const EventBrainReset&); EventBrainReset& operator=(const EventBrainReset&); Brain* m_brain; // ADD_NEW_MEMBERS_HERE }; #ifdef __EVENT_BRAIN_RESET_DECLARE__ // #endif // __EVENT_BRAIN_RESET_DECLARE__ } // namespace #endif //__EVENT_BRAIN_RESET_H__ workbench-1.1.1/src/Brain/EventBrainStructureGetAll.cxx000066400000000000000000000053551255417355300231330ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __EVENT_BRAIN_STRUCTURE_GET_ALL_DECLARE__ #include "EventBrainStructureGetAll.h" #undef __EVENT_BRAIN_STRUCTURE_GET_ALL_DECLARE__ #include "BrainStructure.h" #include "CaretAssert.h" #include "EventTypeEnum.h" using namespace caret; /** * \class caret::EventBrainStructureGetAll * \brief Get all brain structures. * \ingroup Brain */ /** * Constructor. */ EventBrainStructureGetAll::EventBrainStructureGetAll() : Event(EventTypeEnum::EVENT_BRAIN_STRUCTURE_GET_ALL) { } /** * Destructor. */ EventBrainStructureGetAll::~EventBrainStructureGetAll() { } /** * Add a brain structure. * * @param brainStructure * Brain structure that is added. */ void EventBrainStructureGetAll::addBrainStructure(BrainStructure* brainStructure) { m_brainStructures.push_back(brainStructure); } /** * @return Number of brain structures that were found. */ int32_t EventBrainStructureGetAll::getNumberOfBrainStructures() const { return m_brainStructures.size(); } /** * Get the brain structure at the given index. * * @param indx * Index of the brain structure. * @return * Brain structure at the given index. */ BrainStructure* EventBrainStructureGetAll::getBrainStructureByIndex(const int32_t indx) { CaretAssertVectorIndex(m_brainStructures, indx); return m_brainStructures[indx]; } /** * Get the brain structure of the specified type. * * @param structure * Type of structure. * @return * Brain structure with the given structure type or NULL if not found. */ BrainStructure* EventBrainStructureGetAll::getBrainStructureByStructure(const StructureEnum::Enum structure) { for (std::vector::iterator iter = m_brainStructures.begin(); iter != m_brainStructures.end(); iter++) { BrainStructure* bs = *iter; if (bs->getStructure() == structure) { return bs; } } return NULL; } workbench-1.1.1/src/Brain/EventBrainStructureGetAll.h000066400000000000000000000040241255417355300225500ustar00rootroot00000000000000#ifndef __EVENT_BRAIN_STRUCTURE_GET_ALL_H__ #define __EVENT_BRAIN_STRUCTURE_GET_ALL_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "Event.h" #include "StructureEnum.h" namespace caret { class BrainStructure; class EventBrainStructureGetAll : public Event { public: EventBrainStructureGetAll(); virtual ~EventBrainStructureGetAll(); void addBrainStructure(BrainStructure* brainStructure); int32_t getNumberOfBrainStructures() const; BrainStructure* getBrainStructureByIndex(const int32_t indx); BrainStructure* getBrainStructureByStructure(const StructureEnum::Enum structure); private: EventBrainStructureGetAll(const EventBrainStructureGetAll&); EventBrainStructureGetAll& operator=(const EventBrainStructureGetAll&); public: // ADD_NEW_METHODS_HERE private: std::vector m_brainStructures; // ADD_NEW_MEMBERS_HERE }; #ifdef __EVENT_BRAIN_STRUCTURE_GET_ALL_DECLARE__ // #endif // __EVENT_BRAIN_STRUCTURE_GET_ALL_DECLARE__ } // namespace #endif //__EVENT_BRAIN_STRUCTURE_GET_ALL_H__ workbench-1.1.1/src/Brain/EventBrowserTabDelete.cxx000066400000000000000000000033741255417355300222620ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretAssert.h" #include "BrowserTabContent.h" #include "EventBrowserTabDelete.h" using namespace caret; /** * Constructor. */ EventBrowserTabDelete::EventBrowserTabDelete(BrowserTabContent* browserTab) : Event(EventTypeEnum::EVENT_BROWSER_TAB_DELETE) { CaretAssert(browserTab); m_browserTab = browserTab; m_browserTabIndex = browserTab->getTabNumber(); } /** * Destructor. */ EventBrowserTabDelete::~EventBrowserTabDelete() { } /** * Get the browser tab that is to be deleted. * Note that this may point to a browser tab that * has been deleted and using the pointer in this * case could be a disaster. * * @return * Pointer to browser tab that is to be deleted. */ BrowserTabContent* EventBrowserTabDelete::getBrowserTab() { return m_browserTab; } /** * @return Index of browser tab being deleted. */ int32_t EventBrowserTabDelete::getBrowserTabIndex() const { return m_browserTabIndex; } workbench-1.1.1/src/Brain/EventBrowserTabDelete.h000066400000000000000000000031761255417355300217070ustar00rootroot00000000000000#ifndef __EVENT_BROWSER_TAB_DELETE_H__ #define __EVENT_BROWSER_TAB_DELETE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "Event.h" namespace caret { class BrowserTabContent; /// Event for deleting a browser tab class EventBrowserTabDelete : public Event { public: EventBrowserTabDelete(BrowserTabContent* browserTab); virtual ~EventBrowserTabDelete(); BrowserTabContent* getBrowserTab(); int32_t getBrowserTabIndex() const; private: EventBrowserTabDelete(const EventBrowserTabDelete&); EventBrowserTabDelete& operator=(const EventBrowserTabDelete&); BrowserTabContent* m_browserTab; int32_t m_browserTabIndex; }; } // namespace #endif // __EVENT_BROWSER_TAB_DELETE_H__ workbench-1.1.1/src/Brain/EventBrowserTabGet.cxx000066400000000000000000000032721255417355300215740ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "EventBrowserTabGet.h" using namespace caret; /** * Constructor. */ EventBrowserTabGet::EventBrowserTabGet(const int32_t tabNumber) : Event(EventTypeEnum::EVENT_BROWSER_TAB_GET) { this->tabNumber = tabNumber; this->browserTab = NULL; } /** * Destructor. */ EventBrowserTabGet::~EventBrowserTabGet() { } /** * Get the browser tab that is to be deleted. * * @return * Pointer to browser tab that is to be deleted. */ BrowserTabContent* EventBrowserTabGet::getBrowserTab() { return this->browserTab; } /** * Set the browser tab for the requested tab number. * @param browserTab The tab. */ void EventBrowserTabGet::setBrowserTab(BrowserTabContent* browserTab) { this->browserTab = browserTab; } /** * @return Returns the requested tab number. */ int32_t EventBrowserTabGet::getTabNumber() const { return this->tabNumber; } workbench-1.1.1/src/Brain/EventBrowserTabGet.h000066400000000000000000000032211255417355300212130ustar00rootroot00000000000000#ifndef __EVENT_BROWSER_TAB_GET_H__ #define __EVENT_BROWSER_TAB_GET_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "Event.h" namespace caret { class BrowserTabContent; /// Get a browser tab by its tab number class EventBrowserTabGet : public Event { public: EventBrowserTabGet(const int32_t tabNumber); virtual ~EventBrowserTabGet(); BrowserTabContent* getBrowserTab(); void setBrowserTab(BrowserTabContent* browserTab); int32_t getTabNumber() const; private: EventBrowserTabGet(const EventBrowserTabGet&); EventBrowserTabGet& operator=(const EventBrowserTabGet&); BrowserTabContent* browserTab; int32_t tabNumber; }; } // namespace #endif // __EVENT_BROWSER_TAB_GET_H__ workbench-1.1.1/src/Brain/EventBrowserTabGetAll.cxx000066400000000000000000000046101255417355300222220ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "BrowserTabContent.h" #include "CaretAssert.h" #include "EventBrowserTabGetAll.h" using namespace caret; /** * Constructor. */ EventBrowserTabGetAll::EventBrowserTabGetAll() : Event(EventTypeEnum::EVENT_BROWSER_TAB_GET_ALL) { } /** * Destructor. */ EventBrowserTabGetAll::~EventBrowserTabGetAll() { } /** * @return The number of browser tabs. */ int32_t EventBrowserTabGetAll::getNumberOfBrowserTabs() const { return this->allBrowserTabs.size(); } /** * Get the browser tab at the given index. * @param indx * Index of the browser tab. * @return Browser tab at the given index. */ BrowserTabContent* EventBrowserTabGetAll::getBrowserTab(const int32_t indx) { CaretAssertVectorIndex(this->allBrowserTabs, indx); return this->allBrowserTabs[indx]; } /** * Add a browser tab. * @param browserTab * Tab that is added. */ void EventBrowserTabGetAll::addBrowserTab(BrowserTabContent* browserTab) { this->allBrowserTabs.push_back(browserTab); } /** * @return All browser tabs. */ std::vector EventBrowserTabGetAll::getAllBrowserTabs() const { return this->allBrowserTabs; } /** * @return The indices of all browser tabs. */ std::vector EventBrowserTabGetAll::getBrowserTabIndices() const { std::vector tabIndices; for (std::vector::const_iterator iter = allBrowserTabs.begin(); iter != allBrowserTabs.end(); iter++) { const BrowserTabContent* btc = *iter; tabIndices.push_back(btc->getTabNumber()); } return tabIndices; } workbench-1.1.1/src/Brain/EventBrowserTabGetAll.h000066400000000000000000000034351255417355300216530ustar00rootroot00000000000000#ifndef __EVENT_BROWSER_TAB_GET_ALL_H__ #define __EVENT_BROWSER_TAB_GET_ALL_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "Event.h" namespace caret { class BrowserTabContent; /// Get all browser tabs class EventBrowserTabGetAll : public Event { public: EventBrowserTabGetAll(); virtual ~EventBrowserTabGetAll(); int32_t getNumberOfBrowserTabs() const; BrowserTabContent* getBrowserTab(const int32_t indx); std::vector getAllBrowserTabs() const; void addBrowserTab(BrowserTabContent* browserTab); std::vector getBrowserTabIndices() const; private: EventBrowserTabGetAll(const EventBrowserTabGetAll&); EventBrowserTabGetAll& operator=(const EventBrowserTabGetAll&); std::vector allBrowserTabs; }; } // namespace #endif // __EVENT_BROWSER_TAB_GET_ALL_H__ workbench-1.1.1/src/Brain/EventBrowserTabNew.cxx000066400000000000000000000031011255417355300215750ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "EventBrowserTabNew.h" using namespace caret; /** * Constructor. */ EventBrowserTabNew::EventBrowserTabNew() : Event(EventTypeEnum::EVENT_BROWSER_TAB_NEW) { this->browserTab = NULL; } /** * Destructor. */ EventBrowserTabNew::~EventBrowserTabNew() { } /** * Get the browser tab that was created. * * @return * Pointer to browser tab that was created or * NULL if a browser tab could not be created. */ BrowserTabContent* EventBrowserTabNew::getBrowserTab() { return this->browserTab; } /** * Set the created browser tab. * * @param browserTab * Browser tab that was created. */ void EventBrowserTabNew::setBrowserTab(BrowserTabContent* browserTab) { this->browserTab = browserTab; } workbench-1.1.1/src/Brain/EventBrowserTabNew.h000066400000000000000000000030501255417355300212250ustar00rootroot00000000000000#ifndef __EVENT_BROWSER_TAB_NEW_H__ #define __EVENT_BROWSER_TAB_NEW_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "Event.h" namespace caret { class BrowserTabContent; /// Event for creating a new browser tab class EventBrowserTabNew : public Event { public: EventBrowserTabNew(); virtual ~EventBrowserTabNew(); BrowserTabContent* getBrowserTab(); void setBrowserTab(BrowserTabContent* browserTab); private: EventBrowserTabNew(const EventBrowserTabNew&); EventBrowserTabNew& operator=(const EventBrowserTabNew&); BrowserTabContent* browserTab; }; } // namespace #endif // __EVENT_BROWSER_TAB_NEW_H__ workbench-1.1.1/src/Brain/EventCaretMappableDataFileMapsViewedInOverlays.cxx000066400000000000000000000046001255417355300271510ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __EVENT_CARET_MAPPABLE_DATA_FILE_MAPS_VIEWED_IN_OVERLAYS_DECLARE__ #include "EventCaretMappableDataFileMapsViewedInOverlays.h" #undef __EVENT_CARET_MAPPABLE_DATA_FILE_MAPS_VIEWED_IN_OVERLAYS_DECLARE__ #include "CaretAssert.h" #include "EventTypeEnum.h" using namespace caret; /** * \class caret::EventCaretMappableDataFileMapsViewedInOverlays * \brief Get maps viewed as an overlay for the given data file type * \ingroup Brain */ /** * Constructor. */ EventCaretMappableDataFileMapsViewedInOverlays::EventCaretMappableDataFileMapsViewedInOverlays(const CaretMappableDataFile* caretMappableDataFile) : Event(EventTypeEnum::EVENT_CARET_MAPPABLE_DATA_FILE_MAPS_VIEWED_IN_OVERLAYS), m_caretMappableDataFile(caretMappableDataFile) { CaretAssert(caretMappableDataFile); } /** * Destructor. */ EventCaretMappableDataFileMapsViewedInOverlays::~EventCaretMappableDataFileMapsViewedInOverlays() { } /** * @return The type of data file for which maps viewed as overlay are desired. */ const CaretMappableDataFile* EventCaretMappableDataFileMapsViewedInOverlays::getCaretMappableDataFile() const { return m_caretMappableDataFile; } /** * Add map index for caret mappable data file. * * @param mapIndex * Selected map index for data file. */ void EventCaretMappableDataFileMapsViewedInOverlays::addMapIndex(const int32_t mapIndex) { m_mapIndices.insert(mapIndex); } /** * @return Displayed as overlay map indices for file. */ std::set EventCaretMappableDataFileMapsViewedInOverlays::getSelectedMapIndices() const { return m_mapIndices; } workbench-1.1.1/src/Brain/EventCaretMappableDataFileMapsViewedInOverlays.h000066400000000000000000000043661255417355300266070ustar00rootroot00000000000000#ifndef __EVENT_CARET_MAPPABLE_DATA_FILE_MAPS_VIEWED_IN_OVERLAYS_H__ #define __EVENT_CARET_MAPPABLE_DATA_FILE_MAPS_VIEWED_IN_OVERLAYS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "Event.h" namespace caret { class CaretMappableDataFile; class EventCaretMappableDataFileMapsViewedInOverlays : public Event { public: EventCaretMappableDataFileMapsViewedInOverlays(const CaretMappableDataFile* caretMappableDataFile); virtual ~EventCaretMappableDataFileMapsViewedInOverlays(); const CaretMappableDataFile* getCaretMappableDataFile() const; void addMapIndex(const int32_t mapIndex); std::set getSelectedMapIndices() const; // ADD_NEW_METHODS_HERE private: EventCaretMappableDataFileMapsViewedInOverlays(const EventCaretMappableDataFileMapsViewedInOverlays&); EventCaretMappableDataFileMapsViewedInOverlays& operator=(const EventCaretMappableDataFileMapsViewedInOverlays&); // ADD_NEW_MEMBERS_HERE const CaretMappableDataFile* m_caretMappableDataFile; std::set m_mapIndices; }; #ifdef __EVENT_CARET_MAPPABLE_DATA_FILE_MAPS_VIEWED_IN_OVERLAYS_DECLARE__ // #endif // __EVENT_CARET_MAPPABLE_DATA_FILE_MAPS_VIEWED_IN_OVERLAYS_DECLARE__ } // namespace #endif //__EVENT_CARET_MAPPABLE_DATA_FILE_MAPS_VIEWED_IN_OVERLAYS_H__ workbench-1.1.1/src/Brain/EventDataFileAdd.cxx000066400000000000000000000030221255417355300211350ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __EVENT_DATA_FILE_ADD_DECLARE__ #include "EventDataFileAdd.h" #undef __EVENT_DATA_FILE_ADD_DECLARE__ #include "CaretAssert.h" #include "EventTypeEnum.h" using namespace caret; /** * \class caret::EventDataFileAdd * \brief Add a data file to the Brain. * \ingroup Brain */ /** * Constructor. */ EventDataFileAdd::EventDataFileAdd(CaretDataFile* caretDataFile) : Event(EventTypeEnum::EVENT_DATA_FILE_ADD) { CaretAssert(caretDataFile); m_caretDataFile = caretDataFile; } /** * Destructor. */ EventDataFileAdd::~EventDataFileAdd() { } /** * @return Caret data file that is added to the brain. */ CaretDataFile* EventDataFileAdd::getCaretDataFile() { return m_caretDataFile; } workbench-1.1.1/src/Brain/EventDataFileAdd.h000066400000000000000000000031651255417355300205720ustar00rootroot00000000000000#ifndef __EVENT_DATA_FILE_ADD_H__ #define __EVENT_DATA_FILE_ADD_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "Event.h" namespace caret { class CaretDataFile; class EventDataFileAdd : public Event { public: EventDataFileAdd(CaretDataFile* caretDataFile); virtual ~EventDataFileAdd(); CaretDataFile* getCaretDataFile(); // ADD_NEW_METHODS_HERE private: EventDataFileAdd(const EventDataFileAdd&); EventDataFileAdd& operator=(const EventDataFileAdd&); CaretDataFile* m_caretDataFile; // ADD_NEW_MEMBERS_HERE }; #ifdef __EVENT_DATA_FILE_ADD_DECLARE__ // #endif // __EVENT_DATA_FILE_ADD_DECLARE__ } // namespace #endif //__EVENT_DATA_FILE_ADD_H__ workbench-1.1.1/src/Brain/EventDataFileDelete.cxx000066400000000000000000000031701255417355300216530ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __EVENT_DATA_FILE_DELETE_DECLARE__ #include "EventDataFileDelete.h" #undef __EVENT_DATA_FILE_DELETE_DECLARE__ #include "CaretAssert.h" #include "EventTypeEnum.h" using namespace caret; /** * \class caret::EventDataFileDelete * \brief Event for deleting a data file from the brain. * \ingroup Brain */ /** * Constructor. * * @param caretDataFile * File that is deleted. */ EventDataFileDelete::EventDataFileDelete(CaretDataFile* caretDataFile) : Event(EventTypeEnum::EVENT_DATA_FILE_DELETE) { CaretAssert(caretDataFile); m_caretDataFile = caretDataFile; } /** * Destructor. */ EventDataFileDelete::~EventDataFileDelete() { } /** * @return Caret data file that is added to the brain. */ CaretDataFile* EventDataFileDelete::getCaretDataFile() { return m_caretDataFile; } workbench-1.1.1/src/Brain/EventDataFileDelete.h000066400000000000000000000032251255417355300213010ustar00rootroot00000000000000#ifndef __EVENT_DATA_FILE_DELETE_H__ #define __EVENT_DATA_FILE_DELETE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "Event.h" namespace caret { class CaretDataFile; class EventDataFileDelete : public Event { public: EventDataFileDelete(CaretDataFile* dataFile); virtual ~EventDataFileDelete(); CaretDataFile* getCaretDataFile(); // ADD_NEW_METHODS_HERE private: EventDataFileDelete(const EventDataFileDelete&); EventDataFileDelete& operator=(const EventDataFileDelete&); CaretDataFile* m_caretDataFile; // ADD_NEW_MEMBERS_HERE }; #ifdef __EVENT_DATA_FILE_DELETE_DECLARE__ // #endif // __EVENT_DATA_FILE_DELETE_DECLARE__ } // namespace #endif //__EVENT_DATA_FILE_DELETE_H__ workbench-1.1.1/src/Brain/EventDataFileRead.cxx000066400000000000000000000166001255417355300213260ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretAssert.h" #include "EventDataFileRead.h" using namespace caret; /** * Constructor. * * @param loadIntoBrain * Brain into which file is loaded. */ EventDataFileRead::EventDataFileRead(Brain* loadIntoBrain) : Event(EventTypeEnum::EVENT_DATA_FILE_READ) { this->loadIntoBrain = loadIntoBrain; this->username = ""; this->password = ""; CaretAssert(this->loadIntoBrain); } /** * Destructor. */ EventDataFileRead::~EventDataFileRead() { } /** * Add a data file for reading. * * @param dataFileType * Type of data file. * @param dataFileName * Name of the data file. */ void EventDataFileRead::addDataFile(const DataFileTypeEnum::Enum dataFileType, const AString& dataFileName) { CaretAssert(dataFileType != DataFileTypeEnum::UNKNOWN); CaretAssert(dataFileName.isEmpty() == false); m_dataFiles.push_back(FileData(StructureEnum::INVALID, dataFileType, dataFileName, false)); } /** * Add a data file for reading. * * @param structure * Structure for file if not present in file. * @param dataFileType * Type of data file. * @param dataFileName * Name of the data file. */ void EventDataFileRead::addDataFile(const StructureEnum::Enum structure, const DataFileTypeEnum::Enum dataFileType, const AString& dataFileName) { //CaretAssert(structure != StructureEnum::INVALID); CaretAssert(dataFileType != DataFileTypeEnum::UNKNOWN); CaretAssert(dataFileName.isEmpty() == false); m_dataFiles.push_back(FileData(structure, dataFileType, dataFileName, false)); } /** * @return Number of data files to read. */ int32_t EventDataFileRead::getNumberOfDataFilesToRead() const { return m_dataFiles.size(); } /** * Get the name of the data file that is to be loaded. * * @param dataFileIndex * Index of the data file. * @return Name of data file to load. */ AString EventDataFileRead::getDataFileName(const int32_t dataFileIndex) const { CaretAssertVectorIndex(m_dataFiles, dataFileIndex); return m_dataFiles[dataFileIndex].m_dataFileName; } /** * Get the type of data file for loading. * * @param dataFileIndex * Index of the data file. * @return Type of file for loading. */ DataFileTypeEnum::Enum EventDataFileRead::getDataFileType(const int32_t dataFileIndex) const { CaretAssertVectorIndex(m_dataFiles, dataFileIndex); return m_dataFiles[dataFileIndex].m_dataFileType; } /** * Get brain into which file is loaded. * @return Brain into which file is loaded. */ Brain* EventDataFileRead::getLoadIntoBrain() { return this->loadIntoBrain; } /** * @param dataFileIndex * Index of the data file. * @return The structure associated with the data file. */ StructureEnum::Enum EventDataFileRead::getStructure(const int32_t dataFileIndex) const { CaretAssertVectorIndex(m_dataFiles, dataFileIndex); return m_dataFiles[dataFileIndex].m_structure; } /** * Get the error message for a file. * @param dataFileIndex * Index of the file. * @return * Error message. */ AString EventDataFileRead::getFileErrorMessage(const int32_t dataFileIndex) const { CaretAssertVectorIndex(m_dataFiles, dataFileIndex); return m_dataFiles[dataFileIndex].m_errorMessage; } /** * Set the error message for a file. * @param dataFileIndex * Index of the file. * @param errorMessage * Error message. */ void EventDataFileRead::setFileErrorMessage(const int32_t dataFileIndex, const AString& errorMessage) { CaretAssertVectorIndex(m_dataFiles, dataFileIndex); m_dataFiles[dataFileIndex].m_errorMessage = errorMessage; } /** * Was there an error reading the file at the given index. * @param dataFileIndex * Index of the file. * @return * true if there was an error, otherwise false. */ bool EventDataFileRead::isFileError(const int32_t dataFileIndex) const { CaretAssertVectorIndex(m_dataFiles, dataFileIndex); return (m_dataFiles[dataFileIndex].m_errorMessage.isEmpty() == false); } /** * @param dataFileIndex * Index of the data file. * @return True if the file could not be read due * to an invalid structure. */ bool EventDataFileRead::isFileErrorInvalidStructure(const int32_t dataFileIndex) const { CaretAssertVectorIndex(m_dataFiles, dataFileIndex); return m_dataFiles[dataFileIndex].m_invalidStructureError; } /** * Set the invalid structure status. * @param dataFileIndex * Index of the data file. * @param status * New invalid structure status (true if invalid). */ void EventDataFileRead::setFileErrorInvalidStructure(const int32_t dataFileIndex, const bool status) { CaretAssertVectorIndex(m_dataFiles, dataFileIndex); m_dataFiles[dataFileIndex].m_invalidStructureError = status; } /** * @return File that was read for the given index. */ CaretDataFile* EventDataFileRead::getDataFileRead(const int32_t dataFileIndex) { CaretAssertVectorIndex(m_dataFiles, dataFileIndex); return m_dataFiles[dataFileIndex].m_caretDataFileThatWasRead; } /** * Set the file that was read. * * @param dataFileIndex * Index of the file. * @param caretDataFile * Pointer to file that was read for given index. */ void EventDataFileRead::setDataFileRead(const int32_t dataFileIndex, CaretDataFile* caretDataFile) { CaretAssertVectorIndex(m_dataFiles, dataFileIndex); m_dataFiles[dataFileIndex].m_caretDataFileThatWasRead = caretDataFile; } /** * @return The username. */ AString EventDataFileRead::getUsername() const { return this->username; } /** * @return The password. */ AString EventDataFileRead::getPassword() const { return this->password; } /** * Set the username and password. * * @param username * Name of user account. * @param password * Password of user account. */ void EventDataFileRead::setUsernameAndPassword(const AString& username, const AString& password) { this->username = username; this->password = password; } /** * @param dataFileIndex * Index of the data file. * @return After file is read, mark it as modified. */ bool EventDataFileRead::isFileToBeMarkedModified(const int32_t dataFileIndex) const { CaretAssertVectorIndex(m_dataFiles, dataFileIndex); return m_dataFiles[dataFileIndex].m_markFileAsModified; } workbench-1.1.1/src/Brain/EventDataFileRead.h000066400000000000000000000102761255417355300207560ustar00rootroot00000000000000#ifndef __EVENT_DATA_FILE_READ_H__ #define __EVENT_DATA_FILE_READ_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "DataFileTypeEnum.h" #include "Event.h" #include "StructureEnum.h" namespace caret { class Brain; class CaretDataFile; /** * Event for reading one or more data files. */ class EventDataFileRead : public Event { public: EventDataFileRead(Brain* loadIntoBrain); virtual ~EventDataFileRead(); void addDataFile(const DataFileTypeEnum::Enum dataFileType, const AString& dataFileName); void addDataFile(const StructureEnum::Enum structure, const DataFileTypeEnum::Enum dataFileType, const AString& dataFileName); int32_t getNumberOfDataFilesToRead() const; AString getDataFileName(const int32_t dataFileIndex) const; DataFileTypeEnum::Enum getDataFileType(const int32_t dataFileIndex) const; Brain* getLoadIntoBrain(); StructureEnum::Enum getStructure(const int32_t dataFileIndex) const; AString getFileErrorMessage(const int32_t dataFileIndex) const; void setFileErrorMessage(const int32_t dataFileIndex, const AString& errorMessage); bool isFileError(const int32_t dataFileIndex) const; bool isFileErrorInvalidStructure(const int32_t dataFileIndex) const; void setFileErrorInvalidStructure(const int32_t dataFileIndex, const bool status); AString getUsername() const; AString getPassword() const; void setUsernameAndPassword(const AString& username, const AString& password); bool isFileToBeMarkedModified(const int32_t dataFileIndex) const; CaretDataFile* getDataFileRead(const int32_t dataFileIndex); void setDataFileRead(const int32_t dataFileIndex, CaretDataFile* caretDataFile); private: class FileData { public: FileData(const StructureEnum::Enum structure, const DataFileTypeEnum::Enum dataFileType, const AString& dataFileName, const bool markFileAsModified) : m_structure(structure), m_dataFileType(dataFileType), m_dataFileName(dataFileName), m_markFileAsModified(markFileAsModified) { m_invalidStructureError = false; m_caretDataFileThatWasRead = NULL; } ~FileData() { } StructureEnum::Enum m_structure; DataFileTypeEnum::Enum m_dataFileType; AString m_dataFileName; AString m_errorMessage; CaretDataFile* m_caretDataFileThatWasRead; bool m_markFileAsModified; bool m_invalidStructureError; }; std::vector m_dataFiles; EventDataFileRead(const EventDataFileRead&); EventDataFileRead& operator=(const EventDataFileRead&); Brain* loadIntoBrain; AString username; AString password; }; } // namespace #endif // __EVENT_DATA_FILE_READ_H__ workbench-1.1.1/src/Brain/EventDataFileReload.cxx000066400000000000000000000060441255417355300216620ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __EVENT_DATA_FILE_RELOAD_DECLARE__ #include "EventDataFileReload.h" #undef __EVENT_DATA_FILE_RELOAD_DECLARE__ #include "EventTypeEnum.h" using namespace caret; /** * \class caret::EventDataFileReload * \brief Event for reloading a Caret Data File. * \ingroup Brain */ /** * Constructor. * * Note: If reload fails, the caretDataFile WILL BE DELETED and the pointer * must no longer be deferenced. * * @param brain * Brain into which file is reloaded. * @param caretDataFile * Caret data file that is reloaded. */ EventDataFileReload::EventDataFileReload(Brain* brain, CaretDataFile* caretDataFile) : Event(EventTypeEnum::EVENT_DATA_FILE_RELOAD), m_brain(brain), m_caretDataFile(caretDataFile) { } /** * Destructor. */ EventDataFileReload::~EventDataFileReload() { } /** * @return Brain into which file is loaded. */ Brain* EventDataFileReload::getBrain() { return this->m_brain; } /** * @return The Caret Data File that will be reloaded. */ CaretDataFile* EventDataFileReload::getCaretDataFile() { return m_caretDataFile; } /** * @return true if there was an error reloading the file, else false. */ bool EventDataFileReload::isError() const { const bool errorFlag = (m_errorMessage.isEmpty() == false); return errorFlag; } /** * @return The error message. */ AString EventDataFileReload::getErrorMessage() const { return m_errorMessage; } /** * Set there error message describing reloading error. * * @param errorMessage * Message describing the error. */ void EventDataFileReload::setErrorMessage(const AString& errorMessage) { m_errorMessage = errorMessage; } /** * @return The username. */ AString EventDataFileReload::getUsername() const { return m_username; } /** * @return The password. */ AString EventDataFileReload::getPassword() const { return m_password; } /** * Set the username and password. * * @param username * Name of user account. * @param password * Password of user account. */ void EventDataFileReload::setUsernameAndPassword(const AString& username, const AString& password) { m_username = username; m_password = password; } workbench-1.1.1/src/Brain/EventDataFileReload.h000066400000000000000000000044431255417355300213100ustar00rootroot00000000000000#ifndef __EVENT_DATA_FILE_RELOAD_H__ #define __EVENT_DATA_FILE_RELOAD_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "Event.h" namespace caret { class Brain; class CaretDataFile; class EventDataFileReload : public Event { public: EventDataFileReload(Brain* brain, CaretDataFile* caretDataFile); virtual ~EventDataFileReload(); Brain* getBrain(); CaretDataFile* getCaretDataFile(); bool isError() const; AString getErrorMessage() const; void setErrorMessage(const AString& errorMessage); AString getUsername() const; AString getPassword() const; void setUsernameAndPassword(const AString& username, const AString& password); private: EventDataFileReload(const EventDataFileReload&); EventDataFileReload& operator=(const EventDataFileReload&); public: // ADD_NEW_METHODS_HERE private: Brain* m_brain; CaretDataFile* m_caretDataFile; AString m_username; AString m_password; AString m_errorMessage; // ADD_NEW_MEMBERS_HERE }; #ifdef __EVENT_DATA_FILE_RELOAD_DECLARE__ // #endif // __EVENT_DATA_FILE_RELOAD_DECLARE__ } // namespace #endif //__EVENT_DATA_FILE_RELOAD_H__ workbench-1.1.1/src/Brain/EventIdentificationHighlightLocation.cxx000066400000000000000000000060131255417355300253300ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "BrainConstants.h" #include "CaretAssert.h" #include "EventIdentificationHighlightLocation.h" using namespace caret; /** * Constructor for identification event of location. * * @parma tabIndex * Index of tab in which identification took place. This value may * be negative indicating that the identification request is not * for a specific browser tab. One source for this is the Select Brainordinate * option on the Information Window. * @param xyz * Stereotaxic location of selected item. */ EventIdentificationHighlightLocation::EventIdentificationHighlightLocation(const int32_t tabIndex, const float xyz[3], const LOAD_FIBER_ORIENTATION_SAMPLES_MODE loadFiberOrientationSamplesMode) : Event(EventTypeEnum::EVENT_IDENTIFICATION_HIGHLIGHT_LOCATION), m_tabIndex(tabIndex), m_loadFiberOrientationSamplesMode(loadFiberOrientationSamplesMode) { /* * NOTE: a negative value is allowed. */ CaretAssert(tabIndex < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS); m_xyz[0] = xyz[0]; m_xyz[1] = xyz[1]; m_xyz[2] = xyz[2]; } /** * Destructor. */ EventIdentificationHighlightLocation::~EventIdentificationHighlightLocation() { } /** * @return The stereotaxic location of the identification (valid for all). */ const float* EventIdentificationHighlightLocation::getXYZ() const { return m_xyz; } /** * Is the tab with the given index selected for identification operations? * * @param tabIndex * Index of tab. * @return True if tab is selected, else false. */ bool EventIdentificationHighlightLocation::isTabSelected(const int32_t tabIndex) const { /* * All tabs? */ if (m_tabIndex < 0) { return true; } else if (m_tabIndex == tabIndex) { return true; } return false; } /** * @return The mode for loading of fiber orientation samples. */ EventIdentificationHighlightLocation::LOAD_FIBER_ORIENTATION_SAMPLES_MODE EventIdentificationHighlightLocation::getLoadFiberOrientationSamplesMode() const { return m_loadFiberOrientationSamplesMode; } workbench-1.1.1/src/Brain/EventIdentificationHighlightLocation.h000066400000000000000000000044301255417355300247560ustar00rootroot00000000000000#ifndef __EVENT_IDENTIFICATION_HIGHLIGHT_LOCATION_H__ #define __EVENT_IDENTIFICATION_HIGHLIGHT_LOCATION_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "Event.h" #include "StructureEnum.h" namespace caret { /// Highlight location of an indentification class EventIdentificationHighlightLocation : public Event { public: enum LOAD_FIBER_ORIENTATION_SAMPLES_MODE { LOAD_FIBER_ORIENTATION_SAMPLES_MODE_YES, LOAD_FIBER_ORIENTATION_SAMPLES_MODE_NO }; EventIdentificationHighlightLocation(const int32_t tabIndex, const float xyz[3], const LOAD_FIBER_ORIENTATION_SAMPLES_MODE loadFiberOrientationSamplesMode); virtual ~EventIdentificationHighlightLocation(); const float* getXYZ() const; bool isTabSelected(const int32_t tabIndex) const; LOAD_FIBER_ORIENTATION_SAMPLES_MODE getLoadFiberOrientationSamplesMode() const; private: EventIdentificationHighlightLocation(const EventIdentificationHighlightLocation&); EventIdentificationHighlightLocation& operator=(const EventIdentificationHighlightLocation&); const int32_t m_tabIndex; float m_xyz[3]; const LOAD_FIBER_ORIENTATION_SAMPLES_MODE m_loadFiberOrientationSamplesMode; }; } // namespace #endif // __EVENT_IDENTIFICATION_HIGHLIGHT_LOCATION_H__ workbench-1.1.1/src/Brain/EventModelAdd.cxx000066400000000000000000000024041255417355300205270ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretAssert.h" #include "EventModelAdd.h" using namespace caret; /** * Constructor. * * @param model * Model to add. */ EventModelAdd::EventModelAdd(Model* model) : Event(EventTypeEnum::EVENT_MODEL_ADD) { m_model = model; CaretAssert(m_model); } /** * Destructor. */ EventModelAdd::~EventModelAdd() { } /** * @return Model that is to be added. */ Model* EventModelAdd::getModel() { return m_model; } workbench-1.1.1/src/Brain/EventModelAdd.h000066400000000000000000000025471255417355300201640ustar00rootroot00000000000000#ifndef __EVENT_MODEL_ADD_H__ #define __EVENT_MODEL_ADD_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "Event.h" namespace caret { class Model; /// Event for adding models class EventModelAdd : public Event { public: EventModelAdd(Model* model); virtual ~EventModelAdd(); Model* getModel(); private: EventModelAdd(const EventModelAdd&); EventModelAdd& operator=(const EventModelAdd&); Model* m_model; }; } // namespace #endif // __EVENT_MODEL_ADD_H__ workbench-1.1.1/src/Brain/EventModelDelete.cxx000066400000000000000000000023631255417355300212450ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretAssert.h" #include "EventModelDelete.h" using namespace caret; /** * Constructor. */ EventModelDelete::EventModelDelete(Model* model) : Event(EventTypeEnum::EVENT_MODEL_DELETE) { m_model = model; CaretAssert(m_model); } /** * Destructor. */ EventModelDelete::~EventModelDelete() { } /** * @return Model that is to be deleted. */ Model* EventModelDelete::getModel() { return m_model; } workbench-1.1.1/src/Brain/EventModelDelete.h000066400000000000000000000026271255417355300206750ustar00rootroot00000000000000#ifndef __EVENT_MODEL_DELETE_H__ #define __EVENT_MODEL_DELETE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "Event.h" namespace caret { class Model; /// Event for deleting models class EventModelDelete : public Event { public: EventModelDelete(Model* model); virtual ~EventModelDelete(); Model* getModel(); private: EventModelDelete(const EventModelDelete&); EventModelDelete& operator=(const EventModelDelete&); Model* m_model; }; } // namespace #endif // __EVENT_MODEL_DELETE_H__ workbench-1.1.1/src/Brain/EventModelGetAll.cxx000066400000000000000000000052671255417355300212210ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "EventModelGetAll.h" #include "ModelSurface.h" using namespace caret; /** * Constructor. */ EventModelGetAll::EventModelGetAll() : Event(EventTypeEnum::EVENT_MODEL_GET_ALL) { } /** * Destructor. */ EventModelGetAll::~EventModelGetAll() { } /** * Add models. * @param modelsToAdd * These model added. */ void EventModelGetAll::addModels( const std::vector& modelsToAdd) { m_models.insert(m_models.end(), modelsToAdd.begin(), modelsToAdd.end()); } /** * Get the model. * * @return vector containing the model. */ const std::vector EventModelGetAll::getModels() const { return this->m_models; } /** * Is a model valid? * * @param model * Model that is checked for validity. * * @return true if valid, else false. */ bool EventModelGetAll::isModelValid( const Model* model) const { if (std::find(this->m_models.begin(), this->m_models.end(), model) != m_models.end()) { return true; } return false; } /** * Get the first model. * * @return Pointer to first model or * NULL if there are no model. */ Model* EventModelGetAll::getFirstModel() const { if (m_models.empty() == false) { return m_models[0]; } return NULL; } /** * Get the first model surface. * * @return Pointer to first model surface or * NULL if there are no model surfaces. */ ModelSurface* EventModelGetAll::getFirstModelSurface() const { ModelSurface* surfaceModelOut = NULL; const int32_t numModels = static_cast(m_models.size()); for (int32_t i = 0; i < numModels; i++) { surfaceModelOut = dynamic_cast(m_models[i]); if (surfaceModelOut != NULL) { break; } } return surfaceModelOut; } workbench-1.1.1/src/Brain/EventModelGetAll.h000066400000000000000000000033701255417355300206370ustar00rootroot00000000000000#ifndef __EVENT_GET_MODEL_DISPLAY_CONTROLLERS_H__ #define __EVENT_GET_MODEL_DISPLAY_CONTROLLERS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "Event.h" namespace caret { class Model; class ModelSurface; /// Event for getting models class EventModelGetAll : public Event { public: EventModelGetAll(); virtual ~EventModelGetAll(); void addModels(const std::vector& modelsToAdd); const std::vector getModels() const; bool isModelValid(const Model* model) const; Model* getFirstModel() const; ModelSurface* getFirstModelSurface() const; private: EventModelGetAll(const EventModelGetAll&); EventModelGetAll& operator=(const EventModelGetAll&); std::vector m_models; }; } // namespace #endif // __EVENT_GET_MODEL_DISPLAY_CONTROLLERS_H__ workbench-1.1.1/src/Brain/EventModelSurfaceGet.cxx000066400000000000000000000032751255417355300220760ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretAssert.h" #include "EventModelSurfaceGet.h" using namespace caret; /** * Constructor. */ EventModelSurfaceGet::EventModelSurfaceGet(const Surface* surface) : Event(EventTypeEnum::EVENT_MODEL_SURFACE_GET), surface(surface) { CaretAssert(surface); } /** * Destructor. */ EventModelSurfaceGet::~EventModelSurfaceGet() { } /** * @return The model surface that was found. */ ModelSurface* EventModelSurfaceGet::getModelSurface() { return m_modelSurface; } /** * Set the model surface. * @param modelSurface * Model surface that matches the specified surface. */ void EventModelSurfaceGet::setModelSurface(ModelSurface* modelSurface) { m_modelSurface = modelSurface; } /** * @return Returns the surface for which the model surface is requested. */ const Surface* EventModelSurfaceGet::getSurface() const { return this->surface; } workbench-1.1.1/src/Brain/EventModelSurfaceGet.h000066400000000000000000000033141255417355300215150ustar00rootroot00000000000000#ifndef __EVENT_MODEL_SURFACE_GET_H__ #define __EVENT_MODEL_SURFACE_GET_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "Event.h" namespace caret { class ModelSurface; class Surface; /// Find the Surface model that contains a specific Surface. class EventModelSurfaceGet : public Event { public: EventModelSurfaceGet(const Surface* surface); virtual ~EventModelSurfaceGet(); ModelSurface* getModelSurface(); void setModelSurface(ModelSurface* modelSurface); const Surface* getSurface() const; private: EventModelSurfaceGet(const EventModelSurfaceGet&); EventModelSurfaceGet& operator=(const EventModelSurfaceGet&); ModelSurface* m_modelSurface; const Surface* surface; }; } // namespace #endif // __EVENT_MODEL_SURFACE_GET_H__ workbench-1.1.1/src/Brain/EventNodeDataFilesGet.cxx000066400000000000000000000057451255417355300221730ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretAssert.h" #include "EventNodeDataFilesGet.h" #include "LabelFile.h" #include "MetricFile.h" #include "RgbaFile.h" using namespace caret; /** * Constructor for node data files that are * associated with ANY surface. */ EventNodeDataFilesGet::EventNodeDataFilesGet() : Event(EventTypeEnum::EVENT_GET_NODE_DATA_FILES), surface(NULL) { } /** * Constructor for node data files that are * associated with a specific surface. */ EventNodeDataFilesGet::EventNodeDataFilesGet(const Surface* surfaceIn) : Event(EventTypeEnum::EVENT_GET_NODE_DATA_FILES), surface(surfaceIn) { } /** * Destructor. */ EventNodeDataFilesGet::~EventNodeDataFilesGet() { } /** * Add a node data file. * @param nodeDataFile * Data file that is added. */ void EventNodeDataFilesGet::addFile(GiftiTypeFile* nodeDataFile) { CaretAssert(nodeDataFile); if (nodeDataFile->getNumberOfColumns() <= 0) { return; } LabelFile* lf = dynamic_cast(nodeDataFile); if (lf != NULL) { this->labelFiles.push_back(lf); return; } MetricFile* mf = dynamic_cast(nodeDataFile); if (mf != NULL) { this->metricFiles.push_back(mf); return; } RgbaFile* rf = dynamic_cast(nodeDataFile); if (rf != NULL) { this->rgbaFiles.push_back(rf); return; } CaretAssertMessage(0, "Unsupported vertex data file: " + AString(typeid(nodeDataFile).name()) + " New vertex data file added?"); } /** * @return Returns all data files. */ void EventNodeDataFilesGet::getAllFiles(std::vector& allFilesOut) const { allFilesOut.clear(); allFilesOut.insert(allFilesOut.end(), this->labelFiles.begin(), this->labelFiles.end()); allFilesOut.insert(allFilesOut.end(), this->metricFiles.begin(), this->metricFiles.end()); allFilesOut.insert(allFilesOut.end(), this->rgbaFiles.begin(), this->rgbaFiles.end()); } workbench-1.1.1/src/Brain/EventNodeDataFilesGet.h000066400000000000000000000051221255417355300216050ustar00rootroot00000000000000#ifndef __EVENT_NODE_DATA_FILES_GET_H__ #define __EVENT_NODE_DATA_FILES_GET_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "Event.h" namespace caret { class GiftiTypeFile; class LabelFile; class MetricFile; class Model; class RgbaFile; class Surface; /// Event that gets all node data files. class EventNodeDataFilesGet : public Event { public: EventNodeDataFilesGet(); EventNodeDataFilesGet(const Surface* surface); virtual ~EventNodeDataFilesGet(); void addFile(GiftiTypeFile* nodeDataFile); /** * @return Returns the surface for which associated data * files are requested. If NULL, then node data files * from all brain structures are requested. */ const Surface* getSurface() const { return this->surface; } /** @return Returns the label files. */ std::vector getLabelFiles() const { return this->labelFiles; } /** @return Returns the metric files. */ std::vector getMetricFiles() const { return this->metricFiles; } /** @return Returns the rgba files. */ std::vector getRgbaFiles() const { return this->rgbaFiles; } void getAllFiles(std::vector& allFilesOut) const; private: EventNodeDataFilesGet(const EventNodeDataFilesGet&); EventNodeDataFilesGet& operator=(const EventNodeDataFilesGet&); const Surface* surface; std::vector labelFiles; std::vector metricFiles; std::vector rgbaFiles; }; } // namespace #endif // __EVENT_NODE_DATA_FILES_GET_H__ workbench-1.1.1/src/Brain/EventNodeIdentificationColorsGetFromCharts.cxx000066400000000000000000000102131255417355300264250ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __EVENT_NODE_IDENTIFICATION_COLORS_GET_FROM_CHARTS_DECLARE__ #include "EventNodeIdentificationColorsGetFromCharts.h" #undef __EVENT_NODE_IDENTIFICATION_COLORS_GET_FROM_CHARTS_DECLARE__ #include "CaretAssert.h" #include "EventTypeEnum.h" using namespace caret; /** * \class caret::EventNodeIdentificationColorsGetFromCharts * \brief Get colors for node identification systems displayed in charts * \ingroup Brain */ /** * Constructor. * * @param structure * Structure for matching to nodes in charts. * @param tabIndex * Index of tab * @param nodeIndices * Indices of nodes for which chart colors are requested. */ EventNodeIdentificationColorsGetFromCharts::EventNodeIdentificationColorsGetFromCharts(const StructureEnum::Enum structure, const int32_t tabIndex, const std::vector& nodeIndices) : Event(EventTypeEnum::EVENT_NODE_IDENTIFICATION_COLORS_GET_FROM_CHARTS) { m_structureName = StructureEnum::toName(structure); m_tabIndex = tabIndex; m_nodeIndices = nodeIndices; } /** * Destructor. */ EventNodeIdentificationColorsGetFromCharts::~EventNodeIdentificationColorsGetFromCharts() { } /** * Add a node and its color (this method is called by charts). * * @param nodeIndex * Index of the node. * @param rgb * RGB coloring for the node. */ void EventNodeIdentificationColorsGetFromCharts::addNode(const int32_t nodeIndex, const float rgb[3]) { if (m_nodeRgbColor.find(nodeIndex) != m_nodeRgbColor.end()) { return; } RgbColor rgbColor; rgbColor.rgb[0] = rgb[0] * 255.0; rgbColor.rgb[1] = rgb[1] * 255.0; rgbColor.rgb[2] = rgb[2] * 255.0; m_nodeRgbColor.insert(std::pair(nodeIndex, rgbColor)); } /** * Apply chart color to node. If there is chart coloring for the node * with the given index, it is applied. Otherwise, no action is taken. * This method is called by drawing code. * * @param nodeIndex * Index of the node. * @param rgb * Receives coloring for the node from the chart coloring (if available). */ void EventNodeIdentificationColorsGetFromCharts::applyChartColorToNode(const int32_t nodeIndex, uint8_t rgb[3]) { std::map::const_iterator iter = m_nodeRgbColor.find(nodeIndex); if (iter != m_nodeRgbColor.end()) { RgbColor rgbColor = iter->second; rgb[0] = rgbColor.rgb[0]; rgb[1] = rgbColor.rgb[1]; rgb[2] = rgbColor.rgb[2]; } } /** * @return Name of the surface node's structure. This method is called by charts. */ AString EventNodeIdentificationColorsGetFromCharts::getStructureName() const { return m_structureName; } /** * @return Index of tab where notification symbols are displayed. */ int32_t EventNodeIdentificationColorsGetFromCharts::getTabIndex() const { return m_tabIndex; } /** * @return Indices of nodes for which chart colors are requested. */ std::vector EventNodeIdentificationColorsGetFromCharts::getNodeIndices() const { return m_nodeIndices; } workbench-1.1.1/src/Brain/EventNodeIdentificationColorsGetFromCharts.h000066400000000000000000000052171255417355300260620ustar00rootroot00000000000000#ifndef __EVENT_NODE_IDENTIFICATION_COLORS_GET_FROM_CHARTS_H__ #define __EVENT_NODE_IDENTIFICATION_COLORS_GET_FROM_CHARTS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "Event.h" #include "StructureEnum.h" namespace caret { class EventNodeIdentificationColorsGetFromCharts : public Event { public: EventNodeIdentificationColorsGetFromCharts(const StructureEnum::Enum structure, const int32_t tabIndex, const std::vector& nodeIndices); virtual ~EventNodeIdentificationColorsGetFromCharts(); void addNode(const int32_t nodeIndex, const float rgba[4]); void applyChartColorToNode(const int32_t nodeIndex, uint8_t rgba[4]); AString getStructureName() const; int32_t getTabIndex() const; std::vector getNodeIndices() const; // ADD_NEW_METHODS_HERE private: EventNodeIdentificationColorsGetFromCharts(const EventNodeIdentificationColorsGetFromCharts&); EventNodeIdentificationColorsGetFromCharts& operator=(const EventNodeIdentificationColorsGetFromCharts&); // ADD_NEW_MEMBERS_HERE AString m_structureName; int32_t m_tabIndex; std::vector m_nodeIndices; struct RgbColor { uint8_t rgb[3]; }; std::map m_nodeRgbColor; }; #ifdef __EVENT_NODE_IDENTIFICATION_COLORS_GET_FROM_CHARTS_DECLARE__ // #endif // __EVENT_NODE_IDENTIFICATION_COLORS_GET_FROM_CHARTS_DECLARE__ } // namespace #endif //__EVENT_NODE_IDENTIFICATION_COLORS_GET_FROM_CHARTS_H__ workbench-1.1.1/src/Brain/EventOverlayValidate.cxx000066400000000000000000000034441255417355300221560ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __EVENT_OVERLAY_VALIDATE_DECLARE__ #include "EventOverlayValidate.h" #undef __EVENT_OVERLAY_VALIDATE_DECLARE__ #include "EventTypeEnum.h" using namespace caret; /** * \class caret::EventOverlayValidate * \brief Test an overlay for validity (it exists). * \ingroup Brain */ /** * Constructor. */ EventOverlayValidate::EventOverlayValidate(const Overlay* overlay) : Event(EventTypeEnum::EVENT_OVERLAY_VALIDATE), m_overlay(overlay) { m_valid = false; } /** * Destructor. */ EventOverlayValidate::~EventOverlayValidate() { } /** * @return true if the overlay was found to be valid. */ bool EventOverlayValidate::isValidOverlay() const { return m_valid; } /** * Set the validity if the given overlay is the overlay * that was passed to the constructor. * * @param overlay * Overlay tested for match. */ void EventOverlayValidate::testValidOverlay(const Overlay* overlay) { if (m_overlay == overlay) { m_valid = true; } } workbench-1.1.1/src/Brain/EventOverlayValidate.h000066400000000000000000000034011255417355300215740ustar00rootroot00000000000000#ifndef __EVENT_OVERLAY_VALIDATE_H__ #define __EVENT_OVERLAY_VALIDATE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "Event.h" namespace caret { class Overlay; class EventOverlayValidate : public Event { public: EventOverlayValidate(const Overlay* overlay); virtual ~EventOverlayValidate(); bool isValidOverlay() const; void testValidOverlay(const Overlay* overlay); private: EventOverlayValidate(const EventOverlayValidate&); EventOverlayValidate& operator=(const EventOverlayValidate&); public: // ADD_NEW_METHODS_HERE private: // ADD_NEW_MEMBERS_HERE const Overlay* m_overlay; bool m_valid; }; #ifdef __EVENT_OVERLAY_VALIDATE_DECLARE__ // #endif // __EVENT_OVERLAY_VALIDATE_DECLARE__ } // namespace #endif //__EVENT_OVERLAY_VALIDATE_H__ workbench-1.1.1/src/Brain/EventSpecFileReadDataFiles.cxx000066400000000000000000000047271255417355300231330ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretAssert.h" #include "EventSpecFileReadDataFiles.h" using namespace caret; /** * Constructor. * * @param loadIntoBrain * Brain into which file is loaded. * @param specFile * Spec file that has its files read. */ EventSpecFileReadDataFiles::EventSpecFileReadDataFiles(Brain* loadIntoBrain, SpecFile* specFile) : Event(EventTypeEnum::EVENT_SPEC_FILE_READ_DATA_FILES) { this->loadIntoBrain = loadIntoBrain; this->specFile = specFile; this->username = ""; this->password = ""; CaretAssert(this->loadIntoBrain); CaretAssert(this->specFile); } /** * Destructor. */ EventSpecFileReadDataFiles::~EventSpecFileReadDataFiles() { } /** * @return The spec file that is to have its data files loaded. */ SpecFile* EventSpecFileReadDataFiles::getSpecFile() { return this->specFile; } /** * @return The brain into which files is loaded. */ Brain* EventSpecFileReadDataFiles::getLoadIntoBrain() { return this->loadIntoBrain; } /** * @return The username. */ AString EventSpecFileReadDataFiles::getUsername() const { return this->username; } /** * @return The password. */ AString EventSpecFileReadDataFiles::getPassword() const { return this->password; } /** * Set the username and password. * * @param username * Name of user account. * @param password * Password of user account. */ void EventSpecFileReadDataFiles::setUsernameAndPassword(const AString& username, const AString& password) { this->username = username; this->password = password; } workbench-1.1.1/src/Brain/EventSpecFileReadDataFiles.h000066400000000000000000000041001255417355300225410ustar00rootroot00000000000000#ifndef __EVENT_SPEC_FILE_READ_DATA_FILES_H__ #define __EVENT_SPEC_FILE_READ_DATA_FILES_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "DataFileTypeEnum.h" #include "Event.h" namespace caret { class Brain; class SpecFile; /** * Event for reading data files selected in a spec file. */ class EventSpecFileReadDataFiles : public Event { public: EventSpecFileReadDataFiles(Brain* loadIntoBrain, SpecFile* specFile); virtual ~EventSpecFileReadDataFiles(); SpecFile* getSpecFile(); Brain* getLoadIntoBrain(); AString getUsername() const; AString getPassword() const; void setUsernameAndPassword(const AString& username, const AString& password); private: EventSpecFileReadDataFiles(const EventSpecFileReadDataFiles&); EventSpecFileReadDataFiles& operator=(const EventSpecFileReadDataFiles&); Brain* loadIntoBrain; SpecFile* specFile; AString username; AString password; bool errorInvalidStructure; }; } // namespace #endif // __EVENT_SPEC_FILE_READ_DATA_FILES_H__ workbench-1.1.1/src/Brain/EventSurfacesGet.cxx000066400000000000000000000064141255417355300212760ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretAssert.h" #include "EventSurfacesGet.h" #include "Surface.h" using namespace caret; /** * Construct an event for getting surfaces from * all structures and surface types. Methods * are provided for constraining the surfaces * to those with specified structures and/or * surface types. */ EventSurfacesGet::EventSurfacesGet() : Event(EventTypeEnum::EVENT_SURFACES_GET) { } /** * Destructore. */ EventSurfacesGet::~EventSurfacesGet() { } /** * Add a surface. If the surface does not * meet any surface/structure constraints, * it will not be added. * * @param surface * Surface that is added. */ void EventSurfacesGet::addSurface(Surface* surface) { if (this->structureConstraints.empty() == false) { const StructureEnum::Enum structure = surface->getStructure(); if (std::find(this->structureConstraints.begin(), this->structureConstraints.end(), structure) == this->structureConstraints.end()) { return; } } if (this->surfaceTypeConstraints.empty() == false) { const SurfaceTypeEnum::Enum surfaceType = surface->getSurfaceType(); if (std::find(this->surfaceTypeConstraints.begin(), this->surfaceTypeConstraints.end(), surfaceType) == this->surfaceTypeConstraints.end()) { return; } } this->surfaces.push_back(surface); } /** * Add a structure contraints. If structure constraints * are added, only surface of the specified structures * are obtained. May be called more than once for * constraining to multiple surface types. * * @param structure * Structure for constraining selection. */ void EventSurfacesGet::addStructureConstraint(const StructureEnum::Enum structure) { this->structureConstraints.push_back(structure); } /** * Add a surface type contraints. If surface type constraints * are added, only surfaces of the specified surface types * are obtained. May be called more than once for constraining * to multiple surface types. * * @param surfaceType * Surface type for constraining selection. */ void EventSurfacesGet::addSurfaceTypeConstraint(const SurfaceTypeEnum::Enum surfaceType) { this->surfaceTypeConstraints.push_back(surfaceType); } /** * @return Surfaces that were obtained. */ std::vector EventSurfacesGet::getSurfaces() const { return this->surfaces; } workbench-1.1.1/src/Brain/EventSurfacesGet.h000066400000000000000000000035251255417355300207230ustar00rootroot00000000000000#ifndef __EVENT_SURFACES_GET_H__ #define __EVENT_SURFACES_GET_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "Event.h" #include "StructureEnum.h" #include "SurfaceTypeEnum.h" namespace caret { class Surface; /// Get surfaces class EventSurfacesGet : public Event { public: EventSurfacesGet(); virtual ~EventSurfacesGet(); void addSurface(Surface* surface); void addStructureConstraint(const StructureEnum::Enum structure); void addSurfaceTypeConstraint(const SurfaceTypeEnum::Enum surfaceType); std::vector getSurfaces() const; private: EventSurfacesGet(const EventSurfacesGet&); EventSurfacesGet& operator=(const EventSurfacesGet&); std::vector surfaces; std::vector structureConstraints; std::vector surfaceTypeConstraints; }; } // namespace #endif // __EVENT_SURFACES_GET_H__ workbench-1.1.1/src/Brain/FeatureColoringTypeEnum.cxx000066400000000000000000000230351255417355300226360ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __FEATURE_COLORING_TYPE_ENUM_DECLARE__ #include "FeatureColoringTypeEnum.h" #undef __FEATURE_COLORING_TYPE_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::FeatureColoringTypeEnum * \brief Coloring type for features. * \ingroup GuiQt */ /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ FeatureColoringTypeEnum::FeatureColoringTypeEnum(const Enum enumValue, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCodeCounter++; this->name = name; this->guiName = guiName; } /** * Destructor. */ FeatureColoringTypeEnum::~FeatureColoringTypeEnum() { } /** * Initialize the enumerated metadata. */ void FeatureColoringTypeEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(FeatureColoringTypeEnum(FEATURE_COLORING_TYPE_CLASS, "FEATURE_COLORING_TYPE_CLASS", "Class")); enumData.push_back(FeatureColoringTypeEnum(FEATURE_COLORING_TYPE_NAME, "FEATURE_COLORING_TYPE_NAME", "Name")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const FeatureColoringTypeEnum* FeatureColoringTypeEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const FeatureColoringTypeEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString FeatureColoringTypeEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const FeatureColoringTypeEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ FeatureColoringTypeEnum::Enum FeatureColoringTypeEnum::fromName(const AString& nameIn, bool* isValidOut) { AString name = nameIn; /* * Translate foci coloring type that was replaced */ if (name == "FOCI_COLORING_TYPE_CLASS") { name = "FEATURE_COLORING_TYPE_CLASS"; } else if (name == "FOCI_COLORING_TYPE_NAME") { name = "FEATURE_COLORING_TYPE_NAME"; } if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = FEATURE_COLORING_TYPE_NAME; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const FeatureColoringTypeEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type FeatureColoringTypeEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString FeatureColoringTypeEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const FeatureColoringTypeEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ FeatureColoringTypeEnum::Enum FeatureColoringTypeEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = FEATURE_COLORING_TYPE_NAME; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const FeatureColoringTypeEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type FeatureColoringTypeEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t FeatureColoringTypeEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const FeatureColoringTypeEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ FeatureColoringTypeEnum::Enum FeatureColoringTypeEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = FEATURE_COLORING_TYPE_NAME; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const FeatureColoringTypeEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type FeatureColoringTypeEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void FeatureColoringTypeEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void FeatureColoringTypeEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(FeatureColoringTypeEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void FeatureColoringTypeEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(FeatureColoringTypeEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } workbench-1.1.1/src/Brain/FeatureColoringTypeEnum.h000066400000000000000000000062461255417355300222700ustar00rootroot00000000000000#ifndef __FEATURE_COLORING_TYPE_ENUM__H_ #define __FEATURE_COLORING_TYPE_ENUM__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class FeatureColoringTypeEnum { public: /** * Enumerated values. */ enum Enum { /** Use class for coloring */ FEATURE_COLORING_TYPE_CLASS, /** Use name for coloring */ FEATURE_COLORING_TYPE_NAME }; ~FeatureColoringTypeEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); private: FeatureColoringTypeEnum(const Enum enumValue, const AString& name, const AString& guiName); static const FeatureColoringTypeEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; #ifdef __FEATURE_COLORING_TYPE_ENUM_DECLARE__ std::vector FeatureColoringTypeEnum::enumData; bool FeatureColoringTypeEnum::initializedFlag = false; int32_t FeatureColoringTypeEnum::integerCodeCounter = 0; #endif // __FEATURE_COLORING_TYPE_ENUM_DECLARE__ } // namespace #endif //__FEATURE_COLORING_TYPE_ENUM__H_ workbench-1.1.1/src/Brain/FiberOrientationSamplesLoader.cxx000066400000000000000000000346211255417355300240010ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __FIBER_ORIENTATION_SAMPLES_LOADER_DECLARE__ #include "FiberOrientationSamplesLoader.h" #undef __FIBER_ORIENTATION_SAMPLES_LOADER_DECLARE__ #include "Brain.h" #include "CaretAssert.h" #include "CiftiFiberOrientationFile.h" #include "DataFileException.h" #include "FiberOrientation.h" #include "FileInformation.h" #include "EventIdentificationHighlightLocation.h" #include "EventManager.h" #include "SpecFile.h" #include "VolumeFile.h" using namespace caret; /** * \class caret::FiberOrientationSamplesLoader * \brief Loads Fiber Orientation samples display on a sphere in features toolbox * \ingroup Brain */ /** * Constructor. */ FiberOrientationSamplesLoader::FiberOrientationSamplesLoader() : CaretObject() { m_sampleVolumesLoadAttemptValid = false; m_sampleVolumesValid = false; for (int32_t i = 0; i < 3; i++) { m_sampleMagnitudeVolumes[i] = NULL; m_sampleThetaVolumes[i] = NULL; m_samplePhiVolumes[i] = NULL; } m_lastIdentificationValid = false; EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_IDENTIFICATION_HIGHLIGHT_LOCATION); } /** * Destructor. */ FiberOrientationSamplesLoader::~FiberOrientationSamplesLoader() { reset(); EventManager::get()->removeAllEventsFromListener(this); } /** * Reset all settings to their defaults * and remove any data. */ void FiberOrientationSamplesLoader::reset() { m_sampleVolumesLoadAttemptValid = false; m_sampleVolumesValid = false; for (int32_t i = 0; i < 3; i++) { if (m_sampleMagnitudeVolumes[i] != NULL) { delete m_sampleMagnitudeVolumes[i]; m_sampleMagnitudeVolumes[i] = NULL; } if (m_sampleThetaVolumes[i] != NULL) { delete m_sampleThetaVolumes[i]; m_sampleThetaVolumes[i] = NULL; } if (m_samplePhiVolumes[i] != NULL) { delete m_samplePhiVolumes[i]; m_samplePhiVolumes[i] = NULL; } } m_lastIdentificationValid = false; } /** * Receive events from the event manager. * * @param event * Event sent by event manager. */ void FiberOrientationSamplesLoader::receiveEvent(Event* event) { if (event->getEventType() == EventTypeEnum::EVENT_IDENTIFICATION_HIGHLIGHT_LOCATION) { EventIdentificationHighlightLocation* idEvent = dynamic_cast(event); CaretAssert(idEvent); switch (idEvent->getLoadFiberOrientationSamplesMode()) { case EventIdentificationHighlightLocation::LOAD_FIBER_ORIENTATION_SAMPLES_MODE_NO: break; case EventIdentificationHighlightLocation::LOAD_FIBER_ORIENTATION_SAMPLES_MODE_YES: { const float* xyz = idEvent->getXYZ(); if (xyz != NULL) { m_lastIdentificationValid = true; m_lastIdentificationXYZ[0] = xyz[0]; m_lastIdentificationXYZ[1] = xyz[1]; m_lastIdentificationXYZ[2] = xyz[2]; } } break; } } } /** * Get the fiber orientation vectors for display on a sphere. * * @param brain * Brain for which vectors are loaded. * @param xVectors * Vectors for X-orientation. * @param yVectors * Vectors for Y-orientation. * @param zVectors * Vectors for Z-orientation. * @param fiberOrientation * The nearby fiber orientation * @param errorMessageOut * Will contain any error messages. * This error message will only be set in some cases when there is an * error. * @return * True if data is valid, else false. */ bool FiberOrientationSamplesLoader::getFiberOrientationSphericalSamplesVectors(Brain* brain, std::vector& xVectors, std::vector& yVectors, std::vector& zVectors, FiberOrientation* &fiberOrientationOut, AString& errorMessageOut) { CaretAssert(brain); errorMessageOut = ""; fiberOrientationOut = NULL; if (m_lastIdentificationValid) { if (loadSphericalOrientationVolumes(brain, errorMessageOut) == false) { return false; } if (brain->getNumberOfConnectivityFiberOrientationFiles() > 0) { CiftiFiberOrientationFile* cfof = brain->getConnectivityFiberOrientationFile(0); FiberOrientation* nearestFiberOrientation = cfof->getFiberOrientationNearestCoordinate(m_lastIdentificationXYZ, 3.0); if (nearestFiberOrientation != NULL) { fiberOrientationOut = nearestFiberOrientation; } } int64_t ijk[3]; m_sampleThetaVolumes[0]->enclosingVoxel(m_lastIdentificationXYZ, ijk); if (m_sampleThetaVolumes[0]->indexValid(ijk)) { std::vector dims; m_sampleThetaVolumes[0]->getDimensions(dims); const int64_t numberOfOrientations = dims[3]; xVectors.resize(numberOfOrientations); yVectors.resize(numberOfOrientations); zVectors.resize(numberOfOrientations); for (int32_t iAxis = 0; iAxis < 3; iAxis++) { for (int64_t iOrient = 0; iOrient < numberOfOrientations; iOrient++) { const float theta = m_sampleThetaVolumes[iAxis]->getValue(ijk[0], ijk[1], ijk[2], iOrient, 0); const float phi = m_samplePhiVolumes[iAxis]->getValue(ijk[0], ijk[1], ijk[2], iOrient, 0); const float magnitude = m_sampleMagnitudeVolumes[iAxis]->getValue(ijk[0], ijk[1], ijk[2], iOrient, 0); switch (iAxis) { case 0: { FiberOrientationSamplesVector& ov = xVectors[iOrient]; ov.direction[0] = -std::sin(theta) * std::cos(phi); ov.direction[1] = std::sin(theta) * std::sin(phi); ov.direction[2] = std::cos(theta); ov.magnitude = magnitude; ov.setColor(); } break; case 1: { FiberOrientationSamplesVector& ov = yVectors[iOrient]; ov.direction[0] = -std::sin(theta) * std::cos(phi); ov.direction[1] = std::sin(theta) * std::sin(phi); ov.direction[2] = std::cos(theta); ov.magnitude = magnitude; ov.setColor(); } break; case 2: { FiberOrientationSamplesVector& ov = zVectors[iOrient]; ov.direction[0] = -std::sin(theta) * std::cos(phi); ov.direction[1] = std::sin(theta) * std::sin(phi); ov.direction[2] = std::cos(theta); ov.magnitude = magnitude; ov.setColor(); } break; } } } return true; } } return false; } /** * Get the volumes containing the spherical orienations. * * @param brain * Brain for which vectors are loaded. * @param magnitudeVolumesOut * The volumes containing the magnitudes. * @param phiAngleVolumesOut * The volumes containing the phi angles. * @param thetaAngleVolumesOut * The volumes containing the theta angles. * */ bool FiberOrientationSamplesLoader::loadSphericalOrientationVolumes(Brain* brain, AString& errorMessageOut) { errorMessageOut = ""; FileInformation specFileInfo(brain->getSpecFile()->getFileName()); const AString directoryName = specFileInfo.getPathName(); if (m_sampleVolumesValid == false) { if (m_sampleVolumesLoadAttemptValid == false) { const AString filePrefix = "merged_"; const AString fileSuffix = "samples.nii.gz"; std::vector allVolumes; for (int32_t i = 0; i < 3; i++) { m_sampleMagnitudeVolumes[i] = new VolumeFile(); m_samplePhiVolumes[i] = new VolumeFile(); m_sampleThetaVolumes[i] = new VolumeFile(); const AString fileNumber = AString::number(i + 1); try { const AString magFileName = (filePrefix + "f" + fileNumber + fileSuffix); FileInformation magFileInfo(directoryName, magFileName); const AString magFilePath = magFileInfo.getAbsoluteFilePath(); m_sampleMagnitudeVolumes[i]->readFile(magFilePath); allVolumes.push_back(m_sampleMagnitudeVolumes[i]); } catch (const DataFileException& dfe) { if (errorMessageOut.isEmpty() == false) { errorMessageOut += "\n"; } errorMessageOut += dfe.whatString(); } try { const AString phiFileName = (filePrefix + "ph" + fileNumber + fileSuffix); FileInformation phiFileInfo(directoryName, phiFileName); const AString phiFilePath = phiFileInfo.getAbsoluteFilePath(); m_samplePhiVolumes[i]->readFile(phiFilePath); allVolumes.push_back(m_samplePhiVolumes[i]); } catch (const DataFileException& dfe) { if (errorMessageOut.isEmpty() == false) { errorMessageOut += "\n"; } errorMessageOut += dfe.whatString(); } try { const AString thetaFileName = (filePrefix + "th" + fileNumber + fileSuffix); FileInformation thetaFileInfo(directoryName, thetaFileName); const AString thetaFilePath = thetaFileInfo.getAbsoluteFilePath(); m_sampleThetaVolumes[i]->readFile(thetaFilePath); allVolumes.push_back(m_sampleThetaVolumes[i]); } catch (const DataFileException& dfe) { if (errorMessageOut.isEmpty() == false) { errorMessageOut += "\n"; } errorMessageOut += dfe.whatString(); } } if (errorMessageOut.isEmpty()) { std::vector dims; for (std::vector::iterator iter = allVolumes.begin(); iter != allVolumes.end(); iter++) { VolumeFile* vf = *iter; std::vector volDims; vf->getDimensions(volDims); if (dims.empty()) { dims = volDims; } else if (dims != volDims) { errorMessageOut += "ERROR: Sample volumes have mis-matched dimensions"; } } m_sampleVolumesValid = true; } m_sampleVolumesLoadAttemptValid = true; } } if (m_sampleVolumesValid) { return true; } return false; } workbench-1.1.1/src/Brain/FiberOrientationSamplesLoader.h000066400000000000000000000062451255417355300234270ustar00rootroot00000000000000#ifndef __FIBER_ORIENTATION_SAMPLES_LOADER_H__ #define __FIBER_ORIENTATION_SAMPLES_LOADER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "EventListenerInterface.h" #include "FiberOrientationSamplesVector.h" namespace caret { class Brain; class FiberOrientation; class VolumeFile; class FiberOrientationSamplesLoader : public CaretObject, public EventListenerInterface { public: FiberOrientationSamplesLoader(); virtual ~FiberOrientationSamplesLoader(); bool getFiberOrientationSphericalSamplesVectors(Brain* brain, std::vector& xVectors, std::vector& yVectors, std::vector& zVectors, FiberOrientation* &fiberOrientationOut, AString& errorMessageOut); void reset(); void receiveEvent(Event* event); private: FiberOrientationSamplesLoader(const FiberOrientationSamplesLoader&); FiberOrientationSamplesLoader& operator=(const FiberOrientationSamplesLoader&); bool loadSphericalOrientationVolumes(Brain* brain, AString& errorMessageOut); /** Tried to load sample volumes since last reset (they may or may not be valid) */ bool m_sampleVolumesLoadAttemptValid; /** Sample volumes were loaded and are valid */ bool m_sampleVolumesValid; /* sample magnitude volumes */ VolumeFile* m_sampleMagnitudeVolumes[3]; /* sample theta angle volumes */ VolumeFile* m_sampleThetaVolumes[3]; /* sample phi angle volumes */ VolumeFile* m_samplePhiVolumes[3]; /* last identified location */ float m_lastIdentificationXYZ[3]; /* last identification valid */ bool m_lastIdentificationValid; }; #ifdef __FIBER_ORIENTATION_SAMPLES_LOADER_DECLARE__ // #endif // __FIBER_ORIENTATION_SAMPLES_LOADER_DECLARE__ } // namespace #endif //__FIBER_ORIENTATION_SAMPLES_LOADER_H__ workbench-1.1.1/src/Brain/FiberOrientationSamplesVector.h000066400000000000000000000036651255417355300234660ustar00rootroot00000000000000#ifndef __FIBER_ORIENTATION_SAMPLES_VECTOR_H__ #define __FIBER_ORIENTATION_SAMPLES_VECTOR_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ namespace caret { /** * \struct caret::FiberOrientationSamplesVector * \brief Stores a fiber orientation samples vector * \ingroup Brain */ struct FiberOrientationSamplesVector { /** The direction vector */ float direction[3]; /** The magnitude (length) */ float magnitude; /** RGB coloring for vector */ float rgb[3]; /** * Set the RGB color to absolute values of the directional vector */ void setColor() { rgb[0] = (direction[0] >= 0.0) ? direction[0] : -direction[0]; rgb[1] = (direction[1] >= 0.0) ? direction[1] : -direction[1]; rgb[2] = (direction[2] >= 0.0) ? direction[2] : -direction[2]; } }; #ifdef __FIBER_ORIENTATION_SAMPLES_VECTOR_DECLARE__ // #endif // __FIBER_ORIENTATION_SAMPLES_VECTOR_DECLARE__ } // namespace #endif //__FIBER_ORIENTATION_SAMPLES_VECTOR_H__ workbench-1.1.1/src/Brain/FiberOrientationSymbolTypeEnum.cxx000066400000000000000000000227371255417355300242070ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __FIBER_ORIENTATION_SYMBOL_TYPE_ENUM_DECLARE__ #include "FiberOrientationSymbolTypeEnum.h" #undef __FIBER_ORIENTATION_SYMBOL_TYPE_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::FiberOrientationSymbolTypeEnum * \brief * * */ /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ FiberOrientationSymbolTypeEnum::FiberOrientationSymbolTypeEnum(const Enum enumValue, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCodeCounter++; this->name = name; this->guiName = guiName; } /** * Destructor. */ FiberOrientationSymbolTypeEnum::~FiberOrientationSymbolTypeEnum() { } /** * Initialize the enumerated metadata. */ void FiberOrientationSymbolTypeEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(FiberOrientationSymbolTypeEnum(FIBER_SYMBOL_FANS, "FIBER_SYMBOL_FANS", "Fans")); enumData.push_back(FiberOrientationSymbolTypeEnum(FIBER_SYMBOL_LINES, "FIBER_SYMBOL_LINES", "Lines")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const FiberOrientationSymbolTypeEnum* FiberOrientationSymbolTypeEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const FiberOrientationSymbolTypeEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString FiberOrientationSymbolTypeEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const FiberOrientationSymbolTypeEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ FiberOrientationSymbolTypeEnum::Enum FiberOrientationSymbolTypeEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = FIBER_SYMBOL_LINES; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const FiberOrientationSymbolTypeEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type FiberOrientationSymbolTypeEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString FiberOrientationSymbolTypeEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const FiberOrientationSymbolTypeEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ FiberOrientationSymbolTypeEnum::Enum FiberOrientationSymbolTypeEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = FIBER_SYMBOL_LINES; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const FiberOrientationSymbolTypeEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type FiberOrientationSymbolTypeEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t FiberOrientationSymbolTypeEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const FiberOrientationSymbolTypeEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ FiberOrientationSymbolTypeEnum::Enum FiberOrientationSymbolTypeEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = FIBER_SYMBOL_LINES; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const FiberOrientationSymbolTypeEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type FiberOrientationSymbolTypeEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void FiberOrientationSymbolTypeEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void FiberOrientationSymbolTypeEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(FiberOrientationSymbolTypeEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void FiberOrientationSymbolTypeEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(FiberOrientationSymbolTypeEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } workbench-1.1.1/src/Brain/FiberOrientationSymbolTypeEnum.h000066400000000000000000000064101255417355300236220ustar00rootroot00000000000000#ifndef __FIBER_ORIENTATION_SYMBOL_TYPE_ENUM__H_ #define __FIBER_ORIENTATION_SYMBOL_TYPE_ENUM__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class FiberOrientationSymbolTypeEnum { public: /** * Enumerated values. */ enum Enum { /** Use Fans for drawing fibers */ FIBER_SYMBOL_FANS, /** Use Lines for drawing fibers */ FIBER_SYMBOL_LINES }; ~FiberOrientationSymbolTypeEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); private: FiberOrientationSymbolTypeEnum(const Enum enumValue, const AString& name, const AString& guiName); static const FiberOrientationSymbolTypeEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; #ifdef __FIBER_ORIENTATION_SYMBOL_TYPE_ENUM_DECLARE__ std::vector FiberOrientationSymbolTypeEnum::enumData; bool FiberOrientationSymbolTypeEnum::initializedFlag = false; int32_t FiberOrientationSymbolTypeEnum::integerCodeCounter = 0; #endif // __FIBER_ORIENTATION_SYMBOL_TYPE_ENUM_DECLARE__ } // namespace #endif //__FIBER_ORIENTATION_SYMBOL_TYPE_ENUM__H_ workbench-1.1.1/src/Brain/FociDrawingTypeEnum.cxx000066400000000000000000000217541255417355300217500ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __FOCI_DRAWING_TYPE_ENUM_DECLARE__ #include "FociDrawingTypeEnum.h" #undef __FOCI_DRAWING_TYPE_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::FociDrawingTypeEnum * \brief * * */ /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ FociDrawingTypeEnum::FociDrawingTypeEnum(const Enum enumValue, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCodeCounter++; this->name = name; this->guiName = guiName; } /** * Destructor. */ FociDrawingTypeEnum::~FociDrawingTypeEnum() { } /** * Initialize the enumerated metadata. */ void FociDrawingTypeEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(FociDrawingTypeEnum(DRAW_AS_SPHERES, "DRAW_AS_SPHERES", "Spheres")); enumData.push_back(FociDrawingTypeEnum(DRAW_AS_SQUARES, "DRAW_AS_SQUARES", "Squares")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const FociDrawingTypeEnum* FociDrawingTypeEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const FociDrawingTypeEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString FociDrawingTypeEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const FociDrawingTypeEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ FociDrawingTypeEnum::Enum FociDrawingTypeEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = DRAW_AS_SPHERES; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const FociDrawingTypeEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type FociDrawingTypeEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString FociDrawingTypeEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const FociDrawingTypeEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ FociDrawingTypeEnum::Enum FociDrawingTypeEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = DRAW_AS_SPHERES; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const FociDrawingTypeEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type FociDrawingTypeEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t FociDrawingTypeEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const FociDrawingTypeEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ FociDrawingTypeEnum::Enum FociDrawingTypeEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = DRAW_AS_SPHERES; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const FociDrawingTypeEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type FociDrawingTypeEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void FociDrawingTypeEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void FociDrawingTypeEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(FociDrawingTypeEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void FociDrawingTypeEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(FociDrawingTypeEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } workbench-1.1.1/src/Brain/FociDrawingTypeEnum.h000066400000000000000000000061401255417355300213650ustar00rootroot00000000000000#ifndef __FOCI_DRAWING_TYPE_ENUM__H_ #define __FOCI_DRAWING_TYPE_ENUM__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class FociDrawingTypeEnum { public: /** * Enumerated values for foci drawing type. */ enum Enum { /** Draw as spheres */ DRAW_AS_SPHERES, /** Draw as squares*/ DRAW_AS_SQUARES }; ~FociDrawingTypeEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); private: FociDrawingTypeEnum(const Enum enumValue, const AString& name, const AString& guiName); static const FociDrawingTypeEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; #ifdef __FOCI_DRAWING_TYPE_ENUM_DECLARE__ std::vector FociDrawingTypeEnum::enumData; bool FociDrawingTypeEnum::initializedFlag = false; int32_t FociDrawingTypeEnum::integerCodeCounter = 0; #endif // __FOCI_DRAWING_TYPE_ENUM_DECLARE__ } // namespace #endif //__FOCI_DRAWING_TYPE_ENUM__H_ workbench-1.1.1/src/Brain/FtglFontTextRenderer.cxx000066400000000000000000000360251255417355300221410ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ /**************************************************************************** ** ** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the QtOpenGL module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Digia. For licensing terms and ** conditions see http://qt.digia.com/licensing. For further information ** use the contact form at http://qt.digia.com/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Digia gives you certain additional ** rights. These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #define __FTGL_FONT_TEXT_RENDERER_DECLARE__ #include "FtglFontTextRenderer.h" #undef __FTGL_FONT_TEXT_RENDERER_DECLARE__ #include #include "CaretAssert.h" #include "CaretLogger.h" #include "CaretOpenGLInclude.h" #ifdef HAVE_FREETYPE #include #include using namespace FTGL; #endif // HAVE_FREETYPE using namespace caret; /** * \class caret::FtglFontTextRenderer * \brief Text rendering using QGLWidget. * * Draws text using methods in QGLWidget. * * @param glWidget * QGLWidget that does the text rendering. */ /** * Constructor. */ FtglFontTextRenderer::FtglFontTextRenderer() : BrainOpenGLTextRenderInterface() { #ifdef HAVE_FREETYPE m_boldFont.initialize(":/FtglFonts/VeraBd.ttf"); m_normalFont.initialize(":/FtglFonts/VeraSe.ttf"); #endif // HAVE_FREETYPE } /** * Destructor. */ FtglFontTextRenderer::~FtglFontTextRenderer() { #ifdef HAVE_FREETYPE #endif // HAVE_FREETYPE } /** * @return The font system is valid. */ bool FtglFontTextRenderer::isValid() const { return m_normalFont.m_valid; } /* * Get the font with the given style and height. * Returned font not guaranteed to be desired size. * * @param textStyle * Style of the text. * @param fontHeight * Height of the font. * @return * The font. May be NULL due to */ FTPixmapFont* FtglFontTextRenderer::getFont(const TextStyle textStyle, const int fontHeight) { #ifdef HAVE_FREETYPE FTPixmapFont* pixmapFont = NULL; switch (textStyle) { case BrainOpenGLTextRenderInterface::BOLD: if (m_boldFont.m_valid) { pixmapFont = m_boldFont.m_pixmapFont; } break; case BrainOpenGLTextRenderInterface::NORMAL: if (m_normalFont.m_valid) { pixmapFont = m_normalFont.m_pixmapFont; } break; } if (pixmapFont != NULL) { if ( ! pixmapFont->FaceSize(fontHeight)) { QString msg("Failed to set requested font height=" + AString::number(fontHeight) + "."); if (pixmapFont->FaceSize(14)) { msg += " Defaulting to font height=14"; } else { msg += " Defaulting to font height=14 also failed."; } CaretLogWarning(msg); } return pixmapFont; } CaretLogSevere("Trying to use FTGL Font rendering but font is not valid."); return NULL; #else // HAVE_FREETYPE return NULL; #endif // HAVE_FREETYPE } /** * Draw text at the given window coordinates. * * @param viewport * The current viewport. * @param windowX * X-coordinate in the window of first text character * using the 'alignment' * @param windowY * Y-coordinate in the window at which bottom of text is placed. * @param text * Text that is to be drawn. * @param alignment * Alignment of text * @param textStyle * Style of the text. * @param fontHeight * Height of the text. */ void FtglFontTextRenderer::drawTextAtWindowCoords(const int viewport[4], const int windowX, const int windowY, const QString& text, const TextAlignmentX alignmentX, const TextAlignmentY alignmentY, const TextStyle textStyle, const int fontHeight) { if (text.isEmpty()) { return; } #ifdef HAVE_FREETYPE FTPixmapFont* pixmapFont = getFont(textStyle, fontHeight); if (! pixmapFont) { return; } saveStateOfOpenGL(); /* * Disable depth testing so text not occluded */ glDisable(GL_DEPTH_TEST); /* * Set the orthographic projection so that its origin is in the bottom * left corner. It needs to be there since we are drawing in window * coordinates. We do not know the true size of the window but that * is okay since we can set the orthographic view so that the bottom * left corner is the origin and the top right corner is the top * right corner of the user's viewport. */ glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0, (viewport[2]), 0, (viewport[3]), -1, 1); /* * Viewing projection is just the identity matrix since * we are drawing in window coordinates. */ glMatrixMode(GL_MODELVIEW); glLoadIdentity(); const bool drawCrosshairsAtFontStartingCoordinate = false; if (drawCrosshairsAtFontStartingCoordinate) { GLfloat savedRGBA[4]; glGetFloatv(GL_CURRENT_COLOR, savedRGBA); glColor3f(0.0, 0.0, 0.0); glLineWidth(1.0); glPushMatrix(); glTranslatef(windowX, windowY, 0.0); glBegin(GL_LINES); glVertex2i(-20, 0); glVertex2i( 20, 0); glVertex2i(0, -20); glVertex2i(0, 20); glEnd(); glPopMatrix(); glColor3f(savedRGBA[0], savedRGBA[1], savedRGBA[2]); } const FTBBox bbox = pixmapFont->BBox(text.toAscii().constData()); const FTPoint lower = bbox.Lower(); const FTPoint upper = bbox.Upper(); float textOffsetX = 0; switch (alignmentX) { case BrainOpenGLTextRenderInterface::X_CENTER: textOffsetX = -((upper.X() - lower.X()) / 2.0); break; case BrainOpenGLTextRenderInterface::X_LEFT: textOffsetX = -lower.X(); break; case BrainOpenGLTextRenderInterface::X_RIGHT: textOffsetX = -upper.X(); break; } float textOffsetY = 0; switch (alignmentY) { case BrainOpenGLTextRenderInterface::Y_BOTTOM: textOffsetY = -lower.Y(); break; case BrainOpenGLTextRenderInterface::Y_CENTER: textOffsetY = -((upper.Y() - lower.Y()) / 2.0); break; case BrainOpenGLTextRenderInterface::Y_TOP: textOffsetY = -upper.Y(); break; } float textX = windowX + textOffsetX; float textY = windowY + textOffsetY; glRasterPos2f(textX, textY); pixmapFont->Render(text.toAscii().constData()); restoreStateOfOpenGL(); #else // HAVE_FREETYPE CaretLogSevere("Trying to use FTGL Font rendering but it cannot be used due to FreeType not found."); #endif // HAVE_FREETYPE } /** * Get the bounds of the text (in pixels) using the given text * attributes. * * @param widthOut * Output containing width of text characters. * @param heightOut * Output containing height of text characters. * @param text * Text that is to be drawn. * @param textStyle * Style of the text. * @param fontHeight * Height of the text. */ void FtglFontTextRenderer::getTextBoundsInPixels(int32_t& widthOut, int32_t& heightOut, const QString& text, const TextStyle textStyle, const int fontHeight) { widthOut = 0; heightOut = 0; #ifdef HAVE_FREETYPE FTPixmapFont* pixmapFont = getFont(textStyle, fontHeight); if (! pixmapFont) { return; } const FTBBox bbox = pixmapFont->BBox(text.toAscii().constData()); const FTPoint lower = bbox.Lower(); const FTPoint upper = bbox.Upper(); widthOut = upper.X() - lower.X(); heightOut = upper.Y() - lower.Y(); #endif // HAVE_FREETYPE } /** * Draw text at the given model coordinates. * * @param modelX * X-coordinate in model space of first text character * @param modelY * Y-coordinate in model space. * @param modelZ * Z-coordinate in model space. * @param text * Text that is to be drawn. * @param textStyle * Style of the text. * @param fontHeight * Height of the text. */ void FtglFontTextRenderer::drawTextAtModelCoords(const double modelX, const double modelY, const double modelZ, const QString& text, const TextStyle textStyle, const int fontHeight) { GLdouble modelMatrix[16]; GLdouble projectionMatrix[16]; GLint viewport[4]; glGetDoublev(GL_MODELVIEW_MATRIX, modelMatrix); glGetDoublev(GL_PROJECTION_MATRIX, projectionMatrix); glGetIntegerv(GL_VIEWPORT, viewport); GLdouble windowX, windowY, windowZ; if (gluProject(modelX, modelY, modelZ, modelMatrix, projectionMatrix, viewport, &windowX, &windowY, &windowZ) == GL_TRUE) { drawTextAtWindowCoords(viewport, windowX, windowY, text, X_CENTER, Y_CENTER, textStyle, fontHeight); } else { CaretLogSevere("gluProject() failed for drawing text at model coordinates."); } } /** * Save the state of OpenGL. * Copied from Qt's qgl.cpp, qt_save_gl_state(). */ void FtglFontTextRenderer::saveStateOfOpenGL() { glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS); glPushAttrib(GL_ALL_ATTRIB_BITS); glMatrixMode(GL_TEXTURE); glPushMatrix(); glLoadIdentity(); glMatrixMode(GL_PROJECTION); glPushMatrix(); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glShadeModel(GL_FLAT); glDisable(GL_CULL_FACE); glDisable(GL_LIGHTING); glDisable(GL_STENCIL_TEST); glDisable(GL_DEPTH_TEST); glEnable(GL_BLEND); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); } /** * Restore the state of OpenGL. * Copied from Qt's qgl.cpp, qt_restore_gl_state(). */ void FtglFontTextRenderer::restoreStateOfOpenGL() { glMatrixMode(GL_TEXTURE); glPopMatrix(); glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); glPopAttrib(); glPopClientAttrib(); } /** * Constructs invalid font data. */ FtglFontTextRenderer::FontData::FontData() { m_valid = false; m_pixmapFont = NULL; } /** * Destructs font data. */ FtglFontTextRenderer::FontData::~FontData() { if (m_pixmapFont != NULL) { delete m_pixmapFont; m_pixmapFont = NULL; } } /** * Initialize font data. * * @param fontFileName * Name of font file. */ void FtglFontTextRenderer::FontData::initialize(const AString& fontFileName) { #ifdef HAVE_FREETYPE QFile file(fontFileName); if (file.open(QFile::ReadOnly)) { m_fontData = file.readAll(); const size_t numBytes = m_fontData.size(); if (numBytes > 0) { const unsigned char* data = (const unsigned char*)m_fontData.data(); m_pixmapFont = new FTPixmapFont(data, numBytes); if (m_pixmapFont->Error()) { delete m_pixmapFont; m_pixmapFont = NULL; CaretLogSevere("Unable to load font file " + file.fileName()); return; } } else { CaretLogSevere("Error reading data from " + file.fileName() + " error: " + file.errorString()); return; } } else { CaretLogSevere("Unable to open " + file.fileName() + " error: " + file.errorString()); return; } m_valid = true; #else // HAVE_FREETYPE CaretLogWarning("Trying to initialize FTGL fonts but program was compiled without FreeType.\n" "Text labels may be missing in graphics windows."); #endif // HAVE_FREETYPE } /** * @return Name of the text renderer. */ AString FtglFontTextRenderer::getName() const { return "FTGL Font"; } workbench-1.1.1/src/Brain/FtglFontTextRenderer.h000066400000000000000000000065121255417355300215640ustar00rootroot00000000000000#ifndef __FTGL_FONT_TEXT_RENDERER_H__ #define __FTGL_FONT_TEXT_RENDERER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "BrainOpenGLTextRenderInterface.h" class FTPixmapFont; namespace caret { class FtglFontTextRenderer : public BrainOpenGLTextRenderInterface { public: FtglFontTextRenderer(); virtual ~FtglFontTextRenderer(); bool isValid() const; void drawTextAtWindowCoords(const int viewport[4], const int windowX, const int windowY, const QString& text, const TextAlignmentX alignmentX, const TextAlignmentY alignmentY, const TextStyle textStyle, const int fontHeight); void drawTextAtModelCoords(const double modelX, const double modelY, const double modelZ, const QString& text, const TextStyle textStyle, const int fontHeight); void getTextBoundsInPixels(int32_t& widthOut, int32_t& heightOut, const QString& text, const TextStyle textStyle, const int fontHeight); virtual AString getName() const; private: FtglFontTextRenderer(const FtglFontTextRenderer&); FtglFontTextRenderer& operator=(const FtglFontTextRenderer&); private: FTPixmapFont* getFont(const TextStyle textStyle, const int fontHeight); class FontData { public: FontData(); ~FontData(); void initialize(const AString& fontFileName); QByteArray m_fontData; FTPixmapFont* m_pixmapFont; bool m_valid; }; void saveStateOfOpenGL(); void restoreStateOfOpenGL(); FontData m_normalFont; FontData m_boldFont; }; #ifdef __FTGL_FONT_TEXT_RENDERER_DECLARE__ // #endif // __FTGL_FONT_TEXT_RENDERER_DECLARE__ } // namespace #endif //__FTGL_FONT_TEXT_RENDERER_H__ workbench-1.1.1/src/Brain/IdentificationManager.cxx000066400000000000000000000466651255417355300223210ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __IDENTIFICATION_MANAGER_DECLARE__ #include "IdentificationManager.h" #undef __IDENTIFICATION_MANAGER_DECLARE__ #include "Brain.h" #include "BrowserTabContent.h" #include "CaretAssert.h" #include "CaretLogger.h" #include "EventBrowserTabGetAll.h" #include "EventManager.h" #include "IdentifiedItemNode.h" #include "IdentifiedItemVoxel.h" #include "MathFunctions.h" #include "SceneClassAssistant.h" #include "SceneClass.h" #include "ScenePrimitive.h" using namespace caret; /** * \class caret::IdentificationManager * \brief Manages identified items. */ /** * Constructor. */ IdentificationManager::IdentificationManager() : SceneableInterface() { m_sceneAssistant = new SceneClassAssistant(); m_contralateralIdentificationEnabled = false; m_identificationSymbolColor = CaretColorEnum::WHITE; m_identificationContralateralSymbolColor = CaretColorEnum::LIME; m_identifcationSymbolSize = 3.0; m_identifcationMostRecentSymbolSize = 5.0; m_sceneAssistant->add("m_contralateralIdentificationEnabled", &m_contralateralIdentificationEnabled); m_sceneAssistant->add("m_identifcationSymbolSize", &m_identifcationSymbolSize); m_sceneAssistant->add("m_identifcationMostRecentSymbolSize", &m_identifcationMostRecentSymbolSize); m_sceneAssistant->add("m_identificationSymbolColor", &m_identificationSymbolColor); m_sceneAssistant->add("m_identificationContralateralSymbolColor", &m_identificationContralateralSymbolColor); removeAllIdentifiedItems(); } /** * Destructor. */ IdentificationManager::~IdentificationManager() { removeAllIdentifiedItems(); delete m_sceneAssistant; } /** * Add an identified item. * @param item * Identified item that is added. * NOTE: Takes ownership of this item and will delete, at the appropriate time. * If item is a node and contralateral identification is enabled, the contralateral * structure will be set in the node item. */ void IdentificationManager::addIdentifiedItem(IdentifiedItem* item) { CaretAssert(item); IdentifiedItemNode* nodeItem = dynamic_cast(item); if (nodeItem != NULL) { if (m_contralateralIdentificationEnabled) { const StructureEnum::Enum contralateralStructure = StructureEnum::getContralateralStructure(nodeItem->getStructure()); nodeItem->setContralateralStructure(contralateralStructure); } } addIdentifiedItemPrivate(item); } /** * Add an identified item. * @param item * Identified item that is added. * NOTE: Takes ownership of this item and will delete, at the appropriate time. */ void IdentificationManager::addIdentifiedItemPrivate(IdentifiedItem* item) { CaretAssert(item); m_mostRecentIdentifiedItem = item; m_identifiedItems.push_back(item); } /** * @return String containing identification text for information window. */ AString IdentificationManager::getIdentificationText() const { AString text; for (std::list::const_iterator iter = m_identifiedItems.begin(); iter != m_identifiedItems.end(); iter++) { const IdentifiedItem* item = *iter; if (text.isEmpty() == false) { text += "

"; } text += item->getText(); } return text; } /** * Remove all identification text. Node and voxels items have their text * removed and all other identification items are removed. */ void IdentificationManager::removeIdentificationText() { std::list idItemsToKeep; for (std::list::iterator iter = m_identifiedItems.begin(); iter != m_identifiedItems.end(); iter++) { IdentifiedItem* item = *iter; IdentifiedItemNode* nodeItem = dynamic_cast(item); IdentifiedItemVoxel* voxelItem = dynamic_cast(item); if ((nodeItem != NULL) || (voxelItem != NULL)) { item->clearText(); idItemsToKeep.push_back(item); } else { if (m_mostRecentIdentifiedItem == item) { m_mostRecentIdentifiedItem = NULL; } delete item; } } m_identifiedItems = idItemsToKeep; } /** * Get identified nodes for the surface with the given structure and * number of nodes. * * @param structure * The structure * @param surfaceNumberOfNodes * Number of nodes in surface. */ std::vector IdentificationManager::getNodeIdentifiedItemsForSurface(const StructureEnum::Enum structure, const int32_t surfaceNumberOfNodes) const { std::vector nodeItemsOut; for (std::list::const_iterator iter = m_identifiedItems.begin(); iter != m_identifiedItems.end(); iter++) { const IdentifiedItem* item = *iter; const IdentifiedItemNode* nodeItem = dynamic_cast(item); if (nodeItem != NULL) { if (nodeItem->isValid()) { if (nodeItem->getSurfaceNumberOfNodes() == surfaceNumberOfNodes) { bool useIt = false; if (nodeItem->getStructure() == structure) { useIt = true; } else if (nodeItem->getContralateralStructure() == structure) { useIt = true; } if (useIt) { IdentifiedItemNode nodeID(*nodeItem); const float* symbolRGB = CaretColorEnum::toRGB(m_identificationSymbolColor); nodeID.setSymbolRGB(symbolRGB); const float* contralateralSymbolRGB = CaretColorEnum::toRGB(m_identificationContralateralSymbolColor); nodeID.setContralateralSymbolRGB(contralateralSymbolRGB); if (item == m_mostRecentIdentifiedItem) { nodeID.setSymbolSize(m_identifcationMostRecentSymbolSize); } else { nodeID.setSymbolSize(m_identifcationSymbolSize); } nodeItemsOut.push_back(nodeID); } } } } } return nodeItemsOut; } /** * @return All identified voxels. */ std::vector IdentificationManager::getIdentifiedItemsForVolume() const { std::vector itemsOut; for (std::list::const_iterator iter = m_identifiedItems.begin(); iter != m_identifiedItems.end(); iter++) { const IdentifiedItem* item = *iter; const IdentifiedItemVoxel* voxelItem = dynamic_cast(item); if (voxelItem != NULL) { if (voxelItem->isValid()) { IdentifiedItemVoxel voxelID(*voxelItem); const float* symbolRGB = CaretColorEnum::toRGB(m_identificationSymbolColor); voxelID.setSymbolRGB(symbolRGB); voxelID.setSymbolSize(m_identifcationSymbolSize); itemsOut.push_back(voxelID); } } } return itemsOut; } /** * Remove any identification for the node in the surface with the given * structure and number of nodes. * * @param structure * The structure * @param surfaceNumberOfNodes * Number of nodes in surface. * @param nodeIndex * Index of the node. */ void IdentificationManager::removeIdentifiedNodeItem(const StructureEnum::Enum structure, const int32_t surfaceNumberOfNodes, const int32_t nodeIndex) { for (std::list::iterator iter = m_identifiedItems.begin(); iter != m_identifiedItems.end(); iter++) { IdentifiedItem* item = *iter; const IdentifiedItemNode* node = dynamic_cast(item); if (node != NULL) { if ((node->getStructure() == structure) || (node->getContralateralStructure() == structure)) { if ((node->getSurfaceNumberOfNodes() == surfaceNumberOfNodes) && (node->getNodeIndex() == nodeIndex)) { m_identifiedItems.erase(iter); delete item; return; } } } } } /** * Remove identified voxel at the given coordinate. * * @param xyz * Coordinates for voxel that is to be removed. */ void IdentificationManager::removeIdentifiedVoxelItem(const float xyz[3]) { const float tolerance = 0.01; for (std::list::iterator iter = m_identifiedItems.begin(); iter != m_identifiedItems.end(); iter++) { IdentifiedItem* item = *iter; const IdentifiedItemVoxel* voxel = dynamic_cast(item); if (voxel != NULL) { if (voxel->isValid()) { float voxelXYZ[3]; voxel->getXYZ(voxelXYZ); const float distSQ = MathFunctions::distanceSquared3D(xyz, voxelXYZ); if (distSQ < tolerance) { m_identifiedItems.erase(iter); delete item; return; } } } } } /** * Remove all identified items. */ void IdentificationManager::removeAllIdentifiedItems() { for (std::list::iterator iter = m_identifiedItems.begin(); iter != m_identifiedItems.end(); iter++) { IdentifiedItem* item = *iter; delete item; } m_identifiedItems.clear(); m_mostRecentIdentifiedItem = NULL; } /** * Remove all identification symbols while preserving text. * * Text from identification symbols for surface or volume are * inserted into new identified items and the symbol items * are removed. */ void IdentificationManager::removeAllIdentifiedSymbols() { std::list idItemsToKeep; for (std::list::iterator iter = m_identifiedItems.begin(); iter != m_identifiedItems.end(); iter++) { IdentifiedItem* item = *iter; IdentifiedItemNode* nodeItem = dynamic_cast(item); IdentifiedItemVoxel* voxelItem = dynamic_cast(item); IdentifiedItem* itemToKeep = NULL; if ((nodeItem != NULL) || (voxelItem != NULL)) { if (m_mostRecentIdentifiedItem == item) { m_mostRecentIdentifiedItem = NULL; } itemToKeep = new IdentifiedItem(item->getText()); delete item; } else { itemToKeep = item; } if (itemToKeep != NULL) { if (itemToKeep->getText().isEmpty()) { delete itemToKeep; itemToKeep = NULL; } else { idItemsToKeep.push_back(itemToKeep); } } } m_identifiedItems = idItemsToKeep; } /** * @return Status of contralateral identification. */ bool IdentificationManager::isContralateralIdentificationEnabled() const { return m_contralateralIdentificationEnabled; } /** * Set status of contralateral identification. * @param * New status. */ void IdentificationManager::setContralateralIdentificationEnabled(const bool enabled) { m_contralateralIdentificationEnabled = enabled; } /** * @return The size of the identification symbol */ float IdentificationManager::getIdentificationSymbolSize() const { return m_identifcationSymbolSize; } /** * Set the size of the identification symbol * @param symbolSize * New size of symbol. */ void IdentificationManager::setIdentificationSymbolSize(const float symbolSize) { m_identifcationSymbolSize = symbolSize; } /** * @return The size of the most recent identification symbol */ float IdentificationManager::getMostRecentIdentificationSymbolSize() const { return m_identifcationMostRecentSymbolSize; } /** * Set the size of the most recent identification symbol * @param symbolSize * New size of symbol. */ void IdentificationManager::setMostRecentIdentificationSymbolSize(const float symbolSize) { m_identifcationMostRecentSymbolSize = symbolSize; } /** * @return The color of the identification symbol. */ CaretColorEnum::Enum IdentificationManager::getIdentificationSymbolColor() const { return m_identificationSymbolColor; } /** * Set the color of the identification symbol. * @param color * New color. */ void IdentificationManager::setIdentificationSymbolColor(const CaretColorEnum::Enum color) { m_identificationSymbolColor = color; } /** * @return The color of the contralateral identification symbol. */ CaretColorEnum::Enum IdentificationManager::getIdentificationContralateralSymbolColor() const { return m_identificationContralateralSymbolColor; } /** * Set the color of the contralateral identification symbol. * @param color * New color. */ void IdentificationManager::setIdentificationContralateralSymbolColor(const CaretColorEnum::Enum color) { m_identificationContralateralSymbolColor = color; } /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ SceneClass* IdentificationManager::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { switch (sceneAttributes->getSceneType()) { case SceneTypeEnum::SCENE_TYPE_FULL: break; case SceneTypeEnum::SCENE_TYPE_GENERIC: break; } SceneClass* sceneClass = new SceneClass(instanceName, "IdentificationManager", 1); m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); for (std::list::iterator iter = m_identifiedItems.begin(); iter != m_identifiedItems.end(); iter++) { IdentifiedItem* item = *iter; sceneClass->addClass(item->saveToScene(sceneAttributes, "identifiedItem")); } return sceneClass; } /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass for the instance of a class that implements * this interface. May be NULL for some types of scenes. */ void IdentificationManager::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } removeAllIdentifiedItems(); m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); const int32_t numChildren = sceneClass->getNumberOfObjects(); for (int32_t i = 0; i < numChildren; i++) { const SceneObject* so = sceneClass->getObjectAtIndex(i); if (so->getName() == "identifiedItem") { const SceneClass* sc = dynamic_cast(so); if (sc != NULL) { const AString className = sc->getClassName(); if (className == "IdentifiedItem") { IdentifiedItem* item = new IdentifiedItem(); item->restoreFromScene(sceneAttributes, sc); if (item->isValid()) { addIdentifiedItemPrivate(item); } else { delete item; } } else if (className == "IdentifiedItemNode") { IdentifiedItemNode* item = new IdentifiedItemNode(); item->restoreFromScene(sceneAttributes, sc); if (item->isValid()) { addIdentifiedItemPrivate(item); } else { delete item; } } else if (className == "IdentifiedItemVoxel") { IdentifiedItemVoxel* item = new IdentifiedItemVoxel(); item->restoreFromScene(sceneAttributes, sc); if (item->isValid()) { addIdentifiedItemPrivate(item); } else { delete item; } } else { const AString msg = ("IdentifiedItem from scene is invalid. " "Has a new IdentifiedItem type been added? " "Class name=" + className); CaretAssertMessage(0, msg); CaretLogSevere(msg); } } } } /* * "m_volumeIdentificationEnabled" was removed when volume identification * was made a tab property. If this item is present in the scene, * update volume ID status in all tabs. */ const ScenePrimitive* idPrimitive = sceneClass->getPrimitive("m_volumeIdentificationEnabled"); if (idPrimitive != NULL) { const bool volumeID = sceneClass->getBooleanValue("m_volumeIdentificationEnabled"); EventBrowserTabGetAll allTabs; EventManager::get()->sendEvent(allTabs.getPointer()); std::vector tabContent = allTabs.getAllBrowserTabs(); for (std::vector::iterator iter = tabContent.begin(); iter != tabContent.end(); iter++) { BrowserTabContent* btc = *iter; btc->setIdentificationUpdatesVolumeSlices(volumeID); } } } workbench-1.1.1/src/Brain/IdentificationManager.h000066400000000000000000000104611255417355300217270ustar00rootroot00000000000000#ifndef __IDENTIFICATION_MANAGER_H__ #define __IDENTIFICATION_MANAGER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretColorEnum.h" #include "SceneableInterface.h" #include "StructureEnum.h" namespace caret { class IdentifiedItem; class IdentifiedItemNode; class IdentifiedItemVoxel; class SceneClassAssistant; class IdentificationManager : public SceneableInterface { public: IdentificationManager(); virtual ~IdentificationManager(); void addIdentifiedItem(IdentifiedItem* item); AString getIdentificationText() const; std::vector getNodeIdentifiedItemsForSurface(const StructureEnum::Enum structure, const int32_t surfaceNumberOfNodes) const; std::vector getIdentifiedItemsForVolume() const; void removeIdentifiedNodeItem(const StructureEnum::Enum structure, const int32_t surfaceNumberOfNodes, const int32_t nodeIndex); void removeIdentifiedVoxelItem(const float xyz[3]); void removeIdentificationText(); void removeAllIdentifiedItems(); void removeAllIdentifiedSymbols(); bool isContralateralIdentificationEnabled() const; void setContralateralIdentificationEnabled(const bool enabled); float getIdentificationSymbolSize() const; void setIdentificationSymbolSize(const float symbolSize); float getMostRecentIdentificationSymbolSize() const; void setMostRecentIdentificationSymbolSize(const float symbolSize); CaretColorEnum::Enum getIdentificationSymbolColor() const; void setIdentificationSymbolColor(const CaretColorEnum::Enum color); CaretColorEnum::Enum getIdentificationContralateralSymbolColor() const; void setIdentificationContralateralSymbolColor(const CaretColorEnum::Enum color); virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private: IdentificationManager(const IdentificationManager&); IdentificationManager& operator=(const IdentificationManager&); public: // ADD_NEW_METHODS_HERE private: void addIdentifiedItemPrivate(IdentifiedItem* item); // ADD_NEW_MEMBERS_HERE SceneClassAssistant* m_sceneAssistant; std::list m_identifiedItems; AString m_previousIdentifiedItemsText; IdentifiedItem* m_mostRecentIdentifiedItem; bool m_contralateralIdentificationEnabled; float m_identifcationSymbolSize; float m_identifcationMostRecentSymbolSize; CaretColorEnum::Enum m_identificationSymbolColor; CaretColorEnum::Enum m_identificationContralateralSymbolColor; }; #ifdef __IDENTIFICATION_MANAGER_DECLARE__ // #endif // __IDENTIFICATION_MANAGER_DECLARE__ } // namespace #endif //__IDENTIFICATION_MANAGER_H__ workbench-1.1.1/src/Brain/IdentificationStringBuilder.cxx000066400000000000000000000167111255417355300235110ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "IdentificationStringBuilder.h" using namespace caret; /** * Constructor. * * */ IdentificationStringBuilder::IdentificationStringBuilder() : HtmlStringBuilder() { } /** * Destructor */ IdentificationStringBuilder::~IdentificationStringBuilder() { } /** * Add text to the string. A newline is added at the end. * A colon is added after the bold text but in normal text. * If "normalText" is an empty string, no text is output. * * @param indentFlag Indent the text. * @param boldText The bold text that starts the line. * @param normalText The normal text placed after the bold text. * */ void IdentificationStringBuilder::addLine( const bool indentFlag, const AString& boldText, const AString& normalText) { if (normalText.length() <= 0) { return; } if (indentFlag) { addIndent(); } addBold(boldText); add(": "); add(normalText); this->addLineBreak(); } /** * Add text to the string. A newline is added at the end. * If "normalText" is an empty string, no text is output. * * @param indentFlag Indent the text. * @param normalText The normal text placed after the bold text. * */ void IdentificationStringBuilder::addLine( const bool indentFlag, const AString& normalText) { if (normalText.length() <= 0) { return; } if (indentFlag) { addIndent(); } add(normalText); this->addLineBreak();} /** * Add text to the string. A newline is added at the end. * A colon is added after the bold text but in normal text. * * @param indentFlag Indent the text. * @param boldText The bold text that starts the line. * @param number The number placed after the bold text. * @param displayOnlyIfNumberIsNonZeroFlag - only display text if * number is non-zero. * */ void IdentificationStringBuilder::addLine( const bool indentFlag, const AString& boldText, const int32_t number, const bool displayOnlyIfNumberIsNonZeroFlag) { bool displayIt = true; if (displayOnlyIfNumberIsNonZeroFlag) { displayIt = (number != 0); } if (displayIt) { addLine(indentFlag, boldText, AString::number(number)); } } /** * Add text to the string. A newline is added at the end. * A colon is added after the bold text but in normal text. * * @param indentFlag Indent the text. * @param boldText The bold text that starts the line. * @param number The number placed after the bold text. * @param displayOnlyIfNumberIsNonZeroFlag - only display text if * number is non-zero. * */ void IdentificationStringBuilder::addLine( const bool indentFlag, const AString& boldText, const float number, const bool displayOnlyIfNumberIsNonZeroFlag) { bool displayIt = true; if (displayOnlyIfNumberIsNonZeroFlag) { displayIt = (number != 0.0f); } if (displayIt) { addLine(indentFlag, boldText, AString::number(number)); } } /** * Add text to the string. A newline is added at the end. * A colon is added after the bold text but in normal text. * * @param indentFlag Indent the text. * @param boldText The bold text that starts the line. * @param floatArray The float array placed after the bold text. * @param floatArrayNumberOfElements Number of elements in the array. * @param displayOnlyIfNonZeroElementInArrayFlag - only display text if * array contains at least one non-zero element. * */ void IdentificationStringBuilder::addLine( const bool indentFlag, const AString& boldText, const float floatArray[], const int floatArrayNumberOfElements, const bool displayOnlyIfNonZeroElementInArrayFlag) { bool displayIt = true; if (displayOnlyIfNonZeroElementInArrayFlag) { displayIt = false; for (int64_t i = 0; i < floatArrayNumberOfElements; i++) { if (floatArray[i] != 0.0f) { displayIt = true; break; } } } if (displayIt) { AString sbf; sbf.reserve(1024); for (int i = 0; i < floatArrayNumberOfElements; i++) { if (i == 0) { sbf.append("("); } else { sbf.append(", "); } sbf.append(AString::number(floatArray[i])); if (i == (floatArrayNumberOfElements - 1)) { sbf.append(")"); } } addLine(indentFlag, boldText, sbf); } } /** * Add a list of objects using the "toString()" method. If an * object in the list is null, a single blank character is placed * into the values list. * @param indentFlag true if indentation is needed. * @param boldText Bold text that is displayed before the values. * @param objectList List of objects that have the toString() * value displayed. * @param displayOnlyIfNonNullElementInArrayFlag Text is only * displayed if one of the items in the list is not null. * */ void IdentificationStringBuilder::addLine( const bool indentFlag, const AString& boldText, const std::vector& objectList, const bool displayOnlyIfNonNullElementInArrayFlag) { bool displayIt = true; if (displayOnlyIfNonNullElementInArrayFlag) { displayIt = false; const int64_t numObjects = static_cast(objectList.size()); for (int64_t i = 0; i < numObjects; i++) { if (objectList[i] != NULL) { displayIt = true; break; } } } if (displayIt) { AString sbf; sbf.reserve(2048); int numItems = objectList.size(); for (int i = 0; i < numItems; i++) { if (i == 0) { sbf.append("("); } else { sbf.append(", "); } const CaretObject* obj = objectList[i]; if (obj != NULL) { sbf.append(obj->toString()); } if (i == (numItems - 1)) { sbf.append(")"); } } addLine(indentFlag, boldText, sbf); } } /** * Indent the line. */ void IdentificationStringBuilder::addIndent() { this->addSpaces(4); } workbench-1.1.1/src/Brain/IdentificationStringBuilder.h000066400000000000000000000053751255417355300231420ustar00rootroot00000000000000#ifndef __IDENTIFICATIONSTRINGBUILDER_H__ #define __IDENTIFICATIONSTRINGBUILDER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "HtmlStringBuilder.h" #include #include namespace caret { class IdentificationStringBuilder : public HtmlStringBuilder { public: IdentificationStringBuilder(); virtual ~IdentificationStringBuilder(); private: IdentificationStringBuilder(const IdentificationStringBuilder& o); IdentificationStringBuilder& operator=(const IdentificationStringBuilder& o); public: void addLine( const bool indentFlag, const AString& boldText, const AString& normalText); void addLine( const bool indentFlag, const AString& normalText); void addLine( const bool indentFlag, const AString& boldText, const int32_t number, const bool displayOnlyIfNumberIsNonZeroFlag); void addLine( const bool indentFlag, const AString& boldText, const float number, const bool displayOnlyIfNumberIsNonZeroFlag); void addLine(const bool indentFlag, const AString& boldText, const float floatArray[], const int floatArrayNumberOfElements, const bool displayOnlyIfNonZeroElementInArrayFlag); void addLine( const bool indentFlag, const AString& boldText, const std::vector& objectList, const bool displayOnlyIfNonNullElementInArrayFlag); void addIndent(); }; } // namespace #endif // __IDENTIFICATIONSTRINGBUILDER_H__ workbench-1.1.1/src/Brain/IdentificationTextGenerator.cxx000066400000000000000000001332211255417355300235230ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __IDENTIFICATION_TEXT_GENERATOR_DECLARE__ #include "IdentificationTextGenerator.h" #undef __IDENTIFICATION_TEXT_GENERATOR_DECLARE__ #include "Border.h" #include "Brain.h" #include "BrainStructure.h" #include "BrowserTabContent.h" #include "CaretAssert.h" #include "CaretMappableDataFile.h" #include "ChartDataCartesian.h" #include "ChartDataSource.h" #include "ChartModelDataSeries.h" #include "ChartableMatrixInterface.h" #include "CiftiMappableConnectivityMatrixDataFile.h" #include "CiftiMappableDataFile.h" #include "CaretVolumeExtension.h" #include "EventBrowserTabGetAll.h" #include "EventManager.h" #include "FileInformation.h" #include "FociFile.h" #include "Focus.h" #include "GiftiLabel.h" #include "OverlaySet.h" #include "SelectionItemBorderSurface.h" #include "SelectionItemChartDataSeries.h" #include "SelectionItemChartFrequencySeries.h" #include "SelectionItemChartMatrix.h" #include "SelectionItemCiftiConnectivityMatrixRowColumn.h" #include "SelectionItemChartTimeSeries.h" #include "SelectionItemFocusSurface.h" #include "SelectionItemFocusVolume.h" #include "SelectionItemSurfaceNode.h" #include "SelectionItemVoxel.h" #include "SelectionManager.h" #include "IdentificationStringBuilder.h" #include "LabelFile.h" #include "MetricFile.h" #include "Surface.h" #include "SurfaceProjectedItem.h" #include "SurfaceProjectionBarycentric.h" #include "SurfaceProjectionVanEssen.h" #include "VolumeFile.h" using namespace caret; /** * \class IdentificationTextGenerator * \brief Creates text describing selected data. * * Examine the selected data and generate descriptive text. */ /** * Constructor. */ IdentificationTextGenerator::IdentificationTextGenerator() : CaretObject() { } /** * Destructor. */ IdentificationTextGenerator::~IdentificationTextGenerator() { } /** * Create identification text from selection in the identification manager. * @param idManager * Identification manager containing selection. * @param brain * The brain. */ AString IdentificationTextGenerator::createIdentificationText(const SelectionManager* idManager, const Brain* brain) const { CaretAssert(idManager); CaretAssert(brain); IdentificationStringBuilder idText; const SelectionItemSurfaceNode* surfaceID = idManager->getSurfaceNodeIdentification(); this->generateSurfaceIdentificationText(idText, brain, surfaceID); this->generateSurfaceBorderIdentifcationText(idText, idManager->getSurfaceBorderIdentification()); this->generateSurfaceFociIdentifcationText(idText, idManager->getSurfaceFocusIdentification()); this->generateVolumeFociIdentifcationText(idText, idManager->getVolumeFocusIdentification()); this->generateVolumeIdentificationText(idText, brain, idManager->getVoxelIdentification()); this->generateChartDataSeriesIdentificationText(idText, idManager->getChartDataSeriesIdentification()); this->generateChartFrequencySeriesIdentificationText(idText, idManager->getChartFrequencySeriesIdentification()); this->generateChartTimeSeriesIdentificationText(idText, idManager->getChartTimeSeriesIdentification()); this->generateChartMatrixIdentificationText(idText, idManager->getChartMatrixIdentification()); this->generateCiftiConnectivityMatrixIdentificationText(idText, idManager->getCiftiConnectivityMatrixRowColumnIdentification()); return idText.toString(); } /** * Generate identification text for volume voxel identification. * * @param idText * String builder for identification text. * @param brain * The brain. * @param idVolumeVoxel * Information for volume voxel ID. */ void IdentificationTextGenerator::generateVolumeIdentificationText(IdentificationStringBuilder& idText, const Brain* brain, const SelectionItemVoxel* idVolumeVoxel) const { if (idVolumeVoxel->isValid() == false) { return; } int64_t ijk[3]; const VolumeMappableInterface* idVolumeFile = idVolumeVoxel->getVolumeFile(); idVolumeVoxel->getVoxelIJK(ijk); float x, y, z; idVolumeFile->indexToSpace(ijk[0], ijk[1], ijk[2], x, y, z); idText.addLine(false, "Voxel XYZ (" + AString::number(x) + ", " + AString::number(y) + ", " + AString::number(z) + ")"); const float xyz[3] = { x, y, z }; /* * Get all volume files */ std::vector volumeInterfaces; const int32_t numVolumeFiles = brain->getNumberOfVolumeFiles(); for (int32_t i = 0; i < numVolumeFiles; i++) { const VolumeFile* vf = brain->getVolumeFile(i); volumeInterfaces.push_back(vf); } /* * Get the CIFTI files that are volume mappable */ std::vector allCiftiMappableDataFiles; brain->getAllCiftiMappableDataFiles(allCiftiMappableDataFiles); for (std::vector::iterator ciftiMapIter = allCiftiMappableDataFiles.begin(); ciftiMapIter != allCiftiMappableDataFiles.end(); ciftiMapIter++) { const CiftiMappableDataFile* cmdf = *ciftiMapIter; if (cmdf->isEmpty() == false) { if (cmdf->isVolumeMappable()) { volumeInterfaces.push_back(cmdf); } } } /* * In first loop, show values for 'idVolumeFile' (the underlay volume) * In second loop, show values for all other volume files */ const int32_t numberOfVolumeMappableFiles = static_cast(volumeInterfaces.size()); for (int32_t iLoop = 0; iLoop < 2; iLoop++) { for (int32_t i = 0; i < numberOfVolumeMappableFiles; i++) { const VolumeMappableInterface* volumeInterfaceFile = volumeInterfaces[i]; const VolumeFile* volumeFile = dynamic_cast(volumeInterfaceFile); const CiftiMappableDataFile* ciftiFile = dynamic_cast(volumeInterfaceFile); CaretAssert((volumeFile != NULL) || (ciftiFile != NULL)); const CaretMappableDataFile* caretMappableDataFile = dynamic_cast(volumeInterfaceFile); CaretAssert(caretMappableDataFile != NULL); if (volumeInterfaceFile == idVolumeFile) { if (iLoop != 0) { continue; } } else if (iLoop == 0) { continue; } int64_t vfI, vfJ, vfK; volumeInterfaceFile->enclosingVoxel(x, y, z, vfI, vfJ, vfK); if (volumeInterfaceFile->indexValid(vfI, vfJ, vfK)) { if (volumeFile != NULL) { AString boldText = caretMappableDataFile->getFileNameNoPath(); boldText += (" IJK (" + AString::number(vfI) + ", " + AString::number(vfJ) + ", " + AString::number(vfK) + ") "); AString text; const int32_t numMaps = caretMappableDataFile->getNumberOfMaps(); for (int jMap = 0; jMap < numMaps; jMap++) { if (jMap > 0) { text += " "; } if (volumeFile != NULL) { if (volumeFile->getType() == SubvolumeAttributes::LABEL) { const int32_t labelIndex = static_cast(volumeFile->getValue(vfI, vfJ, vfK, jMap)); const GiftiLabelTable* glt = volumeFile->getMapLabelTable(jMap); const GiftiLabel* gl = glt->getLabel(labelIndex); if (gl != NULL) { text += gl->getName(); } else { text += ("LABLE_MISSING_FOR_INDEX=" + AString::number(labelIndex)); } } else { text += AString::number(volumeFile->getValue(vfI, vfJ, vfK, jMap)); } } else if (ciftiFile != NULL) { } } idText.addLine(true, boldText, text); } else if (ciftiFile != NULL) { if (ciftiFile->isEmpty() == false) { const int numMaps = ciftiFile->getNumberOfMaps(); std::vector mapIndices; for (int32_t i = 0; i < numMaps; i++) { mapIndices.push_back(i); } /* * Limit dense scalar and data series to maps selected in the overlays * from all tabs. */ bool limitMapIndicesFlag = false; switch (ciftiFile->getDataFileType()) { case DataFileTypeEnum::BORDER: break; case DataFileTypeEnum::CONNECTIVITY_DENSE: break; case DataFileTypeEnum::CONNECTIVITY_DENSE_LABEL: break; case DataFileTypeEnum::CONNECTIVITY_DENSE_PARCEL: break; case DataFileTypeEnum::CONNECTIVITY_DENSE_SCALAR: limitMapIndicesFlag = true; break; case DataFileTypeEnum::CONNECTIVITY_DENSE_TIME_SERIES: limitMapIndicesFlag = true; break; case DataFileTypeEnum::CONNECTIVITY_FIBER_ORIENTATIONS_TEMPORARY: break; case DataFileTypeEnum::CONNECTIVITY_FIBER_TRAJECTORY_TEMPORARY: break; case DataFileTypeEnum::CONNECTIVITY_PARCEL: break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_DENSE: break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_LABEL: limitMapIndicesFlag = true; break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_SCALAR: limitMapIndicesFlag = true; break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_SERIES: limitMapIndicesFlag = true; break; case DataFileTypeEnum::CONNECTIVITY_SCALAR_DATA_SERIES: break; case DataFileTypeEnum::FOCI: break; case DataFileTypeEnum::IMAGE: break; case DataFileTypeEnum::LABEL: break; case DataFileTypeEnum::METRIC: break; case DataFileTypeEnum::PALETTE: break; case DataFileTypeEnum::RGBA: break; case DataFileTypeEnum::SCENE: break; case DataFileTypeEnum::SPECIFICATION: break; case DataFileTypeEnum::SURFACE: break; case DataFileTypeEnum::UNKNOWN: CaretAssert(0); break; case DataFileTypeEnum::VOLUME: break; } if (limitMapIndicesFlag) { getMapIndicesOfFileUsedInOverlays(ciftiFile, mapIndices); } // /* // * Limit dense scalar and data series to maps selected in the overlay. // */ // if ((ciftiFile->getDataFileType() == DataFileTypeEnum::CONNECTIVITY_DENSE_SCALAR) // || (ciftiFile->getDataFileType() == DataFileTypeEnum::CONNECTIVITY_DENSE_TIME_SERIES)) { // getMapIndicesOfFileUsedInOverlays(ciftiFile, // mapIndices); // } AString textValue; int64_t voxelIJK[3]; if (ciftiFile->getVolumeVoxelIdentificationForMaps(mapIndices, xyz, voxelIJK, textValue)) { AString boldText = (DataFileTypeEnum::toOverlayTypeName(ciftiFile->getDataFileType()) + " " + ciftiFile->getFileNameNoPath() + " IJK (" + AString::number(voxelIJK[0]) + ", " + AString::number(voxelIJK[1]) + ", " + AString::number(voxelIJK[2]) + ") "); idText.addLine(true, boldText, textValue); } } } } } } } /** * Generate identification text for a surface node identification. * @param idText * String builder for identification text. * @param brain * The brain. * @param browserTabContent * Content of the browser tab. * @param idSurfaceNode * Information for surface node ID. */ void IdentificationTextGenerator::generateSurfaceIdentificationText(IdentificationStringBuilder& idText, const Brain* brain, const SelectionItemSurfaceNode* idSurfaceNode) const { const Surface* surface = idSurfaceNode->getSurface(); const int32_t nodeNumber = idSurfaceNode->getNodeNumber(); if ((surface != NULL) && (nodeNumber >= 0)) { AString surfaceID; surfaceID += ("VERTEX " + StructureEnum::toGuiName(surface->getStructure())); idText.addLine(false, surfaceID, nodeNumber, false); const float* xyz = surface->getCoordinate(nodeNumber); idText.addLine(true, SurfaceTypeEnum::toGuiName(surface->getSurfaceType()).toUpper() + " XYZ: " + AString::number(xyz[0]) + ", " + AString::number(xyz[1]) + ", " + AString::number(xyz[2])); const BrainStructure* brainStructure = surface->getBrainStructure(); CaretAssert(brainStructure); // std::vector allCiftiMappableDataFiles; // brain->getAllCiftiMappableDataFiles(allCiftiMappableDataFiles); // for (std::vector::iterator ciftiMapIter = allCiftiMappableDataFiles.begin(); // ciftiMapIter != allCiftiMappableDataFiles.end(); // ciftiMapIter++) { // const CiftiMappableDataFile* cmdf = *ciftiMapIter; // if (cmdf->isEmpty() == false) { // const int numMaps = cmdf->getNumberOfMaps(); // for (int32_t iMap = 0; iMap < numMaps; iMap++) { // AString textValue; // if (cmdf->getMapSurfaceNodeValue(iMap, surface->getStructure(), nodeNumber, surface->getNumberOfNodes(), textValue)) { // AString boldText = (DataFileTypeEnum::toOverlayTypeName(cmdf->getDataFileType()) // + " " // + cmdf->getFileNameNoPath()); // idText.addLine(true, boldText, textValue); // } // } // } // } std::vector allCiftiMappableDataFiles; brain->getAllCiftiMappableDataFiles(allCiftiMappableDataFiles); for (std::vector::iterator ciftiMapIter = allCiftiMappableDataFiles.begin(); ciftiMapIter != allCiftiMappableDataFiles.end(); ciftiMapIter++) { const CiftiMappableDataFile* cmdf = *ciftiMapIter; AString boldText = (DataFileTypeEnum::toOverlayTypeName(cmdf->getDataFileType()) + " " + cmdf->getFileNameNoPath()); std::vector mapIndices; for (int32_t i = 0; i < cmdf->getNumberOfMaps(); i++) { mapIndices.push_back(i); } /* * Limit dense scalar and data series to maps selected in the overlays * from all tabs. */ bool limitMapIndicesFlag = false; switch (cmdf->getDataFileType()) { case DataFileTypeEnum::BORDER: break; case DataFileTypeEnum::CONNECTIVITY_DENSE: break; case DataFileTypeEnum::CONNECTIVITY_DENSE_LABEL: break; case DataFileTypeEnum::CONNECTIVITY_DENSE_PARCEL: break; case DataFileTypeEnum::CONNECTIVITY_DENSE_SCALAR: limitMapIndicesFlag = true; break; case DataFileTypeEnum::CONNECTIVITY_DENSE_TIME_SERIES: limitMapIndicesFlag = true; break; case DataFileTypeEnum::CONNECTIVITY_FIBER_ORIENTATIONS_TEMPORARY: break; case DataFileTypeEnum::CONNECTIVITY_FIBER_TRAJECTORY_TEMPORARY: break; case DataFileTypeEnum::CONNECTIVITY_PARCEL: break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_DENSE: break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_LABEL: limitMapIndicesFlag = true; break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_SCALAR: limitMapIndicesFlag = true; break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_SERIES: limitMapIndicesFlag = true; break; case DataFileTypeEnum::CONNECTIVITY_SCALAR_DATA_SERIES: break; case DataFileTypeEnum::FOCI: break; case DataFileTypeEnum::IMAGE: break; case DataFileTypeEnum::LABEL: break; case DataFileTypeEnum::METRIC: break; case DataFileTypeEnum::PALETTE: break; case DataFileTypeEnum::RGBA: break; case DataFileTypeEnum::SCENE: break; case DataFileTypeEnum::SPECIFICATION: break; case DataFileTypeEnum::SURFACE: break; case DataFileTypeEnum::UNKNOWN: CaretAssert(0); break; case DataFileTypeEnum::VOLUME: break; } if (limitMapIndicesFlag) { getMapIndicesOfFileUsedInOverlays(cmdf, mapIndices); } AString textValue; const bool valid = cmdf->getSurfaceNodeIdentificationForMaps(mapIndices, surface->getStructure(), nodeNumber, surface->getNumberOfNodes(), textValue); if (valid) { idText.addLine(true, boldText, textValue); } // const CiftiMappableConnectivityMatrixDataFile* connCifti = dynamic_cast(cmdf); // if (cmdf->isEmpty() == false) { // const int numMaps = cmdf->getNumberOfMaps(); // if (numMaps > 0) { // if (connCifti != NULL) { // AString textValue; // const int32_t mapIndex = 0; // if (cmdf->getMapSurfaceNodeValue(mapIndex, surface->getStructure(), nodeNumber, surface->getNumberOfNodes(), textValue)) { // AString boldText = (DataFileTypeEnum::toOverlayTypeName(cmdf->getDataFileType()) // + " " // + cmdf->getFileNameNoPath()); // idText.addLine(true, boldText, textValue); // } // } // else { // AString boldText = (DataFileTypeEnum::toOverlayTypeName(cmdf->getDataFileType()) // + " " // + cmdf->getFileNameNoPath()); // std::vector nodeData; // if (cmdf->getSeriesDataForSurfaceNode(surface->getStructure(), // nodeNumber, // nodeData)) { // for (int32_t iMap = 0; iMap < numMaps; iMap++) { // AString textValue = AString::number(nodeData[iMap]); // idText.addLine(true, boldText, textValue); // } // } // } // } // } } const int32_t numLabelFiles = brainStructure->getNumberOfLabelFiles(); for (int32_t i = 0; i < numLabelFiles; i++) { const LabelFile* lf = brainStructure->getLabelFile(i); AString boldText = "LABEL " + lf->getFileNameNoPath() + ":"; AString text; const int numMaps = lf->getNumberOfMaps(); for (int32_t j = 0; j < numMaps; j++) { AString labelName = lf->getLabelName(nodeNumber, j); if (labelName.isEmpty()) { labelName = ("Map-" + AString::number(j + 1)); } text += (" " + labelName); } idText.addLine(true, boldText, text); } const int32_t numMetricFiles = brainStructure->getNumberOfMetricFiles(); for (int32_t i = 0; i < numMetricFiles; i++) { const MetricFile* mf = brainStructure->getMetricFile(i); AString boldText = "METRIC " + mf->getFileNameNoPath() + ":"; AString text; const int numMaps = mf->getNumberOfMaps(); for (int32_t j = 0; j < numMaps; j++) { text += (" " + AString::number(mf->getValue(nodeNumber, j))); } idText.addLine(true, boldText, text); } } } /** * Find the usage of the file's maps in all overlays. * * @param caretMappableDataFile * The file whose usage is desired. * @param mapIndicesOut * Indices of maps of the file that are used in overlays. */ void IdentificationTextGenerator::getMapIndicesOfFileUsedInOverlays(const CaretMappableDataFile* caretMappableDataFile, std::vector& mapIndicesOut) const { mapIndicesOut.clear(); EventBrowserTabGetAll allTabsEvent; EventManager::get()->sendEvent(allTabsEvent.getPointer()); const std::vector allTabs = allTabsEvent.getAllBrowserTabs(); for (std::vector::const_iterator tabIter = allTabs.begin(); tabIter != allTabs.end(); tabIter++) { BrowserTabContent* tabContent = *tabIter; OverlaySet* overlaySet = tabContent->getOverlaySet(); if (overlaySet != NULL) { std::vector mapIndices; overlaySet->getSelectedMapIndicesForFile(caretMappableDataFile, false, // true => enabled overlays mapIndices); mapIndicesOut.insert(mapIndicesOut.end(), mapIndices.begin(), mapIndices.end()); } } /* * Sort and remove all duplicates */ if (mapIndicesOut.empty() == false) { std::sort(mapIndicesOut.begin(), mapIndicesOut.end()); std::vector::iterator uniqueIter = std::unique(mapIndicesOut.begin(), mapIndicesOut.end()); mapIndicesOut.resize(std::distance(mapIndicesOut.begin(), uniqueIter)); } } /** * Generate identification text for a data series chart. * @param idText * String builder for identification text. * @param idChartDataSeries * Information for chart id. */ void IdentificationTextGenerator::generateChartDataSeriesIdentificationText(IdentificationStringBuilder& idText, const SelectionItemChartDataSeries* idChartDataSeries) const { if (idChartDataSeries->isValid()) { //const ChartModelDataSeries* chartModelDataSeries = idChartDataSeries->getChartModelDataSeries(); const ChartDataCartesian* chartDataCartesian = idChartDataSeries->getChartDataCartesian(); const ChartDataSource* chartDataSource = chartDataCartesian->getChartDataSource(); generateChartDataSourceText(idText, "DATA SERIES CHART", chartDataSource); } } /** * Generate identification text for a data series chart. * @param idText * String builder for identification text. * @param idChartDataSeries * Information for chart id. */ void IdentificationTextGenerator::generateChartFrequencySeriesIdentificationText(IdentificationStringBuilder& idText, const SelectionItemChartFrequencySeries* idChartFrequencySeries) const { if (idChartFrequencySeries->isValid()) { const ChartDataCartesian* chartDataCartesian = idChartFrequencySeries->getChartDataCartesian(); const ChartDataSource* chartDataSource = chartDataCartesian->getChartDataSource(); generateChartDataSourceText(idText, "FREQUENCY SERIES CHART", chartDataSource); } } /** * Generate identification text for a matrix chart. * @param idText * String builder for identification text. * @param idChartMatrix * Information for matrix chart id. */ void IdentificationTextGenerator::generateChartMatrixIdentificationText(IdentificationStringBuilder& idText, const SelectionItemChartMatrix* idChartMatrix) const { if (idChartMatrix->isValid()) { const ChartableMatrixInterface* chartMatrixInterface = idChartMatrix->getChartableMatrixInterface(); const CaretMappableDataFile* caretMappableDataFile = chartMatrixInterface->getMatrixChartCaretMappableDataFile(); const int32_t rowIndex = idChartMatrix->getMatrixRowIndex(); const int32_t columnIndex = idChartMatrix->getMatrixColumnIndex(); AString rowName; AString columnName; AString cellValue; const bool validData = chartMatrixInterface->getMatrixCellAttributes(rowIndex, columnIndex, cellValue, rowName, columnName); AString boldText("MATRIX CHART"); idText.addLine(false, boldText, caretMappableDataFile->getFileNameNoPath()); if (validData) { idText.addLine(true, ("Row " + AString::number(rowIndex + 1)), rowName); idText.addLine(true, ("Column " + AString::number(columnIndex + 1)), columnName); idText.addLine(true, "Value", cellValue); } } } /** * Generate identification text for a CIFTI Connectivity Matrix Row/Column * @param idText * String builder for identification text. * @param idCiftiConnMatrix * Information for CIFTI Connectivity Matrix Row/Column. */ void IdentificationTextGenerator::generateCiftiConnectivityMatrixIdentificationText(IdentificationStringBuilder& idText, const SelectionItemCiftiConnectivityMatrixRowColumn* idCiftiConnMatrix) const { if (idCiftiConnMatrix->isValid()) { const CiftiMappableConnectivityMatrixDataFile* connMatrixFile = idCiftiConnMatrix->getCiftiConnectivityMatrixFile(); const int32_t rowIndex = idCiftiConnMatrix->getMatrixRowIndex(); const int32_t colIndex = idCiftiConnMatrix->getMatrixColumnIndex(); AString boldText("MATRIX ROW/COLUMN"); idText.addLine(false, boldText, connMatrixFile->getFileNameNoPath()); AString rowName = ""; AString colName = ""; bool validData = true; if (validData) { if (rowIndex >= 0) { idText.addLine(true, ("Row " + AString::number(rowIndex + 1)), rowName); } if (colIndex >= 0) { idText.addLine(true, ("Column " + AString::number(colIndex + 1)), colName); } } } } /** * Generate identification text for chart data source. * @param idText * String builder for identification text. * @param typeOfChartText * Text describing the type of chart. * @param chartDataSource * Source of chart data. */ void IdentificationTextGenerator::generateChartDataSourceText(IdentificationStringBuilder& idText, const AString& typeOfChartText, const ChartDataSource* chartDataSource) const { AString chartFileName = chartDataSource->getChartableFileName(); if (! chartFileName.isEmpty()) { chartFileName = FileInformation(chartFileName).getFileName(); } idText.addLine(false, typeOfChartText, chartDataSource->getChartableFileName()); switch (chartDataSource->getDataSourceMode()) { case ChartDataSourceModeEnum::CHART_DATA_SOURCE_MODE_INVALID: break; case ChartDataSourceModeEnum::CHART_DATA_SOURCE_MODE_FILE_ROW: { AString fileName; int32_t rowIndex; chartDataSource->getFileRow(fileName, rowIndex); idText.addLine(true, "File", fileName); idText.addLine(true, "Row", AString::number(rowIndex + 1)); } break; case ChartDataSourceModeEnum::CHART_DATA_SOURCE_MODE_SURFACE_NODE_INDEX: { AString structureName; int32_t numberOfNodes; int32_t nodeIndex; chartDataSource->getSurfaceNode(structureName, numberOfNodes, nodeIndex); idText.addLine(true, "Structure", structureName); idText.addLine(true, "Vertex Index", AString::number(nodeIndex)); } break; case ChartDataSourceModeEnum::CHART_DATA_SOURCE_MODE_SURFACE_NODE_INDICES_AVERAGE: { AString structureName; int32_t numberOfNodes; std::vector nodeIndices; chartDataSource->getSurfaceNodeAverage(structureName, numberOfNodes, nodeIndices); idText.addLine(true, "Structure", structureName); idText.addLine(true, "Vertex Average Count", AString::number(nodeIndices.size())); } break; case ChartDataSourceModeEnum::CHART_DATA_SOURCE_MODE_VOXEL_IJK: { float voxelXYZ[3]; chartDataSource->getVolumeVoxel(voxelXYZ); idText.addLine(true, "Voxel XYZ", AString::fromNumbers(voxelXYZ, 3, ",")); } break; } } /** * Generate identification text for a time series chart. * @param idText * String builder for identification text. * @param idChartTimeSeries * Information for chart id. */ void IdentificationTextGenerator::generateChartTimeSeriesIdentificationText(IdentificationStringBuilder& idText, const SelectionItemChartTimeSeries* idChartTimeSeries) const { if (idChartTimeSeries->isValid()) { //const ChartModelDataSeries* chartModelDataSeries = idChartDataSeries->getChartModelDataSeries(); const ChartDataCartesian* chartDataCartesian = idChartTimeSeries->getChartDataCartesian(); const ChartDataSource* chartDataSource = chartDataCartesian->getChartDataSource(); generateChartDataSourceText(idText, "TIME SERIES CHART", chartDataSource); } } /** * Generate identification text for a surface border identification. * @param idText * String builder for identification text. * @param idSurfaceBorder * Information for surface border ID. */ void IdentificationTextGenerator::generateSurfaceBorderIdentifcationText(IdentificationStringBuilder& idText, const SelectionItemBorderSurface* idSurfaceBorder) const { if (idSurfaceBorder->isValid()) { const Border* border = idSurfaceBorder->getBorder(); const SurfaceProjectedItem* spi = border->getPoint(idSurfaceBorder->getBorderPointIndex()); float xyz[3]; spi->getProjectedPosition(*idSurfaceBorder->getSurface(), xyz, false); AString boldText = ("BORDER " + StructureEnum::toGuiName(spi->getStructure()) + " Name: " + border->getName()); if (border->getClassName().isEmpty() == false) { boldText += (" ClassName: " + border->getClassName() + ": "); } const AString text = ("(" + AString::number(idSurfaceBorder->getBorderIndex()) + "," + AString::number(idSurfaceBorder->getBorderPointIndex()) + ") (" + AString::fromNumbers(xyz, 3, ",") + ")"); idText.addLine(false, boldText, text); } } /** * Generate identification text for a surface focus identification. * @param idText * String builder for identification text. * @param idSurfaceFocus * Information for surface focus ID. */void IdentificationTextGenerator::generateSurfaceFociIdentifcationText(IdentificationStringBuilder& idText, const SelectionItemFocusSurface* idSurfaceFocus) const { if (idSurfaceFocus->isValid()) { const Focus* focus = idSurfaceFocus->getFocus(); idText.addLine(false, "FOCUS", focus->getName()); idText.addLine(true, "Index", AString::number(idSurfaceFocus->getFocusIndex())); const int32_t projectionIndex = idSurfaceFocus->getFocusProjectionIndex(); const SurfaceProjectedItem* spi = focus->getProjection(projectionIndex); float xyzProj[3]; spi->getProjectedPosition(*idSurfaceFocus->getSurface(), xyzProj, false); float xyzStereo[3]; spi->getStereotaxicXYZ(xyzStereo); idText.addLine(true, "Structure", StructureEnum::toGuiName(spi->getStructure())); if (spi->isStereotaxicXYZValid()) { idText.addLine(true, "XYZ (Stereotaxic)", xyzStereo, 3, true); } else { idText.addLine(true, "XYZ (Stereotaxic)", "Invalid"); } bool projValid = false; AString xyzProjName = "XYZ (Projected)"; if (spi->getBarycentricProjection()->isValid()) { xyzProjName = "XYZ (Projected to Triangle)"; projValid = true; } else if (spi->getVanEssenProjection()->isValid()) { xyzProjName = "XYZ (Projected to Edge)"; projValid = true; } if (projValid) { idText.addLine(true, xyzProjName, xyzProj, 3, true); } else { idText.addLine(true, xyzProjName, "Invalid"); } const int32_t numberOfProjections = focus->getNumberOfProjections(); for (int32_t i = 0; i < numberOfProjections; i++) { if (i != projectionIndex) { const SurfaceProjectedItem* proj = focus->getProjection(i); AString projTypeName = ""; if (proj->getBarycentricProjection()->isValid()) { projTypeName = "Triangle"; } else if (proj->getVanEssenProjection()->isValid()) { projTypeName = "Edge"; } if (projTypeName.isEmpty() == false) { const AString txt = (StructureEnum::toGuiName(proj->getStructure()) + " (" + projTypeName + ")"); idText.addLine(true, "Ambiguous Projection", txt); } } } idText.addLine(true, "Area", focus->getArea()); idText.addLine(true, "Class Name", focus->getClassName()); idText.addLine(true, "Comment", focus->getComment()); idText.addLine(true, "Extent", focus->getExtent(), true); idText.addLine(true, "Geography", focus->getGeography()); idText.addLine(true, "Region of Interest", focus->getRegionOfInterest()); idText.addLine(true, "Statistic", focus->getStatistic()); } } /** * Generate identification text for a volume focus identification. * @param idText * String builder for identification text. * @param idVolumeFocus * Information for surface focus ID. */void IdentificationTextGenerator::generateVolumeFociIdentifcationText(IdentificationStringBuilder& idText, const SelectionItemFocusVolume* idVolumeFocus) const { if (idVolumeFocus->isValid()) { const Focus* focus = idVolumeFocus->getFocus(); const SurfaceProjectedItem* spi = focus->getProjection(idVolumeFocus->getFocusProjectionIndex()); float xyzVolume[3]; spi->getVolumeXYZ(xyzVolume); float xyzStereo[3]; spi->getStereotaxicXYZ(xyzStereo); idText.addLine(false, "FOCUS", focus->getName()); idText.addLine(true, "Index", AString::number(idVolumeFocus->getFocusIndex())); idText.addLine(true, "Structure", StructureEnum::toGuiName(spi->getStructure())); if (spi->isStereotaxicXYZValid()) { idText.addLine(true, "XYZ (Stereotaxic)", xyzStereo, 3, true); } else { idText.addLine(true, "XYZ (Stereotaxic)", "Invalid"); } AString xyzVolumeName = "XYZ (Volume)"; idText.addLine(true, xyzVolumeName, xyzVolume, 3, true); idText.addLine(true, "Area", focus->getArea()); idText.addLine(true, "Class Name", focus->getClassName()); idText.addLine(true, "Comment", focus->getComment()); idText.addLine(true, "Extent", focus->getExtent(), true); idText.addLine(true, "Geography", focus->getGeography()); idText.addLine(true, "Region of Interest", focus->getRegionOfInterest()); idText.addLine(true, "Statistic", focus->getStatistic()); } } /** * Get a description of this object's content. * @return String describing this object's content. */ AString IdentificationTextGenerator::toString() const { return "IdentificationTextGenerator"; } workbench-1.1.1/src/Brain/IdentificationTextGenerator.h000066400000000000000000000116721255417355300231550ustar00rootroot00000000000000#ifndef __IDENTIFICATION_TEXT_GENERATOR__H_ #define __IDENTIFICATION_TEXT_GENERATOR__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" namespace caret { class Brain; class BrowserTabContent; class CaretMappableDataFile; class ChartDataSource; class SelectionItemBorderSurface; class SelectionItemChartDataSeries; class SelectionItemChartFrequencySeries; class SelectionItemChartMatrix; class SelectionItemCiftiConnectivityMatrixRowColumn; class SelectionItemChartTimeSeries; class SelectionItemFocusSurface; class SelectionItemFocusVolume; class SelectionItemSurfaceNode; class SelectionItemVoxel; class SelectionManager; class IdentificationStringBuilder; class IdentificationTextGenerator : public CaretObject { public: IdentificationTextGenerator(); virtual ~IdentificationTextGenerator(); AString createIdentificationText(const SelectionManager* idManager, const Brain* brain) const; private: IdentificationTextGenerator(const IdentificationTextGenerator&); IdentificationTextGenerator& operator=(const IdentificationTextGenerator&); public: virtual AString toString() const; private: void generateSurfaceBorderIdentifcationText(IdentificationStringBuilder& idText, const SelectionItemBorderSurface* idSurfaceBorder) const; void generateSurfaceFociIdentifcationText(IdentificationStringBuilder& idText, const SelectionItemFocusSurface* idSurfaceFocus) const; void generateVolumeFociIdentifcationText(IdentificationStringBuilder& idText, const SelectionItemFocusVolume* idVolumeFocus) const; void generateSurfaceIdentificationText(IdentificationStringBuilder& idText, const Brain* brain, const SelectionItemSurfaceNode* idSurfaceNode) const; void generateVolumeIdentificationText(IdentificationStringBuilder& idText, const Brain* brain, const SelectionItemVoxel* idVolumeVoxel) const; void generateChartDataSeriesIdentificationText(IdentificationStringBuilder& idText, const SelectionItemChartDataSeries* idChartDataSeries) const; void generateChartFrequencySeriesIdentificationText(IdentificationStringBuilder& idText, const SelectionItemChartFrequencySeries* idChartFrequencySeries) const; void generateChartMatrixIdentificationText(IdentificationStringBuilder& idText, const SelectionItemChartMatrix* idChartMatrix) const; void generateCiftiConnectivityMatrixIdentificationText(IdentificationStringBuilder& idText, const SelectionItemCiftiConnectivityMatrixRowColumn* idCiftiConnMatrix) const; void generateChartTimeSeriesIdentificationText(IdentificationStringBuilder& idText, const SelectionItemChartTimeSeries* idChartTimeSeries) const; void getMapIndicesOfFileUsedInOverlays(const CaretMappableDataFile* caretMappableDataFile, std::vector& mapIndicesOut) const; void generateChartDataSourceText(IdentificationStringBuilder& idText, const AString& typeOfChartText, const ChartDataSource* chartDataSource) const; }; #ifdef __IDENTIFICATION_TEXT_GENERATOR_DECLARE__ // #endif // __IDENTIFICATION_TEXT_GENERATOR_DECLARE__ } // namespace #endif //__IDENTIFICATION_TEXT_GENERATOR__H_ workbench-1.1.1/src/Brain/IdentificationWithColor.cxx000066400000000000000000000125751255417355300226520ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __IDENTIFICATION_WITH_COLOR_DECLARE__ #include "IdentificationWithColor.h" #undef __IDENTIFICATION_WITH_COLOR_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class IdentificationWithColor * \brief Assists with identification of items using colors. * * To perform identification of items, an identifier * is encoded as RGB colors. The color present at * the location of identification is then decoded * to recover the identifier. * * Since there can be many items, this class * should be used once for each type of item. */ /** * Constructor. */ IdentificationWithColor::IdentificationWithColor() : CaretObject() { this->itemCounter = 0; this->items.reserve(250000); } /** * Destructor. */ IdentificationWithColor::~IdentificationWithColor() { } /** * Gets an RGB color that is associated with the * specified indices. * @param rgb * Output color components. * @param dataType * Type of data. * @param index1 * First index of item. * @param index2 * Optional second index of item. * @param index3 * Optional third index of item. */ void IdentificationWithColor::addItem(uint8_t rgbOut[4], const SelectionItemDataTypeEnum::Enum dataType, const int32_t index1, const int32_t index2, const int32_t index3) { if (this->itemCounter >= static_cast(this->items.size())) { this->items.push_back(Item()); } IdentificationWithColor::encodeIntegerIntoRGB(this->itemCounter, rgbOut); rgbOut[3] = 255; Item& item = this->items[this->itemCounter]; item.dataType = dataType; item.rgb[0] = rgbOut[0]; item.rgb[1] = rgbOut[1]; item.rgb[2] = rgbOut[2]; item.index1 = index1; item.index2 = index2; item.index3 = index3; itemCounter++; } /** * Gets indices associated with an RGB color. * @param rgbOut * Output color components. * @param dataType * Type of data. * @param index1Out * Set to first index of item. * @param index2 * Set to second index of item. This paramter * may be NULL; * @param index3 * Set to third index of item. This paramter * may be NULL; */ void IdentificationWithColor::getItem(const uint8_t rgb[4], const SelectionItemDataTypeEnum::Enum dataType, int32_t* index1Out, int32_t* index2Out, int32_t* index3Out) const { CaretAssert(index1Out); const int32_t integerValue = IdentificationWithColor::decodeIntegerFromRGB(rgb); *index1Out = -1; if (index2Out != NULL) { *index2Out = -1; } if (index3Out != NULL) { *index3Out = -1; } if ((integerValue < 0) || (integerValue >= this->itemCounter)) { return; } const Item& item = this->items[integerValue]; if (dataType == item.dataType) { *index1Out = item.index1; if (index2Out != NULL) { *index2Out = item.index2; } if (index3Out != NULL) { *index3Out = item.index3; } } } /** * Reset for a new round of identification. * @param estimatedNumberOfItems * The estimated number of items. Must be non-negative * and can improve performance if greater than or equal * to the number of items. */ void IdentificationWithColor::reset(const int32_t estimatedNumberOfItems) { this->itemCounter = 0; if (estimatedNumberOfItems > static_cast(this->items.size())) { this->items.reserve(estimatedNumberOfItems); } } /** * Encode an integer as RGB values. * @param integerValue * Integer value. * @param rgbOut * Output RGB. */ void IdentificationWithColor::encodeIntegerIntoRGB(const int32_t integerValue, uint8_t rgbOut[3]) { rgbOut[2] = (uint8_t)(integerValue & 0xff); rgbOut[1] = (uint8_t)((integerValue >> 8) & 0xff); rgbOut[0] = (uint8_t)((integerValue >> 16) & 0xff); } /** * Decode an integer from RGB values. * @param rgb * RGB value. * @return The integer value. */ int32_t IdentificationWithColor::decodeIntegerFromRGB(const uint8_t rgb[3]) { int32_t r = rgb[0] & 0xff; int32_t g = rgb[1] & 0xff; int32_t b = rgb[2] & 0xff; int32_t colorValue = (r << 16) + (g << 8) + b; return colorValue; } /** * Get a description of this object's content. * @return String describing this object's content. */ AString IdentificationWithColor::toString() const { return "IdentificationWithColor"; } workbench-1.1.1/src/Brain/IdentificationWithColor.h000066400000000000000000000053441255417355300222730ustar00rootroot00000000000000#ifndef __IDENTIFICATION_WITH_COLOR__H_ #define __IDENTIFICATION_WITH_COLOR__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "SelectionItemDataTypeEnum.h" namespace caret { class IdentificationWithColor : public CaretObject { public: IdentificationWithColor(); virtual ~IdentificationWithColor(); void addItem(uint8_t rgbOut[4], const SelectionItemDataTypeEnum::Enum dataType, const int32_t index1, const int32_t index2 = -1, const int32_t index3 = -1); void getItem(const uint8_t rgb[4], const SelectionItemDataTypeEnum::Enum dataType, int32_t* index1Out, int32_t* index2Out = NULL, int32_t* index3Out = NULL) const; void reset(const int32_t estimatedNumberOfItems = -1); private: IdentificationWithColor(const IdentificationWithColor&); IdentificationWithColor& operator=(const IdentificationWithColor&); static void encodeIntegerIntoRGB(const int32_t integerValue, uint8_t rgbOut[3]); static int32_t decodeIntegerFromRGB(const uint8_t rgb[3]); public: virtual AString toString() const; private: class Item { public: SelectionItemDataTypeEnum::Enum dataType; uint8_t rgb[3]; int32_t index1; int32_t index2; int32_t index3; }; std::vector items; int32_t itemCounter; }; #ifdef __IDENTIFICATION_WITH_COLOR_DECLARE__ // #endif // __IDENTIFICATION_WITH_COLOR_DECLARE__ } // namespace #endif //__IDENTIFICATION_WITH_COLOR__H_ workbench-1.1.1/src/Brain/IdentifiedItem.cxx000066400000000000000000000147761255417355300207560ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __IDENTIFIED_ITEM_DECLARE__ #include "IdentifiedItem.h" #undef __IDENTIFIED_ITEM_DECLARE__ #include "SceneClassAssistant.h" using namespace caret; /** * \class caret::IdentifiedItem * \brief Describes an identified item. */ /** * Constructor. * */ IdentifiedItem::IdentifiedItem() : CaretObject() { initializeMembers(); } /** * Constructor. * * @param text * Text describing the identified item. */ IdentifiedItem::IdentifiedItem(const AString& text) : CaretObject() { initializeMembers(); m_text = text; } /** * Destructor. */ IdentifiedItem::~IdentifiedItem() { delete m_sceneAssistant; } /** * Copy constructor. * @param obj * Object that is copied. */ IdentifiedItem::IdentifiedItem(const IdentifiedItem& obj) : CaretObject(obj), SceneableInterface() { this->initializeMembers(); this->copyHelperIdentifiedItem(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ IdentifiedItem& IdentifiedItem::operator=(const IdentifiedItem& obj) { if (this != &obj) { CaretObject::operator=(obj); this->copyHelperIdentifiedItem(obj); } return *this; } /** * Initialize a new instance of this class. */ void IdentifiedItem::initializeMembers() { m_text.clear(); m_showIdentificationSymbol = true; m_sceneAssistant = new SceneClassAssistant(); m_sceneAssistant->add("m_text", &m_text); m_sceneAssistant->add("m_showIdentificationSymbol", &m_showIdentificationSymbol); } /** * Helps with copying an object of this type. * @param obj * Object that is copied. */ void IdentifiedItem::copyHelperIdentifiedItem(const IdentifiedItem& obj) { m_text = obj.m_text; m_showIdentificationSymbol = obj.m_showIdentificationSymbol; } /** * @return Is this item valid? Typically only used when restoring * from scene. */ bool IdentifiedItem::isValid() const { if (m_text.isEmpty() == false) { return true; } return false; } /** * Append text to this item's text. */ void IdentifiedItem::appendText(const AString& text) { m_text += text; } /** * Clear the text for this item. */ void IdentifiedItem::clearText() { m_text = ""; } /** * @return The text describing the identified item. */ AString IdentifiedItem::getText() const { return m_text; } /** * Get a description of this object's content. * @return String describing this object's content. */ AString IdentifiedItem::toString() const { return ("m_text=" + m_text); } /** * @return Is the identification symbol displayed for this item? */ bool IdentifiedItem::isShowIdentificationSymbol() const { return m_showIdentificationSymbol; } /** * Set show identification symbol for this item. * By default the symbol is on but sub-classes * can call this method to change the status. * * @param showSymbol * New status for showing identification symbol. */ void IdentifiedItem::setShowIdentificationSymbol(const bool showSymbol) { m_showIdentificationSymbol = showSymbol; } /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ SceneClass* IdentifiedItem::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "IdentifiedItem", 1); saveMembers(sceneAttributes, sceneClass); return sceneClass; } /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass for the instance of a class that implements * this interface. May be NULL for some types of scenes. */ void IdentifiedItem::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } restoreMembers(sceneAttributes, sceneClass); } /** * Restore members (protected function for derived classes). * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass for the instance of a class that implements * this interface. */ void IdentifiedItem::restoreMembers(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); } /** * Save members (protected function for derived classes). * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass for the instance of a class that implements * this interface. */ void IdentifiedItem::saveMembers(const SceneAttributes* sceneAttributes, SceneClass* sceneClass) { m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); } workbench-1.1.1/src/Brain/IdentifiedItem.h000066400000000000000000000055621255417355300203740ustar00rootroot00000000000000#ifndef __IDENTIFIED_ITEM_H__ #define __IDENTIFIED_ITEM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "SceneableInterface.h" namespace caret { class SceneClassAssistant; class IdentifiedItem : public CaretObject, public SceneableInterface { public: IdentifiedItem(); IdentifiedItem(const AString& text); virtual ~IdentifiedItem(); IdentifiedItem(const IdentifiedItem& obj); IdentifiedItem& operator=(const IdentifiedItem& obj); // ADD_NEW_METHODS_HERE virtual bool isValid() const; void appendText(const AString& text); void clearText(); AString getText() const; bool isShowIdentificationSymbol() const; virtual AString toString() const; virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); protected: virtual void restoreMembers(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); virtual void saveMembers(const SceneAttributes* sceneAttributes, SceneClass* sceneClass); // This method MUST NOT be virtual as it may be called // from a sub-class constructor. void setShowIdentificationSymbol(const bool showSymbol); private: void copyHelperIdentifiedItem(const IdentifiedItem& obj); void initializeMembers(); // ADD_NEW_MEMBERS_HERE AString m_text; bool m_showIdentificationSymbol; SceneClassAssistant* m_sceneAssistant; }; #ifdef __IDENTIFIED_ITEM_DECLARE__ // #endif // __IDENTIFIED_ITEM_DECLARE__ } // namespace #endif //__IDENTIFIED_ITEM_H__ workbench-1.1.1/src/Brain/IdentifiedItemNode.cxx000066400000000000000000000260761255417355300215600ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __IDENTIFIED_ITEM_NODE_DECLARE__ #include "IdentifiedItemNode.h" #undef __IDENTIFIED_ITEM_NODE_DECLARE__ #include "CaretPreferences.h" #include "SceneAttributes.h" #include "SceneClassAssistant.h" #include "SessionManager.h" using namespace caret; /** * \class caret::IdentifiedItem * \brief Describes an identified item. */ /** * Constructor. * * @param text * Text describing the identified item. * @param structure * Structure on which identification took place. * @param contralateralStructure * Contralateral of identification structure. * @param surfaceNumberOfNodes * Number of nodes in the surface on which identification took place. * @param nodeIndex * Index of node that was identified. * */ IdentifiedItemNode::IdentifiedItemNode() : IdentifiedItem() { initializeMembers(); } /** * Constructor. * * @param text * Text describing the identified item. * @param structure * Structure on which identification took place. * @param contralateralStructure * Contralateral of identification structure. * @param surfaceNumberOfNodes * Number of nodes in the surface on which identification took place. * @param nodeIndex * Index of node that was identified. * */ IdentifiedItemNode::IdentifiedItemNode(const AString& text, const StructureEnum::Enum structure, const int32_t surfaceNumberOfNodes, const int32_t nodeIndex) : IdentifiedItem(text) { initializeMembers(); m_structure = structure; m_surfaceNumberOfNodes = surfaceNumberOfNodes, m_nodeIndex = nodeIndex; } /** * Destructor. */ IdentifiedItemNode::~IdentifiedItemNode() { delete m_sceneAssistant; } /** * Copy constructor. * @param obj * Object that is copied. */ IdentifiedItemNode::IdentifiedItemNode(const IdentifiedItemNode& obj) : IdentifiedItem(obj) { initializeMembers(); this->copyHelperIdentifiedItemNode(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ IdentifiedItemNode& IdentifiedItemNode::operator=(const IdentifiedItemNode& obj) { if (this != &obj) { IdentifiedItem::operator=(obj); this->copyHelperIdentifiedItemNode(obj); } return *this; } /** * Helps with copying an object of this type. * @param obj * Object that is copied. */ void IdentifiedItemNode::copyHelperIdentifiedItemNode(const IdentifiedItemNode& obj) { m_structure = obj.m_structure; m_contralateralStructure = obj.m_contralateralStructure; m_surfaceNumberOfNodes = obj.m_surfaceNumberOfNodes; m_nodeIndex = obj.m_nodeIndex; m_symbolRGB[0] = obj.m_symbolRGB[0]; m_symbolRGB[1] = obj.m_symbolRGB[1]; m_symbolRGB[2] = obj.m_symbolRGB[2]; m_contralateralSymbolRGB[0] = obj.m_contralateralSymbolRGB[0]; m_contralateralSymbolRGB[1] = obj.m_contralateralSymbolRGB[1]; m_contralateralSymbolRGB[2] = obj.m_contralateralSymbolRGB[2]; m_symbolSize = obj.m_symbolSize; /* * Even though the show identification symbol is a member * of the superclass, it needs to be copied here since * initializeMembers() as it gets intialized for all * constructors but copying needs to take precedence. */ setShowIdentificationSymbol(obj.isShowIdentificationSymbol()); } /** * Initialize members of this class. */ void IdentifiedItemNode::initializeMembers() { m_structure = StructureEnum::INVALID; m_contralateralStructure = StructureEnum::INVALID; m_surfaceNumberOfNodes = -1, m_nodeIndex = -1; m_sceneAssistant = new SceneClassAssistant(); m_sceneAssistant->add("m_structure", &m_structure); m_sceneAssistant->add("m_contralateralStructure", &m_contralateralStructure); m_sceneAssistant->add("m_surfaceNumberOfNodes", &m_surfaceNumberOfNodes); m_sceneAssistant->add("m_nodeIndex", &m_nodeIndex); m_sceneAssistant->addArray("m_symbolRGB", m_symbolRGB, 3, 0); m_sceneAssistant->addArray("m_contralateralSymbolRGB", m_contralateralSymbolRGB, 3, 0); m_sceneAssistant->add("m_symbolSize", &m_symbolSize); CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); setShowIdentificationSymbol(prefs->isShowSurfaceIdentificationSymbols()); } /** * @return Is this item valid? Typically only used when restoring * from scene. */ bool IdentifiedItemNode::isValid() const { if (m_structure == StructureEnum::INVALID) { return false; } if (m_surfaceNumberOfNodes <= 0) { return false; } if (m_nodeIndex <= 0) { return false; } return true; } /** * @return The structure for the identified node. */ StructureEnum::Enum IdentifiedItemNode::getStructure() const { return m_structure; } /** * @return The contralateral structure of the identified node. */ StructureEnum::Enum IdentifiedItemNode::getContralateralStructure() const { return m_contralateralStructure; } /** * Set the contralateral structure. * @param contralateralStructure * The contralateral structure. */ void IdentifiedItemNode::setContralateralStructure(const StructureEnum::Enum contralateralStructure) { m_contralateralStructure = contralateralStructure; } /** * @return The number of nodes in the surface on which identification took place. */ int32_t IdentifiedItemNode::getSurfaceNumberOfNodes() const { return m_surfaceNumberOfNodes; } /** * @return The index of the surface node that was identified. */ int32_t IdentifiedItemNode::getNodeIndex() const { return m_nodeIndex; } /** * @return The color for the symbol's identification symbol. */ const float* IdentifiedItemNode::getSymbolRGB() const { return m_symbolRGB; } /** * @return The color for the symbol's identification symbol on the * contralateral surface. */ const float* IdentifiedItemNode::getContralateralSymbolRGB() const { return m_contralateralSymbolRGB; } /** * Get color for the identification symbol. * * @param rgbaOut * RGBA ranging 0 to 255. */ void IdentifiedItemNode::getSymbolRGBA(uint8_t rgbaOut[4]) const { rgbaOut[0] = static_cast(m_symbolRGB[0] * 255.0); rgbaOut[1] = static_cast(m_symbolRGB[1] * 255.0); rgbaOut[2] = static_cast(m_symbolRGB[2] * 255.0); rgbaOut[3] = 255; } /** * Get color for the contralateral identification symbol. * * @param rgbaOut * RGBA ranging 0 to 255. */ void IdentifiedItemNode::getContralateralSymbolRGB(uint8_t rgbaOut[4]) const { rgbaOut[0] = static_cast(m_contralateralSymbolRGB[0] * 255.0); rgbaOut[1] = static_cast(m_contralateralSymbolRGB[1] * 255.0); rgbaOut[2] = static_cast(m_contralateralSymbolRGB[2] * 255.0); rgbaOut[3] = 255; } /** * @return The size of the symbol. */ float IdentifiedItemNode::getSymbolSize() const { return m_symbolSize; } /** * Set the color for the identification symbol. * * @param rgb * Red, green, blue color components for identification system. */ void IdentifiedItemNode::setSymbolRGB(const float* rgb) { m_symbolRGB[0] = rgb[0]; m_symbolRGB[1] = rgb[1]; m_symbolRGB[2] = rgb[2]; } /** * Set the color for the contralateral identification symbol. * * @param rgb * Red, green, blue color components for identification system. */ void IdentifiedItemNode::setContralateralSymbolRGB(const float* rgb) { m_contralateralSymbolRGB[0] = rgb[0]; m_contralateralSymbolRGB[1] = rgb[1]; m_contralateralSymbolRGB[2] = rgb[2]; } /** * Set the size of the identification symbol. * * @param symbolSize * Size of identification symbol. */ void IdentifiedItemNode::setSymbolSize(const float symbolSize) { m_symbolSize = symbolSize; } /** * Get a description of this object's content. * @return String describing this object's content. */ AString IdentifiedItemNode::toString() const { const AString s = (IdentifiedItem::toString() + ", m_structure=" + StructureEnum::toName(m_structure) + ", m_contralateralStructure=" + StructureEnum::toName(m_contralateralStructure) + ", m_surfaceNumberOfNodes=" + AString::number(m_surfaceNumberOfNodes) + ", m_nodeIndex=" + AString::number(m_nodeIndex)); return s; } /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ SceneClass* IdentifiedItemNode::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { switch (sceneAttributes->getSceneType()) { case SceneTypeEnum::SCENE_TYPE_FULL: break; case SceneTypeEnum::SCENE_TYPE_GENERIC: break; } SceneClass* sceneClass = new SceneClass(instanceName, "IdentifiedItemNode", 1); m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); /* * Save data in parent class.+ */ saveMembers(sceneAttributes, sceneClass); return sceneClass; } /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass for the instance of a class that implements * this interface. May be NULL for some types of scenes. */ void IdentifiedItemNode::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); /* * Restores data in parent class. */ restoreMembers(sceneAttributes, sceneClass); } workbench-1.1.1/src/Brain/IdentifiedItemNode.h000066400000000000000000000067621255417355300212050ustar00rootroot00000000000000#ifndef __IDENTIFIED_ITEM_NODE_H__ #define __IDENTIFIED_ITEM_NODE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "IdentifiedItem.h" #include "StructureEnum.h" namespace caret { class IdentifiedItemNode : public IdentifiedItem { public: IdentifiedItemNode(); IdentifiedItemNode(const AString& text, const StructureEnum::Enum structure, const int32_t surfaceNumberOfNodes, const int32_t nodeIndex); virtual ~IdentifiedItemNode(); IdentifiedItemNode(const IdentifiedItemNode& obj); IdentifiedItemNode& operator=(const IdentifiedItemNode& obj); // ADD_NEW_METHODS_HERE virtual bool isValid() const; // AString getText() const; StructureEnum::Enum getStructure() const; StructureEnum::Enum getContralateralStructure() const; void setContralateralStructure(const StructureEnum::Enum contralateralStructure); int32_t getSurfaceNumberOfNodes() const; int32_t getNodeIndex() const; const float* getSymbolRGB() const; const float* getContralateralSymbolRGB() const; void getSymbolRGBA(uint8_t rgbaOut[4]) const; void getContralateralSymbolRGB(uint8_t rgbaOut[4]) const; float getSymbolSize() const; void setSymbolRGB(const float* rgb); void setContralateralSymbolRGB(const float* rgb); void setSymbolSize(const float symbolSize); virtual AString toString() const; virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private: void copyHelperIdentifiedItemNode(const IdentifiedItemNode& obj); void initializeMembers(); // ADD_NEW_MEMBERS_HERE StructureEnum::Enum m_structure; StructureEnum::Enum m_contralateralStructure; int32_t m_surfaceNumberOfNodes; int32_t m_nodeIndex; float m_symbolRGB[3]; float m_contralateralSymbolRGB[3]; float m_symbolSize; SceneClassAssistant* m_sceneAssistant; }; #ifdef __IDENTIFIED_ITEM_NODE_DECLARE__ // #endif // __IDENTIFIED_ITEM_NODE_DECLARE__ } // namespace #endif //__IDENTIFIED_ITEM_NODE_H__ workbench-1.1.1/src/Brain/IdentifiedItemVoxel.cxx000066400000000000000000000170631255417355300217640ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __IDENTIFIED_ITEM_VOXEL_DECLARE__ #include "IdentifiedItemVoxel.h" #undef __IDENTIFIED_ITEM_VOXEL_DECLARE__ #include "CaretAssert.h" #include "CaretPreferences.h" #include "SceneClass.h" #include "SceneClassAssistant.h" #include "SessionManager.h" using namespace caret; /** * \class caret::IdentifiedItemVoxel * \brief Identified voxel. * \ingroup Brain */ /** * Constructor. */ IdentifiedItemVoxel::IdentifiedItemVoxel() : IdentifiedItem() { initializeMembers(); } /** * Constructor. */ IdentifiedItemVoxel::IdentifiedItemVoxel(const AString& text, const float xyz[3]) : IdentifiedItem(text) { initializeMembers(); m_xyz[0] = xyz[0]; m_xyz[1] = xyz[1]; m_xyz[2] = xyz[2]; } /** * Destructor. */ IdentifiedItemVoxel::~IdentifiedItemVoxel() { delete m_sceneAssistant; } /** * Copy constructor. * @param obj * Object that is copied. */ IdentifiedItemVoxel::IdentifiedItemVoxel(const IdentifiedItemVoxel& obj) : IdentifiedItem(obj) { initializeMembers(); this->copyHelperIdentifiedItemVoxel(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ IdentifiedItemVoxel& IdentifiedItemVoxel::operator=(const IdentifiedItemVoxel& obj) { if (this != &obj) { IdentifiedItem::operator=(obj); this->copyHelperIdentifiedItemVoxel(obj); } return *this; } /** * Initialize members of this class. */ void IdentifiedItemVoxel::initializeMembers() { m_xyz[0] = 0.0; m_xyz[1] = 0.0; m_xyz[2] = 0.0; m_symbolRGB[0] = 0; m_symbolRGB[1] = 0; m_symbolRGB[1] = 0; m_symbolSize = 0.0; m_sceneAssistant = new SceneClassAssistant(); m_sceneAssistant->addArray("m_xyz", m_xyz, 3, 0.0); m_sceneAssistant->addArray("m_symbolRGB", m_symbolRGB, 3, 0); m_sceneAssistant->add("m_symbolSize", &m_symbolSize); CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); setShowIdentificationSymbol(prefs->isShowVolumeIdentificationSymbols()); } /** * Helps with copying an object of this type. * @param obj * Object that is copied. */ void IdentifiedItemVoxel::copyHelperIdentifiedItemVoxel(const IdentifiedItemVoxel& obj) { m_xyz[0] = obj.m_xyz[0]; m_xyz[1] = obj.m_xyz[1]; m_xyz[2] = obj.m_xyz[2]; m_symbolRGB[0] = obj.m_symbolRGB[0]; m_symbolRGB[1] = obj.m_symbolRGB[1]; m_symbolRGB[2] = obj.m_symbolRGB[2]; m_symbolSize = obj.m_symbolSize; /* * Even though the show identification symbol is a member * of the superclass, it needs to be copied here since * initializeMembers() as it gets intialized for all * constructors but copying needs to take precedence. */ setShowIdentificationSymbol(obj.isShowIdentificationSymbol()); } /** * Get the coordinates of the identified voxel. * * @param xyzOut * Output with coordinates of voxel. */ void IdentifiedItemVoxel::getXYZ(float xyzOut[3]) const { xyzOut[0] = m_xyz[0]; xyzOut[1] = m_xyz[1]; xyzOut[2] = m_xyz[2]; } /** * @return Is this item valid? Typically only used when restoring * from scene. True if the symbol size is greater than zero, * else false. */ bool IdentifiedItemVoxel::isValid() const { if (m_symbolSize > 0.0) { return true; } return true; } /** * @return The color for the symbol's identification symbol. */ const float* IdentifiedItemVoxel::getSymbolRGB() const { return m_symbolRGB; } /** * Get color for the identification symbol. * * @param rgbaOut * RGBA ranging 0 to 255. */ void IdentifiedItemVoxel::getSymbolRGBA(uint8_t rgbaOut[4]) const { rgbaOut[0] = static_cast(m_symbolRGB[0] * 255.0); rgbaOut[1] = static_cast(m_symbolRGB[1] * 255.0); rgbaOut[2] = static_cast(m_symbolRGB[2] * 255.0); rgbaOut[3] = 255; } /** * @return The size of the symbol. */ float IdentifiedItemVoxel::getSymbolSize() const { return m_symbolSize; } /** * Set the color for the identification symbol. * * @param rgb * Red, green, blue color components for identification system. */ void IdentifiedItemVoxel::setSymbolRGB(const float* rgb) { m_symbolRGB[0] = rgb[0]; m_symbolRGB[1] = rgb[1]; m_symbolRGB[2] = rgb[2]; } /** * Set the size of the symbol. * * @param symbolSize * Size of the symbol. */ void IdentifiedItemVoxel::setSymbolSize(const float symbolSize) { m_symbolSize = symbolSize; } /** * Get a description of this object's content. * @return String describing this object's content. */ AString IdentifiedItemVoxel::toString() const { const AString s = (IdentifiedItem::toString() + ", m_xyz=" + AString::fromNumbers(m_xyz, 3, ", ")); return s; } /** * Save information specific to this type of model to the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @param instanceName * Name of instance in the scene. */ SceneClass* IdentifiedItemVoxel::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "IdentifiedItemVoxel", 1); m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); /* * Save data in parent class. */ saveMembers(sceneAttributes, sceneClass); // Uncomment if sub-classes must save to scene //saveSubClassDataToScene(sceneAttributes, // sceneClass); return sceneClass; } /** * Restore information specific to the type of model from the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass from which model specific information is obtained. */ void IdentifiedItemVoxel::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); /* * Restores data in parent class. */ restoreMembers(sceneAttributes, sceneClass); //Uncomment if sub-classes must restore from scene //restoreSubClassDataFromScene(sceneAttributes, // sceneClass); } workbench-1.1.1/src/Brain/IdentifiedItemVoxel.h000066400000000000000000000063251255417355300214100ustar00rootroot00000000000000#ifndef __IDENTIFIED_ITEM_VOXEL_H__ #define __IDENTIFIED_ITEM_VOXEL_H__ /*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "IdentifiedItem.h" namespace caret { class SceneClassAssistant; class IdentifiedItemVoxel : public IdentifiedItem{ public: IdentifiedItemVoxel(); IdentifiedItemVoxel(const AString& text, const float xyz[3]); virtual ~IdentifiedItemVoxel(); IdentifiedItemVoxel(const IdentifiedItemVoxel& obj); IdentifiedItemVoxel& operator=(const IdentifiedItemVoxel& obj); virtual bool isValid() const; void getXYZ(float xyzOut[3]) const; const float* getSymbolRGB() const; void getSymbolRGBA(uint8_t rgbaOut[4]) const; float getSymbolSize() const; void setSymbolRGB(const float* rgb); void setSymbolSize(const float symbolSize); virtual AString toString() const; // ADD_NEW_METHODS_HERE virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); // If there will be sub-classes of this class that need to save // and restore data from scenes, these pure virtual methods can // be uncommented to force their implemetation by sub-classes. // protected: // virtual void saveSubClassDataToScene(const SceneAttributes* sceneAttributes, // SceneClass* sceneClass) = 0; // // virtual void restoreSubClassDataFromScene(const SceneAttributes* sceneAttributes, // const SceneClass* sceneClass) = 0; private: void copyHelperIdentifiedItemVoxel(const IdentifiedItemVoxel& obj); void initializeMembers(); float m_xyz[3]; float m_symbolRGB[3]; float m_symbolSize; SceneClassAssistant* m_sceneAssistant; // ADD_NEW_MEMBERS_HERE }; #ifdef __IDENTIFIED_ITEM_VOXEL_DECLARE__ // #endif // __IDENTIFIED_ITEM_VOXEL_DECLARE__ } // namespace #endif //__IDENTIFIED_ITEM_VOXEL_H__ workbench-1.1.1/src/Brain/Model.cxx000066400000000000000000000342141255417355300171200ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretAssert.h" #include "CaretLogger.h" #include "EventManager.h" #include "Model.h" #include "ModelSurface.h" #include "ModelVolume.h" #include "OverlaySet.h" #include "SceneAttributes.h" #include "SceneClass.h" #include "SceneClassArray.h" #include "Surface.h" using namespace caret; /** * Constructor. * @param m_modelType Type of this model. * @param brain Brain that 'owns' this model. */ Model::Model(const ModelTypeEnum::Enum modelType, Brain* brain) : CaretObject() { m_brain = brain; initializeMembersModel(); m_modelType = modelType; } /** * Destructor */ Model::~Model() { } void Model::initializeMembersModel() { } /** * @return The type of model. */ ModelTypeEnum::Enum Model::getModelType() const { return m_modelType; } /** * Get a String for use in the GUI. * * @return String for use in the GUI. * */ AString Model::toString() const { return getNameForGUI(true); } /** * Get a text description of the window's content. * * @param tabIndex * Index of the tab for content description. * @param descriptionOut * Description of the window's content. */ void Model::getDescriptionOfContent(const int32_t /*tabIndex*/, PlainTextStringBuilder& descriptionOut) const { descriptionOut.addLine(getNameForGUI(true)); } /** * Get the brain that created this model. * @return The brain. */ Brain* Model::getBrain() { return m_brain; } /** * Intended for overriding by sub-classes so that they * can selected the desired surfaces after file loading. */ void Model::initializeSelectedSurfaces() { /* nothing */ } /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @param instanceName * Name of the class' instance. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ SceneClass* Model::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "Model", 1); /* * Do not use scene assistant to save model type since special handling * is needed when it is restored. */ sceneClass->addEnumeratedType("m_modelType", m_modelType); if (m_modelType == ModelTypeEnum::MODEL_TYPE_SURFACE) { const ModelSurface* surfaceModel = dynamic_cast(this); CaretAssert(surfaceModel); sceneClass->addString("surfaceName", surfaceModel->getSurface()->getFileNameNoPath()); } /* * Get indices of tabs that are to be saved to scene. */ const std::vector tabIndices = sceneAttributes->getIndicesOfTabsForSavingToScene(); const int32_t numActiveTabs = static_cast(tabIndices.size()); /* * Save the overlays (except for yoking) */ std::vector overlaySetClassVector; for (int32_t iat = 0; iat < numActiveTabs; iat++) { const int32_t tabIndex = tabIndices[iat]; SceneClass* overlaySetClass = new SceneClass(("modelOverlay[" + AString::number(iat) + "]"), "OverlaySet", 1); overlaySetClass->addInteger("tabIndex", tabIndex); overlaySetClass->addChild(getOverlaySet(tabIndex)->saveToScene(sceneAttributes, "overlaySet")); overlaySetClassVector.push_back(overlaySetClass); } SceneClassArray* overlaySetClassArray = new SceneClassArray("m_overlaySet", overlaySetClassVector); sceneClass->addChild(overlaySetClassArray); /* * Save information specific to the type of model */ saveModelSpecificInformationToScene(sceneAttributes, sceneClass); return sceneClass; } /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass for the instance of a class that implements * this interface. May be NULL for some types of scenes. */ void Model::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } /* * This model was created by the parent scene class. * The model type in the scene should match what was saved. * If not, a serious (programming) error has occurred. */ const ModelTypeEnum::Enum savedModelType = sceneClass->getEnumeratedTypeValue("m_modelType", ModelTypeEnum::MODEL_TYPE_INVALID); if (savedModelType == ModelTypeEnum::MODEL_TYPE_INVALID) { CaretLogSevere("Non-matching model type when restoring scene: " + ModelTypeEnum::toName(savedModelType)); return; } if (savedModelType != m_modelType) { return; } if (m_modelType == ModelTypeEnum::MODEL_TYPE_SURFACE) { const AString surfaceName = sceneClass->getStringValue("surfaceName", "NOT-FOUND"); const ModelSurface* surfaceModel = dynamic_cast(this); CaretAssert(surfaceModel); if (surfaceName != surfaceModel->getSurface()->getFileNameNoPath()) { /* * Exit as this is not the surface for restoring (name does not match) */ return; } } /* * Restore the overlays (except for yoking) */ const SceneClassArray* overlaySetClassArray = sceneClass->getClassArray("m_overlaySet"); if (overlaySetClassArray != NULL) { const int32_t numSavedOverlaySets = overlaySetClassArray->getNumberOfArrayElements(); for (int32_t i = 0; i < numSavedOverlaySets; i++) { const SceneClass* overlaySceneClass = overlaySetClassArray->getClassAtIndex(i); const int32_t tabIndex = overlaySceneClass->getIntegerValue("tabIndex", -1); const SceneClass* overlayClass = overlaySceneClass->getClass("overlaySet"); if ((tabIndex >= 0) && (overlayClass != NULL)) { getOverlaySet(tabIndex)->restoreFromScene(sceneAttributes, overlayClass); } } } /* * Restore any information specific to type of model */ restoreModelSpecificInformationFromScene(sceneAttributes, sceneClass); /* * Check for transformations that are stored in the model's scene. * These are only present in older scene files (circa March 2013 * and earlier). */ m_oldSceneTransformations.resize(BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS); for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_oldSceneTransformations[i].m_rotationValid = false; m_oldSceneTransformations[i].m_scalingValid = false; m_oldSceneTransformations[i].m_translationValid = false; } /* * Restore scaling */ const SceneClassArray* scalingClassArray = sceneClass->getClassArray("m_scaling"); if (scalingClassArray != NULL) { const int32_t numSavedScaling = scalingClassArray->getNumberOfArrayElements(); for (int32_t ism = 0; ism < numSavedScaling; ism++) { const SceneClass* scalingClass = scalingClassArray->getClassAtIndex(ism); const int32_t tabIndex = scalingClass->getIntegerValue("tabIndex", -1); if ((tabIndex >= 0) && (tabIndex < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS)) { m_oldSceneTransformations[tabIndex].m_scaling = scalingClass->getFloatValue("scaling", 1.0); m_oldSceneTransformations[tabIndex].m_scalingValid = true; } } } /* * Restore translation */ const SceneClassArray* translationClassArray = sceneClass->getClassArray("m_translation"); if (translationClassArray != NULL) { const int32_t numSavedTanslations = translationClassArray->getNumberOfArrayElements(); for (int32_t ism = 0; ism < numSavedTanslations; ism++) { const SceneClass* translationClass = translationClassArray->getClassAtIndex(ism); const int32_t tabIndex = translationClass->getIntegerValue("tabIndex", -1); const int32_t viewingTransformIndex = translationClass->getIntegerValue("viewingTransformIndex", -1); if ((tabIndex >= 0) && (tabIndex < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS)) { if (viewingTransformIndex == 0) { if (translationClass->getFloatArrayValue("translation", m_oldSceneTransformations[tabIndex].m_translation, 3) == 3) { m_oldSceneTransformations[tabIndex].m_translationValid = true; } } } } } /* * Restore rotation matrices */ const SceneClassArray* rotationMatrixClassArray = sceneClass->getClassArray("m_viewingRotationMatrix"); if (rotationMatrixClassArray != NULL) { const int32_t numSavedMatrices = rotationMatrixClassArray->getNumberOfArrayElements(); for (int32_t ism = 0; ism < numSavedMatrices; ism++) { const SceneClass* rotationMatrixClass = rotationMatrixClassArray->getClassAtIndex(ism); const int32_t tabIndex = rotationMatrixClass->getIntegerValue("tabIndex", -1); const int32_t viewingTransformIndex = rotationMatrixClass->getIntegerValue("viewingTransformIndex", -1); if ((tabIndex >= 0) && (tabIndex < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS)) { if (viewingTransformIndex == 0) { if (rotationMatrixClass->getFloatArrayValue("matrix", (float*)m_oldSceneTransformations[tabIndex].m_rotationMatrix, 16) == 16) { m_oldSceneTransformations[tabIndex].m_rotationValid = true; } } } } } } /** * Get transformations for a given tab from older scenes that were * created when the transformations were present in every model for * every tab. Transformations have since been moved into the * browser tab content. * * @param tabIndex * Index of tab for transformation. * @param translationOut * The translation for the given tab. * @param scalingOut * The scaling for the given tab. * @param rotationMatrixOut * The rotation matrix for the given tab. * @return * true if the transformations are valid, else false. */ bool Model::getOldSceneTransformation(const int tabIndex, float translationOut[3], float& scalingOut, float rotationMatrixOut[4][4]) const { if ((tabIndex >= 0) && (tabIndex < static_cast(m_oldSceneTransformations.size()))) { const OldSceneTransformation& ost = m_oldSceneTransformations[tabIndex]; if (ost.m_rotationValid && ost.m_scalingValid && ost.m_translationValid) { translationOut[0] = ost.m_translation[0]; translationOut[1] = ost.m_translation[1]; translationOut[2] = ost.m_translation[2]; scalingOut = ost.m_scaling; for (int32_t i = 0; i < 4; i++) { for (int32_t j = 0; j < 4; j++) { rotationMatrixOut[i][j] = ost.m_rotationMatrix[i][j]; } } return true; } } return false; } /** * Copy the tab content from the source tab index to the * destination tab index. * * @param sourceTabIndex * Source from which tab content is copied. * @param destinationTabIndex * Destination to which tab content is copied. */ void Model::copyTabContent(const int32_t /*sourceTabIndex*/, const int32_t /*destinationTabIndex*/) { } workbench-1.1.1/src/Brain/Model.h000066400000000000000000000100531255417355300165400ustar00rootroot00000000000000#ifndef __MODEL_DISPLAY_CONTROLLER_H__ #define __MODEL_DISPLAY_CONTROLLER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "BrainConstants.h" #include "CaretObject.h" #include "ModelTypeEnum.h" #include "SceneableInterface.h" namespace caret { class Brain; class OverlaySet; class PlainTextStringBuilder; /// Base class for a model class Model : public CaretObject, public SceneableInterface { protected: Model(const ModelTypeEnum::Enum modelType, Brain* brain); virtual ~Model(); private: Model(const Model& o); Model& operator=(const Model& o); void initializeMembersModel(); public: virtual void initializeOverlays() = 0; Brain* getBrain(); ModelTypeEnum::Enum getModelType() const; virtual AString getNameForGUI(const bool includeStructureFlag) const = 0; virtual AString getNameForBrowserTab() const = 0; virtual AString toString() const; virtual void getDescriptionOfContent(const int32_t tabIndex, PlainTextStringBuilder& descriptionOut) const; virtual OverlaySet* getOverlaySet(const int tabIndex) = 0; virtual const OverlaySet* getOverlaySet(const int tabIndex) const = 0; virtual void initializeSelectedSurfaces(); virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); bool getOldSceneTransformation(const int tabIndex, float translationOut[3], float& scalingOut, float rotationMatrixOut[4][4]) const; virtual void copyTabContent(const int32_t sourceTabIndex, const int32_t destinationTabIndex); protected: virtual void saveModelSpecificInformationToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass) = 0; virtual void restoreModelSpecificInformationFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) = 0; /** Brain which contains the model */ Brain* m_brain; private: /** * Transformations in older scene files when transforms were stored * in each of the models for every tab. */ class OldSceneTransformation { public: float m_translation[3]; float m_scaling; float m_rotationMatrix[4][4]; bool m_translationValid; bool m_scalingValid; bool m_rotationValid; }; ModelTypeEnum::Enum m_modelType; std::vector m_oldSceneTransformations; }; } // namespace #endif // __MODEL_DISPLAY_CONTROLLER_H__ workbench-1.1.1/src/Brain/ModelChart.cxx000066400000000000000000002275461255417355300201160ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "Brain.h" #include "BrowserTabContent.h" #include "CaretAssert.h" #include "CaretLogger.h" #include "CaretMappableDataFile.h" #include "ChartAxis.h" #include "ChartableLineSeriesBrainordinateInterface.h" #include "CaretDataFileSelectionModel.h" #include "CaretLogger.h" #include "CaretMappableDataFileAndMapSelectionModel.h" #include "ChartableLineSeriesRowColumnInterface.h" #include "ChartableMatrixInterface.h" #include "ChartData.h" #include "ChartDataCartesian.h" #include "ChartDataSource.h" #include "ChartModelDataSeries.h" #include "ChartModelFrequencySeries.h" #include "ChartModelTimeSeries.h" #include "CiftiMappableDataFile.h" #include "CiftiScalarDataSeriesFile.h" #include "EventBrowserTabGetAll.h" #include "EventManager.h" #include "EventNodeIdentificationColorsGetFromCharts.h" #include "ModelChart.h" #include "OverlaySet.h" #include "OverlaySetArray.h" #include "PlainTextStringBuilder.h" #include "SceneClass.h" #include "SceneClassArray.h" #include "SceneClassAssistant.h" #include "SceneObjectMapIntegerKey.h" #include "SurfaceFile.h" using namespace caret; /** * Constructor. * */ ModelChart::ModelChart(Brain* brain) : Model(ModelTypeEnum::MODEL_TYPE_CHART, brain) { std::vector overlaySurfaceStructures; m_overlaySetArray = new OverlaySetArray(overlaySurfaceStructures, Overlay::INCLUDE_VOLUME_FILES_YES, "Chart View"); for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_chartableMatrixFileSelectionModel[i] = CaretDataFileSelectionModel::newInstanceForChartableMatrixParcelInterface(m_brain); m_chartableMatrixSeriesFileSelectionModel[i] = CaretDataFileSelectionModel::newInstanceForCaretDataFileType(m_brain, DataFileTypeEnum::CONNECTIVITY_SCALAR_DATA_SERIES); } initializeCharts(); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_NODE_IDENTIFICATION_COLORS_GET_FROM_CHARTS); } /** * Destructor */ ModelChart::~ModelChart() { delete m_overlaySetArray; EventManager::get()->removeAllEventsFromListener(this); removeAllCharts(); for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { delete m_chartableMatrixFileSelectionModel[i]; m_chartableMatrixFileSelectionModel[i] = NULL; delete m_chartableMatrixSeriesFileSelectionModel[i]; m_chartableMatrixSeriesFileSelectionModel[i] = NULL; } } void ModelChart::initializeCharts() { for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_selectedChartDataType[i] = ChartDataTypeEnum::CHART_DATA_TYPE_INVALID; m_chartModelDataSeries[i] = new ChartModelDataSeries(); m_chartModelDataSeries[i]->getLeftAxis()->setText("Value"); m_chartModelDataSeries[i]->getBottomAxis()->setText("Map Index"); m_chartModelFrequencySeries[i] = new ChartModelFrequencySeries(); m_chartModelFrequencySeries[i]->getLeftAxis()->setText("Value"); m_chartModelFrequencySeries[i]->getBottomAxis()->setText("Frequency"); m_chartModelTimeSeries[i] = new ChartModelTimeSeries(); m_chartModelTimeSeries[i]->getLeftAxis()->setText("Activity"); m_chartModelTimeSeries[i]->getBottomAxis()->setText("Time"); } } /** * Reset this model. */ void ModelChart::reset() { removeAllCharts(); initializeCharts(); } /** * Remove all of the charts. */ void ModelChart::removeAllCharts() { for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { if (m_chartModelDataSeries[i] != NULL) { delete m_chartModelDataSeries[i]; m_chartModelDataSeries[i] = NULL; } if (m_chartModelFrequencySeries[i] != NULL) { delete m_chartModelFrequencySeries[i]; m_chartModelFrequencySeries[i] = NULL; } if (m_chartModelTimeSeries[i] != NULL) { delete m_chartModelTimeSeries[i]; m_chartModelTimeSeries[i] = NULL; } } m_dataSeriesChartData.clear(); m_frequencySeriesChartData.clear(); m_timeSeriesChartData.clear(); m_previousChartMatrixFiles.clear(); } /** * Load chart data for an average of surface nodes. * * @param structure * The surface structure * @param surfaceNumberOfNodes * Number of nodes in surface. * @param nodeIndices * Indices of node. * @throws * DataFileException if there is an error loading data. */ void ModelChart::loadAverageChartDataForSurfaceNodes(const StructureEnum::Enum structure, const int32_t surfaceNumberOfNodes, const std::vector& nodeIndices) { std::map > chartFileEnabledTabs; getTabsAndBrainordinateChartFilesForLineChartLoading(chartFileEnabledTabs); for (std::map >::iterator fileTabIter = chartFileEnabledTabs.begin(); fileTabIter != chartFileEnabledTabs.end(); fileTabIter++) { ChartableLineSeriesBrainordinateInterface* chartFile = fileTabIter->first; const std::vector tabIndices = fileTabIter->second; CaretAssert(chartFile); ChartData* chartData = chartFile->loadAverageLineSeriesChartDataForSurfaceNodes(structure, nodeIndices); if (chartData != NULL) { ChartDataSource* dataSource = chartData->getChartDataSource(); dataSource->setSurfaceNodeAverage(chartFile->getLineSeriesChartCaretMappableDataFile()->getFileName(), StructureEnum::toName(structure), surfaceNumberOfNodes, nodeIndices); addChartToChartModels(tabIndices, chartData); } } } /** * Load chart data for voxel at the given coordinate. * * @param xyz * Coordinate of voxel. * @throws * DataFileException if there is an error loading data. */ void ModelChart::loadChartDataForVoxelAtCoordinate(const float xyz[3]) { std::map > chartFileEnabledTabs; getTabsAndBrainordinateChartFilesForLineChartLoading(chartFileEnabledTabs); for (std::map >::iterator fileTabIter = chartFileEnabledTabs.begin(); fileTabIter != chartFileEnabledTabs.end(); fileTabIter++) { ChartableLineSeriesBrainordinateInterface* chartFile = fileTabIter->first; const std::vector tabIndices = fileTabIter->second; CaretAssert(chartFile); ChartData* chartData = chartFile->loadLineSeriesChartDataForVoxelAtCoordinate(xyz); if (chartData != NULL) { ChartDataSource* dataSource = chartData->getChartDataSource(); dataSource->setVolumeVoxel(chartFile->getLineSeriesChartCaretMappableDataFile()->getFileName(), xyz); addChartToChartModels(tabIndices, chartData); } } } /** * Load chart data for CIFTI Map files yoked to the given yoking group. * * @param mapYokingGroup * The map yoking group. * @param mapIndex * The map index. */ void ModelChart::loadChartDataForYokedCiftiMappableFiles(const MapYokingGroupEnum::Enum mapYokingGroup, const int32_t mapIndex) { if (mapYokingGroup == MapYokingGroupEnum::MAP_YOKING_GROUP_OFF) { return; } std::map > chartFileEnabledTabs; getTabsAndRowColumnChartFilesForLineChartLoading(chartFileEnabledTabs); for (std::map >::iterator fileTabIter = chartFileEnabledTabs.begin(); fileTabIter != chartFileEnabledTabs.end(); fileTabIter++) { ChartableLineSeriesRowColumnInterface* chartFile = fileTabIter->first; CaretAssert(chartFile); CiftiScalarDataSeriesFile* csdsf = dynamic_cast(chartFile); if (csdsf != NULL) { std::vector matchedTabIndices; const std::vector tabIndices = fileTabIter->second; for (std::vector::const_iterator tabIter = tabIndices.begin(); tabIter != tabIndices.end(); tabIter++) { const int32_t tabIndex = *tabIter; if (csdsf->getMatrixRowColumnMapYokingGroup(tabIndex) == mapYokingGroup) { matchedTabIndices.push_back(tabIndex); } } if ( ! matchedTabIndices.empty()) { ChartData* chartData = chartFile->loadLineSeriesChartDataForRow(mapIndex); if (chartData != NULL) { ChartDataSource* dataSource = chartData->getChartDataSource(); dataSource->setFileRow(chartFile->getLineSeriesChartCaretMappableDataFile()->getFileName(), mapIndex); addChartToChartModels(matchedTabIndices, chartData); } } } } } /** * Load chart data from given file at the given row. * * @param ciftiMapFile * The CIFTI file. * @param rowIndex * Index of row in the file. */ void ModelChart::loadChartDataForCiftiMappableFileRow(CiftiMappableDataFile* ciftiMapFile, const int32_t rowIndex) { CaretAssert(ciftiMapFile); std::map > chartFileEnabledTabs; getTabsAndRowColumnChartFilesForLineChartLoading(chartFileEnabledTabs); for (std::map >::iterator fileTabIter = chartFileEnabledTabs.begin(); fileTabIter != chartFileEnabledTabs.end(); fileTabIter++) { ChartableLineSeriesRowColumnInterface* chartFile = fileTabIter->first; if (ciftiMapFile == dynamic_cast(chartFile)) { const std::vector tabIndices = fileTabIter->second; CaretAssert(chartFile); ChartData* chartData = chartFile->loadLineSeriesChartDataForRow(rowIndex); if (chartData != NULL) { ChartDataSource* dataSource = chartData->getChartDataSource(); dataSource->setFileRow(chartFile->getLineSeriesChartCaretMappableDataFile()->getFileName(), rowIndex); addChartToChartModels(tabIndices, chartData); } } } // if (ciftiMapFile != NULL) { // ChartableLineSeriesRowColumnInterface* chartableLineFile = dynamic_cast(ciftiMapFile); // if (chartableLineFile != NULL) { // ChartData* chartData = chartableLineFile->loadLineSeriesChartDataForRow(rowIndex); // if (chartData != NULL) { // } // } // else { // CaretLogSevere("Loading row charts from file type " // + DataFileTypeEnum::toGuiName(ciftiMapFile->getDataFileType()) // + " name " // + ciftiMapFile->getFileName() // + " is not supported."); // } // } } /** * Add the chart to the given tabs. * * @param tabIndices * Indices of tabs for chart data * @param chartData * Chart data that is added. */ void ModelChart::addChartToChartModels(const std::vector& tabIndices, ChartData* chartData) { CaretAssert(chartData); const ChartDataTypeEnum::Enum chartDataDataType = chartData->getChartDataType(); switch (chartDataDataType) { case ChartDataTypeEnum::CHART_DATA_TYPE_INVALID: CaretAssert(0); break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_DATA_SERIES: { ChartDataCartesian* cdc = dynamic_cast(chartData); CaretAssert(cdc); QSharedPointer cdcPtr(cdc); for (std::vector::const_iterator iter = tabIndices.begin(); iter != tabIndices.end(); iter++) { const int32_t tabIndex = *iter; CaretAssertArrayIndex(m_chartModelDataSeries, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_chartModelDataSeries[tabIndex]->addChartData(cdcPtr); } m_dataSeriesChartData.push_front(cdcPtr.toWeakRef()); } break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_FREQUENCY_SERIES: { ChartDataCartesian* cdc = dynamic_cast(chartData); CaretAssert(cdc); QSharedPointer cdcPtr(cdc); for (std::vector::const_iterator iter = tabIndices.begin(); iter != tabIndices.end(); iter++) { const int32_t tabIndex = *iter; CaretAssertArrayIndex(m_chartModelFrequencySeries, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_chartModelFrequencySeries[tabIndex]->addChartData(cdcPtr); } m_frequencySeriesChartData.push_front(cdcPtr.toWeakRef()); } break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_TIME_SERIES: { ChartDataCartesian* cdc = dynamic_cast(chartData); CaretAssert(cdc); QSharedPointer cdcPtr(cdc); for (std::vector::const_iterator iter = tabIndices.begin(); iter != tabIndices.end(); iter++) { const int32_t tabIndex = *iter; CaretAssertArrayIndex(m_chartModelTimeSeries, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_chartModelTimeSeries[tabIndex]->addChartData(cdcPtr); } m_timeSeriesChartData.push_front(cdcPtr.toWeakRef()); } break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_LAYER: CaretAssert(0); break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_SERIES: break; } } /** * Get tabs and brainordinate chart files for loading chart data. * * @param chartBrainordinateFileEnabledTabsOut * Map with first being a chartable file and the second being * tabs for which that chartable file is enabled. */ void ModelChart::getTabsAndBrainordinateChartFilesForLineChartLoading(std::map >& chartBrainordinateFileEnabledTabsOut) const { // chartFileEnabledTabsOut.clear(); // // EventBrowserTabGetAll allTabsEvent; // EventManager::get()->sendEvent(allTabsEvent.getPointer()); // std::vector validTabIndices = allTabsEvent.getBrowserTabIndices(); // // std::vector chartFiles; // m_brain->getAllChartableBrainordinateDataFilesWithChartingEnabled(chartFiles); // // for (std::vector::iterator iter = chartFiles.begin(); // iter != chartFiles.end(); // iter++) { // ChartableLineSeriesBrainordinateInterface* cf = *iter; // std::vector chartFileTabIndices; // // for (std::vector::iterator tabIter = validTabIndices.begin(); // tabIter != validTabIndices.end(); // tabIter++) { // const int32_t tabIndex = *tabIter; // if (cf->isLineSeriesChartingEnabled(tabIndex)) { // chartFileTabIndices.push_back(tabIndex); // } // } // // if ( ! chartFileTabIndices.empty()) { // chartFileEnabledTabsOut.insert(std::make_pair(cf, chartFileTabIndices)); // } // } chartBrainordinateFileEnabledTabsOut.clear(); std::map > chartFileEnabledTabs; getTabsAndLineSeriesChartFilesForLineChartLoading(chartFileEnabledTabs); for (std::map >::iterator iter = chartFileEnabledTabs.begin(); iter != chartFileEnabledTabs.end(); iter++) { ChartableLineSeriesBrainordinateInterface* brainChartFile = dynamic_cast(iter->first); if (brainChartFile != NULL) { chartBrainordinateFileEnabledTabsOut.insert(std::make_pair(brainChartFile, iter->second)); } } } /** * Get tabs and row column chart files for loading chart data. * * @param chartRowColumnFilesEnabledTabsOut * Map with first being a chartable file and the second being * tabs for which that chartable file is enabled. */ void ModelChart::getTabsAndRowColumnChartFilesForLineChartLoading(std::map >& chartRowColumnFilesEnabledTabsOut) const { chartRowColumnFilesEnabledTabsOut.clear(); std::map > chartFileEnabledTabs; getTabsAndLineSeriesChartFilesForLineChartLoading(chartFileEnabledTabs); for (std::map >::iterator iter = chartFileEnabledTabs.begin(); iter != chartFileEnabledTabs.end(); iter++) { ChartableLineSeriesRowColumnInterface* rowColChartFile = dynamic_cast(iter->first); if (rowColChartFile != NULL) { chartRowColumnFilesEnabledTabsOut.insert(std::make_pair(rowColChartFile, iter->second)); } } } /** * Get line series chart files for loading chart data. * * @param chartFileEnabledTabsOut * Map with first being a chartable file and the second being * tabs for which that chartable file is enabled. */ void ModelChart::getTabsAndLineSeriesChartFilesForLineChartLoading(std::map >& chartFileEnabledTabsOut) const { chartFileEnabledTabsOut.clear(); EventBrowserTabGetAll allTabsEvent; EventManager::get()->sendEvent(allTabsEvent.getPointer()); std::vector validTabIndices = allTabsEvent.getBrowserTabIndices(); std::vector chartFiles; m_brain->getAllChartableLineSeriesDataFiles(chartFiles); for (std::vector::iterator iter = chartFiles.begin(); iter != chartFiles.end(); iter++) { ChartableLineSeriesInterface* cf = *iter; std::vector chartFileTabIndices; for (std::vector::iterator tabIter = validTabIndices.begin(); tabIter != validTabIndices.end(); tabIter++) { const int32_t tabIndex = *tabIter; if (cf->isLineSeriesChartingEnabled(tabIndex)) { chartFileTabIndices.push_back(tabIndex); } } if ( ! chartFileTabIndices.empty()) { chartFileEnabledTabsOut.insert(std::make_pair(cf, chartFileTabIndices)); } } } /** * Load chart data for a surface node. * * @param structure * The surface structure * @param surfaceNumberOfNodes * Number of nodes in surface. * @param nodeIndex * Index of node. * @throws * DataFileException if there is an error loading data. */ void ModelChart::loadChartDataForSurfaceNode(const StructureEnum::Enum structure, const int32_t surfaceNumberOfNodes, const int32_t nodeIndex) { std::map > chartFileEnabledTabs; getTabsAndBrainordinateChartFilesForLineChartLoading(chartFileEnabledTabs); for (std::map >::iterator fileTabIter = chartFileEnabledTabs.begin(); fileTabIter != chartFileEnabledTabs.end(); fileTabIter++) { ChartableLineSeriesBrainordinateInterface* chartFile = fileTabIter->first; const std::vector tabIndices = fileTabIter->second; CaretAssert(chartFile); ChartData* chartData = chartFile->loadLineSeriesChartDataForSurfaceNode(structure, nodeIndex); if (chartData != NULL) { ChartDataSource* dataSource = chartData->getChartDataSource(); dataSource->setSurfaceNode(chartFile->getLineSeriesChartCaretMappableDataFile()->getFileName(), StructureEnum::toName(structure), surfaceNumberOfNodes, nodeIndex); addChartToChartModels(tabIndices, chartData); } } } /** * Receive an event. * * @param event * The event that the receive can respond to. */ void ModelChart::receiveEvent(Event* event) { if (event->getEventType() == EventTypeEnum::EVENT_NODE_IDENTIFICATION_COLORS_GET_FROM_CHARTS) { EventNodeIdentificationColorsGetFromCharts* nodeChartID = dynamic_cast(event); CaretAssert(nodeChartID); EventBrowserTabGetAll allTabsEvent; EventManager::get()->sendEvent(allTabsEvent.getPointer()); std::vector validTabIndices = allTabsEvent.getBrowserTabIndices(); const AString structureName = nodeChartID->getStructureName(); std::vector cartesianChartData; for (std::list >::iterator dsIter = m_dataSeriesChartData.begin(); dsIter != m_dataSeriesChartData.end(); dsIter++) { QSharedPointer spCart = dsIter->toStrongRef(); if ( ! spCart.isNull()) { cartesianChartData.push_back(spCart.data()); } } for (std::list >::iterator tsIter = m_frequencySeriesChartData.begin(); tsIter != m_frequencySeriesChartData.end(); tsIter++) { QSharedPointer spCart = tsIter->toStrongRef(); if ( ! spCart.isNull()) { cartesianChartData.push_back(spCart.data()); } } for (std::list >::iterator tsIter = m_timeSeriesChartData.begin(); tsIter != m_timeSeriesChartData.end(); tsIter++) { QSharedPointer spCart = tsIter->toStrongRef(); if ( ! spCart.isNull()) { cartesianChartData.push_back(spCart.data()); } } /* * Iterate over node indices for which colors are desired. */ const std::vector nodeIndices = nodeChartID->getNodeIndices(); for (std::vector::const_iterator nodeIter = nodeIndices.begin(); nodeIter != nodeIndices.end(); nodeIter++) { const int32_t nodeIndex = *nodeIter; /* * Iterate over the data in the cartesian chart */ for (std::vector::iterator cdIter = cartesianChartData.begin(); cdIter != cartesianChartData.end(); cdIter++) { const ChartDataCartesian* cdc = *cdIter; const ChartDataSource* cds = cdc->getChartDataSource(); if (cds->isSurfaceNodeSourceOfData(structureName, nodeIndex)) { /* * Found node index so add its color to the event */ const CaretColorEnum::Enum color = cdc->getColor(); const float* rgb = CaretColorEnum::toRGB(color); nodeChartID->addNode(nodeIndex, rgb); break; } } } nodeChartID->setEventProcessed(); } } /** * Get the name for use in a GUI. * * @param includeStructureFlag - Prefix label with structure to which * this structure model belongs. * @return Name for use in a GUI. * */ AString ModelChart::getNameForGUI(const bool /*includeStructureFlag*/) const { AString name = "Chart"; return name; } /** * @return The name that should be displayed in the tab * displaying this model. */ AString ModelChart::getNameForBrowserTab() const { AString name = "Chart"; return name; } /** * Get the overlay set for the given tab. * @param tabIndex * Index of tab. * @return * Overlay set at the given tab index. */ OverlaySet* ModelChart::getOverlaySet(const int tabIndex) { CaretAssertArrayIndex(m_overlaySetArray, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_overlaySetArray->getOverlaySet(tabIndex); } /** * Get the overlay set for the given tab. * @param tabIndex * Index of tab. * @return * Overlay set at the given tab index. */ const OverlaySet* ModelChart::getOverlaySet(const int tabIndex) const { CaretAssertArrayIndex(m_overlaySetArray, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_overlaySetArray->getOverlaySet(tabIndex); } /** * Initilize the overlays for this model. */ void ModelChart::initializeOverlays() { m_overlaySetArray->initializeOverlaySelections(); } /** * Save information specific to this type of model to the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @param sceneClass * SceneClass to which model specific information is added. */ void ModelChart::saveModelSpecificInformationToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass) { std::vector tabIndices = sceneAttributes->getIndicesOfTabsForSavingToScene(); std::set validChartDataIDs; saveChartModelsToScene(sceneAttributes, sceneClass, tabIndices, validChartDataIDs); sceneClass->addEnumeratedTypeArrayForTabIndices("m_selectedChartDataType", m_selectedChartDataType, tabIndices); /* * Save matrix chart models to scene. */ SceneObjectMapIntegerKey* matrixSceneMap = new SceneObjectMapIntegerKey("chartableMatrixFileSelectionModelMap", SceneObjectDataTypeEnum::SCENE_CLASS); std::vector matrixSelectionVector; for (std::vector::const_iterator tabIter = tabIndices.begin(); tabIter != tabIndices.end(); tabIter++) { const int32_t tabIndex = *tabIter; matrixSceneMap->addClass(tabIndex, m_chartableMatrixFileSelectionModel[tabIndex]->saveToScene(sceneAttributes, "m_chartableMatrixFileSelectionModel")); } sceneClass->addChild(matrixSceneMap); /* * Save matrix series chart models to scene. */ SceneObjectMapIntegerKey* matrixSeriesSceneMap = new SceneObjectMapIntegerKey("chartableMatrixSeriesFileSelectionModelMap", SceneObjectDataTypeEnum::SCENE_CLASS); std::vector matrixSeriesSelectionVector; for (std::vector::const_iterator tabIter = tabIndices.begin(); tabIter != tabIndices.end(); tabIter++) { const int32_t tabIndex = *tabIter; matrixSeriesSceneMap->addClass(tabIndex, m_chartableMatrixSeriesFileSelectionModel[tabIndex]->saveToScene(sceneAttributes, "m_chartableMatrixSeriesFileSelectionModel")); } sceneClass->addChild(matrixSeriesSceneMap); } /** * Restore information specific to the type of model from the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass from which model specific information is obtained. */ void ModelChart::restoreModelSpecificInformationFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { reset(); /* * Restore the chart models */ restoreChartModelsFromScene(sceneAttributes, sceneClass); sceneClass->getEnumerateTypeArrayForTabIndices("m_selectedChartDataType", m_selectedChartDataType); /* * Restore matrix chart models from scene. */ const SceneObjectMapIntegerKey* matrixSceneMap = sceneClass->getMapIntegerKey("chartableMatrixFileSelectionModelMap"); if (matrixSceneMap != NULL) { const std::vector tabIndices = matrixSceneMap->getKeys(); for (std::vector::const_iterator tabIter = tabIndices.begin(); tabIter != tabIndices.end(); tabIter++) { const int32_t tabIndex = *tabIter; const SceneClass* sceneClass = matrixSceneMap->classValue(tabIndex); m_chartableMatrixFileSelectionModel[tabIndex]->restoreFromScene(sceneAttributes, sceneClass); } } /* * Restore matrix chart series models from scene. */ const SceneObjectMapIntegerKey* matrixSeriesSceneMap = sceneClass->getMapIntegerKey("chartableMatrixSeriesFileSelectionModelMap"); if (matrixSeriesSceneMap != NULL) { const std::vector tabIndices = matrixSeriesSceneMap->getKeys(); for (std::vector::const_iterator tabIter = tabIndices.begin(); tabIter != tabIndices.end(); tabIter++) { const int32_t tabIndex = *tabIter; const SceneClass* sceneClass = matrixSeriesSceneMap->classValue(tabIndex); m_chartableMatrixSeriesFileSelectionModel[tabIndex]->restoreFromScene(sceneAttributes, sceneClass); } } } /** * Save chart models to the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @param sceneClass * SceneClass to which model specific information is added. */ void ModelChart::saveChartModelsToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass, const std::vector& tabIndices, std::set& validChartDataIDsOut) { validChartDataIDsOut.clear(); std::set chartDataForSavingToSceneSet; /* * Save chart models to scene. */ std::vector chartModelVector; for (std::vector::const_iterator tabIter = tabIndices.begin(); tabIter != tabIndices.end(); tabIter++) { const int32_t tabIndex = *tabIter; ChartModel* chartModel = NULL; switch (getSelectedChartDataType(tabIndex)) { case ChartDataTypeEnum::CHART_DATA_TYPE_INVALID: break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_LAYER: break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_SERIES: break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_FREQUENCY_SERIES: chartModel = getSelectedFrequencySeriesChartModel(tabIndex); break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_DATA_SERIES: chartModel = getSelectedDataSeriesChartModel(tabIndex); break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_TIME_SERIES: chartModel = getSelectedTimeSeriesChartModel(tabIndex); break; } if (chartModel != NULL) { SceneClass* chartModelClass = chartModel->saveToScene(sceneAttributes, "chartModel"); if (chartModelClass == NULL) { continue; } SceneClass* chartClassContainer = new SceneClass("chartClassContainer", "ChartClassContainer", 1); chartClassContainer->addInteger("tabIndex", tabIndex); chartClassContainer->addEnumeratedType("chartDataType", chartModel->getChartDataType()); chartClassContainer->addClass(chartModelClass); chartModelVector.push_back(chartClassContainer); /* * Add chart data that is in models saved to scene. * */ std::vector chartDatasInModel = chartModel->getAllChartDatas(); chartDataForSavingToSceneSet.insert(chartDatasInModel.begin(), chartDatasInModel.end()); } } if ( ! chartModelVector.empty()) { SceneClassArray* modelArray = new SceneClassArray("chartModelArray", chartModelVector); sceneClass->addChild(modelArray); } if ( ! chartDataForSavingToSceneSet.empty()) { std::vector chartDataClassVector; for (std::set::iterator cdIter = chartDataForSavingToSceneSet.begin(); cdIter != chartDataForSavingToSceneSet.end(); cdIter++) { ChartData* chartData = *cdIter; SceneClass* chartDataClass = chartData->saveToScene(sceneAttributes, "chartData"); SceneClass* chartDataContainer = new SceneClass("chartDataContainer", "ChartDataContainer", 1); chartDataContainer->addEnumeratedType("chartDataType", chartData->getChartDataType()); chartDataContainer->addClass(chartDataClass); chartDataClassVector.push_back(chartDataContainer); } if ( ! chartDataClassVector.empty()) { SceneClassArray* dataArray = new SceneClassArray("chartDataArray", chartDataClassVector); sceneClass->addChild(dataArray); } } } /** * Restore the chart models from the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass from which model specific information is obtained. */ void ModelChart::restoreChartModelsFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { /* * Restore the chart models */ const SceneClassArray* chartModelArray = sceneClass->getClassArray("chartModelArray"); if (chartModelArray != NULL) { const int numElements = chartModelArray->getNumberOfArrayElements(); for (int32_t i = 0; i < numElements; i++) { const SceneClass* chartClassContainer = chartModelArray->getClassAtIndex(i); if (chartClassContainer != NULL) { const int32_t tabIndex = chartClassContainer->getIntegerValue("tabIndex", -1); const ChartDataTypeEnum::Enum chartDataType = chartClassContainer->getEnumeratedTypeValue("chartDataType", ChartDataTypeEnum::CHART_DATA_TYPE_INVALID); const SceneClass* chartModelClass = chartClassContainer->getClass("chartModel"); if ((tabIndex >= 0) && (chartDataType != ChartDataTypeEnum::CHART_DATA_TYPE_INVALID) && (chartModelClass != NULL)) { CaretAssertArrayIndex(m_chartModelDataSeries, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); switch (chartDataType) { case ChartDataTypeEnum::CHART_DATA_TYPE_INVALID: break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_LAYER: CaretAssert(0); break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_SERIES: break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_DATA_SERIES: m_chartModelDataSeries[tabIndex]->restoreFromScene(sceneAttributes, chartModelClass); break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_FREQUENCY_SERIES: m_chartModelFrequencySeries[tabIndex]->restoreFromScene(sceneAttributes, chartModelClass); break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_TIME_SERIES: m_chartModelTimeSeries[tabIndex]->restoreFromScene(sceneAttributes, chartModelClass); break; } } } } } /* * Restore the chart data. */ std::vector > restoredChartData; const SceneClassArray* chartDataArray = sceneClass->getClassArray("chartDataArray"); if (chartDataArray != NULL) { const int numElements = chartDataArray->getNumberOfArrayElements(); for (int32_t i = 0; i < numElements; i++) { const SceneClass* chartDataContainer = chartDataArray->getClassAtIndex(i); if (chartDataContainer != NULL) { const ChartDataTypeEnum::Enum chartDataType = chartDataContainer->getEnumeratedTypeValue("chartDataType", ChartDataTypeEnum::CHART_DATA_TYPE_INVALID); const SceneClass* chartDataClass = chartDataContainer->getClass("chartData"); if ((chartDataType != ChartDataTypeEnum::CHART_DATA_TYPE_INVALID) && (chartDataClass != NULL)) { ChartData* chartData = ChartData::newChartDataForChartDataType(chartDataType); CaretAssert(chartData); /* * The chart's points are saved in the scene and this * function call restores the points. This is part of * the original implementation. However, it was decided * that the when the scene is restored, the data should * be loaded from the file to reflect any changes made * to the data file. But, we still load the chart points * saved to the scene in case the file is missing. */ chartData->restoreFromScene(sceneAttributes, chartDataClass); /* * Now load the chart data from the file using the * information in the chart's data source. If this is * successful, then overwrite the chart data points * that were loaded from the scene file. * * Also need to copy the unique identifier so that * the chart data goes to the correct model. */ ChartData* newChartData = loadCartesianChartWhenRestoringScene(chartData); if (newChartData != NULL) { newChartData->setUniqueIdentifier(chartData->getUniqueIdentifier()); delete chartData; chartData = newChartData; } else { const AString msg("FAILED to load line chart data from file for " + chartData->getChartDataSource()->getDescription() + " so chart points from scene will be used. If content of the file " " has changed since the scene was created, the chart may not be accurate."); sceneAttributes->addToErrorMessage(msg); CaretLogWarning(msg); } restoredChartData.push_back(QSharedPointer(chartData)); } } } } /* * Have chart models restore pointers to chart data * The chart models use shared pointers are used since the chart * data may be in multiple tabs. User may remove the charts * from some tabs but not others and shared pointers make management * easier. */ for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_chartModelDataSeries[i]->restoreChartDataFromScene(sceneAttributes, restoredChartData); } for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_chartModelFrequencySeries[i]->restoreChartDataFromScene(sceneAttributes, restoredChartData); } for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_chartModelTimeSeries[i]->restoreChartDataFromScene(sceneAttributes, restoredChartData); } /* * The chart data are also saved here as weak pointers so * that they can be saved to a scene only one time. If */ for (std::vector >::iterator rcdIter = restoredChartData.begin(); rcdIter != restoredChartData.end(); rcdIter++) { QSharedPointer chartPointer = *rcdIter; switch (chartPointer->getChartDataType()) { case ChartDataTypeEnum::CHART_DATA_TYPE_INVALID: CaretAssert(0); break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_LAYER: CaretAssert(0); break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_SERIES: CaretAssert(0); break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_DATA_SERIES: { QSharedPointer cartChartPointer = chartPointer.dynamicCast(); CaretAssert( ! cartChartPointer.isNull()); m_dataSeriesChartData.push_back(cartChartPointer); } break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_FREQUENCY_SERIES: { QSharedPointer cartChartPointer = chartPointer.dynamicCast(); CaretAssert( ! cartChartPointer.isNull()); m_frequencySeriesChartData.push_back(cartChartPointer); } break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_TIME_SERIES: { QSharedPointer cartChartPointer = chartPointer.dynamicCast(); CaretAssert( ! cartChartPointer.isNull()); m_timeSeriesChartData.push_back(cartChartPointer); } break; } } } /** * Load the cartesian chart data using the given chart data source. * * @param chartData * ChartData that is cast to ChartDataCartesian and if successful, * the chart is duplicated using data from files. * @return * Pointer to successfully loaded chart data or NULL if not found or error. */ ChartData* ModelChart::loadCartesianChartWhenRestoringScene(const ChartData* chartData) { CaretAssert(chartData); ChartDataCartesian* chartDataOut = NULL; const ChartDataCartesian* cartesianChart = dynamic_cast(chartData); if (cartesianChart != NULL) { const ChartDataTypeEnum::Enum chartDataType = cartesianChart->getChartDataType(); std::vector chartableDataFiles; m_brain->getAllChartableLineSeriesDataFiles(chartableDataFiles); for (std::vector::iterator iter = chartableDataFiles.begin(); iter != chartableDataFiles.end(); iter++) { ChartableLineSeriesBrainordinateInterface* chartableBrainFile = dynamic_cast(*iter); ChartableLineSeriesRowColumnInterface* chartableRowColumnFile = dynamic_cast(*iter); if (chartableBrainFile != NULL) { if (chartableBrainFile->isLineSeriesChartDataTypeSupported(chartDataType)) { CaretMappableDataFile* chartMapFile = chartableBrainFile->getLineSeriesChartCaretMappableDataFile(); CaretAssert(chartMapFile); const ChartDataSource* chartDataSource = cartesianChart->getChartDataSource(); CaretAssert(chartDataSource); const AString chartMapFileName = chartMapFile->getFileName(); const AString chartSourceFileName = chartDataSource->getChartableFileName(); if (chartMapFileName == chartSourceFileName) { const ChartDataSourceModeEnum::Enum sourceMode = chartDataSource->getDataSourceMode(); switch (sourceMode) { case ChartDataSourceModeEnum::CHART_DATA_SOURCE_MODE_INVALID: break; case ChartDataSourceModeEnum::CHART_DATA_SOURCE_MODE_FILE_ROW: { CaretAssert(0); } break; case ChartDataSourceModeEnum::CHART_DATA_SOURCE_MODE_SURFACE_NODE_INDEX: { AString structureName; int32_t surfaceNumberOfNodes = -1; int32_t nodeIndex = -1; chartDataSource->getSurfaceNode(structureName, surfaceNumberOfNodes, nodeIndex); bool structureNameValid = false; const StructureEnum::Enum structure = StructureEnum::fromName(structureName, &structureNameValid); chartDataOut = chartableBrainFile->loadLineSeriesChartDataForSurfaceNode(structure, nodeIndex); } break; case ChartDataSourceModeEnum::CHART_DATA_SOURCE_MODE_SURFACE_NODE_INDICES_AVERAGE: { AString structureName; int32_t surfaceNumberOfNodes = -1; std::vector nodeIndices; chartDataSource->getSurfaceNodeAverage(structureName, surfaceNumberOfNodes, nodeIndices); bool structureNameValid = false; const StructureEnum::Enum structure = StructureEnum::fromName(structureName, &structureNameValid); chartDataOut = chartableBrainFile->loadAverageLineSeriesChartDataForSurfaceNodes(structure, nodeIndices); } break; case ChartDataSourceModeEnum::CHART_DATA_SOURCE_MODE_VOXEL_IJK: { float voxelXYZ[3]; chartDataSource->getVolumeVoxel(voxelXYZ); chartDataOut= chartableBrainFile->loadLineSeriesChartDataForVoxelAtCoordinate(voxelXYZ); } break; } } } } if (chartableRowColumnFile != NULL) { if (chartableRowColumnFile->isLineSeriesChartDataTypeSupported(chartDataType)) { CaretMappableDataFile* chartMapFile = chartableRowColumnFile->getLineSeriesChartCaretMappableDataFile(); CaretAssert(chartMapFile); const ChartDataSource* chartDataSource = cartesianChart->getChartDataSource(); CaretAssert(chartDataSource); const AString chartMapFileName = chartMapFile->getFileName(); const AString chartSourceFileName = chartDataSource->getChartableFileName(); if (chartMapFileName == chartSourceFileName) { const ChartDataSourceModeEnum::Enum sourceMode = chartDataSource->getDataSourceMode(); switch (sourceMode) { case ChartDataSourceModeEnum::CHART_DATA_SOURCE_MODE_INVALID: break; case ChartDataSourceModeEnum::CHART_DATA_SOURCE_MODE_FILE_ROW: { AString chartFileName; int32_t fileRowIndex; chartDataSource->getFileRow(chartFileName, fileRowIndex); chartDataOut = chartableRowColumnFile->loadLineSeriesChartDataForRow(fileRowIndex); } break; case ChartDataSourceModeEnum::CHART_DATA_SOURCE_MODE_SURFACE_NODE_INDEX: { CaretAssert(0); } break; case ChartDataSourceModeEnum::CHART_DATA_SOURCE_MODE_SURFACE_NODE_INDICES_AVERAGE: { CaretAssert(0); } break; case ChartDataSourceModeEnum::CHART_DATA_SOURCE_MODE_VOXEL_IJK: { CaretAssert(0); } break; } } } } } } if (chartDataOut != NULL) { /* * Copy the source of the chart (node, surface, voxel, etc) */ chartDataOut->getChartDataSource()->copy(chartData->getChartDataSource()); const ChartDataCartesian* chartCartData = dynamic_cast(chartData); if (chartCartData != NULL) { chartDataOut->setColor(chartCartData->getColor()); } } return chartDataOut; } /** * Get a text description of the window's content. * * @param tabIndex * Index of the tab for content description. * @param descriptionOut * Description of the window's content. */ void ModelChart::getDescriptionOfContent(const int32_t tabIndex, PlainTextStringBuilder& descriptionOut) const { ChartModel* chartModel = NULL; switch (getSelectedChartDataType(tabIndex)) { case ChartDataTypeEnum::CHART_DATA_TYPE_INVALID: break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_LAYER: { CaretDataFileSelectionModel* sm = m_chartableMatrixFileSelectionModel[tabIndex]; const CaretDataFile* caretFile = sm->getSelectedFile(); if (caretFile != NULL) { descriptionOut.addLine("Matrix (layer) chart for: " + caretFile->getFileNameNoPath()); return; } } break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_SERIES: { CaretDataFileSelectionModel* sm = m_chartableMatrixSeriesFileSelectionModel[tabIndex]; const CaretDataFile* caretFile = sm->getSelectedFile(); if (caretFile != NULL) { descriptionOut.addLine("Matrix (series) chart for: " + caretFile->getFileNameNoPath()); return; } } break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_DATA_SERIES: chartModel = const_cast(getSelectedDataSeriesChartModel(tabIndex)); break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_FREQUENCY_SERIES: chartModel = const_cast(getSelectedFrequencySeriesChartModel(tabIndex)); break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_TIME_SERIES: chartModel = const_cast(getSelectedTimeSeriesChartModel(tabIndex)); break; } const ChartModel* chartModelConst = chartModel; if (chartModel != NULL) { descriptionOut.addLine("Chart Type: " + ChartDataTypeEnum::toGuiName(chartModel->getChartDataType())); descriptionOut.pushIndentation(); const std::vector cdVec = chartModelConst->getAllChartDatas(); for (std::vector::const_iterator iter = cdVec.begin(); iter != cdVec.end(); iter++) { const ChartData* cd = *iter; if (cd->isSelected(tabIndex)) { descriptionOut.addLine(cd->getChartDataSource()->getDescription()); } } if (chartModel->isAverageChartDisplaySupported()) { if (chartModel->isAverageChartDisplaySelected()) { descriptionOut.addLine("Average Chart Displayed"); } } descriptionOut.popIndentation(); } else { descriptionOut.addLine("No charts to display"); } } /** * Copy the tab content from the source tab index to the * destination tab index. * * @param sourceTabIndex * Source from which tab content is copied. * @param destinationTabIndex * Destination to which tab content is copied. */ void ModelChart::copyTabContent(const int32_t sourceTabIndex, const int32_t destinationTabIndex) { Model::copyTabContent(sourceTabIndex, destinationTabIndex); m_overlaySetArray->copyOverlaySet(sourceTabIndex, destinationTabIndex); m_selectedChartDataType[destinationTabIndex] = m_selectedChartDataType[sourceTabIndex]; *m_chartModelDataSeries[destinationTabIndex] = *m_chartModelDataSeries[sourceTabIndex]; *m_chartModelFrequencySeries[destinationTabIndex] = *m_chartModelFrequencySeries[sourceTabIndex]; *m_chartModelTimeSeries[destinationTabIndex] = *m_chartModelTimeSeries[sourceTabIndex]; m_chartableMatrixFileSelectionModel[destinationTabIndex]->setSelectedFile(m_chartableMatrixFileSelectionModel[sourceTabIndex]->getSelectedFile()); m_chartableMatrixSeriesFileSelectionModel[destinationTabIndex]->setSelectedFile(m_chartableMatrixSeriesFileSelectionModel[sourceTabIndex]->getSelectedFile()); } /** * Set the type of chart selected in the given tab. * * @param tabIndex * Index of tab. * @param dataType * Type of data for chart. */ void ModelChart::setSelectedChartDataType(const int32_t tabIndex, const ChartDataTypeEnum::Enum dataType) { CaretAssertArrayIndex(m_selectedChartDataType, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_selectedChartDataType[tabIndex] = dataType; } /** * Get the valid chart data types based upon the currently loaded files. * * @param validChartDataTypesOut * Output containing valid chart data types. */ void ModelChart::getValidChartDataTypes(std::vector& validChartDataTypesOut) const { validChartDataTypesOut.clear(); bool haveDataSeries = false; bool haveFrequencySeries = false; bool haveMatrixLayers = false; bool haveMatrixSeries = false; bool haveTimeSeries = false; std::vector allLineChartableFiles; m_brain->getAllChartableLineSeriesDataFiles(allLineChartableFiles); for (std::vector::iterator fileIter = allLineChartableFiles.begin(); fileIter != allLineChartableFiles.end(); fileIter++) { ChartableLineSeriesInterface* chartFile = *fileIter; std::vector chartDataTypes; chartFile->getSupportedLineSeriesChartDataTypes(chartDataTypes); for (std::vector::iterator typeIter = chartDataTypes.begin(); typeIter != chartDataTypes.end(); typeIter++) { const ChartDataTypeEnum::Enum cdt = *typeIter; switch (cdt) { case ChartDataTypeEnum::CHART_DATA_TYPE_INVALID: break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_LAYER: break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_SERIES: break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_DATA_SERIES: haveDataSeries = true; break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_FREQUENCY_SERIES: haveFrequencySeries = true; break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_TIME_SERIES: haveTimeSeries = true; break; } } } // std::vector allBrainordinateChartableFiles; // m_brain->getAllChartableBrainordinateDataFiles(allBrainordinateChartableFiles); // // for (std::vector::iterator fileIter = allBrainordinateChartableFiles.begin(); // fileIter != allBrainordinateChartableFiles.end(); // fileIter++) { // ChartableLineSeriesBrainordinateInterface* chartFile = *fileIter; // // std::vector chartDataTypes; // chartFile->getSupportedLineSeriesChartDataTypes(chartDataTypes); // // for (std::vector::iterator typeIter = chartDataTypes.begin(); // typeIter != chartDataTypes.end(); // typeIter++) { // const ChartDataTypeEnum::Enum cdt = *typeIter; // switch (cdt) { // case ChartDataTypeEnum::CHART_DATA_TYPE_INVALID: // break; // case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_LAYER: // break; // case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_SERIES: // break; // case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_DATA_SERIES: // haveDataSeries = true; // break; // case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_FREQUENCY_SERIES: // haveFrequencySeries = true; // break; // case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_TIME_SERIES: // haveTimeSeries = true; // break; // } // } // } std::vector allMatrixChartableFiles; m_brain->getAllChartableMatrixDataFiles(allMatrixChartableFiles); for (std::vector::iterator fileIter = allMatrixChartableFiles.begin(); fileIter != allMatrixChartableFiles.end(); fileIter++) { ChartableMatrixInterface* chartFile = *fileIter; std::vector chartDataTypes; chartFile->getSupportedMatrixChartDataTypes(chartDataTypes); for (std::vector::iterator typeIter = chartDataTypes.begin(); typeIter != chartDataTypes.end(); typeIter++) { const ChartDataTypeEnum::Enum cdt = *typeIter; switch (cdt) { case ChartDataTypeEnum::CHART_DATA_TYPE_INVALID: break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_LAYER: haveMatrixLayers = true; break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_SERIES: haveMatrixSeries = true; break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_DATA_SERIES: break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_FREQUENCY_SERIES: break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_TIME_SERIES: break; } } } if (haveDataSeries) { validChartDataTypesOut.push_back(ChartDataTypeEnum::CHART_DATA_TYPE_LINE_DATA_SERIES); } if (haveFrequencySeries) { validChartDataTypesOut.push_back(ChartDataTypeEnum::CHART_DATA_TYPE_LINE_FREQUENCY_SERIES); } if (haveMatrixLayers) { validChartDataTypesOut.push_back(ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_LAYER); } if (haveMatrixSeries) { validChartDataTypesOut.push_back(ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_SERIES); } if (haveTimeSeries) { validChartDataTypesOut.push_back(ChartDataTypeEnum::CHART_DATA_TYPE_LINE_TIME_SERIES); } } /** * Get the type of chart selected in the given tab. * * @param tabIndex * Index of tab. * @return * Chart type in the given tab. */ ChartDataTypeEnum::Enum ModelChart::getSelectedChartDataType(const int32_t tabIndex) const { CaretAssertArrayIndex(m_selectedChartDataType, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); ChartDataTypeEnum::Enum chartDataType = m_selectedChartDataType[tabIndex]; /* * Verify that the selected chart data type is valid. */ std::vector validChartDataTypes; getValidChartDataTypes(validChartDataTypes); if (std::find(validChartDataTypes.begin(), validChartDataTypes.end(), chartDataType) == validChartDataTypes.end()) { chartDataType = ChartDataTypeEnum::CHART_DATA_TYPE_INVALID; } /* * If selected chart data type is invalid, find a valid chart type, * preferably one that contains data. */ if (chartDataType == ChartDataTypeEnum::CHART_DATA_TYPE_INVALID) { if ( ! validChartDataTypes.empty()) { /* * Will become the the first valid chart data type that contains * data (if there is one) */ ChartDataTypeEnum::Enum chartDataTypeWithValidData = ChartDataTypeEnum::CHART_DATA_TYPE_INVALID; /* * Loop through all chart types (some or all valid charts * types may not contain data until the user commands loading of data) */ std::vector allChartDataTypes; ChartDataTypeEnum::getAllEnums(allChartDataTypes); for (std::vector::iterator iter = allChartDataTypes.begin(); iter != allChartDataTypes.end(); iter++) { const ChartDataTypeEnum::Enum cdt = *iter; if (std::find(validChartDataTypes.begin(), validChartDataTypes.end(), cdt) != validChartDataTypes.end()) { if (chartDataType == ChartDataTypeEnum::CHART_DATA_TYPE_INVALID) { chartDataType = cdt; } switch (cdt) { case ChartDataTypeEnum::CHART_DATA_TYPE_INVALID: break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_LAYER: if (chartDataTypeWithValidData == ChartDataTypeEnum::CHART_DATA_TYPE_INVALID) { chartDataTypeWithValidData = ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_LAYER; } break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_SERIES: if (chartDataTypeWithValidData == ChartDataTypeEnum::CHART_DATA_TYPE_INVALID) { chartDataTypeWithValidData = ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_SERIES; } break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_DATA_SERIES: if (m_chartModelDataSeries[tabIndex]->getNumberOfChartData() > 0) { if (chartDataTypeWithValidData == ChartDataTypeEnum::CHART_DATA_TYPE_INVALID) { chartDataTypeWithValidData = ChartDataTypeEnum::CHART_DATA_TYPE_LINE_DATA_SERIES; } } break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_FREQUENCY_SERIES: if (m_chartModelFrequencySeries[tabIndex]->getNumberOfChartData() > 0) { if (chartDataTypeWithValidData == ChartDataTypeEnum::CHART_DATA_TYPE_INVALID) { chartDataTypeWithValidData = ChartDataTypeEnum::CHART_DATA_TYPE_LINE_FREQUENCY_SERIES; } } break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_TIME_SERIES: if (m_chartModelTimeSeries[tabIndex]->getNumberOfChartData() > 0) { if (chartDataTypeWithValidData == ChartDataTypeEnum::CHART_DATA_TYPE_INVALID) { chartDataTypeWithValidData = ChartDataTypeEnum::CHART_DATA_TYPE_LINE_TIME_SERIES; } } break; } } } if (chartDataTypeWithValidData != ChartDataTypeEnum::CHART_DATA_TYPE_INVALID) { chartDataType = chartDataTypeWithValidData; } else if (chartDataType == ChartDataTypeEnum::CHART_DATA_TYPE_INVALID) { chartDataType = validChartDataTypes[0]; } } } /* * Selected type may have changed due to loaded files changing */ m_selectedChartDataType[tabIndex] = chartDataType; return chartDataType; } /** * Get the data series chart model selected in the given tab. * * @param tabIndex * Index of tab. * @return * Data series chart model in the given tab. */ ChartModelDataSeries* ModelChart::getSelectedDataSeriesChartModel(const int32_t tabIndex) { const ChartModelDataSeries* model = getSelectedDataSeriesChartModelHelper(tabIndex); if (model == NULL) { return NULL; } ChartModelDataSeries* nonConstModel = const_cast(model); return nonConstModel; } /** * Get the data series chart model selected in the given tab. * * @param tabIndex * Index of tab. * @return * Data series chart model in the given tab. */ const ChartModelDataSeries* ModelChart::getSelectedDataSeriesChartModel(const int32_t tabIndex) const { return getSelectedDataSeriesChartModelHelper(tabIndex); } /** * Helper to get the data series chart model selected in the given tab. * * @param tabIndex * Index of tab. * @return * Data series chart model in the given tab. */ const ChartModelDataSeries* ModelChart::getSelectedDataSeriesChartModelHelper(const int32_t tabIndex) const { CaretAssertArrayIndex(m_chartModelDataSeries, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_chartModelDataSeries[tabIndex]; } /** * Get the time series chart model selected in the given tab. * * @param tabIndex * Index of tab. * @return * time series chart model in the given tab. */ ChartModelFrequencySeries* ModelChart::getSelectedFrequencySeriesChartModel(const int32_t tabIndex) { const ChartModelFrequencySeries* model = getSelectedFrequencySeriesChartModelHelper(tabIndex); if (model == NULL) { return NULL; } ChartModelFrequencySeries* nonConstModel = const_cast(model); return nonConstModel; } /** * Get the time series chart model selected in the given tab. * * @param tabIndex * Index of tab. * @return * time series chart model in the given tab. */ const ChartModelFrequencySeries* ModelChart::getSelectedFrequencySeriesChartModel(const int32_t tabIndex) const { return getSelectedFrequencySeriesChartModelHelper(tabIndex); } /** * Helper to get the time series chart model selected in the given tab. * * @param tabIndex * Index of tab. * @return * time series chart model in the given tab. */ const ChartModelFrequencySeries* ModelChart::getSelectedFrequencySeriesChartModelHelper(const int32_t tabIndex) const { CaretAssertArrayIndex(m_chartModelFrequencySeries, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_chartModelFrequencySeries[tabIndex]; } /** * Get the time series chart model selected in the given tab. * * @param tabIndex * Index of tab. * @return * time series chart model in the given tab. */ ChartModelTimeSeries* ModelChart::getSelectedTimeSeriesChartModel(const int32_t tabIndex) { const ChartModelTimeSeries* model = getSelectedTimeSeriesChartModelHelper(tabIndex); if (model == NULL) { return NULL; } ChartModelTimeSeries* nonConstModel = const_cast(model); return nonConstModel; } /** * Get the time series chart model selected in the given tab. * * @param tabIndex * Index of tab. * @return * time series chart model in the given tab. */ const ChartModelTimeSeries* ModelChart::getSelectedTimeSeriesChartModel(const int32_t tabIndex) const { return getSelectedTimeSeriesChartModelHelper(tabIndex); } /** * Helper to get the time series chart model selected in the given tab. * * @param tabIndex * Index of tab. * @return * time series chart model in the given tab. */ const ChartModelTimeSeries* ModelChart::getSelectedTimeSeriesChartModelHelper(const int32_t tabIndex) const { CaretAssertArrayIndex(m_chartModelTimeSeries, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_chartModelTimeSeries[tabIndex]; } /** * Get the chartable matrix parcel file selection model for the given tab. * * @param tabIndex * Index of the tab. * @return * Chartable file selection model for the tab. */ CaretDataFileSelectionModel* ModelChart::getChartableMatrixParcelFileSelectionModel(const int32_t tabIndex) { CaretAssertArrayIndex(m_chartableMatrixFileSelectionModel, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_chartableMatrixFileSelectionModel[tabIndex]; } /** * Get the chartable matrix series file selection model for the given tab. * * @param tabIndex * Index of the tab. * @return * Chartable file selection model for the tab. */ CaretDataFileSelectionModel* ModelChart::getChartableMatrixSeriesFileSelectionModel(const int32_t tabIndex) { CaretAssertArrayIndex(m_chartableMatrixSeriesFileSelectionModel, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_chartableMatrixSeriesFileSelectionModel[tabIndex]; } workbench-1.1.1/src/Brain/ModelChart.h000066400000000000000000000205521255417355300175270ustar00rootroot00000000000000#ifndef __MODEL_CHART_H__ #define __MODEL_CHART_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #include "ChartDataTypeEnum.h" #include "EventListenerInterface.h" #include "MapYokingGroupEnum.h" #include "Model.h" #include "StructureEnum.h" namespace caret { class CaretDataFileSelectionModel; class ChartData; class ChartDataCartesian; class ChartDataSource; class ChartableLineSeriesBrainordinateInterface; class ChartableLineSeriesInterface; class ChartableLineSeriesRowColumnInterface; class ChartableMatrixInterface; class ChartModel; class ChartModelDataSeries; class ChartModelFrequencySeries; class ChartModelTimeSeries; class CiftiConnectivityMatrixParcelFile; class CiftiMappableDataFile; class OverlaySetArray; class SurfaceFile; /// Controls the display of a chart. class ModelChart : public Model, public EventListenerInterface { public: ModelChart(Brain* brain); virtual ~ModelChart(); void initializeOverlays(); AString getNameForGUI(const bool includeStructureFlag) const; virtual AString getNameForBrowserTab() const; void loadChartDataForSurfaceNode(const StructureEnum::Enum structure, const int32_t surfaceNumberOfNodes, const int32_t nodeIndex); void loadAverageChartDataForSurfaceNodes(const StructureEnum::Enum structure, const int32_t surfaceNumberOfNodes, const std::vector& nodeIndices); void loadChartDataForVoxelAtCoordinate(const float xyz[3]); void loadChartDataForCiftiMappableFileRow(CiftiMappableDataFile* ciftiMapFile, const int32_t rowIndex); void loadChartDataForYokedCiftiMappableFiles(const MapYokingGroupEnum::Enum mapYokingGroup, const int32_t mapIndex); OverlaySet* getOverlaySet(const int tabIndex); const OverlaySet* getOverlaySet(const int tabIndex) const; virtual void receiveEvent(Event* event); void getValidChartDataTypes(std::vector& validChartDataTypesOut) const; ChartDataTypeEnum::Enum getSelectedChartDataType(const int32_t tabIndex) const; void setSelectedChartDataType(const int32_t tabIndex, const ChartDataTypeEnum::Enum dataType); ChartModelDataSeries* getSelectedDataSeriesChartModel(const int32_t tabIndex); const ChartModelDataSeries* getSelectedDataSeriesChartModel(const int32_t tabIndex) const; ChartModelFrequencySeries* getSelectedFrequencySeriesChartModel(const int32_t tabIndex); const ChartModelFrequencySeries* getSelectedFrequencySeriesChartModel(const int32_t tabIndex) const; ChartModelTimeSeries* getSelectedTimeSeriesChartModel(const int32_t tabIndex); const ChartModelTimeSeries* getSelectedTimeSeriesChartModel(const int32_t tabIndex) const; virtual void getDescriptionOfContent(const int32_t tabIndex, PlainTextStringBuilder& descriptionOut) const; virtual void copyTabContent(const int32_t sourceTabIndex, const int32_t destinationTabIndex); void reset(); CaretDataFileSelectionModel* getChartableMatrixParcelFileSelectionModel(const int32_t tabIndex); CaretDataFileSelectionModel* getChartableMatrixSeriesFileSelectionModel(const int32_t tabIndex); protected: virtual void saveModelSpecificInformationToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass); virtual void restoreModelSpecificInformationFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private: ModelChart(const ModelChart&); ModelChart& operator=(const ModelChart&); void addChartToChartModels(const std::vector& tabIndices, ChartData* chartData); void initializeCharts(); void removeAllCharts(); const ChartModelDataSeries* getSelectedDataSeriesChartModelHelper(const int32_t tabIndex) const; const ChartModelFrequencySeries* getSelectedFrequencySeriesChartModelHelper(const int32_t tabIndex) const; const ChartModelTimeSeries* getSelectedTimeSeriesChartModelHelper(const int32_t tabIndex) const; void saveChartModelsToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass, const std::vector& tabIndices, std::set& validChartDataIDsOut); void restoreChartModelsFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); void getTabsAndBrainordinateChartFilesForLineChartLoading(std::map >& chartBrainordinateFileEnabledTabsOut) const; void getTabsAndRowColumnChartFilesForLineChartLoading(std::map >& chartRowColumnFilesEnabledTabsOut) const; void getTabsAndLineSeriesChartFilesForLineChartLoading(std::map >& chartFileEnabledTabsOut) const; ChartData* loadCartesianChartWhenRestoringScene(const ChartData* chartData); /** Overlays sets for this model and for each tab */ OverlaySetArray* m_overlaySetArray; mutable ChartDataTypeEnum::Enum m_selectedChartDataType[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; /** Chart model for data-series data */ ChartModelDataSeries* m_chartModelDataSeries[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; /** Chart model for frequency-series data */ ChartModelFrequencySeries* m_chartModelFrequencySeries[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; /** Chart model for time-series data */ ChartModelTimeSeries* m_chartModelTimeSeries[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; /** Contains data series charts */ std::list > m_dataSeriesChartData; /** Contains time series charts */ std::list > m_frequencySeriesChartData; /** Contains time series charts */ std::list > m_timeSeriesChartData; std::vector m_previousChartMatrixFiles; CaretDataFileSelectionModel* m_chartableMatrixFileSelectionModel[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; CaretDataFileSelectionModel* m_chartableMatrixSeriesFileSelectionModel[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; SceneClassAssistant* m_sceneAssistant; }; } // namespace #endif // __MODEL_CHART_H__ workbench-1.1.1/src/Brain/ModelSurface.cxx000066400000000000000000000166311255417355300204340ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "BrainStructure.h" #include "BrowserTabContent.h" #include "BoundingBox.h" #include "CaretAssert.h" #include "EventManager.h" #include "EventModelSurfaceGet.h" #include "ModelSurface.h" #include "Brain.h" #include "BrainOpenGL.h" #include "OverlaySet.h" #include "SceneClass.h" #include "SceneClassAssistant.h" #include "Surface.h" using namespace caret; /** * Constructor. * @param surface - surface for this model. * */ ModelSurface::ModelSurface(Brain* brain, Surface* surface) : Model(ModelTypeEnum::MODEL_TYPE_SURFACE, brain) { CaretAssert(surface); initializeMembersModelSurface(); m_surface = surface; EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_MODEL_SURFACE_GET); } /** * Destructor */ ModelSurface::~ModelSurface() { EventManager::get()->removeAllEventsFromListener(this); } /** * Receive an event. * * @param event * The event that the receive can respond to. */ void ModelSurface::receiveEvent(Event* event) { if (event->getEventType() == EventTypeEnum::EVENT_MODEL_SURFACE_GET) { EventModelSurfaceGet* getSurfaceEvent = dynamic_cast(event); CaretAssert(getSurfaceEvent); /* * Looking for this model? */ if (getSurfaceEvent->getSurface() == getSurface()) { getSurfaceEvent->setModelSurface(this); getSurfaceEvent->setEventProcessed(); } } else { CaretAssertMessage(0, "Unexpected event: " + EventTypeEnum::toName(event->getEventType())); } } void ModelSurface::initializeMembersModelSurface() { m_surface = NULL; } /** * Get the surface in this model. * @return Surface in this model. */ Surface* ModelSurface::getSurface() { return m_surface; } /** * Get the surface in this model. * @return Surface in this model. */ const Surface* ModelSurface::getSurface() const { return m_surface; } /** * Get the name for use in a GUI. * * @param includeStructureFlag - Prefix label with structure to which * this structure model belongs. * @return Name for use in a GUI. * */ AString ModelSurface::getNameForGUI(const bool includeStructureFlag) const { AString name; if (includeStructureFlag) { const StructureEnum::Enum structure = m_surface->getStructure(); name += StructureEnum::toGuiName(structure); name += " "; } name += m_surface->getFileNameNoPath(); return name; } /** * @return The name that should be displayed in the tab * displaying this model. */ AString ModelSurface::getNameForBrowserTab() const { const StructureEnum::Enum structure = m_surface->getStructure(); AString name = StructureEnum::toGuiName(structure); if (structure == StructureEnum::CEREBELLUM) { name = "Cbllm"; } return name; } /** * Set the scaling so that the model fills the window. * */ //void //ModelSurface::setDefaultScalingToFitWindow() //{ // BoundingBox bounds; // m_surface->getBounds(bounds); // // float bigY = std::max(std::abs(bounds.getMinY()), bounds.getMaxY()); // float percentScreenY = BrainOpenGL::getModelViewingHalfWindowHeight() * 0.90f; // float scale = percentScreenY / bigY; // m_defaultModelScaling = scale; // // for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { // setScaling(i, m_defaultModelScaling); // } //} /** * Get the overlay set for the given tab. * @param tabIndex * Index of tab. * @return * Overlay set at the given tab index. */ OverlaySet* ModelSurface::getOverlaySet(const int tabIndex) { if (m_surface != NULL) { BrainStructure* brainStructure = m_surface->getBrainStructure(); if (brainStructure != NULL) { return brainStructure->getOverlaySet(tabIndex); } } return NULL; } /** * Get the overlay set for the given tab. * @param tabIndex * Index of tab. * @return * Overlay set at the given tab index. */ const OverlaySet* ModelSurface::getOverlaySet(const int tabIndex) const { if (m_surface != NULL) { const BrainStructure* brainStructure = m_surface->getBrainStructure(); if (brainStructure != NULL) { return brainStructure->getOverlaySet(tabIndex); } } return NULL; } /** * Initilize the overlays for this model. */ void ModelSurface::initializeOverlays() { } /** * Save information specific to this type of model to the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @param sceneClass * SceneClass to which model specific information is added. */ void ModelSurface::saveModelSpecificInformationToScene(const SceneAttributes* /*sceneAttributes*/, SceneClass* /*sceneClass*/) { /* nothing to add to scene */ } /** * Restore information specific to the type of model from the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass from which model specific information is obtained. */ void ModelSurface::restoreModelSpecificInformationFromScene(const SceneAttributes* /*sceneAttributes*/, const SceneClass* /*sceneClass*/) { /* nothing to restore from scene */ } /** * Get a text description of the window's content. * * @param tabIndex * Index of the tab for content description. * @param descriptionOut * Description of the window's content. */ void ModelSurface::getDescriptionOfContent(const int32_t /*tabIndex*/, PlainTextStringBuilder& descriptionOut) const { AString msg; const Surface* surface = getSurface(); if (surface != NULL) { surface->getDescriptionOfContent(descriptionOut); } } /** * Copy the tab content from the source tab index to the * destination tab index. * * @param sourceTabIndex * Source from which tab content is copied. * @param destinationTabIndex * Destination to which tab content is copied. */ void ModelSurface::copyTabContent(const int32_t sourceTabIndex, const int32_t destinationTabIndex) { Model::copyTabContent(sourceTabIndex, destinationTabIndex); } workbench-1.1.1/src/Brain/ModelSurface.h000066400000000000000000000054751255417355300200650ustar00rootroot00000000000000#ifndef __MODEL_SURFACE_H__ #define __MODEL_SURFACE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "EventListenerInterface.h" #include "Model.h" namespace caret { class Surface; /// Controls the display of a surface. class ModelSurface : public Model, public EventListenerInterface { public: ModelSurface(Brain* brain, Surface* surface); virtual ~ModelSurface(); virtual void receiveEvent(Event* event); OverlaySet* getOverlaySet(const int tabIndex); const OverlaySet* getOverlaySet(const int tabIndex) const; void initializeOverlays(); virtual void getDescriptionOfContent(const int32_t tabIndex, PlainTextStringBuilder& descriptionOut) const; virtual void copyTabContent(const int32_t sourceTabIndex, const int32_t destinationTabIndex); private: ModelSurface(const ModelSurface&); ModelSurface& operator=(const ModelSurface&); private: void initializeMembersModelSurface(); public: Surface* getSurface(); const Surface* getSurface() const; AString getNameForGUI(const bool includeStructureFlag) const; virtual AString getNameForBrowserTab() const; //void setDefaultScalingToFitWindow(); protected: virtual void saveModelSpecificInformationToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass); virtual void restoreModelSpecificInformationFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private: /**Surface that uses this model */ Surface* m_surface; }; } // namespace #endif // __MODEL_SURFACE_H__ workbench-1.1.1/src/Brain/ModelSurfaceMontage.cxx000066400000000000000000001212601255417355300217420ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "BrainStructure.h" #include "BrowserTabContent.h" #include "BoundingBox.h" #include "Brain.h" #include "BrainOpenGL.h" #include "CaretAssert.h" #include "EventManager.h" #include "EventModelSurfaceGet.h" #include "ModelSurfaceMontage.h" #include "OverlaySet.h" #include "SceneAttributes.h" #include "SceneClass.h" #include "SceneClassArray.h" #include "SceneClassAssistant.h" #include "SceneObjectMapIntegerKey.h" #include "ScenePrimitive.h" #include "SurfaceMontageConfigurationCerebellar.h" #include "SurfaceMontageConfigurationCerebral.h" #include "SurfaceMontageConfigurationFlatMaps.h" #include "SurfaceSelectionModel.h" using namespace caret; /** * Constructor. * @param surface - surface for this model. * */ ModelSurfaceMontage::ModelSurfaceMontage(Brain* brain) : Model(ModelTypeEnum::MODEL_TYPE_SURFACE_MONTAGE, brain) { std::vector validSurfaceTypes; validSurfaceTypes.push_back(SurfaceTypeEnum::ANATOMICAL); validSurfaceTypes.push_back(SurfaceTypeEnum::RECONSTRUCTION); validSurfaceTypes.push_back(SurfaceTypeEnum::INFLATED); validSurfaceTypes.push_back(SurfaceTypeEnum::VERY_INFLATED); for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_selectedConfigurationType[i] = SurfaceMontageConfigurationTypeEnum::CEREBRAL_CORTEX_CONFIGURATION; m_cerebellarConfiguration[i] = new SurfaceMontageConfigurationCerebellar(i); m_cerebralConfiguration[i] = new SurfaceMontageConfigurationCerebral(i); m_flatMapsConfiguration[i] = new SurfaceMontageConfigurationFlatMaps(i); } std::vector overlaySurfaceStructures; overlaySurfaceStructures.push_back(StructureEnum::CORTEX_LEFT); overlaySurfaceStructures.push_back(StructureEnum::CORTEX_RIGHT); } /** * Destructor */ ModelSurfaceMontage::~ModelSurfaceMontage() { EventManager::get()->removeAllEventsFromListener(this); for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { delete m_cerebellarConfiguration[i]; delete m_cerebralConfiguration[i]; delete m_flatMapsConfiguration[i]; } } /** * Receive an event. * * @param event * The event that the receive can respond to. */ void ModelSurfaceMontage::receiveEvent(Event* /*event*/) { } /** * Initialize the selected surfaces. */ void ModelSurfaceMontage::initializeSelectedSurfaces() { for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_cerebellarConfiguration[i]->initializeSelectedSurfaces(); m_cerebralConfiguration[i]->initializeSelectedSurfaces(); m_flatMapsConfiguration[i]->initializeSelectedSurfaces(); } } /** * Get the name for use in a GUI. * * @param includeStructureFlag - Prefix label with structure to which * this structure model belongs. * @return Name for use in a GUI. * */ AString ModelSurfaceMontage::getNameForGUI(const bool /*includeStructureFlag*/) const { AString name = "Surface Montage"; return name; } /** * @return The name that should be displayed in the tab * displaying this model. */ AString ModelSurfaceMontage::getNameForBrowserTab() const { AString name = "Montage"; return name; } /** * Get the overlay set for the given tab. * @param tabIndex * Index of tab. * @return * Overlay set at the given tab index. */ OverlaySet* ModelSurfaceMontage::getOverlaySet(const int tabIndex) { return getSelectedConfiguration(tabIndex)->getOverlaySet(); } /** * Get the overlay set for the given tab. * @param tabIndex * Index of tab. * @return * Overlay set at the given tab index. */ const OverlaySet* ModelSurfaceMontage::getOverlaySet(const int tabIndex) const { return getSelectedConfiguration(tabIndex)->getOverlaySet(); } /** * Initilize the overlays for this model. */ void ModelSurfaceMontage::initializeOverlays() { for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_cerebellarConfiguration[i]->getOverlaySet()->initializeOverlays(); m_cerebralConfiguration[i]->getOverlaySet()->initializeOverlays(); m_flatMapsConfiguration[i]->getOverlaySet()->initializeOverlays(); } } /** * Get a surface for the given struture in the given tab. Since there * may be one surface of the given structure, the returned surface * may be different in future calls based upon the surfaces the user * has chosen for display. * * @param structure * Structure for the surface * @param windowTabNumber * Tab number of window. * @param Pointer to selected surface for given structure or NULL if not available. */ Surface* ModelSurfaceMontage::getSelectedSurface(const StructureEnum::Enum structure, const int32_t windowTabNumber) { std::vector selectionModels; switch (getSelectedConfigurationType(windowTabNumber)) { case SurfaceMontageConfigurationTypeEnum::CEREBELLAR_CORTEX_CONFIGURATION: { SurfaceMontageConfigurationCerebellar* smcc = getCerebellarConfiguration(windowTabNumber); if (structure == StructureEnum::CEREBELLUM) { selectionModels.push_back(smcc->getFirstSurfaceSelectionModel()); selectionModels.push_back(smcc->getSecondSurfaceSelectionModel()); } } break; case SurfaceMontageConfigurationTypeEnum::CEREBRAL_CORTEX_CONFIGURATION: { SurfaceMontageConfigurationCerebral* smcc = getCerebralConfiguration(windowTabNumber); switch (structure) { case StructureEnum::CORTEX_LEFT: selectionModels.push_back(smcc->getLeftFirstSurfaceSelectionModel()); selectionModels.push_back(smcc->getLeftSecondSurfaceSelectionModel()); break; case StructureEnum::CORTEX_RIGHT: selectionModels.push_back(smcc->getRightFirstSurfaceSelectionModel()); selectionModels.push_back(smcc->getRightSecondSurfaceSelectionModel()); break; default: break; } } break; case SurfaceMontageConfigurationTypeEnum::FLAT_CONFIGURATION: { SurfaceMontageConfigurationFlatMaps* smcfm = getFlatMapsConfiguration(windowTabNumber); switch (structure) { case StructureEnum::CEREBELLUM: selectionModels.push_back(smcfm->getCerebellumSurfaceSelectionModel()); case StructureEnum::CORTEX_LEFT: selectionModels.push_back(smcfm->getLeftSurfaceSelectionModel()); break; case StructureEnum::CORTEX_RIGHT: selectionModels.push_back(smcfm->getRightSurfaceSelectionModel()); break; default: break; } } break; } Surface* surfaceOut = NULL; for (std::vector::iterator iter = selectionModels.begin(); iter != selectionModels.end(); iter++) { SurfaceSelectionModel* sm = *iter; if (sm != NULL) { surfaceOut = sm->getSurface(); break; } } return surfaceOut; } /** * Get the selected configuration for the given tab. * * @param tabIndex * Index of tab for the selected configuration. */ SurfaceMontageConfigurationAbstract* ModelSurfaceMontage::getSelectedConfiguration(const int32_t tabIndex) { switch (getSelectedConfigurationType(tabIndex)) { case SurfaceMontageConfigurationTypeEnum::CEREBELLAR_CORTEX_CONFIGURATION: CaretAssertArrayIndex(m_cerebellarConfiguration, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_cerebellarConfiguration[tabIndex]; break; case SurfaceMontageConfigurationTypeEnum::CEREBRAL_CORTEX_CONFIGURATION: CaretAssertArrayIndex(m_cerebralConfiguration, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_cerebralConfiguration[tabIndex]; break; case SurfaceMontageConfigurationTypeEnum::FLAT_CONFIGURATION: CaretAssertArrayIndex(m_flatMapsConfiguration, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_flatMapsConfiguration[tabIndex]; break; } return NULL; } /** * Get the selected configuration for the given tab. * * @param tabIndex * Index of tab for the selected configuration. */ const SurfaceMontageConfigurationAbstract* ModelSurfaceMontage::getSelectedConfiguration(const int32_t tabIndex) const { ModelSurfaceMontage* msm = const_cast(this); return msm->getSelectedConfiguration(tabIndex); } /** * @return The type of configuration in the given tab. * * @param tabIndex * Index of the tab. */ SurfaceMontageConfigurationTypeEnum::Enum ModelSurfaceMontage::getSelectedConfigurationType(const int32_t tabIndex) const { /* * Find valid configurations */ std::vector< SurfaceMontageConfigurationTypeEnum::Enum> validTypes; if (m_cerebellarConfiguration[tabIndex]->isValid()) { validTypes.push_back(SurfaceMontageConfigurationTypeEnum::CEREBELLAR_CORTEX_CONFIGURATION); } if (m_cerebralConfiguration[tabIndex]->isValid()) { validTypes.push_back(SurfaceMontageConfigurationTypeEnum::CEREBRAL_CORTEX_CONFIGURATION); } if (m_flatMapsConfiguration[tabIndex]->isValid()) { validTypes.push_back(SurfaceMontageConfigurationTypeEnum::FLAT_CONFIGURATION); } /* * Verify selected type is valid */ bool validTypeSelected = false; switch (m_selectedConfigurationType[tabIndex]) { case SurfaceMontageConfigurationTypeEnum::CEREBELLAR_CORTEX_CONFIGURATION: CaretAssertArrayIndex(m_cerebellarConfiguration, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); if (m_cerebellarConfiguration[tabIndex]->isValid()) { validTypeSelected = true; } break; case SurfaceMontageConfigurationTypeEnum::CEREBRAL_CORTEX_CONFIGURATION: CaretAssertArrayIndex(m_cerebralConfiguration, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); if (m_cerebralConfiguration[tabIndex]->isValid()) { validTypeSelected = true; } break; case SurfaceMontageConfigurationTypeEnum::FLAT_CONFIGURATION: CaretAssertArrayIndex(m_flatMapsConfiguration, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); if (m_flatMapsConfiguration[tabIndex]->isValid()) { validTypeSelected = true; } break; } /* * If configuration type selected is not valid, choose another * configuration type. */ if ( ! validTypeSelected) { if ( ! validTypes.empty()) { m_selectedConfigurationType[tabIndex] = validTypes[0]; } } return m_selectedConfigurationType[tabIndex]; } /** * Set type of configuration in the given tab. * * @param tabIndex * Index of the tab. * @param configurationType * New configuration type for the tab index. */ void ModelSurfaceMontage::setSelectedConfigurationType(const int32_t tabIndex, const SurfaceMontageConfigurationTypeEnum::Enum configurationType) { m_selectedConfigurationType[tabIndex] = configurationType; } /** * Get the cerebellar configuration in the given tab. * * @param tabIndex * Index of tab. * @return * Cerebellar configuration. */ SurfaceMontageConfigurationCerebellar * ModelSurfaceMontage::getCerebellarConfiguration(const int32_t tabIndex) { CaretAssertArrayIndex(m_cerebellarConfiguration, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_cerebellarConfiguration[tabIndex]; } /** * Get the cerebellar configuration in the given tab. * * @param tabIndex * Index of tab. * @return * Cerebellar configuration. */ const SurfaceMontageConfigurationCerebellar* ModelSurfaceMontage::getCerebellarConfiguration(const int32_t tabIndex) const { CaretAssertArrayIndex(m_cerebellarConfiguration, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_cerebellarConfiguration[tabIndex]; } /** * Get the cerebral configuration in the given tab. * * @param tabIndex * Index of tab. * @return * Cerebral configuration. */ SurfaceMontageConfigurationCerebral * ModelSurfaceMontage::getCerebralConfiguration(const int32_t tabIndex) { CaretAssertArrayIndex(m_cerebralConfiguration, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_cerebralConfiguration[tabIndex]; } /** * Get the cerebral configuration in the given tab. * * @param tabIndex * Index of tab. * @return * Cerebral configuration. */ const SurfaceMontageConfigurationCerebral* ModelSurfaceMontage::getCerebralConfiguration(const int32_t tabIndex) const { CaretAssertArrayIndex(m_cerebralConfiguration, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_cerebralConfiguration[tabIndex]; } /** * Get the flat maps configuration in the given tab. * * @param tabIndex * Index of tab. * @return * Flat maps configuration. */ SurfaceMontageConfigurationFlatMaps * ModelSurfaceMontage::getFlatMapsConfiguration(const int32_t tabIndex) { CaretAssertArrayIndex(m_flatMapsConfiguration, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_flatMapsConfiguration[tabIndex]; } /** * Get the flat maps configuration in the given tab. * * @param tabIndex * Index of tab. * @return * Flat maps configuration. */ const SurfaceMontageConfigurationFlatMaps* ModelSurfaceMontage::getFlatMapsConfiguration(const int32_t tabIndex) const { CaretAssertArrayIndex(m_flatMapsConfiguration, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_flatMapsConfiguration[tabIndex]; } /** * Save information specific to this type of model to the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @param sceneClass * SceneClass to which model specific information is added. */ void ModelSurfaceMontage::saveModelSpecificInformationToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass) { sceneClass->addInteger("montageVersion", 3); SceneObjectMapIntegerKey* cerebellarConfigurationMap = new SceneObjectMapIntegerKey("m_cerebellarConfiguration", SceneObjectDataTypeEnum::SCENE_CLASS); SceneObjectMapIntegerKey* cerebralConfigurationMap = new SceneObjectMapIntegerKey("m_cerebralConfiguration", SceneObjectDataTypeEnum::SCENE_CLASS); SceneObjectMapIntegerKey* flatConfigurationMap = new SceneObjectMapIntegerKey("m_flatMapsConfiguration", SceneObjectDataTypeEnum::SCENE_CLASS); /* * Get indices of tabs that are to be saved to scene. */ const std::vector tabIndices = sceneAttributes->getIndicesOfTabsForSavingToScene(); const int32_t numActiveTabs = static_cast(tabIndices.size()); sceneClass->addEnumeratedTypeArrayForTabIndices("m_selectedConfigurationType", m_selectedConfigurationType, tabIndices); for (int32_t i = 0; i < numActiveTabs; i++) { const int32_t tabIndex = tabIndices[i]; const AString tabString = ("[" + AString::number(tabIndex) + "]"); cerebellarConfigurationMap->addClass(tabIndex, m_cerebellarConfiguration[tabIndex]->saveToScene(sceneAttributes, "m_cerebellarConfiguration" + tabString)); cerebralConfigurationMap->addClass(tabIndex, m_cerebralConfiguration[tabIndex]->saveToScene(sceneAttributes, "m_cerebralConfiguration" + tabString)); flatConfigurationMap->addClass(tabIndex, m_flatMapsConfiguration[tabIndex]->saveToScene(sceneAttributes, "m_flatMapsConfiguration" + tabString)); } sceneClass->addChild(cerebellarConfigurationMap); sceneClass->addChild(cerebralConfigurationMap); sceneClass->addChild(flatConfigurationMap); // /* // * Surfaces // */ // SceneObjectMapIntegerKey* leftSurfaceMap = new SceneObjectMapIntegerKey("m_leftSurfaceSelectionModel", // SceneObjectDataTypeEnum::SCENE_CLASS); // SceneObjectMapIntegerKey* leftSecondSurfaceMap = new SceneObjectMapIntegerKey("m_leftSecondSurfaceSelectionModel", // SceneObjectDataTypeEnum::SCENE_CLASS); // SceneObjectMapIntegerKey* rightSurfaceMap = new SceneObjectMapIntegerKey("m_rightSurfaceSelectionModel", // SceneObjectDataTypeEnum::SCENE_CLASS); // SceneObjectMapIntegerKey* rightSecondSurfaceMap = new SceneObjectMapIntegerKey("m_rightSecondSurfaceSelectionModel", // SceneObjectDataTypeEnum::SCENE_CLASS); // for (int32_t iat = 0; iat < numActiveTabs; iat++) { // const int32_t tabIndex = tabIndices[iat]; // const AString tabString = ("[" + AString::number(tabIndex) + "]"); // // leftSurfaceMap->addClass(tabIndex, // m_leftSurfaceSelectionModel[tabIndex]->saveToScene(sceneAttributes, // "m_leftSurfaceSelectionModel" + tabString)); // leftSecondSurfaceMap->addClass(tabIndex, // m_leftSecondSurfaceSelectionModel[tabIndex]->saveToScene(sceneAttributes, // "m_leftSecondSurfaceSelectionModel" + tabString)); // rightSurfaceMap->addClass(tabIndex, // m_rightSurfaceSelectionModel[tabIndex]->saveToScene(sceneAttributes, // "m_rightSurfaceSelectionModel" + tabString)); // rightSecondSurfaceMap->addClass(tabIndex, // m_rightSecondSurfaceSelectionModel[tabIndex]->saveToScene(sceneAttributes, // "m_rightSecondSurfaceSelectionModel" + tabString)); // } // sceneClass->addChild(leftSurfaceMap); // sceneClass->addChild(leftSecondSurfaceMap); // sceneClass->addChild(rightSurfaceMap); // sceneClass->addChild(rightSecondSurfaceMap); // // /* // * Selections // */ // SceneObjectMapIntegerKey* rightEnabledMap = new SceneObjectMapIntegerKey("m_rightEnabled", // SceneObjectDataTypeEnum::SCENE_BOOLEAN); // SceneObjectMapIntegerKey* leftEnabledMap = new SceneObjectMapIntegerKey("m_leftEnabled", // SceneObjectDataTypeEnum::SCENE_BOOLEAN); // SceneObjectMapIntegerKey* firstSurfaceEnabledMap = new SceneObjectMapIntegerKey("m_firstSurfaceEnabled", // SceneObjectDataTypeEnum::SCENE_BOOLEAN); // SceneObjectMapIntegerKey* secondSurfaceEnabledMap = new SceneObjectMapIntegerKey("m_secondSurfaceEnabled", // SceneObjectDataTypeEnum::SCENE_BOOLEAN); // for (int32_t iat = 0; iat < numActiveTabs; iat++) { // const int32_t tabIndex = tabIndices[iat]; // rightEnabledMap->addBoolean(tabIndex, // m_rightEnabled[tabIndex]); // leftEnabledMap->addBoolean(tabIndex, // m_leftEnabled[tabIndex]); // firstSurfaceEnabledMap->addBoolean(tabIndex, // m_firstSurfaceEnabled[tabIndex]); // secondSurfaceEnabledMap->addBoolean(tabIndex, // m_secondSurfaceEnabled[tabIndex]); // } // sceneClass->addChild(rightEnabledMap); // sceneClass->addChild(leftEnabledMap); // sceneClass->addChild(firstSurfaceEnabledMap); // sceneClass->addChild(secondSurfaceEnabledMap); } /** * Restore information specific to the type of model from the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass from which model specific information is obtained. */ void ModelSurfaceMontage::restoreModelSpecificInformationFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { const int32_t montageVersion = sceneClass->getIntegerValue("montageVersion", 1); if (montageVersion >= 3) { /* * Restore Cerebellum */ const SceneObjectMapIntegerKey* cerebellumMap = sceneClass->getMapIntegerKey("m_cerebellarConfiguration"); if (cerebellumMap != NULL) { const std::map& structureMap = cerebellumMap->getMap(); for (std::map::const_iterator iter = structureMap.begin(); iter != structureMap.end(); iter++) { const int32_t key = iter->first; const SceneClass* cerebellarSceneClass = dynamic_cast(iter->second); CaretAssertArrayIndex(m_cerebellarConfiguration, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, key); m_cerebellarConfiguration[key]->restoreFromScene(sceneAttributes, cerebellarSceneClass); } } /* * Restore Cortext */ const SceneObjectMapIntegerKey* cerebralMap = sceneClass->getMapIntegerKey("m_cerebralConfiguration"); if (cerebellumMap != NULL) { const std::map& structureMap = cerebralMap->getMap(); for (std::map::const_iterator iter = structureMap.begin(); iter != structureMap.end(); iter++) { const int32_t key = iter->first; const SceneClass* cerebralSceneClass = dynamic_cast(iter->second); CaretAssertArrayIndex(m_cerebralConfiguration, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, key); m_cerebralConfiguration[key]->restoreFromScene(sceneAttributes, cerebralSceneClass); } } /* * Restore flat maps */ const SceneObjectMapIntegerKey* flatMap = sceneClass->getMapIntegerKey("m_flatMapsConfiguration"); if (cerebellumMap != NULL) { const std::map& structureMap = flatMap->getMap(); for (std::map::const_iterator iter = structureMap.begin(); iter != structureMap.end(); iter++) { const int32_t key = iter->first; const SceneClass* flatClass = dynamic_cast(iter->second); CaretAssertArrayIndex(m_flatMapsConfiguration, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, key); m_flatMapsConfiguration[key]->restoreFromScene(sceneAttributes, flatClass); } } sceneClass->getEnumerateTypeArrayForTabIndices("m_selectedConfigurationType", m_selectedConfigurationType); } else { restoreFromSceneVersionTwoAndEarlier(sceneAttributes, sceneClass, montageVersion); return; } } /** * Restore information from the ModelSurfaceMontage that existed prior * to the cerebellar, cerebral, and flat montage configurations. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass from which information is restored. */ void ModelSurfaceMontage::restoreFromSceneVersionTwoAndEarlier(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass, const int32_t montageVersion) { /* * Restore left surface */ const SceneObjectMapIntegerKey* leftSurfaceMap = sceneClass->getMapIntegerKey("m_leftSurfaceSelectionModel"); if (leftSurfaceMap != NULL) { const std::map& surfaceMap = leftSurfaceMap->getMap(); for (std::map::const_iterator iter = surfaceMap.begin(); iter != surfaceMap.end(); iter++) { const int32_t key = iter->first; const SceneClass* surfaceClass = dynamic_cast(iter->second); CaretAssertArrayIndex(m_cerebralConfiguration, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, key); SurfaceSelectionModel* ssm = m_cerebralConfiguration[key]->getLeftFirstSurfaceSelectionModel(); ssm->restoreFromScene(sceneAttributes, surfaceClass); } } /* * Restore left second surface */ const SceneObjectMapIntegerKey* leftSecondSurfaceMap = sceneClass->getMapIntegerKey("m_leftSecondSurfaceSelectionModel"); if (leftSecondSurfaceMap != NULL) { const std::map& surfaceMap = leftSecondSurfaceMap->getMap(); for (std::map::const_iterator iter = surfaceMap.begin(); iter != surfaceMap.end(); iter++) { const int32_t key = iter->first; const SceneClass* surfaceClass = dynamic_cast(iter->second); CaretAssertArrayIndex(m_cerebralConfiguration, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, key); SurfaceSelectionModel* ssm = m_cerebralConfiguration[key]->getLeftSecondSurfaceSelectionModel(); ssm->restoreFromScene(sceneAttributes, surfaceClass); } } /* * Restore right surface */ const SceneObjectMapIntegerKey* rightSurfaceMap = sceneClass->getMapIntegerKey("m_rightSurfaceSelectionModel"); if (rightSurfaceMap != NULL) { const std::map& surfaceMap = rightSurfaceMap->getMap(); for (std::map::const_iterator iter = surfaceMap.begin(); iter != surfaceMap.end(); iter++) { const int32_t key = iter->first; const SceneClass* surfaceClass = dynamic_cast(iter->second); CaretAssertArrayIndex(m_cerebralConfiguration, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, key); SurfaceSelectionModel* ssm = m_cerebralConfiguration[key]->getRightFirstSurfaceSelectionModel(); ssm->restoreFromScene(sceneAttributes, surfaceClass); } } /* * Restore right second surface */ const SceneObjectMapIntegerKey* rightSecondSurfaceMap = sceneClass->getMapIntegerKey("m_rightSecondSurfaceSelectionModel"); if (rightSecondSurfaceMap != NULL) { const std::map& surfaceMap = rightSecondSurfaceMap->getMap(); for (std::map::const_iterator iter = surfaceMap.begin(); iter != surfaceMap.end(); iter++) { const int32_t key = iter->first; const SceneClass* surfaceClass = dynamic_cast(iter->second); CaretAssertArrayIndex(m_cerebralConfiguration, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, key); SurfaceSelectionModel* ssm = m_cerebralConfiguration[key]->getRightSecondSurfaceSelectionModel(); ssm->restoreFromScene(sceneAttributes, surfaceClass); } } if (montageVersion <= 1) { /* * Version 1 had only dual option so enable items added in version 2 */ for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { CaretAssertArrayIndex(m_cerebralConfiguration, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, i); m_cerebralConfiguration[i]->setLeftEnabled(true); m_cerebralConfiguration[i]->setRightEnabled(true); m_cerebralConfiguration[i]->setFirstSurfaceEnabled(true); m_cerebralConfiguration[i]->setSecondSurfaceEnabled(true); } /* * Restore dual configuration as second surface */ const SceneObjectMapIntegerKey* dualConfigurationMap = sceneClass->getMapIntegerKey("m_dualConfigurationEnabled"); if (dualConfigurationMap != NULL) { const std::map& dataMap = dualConfigurationMap->getMap(); for (std::map::const_iterator iter = dataMap.begin(); iter != dataMap.end(); iter++) { const int32_t key = iter->first; const ScenePrimitive* primitive = dynamic_cast(iter->second); CaretAssertArrayIndex(m_cerebralConfiguration, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, key); m_cerebralConfiguration[key]->setSecondSurfaceEnabled(primitive->booleanValue()); } } } else { /* * Restore left enabled */ const SceneObjectMapIntegerKey* leftEnabledMap = sceneClass->getMapIntegerKey("m_leftEnabled"); if (leftEnabledMap != NULL) { const std::map& dataMap = leftEnabledMap->getMap(); for (std::map::const_iterator iter = dataMap.begin(); iter != dataMap.end(); iter++) { const int32_t key = iter->first; const ScenePrimitive* primitive = dynamic_cast(iter->second); CaretAssertArrayIndex(m_cerebralConfiguration, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, key); m_cerebralConfiguration[key]->setLeftEnabled(primitive->booleanValue()); } } /* * Restore right enabled */ const SceneObjectMapIntegerKey* rightEnabledMap = sceneClass->getMapIntegerKey("m_rightEnabled"); if (rightEnabledMap != NULL) { const std::map& dataMap = rightEnabledMap->getMap(); for (std::map::const_iterator iter = dataMap.begin(); iter != dataMap.end(); iter++) { const int32_t key = iter->first; const ScenePrimitive* primitive = dynamic_cast(iter->second); CaretAssertArrayIndex(m_cerebralConfiguration, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, key); m_cerebralConfiguration[key]->setRightEnabled(primitive->booleanValue()); } } /* * Restore first surface enabled */ const SceneObjectMapIntegerKey* firstSurfaceEnabledMap = sceneClass->getMapIntegerKey("m_firstSurfaceEnabled"); if (firstSurfaceEnabledMap != NULL) { const std::map& dataMap = firstSurfaceEnabledMap->getMap(); for (std::map::const_iterator iter = dataMap.begin(); iter != dataMap.end(); iter++) { const int32_t key = iter->first; const ScenePrimitive* primitive = dynamic_cast(iter->second); CaretAssertArrayIndex(m_cerebralConfiguration, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, key); m_cerebralConfiguration[key]->setFirstSurfaceEnabled(primitive->booleanValue()); } } /* * Restore second surface enabled */ const SceneObjectMapIntegerKey* secondSurfaceEnabledMap = sceneClass->getMapIntegerKey("m_secondSurfaceEnabled"); if (secondSurfaceEnabledMap != NULL) { const std::map& dataMap = secondSurfaceEnabledMap->getMap(); for (std::map::const_iterator iter = dataMap.begin(); iter != dataMap.end(); iter++) { const int32_t key = iter->first; const ScenePrimitive* primitive = dynamic_cast(iter->second); CaretAssertArrayIndex(m_cerebralConfiguration, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, key); m_cerebralConfiguration[key]->setSecondSurfaceEnabled(primitive->booleanValue()); } } } /* * Previous surface montage did not have lateral/medial selections */ for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { CaretAssertArrayIndex(m_cerebralConfiguration, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, i); m_cerebralConfiguration[i]->setLateralEnabled(true); m_cerebralConfiguration[i]->setMedialEnabled(true); } } /** * Get the montage viewports for drawing by OpenGL. The montage viewports * will be updated prior to returning them. OpenGL will update * the viewing dimensions (x, y, width, height) in the returned montage * viewports. * * @param tabIndex * Tab for which viewports are requested. * @param surfaceMontageViewports * The montage viewports. */ void ModelSurfaceMontage::getSurfaceMontageViewportsForDrawing(const int32_t tabIndex, std::vector& surfaceMontageViewports) { SurfaceMontageConfigurationAbstract* config = getSelectedConfiguration(tabIndex); if (config != NULL) { config->getSurfaceMontageViewportsForDrawing(surfaceMontageViewports); } else { surfaceMontageViewports.clear(); } } /** * Get the montage viewports that will be used by the mouse transformations. * * @param tabIndex * Tab for which viewports are requested. * @param surfaceMontageViewports * The montage viewports. */ void ModelSurfaceMontage::getSurfaceMontageViewportsForTransformation(const int32_t tabIndex, std::vector& surfaceMontageViewports) const { const SurfaceMontageConfigurationAbstract* config = getSelectedConfiguration(tabIndex); if (config != NULL) { config->getSurfaceMontageViewportsForTransformation(surfaceMontageViewports); } else { surfaceMontageViewports.clear(); } } /** * @return A string describing the model. */ AString ModelSurfaceMontage::toString() const { AString msg; msg.appendWithNewLine("Surface Montage: "); return msg; } /** * Get a text description of the window's content. * * @param tabIndex * Index of the tab for content description. * @param descriptionOut * Description of the window's content. */ void ModelSurfaceMontage::getDescriptionOfContent(const int32_t tabIndex, PlainTextStringBuilder& descriptionOut) const { const SurfaceMontageConfigurationAbstract* config = getSelectedConfiguration(tabIndex); if (config != NULL) { config->getDescriptionOfContent(descriptionOut); } } /** * Copy the tab content from the source tab index to the * destination tab index. * * @param sourceTabIndex * Source from which tab content is copied. * @param destinationTabIndex * Destination to which tab content is copied. */ void ModelSurfaceMontage::copyTabContent(const int32_t sourceTabIndex, const int32_t destinationTabIndex) { Model::copyTabContent(sourceTabIndex, destinationTabIndex); CaretAssertArrayIndex(m_selectedConfigurationType, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, sourceTabIndex); CaretAssertArrayIndex(m_selectedConfigurationType, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, destinationTabIndex); m_cerebellarConfiguration[destinationTabIndex]->copyConfiguration(m_cerebellarConfiguration[sourceTabIndex]); m_cerebralConfiguration[destinationTabIndex]->copyConfiguration(m_cerebralConfiguration[sourceTabIndex]); m_flatMapsConfiguration[destinationTabIndex]->copyConfiguration(m_flatMapsConfiguration[sourceTabIndex]); m_selectedConfigurationType[destinationTabIndex] = m_selectedConfigurationType[sourceTabIndex]; } workbench-1.1.1/src/Brain/ModelSurfaceMontage.h000066400000000000000000000126131255417355300213700ustar00rootroot00000000000000#ifndef __MODEL_SURFACE_MONTAGE_H__ #define __MODEL_SURFACE_MONTAGE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "EventListenerInterface.h" #include "Model.h" #include "StructureEnum.h" #include "SurfaceMontageViewport.h" #include "SurfaceMontageConfigurationTypeEnum.h" namespace caret { class SurfaceMontageConfigurationAbstract; class SurfaceMontageConfigurationCerebellar; class SurfaceMontageConfigurationCerebral; class SurfaceMontageConfigurationFlatMaps; /// Controls the display of a surface montage class ModelSurfaceMontage : public Model, public EventListenerInterface { public: ModelSurfaceMontage(Brain* brain); virtual ~ModelSurfaceMontage(); virtual void initializeSelectedSurfaces(); virtual void receiveEvent(Event* event); OverlaySet* getOverlaySet(const int tabIndex); const OverlaySet* getOverlaySet(const int tabIndex) const; void initializeOverlays(); Surface* getSelectedSurface(const StructureEnum::Enum structure, const int32_t windowTabNumber); AString getNameForGUI(const bool includeStructureFlag) const; virtual AString getNameForBrowserTab() const; void getSurfaceMontageViewportsForDrawing(const int32_t tabIndex, std::vector& surfaceMontageViewports); void getSurfaceMontageViewportsForTransformation(const int32_t tabIndex, std::vector& surfaceMontageViewports) const; SurfaceMontageConfigurationTypeEnum::Enum getSelectedConfigurationType(const int32_t tabIndex) const; void setSelectedConfigurationType(const int32_t tabIndex, const SurfaceMontageConfigurationTypeEnum::Enum configurationType); SurfaceMontageConfigurationAbstract* getSelectedConfiguration(const int32_t tabIndex); const SurfaceMontageConfigurationAbstract* getSelectedConfiguration(const int32_t tabIndex) const; SurfaceMontageConfigurationCerebellar * getCerebellarConfiguration(const int32_t tabIndex); const SurfaceMontageConfigurationCerebellar* getCerebellarConfiguration(const int32_t tabIndex) const; SurfaceMontageConfigurationCerebral * getCerebralConfiguration(const int32_t tabIndex); const SurfaceMontageConfigurationCerebral* getCerebralConfiguration(const int32_t tabIndex) const; SurfaceMontageConfigurationFlatMaps * getFlatMapsConfiguration(const int32_t tabIndex); const SurfaceMontageConfigurationFlatMaps* getFlatMapsConfiguration(const int32_t tabIndex) const; virtual AString toString() const; virtual void getDescriptionOfContent(const int32_t tabIndex, PlainTextStringBuilder& descriptionOut) const; virtual void copyTabContent(const int32_t sourceTabIndex, const int32_t destinationTabIndex); protected: virtual void saveModelSpecificInformationToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass); virtual void restoreModelSpecificInformationFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private: ModelSurfaceMontage(const ModelSurfaceMontage&); ModelSurfaceMontage& operator=(const ModelSurfaceMontage&); void restoreFromSceneVersionTwoAndEarlier(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass, const int32_t montageVersion); SurfaceMontageConfigurationCerebellar* m_cerebellarConfiguration[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; SurfaceMontageConfigurationCerebral* m_cerebralConfiguration[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; SurfaceMontageConfigurationFlatMaps* m_flatMapsConfiguration[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; mutable SurfaceMontageConfigurationTypeEnum::Enum m_selectedConfigurationType[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; }; } // namespace #endif // __MODEL_SURFACE_MONTAGE_H__ workbench-1.1.1/src/Brain/ModelSurfaceSelector.cxx000066400000000000000000000321711255417355300221320ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __MODEL_SURFACE_SELECTOR_DECLARE__ #include "ModelSurfaceSelector.h" #undef __MODEL_SURFACE_SELECTOR_DECLARE__ #include "Brain.h" #include "BrainStructure.h" #include "EventManager.h" #include "EventModelGetAll.h" #include "ModelSurface.h" #include "SceneAttributes.h" #include "SceneClass.h" #include "SceneClassAssistant.h" #include "Surface.h" #include using namespace caret; /** * \class ModelSurfaceSelector * \brief Maintains selection of surface model * * Maintains selection of a surface model with the ability to limit the * surface models to those from a specific brain structure. */ /** * Constructor. */ ModelSurfaceSelector::ModelSurfaceSelector() : CaretObject() { m_defaultStructure = StructureEnum::ALL; m_selectedStructure = StructureEnum::ALL; m_selectedSurfaceModel = NULL; } /** * Destructor. */ ModelSurfaceSelector::~ModelSurfaceSelector() { } /** * @return The selected surface model. */ ModelSurface* ModelSurfaceSelector::getSelectedSurfaceModel() { this->updateSelector(); return m_selectedSurfaceModel; } /** * @return The selected structure. */ StructureEnum::Enum ModelSurfaceSelector::getSelectedStructure() { this->updateSelector(); return m_selectedStructure; } /** * Set the selected surface model. * * @param surfaceModel * Model that is selected. */ void ModelSurfaceSelector::setSelectedSurfaceModel(ModelSurface* surfaceModel) { m_selectedSurfaceModel = surfaceModel; if (m_selectedStructure != StructureEnum::ALL) { m_selectedStructure = surfaceModel->getSurface()->getStructure(); } this->updateSelector(); } /** * Set the selected structure. * * @param selectedStructure * Structure that is selected. */ void ModelSurfaceSelector::setSelectedStructure(const StructureEnum::Enum selectedStructure) { m_selectedStructure = selectedStructure; this->updateSelector(); } /** * Get the structures available for selection. * * @param selectableStructuresOut * Output containing structures that can be selected. */ void ModelSurfaceSelector::getSelectableStructures( std::vector& selectableStructuresOut) const { selectableStructuresOut.clear(); selectableStructuresOut.insert(selectableStructuresOut.end(), m_availableStructures.begin(), m_availableStructures.end()); } /** * Get the surface models available for selection. * * @param selectableSurfaceModelsOut * Output containing surface models that can be selected. */ void ModelSurfaceSelector::getSelectableSurfaceModels( std::vector& selectableSurfaceModelsOut) const { selectableSurfaceModelsOut.clear(); selectableSurfaceModelsOut.insert(selectableSurfaceModelsOut.end(), m_availableSurfaceModels.begin(), m_availableSurfaceModels.end()); } /** * Update the selector with the available surface models. */ void ModelSurfaceSelector::updateSelector(const std::vector modelDisplayModels) { m_allSurfaceModels.clear(); for (std::vector::const_iterator iter = modelDisplayModels.begin(); iter != modelDisplayModels.end(); iter++) { ModelSurface* surfaceModel = dynamic_cast(*iter); if (surfaceModel != NULL) { m_allSurfaceModels.push_back(surfaceModel); } } this->updateSelector(); } /** * Update the selector with the available surface models. */ void ModelSurfaceSelector::updateSelector() { bool haveCortexLeft = false; bool haveCortexRight = false; bool haveCerebellum = false; /* * Find the ALL surface models and structures */ for (std::vector::const_iterator iter = m_allSurfaceModels.begin(); iter != m_allSurfaceModels.end(); iter++) { ModelSurface* surfaceModel = *iter; const Surface* surface = surfaceModel->getSurface(); const StructureEnum::Enum structure = surface->getStructure(); switch (structure) { case StructureEnum::CEREBELLUM: haveCerebellum = true; break; case StructureEnum::CORTEX_LEFT: haveCortexLeft = true; break; case StructureEnum::CORTEX_RIGHT: haveCortexRight = true; break; default: break; } } /* * Determine which structures are available. */ m_availableStructures.clear(); m_availableStructures.push_back(StructureEnum::ALL); if (haveCerebellum) { m_availableStructures.push_back(StructureEnum::CEREBELLUM); } if (haveCortexLeft) { m_availableStructures.push_back(StructureEnum::CORTEX_LEFT); } if (haveCortexRight) { m_availableStructures.push_back(StructureEnum::CORTEX_RIGHT); } /* * Update the structure selection. */ if (std::find(m_availableStructures.begin(), m_availableStructures.end(), m_selectedStructure) == m_availableStructures.end()) { if (m_availableStructures.empty() == false) { m_selectedStructure = m_availableStructures[0]; } else { m_selectedStructure = m_defaultStructure; } } /* * Update the available surface models. */ m_availableSurfaceModels.clear(); for (std::vector::iterator iter = m_allSurfaceModels.begin(); iter != m_allSurfaceModels.end(); iter++) { ModelSurface* surfaceModel = *iter; const Surface* surface = surfaceModel->getSurface(); const StructureEnum::Enum structure = surface->getStructure(); bool useIt = false; if (m_selectedStructure == StructureEnum::ALL) { useIt = true; } else if (m_selectedStructure == structure) { useIt = true; } if (useIt) { m_availableSurfaceModels.push_back(surfaceModel); } } /* * Update the surface selection. */ if (std::find(m_availableSurfaceModels.begin(), m_availableSurfaceModels.end(), m_selectedSurfaceModel) == m_availableSurfaceModels.end()) { /* * Selected model is not found. */ m_selectedSurfaceModel = NULL; /* * First, see if a previous model for structure still exists, if so, use it. */ std::map::iterator iter = m_previousSelectedSurfaceModel.find(m_selectedStructure); if (iter != m_previousSelectedSurfaceModel.end()) { ModelSurface* previousModel = iter->second; if (std::find(m_availableSurfaceModels.begin(), m_availableSurfaceModels.end(), previousModel) != m_availableSurfaceModels.end()) { m_selectedSurfaceModel = previousModel; } } /* * Still not found? */ if (m_selectedSurfaceModel == NULL) { /* * Default to first */ if (m_availableSurfaceModels.empty() == false) { m_selectedSurfaceModel = m_availableSurfaceModels[0]; /* * Try to find and used the primary anatomical surface. */ Brain* brain = m_selectedSurfaceModel->getBrain(); if (brain != NULL) { BrainStructure* brainStructure = brain->getBrainStructure(m_selectedStructure, false); if (brainStructure != NULL) { const Surface* volInteractSurface = brainStructure->getPrimaryAnatomicalSurface(); if (volInteractSurface != NULL) { const int numSurfaceModels = static_cast(m_availableSurfaceModels.size()); for (int32_t i = 0; i < numSurfaceModels; i++) { if (m_availableSurfaceModels[i]->getSurface() == volInteractSurface) { m_selectedSurfaceModel = m_availableSurfaceModels[i]; break; } } } } } } } } /* * Save model for retrieval later. */ if (m_selectedSurfaceModel != NULL) { m_previousSelectedSurfaceModel[m_selectedStructure] = m_selectedSurfaceModel; } } /** * Get a description of this object's content. * @return String describing this object's content. */ AString ModelSurfaceSelector::toString() const { AString msg = "selectedStructure=" + StructureEnum::toName(m_selectedStructure) + "selectedSurface="; if (m_selectedSurfaceModel != NULL) { msg += m_selectedSurfaceModel->getNameForGUI(false); } return msg; } /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @param instanceName * Name of the class' instance. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ SceneClass* ModelSurfaceSelector::saveToScene(const SceneAttributes* /*sceneAttributes*/, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "ModelSurfaceSelector", 1); sceneClass->addEnumeratedType("m_selectedStructure", m_selectedStructure); if (m_selectedSurfaceModel != NULL) { const Surface* surface = m_selectedSurfaceModel->getSurface(); if (surface != NULL) { const AString filename = surface->getFileNameNoPath(); sceneClass->addString("surfaceFileName", filename); } } return sceneClass; } /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass for the instance of a class that implements * this interface. May be NULL for some types of scenes. */ void ModelSurfaceSelector::restoreFromScene(const SceneAttributes* /*sceneAttributes*/, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } m_selectedStructure = sceneClass->getEnumeratedTypeValue("m_selectedStructure", m_selectedStructure); setSelectedStructure(m_selectedStructure); const AString surfaceFileName = sceneClass->getStringValue("surfaceFileName", ""); ModelSurface* matchedSurfaceModel = NULL; if (surfaceFileName.isEmpty() == false) { for (std::vector::iterator iter = m_availableSurfaceModels.begin(); iter != m_availableSurfaceModels.end(); iter++) { const Surface* surface = (*iter)->getSurface(); if (surfaceFileName == surface->getFileNameNoPath()) { matchedSurfaceModel = *iter; break; } } } /* * Note: setSelectedSurfaceModel() will update the content of * m_availableSurfaceModels and the above iterators will become * invalid. So setSelectedSurfaceModel() must be called outside * of the loop. * Bug found by JS. */ if (matchedSurfaceModel != NULL) { setSelectedSurfaceModel(matchedSurfaceModel); } } workbench-1.1.1/src/Brain/ModelSurfaceSelector.h000066400000000000000000000061621255417355300215600ustar00rootroot00000000000000#ifndef __MODEL_SURFACE_SELECTOR__H_ #define __MODEL_SURFACE_SELECTOR__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretObject.h" #include "SceneableInterface.h" #include "StructureEnum.h" namespace caret { class Model; class ModelSurface; class ModelSurfaceSelector : public CaretObject, public SceneableInterface { public: ModelSurfaceSelector(); virtual ~ModelSurfaceSelector(); ModelSurface* getSelectedSurfaceModel(); StructureEnum::Enum getSelectedStructure(); void setSelectedSurfaceModel(ModelSurface* surfaceModel); void setSelectedStructure(const StructureEnum::Enum selectedStructure); void getSelectableStructures( std::vector& selectableSructuresOut) const; void getSelectableSurfaceModels( std::vector& selectableSurfaceModelsOut) const; void updateSelector(const std::vector modelDisplayModels); AString toString() const; virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private: ModelSurfaceSelector(const ModelSurfaceSelector&); ModelSurfaceSelector& operator=(const ModelSurfaceSelector&); void updateSelector(); private: std::vector m_availableStructures; StructureEnum::Enum m_selectedStructure; StructureEnum::Enum m_defaultStructure; std::vector m_allSurfaceModels; std::vector m_availableSurfaceModels; ModelSurface* m_selectedSurfaceModel; std::map m_previousSelectedSurfaceModel; }; #ifdef __MODEL_SURFACE_SELECTOR_DECLARE__ // #endif // __MODEL_SURFACE_SELECTOR_DECLARE__ } // namespace #endif //__MODEL_SURFACE_SELECTOR__H_ workbench-1.1.1/src/Brain/ModelTypeEnum.cxx000066400000000000000000000206071255417355300206100ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __MODEL_DISPLAY_CONTROLLER_TYPE_ENUM_DECLARE__ #include "ModelTypeEnum.h" #undef __MODEL_DISPLAY_CONTROLLER_TYPE_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * Constructor. * * @param enumValue * An enumerated value. * @param integerCode * Integer code for this enumerated value. * * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ ModelTypeEnum::ModelTypeEnum(const Enum enumValue, const int32_t integerCode, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCode; this->name = name; this->guiName = guiName; } /** * Destructor. */ ModelTypeEnum::~ModelTypeEnum() { } /** * Initialize the enumerated metadata. */ void ModelTypeEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(ModelTypeEnum(MODEL_TYPE_INVALID, 0, "MODEL_TYPE_INVALID", "Invalid")); enumData.push_back(ModelTypeEnum(MODEL_TYPE_CHART, 1, "MODEL_TYPE_CHART", "Chart")); enumData.push_back(ModelTypeEnum(MODEL_TYPE_SURFACE, 2, "MODEL_TYPE_SURFACE", "Surface")); enumData.push_back(ModelTypeEnum(MODEL_TYPE_SURFACE_MONTAGE, 3, "MODEL_TYPE_SURFACE_MONTAGE", "Surface Montage")); enumData.push_back(ModelTypeEnum(MODEL_TYPE_VOLUME_SLICES, 4, "MODEL_TYPE_VOLUME_SLICES", "Volume")); enumData.push_back(ModelTypeEnum(MODEL_TYPE_WHOLE_BRAIN, 5, "MODEL_TYPE_WHOLE_BRAIN", "Whole Brain")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const ModelTypeEnum* ModelTypeEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const ModelTypeEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString ModelTypeEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const ModelTypeEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ ModelTypeEnum::Enum ModelTypeEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = MODEL_TYPE_INVALID; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ModelTypeEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type ModelTypeEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString ModelTypeEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const ModelTypeEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param guiName * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ ModelTypeEnum::Enum ModelTypeEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = MODEL_TYPE_INVALID; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ModelTypeEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type ModelTypeEnum")); } return enumValue; } /** * Get the integer code for a data type. * @param enumValue * Enumerated value. * @return * Integer code for data type. */ int32_t ModelTypeEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const ModelTypeEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ ModelTypeEnum::Enum ModelTypeEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = MODEL_TYPE_INVALID; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ModelTypeEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type ModelTypeEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void ModelTypeEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } workbench-1.1.1/src/Brain/ModelTypeEnum.h000066400000000000000000000061111255417355300202270ustar00rootroot00000000000000#ifndef __MODEL_DISPLAY_CONTROLLER_TYPE_ENUM__H_ #define __MODEL_DISPLAY_CONTROLLER_TYPE_ENUM__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { /// Enumerated type for class ModelTypeEnum { public: /** * Enumerated values. */ enum Enum { /** Invalid */ MODEL_TYPE_INVALID, /** Chart */ MODEL_TYPE_CHART, /** Surface */ MODEL_TYPE_SURFACE, /** Surface Montage */ MODEL_TYPE_SURFACE_MONTAGE, /** Volume Slices */ MODEL_TYPE_VOLUME_SLICES, /** Whole Brain */ MODEL_TYPE_WHOLE_BRAIN }; ~ModelTypeEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); private: ModelTypeEnum(const Enum enumValue, const int32_t integerCode, const AString& name, const AString& guiName); static const ModelTypeEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; #ifdef __MODEL_DISPLAY_CONTROLLER_TYPE_ENUM_DECLARE__ std::vector ModelTypeEnum::enumData; bool ModelTypeEnum::initializedFlag = false; #endif // __MODEL_DISPLAY_CONTROLLER_TYPE_ENUM_DECLARE__ } // namespace #endif //__MODEL_DISPLAY_CONTROLLER_TYPE_ENUM__H_ workbench-1.1.1/src/Brain/ModelVolume.cxx000066400000000000000000000145141255417355300203110ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "Brain.h" #include "BrowserTabContent.h" #include "EventBrowserTabGet.h" #include "EventManager.h" #include "Overlay.h" #include "OverlaySet.h" #include "OverlaySetArray.h" #include "ModelVolume.h" #include "SceneAttributes.h" #include "SceneClass.h" #include "SceneClassAssistant.h" #include "SceneEnumeratedType.h" #include "VolumeFile.h" using namespace caret; /** * Constructor. * @param brain - brain to which this volume model belongs. * */ ModelVolume::ModelVolume(Brain* brain) : Model(ModelTypeEnum::MODEL_TYPE_VOLUME_SLICES, brain) { std::vector overlaySurfaceStructures; m_overlaySetArray = new OverlaySetArray(overlaySurfaceStructures, Overlay::INCLUDE_VOLUME_FILES_YES, "Volume View"); m_lastVolumeFile = NULL; /* * Scene note: overlaySet is restored by parent class */ m_sceneAssistant = new SceneClassAssistant(); } /** * Destructor */ ModelVolume::~ModelVolume() { EventManager::get()->removeAllEventsFromListener(this); delete m_overlaySetArray; delete m_sceneAssistant; } /** * Get the name for use in a GUI. * * @param includeStructureFlag Prefix label with structure to which * this structure model belongs. * @return Name for use in a GUI. * */ AString ModelVolume::getNameForGUI(const bool /*includeStructureFlag*/) const { return "Volume"; } /** * @return The name that should be displayed in the tab * displaying this model. */ AString ModelVolume::getNameForBrowserTab() const { return "Volume"; } /** * Get the bottom-most active volume in the given window tab. * If no overlay is set to volume data, one will be set to a * volume if there is a volume loaded. * @param windowTabNumber * Tab number for content. * @return * Bottom-most volume or NULL if no volumes available. */ VolumeMappableInterface* ModelVolume::getUnderlayVolumeFile(const int32_t windowTabNumber) const { OverlaySet* overlaySet = m_overlaySetArray->getOverlaySet(windowTabNumber); VolumeMappableInterface* vf = overlaySet->getUnderlayVolume(); if (vf == NULL) { vf = overlaySet->setUnderlayToVolume(); } // EventBrowserTabGet getBrowserTabEvent(windowTabNumber); // EventManager::get()->sendEvent(getBrowserTabEvent.getPointer()); // BrowserTabContent* btc = getBrowserTabEvent.getBrowserTab(); // if (btc != NULL) { // OverlaySet* overlaySet = btc->getOverlaySet(); // vf = overlaySet->getUnderlayVolume(); // if (vf == NULL) { // vf = overlaySet->setUnderlayToVolume(); // } // } return vf; } /** * Update the model. * @param windowTabNumber * Tab number of window. */ void ModelVolume::updateModel(const int32_t /*windowTabNumber*/) { } /** * Receive events from the event manager. * * @param event * The event. */ void ModelVolume::receiveEvent(Event* /*event*/) { } /** * Get the overlay set for the given tab. * @param tabIndex * Index of tab. * @return * Overlay set at the given tab index. */ OverlaySet* ModelVolume::getOverlaySet(const int tabIndex) { return m_overlaySetArray->getOverlaySet(tabIndex); } /** * Get the overlay set for the given tab. * @param tabIndex * Index of tab. * @return * Overlay set at the given tab index. */ const OverlaySet* ModelVolume::getOverlaySet(const int tabIndex) const { return m_overlaySetArray->getOverlaySet(tabIndex); } /** * Initilize the overlays for this model. */ void ModelVolume::initializeOverlays() { m_overlaySetArray->initializeOverlaySelections(); } /** * Save information specific to this type of model to the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @param sceneClass * SceneClass to which model specific information is added. */ void ModelVolume::saveModelSpecificInformationToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass) { m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); } /** * Restore information specific to the type of model from the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass from which model specific information is obtained. */ void ModelVolume::restoreModelSpecificInformationFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); } /** * Copy the tab content from the source tab index to the * destination tab index. * * @param sourceTabIndex * Source from which tab content is copied. * @param destinationTabIndex * Destination to which tab content is copied. */ void ModelVolume::copyTabContent(const int32_t sourceTabIndex, const int32_t destinationTabIndex) { Model::copyTabContent(sourceTabIndex, destinationTabIndex); m_overlaySetArray->copyOverlaySet(sourceTabIndex, destinationTabIndex); } workbench-1.1.1/src/Brain/ModelVolume.h000066400000000000000000000055161255417355300177400ustar00rootroot00000000000000#ifndef __MODEL_DISPLAY_CONTROLLER_VOLUME_H__ #define __MODEL_DISPLAY_CONTROLLER_VOLUME_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "EventListenerInterface.h" #include "Model.h" namespace caret { class Brain; class SceneClassAssistant; class OverlaySetArray; class VolumeMappableInterface; /// Controls the display of a volumes. class ModelVolume : public Model, public EventListenerInterface { public: ModelVolume(Brain* brain); virtual ~ModelVolume(); VolumeMappableInterface* getUnderlayVolumeFile(const int32_t windowTabNumber) const; void updateModel(const int32_t windowTabNumber); void receiveEvent(Event* event); OverlaySet* getOverlaySet(const int tabIndex); const OverlaySet* getOverlaySet(const int tabIndex) const; void initializeOverlays(); virtual void copyTabContent(const int32_t sourceTabIndex, const int32_t destinationTabIndex); protected: virtual void saveModelSpecificInformationToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass); virtual void restoreModelSpecificInformationFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private: ModelVolume(const ModelVolume&); ModelVolume& operator=(const ModelVolume&); private: public: AString getNameForGUI(const bool includeStructureFlag) const; virtual AString getNameForBrowserTab() const; private: VolumeMappableInterface* m_lastVolumeFile; /** Overlays sets for this model and for each tab */ OverlaySetArray* m_overlaySetArray; SceneClassAssistant* m_sceneAssistant; }; } // namespace #endif // __MODEL_DISPLAY_CONTROLLER_VOLUME_H__ workbench-1.1.1/src/Brain/ModelWholeBrain.cxx000066400000000000000000000566541255417355300211070ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __MODEL_WHOLE_BRAIN_DEFINE__ #include "ModelWholeBrain.h" #undef __MODEL_WHOLE_BRAIN_DEFINE__ #include "Brain.h" #include "BrainStructure.h" #include "BrowserTabContent.h" #include "EventBrowserTabGet.h" #include "EventManager.h" #include "EventModelGetAll.h" #include "EventSurfacesGet.h" #include "IdentificationManager.h" #include "ModelSurface.h" #include "OverlaySet.h" #include "OverlaySetArray.h" #include "SceneClass.h" #include "SceneClassArray.h" #include "SceneClassAssistant.h" #include "Surface.h" using namespace caret; /** * Constructor. * @param brain - brain to which this whole brain model belongs. * */ ModelWholeBrain::ModelWholeBrain(Brain* brain) : Model(ModelTypeEnum::MODEL_TYPE_WHOLE_BRAIN, brain) { if (s_anatomicalSurfaceTypes.empty()) { SurfaceTypeEnum::getAllAnatomicallyShapedEnums(s_anatomicalSurfaceTypes); } for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_selectedSurfaceType[i] = SurfaceTypeEnum::ANATOMICAL; m_cerebellumEnabled[i] = true; m_leftEnabled[i] = true; m_rightEnabled[i] = true; m_leftRightSeparation[i] = 0.0; m_cerebellumSeparation[i] = 0.0; } std::vector overlaySurfaceStructures; overlaySurfaceStructures.push_back(StructureEnum::CORTEX_LEFT); overlaySurfaceStructures.push_back(StructureEnum::CORTEX_RIGHT); overlaySurfaceStructures.push_back(StructureEnum::CEREBELLUM); m_overlaySetArray = new OverlaySetArray(overlaySurfaceStructures, Overlay::INCLUDE_VOLUME_FILES_YES, "All View"); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_IDENTIFICATION_HIGHLIGHT_LOCATION); m_sceneAssistant = new SceneClassAssistant(); m_sceneAssistant->addTabIndexedEnumeratedTypeArray("m_selectedSurfaceType", m_selectedSurfaceType); m_sceneAssistant->addTabIndexedBooleanArray("m_leftEnabled", m_leftEnabled); m_sceneAssistant->addTabIndexedBooleanArray("m_rightEnabled", m_rightEnabled); m_sceneAssistant->addTabIndexedBooleanArray("m_cerebellumEnabled", m_cerebellumEnabled); m_sceneAssistant->addTabIndexedFloatArray("m_leftRightSeparation", m_leftRightSeparation); m_sceneAssistant->addTabIndexedFloatArray("m_cerebellumSeparation", m_cerebellumSeparation); } /** * Destructor */ ModelWholeBrain::~ModelWholeBrain() { EventManager::get()->removeAllEventsFromListener(this); delete m_overlaySetArray; delete m_sceneAssistant; } /** * Get the available surface types. * @param surfaceTypesOut * Output loaded with the available surface types. */ void ModelWholeBrain::getAvailableSurfaceTypes(std::vector& surfaceTypesOut) { updateModel(); surfaceTypesOut.clear(); surfaceTypesOut.insert(surfaceTypesOut.end(), m_availableSurfaceTypes.begin(), m_availableSurfaceTypes.end()); } /** * Get the selected surface type for the given tab. * * @param windowTabNumber * The tab. * @return * Surface type for the tab. */ SurfaceTypeEnum::Enum ModelWholeBrain::getSelectedSurfaceType(const int32_t windowTabNumber) { updateModel(); return m_selectedSurfaceType[windowTabNumber]; } /** * Update this model. */ void ModelWholeBrain::updateModel() { /* * Get all of the surfaces */ EventSurfacesGet surfaceEvent; EventManager::get()->sendEvent(surfaceEvent.getPointer()); std::vector allSurfaces = surfaceEvent.getSurfaces(); /* * Update the available surface types */ m_availableSurfaceTypes.clear(); for (std::vector::iterator surfIter = allSurfaces.begin(); surfIter != allSurfaces.end(); surfIter++) { m_availableSurfaceTypes.insert((*surfIter)->getSurfaceType()); } /* * Set the default surface type from the available anatomical types */ std::vector::iterator defaultSurfaceTypeIter = std::find_first_of(s_anatomicalSurfaceTypes.begin(), s_anatomicalSurfaceTypes.end(), m_availableSurfaceTypes.begin(), m_availableSurfaceTypes.end()); const SurfaceTypeEnum::Enum defaultSurfaceType = ((defaultSurfaceTypeIter != s_anatomicalSurfaceTypes.end()) ? *defaultSurfaceTypeIter : SurfaceTypeEnum::ANATOMICAL); /* * If the selected surface type in a tab is no longer valid, update * selected surface type. */ for (int32_t iTab = 0; iTab < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; iTab++) { if (std::find(m_availableSurfaceTypes.begin(), m_availableSurfaceTypes.end(), m_selectedSurfaceType[iTab]) == m_availableSurfaceTypes.end()) { m_selectedSurfaceType[iTab] = defaultSurfaceType; } } /* * Deselect any surfaces that are no longer valid. They will get updated * the next time getSelected */ for (int32_t tabIndex = 0; tabIndex < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; tabIndex++) { for (std::map, Surface*>::iterator mapIter = m_selectedSurface[tabIndex].begin(); mapIter != m_selectedSurface[tabIndex].end(); mapIter++) { if (std::find(allSurfaces.begin(), allSurfaces.end(), mapIter->second) == allSurfaces.end()) { mapIter->second = NULL; } } } } ///** // * Update this model. // */ //void //ModelWholeBrain::updateModel() //{ // /* // * Get all model to find loaded surface types. // */ // EventModelGetAll eventGetModels; // EventManager::get()->sendEvent(eventGetModels.getPointer()); // const std::vector allModels = // eventGetModels.getModels(); // // /* // * Get ALL possible surface types. // */ // std::vector allSurfaceTypes; // SurfaceTypeEnum::getAllEnums(allSurfaceTypes); // const int32_t numEnumTypes = static_cast(allSurfaceTypes.size()); // std::vector surfaceTypeValid(numEnumTypes, false); // // /* // * Find surface types that are actually used. // */ // for (std::vector::const_iterator iter = allModels.begin(); // iter != allModels.end(); // iter++) { // ModelSurface* surfaceModel = // dynamic_cast(*iter); // if (surfaceModel != NULL) { // SurfaceTypeEnum::Enum surfaceType = surfaceModel->getSurface()->getSurfaceType(); // // for (int i = 0; i < numEnumTypes; i++) { // if (allSurfaceTypes[i] == surfaceType) { // surfaceTypeValid[i] = true; // break; // } // } // } // } // // /* // * Set the available surface types. // */ // m_availableSurfaceTypes.clear(); // for (int i = 0; i < numEnumTypes; i++) { // if (surfaceTypeValid[i]) { // m_availableSurfaceTypes.push_back(allSurfaceTypes[i]); // } // } // // // // /* // * Update the selected surface and volume types. // */ // for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { // if (std::find(m_availableSurfaceTypes.begin(), // m_availableSurfaceTypes.end(), // m_selectedSurfaceType[i]) == m_availableSurfaceTypes.end()) { // if (m_availableSurfaceTypes.empty() == false) { // m_selectedSurfaceType[i] = m_availableSurfaceTypes[0]; // } // else { // m_selectedSurfaceType[i] = SurfaceTypeEnum::ANATOMICAL; // } // } // // VolumeMappableInterface* vf = getUnderlayVolumeFile(i); // if (vf != NULL) { //// m_volumeSlicesSelected[i].updateForVolumeFile(vf); // } // } //} /** * Set the selected surface type. * @param windowTabNumber * Index of window tab. * @param surfaceType * New surface type. */ void ModelWholeBrain::setSelectedSurfaceType(const int32_t windowTabNumber, const SurfaceTypeEnum::Enum surfaceType) { m_selectedSurfaceType[windowTabNumber] = surfaceType; updateModel(); } /** * Get the name for use in a GUI. * * @param includeStructureFlag - Prefix label with structure to which * this structure model belongs. * @return Name for use in a GUI. * */ AString ModelWholeBrain::getNameForGUI(const bool /*includeStructureFlag*/) const { return "Whole Brain"; } /** * @return The name that should be displayed in the tab * displaying this model. */ AString ModelWholeBrain::getNameForBrowserTab() const { return "All "; } /** * Get the bottom-most active volume in the given window tab. * @param windowTabNumber * Tab number for content. * @return * Bottom-most volume or NULL if not available (such as * when all overlay are not volumes or they are disabled). */ VolumeMappableInterface* ModelWholeBrain::getUnderlayVolumeFile(const int32_t windowTabNumber) const { VolumeMappableInterface* vf = NULL; EventBrowserTabGet getBrowserTabEvent(windowTabNumber); EventManager::get()->sendEvent(getBrowserTabEvent.getPointer()); BrowserTabContent* btc = getBrowserTabEvent.getBrowserTab(); if (btc != NULL) { OverlaySet* overlaySet = btc->getOverlaySet(); if (overlaySet != NULL) { vf = overlaySet->getUnderlayVolume(); } } return vf; } /** * @return Return the surfaces displayed in the given tab. * @param windowTabIndex * THe tab. */ std::vector ModelWholeBrain::getSelectedSurfaces(const int32_t windowTabIndex) { std::vector surfaces; /* * Get the surfaces. */ Brain* brain = getBrain(); const int32_t numberOfBrainStructures = brain->getNumberOfBrainStructures(); for (int32_t i = 0; i < numberOfBrainStructures; i++) { BrainStructure* brainStructure = brain->getBrainStructure(i); const StructureEnum::Enum structure = brainStructure->getStructure(); Surface* surface = getSelectedSurface(structure, windowTabIndex); surfaces.push_back(surface); } return surfaces; } /** * Get the surface for the given structure in the given tab that is for * the currently selected surface type. * * @param structure * Structure for the surface * @param windowTabNumber * Tab number of window. * @param Pointer to selected surface for given structure or NULL if not available. */ Surface* ModelWholeBrain::getSelectedSurface(const StructureEnum::Enum structure, const int32_t windowTabNumber) { const SurfaceTypeEnum::Enum surfaceType = getSelectedSurfaceType(windowTabNumber); std::pair key = std::make_pair(structure, surfaceType); /* * Get the currently selected surface. */ Surface* s = m_selectedSurface[windowTabNumber][key]; /* * See if currently selected surface is valid. */ BrainStructure* brainStructure = m_brain->getBrainStructure(structure, false); if (brainStructure == NULL) { return NULL; } std::vector surfaces; brainStructure->getSurfacesOfType(surfaceType, surfaces); if (std::find(surfaces.begin(), surfaces.end(), s) == surfaces.end()) { s = NULL; } /* * If no selected surface, use first surface. */ if (s == NULL) { if (surfaces.empty() == false) { s = surfaces[0]; } } m_selectedSurface[windowTabNumber][key] = s; return s; } /** * Set the selected surface for the given structure in the given window tab * that is for the currently selected surface type. * surface type. * * @param structure * Structure for the surface * @param windowTabNumber * Tab number of window. * @param surface * Newly selected surface. */ void ModelWholeBrain::setSelectedSurface(const StructureEnum::Enum structure, const int32_t windowTabNumber, Surface* surface) { const SurfaceTypeEnum::Enum surfaceType = getSelectedSurfaceType(windowTabNumber); std::pair key = std::make_pair(structure, surfaceType); m_selectedSurface[windowTabNumber][key] = surface; } /** * Receive events from the event manager. * * @param event * The event. */ void ModelWholeBrain::receiveEvent(Event* /*event*/) { } /** * Get the overlay set for the given tab. * @param tabIndex * Index of tab. * @return * Overlay set at the given tab index. */ OverlaySet* ModelWholeBrain::getOverlaySet(const int tabIndex) { return m_overlaySetArray->getOverlaySet(tabIndex); } /** * Get the overlay set for the given tab. * @param tabIndex * Index of tab. * @return * Overlay set at the given tab index. */ const OverlaySet* ModelWholeBrain::getOverlaySet(const int tabIndex) const { return m_overlaySetArray->getOverlaySet(tabIndex); } /** * Initilize the overlays for this model. */ void ModelWholeBrain::initializeOverlays() { m_overlaySetArray->initializeOverlaySelections(); } /** * Initialize the selected surfaces. */ void ModelWholeBrain::initializeSelectedSurfaces() { std::vector surfaceTypes; getAvailableSurfaceTypes(surfaceTypes); if (std::find(surfaceTypes.begin(), surfaceTypes.end(), SurfaceTypeEnum::ANATOMICAL) != surfaceTypes.end()) { for (int32_t iTab = 0; iTab < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; iTab++) { setSelectedSurfaceType(iTab, SurfaceTypeEnum::ANATOMICAL); } } const int32_t numberOfStructures = m_brain->getNumberOfBrainStructures(); for (int32_t i = 0; i < numberOfStructures; i++) { const BrainStructure* brainStructure = m_brain->getBrainStructure(i); Surface* surface = const_cast(brainStructure->getPrimaryAnatomicalSurface()); if (surface != NULL) { const StructureEnum::Enum structure = brainStructure->getStructure(); for (int32_t iTab = 0; iTab < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; iTab++) { setSelectedSurface(structure, iTab, surface); } } } } /** * Save information specific to this type of model to the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @param sceneClass * SceneClass to which model specific information is added. */ void ModelWholeBrain::saveModelSpecificInformationToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass) { m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); const std::vector tabIndices = sceneAttributes->getIndicesOfTabsForSavingToScene(); const int32_t numTabs = static_cast(tabIndices.size()); std::vector classesForSelectedSurfaceArray; for (int32_t i = 0; i < numTabs; i++) { const int32_t tabIndex = tabIndices[i]; const SurfaceTypeEnum::Enum selectedSurfaceType = getSelectedSurfaceType(tabIndex); /* * Updates selected surfaces */ getSelectedSurfaces(tabIndex); for (std::map, Surface*>::iterator mapIter = m_selectedSurface[tabIndex].begin(); mapIter != m_selectedSurface[tabIndex].end(); mapIter++) { std::pair structureSurfaceType = mapIter->first; const SurfaceTypeEnum::Enum surfaceType = structureSurfaceType.second; if (surfaceType == selectedSurfaceType) { Surface* surface = mapIter->second; if (surface != NULL) { const StructureEnum::Enum structure = structureSurfaceType.first; const AString name = ("m_selectedSurface[" + AString::number(tabIndex) + "]"); SceneClass* surfaceClass = new SceneClass(name, "SurfaceSelectionMap", 1); surfaceClass->addInteger("tabIndex", tabIndex); surfaceClass->addEnumeratedType("structure", structure); surfaceClass->addEnumeratedType("surfaceType", surfaceType); surfaceClass->addString("surfaceName", surface->getFileNameNoPath()); classesForSelectedSurfaceArray.push_back(surfaceClass); } } } } sceneClass->addChild(new SceneClassArray("m_selectedSurface", classesForSelectedSurfaceArray)); } /** * Restore information specific to the type of model from the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass from which model specific information is obtained. */ void ModelWholeBrain::restoreModelSpecificInformationFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } /* * Restore selected surface */ const SceneClassArray* surfaceSelectionArray = sceneClass->getClassArray("m_selectedSurface"); if (surfaceSelectionArray != NULL) { const int32_t numClasses = surfaceSelectionArray->getNumberOfArrayElements(); for (int32_t ica = 0; ica < numClasses; ica++) { const SceneClass* surfaceClass = surfaceSelectionArray->getClassAtIndex(ica); const int32_t tabIndex = surfaceClass->getIntegerValue("tabIndex", -1); const StructureEnum::Enum structure = surfaceClass->getEnumeratedTypeValue("structure", StructureEnum::INVALID); const SurfaceTypeEnum::Enum surfaceType = surfaceClass->getEnumeratedTypeValue("surfaceType", SurfaceTypeEnum::UNKNOWN); const AString surfaceName = surfaceClass->getStringValue("surfaceName", ""); if ((tabIndex >= 0) && (structure != StructureEnum::INVALID) && (surfaceType != SurfaceTypeEnum::UNKNOWN) && (surfaceName.isEmpty() == false)) { BrainStructure* brainStructure = getBrain()->getBrainStructure(structure, false); if (brainStructure != NULL) { const int32_t numSurfaces = brainStructure->getNumberOfSurfaces(); for (int32_t i = 0; i < numSurfaces; i++) { Surface* surface = brainStructure->getSurface(i); const AString loadedSurfaceName = surface->getFileName(); if (loadedSurfaceName.endsWith(surfaceName)) { setSelectedSurfaceType(tabIndex, surfaceType); setSelectedSurface(structure, tabIndex, surface); break; } } } } } } /* * Need to do after */ m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); } /** * Copy the tab content from the source tab index to the * destination tab index. * * @param sourceTabIndex * Source from which tab content is copied. * @param destinationTabIndex * Destination to which tab content is copied. */ void ModelWholeBrain::copyTabContent(const int32_t sourceTabIndex, const int32_t destinationTabIndex) { Model::copyTabContent(sourceTabIndex, destinationTabIndex); CaretAssertArrayIndex(m_selectedSurfaceType, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, sourceTabIndex); CaretAssertArrayIndex(m_selectedSurfaceType, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, destinationTabIndex); m_selectedSurface[destinationTabIndex] = m_selectedSurface[sourceTabIndex]; m_selectedSurfaceType[destinationTabIndex] = m_selectedSurfaceType[sourceTabIndex]; m_leftEnabled[destinationTabIndex] = m_leftEnabled[sourceTabIndex]; m_rightEnabled[destinationTabIndex] = m_rightEnabled[sourceTabIndex]; m_cerebellumEnabled[destinationTabIndex] = m_cerebellumEnabled[sourceTabIndex]; m_leftRightSeparation[destinationTabIndex] = m_leftRightSeparation[sourceTabIndex]; m_cerebellumSeparation[destinationTabIndex] = m_cerebellumSeparation[sourceTabIndex]; m_overlaySetArray->copyOverlaySet(sourceTabIndex, destinationTabIndex); } workbench-1.1.1/src/Brain/ModelWholeBrain.h000066400000000000000000000121461255417355300205200ustar00rootroot00000000000000#ifndef __MODEL_WHOLE_BRAIN_H__ #define __MODEL_WHOLE_BRAIN_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "BrainConstants.h" #include "EventListenerInterface.h" #include "Model.h" #include "StructureEnum.h" #include "SurfaceTypeEnum.h" namespace caret { class Brain; class OverlaySetArray; class Surface; class SceneClassAssistant; class VolumeMappableInterface; /// Controls the display of a whole brain. class ModelWholeBrain : public Model, public EventListenerInterface { public: ModelWholeBrain(Brain* brain); virtual ~ModelWholeBrain(); VolumeMappableInterface* getUnderlayVolumeFile(const int32_t windowTabNumber) const; void getAvailableSurfaceTypes(std::vector& surfaceTypesOut); SurfaceTypeEnum::Enum getSelectedSurfaceType(const int32_t windowTabNumber); void setSelectedSurfaceType(const int32_t windowTabNumber, const SurfaceTypeEnum::Enum surfaceType); std::vector getSelectedSurfaces(const int32_t windowTabNumber); Surface* getSelectedSurface(const StructureEnum::Enum structure, const int32_t windowTabNumber); void setSelectedSurface(const StructureEnum::Enum structure, const int32_t windowTabNumber, Surface* surface); void receiveEvent(Event* event); OverlaySet* getOverlaySet(const int tabIndex); const OverlaySet* getOverlaySet(const int tabIndex) const; void initializeOverlays(); virtual void initializeSelectedSurfaces(); virtual void copyTabContent(const int32_t sourceTabIndex, const int32_t destinationTabIndex); void updateModel(); protected: virtual void saveModelSpecificInformationToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass); virtual void restoreModelSpecificInformationFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private: ModelWholeBrain(const ModelWholeBrain&); ModelWholeBrain& operator=(const ModelWholeBrain&); public: AString getNameForGUI(const bool includeStructureFlag) const; virtual AString getNameForBrowserTab() const; private: /** Type of surface for display */ mutable SurfaceTypeEnum::Enum m_selectedSurfaceType[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; /** Selected surface for structure/surface-type */ std::map, Surface*> m_selectedSurface[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; /** Available surface types */ std::set m_availableSurfaceTypes; bool m_leftEnabled[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; bool m_rightEnabled[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; bool m_cerebellumEnabled[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; float m_leftRightSeparation[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; float m_cerebellumSeparation[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; /** Surface types that have an anatomical appearance. */ static std::vector s_anatomicalSurfaceTypes; // mutable VolumeSliceCoordinateSelection m_volumeSlicesSelected[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; VolumeMappableInterface* m_lastVolumeFile; /** Overlays sets for this model and for each tab */ OverlaySetArray* m_overlaySetArray; SceneClassAssistant* m_sceneAssistant; }; #ifdef __MODEL_WHOLE_BRAIN_DEFINE__ std::vector ModelWholeBrain::s_anatomicalSurfaceTypes; #endif // __MODEL_WHOLE_BRAIN_DEFINE__ } // namespace #endif // __MODEL_WHOLE_BRAIN_H__ workbench-1.1.1/src/Brain/Overlay.cxx000066400000000000000000000601271255417355300175030ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __OVERLAY_DECLARE__ #include "Overlay.h" #undef __OVERLAY_DECLARE__ #include "CaretAssert.h" #include "CaretLogger.h" #include "CaretMappableDataFile.h" #include "EventCaretMappableDataFilesGet.h" #include "EventManager.h" #include "EventOverlayValidate.h" #include "LabelFile.h" #include "MetricFile.h" #include "PlainTextStringBuilder.h" #include "RgbaFile.h" #include "SceneClass.h" #include "SceneClassAssistant.h" #include "Surface.h" #include "VolumeFile.h" using namespace caret; /** * \class Overlay * \brief An overlay for selection of mappable data. */ /** * Constructor for files in the given structurs and perhaps volume files. * * @param includeSurfaceStructures * Surface structures for files available in this overlay. * @param includeVolumeFiles * Include (or not) volume files. */ Overlay::Overlay(const std::vector& includeSurfaceStructures, const Overlay::IncludeVolumeFiles includeVolumeFiles) : m_includeSurfaceStructures(includeSurfaceStructures), m_includeVolumeFiles(includeVolumeFiles) { m_opacity = 1.0; m_selectedMapFile = NULL; m_selectedMapIndex = -1; m_name = "Overlay "; m_enabled = true; m_paletteDisplayedFlag = false; m_mapYokingGroup = MapYokingGroupEnum::MAP_YOKING_GROUP_OFF; m_wholeBrainVoxelDrawingMode = WholeBrainVoxelDrawingMode::DRAW_VOXELS_ON_TWO_D_SLICES; m_sceneAssistant = new SceneClassAssistant(); m_sceneAssistant->add("m_opacity", &m_opacity); m_sceneAssistant->add("m_enabled", &m_enabled); m_sceneAssistant->add("m_paletteDisplayedFlag", &m_paletteDisplayedFlag); m_sceneAssistant->add("m_wholeBrainVoxelDrawingMode", &m_wholeBrainVoxelDrawingMode); m_sceneAssistant->add("m_mapYokingGroup", &m_mapYokingGroup); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_OVERLAY_VALIDATE); } /** * Destructor. */ Overlay::~Overlay() { EventManager::get()->removeAllEventsFromListener(this); delete m_sceneAssistant; } /** * Receives events that this object is listening for. * * @param event * An event. */ void Overlay::receiveEvent(Event* event) { if (event->getEventType() == EventTypeEnum::EVENT_OVERLAY_VALIDATE) { EventOverlayValidate* eov = dynamic_cast(event); CaretAssert(eov); eov->testValidOverlay(this); } } /** * Set the number of this overlay. * * @param overlayIndex * Index for this overlay. */ void Overlay::setOverlayNumber(const int32_t overlayIndex) { m_name = "Overlay " + AString::number(overlayIndex + 1); } /** * Get the opacity. * * @return The opacity. */ float Overlay::getOpacity() const { return m_opacity; } /** * Set the opacity. * * @param opacity * New value for opacity. */ void Overlay::setOpacity(const float opacity) { m_opacity = opacity; } /** * @return The voxel drawing mode for whole brain. */ WholeBrainVoxelDrawingMode::Enum Overlay::getWholeBrainVoxelDrawingMode() const { return m_wholeBrainVoxelDrawingMode; } /** * Set the voxel drawing mode for whole brain. * * @param wholeBrainVoxelDrawingMode * New mode. */ void Overlay::setWholeBrainVoxelDrawingMode(const WholeBrainVoxelDrawingMode::Enum wholeBrainVoxelDrawingMode) { m_wholeBrainVoxelDrawingMode = wholeBrainVoxelDrawingMode; } AString Overlay::getName() const { return m_name; } /** * Get a description of this object's content. * @return String describing this object's content. */ AString Overlay::toString() const { PlainTextStringBuilder tb; getDescriptionOfContent(tb); return tb.getText(); } /** * Get a text description of the window's content. * * @param descriptionOut * Description of the window's content. */ void Overlay::getDescriptionOfContent(PlainTextStringBuilder& descriptionOut) const { Overlay* me = const_cast(this); if (me != NULL) { if (me->isEnabled()) { CaretMappableDataFile* mapFile = NULL; int32_t mapIndex = 0; me->getSelectionData(mapFile, mapIndex); if (mapFile != NULL) { descriptionOut.addLine("File: "+ mapFile->getFileNameNoPath()); if (mapFile->hasMapAttributes()) { if ((mapIndex >= 0) && (mapIndex < mapFile->getNumberOfMaps())) { descriptionOut.addLine("Map Index: " + AString::number(mapIndex + 1)); descriptionOut.addLine("Map Name: " + mapFile->getMapName(mapIndex)); } } descriptionOut.addLine("Structure: " + StructureEnum::toGuiName(mapFile->getStructure())); } } } } /** * @return Enabled status for this surface overlay. */ bool Overlay::isEnabled() const { return m_enabled; } /** * Set the enabled status for this surface overlay. * @param enabled * New status. */ void Overlay::setEnabled(const bool enabled) { m_enabled = enabled; } /** * Copy the data from the given overlay to this overlay. * @param overlay * Overlay from which data is transferred. */ void Overlay::copyData(const Overlay* overlay) { CaretAssert(overlay); /* * These members are not copied since they * identify the overlay: * name * overlayIndex * */ m_opacity = overlay->m_opacity; m_enabled = overlay->m_enabled; m_selectedMapFile = overlay->m_selectedMapFile; m_selectedMapIndex = overlay->m_selectedMapIndex; m_paletteDisplayedFlag = overlay->m_paletteDisplayedFlag; m_mapYokingGroup = overlay->m_mapYokingGroup; } /** * Swap the data from the given overlay to this overlay. * @param overlay * Overlay from which data is transferred. */ void Overlay::swapData(Overlay* overlay) { Overlay* swapOverlay = new Overlay(m_includeSurfaceStructures, m_includeVolumeFiles); swapOverlay->copyData(overlay); overlay->copyData(this); copyData(swapOverlay); delete swapOverlay; } /** * Return the selection information. This method is typically * called to update the user-interface. * * @param selectedMapFileOut * The selected map file. May be NULL. * @param selectedMapIndexOut * Index of selected map in the selected file. */ void Overlay::getSelectionData(CaretMappableDataFile* &selectedMapFileOut, int32_t& selectedMapIndexOut) { std::vector mapFiles; getSelectionData(mapFiles, selectedMapFileOut, //mapUniqueID, selectedMapIndexOut); } /** * Return the selection information. This method is typically * called to update the user-interface. * * @param mapFilesOut * Contains all map files that can be selected. * @param selectedMapFileOut * The selected map file. May be NULL. * @param selectedMapUniqueIDOut * UniqueID of selected map. * @param selectedMapIndexOut * Index of selected map in the selected file. */ void Overlay::getSelectionData(std::vector& mapFilesOut, CaretMappableDataFile* &selectedMapFileOut, //AString& selectedMapUniqueIDOut, int32_t& selectedMapIndexOut) { mapFilesOut.clear(); selectedMapFileOut = NULL; selectedMapIndexOut = -1; /** * Get the data files. */ std::vector allDataFiles; EventCaretMappableDataFilesGet eventGetMapDataFiles; EventManager::get()->sendEvent(eventGetMapDataFiles.getPointer()); eventGetMapDataFiles.getAllFiles(allDataFiles); bool showVolumeMapFiles = false; switch (m_includeVolumeFiles) { case INCLUDE_VOLUME_FILES_NO: break; case INCLUDE_VOLUME_FILES_YES: showVolumeMapFiles = true; break; } bool showSurfaceMapFiles = false; if ( ! m_includeSurfaceStructures.empty()) { showSurfaceMapFiles = true; } /* * Use only those data files that meet criteria. */ for (std::vector::iterator iter = allDataFiles.begin(); iter != allDataFiles.end(); iter++) { CaretMappableDataFile* mapFile = *iter; bool useIt = false; bool mappable = false; if (mapFile->isSurfaceMappable()) { mappable = true; if (showSurfaceMapFiles) { for (std::vector::const_iterator iter = m_includeSurfaceStructures.begin(); iter != m_includeSurfaceStructures.end(); iter++) { if (mapFile->isMappableToSurfaceStructure(*iter)) { useIt = true; break; } } } } if (mapFile->isVolumeMappable()) { mappable = true; if (showVolumeMapFiles) { useIt = true; } } if (useIt) { mapFilesOut.push_back(mapFile); } } /* * Does selected data file still no longer exist? */ if (std::find(mapFilesOut.begin(), mapFilesOut.end(), m_selectedMapFile) == mapFilesOut.end()) { /* * Invalidate seleted file and disable yoking since * the selected file will change. */ m_selectedMapFile = NULL; m_mapYokingGroup = MapYokingGroupEnum::MAP_YOKING_GROUP_OFF; } /* * If selected data file is valid, see if selected * map is still valid. If not, use first map. */ if (m_selectedMapFile != NULL) { if (m_selectedMapIndex >= m_selectedMapFile->getNumberOfMaps()) { m_selectedMapIndex = m_selectedMapFile->getNumberOfMaps() - 1; } if (m_selectedMapIndex < 0) { m_selectedMapIndex = 0; } } else { /* * Use first map in first file that has one or more maps. */ if (m_selectedMapFile == NULL) { if (mapFilesOut.empty() == false) { for (std::vector::iterator iter = mapFilesOut.begin(); iter != mapFilesOut.end(); iter++) { CaretMappableDataFile* mapTypeFile = *iter; if (mapTypeFile->getNumberOfMaps() > 0) { m_selectedMapFile = mapTypeFile; m_selectedMapIndex = 0; } } } } } selectedMapFileOut = m_selectedMapFile; if (selectedMapFileOut != NULL) { // /* // * Update for overlay yoking // */ // if (m_mapYokingGroup != MapYokingGroupEnum::MAP_YOKING_GROUP_OFF) { // const int32_t yokeMapIndex = MapYokingGroupEnum::getSelectedMapIndex(m_mapYokingGroup); // if ((yokeMapIndex >= 0) // && (yokeMapIndex < selectedMapFileOut->getNumberOfMaps())) { // m_selectedMapIndex = yokeMapIndex; // } // else if (yokeMapIndex >= selectedMapFileOut->getNumberOfMaps()) { // m_selectedMapIndex = selectedMapFileOut->getNumberOfMaps() - 1; // } // } // selectedMapIndexOut = m_selectedMapIndex; //m_selectedMapFile->getMapIndexFromUniqueID(selectedMapUniqueIDOut); } } /** * Set the selected map file and map. * @param selectedMapFile * File that is selected. * @param selectedMapName * Map name that is selected. */ void Overlay::setSelectionData(CaretMappableDataFile* selectedMapFile, const int32_t selectedMapIndex) { m_selectedMapFile = selectedMapFile; m_selectedMapIndex = selectedMapIndex; if (m_mapYokingGroup != MapYokingGroupEnum::MAP_YOKING_GROUP_OFF) { if (m_selectedMapFile == NULL) { m_mapYokingGroup = MapYokingGroupEnum::MAP_YOKING_GROUP_OFF; } // if (selectedMapFile != NULL) { // MapYokingGroupEnum::setSelectedMapIndex(m_mapYokingGroup, // selectedMapIndex); // } // else { // m_mapYokingGroup = MapYokingGroupEnum::MAP_YOKING_GROUP_OFF; // } } } /** * @return Is display of palette in graphics window enabled? */ bool Overlay::isPaletteDisplayEnabled() const { return m_paletteDisplayedFlag; } /** * Set display of palette in graphics window. * @param enabled * New status for palette display in graphics window. */ void Overlay::setPaletteDisplayEnabled(const bool enabled) { m_paletteDisplayedFlag = enabled; } /** * @return Selected map yoking group. */ MapYokingGroupEnum::Enum Overlay::getMapYokingGroup() const { return m_mapYokingGroup; } /** * Set the map yoking group. * * @param mapYokingGroup * New value for map yoking group. */ void Overlay::setMapYokingGroup(const MapYokingGroupEnum::Enum mapYokingGroup) { m_mapYokingGroup = mapYokingGroup; } /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @param instanceName * Name of the class' instance. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ SceneClass* Overlay::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "Overlay", 1); m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); std::vector mapFiles; CaretMappableDataFile* selectedMapFile = NULL; //AString selectedMapUniqueID; int32_t selectedMapIndex; getSelectionData(mapFiles, selectedMapFile, //selectedMapUniqueID, selectedMapIndex); if ((selectedMapFile != NULL) && (selectedMapIndex >= 0)) { sceneClass->addPathName("selectedMapFileNameWithPath", selectedMapFile->getFileName()); sceneClass->addString("selectedMapFile", selectedMapFile->getFileNameNoPath()); sceneClass->addString("selectedMapName", selectedMapFile->getMapName(selectedMapIndex)); sceneClass->addInteger("selectedMapIndex", selectedMapIndex); } else { sceneClass->addPathName("selectedMapFileNameWithPath", ""); sceneClass->addString("selectedMapFile", ""); sceneClass->addString("selectedMapName", ""); sceneClass->addInteger("selectedMapIndex", -1); } return sceneClass; } /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass for the instance of a class that implements * this interface. May be NULL for some types of scenes. */ void Overlay::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } /* * Making a call to getSelectionData() to get the availble * map files */ std::vector mapFiles; CaretMappableDataFile* unusedSelectedMapFile = NULL; AString unusedSelectedMapUniqueID; int32_t unusedSelectedMapIndex; getSelectionData(mapFiles, unusedSelectedMapFile, //unusedSelectedMapUniqueID, unusedSelectedMapIndex); m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); /* * OverlayYokingGroup was replaced by MapYokingGroup. * If an overlay yoking group is found, convert it to * a map yoking group. */ const AString overlayYokingGroupName = sceneClass->getEnumeratedTypeValueAsString("m_yokingGroup", ""); if ( ! overlayYokingGroupName.isEmpty()) { bool valid = false; const MapYokingGroupEnum::Enum mapGroup = MapYokingGroupEnum::fromOverlayYokingGroupEnumName(overlayYokingGroupName, &valid); if (valid) { m_mapYokingGroup = mapGroup; } } const AString selectedMapFileNameWithPath = sceneClass->getPathNameValue("selectedMapFileNameWithPath"); const AString selectedMapFileName = sceneClass->getStringValue("selectedMapFile", ""); const AString selectedMapUniqueID = sceneClass->getStringValue("selectedMapUniqueID", ""); const AString selectedMapName = sceneClass->getStringValue("selectedMapName", ""); const int32_t selectedMapIndex = sceneClass->getIntegerValue("selectedMapIndex", -1); bool found = false; /* * Is used when the file is found but a map is not matched */ CaretMappableDataFile* matchedMapFile = NULL; /* * First try to find file by filename INCLUDING path and map by unique ID */ /* * Find map by unique ID, map index, and map file */ CaretMappableDataFile* foundUniqueIdMapFile = NULL; int32_t foundUniqueIdMapIndex= -1; CaretMappableDataFile* foundMapNameFile = NULL; int32_t foundMapNameIndex = -1; CaretMappableDataFile* foundMapIndexFile = NULL; int32_t foundMapIndex = -1; /* * Try to match files twice. First time by name with path, then * by name without path. */ for (int iTries = 0; iTries < 2; iTries++) { for (std::vector::iterator iter = mapFiles.begin(); iter != mapFiles.end(); iter++) { CaretMappableDataFile* mapFile = *iter; bool testIt = false; switch (iTries) { case 0: { const AString fileName = mapFile->getFileName(); if (fileName == selectedMapFileNameWithPath) { testIt = true; } } break; case 1: { const AString fileName = mapFile->getFileNameNoPath(); if (fileName == selectedMapFileName) { testIt = true; } } break; } if (testIt) { CaretMappableDataFile* mapFile = *iter; matchedMapFile = mapFile; if (foundUniqueIdMapIndex < 0) { const int uniqueIndex = mapFile->getMapIndexFromUniqueID(selectedMapUniqueID); if (uniqueIndex >= 0) { foundUniqueIdMapFile = mapFile; foundUniqueIdMapIndex = uniqueIndex; } } if (foundMapNameIndex < 0) { if ( ! selectedMapName.isEmpty()) { const int mapNameIndex = mapFile->getMapIndexFromName(selectedMapName); if (mapNameIndex >= 0) { foundMapNameFile = mapFile; foundMapNameIndex = mapNameIndex; } } } if (foundMapIndex < 0) { if (selectedMapIndex >= 0) { if (selectedMapIndex < mapFile->getNumberOfMaps()) { foundMapIndexFile = mapFile; foundMapIndex = selectedMapIndex; } } } } } } if (! found) { if (foundMapIndex >= 0) { if (foundMapIndexFile != NULL) { setSelectionData(foundMapIndexFile, foundMapIndex); found = true; } } } if (! found) { if (foundUniqueIdMapIndex >= 0) { if (foundUniqueIdMapFile != NULL) { setSelectionData(foundUniqueIdMapFile, foundUniqueIdMapIndex); found = true; } } } if (! found) { if (foundMapNameIndex >= 0) { if (foundMapNameFile != NULL) { setSelectionData(foundMapNameFile, foundMapNameIndex); found = true; } } } if (found == false) { /* * If not found by unique ID, try to find map by name */ if (selectedMapName.isEmpty() == false) { for (std::vector::iterator iter = mapFiles.begin(); iter != mapFiles.end(); iter++) { CaretMappableDataFile* mapFile = *iter; const AString fileName = mapFile->getFileNameNoPath(); if (fileName == selectedMapFileName) { CaretMappableDataFile* mapFile = *iter; matchedMapFile = mapFile; const int32_t mapIndex = mapFile->getMapIndexFromName(selectedMapName); if (mapIndex >= 0) { setSelectionData(mapFile, mapIndex); found = true; break; } } } } } /* * If file found but not matching map, use first map from the file. * This may occur when the map does not have a name. */ if (found == false) { if (matchedMapFile != NULL) { if (matchedMapFile->getNumberOfMaps() > 0) { setSelectionData(matchedMapFile, 0); } } } } workbench-1.1.1/src/Brain/Overlay.h000066400000000000000000000121121255417355300171170ustar00rootroot00000000000000#ifndef __OVERLAY__H_ #define __OVERLAY__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretObject.h" #include "DataFileTypeEnum.h" #include "EventListenerInterface.h" #include "MapYokingGroupEnum.h" #include "PlainTextStringBuilder.h" #include "SceneableInterface.h" #include "WholeBrainVoxelDrawingMode.h" #include "StructureEnum.h" namespace caret { class CaretMappableDataFile; class SceneClassAssistant; class Overlay : public CaretObject, public EventListenerInterface, public SceneableInterface { public: enum IncludeVolumeFiles { INCLUDE_VOLUME_FILES_YES, INCLUDE_VOLUME_FILES_NO }; Overlay(const std::vector& includeSurfaceStructures, const Overlay::IncludeVolumeFiles includeVolumeFiles); virtual ~Overlay(); virtual void receiveEvent(Event* event); float getOpacity() const; void setOpacity(const float opacity); AString getName() const; void setOverlayNumber(const int32_t overlayIndex); virtual AString toString() const; virtual void getDescriptionOfContent(PlainTextStringBuilder& descriptionOut) const; bool isEnabled() const; void setEnabled(const bool enabled); WholeBrainVoxelDrawingMode::Enum getWholeBrainVoxelDrawingMode() const; void setWholeBrainVoxelDrawingMode(const WholeBrainVoxelDrawingMode::Enum wholeBrainVoxelDrawingMode); void copyData(const Overlay* overlay); void swapData(Overlay* overlay); void getSelectionData(std::vector& mapFilesOut, CaretMappableDataFile* &selectedMapFileOut, int32_t& selectedMapIndexOut); void getSelectionData(CaretMappableDataFile* &selectedMapFileOut, int32_t& selectedMapIndexOut); void setSelectionData(CaretMappableDataFile* selectedMapFile, const int32_t selectedMapIndex); bool isPaletteDisplayEnabled() const; void setPaletteDisplayEnabled(const bool enabled); MapYokingGroupEnum::Enum getMapYokingGroup() const; void setMapYokingGroup(const MapYokingGroupEnum::Enum mapYokingGroup); virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private: Overlay(const Overlay&); Overlay& operator=(const Overlay&); /** Surface structures of data files displayed in this overlay */ const std::vector m_includeSurfaceStructures; /** Include volume files in this overlay */ const IncludeVolumeFiles m_includeVolumeFiles; /** Name of overlay (DO NOT COPY)*/ AString m_name; /** Index of this overlay (DO NOT COPY)*/ int32_t m_overlayIndex; /** opacity for overlay */ float m_opacity; /** enabled status */ mutable bool m_enabled; /** map yoking group */ MapYokingGroupEnum::Enum m_mapYokingGroup; /** available mappable files */ //std::vector m_mapFiles; /** selected mappable file */ CaretMappableDataFile* m_selectedMapFile; /** selected map index */ int32_t m_selectedMapIndex; /** selected data file map unique id */ //AString m_selectedMapUniqueID; /** Display palette in graphics window */ bool m_paletteDisplayedFlag; /** Voxel drawing mode in Whole Brain View */ WholeBrainVoxelDrawingMode::Enum m_wholeBrainVoxelDrawingMode; /** helps with scene save/restore */ SceneClassAssistant* m_sceneAssistant; }; #ifdef __OVERLAY_DECLARE__ #endif // __OVERLAY_DECLARE__ } // namespace #endif //__OVERLAY__H_ workbench-1.1.1/src/Brain/OverlaySet.cxx000066400000000000000000001340071255417355300201560ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #define __OVERLAY_SET_DECLARE__ #include "OverlaySet.h" #undef __OVERLAY_SET_DECLARE__ #include "BrainStructure.h" #include "CaretAssert.h" #include "CaretLogger.h" #include "CaretMappableDataFile.h" #include "CiftiBrainordinateLabelFile.h" #include "CiftiBrainordinateScalarFile.h" #include "EventCaretMappableDataFilesGet.h" #include "EventManager.h" #include "EventMapYokingSelectMap.h" #include "EventMapYokingValidation.h" #include "LabelFile.h" #include "MetricFile.h" #include "ModelSurfaceMontage.h" #include "ModelVolume.h" #include "ModelWholeBrain.h" #include "Overlay.h" #include "PlainTextStringBuilder.h" #include "Scene.h" #include "SceneClass.h" #include "SceneClassArray.h" #include "SceneClassAssistant.h" #include "Surface.h" #include "VolumeFile.h" using namespace caret; /** * \class OverlaySet * \brief Contains a set of overlay assignments * * The maximum number of overlays is fixed. The number * of overlays presented to the user varies and is * controlled using the ToolBox in a Browser Window. * * The primary overlay is always the overlay at index zero. * The underlay is the overlay at (numberOfDisplayedOverlays - 1). * When models are colored, the overlays are assigned * starting with the underlay and concluding with the primary * overlay. */ /** * \class OverlaySet * \brief Contains a set of overlay assignments * * The maximum number of overlays is fixed. The number * of overlays presented to the user varies and is * controlled using the ToolBox in a Browser Window. * * The primary overlay is always the overlay at index zero. * The underlay is the overlay at (numberOfDisplayedOverlays - 1). * When models are colored, the overlays are assigned * starting with the underlay and concluding with the primary * overlay. */ /** * Constructor for the given surface structures, surface types, and volumes. * * @param name * Name for this overlay set * @param tabIndex * Index of tab for this overlay set. * @param includeSurfaceStructures * Surface structures for data files displayed in this overlay set. * @param includeVolumeFiles * Surface structures for data files displayed in this overlay set. */ OverlaySet::OverlaySet(const AString& name, const int32_t tabIndex, const std::vector& includeSurfaceStructures, const Overlay::IncludeVolumeFiles includeVolumeFiles) : CaretObject(), m_name(name), m_tabIndex(tabIndex), m_includeSurfaceStructures(includeSurfaceStructures), m_includeVolumeFiles(includeVolumeFiles) { m_numberOfDisplayedOverlays = BrainConstants::MINIMUM_NUMBER_OF_OVERLAYS; m_sceneAssistant = new SceneClassAssistant(); m_sceneAssistant->add("m_numberOfDisplayedOverlays", &m_numberOfDisplayedOverlays); for (int i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_OVERLAYS; i++) { m_overlays[i] = new Overlay(includeSurfaceStructures, includeVolumeFiles); } EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_MAP_YOKING_VALIDATION); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_MAP_YOKING_SELECT_MAP); } /** * Destructor. */ OverlaySet::~OverlaySet() { EventManager::get()->removeAllEventsFromListener(this); for (int i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_OVERLAYS; i++) { delete m_overlays[i]; } delete m_sceneAssistant; } /** * Copy the given overlay set to this overlay set. * @param overlaySet * Overlay set that is copied. */ void OverlaySet::copyOverlaySet(const OverlaySet* overlaySet) { for (int i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_OVERLAYS; i++) { m_overlays[i]->copyData(overlaySet->getOverlay(i)); } m_numberOfDisplayedOverlays = overlaySet->m_numberOfDisplayedOverlays; } /** * @return Returns the top-most overlay regardless of its enabled status. */ Overlay* OverlaySet::getPrimaryOverlay() { return m_overlays[0]; } /** * @return Returns the underlay which is the lowest * displayed overlay. */ Overlay* OverlaySet::getUnderlay() { return m_overlays[getNumberOfDisplayedOverlays() - 1]; } /* * Get the bottom-most overlay that is a volume file for the given * browser tab. * @param browserTabContent * Content of browser tab. * @return Returns the bottom-most overlay that is set a a volume file. * Will return NULL if no, enabled overlays are set to a volume file. */ VolumeMappableInterface* OverlaySet::getUnderlayVolume() { VolumeMappableInterface* vf = NULL; for (int32_t i = (getNumberOfDisplayedOverlays() - 1); i >= 0; i--) { if (m_overlays[i]->isEnabled()) { CaretMappableDataFile* mapFile = NULL; int32_t mapIndex; m_overlays[i]->getSelectionData(mapFile, mapIndex); if (mapFile != NULL) { if (mapFile->isVolumeMappable()) { vf = dynamic_cast(mapFile); if (vf != NULL) { break; } } } } } return vf; } /** * If NO overlay (any overlay) is set to a volume, set the underlay to the first * volume that it finds. * @return Returns the volume file that was selected or NULL if no * volume file was found. */ VolumeMappableInterface* OverlaySet::setUnderlayToVolume() { VolumeMappableInterface * vf = getUnderlayVolume(); if (vf == NULL) { const int32_t overlayIndex = getNumberOfDisplayedOverlays() - 1; if (overlayIndex >= 0) { std::vector mapFiles; CaretMappableDataFile* mapFile; //AString mapUniqueID; int32_t mapIndex; m_overlays[overlayIndex]->getSelectionData(mapFiles, mapFile, //mapUniqueID, mapIndex); const int32_t numMapFiles = static_cast(mapFiles.size()); for (int32_t i = 0; i < numMapFiles; i++) { if (mapFiles[i]->isVolumeMappable()) { vf = dynamic_cast(mapFiles[i]); if (vf != NULL) { CaretMappableDataFile* cmdf = dynamic_cast(vf); CaretAssert(cmdf); m_overlays[overlayIndex]->setSelectionData(cmdf, 0); break; } } } } } return vf; } /** * Get the overlay at the specified index. * @param overlayNumber * Index of the overlay. * @return Overlay at the given index. */ const Overlay* OverlaySet::getOverlay(const int32_t overlayNumber) const { CaretAssertArrayIndex(m_overlays, BrainConstants::MAXIMUM_NUMBER_OF_OVERLAYS, overlayNumber); return m_overlays[overlayNumber]; } /** * Get the overlay at the specified index. * @param overlayNumber * Index of the overlay. * @return Overlay at the given index. */ Overlay* OverlaySet::getOverlay(const int32_t overlayNumber) { CaretAssertArrayIndex(m_overlays, BrainConstants::MAXIMUM_NUMBER_OF_OVERLAYS, overlayNumber); return m_overlays[overlayNumber]; } /** * Get a description of this object's content. * @return String describing this object's content. */ AString OverlaySet::toString() const { PlainTextStringBuilder tb; getDescriptionOfContent(tb); return tb.getText(); } /** * Get a text description of the window's content. * * @param descriptionOut * Description of the window's content. */ void OverlaySet::getDescriptionOfContent(PlainTextStringBuilder& descriptionOut) const { descriptionOut.addLine("Overlay Set"); const int numOverlays = getNumberOfDisplayedOverlays(); for (int32_t i = 0; i < numOverlays; i++) { if (getOverlay(i)->isEnabled()) { descriptionOut.pushIndentation(); descriptionOut.addLine("Overlay " + AString::number(i + 1) + ": "); descriptionOut.pushIndentation(); getOverlay(i)->getDescriptionOfContent(descriptionOut); descriptionOut.popIndentation(); descriptionOut.popIndentation(); } } } /** * Add a displayed overlay. If the maximum * number of surface overlays is reached, * this method has no effect. */ void OverlaySet::addDisplayedOverlay() { m_numberOfDisplayedOverlays++; if (m_numberOfDisplayedOverlays > BrainConstants::MAXIMUM_NUMBER_OF_OVERLAYS) { m_numberOfDisplayedOverlays = BrainConstants::MAXIMUM_NUMBER_OF_OVERLAYS; } } /** * @return Returns the number of displayed overlays. */ int32_t OverlaySet::getNumberOfDisplayedOverlays() const { return m_numberOfDisplayedOverlays; } /** * Sets the number of displayed overlays. * @param numberOfDisplayedOverlays * Number of overlays for display. */ void OverlaySet::setNumberOfDisplayedOverlays(const int32_t numberOfDisplayedOverlays) { const int32_t oldNumberOfDisplayedOverlays = m_numberOfDisplayedOverlays; m_numberOfDisplayedOverlays = numberOfDisplayedOverlays; if (m_numberOfDisplayedOverlays < BrainConstants::MINIMUM_NUMBER_OF_OVERLAYS) { m_numberOfDisplayedOverlays = BrainConstants::MINIMUM_NUMBER_OF_OVERLAYS; } if (m_numberOfDisplayedOverlays > BrainConstants::MAXIMUM_NUMBER_OF_OVERLAYS) { m_numberOfDisplayedOverlays = BrainConstants::MAXIMUM_NUMBER_OF_OVERLAYS; } /* * If one overlay added (probably through GUI), * shift all overlays down one position so that * new overlay appears at the top */ const int32_t numberOfOverlaysAdded = m_numberOfDisplayedOverlays - oldNumberOfDisplayedOverlays; if (numberOfOverlaysAdded == 1) { for (int32_t i = (m_numberOfDisplayedOverlays - 1); i >= 0; i--) { moveDisplayedOverlayDown(i); } } } /** * Insert an overlay below this overlay * @param overlayIndex * Index of overlay for which an overlay is added below */ void OverlaySet::insertOverlayAbove(const int32_t overlayIndex) { if (m_numberOfDisplayedOverlays < BrainConstants::MAXIMUM_NUMBER_OF_OVERLAYS) { m_numberOfDisplayedOverlays++; for (int32_t i = (m_numberOfDisplayedOverlays - 2); i >= overlayIndex; i--) { moveDisplayedOverlayDown(i); } } } /** * Insert an overlay above this overlay * @param overlayIndex * Index of overlay for which an overlay is added above */ void OverlaySet::insertOverlayBelow(const int32_t overlayIndex) { if (m_numberOfDisplayedOverlays < BrainConstants::MAXIMUM_NUMBER_OF_OVERLAYS) { m_numberOfDisplayedOverlays++; for (int32_t i = (m_numberOfDisplayedOverlays - 2); i > overlayIndex; i--) { moveDisplayedOverlayDown(i); } } } /** * Remove a displayed overlay. This method will have * no effect if the minimum number of overlays are * displayed * * @param overlayIndex * Index of overlay for removal from display. */ void OverlaySet::removeDisplayedOverlay(const int32_t overlayIndex) { CaretAssertArrayIndex(m_overlays, BrainConstants::MAXIMUM_NUMBER_OF_OVERLAYS, overlayIndex); m_overlays[overlayIndex]->setMapYokingGroup(MapYokingGroupEnum::MAP_YOKING_GROUP_OFF); if (m_numberOfDisplayedOverlays > BrainConstants::MINIMUM_NUMBER_OF_OVERLAYS) { m_numberOfDisplayedOverlays--; for (int32_t i = overlayIndex; i < m_numberOfDisplayedOverlays; i++) { m_overlays[i]->copyData(m_overlays[i+1]); } } } /** * Move the overlay at the given index up one level * (swap it with overlayIndex - 1). This method will * have no effect if the overlay is the top-most overlay. * * @param overlayIndex * Index of overlay that is to be moved up. */ void OverlaySet::moveDisplayedOverlayUp(const int32_t overlayIndex) { if (overlayIndex > 0) { m_overlays[overlayIndex]->swapData(m_overlays[overlayIndex - 1]); } } /** * Move the overlay at the given index down one level * (swap it with overlayIndex + 1). This method will * have no effect if the overlay is the bottom-most overlay. * * @param overlayIndex * Index of overlay that is to be moved down. */ void OverlaySet::moveDisplayedOverlayDown(const int32_t overlayIndex) { const int32_t nextOverlayIndex = overlayIndex + 1; if (nextOverlayIndex < m_numberOfDisplayedOverlays) { m_overlays[overlayIndex]->swapData(m_overlays[nextOverlayIndex]); } } /** * Match the desired names to maps (or file if no maps match) and optionally * structure to files/maps and return the matches. * * Note: If there are NO match names, all files are matched using the first * map in each file. * * @param matchedFilesOut * Output to which matched files are APPENDED. * @param matchedFileIndicesOut * Output to which matched file indices are APPENDED. * @param matchToStructures * If not empty, only include files that map to these structures. If * matchToVolumeData is true, this parameter is ignored. If this is (empty * OR All) AND matchToVolumeData is false, all structures match. * @param dataFileType * Data file type of desired files. * @param matchToVolumeData * Include only files that map to volume data. If true, matchToStructures * is ignored. * @param matchToNamesRegularExpressionText * Text for regular expression used for name matching. * @param matchToNamesRegularExpressionResult * Status of regular expression matching for inclusion of file. * @param matchOneFilePerStructure * If true, limit matched files so there is no more than one file * for each structure. * @param * True if matching files were found, else false. */ bool OverlaySet::findFilesWithMapNamed(std::vector& matchedFilesOut, std::vector& matchedFileIndicesOut, const std::vector& matchToStructures, const DataFileTypeEnum::Enum dataFileType, const bool matchToVolumeData, const AString& matchToNamesRegularExpressionText, const bool matchToNamesRegularExpressionResult, const bool matchOneFilePerStructure) { std::vector matchedFiles; std::vector matchedFileIndices; /* * Aggregate matching names and make them lower case */ QRegExp regularExpression; if (matchToNamesRegularExpressionText.isEmpty() == false) { regularExpression = QRegExp(matchToNamesRegularExpressionText); } /* * Get files matching data type */ EventCaretMappableDataFilesGet mapFileGetEvent(dataFileType); EventManager::get()->sendEvent(mapFileGetEvent.getPointer()); std::vector matchToMapFiles; mapFileGetEvent.getAllFiles(matchToMapFiles); const int32_t numberOfMatchFiles = static_cast(matchToMapFiles.size()); if (numberOfMatchFiles <= 0) { return false; } /* * Determine which files should be tested by examing * structure or if volume */ std::vector testMapFiles; for (int32_t iFile = 0; iFile < numberOfMatchFiles; iFile++) { bool fileMatchFlag = false; CaretMappableDataFile* mapFile = matchToMapFiles[iFile]; /* * Volume mappable files only? */ if (matchToVolumeData) { if (mapFile->isVolumeMappable()) { fileMatchFlag = true; } } /* * Test structures? */ if (matchToStructures.empty() == false) { const StructureEnum::Enum mapFileStructure = mapFile->getStructure(); /* * File maps to ALL structures? */ if (mapFileStructure == StructureEnum::ALL) { fileMatchFlag = true; } else { /* * Specific structutures */ if (std::find(matchToStructures.begin(), matchToStructures.end(), mapFileStructure) != matchToStructures.end()) { fileMatchFlag = true; } } } if (fileMatchFlag) { if (mapFile->getNumberOfMaps() > 0) { testMapFiles.push_back(mapFile); } } } /* * No files to test? */ const int32_t numTestFiles = static_cast(testMapFiles.size()); if (numTestFiles <= 0) { return false; } std::set matchedStructures; /* * If there are names to match */ if (matchToNamesRegularExpressionText.isEmpty() == false) { /* * First preference is matching MAP name */ for (int32_t iFile = 0; iFile < numTestFiles; iFile++) { CaretMappableDataFile* mapFile = testMapFiles[iFile]; const StructureEnum::Enum mapFileStructure = mapFile->getStructure(); if (matchOneFilePerStructure) { if (matchedStructures.find(mapFileStructure) != matchedStructures.end()) { continue; } } /* * If NOT matching, exclude files whose name matches */ if (matchToNamesRegularExpressionResult == false) { const AString fileName = mapFile->getFileNameNoPath().toLower(); const bool fileNameMatch = (regularExpression.indexIn(fileName) >= 0); if (fileNameMatch) { continue; } } const int32_t numMaps = mapFile->getNumberOfMaps(); for (int32_t iMap = 0; iMap < numMaps; iMap++) { const AString mapName = mapFile->getMapName(iMap).toLower(); const bool match = (regularExpression.indexIn(mapName) >= 0); if (match == matchToNamesRegularExpressionResult) { matchedFiles.push_back(mapFile); matchedFileIndices.push_back(iMap); matchedStructures.insert(mapFileStructure); break; } } } /* * Find matching FILE name if NO map matches */ if (matchedFiles.empty()) { for (int32_t iFile = 0; iFile < numTestFiles; iFile++) { CaretMappableDataFile* mapFile = testMapFiles[iFile]; const StructureEnum::Enum mapFileStructure = mapFile->getStructure(); if (matchOneFilePerStructure) { if (matchedStructures.find(mapFileStructure) != matchedStructures.end()) { continue; } } const AString fileName = mapFile->getFileNameNoPath().toLower(); const bool match = (regularExpression.indexIn(fileName) >= 0); if (match == matchToNamesRegularExpressionResult) { matchedFiles.push_back(mapFile); matchedFileIndices.push_back(0); matchedStructures.insert(mapFileStructure); } } } } else { /* * No names to match so just match to first map in each file */ for (int32_t iFile = 0; iFile < numTestFiles; iFile++) { CaretMappableDataFile* mapFile = testMapFiles[iFile]; const StructureEnum::Enum mapFileStructure = mapFile->getStructure(); if (matchOneFilePerStructure) { if (matchedStructures.find(mapFileStructure) != matchedStructures.end()) { continue; } } matchedFiles.push_back(mapFile); matchedFileIndices.push_back(0); matchedStructures.insert(mapFileStructure); } } CaretAssert(matchedFiles.size() == matchedFileIndices.size()); const bool filesFound = (matchedFiles.empty() == false); /* * APPEND to output, do not replace */ matchedFilesOut.insert(matchedFilesOut.end(), matchedFiles.begin(), matchedFiles.end()); matchedFileIndicesOut.insert(matchedFileIndicesOut.end(), matchedFileIndices.begin(), matchedFileIndices.end()); return filesFound; } /** * Find underlay files. * * @param matchToStructures * Structures to include. * @param includeVolumeFiles * Include volume files. * @param filesOut * Output containing files that were selected. * @param mapIndicesOut * Output containing maps indices in files that were selected. */ void OverlaySet::findUnderlayFiles( const std::vector& matchToStructures, const bool includeVolumeFiles, std::vector& filesOut, std::vector& mapIndicesOut) { /* * First, try to find CIFTI shape files */ if (findFilesWithMapNamed(filesOut, mapIndicesOut, matchToStructures, DataFileTypeEnum::CONNECTIVITY_DENSE_SCALAR, false, s_shapeMatchRegularExpressionText, true, true) == false) { /* * Second, try to find METRIC shape files */ findFilesWithMapNamed(filesOut, mapIndicesOut, matchToStructures, DataFileTypeEnum::METRIC, false, s_shapeMatchRegularExpressionText, true, true); } if (includeVolumeFiles) { std::vector volumeFiles = getVolumeFiles(); const int32_t numVolumes = static_cast(volumeFiles.size()); if (numVolumes > 0) { bool foundAnatomyVolume = false; for (int32_t i = 0; i < numVolumes; i++) { VolumeFile* vf = volumeFiles[i]; if (vf->getType() == SubvolumeAttributes::ANATOMY) { if (vf->getNumberOfMaps() > 0) { filesOut.push_back(vf); mapIndicesOut.push_back(0); foundAnatomyVolume = true; break; } } } if (foundAnatomyVolume == false) { for (int32_t i = 0; i < numVolumes; i++) { VolumeFile* vf = volumeFiles[i]; bool testIt = true; if (vf->getType() == SubvolumeAttributes::LABEL) { testIt = false; } if (testIt) { if (vf->getNumberOfMaps() > 0) { PaletteColorMapping* pcm = vf->getMapPaletteColorMapping(0); if (pcm != NULL) { const AString paletteName = pcm->getSelectedPaletteName(); if (paletteName.contains("gray") || paletteName.contains("grey")) { filesOut.push_back(vf); mapIndicesOut.push_back(0); foundAnatomyVolume = true; break; } } } } } } } } CaretAssert(filesOut.size() == mapIndicesOut.size()); } /** * @return All volume files. */ std::vector OverlaySet::getVolumeFiles() const { std::vector volumeFiles; EventCaretMappableDataFilesGet mapFileGetEvent(DataFileTypeEnum::VOLUME); EventManager::get()->sendEvent(mapFileGetEvent.getPointer()); std::vector matchToMapFiles; mapFileGetEvent.getAllFiles(matchToMapFiles); for (std::vector::iterator iter = matchToMapFiles.begin(); iter != matchToMapFiles.end(); iter++) { VolumeFile* vf = dynamic_cast(*iter); CaretAssert(vf); volumeFiles.push_back(vf); } return volumeFiles; } /** * Find middle layer files. * * @param matchToStructures * Structures to include. * @param includeVolumeFiles * Include volume files. * @param filesOut * Output containing files that were selected. * @param mapIndicesOut * Output containing maps indices in files that were selected. */ void OverlaySet::findMiddleLayerFiles(const std::vector& matchToStructures, const bool includeVolumeFiles, std::vector& filesOut, std::vector& mapIndicesOut) { std::vector matchToNames; /* * First, try to find CIFTI scalar files with myelin */ if (findFilesWithMapNamed(filesOut, mapIndicesOut, matchToStructures, DataFileTypeEnum::CONNECTIVITY_DENSE_SCALAR, includeVolumeFiles, s_myelinMatchRegularExpressionText, true, false) == false) { /* * Second, try to find METRIC files with neither shape nor myelin */ findFilesWithMapNamed(filesOut, mapIndicesOut, matchToStructures, DataFileTypeEnum::METRIC, includeVolumeFiles, s_myelinMatchRegularExpressionText, true, false); } /* * Second, try to find CIFTI scalar files with neither shape nor myelin */ if (findFilesWithMapNamed(filesOut, mapIndicesOut, matchToStructures, DataFileTypeEnum::CONNECTIVITY_DENSE_SCALAR, includeVolumeFiles, s_shapeMyelinMatchRegularExpressionText, false, false) == false) { /* * Second, try to find METRIC files with neither shape nor myelin */ findFilesWithMapNamed(filesOut, mapIndicesOut, matchToStructures, DataFileTypeEnum::METRIC, includeVolumeFiles, s_shapeMyelinMatchRegularExpressionText, false, false); } if (includeVolumeFiles) { std::vector volumeFiles = getVolumeFiles(); const int32_t numVolumes = static_cast(volumeFiles.size()); for (int32_t i = 0; i < numVolumes; i++) { VolumeFile* vf = volumeFiles[i]; if ((vf->getType() == SubvolumeAttributes::FUNCTIONAL)) { if (vf->getNumberOfMaps() > 0) { filesOut.push_back(vf); mapIndicesOut.push_back(0); break; } } } } CaretAssert(filesOut.size() == mapIndicesOut.size()); } /** * Find overlay files. * * @param matchToStructures * Structures to include. * @param includeVolumeFiles * Include volume files. * @param filesOut * Output containing files that were selected. * @param mapIndicesOut * Output containing maps indices in files that were selected. */ void OverlaySet::findOverlayFiles(const std::vector& matchToStructures, const bool includeVolumeFiles, std::vector& filesOut, std::vector& mapIndicesOut) { /* * First, try to find CIFTI LABEL files */ if (findFilesWithMapNamed(filesOut, mapIndicesOut, matchToStructures, DataFileTypeEnum::CONNECTIVITY_DENSE_LABEL, includeVolumeFiles, "", true, true) == false) { /* * Second, try to find LABEL files */ findFilesWithMapNamed(filesOut, mapIndicesOut, matchToStructures, DataFileTypeEnum::LABEL, includeVolumeFiles, "", true, true); } if (includeVolumeFiles) { std::vector volumeFiles = getVolumeFiles(); const int32_t numVolumes = static_cast(volumeFiles.size()); for (int32_t i = 0; i < numVolumes; i++) { VolumeFile* vf = volumeFiles[i]; if (vf->getType() == SubvolumeAttributes::LABEL) { if (vf->getNumberOfMaps() > 0) { filesOut.push_back(vf); mapIndicesOut.push_back(0); break; } } } } CaretAssert(filesOut.size() == mapIndicesOut.size()); } /** * Initialize the overlays. */ void OverlaySet::initializeOverlays() { bool isMatchToVolumeUnderlay = false; bool isMatchToVolumeOverlays = false; switch (m_includeVolumeFiles) { case Overlay::INCLUDE_VOLUME_FILES_NO: break; case Overlay::INCLUDE_VOLUME_FILES_YES: /* * If no surface structures, then it must be volume slice view * so allow volumes to be in the overlays. */ if (m_includeSurfaceStructures.empty()) { isMatchToVolumeOverlays = true; } isMatchToVolumeUnderlay = true; break; } /* * Underlays consist of anatomical type data */ std::vector underlayMapFiles; std::vector underlayMapIndices; findUnderlayFiles(m_includeSurfaceStructures, isMatchToVolumeUnderlay, underlayMapFiles, underlayMapIndices); /* * Middle layers are Cifti labels or Gifti Labels * that do not contain shape data */ std::vector middleLayerMapFiles; std::vector middleLayerMapIndices; findMiddleLayerFiles(m_includeSurfaceStructures, isMatchToVolumeOverlays, middleLayerMapFiles, middleLayerMapIndices); /* * Overlays consist of Cifti scalars or Gifti Metric */ std::vector overlayMapFiles; std::vector overlayMapIndices; findOverlayFiles(m_includeSurfaceStructures, isMatchToVolumeOverlays, overlayMapFiles, overlayMapIndices); const int32_t numberOfUnderlayFiles = static_cast(underlayMapFiles.size()); /* * Number of overlay that are displayed. */ const int32_t numberOfDisplayedOverlays = getNumberOfDisplayedOverlays(); /* * Track overlay that were initialized */ std::vector overlayInitializedFlag(numberOfDisplayedOverlays, false); /* * Put in the shape files at the bottom * Note that highest overlay index is bottom */ int32_t overlayIndexForUnderlay = (numberOfDisplayedOverlays - 1); for (int32_t underlayFileIndex = 0; underlayFileIndex < numberOfUnderlayFiles; underlayFileIndex++) { if (overlayIndexForUnderlay >= 0) { Overlay* overlay = getOverlay(overlayIndexForUnderlay); overlay->setSelectionData(underlayMapFiles[underlayFileIndex], underlayMapIndices[underlayFileIndex]); overlayInitializedFlag[overlayIndexForUnderlay] = true; overlayIndexForUnderlay--; } else { break; } } /* * Combine overlay and middle layer files */ std::vector upperLayerFiles; std::vector upperLayerIndices; upperLayerFiles.insert(upperLayerFiles.end(), overlayMapFiles.begin(), overlayMapFiles.end()); upperLayerIndices.insert(upperLayerIndices.end(), overlayMapIndices.begin(), overlayMapIndices.end()); upperLayerFiles.insert(upperLayerFiles.end(), middleLayerMapFiles.begin(), middleLayerMapFiles.end()); upperLayerIndices.insert(upperLayerIndices.end(), middleLayerMapIndices.begin(), middleLayerMapIndices.end()); CaretAssert(upperLayerFiles.size() == upperLayerIndices.size()); const int32_t numberOfUpperFiles = static_cast(upperLayerFiles.size()); /* * Put in overlay and middle layer files */ for (int32_t upperFileIndex = 0; upperFileIndex < numberOfUpperFiles; upperFileIndex++) { /* * Find available overlay */ int32_t upperLayerOverlayIndex = -1; for (int32_t overlayIndex = 0; overlayIndex < numberOfDisplayedOverlays; overlayIndex++) { if (overlayInitializedFlag[overlayIndex] == false) { upperLayerOverlayIndex = overlayIndex; break; } } if (upperLayerOverlayIndex >= 0) { Overlay* upperLayerOverlay = getOverlay(upperLayerOverlayIndex); upperLayerOverlay->setSelectionData(upperLayerFiles[upperFileIndex], upperLayerIndices[upperFileIndex]); overlayInitializedFlag[upperLayerOverlayIndex] = true; } else { break; } } /* * Disable overlays that were not initialized */ for (int32_t i = 0; i < numberOfDisplayedOverlays; i++) { CaretAssertVectorIndex(overlayInitializedFlag, i); getOverlay(i)->setEnabled(overlayInitializedFlag[i]); } } /** * Get any label files that are selected and applicable for the given surface. * @param surface * Surface for which label files are desired. * @param labelFilesOut * Label files that are applicable to the given surface. * @param labelMapIndicesOut * Selected map indices in the output label files. */ void OverlaySet::getLabelFilesForSurface(const Surface* surface, std::vector& labelFilesOut, std::vector& labelMapIndicesOut) { CaretAssert(surface); labelFilesOut.clear(); labelMapIndicesOut.clear(); const int32_t numberOfOverlays = getNumberOfDisplayedOverlays(); for (int32_t i = 0; i < numberOfOverlays; i++) { Overlay* overlay = getOverlay(i); if (overlay->isEnabled()) { CaretMappableDataFile* mapFile; int32_t mapIndex; overlay->getSelectionData(mapFile, mapIndex); if (mapFile != NULL) { if (mapFile->getDataFileType() == DataFileTypeEnum::LABEL) { if (mapFile->getStructure() == surface->getStructure()) { LabelFile* labelFile = dynamic_cast(mapFile); labelFilesOut.push_back(labelFile); labelMapIndicesOut.push_back(mapIndex); } } } } } } /** * For the given caret mappable data file, find overlays in which the * file is selected and return the indices of the selected maps. * * @param caretMappableDataFile * The caret mappable data file. * @param isLimitToEnabledOverlays * If true, only include map indices for overlay that are enabled. * Otherwise, include map indices for all overlays. * @param selectedMapIndicesOut * Output containing map indices for the given caret mappable data files * that are selected as overlays in this overlay set. */ void OverlaySet::getSelectedMapIndicesForFile(const CaretMappableDataFile* caretMappableDataFile, const bool isLimitToEnabledOverlays, std::vector& selectedMapIndicesOut) const { selectedMapIndicesOut.clear(); /* * Put indices in a set to avoid duplicates and keep them sorted. */ std::set mapIndicesSet; const int32_t numberOfOverlays = getNumberOfDisplayedOverlays(); for (int32_t i = 0; i < numberOfOverlays; i++) { Overlay* overlay = const_cast(getOverlay(i)); bool checkIt = true; if (isLimitToEnabledOverlays) { if (overlay->isEnabled() == false) { checkIt = false; } } if (checkIt) { CaretMappableDataFile* mapFile; int32_t mapIndex; overlay->getSelectionData(mapFile, mapIndex); if (mapFile == caretMappableDataFile) { mapIndicesSet.insert(mapIndex); } } } selectedMapIndicesOut.insert(selectedMapIndicesOut.end(), mapIndicesSet.begin(), mapIndicesSet.end()); } /** * Reset the yoking status of all overlays to off. */ void OverlaySet::resetOverlayYokingToOff() { for (int i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_OVERLAYS; i++) { m_overlays[i]->setMapYokingGroup(MapYokingGroupEnum::MAP_YOKING_GROUP_OFF); } } /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @param instanceName * Name of the class' instance. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ SceneClass* OverlaySet::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "OverlaySet", 1); m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); // const int32_t numOverlaysToSave = BrainConstants::MAXIMUM_NUMBER_OF_OVERLAYS; const int32_t numOverlaysToSave = getNumberOfDisplayedOverlays(); std::vector overlayClassVector; for (int i = 0; i < numOverlaysToSave; i++) { overlayClassVector.push_back(m_overlays[i]->saveToScene(sceneAttributes, "m_overlays")); } SceneClassArray* overlayClassArray = new SceneClassArray("m_overlays", overlayClassVector); sceneClass->addChild(overlayClassArray); return sceneClass; } /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass for the instance of a class that implements * this interface. May be NULL for some types of scenes. */ void OverlaySet::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); const SceneClassArray* overlayClassArray = sceneClass->getClassArray("m_overlays"); if (overlayClassArray != NULL) { const int32_t numOverlays = std::min(overlayClassArray->getNumberOfArrayElements(), (int32_t)BrainConstants::MAXIMUM_NUMBER_OF_OVERLAYS); for (int32_t i = 0; i < numOverlays; i++) { m_overlays[i]->restoreFromScene(sceneAttributes, overlayClassArray->getClassAtIndex(i)); } } } /** * Receive an event. * * @param event * An event for which this instance is listening. */ void OverlaySet::receiveEvent(Event* event) { if (event->getEventType() == EventTypeEnum::EVENT_MAP_YOKING_VALIDATION) { /* * The events intended for overlays are received here so that * only DISPLAYED overlays are updated. */ EventMapYokingValidation* mapYokeEvent = dynamic_cast(event); CaretAssert(mapYokeEvent); const MapYokingGroupEnum::Enum requestedYokingGroup = mapYokeEvent->getMapYokingGroup(); if (requestedYokingGroup != MapYokingGroupEnum::MAP_YOKING_GROUP_OFF) { /* * Find all overlays with the requested yoking */ const int32_t overlayCount = getNumberOfDisplayedOverlays(); for (int32_t j = 0; j < overlayCount; j++) { Overlay* overlay = getOverlay(j); CaretMappableDataFile* mapFile = NULL; int32_t mapIndex = -1; overlay->getSelectionData(mapFile, mapIndex); if (mapFile != NULL) { mapYokeEvent->addMapYokedFile(mapFile, overlay->getMapYokingGroup(), m_tabIndex); } } } mapYokeEvent->setEventProcessed(); } else if (event->getEventType() == EventTypeEnum::EVENT_MAP_YOKING_SELECT_MAP) { /* * The events intended for overlays are received here so that * only DISPLAYED overlays are updated. */ EventMapYokingSelectMap* selectMapEvent = dynamic_cast(event); CaretAssert(selectMapEvent); const MapYokingGroupEnum::Enum eventYokingGroup = selectMapEvent->getMapYokingGroup(); if (eventYokingGroup != MapYokingGroupEnum::MAP_YOKING_GROUP_OFF) { const int32_t yokingGroupMapIndex = MapYokingGroupEnum::getSelectedMapIndex(eventYokingGroup); const bool yokingGroupSelectedStatus = MapYokingGroupEnum::isEnabled(eventYokingGroup); const CaretMappableDataFile* eventMapFile = selectMapEvent->getCaretMappableDataFile(); /* * Find all overlays with the requested yoking */ const int32_t overlayCount = getNumberOfDisplayedOverlays(); for (int32_t j = 0; j < overlayCount; j++) { Overlay* overlay = getOverlay(j); if (overlay->getMapYokingGroup() == selectMapEvent->getMapYokingGroup()) { CaretMappableDataFile* mapFile = NULL; int32_t mapIndex = -1; overlay->getSelectionData(mapFile, mapIndex); if (mapFile != NULL) { if (yokingGroupMapIndex < mapFile->getNumberOfMaps()) { overlay->setSelectionData(mapFile, yokingGroupMapIndex); } if (mapFile == eventMapFile) { overlay->setEnabled(yokingGroupSelectedStatus); } } } } selectMapEvent->setEventProcessed(); } // const MapYokingGroupEnum::Enum mapYokingGroup = selectMapEvent->get } } workbench-1.1.1/src/Brain/OverlaySet.h000066400000000000000000000155311255417355300176030ustar00rootroot00000000000000#ifndef __OVERLAY_SET__H_ #define __OVERLAY_SET__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "BrainConstants.h" #include "CaretObject.h" #include "EventListenerInterface.h" #include "Overlay.h" #include "SceneableInterface.h" #include "StructureEnum.h" namespace caret { class BrowserTabContent; class LabelFile; class SceneClassAssistant; class Surface; class PlaneTextStringBuilder; class VolumeFile; class VolumeMappableInterface; class OverlaySet : public CaretObject, public EventListenerInterface, public SceneableInterface { public: OverlaySet(const AString& name, const int32_t tabIndex, const std::vector& includeSurfaceStructures, const Overlay::IncludeVolumeFiles includeVolumeFiles); virtual ~OverlaySet(); virtual void receiveEvent(Event* event); void copyOverlaySet(const OverlaySet* overlaySet); Overlay* getPrimaryOverlay(); Overlay* getUnderlay(); VolumeMappableInterface* getUnderlayVolume(); Overlay* getOverlay(const int32_t overlayNumber); const Overlay* getOverlay(const int32_t overlayNumber) const; void addDisplayedOverlay(); void setNumberOfDisplayedOverlays(const int32_t numberOfDisplayedOverlays); int32_t getNumberOfDisplayedOverlays() const; void insertOverlayAbove(const int32_t overlayIndex); void insertOverlayBelow(const int32_t overlayIndex); void removeDisplayedOverlay(const int32_t overlayIndex); void moveDisplayedOverlayUp(const int32_t overlayIndex); void moveDisplayedOverlayDown(const int32_t overlayIndex); VolumeMappableInterface* setUnderlayToVolume(); void initializeOverlays(); void getSelectedMapIndicesForFile(const CaretMappableDataFile* caretMappableDataFile, const bool isLimitToEnabledOverlays, std::vector& selectedMapIndicesOut) const; void getLabelFilesForSurface(const Surface* surface, std::vector& labelFilesOut, std::vector& labelMapIndicesOut); void resetOverlayYokingToOff(); virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); public: virtual AString toString() const; virtual void getDescriptionOfContent(PlainTextStringBuilder& descriptionOut) const; private: void findUnderlayFiles(const std::vector& matchToStructures, const bool includeVolumeFiles, std::vector& filesOut, std::vector& mapIndicesOut); void findMiddleLayerFiles(const std::vector& matchToStructures, const bool includeVolumeFiles, std::vector& filesOut, std::vector& mapIndicesOut); void findOverlayFiles(const std::vector& matchToStructures, const bool includeVolumeFiles, std::vector& filesOut, std::vector& mapIndicesOut); OverlaySet(const OverlaySet&); OverlaySet& operator=(const OverlaySet&); Overlay* m_overlays[BrainConstants::MAXIMUM_NUMBER_OF_OVERLAYS]; bool findFilesWithMapNamed(std::vector& matchedFilesOut, std::vector& matchedFileIndicesOut, const std::vector& matchToStructures, const DataFileTypeEnum::Enum dataFileType, const bool matchToVolumeData, const AString& matchToNamesRegularExpressionText, const bool matchToNamesRegularExpressionResult, const bool matchOneFilePerStructure); std::vector getVolumeFiles() const; AString m_name; int32_t m_tabIndex; /** Surface structures of data files displayed in this overlay */ const std::vector m_includeSurfaceStructures; /** Include volume files in this overlay */ const Overlay::IncludeVolumeFiles m_includeVolumeFiles; int32_t m_numberOfDisplayedOverlays; SceneClassAssistant* m_sceneAssistant; /** regular expression for matching myeline names - NOT saved to scenes */ static const AString s_myelinMatchRegularExpressionText; /** regular expression for matching shape names - NOT saved to scenes */ static const AString s_shapeMatchRegularExpressionText; /** regular expression for matching shape and myelin names - NOT saved to scenes */ static const AString s_shapeMyelinMatchRegularExpressionText; }; #ifdef __OVERLAY_SET_DECLARE__ AString const OverlaySet::s_myelinMatchRegularExpressionText = "(myelin)"; AString const OverlaySet::s_shapeMatchRegularExpressionText = "(sulc|shape|curv|depth|thick)"; AString const OverlaySet::s_shapeMyelinMatchRegularExpressionText = "(myelin|sulc|shape|curv|depth|thick)"; #endif // __OVERLAY_SET_DECLARE__ } // namespace #endif //__OVERLAY_SET__H_ workbench-1.1.1/src/Brain/OverlaySetArray.cxx000066400000000000000000000116151255417355300211540ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __OVERLAY_SET_ARRAY_DECLARE__ #include "OverlaySetArray.h" #undef __OVERLAY_SET_ARRAY_DECLARE__ #include "BrainConstants.h" #include "CaretAssert.h" #include "EventBrowserTabDelete.h" #include "EventManager.h" #include "OverlaySet.h" using namespace caret; /** * \class caret::OverlaySetArray * \brief Maintains an array of overlay sets for use with a model * \ingroup Brain */ /** * Constructor. * * @param includeSurfaceStructures * Surface structures for files available in this overlay. * @param includeVolumeFiles * Include (or not) volume files. * @param name * Name of model using this overlay set. This name is displayed * if there is an attempt to yoke models with incompatible overlays. */ OverlaySetArray::OverlaySetArray(const std::vector& includeSurfaceStructures, const Overlay::IncludeVolumeFiles includeVolumeFiles, const AString& name) : CaretObject(), m_name(name) { m_overlaySets.resize(BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS); for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_overlaySets[i] = new OverlaySet(name, i, includeSurfaceStructures, includeVolumeFiles); } EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_BROWSER_TAB_DELETE); } /** * Destructor. */ OverlaySetArray::~OverlaySetArray() { EventManager::get()->removeAllEventsFromListener(this); for (std::vector::iterator iter = m_overlaySets.begin(); iter != m_overlaySets.end(); iter++) { delete *iter; } m_overlaySets.clear(); } /** * Get a description of this object's content. * @return String describing this object's content. */ AString OverlaySetArray::toString() const { return "OverlaySetArray"; } /** * @return The number of overlay sets. */ int32_t OverlaySetArray::getNumberOfOverlaySets() { return m_overlaySets.size(); } /** * Get the overlay set at the given index. * * @param indx * Index of overlay set. * @return * Overlay set at given index. */ OverlaySet* OverlaySetArray::getOverlaySet(const int32_t indx) { CaretAssertVectorIndex(m_overlaySets, indx); return m_overlaySets[indx]; } /** * Initialize the overlay selections. */ void OverlaySetArray::initializeOverlaySelections() { for (int32_t iTab = 0; iTab < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; iTab++) { CaretAssertVectorIndex(m_overlaySets, iTab); m_overlaySets[iTab]->initializeOverlays(); } } /** * Receive an event. * * @param event * An event for which this instance is listening. */ void OverlaySetArray::receiveEvent(Event* event) { if (event->getEventType() == EventTypeEnum::EVENT_BROWSER_TAB_DELETE) { EventBrowserTabDelete* deleteTabEvent = dynamic_cast(event); CaretAssert(deleteTabEvent); /* * Since tab is being deleted, reset any overlay yoking for the tab. */ const int32_t tabIndex = deleteTabEvent->getBrowserTabIndex(); if ((tabIndex > 0) && (tabIndex < getNumberOfOverlaySets())) { m_overlaySets[tabIndex]->resetOverlayYokingToOff(); } deleteTabEvent->setEventProcessed(); } } /** * Copy the overlay set from the source tab index to the * destination tab index. * * @param sourceTabIndex * Source from which tab content is copied. * @param destinationTabIndex * Destination to which tab content is copied. */ void OverlaySetArray::copyOverlaySet(const int32_t sourceTabIndex, const int32_t destinationTabIndex) { CaretAssertVectorIndex(m_overlaySets, sourceTabIndex); CaretAssertVectorIndex(m_overlaySets, destinationTabIndex); const OverlaySet* sourceOverlaySet = m_overlaySets[sourceTabIndex]; OverlaySet* destinationOverlaySet = m_overlaySets[destinationTabIndex]; destinationOverlaySet->copyOverlaySet(sourceOverlaySet); } workbench-1.1.1/src/Brain/OverlaySetArray.h000066400000000000000000000045431255417355300206030ustar00rootroot00000000000000#ifndef __OVERLAY_SET_ARRAY_H__ #define __OVERLAY_SET_ARRAY_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "EventListenerInterface.h" #include "Overlay.h" namespace caret { class OverlaySet; class OverlaySetArray : public CaretObject, public EventListenerInterface { public: OverlaySetArray(const std::vector& includeSurfaceStructures, const Overlay::IncludeVolumeFiles includeVolumeFiles, const AString& name); virtual ~OverlaySetArray(); int32_t getNumberOfOverlaySets(); OverlaySet* getOverlaySet(const int32_t indx); void initializeOverlaySelections(); void copyOverlaySet(const int32_t sourceTabIndex, const int32_t destinationTabIndex); private: OverlaySetArray(const OverlaySetArray&); OverlaySetArray& operator=(const OverlaySetArray&); public: // ADD_NEW_METHODS_HERE virtual AString toString() const; virtual void receiveEvent(Event* event); private: // ADD_NEW_MEMBERS_HERE void initialize(); /** Name for this overlay set array */ AString m_name; /** The overlay sets */ std::vector m_overlaySets; }; #ifdef __OVERLAY_SET_ARRAY_DECLARE__ // #endif // __OVERLAY_SET_ARRAY_DECLARE__ } // namespace #endif //__OVERLAY_SET_ARRAY_H__ workbench-1.1.1/src/Brain/ProjectionViewTypeEnum.cxx000066400000000000000000000264011255417355300225150ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __PROJECTION_VIEW_TYPE_ENUM_DECLARE__ #include "ProjectionViewTypeEnum.h" #undef __PROJECTION_VIEW_TYPE_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::ProjectionViewTypeEnum * \brief Type for viewing models from left or right */ /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ ProjectionViewTypeEnum::ProjectionViewTypeEnum(const Enum enumValue, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCodeCounter++; this->name = name; this->guiName = guiName; } /** * Destructor. */ ProjectionViewTypeEnum::~ProjectionViewTypeEnum() { } /** * Initialize the enumerated metadata. */ void ProjectionViewTypeEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(ProjectionViewTypeEnum(PROJECTION_VIEW_CEREBELLUM_ANTERIOR, "PROJECTION_VIEW_CEREBELLUM_ANTERIOR", "Cerebellum Anterior")); enumData.push_back(ProjectionViewTypeEnum(PROJECTION_VIEW_CEREBELLUM_DORSAL, "PROJECTION_VIEW_CEREBELLUM_DORSAL", "Cerebellum Dorsal")); enumData.push_back(ProjectionViewTypeEnum(PROJECTION_VIEW_CEREBELLUM_POSTERIOR, "PROJECTION_VIEW_CEREBELLUM_POSTERIOR", "Cerebellum Posterior")); enumData.push_back(ProjectionViewTypeEnum(PROJECTION_VIEW_CEREBELLUM_VENTRAL, "PROJECTION_VIEW_CEREBELLUM_VENTRAL", "Cerebellum Ventral")); enumData.push_back(ProjectionViewTypeEnum(PROJECTION_VIEW_CEREBELLUM_FLAT_SURFACE, "PROJECTION_VIEW_CEREBELLUM_FLAT_SURFACE", "Cerebellum Flat")); enumData.push_back(ProjectionViewTypeEnum(PROJECTION_VIEW_LEFT_LATERAL, "PROJECTION_VIEW_LEFT_LATERAL", "Left Lateral")); enumData.push_back(ProjectionViewTypeEnum(PROJECTION_VIEW_LEFT_MEDIAL, "PROJECTION_VIEW_LEFT_MEDIAL", "Left Medial")); enumData.push_back(ProjectionViewTypeEnum(PROJECTION_VIEW_LEFT_FLAT_SURFACE, "PROJECTION_VIEW_LEFT_FLAT_SURFACE", "Left Flat")); enumData.push_back(ProjectionViewTypeEnum(PROJECTION_VIEW_RIGHT_LATERAL, "PROJECTION_VIEW_RIGHT_LATERAL", "Right Lateral")); enumData.push_back(ProjectionViewTypeEnum(PROJECTION_VIEW_RIGHT_MEDIAL, "PROJECTION_VIEW_RIGHT_MEDIAL", "Right Medial")); enumData.push_back(ProjectionViewTypeEnum(PROJECTION_VIEW_RIGHT_FLAT_SURFACE, "PROJECTION_VIEW_RIGHT_FLAT_SURFACE", "Right Flat")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const ProjectionViewTypeEnum* ProjectionViewTypeEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const ProjectionViewTypeEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString ProjectionViewTypeEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const ProjectionViewTypeEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ ProjectionViewTypeEnum::Enum ProjectionViewTypeEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = PROJECTION_VIEW_LEFT_LATERAL; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ProjectionViewTypeEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type ProjectionViewTypeEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString ProjectionViewTypeEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const ProjectionViewTypeEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ ProjectionViewTypeEnum::Enum ProjectionViewTypeEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = PROJECTION_VIEW_LEFT_LATERAL; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ProjectionViewTypeEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type ProjectionViewTypeEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t ProjectionViewTypeEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const ProjectionViewTypeEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ ProjectionViewTypeEnum::Enum ProjectionViewTypeEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = PROJECTION_VIEW_LEFT_LATERAL; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ProjectionViewTypeEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type ProjectionViewTypeEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void ProjectionViewTypeEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void ProjectionViewTypeEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(ProjectionViewTypeEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void ProjectionViewTypeEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(ProjectionViewTypeEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } workbench-1.1.1/src/Brain/ProjectionViewTypeEnum.h000066400000000000000000000100061255417355300221340ustar00rootroot00000000000000#ifndef __PROJECTION_VIEW_TYPE_ENUM_H__ #define __PROJECTION_VIEW_TYPE_ENUM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class ProjectionViewTypeEnum { public: /** * Enumerated values. */ enum Enum { /** Viewing cerebellum in anterior orientation */ PROJECTION_VIEW_CEREBELLUM_ANTERIOR, /** Viewing cerebellum in dorsal orientation */ PROJECTION_VIEW_CEREBELLUM_DORSAL, /** Viewing cerebellum in posterior orientation */ PROJECTION_VIEW_CEREBELLUM_POSTERIOR, /** Viewing cerebellum in ventral orientation */ PROJECTION_VIEW_CEREBELLUM_VENTRAL, /** Viewing cerebellum in flat surface */ PROJECTION_VIEW_CEREBELLUM_FLAT_SURFACE, /** Viewing models from left lateral */ PROJECTION_VIEW_LEFT_LATERAL, /** Viewing models from left medial */ PROJECTION_VIEW_LEFT_MEDIAL, /** Viewing model left flat surface */ PROJECTION_VIEW_LEFT_FLAT_SURFACE, /** Viewing models from right */ PROJECTION_VIEW_RIGHT_LATERAL, /** Viewing models from right medial */ PROJECTION_VIEW_RIGHT_MEDIAL, /** Viewing model right flat surface */ PROJECTION_VIEW_RIGHT_FLAT_SURFACE }; ~ProjectionViewTypeEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); private: ProjectionViewTypeEnum(const Enum enumValue, const AString& name, const AString& guiName); static const ProjectionViewTypeEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; #ifdef __PROJECTION_VIEW_TYPE_ENUM_DECLARE__ std::vector ProjectionViewTypeEnum::enumData; bool ProjectionViewTypeEnum::initializedFlag = false; int32_t ProjectionViewTypeEnum::integerCodeCounter = 0; #endif // __PROJECTION_VIEW_TYPE_ENUM_DECLARE__ } // namespace #endif //__PROJECTION_VIEW_TYPE_ENUM_H__ workbench-1.1.1/src/Brain/SelectionItem.cxx000066400000000000000000000145071255417355300206270ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SELECTION_ITEM_DECLARE__ #include "SelectionItem.h" #undef __SELECTION_ITEM_DECLARE__ #include using namespace caret; #include "SelectionItemDataTypeEnum.h" /** * \class SelectionItem * \brief Abstract class for selected items. * * Abstract class for selected items. */ /** * Constructor. */ SelectionItem::SelectionItem(const SelectionItemDataTypeEnum::Enum itemDataType) : CaretObject() { m_itemDataType = itemDataType; m_enabledForSelection = true; m_brain = NULL; m_screenDepth = 0.0; m_screenXYZ[0] = 0.0; m_screenXYZ[1] = 0.0; m_screenXYZ[2] = std::numeric_limits::max(); m_modelXYZ[0] = 0.0; m_modelXYZ[1] = 0.0; m_modelXYZ[2] = 0.0; } /** * Destructor. */ SelectionItem::~SelectionItem() { } /** * Copy constructor. * @param obj * Object that is copied. */ SelectionItem::SelectionItem(const SelectionItem& obj) : CaretObject(obj) { copyHelperSelectionItem(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ SelectionItem& SelectionItem::operator=(const SelectionItem& obj) { if (this != &obj) { CaretObject::operator=(obj); copyHelperSelectionItem(obj); } return *this; } /** * Helps with copying an object of this type. * @param ff * Object that is copied. */ void SelectionItem::copyHelperSelectionItem(const SelectionItem& idItem) { m_brain = idItem.m_brain; m_enabledForSelection = idItem.m_enabledForSelection; m_screenDepth = idItem.m_screenDepth; m_screenXYZ[0] = idItem.m_screenXYZ[0]; m_screenXYZ[1] = idItem.m_screenXYZ[1]; m_screenXYZ[2] = idItem.m_screenXYZ[2]; m_modelXYZ[0] = idItem.m_modelXYZ[0]; m_modelXYZ[1] = idItem.m_modelXYZ[1]; m_modelXYZ[2] = idItem.m_modelXYZ[2]; } /** * Reset this selection item. Deriving * classes should override this method to * reset its selection data and also call * the method in this class. */ void SelectionItem::reset() { m_brain = NULL; m_screenDepth = 0.0; m_screenXYZ[0] = 0.0; m_screenXYZ[1] = 0.0; m_screenXYZ[2] = std::numeric_limits::max(); m_modelXYZ[0] = 0.0; m_modelXYZ[1] = 0.0; m_modelXYZ[2] = 0.0; } /** * Get a description of this object's content. * @return String describing this object's content. */ AString SelectionItem::toString() const { AString text = ""; text += ("Type: " + SelectionItemDataTypeEnum::toGuiName(m_itemDataType) + "\n"); text += ("Depth: " + AString::number(m_screenDepth) + "\n"); text += ("Model XYZ: " + AString::fromNumbers(m_modelXYZ, 3, ", ") + "\n"); text += ("Screen XYZ: " + AString::fromNumbers(m_screenXYZ, 3, ", ") + "\n"); return text; } /** * @return The type of selected item. */ SelectionItemDataTypeEnum::Enum SelectionItem::getItemDataType() const { return m_itemDataType; } /** * @return Data type enabled for selection. */ bool SelectionItem::isEnabledForSelection() const { return m_enabledForSelection; } /** * Set the data type enabled for selection. * @param enabled * New value for selection enabled status. */ void SelectionItem::setEnabledForSelection(const bool enabled) { m_enabledForSelection = enabled; } /** * @return Brain in which identification item resides. */ Brain* SelectionItem::getBrain() { return m_brain; } /** * Set the brain. * * param brain * Brain in which identification item resides. */ void SelectionItem::setBrain(Brain* brain) { m_brain = brain; } /** * @return Screen depth of item. */ double SelectionItem::getScreenDepth() const { return m_screenDepth; } /** * Set the screen depth of the item. * @param screenDepth * New value for screen depth. */ void SelectionItem::setScreenDepth(const double screenDepth) { m_screenDepth = screenDepth; } /** * Get the screen XYZ of the selected item. * @param screenXYZ * XYZ out. */ void SelectionItem::getScreenXYZ(double screenXYZ[3]) const { screenXYZ[0] = m_screenXYZ[0]; screenXYZ[1] = m_screenXYZ[1]; screenXYZ[2] = m_screenXYZ[2]; } /** * Set the screen XYZ of the selected item. * @param screenXYZ * new XYZ. */ void SelectionItem::setScreenXYZ(const double screenXYZ[3]) { m_screenXYZ[0] = screenXYZ[0]; m_screenXYZ[1] = screenXYZ[1]; m_screenXYZ[2] = screenXYZ[2]; } /** * Get the model XYZ of the selected item. * @param modelXYZ * XYZ out. */ void SelectionItem::getModelXYZ(double modelXYZ[3]) const { modelXYZ[0] = m_modelXYZ[0]; modelXYZ[1] = m_modelXYZ[1]; modelXYZ[2] = m_modelXYZ[2]; } /** * Set the model XYZ of the selected item. * @param modelXYZ * new XYZ. */ void SelectionItem::setModelXYZ(const double modelXYZ[3]) { m_modelXYZ[0] = modelXYZ[0]; m_modelXYZ[1] = modelXYZ[1]; m_modelXYZ[2] = modelXYZ[2]; } /** * Is the other screen depth closer to the viewer than the currently * selected item? So, if true is returned, then replace the * current identification item * * (1) If there is no selected item, true is immediately returned. * (2) If there is an selected item and the other screen depth is closer * to the viewer, true is returned. * (3) false is returned. * * @param otherScreenDepth * Screen depth for testing. * @return result of test. */ bool SelectionItem::isOtherScreenDepthCloserToViewer(const double otherScreenDepth) const { if (isValid() == false) { return true; } if (otherScreenDepth < m_screenDepth) { return true; } return false; } workbench-1.1.1/src/Brain/SelectionItem.h000066400000000000000000000054251255417355300202530ustar00rootroot00000000000000#ifndef __SELECTION_ITEM__H_ #define __SELECTION_ITEM__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "SelectionItemDataTypeEnum.h" namespace caret { class Brain; class SelectionItem : public CaretObject { protected: SelectionItem(const SelectionItemDataTypeEnum::Enum itemDataType); SelectionItem(const SelectionItem&); SelectionItem& operator=(const SelectionItem&); public: virtual ~SelectionItem(); SelectionItemDataTypeEnum::Enum getItemDataType() const; bool isEnabledForSelection() const; void setEnabledForSelection(const bool enabled); Brain* getBrain(); void setBrain(Brain* brain); bool isOtherScreenDepthCloserToViewer(const double otherScreenDepth) const; double getScreenDepth() const; void setScreenDepth(const double screenDepth); void getScreenXYZ(double screenXYZ[3]) const; void setScreenXYZ(const double screenXYZ[3]); void getModelXYZ(double modelXYZ[3]) const; void setModelXYZ(const double modelXYZ[3]); /** * @return Is the selected item valid? */ virtual bool isValid() const = 0; virtual void reset(); private: public: virtual AString toString() const; protected: SelectionItemDataTypeEnum::Enum m_itemDataType; bool m_enabledForSelection; Brain* m_brain; double m_screenDepth; double m_screenXYZ[3]; double m_modelXYZ[3]; private: void copyHelperSelectionItem(const SelectionItem& idItem); }; #ifdef __SELECTION_ITEM_DECLARE__ // #endif // __SELECTION_ITEM_DECLARE__ } // namespace #endif //__SELECTION_ITEM__H_ workbench-1.1.1/src/Brain/SelectionItemBorderSurface.cxx000066400000000000000000000113671255417355300232770ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SELECTION_ITEM_BORDER_SURFACE_DECLARE__ #include "SelectionItemBorderSurface.h" #undef __SELECTION_ITEM_BORDER_SURFACE_DECLARE__ #include "Border.h" #include "BorderFile.h" #include "Surface.h" using namespace caret; /** * \class SelectionItemBorderSurface * \brief Contains information about the selected border. */ /** * Constructor. */ SelectionItemBorderSurface::SelectionItemBorderSurface() : SelectionItem(SelectionItemDataTypeEnum::BORDER_SURFACE) { this->surface = NULL; this->border = NULL; this->borderFile = NULL; this->borderIndex = -1; this->borderPointIndex = -1; } /** * Destructor. */ SelectionItemBorderSurface::~SelectionItemBorderSurface() { } /** * Reset this selection item. */ void SelectionItemBorderSurface::reset() { SelectionItem::reset(); this->surface = NULL; this->border = NULL; this->borderFile = NULL; this->borderIndex = -1; this->borderPointIndex = -1; } /** * @return Is this selected item valid? */ bool SelectionItemBorderSurface::isValid() const { return (this->border != NULL); } /** * @return Surface on which border was drawn. */ const Surface* SelectionItemBorderSurface::getSurface() const { return this->surface; } /** * @return Surface on which border was drawn. */ Surface* SelectionItemBorderSurface::getSurface() { return this->surface; } /** * Set the surface on which border was drawn. * @param surface * New value for surface. */ void SelectionItemBorderSurface::setSurface(Surface* surface) { this->surface = surface; } /** * @return The border that was selected. */ const Border* SelectionItemBorderSurface::getBorder() const { return this->border; } /** * @return The border that was selected. */ Border* SelectionItemBorderSurface::getBorder() { return this->border; } /** * Set the border that was selected. * @param border * New value for border. */ void SelectionItemBorderSurface::setBorder(Border* border) { this->border = border; } /** * @return The border file containing border that was selected. */ const BorderFile* SelectionItemBorderSurface::getBorderFile() const { return this->borderFile; } /** * @return The border file containing border that was selected. */ BorderFile* SelectionItemBorderSurface::getBorderFile() { return this->borderFile; } /** * Set the border file containing border that was selected. * @param borderFile * New value for border file. */ void SelectionItemBorderSurface::setBorderFile(BorderFile* borderFile) { this->borderFile = borderFile; } /** * return Index of selected border. */ int32_t SelectionItemBorderSurface::getBorderIndex() const { return this->borderIndex; } /** * Set index of selected border. * @param borderIndex * New value for border index. */ void SelectionItemBorderSurface::setBorderIndex(const int32_t borderIndex) { this->borderIndex = borderIndex; } /** * return Index of selected border. */ int32_t SelectionItemBorderSurface::getBorderPointIndex() const { return this->borderPointIndex; } /** * Set index of selected border. * @param borderIndex * New value for border index. */ void SelectionItemBorderSurface::setBorderPointIndex(const int32_t borderPointIndex) { this->borderPointIndex = borderPointIndex; } /** * Get a description of m_ object's content. * @return String describing m_ object's content. */ AString SelectionItemBorderSurface::toString() const { AString text = SelectionItem::toString(); text += ("Surface: " + ((surface != NULL) ? surface->getFileNameNoPath() : "INVALID") + "\n"); text += ("Border File: " + ((borderFile != NULL) ? borderFile->getFileNameNoPath() : "INVALID") + "\n"); text += ("Border: " + ((border != NULL) ? border->getName() : "INVALID") + "\n"); text += ("Border Index: " + AString::number(borderIndex) + "\n"); text += ("Border Point Index: " + AString::number(borderPointIndex) + "\n"); return text; } workbench-1.1.1/src/Brain/SelectionItemBorderSurface.h000066400000000000000000000050271255417355300227200ustar00rootroot00000000000000#ifndef __SELECTION_ITEM_BORDER_SURFACE__H_ #define __SELECTION_ITEM_BORDER_SURFACE__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "SelectionItem.h" namespace caret { class Border; class BorderFile; class Surface; class SelectionItemBorderSurface : public SelectionItem { public: SelectionItemBorderSurface(); virtual ~SelectionItemBorderSurface(); virtual bool isValid() const; Surface* getSurface(); const Surface* getSurface() const; Border* getBorder(); const Border* getBorder() const; void setBorder(Border* border); BorderFile* getBorderFile(); const BorderFile* getBorderFile() const; void setBorderFile(BorderFile* borderFile); void setSurface(Surface* surface); int32_t getBorderIndex() const; void setBorderIndex(const int32_t borderIndex); int32_t getBorderPointIndex() const; void setBorderPointIndex(const int32_t borderPointIndex); void reset(); virtual AString toString() const; private: SelectionItemBorderSurface(const SelectionItemBorderSurface&); SelectionItemBorderSurface& operator=(const SelectionItemBorderSurface&); Border* border; BorderFile* borderFile; Surface* surface; int32_t borderIndex; int32_t borderPointIndex; }; #ifdef __SELECTION_ITEM_BORDER_SURFACE_DECLARE__ // #endif // __SELECTION_ITEM_BORDER_SURFACE_DECLARE__ } // namespace #endif //__SELECTION_ITEM_BORDER_SURFACE__H_ workbench-1.1.1/src/Brain/SelectionItemChartDataSeries.cxx000066400000000000000000000102041255417355300235440ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SELECTION_ITEM_CHART_DATA_SERIES_DECLARE__ #include "SelectionItemChartDataSeries.h" #undef __SELECTION_ITEM_CHART_DATA_SERIES_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::SelectionItemChartDataSeries * \brief Contains selection of a data-series chart. * \ingroup Brain */ /** * Constructor. */ SelectionItemChartDataSeries::SelectionItemChartDataSeries() : SelectionItem(SelectionItemDataTypeEnum::CHART_DATA_SERIES) { m_chartModelDataSeries = NULL; m_chartDataCartesian = NULL; m_chartDataPointIndex = -1; } /** * Destructor. */ SelectionItemChartDataSeries::~SelectionItemChartDataSeries() { } /** * Copy constructor. * @param obj * Object that is copied. */ SelectionItemChartDataSeries::SelectionItemChartDataSeries(const SelectionItemChartDataSeries& obj) : SelectionItem(obj) { this->copyHelperSelectionItemChartDataSeries(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ SelectionItemChartDataSeries& SelectionItemChartDataSeries::operator=(const SelectionItemChartDataSeries& obj) { if (this != &obj) { SelectionItem::operator=(obj); this->copyHelperSelectionItemChartDataSeries(obj); } return *this; } /** * Helps with copying an object of this type. * @param obj * Object that is copied. */ void SelectionItemChartDataSeries::copyHelperSelectionItemChartDataSeries(const SelectionItemChartDataSeries& obj) { m_chartModelDataSeries = obj.m_chartModelDataSeries; m_chartDataCartesian = obj.m_chartDataCartesian; m_chartDataPointIndex = obj.m_chartDataPointIndex; } /** * @return True if the selected chart is valid, else false. */ bool SelectionItemChartDataSeries::isValid() const { if ((m_chartModelDataSeries != NULL) && (m_chartDataCartesian != NULL) && (m_chartDataPointIndex >= 0)) { return true; } return false; } /** * Reset the selections. */ void SelectionItemChartDataSeries::reset() { m_chartModelDataSeries = NULL; m_chartDataCartesian = NULL; m_chartDataPointIndex = -1; } /** * @return */ ChartModelDataSeries* SelectionItemChartDataSeries::getChartModelDataSeries() const { return m_chartModelDataSeries; } /** * @return */ ChartDataCartesian* SelectionItemChartDataSeries::getChartDataCartesian() const { return m_chartDataCartesian; } /** * @return */ int32_t SelectionItemChartDataSeries::getChartDataPointIndex() const { return m_chartDataPointIndex; } /** * Set the selected chart information. * * @param chartModelDataSeries * Data series chart model that was selected. * @param chartDataCartesian * Cartesian chart data that was selected. * @param chartDataPointIndex * Point index of selected chart data. */ void SelectionItemChartDataSeries::setChart(ChartModelDataSeries* chartModelDataSeries, ChartDataCartesian* chartDataCartesian, const int32_t chartDataPointIndex) { CaretAssert(chartModelDataSeries); CaretAssert(chartDataCartesian); CaretAssert(chartDataPointIndex >= 0); m_chartModelDataSeries = chartModelDataSeries; m_chartDataCartesian = chartDataCartesian; m_chartDataPointIndex = chartDataPointIndex; } workbench-1.1.1/src/Brain/SelectionItemChartDataSeries.h000066400000000000000000000046551255417355300232060ustar00rootroot00000000000000#ifndef __SELECTION_ITEM_CHART_DATA_SERIES_H__ #define __SELECTION_ITEM_CHART_DATA_SERIES_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "SelectionItem.h" namespace caret { class ChartDataCartesian; class ChartModelDataSeries; class SelectionItemChartDataSeries : public SelectionItem { public: SelectionItemChartDataSeries(); virtual ~SelectionItemChartDataSeries(); SelectionItemChartDataSeries(const SelectionItemChartDataSeries& obj); SelectionItemChartDataSeries& operator=(const SelectionItemChartDataSeries& obj); virtual bool isValid() const; virtual void reset(); ChartModelDataSeries* getChartModelDataSeries() const; ChartDataCartesian* getChartDataCartesian() const; int32_t getChartDataPointIndex() const; void setChart(ChartModelDataSeries* chartModelDataSeries, ChartDataCartesian* chartDataCartesian, const int32_t chartDataPointIndex); // ADD_NEW_METHODS_HERE private: void copyHelperSelectionItemChartDataSeries(const SelectionItemChartDataSeries& obj); ChartModelDataSeries* m_chartModelDataSeries; ChartDataCartesian* m_chartDataCartesian; int32_t m_chartDataPointIndex; // ADD_NEW_MEMBERS_HERE }; #ifdef __SELECTION_ITEM_CHART_DATA_SERIES_DECLARE__ // #endif // __SELECTION_ITEM_CHART_DATA_SERIES_DECLARE__ } // namespace #endif //__SELECTION_ITEM_CHART_DATA_SERIES_H__ workbench-1.1.1/src/Brain/SelectionItemChartFrequencySeries.cxx000066400000000000000000000105211255417355300246360ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SELECTION_ITEM_CHART_FREQUENCY_SERIES_DECLARE__ #include "SelectionItemChartFrequencySeries.h" #undef __SELECTION_ITEM_CHART_FREQUENCY_SERIES_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::SelectionItemChartFrequencySeries * \brief Contains selection of a data-series chart. * \ingroup Brain */ /** * Constructor. */ SelectionItemChartFrequencySeries::SelectionItemChartFrequencySeries() : SelectionItem(SelectionItemDataTypeEnum::CHART_FREQUENCY_SERIES) { m_chartModelFrequencySeries = NULL; m_chartDataCartesian = NULL; m_chartDataPointIndex = -1; } /** * Destructor. */ SelectionItemChartFrequencySeries::~SelectionItemChartFrequencySeries() { } /** * Copy constructor. * @param obj * Object that is copied. */ SelectionItemChartFrequencySeries::SelectionItemChartFrequencySeries(const SelectionItemChartFrequencySeries& obj) : SelectionItem(obj) { this->copyHelperSelectionItemChartFrequencySeries(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ SelectionItemChartFrequencySeries& SelectionItemChartFrequencySeries::operator=(const SelectionItemChartFrequencySeries& obj) { if (this != &obj) { SelectionItem::operator=(obj); this->copyHelperSelectionItemChartFrequencySeries(obj); } return *this; } /** * Helps with copying an object of this type. * @param obj * Object that is copied. */ void SelectionItemChartFrequencySeries::copyHelperSelectionItemChartFrequencySeries(const SelectionItemChartFrequencySeries& obj) { m_chartModelFrequencySeries = obj.m_chartModelFrequencySeries; m_chartDataCartesian = obj.m_chartDataCartesian; m_chartDataPointIndex = obj.m_chartDataPointIndex; } /** * @return True if the selected chart is valid, else false. */ bool SelectionItemChartFrequencySeries::isValid() const { if ((m_chartModelFrequencySeries != NULL) && (m_chartDataCartesian != NULL) && (m_chartDataPointIndex >= 0)) { return true; } return false; } /** * Reset the selections. */ void SelectionItemChartFrequencySeries::reset() { m_chartModelFrequencySeries = NULL; m_chartDataCartesian = NULL; m_chartDataPointIndex = -1; } /** * @return */ ChartModelFrequencySeries* SelectionItemChartFrequencySeries::getChartModelFrequencySeries() const { return m_chartModelFrequencySeries; } /** * @return */ ChartDataCartesian* SelectionItemChartFrequencySeries::getChartDataCartesian() const { return m_chartDataCartesian; } /** * @return */ int32_t SelectionItemChartFrequencySeries::getChartDataPointIndex() const { return m_chartDataPointIndex; } /** * Set the selected chart information. * * @param chartModelFrequencySeries * Frequency series chart model that was selected. * @param chartDataCartesian * Cartesian chart data that was selected. * @param chartDataPointIndex * Point index of selected chart data. */ void SelectionItemChartFrequencySeries::setChart(ChartModelFrequencySeries* chartModelFrequencySeries, ChartDataCartesian* chartDataCartesian, const int32_t chartDataPointIndex) { CaretAssert(chartModelFrequencySeries); CaretAssert(chartDataCartesian); CaretAssert(chartDataPointIndex >= 0); m_chartModelFrequencySeries = chartModelFrequencySeries; m_chartDataCartesian = chartDataCartesian; m_chartDataPointIndex = chartDataPointIndex; } workbench-1.1.1/src/Brain/SelectionItemChartFrequencySeries.h000066400000000000000000000050261255417355300242670ustar00rootroot00000000000000#ifndef __SELECTION_ITEM_CHART_FREQUENCY_SERIES_H__ #define __SELECTION_ITEM_CHART_FREQUENCY_SERIES_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "SelectionItem.h" namespace caret { class ChartDataCartesian; class ChartModelFrequencySeries; class SelectionItemChartFrequencySeries : public SelectionItem { public: SelectionItemChartFrequencySeries(); virtual ~SelectionItemChartFrequencySeries(); SelectionItemChartFrequencySeries(const SelectionItemChartFrequencySeries& obj); SelectionItemChartFrequencySeries& operator=(const SelectionItemChartFrequencySeries& obj); virtual bool isValid() const; virtual void reset(); ChartModelFrequencySeries* getChartModelFrequencySeries() const; ChartDataCartesian* getChartDataCartesian() const; int32_t getChartDataPointIndex() const; void setChart(ChartModelFrequencySeries* chartModelFrequencySeries, ChartDataCartesian* chartDataCartesian, const int32_t chartDataPointIndex); // ADD_NEW_METHODS_HERE private: void copyHelperSelectionItemChartFrequencySeries(const SelectionItemChartFrequencySeries& obj); ChartModelFrequencySeries* m_chartModelFrequencySeries; ChartDataCartesian* m_chartDataCartesian; int32_t m_chartDataPointIndex; // ADD_NEW_MEMBERS_HERE }; #ifdef __SELECTION_ITEM_CHART_FREQUENCY_SERIES_DECLARE__ // #endif // __SELECTION_ITEM_CHART_FREQUENCY_SERIES_DECLARE__ } // namespace #endif //__SELECTION_ITEM_CHART_FREQUENCY_SERIES_H__ workbench-1.1.1/src/Brain/SelectionItemChartMatrix.cxx000066400000000000000000000100401255417355300227620ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SELECTION_ITEM_CHART_MATRIX_DECLARE__ #include "SelectionItemChartMatrix.h" #undef __SELECTION_ITEM_CHART_MATRIX_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::SelectionItemChartMatrix * \brief Contains selection of a data-series chart. * \ingroup Brain */ /** * Constructor. */ SelectionItemChartMatrix::SelectionItemChartMatrix() : SelectionItem(SelectionItemDataTypeEnum::CHART_MATRIX) { m_chartableMatrixInterface = NULL; m_matrixRowIndex = -1; m_matrixColumnIndex = -1; } /** * Destructor. */ SelectionItemChartMatrix::~SelectionItemChartMatrix() { } /** * Copy constructor. * @param obj * Object that is copied. */ SelectionItemChartMatrix::SelectionItemChartMatrix(const SelectionItemChartMatrix& obj) : SelectionItem(obj) { this->copyHelperSelectionItemChartMatrix(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ SelectionItemChartMatrix& SelectionItemChartMatrix::operator=(const SelectionItemChartMatrix& obj) { if (this != &obj) { SelectionItem::operator=(obj); this->copyHelperSelectionItemChartMatrix(obj); } return *this; } /** * Helps with copying an object of this type. * @param obj * Object that is copied. */ void SelectionItemChartMatrix::copyHelperSelectionItemChartMatrix(const SelectionItemChartMatrix& obj) { m_chartableMatrixInterface = obj.m_chartableMatrixInterface; m_matrixRowIndex = obj.m_matrixRowIndex; m_matrixColumnIndex = obj.m_matrixColumnIndex; } /** * @return True if the selected chart is valid, else false. */ bool SelectionItemChartMatrix::isValid() const { if ((m_chartableMatrixInterface != NULL) && (m_matrixRowIndex >= 0) && (m_matrixColumnIndex >= 0)) { return true; } return false; } /** * Reset the selections. */ void SelectionItemChartMatrix::reset() { m_chartableMatrixInterface = NULL; m_matrixRowIndex = -1; m_matrixColumnIndex = -1; } /** * @return The Chartable Matrix Interface (file) */ ChartableMatrixInterface* SelectionItemChartMatrix::getChartableMatrixInterface() const { return m_chartableMatrixInterface; } /** * @return Matrix row index. */ int32_t SelectionItemChartMatrix::getMatrixRowIndex() const { return m_matrixRowIndex; } /** * @return Matrix column index. */ int32_t SelectionItemChartMatrix::getMatrixColumnIndex() const { return m_matrixColumnIndex; } /** * Set the selection information. * * @param chartableMatrixInterface * The chartable matrix interface (file) * @param matrixRowIndex * Row index * @param matrixColumnIndex * Column index */ void SelectionItemChartMatrix::setChartMatrix(ChartableMatrixInterface* chartableMatrixInterface, const int32_t matrixRowIndex, const int32_t matrixColumnIndex) { CaretAssert(chartableMatrixInterface); CaretAssert(matrixRowIndex >= 0); CaretAssert(matrixColumnIndex >= 0); m_chartableMatrixInterface = chartableMatrixInterface; m_matrixRowIndex = matrixRowIndex; m_matrixColumnIndex = matrixColumnIndex; } workbench-1.1.1/src/Brain/SelectionItemChartMatrix.h000066400000000000000000000045201255417355300224150ustar00rootroot00000000000000#ifndef __SELECTION_ITEM_CHART_MATRIX_H__ #define __SELECTION_ITEM_CHART_MATRIX_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "SelectionItem.h" namespace caret { class ChartableMatrixInterface; class SelectionItemChartMatrix : public SelectionItem { public: SelectionItemChartMatrix(); virtual ~SelectionItemChartMatrix(); SelectionItemChartMatrix(const SelectionItemChartMatrix& obj); SelectionItemChartMatrix& operator=(const SelectionItemChartMatrix& obj); virtual bool isValid() const; virtual void reset(); ChartableMatrixInterface* getChartableMatrixInterface() const; int32_t getMatrixRowIndex() const; int32_t getMatrixColumnIndex() const; void setChartMatrix(ChartableMatrixInterface* chartableMatrixInterface, const int32_t matrixRowIndex, const int32_t matrixColumnIndex); // ADD_NEW_METHODS_HERE private: void copyHelperSelectionItemChartMatrix(const SelectionItemChartMatrix& obj); ChartableMatrixInterface* m_chartableMatrixInterface; int32_t m_matrixRowIndex; int32_t m_matrixColumnIndex; // ADD_NEW_MEMBERS_HERE }; #ifdef __SELECTION_ITEM_CHART_MATRIX_DECLARE__ // #endif // __SELECTION_ITEM_CHART_MATRIX_DECLARE__ } // namespace #endif //__SELECTION_ITEM_CHART_MATRIX_H__ workbench-1.1.1/src/Brain/SelectionItemChartTimeSeries.cxx000066400000000000000000000102041255417355300235710ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SELECTION_ITEM_CHART_TIME_SERIES_DECLARE__ #include "SelectionItemChartTimeSeries.h" #undef __SELECTION_ITEM_CHART_TIME_SERIES_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::SelectionItemChartTimeSeries * \brief Contains selection of a time-series chart. * \ingroup Brain */ /** * Constructor. */ SelectionItemChartTimeSeries::SelectionItemChartTimeSeries() : SelectionItem(SelectionItemDataTypeEnum::CHART_TIME_SERIES) { m_chartModelTimeSeries = NULL; m_chartDataCartesian = NULL; m_chartDataPointIndex = -1; } /** * Destructor. */ SelectionItemChartTimeSeries::~SelectionItemChartTimeSeries() { } /** * Copy constructor. * @param obj * Object that is copied. */ SelectionItemChartTimeSeries::SelectionItemChartTimeSeries(const SelectionItemChartTimeSeries& obj) : SelectionItem(obj) { this->copyHelperSelectionItemChartTimeSeries(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ SelectionItemChartTimeSeries& SelectionItemChartTimeSeries::operator=(const SelectionItemChartTimeSeries& obj) { if (this != &obj) { SelectionItem::operator=(obj); this->copyHelperSelectionItemChartTimeSeries(obj); } return *this; } /** * Helps with copying an object of this type. * @param obj * Object that is copied. */ void SelectionItemChartTimeSeries::copyHelperSelectionItemChartTimeSeries(const SelectionItemChartTimeSeries& obj) { m_chartModelTimeSeries = obj.m_chartModelTimeSeries; m_chartDataCartesian = obj.m_chartDataCartesian; m_chartDataPointIndex = obj.m_chartDataPointIndex; } /** * @return True if the selected chart is valid, else false. */ bool SelectionItemChartTimeSeries::isValid() const { if ((m_chartModelTimeSeries != NULL) && (m_chartDataCartesian != NULL) && (m_chartDataPointIndex >= 0)) { return true; } return false; } /** * Reset the selections. */ void SelectionItemChartTimeSeries::reset() { m_chartModelTimeSeries = NULL; m_chartDataCartesian = NULL; m_chartDataPointIndex = -1; } /** * @return */ ChartModelTimeSeries* SelectionItemChartTimeSeries::getChartModelTimeSeries() const { return m_chartModelTimeSeries; } /** * @return */ ChartDataCartesian* SelectionItemChartTimeSeries::getChartDataCartesian() const { return m_chartDataCartesian; } /** * @return */ int32_t SelectionItemChartTimeSeries::getChartDataPointIndex() const { return m_chartDataPointIndex; } /** * Set the selected chart information. * * @param chartModelTimeSeries * Data series chart model that was selected. * @param chartDataCartesian * Cartesian chart data that was selected. * @param chartDataPointIndex * Point index of selected chart data. */ void SelectionItemChartTimeSeries::setChart(ChartModelTimeSeries* chartModelTimeSeries, ChartDataCartesian* chartDataCartesian, const int32_t chartDataPointIndex) { CaretAssert(chartModelTimeSeries); CaretAssert(chartDataCartesian); CaretAssert(chartDataPointIndex >= 0); m_chartModelTimeSeries = chartModelTimeSeries; m_chartDataCartesian = chartDataCartesian; m_chartDataPointIndex = chartDataPointIndex; } workbench-1.1.1/src/Brain/SelectionItemChartTimeSeries.h000066400000000000000000000046551255417355300232330ustar00rootroot00000000000000#ifndef __SELECTION_ITEM_CHART_TIME_SERIES_H__ #define __SELECTION_ITEM_CHART_TIME_SERIES_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "SelectionItem.h" namespace caret { class ChartDataCartesian; class ChartModelTimeSeries; class SelectionItemChartTimeSeries : public SelectionItem { public: SelectionItemChartTimeSeries(); virtual ~SelectionItemChartTimeSeries(); SelectionItemChartTimeSeries(const SelectionItemChartTimeSeries& obj); SelectionItemChartTimeSeries& operator=(const SelectionItemChartTimeSeries& obj); virtual bool isValid() const; virtual void reset(); ChartModelTimeSeries* getChartModelTimeSeries() const; ChartDataCartesian* getChartDataCartesian() const; int32_t getChartDataPointIndex() const; void setChart(ChartModelTimeSeries* chartModelTimeSeries, ChartDataCartesian* chartDataCartesian, const int32_t chartDataPointIndex); // ADD_NEW_METHODS_HERE private: void copyHelperSelectionItemChartTimeSeries(const SelectionItemChartTimeSeries& obj); ChartModelTimeSeries* m_chartModelTimeSeries; ChartDataCartesian* m_chartDataCartesian; int32_t m_chartDataPointIndex; // ADD_NEW_MEMBERS_HERE }; #ifdef __SELECTION_ITEM_CHART_TIME_SERIES_DECLARE__ // #endif // __SELECTION_ITEM_CHART_TIME_SERIES_DECLARE__ } // namespace #endif //__SELECTION_ITEM_CHART_TIME_SERIES_H__ workbench-1.1.1/src/Brain/SelectionItemCiftiConnectivityMatrixRowColumn.cxx000066400000000000000000000126711255417355300272400ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SELECTION_ITEM_CIFTI_CONNECTIVITY_MATRIX_ROW_COLUMN_DECLARE__ #include "SelectionItemCiftiConnectivityMatrixRowColumn.h" #undef __SELECTION_ITEM_CIFTI_CONNECTIVITY_MATRIX_ROW_COLUMN_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::SelectionItemCiftiConnectivityMatrixRowColumn * \brief Contains selection of a row or column in a CIFTI connectivity matrix file. * \ingroup Brain */ /** * Constructor. */ SelectionItemCiftiConnectivityMatrixRowColumn::SelectionItemCiftiConnectivityMatrixRowColumn() : SelectionItem(SelectionItemDataTypeEnum::CIFTI_CONNECTIVITY_MATRIX_ROW_COLUMN) { m_ciftiConnectivityMatrixFile = NULL; m_matrixRowIndex = -1; m_matrixColumnIndex = -1; } /** * Destructor. */ SelectionItemCiftiConnectivityMatrixRowColumn::~SelectionItemCiftiConnectivityMatrixRowColumn() { } /** * Copy constructor. * @param obj * Object that is copied. */ SelectionItemCiftiConnectivityMatrixRowColumn::SelectionItemCiftiConnectivityMatrixRowColumn(const SelectionItemCiftiConnectivityMatrixRowColumn& obj) : SelectionItem(obj) { this->copyHelperSelectionItemCiftiConnectivityMatrixRowColumn(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ SelectionItemCiftiConnectivityMatrixRowColumn& SelectionItemCiftiConnectivityMatrixRowColumn::operator=(const SelectionItemCiftiConnectivityMatrixRowColumn& obj) { if (this != &obj) { SelectionItem::operator=(obj); this->copyHelperSelectionItemCiftiConnectivityMatrixRowColumn(obj); } return *this; } /** * Helps with copying an object of this type. * @param obj * Object that is copied. */ void SelectionItemCiftiConnectivityMatrixRowColumn::copyHelperSelectionItemCiftiConnectivityMatrixRowColumn(const SelectionItemCiftiConnectivityMatrixRowColumn& obj) { m_ciftiConnectivityMatrixFile = obj.m_ciftiConnectivityMatrixFile; m_matrixRowIndex = obj.m_matrixRowIndex; m_matrixColumnIndex = obj.m_matrixColumnIndex; } /** * @return True if the CIFTI connectivity matrix row or column is valid, else false. */ bool SelectionItemCiftiConnectivityMatrixRowColumn::isValid() const { if (m_ciftiConnectivityMatrixFile != NULL) { if ((m_matrixRowIndex >= 0) || (m_matrixColumnIndex >= 0)) { return true; } } return false; } /** * Reset the selections. */ void SelectionItemCiftiConnectivityMatrixRowColumn::reset() { m_ciftiConnectivityMatrixFile = NULL; m_matrixRowIndex = -1; m_matrixColumnIndex = -1; } /** * @return CIFTI connectivity matrix file. */ CiftiMappableConnectivityMatrixDataFile* SelectionItemCiftiConnectivityMatrixRowColumn::getCiftiConnectivityMatrixFile() const { return m_ciftiConnectivityMatrixFile; } /** * @return CIFTI connectivity matrix row index. A negative value * indicates that the row index is invalid. */ int32_t SelectionItemCiftiConnectivityMatrixRowColumn::getMatrixRowIndex() const { return m_matrixRowIndex; } /** * @return CIFTI connectivity matrix column index. A negative value * indicates that the column index is invalid. */ int32_t SelectionItemCiftiConnectivityMatrixRowColumn::getMatrixColumnIndex() const { return m_matrixColumnIndex; } /** * Set the selection to a CIFTI connectivity matrix file row. * * @param ciftiConnectivityMatrixFile * The CIFTI connectivity matrix file * @param matrixRowIndex * Row index */ void SelectionItemCiftiConnectivityMatrixRowColumn::setFileRow(CiftiMappableConnectivityMatrixDataFile* ciftiConnectivityMatrixFile, const int32_t matrixRowIndex) { CaretAssert(ciftiConnectivityMatrixFile); CaretAssert(matrixRowIndex >= 0); m_ciftiConnectivityMatrixFile = ciftiConnectivityMatrixFile; m_matrixRowIndex = matrixRowIndex; m_matrixColumnIndex = -1; } /** * Set the selection to a CIFTI connectivity matrix file column. * * @param ciftiConnectivityMatrixFile * The CIFTI connectivity matrix file * @param matrixColumnIndex * Column index */ void SelectionItemCiftiConnectivityMatrixRowColumn::setFileColumn(CiftiMappableConnectivityMatrixDataFile* ciftiConnectivityMatrixFile, const int32_t matrixColumnIndex) { CaretAssert(ciftiConnectivityMatrixFile); CaretAssert(matrixColumnIndex >= 0); m_ciftiConnectivityMatrixFile = ciftiConnectivityMatrixFile; m_matrixRowIndex = -1; m_matrixColumnIndex = matrixColumnIndex; } workbench-1.1.1/src/Brain/SelectionItemCiftiConnectivityMatrixRowColumn.h000066400000000000000000000055201255417355300266600ustar00rootroot00000000000000#ifndef __SELECTION_ITEM_CIFTI_CONNECTIVITY_MATRIX_ROW_COLUMN_H__ #define __SELECTION_ITEM_CIFTI_CONNECTIVITY_MATRIX_ROW_COLUMN_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "SelectionItem.h" namespace caret { class CiftiMappableConnectivityMatrixDataFile; class SelectionItemCiftiConnectivityMatrixRowColumn : public SelectionItem { public: SelectionItemCiftiConnectivityMatrixRowColumn(); virtual ~SelectionItemCiftiConnectivityMatrixRowColumn(); SelectionItemCiftiConnectivityMatrixRowColumn(const SelectionItemCiftiConnectivityMatrixRowColumn& obj); SelectionItemCiftiConnectivityMatrixRowColumn& operator=(const SelectionItemCiftiConnectivityMatrixRowColumn& obj); virtual bool isValid() const; virtual void reset(); CiftiMappableConnectivityMatrixDataFile* getCiftiConnectivityMatrixFile() const; int32_t getMatrixRowIndex() const; int32_t getMatrixColumnIndex() const; void setFileRow(CiftiMappableConnectivityMatrixDataFile* ciftiConnectivityMatrixFile, const int32_t matrixRowIndex); void setFileColumn(CiftiMappableConnectivityMatrixDataFile* ciftiConnectivityMatrixFile, const int32_t matrixColumnIndex); // ADD_NEW_METHODS_HERE private: void copyHelperSelectionItemCiftiConnectivityMatrixRowColumn(const SelectionItemCiftiConnectivityMatrixRowColumn& obj); CiftiMappableConnectivityMatrixDataFile* m_ciftiConnectivityMatrixFile; int32_t m_matrixRowIndex; int32_t m_matrixColumnIndex; // ADD_NEW_MEMBERS_HERE }; #ifdef __SELECTION_ITEM_CIFTI_CONNECTIVITY_MATRIX_ROW_COLUMN_DECLARE__ // #endif // __SELECTION_ITEM_CIFTI_CONNECTIVITY_MATRIX_ROW_COLUMN_DECLARE__ } // namespace #endif //__SELECTION_ITEM_CIFTI_CONNECTIVITY_MATRIX_ROW_COLUMN_H__ workbench-1.1.1/src/Brain/SelectionItemDataTypeEnum.cxx000066400000000000000000000301171255417355300231030ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __SELECTION_ITEM_DATA_TYPE_ENUM_DECLARE__ #include "SelectionItemDataTypeEnum.h" #undef __SELECTION_ITEM_DATA_TYPE_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ SelectionItemDataTypeEnum::SelectionItemDataTypeEnum(const Enum enumValue, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCodeCounter++; this->name = name; this->guiName = guiName; } /** * Destructor. */ SelectionItemDataTypeEnum::~SelectionItemDataTypeEnum() { } /** * Initialize the enumerated metadata. */ void SelectionItemDataTypeEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(SelectionItemDataTypeEnum(INVALID, "INVALID", "Invalid")); enumData.push_back(SelectionItemDataTypeEnum(ANNOTATION, "ANNOTATION", "Annotation")); enumData.push_back(SelectionItemDataTypeEnum(BORDER_SURFACE, "BORDER_SURFACE", "Surface Border")); enumData.push_back(SelectionItemDataTypeEnum(BORDER_VOLUME, "BORDER_VOLUME", "Volume Border")); enumData.push_back(SelectionItemDataTypeEnum(CHART_DATA_SERIES, "CHART_DATA_SERIES", "Data-Series Chart")); enumData.push_back(SelectionItemDataTypeEnum(CHART_FREQUENCY_SERIES, "CHART_FREQUENCY_SERIES", "Frequency-Series Chart")); enumData.push_back(SelectionItemDataTypeEnum(CHART_MATRIX, "CHART_MATRIX", "Matrix Chart")); enumData.push_back(SelectionItemDataTypeEnum(CHART_TIME_SERIES, "CHART_TIME_SERIES", "Time-Series Chart")); enumData.push_back(SelectionItemDataTypeEnum(CIFTI_CONNECTIVITY_MATRIX_ROW_COLUMN, "CIFTI_CONNECTIVITY_MATRIX_ROW_COLUMN", "CIFTI Connectivity Row or Column")); enumData.push_back(SelectionItemDataTypeEnum(FOCUS_SURFACE, "FOCUS_SURFACE", "Surface Focus")); enumData.push_back(SelectionItemDataTypeEnum(FOCUS_VOLUME, "FOCUS_VOLUME", "Volume Focus")); enumData.push_back(SelectionItemDataTypeEnum(SURFACE_NODE, "SURFACE_NODE", "Surface Vertex")); enumData.push_back(SelectionItemDataTypeEnum(SURFACE_NODE_IDENTIFICATION_SYMBOL, "SURFACE_NODE_IDENTIFICATION_SYMBOL", "Surface Vertex Identification Symbol")); enumData.push_back(SelectionItemDataTypeEnum(SURFACE_TRIANGLE, "SURFACE_TRIANGLE", "Surface Triangle")); enumData.push_back(SelectionItemDataTypeEnum(VOXEL, "VOXEL", "Voxel")); enumData.push_back(SelectionItemDataTypeEnum(VOXEL_EDITING, "VOXEL_EDITING", "Voxel Editing")); enumData.push_back(SelectionItemDataTypeEnum(VOXEL_IDENTIFICATION_SYMBOL, "VOXEL_IDENTIFICATION_SYMBOL", "Voxel Identification Symbol")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const SelectionItemDataTypeEnum* SelectionItemDataTypeEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const SelectionItemDataTypeEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString SelectionItemDataTypeEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const SelectionItemDataTypeEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ SelectionItemDataTypeEnum::Enum SelectionItemDataTypeEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = INVALID; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const SelectionItemDataTypeEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type SelectionItemDataTypeEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString SelectionItemDataTypeEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const SelectionItemDataTypeEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param guiName * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ SelectionItemDataTypeEnum::Enum SelectionItemDataTypeEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = INVALID; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const SelectionItemDataTypeEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type SelectionItemDataTypeEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t SelectionItemDataTypeEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const SelectionItemDataTypeEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ SelectionItemDataTypeEnum::Enum SelectionItemDataTypeEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = INVALID; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const SelectionItemDataTypeEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type SelectionItemDataTypeEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void SelectionItemDataTypeEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void SelectionItemDataTypeEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(SelectionItemDataTypeEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allGuiNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void SelectionItemDataTypeEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(SelectionItemDataTypeEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } workbench-1.1.1/src/Brain/SelectionItemDataTypeEnum.h000066400000000000000000000103011255417355300225210ustar00rootroot00000000000000#ifndef __SELECTION_ITEM_DATA_TYPE_ENUM__H_ #define __SELECTION_ITEM_DATA_TYPE_ENUM__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { /** * \class SelectionItemDataTypeEnum * \brief Enumerated type for selected items * * Enumerated data type for selected items. */ class SelectionItemDataTypeEnum { public: /** * Enumerated values. */ enum Enum { /** Invalid */ INVALID, /** Annotation */ ANNOTATION, /** Border on Surface */ BORDER_SURFACE, /** Border on Volume Slice */ BORDER_VOLUME, /** Data-Series Chart */ CHART_DATA_SERIES, /** Frequency-Series Chart */ CHART_FREQUENCY_SERIES, /** Matrix chart */ CHART_MATRIX, /** Time-Series Chart */ CHART_TIME_SERIES, /** CIFTI Connectivity Matrix Row or Column */ CIFTI_CONNECTIVITY_MATRIX_ROW_COLUMN, /** Focus on Surface */ FOCUS_SURFACE, /** Focus on Volume */ FOCUS_VOLUME, /** Surface Node*/ SURFACE_NODE, /** Surface Node Identification Symbol */ SURFACE_NODE_IDENTIFICATION_SYMBOL, /** Surface Triangle */ SURFACE_TRIANGLE, /** Volume Voxel */ VOXEL, /** Voxel Editing */ VOXEL_EDITING, /** Voxel identification symbol */ VOXEL_IDENTIFICATION_SYMBOL }; ~SelectionItemDataTypeEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); private: SelectionItemDataTypeEnum(const Enum enumValue, const AString& name, const AString& guiName); static const SelectionItemDataTypeEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; #ifdef __SELECTION_ITEM_DATA_TYPE_ENUM_DECLARE__ std::vector SelectionItemDataTypeEnum::enumData; bool SelectionItemDataTypeEnum::initializedFlag = false; int32_t SelectionItemDataTypeEnum::integerCodeCounter = 0; #endif // __SELECTION_ITEM_DATA_TYPE_ENUM_DECLARE__ } // namespace #endif //__SELECTION_ITEM_DATA_TYPE_ENUM__H_ workbench-1.1.1/src/Brain/SelectionItemFocusSurface.cxx000066400000000000000000000113151255417355300231320ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SELECTION_ITEM_FOCUS_SURFACE_DECLARE__ #include "SelectionItemFocusSurface.h" #undef __SELECTION_ITEM_FOCUS_SURFACE_DECLARE__ #include "FociFile.h" #include "Focus.h" #include "Surface.h" using namespace caret; /** * \class SelectionItemFocusSurface * \brief Contains information about the selected focus. */ /** * Constructor. */ SelectionItemFocusSurface::SelectionItemFocusSurface() : SelectionItem(SelectionItemDataTypeEnum::FOCUS_SURFACE) { this->surface = NULL; this->focus = NULL; this->fociFile = NULL; this->focusIndex = -1; this->focusProjectionIndex = -1; } /** * Destructor. */ SelectionItemFocusSurface::~SelectionItemFocusSurface() { } /** * Reset this selection item. */ void SelectionItemFocusSurface::reset() { SelectionItem::reset(); this->surface = NULL; this->focus = NULL; this->fociFile = NULL; this->focusIndex = -1; this->focusProjectionIndex = -1; } /** * @return Is this selected item valid? */ bool SelectionItemFocusSurface::isValid() const { return (this->focus != NULL); } /** * @return Surface on which focus was drawn. */ const Surface* SelectionItemFocusSurface::getSurface() const { return this->surface; } /** * @return Surface on which focus was drawn. */ Surface* SelectionItemFocusSurface::getSurface() { return this->surface; } /** * Set the surface on which focus was drawn. * @param surface * New value for surface. */ void SelectionItemFocusSurface::setSurface(Surface* surface) { this->surface = surface; } /** * @return The focus that was selected. */ const Focus* SelectionItemFocusSurface::getFocus() const { return this->focus; } /** * @return The focus that was selected. */ Focus* SelectionItemFocusSurface::getFocus() { return this->focus; } /** * Set the focus that was selected. * @param focus * New value for focus. */ void SelectionItemFocusSurface::setFocus(Focus* focus) { this->focus = focus; } /** * @return The focus file containing focus that was selected. */ const FociFile* SelectionItemFocusSurface::getFociFile() const { return this->fociFile; } /** * @return The focus file containing focus that was selected. */ FociFile* SelectionItemFocusSurface::getFociFile() { return this->fociFile; } /** * Set the focus file containing focus that was selected. * @param fociFile * New value for focus file. */ void SelectionItemFocusSurface::setFociFile(FociFile* fociFile) { this->fociFile = fociFile; } /** * return Index of selected focus. */ int32_t SelectionItemFocusSurface::getFocusIndex() const { return this->focusIndex; } /** * Set index of selected focus. * @param focusIndex * New value for focus index. */ void SelectionItemFocusSurface::setFocusIndex(const int32_t focusIndex) { this->focusIndex = focusIndex; } /** * return Projection Index of selected focus. */ int32_t SelectionItemFocusSurface::getFocusProjectionIndex() const { return this->focusProjectionIndex; } /** * Set projection index of selected focus. * @param focusProjectionIndex * New value for focus index. */ void SelectionItemFocusSurface::setFocusProjectionIndex(const int32_t focusProjectionIndex) { this->focusProjectionIndex = focusProjectionIndex; } /** * Get a description of m_ object's content. * @return String describing m_ object's content. */ AString SelectionItemFocusSurface::toString() const { AString text = SelectionItem::toString(); text += ("Surface: " + ((surface != NULL) ? surface->getFileNameNoPath() : "INVALID") + "\n"); text += ("Foci File: " + ((fociFile != NULL) ? fociFile->getFileNameNoPath() : "INVALID") + "\n"); text += ("Focus: " + ((focus != NULL) ? focus->getName() : "INVALID") + "\n"); text += ("Focus Index: " + AString::number(focusIndex) + "\n"); text += ("Focus Projection Index: " + AString::number(focusProjectionIndex) + "\n"); return text; } workbench-1.1.1/src/Brain/SelectionItemFocusSurface.h000066400000000000000000000047571255417355300225730ustar00rootroot00000000000000#ifndef __SELECTION_ITEM_FOCUS_SURFACE__H_ #define __SELECTION_ITEM_FOCUS_SURFACE__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "SelectionItem.h" namespace caret { class Focus; class FociFile; class Surface; class SelectionItemFocusSurface : public SelectionItem { public: SelectionItemFocusSurface(); virtual ~SelectionItemFocusSurface(); virtual bool isValid() const; Surface* getSurface(); const Surface* getSurface() const; Focus* getFocus(); const Focus* getFocus() const; void setFocus(Focus* focus); FociFile* getFociFile(); const FociFile* getFociFile() const; void setFociFile(FociFile* fociFile); void setSurface(Surface* surface); int32_t getFocusIndex() const; void setFocusIndex(const int32_t focusIndex); int32_t getFocusProjectionIndex() const; void setFocusProjectionIndex(const int32_t focusIndex); void reset(); virtual AString toString() const; private: SelectionItemFocusSurface(const SelectionItemFocusSurface&); SelectionItemFocusSurface& operator=(const SelectionItemFocusSurface&); Focus* focus; FociFile* fociFile; Surface* surface; int32_t focusIndex; int32_t focusProjectionIndex; }; #ifdef __SELECTION_ITEM_FOCUS_SURFACE_DECLARE__ // #endif // __SELECTION_ITEM_FOCUS_SURFACE_DECLARE__ } // namespace #endif //__SELECTION_ITEM_FOCUS_SURFACE__H_ workbench-1.1.1/src/Brain/SelectionItemFocusVolume.cxx000066400000000000000000000117211255417355300230120ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SELECTION_ITEM_FOCUS_VOLUME_DECLARE__ #include "SelectionItemFocusVolume.h" #undef __SELECTION_ITEM_FOCUS_VOLUME_DECLARE__ #include "Focus.h" #include "FociFile.h" #include "VolumeFile.h" using namespace caret; /** * \class SelectionItemFocusVolume * \brief Contains information about the selected focus. */ /** * Constructor. */ SelectionItemFocusVolume::SelectionItemFocusVolume() : SelectionItem(SelectionItemDataTypeEnum::FOCUS_VOLUME) { this->volumeFile = NULL; this->focus = NULL; this->fociFile = NULL; this->focusIndex = -1; this->focusProjectionIndex = -1; } /** * Destructor. */ SelectionItemFocusVolume::~SelectionItemFocusVolume() { } /** * Reset this selection item. */ void SelectionItemFocusVolume::reset() { SelectionItem::reset(); this->volumeFile = NULL; this->focus = NULL; this->fociFile = NULL; this->focusIndex = -1; this->focusProjectionIndex = -1; } /** * @return Is this selected item valid? */ bool SelectionItemFocusVolume::isValid() const { return (this->focus != NULL); } /** * @return VolumeFile on which focus was drawn. */ const VolumeMappableInterface* SelectionItemFocusVolume::getVolumeFile() const { return this->volumeFile; } /** * @return VolumeFile on which focus was drawn. */ VolumeMappableInterface* SelectionItemFocusVolume::getVolumeFile() { return this->volumeFile; } /** * Set the volume file on which focus was drawn. * @param volumeFile * New value for volumeFile. */ void SelectionItemFocusVolume::setVolumeFile(VolumeMappableInterface* volumeFile) { this->volumeFile = volumeFile; } /** * @return The focus that was selected. */ const Focus* SelectionItemFocusVolume::getFocus() const { return this->focus; } /** * @return The focus that was selected. */ Focus* SelectionItemFocusVolume::getFocus() { return this->focus; } /** * Set the focus that was selected. * @param focus * New value for focus. */ void SelectionItemFocusVolume::setFocus(Focus* focus) { this->focus = focus; } /** * @return The focus file containing focus that was selected. */ const FociFile* SelectionItemFocusVolume::getFociFile() const { return this->fociFile; } /** * @return The focus file containing focus that was selected. */ FociFile* SelectionItemFocusVolume::getFociFile() { return this->fociFile; } /** * Set the focus file containing focus that was selected. * @param fociFile * New value for focus file. */ void SelectionItemFocusVolume::setFociFile(FociFile* fociFile) { this->fociFile = fociFile; } /** * return Index of selected focus. */ int32_t SelectionItemFocusVolume::getFocusIndex() const { return this->focusIndex; } /** * Set index of selected focus. * @param focusIndex * New value for focus index. */ void SelectionItemFocusVolume::setFocusIndex(const int32_t focusIndex) { this->focusIndex = focusIndex; } /** * return Projection Index of selected focus. */ int32_t SelectionItemFocusVolume::getFocusProjectionIndex() const { return this->focusProjectionIndex; } /** * Set projection index of selected focus. * @param focusProjectionIndex * New value for focus index. */ void SelectionItemFocusVolume::setFocusProjectionIndex(const int32_t focusProjectionIndex) { this->focusProjectionIndex = focusProjectionIndex; } /** * Get a description of m_ object's content. * @return String describing m_ object's content. */ AString SelectionItemFocusVolume::toString() const { AString name = "INVALID"; if (volumeFile != NULL) { CaretMappableDataFile* cmdf = dynamic_cast(volumeFile); if (cmdf != NULL) { name = cmdf->getFileNameNoPath(); } } AString text = SelectionItem::toString(); text += ("Volume File: " + name + "\n"); text += ("Foci File: " + ((fociFile != NULL) ? fociFile->getFileNameNoPath() : "INVALID") + "\n"); text += ("Focus: " + ((focus != NULL) ? focus->getName() : "INVALID") + "\n"); text += ("Focus Index: " + AString::number(focusIndex) + "\n"); text += ("Focus Projection Index: " + AString::number(focusProjectionIndex) + "\n"); return text; } workbench-1.1.1/src/Brain/SelectionItemFocusVolume.h000066400000000000000000000051011255417355300224320ustar00rootroot00000000000000#ifndef __SELECTION_ITEM_FOCUS_VOLUME__H_ #define __SELECTION_ITEM_FOCUS_VOLUME__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "SelectionItem.h" namespace caret { class Focus; class FociFile; class VolumeMappableInterface; class SelectionItemFocusVolume : public SelectionItem { public: SelectionItemFocusVolume(); virtual ~SelectionItemFocusVolume(); virtual bool isValid() const; VolumeMappableInterface* getVolumeFile(); const VolumeMappableInterface* getVolumeFile() const; Focus* getFocus(); const Focus* getFocus() const; void setFocus(Focus* focus); FociFile* getFociFile(); const FociFile* getFociFile() const; void setFociFile(FociFile* fociFile); void setVolumeFile(VolumeMappableInterface* volumeFile); int32_t getFocusIndex() const; void setFocusIndex(const int32_t focusIndex); int32_t getFocusProjectionIndex() const; void setFocusProjectionIndex(const int32_t focusIndex); void reset(); virtual AString toString() const; private: SelectionItemFocusVolume(const SelectionItemFocusVolume&); SelectionItemFocusVolume& operator=(const SelectionItemFocusVolume&); Focus* focus; FociFile* fociFile; VolumeMappableInterface* volumeFile; int32_t focusIndex; int32_t focusProjectionIndex; }; #ifdef __SELECTION_ITEM_FOCUS_VOLUME_DECLARE__ // #endif // __SELECTION_ITEM_FOCUS_VOLUME_DECLARE__ } // namespace #endif //__SELECTION_ITEM_FOCUS_VOLUME__H_ workbench-1.1.1/src/Brain/SelectionItemSurfaceNode.cxx000066400000000000000000000106431255417355300227430ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SELECTION_ITEM_SURFACE_NODE_DECLARE__ #include "SelectionItemSurfaceNode.h" #undef __SELECTION_ITEM_SURFACE_NODE_DECLARE__ #include "Surface.h" using namespace caret; /** * \class SelectionItemSurfaceNode * \brief Selected node. * * Information about the selected node. */ /** * Constructor. */ SelectionItemSurfaceNode::SelectionItemSurfaceNode() : SelectionItem(SelectionItemDataTypeEnum::SURFACE_NODE) { m_surface = NULL; m_contralateralFlag = false; m_nodeNumber = -1; } /** * Destructor. */ SelectionItemSurfaceNode::~SelectionItemSurfaceNode() { } /** * Copy constructor. * @param obj * Object that is copied. */ SelectionItemSurfaceNode::SelectionItemSurfaceNode(const SelectionItemSurfaceNode& obj) : SelectionItem(obj) { copyHelperSelectionItemSurfaceNode(obj); } /** * Assignment operator. * @param obj * Data copied from obj to m_. * @return * Reference to m_ object. */ SelectionItemSurfaceNode& SelectionItemSurfaceNode::operator=(const SelectionItemSurfaceNode& obj) { if (this != &obj) { SelectionItem::operator=(obj); copyHelperSelectionItemSurfaceNode(obj); } return *this; } /** * Helps with copying an object of m_ type. * @param ff * Object that is copied. */ void SelectionItemSurfaceNode::copyHelperSelectionItemSurfaceNode(const SelectionItemSurfaceNode& idItem) { m_surface = idItem.m_surface; m_nodeNumber = idItem.m_nodeNumber; m_contralateralFlag = idItem.m_contralateralFlag; } /** * Reset the selection item. */ void SelectionItemSurfaceNode::reset() { SelectionItem::reset(); m_surface = NULL; m_nodeNumber = -1; m_contralateralFlag = false; } /** * @return Is m_ selected item valid? */ bool SelectionItemSurfaceNode::isValid() const { return ((m_surface != NULL) && (m_nodeNumber >= 0)); } /** * @return Surface containing selected node. */ const Surface* SelectionItemSurfaceNode::getSurface() const { return m_surface; } /** * @return Surface containing selected node. */ Surface* SelectionItemSurfaceNode::getSurface() { return m_surface; } /** * Set the surface containing the selected node. * @param surface * New value for surface. * */ void SelectionItemSurfaceNode::setSurface(Surface* surface) { m_surface = surface; } /** * return Number of selected node. */ int32_t SelectionItemSurfaceNode::getNodeNumber() const { return m_nodeNumber; } /** * Set node number that was selected. * @param nodeNumber * New value for node. */ void SelectionItemSurfaceNode::setNodeNumber(const int32_t nodeNumber) { m_nodeNumber = nodeNumber; } ///** // * @return Is m_ a contralateral identification? // */ //bool //SelectionItemSurfaceNode::isContralateral() const //{ // return m_contralateralFlag; //} // ///** // * Set contralateral identification status. // * @param status // * New status. // */ //void //SelectionItemSurfaceNode::setContralateral(const bool status) //{ // m_contralateralFlag = status; //} /** * Get a description of m_ object's content. * @return String describing m_ object's content. */ AString SelectionItemSurfaceNode::toString() const { AString text = SelectionItem::toString(); text += ("Surface: " + ((m_surface != NULL) ? m_surface->getFileNameNoPath() : "INVALID") + "\n"); text += "Vertex: " + AString::number(m_nodeNumber) + "\n"; if (isValid() && (m_surface != NULL)) { text += "Coordinate: " + AString::fromNumbers(m_surface->getCoordinate(m_nodeNumber), 3, ", ") + "\n"; } text += "Contralateral: " + AString::fromBool(m_contralateralFlag) + "\n"; return text; } workbench-1.1.1/src/Brain/SelectionItemSurfaceNode.h000066400000000000000000000043761255417355300223760ustar00rootroot00000000000000#ifndef __SELECTION_ITEM_SURFACE_NODE__H_ #define __SELECTION_ITEM_SURFACE_NODE__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "SelectionItem.h" namespace caret { class Surface; class SelectionItemSurfaceNode : public SelectionItem { public: SelectionItemSurfaceNode(); virtual ~SelectionItemSurfaceNode(); SelectionItemSurfaceNode(const SelectionItemSurfaceNode&); SelectionItemSurfaceNode& operator=(const SelectionItemSurfaceNode&); virtual bool isValid() const; Surface* getSurface(); const Surface* getSurface() const; void setSurface(Surface* surface); int32_t getNodeNumber() const; void setNodeNumber(const int32_t nodeNumber); // bool isContralateral() const; // // void setContralateral(const bool status); // virtual void reset(); virtual AString toString() const; private: void copyHelperSelectionItemSurfaceNode(const SelectionItemSurfaceNode& idItem); public: private: Surface* m_surface; int32_t m_nodeNumber; bool m_contralateralFlag; }; #ifdef __SELECTION_ITEM_SURFACE_NODE_DECLARE__ // #endif // __SELECTION_ITEM_SURFACE_NODE_DECLARE__ } // namespace #endif //__SELECTION_ITEM_SURFACE_NODE__H_ workbench-1.1.1/src/Brain/SelectionItemSurfaceNodeIdentificationSymbol.cxx000066400000000000000000000063231255417355300270030ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SELECTION_ITEM_SURFACE_NODE_IDENTIFICATION_SYMBOL_DECLARE__ #include "SelectionItemSurfaceNodeIdentificationSymbol.h" #undef __SELECTION_ITEM_SURFACE_NODE_IDENTIFICATION_SYMBOL_DECLARE__ #include "Surface.h" using namespace caret; /** * \class SelectionItemSurfaceNodeIdentificationSymbol * \brief Selected node symbol * * Information about the selected node symbol. */ /** * Constructor. */ SelectionItemSurfaceNodeIdentificationSymbol::SelectionItemSurfaceNodeIdentificationSymbol() : SelectionItem(SelectionItemDataTypeEnum::SURFACE_NODE_IDENTIFICATION_SYMBOL) { this->surface = NULL; this->nodeNumber = -1; } /** * Destructor. */ SelectionItemSurfaceNodeIdentificationSymbol::~SelectionItemSurfaceNodeIdentificationSymbol() { } /** * Reset this selection item. */ void SelectionItemSurfaceNodeIdentificationSymbol::reset() { SelectionItem::reset(); this->surface = NULL; this->nodeNumber = -1; } /** * @return Is this selected item valid? */ bool SelectionItemSurfaceNodeIdentificationSymbol::isValid() const { return (this->nodeNumber >= 0); } /** * @return Surface containing selected node. */ const Surface* SelectionItemSurfaceNodeIdentificationSymbol::getSurface() const { return this->surface; } /** * @return Surface containing selected node. */ Surface* SelectionItemSurfaceNodeIdentificationSymbol::getSurface() { return this->surface; } /** * Set the surface containing the selected node. * @param surface * New value for surface. * */ void SelectionItemSurfaceNodeIdentificationSymbol::setSurface(Surface* surface) { this->surface = surface; } /** * return Number of selected node. */ int32_t SelectionItemSurfaceNodeIdentificationSymbol::getNodeNumber() const { return this->nodeNumber; } /** * Set node number that was selected. * @param nodeNumber * New value for node. */ void SelectionItemSurfaceNodeIdentificationSymbol::setNodeNumber(const int32_t nodeNumber) { this->nodeNumber = nodeNumber; } /** * Get a description of this object's content. * @return String describing this object's content. */ AString SelectionItemSurfaceNodeIdentificationSymbol::toString() const { AString text = SelectionItem::toString(); text += ("Surface: " + ((surface != NULL) ? surface->getFileNameNoPath() : "INVALID") + "\n"); text += "Vertex: " + AString::number(this->nodeNumber) + "\n"; return text; } workbench-1.1.1/src/Brain/SelectionItemSurfaceNodeIdentificationSymbol.h000066400000000000000000000043541255417355300264320ustar00rootroot00000000000000#ifndef __SELECTION_ITEM_SURFACE_NODE_IDENTIFICATION_SYMBOL__H_ #define __SELECTION_ITEM_SURFACE_NODE_IDENTIFICATION_SYMBOL__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "SelectionItem.h" namespace caret { class Surface; class SelectionItemSurfaceNodeIdentificationSymbol : public SelectionItem { public: SelectionItemSurfaceNodeIdentificationSymbol(); virtual ~SelectionItemSurfaceNodeIdentificationSymbol(); virtual bool isValid() const; Surface* getSurface(); const Surface* getSurface() const; void setSurface(Surface* surface); int32_t getNodeNumber() const; void setNodeNumber(const int32_t nodeNumber); virtual void reset(); virtual AString toString() const; private: SelectionItemSurfaceNodeIdentificationSymbol(const SelectionItemSurfaceNodeIdentificationSymbol&); SelectionItemSurfaceNodeIdentificationSymbol& operator=(const SelectionItemSurfaceNodeIdentificationSymbol&); public: private: Surface* surface; int32_t nodeNumber; }; #ifdef __SELECTION_ITEM_SURFACE_NODE_IDENTIFICATION_SYMBOL_DECLARE__ // #endif // __SELECTION_ITEM_SURFACE_NODE_IDENTIFICATION_SYMBOL_DECLARE__ } // namespace #endif //__SELECTION_ITEM_SURFACE_NODE_IDENTIFICATION_SYMBOL__H_ workbench-1.1.1/src/Brain/SelectionItemSurfaceTriangle.cxx000066400000000000000000000133401255417355300236200ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SELECTION_ITEM_SURFACE_TRIANGLE_DECLARE__ #include "SelectionItemSurfaceTriangle.h" #undef __SELECTION_ITEM_SURFACE_TRIANGLE_DECLARE__ #include #include "Surface.h" using namespace caret; /** * \class SelectionItemSurfaceTriangle * \brief Selected node. * * Information about the selected node. */ /** * Constructor. */ SelectionItemSurfaceTriangle::SelectionItemSurfaceTriangle() : SelectionItem(SelectionItemDataTypeEnum::SURFACE_TRIANGLE) { this->surface = NULL; this->triangleNumber = -1; this->nearestNodeNumber = -1; this->nearestNodeScreenXYZ[0] = 0.0; this->nearestNodeScreenXYZ[1] = 0.0; this->nearestNodeScreenXYZ[2] = std::numeric_limits::max(); this->nearestNodeModelXYZ[0] = 0.0; this->nearestNodeModelXYZ[1] = 0.0; this->nearestNodeModelXYZ[2] = 0.0; } /** * Destructor. */ SelectionItemSurfaceTriangle::~SelectionItemSurfaceTriangle() { } /** * Reset this selection item. */ void SelectionItemSurfaceTriangle::reset() { SelectionItem::reset(); this->surface = NULL; this->triangleNumber = -1; this->nearestNodeNumber = -1; this->nearestNodeScreenXYZ[0] = 0.0; this->nearestNodeScreenXYZ[1] = 0.0; this->nearestNodeScreenXYZ[2] = std::numeric_limits::max(); this->nearestNodeModelXYZ[0] = 0.0; this->nearestNodeModelXYZ[1] = 0.0; this->nearestNodeModelXYZ[2] = 0.0; } /** * return Is this selected item valid? */ bool SelectionItemSurfaceTriangle::isValid() const { return (this->triangleNumber >= 0); } /** * return Surface containing selected node. */ Surface* SelectionItemSurfaceTriangle::getSurface() { return this->surface; } /** * Set the surface containing the selected node. * @param surface * New value for surface. * */ void SelectionItemSurfaceTriangle::setSurface(Surface* surface) { this->surface = surface; } /** * return Number of selected triangle. */ int32_t SelectionItemSurfaceTriangle::getTriangleNumber() const { return this->triangleNumber; } /** * Set triangle number that was selected. * @param triangleNumber * New value for triangle. */ void SelectionItemSurfaceTriangle::setTriangleNumber(const int32_t triangleNumber) { this->triangleNumber = triangleNumber; } /** * @return Node nearest the mouse click in screen X&Y coordinates. * Will return negative if invalid. */ int32_t SelectionItemSurfaceTriangle::getNearestNodeNumber() const { return this->nearestNodeNumber; } /** * Set the node nearest to the mouse click in screen X&Y coordinates. * @param nearestNodeNumber * New value for the node. */ void SelectionItemSurfaceTriangle::setNearestNode(const int32_t nearestNodeNumber) { this->nearestNodeNumber = nearestNodeNumber; } /** * Get a description of this object's content. * @return String describing this object's content. */ AString SelectionItemSurfaceTriangle::toString() const { AString text = SelectionItem::toString(); text += ("Surface: " + ((surface != NULL) ? surface->getFileNameNoPath() : "INVALID") + "\n"); text += "Triangle: " + AString::number(this->triangleNumber) + "\n"; text += "Nearest Vertex: " + AString::number(this->nearestNodeNumber) + "\n"; if (this->isValid() && (surface != NULL)) { if (this->nearestNodeNumber >= 0) { text += "Coordinate: " + AString::fromNumbers(surface->getCoordinate(this->nearestNodeNumber), 3, ", ") + "\n"; } } return text; } /** * Get the screen XYZ of the nearest node. * @param nearestNodeScreenXYZ * XYZ out. */ void SelectionItemSurfaceTriangle::getNearestNodeScreenXYZ(double nearestNodeScreenXYZ[3]) const { nearestNodeScreenXYZ[0] = this->nearestNodeScreenXYZ[0]; nearestNodeScreenXYZ[1] = this->nearestNodeScreenXYZ[1]; nearestNodeScreenXYZ[2] = this->nearestNodeScreenXYZ[2]; } /** * Set the screen XYZ of the nearest node. * @param nearestNodeScreenXYZ * new XYZ. */ void SelectionItemSurfaceTriangle::setNearestNodeScreenXYZ(const double nearestNodeScreenXYZ[3]) { this->nearestNodeScreenXYZ[0] = nearestNodeScreenXYZ[0]; this->nearestNodeScreenXYZ[1] = nearestNodeScreenXYZ[1]; this->nearestNodeScreenXYZ[2] = nearestNodeScreenXYZ[2]; } /** * Get the model XYZ of the nearest node. * @param nearestNodeModelXYZ * XYZ out. */ void SelectionItemSurfaceTriangle::getNearestNodeModelXYZ(double nearestNodeModelXYZ[3]) const { nearestNodeModelXYZ[0] = this->nearestNodeModelXYZ[0]; nearestNodeModelXYZ[1] = this->nearestNodeModelXYZ[1]; nearestNodeModelXYZ[2] = this->nearestNodeModelXYZ[2]; } /** * Set the model XYZ of the nearest node. * @param nearestNodeModelXYZ * new XYZ. */ void SelectionItemSurfaceTriangle::setNearestNodeModelXYZ(const double nearestNodeModelXYZ[3]) { this->nearestNodeModelXYZ[0] = nearestNodeModelXYZ[0]; this->nearestNodeModelXYZ[1] = nearestNodeModelXYZ[1]; this->nearestNodeModelXYZ[2] = nearestNodeModelXYZ[2]; } workbench-1.1.1/src/Brain/SelectionItemSurfaceTriangle.h000066400000000000000000000050521255417355300232460ustar00rootroot00000000000000#ifndef __SELECTION_ITEM_SURFACE_TRIANGLE__H_ #define __SELECTION_ITEM_SURFACE_TRIANGLE__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "SelectionItem.h" namespace caret { class Surface; class SelectionItemSurfaceTriangle : public SelectionItem { public: SelectionItemSurfaceTriangle(); virtual ~SelectionItemSurfaceTriangle(); virtual bool isValid() const; Surface* getSurface(); void setSurface(Surface* surface); int32_t getTriangleNumber() const; void setTriangleNumber(const int32_t triangleNumber); int32_t getNearestNodeNumber() const; void setNearestNode(const int32_t nearestNodeNumber); void getNearestNodeScreenXYZ(double screenXYZ[3]) const; void setNearestNodeScreenXYZ(const double screenXYZ[3]); void getNearestNodeModelXYZ(double modelXYZ[3]) const; void setNearestNodeModelXYZ(const double modelXYZ[3]); virtual void reset(); virtual AString toString() const; private: SelectionItemSurfaceTriangle(const SelectionItemSurfaceTriangle&); SelectionItemSurfaceTriangle& operator=(const SelectionItemSurfaceTriangle&); private: Surface* surface; int32_t triangleNumber; int32_t nearestNodeNumber; double nearestNodeScreenXYZ[3]; double nearestNodeModelXYZ[3]; }; #ifdef __SELECTION_ITEM_SURFACE_TRIANGLE_DECLARE__ // #endif // __SELECTION_ITEM_SURFACE_TRIANGLE_DECLARE__ } // namespace #endif //__SELECTION_ITEM_SURFACE_TRIANGLE__H_ workbench-1.1.1/src/Brain/SelectionItemVoxel.cxx000066400000000000000000000114741255417355300216450ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SELECTION_ITEM_VOXEL_DECLARE__ #include "SelectionItemVoxel.h" #undef __SELECTION_ITEM_VOXEL_DECLARE__ #include "CaretAssert.h" #include "VolumeFile.h" using namespace caret; /** * \class SelectionItemVoxel * \brief Selected voxel. * * Information about an selected voxel. */ /** * Constructor. */ SelectionItemVoxel::SelectionItemVoxel() : SelectionItem(SelectionItemDataTypeEnum::VOXEL) { /* * Note: reset() is virtual so cannot call from constructor. */ resetPrivate(); } /** * Constructor for child classes. * * @param itemDataType * The selection item data type for child class. */ SelectionItemVoxel::SelectionItemVoxel(const SelectionItemDataTypeEnum::Enum itemDataType) : SelectionItem(itemDataType) { /* * Note: reset() is virtual so cannot call from constructor. */ resetPrivate(); } /** * Destructor. */ SelectionItemVoxel::~SelectionItemVoxel() { } /** * Copy constructor. * @param obj * Object that is copied. */ SelectionItemVoxel::SelectionItemVoxel(const SelectionItemVoxel& obj) : SelectionItem(obj) { copyHelperSelectionItemVoxel(obj); } /** * Assignment operator. * @param obj * Data copied from obj to m_. * @return * Reference to m_ object. */ SelectionItemVoxel& SelectionItemVoxel::operator=(const SelectionItemVoxel& obj) { if (this != &obj) { SelectionItem::operator=(obj); copyHelperSelectionItemVoxel(obj); } return *this; } /** * Helps with copying an object of m_ type. * @param ff * Object that is copied. */ void SelectionItemVoxel::copyHelperSelectionItemVoxel(const SelectionItemVoxel& idItem) { m_volumeFile = idItem.m_volumeFile; m_voxelIJK[0] = idItem.m_voxelIJK[0]; m_voxelIJK[1] = idItem.m_voxelIJK[1]; m_voxelIJK[2] = idItem.m_voxelIJK[2]; } /** * Reset this selection item. */ void SelectionItemVoxel::reset() { SelectionItem::reset(); resetPrivate(); } /** * Reset this selection item. */ void SelectionItemVoxel::resetPrivate() { m_volumeFile = NULL; m_voxelIJK[0] = -1; m_voxelIJK[1] = -1; m_voxelIJK[2] = -1; } /** * @return The volume file. */ const VolumeMappableInterface* SelectionItemVoxel::getVolumeFile() const { return m_volumeFile; } /** * Get the voxel indices. * @param voxelIJK * Output containing voxel indices. */ void SelectionItemVoxel::getVoxelIJK(int64_t voxelIJK[3]) const { voxelIJK[0] = m_voxelIJK[0]; voxelIJK[1] = m_voxelIJK[1]; voxelIJK[2] = m_voxelIJK[2]; } /** * Set the volume file. * * @param brain * Brain containing the volume. * @param volumeFile * New value for volume file. * @param voxelIJK * New value for voxel indices. * @param screenDepth * The screen depth. */ void SelectionItemVoxel::setVoxelIdentification(Brain* brain, VolumeMappableInterface* volumeFile, const int64_t voxelIJK[3], const double screenDepth) { setBrain(brain); m_volumeFile = volumeFile; m_voxelIJK[0] = voxelIJK[0]; m_voxelIJK[1] = voxelIJK[1]; m_voxelIJK[2] = voxelIJK[2]; setScreenDepth(screenDepth); } /** * @return Is this selected item valid? */ bool SelectionItemVoxel::isValid() const { return (m_volumeFile != NULL); } /** * Get a description of this object's content. * @return String describing this object's content. */ AString SelectionItemVoxel::toString() const { AString text = SelectionItem::toString(); AString name = "INVALID"; if (m_volumeFile != NULL) { CaretMappableDataFile* cmdf = dynamic_cast(m_volumeFile); if (cmdf != NULL) { name = cmdf->getFileNameNoPath(); } } text += ("Volume: " + name); text += ("Voxel: " + AString::number(m_voxelIJK[0]) + ", " + AString::number(m_voxelIJK[1]) + ", " + AString::number(m_voxelIJK[2]) + "\n"); return text; } workbench-1.1.1/src/Brain/SelectionItemVoxel.h000066400000000000000000000044621255417355300212710ustar00rootroot00000000000000#ifndef __SELECTION_ITEM_VOXEL__H_ #define __SELECTION_ITEM_VOXEL__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "SelectionItem.h" namespace caret { class VolumeMappableInterface; class SelectionItemVoxel : public SelectionItem { public: SelectionItemVoxel(); virtual ~SelectionItemVoxel(); SelectionItemVoxel(const SelectionItemVoxel&); SelectionItemVoxel& operator=(const SelectionItemVoxel&); virtual bool isValid() const; const VolumeMappableInterface* getVolumeFile() const; void getVoxelIJK(int64_t voxelIJK[3]) const; void setVoxelIdentification(Brain* brain, VolumeMappableInterface* volumeFile, const int64_t voxelIJK[3], const double screenDepth); virtual void reset(); virtual AString toString() const; protected: SelectionItemVoxel(const SelectionItemDataTypeEnum::Enum itemDataType); private: void copyHelperSelectionItemVoxel(const SelectionItemVoxel& idItem); void resetPrivate(); VolumeMappableInterface* m_volumeFile; int64_t m_voxelIJK[3]; }; #ifdef __SELECTION_ITEM_VOXEL_DECLARE__ // #endif // __SELECTION_ITEM_VOXEL_DECLARE__ } // namespace #endif //__SELECTION_ITEM_VOXEL__H_ workbench-1.1.1/src/Brain/SelectionItemVoxelEditing.cxx000066400000000000000000000060251255417355300231450ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SELECTION_ITEM_VOXEL_EDITING_DECLARE__ #include "SelectionItemVoxelEditing.h" #undef __SELECTION_ITEM_VOXEL_EDITING_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::SelectionItemVoxelEditing * \brief Information about selected voxel used in volume editing. * \ingroup Brain * * Reports selection of any voxel, even those that are * not displayed. This allows the user to select "off" voxels * so that they can be turned on when editing a volume. */ /** * Constructor. */ SelectionItemVoxelEditing::SelectionItemVoxelEditing() : SelectionItemVoxel(SelectionItemDataTypeEnum::VOXEL_EDITING) { m_volumeFileForEditing = NULL; } /** * Destructor. */ SelectionItemVoxelEditing::~SelectionItemVoxelEditing() { } /** * @return Volume file that is being edited. */ const VolumeFile* SelectionItemVoxelEditing::getVolumeFileForEditing() const { return m_volumeFileForEditing; } /** * @return Volume file that is being edited. */ VolumeFile* SelectionItemVoxelEditing::getVolumeFileForEditing() { return m_volumeFileForEditing; } /** * Set the volume file that is being edited. * * @param volumeFile * Volume file that is being edited. */ void SelectionItemVoxelEditing::setVolumeFileForEditing(VolumeFile* volumeFile) { m_volumeFileForEditing = volumeFile; } /** * Reset this selection item. */ void SelectionItemVoxelEditing::reset() { SelectionItemVoxel::reset(); m_volumeFileForEditing = NULL; } /** * Set the voxel diff XYZ (bottom left to top right * of screen drawing). * * @param voxelDiffXYZ * Difference from voxel bottom left to top right on screen. */ void SelectionItemVoxelEditing::setVoxelDiffXYZ(const float voxelDiffXYZ[3]) { m_voxelDiffXYZ[0] = voxelDiffXYZ[0]; m_voxelDiffXYZ[1] = voxelDiffXYZ[1]; m_voxelDiffXYZ[2] = voxelDiffXYZ[2]; } /** * Get the voxel diff XYZ (bottom left to top right * of screen drawing). * * @param voxelDiffXYZ * Difference from voxel bottom left to top right on screen. */ void SelectionItemVoxelEditing::getVoxelDiffXYZ(float voxelDiffXYZ[3]) const { voxelDiffXYZ[0] = m_voxelDiffXYZ[0]; voxelDiffXYZ[1] = m_voxelDiffXYZ[1]; voxelDiffXYZ[2] = m_voxelDiffXYZ[2]; } workbench-1.1.1/src/Brain/SelectionItemVoxelEditing.h000066400000000000000000000041101255417355300225630ustar00rootroot00000000000000#ifndef __SELECTION_ITEM_VOXEL_EDITING_H__ #define __SELECTION_ITEM_VOXEL_EDITING_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "SelectionItemVoxel.h" namespace caret { class VolumeFile; class SelectionItemVoxelEditing : public SelectionItemVoxel { public: SelectionItemVoxelEditing(); virtual ~SelectionItemVoxelEditing(); const VolumeFile* getVolumeFileForEditing() const; VolumeFile* getVolumeFileForEditing(); void setVolumeFileForEditing(VolumeFile* volumeFile); void setVoxelDiffXYZ(const float voxelDiffXYZ[3]); void getVoxelDiffXYZ(float voxelDiffXYZ[3]) const; virtual void reset(); // ADD_NEW_METHODS_HERE private: SelectionItemVoxelEditing(const SelectionItemVoxelEditing&); SelectionItemVoxelEditing& operator=(const SelectionItemVoxelEditing&); VolumeFile* m_volumeFileForEditing; float m_voxelDiffXYZ[3]; // ADD_NEW_MEMBERS_HERE }; #ifdef __SELECTION_ITEM_VOXEL_EDITING_DECLARE__ // #endif // __SELECTION_ITEM_VOXEL_EDITING_DECLARE__ } // namespace #endif //__SELECTION_ITEM_VOXEL_EDITING_H__ workbench-1.1.1/src/Brain/SelectionItemVoxelIdentificationSymbol.cxx000066400000000000000000000103141255417355300256750ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SELECTION_ITEM_VOXEL_IDENTIFICATION_SYMBOL_DECLARE__ #include "SelectionItemVoxelIdentificationSymbol.h" #undef __SELECTION_ITEM_VOXEL_IDENTIFICATION_SYMBOL_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::SelectionItemVoxelIdentificationSymbol * \brief * \ingroup Brain * * */ /** * Constructor. */ SelectionItemVoxelIdentificationSymbol::SelectionItemVoxelIdentificationSymbol() : SelectionItem(SelectionItemDataTypeEnum::VOXEL_IDENTIFICATION_SYMBOL) { /* * Note: reset() is virtual so cannot call from constructor. */ resetPrivate(); } /** * Destructor. */ SelectionItemVoxelIdentificationSymbol::~SelectionItemVoxelIdentificationSymbol() { } /** * Copy constructor. * @param obj * Object that is copied. */ SelectionItemVoxelIdentificationSymbol::SelectionItemVoxelIdentificationSymbol(const SelectionItemVoxelIdentificationSymbol& obj) : SelectionItem(obj) { this->copyHelperSelectionItemVoxelIdentificationSymbol(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ SelectionItemVoxelIdentificationSymbol& SelectionItemVoxelIdentificationSymbol::operator=(const SelectionItemVoxelIdentificationSymbol& obj) { if (this != &obj) { SelectionItem::operator=(obj); this->copyHelperSelectionItemVoxelIdentificationSymbol(obj); } return *this; } /** * Helps with copying an object of this type. * @param obj * Object that is copied. */ void SelectionItemVoxelIdentificationSymbol::copyHelperSelectionItemVoxelIdentificationSymbol(const SelectionItemVoxelIdentificationSymbol& obj) { m_voxelXYZ[0] = obj.m_voxelXYZ[0]; m_voxelXYZ[1] = obj.m_voxelXYZ[1]; m_voxelXYZ[2] = obj.m_voxelXYZ[2]; m_voxelValid = obj.m_voxelValid; } /** * Get the coordinates of the voxel identification symbol. * * @param xyzOut * Coordinates of the voxel identification symbol. */ void SelectionItemVoxelIdentificationSymbol::getVoxelXYZ(float xyzOut[3]) const { xyzOut[0] = m_voxelXYZ[0]; xyzOut[1] = m_voxelXYZ[1]; xyzOut[2] = m_voxelXYZ[2]; } /** * Set the coordinates of the voxel identification symbol. * * @param xyzOut * Coordinates of the voxel identification symbol. */ void SelectionItemVoxelIdentificationSymbol::setVoxelXYZ(const float xyz[3]) { m_voxelXYZ[0] = xyz[0]; m_voxelXYZ[1] = xyz[1]; m_voxelXYZ[2] = xyz[2]; m_voxelValid = true; } /** * Reset this selection item. */ void SelectionItemVoxelIdentificationSymbol::reset() { SelectionItem::reset(); resetPrivate(); } /** * Reset this items data. */ void SelectionItemVoxelIdentificationSymbol::resetPrivate() { m_voxelValid = false; m_voxelXYZ[0] = 0.0; m_voxelXYZ[1] = 0.0; m_voxelXYZ[2] = 0.0; } /** * @return Is this selected item valid? */ bool SelectionItemVoxelIdentificationSymbol::isValid() const { return m_voxelValid; } /** * Get a description of this object's content. * @return String describing this object's content. */ AString SelectionItemVoxelIdentificationSymbol::toString() const { AString text = SelectionItem::toString(); text += ("Voxel XYZ: " + AString::fromNumbers(m_voxelXYZ, 3, ", ") + "\n" + "Valid: " + AString::fromBool(m_voxelValid) + "\n"); return text; } workbench-1.1.1/src/Brain/SelectionItemVoxelIdentificationSymbol.h000066400000000000000000000043311255417355300253240ustar00rootroot00000000000000#ifndef __SELECTION_ITEM_VOXEL_IDENTIFICATION_SYMBOL_H__ #define __SELECTION_ITEM_VOXEL_IDENTIFICATION_SYMBOL_H__ /*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "SelectionItem.h" namespace caret { class SelectionItemVoxelIdentificationSymbol : public SelectionItem { public: SelectionItemVoxelIdentificationSymbol(); virtual ~SelectionItemVoxelIdentificationSymbol(); SelectionItemVoxelIdentificationSymbol(const SelectionItemVoxelIdentificationSymbol& obj); SelectionItemVoxelIdentificationSymbol& operator=(const SelectionItemVoxelIdentificationSymbol& obj); void getVoxelXYZ(float xyzOut[3]) const; void setVoxelXYZ(const float xyz[3]); virtual bool isValid() const; virtual void reset(); virtual AString toString() const; // ADD_NEW_METHODS_HERE private: void copyHelperSelectionItemVoxelIdentificationSymbol(const SelectionItemVoxelIdentificationSymbol& obj); void resetPrivate(); float m_voxelXYZ[3]; bool m_voxelValid; // ADD_NEW_MEMBERS_HERE }; #ifdef __SELECTION_ITEM_VOXEL_IDENTIFICATION_SYMBOL_DECLARE__ // #endif // __SELECTION_ITEM_VOXEL_IDENTIFICATION_SYMBOL_DECLARE__ } // namespace #endif //__SELECTION_ITEM_VOXEL_IDENTIFICATION_SYMBOL_H__ workbench-1.1.1/src/Brain/SelectionManager.cxx000066400000000000000000000522431255417355300213020ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretOpenGLInclude.h" #include #include #include "BrainConstants.h" #include "CaretAssert.h" #include "CaretLogger.h" #define __SELECTION_MANAGER_DECLARE__ #include "SelectionManager.h" #undef __SELECTION_MANAGER_DECLARE__ #include "SelectionItemBorderSurface.h" #include "SelectionItemChartDataSeries.h" #include "SelectionItemChartFrequencySeries.h" #include "SelectionItemChartMatrix.h" #include "SelectionItemChartTimeSeries.h" #include "SelectionItemCiftiConnectivityMatrixRowColumn.h" #include "SelectionItemFocusSurface.h" #include "SelectionItemFocusVolume.h" #include "SelectionItemSurfaceNode.h" #include "SelectionItemSurfaceNodeIdentificationSymbol.h" #include "SelectionItemSurfaceTriangle.h" #include "SelectionItemVoxel.h" #include "SelectionItemVoxelEditing.h" #include "SelectionItemVoxelIdentificationSymbol.h" #include "IdentificationTextGenerator.h" #include "Surface.h" using namespace caret; /** * \class SelectionManager * \brief Manages identification. * * Manages identification. */ /** * Constructor. */ SelectionManager::SelectionManager() : CaretObject() { m_surfaceBorderIdentification = new SelectionItemBorderSurface(); m_chartDataSeriesIdentification = new SelectionItemChartDataSeries(); m_chartDataFrequencyIdentification = new SelectionItemChartFrequencySeries(); m_chartMatrixIdentification = new SelectionItemChartMatrix(); m_ciftiConnectivityMatrixRowColumnIdentfication = new SelectionItemCiftiConnectivityMatrixRowColumn(); m_chartTimeSeriesIdentification = new SelectionItemChartTimeSeries(); m_surfaceFocusIdentification = new SelectionItemFocusSurface(); m_volumeFocusIdentification = new SelectionItemFocusVolume(); m_surfaceNodeIdentification = new SelectionItemSurfaceNode(); m_surfaceNodeIdentificationSymbol = new SelectionItemSurfaceNodeIdentificationSymbol(); m_surfaceTriangleIdentification = new SelectionItemSurfaceTriangle(); m_voxelIdentification = new SelectionItemVoxel(); m_voxelIdentificationSymbol = new SelectionItemVoxelIdentificationSymbol(); m_voxelEditingIdentification = new SelectionItemVoxelEditing(); m_allSelectionItems.push_back(m_surfaceBorderIdentification); m_allSelectionItems.push_back(m_chartDataSeriesIdentification); m_allSelectionItems.push_back(m_chartDataFrequencyIdentification); m_allSelectionItems.push_back(m_chartMatrixIdentification); m_allSelectionItems.push_back(m_chartTimeSeriesIdentification); m_allSelectionItems.push_back(m_ciftiConnectivityMatrixRowColumnIdentfication); m_allSelectionItems.push_back(m_surfaceFocusIdentification); m_allSelectionItems.push_back(m_surfaceNodeIdentification); m_allSelectionItems.push_back(m_surfaceNodeIdentificationSymbol); m_allSelectionItems.push_back(m_surfaceTriangleIdentification); m_allSelectionItems.push_back(m_voxelIdentification); m_allSelectionItems.push_back(m_voxelIdentificationSymbol); m_allSelectionItems.push_back(m_voxelEditingIdentification); m_allSelectionItems.push_back(m_volumeFocusIdentification); m_surfaceSelectedItems.push_back(m_surfaceNodeIdentification); m_surfaceSelectedItems.push_back(m_surfaceTriangleIdentification); m_layeredSelectedItems.push_back(m_surfaceBorderIdentification); m_layeredSelectedItems.push_back(m_surfaceFocusIdentification); m_volumeSelectedItems.push_back(m_voxelIdentification); m_volumeSelectedItems.push_back(m_voxelEditingIdentification); m_volumeSelectedItems.push_back(m_volumeFocusIdentification); m_idTextGenerator = new IdentificationTextGenerator(); m_lastSelectedItem = NULL; reset(); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_IDENTIFICATION_SYMBOL_REMOVAL); } /** * Destructor. */ SelectionManager::~SelectionManager() { reset(); delete m_surfaceBorderIdentification; m_surfaceBorderIdentification = NULL; delete m_chartDataSeriesIdentification; m_chartDataSeriesIdentification = NULL; delete m_chartDataFrequencyIdentification; m_chartDataFrequencyIdentification = NULL; delete m_chartMatrixIdentification; m_chartMatrixIdentification = NULL; delete m_chartTimeSeriesIdentification; m_chartTimeSeriesIdentification = NULL; delete m_ciftiConnectivityMatrixRowColumnIdentfication; m_ciftiConnectivityMatrixRowColumnIdentfication = NULL; delete m_surfaceFocusIdentification; m_surfaceFocusIdentification = NULL; delete m_surfaceNodeIdentification; m_surfaceNodeIdentification = NULL; delete m_surfaceNodeIdentificationSymbol; m_surfaceNodeIdentificationSymbol = NULL; delete m_surfaceTriangleIdentification; m_surfaceTriangleIdentification = NULL; delete m_voxelIdentification; m_voxelIdentification = NULL; delete m_voxelEditingIdentification; m_voxelEditingIdentification = NULL; delete m_voxelIdentificationSymbol; m_voxelIdentificationSymbol = NULL; delete m_volumeFocusIdentification; m_volumeFocusIdentification = NULL; delete m_idTextGenerator; m_idTextGenerator = NULL; if (m_lastSelectedItem != NULL) { delete m_lastSelectedItem; } EventManager::get()->removeAllEventsFromListener(this); } /** * Receive events from the event manager. * * @param event * The event. */ void SelectionManager::receiveEvent(Event* /*event*/) { } /** * Filter selections to arbitrate between triangle/node * and to remove any selections behind another selection. * * @param applySelectionBackgroundFiltering * If true (which is in most cases), if there are multiple items * selected, those items "behind" other items are not reported. * For example, suppose a focus is selected and there is a node * the focus. If this parameter is true, the node will NOT be * selected. If this parameter is false, the node will be * selected. */ void SelectionManager::filterSelections(const bool applySelectionBackgroundFiltering) { AString logText; for (std::vector::iterator iter = m_allSelectionItems.begin(); iter != m_allSelectionItems.end(); iter++) { SelectionItem* item = *iter; if (item->isValid()) { logText += ("\n" + item->toString() + "\n"); } } CaretLogFine("Selected Items BEFORE filtering: " + logText); SelectionItemSurfaceTriangle* triangleID = m_surfaceTriangleIdentification; SelectionItemSurfaceNode* nodeID = m_surfaceNodeIdentification; // // If both a node and triangle are found // if ((nodeID->getNodeNumber() >= 0) && (triangleID->getTriangleNumber() >= 0)) { // // Is node further from user than triangle? // double depthDiff = m_surfaceNodeIdentification->getScreenDepth() - triangleID->getScreenDepth(); if (depthDiff > 0.00001) { // // Do not use node // m_surfaceNodeIdentification->reset(); } } // // Have a triangle ? // const int32_t triangleNumber = triangleID->getTriangleNumber(); if (triangleNumber >= 0) { // // If no node, use node in nearest triangle // if (m_surfaceNodeIdentification->getNodeNumber() < 0) { const int32_t nearestNode = triangleID->getNearestNodeNumber(); if (nearestNode >= 0) { CaretLogFine("Switched vertex to triangle nearest vertex ." + AString::number(nearestNode)); nodeID->setNodeNumber(nearestNode); nodeID->setScreenDepth(triangleID->getScreenDepth()); nodeID->setSurface(triangleID->getSurface()); double xyz[3]; triangleID->getNearestNodeScreenXYZ(xyz); nodeID->setScreenXYZ(xyz); triangleID->getNearestNodeModelXYZ(xyz); nodeID->setModelXYZ(xyz); nodeID->setBrain(triangleID->getBrain()); } } } /* * See if node identification symbol is too far from selected node. * This may occur if the symbol is on the other side of the surface. */ if ((m_surfaceNodeIdentificationSymbol->getNodeNumber() >= 0) && (m_surfaceNodeIdentification->getNodeNumber() >= 0)) { const double depthDiff = (m_surfaceNodeIdentificationSymbol->getScreenDepth() - m_surfaceNodeIdentification->getScreenDepth()); if (depthDiff > 0.01) { m_surfaceNodeIdentificationSymbol->reset(); } else { m_surfaceNodeIdentification->reset(); } } if (m_voxelIdentificationSymbol->isValid() && m_voxelIdentification->isValid()) { const double depthDiff = (m_voxelIdentificationSymbol->getScreenDepth() - m_voxelIdentification->getScreenDepth()); if (depthDiff > 0.01) { m_voxelIdentificationSymbol->reset(); } else { m_voxelIdentification->reset(); } } if (applySelectionBackgroundFiltering) { clearDistantSelections(); } logText = ""; for (std::vector::iterator iter = m_allSelectionItems.begin(); iter != m_allSelectionItems.end(); iter++) { SelectionItem* item = *iter; if (item->isValid()) { logText += ("\n" + item->toString() + "\n"); } } CaretLogFine("Selected Items AFTER filtering: " + logText); } /** * Examine the selection groups and manipulate them * so that there are not items selected in more * than one group. */ void SelectionManager::clearDistantSelections() { std::vector* > itemGroups; /* * Make layers items slightly closer since they are * often pasted onto the surface. */ for (std::vector::iterator iter = m_layeredSelectedItems.begin(); iter != m_layeredSelectedItems.end(); iter++) { SelectionItem* item = *iter; item->setScreenDepth(item->getScreenDepth()* 0.99); } itemGroups.push_back(&m_layeredSelectedItems); itemGroups.push_back(&m_surfaceSelectedItems); itemGroups.push_back(&m_volumeSelectedItems); std::vector* minDepthGroup = NULL; double minDepth = std::numeric_limits::max(); for (std::vector* >::iterator iter = itemGroups.begin(); iter != itemGroups.end(); iter++) { std::vector* group = *iter; SelectionItem* minDepthItem = getMinimumDepthFromMultipleSelections(*group); if (minDepthItem != NULL) { double md = minDepthItem->getScreenDepth(); if (md < minDepth) { minDepthGroup = group; minDepth = md; } } } if (minDepthGroup != NULL) { for (std::vector* >::iterator iter = itemGroups.begin(); iter != itemGroups.end(); iter++) { std::vector* group = *iter; if (group != minDepthGroup) { for (std::vector::iterator iter = group->begin(); iter != group->end(); iter++) { SelectionItem* item = *iter; item->reset(); } } } } } /** * Reset all selected items except for the given selected item. * @param selectedItem * SelectedItem that is NOT reset. */ void SelectionManager::clearOtherSelectedItems(SelectionItem* selectedItem) { for (std::vector::iterator iter = m_allSelectionItems.begin(); iter != m_allSelectionItems.end(); iter++) { SelectionItem* item = *iter; if (item != selectedItem) { item->reset(); } } } /** * From the list of selectable items, find the item with the * minimum depth. * @param items List of selectable items. * @return Reference to selectable item with the minimum depth * or NULL if no valid selectable items in the list. */ SelectionItem* SelectionManager::getMinimumDepthFromMultipleSelections(std::vector items) const { double minDepth = std::numeric_limits::max(); SelectionItem* minDepthItem = NULL; for (std::vector::iterator iter = items.begin(); iter != items.end(); iter++) { SelectionItem* item = *iter; if (item->isValid()) { if (item->getScreenDepth() < minDepth) { minDepthItem = item; minDepth = item->getScreenDepth(); } } } return minDepthItem; } /** * Set the enabled status for all selection items. * * @param status * New status for ALL selection items. */ void SelectionManager::setAllSelectionsEnabled(const bool status) { for (std::vector::iterator iter = m_allSelectionItems.begin(); iter != m_allSelectionItems.end(); iter++) { SelectionItem* item = *iter; item->setEnabledForSelection(status); } } /** * Get text describing the current identification data. * @param brain * Brain containing the data. */ AString SelectionManager::getIdentificationText(const Brain* brain) const { CaretAssert(brain); const AString text = m_idTextGenerator->createIdentificationText(this, brain); return text; } /** * Reset all identification. */ void SelectionManager::reset() { for (std::vector::iterator iter = m_allSelectionItems.begin(); iter != m_allSelectionItems.end(); iter++) { SelectionItem* item = *iter; item->reset(); } } /** * @return Identification for surface node. */ SelectionItemSurfaceNode* SelectionManager::getSurfaceNodeIdentification() { return m_surfaceNodeIdentification; } /** * @return Identification for surface node. */ const SelectionItemSurfaceNode* SelectionManager::getSurfaceNodeIdentification() const { return m_surfaceNodeIdentification; } /** * @return Identification for surface node. */ const SelectionItemSurfaceNodeIdentificationSymbol* SelectionManager::getSurfaceNodeIdentificationSymbol() const { return m_surfaceNodeIdentificationSymbol; } /** * @return Identification for surface node. */ SelectionItemSurfaceNodeIdentificationSymbol* SelectionManager::getSurfaceNodeIdentificationSymbol() { return m_surfaceNodeIdentificationSymbol; } /** * @return Identification for surface triangle. */ SelectionItemSurfaceTriangle* SelectionManager::getSurfaceTriangleIdentification() { return m_surfaceTriangleIdentification; } /** * @return Identification for surface triangle. */ const SelectionItemSurfaceTriangle* SelectionManager::getSurfaceTriangleIdentification() const { return m_surfaceTriangleIdentification; } /** * @return Identification for voxels. */ const SelectionItemVoxel* SelectionManager::getVoxelIdentification() const { return m_voxelIdentification; } /** * @return Identification for voxels. */ SelectionItemVoxel* SelectionManager::getVoxelIdentification() { return m_voxelIdentification; } /** * @return Identification for voxel identification system. */ const SelectionItemVoxelIdentificationSymbol* SelectionManager::getVoxelIdentificationSymbol() const { return m_voxelIdentificationSymbol; } /** * @return Identification for voxels. */ SelectionItemVoxelIdentificationSymbol* SelectionManager::getVoxelIdentificationSymbol() { return m_voxelIdentificationSymbol; } /** * @return Identification for voxel editing. */ const SelectionItemVoxelEditing* SelectionManager::getVoxelEditingIdentification() const { return m_voxelEditingIdentification; } /** * @return Identification for voxel editing. */ SelectionItemVoxelEditing* SelectionManager::getVoxelEditingIdentification() { return m_voxelEditingIdentification; } /** * @return Identification for borders. */ SelectionItemBorderSurface* SelectionManager::getSurfaceBorderIdentification() { return m_surfaceBorderIdentification; } /** * @return Identification for borders. */ const SelectionItemBorderSurface* SelectionManager::getSurfaceBorderIdentification() const { return m_surfaceBorderIdentification; } /** * @return Identification for foci. */ SelectionItemFocusSurface* SelectionManager::getSurfaceFocusIdentification() { return m_surfaceFocusIdentification; } /** * @return Identification for foci. */ const SelectionItemFocusSurface* SelectionManager::getSurfaceFocusIdentification() const { return m_surfaceFocusIdentification; } /** * @return Identification for foci. */ SelectionItemFocusVolume* SelectionManager::getVolumeFocusIdentification() { return m_volumeFocusIdentification; } /** * @return Identification for foci. */ const SelectionItemFocusVolume* SelectionManager::getVolumeFocusIdentification() const { return m_volumeFocusIdentification; } /** * @return Identification for data-series chart. */ SelectionItemChartDataSeries* SelectionManager::getChartDataSeriesIdentification() { return m_chartDataSeriesIdentification; } /** * @return Identification for data-series chart (const method). */ const SelectionItemChartDataSeries* SelectionManager::getChartDataSeriesIdentification() const { return m_chartDataSeriesIdentification; } /** * @return Identification for frequency-series chart. */ SelectionItemChartFrequencySeries* SelectionManager::getChartFrequencySeriesIdentification() { return m_chartDataFrequencyIdentification; } /** * @return Identification for frequency-series chart (const method). */ const SelectionItemChartFrequencySeries* SelectionManager::getChartFrequencySeriesIdentification() const { return m_chartDataFrequencyIdentification; } /** * @return Identification for matrix chart. */ SelectionItemChartMatrix* SelectionManager::getChartMatrixIdentification() { return m_chartMatrixIdentification; } /** * @return Identification for matrix chart (const method) */ const SelectionItemChartMatrix* SelectionManager::getChartMatrixIdentification() const { return m_chartMatrixIdentification; } /** * @return Identification for CIFTI Connectivity Matrix Row/Column. */ SelectionItemCiftiConnectivityMatrixRowColumn* SelectionManager::getCiftiConnectivityMatrixRowColumnIdentification() { return m_ciftiConnectivityMatrixRowColumnIdentfication; } /** * @return Identification for CIFTI Connectivity Matrix Row/Column. */ const SelectionItemCiftiConnectivityMatrixRowColumn* SelectionManager::getCiftiConnectivityMatrixRowColumnIdentification() const { return m_ciftiConnectivityMatrixRowColumnIdentfication; } /** * @return Identification for time-series chart. */ SelectionItemChartTimeSeries* SelectionManager::getChartTimeSeriesIdentification() { return m_chartTimeSeriesIdentification; } /** * @return Identification for time-series chart (const method). */ const SelectionItemChartTimeSeries* SelectionManager::getChartTimeSeriesIdentification() const { return m_chartTimeSeriesIdentification; } /** * Get a description of this object's content. * @return String describing this object's content. */ AString SelectionManager::toString() const { return "SelectionManager"; } /** * @return The last selected item (may be NULL). */ const SelectionItem* SelectionManager::getLastSelectedItem() const { return m_lastSelectedItem; } /** * Set the last selected item to the given item. * * @param lastItem * The last item that was selected; */ void SelectionManager::setLastSelectedItem(const SelectionItem* lastItem) { if (m_lastSelectedItem != NULL) { delete m_lastSelectedItem; } m_lastSelectedItem = NULL; if (lastItem != NULL) { const SelectionItemSurfaceNode* nodeID = dynamic_cast(lastItem); const SelectionItemVoxel* voxelID = dynamic_cast(lastItem); if (nodeID != NULL) { m_lastSelectedItem = new SelectionItemSurfaceNode(*nodeID); } else if (voxelID != NULL) { m_lastSelectedItem = new SelectionItemVoxel(*voxelID); } else { CaretAssertMessage(0, ("Unsupported last ID type" + lastItem->toString())); } } } workbench-1.1.1/src/Brain/SelectionManager.h000066400000000000000000000160351255417355300207260ustar00rootroot00000000000000#ifndef __SELECTION_MANAGER__H_ #define __SELECTION_MANAGER__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "EventListenerInterface.h" namespace caret { class Brain; class BrowserTabContent; class SelectionItem; class SelectionItemBorderSurface; class SelectionItemChartDataSeries; class SelectionItemChartFrequencySeries; class SelectionItemChartMatrix; class SelectionItemChartTimeSeries; class SelectionItemCiftiConnectivityMatrixRowColumn; class SelectionItemFocusSurface; class SelectionItemFocusVolume; class SelectionItemSurfaceNode; class SelectionItemSurfaceNodeIdentificationSymbol; class SelectionItemSurfaceTriangle; class SelectionItemVoxel; class SelectionItemVoxelEditing; class SelectionItemVoxelIdentificationSymbol; class IdentificationTextGenerator; class Surface; class SelectionManager : public CaretObject, public EventListenerInterface { public: SelectionManager(); virtual ~SelectionManager(); void receiveEvent(Event* event); void reset(); SelectionItemBorderSurface* getSurfaceBorderIdentification(); const SelectionItemBorderSurface* getSurfaceBorderIdentification() const; SelectionItemFocusSurface* getSurfaceFocusIdentification(); const SelectionItemFocusSurface* getSurfaceFocusIdentification() const; SelectionItemFocusVolume* getVolumeFocusIdentification(); const SelectionItemFocusVolume* getVolumeFocusIdentification() const; SelectionItemSurfaceNode* getSurfaceNodeIdentification(); const SelectionItemSurfaceNode* getSurfaceNodeIdentification() const; SelectionItemSurfaceNodeIdentificationSymbol* getSurfaceNodeIdentificationSymbol(); const SelectionItemSurfaceNodeIdentificationSymbol* getSurfaceNodeIdentificationSymbol() const; SelectionItemSurfaceTriangle* getSurfaceTriangleIdentification(); const SelectionItemSurfaceTriangle* getSurfaceTriangleIdentification() const; const SelectionItemVoxel* getVoxelIdentification() const; SelectionItemVoxel* getVoxelIdentification(); SelectionItemVoxelIdentificationSymbol* getVoxelIdentificationSymbol(); const SelectionItemVoxelIdentificationSymbol* getVoxelIdentificationSymbol() const; SelectionItemVoxelEditing* getVoxelEditingIdentification(); const SelectionItemVoxelEditing* getVoxelEditingIdentification() const; SelectionItemChartDataSeries* getChartDataSeriesIdentification(); const SelectionItemChartDataSeries* getChartDataSeriesIdentification() const; SelectionItemChartFrequencySeries* getChartFrequencySeriesIdentification(); const SelectionItemChartFrequencySeries* getChartFrequencySeriesIdentification() const; SelectionItemChartMatrix* getChartMatrixIdentification(); const SelectionItemChartMatrix* getChartMatrixIdentification() const; SelectionItemChartTimeSeries* getChartTimeSeriesIdentification(); const SelectionItemChartTimeSeries* getChartTimeSeriesIdentification() const; SelectionItemCiftiConnectivityMatrixRowColumn* getCiftiConnectivityMatrixRowColumnIdentification(); const SelectionItemCiftiConnectivityMatrixRowColumn* getCiftiConnectivityMatrixRowColumnIdentification() const; AString getIdentificationText(const Brain* brain) const; void filterSelections(const bool applySelectionBackgroundFiltering); void clearDistantSelections(); void clearOtherSelectedItems(SelectionItem* selectedItem); const SelectionItem* getLastSelectedItem() const; void setLastSelectedItem(const SelectionItem* lastItem); void setAllSelectionsEnabled(const bool status); private: SelectionManager(const SelectionManager&); SelectionManager& operator=(const SelectionManager&); public: virtual AString toString() const; private: SelectionItem* getMinimumDepthFromMultipleSelections(std::vector items) const; /** ALL items */ std::vector m_allSelectionItems; /** Layered items (foci, borders, etc.) */ std::vector m_layeredSelectedItems; /** Surface items (nodes, triangles) */ std::vector m_surfaceSelectedItems; /** Volume items */ std::vector m_volumeSelectedItems; SelectionItemBorderSurface* m_surfaceBorderIdentification; SelectionItemChartDataSeries* m_chartDataSeriesIdentification; SelectionItemChartFrequencySeries* m_chartDataFrequencyIdentification; SelectionItemChartMatrix* m_chartMatrixIdentification; SelectionItemChartTimeSeries* m_chartTimeSeriesIdentification; SelectionItemCiftiConnectivityMatrixRowColumn* m_ciftiConnectivityMatrixRowColumnIdentfication; SelectionItemFocusSurface* m_surfaceFocusIdentification; SelectionItemFocusVolume* m_volumeFocusIdentification; SelectionItemSurfaceNode* m_surfaceNodeIdentification; SelectionItemSurfaceNodeIdentificationSymbol* m_surfaceNodeIdentificationSymbol; SelectionItemSurfaceTriangle* m_surfaceTriangleIdentification; IdentificationTextGenerator* m_idTextGenerator; SelectionItemVoxel* m_voxelIdentification; SelectionItemVoxelIdentificationSymbol* m_voxelIdentificationSymbol; SelectionItemVoxelEditing* m_voxelEditingIdentification; /** Last selected item DOES NOT GET PUT IN m_allSelectionItems */ SelectionItem* m_lastSelectedItem; }; #ifdef __SELECTION_MANAGER_DECLARE__ // #endif // __SELECTION_MANAGER_DECLARE__ } // namespace #endif //__SELECTION_MANAGER__H_ workbench-1.1.1/src/Brain/SessionManager.cxx000066400000000000000000000650501255417355300210000ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __SESSION_MANAGER_DECLARE__ #include "SessionManager.h" #undef __SESSION_MANAGER_DECLARE__ #include "ApplicationInformation.h" #include "Brain.h" #include "BrowserTabContent.h" #include "CaretAssert.h" #include "CaretLogger.h" #include "CaretPreferences.h" #include "CiftiConnectivityMatrixDataFileManager.h" #include "CiftiFiberTrajectoryManager.h" #include "ElapsedTimer.h" #include "EventManager.h" #include "EventBrowserTabDelete.h" #include "EventBrowserTabGet.h" #include "EventBrowserTabGetAll.h" #include "EventBrowserTabIndicesGetAll.h" #include "EventBrowserTabNew.h" #include "EventModelAdd.h" #include "EventModelDelete.h" #include "EventModelGetAll.h" #include "EventProgressUpdate.h" #include "LogManager.h" #include "MapYokingGroupEnum.h" #include "ModelWholeBrain.h" #include "SceneAttributes.h" #include "SceneClass.h" #include "SceneClassArray.h" #include "ScenePrimitiveArray.h" #include "VolumeSurfaceOutlineSetModel.h" using namespace caret; /** * Constructor. */ SessionManager::SessionManager() : CaretObject(), EventListenerInterface(), SceneableInterface() { m_caretPreferences = new CaretPreferences(); m_ciftiConnectivityMatrixDataFileManager = new CiftiConnectivityMatrixDataFileManager(); m_ciftiFiberTrajectoryManager = new CiftiFiberTrajectoryManager(); for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_browserTabs[i] = NULL; } EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_BROWSER_TAB_DELETE); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_BROWSER_TAB_GET); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_BROWSER_TAB_GET_ALL); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_BROWSER_TAB_INDICES_GET_ALL); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_BROWSER_TAB_NEW); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_MODEL_ADD); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_MODEL_DELETE); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_MODEL_GET_ALL); Brain* brain = new Brain(); m_brains.push_back(brain); } /** * Destructor. */ SessionManager::~SessionManager() { /* * Delete browser tab content. Workbench requests deletion of tab content * as browser tabs are closed. However, command line does not issue * commands to delete tabs so this code will do so. */ for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { if (m_browserTabs[i] != NULL) { delete m_browserTabs[i]; m_browserTabs[i] = NULL; } } int32_t numberOfBrains = getNumberOfBrains(); for (int32_t i = (numberOfBrains - 1); i >= 0; i--) { delete m_brains[i]; } m_brains.clear(); EventManager::get()->removeAllEventsFromListener(this); delete m_ciftiConnectivityMatrixDataFileManager; delete m_ciftiFiberTrajectoryManager; delete m_caretPreferences; } /** * Create the session manager. * This must be called one AND ONLY one time prior to any * other Caret mechanisms. * * @param applicationType * The type of application (command line or GUI). */ void SessionManager::createSessionManager(const ApplicationTypeEnum::Enum applicationType) { CaretAssertMessage((s_singletonSessionManager == NULL), "Session manager has already been created."); /* * Set the type of application. */ ApplicationInformation::setApplicationType(applicationType); /* * Create log manager. */ LogManager::createLogManager(); /* * Create event manager first. */ EventManager::createEventManager(); /* * Create session manager. */ s_singletonSessionManager = new SessionManager(); } /** * Delete the session manager. * This may only be called one time after session manager is created. */ void SessionManager::deleteSessionManager() { CaretAssertMessage((s_singletonSessionManager != NULL), "Session manager does not exist, cannot delete it."); delete s_singletonSessionManager; s_singletonSessionManager = NULL; /* * Session manager must be deleted before the event * manager is deleted. */ EventManager::deleteEventManager(); LogManager::deleteLogManager(); } /** * Get the one and only session manager. * * @return Pointer to the session manager. */ SessionManager* SessionManager::get() { CaretAssertMessage(s_singletonSessionManager, "Session manager was not created.\n" "It must be created with SessionManager::createSessionManager()."); return s_singletonSessionManager; } /** * Add a brain to this session. * In most cases, there is one brain per subject. * * @param shareDisplayPropertiesFlag * If true display properties are shared which * is appropriate for the GUI version of Caret. * For SumsDB, false may be appropriate to keep * each subject independent of the other. * * @return New brain that was added. */ Brain* SessionManager::addBrain(const bool /*shareDisplayPropertiesFlag*/) { CaretAssertMessage(0, "Adding brains not implemented at this time."); return NULL; } /** * Get the number of brains. * There is always at least one brain. * * @return Number of brains. */ int32_t SessionManager::getNumberOfBrains() const { return m_brains.size(); } /** * Get the brain at the specified index. * There is always one brain so passing * zero as the index will always work. * * @param brainIndex * Index of brain. * @return * Brain at specified index. */ Brain* SessionManager::getBrain(const int32_t brainIndex) { CaretAssertVectorIndex(m_brains, brainIndex); return m_brains[brainIndex]; } /** * Get a description of this object's content. * @return String describing this object's content. */ AString SessionManager::toString() const { return "SessionManager"; } /** * Receive events. * * @param event * Event that needs to be processed. */ void SessionManager::receiveEvent(Event* event) { if (event->getEventType() == EventTypeEnum::EVENT_BROWSER_TAB_NEW) { EventBrowserTabNew* tabEvent = dynamic_cast(event); CaretAssert(tabEvent); tabEvent->setEventProcessed(); bool createdTab = false; for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { if (m_browserTabs[i] == NULL) { BrowserTabContent* tab = new BrowserTabContent(i); tab->update(m_models); m_browserTabs[i] = tab; tabEvent->setBrowserTab(tab); createdTab = true; break; } } if (createdTab == false) { tabEvent->setErrorMessage("Workbench is exhausted. It cannot create any more tabs."); } } else if (event->getEventType() == EventTypeEnum::EVENT_BROWSER_TAB_DELETE) { EventBrowserTabDelete* tabEvent = dynamic_cast(event); CaretAssert(tabEvent); BrowserTabContent* tab = tabEvent->getBrowserTab(); for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { if (m_browserTabs[i] == tab) { delete m_browserTabs[i]; m_browserTabs[i] = NULL; tabEvent->setEventProcessed(); break; } } } else if (event->getEventType() == EventTypeEnum::EVENT_BROWSER_TAB_GET) { EventBrowserTabGet* tabEvent = dynamic_cast(event); CaretAssert(tabEvent); tabEvent->setEventProcessed(); const int32_t tabNumber = tabEvent->getTabNumber(); CaretAssertArrayIndex(m_browserTabs, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabNumber); tabEvent->setBrowserTab(m_browserTabs[tabNumber]); } else if (event->getEventType() == EventTypeEnum::EVENT_BROWSER_TAB_GET_ALL) { EventBrowserTabGetAll* tabEvent = dynamic_cast(event); CaretAssert(tabEvent); tabEvent->setEventProcessed(); for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { if (m_browserTabs[i] != NULL) { tabEvent->addBrowserTab(m_browserTabs[i]); } } } else if (event->getEventType() == EventTypeEnum::EVENT_BROWSER_TAB_INDICES_GET_ALL) { EventBrowserTabIndicesGetAll* tabEvent = dynamic_cast(event); CaretAssert(tabEvent); tabEvent->setEventProcessed(); for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { if (m_browserTabs[i] != NULL) { tabEvent->addBrowserTabIndex(m_browserTabs[i]->getTabNumber()); } } } else if (event->getEventType() == EventTypeEnum::EVENT_MODEL_ADD) { EventModelAdd* addModelsEvent = dynamic_cast(event); CaretAssert(addModelsEvent); addModelsEvent->setEventProcessed(); m_models.push_back(addModelsEvent->getModel()); updateBrowserTabContents(); } else if (event->getEventType() == EventTypeEnum::EVENT_MODEL_DELETE) { EventModelDelete* deleteModelsEvent = dynamic_cast(event); CaretAssert(deleteModelsEvent); deleteModelsEvent->setEventProcessed(); Model* model = deleteModelsEvent->getModel(); std::vector::iterator iter = std::find(m_models.begin(), m_models.end(), model); CaretAssertMessage(iter != m_models.end(), "Trying to delete non-existent model"); m_models.erase(iter); updateBrowserTabContents(); } else if (event->getEventType() == EventTypeEnum::EVENT_MODEL_GET_ALL) { EventModelGetAll* getModelsEvent = dynamic_cast(event); CaretAssert(getModelsEvent); getModelsEvent->setEventProcessed(); getModelsEvent->addModels(m_models); } } /** * Update all of the browser tab contents since the models have changed. */ void SessionManager::updateBrowserTabContents() { for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { if (m_browserTabs[i] != NULL) { m_browserTabs[i]->update(m_models); } } } /** * @return The caret preferences */ CaretPreferences* SessionManager::getCaretPreferences() { return m_caretPreferences; } /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ SceneClass* SessionManager::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { switch (sceneAttributes->getSceneType()) { case SceneTypeEnum::SCENE_TYPE_FULL: break; case SceneTypeEnum::SCENE_TYPE_GENERIC: break; } SceneClass* sceneClass = new SceneClass(instanceName, "SessionManager", 1); { /* * Save map yoking groups. */ std::vector mapYokingGroups; MapYokingGroupEnum::getAllEnums(mapYokingGroups); const int32_t numMapYokingGroups = static_cast(mapYokingGroups.size()); if (numMapYokingGroups > 0) { std::vector mapMapSelections(numMapYokingGroups, 1); CaretArray mapMapEnabled(numMapYokingGroups); for (int32_t i = 0; i < numMapYokingGroups; i++) { mapMapEnabled[i] = false; } for (int32_t i = 0; i < numMapYokingGroups; i++) { const MapYokingGroupEnum::Enum enumValue = mapYokingGroups[i]; if (enumValue != MapYokingGroupEnum::MAP_YOKING_GROUP_OFF) { const int32_t groupIndex = MapYokingGroupEnum::toIntegerCode(enumValue); const int32_t mapIndex = MapYokingGroupEnum::getSelectedMapIndex(enumValue); mapMapSelections[groupIndex] = mapIndex; mapMapEnabled[groupIndex] = MapYokingGroupEnum::isEnabled(enumValue); } } sceneClass->addIntegerArray("MapYokingIndexArray", &mapMapSelections[0], numMapYokingGroups); sceneClass->addBooleanArray("MapYokingEnabledArray", &mapMapEnabled[0], numMapYokingGroups); } } /* * Save brains */ std::vector brainSceneClasses; const int32_t numBrains = m_brains.size(); for (int32_t i = 0; i < numBrains; i++) { brainSceneClasses.push_back(m_brains[i]->saveToScene(sceneAttributes, "m_brains")); } sceneClass->addChild(new SceneClassArray("m_brains", brainSceneClasses)); /* * Get the tabs that are to be included in the scene */ const std::vector tabIndicesForScene = sceneAttributes->getIndicesOfTabsForSavingToScene(); /* * Save browser tabs */ std::vector browserTabSceneClasses; for (int32_t tabIndex = 0; tabIndex < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; tabIndex++) { if (std::find(tabIndicesForScene.begin(), tabIndicesForScene.end(), tabIndex) != tabIndicesForScene.end()) { BrowserTabContent* btc = m_browserTabs[tabIndex]; if (btc != NULL) { browserTabSceneClasses.push_back(btc->saveToScene(sceneAttributes, "m_browserTabs")); } } } sceneClass->addChild(new SceneClassArray("m_browserTabs", browserTabSceneClasses)); return sceneClass; } /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass for the instance of a class that implements * this interface. May be NULL for some types of scenes. */ void SessionManager::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } int32_t progressCounter = 0; const int32_t PROGRESS_RESTORING_BRAIN = progressCounter++; const int32_t PROGRESS_RESTORING_TABS = progressCounter++; const int32_t PROGRESS_RESTORING_GUI = progressCounter++; const int32_t PROGRESS_RESTORING_TOTAL = progressCounter++; ElapsedTimer timer; timer.start(); EventProgressUpdate progressEvent(0, PROGRESS_RESTORING_TOTAL, PROGRESS_RESTORING_BRAIN, "Restoring Brain"); EventManager::get()->sendEvent(progressEvent.getPointer()); if (progressEvent.isCancelled()) { resetBrains(true); return; } switch (sceneAttributes->getSceneType()) { case SceneTypeEnum::SCENE_TYPE_FULL: break; case SceneTypeEnum::SCENE_TYPE_GENERIC: break; } { /* * Restore map yoking groups */ std::vector mapYokingGroups; MapYokingGroupEnum::getAllEnums(mapYokingGroups); const int32_t numMapYokingGroups = static_cast(mapYokingGroups.size()); if (numMapYokingGroups > 0) { std::vector mapIndexSelections(numMapYokingGroups, 1); sceneClass->getIntegerArrayValue("MapYokingIndexArray", &mapIndexSelections[0], numMapYokingGroups, 1); for (int32_t i = 0; i < numMapYokingGroups; i++) { bool isValid = false; const MapYokingGroupEnum::Enum enumValue = MapYokingGroupEnum::fromIntegerCode(i, &isValid); if (enumValue != MapYokingGroupEnum::MAP_YOKING_GROUP_OFF) { if (isValid) { MapYokingGroupEnum::setSelectedMapIndex(enumValue, mapIndexSelections[i]); } else { MapYokingGroupEnum::setSelectedMapIndex(enumValue, 1); } } } const ScenePrimitiveArray* mapEnabledArray = sceneClass->getPrimitiveArray("MapYokingEnabledArray"); if (mapEnabledArray != NULL) { for (int32_t i = 0; i < numMapYokingGroups; i++) { bool isValid = false; const MapYokingGroupEnum::Enum enumValue = MapYokingGroupEnum::fromIntegerCode(i, &isValid); if (enumValue != MapYokingGroupEnum::MAP_YOKING_GROUP_OFF) { /* * Enabled status was added on 10/17/2014. * Previous to this data, there was no enabled status and * we will assume enabled status was on. */ bool enabledStatus = true; if (isValid) { if (mapEnabledArray != NULL) { if (i < mapEnabledArray->getNumberOfArrayElements()) { enabledStatus = mapEnabledArray->booleanValue(i); } } } MapYokingGroupEnum::setEnabled(enumValue, enabledStatus); } } } } } { /* * Restore overlay yoking groups * Note: Overlay yoking groups were replaced by MapYokingGroupEnum * So if overlay yoking group is found, the scene was created * before map yoking groups. */ const ScenePrimitiveArray* overlayEnabledArray = sceneClass->getPrimitiveArray("OverlayYokingEnabledArray"); const ScenePrimitiveArray* mapIndexArray = sceneClass->getPrimitiveArray("OverlayYokingGroupArray"); if (mapIndexArray != NULL) { const int32_t numMapIndexElements = mapIndexArray->getNumberOfArrayElements(); for (int32_t i = 0; i < numMapIndexElements; i++) { bool isValid = false; const MapYokingGroupEnum::Enum enumValue = MapYokingGroupEnum::fromIntegerCode(i, &isValid); if (enumValue != MapYokingGroupEnum::MAP_YOKING_GROUP_OFF) { if (isValid) { MapYokingGroupEnum::setSelectedMapIndex(enumValue, mapIndexArray->integerValue(i)); } else { MapYokingGroupEnum::setSelectedMapIndex(enumValue, 1); } } } if (overlayEnabledArray != NULL) { const int32_t numMapEnabledElements = overlayEnabledArray->getNumberOfArrayElements(); for (int32_t i = 0; i < numMapEnabledElements; i++) { bool isValid = false; const MapYokingGroupEnum::Enum enumValue = MapYokingGroupEnum::fromIntegerCode(i, &isValid); if (enumValue != MapYokingGroupEnum::MAP_YOKING_GROUP_OFF) { /* * Enabled status was added on 10/17/2014. * Previous to this data, there was no enabled status and * we will assume enabled status was on. */ bool enabledStatus = true; if (isValid) { if (overlayEnabledArray != NULL) { if (i < overlayEnabledArray->getNumberOfArrayElements()) { enabledStatus = overlayEnabledArray->booleanValue(i); } } } MapYokingGroupEnum::setEnabled(enumValue, enabledStatus); } } } } } /* * Restore brains */ const SceneClassArray* brainArray = sceneClass->getClassArray("m_brains"); const int32_t numBrainClasses = brainArray->getNumberOfArrayElements(); for (int32_t i = 0; i < numBrainClasses; i++) { const SceneClass* brainClass = brainArray->getClassAtIndex(i); if (i < static_cast(m_brains.size())) { m_brains[i]->restoreFromScene(sceneAttributes, brainClass); } else { Brain* brain = new Brain(); brain->restoreFromScene(sceneAttributes, brainClass); } } CaretLogFine("Time to restore brain was " + QString::number(timer.getElapsedTimeSeconds(), 'f', 3) + " seconds"); timer.reset(); progressEvent.setProgress(PROGRESS_RESTORING_TABS, "Restoring Content of Browser Tabs"); EventManager::get()->sendEvent(progressEvent.getPointer()); if (progressEvent.isCancelled()) { resetBrains(true); return; } /* * Remove all tabs */ for (int32_t iTab = 0; iTab < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; iTab++) { if (m_browserTabs[iTab] != NULL) { delete m_browserTabs[iTab]; m_browserTabs[iTab] = NULL; } } /* * Restore tabs */ const SceneClassArray* browserTabArray = sceneClass->getClassArray("m_browserTabs"); const int32_t numBrowserTabClasses = browserTabArray->getNumberOfArrayElements(); for (int32_t i = 0; i < numBrowserTabClasses; i++) { const SceneClass* browserTabClass = browserTabArray->getClassAtIndex(i); BrowserTabContent* tab = new BrowserTabContent(i); tab->update(m_models); tab->getVolumeSurfaceOutlineSet()->selectSurfacesAfterSpecFileLoaded(m_brains[0], false); tab->restoreFromScene(sceneAttributes, browserTabClass); const int32_t tabIndex = tab->getTabNumber(); CaretAssert(tabIndex >= 0); m_browserTabs[tabIndex] = tab; } CaretLogFine("Time to restore browser tab content was " + QString::number(timer.getElapsedTimeSeconds(), 'f', 3) + " seconds"); timer.reset(); progressEvent.setProgress(PROGRESS_RESTORING_GUI, "Restoring Graphical User Interface"); EventManager::get()->sendEvent(progressEvent.getPointer()); if (progressEvent.isCancelled()) { resetBrains(true); return; } } /** * Reset the first brain and remove all other brains. */ void SessionManager::resetBrains(const bool keepSceneFiles) { const int32_t numBrains = static_cast(m_brains.size()); for (int32_t i = 0; i < numBrains; i++) { if (i > 0) { delete m_brains[i]; } else if (keepSceneFiles) { m_brains[i]->resetBrainKeepSceneFiles(); } else { m_brains[i]->resetBrain(); } } if (numBrains > 1) { m_brains.resize(1); } } /** * @param The CIFTI connectivity matrix data file manager */ CiftiConnectivityMatrixDataFileManager* SessionManager::getCiftiConnectivityMatrixDataFileManager() { return m_ciftiConnectivityMatrixDataFileManager; } /** * @param The CIFTI connectivity matrix data file manager */ const CiftiConnectivityMatrixDataFileManager* SessionManager::getCiftiConnectivityMatrixDataFileManager() const { return m_ciftiConnectivityMatrixDataFileManager; } /** * @return The CIFTI Fiber Trajectory Manager */ CiftiFiberTrajectoryManager* SessionManager::getCiftiFiberTrajectoryManager() { return m_ciftiFiberTrajectoryManager; } /** * @return The CIFTI Fiber Trajectory Manager (const method) */ const CiftiFiberTrajectoryManager* SessionManager::getCiftiFiberTrajectoryManager() const { return m_ciftiFiberTrajectoryManager; } workbench-1.1.1/src/Brain/SessionManager.h000066400000000000000000000076441255417355300204320ustar00rootroot00000000000000#ifndef __SESSION_MANAGER__H_ #define __SESSION_MANAGER__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "ApplicationTypeEnum.h" #include "BrainConstants.h" #include "CaretObject.h" #include "EventListenerInterface.h" #include "SceneableInterface.h" namespace caret { class Brain; class BrowserTabContent; class CaretPreferences; class CiftiConnectivityMatrixDataFileManager; class CiftiFiberTrajectoryManager; class Model; /// Manages a Caret session which contains 'global' brain data. class SessionManager : public CaretObject, public EventListenerInterface, public SceneableInterface { public: static void createSessionManager(const ApplicationTypeEnum::Enum applicationType); static void deleteSessionManager(); static SessionManager* get(); void receiveEvent(Event* event); Brain* addBrain(const bool shareDisplayPropertiesFlag); int32_t getNumberOfBrains() const; Brain* getBrain(const int32_t brainIndex); CaretPreferences* getCaretPreferences(); CiftiConnectivityMatrixDataFileManager* getCiftiConnectivityMatrixDataFileManager(); const CiftiConnectivityMatrixDataFileManager* getCiftiConnectivityMatrixDataFileManager() const; CiftiFiberTrajectoryManager* getCiftiFiberTrajectoryManager(); const CiftiFiberTrajectoryManager* getCiftiFiberTrajectoryManager() const; virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private: SessionManager(); virtual ~SessionManager(); SessionManager(const SessionManager&); SessionManager& operator=(const SessionManager&); public: virtual AString toString() const; private: void updateBrowserTabContents(); void resetBrains(const bool keepSceneFiles); /** The session manager */ static SessionManager* s_singletonSessionManager; /** The browser tabs */ BrowserTabContent* m_browserTabs[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; /** Holds valid models */ std::vector m_models; /** Holds all loaded brains */ std::vector m_brains; /** Caret's preferences */ CaretPreferences* m_caretPreferences; /** Loads connectivity matrix data */ CiftiConnectivityMatrixDataFileManager* m_ciftiConnectivityMatrixDataFileManager; /** Loads fiber trajectory data */ CiftiFiberTrajectoryManager* m_ciftiFiberTrajectoryManager; }; #ifdef __SESSION_MANAGER_DECLARE__ SessionManager* SessionManager::s_singletonSessionManager = NULL; #endif // __SESSION_MANAGER_DECLARE__ } // namespace #endif //__SESSION_MANAGER__H_ workbench-1.1.1/src/Brain/Surface.cxx000066400000000000000000000056271255417355300174560ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "BoundingBox.h" #include "BrainStructure.h" #include "Surface.h" using namespace caret; /** * Constructor. */ Surface::Surface() { this->initializeMemberSurface(); } /** * Destructor. */ Surface::~Surface() { } /** * Copy constructor. * * @param s * Surface that is copied. */ Surface::Surface(const Surface& s) : SurfaceFile(s) { this->copyHelperSurface(s); } /** * Assignment operator. * * @param s * Contents of "s" are assigned to this. */ Surface& Surface::operator=(const Surface& s) { if (this != &s) { SurfaceFile::operator=(s); this->copyHelperSurface(s); } return *this; } /** * */ AString Surface::getNameForGUI(bool includeStructureFlag) const { AString msg; if (includeStructureFlag) { msg += StructureEnum::toGuiName(this->getStructure()); msg += " "; } msg += SurfaceTypeEnum::toGuiName(this->getSurfaceType()); msg += " "; msg += this->getFileNameNoPath(); return msg; } /** * Set a bounding box using this surface's coordinates. * * @param boundingBoxOut * Bounding box that is updated. */ void Surface::getBounds(BoundingBox& boundingBoxOut) const { boundingBoxOut.set(this->getCoordinate(0), this->getNumberOfNodes()); } /** * Initialize members of this class. */ void Surface::initializeMemberSurface() { this->brainStructure = NULL; } /** * Helps with copying this surface. */ void Surface::copyHelperSurface(const Surface& /*s*/) { this->initializeMemberSurface(); this->computeNormals(); } /** * @return Brain structure that holds this surface. */ const BrainStructure* Surface::getBrainStructure() const { return this->brainStructure; } /** * @return Brain structure that holds this surface. */ BrainStructure* Surface::getBrainStructure() { return this->brainStructure; } /** * Set brain structure that holds this surface. * @param brainStructure * New value for brain structure. */ void Surface::setBrainStructure(BrainStructure* brainStructure) { this->brainStructure = brainStructure; } workbench-1.1.1/src/Brain/Surface.h000066400000000000000000000034251255417355300170750ustar00rootroot00000000000000 #ifndef __SURFACE_H__ #define __SURFACE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "SurfaceFile.h" namespace caret { class BoundingBox; class BrainStructure; /** * Maintains view of some type of object. */ class Surface : public SurfaceFile { public: Surface(); ~Surface(); Surface(const Surface& s); Surface& operator=(const Surface& s); AString getNameForGUI(bool includeStructureFlag) const; void getBounds(BoundingBox& boundingBoxOut) const; const BrainStructure* getBrainStructure() const; BrainStructure* getBrainStructure(); void setBrainStructure(BrainStructure* brainStructure); private: void initializeMemberSurface(); void copyHelperSurface(const Surface& s); BrainStructure* brainStructure; }; } // namespace #endif // __SURFACE_H__ workbench-1.1.1/src/Brain/SurfaceDrawingTypeEnum.cxx000066400000000000000000000227521255417355300224570ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __SURFACE_DRAWING_TYPE_ENUM_DECLARE__ #include "SurfaceDrawingTypeEnum.h" #undef __SURFACE_DRAWING_TYPE_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::SurfaceDrawingTypeEnum * \brief * * */ /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ SurfaceDrawingTypeEnum::SurfaceDrawingTypeEnum(const Enum enumValue, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCodeCounter++; this->name = name; this->guiName = guiName; } /** * Destructor. */ SurfaceDrawingTypeEnum::~SurfaceDrawingTypeEnum() { } /** * Initialize the enumerated metadata. */ void SurfaceDrawingTypeEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(SurfaceDrawingTypeEnum(DRAW_HIDE, "DRAW_HIDE", "Hide")); enumData.push_back(SurfaceDrawingTypeEnum(DRAW_AS_LINKS, "DRAW_AS_LINKS", "Links (Edges)")); enumData.push_back(SurfaceDrawingTypeEnum(DRAW_AS_NODES, "DRAW_AS_NODES", "Vertices")); enumData.push_back(SurfaceDrawingTypeEnum(DRAW_AS_TRIANGLES, "DRAW_AS_TRIANGLES", "Triangles")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const SurfaceDrawingTypeEnum* SurfaceDrawingTypeEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const SurfaceDrawingTypeEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString SurfaceDrawingTypeEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const SurfaceDrawingTypeEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ SurfaceDrawingTypeEnum::Enum SurfaceDrawingTypeEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = DRAW_AS_TRIANGLES; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const SurfaceDrawingTypeEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type SurfaceDrawingTypeEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString SurfaceDrawingTypeEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const SurfaceDrawingTypeEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ SurfaceDrawingTypeEnum::Enum SurfaceDrawingTypeEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = DRAW_AS_TRIANGLES; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const SurfaceDrawingTypeEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type SurfaceDrawingTypeEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t SurfaceDrawingTypeEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const SurfaceDrawingTypeEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ SurfaceDrawingTypeEnum::Enum SurfaceDrawingTypeEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = DRAW_AS_TRIANGLES; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const SurfaceDrawingTypeEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type SurfaceDrawingTypeEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void SurfaceDrawingTypeEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void SurfaceDrawingTypeEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(SurfaceDrawingTypeEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void SurfaceDrawingTypeEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(SurfaceDrawingTypeEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } workbench-1.1.1/src/Brain/SurfaceDrawingTypeEnum.h000066400000000000000000000063531255417355300221030ustar00rootroot00000000000000#ifndef __SURFACE_DRAWING_TYPE_ENUM__H_ #define __SURFACE_DRAWING_TYPE_ENUM__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class SurfaceDrawingTypeEnum { public: /** * Enumerated values. */ enum Enum { /** Do not draw the surface */ DRAW_HIDE, /** Draw as links (edges) */ DRAW_AS_LINKS, /** Draw as nodes */ DRAW_AS_NODES, /** Draw as triangles */ DRAW_AS_TRIANGLES }; ~SurfaceDrawingTypeEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); private: SurfaceDrawingTypeEnum(const Enum enumValue, const AString& name, const AString& guiName); static const SurfaceDrawingTypeEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; #ifdef __SURFACE_DRAWING_TYPE_ENUM_DECLARE__ std::vector SurfaceDrawingTypeEnum::enumData; bool SurfaceDrawingTypeEnum::initializedFlag = false; int32_t SurfaceDrawingTypeEnum::integerCodeCounter = 0; #endif // __SURFACE_DRAWING_TYPE_ENUM_DECLARE__ } // namespace #endif //__SURFACE_DRAWING_TYPE_ENUM__H_ workbench-1.1.1/src/Brain/SurfaceMontageConfigurationAbstract.cxx000066400000000000000000000212721255417355300251770ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SURFACE_MONTAGE_CONFIGURATION_ABSTRACT_DECLARE__ #include "SurfaceMontageConfigurationAbstract.h" #undef __SURFACE_MONTAGE_CONFIGURATION_ABSTRACT_DECLARE__ #include "CaretAssert.h" #include "OverlaySet.h" #include "SceneClass.h" #include "SceneClassAssistant.h" using namespace caret; /** * \class caret::SurfaceMontageConfiguration * \brief Abstract class for surface montage configurations * \ingroup Brain */ /** * Constructor. * * @param supportsLayoutOrientation * True if the subclass supports layout orientation (landscape/portrait). */ SurfaceMontageConfigurationAbstract::SurfaceMontageConfigurationAbstract(const SurfaceMontageConfigurationTypeEnum::Enum configurationType, const SupportLayoutOrientation supportsLayoutOrientation) : CaretObject(), m_configurationType(configurationType), m_supportsLayoutOrientation(supportsLayoutOrientation) { m_sceneAssistant = new SceneClassAssistant(); m_overlaySet = NULL; m_layoutOrientation = SurfaceMontageLayoutOrientationEnum::LANDSCAPE_LAYOUT_ORIENTATION; /* * Note: Since these members are initialized by the constructor, * they do not need to be saved to the scene: * m_configuration * m_supportsLayoutOrientation * m_surfaceMontageViewports * */ m_sceneAssistant->add("m_layoutOrientation", &m_layoutOrientation); } /** * Destructor. */ SurfaceMontageConfigurationAbstract::~SurfaceMontageConfigurationAbstract() { delete m_sceneAssistant; CaretAssertMessage(m_overlaySet, "Did you forget to call setupOverlaySet() from subclass constructor?"); delete m_overlaySet; } /** * Get a description of this object's content. * @return String describing this object's content. */ AString SurfaceMontageConfigurationAbstract::toString() const { return "SurfaceMontageConfiguration"; } /** * Setup the overlay set for the subclass. * * @param includeSurfaceStructures * Surface structures supported by subclass. */ void SurfaceMontageConfigurationAbstract::setupOverlaySet(const AString& overlaySetName, const int32_t tabIndex, const std::vector& includeSurfaceStructures) { m_overlaySet = new OverlaySet(overlaySetName, tabIndex, includeSurfaceStructures, Overlay::INCLUDE_VOLUME_FILES_NO); m_sceneAssistant->add("m_overlaySet", "OverlaySet", m_overlaySet); } /** * @return The overlay set */ OverlaySet* SurfaceMontageConfigurationAbstract::getOverlaySet() { CaretAssertMessage(m_overlaySet, "Did you forget to call setupOverlaySet() from subclass constructor?"); return m_overlaySet; } /** * @return The overlay set (const method) */ const OverlaySet* SurfaceMontageConfigurationAbstract::getOverlaySet() const { CaretAssertMessage(m_overlaySet, "Did you forget to call setupOverlaySet() from subclass constructor?"); return m_overlaySet; } /** * Save information specific to this type of model to the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @param instanceName * Name of instance in the scene. */ SceneClass* SurfaceMontageConfigurationAbstract::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "SurfaceMontageConfigurationAbstract", 1); m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); saveMembersToScene(sceneAttributes, sceneClass); return sceneClass; } /** * Restore information specific to the type of model from the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass from which model specific information is obtained. */ void SurfaceMontageConfigurationAbstract::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); restoreMembersFromScene(sceneAttributes, sceneClass); } /** * @return The configuration type. */ SurfaceMontageConfigurationTypeEnum::Enum SurfaceMontageConfigurationAbstract::getConfigurationType() const { return m_configurationType; } /** * @return Configuration has a layout orientation. */ bool SurfaceMontageConfigurationAbstract::hasLayoutOrientation() const { return (m_supportsLayoutOrientation == SUPPORTS_LAYOUT_ORIENTATION_YES); } /** * @return The selected layout orientation. */ SurfaceMontageLayoutOrientationEnum::Enum SurfaceMontageConfigurationAbstract::getLayoutOrientation() const { return m_layoutOrientation; } /** * Set the layout orientation. * * @param layoutOrientation * New value for layout orientation. */ void SurfaceMontageConfigurationAbstract::setLayoutOrientation(const SurfaceMontageLayoutOrientationEnum::Enum layoutOrientation) { m_layoutOrientation = layoutOrientation; } /** * Get the montage viewports for drawing by OpenGL. The montage viewports * will be updated prior to returning them. OpenGL will update * the viewing dimensions (x, y, width, height) in the returned montage * viewports. * * @param surfaceMontageViewports * The montage viewports. */ void SurfaceMontageConfigurationAbstract::getSurfaceMontageViewportsForDrawing(std::vector& surfaceMontageViewports) { m_surfaceMontageViewports.clear(); updateSurfaceMontageViewports(m_surfaceMontageViewports); for (std::vector::iterator iter = m_surfaceMontageViewports.begin(); iter != m_surfaceMontageViewports.end(); iter++) { SurfaceMontageViewport& svp = *iter; surfaceMontageViewports.push_back(&svp); } } /** * Get the montage viewports that will be used by the mouse transformations. * * @param surfaceMontageViewports * The montage viewports. */ void SurfaceMontageConfigurationAbstract::getSurfaceMontageViewportsForTransformation(std::vector& surfaceMontageViewports) const { surfaceMontageViewports.clear(); for (std::vector::const_iterator iter = m_surfaceMontageViewports.begin(); iter != m_surfaceMontageViewports.end(); iter++) { const SurfaceMontageViewport& svp = *iter; surfaceMontageViewports.push_back(&svp); } } /** * Copy the given configuration to this configurtion. * * @param configuration. * Configuration that is copied. */ void SurfaceMontageConfigurationAbstract::copyConfiguration(SurfaceMontageConfigurationAbstract* configuration) { CaretAssert(m_configurationType == configuration->m_configurationType); m_overlaySet->copyOverlaySet(configuration->m_overlaySet); m_layoutOrientation = configuration->m_layoutOrientation; m_surfaceMontageViewports = configuration->m_surfaceMontageViewports; } workbench-1.1.1/src/Brain/SurfaceMontageConfigurationAbstract.h000066400000000000000000000123071255417355300246230ustar00rootroot00000000000000#ifndef __SURFACE_MONTAGE_CONFIGURATION_ABSTRACT_H__ #define __SURFACE_MONTAGE_CONFIGURATION_ABSTRACT_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "SceneableInterface.h" #include "StructureEnum.h" #include "SurfaceMontageConfigurationTypeEnum.h" #include "SurfaceMontageLayoutOrientationEnum.h" #include "SurfaceMontageViewport.h" namespace caret { class OverlaySet; class PlainTextStringBuilder; class SceneClassAssistant; class SurfaceMontageViewport; class SurfaceMontageConfigurationAbstract : public CaretObject, public SceneableInterface { public: enum SupportLayoutOrientation { SUPPORTS_LAYOUT_ORIENTATION_YES, SUPPORTS_LAYOUT_ORIENTATION_NO, }; SurfaceMontageConfigurationAbstract(const SurfaceMontageConfigurationTypeEnum::Enum configuration, const SupportLayoutOrientation supportsLayoutOrientation); virtual ~SurfaceMontageConfigurationAbstract(); SurfaceMontageConfigurationTypeEnum::Enum getConfigurationType() const; virtual void initializeSelectedSurfaces() = 0; virtual bool isValid() = 0; OverlaySet* getOverlaySet(); const OverlaySet* getOverlaySet() const; bool hasLayoutOrientation() const; SurfaceMontageLayoutOrientationEnum::Enum getLayoutOrientation() const; void setLayoutOrientation(const SurfaceMontageLayoutOrientationEnum::Enum layoutOrientation); virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); void getSurfaceMontageViewportsForDrawing(std::vector& surfaceMontageViewports); void getSurfaceMontageViewportsForTransformation(std::vector& surfaceMontageViewports) const; virtual void copyConfiguration(SurfaceMontageConfigurationAbstract* configuration); private: SurfaceMontageConfigurationAbstract(const SurfaceMontageConfigurationAbstract&); SurfaceMontageConfigurationAbstract& operator=(const SurfaceMontageConfigurationAbstract&); protected: /** * Update the montage viewports using the current selected surfaces and settings. * * @param surfaceMontageViewports * Will be loaded with the montage viewports. */ virtual void updateSurfaceMontageViewports(std::vector& surfaceMontageViewports) = 0; void setupOverlaySet(const AString& overlaySetName, const int32_t tabIndex, const std::vector& includeSurfaceStructures); virtual void saveMembersToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass) = 0; virtual void restoreMembersFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) = 0; public: // ADD_NEW_METHODS_HERE virtual AString toString() const; virtual void getDescriptionOfContent(PlainTextStringBuilder& descriptionOut) const = 0; virtual void getDisplayedSurfaces(std::vector& surfacesOut) const = 0; private: SceneClassAssistant* m_sceneAssistant; const SurfaceMontageConfigurationTypeEnum::Enum m_configurationType; const SupportLayoutOrientation m_supportsLayoutOrientation; SurfaceMontageLayoutOrientationEnum::Enum m_layoutOrientation; OverlaySet* m_overlaySet; std::vector m_surfaceMontageViewports; // ADD_NEW_MEMBERS_HERE friend class ModelSurfaceMontage; }; #ifdef __SURFACE_MONTAGE_CONFIGURATION_ABSTRACT_DECLARE__ // #endif // __SURFACE_MONTAGE_CONFIGURATION_ABSTRACT_DECLARE__ } // namespace #endif //__SURFACE_MONTAGE_CONFIGURATION_ABSTRACT_H__ workbench-1.1.1/src/Brain/SurfaceMontageConfigurationCerebellar.cxx000066400000000000000000000560161255417355300255000ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SURFACE_MONTAGE_CONFIGURATION_CEREBELLAR_DECLARE__ #include "SurfaceMontageConfigurationCerebellar.h" #undef __SURFACE_MONTAGE_CONFIGURATION_CEREBELLAR_DECLARE__ #include "CaretAssert.h" #include "PlainTextStringBuilder.h" #include "SceneClass.h" #include "SceneClassAssistant.h" #include "Surface.h" #include "SurfaceSelectionModel.h" using namespace caret; /** * \class caret::SurfaceMontageConfigurationCerebellar * \brief Surface montage configuration for cerebellum. * \ingroup Brain */ /** * Constructor. */ SurfaceMontageConfigurationCerebellar::SurfaceMontageConfigurationCerebellar(const int32_t tabIndex) : SurfaceMontageConfigurationAbstract(SurfaceMontageConfigurationTypeEnum::CEREBELLAR_CORTEX_CONFIGURATION, SUPPORTS_LAYOUT_ORIENTATION_YES) { std::vector validSurfaceTypes; SurfaceTypeEnum::getAllEnumsExceptFlat(validSurfaceTypes); m_firstSurfaceSelectionModel = new SurfaceSelectionModel(StructureEnum::CEREBELLUM, validSurfaceTypes); m_secondSurfaceSelectionModel = new SurfaceSelectionModel(StructureEnum::CEREBELLUM, validSurfaceTypes); m_firstSurfaceEnabled = true; m_secondSurfaceEnabled = false; m_dorsalEnabled = true; m_ventralEnabled = true; m_anteriorEnabled = true; m_posteriorEnabled = true; m_sceneAssistant = new SceneClassAssistant(); m_sceneAssistant->add("m_firstSurfaceSelectionModel", "SurfaceSelectionModel", m_firstSurfaceSelectionModel); m_sceneAssistant->add("m_secondSurfaceSelectionModel", "SurfaceSelectionModel", m_secondSurfaceSelectionModel); m_sceneAssistant->add("m_firstSurfaceEnabled", &m_firstSurfaceEnabled); m_sceneAssistant->add("m_secondSurfaceEnabled", &m_secondSurfaceEnabled); m_sceneAssistant->add("m_dorsalEnabled", &m_dorsalEnabled); m_sceneAssistant->add("m_ventralEnabled", &m_ventralEnabled); m_sceneAssistant->add("m_anteriorEnabled", &m_anteriorEnabled); m_sceneAssistant->add("m_posteriorEnabled", &m_posteriorEnabled); std::vector supportedStructures; supportedStructures.push_back(StructureEnum::CEREBELLUM); setupOverlaySet("Cerebellar Montage", tabIndex, supportedStructures); } /** * Destructor. */ SurfaceMontageConfigurationCerebellar::~SurfaceMontageConfigurationCerebellar() { delete m_firstSurfaceSelectionModel; delete m_secondSurfaceSelectionModel; delete m_sceneAssistant; } /** * @return Is this configuration valid? */ bool SurfaceMontageConfigurationCerebellar::isValid() { const bool valid = (getFirstSurfaceSelectionModel()->getSurface() != NULL); return valid; } /** * Initialize the selected surfaces. */ void SurfaceMontageConfigurationCerebellar::initializeSelectedSurfaces() { } /** * @return First surface selection model. */ SurfaceSelectionModel* SurfaceMontageConfigurationCerebellar::getFirstSurfaceSelectionModel() { return m_firstSurfaceSelectionModel; } /** * @return First surface selection model. */ const SurfaceSelectionModel* SurfaceMontageConfigurationCerebellar::getFirstSurfaceSelectionModel() const { return m_firstSurfaceSelectionModel; } /** * @return Second surface selection model. */ SurfaceSelectionModel* SurfaceMontageConfigurationCerebellar::getSecondSurfaceSelectionModel() { return m_secondSurfaceSelectionModel; } /** * @return Second surface selection model. */ const SurfaceSelectionModel* SurfaceMontageConfigurationCerebellar::getSecondSurfaceSelectionModel() const { return m_secondSurfaceSelectionModel; } /** * @return Is first surface enabled. */ bool SurfaceMontageConfigurationCerebellar::isFirstSurfaceEnabled() const { return m_firstSurfaceEnabled; } /** * Set first surface enabled status. * * @param enabled * New enabled status. */ void SurfaceMontageConfigurationCerebellar::setFirstSurfaceEnabled(const bool enabled) { m_firstSurfaceEnabled = enabled; } /** * @return Is second surface enabled. */ bool SurfaceMontageConfigurationCerebellar::isSecondSurfaceEnabled() const { return m_secondSurfaceEnabled; } /** * Set first surface enabled status. * * @param enabled * New enabled status. */ void SurfaceMontageConfigurationCerebellar::setSecondSurfaceEnabled(const bool enabled) { m_secondSurfaceEnabled = enabled; } /** * @return Is dorsal enabled. */ bool SurfaceMontageConfigurationCerebellar::isDorsalEnabled() const { return m_dorsalEnabled; } /** * Set dorsal enabled. * * @param enabled * New enabled status. */ void SurfaceMontageConfigurationCerebellar::setDorsalEnabled(const bool enabled) { m_dorsalEnabled = enabled; } /** * @return Is ventral enabled. */ bool SurfaceMontageConfigurationCerebellar::isVentralEnabled() const { return m_ventralEnabled; } /** * Set ventral enabled. * * @param enabled * New enabled status. */ void SurfaceMontageConfigurationCerebellar::setVentralEnabled(const bool enabled) { m_ventralEnabled = enabled; } /** * @return Is anterior enabled. */ bool SurfaceMontageConfigurationCerebellar::isAnteriorEnabled() const { return m_anteriorEnabled; } /** * Set anterior enabled. * * @param enabled * New enabled status. */ void SurfaceMontageConfigurationCerebellar::setAnteriorEnabled(const bool enabled) { m_anteriorEnabled = enabled; } /** * @return Is posterior enabled. */ bool SurfaceMontageConfigurationCerebellar::isPosteriorEnabled() const { return m_posteriorEnabled; } /** * Set posterior enabled. * * @param enabled * New enabled status. */ void SurfaceMontageConfigurationCerebellar::setPosteriorEnabled(const bool enabled) { m_posteriorEnabled = enabled; } /** * Update the montage viewports using the current selected surfaces and settings. * * @param surfaceMontageViewports * Will be loaded with the montage viewports. */ void SurfaceMontageConfigurationCerebellar::updateSurfaceMontageViewports(std::vector& surfaceMontageViewports) { surfaceMontageViewports.clear(); std::vector anteriorViewports; std::vector dorsalViewports; std::vector posteriorViewports; std::vector ventralViewports; int32_t numFirst = 0; if (m_firstSurfaceEnabled) { Surface* surface = m_firstSurfaceSelectionModel->getSurface(); if (surface != NULL) { if (m_anteriorEnabled) { anteriorViewports.push_back(SurfaceMontageViewport(surface, ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_ANTERIOR)); numFirst++; } if (m_dorsalEnabled) { dorsalViewports.push_back(SurfaceMontageViewport(surface, ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_DORSAL)); numFirst++; } if (m_posteriorEnabled) { posteriorViewports.push_back(SurfaceMontageViewport(surface, ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_POSTERIOR)); numFirst++; } if (m_ventralEnabled) { ventralViewports.push_back(SurfaceMontageViewport(surface, ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_VENTRAL)); numFirst++; } } } int32_t numSecond = 0; if (m_secondSurfaceEnabled) { Surface* surface = m_secondSurfaceSelectionModel->getSurface(); if (surface != NULL) { if (m_anteriorEnabled) { anteriorViewports.push_back(SurfaceMontageViewport(surface, ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_ANTERIOR)); numSecond++; } if (m_dorsalEnabled) { dorsalViewports.push_back(SurfaceMontageViewport(surface, ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_DORSAL)); numSecond++; } if (m_posteriorEnabled) { posteriorViewports.push_back(SurfaceMontageViewport(surface, ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_POSTERIOR)); numSecond++; } if (m_ventralEnabled) { ventralViewports.push_back(SurfaceMontageViewport(surface, ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_VENTRAL)); numSecond++; } } } std::vector allViewports; allViewports.insert(allViewports.end(), dorsalViewports.begin(), dorsalViewports.end()); allViewports.insert(allViewports.end(), ventralViewports.begin(), ventralViewports.end()); allViewports.insert(allViewports.end(), anteriorViewports.begin(), anteriorViewports.end()); allViewports.insert(allViewports.end(), posteriorViewports.begin(), posteriorViewports.end()); const int32_t totalNum = numFirst + numSecond; CaretAssert(static_cast(allViewports.size()) == totalNum); if (totalNum == 1) { surfaceMontageViewports.insert(surfaceMontageViewports.end(), allViewports.begin(), allViewports.end()); CaretAssert(surfaceMontageViewports.size() == 1); surfaceMontageViewports[0].setRowAndColumn(0, 0); } else if (totalNum == 2) { surfaceMontageViewports.insert(surfaceMontageViewports.end(), allViewports.begin(), allViewports.end()); CaretAssert(surfaceMontageViewports.size() == 2); switch (getLayoutOrientation()) { case SurfaceMontageLayoutOrientationEnum::LANDSCAPE_LAYOUT_ORIENTATION: surfaceMontageViewports[0].setRowAndColumn(0, 0); surfaceMontageViewports[1].setRowAndColumn(0, 1); break; case SurfaceMontageLayoutOrientationEnum::PORTRAIT_LAYOUT_ORIENTATION: surfaceMontageViewports[0].setRowAndColumn(0, 0); surfaceMontageViewports[1].setRowAndColumn(1, 0); break; } } else if (totalNum == 3) { surfaceMontageViewports.insert(surfaceMontageViewports.end(), allViewports.begin(), allViewports.end()); CaretAssert(surfaceMontageViewports.size() == 3); for (int32_t i = 0; i < 3; i++) { SurfaceMontageViewport& svp = surfaceMontageViewports[i]; switch (svp.getProjectionViewType()) { case ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_ANTERIOR: svp.setRowAndColumn(1, 0); break; case ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_DORSAL: svp.setRowAndColumn(0, 0); break; case ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_POSTERIOR: svp.setRowAndColumn(1, 1); break; case ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_VENTRAL: svp.setRowAndColumn(0, 1); break; default: CaretAssert(0); } } } else if (totalNum == 4) { if ((numFirst == 4) || (numSecond == 4)){ surfaceMontageViewports.insert(surfaceMontageViewports.end(), allViewports.begin(), allViewports.end()); CaretAssert(surfaceMontageViewports.size() == 4); surfaceMontageViewports[0].setRowAndColumn(0, 0); surfaceMontageViewports[1].setRowAndColumn(0, 1); surfaceMontageViewports[2].setRowAndColumn(1, 0); surfaceMontageViewports[3].setRowAndColumn(1, 1); } else if (numFirst == numSecond) { surfaceMontageViewports.insert(surfaceMontageViewports.end(), allViewports.begin(), allViewports.end()); CaretAssert(surfaceMontageViewports.size() == 4); surfaceMontageViewports[0].setRowAndColumn(0, 0); surfaceMontageViewports[1].setRowAndColumn(0, 1); surfaceMontageViewports[2].setRowAndColumn(1, 0); surfaceMontageViewports[3].setRowAndColumn(1, 1); } else { CaretAssert(0); } } else if (totalNum == 6) { surfaceMontageViewports.insert(surfaceMontageViewports.end(), allViewports.begin(), allViewports.end()); CaretAssert(surfaceMontageViewports.size() == 6); switch (getLayoutOrientation()) { case SurfaceMontageLayoutOrientationEnum::LANDSCAPE_LAYOUT_ORIENTATION: surfaceMontageViewports[0].setRowAndColumn(0, 0); surfaceMontageViewports[1].setRowAndColumn(1, 0); surfaceMontageViewports[2].setRowAndColumn(0, 1); surfaceMontageViewports[3].setRowAndColumn(1, 1); surfaceMontageViewports[4].setRowAndColumn(0, 2); surfaceMontageViewports[5].setRowAndColumn(1, 2); break; case SurfaceMontageLayoutOrientationEnum::PORTRAIT_LAYOUT_ORIENTATION: surfaceMontageViewports[0].setRowAndColumn(0, 0); surfaceMontageViewports[1].setRowAndColumn(0, 1); surfaceMontageViewports[2].setRowAndColumn(1, 0); surfaceMontageViewports[3].setRowAndColumn(1, 1); surfaceMontageViewports[4].setRowAndColumn(2, 0); surfaceMontageViewports[5].setRowAndColumn(2, 1); break; } } else if (totalNum == 8) { surfaceMontageViewports.insert(surfaceMontageViewports.end(), allViewports.begin(), allViewports.end()); CaretAssert(surfaceMontageViewports.size() == 8); switch (getLayoutOrientation()) { case SurfaceMontageLayoutOrientationEnum::LANDSCAPE_LAYOUT_ORIENTATION: surfaceMontageViewports[0].setRowAndColumn(0, 0); surfaceMontageViewports[1].setRowAndColumn(0, 2); surfaceMontageViewports[2].setRowAndColumn(0, 1); surfaceMontageViewports[3].setRowAndColumn(0, 3); surfaceMontageViewports[4].setRowAndColumn(1, 0); surfaceMontageViewports[5].setRowAndColumn(1, 2); surfaceMontageViewports[6].setRowAndColumn(1, 1); surfaceMontageViewports[7].setRowAndColumn(1, 3); break; case SurfaceMontageLayoutOrientationEnum::PORTRAIT_LAYOUT_ORIENTATION: surfaceMontageViewports[0].setRowAndColumn(0, 0); surfaceMontageViewports[1].setRowAndColumn(0, 1); surfaceMontageViewports[2].setRowAndColumn(1, 0); surfaceMontageViewports[3].setRowAndColumn(1, 1); surfaceMontageViewports[4].setRowAndColumn(2, 0); surfaceMontageViewports[5].setRowAndColumn(2, 1); surfaceMontageViewports[6].setRowAndColumn(3, 0); surfaceMontageViewports[7].setRowAndColumn(3, 1); break; } } else if (totalNum > 0) { CaretAssert(0); } // const int32_t numViewports = static_cast(surfaceMontageViewports.size()); // CaretAssert(totalNum == numViewports); // // std::cout << "Orientation: " << SurfaceMontageLayoutOrientationEnum::toName(getLayoutOrientation()) << std::endl; // for (int32_t i = 0; i < numViewports; i++) { // const SurfaceMontageViewport& svp = surfaceMontageViewports[i]; // std::cout << qPrintable("(" // + AString::number(svp.getRow()) // + "," // + AString::number(svp.getColumn()) // + ") " // + ProjectionViewTypeEnum::toName(svp.getProjectionViewType())) // << std::endl; // } // std::cout << std::endl; } /** * Save members to the given scene class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * @param sceneClass * sceneClass to which information is added. */ void SurfaceMontageConfigurationCerebellar::saveMembersToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass) { m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); } /** * Restore information specific to the type of model from the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass from which information is restored. */ void SurfaceMontageConfigurationCerebellar::restoreMembersFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); } /** * Get a description of this object's content. * @return String describing this object's content. */ AString SurfaceMontageConfigurationCerebellar::toString() const { PlainTextStringBuilder tb; getDescriptionOfContent(tb); return tb.getText(); } /** * Get a text description of the instance's content. * * @param descriptionOut * Description of the instance's content. */ void SurfaceMontageConfigurationCerebellar::getDescriptionOfContent(PlainTextStringBuilder& descriptionOut) const { descriptionOut.addLine("Cerebellar Montage: "); descriptionOut.pushIndentation(); const Surface* firstSurface = getFirstSurfaceSelectionModel()->getSurface(); if (firstSurface != NULL) { if (isFirstSurfaceEnabled()) { descriptionOut.addLine("Surface:"); descriptionOut.pushIndentation(); firstSurface->getDescriptionOfContent(descriptionOut); descriptionOut.popIndentation(); } } const Surface* secondSurface = getSecondSurfaceSelectionModel()->getSurface(); if (secondSurface != NULL) { if (isSecondSurfaceEnabled()) { descriptionOut.addLine("Surface:"); descriptionOut.pushIndentation(); secondSurface->getDescriptionOfContent(descriptionOut); descriptionOut.popIndentation(); } } AString viewsMsg = "Selected Views: "; if (isAnteriorEnabled()) { viewsMsg += " Anterior"; } if (isDorsalEnabled()) { viewsMsg += " Dorsal"; } if (isPosteriorEnabled()) { viewsMsg += " Posterior"; } if (isVentralEnabled()) { viewsMsg += " Ventral"; } descriptionOut.addLine(viewsMsg); descriptionOut.popIndentation(); } /** * Get all surfaces displayed in this configuration. * * @param surfaceOut * Will contain all displayed surfaces upon exit. */ void SurfaceMontageConfigurationCerebellar::getDisplayedSurfaces(std::vector& surfacesOut) const { surfacesOut.clear(); const Surface* firstSurface = getFirstSurfaceSelectionModel()->getSurface(); if (firstSurface != NULL) { if (isFirstSurfaceEnabled()) { surfacesOut.push_back(const_cast(firstSurface)); } } const Surface* secondSurface = getSecondSurfaceSelectionModel()->getSurface(); if (secondSurface != NULL) { if (isSecondSurfaceEnabled()) { if (secondSurface != firstSurface) { surfacesOut.push_back(const_cast(secondSurface)); } } } } /** * Copy the given configuration to this configurtion. * * @param configuration. * Configuration that is copied. */ void SurfaceMontageConfigurationCerebellar::copyConfiguration(SurfaceMontageConfigurationAbstract* configuration) { SurfaceMontageConfigurationCerebellar* cerebellarConfiguration = dynamic_cast(configuration); CaretAssert(cerebellarConfiguration); SurfaceMontageConfigurationAbstract::copyConfiguration(configuration); m_firstSurfaceSelectionModel->setSurface(cerebellarConfiguration->m_firstSurfaceSelectionModel->getSurface()); m_secondSurfaceSelectionModel->setSurface(cerebellarConfiguration->m_secondSurfaceSelectionModel->getSurface()); m_firstSurfaceEnabled = cerebellarConfiguration->m_firstSurfaceEnabled; m_secondSurfaceEnabled = cerebellarConfiguration->m_secondSurfaceEnabled; m_dorsalEnabled = cerebellarConfiguration->m_dorsalEnabled; m_ventralEnabled = cerebellarConfiguration->m_ventralEnabled; m_posteriorEnabled = cerebellarConfiguration->m_posteriorEnabled; m_anteriorEnabled = cerebellarConfiguration->m_anteriorEnabled; } workbench-1.1.1/src/Brain/SurfaceMontageConfigurationCerebellar.h000066400000000000000000000103371255417355300251210ustar00rootroot00000000000000#ifndef __SURFACE_MONTAGE_CONFIGURATION_CEREBELLAR_H__ #define __SURFACE_MONTAGE_CONFIGURATION_CEREBELLAR_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "SurfaceMontageConfigurationAbstract.h" namespace caret { class SceneClassAssistant; class SurfaceSelectionModel; class SurfaceMontageConfigurationCerebellar : public SurfaceMontageConfigurationAbstract { public: SurfaceMontageConfigurationCerebellar(const int32_t tabIndex); virtual ~SurfaceMontageConfigurationCerebellar(); virtual void initializeSelectedSurfaces(); SurfaceSelectionModel* getFirstSurfaceSelectionModel(); const SurfaceSelectionModel* getFirstSurfaceSelectionModel() const; SurfaceSelectionModel* getSecondSurfaceSelectionModel(); const SurfaceSelectionModel* getSecondSurfaceSelectionModel() const; bool isFirstSurfaceEnabled() const; void setFirstSurfaceEnabled(const bool enabled); bool isSecondSurfaceEnabled() const; void setSecondSurfaceEnabled(const bool enabled); bool isDorsalEnabled() const; void setDorsalEnabled(const bool enabled); bool isVentralEnabled() const; void setVentralEnabled(const bool enabled); bool isAnteriorEnabled() const; void setAnteriorEnabled(const bool enabled); bool isPosteriorEnabled() const; void setPosteriorEnabled(const bool enabled); virtual void updateSurfaceMontageViewports(std::vector& surfaceMontageViewports); virtual bool isValid(); virtual void copyConfiguration(SurfaceMontageConfigurationAbstract* configuration); private: SurfaceMontageConfigurationCerebellar(const SurfaceMontageConfigurationCerebellar&); SurfaceMontageConfigurationCerebellar& operator=(const SurfaceMontageConfigurationCerebellar&); public: virtual AString toString() const; virtual void getDescriptionOfContent(PlainTextStringBuilder& descriptionOut) const; virtual void getDisplayedSurfaces(std::vector& surfacesOut) const; // ADD_NEW_METHODS_HERE protected: virtual void saveMembersToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass); virtual void restoreMembersFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private: SceneClassAssistant* m_sceneAssistant; SurfaceSelectionModel* m_firstSurfaceSelectionModel; SurfaceSelectionModel* m_secondSurfaceSelectionModel; bool m_firstSurfaceEnabled; bool m_secondSurfaceEnabled; bool m_dorsalEnabled; bool m_ventralEnabled; bool m_anteriorEnabled; bool m_posteriorEnabled; // ADD_NEW_MEMBERS_HERE friend class ModelSurfaceMontage; }; #ifdef __SURFACE_MONTAGE_CONFIGURATION_CEREBELLAR_DECLARE__ // #endif // __SURFACE_MONTAGE_CONFIGURATION_CEREBELLAR_DECLARE__ } // namespace #endif //__SURFACE_MONTAGE_CONFIGURATION_CEREBELLAR_H__ workbench-1.1.1/src/Brain/SurfaceMontageConfigurationCerebral.cxx000066400000000000000000000712141255417355300251540ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SURFACE_MONTAGE_CONFIGURATION_CEREBRAL_DECLARE__ #include "SurfaceMontageConfigurationCerebral.h" #undef __SURFACE_MONTAGE_CONFIGURATION_CEREBRAL_DECLARE__ #include "BrainStructure.h" #include "CaretAssert.h" #include "EventBrainStructureGetAll.h" #include "EventManager.h" #include "PlainTextStringBuilder.h" #include "SceneClass.h" #include "SceneClassAssistant.h" #include "Surface.h" #include "SurfaceSelectionModel.h" using namespace caret; /** * \class caret::SurfaceMontageConfigurationCerebral * \brief Surface montage cerebral cortext configuration. * \ingroup Brain */ /** * Constructor. */ SurfaceMontageConfigurationCerebral::SurfaceMontageConfigurationCerebral(const int32_t tabIndex) : SurfaceMontageConfigurationAbstract(SurfaceMontageConfigurationTypeEnum::CEREBRAL_CORTEX_CONFIGURATION, SUPPORTS_LAYOUT_ORIENTATION_YES) { std::vector validSurfaceTypes; SurfaceTypeEnum::getAllEnumsExceptFlat(validSurfaceTypes); m_leftFirstSurfaceSelectionModel = new SurfaceSelectionModel(StructureEnum::CORTEX_LEFT, validSurfaceTypes); m_leftSecondSurfaceSelectionModel = new SurfaceSelectionModel(StructureEnum::CORTEX_LEFT, validSurfaceTypes); m_rightFirstSurfaceSelectionModel = new SurfaceSelectionModel(StructureEnum::CORTEX_RIGHT, validSurfaceTypes); m_rightSecondSurfaceSelectionModel = new SurfaceSelectionModel(StructureEnum::CORTEX_RIGHT, validSurfaceTypes); m_leftEnabled = true; m_rightEnabled = true; m_firstSurfaceEnabled = true; m_secondSurfaceEnabled = false; m_lateralEnabled = true; m_medialEnabled = true; m_sceneAssistant = new SceneClassAssistant(); m_sceneAssistant->add("m_leftFirstSurfaceSelectionModel", "SurfaceSelectionModel", m_leftFirstSurfaceSelectionModel); m_sceneAssistant->add("m_leftSecondSurfaceSelectionModel", "SurfaceSelectionModel", m_leftSecondSurfaceSelectionModel); m_sceneAssistant->add("m_rightFirstSurfaceSelectionModel", "SurfaceSelectionModel", m_rightFirstSurfaceSelectionModel); m_sceneAssistant->add("m_rightSecondSurfaceSelectionModel", "SurfaceSelectionModel", m_rightSecondSurfaceSelectionModel); m_sceneAssistant->add("m_leftEnabled", &m_leftEnabled); m_sceneAssistant->add("m_rightEnabled", &m_rightEnabled); m_sceneAssistant->add("m_firstSurfaceEnabled", &m_firstSurfaceEnabled); m_sceneAssistant->add("m_secondSurfaceEnabled", &m_secondSurfaceEnabled); m_sceneAssistant->add("m_lateralEnabled", &m_lateralEnabled); m_sceneAssistant->add("m_medialEnabled", &m_medialEnabled); std::vector supportedStructures; supportedStructures.push_back(StructureEnum::CORTEX_LEFT); supportedStructures.push_back(StructureEnum::CORTEX_RIGHT); setupOverlaySet("Cerebral Montage", tabIndex, supportedStructures); } /** * Destructor. */ SurfaceMontageConfigurationCerebral::~SurfaceMontageConfigurationCerebral() { delete m_leftFirstSurfaceSelectionModel; delete m_leftSecondSurfaceSelectionModel; delete m_rightFirstSurfaceSelectionModel; delete m_rightSecondSurfaceSelectionModel; delete m_sceneAssistant; } /** * Initialize the selected surfaces. */ void SurfaceMontageConfigurationCerebral::initializeSelectedSurfaces() { EventBrainStructureGetAll brainStructureEvent; EventManager::get()->sendEvent(brainStructureEvent.getPointer()); Surface* leftAnatSurface = NULL; BrainStructure* leftBrainStructure = brainStructureEvent.getBrainStructureByStructure(StructureEnum::CORTEX_LEFT); if (leftBrainStructure != NULL) { leftAnatSurface = leftBrainStructure->getPrimaryAnatomicalSurface(); } Surface* rightAnatSurface = NULL; BrainStructure* rightBrainStructure = brainStructureEvent.getBrainStructureByStructure(StructureEnum::CORTEX_RIGHT); if (rightBrainStructure != NULL) { rightAnatSurface = rightBrainStructure->getPrimaryAnatomicalSurface(); } m_leftFirstSurfaceSelectionModel->setSurfaceToType(SurfaceTypeEnum::ANATOMICAL); if (leftAnatSurface != NULL) { m_leftFirstSurfaceSelectionModel->setSurface(leftAnatSurface); } m_leftSecondSurfaceSelectionModel->setSurfaceToType(SurfaceTypeEnum::INFLATED, SurfaceTypeEnum::VERY_INFLATED); m_rightFirstSurfaceSelectionModel->setSurfaceToType(SurfaceTypeEnum::ANATOMICAL); if (rightAnatSurface != NULL) { m_rightFirstSurfaceSelectionModel->setSurface(rightAnatSurface); } m_rightSecondSurfaceSelectionModel->setSurfaceToType(SurfaceTypeEnum::INFLATED, SurfaceTypeEnum::VERY_INFLATED); } /** * @return Is this configuration valid? */ bool SurfaceMontageConfigurationCerebral::isValid() { const bool valid = ((getLeftFirstSurfaceSelectionModel()->getSurface() != NULL) || (getRightFirstSurfaceSelectionModel()->getSurface() != NULL)); return valid; } /** * Update the montage viewports using the current selected surfaces and settings. * * @param surfaceMontageViewports * Will be loaded with the montage viewports. */ void SurfaceMontageConfigurationCerebral::updateSurfaceMontageViewports(std::vector& surfaceMontageViewports) { surfaceMontageViewports.clear(); std::vector leftLateralViewports; std::vector leftMedialViewports; std::vector rightLateralViewports; std::vector rightMedialViewports; if (m_leftEnabled) { if (m_firstSurfaceEnabled) { Surface* leftSurface = m_leftFirstSurfaceSelectionModel->getSurface(); if (leftSurface != NULL) { if (m_lateralEnabled) { SurfaceMontageViewport smv(leftSurface, ProjectionViewTypeEnum::PROJECTION_VIEW_LEFT_LATERAL); leftLateralViewports.push_back(smv); } if (m_medialEnabled) { SurfaceMontageViewport smv(leftSurface, ProjectionViewTypeEnum::PROJECTION_VIEW_LEFT_MEDIAL); leftMedialViewports.push_back(smv); } } } if (m_secondSurfaceEnabled) { Surface* leftSurface = m_leftSecondSurfaceSelectionModel->getSurface(); if (leftSurface != NULL) { if (m_lateralEnabled) { SurfaceMontageViewport smv(leftSurface, ProjectionViewTypeEnum::PROJECTION_VIEW_LEFT_LATERAL); leftLateralViewports.push_back(smv); } if (m_medialEnabled) { SurfaceMontageViewport smv(leftSurface, ProjectionViewTypeEnum::PROJECTION_VIEW_LEFT_MEDIAL); leftMedialViewports.push_back(smv); } } } } if (m_rightEnabled) { if (m_firstSurfaceEnabled) { Surface* rightSurface = m_rightFirstSurfaceSelectionModel->getSurface(); if (rightSurface != NULL) { if (m_lateralEnabled) { SurfaceMontageViewport smv(rightSurface, ProjectionViewTypeEnum::PROJECTION_VIEW_RIGHT_LATERAL); rightLateralViewports.push_back(smv); } if (m_medialEnabled) { SurfaceMontageViewport smv(rightSurface, ProjectionViewTypeEnum::PROJECTION_VIEW_RIGHT_MEDIAL); rightMedialViewports.push_back(smv); } } } } if (m_rightEnabled) { if (m_secondSurfaceEnabled) { Surface* rightSurface = m_rightSecondSurfaceSelectionModel->getSurface(); if (rightSurface != NULL) { if (m_lateralEnabled) { SurfaceMontageViewport smv(rightSurface, ProjectionViewTypeEnum::PROJECTION_VIEW_RIGHT_LATERAL); rightLateralViewports.push_back(smv); } if (m_medialEnabled) { SurfaceMontageViewport smv(rightSurface, ProjectionViewTypeEnum::PROJECTION_VIEW_RIGHT_MEDIAL); rightMedialViewports.push_back(smv); } } } } std::vector leftViewports; leftViewports.insert(leftViewports.end(), leftLateralViewports.begin(), leftLateralViewports.end()); leftViewports.insert(leftViewports.end(), leftMedialViewports.begin(), leftMedialViewports.end()); std::vector rightViewports; rightViewports.insert(rightViewports.end(), rightLateralViewports.begin(), rightLateralViewports.end()); rightViewports.insert(rightViewports.end(), rightMedialViewports.begin(), rightMedialViewports.end()); const int32_t numLeft = static_cast(leftViewports.size()); const int32_t numRight = static_cast(rightViewports.size()); const int32_t totalNum = numLeft + numRight; if (totalNum == 1) { surfaceMontageViewports.insert(surfaceMontageViewports.end(), leftViewports.begin(), leftViewports.end()); surfaceMontageViewports.insert(surfaceMontageViewports.end(), rightViewports.begin(), rightViewports.end()); CaretAssert(surfaceMontageViewports.size() == 1); surfaceMontageViewports[0].setRowAndColumn(0, 0); } else if (totalNum == 2) { surfaceMontageViewports.insert(surfaceMontageViewports.end(), leftViewports.begin(), leftViewports.end()); surfaceMontageViewports.insert(surfaceMontageViewports.end(), rightViewports.begin(), rightViewports.end()); CaretAssert(surfaceMontageViewports.size() == 2); switch (getLayoutOrientation()) { case SurfaceMontageLayoutOrientationEnum::LANDSCAPE_LAYOUT_ORIENTATION: surfaceMontageViewports[0].setRowAndColumn(0, 0); surfaceMontageViewports[1].setRowAndColumn(0, 1); break; case SurfaceMontageLayoutOrientationEnum::PORTRAIT_LAYOUT_ORIENTATION: surfaceMontageViewports[0].setRowAndColumn(0, 0); surfaceMontageViewports[1].setRowAndColumn(1, 0); break; } } else if (totalNum == 4) { if (numLeft == 4) { surfaceMontageViewports.insert(surfaceMontageViewports.end(), leftLateralViewports.begin(), leftLateralViewports.end()); surfaceMontageViewports.insert(surfaceMontageViewports.end(), leftMedialViewports.begin(), leftMedialViewports.end()); CaretAssert(surfaceMontageViewports.size() == 4); surfaceMontageViewports[0].setRowAndColumn(0, 0); surfaceMontageViewports[1].setRowAndColumn(1, 0); surfaceMontageViewports[2].setRowAndColumn(0, 1); surfaceMontageViewports[3].setRowAndColumn(1, 1); } else if (numRight == 4) { surfaceMontageViewports.insert(surfaceMontageViewports.end(), rightLateralViewports.begin(), rightLateralViewports.end()); surfaceMontageViewports.insert(surfaceMontageViewports.end(), rightMedialViewports.begin(), rightMedialViewports.end()); CaretAssert(surfaceMontageViewports.size() == 4); surfaceMontageViewports[0].setRowAndColumn(0, 0); surfaceMontageViewports[1].setRowAndColumn(1, 0); surfaceMontageViewports[2].setRowAndColumn(0, 1); surfaceMontageViewports[3].setRowAndColumn(1, 1); } else if (numLeft == numRight) { surfaceMontageViewports.insert(surfaceMontageViewports.end(), leftViewports.begin(), leftViewports.end()); surfaceMontageViewports.insert(surfaceMontageViewports.end(), rightViewports.begin(), rightViewports.end()); CaretAssert(surfaceMontageViewports.size() == 4); surfaceMontageViewports[0].setRowAndColumn(0, 0); surfaceMontageViewports[1].setRowAndColumn(1, 0); surfaceMontageViewports[2].setRowAndColumn(0, 1); surfaceMontageViewports[3].setRowAndColumn(1, 1); } else { CaretAssert(0); } } else if (totalNum == 8) { surfaceMontageViewports.insert(surfaceMontageViewports.end(), leftLateralViewports.begin(), leftLateralViewports.end()); surfaceMontageViewports.insert(surfaceMontageViewports.end(), leftMedialViewports.begin(), leftMedialViewports.end()); surfaceMontageViewports.insert(surfaceMontageViewports.end(), rightLateralViewports.begin(), rightLateralViewports.end()); surfaceMontageViewports.insert(surfaceMontageViewports.end(), rightMedialViewports.begin(), rightMedialViewports.end()); CaretAssert(surfaceMontageViewports.size() == 8); switch (getLayoutOrientation()) { case SurfaceMontageLayoutOrientationEnum::LANDSCAPE_LAYOUT_ORIENTATION: surfaceMontageViewports[0].setRowAndColumn(0, 0); surfaceMontageViewports[1].setRowAndColumn(1, 0); surfaceMontageViewports[2].setRowAndColumn(0, 1); surfaceMontageViewports[3].setRowAndColumn(1, 1); surfaceMontageViewports[4].setRowAndColumn(0, 2); surfaceMontageViewports[5].setRowAndColumn(1, 2); surfaceMontageViewports[6].setRowAndColumn(0, 3); surfaceMontageViewports[7].setRowAndColumn(1, 3); break; case SurfaceMontageLayoutOrientationEnum::PORTRAIT_LAYOUT_ORIENTATION: surfaceMontageViewports[0].setRowAndColumn(0, 0); surfaceMontageViewports[1].setRowAndColumn(1, 0); surfaceMontageViewports[2].setRowAndColumn(0, 1); surfaceMontageViewports[3].setRowAndColumn(1, 1); surfaceMontageViewports[4].setRowAndColumn(2, 0); surfaceMontageViewports[5].setRowAndColumn(3, 0); surfaceMontageViewports[6].setRowAndColumn(2, 1); surfaceMontageViewports[7].setRowAndColumn(3, 1); break; } } else if (totalNum > 0) { CaretAssert(0); } CaretAssert(totalNum == static_cast(surfaceMontageViewports.size())); // std::cout << "Orientation: " << SurfaceMontageLayoutOrientationEnum::toName(getLayoutOrientation()) << std::endl; // for (int32_t i = 0; i < numViewports; i++) { // const SurfaceMontageViewport& svp = surfaceMontageViewports[i]; // std::cout << qPrintable("(" // + AString::number(svp.getRow()) // + "," // + AString::number(svp.getColumn()) // + ") " // + ProjectionViewTypeEnum::toName(svp.getProjectionViewType())) // << std::endl; // } // std::cout << std::endl; } /** * Save members to the given scene class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * @param sceneClass * sceneClass to which information is added. */ void SurfaceMontageConfigurationCerebral::saveMembersToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass) { m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); } /** * Restore information specific to the type of model from the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass from which information is restored. */ void SurfaceMontageConfigurationCerebral::restoreMembersFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); } /** * @return Is left enabled? */ bool SurfaceMontageConfigurationCerebral::isLeftEnabled() const { return m_leftEnabled; } /** * Set left enabled * @param enabled * New status */ void SurfaceMontageConfigurationCerebral::setLeftEnabled(const bool enabled) { m_leftEnabled = enabled; } /** * @return Is right enabled? */ bool SurfaceMontageConfigurationCerebral::isRightEnabled() const { return m_rightEnabled; } /** * Set right enabled * @param enabled * New status */ void SurfaceMontageConfigurationCerebral::setRightEnabled(const bool enabled) { m_rightEnabled = enabled; } /** * @return Is lateral enabled? */ bool SurfaceMontageConfigurationCerebral::isLateralEnabled() const { return m_lateralEnabled; } /** * Set lateral enabled * @param enabled * New status */ void SurfaceMontageConfigurationCerebral::setLateralEnabled(const bool enabled) { m_lateralEnabled = enabled; } /** * @return Is medial enabled? */ bool SurfaceMontageConfigurationCerebral::isMedialEnabled() const { return m_medialEnabled; } /** * Set medial enabled * @param enabled * New status */ void SurfaceMontageConfigurationCerebral::setMedialEnabled(const bool enabled) { m_medialEnabled = enabled; } /** * @return Is enabled? */ bool SurfaceMontageConfigurationCerebral::isFirstSurfaceEnabled() const { return m_firstSurfaceEnabled; } /** * Set first surface enabled * @param enabled * New status */ void SurfaceMontageConfigurationCerebral::setFirstSurfaceEnabled(const bool enabled) { m_firstSurfaceEnabled = enabled; } /** * @return Is first surfce enabled? */ bool SurfaceMontageConfigurationCerebral::isSecondSurfaceEnabled() const { return m_secondSurfaceEnabled; } /** * Set second surface enabled * @param enabled * New status */ void SurfaceMontageConfigurationCerebral::setSecondSurfaceEnabled(const bool enabled) { m_secondSurfaceEnabled = enabled; } /** * @return the left first surface selection in this configuration. */ SurfaceSelectionModel* SurfaceMontageConfigurationCerebral::getLeftFirstSurfaceSelectionModel() { return m_leftFirstSurfaceSelectionModel; } /** * @return the left first surface selection in this configuration. */ const SurfaceSelectionModel* SurfaceMontageConfigurationCerebral::getLeftFirstSurfaceSelectionModel() const { return m_leftFirstSurfaceSelectionModel; } /** * @return the left second surface selection in this configuration. */ SurfaceSelectionModel* SurfaceMontageConfigurationCerebral::getLeftSecondSurfaceSelectionModel() { return m_leftSecondSurfaceSelectionModel; } /** * @return the left second surface selection in this configuration. */ const SurfaceSelectionModel* SurfaceMontageConfigurationCerebral::getLeftSecondSurfaceSelectionModel() const { return m_leftSecondSurfaceSelectionModel; } /** * @return the right first surface selection in this configuration. */ SurfaceSelectionModel* SurfaceMontageConfigurationCerebral::getRightFirstSurfaceSelectionModel() { return m_rightFirstSurfaceSelectionModel; } /** * @return the right first surface selection in this configuration. */ const SurfaceSelectionModel* SurfaceMontageConfigurationCerebral::getRightFirstSurfaceSelectionModel() const { return m_rightFirstSurfaceSelectionModel; } /** * @return the right second surface selection in this configuration. */ SurfaceSelectionModel* SurfaceMontageConfigurationCerebral::getRightSecondSurfaceSelectionModel() { return m_rightSecondSurfaceSelectionModel; } /** * @return the right second surface selection in this configuration. */ const SurfaceSelectionModel* SurfaceMontageConfigurationCerebral::getRightSecondSurfaceSelectionModel() const { return m_rightSecondSurfaceSelectionModel; } /** * Get a description of this object's content. * @return String describing this object's content. */ AString SurfaceMontageConfigurationCerebral::toString() const { PlainTextStringBuilder tb; getDescriptionOfContent(tb); return tb.getText(); } /** * Get a text description of the instance's content. * * @param descriptionOut * Description of the instance's content. */ void SurfaceMontageConfigurationCerebral::getDescriptionOfContent(PlainTextStringBuilder& descriptionOut) const { AString msg; descriptionOut.addLine("Cerebral Montage: "); descriptionOut.pushIndentation(); if (isLeftEnabled()) { if (isFirstSurfaceEnabled()) { const Surface* firstLeftSurface = getLeftFirstSurfaceSelectionModel()->getSurface(); if (firstLeftSurface != NULL) { descriptionOut.addLine("Left Surface:"); descriptionOut.pushIndentation(); firstLeftSurface->getDescriptionOfContent(descriptionOut); descriptionOut.popIndentation(); } } if (isSecondSurfaceEnabled()) { const Surface* secondLeftSurface = getLeftSecondSurfaceSelectionModel()->getSurface(); if (secondLeftSurface != NULL) { descriptionOut.addLine("Left Surface:"); descriptionOut.pushIndentation(); secondLeftSurface->getDescriptionOfContent(descriptionOut); descriptionOut.popIndentation(); } } } if (isRightEnabled()) { if (isFirstSurfaceEnabled()) { const Surface* firstRightSurface = getRightFirstSurfaceSelectionModel()->getSurface(); if (firstRightSurface != NULL) { descriptionOut.addLine("Right Surface:"); descriptionOut.pushIndentation(); firstRightSurface->getDescriptionOfContent(descriptionOut); descriptionOut.popIndentation(); } } if (isSecondSurfaceEnabled()) { const Surface* secondRightSurface = getRightSecondSurfaceSelectionModel()->getSurface(); if (secondRightSurface != NULL) { descriptionOut.addLine("Right Surface:"); descriptionOut.pushIndentation(); secondRightSurface->getDescriptionOfContent(descriptionOut); descriptionOut.popIndentation(); } } } AString viewsMsg = "Selected Views: "; if (isLateralEnabled()) { viewsMsg += " Lateral"; } if (isMedialEnabled()) { viewsMsg += " Medial"; } descriptionOut.addLine(viewsMsg); descriptionOut.popIndentation(); } /** * Get all surfaces displayed in this configuration. * * @param surfaceOut * Will contain all displayed surfaces upon exit. */ void SurfaceMontageConfigurationCerebral::getDisplayedSurfaces(std::vector& surfacesOut) const { surfacesOut.clear(); if (isLeftEnabled()) { Surface* firstLeftSurface = NULL;; if (isFirstSurfaceEnabled()) { firstLeftSurface = const_cast(getLeftFirstSurfaceSelectionModel()->getSurface()); if (firstLeftSurface != NULL) { surfacesOut.push_back(const_cast(firstLeftSurface)); } } if (isSecondSurfaceEnabled()) { const Surface* secondLeftSurface = getLeftSecondSurfaceSelectionModel()->getSurface(); if (secondLeftSurface != NULL) { if (secondLeftSurface != firstLeftSurface) { surfacesOut.push_back(const_cast(secondLeftSurface)); } } } } if (isRightEnabled()) { Surface* firstRightSurface = NULL; if (isFirstSurfaceEnabled()) { firstRightSurface = const_cast(getRightFirstSurfaceSelectionModel()->getSurface()); if (firstRightSurface != NULL) { surfacesOut.push_back(const_cast(firstRightSurface)); } } if (isSecondSurfaceEnabled()) { const Surface* secondRightSurface = getRightSecondSurfaceSelectionModel()->getSurface(); if (secondRightSurface != NULL) { if (secondRightSurface != firstRightSurface) { surfacesOut.push_back(const_cast(secondRightSurface)); } } } } } /** * Copy the given configuration to this configurtion. * * @param configuration. * Configuration that is copied. */ void SurfaceMontageConfigurationCerebral::copyConfiguration(SurfaceMontageConfigurationAbstract* configuration) { SurfaceMontageConfigurationAbstract::copyConfiguration(configuration); SurfaceMontageConfigurationCerebral* cerebralConfiguration = dynamic_cast(configuration); CaretAssert(cerebralConfiguration); m_leftFirstSurfaceSelectionModel->setSurface(cerebralConfiguration->m_leftFirstSurfaceSelectionModel->getSurface()); m_leftSecondSurfaceSelectionModel->setSurface(cerebralConfiguration->m_leftSecondSurfaceSelectionModel->getSurface()); m_rightFirstSurfaceSelectionModel->setSurface(cerebralConfiguration->m_rightFirstSurfaceSelectionModel->getSurface()); m_rightSecondSurfaceSelectionModel->setSurface(cerebralConfiguration->m_rightSecondSurfaceSelectionModel->getSurface()); m_leftEnabled = cerebralConfiguration->m_leftEnabled; m_rightEnabled = cerebralConfiguration->m_rightEnabled; m_firstSurfaceEnabled = cerebralConfiguration->m_firstSurfaceEnabled; m_secondSurfaceEnabled = cerebralConfiguration->m_secondSurfaceEnabled; m_lateralEnabled = cerebralConfiguration->m_lateralEnabled; m_medialEnabled = cerebralConfiguration->m_medialEnabled; } workbench-1.1.1/src/Brain/SurfaceMontageConfigurationCerebral.h000066400000000000000000000112531255417355300245760ustar00rootroot00000000000000#ifndef __SURFACE_MONTAGE_CONFIGURATION_CEREBRAL_H__ #define __SURFACE_MONTAGE_CONFIGURATION_CEREBRAL_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "SurfaceMontageConfigurationAbstract.h" namespace caret { class SceneClassAssistant; class SurfaceSelectionModel; class SurfaceMontageConfigurationCerebral : public SurfaceMontageConfigurationAbstract { public: SurfaceMontageConfigurationCerebral(const int32_t tabIndex); virtual ~SurfaceMontageConfigurationCerebral(); SurfaceSelectionModel* getLeftFirstSurfaceSelectionModel(); const SurfaceSelectionModel* getLeftFirstSurfaceSelectionModel() const; SurfaceSelectionModel* getLeftSecondSurfaceSelectionModel(); const SurfaceSelectionModel* getLeftSecondSurfaceSelectionModel() const; SurfaceSelectionModel* getRightFirstSurfaceSelectionModel(); const SurfaceSelectionModel* getRightFirstSurfaceSelectionModel() const; SurfaceSelectionModel* getRightSecondSurfaceSelectionModel(); const SurfaceSelectionModel* getRightSecondSurfaceSelectionModel() const; bool isLeftEnabled() const; void setLeftEnabled(const bool enabled); bool isRightEnabled() const; void setRightEnabled(const bool enabled); bool isLateralEnabled() const; void setLateralEnabled(const bool enabled); bool isMedialEnabled() const; void setMedialEnabled(const bool enabled); bool isFirstSurfaceEnabled() const; void setFirstSurfaceEnabled(const bool enabled); bool isSecondSurfaceEnabled() const; void setSecondSurfaceEnabled(const bool enabled); virtual void initializeSelectedSurfaces(); virtual bool isValid(); virtual void updateSurfaceMontageViewports(std::vector& surfaceMontageViewports); virtual void copyConfiguration(SurfaceMontageConfigurationAbstract* configuration); private: SurfaceMontageConfigurationCerebral(const SurfaceMontageConfigurationCerebral&); SurfaceMontageConfigurationCerebral& operator=(const SurfaceMontageConfigurationCerebral&); public: virtual AString toString() const; virtual void getDescriptionOfContent(PlainTextStringBuilder& descriptionOut) const; virtual void getDisplayedSurfaces(std::vector& surfacesOut) const; // ADD_NEW_METHODS_HERE protected: virtual void saveMembersToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass); virtual void restoreMembersFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private: SceneClassAssistant* m_sceneAssistant; SurfaceSelectionModel* m_leftFirstSurfaceSelectionModel; SurfaceSelectionModel* m_leftSecondSurfaceSelectionModel; SurfaceSelectionModel* m_rightFirstSurfaceSelectionModel; SurfaceSelectionModel* m_rightSecondSurfaceSelectionModel; bool m_leftEnabled; bool m_rightEnabled; bool m_firstSurfaceEnabled; bool m_secondSurfaceEnabled; bool m_lateralEnabled; bool m_medialEnabled; // ADD_NEW_MEMBERS_HERE friend class ModelSurfaceMontage; }; #ifdef __SURFACE_MONTAGE_CONFIGURATION_CEREBRAL_DECLARE__ // #endif // __SURFACE_MONTAGE_CONFIGURATION_CEREBRAL_DECLARE__ } // namespace #endif //__SURFACE_MONTAGE_CONFIGURATION_CEREBRAL_H__ workbench-1.1.1/src/Brain/SurfaceMontageConfigurationFlatMaps.cxx000066400000000000000000000325531255417355300251470ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SURFACE_MONTAGE_CONFIGURATION_FLAT_MAPS_DECLARE__ #include "SurfaceMontageConfigurationFlatMaps.h" #undef __SURFACE_MONTAGE_CONFIGURATION_FLAT_MAPS_DECLARE__ #include "CaretAssert.h" #include "PlainTextStringBuilder.h" #include "SceneClass.h" #include "SceneClassAssistant.h" #include "Surface.h" #include "SurfaceSelectionModel.h" using namespace caret; /** * \class caret::SurfaceMontageConfigurationFlatMaps * \brief Surface montage configuration for flat maps. * \ingroup Brain */ /** * Constructor. */ SurfaceMontageConfigurationFlatMaps::SurfaceMontageConfigurationFlatMaps(const int32_t tabIndex) : SurfaceMontageConfigurationAbstract(SurfaceMontageConfigurationTypeEnum::FLAT_CONFIGURATION, SUPPORTS_LAYOUT_ORIENTATION_NO) { std::vector validSurfaceTypes; validSurfaceTypes.push_back(SurfaceTypeEnum::FLAT); m_leftSurfaceSelectionModel = new SurfaceSelectionModel(StructureEnum::CORTEX_LEFT, validSurfaceTypes); m_rightSurfaceSelectionModel = new SurfaceSelectionModel(StructureEnum::CORTEX_RIGHT, validSurfaceTypes); m_cerebellumSurfaceSelectionModel = new SurfaceSelectionModel(StructureEnum::CEREBELLUM, validSurfaceTypes); m_leftEnabled = true; m_rightEnabled = true; m_cerebellumEnabled = true; m_sceneAssistant = new SceneClassAssistant(); m_sceneAssistant->add("m_leftSurfaceSelectionModel", "SurfaceSelectionModel", m_leftSurfaceSelectionModel); m_sceneAssistant->add("m_rightSurfaceSelectionModel", "SurfaceSelectionModel", m_rightSurfaceSelectionModel); m_sceneAssistant->add("m_cerebellumSurfaceSelectionModel", "SurfaceSelectionModel", m_cerebellumSurfaceSelectionModel); m_sceneAssistant->add("m_leftEnabled", &m_leftEnabled); m_sceneAssistant->add("m_rightEnabled", &m_rightEnabled); m_sceneAssistant->add("m_cerebellumEnabled", &m_cerebellumEnabled); std::vector supportedStructures; supportedStructures.push_back(StructureEnum::CEREBELLUM); supportedStructures.push_back(StructureEnum::CORTEX_LEFT); supportedStructures.push_back(StructureEnum::CORTEX_RIGHT); setupOverlaySet("Flat Montage", tabIndex, supportedStructures); } /** * Destructor. */ SurfaceMontageConfigurationFlatMaps::~SurfaceMontageConfigurationFlatMaps() { delete m_leftSurfaceSelectionModel; delete m_rightSurfaceSelectionModel; delete m_cerebellumSurfaceSelectionModel; delete m_sceneAssistant; } /** * Initialize the selected surfaces. */ void SurfaceMontageConfigurationFlatMaps::initializeSelectedSurfaces() { } /** * @return Is this configuration valid? */ bool SurfaceMontageConfigurationFlatMaps::isValid() { const bool valid = ((getLeftSurfaceSelectionModel()->getSurface() != NULL) || (getRightSurfaceSelectionModel()->getSurface() != NULL) || (getCerebellumSurfaceSelectionModel()->getSurface() != NULL)); return valid; } /** * @return Is left enabled? */ bool SurfaceMontageConfigurationFlatMaps::isLeftEnabled() const { return m_leftEnabled; } /** * Set left enabled * @param enabled * New status */ void SurfaceMontageConfigurationFlatMaps::setLeftEnabled(const bool enabled) { m_leftEnabled = enabled; } /** * @return Is right enabled? */ bool SurfaceMontageConfigurationFlatMaps::isRightEnabled() const { return m_rightEnabled; } /** * Set right enabled * @param enabled * New status */ void SurfaceMontageConfigurationFlatMaps::setRightEnabled(const bool enabled) { m_rightEnabled = enabled; } /** * @return Is cerebellum enabled? */ bool SurfaceMontageConfigurationFlatMaps::isCerebellumEnabled() const { return m_cerebellumEnabled; } /** * Set cerebellum enabled * @param enabled * New status */ void SurfaceMontageConfigurationFlatMaps::setCerebellumEnabled(const bool enabled) { m_cerebellumEnabled = enabled; } /** * @return the left surface selection in this configuration. */ SurfaceSelectionModel* SurfaceMontageConfigurationFlatMaps::getLeftSurfaceSelectionModel() { return m_leftSurfaceSelectionModel; } /** * @return the left surface selection in this configuration. */ const SurfaceSelectionModel* SurfaceMontageConfigurationFlatMaps::getLeftSurfaceSelectionModel() const { return m_leftSurfaceSelectionModel; } /** * @return the right surface selection in this configuration. */ SurfaceSelectionModel* SurfaceMontageConfigurationFlatMaps::getRightSurfaceSelectionModel() { return m_rightSurfaceSelectionModel; } /** * @return the right surface selection in this configuration. */ const SurfaceSelectionModel* SurfaceMontageConfigurationFlatMaps::getRightSurfaceSelectionModel() const { return m_rightSurfaceSelectionModel; } /** * @return the cerebellum surface selection in this configuration. */ SurfaceSelectionModel* SurfaceMontageConfigurationFlatMaps::getCerebellumSurfaceSelectionModel() { return m_cerebellumSurfaceSelectionModel; } /** * @return the cerebellum surface selection in this configuration. */ const SurfaceSelectionModel* SurfaceMontageConfigurationFlatMaps::getCerebellumSurfaceSelectionModel() const { return m_cerebellumSurfaceSelectionModel; } /** * Update the montage viewports using the current selected surfaces and settings. * * @param surfaceMontageViewports * Will be loaded with the montage viewports. */ void SurfaceMontageConfigurationFlatMaps::updateSurfaceMontageViewports(std::vector& surfaceMontageViewports) { surfaceMontageViewports.clear(); if (m_leftEnabled) { Surface* leftSurface = m_leftSurfaceSelectionModel->getSurface(); if (leftSurface != NULL) { SurfaceMontageViewport smv(leftSurface, ProjectionViewTypeEnum::PROJECTION_VIEW_LEFT_FLAT_SURFACE); surfaceMontageViewports.push_back(smv); } } if (m_rightEnabled) { Surface* rightSurface = m_rightSurfaceSelectionModel->getSurface(); if (rightSurface != NULL) { SurfaceMontageViewport smv(rightSurface, ProjectionViewTypeEnum::PROJECTION_VIEW_RIGHT_FLAT_SURFACE); surfaceMontageViewports.push_back(smv); } } if (m_cerebellumEnabled) { Surface* cerebellumSurface = m_cerebellumSurfaceSelectionModel->getSurface(); if (cerebellumSurface != NULL) { SurfaceMontageViewport smv(cerebellumSurface, ProjectionViewTypeEnum::PROJECTION_VIEW_CEREBELLUM_FLAT_SURFACE); surfaceMontageViewports.push_back(smv); } } const int32_t numSurfaces = static_cast(surfaceMontageViewports.size()); for (int32_t i = 0; i < numSurfaces; i++) { switch (getLayoutOrientation()) { case SurfaceMontageLayoutOrientationEnum::LANDSCAPE_LAYOUT_ORIENTATION: surfaceMontageViewports[i].setRowAndColumn(0, i); break; case SurfaceMontageLayoutOrientationEnum::PORTRAIT_LAYOUT_ORIENTATION: surfaceMontageViewports[i].setRowAndColumn(i, 0); break; } } } /** * Save members to the given scene class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * @param sceneClass * sceneClass to which information is added. */ void SurfaceMontageConfigurationFlatMaps::saveMembersToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass) { m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); } /** * Restore information specific to the type of model from the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass from which information is restored. */ void SurfaceMontageConfigurationFlatMaps::restoreMembersFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); } /** * Get a description of this object's content. * @return String describing this object's content. */ AString SurfaceMontageConfigurationFlatMaps::toString() const { PlainTextStringBuilder tb; getDescriptionOfContent(tb); return tb.getText(); } /** * Get a text description of the instance's content. * * @param descriptionOut * Description of the instance's content. */ void SurfaceMontageConfigurationFlatMaps::getDescriptionOfContent(PlainTextStringBuilder& descriptionOut) const { descriptionOut.addLine("Flat Montage: "); if (isLeftEnabled()) { const Surface* firstLeftSurface = getLeftSurfaceSelectionModel()->getSurface(); if (firstLeftSurface != NULL) { descriptionOut.addLine("Left Surface:"); descriptionOut.pushIndentation(); firstLeftSurface->getDescriptionOfContent(descriptionOut); descriptionOut.popIndentation(); } } if (isRightEnabled()) { const Surface* firstRightSurface = getRightSurfaceSelectionModel()->getSurface(); if (firstRightSurface != NULL) { descriptionOut.addLine("Right Surface:"); descriptionOut.pushIndentation(); firstRightSurface->getDescriptionOfContent(descriptionOut); descriptionOut.popIndentation(); } } if (isCerebellumEnabled()) { const Surface* cerebellumSurface = getCerebellumSurfaceSelectionModel()->getSurface(); if (cerebellumSurface != NULL) { descriptionOut.addLine("Cerebellum Surface:"); descriptionOut.pushIndentation(); cerebellumSurface->getDescriptionOfContent(descriptionOut); descriptionOut.popIndentation(); } } } /** * Get all surfaces displayed in this configuration. * * @param surfaceOut * Will contain all displayed surfaces upon exit. */ void SurfaceMontageConfigurationFlatMaps::getDisplayedSurfaces(std::vector& surfacesOut) const { surfacesOut.clear(); if (isLeftEnabled()) { const Surface* firstLeftSurface = getLeftSurfaceSelectionModel()->getSurface(); if (firstLeftSurface != NULL) { surfacesOut.push_back(const_cast(firstLeftSurface)); } } if (isRightEnabled()) { const Surface* firstRightSurface = getRightSurfaceSelectionModel()->getSurface(); if (firstRightSurface != NULL) { surfacesOut.push_back(const_cast(firstRightSurface)); } } if (isCerebellumEnabled()) { const Surface* cerebellumSurface = getCerebellumSurfaceSelectionModel()->getSurface(); if (cerebellumSurface != NULL) { surfacesOut.push_back(const_cast(cerebellumSurface)); } } } /** * Copy the given configuration to this configurtion. * * @param configuration. * Configuration that is copied. */ void SurfaceMontageConfigurationFlatMaps::copyConfiguration(SurfaceMontageConfigurationAbstract* configuration) { SurfaceMontageConfigurationAbstract::copyConfiguration(configuration); SurfaceMontageConfigurationFlatMaps* flatConfiguration = dynamic_cast(configuration); CaretAssert(flatConfiguration); m_leftSurfaceSelectionModel->setSurface(flatConfiguration->m_leftSurfaceSelectionModel->getSurface()); m_rightSurfaceSelectionModel->setSurface(flatConfiguration->m_rightSurfaceSelectionModel->getSurface()); m_cerebellumSurfaceSelectionModel->setSurface(flatConfiguration->m_cerebellumSurfaceSelectionModel->getSurface()); m_leftEnabled = flatConfiguration->m_leftEnabled; m_rightEnabled = flatConfiguration->m_rightEnabled; m_cerebellumEnabled = flatConfiguration->m_cerebellumEnabled; } workbench-1.1.1/src/Brain/SurfaceMontageConfigurationFlatMaps.h000066400000000000000000000077211255417355300245730ustar00rootroot00000000000000#ifndef __SURFACE_MONTAGE_CONFIGURATION_FLAT_MAPS_H__ #define __SURFACE_MONTAGE_CONFIGURATION_FLAT_MAPS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "SurfaceMontageConfigurationAbstract.h" namespace caret { class SceneClassAssistant; class SurfaceSelectionModel; class SurfaceMontageConfigurationFlatMaps : public SurfaceMontageConfigurationAbstract { public: SurfaceMontageConfigurationFlatMaps(const int32_t tabIndex); virtual ~SurfaceMontageConfigurationFlatMaps(); virtual void initializeSelectedSurfaces(); virtual bool isValid(); SurfaceSelectionModel* getLeftSurfaceSelectionModel(); const SurfaceSelectionModel* getLeftSurfaceSelectionModel() const; SurfaceSelectionModel* getRightSurfaceSelectionModel(); const SurfaceSelectionModel* getRightSurfaceSelectionModel() const; SurfaceSelectionModel* getCerebellumSurfaceSelectionModel(); const SurfaceSelectionModel* getCerebellumSurfaceSelectionModel() const; bool isLeftEnabled() const; void setLeftEnabled(const bool enabled); bool isRightEnabled() const; void setRightEnabled(const bool enabled); bool isCerebellumEnabled() const; void setCerebellumEnabled(const bool enabled); virtual void updateSurfaceMontageViewports(std::vector& surfaceMontageViewports); virtual void copyConfiguration(SurfaceMontageConfigurationAbstract* configuration); private: SurfaceMontageConfigurationFlatMaps(const SurfaceMontageConfigurationFlatMaps&); SurfaceMontageConfigurationFlatMaps& operator=(const SurfaceMontageConfigurationFlatMaps&); public: virtual AString toString() const; virtual void getDescriptionOfContent(PlainTextStringBuilder& descriptionOut) const; virtual void getDisplayedSurfaces(std::vector& surfacesOut) const; // ADD_NEW_METHODS_HERE protected: virtual void saveMembersToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass); virtual void restoreMembersFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private: SceneClassAssistant* m_sceneAssistant; SurfaceSelectionModel* m_leftSurfaceSelectionModel; SurfaceSelectionModel* m_rightSurfaceSelectionModel; SurfaceSelectionModel* m_cerebellumSurfaceSelectionModel; bool m_leftEnabled; bool m_rightEnabled; bool m_cerebellumEnabled; // ADD_NEW_MEMBERS_HERE friend class ModelSurfaceMontage; }; #ifdef __SURFACE_MONTAGE_CONFIGURATION_FLAT_MAPS_DECLARE__ // #endif // __SURFACE_MONTAGE_CONFIGURATION_FLAT_MAPS_DECLARE__ } // namespace #endif //__SURFACE_MONTAGE_CONFIGURATION_FLAT_MAPS_H__ workbench-1.1.1/src/Brain/SurfaceMontageConfigurationTypeEnum.cxx000066400000000000000000000272031255417355300252020ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __SURFACE_MONTAGE_CONFIGURATION_TYPE_ENUM_DECLARE__ #include "SurfaceMontageConfigurationTypeEnum.h" #undef __SURFACE_MONTAGE_CONFIGURATION_TYPE_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::SurfaceMontageConfigurationTypeEnum * \brief * * * * Using this enumerated type in the GUI with an EnumComboBoxTemplate * * Header File (.h) * Forward declare the data type: * class EnumComboBoxTemplate; * * Declare the member: * EnumComboBoxTemplate* m_surfaceMontageConfigurationTypeEnumComboBox; * * Declare a slot that is called when user changes selection * private slots: * void surfaceMontageConfigurationTypeEnumComboBoxItemActivated(); * * Implementation File (.cxx) * Include the header files * #include "EnumComboBoxTemplate.h" * #include "SurfaceMontageConfigurationTypeEnum.h" * * Instatiate: * m_surfaceMontageConfigurationTypeEnumComboBox = new EnumComboBoxTemplate(this); * m_surfaceMontageConfigurationTypeEnumComboBox->setup(); * * Get notified when the user changes the selection: * QObject::connect(m_surfaceMontageConfigurationTypeEnumComboBox, SIGNAL(itemActivated()), * this, SLOT(surfaceMontageConfigurationTypeEnumComboBoxItemActivated())); * * Update the selection: * m_surfaceMontageConfigurationTypeEnumComboBox->setSelectedItem(NEW_VALUE); * * Read the selection: * const SurfaceMontageConfigurationTypeEnum::Enum VARIABLE = m_surfaceMontageConfigurationTypeEnumComboBox->getSelectedItem(); * */ /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ SurfaceMontageConfigurationTypeEnum::SurfaceMontageConfigurationTypeEnum(const Enum enumValue, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCodeCounter++; this->name = name; this->guiName = guiName; } /** * Destructor. */ SurfaceMontageConfigurationTypeEnum::~SurfaceMontageConfigurationTypeEnum() { } /** * Initialize the enumerated metadata. */ void SurfaceMontageConfigurationTypeEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(SurfaceMontageConfigurationTypeEnum(CEREBELLAR_CORTEX_CONFIGURATION, "CEREBELLAR_CORTEX_CONFIGURATION", "Cerebellar Cortex")); enumData.push_back(SurfaceMontageConfigurationTypeEnum(CEREBRAL_CORTEX_CONFIGURATION, "CEREBRAL_CORTEX_CONFIGURATION", "Cerebral Cortex")); enumData.push_back(SurfaceMontageConfigurationTypeEnum(FLAT_CONFIGURATION, "FLAT_CONFIGURATION", "Flat Maps")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const SurfaceMontageConfigurationTypeEnum* SurfaceMontageConfigurationTypeEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const SurfaceMontageConfigurationTypeEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString SurfaceMontageConfigurationTypeEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const SurfaceMontageConfigurationTypeEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ SurfaceMontageConfigurationTypeEnum::Enum SurfaceMontageConfigurationTypeEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = SurfaceMontageConfigurationTypeEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const SurfaceMontageConfigurationTypeEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type SurfaceMontageConfigurationTypeEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString SurfaceMontageConfigurationTypeEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const SurfaceMontageConfigurationTypeEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ SurfaceMontageConfigurationTypeEnum::Enum SurfaceMontageConfigurationTypeEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = SurfaceMontageConfigurationTypeEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const SurfaceMontageConfigurationTypeEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type SurfaceMontageConfigurationTypeEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t SurfaceMontageConfigurationTypeEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const SurfaceMontageConfigurationTypeEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ SurfaceMontageConfigurationTypeEnum::Enum SurfaceMontageConfigurationTypeEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = SurfaceMontageConfigurationTypeEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const SurfaceMontageConfigurationTypeEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type SurfaceMontageConfigurationTypeEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void SurfaceMontageConfigurationTypeEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void SurfaceMontageConfigurationTypeEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(SurfaceMontageConfigurationTypeEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void SurfaceMontageConfigurationTypeEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(SurfaceMontageConfigurationTypeEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } workbench-1.1.1/src/Brain/SurfaceMontageConfigurationTypeEnum.h000066400000000000000000000065311255417355300246300ustar00rootroot00000000000000#ifndef __SURFACE_MONTAGE_CONFIGURATION_TYPE_ENUM_H__ #define __SURFACE_MONTAGE_CONFIGURATION_TYPE_ENUM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class SurfaceMontageConfigurationTypeEnum { public: /** * Enumerated values. */ enum Enum { /** */ CEREBELLAR_CORTEX_CONFIGURATION, /** */ CEREBRAL_CORTEX_CONFIGURATION, /** */ FLAT_CONFIGURATION }; ~SurfaceMontageConfigurationTypeEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); private: SurfaceMontageConfigurationTypeEnum(const Enum enumValue, const AString& name, const AString& guiName); static const SurfaceMontageConfigurationTypeEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; #ifdef __SURFACE_MONTAGE_CONFIGURATION_TYPE_ENUM_DECLARE__ std::vector SurfaceMontageConfigurationTypeEnum::enumData; bool SurfaceMontageConfigurationTypeEnum::initializedFlag = false; int32_t SurfaceMontageConfigurationTypeEnum::integerCodeCounter = 0; #endif // __SURFACE_MONTAGE_CONFIGURATION_TYPE_ENUM_DECLARE__ } // namespace #endif //__SURFACE_MONTAGE_CONFIGURATION_TYPE_ENUM_H__ workbench-1.1.1/src/Brain/SurfaceMontageLayoutOrientationEnum.cxx000066400000000000000000000266471255417355300252350ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __SURFACE_MONTAGE_LAYOUT_ORIENTATION_ENUM_DECLARE__ #include "SurfaceMontageLayoutOrientationEnum.h" #undef __SURFACE_MONTAGE_LAYOUT_ORIENTATION_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::SurfaceMontageLayoutOrientationEnum * \brief * * * * Using this enumerated type in the GUI with an EnumComboBoxTemplate * * Header File (.h) * Forward declare the data type: * class EnumComboBoxTemplate; * * Declare the member: * EnumComboBoxTemplate* m_surfaceMontageLayoutOrientationEnumComboBox; * * Declare a slot that is called when user changes selection * private slots: * void surfaceMontageLayoutOrientationEnumComboBoxItemActivated(); * * Implementation File (.cxx) * Include the header files * #include "EnumComboBoxTemplate.h" * #include "SurfaceMontageLayoutOrientationEnum.h" * * Instatiate: * m_surfaceMontageLayoutOrientationEnumComboBox = new EnumComboBoxTemplate(this); * m_surfaceMontageLayoutOrientationEnumComboBox->setup(); * * Get notified when the user changes the selection: * QObject::connect(m_surfaceMontageLayoutOrientationEnumComboBox, SIGNAL(itemActivated()), * this, SLOT(surfaceMontageLayoutOrientationEnumComboBoxItemActivated())); * * Update the selection: * m_surfaceMontageLayoutOrientationEnumComboBox->setSelectedItem(NEW_VALUE); * * Read the selection: * const SurfaceMontageLayoutOrientationEnum::Enum VARIABLE = m_surfaceMontageLayoutOrientationEnumComboBox->getSelectedItem(); * */ /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ SurfaceMontageLayoutOrientationEnum::SurfaceMontageLayoutOrientationEnum(const Enum enumValue, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCodeCounter++; this->name = name; this->guiName = guiName; } /** * Destructor. */ SurfaceMontageLayoutOrientationEnum::~SurfaceMontageLayoutOrientationEnum() { } /** * Initialize the enumerated metadata. */ void SurfaceMontageLayoutOrientationEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(SurfaceMontageLayoutOrientationEnum(LANDSCAPE_LAYOUT_ORIENTATION, "LANDSCAPE_LAYOUT_ORIENTATION", "Landscape")); enumData.push_back(SurfaceMontageLayoutOrientationEnum(PORTRAIT_LAYOUT_ORIENTATION, "PORTRAIT_LAYOUT_ORIENTATION", "Portrait")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const SurfaceMontageLayoutOrientationEnum* SurfaceMontageLayoutOrientationEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const SurfaceMontageLayoutOrientationEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString SurfaceMontageLayoutOrientationEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const SurfaceMontageLayoutOrientationEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ SurfaceMontageLayoutOrientationEnum::Enum SurfaceMontageLayoutOrientationEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = SurfaceMontageLayoutOrientationEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const SurfaceMontageLayoutOrientationEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type SurfaceMontageLayoutOrientationEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString SurfaceMontageLayoutOrientationEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const SurfaceMontageLayoutOrientationEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ SurfaceMontageLayoutOrientationEnum::Enum SurfaceMontageLayoutOrientationEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = SurfaceMontageLayoutOrientationEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const SurfaceMontageLayoutOrientationEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type SurfaceMontageLayoutOrientationEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t SurfaceMontageLayoutOrientationEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const SurfaceMontageLayoutOrientationEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ SurfaceMontageLayoutOrientationEnum::Enum SurfaceMontageLayoutOrientationEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = SurfaceMontageLayoutOrientationEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const SurfaceMontageLayoutOrientationEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type SurfaceMontageLayoutOrientationEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void SurfaceMontageLayoutOrientationEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void SurfaceMontageLayoutOrientationEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(SurfaceMontageLayoutOrientationEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void SurfaceMontageLayoutOrientationEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(SurfaceMontageLayoutOrientationEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } workbench-1.1.1/src/Brain/SurfaceMontageLayoutOrientationEnum.h000066400000000000000000000064511255417355300246510ustar00rootroot00000000000000#ifndef __SURFACE_MONTAGE_LAYOUT_ORIENTATION_ENUM_H__ #define __SURFACE_MONTAGE_LAYOUT_ORIENTATION_ENUM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class SurfaceMontageLayoutOrientationEnum { public: /** * Enumerated values. */ enum Enum { /** */ LANDSCAPE_LAYOUT_ORIENTATION, /** */ PORTRAIT_LAYOUT_ORIENTATION }; ~SurfaceMontageLayoutOrientationEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); private: SurfaceMontageLayoutOrientationEnum(const Enum enumValue, const AString& name, const AString& guiName); static const SurfaceMontageLayoutOrientationEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; #ifdef __SURFACE_MONTAGE_LAYOUT_ORIENTATION_ENUM_DECLARE__ std::vector SurfaceMontageLayoutOrientationEnum::enumData; bool SurfaceMontageLayoutOrientationEnum::initializedFlag = false; int32_t SurfaceMontageLayoutOrientationEnum::integerCodeCounter = 0; #endif // __SURFACE_MONTAGE_LAYOUT_ORIENTATION_ENUM_DECLARE__ } // namespace #endif //__SURFACE_MONTAGE_LAYOUT_ORIENTATION_ENUM_H__ workbench-1.1.1/src/Brain/SurfaceMontageViewport.cxx000066400000000000000000000127521255417355300225260ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SURFACE_MONTAGE_VIEWPORT_DECLARE__ #include "SurfaceMontageViewport.h" #undef __SURFACE_MONTAGE_VIEWPORT_DECLARE__ #include "CaretAssert.h" #include "Surface.h" #include "StructureEnum.h" using namespace caret; /** * \class caret::SurfaceMontageViewport * \brief Viewports in a surface montage */ /** * Constructor. * * @param surface * Surface in the montage. * @param projectionViewType * Projection view type. */ SurfaceMontageViewport::SurfaceMontageViewport(Surface* surface, const ProjectionViewTypeEnum::Enum projectionViewType) : CaretObject() { CaretAssert(surface); m_surface = surface; m_structure = surface->getStructure(); m_projectionViewType = projectionViewType; m_viewport[0] = -1; m_viewport[1] = -1; m_viewport[2] = -1; m_viewport[3] = -1; m_row = -1; m_column = -1; } /** * Destructor. */ SurfaceMontageViewport::~SurfaceMontageViewport() { } /** * @return true if the coordinates are inside the viewport, else false. * @param x * X-coordinate * @param y * Y-coordinate */ bool SurfaceMontageViewport::isInside(const int32_t x, const int32_t y) const { if (x < m_viewport[0]) return false; if (x > (m_viewport[0] + m_viewport[2])) return false; if (y < m_viewport[1]) return false; if (y > (m_viewport[1] + m_viewport[3])) return false; return true; } /** * Set the row and column. * * @param row * New value for row. * @param column * New value for column. */ void SurfaceMontageViewport::setRowAndColumn(const int32_t row, const int32_t column) { m_row = row; m_column = column; } /** * Get the viewport. * * @param viewportOut * Output containing viewport. */ void SurfaceMontageViewport::getViewport(int32_t viewportOut[4]) const { CaretAssert((m_viewport[0] >= 0) && (m_viewport[1] >= 0) && (m_viewport[2] > 0) && (m_viewport[3] > 1)); viewportOut[0] = m_viewport[0]; viewportOut[1] = m_viewport[1]; viewportOut[2] = m_viewport[2]; viewportOut[3] = m_viewport[3]; } /** * Set the viewport for this item. * @param viewport * Values for viewport. */ void SurfaceMontageViewport::setViewport(const int32_t viewport[4]) { m_viewport[0] = viewport[0]; m_viewport[1] = viewport[1]; m_viewport[2] = viewport[2]; m_viewport[3] = viewport[3]; } /** * @return Row of this item (0 is top) */ int32_t SurfaceMontageViewport::getRow() const { CaretAssert(m_row >= 0); return m_row; } /** * @return Column of this item (0 is left) */ int32_t SurfaceMontageViewport::getColumn() const { CaretAssert(m_column >= 0); return m_column; } /** * @return X-coordinate in viewport. */ int32_t SurfaceMontageViewport::getX() const { CaretAssert( m_viewport[0] >= 0); return m_viewport[0]; } /** * @return Y-Coordinate in viewport. */ int32_t SurfaceMontageViewport::getY() const { CaretAssert(m_viewport[1] >= 0); return m_viewport[1]; } /** * @return Width of viewport. */ int32_t SurfaceMontageViewport::getWidth() const { CaretAssert(m_viewport[2] > 0); return m_viewport[2]; } /** * @return Height of viewport. */ int32_t SurfaceMontageViewport::getHeight() const { CaretAssert(m_viewport[3] > 0); return m_viewport[3]; } /** * Find the number of rows and columns for the given montage viewports. * * @param montageViewports * The montage viewports. * @param numberOfRowsOut * Output number of rows. * @param numberOfColumnsOut * Output number of columns. */ void SurfaceMontageViewport::getNumberOfRowsAndColumns(const std::vector& montageViewports, int32_t& numberOfRowsOut, int32_t& numberOfColumnsOut) { numberOfRowsOut = 0; numberOfColumnsOut = 0; if (montageViewports.empty()) { return; } for (std::vector::const_iterator iter = montageViewports.begin(); iter != montageViewports.end(); iter++) { const SurfaceMontageViewport* svp = *iter; const int32_t row = svp->getRow(); const int32_t column = svp->getColumn(); if (row > numberOfRowsOut) { numberOfRowsOut = row; } if (column > numberOfColumnsOut) { numberOfColumnsOut = column; } } /* * Add one since row and column in viewports are indices that range 0 to 1 */ numberOfRowsOut++; numberOfColumnsOut++; } workbench-1.1.1/src/Brain/SurfaceMontageViewport.h000066400000000000000000000065401255417355300221510ustar00rootroot00000000000000#ifndef __SURFACE_MONTAGE_VIEWPORT_H__ #define __SURFACE_MONTAGE_VIEWPORT_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "ProjectionViewTypeEnum.h" #include "StructureEnum.h" namespace caret { class Surface; class SurfaceMontageViewport : public CaretObject { public: // SurfaceMontageViewport(); SurfaceMontageViewport(Surface* surface, const ProjectionViewTypeEnum::Enum projectionViewType); virtual ~SurfaceMontageViewport(); /** * @return The surface in the viewport. */ Surface* getSurface() const { return m_surface; } ProjectionViewTypeEnum::Enum getProjectionViewType() const { return m_projectionViewType; } bool isInside(const int32_t x, const int32_t y) const; /** * @return Row of this item (0 is top) */ int32_t getRow() const; /** * @return Column of this item (0 is left) */ int32_t getColumn() const; void setRowAndColumn(const int32_t row, const int32_t column); /** * @return X-coordinate in viewport. */ int32_t getX() const; /** * @return Y-Coordinate in viewport. */ int32_t getY() const; /** * @return Width of viewport. */ int32_t getWidth() const; /** * @return Height of viewport. */ int32_t getHeight() const; void getViewport(int32_t viewportOut[4]) const; void setViewport(const int32_t viewport[4]); static void getNumberOfRowsAndColumns(const std::vector& montageViewports, int32_t& numberOfRowsOut, int32_t& numberOfColumnsOut); private: int32_t m_row; int32_t m_column; int32_t m_viewport[4]; Surface* m_surface; ProjectionViewTypeEnum::Enum m_projectionViewType; StructureEnum::Enum m_structure; // ADD_NEW_METHODS_HERE private: // ADD_NEW_MEMBERS_HERE }; #ifdef __SURFACE_MONTAGE_VIEWPORT_DECLARE__ // #endif // __SURFACE_MONTAGE_VIEWPORT_DECLARE__ } // namespace #endif //__SURFACE_MONTAGE_VIEWPORT_H__ workbench-1.1.1/src/Brain/SurfaceNodeColoring.cxx000066400000000000000000001626571255417355300217700ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SURFACE_NODE_COLORING_DECLARE__ #include "SurfaceNodeColoring.h" #undef __SURFACE_NODE_COLORING_DECLARE__ #include "Brain.h" #include "BrainordinateRegionOfInterest.h" #include "BrainStructure.h" #include "BrowserTabContent.h" #include "EventBrowserTabGet.h" #include "CaretAssert.h" #include "CaretLogger.h" #include "CaretPreferences.h" #include "CiftiBrainordinateDataSeriesFile.h" #include "CiftiBrainordinateLabelFile.h" #include "CiftiBrainordinateScalarFile.h" #include "CiftiConnectivityMatrixParcelFile.h" #include "CiftiMappableConnectivityMatrixDataFile.h" #include "CiftiParcelLabelFile.h" #include "CiftiParcelScalarFile.h" #include "CiftiParcelSeriesFile.h" #include "DisplayPropertiesSurface.h" #include "GroupAndNameHierarchyModel.h" #include "DisplayPropertiesLabels.h" #include "EventManager.h" #include "EventModelSurfaceGet.h" #include "GiftiLabel.h" #include "GiftiLabelTable.h" #include "GroupAndNameHierarchyGroup.h" #include "LabelFile.h" #include "LabelDrawingProperties.h" #include "MetricFile.h" #include "ModelSurface.h" #include "ModelSurfaceMontage.h" #include "ModelVolume.h" #include "ModelWholeBrain.h" #include "NodeAndVoxelColoring.h" #include "Overlay.h" #include "OverlaySet.h" #include "Palette.h" #include "PaletteColorMapping.h" #include "PaletteFile.h" #include "PaletteScalarAndColor.h" #include "RgbaFile.h" #include "SessionManager.h" #include "Surface.h" #include "TopologyHelper.h" using namespace caret; /** * Constructor. */ SurfaceNodeColoring::SurfaceNodeColoring() : CaretObject() { } /** * Destructor. */ SurfaceNodeColoring::~SurfaceNodeColoring() { } /** * Get a description of this object's content. * @return String describing this object's content. */ AString SurfaceNodeColoring::toString() const { return "SurfaceNodeColoring"; } /** * Assign color components to surface nodes. * If colors are currently valid, no changes are made to the surface coloring. * @param model * Model that is displayed. If NULL use find ModelSurface * for the surface. This case occurs when needing surface coloring * when surface outline is drawn on volume slices. * @param surface * Surface that is displayed. * @param browserTabIndex * Index of tab in which model is displayed. */ float* SurfaceNodeColoring::colorSurfaceNodes(Model* model, Surface* surface, const int32_t browserTabIndex) { CaretAssert(surface); ModelSurface* surfaceModel = dynamic_cast(model); ModelSurfaceMontage* surfaceMontageModel = dynamic_cast(model); ModelWholeBrain* wholeBrainModel = dynamic_cast(model); OverlaySet* overlaySet = NULL; float* rgba = NULL; EventBrowserTabGet getBrowserTab(browserTabIndex); EventManager::get()->sendEvent(getBrowserTab.getPointer()); BrowserTabContent* browserTabContent = getBrowserTab.getBrowserTab(); Brain* brain = NULL; if (model != NULL) { brain = model->getBrain(); } /* * For a NULL model, find and use the surface model for the * surface and in the same tab as the volume model. This typically * occurs when the volume surface outline is drawn over a volume slice. */ if (model == NULL) { EventModelSurfaceGet surfaceGet(surface); EventManager::get()->sendEvent(surfaceGet.getPointer()); surfaceModel = surfaceGet.getModelSurface(); CaretAssert(surfaceModel); if (surfaceModel != NULL) { brain = surfaceModel->getBrain(); } /* * If whole brain is displayed in the tab, use coloring * from whole brain instead of surface. */ if (browserTabContent != NULL) { ModelWholeBrain* wholeBrain = browserTabContent->getDisplayedWholeBrainModel(); if (wholeBrain != NULL) { wholeBrainModel = wholeBrain; brain = wholeBrainModel->getBrain(); surfaceModel = NULL; } ModelSurfaceMontage* surfMont = browserTabContent->getDisplayedSurfaceMontageModel(); if (surfMont != NULL) { surfaceMontageModel = surfMont; brain = surfaceMontageModel->getBrain(); surfaceModel = NULL; } } } /* * Get coloring and overlays for the valid model. */ if (surfaceModel != NULL) { rgba = surface->getSurfaceNodeColoringRgbaForBrowserTab(browserTabIndex); overlaySet = surfaceModel->getOverlaySet(browserTabIndex); } else if (surfaceMontageModel != NULL) { rgba = surface->getSurfaceMontageNodeColoringRgbaForBrowserTab(browserTabIndex); overlaySet = surfaceMontageModel->getOverlaySet(browserTabIndex); } else if (wholeBrainModel != NULL) { rgba = surface->getWholeBrainNodeColoringRgbaForBrowserTab(browserTabIndex); overlaySet = wholeBrainModel->getOverlaySet(browserTabIndex); } CaretAssert(overlaySet); /* * RGBA will be Non-NULL if the surface HAS valid coloring */ if (rgba != NULL) { return rgba; } /* * Drawing type for labels */ DisplayPropertiesLabels* displayPropertiesLabels = NULL; if (brain != NULL) { displayPropertiesLabels = brain->getDisplayPropertiesLabels(); } const int numNodes = surface->getNumberOfNodes(); const int numColorComponents = numNodes * 4; float *rgbaColor = new float[numColorComponents]; /* * Color the surface nodes */ this->colorSurfaceNodes(displayPropertiesLabels, browserTabIndex, surface, overlaySet, rgbaColor); if (surfaceModel != NULL) { surface->setSurfaceNodeColoringRgbaForBrowserTab(browserTabIndex, rgbaColor); rgba = surface->getSurfaceNodeColoringRgbaForBrowserTab(browserTabIndex); } else if (surfaceMontageModel != NULL) { surface->setSurfaceMontageNodeColoringRgbaForBrowserTab(browserTabIndex, rgbaColor); rgba = surface->getSurfaceMontageNodeColoringRgbaForBrowserTab(browserTabIndex); } else if (wholeBrainModel != NULL) { surface->setWholeBrainNodeColoringRgbaForBrowserTab(browserTabIndex, rgbaColor); rgba = surface->getWholeBrainNodeColoringRgbaForBrowserTab(browserTabIndex); } if(rgbaColor) delete [] rgbaColor; return rgba; } /** * Show brainordinate region of interest highlighting on the surface. * * @param brain * The brain. * @param surface * Surface on which highlighting is displayed. * @param rgbaNodeColors * Node coloring that is updated with highlighting. */ void SurfaceNodeColoring::showBrainordinateHighlightRegionOfInterest(const Brain* brain, const Surface* surface, float* rgbaNodeColors) { CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); uint8_t foregroundColorByte[4]; prefs->getColorForegroundSurfaceView(foregroundColorByte); const float foregroundColor[4] = { foregroundColorByte[0], foregroundColorByte[1], foregroundColorByte[2], 1.0 }; const BrainordinateRegionOfInterest* roi = brain->getBrainordinateHighlightRegionOfInterest(); CaretAssert(roi); if (roi->isBrainordinateHighlightingEnabled()) { const StructureEnum::Enum structure = surface->getStructure(); const int64_t surfaceNumberOfNodes = surface->getNumberOfNodes(); if (roi->hasNodesForSurfaceStructure(structure, surfaceNumberOfNodes)) { const std::vector& nodeIndices = roi->getNodesForSurfaceStructure(structure, surfaceNumberOfNodes); for (std::vector::const_iterator nodeIter = nodeIndices.begin(); nodeIter != nodeIndices.end(); nodeIter++) { const int64_t nodeIndex = *nodeIter; const int64_t rgbaIndex = nodeIndex * 4; CaretAssertArrayIndex(rgbaNodeColors, surfaceNumberOfNodes*4 , rgbaIndex + 3); rgbaNodeColors[rgbaIndex] = foregroundColor[0]; rgbaNodeColors[rgbaIndex+1] = foregroundColor[1]; rgbaNodeColors[rgbaIndex+2] = foregroundColor[2]; rgbaNodeColors[rgbaIndex+3] = foregroundColor[3]; } } } } /** * Assign color components to surface nodes. * * @param surface * Surface that has its nodes colored. * @param overlaySet * Surface overlay assignments for surface. * @param rgbaNodeColors * RGBA color components that are set by this method. */ void SurfaceNodeColoring::colorSurfaceNodes(const DisplayPropertiesLabels* displayPropertiesLabels, const int32_t browserTabIndex, const Surface* surface, OverlaySet* overlaySet, float* rgbaNodeColors) { const int32_t numNodes = surface->getNumberOfNodes(); const int32_t numberOfDisplayedOverlays = overlaySet->getNumberOfDisplayedOverlays(); /* * Default color. */ for (int32_t i = 0; i < numNodes; i++) { const int32_t i4 = i * 4; rgbaNodeColors[i4] = 0.70; rgbaNodeColors[i4+1] = 0.70; rgbaNodeColors[i4+2] = 0.70; rgbaNodeColors[i4+3] = 1.0; } const BrainStructure* brainStructure = surface->getBrainStructure(); CaretAssert(brainStructure); const Brain* brain = brainStructure->getBrain(); CaretAssert(brain); bool firstOverlayFlag = true; float* overlayRGBV = new float[numNodes * 4]; for (int32_t iOver = (numberOfDisplayedOverlays - 1); iOver >= 0; iOver--) { Overlay* overlay = overlaySet->getOverlay(iOver); if (overlay->isEnabled()) { std::vector mapFiles; CaretMappableDataFile* selectedMapFile; int32_t selectedMapIndex; overlay->getSelectionData(mapFiles, selectedMapFile, selectedMapIndex); DataFileTypeEnum::Enum mapDataFileType = DataFileTypeEnum::UNKNOWN; if (selectedMapFile != NULL) { mapDataFileType = selectedMapFile->getDataFileType(); } bool isColoringValid = false; switch (mapDataFileType) { case DataFileTypeEnum::BORDER: break; case DataFileTypeEnum::CONNECTIVITY_DENSE: { CiftiMappableConnectivityMatrixDataFile* cmf = dynamic_cast(selectedMapFile); isColoringValid = assignCiftiMappableConnectivityMatrixColoring(brainStructure, cmf, selectedMapIndex, numNodes, overlayRGBV); } break; case DataFileTypeEnum::CONNECTIVITY_DENSE_LABEL: isColoringValid = this->assignCiftiDenseLabelColoring(displayPropertiesLabels, browserTabIndex, brainStructure, surface, dynamic_cast(selectedMapFile), selectedMapIndex, numNodes, overlayRGBV); break; case DataFileTypeEnum::CONNECTIVITY_DENSE_PARCEL: { CiftiMappableConnectivityMatrixDataFile* cmf = dynamic_cast(selectedMapFile); isColoringValid = assignCiftiMappableConnectivityMatrixColoring(brainStructure, cmf, selectedMapIndex, numNodes, overlayRGBV); } break; case DataFileTypeEnum::CONNECTIVITY_DENSE_SCALAR: isColoringValid = this->assignCiftiScalarColoring(brainStructure, dynamic_cast(selectedMapFile), selectedMapIndex, numNodes, overlayRGBV); break; case DataFileTypeEnum::CONNECTIVITY_DENSE_TIME_SERIES: isColoringValid = this->assignCiftiDataSeriesColoring(brainStructure, dynamic_cast(selectedMapFile), selectedMapIndex, numNodes, overlayRGBV); break; case DataFileTypeEnum::CONNECTIVITY_FIBER_ORIENTATIONS_TEMPORARY: break; case DataFileTypeEnum::CONNECTIVITY_FIBER_TRAJECTORY_TEMPORARY: break; case DataFileTypeEnum::CONNECTIVITY_PARCEL: { CiftiMappableConnectivityMatrixDataFile* cmf = dynamic_cast(selectedMapFile); isColoringValid = assignCiftiMappableConnectivityMatrixColoring(brainStructure, cmf, selectedMapIndex, numNodes, overlayRGBV); } break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_DENSE: { CiftiMappableConnectivityMatrixDataFile* cmf = dynamic_cast(selectedMapFile); isColoringValid = assignCiftiMappableConnectivityMatrixColoring(brainStructure, cmf, selectedMapIndex, numNodes, overlayRGBV); } break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_LABEL: { CiftiParcelLabelFile* cplf = dynamic_cast(selectedMapFile); isColoringValid = assignCiftiParcelLabelColoring(displayPropertiesLabels, browserTabIndex, brainStructure, surface, cplf, selectedMapIndex, numNodes, overlayRGBV); } break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_SCALAR: isColoringValid = this->assignCiftiParcelScalarColoring(brainStructure, dynamic_cast(selectedMapFile), selectedMapIndex, numNodes, overlayRGBV); break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_SERIES: isColoringValid = this->assignCiftiParcelSeriesColoring(brainStructure, dynamic_cast(selectedMapFile), selectedMapIndex, numNodes, overlayRGBV); break; case DataFileTypeEnum::CONNECTIVITY_SCALAR_DATA_SERIES: break; case DataFileTypeEnum::FOCI: break; case DataFileTypeEnum::IMAGE: break; case DataFileTypeEnum::LABEL: isColoringValid = this->assignLabelColoring(displayPropertiesLabels, browserTabIndex, brainStructure, surface, dynamic_cast(selectedMapFile), selectedMapIndex, numNodes, overlayRGBV); break; case DataFileTypeEnum::METRIC: isColoringValid = this->assignMetricColoring(brainStructure, dynamic_cast(selectedMapFile), selectedMapIndex, numNodes, overlayRGBV); break; case DataFileTypeEnum::PALETTE: break; case DataFileTypeEnum::RGBA: isColoringValid = this->assignRgbaColoring(brainStructure, dynamic_cast(selectedMapFile), selectedMapIndex, numNodes, overlayRGBV); break; case DataFileTypeEnum::SCENE: break; case DataFileTypeEnum::SPECIFICATION: break; case DataFileTypeEnum::SURFACE: break; case DataFileTypeEnum::VOLUME: break; case DataFileTypeEnum::UNKNOWN: break; } if (isColoringValid) { const float opacity = overlay->getOpacity(); const float oneMinusOpacity = 1.0 - opacity; for (int32_t i = 0; i < numNodes; i++) { const int32_t i4 = i * 4; const float valid = overlayRGBV[i4 + 3]; if (valid > 0.0 ) { if (opacity < 1.0) { if (firstOverlayFlag) { /* * When first overlay, there is nothing to * blend with */ rgbaNodeColors[i4] = (overlayRGBV[i4] * opacity); rgbaNodeColors[i4+1] = (overlayRGBV[i4+1] * opacity); rgbaNodeColors[i4+2] = (overlayRGBV[i4+2] * opacity); } else { /* * Blend with underlaying colors */ rgbaNodeColors[i4] = (overlayRGBV[i4] * opacity) + (rgbaNodeColors[i4] * oneMinusOpacity); rgbaNodeColors[i4+1] = (overlayRGBV[i4+1] * opacity) + (rgbaNodeColors[i4+1] * oneMinusOpacity); rgbaNodeColors[i4+2] = (overlayRGBV[i4+2] * opacity) + (rgbaNodeColors[i4+2] * oneMinusOpacity); } } else { /* * No opacity so simple replace coloring */ rgbaNodeColors[i4] = overlayRGBV[i4]; rgbaNodeColors[i4+1] = overlayRGBV[i4+1]; rgbaNodeColors[i4+2] = overlayRGBV[i4+2]; } } } firstOverlayFlag = false; } } } /* * Opacity from first overlay is used as overall surface opacity * so replace alpha with opacity */ const float opacity = brain->getDisplayPropertiesSurface()->getOpacity(); if (opacity < 1.0) { for (int32_t i = 0; i < numNodes; i++) { const int32_t i4 = i * 4; rgbaNodeColors[i4+3] = opacity; } } showBrainordinateHighlightRegionOfInterest(brain, surface, rgbaNodeColors); delete[] overlayRGBV; } /** * Assign label coloring to nodes * @param brainStructure * The brain structure that contains the data files. * @param labelFile * Label file that is selected. * @param labelMapUniqueID * UniqueID of selected map. * @param numberOfNodes * Number of nodes in surface. * @param rgbv * Color components set by this method. * Red, green, blue, valid. If the valid component is * zero, it indicates that the overlay did not assign * any coloring to the node. * @return * True if coloring is valid, else false. */ bool SurfaceNodeColoring::assignLabelColoring(const DisplayPropertiesLabels* displayPropertiesLabels, const int32_t browserTabIndex, const BrainStructure* /*brainStructure*/, const Surface* surface, const LabelFile* labelFile, const int32_t displayColumn, const int32_t numberOfNodes, float* rgbv) { if (labelFile == NULL) { return false; } if ( ! labelFile->isMappableToSurfaceStructure(surface->getStructure())) { return false; } const LabelDrawingProperties* props = labelFile->getLabelDrawingProperties(); LabelDrawingTypeEnum::Enum labelDrawingType = props->getDrawingType(); CaretColorEnum::Enum outlineColor = props->getOutlineColor(); const DisplayGroupEnum::Enum displayGroup = displayPropertiesLabels->getDisplayGroupForTab(browserTabIndex); const GroupAndNameHierarchyModel* classNameModel = labelFile->getGroupAndNameHierarchyModel(); if (classNameModel->isSelected(displayGroup, browserTabIndex) == false) { return false; } if (displayColumn < 0) { return false; } /* * Invalidate all coloring. */ for (int32_t i = 0; i < numberOfNodes; i++) { rgbv[i*4+3] = 0.0; } const GiftiLabelTable* labelTable = labelFile->getLabelTable(); CaretAssert(surface); CaretPointer topologyHelper = surface->getTopologyHelper(); /* * Assign colors from labels to nodes */ std::vector labelKeys; for (int32_t i = 0; i < numberOfNodes; i++) { labelKeys.push_back(labelFile->getLabelKey(i, displayColumn)); } const bool drawMedialWallFilledFlag = props->isDrawMedialWallFilled(); assignLabelTableColors(labelTable, labelDrawingType, outlineColor, topologyHelper, displayGroup, browserTabIndex, labelKeys, drawMedialWallFilledFlag, rgbv); return true; } /** * Assign label coloring to surface nodes. * * @param labelTable * The label table. * @param labelDrawingType * Label drawing type. * @param outlineColor * Outline color. * @param topologyHelper * The topology helper for node neighbors. * @param displayGroup * Selected Display Group * @param browserTabIndex * Index of browser tab. * @param labelIndices * Indices of labels for each node. * @param medialWallLabelKey * Index of the medial wall label key * @param drawMedialWallFilledFlag * True if medial wall is always filled * @param rgbv * RGB coloring. (4 per node). * */ void SurfaceNodeColoring::assignLabelTableColors(const GiftiLabelTable* labelTable, const LabelDrawingTypeEnum::Enum labelDrawingType, const CaretColorEnum::Enum outlineColor, const CaretPointer topologyHelper, const DisplayGroupEnum::Enum displayGroup, const int32_t browserTabIndex, const std::vector& labelIndices, const bool drawMedialWallFilledFlag, float* rgbv) { const int32_t numberOfIndices = static_cast(labelIndices.size()); /* * Invalidate all coloring. */ for (int32_t i = 0; i < numberOfIndices; i++) { rgbv[i*4+3] = 0.0; } float outlineRGBA[4]; CaretColorEnum::toRGBFloat(outlineColor, outlineRGBA); outlineRGBA[3] = 1.0; /* * Assign colors from labels to nodes */ float nodeRGBA[4]; for (int32_t i = 0; i < numberOfIndices; i++) { CaretAssertVectorIndex(labelIndices, i); const int32_t labelKey= static_cast(labelIndices[i]); const GiftiLabel* label = labelTable->getLabel(labelKey); if (label == NULL) { continue; } const GroupAndNameHierarchyItem* nameItem = label->getGroupNameSelectionItem(); if (nameItem != NULL) { if (nameItem->isSelected(displayGroup, browserTabIndex) == false) { continue; } } /* * Initialize node color to its label's color */ label->getColor(nodeRGBA); if (nodeRGBA[3] <= 0.0) { continue; } /* * If a node is the same color as all of its neighbors, * use the fill color. Otherwise, use the outline color. */ bool isLabelBoundaryNode = false; int32_t numNeighbors = 0; const int32_t* allNeighbors = topologyHelper->getNodeNeighbors(i, numNeighbors); for (int32_t n = 0; n < numNeighbors; n++) { const int32_t neighborNodeIndex = allNeighbors[n]; CaretAssertVectorIndex(labelIndices, neighborNodeIndex); const int32_t neighborLabelKey = static_cast(labelIndices[neighborNodeIndex]); if (labelKey != neighborLabelKey) { isLabelBoundaryNode = true; break; } } /* * User may request that medial wall is always filled * and never receives outlining */ bool doOutlineFlag = true; if (drawMedialWallFilledFlag) { if (label->isMedialWallName()) { doOutlineFlag = false; } } if (doOutlineFlag) { switch (labelDrawingType) { case LabelDrawingTypeEnum::DRAW_FILLED: break; case LabelDrawingTypeEnum::DRAW_FILLED_WITH_OUTLINE_COLOR: if (isLabelBoundaryNode) { nodeRGBA[0] = outlineRGBA[0]; nodeRGBA[1] = outlineRGBA[1]; nodeRGBA[2] = outlineRGBA[2]; nodeRGBA[3] = outlineRGBA[3]; } break; case LabelDrawingTypeEnum::DRAW_OUTLINE_COLOR: if (isLabelBoundaryNode) { nodeRGBA[0] = outlineRGBA[0]; nodeRGBA[1] = outlineRGBA[1]; nodeRGBA[2] = outlineRGBA[2]; nodeRGBA[3] = outlineRGBA[3]; } else { nodeRGBA[3] = 0.0; } break; case LabelDrawingTypeEnum::DRAW_OUTLINE_LABEL_COLOR: if ( ! isLabelBoundaryNode) { nodeRGBA[3] = 0.0; } break; } } const int32_t i4 = i * 4; rgbv[i4] = nodeRGBA[0]; rgbv[i4+1] = nodeRGBA[1]; rgbv[i4+2] = nodeRGBA[2]; rgbv[i4+3] = nodeRGBA[3]; } } /** * Assign metric coloring to nodes * @param brainStructure * The brain structure that contains the data files. * @param metricFile * Metric file that is selected. * @param metricMapUniqueID * UniqueID of selected map. * @param numberOfNodes * Number of nodes in surface. * @param rgbv * Color components set by this method. * Red, green, blue, valid. If the valid component is * zero, it indicates that the overlay did not assign * any coloring to the node. * @return * True if coloring is valid, else false. */ bool SurfaceNodeColoring::assignMetricColoring(const BrainStructure* brainStructure, MetricFile* metricFile, const int32_t displayColumn, const int32_t numberOfNodes, float* rgbv) { if (displayColumn < 0) { return false; } if ( ! metricFile->isMappableToSurfaceStructure(brainStructure->getStructure())) { return false; } const PaletteColorMapping* paletteColorMapping = metricFile->getPaletteColorMapping(displayColumn); /* * Invalidate all coloring. */ for (int32_t i = 0; i < numberOfNodes; i++) { rgbv[i*4+3] = 0.0; } /* * Get min/max ranges. */ int thresholdColumn = metricFile->getColumnIndexFromColumnName(paletteColorMapping->getThresholdDataName()); if (thresholdColumn < 0) { thresholdColumn = displayColumn; } const float* metricDisplayData = metricFile->getValuePointerForColumn(displayColumn); const float* metricThresholdData = metricFile->getValuePointerForColumn(thresholdColumn); FastStatistics* statistics = NULL; switch (metricFile->getPaletteNormalizationMode()) { case PaletteNormalizationModeEnum::NORMALIZATION_ALL_MAP_DATA: statistics = const_cast(metricFile->getFileFastStatistics()); break; case PaletteNormalizationModeEnum::NORMALIZATION_SELECTED_MAP_DATA: statistics = const_cast(metricFile->getMapFastStatistics(displayColumn)); break; } CaretAssert(statistics); //const FastStatistics* statistics = metricFile->getMapFastStatistics(displayColumn); const Brain* brain = brainStructure->getBrain(); const AString paletteName = paletteColorMapping->getSelectedPaletteName(); const Palette* palette = brain->getPaletteFile()->getPaletteByName(paletteName); if ((statistics != NULL) && (palette != NULL)) { NodeAndVoxelColoring::colorScalarsWithPalette(statistics, paletteColorMapping, palette, metricDisplayData, metricThresholdData, numberOfNodes, rgbv); } else { CaretLogSevere("Selected palette for metric is invalid: \"" + paletteName + "\""); } return true; } /** * Assign cifti scalar coloring to nodes * @param brainStructure * The brain structure that contains the data files. * @param ciftiScalarFile * Cifti Scalar file that is selected. * @param ciftiMapUniqueID * UniqueID of selected map. * @param numberOfNodes * Number of nodes in surface. * @param rgbv * Color components set by this method. * Red, green, blue, valid. If the valid component is * zero, it indicates that the overlay did not assign * any coloring to the node. * @return * True if coloring is valid, else false. */ bool SurfaceNodeColoring::assignCiftiMappableConnectivityMatrixColoring(const BrainStructure* brainStructure, CiftiMappableConnectivityMatrixDataFile* ciftiConnectivityMatrixFile, const int32_t mapIndex, const int32_t numberOfNodes, float* rgbv) { CaretAssert(ciftiConnectivityMatrixFile); if (mapIndex < 0) { return false; } /* * Invalidate all coloring. */ for (int32_t i = 0; i < numberOfNodes; i++) { rgbv[i*4+3] = 0.0; } const StructureEnum::Enum structure = brainStructure->getStructure(); std::vector dataValues(numberOfNodes); ciftiConnectivityMatrixFile->getMapSurfaceNodeColoring(brainStructure->getBrain()->getPaletteFile(), mapIndex, structure, rgbv, &dataValues[0], numberOfNodes); CiftiConnectivityMatrixParcelFile* parcelFile = dynamic_cast(ciftiConnectivityMatrixFile); if (parcelFile != NULL) { const Surface* surface = brainStructure->getPrimaryAnatomicalSurface(); CaretPointer topologyHelper = surface->getTopologyHelper(); std::set nodeSet; bool selectedParcelValid = false; selectedParcelValid = ciftiConnectivityMatrixFile->getParcelNodesElementForSelectedParcel(nodeSet,structure); std::vector selectedParcelNodes = std::vector(nodeSet.begin(), nodeSet.end()); if(selectedParcelValid) { const CaretColorEnum::Enum parcelColor = parcelFile->getSelectedParcelColor(); const float* rgb = CaretColorEnum::toRGB(parcelColor); CaretAssert(rgb); switch (parcelFile->getSelectedParcelColoringMode()) { case CiftiParcelColoringModeEnum::CIFTI_PARCEL_COLORING_OFF: break; case CiftiParcelColoringModeEnum::CIFTI_PARCEL_COLORING_FILL: { for(uint64_t pNode = 0; pNode < selectedParcelNodes.size(); pNode++) { int64_t nodeIndex = selectedParcelNodes[pNode]; if(nodeIndex >= 0 && nodeIndex < numberOfNodes) { uint64_t node4 = nodeIndex*4; rgbv[node4] = rgb[0]; rgbv[node4+1] = rgb[1]; rgbv[node4+2] = rgb[2]; rgbv[node4+3] = 1.0; continue; } } } break; case CiftiParcelColoringModeEnum::CIFTI_PARCEL_COLORING_OUTLINE: { /* * Check for any neighbors with different label key. */ //make a quick lookup table std::vector selectedNodesLookup(numberOfNodes,0); for(uint64_t pNode = 0;pNode < selectedParcelNodes.size();pNode++) { int64_t nodeIndex = selectedParcelNodes[pNode]; if(nodeIndex >= 0 && nodeIndex < (int64_t)selectedNodesLookup.size()) { selectedNodesLookup[nodeIndex] = 1; } } for(uint64_t pNode = 0;pNode < selectedParcelNodes.size();pNode++) { int32_t numNeighbors = 0; const int64_t nodeIndex = selectedParcelNodes[pNode]; const int32_t* allNeighbors = topologyHelper->getNodeNeighbors(nodeIndex, numNeighbors); for (int32_t n = 0; n < numNeighbors; n++) { const int32_t neighbor = allNeighbors[n]; if (!selectedNodesLookup[neighbor]) { int64_t node4 = nodeIndex*4; rgbv[node4] = rgb[0]; rgbv[node4+1] = rgb[1]; rgbv[node4+2] = rgb[2]; rgbv[node4+3] = 1.0; continue; } } } } break; } } } return true; } /** * Assign cifti dense label coloring to nodes * @param brainStructure * The brain structure that contains the data files. * @param ciftiLabelFile * Cifti Label file that is selected. * @param ciftiMapUniqueID * UniqueID of selected map. * @param numberOfNodes * Number of nodes in surface. * @param rgbv * Color components set by this method. * Red, green, blue, valid. If the valid component is * zero, it indicates that the overlay did not assign * any coloring to the node. * @return * True if coloring is valid, else false. */ bool SurfaceNodeColoring::assignCiftiDenseLabelColoring(const DisplayPropertiesLabels* displayPropertiesLabels, const int32_t browserTabIndex, const BrainStructure* brainStructure, const Surface* surface, CiftiBrainordinateLabelFile* ciftiLabelFile, const int32_t mapIndex, const int32_t numberOfNodes, float* rgbv) { CaretAssert(displayPropertiesLabels); CaretAssert(brainStructure); CaretAssert(ciftiLabelFile); CaretAssert(rgbv); Brain* brain = (Brain*)(brainStructure->getBrain()); std::vector allCiftiBrainordinateLabelFiles; brain->getConnectivityDenseLabelFiles(allCiftiBrainordinateLabelFiles); if (mapIndex < 0) { return false; } const DisplayGroupEnum::Enum displayGroup = displayPropertiesLabels->getDisplayGroupForTab(browserTabIndex); const LabelDrawingProperties* props = ciftiLabelFile->getLabelDrawingProperties(); LabelDrawingTypeEnum::Enum labelDrawingType = props->getDrawingType(); CaretColorEnum::Enum outlineColor = props->getOutlineColor(); /* * Invalidate all coloring. */ for (int32_t i = 0; i < numberOfNodes; i++) { rgbv[i*4+3] = 0.0; } /* * Update coloring */ if (ciftiLabelFile->isMapColoringValid(mapIndex) == false) { ciftiLabelFile->updateScalarColoringForMap(mapIndex, brain->getPaletteFile()); } std::vector dataValues(numberOfNodes); /* * Assigns colors for all nodes. */ const StructureEnum::Enum structure = brainStructure->getStructure(); ciftiLabelFile->getMapSurfaceNodeColoring(brainStructure->getBrain()->getPaletteFile(), mapIndex, structure, rgbv, &dataValues[0], numberOfNodes); CaretAssert(surface); CaretPointer topologyHelper = surface->getTopologyHelper(); /* * All nodes are colored. Remove coloring for nodes whose * label is not selected. */ GiftiLabelTable* labelTable = ciftiLabelFile->getMapLabelTable(mapIndex); CaretAssert(labelTable); const bool drawMedialWallFilledFlag = props->isDrawMedialWallFilled(); assignLabelTableColors(labelTable, labelDrawingType, outlineColor, topologyHelper, displayGroup, browserTabIndex, dataValues, drawMedialWallFilledFlag, rgbv); return true; } /** * Assign cifti parcel label coloring to nodes * @param brainStructure * The brain structure that contains the data files. * @param ciftiParcelLabelFile * Cifti Parcel Label file that is selected. * @param mapIndex * Index of selected map. * @param numberOfNodes * Number of nodes in surface. * @param rgbv * Color components set by this method. * Red, green, blue, valid. If the valid component is * zero, it indicates that the overlay did not assign * any coloring to the node. * @return * True if coloring is valid, else false. */ bool SurfaceNodeColoring::assignCiftiParcelLabelColoring(const DisplayPropertiesLabels* displayPropertiesLabels, const int32_t browserTabIndex, const BrainStructure* brainStructure, const Surface* surface, CiftiParcelLabelFile* ciftiParcelLabelFile, const int32_t mapIndex, const int32_t numberOfNodes, float* rgbv) { CaretAssert(displayPropertiesLabels); CaretAssert(brainStructure); CaretAssert(ciftiParcelLabelFile); CaretAssert(rgbv); Brain* brain = (Brain*)(brainStructure->getBrain()); std::vector allCiftiBrainordinateLabelFiles; brain->getConnectivityDenseLabelFiles(allCiftiBrainordinateLabelFiles); if (mapIndex < 0) { return false; } const DisplayGroupEnum::Enum displayGroup = displayPropertiesLabels->getDisplayGroupForTab(browserTabIndex); const LabelDrawingProperties* props = ciftiParcelLabelFile->getLabelDrawingProperties(); LabelDrawingTypeEnum::Enum labelDrawingType = props->getDrawingType(); CaretColorEnum::Enum outlineColor = props->getOutlineColor(); /* * Invalidate all coloring. */ for (int32_t i = 0; i < numberOfNodes; i++) { rgbv[i*4+3] = 0.0; } /* * Update coloring */ if (ciftiParcelLabelFile->isMapColoringValid(mapIndex) == false) { ciftiParcelLabelFile->updateScalarColoringForMap(mapIndex, brain->getPaletteFile()); } std::vector dataValues(numberOfNodes); /* * Assigns colors for all nodes. */ const StructureEnum::Enum structure = brainStructure->getStructure(); ciftiParcelLabelFile->getMapSurfaceNodeColoring(brainStructure->getBrain()->getPaletteFile(), mapIndex, structure, rgbv, &dataValues[0], numberOfNodes); CaretAssert(surface); CaretPointer topologyHelper = surface->getTopologyHelper(); /* * All nodes are colored. Remove coloring for nodes whose * label is not selected. */ GiftiLabelTable* labelTable = ciftiParcelLabelFile->getMapLabelTable(mapIndex); CaretAssert(labelTable); const bool drawMedialWallFilledFlag = props->isDrawMedialWallFilled(); assignLabelTableColors(labelTable, labelDrawingType, outlineColor, topologyHelper, displayGroup, browserTabIndex, dataValues, drawMedialWallFilledFlag, rgbv); return true; } /** * Assign cifti scalar coloring to nodes * @param brainStructure * The brain structure that contains the data files. * @param ciftiScalarFile * Cifti Scalar file that is selected. * @param ciftiMapUniqueID * UniqueID of selected map. * @param numberOfNodes * Number of nodes in surface. * @param rgbv * Color components set by this method. * Red, green, blue, valid. If the valid component is * zero, it indicates that the overlay did not assign * any coloring to the node. * @return * True if coloring is valid, else false. */ bool SurfaceNodeColoring::assignCiftiScalarColoring(const BrainStructure* brainStructure, CiftiBrainordinateScalarFile* ciftiScalarFile, const int32_t mapIndex, const int32_t numberOfNodes, float* rgbv) { Brain* brain = (Brain*)(brainStructure->getBrain()); std::vector allCiftiBrainordinateScalarFiles; brain->getConnectivityDenseScalarFiles(allCiftiBrainordinateScalarFiles); if (mapIndex < 0) { return false; } /* * Invalidate all coloring. */ for (int32_t i = 0; i < numberOfNodes; i++) { rgbv[i*4+3] = 0.0; } /* * Update coloring */ if (ciftiScalarFile->isMapColoringValid(mapIndex) == false) { ciftiScalarFile->updateScalarColoringForMap(mapIndex, brain->getPaletteFile()); } std::vector dataValues(numberOfNodes); const StructureEnum::Enum structure = brainStructure->getStructure(); ciftiScalarFile->getMapSurfaceNodeColoring(brainStructure->getBrain()->getPaletteFile(), mapIndex, structure, rgbv, &dataValues[0], numberOfNodes); return true; } /** * Assign cifti parcel scalar coloring to nodes * @param brainStructure * The brain structure that contains the data files. * @param ciftiScalarFile * Cifti Scalar file that is selected. * @param ciftiMapUniqueID * UniqueID of selected map. * @param numberOfNodes * Number of nodes in surface. * @param rgbv * Color components set by this method. * Red, green, blue, valid. If the valid component is * zero, it indicates that the overlay did not assign * any coloring to the node. * @return * True if coloring is valid, else false. */ bool SurfaceNodeColoring::assignCiftiParcelScalarColoring(const BrainStructure* brainStructure, CiftiParcelScalarFile* ciftiParcelScalarFile, const int32_t mapIndex, const int32_t numberOfNodes, float* rgbv) { Brain* brain = (Brain*)(brainStructure->getBrain()); std::vector allCiftiParcelScalarFiles; brain->getConnectivityParcelScalarFiles(allCiftiParcelScalarFiles); if (mapIndex < 0) { return false; } /* * Invalidate all coloring. */ for (int32_t i = 0; i < numberOfNodes; i++) { rgbv[i*4+3] = 0.0; } /* * Update coloring */ if (ciftiParcelScalarFile->isMapColoringValid(mapIndex) == false) { ciftiParcelScalarFile->updateScalarColoringForMap(mapIndex, brain->getPaletteFile()); } std::vector dataValues(numberOfNodes); const StructureEnum::Enum structure = brainStructure->getStructure(); ciftiParcelScalarFile->getMapSurfaceNodeColoring(brainStructure->getBrain()->getPaletteFile(), mapIndex, structure, rgbv, &dataValues[0], numberOfNodes); return true; } /** * Assign cifti scalar coloring to nodes * @param brainStructure * The brain structure that contains the data files. * @param ciftiDataSeriesFile * Cifti Data Series file that is selected. * @param ciftiMapUniqueID * UniqueID of selected map. * @param numberOfNodes * Number of nodes in surface. * @param rgbv * Color components set by this method. * Red, green, blue, valid. If the valid component is * zero, it indicates that the overlay did not assign * any coloring to the node. * @return * True if coloring is valid, else false. */ bool SurfaceNodeColoring::assignCiftiDataSeriesColoring(const BrainStructure* brainStructure, CiftiBrainordinateDataSeriesFile* ciftiDataSeriesFile, const int32_t mapIndex, const int32_t numberOfNodes, float* rgbv) { Brain* brain = (Brain*)(brainStructure->getBrain()); std::vector allCiftiDataSeriesFiles; brain->getConnectivityDataSeriesFiles(allCiftiDataSeriesFiles); if (mapIndex < 0) { return false; } /* * Invalidate all coloring. */ for (int32_t i = 0; i < numberOfNodes; i++) { rgbv[i*4+3] = 0.0; } /* * Update coloring */ if (ciftiDataSeriesFile->isMapColoringValid(mapIndex) == false) { ciftiDataSeriesFile->updateScalarColoringForMap(mapIndex, brain->getPaletteFile()); } /* * Get Coloring */ std::vector dataValues(numberOfNodes); const StructureEnum::Enum structure = brainStructure->getStructure(); ciftiDataSeriesFile->getMapSurfaceNodeColoring(brainStructure->getBrain()->getPaletteFile(), mapIndex, structure, rgbv, &dataValues[0], numberOfNodes); return true; } /** * Assign cifti parcel series coloring to nodes * @param brainStructure * The brain structure that contains the data files. * @param ciftiParcelSeriesFile * Cifti Parcel Series file that is selected. * @param ciftiMapUniqueID * UniqueID of selected map. * @param numberOfNodes * Number of nodes in surface. * @param rgbv * Color components set by this method. * Red, green, blue, valid. If the valid component is * zero, it indicates that the overlay did not assign * any coloring to the node. * @return * True if coloring is valid, else false. */ bool SurfaceNodeColoring::assignCiftiParcelSeriesColoring(const BrainStructure* brainStructure, CiftiParcelSeriesFile* ciftiParcelSeriesFile, const int32_t mapIndex, const int32_t numberOfNodes, float* rgbv) { Brain* brain = (Brain*)(brainStructure->getBrain()); std::vector allCiftiParcelSeriesFiles; brain->getConnectivityParcelSeriesFiles(allCiftiParcelSeriesFiles); if (mapIndex < 0) { return false; } /* * Invalidate all coloring. */ for (int32_t i = 0; i < numberOfNodes; i++) { rgbv[i*4+3] = 0.0; } /* * Update coloring */ if (ciftiParcelSeriesFile->isMapColoringValid(mapIndex) == false) { ciftiParcelSeriesFile->updateScalarColoringForMap(mapIndex, brain->getPaletteFile()); } /* * Get Coloring */ std::vector dataValues(numberOfNodes); const StructureEnum::Enum structure = brainStructure->getStructure(); ciftiParcelSeriesFile->getMapSurfaceNodeColoring(brainStructure->getBrain()->getPaletteFile(), mapIndex, structure, rgbv, &dataValues[0], numberOfNodes); return true; } /** * Assign RGBA coloring to nodes * @param brainStructure * The brain structure that contains the data files. * @param rgbaFile * RGBA file that is selected. * @param metricMapUniqueID * UniqueID of selected map. * @param numberOfNodes * Number of nodes in surface. * @param rgbv * Color components set by this method. * Red, green, blue, valid. If the valid component is * zero, it indicates that the overlay did not assign * any coloring to the node. * @return * True if coloring is valid, else false. */ bool SurfaceNodeColoring::assignRgbaColoring(const BrainStructure* /*brainStructure*/, const RgbaFile* /*rgbaFile*/, const int32_t /*mapIndex*/, const int32_t numberOfNodes, float* rgbv) { CaretAssertMessage(0, "Add implementation."); for (int32_t i = 0; i < numberOfNodes; i++) { const int32_t i4 = i * 4; rgbv[i4] = 0.0; rgbv[i4+1] = 0.0; rgbv[i4+2] = 1.0; rgbv[i4+3] = 1.0; } return true; } workbench-1.1.1/src/Brain/SurfaceNodeColoring.h000066400000000000000000000176351255417355300214100ustar00rootroot00000000000000#ifndef __SURFACE_NODE_COLORING__H_ #define __SURFACE_NODE_COLORING__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretColorEnum.h" #include "CaretObject.h" #include "CaretPointer.h" #include "DisplayGroupEnum.h" #include "LabelDrawingTypeEnum.h" namespace caret { class Brain; class BrainStructure; class BrowserTabContent; class CiftiMappableConnectivityMatrixDataFile; class CiftiBrainordinateDataSeriesFile; class CiftiBrainordinateLabelFile; class CiftiBrainordinateScalarFile; class CiftiParcelLabelFile; class CiftiParcelScalarFile; class CiftiParcelSeriesFile; class DisplayPropertiesLabels; class GiftiLabelTable; class Model; class LabelFile; class MetricFile; class OverlaySet; class Palette; class PaletteColorMapping; class RgbaFile; class Surface; class TopologyHelper; /// Performs coloring of surface nodes class SurfaceNodeColoring : public CaretObject { public: SurfaceNodeColoring(); virtual ~SurfaceNodeColoring(); float* colorSurfaceNodes(Model* model, Surface* surface, const int32_t browserTabIndex); private: SurfaceNodeColoring(const SurfaceNodeColoring&); SurfaceNodeColoring& operator=(const SurfaceNodeColoring&); public: virtual AString toString() const; private: enum MetricColorType { METRIC_COLOR_TYPE_NORMAL, METRIC_COLOR_TYPE_POS_THRESH_COLOR, METRIC_COLOR_TYPE_NEG_THRESH_COLOR, METRIC_COLOR_TYPE_DO_NOT_COLOR }; void colorSurfaceNodes(const DisplayPropertiesLabels* dpl, const int32_t browserTabIndex, const Surface* surface, OverlaySet* overlaySet, float* rgbaNodeColors); bool assignLabelColoring(const DisplayPropertiesLabels* dpl, const int32_t browserTabIndex, const BrainStructure* brainStructure, const Surface* surface, const LabelFile* labelFile, const int32_t mapIndex, const int32_t numberOfNodes, float* rgbv); bool assignCiftiDenseLabelColoring(const DisplayPropertiesLabels* displayPropertiesLabels, const int32_t browserTabIndex, const BrainStructure* brainStructure, const Surface* surface, CiftiBrainordinateLabelFile* ciftiLabelFile, const int32_t mapIndex, const int32_t numberOfNodes, float* rgbv); bool assignCiftiScalarColoring(const BrainStructure* brainStructure, CiftiBrainordinateScalarFile* ciftiScalarFile, const int32_t mapIndex, const int32_t numberOfNodes, float* rgbv); bool assignCiftiParcelLabelColoring(const DisplayPropertiesLabels* displayPropertiesLabels, const int32_t browserTabIndex, const BrainStructure* brainStructure, const Surface* surface, CiftiParcelLabelFile* ciftiParcelLabelFile, const int32_t mapIndex, const int32_t numberOfNodes, float* rgbv); bool assignCiftiParcelScalarColoring(const BrainStructure* brainStructure, CiftiParcelScalarFile* ciftiScalarFile, const int32_t mapIndex, const int32_t numberOfNodes, float* rgbv); bool assignCiftiDataSeriesColoring(const BrainStructure* brainStructure, CiftiBrainordinateDataSeriesFile* ciftiDataSeriesFile, const int32_t mapIndex, const int32_t numberOfNodes, float* rgbv); bool assignCiftiParcelSeriesColoring(const BrainStructure* brainStructure, CiftiParcelSeriesFile* ciftiParcelSeriesFile, const int32_t mapIndex, const int32_t numberOfNodes, float* rgbv); bool assignCiftiMappableConnectivityMatrixColoring(const BrainStructure* brainStructure, CiftiMappableConnectivityMatrixDataFile* ciftiConnectivityMatrixFile, const int32_t mapIndex, const int32_t numberOfNodes, float* rgbv); bool assignMetricColoring(const BrainStructure* brainStructure, MetricFile* metricFile, const int32_t mapIndex, const int32_t numberOfNodes, float* rgbv); bool assignRgbaColoring(const BrainStructure* brainStructure, const RgbaFile* rgbaFile, const int32_t mapIndex, const int32_t numberOfNodes, float* rgbv); void assignLabelTableColors(const GiftiLabelTable* labelTable, const LabelDrawingTypeEnum::Enum labelDrawingType, const CaretColorEnum::Enum outlineColor, const CaretPointer topologyHelper, const DisplayGroupEnum::Enum displayGroup, const int32_t browserTabIndex, const std::vector& labelIndices, const bool drawMedialWallFilledFlag, float* rgbv); void showBrainordinateHighlightRegionOfInterest(const Brain* brain, const Surface* surface, float* rgbaNodeColors); }; #ifdef __SURFACE_NODE_COLORING_DECLARE__ #endif // __SURFACE_NODE_COLORING_DECLARE__ } // namespace #endif //__SURFACE_NODE_COLORING__H_ workbench-1.1.1/src/Brain/SurfaceSelectionModel.cxx000066400000000000000000000337531255417355300223060ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __SURFACE_SELECTION_MODEL_DECLARE__ #include "SurfaceSelectionModel.h" #undef __SURFACE_SELECTION_MODEL_DECLARE__ #include "BrainStructure.h" #include "EventBrainStructureGetAll.h" #include "EventManager.h" #include "EventSurfacesGet.h" #include "SceneAttributes.h" #include "SceneClass.h" #include "Surface.h" using namespace caret; /** * \class SurfaceSelection * \brief Maintains selection of a surface. * * Maintains selection of a surface. * * The constructors allow one to limit that available surfaces to those * from specified structures and surface types. * * If the selected surface becomes invalid, a different surface will * be selected. */ /** * Constructor for surfaces from a specific structure and of the given * surface types. * * @param structure * Limit to surfaces from this structure. * @param surfaceTypes * Types of surfaces that are available for selection. */ SurfaceSelectionModel::SurfaceSelectionModel(const StructureEnum::Enum structure, const std::vector& surfaceTypes) : CaretObject() { m_allowableStructures.push_back(structure); m_allowableSurfaceTypes = surfaceTypes; CaretAssert( ! surfaceTypes.empty()); } /** * Constructor for surfaces from any structure and of the given * surface types. * * @param surfaceTypes * Types of surfaces that are available for selection. */ SurfaceSelectionModel::SurfaceSelectionModel(const std::vector& surfaceTypes) : CaretObject() { m_allowableSurfaceTypes = surfaceTypes; CaretAssert( ! surfaceTypes.empty()); } /** * Destructor. */ SurfaceSelectionModel::~SurfaceSelectionModel() { } /** * @return The selected surface (NULL if none) */ Surface* SurfaceSelectionModel::getSurface() { updateModel(); return m_selectedSurface; } /** * @return The selected surface (NULL if none) */ const Surface* SurfaceSelectionModel::getSurface() const { updateModel(); return m_selectedSurface; } /** * Set the selected surface. * @param surface * New seleted surface. */ void SurfaceSelectionModel::setSurface(Surface* surface) { m_selectedSurface = surface; } /** * Set the selected surface to a surface of any of the given types with * first type having highest priority and last type having lowest priority. * * @param surfaceType * Highest priority type. * @param surfaceType2 * Second priority type. * @param surfaceType3 * Third priority type. * @param surfaceType4 * Fourth priority type. * @param surfaceType5 * Lowest priority type. */ void SurfaceSelectionModel::setSurfaceToType(const SurfaceTypeEnum::Enum surfaceType, const SurfaceTypeEnum::Enum surfaceType2, const SurfaceTypeEnum::Enum surfaceType3, const SurfaceTypeEnum::Enum surfaceType4, const SurfaceTypeEnum::Enum surfaceType5) { std::vector surfaces = getAvailableSurfaces(); std::vector surfaceTypes; surfaceTypes.push_back(surfaceType); if (surfaceType2 != SurfaceTypeEnum::UNKNOWN) { surfaceTypes.push_back(surfaceType2); } if (surfaceType3 != SurfaceTypeEnum::UNKNOWN) { surfaceTypes.push_back(surfaceType3); } if (surfaceType4 != SurfaceTypeEnum::UNKNOWN) { surfaceTypes.push_back(surfaceType4); } if (surfaceType5 != SurfaceTypeEnum::UNKNOWN) { surfaceTypes.push_back(surfaceType5); } for (std::vector::iterator typeIter = surfaceTypes.begin(); typeIter != surfaceTypes.end(); typeIter++) { const SurfaceTypeEnum::Enum type = *typeIter; for (std::vector::iterator surfaceIter = surfaces.begin(); surfaceIter != surfaces.end(); surfaceIter++) { Surface* s = *surfaceIter; if (s->getSurfaceType() == type) { setSurface(s); return; } } } } /** * @return A vector containing surfaces available * for selection. */ std::vector SurfaceSelectionModel::getAvailableSurfaces() const { std::vector unknownSurfaces; std::vector reconstructionSurfaces; std::vector anatomicalSurfaces; std::vector inflatedSurfaces; std::vector veryInflatedSurfaces; std::vector sphericalSurfaces; std::vector semiSphericalSurfaces; std::vector ellipsoidSurfaces; std::vector flatSurfaces; std::vector hullSurfaces; /* * Get ALL surfaces */ EventSurfacesGet getSurfacesEvent; EventManager::get()->sendEvent(getSurfacesEvent.getPointer()); std::vector allSurfaces = getSurfacesEvent.getSurfaces(); for (std::vector::iterator iter = allSurfaces.begin(); iter != allSurfaces.end(); iter++) { Surface* surface = *iter; /* * Filter by structure */ bool passesStructureTestFlag = false; if (m_allowableStructures.empty()) { passesStructureTestFlag = true; } else { const StructureEnum::Enum structure = surface->getStructure(); if (std::find(m_allowableStructures.begin(), m_allowableStructures.end(), structure) != m_allowableStructures.end()) { passesStructureTestFlag = true; } } if (passesStructureTestFlag) { const SurfaceTypeEnum::Enum surfaceType = surface->getSurfaceType(); if (std::find(m_allowableSurfaceTypes.begin(), m_allowableSurfaceTypes.end(), surfaceType) != m_allowableSurfaceTypes.end()) { switch (surfaceType) { case SurfaceTypeEnum::UNKNOWN: unknownSurfaces.push_back(surface); break; case SurfaceTypeEnum::RECONSTRUCTION: reconstructionSurfaces.push_back(surface); break; case SurfaceTypeEnum::ANATOMICAL: anatomicalSurfaces.push_back(surface); break; case SurfaceTypeEnum::INFLATED: inflatedSurfaces.push_back(surface); break; case SurfaceTypeEnum::VERY_INFLATED: veryInflatedSurfaces.push_back(surface); break; case SurfaceTypeEnum::SPHERICAL: sphericalSurfaces.push_back(surface); break; case SurfaceTypeEnum::SEMI_SPHERICAL: semiSphericalSurfaces.push_back(surface); break; case SurfaceTypeEnum::ELLIPSOID: ellipsoidSurfaces.push_back(surface); break; case SurfaceTypeEnum::FLAT: flatSurfaces.push_back(surface); break; case SurfaceTypeEnum::HULL: hullSurfaces.push_back(surface); break; } } } } std::vector surfacesOut; surfacesOut.insert(surfacesOut.end(), anatomicalSurfaces.begin(), anatomicalSurfaces.end()); surfacesOut.insert(surfacesOut.end(), reconstructionSurfaces.begin(), reconstructionSurfaces.end()); surfacesOut.insert(surfacesOut.end(), inflatedSurfaces.begin(), inflatedSurfaces.end()); surfacesOut.insert(surfacesOut.end(), veryInflatedSurfaces.begin(), veryInflatedSurfaces.end()); surfacesOut.insert(surfacesOut.end(), sphericalSurfaces.begin(), sphericalSurfaces.end()); surfacesOut.insert(surfacesOut.end(), semiSphericalSurfaces.begin(), semiSphericalSurfaces.end()); surfacesOut.insert(surfacesOut.end(), ellipsoidSurfaces.begin(), ellipsoidSurfaces.end()); surfacesOut.insert(surfacesOut.end(), hullSurfaces.begin(), hullSurfaces.end()); surfacesOut.insert(surfacesOut.end(), flatSurfaces.begin(), flatSurfaces.end()); surfacesOut.insert(surfacesOut.end(), unknownSurfaces.begin(), unknownSurfaces.end()); return surfacesOut; } /** * Update the model. * May be needed if the loaded surfaces change. */ void SurfaceSelectionModel::updateModel() const { std::vector surfaces = getAvailableSurfaces(); if (surfaces.empty()) { m_selectedSurface = NULL; return; } if (m_selectedSurface != NULL) { if (std::find(surfaces.begin(), surfaces.end(), m_selectedSurface) == surfaces.end()) { m_selectedSurface = NULL; } } if (m_selectedSurface == NULL) { EventBrainStructureGetAll brainStructureEvent; EventManager::get()->sendEvent(brainStructureEvent.getPointer()); const int32_t numBrainStructures = brainStructureEvent.getNumberOfBrainStructures(); for (int32_t i = 0; i < numBrainStructures; i++) { BrainStructure* bs = brainStructureEvent.getBrainStructureByIndex(i); /* * Use the primary anatomical surface if it is acceptable */ Surface* primaryAnatomicalSurface = NULL; if (m_allowableStructures.empty()) { primaryAnatomicalSurface = bs->getPrimaryAnatomicalSurface(); } else { const StructureEnum::Enum structure = bs->getStructure(); if (std::find(m_allowableStructures.begin(), m_allowableStructures.end(), structure) != m_allowableStructures.end()) { primaryAnatomicalSurface = bs->getPrimaryAnatomicalSurface(); break; } } if (primaryAnatomicalSurface != NULL) { if (std::find(surfaces.begin(), surfaces.end(), primaryAnatomicalSurface) != surfaces.end()) { m_selectedSurface = primaryAnatomicalSurface; break; } } } if (m_selectedSurface == NULL) { m_selectedSurface = surfaces[0]; } } } /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @param instanceName * Name of the class' instance. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ SceneClass* SurfaceSelectionModel::saveToScene(const SceneAttributes* /*sceneAttributes*/, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "SurfaceSelectionModel", 1); Surface* surface = getSurface(); if (surface != NULL) { sceneClass->addString("m_selectedSurface", surface->getFileNameNoPath()); } return sceneClass; } /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass for the instance of a class that implements * this interface. May be NULL for some types of scenes. */ void SurfaceSelectionModel::restoreFromScene(const SceneAttributes* /*sceneAttributes*/, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } const AString& surfaceFileName = sceneClass->getStringValue("m_selectedSurface", ""); if (surfaceFileName.isEmpty() == false) { std::vector surfaces = getAvailableSurfaces(); for (std::vector::iterator iter = surfaces.begin(); iter != surfaces.end(); iter++) { Surface* s = *iter; if (s->getFileNameNoPath() == surfaceFileName) { setSurface(s); break; } } } } workbench-1.1.1/src/Brain/SurfaceSelectionModel.h000066400000000000000000000062751255417355300217320ustar00rootroot00000000000000#ifndef __SURFACE_SELECTION_MODEL_H__ #define __SURFACE_SELECTION_MODEL_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "SceneableInterface.h" #include "StructureEnum.h" #include "SurfaceTypeEnum.h" namespace caret { class Surface; class SurfaceSelectionModel : public CaretObject, public SceneableInterface { public: SurfaceSelectionModel(const StructureEnum::Enum structure, const std::vector& surfaceTypes); SurfaceSelectionModel(const std::vector& surfaceTypes); virtual ~SurfaceSelectionModel(); Surface* getSurface(); const Surface* getSurface() const; void setSurface(Surface* surface); void setSurfaceToType(const SurfaceTypeEnum::Enum surfaceType, const SurfaceTypeEnum::Enum surfaceType2 = SurfaceTypeEnum::UNKNOWN, const SurfaceTypeEnum::Enum surfaceType3 = SurfaceTypeEnum::UNKNOWN, const SurfaceTypeEnum::Enum surfaceType4 = SurfaceTypeEnum::UNKNOWN, const SurfaceTypeEnum::Enum surfaceType5 = SurfaceTypeEnum::UNKNOWN); std::vector getAvailableSurfaces() const; void updateModel() const; virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private: SurfaceSelectionModel(const SurfaceSelectionModel&); SurfaceSelectionModel& operator=(const SurfaceSelectionModel&); mutable Surface* m_selectedSurface; /** If empty, allow any structure, otherwise restrict to these structures */ std::vector m_allowableStructures; /** If empty, allow any surface type, otherwise restrict to these types */ std::vector m_allowableSurfaceTypes; }; #ifdef __SURFACE_SELECTION_MODEL_DECLARE__ // #endif // __SURFACE_SELECTION_MODEL_DECLARE__ } // namespace #endif //__SURFACE_SELECTION_MODEL_H__ workbench-1.1.1/src/Brain/ViewingTransformations.cxx000066400000000000000000000202201255417355300225720ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __VIEWING_TRANSFORMATIONS_DECLARE__ #include "ViewingTransformations.h" #undef __VIEWING_TRANSFORMATIONS_DECLARE__ #include "SceneClass.h" #include "SceneClassAssistant.h" using namespace caret; /** * \class caret::ViewingTransformations * \brief Viewing transformations (pan/rotate/zoom). * \ingroup Brain */ /** * Constructor. */ ViewingTransformations::ViewingTransformations() : CaretObject() { m_sceneAssistant = new SceneClassAssistant(); m_rotationMatrix = new Matrix4x4(); m_translation[0] = 0.0; m_translation[1] = 0.0; m_translation[2] = 0.0; m_scaling = 1.0; m_sceneAssistant->addArray("m_translation", m_translation, 3, 0.0); m_sceneAssistant->add("m_scaling", &m_scaling); } /** * Destructor. */ ViewingTransformations::~ViewingTransformations() { delete m_rotationMatrix; delete m_sceneAssistant; } /** * Copy constructor. * @param obj * Object that is copied. */ ViewingTransformations::ViewingTransformations(const ViewingTransformations& obj) : CaretObject(obj), SceneableInterface(obj) { this->copyHelperViewingTransformations(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ ViewingTransformations& ViewingTransformations::operator=(const ViewingTransformations& obj) { if (this != &obj) { CaretObject::operator=(obj); this->copyHelperViewingTransformations(obj); } return *this; } /** * Helps with copying an object of this type. * @param obj * Object that is copied. */ void ViewingTransformations::copyHelperViewingTransformations(const ViewingTransformations& obj) { *m_rotationMatrix = *obj.m_rotationMatrix; m_translation[0] = obj.m_translation[0]; m_translation[1] = obj.m_translation[1]; m_translation[2] = obj.m_translation[2]; m_scaling = obj.m_scaling; } /** * @return The viewing translation. */ const float* ViewingTransformations::getTranslation() const { return m_translation; } /** * Get the viewing translation. * * @param translationOut * Translation values output. */ void ViewingTransformations::getTranslation(float translationOut[3]) const { translationOut[0] = m_translation[0]; translationOut[1] = m_translation[1]; translationOut[2] = m_translation[2]; } /** * Set the viewing translation. * * @param translation * New translation values. */ void ViewingTransformations::setTranslation( const float translation[3]) { m_translation[0] = translation[0]; m_translation[1] = translation[1]; m_translation[2] = translation[2]; } /** * Set the viewing translation. * * @param translationX * New translation X-value. * @param translationY * New translation Y-value. * @param translationZ * New translation Z-value. */ void ViewingTransformations::setTranslation(const float translationX, const float translationY, const float translationZ) { m_translation[0] = translationX; m_translation[1] = translationY; m_translation[2] = translationZ; } /** * @return The viewing scaling. */ float ViewingTransformations::getScaling() const { return m_scaling; } /** * Set the viewing scaling. * @param scaling * New value for scaling. */ void ViewingTransformations::setScaling(const float scaling) { m_scaling = scaling; } /** * @return The rotation matrix. */ Matrix4x4 ViewingTransformations::getRotationMatrix() const { return *m_rotationMatrix; } /** * Set the rotation matrix. * * @param rotationMatrix * The new rotation matrix. */ void ViewingTransformations::setRotationMatrix(const Matrix4x4& rotationMatrix) { *m_rotationMatrix = rotationMatrix; } /** * Reset the view to the default view for a SURFACE */ void ViewingTransformations::resetView() { setTranslation(0.0, 0.0, 0.0); m_rotationMatrix->identity(); setScaling(1.0); leftView(); } /** * Set to a right side view. */ void ViewingTransformations::rightView() { m_rotationMatrix->identity(); m_rotationMatrix->rotateY(-90.0); m_rotationMatrix->rotateZ(-90.0); } /** * set to a left side view. */ void ViewingTransformations::leftView() { m_rotationMatrix->identity(); m_rotationMatrix->rotateY(90.0); m_rotationMatrix->rotateZ(90.0); } /** * set to a anterior view. */ void ViewingTransformations::anteriorView() { m_rotationMatrix->identity(); m_rotationMatrix->setRotation(90.0, 0.0, -180.0); // m_rotationMatrix->rotateX(-90.0); // m_rotationMatrix->rotateY(180.0); } /** * set to a posterior view. */ void ViewingTransformations::posteriorView() { m_rotationMatrix->identity(); m_rotationMatrix->setRotation(-90.0, 0.0, 0.0); // m_rotationMatrix->rotateX(-90.0); } /** * set to a dorsal view. */ void ViewingTransformations::dorsalView() { // m_rotationMatrix->identity(); m_rotationMatrix->setRotation(0.0, 0.0, 90.0); } /** * set to a ventral view. */ void ViewingTransformations::ventralView() { // m_rotationMatrix->identity(); // m_rotationMatrix->rotateY(-180.0); m_rotationMatrix->setRotation(0.0, 180.0, 90.0); } /** * Get a description of this object's content. * @return String describing this object's content. */ AString ViewingTransformations::toString() const { return "ViewingTransformations"; } /** * Save information specific to this type of model to the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @param instanceName * Name of instance in the scene. */ SceneClass* ViewingTransformations::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "ViewingTransformations", 1); m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); /* * Save rotation matrices. */ float matrix[4][4]; m_rotationMatrix->getMatrix(matrix); sceneClass->addFloatArray("m_rotationMatrix", (float*)matrix, 16); return sceneClass; } /** * Restore information specific to the type of model from the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass from which model specific information is obtained. */ void ViewingTransformations::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); /* * Restore rotation matrices. */ float matrix[4][4]; if (sceneClass->getFloatArrayValue("m_rotationMatrix", (float*)matrix, 16) == 16) { m_rotationMatrix->setMatrix(matrix); } else { m_rotationMatrix->identity(); } } workbench-1.1.1/src/Brain/ViewingTransformations.h000066400000000000000000000063471255417355300222350ustar00rootroot00000000000000#ifndef __VIEWING_TRANSFORMATIONS_H__ #define __VIEWING_TRANSFORMATIONS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "Matrix4x4.h" #include "SceneableInterface.h" namespace caret { class SceneClassAssistant; class ViewingTransformations : public CaretObject, public SceneableInterface { public: ViewingTransformations(); virtual ~ViewingTransformations(); ViewingTransformations(const ViewingTransformations& obj); ViewingTransformations& operator=(const ViewingTransformations& obj); const float* getTranslation() const; void getTranslation(float translationOut[3]) const; void setTranslation( const float translation[3]); void setTranslation(const float translationX, const float translationY, const float translationZ); float getScaling() const; void setScaling(const float scaling); Matrix4x4 getRotationMatrix() const; void setRotationMatrix(const Matrix4x4& rotationMatrix); virtual void resetView(); virtual void rightView(); virtual void leftView(); virtual void anteriorView(); virtual void posteriorView(); virtual void dorsalView(); virtual void ventralView(); virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); // ADD_NEW_METHODS_HERE virtual AString toString() const; protected: /** Rotation matrix. */ Matrix4x4* m_rotationMatrix; /** Translation */ float m_translation[3]; /** Scaling. */ float m_scaling; private: void copyHelperViewingTransformations(const ViewingTransformations& obj); SceneClassAssistant* m_sceneAssistant; // ADD_NEW_MEMBERS_HERE }; #ifdef __VIEWING_TRANSFORMATIONS_DECLARE__ // #endif // __VIEWING_TRANSFORMATIONS_DECLARE__ } // namespace #endif //__VIEWING_TRANSFORMATIONS_H__ workbench-1.1.1/src/Brain/ViewingTransformationsCerebellum.cxx000066400000000000000000000055231255417355300246030ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __VIEWING_TRANSFORMATIONS_CEREBELLUM_DECLARE__ #include "ViewingTransformationsCerebellum.h" #undef __VIEWING_TRANSFORMATIONS_CEREBELLUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::ViewingTransformationsCerebellum * \brief Viewing transformations (pan/rotate/zoom) for cerebellum. * \ingroup Brain * * Extends ViewingTransformations with differences for cerebellum viewing. */ /** * Constructor. */ ViewingTransformationsCerebellum::ViewingTransformationsCerebellum() : ViewingTransformations() { } /** * Destructor. */ ViewingTransformationsCerebellum::~ViewingTransformationsCerebellum() { } /** * Copy constructor. * @param obj * Object that is copied. */ ViewingTransformationsCerebellum::ViewingTransformationsCerebellum(const ViewingTransformationsCerebellum& obj) : ViewingTransformations(obj) { this->copyHelperViewingTransformationsCerebellum(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ ViewingTransformationsCerebellum& ViewingTransformationsCerebellum::operator=(const ViewingTransformationsCerebellum& obj) { if (this != &obj) { ViewingTransformations::operator=(obj); this->copyHelperViewingTransformationsCerebellum(obj); } return *this; } /** * Helps with copying an object of this type. * @param obj * Object that is copied. */ void ViewingTransformationsCerebellum::copyHelperViewingTransformationsCerebellum(const ViewingTransformationsCerebellum& /*obj*/) { /* nothing to copy */ } /** * Reset the view to the default view. */ void ViewingTransformationsCerebellum::resetView() { ViewingTransformations::resetView(); dorsalView(); } /** * set to a dorsal view. */ void ViewingTransformationsCerebellum::dorsalView() { m_rotationMatrix->setRotation(0.0, 0.0, 0.0); } /** * set to a ventral view. */ void ViewingTransformationsCerebellum::ventralView() { m_rotationMatrix->setRotation(0.0, 180.0, 180.0); } workbench-1.1.1/src/Brain/ViewingTransformationsCerebellum.h000066400000000000000000000036371255417355300242340ustar00rootroot00000000000000#ifndef __VIEWING_TRANSFORMATIONS_CEREBELLUM_H__ #define __VIEWING_TRANSFORMATIONS_CEREBELLUM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "ViewingTransformations.h" namespace caret { class ViewingTransformationsCerebellum : public ViewingTransformations { public: ViewingTransformationsCerebellum(); virtual ~ViewingTransformationsCerebellum(); ViewingTransformationsCerebellum(const ViewingTransformationsCerebellum& obj); ViewingTransformationsCerebellum& operator=(const ViewingTransformationsCerebellum& obj); virtual void resetView(); virtual void ventralView(); virtual void dorsalView(); // ADD_NEW_METHODS_HERE private: void copyHelperViewingTransformationsCerebellum(const ViewingTransformationsCerebellum& obj); // ADD_NEW_MEMBERS_HERE }; #ifdef __VIEWING_TRANSFORMATIONS_CEREBELLUM_DECLARE__ // #endif // __VIEWING_TRANSFORMATIONS_CEREBELLUM_DECLARE__ } // namespace #endif //__VIEWING_TRANSFORMATIONS_CEREBELLUM_H__ workbench-1.1.1/src/Brain/ViewingTransformationsVolume.cxx000066400000000000000000000047441255417355300237770ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __VIEWING_TRANSFORMATIONS_VOLUME_DECLARE__ #include "ViewingTransformationsVolume.h" #undef __VIEWING_TRANSFORMATIONS_VOLUME_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::ViewingTransformationsVolume * \brief Viewing transformations (pan/rotate/zoom) for volume. * \ingroup Brain * * Extends ViewingTransformations with differences for volume viewing. */ /** * Constructor. */ ViewingTransformationsVolume::ViewingTransformationsVolume() : ViewingTransformations() { } /** * Destructor. */ ViewingTransformationsVolume::~ViewingTransformationsVolume() { } /** * Copy constructor. * @param obj * Object that is copied. */ ViewingTransformationsVolume::ViewingTransformationsVolume(const ViewingTransformationsVolume& obj) : ViewingTransformations(obj) { this->copyHelperViewingTransformationsVolume(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ ViewingTransformationsVolume& ViewingTransformationsVolume::operator=(const ViewingTransformationsVolume& obj) { if (this != &obj) { ViewingTransformations::operator=(obj); this->copyHelperViewingTransformationsVolume(obj); } return *this; } /** * Helps with copying an object of this type. * @param obj * Object that is copied. */ void ViewingTransformationsVolume::copyHelperViewingTransformationsVolume(const ViewingTransformationsVolume& /*obj*/) { } /** * Reset the view to the default view for a VOLUME. */ void ViewingTransformationsVolume::resetView() { ViewingTransformations::resetView(); m_rotationMatrix->identity(); } workbench-1.1.1/src/Brain/ViewingTransformationsVolume.h000066400000000000000000000034271255417355300234210ustar00rootroot00000000000000#ifndef __VIEWING_TRANSFORMATIONS_VOLUME_H__ #define __VIEWING_TRANSFORMATIONS_VOLUME_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "ViewingTransformations.h" namespace caret { class ViewingTransformationsVolume : public ViewingTransformations { public: ViewingTransformationsVolume(); virtual ~ViewingTransformationsVolume(); ViewingTransformationsVolume(const ViewingTransformationsVolume& obj); ViewingTransformationsVolume& operator=(const ViewingTransformationsVolume& obj); virtual void resetView(); // ADD_NEW_METHODS_HERE private: void copyHelperViewingTransformationsVolume(const ViewingTransformationsVolume& obj); // ADD_NEW_MEMBERS_HERE }; #ifdef __VIEWING_TRANSFORMATIONS_VOLUME_DECLARE__ // #endif // __VIEWING_TRANSFORMATIONS_VOLUME_DECLARE__ } // namespace #endif //__VIEWING_TRANSFORMATIONS_VOLUME_H__ workbench-1.1.1/src/Brain/VolumeSliceDrawingTypeEnum.cxx000066400000000000000000000256501255417355300233160ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __VOLUME_SLICE_DRAWING_TYPE_ENUM_DECLARE__ #include "VolumeSliceDrawingTypeEnum.h" #undef __VOLUME_SLICE_DRAWING_TYPE_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::VolumeSliceDrawingTypeEnum * \brief Enumerated type for type of volume slice drawing * * Enumerated type for drawing a single slice or a montage of slices * * Using this enumerated type in the GUI with an EnumComboBoxTemplate * * Header File (.h) * Forward declare the data type: * class EnumComboBoxTemplate; * * Declare the member: * EnumComboBoxTemplate* m_volumeSliceDrawingTypeEnumComboBox; * * Declare a slot that is called when user changes selection * private slots: * void volumeSliceDrawingTypeEnumComboBoxItemActivated(); * * Implementation File (.cxx) * Include the header files * #include "EnumComboBoxTemplate.h" * #include "VolumeSliceDrawingTypeEnum.h" * * Instatiate: * m_volumeSliceDrawingTypeEnumComboBox = new EnumComboBoxTemplate(this); * m_volumeSliceDrawingTypeEnumComboBox->setup(); * * Get notified when the user changes the selection: * QObject::connect(m_volumeSliceDrawingTypeEnumComboBox, SIGNAL(itemActivated()), * this, SLOT(volumeSliceDrawingTypeEnumComboBoxItemActivated())); * * Update the selection: * m_volumeSliceDrawingTypeEnumComboBox->setSelectedItem(NEW_VALUE); * * Read the selection: * const VolumeSliceDrawingTypeEnum::Enum VARIABLE = m_volumeSliceDrawingTypeEnumComboBox->getSelectedItem(); * */ /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ VolumeSliceDrawingTypeEnum::VolumeSliceDrawingTypeEnum(const Enum enumValue, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCodeCounter++; this->name = name; this->guiName = guiName; } /** * Destructor. */ VolumeSliceDrawingTypeEnum::~VolumeSliceDrawingTypeEnum() { } /** * Initialize the enumerated metadata. */ void VolumeSliceDrawingTypeEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(VolumeSliceDrawingTypeEnum(VOLUME_SLICE_DRAW_MONTAGE, "VOLUME_SLICE_DRAW_MONTAGE", "Draw a montage of slices")); enumData.push_back(VolumeSliceDrawingTypeEnum(VOLUME_SLICE_DRAW_SINGLE, "VOLUME_SLICE_DRAW_SINGLE", "Draw a single slice")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const VolumeSliceDrawingTypeEnum* VolumeSliceDrawingTypeEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const VolumeSliceDrawingTypeEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString VolumeSliceDrawingTypeEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const VolumeSliceDrawingTypeEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ VolumeSliceDrawingTypeEnum::Enum VolumeSliceDrawingTypeEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = VolumeSliceDrawingTypeEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const VolumeSliceDrawingTypeEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type VolumeSliceDrawingTypeEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString VolumeSliceDrawingTypeEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const VolumeSliceDrawingTypeEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ VolumeSliceDrawingTypeEnum::Enum VolumeSliceDrawingTypeEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = VolumeSliceDrawingTypeEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const VolumeSliceDrawingTypeEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type VolumeSliceDrawingTypeEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t VolumeSliceDrawingTypeEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const VolumeSliceDrawingTypeEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ VolumeSliceDrawingTypeEnum::Enum VolumeSliceDrawingTypeEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = VolumeSliceDrawingTypeEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const VolumeSliceDrawingTypeEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type VolumeSliceDrawingTypeEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void VolumeSliceDrawingTypeEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void VolumeSliceDrawingTypeEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(VolumeSliceDrawingTypeEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void VolumeSliceDrawingTypeEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(VolumeSliceDrawingTypeEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } workbench-1.1.1/src/Brain/VolumeSliceDrawingTypeEnum.h000066400000000000000000000063211255417355300227350ustar00rootroot00000000000000#ifndef __VOLUME_SLICE_DRAWING_TYPE_ENUM_H__ #define __VOLUME_SLICE_DRAWING_TYPE_ENUM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class VolumeSliceDrawingTypeEnum { public: /** * Enumerated values. */ enum Enum { /** Draw a montage of slices */ VOLUME_SLICE_DRAW_MONTAGE, /** Draw a single slice */ VOLUME_SLICE_DRAW_SINGLE }; ~VolumeSliceDrawingTypeEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); private: VolumeSliceDrawingTypeEnum(const Enum enumValue, const AString& name, const AString& guiName); static const VolumeSliceDrawingTypeEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; #ifdef __VOLUME_SLICE_DRAWING_TYPE_ENUM_DECLARE__ std::vector VolumeSliceDrawingTypeEnum::enumData; bool VolumeSliceDrawingTypeEnum::initializedFlag = false; int32_t VolumeSliceDrawingTypeEnum::integerCodeCounter = 0; #endif // __VOLUME_SLICE_DRAWING_TYPE_ENUM_DECLARE__ } // namespace #endif //__VOLUME_SLICE_DRAWING_TYPE_ENUM_H__ workbench-1.1.1/src/Brain/VolumeSliceSettings.cxx000066400000000000000000000545521255417355300220370ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __VOLUME_SLICE_SETTINGS_DECLARE__ #include "VolumeSliceSettings.h" #undef __VOLUME_SLICE_SETTINGS_DECLARE__ #include "CaretLogger.h" #include "PlainTextStringBuilder.h" #include "SceneClass.h" #include "SceneClassAssistant.h" #include "SceneEnumeratedType.h" #include "VolumeFile.h" using namespace caret; /** * \class caret::VolumeSliceSettings * \brief Settings that control the display of volume slices. * \ingroup Brain */ /** * Constructor. */ VolumeSliceSettings::VolumeSliceSettings() : CaretObject() { m_sliceViewPlane = VolumeSliceViewPlaneEnum::AXIAL; m_sliceDrawingType = VolumeSliceDrawingTypeEnum::VOLUME_SLICE_DRAW_SINGLE; m_sliceProjectionType = VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_ORTHOGONAL; m_montageNumberOfColumns = 3; m_montageNumberOfRows = 4; m_montageSliceSpacing = 5; m_sliceCoordinateAxial = 0.0; m_sliceCoordinateCoronal = 0.0; m_sliceCoordinateParasagittal = 0.0; m_sliceEnabledAxial = true; m_sliceEnabledCoronal = true; m_sliceEnabledParasagittal = true; m_initializedFlag = false; //m_lastVolumeFile = NULL; m_sceneAssistant = new SceneClassAssistant(); m_sceneAssistant->add("m_sliceViewPlane", &m_sliceViewPlane); m_sceneAssistant->add("m_sliceDrawingType", &m_sliceDrawingType); m_sceneAssistant->add("m_sliceProjectionType", &m_sliceProjectionType); m_sceneAssistant->add("m_montageNumberOfColumns", &m_montageNumberOfColumns); m_sceneAssistant->add("m_montageNumberOfRows", &m_montageNumberOfRows); m_sceneAssistant->add("m_montageSliceSpacing", &m_montageSliceSpacing); m_sceneAssistant->add("m_sliceCoordinateAxial", &m_sliceCoordinateAxial); m_sceneAssistant->add("m_sliceCoordinateCoronal", &m_sliceCoordinateCoronal); m_sceneAssistant->add("m_sliceCoordinateParasagittal", &m_sliceCoordinateParasagittal); m_sceneAssistant->add("m_sliceEnabledAxial", &m_sliceEnabledAxial); m_sceneAssistant->add("m_sliceEnabledCoronal", &m_sliceEnabledCoronal); m_sceneAssistant->add("m_sliceEnabledParasagittal", &m_sliceEnabledParasagittal); } /** * Destructor. */ VolumeSliceSettings::~VolumeSliceSettings() { delete m_sceneAssistant; } /** * Copy constructor. * @param obj * Object that is copied. */ VolumeSliceSettings::VolumeSliceSettings(const VolumeSliceSettings& obj) : CaretObject(obj), SceneableInterface(obj) { this->copyHelperVolumeSliceSettings(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ VolumeSliceSettings& VolumeSliceSettings::operator=(const VolumeSliceSettings& obj) { if (this != &obj) { CaretObject::operator=(obj); this->copyHelperVolumeSliceSettings(obj); } return *this; } /** * Helps with copying an object of this type. * @param obj * Object that is copied. */ void VolumeSliceSettings::copyHelperVolumeSliceSettings(const VolumeSliceSettings& obj) { m_sliceViewPlane = obj.m_sliceViewPlane; m_sliceDrawingType = obj.m_sliceDrawingType; m_sliceProjectionType = obj.m_sliceProjectionType; m_montageNumberOfColumns = obj.m_montageNumberOfColumns; m_montageNumberOfRows = obj.m_montageNumberOfRows; m_montageSliceSpacing = obj.m_montageSliceSpacing; m_sliceCoordinateParasagittal = obj.m_sliceCoordinateParasagittal; m_sliceCoordinateCoronal = obj.m_sliceCoordinateCoronal; m_sliceCoordinateAxial = obj.m_sliceCoordinateAxial; m_sliceEnabledParasagittal = obj.m_sliceEnabledParasagittal; m_sliceEnabledCoronal = obj.m_sliceEnabledCoronal; m_sliceEnabledAxial = obj.m_sliceEnabledAxial; m_initializedFlag = true; } /** * Get a description of this object's content. * @return String describing this object's content. */ AString VolumeSliceSettings::toString() const { return "VolumeSliceSettings"; } /** * Get a text description of the instance's content. * * @param modelType * Type of model. * @param descriptionOut * Description of the instance's content. */ void VolumeSliceSettings::getDescriptionOfContent(const ModelTypeEnum::Enum modelType, PlainTextStringBuilder& descriptionOut) const { bool volumeFlag = false; bool wholeBrainFlag = false; switch (modelType) { case ModelTypeEnum::MODEL_TYPE_CHART: break; case ModelTypeEnum::MODEL_TYPE_INVALID: break; case ModelTypeEnum::MODEL_TYPE_SURFACE: break; case ModelTypeEnum::MODEL_TYPE_SURFACE_MONTAGE: break; case ModelTypeEnum::MODEL_TYPE_VOLUME_SLICES: volumeFlag = true; break; case ModelTypeEnum::MODEL_TYPE_WHOLE_BRAIN: wholeBrainFlag = true; break; } bool showParasagittalCoordinate = false; bool showCoronalCoordinate = false; bool showAxialCoordinate = false; descriptionOut.addLine("Volume Slice Settings: "); descriptionOut.pushIndentation(); if (volumeFlag) { descriptionOut.addLine(" View Plane: " + VolumeSliceViewPlaneEnum::toGuiName(m_sliceViewPlane)); showParasagittalCoordinate = true; showCoronalCoordinate = true; showAxialCoordinate = true; } if (wholeBrainFlag) { if (m_sliceEnabledParasagittal) { showParasagittalCoordinate = true; } if (m_sliceEnabledCoronal) { showCoronalCoordinate = true; } if (m_sliceEnabledAxial) { showAxialCoordinate = true; } } if (showParasagittalCoordinate) { descriptionOut.addLine(" Parasagittal (X-axis) Coordinate: " + AString::number(m_sliceCoordinateParasagittal, 'f', 3)); } if (showCoronalCoordinate) { descriptionOut.addLine(" Coronal (Y-axis) Coordinate: " + AString::number(m_sliceCoordinateCoronal, 'f', 3)); } if (showAxialCoordinate) { descriptionOut.addLine(" Axial (Z-axis) Coordinate: " + AString::number(m_sliceCoordinateAxial, 'f', 3)); } descriptionOut.popIndentation(); } /** * @return The slice view plane. * */ VolumeSliceViewPlaneEnum::Enum VolumeSliceSettings::getSliceViewPlane() const { return m_sliceViewPlane; } /** * Set the slice view plane. * @param windowTabNumber * New value for slice plane. */ void VolumeSliceSettings::setSliceViewPlane(const VolumeSliceViewPlaneEnum::Enum slicePlane) { m_sliceViewPlane = slicePlane; } /** * @return Type of slice drawing (single/montage) */ VolumeSliceDrawingTypeEnum::Enum VolumeSliceSettings::getSliceDrawingType() const { return m_sliceDrawingType; } /** * Set type of slice drawing (single/montage) * * @param sliceDrawingType * New value for slice drawing type. */ void VolumeSliceSettings::setSliceDrawingType(const VolumeSliceDrawingTypeEnum::Enum sliceDrawingType) { m_sliceDrawingType = sliceDrawingType; } /** * @return Type of slice projection (oblique/orthogonal) */ VolumeSliceProjectionTypeEnum::Enum VolumeSliceSettings::getSliceProjectionType() const { return m_sliceProjectionType; } /** * Set type of slice projection (oblique/orthogonal) * * @param sliceProjectionType * New value for slice projection type. */ void VolumeSliceSettings::setSliceProjectionType(const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType) { m_sliceProjectionType = sliceProjectionType; } /** * @return the montage number of columns for the given window tab. */ int32_t VolumeSliceSettings::getMontageNumberOfColumns() const { return m_montageNumberOfColumns; } /** * Set the montage number of columns in the given window tab. * @param montageNumberOfColumns * New value for montage number of columns */ void VolumeSliceSettings::setMontageNumberOfColumns(const int32_t montageNumberOfColumns) { m_montageNumberOfColumns = montageNumberOfColumns; } /** * @return the montage number of rows for the given window tab. */ int32_t VolumeSliceSettings::getMontageNumberOfRows() const { return m_montageNumberOfRows; } /** * Set the montage number of rows. * @param montageNumberOfRows * New value for montage number of rows */ void VolumeSliceSettings::setMontageNumberOfRows(const int32_t montageNumberOfRows) { m_montageNumberOfRows = montageNumberOfRows; } /** * @return the montage slice spacing. */ int32_t VolumeSliceSettings::getMontageSliceSpacing() const { return m_montageSliceSpacing; } /** * Set the montage slice spacing. * @param montageSliceSpacing * New value for montage slice spacing */ void VolumeSliceSettings::setMontageSliceSpacing(const int32_t montageSliceSpacing) { m_montageSliceSpacing = montageSliceSpacing; } /** * Set the selected slices to the origin. */ void VolumeSliceSettings::setSlicesToOrigin() { selectSlicesAtOrigin(); } /** * Reset the slices. */ void VolumeSliceSettings::reset() { m_sliceCoordinateAxial = 0.0; m_sliceCoordinateCoronal = 0.0; m_sliceCoordinateParasagittal = 0.0; m_sliceEnabledAxial = true; m_sliceEnabledCoronal = true; m_sliceEnabledParasagittal = true; m_initializedFlag = false; } /** * Update the slices coordinates so that they are valid for * the given VolumeFile. * @param volumeFile * File for which slice coordinates are made valid. */ void VolumeSliceSettings::updateForVolumeFile(const VolumeMappableInterface* volumeFile) { if (volumeFile == NULL) { reset(); return; } if (m_initializedFlag == false) { m_initializedFlag = true; selectSlicesAtOrigin(); } /* * These calls will make the slices valid */ getSliceIndexParasagittal(volumeFile); getSliceIndexCoronal(volumeFile); getSliceIndexAxial(volumeFile); } /** * Set the slice indices so that they are at the origin. */ void VolumeSliceSettings::selectSlicesAtOrigin() { m_sliceCoordinateAxial = 0.0; m_sliceCoordinateCoronal = 0.0; m_sliceCoordinateParasagittal = 0.0; } /** * Set the selected slices to the given coordinate. * @param xyz * Coordinate for selected slices. */ void VolumeSliceSettings::selectSlicesAtCoordinate(const float xyz[3]) { m_sliceCoordinateParasagittal = xyz[0]; m_sliceCoordinateCoronal = xyz[1]; m_sliceCoordinateAxial = xyz[2]; } /** * Return the axial slice index. * @return * Axial slice index or negative if invalid */ int64_t VolumeSliceSettings::getSliceIndexAxial(const VolumeMappableInterface* volumeFile) const { CaretAssert(volumeFile); std::vector dimensions; volumeFile->getDimensions(dimensions); int64_t axialSliceOut = -1; if (dimensions[2] >= 0) { int64_t paragittalSlice, coronalSlice; volumeFile->enclosingVoxel(m_sliceCoordinateParasagittal, m_sliceCoordinateCoronal, m_sliceCoordinateAxial, paragittalSlice, coronalSlice, axialSliceOut); if (axialSliceOut < 0) { axialSliceOut = 0; float xyz[3]; volumeFile->indexToSpace(0, 0, axialSliceOut, xyz); m_sliceCoordinateAxial = xyz[2]; } else if (axialSliceOut >= dimensions[2]) { axialSliceOut = dimensions[2] - 1; float xyz[3]; volumeFile->indexToSpace(0, 0, axialSliceOut, xyz); m_sliceCoordinateAxial = xyz[2]; } } return axialSliceOut; } /** * Set the axial slice index. * @param * New value for axial slice index. */ void VolumeSliceSettings::setSliceIndexAxial(const VolumeMappableInterface* volumeFile, const int64_t sliceIndexAxial) { CaretAssert(volumeFile); float xyz[3]; volumeFile->indexToSpace(0, 0, sliceIndexAxial, xyz); m_sliceCoordinateAxial = xyz[2]; } /** * Return the coronal slice index. * @return * Coronal slice index. */ int64_t VolumeSliceSettings::getSliceIndexCoronal(const VolumeMappableInterface* volumeFile) const { CaretAssert(volumeFile); std::vector dimensions; volumeFile->getDimensions(dimensions); int64_t coronalSliceOut = -1; if (dimensions[1] >= 0) { int64_t paragittalSlice, axialSlice; volumeFile->enclosingVoxel(m_sliceCoordinateParasagittal, m_sliceCoordinateCoronal, m_sliceCoordinateAxial, paragittalSlice, coronalSliceOut, axialSlice); if (coronalSliceOut < 0) { coronalSliceOut = 0; float xyz[3]; volumeFile->indexToSpace(0, coronalSliceOut, 0, xyz); m_sliceCoordinateCoronal = xyz[1]; } else if (coronalSliceOut >= dimensions[1]) { coronalSliceOut = dimensions[1] - 1; float xyz[3]; volumeFile->indexToSpace(0, coronalSliceOut, 0, xyz); m_sliceCoordinateCoronal = xyz[1]; } } return coronalSliceOut; } /** * Set the coronal slice index. * @param sliceIndexCoronal * New value for coronal slice index. */ void VolumeSliceSettings::setSliceIndexCoronal(const VolumeMappableInterface* volumeFile, const int64_t sliceIndexCoronal) { CaretAssert(volumeFile); float xyz[3]; volumeFile->indexToSpace(0, sliceIndexCoronal, 0, xyz); m_sliceCoordinateCoronal = xyz[1]; } /** * Return the parasagittal slice index. * @return * Parasagittal slice index. */ int64_t VolumeSliceSettings::getSliceIndexParasagittal(const VolumeMappableInterface* volumeFile) const { std::vector dimensions; volumeFile->getDimensions(dimensions); int64_t parasagittalSliceOut = -1; if (dimensions[0] >= 0) { int64_t coronalSlice, axialSlice; volumeFile->enclosingVoxel(m_sliceCoordinateParasagittal, m_sliceCoordinateCoronal, m_sliceCoordinateAxial, parasagittalSliceOut, coronalSlice, axialSlice); if (parasagittalSliceOut < 0) { parasagittalSliceOut = 0; float xyz[3]; volumeFile->indexToSpace(parasagittalSliceOut, 0, 0, xyz); m_sliceCoordinateParasagittal = xyz[0]; } else if (parasagittalSliceOut >= dimensions[0]) { parasagittalSliceOut = dimensions[0] - 1; float xyz[3]; volumeFile->indexToSpace(parasagittalSliceOut, 0, 0, xyz); m_sliceCoordinateParasagittal = xyz[0]; } } return parasagittalSliceOut; } /** * Set the parasagittal slice index. * @param sliceIndexParasagittal * New value for parasagittal slice index. */ void VolumeSliceSettings::setSliceIndexParasagittal(const VolumeMappableInterface* volumeFile, const int64_t sliceIndexParasagittal) { CaretAssert(volumeFile); float xyz[3]; volumeFile->indexToSpace(sliceIndexParasagittal, 0, 0, xyz); m_sliceCoordinateParasagittal = xyz[0]; } /** * @return Coordinate of axial slice. */ float VolumeSliceSettings::getSliceCoordinateAxial() const { return m_sliceCoordinateAxial; } /** * Set the coordinate for the axial slice. * @param z * Z-coordinate for axial slice. */ void VolumeSliceSettings::setSliceCoordinateAxial(const float z) { m_sliceCoordinateAxial = z; } /** * @return Coordinate of coronal slice. */ float VolumeSliceSettings::getSliceCoordinateCoronal() const { return m_sliceCoordinateCoronal; } /** * Set the coordinate for the coronal slice. * @param y * Y-coordinate for coronal slice. */ void VolumeSliceSettings::setSliceCoordinateCoronal(const float y) { m_sliceCoordinateCoronal = y; } /** * @return Coordinate of parasagittal slice. */ float VolumeSliceSettings::getSliceCoordinateParasagittal() const { return m_sliceCoordinateParasagittal; } /** * Set the coordinate for the parasagittal slice. * @param x * X-coordinate for parasagittal slice. */ void VolumeSliceSettings::setSliceCoordinateParasagittal(const float x) { m_sliceCoordinateParasagittal = x; } /** * Is the parasagittal slice enabled? * @return * Enabled status of parasagittal slice. */ bool VolumeSliceSettings::isSliceParasagittalEnabled() const { return m_sliceEnabledParasagittal; } /** * Set the enabled status of the parasagittal slice. * @param sliceEnabledParasagittal * New enabled status. */ void VolumeSliceSettings::setSliceParasagittalEnabled(const bool sliceEnabledParasagittal) { m_sliceEnabledParasagittal = sliceEnabledParasagittal; } /** * Is the coronal slice enabled? * @return * Enabled status of coronal slice. */ bool VolumeSliceSettings::isSliceCoronalEnabled() const { return m_sliceEnabledCoronal; } /** * Set the enabled status of the coronal slice. * @param sliceEnabledCoronal * New enabled status. */ void VolumeSliceSettings::setSliceCoronalEnabled(const bool sliceEnabledCoronal) { m_sliceEnabledCoronal = sliceEnabledCoronal; } /** * Is the axial slice enabled? * @return * Enabled status of axial slice. */ bool VolumeSliceSettings::isSliceAxialEnabled() const { return m_sliceEnabledAxial; } /** * Set the enabled status of the axial slice. * @param sliceEnabledAxial * New enabled status. */ void VolumeSliceSettings::setSliceAxialEnabled(const bool sliceEnabledAxial) { m_sliceEnabledAxial = sliceEnabledAxial; } /** * Save information specific to this type of model to the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @param instanceName * Name of instance in the scene. */ SceneClass* VolumeSliceSettings::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "VolumeSliceSettings", 2); m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); return sceneClass; } /** * Restore information specific to the type of model from the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass from which model specific information is obtained. */ void VolumeSliceSettings::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } /* * Added in scene version 2 */ m_sliceDrawingType = VolumeSliceDrawingTypeEnum::VOLUME_SLICE_DRAW_SINGLE; m_sliceProjectionType = VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_ORTHOGONAL; m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); if (sceneClass->getVersionNumber() < 2) { /* * Set slice drawing type and projection type using old slice view mode. */ const AString oldViewModeValue = sceneClass->getEnumeratedTypeValueAsString("m_sliceViewMode"); if (! oldViewModeValue.isEmpty()) { if (oldViewModeValue == "MONTAGE") { m_sliceDrawingType = VolumeSliceDrawingTypeEnum::VOLUME_SLICE_DRAW_MONTAGE; m_sliceProjectionType = VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_ORTHOGONAL; } else if (oldViewModeValue == "OBLIQUE") { m_sliceDrawingType = VolumeSliceDrawingTypeEnum::VOLUME_SLICE_DRAW_SINGLE; m_sliceProjectionType = VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_OBLIQUE; } else if (oldViewModeValue == "ORTHOGONAL") { m_sliceDrawingType = VolumeSliceDrawingTypeEnum::VOLUME_SLICE_DRAW_SINGLE; m_sliceProjectionType = VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_ORTHOGONAL; } else { CaretLogWarning("Unrecognized value for old m_sliceViewMode: " + oldViewModeValue); } } } /* * Restoring scene initialize all members. * If this is not done, the slices will be reset * in updateForVolumeFile(). */ m_initializedFlag = true; } workbench-1.1.1/src/Brain/VolumeSliceSettings.h000066400000000000000000000147171255417355300214630ustar00rootroot00000000000000#ifndef __VOLUME_SLICE_SETTINGS_H__ #define __VOLUME_SLICE_SETTINGS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "ModelTypeEnum.h" #include "SceneableInterface.h" #include "VolumeSliceDrawingTypeEnum.h" #include "VolumeSliceProjectionTypeEnum.h" #include "VolumeSliceViewPlaneEnum.h" namespace caret { class PlainTextStringBuilder; class SceneClassAssistant; class VolumeMappableInterface; class VolumeSliceCoordinateSelection; class VolumeSliceSettings : public CaretObject, public SceneableInterface { public: VolumeSliceSettings(); virtual ~VolumeSliceSettings(); VolumeSliceSettings(const VolumeSliceSettings& obj); VolumeSliceSettings& operator=(const VolumeSliceSettings& obj); VolumeSliceViewPlaneEnum::Enum getSliceViewPlane() const; void setSliceViewPlane(VolumeSliceViewPlaneEnum::Enum sliceAxisMode); VolumeSliceDrawingTypeEnum::Enum getSliceDrawingType() const; void setSliceDrawingType(const VolumeSliceDrawingTypeEnum::Enum sliceDrawingType); VolumeSliceProjectionTypeEnum::Enum getSliceProjectionType() const; void setSliceProjectionType(const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType); int32_t getMontageNumberOfColumns() const; void setMontageNumberOfColumns(const int32_t montageNumberOfColumns); int32_t getMontageNumberOfRows() const; void setMontageNumberOfRows(const int32_t montageNumberOfRows); int32_t getMontageSliceSpacing() const; void setMontageSliceSpacing(const int32_t montageSliceSpacing); VolumeSliceCoordinateSelection* getSelectedVolumeSlices(VolumeMappableInterface* underlayVolumeFile); const VolumeSliceCoordinateSelection* getSelectedVolumeSlices(VolumeMappableInterface* underlayVolumeFile) const; void setSlicesToOrigin(); float getSliceCoordinateAxial() const; void setSliceCoordinateAxial(const float x); float getSliceCoordinateCoronal() const; void setSliceCoordinateCoronal(const float y); float getSliceCoordinateParasagittal() const; void setSliceCoordinateParasagittal(const float z); int64_t getSliceIndexAxial(const VolumeMappableInterface* volumeFile) const; void setSliceIndexAxial(const VolumeMappableInterface* volumeFile, const int64_t sliceIndexAxial); int64_t getSliceIndexCoronal(const VolumeMappableInterface* volumeFile) const; void setSliceIndexCoronal(const VolumeMappableInterface* volumeFile, const int64_t sliceIndexCoronal); int64_t getSliceIndexParasagittal(const VolumeMappableInterface* volumeFile) const; void setSliceIndexParasagittal(const VolumeMappableInterface* volumeFile, const int64_t sliceIndexParasagittal); bool isSliceParasagittalEnabled() const; void setSliceParasagittalEnabled(const bool sliceEnabledParasagittal); bool isSliceCoronalEnabled() const; void setSliceCoronalEnabled(const bool sliceEnabledCoronal); bool isSliceAxialEnabled() const; void setSliceAxialEnabled(const bool sliceEnabledAxial); void updateForVolumeFile(const VolumeMappableInterface* volumeFile); void selectSlicesAtOrigin(); void selectSlicesAtCoordinate(const float xyz[3]); void reset(); virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); // ADD_NEW_METHODS_HERE virtual AString toString() const; virtual void getDescriptionOfContent(const ModelTypeEnum::Enum modelType, PlainTextStringBuilder& descriptionOut) const; private: void copyHelperVolumeSliceSettings(const VolumeSliceSettings& obj); // ADD_NEW_MEMBERS_HERE /** Axis of slice being viewed */ VolumeSliceViewPlaneEnum::Enum m_sliceViewPlane; /** Type of slice drawing (single/montage) */ VolumeSliceDrawingTypeEnum::Enum m_sliceDrawingType; /** Type of slice projection (oblique/orthogonal) */ VolumeSliceProjectionTypeEnum::Enum m_sliceProjectionType; /** Number of montage rows */ int32_t m_montageNumberOfRows; /** Number of montage columns */ int32_t m_montageNumberOfColumns; /** Montage slice spacing */ int32_t m_montageSliceSpacing; mutable float m_sliceCoordinateParasagittal; mutable float m_sliceCoordinateCoronal; mutable float m_sliceCoordinateAxial; bool m_sliceEnabledParasagittal; bool m_sliceEnabledCoronal; bool m_sliceEnabledAxial; bool m_initializedFlag; //VolumeFile* m_lastVolumeFile; SceneClassAssistant* m_sceneAssistant; }; #ifdef __VOLUME_SLICE_SETTINGS_DECLARE__ // #endif // __VOLUME_SLICE_SETTINGS_DECLARE__ } // namespace #endif //__VOLUME_SLICE_SETTINGS_H__ workbench-1.1.1/src/Brain/VolumeSurfaceOutlineColorOrTabModel.cxx000066400000000000000000000373431255417355300251160ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __VOLUME_SURFACE_OUTLINE_COLOR_OR_TAB_MODEL_DECLARE__ #include "VolumeSurfaceOutlineColorOrTabModel.h" #undef __VOLUME_SURFACE_OUTLINE_COLOR_OR_TAB_MODEL_DECLARE__ #include "BrainConstants.h" #include "BrowserTabContent.h" #include "CaretAssert.h" #include "EventBrowserTabGet.h" #include "EventManager.h" #include "SceneClass.h" using namespace caret; /** * \class caret::VolumeSurfaceOutlineColorOrTabModel * \brief Model for selection of Color or Tab for Surface Outline * * Allows selection of a color or a browser tab for volume surface * outline. If a color is selected, the surface outline is drawn * in that color. If a browser tab is selected, the surface outline * is drawn in the current coloring for the selected surface using * the coloring assigned to the surface in the selected browser tab. * * Note: Only valid browser tabs are available for selection. */ /** * Constructor. */ VolumeSurfaceOutlineColorOrTabModel::VolumeSurfaceOutlineColorOrTabModel() : CaretObject() { m_selectedItem = NULL; for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { Item* item = new Item(i); m_allItems.push_back(item); } std::vector allColors; CaretColorEnum::getAllEnums(allColors); for (std::vector::iterator iter = allColors.begin(); iter != allColors.end(); iter++) { Item* item = new Item(*iter); m_allItems.push_back(item); } for (std::vector::iterator iter = m_allItems.begin(); iter != m_allItems.end(); iter++) { Item* item = *iter; if (item->isValid()) { m_selectedItem = const_cast(item); break; } } } /** * Destructor. */ VolumeSurfaceOutlineColorOrTabModel::~VolumeSurfaceOutlineColorOrTabModel() { for (std::vector::iterator iter = m_allItems.begin(); iter != m_allItems.end(); iter++) { Item* item = *iter; delete item; } m_allItems.clear(); } /** * Copy the given volume surface outline color or tab model. * @param modelToCopy * Model that is copied. */ void VolumeSurfaceOutlineColorOrTabModel::copyVolumeSurfaceOutlineColorOrTabModel(VolumeSurfaceOutlineColorOrTabModel* modelToCopy) { Item* otherItem = modelToCopy->getSelectedItem(); switch (otherItem->getItemType()) { case VolumeSurfaceOutlineColorOrTabModel::Item::ITEM_TYPE_COLOR: setColor(otherItem->getColor()); break; case VolumeSurfaceOutlineColorOrTabModel::Item::ITEM_TYPE_BROWSER_TAB: setBrowserTabIndex(otherItem->getBrowserTabIndex()); break; } } /** * @return All of the valid items for this model. */ std::vector VolumeSurfaceOutlineColorOrTabModel::getValidItems() { std::vector items; /* * Return all valid items */ for (std::vector::iterator iter = m_allItems.begin(); iter != m_allItems.end(); iter++) { Item* item = *iter; if (item->isValid()) { items.push_back(item); } } return items; } /** * @return Pointer to selected item (NULL if selection * is invalid. */ VolumeSurfaceOutlineColorOrTabModel::Item* VolumeSurfaceOutlineColorOrTabModel::getSelectedItem() { const int32_t numItems = static_cast(m_allItems.size()); /* * Make sure selected item is valid */ int32_t itemIndex = -1; if (m_selectedItem != NULL) { for (int32_t i = 0; i < numItems; i++) { if (m_allItems[i] == m_selectedItem) { if (m_allItems[i]->isValid() == false) { /* * Selected item is invalid */ m_selectedItem = NULL; } itemIndex = i; break; } } } if (m_selectedItem == NULL) { /* * Choose the previous valid item */ if (itemIndex >= 0) { for (int iBack = (itemIndex - 1); iBack >= 0; iBack--) { if (m_allItems[iBack]->isValid()) { m_selectedItem = m_allItems[iBack]; break; } } } if (m_selectedItem == NULL) { /* * Choose first valid item */ for (int i = 0; i < numItems; i++) { if (m_allItems[i]->isValid()) { m_selectedItem = m_allItems[i]; break; } } } } return m_selectedItem; } /** * Set the selected item. * @param item * New selected item. */ void VolumeSurfaceOutlineColorOrTabModel::setSelectedItem(const Item* item) { std::vector allItems = getValidItems(); const int32_t numItems = static_cast(allItems.size()); for (int32_t i = 0; i < numItems; i++) { if (item->equals(*allItems[i])) { m_selectedItem = allItems[i]; break; } } } /** * Set the selection to the given color. * @param color * Color that is to be selected. */ void VolumeSurfaceOutlineColorOrTabModel::setColor(const CaretColorEnum::Enum color) { std::vector allItems = getValidItems(); const int32_t numItems = static_cast(allItems.size()); for (int32_t i = 0; i < numItems; i++) { if (allItems[i]->getItemType() == Item::ITEM_TYPE_COLOR) { if (allItems[i]->getColor() == color) { setSelectedItem(allItems[i]); break; } } } } /** * Set the selection to the given browser tab. * @param browserTabIndex * Index of browser tab for selection. */ void VolumeSurfaceOutlineColorOrTabModel::setBrowserTabIndex(const int32_t browserTabIndex) { std::vector allItems = getValidItems(); const int32_t numItems = static_cast(allItems.size()); for (int32_t i = 0; i < numItems; i++) { if (allItems[i]->getItemType() == Item::ITEM_TYPE_BROWSER_TAB) { if (allItems[i]->getBrowserTabIndex() == browserTabIndex) { setSelectedItem(allItems[i]); break; } } } } /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ SceneClass* VolumeSurfaceOutlineColorOrTabModel::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "VolumeSurfaceOutlineColorOrTabModel", 1); if (m_selectedItem != NULL) { sceneClass->addChild(m_selectedItem->saveToScene(sceneAttributes, "m_selectedItem")); } return sceneClass; } /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * SceneClass containing the state that was previously * saved and should be restored. */ void VolumeSurfaceOutlineColorOrTabModel::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } Item item(0); item.restoreFromScene(sceneAttributes, sceneClass->getClass("m_selectedItem")); setSelectedItem(&item); } //====================================================================== /** * \class caret::VolumeSurfaceOutlineColorOrTabModel::Item * \brief An item in VolumeSurfaceOutlineColorOrTabModel. * * At this time, item is either a color or a browser * tab index. */ /** * Constructor for a Caret Color. * @param color * The caret color. */ VolumeSurfaceOutlineColorOrTabModel::Item::Item(const CaretColorEnum::Enum color) { m_color = color; m_browserTabIndex = 0; m_itemType = ITEM_TYPE_COLOR; } /** * Constructor for a browser tab. * @param browserTabIndex * Index of browser tab. */ VolumeSurfaceOutlineColorOrTabModel::Item::Item(const int32_t browserTabIndex) { m_color = CaretColorEnum::BLACK; m_browserTabIndex = browserTabIndex; m_itemType = ITEM_TYPE_BROWSER_TAB; } /** * Destructor. */ VolumeSurfaceOutlineColorOrTabModel::Item::~Item() { } /** * Is this item equal to another item? * * @param item * Item for comparison. * @return * True if items are equal, else false. */ bool VolumeSurfaceOutlineColorOrTabModel::Item::equals(const Item& item) const { if (m_itemType == item.m_itemType) { switch (m_itemType) { case ITEM_TYPE_BROWSER_TAB: if (m_browserTabIndex == item.m_browserTabIndex) { return true; } break; case ITEM_TYPE_COLOR: if (m_color == item.m_color) { return true; } break; } } return false; } /** * @return Is this item valid? */ bool VolumeSurfaceOutlineColorOrTabModel::Item::isValid() const { bool valid = false; switch (m_itemType) { case ITEM_TYPE_BROWSER_TAB: if (getBrowserTabContent() != NULL) { valid = true; } break; case ITEM_TYPE_COLOR: valid = true; break; } return valid; } /** * @return Name of this item. */ AString VolumeSurfaceOutlineColorOrTabModel::Item::getName() { AString name = "PROGRAM ERROR"; switch(m_itemType) { case ITEM_TYPE_BROWSER_TAB: { // BrowserTabContent* btc = getBrowserTabContent(); // if (btc != NULL) { // name = ("Tab " // + AString::number(btc->getTabNumber() + 1)); // } name = ("Tab " + AString::number(m_browserTabIndex + 1)); } break; case ITEM_TYPE_COLOR: name = CaretColorEnum::toGuiName(m_color); break; } return name; } /** * @return Type of item. */ VolumeSurfaceOutlineColorOrTabModel::Item::ItemType VolumeSurfaceOutlineColorOrTabModel::Item::getItemType() const { return m_itemType; } /** * @return Pointer to browser tab in this item or NULL * if this item does NOT contain a browser tab. */ BrowserTabContent* VolumeSurfaceOutlineColorOrTabModel::Item::getBrowserTabContent() { EventBrowserTabGet getTabEvent(m_browserTabIndex); EventManager::get()->sendEvent(getTabEvent.getPointer()); BrowserTabContent* tabContent = getTabEvent.getBrowserTab(); return tabContent; } /** * @return Pointer to browser tab in this item or NULL * if this item does NOT contain a browser tab. */ const BrowserTabContent* VolumeSurfaceOutlineColorOrTabModel::Item::getBrowserTabContent() const { EventBrowserTabGet getTabEvent(m_browserTabIndex); EventManager::get()->sendEvent(getTabEvent.getPointer()); BrowserTabContent* tabContent = getTabEvent.getBrowserTab(); return tabContent; } /** * @return Index of browser tab in this item. This will always * return an integer greater than or equal to zero. Use isItemValid() * to ensure this item is valid. */ int32_t VolumeSurfaceOutlineColorOrTabModel::Item::getBrowserTabIndex() const { return m_browserTabIndex; } /** * @return Enumerated type for color in this item. Returned * value is undefined if a color is NOT in this item. */ CaretColorEnum::Enum VolumeSurfaceOutlineColorOrTabModel::Item::getColor() { return m_color; } /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ SceneClass* VolumeSurfaceOutlineColorOrTabModel::Item::saveToScene(const SceneAttributes* /*sceneAttributes*/, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "VolumeSurfaceOutlineColorOrTabModel::Item", 1); sceneClass->addInteger("m_browserTabIndex", m_browserTabIndex); sceneClass->addEnumeratedType("m_color", m_color); switch (m_itemType) { case ITEM_TYPE_COLOR: sceneClass->addString("m_itemType", "ITEM_TYPE_COLOR"); break; case ITEM_TYPE_BROWSER_TAB: sceneClass->addString("m_itemType", "ITEM_TYPE_BROWSER_TAB"); break; } sceneClass->addInteger("m_browserTabIndex", m_browserTabIndex); return sceneClass; } /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * SceneClass containing the state that was previously * saved and should be restored. */ void VolumeSurfaceOutlineColorOrTabModel::Item::restoreFromScene(const SceneAttributes* /*sceneAttributes*/, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } m_browserTabIndex = sceneClass->getIntegerValue("m_browserTabIndex"); m_color = sceneClass->getEnumeratedTypeValue("m_color", CaretColorEnum::BLUE); const AString itemTypeName = sceneClass->getStringValue("m_itemType", "ITEM_TYPE_COLOR"); if (itemTypeName == "ITEM_TYPE_BROWSER_TAB") { m_itemType = ITEM_TYPE_BROWSER_TAB; } else if (itemTypeName == "ITEM_TYPE_COLOR") { m_itemType = ITEM_TYPE_COLOR; } else { CaretAssert(0); } } workbench-1.1.1/src/Brain/VolumeSurfaceOutlineColorOrTabModel.h000066400000000000000000000100231255417355300245250ustar00rootroot00000000000000#ifndef __VOLUME_SURFACE_OUTLINE_COLOR_OR_TAB_MODEL__H_ #define __VOLUME_SURFACE_OUTLINE_COLOR_OR_TAB_MODEL__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "BrainConstants.h" #include "CaretColorEnum.h" #include "CaretObject.h" #include "SceneableInterface.h" namespace caret { class BrowserTabContent; class SceneAttributes; class VolumeSurfaceOutlineColorOrTabModel : public CaretObject, public SceneableInterface { public: class Item : public SceneableInterface { public: /** * Type of item. */ enum ItemType { /** Item is a browser tab */ ITEM_TYPE_BROWSER_TAB, /** Item is a color */ ITEM_TYPE_COLOR }; Item(const CaretColorEnum::Enum color); Item(const int32_t browserTabIndex); ~Item(); bool isValid() const; bool equals(const Item& item) const; AString getName(); ItemType getItemType() const; BrowserTabContent* getBrowserTabContent(); const BrowserTabContent* getBrowserTabContent() const; int32_t getBrowserTabIndex() const; CaretColorEnum::Enum getColor(); virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private: int32_t m_browserTabIndex; CaretColorEnum::Enum m_color; ItemType m_itemType; }; public: VolumeSurfaceOutlineColorOrTabModel(); virtual ~VolumeSurfaceOutlineColorOrTabModel(); std::vector getValidItems(); void copyVolumeSurfaceOutlineColorOrTabModel(VolumeSurfaceOutlineColorOrTabModel* modelToCopy); Item* getSelectedItem(); void setSelectedItem(const Item* item); void setColor(const CaretColorEnum::Enum color); void setBrowserTabIndex(const int32_t browserTabIndex); virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private: VolumeSurfaceOutlineColorOrTabModel(const VolumeSurfaceOutlineColorOrTabModel&); VolumeSurfaceOutlineColorOrTabModel& operator=(const VolumeSurfaceOutlineColorOrTabModel&); std::vector m_allItems; Item* m_selectedItem; }; #ifdef __VOLUME_SURFACE_OUTLINE_COLOR_OR_TAB_MODEL_DECLARE__ // #endif // __VOLUME_SURFACE_OUTLINE_COLOR_OR_TAB_MODEL_DECLARE__ } // namespace #endif //__VOLUME_SURFACE_OUTLINE_COLOR_OR_TAB_MODEL__H_ workbench-1.1.1/src/Brain/VolumeSurfaceOutlineModel.cxx000066400000000000000000000145361255417355300231660ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __VOLUME_SURFACE_OUTLINE_MODEL_DECLARE__ #include "VolumeSurfaceOutlineModel.h" #undef __VOLUME_SURFACE_OUTLINE_MODEL_DECLARE__ #include "SceneClass.h" #include "SceneClassAssistant.h" #include "SurfaceSelectionModel.h" #include "SurfaceTypeEnum.h" #include "VolumeSurfaceOutlineColorOrTabModel.h" using namespace caret; /** * \class VolumeSurfaceOutlineSelection * \brief Controls display of a volume surface outline. * * Controls display of a volume surface outline. */ /** * Constructor. */ VolumeSurfaceOutlineModel::VolumeSurfaceOutlineModel() : CaretObject() { std::vector validSurfaceTypes; validSurfaceTypes.push_back(SurfaceTypeEnum::ANATOMICAL); validSurfaceTypes.push_back(SurfaceTypeEnum::RECONSTRUCTION); validSurfaceTypes.push_back(SurfaceTypeEnum::INFLATED); validSurfaceTypes.push_back(SurfaceTypeEnum::VERY_INFLATED); m_displayed = false; m_thickness = VolumeSurfaceOutlineModel::DEFAULT_LINE_THICKNESS; m_surfaceSelectionModel = new SurfaceSelectionModel(validSurfaceTypes); m_colorOrTabModel = new VolumeSurfaceOutlineColorOrTabModel(); m_sceneAssistant = new SceneClassAssistant(); m_sceneAssistant->add("m_displayed", &m_displayed); m_sceneAssistant->add("m_thickness", &m_thickness); m_sceneAssistant->add("m_surfaceSelectionModel", "SurfaceSelectionModel", m_surfaceSelectionModel); m_sceneAssistant->add("m_colorOrTabModel", "VolumeSurfaceOutlineColorOrTabModel", m_colorOrTabModel); } /** * Destructor. */ VolumeSurfaceOutlineModel::~VolumeSurfaceOutlineModel() { delete m_surfaceSelectionModel; delete m_colorOrTabModel; delete m_sceneAssistant; } /** * Copy the given volume surface outline model to this model. * @param modelToCopy * Model that is copied. */ void VolumeSurfaceOutlineModel::copyVolumeSurfaceOutlineModel(VolumeSurfaceOutlineModel* modelToCopy) { m_displayed = modelToCopy->m_displayed; m_thickness = modelToCopy->m_thickness; m_surfaceSelectionModel->setSurface(modelToCopy->getSurface()); VolumeSurfaceOutlineColorOrTabModel* colorTabToCopy = modelToCopy->getColorOrTabModel(); m_colorOrTabModel->copyVolumeSurfaceOutlineColorOrTabModel(colorTabToCopy); } /** * Get a description of this object's content. * @return String describing this object's content. */ AString VolumeSurfaceOutlineModel::toString() const { return "VolumeSurfaceOutlineSelection"; } /** * @return Is this surface outline displayed? */ bool VolumeSurfaceOutlineModel::isDisplayed() const { return m_displayed; } /** * Set the display status of the surface outline. * @param displayed * New display status. */ void VolumeSurfaceOutlineModel::setDisplayed(const bool displayed) { m_displayed = displayed; } /** * @return Thickness for drawing surface. */ float VolumeSurfaceOutlineModel::getThickness() const { return m_thickness; } /** * Set the thickness for drawing the surface. * @param thickness * New value for thickness. */ void VolumeSurfaceOutlineModel::setThickness(const float thickness) { m_thickness = thickness; } /** * @return The surface selector used to select the surface. */ SurfaceSelectionModel* VolumeSurfaceOutlineModel::getSurfaceSelectionModel() { return m_surfaceSelectionModel; } /** * @return Get the selected surface. */ const Surface* VolumeSurfaceOutlineModel::getSurface() const { return m_surfaceSelectionModel->getSurface(); } /** * @return Get the selected surface. */ Surface* VolumeSurfaceOutlineModel::getSurface() { return m_surfaceSelectionModel->getSurface(); } /** * @return The model for color or tab selection. */ VolumeSurfaceOutlineColorOrTabModel* VolumeSurfaceOutlineModel::getColorOrTabModel() { return m_colorOrTabModel; } /** * @return The model for color or tab selection. */ const VolumeSurfaceOutlineColorOrTabModel* VolumeSurfaceOutlineModel::getColorOrTabModel() const { return m_colorOrTabModel; } /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ SceneClass* VolumeSurfaceOutlineModel::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "VolumeSurfaceOutlineModel", 1); m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); return sceneClass; } /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * SceneClass containing the state that was previously * saved and should be restored. */ void VolumeSurfaceOutlineModel::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); } workbench-1.1.1/src/Brain/VolumeSurfaceOutlineModel.h000066400000000000000000000060671255417355300226130ustar00rootroot00000000000000#ifndef __VOLUME_SURFACE_OUTLINE_MODEL__H_ #define __VOLUME_SURFACE_OUTLINE_MODEL__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "SceneableInterface.h" namespace caret { class Surface; class SceneAttributes; class SceneClassAssistant; class SurfaceSelectionModel; class VolumeSurfaceOutlineColorOrTabModel; class VolumeSurfaceOutlineModel : public CaretObject, public SceneableInterface { public: VolumeSurfaceOutlineModel(); virtual ~VolumeSurfaceOutlineModel(); void copyVolumeSurfaceOutlineModel(VolumeSurfaceOutlineModel* modelToCopy); bool isDisplayed() const; void setDisplayed(const bool displayed); float getThickness() const; void setThickness(const float thickness); SurfaceSelectionModel* getSurfaceSelectionModel(); const Surface* getSurface() const; Surface* getSurface(); VolumeSurfaceOutlineColorOrTabModel* getColorOrTabModel(); const VolumeSurfaceOutlineColorOrTabModel* getColorOrTabModel() const; virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); static const int32_t DEFAULT_LINE_THICKNESS; private: VolumeSurfaceOutlineModel(const VolumeSurfaceOutlineModel&); VolumeSurfaceOutlineModel& operator=(const VolumeSurfaceOutlineModel&); public: virtual AString toString() const; private: bool m_displayed; float m_thickness; SurfaceSelectionModel* m_surfaceSelectionModel; VolumeSurfaceOutlineColorOrTabModel* m_colorOrTabModel; SceneClassAssistant* m_sceneAssistant; }; #ifdef __VOLUME_SURFACE_OUTLINE_MODEL_DECLARE__ const int32_t VolumeSurfaceOutlineModel::DEFAULT_LINE_THICKNESS = 2.0; #endif // __VOLUME_SURFACE_OUTLINE_MODEL_DECLARE__ } // namespace #endif //__VOLUME_SURFACE_OUTLINE_MODEL__H_ workbench-1.1.1/src/Brain/VolumeSurfaceOutlineSetModel.cxx000066400000000000000000000340511255417355300236340ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __VOLUME_SURFACE_OUTLINE_SET_MODEL_DECLARE__ #include "VolumeSurfaceOutlineSetModel.h" #undef __VOLUME_SURFACE_OUTLINE_SET_MODEL_DECLARE__ #include "Brain.h" #include "BrainStructure.h" #include "BrowserTabContent.h" #include "CaretAssert.h" #include "EventBrowserTabGetAll.h" #include "EventManager.h" #include "ModelSurface.h" #include "SceneClassAssistant.h" #include "SceneClass.h" #include "SceneClassArray.h" #include "Surface.h" #include "SurfaceSelectionModel.h" #include "VolumeSurfaceOutlineColorOrTabModel.h" #include "VolumeSurfaceOutlineModel.h" #include "SceneableInterface.h" using namespace caret; /** * \class caret::VolumeSurfaceOutlineSetModel * \brief Holds a set of VolumeSurfaceOutlineModels * * Holds a set of VolumeSurfaceOutlineModels. Users * may add additional surface outline models up to * a fixed limit. There is also a minimum number * that are displayed. */ /** * Constructor. */ VolumeSurfaceOutlineSetModel::VolumeSurfaceOutlineSetModel() : CaretObject() { for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_VOLUME_SURFACE_OUTLINES; i++) { m_outlineModels[i] = new VolumeSurfaceOutlineModel(); } m_numberOfDisplayedVolumeSurfaceOutlines = 6; //BrainConstants::MINIMUM_NUMBER_OF_VOLUME_SURFACE_OUTLINES; m_sceneAssistant = new SceneClassAssistant(); m_sceneAssistant->add("m_numberOfDisplayedVolumeSurfaceOutlines", &m_numberOfDisplayedVolumeSurfaceOutlines); } /** * Destructor. */ VolumeSurfaceOutlineSetModel::~VolumeSurfaceOutlineSetModel() { for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_VOLUME_SURFACE_OUTLINES; i++) { delete m_outlineModels[i]; } delete m_sceneAssistant; } /** * @return The number of volume surface outlines. */ int32_t VolumeSurfaceOutlineSetModel::getNumberOfDislayedVolumeSurfaceOutlines() const { return m_numberOfDisplayedVolumeSurfaceOutlines; } /** * Copy the given volume surface outline set model. * @param setModel * Model that is copied. */ void VolumeSurfaceOutlineSetModel::copyVolumeSurfaceOutlineSetModel(VolumeSurfaceOutlineSetModel* setModel) { for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_VOLUME_SURFACE_OUTLINES; i++) { m_outlineModels[i]->copyVolumeSurfaceOutlineModel(setModel->getVolumeSurfaceOutlineModel(i)); } m_numberOfDisplayedVolumeSurfaceOutlines = setModel->getNumberOfDislayedVolumeSurfaceOutlines(); } /** * Set the number of volume surface outlines. * @param numberDisplayed * Number of displayed volume surface outlines. */ void VolumeSurfaceOutlineSetModel::setNumberOfDisplayedVolumeSurfaceOutlines(const int32_t numberDisplayed) { m_numberOfDisplayedVolumeSurfaceOutlines = numberDisplayed; if (m_numberOfDisplayedVolumeSurfaceOutlines < BrainConstants::MINIMUM_NUMBER_OF_VOLUME_SURFACE_OUTLINES) { m_numberOfDisplayedVolumeSurfaceOutlines = BrainConstants::MINIMUM_NUMBER_OF_VOLUME_SURFACE_OUTLINES; } if (m_numberOfDisplayedVolumeSurfaceOutlines > BrainConstants::MAXIMUM_NUMBER_OF_VOLUME_SURFACE_OUTLINES) { m_numberOfDisplayedVolumeSurfaceOutlines = BrainConstants::MAXIMUM_NUMBER_OF_VOLUME_SURFACE_OUTLINES; } } /** * @return The volume surface outline model at the given index. * @param indx * Index of volume surface outline model. */ VolumeSurfaceOutlineModel* VolumeSurfaceOutlineSetModel::getVolumeSurfaceOutlineModel(const int32_t indx) { CaretAssertArrayIndex(m_outlineModels, BrainConstants::MAXIMUM_NUMBER_OF_VOLUME_SURFACE_OUTLINES, indx); return m_outlineModels[indx]; } /** * @return The volume surface outline model at the given index. * @param indx * Index of volume surface outline model. */ const VolumeSurfaceOutlineModel* VolumeSurfaceOutlineSetModel::getVolumeSurfaceOutlineModel(const int32_t indx) const { CaretAssertArrayIndex(m_outlineModels, BrainConstants::MAXIMUM_NUMBER_OF_VOLUME_SURFACE_OUTLINES, indx); return m_outlineModels[indx]; } /** * Set the default selected surfaces after a spec file is loaded. * @searchForTabs * If true, examine the loaded tabs to find left and right surfaces. */ void VolumeSurfaceOutlineSetModel::selectSurfacesAfterSpecFileLoaded(Brain* brain, const bool searchForTabs) { EventBrowserTabGetAll getAllTabs; EventManager::get()->sendEvent(getAllTabs.getPointer()); /* * Find tabs with left/right */ int32_t leftTabIndex = -1; int32_t rightTabIndex = -1; const int numTabs = getAllTabs.getNumberOfBrowserTabs(); if (searchForTabs) { for (int32_t i = 0; i < numTabs; i++) { BrowserTabContent* tabContent = getAllTabs.getBrowserTab(i); ModelSurface* surfaceModel = tabContent->getDisplayedSurfaceModel(); if (surfaceModel != NULL) { const StructureEnum::Enum structure = surfaceModel->getSurface()->getStructure(); switch (structure) { case StructureEnum::CORTEX_LEFT: leftTabIndex = tabContent->getTabNumber(); break; case StructureEnum::CORTEX_RIGHT: rightTabIndex = tabContent->getTabNumber(); break; default: break; } } } } else { if (numTabs >= 1) { leftTabIndex = 0; } if (numTabs >= 2) { rightTabIndex = 1; } } Surface* leftMidThickSurface = NULL; Surface* leftWhiteSurface = NULL; Surface* leftPialSurface = NULL; BrainStructure* leftBrainStructure = brain->getBrainStructure(StructureEnum::CORTEX_LEFT, false); if (leftBrainStructure != NULL) { leftMidThickSurface = leftBrainStructure->getSurfaceContainingTextInName("midthick"); if (leftMidThickSurface == NULL) { leftMidThickSurface = leftBrainStructure->getPrimaryAnatomicalSurface(); } leftWhiteSurface = leftBrainStructure->getSurfaceContainingTextInName("white"); leftPialSurface = leftBrainStructure->getSurfaceContainingTextInName("pial"); } Surface* rightMidThickSurface = NULL; Surface* rightWhiteSurface = NULL; Surface* rightPialSurface = NULL; BrainStructure* rightBrainStructure = brain->getBrainStructure(StructureEnum::CORTEX_RIGHT, false); if (rightBrainStructure != NULL) { rightMidThickSurface = rightBrainStructure->getSurfaceContainingTextInName("midthick"); if (rightMidThickSurface == NULL) { rightMidThickSurface = rightBrainStructure->getPrimaryAnatomicalSurface(); } rightWhiteSurface = rightBrainStructure->getSurfaceContainingTextInName("white"); rightPialSurface = rightBrainStructure->getSurfaceContainingTextInName("pial"); } for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_VOLUME_SURFACE_OUTLINES; i++) { m_outlineModels[i]->getColorOrTabModel()->setColor(CaretColorEnum::BLACK); m_outlineModels[i]->setThickness(VolumeSurfaceOutlineModel::DEFAULT_LINE_THICKNESS); } int nextOutlineIndex = 0; addSurfaceOutline(leftMidThickSurface, VolumeSurfaceOutlineModel::DEFAULT_LINE_THICKNESS, leftTabIndex, CaretColorEnum::BLACK, nextOutlineIndex); addSurfaceOutline(rightMidThickSurface, VolumeSurfaceOutlineModel::DEFAULT_LINE_THICKNESS, rightTabIndex, CaretColorEnum::BLACK, nextOutlineIndex); addSurfaceOutline(leftWhiteSurface, VolumeSurfaceOutlineModel::DEFAULT_LINE_THICKNESS, -1, CaretColorEnum::LIME, nextOutlineIndex); addSurfaceOutline(rightWhiteSurface, VolumeSurfaceOutlineModel::DEFAULT_LINE_THICKNESS, -1, CaretColorEnum::LIME, nextOutlineIndex); addSurfaceOutline(leftPialSurface, VolumeSurfaceOutlineModel::DEFAULT_LINE_THICKNESS, -1, CaretColorEnum::BLUE, nextOutlineIndex); addSurfaceOutline(rightPialSurface, VolumeSurfaceOutlineModel::DEFAULT_LINE_THICKNESS, -1, CaretColorEnum::BLUE, nextOutlineIndex); } /** * Add a surface outline at the given outlineIndex. The * outlineIndex is incremented. * * @param surface * Surface that is added. If NULL, no action is taken. * @param thickness * Thickness for surface outline. * @param browserTabIndex * If greater than or equal to zero, the color source * is set to this tab index. * @param color * If browserTabIndex is less than zero, the color source * is set to this color. * @param outlineIndex * If an outline was added, it is placed at this value * and it is incremented. If this index is greater * than or equal to the number of available surface * outlines, no action is taken. */ void VolumeSurfaceOutlineSetModel::addSurfaceOutline(Surface* surface, const float thickness, const int32_t browserTabIndex, const CaretColorEnum::Enum color, int32_t& outlineIndex) { if (surface != NULL) { if (surface->getSurfaceType() == SurfaceTypeEnum::ANATOMICAL) { if (outlineIndex < BrainConstants::MAXIMUM_NUMBER_OF_VOLUME_SURFACE_OUTLINES) { VolumeSurfaceOutlineModel* vsos = m_outlineModels[outlineIndex]; vsos->getSurfaceSelectionModel()->setSurface(surface); vsos->setThickness(thickness); if (browserTabIndex >= 0) { vsos->getColorOrTabModel()->setBrowserTabIndex(browserTabIndex); } else { vsos->getColorOrTabModel()->setColor(color); } outlineIndex++; } } } } /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ SceneClass* VolumeSurfaceOutlineSetModel::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "VolumeSurfaceOutlineSetModel", 1); m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); std::vector outlineModelSceneClassVector; for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_VOLUME_SURFACE_OUTLINES; i++ ) { outlineModelSceneClassVector.push_back(m_outlineModels[i]->saveToScene(sceneAttributes, ("m_outlineModels[" + AString::number(i) + "]"))); } sceneClass->addChild(new SceneClassArray("m_outlineModels", outlineModelSceneClassVector)); return sceneClass; } /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * SceneClass containing the state that was previously * saved and should be restored. */ void VolumeSurfaceOutlineSetModel::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); const SceneClassArray* outlineModelsArrayClass = sceneClass->getClassArray("m_outlineModels"); if (outlineModelsArrayClass != NULL) { const int32_t maxNum = std::min(outlineModelsArrayClass->getNumberOfArrayElements(), (int32_t)BrainConstants::MAXIMUM_NUMBER_OF_VOLUME_SURFACE_OUTLINES); for (int32_t i = 0; i < maxNum; i++) { m_outlineModels[i]->restoreFromScene(sceneAttributes, outlineModelsArrayClass->getClassAtIndex(i)); } } } workbench-1.1.1/src/Brain/VolumeSurfaceOutlineSetModel.h000066400000000000000000000063231255417355300232620ustar00rootroot00000000000000#ifndef __VOLUME_SURFACE_OUTLINE_SET_MODEL__H_ #define __VOLUME_SURFACE_OUTLINE_SET_MODEL__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "BrainConstants.h" #include "CaretColorEnum.h" #include "CaretObject.h" #include "SceneableInterface.h" namespace caret { class Brain; class Surface; class SceneAttributes; class SceneClassAssistant; class VolumeSurfaceOutlineModel; class VolumeSurfaceOutlineSetModel : public CaretObject, public SceneableInterface { public: VolumeSurfaceOutlineSetModel(); virtual ~VolumeSurfaceOutlineSetModel(); void copyVolumeSurfaceOutlineSetModel(VolumeSurfaceOutlineSetModel* setModel); int32_t getNumberOfDislayedVolumeSurfaceOutlines() const; void setNumberOfDisplayedVolumeSurfaceOutlines(const int32_t numberDisplayed); VolumeSurfaceOutlineModel* getVolumeSurfaceOutlineModel(const int32_t indx); const VolumeSurfaceOutlineModel* getVolumeSurfaceOutlineModel(const int32_t indx) const; void selectSurfacesAfterSpecFileLoaded(Brain* brain, const bool searchForTabs); virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private: VolumeSurfaceOutlineSetModel(const VolumeSurfaceOutlineSetModel&); VolumeSurfaceOutlineSetModel& operator=(const VolumeSurfaceOutlineSetModel&); void addSurfaceOutline(Surface* surface, const float thickness, const int32_t browserTabIndex, const CaretColorEnum::Enum color, int32_t& outlineIndex); VolumeSurfaceOutlineModel* m_outlineModels[BrainConstants::MAXIMUM_NUMBER_OF_VOLUME_SURFACE_OUTLINES]; int32_t m_numberOfDisplayedVolumeSurfaceOutlines; SceneClassAssistant* m_sceneAssistant; }; #ifdef __VOLUME_SURFACE_OUTLINE_SET_MODEL_DECLARE__ // #endif // __VOLUME_SURFACE_OUTLINE_SET_MODEL_DECLARE__ } // namespace #endif //__VOLUME_SURFACE_OUTLINE_SET_MODEL__H_ workbench-1.1.1/src/Brain/WholeBrainSurfaceSettings.cxx000066400000000000000000000151321255417355300231420ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __WHOLE_BRAIN_SURFACE_SETTINGS_DECLARE__ #include "WholeBrainSurfaceSettings.h" #undef __WHOLE_BRAIN_SURFACE_SETTINGS_DECLARE__ #include "SceneClass.h" #include "SceneClassAssistant.h" using namespace caret; /** * \class caret::WholeBrainSurfaceSettings * \brief Surface settings for whole brain. * \ingroup Brain */ /** * Constructor. */ WholeBrainSurfaceSettings::WholeBrainSurfaceSettings() : CaretObject() { m_cerebellumEnabled = true; m_leftEnabled = true; m_rightEnabled = true; m_leftRightSeparation = 0.0; m_cerebellumSeparation = 0.0; m_sceneAssistant = new SceneClassAssistant(); m_sceneAssistant->add("m_leftEnabled", &m_leftEnabled); m_sceneAssistant->add("m_rightEnabled", &m_rightEnabled); m_sceneAssistant->add("m_cerebellumEnabled", &m_cerebellumEnabled); m_sceneAssistant->add("m_leftRightSeparation", &m_leftRightSeparation); m_sceneAssistant->add("m_cerebellumSeparation", &m_cerebellumSeparation); } /** * Destructor. */ WholeBrainSurfaceSettings::~WholeBrainSurfaceSettings() { delete m_sceneAssistant; } /** * Copy constructor. * @param obj * Object that is copied. */ WholeBrainSurfaceSettings::WholeBrainSurfaceSettings(const WholeBrainSurfaceSettings& obj) : CaretObject(obj), SceneableInterface(obj) { this->copyHelperWholeBrainSurfaceSettings(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ WholeBrainSurfaceSettings& WholeBrainSurfaceSettings::operator=(const WholeBrainSurfaceSettings& obj) { if (this != &obj) { CaretObject::operator=(obj); this->copyHelperWholeBrainSurfaceSettings(obj); } return *this; } /** * Helps with copying an object of this type. * @param obj * Object that is copied. */ void WholeBrainSurfaceSettings::copyHelperWholeBrainSurfaceSettings(const WholeBrainSurfaceSettings& obj) { m_cerebellumEnabled = obj.m_cerebellumEnabled; m_leftEnabled = obj.m_leftEnabled; m_rightEnabled = obj.m_rightEnabled; m_leftRightSeparation = obj.m_leftRightSeparation; m_cerebellumSeparation = obj.m_cerebellumSeparation; } /** * Get a description of this object's content. * @return String describing this object's content. */ AString WholeBrainSurfaceSettings::toString() const { return "WholeBrainSurfaceSettings"; } /** * @return Enabled status for left cerebral cortex. */ bool WholeBrainSurfaceSettings::isLeftEnabled() const { return m_leftEnabled; } /** * Set the enabled status for the left hemisphere. * @param windowTabNumber * Index of window tab. * @param enabled * New enabled status. */ void WholeBrainSurfaceSettings::setLeftEnabled(const bool enabled) { m_leftEnabled = enabled; } /** * @return Enabled status for right cerebral cortex. */ bool WholeBrainSurfaceSettings::isRightEnabled() const { return m_rightEnabled; } /** * Set the enabled status for the right hemisphere. * @param enabled * New enabled status. */ void WholeBrainSurfaceSettings::setRightEnabled(const bool enabled) { m_rightEnabled = enabled; } /** * @return Enabled status for cerebellum. */ bool WholeBrainSurfaceSettings::isCerebellumEnabled() const { return m_cerebellumEnabled; } /** * Set the enabled status for the cerebellum. * @param enabled * New enabled status. */ void WholeBrainSurfaceSettings::setCerebellumEnabled(const bool enabled) { m_cerebellumEnabled = enabled; } /** * @return The separation between the left and right surfaces. */ float WholeBrainSurfaceSettings::getLeftRightSeparation() const { return m_leftRightSeparation; } /** * Set the separation between the cerebellum and the left/right surfaces. * @param separation * New value for separation. */ void WholeBrainSurfaceSettings::setLeftRightSeparation(const float separation) { m_leftRightSeparation = separation; } /** * @return The separation between the left/right surfaces. */ float WholeBrainSurfaceSettings::getCerebellumSeparation() const { return m_cerebellumSeparation; } /** * Set the separation between the cerebellum and the eft and right surfaces. * @param separation * New value for separation. */ void WholeBrainSurfaceSettings::setCerebellumSeparation(const float separation) { m_cerebellumSeparation = separation; } /** * Save information specific to this type of model to the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @param instanceName * Name of instance in the scene. */ SceneClass* WholeBrainSurfaceSettings::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "WholeBrainSurfaceSettings", 1); m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); return sceneClass; } /** * Restore information specific to the type of model from the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass from which model specific information is obtained. */ void WholeBrainSurfaceSettings::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); } workbench-1.1.1/src/Brain/WholeBrainSurfaceSettings.h000066400000000000000000000056521255417355300225750ustar00rootroot00000000000000#ifndef __WHOLE_BRAIN_SURFACE_SETTINGS_H__ #define __WHOLE_BRAIN_SURFACE_SETTINGS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "SceneableInterface.h" namespace caret { class SceneClassAssistant; class WholeBrainSurfaceSettings : public CaretObject, public SceneableInterface { public: WholeBrainSurfaceSettings(); virtual ~WholeBrainSurfaceSettings(); WholeBrainSurfaceSettings(const WholeBrainSurfaceSettings& obj); WholeBrainSurfaceSettings& operator=(const WholeBrainSurfaceSettings& obj); bool isLeftEnabled() const; void setLeftEnabled(const bool enabled); bool isRightEnabled() const; void setRightEnabled(const bool enabled); bool isCerebellumEnabled() const; void setCerebellumEnabled(const bool enabled); float getLeftRightSeparation() const; void setLeftRightSeparation(const float separation); float getCerebellumSeparation() const; void setCerebellumSeparation(const float separation); // ADD_NEW_METHODS_HERE virtual AString toString() const; virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private: void copyHelperWholeBrainSurfaceSettings(const WholeBrainSurfaceSettings& obj); SceneClassAssistant* m_sceneAssistant; bool m_leftEnabled; bool m_rightEnabled; bool m_cerebellumEnabled; float m_leftRightSeparation; float m_cerebellumSeparation; // ADD_NEW_MEMBERS_HERE }; #ifdef __WHOLE_BRAIN_SURFACE_SETTINGS_DECLARE__ // #endif // __WHOLE_BRAIN_SURFACE_SETTINGS_DECLARE__ } // namespace #endif //__WHOLE_BRAIN_SURFACE_SETTINGS_H__ workbench-1.1.1/src/Brain/WholeBrainVoxelDrawingMode.cxx000066400000000000000000000232441255417355300232520ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __WHOLE_BRAIN_VOXEL_DRAWING_MODE_ENUM_DECLARE__ #include "WholeBrainVoxelDrawingMode.h" #undef __WHOLE_BRAIN_VOXEL_DRAWING_MODE_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::WholeBrainVoxelDrawingMode * \brief Enumerated type for drawing of voxels in whole brain view. */ /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ WholeBrainVoxelDrawingMode::WholeBrainVoxelDrawingMode(const Enum enumValue, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCodeCounter++; this->name = name; this->guiName = guiName; } /** * Destructor. */ WholeBrainVoxelDrawingMode::~WholeBrainVoxelDrawingMode() { } /** * Initialize the enumerated metadata. */ void WholeBrainVoxelDrawingMode::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(WholeBrainVoxelDrawingMode(DRAW_VOXELS_AS_THREE_D_CUBES, "DRAW_VOXELS_AS_THREE_D_CUBES", "Draw Voxels as Cubes (3D)")); enumData.push_back(WholeBrainVoxelDrawingMode(DRAW_VOXELS_AS_ROUNDED_THREE_D_CUBES, "DRAW_VOXELS_AS_ROUNDED_THREE_D_CUBES", "Draw Voxels as Rounded Cubes (3D)")); enumData.push_back(WholeBrainVoxelDrawingMode(DRAW_VOXELS_ON_TWO_D_SLICES, "DRAW_VOXELS_ON_TWO_D_SLICES", "Draw Voxels on Slices (2D)")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const WholeBrainVoxelDrawingMode* WholeBrainVoxelDrawingMode::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const WholeBrainVoxelDrawingMode* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString WholeBrainVoxelDrawingMode::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const WholeBrainVoxelDrawingMode* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ WholeBrainVoxelDrawingMode::Enum WholeBrainVoxelDrawingMode::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = DRAW_VOXELS_ON_TWO_D_SLICES; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const WholeBrainVoxelDrawingMode& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type WholeBrainVoxelDrawingMode")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString WholeBrainVoxelDrawingMode::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const WholeBrainVoxelDrawingMode* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ WholeBrainVoxelDrawingMode::Enum WholeBrainVoxelDrawingMode::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = DRAW_VOXELS_ON_TWO_D_SLICES; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const WholeBrainVoxelDrawingMode& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type WholeBrainVoxelDrawingMode")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t WholeBrainVoxelDrawingMode::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const WholeBrainVoxelDrawingMode* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ WholeBrainVoxelDrawingMode::Enum WholeBrainVoxelDrawingMode::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = DRAW_VOXELS_ON_TWO_D_SLICES; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const WholeBrainVoxelDrawingMode& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type WholeBrainVoxelDrawingMode")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void WholeBrainVoxelDrawingMode::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void WholeBrainVoxelDrawingMode::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(WholeBrainVoxelDrawingMode::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void WholeBrainVoxelDrawingMode::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(WholeBrainVoxelDrawingMode::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } workbench-1.1.1/src/Brain/WholeBrainVoxelDrawingMode.h000066400000000000000000000066161255417355300227030ustar00rootroot00000000000000#ifndef __WHOLE_BRAIN_VOXEL_DRAWING_MODE_ENUM_H__ #define __WHOLE_BRAIN_VOXEL_DRAWING_MODE_ENUM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class WholeBrainVoxelDrawingMode { public: /** * Enumerated values. */ enum Enum { /** In Whole Brain, view volume data as 3D cubes */ DRAW_VOXELS_AS_THREE_D_CUBES, /** In Whole Brain, view volume data as Rounded 3D cubes */ DRAW_VOXELS_AS_ROUNDED_THREE_D_CUBES, /** In Whole Brain, view volume data on slices */ DRAW_VOXELS_ON_TWO_D_SLICES }; ~WholeBrainVoxelDrawingMode(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); private: WholeBrainVoxelDrawingMode(const Enum enumValue, const AString& name, const AString& guiName); static const WholeBrainVoxelDrawingMode* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; #ifdef __WHOLE_BRAIN_VOXEL_DRAWING_MODE_ENUM_DECLARE__ std::vector WholeBrainVoxelDrawingMode::enumData; bool WholeBrainVoxelDrawingMode::initializedFlag = false; int32_t WholeBrainVoxelDrawingMode::integerCodeCounter = 0; #endif // __WHOLE_BRAIN_VOXEL_DRAWING_MODE_ENUM_DECLARE__ } // namespace #endif //__WHOLE_BRAIN_VOXEL_DRAWING_MODE_ENUM_H__ workbench-1.1.1/src/CMakeLists.txt000077500000000000000000000402601255417355300170420ustar00rootroot00000000000000# # Minimum required version of CMAKE # CMAKE_MINIMUM_REQUIRED (VERSION 2.8) # # # #message(INFORMATION "\nTo get the correct version of QT, qmake must be in the PATH\n") #TSC: use "CACHE " syntax in SET commands so they can be overridden by cmake options # # Setting the compiler MUST be done before the PROJECT # statement or else an infinite loop will occur indicating # that the compiler has been redefined. # IF(APPLE) ADD_DEFINITIONS(-DCARET_OS_MACOSX) ELSEIF(UNIX) IF(${CMAKE_SYSTEM_NAME} MATCHES "Linux") ADD_DEFINITIONS(-DCARET_OS_LINUX) ENDIF(${CMAKE_SYSTEM_NAME} MATCHES "Linux") ELSEIF(WIN32) ADD_DEFINITIONS(-DCARET_OS_WINDOWS) IF(MSVC) ADD_DEFINITIONS(-DCARET_OS_WINDOWS_MSVC) IF(CMAKE_CL_64) ## SET(CMAKE_GENERATOR_TOOLSET "v120_CTP_Nov2012" CACHE STRING "Platform Toolset" FORCE) ADD_DEFINITIONS(-D_USE_MATH_DEFINES -DNOMINMAX) SET( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -MP -wd4290 -wd4244 -wd4267 -wd4305 -wd4100 -wd4005" ) ##SET( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -we4061") SET( ZLIB_INCLUDE_DIR "C:\\dev64\\install\\zlib\\include" CACHE STRING "zlib include directory (headers)") # SET( ZLIB_LIBRARY "C:\\dev64\\install\\zlib\\lib\\zlib.lib" CACHE STRING "zlib library (binary)") SET(ZLIB_LIBRARY optimized "C:\\dev64\\install\\zlib\\lib\\zlib.lib" debug "C:\\dev64\\install\\zlib\\lib\\zlibd.lib" CACHE STRING "zlib library (binary)") SET( OPENSSL_ROOT_DIR "c:\\dev64\\install\\openssl" CACHE_STRING "open ssl root directory") ELSE() ## SET(CMAKE_GENERATOR_TOOLSET "v120_CTP_Nov2012" CACHE STRING "Platform Toolset" FORCE) ADD_DEFINITIONS(-D_USE_MATH_DEFINES -DNOMINMAX) SET( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -MP -wd4290 -wd4244 -wd4267 -wd4305 -wd4100 -wd4005" ) ##SET( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -we4061") SET( ZLIB_INCLUDE_DIR "C:\\dev32\\install\\zlib\\include" CACHE STRING "zlib include directory (headers)") # SET( ZLIB_LIBRARY "C:\\dev32\\install\\zlib\\lib\\zlib.lib" CACHE STRING "zlib library (binary)") SET(ZLIB_LIBRARY optimized "C:\\dev32\\install\\zlib\\lib\\zlib.lib" debug "C:\\dev32\\install\\zlib\\lib\\zlibd.lib" CACHE STRING "zlib library (binary)") SET( OPENSSL_ROOT_DIR "c:\\dev32\\install\\openssl" CACHE_STRING "open ssl root directory") ENDIF(CMAKE_CL_64) ELSE(MSVC) SET( OPENSSL_ROOT_DIR "c:\\dev32\\install\\openssl" CACHE_STRING "open ssl root directory") FIND_PATH(ZLIB_INCLUDE_DIR zlib.h C:\\dev32\\install\\zlib\\include C:\\zlib_software\\zlib-1.2.5-install\\include $ENV{ZLIB_INC_DIR}) FIND_PATH(ZLIB_LIBRARY libzlib.a C:\\zlib_software\\zlib-1.2.5-install\\lib) IF (NOT ZLIB_FOUND) FIND_PATH(ZLIB_LIBRARY zlib.lib C:\\dev32\\install\\zlib\\lib) ENDIF(NOT ZLIB_FOUND) IF (NOT ZLIB_FOUND) FIND_PATH(ZLIB_LIBRARY libz.a $ENV{ZLIB_LIB_DIR}) ENDIF(NOT ZLIB_FOUND) ### SET( ZLIB_INCLUDE_DIR "C:\\dev32\\install\\zlib\\include" CACHE STRING "zlib include directory (headers)") ### SET( ZLIB_LIBRARY "C:\\dev32\\install\\zlib\\lib\\zlib.lib" CACHE STRING "zlib library (binary)") ENDIF(MSVC) ELSE(APPLE) MESSAGE(FATAL_ERROR "Unrecognized operating system " ${CMAKE_SYSTEM_NAME}) ENDIF(APPLE) #cmake_policy(SET CMP0015 OLD) # # Shows compilation command when true # SET(CMAKE_VERBOSE_MAKEFILE TRUE CACHE BOOL "cause all build commands to be displayed") # # Allow support for C11X compiler # SET (WORKBENCH_C11X FALSE) ##SET (WORKBENCH_C11X TRUE) IF ("$ENV{WORKBENCH_CONFIGURE_C11X}" STREQUAL "YES") SET (WORKBENCH_C11X TRUE) MESSAGE("Configuring Workbench build with C11X enabled.") ENDIF ("$ENV{WORKBENCH_CONFIGURE_C11X}" STREQUAL "YES") # # Set flags for C11 compiler # Only set for C++ compiler # C11x options are not recognized by C compiler # SET (CLANG_11X_FLAGS "") SET (GNU_11X_FLAGS "") SET (INTEL_11X_FLAGS "") IF (WORKBENCH_C11X) ADD_DEFINITIONS("-DWORKBENCH_HAVE_C11X") SET (CLANG_11X_FLAGS "-std=c++11 -stdlib=libstdc++ -Wno-error=c++11-narrowing") ####SET (CLANG_11X_FLAGS "-std=c++11 -stdlib=libc++ -Wno-error=c++11-narrowing") SET (GNU_11X_FLAGS "-std=c++11 -Wno-error=c++11-narrowing") SET (INTEL_11X_FLAGS "-std=c++11 -Wno-error=c++11-narrowing") ENDIF (WORKBENCH_C11X) # # Intel compiler # IF (${CMAKE_CXX_COMPILER} MATCHES "^.*icpc$") ADD_DEFINITIONS("-W -Wall -Werror=return-type -Werror=switch -Wunused-parameter") SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${INTEL_11X_FLAGS}") ENDIF (${CMAKE_CXX_COMPILER} MATCHES "^.*icpc$") # # Clang compiler on Mac # UNSET(CLANG_FLAG) IF (${CMAKE_CXX_COMPILER} MATCHES "^.*clang\\+\\+.*") SET(CLANG_FLAG TRUE) ENDIF (${CMAKE_CXX_COMPILER} MATCHES "^.*clang\\+\\+.*") IF (${CMAKE_CXX_COMPILER} MATCHES "^.*clang2\\+\\+.*") SET(CLANG_FLAG TRUE) ENDIF (${CMAKE_CXX_COMPILER} MATCHES "^.*clang2\\+\\+.*") IF (CLANG_FLAG) MESSAGE("USING CLANG COMPILER ${CMAKE_CXX_COMPILER}") ADD_DEFINITIONS("-W -Wall -Werror=return-type -Werror=switch -Wunused-parameter") SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CLANG_11X_FLAGS}") ##SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CLANG_11X_FLAGS} -W -Wall -Werror=return-type -Werror=switch -Wunused-parameter") ##SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CLANG_11X_FLAGS} -W -Wall -Werror=return-type -Werror=switch -Wunused-parameter" CACHE STRING "C++ compiler options" FORCE) IF (WORKBENCH_C11X) ####set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -std=c++11 -stdlib=libc++") ENDIF (WORKBENCH_C11X) ENDIF (CLANG_FLAG) # # IF GNU compiler, functions without a return type or switch # statements that do not handle all of the enumerated types # are treated as an error. Also, all warnings. # ###IF (CMAKE_COMPILER_IS_GNUCXX) IF (NOT MSVC) ## SET(CMAKE_CXX_FLAGS "-W -Wall -Werror=return-type -Werror=switch ${CMAKE_CXX_FLAGS}" CACHE STRING "c++ compiler specific options") ## SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -W -Wall -Werror=return-type -Werror=switch -std=c++0x") ##SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") if (CMAKE_COMPILER_IS_GNUCC) execute_process(COMMAND ${CMAKE_C_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION) string(REGEX MATCHALL "[0-9]+" GCC_VERSION_COMPONENTS ${GCC_VERSION}) list(GET GCC_VERSION_COMPONENTS 0 GCC_MAJOR) list(GET GCC_VERSION_COMPONENTS 1 GCC_MINOR) message("gcc major minor version numbers are: " ${GCC_MAJOR},${GCC_MINOR}) if(${GCC_VERSION} VERSION_LESS "4.2") #SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -W -Wall" CACHE STRING "c++ compiler specific options") ADD_DEFINITIONS(-W -Wall) else() #SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -W -Wall -Werror=return-type -Werror=switch" CACHE STRING "c++ compiler specific options") ADD_DEFINITIONS(-W -Wall -Werror=return-type -Werror=switch -Wunused-parameter) SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${GNU_11X_FLAGS}") endif() if (${GCC_VERSION} VERSION_LESS "4.9") # there is no greater than or equal in CMake else() ADD_DEFINITIONS(-Wno-narrowing -Wno-unused-local-typedefs) endif() # execute_process(COMMAND uname -n OUTPUT_VARIABLE MACHINE_NAME) # message("MACHINE_NAME: ${MACHINE_NAME}") # if (${MACHINE_NAME} MATCHES "linuxbuild") # message("is linuxbuild") # SET(CMAKE_EXE_LINKER_FLAGS "-Wl,-E" ${CMAKE_EXE_LINKER_FLAGS}) # endif() endif() ENDIF (NOT MSVC) # # will set name of XCode project: # disabled at this time # ##PROJECT(BorderOptProject C CXX) FIND_PACKAGE(OpenSSL) IF(OPENSSL_FOUND) INCLUDE_DIRECTORIES(${OPENSSL_INCLUDE_DIR}) ENDIF(OPENSSL_FOUND) # # Must have QT 4.8 or later # FIND_PACKAGE(Qt4 4.8 REQUIRED) IF(QT4_FOUND) ELSE(QT4_FOUND) MESSAGE(FATAL_ERROR "QT4 not found") ENDIF(QT4_FOUND) # # QT include files # INCLUDE(${QT_USE_FILE}) # # The Find OpenMP package may not work on all systems and the user may # furnish the paths to the OpenMP files by using environment variables. # # The environment variables are: # OPENMP_COMPILE_OPTION=-fopenmp # OPENMP_HEADER_DIR=/usr/local/clang-openmp-opt/llvm/build/Release/include # OPENMP_LIB_DIR=/usr/local/clang-openmp-opt/llvm/build/Release/lib # UNSET(OPENMP_FOUND) IF (EXISTS $ENV{OPENMP_HEADER_DIR}) MESSAGE("OpenMP Header File: $ENV{OPENMP_HEADER_DIR}") IF (EXISTS $ENV{OPENMP_LIB_DIR}) MESSAGE("OpenMP Library File: $ENV{OPENMP_LIB_DIR}") SET (STUFF $ENV{OPENMP_COMPILE_OPTION}) IF (DEFINED STUFF) MESSAGE("OpenMP Compiler Option: $ENV{OPENMP_COMPILE_OPTION}") SET(OpenMP_CXX_FLAGS "-I$ENV{OPENMP_HEADER_DIR} $ENV{OPENMP_COMPILE_OPTION}") SET(OpenMP_C_FLAGS "-I$ENV{OPENMP_HEADER_DIR} $ENV{OPENMP_COMPILE_OPTION}") SET(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} -L$ENV{OPENMP_LIB_DIR}) SET(OPENMP_FOUND TRUE) ENDIF (DEFINED STUFF) ENDIF (EXISTS $ENV{OPENMP_LIB_DIR}) ENDIF (EXISTS $ENV{OPENMP_HEADER_DIR}) # # IF OpenMP not found through environment variables, # Use CMAKE's Find OpenMP module # IF (NOT OPENMP_FOUND) FIND_PACKAGE(OpenMP) ENDIF (NOT OPENMP_FOUND) # # If OpenMP is found, may need to set compiler and linker flags # IF (OPENMP_FOUND) # add definitions will add the flag to the linker and resource compilers, which don't understand the openmp option SET(CMAKE_CXX_FLAGS "${OpenMP_CXX_FLAGS} ${CMAKE_CXX_FLAGS}") # # Try to link static with Intel Compiler # IF (${CMAKE_CXX_COMPILER} MATCHES "^.*icpc$") MESSAGE(WARNING "Intel Compiler Being Used") SET (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -openmp-link=static") SET (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-intel") ADD_DEFINITIONS("-static-intel") ENDIF() ELSE (OPENMP_FOUND) MESSAGE(WARNING "OpenMP was not found") IF (CLANG_FLAG) # # The clang compiler does not support OpenMP so it produces many warnings # with "Unknown pragma ignored". So, tell clang to ignore unknown pragmas # so the message is not printed. # ADD_DEFINITIONS("-Wno-unknown-pragmas") ENDIF (CLANG_FLAG) ENDIF(OPENMP_FOUND) MESSAGE("\nC++ flags ${CMAKE_CXX_FLAGS}\n") # # MUST have ZLIB # FIND_PACKAGE(ZLIB) IF ( ZLIB_FOUND ) INCLUDE_DIRECTORIES(${ZLIB_INCLUDE_DIRS}) ELSE (ZLIB_FOUND) MESSAGE(FATAL_ERROR "ZLIB was not found") ENDIF (ZLIB_FOUND) # # Quazip needs this defined here for static linking on windows # IF(WIN32) IF(MSVC) ADD_DEFINITIONS(-DQUAZIP_STATIC) ENDIF(MSVC) ENDIF(WIN32) # # Find FreeType # SET(FTGL_FONT_MODULE_FOR_LINKING "") FIND_PACKAGE(Freetype) IF (FREETYPE_FOUND) SET (FTGL_FONT_MODULE_FOR_LINKING FtglFont) MESSAGE("FreeType library found") MESSAGE(" INCLUDES ${FREETYPE_INCLUDE_DIRS}") MESSAGE(" INCLUDES_FT2_BUILD ${FREETYPE_INCLUDE_DIR_ft2build}") MESSAGE(" INCLUDES_FT2 ${FREETYPE_INCLUDE_DIR_freetype2}") MESSAGE(" LIBS ${FREETYPE_LIBRARIES}") ADD_DEFINITIONS(-DHAVE_FREETYPE) ELSE (FREETYPE_FOUND) SET (FREETYPE_LIBRARY "") SET (FREETYPE_LIBRARIES "") MESSAGE("FreeType library NOT found") MESSAGE(" The environment variable FREETYPE_DIR can be set to the") MESSAGE(" directory containing FreeType include and lib.") MESSAGE("") MESSAGE(" ") MESSAGE(" On Windows (and possibly other systems) it may be necessary to set") MESSAGE(" DFREETYPE_INCLUDE_DIR_freetype2") MESSAGE(" /FreeType-X.Y.Z/include/freetype2") MESSAGE(" DFREETYPE_INCLUDE_DIR_ft2build") MESSAGE(" /FreeType-X.Y.Z/include") MESSAGE(" DFREETYPE_LIBRARY") MESSAGE(" /FreeType-X.Y.Z/lib/freetype.lib") MESSAGE(" ") MESSAGE(" These variable can be set when running cmake. For example:") MESSAGE(" cmake -DFREETYPE_INCLUDE_DIR_freetype2=/FreeType-X.Y.Z/include/freetype2") MESSAGE(" ") ENDIF (FREETYPE_FOUND) # # Fixes issue with XCode and newer version of CMake. # It prevents the ZERO_CHECK dependency from running # (which is very slow) every time a build is performed # in XCode. # IF (APPLE) SET (CMAKE_SUPPRESS_REGENERATION TRUE) ENDIF (APPLE) #============================================================================= # # Test for offscreen mesa (optional library) # If found, set some variables. Since, Mesa is only used for # command line # SET(OSMESA_FOUND FALSE) SET(OSMESA_DEFINITION "") SET(OSMESA_OFFSCREEN_LIBRARY "") SET(OSMESA_GL_LIBRARY "") SET(OSMESA_GLU_LIBRARY "") SET(OSMESA_INCLUDE_DIRECTORY "") MESSAGE("OSMESA_DIR: $ENV{OSMESA_DIR}") IF (EXISTS $ENV{OSMESA_DIR}) IF (EXISTS $ENV{OSMESA_DIR}/include/GL/osmesa.h) MESSAGE("Have Mesa Include Directory") FIND_LIBRARY(OSMESA_LIBRARY_FOUND NAMES libOSMesa.a libOSMesa.so OSMesa.lib OSMesa.dll PATHS $ENV{OSMESA_DIR}/lib) FIND_LIBRARY(OSMESA_GL_LIBRARY_FOUND NAMES libGL.a libGL.so PATHS $ENV{OSMESA_DIR}/lib) FIND_LIBRARY(OSMESA_GLU_LIBRARY_FOUND NAMES libGLU.a libGLU.so PATHS $ENV{OSMESA_DIR}/lib) MESSAGE("OSMesa lib: " ${OSMESA_LIBRARY}) IF (EXISTS ${OSMESA_LIBRARY_FOUND} AND EXISTS ${OSMESA_GL_LIBRARY_FOUND} AND EXISTS ${OSMESA_GLU_LIBRARY_FOUND}) SET(OSMESA_DEFINITION -DHAVE_OSMESA) SET(OSMESA_OFFSCREEN_LIBRARY ${OSMESA_LIBRARY_FOUND}) SET(OSMESA_GL_LIBRARY ${OSMESA_GL_LIBRARY_FOUND}) SET(OSMESA_GLU_LIBRARY ${OSMESA_GLU_LIBRARY_FOUND}) SET(OSMESA_INCLUDE_DIRECTORY $ENV{OSMESA_DIR}/include) SET(OSMESA_FOUND TRUE) MESSAGE("Offscreen Mesa Library was found") MESSAGE(" Definition: ${OSMESA_DEFINITION}") MESSAGE(" Include: ${OSMESA_INCLUDE_DIRECTORY}") MESSAGE(" Libraries: ${OSMESA_OFFSCREEN_LIBRARY}") MESSAGE(" Libraries: ${OSMESA_GL_LIBRARY}") MESSAGE(" Libraries: ${OSMESA_GLU_LIBRARY}") ENDIF (EXISTS ${OSMESA_LIBRARY_FOUND} AND EXISTS ${OSMESA_GL_LIBRARY_FOUND} AND EXISTS ${OSMESA_GLU_LIBRARY_FOUND}) ENDIF (EXISTS $ENV{OSMESA_DIR}/include/GL/osmesa.h) ENDIF (EXISTS $ENV{OSMESA_DIR}) #============================================================================= MESSAGE("") MESSAGE("Compiler: ${CMAKE_CXX_COMPILER}") MESSAGE("Compiler Version: ${CMAKE_CXX_COMPILER_VERSION}") MESSAGE("") #============================================================================= # # All subdirectories that will be configured for building # ADD_SUBDIRECTORY ( Quazip ) ADD_SUBDIRECTORY ( Common ) ADD_SUBDIRECTORY ( Xml ) ADD_SUBDIRECTORY ( Scenes ) ADD_SUBDIRECTORY ( OSMesaDummy ) IF (FREETYPE_FOUND) ADD_SUBDIRECTORY ( FtglFont ) ENDIF (FREETYPE_FOUND) ADD_SUBDIRECTORY ( Charting ) ADD_SUBDIRECTORY ( Palette ) ADD_SUBDIRECTORY ( FilesBase ) ADD_SUBDIRECTORY ( Nifti ) ADD_SUBDIRECTORY ( Gifti ) ADD_SUBDIRECTORY ( Cifti ) ADD_SUBDIRECTORY ( Files ) ADD_SUBDIRECTORY ( OperationsBase ) ADD_SUBDIRECTORY ( Algorithms ) ADD_SUBDIRECTORY ( Operations ) ADD_SUBDIRECTORY ( Brain ) ADD_SUBDIRECTORY ( Qwt ) ADD_SUBDIRECTORY ( GuiQt ) ADD_SUBDIRECTORY ( Commands ) ADD_SUBDIRECTORY ( Desktop ) ADD_SUBDIRECTORY ( CommandLine ) ADD_SUBDIRECTORY ( Tests ) # #CTest tests # ENABLE_TESTING() ADD_TEST(timer ${CMAKE_CURRENT_BINARY_DIR}/Tests/test_driver timer) ADD_TEST(progress ${CMAKE_CURRENT_BINARY_DIR}/Tests/test_driver progress) ADD_TEST(volumefile ${CMAKE_CURRENT_BINARY_DIR}/Tests/test_driver volumefile) #debian build machines don't have internet access #ADD_TEST(http ${CMAKE_CURRENT_BINARY_DIR}/Tests/test_driver http) ADD_TEST(heap ${CMAKE_CURRENT_BINARY_DIR}/Tests/test_driver heap) ADD_TEST(pointer ${CMAKE_CURRENT_BINARY_DIR}/Tests/test_driver pointer) ADD_TEST(statistics ${CMAKE_CURRENT_BINARY_DIR}/Tests/test_driver statistics) ADD_TEST(quaternion ${CMAKE_CURRENT_BINARY_DIR}/Tests/test_driver quaternion) ADD_TEST(mathexpression ${CMAKE_CURRENT_BINARY_DIR}/Tests/test_driver mathexpression) ADD_TEST(lookup ${CMAKE_CURRENT_BINARY_DIR}/Tests/test_driver lookup) workbench-1.1.1/src/CMakeScripts/000077500000000000000000000000001255417355300166255ustar00rootroot00000000000000workbench-1.1.1/src/CMakeScripts/copy_mac_frameworks.sh000077500000000000000000000007241255417355300232210ustar00rootroot00000000000000#!/bin/sh # # This script copies adds the frameworks to the Mac Bundles # exeName=$1 for buildType in Debug/ Release/ RelWithDebInfo/ MinRelSize/ "" do echo "BUILD TYPE ${buildType}" appName=${buildType}${exeName}.app exeName=${appName}/Contents/MacOS/${exeName} echo "App ${appName}" echo "Exe ${exeName}" if [ -f ${exeName} ] ; then if [ ! -d ${appName}/Contents/Frameworks ] ; then macdeployqt ${appName} fi fi done workbench-1.1.1/src/CMakeScripts/copy_mac_icon.sh000077500000000000000000000010051255417355300217620ustar00rootroot00000000000000#!/bin/sh # # This script copies the mac icon into # into the Mac App's Resources directory. # exeName=$1 iconName=$2 for buildType in Debug/ Release/ RelWithDebInfo/ MinRelSize/ "" do echo "BUILD TYPE ${buildType}" appName=${buildType}${exeName}.app/Contents/MacOS/${exeName} echo "App ${appName}" if [ -f ${appName} ] ; then cp $iconName ${buildType}${exeName}.app/Contents/Resources #cp -R ${QTDIR}/src/gui/mac/qt_menu.nib ${buildType}${exeName}.app/Contents/Resources fi done workbench-1.1.1/src/CMakeScripts/copy_mac_nib.sh000077500000000000000000000020151255417355300216040ustar00rootroot00000000000000#!/bin/sh # # This script copies the qt_menu.nib directory from QT # into the Mac App's Resources directory. # exeName=$1 for buildType in Debug/ Release/ RelWithDebInfo/ MinRelSize/ "" do echo "BUILD TYPE ${buildType}" appName=${buildType}${exeName}.app/Contents/MacOS/${exeName} echo "App ${appName}" if [ -f ${appName} ] ; then if [ ! -d ${buildType}${exeName}.app/Contents/Resources ] ; then echo "Creating resources directory" mkdir ${buildType}${exeName}.app/Contents/Resources fi nib1=${QTDIR}/src/gui/mac/qt_menu.nib echo "nib1: ${nib1}" if [ -f ${nib1} ] ; then echo "Copying NIB file 1" cp -R ${QTDIR}/src/gui/mac/qt_menu.nib ${buildType}${exeName}.app/Contents/Resources fi nib2=${QTDIR}/lib/QtGui.framework/Versions/4/Resources/qt_menu.nib echo "nib2: ${nib2}" if [ -d ${nib2} ] ; then echo "Copying NIB file 2" cp -R ${nib2} ${buildType}${exeName}.app/Contents/Resources fi fi done workbench-1.1.1/src/CMakeScripts/git_commit_info.cmake.in000066400000000000000000000015631255417355300234070ustar00rootroot00000000000000FIND_PACKAGE(Git) IF(GIT_FOUND) EXECUTE_PROCESS( COMMAND ${GIT_EXECUTABLE} rev-parse HEAD OUTPUT_VARIABLE "commit" OUTPUT_STRIP_TRAILING_WHITESPACE ) EXECUTE_PROCESS( COMMAND ${GIT_EXECUTABLE} log -1 --pretty=format:%ai OUTPUT_VARIABLE "commit_date" OUTPUT_STRIP_TRAILING_WHITESPACE ) ELSE(GIT_FOUND) SET(commit "unknown") SET(commit_date "unknown") ENDIF(GIT_FOUND) IF(WIN32) EXECUTE_PROCESS( COMMAND powershell.exe -Command "& {(Get-Content ${INFILE}) | ForEach-Object { ($_ -replace \"@COMMIT@\", \"${commit}\") -replace \"@COMMIT_DATE@\", \"${commit_date}\" } | Set-Content ${OUTFILE}}" ) ELSE(WIN32) EXECUTE_PROCESS( INPUT_FILE "${INFILE}" COMMAND sed -e "s/@COMMIT@/${commit}/g" -e "s/@COMMIT_DATE@/${commit_date}/g" OUTPUT_FILE "${OUTFILE}" ) ENDIF(WIN32) workbench-1.1.1/src/CMakeScripts/save.sh000077500000000000000000000011741255417355300201250ustar00rootroot00000000000000#/bin/sh -v set on echo "Current Directory" `pwd` if [ -f Debug/desktop.app/Contents/MacOS/desktop ] ; then # cp -R $QTDIR/src/gui/mac/qt_menu.nib Debug/desktop.app/Contents/Resources ; fi if [ -f Release/desktop.app/Contents/MacOS/desktop ] ; then echo "Exists" if [ ! -d Debug/desktop.app/Contents/Resources ] ; then mkdir Debug/desktop.app/Contents/Resources fi fi if [ -f RelWithDebInfo/desktop.app/Contents/MacOS/desktop ] ; then echo "Exists" fi if [ -f MinRelSize/desktop.app/Contents/MacOS/desktop ] ; then echo "Exists" fi if [ -f desktop.app/Contents/MacOS/desktop ] ; then echo "Exists" fi workbench-1.1.1/src/Charting/000077500000000000000000000000001255417355300160345ustar00rootroot00000000000000workbench-1.1.1/src/Charting/CMakeLists.txt000066400000000000000000000022351255417355300205760ustar00rootroot00000000000000 # # Name of Project # PROJECT(Charting) # # Create a library # ADD_LIBRARY(Charting ChartAxis.h ChartAxisCartesian.h ChartAxisLocationEnum.h ChartAxisTypeEnum.h ChartAxisUnitsEnum.h ChartData.h ChartDataCartesian.h ChartDataSource.h ChartDataSourceModeEnum.h ChartDataTypeEnum.h ChartMatrixDisplayProperties.h ChartMatrixLoadingDimensionEnum.h ChartMatrixScaleModeEnum.h ChartModel.h ChartModelCartesian.h ChartModelDataSeries.h ChartModelFrequencySeries.h ChartModelTimeSeries.h ChartPoint.h ChartScaleAutoRanging.h ChartSelectionModeEnum.h ChartAxis.cxx ChartAxisCartesian.cxx ChartAxisLocationEnum.cxx ChartAxisTypeEnum.cxx ChartAxisUnitsEnum.cxx ChartData.cxx ChartDataCartesian.cxx ChartDataSource.cxx ChartDataSourceModeEnum.cxx ChartDataTypeEnum.cxx ChartMatrixDisplayProperties.cxx ChartMatrixLoadingDimensionEnum.cxx ChartMatrixScaleModeEnum.cxx ChartModel.cxx ChartModelCartesian.cxx ChartModelDataSeries.cxx ChartModelFrequencySeries.cxx ChartModelTimeSeries.cxx ChartPoint.cxx ChartScaleAutoRanging.cxx ChartSelectionModeEnum.cxx ) # # Include directories # INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/Charting ${CMAKE_SOURCE_DIR}/Common ${CMAKE_SOURCE_DIR}/Scenes ) workbench-1.1.1/src/Charting/ChartAxis.cxx000066400000000000000000000216421255417355300204530ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CHART_AXIS_DECLARE__ #include "ChartAxis.h" #undef __CHART_AXIS_DECLARE__ #include "CaretAssert.h" #include "ChartAxisCartesian.h" #include "ChartScaleAutoRanging.h" #include "SceneClass.h" #include "SceneClassAssistant.h" using namespace caret; /** * \class caret::ChartAxis * \brief Contains information about a chart axis. * \ingroup Charting */ /** * Constructor. * * @param axisType * The axis type. * @param axisLocation. * Axis location. */ ChartAxis::ChartAxis(const ChartAxisTypeEnum::Enum axisType, const ChartAxisLocationEnum::Enum axisLocation) : CaretObject(), SceneableInterface(), m_axisType(axisType), m_axisLocation(axisLocation) { initializeMembersChartAxis(); } /** * Create and return an axis of the given type and at the given location. * * @param axisType * Type of axis. * @param axisLocation * Location of axis. * @return * Axis that was created. */ ChartAxis* ChartAxis::newChartAxisForTypeAndLocation(const ChartAxisTypeEnum::Enum axisType, const ChartAxisLocationEnum::Enum axisLocation) { ChartAxis* axis = NULL; switch (axisType) { case ChartAxisTypeEnum::CHART_AXIS_TYPE_CARTESIAN: axis = new ChartAxisCartesian(axisLocation); break; case ChartAxisTypeEnum::CHART_AXIS_TYPE_NONE: CaretAssert(0); break; } return axis; } /** * Destructor. */ ChartAxis::~ChartAxis() { delete m_sceneAssistant; } /** * Initialize members of a new instance. */ void ChartAxis::initializeMembersChartAxis() { m_parentChartModel = NULL; m_autoRangeScaleEnabled = true; m_axisUnits = ChartAxisUnitsEnum::CHART_AXIS_UNITS_NONE; m_labelFontSize = 12; m_visible = false; m_sceneAssistant = new SceneClassAssistant(); m_sceneAssistant->add("m_axisLocation", &m_axisLocation); m_sceneAssistant->add("m_autoRangeScaleEnabled", &m_autoRangeScaleEnabled); m_sceneAssistant->add("m_labelFontSize", &m_labelFontSize); m_sceneAssistant->add("m_visible", &m_visible); m_sceneAssistant->add("m_text", &m_text); } /** * Copy constructor. * @param obj * Object that is copied. */ ChartAxis::ChartAxis(const ChartAxis& obj) : CaretObject(obj), SceneableInterface(obj) { initializeMembersChartAxis(); copyHelperChartAxis(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ ChartAxis& ChartAxis::operator=(const ChartAxis& obj) { if (this != &obj) { CaretObject::operator=(obj); this->copyHelperChartAxis(obj); } return *this; } /** * Helps with copying an object of this type. * @param obj * Object that is copied. */ void ChartAxis::copyHelperChartAxis(const ChartAxis& obj) { m_parentChartModel = NULL; m_axisType = obj.m_axisType; m_axisLocation = obj.m_axisLocation; m_text = obj.m_text; m_axisUnits = obj.m_axisUnits; m_labelFontSize = obj.m_labelFontSize; m_visible = obj.m_visible; m_autoRangeScaleEnabled = obj.m_autoRangeScaleEnabled; } /** * @return The type of the axis. */ ChartAxisTypeEnum::Enum ChartAxis::getAxisType() const { return m_axisType; } /** * @return The location of the axis. */ ChartAxisLocationEnum::Enum ChartAxis::getAxisLocation() const { return m_axisLocation; } /** * Set the parent chart model. * * @param parentChartModel * Chart in which this axis is used. */ void ChartAxis::setParentChartModel(ChartModel* parentChartModel) { m_parentChartModel = parentChartModel; } /** * @return The chart model that uses this axis (may be NULL). */ ChartModel* ChartAxis::getParentChartModel() { return m_parentChartModel; } /** * @return The chart model that uses this axis (may be NULL). */ const ChartModel* ChartAxis::getParentChartModel() const { return m_parentChartModel; } /** * Get a description of this object's content. * @return String describing this object's content. */ AString ChartAxis::toString() const { return "ChartAxis"; } /** * @return Text for axis label. */ AString ChartAxis::getText() const { return m_text; } /** * Set text for axis label. * * @param text * New text for label. */ void ChartAxis::setText(const AString& text) { m_text = text; } /** * @return Axis Units. */ ChartAxisUnitsEnum::Enum ChartAxis::getAxisUnits() const { return m_axisUnits; } /** * Set the units for the axis. * * @param axisUnits * New value for axis units. */ void ChartAxis::setAxisUnits(const ChartAxisUnitsEnum::Enum axisUnits) { m_axisUnits = axisUnits; } /** * Return the suffix for the axis units */ AString ChartAxis::getAxisUnitsSuffix() const { AString suffix; switch (m_axisUnits) { case ChartAxisUnitsEnum::CHART_AXIS_UNITS_NONE: break; case ChartAxisUnitsEnum::CHART_AXIS_UNITS_FREQUENCY_HERTZ: suffix = "hz"; break; case ChartAxisUnitsEnum::CHART_AXIS_UNITS_TIME_SECONDS: suffix = "s"; break; } return suffix; } /** * @return Font size for the label's text. */ int32_t ChartAxis::getLabelFontSize() const { return m_labelFontSize; } /** * Set font size for label's text. * * @param fontSize * New value for font size. */ void ChartAxis::setLabelFontSize(const float fontSize) { m_labelFontSize = fontSize; } /** * @return True if this axis should be displayed. */ bool ChartAxis::isVisible() const { return m_visible; } /** * Set this axis should be displayed. * * @param visible * True if displayed, else false. */ void ChartAxis::setVisible(const bool visible) { m_visible = visible; } /** * Is auto range scale enabled (scale matches data) */ bool ChartAxis::isAutoRangeScaleEnabled() const { return m_autoRangeScaleEnabled; } /** * Set auto range scale enabled (scale matches data) * * @param autoRangeScaleEnabled * New status. */ void ChartAxis::setAutoRangeScaleEnabled(const bool autoRangeScaleEnabled) { m_autoRangeScaleEnabled = autoRangeScaleEnabled; if (m_autoRangeScaleEnabled) { updateForAutoRangeScale(); } } /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @param instanceName * Name of the class' instance. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ SceneClass* ChartAxis::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "ChartAxis", 1); m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); saveSubClassDataToScene(sceneAttributes, sceneClass); return sceneClass; } /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass for the instance of a class that implements * this interface. May be NULL for some types of scenes. */ void ChartAxis::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); restoreSubClassDataFromScene(sceneAttributes, sceneClass); } workbench-1.1.1/src/Charting/ChartAxis.h000066400000000000000000000115721255417355300201010ustar00rootroot00000000000000#ifndef __CHART_AXIS_H__ #define __CHART_AXIS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "ChartAxisLocationEnum.h" #include "ChartAxisTypeEnum.h" #include "ChartAxisUnitsEnum.h" #include "SceneableInterface.h" namespace caret { class ChartModel; class SceneClassAssistant; class ChartAxis : public CaretObject, public SceneableInterface { public: enum Axis { AXIS_BOTTOM, AXIS_LEFT, AXIS_RIGHT, AXIS_TOP }; static ChartAxis* newChartAxisForTypeAndLocation(const ChartAxisTypeEnum::Enum axisType, const ChartAxisLocationEnum::Enum axisLocation); virtual ~ChartAxis(); ChartAxis(const ChartAxis&); ChartAxis& operator=(const ChartAxis&); /** * At times a copy of chart axis will be needed BUT it must be * the proper subclass so copy constructor and assignment operator * will function when this abstract, base class is used. Each * subclass will override this method so that the returned class * is of the proper type. * * @return Copy of this instance that is the actual subclass. */ virtual ChartAxis* clone() const = 0; void setParentChartModel(ChartModel* parentChartModel); ChartAxisTypeEnum::Enum getAxisType() const; ChartAxisLocationEnum::Enum getAxisLocation() const; AString getText() const; void setText(const AString& text); ChartAxisUnitsEnum::Enum getAxisUnits() const; void setAxisUnits(const ChartAxisUnitsEnum::Enum axisUnits); AString getAxisUnitsSuffix() const; int32_t getLabelFontSize() const; void setLabelFontSize(const float fontSize); bool isAutoRangeScaleEnabled() const; void setAutoRangeScaleEnabled(const bool autoRangeScaleEnabled); bool isVisible() const; void setVisible(const bool visible); /** * Update for auto range scale. */ virtual void updateForAutoRangeScale() = 0; virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); protected: ChartAxis(const ChartAxisTypeEnum::Enum axisType, const ChartAxisLocationEnum::Enum axisLocation); ChartModel* getParentChartModel(); const ChartModel* getParentChartModel() const; virtual void saveSubClassDataToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass) = 0; virtual void restoreSubClassDataFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) = 0; private: void copyHelperChartAxis(const ChartAxis& obj); public: // ADD_NEW_METHODS_HERE virtual AString toString() const; private: void initializeMembersChartAxis(); ChartAxisTypeEnum::Enum m_axisType; ChartAxisLocationEnum::Enum m_axisLocation; ChartModel* m_parentChartModel; AString m_text; ChartAxisUnitsEnum::Enum m_axisUnits; int32_t m_labelFontSize; bool m_visible; bool m_autoRangeScaleEnabled; /** helps with scene save/restore */ SceneClassAssistant* m_sceneAssistant; // ADD_NEW_MEMBERS_HERE }; #ifdef __CHART_AXIS_DECLARE__ // #endif // __CHART_AXIS_DECLARE__ } // namespace #endif //__CHART_AXIS_H__ workbench-1.1.1/src/Charting/ChartAxisCartesian.cxx000066400000000000000000000442561255417355300223130ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __CHART_AXIS_CARTESIAN_DECLARE__ #include "ChartAxisCartesian.h" #undef __CHART_AXIS_CARTESIAN_DECLARE__ #include "CaretAssert.h" #include "CaretLogger.h" #include "ChartModelCartesian.h" #include "ChartScaleAutoRanging.h" #include "EventAlertUser.h" #include "EventManager.h" #include "MathFunctions.h" #include "SceneClass.h" #include "SceneClassAssistant.h" using namespace caret; /** * \class caret::ChartAxisCartesian * \brief Axes for Cartesian Data. * \ingroup Charting */ /** * Constructor. * * @param axisLocation. * Axis location. */ ChartAxisCartesian::ChartAxisCartesian(const ChartAxisLocationEnum::Enum axisLocation) : ChartAxis(ChartAxisTypeEnum::CHART_AXIS_TYPE_CARTESIAN, axisLocation) { initializeMembersChartAxisCartesian(); } /** * Destructor. */ ChartAxisCartesian::~ChartAxisCartesian() { delete m_sceneAssistant; } /** * Copy constructor. * @param obj * Object that is copied. */ ChartAxisCartesian::ChartAxisCartesian(const ChartAxisCartesian& obj) : ChartAxis(obj) { initializeMembersChartAxisCartesian(); this->copyHelperChartAxisCartesian(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ ChartAxisCartesian& ChartAxisCartesian::operator=(const ChartAxisCartesian& obj) { if (this != &obj) { ChartAxis::operator=(obj); this->copyHelperChartAxisCartesian(obj); } return *this; } /** * Helps with copying an object of this type. * @param obj * Object that is copied. */ void ChartAxisCartesian::copyHelperChartAxisCartesian(const ChartAxisCartesian& obj) { m_maximumValue = obj.m_maximumValue; m_minimumValue = obj.m_minimumValue; m_digitsRightOfDecimal = obj.m_digitsRightOfDecimal; } /** * Initialize class members. */ void ChartAxisCartesian::initializeMembersChartAxisCartesian() { m_minimumValue = 0.0; m_maximumValue = 1.0; m_digitsRightOfDecimal = 1; m_axisLabelsMinimumValue = 0.0; m_axisLabelsMaximumValue = 1.0; m_sceneAssistant = new SceneClassAssistant(); m_sceneAssistant->add("m_minimumValue", &m_minimumValue); m_sceneAssistant->add("m_maximumValue", &m_maximumValue); m_sceneAssistant->add("m_digitsRightOfDecimal", &m_digitsRightOfDecimal); m_sceneAssistant->add("m_axisLabelsMinimumValue", &m_axisLabelsMinimumValue); m_sceneAssistant->add("m_axisLabelsMaximumValue", &m_axisLabelsMaximumValue); } /** * @return Minimum value for axis. */ float ChartAxisCartesian::getMinimumValue() const { return m_minimumValue; } /** * Set minimum value for axis. * * @param minimumValue * New minimum value for axis. */ void ChartAxisCartesian::setMinimumValue(const float minimumValue) { m_minimumValue = minimumValue; } /** * @return Maximum value for axis. */ float ChartAxisCartesian::getMaximumValue() const { return m_maximumValue; } /** * Set maximum value for axis. * * @param maximumValue * New maximum value for axis. */ void ChartAxisCartesian::setMaximumValue(const float maximumValue) { m_maximumValue = maximumValue; } /** * Update for auto range scale. */ void ChartAxisCartesian::updateForAutoRangeScale() { if (isAutoRangeScaleEnabled()) { const ChartModel* chartModel = getParentChartModel(); CaretAssert(chartModel); const ChartModelCartesian* chartModelCartesian = dynamic_cast(chartModel); float minX, maxX, minY, maxY; chartModelCartesian->getBounds(minX, maxX, minY, maxY); float minValue = m_minimumValue; float maxValue = m_maximumValue; switch (getAxisLocation()) { case ChartAxisLocationEnum::CHART_AXIS_LOCATION_BOTTOM: minValue = minX; maxValue = maxX; break; case ChartAxisLocationEnum::CHART_AXIS_LOCATION_LEFT: minValue = minY; maxValue = maxY; break; case ChartAxisLocationEnum::CHART_AXIS_LOCATION_RIGHT: minValue = minY; maxValue = maxY; break; case ChartAxisLocationEnum::CHART_AXIS_LOCATION_TOP: minValue = minX; maxValue = maxX; break; } m_minimumValue = minValue; m_maximumValue = maxValue; } double scaleStep = 0.0; double scaleMin = 0.0; double scaleMax = 0.0; int32_t digitsRightOfDecimal = 0; ChartScaleAutoRanging::createAutoScale(m_minimumValue, m_maximumValue, scaleMin, scaleMax, scaleStep, digitsRightOfDecimal); m_axisLabelsMinimumValue = scaleMin; m_axisLabelsMaximumValue = scaleMax; m_axisLabelsStepValue = scaleStep; m_digitsRightOfDecimal = digitsRightOfDecimal; /** * Use auto-scaled range for left and right axis */ if (isAutoRangeScaleEnabled()) { switch (getAxisLocation()) { case ChartAxisLocationEnum::CHART_AXIS_LOCATION_BOTTOM: break; case ChartAxisLocationEnum::CHART_AXIS_LOCATION_LEFT: m_minimumValue = m_axisLabelsMinimumValue; m_maximumValue = m_axisLabelsMaximumValue; break; case ChartAxisLocationEnum::CHART_AXIS_LOCATION_RIGHT: m_minimumValue = m_axisLabelsMinimumValue; m_maximumValue = m_axisLabelsMaximumValue; break; case ChartAxisLocationEnum::CHART_AXIS_LOCATION_TOP: break; } } } /** * Get the axis labels and their positions for drawing the scale. * * @param axisLengthInPixels * Length of axis in pixels. * @param fontSizeInPixels * Size of the font in pixels. * @param labelOffsetInPixelsOut * Output containing offset in pixels for the scale labels. * @param labelTextOut * Output containing text for scale labels. */ void ChartAxisCartesian::getLabelsAndPositions(const float axisLengthInPixels, const float /*fontSizeInPixels*/, std::vector& labelOffsetInPixelsOut, std::vector& labelTextOut) { labelOffsetInPixelsOut.clear(); labelTextOut.clear(); if (axisLengthInPixels < 25.0) { return; } updateForAutoRangeScale(); float dataStart = m_minimumValue; float dataEnd = m_maximumValue; float dataRange = (m_maximumValue - m_minimumValue); if (dataRange <= 0.0) { return; } float labelsStart = m_axisLabelsMinimumValue; float labelsEnd = m_axisLabelsMaximumValue; /* * If the "labels end" or "labels start" value is not valid (infinity or not-a-number) there * are invalid values in the data and will cause the labels processing later * in this method to fail. So, alert the user that there is a problem in * the data. * * A set is used to track those models for which the user has * already been alerted. Otherwise, the alert message will be * displayed every time this method is called (which is many) and * the user will receive endless pop-ups. */ if ( (! MathFunctions::isNumeric(labelsStart)) || (! MathFunctions::isNumeric(labelsEnd))) { const ChartModel* chartModel = getParentChartModel(); CaretAssert(chartModel); const ChartModelCartesian* chartModelCartesian = dynamic_cast(chartModel); static std::set invalidChartModelCartesians; if (invalidChartModelCartesians.find(chartModelCartesian) == invalidChartModelCartesians.end()) { invalidChartModelCartesians.insert(chartModelCartesian); const AString msg("Invalid numbers (infinity or not-a-number) found when trying to create chart. " "Run \"wb_command -file-information\" on files being charted to find the file " "that contains invalid data so that the file can be fixed."); EventManager::get()->sendEvent(EventAlertUser(msg).getPointer()); } return; } float labelsRange = (m_axisLabelsMaximumValue - m_axisLabelsMinimumValue); if (labelsRange <= 0.0) { return; } const float tickLabelsStep = m_axisLabelsStepValue; if (tickLabelsStep <= 0.0) { return; } float labelValue = labelsStart; while (labelValue <= labelsEnd) { float labelParametricValue = (labelValue - dataStart) / dataRange; float labelValueForText = labelValue; if (dataRange >= 10.0) { /* * Is this the first label? */ if (labelValue <= labelsStart) { /* * Handles case when the minimum DATA value is just a little * bit greater than the minimum value for axis labels such * as in Data-Series data when the minimum data value is "1" * and the minimum axis label value is "0". Without this * code no value is displayed at the left edge of the axis. */ if (labelParametricValue < 0.0) { const float nextParametricValue = ((labelValue + tickLabelsStep) - dataStart) / dataRange; if (nextParametricValue > 0.05) { labelParametricValue = 0.0; labelValueForText = dataStart; } } } if (labelParametricValue < 0.0) { if (labelParametricValue >= -0.01) { labelParametricValue = 0.0; } } /* * Is this the last label? */ if (labelValue >= labelsEnd) { /* * Like above, ensures a value is displayed at the right * edge of the axis. */ if (labelParametricValue > 1.0) { const float prevParametricValue = ((labelValue - tickLabelsStep) - dataStart) / dataRange; if (prevParametricValue < 0.95) { labelParametricValue = 1.0; labelValueForText = dataEnd; } } } if (labelParametricValue > 1.0) { if (labelParametricValue < 1.01) { labelParametricValue = 1.0; } } } if ((labelParametricValue >= 0.0) && (labelParametricValue <= 1.0)) { const float labelPixelsPosition = axisLengthInPixels * labelParametricValue; const AString labelText = AString::number(labelValueForText, 'f', m_digitsRightOfDecimal); labelOffsetInPixelsOut.push_back(labelPixelsPosition); labelTextOut.push_back(labelText); } else { // std::cout << "Label value=" << labelValue << " parametric=" << labelParametricValue << " failed." << std::endl; } labelValue += tickLabelsStep; } } // 29 April 2014 ///** // * Get the axis labels and their positions for drawing the scale. // * // * @param axisLengthInPixels // * Length of axis in pixels. // * @param fontSizeInPixels // * Size of the font in pixels. // * @param labelOffsetInPixelsOut // * Output containing offset in pixels for the scale labels. // * @param labelTextOut // * Output containing text for scale labels. // */ //void //ChartAxisCartesian::getLabelsAndPositions(const float axisLengthInPixels, // const float /*fontSizeInPixels*/, // std::vector& labelOffsetInPixelsOut, // std::vector& labelTextOut) //{ // labelOffsetInPixelsOut.clear(); // labelTextOut.clear(); // // if (axisLengthInPixels < 25.0) { // return; // } // // updateForAutoRangeScale(); // // const float numberOfTicks = 5; // // float dataStart = m_minimumValue; // float dataEnd = m_maximumValue; // float dataRange = (m_maximumValue - m_minimumValue); // if (dataRange <= 0.0) { // return; // } // // float labelsStart = m_axisLabelsMinimumValue; // float labelsRange = (m_axisLabelsMaximumValue - m_axisLabelsMinimumValue); // if (labelsRange <= 0.0) { // return; // } // const float tickLabelsStep = labelsRange / numberOfTicks; // if (tickLabelsStep <= 0.0) { // return; // } // // float labelValue = labelsStart; // for (int32_t i = 0; i <= numberOfTicks; i++) { // float labelParametricValue = (labelValue - dataStart) / dataRange; // // float labelValueForText = labelValue; // // if (dataRange >= 10.0) { // if (i == 0) { // /* // * Handles case when the minimum DATA value is just a little // * bit greater than the minimum value for axis labels such // * as in Data-Series data when the minimum data value is "1" // * and the minimum axis label value is "0". Without this // * code no value is displayed at the left edge of the axis. // */ // if (labelParametricValue < 0.0) { // const float nextParametricValue = ((labelValue + tickLabelsStep) - dataStart) / dataRange; // if (nextParametricValue > 0.05) { // labelParametricValue = 0.0; // labelValueForText = dataStart; // } // } // } // // if (labelParametricValue < 0.0) { // if (labelParametricValue >= -0.01) { // labelParametricValue = 0.0; // } // } // // if (i == numberOfTicks) { // /* // * Like above, ensures a value is displayed at the right // * edge of the axis. // */ // if (labelParametricValue > 1.0) { // const float prevParametricValue = ((labelValue - tickLabelsStep) - dataStart) / dataRange; // if (prevParametricValue < 0.95) { // labelParametricValue = 1.0; // labelValueForText = dataEnd; // } // } // } // // if (labelParametricValue > 1.0) { // if (labelParametricValue < 1.01) { // labelParametricValue = 1.0; // } // } // } // // if ((labelParametricValue >= 0.0) // && (labelParametricValue <= 1.0)) { // const float labelPixelsPosition = axisLengthInPixels * labelParametricValue; // const AString labelText = AString::number(labelValueForText, 'f', m_digitsRightOfDecimal); // // labelOffsetInPixelsOut.push_back(labelPixelsPosition); // labelTextOut.push_back(labelText); // } // else { //// std::cout << "Label value=" << labelValue << " parametric=" << labelParametricValue << " failed." << std::endl; // } // // labelValue += tickLabelsStep; // } //} /** * At times a copy of chart data will be needed BUT it must be * the proper subclass so copy constructor and assignment operator * will no function when this abstract, base class is used. Each * subclass will override this method so that the returned class * is of the proper type. * * @return Copy of this instance that is the actual subclass. */ ChartAxis* ChartAxisCartesian::clone() const { ChartAxisCartesian* cloneCopy = new ChartAxisCartesian(*this); return cloneCopy; } /** * Save subclass data to the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass to which data members should be added. Will always * be valid (non-NULL). */ void ChartAxisCartesian::saveSubClassDataToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass) { m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); } /** * Restore file data from the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass for the instance of a class that implements * this interface. Will NEVER be NULL. */ void ChartAxisCartesian::restoreSubClassDataFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); } workbench-1.1.1/src/Charting/ChartAxisCartesian.h000066400000000000000000000060621255417355300217310ustar00rootroot00000000000000#ifndef __CHART_AXIS_CARTESIAN_H__ #define __CHART_AXIS_CARTESIAN_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "ChartAxis.h" namespace caret { class ChartModelCartesian; class ChartAxisCartesian : public ChartAxis { public: virtual ~ChartAxisCartesian(); ChartAxisCartesian(const ChartAxisCartesian& obj); ChartAxisCartesian& operator=(const ChartAxisCartesian& obj); virtual ChartAxis* clone() const; float getMinimumValue() const; void setMinimumValue(const float minimumValue); float getMaximumValue() const; void setMaximumValue(const float maximumValue); void getLabelsAndPositions(const float axisLengthInPixels, const float fontSizeInPixels, std::vector& labelOffsetInPixelsOut, std::vector& labelTextOut); // ADD_NEW_METHODS_HERE protected: ChartAxisCartesian(const ChartAxisLocationEnum::Enum axisLocation); virtual void updateForAutoRangeScale(); virtual void saveSubClassDataToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass); virtual void restoreSubClassDataFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private: void copyHelperChartAxisCartesian(const ChartAxisCartesian& obj); void initializeMembersChartAxisCartesian(); SceneClassAssistant* m_sceneAssistant; mutable float m_maximumValue; mutable float m_minimumValue; mutable int32_t m_digitsRightOfDecimal; mutable float m_axisLabelsMinimumValue; mutable float m_axisLabelsMaximumValue; mutable float m_axisLabelsStepValue; // ADD_NEW_MEMBERS_HERE friend class ChartAxis; }; #ifdef __CHART_AXIS_CARTESIAN_DECLARE__ // #endif // __CHART_AXIS_CARTESIAN_DECLARE__ } // namespace #endif //__CHART_AXIS_CARTESIAN_H__ workbench-1.1.1/src/Charting/ChartAxisLocationEnum.cxx000066400000000000000000000255461255417355300230000ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __CHART_AXIS_LOCATION_ENUM_DECLARE__ #include "ChartAxisLocationEnum.h" #undef __CHART_AXIS_LOCATION_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::ChartAxisLocationEnum * \brief Location of a chart axis. * * Using this enumerated type in the GUI with an EnumComboBoxTemplate * * Header File (.h) * Forward declare the data type: * class EnumComboBoxTemplate; * * Declare the member: * EnumComboBoxTemplate* m_chartAxisLocationEnumComboBox; * * Declare a slot that is called when user changes selection * private slots: * void chartAxisLocationEnumComboBoxItemActivated(); * * Implementation File (.cxx) * Include the header files * #include "EnumComboBoxTemplate.h" * #include "ChartAxisLocationEnum.h" * * Instatiate: * m_chartAxisLocationEnumComboBox = new EnumComboBoxTemplate(this); * m_chartAxisLocationEnumComboBox->setup(); * * Get notified when the user changes the selection: * QObject::connect(m_chartAxisLocationEnumComboBox, SIGNAL(itemActivated()), * this, SLOT(chartAxisLocationEnumComboBoxItemActivated())); * * Update the selection: * m_chartAxisLocationEnumComboBox->setSelectedItem(NEW_VALUE); * * Read the selection: * const ChartAxisLocationEnum::Enum VARIABLE = m_chartAxisLocationEnumComboBox->getSelectedItem(); * */ /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ ChartAxisLocationEnum::ChartAxisLocationEnum(const Enum enumValue, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCodeCounter++; this->name = name; this->guiName = guiName; } /** * Destructor. */ ChartAxisLocationEnum::~ChartAxisLocationEnum() { } /** * Initialize the enumerated metadata. */ void ChartAxisLocationEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(ChartAxisLocationEnum(CHART_AXIS_LOCATION_BOTTOM, "CHART_AXIS_LOCATION_BOTTOM", "Bottom")); enumData.push_back(ChartAxisLocationEnum(CHART_AXIS_LOCATION_LEFT, "CHART_AXIS_LOCATION_LEFT", "Left")); enumData.push_back(ChartAxisLocationEnum(CHART_AXIS_LOCATION_RIGHT, "CHART_AXIS_LOCATION_RIGHT", "Right")); enumData.push_back(ChartAxisLocationEnum(CHART_AXIS_LOCATION_TOP, "CHART_AXIS_LOCATION_TOP", "Top")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const ChartAxisLocationEnum* ChartAxisLocationEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const ChartAxisLocationEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString ChartAxisLocationEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const ChartAxisLocationEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ ChartAxisLocationEnum::Enum ChartAxisLocationEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = ChartAxisLocationEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ChartAxisLocationEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type ChartAxisLocationEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString ChartAxisLocationEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const ChartAxisLocationEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ ChartAxisLocationEnum::Enum ChartAxisLocationEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = ChartAxisLocationEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ChartAxisLocationEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type ChartAxisLocationEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t ChartAxisLocationEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const ChartAxisLocationEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ ChartAxisLocationEnum::Enum ChartAxisLocationEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = ChartAxisLocationEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ChartAxisLocationEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type ChartAxisLocationEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void ChartAxisLocationEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void ChartAxisLocationEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(ChartAxisLocationEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void ChartAxisLocationEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(ChartAxisLocationEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } workbench-1.1.1/src/Charting/ChartAxisLocationEnum.h000066400000000000000000000063631255417355300224210ustar00rootroot00000000000000#ifndef __CHART_AXIS_LOCATION_ENUM_H__ #define __CHART_AXIS_LOCATION_ENUM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class ChartAxisLocationEnum { public: /** * Enumerated values. */ enum Enum { /** Axis at bottom */ CHART_AXIS_LOCATION_BOTTOM, /** Axis at left */ CHART_AXIS_LOCATION_LEFT, /** Axis at right */ CHART_AXIS_LOCATION_RIGHT, /** Axis at top */ CHART_AXIS_LOCATION_TOP }; ~ChartAxisLocationEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); private: ChartAxisLocationEnum(const Enum enumValue, const AString& name, const AString& guiName); static const ChartAxisLocationEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; #ifdef __CHART_AXIS_LOCATION_ENUM_DECLARE__ std::vector ChartAxisLocationEnum::enumData; bool ChartAxisLocationEnum::initializedFlag = false; int32_t ChartAxisLocationEnum::integerCodeCounter = 0; #endif // __CHART_AXIS_LOCATION_ENUM_DECLARE__ } // namespace #endif //__CHART_AXIS_LOCATION_ENUM_H__ workbench-1.1.1/src/Charting/ChartAxisTypeEnum.cxx000066400000000000000000000243621255417355300221440ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __CHART_AXIS_TYPE_ENUM_DECLARE__ #include "ChartAxisTypeEnum.h" #undef __CHART_AXIS_TYPE_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::ChartAxisTypeEnum * \brief Type for a chart axis. * * Using this enumerated type in the GUI with an EnumComboBoxTemplate * * Header File (.h) * Forward declare the data type: * class EnumComboBoxTemplate; * * Declare the member: * EnumComboBoxTemplate* m_chartAxisTypeEnumComboBox; * * Declare a slot that is called when user changes selection * private slots: * void chartAxisTypeEnumComboBoxItemActivated(); * * Implementation File (.cxx) * Include the header files * #include "EnumComboBoxTemplate.h" * #include "ChartAxisTypeEnum.h" * * Instatiate: * m_chartAxisTypeEnumComboBox = new EnumComboBoxTemplate(this); * m_chartAxisTypeEnumComboBox->setup(); * * Get notified when the user changes the selection: * QObject::connect(m_chartAxisTypeEnumComboBox, SIGNAL(itemActivated()), * this, SLOT(chartAxisTypeEnumComboBoxItemActivated())); * * Update the selection: * m_chartAxisTypeEnumComboBox->setSelectedItem(NEW_VALUE); * * Read the selection: * const ChartAxisTypeEnum::Enum VARIABLE = m_chartAxisTypeEnumComboBox->getSelectedItem(); * */ /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ ChartAxisTypeEnum::ChartAxisTypeEnum(const Enum enumValue, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCodeCounter++; this->name = name; this->guiName = guiName; } /** * Destructor. */ ChartAxisTypeEnum::~ChartAxisTypeEnum() { } /** * Initialize the enumerated metadata. */ void ChartAxisTypeEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(ChartAxisTypeEnum(CHART_AXIS_TYPE_NONE, "CHART_AXIS_TYPE_NONE", "None Axis")); enumData.push_back(ChartAxisTypeEnum(CHART_AXIS_TYPE_CARTESIAN, "CHART_AXIS_TYPE_CARTESIAN", "Cartesian Axis")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const ChartAxisTypeEnum* ChartAxisTypeEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const ChartAxisTypeEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString ChartAxisTypeEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const ChartAxisTypeEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ ChartAxisTypeEnum::Enum ChartAxisTypeEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = ChartAxisTypeEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ChartAxisTypeEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type ChartAxisTypeEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString ChartAxisTypeEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const ChartAxisTypeEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ ChartAxisTypeEnum::Enum ChartAxisTypeEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = ChartAxisTypeEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ChartAxisTypeEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type ChartAxisTypeEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t ChartAxisTypeEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const ChartAxisTypeEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ ChartAxisTypeEnum::Enum ChartAxisTypeEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = ChartAxisTypeEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ChartAxisTypeEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type ChartAxisTypeEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void ChartAxisTypeEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void ChartAxisTypeEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(ChartAxisTypeEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void ChartAxisTypeEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(ChartAxisTypeEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } workbench-1.1.1/src/Charting/ChartAxisTypeEnum.h000066400000000000000000000060651255417355300215710ustar00rootroot00000000000000#ifndef __CHART_AXIS_TYPE_ENUM_H__ #define __CHART_AXIS_TYPE_ENUM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class ChartAxisTypeEnum { public: /** * Enumerated values. */ enum Enum { /** No Type */ CHART_AXIS_TYPE_NONE, /** Cartesian Data */ CHART_AXIS_TYPE_CARTESIAN }; ~ChartAxisTypeEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); private: ChartAxisTypeEnum(const Enum enumValue, const AString& name, const AString& guiName); static const ChartAxisTypeEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; #ifdef __CHART_AXIS_TYPE_ENUM_DECLARE__ std::vector ChartAxisTypeEnum::enumData; bool ChartAxisTypeEnum::initializedFlag = false; int32_t ChartAxisTypeEnum::integerCodeCounter = 0; #endif // __CHART_AXIS_TYPE_ENUM_DECLARE__ } // namespace #endif //__CHART_AXIS_TYPE_ENUM_H__ workbench-1.1.1/src/Charting/ChartAxisUnitsEnum.cxx000066400000000000000000000250411255417355300223200ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __CHART_AXIS_UNITS_ENUM_DECLARE__ #include "ChartAxisUnitsEnum.h" #undef __CHART_AXIS_UNITS_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::ChartAxisUnitsEnum * \brief Units for chart axes * * Using this enumerated type in the GUI with an EnumComboBoxTemplate * * Header File (.h) * Forward declare the data type: * class EnumComboBoxTemplate; * * Declare the member: * EnumComboBoxTemplate* m_chartDataAxisUnitsEnumComboBox; * * Declare a slot that is called when user changes selection * private slots: * void chartDataAxisUnitsEnumComboBoxItemActivated(); * * Implementation File (.cxx) * Include the header files * #include "EnumComboBoxTemplate.h" * #include "ChartAxisUnitsEnum.h" * * Instatiate: * m_chartDataAxisUnitsEnumComboBox = new EnumComboBoxTemplate(this); * m_chartDataAxisUnitsEnumComboBox->setup(); * * Get notified when the user changes the selection: * QObject::connect(m_chartDataAxisUnitsEnumComboBox, SIGNAL(itemActivated()), * this, SLOT(chartDataAxisUnitsEnumComboBoxItemActivated())); * * Update the selection: * m_chartDataAxisUnitsEnumComboBox->setSelectedItem(NEW_VALUE); * * Read the selection: * const ChartAxisUnitsEnum::Enum VARIABLE = m_chartDataAxisUnitsEnumComboBox->getSelectedItem(); * */ /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ ChartAxisUnitsEnum::ChartAxisUnitsEnum(const Enum enumValue, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCodeCounter++; this->name = name; this->guiName = guiName; } /** * Destructor. */ ChartAxisUnitsEnum::~ChartAxisUnitsEnum() { } /** * Initialize the enumerated metadata. */ void ChartAxisUnitsEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(ChartAxisUnitsEnum(CHART_AXIS_UNITS_NONE, "CHART_AXIS_UNITS_NONE", "None")); enumData.push_back(ChartAxisUnitsEnum(CHART_AXIS_UNITS_FREQUENCY_HERTZ, "CHART_AXIS_UNITS_FREQUENCY_HERTZ", "Frequency")); enumData.push_back(ChartAxisUnitsEnum(CHART_AXIS_UNITS_TIME_SECONDS, "CHART_AXIS_UNITS_TIME_SECONDS", "Time")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const ChartAxisUnitsEnum* ChartAxisUnitsEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const ChartAxisUnitsEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString ChartAxisUnitsEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const ChartAxisUnitsEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ ChartAxisUnitsEnum::Enum ChartAxisUnitsEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = ChartAxisUnitsEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ChartAxisUnitsEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type ChartAxisUnitsEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString ChartAxisUnitsEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const ChartAxisUnitsEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ ChartAxisUnitsEnum::Enum ChartAxisUnitsEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = ChartAxisUnitsEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ChartAxisUnitsEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type ChartAxisUnitsEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t ChartAxisUnitsEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const ChartAxisUnitsEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ ChartAxisUnitsEnum::Enum ChartAxisUnitsEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = ChartAxisUnitsEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ChartAxisUnitsEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type ChartAxisUnitsEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void ChartAxisUnitsEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void ChartAxisUnitsEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(ChartAxisUnitsEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void ChartAxisUnitsEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(ChartAxisUnitsEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } workbench-1.1.1/src/Charting/ChartAxisUnitsEnum.h000066400000000000000000000062161255417355300217500ustar00rootroot00000000000000#ifndef __CHART_AXIS_UNITS_ENUM_H__ #define __CHART_AXIS_UNITS_ENUM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class ChartAxisUnitsEnum { public: /** * Enumerated values. */ enum Enum { /** No units */ CHART_AXIS_UNITS_NONE, /** Frequency units */ CHART_AXIS_UNITS_FREQUENCY_HERTZ, /** Time units */ CHART_AXIS_UNITS_TIME_SECONDS }; ~ChartAxisUnitsEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); private: ChartAxisUnitsEnum(const Enum enumValue, const AString& name, const AString& guiName); static const ChartAxisUnitsEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; #ifdef __CHART_AXIS_UNITS_ENUM_DECLARE__ std::vector ChartAxisUnitsEnum::enumData; bool ChartAxisUnitsEnum::initializedFlag = false; int32_t ChartAxisUnitsEnum::integerCodeCounter = 0; #endif // __CHART_AXIS_UNITS_ENUM_DECLARE__ } // namespace #endif //__CHART_AXIS_UNITS_ENUM_H__ workbench-1.1.1/src/Charting/ChartData.cxx000066400000000000000000000205171255417355300204200ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CHART_DATA_DECLARE__ #include "ChartData.h" #undef __CHART_DATA_DECLARE__ #include "CaretAssert.h" #include "ChartDataCartesian.h" #include "ChartDataSource.h" #include "SceneClass.h" #include "SceneClassAssistant.h" #include "SystemUtilities.h" using namespace caret; /** * \class caret::ChartData * \brief Base class for chart data. * \ingroup Charting */ /** * Constructor. * * @param chartDataType * Type of chart model. */ ChartData::ChartData(const ChartDataTypeEnum::Enum chartDataType) : CaretObject(), SceneableInterface(), m_chartDataType(chartDataType) { initializeMembersChartData(); } /** * Destructor. */ ChartData::~ChartData() { delete m_sceneAssistant; delete m_chartDataSource; } /** * Initialize members of a new instance. */ void ChartData::initializeMembersChartData() { m_chartDataSource = new ChartDataSource(); for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_selectionStatus[i] = true; } m_uniqueIdentifier = SystemUtilities::createUniqueID(); m_sceneAssistant = new SceneClassAssistant(); m_sceneAssistant->add("m_chartDataSource", "ChartDataSource", m_chartDataSource); m_sceneAssistant->addTabIndexedBooleanArray("m_selectionStatus", m_selectionStatus); m_sceneAssistant->add("m_uniqueIdentifier", &m_uniqueIdentifier); } /** * Copy constructor. * @param obj * Object that is copied. */ ChartData::ChartData(const ChartData& obj) : CaretObject(obj), SceneableInterface(obj), m_chartDataType(obj.m_chartDataType) { initializeMembersChartData(); this->copyHelperChartData(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ ChartData& ChartData::operator=(const ChartData& obj) { if (this != &obj) { CaretObject::operator=(obj); this->copyHelperChartData(obj); } return *this; } /** * Helps with copying an object of this type. * @param obj * Object that is copied. */ void ChartData::copyHelperChartData(const ChartData& obj) { CaretAssert(0); m_chartDataType = obj.m_chartDataType; *m_chartDataSource = *obj.m_chartDataSource; for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_selectionStatus[i] = obj.m_selectionStatus[i]; } } /** * Create a new instance of a chart for the given data type. * * @param chartDataType * Type of chart data. * @return * Pointer to new instance. */ ChartData* ChartData::newChartDataForChartDataType(const ChartDataTypeEnum::Enum chartDataType) { ChartData* chartData = NULL; switch (chartDataType) { case ChartDataTypeEnum::CHART_DATA_TYPE_INVALID: CaretAssert(0); break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_LAYER: CaretAssert(0); break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_SERIES: break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_DATA_SERIES: chartData = new ChartDataCartesian(chartDataType, ChartAxisUnitsEnum::CHART_AXIS_UNITS_NONE, ChartAxisUnitsEnum::CHART_AXIS_UNITS_NONE); break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_FREQUENCY_SERIES: chartData = new ChartDataCartesian(chartDataType, ChartAxisUnitsEnum::CHART_AXIS_UNITS_NONE, ChartAxisUnitsEnum::CHART_AXIS_UNITS_TIME_SECONDS); break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_TIME_SERIES: chartData = new ChartDataCartesian(chartDataType, ChartAxisUnitsEnum::CHART_AXIS_UNITS_NONE, ChartAxisUnitsEnum::CHART_AXIS_UNITS_TIME_SECONDS); break; } return chartData; } /** * Save information specific to this type of model to the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @param instanceName * Name of instance in the scene. */ SceneClass* ChartData::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "ChartData", 1); m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); saveSubClassDataToScene(sceneAttributes, sceneClass); return sceneClass; } /** * Restore information specific to the type of model from the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass from which model specific information is obtained. */ void ChartData::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); restoreSubClassDataFromScene(sceneAttributes, sceneClass); } /** * @return The chart data model type. */ ChartDataTypeEnum::Enum ChartData::getChartDataType() const { return m_chartDataType; } /** * @return The source of the chart data (const method). */ const ChartDataSource* ChartData::getChartDataSource() const { return m_chartDataSource; } /** * @return The source of the chart data. */ ChartDataSource* ChartData::getChartDataSource() { return m_chartDataSource; } /** * @return The selection status in the given tab * @param tabIndex * Index of the tab. */ bool ChartData::isSelected(const int32_t tabIndex) const { CaretAssertArrayIndex(m_selectionStatus, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_selectionStatus[tabIndex]; } /** * Set the selection status. * * @param selectionStatus * New selection status. */ void ChartData::setSelected(const int32_t tabIndex, const bool selectionStatus) { CaretAssertArrayIndex(m_selectionStatus, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_selectionStatus[tabIndex] = selectionStatus; /* * When selection status is true, * notify parent. */ // if (m_selectionStatus[tabIndex]) { // if (m_parentChartModel != NULL) { // m_parentChartModel->childChartDataSelectionChanged(this); // } // } } /** * @return The Unique Identifier (UUID). */ AString ChartData::getUniqueIdentifier() const { return m_uniqueIdentifier; } /** * Set the unique identifier. * * @param uniqueIdentifier * The new unique identifier. */ void ChartData::setUniqueIdentifier(const AString& uniqueIdentifier) { m_uniqueIdentifier = uniqueIdentifier; } /** * Get a description of this object's content. * @return String describing this object's content. */ AString ChartData::toString() const { return "ChartData"; } workbench-1.1.1/src/Charting/ChartData.h000066400000000000000000000115121255417355300200400ustar00rootroot00000000000000#ifndef __CHART_DATA_H__ #define __CHART_DATA_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "BrainConstants.h" #include "CaretObject.h" #include "ChartDataTypeEnum.h" #include "SceneableInterface.h" namespace caret { class ChartDataSource; class SceneClassAssistant; class ChartData : public CaretObject, public SceneableInterface { public: static ChartData* newChartDataForChartDataType(const ChartDataTypeEnum::Enum chartDataType); virtual ~ChartData(); /** * At times a copy of chart data will be needed BUT it must be * the proper subclass so copy constructor and assignment operator * will function when this abstract, base class is used. Each * subclass will override this method so that the returned class * is of the proper type. * * @return Copy of this instance that is the actual subclass. */ virtual ChartData* clone() const = 0; ChartDataTypeEnum::Enum getChartDataType() const; const ChartDataSource* getChartDataSource() const; ChartDataSource* getChartDataSource(); bool isSelected(const int32_t tabIndex) const; void setSelected(const int32_t tabIndex, const bool selectionStatus); void clearSelected(); AString getUniqueIdentifier() const; void setUniqueIdentifier(const AString& uniqueIdentifier); virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); // ADD_NEW_METHODS_HERE virtual AString toString() const; protected: ChartData(const ChartDataTypeEnum::Enum chartDataType); ChartData(const ChartData& obj); ChartData& operator=(const ChartData& obj); /** * Save subclass data to the scene. sceneClass * will be valid and any scene data should be added to it. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass to which data members should be added. */ virtual void saveSubClassDataToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass) = 0; /** * Restore file data from the scene. The scene class * will be valid and any scene data may be obtained from it. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass for the instance of a class that implements * this interface. Will NEVER be NULL. */ virtual void restoreSubClassDataFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) = 0; private: void initializeMembersChartData(); void copyHelperChartData(const ChartData& obj); SceneClassAssistant* m_sceneAssistant; ChartDataTypeEnum::Enum m_chartDataType; ChartDataSource* m_chartDataSource; bool m_selectionStatus[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; AString m_uniqueIdentifier; // ADD_NEW_MEMBERS_HERE }; #ifdef __CHART_DATA_DECLARE__ // #endif // __CHART_DATA_DECLARE__ } // namespace #endif //__CHART_DATA_H__ workbench-1.1.1/src/Charting/ChartDataCartesian.cxx000066400000000000000000000325311255417355300222510ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CHART_DATA_CARTESIAN_DECLARE__ #include "ChartDataCartesian.h" #undef __CHART_DATA_CARTESIAN_DECLARE__ #include #include #include "CaretAssert.h" #include "ChartPoint.h" #include "SceneClass.h" #include "SceneClassAssistant.h" using namespace caret; /** * \class caret::ChartDataCartesian * \brief Chart cartesian data. * \ingroup Charting */ /** * Constructor. * * @param chartDataType * Type of chart data model. * @param dataAxisUnitsX * Data units for X-axis. * @param dataAxisUnitsY * Data units for Y-axis. */ ChartDataCartesian::ChartDataCartesian(const ChartDataTypeEnum::Enum chartDataType, const ChartAxisUnitsEnum::Enum dataAxisUnitsX, const ChartAxisUnitsEnum::Enum dataAxisUnitsY) : ChartData(chartDataType), m_dataAxisUnitsX(dataAxisUnitsX), m_dataAxisUnitsY(dataAxisUnitsY) { initializeMembersChartDataCartesian(); } /** * Destructor. */ ChartDataCartesian::~ChartDataCartesian() { removeAllPoints(); delete m_sceneAssistant; } /** * Initialize members of a new instance. */ void ChartDataCartesian::initializeMembersChartDataCartesian() { m_boundsValid = false; m_color = CaretColorEnum::RED; m_timeStartInSecondsAxisX = 0.0; m_timeStepInSecondsAxisX = 1.0; std::vector colorEnums; CaretColorEnum::getAllEnums(colorEnums); const int32_t numCaretColors = static_cast(colorEnums.size()); bool colorFound = false; while ( ! colorFound) { ChartDataCartesian::caretColorIndex++; if (ChartDataCartesian::caretColorIndex >= numCaretColors) { ChartDataCartesian::caretColorIndex = 0; } if (colorEnums[ChartDataCartesian::caretColorIndex] == CaretColorEnum::BLACK) { /* do not use black */ } else if (colorEnums[ChartDataCartesian::caretColorIndex] == CaretColorEnum::WHITE) { /* do not use white */ } else { m_color = colorEnums[ChartDataCartesian::caretColorIndex]; colorFound = true; } } m_sceneAssistant = new SceneClassAssistant(); m_sceneAssistant->add("m_dataAxisUnitsX", &m_dataAxisUnitsX); m_sceneAssistant->add("m_dataAxisUnitsY", &m_dataAxisUnitsY); m_sceneAssistant->add("m_color", &m_color); m_sceneAssistant->add("m_timeStartInSecondsAxisX", &m_timeStartInSecondsAxisX); m_sceneAssistant->add("m_timeStepInSecondsAxisX", &m_timeStepInSecondsAxisX); } /** * Remove all points in the model. */ void ChartDataCartesian::removeAllPoints() { for (std::vector::const_iterator iter = m_points.begin(); iter != m_points.end(); iter++) { delete *iter; } m_points.clear(); m_boundsValid = false; } /** * At times a copy of chart data will be needed BUT it must be * the proper subclass so copy constructor and assignment operator * will no function when this abstract, base class is used. Each * subclass will override this method so that the returned class * is of the proper type. * * @return Copy of this instance that is the actual subclass. */ ChartData* ChartDataCartesian::clone() const { ChartDataCartesian* cloneCopy = new ChartDataCartesian(*this); return cloneCopy; } /** * Copy constructor. * @param obj * Object that is copied. */ ChartDataCartesian::ChartDataCartesian(const ChartDataCartesian& obj) : ChartData(obj), m_dataAxisUnitsX(obj.m_dataAxisUnitsX), m_dataAxisUnitsY(obj.m_dataAxisUnitsY) { initializeMembersChartDataCartesian(); this->copyHelperChartDataCartesian(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ ChartDataCartesian& ChartDataCartesian::operator=(const ChartDataCartesian& obj) { if (this != &obj) { ChartData::operator=(obj); this->copyHelperChartDataCartesian(obj); } return *this; } /** * Helps with copying an object of this type. * @param obj * Object that is copied. */ void ChartDataCartesian::copyHelperChartDataCartesian(const ChartDataCartesian& obj) { CaretAssert(0); m_dataAxisUnitsX = obj.m_dataAxisUnitsX; m_dataAxisUnitsY = obj.m_dataAxisUnitsY; removeAllPoints(); for (std::vector::const_iterator iter = obj.m_points.begin(); iter != obj.m_points.end(); iter++) { const ChartPoint* cp = *iter; m_points.push_back(new ChartPoint(*cp)); } m_boundsValid = false; m_color = obj.m_color; m_timeStartInSecondsAxisX = obj.m_timeStartInSecondsAxisX; m_timeStepInSecondsAxisX = obj.m_timeStepInSecondsAxisX; } /** * Add a point. * * @param x * X-coordinate. * @param y * Y-coordinate. */ void ChartDataCartesian::addPoint(const float x, const float y) { m_points.push_back(new ChartPoint(x, y)); m_boundsValid = false; } /** * @return Number of points. */ int32_t ChartDataCartesian::getNumberOfPoints() const { return m_points.size(); } /** * Get the point at the given index. * * @param pointIndex * Index of point. * @return * Point at the given index. */ const ChartPoint* ChartDataCartesian::getPointAtIndex(const int32_t pointIndex) const { CaretAssertVectorIndex(m_points, pointIndex); return m_points[pointIndex]; } /** * Get the bounds of all of the points. * * @param xMinimumOut * Minimum X-coordinate of all points. * @param xMaximumOut * Maximum X-coordinate of all points. * @param yMinimumOut * Minimum Y-coordinate of all points. * @param yMaximumOut * Maximum Y-coordinate of all points. */ void ChartDataCartesian::getBounds(float& xMinimumOut, float& xMaximumOut, float& yMinimumOut, float& yMaximumOut) const { if (! m_boundsValid) { float xMin = 0.0; float xMax = 0.0; float yMin = 0.0; float yMax = 0.0; float zMin = 0.0; float zMax = 0.0; const int32_t numPoints = getNumberOfPoints(); if (numPoints > 0) { xMin = std::numeric_limits::max(); xMax = -std::numeric_limits::max(); yMin = std::numeric_limits::max(); yMax = -std::numeric_limits::max(); for (int32_t i = 0; i < numPoints; i++) { const float* xy = getPointAtIndex(i)->getXY(); const float x = xy[0]; const float y = xy[1]; if (x < xMin) xMin = x; if (x > xMax) xMax = x; if (y < yMin) yMin = y; if (y > yMax) yMax = y; } m_boundsValid = true; } m_bounds[0] = xMin; m_bounds[1] = xMax; m_bounds[2] = yMin; m_bounds[3] = yMax; m_bounds[4] = zMin; m_bounds[5] = zMax; } xMinimumOut = m_bounds[0]; xMaximumOut = m_bounds[1]; yMinimumOut = m_bounds[2]; yMaximumOut = m_bounds[3]; } /** * @return The time start in seconds for the X-Axis (Valid when * the X-axis is time) */ float ChartDataCartesian::getTimeStartInSecondsAxisX() const { return m_timeStartInSecondsAxisX; } /** * Set the time start in seconds for the X-Axis (Valid when * the X-axis is time) * * @param timeStartInSecondsAxisX * Time of first point in the X-axis. */ void ChartDataCartesian::setTimeStartInSecondsAxisX(const float timeStartInSecondsAxisX) { m_timeStartInSecondsAxisX = timeStartInSecondsAxisX; } /** * @return The time step in seconds for the X-Axis (Valid when * the X-axis is time) */ float ChartDataCartesian::getTimeStepInSecondsAxisX() const { return m_timeStepInSecondsAxisX; } /** * Set the time step in seconds for the X-Axis (Valid when * the X-axis is time) * * @param timeStepInSecondsAxisX * Number of seconds between consecutive points in X-axis. */ void ChartDataCartesian::setTimeStepInSecondsAxisX(const float timeStepInSecondsAxisX) { m_timeStepInSecondsAxisX = timeStepInSecondsAxisX; } /** * @return Data units for X axis */ ChartAxisUnitsEnum::Enum ChartDataCartesian::getDataAxisUnitsX() { return m_dataAxisUnitsX; } /** * @return Data units for Y axis */ ChartAxisUnitsEnum::Enum ChartDataCartesian::getDataAxisUnitsY() { return m_dataAxisUnitsY; } /** * @return Color for chart */ CaretColorEnum::Enum ChartDataCartesian::getColor() const { return m_color; } /** * Set the color for the chart. * * @param color * New color for chart. */ void ChartDataCartesian::setColor(const CaretColorEnum::Enum color) { m_color = color; } /** * Save subclass data to the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass to which data members should be added. Will always * be valid (non-NULL). */ void ChartDataCartesian::saveSubClassDataToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass) { SceneClass* chartDataCartesian = new SceneClass("chartDataCartesian", "ChartDataCartesian", 1); m_sceneAssistant->saveMembers(sceneAttributes, chartDataCartesian); const int32_t numPoints2D = getNumberOfPoints(); if (numPoints2D > 0) { chartDataCartesian->addInteger("numberOfPoints2D", numPoints2D); AString pointString; pointString.reserve(numPoints2D * 2 * 10); QTextStream textStream(&pointString, QIODevice::WriteOnly); for (int32_t i = 0; i < numPoints2D; i++) { const float* xy = m_points[i]->getXY(); textStream << xy[0] << " " << xy[1] << " "; } chartDataCartesian->addString("points2D", pointString); } sceneClass->addClass(chartDataCartesian); } /** * Restore file data from the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass for the instance of a class that implements * this interface. Will NEVER be NULL. */ void ChartDataCartesian::restoreSubClassDataFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { removeAllPoints(); const SceneClass* chartDataCartesian = sceneClass->getClass("chartDataCartesian"); if (chartDataCartesian == NULL) { return; } m_sceneAssistant->restoreMembers(sceneAttributes, chartDataCartesian); const int32_t numPoints2D = chartDataCartesian->getIntegerValue("numberOfPoints2D", -1); if (numPoints2D > 0) { AString pointString = chartDataCartesian->getStringValue("points2D", ""); if ( ! pointString.isEmpty()) { float x, y; QTextStream textStream(&pointString, QIODevice::ReadOnly); for (int32_t i = 0; i < numPoints2D; i++) { if (textStream.atEnd()) { sceneAttributes->addToErrorMessage("Tried to read " + AString::number(numPoints2D) + " but only got " + AString::number(i)); break; } textStream >> x; textStream >> y; m_points.push_back(new ChartPoint(x, y)); } } } } workbench-1.1.1/src/Charting/ChartDataCartesian.h000066400000000000000000000074051255417355300217000ustar00rootroot00000000000000#ifndef __CHART_DATA_CARTESIAN_H__ #define __CHART_DATA_CARTESIAN_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretColorEnum.h" #include "ChartAxisUnitsEnum.h" #include "ChartData.h" namespace caret { class ChartPoint; class ChartDataCartesian : public ChartData { public: ChartDataCartesian(const ChartDataTypeEnum::Enum chartDataType, const ChartAxisUnitsEnum::Enum dataAxisUnitsX, const ChartAxisUnitsEnum::Enum dataAxisUnitsY); virtual ~ChartDataCartesian(); virtual ChartData* clone() const; void addPoint(const float x, const float y); int32_t getNumberOfPoints() const; const ChartPoint* getPointAtIndex(const int32_t pointIndex) const; void getBounds(float& xMinimumOut, float& xMaximumOut, float& yMinimumOut, float& yMaximumOut) const; ChartAxisUnitsEnum::Enum getDataAxisUnitsX(); ChartAxisUnitsEnum::Enum getDataAxisUnitsY(); CaretColorEnum::Enum getColor() const; void setColor(const CaretColorEnum::Enum color); float getTimeStartInSecondsAxisX() const; void setTimeStartInSecondsAxisX(const float timeStart); float getTimeStepInSecondsAxisX() const; void setTimeStepInSecondsAxisX(const float timeStep); // ADD_NEW_METHODS_HERE protected: virtual void saveSubClassDataToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass); virtual void restoreSubClassDataFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private: ChartDataCartesian(const ChartDataCartesian& obj); ChartDataCartesian& operator=(const ChartDataCartesian& obj); void copyHelperChartDataCartesian(const ChartDataCartesian& obj); void initializeMembersChartDataCartesian(); void removeAllPoints(); std::vector m_points; mutable float m_bounds[6]; mutable bool m_boundsValid; ChartAxisUnitsEnum::Enum m_dataAxisUnitsX; ChartAxisUnitsEnum::Enum m_dataAxisUnitsY; CaretColorEnum::Enum m_color; float m_timeStartInSecondsAxisX; float m_timeStepInSecondsAxisX; static int32_t caretColorIndex; SceneClassAssistant* m_sceneAssistant; // ADD_NEW_MEMBERS_HERE }; #ifdef __CHART_DATA_CARTESIAN_DECLARE__ int32_t ChartDataCartesian::caretColorIndex = 0; #endif // __CHART_DATA_CARTESIAN_DECLARE__ } // namespace #endif //__CHART_DATA_CARTESIAN_H__ workbench-1.1.1/src/Charting/ChartDataSource.cxx000066400000000000000000000346631255417355300216100ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #define __CHART_DATA_SOURCE_DECLARE__ #include "ChartDataSource.h" #undef __CHART_DATA_SOURCE_DECLARE__ #include "CaretAssert.h" #include "CaretLogger.h" #include "FileInformation.h" #include "SceneClass.h" #include "SceneClassArray.h" #include "SceneClassAssistant.h" using namespace caret; /** * \class caret::ChartDataSource * \brief Contains source of data that is displayed in a chart. * \ingroup Charting */ /** * Constructor. */ ChartDataSource::ChartDataSource() : CaretObject(), SceneableInterface() { initializeMembersChartDataSource(); } /** * Destructor. */ ChartDataSource::~ChartDataSource() { delete m_sceneAssistant; } /** * Initialize members of a new instance. */ void ChartDataSource::initializeMembersChartDataSource() { m_dataSourceMode = ChartDataSourceModeEnum::CHART_DATA_SOURCE_MODE_INVALID; m_nodeIndex = -1; m_voxelXYZ[0] = 0.0; m_voxelXYZ[1] = 0.0; m_voxelXYZ[2] = 0.0; m_fileRowIndex = -1; m_sceneAssistant = new SceneClassAssistant(); m_sceneAssistant->add("m_dataSourceMode", &m_dataSourceMode); m_sceneAssistant->add("m_nodeIndex", &m_nodeIndex); m_sceneAssistant->add("m_surfaceNumberOfNodes", &m_surfaceNumberOfNodes); m_sceneAssistant->add("m_surfaceStructureName", &m_surfaceStructureName); m_sceneAssistant->addArray("m_voxelXYZ", m_voxelXYZ, 3, -1); m_sceneAssistant->add("m_fileRowIndex", &m_fileRowIndex); } /** * Copy constructor. * @param obj * Object that is copied. */ ChartDataSource::ChartDataSource(const ChartDataSource& obj) : CaretObject(obj), SceneableInterface(obj) { initializeMembersChartDataSource(); this->copyHelperChartDataSource(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ ChartDataSource& ChartDataSource::operator=(const ChartDataSource& obj) { if (this != &obj) { CaretObject::operator=(obj); this->copyHelperChartDataSource(obj); } return *this; } /** * Copy the given data source to me. * * @param copyFrom * Chart data source that is copied to me. */ void ChartDataSource::copy(const ChartDataSource* copyFrom) { CaretAssert(copyFrom); copyHelperChartDataSource(*copyFrom); } /** * Helps with copying an object of this type. * @param obj * Object that is copied. */ void ChartDataSource::copyHelperChartDataSource(const ChartDataSource& obj) { m_dataSourceMode = obj.m_dataSourceMode; m_chartableFileName = obj.m_chartableFileName; m_nodeIndex = obj.m_nodeIndex; m_surfaceNumberOfNodes = obj.m_surfaceNumberOfNodes; m_surfaceStructureName = obj.m_surfaceStructureName; m_nodeIndicesAverage = obj.m_nodeIndicesAverage; m_voxelXYZ[0] = obj.m_voxelXYZ[0]; m_voxelXYZ[1] = obj.m_voxelXYZ[1]; m_voxelXYZ[2] = obj.m_voxelXYZ[2]; m_fileRowIndex = obj.m_fileRowIndex; } /** * @return Name of the chartable file. */ AString ChartDataSource::getChartableFileName() const { return m_chartableFileName; } /** * Setup for a surface node source. * * @param chartableFileName * Name of the chartable file. * @param surfaceStructureName * Name of surface structure. * @param surfaceNumberOfNodes * Number of nodes in the surface. * @param nodeIndex * Index of the surface node. */ void ChartDataSource::setSurfaceNode(const AString& chartableFileName, const AString& surfaceStructureName, const int32_t surfaceNumberOfNodes, const int32_t nodeIndex) { CaretAssert(nodeIndex >= 0); m_dataSourceMode = ChartDataSourceModeEnum::CHART_DATA_SOURCE_MODE_SURFACE_NODE_INDEX; m_chartableFileName = chartableFileName; m_surfaceNumberOfNodes = surfaceNumberOfNodes; m_surfaceStructureName = surfaceStructureName; m_nodeIndex = nodeIndex; } /** * Is the given node the source of the data? * * @param surfaceStructureName * Name of surface structure. * @param nodeIndex * Index of the surface node. * @return * True if node is source of data, else false. */ bool ChartDataSource::isSurfaceNodeSourceOfData(const AString& surfaceStructureName, const int32_t nodeIndex) const { if (m_dataSourceMode == ChartDataSourceModeEnum::CHART_DATA_SOURCE_MODE_SURFACE_NODE_INDEX) { if (m_nodeIndex == nodeIndex) { if (m_surfaceStructureName == surfaceStructureName) { return true; } } } return false; } /** * @return Mode indicating source of the data. */ ChartDataSourceModeEnum::Enum ChartDataSource::getDataSourceMode() const { return m_dataSourceMode; } /** * Get the surface node data source. * * @param surfaceStructureName * Name of surface structure. * @param surfaceNumberOfNodes * Number of nodes in the surface. * @param nodeIndex * Index of the surface node. */ void ChartDataSource::getSurfaceNode(AString& surfaceStructureName, int32_t& surfaceNumberOfNodes, int32_t& nodeIndex) const { surfaceStructureName = m_surfaceStructureName; surfaceNumberOfNodes = m_surfaceNumberOfNodes; nodeIndex = m_nodeIndex; } /** * Get the surface node average data source. * * @param surfaceStructureName * Name of surface structure. * @param surfaceNumberOfNodes * Number of nodes in the surface. * @param nodeIndices * Indices of the surface node. */ void ChartDataSource::getSurfaceNodeAverage(AString& surfaceStructureName, int32_t& surfaceNumberOfNodes, std::vector& nodeIndices) const { surfaceStructureName = m_surfaceStructureName; surfaceNumberOfNodes = m_surfaceNumberOfNodes; nodeIndices = m_nodeIndicesAverage; } /** * Get the surface node average data source. * * @param chartableFileName * Name of the chartable file. * @param surfaceStructureName * Name of surface structure. * @param surfaceNumberOfNodes * Number of nodes in the surface. * @param nodeIndices * Indices of the surface node. */ void ChartDataSource::setSurfaceNodeAverage(const AString& chartableFileName, const AString& surfaceStructureName, const int32_t surfaceNumberOfNodes, const std::vector& nodeIndices) { m_dataSourceMode = ChartDataSourceModeEnum::CHART_DATA_SOURCE_MODE_SURFACE_NODE_INDICES_AVERAGE; m_chartableFileName = chartableFileName; m_surfaceStructureName = surfaceStructureName; m_surfaceNumberOfNodes = surfaceNumberOfNodes; m_nodeIndicesAverage = nodeIndices; } /** * Get the volume voxel data source. * * @param ijk * Indices of the voxel. */ void ChartDataSource::getVolumeVoxel(float xyz[3]) const { xyz[0] = m_voxelXYZ[0]; xyz[1] = m_voxelXYZ[1]; xyz[2] = m_voxelXYZ[2]; } /** * Set the volume voxel data source. * * @param chartableFileName * Name of the chartable file. * @param ijk * Indices of the voxel. */ void ChartDataSource::setVolumeVoxel(const AString& chartableFileName, const float xyz[3]) { m_dataSourceMode = ChartDataSourceModeEnum::CHART_DATA_SOURCE_MODE_VOXEL_IJK; m_chartableFileName = chartableFileName; m_voxelXYZ[0] = xyz[0]; m_voxelXYZ[1] = xyz[1]; m_voxelXYZ[2] = xyz[2]; } /** * Get the file row data source. * * @param chartableFileName * Name of the file. * @param fileRowIndex * Index of the row. */ void ChartDataSource::getFileRow(AString& chartableFileName, int32_t& fileRowIndex) const { chartableFileName = m_chartableFileName; fileRowIndex = m_fileRowIndex; } /** * Set the file row data source. * * @param chartableFileName * Name of the file. * @param fileRowIndex * Index of the row. */ void ChartDataSource::setFileRow(const AString& chartableFileName, const int32_t fileRowIndex) { m_dataSourceMode = ChartDataSourceModeEnum::CHART_DATA_SOURCE_MODE_FILE_ROW; m_chartableFileName = chartableFileName; m_fileRowIndex = fileRowIndex; } /** * Get a description of this object's content. * @return String describing this object's content. */ AString ChartDataSource::getDescription() const { AString s; switch (m_dataSourceMode) { case ChartDataSourceModeEnum::CHART_DATA_SOURCE_MODE_INVALID: break; case ChartDataSourceModeEnum::CHART_DATA_SOURCE_MODE_FILE_ROW: s += (" Row " + AString::number(m_fileRowIndex + 1)); break; case ChartDataSourceModeEnum::CHART_DATA_SOURCE_MODE_SURFACE_NODE_INDEX: s += (m_surfaceStructureName + ": Vertex " + AString::number(m_nodeIndex)); break; case ChartDataSourceModeEnum::CHART_DATA_SOURCE_MODE_SURFACE_NODE_INDICES_AVERAGE: s += (m_surfaceStructureName + ": Average of " + AString::number(m_nodeIndicesAverage.size()) + " Vertices"); break; case ChartDataSourceModeEnum::CHART_DATA_SOURCE_MODE_VOXEL_IJK: s += ("Voxel XYZ (" + AString::fromNumbers(m_voxelXYZ, 3, ",") + ")"); break; } if ( ! s.isEmpty()) { FileInformation fileInfo(m_chartableFileName); s += (" from " + fileInfo.getFileName()); } return s; } /** * Get a description of this object's content. * @return String describing this object's content. */ AString ChartDataSource::toString() const { AString s = "ChartDataSource"; return s; } /** * Save information specific to this type of model to the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @param instanceName * Name of instance in the scene. */ SceneClass* ChartDataSource::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "ChartDataSource", 1); m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); sceneClass->addPathName("m_chartableFileName", m_chartableFileName); switch (m_dataSourceMode) { case ChartDataSourceModeEnum::CHART_DATA_SOURCE_MODE_INVALID: break; case ChartDataSourceModeEnum::CHART_DATA_SOURCE_MODE_FILE_ROW: break; case ChartDataSourceModeEnum::CHART_DATA_SOURCE_MODE_SURFACE_NODE_INDEX: break; case ChartDataSourceModeEnum::CHART_DATA_SOURCE_MODE_SURFACE_NODE_INDICES_AVERAGE: { const int32_t numNodes = static_cast(m_nodeIndicesAverage.size()); if (numNodes > 0) { sceneClass->addIntegerArray("m_nodeIndicesAverage", &m_nodeIndicesAverage[0], numNodes); } } break; case ChartDataSourceModeEnum::CHART_DATA_SOURCE_MODE_VOXEL_IJK: break; } return sceneClass; } /** * Restore information specific to the type of model from the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass from which model specific information is obtained. */ void ChartDataSource::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); m_chartableFileName = sceneClass->getPathNameValue("m_chartableFileName"); switch (m_dataSourceMode) { case ChartDataSourceModeEnum::CHART_DATA_SOURCE_MODE_INVALID: break; case ChartDataSourceModeEnum::CHART_DATA_SOURCE_MODE_FILE_ROW: break; case ChartDataSourceModeEnum::CHART_DATA_SOURCE_MODE_SURFACE_NODE_INDEX: break; case ChartDataSourceModeEnum::CHART_DATA_SOURCE_MODE_SURFACE_NODE_INDICES_AVERAGE: { const SceneClassArray* nodeArray = sceneClass->getClassArray("m_nodeIndicesAverage"); if (nodeArray != NULL) { const int32_t numNodes = nodeArray->getNumberOfArrayElements(); if (numNodes > 0) { m_nodeIndicesAverage.resize(numNodes); sceneClass->getIntegerArrayValue("m_nodeIndicesAverage", &m_nodeIndicesAverage[0], numNodes); } } } break; case ChartDataSourceModeEnum::CHART_DATA_SOURCE_MODE_VOXEL_IJK: break; } } workbench-1.1.1/src/Charting/ChartDataSource.h000066400000000000000000000104051255417355300212210ustar00rootroot00000000000000#ifndef __CHART_DATA_SOURCE_H__ #define __CHART_DATA_SOURCE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "ChartDataSourceModeEnum.h" #include "SceneableInterface.h" namespace caret { class SceneClassAssistant; class ChartDataSource : public CaretObject, public SceneableInterface { public: ChartDataSource(); virtual ~ChartDataSource(); ChartDataSource(const ChartDataSource&); ChartDataSource& operator=(const ChartDataSource&); void copy(const ChartDataSource* copyFrom); virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); ChartDataSourceModeEnum::Enum getDataSourceMode() const; AString getChartableFileName() const; void getSurfaceNode(AString& surfaceStructureName, int32_t& surfaceNumberOfNodes, int32_t& nodeIndex) const; void setSurfaceNode(const AString& chartableFileName, const AString& surfaceStructureName, const int32_t surfaceNumberOfNodes, const int32_t nodeIndex); bool isSurfaceNodeSourceOfData(const AString& surfaceStructureName, const int32_t nodeIndex) const; void getSurfaceNodeAverage(AString& surfaceStructureName, int32_t& surfaceNumberOfNodes, std::vector& nodeIndices) const; void setSurfaceNodeAverage(const AString& chartableFileName, const AString& surfaceStructureName, const int32_t surfaceNumberOfNodes, const std::vector& nodeIndices); void getVolumeVoxel(float xyz[3]) const; void setVolumeVoxel(const AString& chartableFileName, const float xyz[3]); void getFileRow(AString& chartableFileName, int32_t& fileRowIndex) const; void setFileRow(const AString& chartableFileName, const int32_t fileRowIndex); AString getDescription() const; private: void copyHelperChartDataSource(const ChartDataSource& obj); public: // ADD_NEW_METHODS_HERE virtual AString toString() const; private: void initializeMembersChartDataSource(); SceneClassAssistant* m_sceneAssistant; ChartDataSourceModeEnum::Enum m_dataSourceMode; AString m_chartableFileName; int32_t m_surfaceNumberOfNodes; AString m_surfaceStructureName; std::vector m_nodeIndicesAverage; int32_t m_nodeIndex; float m_voxelXYZ[3]; int32_t m_fileRowIndex; // ADD_NEW_MEMBERS_HERE }; #ifdef __CHART_DATA_SOURCE_DECLARE__ // #endif // __CHART_DATA_SOURCE_DECLARE__ } // namespace #endif //__CHART_DATA_SOURCE_H__ workbench-1.1.1/src/Charting/ChartDataSourceModeEnum.cxx000066400000000000000000000267161255417355300232420ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __CHART_DATA_SOURCE_MODE_ENUM_DECLARE__ #include "ChartDataSourceModeEnum.h" #undef __CHART_DATA_SOURCE_MODE_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::ChartDataSourceModeEnum * \brief Enumerated type for shource source of a chart. * * * * Using this enumerated type in the GUI with an EnumComboBoxTemplate * * Header File (.h) * Forward declare the data type: * class EnumComboBoxTemplate; * * Declare the member: * EnumComboBoxTemplate* m_chartDataSourceModeEnumComboBox; * * Declare a slot that is called when user changes selection * private slots: * void chartDataSourceModeEnumComboBoxItemActivated(); * * Implementation File (.cxx) * Include the header files * #include "EnumComboBoxTemplate.h" * #include "ChartDataSourceModeEnum.h" * * Instatiate: * m_chartDataSourceModeEnumComboBox = new EnumComboBoxTemplate(this); * m_chartDataSourceModeEnumComboBox->setup(); * * Get notified when the user changes the selection: * QObject::connect(m_chartDataSourceModeEnumComboBox, SIGNAL(itemActivated()), * this, SLOT(chartDataSourceModeEnumComboBoxItemActivated())); * * Update the selection: * m_chartDataSourceModeEnumComboBox->setSelectedItem(NEW_VALUE); * * Read the selection: * const ChartDataSourceModeEnum::Enum VARIABLE = m_chartDataSourceModeEnumComboBox->getSelectedItem(); * */ /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ ChartDataSourceModeEnum::ChartDataSourceModeEnum(const Enum enumValue, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCodeCounter++; this->name = name; this->guiName = guiName; } /** * Destructor. */ ChartDataSourceModeEnum::~ChartDataSourceModeEnum() { } /** * Initialize the enumerated metadata. */ void ChartDataSourceModeEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(ChartDataSourceModeEnum(CHART_DATA_SOURCE_MODE_INVALID, "CHART_DATA_SOURCE_MODE_INVALID", "Invalid")); enumData.push_back(ChartDataSourceModeEnum(CHART_DATA_SOURCE_MODE_FILE_ROW, "CHART_DATA_SOURCE_MODE_FILE_ROW", "Chart Source File Row")); enumData.push_back(ChartDataSourceModeEnum(CHART_DATA_SOURCE_MODE_SURFACE_NODE_INDEX, "CHART_DATA_SOURCE_MODE_SURFACE_NODE_INDEX", "Chart Source Surface Node")); enumData.push_back(ChartDataSourceModeEnum(CHART_DATA_SOURCE_MODE_SURFACE_NODE_INDICES_AVERAGE, "CHART_DATA_SOURCE_MODE_SURFACE_NODE_INDICES_AVERAGE", "Chart Source Average of Surface Nodes")); enumData.push_back(ChartDataSourceModeEnum(CHART_DATA_SOURCE_MODE_VOXEL_IJK, "CHART_DATA_SOURCE_MODE_VOXEL_IJK", "Chart Source Voxel")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const ChartDataSourceModeEnum* ChartDataSourceModeEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const ChartDataSourceModeEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString ChartDataSourceModeEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const ChartDataSourceModeEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ ChartDataSourceModeEnum::Enum ChartDataSourceModeEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = ChartDataSourceModeEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ChartDataSourceModeEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type ChartDataSourceModeEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString ChartDataSourceModeEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const ChartDataSourceModeEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ ChartDataSourceModeEnum::Enum ChartDataSourceModeEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = ChartDataSourceModeEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ChartDataSourceModeEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type ChartDataSourceModeEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t ChartDataSourceModeEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const ChartDataSourceModeEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ ChartDataSourceModeEnum::Enum ChartDataSourceModeEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = ChartDataSourceModeEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ChartDataSourceModeEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type ChartDataSourceModeEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void ChartDataSourceModeEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void ChartDataSourceModeEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(ChartDataSourceModeEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void ChartDataSourceModeEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(ChartDataSourceModeEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } workbench-1.1.1/src/Charting/ChartDataSourceModeEnum.h000066400000000000000000000067461255417355300226700ustar00rootroot00000000000000#ifndef __CHART_DATA_SOURCE_MODE_ENUM_H__ #define __CHART_DATA_SOURCE_MODE_ENUM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class ChartDataSourceModeEnum { public: /** * Enumerated values. */ enum Enum { /** Invalid mode */ CHART_DATA_SOURCE_MODE_INVALID, /** Chart is from a file's row */ CHART_DATA_SOURCE_MODE_FILE_ROW, /** Chart is from a surface node index */ CHART_DATA_SOURCE_MODE_SURFACE_NODE_INDEX, /** Chart is from an average of surface node indices */ CHART_DATA_SOURCE_MODE_SURFACE_NODE_INDICES_AVERAGE, /** Chart is from a voxel index */ CHART_DATA_SOURCE_MODE_VOXEL_IJK }; ~ChartDataSourceModeEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); private: ChartDataSourceModeEnum(const Enum enumValue, const AString& name, const AString& guiName); static const ChartDataSourceModeEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; #ifdef __CHART_DATA_SOURCE_MODE_ENUM_DECLARE__ std::vector ChartDataSourceModeEnum::enumData; bool ChartDataSourceModeEnum::initializedFlag = false; int32_t ChartDataSourceModeEnum::integerCodeCounter = 0; #endif // __CHART_DATA_SOURCE_MODE_ENUM_DECLARE__ } // namespace #endif //__CHART_DATA_SOURCE_MODE_ENUM_H__ workbench-1.1.1/src/Charting/ChartDataTypeEnum.cxx000066400000000000000000000272101255417355300221040ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __CHART_DATA_TYPE_ENUM_DECLARE__ #include "ChartDataTypeEnum.h" #undef __CHART_DATA_TYPE_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::ChartDataTypeEnum * \brief Enumerated type for type of chart data. * * Using this enumerated type in the GUI with an EnumComboBoxTemplate * * Header File (.h) * Forward declare the data type: * class EnumComboBoxTemplate; * * Declare the member: * EnumComboBoxTemplate* m_chartDataTypeEnumComboBox; * * Declare a slot that is called when user changes selection * private slots: * void chartDataTypeEnumComboBoxItemActivated(); * * Implementation File (.cxx) * Include the header files * #include "EnumComboBoxTemplate.h" * #include "ChartDataTypeEnum.h" * * Instatiate: * m_chartDataTypeEnumComboBox = new EnumComboBoxTemplate(this); * m_chartDataTypeEnumComboBox->setup(); * * Get notified when the user changes the selection: * QObject::connect(m_chartDataTypeEnumComboBox, SIGNAL(itemActivated()), * this, SLOT(chartDataTypeEnumComboBoxItemActivated())); * * Update the selection: * m_chartDataTypeEnumComboBox->setSelectedItem(NEW_VALUE); * * Read the selection: * const ChartDataTypeEnum::Enum VARIABLE = m_chartDataTypeEnumComboBox->getSelectedItem(); * */ /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ ChartDataTypeEnum::ChartDataTypeEnum(const Enum enumValue, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCodeCounter++; this->name = name; this->guiName = guiName; } /** * Destructor. */ ChartDataTypeEnum::~ChartDataTypeEnum() { } /** * Initialize the enumerated metadata. */ void ChartDataTypeEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(ChartDataTypeEnum(CHART_DATA_TYPE_INVALID, "CHART_DATA_TYPE_INVALID", "Invalid")); enumData.push_back(ChartDataTypeEnum(CHART_DATA_TYPE_LINE_DATA_SERIES, "CHART_DATA_TYPE_LINE_DATA_SERIES", "Data Series")); enumData.push_back(ChartDataTypeEnum(CHART_DATA_TYPE_LINE_FREQUENCY_SERIES, "CHART_DATA_TYPE_LINE_FREQUENCY_SERIES", "Frequency Series")); enumData.push_back(ChartDataTypeEnum(CHART_DATA_TYPE_LINE_TIME_SERIES, "CHART_DATA_TYPE_LINE_TIME_SERIES", "Time Series")); enumData.push_back(ChartDataTypeEnum(CHART_DATA_TYPE_MATRIX_LAYER, "CHART_DATA_TYPE_MATRIX_LAYER", "Matrix - Layer")); enumData.push_back(ChartDataTypeEnum(CHART_DATA_TYPE_MATRIX_SERIES, "CHART_DATA_TYPE_MATRIX_SERIES", "Matrix - Series")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const ChartDataTypeEnum* ChartDataTypeEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const ChartDataTypeEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString ChartDataTypeEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const ChartDataTypeEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param nameIn * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ ChartDataTypeEnum::Enum ChartDataTypeEnum::fromName(const AString& nameIn, bool* isValidOut) { if (initializedFlag == false) initialize(); /* * Convert from obsolete names */ AString name(nameIn); if (name == "CHART_DATA_TYPE_MATRIX") { name = "CHART_DATA_TYPE_MATRIX_LAYER"; } else if (name == "CHART_DATA_TYPE_DATA_SERIES") { name = "CHART_DATA_TYPE_LINE_DATA_SERIES"; } else if (name == "CHART_DATA_TYPE_TIME_SERIES") { name = "CHART_DATA_TYPE_LINE_TIME_SERIES"; } bool validFlag = false; Enum enumValue = ChartDataTypeEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ChartDataTypeEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type ChartDataTypeEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString ChartDataTypeEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const ChartDataTypeEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param guiNameIn * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ ChartDataTypeEnum::Enum ChartDataTypeEnum::fromGuiName(const AString& guiNameIn, bool* isValidOut) { if (initializedFlag == false) initialize(); AString guiName(guiNameIn); if (guiName == "Matrix") { guiName = "Matrix - Layer"; } bool validFlag = false; Enum enumValue = ChartDataTypeEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ChartDataTypeEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type ChartDataTypeEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t ChartDataTypeEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const ChartDataTypeEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ ChartDataTypeEnum::Enum ChartDataTypeEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = ChartDataTypeEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ChartDataTypeEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type ChartDataTypeEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void ChartDataTypeEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void ChartDataTypeEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(ChartDataTypeEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void ChartDataTypeEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(ChartDataTypeEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } workbench-1.1.1/src/Charting/ChartDataTypeEnum.h000066400000000000000000000065761255417355300215450ustar00rootroot00000000000000#ifndef __CHART_DATA_TYPE_ENUM_H__ #define __CHART_DATA_TYPE_ENUM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class ChartDataTypeEnum { public: /** * Enumerated values. */ enum Enum { /** Invalid */ CHART_DATA_TYPE_INVALID, /** Line Data Series */ CHART_DATA_TYPE_LINE_DATA_SERIES, /** Line Frequency Series */ CHART_DATA_TYPE_LINE_FREQUENCY_SERIES, /** Line Time Series */ CHART_DATA_TYPE_LINE_TIME_SERIES, /** Matrix (connectivity in layer) */ CHART_DATA_TYPE_MATRIX_LAYER, /** Matrix (series data) */ CHART_DATA_TYPE_MATRIX_SERIES }; ~ChartDataTypeEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); private: ChartDataTypeEnum(const Enum enumValue, const AString& name, const AString& guiName); static const ChartDataTypeEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; #ifdef __CHART_DATA_TYPE_ENUM_DECLARE__ std::vector ChartDataTypeEnum::enumData; bool ChartDataTypeEnum::initializedFlag = false; int32_t ChartDataTypeEnum::integerCodeCounter = 0; #endif // __CHART_DATA_TYPE_ENUM_DECLARE__ } // namespace #endif //__CHART_DATA_TYPE_ENUM_H__ workbench-1.1.1/src/Charting/ChartMatrixDisplayProperties.cxx000066400000000000000000000223001255417355300244060ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CHART_MATRIX_DISPLAY_PROPERTIES_DECLARE__ #include "ChartMatrixDisplayProperties.h" #undef __CHART_MATRIX_DISPLAY_PROPERTIES_DECLARE__ #include "CaretAssert.h" #include "SceneClass.h" #include "SceneClassAssistant.h" using namespace caret; /** * \class caret::ChartMatrixDisplayProperties * \brief Properites for display of matrix charts. * \ingroup Charting */ /** * Constructor. */ ChartMatrixDisplayProperties::ChartMatrixDisplayProperties() : CaretObject() { m_scaleMode = ChartMatrixScaleModeEnum::CHART_MATRIX_SCALE_AUTO; m_cellWidth = 10.0; m_cellHeight = 10.0; m_colorBarDisplayed = false; m_highlightSelectedRowColumn = true; m_displayGridLines = true; resetPropertiesToDefault(); m_sceneAssistant = new SceneClassAssistant(); m_sceneAssistant->add("m_cellWidth", &m_cellWidth); m_sceneAssistant->add("m_cellHeight", &m_cellHeight); m_sceneAssistant->add("m_viewZooming", &m_viewZooming); m_sceneAssistant->addArray("m_viewPanning", m_viewPanning, 2, 0.0); m_sceneAssistant->add("m_colorBarDisplayed", &m_colorBarDisplayed); m_sceneAssistant->add("m_highlightSelectedRowColumn", &m_highlightSelectedRowColumn); m_sceneAssistant->add("m_displayGridLines", &m_displayGridLines); m_sceneAssistant->add("m_scaleMode", &m_scaleMode); } /** * Destructor. */ ChartMatrixDisplayProperties::~ChartMatrixDisplayProperties() { delete m_sceneAssistant; } /** * Copy constructor. * @param obj * Object that is copied. */ ChartMatrixDisplayProperties::ChartMatrixDisplayProperties(const ChartMatrixDisplayProperties& obj) : CaretObject(obj), SceneableInterface() { this->copyHelperChartMatrixDisplayProperties(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ ChartMatrixDisplayProperties& ChartMatrixDisplayProperties::operator=(const ChartMatrixDisplayProperties& obj) { if (this != &obj) { CaretObject::operator=(obj); this->copyHelperChartMatrixDisplayProperties(obj); } return *this; } /** * Reset to the default. */ void ChartMatrixDisplayProperties::resetPropertiesToDefault() { m_viewPanning[0] = 0.0; m_viewPanning[1] = 0.0; m_viewZooming = 1.0; // m_cellWidth = 10.0; // m_cellHeight = 10.0; setScaleMode(ChartMatrixScaleModeEnum::CHART_MATRIX_SCALE_AUTO); } /** * Helps with copying an object of this type. * @param obj * Object that is copied. */ void ChartMatrixDisplayProperties::copyHelperChartMatrixDisplayProperties(const ChartMatrixDisplayProperties& obj) { m_viewPanning[0] = obj.m_viewPanning[0]; m_viewPanning[1] = obj.m_viewPanning[1]; m_viewZooming = obj.m_viewZooming; m_cellWidth = obj.m_cellWidth; m_cellHeight = obj.m_cellHeight; m_scaleMode = obj.m_scaleMode; m_colorBarDisplayed = obj.m_colorBarDisplayed; } /** * Get a description of this object's content. * @return String describing this object's content. */ AString ChartMatrixDisplayProperties::toString() const { return "ChartMatrixDisplayProperties"; } /** * @return Widgth of matrix cell in pixels */ float ChartMatrixDisplayProperties::getCellWidth() const { return m_cellWidth; } /** * Set width of matrix cell in pixels * @param cellWidth * New value for size of matrix cell width in pixels */ void ChartMatrixDisplayProperties::setCellWidth(const float cellWidth) { m_cellWidth = cellWidth; } /** * @return Height of matrix cell in pixels */ float ChartMatrixDisplayProperties::getCellHeight() const { return m_cellHeight; } /** * Set height of matrix cell in pixels * @param cellHeight * New value for size of matrix cell in pixels */ void ChartMatrixDisplayProperties::setCellHeight(const float cellHeight) { m_cellHeight = cellHeight; } /** * @return zooming for view of matrix */ float ChartMatrixDisplayProperties::getViewZooming() const { return m_viewZooming; } /** * Set zooming for view of matrix * @param viewZooming * New value for zooming for view of matrix */ void ChartMatrixDisplayProperties::setViewZooming(const float viewZooming) { m_viewZooming = viewZooming; setScaleMode(ChartMatrixScaleModeEnum::CHART_MATRIX_SCALE_MANUAL); } /** * @return panning for view of matrix */ void ChartMatrixDisplayProperties::getViewPanning(float viewPanningOut[2]) const { viewPanningOut[0] = m_viewPanning[0]; viewPanningOut[1] = m_viewPanning[1]; } /** * Set panning for view of matrix * @param viewPanning[2] * New value for panning for view of matrix */ void ChartMatrixDisplayProperties::setViewPanning(const float viewPanning[2]) { m_viewPanning[0] = viewPanning[0]; m_viewPanning[1] = viewPanning[1]; setScaleMode(ChartMatrixScaleModeEnum::CHART_MATRIX_SCALE_MANUAL); } /** * @return scale mode for view of matrix */ ChartMatrixScaleModeEnum::Enum ChartMatrixDisplayProperties::getScaleMode() const { return m_scaleMode; } /** * Set the scale mode. * * @param scaleMode * New value for scale mode. */ void ChartMatrixDisplayProperties::setScaleMode(const ChartMatrixScaleModeEnum::Enum scaleMode) { m_scaleMode = scaleMode; } /** * Save information specific to this type of model to the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @param instanceName * Name of instance in the scene. */ SceneClass* ChartMatrixDisplayProperties::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "ChartMatrixDisplayProperties", 1); m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); // Uncomment if sub-classes must save to scene //saveSubClassDataToScene(sceneAttributes, // sceneClass); return sceneClass; } /** * Restore information specific to the type of model from the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass from which model specific information is obtained. */ void ChartMatrixDisplayProperties::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); //Uncomment if sub-classes must restore from scene //restoreSubClassDataFromScene(sceneAttributes, // sceneClass); } /** * Is the colorbar displayed for the given tab? * * @return * True if colorbar is displayed, else false. */ bool ChartMatrixDisplayProperties::isColorBarDisplayed() const { return m_colorBarDisplayed; } /** * Set the colorbar displayed for the given tab. * * @param displayed * True if colorbar is displayed, else false. */ void ChartMatrixDisplayProperties::setColorBarDisplayed(const bool displayed) { m_colorBarDisplayed = displayed; } /** * Is the selected row/column highlighted? */ bool ChartMatrixDisplayProperties::isSelectedRowColumnHighlighted() const { return m_highlightSelectedRowColumn; } /** * Set the selected row/column highlighted status. * * @param highlightStatus * New status for lighlighting selected row/column. */ void ChartMatrixDisplayProperties::setSelectedRowColumnHighlighted(const bool highlightStatus) { m_highlightSelectedRowColumn = highlightStatus; } /** * Are the grid lines displayed? */ bool ChartMatrixDisplayProperties::isGridLinesDisplayed() const { return m_displayGridLines; } /** * Set the grid lines displayed for the given tab. * * @param displayGridLines * True if grid lines are displayed, else false. */ void ChartMatrixDisplayProperties::setGridLinesDisplayed(const bool displayGridLines) { m_displayGridLines = displayGridLines; } workbench-1.1.1/src/Charting/ChartMatrixDisplayProperties.h000066400000000000000000000110411255417355300240330ustar00rootroot00000000000000#ifndef __CHART_MATRIX_DISPLAY_PROPERTIES_H__ #define __CHART_MATRIX_DISPLAY_PROPERTIES_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "BrainConstants.h" #include "CaretObject.h" #include "ChartMatrixScaleModeEnum.h" #include "SceneableInterface.h" namespace caret { class SceneClassAssistant; class ChartMatrixDisplayProperties : public CaretObject, public SceneableInterface { public: ChartMatrixDisplayProperties(); virtual ~ChartMatrixDisplayProperties(); ChartMatrixDisplayProperties(const ChartMatrixDisplayProperties& obj); ChartMatrixDisplayProperties& operator=(const ChartMatrixDisplayProperties& obj); bool isColorBarDisplayed() const; void setColorBarDisplayed(const bool displayed); float getCellWidth() const; void setCellWidth(const float cellSizeX); float getCellHeight() const; void setCellHeight(const float cellHeight); float getViewZooming() const; void setViewZooming(const float viewZooming); void getViewPanning(float viewPanningOut[2]) const; void setViewPanning(const float viewPanning[2]); ChartMatrixScaleModeEnum::Enum getScaleMode() const; void setScaleMode(const ChartMatrixScaleModeEnum::Enum scaleMode); bool isGridLinesDisplayed() const; void setGridLinesDisplayed(const bool displayGridLines); void resetPropertiesToDefault(); bool isSelectedRowColumnHighlighted() const; void setSelectedRowColumnHighlighted(const bool highlightStatus); // ADD_NEW_METHODS_HERE virtual AString toString() const; virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); // If there will be sub-classes of this class that need to save // and restore data from scenes, these pure virtual methods can // be uncommented to force their implemetation by sub-classes. // protected: // virtual void saveSubClassDataToScene(const SceneAttributes* sceneAttributes, // SceneClass* sceneClass) = 0; // // virtual void restoreSubClassDataFromScene(const SceneAttributes* sceneAttributes, // const SceneClass* sceneClass) = 0; private: void copyHelperChartMatrixDisplayProperties(const ChartMatrixDisplayProperties& obj); SceneClassAssistant* m_sceneAssistant; /** matrix cell width in pixels*/ float m_cellWidth; /** matrix cell height in pixels*/ float m_cellHeight; /** zooming for view of matrix*/ float m_viewZooming; /** panning for view of matrix*/ float m_viewPanning[2]; /** scale mode for view of matrix*/ ChartMatrixScaleModeEnum::Enum m_scaleMode; /** Display color bar */ bool m_colorBarDisplayed; /** Highlight the selected row/column */ bool m_highlightSelectedRowColumn; /** Display grid lines */ bool m_displayGridLines; // ADD_NEW_MEMBERS_HERE }; #ifdef __CHART_MATRIX_DISPLAY_PROPERTIES_DECLARE__ // #endif // __CHART_MATRIX_DISPLAY_PROPERTIES_DECLARE__ } // namespace #endif //__CHART_MATRIX_DISPLAY_PROPERTIES_H__ workbench-1.1.1/src/Charting/ChartMatrixLoadingDimensionEnum.cxx000066400000000000000000000263121255417355300250030ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __CHART_MATRIX_LOADING_TYPE_ENUM_DECLARE__ #include "ChartMatrixLoadingDimensionEnum.h" #undef __CHART_MATRIX_LOADING_TYPE_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::ChartMatrixLoadingDimensionEnum * \brief Enumerated type for loading matrix data by row or column * * Using this enumerated type in the GUI with an EnumComboBoxTemplate * * Header File (.h) * Forward declare the data type: * class EnumComboBoxTemplate; * * Declare the member: * EnumComboBoxTemplate* m_ChartMatrixLoadingDimensionEnumComboBox; * * Declare a slot that is called when user changes selection * private slots: * void ChartMatrixLoadingDimensionEnumComboBoxItemActivated(); * * Implementation File (.cxx) * Include the header files * #include "EnumComboBoxTemplate.h" * #include "ChartMatrixLoadingDimensionEnum.h" * * Instatiate: * m_ChartMatrixLoadingDimensionEnumComboBox = new EnumComboBoxTemplate(this); * m_ChartMatrixLoadingDimensionEnumComboBox->setup(); * * Get notified when the user changes the selection: * QObject::connect(m_ChartMatrixLoadingDimensionEnumComboBox, SIGNAL(itemActivated()), * this, SLOT(ChartMatrixLoadingDimensionEnumComboBoxItemActivated())); * * Update the selection: * m_ChartMatrixLoadingDimensionEnumComboBox->setSelectedItem(NEW_VALUE); * * Read the selection: * const ChartMatrixLoadingDimensionEnum::Enum VARIABLE = m_ChartMatrixLoadingDimensionEnumComboBox->getSelectedItem(); * */ /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ ChartMatrixLoadingDimensionEnum::ChartMatrixLoadingDimensionEnum(const Enum enumValue, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCodeCounter++; this->name = name; this->guiName = guiName; } /** * Destructor. */ ChartMatrixLoadingDimensionEnum::~ChartMatrixLoadingDimensionEnum() { } /** * Initialize the enumerated metadata. */ void ChartMatrixLoadingDimensionEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(ChartMatrixLoadingDimensionEnum(CHART_MATRIX_LOADING_BY_ROW, "CHART_MATRIX_LOADING_BY_ROW", "Row")); enumData.push_back(ChartMatrixLoadingDimensionEnum(CHART_MATRIX_LOADING_BY_COLUMN, "CHART_MATRIX_LOADING_BY_COLUMN", "Column")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const ChartMatrixLoadingDimensionEnum* ChartMatrixLoadingDimensionEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const ChartMatrixLoadingDimensionEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString ChartMatrixLoadingDimensionEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const ChartMatrixLoadingDimensionEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ ChartMatrixLoadingDimensionEnum::Enum ChartMatrixLoadingDimensionEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = ChartMatrixLoadingDimensionEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ChartMatrixLoadingDimensionEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type ChartMatrixLoadingDimensionEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString ChartMatrixLoadingDimensionEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const ChartMatrixLoadingDimensionEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ ChartMatrixLoadingDimensionEnum::Enum ChartMatrixLoadingDimensionEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = ChartMatrixLoadingDimensionEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ChartMatrixLoadingDimensionEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type ChartMatrixLoadingDimensionEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t ChartMatrixLoadingDimensionEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const ChartMatrixLoadingDimensionEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ ChartMatrixLoadingDimensionEnum::Enum ChartMatrixLoadingDimensionEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = ChartMatrixLoadingDimensionEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ChartMatrixLoadingDimensionEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type ChartMatrixLoadingDimensionEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void ChartMatrixLoadingDimensionEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void ChartMatrixLoadingDimensionEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(ChartMatrixLoadingDimensionEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void ChartMatrixLoadingDimensionEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(ChartMatrixLoadingDimensionEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } workbench-1.1.1/src/Charting/ChartMatrixLoadingDimensionEnum.h000066400000000000000000000064031255417355300244270ustar00rootroot00000000000000#ifndef __CHART_MATRIX_LOADING_TYPE_ENUM_H__ #define __CHART_MATRIX_LOADING_TYPE_ENUM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class ChartMatrixLoadingDimensionEnum { public: /** * Enumerated values. */ enum Enum { /** Load data from column */ CHART_MATRIX_LOADING_BY_COLUMN, /** Load data from row */ CHART_MATRIX_LOADING_BY_ROW }; ~ChartMatrixLoadingDimensionEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); private: ChartMatrixLoadingDimensionEnum(const Enum enumValue, const AString& name, const AString& guiName); static const ChartMatrixLoadingDimensionEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; #ifdef __CHART_MATRIX_LOADING_TYPE_ENUM_DECLARE__ std::vector ChartMatrixLoadingDimensionEnum::enumData; bool ChartMatrixLoadingDimensionEnum::initializedFlag = false; int32_t ChartMatrixLoadingDimensionEnum::integerCodeCounter = 0; #endif // __CHART_MATRIX_LOADING_TYPE_ENUM_DECLARE__ } // namespace #endif //__CHART_MATRIX_LOADING_TYPE_ENUM_H__ workbench-1.1.1/src/Charting/ChartMatrixScaleModeEnum.cxx000066400000000000000000000253371255417355300234220ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __CHART_MATRIX_SCALE_MODE_ENUM_DECLARE__ #include "ChartMatrixScaleModeEnum.h" #undef __CHART_MATRIX_SCALE_MODE_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::ChartMatrixScaleModeEnum * \brief Scale mode for viewing chart matrices. * * * * Using this enumerated type in the GUI with an EnumComboBoxTemplate * * Header File (.h) * Forward declare the data type: * class EnumComboBoxTemplate; * * Declare the member: * EnumComboBoxTemplate* m_chartMatrixScaleModeEnumComboBox; * * Declare a slot that is called when user changes selection * private slots: * void chartMatrixScaleModeEnumComboBoxItemActivated(); * * Implementation File (.cxx) * Include the header files * #include "EnumComboBoxTemplate.h" * #include "ChartMatrixScaleModeEnum.h" * * Instatiate: * m_chartMatrixScaleModeEnumComboBox = new EnumComboBoxTemplate(this); * m_chartMatrixScaleModeEnumComboBox->setup(); * * Get notified when the user changes the selection: * QObject::connect(m_chartMatrixScaleModeEnumComboBox, SIGNAL(itemActivated()), * this, SLOT(chartMatrixScaleModeEnumComboBoxItemActivated())); * * Update the selection: * m_chartMatrixScaleModeEnumComboBox->setSelectedItem(NEW_VALUE); * * Read the selection: * const ChartMatrixScaleModeEnum::Enum VARIABLE = m_chartMatrixScaleModeEnumComboBox->getSelectedItem(); * */ /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ ChartMatrixScaleModeEnum::ChartMatrixScaleModeEnum(const Enum enumValue, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCodeCounter++; this->name = name; this->guiName = guiName; } /** * Destructor. */ ChartMatrixScaleModeEnum::~ChartMatrixScaleModeEnum() { } /** * Initialize the enumerated metadata. */ void ChartMatrixScaleModeEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(ChartMatrixScaleModeEnum(CHART_MATRIX_SCALE_AUTO, "CHART_MATRIX_SCALE_AUTO", "Auto")); enumData.push_back(ChartMatrixScaleModeEnum(CHART_MATRIX_SCALE_MANUAL, "CHART_MATRIX_SCALE_MANUAL", "Manual")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const ChartMatrixScaleModeEnum* ChartMatrixScaleModeEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const ChartMatrixScaleModeEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString ChartMatrixScaleModeEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const ChartMatrixScaleModeEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ ChartMatrixScaleModeEnum::Enum ChartMatrixScaleModeEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = ChartMatrixScaleModeEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ChartMatrixScaleModeEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type ChartMatrixScaleModeEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString ChartMatrixScaleModeEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const ChartMatrixScaleModeEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ ChartMatrixScaleModeEnum::Enum ChartMatrixScaleModeEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = ChartMatrixScaleModeEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ChartMatrixScaleModeEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type ChartMatrixScaleModeEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t ChartMatrixScaleModeEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const ChartMatrixScaleModeEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ ChartMatrixScaleModeEnum::Enum ChartMatrixScaleModeEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = ChartMatrixScaleModeEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ChartMatrixScaleModeEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type ChartMatrixScaleModeEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void ChartMatrixScaleModeEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void ChartMatrixScaleModeEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(ChartMatrixScaleModeEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void ChartMatrixScaleModeEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(ChartMatrixScaleModeEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } workbench-1.1.1/src/Charting/ChartMatrixScaleModeEnum.h000066400000000000000000000062511255417355300230410ustar00rootroot00000000000000#ifndef __CHART_MATRIX_SCALE_MODE_ENUM_H__ #define __CHART_MATRIX_SCALE_MODE_ENUM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class ChartMatrixScaleModeEnum { public: /** * Enumerated values. */ enum Enum { /** Auto scale mode */ CHART_MATRIX_SCALE_AUTO, /** Manual scale mode */ CHART_MATRIX_SCALE_MANUAL }; ~ChartMatrixScaleModeEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); private: ChartMatrixScaleModeEnum(const Enum enumValue, const AString& name, const AString& guiName); static const ChartMatrixScaleModeEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; #ifdef __CHART_MATRIX_SCALE_MODE_ENUM_DECLARE__ std::vector ChartMatrixScaleModeEnum::enumData; bool ChartMatrixScaleModeEnum::initializedFlag = false; int32_t ChartMatrixScaleModeEnum::integerCodeCounter = 0; #endif // __CHART_MATRIX_SCALE_MODE_ENUM_DECLARE__ } // namespace #endif //__CHART_MATRIX_SCALE_MODE_ENUM_H__ workbench-1.1.1/src/Charting/ChartModel.cxx000066400000000000000000000671031255417355300206110ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CHART_MODEL_DECLARE__ #include "ChartModel.h" #undef __CHART_MODEL_DECLARE__ #include "CaretAssert.h" #include "CaretLogger.h" #include "ChartAxis.h" #include "ChartData.h" #include "ChartDataCartesian.h" #include "ChartDataSource.h" #include "SceneClass.h" #include "SceneClassArray.h" #include "SceneClassAssistant.h" #include "SceneStringArray.h" using namespace caret; /** * \class caret::ChartModel * \brief Base class for chart model * \ingroup Charting * * Base class for chart model that displays chart data of the same type. * Some data types may require mututal exclusive display. */ /** * Constructor. * * @param chartDataType * Model type of chart that is managed. * @param chartSelectionMode * The selection mode. */ ChartModel::ChartModel(const ChartDataTypeEnum::Enum chartDataType, const ChartSelectionModeEnum::Enum chartSelectionMode) : CaretObject(), SceneableInterface(), m_chartDataType(chartDataType), m_chartSelectionMode(chartSelectionMode) { m_bottomAxis = NULL; m_leftAxis = NULL; m_rightAxis = NULL; m_topAxis = NULL; switch (m_chartSelectionMode) { case ChartSelectionModeEnum::CHART_SELECTION_MODE_ANY: m_maximumNumberOfChartDatasToDisplay = 5; break; case ChartSelectionModeEnum::CHART_SELECTION_MODE_SINGLE: m_maximumNumberOfChartDatasToDisplay = 1; break; } m_averageChartDisplaySelected = false; m_sceneAssistant = new SceneClassAssistant(); m_sceneAssistant->add("m_maximumNumberOfChartDatasToDisplay", &m_maximumNumberOfChartDatasToDisplay); m_sceneAssistant->add("m_averageChartDisplaySelected", &m_averageChartDisplaySelected); } /** * Destructor. */ ChartModel::~ChartModel() { delete m_sceneAssistant; removeAllAxes(); removeChartDataPrivate(); } /** * Remove the data data. * NOTE: This method cannot be called by constructor/destructor since * it calls a virtual method. */ void ChartModel::removeChartData() { removeChartDataPrivate(); updateAfterChartDataHasBeenAddedOrRemoved(); } /** * Remove the data data. */ void ChartModel::removeChartDataPrivate() { m_chartDatas.clear(); } /** * Remove all axes. */ void ChartModel::removeAllAxes() { if (m_bottomAxis != NULL) { delete m_bottomAxis; m_bottomAxis = NULL; } if (m_leftAxis != NULL) { delete m_leftAxis; m_leftAxis = NULL; } if (m_rightAxis != NULL) { delete m_rightAxis; m_rightAxis = NULL; } if (m_topAxis != NULL) { delete m_topAxis; m_topAxis = NULL; } } /** * Copy constructor. * @param obj * Object that is copied. */ ChartModel::ChartModel(const ChartModel& obj) : CaretObject(obj), SceneableInterface(obj), m_chartDataType(obj.m_chartDataType), m_chartSelectionMode(obj.m_chartSelectionMode) { this->copyHelperChartModel(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ ChartModel& ChartModel::operator=(const ChartModel& obj) { if (this != &obj) { this->copyHelperChartModel(obj); } return *this; } /** * Helps with copying an object of this type. * @param obj * Object that is copied. */ void ChartModel::copyHelperChartModel(const ChartModel& obj) { m_chartDataType = obj.m_chartDataType; m_chartSelectionMode = obj.m_chartSelectionMode; m_maximumNumberOfChartDatasToDisplay = obj.m_maximumNumberOfChartDatasToDisplay; removeAllAxes(); if (obj.m_leftAxis != NULL) { setLeftAxis(obj.m_leftAxis->clone()); } if (obj.m_rightAxis != NULL) { setRightAxis(obj.m_rightAxis->clone()); } if (obj.m_bottomAxis != NULL) { setBottomAxis(obj.m_bottomAxis->clone()); } if (obj.m_topAxis != NULL) { setTopAxis(obj.m_topAxis->clone()); } m_averageChartDisplaySelected = obj.m_averageChartDisplaySelected; removeChartData(); m_chartDatas = obj.m_chartDatas; ChartData* selectedChartData = NULL; switch (m_chartSelectionMode) { case ChartSelectionModeEnum::CHART_SELECTION_MODE_ANY: break; case ChartSelectionModeEnum::CHART_SELECTION_MODE_SINGLE: { /* * If no item selected, choose oldest */ if (selectedChartData == NULL) { const int32_t numData = static_cast(m_chartDatas.size()); if (numData > 0) { const int32_t lastIndex = numData - 1; selectedChartData = m_chartDatas[lastIndex].data(); } } if (selectedChartData != NULL) { /* * Calling the setSelected method will ensure that * mutual exclusion for selection is maintained */ // selectedChartData->setSelected(true); } } break; } updateAfterChartDataHasBeenAddedOrRemoved(); } /** * @return The chart data type. */ ChartDataTypeEnum::Enum ChartModel::getChartDataType() const { return m_chartDataType; } /** * Get a description of this object's content. * @return String describing this object's content. */ AString ChartModel::toString() const { return "ChartModel"; } /** * @return Support for multiple chart display. Some chart types allow * it and others do not. */ ChartSelectionModeEnum::Enum ChartModel::getChartSelectionMode() const { return m_chartSelectionMode; } /** * Is this model empty (zero charts)? */ bool ChartModel::isEmpty() const { return m_chartDatas.empty(); } /** * Add a chart model to this controller. * * @param chartData * Model that is added. */ void ChartModel::addChartData(const QSharedPointer& chartData) { /* * If the display is limited to one chart and both the chart * in deque and the chart to add are both cartesian charts, * copy the chart color. */ if (getMaximumNumberOfChartDatasToDisplay() == 1) { if ( ! m_chartDatas.empty()) { ChartData* firstChartData = getChartDataAtIndex(0); ChartDataCartesian* cartesianChart = dynamic_cast(firstChartData); if (cartesianChart != NULL) { ChartDataCartesian* newCartesianChart = dynamic_cast(chartData.data()); if (newCartesianChart != NULL) { newCartesianChart->setColor(cartesianChart->getColor()); } } } } m_chartDatas.push_front(chartData); updateUsingMaximumNumberOfChartDatasToDisplay(); updateAfterChartDataHasBeenAddedOrRemoved(); } /** * Update so the number of charts is never greater than * the maximum. */ void ChartModel::updateUsingMaximumNumberOfChartDatasToDisplay() { /* * If needed, remove extra items at end of deque */ const int32_t numToRemove = (static_cast(m_chartDatas.size()) - m_maximumNumberOfChartDatasToDisplay); if (numToRemove > 0) { for (int32_t i = 0; i < numToRemove; i++) { m_chartDatas.pop_back(); } } switch (m_chartSelectionMode) { case ChartSelectionModeEnum::CHART_SELECTION_MODE_ANY: break; case ChartSelectionModeEnum::CHART_SELECTION_MODE_SINGLE: { /* * See if any item is selected */ bool haveSelectedItem = false; const int32_t numData = static_cast(m_chartDatas.size()); for (int32_t i = 0; i < numData; i++) { } /* * If no item selected, selected oldest */ if ( ! haveSelectedItem) { if (numData >= 0) { } } } break; } } /** * @return All chart datas (const method) */ std::vector ChartModel::getAllChartDatas() const { std::vector datasOut; for (std::deque >::const_iterator iter = m_chartDatas.begin(); iter != m_chartDatas.end(); iter++) { datasOut.push_back(iter->data()); } return datasOut; } /** * @return All chart datas. */ std::vector ChartModel::getAllChartDatas() { std::vector datasOut; for (std::deque >::const_iterator iter = m_chartDatas.begin(); iter != m_chartDatas.end(); iter++) { datasOut.push_back(iter->data()); } return datasOut; } /** * @return All SELECTED chart datas in the given tab. * @param tabIndex * Index of tab. */ std::vector ChartModel::getAllSelectedChartDatas(const int32_t tabIndex) const { std::vector datasOut; for (std::deque >::const_iterator iter = m_chartDatas.begin(); iter != m_chartDatas.end(); iter++) { ChartData* cd = iter->data(); if (cd->isSelected(tabIndex)) { datasOut.push_back(cd); } } return datasOut; } /** * @return Number of chart data. */ int32_t ChartModel::getNumberOfChartData() const { return m_chartDatas.size(); } /** * Get the chart data at the given index. * * @param chartDataIndex * Index of desired chart data. * @return * ChartData at the given index. */ ChartData* ChartModel::getChartDataAtIndex(const int32_t chartDataIndex) { CaretAssertVectorIndex(m_chartDatas, chartDataIndex); return m_chartDatas[chartDataIndex].data(); } /** * Get the chart data at the given index (const method). * * @param chartDataIndex * Index of desired chart data. * @return * ChartData at the given index. */ const ChartData* ChartModel::getChartDataAtIndex(const int32_t chartDataIndex) const { CaretAssertVectorIndex(m_chartDatas, chartDataIndex); return m_chartDatas[chartDataIndex].data(); } /** * Move the chart data at the given index by swapping with the chart * data at (chartDataIndex - 1). * * @param chartDataIndex * Index of the chart data. */ void ChartModel::moveChartDataAtIndexToOneLowerIndex(const int32_t chartDataIndex) { CaretAssertVectorIndex(m_chartDatas, chartDataIndex); if (chartDataIndex > 0) { std::swap(m_chartDatas[chartDataIndex], m_chartDatas[chartDataIndex - 1]); updateAfterChartDataHasBeenAddedOrRemoved(); } } /** * Move the chart data at the given index by swapping with the chart * data at (chartDataIndex + 1). * * @param chartDataIndex * Index of the chart data. */ void ChartModel::moveChartDataAtIndexToOneHigherIndex(const int32_t chartDataIndex) { CaretAssertVectorIndex(m_chartDatas, chartDataIndex); if (chartDataIndex < (static_cast(m_chartDatas.size()) - 1)) { std::swap(m_chartDatas[chartDataIndex], m_chartDatas[chartDataIndex + 1]); updateAfterChartDataHasBeenAddedOrRemoved(); } } /** * Remove the chart data at the given index. * * @param chartDataIndex * Index of the chart data. */ void ChartModel::removeChartAtIndex(const int32_t chartDataIndex) { CaretAssertVectorIndex(m_chartDatas, chartDataIndex); const int32_t lastIndex = m_chartDatas.size() - 1; for (int32_t i = chartDataIndex; i < lastIndex; i++) { CaretAssertVectorIndex(m_chartDatas, (i + 1)); m_chartDatas[i] = m_chartDatas[i + 1]; } m_chartDatas.resize(m_chartDatas.size() - 1); updateAfterChartDataHasBeenAddedOrRemoved(); } /** * @return Is average chart data display selected. * NOTE: Not all charts support an average. */ bool ChartModel::isAverageChartDisplaySelected() const { if (isAverageChartDisplaySupported()) { return m_averageChartDisplaySelected; } return false; } /** * Set the average chart data selected. * NOTE: Not all charts support an average. * * @param selected * New status. */ void ChartModel::setAverageChartDisplaySelected(const bool selected) { m_averageChartDisplaySelected = selected; } /** * @return The MAXIMUM number of chart models for display. * * NOTE: This value MAY BE GREATER than the actual number of chart models * that are available. */ int32_t ChartModel::getMaximumNumberOfChartDatasToDisplay() const { return m_maximumNumberOfChartDatasToDisplay; } /** * Set the number of most recent chart models for display. * * @param numberToDisplay * New number of most recent models for display. */ void ChartModel::setMaximumNumberOfChartDatasToDisplay(const int32_t numberToDisplay) { CaretAssert(numberToDisplay > 0); switch (m_chartSelectionMode) { case ChartSelectionModeEnum::CHART_SELECTION_MODE_ANY: m_maximumNumberOfChartDatasToDisplay = numberToDisplay; break; case ChartSelectionModeEnum::CHART_SELECTION_MODE_SINGLE: m_maximumNumberOfChartDatasToDisplay = 1; break; } updateUsingMaximumNumberOfChartDatasToDisplay(); updateAfterChartDataHasBeenAddedOrRemoved(); } /** * @return Chart Axis for left. NULL if axis not valid. */ ChartAxis* ChartModel::getLeftAxis() { return m_leftAxis; } /** * @return Chart Axis for left (const method). NULL if axis not valid. */ const ChartAxis* ChartModel::getLeftAxis() const { return m_leftAxis; } /** * Set the bottom axis. Replaces current axis. * * @param bottomAxis * New bottom axis. */ void ChartModel::setBottomAxis(ChartAxis* bottomAxis) { if (m_bottomAxis != NULL) { delete m_bottomAxis; } m_bottomAxis = bottomAxis; m_bottomAxis->setParentChartModel(this); } /** * @return Chart Axis for right. NULL if axis not valid. */ ChartAxis* ChartModel::getRightAxis() { return m_rightAxis; } /** * @return Chart Axis for right (const method). NULL if axis not valid. */ const ChartAxis* ChartModel::getRightAxis() const { return m_rightAxis; } /** * Set the right axis. Replaces current axis. * * @param rightAxis * New right axis. */ void ChartModel::setRightAxis(ChartAxis* rightAxis) { if (m_rightAxis != NULL) { delete m_rightAxis; } m_rightAxis = rightAxis; m_rightAxis->setParentChartModel(this); } /** * @return Chart Bottom for bottom. NULL if axis not valid. */ ChartAxis* ChartModel::getBottomAxis() { return m_bottomAxis; } /** * @return Chart Axis for bottom (const method). NULL if axis not valid. */ const ChartAxis* ChartModel::getBottomAxis() const { return m_bottomAxis; } /** * Set the left axis. Replaces current axis. * * @param leftAxis * New left axis. */ void ChartModel::setLeftAxis(ChartAxis* leftAxis) { if (m_leftAxis != NULL) { delete m_leftAxis; } m_leftAxis = leftAxis; m_leftAxis->setParentChartModel(this); } /** * @return Chart Axis for top. NULL if axis not valid. */ ChartAxis* ChartModel::getTopAxis() { return m_topAxis; } /** * @return Chart Axis for top (const method). NULL if axis not valid. */ const ChartAxis* ChartModel::getTopAxis() const { return m_topAxis; } /** * Set the top axis. Replaces current axis. * * @param topAxis * New top axis. */ void ChartModel::setTopAxis(ChartAxis* topAxis) { if (m_topAxis != NULL) { delete m_topAxis; } m_topAxis = topAxis; m_topAxis->setParentChartModel(this); } /** * Called by child ChartData when its selection status changes. * * If the selection mode is mutually exclusive, this method * ensures that no more than one child is selected. * * If the selection mode is NOT mutually exclusive, no * action is taken. */ void ChartModel::childChartDataSelectionChanged(ChartData* /*childChartData*/) { switch (m_chartSelectionMode) { case ChartSelectionModeEnum::CHART_SELECTION_MODE_ANY: break; case ChartSelectionModeEnum::CHART_SELECTION_MODE_SINGLE: // if (childChartData->isSelected()) { // const int32_t numChartData = static_cast(m_chartDatas.size()); // for (int32_t i = 0; i < numChartData; i++) { // ChartData* cd = m_chartDatas[i]; // if (cd != childChartData) { // cd->setSelected(false); // } // } // } break; } } /** * Update after chart data has been added or removed. */ void ChartModel::updateAfterChartDataHasBeenAddedOrRemoved() { if (m_bottomAxis != NULL) { m_bottomAxis->updateForAutoRangeScale(); } if (m_leftAxis != NULL) { m_leftAxis->updateForAutoRangeScale(); } if (m_rightAxis != NULL) { m_rightAxis->updateForAutoRangeScale(); } if (m_topAxis != NULL) { m_topAxis->updateForAutoRangeScale(); } } /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @param instanceName * Name of the class' instance. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ SceneClass* ChartModel::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { const int32_t numChartData = static_cast(m_chartDatas.size()); if (numChartData <= 0) { return NULL; } SceneClass* sceneClass = new SceneClass(instanceName, "ChartModel", 2); m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); if (numChartData > 0) { std::vector chartDataUniqueIDs; for (int32_t i = 0; i < numChartData; i++) { chartDataUniqueIDs.push_back(m_chartDatas[i]->getUniqueIdentifier()); } sceneClass->addStringArray("chartUniqueIDsArray", &chartDataUniqueIDs[0], numChartData); } if (m_bottomAxis != NULL) { sceneClass->addEnumeratedType("bottomAxisType", m_bottomAxis->getAxisType()); sceneClass->addChild(m_bottomAxis->saveToScene(sceneAttributes, "m_bottomAxis")); } if (m_leftAxis != NULL) { sceneClass->addEnumeratedType("leftAxisType", m_leftAxis->getAxisType()); sceneClass->addChild(m_leftAxis->saveToScene(sceneAttributes, "m_leftAxis")); } if (m_rightAxis != NULL) { sceneClass->addEnumeratedType("rightAxisType", m_rightAxis->getAxisType()); sceneClass->addChild(m_rightAxis->saveToScene(sceneAttributes, "m_rightAxis")); } if (m_topAxis != NULL) { sceneClass->addEnumeratedType("topAxisType", m_topAxis->getAxisType()); sceneClass->addChild(m_topAxis->saveToScene(sceneAttributes, "m_topAxis")); } saveSubClassDataToScene(sceneAttributes, sceneClass); return sceneClass; } /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass for the instance of a class that implements * this interface. May be NULL for some types of scenes. */ void ChartModel::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } const int32_t versionNumber = sceneClass->getVersionNumber(); removeChartData(); m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); removeAllAxes(); /* * Restore bottom axis */ const ChartAxisTypeEnum::Enum bottomAxisType = sceneClass->getEnumeratedTypeValue("bottomAxisType", ChartAxisTypeEnum::CHART_AXIS_TYPE_NONE); if (bottomAxisType != ChartAxisTypeEnum::CHART_AXIS_TYPE_NONE) { ChartAxis* axis = ChartAxis::newChartAxisForTypeAndLocation(bottomAxisType, ChartAxisLocationEnum::CHART_AXIS_LOCATION_BOTTOM); axis->restoreFromScene(sceneAttributes, sceneClass->getClass("m_bottomAxis")); setBottomAxis(axis); } /* * Restore left axis */ const ChartAxisTypeEnum::Enum leftAxisType = sceneClass->getEnumeratedTypeValue("leftAxisType", ChartAxisTypeEnum::CHART_AXIS_TYPE_NONE); if (leftAxisType != ChartAxisTypeEnum::CHART_AXIS_TYPE_NONE) { ChartAxis* axis = ChartAxis::newChartAxisForTypeAndLocation(leftAxisType, ChartAxisLocationEnum::CHART_AXIS_LOCATION_LEFT); axis->restoreFromScene(sceneAttributes, sceneClass->getClass("m_leftAxis")); setLeftAxis(axis); } if (versionNumber >= 2) { /* * Restore right axis */ const ChartAxisTypeEnum::Enum rightAxisType = sceneClass->getEnumeratedTypeValue("rightAxisType", ChartAxisTypeEnum::CHART_AXIS_TYPE_NONE); if (rightAxisType != ChartAxisTypeEnum::CHART_AXIS_TYPE_NONE) { ChartAxis* axis = ChartAxis::newChartAxisForTypeAndLocation(rightAxisType, ChartAxisLocationEnum::CHART_AXIS_LOCATION_RIGHT); axis->restoreFromScene(sceneAttributes, sceneClass->getClass("m_rightAxis")); setRightAxis(axis); } /* * Restore top axis */ const ChartAxisTypeEnum::Enum topAxisType = sceneClass->getEnumeratedTypeValue("topAxisType", ChartAxisTypeEnum::CHART_AXIS_TYPE_NONE); if (topAxisType != ChartAxisTypeEnum::CHART_AXIS_TYPE_NONE) { ChartAxis* axis = ChartAxis::newChartAxisForTypeAndLocation(topAxisType, ChartAxisLocationEnum::CHART_AXIS_LOCATION_TOP); axis->restoreFromScene(sceneAttributes, sceneClass->getClass("m_topAxis")); setTopAxis(axis); } } /* * Restore unique IDs of ChartData */ m_chartDataUniqueIDsRestoredFromScene.clear(); const ScenePrimitiveArray* chartUniqueIDsArray = sceneClass->getPrimitiveArray("chartUniqueIDsArray"); if (chartUniqueIDsArray != NULL) { const int32_t numElements = chartUniqueIDsArray->getNumberOfArrayElements(); m_chartDataUniqueIDsRestoredFromScene.resize(numElements); chartUniqueIDsArray->stringValues(m_chartDataUniqueIDsRestoredFromScene, ""); /* * Need to reverse order so that multiple charts are restored * in the correct order. Done here rather than when saving * scenes so that old scenes are restored correctly. */ if ( ! m_chartDataUniqueIDsRestoredFromScene.empty()) { std::reverse(m_chartDataUniqueIDsRestoredFromScene.begin(), m_chartDataUniqueIDsRestoredFromScene.end()); } } restoreSubClassDataFromScene(sceneAttributes, sceneClass); } /** * Restore chart data from scene by matching unique identifiers from charts * * @param restoredChartData * Chart data restored from scenes. */ void ChartModel::restoreChartDataFromScene(const SceneAttributes* sceneAttributes, std::vector >& restoredChartData) { const int32_t numChartUniqueIDsFromScene = static_cast(m_chartDataUniqueIDsRestoredFromScene.size()); if (numChartUniqueIDsFromScene > 0) { std::vector > chartDataVector(numChartUniqueIDsFromScene); /* * Need to keep chart data in same order as when scene was created */ for (int32_t i = 0; i < numChartUniqueIDsFromScene; i++) { for (std::vector >::iterator chartIter = restoredChartData.begin(); chartIter != restoredChartData.end(); chartIter++) { QSharedPointer cd = *chartIter; if (cd->getUniqueIdentifier() == m_chartDataUniqueIDsRestoredFromScene[i]) { if (cd->getChartDataType() == m_chartDataType) { chartDataVector[i] = cd; break; } } } } /* * Add the restored chart data */ for (int32_t i = 0; i < numChartUniqueIDsFromScene; i++) { QSharedPointer cd = chartDataVector[i]; if (cd.isNull()) { const AString msg("Failed to restore chart with Unique ID: " + m_chartDataUniqueIDsRestoredFromScene[i]); sceneAttributes->addToErrorMessage(msg); CaretLogSevere(msg); } else { addChartData(cd); } } } m_chartDataUniqueIDsRestoredFromScene.clear(); updateUsingMaximumNumberOfChartDatasToDisplay(); updateAfterChartDataHasBeenAddedOrRemoved(); } workbench-1.1.1/src/Charting/ChartModel.h000066400000000000000000000143151255417355300202330ustar00rootroot00000000000000#ifndef __CHART_MODEL_H__ #define __CHART_MODEL_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "CaretObject.h" #include "ChartDataTypeEnum.h" #include "ChartSelectionModeEnum.h" #include "SceneableInterface.h" namespace caret { class ChartAxis; class ChartData; class SceneClassAssistant; class ChartModel : public CaretObject, public SceneableInterface { public: ChartModel(const ChartDataTypeEnum::Enum chartDataType, const ChartSelectionModeEnum::Enum chartSelectionMode); ChartModel(const ChartModel&); ChartModel& operator=(const ChartModel&); virtual ~ChartModel(); void removeChartData(); ChartDataTypeEnum::Enum getChartDataType() const; ChartSelectionModeEnum::Enum getChartSelectionMode() const; void addChartData(const QSharedPointer& chartData); virtual int32_t getMaximumNumberOfChartDatasToDisplay() const; void setMaximumNumberOfChartDatasToDisplay(const int32_t numberToDisplay); bool isEmpty() const; std::vector getAllChartDatas() const; std::vector getAllChartDatas(); std::vector getAllSelectedChartDatas(const int32_t tabIndex) const; int32_t getNumberOfChartData() const; ChartData* getChartDataAtIndex(const int32_t chartDataIndex); const ChartData* getChartDataAtIndex(const int32_t chartDataIndex) const; void moveChartDataAtIndexToOneLowerIndex(const int32_t chartDataIndex); void moveChartDataAtIndexToOneHigherIndex(const int32_t chartDataIndex); void removeChartAtIndex(const int32_t chartDataIndex); /** * @return Is an average of data supported? */ virtual bool isAverageChartDisplaySupported() const = 0; /** * Get the average for charts in the given tab. * * @param tabIndex * Index of the tab. * * @return * The average chart data. Will return NULL if either * no data to average or model does not support an average. * Includes only those chart data that are displayed. */ virtual const ChartData* getAverageChartDataForDisplay(const int32_t tabIndex) const = 0; bool isAverageChartDisplaySelected() const; void setAverageChartDisplaySelected(const bool selected); ChartAxis* getLeftAxis(); const ChartAxis* getLeftAxis() const; ChartAxis* getRightAxis(); const ChartAxis* getRightAxis() const; ChartAxis* getBottomAxis(); const ChartAxis* getBottomAxis() const; ChartAxis* getTopAxis(); const ChartAxis* getTopAxis() const; virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); void restoreChartDataFromScene(const SceneAttributes* sceneAttributes, std::vector >& restoredChartData); // ADD_NEW_METHODS_HERE virtual AString toString() const; protected: void setBottomAxis(ChartAxis* leftAxis); void setLeftAxis(ChartAxis* leftAxis); void setRightAxis(ChartAxis* leftAxis); void setTopAxis(ChartAxis* leftAxis); virtual void updateAfterChartDataHasBeenAddedOrRemoved(); virtual void saveSubClassDataToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass) = 0; virtual void restoreSubClassDataFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) = 0; private: void copyHelperChartModel(const ChartModel& obj); void childChartDataSelectionChanged(ChartData* childChartData); void removeChartDataPrivate(); void removeAllAxes(); void updateUsingMaximumNumberOfChartDatasToDisplay(); ChartDataTypeEnum::Enum m_chartDataType; ChartSelectionModeEnum::Enum m_chartSelectionMode; std::deque > m_chartDatas; int32_t m_maximumNumberOfChartDatasToDisplay; ChartAxis* m_leftAxis; ChartAxis* m_rightAxis; ChartAxis* m_bottomAxis; ChartAxis* m_topAxis; std::vector m_chartDataUniqueIDsRestoredFromScene; bool m_averageChartDisplaySelected; /** helps with scene save/restore */ SceneClassAssistant* m_sceneAssistant; // ADD_NEW_MEMBERS_HERE friend class ChartData; }; #ifdef __CHART_MODEL_DECLARE__ // #endif // __CHART_MODEL_DECLARE__ } // namespace #endif //__CHART_MODEL_H__ workbench-1.1.1/src/Charting/ChartModelCartesian.cxx000066400000000000000000000241121255417355300224340ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #define __CHART_MODEL_CARTESIAN_DECLARE__ #include "ChartModelCartesian.h" #undef __CHART_MODEL_CARTESIAN_DECLARE__ #include "CaretAssert.h" #include "ChartAxis.h" #include "ChartAxisCartesian.h" #include "ChartDataCartesian.h" #include "ChartPoint.h" #include "ChartScaleAutoRanging.h" #include "SceneClassAssistant.h" using namespace caret; /** * \class caret::ChartModelCartesian * \brief Chart Model for cartesian data. * \ingroup Charting */ /** * Constructor. * * @param chartDataDataType * The chart model data type. * @param dataAxisUnitsX * Units for the X-axis. * @param dataAxisUnitsY * Units for the Y-axis. */ ChartModelCartesian::ChartModelCartesian(const ChartDataTypeEnum::Enum chartDataType, const ChartAxisUnitsEnum::Enum dataAxisUnitsX, const ChartAxisUnitsEnum::Enum dataAxisUnitsY) : ChartModel(chartDataType, ChartSelectionModeEnum::CHART_SELECTION_MODE_ANY) { m_averageChartData = NULL; m_lineWidth = 1.0; setLeftAxis(ChartAxis::newChartAxisForTypeAndLocation(ChartAxisTypeEnum::CHART_AXIS_TYPE_CARTESIAN, ChartAxisLocationEnum::CHART_AXIS_LOCATION_LEFT)); getLeftAxis()->setAxisUnits(dataAxisUnitsY); getLeftAxis()->setVisible(true); setBottomAxis(ChartAxis::newChartAxisForTypeAndLocation(ChartAxisTypeEnum::CHART_AXIS_TYPE_CARTESIAN, ChartAxisLocationEnum::CHART_AXIS_LOCATION_BOTTOM)); getBottomAxis()->setAxisUnits(dataAxisUnitsX); getBottomAxis()->setVisible(true); m_sceneAssistant = new SceneClassAssistant(); m_sceneAssistant->add("m_lineWidth", &m_lineWidth); } /** * Destructor. */ ChartModelCartesian::~ChartModelCartesian() { if (m_averageChartData != NULL) { delete m_averageChartData; } delete m_sceneAssistant; } /** * Copy constructor. * @param obj * Object that is copied. */ ChartModelCartesian::ChartModelCartesian(const ChartModelCartesian& obj) : ChartModel(obj) { this->copyHelperChartModelCartesian(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ ChartModelCartesian& ChartModelCartesian::operator=(const ChartModelCartesian& obj) { if (this != &obj) { ChartModel::operator=(obj); this->copyHelperChartModelCartesian(obj); } return *this; } /** * Helps with copying an object of this type. * @param obj * Object that is copied. */ void ChartModelCartesian::copyHelperChartModelCartesian(const ChartModelCartesian& /*obj*/) { if (m_averageChartData != NULL) { delete m_averageChartData; m_averageChartData = NULL; } } /** * @return Is an average of data supported? */ bool ChartModelCartesian::isAverageChartDisplaySupported() const { return true; } /** * Get the average for charts in the given tab. * * @param tabIndex * Index of the tab. * * @return * The average chart data. Will return NULL if either * no data to average or model does not support an average. * Includes only those chart data that are displayed. */ const ChartData* ChartModelCartesian::getAverageChartDataForDisplay(const int32_t tabIndex) const { if (m_averageChartData != NULL) { delete m_averageChartData; m_averageChartData = NULL; } /* * Data may be from multiple files so compute an average of those * that match the first (newest) file. */ const std::vector allData = getAllSelectedChartDatas(tabIndex); if ( ! allData.empty()) { std::vector xValue; std::vector ySum; int64_t averageCounter = 0; ChartDataTypeEnum::Enum firstChartDataType = ChartDataTypeEnum::CHART_DATA_TYPE_INVALID; bool firstFlag = true; for (std::vector::const_iterator iter = allData.begin(); iter != allData.end(); iter++) { const ChartData* chartData = *iter; const ChartDataCartesian* cartesianData = dynamic_cast(chartData); CaretAssert(cartesianData); const int64_t numPoints = cartesianData->getNumberOfPoints(); if (firstFlag) { if (numPoints > 0) { firstFlag = false; xValue.resize(numPoints); ySum.resize(numPoints); for (int64_t i = 0; i < numPoints; i++) { const ChartPoint* point = cartesianData->getPointAtIndex(i); xValue[i] = point->getX(); ySum[i] = point->getY(); } firstChartDataType = cartesianData->getChartDataType(); averageCounter = 1; } } else { if (numPoints == static_cast(ySum.size())) { for (int64_t i = 0; i < numPoints; i++) { const ChartPoint* point = cartesianData->getPointAtIndex(i); ySum[i] += point->getY(); } averageCounter++; } } } if (averageCounter > 0) { const int64_t numPoints = static_cast(ySum.size()); for (int64_t i = 0; i < numPoints; i++) { ySum[i] /= averageCounter; } m_averageChartData = dynamic_cast(ChartData::newChartDataForChartDataType(firstChartDataType)); for (int32_t i = 0; i < numPoints; i++) { m_averageChartData->addPoint(xValue[i], ySum[i]); } } } return m_averageChartData; } /** * Get the bounds of all of the data in themodel. * * @param boundsMinX * Minimum X-coordinate of all points. * @param boundsMaxX * Maximum X-coordinate of all points. * @param boundsMinY * Minimum Y-coordinate of all points. * @param boundsMaxY * Maximum Y-coordinate of all points. */ void ChartModelCartesian::getBounds(float& boundsMinX, float& boundsMaxX, float& boundsMinY, float& boundsMaxY) const { boundsMinX = 0.0; boundsMaxX = 0.0; boundsMinY = 0.0; boundsMaxY = 0.0; const std::vector allData = getAllChartDatas(); if ( ! allData.empty()) { boundsMinX = std::numeric_limits::max(); boundsMaxX = -std::numeric_limits::max(); boundsMinY = std::numeric_limits::max(); boundsMaxY = -std::numeric_limits::max(); for (std::vector::const_iterator iter = allData.begin(); iter != allData.end(); iter++) { const ChartData* chartData = *iter; const ChartDataCartesian* cartesianData = dynamic_cast(chartData); CaretAssert(cartesianData); float xMin, xMax, yMin, yMax; cartesianData->getBounds(xMin, xMax, yMin, yMax); if (xMin < boundsMinX) boundsMinX = xMin; if (xMax > boundsMaxX) boundsMaxX = xMax; if (yMin < boundsMinY) boundsMinY = yMin; if (yMax > boundsMaxY) boundsMaxY = yMax; } } } /** * Get the line width for the chart. * * @return * Line width for the chart. */ float ChartModelCartesian::getLineWidth() const { return m_lineWidth; } /** * Set the line width for the chart. * * param lineWidth * Line width for chart. */ void ChartModelCartesian::setLineWidth(const float lineWidth) { m_lineWidth = lineWidth; } /** * Save subclass data to the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass to which data members should be added. Will always * be valid (non-NULL). */ void ChartModelCartesian::saveSubClassDataToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass) { m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); } /** * Restore file data from the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass for the instance of a class that implements * this interface. Will NEVER be NULL. */ void ChartModelCartesian::restoreSubClassDataFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); } workbench-1.1.1/src/Charting/ChartModelCartesian.h000066400000000000000000000057061255417355300220710ustar00rootroot00000000000000#ifndef __CHART_MODEL_CARTESIAN_H__ #define __CHART_MODEL_CARTESIAN_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "ChartAxisUnitsEnum.h" #include "ChartModel.h" namespace caret { class ChartDataCartesian; class ChartModelCartesian : public ChartModel { public: ChartModelCartesian(const ChartDataTypeEnum::Enum chartDataType, const ChartAxisUnitsEnum::Enum dataAxisUnitsX, const ChartAxisUnitsEnum::Enum dataAxisUnitsY); virtual ~ChartModelCartesian(); ChartModelCartesian(const ChartModelCartesian& obj); ChartModelCartesian& operator=(const ChartModelCartesian& obj); void getBounds(float& boundsMinX, float& boundsMaxX, float& boundsMinY, float& boundsMaxY) const; virtual bool isAverageChartDisplaySupported() const; virtual const ChartData* getAverageChartDataForDisplay(const int32_t tabIndex) const; float getLineWidth() const; void setLineWidth(const float lineWidth); // ADD_NEW_METHODS_HERE protected: virtual void saveSubClassDataToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass); virtual void restoreSubClassDataFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private: void copyHelperChartModelCartesian(const ChartModelCartesian& obj); // void adjustAxisDefaultRange(float& minValue, // float& maxValue); SceneClassAssistant* m_sceneAssistant; mutable ChartDataCartesian* m_averageChartData; float m_lineWidth; // ADD_NEW_MEMBERS_HERE }; #ifdef __CHART_MODEL_CARTESIAN_DECLARE__ // #endif // __CHART_MODEL_CARTESIAN_DECLARE__ } // namespace #endif //__CHART_MODEL_CARTESIAN_H__ workbench-1.1.1/src/Charting/ChartModelDataSeries.cxx000066400000000000000000000043551255417355300225560ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CHART_MODEL_DATA_SERIES_DECLARE__ #include "ChartModelDataSeries.h" #undef __CHART_MODEL_DATA_SERIES_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::ChartModelDataSeries * \brief Chart model for line series charts. * \ingroup Charting */ /** * Constructor. */ ChartModelDataSeries::ChartModelDataSeries() : ChartModelCartesian(ChartDataTypeEnum::CHART_DATA_TYPE_LINE_DATA_SERIES, ChartAxisUnitsEnum::CHART_AXIS_UNITS_NONE, ChartAxisUnitsEnum::CHART_AXIS_UNITS_NONE) { } /** * Destructor. */ ChartModelDataSeries::~ChartModelDataSeries() { } /** * Copy constructor. * @param obj * Object that is copied. */ ChartModelDataSeries::ChartModelDataSeries(const ChartModelDataSeries& obj) : ChartModelCartesian(obj) { this->copyHelperChartModelDataSeries(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ ChartModelDataSeries& ChartModelDataSeries::operator=(const ChartModelDataSeries& obj) { if (this != &obj) { ChartModelCartesian::operator=(obj); this->copyHelperChartModelDataSeries(obj); } return *this; } /** * Helps with copying an object of this type. * @param obj * Object that is copied. */ void ChartModelDataSeries::copyHelperChartModelDataSeries(const ChartModelDataSeries& /*obj*/) { } workbench-1.1.1/src/Charting/ChartModelDataSeries.h000066400000000000000000000031731255417355300222000ustar00rootroot00000000000000#ifndef __CHART_MODEL_DATA_SERIES_H__ #define __CHART_MODEL_DATA_SERIES_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "ChartModelCartesian.h" namespace caret { class ChartModelDataSeries : public ChartModelCartesian { public: ChartModelDataSeries(); virtual ~ChartModelDataSeries(); ChartModelDataSeries(const ChartModelDataSeries& obj); ChartModelDataSeries& operator=(const ChartModelDataSeries& obj); // ADD_NEW_METHODS_HERE private: void copyHelperChartModelDataSeries(const ChartModelDataSeries& obj); // ADD_NEW_MEMBERS_HERE }; #ifdef __CHART_MODEL_DATA_SERIES_DECLARE__ // #endif // __CHART_MODEL_DATA_SERIES_DECLARE__ } // namespace #endif //__CHART_MODEL_DATA_SERIES_H__ workbench-1.1.1/src/Charting/ChartModelFrequencySeries.cxx000066400000000000000000000045411255417355300236430ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CHART_MODEL_FREQUENCY_SERIES_DECLARE__ #include "ChartModelFrequencySeries.h" #undef __CHART_MODEL_FREQUENCY_SERIES_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::ChartModelFrequencySeries * \brief Chart model for frequency series charts. * \ingroup Charting */ /** * Constructor. */ ChartModelFrequencySeries::ChartModelFrequencySeries() : ChartModelCartesian(ChartDataTypeEnum::CHART_DATA_TYPE_LINE_FREQUENCY_SERIES, ChartAxisUnitsEnum::CHART_AXIS_UNITS_FREQUENCY_HERTZ, ChartAxisUnitsEnum::CHART_AXIS_UNITS_NONE) { } /** * Destructor. */ ChartModelFrequencySeries::~ChartModelFrequencySeries() { } /** * Copy constructor. * @param obj * Object that is copied. */ ChartModelFrequencySeries::ChartModelFrequencySeries(const ChartModelFrequencySeries& obj) : ChartModelCartesian(obj) { this->copyHelperChartModelFrequencySeries(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ ChartModelFrequencySeries& ChartModelFrequencySeries::operator=(const ChartModelFrequencySeries& obj) { if (this != &obj) { ChartModelCartesian::operator=(obj); this->copyHelperChartModelFrequencySeries(obj); } return *this; } /** * Helps with copying an object of this type. * @param obj * Object that is copied. */ void ChartModelFrequencySeries::copyHelperChartModelFrequencySeries(const ChartModelFrequencySeries& /*obj*/) { } workbench-1.1.1/src/Charting/ChartModelFrequencySeries.h000066400000000000000000000033011255417355300232610ustar00rootroot00000000000000#ifndef __CHART_MODEL_FREQUENCY_SERIES_H__ #define __CHART_MODEL_FREQUENCY_SERIES_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "ChartModelCartesian.h" namespace caret { class ChartModelFrequencySeries : public ChartModelCartesian { public: ChartModelFrequencySeries(); virtual ~ChartModelFrequencySeries(); ChartModelFrequencySeries(const ChartModelFrequencySeries& obj); ChartModelFrequencySeries& operator=(const ChartModelFrequencySeries& obj); // ADD_NEW_METHODS_HERE private: void copyHelperChartModelFrequencySeries(const ChartModelFrequencySeries& obj); // ADD_NEW_MEMBERS_HERE }; #ifdef __CHART_MODEL_FREQUENCY_SERIES_DECLARE__ // #endif // __CHART_MODEL_FREQUENCY_SERIES_DECLARE__ } // namespace #endif //__CHART_MODEL_FREQUENCY_SERIES_H__ workbench-1.1.1/src/Charting/ChartModelTimeSeries.cxx000066400000000000000000000043651255417355300226040ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CHART_MODEL_TIME_SERIES_DECLARE__ #include "ChartModelTimeSeries.h" #undef __CHART_MODEL_TIME_SERIES_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::ChartModelTimeSeries * \brief Chart model for line series charts. * \ingroup Charting */ /** * Constructor. */ ChartModelTimeSeries::ChartModelTimeSeries() : ChartModelCartesian(ChartDataTypeEnum::CHART_DATA_TYPE_LINE_TIME_SERIES, ChartAxisUnitsEnum::CHART_AXIS_UNITS_TIME_SECONDS, ChartAxisUnitsEnum::CHART_AXIS_UNITS_NONE) { } /** * Destructor. */ ChartModelTimeSeries::~ChartModelTimeSeries() { } /** * Copy constructor. * @param obj * Object that is copied. */ ChartModelTimeSeries::ChartModelTimeSeries(const ChartModelTimeSeries& obj) : ChartModelCartesian(obj) { this->copyHelperChartModelTimeSeries(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ ChartModelTimeSeries& ChartModelTimeSeries::operator=(const ChartModelTimeSeries& obj) { if (this != &obj) { ChartModelCartesian::operator=(obj); this->copyHelperChartModelTimeSeries(obj); } return *this; } /** * Helps with copying an object of this type. * @param obj * Object that is copied. */ void ChartModelTimeSeries::copyHelperChartModelTimeSeries(const ChartModelTimeSeries& /*obj*/) { } workbench-1.1.1/src/Charting/ChartModelTimeSeries.h000066400000000000000000000031731255417355300222250ustar00rootroot00000000000000#ifndef __CHART_MODEL_TIME_SERIES_H__ #define __CHART_MODEL_TIME_SERIES_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "ChartModelCartesian.h" namespace caret { class ChartModelTimeSeries : public ChartModelCartesian { public: ChartModelTimeSeries(); virtual ~ChartModelTimeSeries(); ChartModelTimeSeries(const ChartModelTimeSeries& obj); ChartModelTimeSeries& operator=(const ChartModelTimeSeries& obj); // ADD_NEW_METHODS_HERE private: void copyHelperChartModelTimeSeries(const ChartModelTimeSeries& obj); // ADD_NEW_MEMBERS_HERE }; #ifdef __CHART_MODEL_TIME_SERIES_DECLARE__ // #endif // __CHART_MODEL_TIME_SERIES_DECLARE__ } // namespace #endif //__CHART_MODEL_TIME_SERIES_H__ workbench-1.1.1/src/Charting/ChartPoint.cxx000066400000000000000000000050661255417355300206420ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CHART_POINT_DECLARE__ #include "ChartPoint.h" #undef __CHART_POINT_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::ChartPoint * \brief Point displayed in a chart. * \ingroup Charting */ /** * Constructor. * * @param x * X coordinate of point. * @param y * Y coordinate of point. */ ChartPoint::ChartPoint(const float x, const float y) : CaretObject() { m_xyz[0] = x; m_xyz[1] = y; m_xyz[2] = 0.0; } /** * Destructor. */ ChartPoint::~ChartPoint() { } /** * Copy constructor. * @param obj * Object that is copied. */ ChartPoint::ChartPoint(const ChartPoint& obj) : CaretObject(obj) { this->copyHelperChartPoint(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ ChartPoint& ChartPoint::operator=(const ChartPoint& obj) { if (this != &obj) { CaretObject::operator=(obj); this->copyHelperChartPoint(obj); } return *this; } /** * Helps with copying an object of this type. * @param obj * Object that is copied. */ void ChartPoint::copyHelperChartPoint(const ChartPoint& obj) { m_xyz[0] = obj.m_xyz[0]; m_xyz[1] = obj.m_xyz[1]; m_xyz[2] = obj.m_xyz[2]; } /** * Get a description of this object's content. * @return String describing this object's content. */ AString ChartPoint::toString() const { return "ChartPoint"; } /** * @return Pointer to X & Y coordinates. */ const float* ChartPoint::getXY() const { return &m_xyz[0]; } /** * @return X coordinate. */ float ChartPoint::getX() const { return m_xyz[0]; } /** * @return Y coordinate. */ float ChartPoint::getY() const { return m_xyz[1]; } workbench-1.1.1/src/Charting/ChartPoint.h000066400000000000000000000033061255417355300202620ustar00rootroot00000000000000#ifndef __CHART_POINT_H__ #define __CHART_POINT_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" namespace caret { class ChartPoint : public CaretObject { public: ChartPoint(const float x, const float y); virtual ~ChartPoint(); ChartPoint(const ChartPoint& obj); ChartPoint& operator=(const ChartPoint& obj); const float* getXY() const; float getX() const; float getY() const; // ADD_NEW_METHODS_HERE virtual AString toString() const; private: void copyHelperChartPoint(const ChartPoint& obj); float m_xyz[3]; // ADD_NEW_MEMBERS_HERE }; #ifdef __CHART_POINT_DECLARE__ // #endif // __CHART_POINT_DECLARE__ } // namespace #endif //__CHART_POINT_H__ workbench-1.1.1/src/Charting/ChartScaleAutoRanging.cxx000066400000000000000000000342151255417355300227350ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #define __CHART_SCALE_AUTO_RANGING_DECLARE__ #include "ChartScaleAutoRanging.h" #undef __CHART_SCALE_AUTO_RANGING_DECLARE__ #include "CaretAssert.h" #include "CaretLogger.h" using namespace caret; /** * \class caret::ChartScaleAutoRanging * \brief Adjust minimum and maximum value for auto ranging. * \ingroup Charting */ /** * Constructor. */ ChartScaleAutoRanging::ChartScaleAutoRanging() : CaretObject() { } /** * Destructor. */ ChartScaleAutoRanging::~ChartScaleAutoRanging() { } /** * Get a description of this object's content. * @return String describing this object's content. */ AString ChartScaleAutoRanging::toString() const { return "ChartScaleAutoRanging"; } static const double smallNegValue = -1.0e-6; static const double smallPosValue = 1.0e-6; /** * Is the value roughly zero? * * @param value * The value * @return * True if value is roughly zero, else false. */ bool ChartScaleAutoRanging::isZero(const double value) { if ((value >= smallNegValue) && (value < smallPosValue)) { return true; } return false; } ///** // * Adjust the magnitude of a value so that it is an integral // * value that is more/less positive (if positive) or more/less negative // * (if negative). // * // * @param valueIn // * The input value. // * @param rangeIn // * Range between minimum and maximum values. // * @param increaseMagnitudeFlag // * True if increasing value, else false. // * @return // * The adjusted value. // */ //double //ChartScaleAutoRanging::adjustValueMagnitude(const double valueIn, // const double rangeIn, // const bool increaseMagnitudeFlag) //{ // double outputValue = valueIn; // double range = rangeIn; // // if (isZero(valueIn)) { // outputValue = 0.0; // } // else if (isZero(range)) { // outputValue = valueIn; // } // else { // double value = valueIn; // double scaleAmount = 1.0; // // /* // * If value is less than one scale it up to be // * greater than or equal to one // */ // if (range < 1.0) { // double originalRange = range; // int32_t counter = 0; // while (range < 1.0) { // scaleAmount *= 10.0; // range = originalRange * scaleAmount; // counter++; // if (counter > 10) { // /* // * Should never happen but don't get stuck in loop // */ // break; // } // } // // value *= scaleAmount; // } // // /* // * Only works for positive values // * Will be converted back to negative later // */ // bool flipFlag = false; // if (value < 0.0) { // flipFlag = true; // value = -value; // } // // /* // * Determine size of value (10s, 100s, 1000s, etc) // */ // const double rangeLog10 = std::log10(range); // double logLessOne = std::floor(rangeLog10); // if (logLessOne >= 1) { // --logLessOne; // } // else { // logLessOne = 0.0; // } // // const int64_t intLogValue = std::pow(10.0, logLessOne); // // /* // * Adust the value // */ // double adjValue = (increaseMagnitudeFlag // ? (value + intLogValue) // : (value - intLogValue)); // // bool addSmallPercentFlag = true; // if (addSmallPercentFlag) { // const double percentAmount = range * 0.05; // adjValue = (increaseMagnitudeFlag // ? (adjValue + percentAmount) // : (adjValue - percentAmount)); // } // // double intValue = (int64_t)adjValue; // if (intLogValue >= 1) { // intValue = (int64_t)adjValue / intLogValue; // } // // double newValue = intValue * intLogValue; // // if (flipFlag) { // newValue = -newValue; // } // // outputValue = newValue / scaleAmount; // } // // return outputValue; //} // ///** // * Increase the value. // * // * @param valueIn // * The input value. // * @param range // * Range between minimum and maximum values. // * @return // * The adjusted value. // */ //double //ChartScaleAutoRanging::adjustValueUp(double valueIn, // const double range) //{ // return adjustValueMagnitude(valueIn, range, true); //} // ///** // * Decrease the value. // * // * @param valueIn // * The input value. // * @param range // * Range between minimum and maximum values. // * @return // * The adjusted value. // */ //double //ChartScaleAutoRanging::adjustValueDown(double valueIn, // const double range) //{ // return adjustValueMagnitude(valueIn, range, false); //} // ///** // * Adjust the data min/max values to that they extend a little // * beyond the rannge of the data but at an integral value. // * // * @param minValueInOut // * Minimum value that is adjusted. // * @param maxValueInOut // * Maximum value that is adjusted. // */ //void //ChartScaleAutoRanging::adjustAxisDefaultRange(float& minValueInOut, // float& maxValueInOut) //{ // double maxValue = maxValueInOut; // double minValue = minValueInOut; // // /* // * Handle instance where max value is greater than // * min value. // */ // bool invertedRangeFlag = false; // double dataRange = maxValue - minValue; // if (dataRange < 0.0) { // std::swap(minValue, maxValue); // dataRange = -dataRange; // invertedRangeFlag = true; // } // // if (maxValue > 0) { // maxValue = adjustValueUp(maxValue, dataRange); // } // else if (maxValue < 0.0) { // maxValue = adjustValueDown(maxValue, dataRange); // } // // if (minValue > 0) { // minValue = adjustValueDown(minValue, dataRange); // } // else if (minValue < 0.0) { // minValue = adjustValueUp(minValue, dataRange); // } // // CaretLogFine("Range was (" // + AString::number(minValueInOut) // + ", " // + AString::number(maxValueInOut) // + ") now (" // + AString::number(minValue) // + ", " // + AString::number(maxValue) // + ")"); // // if (invertedRangeFlag) { // minValueInOut = maxValue; // maxValueInOut = minValue; // } // else { // minValueInOut = minValue; // maxValueInOut = maxValue; // } //} //#ifdef POW_NOT_TRUSTWORTHY ///* if roundoff errors in pow cause problems, use this: */ // //double expt(a, n) //double a; //register int n; //{ // double x; // // x = 1.; // if (n>0) for (; n>0; n--) x *= a; // else for (; n<0; n++) x /= a; // return x; //} // //#else //# define expt(a, n) pow(a, (double)(n)) //#endif static double expt(double a, int32_t n) { return std::pow(a, static_cast(n)); } /* * nicenum: find a "nice" number approximately equal to x. * Round the number if round=1, take ceiling if round=0 */ static double nicenum(double x, int round) { int expv; /* exponent of x */ double f; /* fractional part of x */ double nf; /* nice, rounded fraction */ expv = std::floor(std::log10(x)); f = x/expt(10., expv); /* between 1 and 10 */ if (round) if (f<1.5) nf = 1.; else if (f<3.) nf = 2.; else if (f<7.) nf = 5.; else nf = 10.; else if (f<=1.) nf = 1.; else if (f<=2.) nf = 2.; else if (f<=5.) nf = 5.; else nf = 10.; return nf*expt(10., expv); } //#define NTICK 5 /* desired number of tick marks */ //void //loose_label(double min, double max) //{ // char str[6], temp[20]; // int nfrac; // double d; /* tick mark spacing */ // double graphmin, graphmax; /* graph range min and max */ // double range, x; // // /* we expect min!=max */ // range = nicenum(max-min, 0); // d = nicenum(range/(NTICK-1), 1); // graphmin = floor(min/d)*d; // graphmax = ceil(max/d)*d; // nfrac = std::max(-floor(log10(d)), (double)0); /* # of fractional digits to show */ // sprintf(str, "%%.%df", nfrac); /* simplest axis labels */ // // printf("graphmin=%g graphmax=%g increment=%g\n", graphmin, graphmax, d); // for (x=graphmin; x #endif // __CHART_SCALE_AUTO_RANGING_DECLARE__ } // namespace #endif //__CHART_SCALE_AUTO_RANGING_H__ workbench-1.1.1/src/Charting/ChartSelectionModeEnum.cxx000066400000000000000000000251551255417355300231310ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __CHART_SELECTION_MODE_ENUM_DECLARE__ #include "ChartSelectionModeEnum.h" #undef __CHART_SELECTION_MODE_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::ChartSelectionModeEnum * \brief Enumerated type for chart selection mode. * * Using this enumerated type in the GUI with an EnumComboBoxTemplate * * Header File (.h) * Forward declare the data type: * class EnumComboBoxTemplate; * * Declare the member: * EnumComboBoxTemplate* m_chartSelectionModeEnumComboBox; * * Declare a slot that is called when user changes selection * private slots: * void chartSelectionModeEnumComboBoxItemActivated(); * * Implementation File (.cxx) * Include the header files * #include "EnumComboBoxTemplate.h" * #include "ChartSelectionModeEnum.h" * * Instatiate: * m_chartSelectionModeEnumComboBox = new EnumComboBoxTemplate(this); * m_chartSelectionModeEnumComboBox->setup(); * * Get notified when the user changes the selection: * QObject::connect(m_chartSelectionModeEnumComboBox, SIGNAL(itemActivated()), * this, SLOT(chartSelectionModeEnumComboBoxItemActivated())); * * Update the selection: * m_chartSelectionModeEnumComboBox->setSelectedItem(NEW_VALUE); * * Read the selection: * const ChartSelectionModeEnum::Enum VARIABLE = m_chartSelectionModeEnumComboBox->getSelectedItem(); * */ /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ ChartSelectionModeEnum::ChartSelectionModeEnum(const Enum enumValue, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCodeCounter++; this->name = name; this->guiName = guiName; } /** * Destructor. */ ChartSelectionModeEnum::~ChartSelectionModeEnum() { } /** * Initialize the enumerated metadata. */ void ChartSelectionModeEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(ChartSelectionModeEnum(CHART_SELECTION_MODE_ANY, "CHART_SELECTION_MODE_ANY", "Any item(s) can be selected")); enumData.push_back(ChartSelectionModeEnum(CHART_SELECTION_MODE_SINGLE, "CHART_SELECTION_MODE_SINGLE", "Only one item can be selected")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const ChartSelectionModeEnum* ChartSelectionModeEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const ChartSelectionModeEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString ChartSelectionModeEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const ChartSelectionModeEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ ChartSelectionModeEnum::Enum ChartSelectionModeEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = ChartSelectionModeEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ChartSelectionModeEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type ChartSelectionModeEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString ChartSelectionModeEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const ChartSelectionModeEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ ChartSelectionModeEnum::Enum ChartSelectionModeEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = ChartSelectionModeEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ChartSelectionModeEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type ChartSelectionModeEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t ChartSelectionModeEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const ChartSelectionModeEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ ChartSelectionModeEnum::Enum ChartSelectionModeEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = ChartSelectionModeEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ChartSelectionModeEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type ChartSelectionModeEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void ChartSelectionModeEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void ChartSelectionModeEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(ChartSelectionModeEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void ChartSelectionModeEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(ChartSelectionModeEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } workbench-1.1.1/src/Charting/ChartSelectionModeEnum.h000066400000000000000000000063111255417355300225470ustar00rootroot00000000000000#ifndef __CHART_SELECTION_MODE_ENUM_H__ #define __CHART_SELECTION_MODE_ENUM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class ChartSelectionModeEnum { public: /** * Enumerated values. */ enum Enum { /** Any number of items can be selected from none to all */ CHART_SELECTION_MODE_ANY, /** Only one item can be selected at any time */ CHART_SELECTION_MODE_SINGLE }; ~ChartSelectionModeEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); private: ChartSelectionModeEnum(const Enum enumValue, const AString& name, const AString& guiName); static const ChartSelectionModeEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; #ifdef __CHART_SELECTION_MODE_ENUM_DECLARE__ std::vector ChartSelectionModeEnum::enumData; bool ChartSelectionModeEnum::initializedFlag = false; int32_t ChartSelectionModeEnum::integerCodeCounter = 0; #endif // __CHART_SELECTION_MODE_ENUM_DECLARE__ } // namespace #endif //__CHART_SELECTION_MODE_ENUM_H__ workbench-1.1.1/src/Cifti/000077500000000000000000000000001255417355300153335ustar00rootroot00000000000000workbench-1.1.1/src/Cifti/CMakeLists.txt000066400000000000000000000016541255417355300201010ustar00rootroot00000000000000 # # Name of Project # PROJECT (Cifti) # # Need XML from Qt # SET(QT_DONT_USE_QTGUI) SET(QT_USE_QTNETWORK TRUE) # # Add QT for includes # INCLUDE (${QT_USE_FILE}) # # Create GIFTI Library # ADD_LIBRARY(Cifti CiftiInterface.h CiftiXMLOld.h CiftiXMLElements.h CiftiXMLReader.h CiftiXMLWriter.h CiftiFile.h CiftiXML.h CiftiMappingType.h CiftiBrainModelsMap.h CiftiLabelsMap.h CiftiParcelsMap.h CiftiScalarsMap.h CiftiSeriesMap.h CiftiVersion.h CiftiInterface.cxx CiftiXMLOld.cxx CiftiXMLElements.cxx CiftiXMLReader.cxx CiftiXMLWriter.cxx CiftiFile.cxx CiftiXML.cxx CiftiMappingType.cxx CiftiBrainModelsMap.cxx CiftiLabelsMap.cxx CiftiParcelsMap.cxx CiftiScalarsMap.cxx CiftiSeriesMap.cxx CiftiVersion.cxx ) # # Find Headers # INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/FilesBase ${CMAKE_SOURCE_DIR}/Cifti ${CMAKE_SOURCE_DIR}/Palette ${CMAKE_SOURCE_DIR}/Gifti ${CMAKE_SOURCE_DIR}/Nifti ${CMAKE_SOURCE_DIR}/Xml ${CMAKE_SOURCE_DIR}/Common ) workbench-1.1.1/src/Cifti/CiftiBrainModelsMap.cxx000066400000000000000000001124201255417355300216730ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CiftiBrainModelsMap.h" #include "CaretException.h" #include #include using namespace std; using namespace caret; void CiftiBrainModelsMap::addSurfaceModel(const int64_t& numberOfNodes, const StructureEnum::Enum& structure, const float* roi) { vector tempVector;//pass-through to the other addSurfaceModel after converting roi to vector of indices tempVector.reserve(numberOfNodes);//to make it allocate only once for (int64_t i = 0; i < numberOfNodes; ++i) { if (roi == NULL || roi[i] > 0.0f) { tempVector.push_back(i); } } addSurfaceModel(numberOfNodes, structure, tempVector); } void CiftiBrainModelsMap::addSurfaceModel(const int64_t& numberOfNodes, const StructureEnum::Enum& structure, const vector& nodeList) { if (m_surfUsed.find(structure) != m_surfUsed.end()) { throw CaretException("surface structures cannot be repeated in a brain models map"); } BrainModelPriv myModel; myModel.m_type = SURFACE; myModel.m_brainStructure = structure; myModel.m_surfaceNumberOfNodes = numberOfNodes; myModel.m_nodeIndices = nodeList; myModel.setupSurface(getNextStart());//do internal setup - also does error checking m_modelsInfo.push_back(myModel); m_surfUsed[structure] = m_modelsInfo.size() - 1; } void CiftiBrainModelsMap::BrainModelPriv::setupSurface(const int64_t& start) { if (m_surfaceNumberOfNodes < 1) { throw CaretException("surface must have at least 1 vertex"); } m_modelStart = start; int64_t listSize = (int64_t)m_nodeIndices.size(); if (listSize == 0) { throw CaretException("vertex list must have nonzero length");//NOTE: technically not required by Cifti-1, remove if problematic } m_modelEnd = start + listSize;//one after last vector used(m_surfaceNumberOfNodes, false); m_nodeToIndexLookup = vector(m_surfaceNumberOfNodes, -1);//reset all to -1 to start for (int64_t i = 0; i < listSize; ++i) { if (m_nodeIndices[i] < 0) { throw CaretException("vertex list contains negative index"); } if (m_nodeIndices[i] >= m_surfaceNumberOfNodes) { throw CaretException("vertex list contains an index that don't exist in the surface"); } if (used[m_nodeIndices[i]]) { throw CaretException("vertex list contains reused index"); } used[m_nodeIndices[i]] = true; m_nodeToIndexLookup[m_nodeIndices[i]] = start + i; } } void CiftiBrainModelsMap::addVolumeModel(const StructureEnum::Enum& structure, const vector& ijkList) { if (m_volUsed.find(structure) != m_volUsed.end()) { throw CaretException("volume structures cannot be repeated in a brain models map"); } int64_t listSize = (int64_t)ijkList.size(); if (listSize == 0) { throw CaretException("voxel list must have nonzero length");//NOTE: technically not required by Cifti-1, remove if problematic } if (listSize % 3 != 0) { throw CaretException("voxel list must have a length that is a multiple of 3"); } int64_t numElems = listSize / 3; const int64_t* dims = NULL; if (!m_ignoreVolSpace) { if (!m_haveVolumeSpace) { throw CaretException("you must set the volume space before adding volume models"); } dims = m_volSpace.getDims(); } CaretCompact3DLookup > tempLookup = m_voxelToIndexLookup;//a copy of the lookup should be faster than other methods of checking for overlap and repeat int64_t nextStart = getNextStart(); for (int64_t index = 0; index < numElems; ++index)//do all error checking before adding to lookup { int64_t index3 = index * 3; if (ijkList[index3] < 0 || ijkList[index3 + 1] < 0 || ijkList[index3 + 2] < 0) { throw CaretException("found negative index in voxel list"); } if (!m_ignoreVolSpace && (ijkList[index3] >= dims[0] || ijkList[index3 + 1] >= dims[1] || ijkList[index3 + 2] >= dims[2])) { throw CaretException("found invalid index triple in voxel list: (" + AString::number(ijkList[index3]) + ", " + AString::number(ijkList[index3 + 1]) + ", " + AString::number(ijkList[index3 + 2]) + ")"); } if (tempLookup.find(ijkList[index3], ijkList[index3 + 1], ijkList[index3 + 2]) != NULL) { throw CaretException("volume models may not reuse voxels, either internally or from other structures"); } tempLookup.at(ijkList[index3], ijkList[index3 + 1], ijkList[index3 + 2]) = pair(nextStart + index, structure); } m_voxelToIndexLookup = tempLookup; BrainModelPriv myModel; myModel.m_type = VOXELS; myModel.m_brainStructure = structure; myModel.m_voxelIndicesIJK = ijkList; myModel.m_modelStart = nextStart; myModel.m_modelEnd = nextStart + numElems;//one after last m_modelsInfo.push_back(myModel); m_volUsed[structure] = m_modelsInfo.size() - 1; } void CiftiBrainModelsMap::clear() { m_modelsInfo.clear(); m_haveVolumeSpace = false; m_ignoreVolSpace = false; m_voxelToIndexLookup.clear(); m_surfUsed.clear(); m_volUsed.clear(); } int64_t CiftiBrainModelsMap::getIndexForNode(const int64_t& node, const StructureEnum::Enum& structure) const { CaretAssert(node >= 0); map::const_iterator iter = m_surfUsed.find(structure); if (iter == m_surfUsed.end()) { return -1; } CaretAssertVectorIndex(m_modelsInfo, iter->second); const BrainModelPriv& myModel = m_modelsInfo[iter->second]; if (node >= myModel.m_surfaceNumberOfNodes) return -1; CaretAssertVectorIndex(myModel.m_nodeToIndexLookup, node); return myModel.m_nodeToIndexLookup[node]; } int64_t CiftiBrainModelsMap::getIndexForVoxel(const int64_t* ijk, StructureEnum::Enum* structureOut) const { return getIndexForVoxel(ijk[0], ijk[1], ijk[2], structureOut); } int64_t CiftiBrainModelsMap::getIndexForVoxel(const int64_t& i, const int64_t& j, const int64_t& k, StructureEnum::Enum* structureOut) const { const pair* iter = m_voxelToIndexLookup.find(i, j, k);//the lookup tolerates weirdness like negatives if (iter == NULL) return -1; if (structureOut != NULL) *structureOut = iter->second; return iter->first; } CiftiBrainModelsMap::IndexInfo CiftiBrainModelsMap::getInfoForIndex(const int64_t index) const { CaretAssert(index >= 0 && index < getLength()); IndexInfo ret; int numModels = (int)m_modelsInfo.size(); int low = 0, high = numModels - 1;//bisection search while (low != high) { int guess = (low + high) / 2; if (m_modelsInfo[guess].m_modelEnd > index)//modelEnd is 1 after last valid index, equal to next start if there is a next { if (m_modelsInfo[guess].m_modelStart > index) { high = guess - 1; } else { high = guess; low = guess; } } else { low = guess + 1; } } CaretAssert(index >= m_modelsInfo[low].m_modelStart && index < m_modelsInfo[low].m_modelEnd);//otherwise we have a broken invariant ret.m_structure = m_modelsInfo[low].m_brainStructure; ret.m_type = m_modelsInfo[low].m_type; if (ret.m_type == SURFACE) { ret.m_surfaceNode = m_modelsInfo[low].m_nodeIndices[index - m_modelsInfo[low].m_modelStart]; } else { int64_t baseIndex = 3 * (index - m_modelsInfo[low].m_modelStart); ret.m_ijk[0] = m_modelsInfo[low].m_voxelIndicesIJK[baseIndex]; ret.m_ijk[1] = m_modelsInfo[low].m_voxelIndicesIJK[baseIndex + 1]; ret.m_ijk[2] = m_modelsInfo[low].m_voxelIndicesIJK[baseIndex + 2]; } return ret; } int64_t CiftiBrainModelsMap::getLength() const { return getNextStart(); } vector CiftiBrainModelsMap::getModelInfo() const { vector ret; int numModels = (int)m_modelsInfo.size(); ret.resize(numModels); for (int i = 0; i < numModels; ++i) { ret[i].m_structure = m_modelsInfo[i].m_brainStructure; ret[i].m_type = m_modelsInfo[i].m_type; ret[i].m_indexStart = m_modelsInfo[i].m_modelStart; ret[i].m_indexCount = m_modelsInfo[i].m_modelEnd - m_modelsInfo[i].m_modelStart; } return ret; } int64_t CiftiBrainModelsMap::getNextStart() const { if (m_modelsInfo.size() == 0) return 0; return m_modelsInfo.back().m_modelEnd;//NOTE: the models are sorted by their index range, so this works } const vector& CiftiBrainModelsMap::getNodeList(const StructureEnum::Enum& structure) const { map::const_iterator iter = m_surfUsed.find(structure); if (iter == m_surfUsed.end()) { throw CaretException("getNodeList called for nonexistant structure");//throw if it doesn't exist, because we don't have a reference to return - things should identify which structures exist before calling this } CaretAssertVectorIndex(m_modelsInfo, iter->second); return m_modelsInfo[iter->second].m_nodeIndices; } vector CiftiBrainModelsMap::getSurfaceMap(const StructureEnum::Enum& structure) const { vector ret; map::const_iterator iter = m_surfUsed.find(structure); if (iter == m_surfUsed.end()) { throw CaretException("getSurfaceMap called for nonexistant structure");//also throw, for consistency } CaretAssertVectorIndex(m_modelsInfo, iter->second); const BrainModelPriv& myModel = m_modelsInfo[iter->second]; int64_t numUsed = (int64_t)myModel.m_nodeIndices.size(); ret.resize(numUsed); for (int64_t i = 0; i < numUsed; ++i) { ret[i].m_ciftiIndex = myModel.m_modelStart + i; ret[i].m_surfaceNode = myModel.m_nodeIndices[i]; } return ret; } int64_t CiftiBrainModelsMap::getSurfaceNumberOfNodes(const StructureEnum::Enum& structure) const { map::const_iterator iter = m_surfUsed.find(structure); if (iter == m_surfUsed.end()) { return -1; } CaretAssertVectorIndex(m_modelsInfo, iter->second); const BrainModelPriv& myModel = m_modelsInfo[iter->second]; return myModel.m_surfaceNumberOfNodes; } vector CiftiBrainModelsMap::getSurfaceStructureList() const { vector ret; ret.reserve(m_surfUsed.size());//we can use this to tell us how many there are, but it has reordered them int numModels = (int)m_modelsInfo.size(); for (int i = 0; i < numModels; ++i)//we need them in the order they occur in { if (m_modelsInfo[i].m_type == SURFACE) { ret.push_back(m_modelsInfo[i].m_brainStructure); } } return ret; } bool CiftiBrainModelsMap::hasSurfaceData(const StructureEnum::Enum& structure) const { map::const_iterator iter = m_surfUsed.find(structure); return (iter != m_surfUsed.end()); } vector CiftiBrainModelsMap::getFullVolumeMap() const { vector ret; int numModels = (int)m_modelsInfo.size(); for (int i = 0; i < numModels; ++i) { if (m_modelsInfo[i].m_type == VOXELS) { const BrainModelPriv& myModel = m_modelsInfo[i]; int64_t listSize = (int64_t)myModel.m_voxelIndicesIJK.size(); CaretAssert(listSize % 3 == 0); int64_t numUsed = listSize / 3; if (ret.size() == 0) ret.reserve(numUsed);//keep it from doing multiple expansion copies on the first model for (int64_t i = 0; i < numUsed; ++i) { int64_t i3 = i * 3; VolumeMap temp; temp.m_ciftiIndex = myModel.m_modelStart + i; temp.m_ijk[0] = myModel.m_voxelIndicesIJK[i3]; temp.m_ijk[1] = myModel.m_voxelIndicesIJK[i3 + 1]; temp.m_ijk[2] = myModel.m_voxelIndicesIJK[i3 + 2]; ret.push_back(temp); } } } return ret; } const VolumeSpace& CiftiBrainModelsMap::getVolumeSpace() const { CaretAssert(!m_ignoreVolSpace); if (!m_haveVolumeSpace) { throw CaretException("getVolumeSpace called when no volume space exists"); } return m_volSpace; } vector CiftiBrainModelsMap::getVolumeStructureList() const { vector ret; ret.reserve(m_volUsed.size());//we can use this to tell us how many there are, but it has reordered them int numModels = (int)m_modelsInfo.size(); for (int i = 0; i < numModels; ++i)//we need them in the order they occur in { if (m_modelsInfo[i].m_type == VOXELS) { ret.push_back(m_modelsInfo[i].m_brainStructure); } } return ret; } vector CiftiBrainModelsMap::getVolumeStructureMap(const StructureEnum::Enum& structure) const { vector ret; map::const_iterator iter = m_volUsed.find(structure); if (iter == m_volUsed.end()) { throw CaretException("getVolumeStructureMap called for nonexistant structure");//also throw, for consistency } CaretAssertVectorIndex(m_modelsInfo, iter->second); const BrainModelPriv& myModel = m_modelsInfo[iter->second]; int64_t listSize = (int64_t)myModel.m_voxelIndicesIJK.size(); CaretAssert(listSize % 3 == 0); int64_t numUsed = listSize / 3; ret.resize(numUsed); for (int64_t i = 0; i < numUsed; ++i) { int64_t i3 = i * 3; ret[i].m_ciftiIndex = myModel.m_modelStart + i; ret[i].m_ijk[0] = myModel.m_voxelIndicesIJK[i3]; ret[i].m_ijk[1] = myModel.m_voxelIndicesIJK[i3 + 1]; ret[i].m_ijk[2] = myModel.m_voxelIndicesIJK[i3 + 2]; } return ret; } const vector& CiftiBrainModelsMap::getVoxelList(const StructureEnum::Enum& structure) const { map::const_iterator iter = m_volUsed.find(structure); if (iter == m_volUsed.end()) { throw CaretException("getVoxelList called for nonexistant structure");//throw if it doesn't exist, because we don't have a reference to return - things should identify which structures exist before calling this } CaretAssertVectorIndex(m_modelsInfo, iter->second); return m_modelsInfo[iter->second].m_voxelIndicesIJK; } bool CiftiBrainModelsMap::hasVolumeData() const { return (m_volUsed.size() != 0); } bool CiftiBrainModelsMap::hasVolumeData(const StructureEnum::Enum& structure) const { map::const_iterator iter = m_volUsed.find(structure); return (iter != m_volUsed.end()); } void CiftiBrainModelsMap::setVolumeSpace(const VolumeSpace& space) { for (map::const_iterator iter = m_volUsed.begin(); iter != m_volUsed.end(); ++iter)//the main time this loop isn't empty is parsing cifti-1 { CaretAssertVectorIndex(m_modelsInfo, iter->second); const BrainModelPriv& myModel = m_modelsInfo[iter->second]; int64_t listSize = (int64_t)myModel.m_voxelIndicesIJK.size(); CaretAssert(listSize % 3 == 0); for (int64_t i3 = 0; i3 < listSize; i3 += 3) { if (!space.indexValid(myModel.m_voxelIndicesIJK[i3], myModel.m_voxelIndicesIJK[i3 + 1], myModel.m_voxelIndicesIJK[i3 + 2])) { throw CaretException("invalid voxel found for volume space"); } } } m_ignoreVolSpace = false; m_haveVolumeSpace = true; m_volSpace = space; } bool CiftiBrainModelsMap::operator==(const CiftiMappingType& rhs) const { if (rhs.getType() != getType()) return false; const CiftiBrainModelsMap& myrhs = dynamic_cast(rhs); CaretAssert(!m_ignoreVolSpace && !myrhs.m_ignoreVolSpace);//these should only be true while in the process of parsing cifti-1, never otherwise if (m_haveVolumeSpace != myrhs.m_haveVolumeSpace) return false; if (m_haveVolumeSpace && (m_volSpace != myrhs.m_volSpace)) return false; return (m_modelsInfo == myrhs.m_modelsInfo);//NOTE: these are sorted by index range, so this works } bool CiftiBrainModelsMap::approximateMatch(const CiftiMappingType& rhs, QString* explanation) const { if (rhs.getType() != getType()) { if (explanation != NULL) *explanation = CiftiMappingType::mappingTypeToName(rhs.getType()) + " mapping never matches " + CiftiMappingType::mappingTypeToName(getType()); return false; } const CiftiBrainModelsMap& myrhs = dynamic_cast(rhs);//there is no user-specified metadata, but we want informative messages, so copy and modify the code from == CaretAssert(!m_ignoreVolSpace && !myrhs.m_ignoreVolSpace);//these should only be true while in the process of parsing cifti-1, never otherwise if (m_haveVolumeSpace != myrhs.m_haveVolumeSpace) { if (explanation != NULL) *explanation = "one of the mappings has no volume data"; return false; } if (m_haveVolumeSpace && (m_volSpace != myrhs.m_volSpace)) { if (explanation != NULL) *explanation = "mappings have a different volume space"; return false; } if (m_modelsInfo != myrhs.m_modelsInfo) { if (explanation != NULL) *explanation = "mappings include different brainordinates"; return false; } return true; } bool CiftiBrainModelsMap::BrainModelPriv::operator==(const BrainModelPriv& rhs) const { if (m_brainStructure != rhs.m_brainStructure) return false; if (m_type != rhs.m_type) return false; if (m_modelStart != rhs.m_modelStart) return false; if (m_modelEnd != rhs.m_modelEnd) return false; if (m_type == SURFACE) { if (m_surfaceNumberOfNodes != rhs.m_surfaceNumberOfNodes) return false; int64_t listSize = (int64_t)m_nodeIndices.size(); CaretAssert((int64_t)rhs.m_nodeIndices.size() == listSize);//this should already be checked by start/end above for (int64_t i = 0; i < listSize; ++i) { if (m_nodeIndices[i] != rhs.m_nodeIndices[i]) return false; } } else { int64_t listSize = (int64_t)m_voxelIndicesIJK.size(); CaretAssert((int64_t)rhs.m_voxelIndicesIJK.size() == listSize);//this should already be checked by start/end above for (int64_t i = 0; i < listSize; ++i) { if (m_voxelIndicesIJK[i] != rhs.m_voxelIndicesIJK[i]) return false; } } return true; } void CiftiBrainModelsMap::readXML1(QXmlStreamReader& xml) { clear(); m_ignoreVolSpace = true;//because in cifti-1, the volume space is not in this element - so, we rely on CiftiXML to check for volume data, and set the volume space afterwards vector parsedModels; for (xml.readNext(); !xml.atEnd() && !xml.isEndElement(); xml.readNext()) { switch (xml.tokenType()) { case QXmlStreamReader::StartElement: { QStringRef name = xml.name(); if (name != "BrainModel") { throw CaretException("unexpected element in brain models map: " + name.toString()); } ParseHelperModel thisModel; thisModel.parseBrainModel1(xml); if (xml.hasError()) return; parsedModels.push_back(thisModel); break;//the readNext in the for will remove the BrainModel end element } default: break; } } if (xml.hasError()) return; sort(parsedModels.begin(), parsedModels.end()); int64_t numModels = (int64_t)parsedModels.size();//because we haven't checked them for unique values of BrainStructure yet...yeah, its paranoid int64_t curOffset = 0; for (int64_t i = 0; i < numModels; ++i) { if (parsedModels[i].m_offset != curOffset) { if (parsedModels[i].m_offset < curOffset) { throw CaretException("models overlap at index " + QString::number(parsedModels[i].m_offset) + ", model " + QString::number(i)); } else { throw CaretException("index " + QString::number(curOffset) + " is not assigned to any model"); } } curOffset += parsedModels[i].m_count; } for (int64_t i = 0; i < numModels; ++i) { if (parsedModels[i].m_type == SURFACE) { addSurfaceModel(parsedModels[i].m_surfaceNumberOfNodes, parsedModels[i].m_brainStructure, parsedModels[i].m_nodeIndices); } else { addVolumeModel(parsedModels[i].m_brainStructure, parsedModels[i].m_voxelIndicesIJK); } } m_ignoreVolSpace = false;//in case there are no voxels, but some will be added later CaretAssert(xml.isEndElement() && xml.name() == "MatrixIndicesMap"); } void CiftiBrainModelsMap::readXML2(QXmlStreamReader& xml) { clear(); vector parsedModels; for (xml.readNext(); !xml.atEnd() && !xml.isEndElement(); xml.readNext()) { switch (xml.tokenType()) { case QXmlStreamReader::StartElement: { QStringRef name = xml.name(); if (name == "BrainModel") { ParseHelperModel thisModel; thisModel.parseBrainModel2(xml); if (xml.hasError()) break; parsedModels.push_back(thisModel); } else if (name == "Volume") { if (m_haveVolumeSpace) { throw CaretException("Volume specified more than once in Brain Models mapping type"); } else { m_volSpace.readCiftiXML2(xml); if (xml.hasError()) return; m_haveVolumeSpace = true; } } else { throw CaretException("unexpected element in brain models map: " + name.toString()); } break;//the readNext in the for will remove the BrainModel or Volume end element } default: break; } } if (xml.hasError()) return; sort(parsedModels.begin(), parsedModels.end()); int64_t numModels = (int64_t)parsedModels.size();//because we haven't checked them for unique values of BrainStructure yet...yeah, its paranoid int64_t curOffset = 0; for (int64_t i = 0; i < numModels; ++i) { if (parsedModels[i].m_offset != curOffset) { if (parsedModels[i].m_offset < curOffset) { throw CaretException("models overlap at index " + QString::number(parsedModels[i].m_offset) + ", model " + QString::number(i)); } else { throw CaretException("index " + QString::number(curOffset) + " is not assigned to any model"); } } curOffset += parsedModels[i].m_count; } for (int64_t i = 0; i < numModels; ++i) { if (parsedModels[i].m_type == SURFACE) { addSurfaceModel(parsedModels[i].m_surfaceNumberOfNodes, parsedModels[i].m_brainStructure, parsedModels[i].m_nodeIndices); } else { addVolumeModel(parsedModels[i].m_brainStructure, parsedModels[i].m_voxelIndicesIJK); } } CaretAssert(xml.isEndElement() && xml.name() == "MatrixIndicesMap"); } void CiftiBrainModelsMap::ParseHelperModel::parseBrainModel1(QXmlStreamReader& xml) { QXmlStreamAttributes attrs = xml.attributes(); if (!attrs.hasAttribute("ModelType")) { throw CaretException("BrainModel missing required attribute ModelType"); } QStringRef value = attrs.value("ModelType"); if (value == "CIFTI_MODEL_TYPE_SURFACE") { m_type = SURFACE; } else if (value == "CIFTI_MODEL_TYPE_VOXELS") { m_type = VOXELS; } else { throw CaretException("invalid value for ModelType: " + value.toString()); } if (!attrs.hasAttribute("BrainStructure")) { throw CaretException("BrainModel missing required attribute BrainStructure"); } value = attrs.value("BrainStructure"); bool ok = false; m_brainStructure = StructureEnum::fromCiftiName(value.toString(), &ok); if (!ok) { throw CaretException("invalid value for BrainStructure: " + value.toString()); } if (!attrs.hasAttribute("IndexOffset")) { throw CaretException("BrainModel missing required attribute IndexOffset"); } value = attrs.value("IndexOffset"); m_offset = value.toString().toLongLong(&ok); if (!ok || m_offset < 0) { throw CaretException("IndexOffset must be a non-negative integer"); } if (!attrs.hasAttribute("IndexCount")) { throw CaretException("BrainModel missing required attribute IndexCount"); } value = attrs.value("IndexCount"); m_count = value.toString().toLongLong(&ok); if (!ok || m_count < 1)//NOTE: not technically required by cifti-1, would need some rewriting to support empty brainmodels { throw CaretException("IndexCount must be a positive integer"); } if (m_type == SURFACE) { if (!attrs.hasAttribute("SurfaceNumberOfNodes")) { throw CaretException("BrainModel missing required attribute SurfaceNumberOfNodes"); } value = attrs.value("SurfaceNumberOfNodes"); m_surfaceNumberOfNodes = value.toString().toLongLong(&ok); if (!ok || m_surfaceNumberOfNodes < 1) { throw CaretException("SurfaceNumberOfNodes must be a positive integer"); } if (!xml.readNextStartElement())//special case in cifti-1 { m_nodeIndices.resize(m_count); for (int64_t i = 0; i < m_count; ++i) { m_nodeIndices[i] = i; } } else { if (xml.name() != "NodeIndices") { throw CaretException("unexpected element in BrainModel of SURFACE type: " + xml.name().toString()); } m_nodeIndices = readIndexArray(xml); xml.readNext();//remove the end element of NodeIndices } if (xml.hasError()) return; if ((int64_t)m_nodeIndices.size() != m_count) { throw CaretException("number of vertex indices does not match IndexCount"); } } else { if (!xml.readNextStartElement()) { throw CaretException("BrainModel requires a child element"); } if (xml.name() != "VoxelIndicesIJK") { throw CaretException("unexpected element in BrainModel of VOXELS type: " + xml.name().toString()); } m_voxelIndicesIJK = readIndexArray(xml); if (xml.hasError()) return; if (m_voxelIndicesIJK.size() % 3 != 0) { throw CaretException("number of voxel indices is not a multiple of 3"); } if ((int64_t)m_voxelIndicesIJK.size() != m_count * 3) { throw CaretException("number of voxel indices does not match IndexCount"); } xml.readNext();//remove the end element of VoxelIndicesIJK } while (!xml.atEnd() && !xml.isEndElement())//locate the end element of BrainModel { switch(xml.readNext()) { case QXmlStreamReader::StartElement: throw CaretException("unexpected second element in BrainModel: " + xml.name().toString()); default: break; } } CaretAssert(xml.isEndElement() && xml.name() == "BrainModel"); } void CiftiBrainModelsMap::ParseHelperModel::parseBrainModel2(QXmlStreamReader& xml) { QXmlStreamAttributes attrs = xml.attributes(); if (!attrs.hasAttribute("ModelType")) { throw CaretException("BrainModel missing required attribute ModelType"); } QStringRef value = attrs.value("ModelType"); if (value == "CIFTI_MODEL_TYPE_SURFACE") { m_type = SURFACE; } else if (value == "CIFTI_MODEL_TYPE_VOXELS") { m_type = VOXELS; } else { throw CaretException("invalid value for ModelType: " + value.toString()); } if (!attrs.hasAttribute("BrainStructure")) { throw CaretException("BrainModel missing required attribute BrainStructure"); } value = attrs.value("BrainStructure"); bool ok = false; m_brainStructure = StructureEnum::fromCiftiName(value.toString(), &ok); if (!ok) { throw CaretException("invalid value for BrainStructure: " + value.toString()); } if (!attrs.hasAttribute("IndexOffset")) { throw CaretException("BrainModel missing required attribute IndexOffset"); } value = attrs.value("IndexOffset"); m_offset = value.toString().toLongLong(&ok); if (!ok || m_offset < 0) { throw CaretException("IndexOffset must be a non-negative integer"); } if (!attrs.hasAttribute("IndexCount")) { throw CaretException("BrainModel missing required attribute IndexCount"); } value = attrs.value("IndexCount"); m_count = value.toString().toLongLong(&ok); if (!ok || m_count < 1) { throw CaretException("IndexCount must be a positive integer"); } if (m_type == SURFACE) { if (!attrs.hasAttribute("SurfaceNumberOfVertices")) { throw CaretException("BrainModel missing required attribute SurfaceNumberOfVertices"); } value = attrs.value("SurfaceNumberOfVertices"); m_surfaceNumberOfNodes = value.toString().toLongLong(&ok); if (!ok || m_surfaceNumberOfNodes < 1) { throw CaretException("SurfaceNumberOfVertices must be a positive integer"); } if (!xml.readNextStartElement()) { throw CaretException("BrainModel requires a child element"); } if (xml.name() != "VertexIndices") { throw CaretException("unexpected element in BrainModel of SURFACE type: " + xml.name().toString()); } m_nodeIndices = readIndexArray(xml); if (xml.hasError()) return; if ((int64_t)m_nodeIndices.size() != m_count) { throw CaretException("number of vertex indices does not match IndexCount"); } xml.readNext();//remove the end element of NodeIndices } else { if (!xml.readNextStartElement()) { throw CaretException("BrainModel requires a child element"); } if (xml.name() != "VoxelIndicesIJK") { throw CaretException("unexpected element in BrainModel of VOXELS type: " + xml.name().toString()); } m_voxelIndicesIJK = readIndexArray(xml); if (xml.hasError()) return; if (m_voxelIndicesIJK.size() % 3 != 0) { throw CaretException("number of voxel indices is not a multiple of 3"); } if ((int64_t)m_voxelIndicesIJK.size() != m_count * 3) { throw CaretException("number of voxel indices does not match IndexCount"); } xml.readNext();//remove the end element of VoxelIndicesIJK } while (!xml.atEnd() && !xml.isEndElement())//locate the end element of BrainModel { switch(xml.readNext()) { case QXmlStreamReader::StartElement: throw CaretException("unexpected second element in BrainModel: " + xml.name().toString()); default: break; } } CaretAssert(xml.isEndElement() && xml.name() == "BrainModel"); } vector CiftiBrainModelsMap::ParseHelperModel::readIndexArray(QXmlStreamReader& xml) { vector ret; QString text = xml.readElementText();//raises error if it encounters a start element if (xml.hasError()) return ret; QStringList separated = text.split(QRegExp("\\s+"), QString::SkipEmptyParts); int64_t numElems = separated.size(); ret.reserve(numElems); for (int64_t i = 0; i < numElems; ++i) { bool ok = false; ret.push_back(separated[i].toLongLong(&ok)); if (!ok) { throw CaretException("found noninteger in index array: " + separated[i]); } if (ret.back() < 0) { throw CaretException("found negative integer in index array: " + separated[i]); } } return ret; } void CiftiBrainModelsMap::writeXML1(QXmlStreamWriter& xml) const { CaretAssert(!m_ignoreVolSpace); xml.writeAttribute("IndicesMapToDataType", "CIFTI_INDEX_TYPE_BRAIN_MODELS"); int numModels = (int)m_modelsInfo.size(); for (int i = 0; i < numModels; ++i) { const BrainModelPriv& myModel = m_modelsInfo[i]; xml.writeStartElement("BrainModel"); xml.writeAttribute("IndexOffset", QString::number(myModel.m_modelStart)); xml.writeAttribute("IndexCount", QString::number(myModel.m_modelEnd - myModel.m_modelStart)); xml.writeAttribute("BrainStructure", StructureEnum::toCiftiName(myModel.m_brainStructure)); if (myModel.m_type == SURFACE) { xml.writeAttribute("ModelType", "CIFTI_MODEL_TYPE_SURFACE"); xml.writeAttribute("SurfaceNumberOfNodes", QString::number(myModel.m_surfaceNumberOfNodes)); xml.writeStartElement("NodeIndices"); QString text = ""; int64_t numNodes = (int64_t)myModel.m_nodeIndices.size(); for (int64_t j = 0; j < numNodes; ++j) { if (j != 0) text += " "; text += QString::number(myModel.m_nodeIndices[j]); } xml.writeCharacters(text); xml.writeEndElement(); } else { xml.writeAttribute("ModelType", "CIFTI_MODEL_TYPE_VOXELS"); xml.writeStartElement("VoxelIndicesIJK"); QString text = ""; int64_t listSize = (int64_t)myModel.m_voxelIndicesIJK.size(); CaretAssert(listSize % 3 == 0); for (int64_t j = 0; j < listSize; j += 3) { text += QString::number(myModel.m_voxelIndicesIJK[j]) + " " + QString::number(myModel.m_voxelIndicesIJK[j + 1]) + " " + QString::number(myModel.m_voxelIndicesIJK[j + 2]) + "\n"; } xml.writeCharacters(text); xml.writeEndElement(); } xml.writeEndElement(); } } void CiftiBrainModelsMap::writeXML2(QXmlStreamWriter& xml) const { CaretAssert(!m_ignoreVolSpace); xml.writeAttribute("IndicesMapToDataType", "CIFTI_INDEX_TYPE_BRAIN_MODELS"); if (hasVolumeData())//could be m_haveVolumeSpace if we want to be able to write a volspace without having voxels, but that seems silly { m_volSpace.writeCiftiXML2(xml); } int numModels = (int)m_modelsInfo.size(); for (int i = 0; i < numModels; ++i) { const BrainModelPriv& myModel = m_modelsInfo[i]; xml.writeStartElement("BrainModel"); xml.writeAttribute("IndexOffset", QString::number(myModel.m_modelStart)); xml.writeAttribute("IndexCount", QString::number(myModel.m_modelEnd - myModel.m_modelStart)); xml.writeAttribute("BrainStructure", StructureEnum::toCiftiName(myModel.m_brainStructure)); if (myModel.m_type == SURFACE) { xml.writeAttribute("ModelType", "CIFTI_MODEL_TYPE_SURFACE"); xml.writeAttribute("SurfaceNumberOfVertices", QString::number(myModel.m_surfaceNumberOfNodes)); xml.writeStartElement("VertexIndices"); QString text = ""; int64_t numNodes = (int64_t)myModel.m_nodeIndices.size(); for (int64_t j = 0; j < numNodes; ++j) { if (j != 0) text += " "; text += QString::number(myModel.m_nodeIndices[j]); } xml.writeCharacters(text); xml.writeEndElement(); } else { xml.writeAttribute("ModelType", "CIFTI_MODEL_TYPE_VOXELS"); xml.writeStartElement("VoxelIndicesIJK"); QString text = ""; int64_t listSize = (int64_t)myModel.m_voxelIndicesIJK.size(); CaretAssert(listSize % 3 == 0); for (int64_t j = 0; j < listSize; j += 3) { text += QString::number(myModel.m_voxelIndicesIJK[j]) + " " + QString::number(myModel.m_voxelIndicesIJK[j + 1]) + " " + QString::number(myModel.m_voxelIndicesIJK[j + 2]) + "\n"; } xml.writeCharacters(text); xml.writeEndElement(); } xml.writeEndElement(); } } workbench-1.1.1/src/Cifti/CiftiBrainModelsMap.h000066400000000000000000000147731255417355300213340ustar00rootroot00000000000000#ifndef __CIFTI_BRAIN_MODELS_MAP_H__ #define __CIFTI_BRAIN_MODELS_MAP_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CiftiMappingType.h" #include "CaretCompact3DLookup.h" #include "StructureEnum.h" #include "VolumeSpace.h" #include #include #include namespace caret { class CiftiBrainModelsMap : public CiftiMappingType { public: enum ModelType { SURFACE, VOXELS }; struct SurfaceMap { int64_t m_ciftiIndex; int64_t m_surfaceNode; }; struct VolumeMap { int64_t m_ciftiIndex; int64_t m_ijk[3]; }; struct ModelInfo { ModelType m_type; StructureEnum::Enum m_structure; int64_t m_indexStart, m_indexCount;//these are intended only for summary info, use getSurfaceMap, etc for the index to vertex/voxel mappings }; struct IndexInfo { ModelType m_type; StructureEnum::Enum m_structure; int64_t m_surfaceNode;//only one of these two will be valid int64_t m_ijk[3]; }; bool hasVolumeData() const; bool hasVolumeData(const StructureEnum::Enum& structure) const; bool hasSurfaceData(const StructureEnum::Enum& structure) const; int64_t getIndexForNode(const int64_t& node, const StructureEnum::Enum& structure) const; int64_t getIndexForVoxel(const int64_t* ijk, StructureEnum::Enum* structureOut = NULL) const; int64_t getIndexForVoxel(const int64_t& i, const int64_t& j, const int64_t& k, StructureEnum::Enum* structureOut = NULL) const; IndexInfo getInfoForIndex(const int64_t index) const; std::vector getSurfaceMap(const StructureEnum::Enum& structure) const; std::vector getFullVolumeMap() const; std::vector getVolumeStructureMap(const StructureEnum::Enum& structure) const; const VolumeSpace& getVolumeSpace() const; int64_t getSurfaceNumberOfNodes(const StructureEnum::Enum& structure) const; std::vector getSurfaceStructureList() const; std::vector getVolumeStructureList() const; const std::vector& getNodeList(const StructureEnum::Enum& structure) const;//useful for copying mappings to a new dense mapping const std::vector& getVoxelList(const StructureEnum::Enum& structure) const; std::vector getModelInfo() const; CiftiBrainModelsMap() { m_haveVolumeSpace = false; m_ignoreVolSpace = false; } void addSurfaceModel(const int64_t& numberOfNodes, const StructureEnum::Enum& structure, const float* roi = NULL); void addSurfaceModel(const int64_t& numberOfNodes, const StructureEnum::Enum& structure, const std::vector& nodeList); void addVolumeModel(const StructureEnum::Enum& structure, const std::vector& ijkList); void setVolumeSpace(const VolumeSpace& space); void clear(); CiftiMappingType* clone() const { return new CiftiBrainModelsMap(*this); } MappingType getType() const { return BRAIN_MODELS; } int64_t getLength() const; bool operator==(const CiftiMappingType& rhs) const; bool approximateMatch(const CiftiMappingType& rhs, QString* explanation = NULL) const; void readXML1(QXmlStreamReader& xml); void readXML2(QXmlStreamReader& xml); void writeXML1(QXmlStreamWriter& xml) const; void writeXML2(QXmlStreamWriter& xml) const; private: struct BrainModelPriv { ModelType m_type; StructureEnum::Enum m_brainStructure; int64_t m_surfaceNumberOfNodes; std::vector m_nodeIndices; std::vector m_voxelIndicesIJK; int64_t m_modelStart, m_modelEnd;//stuff only needed for optimization - models are kept in sorted order by their index ranges std::vector m_nodeToIndexLookup; bool operator==(const BrainModelPriv& rhs) const; bool operator!=(const BrainModelPriv& rhs) const { return !((*this) == rhs); } void setupSurface(const int64_t& start); }; VolumeSpace m_volSpace; bool m_haveVolumeSpace, m_ignoreVolSpace;//second is needed for parsing cifti-1 std::vector m_modelsInfo; std::map m_surfUsed, m_volUsed; CaretCompact3DLookup > m_voxelToIndexLookup;//make one unified lookup rather than separate lookups per volume structure int64_t getNextStart() const; struct ParseHelperModel {//specifically to allow the parsed elements to be sorted before using addSurfaceModel/addVolumeModel ModelType m_type; StructureEnum::Enum m_brainStructure; int64_t m_surfaceNumberOfNodes; std::vector m_nodeIndices; std::vector m_voxelIndicesIJK; int64_t m_offset, m_count; bool operator<(const ParseHelperModel& rhs) const { if (m_offset < rhs.m_offset) return true; if (m_offset > rhs.m_offset) return false;//get the common cases first if (m_count < rhs.m_count) return true;//in case we have a zero-length model - this shouldn't happen, usually return false; } void parseBrainModel1(QXmlStreamReader& xml); void parseBrainModel2(QXmlStreamReader& xml); static std::vector readIndexArray(QXmlStreamReader& xml); }; }; } #endif //__CIFTI_BRAIN_MODELS_MAP_H__ workbench-1.1.1/src/Cifti/CiftiFile.cxx000066400000000000000000000727651255417355300177360ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CiftiFile.h" #include "ByteOrderEnum.h" #include "CaretAssert.h" #include "CaretHttpManager.h" #include "CaretLogger.h" #include "DataFileException.h" #include "FileInformation.h" #include "MultiDimArray.h" #include "MultiDimIterator.h" #include "NiftiIO.h" using namespace std; using namespace caret; //private implementation classes namespace caret { class CiftiOnDiskImpl : public CiftiFile::WriteImplInterface { mutable NiftiIO m_nifti;//because file objects aren't stateless (current position), so reading "changes" them CiftiXML m_xml;//because we need to parse it to set up the dimensions anyway public: CiftiOnDiskImpl(const QString& filename);//read-only CiftiOnDiskImpl(const QString& filename, const CiftiXML& xml, const CiftiVersion& version);//make new empty file with read/write void getRow(float* dataOut, const std::vector& indexSelect, const bool& tolerateShortRead) const; void getColumn(float* dataOut, const int64_t& index) const; const CiftiXML& getCiftiXML() const { return m_xml; } QString getFilename() const { return m_nifti.getFilename(); } void setRow(const float* dataIn, const std::vector& indexSelect); void setColumn(const float* dataIn, const int64_t& index); }; class CiftiMemoryImpl : public CiftiFile::WriteImplInterface { MultiDimArray m_array; public: CiftiMemoryImpl(const CiftiXML& xml); void getRow(float* dataOut, const std::vector& indexSelect, const bool& tolerateShortRead) const; void getColumn(float* dataOut, const int64_t& index) const; bool isInMemory() const { return true; } void setRow(const float* dataIn, const std::vector& indexSelect); void setColumn(const float* dataIn, const int64_t& index); }; class CiftiXnatImpl : public CiftiFile::ReadImplInterface { CiftiXML m_xml;//because we need to parse it to check the dimensions anyway CaretHttpRequest m_baseRequest; void init(const QString& url); void getReqAsFloats(float* data, const int64_t& dataSize, CaretHttpRequest& request) const; int64_t getSizeFromReq(CaretHttpRequest& request); public: CiftiXnatImpl(const QString& url, const QString& user, const QString& pass); CiftiXnatImpl(const QString& url);//reuse existing user/pass, or access non-protected url - in the future, maybe only the second use (private http manager) void getRow(float* dataOut, const std::vector& indexSelect, const bool& tolerateShortRead) const; void getColumn(float* dataOut, const int64_t& index) const; const CiftiXML& getCiftiXML() const { return m_xml; } }; } CiftiFile::ReadImplInterface::~ReadImplInterface() { } CiftiFile::WriteImplInterface::~WriteImplInterface() { } CiftiFile::CiftiFile(const QString& fileName) { openFile(fileName); } void CiftiFile::openFile(const QString& fileName) { m_writingImpl.grabNew(NULL); m_readingImpl.grabNew(NULL);//to make sure it closes everything first, even if the open throws m_dims.clear(); CaretPointer newRead(new CiftiOnDiskImpl(FileInformation(fileName).getAbsoluteFilePath()));//this constructor opens existing file read-only m_readingImpl = newRead;//it should be noted that if the constructor throws (if the file isn't readable), new guarantees the memory allocated for the object will be freed m_xml = newRead->getCiftiXML(); m_dims = m_xml.getDimensions(); m_onDiskVersion = m_xml.getParsedVersion(); m_fileName = fileName; } void CiftiFile::openURL(const QString& url, const QString& user, const QString& pass) { m_writingImpl.grabNew(NULL); m_readingImpl.grabNew(NULL);//to make sure it closes everything first, even if the open throws m_dims.clear(); CaretPointer newRead(new CiftiXnatImpl(url, user, pass)); m_readingImpl = newRead; m_xml = newRead->getCiftiXML(); m_dims = m_xml.getDimensions(); m_fileName = url; } void CiftiFile::openURL(const QString& url) { m_writingImpl.grabNew(NULL); m_readingImpl.grabNew(NULL);//to make sure it closes everything first, even if the open throws m_dims.clear(); CaretPointer newRead(new CiftiXnatImpl(url)); m_readingImpl = newRead; m_xml = newRead->getCiftiXML(); m_dims = m_xml.getDimensions(); m_fileName = url; } void CiftiFile::setWritingFile(const QString& fileName, const CiftiVersion& writingVersion) { m_writingFile = FileInformation(fileName).getAbsoluteFilePath();//always resolve paths as soon as they enter CiftiFile, in case some clown changes directory before writing data m_writingImpl.grabNew(NULL);//prevent writing to previous writing implementation, let the next set...() set up for writing m_onDiskVersion = writingVersion;//so that we can do on-disk writing with the old version m_fileName = fileName; } void CiftiFile::writeFile(const QString& fileName, const CiftiVersion& writingVersion) { if (m_readingImpl == NULL || m_dims.empty()) throw DataFileException("writeFile called on uninitialized CiftiFile"); FileInformation myInfo(fileName); QString canonicalFilename = myInfo.getCanonicalFilePath();//NOTE: returns EMPTY STRING for nonexistant file const CiftiOnDiskImpl* testImpl = dynamic_cast(m_readingImpl.getPointer()); bool collision = false, hadWriter = (m_writingImpl != NULL); if (testImpl != NULL && canonicalFilename != "" && FileInformation(testImpl->getFilename()).getCanonicalFilePath() == canonicalFilename) {//empty string test is so that we don't say collision if both are nonexistant - could happen if file is removed/unlinked while reading on some filesystems if (m_onDiskVersion == writingVersion) return;//don't need to copy to itself collision = true;//we need to copy to memory temporarily CaretPointer tempMemory(new CiftiMemoryImpl(m_xml)); copyImplData(m_readingImpl, tempMemory, m_dims); m_readingImpl = tempMemory;//we are about to make the old reading impl very unhappy, replace it so that if we get an error while writing, we hang onto the memory version m_writingImpl.grabNew(NULL);//and make it re-magic the writing implementation again if data is set } CaretPointer tempWrite(new CiftiOnDiskImpl(myInfo.getAbsoluteFilePath(), m_xml, writingVersion)); copyImplData(m_readingImpl, tempWrite, m_dims); if (collision)//if we rewrote the file, we need the handle to the new file, and to dump the temporary in-memory version { m_onDiskVersion = writingVersion;//also record the current version number m_readingImpl = tempWrite;//replace the temporary memory version if (hadWriter)//if it was in read-write mode { m_writingImpl = tempWrite;//set the writer too } } } void CiftiFile::convertToInMemory() { if (isInMemory()) return; m_writingFile = "";//make sure it doesn't do on-disk when set...() is called if (m_readingImpl == NULL) return;//not set up yet CaretPointer tempWrite(new CiftiMemoryImpl(m_xml));//if we get an error while reading, free the memory immediately, and don't leave m_readingImpl and m_writingImpl pointing to different things copyImplData(m_readingImpl, tempWrite, m_dims); m_writingImpl = tempWrite; m_readingImpl = tempWrite; } bool CiftiFile::isInMemory() const { if (m_readingImpl == NULL) { return (m_writingFile == "");//return what it would be if verifyWriteImpl() was called } else { return m_readingImpl->isInMemory(); } } void CiftiFile::getRow(float* dataOut, const vector& indexSelect, const bool& tolerateShortRead) const { if (m_dims.empty()) throw DataFileException("getRow called on uninitialized CiftiFile"); if (m_readingImpl == NULL) return;//NOT an error because we are pretending to have a matrix already, while we are waiting for setRow to actually start writing the file m_readingImpl->getRow(dataOut, indexSelect, tolerateShortRead); } void CiftiFile::getColumn(float* dataOut, const int64_t& index) const { if (m_dims.empty()) throw DataFileException("getColumn called on uninitialized CiftiFile"); if (m_dims.size() != 2) throw DataFileException("getColumn called on non-2D CiftiFile"); if (m_readingImpl == NULL) return;//NOT an error because we are pretending to have a matrix already, while we are waiting for setRow to actually start writing the file m_readingImpl->getColumn(dataOut, index); } void CiftiFile::setCiftiXML(const CiftiXML& xml, const bool useOldMetadata) { m_readingImpl.grabNew(NULL);//drop old implementation, as it is now invalid due to XML (and therefore matrix size) change m_writingImpl.grabNew(NULL); if (xml.getNumberOfDimensions() == 0) throw DataFileException("setCiftiXML called with 0-dimensional CiftiXML"); if (useOldMetadata) { const GiftiMetaData* oldmd = m_xml.getFileMetaData(); if (oldmd != NULL) { GiftiMetaData newmd = *oldmd;//make a copy oldmd = NULL;//don't leave a potentially dangling pointer around m_xml = xml;//because this will result in a new pointer for the metadata GiftiMetaData* changemd = m_xml.getFileMetaData(); if (changemd != NULL) { *changemd = newmd; } } else { m_xml = xml; } } else { m_xml = xml; } m_dims = m_xml.getDimensions(); for (size_t i = 0; i < m_dims.size(); ++i) { if (m_dims[i] < 1) throw DataFileException("cifti xml dimensions must be greater than zero"); } } void CiftiFile::setCiftiXML(const CiftiXMLOld& xml, const bool useOldMetadata) { QString xmlText; xml.writeXML(xmlText); CiftiXML tempXML;//so that we can use the same code path tempXML.readXML(xmlText); if (tempXML.getDimensionLength(CiftiXML::ALONG_ROW) < 1) { CiftiSeriesMap& tempMap = tempXML.getSeriesMap(CiftiXML::ALONG_ROW); tempMap.setLength(xml.getDimensionLength(CiftiXMLOld::ALONG_ROW)); } if (tempXML.getDimensionLength(CiftiXML::ALONG_COLUMN) < 1) { CiftiSeriesMap& tempMap = tempXML.getSeriesMap(CiftiXML::ALONG_COLUMN); tempMap.setLength(xml.getDimensionLength(CiftiXMLOld::ALONG_COLUMN)); } setCiftiXML(tempXML, useOldMetadata); } void CiftiFile::setRow(const float* dataIn, const vector& indexSelect) { verifyWriteImpl(); m_writingImpl->setRow(dataIn, indexSelect); } void CiftiFile::setColumn(const float* dataIn, const int64_t& index) { verifyWriteImpl(); if (m_dims.size() != 2) throw DataFileException("setColumn called on non-2D CiftiFile"); m_writingImpl->setColumn(dataIn, index); } //compatibility with old interface void CiftiFile::getRow(float* dataOut, const int64_t& index, const bool& tolerateShortRead) const { if (m_dims.empty()) throw DataFileException("getRow called on uninitialized CiftiFile"); if (m_dims.size() != 2) throw DataFileException("getRow with single index called on non-2D CiftiFile"); if (m_readingImpl == NULL) return;//NOT an error because we are pretending to have a matrix already, while we are waiting for setRow to actually start writing the file vector tempvec(1, index);//could use a member if we need more speed m_readingImpl->getRow(dataOut, tempvec, tolerateShortRead); } void CiftiFile::getRow(float* dataOut, const int64_t& index) const { getRow(dataOut, index, false);//once CiftiInterface is gone, we can collapse this into a default value } int64_t CiftiFile::getNumberOfRows() const { if (m_dims.empty()) throw DataFileException("getNumberOfRows called on uninitialized CiftiFile"); if (m_dims.size() != 2) throw DataFileException("getNumberOfRows called on non-2D CiftiFile"); return m_dims[1];//length of a column } int64_t CiftiFile::getNumberOfColumns() const { if (m_dims.empty()) throw DataFileException("getNumberOfRows called on uninitialized CiftiFile"); if (m_dims.size() != 2) throw DataFileException("getNumberOfRows called on non-2D CiftiFile"); return m_dims[0];//length of a row } void CiftiFile::setRow(const float* dataIn, const int64_t& index) { verifyWriteImpl(); if (m_dims.size() != 2) throw DataFileException("setRow with single index called on non-2D CiftiFile"); vector tempvec(1, index);//could use a member if we need more speed m_writingImpl->setRow(dataIn, tempvec); } //*///end old compatibility functions void CiftiFile::verifyWriteImpl() {//this is where the magic happens - we want to emulate being a simple in-memory file, but actually be reading/writing on-disk when possible if (m_writingImpl != NULL) return; CaretAssert(!m_dims.empty());//if the xml hasn't been set, then we can't do anything meaningful if (m_dims.empty()) throw DataFileException("setRow or setColumn attempted on uninitialized CiftiFile"); if (m_writingFile == "") { if (m_readingImpl != NULL) { convertToInMemory(); } else { m_writingImpl.grabNew(new CiftiMemoryImpl(m_xml)); } } else {//NOTE: m_onDiskVersion gets set in setWritingFile if (m_readingImpl != NULL) { CiftiOnDiskImpl* testImpl = dynamic_cast(m_readingImpl.getPointer()); if (testImpl != NULL) { QString canonicalCurrent = FileInformation(testImpl->getFilename()).getCanonicalFilePath();//returns "" if nonexistant, if unlinked while open if (canonicalCurrent != "" && canonicalCurrent == FileInformation(m_writingFile).getCanonicalFilePath())//these were already absolute { convertToInMemory();//save existing data in memory before we clobber file } } } m_writingImpl.grabNew(new CiftiOnDiskImpl(m_writingFile, m_xml, m_onDiskVersion));//this constructor makes new file for writing if (m_readingImpl != NULL) { copyImplData(m_readingImpl, m_writingImpl, m_dims); } } m_readingImpl = m_writingImpl;//read-only implementations are set up in specialized functions } void CiftiFile::copyImplData(const ReadImplInterface* from, WriteImplInterface* to, const vector& dims) { vector iterateDims(dims.begin() + 1, dims.end()); vector scratchRow(dims[0]); for (MultiDimIterator iter(iterateDims); !iter.atEnd(); ++iter) { from->getRow(scratchRow.data(), *iter, false); to->setRow(scratchRow.data(), *iter); } } CiftiMemoryImpl::CiftiMemoryImpl(const CiftiXML& xml) { CaretAssert(xml.getNumberOfDimensions() != 0); m_array.resize(xml.getDimensions()); } void CiftiMemoryImpl::getRow(float* dataOut, const vector& indexSelect, const bool&) const { const float* ref = m_array.get(1, indexSelect); int64_t rowSize = m_array.getDimensions()[0];//we don't accept 0-D CiftiXML, so this will always work for (int64_t i = 0; i < rowSize; ++i) { dataOut[i] = ref[i]; } } void CiftiMemoryImpl::getColumn(float* dataOut, const int64_t& index) const { CaretAssert(m_array.getDimensions().size() == 2);//otherwise, CiftiFile shouldn't have called this const float* ref = m_array.get(2, vector());//empty vector is intentional, only 2 dimensions exist, so no more to select from int64_t rowSize = m_array.getDimensions()[0]; int64_t colSize = m_array.getDimensions()[1]; CaretAssert(index >= 0 && index < rowSize);//because we are doing the indexing math manually for speed for (int64_t i = 0; i < colSize; ++i) { dataOut[i] = ref[index + rowSize * i]; } } void CiftiMemoryImpl::setRow(const float* dataIn, const vector& indexSelect) { float* ref = m_array.get(1, indexSelect); int64_t rowSize = m_array.getDimensions()[0];//we don't accept 0-D CiftiXML, so this will always work for (int64_t i = 0; i < rowSize; ++i) { ref[i] = dataIn[i]; } } void CiftiMemoryImpl::setColumn(const float* dataIn, const int64_t& index) { CaretAssert(m_array.getDimensions().size() == 2);//otherwise, CiftiFile shouldn't have called this float* ref = m_array.get(2, vector());//empty vector is intentional, only 2 dimensions exist, so no more to select from int64_t rowSize = m_array.getDimensions()[0]; int64_t colSize = m_array.getDimensions()[1]; CaretAssert(index >= 0 && index < rowSize);//because we are doing the indexing math manually for speed for (int64_t i = 0; i < colSize; ++i) { ref[index + rowSize * i] = dataIn[i]; } } CiftiOnDiskImpl::CiftiOnDiskImpl(const QString& filename) {//opens existing file for reading m_nifti.openRead(filename);//read-only, so we don't need write permission to read a cifti file if (m_nifti.getNumComponents() != 1) throw DataFileException("complex or rgb datatype found in file '" + filename + "', these are not supported in cifti"); const NiftiHeader& myHeader = m_nifti.getHeader(); int numExts = (int)myHeader.m_extensions.size(), whichExt = -1; for (int i = 0; i < numExts; ++i) { if (myHeader.m_extensions[i]->m_ecode == NIFTI_ECODE_CIFTI) { whichExt = i; break; } } if (whichExt == -1) throw DataFileException("no cifti extension found in file '" + filename + "'"); m_xml.readXML(QByteArray(myHeader.m_extensions[whichExt]->m_bytes.data(), myHeader.m_extensions[whichExt]->m_bytes.size()));//CiftiXML should be under 2GB vector dimCheck = m_nifti.getDimensions(); if (dimCheck.size() < 5) { if (m_xml.getParsedVersion() == CiftiVersion(1, 0) && dimCheck.size() == 2)//QUIRK: we wrote some cifti-1 files with the dimensions in dim[1] and dim[2] { CaretLogWarning("invalid dimensions in cifti file '" + filename + "', attempting recovery");//becase cifti-1 was 2D only, we can try to recover vector dimFix(4, 1); dimFix.push_back(dimCheck[0]); dimFix.push_back(dimCheck[1]); dimCheck = dimFix; m_nifti.overrideDimensions(dimCheck);//will actually get overridden again below since cifti-1 has reversed first dims } else { throw DataFileException("invalid dimensions in cifti file '" + filename + "'"); } } for (int i = 0; i < 4; ++i) { if (dimCheck[i] != 1) throw DataFileException("non-singular dimension #" + QString::number(i + 1) + " in cifti file '" + filename + "'"); } if (m_xml.getParsedVersion().hasReversedFirstDims()) { while (dimCheck.size() < 6) dimCheck.push_back(1);//just in case int64_t temp = dimCheck[4];//note: nifti dim[5] is the 5th dimension, index 4 in this vector dimCheck[4] = dimCheck[5]; dimCheck[5] = temp; m_nifti.overrideDimensions(dimCheck); } if (m_xml.getNumberOfDimensions() + 4 != (int)dimCheck.size()) throw DataFileException("XML does not match number of nifti dimensions in file " + filename + "'"); for (int i = 4; i < (int)dimCheck.size(); ++i) { if (m_xml.getDimensionLength(i - 4) < 1)//CiftiXML will only let this happen with cifti-1 { m_xml.getSeriesMap(i - 4).setLength(dimCheck[i]);//and only in a series map } else { if (m_xml.getDimensionLength(i - 4) != dimCheck[i]) { throw DataFileException("xml and nifti header disagree on matrix dimensions"); } } } } CiftiOnDiskImpl::CiftiOnDiskImpl(const QString& filename, const CiftiXML& xml, const CiftiVersion& version) {//starts writing new file NiftiHeader outHeader; outHeader.setDataType(NIFTI_TYPE_FLOAT32);//actually redundant currently, default is float32 char intentName[16]; int32_t intentCode = xml.getIntentInfo(version, intentName); outHeader.setIntent(intentCode, intentName); QByteArray xmlBytes = xml.writeXMLToQByteArray(version); CaretPointer outExtension(new NiftiExtension()); outExtension->m_ecode = NIFTI_ECODE_CIFTI; int numBytes = xmlBytes.size(); outExtension->m_bytes.resize(numBytes); for (int i = 0; i < numBytes; ++i) { outExtension->m_bytes[i] = xmlBytes[i]; } outHeader.m_extensions.push_back(outExtension); vector matrixDims = xml.getDimensions(); vector niftiDims(4, 1);//the reserved space and time dims niftiDims.insert(niftiDims.end(), matrixDims.begin(), matrixDims.end()); if (version.hasReversedFirstDims()) { vector headerDims = niftiDims; while (headerDims.size() < 6) headerDims.push_back(1);//just in case int64_t temp = headerDims[4]; headerDims[4] = headerDims[5]; headerDims[5] = temp; outHeader.setDimensions(headerDims);//give the header the reversed dimensions m_nifti.writeNew(filename, outHeader, 2, true); m_nifti.overrideDimensions(niftiDims);//and then tell the nifti reader to use the correct dimensions } else { outHeader.setDimensions(niftiDims); m_nifti.writeNew(filename, outHeader, 2, true); } m_xml = xml; } void CiftiOnDiskImpl::getRow(float* dataOut, const vector& indexSelect, const bool& tolerateShortRead) const { m_nifti.readData(dataOut, 5, indexSelect, tolerateShortRead);//5 means 4 reserved (space and time) plus the first cifti dimension } void CiftiOnDiskImpl::getColumn(float* dataOut, const int64_t& index) const { CaretAssert(m_xml.getNumberOfDimensions() == 2);//otherwise this shouldn't be called CaretAssert(index >= 0 && index < m_xml.getDimensionLength(CiftiXML::ALONG_ROW)); CaretLogFine("getColumn called on CiftiOnDiskImpl, this will be slow");//generate logging messages at a low priority vector indexSelect(2); indexSelect[0] = index; int64_t colLength = m_xml.getDimensionLength(CiftiXML::ALONG_COLUMN); for (int64_t i = 0; i < colLength; ++i)//assume if they really want getColumn on disk, they don't want their pagecache obliterated, so read it 1 element at a time { indexSelect[1] = i; m_nifti.readData(dataOut + i, 4, indexSelect);//4 means just the 4 reserved dimensions, so 1 element of the matrix } } void CiftiOnDiskImpl::setRow(const float* dataIn, const vector& indexSelect) { m_nifti.writeData(dataIn, 5, indexSelect); } void CiftiOnDiskImpl::setColumn(const float* dataIn, const int64_t& index) { CaretAssert(m_xml.getNumberOfDimensions() == 2);//otherwise this shouldn't be called CaretAssert(index >= 0 && index < m_xml.getDimensionLength(CiftiXML::ALONG_ROW)); CaretLogFine("setColumn called on CiftiOnDiskImpl, this will be slow");//generate logging messages at a low priority vector indexSelect(2); indexSelect[0] = index; int64_t colLength = m_xml.getDimensionLength(CiftiXML::ALONG_COLUMN); for (int64_t i = 0; i < colLength; ++i)//don't do RMW, so write it 1 element at a time { indexSelect[1] = i; m_nifti.writeData(dataIn + i, 4, indexSelect);//4 means just the 4 reserved dimensions, so 1 element of the matrix } } CiftiXnatImpl::CiftiXnatImpl(const QString& url, const QString& user, const QString& pass) { CaretHttpManager::setAuthentication(url, user, pass); init(url); } CiftiXnatImpl::CiftiXnatImpl(const QString& url) { init(url); } void CiftiXnatImpl::init(const QString& url) { m_baseRequest.m_url = url; m_baseRequest.m_method = CaretHttpManager::POST; int32_t start = url.indexOf('?'); bool foundSearchID = false; bool foundResource = false; while ((!foundSearchID) && (!foundResource)) { if (start == -1) { throw DataFileException("Error: searchID not found in URL string"); } if (url.mid(start + 1, 9) == "searchID=") { foundSearchID = true; } else if (url.mid(start + 1, 9) == "resource=") { foundResource = true; } start = url.indexOf('&', start + 1); } m_baseRequest.m_queries.push_back(make_pair(AString("type"), AString("dconn"))); CaretHttpRequest metadata = m_baseRequest; if (foundResource) { metadata.m_queries.push_back(make_pair(AString("metadata"), AString("true"))); } else { metadata.m_queries.push_back(make_pair(AString("metadata"), AString(""))); } CaretHttpResponse myResponse; CaretHttpManager::httpRequest(metadata, myResponse); if (!myResponse.m_ok) { throw DataFileException("Error opening URL, response code: " + AString::number(myResponse.m_responseCode)); } myResponse.m_body.push_back('\0');//null terminate it so we can construct an AString easily - CaretHttpManager is nice and pre-reserves this room for this purpose AString theBody(myResponse.m_body.data()); m_xml.readXML(theBody); if (m_xml.getNumberOfDimensions() != 2) { throw DataFileException("only 2D cifti are supported via URL at this time"); } if (m_xml.getMappingType(CiftiXML::ALONG_ROW) == CiftiMappingType::SERIES && m_xml.getDimensionLength(CiftiXML::ALONG_ROW) < 1) { CaretHttpRequest rowRequest = m_baseRequest; rowRequest.m_queries.push_back(make_pair(AString("row-index"), AString("0"))); m_xml.getSeriesMap(CiftiXML::ALONG_ROW).setLength(getSizeFromReq(rowRequest)); } if (m_xml.getMappingType(CiftiXML::ALONG_COLUMN) == CiftiMappingType::SERIES && m_xml.getDimensionLength(CiftiXML::ALONG_COLUMN) < 1) { CaretHttpRequest columnRequest = m_baseRequest; columnRequest.m_queries.push_back(make_pair(AString("column-index"), AString("0"))); m_xml.getSeriesMap(CiftiXML::ALONG_COLUMN).setLength(getSizeFromReq(columnRequest)); } CaretLogFine("Connected URL: " + url + "\nRow/Column length:" + QString::number(m_xml.getDimensionLength(CiftiXML::ALONG_ROW)) + "/" + QString::number(m_xml.getDimensionLength(CiftiXML::ALONG_COLUMN))); } void CiftiXnatImpl::getReqAsFloats(float* data, const int64_t& dataSize, CaretHttpRequest& request) const { CaretHttpResponse myResponse; CaretHttpManager::httpRequest(request, myResponse); if (!myResponse.m_ok) { throw DataFileException("Error getting row, response code: " + AString::number(myResponse.m_responseCode)); } if (myResponse.m_body.size() % 4 != 0)//expect a multiple of 4 bytes { throw DataFileException("Bad reply, number of bytes is not a multiple of 4"); } int32_t numItems = *((int32_t*)myResponse.m_body.data()); if (ByteOrderEnum::isSystemBigEndian()) { ByteSwapping::swap(numItems); } if (numItems * 4 + 4 != (int64_t)myResponse.m_body.size()) { throw DataFileException("Bad reply, number of items does not match length of reply"); } if (dataSize != numItems) { throw DataFileException("Bad reply, number of items does not match header"); } float* myPointer = (float*)(myResponse.m_body.data() + 4);//skip the first element (which is an int32) for (int i = 0; i < numItems; ++i) { data[i] = myPointer[i]; } if (ByteOrderEnum::isSystemBigEndian()) { ByteSwapping::swapArray(data, numItems); } } int64_t CiftiXnatImpl::getSizeFromReq(CaretHttpRequest& request) { CaretHttpResponse myResponse; CaretHttpManager::httpRequest(request, myResponse); if (!myResponse.m_ok) { throw DataFileException("Error getting row, response code: " + AString::number(myResponse.m_responseCode)); } if (myResponse.m_body.size() % 4 != 0)//expect a multiple of 4 bytes { throw DataFileException("Bad reply, number of bytes is not a multiple of 4"); } int32_t numItems = *((int32_t*)myResponse.m_body.data()); if (ByteOrderEnum::isSystemBigEndian()) { ByteSwapping::swap(numItems); } if (numItems * 4 + 4 != (int64_t)myResponse.m_body.size()) { throw DataFileException("Bad reply, number of items does not match length of reply"); } return numItems; } void CiftiXnatImpl::getRow(float* dataOut, const vector& indexSelect, const bool&) const { CaretAssert(indexSelect.size() == 1); CaretHttpRequest rowRequest = m_baseRequest; rowRequest.m_queries.push_back(make_pair(AString("row-index"), AString::number(indexSelect[0]))); getReqAsFloats(dataOut, m_xml.getDimensionLength(CiftiXML::ALONG_ROW), rowRequest); } void CiftiXnatImpl::getColumn(float* dataOut, const int64_t& index) const { CaretHttpRequest columnRequest = m_baseRequest; columnRequest.m_queries.push_back(make_pair(AString("column-index"), AString::number(index))); getReqAsFloats(dataOut, m_xml.getDimensionLength(CiftiXML::ALONG_COLUMN), columnRequest); } workbench-1.1.1/src/Cifti/CiftiFile.h000066400000000000000000000107031255417355300173430ustar00rootroot00000000000000#ifndef __CIFTI_FILE_H__ #define __CIFTI_FILE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretPointer.h" #include "CiftiInterface.h" #include "CiftiXML.h" #include "CiftiXMLOld.h" #include #include namespace caret { class CiftiFile : public CiftiInterface { public: CiftiFile() { } explicit CiftiFile(const QString &fileName);//calls openFile void openFile(const QString& fileName);//starts on-disk reading void openURL(const QString& url, const QString& user, const QString& pass);//open from XNAT void openURL(const QString& url);//same, without user/pass (or curently, reusing existing auth if the server matches void setWritingFile(const QString& fileName, const CiftiVersion& writingVersion = CiftiVersion());//starts on-disk writing void writeFile(const QString& fileName, const CiftiVersion& writingVersion = CiftiVersion());//leaves current state as-is, rewrites if already writing to that filename and version mismatch void convertToInMemory(); QString getFileName() const { return m_fileName; } bool isInMemory() const; void getRow(float* dataOut, const std::vector& indexSelect, const bool& tolerateShortRead = false) const;//tolerateShortRead is useful for on-disk writing when it is easiest to do RMW multiple times on a new file const std::vector& getDimensions() const { return m_dims; } void getColumn(float* dataOut, const int64_t& index) const;//for 2D only, will be slow if on disk! void setCiftiXML(const CiftiXML& xml, const bool useOldMetadata = true); void setCiftiXML(const CiftiXMLOld &xml, const bool useOldMetadata = true);//set xml from old implementation void setRow(const float* dataIn, const std::vector& indexSelect); void setColumn(const float* dataIn, const int64_t& index);//for 2D only, will be slow if on disk! void getRow(float* dataOut, const int64_t& index, const bool& tolerateShortRead) const;//backwards compatibility for old CiftiFile/CiftiInterface void getRow(float* dataOut, const int64_t& index) const; int64_t getNumberOfRows() const; int64_t getNumberOfColumns() const; void setRow(const float* dataIn, const int64_t& index);//backwards compatibility for old CiftiFile class ReadImplInterface { public: virtual void getRow(float* dataOut, const std::vector& indexSelect, const bool& tolerateShortRead) const = 0; virtual void getColumn(float* dataOut, const int64_t& index) const = 0; virtual bool isInMemory() const { return false; } virtual ~ReadImplInterface(); }; //assume if you can write to it, you can also read from it class WriteImplInterface : public ReadImplInterface { public: virtual void setRow(const float* dataIn, const std::vector& indexSelect) = 0; virtual void setColumn(const float* dataIn, const int64_t& index) = 0; virtual ~WriteImplInterface(); }; private: std::vector m_dims; CaretPointer m_writingImpl;//this will be equal to m_readingImpl when non-null CaretPointer m_readingImpl; QString m_writingFile, m_fileName; //CiftiXML m_xml;//uncomment when we drop CiftiInterface CiftiVersion m_onDiskVersion; void verifyWriteImpl(); static void copyImplData(const ReadImplInterface* from, WriteImplInterface* to, const std::vector& dims); }; } #endif //__CIFTI_FILE_H__ workbench-1.1.1/src/Cifti/CiftiInterface.cxx000066400000000000000000000431721255417355300207450ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CiftiInterface.h" #include "DataFileException.h" #include #include using namespace caret; using namespace std; CiftiInterface::CiftiInterface() { m_dataRangeValid = false; } void CiftiInterface::invalidateDataRange() { m_dataRangeValid = false; } bool CiftiInterface::getDataRangeFromAllMaps(float& minOut, float& maxOut) const { if (!m_dataRangeValid) { int64_t numRows = getNumberOfRows(), rowSize = getNumberOfColumns(); if (numRows <= 0 || rowSize <= 0) { maxOut = numeric_limits::max(); minOut = -maxOut; return false; } m_dataRangeMin = numeric_limits::max(); m_dataRangeMax = -m_dataRangeMin; vector tempRow(rowSize); for (int64_t row = 0; row < numRows; ++row) { getRow(tempRow.data(), row); for (int64_t i = 0; i < rowSize; ++i) { if (tempRow[i] > m_dataRangeMax) m_dataRangeMax = tempRow[i]; if (tempRow[i] < m_dataRangeMin) m_dataRangeMin = tempRow[i]; } } m_dataRangeValid = true; } minOut = m_dataRangeMin; maxOut = m_dataRangeMax; return true; } CiftiXMLOld CiftiInterface::getCiftiXMLOld() const { CiftiXMLOld ret; if (m_xml.getNumberOfDimensions() != 2) throw DataFileException("can't convert to old XML because number of dimensions isn't 2"); ret.readXML(m_xml.writeXMLToString(CiftiVersion(1, 0))); if (ret.getDimensionLength(CiftiXMLOld::ALONG_ROW) < 1) { ret.setRowNumberOfTimepoints(m_xml.getDimensionLength(CiftiXML::ALONG_ROW)); } if (ret.getDimensionLength(CiftiXMLOld::ALONG_COLUMN) < 1) { ret.setColumnNumberOfTimepoints(m_xml.getDimensionLength(CiftiXML::ALONG_COLUMN)); } return ret; } bool CiftiInterface::checkColumnIndex(int64_t index) const { if (index < 0 || index >= getNumberOfColumns()) { return false; } return true; } bool CiftiInterface::checkRowIndex(int64_t index) const { if (index < 0 || index >= getNumberOfRows()) { return false; } return true; } bool CiftiInterface::getColumnFromNode(float* columnOut, const int64_t node, const caret::StructureEnum::Enum structure) const { int64_t myIndex = -1; const CiftiMappingType& myGenMap = *(m_xml.getMap(CiftiXML::ALONG_ROW)); if (myGenMap.getType() == CiftiMappingType::BRAIN_MODELS) { const CiftiBrainModelsMap& myMap = (const CiftiBrainModelsMap&)myGenMap; myIndex = myMap.getIndexForNode(node, structure); } else if (myGenMap.getType() == CiftiMappingType::PARCELS) { const CiftiParcelsMap& myMap = (const CiftiParcelsMap&)myGenMap; myIndex = myMap.getIndexForNode(node, structure); } if (!checkColumnIndex(myIndex)) return false; getColumn(columnOut, myIndex); return true; } bool CiftiInterface::getColumnFromVoxel(float* columnOut, const int64_t* ijk) const { int64_t myIndex = -1; const CiftiMappingType& myGenMap = *(m_xml.getMap(CiftiXML::ALONG_ROW)); if (myGenMap.getType() == CiftiMappingType::BRAIN_MODELS) { const CiftiBrainModelsMap& myMap = (const CiftiBrainModelsMap&)myGenMap; myIndex = myMap.getIndexForVoxel(ijk); } else if (myGenMap.getType() == CiftiMappingType::PARCELS) { const CiftiParcelsMap& myMap = (const CiftiParcelsMap&)myGenMap; myIndex = myMap.getIndexForVoxel(ijk); } if (!checkColumnIndex(myIndex)) return false; getColumn(columnOut, myIndex); return true; } bool CiftiInterface::getRowFromNode(float* rowOut, const int64_t node, const caret::StructureEnum::Enum structure) const { int64_t myIndex = -1; const CiftiMappingType& myGenMap = *(m_xml.getMap(CiftiXML::ALONG_COLUMN)); if (myGenMap.getType() == CiftiMappingType::BRAIN_MODELS) { const CiftiBrainModelsMap& myMap = (const CiftiBrainModelsMap&)myGenMap; myIndex = myMap.getIndexForNode(node, structure); } else if (myGenMap.getType() == CiftiMappingType::PARCELS) { const CiftiParcelsMap& myMap = (const CiftiParcelsMap&)myGenMap; myIndex = myMap.getIndexForNode(node, structure); } if (!checkRowIndex(myIndex)) return false; getRow(rowOut, myIndex); return true; } bool CiftiInterface::getRowFromNode(float* rowOut, const int64_t node, const caret::StructureEnum::Enum structure, int64_t& rowIndexOut) const { rowIndexOut = -1; const CiftiMappingType& myGenMap = *(m_xml.getMap(CiftiXML::ALONG_COLUMN)); if (myGenMap.getType() == CiftiMappingType::BRAIN_MODELS) { const CiftiBrainModelsMap& myMap = (const CiftiBrainModelsMap&)myGenMap; rowIndexOut = myMap.getIndexForNode(node, structure); } else if (myGenMap.getType() == CiftiMappingType::PARCELS) { const CiftiParcelsMap& myMap = (const CiftiParcelsMap&)myGenMap; rowIndexOut = myMap.getIndexForNode(node, structure); } if (!checkRowIndex(rowIndexOut)) return false; getRow(rowOut, rowIndexOut); return true; } bool CiftiInterface::getRowFromVoxel(float* rowOut, const int64_t* ijk) const { int64_t myIndex = -1; const CiftiMappingType& myGenMap = *(m_xml.getMap(CiftiXML::ALONG_COLUMN)); if (myGenMap.getType() == CiftiMappingType::BRAIN_MODELS) { const CiftiBrainModelsMap& myMap = (const CiftiBrainModelsMap&)myGenMap; myIndex = myMap.getIndexForVoxel(ijk); } else if (myGenMap.getType() == CiftiMappingType::PARCELS) { const CiftiParcelsMap& myMap = (const CiftiParcelsMap&)myGenMap; myIndex = myMap.getIndexForVoxel(ijk); } if (!checkRowIndex(myIndex)) return false; getRow(rowOut, myIndex); return true; } bool CiftiInterface::getColumnFromVoxelCoordinate(float* columnOut, const float* xyz) const { int64_t myIndex = -1; int64_t ijk[3]; const CiftiMappingType& myGenMap = *(m_xml.getMap(CiftiXML::ALONG_ROW)); if (myGenMap.getType() == CiftiMappingType::BRAIN_MODELS) { const CiftiBrainModelsMap& myMap = (const CiftiBrainModelsMap&)myGenMap; myMap.getVolumeSpace().enclosingVoxel(xyz, ijk); myIndex = myMap.getIndexForVoxel(ijk); } else if (myGenMap.getType() == CiftiMappingType::PARCELS) { const CiftiParcelsMap& myMap = (const CiftiParcelsMap&)myGenMap; myMap.getVolumeSpace().enclosingVoxel(xyz, ijk); myIndex = myMap.getIndexForVoxel(ijk); } if (!checkColumnIndex(myIndex)) return false; getColumn(columnOut, myIndex); return true; } bool CiftiInterface::getRowFromVoxelCoordinate(float* rowOut, const float* xyz) const { int64_t myIndex = -1; int64_t ijk[3]; const CiftiMappingType& myGenMap = *(m_xml.getMap(CiftiXML::ALONG_COLUMN)); if (myGenMap.getType() == CiftiMappingType::BRAIN_MODELS) { const CiftiBrainModelsMap& myMap = (const CiftiBrainModelsMap&)myGenMap; myMap.getVolumeSpace().enclosingVoxel(xyz, ijk); myIndex = myMap.getIndexForVoxel(ijk); } else if (myGenMap.getType() == CiftiMappingType::PARCELS) { const CiftiParcelsMap& myMap = (const CiftiParcelsMap&)myGenMap; myMap.getVolumeSpace().enclosingVoxel(xyz, ijk); myIndex = myMap.getIndexForVoxel(ijk); } if (!checkRowIndex(myIndex)) return false; getRow(rowOut, myIndex); return true; } bool CiftiInterface::getRowFromVoxelCoordinate(float* rowOut, const float* xyz, int64_t& rowIndexOut) const { rowIndexOut = -1; int64_t ijk[3]; const CiftiMappingType& myGenMap = *(m_xml.getMap(CiftiXML::ALONG_COLUMN)); if (myGenMap.getType() == CiftiMappingType::BRAIN_MODELS) { const CiftiBrainModelsMap& myMap = (const CiftiBrainModelsMap&)myGenMap; myMap.getVolumeSpace().enclosingVoxel(xyz, ijk); rowIndexOut = myMap.getIndexForVoxel(ijk); } else if (m_xml.getMappingType(CiftiXML::ALONG_ROW) == CiftiMappingType::PARCELS) { const CiftiParcelsMap& myMap = (const CiftiParcelsMap&)myGenMap; myMap.getVolumeSpace().enclosingVoxel(xyz, ijk); rowIndexOut = myMap.getIndexForVoxel(ijk); } if (!checkRowIndex(rowIndexOut)) return false; getRow(rowOut, rowIndexOut); return true; } bool CiftiInterface::getColumnFromTimepoint(float* columnOut, const float seconds) const { const CiftiMappingType& myGenMap = *(m_xml.getMap(CiftiXML::ALONG_ROW)); if (myGenMap.getType() != CiftiMappingType::SERIES) return false; const CiftiSeriesMap& myMap = (const CiftiSeriesMap&)myGenMap; int64_t myIndex = (int64_t)floor((seconds - myMap.getStart()) / myMap.getStep() + 0.5f); if (!checkColumnIndex(myIndex)) return false; getColumn(columnOut, myIndex); return true; } //column and frame are the same value currently, this function exists only //to keep the concepts of frame and time separate from being conflated bool CiftiInterface::getColumnFromFrame(float* columnOut, const int frame) const { if(!checkColumnIndex(frame)) { return false; } getColumn(columnOut, frame); return true; } bool CiftiInterface::getRowFromTimepoint(float* rowOut, const float seconds) const { const CiftiMappingType& myGenMap = *(m_xml.getMap(CiftiXML::ALONG_COLUMN)); if (myGenMap.getType() != CiftiMappingType::SERIES) return false; const CiftiSeriesMap& myMap = (const CiftiSeriesMap&)myGenMap; int64_t myIndex = (int64_t)floor((seconds - myMap.getStart()) / myMap.getStep() + 0.5f); if (!checkColumnIndex(myIndex)) return false; getRow(rowOut, myIndex); return true; } bool CiftiInterface::getSurfaceMapForRows(vector& mappingOut, const StructureEnum::Enum structure) const { if (m_xml.getMappingType(CiftiXML::ALONG_ROW) != CiftiMappingType::BRAIN_MODELS) return false; mappingOut = m_xml.getBrainModelsMap(CiftiXML::ALONG_ROW).getSurfaceMap(structure);//will throw on structure missing return true; } bool CiftiInterface::getSurfaceMapForColumns(vector& mappingOut, const StructureEnum::Enum structure) const { if (m_xml.getMappingType(CiftiXML::ALONG_COLUMN) != CiftiMappingType::BRAIN_MODELS) return false; mappingOut = m_xml.getBrainModelsMap(CiftiXML::ALONG_COLUMN).getSurfaceMap(structure);//will throw on structure missing return true; } bool CiftiInterface::getVolumeMapForRows(vector& mappingOut) const { if (m_xml.getMappingType(CiftiXML::ALONG_ROW) != CiftiMappingType::BRAIN_MODELS) return false; mappingOut = m_xml.getBrainModelsMap(CiftiXML::ALONG_ROW).getFullVolumeMap(); return true; } bool CiftiInterface::getVolumeMapForColumns(vector& mappingOut) const { if (m_xml.getMappingType(CiftiXML::ALONG_COLUMN) != CiftiMappingType::BRAIN_MODELS) return false; mappingOut = m_xml.getBrainModelsMap(CiftiXML::ALONG_COLUMN).getFullVolumeMap(); return true; } int64_t CiftiInterface::getRowSurfaceNumberOfNodes(const StructureEnum::Enum structure) const { if (m_xml.getMappingType(CiftiXML::ALONG_ROW) == CiftiMappingType::BRAIN_MODELS) { return m_xml.getBrainModelsMap(CiftiXML::ALONG_ROW).getSurfaceNumberOfNodes(structure);//will throw on structure missing } if (m_xml.getMappingType(CiftiXML::ALONG_ROW) == CiftiMappingType::PARCELS) { return m_xml.getParcelsMap(CiftiXML::ALONG_ROW).getSurfaceNumberOfNodes(structure); } return -1; // if (m_xml.getMappingType(CiftiXML::ALONG_ROW) != CiftiMappingType::BRAIN_MODELS) return -1; // return m_xml.getBrainModelsMap(CiftiXML::ALONG_ROW).getSurfaceNumberOfNodes(structure);//will throw on structure missing } int64_t CiftiInterface::getColumnSurfaceNumberOfNodes(const StructureEnum::Enum structure) const { if (m_xml.getMappingType(CiftiXML::ALONG_COLUMN) == CiftiMappingType::BRAIN_MODELS) { return m_xml.getBrainModelsMap(CiftiXML::ALONG_COLUMN).getSurfaceNumberOfNodes(structure);//will throw on structure missing } if (m_xml.getMappingType(CiftiXML::ALONG_COLUMN) == CiftiMappingType::PARCELS) { return m_xml.getParcelsMap(CiftiXML::ALONG_COLUMN).getSurfaceNumberOfNodes(structure); } return -1; // if (m_xml.getMappingType(CiftiXML::ALONG_COLUMN) != CiftiMappingType::BRAIN_MODELS) return -1; // return m_xml.getBrainModelsMap(CiftiXML::ALONG_COLUMN).getSurfaceNumberOfNodes(structure);//will throw on structure missing } bool CiftiInterface::getRowTimestep(float& seconds) const { if (m_xml.getMappingType(CiftiXML::ALONG_ROW) != CiftiMappingType::SERIES) return false; seconds = m_xml.getSeriesMap(CiftiXML::ALONG_ROW).getStep(); return true; } bool CiftiInterface::getColumnTimestep(float& seconds) const { if (m_xml.getMappingType(CiftiXML::ALONG_COLUMN) != CiftiMappingType::SERIES) return false; seconds = m_xml.getSeriesMap(CiftiXML::ALONG_COLUMN).getStep(); return true; } bool CiftiInterface::getVolumeAttributesForPlumb(VolumeSpace::OrientTypes orientOut[3], int64_t dimensionsOut[3], float originOut[3], float spacingOut[3]) const { for (int i = 0; i < m_xml.getNumberOfDimensions(); ++i) { if (m_xml.getMappingType(i) == CiftiMappingType::BRAIN_MODELS) { const CiftiBrainModelsMap& myMap = m_xml.getBrainModelsMap(i); if (myMap.hasVolumeData()) { if (!myMap.getVolumeSpace().isPlumb()) return false; myMap.getVolumeSpace().getOrientAndSpacingForPlumb(orientOut, spacingOut, originOut); const int64_t* dims = myMap.getVolumeSpace().getDims(); dimensionsOut[0] = dims[0]; dimensionsOut[1] = dims[1]; dimensionsOut[2] = dims[2]; return true; } } else if (m_xml.getMappingType(i) == CiftiMappingType::PARCELS) { const CiftiParcelsMap& myMap = m_xml.getParcelsMap(i); if (myMap.hasVolumeData()) { if (!myMap.getVolumeSpace().isPlumb()) return false; myMap.getVolumeSpace().getOrientAndSpacingForPlumb(orientOut, spacingOut, originOut); const int64_t* dims = myMap.getVolumeSpace().getDims(); dimensionsOut[0] = dims[0]; dimensionsOut[1] = dims[1]; dimensionsOut[2] = dims[2]; return true; } } } return false; } bool CiftiInterface::hasRowVolumeData() const { if (m_xml.getMappingType(CiftiXML::ALONG_ROW) == CiftiMappingType::BRAIN_MODELS) { return m_xml.getBrainModelsMap(CiftiXML::ALONG_ROW).hasVolumeData(); } else if (m_xml.getMappingType(CiftiXML::ALONG_ROW) == CiftiMappingType::PARCELS) { return m_xml.getParcelsMap(CiftiXML::ALONG_ROW).hasVolumeData(); } return false; } bool CiftiInterface::hasColumnVolumeData() const { if (m_xml.getMappingType(CiftiXML::ALONG_COLUMN) == CiftiMappingType::BRAIN_MODELS) { return m_xml.getBrainModelsMap(CiftiXML::ALONG_COLUMN).hasVolumeData(); } else if (m_xml.getMappingType(CiftiXML::ALONG_COLUMN) == CiftiMappingType::PARCELS) { return m_xml.getParcelsMap(CiftiXML::ALONG_COLUMN).hasVolumeData(); } return false; } bool CiftiInterface::hasRowSurfaceData(const StructureEnum::Enum structure) const { if (m_xml.getMappingType(CiftiXML::ALONG_ROW) == CiftiMappingType::BRAIN_MODELS) { return m_xml.getBrainModelsMap(CiftiXML::ALONG_ROW).hasSurfaceData(structure); } else if (m_xml.getMappingType(CiftiXML::ALONG_ROW) == CiftiMappingType::PARCELS) { return m_xml.getParcelsMap(CiftiXML::ALONG_ROW).hasSurfaceData(structure); } return false; } bool CiftiInterface::hasColumnSurfaceData(const StructureEnum::Enum structure) const { if (m_xml.getMappingType(CiftiXML::ALONG_COLUMN) == CiftiMappingType::BRAIN_MODELS) { return m_xml.getBrainModelsMap(CiftiXML::ALONG_COLUMN).hasSurfaceData(structure); } else if (m_xml.getMappingType(CiftiXML::ALONG_COLUMN) == CiftiMappingType::PARCELS) { return m_xml.getParcelsMap(CiftiXML::ALONG_COLUMN).hasSurfaceData(structure); } return false; } AString CiftiInterface::getMapNameForColumnIndex(const int& index) const { if (m_xml.getMappingType(CiftiXML::ALONG_COLUMN) == CiftiMappingType::SCALARS) { return m_xml.getScalarsMap(CiftiXML::ALONG_COLUMN).getMapName(index); } else if (m_xml.getMappingType(CiftiXML::ALONG_COLUMN) == CiftiMappingType::LABELS) { return m_xml.getLabelsMap(CiftiXML::ALONG_COLUMN).getMapName(index); } return ""; } AString CiftiInterface::getMapNameForRowIndex(const int& index) const { if (m_xml.getMappingType(CiftiXML::ALONG_ROW) == CiftiMappingType::SCALARS) { return m_xml.getScalarsMap(CiftiXML::ALONG_ROW).getMapName(index); } else if (m_xml.getMappingType(CiftiXML::ALONG_ROW) == CiftiMappingType::LABELS) { return m_xml.getLabelsMap(CiftiXML::ALONG_ROW).getMapName(index); } return ""; } CiftiInterface::~CiftiInterface() { } workbench-1.1.1/src/Cifti/CiftiInterface.h000066400000000000000000000143661255417355300203750ustar00rootroot00000000000000#ifndef __CIFTI_INTERFACE_H__ #define __CIFTI_INTERFACE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CiftiXML.h" #include "CiftiXMLOld.h" namespace caret { class CiftiInterface { mutable bool m_dataRangeValid; mutable float m_dataRangeMin, m_dataRangeMax; protected: CiftiXML m_xml; CiftiInterface(); public: bool checkRowIndex(int64_t index) const; bool checkColumnIndex(int64_t index) const; ///get a row virtual void getRow(float* rowOut, const int64_t& rowIndex) const = 0; ///get a column virtual void getColumn(float* columnOut, const int64_t& columnIndex) const = 0; ///get row size virtual int64_t getNumberOfColumns() const = 0; ///get column size virtual int64_t getNumberOfRows() const = 0; ///get a reference to the XML structure const CiftiXML& getCiftiXML() const { return m_xml; } ///get the old XML structure CiftiXMLOld getCiftiXMLOld() const; ///get a row by surface and node - returns false if not found in mapping bool getRowFromNode(float* rowOut, const int64_t node, const StructureEnum::Enum structure) const; ///get a row by surface and node - returns false if not found in mapping bool getRowFromNode(float* rowOut, const int64_t node, const StructureEnum::Enum structure, int64_t& rowIndexOut) const; ///get a column by surface and node - returns false if not found in mapping bool getColumnFromNode(float* columnOut, const int64_t node, const StructureEnum::Enum structure) const; ///get a row by voxel index - returns false if not found in mapping bool getRowFromVoxel(float* rowOut, const int64_t* ijk) const; ///get a column by voxel index - returns false if not found in mapping bool getColumnFromVoxel(float* columnOut, const int64_t* ijk) const; ///get a row by voxel coordinate - returns false if not found in mapping bool getRowFromVoxelCoordinate(float* rowOut, const float* xyz) const; ///get a row by voxel coordinate - returns false if not found in mapping bool getRowFromVoxelCoordinate(float* rowOut, const float* xyz, int64_t& rowIndexOut) const; ///get a column by voxel coordinate - returns false if not found in mapping bool getColumnFromVoxelCoordinate(float* columnOut, const float* xyz) const; ///get a row by timepoint bool getRowFromTimepoint(float* rowOut, const float seconds) const; ///get a column by timepoint bool getColumnFromTimepoint(float* columnOut, const float seconds) const; ///get a column by frame bool getColumnFromFrame(float* columnOut, const int frame) const; ///get data range bool getDataRangeFromAllMaps(float& minOut, float& maxOut) const; ///called when something changes the data void invalidateDataRange(); ///get the mapping for a surface in rows, returns false and empty vector if not found bool getSurfaceMapForRows(std::vector& mappingOut, const StructureEnum::Enum structure) const; ///get the mapping for a surface in columns, returns false and empty vector if not found bool getSurfaceMapForColumns(std::vector& mappingOut, const StructureEnum::Enum structure) const; ///get the mapping for a surface in rows, returns false and empty vector if not found bool getVolumeMapForRows(std::vector& mappingOut) const; ///get the mapping for a surface in columns, returns false and empty vector if not found bool getVolumeMapForColumns(std::vector& mappingOut) const; ///get the original number of nodes of the surfaces used to make this cifti, for rows int64_t getRowSurfaceNumberOfNodes(const StructureEnum::Enum structure) const; ///get the original number of nodes of the surfaces used to make this cifti, for columns int64_t getColumnSurfaceNumberOfNodes(const StructureEnum::Enum structure) const; ///get the timestep for rows, returns false if not timeseries bool getRowTimestep(float& seconds) const; ///get the timestep for columns, returns false if not timeseries bool getColumnTimestep(float& seconds) const; ///get dimensions, spacing, origin for the volume attribute - returns false if not plumb - NOTE: only uses the volume space of the first dimension that has one bool getVolumeAttributesForPlumb(VolumeSpace::OrientTypes orientOut[3], int64_t dimensionsOut[3], float originOut[3], float spacingOut[3]) const; bool hasRowVolumeData() const; bool hasColumnVolumeData() const; bool hasRowSurfaceData(const StructureEnum::Enum structure) const; bool hasColumnSurfaceData(const StructureEnum::Enum structure) const; ///get the map name for an index along a column AString getMapNameForColumnIndex(const int& index) const; ///get the map name for an index along a row AString getMapNameForRowIndex(const int& index) const; virtual ~CiftiInterface(); }; } #endif //__CIFTI_INTERFACE_H__ workbench-1.1.1/src/Cifti/CiftiLabelsMap.cxx000066400000000000000000000233721255417355300207050ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CiftiLabelsMap.h" #include "CaretAssert.h" #include "CaretException.h" #include "CaretLogger.h" using namespace caret; void CiftiLabelsMap::clear() { m_maps.clear(); } GiftiLabelTable* CiftiLabelsMap::getMapLabelTable(const int64_t& index) const { CaretAssertVectorIndex(m_maps, index); return &(m_maps[index].m_labelTable); } GiftiMetaData* CiftiLabelsMap::getMapMetadata(const int64_t& index) const { CaretAssertVectorIndex(m_maps, index); return &(m_maps[index].m_metaData); } const QString& CiftiLabelsMap::getMapName(const int64_t& index) const { CaretAssertVectorIndex(m_maps, index); return m_maps[index].m_name; } int64_t CiftiLabelsMap::getIndexFromNumberOrName(const QString& numberOrName) const { bool ok = false; int64_t ret = numberOrName.toLongLong(&ok) - 1;//quirk: use string "1" as the first index if (ok) { if (ret < 0 || ret >= getLength()) return -1;//if it is a number, do not try to use it as a name, under any circumstances return ret; } else { int64_t length = getLength(); for (int64_t i = 0; i < length; ++i) { if (numberOrName == getMapName(i)) return i; } return -1; } } void CiftiLabelsMap::setLength(const int64_t& length) { CaretAssert(length > 0); m_maps.resize(length); } void CiftiLabelsMap::setMapName(const int64_t& index, const QString& mapName) const { CaretAssertVectorIndex(m_maps, index); m_maps[index].m_name = mapName; } bool CiftiLabelsMap::approximateMatch(const CiftiMappingType& rhs, QString* explanation) const { switch (rhs.getType()) { case SCALARS: case SERIES://maybe? case LABELS: if (getLength() != rhs.getLength()) { if (explanation != NULL) *explanation = "mappings have different length"; return false; } else return true; default: if (explanation != NULL) *explanation = CiftiMappingType::mappingTypeToName(rhs.getType()) + " mapping never matches " + CiftiMappingType::mappingTypeToName(getType()); return false; } } bool CiftiLabelsMap::operator==(const CiftiMappingType& rhs) const { if (rhs.getType() != getType()) return false; const CiftiLabelsMap& myrhs = dynamic_cast(rhs); return (m_maps == myrhs.m_maps); } bool CiftiLabelsMap::LabelMap::operator==(const LabelMap& rhs) const { if (m_name != rhs.m_name) return false; if (m_labelTable != rhs.m_labelTable) return false; return (m_metaData == rhs.m_metaData); } void CiftiLabelsMap::readXML1(QXmlStreamReader& xml) { CaretLogFiner("parsing nonstandard labels mapping type in cifti-1"); clear(); for (xml.readNext(); !xml.atEnd() && !xml.isEndElement(); xml.readNext()) { switch (xml.tokenType()) { case QXmlStreamReader::StartElement: { if (xml.name() != "NamedMap") { throw CaretException("unexpected element in labels map: " + xml.name().toString()); } LabelMap tempMap; tempMap.readXML1(xml); if (xml.hasError()) return; m_maps.push_back(tempMap); break; } default: break; } } } void CiftiLabelsMap::readXML2(QXmlStreamReader& xml) { clear(); for (xml.readNext(); !xml.atEnd() && !xml.isEndElement(); xml.readNext()) { switch (xml.tokenType()) { case QXmlStreamReader::StartElement: { if (xml.name() != "NamedMap") { throw CaretException("unexpected element in labels map: " + xml.name().toString()); } LabelMap tempMap; tempMap.readXML2(xml); if (xml.hasError()) return; m_maps.push_back(tempMap); break; } default: break; } } } void CiftiLabelsMap::LabelMap::readXML1(QXmlStreamReader& xml) { bool haveName = false, haveTable = false, haveMetaData = false; for (xml.readNext(); !xml.atEnd() && !xml.isEndElement(); xml.readNext()) { switch (xml.tokenType()) { case QXmlStreamReader::StartElement: { QStringRef name = xml.name(); if (name == "MetaData") { if (haveMetaData) { throw CaretException("MetaData specified multiple times in one NamedMap"); } m_metaData.readCiftiXML1(xml); if (xml.hasError()) return; haveMetaData = true; } else if (name == "LabelTable") { if (haveTable) { throw CaretException("LabelTable specified multiple times in one NamedMap"); } m_labelTable.readFromQXmlStreamReader(xml); if (xml.hasError()) return; haveTable = true; } else if (name == "MapName") { if (haveName) { throw CaretException("MapName specified multiple times in one NamedMap"); } m_name = xml.readElementText();//raises error if element encountered if (xml.hasError()) return; haveName = true; } else { throw CaretException("unexpected element in NamedMap: " + name.toString()); } break; } default: break; } } if (!haveName) { throw CaretException("NamedMap missing required child element MapName"); } if (!haveTable) { throw CaretException("NamedMap in labels mapping missing required child element LabelTable"); } } void CiftiLabelsMap::LabelMap::readXML2(QXmlStreamReader& xml) { bool haveName = false, haveTable = false, haveMetaData = false; for (xml.readNext(); !xml.atEnd() && !xml.isEndElement(); xml.readNext()) { switch (xml.tokenType()) { case QXmlStreamReader::StartElement: { QStringRef name = xml.name(); if (name == "MetaData") { if (haveMetaData) { throw CaretException("MetaData specified multiple times in one NamedMap"); } m_metaData.readCiftiXML2(xml); if (xml.hasError()) return; haveMetaData = true; } else if (name == "LabelTable") { if (haveTable) { throw CaretException("LabelTable specified multiple times in one NamedMap"); } m_labelTable.readFromQXmlStreamReader(xml); if (xml.hasError()) return; haveTable = true; } else if (name == "MapName") { if (haveName) { throw CaretException("MapName specified multiple times in one NamedMap"); } m_name = xml.readElementText();//raises error if element encountered if (xml.hasError()) return; haveName = true; } else { throw CaretException("unexpected element in NamedMap: " + name.toString()); } break; } default: break; } } if (!haveName) { throw CaretException("NamedMap missing required child element MapName"); } if (!haveTable) { throw CaretException("NamedMap in labels mapping missing required child element LabelTable"); } } void CiftiLabelsMap::writeXML1(QXmlStreamWriter& xml) const { CaretLogFiner("writing nonstandard labels mapping type in cifti-1"); xml.writeAttribute("IndicesMapToDataType", "CIFTI_INDEX_TYPE_LABELS"); int64_t numMaps = (int64_t)m_maps.size(); for (int64_t i = 0; i < numMaps; ++i) { xml.writeStartElement("NamedMap"); xml.writeTextElement("MapName", m_maps[i].m_name); m_maps[i].m_metaData.writeCiftiXML1(xml); m_maps[i].m_labelTable.writeAsXML(xml); xml.writeEndElement(); } } void CiftiLabelsMap::writeXML2(QXmlStreamWriter& xml) const { int64_t numMaps = (int64_t)m_maps.size(); xml.writeAttribute("IndicesMapToDataType", "CIFTI_INDEX_TYPE_LABELS"); for (int64_t i = 0; i < numMaps; ++i) { xml.writeStartElement("NamedMap"); xml.writeTextElement("MapName", m_maps[i].m_name); m_maps[i].m_metaData.writeCiftiXML2(xml); m_maps[i].m_labelTable.writeAsXML(xml); xml.writeEndElement(); } } workbench-1.1.1/src/Cifti/CiftiLabelsMap.h000066400000000000000000000054171255417355300203320ustar00rootroot00000000000000#ifndef __CIFTI_LABELS_MAP_H__ #define __CIFTI_LABELS_MAP_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CiftiMappingType.h" #include "CaretPointer.h" #include "GiftiMetaData.h" #include "GiftiLabelTable.h" #include #include namespace caret { class CiftiLabelsMap : public CiftiMappingType { public: GiftiMetaData* getMapMetadata(const int64_t& index) const;//HACK: allow modification of label table and metadata within XML without setting the xml on a file again GiftiLabelTable* getMapLabelTable(const int64_t& index) const; const QString& getMapName(const int64_t& index) const; int64_t getIndexFromNumberOrName(const QString& numberOrName) const; QString getIndexName(const int64_t& index) const { return getMapName(index); } void setMapName(const int64_t& index, const QString& mapName) const;//HACK: ditto void setLength(const int64_t& length); void clear(); CiftiMappingType* clone() const { return new CiftiLabelsMap(*this); } MappingType getType() const { return LABELS; } int64_t getLength() const { return m_maps.size(); } bool operator==(const CiftiMappingType& rhs) const; bool approximateMatch(const CiftiMappingType& rhs, QString* explanation = NULL) const; void readXML1(QXmlStreamReader& xml); void readXML2(QXmlStreamReader& xml); void writeXML1(QXmlStreamWriter& xml) const; void writeXML2(QXmlStreamWriter& xml) const; private: struct LabelMap { mutable QString m_name;//we need a better way to change metadata in an in-memory file mutable GiftiMetaData m_metaData;//ditto mutable GiftiLabelTable m_labelTable;//ditto bool operator==(const LabelMap& rhs) const; void readXML1(QXmlStreamReader& xml); void readXML2(QXmlStreamReader& xml); }; std::vector m_maps; }; } #endif //__CIFTI_LABELS_MAP_H__ workbench-1.1.1/src/Cifti/CiftiMappingType.cxx000066400000000000000000000034431255417355300212770ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CiftiMappingType.h" #include "CaretAssert.h" using namespace caret; CiftiMappingType::~CiftiMappingType() {//to ensure that the class's vtable gets defined in an object file } int64_t CiftiMappingType::getIndexFromNumberOrName(const QString& numberOrName) const { bool ok = false; int64_t ret = numberOrName.toLongLong(&ok) - 1;//quirk: use string "1" as the first index if (!ok) return -1; if (ret < 0 || ret >= getLength()) return -1; return ret; } QString CiftiMappingType::getIndexName(const int64_t&) const { return ""; } QString CiftiMappingType::mappingTypeToName(const CiftiMappingType::MappingType& type) { switch (type) { case BRAIN_MODELS: return "BRAIN_MODELS"; case PARCELS: return "PARCELS"; case SERIES: return "SERIES"; case SCALARS: return "SCALARS"; case LABELS: return "LABELS"; } CaretAssert(0); return ""; } workbench-1.1.1/src/Cifti/CiftiMappingType.h000066400000000000000000000051311255417355300207200ustar00rootroot00000000000000#ifndef __CIFTI_MAPPING_TYPE_H__ #define __CIFTI_MAPPING_TYPE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "stdint.h" #include #include #include namespace caret { class CiftiMappingType { public: enum MappingType { BRAIN_MODELS = 1,//compatibility values with old XML enum, in case someone uses the wrong enum PARCELS = 3,//fibers doesn't exist in 2.0 SERIES = 4, SCALARS = 5, LABELS = 6 }; virtual CiftiMappingType* clone() const = 0;//make a copy, preserving the actual type - NOTE: this returns a dynamic allocation that is not owned by anything virtual MappingType getType() const = 0; virtual int64_t getLength() const = 0; virtual int64_t getIndexFromNumberOrName(const QString& numberOrName) const; virtual QString getIndexName(const int64_t& index) const; virtual bool operator==(const CiftiMappingType& rhs) const = 0;//used to check for merging mappings when writing the XML - must compare EVERYTHING that goes into the XML bool operator!=(const CiftiMappingType& rhs) const { return !((*this) == rhs); } virtual bool approximateMatch(const CiftiMappingType& rhs, QString* explanation = NULL) const = 0;//check if things like doing index-wise math would make sense virtual void readXML1(QXmlStreamReader& xml) = 0;//mainly to shorten the type-specific code in CiftiXML virtual void readXML2(QXmlStreamReader& xml) = 0; virtual void writeXML1(QXmlStreamWriter& xml) const = 0; virtual void writeXML2(QXmlStreamWriter& xml) const = 0; virtual ~CiftiMappingType(); static QString mappingTypeToName(const MappingType& type); }; } #endif //__CIFTI_MAPPING_TYPE_H__ workbench-1.1.1/src/Cifti/CiftiParcelsMap.cxx000066400000000000000000000731751255417355300211020ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CiftiParcelsMap.h" #include "CaretException.h" #include "CaretLogger.h" #include #include using namespace std; using namespace caret; void CiftiParcelsMap::addParcel(const CiftiParcelsMap::Parcel& parcel) { int64_t voxelListSize = (int64_t)parcel.m_voxelIndices.size(); CaretCompact3DLookup tempLookup = m_volLookup;//a copy of the lookup should be faster than other methods of checking for overlap and repeat int64_t thisParcel = m_parcels.size(); if (voxelListSize != 0) { const int64_t* dims = NULL; if (!m_ignoreVolSpace) { if (!m_haveVolumeSpace) { throw CaretException("you must set the volume space before adding parcels that use voxels"); } dims = m_volSpace.getDims(); } for (set::const_iterator iter = parcel.m_voxelIndices.begin(); iter != parcel.m_voxelIndices.end(); ++iter)//do all error checking before adding to lookup - might be unnecessary { if (iter->m_ijk[0] < 0 || iter->m_ijk[1] < 0 || iter->m_ijk[2] < 0) { throw CaretException("found negative index triple in voxel list"); } if (!m_ignoreVolSpace && (iter->m_ijk[0] >= dims[0] || iter->m_ijk[1] >= dims[1] || iter->m_ijk[2] >= dims[2])) { throw CaretException("found invalid index triple in voxel list"); } if (tempLookup.find(iter->m_ijk) != NULL) { throw CaretException("parcels may not overlap in voxels"); } tempLookup.at(iter->m_ijk) = thisParcel; } } for (map >::const_iterator iter = parcel.m_surfaceNodes.begin(); iter != parcel.m_surfaceNodes.end(); ++iter) { map::const_iterator info = m_surfInfo.find(iter->first); if (info == m_surfInfo.end()) { throw CaretException("you must set surfaces before adding parcels that use them"); } const set& nodeSet = iter->second; if (nodeSet.size() == 0) { throw CaretException("parcels may not include empty node lists");//NOTE: technically not required by Cifti, change if problematic, but probably never allow empty list in internal state } for (set::const_iterator iter2 = nodeSet.begin(); iter2 != nodeSet.end(); ++iter2) { if (*iter2 < 0) { throw CaretException("found negative vertex in parcel"); } if (*iter2 >= info->second.m_numNodes) { throw CaretException("found invalid vertex in parcel"); } if (info->second.m_lookup[*iter2] != -1) { throw CaretException("parcels may not overlap in vertices"); } } } if (voxelListSize != 0)//all error checking done, modify { m_volLookup = tempLookup; } for (map >::const_iterator iter = parcel.m_surfaceNodes.begin(); iter != parcel.m_surfaceNodes.end(); ++iter) { map::iterator info = m_surfInfo.find(iter->first); CaretAssert(info != m_surfInfo.end()); const set& nodeSet = iter->second; for (set::const_iterator iter2 = nodeSet.begin(); iter2 != nodeSet.end(); ++iter2) { CaretAssertVectorIndex(info->second.m_lookup, *iter2); info->second.m_lookup[*iter2] = thisParcel; } } m_parcels.push_back(parcel); } void CiftiParcelsMap::addSurface(const int& numberOfNodes, const StructureEnum::Enum& structure) { map::const_iterator test = m_surfInfo.find(structure); if (test != m_surfInfo.end()) { throw CaretException("parcel surface structures may not be used more than once"); } SurfaceInfo tempInfo; tempInfo.m_numNodes = numberOfNodes; tempInfo.m_lookup.resize(numberOfNodes, -1); m_surfInfo[structure] = tempInfo; } void CiftiParcelsMap::clear() { m_haveVolumeSpace = false; m_ignoreVolSpace = false; m_parcels.clear(); m_surfInfo.clear(); m_volLookup.clear(); } void CiftiParcelsMap::setVolumeSpace(const VolumeSpace& space) { const int64_t* dims = space.getDims(); int64_t numParcels = (int64_t)m_parcels.size(); for (int64_t i = 0; i < numParcels; ++i) { const set& voxelList = m_parcels[i].m_voxelIndices; for (set::const_iterator iter = voxelList.begin(); iter != voxelList.end(); ++iter) { if (iter->m_ijk[0] >= dims[0] || iter->m_ijk[1] >= dims[1] || iter->m_ijk[2] >= dims[2]) { throw CaretException("parcels may not contain voxel indices outside the volume space"); } } } m_haveVolumeSpace = true; m_ignoreVolSpace = false; m_volSpace = space; } int64_t CiftiParcelsMap::getIndexForNode(const int64_t& node, const StructureEnum::Enum& structure) const { CaretAssert(node >= 0); map::const_iterator test = m_surfInfo.find(structure); if (test == m_surfInfo.end()) return -1; if (node >= test->second.m_numNodes) return -1; CaretAssertVectorIndex(test->second.m_lookup, node); return test->second.m_lookup[node]; } int64_t CiftiParcelsMap::getIndexForVoxel(const int64_t* ijk) const { return getIndexForVoxel(ijk[0], ijk[1], ijk[2]); } int64_t CiftiParcelsMap::getIndexForVoxel(const int64_t& i, const int64_t& j, const int64_t& k) const { const int64_t* test = m_volLookup.find(i, j, k);//the lookup tolerates weirdness like negatives if (test == NULL) return -1; return *test; } vector CiftiParcelsMap::getParcelSurfaceStructures() const { vector ret; ret.reserve(m_surfInfo.size()); for (map::const_iterator iter = m_surfInfo.begin(); iter != m_surfInfo.end(); ++iter) { ret.push_back(iter->first); } return ret; } int64_t CiftiParcelsMap::getIndexFromNumberOrName(const QString& numberOrName) const { bool ok = false; int64_t ret = numberOrName.toLongLong(&ok) - 1;//quirk: use string "1" as the first index if (ok) { if (ret < 0 || ret >= getLength()) return -1;//if it is a number, do not try to use it as a name, under any circumstances return ret; } else { int64_t length = getLength(); for (int64_t i = 0; i < length; ++i) { if (numberOrName == m_parcels[i].m_name) return i; } return -1; } } QString CiftiParcelsMap::getIndexName(const int64_t& index) const { CaretAssertVectorIndex(m_parcels, index); return m_parcels[index].m_name; } const VolumeSpace& CiftiParcelsMap::getVolumeSpace() const { CaretAssert(!m_ignoreVolSpace);//this should never be set except during parsing of cifti-1 if (!m_haveVolumeSpace) { throw CaretException("getVolumeSpace called when no volume space exists"); } return m_volSpace; } int64_t CiftiParcelsMap::getSurfaceNumberOfNodes(const StructureEnum::Enum& structure) const { map::const_iterator iter = m_surfInfo.find(structure); if (iter == m_surfInfo.end()) return -1; return iter->second.m_numNodes; } bool CiftiParcelsMap::hasSurfaceData(const StructureEnum::Enum& structure) const { if (m_surfInfo.find(structure) == m_surfInfo.end()) return false; int64_t numParcels = (int64_t)m_parcels.size(); for (int64_t i = 0; i < numParcels; ++i) { map >::const_iterator iter = m_parcels[i].m_surfaceNodes.find(structure); if (iter != m_parcels[i].m_surfaceNodes.end() && iter->second.size() != 0) return true; } return false; } bool CiftiParcelsMap::hasVolumeData() const { CaretAssert(!m_ignoreVolSpace); int64_t numParcels = (int64_t)m_parcels.size();//NOTE: this function is used when reading cifti-1 to determine whether it is an error to not have a volume space, so we can't just check m_haveVolumeSpace for (int64_t i = 0; i < numParcels; ++i) { if (m_parcels[i].m_voxelIndices.size() != 0) return true; } return false; } bool CiftiParcelsMap::approximateMatch(const CiftiMappingType& rhs, QString* explanation) const { if (rhs.getType() != getType()) { if (explanation != NULL) *explanation = CiftiMappingType::mappingTypeToName(rhs.getType()) + " mapping never matches " + CiftiMappingType::mappingTypeToName(getType()); return false; } const CiftiParcelsMap& myrhs = dynamic_cast(rhs); CaretAssert(!m_ignoreVolSpace && !myrhs.m_ignoreVolSpace); if (m_haveVolumeSpace != myrhs.m_haveVolumeSpace) { if (explanation != NULL) *explanation = "one of the mappings has no volume data"; return false; } if (m_haveVolumeSpace && (m_volSpace != myrhs.m_volSpace)) { if (explanation != NULL) *explanation = "mappings have a different volume space"; return false; } if (m_surfInfo.size() != myrhs.m_surfInfo.size()) { if (explanation != NULL) *explanation = "mappings have a different number of surfaces used"; return false;//as below, return false if they won't write the mapping part to xml the same - 1 to 1 compare only requires 1 simple loop } for (map::const_iterator iter = m_surfInfo.begin(); iter != m_surfInfo.end(); ++iter) { map::const_iterator rhsiter = myrhs.m_surfInfo.find(iter->first); if (rhsiter == myrhs.m_surfInfo.end()) {//technically, they might still have the same meaning, if the surface isn't used, but they will still write differently, so false if (explanation != NULL) *explanation = StructureEnum::toName(iter->first) + " surface expected but not found"; return false; } if (iter->second.m_numNodes != rhsiter->second.m_numNodes) { if (explanation != NULL) *explanation = "different number of vertices for surface " + StructureEnum::toName(iter->first); return false; } } if (m_parcels.size() != myrhs.m_parcels.size()) { if (explanation != NULL) *explanation = "different number of parcels"; return false; } for (int64_t i = 0; i < (int64_t)m_parcels.size(); ++i) { if (!m_parcels[i].approximateMatch(myrhs.m_parcels[i], explanation)) return false; } return true; } bool CiftiParcelsMap::operator==(const CiftiMappingType& rhs) const { if (rhs.getType() != getType()) return false; const CiftiParcelsMap& myrhs = dynamic_cast(rhs); CaretAssert(!m_ignoreVolSpace && !myrhs.m_ignoreVolSpace); if (m_haveVolumeSpace != myrhs.m_haveVolumeSpace) return false; if (m_haveVolumeSpace && m_volSpace != myrhs.m_volSpace) return false; if (m_surfInfo.size() != myrhs.m_surfInfo.size()) return false;//as below, return false if they won't write to xml the same - 1 to 1 compare only requires 1 simple loop for (map::const_iterator iter = m_surfInfo.begin(); iter != m_surfInfo.end(); ++iter) { map::const_iterator rhsiter = myrhs.m_surfInfo.find(iter->first); if (rhsiter == myrhs.m_surfInfo.end()) return false;//technically, they might still have the same meaning, if the surface isn't used, but they will still write differently, so false if (iter->second.m_numNodes != rhsiter->second.m_numNodes) return false; } return (m_parcels == myrhs.m_parcels); } bool CiftiParcelsMap::Parcel::operator==(const CiftiParcelsMap::Parcel& rhs) const { if (m_name != rhs.m_name) return false; if (m_voxelIndices != rhs.m_voxelIndices) return false; return (m_surfaceNodes == rhs.m_surfaceNodes); } //same, but don't test name bool CiftiParcelsMap::Parcel::approximateMatch(const CiftiParcelsMap::Parcel& rhs, QString* explanation) const { bool nameMatches = (m_name == rhs.m_name);//for more informative explanations if (m_voxelIndices != rhs.m_voxelIndices) { if (explanation != NULL) { if (nameMatches) { *explanation = "parcel '" + m_name + "' uses different voxels than parcel in other map"; } else { *explanation = "parcel '" + m_name + "' uses different voxels than same-index parcel '" + rhs.m_name + "' in other map"; } } return false; } if (m_surfaceNodes != rhs.m_surfaceNodes) { if (explanation != NULL) { if (nameMatches) { *explanation = "parcel '" + m_name + "' uses different surface vertices than parcel in other map"; } else { *explanation = "parcel '" + m_name + "' uses different surface vertices than same-index parcel '" + rhs.m_name + "' in other map"; } } return false; } return true; } void CiftiParcelsMap::readXML1(QXmlStreamReader& xml) { CaretLogFiner("parsing nonstandard parcels mapping type in cifti-1"); clear(); m_ignoreVolSpace = true;//cifti-1 has volume space outside the index map vector myParcels;//because we need to add the surfaces first while (xml.readNextStartElement()) { QStringRef name = xml.name(); if (name == "Surface") { QXmlStreamAttributes attrs = xml.attributes(); if (!attrs.hasAttribute("BrainStructure")) { throw CaretException("Surface element missing required attribute BrainStructure"); } bool ok = false; StructureEnum::Enum tempStructure = StructureEnum::fromCiftiName(attrs.value("BrainStructure").toString(), &ok); if (!ok) { throw CaretException("invalid value for BrainStructure: " + attrs.value("BrainStructure").toString()); } if (!attrs.hasAttribute("SurfaceNumberOfNodes")) { throw CaretException("Surface element missing required attribute SurfaceNumberOfNodes"); } int64_t numNodes = attrs.value("SurfaceNumberOfNodes").toString().toLongLong(&ok); if (!ok || numNodes < 1) { throw CaretException("invalid value for SurfaceNumberOfNodes: " + attrs.value("SurfaceNumberOfNodes").toString()); } addSurface(numNodes, tempStructure);//let the standard modification functions do error checking if (xml.readNextStartElement()) { throw CaretException("unexpected element inside Surface: " + xml.name().toString()); } } else if (name == "Parcel") { myParcels.push_back(readParcel1(xml)); } else { throw CaretException("unexpected element in parcels map: " + name.toString()); } } int64_t numParcels = (int64_t)myParcels.size(); for (int64_t i = 0; i < numParcels; ++i) { addParcel(myParcels[i]); } m_ignoreVolSpace = false;//in case there are no voxels, but some will be added later CaretAssert(xml.isEndElement() && xml.name() == "MatrixIndicesMap"); } void CiftiParcelsMap::readXML2(QXmlStreamReader& xml) { clear(); vector myParcels;//because we need to add the surfaces and volume space first while (xml.readNextStartElement()) { QStringRef name = xml.name(); if (name == "Surface") { QXmlStreamAttributes attrs = xml.attributes(); if (!attrs.hasAttribute("BrainStructure")) { throw CaretException("Surface element missing required attribute BrainStructure"); } bool ok = false; StructureEnum::Enum tempStructure = StructureEnum::fromCiftiName(attrs.value("BrainStructure").toString(), &ok); if (!ok) { throw CaretException("invalid value for BrainStructure: " + attrs.value("BrainStructure").toString()); } if (!attrs.hasAttribute("SurfaceNumberOfVertices")) { throw CaretException("Surface element missing required attribute SurfaceNumberOfVertices"); } int64_t numNodes = attrs.value("SurfaceNumberOfVertices").toString().toLongLong(&ok); if (!ok || numNodes < 1) { throw CaretException("invalid value for SurfaceNumberOfVertices: " + attrs.value("SurfaceNumberOfVertices").toString()); } addSurface(numNodes, tempStructure);//let the standard modification functions do error checking if (xml.readNextStartElement()) { throw CaretException("unexpected element inside Surface: " + xml.name().toString()); } } else if (name == "Parcel") { myParcels.push_back(readParcel2(xml)); if (xml.hasError()) return; } else if (name == "Volume") { if (m_haveVolumeSpace) { throw CaretException("Volume specified more than once in Parcels mapping type"); } else { m_volSpace.readCiftiXML2(xml); if (xml.hasError()) return; m_haveVolumeSpace = true; } } else { throw CaretException("unexpected element in parcels map: " + name.toString()); } } int64_t numParcels = (int64_t)myParcels.size(); for (int64_t i = 0; i < numParcels; ++i) { addParcel(myParcels[i]); } CaretAssert(xml.isEndElement() && xml.name() == "MatrixIndicesMap"); } CiftiParcelsMap::Parcel CiftiParcelsMap::readParcel1(QXmlStreamReader& xml) { Parcel ret; bool haveVoxels = false; QXmlStreamAttributes attrs = xml.attributes(); if (!attrs.hasAttribute("Name")) { throw CaretException("Parcel element missing required attribute Name"); } ret.m_name = attrs.value("Name").toString(); while (xml.readNextStartElement()) { QStringRef name = xml.name(); if (name == "Nodes") { QXmlStreamAttributes attrs1 = xml.attributes(); if (!attrs1.hasAttribute("BrainStructure")) { throw CaretException("Nodes element missing required attribute BrainStructure"); } bool ok = false; StructureEnum::Enum myStructure = StructureEnum::fromCiftiName(attrs1.value("BrainStructure").toString(), &ok); if (!ok) { throw CaretException("unrecognized value for BrainStructure: " + attrs1.value("BrainStructure").toString()); } if (ret.m_surfaceNodes.find(myStructure) != ret.m_surfaceNodes.end()) { throw CaretException("Nodes elements may not reuse a BrainStructure within a Parcel"); } set& mySet = ret.m_surfaceNodes[myStructure]; vector array = readIndexArray(xml); if (xml.hasError()) return ret; int64_t arraySize = (int64_t)array.size(); for (int64_t i = 0; i < arraySize; ++i) { if (mySet.find(array[i]) != mySet.end()) { throw CaretException("Nodes elements may not reuse indices"); } mySet.insert(array[i]); } } else if (name == "VoxelIndicesIJK") { if (haveVoxels) { throw CaretException("VoxelIndicesIJK may only appear once in a Parcel"); } vector array = readIndexArray(xml); if (xml.hasError()) return ret; int64_t arraySize = (int64_t)array.size(); if (arraySize % 3 != 0) { throw CaretException("number of indices in VoxelIndicesIJK must be a multiple of 3"); } for (int64_t index3 = 0; index3 < arraySize; index3 += 3) { VoxelIJK temp(array.data() + index3); if (ret.m_voxelIndices.find(temp) != ret.m_voxelIndices.end()) { throw CaretException("VoxelIndicesIJK elements may not reuse voxels"); } ret.m_voxelIndices.insert(temp); } haveVoxels = true; } else { throw CaretException("unexpected element in Parcel: " + name.toString()); } } CaretAssert(xml.isEndElement() && xml.name() == "Parcel"); return ret; } CiftiParcelsMap::Parcel CiftiParcelsMap::readParcel2(QXmlStreamReader& xml) { Parcel ret; bool haveVoxels = false; QXmlStreamAttributes attrs = xml.attributes(); if (!attrs.hasAttribute("Name")) { throw CaretException("Parcel element missing required attribute Name"); } ret.m_name = attrs.value("Name").toString(); while (xml.readNextStartElement()) { QStringRef name = xml.name(); if (name == "Vertices") { QXmlStreamAttributes attrs1 = xml.attributes(); if (!attrs1.hasAttribute("BrainStructure")) { throw CaretException("Vertices element missing required attribute BrainStructure"); } bool ok = false; StructureEnum::Enum myStructure = StructureEnum::fromCiftiName(attrs1.value("BrainStructure").toString(), &ok); if (!ok) { throw CaretException("unrecognized value for BrainStructure: " + attrs1.value("BrainStructure").toString()); } if (ret.m_surfaceNodes.find(myStructure) != ret.m_surfaceNodes.end()) { throw CaretException("Vertices elements may not reuse a BrainStructure within a Parcel"); } set& mySet = ret.m_surfaceNodes[myStructure]; vector array = readIndexArray(xml); if (xml.hasError()) return ret; int64_t arraySize = (int64_t)array.size(); for (int64_t i = 0; i < arraySize; ++i) { if (mySet.find(array[i]) != mySet.end()) { throw CaretException("Vertices elements may not reuse indices"); } mySet.insert(array[i]); } } else if (name == "VoxelIndicesIJK") { if (haveVoxels) { throw CaretException("VoxelIndicesIJK may only appear once in a Parcel"); } vector array = readIndexArray(xml); if (xml.hasError()) return ret; int64_t arraySize = (int64_t)array.size(); if (arraySize % 3 != 0) { throw CaretException("number of indices in VoxelIndicesIJK must be a multiple of 3"); } for (int64_t index3 = 0; index3 < arraySize; index3 += 3) { VoxelIJK temp(array.data() + index3); if (ret.m_voxelIndices.find(temp) != ret.m_voxelIndices.end()) { throw CaretException("VoxelIndicesIJK elements may not reuse voxels"); } ret.m_voxelIndices.insert(temp); } haveVoxels = true; } else { throw CaretException("unexpected element in Parcel: " + name.toString()); } } CaretAssert(xml.isEndElement() && xml.name() == "Parcel"); return ret; } vector CiftiParcelsMap::readIndexArray(QXmlStreamReader& xml) { vector ret; QString text = xml.readElementText();//raises error if it encounters a start element if (xml.hasError()) return ret; QStringList separated = text.split(QRegExp("\\s+"), QString::SkipEmptyParts); int64_t numElems = separated.size(); ret.reserve(numElems); for (int64_t i = 0; i < numElems; ++i) { bool ok = false; ret.push_back(separated[i].toLongLong(&ok)); if (!ok) { throw CaretException("found noninteger in index array: " + separated[i]); } } return ret; } void CiftiParcelsMap::writeXML1(QXmlStreamWriter& xml) const { CaretAssert(!m_ignoreVolSpace); CaretLogFiner("writing nonstandard parcels mapping type in cifti-1"); xml.writeAttribute("IndicesMapToDataType", "CIFTI_INDEX_TYPE_PARCELS"); for (map::const_iterator iter = m_surfInfo.begin(); iter != m_surfInfo.end(); ++iter) { xml.writeStartElement("Surface"); xml.writeAttribute("BrainStructure", StructureEnum::toCiftiName(iter->first)); xml.writeAttribute("SurfaceNumberOfNodes", QString::number(iter->second.m_numNodes)); xml.writeEndElement(); } int64_t numParcels = m_parcels.size(); for (int64_t i = 0; i < numParcels; ++i) { xml.writeStartElement("Parcel"); xml.writeAttribute("Name", m_parcels[i].m_name); int64_t numVoxels = (int64_t)m_parcels[i].m_voxelIndices.size(); if (numVoxels != 0) { xml.writeStartElement("VoxelIndicesIJK"); for (set::const_iterator iter = m_parcels[i].m_voxelIndices.begin(); iter != m_parcels[i].m_voxelIndices.end(); ++iter) { xml.writeCharacters(QString::number(iter->m_ijk[0]) + " " + QString::number(iter->m_ijk[1]) + " " + QString::number(iter->m_ijk[2]) + "\n"); } xml.writeEndElement(); } for (map >::const_iterator iter = m_parcels[i].m_surfaceNodes.begin(); iter != m_parcels[i].m_surfaceNodes.end(); ++iter) { if (iter->second.size() != 0)//prevent writing empty elements, regardless { xml.writeStartElement("Nodes"); xml.writeAttribute("BrainStructure", StructureEnum::toCiftiName(iter->first)); set::const_iterator iter2 = iter->second.begin();//which also allows us to write the first one outside the loop, to not add whitespace on the front or back xml.writeCharacters(QString::number(*iter2)); ++iter2; for (; iter2 != iter->second.end(); ++iter2) { xml.writeCharacters(" " + QString::number(*iter2)); } xml.writeEndElement(); } } xml.writeEndElement(); } } void CiftiParcelsMap::writeXML2(QXmlStreamWriter& xml) const { CaretAssert(!m_ignoreVolSpace); xml.writeAttribute("IndicesMapToDataType", "CIFTI_INDEX_TYPE_PARCELS"); if (hasVolumeData())//could be m_haveVolumeSpace if we want to be able to write a volspace without having voxels, but that seems silly { m_volSpace.writeCiftiXML2(xml); } for (map::const_iterator iter = m_surfInfo.begin(); iter != m_surfInfo.end(); ++iter) { xml.writeStartElement("Surface"); xml.writeAttribute("BrainStructure", StructureEnum::toCiftiName(iter->first)); xml.writeAttribute("SurfaceNumberOfVertices", QString::number(iter->second.m_numNodes)); xml.writeEndElement(); } int64_t numParcels = m_parcels.size(); for (int64_t i = 0; i < numParcels; ++i) { xml.writeStartElement("Parcel"); xml.writeAttribute("Name", m_parcels[i].m_name); int64_t numVoxels = (int64_t)m_parcels[i].m_voxelIndices.size(); if (numVoxels != 0) { xml.writeStartElement("VoxelIndicesIJK"); for (set::const_iterator iter = m_parcels[i].m_voxelIndices.begin(); iter != m_parcels[i].m_voxelIndices.end(); ++iter) { xml.writeCharacters(QString::number(iter->m_ijk[0]) + " " + QString::number(iter->m_ijk[1]) + " " + QString::number(iter->m_ijk[2]) + "\n"); } xml.writeEndElement(); } for (map >::const_iterator iter = m_parcels[i].m_surfaceNodes.begin(); iter != m_parcels[i].m_surfaceNodes.end(); ++iter) { if (iter->second.size() != 0)//prevent writing empty elements, regardless { xml.writeStartElement("Vertices"); xml.writeAttribute("BrainStructure", StructureEnum::toCiftiName(iter->first)); set::const_iterator iter2 = iter->second.begin();//which also allows us to write the first one outside the loop, to not add whitespace on the front or back xml.writeCharacters(QString::number(*iter2)); ++iter2; for (; iter2 != iter->second.end(); ++iter2) { xml.writeCharacters(" " + QString::number(*iter2)); } xml.writeEndElement(); } } xml.writeEndElement(); } } workbench-1.1.1/src/Cifti/CiftiParcelsMap.h000066400000000000000000000074661255417355300205270ustar00rootroot00000000000000#ifndef __CIFTI_PARCELS_MAP_H__ #define __CIFTI_PARCELS_MAP_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CiftiMappingType.h" #include "CaretCompact3DLookup.h" #include "StructureEnum.h" #include "VolumeSpace.h" #include "VoxelIJK.h" #include #include #include namespace caret { class CiftiParcelsMap : public CiftiMappingType { public: struct Parcel { std::map > m_surfaceNodes; std::set m_voxelIndices; QString m_name; bool operator==(const Parcel& rhs) const; bool operator!=(const Parcel& rhs) const { return !((*this) == rhs); } bool approximateMatch(const Parcel& rhs, QString* explanation = NULL) const; }; bool hasVolumeData() const; bool hasSurfaceData(const StructureEnum::Enum& structure) const; const VolumeSpace& getVolumeSpace() const; int64_t getSurfaceNumberOfNodes(const StructureEnum::Enum& structure) const; int64_t getIndexForNode(const int64_t& node, const StructureEnum::Enum& structure) const; int64_t getIndexForVoxel(const int64_t* ijk) const; int64_t getIndexForVoxel(const int64_t& i, const int64_t& j, const int64_t& k) const; std::vector getParcelSurfaceStructures() const; const std::vector& getParcels() const { return m_parcels; } int64_t getIndexFromNumberOrName(const QString& numberOrName) const; QString getIndexName(const int64_t& index) const; CiftiParcelsMap() { m_haveVolumeSpace = false; m_ignoreVolSpace = false; } void addSurface(const int& numberOfNodes, const StructureEnum::Enum& structure); void setVolumeSpace(const VolumeSpace& space); void addParcel(const Parcel& parcel); void clear(); CiftiMappingType* clone() const { return new CiftiParcelsMap(*this); } MappingType getType() const { return PARCELS; } int64_t getLength() const { return m_parcels.size(); } bool operator==(const CiftiMappingType& rhs) const; bool approximateMatch(const CiftiMappingType& rhs, QString* explanation = NULL) const; void readXML1(QXmlStreamReader& xml); void readXML2(QXmlStreamReader& xml); void writeXML1(QXmlStreamWriter& xml) const; void writeXML2(QXmlStreamWriter& xml) const; private: std::vector m_parcels; VolumeSpace m_volSpace; bool m_haveVolumeSpace, m_ignoreVolSpace;//second is needed for parsing cifti-1 struct SurfaceInfo { int64_t m_numNodes; std::vector m_lookup; }; CaretCompact3DLookup m_volLookup; std::map m_surfInfo; static Parcel readParcel1(QXmlStreamReader& xml); static Parcel readParcel2(QXmlStreamReader& xml); static std::vector readIndexArray(QXmlStreamReader& xml); }; } #endif //__CIFTI_PARCELS_MAP_H__ workbench-1.1.1/src/Cifti/CiftiScalarsMap.cxx000066400000000000000000000261221255417355300210670ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CiftiScalarsMap.h" #include "CaretAssert.h" #include "CaretException.h" #include "CaretLogger.h" //HACK: to compare metadata in a const function, we make a copy and remove the palette data - but metadata's copy intentionally breaks == because of the UUID, so we need to reset it #include "GiftiMetaDataXmlElements.h" using namespace caret; void CiftiScalarsMap::clear() { m_maps.clear(); } GiftiMetaData* CiftiScalarsMap::getMapMetadata(const int64_t& index) const { CaretAssertVectorIndex(m_maps, index); return &(m_maps[index].m_metaData); } const QString& CiftiScalarsMap::getMapName(const int64_t& index) const { CaretAssertVectorIndex(m_maps, index); return m_maps[index].m_name; } int64_t CiftiScalarsMap::getIndexFromNumberOrName(const QString& numberOrName) const { bool ok = false; int64_t ret = numberOrName.toLongLong(&ok) - 1;//quirk: use string "1" as the first index if (ok) { if (ret < 0 || ret >= getLength()) return -1;//if it is a number, do not try to use it as a name, under any circumstances return ret; } else { int64_t length = getLength(); for (int64_t i = 0; i < length; ++i) { if (numberOrName == getMapName(i)) return i; } return -1; } } PaletteColorMapping* CiftiScalarsMap::getMapPalette(const int64_t& index) const { CaretAssertVectorIndex(m_maps, index); return m_maps[index].getPalette(); } PaletteColorMapping* CiftiScalarsMap::ScalarMap::getPalette() const { if (m_palette != NULL) { return m_palette; } m_palette.grabNew(new PaletteColorMapping()); if (m_metaData.exists("PaletteColorMapping")) { try { m_palette->decodeFromStringXML(m_metaData.get("PaletteColorMapping")); } catch (XmlException& e) { CaretLogWarning("failed to parse palette settings from metadata: " + e.whatString()); } } return m_palette; } void CiftiScalarsMap::setLength(const int64_t& length) { CaretAssert(length > 0); m_maps.resize(length); } void CiftiScalarsMap::setMapName(const int64_t& index, const QString& mapName) const { CaretAssertVectorIndex(m_maps, index); m_maps[index].m_name = mapName; } bool CiftiScalarsMap::approximateMatch(const CiftiMappingType& rhs, QString* explanation) const { switch (rhs.getType()) { case SCALARS: case SERIES://maybe? case LABELS: if (getLength() != rhs.getLength()) { if (explanation != NULL) *explanation = "mappings have different length"; return false; } else return true; default: if (explanation != NULL) *explanation = CiftiMappingType::mappingTypeToName(rhs.getType()) + " mapping never matches " + CiftiMappingType::mappingTypeToName(getType()); return false; } } bool CiftiScalarsMap::operator==(const CiftiMappingType& rhs) const { if (rhs.getType() != getType()) return false; const CiftiScalarsMap& myrhs = dynamic_cast(rhs); return (m_maps == myrhs.m_maps); } bool CiftiScalarsMap::ScalarMap::operator==(const CiftiScalarsMap::ScalarMap& rhs) const { if (m_name != rhs.m_name) return false; if (*(getPalette()) != *(rhs.getPalette())) return false; GiftiMetaData mytemp = m_metaData, rhstemp = rhs.m_metaData; mytemp.remove("PaletteColorMapping");//we already compared the true palettes, so don't compare the metadata that may or may not encode them if (m_metaData.exists(GiftiMetaDataXmlElements::METADATA_NAME_UNIQUE_ID))//HACK: fix the copy-breaks-UUID silliness { mytemp.set(GiftiMetaDataXmlElements::METADATA_NAME_UNIQUE_ID, m_metaData.get(GiftiMetaDataXmlElements::METADATA_NAME_UNIQUE_ID)); } rhstemp.remove("PaletteColorMapping"); if (rhs.m_metaData.exists(GiftiMetaDataXmlElements::METADATA_NAME_UNIQUE_ID)) { rhstemp.set(GiftiMetaDataXmlElements::METADATA_NAME_UNIQUE_ID, rhs.m_metaData.get(GiftiMetaDataXmlElements::METADATA_NAME_UNIQUE_ID)); } return (mytemp == rhstemp); } void CiftiScalarsMap::readXML1(QXmlStreamReader& xml) { CaretLogFiner("parsing nonstandard scalars mapping type in cifti-1"); clear(); for (xml.readNext(); !xml.atEnd() && !xml.isEndElement(); xml.readNext()) { switch (xml.tokenType()) { case QXmlStreamReader::StartElement: { if (xml.name() != "NamedMap") { throw CaretException("unexpected element in scalars map: " + xml.name().toString()); } m_maps.push_back(ScalarMap());//HACK: because operator= is deliberately broken by GiftiMetadata for UUID m_maps.back().readXML1(xml); if (xml.hasError()) return; break; } default: break; } } CaretAssert(xml.isEndElement() && xml.name() == "MatrixIndicesMap"); } void CiftiScalarsMap::readXML2(QXmlStreamReader& xml) { clear(); for (xml.readNext(); !xml.atEnd() && !xml.isEndElement(); xml.readNext()) { switch (xml.tokenType()) { case QXmlStreamReader::StartElement: { if (xml.name() != "NamedMap") { throw CaretException("unexpected element in scalars map: " + xml.name().toString()); } m_maps.push_back(ScalarMap());//HACK: because operator= is deliberately broken by GiftiMetadata for UUID m_maps.back().readXML2(xml); if (xml.hasError()) return; break; } default: break; } } CaretAssert(xml.isEndElement() && xml.name() == "MatrixIndicesMap"); } void CiftiScalarsMap::ScalarMap::readXML1(QXmlStreamReader& xml) { bool haveName = false, haveMetaData = false; for (xml.readNext(); !xml.atEnd() && !xml.isEndElement(); xml.readNext()) { switch (xml.tokenType()) { case QXmlStreamReader::StartElement: { QStringRef name = xml.name(); if (name == "MetaData") { if (haveMetaData) { throw CaretException("MetaData specified multiple times in one NamedMap"); } m_metaData.readCiftiXML1(xml); if (xml.hasError()) return; haveMetaData = true; } else if (name == "MapName") { if (haveName) { throw CaretException("MapName specified multiple times in one NamedMap"); } m_name = xml.readElementText();//raises error if element encountered if (xml.hasError()) return; haveName = true; } else if (name == "LabelTable") { CaretLogWarning("ignoring LabelTable in Cifti-1 Scalars mapping"); xml.readElementText(QXmlStreamReader::SkipChildElements);//accept some malformed Cifti-1 files if (xml.hasError()) return; } else { throw CaretException("unexpected element in NamedMap: " + name.toString()); } break; } default: break; } } if (!haveName) { throw CaretException("NamedMap missing required child element MapName"); } CaretAssert(xml.isEndElement() && xml.name() == "NamedMap"); } void CiftiScalarsMap::ScalarMap::readXML2(QXmlStreamReader& xml) { bool haveName = false, haveMetaData = false; for (xml.readNext(); !xml.atEnd() && !xml.isEndElement(); xml.readNext()) { switch (xml.tokenType()) { case QXmlStreamReader::StartElement: { QStringRef name = xml.name(); if (name == "MetaData") { if (haveMetaData) { throw CaretException("MetaData specified multiple times in one NamedMap"); } m_metaData.readCiftiXML2(xml); if (xml.hasError()) return; haveMetaData = true; } else if (name == "MapName") { if (haveName) { throw CaretException("MapName specified multiple times in one NamedMap"); } m_name = xml.readElementText();//raises error if element encountered if (xml.hasError()) return; haveName = true; } else { throw CaretException("unexpected element in NamedMap: " + name.toString()); } break; } default: break; } } if (!haveName) { throw CaretException("NamedMap missing required child element MapName"); } CaretAssert(xml.isEndElement() && xml.name() == "NamedMap"); } void CiftiScalarsMap::writeXML1(QXmlStreamWriter& xml) const { CaretLogFiner("writing nonstandard scalars mapping type in cifti-1"); xml.writeAttribute("IndicesMapToDataType", "CIFTI_INDEX_TYPE_SCALARS"); int64_t numMaps = (int64_t)m_maps.size(); for (int64_t i = 0; i < numMaps; ++i) { xml.writeStartElement("NamedMap"); xml.writeTextElement("MapName", m_maps[i].m_name); if (m_maps[i].m_palette != NULL) { m_maps[i].m_metaData.set("PaletteColorMapping", m_maps[i].m_palette->encodeInXML()); } m_maps[i].m_metaData.writeCiftiXML1(xml); xml.writeEndElement(); } } void CiftiScalarsMap::writeXML2(QXmlStreamWriter& xml) const { int64_t numMaps = (int64_t)m_maps.size(); xml.writeAttribute("IndicesMapToDataType", "CIFTI_INDEX_TYPE_SCALARS"); for (int64_t i = 0; i < numMaps; ++i) { xml.writeStartElement("NamedMap"); xml.writeTextElement("MapName", m_maps[i].m_name); if (m_maps[i].m_palette != NULL) { m_maps[i].m_metaData.set("PaletteColorMapping", m_maps[i].m_palette->encodeInXML()); } m_maps[i].m_metaData.writeCiftiXML1(xml); xml.writeEndElement(); } } workbench-1.1.1/src/Cifti/CiftiScalarsMap.h000066400000000000000000000056361255417355300205230ustar00rootroot00000000000000#ifndef __CIFTI_SCALARS_MAP_H__ #define __CIFTI_SCALARS_MAP_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CiftiMappingType.h" #include "CaretPointer.h" #include "GiftiMetaData.h" #include "PaletteColorMapping.h" #include #include namespace caret { class CiftiScalarsMap : public CiftiMappingType { public: GiftiMetaData* getMapMetadata(const int64_t& index) const;//HACK: allow modification of palette and metadata within XML without setting the xml on a file again PaletteColorMapping* getMapPalette(const int64_t& index) const; const QString& getMapName(const int64_t& index) const; int64_t getIndexFromNumberOrName(const QString& numberOrName) const; QString getIndexName(const int64_t& index) const { return getMapName(index); } void setMapName(const int64_t& index, const QString& mapName) const;//ditto void setLength(const int64_t& length); void clear();//do we need this? CiftiMappingType* clone() const { return new CiftiScalarsMap(*this); } MappingType getType() const { return SCALARS; } int64_t getLength() const { return m_maps.size(); } bool operator==(const CiftiMappingType& rhs) const; bool approximateMatch(const CiftiMappingType& rhs, QString* explanation = NULL) const; void readXML1(QXmlStreamReader& xml); void readXML2(QXmlStreamReader& xml); void writeXML1(QXmlStreamWriter& xml) const; void writeXML2(QXmlStreamWriter& xml) const; private: struct ScalarMap { mutable QString m_name;//we need a better way to change metadata in an in-memory file mutable GiftiMetaData m_metaData;//ditto mutable CaretPointer m_palette;//ditto - note, this actually gets written into the metadata PaletteColorMapping* getPalette() const; bool operator==(const ScalarMap& rhs) const; void readXML1(QXmlStreamReader& xml); void readXML2(QXmlStreamReader& xml); }; std::vector m_maps; }; } #endif //__CIFTI_SCALARS_MAP_H__ workbench-1.1.1/src/Cifti/CiftiSeriesMap.cxx000066400000000000000000000205471255417355300207360ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CiftiSeriesMap.h" #include "CaretException.h" #include "CaretLogger.h" #include using namespace caret; using namespace std; CiftiSeriesMap::Unit CiftiSeriesMap::stringToUnit(const QString& string, bool& ok) { ok = true; if (string == "SECOND") { return SECOND; } else if (string == "HERTZ") { return HERTZ; } else if (string == "METER") { return METER; } else if (string == "RADIAN") { return RADIAN; } ok = false; return SECOND; } QString CiftiSeriesMap::unitToString(const CiftiSeriesMap::Unit& theUnit) { switch (theUnit) { case SECOND: return "SECOND"; case HERTZ: return "HERTZ"; case METER: return "METER"; case RADIAN: return "RADIAN"; } CaretAssert(false); return "UNKNOWN"; } vector CiftiSeriesMap::getAllUnits() { vector ret; ret.push_back(SECOND); ret.push_back(HERTZ); ret.push_back(METER); ret.push_back(RADIAN); return ret; } void CiftiSeriesMap::readXML1(QXmlStreamReader& xml) { QXmlStreamAttributes attrs = xml.attributes(); float newStart = 0.0f, newStep = -1.0f, mult = 0.0f; bool ok = false; if (!attrs.hasAttribute("TimeStepUnits")) { throw CaretException("timepoints mapping is missing required attribute TimeStepUnits"); } QStringRef unitString = attrs.value("TimeStepUnits"); if (unitString == "NIFTI_UNITS_SEC") { mult = 1.0f; } else if (unitString == "NIFTI_UNITS_MSEC") { mult = 0.001f; } else if (unitString == "NIFTI_UNITS_USEC") { mult = 0.000001f; } else { throw CaretException("unrecognized value for TimeStepUnits: " + unitString.toString()); } if (attrs.hasAttribute("TimeStart"))//optional and nonstandard { newStart = mult * attrs.value("TimeStart").toString().toFloat(&ok); if (!ok) { throw CaretException("unrecognized value for TimeStart: " + attrs.value("TimeStart").toString()); } } if (!attrs.hasAttribute("TimeStep")) { throw CaretException("timepoints mapping is missing required attribute TimeStep"); } newStep = mult * attrs.value("TimeStep").toString().toFloat(&ok); if (!ok) { throw CaretException("unrecognized value for TimeStep: " + attrs.value("TimeStep").toString()); } if (xml.readNextStartElement()) { throw CaretException("unexpected element in timepoints map: " + xml.name().toString()); } m_length = -1;//cifti-1 doesn't know length in xml, must be set by checking the matrix m_start = newStart; m_step = newStep; m_unit = SECOND; CaretAssert(xml.isEndElement() && xml.name() == "MatrixIndicesMap"); } void CiftiSeriesMap::readXML2(QXmlStreamReader& xml) { QXmlStreamAttributes attrs = xml.attributes(); float newStart = 0.0f, newStep = -1.0f, mult = 0.0f; int64_t newLength = -1; Unit newUnit; bool ok = false; if (!attrs.hasAttribute("SeriesUnit")) { throw CaretException("series mapping is missing required attribute SeriesUnit"); } QStringRef unitString = attrs.value("SeriesUnit"); if (unitString == "HERTZ") { newUnit = HERTZ; } else if (unitString == "METER") { newUnit = METER; } else if (unitString == "RADIAN") { newUnit = RADIAN; } else if (unitString == "SECOND") { newUnit = SECOND; } else { throw CaretException("unrecognized value for SeriesUnit: " + unitString.toString()); } if (!attrs.hasAttribute("SeriesExponent")) { throw CaretException("series mapping is missing required attribute SeriesExponent"); } int exponent = attrs.value("SeriesExponent").toString().toInt(&ok); if (!ok) { throw CaretException("unrecognized value for SeriesExponent: " + attrs.value("SeriesExponent").toString()); } mult = pow(10.0f, exponent); if (!attrs.hasAttribute("SeriesStart")) { throw CaretException("series mapping is missing required attribute SeriesStart"); } newStart = mult * attrs.value("SeriesStart").toString().toFloat(&ok); if (!ok) { throw CaretException("unrecognized value for SeriesStart: " + attrs.value("SeriesStart").toString()); } if (!attrs.hasAttribute("SeriesStep")) { throw CaretException("series mapping is missing required attribute SeriesStep"); } newStep = mult * attrs.value("SeriesStep").toString().toFloat(&ok); if (!ok) { throw CaretException("unrecognized value for SeriesStep: " + attrs.value("SeriesStep").toString()); } if (!attrs.hasAttribute("NumberOfSeriesPoints")) { throw CaretException("series mapping is missing required attribute NumberOfSeriesPoints"); } newLength = attrs.value("NumberOfSeriesPoints").toString().toLongLong(&ok); if (!ok) { throw CaretException("unrecognized value for NumberOfSeriesPoints: " + attrs.value("NumberOfSeriesPoints").toString()); } if (newLength < 1) { throw CaretException("NumberOfSeriesPoints must be positive"); } if (xml.readNextStartElement()) { throw CaretException("unexpected element in series map: " + xml.name().toString()); } m_length = newLength; m_start = newStart; m_step = newStep; m_unit = newUnit; CaretAssert(xml.isEndElement() && xml.name() == "MatrixIndicesMap"); } void CiftiSeriesMap::writeXML1(QXmlStreamWriter& xml) const { CaretAssert(m_length != -1); if (m_unit != SECOND) { CaretLogWarning("changing series units to seconds for CIFTI-1 XML"); } xml.writeAttribute("IndicesMapToDataType", "CIFTI_INDEX_TYPE_TIME_POINTS"); float mult = 1.0f; QString unitString = "NIFTI_UNITS_SEC"; float test = m_step; if (test == 0.0f) test = m_start; if (test != 0.0f) { if (abs(test) < 0.00005f) { mult = 1000000.0f; unitString = "NIFTI_UNITS_USEC"; } else if (abs(test) < 0.05f) { mult = 1000.0f; unitString = "NIFTI_UNITS_MSEC"; } } xml.writeAttribute("TimeStepUnits", unitString); xml.writeAttribute("TimeStart", QString::number(mult * m_start, 'f', 7));//even though it is nonstandard, write it, always xml.writeAttribute("TimeStep", QString::number(mult * m_step, 'f', 7)); } void CiftiSeriesMap::writeXML2(QXmlStreamWriter& xml) const { CaretAssert(m_length != -1); xml.writeAttribute("IndicesMapToDataType", "CIFTI_INDEX_TYPE_SERIES"); int exponent = 0; float test = m_step; if (test == 0.0f) test = m_start; if (test != 0.0f) { exponent = 3 * (int)floor((log10(test) - log10(0.05f)) / 3.0f);//some magic to get the exponent that is a multiple of 3 that puts the test value in [0.05, 50] } float mult = pow(10.0f, -exponent); QString unitString; switch (m_unit) { case HERTZ: unitString = "HERTZ"; break; case METER: unitString = "METER"; break; case RADIAN: unitString = "RADIAN"; break; case SECOND: unitString = "SECOND"; break; } xml.writeAttribute("NumberOfSeriesPoints", QString::number(m_length)); xml.writeAttribute("SeriesExponent", QString::number(exponent)); xml.writeAttribute("SeriesStart", QString::number(mult * m_start, 'f', 7)); xml.writeAttribute("SeriesStep", QString::number(mult * m_step, 'f', 7)); xml.writeAttribute("SeriesUnit", unitString); } workbench-1.1.1/src/Cifti/CiftiSeriesMap.h000066400000000000000000000101441255417355300203530ustar00rootroot00000000000000#ifndef __CIFTI_SERIES_MAP_H__ #define __CIFTI_SERIES_MAP_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretAssert.h" #include "CiftiMappingType.h" namespace caret { class CiftiSeriesMap : public CiftiMappingType { public: enum Unit { HERTZ, METER, RADIAN, SECOND };//should this go somewhere else? float getStart() const { return m_start; }//using getter/setter as style choice to match other mapping types float getStep() const { return m_step; }//getter for number of series points is getLength(), specified by CiftiIndexMap Unit getUnit() const { return m_unit; } CiftiSeriesMap() { m_start = 0.0f; m_step = 1.0f; m_unit = SECOND; m_length = -1;//to make it clear an improperly initialized series map object was used } CiftiSeriesMap(const int64_t& length, const float& start = 0.0f, const float& step = 1.0f, const Unit& unit = SECOND) { m_start = start; m_step = step; m_unit = unit; m_length = length; } void setStart(const float& start) { m_start = start; } void setStep(const float& step) { m_step = step; } void setUnit(const Unit& unit) { m_unit = unit; } void setLength(const int64_t& length) { CaretAssert(length > 0); m_length = length; } static Unit stringToUnit(const QString& string, bool& ok); static QString unitToString(const Unit& theUnit); static std::vector getAllUnits(); CiftiMappingType* clone() const { return new CiftiSeriesMap(*this); } MappingType getType() const { return SERIES; } int64_t getLength() const { return m_length; } bool operator==(const CiftiMappingType& rhs) const { if (rhs.getType() != getType()) return false; const CiftiSeriesMap& temp = dynamic_cast(rhs); return (temp.m_length == m_length && temp.m_unit == m_unit && temp.m_start == m_start && temp.m_step == m_step); } bool approximateMatch(const CiftiMappingType& rhs, QString* explanation = NULL) const { switch (rhs.getType()) { case SCALARS://maybe? case SERIES: case LABELS://maybe? if (getLength() != rhs.getLength()) { if (explanation != NULL) *explanation = "mappings have different length"; return false; } else return true; default: if (explanation != NULL) *explanation = CiftiMappingType::mappingTypeToName(rhs.getType()) + " mapping never matches " + CiftiMappingType::mappingTypeToName(getType()); return false; } } void readXML1(QXmlStreamReader& xml); void readXML2(QXmlStreamReader& xml); void writeXML1(QXmlStreamWriter& xml) const; void writeXML2(QXmlStreamWriter& xml) const; private: int64_t m_length; float m_start, m_step;//exponent gets applied to these on reading Unit m_unit; }; } #endif //__CIFTI_SERIES_MAP_H__ workbench-1.1.1/src/Cifti/CiftiVersion.cxx000066400000000000000000000062641255417355300204730ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CiftiVersion.h" #include "CaretException.h" using namespace caret; CiftiVersion::CiftiVersion() { m_major = 2; m_minor = 0; } CiftiVersion::CiftiVersion(const int16_t& major, const int16_t& minor) { m_major = major; m_minor = minor; } CiftiVersion::CiftiVersion(const QString& versionString) { int result = versionString.indexOf('.'); bool ok = false; if (result < 0) { m_minor = 0; m_major = versionString.toShort(&ok); if (!ok) throw CaretException("improperly formatted CIFTI version string: '" + versionString + "'"); } else { if (result == 0) throw CaretException("improperly formatted CIFTI version string: '" + versionString + "'"); m_major = versionString.mid(0, result).toShort(&ok); if (!ok) throw CaretException("improperly formatted CIFTI version string: '" + versionString + "'"); m_minor = versionString.mid(result + 1).toShort(&ok); if (!ok) throw CaretException("improperly formatted CIFTI version string: '" + versionString + "'"); } } bool CiftiVersion::hasReversedFirstDims() const { if (m_major == 1 && m_minor == 0) return true; return false; } bool CiftiVersion::operator<(const CiftiVersion& rhs) const { if (m_major < rhs.m_major) return true; if (m_major == rhs.m_major && m_minor < rhs.m_minor) return true; return false; } bool CiftiVersion::operator<=(const CiftiVersion& rhs) const { if (m_major < rhs.m_major) return true; if (m_major == rhs.m_major && m_minor <= rhs.m_minor) return true; return false; } bool CiftiVersion::operator==(const CiftiVersion& rhs) const { if (m_major == rhs.m_major && m_minor == rhs.m_minor) return true; return false; } bool CiftiVersion::operator!=(const CiftiVersion& rhs) const { return !(*this == rhs); } bool CiftiVersion::operator>(const caret::CiftiVersion& rhs) const { if (m_major > rhs.m_major) return true; if (m_major == rhs.m_major && m_minor > rhs.m_minor) return true; return false; } bool CiftiVersion::operator>=(const caret::CiftiVersion& rhs) const { if (m_major > rhs.m_major) return true; if (m_major == rhs.m_major && m_minor >= rhs.m_minor) return true; return false; } QString CiftiVersion::toString() const { QString ret = QString::number(m_major); if (m_minor != 0) ret += "." + QString::number(m_minor); return ret; } workbench-1.1.1/src/Cifti/CiftiVersion.h000066400000000000000000000033371255417355300201160ustar00rootroot00000000000000#ifndef __CIFTI_VERSION_H__ #define __CIFTI_VERSION_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "stdint.h" namespace caret { class CiftiVersion { int16_t m_major, m_minor; public: int16_t getMajor() const { return m_major; } int16_t getMinor() const { return m_minor; } CiftiVersion(); CiftiVersion(const int16_t& major, const int16_t& minor); CiftiVersion(const QString& versionString); QString toString() const; bool operator<(const CiftiVersion& rhs) const; bool operator>(const CiftiVersion& rhs) const; bool operator==(const CiftiVersion& rhs) const; bool operator!=(const CiftiVersion& rhs) const; bool operator<=(const CiftiVersion& rhs) const; bool operator>=(const CiftiVersion& rhs) const; ///quirk tests bool hasReversedFirstDims() const; }; } #endif //__CIFTI_VERSION_H__ workbench-1.1.1/src/Cifti/CiftiXML.cxx000066400000000000000000001012161255417355300174770ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CiftiXML.h" #include "CaretAssert.h" #include "CaretException.h" #include "CaretLogger.h" #include "GiftiMetaData.h" #include "PaletteColorMapping.h" #include #include using namespace std; using namespace caret; CiftiXML::CiftiXML(const CiftiXML& rhs) { copyHelper(rhs); } CiftiXML& CiftiXML::operator=(const CiftiXML& rhs) { if (this != &rhs) copyHelper(rhs); return *this; } void CiftiXML::copyHelper(const CiftiXML& rhs) { int numDims = (int)rhs.m_indexMaps.size(); m_indexMaps.resize(numDims); for (int i = 0; i < numDims; ++i) { m_indexMaps[i] = CaretPointer(rhs.m_indexMaps[i]->clone()); } m_parsedVersion = rhs.m_parsedVersion; m_fileMetaData = rhs.m_fileMetaData; if (rhs.m_filePalette != NULL) { m_filePalette.grabNew(new PaletteColorMapping()); *(m_filePalette) = *(rhs.m_filePalette); } else { m_filePalette.grabNew(NULL); } } bool CiftiXML::operator==(const CiftiXML& rhs) const { int numDims = getNumberOfDimensions(); if (rhs.getNumberOfDimensions() != numDims) return false; if (m_fileMetaData != rhs.m_fileMetaData) return false; if ((*getFilePalette()) != (*rhs.getFilePalette())) return false; for (int i = 0; i < numDims; ++i) { const CiftiMappingType* left = getMap(i), *right = rhs.getMap(i); if (left == NULL && right == NULL) continue; if (left == NULL || right == NULL) return false;//only one NULL, due to above test if ((*left) != (*right)) return false;//finally can dereference them } return true; } bool CiftiXML::approximateMatch(const CiftiXML& rhs) const { int numDims = getNumberOfDimensions(); if (rhs.getNumberOfDimensions() != numDims) return false; for (int i = 0; i < numDims; ++i) { const CiftiMappingType* left = getMap(i), *right = rhs.getMap(i); if (left == NULL && right == NULL) continue; if (left == NULL || right == NULL) return false;//only one NULL, due to above test if (!left->approximateMatch(*right)) return false;//finally can dereference them } return true; } const CiftiMappingType* CiftiXML::getMap(const int& direction) const { CaretAssertVectorIndex(m_indexMaps, direction); return m_indexMaps[direction]; } CiftiMappingType* CiftiXML::getMap(const int& direction) { CaretAssertVectorIndex(m_indexMaps, direction); return m_indexMaps[direction]; } GiftiMetaData* CiftiXML::getFileMetaData() const { return &m_fileMetaData; } PaletteColorMapping* CiftiXML::getFilePalette() const { if (m_filePalette != NULL) { return m_filePalette; } m_filePalette.grabNew(new PaletteColorMapping()); if (m_fileMetaData.exists("PaletteColorMapping")) { try { m_filePalette->decodeFromStringXML(m_fileMetaData.get("PaletteColorMapping")); } catch (XmlException& e) { CaretLogWarning("failed to parse palette settings from metadata: " + e.whatString()); } } return m_filePalette; } const CiftiBrainModelsMap& CiftiXML::getBrainModelsMap(const int& direction) const { CaretAssertVectorIndex(m_indexMaps, direction); CaretAssert(getMappingType(direction) == CiftiMappingType::BRAIN_MODELS);//assert so we catch it in debug return dynamic_cast(*getMap(direction));//let release throw bad_cast or segfault } CiftiBrainModelsMap& CiftiXML::getBrainModelsMap(const int& direction) { CaretAssertVectorIndex(m_indexMaps, direction); CaretAssert(getMappingType(direction) == CiftiMappingType::BRAIN_MODELS);//assert so we catch it in debug return dynamic_cast(*getMap(direction));//let release throw bad_cast or segfault } const CiftiLabelsMap& CiftiXML::getLabelsMap(const int& direction) const { CaretAssertVectorIndex(m_indexMaps, direction); CaretAssert(getMappingType(direction) == CiftiMappingType::LABELS);//assert so we catch it in debug return dynamic_cast(*getMap(direction));//let release throw bad_cast or segfault } CiftiLabelsMap& CiftiXML::getLabelsMap(const int& direction) { CaretAssertVectorIndex(m_indexMaps, direction); CaretAssert(getMappingType(direction) == CiftiMappingType::LABELS);//assert so we catch it in debug return dynamic_cast(*getMap(direction));//let release throw bad_cast or segfault } const CiftiParcelsMap& CiftiXML::getParcelsMap(const int& direction) const { CaretAssertVectorIndex(m_indexMaps, direction); CaretAssert(getMappingType(direction) == CiftiMappingType::PARCELS);//assert so we catch it in debug return dynamic_cast(*getMap(direction));//let release throw bad_cast or segfault } CiftiParcelsMap& CiftiXML::getParcelsMap(const int& direction) { CaretAssertVectorIndex(m_indexMaps, direction); CaretAssert(getMappingType(direction) == CiftiMappingType::PARCELS);//assert so we catch it in debug return dynamic_cast(*getMap(direction));//let release throw bad_cast or segfault } const CiftiScalarsMap& CiftiXML::getScalarsMap(const int& direction) const { CaretAssertVectorIndex(m_indexMaps, direction); CaretAssert(getMappingType(direction) == CiftiMappingType::SCALARS);//assert so we catch it in debug return dynamic_cast(*getMap(direction));//let release throw bad_cast or segfault } CiftiScalarsMap& CiftiXML::getScalarsMap(const int& direction) { CaretAssertVectorIndex(m_indexMaps, direction); CaretAssert(getMappingType(direction) == CiftiMappingType::SCALARS);//assert so we catch it in debug return dynamic_cast(*getMap(direction));//let release throw bad_cast or segfault } const CiftiSeriesMap& CiftiXML::getSeriesMap(const int& direction) const { CaretAssertVectorIndex(m_indexMaps, direction); CaretAssert(getMappingType(direction) == CiftiMappingType::SERIES);//assert so we catch it in debug return dynamic_cast(*getMap(direction));//let release throw bad_cast or segfault } CiftiSeriesMap& CiftiXML::getSeriesMap(const int& direction) { CaretAssertVectorIndex(m_indexMaps, direction); CaretAssert(getMappingType(direction) == CiftiMappingType::SERIES);//assert so we catch it in debug return dynamic_cast(*getMap(direction));//let release throw bad_cast or segfault } int64_t CiftiXML::getDimensionLength(const int& direction) const { const CiftiMappingType* tempMap = getMap(direction); CaretAssert(tempMap != NULL); return tempMap->getLength(); } vector CiftiXML::getDimensions() const { vector ret(getNumberOfDimensions()); for (int i = 0; i < (int)ret.size(); ++i) { ret[i] = getDimensionLength(i); } return ret; } CiftiMappingType::MappingType CiftiXML::getMappingType(const int& direction) const { CaretAssertVectorIndex(m_indexMaps, direction); CaretAssert(m_indexMaps[direction] != NULL); return m_indexMaps[direction]->getType(); } void CiftiXML::setMap(const int& direction, const CiftiMappingType& mapIn) { CaretAssertVectorIndex(m_indexMaps, direction); m_indexMaps[direction] = CaretPointer(mapIn.clone()); } void CiftiXML::setNumberOfDimensions(const int& num) { m_indexMaps.resize(num); } void CiftiXML::clear() { setNumberOfDimensions(0); m_filePalette.grabNew(NULL); m_fileMetaData.clear(false); m_parsedVersion = CiftiVersion(); } void CiftiXML::readXML(const QString& text) { QXmlStreamReader xml(text); readXML(xml); } void CiftiXML::readXML(const QByteArray& data) { QString text(data);//constructing a qstring appears to be the simplest way to remove trailing nulls, which otherwise trip an "Extra content at end of document" error readXML(text);//then put it through the string reader, just to simplify code paths } int32_t CiftiXML::getIntentInfo(const CiftiVersion& writingVersion, char intentNameOut[16]) const { int32_t ret; const char* name = NULL; if (writingVersion == CiftiVersion(1, 0))//cifti-1: unknown didn't exist, and "ConnDense" was default { ret = 3001;//default name = "ConnDense"; if (getNumberOfDimensions() > 0 && getMappingType(0) == CiftiMappingType::SERIES) { ret = 3002; name = "ConnDenseTime"; }//same logic as was actually used in CiftiFile if (getNumberOfDimensions() > 1 && getMappingType(1) == CiftiMappingType::SERIES) { ret = 3002; name = "ConnDenseTime"; }//NOTE: name for this code is different than cifti-2 } else if (writingVersion == CiftiVersion(1, 1) || writingVersion == CiftiVersion(2, 0)) {//cifti-2 ret = 3000;//default name = "ConnUnknown"; switch (getNumberOfDimensions()) { case 2: { CiftiMappingType::MappingType first = getMappingType(0), second = getMappingType(1); if (first == CiftiMappingType::BRAIN_MODELS && second == CiftiMappingType::BRAIN_MODELS) { ret = 3001; name = "ConnDense"; } if (first == CiftiMappingType::SERIES && second == CiftiMappingType::BRAIN_MODELS) { ret = 3002; name = "ConnDenseSeries"; } if (first == CiftiMappingType::PARCELS && second == CiftiMappingType::PARCELS) { ret = 3003; name = "ConnParcels"; } if (first == CiftiMappingType::SERIES && second == CiftiMappingType::PARCELS) { ret = 3004; name = "ConnParcelSries"; }//NOTE: 3005 is reserved but not used if (first == CiftiMappingType::SCALARS && second == CiftiMappingType::BRAIN_MODELS) { ret = 3006; name = "ConnDenseScalar"; } if (first == CiftiMappingType::LABELS && second == CiftiMappingType::BRAIN_MODELS) { ret = 3007; name = "ConnDenseLabel"; } if (first == CiftiMappingType::SCALARS && second == CiftiMappingType::PARCELS) { ret = 3008; name = "ConnParcelScalr"; } if (first == CiftiMappingType::BRAIN_MODELS && second == CiftiMappingType::PARCELS) { ret = 3009; name = "ConnParcelDense"; } if (first == CiftiMappingType::PARCELS && second == CiftiMappingType::BRAIN_MODELS) { ret = 3010; name = "ConnDenseParcel"; } break; } case 3: { CiftiMappingType::MappingType first = getMappingType(0), second = getMappingType(1), third = getMappingType(2); if (first == CiftiMappingType::PARCELS && second == CiftiMappingType::PARCELS && third == CiftiMappingType::SERIES) { ret = 3011; name = "ConnPPSr"; } if (first == CiftiMappingType::PARCELS && second == CiftiMappingType::PARCELS && third == CiftiMappingType::SCALARS) { ret = 3012; name = "ConnPPSc"; } break; } default: break; } } else { throw CaretException("unknown cifti version: " + writingVersion.toString()); } int i; for (i = 0; i < 16 && name[i] != '\0'; ++i) intentNameOut[i] = name[i]; for (; i < 16; ++i) intentNameOut[i] = '\0'; return ret; } void CiftiXML::readXML(QXmlStreamReader& xml) { clear(); try { bool haveCifti = false; for (; !xml.atEnd(); xml.readNext()) { if (xml.isStartElement()) { QStringRef name = xml.name(); if (name == "CIFTI") { if (haveCifti) { throw CaretException("CIFTI element may only be specified once"); } QXmlStreamAttributes attributes = xml.attributes(); if(attributes.hasAttribute("Version")) { m_parsedVersion = CiftiVersion(attributes.value("Version").toString()); } else { throw CaretException("Cifti XML missing Version attribute."); } if (m_parsedVersion == CiftiVersion(1, 0))//switch/case on major/minor would be much harder to read { parseCIFTI1(xml); if (xml.hasError()) break; } else if (m_parsedVersion == CiftiVersion(1, 1)) { CaretLogWarning("parsing cifti version '1.1', this should not exist in the wild"); parseCIFTI2(xml);//we used "1.1" to test our cifti-2 implementation if (xml.hasError()) break; } else if (m_parsedVersion == CiftiVersion(2, 0)) { parseCIFTI2(xml); if (xml.hasError()) break; } else { throw CaretException("unknown Cifti Version: '" + m_parsedVersion.toString()); } haveCifti = true; } else { throw CaretException("unexpected root element in Cifti XML: " + name.toString()); } } } if (!xml.hasError() && !haveCifti) { throw CaretException("CIFTI element not found"); } } catch (CaretException& e) { throw CaretException("Cifti XML error: " + e.whatString());//so we can throw on error instead of doing a bunch of dancing with xml.raiseError and xml.hasError } if(xml.hasError()) { throw CaretException("Cifti XML error: " + xml.errorString()); } } void CiftiXML::parseCIFTI1(QXmlStreamReader& xml) { QXmlStreamAttributes attributes = xml.attributes(); if (attributes.hasAttribute("NumberOfMatrices")) { if (attributes.value("NumberOfMatrices") != "1") { throw CaretException("attribute NumberOfMatrices in CIFTI is required to be 1 for CIFTI-1"); } } else { throw CaretException("missing attribute NumberOfMatrices in CIFTI"); } bool haveMatrix = false; while (!xml.atEnd()) { xml.readNext(); if (xml.isStartElement()) { QStringRef name = xml.name(); if (name == "Matrix") { if (haveMatrix) { throw CaretException("Matrix element may only be specified once"); } parseMatrix1(xml); if (xml.hasError()) return; haveMatrix = true; } else { throw CaretException("unexpected element in CIFTI: " + name.toString()); } } else if (xml.isEndElement()) { break; } } if (!haveMatrix) { throw CaretException("Matrix element not found in CIFTI"); } if (xml.hasError()) return; CaretAssert(xml.isEndElement() && xml.name() == "CIFTI"); } void CiftiXML::parseCIFTI2(QXmlStreamReader& xml)//yes, these will often have largely similar code, but it seems cleaner than having only some functions split, or constantly rechecking the version {//also, helps keep changes to cifti-2 away from code that parses cifti-1 bool haveMatrix = false; while (!xml.atEnd()) { xml.readNext(); if (xml.hasError()) return; if (xml.isStartElement()) { QStringRef name = xml.name(); if (name == "Matrix") { if (haveMatrix) { throw CaretException("Matrix element may only be specified once"); } parseMatrix2(xml); if (xml.hasError()) return; haveMatrix = true; } else { throw CaretException("unexpected element in CIFTI: " + name.toString()); } } else if (xml.isEndElement()) { break; } } if (!haveMatrix) { throw CaretException("Matrix element not found in CIFTI"); } CaretAssert(xml.isEndElement() && xml.name() == "CIFTI"); } void CiftiXML::parseMatrix1(QXmlStreamReader& xml) { VolumeSpace fileVolSpace; bool haveVolSpace = false, haveMetadata = false; while (!xml.atEnd()) { xml.readNext(); if (xml.hasError()) return; if (xml.isStartElement()) { QStringRef name = xml.name(); if (name == "MetaData") { if (haveMetadata) { throw CaretException("MetaData may only be specified once in Matrix"); } m_fileMetaData.readCiftiXML1(xml); if (xml.hasError()) return; haveMetadata = true; } else if (name == "MatrixIndicesMap") { parseMatrixIndicesMap1(xml); if (xml.hasError()) return; } else if (name == "Volume") { if (haveVolSpace) { throw CaretException("Volume may only be specified once in Matrix"); } fileVolSpace.readCiftiXML1(xml); if (xml.hasError()) return; haveVolSpace = true; } else if (name == "LabelTable") { CaretLogFiner("skipping unused LabelTable element in Matrix in CIFTI-1"); xml.readElementText(QXmlStreamReader::SkipChildElements); } else { throw CaretException("unexpected element in Matrix: " + name.toString()); } } else if (xml.isEndElement()) { break; } } for (int i = 0; i < (int)m_indexMaps.size(); ++i) { if (m_indexMaps[i] == NULL) { int displaynum = i; if (displaynum < 2) displaynum = 1 - displaynum;//re-invert so that it shows the same number as the XML is missing throw CaretException("missing mapping for dimension '" + QString::number(displaynum) + "'"); } switch (m_indexMaps[i]->getType()) { case CiftiMappingType::BRAIN_MODELS: { CiftiBrainModelsMap& myMap = dynamic_cast(*(m_indexMaps[i])); if (myMap.hasVolumeData()) { if (haveVolSpace) { myMap.setVolumeSpace(fileVolSpace);//also does the needed checking of voxel indices } else { throw CaretException("BrainModels map uses voxels, but no Volume element exists"); } } break; } case CiftiMappingType::PARCELS: { CiftiParcelsMap& myMap = dynamic_cast(*(m_indexMaps[i])); if (myMap.hasVolumeData()) { if (haveVolSpace) { myMap.setVolumeSpace(fileVolSpace);//ditto } else { throw CaretException("Parcels map uses voxels, but no Volume element exists"); } } break; } default: break; } } CaretAssert(xml.isEndElement() && xml.name() == "Matrix"); } void CiftiXML::parseMatrix2(QXmlStreamReader& xml) { bool haveMetadata = false; while (!xml.atEnd()) { xml.readNext(); if (xml.hasError()) return; if (xml.isStartElement()) { QStringRef name = xml.name(); if (name == "MetaData") { if (haveMetadata) { throw CaretException("MetaData may only be specified once in Matrix"); } m_fileMetaData.readCiftiXML2(xml); if (xml.hasError()) return; haveMetadata = true; } else if (name == "MatrixIndicesMap") { parseMatrixIndicesMap2(xml); if (xml.hasError()) return; } else { throw CaretException("unexpected element in Matrix: " + name.toString()); } } else if (xml.isEndElement()) { break; } } for (int i = 0; i < (int)m_indexMaps.size(); ++i) { if (m_indexMaps[i] == NULL) { throw CaretException("missing mapping for dimension '" + QString::number(i) + "'"); } } CaretAssert(xml.isEndElement() && xml.name() == "Matrix"); } void CiftiXML::parseMatrixIndicesMap1(QXmlStreamReader& xml) { QXmlStreamAttributes attributes = xml.attributes(); if (!attributes.hasAttribute("AppliesToMatrixDimension")) { throw CaretException("missing attribute AppliesToMatrixDimension in MatrixIndicesMap"); } if (!attributes.hasAttribute("IndicesMapToDataType")) { throw CaretException("missing attribute IndicesMapToDataType in MatrixIndicesMap"); } QStringList values = attributes.value("AppliesToMatrixDimension").toString().split(','); bool ok = false; set used; for(int i = 0; i < values.size(); i++) { int parsed = values[i].toInt(&ok); if (!ok || parsed < 0) { throw CaretException("bad value in AppliesToMatrixDimension list: " + values[i]); } if (parsed < 2) parsed = 1 - parsed;//in other words, 0 becomes 1 and 1 becomes 0, since cifti-1 had them reversed if (used.find(parsed) != used.end()) { throw CaretException("AppliesToMatrixDimension contains repeated value: " + values[i]); } used.insert(parsed); } CaretPointer toRead; QStringRef type = attributes.value("IndicesMapToDataType"); if (type == "CIFTI_INDEX_TYPE_BRAIN_MODELS") { toRead = CaretPointer(new CiftiBrainModelsMap()); } else if (type == "CIFTI_INDEX_TYPE_TIME_POINTS") { toRead = CaretPointer(new CiftiSeriesMap()); } else if (type == "CIFTI_INDEX_TYPE_LABELS") {//this and below are nonstandard toRead = CaretPointer(new CiftiLabelsMap()); } else if (type == "CIFTI_INDEX_TYPE_PARCELS") { toRead = CaretPointer(new CiftiParcelsMap()); } else if (type == "CIFTI_INDEX_TYPE_SCALARS") { toRead = CaretPointer(new CiftiScalarsMap()); } else { throw CaretException("invalid value for IndicesMapToDataType in CIFTI-1: " + type.toString()); } toRead->readXML1(xml);//this will warn (with 'finer' log level?) if it is nonstandard if (xml.hasError()) return; bool first = true; for (set::iterator iter = used.begin(); iter != used.end(); ++iter) { if (*iter >= (int)m_indexMaps.size()) m_indexMaps.resize(*iter + 1); if (first) { m_indexMaps[*iter] = toRead; first = false; } else { m_indexMaps[*iter] = CaretPointer(toRead->clone());//make in-memory information independent per-dimension, rather than dealing with deduplication everywhere } } CaretAssert(xml.isEndElement() && xml.name() == "MatrixIndicesMap"); } void CiftiXML::parseMatrixIndicesMap2(QXmlStreamReader& xml) { QXmlStreamAttributes attributes = xml.attributes(); if (!attributes.hasAttribute("AppliesToMatrixDimension")) { throw CaretException("missing attribute AppliesToMatrixDimension in MatrixIndicesMap"); } if (!attributes.hasAttribute("IndicesMapToDataType")) { throw CaretException("missing attribute IndicesMapToDataType in MatrixIndicesMap"); } QStringList values = attributes.value("AppliesToMatrixDimension").toString().split(','); bool ok = false; set used; for(int i = 0; i < values.size(); i++) { int parsed = values[i].toInt(&ok); if (!ok || parsed < 0) { throw CaretException("bad value in AppliesToMatrixDimension list: " + values[i]); } if (used.find(parsed) != used.end()) { throw CaretException("AppliesToMatrixDimension contains repeated value: " + values[i]); } used.insert(parsed); } CaretPointer toRead; QStringRef type = attributes.value("IndicesMapToDataType"); if (type == "CIFTI_INDEX_TYPE_BRAIN_MODELS") { toRead = CaretPointer(new CiftiBrainModelsMap()); } else if (type == "CIFTI_INDEX_TYPE_LABELS") { toRead = CaretPointer(new CiftiLabelsMap()); } else if (type == "CIFTI_INDEX_TYPE_PARCELS") { toRead = CaretPointer(new CiftiParcelsMap()); } else if (type == "CIFTI_INDEX_TYPE_SCALARS") { toRead = CaretPointer(new CiftiScalarsMap()); } else if (type == "CIFTI_INDEX_TYPE_SERIES") { toRead = CaretPointer(new CiftiSeriesMap()); } else { throw CaretException("invalid value for IndicesMapToDataType in CIFTI-1: " + type.toString()); } toRead->readXML2(xml); if (xml.hasError()) return; bool first = true; for (set::iterator iter = used.begin(); iter != used.end(); ++iter) { if (*iter >= (int)m_indexMaps.size()) m_indexMaps.resize(*iter + 1); if (first) { m_indexMaps[*iter] = toRead; first = false; } else { m_indexMaps[*iter] = CaretPointer(toRead->clone());//make in-memory information independent per-dimension, rather than dealing with deduplication everywhere } } CaretAssert(xml.isEndElement() && xml.name() == "MatrixIndicesMap"); } QByteArray CiftiXML::writeXMLToQByteArray(const CiftiVersion& writingVersion) const { QByteArray ret; QXmlStreamWriter xml(&ret); xml.setAutoFormatting(true); xml.writeStartDocument(); writeXML(xml, writingVersion); xml.writeEndDocument(); return ret; } QString CiftiXML::writeXMLToString(const CiftiVersion& writingVersion) const { QString ret; QXmlStreamWriter xml(&ret); xml.setAutoFormatting(true); xml.writeStartDocument(); writeXML(xml, writingVersion); xml.writeEndDocument(); return ret; } void CiftiXML::writeXML(QXmlStreamWriter& xml, const CiftiVersion& writingVersion) const { xml.writeStartElement("CIFTI"); xml.writeAttribute("Version", writingVersion.toString()); if (writingVersion == CiftiVersion(1, 0))//switch/case on major/minor would be much harder to read { xml.writeAttribute("NumberOfMatrices", "1"); writeMatrix1(xml); } else if (writingVersion == CiftiVersion(1, 1)) {//we used "1.1" to test our cifti-2 implementation - should we even allow this? CaretLogWarning("writing cifti version '1.1', this should not exist in the wild"); writeMatrix2(xml); } else if (writingVersion == CiftiVersion(2, 0)) { writeMatrix2(xml); } else { throw CaretException("unknown Cifti writing version: '" + writingVersion.toString() + "'"); } xml.writeEndElement(); } void CiftiXML::writeMatrix1(QXmlStreamWriter& xml) const { int numDims = (int)m_indexMaps.size(); bool haveVolData = false; VolumeSpace volSpace; for (int i = 0; i < numDims; ++i) { if (m_indexMaps[i] == NULL) throw CaretException("dimension " + QString::number(i) + " was not given a mapping"); switch (m_indexMaps[i]->getType()) { case CiftiMappingType::BRAIN_MODELS: { const CiftiBrainModelsMap& myMap = dynamic_cast(*(m_indexMaps[i])); if (myMap.hasVolumeData()) { if (haveVolData) { if (myMap.getVolumeSpace() != volSpace) { throw CaretException("cannot write different volume spaces for different dimensions in CIFTI-1"); } } else { haveVolData = true; volSpace = myMap.getVolumeSpace(); } } break; } case CiftiMappingType::PARCELS: { const CiftiParcelsMap& myMap = dynamic_cast(*(m_indexMaps[i])); if (myMap.hasVolumeData()) { if (haveVolData) { if (myMap.getVolumeSpace() != volSpace) { throw CaretException("cannot write different volume spaces for different dimensions in CIFTI-1"); } } else { haveVolData = true; volSpace = myMap.getVolumeSpace(); } } break; } default: break; } } xml.writeStartElement("Matrix"); if (m_filePalette != NULL) { m_fileMetaData.set("PaletteColorMapping", m_filePalette->encodeInXML()); } m_fileMetaData.writeCiftiXML1(xml); if (haveVolData) { volSpace.writeCiftiXML1(xml); } vector used(numDims, false); for (int i = 0; i < numDims; ++i) { if (!used[i]) { used[i] = true; int outputNum = i; if (outputNum < 2) outputNum = 1 - outputNum;//ie, swap 0 and 1 QString appliesTo = QString::number(outputNum);//initialize containing just the current dimension for (int j = i + 1; j < numDims; ++j)//compare to all later unused dimensions for deduplication {//technically, shouldn't need to check for previously used as long as equality is exact, but means maybe fewer comparisons, and to prevent a bug in == from getting stranger behavior if (!used[j]) { if ((*m_indexMaps[i]) == (*m_indexMaps[j])) { outputNum = j; if (outputNum < 2) outputNum = 1 - outputNum; appliesTo += "," + QString::number(outputNum); used[j] = true; } } } xml.writeStartElement("MatrixIndicesMap");//should the CiftiIndexMap do this instead, and we pass appliesTo to it as string? probably not important, we won't use them in any other xml xml.writeAttribute("AppliesToMatrixDimension", appliesTo); m_indexMaps[i]->writeXML1(xml); xml.writeEndElement(); } } xml.writeEndElement(); } void CiftiXML::writeMatrix2(QXmlStreamWriter& xml) const { int numDims = (int)m_indexMaps.size(); for (int i = 0; i < numDims; ++i) { if (m_indexMaps[i] == NULL) throw CaretException("dimension " + QString::number(i) + " was not given a mapping"); } xml.writeStartElement("Matrix"); if (m_filePalette != NULL) { m_fileMetaData.set("PaletteColorMapping", m_filePalette->encodeInXML()); } m_fileMetaData.writeCiftiXML2(xml); vector used(numDims, false); for (int i = 0; i < numDims; ++i) { if (!used[i]) { used[i] = true; QString appliesTo = QString::number(i);//initialize containing just the current dimension for (int j = i + 1; j < numDims; ++j)//compare to all later unused dimensions for deduplication {//technically, shouldn't need to check for previously used as long as equality is exact, but means maybe fewer comparisons, and to prevent a bug in == from getting stranger behavior if (!used[j]) { if ((*m_indexMaps[i]) == (*m_indexMaps[j])) { appliesTo += "," + QString::number(j); used[j] = true; } } } xml.writeStartElement("MatrixIndicesMap");//should the CiftiIndexMap do this instead, and we pass appliesTo to it as string? probably not important, we won't use them in any other xml xml.writeAttribute("AppliesToMatrixDimension", appliesTo); m_indexMaps[i]->writeXML2(xml); xml.writeEndElement(); } } xml.writeEndElement(); } workbench-1.1.1/src/Cifti/CiftiXML.h000066400000000000000000000116311255417355300171250ustar00rootroot00000000000000#ifndef __CIFTI_XML_H__ #define __CIFTI_XML_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretPointer.h" #include "CiftiMappingType.h" #include "CiftiVersion.h" #include "GiftiMetaData.h" //could also be forward declared #include "PaletteColorMapping.h" //just include the mapping types themselves, for convenience #include "CiftiBrainModelsMap.h" #include "CiftiLabelsMap.h" #include "CiftiParcelsMap.h" #include "CiftiScalarsMap.h" #include "CiftiSeriesMap.h" #include #include #include namespace caret { class CiftiXML { public: enum { ALONG_ROW = 0, ALONG_COLUMN = 1, ALONG_STACK = 2//better name for this? }; int getNumberOfDimensions() const { return m_indexMaps.size(); } const CiftiVersion& getParsedVersion() const { return m_parsedVersion; } const CiftiMappingType* getMap(const int& direction) const;//can return null in unfilled XML object CiftiMappingType* getMap(const int& direction);//can return null in unfilled XML object GiftiMetaData* getFileMetaData() const;//HACK: allow modification of palette and metadata within XML without setting the xml on a file again PaletteColorMapping* getFilePalette() const; CiftiMappingType::MappingType getMappingType(const int& direction) const;//convenience functions const CiftiBrainModelsMap& getBrainModelsMap(const int& direction) const; CiftiBrainModelsMap& getBrainModelsMap(const int& direction); const CiftiLabelsMap& getLabelsMap(const int& direction) const; CiftiLabelsMap& getLabelsMap(const int& direction); const CiftiParcelsMap& getParcelsMap(const int& direction) const; CiftiParcelsMap& getParcelsMap(const int& direction); const CiftiScalarsMap& getScalarsMap(const int& direction) const; CiftiScalarsMap& getScalarsMap(const int& direction); const CiftiSeriesMap& getSeriesMap(const int& direction) const; CiftiSeriesMap& getSeriesMap(const int& direction); int64_t getDimensionLength(const int& direction) const; std::vector getDimensions() const; void setNumberOfDimensions(const int& num); void setMap(const int& direction, const CiftiMappingType& mapIn); void clear(); void readXML(QXmlStreamReader& xml); void readXML(const QString& text); void readXML(const QByteArray& data); QString writeXMLToString(const CiftiVersion& writingVersion = CiftiVersion()) const; QByteArray writeXMLToQByteArray(const CiftiVersion& writingVersion = CiftiVersion()) const; void writeXML(QXmlStreamWriter& xml, const CiftiVersion& writingVersion = CiftiVersion()) const; ///uses the mapping types to figure out what the intent info should be int32_t getIntentInfo(const CiftiVersion& writingVersion, char intentNameOut[16]) const; CiftiXML() { } CiftiXML(const CiftiXML& rhs); CiftiXML& operator=(const CiftiXML& rhs); bool operator==(const CiftiXML& rhs) const; bool operator!=(const CiftiXML& rhs) const { return !((*this) == rhs); } bool approximateMatch(const CiftiXML& rhs) const; private: std::vector > m_indexMaps; CiftiVersion m_parsedVersion; mutable GiftiMetaData m_fileMetaData;//hack to allow metadata to be modified without allowing dimension-changing operations mutable CaretPointer m_filePalette; void copyHelper(const CiftiXML& rhs); //parsing functions void parseCIFTI1(QXmlStreamReader& xml); void parseMatrix1(QXmlStreamReader& xml); void parseCIFTI2(QXmlStreamReader& xml); void parseMatrix2(QXmlStreamReader& xml); void parseMatrixIndicesMap1(QXmlStreamReader& xml); void parseMatrixIndicesMap2(QXmlStreamReader& xml); //writing functions void writeMatrix1(QXmlStreamWriter& xml) const; void writeMatrix2(QXmlStreamWriter& xml) const; }; } #endif //__CIFTI_XML_H__ workbench-1.1.1/src/Cifti/CiftiXMLElements.cxx000066400000000000000000000472051255417355300212030ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CiftiXMLElements.h" #include "DataFileException.h" #include "GiftiLabelTable.h" #include "PaletteColorMapping.h" #include "VoxelIJK.h" #include #include using namespace caret; using namespace std; void CiftiBrainModelElement::setupLookup(CiftiMatrixIndicesMapElement& myMap) { if (m_modelType == CIFTI_MODEL_TYPE_SURFACE) { if (m_nodeIndices.size() == 0 && m_indexCount != 0) { if (m_indexCount != m_surfaceNumberOfNodes) { throw DataFileException("empty index list found with nonzero indexCount, but indexCount and surfaceNumberOfNodes don't match"); } m_nodeToIndexLookup.resize(m_surfaceNumberOfNodes); for (int i = 0; i < (int)m_surfaceNumberOfNodes; ++i) { m_nodeToIndexLookup[i] = i + m_indexOffset; } } else { if (m_indexCount != (int64_t)m_nodeIndices.size()) { throw DataFileException("indexCount and size of nodeIndices don't match"); } m_nodeToIndexLookup.resize(m_surfaceNumberOfNodes); for (int i = 0; i < (int)m_surfaceNumberOfNodes; ++i) { m_nodeToIndexLookup[i] = -1; } for (int i = 0; i < (int)m_indexCount; ++i) { m_nodeToIndexLookup[m_nodeIndices[i]] = i + m_indexOffset; } } } if (m_modelType == CIFTI_MODEL_TYPE_VOXELS) {//we need access to the Volume element to support the "empty list" behavior, but since it is never used, and will be removed from the spec, ignore it int64_t voxListSize = (int64_t)m_voxelIndicesIJK.size(); for (int64_t i = 0; i < voxListSize; i += 3) { myMap.m_voxelToIndexLookup.at(m_voxelIndicesIJK[i], m_voxelIndicesIJK[i + 1], m_voxelIndicesIJK[i + 2]) = m_indexOffset + (i / 3); } } } void CiftiMatrixIndicesMapElement::setupLookup() { if (m_indicesMapToDataType == CIFTI_INDEX_TYPE_BRAIN_MODELS) { stable_sort(m_brainModels.begin(), m_brainModels.end());//in order to make the overlapping range check easy, and so that iterating through maps goes linearly through the file int numModels = (int)m_brainModels.size(); set usedSurf, usedVol; for (int i = 0; i < numModels; ++i) { if (i > 0 && m_brainModels[i - 1].m_indexOffset + m_brainModels[i - 1].m_indexCount > m_brainModels[i].m_indexOffset) { throw DataFileException("overlapping index ranges found in a brain models dimension"); } switch (m_brainModels[i].m_modelType) { case CIFTI_MODEL_TYPE_SURFACE: if (usedSurf.find(m_brainModels[i].m_brainStructure) != usedSurf.end()) { throw DataFileException("structure " + StructureEnum::toName(m_brainModels[i].m_brainStructure) + " used multiple times in surface models"); } usedSurf.insert(m_brainModels[i].m_brainStructure); break; case CIFTI_MODEL_TYPE_VOXELS: if (usedVol.find(m_brainModels[i].m_brainStructure) != usedVol.end()) { throw DataFileException("structure " + StructureEnum::toName(m_brainModels[i].m_brainStructure) + " used multiple times in volume models"); } usedVol.insert(m_brainModels[i].m_brainStructure); break; case CIFTI_MODEL_TYPE_INVALID: CaretAssert(false); throw DataFileException("found 'invalid' brain model type, tell the developers"); break; } m_brainModels[i].setupLookup(*this); } } if (m_indicesMapToDataType != CIFTI_INDEX_TYPE_PARCELS) return; int numSurfs = (int)m_parcelSurfaces.size(); for (int i = 0; i < numSurfs; ++i) { m_parcelSurfaces[i].m_lookup.resize(m_parcelSurfaces[i].m_numNodes); for (int j = 0; j < m_parcelSurfaces[i].m_numNodes; ++j) { m_parcelSurfaces[i].m_lookup[j] = -1; } for (int j = i + 1; j < numSurfs; ++j) { if (m_parcelSurfaces[i].m_structure == m_parcelSurfaces[j].m_structure) { throw DataFileException("multiple surfaces with same structure found in a parcel map"); } } } int numParcels = (int)m_parcels.size(); for (int i = 0; i < numParcels; ++i) { const CiftiParcelElement& myParcel = m_parcels[i]; int numSurfParts = (int)myParcel.m_nodeElements.size(); vector surfUsed(m_parcelSurfaces.size(), false); for (int j = 0; j < numSurfParts; ++j) { const CiftiParcelNodesElement& myNodeElement = myParcel.m_nodeElements[j]; StructureEnum::Enum myStruct = myNodeElement.m_structure; int whichSurf; for (whichSurf = 0; whichSurf < numSurfs; ++whichSurf) { if (m_parcelSurfaces[whichSurf].m_structure == myStruct) break; } if (whichSurf >= numSurfs) { throw DataFileException("parcel '" + myParcel.m_parcelName + "' specifies a structure that doesn't match a specified surface"); } if (surfUsed[whichSurf]) { throw DataFileException("parcel '" + myParcel.m_parcelName + "' specified a surface structure more than once"); } CiftiParcelSurfaceElement& mySurf = m_parcelSurfaces[whichSurf]; surfUsed[whichSurf] = true; int numNodes = myNodeElement.m_nodes.size(); for (int k = 0; k < numNodes; ++k) { int64_t node = myNodeElement.m_nodes[k]; if (node < 0 || node >= mySurf.m_numNodes) { throw DataFileException("node number " + AString::number(node) + " is invalid for surface " + StructureEnum::toName(myStruct)); } if (mySurf.m_lookup[node] != -1) { throw DataFileException("surface node " + AString::number(node) + " in surface " + StructureEnum::toName(myStruct) + " specified more than once"); } mySurf.m_lookup[node] = i; } } int64_t voxListSize = (int64_t)myParcel.m_voxelIndicesIJK.size(); for (int64_t j = 0; j < voxListSize; j += 3) { int64_t* test = m_voxelToIndexLookup.find(myParcel.m_voxelIndicesIJK[j], myParcel.m_voxelIndicesIJK[j + 1], myParcel.m_voxelIndicesIJK[j + 2]); if (test != NULL && *test != i) { throw DataFileException("voxel " + AString::number(myParcel.m_voxelIndicesIJK[j]) + ", " + AString::number(myParcel.m_voxelIndicesIJK[j + 1]) + ", " + AString::number(myParcel.m_voxelIndicesIJK[j + 2]) + " used by more than one parcel"); } m_voxelToIndexLookup.at(myParcel.m_voxelIndicesIJK[j], myParcel.m_voxelIndicesIJK[j + 1], myParcel.m_voxelIndicesIJK[j + 2]) = i; } } } bool CiftiMatrixIndicesMapElement::operator==(const CiftiMatrixIndicesMapElement& rhs) const {//NOTE: don't check the applies to dimension vector, this should check only the mapping, not how it is used if (this == &rhs) return true;//compare pointers to skip checking object against itself if (m_indicesMapToDataType != rhs.m_indicesMapToDataType) return false; switch (m_indicesMapToDataType) { case CIFTI_INDEX_TYPE_INVALID: break;//is there anything to check? case CIFTI_INDEX_TYPE_BRAIN_MODELS: { if (m_brainModels.size() != rhs.m_brainModels.size()) return false; vector used(rhs.m_brainModels.size(), false);//prevent reuse, in case some idiocy winds up having overlapping mappings for (size_t i = 0; i < m_brainModels.size(); ++i)//need to allow the mappings to be placed in a different order, as long as the cifti index ranges line up { bool found = false; for (size_t j = 0; j < rhs.m_brainModels.size(); ++j) { if (!used[j] && m_brainModels[i] == rhs.m_brainModels[j]) { used[j] = true; found = true; break; } } if (!found) return false; } } break; case CIFTI_INDEX_TYPE_FIBERS: break;//??? case CIFTI_INDEX_TYPE_PARCELS: { if (m_parcels.size() != rhs.m_parcels.size() || m_parcelSurfaces.size() != rhs.m_parcelSurfaces.size()) return false; vector used(rhs.m_parcelSurfaces.size(), false); for (size_t i = 0; i < m_parcelSurfaces.size(); ++i) { bool found = false; for (size_t j = 0; j < rhs.m_parcelSurfaces.size(); ++j) { if (!used[j] && m_parcelSurfaces[i] == rhs.m_parcelSurfaces[j]) { used[j] = true; found = true; break; } } if (!found) return false; } for (size_t i = 0; i < m_parcels.size(); ++i) { if (m_parcels[i] != rhs.m_parcels[i]) return false; } } break; case CIFTI_INDEX_TYPE_TIME_POINTS: { if (m_numTimeSteps != rhs.m_numTimeSteps) return false; float timestep, rhtimestep; switch (m_timeStepUnits) { case NIFTI_UNITS_SEC: timestep = m_timeStep; break; case NIFTI_UNITS_MSEC: timestep = m_timeStep * 0.001f; break; case NIFTI_UNITS_USEC: timestep = m_timeStep * 0.000001f; break; default: return false; } switch (rhs.m_timeStepUnits) { case NIFTI_UNITS_SEC: rhtimestep = rhs.m_timeStep; break; case NIFTI_UNITS_MSEC: rhtimestep = rhs.m_timeStep * 0.001f; break; case NIFTI_UNITS_USEC: rhtimestep = rhs.m_timeStep * 0.000001f; break; default: return false; } const float TOLERANCE = 0.999f;//if they don't match exactly, and either one of them is zero, or their ratio is far from 1, say they don't match if (timestep != rhtimestep && (timestep == 0.0f || rhtimestep == 0.0f || timestep / rhtimestep < TOLERANCE || rhtimestep / timestep < TOLERANCE)) return false; } break; case CIFTI_INDEX_TYPE_LABELS: case CIFTI_INDEX_TYPE_SCALARS: { size_t size = m_namedMaps.size(); if (rhs.m_namedMaps.size() != size) return false; for (size_t i = 0; i < size; ++i) { if (m_namedMaps[i] != rhs.m_namedMaps[i]) return false; } } break; } return true; } bool CiftiBrainModelElement::operator==(const CiftiBrainModelElement& rhs) const { if (m_indexOffset != rhs.m_indexOffset) return false; if (m_indexCount != rhs.m_indexCount) return false; if (m_modelType != rhs.m_modelType) return false; if (m_brainStructure != rhs.m_brainStructure) return false; switch (m_modelType) { case CIFTI_MODEL_TYPE_SURFACE: { size_t numIndices = m_nodeIndices.size(), rhsIndices = rhs.m_nodeIndices.size(); if (m_surfaceNumberOfNodes != rhs.m_surfaceNumberOfNodes) return false; if (numIndices == 0) { if (rhsIndices != 0) { if ((int64_t)rhsIndices != rhs.m_indexCount) return false; for (size_t i = 0; i < rhsIndices; ++i) { if (rhs.m_nodeIndices[i] != (int64_t)i) return false; } } } else { if (rhsIndices == 0) { if ((int64_t)numIndices != m_indexCount) return false; for (size_t i = 0; i < numIndices; ++i) { if (m_nodeIndices[i] != (int64_t)i) return false; } } else { if (numIndices != rhsIndices) return false; for (size_t i = 0; i < numIndices; ++i) { if (m_nodeIndices[i] != rhs.m_nodeIndices[i]) return false; } } } } break; case CIFTI_MODEL_TYPE_VOXELS: { size_t numIndices = m_voxelIndicesIJK.size(), rhsIndices = m_voxelIndicesIJK.size(); if (numIndices != rhsIndices) return false; for (size_t i = 0; i < numIndices; ++i)//treat them as flat, even though they aren't { if (m_voxelIndicesIJK[i] != rhs.m_voxelIndicesIJK[i]) return false; } } break; case CIFTI_MODEL_TYPE_INVALID: CaretAssert(false); throw DataFileException("found 'invalid' brain model type, tell the developers"); break; } return true; } bool CiftiBrainModelElement::operator<(const CiftiBrainModelElement& rhs) const { return m_indexOffset < rhs.m_indexOffset; } bool CiftiNamedMapElement::operator==(const CiftiNamedMapElement& rhs) const { if (m_mapName != rhs.m_mapName) return false; if (m_labelTable == NULL) { if (rhs.m_labelTable != NULL) return false; } else { if (rhs.m_labelTable == NULL) return false; if (!m_labelTable->matches(*(rhs.m_labelTable))) return false; } return true; } CiftiNamedMapElement::CiftiNamedMapElement(const CiftiNamedMapElement& rhs) { m_mapName = rhs.m_mapName; m_defaultPalette = rhs.m_defaultPalette; m_mapMetaData = rhs.m_mapMetaData; if (rhs.m_palette != NULL) m_palette.grabNew(new PaletteColorMapping(*(rhs.m_palette))); if (rhs.m_labelTable != NULL) m_labelTable.grabNew(new GiftiLabelTable(*(rhs.m_labelTable))); } CiftiNamedMapElement& CiftiNamedMapElement::operator=(const CiftiNamedMapElement& rhs) { if (this == &rhs) return *this; m_mapName = rhs.m_mapName; m_mapMetaData = rhs.m_mapMetaData; if (rhs.m_palette != NULL) { m_palette.grabNew(new PaletteColorMapping(*(rhs.m_palette))); } else { m_palette.grabNew(NULL); } if (rhs.m_labelTable != NULL) { m_labelTable.grabNew(new GiftiLabelTable(*(rhs.m_labelTable))); } else { m_labelTable.grabNew(NULL); } return *this; } ///NOTE: this is not a standard equality test, it skips checking the nodes list because we do that elsewhere bool CiftiParcelNodesElement::operator==(const CiftiParcelNodesElement& rhs) const { if (m_structure != rhs.m_structure) return false; if (m_nodes.size() != rhs.m_nodes.size()) return false; /*for (size_t i = 0; i < m_nodes.size(); ++i) { if (m_nodes[i] != rhs.m_nodes[i]) return false; }//*///not needed, we check node equivalence by checking the lookup in each surface, this way wouldn't work for same nodes in different order anyway return true; } bool CiftiParcelElement::operator==(const CiftiParcelElement& rhs) const { if (m_parcelName != rhs.m_parcelName) return false; if (m_nodeElements.size() != rhs.m_nodeElements.size() || m_voxelIndicesIJK.size() != rhs.m_voxelIndicesIJK.size()) return false; vector used(rhs.m_nodeElements.size(), false); for (size_t i = 0; i < m_nodeElements.size(); ++i) { bool found = false; for (size_t j = 0; j < rhs.m_nodeElements.size(); ++j) { if (!used[j] && m_nodeElements[i] == rhs.m_nodeElements[j]) { found = true; used[j] = true; break; } } if (!found) return false; } set myvoxset, rhsvoxset;//different orders mean the same thing, so sort voxels, then compare for (size_t i = 0; i < m_voxelIndicesIJK.size(); i += 3) { myvoxset.insert(VoxelIJK(m_voxelIndicesIJK.data() + i)); rhsvoxset.insert(VoxelIJK(rhs.m_voxelIndicesIJK.data() + i)); } return (myvoxset == rhsvoxset); } bool CiftiParcelSurfaceElement::operator==(const CiftiParcelSurfaceElement& rhs) const { if (m_structure != rhs.m_structure) return false; if (m_numNodes != rhs.m_numNodes) return false; CaretAssert(m_numNodes == (int64_t)m_lookup.size()); CaretAssert(rhs.m_lookup.size() == m_lookup.size()); for (size_t i = 0; i < m_lookup.size(); ++i) {//check lookup instead of checking node lists in parcels if (m_lookup[i] != rhs.m_lookup[i]) return false; } return true; } CiftiMatrixElement::CiftiMatrixElement(const CiftiMatrixElement& rhs) { m_labelTable = rhs.m_labelTable; m_userMetaData = rhs.m_userMetaData; if (rhs.m_palette != NULL) m_palette.grabNew(new PaletteColorMapping(*(rhs.m_palette))); m_defaultPalette = rhs.m_defaultPalette; m_matrixIndicesMap = rhs.m_matrixIndicesMap; m_volume = rhs.m_volume; } CiftiMatrixElement& CiftiMatrixElement::operator=(const caret::CiftiMatrixElement& rhs) { if (this == &rhs) return *this; m_labelTable = rhs.m_labelTable; m_userMetaData = rhs.m_userMetaData; if (rhs.m_palette != NULL) m_palette.grabNew(new PaletteColorMapping(*(rhs.m_palette))); m_defaultPalette = rhs.m_defaultPalette; m_matrixIndicesMap = rhs.m_matrixIndicesMap; m_volume = rhs.m_volume; return *this; } CiftiNamedMapElement::CiftiNamedMapElement() {//just so that the destructors of GiftiLabelTable and PaletteColorMapping are resolved without including them into the .h m_defaultPalette = false; } CiftiNamedMapElement::~CiftiNamedMapElement() { } CiftiMatrixElement::CiftiMatrixElement() {//ditto m_defaultPalette = false; } CiftiMatrixElement::~CiftiMatrixElement() { } workbench-1.1.1/src/Cifti/CiftiXMLElements.h000066400000000000000000000231251255417355300206230ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #ifndef __CIFTI_XML_ELEMENTS #define __CIFTI_XML_ELEMENTS #include #include #include #include "AString.h" #include "CaretPointer.h" #include "CaretCompact3DLookup.h" #include "CiftiVersion.h" #include "nifti2.h" #include "StructureEnum.h" /* Cifti Defines */ namespace caret { class PaletteColorMapping; class GiftiLabelTable; /*! ModelType */ enum ModelType { CIFTI_MODEL_TYPE_INVALID, CIFTI_MODEL_TYPE_SURFACE=1,/*!< CIFTI_MODEL_TYPE_SURFACE*/ CIFTI_MODEL_TYPE_VOXELS=2/*!< CIFTI_MODEL_TYPE_VOXELS*/ }; /*! IndicesMapToDataType*/ enum IndicesMapToDataType { CIFTI_INDEX_TYPE_INVALID, CIFTI_INDEX_TYPE_BRAIN_MODELS=1,/*!< CIFTI_INDEX_TYPE_BRAIN_MODELS*/ CIFTI_INDEX_TYPE_FIBERS=2,/*!< CIFTI_INDEX_TYPE_FIBERS*/ CIFTI_INDEX_TYPE_PARCELS=3,/*!< CIFTI_INDEX_TYPE_PARCELS*/ CIFTI_INDEX_TYPE_TIME_POINTS=4,/*!< CIFTI_INDEX_TYPE_TIME_POINTS*/ CIFTI_INDEX_TYPE_SCALARS=5, CIFTI_INDEX_TYPE_LABELS=6 }; typedef int voxelIndexType; class CiftiMatrixIndicesMapElement; /// Cifti Brain Model XML Element class CiftiBrainModelElement { public: //CiftiBrainModelElement(); int64_t m_indexOffset; /*!< Index of first element in dimension of the matrix for this brain structure. The value is the number of elements, NOT the number of bytes. */ int64_t m_indexCount; /*!< Number of elements in this brain model. */ ModelType m_modelType; /*!< Type of model representing the brain structure. */ StructureEnum::Enum m_brainStructure; /*!< Identifies the brain structure. Valid values are contained in nifti2.h */ int64_t m_surfaceNumberOfNodes; /*!< This attribute contains the actual (or true) number of nodes in the surface that is associated with this BrainModel.*/ //children std::vector m_nodeIndices; /*!< Contains a list of nodes indices for a BrainModel with ModelType equal to CIFTI_MODEL_TYPE_SURFACE.*/ std::vector m_voxelIndicesIJK; /*!< Identifies the voxels that model a brain structure. */ std::vector m_nodeToIndexLookup;//used by CiftiXML to quickly lookup indexes by node number void setupLookup(CiftiMatrixIndicesMapElement& myMap);//convenience function to populate lookup bool operator==(const CiftiBrainModelElement& rhs) const; bool operator<(const CiftiBrainModelElement& rhs) const;//for sorting by starting index }; struct CiftiNamedMapElement { mutable AString m_mapName; mutable CaretPointer m_labelTable; mutable std::map m_mapMetaData;/*!< User Meta Data*/ mutable CaretPointer m_palette;//palette settings storage mutable bool m_defaultPalette;//secondary variable to enable resetting the palette to use defaults, which will make it not write the palette to file CiftiNamedMapElement(); CiftiNamedMapElement(const CiftiNamedMapElement& rhs);//to turn copy and assignment into a deep copy of the label table ~CiftiNamedMapElement(); CiftiNamedMapElement& operator=(const CiftiNamedMapElement& rhs); bool operator==(const CiftiNamedMapElement& rhs) const; bool operator!=(const CiftiNamedMapElement& rhs) const { return !(*this == rhs); } }; struct CiftiParcelNodesElement { StructureEnum::Enum m_structure; std::vector m_nodes; bool operator==(const CiftiParcelNodesElement& rhs) const; bool operator!=(const CiftiParcelNodesElement& rhs) const { return !(*this == rhs); } }; struct CiftiParcelElement { AString m_parcelName; std::vector m_nodeElements; std::vector m_voxelIndicesIJK; bool operator==(const CiftiParcelElement& rhs) const; bool operator!=(const CiftiParcelElement& rhs) const { return !(*this == rhs); } }; struct CiftiParcelSurfaceElement { StructureEnum::Enum m_structure; int64_t m_numNodes; std::vector m_lookup; bool operator==(const CiftiParcelSurfaceElement& rhs) const; bool operator!=(const CiftiParcelSurfaceElement& rhs) const { return !(*this == rhs); } }; /// Cifti Matrix Indices Map XML Element class CiftiMatrixIndicesMapElement { public: CiftiMatrixIndicesMapElement() { m_timeStep = -1.0; m_timeStart = -1.0; m_hasTimeStart = false; m_timeStepUnits = -1; m_numTimeSteps = -1; m_indicesMapToDataType = CIFTI_INDEX_TYPE_INVALID; } std::vector m_appliesToMatrixDimension; /*!< Lists the dimension(s) of the matrix to which this MatrixIndicesMap applies. */ IndicesMapToDataType m_indicesMapToDataType; /*!< Type of data to which the MatrixIndicesMap applies. */ double m_timeStep; /*!< Indicates amount of time between each timepoint. */ double m_timeStart; bool m_hasTimeStart; int m_timeStepUnits;/*!< Indicates units of TimeStep. */ int m_numTimeSteps;//used by CiftiXML to store the information that is critically lacking in the XML extension std::vector m_brainModels;/*!< A vector array of Brain Models */ CaretCompact3DLookup m_voxelToIndexLookup;//make one unified lookup rather than separate lookups per volume structure std::vector m_namedMaps; std::vector m_parcels; std::vector m_parcelSurfaces; void setupLookup(); bool operator==(const CiftiMatrixIndicesMapElement& rhs) const; }; /// Cifti Label XML Element class CiftiLabelElement { public: CiftiLabelElement() { m_red = m_green = m_blue = m_alpha = m_x = m_y = m_z = 0.0; } unsigned long long m_key;/*!< Corresponding index, starting at zero, of a row and/or column of the connectivity matrix.*/ float m_red;/*!< Red color component for label. Value is floating point with range 0.0 to 1.0.*/ float m_green;/*!< Green color component for label. Value is floating point with range 0.0 to 1.0.*/ float m_blue;/*!< Blue color component for label. Value is floating point with range 0.0 to 1.0.*/ float m_alpha;/*!< Alpha color component for label. Value is floating point with range 0.0 to 1.0.*/ float m_x;/*!< X-coordinate of spatial location associated with the label. Value is floating point.*/ float m_y;/*!< Y-coordinate of spatial location associated with the label. Value is floating point.*/ float m_z;/*!< Z-coordinate of spatial location associated with the label. Value is floating point.*/ QString m_text;/*!< Text of the label.*/ }; /// Transformation Matrix Voxel Indices IJK to XYZ XML Element class TransformationMatrixVoxelIndicesIJKtoXYZElement { public: unsigned long m_dataSpace;/*!< Contains the name of the space of the voxels prior to application of the transformation matrix.*/ unsigned long m_transformedSpace;/*!< Contains the name of the space of the voxels after application of the transformation of voxel IJK indices to spatial XYZ coordinates. */ unsigned long m_unitsXYZ;/*!< Identifies the units of the spatial XYZ coordinates.*/ float m_transform[16];/*!< A 16 element matrix in row-major form which contains a matrix that for conversion of Voxel IJK Indices to spatial XYZ coordinates (+X=>right, +Y=>anterior, +Z=>posterior).*/ }; /// Cifti Volume XML Element class CiftiVolumeElement { public: std::vector m_transformationMatrixVoxelIndicesIJKtoXYZ;/*!< Vector array of 0 or more TransformationMatrixVoxelIndicesIJKtoXYZ*/ unsigned int m_volumeDimensions[3];/*!< Three integer values that indicate original dimensions of the volume from which the voxels were extracted.*/ }; /// Cifti Matrix XML Element class CiftiMatrixElement { public: CiftiMatrixElement(const CiftiMatrixElement& rhs); CiftiMatrixElement& operator=(const CiftiMatrixElement& rhs); CiftiMatrixElement(); ~CiftiMatrixElement(); std::vector m_labelTable;/*!< The Matrix's Label Table (optional)*///TODO: replace this with GiftiLabelTable (or remove? not being used for anything) mutable std::map m_userMetaData;/*!< User Meta Data*/ mutable CaretPointer m_palette;//palette settings storage mutable bool m_defaultPalette;//secondary variable to enable resetting the palette to use defaults, which will make it not write the palette to file std::vector m_matrixIndicesMap;/*!< Vector array of one or more Matrix Indices Map Elements*/ std::vector m_volume;/*!< Volume Element*/ }; /// Cifti Root XML Element class CiftiRootElement { public: CiftiRootElement() { m_numberOfMatrices = 0; } CiftiVersion m_version;/*!< Version String*/ unsigned long m_numberOfMatrices;/*!< Number of Matrices*/ std::vector m_matrices; /*!< Matrices, currently there is only matrix, but future versions may allow for more */ }; } #endif //__CIFTI_ELEMENTS workbench-1.1.1/src/Cifti/CiftiXMLOld.cxx000066400000000000000000002460111255417355300201410ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretAssert.h" #include "CiftiXMLOld.h" #include "DataFileContentInformation.h" #include "DataFileException.h" #include "FloatMatrix.h" #include "GiftiLabelTable.h" #include "Palette.h" #include "PaletteColorMapping.h" #include "CiftiXML.h" #include "CaretLogger.h" using namespace caret; using namespace std; /*void CiftiXMLOld::testNewXML(const QString& xmlString) { CiftiXML test1, test2; CiftiXMLOld compare1, compare2; AString stage; try { stage = "1_newRead1"; test1.readXML(AString(xmlString)); for (int i = 0; i < test1.getNumberOfDimensions(); ++i) { if (test1.getMappingType(i) == CiftiMappingType::SERIES) { CiftiSeriesMap myMap = test1.getSeriesMap(i); myMap.setLength(1);//there are asserts in series map that upon writing, it must know the length, so fake it for now test1.setMap(i, myMap); } } stage = "2_newWrite2"; AString newWritten2 = test1.writeXMLToString(CiftiVersion(1, 1)); stage = "3_newRead2"; test2.readXML(newWritten2); stage = "4_newCompare"; if (test1.getNumberOfDimensions() == test2.getNumberOfDimensions()) { for (int i = 0; i < test1.getNumberOfDimensions(); ++i) { if (*(test1.getMap(i)) != *(test2.getMap(i))) { CaretLogWarning("comparison in new cifti xml failed, dimension " + AString::number(i) + " not equal"); } } } else { CaretLogWarning("comparison in new cifti xml failed, different number of dimensions"); } stage = "5_newWrite1"; AString newWritten1 = test2.writeXMLToString(CiftiVersion(1, 0)); stage = "6_oldRead1"; compare1.readXML(newWritten1, false);//prevent recursively triggering test compare2.readXML(xmlString, false); if (!compare1.matchesForRows(compare2)) { CaretLogWarning("comparison in old xml failed for rows"); } if (!compare1.matchesForColumns(compare2)) { CaretLogWarning("comparison in old xml failed for columns"); } } catch (CaretException& e) { CaretLogWarning("error testing new xml: stage '" + stage + "', error: " + e.whatString()); } catch (std::exception& e) { CaretLogWarning("error testing new xml: stage '" + stage + "', error: " + e.what()); } catch (...) { CaretLogWarning("error testing new xml: stage '" + stage + "', unknown throw type"); } }//*/ /*void CiftiXMLOld::testNewXML(const QByteArray& xmlBytes) { CiftiXML test1, test2; CiftiXMLOld compare1, compare2; AString stage; try { stage = "1_newRead1"; test1.readXML(xmlBytes); for (int i = 0; i < test1.getNumberOfDimensions(); ++i) { if (test1.getMappingType(i) == CiftiMappingType::SERIES) { CiftiSeriesMap myMap = test1.getSeriesMap(i); myMap.setLength(1);//there are asserts in series map that upon writing, it must know the length, so fake it for now test1.setMap(i, myMap); } } stage = "2_newWrite2"; QByteArray newWritten2 = test1.writeXMLToQByteArray(CiftiVersion(1, 1)); stage = "3_newRead2"; test2.readXML(newWritten2); stage = "4_newCompare"; if (test1.getNumberOfDimensions() == test2.getNumberOfDimensions()) { for (int i = 0; i < test1.getNumberOfDimensions(); ++i) { if (*(test1.getMap(i)) != *(test2.getMap(i))) { CaretLogWarning("comparison in new cifti xml failed, dimension " + AString::number(i) + " not equal"); } } } else { CaretLogWarning("comparison in new cifti xml failed, different number of dimensions"); } stage = "5_newWrite1"; QByteArray newWritten1 = test2.writeXMLToQByteArray(CiftiVersion(1, 0)); stage = "6_oldRead1"; compare1.readXML(newWritten1, false);//prevent recursively triggering test compare2.readXML(xmlBytes, false); if (!compare1.matchesForRows(compare2)) { CaretLogWarning("comparison in old xml failed for rows"); } if (!compare1.matchesForColumns(compare2)) { CaretLogWarning("comparison in old xml failed for columns"); } } catch (CaretException& e) { CaretLogWarning("error testing new xml: stage '" + stage + "', error: " + e.whatString()); } catch (std::exception& e) { CaretLogWarning("error testing new xml: stage '" + stage + "', error: " + e.what()); } catch (...) { CaretLogWarning("error testing new xml: stage '" + stage + "', unknown throw type"); } }//*/ CiftiXMLOld::CiftiXMLOld() { m_dimToMapLookup.resize(2, -1);//assume matrix is 2D, for backwards compatibility with Row/Column functions } map* CiftiXMLOld::getFileMetaData() const { if (m_root.m_matrices.size() == 0) return NULL; return &(m_root.m_matrices[0].m_userMetaData); } int64_t CiftiXMLOld::getSurfaceIndex(const int64_t& node, const CiftiBrainModelElement* myElement) const { if (myElement == NULL || myElement->m_modelType != CIFTI_MODEL_TYPE_SURFACE) return -1; if (node < 0 || node > (int64_t)(myElement->m_surfaceNumberOfNodes)) return -1; CaretAssertVectorIndex(myElement->m_nodeToIndexLookup, node); return myElement->m_nodeToIndexLookup[node]; } int64_t CiftiXMLOld::getColumnIndexForNode(const int64_t& node, const StructureEnum::Enum& structure) const { return getSurfaceIndex(node, findSurfaceModel(m_dimToMapLookup[0], structure));//a column index is an index to get an entire column, so index ALONG a row } int64_t CiftiXMLOld::getRowIndexForNode(const int64_t& node, const StructureEnum::Enum& structure) const { return getSurfaceIndex(node, findSurfaceModel(m_dimToMapLookup[1], structure)); } int64_t CiftiXMLOld::getVolumeIndex(const int64_t* ijk, const int& myMapIndex) const { if (myMapIndex == -1 || m_root.m_matrices.size() == 0) return -1; CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, myMapIndex); const CiftiMatrixIndicesMapElement* myMap = &(m_root.m_matrices[0].m_matrixIndicesMap[myMapIndex]); if (myMap->m_indicesMapToDataType != CIFTI_INDEX_TYPE_BRAIN_MODELS) { return -1; } if (m_root.m_matrices[0].m_volume.size() == 0) return -1; const CiftiVolumeElement& myVol = m_root.m_matrices[0].m_volume[0]; if (ijk[0] < 0 || ijk[0] >= (int64_t)myVol.m_volumeDimensions[0]) return -1;//some shortcuts to not search all the voxels on invalid coords if (ijk[1] < 0 || ijk[1] >= (int64_t)myVol.m_volumeDimensions[1]) return -1; if (ijk[2] < 0 || ijk[2] >= (int64_t)myVol.m_volumeDimensions[2]) return -1; const int64_t* test = myMap->m_voxelToIndexLookup.find(ijk); if (test == NULL) return -1; return *test; } int64_t CiftiXMLOld::getColumnIndexForVoxel(const int64_t* ijk) const { return getVolumeIndex(ijk, m_dimToMapLookup[0]); } int64_t CiftiXMLOld::getRowIndexForVoxel(const int64_t* ijk) const { return getVolumeIndex(ijk, m_dimToMapLookup[1]); } bool CiftiXMLOld::getSurfaceMap(const int& direction, vector& mappingOut, const StructureEnum::Enum& structure) const { if (direction < 0 || direction >= (int)m_dimToMapLookup.size()) { mappingOut.clear(); return false; } const CiftiBrainModelElement* myModel = findSurfaceModel(m_dimToMapLookup[direction], structure); if (myModel == NULL || myModel->m_modelType != CIFTI_MODEL_TYPE_SURFACE) { mappingOut.clear(); return false; } int64_t mappingSize = myModel->m_indexCount; mappingOut.resize(mappingSize); if (myModel->m_nodeIndices.size() == 0) { CaretAssert(myModel->m_indexCount == myModel->m_surfaceNumberOfNodes); for (int i = 0; i < mappingSize; ++i) { mappingOut[i].m_ciftiIndex = myModel->m_indexOffset + i; mappingOut[i].m_surfaceNode = i; } } else { for (int i = 0; i < mappingSize; ++i) { mappingOut[i].m_ciftiIndex = myModel->m_indexOffset + i; mappingOut[i].m_surfaceNode = myModel->m_nodeIndices[i]; } } return true; } bool CiftiXMLOld::getSurfaceMapForColumns(vector& mappingOut, const StructureEnum::Enum& structure) const { return getSurfaceMap(ALONG_COLUMN, mappingOut, structure); } bool CiftiXMLOld::getSurfaceMapForRows(vector& mappingOut, const StructureEnum::Enum& structure) const { return getSurfaceMap(ALONG_ROW, mappingOut, structure); } bool CiftiXMLOld::getVolumeMap(const int& direction, vector& mappingOut) const { mappingOut.clear(); if (direction < 0 || direction >= (int)m_dimToMapLookup.size()) { return false; } int myMapIndex = m_dimToMapLookup[direction]; if (myMapIndex == -1 || m_root.m_matrices.size() == 0) { return false; } CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, myMapIndex); const CiftiMatrixIndicesMapElement* myMap = &(m_root.m_matrices[0].m_matrixIndicesMap[myMapIndex]); if (myMap->m_indicesMapToDataType != CIFTI_INDEX_TYPE_BRAIN_MODELS) { return false; } int64_t myIndex = 0; bool first = true; for (int64_t i = 0; i < (int64_t)myMap->m_brainModels.size(); ++i) { if (myMap->m_brainModels[i].m_modelType == CIFTI_MODEL_TYPE_VOXELS) { const vector& myVoxels = myMap->m_brainModels[i].m_voxelIndicesIJK; int64_t voxelArraySize = (int64_t)myVoxels.size(); int64_t modelOffset = myMap->m_brainModels[i].m_indexOffset; int64_t j1 = 0; if (first) { mappingOut.reserve(voxelArraySize / 3);//skip the tiny vector reallocs first = false; } for (int64_t j = 0; j < voxelArraySize; j += 3) { mappingOut.push_back(CiftiVolumeMap());//default constructor should be NOOP and get removed by compiler mappingOut[myIndex].m_ciftiIndex = modelOffset + j1; mappingOut[myIndex].m_ijk[0] = myVoxels[j]; mappingOut[myIndex].m_ijk[1] = myVoxels[j + 1]; mappingOut[myIndex].m_ijk[2] = myVoxels[j + 2]; ++j1; ++myIndex; } } } return true; } bool CiftiXMLOld::getVolumeMapForColumns(vector& mappingOut) const { return getVolumeMap(ALONG_COLUMN, mappingOut); } bool CiftiXMLOld::getVolumeMapForRows(vector& mappingOut) const { return getVolumeMap(ALONG_ROW, mappingOut); } void CiftiXMLOld::getVoxelInfoInDataFileContentInformation(const int& direction, DataFileContentInformation& dataFileInformation) const { if (direction < 0 || direction >= (int)m_dimToMapLookup.size()) { return; } int myMapIndex = m_dimToMapLookup[direction]; if (myMapIndex == -1 || m_root.m_matrices.size() == 0) { return; } CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, myMapIndex); const CiftiMatrixIndicesMapElement* myMap = &(m_root.m_matrices[0].m_matrixIndicesMap[myMapIndex]); if (myMap->m_indicesMapToDataType != CIFTI_INDEX_TYPE_BRAIN_MODELS) { return; } VolumeSpace volumeSpace; getVolumeSpace(volumeSpace); const int64_t* dims = volumeSpace.getDims(); dataFileInformation.addNameAndValue("Dimensions", AString::fromNumbers(dims, 3, ",")); VolumeSpace::OrientTypes orientation[3]; float spacing[3]; float origin[3]; volumeSpace.getOrientAndSpacingForPlumb(orientation, spacing, origin); dataFileInformation.addNameAndValue("Spacing", AString::fromNumbers(spacing, 3, ",")); dataFileInformation.addNameAndValue("Origin", AString::fromNumbers(origin, 3, ",")); const std::vector >& sform = volumeSpace.getSform(); for (uint32_t i = 0; i < sform.size(); i++) { dataFileInformation.addNameAndValue(("sform row " + AString::number(i)), AString::fromNumbers(sform[i], ",")); } int64_t myIndex = 0; for (int64_t i = 0; i < (int64_t)myMap->m_brainModels.size(); ++i) { if (myMap->m_brainModels[i].m_modelType == CIFTI_MODEL_TYPE_VOXELS) { const vector& myVoxels = myMap->m_brainModels[i].m_voxelIndicesIJK; int64_t voxelArraySize = (int64_t)myVoxels.size(); int64_t modelOffset = myMap->m_brainModels[i].m_indexOffset; int64_t j1 = 0; const AString structureName = StructureEnum::toGuiName(myMap->m_brainModels[i].m_brainStructure); for (int64_t j = 0; j < voxelArraySize; j += 3) { const int64_t ijk[3] = { myVoxels[j], myVoxels[j + 1], myVoxels[j + 2] }; float xyz[3]; volumeSpace.indexToSpace(ijk, xyz); const AString msg = ("ijk=(" + AString::fromNumbers(ijk, 3, ",") + "), xyz=(" + AString::fromNumbers(xyz, 3, ", ") + "), row=" + AString::number(modelOffset + j1) + " "); dataFileInformation.addNameAndValue(structureName, msg); ++j1; ++myIndex; } } } } bool CiftiXMLOld::getVolumeStructureMap(const int& direction, vector& mappingOut, const StructureEnum::Enum& structure) const { mappingOut.clear(); CaretAssertVectorIndex(m_dimToMapLookup, direction); int myMapIndex = m_dimToMapLookup[direction]; const CiftiBrainModelElement* myModel = findVolumeModel(myMapIndex, structure); if (myModel == NULL) { return false; } int64_t size = (int64_t)myModel->m_voxelIndicesIJK.size(); CaretAssert(size % 3 == 0); mappingOut.resize(size / 3); int64_t index = 0; for (int64_t i = 0; i < size; i += 3) { mappingOut[index].m_ciftiIndex = myModel->m_indexOffset + index; mappingOut[index].m_ijk[0] = myModel->m_voxelIndicesIJK[i]; mappingOut[index].m_ijk[1] = myModel->m_voxelIndicesIJK[i + 1]; mappingOut[index].m_ijk[2] = myModel->m_voxelIndicesIJK[i + 2]; ++index; } return true; } bool CiftiXMLOld::getVolumeStructureMapForColumns(vector& mappingOut, const StructureEnum::Enum& structure) const { return getVolumeStructureMap(ALONG_COLUMN, mappingOut, structure); } bool CiftiXMLOld::getVolumeStructureMapForRows(vector& mappingOut, const StructureEnum::Enum& structure) const { return getVolumeStructureMap(ALONG_ROW, mappingOut, structure); } bool CiftiXMLOld::getVolumeModelMappings(vector& mappingsOut, const int& myMapIndex) const { mappingsOut.clear(); if (myMapIndex == -1 || m_root.m_matrices.size() == 0) { return false; } CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, myMapIndex); const CiftiMatrixIndicesMapElement* myMap = &(m_root.m_matrices[0].m_matrixIndicesMap[myMapIndex]); if (myMap->m_indicesMapToDataType != CIFTI_INDEX_TYPE_BRAIN_MODELS) { return false; } int numModels = (int)myMap->m_brainModels.size(); mappingsOut.reserve(numModels); for (int i = 0; i < numModels; ++i) { if (myMap->m_brainModels[i].m_modelType == CIFTI_MODEL_TYPE_VOXELS) { mappingsOut.push_back(CiftiVolumeStructureMap()); int whichMap = (int)mappingsOut.size() - 1; mappingsOut[whichMap].m_structure = myMap->m_brainModels[i].m_brainStructure; int numIndices = (int)myMap->m_brainModels[i].m_indexCount; mappingsOut[whichMap].m_map.resize(numIndices); for (int index = 0; index < numIndices; ++index) { mappingsOut[whichMap].m_map[index].m_ciftiIndex = myMap->m_brainModels[i].m_indexOffset + index; int64_t i3 = index * 3; mappingsOut[whichMap].m_map[index].m_ijk[0] = myMap->m_brainModels[i].m_voxelIndicesIJK[i3]; mappingsOut[whichMap].m_map[index].m_ijk[1] = myMap->m_brainModels[i].m_voxelIndicesIJK[i3 + 1]; mappingsOut[whichMap].m_map[index].m_ijk[2] = myMap->m_brainModels[i].m_voxelIndicesIJK[i3 + 2]; } } } return true; } bool CiftiXMLOld::getVolumeModelMapsForColumns(vector& mappingsOut) const { return getVolumeModelMappings(mappingsOut, m_dimToMapLookup[1]); } bool CiftiXMLOld::getVolumeModelMapsForRows(vector& mappingsOut) const { return getVolumeModelMappings(mappingsOut, m_dimToMapLookup[0]); } bool CiftiXMLOld::getStructureLists(const int& direction, vector& surfaceList, vector& volumeList) const { surfaceList.clear(); volumeList.clear(); if (direction < 0 || direction >= (int)m_dimToMapLookup.size()) { return false; } int myMapIndex = m_dimToMapLookup[direction]; if (myMapIndex == -1 || m_root.m_matrices.size() == 0) { return false; } CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, myMapIndex); const CiftiMatrixIndicesMapElement* myMap = &(m_root.m_matrices[0].m_matrixIndicesMap[myMapIndex]); if (myMap->m_indicesMapToDataType != CIFTI_INDEX_TYPE_BRAIN_MODELS) { return false; } int numModels = (int)myMap->m_brainModels.size(); for (int i = 0; i < numModels; ++i) { switch (myMap->m_brainModels[i].m_modelType) { case CIFTI_MODEL_TYPE_SURFACE: surfaceList.push_back(myMap->m_brainModels[i].m_brainStructure); break; case CIFTI_MODEL_TYPE_VOXELS: volumeList.push_back(myMap->m_brainModels[i].m_brainStructure); break; default: break; } } return true; } bool CiftiXMLOld::getStructureListsForColumns(vector& surfaceList, vector& volumeList) const { return getStructureLists(ALONG_COLUMN, surfaceList, volumeList); } bool CiftiXMLOld::getStructureListsForRows(vector& surfaceList, vector& volumeList) const { return getStructureLists(ALONG_ROW, surfaceList, volumeList); } int CiftiXMLOld::getNumberOfBrainModels(const int& direction) const { CaretAssertVectorIndex(m_dimToMapLookup, direction); int myMapIndex = m_dimToMapLookup[direction]; if (myMapIndex == -1 || m_root.m_matrices.size() == 0) { return -1; } CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, myMapIndex); const CiftiMatrixIndicesMapElement* myMap = &(m_root.m_matrices[0].m_matrixIndicesMap[myMapIndex]); if (myMap->m_indicesMapToDataType != CIFTI_INDEX_TYPE_BRAIN_MODELS) { return -1; } return (int)myMap->m_brainModels.size();//reuse of type and structure combinations not allowed, so this is limited to number of structure enum values times number of model types (2) } CiftiBrainModelInfo CiftiXMLOld::getBrainModelInfo(const int& direction, const int& whichModel) const { CiftiBrainModelInfo ret; CaretAssertVectorIndex(m_dimToMapLookup, direction); int myMapIndex = m_dimToMapLookup[direction]; if (myMapIndex == -1 || m_root.m_matrices.size() == 0) { return ret; } CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, myMapIndex); const CiftiMatrixIndicesMapElement* myMap = &(m_root.m_matrices[0].m_matrixIndicesMap[myMapIndex]); if (myMap->m_indicesMapToDataType != CIFTI_INDEX_TYPE_BRAIN_MODELS) { return ret; } CaretAssertVectorIndex(myMap->m_brainModels, whichModel); ret.m_type = myMap->m_brainModels[whichModel].m_modelType; ret.m_structure = myMap->m_brainModels[whichModel].m_brainStructure; return ret; } void CiftiXMLOld::rootChanged() { m_dimToMapLookup.clear();//first, invalidate everything m_dimToMapLookup.resize(2, -1);//assume matrix is 2D, for backwards compatibility with Row/Column functions if (m_root.m_matrices.size() == 0) { return;//it shouldn't crash if it has no matrix, so return instead of throw } CiftiMatrixElement& myMatrix = m_root.m_matrices[0];//assume only one matrix int numMaps = (int)myMatrix.m_matrixIndicesMap.size(); for (int i = 0; i < numMaps; ++i) { CiftiMatrixIndicesMapElement& myMap = myMatrix.m_matrixIndicesMap[i]; int numDimensions = (int)myMap.m_appliesToMatrixDimension.size(); for (int j = 0; j < numDimensions; ++j) { if (myMap.m_appliesToMatrixDimension[j] < 0) throw DataFileException("negative value in m_appliesToMatrixDimension"); while (m_dimToMapLookup.size() <= (size_t)myMap.m_appliesToMatrixDimension[j]) { m_dimToMapLookup.push_back(-1); } m_dimToMapLookup[myMap.m_appliesToMatrixDimension[j]] = i; myMap.setupLookup(); } } } int64_t CiftiXMLOld::getColumnSurfaceNumberOfNodes(const StructureEnum::Enum& structure) const { return getSurfaceNumberOfNodes(ALONG_COLUMN, structure); } int64_t CiftiXMLOld::getRowSurfaceNumberOfNodes(const StructureEnum::Enum& structure) const { return getSurfaceNumberOfNodes(ALONG_ROW, structure); } int64_t CiftiXMLOld::getSurfaceNumberOfNodes(const int& direction, const StructureEnum::Enum& structure) const { if (direction < 0 || direction >= (int)m_dimToMapLookup.size()) return -1; if (m_root.m_matrices.size() == 0) return -1; CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, m_dimToMapLookup[direction]); const CiftiMatrixIndicesMapElement& myMap = m_root.m_matrices[0].m_matrixIndicesMap[m_dimToMapLookup[direction]]; IndicesMapToDataType myType = myMap.m_indicesMapToDataType; if (myType == CIFTI_INDEX_TYPE_BRAIN_MODELS) { const CiftiBrainModelElement* myModel = findSurfaceModel(m_dimToMapLookup[direction], structure); if (myModel == NULL) return -1; return myModel->m_surfaceNumberOfNodes; } else if (myType == CIFTI_INDEX_TYPE_PARCELS) { int numSurfs = (int)myMap.m_parcelSurfaces.size(); for (int i = 0; i < numSurfs; ++i) { if (myMap.m_parcelSurfaces[i].m_structure == structure) { return myMap.m_parcelSurfaces[i].m_numNodes; } } } return -1; } int64_t CiftiXMLOld::getVolumeIndex(const float* xyz, const int& myMapIndex) const { if (m_root.m_matrices.size() == 0) { return -1; } if (m_root.m_matrices[0].m_volume.size() == 0) { return -1; } const CiftiVolumeElement& myVol = m_root.m_matrices[0].m_volume[0]; if (myVol.m_transformationMatrixVoxelIndicesIJKtoXYZ.size() == 0) { return -1; } const TransformationMatrixVoxelIndicesIJKtoXYZElement& myTrans = myVol.m_transformationMatrixVoxelIndicesIJKtoXYZ[0];//oh the humanity FloatMatrix myMatrix = FloatMatrix::zeros(4, 4); for (int i = 0; i < 3; ++i)//NEVER trust the fourth row of input, NEVER! { for (int j = 0; j < 4; ++j) { myMatrix[i][j] = myTrans.m_transform[i * 4 + j]; } } switch (myTrans.m_unitsXYZ) { case NIFTI_UNITS_MM: break; case NIFTI_UNITS_METER: myMatrix *= 1000.0f; break; case NIFTI_UNITS_MICRON: myMatrix *= 0.001f; break; default: return -1; }; myMatrix[3][3] = 1.0f;//i COULD do this by making a fake volume file, but that seems kinda hacky FloatMatrix toIndexSpace = myMatrix.inverse();//invert to convert the other direction FloatMatrix myCoord = FloatMatrix::zeros(4, 1);//column vector myCoord[0][0] = xyz[0]; myCoord[1][0] = xyz[1]; myCoord[2][0] = xyz[2]; myCoord[3][0] = 1.0f; FloatMatrix myIndices = toIndexSpace * myCoord;//matrix multiply int64_t ijk[3]; ijk[0] = (int64_t)floor(myIndices[0][0] + 0.5f); ijk[1] = (int64_t)floor(myIndices[1][0] + 0.5f); ijk[2] = (int64_t)floor(myIndices[2][0] + 0.5f); return getVolumeIndex(ijk, myMapIndex); } int64_t CiftiXMLOld::getColumnIndexForVoxelCoordinate(const float* xyz) const { return getVolumeIndex(xyz, m_dimToMapLookup[0]); } int64_t CiftiXMLOld::getRowIndexForVoxelCoordinate(const float* xyz) const { return getVolumeIndex(xyz, m_dimToMapLookup[1]); } int64_t CiftiXMLOld::getTimestepIndex(const float& seconds, const int& myMapIndex) const { if (myMapIndex == -1 || m_root.m_matrices.size() == 0) { return false; } CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, myMapIndex); const CiftiMatrixIndicesMapElement* myMap = &(m_root.m_matrices[0].m_matrixIndicesMap[myMapIndex]); float myStep; if (!getTimestep(myStep, myMapIndex)) { return -1; } float rawIndex = seconds / myStep; int64_t ret = (int64_t)floor(rawIndex + 0.5f); if (ret < 0 || ret >= myMap->m_numTimeSteps) return -1;//NOTE: should this have a different error value if it is after the end of the timeseries return ret; } int64_t CiftiXMLOld::getColumnIndexForTimepoint(const float& seconds) const { return getTimestepIndex(seconds, m_dimToMapLookup[0]); } int64_t CiftiXMLOld::getRowIndexForTimepoint(const float& seconds) const { return getTimestepIndex(seconds, m_dimToMapLookup[1]); } bool CiftiXMLOld::getTimestep(float& seconds, const int& myMapIndex) const { if (myMapIndex == -1 || m_root.m_matrices.size() == 0) { return false; } CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, myMapIndex); const CiftiMatrixIndicesMapElement* myMap = &(m_root.m_matrices[0].m_matrixIndicesMap[myMapIndex]); if (myMap->m_indicesMapToDataType != CIFTI_INDEX_TYPE_TIME_POINTS) { return false; } switch (myMap->m_timeStepUnits) { case NIFTI_UNITS_SEC: seconds = myMap->m_timeStep; break; case NIFTI_UNITS_MSEC: seconds = myMap->m_timeStep * 0.001f; break; case NIFTI_UNITS_USEC: seconds = myMap->m_timeStep * 0.000001f; break; default: return false; }; return true; } bool CiftiXMLOld::getColumnTimestep(float& seconds) const { return getTimestep(seconds, m_dimToMapLookup[1]); } bool CiftiXMLOld::getRowTimestep(float& seconds) const { return getTimestep(seconds, m_dimToMapLookup[0]); } bool CiftiXMLOld::getTimestart(float& seconds, const int& myMapIndex) const { if (myMapIndex == -1 || m_root.m_matrices.size() == 0) { return false; } CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, myMapIndex); const CiftiMatrixIndicesMapElement* myMap = &(m_root.m_matrices[0].m_matrixIndicesMap[myMapIndex]); if (myMap->m_indicesMapToDataType != CIFTI_INDEX_TYPE_TIME_POINTS || myMap->m_hasTimeStart == false) { return false; } switch (myMap->m_timeStepUnits) { case NIFTI_UNITS_SEC: seconds = myMap->m_timeStart; break; case NIFTI_UNITS_MSEC: seconds = myMap->m_timeStart * 0.001f; break; case NIFTI_UNITS_USEC: seconds = myMap->m_timeStart * 0.000001f; break; default: return false; }; return true; } bool CiftiXMLOld::getColumnTimestart(float& seconds) const { return getTimestart(seconds, m_dimToMapLookup[1]); } bool CiftiXMLOld::getRowTimestart(float& seconds) const { return getTimestart(seconds, m_dimToMapLookup[0]); } bool CiftiXMLOld::getColumnNumberOfTimepoints(int& numTimepoints) const { if (m_dimToMapLookup[1] == -1 || m_root.m_matrices.size() == 0) { return false; } CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, m_dimToMapLookup[1]); const CiftiMatrixIndicesMapElement* myMap = &(m_root.m_matrices[0].m_matrixIndicesMap[m_dimToMapLookup[1]]); if (myMap->m_indicesMapToDataType != CIFTI_INDEX_TYPE_TIME_POINTS) { return false; } numTimepoints = myMap->m_numTimeSteps; return true; } bool CiftiXMLOld::getRowNumberOfTimepoints(int& numTimepoints) const { if (m_dimToMapLookup[0] == -1 || m_root.m_matrices.size() == 0) { return false; } CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, m_dimToMapLookup[0]); const CiftiMatrixIndicesMapElement* myMap = &(m_root.m_matrices[0].m_matrixIndicesMap[m_dimToMapLookup[0]]); if (myMap->m_indicesMapToDataType != CIFTI_INDEX_TYPE_TIME_POINTS) { return false; } numTimepoints = myMap->m_numTimeSteps; return true; } bool CiftiXMLOld::getParcelsForColumns(vector& parcelsOut) const { return getParcels(ALONG_COLUMN, parcelsOut); } bool CiftiXMLOld::getParcelsForRows(vector& parcelsOut) const { return getParcels(ALONG_ROW, parcelsOut); } bool CiftiXMLOld::getParcels(const int& direction, vector< CiftiParcelElement >& parcelsOut) const { CaretAssertVectorIndex(m_dimToMapLookup, direction); if (m_dimToMapLookup[direction] == -1 || m_root.m_matrices.size() == 0) { parcelsOut.clear(); return false; } CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, m_dimToMapLookup[direction]); const CiftiMatrixIndicesMapElement* myMap = &(m_root.m_matrices[0].m_matrixIndicesMap[m_dimToMapLookup[direction]]); if (myMap->m_indicesMapToDataType != CIFTI_INDEX_TYPE_PARCELS) { parcelsOut.clear(); return false; } parcelsOut = myMap->m_parcels; return true; } bool CiftiXMLOld::getParcelSurfaceStructures(const int& direction, vector& structuresOut) const { structuresOut.clear(); if (direction < 0 || direction >= (int)m_dimToMapLookup.size()) return false; if (m_dimToMapLookup[direction] == -1 || m_root.m_matrices.size() == 0) { return false; } CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, m_dimToMapLookup[direction]); const CiftiMatrixIndicesMapElement& myMap = m_root.m_matrices[0].m_matrixIndicesMap[m_dimToMapLookup[direction]]; if (myMap.m_indicesMapToDataType != CIFTI_INDEX_TYPE_PARCELS) return false; for (int i = 0; i < (int)myMap.m_parcelSurfaces.size(); ++i) { structuresOut.push_back(myMap.m_parcelSurfaces[i].m_structure); } return true; } int64_t CiftiXMLOld::getParcelForNode(const int64_t& node, const StructureEnum::Enum& structure, const int& myMapIndex) const { if (node < 0 || myMapIndex == -1 || m_root.m_matrices.size() == 0) { return -1; } CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, myMapIndex); const CiftiMatrixIndicesMapElement& myMap = m_root.m_matrices[0].m_matrixIndicesMap[myMapIndex]; if (myMap.m_indicesMapToDataType != CIFTI_INDEX_TYPE_PARCELS) { return -1; } for (int i = 0; i < (int)myMap.m_parcelSurfaces.size(); ++i) { if (myMap.m_parcelSurfaces[i].m_structure == structure) { if (node < myMap.m_parcelSurfaces[i].m_numNodes) { return myMap.m_parcelSurfaces[i].m_lookup[node]; } else { return -1; } } } return -1; } int64_t CiftiXMLOld::getColumnParcelForNode(const int64_t& node, const StructureEnum::Enum& structure) const { return getParcelForNode(node, structure, m_dimToMapLookup[1]); } int64_t CiftiXMLOld::getRowParcelForNode(const int64_t& node, const caret::StructureEnum::Enum& structure) const { return getParcelForNode(node, structure, m_dimToMapLookup[0]); } int64_t CiftiXMLOld::getParcelForVoxel(const int64_t* ijk, const int& myMapIndex) const { if (ijk[0] < 0 || ijk[1] < 0 || ijk[2] < 0 || myMapIndex == -1 || m_root.m_matrices.size() == 0) { return -1; } CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, myMapIndex); const CiftiMatrixIndicesMapElement& myMap = m_root.m_matrices[0].m_matrixIndicesMap[myMapIndex]; if (myMap.m_indicesMapToDataType != CIFTI_INDEX_TYPE_PARCELS) { return -1; } const int64_t* test = myMap.m_voxelToIndexLookup.find(ijk); if (test == NULL) return -1; return *test; } int64_t CiftiXMLOld::getColumnParcelForVoxel(const int64_t* ijk) const { return getParcelForVoxel(ijk, m_dimToMapLookup[1]); } int64_t CiftiXMLOld::getRowParcelForVoxel(const int64_t* ijk) const { return getParcelForVoxel(ijk, m_dimToMapLookup[0]); } bool CiftiXMLOld::setColumnNumberOfTimepoints(const int& numTimepoints) { if (m_dimToMapLookup[1] == -1 || m_root.m_matrices.size() == 0) { return false; } CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, m_dimToMapLookup[1]); CiftiMatrixIndicesMapElement* myMap = &(m_root.m_matrices[0].m_matrixIndicesMap[m_dimToMapLookup[1]]); if (myMap->m_indicesMapToDataType != CIFTI_INDEX_TYPE_TIME_POINTS) { return false; } myMap->m_numTimeSteps = numTimepoints; return true; } bool CiftiXMLOld::setRowNumberOfTimepoints(const int& numTimepoints) { if (m_dimToMapLookup[0] == -1 || m_root.m_matrices.size() == 0) { return false; } CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, m_dimToMapLookup[0]); CiftiMatrixIndicesMapElement* myMap = &(m_root.m_matrices[0].m_matrixIndicesMap[m_dimToMapLookup[0]]); if (myMap->m_indicesMapToDataType != CIFTI_INDEX_TYPE_TIME_POINTS) { return false; } myMap->m_numTimeSteps = numTimepoints; return true; } bool CiftiXMLOld::setTimestep(const float& seconds, const int& myMapIndex) { if (myMapIndex == -1 || m_root.m_matrices.size() == 0) { return false; } CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, myMapIndex); CiftiMatrixIndicesMapElement* myMap = &(m_root.m_matrices[0].m_matrixIndicesMap[myMapIndex]); if (myMap->m_indicesMapToDataType != CIFTI_INDEX_TYPE_TIME_POINTS) { return false; } float temp = 1.0f; getTimestart(temp, myMapIndex);//convert to seconds myMap->m_timeStart = temp; myMap->m_timeStepUnits = NIFTI_UNITS_SEC; myMap->m_timeStep = seconds; return true; } bool CiftiXMLOld::setColumnTimestep(const float& seconds) { return setTimestep(seconds, m_dimToMapLookup[1]); } bool CiftiXMLOld::setRowTimestep(const float& seconds) { return setTimestep(seconds, m_dimToMapLookup[0]); } bool CiftiXMLOld::setTimestart(const float& seconds, const int& myMapIndex) { if (myMapIndex == -1 || m_root.m_matrices.size() == 0) { return false; } CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, myMapIndex); CiftiMatrixIndicesMapElement* myMap = &(m_root.m_matrices[0].m_matrixIndicesMap[myMapIndex]); if (myMap->m_indicesMapToDataType != CIFTI_INDEX_TYPE_TIME_POINTS) { return false; } float temp = 1.0f; getTimestep(temp, myMapIndex);//convert timestep to seconds myMap->m_timeStep = temp; myMap->m_timeStepUnits = NIFTI_UNITS_SEC; myMap->m_timeStart = seconds; myMap->m_hasTimeStart = true; return true; } bool CiftiXMLOld::setColumnTimestart(const float& seconds) { return setTimestart(seconds, m_dimToMapLookup[1]); } bool CiftiXMLOld::setRowTimestart(const float& seconds) { return setTimestart(seconds, m_dimToMapLookup[0]); } bool CiftiXMLOld::getVolumeAttributesForPlumb(VolumeSpace::OrientTypes orientOut[3], int64_t dimensionsOut[3], float originOut[3], float spacingOut[3]) const { VolumeSpace mySpace; if (!getVolumeSpace(mySpace)) return false; const int64_t* myDims = mySpace.getDims(); dimensionsOut[0] = myDims[0]; dimensionsOut[1] = myDims[1]; dimensionsOut[2] = myDims[2]; mySpace.getOrientAndSpacingForPlumb(orientOut, spacingOut, originOut); return true; } bool CiftiXMLOld::getVolumeDimsAndSForm(int64_t dimsOut[3], vector >& sformOut) const { if (m_root.m_matrices.size() == 0) { return false; } if (m_root.m_matrices[0].m_volume.size() == 0) { return false; } const CiftiVolumeElement& myVol = m_root.m_matrices[0].m_volume[0]; if (myVol.m_transformationMatrixVoxelIndicesIJKtoXYZ.size() == 0) { return false; } const TransformationMatrixVoxelIndicesIJKtoXYZElement& myTrans = myVol.m_transformationMatrixVoxelIndicesIJKtoXYZ[0];//oh the humanity FloatMatrix myMatrix = FloatMatrix::zeros(3, 4);//no fourth row for (int i = 0; i < 3; ++i) { for (int j = 0; j < 4; ++j) { myMatrix[i][j] = myTrans.m_transform[i * 4 + j]; } } switch (myTrans.m_unitsXYZ) { case NIFTI_UNITS_MM: break; case NIFTI_UNITS_METER: myMatrix *= 1000.0f; break; case NIFTI_UNITS_MICRON: myMatrix *= 0.001f; break; default: return false; }; sformOut = myMatrix.getMatrix(); dimsOut[0] = myVol.m_volumeDimensions[0]; dimsOut[1] = myVol.m_volumeDimensions[1]; dimsOut[2] = myVol.m_volumeDimensions[2]; return true; } void CiftiXMLOld::setVolumeDimsAndSForm(const int64_t dims[3], const vector >& sform) { CaretAssert(sform.size() == 3 || sform.size() == 4); if (hasVolumeData(ALONG_COLUMN) || hasVolumeData(ALONG_ROW)) { VolumeSpace tempSpace; if (getVolumeSpace(tempSpace))//if it fails to get a volume space when it has volume data...allow it to set it, I guess { if (!tempSpace.matches(VolumeSpace(dims, sform))) { throw DataFileException("cannot change the volume space of cifti xml that already has volume mapping(s)"); } return; } } if (m_root.m_matrices.size() == 0) { m_root.m_matrices.resize(1); } if (m_root.m_matrices[0].m_volume.size() == 0) { m_root.m_matrices[0].m_volume.resize(1); } CiftiVolumeElement& myVol = m_root.m_matrices[0].m_volume[0]; if (myVol.m_transformationMatrixVoxelIndicesIJKtoXYZ.size() == 0) { myVol.m_transformationMatrixVoxelIndicesIJKtoXYZ.resize(1); } TransformationMatrixVoxelIndicesIJKtoXYZElement& myTrans = myVol.m_transformationMatrixVoxelIndicesIJKtoXYZ[0];//oh the humanity for (int i = 0; i < 3; ++i) { CaretAssert(sform[i].size() == 4); for (int j = 0; j < 4; ++j) { myTrans.m_transform[i * 4 + j] = sform[i][j]; } } myTrans.m_transform[12] = 0.0f;//force last row to be set to 0 0 0 1 internally for sanity, even though we don't use it myTrans.m_transform[13] = 0.0f; myTrans.m_transform[14] = 0.0f; myTrans.m_transform[15] = 1.0f; myTrans.m_unitsXYZ = NIFTI_UNITS_MM; myVol.m_volumeDimensions[0] = dims[0]; myVol.m_volumeDimensions[1] = dims[1]; myVol.m_volumeDimensions[2] = dims[2]; } bool CiftiXMLOld::getVolumeSpace(VolumeSpace& volSpaceOut) const { if (m_root.m_matrices.size() == 0) { return false; } if (m_root.m_matrices[0].m_volume.size() == 0) { return false; } const CiftiVolumeElement& myVol = m_root.m_matrices[0].m_volume[0]; if (myVol.m_transformationMatrixVoxelIndicesIJKtoXYZ.size() == 0) { return false; } const TransformationMatrixVoxelIndicesIJKtoXYZElement& myTrans = myVol.m_transformationMatrixVoxelIndicesIJKtoXYZ[0];//oh the humanity FloatMatrix myMatrix = FloatMatrix::zeros(3, 4);//no fourth row for (int i = 0; i < 3; ++i) { for (int j = 0; j < 4; ++j) { myMatrix[i][j] = myTrans.m_transform[i * 4 + j]; } } switch (myTrans.m_unitsXYZ) { case NIFTI_UNITS_MM: break; case NIFTI_UNITS_METER: myMatrix *= 1000.0f; break; case NIFTI_UNITS_MICRON: myMatrix *= 0.001f; break; default: return false; }; int64_t dims[3] = { myVol.m_volumeDimensions[0], myVol.m_volumeDimensions[1], myVol.m_volumeDimensions[2] }; volSpaceOut.setSpace(dims, myMatrix.getMatrix()); return true; } AString CiftiXMLOld::getMapName(const int& direction, const int64_t& index) const { if (m_dimToMapLookup[direction] == -1 || m_root.m_matrices.size() == 0) { return ""; } CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, m_dimToMapLookup[direction]); const CiftiMatrixIndicesMapElement& myMap = m_root.m_matrices[0].m_matrixIndicesMap[m_dimToMapLookup[direction]]; if (myMap.m_indicesMapToDataType != CIFTI_INDEX_TYPE_SCALARS && myMap.m_indicesMapToDataType != CIFTI_INDEX_TYPE_LABELS) { return ""; } CaretAssertVectorIndex(myMap.m_namedMaps, index); return myMap.m_namedMaps[index].m_mapName; } int64_t CiftiXMLOld::getMapIndexFromNameOrNumber(const int& direction, const AString& numberOrName) const { if (m_dimToMapLookup[direction] == -1 || m_root.m_matrices.size() == 0) { return -1; } CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, m_dimToMapLookup[direction]); const CiftiMatrixIndicesMapElement& myMap = m_root.m_matrices[0].m_matrixIndicesMap[m_dimToMapLookup[direction]]; bool ok = false; int32_t ret = numberOrName.toInt(&ok) - 1;//compensate for 1-indexing that command line parsing uses if (ok)//always work for integers, even when it is something like brain models or parcels, code that cares can check the mapping type { if (ret < 0 || ret >= getDimensionLength(direction)) { ret = -1; } } else {//DO NOT search by name if the string was parsed as an integer correctly, or some idiot who names their maps as integers will get confused //when getting map "12" out of a file after the file expands to more than 12 elements suddenly does something different if (myMap.m_indicesMapToDataType != CIFTI_INDEX_TYPE_SCALARS && myMap.m_indicesMapToDataType != CIFTI_INDEX_TYPE_LABELS) { return -1;//if we don't have map names to look at, return early rather than repeatedly trying to match against "" (which would be incorrect anyway) } int64_t numMaps = getDimensionLength(direction); ret = -1; for (int64_t i = 0; i < numMaps; ++i) { if (getMapName(direction, i) == numberOrName) { ret = i; break; } } } return ret; } AString CiftiXMLOld::getMapNameForColumnIndex(const int64_t& index) const { return getMapName(ALONG_COLUMN, index); } AString CiftiXMLOld::getMapNameForRowIndex(const int64_t& index) const { return getMapName(ALONG_ROW, index); } bool CiftiXMLOld::setMapNameForIndex(const int& direction, const int64_t& index, const AString& name) const { if (direction < 0 || direction >= (int)m_dimToMapLookup.size()) { return false; } int myMapIndex = m_dimToMapLookup[direction]; if (myMapIndex == -1 || m_root.m_matrices.size() == 0) { return false; } const CiftiMatrixIndicesMapElement& myMap = m_root.m_matrices[0].m_matrixIndicesMap[myMapIndex]; if (myMap.m_indicesMapToDataType != CIFTI_INDEX_TYPE_SCALARS && myMap.m_indicesMapToDataType != CIFTI_INDEX_TYPE_LABELS) { return false; } CaretAssertVectorIndex(myMap.m_namedMaps, index); myMap.m_namedMaps[index].m_mapName = name; return true; } bool CiftiXMLOld::setMapNameForColumnIndex(const int64_t& index, const AString& name) const { return setMapNameForIndex(ALONG_COLUMN, index, name); } bool CiftiXMLOld::setMapNameForRowIndex(const int64_t& index, const AString& name) const { return setMapNameForIndex(ALONG_ROW, index, name); } GiftiLabelTable* CiftiXMLOld::getMapLabelTable(const int& direction, const int64_t& index) const { if (m_dimToMapLookup[direction] == -1 || m_root.m_matrices.size() == 0) { return NULL; } CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, m_dimToMapLookup[direction]); const CiftiMatrixIndicesMapElement& myMap = m_root.m_matrices[0].m_matrixIndicesMap[m_dimToMapLookup[direction]]; if (myMap.m_indicesMapToDataType != CIFTI_INDEX_TYPE_LABELS) { return NULL; } CaretAssertVectorIndex(myMap.m_namedMaps, index); return myMap.m_namedMaps[index].m_labelTable; } GiftiLabelTable* CiftiXMLOld::getLabelTableForColumnIndex(const int64_t& index) const { return getMapLabelTable(ALONG_COLUMN, index); } GiftiLabelTable* CiftiXMLOld::getLabelTableForRowIndex(const int64_t& index) const { return getMapLabelTable(ALONG_ROW, index); } bool CiftiXMLOld::setLabelTable(const int64_t& index, const GiftiLabelTable& labelTable, const int& myMapIndex) { if (myMapIndex == -1 || m_root.m_matrices.size() == 0) { return false; } CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, myMapIndex); CiftiMatrixIndicesMapElement& myMap = m_root.m_matrices[0].m_matrixIndicesMap[myMapIndex]; if (myMap.m_indicesMapToDataType != CIFTI_INDEX_TYPE_LABELS) { return false; } CaretAssertVectorIndex(myMap.m_namedMaps, index); if (myMap.m_namedMaps[index].m_labelTable == NULL)//should never happen, but just in case { myMap.m_namedMaps[index].m_labelTable.grabNew(new GiftiLabelTable(labelTable)); } else { *(myMap.m_namedMaps[index].m_labelTable) = labelTable; } return true; } bool CiftiXMLOld::setLabelTableForColumnIndex(const int64_t& index, const GiftiLabelTable& labelTable) { return setLabelTable(index, labelTable, m_dimToMapLookup[1]); } bool CiftiXMLOld::setLabelTableForRowIndex(const int64_t& index, const GiftiLabelTable& labelTable) { return setLabelTable(index, labelTable, m_dimToMapLookup[0]); } bool CiftiXMLOld::hasVolumeData(const int& direction) const { if (direction < 0 || direction >= (int)m_dimToMapLookup.size()) { return false; } int myMapIndex = m_dimToMapLookup[direction]; if (myMapIndex == -1 || m_root.m_matrices.size() == 0) { return false; } CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, myMapIndex); const CiftiMatrixIndicesMapElement* myMap = &(m_root.m_matrices[0].m_matrixIndicesMap[myMapIndex]); if (m_root.m_matrices[0].m_volume.size() == 0) { return false; } if (myMap == NULL) { return false; } if (myMap->m_indicesMapToDataType == CIFTI_INDEX_TYPE_BRAIN_MODELS) { const CiftiVolumeElement& myVol = m_root.m_matrices[0].m_volume[0]; if (myVol.m_transformationMatrixVoxelIndicesIJKtoXYZ.size() == 0) { return false; } for (int64_t i = 0; i < (int64_t)myMap->m_brainModels.size(); ++i) { if (myMap->m_brainModels[i].m_modelType == CIFTI_MODEL_TYPE_VOXELS) { return true; } } } else if (myMap->m_indicesMapToDataType == CIFTI_INDEX_TYPE_PARCELS) { for (int64_t i = 0; i < (int64_t)myMap->m_parcels.size(); ++i) { if (myMap->m_parcels[i].m_voxelIndicesIJK.size() != 0) { return true; } }//TSC: I now think it should say true for parcels as long as there are voxels, useful for checking whether the volume XML element is needed } return false; } bool CiftiXMLOld::hasRowVolumeData() const { return hasVolumeData(ALONG_ROW); } bool CiftiXMLOld::hasColumnVolumeData() const { return hasVolumeData(ALONG_COLUMN); } bool CiftiXMLOld::hasColumnSurfaceData(const StructureEnum::Enum& structure) const { return hasSurfaceData(ALONG_COLUMN, structure); } bool CiftiXMLOld::hasRowSurfaceData(const StructureEnum::Enum& structure) const { return hasSurfaceData(ALONG_ROW, structure); } bool CiftiXMLOld::hasSurfaceData(const int& direction, const StructureEnum::Enum& structure) const { if (direction < 0 || direction >= (int)m_dimToMapLookup.size()) { return false; } int myMapIndex = m_dimToMapLookup[direction]; if (myMapIndex == -1 || m_root.m_matrices.size() == 0) { return false; } CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, myMapIndex); const CiftiMatrixIndicesMapElement* myMap = &(m_root.m_matrices[0].m_matrixIndicesMap[myMapIndex]); if (myMap->m_indicesMapToDataType == CIFTI_INDEX_TYPE_BRAIN_MODELS) { return (findSurfaceModel(m_dimToMapLookup[direction], structure) != NULL); } else if (myMap->m_indicesMapToDataType == CIFTI_INDEX_TYPE_PARCELS) { bool found = false; for (int i = 0; i < (int)myMap->m_parcelSurfaces.size(); ++i) { if (myMap->m_parcelSurfaces[i].m_structure == structure) { found = true;//TODO: figure out if we should just return true here } } if (!found) return false; for (int64_t i = 0; i < (int64_t)myMap->m_parcels.size(); ++i) { const CiftiParcelElement& myParcel = myMap->m_parcels[i]; for (int j = 0; j < (int)myParcel.m_nodeElements.size(); ++j) { const CiftiParcelNodesElement& myNodes = myParcel.m_nodeElements[j]; if (myNodes.m_structure == structure && myNodes.m_nodes.size() != 0) return true;//instead of checking that at least one parcel actually uses it } } return false; } else { return false; } } bool CiftiXMLOld::addSurfaceModelToColumns(const int& numberOfNodes, const StructureEnum::Enum& structure, const float* roi) { return addSurfaceModel(ALONG_COLUMN, numberOfNodes, structure, roi); } bool CiftiXMLOld::addSurfaceModelToRows(const int& numberOfNodes, const StructureEnum::Enum& structure, const float* roi) { return addSurfaceModel(ALONG_ROW, numberOfNodes, structure, roi); } bool CiftiXMLOld::addSurfaceModel(const int& direction, const int& numberOfNodes, const StructureEnum::Enum& structure, const float* roi) { CaretAssertVectorIndex(m_dimToMapLookup, direction); separateMaps(); if (m_dimToMapLookup[direction] == -1 || m_root.m_matrices.size() == 0) { return false; } CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, m_dimToMapLookup[direction]); CiftiMatrixIndicesMapElement* myMap = &(m_root.m_matrices[0].m_matrixIndicesMap[m_dimToMapLookup[direction]]); if (myMap->m_indicesMapToDataType != CIFTI_INDEX_TYPE_BRAIN_MODELS) return false; if (findSurfaceModel(m_dimToMapLookup[direction], structure) != NULL) return false; CiftiBrainModelElement tempModel; tempModel.m_brainStructure = structure; tempModel.m_modelType = CIFTI_MODEL_TYPE_SURFACE; tempModel.m_indexOffset = getNewRangeStart(m_dimToMapLookup[direction]); tempModel.m_surfaceNumberOfNodes = numberOfNodes; if (roi == NULL) { tempModel.m_indexCount = numberOfNodes; } else { tempModel.m_indexCount = 0; tempModel.m_nodeIndices.reserve(numberOfNodes); bool allNodes = true; for (int i = 0; i < numberOfNodes; ++i) { if (roi[i] > 0.0f) { tempModel.m_nodeIndices.push_back(i); } else { allNodes = false; } } if (allNodes) { tempModel.m_nodeIndices.clear(); tempModel.m_indexCount = numberOfNodes; } else { tempModel.m_indexCount = (unsigned long long)tempModel.m_nodeIndices.size(); } } myMap->m_brainModels.push_back(tempModel); myMap->m_brainModels.back().setupLookup(*myMap); return true; } bool CiftiXMLOld::addSurfaceModelToColumns(const int& numberOfNodes, const StructureEnum::Enum& structure, const vector& nodeList) { return addSurfaceModel(ALONG_COLUMN, numberOfNodes, structure, nodeList); } bool CiftiXMLOld::addSurfaceModelToRows(const int& numberOfNodes, const StructureEnum::Enum& structure, const vector& nodeList) { return addSurfaceModel(ALONG_ROW, numberOfNodes, structure, nodeList); } bool CiftiXMLOld::addSurfaceModel(const int& direction, const int& numberOfNodes, const StructureEnum::Enum& structure, const vector& nodeList) { CaretAssertVectorIndex(m_dimToMapLookup, direction); separateMaps(); if (m_dimToMapLookup[direction] == -1 || m_root.m_matrices.size() == 0) { return false; } CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, m_dimToMapLookup[direction]); CaretAssertMessage(checkSurfaceNodes(nodeList, numberOfNodes), "node list has node numbers that don't exist in the surface"); CiftiMatrixIndicesMapElement* myMap = &(m_root.m_matrices[0].m_matrixIndicesMap[m_dimToMapLookup[direction]]);//call the check function inside an assert so it never does the check in release builds if (myMap->m_indicesMapToDataType != CIFTI_INDEX_TYPE_BRAIN_MODELS) return false; if (findSurfaceModel(m_dimToMapLookup[direction], structure) != NULL) return false; CiftiBrainModelElement tempModel; tempModel.m_brainStructure = structure; tempModel.m_modelType = CIFTI_MODEL_TYPE_SURFACE; tempModel.m_indexOffset = getNewRangeStart(m_dimToMapLookup[direction]); tempModel.m_surfaceNumberOfNodes = numberOfNodes; tempModel.m_indexCount = (int64_t)nodeList.size(); if ((int)nodeList.size() == numberOfNodes) { bool sequential = true; for (int i = 0; i < numberOfNodes; ++i) { if (nodeList[i] != i) { sequential = false; break; } } if (!sequential) { tempModel.m_nodeIndices = nodeList; } } else { tempModel.m_nodeIndices = nodeList; } myMap->m_brainModels.push_back(tempModel); myMap->m_brainModels.back().setupLookup(*myMap); return true; } bool CiftiXMLOld::checkSurfaceNodes(const vector& nodeList, const int& numberOfNodes) const { int listSize = (int)nodeList.size(); for (int i = 0; i < listSize; ++i) { if (nodeList[i] < 0 || nodeList[i] >= numberOfNodes) return false; } return true; } bool CiftiXMLOld::addVolumeModel(const int& direction, const vector& ijkList, const StructureEnum::Enum& structure) { CaretAssertVectorIndex(m_dimToMapLookup, direction); separateMaps(); if (m_dimToMapLookup[direction] == -1 || m_root.m_matrices.size() == 0) { return false; } CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, m_dimToMapLookup[direction]); CiftiMatrixIndicesMapElement* myMap = &(m_root.m_matrices[0].m_matrixIndicesMap[m_dimToMapLookup[direction]]); if (myMap->m_indicesMapToDataType != CIFTI_INDEX_TYPE_BRAIN_MODELS) return false; if (findVolumeModel(m_dimToMapLookup[direction], structure) != NULL) return false; CaretAssertMessage(checkVolumeIndices(ijkList), "volume voxel list doesn't match cifti volume space, do setVolumeDimsAndSForm first"); CiftiBrainModelElement tempModel;//call the check function inside an assert so it never does the check in release builds tempModel.m_brainStructure = structure; tempModel.m_modelType = CIFTI_MODEL_TYPE_VOXELS; tempModel.m_indexOffset = getNewRangeStart(m_dimToMapLookup[direction]); tempModel.m_indexCount = ijkList.size() / 3; tempModel.m_voxelIndicesIJK = ijkList; myMap->m_brainModels.push_back(tempModel); return true; } bool CiftiXMLOld::addVolumeModelToColumns(const vector& ijkList, const StructureEnum::Enum& structure) { return addVolumeModel(ALONG_COLUMN, ijkList, structure); } bool CiftiXMLOld::addVolumeModelToRows(const vector& ijkList, const StructureEnum::Enum& structure) { return addVolumeModel(ALONG_ROW, ijkList, structure); } bool CiftiXMLOld::addParcelSurfaceToColumns(const int& numberOfNodes, const StructureEnum::Enum& structure) { return addParcelSurface(ALONG_COLUMN, numberOfNodes, structure); } bool CiftiXMLOld::addParcelSurfaceToRows(const int& numberOfNodes, const StructureEnum::Enum& structure) { return addParcelSurface(ALONG_ROW, numberOfNodes, structure); } bool CiftiXMLOld::addParcelSurface(const int& direction, const int& numberOfNodes, const caret::StructureEnum::Enum& structure) { if (direction < 0 || direction >= (int)m_dimToMapLookup.size()) { return false; } separateMaps(); if (numberOfNodes < 1 || m_dimToMapLookup[direction] == -1 || m_root.m_matrices.size() == 0) { return false; } CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, m_dimToMapLookup[direction]); CiftiMatrixIndicesMapElement& myMap = m_root.m_matrices[0].m_matrixIndicesMap[m_dimToMapLookup[direction]]; if (myMap.m_indicesMapToDataType != CIFTI_INDEX_TYPE_PARCELS) return false; CiftiParcelSurfaceElement tempSurf; tempSurf.m_numNodes = numberOfNodes; tempSurf.m_structure = structure; myMap.m_parcelSurfaces.push_back(tempSurf); myMap.setupLookup();//TODO: make the lookup maintenance incremental return true; } bool CiftiXMLOld::addParcelToColumns(const CiftiParcelElement& parcel) { return addParcel(ALONG_COLUMN, parcel); } bool CiftiXMLOld::addParcelToRows(const caret::CiftiParcelElement& parcel) { return addParcel(ALONG_ROW, parcel); } bool CiftiXMLOld::addParcel(const int& direction, const CiftiParcelElement& parcel) { if (direction < 0 || direction >= (int)m_dimToMapLookup.size()) { return false; } separateMaps(); if (m_dimToMapLookup[direction] == -1 || m_root.m_matrices.size() == 0) { return false; } CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, m_dimToMapLookup[direction]); CiftiMatrixIndicesMapElement& myMap = m_root.m_matrices[0].m_matrixIndicesMap[m_dimToMapLookup[direction]]; if (myMap.m_indicesMapToDataType != CIFTI_INDEX_TYPE_PARCELS) return false; if (!checkVolumeIndices(parcel.m_voxelIndicesIJK)) return false; myMap.m_parcels.push_back(parcel);//NOTE: setupLookup does error checking for nodes try { myMap.setupLookup();//TODO: make the lookup maintenance incremental, decide on throw vs bool return, separate sanity checking? } catch (...) { return false; } return true; } bool CiftiXMLOld::checkVolumeIndices(const vector& ijkList) const { int64_t listSize = (int64_t)ijkList.size(); if (listSize == 0) return true; if (listSize % 3 != 0) return false; int64_t dims[3]; vector > sform;//not used, but needed by the funciton if (!getVolumeDimsAndSForm(dims, sform)) return false; for (int i = 0; i < listSize; i += 3) { if (ijkList[i] < 0 || ijkList[i] >= dims[0]) return false; if (ijkList[i + 1] < 0 || ijkList[i + 1] >= dims[1]) return false; if (ijkList[i + 2] < 0 || ijkList[i + 2] >= dims[2]) return false; } return true; } void CiftiXMLOld::applyColumnMapToRows() { if (m_dimToMapLookup[0] == m_dimToMapLookup[1]) return; applyDimensionHelper(1, 0); } void CiftiXMLOld::applyRowMapToColumns() { if (m_dimToMapLookup[0] == m_dimToMapLookup[1]) return; applyDimensionHelper(0, 1); } void CiftiXMLOld::applyDimensionHelper(const int& from, const int& to) { if (m_root.m_matrices.size() == 0) return; CiftiMatrixElement& myMatrix = m_root.m_matrices[0];//assume only one matrix int numMaps = (int)myMatrix.m_matrixIndicesMap.size(); for (int i = 0; i < numMaps; ++i) { CiftiMatrixIndicesMapElement& myMap = myMatrix.m_matrixIndicesMap[i]; int numDimensions = (int)myMap.m_appliesToMatrixDimension.size(); for (int j = 0; j < numDimensions; ++j) { if (myMap.m_appliesToMatrixDimension[j] == to) { myMap.m_appliesToMatrixDimension.erase(myMap.m_appliesToMatrixDimension.begin() + j); --numDimensions; --j; break; } } for (int j = 0; j < numDimensions; ++j) { if (myMap.m_appliesToMatrixDimension[j] == from) { myMap.m_appliesToMatrixDimension.push_back(to); break; } } if (myMap.m_appliesToMatrixDimension.size() == 0) { myMatrix.m_matrixIndicesMap.erase(myMatrix.m_matrixIndicesMap.begin() + i); for (int j = 0; j < (int)m_dimToMapLookup.size(); ++j) { if (m_dimToMapLookup[j] > i) --m_dimToMapLookup[j]; } --numMaps; --i;//make sure we don't skip a map due to an erase } } m_dimToMapLookup[to] = m_dimToMapLookup[from]; } void CiftiXMLOld::resetColumnsToBrainModels() { resetDirectionToBrainModels(ALONG_COLUMN); } void CiftiXMLOld::resetRowsToBrainModels() { resetDirectionToBrainModels(ALONG_ROW); } void CiftiXMLOld::resetDirectionToBrainModels(const int& direction) { if (m_dimToMapLookup[direction] == -1) { m_dimToMapLookup[direction] = createMap(direction); } else { separateMaps(); } CiftiMatrixIndicesMapElement myMap; myMap.m_appliesToMatrixDimension.push_back(direction); myMap.m_indicesMapToDataType = CIFTI_INDEX_TYPE_BRAIN_MODELS; m_root.m_matrices[0].m_matrixIndicesMap[m_dimToMapLookup[direction]] = myMap; } void CiftiXMLOld::resetColumnsToTimepoints(const float& timestep, const int& timepoints, const float& timestart) { if (m_dimToMapLookup[1] == -1) { m_dimToMapLookup[1] = createMap(1); } else { separateMaps(); } CiftiMatrixIndicesMapElement myMap; myMap.m_appliesToMatrixDimension.push_back(1); myMap.m_indicesMapToDataType = CIFTI_INDEX_TYPE_TIME_POINTS; myMap.m_timeStepUnits = NIFTI_UNITS_SEC; myMap.m_timeStep = timestep; myMap.m_timeStart = timestart; myMap.m_hasTimeStart = true; myMap.m_numTimeSteps = timepoints; m_root.m_matrices[0].m_matrixIndicesMap[m_dimToMapLookup[1]] = myMap; } void CiftiXMLOld::resetRowsToTimepoints(const float& timestep, const int& timepoints, const float& timestart) { if (m_dimToMapLookup[0] == -1) { m_dimToMapLookup[0] = createMap(0); } else { separateMaps(); } CiftiMatrixIndicesMapElement myMap; myMap.m_appliesToMatrixDimension.push_back(0); myMap.m_indicesMapToDataType = CIFTI_INDEX_TYPE_TIME_POINTS; myMap.m_timeStepUnits = NIFTI_UNITS_SEC; myMap.m_timeStep = timestep; myMap.m_timeStart = timestart; myMap.m_hasTimeStart = true; myMap.m_numTimeSteps = timepoints; m_root.m_matrices[0].m_matrixIndicesMap[m_dimToMapLookup[0]] = myMap; } void CiftiXMLOld::resetColumnsToScalars(const int64_t& numMaps) { resetDirectionToScalars(ALONG_COLUMN, numMaps); } void CiftiXMLOld::resetRowsToScalars(const int64_t& numMaps) { resetDirectionToScalars(ALONG_ROW, numMaps); } void CiftiXMLOld::resetDirectionToScalars(const int& direction, const int64_t& numMaps) { CaretAssertVectorIndex(m_dimToMapLookup, direction); if (m_dimToMapLookup[direction] == -1) { m_dimToMapLookup[direction] = createMap(direction); } else { separateMaps(); } CiftiMatrixIndicesMapElement myMap; myMap.m_appliesToMatrixDimension.push_back(direction); myMap.m_indicesMapToDataType = CIFTI_INDEX_TYPE_SCALARS; myMap.m_namedMaps.resize(numMaps); m_root.m_matrices[0].m_matrixIndicesMap[m_dimToMapLookup[direction]] = myMap; } void CiftiXMLOld::resetColumnsToLabels(const int64_t& numMaps) { resetDirectionToLabels(ALONG_COLUMN, numMaps); } void CiftiXMLOld::resetRowsToLabels(const int64_t& numMaps) { resetDirectionToLabels(ALONG_ROW, numMaps); } void CiftiXMLOld::resetDirectionToLabels(const int& direction, const int64_t& numMaps) { CaretAssertVectorIndex(m_dimToMapLookup, direction); if (m_dimToMapLookup[direction] == -1) { m_dimToMapLookup[direction] = createMap(direction); } else { separateMaps(); } CiftiMatrixIndicesMapElement myMap; myMap.m_appliesToMatrixDimension.push_back(direction); myMap.m_indicesMapToDataType = CIFTI_INDEX_TYPE_LABELS; myMap.m_namedMaps.resize(numMaps); for (int64_t i = 0; i < numMaps; ++i) { myMap.m_namedMaps[i].m_labelTable.grabNew(new GiftiLabelTable()); } m_root.m_matrices[0].m_matrixIndicesMap[m_dimToMapLookup[direction]] = myMap; } void CiftiXMLOld::resetColumnsToParcels() { resetDirectionToParcels(ALONG_COLUMN); } void CiftiXMLOld::resetRowsToParcels() { resetDirectionToParcels(ALONG_ROW); } void CiftiXMLOld::resetDirectionToParcels(const int& direction) { CaretAssertVectorIndex(m_dimToMapLookup, direction); if (m_dimToMapLookup[direction] == -1) { m_dimToMapLookup[direction] = createMap(direction); } else { separateMaps(); } CiftiMatrixIndicesMapElement myMap; myMap.m_appliesToMatrixDimension.push_back(direction); myMap.m_indicesMapToDataType = CIFTI_INDEX_TYPE_PARCELS; m_root.m_matrices[0].m_matrixIndicesMap[m_dimToMapLookup[direction]] = myMap; } int CiftiXMLOld::createMap(int dimension) { CiftiMatrixIndicesMapElement tempMap; tempMap.m_appliesToMatrixDimension.push_back(dimension); if (m_root.m_matrices.size() == 0) { m_root.m_matrices.resize(1); m_root.m_numberOfMatrices = 1;//TODO: remove this variable } CiftiMatrixElement& myMatrix = m_root.m_matrices[0];//assume only one matrix myMatrix.m_matrixIndicesMap.push_back(tempMap); return myMatrix.m_matrixIndicesMap.size() - 1; } void CiftiXMLOld::separateMaps() { if (m_root.m_matrices.size() == 0) return; CiftiMatrixElement& myMatrix = m_root.m_matrices[0];//assume only one matrix int numMaps = (int)myMatrix.m_matrixIndicesMap.size(); for (int i = 0; i < numMaps; ++i)//don't need to loop over newly created maps { CiftiMatrixIndicesMapElement myMap = myMatrix.m_matrixIndicesMap[i];//make a copy because we are modifying this vector int numDimensions = (int)myMap.m_appliesToMatrixDimension.size(); for (int j = 1; j < numDimensions; ++j)//leave the first in place { int whichDim = myMap.m_appliesToMatrixDimension[j]; myMatrix.m_matrixIndicesMap.push_back(myMap); myMatrix.m_matrixIndicesMap.back().m_appliesToMatrixDimension.resize(1); myMatrix.m_matrixIndicesMap.back().m_appliesToMatrixDimension[0] = whichDim; m_dimToMapLookup[whichDim] = myMatrix.m_matrixIndicesMap.size() - 1; } myMatrix.m_matrixIndicesMap[i].m_appliesToMatrixDimension.resize(1);//ditch all but the first, they have their own maps } } int64_t CiftiXMLOld::getNewRangeStart(const int& myMapIndex) const { if (myMapIndex == -1 || m_root.m_matrices.size() == 0) { CaretAssert(false); } CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, myMapIndex); const CiftiMatrixIndicesMapElement* myMap = &(m_root.m_matrices[0].m_matrixIndicesMap[myMapIndex]); CaretAssert(myMap != NULL && myMap->m_indicesMapToDataType == CIFTI_INDEX_TYPE_BRAIN_MODELS); int numModels = (int)myMap->m_brainModels.size(); int64_t curRet = 0; for (int i = 0; i < numModels; ++i) { int64_t thisEnd = myMap->m_brainModels[i].m_indexOffset + myMap->m_brainModels[i].m_indexCount; if (thisEnd > curRet) { curRet = thisEnd; } } return curRet; } int64_t CiftiXMLOld::getDimensionLength(const int& direction) const { if (direction < 0 || direction >= (int)m_dimToMapLookup.size()) { throw DataFileException("getDimensionLength called on nonexistant dimension"); } int myMapIndex = m_dimToMapLookup[direction]; if (myMapIndex == -1 || m_root.m_matrices.size() == 0) { throw DataFileException("getDimensionLength called on nonexistant dimension"); } CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, myMapIndex); const CiftiMatrixIndicesMapElement* myMap = &(m_root.m_matrices[0].m_matrixIndicesMap[myMapIndex]); if (myMap->m_indicesMapToDataType == CIFTI_INDEX_TYPE_TIME_POINTS) { return myMap->m_numTimeSteps; } else if (myMap->m_indicesMapToDataType == CIFTI_INDEX_TYPE_BRAIN_MODELS) { return getNewRangeStart(m_dimToMapLookup[direction]); } else if (myMap->m_indicesMapToDataType == CIFTI_INDEX_TYPE_SCALARS || myMap->m_indicesMapToDataType == CIFTI_INDEX_TYPE_LABELS) { return myMap->m_namedMaps.size(); } else if (myMap->m_indicesMapToDataType == CIFTI_INDEX_TYPE_PARCELS) { return myMap->m_parcels.size(); } else { throw DataFileException("unknown cifti mapping type"); } } int64_t CiftiXMLOld::getNumberOfColumns() const {//number of columns is LENGTH OF A ROW return getDimensionLength(ALONG_ROW); } int64_t CiftiXMLOld::getNumberOfRows() const { return getDimensionLength(ALONG_COLUMN); } IndicesMapToDataType CiftiXMLOld::getColumnMappingType() const { if (m_dimToMapLookup[1] == -1 || m_root.m_matrices.size() == 0) { return CIFTI_INDEX_TYPE_INVALID; } CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, m_dimToMapLookup[1]); const CiftiMatrixIndicesMapElement* myMap = &(m_root.m_matrices[0].m_matrixIndicesMap[m_dimToMapLookup[1]]); return myMap->m_indicesMapToDataType; } IndicesMapToDataType CiftiXMLOld::getRowMappingType() const { if (m_dimToMapLookup[0] == -1 || m_root.m_matrices.size() == 0) { return CIFTI_INDEX_TYPE_INVALID; } CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, m_dimToMapLookup[0]); const CiftiMatrixIndicesMapElement* myMap = &(m_root.m_matrices[0].m_matrixIndicesMap[m_dimToMapLookup[0]]); return myMap->m_indicesMapToDataType; } IndicesMapToDataType CiftiXMLOld::getMappingType(const int& direction) const { if (direction < 0 || direction >= (int)m_dimToMapLookup.size()) { CaretAssertMessage(false, "CiftiXML::getMappingType called with invalid direction"); return CIFTI_INDEX_TYPE_INVALID; } if (m_dimToMapLookup[direction] == -1 || m_root.m_matrices.size() == 0) { return CIFTI_INDEX_TYPE_INVALID; } CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, m_dimToMapLookup[direction]); const CiftiMatrixIndicesMapElement* myMap = &(m_root.m_matrices[0].m_matrixIndicesMap[m_dimToMapLookup[direction]]); return myMap->m_indicesMapToDataType; } const CiftiBrainModelElement* CiftiXMLOld::findSurfaceModel(const int& myMapIndex, const StructureEnum::Enum& structure) const { if (myMapIndex == -1 || m_root.m_matrices.size() == 0) { return NULL; } CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, myMapIndex); const CiftiMatrixIndicesMapElement* myMap = &(m_root.m_matrices[0].m_matrixIndicesMap[myMapIndex]); if (myMap->m_indicesMapToDataType != CIFTI_INDEX_TYPE_BRAIN_MODELS) return NULL; const vector& myModels = myMap->m_brainModels; int numModels = myModels.size(); for (int i = 0; i < numModels; ++i) { if (myModels[i].m_brainStructure == structure && myModels[i].m_modelType == CIFTI_MODEL_TYPE_SURFACE) { return &(myModels[i]); } } return NULL; } const CiftiBrainModelElement* CiftiXMLOld::findVolumeModel(const int& myMapIndex, const StructureEnum::Enum& structure) const { if (myMapIndex == -1 || m_root.m_matrices.size() == 0) { return NULL; } CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, myMapIndex); const CiftiMatrixIndicesMapElement* myMap = &(m_root.m_matrices[0].m_matrixIndicesMap[myMapIndex]); if (myMap->m_indicesMapToDataType != CIFTI_INDEX_TYPE_BRAIN_MODELS) return NULL; const vector& myModels = myMap->m_brainModels; int numModels = myModels.size(); for (int i = 0; i < numModels; ++i) { if (myModels[i].m_brainStructure == structure && myModels[i].m_modelType == CIFTI_MODEL_TYPE_VOXELS) { return &(myModels[i]); } } return NULL; } bool CiftiXMLOld::matchesForColumns(const CiftiXMLOld& rhs) const { return mappingMatches(ALONG_COLUMN, rhs, ALONG_COLUMN); } bool CiftiXMLOld::matchesForRows(const CiftiXMLOld& rhs) const { return mappingMatches(ALONG_ROW, rhs, ALONG_ROW); } bool CiftiXMLOld::mappingMatches(const int& direction, const CiftiXMLOld& other, const int& otherDirection) const { CaretAssertVectorIndex(m_dimToMapLookup, direction); CaretAssertVectorIndex(other.m_dimToMapLookup, otherDirection); if (m_root.m_matrices.size() == 0 || m_dimToMapLookup[direction] == -1) { return (other.m_root.m_matrices.size() == 0 || other.m_dimToMapLookup[otherDirection] == -1); } CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, m_dimToMapLookup[direction]); CaretAssertVectorIndex(other.m_root.m_matrices[0].m_matrixIndicesMap, other.m_dimToMapLookup[otherDirection]); if (hasVolumeData(direction) && !(other.hasVolumeData(otherDirection) && matchesVolumeSpace(other))) return false; return (m_root.m_matrices[0].m_matrixIndicesMap[m_dimToMapLookup[direction]] == other.m_root.m_matrices[0].m_matrixIndicesMap[other.m_dimToMapLookup[otherDirection]]); } void CiftiXMLOld::copyMapping(const int& direction, const CiftiXMLOld& other, const int& otherDirection) { CaretAssert(direction > -1 && otherDirection > -1); if ((int)other.m_dimToMapLookup.size() <= otherDirection || other.m_dimToMapLookup[otherDirection] == -1) { throw DataFileException("copyMapping called with nonexistant mapping to copy"); } bool copyVolSpace = false, haveVoxels = false; for (int i = 0; i < (int)m_dimToMapLookup.size(); ++i) { if (i != direction && hasVolumeData(i)) { haveVoxels = true; } } if (other.hasVolumeData(otherDirection)) { if (haveVoxels) { if (!matchesVolumeSpace(other)) throw DataFileException("cannot copy mapping from other cifti due to volume space mismatch"); } else { copyVolSpace = true; } } else { if (!haveVoxels) { m_root.m_matrices[0].m_volume.clear(); } } if (m_dimToMapLookup[direction] == -1) { m_dimToMapLookup[direction] = createMap(direction); } else { separateMaps(); } if (copyVolSpace) {//we have checked that this is okay because if we have any voxel data, it is in the map that is about to be replaced m_root.m_matrices[0].m_volume = other.m_root.m_matrices[0].m_volume; } CiftiMatrixIndicesMapElement& myMap = m_root.m_matrices[0].m_matrixIndicesMap[m_dimToMapLookup[direction]]; myMap = other.m_root.m_matrices[0].m_matrixIndicesMap[other.m_dimToMapLookup[otherDirection]]; myMap.m_appliesToMatrixDimension.clear(); myMap.m_appliesToMatrixDimension.push_back(direction);//the member lookups should already be valid, copy works } map* CiftiXMLOld::getMapMetadata(const int& direction, const int& index) const { CaretAssertVectorIndex(m_dimToMapLookup, direction); int myMapIndex = m_dimToMapLookup[direction]; if (myMapIndex == -1 || m_root.m_matrices.size() == 0) { return NULL; } CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, myMapIndex); const CiftiMatrixIndicesMapElement& myMap = m_root.m_matrices[0].m_matrixIndicesMap[myMapIndex]; if (myMap.m_indicesMapToDataType != CIFTI_INDEX_TYPE_LABELS && myMap.m_indicesMapToDataType != CIFTI_INDEX_TYPE_SCALARS) { return NULL; } CaretAssertVectorIndex(myMap.m_namedMaps, index); return &(myMap.m_namedMaps[index].m_mapMetaData); } PaletteColorMapping* CiftiXMLOld::getFilePalette() const { if (m_root.m_matrices.size() == 0) return NULL; if (m_root.m_matrices[0].m_palette == NULL) {//load from metadata m_root.m_matrices[0].m_palette.grabNew(new PaletteColorMapping()); map::const_iterator myIter = m_root.m_matrices[0].m_userMetaData.find("PaletteColorMapping"); if (myIter != m_root.m_matrices[0].m_userMetaData.end()) { m_root.m_matrices[0].m_palette->decodeFromStringXML(myIter->second); } /*} else {//will be needed if we make default palettes a user preference if (m_root.m_matrices[0].m_defaultPalette) {//TODO: load the current defaults into the existing palette, find some way of only doing this if the defaults were modified since last time this was called }//*/ } return m_root.m_matrices[0].m_palette.getPointer(); } PaletteColorMapping* CiftiXMLOld::getMapPalette(const int& direction, const int& index) const { CaretAssertVectorIndex(m_dimToMapLookup, direction); int myMapIndex = m_dimToMapLookup[direction]; if (myMapIndex == -1 || m_root.m_matrices.size() == 0) { return NULL; } CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, myMapIndex); const CiftiMatrixIndicesMapElement& myMap = m_root.m_matrices[0].m_matrixIndicesMap[myMapIndex]; if (myMap.m_indicesMapToDataType != CIFTI_INDEX_TYPE_SCALARS) { return NULL; } CaretAssertVectorIndex(myMap.m_namedMaps, index); const CiftiNamedMapElement& myElem = myMap.m_namedMaps[index]; if (myElem.m_palette == NULL) {//load from metadata myElem.m_palette.grabNew(new PaletteColorMapping()); map::const_iterator myIter = myElem.m_mapMetaData.find("PaletteColorMapping"); if (myIter != myElem.m_mapMetaData.end()) { myElem.m_palette->decodeFromStringXML(myIter->second); } /*} else {//will be needed if we make default palettes a user preference if (m_root.m_matrices[0].m_defaultPalette) {//TODO: load the current defaults into the existing palette, find some way of only doing this if the defaults were modified since last time this was called }//*/ } return myElem.m_palette; } bool CiftiXMLOld::operator==(const caret::CiftiXMLOld& rhs) const { if (this == &rhs) return true;//compare pointers to skip checking object against itself if (m_root.m_matrices.size() != 1 || m_root.m_matrices[0].m_matrixIndicesMap.size() != 2) { throw DataFileException("old CIFTI XML implementation only supports single-matrix, 2D cifti"); } if (m_root.m_matrices.size() != rhs.m_root.m_matrices.size()) return false; if (m_root.m_matrices[0].m_matrixIndicesMap.size() != rhs.m_root.m_matrices[0].m_matrixIndicesMap.size()) return false; if (!matchesVolumeSpace(rhs)) return false; if (!matchesForColumns(rhs)) return false; if (!matchesForRows(rhs)) return false; return true; } bool CiftiXMLOld::matchesVolumeSpace(const CiftiXMLOld& rhs) const { if (hasColumnVolumeData() || hasRowVolumeData()) { if (!(rhs.hasColumnVolumeData() || rhs.hasRowVolumeData())) { return false; } } else { if (rhs.hasColumnVolumeData() || rhs.hasRowVolumeData()) { return false; } else { return true;//don't check for matching/existing sforms if there are no voxel maps in either } } int64_t dims[3], rdims[3]; vector > sform, rsform; if (!getVolumeDimsAndSForm(dims, sform) || !rhs.getVolumeDimsAndSForm(rdims, rsform)) {//should NEVER happen CaretAssertMessage(false, "has*VolumeData() and getVolumeDimsAndSForm() disagree"); throw DataFileException("has*VolumeData() and getVolumeDimsAndSForm() disagree"); } const float TOLER_RATIO = 0.999f;//ratio a spacing element can mismatch by for (int i = 0; i < 3; ++i) { if (dims[i] != rdims[i]) return false; for (int j = 0; j < 4; ++j) { float left = sform[i][j]; float right = rsform[i][j]; if (left != right && (left == 0.0f || right == 0.0f || left / right < TOLER_RATIO || right / left < TOLER_RATIO)) return false; } } return true; } void CiftiXMLOld::swapMappings(const int& direction1, const int& direction2) { CaretAssertVectorIndex(m_dimToMapLookup, direction1); CaretAssertVectorIndex(m_dimToMapLookup, direction2); if (direction1 < 0 || direction1 >= (int)m_dimToMapLookup.size() || direction2 < 0 || direction2 >= (int)m_dimToMapLookup.size()) { throw DataFileException("invalid direction specified to swapMappings, notify the developers"); } int mapIndex1 = m_dimToMapLookup[direction1]; int mapIndex2 = m_dimToMapLookup[direction2]; if (mapIndex1 == -1 || mapIndex2 == -1 || m_root.m_matrices.size() == 0) { throw DataFileException("invalid direction specified to swapMappings, notify the developers"); } CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, mapIndex1); CaretAssertVectorIndex(m_root.m_matrices[0].m_matrixIndicesMap, mapIndex2); if (mapIndex1 == mapIndex2) return;//nothing to do if they refer to the same mapping CiftiMatrixIndicesMapElement& mapRef1 = m_root.m_matrices[0].m_matrixIndicesMap[mapIndex1];//give them shorter variable names CiftiMatrixIndicesMapElement& mapRef2 = m_root.m_matrices[0].m_matrixIndicesMap[mapIndex2]; m_dimToMapLookup[direction1] = mapIndex2;//swap them by changing the lookup values m_dimToMapLookup[direction2] = mapIndex1; int numApply = (int)mapRef1.m_appliesToMatrixDimension.size(), i;//but we also need to modify the "applies to" lists for (i = 0; i < numApply; ++i) { if (mapRef1.m_appliesToMatrixDimension[i] == direction1)//we made the references from the old lookup values, so these are same { mapRef1.m_appliesToMatrixDimension[i] = direction2;//change the "applies to" element break; } } CaretAssert(i < numApply);//otherwise, we didn't find the element to modify, ie, something went horribly wrong numApply = (int)mapRef2.m_appliesToMatrixDimension.size();//and for the other mapping for (i = 0; i < numApply; ++i) { if (mapRef2.m_appliesToMatrixDimension[i] == direction2) { mapRef2.m_appliesToMatrixDimension[i] = direction1; break; } } CaretAssert(i < numApply); } workbench-1.1.1/src/Cifti/CiftiXMLOld.h000066400000000000000000000626531255417355300175760ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #ifndef __CIFTI_XML_OLD__ #define __CIFTI_XML_OLD__ #include "StructureEnum.h" #include "CiftiXMLElements.h" #include "CiftiXMLReader.h" #include "CiftiXMLWriter.h" #include "VolumeBase.h" #include "VolumeSpace.h" #include #include namespace caret { class DataFileContentInformation; /// Simple Container class for storing Cifti XML meta data struct CiftiSurfaceMap { int64_t m_ciftiIndex; int64_t m_surfaceNode; }; struct CiftiVolumeMap { int64_t m_ciftiIndex; int64_t m_ijk[3]; }; struct CiftiVolumeStructureMap { std::vector m_map; StructureEnum::Enum m_structure; }; struct CiftiBrainModelInfo { ModelType m_type; StructureEnum::Enum m_structure; CiftiBrainModelInfo() { m_type = CIFTI_MODEL_TYPE_INVALID; m_structure = StructureEnum::INVALID; } }; class CiftiXMLOld { public: enum { ALONG_ROW = 0, ALONG_COLUMN = 1, ALONG_STACK = 2//better name for this? }; /** * Default Constructor * * Default Constructor */ CiftiXMLOld(); /** * Constructor * * Constructor, create class using already existing Cifti xml tree * @param xml_root */ //CiftiXML(CiftiRootElement &xml_root) { m_root = xml_root; rootChanged(); } /** * Constructor * * Constructor, create class using ASCII formatted byte array that * containes xml meta data text * @param bytes */ CiftiXMLOld(const QByteArray &bytes) { readXML(bytes); } /** * Constructor * * Constructor, create class using QString that contains xml * meta data text * @param xml_string */ CiftiXMLOld(const QString &xml_string) { readXML(xml_string); } /** * Constructor * * Constructor, create class using QXmlStreamReader. * QXmlStreamReader is assumed to be reading from Cifti XML * Text. * @param xml_stream */ CiftiXMLOld(QXmlStreamReader &xml_stream) { readXML(xml_stream); } /** * readXML * * readXML, replacing the currently Cifti XML Root, if it exists * @param bytes an ASCII formatted byte array that contains Cifti XML data */ void readXML(const QByteArray &bytes) { QString text(bytes);readXML(text); } /** * readXML * * readXML, replacing the currently Cifti XML Root, if it exists * @param text QString that contains Cifti XML data */ void readXML(const QString &text) {QXmlStreamReader xml(text); readXML(xml); } /** * readXML * * readXML, replacing the currently Cifti XML Root, if it exists * @param xml_stream */ void readXML(QXmlStreamReader &xml_stream) { CiftiXMLReader myReader; myReader.parseCiftiXML(xml_stream,m_root); rootChanged(); } /** * writeXML * * write the Cifti XML data to the supplied QString * @param text */ void writeXML(QString &text) const { text = ""; QXmlStreamWriter xml(&text); CiftiXMLWriter myWriter; myWriter.writeCiftiXML(xml,m_root); }//we don't use the old writer in testing, so it won't recurse /** * writeXML * * write the Cifti XML data to the supplied byte array. * @param bytes */ void writeXML(QByteArray &bytes) const { bytes.clear(); QXmlStreamWriter xml(&bytes); CiftiXMLWriter myWriter; myWriter.writeCiftiXML(xml,m_root); } //static void testNewXML(const QString& xmlString); //static void testNewXML(const QByteArray& xmlString); /** * setXMLRoot * * set the Cifti XML root * @param xml_root */ //void setXMLRoot (CiftiRootElement &xml_root) { m_root = xml_root; rootChanged(); } /** * getXMLRoot * * get a copy of the Cifti XML Root * @param xml_root */ //void getXMLRoot (CiftiRootElement &xml_root) const { xml_root = m_root; } const CiftiVersion& getVersion() const { return m_root.m_version; } ///get the row index for a node, returns -1 if it doesn't find a matching mapping int64_t getRowIndexForNode(const int64_t& node, const StructureEnum::Enum& structure) const; ///get the column index for a node, returns -1 if it doesn't find a matching mapping int64_t getColumnIndexForNode(const int64_t& node, const StructureEnum::Enum& structure) const; ///get the row index for a voxel, returns -1 if it doesn't find a matching mapping int64_t getRowIndexForVoxel(const int64_t* ijk) const; ///get the column index for a voxel, returns -1 if it doesn't find a matching mapping int64_t getColumnIndexForVoxel(const int64_t* ijk) const; ///get the column index for a voxel coordinate, returns -1 if the closest indexes have no cifti data int64_t getRowIndexForVoxelCoordinate(const float* xyz) const; ///get the column index for a voxel coordinate, returns -1 if the closest indexes have no cifti data int64_t getColumnIndexForVoxelCoordinate(const float* xyz) const; ///get row index for a timepoint int64_t getRowIndexForTimepoint(const float& seconds) const; ///get row index for a timepoint int64_t getColumnIndexForTimepoint(const float& seconds) const; ///get the mapping for a surface in rows, returns false and empty vector if not found bool getSurfaceMapForRows(std::vector& mappingOut, const StructureEnum::Enum& structure) const; ///get the mapping for a surface in columns, returns false and empty vector if not found bool getSurfaceMapForColumns(std::vector& mappingOut, const StructureEnum::Enum& structure) const; ///get the mapping for a surface in columns, returns false and empty vector if not found bool getSurfaceMap(const int& direction, std::vector& mappingOut, const StructureEnum::Enum& structure) const; ///get the mapping for a volume in rows, returns false and empty vector if not found bool getVolumeMapForRows(std::vector& mappingOut) const; ///get the mapping for a volume in columns, returns false and empty vector if not found bool getVolumeMapForColumns(std::vector& mappingOut) const; ///get the mapping for a volume, returns false and empty vector if not found bool getVolumeMap(const int& direction, std::vector& mappingOut) const; void getVoxelInfoInDataFileContentInformation(const int& direction, DataFileContentInformation& dataFileInformation) const; ///get the mapping for a volume structure in rows, returns false and empty vector if not found bool getVolumeStructureMapForRows(std::vector& mappingOut, const StructureEnum::Enum& structure) const; ///get the mapping for a volume structure in columns, returns false and empty vector if not found bool getVolumeStructureMapForColumns(std::vector& mappingOut, const StructureEnum::Enum& structure) const; ///get the mapping for a volume structure bool getVolumeStructureMap(const int& direction, std::vector& mappingOut, const StructureEnum::Enum& structure) const; ///get the lists of what structures exist bool getStructureListsForRows(std::vector& surfaceList, std::vector& volumeList) const; ///get the lists of what structures exist bool getStructureListsForColumns(std::vector& surfaceList, std::vector& volumeList) const; ///get the lists of what structures exist bool getStructureLists(const int& direction, std::vector& surfaceList, std::vector& volumeList) const; ///get the number of structures for a brain model mapping int getNumberOfBrainModels(const int& direction) const; ///get structure info by structure index CiftiBrainModelInfo getBrainModelInfo(const int& direction, const int& whichModel) const; ///get the list of volume parcels and their maps in rows, returns false and empty vector if not found bool getVolumeModelMapsForRows(std::vector& mappingsOut) const; ///get the list of volume parcels and their maps in columns, returns false and empty vector if not found bool getVolumeModelMapsForColumns(std::vector& mappingsOut) const; ///get the original number of nodes of the surfaces used to make this cifti, for rows int64_t getRowSurfaceNumberOfNodes(const StructureEnum::Enum& structure) const; ///get the original number of nodes of the surfaces used to make this cifti, for columns int64_t getColumnSurfaceNumberOfNodes(const StructureEnum::Enum& structure) const; ///get the original number of nodes of the surfaces used to make this cifti along a direction int64_t getSurfaceNumberOfNodes(const int& direction, const StructureEnum::Enum& structure) const; ///get the timestep for rows, returns false if not timeseries bool getRowTimestep(float& seconds) const; ///get the timestep for columns, returns false if not timeseries bool getColumnTimestep(float& seconds) const; ///get the timestart for rows, returns false if not set or not timeseries bool getRowTimestart(float& seconds) const; ///get the timestart for columns, returns false if not set or not timeseries bool getColumnTimestart(float& seconds) const; ///get the number of timepoints for rows, returns false if not timeseries, sets -1 if unknown number of timepoints bool getRowNumberOfTimepoints(int& numTimepoints) const; ///get the number of timepoints for rows, returns false if not timeseries, sets -1 if unknown number of timepoints bool getColumnNumberOfTimepoints(int& numTimepoints) const; ///get the parcels for rows bool getParcelsForRows(std::vector& parcelsOut) const; ///get the parcels for columns bool getParcelsForColumns(std::vector& parcelsOut) const; ///get the parcels for a dimension bool getParcels(const int& direction, std::vector& parcelsOut) const; ///get the parcel surface structures bool getParcelSurfaceStructures(const int& direction, std::vector& structuresOut) const; ///get the row parcel for a node int64_t getRowParcelForNode(const int64_t& node, const StructureEnum::Enum& structure) const; ///get the column parcel for a node int64_t getColumnParcelForNode(const int64_t& node, const StructureEnum::Enum& structure) const; ///get the row parcel for a voxel int64_t getRowParcelForVoxel(const int64_t* ijk) const; ///get the row parcel for a voxel int64_t getColumnParcelForVoxel(const int64_t* ijk) const; ///set the timestep for rows, returns false if not timeseries bool setRowTimestep(const float& seconds); ///set the timestep for columns, returns false if not timeseries bool setColumnTimestep(const float& seconds); ///set the timestart for rows, returns false if not set or not timeseries bool setRowTimestart(const float& seconds); ///set the timestart for columns, returns false if not set or not timeseries bool setColumnTimestart(const float& seconds); ///set the number of timepoints for rows, returns false if not timeseries bool setRowNumberOfTimepoints(const int& numTimepoints); ///set the number of timepoints for rows, returns false if not timeseries bool setColumnNumberOfTimepoints(const int& numTimepoints); ///set rows to be brain models, and clear the list of brain models for rows void resetRowsToBrainModels(); ///set columns to be brain models, and clear the list of brain models for columns void resetColumnsToBrainModels(); ///set direction to be brain models, and clear it void resetDirectionToBrainModels(const int& direction); ///add a surface brain model to the list of brain models for rows bool addSurfaceModelToRows(const int& numberOfNodes, const StructureEnum::Enum& structure, const float* roi = NULL); ///add a surface brain model to the list of brain models for columns bool addSurfaceModelToColumns(const int& numberOfNodes, const StructureEnum::Enum& structure, const float* roi = NULL); ///add a surface brain model to the list of brain models for rows bool addSurfaceModelToRows(const int& numberOfNodes, const StructureEnum::Enum& structure, const std::vector& nodeList); ///add a surface brain model to the list of brain models for columns bool addSurfaceModelToColumns(const int& numberOfNodes, const StructureEnum::Enum& structure, const std::vector& nodeList); ///add a volume brain model to the list of brain models for rows bool addVolumeModelToRows(const std::vector& ijkList, const StructureEnum::Enum& structure); ///add a volume brain model to the list of brain models for columns bool addVolumeModelToColumns(const std::vector& ijkList, const StructureEnum::Enum& structure); ///add surface or volume model by direction bool addSurfaceModel(const int& direction, const int& numberOfNodes, const StructureEnum::Enum& structure, const float* roi = NULL); bool addSurfaceModel(const int& direction, const int& numberOfNodes, const StructureEnum::Enum& structure, const std::vector& nodeList); bool addVolumeModel(const int& direction, const std::vector& ijkList, const StructureEnum::Enum& structure); ///add a surface to the list of parcel surfaces for rows bool addParcelSurfaceToRows(const int& numberOfNodes, const StructureEnum::Enum& structure); ///add a surface to the list of parcel surfaces for columns bool addParcelSurfaceToColumns(const int& numberOfNodes, const StructureEnum::Enum& structure); ///add a surface to the list of parcel surfaces bool addParcelSurface(const int& direction, const int& numberOfNodes, const StructureEnum::Enum& structure); ///add a parcel to rows bool addParcelToRows(const CiftiParcelElement& parcel); ///add a parcel to columns bool addParcelToColumns(const CiftiParcelElement& parcel); ///add a parcel to columns bool addParcel(const int& direction, const CiftiParcelElement& parcel); ///set rows to be of type timepoints void resetRowsToTimepoints(const float& timestep, const int& numTimepoints, const float& timestart = 0.0f); ///set columns to be of type timepoints void resetColumnsToTimepoints(const float& timestep, const int& numTimepoints, const float& timestart = 0.0f); ///set rows to be of type scalars void resetRowsToScalars(const int64_t& numMaps); ///set columns to be of type scalars void resetColumnsToScalars(const int64_t& numMaps); ///set a direction to scalars void resetDirectionToScalars(const int& direction, const int64_t& numMaps); ///set rows to be of type labels void resetRowsToLabels(const int64_t& numMaps); ///set columns to be of type labels void resetColumnsToLabels(const int64_t& numMaps); ///set a direction to labels void resetDirectionToLabels(const int& direction, const int64_t& numMaps); ///set rows to be of type parcels void resetRowsToParcels(); ///set columns to be of type parcels void resetColumnsToParcels(); ///set dimension to be of type parcels void resetDirectionToParcels(const int& direction); ///get the map name for an index along a column AString getMapNameForColumnIndex(const int64_t& index) const; ///get the map name for an index along a row AString getMapNameForRowIndex(const int64_t& index) const; ///get the map name for an index AString getMapName(const int& direction, const int64_t& index) const; ///get the index for a map number/name - NOTE: returns -1 if mapping type doesn't support names int64_t getMapIndexFromNameOrNumber(const int& direction, const AString& numberOrName) const; //HACK: const method returns non-const GiftiLabelTable pointer because getCiftiXML MUST return a const CiftiXML&, but we need to be able to change the label table ///get the label table for an index along a column GiftiLabelTable* getLabelTableForColumnIndex(const int64_t& index) const; //HACK: const method returns non-const GiftiLabelTable pointer because getCiftiXML MUST return a const CiftiXML&, but we need to be able to change the label table ///get the label table for an index along a row GiftiLabelTable* getLabelTableForRowIndex(const int64_t& index) const; //HACK: const method returns non-const GiftiLabelTable pointer because getCiftiXML MUST return a const CiftiXML&, but we need to be able to change the label table ///get the label table for an index GiftiLabelTable* getMapLabelTable(const int& direction, const int64_t& myMapIndex) const; ///set the map name for an index along a column bool setMapNameForColumnIndex(const int64_t& index, const AString& name) const; ///set the map name for an index along a row bool setMapNameForRowIndex(const int64_t& index, const AString& name) const; ///set the map name for an index bool setMapNameForIndex(const int& direction, const int64_t& index, const AString& name) const; ///set the label table for an index along a column bool setLabelTableForColumnIndex(const int64_t& index, const GiftiLabelTable& labelTable); ///set the label table for an index along a row bool setLabelTableForRowIndex(const int64_t& index, const GiftiLabelTable& labelTable); ///set the column map to also apply to rows void applyColumnMapToRows(); ///set the row map to also apply to columns void applyRowMapToColumns(); ///get the number of rows (column length) int64_t getNumberOfRows() const; ///get the number of columns (row length) int64_t getNumberOfColumns() const; ///get the length of a dimension int64_t getDimensionLength(const int& direction) const; ///get what mapping type the rows use IndicesMapToDataType getRowMappingType() const; ///get what mapping type the columns use IndicesMapToDataType getColumnMappingType() const; ///get what mapping type a dimension uses IndicesMapToDataType getMappingType(const int& direction) const; ///get dimensions, spacing, origin for the volume attribute - returns false if not plumb bool getVolumeAttributesForPlumb(VolumeSpace::OrientTypes orientOut[3], int64_t dimensionsOut[3], float originOut[3], float spacingOut[3]) const; ///get dimensions and sform, useful for making a volume bool getVolumeDimsAndSForm(int64_t dimsOut[3], std::vector >& sformOut) const; ///set the volume space void setVolumeDimsAndSForm(const int64_t dims[3], const std::vector >& sform); ///get volume space object bool getVolumeSpace(VolumeSpace& volSpaceOut) const; ///swap mappings between two directions void swapMappings(const int& direction1, const int& direction2); ///check what types of data it has bool hasRowVolumeData() const; bool hasColumnVolumeData() const; bool hasVolumeData(const int& direction) const; bool hasRowSurfaceData(const StructureEnum::Enum& structure) const; bool hasColumnSurfaceData(const StructureEnum::Enum& structure) const; bool hasSurfaceData(const int& direction, const StructureEnum::Enum& structure) const; ///comparison bool mappingMatches(const int& direction, const CiftiXMLOld& other, const int& otherDirection) const; bool matchesForRows(const CiftiXMLOld& rhs) const; bool matchesForColumns(const CiftiXMLOld& rhs) const; bool matchesVolumeSpace(const CiftiXMLOld& rhs) const; bool operator==(const CiftiXMLOld& rhs) const; bool operator!=(const CiftiXMLOld& rhs) const { return !((*this) == rhs); } ///take a mapping from another xml object void copyMapping(const int& direction, const CiftiXMLOld& other, const int& otherDirection); std::map* getFileMetaData() const; std::map* getMapMetadata(const int& direction, const int& index) const; //HACK: const method returns non-const PaletteColorMapping pointer because getCiftiXML MUST return a const CiftiXML&, but we need to be able to change the palette PaletteColorMapping* getFilePalette() const; PaletteColorMapping* getMapPalette(const int& direction, const int& index) const; protected: CiftiRootElement m_root; //int m_rowMapIndex, m_colMapIndex; std::vector m_dimToMapLookup; ///updates the member variables associated with our root, should only be needed after reading from XML void rootChanged(); ///convenience functions to grab the correct model out of the tree, to replace the rowLeft/rowRight stuff (since we might have other surfaces too) const CiftiBrainModelElement* findSurfaceModel(const int& myMapIndex, const StructureEnum::Enum& structure) const; const CiftiBrainModelElement* findVolumeModel(const int& myMapIndex, const StructureEnum::Enum& structure) const; ///some boilerplate to get the correct index in a particular mapping int64_t getSurfaceIndex(const int64_t& node, const CiftiBrainModelElement* myModel) const; int64_t getVolumeIndex(const int64_t* ijk, const int& myMapIndex) const; int64_t getVolumeIndex(const float* xyz, const int& myMapIndex) const; int64_t getTimestepIndex(const float& seconds, const int& myMapIndex) const; int64_t getParcelForNode(const int64_t& node, const StructureEnum::Enum& structure, const int& myMapIndex) const; int64_t getParcelForVoxel(const int64_t* ijk, const int& myMapIndex) const; ///boilerplate for map information bool getTimestep(float& seconds, const int& myMapIndex) const; bool getTimestart(float& seconds, const int& myMapIndex) const; bool setTimestep(const float& seconds, const int& myMapIndex); bool setTimestart(const float& seconds, const int& myMapIndex); bool setLabelTable(const int64_t& index, const GiftiLabelTable& labelTable, const int& myMapIndex); ///some boilerplate to retrieve mappings bool getVolumeModelMappings(std::vector& mappingsOut, const int& myMapIndex) const; ///miscellaneous bool checkVolumeIndices(const std::vector& ijkList) const; bool checkSurfaceNodes(const std::vector& nodeList, const int& numberOfNodes) const; void applyDimensionHelper(const int& from, const int& to); int64_t getNewRangeStart(const int& myMapIndex) const; void separateMaps(); int createMap(int dimension); }; } #endif //__CIFTI_XML_OLD__ workbench-1.1.1/src/Cifti/CiftiXMLReader.cxx000066400000000000000000000771451255417355300206370ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "CaretLogger.h" #include "CiftiXMLElements.h" #include "CiftiXMLReader.h" #include "DataFileException.h" #include "GiftiLabelTable.h" using namespace caret; using namespace std; void CiftiXMLReader::parseCiftiXML(QXmlStreamReader &xml, CiftiRootElement &rootElement) { while (!xml.atEnd() && !xml.hasError()) { xml.readNext(); QString test = xml.name().toString(); if(xml.isStartElement()) { QString elementName = xml.name().toString(); if(elementName == "CIFTI") { QXmlStreamAttributes attributes = xml.attributes(); if(attributes.hasAttribute("Version")) { rootElement.m_version = CiftiVersion(attributes.value("Version").toString()); m_readingVersion = rootElement.m_version; if (m_readingVersion != CiftiVersion(1, 0)) xml.raiseError("cannot read version " + m_readingVersion.toString() + " with old XML code"); } else xml.raiseError("Cifti XML Header missing Version String."); if(attributes.hasAttribute("NumberOfMatrices")) rootElement.m_numberOfMatrices = attributes.value("NumberOfMatrices").toString().toULong(); else xml.raiseError("Cifti XML Header missing number of matrices."); } else if(elementName == "Matrix") { rootElement.m_matrices.push_back(CiftiMatrixElement()); parseMatrixElement(xml,rootElement.m_matrices.back()); } else xml.raiseError("unknown element in Cifti: " + elementName); } } if(xml.hasError()) { throw DataFileException("XML error: " + xml.errorString()); } else if(!xml.atEnd()) { CaretLogWarning("Finished parsing Cifti XML without error, but not at end of XML"); } if (rootElement.m_numberOfMatrices != rootElement.m_matrices.size()) { CaretLogWarning("NumberOfMatrices does not match number of elements"); } } void CiftiXMLReader::parseMatrixElement(QXmlStreamReader &xml, CiftiMatrixElement &matrixElement) { QString test = xml.name().toString(); while (!(xml.isEndElement() && (xml.name().toString() == "Matrix")) && !xml.hasError()) {// && xml.name() == "Matrix") { xml.readNext(); QString test2 = xml.name().toString(); if(xml.isStartElement()) { QString elementName = xml.name().toString(); if(elementName == "MetaData") { parseMetaData(xml,matrixElement.m_userMetaData); } else if(elementName == "LabelTable") { parseLabelTable(xml,matrixElement.m_labelTable); } else if(elementName == "MatrixIndicesMap") { matrixElement.m_matrixIndicesMap.push_back(CiftiMatrixIndicesMapElement()); parseMatrixIndicesMap(xml,matrixElement.m_matrixIndicesMap.back()); } else if(elementName == "Volume") { matrixElement.m_volume.push_back(CiftiVolumeElement()); parseVolume(xml,matrixElement.m_volume.back()); if (xml.hasError()) return; } else xml.raiseError("unknown element in Matrix: " + elementName); } } QString test2=xml.name().toString(); //check end element if(!xml.hasError()) if(!xml.isEndElement() || (xml.name().toString() != "Matrix")) xml.raiseError("Matrix end tag not found."); } void CiftiXMLReader::parseMetaData(QXmlStreamReader &xml, map &userMetaData) { while (!(xml.isEndElement() && (xml.name().toString() == "MetaData")) && !xml.hasError()) {// && xml.name() == "MetaData") { xml.readNext(); if(xml.isStartElement()) { QString elementName = xml.name().toString(); if(elementName == "MD") { parseMetaDataElement(xml,userMetaData); } else xml.raiseError("unknown element in MetaData: " + elementName); } } //check for end element if(!xml.isEndElement() || (xml.name().toString() != "MetaData")) xml.raiseError("MetaData end tag not found."); } void CiftiXMLReader::parseMetaDataElement(QXmlStreamReader &xml, map &userMetaData) { QString name; QString value; QString test; bool haveName = false, haveValue = false; while (!(xml.isEndElement() && (xml.name().toString() == "MD")) && !xml.hasError()) { test = xml.name().toString(); xml.readNext(); if(xml.isStartElement()) { QString elementName = xml.name().toString(); if(elementName == "Name") { xml.readNext(); if(xml.tokenType() != QXmlStreamReader::Characters) { return; } name = xml.text().toString(); haveName = true; xml.readNext(); if(!xml.isEndElement()) xml.raiseError("End element for meta data name tag not found."); } else if(elementName == "Value") { xml.readNext(); if(xml.tokenType() != QXmlStreamReader::Characters) { return; } value = xml.text().toString(); haveValue = true; xml.readNext(); if(!xml.isEndElement()) xml.raiseError("End element for meta data value tag not found."); } else xml.raiseError("unknown element in MD: " + elementName); } } if (!haveName || !haveValue) xml.raiseError("MD element is missing name or value"); userMetaData[name] = value; if(!xml.isEndElement() || (xml.name().toString() != "MD")) xml.raiseError("End element for MD tag not found"); } void CiftiXMLReader::parseLabelTable(QXmlStreamReader &xml, std::vector &labelTable) { while (!(xml.isEndElement() && (xml.name().toString() == "LabelTable"))&& !xml.hasError()) {// && xml.name() == "Matrix") { xml.readNext(); if(xml.isStartElement()) { QString elementName = xml.name().toString(); if(elementName == "Label") { labelTable.push_back(CiftiLabelElement()); parseLabel(xml,labelTable.back()); } else xml.raiseError("unknown element in LabelTable: " + elementName); } } //check end element if(!xml.isEndElement() || (xml.name().toString() != "LabelTable")) { xml.raiseError("End element for label table not found."); } } void CiftiXMLReader::parseLabel(QXmlStreamReader &xml, CiftiLabelElement &label) { if(!(xml.name().toString() == "Label")) xml.raiseError("Error parsing Label\n"); QXmlStreamAttributes attributes = xml.attributes(); //get attribute values if(attributes.hasAttribute("Key")) label.m_key = attributes.value("Key").toString().toULongLong(); else xml.raiseError("Label does not contain Key value\n"); if(attributes.hasAttribute("Red")) label.m_red = attributes.value("Red").toString().toFloat(); else xml.raiseError("Label does not contain Red value\n"); if(attributes.hasAttribute("Green")) label.m_green = attributes.value("Green").toString().toFloat(); else xml.raiseError("Label does not contain Green value\n"); if(attributes.hasAttribute("Blue")) label.m_blue = attributes.value("Blue").toString().toFloat(); else xml.raiseError("Label does not contain Blue value\n"); if(attributes.hasAttribute("Alpha")) label.m_alpha = attributes.value("Alpha").toString().toFloat(); else xml.raiseError("Label does not contain Alpha value\n"); if(attributes.hasAttribute("X")) label.m_x = attributes.value("X").toString().toFloat(); else xml.raiseError("Label does not contain X value\n"); if(attributes.hasAttribute("Y")) label.m_y = attributes.value("Y").toString().toFloat(); else xml.raiseError("Label does not contain Y value\n"); if(attributes.hasAttribute("Z")) label.m_z = attributes.value("Z").toString().toFloat(); else xml.raiseError("Label does not contain Z value\n"); //get Label Text xml.readNext(); if(xml.tokenType() != QXmlStreamReader::Characters) { return; } label.m_text = xml.text().toString(); //get end element xml.readNext(); if(!xml.isEndElement()) { xml.raiseError("End element for label not found."); } } void CiftiXMLReader::parseMatrixIndicesMap(QXmlStreamReader &xml, CiftiMatrixIndicesMapElement &matrixIndicesMap) { QXmlStreamAttributes attributes = xml.attributes(); //get attribute values if(attributes.hasAttribute("AppliesToMatrixDimension")) { QStringList values = attributes.value("AppliesToMatrixDimension").toString().split(','); bool ok = false; for(int i = 0;ireadFromQXmlStreamReader(xml);//we need to do something to read through the label table, so give it to the parser always if (!needLabels)//if it shouldn't exist, drop it { CaretLogWarning("found label table in a scalar map, discarding"); namedMap.m_labelTable.grabNew(NULL); } haveLabelTable = true; } else if (xml.name() == "MetaData") { if (haveMetaData) { xml.raiseError("MetaData specified more than once in NamedMap"); break; } parseMetaData(xml, namedMap.m_mapMetaData); haveMetaData = true; } else { xml.raiseError("unknown element in NamedMap: " + xml.name().toString()); break; } } xml.readNext(); } if (!xml.hasError() && (!haveName)) { xml.raiseError("NamedMap element is missing MapName element"); } if (!xml.hasError() && !haveLabelTable && needLabels) { xml.raiseError("NamedMap element is missing LabelTable element while type is CIFTI_INDEX_TYPE_LABELS"); } if (!xml.hasError() && (!xml.isEndElement() || xml.name() != "NamedMap")) { xml.raiseError("unexpected element in NamedMap: " + xml.name().toString()); } } void CiftiXMLReader::parseParcel(QXmlStreamReader& xml, CiftiParcelElement& parcel) { QXmlStreamAttributes attributes = xml.attributes(); if (attributes.hasAttribute("Name")) { parcel.m_parcelName = attributes.value("Name").toString(); } else { xml.raiseError("Required attribute 'Name' missing from Parcel"); } xml.readNext(); while (!xml.hasError() && !(xml.isEndElement() && xml.name() == "Parcel")) { if (xml.isStartElement()) { if (xml.name() == "Nodes") { parcel.m_nodeElements.push_back(CiftiParcelNodesElement()); parseParcelNodes(xml, parcel.m_nodeElements.back()); } else if (xml.name() == "VoxelIndicesIJK") { xml.readNext(); if(xml.tokenType() == QXmlStreamReader::Characters) { QString voxelIndicesIJK = xml.text().toString(); QStringList list = voxelIndicesIJK.split(QRegExp("\\D+"),QString::SkipEmptyParts); if(list.count()%3) xml.raiseError("VoxelIndicesIJK has an incomplete triplet"); bool ok = true; for(int i = 0;i namespace caret { class CiftiXMLReader { CiftiVersion m_readingVersion; void parseMatrixElement(QXmlStreamReader &xml, CiftiMatrixElement &matrixElement); void parseMetaData(QXmlStreamReader &xml, std::map &metaData); void parseMetaDataElement(QXmlStreamReader &xml, std::map &userMetaData); void parseLabelTable(QXmlStreamReader &xml, std::vector &labelElement); void parseLabel(QXmlStreamReader &xml, CiftiLabelElement &label); void parseMatrixIndicesMap(QXmlStreamReader &xml, CiftiMatrixIndicesMapElement &matrixIndicesMap); void parseBrainModel(QXmlStreamReader &xml, CiftiBrainModelElement &brainModel); void parseNamedMap(QXmlStreamReader &xml, CiftiNamedMapElement &namedMap, const bool needLabels); void parseParcel(QXmlStreamReader &xml, CiftiParcelElement& parcel); void parseParcelNodes(QXmlStreamReader &xml, CiftiParcelNodesElement& parcelNodes); void parseVolume(QXmlStreamReader &xml, CiftiVolumeElement &volume); void parseTransformationMatrixVoxelIndicesIJKtoXYZ(QXmlStreamReader &xml, TransformationMatrixVoxelIndicesIJKtoXYZElement &transform); public: void parseCiftiXML(QXmlStreamReader &xml, CiftiRootElement &rootElement); }; } #endif //__CIFTI_XML_READER_H__ workbench-1.1.1/src/Cifti/CiftiXMLWriter.cxx000066400000000000000000000367321255417355300207060ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CiftiXMLWriter.h" #include "GiftiLabelTable.h" #include "PaletteColorMapping.h" using namespace caret; using namespace std; void CiftiXMLWriter::writeCiftiXML(QXmlStreamWriter &xml, const CiftiRootElement &rootElement) { m_writingVersion = CiftiVersion(1, 0);//HACK: this writer can't support other versions of XML, the structure is too closely tied to the 1.0 representation xml.setAutoFormatting(true); xml.writeStartElement("CIFTI"); xml.writeAttribute("Version", m_writingVersion.toString()); xml.writeAttribute("NumberOfMatrices",QString::number(rootElement.m_numberOfMatrices)); for(unsigned int i = 0;i metadataCopy = matrixElement.m_userMetaData;//since we may be modifying the map, we must make a copy of it if (matrixElement.m_palette != NULL) {//NULL palette means we didn't mess with palette at all if (matrixElement.m_defaultPalette && !(matrixElement.m_palette->isModified())) {//it is set to use the default palette instead of a custom palette, so remove the palette item from metadata, if it exists metadataCopy.erase("PaletteColorMapping"); } else { metadataCopy["PaletteColorMapping"] = matrixElement.m_palette->encodeInXML(); } } if(metadataCopy.size() > 0) writeMetaData(xml,metadataCopy); if(matrixElement.m_volume.size() > 0) writeVolume(xml, matrixElement.m_volume[0]); if(matrixElement.m_labelTable.size() > 0) writeLabelTable(xml, matrixElement.m_labelTable); for(unsigned int i = 0;i &metaData) { xml.writeStartElement("MetaData"); map::const_iterator i; for (i = metaData.begin(); i != metaData.end(); ++i) { writeMetaDataElement(xml,i->first,i->second); } xml.writeEndElement(); } void CiftiXMLWriter::writeMetaDataElement(QXmlStreamWriter &xml, const AString &name, const AString &value) { xml.writeStartElement("MD"); xml.writeStartElement("Name"); xml.writeCharacters(name); xml.writeEndElement();//Name xml.writeStartElement("Value"); xml.writeCharacters(value); xml.writeEndElement();//Value xml.writeEndElement();//MD } void CiftiXMLWriter::writeLabelTable(QXmlStreamWriter &xml, const std::vector &labelElement) { xml.writeStartElement("LabelTable"); for(unsigned int i=0;i0) { QString str; xml.writeAttribute("TimeStep",str.sprintf("%.10f",matrixIndicesMap.m_timeStep)); if (matrixIndicesMap.m_hasTimeStart) xml.writeAttribute("TimeStart",str.sprintf("%.10f",matrixIndicesMap.m_timeStart)); xml.writeAttribute("TimeStepUnits",timeStepUnits); } if(matrixIndicesMap.m_appliesToMatrixDimension.size()) { int lastElement = matrixIndicesMap.m_appliesToMatrixDimension.size() -1; QString appliesToMatrixDimension, str; for(int i = 0;i &ind = brainModel.m_voxelIndicesIJK; unsigned long long lastVoxelIndex = ind.size(); if(lastVoxelIndex) { xml.writeStartElement("VoxelIndicesIJK"); QString line( "%1 %2 %3\n"); if((lastVoxelIndex%3)) { std::cout << "Error writing BrainModel, invalid number of voxel indices:" << lastVoxelIndex << std::endl; return;//TODO throw exception } //else //std::cout << "voxel indices ok:" << lastVoxelIndex<< std::endl; for(unsigned int i = 0;i < lastVoxelIndex;i+=3) { xml.writeCharacters(line.arg(QString::number(ind[i]),QString::number(ind[i+1]),QString::number(ind[i+2]))); } xml.writeEndElement();//voxelIndicesIJK } xml.writeEndElement(); } void CiftiXMLWriter::writeNamedMap(QXmlStreamWriter& xml, const CiftiNamedMapElement& namedMap) { xml.writeStartElement("NamedMap"); xml.writeStartElement("MapName"); xml.writeCharacters(namedMap.m_mapName); xml.writeEndElement(); if (namedMap.m_labelTable != NULL) { namedMap.m_labelTable->writeAsXML(xml); } map metadataCopy = namedMap.m_mapMetaData;//make a copy because we may need to modify it to integrate palette if (namedMap.m_palette != NULL) {//NULL palette means we didn't mess with palette at all, leave metadata unchanged if (namedMap.m_defaultPalette && !(namedMap.m_palette->isModified())) {//it is set to use the default palette instead of a custom palette, so remove the palette item from metadata, if it exists metadataCopy.erase("PaletteColorMapping"); } else { metadataCopy["PaletteColorMapping"] = namedMap.m_palette->encodeInXML(); } } if (metadataCopy.size() != 0) { writeMetaData(xml, metadataCopy); } xml.writeEndElement(); } void CiftiXMLWriter::writeParcel(QXmlStreamWriter& xml, const CiftiParcelElement& parcel) { xml.writeStartElement("Parcel"); xml.writeAttribute("Name", parcel.m_parcelName); int numNodeElements = (int)parcel.m_nodeElements.size(); for (int i = 0; i < numNodeElements; ++i) { writeParcelNodes(xml, parcel.m_nodeElements[i]); } int numVoxInds = (int)parcel.m_voxelIndicesIJK.size(); if (numVoxInds > 0) { xml.writeStartElement("VoxelIndicesIJK"); xml.writeCharacters(AString::number(parcel.m_voxelIndicesIJK[0])); int state = 0; for (int i = 1; i < numVoxInds; ++i) { if (state >= 2) { state = 0; xml.writeCharacters("\n"); } else { ++state; xml.writeCharacters(" "); } xml.writeCharacters(AString::number(parcel.m_voxelIndicesIJK[i])); } xml.writeEndElement(); } xml.writeEndElement(); } void CiftiXMLWriter::writeParcelNodes(QXmlStreamWriter& xml, const CiftiParcelNodesElement& parcelNodes) { int numNodes = (int)parcelNodes.m_nodes.size(); if (numNodes > 0)//don't write empty elements even if they exist in the tree { xml.writeStartElement("Nodes"); xml.writeAttribute("BrainStructure", StructureEnum::toCiftiName(parcelNodes.m_structure)); xml.writeCharacters(AString::number(parcelNodes.m_nodes[0])); for (int i = 1; i < numNodes; ++i) { xml.writeCharacters(" "); xml.writeCharacters(AString::number(parcelNodes.m_nodes[i])); } xml.writeEndElement(); } } void CiftiXMLWriter::writeVolume(QXmlStreamWriter &xml, const CiftiVolumeElement &volume) { xml.writeStartElement("Volume"); QString str("%1,%2,%3"); xml.writeAttribute("VolumeDimensions", str.arg(QString::number(volume.m_volumeDimensions[0]),QString::number(volume.m_volumeDimensions[1]),QString::number(volume.m_volumeDimensions[2]))); for(unsigned int i = 0;i 0) xml.writeAttribute("DataSpace", dataSpaceString); if(transformedSpaceString.length() > 0) xml.writeAttribute("TransformedSpace", transformedSpaceString); if(unitsXYZString.length() > 0) xml.writeAttribute("UnitsXYZ", unitsXYZString); QString matrixString; for(int i = 0;i<12;i++) { matrixString += QString::number(transform.m_transform[i], 'f', 10) + " "; } for (int i = 0; i < 3; ++i)//always write 0 0 0 1, ignore the actual last row { matrixString += QString::number(0.0f, 'f', 10) + " "; } matrixString += QString::number(1.0f, 'f', 10); xml.writeCharacters(matrixString); xml.writeEndElement(); } void CiftiXMLWriter::getModelTypeString(int modelType, QString &modelTypeString) { if(modelType == CIFTI_MODEL_TYPE_SURFACE) modelTypeString = "CIFTI_MODEL_TYPE_SURFACE"; else if(modelType == CIFTI_MODEL_TYPE_VOXELS) modelTypeString = "CIFTI_MODEL_TYPE_VOXELS"; } void CiftiXMLWriter::getDataSpaceString(int dataSpace, QString &dataSpaceString) { if(dataSpace == NIFTI_XFORM_UNKNOWN) dataSpaceString = "NIFTI_XFORM_UNKNOWN"; else if(dataSpace == NIFTI_XFORM_SCANNER_ANAT) dataSpaceString = "NIFTI_XFORM_SCANNER_ANAT"; else if(dataSpace == NIFTI_XFORM_ALIGNED_ANAT) dataSpaceString = "NIFTI_XFORM_ALIGNED_ANAT"; else if(dataSpace == NIFTI_XFORM_TALAIRACH) dataSpaceString = "NIFTI_XFORM_TALAIRACH"; else if(dataSpace == NIFTI_XFORM_MNI_152) dataSpaceString = "NIFTI_XFORM_MNI_152"; } void CiftiXMLWriter::getUnitsXYZString(int unitsXYZ, QString &unitsXYZString) { if(unitsXYZ == NIFTI_UNITS_MM) unitsXYZString = "NIFTI_UNITS_MM"; else if(unitsXYZ == NIFTI_UNITS_MICRON) unitsXYZString = "NIFTI_UNITS_MICRON"; } workbench-1.1.1/src/Cifti/CiftiXMLWriter.h000066400000000000000000000050341255417355300203220ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #ifndef __CIFTI_XML_WRITER_H__ #define __CIFTI_XML_WRITER_H__ #include #include "CiftiXMLElements.h" namespace caret { class CiftiXMLWriter { CiftiVersion m_writingVersion; void writeMatrixElement(QXmlStreamWriter &xml, const CiftiMatrixElement &matrixElement); void writeMetaData(QXmlStreamWriter &xml, const std::map &metaData); void writeMetaDataElement(QXmlStreamWriter &xml, const AString &name, const AString &value); void writeLabelTable(QXmlStreamWriter &xml, const std::vector &labelElement); void writeLabel(QXmlStreamWriter &xml, const CiftiLabelElement &label); void writeMatrixIndicesMap(QXmlStreamWriter &xml, const CiftiMatrixIndicesMapElement &matrixIndicesMap); void writeBrainModel(QXmlStreamWriter &xml, const CiftiBrainModelElement &brainModel); void writeNamedMap(QXmlStreamWriter& xml, const CiftiNamedMapElement& namedMap); void writeParcel(QXmlStreamWriter& xml, const CiftiParcelElement& parcel); void writeParcelNodes(QXmlStreamWriter& xml, const CiftiParcelNodesElement& parcelNodes); void writeVolume(QXmlStreamWriter &xml, const CiftiVolumeElement &volume); void writeTransformationMatrixVoxelIndicesIJKtoXYZ(QXmlStreamWriter &xml, const TransformationMatrixVoxelIndicesIJKtoXYZElement &transform); void getModelTypeString(int modelType, QString &modelTypeString); void getDataSpaceString(int dataSpace, QString &dataSpaceString); void getUnitsXYZString(int UnitsXYZ, QString &unitsXYZString); public: void writeCiftiXML(QXmlStreamWriter &xml, const CiftiRootElement &rootElement); }; } #endif //__CIFTI_XML_WRITER_H__ workbench-1.1.1/src/Cifti/Doxyfile000066400000000000000000002045441255417355300170520ustar00rootroot00000000000000# Doxyfile 1.7.1 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project # # All text after a hash (#) is considered a comment and will be ignored # The format is: # TAG = value [value, ...] # For lists items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (" ") #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the config file # that follow. The default is UTF-8 which is also the encoding used for all # text before the first occurrence of this tag. Doxygen uses libiconv (or the # iconv built into libc) for the transcoding. See # http://www.gnu.org/software/libiconv for the list of possible encodings. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or a sequence of words surrounded # by quotes) that should identify the project. PROJECT_NAME = Cifti # The PROJECT_NUMBER tag can be used to enter a project or revision number. # This could be handy for archiving the generated documentation or # if some version control system is used. PROJECT_NUMBER = # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) # base path where the generated documentation will be put. # If a relative path is entered, it will be relative to the location # where doxygen was started. If left blank the current directory will be used. OUTPUT_DIRECTORY = ./doxy # If the CREATE_SUBDIRS tag is set to YES, then doxygen will create # 4096 sub-directories (in 2 levels) under the output directory of each output # format and will distribute the generated files over these directories. # Enabling this option can be useful when feeding doxygen a huge amount of # source files, where putting all generated files in the same directory would # otherwise cause performance problems for the file system. CREATE_SUBDIRS = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # The default language is English, other supported languages are: # Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, # Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, # Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English # messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, # Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, # Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will # include brief member descriptions after the members that are listed in # the file and class documentation (similar to JavaDoc). # Set to NO to disable this. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend # the brief description of a member or function before the detailed description. # Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator # that is used to form the text in various listings. Each string # in this list, if found as the leading text of the brief description, will be # stripped from the text and the result after processing the whole list, is # used as the annotated text. Otherwise, the brief description is used as-is. # If left blank, the following values are used ("$name" is automatically # replaced with the name of the entity): "The $name class" "The $name widget" # "The $name file" "is" "provides" "specifies" "contains" # "represents" "a" "an" "the" ABBREVIATE_BRIEF = # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # Doxygen will generate a detailed section even if there is only a brief # description. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full # path before files name in the file list and in the header files. If set # to NO the shortest path that makes the file name unique will be used. FULL_PATH_NAMES = YES # If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag # can be used to strip a user-defined part of the path. Stripping is # only done if one of the specified strings matches the left-hand part of # the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the # path to strip. STRIP_FROM_PATH = # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of # the path mentioned in the documentation of a class, which tells # the reader which header file to include in order to use a class. # If left blank only the name of the header file containing the class # definition is used. Otherwise one should specify the include paths that # are normally passed to the compiler using the -I flag. STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter # (but less readable) file names. This can be useful is your file systems # doesn't support long names like on DOS, Mac, or CD-ROM. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen # will interpret the first line (until the first dot) of a JavaDoc-style # comment as the brief description. If set to NO, the JavaDoc # comments will behave just like regular Qt-style comments # (thus requiring an explicit @brief command for a brief description.) JAVADOC_AUTOBRIEF = NO # If the QT_AUTOBRIEF tag is set to YES then Doxygen will # interpret the first line (until the first dot) of a Qt-style # comment as the brief description. If set to NO, the comments # will behave just like regular Qt-style comments (thus requiring # an explicit \brief command for a brief description.) QT_AUTOBRIEF = NO # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen # treat a multi-line C++ special comment block (i.e. a block of //! or /// # comments) as a brief description. This used to be the default behaviour. # The new default is to treat a multi-line C++ comment block as a detailed # description. Set this tag to YES if you prefer the old behaviour instead. MULTILINE_CPP_IS_BRIEF = NO # If the INHERIT_DOCS tag is set to YES (the default) then an undocumented # member inherits the documentation from any documented member that it # re-implements. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce # a new page for each member. If set to NO, the documentation of a member will # be part of the file/class/namespace that contains it. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. # Doxygen uses this value to replace tabs by spaces in code fragments. TAB_SIZE = 8 # This tag can be used to specify a number of aliases that acts # as commands in the documentation. An alias has the form "name=value". # For example adding "sideeffect=\par Side Effects:\n" will allow you to # put the command \sideeffect (or @sideeffect) in the documentation, which # will result in a user-defined paragraph with heading "Side Effects:". # You can put \n's in the value part of an alias to insert newlines. ALIASES = # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C # sources only. Doxygen will then generate output that is more tailored for C. # For instance, some of the names that are used will be different. The list # of all members will be omitted, etc. OPTIMIZE_OUTPUT_FOR_C = NO # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java # sources only. Doxygen will then generate output that is more tailored for # Java. For instance, namespaces will be presented as packages, qualified # scopes will look different, etc. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources only. Doxygen will then generate output that is more tailored for # Fortran. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL # sources. Doxygen will then generate output that is tailored for # VHDL. OPTIMIZE_OUTPUT_VHDL = NO # Doxygen selects the parser to use depending on the extension of the files it # parses. With this tag you can assign which parser to use for a given extension. # Doxygen has a built-in mapping, but you can override or extend it using this # tag. The format is ext=language, where ext is a file extension, and language # is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C, # C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make # doxygen treat .inc files as Fortran files (default is PHP), and .f files as C # (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions # you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. EXTENSION_MAPPING = # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should # set this tag to YES in order to let doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); v.s. # func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. BUILTIN_STL_SUPPORT = YES # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. # Doxygen will parse them like normal C++ but will assume all classes use public # instead of private inheritance when no explicit protection keyword is present. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate getter # and setter methods for a property. Setting this option to YES (the default) # will make doxygen to replace the get and set methods by a property in the # documentation. This will only work if the methods are indeed getting or # setting a simple type. If this is not the case, or you want to show the # methods anyway, you should set this option to NO. IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES, then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. DISTRIBUTE_GROUP_DOC = NO # Set the SUBGROUPING tag to YES (the default) to allow class member groups of # the same type (for instance a group of public functions) to be put as a # subgroup of that type (e.g. under the Public Functions section). Set it to # NO to prevent subgrouping. Alternatively, this can be done per class using # the \nosubgrouping command. SUBGROUPING = YES # When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum # is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, # namespace, or class. And the struct will be named TypeS. This can typically # be useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. TYPEDEF_HIDES_STRUCT = NO # The SYMBOL_CACHE_SIZE determines the size of the internal cache use to # determine which symbols to keep in memory and which to flush to disk. # When the cache is full, less often used symbols will be written to disk. # For small to medium size projects (<1000 input files) the default value is # probably good enough. For larger projects a too small cache size can cause # doxygen to be busy swapping symbols to and from disk most of the time # causing a significant performance penality. # If the system has enough physical memory increasing the cache will improve the # performance by keeping more symbols in memory. Note that the value works on # a logarithmic scale so increasing the size by one will rougly double the # memory usage. The cache size is given by this formula: # 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, # corresponding to a cache size of 2^16 = 65536 symbols SYMBOL_CACHE_SIZE = 0 #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in # documentation are documented, even if no documentation was available. # Private class members and static file members will be hidden unless # the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES EXTRACT_ALL = NO # If the EXTRACT_PRIVATE tag is set to YES all private members of a class # will be included in the documentation. EXTRACT_PRIVATE = NO # If the EXTRACT_STATIC tag is set to YES all static members of a file # will be included in the documentation. EXTRACT_STATIC = NO # If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) # defined locally in source files will be included in the documentation. # If set to NO only classes defined in header files are included. EXTRACT_LOCAL_CLASSES = NO # This flag is only useful for Objective-C code. When set to YES local # methods, which are defined in the implementation section but not in # the interface are included in the documentation. # If set to NO (the default) only methods in the interface are included. EXTRACT_LOCAL_METHODS = NO # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called # 'anonymous_namespace{file}', where file will be replaced with the base # name of the file that contains the anonymous namespace. By default # anonymous namespace are hidden. EXTRACT_ANON_NSPACES = NO # If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all # undocumented members of documented classes, files or namespaces. # If set to NO (the default) these members will be included in the # various overviews, but no documentation section is generated. # This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_MEMBERS = YES # If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. # If set to NO (the default) these classes will be included in the various # overviews. This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_CLASSES = YES # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all # friend (class|struct|union) declarations. # If set to NO (the default) these declarations will be included in the # documentation. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any # documentation blocks found inside the body of a function. # If set to NO (the default) these blocks will be appended to the # function's detailed documentation block. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation # that is typed after a \internal command is included. If the tag is set # to NO (the default) then the documentation will be excluded. # Set it to YES to include the internal documentation. INTERNAL_DOCS = NO # If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate # file names in lower-case letters. If set to YES upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. CASE_SENSE_NAMES = YES # If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen # will show members with their full class and namespace scopes in the # documentation. If set to YES the scope will be hidden. HIDE_SCOPE_NAMES = NO # If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen # will put a list of the files that are included by a file in the documentation # of that file. SHOW_INCLUDE_FILES = YES # If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen # will list include files with double quotes in the documentation # rather than with sharp brackets. FORCE_LOCAL_INCLUDES = NO # If the INLINE_INFO tag is set to YES (the default) then a tag [inline] # is inserted in the documentation for inline members. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen # will sort the (detailed) documentation of file and class members # alphabetically by member name. If set to NO the members will appear in # declaration order. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the # brief documentation of file, namespace and class members alphabetically # by member name. If set to NO (the default) the members will appear in # declaration order. SORT_BRIEF_DOCS = NO # If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen # will sort the (brief and detailed) documentation of class members so that # constructors and destructors are listed first. If set to NO (the default) # the constructors will appear in the respective orders defined by # SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. # This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO # and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. SORT_MEMBERS_CTORS_1ST = NO # If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the # hierarchy of group names into alphabetical order. If set to NO (the default) # the group names will appear in their defined order. SORT_GROUP_NAMES = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be # sorted by fully-qualified names, including namespaces. If set to # NO (the default), the class list will be sorted only by class name, # not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the # alphabetical list. SORT_BY_SCOPE_NAME = NO # The GENERATE_TODOLIST tag can be used to enable (YES) or # disable (NO) the todo list. This list is created by putting \todo # commands in the documentation. GENERATE_TODOLIST = YES # The GENERATE_TESTLIST tag can be used to enable (YES) or # disable (NO) the test list. This list is created by putting \test # commands in the documentation. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable (YES) or # disable (NO) the bug list. This list is created by putting \bug # commands in the documentation. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or # disable (NO) the deprecated list. This list is created by putting # \deprecated commands in the documentation. GENERATE_DEPRECATEDLIST= YES # The ENABLED_SECTIONS tag can be used to enable conditional # documentation sections, marked by \if sectionname ... \endif. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines # the initial value of a variable or define consists of for it to appear in # the documentation. If the initializer consists of more lines than specified # here it will be hidden. Use a value of 0 to hide initializers completely. # The appearance of the initializer of individual variables and defines in the # documentation can be controlled using \showinitializer or \hideinitializer # command in the documentation regardless of this setting. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated # at the bottom of the documentation of classes and structs. If set to YES the # list will mention the files that were used to generate the documentation. SHOW_USED_FILES = YES # If the sources in your project are distributed over multiple directories # then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy # in the documentation. The default is NO. SHOW_DIRECTORIES = NO # Set the SHOW_FILES tag to NO to disable the generation of the Files page. # This will remove the Files entry from the Quick Index and from the # Folder Tree View (if specified). The default is YES. SHOW_FILES = YES # Set the SHOW_NAMESPACES tag to NO to disable the generation of the # Namespaces page. # This will remove the Namespaces entry from the Quick Index # and from the Folder Tree View (if specified). The default is YES. SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command , where is the value of # the FILE_VERSION_FILTER tag, and is the name of an input file # provided by doxygen. Whatever the program writes to standard output # is used as the file version. See the manual for examples. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed # by doxygen. The layout file controls the global structure of the generated # output files in an output format independent way. The create the layout file # that represents doxygen's defaults, run doxygen with the -l option. # You can optionally specify a file name after the option, if omitted # DoxygenLayout.xml will be used as the name of the layout file. LAYOUT_FILE = #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated # by doxygen. Possible values are YES and NO. If left blank NO is used. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are # generated by doxygen. Possible values are YES and NO. If left blank # NO is used. WARNINGS = YES # If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings # for undocumented members. If EXTRACT_ALL is set to YES then this flag will # automatically be disabled. WARN_IF_UNDOCUMENTED = YES # If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some # parameters in a documented function, or documenting parameters that # don't exist or using markup commands wrongly. WARN_IF_DOC_ERROR = YES # This WARN_NO_PARAMDOC option can be abled to get warnings for # functions that are documented, but have no documentation for their parameters # or return value. If set to NO (the default) doxygen will only warn about # wrong or incomplete parameter documentation, but not about the absence of # documentation. WARN_NO_PARAMDOC = NO # The WARN_FORMAT tag determines the format of the warning messages that # doxygen can produce. The string should contain the $file, $line, and $text # tags, which will be replaced by the file and line number from which the # warning originated and the warning text. Optionally the format may contain # $version, which will be replaced by the version of the file (if it could # be obtained via FILE_VERSION_FILTER) WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning # and error messages should be written. If left blank the output is written # to stderr. WARN_LOGFILE = #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag can be used to specify the files and/or directories that contain # documented source files. You may enter file names like "myfile.cpp" or # directories like "/usr/src/myproject". Separate the files or directories # with spaces. INPUT = . # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is # also the default input encoding. Doxygen uses libiconv (or the iconv built # into libc) for the transcoding. See http://www.gnu.org/software/libiconv for # the list of possible encodings. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank the following patterns are tested: # *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx # *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 FILE_PATTERNS = *.h \ *.cxx # The RECURSIVE tag can be used to turn specify whether or not subdirectories # should be searched for input files as well. Possible values are YES and NO. # If left blank NO is used. RECURSIVE = NO # The EXCLUDE tag can be used to specify files and/or directories that should # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used select whether or not files or # directories that are symbolic links (a Unix filesystem feature) are excluded # from the input. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. Note that the wildcards are matched # against the file with absolute path, so to exclude all test directories # for example use the pattern */test/* EXCLUDE_PATTERNS = # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test EXCLUDE_SYMBOLS = # The EXAMPLE_PATH tag can be used to specify one or more files or # directories that contain example code fragments that are included (see # the \include command). EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank all files are included. EXAMPLE_PATTERNS = # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude # commands irrespective of the value of the RECURSIVE tag. # Possible values are YES and NO. If left blank NO is used. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or # directories that contain image that are included in the documentation (see # the \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command , where # is the value of the INPUT_FILTER tag, and is the name of an # input file. Doxygen will then use the output that the filter program writes # to standard output. # If FILTER_PATTERNS is specified, this tag will be # ignored. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. # Doxygen will compare the file name with each pattern and apply the # filter if there is a match. # The filters are a list of the form: # pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further # info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER # is applied to all files. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will be used to filter the input files when producing source # files to browse (i.e. when SOURCE_BROWSER is set to YES). FILTER_SOURCE_FILES = NO #--------------------------------------------------------------------------- # configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will # be generated. Documented entities will be cross-referenced with these sources. # Note: To get rid of all source code in the generated output, make sure also # VERBATIM_HEADERS is set to NO. SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body # of functions and classes directly in the documentation. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct # doxygen to hide any special comment blocks from generated source code # fragments. Normal C and C++ comments will always remain visible. STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES # then for each documented function all documented # functions referencing it will be listed. REFERENCED_BY_RELATION = NO # If the REFERENCES_RELATION tag is set to YES # then for each documented function all documented entities # called/used by that function will be listed. REFERENCES_RELATION = NO # If the REFERENCES_LINK_SOURCE tag is set to YES (the default) # and SOURCE_BROWSER tag is set to YES, then the hyperlinks from # functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will # link to the source code. # Otherwise they will link to the documentation. REFERENCES_LINK_SOURCE = YES # If the USE_HTAGS tag is set to YES then the references to source code # will point to the HTML generated by the htags(1) tool instead of doxygen # built-in source browser. The htags tool is part of GNU's global source # tagging system (see http://www.gnu.org/software/global/global.html). You # will need version 4.8.6 or higher. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen # will generate a verbatim copy of the header file for each class for # which an include is specified. Set to NO to disable this. VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- # configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index # of all compounds will be generated. Enable this if the project # contains a lot of classes, structs, unions or interfaces. ALPHABETICAL_INDEX = YES # If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then # the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns # in which this list will be split (can be a number in the range [1..20]) COLS_IN_ALPHA_INDEX = 5 # In case all classes in a project start with a common prefix, all # classes will be put under the same header in the alphabetical index. # The IGNORE_PREFIX tag can be used to specify one or more prefixes that # should be ignored while generating the index headers. IGNORE_PREFIX = #--------------------------------------------------------------------------- # configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES (the default) Doxygen will # generate HTML output. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `html' will be used as the default path. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for # each generated HTML page (for example: .htm,.php,.asp). If it is left blank # doxygen will generate files with .html extension. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a personal HTML header for # each generated HTML page. If it is left blank doxygen will generate a # standard header. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a personal HTML footer for # each generated HTML page. If it is left blank doxygen will generate a # standard footer. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading # style sheet that is used by each HTML page. It can be used to # fine-tune the look of the HTML output. If the tag is left blank doxygen # will generate a default style sheet. Note that doxygen will try to copy # the style sheet file to the HTML output directory, so don't put your own # stylesheet in the HTML output directory as well, or it will be erased! HTML_STYLESHEET = # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. # Doxygen will adjust the colors in the stylesheet and background images # according to this color. Hue is specified as an angle on a colorwheel, # see http://en.wikipedia.org/wiki/Hue for more information. # For instance the value 0 represents red, 60 is yellow, 120 is green, # 180 is cyan, 240 is blue, 300 purple, and 360 is red again. # The allowed range is 0 to 359. HTML_COLORSTYLE_HUE = 220 # The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of # the colors in the HTML output. For a value of 0 the output will use # grayscales only. A value of 255 will produce the most vivid colors. HTML_COLORSTYLE_SAT = 100 # The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to # the luminance component of the colors in the HTML output. Values below # 100 gradually make the output lighter, whereas values above 100 make # the output darker. The value divided by 100 is the actual gamma applied, # so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, # and 100 does not change the gamma. HTML_COLORSTYLE_GAMMA = 80 # If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML # page will contain the date and time when the page was generated. Setting # this to NO can help when comparing the output of multiple runs. HTML_TIMESTAMP = YES # If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, # files or namespaces will be aligned in HTML using tables. If set to # NO a bullet list will be used. HTML_ALIGN_MEMBERS = YES # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. For this to work a browser that supports # JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox # Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). HTML_DYNAMIC_SECTIONS = NO # If the GENERATE_DOCSET tag is set to YES, additional index files # will be generated that can be used as input for Apple's Xcode 3 # integrated development environment, introduced with OSX 10.5 (Leopard). # To create a documentation set, doxygen will generate a Makefile in the # HTML output directory. Running make will produce the docset in that # directory and running "make install" will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find # it at startup. # See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html # for more information. GENERATE_DOCSET = NO # When GENERATE_DOCSET tag is set to YES, this tag determines the name of the # feed. A documentation feed provides an umbrella under which multiple # documentation sets from a single provider (such as a company or product suite) # can be grouped. DOCSET_FEEDNAME = "Doxygen generated docs" # When GENERATE_DOCSET tag is set to YES, this tag specifies a string that # should uniquely identify the documentation set bundle. This should be a # reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen # will append .docset to the name. DOCSET_BUNDLE_ID = org.doxygen.Project # When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify # the documentation publisher. This should be a reverse domain-name style # string, e.g. com.mycompany.MyDocSet.documentation. DOCSET_PUBLISHER_ID = org.doxygen.Publisher # The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. DOCSET_PUBLISHER_NAME = Publisher # If the GENERATE_HTMLHELP tag is set to YES, additional index files # will be generated that can be used as input for tools like the # Microsoft HTML help workshop to generate a compiled HTML help file (.chm) # of the generated HTML documentation. GENERATE_HTMLHELP = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can # be used to specify the file name of the resulting .chm file. You # can add a path in front of the file if the result should not be # written to the html output directory. CHM_FILE = # If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can # be used to specify the location (absolute path including file name) of # the HTML help compiler (hhc.exe). If non-empty doxygen will try to run # the HTML help compiler on the generated index.hhp. HHC_LOCATION = # If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag # controls if a separate .chi index file is generated (YES) or that # it should be included in the master .chm file (NO). GENERATE_CHI = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING # is used to encode HtmlHelp index (hhk), content (hhc) and project file # content. CHM_INDEX_ENCODING = # If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag # controls whether a binary table of contents is generated (YES) or a # normal table of contents (NO) in the .chm file. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members # to the contents of the HTML help documentation and to the tree view. TOC_EXPAND = NO # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and # QHP_VIRTUAL_FOLDER are set, an additional index file will be generated # that can be used as input for Qt's qhelpgenerator to generate a # Qt Compressed Help (.qch) of the generated HTML documentation. GENERATE_QHP = NO # If the QHG_LOCATION tag is specified, the QCH_FILE tag can # be used to specify the file name of the resulting .qch file. # The path specified is relative to the HTML output folder. QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating # Qt Help Project output. For more information please see # http://doc.trolltech.com/qthelpproject.html#namespace QHP_NAMESPACE = org.doxygen.Project # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating # Qt Help Project output. For more information please see # http://doc.trolltech.com/qthelpproject.html#virtual-folders QHP_VIRTUAL_FOLDER = doc # If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to # add. For more information please see # http://doc.trolltech.com/qthelpproject.html#custom-filters QHP_CUST_FILTER_NAME = # The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see # # Qt Help Project / Custom Filters. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's # filter section matches. # # Qt Help Project / Filter Attributes. QHP_SECT_FILTER_ATTRS = # If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can # be used to specify the location of Qt's qhelpgenerator. # If non-empty doxygen will try to run qhelpgenerator on the generated # .qhp file. QHG_LOCATION = # If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files # will be generated, which together with the HTML files, form an Eclipse help # plugin. To install this plugin and make it available under the help contents # menu in Eclipse, the contents of the directory containing the HTML and XML # files needs to be copied into the plugins directory of eclipse. The name of # the directory within the plugins directory should be the same as # the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before # the help appears. GENERATE_ECLIPSEHELP = NO # A unique identifier for the eclipse help plugin. When installing the plugin # the directory name containing the HTML and XML files should also have # this name. ECLIPSE_DOC_ID = org.doxygen.Project # The DISABLE_INDEX tag can be used to turn on/off the condensed index at # top of each HTML page. The value NO (the default) enables the index and # the value YES disables it. DISABLE_INDEX = NO # This tag can be used to set the number of enum values (range [1..20]) # that doxygen will group on one line in the generated HTML documentation. ENUM_VALUES_PER_LINE = 4 # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. # If the tag value is set to YES, a side panel will be generated # containing a tree-like index structure (just like the one that # is generated for HTML Help). For this to work a browser that supports # JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). # Windows users are probably better off using the HTML help feature. GENERATE_TREEVIEW = NO # By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories, # and Class Hierarchy pages using a tree view instead of an ordered list. USE_INLINE_TREES = NO # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be # used to set the initial width (in pixels) of the frame in which the tree # is shown. TREEVIEW_WIDTH = 250 # When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open # links to external symbols imported via tag files in a separate window. EXT_LINKS_IN_WINDOW = NO # Use this tag to change the font size of Latex formulas included # as images in the HTML documentation. The default is 10. Note that # when you change the font size after a successful doxygen run you need # to manually remove any form_*.png images from the HTML output directory # to force them to be regenerated. FORMULA_FONTSIZE = 10 # Use the FORMULA_TRANPARENT tag to determine whether or not the images # generated for formulas are transparent PNGs. Transparent PNGs are # not supported properly for IE 6.0, but are supported on all modern browsers. # Note that when changing this option you need to delete any form_*.png files # in the HTML output before the changes have effect. FORMULA_TRANSPARENT = YES # When the SEARCHENGINE tag is enabled doxygen will generate a search box # for the HTML output. The underlying search engine uses javascript # and DHTML and should work on any modern browser. Note that when using # HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets # (GENERATE_DOCSET) there is already a search function so this one should # typically be disabled. For large projects the javascript based search engine # can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. SEARCHENGINE = YES # When the SERVER_BASED_SEARCH tag is enabled the search engine will be # implemented using a PHP enabled web server instead of at the web client # using Javascript. Doxygen will generate the search PHP script and index # file to put on the web server. The advantage of the server # based approach is that it scales better to large projects and allows # full text search. The disadvances is that it is more difficult to setup # and does not have live searching capabilities. SERVER_BASED_SEARCH = NO #--------------------------------------------------------------------------- # configuration options related to the LaTeX output #--------------------------------------------------------------------------- # If the GENERATE_LATEX tag is set to YES (the default) Doxygen will # generate Latex output. GENERATE_LATEX = NO # The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `latex' will be used as the default path. LATEX_OUTPUT = latex # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be # invoked. If left blank `latex' will be used as the default command name. # Note that when enabling USE_PDFLATEX this option is only used for # generating bitmaps for formulas in the HTML output, but not in the # Makefile that is written to the output directory. LATEX_CMD_NAME = latex # The MAKEINDEX_CMD_NAME tag can be used to specify the command name to # generate index for LaTeX. If left blank `makeindex' will be used as the # default command name. MAKEINDEX_CMD_NAME = makeindex # If the COMPACT_LATEX tag is set to YES Doxygen generates more compact # LaTeX documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_LATEX = NO # The PAPER_TYPE tag can be used to set the paper type that is used # by the printer. Possible values are: a4, a4wide, letter, legal and # executive. If left blank a4wide will be used. PAPER_TYPE = a4wide # The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX # packages that should be included in the LaTeX output. EXTRA_PACKAGES = # The LATEX_HEADER tag can be used to specify a personal LaTeX header for # the generated latex document. The header should contain everything until # the first chapter. If it is left blank doxygen will generate a # standard header. Notice: only use this tag if you know what you are doing! LATEX_HEADER = # If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated # is prepared for conversion to pdf (using ps2pdf). The pdf file will # contain links (just like the HTML output) instead of page references # This makes the output suitable for online browsing using a pdf viewer. PDF_HYPERLINKS = YES # If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of # plain latex in the generated Makefile. Set this option to YES to get a # higher quality PDF documentation. USE_PDFLATEX = YES # If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. # command to the generated LaTeX files. This will instruct LaTeX to keep # running if errors occur, instead of asking the user for help. # This option is also used when generating formulas in HTML. LATEX_BATCHMODE = NO # If LATEX_HIDE_INDICES is set to YES then doxygen will not # include the index chapters (such as File Index, Compound Index, etc.) # in the output. LATEX_HIDE_INDICES = NO # If LATEX_SOURCE_CODE is set to YES then doxygen will include # source code with syntax highlighting in the LaTeX output. # Note that which sources are shown also depends on other settings # such as SOURCE_BROWSER. LATEX_SOURCE_CODE = NO #--------------------------------------------------------------------------- # configuration options related to the RTF output #--------------------------------------------------------------------------- # If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output # The RTF output is optimized for Word 97 and may not look very pretty with # other RTF readers or editors. GENERATE_RTF = NO # The RTF_OUTPUT tag is used to specify where the RTF docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `rtf' will be used as the default path. RTF_OUTPUT = rtf # If the COMPACT_RTF tag is set to YES Doxygen generates more compact # RTF documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_RTF = NO # If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated # will contain hyperlink fields. The RTF file will # contain links (just like the HTML output) instead of page references. # This makes the output suitable for online browsing using WORD or other # programs which support those fields. # Note: wordpad (write) and others do not support links. RTF_HYPERLINKS = NO # Load stylesheet definitions from file. Syntax is similar to doxygen's # config file, i.e. a series of assignments. You only have to provide # replacements, missing definitions are set to their default value. RTF_STYLESHEET_FILE = # Set optional variables used in the generation of an rtf document. # Syntax is similar to doxygen's config file. RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # configuration options related to the man page output #--------------------------------------------------------------------------- # If the GENERATE_MAN tag is set to YES (the default) Doxygen will # generate man pages GENERATE_MAN = YES # The MAN_OUTPUT tag is used to specify where the man pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `man' will be used as the default path. MAN_OUTPUT = man # The MAN_EXTENSION tag determines the extension that is added to # the generated man pages (default is the subroutine's section .3) MAN_EXTENSION = .3 # If the MAN_LINKS tag is set to YES and Doxygen generates man output, # then it will generate one additional man file for each entity # documented in the real man page(s). These additional files # only source the real man page, but without them the man command # would be unable to find the correct page. The default is NO. MAN_LINKS = NO #--------------------------------------------------------------------------- # configuration options related to the XML output #--------------------------------------------------------------------------- # If the GENERATE_XML tag is set to YES Doxygen will # generate an XML file that captures the structure of # the code including all documentation. GENERATE_XML = NO # The XML_OUTPUT tag is used to specify where the XML pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `xml' will be used as the default path. XML_OUTPUT = xml # The XML_SCHEMA tag can be used to specify an XML schema, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_SCHEMA = # The XML_DTD tag can be used to specify an XML DTD, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_DTD = # If the XML_PROGRAMLISTING tag is set to YES Doxygen will # dump the program listings (including syntax highlighting # and cross-referencing information) to the XML output. Note that # enabling this will significantly increase the size of the XML output. XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- # configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will # generate an AutoGen Definitions (see autogen.sf.net) file # that captures the structure of the code including all # documentation. Note that this feature is still experimental # and incomplete at the moment. GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- # configuration options related to the Perl module output #--------------------------------------------------------------------------- # If the GENERATE_PERLMOD tag is set to YES Doxygen will # generate a Perl module file that captures the structure of # the code including all documentation. Note that this # feature is still experimental and incomplete at the # moment. GENERATE_PERLMOD = NO # If the PERLMOD_LATEX tag is set to YES Doxygen will generate # the necessary Makefile rules, Perl scripts and LaTeX code to be able # to generate PDF and DVI output from the Perl module output. PERLMOD_LATEX = NO # If the PERLMOD_PRETTY tag is set to YES the Perl module output will be # nicely formatted so it can be parsed by a human reader. # This is useful # if you want to understand what is going on. # On the other hand, if this # tag is set to NO the size of the Perl module output will be much smaller # and Perl will parse it just the same. PERLMOD_PRETTY = YES # The names of the make variables in the generated doxyrules.make file # are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. # This is useful so different doxyrules.make files included by the same # Makefile don't overwrite each other's variables. PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- # If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will # evaluate all C-preprocessor directives found in the sources and include # files. ENABLE_PREPROCESSING = YES # If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro # names in the source code. If set to NO (the default) only conditional # compilation will be performed. Macro expansion can be done in a controlled # way by setting EXPAND_ONLY_PREDEF to YES. MACRO_EXPANSION = NO # If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES # then the macro expansion is limited to the macros specified with the # PREDEFINED and EXPAND_AS_DEFINED tags. EXPAND_ONLY_PREDEF = NO # If the SEARCH_INCLUDES tag is set to YES (the default) the includes files # in the INCLUDE_PATH (see below) will be search if a #include is found. SEARCH_INCLUDES = YES # The INCLUDE_PATH tag can be used to specify one or more directories that # contain include files that are not input files but should be processed by # the preprocessor. INCLUDE_PATH = # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # patterns (like *.h and *.hpp) to filter out the header-files in the # directories. If left blank, the patterns specified with FILE_PATTERNS will # be used. INCLUDE_FILE_PATTERNS = # The PREDEFINED tag can be used to specify one or more macro names that # are defined before the preprocessor is started (similar to the -D option of # gcc). The argument of the tag is a list of macros of the form: name # or name=definition (no spaces). If the definition and the = are # omitted =1 is assumed. To prevent a macro definition from being # undefined via #undef or recursively expanded use the := operator # instead of the = operator. PREDEFINED = # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then # this tag can be used to specify a list of macro names that should be expanded. # The macro definition that is found in the sources will be used. # Use the PREDEFINED tag if you want to use a different macro definition. EXPAND_AS_DEFINED = # If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then # doxygen's preprocessor will remove all function-like macros that are alone # on a line, have an all uppercase name, and do not end with a semicolon. Such # function macros are typically used for boiler-plate code, and will confuse # the parser if not removed. SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- # Configuration::additions related to external references #--------------------------------------------------------------------------- # The TAGFILES option can be used to specify one or more tagfiles. # Optionally an initial location of the external documentation # can be added for each tagfile. The format of a tag file without # this location is as follows: # # TAGFILES = file1 file2 ... # Adding location for the tag files is done as follows: # # TAGFILES = file1=loc1 "file2 = loc2" ... # where "loc1" and "loc2" can be relative or absolute paths or # URLs. If a location is present for each tag, the installdox tool # does not have to be run to correct the links. # Note that each tag file must have a unique name # (where the name does NOT include the path) # If a tag file is not located in the directory in which doxygen # is run, you must also specify the path to the tagfile here. TAGFILES = # When a file name is specified after GENERATE_TAGFILE, doxygen will create # a tag file that is based on the input files it reads. GENERATE_TAGFILE = # If the ALLEXTERNALS tag is set to YES all external classes will be listed # in the class index. If set to NO only the inherited external classes # will be listed. ALLEXTERNALS = NO # If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed # in the modules index. If set to NO, only the current project's groups will # be listed. EXTERNAL_GROUPS = YES # The PERL_PATH should be the absolute path and name of the perl script # interpreter (i.e. the result of `which perl'). PERL_PATH = /usr/bin/perl #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- # If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will # generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base # or super classes. Setting the tag to NO turns the diagrams off. Note that # this option is superseded by the HAVE_DOT option below. This is only a # fallback. It is recommended to install and use dot, since it yields more # powerful graphs. CLASS_DIAGRAMS = YES # You can define message sequence charts within doxygen comments using the \msc # command. Doxygen will then run the mscgen tool (see # http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the # documentation. The MSCGEN_PATH tag allows you to specify the directory where # the mscgen tool resides. If left empty the tool is assumed to be found in the # default search path. MSCGEN_PATH = # If set to YES, the inheritance and collaboration graphs will hide # inheritance and usage relations if the target is undocumented # or is not a class. HIDE_UNDOC_RELATIONS = YES # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz, a graph visualization # toolkit from AT&T and Lucent Bell Labs. The other options in this section # have no effect if this option is set to NO (the default) HAVE_DOT = YES # The DOT_NUM_THREADS specifies the number of dot invocations doxygen is # allowed to run in parallel. When set to 0 (the default) doxygen will # base this on the number of processors available in the system. You can set it # explicitly to a value larger than 0 to get control over the balance # between CPU load and processing speed. DOT_NUM_THREADS = 0 # By default doxygen will write a font called FreeSans.ttf to the output # directory and reference it in all dot files that doxygen generates. This # font does not include all possible unicode characters however, so when you need # these (or just want a differently looking font) you can specify the font name # using DOT_FONTNAME. You need need to make sure dot is able to find the font, # which can be done by putting it in a standard location or by setting the # DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory # containing the font. DOT_FONTNAME = FreeSans.ttf # The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. # The default size is 10pt. DOT_FONTSIZE = 10 # By default doxygen will tell dot to use the output directory to look for the # FreeSans.ttf font (which doxygen will put there itself). If you specify a # different font using DOT_FONTNAME you can set the path where dot # can find it using this tag. DOT_FONTPATH = # If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect inheritance relations. Setting this tag to YES will force the # the CLASS_DIAGRAMS tag to NO. CLASS_GRAPH = YES # If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect implementation dependencies (inheritance, containment, and # class references variables) of the class with other documented classes. COLLABORATION_GRAPH = YES # If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen # will generate a graph for groups, showing the direct groups dependencies GROUP_GRAPHS = YES # If the UML_LOOK tag is set to YES doxygen will generate inheritance and # collaboration diagrams in a style similar to the OMG's Unified Modeling # Language. UML_LOOK = NO # If set to YES, the inheritance and collaboration graphs will show the # relations between templates and their instances. TEMPLATE_RELATIONS = NO # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT # tags are set to YES then doxygen will generate a graph for each documented # file showing the direct and indirect include dependencies of the file with # other documented files. INCLUDE_GRAPH = YES # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and # HAVE_DOT tags are set to YES then doxygen will generate a graph for each # documented header file showing the documented files that directly or # indirectly include this file. INCLUDED_BY_GRAPH = YES # If the CALL_GRAPH and HAVE_DOT options are set to YES then # doxygen will generate a call dependency graph for every global function # or class method. Note that enabling this option will significantly increase # the time of a run. So in most cases it will be better to enable call graphs # for selected functions only using the \callgraph command. CALL_GRAPH = YES # If the CALLER_GRAPH and HAVE_DOT tags are set to YES then # doxygen will generate a caller dependency graph for every global function # or class method. Note that enabling this option will significantly increase # the time of a run. So in most cases it will be better to enable caller # graphs for selected functions only using the \callergraph command. CALLER_GRAPH = YES # If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen # will graphical hierarchy of all classes instead of a textual one. GRAPHICAL_HIERARCHY = YES # If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES # then doxygen will show the dependencies a directory has on other directories # in a graphical way. The dependency relations are determined by the #include # relations between the files in the directories. DIRECTORY_GRAPH = YES # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. Possible values are png, jpg, or gif # If left blank png will be used. DOT_IMAGE_FORMAT = png # The tag DOT_PATH can be used to specify the path where the dot tool can be # found. If left blank, it is assumed the dot tool can be found in the path. DOT_PATH = # The DOTFILE_DIRS tag can be used to specify one or more directories that # contain dot files that are included in the documentation (see the # \dotfile command). DOTFILE_DIRS = # The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of # nodes that will be shown in the graph. If the number of nodes in a graph # becomes larger than this value, doxygen will truncate the graph, which is # visualized by representing a node as a red box. Note that doxygen if the # number of direct children of the root node in a graph is already larger than # DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note # that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. DOT_GRAPH_MAX_NODES = 50 # The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the # graphs generated by dot. A depth value of 3 means that only nodes reachable # from the root by following a path via at most 3 edges will be shown. Nodes # that lay further from the root node will be omitted. Note that setting this # option to 1 or 2 may greatly reduce the computation time needed for large # code bases. Also note that the size of a graph can be further restricted by # DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. MAX_DOT_GRAPH_DEPTH = 0 # Set the DOT_TRANSPARENT tag to YES to generate images with a transparent # background. This is disabled by default, because dot on Windows does not # seem to support this out of the box. Warning: Depending on the platform used, # enabling this option may lead to badly anti-aliased labels on the edges of # a graph (i.e. they become hard to read). DOT_TRANSPARENT = NO # Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This # makes dot run faster, but since only newer versions of dot (>1.8.10) # support this, this feature is disabled by default. DOT_MULTI_TARGETS = YES # If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will # generate a legend page explaining the meaning of the various boxes and # arrows in the dot generated graphs. GENERATE_LEGEND = YES # If the DOT_CLEANUP tag is set to YES (the default) Doxygen will # remove the intermediate dot files that are used to generate # the various graphs. DOT_CLEANUP = YES workbench-1.1.1/src/Cifti/QUICKSTART000066400000000000000000000020511255417355300167460ustar00rootroot00000000000000The entry point for reading and writing Cifti data is the object CiftiFile. To get a handle to a Cifti File, use the following syntax: CiftiFile *cf = new CiftiFile("testciftifile.dtseries.nii"); The Cifti 1.0 API breaks the file up into three logical sections, the Nifti 2 Header, the Cifti XML meta data, and the Cifti Matrix data. To access the data use the following functions: //read/write nifti header Nifti2Header header; cf->setHeader(*header); cf->getHeader(*header); //read/write Cifti 1.0 XML extension "header" CiftiXML xml; cf->setCiftiXML(xml); cf->getCiftiXML(xml); //read/write Cifti matrix CiftiMatrix *matrix = new CiftiMatrix(); cf->setCiftiMatrix(*matrix); matrix = cf->getCiftiMatrix(); //writing to a new Cifti File cf->writeFile("outputfile.dtseries.nii"); For more detailed information on how to manipulated Cifti data, look at the doxygen documentation, located in the doc sub-directory, and the Cifti 1.0 specification, located at the following URL: http://www.nitrc.org/plugins/mwiki/index.php/cifti:ConnectivityMatrixFileFormats workbench-1.1.1/src/Cifti/README000066400000000000000000000011141255417355300162100ustar00rootroot00000000000000Cifti lib requires the QT toolkit to compile. The first version of Cifti was written to build with Qt 4.7.1, and it is recommended that you use this, or a later version of QT. To build the release version on Linux/Unix/OSx, run the following command: > qmake "CONFIG+=release" make To build the debug version, run: > qmake "CONFIG+=debug" make To build on Windows with Visual Studio, run: > qmake "CONFIG+=vs" make To build documentation, run: > qmake "CONFIG+=doc" make doc To build a shared library, add the keyword "dll" to qmake CONFIG, i.e.: > qmake "CONFIG+=release dll" make workbench-1.1.1/src/Cifti/examples/000077500000000000000000000000001255417355300171515ustar00rootroot00000000000000workbench-1.1.1/src/Cifti/examples/examples.pro000066400000000000000000000026151255417355300215150ustar00rootroot00000000000000###################################################################### # Automatically generated by qmake (1.04a) Wed Jan 15 08:39:18 2003 ###################################################################### TARGET = simple !vs:TEMPLATE = app vs:TEMPLATE=vcapp CONFIG += staticlib INCLUDEPATH += . ../. LIBS += -L../. -lCifti SOURCES = simple.cxx !win32 { CONFIG+=DEBUG QMAKE_LFLAGS_DEBUG-=-O1 -O2 QMAKE_LFLAGS_DEBUG+=-O0 QMAKE_CXXFLAGS_DEBUG-=-O1 -O2 QMAKE_CXXFLAGS_DEBUG+=-O0 } win32:vs { CONFIG+=DEBUG_AND_RELEASE CONFIG(debug,release|debug) { QMAKE_LFLAGS-=-O1 -O2 QMAKE_LFLAGS+=-O0 } } # # Visual Studio specific build flags # win32:vs { CONFIG(release,release|debug) { QMAKE_CXXFLAGS_RELEASE -= -O1 -Wl,-O2 QMAKE_LFLAGS_RELEASE -= -O1 -Wl,-O2 QMAKE_CXXFLAGS_RELEASE += -O2 -D_CRT_SECURE_NO_WARNINGS -D_USE_MATH_DEFINES -wd"4290" -wd"4244" -wd"4267" -wd"4305" -wd"4100" -wd"4005" -MP -DNOMINMAX QMAKE_LFLAGS_RELEASE += -O2 -D_USE_MATH_DEFINES -STACK:10000000 } CONFIG(debug,release|debug) { QMAKE_CXXFLAGS_DEBUG -= -O2 QMAKE_LFLAGS_DEBUG -= -O2 QMAKE_CXXFLAGS_DEBUG += -D_CRT_SECURE_NO_WARNINGS -D_DEBUG -D_USE_MATH_DEFINES -wd"4290" -wd"4244" -wd"4267" -wd"4305" -wd"4100" -wd"4005" -MP -DNOMINMAX QMAKE_LFLAGS_DEBUG += -DEBUG -STACK:10000000 } } workbench-1.1.1/src/Cifti/examples/simple.cxx000066400000000000000000000052701255417355300211720ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "CiftiFile.h" #include "iostream" #include "CiftiXMLElements.h" #include "CiftiXMLReader.h" #include "CiftiXMLWriter.h" #include #include CiftiRootElement root; int main(int argc, char **argv) { if(argc != 2) { std::cout << "Usage: " << std::endl; std::cout << "simple " << std::endl; std::cout << "To download example Cifti Data, please go to the following URL:" << std::endl; std::cout << "http://www.nitrc.org/plugins/mwiki/index.php/cifti:ConnectivityMatrixFileFormats" << std::endl; } QString inFileName(argv[1]); QString outFileName(argv[2]); CiftiFile *cf = new CiftiFile(inFileName); if(!cf) std::cout << "There was an error opening the Cifti File." << std::endl; CiftiXML *xml = cf->getCiftiXML(); Nifti2Header *header = cf->getHeader(); CiftiMatrix *matrix = cf->getCiftiMatrix(); // print out Nifti Header in human readable format QString *string = header->getHeaderAsString(); std::cout << string->toAscii().data() << std::endl; delete string; string = NULL; // Print out number of Brain Model Elements CiftiRootElement root; xml->getXMLRoot(root); std::cout << "Number of Brain Elements is: " << root.m_matrices.at(0).m_matrixIndicesMap.at(0).m_brainModels.size() << std::endl; // Print out matrix dimensions float *data = NULL; std::vector dimensions; try { matrix->getMatrixData(data, dimensions); } catch (CiftiFileException e) { std::cout << "There was an error getting the Cifti Matrix data" << e.what() << std::endl; } for(unsigned int i = 0;iwriteFile(outFileName); return 0; } workbench-1.1.1/src/CommandLine/000077500000000000000000000000001255417355300164635ustar00rootroot00000000000000workbench-1.1.1/src/CommandLine/CMakeLists.txt000066400000000000000000000032571255417355300212320ustar00rootroot00000000000000 # # Name of project # PROJECT (CommandLine) #cmake_policy(SET CMP0015 OLD) # # Need XML from Qt # #SET(QT_DONT_USE_QTGUI TRUE) SET(QT_USE_QTXML TRUE) SET(QT_USE_QTNETWORK TRUE) # # Add QT for includes # INCLUDE (${QT_USE_FILE}) # # Name of executable # set (EXE_NAME wb_command) # # Resources # SET (RESOURCES_QRC_FILE ../Resources/resources.qrc) QT4_ADD_RESOURCES(IMAGE_RCS_SRCS ${RESOURCES_QRC_FILE}) # # Create the executable # Apple creates a bundle # IF (APPLE) ADD_EXECUTABLE(${EXE_NAME} MACOSX_BUNDLE wb_command.cxx ${IMAGE_RCS_SRCS} ) ELSE (APPLE) ADD_EXECUTABLE(${EXE_NAME} wb_command.cxx ${IMAGE_RCS_SRCS} ) ENDIF (APPLE) # # Libraries that are linked # Note: OSMESA library variables will be empty and have no effect # if OSMESA is not available # TARGET_LINK_LIBRARIES(${EXE_NAME} Commands Operations Algorithms OperationsBase Brain ${FTGL_FONT_MODULE_FOR_LINKING} Files Palette Gifti Cifti Nifti Charting FilesBase Scenes Xml Common Quazip ${FREETYPE_LIBRARIES} ${QT_LIBRARIES} ${OSMESA_OFFSCREEN_LIBRARY} ${OSMESA_GL_LIBRARY} ${OSMESA_GLU_LIBRARY} ${ZLIB_LIBRARIES} ${LIBS}) INSTALL(TARGETS ${EXE_NAME} DESTINATION bin) # # Find Headers # INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/CommandLine ${CMAKE_SOURCE_DIR}/Commands ${CMAKE_SOURCE_DIR}/Operations ${CMAKE_SOURCE_DIR}/Algorithms ${CMAKE_SOURCE_DIR}/OperationsBase ${CMAKE_SOURCE_DIR}/Brain ${CMAKE_SOURCE_DIR}/Charting ${CMAKE_SOURCE_DIR}/Palette ${CMAKE_SOURCE_DIR}/Files ${CMAKE_SOURCE_DIR}/Gifti ${CMAKE_SOURCE_DIR}/Cifti ${CMAKE_SOURCE_DIR}/Nifti ${CMAKE_SOURCE_DIR}/FilesBase ${CMAKE_SOURCE_DIR}/Scenes ${CMAKE_SOURCE_DIR}/Xml ${CMAKE_SOURCE_DIR}/Common ${CMAKE_SOURCE_DIR}/Quazip ) workbench-1.1.1/src/CommandLine/README.txt000066400000000000000000000000651255417355300201620ustar00rootroot00000000000000To add new commands, see the README.txt in Commands. workbench-1.1.1/src/CommandLine/wb_command.cxx000066400000000000000000000105641255417355300213230ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include "AString.h" #include "CaretAssert.h" #include "CaretHttpManager.h" #include "CaretCommandLine.h" #include "CaretLogger.h" #include "CommandOperationManager.h" #include "ProgramParameters.h" #include "SessionManager.h" #include "SystemUtilities.h" #include "VolumeFile.h" using namespace caret; using namespace std; static int runCommand(int argc, char* argv[]) { ProgramParameters parameters(argc, argv); caret_global_commandLine_init(parameters); /* * Log the command parameters. */ CaretLogFine("Running: " + caret_global_commandLine); CommandOperationManager* commandManager = NULL; int ret = 0; try { commandManager = CommandOperationManager::getCommandOperationManager(); commandManager->runCommand(parameters); } catch (CaretException& e) { cerr << "\nWhile running:\n" << caret_global_commandLine << "\n\nERROR: " << e.whatString().toStdString() << endl << endl; ret = -1; } catch (bad_alloc& e) {//if we stop using a handler for new cerr << "\nWhile running:\n" << caret_global_commandLine << "\n\nERROR: " << e.what() << endl; cerr << endl << "OUT OF MEMORY" << endl << endl << "This means that Workbench is unable to get memory that it needs." << endl << "Possible causes:" << endl << " (1) Your computer lacks sufficient RAM." << endl << " (2) Swap space is too small (you might increase it)." << endl << " (3) Your computer may be using an non-English character" << endl//is this relevant? << " set. Try switching to the English character set." << endl << endl; ret = -1; } catch (exception& e) { cerr << "\nWhile running:\n" << caret_global_commandLine << "\n\nERROR: " << e.what() << endl << endl; ret = -1; } catch (...) { cerr << "\nWhile running:\n" << caret_global_commandLine << "\n\nERROR: caught unknown exception type, rethrowing..." << endl << endl; if (commandManager != NULL) { CommandOperationManager::deleteCommandOperationManager(); } throw;//rethrow, the runtime might print the type } if (commandManager != NULL) { CommandOperationManager::deleteCommandOperationManager(); } return ret; } int main(int argc, char* argv[]) { srand(time(NULL)); int result = 0; { /* * Handle uncaught exceptions */ SystemUtilities::setUnexpectedHandler(); /* * Create the session manager. */ SessionManager::createSessionManager(ApplicationTypeEnum::APPLICATION_TYPE_COMMAND_LINE); /* * Disable volume voxel coloring since it can be a little slow * and voxel coloring is not needed for any commands (at this * time). */ VolumeFile::setVoxelColoringEnabled(false); QCoreApplication myApp(argc, argv);//so that it doesn't need to link against gui result = runCommand(argc, argv); /* * Delete the session manager. */ SessionManager::deleteSessionManager(); CaretHttpManager::deleteHttpManager();//does this belong in some other singleton manager? myApp.processEvents();//since we don't exec(), let it clean up any ->deleteLater()s } /* * See if any objects were not deleted. */ CaretObject::printListOfObjectsNotDeleted(true); return result; } workbench-1.1.1/src/Commands/000077500000000000000000000000001255417355300160365ustar00rootroot00000000000000workbench-1.1.1/src/Commands/CMakeLists.txt000066400000000000000000000023761255417355300206060ustar00rootroot00000000000000# # Name of project # PROJECT (Commands) # # Need XML from Qt # SET(QT_DONT_USE_QTGUI) # # Add QT for includes # INCLUDE (${QT_USE_FILE}) # # Create the brain library # ADD_LIBRARY(Commands CommandClassAddMember.h CommandClassCreate.h CommandClassCreateAlgorithm.h CommandClassCreateBase.h CommandClassCreateEnum.h CommandClassCreateOperation.h CommandC11xTesting.h CommandException.h CommandGiftiConvert.h CommandOperation.h CommandOperationManager.h CommandParser.h CommandUnitTest.h CommandClassAddMember.cxx CommandClassCreate.cxx CommandClassCreateAlgorithm.cxx CommandClassCreateBase.cxx CommandClassCreateEnum.cxx CommandClassCreateOperation.cxx CommandC11xTesting.cxx CommandException.cxx CommandGiftiConvert.cxx CommandOperation.cxx CommandOperationManager.cxx CommandParser.cxx CommandUnitTest.cxx ) # # Find Headers # INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/Commands ${CMAKE_SOURCE_DIR}/Algorithms ${CMAKE_SOURCE_DIR}/Charting ${CMAKE_SOURCE_DIR}/Operations ${CMAKE_SOURCE_DIR}/OperationsBase ${CMAKE_SOURCE_DIR}/Brain ${CMAKE_SOURCE_DIR}/FilesBase ${CMAKE_SOURCE_DIR}/Files ${CMAKE_SOURCE_DIR}/Gifti ${CMAKE_SOURCE_DIR}/Cifti ${CMAKE_SOURCE_DIR}/Palette ${CMAKE_SOURCE_DIR}/Nifti ${CMAKE_SOURCE_DIR}/Scenes ${CMAKE_SOURCE_DIR}/Xml ${CMAKE_SOURCE_DIR}/Common ) workbench-1.1.1/src/Commands/CommandC11xTesting.cxx000066400000000000000000000104611255417355300221350ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include // Not on Intel #include #include #include "CaretAssert.h" #include "CommandC11xTesting.h" #include "ProgramParameters.h" using namespace caret; /** * Constructor. */ CommandC11xTesting::CommandC11xTesting() : CommandOperation("-c11x-test", "C11X Compiler Compatibility Testing") { } /** * Destructor. */ CommandC11xTesting::~CommandC11xTesting() { } AString CommandC11xTesting::getHelpInformation(const AString& /*programName*/) { AString helpInfo = ("\n" "Test for C11x support in compilers.\n" "\n" "Usage: \n" " \n" ); return helpInfo; } /** * Execute the operation. * * @param parameters * Parameters for the operation. * @throws CommandException * If the command failed. * @throws ProgramParametersException * If there is an error in the parameters. */ void CommandC11xTesting::executeOperation(ProgramParameters& /*parameters*/) { #ifdef WORKBENCH_HAVE_C11X Cpp11xTesting cppx; cppx.test(); #endif // WORKBENCH_HAVE_C11X } #ifdef WORKBENCH_HAVE_C11X /** * Constructor. * * Tests delegating constructor (calls another constructor within same class) * Not supported by Intel compiler */ Cpp11xTesting::Cpp11xTesting() /*: Cp11xTesting(5)*/ { } /** * Constructor. */ Cpp11xTesting::Cpp11xTesting(const int /*value*/) { } /** * Destructor. */ Cpp11xTesting::~Cpp11xTesting() { } /** * Test 'noexcept' keyword indicating no exception is thrown * Not supported by Intel Compiler */ //void //Cpp11xTesting::methodName() noexcept() //{ // //} void Cpp11xTesting::test() { std::cout << "Initialized value of X: " << m_x << std::endl; std::cout << std::endl; /* * Space no longer needed between consecutive '>' */ std::vector> vvi; /* * Initialization list for vector * Not supported by Intel Compiler */ std::vector values; // { 3, 8, 5 }; values.push_back(3); values.push_back(8); values.push_back(5); /* * Iterator over vector using both 'auto' and the new iterator */ std::cout << "Vector auto and new iterator: "; for (auto i : values) { std::cout << i << " "; } std::cout << std::endl; /* * 'nullptr' instead of NULL or zero. */ float* fptr = nullptr; if (fptr == nullptr) { std::cout << "nullptr works" << std::endl; } /* * Array initialization, new style * Not supported by Intel compiler */ //int a[] { 1, 3, 5 }; int a[] = { 1, 3, 5 }; /* * Iteration over array using both 'auto' and the new iterator */ std::cout << "Array auto and new iterator: "; for (auto i : a) { std::cout << i << " "; } std::cout << std::endl; /* * Tuple * Not supported by Intel compiler */ //std::tuple tp(5, 9.1, 12.5); //std::cout << "Tuple 2nd value: " << std::get<1>(tp) << std::endl; /* * Test Lambda */ auto l = [] (const char* s) { std::cout << "Lambda: " << s << std::endl; }; l("lambda test"); } /** * 'override' keyword would cause an error if superclass did not have methodName() * Not supported by Intel compiler */ //void //SubClass:methodName() override noexcept //{ // //} #endif // WORKBENCH_HAVE_C11X workbench-1.1.1/src/Commands/CommandC11xTesting.h000066400000000000000000000046021255417355300215620ustar00rootroot00000000000000#ifndef __COMMAND_C11X_TESTING__H__ #define __COMMAND_C11X_TESTING__H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CommandOperation.h" namespace caret { /// Command that adds members and getter/setter methods to a class class CommandC11xTesting : public CommandOperation { public: CommandC11xTesting(); virtual ~CommandC11xTesting(); virtual void executeOperation(ProgramParameters& parameters); AString getHelpInformation(const AString& /*programName*/); private: CommandC11xTesting(const CommandC11xTesting&); CommandC11xTesting& operator=(const CommandC11xTesting&); }; #ifdef WORKBENCH_HAVE_C11X class Cpp11xTesting { public: Cpp11xTesting(); Cpp11xTesting(const int value); // Intel does not suport 'noexcept' keyword //virtual void methodName() noexcept; virtual ~Cpp11xTesting(); void test(); private: Cpp11xTesting(const Cpp11xTesting&); Cpp11xTesting& operator=(const Cpp11xTesting&); /* * Initialization of a member's value. */ int m_x = 5; }; class SubClass : public Cpp11xTesting { SubClass() : Cpp11xTesting(5) { } virtual ~SubClass() { } // Intel does not support 'override' keyword //virtual void methodName() override noexcept; }; #endif // WORKBENCH_HAVE_C11X } // namespace #endif // __COMMAND_C11X_TESTING__H__ workbench-1.1.1/src/Commands/CommandClassAddMember.cxx000066400000000000000000000442331255417355300226750ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretAssert.h" #include "CommandClassAddMember.h" #include "CommandClassCreate.h" #include "DataFileException.h" #include "FileInformation.h" #include "ProgramParameters.h" #include "TextFile.h" using namespace caret; /** * Constructor. */ CommandClassAddMember::CommandClassAddMember() : CommandOperation("-class-add-member", "ADD MEMBER AND GETTER/SETTER TO SOURCE CODE FILES (.h and .cxx)") { } /** * Destructor. */ CommandClassAddMember::~CommandClassAddMember() { } AString CommandClassAddMember::getHelpInformation(const AString& /*programName*/) { AString helpInfo = ("\n" "Add members to class header (.h) and implementation (.cxx) files.\n" "\n" "Usage: \n" " [-add-to-files]\n" " [-m ]...\n" "\n" " If the -add-to-files is not specified, the code for the\n" " header and implementation files is printed to the \n" " terminal.\n" "\n" " If the -add-to-files is specified, the class files are\n" " expected to be in the current directory and named\n" " .h and .cxx. The header \n" " file must contain this text in its private section:\n" " " + CommandClassCreate::getNewMembersString() + "\n" " The implementation file must contain this text in\n" " its public section:\n" " " + CommandClassCreate::getNewMethodsString() + "\n" " If either of these text string are missing, the code \n" " that would have been added to the file(s) is printed\n" " to the terminal.\n" " \n" " For each member, three text strings separated by a space\n" " must be provided and they are the name of the member\n" " its data type, and a description of the member. If the\n" " description contains spaces the description must be\n" " enclosed in double quotes (\"\").\n" " \n" " If the data type begins with a capital letter, it is\n" " assumed to be the name of a class. In this case, both\n" " const and non-const getters are created but not setter\n" " is created. Otherwise, the data type is expected to be\n" " a primitive type and both a getter and a setter are\n" " created. Note that AString and QString are treated as\n" " primitive types.\n" " \n" ); return helpInfo; } /** * Execute the operation. * * @param parameters * Parameters for the operation. * @throws CommandException * If the command failed. * @throws ProgramParametersException * If there is an error in the parameters. */ void CommandClassAddMember::executeOperation(ProgramParameters& parameters) { bool isAddToFiles = false; AString headerMemberCode = ""; AString headerMethodCode = ""; AString implementationCode = ""; const AString indentText = " "; /* * Process parameters */ const AString className = parameters.nextString("Class Name"); while (parameters.hasNext()) { const AString param = parameters.nextString(""); if (param == "-add-to-files") { isAddToFiles = true; } else if (param == "-m") { /* * Adding a member */ AString name = parameters.nextString("Member name"); if (name.indexOf(" ") >= 0) { throw CommandException("Member named \"" + name + "\" cannot contain spaces."); } if (name.isEmpty()) { throw CommandException("A member name is empty."); } else { if (name[0].isLetter() == false) { throw CommandException("Member named \"" + name + "\" must begin with a letter."); } if (name.indexOf(" ") >= 0) { throw CommandException("Member named \"" + name + "\" cannot contain spaces."); } } AString dataType = parameters.nextString("Data type for member " + name); if (dataType.isEmpty()) { throw CommandException("A data type is empty."); } if (dataType.indexOf(" ") >= 0) { throw CommandException("Data Type named \"" + name + "\" cannot contain spaces."); } const AString description = parameters.nextString("Description for member " + name); /* * If first letter of data type is upper-case, assume it * is a class and use a pointer for the class. However, * treat AString and QString as primitive types. */ bool isString = false; bool isClass = false; const QChar firstDataTypeChar = dataType[0]; if (firstDataTypeChar.isUpper()) { if ((dataType == "AString") || (dataType == "QString")) { isString = true; } else { isClass = true; } } const AString returnType = dataType + (isClass ? "*" : ""); /* * Determine partial name for getter and setter * handling removal or addition of "m_" for * member name. */ AString getSetName = name; AString parameterName = name; if (name.startsWith("m_")) { getSetName = name.mid(2); parameterName = name.mid(2); } else { name = "m_" + name; } /* * Create the names for the getter and setter methods */ QChar firstGetSetNameChar = getSetName[0]; if (firstGetSetNameChar.isLower()) { firstGetSetNameChar = firstGetSetNameChar.toUpper(); getSetName[0] = firstGetSetNameChar; } const AString getterName = ("get" + getSetName); const AString setterName = ("set" + getSetName); /* * Declare the member for the header file */ if (description.isEmpty() == false) { headerMemberCode += (indentText + "/** " + description + "*/\n"); } headerMemberCode += (indentText + returnType + " " + name + ";" + "\n" + "\n"); const AString classColonName = (className + "::"); if (isClass) { /* * For class members, create only getter methods * that are both const and non-const */ for (int32_t i = 0; i < 2; i++) { const bool isConstMethod = (i == 0); headerMethodCode += (indentText + (isConstMethod ? "const " : "") + returnType + " " + getterName + "()" + (isConstMethod ? " const" : "") + ";\n" + "\n"); implementationCode += ("/**\n" " * @return " + description + "\n" " */\n" + (isConstMethod ? "const " : "") + returnType + "\n" + classColonName + getterName + "()" + (isConstMethod ? " const" : "") + "\n" + "{\n" + " return " + name + ";\n" + "}\n" + "\n"); } } else { /* * Getter and setter in header file */ headerMethodCode += (indentText + returnType + " " + getterName + "() const;\n" + "\n"); headerMethodCode += (indentText + "void " + setterName + "(const " + dataType + (isString ? "& " : " ") + parameterName + ");\n" + "\n"); /* * Getter and setter in implementation file */ implementationCode += ("/**\n" " * @return " + description + "\n" " */\n" + returnType + "\n" + classColonName + getterName + "() const\n" + "{\n" + " return " + name + ";\n" + "}\n" + "\n"); implementationCode += ("/**\n" " * Set " + description + "\n" " * @param " + parameterName + "\n" " * New value for " + description + "\n" " */\n" + "void\n" + classColonName + setterName + "(const " + dataType + (isString ? "& " : " ") + parameterName + ")\n" + "{\n" + " " + name + " = " + parameterName + ";\n" + "}\n" + "\n"); } } else { throw ProgramParametersException("Unrecognized parameter: " + param); } } if (headerMemberCode.isEmpty()) { throw CommandException("No members were specified."); } AString errorMessage; bool isWriteToTerminal = true; if (isAddToFiles) { const AString headerFileName = className + ".h"; const AString implementationFileName = className + ".cxx"; FileInformation headerInfo(headerFileName); if (headerInfo.exists() == false) { errorMessage += headerFileName + " was not found."; } FileInformation impInfo(implementationFileName); if (impInfo.exists() == false) { errorMessage += implementationFileName + " was not found."; } /* * Copy existing files to the temporary directory */ QDir tempDir = QDir::temp(); AString tempHeaderFileName = tempDir.absoluteFilePath(headerFileName); AString tempImplementationFileName = tempDir.absoluteFilePath(implementationFileName); try { TextFile headerTextFile; headerTextFile.readFile(headerFileName); headerTextFile.writeFile(tempHeaderFileName); TextFile implementationTextFile; implementationTextFile.readFile(implementationFileName); implementationTextFile.writeFile(tempImplementationFileName); std::cout << "Backup of header file: " << qPrintable(tempHeaderFileName) << std::endl; std::cout << "Backup of implementation file: " << qPrintable(tempImplementationFileName) << std::endl; AString headerText = headerTextFile.getText(); const int newMemberOffset = findInsertionOffset(headerText, CommandClassCreate::getNewMembersString()); if (newMemberOffset >= 0) { headerMemberCode += "\n"; headerText.insert(newMemberOffset, headerMemberCode); } else { if (errorMessage.isEmpty()) { errorMessage += "\n"; } errorMessage += ("Unable to insert new members in header file. Did not find text " + CommandClassCreate::getNewMembersString() + " in the file " + headerFileName); } const int newMethodDeclareOffset = findInsertionOffset(headerText, CommandClassCreate::getNewMethodsString()); if (newMethodDeclareOffset >= 0) { headerMethodCode += "\n"; headerText.insert(newMethodDeclareOffset, headerMethodCode); } else { if (errorMessage.isEmpty()) { errorMessage += "\n"; } errorMessage += ("Unable to insert new methods in header file. Did not find text " + CommandClassCreate::getNewMethodsString() + " in the file " + headerFileName); } if ((newMemberOffset >= 0) && (newMethodDeclareOffset >= 0)) { headerTextFile.replaceText(headerText); isWriteToTerminal = false; headerTextFile.writeFile(headerFileName); } implementationCode += "\n"; implementationTextFile.addText("\n" + implementationCode); implementationTextFile.writeFile(implementationFileName); } catch (const DataFileException& dfe) { throw CommandException(dfe); } } if (isWriteToTerminal) { std::cout << std::endl; std::cout << "Header Code Getter Setter -------------------------------------" << std::endl; std::cout << qPrintable(headerMethodCode) << std::endl; std::cout << "Header Code Declaration ---------------------------------------" << std::endl; std::cout << qPrintable(headerMemberCode) << std::endl; std::cout << "Implementation Code -------------------------------------------" << std::endl; std::cout << qPrintable(implementationCode) << std::endl; } if (errorMessage.isEmpty() == false) { throw CommandException(errorMessage); } } /** * Find the offset of the new line or carriage return in the given text * using the given search text. Offset is first character after the * new line or carriage return. * * @param text * Text that is searched. * @param searchForText * Text that is searched for. * @return * Offset of the search text in the text or negative if * the search text is not found. */ int CommandClassAddMember::findInsertionOffset(const AString& text, const AString& searchForText) { int indx = text.indexOf(searchForText); if (indx >= 0) { while (indx >= 0) { const QChar ch = text[indx]; if ((ch == '\n') || (ch == '\r')) { indx++; break; } indx--; } return indx; } return -1; } workbench-1.1.1/src/Commands/CommandClassAddMember.h000066400000000000000000000032671255417355300223240ustar00rootroot00000000000000#ifndef __COMMAND_CLASS_ADD_MEMBER__H__ #define __COMMAND_CLASS_ADD_MEMBER__H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CommandOperation.h" namespace caret { /// Command that adds members and getter/setter methods to a class class CommandClassAddMember : public CommandOperation { public: CommandClassAddMember(); virtual ~CommandClassAddMember(); virtual void executeOperation(ProgramParameters& parameters); AString getHelpInformation(const AString& /*programName*/); private: CommandClassAddMember(const CommandClassAddMember&); CommandClassAddMember& operator=(const CommandClassAddMember&); int findInsertionOffset(const AString& text, const AString& searchForText); }; } // namespace #endif // __COMMAND_CLASS_ADD_MEMBER__H__ workbench-1.1.1/src/Commands/CommandClassCreate.cxx000066400000000000000000000664651255417355300222730ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretAssertion.h" #include "CommandClassCreate.h" #include "DataFileException.h" #include "FileInformation.h" #include "ProgramParameters.h" #include "TextFile.h" using namespace caret; /** * Constructor. */ CommandClassCreate::CommandClassCreate() : CommandClassCreateBase("-class-create", "CREATE CLASS SOURCE CODE FILES (.h and .cxx)") { } /** * Destructor. */ CommandClassCreate::~CommandClassCreate() { } AString CommandClassCreate::getHelpInformation(const AString& /*programName*/) { AString helpInfo = ("\n" "Create class header (.h) and implementation (.cxx) files.\n" "\n" "Usage: \n" " [-copy] \n" " [-event-class ]\n" " [-event-listener] \n" " [-no-parent] \n" " [-parent ] \n" "\n" "\n" "Options: \n" " -copy\n" " Adds copy constructor and assignment operator\n" " \n" " -event-class \n" " When creating an Event subclass, using this\n" " option will automatically set the parent\n" " class to Event and place the given event\n" " enumerated type value into the parameter\n" " for the Event class constructor.\n" " \n" " For the there is no need\n" " to prepend it with \"EventTypeEnum::\".\n" " \n" " -event-listener \n" " Implement the EventListenerInterface so\n" " that the class may listen for events.\n" " \n" " -no-parent\n" " Created class is not derived from any other\n" " class. By default, the parent class is\n" " CaretObject.\n" " \n" " -parent \n" " Specify the parent (derived from) class.\n" " By default, the parent class is CaretObject.\n" " \n" " -scene\n" " Implement the SceneableInterface so that \n" " instances of the class can be restored from \n" " and saved to scenes. \n" " \n" " -scene-sub-class\n" " Adds methods that can be called by the super- \n" " class so that this sub-class can save and \n" " restore data to and from scenes. \n" " \n" " This option should only be used when creating\n" " a class whose super class implements the \n" " SceneableInterface\n" " \n" ); return helpInfo; } /** * Execute the operation. * * @param parameters * Parameters for the operation. * @throws CommandException * If the command failed. * @throws ProgramParametersException * If there is an error in the parameters. */ void CommandClassCreate::executeOperation(ProgramParameters& parameters) { const AString className = parameters.nextString("Class Name"); AString derivedFromClassName = "CaretObject"; AString eventTypeEnumName; bool hasCopyAndAssignment = false; bool hasEventListener = false; bool hasScenes = false; bool hasScenesSubClass = false; while (parameters.hasNext()) { const AString& param = parameters.nextString("Create Class Parameter"); if (param == "-copy") { hasCopyAndAssignment = true; } else if (param == "-event-class") { eventTypeEnumName = parameters.nextString("Event Type Enum Name"); if (eventTypeEnumName.contains("::") == false) { eventTypeEnumName.insert(0, "EventTypeEnum::"); } } else if (param == "-event-listener") { hasEventListener = true; } else if (param == "-parent") { derivedFromClassName = parameters.nextString("Parent Class Name"); if (derivedFromClassName.isEmpty()) { throw CommandException("Parent class name is empty."); } else { if (derivedFromClassName[0].isUpper() == false) { throw CommandException("Parent class name must begin with a Capital Letter"); } } } else if (param == "-no-parent") { derivedFromClassName = ""; } else if (param == "-scene") { hasScenes = true; } else if (param == "-scene-sub-class") { hasScenesSubClass = true; } else { throw CommandException("Invalid parameter: " + param); } } if (hasScenes && hasScenesSubClass) { throw CommandException("Only one, but not both scene options " "may be specified: " "-scene -scene-sub-class"); } if (className.isEmpty()) { throw CommandException("class name is empty."); } AString errorMessage; if (eventTypeEnumName.isEmpty() == false) { derivedFromClassName = "Event"; if (className.startsWith("Event") == false) { errorMessage += ("Event classes must being with \"Event\"\n"); } } if (className[0].isUpper() == false) { errorMessage += "First letter of class name must be upper case.\n"; } const AString headerFileName = className + ".h"; const AString implementationFileName = className + ".cxx"; FileInformation headerInfo(headerFileName); if (headerInfo.exists()) { errorMessage += headerFileName + " exists and this command will not overwrite it.\n"; } FileInformation impInfo(implementationFileName); if (impInfo.exists()) { errorMessage += implementationFileName + " exists and this command will not overwrite it.\n"; } if (errorMessage.isEmpty() == false) { throw CommandException(errorMessage); } AString ifndefName; AString ifdefNameStaticDeclarations; this->getIfDefNames(className, ifndefName, ifdefNameStaticDeclarations); this->createHeaderFile(headerFileName, className, derivedFromClassName, ifndefName, ifdefNameStaticDeclarations, hasCopyAndAssignment, hasEventListener, hasScenes, hasScenesSubClass); this->createImplementationFile(implementationFileName, className, derivedFromClassName, eventTypeEnumName, ifdefNameStaticDeclarations, hasCopyAndAssignment, hasEventListener, hasScenes, hasScenesSubClass); } /** * Create and write the header (.h) file. * * @param outputFileName * Name for file that is written. * @param className * Name of class. * @param derivedFromClassName * Name of class from which this class is derived. * @param ifdefName * Name of "ifndef" value. * @param ifdefNameStaticDeclaration * Name for "infdef" of static declarations. * @param hasCopyAndAssignment * Has copy constructor and assignment operator. * @param hasEventListener * Class implements the EventListener interface * @param hasSceneInterface * Class implements the SceneableInterface for scene support. * @param hasSubClassSceneSaving * Parent class implements the SceneableInterface so add methods * for saving sub-class data to scene that will be called by parent. */ void CommandClassCreate::createHeaderFile(const AString& outputFileName, const AString& className, const AString& derivedFromClassName, const AString& ifndefName, const AString& ifdefNameStaticDeclaration, const bool hasCopyAndAssignment, const bool hasEventListener, const bool hasSceneInterface, const bool hasSubClassSceneSaving) { AString t; AString derivedFromDeclaration; if (derivedFromClassName.isEmpty() == false) { derivedFromDeclaration = (" : public " + derivedFromClassName); } t += ("#ifndef " + ifndefName + "\n"); t += ("#define " + ifndefName + "\n"); t += this->getCopyright(); t += ("\n"); t += (this->getIncludeDeclaration(derivedFromClassName) + "\n"); t += ("\n"); if (hasEventListener) { t += (this->getIncludeDeclaration("EventListenerInterface") + "\n"); if (derivedFromDeclaration.isEmpty()) { derivedFromDeclaration += (" : "); } else { derivedFromDeclaration += (", "); } derivedFromDeclaration += ("public EventListenerInterface"); } if (hasSceneInterface) { t += (this->getIncludeDeclaration("SceneableInterface") + "\n"); if (derivedFromDeclaration.isEmpty()) { derivedFromDeclaration += (" : "); } else { derivedFromDeclaration += (", "); } derivedFromDeclaration += ("public SceneableInterface"); } t += ("\n"); t += ("\n"); t += ("namespace caret {\n"); if (hasSceneInterface) { t += (" class SceneClassAssistant;\n"); } t += ("\n"); t += (" class " + className + derivedFromDeclaration + " {\n"); t += (" \n"); if (derivedFromClassName.startsWith("Q") || derivedFromClassName.startsWith("WuQ")) { t += (" Q_OBJECT\n"); t += ("\n"); } t += (" public:\n"); t += (" " + className + "();\n"); t += (" \n"); t += (" virtual ~" + className + "();\n"); t += (" \n"); if (hasCopyAndAssignment) { t += (" " + className + "(const " + className + "& obj);\n"); t += ("\n"); t += (" " + className + "& operator=(const " + className + "& obj);\n"); t += (" \n"); } else { // t += (" private:\n"); // t += (" " + className + "(const " + className + "&);\n"); // t += ("\n"); // t += (" " + className + "& operator=(const " + className + "&);\n"); // t += (" \n"); // t += (" public:\n"); } t += ("\n"); t += (" " + getNewMethodsString() + "\n"); t += ("\n"); if (derivedFromClassName == "CaretObject") { t += (" virtual AString toString() const;\n"); t += (" \n"); } if (hasEventListener) { t += (" virtual void receiveEvent(Event* event);\n"); t += ("\n"); } if (hasSceneInterface) { t += (" virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes,\n"); t += (" const AString& instanceName);\n"); t += ("\n"); t += (" virtual void restoreFromScene(const SceneAttributes* sceneAttributes,\n"); t += (" const SceneClass* sceneClass);\n"); t += ("\n"); } if (hasSceneInterface || hasSubClassSceneSaving) { AString comment = ""; AString virtualZero =""; if (hasSceneInterface) { comment = "//"; virtualZero = " = 0"; } t += (" \n"); t += (" \n"); t += (" \n"); t += (" \n"); t += (" \n"); if (hasSceneInterface) { t += ("// If there will be sub-classes of this class that need to save\n"); t += ("// and restore data from scenes, these pure virtual methods can\n"); t += ("// be uncommented to force their implemetation by sub-classes.\n"); } t += (comment + " protected: \n"); t += (comment + " virtual void saveSubClassDataToScene(const SceneAttributes* sceneAttributes,\n"); t += (comment + " SceneClass* sceneClass)" + virtualZero + ";\n"); t += (comment + "\n"); t += (comment + " virtual void restoreSubClassDataFromScene(const SceneAttributes* sceneAttributes,\n"); t += (comment + " const SceneClass* sceneClass)" + virtualZero + ";\n"); t += ("\n"); } t += (" private:\n"); if (hasCopyAndAssignment) { t += (" void copyHelper" + className + "(const " + className + "& obj);\n"); t += ("\n"); } else { t += (" " + className + "(const " + className + "&);\n"); t += ("\n"); t += (" " + className + "& operator=(const " + className + "&);\n"); t += (" \n"); } if (hasSceneInterface || hasSubClassSceneSaving) { t += (" SceneClassAssistant* m_sceneAssistant;\n"); t += ("\n"); } t += (" " + getNewMembersString() + "\n"); t += ("\n"); t += (" };\n"); t += (" \n"); t += ("#ifdef " + ifdefNameStaticDeclaration + "\n"); t += (" // \n"); t += ("#endif // " + ifdefNameStaticDeclaration + "\n"); t += ("\n"); t += ("} // namespace\n"); t += ("#endif //" + ifndefName + "\n"); TextFile tf; tf.replaceText(t); try { tf.writeFile(outputFileName); } catch (const DataFileException& e) { throw CommandException(e); } } /** * Create and write the implementation (.cxx) file. * * @param outputFileName * Name for file that is written. * @param className * Name of class. * @param derivedFromClassName * Name of parent class * @param eventTypeEnumName * Name of event type enumerated type (if subclass of Event). * @param ifdefNameStaticDeclaration * Name for "infdef" of static declarations. * @param hasCopyAndAssignment * Has copy constructor and assignment operator. * @param hasEventListener * Class implements the EventListener interface * @param hasSceneInterface * Class implements the SceneableInterface for scene support. * @param hasSubClassSceneSaving * Parent class implements the SceneableInterface so add methods * for saving sub-class data to scene that will be called by parent. */ void CommandClassCreate::createImplementationFile(const AString& outputFileName, const AString& className, const AString& derivedFromClassName, const AString& eventTypeEnumName, const AString& ifdefNameStaticDeclaration, const bool hasCopyAndAssignment, const bool hasEventListener, const bool hasSceneInterface, const bool hasSubClassSceneSaving) { AString module; FileInformation dirInfo(QDir::currentPath()); if (dirInfo.exists()) { if (dirInfo.isDirectory()) { module = dirInfo.getFileName(); } } AString t; t += this->getCopyright(); t += ("#define " + ifdefNameStaticDeclaration + "\n"); t += ("#include \"" + className + ".h\"\n"); t += ("#undef " + ifdefNameStaticDeclaration + "\n"); t += ("\n"); t += (this->getIncludeDeclaration("CaretAssert") + "\n"); if (eventTypeEnumName.isEmpty() == false) { t += ("#include \"EventTypeEnum.h\"\n"); t += ("\n"); } if (hasEventListener) { t += (this->getIncludeDeclaration("EventManager") + "\n"); } if (hasSceneInterface || hasSubClassSceneSaving) { t += (this->getIncludeDeclaration("SceneClass") + "\n"); t += (this->getIncludeDeclaration("SceneClassAssistant") + "\n"); t += ("\n"); } t += ("using namespace caret;\n"); t += ("\n"); t += ("\n"); t += (" \n"); t += ("/**\n"); t += (" * \\class caret::" + className + " \n"); t += (" * \\brief \n"); if (module.isEmpty() == false) { t += (" * \\ingroup " + module + "\n"); } t += (" *\n"); t += (" * \n"); t += (" */\n"); t += ("\n"); t += ("/**\n"); t += (" * Constructor.\n"); t += (" */\n"); t += ("" + className + "::" + className + "()\n"); if (derivedFromClassName.isEmpty() == false) { t += (": " + derivedFromClassName + "("+ eventTypeEnumName + ")\n"); } t += ("{\n"); t += (" \n"); if (hasSceneInterface || hasSubClassSceneSaving) { t += (" m_sceneAssistant = new SceneClassAssistant();\n"); t += (" \n"); } if (hasEventListener) { t += ("// EventManager::get()->addEventListener(this, EventTypeEnum::);\n"); } t += ("}\n"); t += ("\n"); t += ("/**\n"); t += (" * Destructor.\n"); t += (" */\n"); t += ("" + className + "::~" + className + "()\n"); t += ("{\n"); if (hasEventListener) { t += (" EventManager::get()->removeAllEventsFromListener(this);\n"); } if (hasSceneInterface || hasSubClassSceneSaving) { t += (" delete m_sceneAssistant;\n"); } t += ("}\n"); t += ("\n"); if (hasCopyAndAssignment) { t += ("/**\n"); t += (" * Copy constructor.\n"); t += (" * @param obj\n"); t += (" * Object that is copied.\n"); t += (" */\n"); t += ("" + className + "::" + className + "(const " + className + "& obj)\n"); t += (": " + derivedFromClassName + "(obj)\n"); t += ("{\n"); t += (" this->copyHelper" + className + "(obj);\n"); t += ("}\n"); t += ("\n"); t += ("/**\n"); t += (" * Assignment operator.\n"); t += (" * @param obj\n"); t += (" * Data copied from obj to this.\n"); t += (" * @return \n"); t += (" * Reference to this object.\n"); t += (" */\n"); t += ("" + className + "&\n"); t += ("" + className + "::operator=(const " + className + "& obj)\n"); t += ("{\n"); t += (" if (this != &obj) {\n"); t += (" " + derivedFromClassName + "::operator=(obj);\n"); t += (" this->copyHelper" + className + "(obj);\n"); t += (" }\n"); t += (" return *this; \n"); t += ("}\n"); t += ("\n"); t += ("/**\n"); t += (" * Helps with copying an object of this type.\n"); t += (" * @param obj\n"); t += (" * Object that is copied.\n"); t += (" */\n"); t += ("void \n"); t += ("" + className + "::copyHelper" + className + "(const " + className + "& obj)\n"); t += ("{\n"); t += (" \n"); t += ("}\n"); t += ("\n"); } if (derivedFromClassName == "CaretObject") { t += ("/**\n"); t += (" * Get a description of this object's content.\n"); t += (" * @return String describing this object's content.\n"); t += (" */\n"); t += ("AString \n"); t += ("" + className + "::toString() const\n"); t += ("{\n"); t += (" return \"" + className + "\";\n"); t += ("}\n"); t += ("\n"); } if (hasEventListener) { t += ("/**\n"); t += (" * Receive an event.\n"); t += (" *\n"); t += (" * @param event\n"); t += (" * An event for which this instance is listening.\n"); t += (" */\n"); t += ("void\n"); t += (className + "::receiveEvent(Event* event)\n"); t += ("{\n"); t += ("// if (event->getEventType() == EventTypeEnum::) {\n"); t += ("// eventName = dynamic_cast(event);\n"); t += ("// CaretAssert(eventName);\n"); t += ("//\n"); t += ("// event->setEventProcessed();\n"); t += ("// }\n"); t += ("}\n"); t += ("\n"); } if (hasSceneInterface) { t += ("/**\n"); t += (" * Save information specific to this type of model to the scene.\n"); t += (" *\n"); t += (" * @param sceneAttributes\n"); t += (" * Attributes for the scene. Scenes may be of different types\n"); t += (" * (full, generic, etc) and the attributes should be checked when\n"); t += (" * saving the scene.\n"); t += (" *\n"); t += (" * @param instanceName\n"); t += (" * Name of instance in the scene.\n"); t += (" */\n"); t += ("SceneClass*\n"); t += (className + "::saveToScene(const SceneAttributes* sceneAttributes,\n"); t += (" const AString& instanceName)\n"); t += ("{\n"); t += (" SceneClass* sceneClass = new SceneClass(instanceName,\n"); t += (" \"" + className + "\",\n"); t += (" 1);\n"); t += (" m_sceneAssistant->saveMembers(sceneAttributes,\n"); t += (" sceneClass);\n"); t += (" \n"); t += (" // Uncomment if sub-classes must save to scene\n"); t += (" //saveSubClassDataToScene(sceneAttributes,\n"); t += (" // sceneClass);\n"); t += (" \n"); t += (" return sceneClass;\n"); t += ("}\n"); t += ("\n"); t += ("/**\n"); t += (" * Restore information specific to the type of model from the scene.\n"); t += (" *\n"); t += (" * @param sceneAttributes\n"); t += (" * Attributes for the scene. Scenes may be of different types\n"); t += (" * (full, generic, etc) and the attributes should be checked when\n"); t += (" * restoring the scene.\n"); t += (" *\n"); t += (" * @param sceneClass\n"); t += (" * sceneClass from which model specific information is obtained.\n"); t += (" */\n"); t += ("void\n"); t += (className + "::restoreFromScene(const SceneAttributes* sceneAttributes,\n"); t += (" const SceneClass* sceneClass)\n"); t += ("{\n"); t += (" if (sceneClass == NULL) {\n"); t += (" return;\n"); t += (" }\n"); t += (" \n"); t += (" m_sceneAssistant->restoreMembers(sceneAttributes,\n"); t += (" sceneClass); \n"); t += (" \n"); t += (" //Uncomment if sub-classes must restore from scene\n"); t += (" //restoreSubClassDataFromScene(sceneAttributes,\n"); t += (" // sceneClass);\n"); t += (" \n"); t += ("}\n"); t += ("\n"); } if (hasSubClassSceneSaving) { t += ("/**\n"); t += (" * Save subclass data to the scene.\n"); t += (" *\n"); t += (" * @param sceneAttributes\n"); t += (" * Attributes for the scene. Scenes may be of different types\n"); t += (" * (full, generic, etc) and the attributes should be checked when\n"); t += (" * restoring the scene.\n"); t += (" *\n"); t += (" * @param sceneClass\n"); t += (" * sceneClass to which data members should be added. Will always\n"); t += (" * be valid (non-NULL).\n"); t += (" */\n"); t += ("void\n"); t += (className + "::saveSubClassDataToScene(const SceneAttributes* sceneAttributes,\n"); t += (" SceneClass* sceneClass)\n"); t += ("{\n"); t += (" m_sceneAssistant->saveMembers(sceneAttributes,\n"); t += (" sceneClass);\n"); t += ("}\n"); t += ("\n"); t += ("/**\n"); t += (" * Restore file data from the scene.\n"); t += (" *\n"); t += (" * @param sceneAttributes\n"); t += (" * Attributes for the scene. Scenes may be of different types\n"); t += (" * (full, generic, etc) and the attributes should be checked when\n"); t += (" * restoring the scene.\n"); t += (" *\n"); t += (" * @param sceneClass\n"); t += (" * sceneClass for the instance of a class that implements\n"); t += (" * this interface. Will NEVER be NULL.\n"); t += (" */\n"); t += ("void\n"); t += (className + "::restoreSubClassDataFromScene(const SceneAttributes* sceneAttributes,\n"); t += (" const SceneClass* sceneClass)\n"); t += ("{\n"); t += (" m_sceneAssistant->restoreMembers(sceneAttributes,\n"); t += (" sceneClass);\n"); t += ("}\n"); t += ("\n"); } TextFile tf; tf.replaceText(t); try { tf.writeFile(outputFileName); } catch (const DataFileException& e) { throw CommandException(e); } } workbench-1.1.1/src/Commands/CommandClassCreate.h000066400000000000000000000060611255417355300217020ustar00rootroot00000000000000#ifndef __COMMAND_CLASS_CREATE__H__ #define __COMMAND_CLASS_CREATE__H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CommandClassCreateBase.h" namespace caret { /// Command that creates class files. class CommandClassCreate : public CommandClassCreateBase { public: CommandClassCreate(); virtual ~CommandClassCreate(); virtual void executeOperation(ProgramParameters& parameters); AString getHelpInformation(const AString& /*programName*/); /** This String is used by the command that adds new members to a class */ static AString getNewMembersString() { return "// ADD_NEW_MEMBERS_HERE"; } /** This String is used by the command that adds new METHODS to a class */ static AString getNewMethodsString() { return "// ADD_NEW_METHODS_HERE"; } private: CommandClassCreate(const CommandClassCreate&); CommandClassCreate& operator=(const CommandClassCreate&); void createHeaderFile(const AString& outputFileName, const AString& className, const AString& derivedFromClassName, const AString& ifdefName, const AString& ifdefNameStaticDeclaration, const bool hasCopyAndAssignment, const bool hasEventListener, const bool hasSceneInterface, const bool hasSubClassSceneSaving); void createImplementationFile(const AString& outputFileName, const AString& className, const AString& derivedFromClassName, const AString& eventTypeEnumName, const AString& ifdefNameStaticDeclaration, const bool hasCopyAndAssignment, const bool hasEventListener, const bool hasSceneInterface, const bool hasSubClassSceneSaving); }; } // namespace #endif // __COMMAND_CLASS_CREATE_H__ workbench-1.1.1/src/Commands/CommandClassCreateAlgorithm.cxx000066400000000000000000000373451255417355300241350ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include "CaretAssertion.h" #include "CommandClassCreateAlgorithm.h" #include "DataFileException.h" #include "FileInformation.h" #include "ProgramParameters.h" #include "TextFile.h" using namespace caret; /** * Constructor. */ CommandClassCreateAlgorithm::CommandClassCreateAlgorithm() : CommandClassCreateBase("-class-create-algorithm", "CREATE SOURCE CODE CLASS FILES (.h, .cxx) FOR ALGORITHM") { } /** * Destructor. */ CommandClassCreateAlgorithm::~CommandClassCreateAlgorithm() { } /** * @return The help information. */ AString CommandClassCreateAlgorithm::getHelpInformation(const AString& /*programName*/) { AString helpInfo = ("\n" "Create Algorithm Class header (.h) and implementation (.cxx) files.\n" "\n" "Usage: \n" " \n" " \n" "\n" " algorithm-class-name\n" " Required name of the algorithm class that MUST start with \"Algorithm\"\n" " \n" " command-line-switch\n" " Required command line switch for algorithm.\n" " \n" " short-description\n" " Required short description within double quotes.\n" " \n" ); return helpInfo; } /** * Execute the operation. * * @param parameters * Parameters for the operation. * @throws CommandException * If the command failed. * @throws ProgramParametersException * If there is an error in the parameters. */ void CommandClassCreateAlgorithm::executeOperation(ProgramParameters& parameters) { const AString algorithmClassName = parameters.nextString("Algorithm Class Name"); const AString commandLineSwitch = parameters.nextString("Command Line Switch"); const AString shortDescription = parameters.nextString("Short Description"); if (parameters.hasNext()) { const AString param = parameters.nextString("Parameter"); throw CommandException("Unexpected extra parameter: " + param); } AString errorMessage; if (algorithmClassName.isEmpty()) { throw CommandException("Algorithm Class Name is empty."); } else { if (algorithmClassName.startsWith("Algorithm") == false) { throw CommandException("Algorithm Class Name must start with \"Algorithm\".\n"); } if (algorithmClassName == "Algorithm") { throw CommandException("\"Algorithm\" is not allowed for Algorithm Class Name"); } } if (commandLineSwitch.isEmpty()) { throw CommandException("Command line switch is empty."); } else if (commandLineSwitch.startsWith("-") == false) { throw CommandException("Command line must begin with \"-\"."); } if (shortDescription.isEmpty()) { throw CommandException("Short description is empty."); } const AString headerFileName = algorithmClassName + ".h"; const AString implementationFileName = algorithmClassName + ".cxx"; FileInformation headerInfo(headerFileName); if (headerInfo.exists()) { errorMessage += headerFileName + " exists and this command will not overwrite it.\n"; } FileInformation impInfo(implementationFileName); if (impInfo.exists()) { errorMessage += implementationFileName + " exists and this command will not overwrite it.\n"; } if (errorMessage.isEmpty() == false) { throw CommandException(errorMessage); } AString ifndefName; AString ifdefNameStaticDeclarations; this->getIfDefNames(algorithmClassName, ifndefName, ifdefNameStaticDeclarations); this->createHeaderFile(headerFileName, algorithmClassName, ifndefName); this->createImplementationFile(implementationFileName, algorithmClassName, commandLineSwitch, shortDescription); } /** * Create and write the header (.h) file. * * @param outputFileName * Name for file that is written. * @param algorithmClassName * Name of algorithm type class. * @param ifndefName * Name of "ifndef" value. */ void CommandClassCreateAlgorithm::createHeaderFile(const AString& outputFileName, const AString& algorithmClassName, const AString& ifndefName) { AString t; t += ("#ifndef " + ifndefName + "\n"); t += ("#define " + ifndefName + "\n"); t += this->getCopyright(); t += ("\n"); t += ("#include \"AbstractAlgorithm.h\"\n"); t += ("\n"); t += ("namespace caret {\n"); t += ("\n"); t += (" class " + algorithmClassName + " : public AbstractAlgorithm {\n"); t += ("\n"); t += (" private:\n"); t += (" " + algorithmClassName + "(); \n"); t += ("\n"); t += (" protected:\n"); t += (" static float getSubAlgorithmWeight();\n"); t += ("\n"); t += (" static float getAlgorithmInternalWeight();\n"); t += ("\n"); t += (" public:\n"); t += (" " + algorithmClassName + "(ProgressObject* myProgObj /*INSERT PARAMETERS HERE*/); \n"); t += ("\n"); t += (" static OperationParameters* getParameters();\n"); t += ("\n"); t += (" static void useParameters(OperationParameters* myParams, \n"); t += (" ProgressObject* myProgObj);\n"); t += ("\n"); t += (" static AString getCommandSwitch();\n"); t += ("\n"); t += (" static AString getShortDescription();\n"); t += ("\n"); t += (" };\n"); t += ("\n"); t += (" typedef TemplateAutoOperation<" + algorithmClassName + "> Auto" + algorithmClassName + ";\n"); t += ("\n"); t += ("} // namespace\n"); t += ("\n"); t += ("#endif //" + ifndefName + "\n"); t += ("\n"); TextFile tf; tf.replaceText(t); try { tf.writeFile(outputFileName); } catch (const DataFileException& e) { throw CommandException(e); } } /** * Create and write the implementation (.cxx) file. * * @param outputFileName * Name for file that is written. * @param algorithmClassName * Name of algorithm type class. * @param commandLineSwitch * Command line switch for algorithm. * @param shortDescription * Short description of algorithm. */ void CommandClassCreateAlgorithm::createImplementationFile(const AString& outputFileName, const AString& algorithmClassName, const AString& commandLineSwitch, const AString& shortDescription) { AString t; t += this->getCopyright(); t += ("#include \"CaretAssert.h\"\n"); t += ("#include \"CaretLogger.h\"\n"); t += ("\n"); t += ("#include \"" + algorithmClassName + ".h\"\n"); t += ("#include \"AlgorithmException.h\"\n"); t += ("\n"); t += ("using namespace caret;\n"); t += ("\n"); t += ("/**\n"); t += (" * \\class caret::" + algorithmClassName + " \n"); t += (" * \\brief " + shortDescription + "\n"); t += (" *\n"); t += (" * \n"); t += (" */\n"); t += ("\n"); t += ("/**\n"); t += (" * @return Command line switch\n"); t += (" */\n"); t += ("AString\n"); t += ("" + algorithmClassName + "::getCommandSwitch()\n"); t += ("{\n"); t += (" return \"" + commandLineSwitch + "\";\n"); t += ("}\n"); t += ("\n"); t += ("/**\n"); t += (" * @return Short description of algorithm\n"); t += (" */\n"); t += ("AString\n"); t += ("" + algorithmClassName + "::getShortDescription()\n"); t += ("{\n"); t += (" return \"" + shortDescription.toUpper() + "\";\n"); t += ("}\n"); t += ("\n"); t += ("/**\n"); t += (" * @return Parameters for algorithm\n"); t += (" */\n"); t += ("OperationParameters*\n"); t += ("" + algorithmClassName + "::getParameters()\n"); t += ("{\n"); t += (" OperationParameters* ret = new OperationParameters();\n"); t += (" \n"); t += (" /*\n"); t += (" * Example parameters:\n"); t += (" *\n"); t += (" * ret->addSurfaceParameter(1, \"surface\", \"the surface to compute on\");\n"); t += (" *\n"); t += (" * ret->addMetricOutputParameter(2, \"metric\", \"the output metric\");\n"); t += (" *\n"); t += (" * OptionalParameter* columnSelect = ret->createOptionalParameter(3, \"-column\", \"select a single column\");\n"); t += (" *\n"); t += (" * columnSelect->addStringParameter(1, \"column\", \"the column number or name\")\n"); t += (" */\n"); t += (" AString helpText = (\"This is where you set the help text. DO NOT add the info about what the command line format is\"\n"); t += (" \"and do not give the command switch, short description, or the short descriptions of parameters. Do not indent,\"\n"); t += (" \"add newlines, or format the text in any way other than to separate paragraphs within the help text prose\");\n"); t += ("\n"); t += (" ret->setHelpText(helpText);\n"); t += (" \n"); t += (" return ret;\n"); t += ("}\n"); t += ("\n"); t += ("/**\n"); t += (" * Use Parameters and perform algorithm\n"); t += (" * @param myParams\n"); t += (" * Parameters for algorithm\n"); t += (" * @param myProgObj\n"); t += (" * The progress object\n"); t += (" * @throws\n"); t += (" * AlgorithmException if errors\n"); t += (" */\n"); t += ("void\n"); t += ("" + algorithmClassName + "::useParameters(OperationParameters* myParams,\n"); t += (" ProgressObject* myProgObj)\n"); t += ("{\n"); t += (" /*\n"); t += (" * Example parameter processing:\n"); t += (" *\n"); t += (" * Gets the surface with key 1\n"); t += (" * SurfaceFile* mySurf = myParams->getSurface(1);\n"); t += (" *\n"); t += (" * Gets the output metric with key 2\n"); t += (" * MetricFile* myMetricOut = myParams->getOutputMetric(2);\n"); t += (" *\n"); t += (" * Gets optional parameter with key 3\n"); t += (" * OptionalParameter* columnSelect = myParams->getOptionalParameter(3);\n"); t += (" * int columnNum = -1;\n"); t += (" * if (columnSelect->m_present) {\n"); t += (" * columnNum = (int)myMetric->getMapIndexFromNameOrNumber(columnSelect->getString(1));\n"); t += (" * if (columnNum < 0) {\n"); t += (" * throw AlgorithmException(\"invalid column specified\");\n"); t += (" * }\n"); t += (" * }\n"); t += (" */\n"); t += (" \n"); t += (" /*\n"); t += (" * Constructs and executes the algorithm \n"); t += (" */\n"); t += (" " + algorithmClassName + "(myProgObj /* INSERT PARAMTERS HERE */);\n"); t += (" \n"); t += ("}\n"); t += ("\n"); t += ("/**\n"); t += (" * Constructor\n"); t += (" *\n"); t += (" * Calling the constructor will execute the algorithm\n"); t += (" *\n"); t += (" * @param myProgObj\n"); t += (" * Parameters for algorithm\n"); t += (" */\n"); t += (algorithmClassName + "::" + algorithmClassName + "(ProgressObject* myProgObj /* INSERT PARAMTERS HERE - may get compilation error if no parameters added */)\n"); t += (" : AbstractAlgorithm(myProgObj)\n"); t += ("{\n"); t += (" /*\n"); t += (" * Uncomment these if you use another algorithm inside here\n"); t += (" *\n"); t += (" * ProgressObject* subAlgProgress1 = NULL;\n"); t += (" * if (myProgObj != NULL) {\n"); t += (" * subAlgProgress1 = myProgObj->addAlgorithm(AlgorithmInsertNameHere::getAlgorithmWeight());\n"); t += (" * }\n"); t += (" */\n"); t += (" \n"); t += (" /*\n"); t += (" * Sets the algorithm up to use the progress object, and will \n"); t += (" * finish the progress object automatically when the algorithm terminates\n"); t += (" */\n"); t += (" LevelProgress myProgress(myProgObj);\n"); t += (" \n"); t += (" /*\n"); t += (" * How you say you are halfway done with the INTERNAL work of the algorithm\n"); t += (" * will report finished automatically when this function ends (myProgress goes out \n"); t += (" of scope, destructor triggers finish\n"); t += (" */\n"); t += (" //myProgress.reportProgress(0.5f);\n"); t += (" \n"); t += ("}\n"); t += ("\n"); t += ("/**\n"); t += (" * @return Algorithm internal weight\n"); t += (" */\n"); t += ("float\n"); t += ("" + algorithmClassName + "::getAlgorithmInternalWeight()\n"); t += ("{\n"); t += (" /*\n"); t += (" * override this if needed, if the progress bar isn't smooth\n"); t += (" */\n"); t += (" return 1.0f;\n"); t += ("}\n"); t += ("\n"); t += ("/**\n"); t += (" * @return Algorithm sub-algorithm weight\n"); t += (" */\n"); t += ("float\n"); t += ("" + algorithmClassName + "::getSubAlgorithmWeight()\n"); t += ("{\n"); t += (" /*\n"); t += (" * If you use a subalgorithm\n"); t += (" */\n"); t += (" //return AlgorithmInsertNameHere::getAlgorithmWeight()\n"); t += (" return 0.0f;\n"); t += ("}\n"); t += ("\n"); TextFile tf; tf.replaceText(t); try { tf.writeFile(outputFileName); } catch (const DataFileException& e) { throw CommandException(e); } std::cout << std::endl; std::cout << "Algorithm was created successfully." << std::endl; std::cout << std::endl; std::cout << "Add the class files to Algorithms/CMakeLists.txt:" << std::endl; std::cout << " " << qPrintable(algorithmClassName) << ".h" << std::endl; std::cout << " " << qPrintable(algorithmClassName) << ".cxx" << std::endl; std::cout << std::endl; std::cout << "Add the header file and algorithm to Commands/CommandOperationManager.cxx:" << std::endl; std::cout << " #include \"" << qPrintable(algorithmClassName) << ".h\"" << std::endl; std::cout << " this->commandOperations.push_back(new CommandParser(new Auto" << algorithmClassName << "()));" << std::endl; std::cout << std::endl; } workbench-1.1.1/src/Commands/CommandClassCreateAlgorithm.h000066400000000000000000000041311255417355300235450ustar00rootroot00000000000000#ifndef __COMMAND_CLASS_CREATE_ALGORITHM_H__ #define __COMMAND_CLASS_CREATE_ALGORITHM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CommandClassCreateBase.h" namespace caret { /// Command that creates class files for an algorithm class CommandClassCreateAlgorithm : public CommandClassCreateBase { public: CommandClassCreateAlgorithm(); virtual ~CommandClassCreateAlgorithm(); virtual void executeOperation(ProgramParameters& parameters); AString getHelpInformation(const AString& /*programName*/); private: CommandClassCreateAlgorithm(const CommandClassCreateAlgorithm&); CommandClassCreateAlgorithm& operator=(const CommandClassCreateAlgorithm&); void createHeaderFile(const AString& outputFileName, const AString& algorithmClassName, const AString& ifndefName); void createImplementationFile(const AString& outputFileName, const AString& algorithmClassName, const AString& commandLineSwitch, const AString& shortDescription); }; } // namespace #endif // __COMMAND_CLASS_CREATE_ALGORITHM_H__ workbench-1.1.1/src/Commands/CommandClassCreateBase.cxx000066400000000000000000000111771255417355300230540ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "CaretAssert.h" #include "CommandClassCreateBase.h" #include "ProgramParameters.h" #include "SystemUtilities.h" using namespace caret; /** * Constructor. */ CommandClassCreateBase::CommandClassCreateBase(const AString& commandLineSwitch, const AString& operationShortDescription) : CommandOperation(commandLineSwitch, operationShortDescription) { } /** * Destructor. */ CommandClassCreateBase::~CommandClassCreateBase() { } /** * Get the names for use with #ifdef declarations. * @param classNameIn * Name of class. * @param ifndefNameOut * Name for use with #ifndef at beginning of file. * @param ifdefNameStaticDeclarationOut * Name for use when declarating static members at * end of the header file. */ void CommandClassCreateBase::getIfDefNames(const AString& classNameIn, AString& ifndefNameOut, AString& ifdefNameStaticDeclarationOut) { ifndefNameOut = ""; ifdefNameStaticDeclarationOut = ""; const int32_t classNameLength = classNameIn.length(); ifndefNameOut += "_"; for (int32_t i = 0; i < classNameLength; i++) { QChar c = classNameIn[i]; if (c.isUpper()) { ifndefNameOut += "_"; } ifndefNameOut += c.toUpper(); } ifdefNameStaticDeclarationOut = ifndefNameOut + "_DECLARE__"; ifndefNameOut += "_H__"; } /** * Get the copyright. * @return Text containing copyright. */ AString CommandClassCreateBase::getCopyright() { const AString year = SystemUtilities::getYear(); AString text; text.append("\n"); text.append("/*LICENSE_START*/\n"); text.append("/*\n"); text.append(" * Copyright (C) " + year + " Washington University School of Medicine\n"); text.append(" *\n"); text.append(" * This program is free software; you can redistribute it and/or modify\n"); text.append(" * it under the terms of the GNU General Public License as published by\n"); text.append(" * the Free Software Foundation; either version 2 of the License, or\n"); text.append(" * (at your option) any later version.\n"); text.append(" *\n"); text.append(" * This program is distributed in the hope that it will be useful,\n"); text.append(" * but WITHOUT ANY WARRANTY; without even the implied warranty of\n"); text.append(" * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"); text.append(" * GNU General Public License for more details.\n"); text.append(" *\n"); text.append(" * You should have received a copy of the GNU General Public License along\n"); text.append(" * with this program; if not, write to the Free Software Foundation, Inc.,\n"); text.append(" * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n"); text.append(" */\n"); text.append("/*LICENSE_END*/\n"); text.append("\n"); return text; } /** * Get the #include declaration for an include file. * @param includeFileName * Name of include file. * @return * Include declaration (no newline at end of line). */ AString CommandClassCreateBase::getIncludeDeclaration(const AString& includeFileName) const { if (includeFileName.isEmpty()) { return ""; } AString txt = "#include "; const QChar firstChar = includeFileName[0]; if (firstChar.isLower()) { txt += ("<" + includeFileName + ">"); } else if (firstChar == 'Q') { txt += ("<" + includeFileName + ">"); } else { AString dotH = ""; if (includeFileName.endsWith(".h") == false) { dotH = ".h"; } txt += ("\"" + includeFileName + dotH + "\""); } return txt; } workbench-1.1.1/src/Commands/CommandClassCreateBase.h000066400000000000000000000034731255417355300225010ustar00rootroot00000000000000#ifndef __COMMAND_CLASS_CREATE_BASE_H__ #define __COMMAND_CLASS_CREATE_BASE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CommandOperation.h" namespace caret { /// Base class for creating class files. class CommandClassCreateBase : public CommandOperation { protected: CommandClassCreateBase(const AString& commandLineSwitch, const AString& operationShortDescription); public: virtual ~CommandClassCreateBase(); private: CommandClassCreateBase(const CommandClassCreateBase&); CommandClassCreateBase& operator=(const CommandClassCreateBase&); protected: AString getIncludeDeclaration(const AString& includeFileName) const; AString getCopyright(); void getIfDefNames(const AString& className, AString& ifdefName, AString& ifdefNameStaticDeclaration); }; } // namespace #endif // __COMMAND_CLASS_CREATE_BASE_H__ workbench-1.1.1/src/Commands/CommandClassCreateEnum.cxx000066400000000000000000000721661255417355300231130ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include "CaretAssertion.h" #include "CommandClassCreateEnum.h" #include "DataFileException.h" #include "FileInformation.h" #include "ProgramParameters.h" #include "TextFile.h" using namespace caret; /** * Constructor. */ CommandClassCreateEnum::CommandClassCreateEnum() : CommandClassCreateBase("-class-create-enum", "CREATE SOURCE CODE CLASS FILES (.h, .cxx) FOR ENUMERATED TYPE") { } /** * Destructor. */ CommandClassCreateEnum::~CommandClassCreateEnum() { } AString CommandClassCreateEnum::getHelpInformation(const AString& /*programName*/) { AString helpInfo = ("\n" "Create enumerated type header (.h) and implementation (.cxx) files.\n" "\n" "Usage: \n" " \n" " \n" "\n" " enum-class-name\n" " Name of the enumerated type. Must end in \"Enum\"\n" " \n" " number-of-values\n" " Number of values in the enumerated type.\n" " \n" " auto-number\n" " Automatically generated integer codes corresponding\n" " to the enumerated values. Value for this parameter\n" " are \"true\" and \"false\".\n" " \n" " [enum-name-1] [enum-name-2]...[enum-name-N]\n" " Optional names for the enumerated values. \n" " \n" " If the number of names listed is greater than\n" " the \"number-of-values\" parameter, the \"number-of-values\"\n" " will become the number of names. If the number\n" " of names is is less than the \"number-of-values\",\n" " empty entries will be created.\n" " \n" ); return helpInfo; } /** * Execute the operation. * * @param parameters * Parameters for the operation. * @throws CommandException * If the command failed. * @throws ProgramParametersException * If there is an error in the parameters. */ void CommandClassCreateEnum::executeOperation(ProgramParameters& parameters) { const AString enumClassName = parameters.nextString("Enum Class Name"); int32_t numberOfEnumValues = parameters.nextInt("Number of Enum Values"); const bool isAutoNumber = parameters.nextBoolean("Auto Number (true/false)"); std::vector enumValueNames; while (parameters.hasNext()) { enumValueNames.push_back(parameters.nextString("Enum Value")); } const int32_t numEnumValueNames = static_cast(enumValueNames.size()); if (numEnumValueNames > 0) { if (numEnumValueNames > numberOfEnumValues) { numberOfEnumValues = numEnumValueNames; } } if (enumClassName.isEmpty()) { throw CommandException("Enum class name is empty."); } AString errorMessage; if (enumClassName.endsWith("Enum") == false) { errorMessage += "Name of class MUST end with \"Enum\".\n"; } if (enumClassName[0].isLower()) { errorMessage += "First letter of class name must be upper case.\n"; } const AString headerFileName = enumClassName + ".h"; const AString implementationFileName = enumClassName + ".cxx"; FileInformation headerInfo(headerFileName); if (headerInfo.exists()) { errorMessage += headerFileName + " exists and this command will not overwrite it.\n"; } FileInformation impInfo(implementationFileName); if (impInfo.exists()) { errorMessage += implementationFileName + " exists and this command will not overwrite it.\n"; } if (errorMessage.isEmpty() == false) { throw CommandException(errorMessage); } AString ifndefName; AString ifdefNameStaticDeclarations; this->getIfDefNames(enumClassName, ifndefName, ifdefNameStaticDeclarations); this->createHeaderFile(headerFileName, enumClassName, ifndefName, ifdefNameStaticDeclarations, numberOfEnumValues, enumValueNames, isAutoNumber); this->createImplementationFile(implementationFileName, enumClassName, ifdefNameStaticDeclarations, numberOfEnumValues, enumValueNames, isAutoNumber); } /** * Create and write the header (.h) file. * * @param outputFileName * Name for file that is written. * @param enumClassName * Name of enumerated type class. * @param ifdefName * Name of "ifndef" value. * @param ifdefNameStaticDeclaration * Name for "infdef" of static declarations. * @param numberOfEnumValues * Number of enumerated type values. * @param enumValueNames * Names for the enumerated values. * @param isAutoNumber * Automatically assign numers/indices to the enumerated values. */ void CommandClassCreateEnum::createHeaderFile(const AString& outputFileName, const AString& enumClassName, const AString& ifndefName, const AString& ifdefNameStaticDeclaration, const int32_t numberOfEnumValues, const std::vector& enumValueNames, const bool isAutoNumber) { AString t; t += ("#ifndef " + ifndefName + "\n"); t += ("#define " + ifndefName + "\n"); t += this->getCopyright(); t += ("\n"); t += ("#include \n"); t += ("#include \n"); t += ("#include \"AString.h\"\n"); t += ("\n"); t += ("namespace caret {\n"); t += ("\n"); t += ("class " + enumClassName + " {\n"); t += ("\n"); t += ("public:\n"); t += (" /**\n"); t += (" * Enumerated values.\n"); t += (" */\n"); t += (" enum Enum {\n"); for (int indx = 0; indx < numberOfEnumValues; indx++) { t += (" /** */\n"); t += (" "); if (indx < static_cast(enumValueNames.size())) { t +=enumValueNames[indx]; } if (indx < (numberOfEnumValues - 1)) { t += (",\n"); } else { t += ("\n"); } } t += (" };\n"); t += ("\n"); t += ("\n"); t += (" ~" + enumClassName + "();\n"); t += ("\n"); t += (" static AString toName(Enum enumValue);\n"); t += (" \n"); t += (" static Enum fromName(const AString& name, bool* isValidOut);\n"); t += (" \n"); t += (" static AString toGuiName(Enum enumValue);\n"); t += (" \n"); t += (" static Enum fromGuiName(const AString& guiName, bool* isValidOut);\n"); t += (" \n"); t += (" static int32_t toIntegerCode(Enum enumValue);\n"); t += (" \n"); t += (" static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut);\n"); t += ("\n"); t += (" static void getAllEnums(std::vector& allEnums);\n"); t += ("\n"); t += (" static void getAllNames(std::vector& allNames, const bool isSorted);\n"); t += ("\n"); t += (" static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted);\n"); t += ("\n"); t += ("private:\n"); t += (" " + enumClassName + "(const Enum enumValue, \n"); if (isAutoNumber == false) { t += (" const int32_t integerCode, \n"); } t += (" const AString& name,\n"); t += (" const AString& guiName);\n"); t += ("\n"); t += (" static const " + enumClassName + "* findData(const Enum enumValue);\n"); t += ("\n"); t += (" /** Holds all instance of enum values and associated metadata */\n"); t += (" static std::vector<" + enumClassName + "> enumData;\n"); t += ("\n"); t += (" /** Initialize instances that contain the enum values and metadata */\n"); t += (" static void initialize();\n"); t += ("\n"); t += (" /** Indicates instance of enum values and metadata have been initialized */\n"); t += (" static bool initializedFlag;\n"); t += (" \n"); if (isAutoNumber) { t += (" /** Auto generated integer codes */\n"); t += (" static int32_t integerCodeCounter;\n"); t += (" \n"); } t += (" /** The enumerated type value for an instance */\n"); t += (" Enum enumValue;\n"); t += ("\n"); t += (" /** The integer code associated with an enumerated value */\n"); t += (" int32_t integerCode;\n"); t += ("\n"); t += (" /** The name, a text string that is identical to the enumerated value */\n"); t += (" AString name;\n"); t += (" \n"); t += (" /** A user-friendly name that is displayed in the GUI */\n"); t += (" AString guiName;\n"); t += ("};\n"); t += ("\n"); t += ("#ifdef " + ifdefNameStaticDeclaration + "\n"); t += ("std::vector<" + enumClassName + "> " + enumClassName + "::enumData;\n"); t += ("bool " + enumClassName + "::initializedFlag = false;\n"); if (isAutoNumber) { t += ("int32_t " + enumClassName + "::integerCodeCounter = 0; \n"); } t += ("#endif // " + ifdefNameStaticDeclaration + "\n"); t += ("\n"); t += ("} // namespace\n"); t += ("#endif //" + ifndefName + "\n"); TextFile tf; tf.replaceText(t); try { tf.writeFile(outputFileName); } catch (const DataFileException& e) { throw CommandException(e); } } /** * Create and write the implementation (.cxx) file. * * @param outputFileName * Name for file that is written. * @param enumClassName * Name of enumerated type class. * @param ifdefNameStaticDeclaration * Name for "infdef" of static declarations. * @param numberOfEnumValues * Number of enumerated type values. * @param enumValueNames * Names for the enumerated values. * @param isAutoNumber * Automatically assign numers/indices to the enumerated values. */ void CommandClassCreateEnum::createImplementationFile(const AString& outputFileName, const AString& enumClassName, const AString& ifdefNameStaticDeclaration, const int32_t numberOfEnumValues, const std::vector& enumValueNames, const bool isAutoNumber) { AString t; t += this->getCopyright(); t += ("#include \n"); t += ("#define " + ifdefNameStaticDeclaration + "\n"); t += ("#include \"" + enumClassName + ".h\"\n"); t += ("#undef " + ifdefNameStaticDeclaration + "\n"); t += ("\n"); t += ("#include \"CaretAssert.h\"\n"); t += ("\n"); t += ("using namespace caret;\n"); t += ("\n"); t += (" \n"); t += ("/**\n"); t += (" * \\class caret::" + enumClassName + " \n"); t += (" * \\brief \n"); t += (" *\n"); t += (" * \n"); t += (" *\n"); t += (getEnumComboBoxTemplateHelpInfo(enumClassName)); t += (" */\n"); t += ("\n"); t += ("/**\n"); t += (" * Constructor.\n"); t += (" *\n"); t += (" * @param enumValue\n"); t += (" * An enumerated value.\n"); if (isAutoNumber == false) { t += (" * @param integerCode\n"); t += (" * Integer code for this enumerated value.\n"); t += (" *\n"); } t += (" * @param name\n"); t += (" * Name of enumerated value.\n"); t += (" *\n"); t += (" * @param guiName\n"); t += (" * User-friendly name for use in user-interface.\n"); t += (" */\n"); t += ("" + enumClassName + "::" + enumClassName + "(const Enum enumValue,\n"); if (isAutoNumber == false) { t += (" const int32_t integerCode,\n"); } t += (" const AString& name,\n"); t += (" const AString& guiName)\n"); t += ("{\n"); t += (" this->enumValue = enumValue;\n"); if (isAutoNumber) { t += (" this->integerCode = integerCodeCounter++;\n"); } else { t += (" this->integerCode = integerCode;\n"); } t += (" this->name = name;\n"); t += (" this->guiName = guiName;\n"); t += ("}\n"); t += ("\n"); t += ("/**\n"); t += (" * Destructor.\n"); t += (" */\n"); t += ("" + enumClassName + "::~" + enumClassName + "()\n"); t += ("{\n"); t += ("}\n"); t += ("\n"); t += ("/**\n"); t += (" * Initialize the enumerated metadata.\n"); t += (" */\n"); t += ("void\n"); t += ("" + enumClassName + "::initialize()\n"); t += ("{\n"); t += (" if (initializedFlag) {\n"); t += (" return;\n"); t += (" }\n"); t += (" initializedFlag = true;\n"); t += ("\n"); for (int32_t indx = 0; indx < numberOfEnumValues; indx++) { AString name = ""; if (indx < static_cast(enumValueNames.size())) { name = enumValueNames[indx]; } t += (" enumData.push_back(" + enumClassName + "(" + name + ", \n"); if (isAutoNumber == false) { t += (" " + AString::number(indx) + ", \n"); } t += (" \"" + name + "\", \n"); t += (" \"\"));\n"); t += (" \n"); } t += ("}\n"); t += ("\n"); t += ("/**\n"); t += (" * Find the data for and enumerated value.\n"); t += (" * @param enumValue\n"); t += (" * The enumerated value.\n"); t += (" * @return Pointer to data for this enumerated type\n"); t += (" * or NULL if no data for type or if type is invalid.\n"); t += (" */\n"); t += ("const " + enumClassName + "*\n"); t += ("" + enumClassName + "::findData(const Enum enumValue)\n"); t += ("{\n"); t += (" if (initializedFlag == false) initialize();\n"); t += ("\n"); t += (" size_t num = enumData.size();\n"); t += (" for (size_t i = 0; i < num; i++) {\n"); t += (" const " + enumClassName + "* d = &enumData[i];\n"); t += (" if (d->enumValue == enumValue) {\n"); t += (" return d;\n"); t += (" }\n"); t += (" }\n"); t += ("\n"); t += (" return NULL;\n"); t += ("}\n"); t += ("\n"); t += ("/**\n"); t += (" * Get a string representation of the enumerated type.\n"); t += (" * @param enumValue \n"); t += (" * Enumerated value.\n"); t += (" * @return \n"); t += (" * String representing enumerated value.\n"); t += (" */\n"); t += ("AString \n"); t += ("" + enumClassName + "::toName(Enum enumValue) {\n"); t += (" if (initializedFlag == false) initialize();\n"); t += (" \n"); t += (" const " + enumClassName + "* enumInstance = findData(enumValue);\n"); t += (" return enumInstance->name;\n"); t += ("}\n"); t += ("\n"); t += ("/**\n"); t += (" * Get an enumerated value corresponding to its name.\n"); t += (" * @param name \n"); t += (" * Name of enumerated value.\n"); t += (" * @param isValidOut \n"); t += (" * If not NULL, it is set indicating that a\n"); t += (" * enum value exists for the input name.\n"); t += (" * @return \n"); t += (" * Enumerated value.\n"); t += (" */\n"); t += ("" + enumClassName + "::Enum \n"); t += ("" + enumClassName + "::fromName(const AString& name, bool* isValidOut)\n"); t += ("{\n"); t += (" if (initializedFlag == false) initialize();\n"); t += (" \n"); t += (" bool validFlag = false;\n"); t += (" Enum enumValue = " + enumClassName + "::enumData[0].enumValue;\n"); t += (" \n"); t += (" for (std::vector<" + enumClassName + ">::iterator iter = enumData.begin();\n"); t += (" iter != enumData.end();\n"); t += (" iter++) {\n"); t += (" const " + enumClassName + "& d = *iter;\n"); t += (" if (d.name == name) {\n"); t += (" enumValue = d.enumValue;\n"); t += (" validFlag = true;\n"); t += (" break;\n"); t += (" }\n"); t += (" }\n"); t += (" \n"); t += (" if (isValidOut != 0) {\n"); t += (" *isValidOut = validFlag;\n"); t += (" }\n"); t += (" else if (validFlag == false) {\n"); t += (" CaretAssertMessage(0, AString(\"Name \" + name + \"failed to match enumerated value for type " + enumClassName + "\"));\n"); t += (" }\n"); t += (" return enumValue;\n"); t += ("}\n"); t += ("\n"); t += ("/**\n"); t += (" * Get a GUI string representation of the enumerated type.\n"); t += (" * @param enumValue \n"); t += (" * Enumerated value.\n"); t += (" * @return \n"); t += (" * String representing enumerated value.\n"); t += (" */\n"); t += ("AString \n"); t += ("" + enumClassName + "::toGuiName(Enum enumValue) {\n"); t += (" if (initializedFlag == false) initialize();\n"); t += (" \n"); t += (" const " + enumClassName + "* enumInstance = findData(enumValue);\n"); t += (" return enumInstance->guiName;\n"); t += ("}\n"); t += ("\n"); t += ("/**\n"); t += (" * Get an enumerated value corresponding to its GUI name.\n"); t += (" * @param s \n"); t += (" * Name of enumerated value.\n"); t += (" * @param isValidOut \n"); t += (" * If not NULL, it is set indicating that a\n"); t += (" * enum value exists for the input name.\n"); t += (" * @return \n"); t += (" * Enumerated value.\n"); t += (" */\n"); t += ("" + enumClassName + "::Enum \n"); t += ("" + enumClassName + "::fromGuiName(const AString& guiName, bool* isValidOut)\n"); t += ("{\n"); t += (" if (initializedFlag == false) initialize();\n"); t += (" \n"); t += (" bool validFlag = false;\n"); t += (" Enum enumValue = " + enumClassName + "::enumData[0].enumValue;\n"); t += (" \n"); t += (" for (std::vector<" + enumClassName + ">::iterator iter = enumData.begin();\n"); t += (" iter != enumData.end();\n"); t += (" iter++) {\n"); t += (" const " + enumClassName + "& d = *iter;\n"); t += (" if (d.guiName == guiName) {\n"); t += (" enumValue = d.enumValue;\n"); t += (" validFlag = true;\n"); t += (" break;\n"); t += (" }\n"); t += (" }\n"); t += (" \n"); t += (" if (isValidOut != 0) {\n"); t += (" *isValidOut = validFlag;\n"); t += (" }\n"); t += (" else if (validFlag == false) {\n"); t += (" CaretAssertMessage(0, AString(\"guiName \" + guiName + \"failed to match enumerated value for type " + enumClassName + "\"));\n"); t += (" }\n"); t += (" return enumValue;\n"); t += ("}\n"); t += ("\n"); t += ("/**\n"); t += (" * Get the integer code for a data type.\n"); t += (" *\n"); t += (" * @return\n"); t += (" * Integer code for data type.\n"); t += (" */\n"); t += ("int32_t\n"); t += ("" + enumClassName + "::toIntegerCode(Enum enumValue)\n"); t += ("{\n"); t += (" if (initializedFlag == false) initialize();\n"); t += (" const " + enumClassName + "* enumInstance = findData(enumValue);\n"); t += (" return enumInstance->integerCode;\n"); t += ("}\n"); t += ("\n"); t += ("/**\n"); t += (" * Find the data type corresponding to an integer code.\n"); t += (" *\n"); t += (" * @param integerCode\n"); t += (" * Integer code for enum.\n"); t += (" * @param isValidOut\n"); t += (" * If not NULL, on exit isValidOut will indicate if\n"); t += (" * integer code is valid.\n"); t += (" * @return\n"); t += (" * Enum for integer code.\n"); t += (" */\n"); t += ("" + enumClassName + "::Enum\n"); t += ("" + enumClassName + "::fromIntegerCode(const int32_t integerCode, bool* isValidOut)\n"); t += ("{\n"); t += (" if (initializedFlag == false) initialize();\n"); t += (" \n"); t += (" bool validFlag = false;\n"); t += (" Enum enumValue = " + enumClassName + "::enumData[0].enumValue;\n"); t += (" \n"); t += (" for (std::vector<" + enumClassName + ">::iterator iter = enumData.begin();\n"); t += (" iter != enumData.end();\n"); t += (" iter++) {\n"); t += (" const " + enumClassName + "& enumInstance = *iter;\n"); t += (" if (enumInstance.integerCode == integerCode) {\n"); t += (" enumValue = enumInstance.enumValue;\n"); t += (" validFlag = true;\n"); t += (" break;\n"); t += (" }\n"); t += (" }\n"); t += (" \n"); t += (" if (isValidOut != 0) {\n"); t += (" *isValidOut = validFlag;\n"); t += (" }\n"); t += (" else if (validFlag == false) {\n"); t += (" CaretAssertMessage(0, AString(\"Integer code \" + AString::number(integerCode) + \"failed to match enumerated value for type " + enumClassName + "\"));\n"); t += (" }\n"); t += (" return enumValue;\n"); t += ("}\n"); t += ("\n"); t += ("/**\n"); t += (" * Get all of the enumerated type values. The values can be used\n"); t += (" * as parameters to toXXX() methods to get associated metadata.\n"); t += (" *\n"); t += (" * @param allEnums\n"); t += (" * A vector that is OUTPUT containing all of the enumerated values.\n"); t += (" */\n"); t += ("void\n"); t += ("" + enumClassName + "::getAllEnums(std::vector<" + enumClassName + "::Enum>& allEnums)\n"); t += ("{\n"); t += (" if (initializedFlag == false) initialize();\n"); t += (" \n"); t += (" allEnums.clear();\n"); t += (" \n"); t += (" for (std::vector<" + enumClassName + ">::iterator iter = enumData.begin();\n"); t += (" iter != enumData.end();\n"); t += (" iter++) {\n"); t += (" allEnums.push_back(iter->enumValue);\n"); t += (" }\n"); t += ("}\n"); t += ("\n"); t += ("/**\n"); t += (" * Get all of the names of the enumerated type values.\n"); t += (" *\n"); t += (" * @param allNames\n"); t += (" * A vector that is OUTPUT containing all of the names of the enumerated values.\n"); t += (" * @param isSorted\n"); t += (" * If true, the names are sorted in alphabetical order.\n"); t += (" */\n"); t += ("void\n"); t += ("" + enumClassName + "::getAllNames(std::vector& allNames, const bool isSorted)\n"); t += ("{\n"); t += (" if (initializedFlag == false) initialize();\n"); t += (" \n"); t += (" allNames.clear();\n"); t += (" \n"); t += (" for (std::vector<" + enumClassName + ">::iterator iter = enumData.begin();\n"); t += (" iter != enumData.end();\n"); t += (" iter++) {\n"); t += (" allNames.push_back(" + enumClassName + "::toName(iter->enumValue));\n"); t += (" }\n"); t += (" \n"); t += (" if (isSorted) {\n"); t += (" std::sort(allNames.begin(), allNames.end());\n"); t += (" }\n"); t += ("}\n"); t += ("\n"); t += ("/**\n"); t += (" * Get all of the GUI names of the enumerated type values.\n"); t += (" *\n"); t += (" * @param allNames\n"); t += (" * A vector that is OUTPUT containing all of the GUI names of the enumerated values.\n"); t += (" * @param isSorted\n"); t += (" * If true, the names are sorted in alphabetical order.\n"); t += (" */\n"); t += ("void\n"); t += ("" + enumClassName + "::getAllGuiNames(std::vector& allGuiNames, const bool isSorted)\n"); t += ("{\n"); t += (" if (initializedFlag == false) initialize();\n"); t += (" \n"); t += (" allGuiNames.clear();\n"); t += (" \n"); t += (" for (std::vector<" + enumClassName + ">::iterator iter = enumData.begin();\n"); t += (" iter != enumData.end();\n"); t += (" iter++) {\n"); t += (" allGuiNames.push_back(" + enumClassName + "::toGuiName(iter->enumValue));\n"); t += (" }\n"); t += (" \n"); t += (" if (isSorted) {\n"); t += (" std::sort(allGuiNames.begin(), allGuiNames.end());\n"); t += (" }\n"); t += ("}\n"); t += ("\n"); TextFile tf; tf.replaceText(t); try { tf.writeFile(outputFileName); } catch (const DataFileException& e) { throw CommandException(e); } } /** * Get a string containing information on how to use this enumerated type * with the EnumComboBoxTemplate in the GUI. * * @param enumClassName * Name of the enumerated type. * @return * String containing usage information. */ AString CommandClassCreateEnum::getEnumComboBoxTemplateHelpInfo(const AString& enumClassName) const { const AString firstLetter = enumClassName.left(1).toLower(); const AString memberName = ("m_" + firstLetter + enumClassName.mid(1) + "ComboBox"); const AString slotName = (memberName.mid(2) + "ItemActivated()"); const AString templateParameter = ("<" + enumClassName + "," + enumClassName + "::Enum>"); const AString setupIndentString(memberName.length() + QString("->setup<").length(), ' '); AString s(" * Using this enumerated type in the GUI with an EnumComboBoxTemplate\n" " * \n" " * Header File (.h)\n" " * Forward declare the data type:\n" " * class EnumComboBoxTemplate;\n" " * \n" " * Declare the member:\n" " * EnumComboBoxTemplate* " + memberName + ";\n" " * \n" " * Declare a slot that is called when user changes selection\n" " * private slots:\n" " * void " + slotName + ";\n" " * \n" " * Implementation File (.cxx)\n" " * Include the header files\n" " * #include \"EnumComboBoxTemplate.h\"\n" " * #include \"" + enumClassName + ".h\"\n" " * \n" " * Instatiate:\n" " * " + memberName + " = new EnumComboBoxTemplate(this);\n" " * " + memberName + "->setup" + templateParameter + "();\n" " * \n" " * Get notified when the user changes the selection: \n" " * QObject::connect(" + memberName + ", SIGNAL(itemActivated()),\n" " * this, SLOT(" + slotName + "));\n" " * \n" " * Update the selection:\n" " * " + memberName + "->setSelectedItem" + templateParameter + "(NEW_VALUE);\n" " * \n" " * Read the selection:\n" " * const " + enumClassName + "::Enum VARIABLE = " + memberName + "->getSelectedItem" + templateParameter + "();\n" " * \n"); return s; } workbench-1.1.1/src/Commands/CommandClassCreateEnum.h000066400000000000000000000050571255417355300225330ustar00rootroot00000000000000#ifndef __COMMAND_CLASS_CREATE_ENUM_H__ #define __COMMAND_CLASS_CREATE_ENUM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CommandClassCreateBase.h" namespace caret { /// Command that creates class files for an enumerated type. class CommandClassCreateEnum : public CommandClassCreateBase { public: CommandClassCreateEnum(); virtual ~CommandClassCreateEnum(); virtual void executeOperation(ProgramParameters& parameters); AString getHelpInformation(const AString& /*programName*/); private: CommandClassCreateEnum(const CommandClassCreateEnum&); CommandClassCreateEnum& operator=(const CommandClassCreateEnum&); void createHeaderFile(const AString& outputFileName, const AString& enumClassName, const AString& ifdefName, const AString& ifdefNameStaticDeclaration, const int32_t numberOfEnumValues, const std::vector& enumValueNames, const bool isAutoNumber); void createImplementationFile(const AString& outputFileName, const AString& enumClassName, const AString& ifdefNameStaticDeclaration, const int32_t numberOfEnumValues, const std::vector& enumValueNames, const bool isAutoNumber); AString getEnumComboBoxTemplateHelpInfo(const AString& enumClassName) const; }; } // namespace #endif // __COMMAND_CLASS_CREATE_ENUM_H__ workbench-1.1.1/src/Commands/CommandClassCreateOperation.cxx000066400000000000000000000263211255417355300241370ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include "CaretAssertion.h" #include "CommandClassCreateOperation.h" #include "DataFileException.h" #include "FileInformation.h" #include "ProgramParameters.h" #include "TextFile.h" using namespace caret; /** * Constructor. */ CommandClassCreateOperation::CommandClassCreateOperation() : CommandClassCreateBase("-class-create-operation", "CREATE SOURCE CODE CLASS FILES (.h, .cxx) FOR OPERATION") { } /** * Destructor. */ CommandClassCreateOperation::~CommandClassCreateOperation() { } AString CommandClassCreateOperation::getHelpInformation(const AString& /*programName*/) { AString helpInfo = ("\n" "Create Operation Class header (.h) and implementation (.cxx) files.\n" "\n" "Usage: \n" " \n" " \n" " [-no-parameters]\n" "\n" " operation-class-name\n" " Required name of the operation class that MUST start with \"Operation\"\n" " \n" " command-line-switch\n" " Required command line switch for operation.\n" " \n" " short-description\n" " Required short description within double quotes.\n" " \n" " -no-parameters\n" " Optional parameter if the operation does not use parameters.\n" " \n" ); return helpInfo; } /** * Execute the operation. * * @param parameters * Parameters for the operation. * @throws CommandException * If the command failed. * @throws ProgramParametersException * If there is an error in the parameters. */ void CommandClassCreateOperation::executeOperation(ProgramParameters& parameters) { const AString operationClassName = parameters.nextString("Operation Class Name"); const AString commandLineSwitch = parameters.nextString("Command Line Switch"); const AString shortDescription = parameters.nextString("Short Description"); bool hasParametersFlag = true; while (parameters.hasNext()) { const AString& param = parameters.nextString("Create Class Parameter"); if (param == "-no-parameters") { hasParametersFlag = false; } else { throw CommandException("Invalid parameter: " + param); } } AString errorMessage; if (operationClassName.isEmpty()) { throw CommandException("Operation Class Name is empty."); } else { if (operationClassName.startsWith("Operation") == false) { throw CommandException("Operation Class Name must start with \"Operation\".\n"); } if (operationClassName == "Operation") { throw CommandException("\"Operation\" is not allowed for Operation Class Name"); } } if (commandLineSwitch.isEmpty()) { throw CommandException("Command line switch is empty."); } else if (commandLineSwitch.startsWith("-") == false) { throw CommandException("Command line must begin with \"-\"."); } if (shortDescription.isEmpty()) { throw CommandException("Short description is empty."); } const AString headerFileName = operationClassName + ".h"; const AString implementationFileName = operationClassName + ".cxx"; FileInformation headerInfo(headerFileName); if (headerInfo.exists()) { errorMessage += headerFileName + " exists and this command will not overwrite it.\n"; } FileInformation impInfo(implementationFileName); if (impInfo.exists()) { errorMessage += implementationFileName + " exists and this command will not overwrite it.\n"; } if (errorMessage.isEmpty() == false) { throw CommandException(errorMessage); } AString ifndefName; AString ifdefNameStaticDeclarations; this->getIfDefNames(operationClassName, ifndefName, ifdefNameStaticDeclarations); this->createHeaderFile(headerFileName, operationClassName, ifndefName, hasParametersFlag); this->createImplementationFile(implementationFileName, operationClassName, commandLineSwitch, shortDescription); } /** * Create and write the header (.h) file. * * @param outputFileName * Name for file that is written. * @param operationClassName * Name of operation type class. * @param ifndefName * Name of "ifndef" value. * @param hasParameters * True if the operation has parameters. */ void CommandClassCreateOperation::createHeaderFile(const AString& outputFileName, const AString& operationClassName, const AString& ifndefName, const bool hasParameters) { AString t; t += ("#ifndef " + ifndefName + "\n"); t += ("#define " + ifndefName + "\n"); t += this->getCopyright(); t += ("\n"); t += ("#include \"AbstractOperation.h\"\n"); t += ("\n"); t += ("namespace caret {\n"); t += ("\n"); t += (" class " + operationClassName + " : public AbstractOperation {\n"); t += ("\n"); t += (" public:\n"); t += (" static OperationParameters* getParameters();\n"); t += ("\n"); t += (" static void useParameters(OperationParameters* myParams, \n"); t += (" ProgressObject* myProgObj);\n"); t += ("\n"); t += (" static AString getCommandSwitch();\n"); t += ("\n"); t += (" static AString getShortDescription();\n"); t += ("\n"); if (hasParameters == false) { t += (" static bool takesParameters() { return false; }\n"); t += ("\n"); } t += (" };\n"); t += ("\n"); t += (" typedef TemplateAutoOperation<" + operationClassName + "> Auto" + operationClassName + ";\n"); t += ("\n"); t += ("} // namespace\n"); t += ("\n"); t += ("#endif //" + ifndefName + "\n"); t += ("\n"); TextFile tf; tf.replaceText(t); try { tf.writeFile(outputFileName); } catch (const DataFileException& e) { throw CommandException(e); } } /** * Create and write the implementation (.cxx) file. * * @param outputFileName * Name for file that is written. * @param operationClassName * Name of operation type class. * @param commandLineSwitch * Command line switch for operation. * @param shortDescription * Short description of operation. */ void CommandClassCreateOperation::createImplementationFile(const AString& outputFileName, const AString& operationClassName, const AString& commandLineSwitch, const AString& shortDescription) { AString t; t += this->getCopyright(); t += ("#include \"CaretAssert.h\"\n"); t += ("#include \"CaretLogger.h\"\n"); t += ("\n"); t += ("#include \"" + operationClassName + ".h\"\n"); t += ("#include \"OperationException.h\"\n"); t += ("\n"); t += ("using namespace caret;\n"); t += ("\n"); t += ("/**\n"); t += (" * \\class caret::" + operationClassName + " \n"); t += (" * \\brief " + shortDescription + "\n"); t += (" *\n"); t += (" * \n"); t += (" */\n"); t += ("\n"); t += ("/**\n"); t += (" * @return Command line switch\n"); t += (" */\n"); t += ("AString\n"); t += ("" + operationClassName + "::getCommandSwitch()\n"); t += ("{\n"); t += (" return \"" + commandLineSwitch + "\";\n"); t += ("}\n"); t += ("\n"); t += ("/**\n"); t += (" * @return Short description of operation\n"); t += (" */\n"); t += ("AString\n"); t += ("" + operationClassName + "::getShortDescription()\n"); t += ("{\n"); t += (" return \"" + shortDescription.toUpper() + "\";\n"); t += ("}\n"); t += ("\n"); t += ("/**\n"); t += (" * @return Parameters for operation\n"); t += (" */\n"); t += ("OperationParameters*\n"); t += ("" + operationClassName + "::getParameters()\n"); t += ("{\n"); t += (" OperationParameters* ret = new OperationParameters();\n"); t += (" \n"); t += (" AString helpText;\n"); t += (" \n"); t += (" ret->setHelpText(helpText);\n"); t += (" \n"); t += (" return ret;\n"); t += ("}\n"); t += ("\n"); t += ("/**\n"); t += (" * Use Parameters and perform operation\n"); t += (" */\n"); t += ("void\n"); t += ("" + operationClassName + "::useParameters(OperationParameters* myParams,\n"); t += (" ProgressObject* myProgObj)\n"); t += ("{\n"); t += (" LevelProgress myProgress(myProgObj);\n"); t += (" \n"); t += ("}\n"); t += ("\n"); TextFile tf; tf.replaceText(t); try { tf.writeFile(outputFileName); } catch (const DataFileException& e) { throw CommandException(e); } std::cout << std::endl; std::cout << "Operation was created successfully." << std::endl; std::cout << std::endl; std::cout << "Add the class files to Operations/CMakeLists.txt:" << std::endl; std::cout << " " << qPrintable(operationClassName) << ".h" << std::endl; std::cout << " " << qPrintable(operationClassName) << ".cxx" << std::endl; std::cout << std::endl; std::cout << "Add the header file and operation to Commands/CommandOperationManager.cxx:" << std::endl; std::cout << " #include \"" << qPrintable(operationClassName) << ".h\"" << std::endl; std::cout << " this->commandOperations.push_back(new CommandParser(new Auto" << operationClassName << "()));" << std::endl; std::cout << std::endl; } workbench-1.1.1/src/Commands/CommandClassCreateOperation.h000066400000000000000000000042211255417355300235570ustar00rootroot00000000000000#ifndef __COMMAND_CLASS_CREATE_OPERATION_H__ #define __COMMAND_CLASS_CREATE_OPERATION_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CommandClassCreateBase.h" namespace caret { /// Command that creates class files for an operation class CommandClassCreateOperation : public CommandClassCreateBase { public: CommandClassCreateOperation(); virtual ~CommandClassCreateOperation(); virtual void executeOperation(ProgramParameters& parameters); AString getHelpInformation(const AString& /*programName*/); private: CommandClassCreateOperation(const CommandClassCreateOperation&); CommandClassCreateOperation& operator=(const CommandClassCreateOperation&); void createHeaderFile(const AString& outputFileName, const AString& operationClassName, const AString& ifndefName, const bool hasParameters); void createImplementationFile(const AString& outputFileName, const AString& operationClassName, const AString& commandLineSwitch, const AString& shortDescription); }; } // namespace #endif // __COMMAND_CLASS_CREATE_OPERATION_H__ workbench-1.1.1/src/Commands/CommandException.cxx000066400000000000000000000041731255417355300220240ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CommandException.h" #include using namespace caret; /** * Constructor. * */ CommandException::CommandException() : CaretException() { this->initializeMembersCommandException(); } /** * Constructor that uses stack trace from the exception * passed in as a parameter. * * @param e Any exception whose stack trace becomes * this exception's stack trace. * */ CommandException::CommandException( const CaretException& e) : CaretException(e) { this->initializeMembersCommandException(); } /** * Constructor. * * @param s Description of the exception. * */ CommandException::CommandException(const AString& s) : CaretException(s) { this->initializeMembersCommandException(); } /** * Copy Constructor. * @param e * Exception that is copied. */ CommandException::CommandException(const CommandException& e) : CaretException(e) { } /** * Assignment operator. * @param e * Exception that is copied. * @return * Copy of the exception. */ CommandException& CommandException::operator=(const CommandException& e) { if (this != &e) { CaretException::operator=(e); } return *this; } /** * Destructor */ CommandException::~CommandException() throw() { } void CommandException::initializeMembersCommandException() { } workbench-1.1.1/src/Commands/CommandException.h000066400000000000000000000027101255417355300214440ustar00rootroot00000000000000#ifndef __COMMAND_EXCEPTION_H__ #define __COMMAND_EXCEPTION_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretException.h" namespace caret { /// An exception thrown during command processing. class CommandException : public CaretException { public: CommandException(); CommandException(const CaretException& e); CommandException(const AString& s); CommandException(const CommandException& e); CommandException& operator=(const CommandException& e); virtual ~CommandException() throw(); private: void initializeMembersCommandException(); }; } // namespace #endif // __COMMAND_EXCEPTION_H__ workbench-1.1.1/src/Commands/CommandGiftiConvert.cxx000066400000000000000000000070371255417355300224730ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include "CommandGiftiConvert.h" #include "FileInformation.h" #include "GiftiFile.h" #include "ProgramParameters.h" using namespace caret; /** * Constructor. */ CommandGiftiConvert::CommandGiftiConvert() : CommandOperation("-gifti-convert", "CONVERT A GIFTI FILE TO A DIFFERENT ENCODING") { } /** * Destructor. */ CommandGiftiConvert::~CommandGiftiConvert() { } /** * @return The help information. */ AString CommandGiftiConvert::getHelpInformation(const AString& /*programName*/) { AString helpInfo = ("Convert GIFTI file to different encoding\n" "\n" "Usage: \n" " \n" " \n" " \n" " gifti-encoding\n" " Required GIFTI encoding.\n" " \n" " input-gifti-file\n" " Required input GIFTI file name.\n" " \n" " output-gifti-file\n" " Required output GIFTI file name.\n" " \n" ); helpInfo += (" Valid GIFTI Encodings: \n" " ASCII\n" " BASE64_BINARY\n" " GZIP_BASE64_BINARY\n" " EXTERNAL_FILE_BINARY\n" "\n"); return helpInfo; } /** * Execute the operation. * * @param parameters * Parameters for the operation. * @throws CommandException * If the command failed. * @throws ProgramParametersException * If there is an error in the parameters. */ void CommandGiftiConvert::executeOperation(ProgramParameters& parameters) { const AString encodingName = parameters.nextString("GIFTI Encoding Name"); const AString inputFileName = parameters.nextString("Input GIFTI File Name"); const AString outputFileName = parameters.nextString("Output GIFTI File Name"); bool isValidEncoding = false; GiftiEncodingEnum::Enum encoding = GiftiEncodingEnum::fromName(encodingName, &isValidEncoding); if (isValidEncoding == false) { throw CommandException("GIFTI Encoding is invalid."); } if (inputFileName.isEmpty()) { throw CommandException("Input GIFTI file name is empty."); } if (outputFileName.isEmpty()) { throw CommandException("Input GIFTI file name is empty."); } GiftiFile gf; gf.readFile(inputFileName); gf.setEncodingForWriting(encoding); gf.writeFile(outputFileName); } workbench-1.1.1/src/Commands/CommandGiftiConvert.h000066400000000000000000000027011255417355300221110ustar00rootroot00000000000000#ifndef __COMMAND_GIFTI_CONVERT__H__ #define __COMMAND_GIFTI_CONVERT__H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CommandOperation.h" namespace caret { /// Command that converts GIFTI files to different encodings class CommandGiftiConvert : public CommandOperation { public: CommandGiftiConvert(); virtual ~CommandGiftiConvert(); virtual void executeOperation(ProgramParameters& parameters); AString getHelpInformation(const AString& /*programName*/); private: CommandGiftiConvert(const CommandGiftiConvert&); CommandGiftiConvert& operator=(const CommandGiftiConvert&); }; } // namespace #endif // __COMMAND_GIFTI_CONVERT__H__ workbench-1.1.1/src/Commands/CommandOperation.cxx000066400000000000000000000043521255417355300220250ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CommandOperation.h" using namespace caret; /** * Constructor. * @param commandLineSwitch * Switch to select this command. * @param operationShortDescription * Short description of the command. */ CommandOperation::CommandOperation(const AString& commandLineSwitch, const AString& operationShortDescription) : CaretObject() { this->commandLineSwitch = commandLineSwitch; this->operationShortDescription = operationShortDescription; } /** * Destructor. */ CommandOperation::~CommandOperation() { } /** * Execute the command. * * @param parameters * Parameters for the operation. * @throws CommandException * If the command failed. */ void CommandOperation::execute(ProgramParameters& parameters, const bool& preventProvenance) { if (preventProvenance) { disableProvenance();//let provenance-ignorant commands not need to deal with an unused parameter } this->executeOperation(parameters); } void CommandOperation::disableProvenance() { } bool CommandOperation::takesParameters() { return true; } /** * Get the short description of the operation. */ AString CommandOperation::getOperationShortDescription() const { return this->operationShortDescription; } /** * Get the command line switch for selecting the operation. */ AString CommandOperation::getCommandLineSwitch() const { return this->commandLineSwitch; } workbench-1.1.1/src/Commands/CommandOperation.h000066400000000000000000000051021255417355300214440ustar00rootroot00000000000000#ifndef __COMMAND_OPERATION_H__ #define __COMMAND_OPERATION_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "CommandException.h" #include "ProgramParametersException.h" #include "AString.h" namespace caret { class ProgramParameters; /// Abstract class for a command operation. class CommandOperation : public CaretObject { public: virtual ~CommandOperation(); void execute(ProgramParameters& parameters, const bool& preventProvenance); protected: /** * Execute the operation. * * @param parameters * Parameters for the operation. * @throws CommandException * If the command failed. * @throws ProgramParametersException * If there is an error in the parameters. */ virtual void executeOperation(ProgramParameters& parameters) = 0; virtual void disableProvenance(); CommandOperation(const AString& commandLineSwitch, const AString& operationShortDescription); private: CommandOperation(); CommandOperation(const CommandOperation&); CommandOperation& operator=(const CommandOperation&); public: AString getOperationShortDescription() const; AString getCommandLineSwitch() const; virtual AString getHelpInformation(const AString& programName) = 0; virtual bool takesParameters(); private: /** Short description listing commands purpose */ AString operationShortDescription; /** Switch on command line */ AString commandLineSwitch; }; } // namespace #endif // __COMMAND_OPERATION_H__ workbench-1.1.1/src/Commands/CommandOperationManager.cxx000066400000000000000000001260151255417355300233210ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __COMMAND_OPERATION_MANAGER_DEFINE__ #include "CommandOperationManager.h" #undef __COMMAND_OPERATION_MANAGER_DEFINE__ #include "AlgorithmBorderResample.h" #include "AlgorithmBorderToVertices.h" #include "AlgorithmCiftiAllLabelsToROIs.h" #include "AlgorithmCiftiAverage.h" #include "AlgorithmCiftiAverageDenseROI.h" #include "AlgorithmCiftiAverageROICorrelation.h" #include "AlgorithmCiftiCorrelation.h" #include "AlgorithmCiftiCorrelationGradient.h" #include "AlgorithmCiftiCreateDenseScalar.h" #include "AlgorithmCiftiCreateDenseTimeseries.h" #include "AlgorithmCiftiCreateLabel.h" #include "AlgorithmCiftiCrossCorrelation.h" #include "AlgorithmCiftiDilate.h" #include "AlgorithmCiftiExtrema.h" #include "AlgorithmCiftiFalseCorrelation.h" #include "AlgorithmCiftiFindClusters.h" #include "AlgorithmCiftiGradient.h" #include "AlgorithmCiftiLabelAdjacency.h" #include "AlgorithmCiftiLabelToROI.h" #include "AlgorithmCiftiMergeDense.h" #include "AlgorithmCiftiPairwiseCorrelation.h" #include "AlgorithmCiftiParcellate.h" #include "AlgorithmCiftiParcelMappingToLabel.h" #include "AlgorithmCiftiReduce.h" #include "AlgorithmCiftiReorder.h" #include "AlgorithmCiftiReplaceStructure.h" #include "AlgorithmCiftiResample.h" #include "AlgorithmCiftiROIsFromExtrema.h" #include "AlgorithmCiftiSeparate.h" #include "AlgorithmCiftiSmoothing.h" #include "AlgorithmCiftiTranspose.h" #include "AlgorithmCiftiVectorOperation.h" #include "AlgorithmCreateSignedDistanceVolume.h" #include "AlgorithmFiberDotProducts.h" #include "AlgorithmFociResample.h" #include "AlgorithmGiftiAllLabelsToROIs.h" #include "AlgorithmGiftiLabelAddPrefix.h" #include "AlgorithmGiftiLabelToROI.h" #include "AlgorithmLabelDilate.h" #include "AlgorithmLabelModifyKeys.h" #include "AlgorithmLabelResample.h" #include "AlgorithmLabelToBorder.h" #include "AlgorithmMetricDilate.h" #include "AlgorithmMetricEstimateFWHM.h" #include "AlgorithmMetricExtrema.h" #include "AlgorithmMetricFalseCorrelation.h" #include "AlgorithmMetricFillHoles.h" #include "AlgorithmMetricFindClusters.h" #include "AlgorithmMetricGradient.h" #include "AlgorithmMetricReduce.h" #include "AlgorithmMetricRegression.h" #include "AlgorithmMetricRemoveIslands.h" #include "AlgorithmMetricResample.h" #include "AlgorithmMetricROIsFromExtrema.h" #include "AlgorithmMetricROIsToBorder.h" #include "AlgorithmMetricSmoothing.h" #include "AlgorithmMetricTFCE.h" #include "AlgorithmMetricToVolumeMapping.h" #include "AlgorithmMetricVectorOperation.h" #include "AlgorithmMetricVectorTowardROI.h" #include "AlgorithmNodesInsideBorder.h" //-border-to-rois #include "AlgorithmSignedDistanceToSurface.h" #include "AlgorithmSurfaceAffineRegression.h" #include "AlgorithmSurfaceApplyAffine.h" #include "AlgorithmSurfaceApplyWarpfield.h" #include "AlgorithmSurfaceAverage.h" #include "AlgorithmSurfaceCortexLayer.h" #include "AlgorithmSurfaceCreateSphere.h" #include "AlgorithmSurfaceDistortion.h" #include "AlgorithmSurfaceFlipLR.h" #include "AlgorithmSurfaceGenerateInflated.h" #include "AlgorithmSurfaceInflation.h" #include "AlgorithmSurfaceMatch.h" #include "AlgorithmSurfaceModifySphere.h" #include "AlgorithmSurfaceResample.h" #include "AlgorithmSurfaceSmoothing.h" #include "AlgorithmSurfaceSphereProjectUnproject.h" #include "AlgorithmSurfaceToSurface3dDistance.h" #include "AlgorithmSurfaceWedgeVolume.h" #include "AlgorithmVolumeAffineResample.h" #include "AlgorithmVolumeAllLabelsToROIs.h" #include "AlgorithmVolumeDilate.h" #include "AlgorithmVolumeEstimateFWHM.h" #include "AlgorithmVolumeExtrema.h" #include "AlgorithmVolumeFillHoles.h" #include "AlgorithmVolumeFindClusters.h" #include "AlgorithmVolumeGradient.h" #include "AlgorithmVolumeLabelToROI.h" #include "AlgorithmVolumeLabelToSurfaceMapping.h" #include "AlgorithmVolumeParcelResampling.h" #include "AlgorithmVolumeParcelResamplingGeneric.h" #include "AlgorithmVolumeParcelSmoothing.h" #include "AlgorithmVolumeReduce.h" #include "AlgorithmVolumeRemoveIslands.h" #include "AlgorithmVolumeROIsFromExtrema.h" #include "AlgorithmVolumeSmoothing.h" #include "AlgorithmVolumeTFCE.h" #include "AlgorithmVolumeToSurfaceMapping.h" #include "AlgorithmVolumeVectorOperation.h" #include "AlgorithmVolumeWarpfieldResample.h" #include "OperationAddToSpecFile.h" #include "OperationBackendAverageDenseROI.h" #include "OperationBackendAverageROICorrelation.h" #include "OperationBorderExportColorTable.h" #include "OperationBorderFileExportToCaret5.h" #include "OperationBorderMerge.h" #include "OperationCiftiChangeTimestep.h" #include "OperationCiftiConvert.h" #include "OperationCiftiConvertToScalar.h" #include "OperationCiftiCopyMapping.h" #include "OperationCiftiCreateDenseFromTemplate.h" #include "OperationCiftiCreateScalarSeries.h" #include "OperationCiftiEstimateFWHM.h" #include "OperationCiftiExportDenseMapping.h" #include "OperationCiftiLabelExportTable.h" #include "OperationCiftiLabelImport.h" #include "OperationCiftiMath.h" #include "OperationCiftiMerge.h" #include "OperationCiftiPalette.h" #include "OperationCiftiResampleDconnMemory.h" #include "AlgorithmCiftiRestrictDenseMap.h" #include "OperationCiftiROIAverage.h" #include "OperationCiftiSeparateAll.h" #include "OperationCiftiStats.h" #include "OperationCiftiWeightedStats.h" #include "OperationConvertAffine.h" #include "OperationConvertFiberOrientations.h" #include "OperationConvertMatrix4ToMatrix2.h" #include "OperationConvertMatrix4ToWorkbenchSparse.h" #include "OperationConvertWarpfield.h" #include "OperationEstimateFiberBinghams.h" #include "OperationFileConvert.h" #include "OperationFileInformation.h" #include "OperationFociGetProjectionVertex.h" #include "OperationFociListCoords.h" #include "OperationLabelExportTable.h" #include "OperationLabelMask.h" #include "OperationLabelMerge.h" #include "OperationMetadataRemoveProvenance.h" #include "OperationMetadataStringReplace.h" #include "OperationMetricConvert.h" #include "OperationMetricLabelImport.h" #include "OperationMetricMask.h" #include "OperationMetricMath.h" #include "OperationMetricMerge.h" #include "OperationMetricPalette.h" #include "OperationMetricStats.h" #include "OperationMetricVertexSum.h" #include "OperationMetricWeightedStats.h" #include "OperationNiftiInformation.h" #include "OperationProbtrackXDotConvert.h" #include "OperationSetMapName.h" #include "OperationSetMapNames.h" #include "OperationSetStructure.h" #include "OperationShowScene.h" #include "OperationSpecFileMerge.h" #include "OperationSurfaceClosestVertex.h" #include "OperationSurfaceCoordinatesToMetric.h" #include "OperationSurfaceCutResample.h" #include "OperationSurfaceFlipNormals.h" #include "OperationSurfaceGeodesicDistance.h" #include "OperationSurfaceGeodesicROIs.h" #include "OperationSurfaceInformation.h" #include "OperationSurfaceNormals.h" #include "OperationSurfaceVertexAreas.h" #include "OperationVolumeCapturePlane.h" #include "OperationVolumeCopyExtensions.h" #include "OperationVolumeCreate.h" #include "OperationVolumeLabelExportTable.h" #include "OperationVolumeLabelImport.h" #include "OperationVolumeMath.h" #include "OperationVolumeMerge.h" #include "OperationVolumePalette.h" #include "OperationVolumeReorient.h" #include "OperationVolumeSetSpace.h" #include "OperationVolumeStats.h" #include "OperationVolumeWeightedStats.h" #include "OperationWbsparseMergeDense.h" #include "OperationZipSceneFile.h" #include "OperationZipSpecFile.h" #include "AlgorithmException.h" #include "ApplicationInformation.h" #include "CommandParser.h" #include "OperationException.h" #include "CommandClassAddMember.h" #include "CommandClassCreate.h" #include "CommandClassCreateAlgorithm.h" #include "CommandClassCreateEnum.h" #include "CommandClassCreateOperation.h" #include "CommandC11xTesting.h" #include "CommandGiftiConvert.h" #include "CommandUnitTest.h" #include "ProgramParameters.h" #include "CaretLogger.h" #include using namespace caret; using namespace std; /** * Get the command operation manager. * * return * Pointer to the command operation manager. */ CommandOperationManager* CommandOperationManager::getCommandOperationManager() { if (singletonCommandOperationManager == NULL) { singletonCommandOperationManager = new CommandOperationManager(); } return singletonCommandOperationManager; } /** * Delete the command operation manager. */ void CommandOperationManager::deleteCommandOperationManager() { if (singletonCommandOperationManager != NULL) { delete singletonCommandOperationManager; singletonCommandOperationManager = NULL; } } /** * Constructor. */ CommandOperationManager::CommandOperationManager() { this->commandOperations.push_back(new CommandParser(new AutoAlgorithmBorderResample())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmBorderToVertices())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmCiftiAllLabelsToROIs())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmCiftiAverage())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmCiftiAverageDenseROI())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmCiftiAverageROICorrelation())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmCiftiCorrelation())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmCiftiCorrelationGradient())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmCiftiCreateDenseScalar())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmCiftiCreateDenseTimeseries())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmCiftiCreateLabel())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmCiftiCrossCorrelation())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmCiftiDilate())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmCiftiExtrema())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmCiftiFalseCorrelation())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmCiftiFindClusters())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmCiftiGradient())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmCiftiLabelAdjacency())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmCiftiLabelToROI())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmCiftiMergeDense())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmCiftiPairwiseCorrelation())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmCiftiParcellate())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmCiftiParcelMappingToLabel())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmCiftiReduce())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmCiftiReorder())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmCiftiReplaceStructure())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmCiftiResample())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmCiftiRestrictDenseMap())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmCiftiROIsFromExtrema())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmCiftiSeparate())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmCiftiSmoothing())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmCiftiTranspose())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmCiftiVectorOperation())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmCreateSignedDistanceVolume())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmFiberDotProducts())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmFociResample())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmGiftiAllLabelsToROIs())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmGiftiLabelAddPrefix())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmGiftiLabelToROI())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmLabelDilate())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmLabelModifyKeys())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmLabelResample())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmLabelToBorder())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmMetricDilate())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmMetricEstimateFWHM())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmMetricExtrema())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmMetricFalseCorrelation())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmMetricFillHoles())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmMetricFindClusters())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmMetricGradient())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmMetricReduce())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmMetricRegression())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmMetricRemoveIslands())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmMetricResample())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmMetricROIsFromExtrema())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmMetricROIsToBorder())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmMetricSmoothing())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmMetricTFCE())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmMetricToVolumeMapping())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmMetricVectorOperation())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmMetricVectorTowardROI())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmNodesInsideBorder()));//-border-to-rois this->commandOperations.push_back(new CommandParser(new AutoAlgorithmSignedDistanceToSurface())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmSurfaceAffineRegression())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmSurfaceApplyAffine())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmSurfaceApplyWarpfield())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmSurfaceAverage())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmSurfaceCortexLayer())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmSurfaceCreateSphere())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmSurfaceDistortion())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmSurfaceFlipLR())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmSurfaceGenerateInflated())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmSurfaceInflation())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmSurfaceMatch())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmSurfaceModifySphere())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmSurfaceResample())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmSurfaceSmoothing())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmSurfaceSphereProjectUnproject())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmSurfaceToSurface3dDistance())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmSurfaceWedgeVolume())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmVolumeAffineResample())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmVolumeAllLabelsToROIs())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmVolumeDilate())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmVolumeEstimateFWHM())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmVolumeExtrema())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmVolumeFillHoles())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmVolumeFindClusters())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmVolumeGradient())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmVolumeLabelToROI())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmVolumeLabelToSurfaceMapping())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmVolumeParcelResampling())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmVolumeParcelResamplingGeneric())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmVolumeParcelSmoothing())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmVolumeReduce())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmVolumeRemoveIslands())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmVolumeROIsFromExtrema())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmVolumeSmoothing())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmVolumeTFCE())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmVolumeToSurfaceMapping())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmVolumeVectorOperation())); this->commandOperations.push_back(new CommandParser(new AutoAlgorithmVolumeWarpfieldResample())); this->commandOperations.push_back(new CommandParser(new AutoOperationAddToSpecFile())); this->commandOperations.push_back(new CommandParser(new AutoOperationBackendAverageDenseROI())); this->commandOperations.push_back(new CommandParser(new AutoOperationBackendAverageROICorrelation())); this->commandOperations.push_back(new CommandParser(new AutoOperationBorderExportColorTable())); this->commandOperations.push_back(new CommandParser(new AutoOperationBorderFileExportToCaret5())); this->commandOperations.push_back(new CommandParser(new AutoOperationBorderMerge())); this->commandOperations.push_back(new CommandParser(new AutoOperationCiftiChangeTimestep())); this->commandOperations.push_back(new CommandParser(new AutoOperationCiftiConvert())); this->commandOperations.push_back(new CommandParser(new AutoOperationCiftiConvertToScalar())); this->commandOperations.push_back(new CommandParser(new AutoOperationCiftiCopyMapping())); this->commandOperations.push_back(new CommandParser(new AutoOperationCiftiCreateDenseFromTemplate())); this->commandOperations.push_back(new CommandParser(new AutoOperationCiftiCreateScalarSeries())); this->commandOperations.push_back(new CommandParser(new AutoOperationCiftiEstimateFWHM())); this->commandOperations.push_back(new CommandParser(new AutoOperationCiftiExportDenseMapping())); this->commandOperations.push_back(new CommandParser(new AutoOperationCiftiLabelExportTable())); this->commandOperations.push_back(new CommandParser(new AutoOperationCiftiLabelImport())); this->commandOperations.push_back(new CommandParser(new AutoOperationCiftiMath())); this->commandOperations.push_back(new CommandParser(new AutoOperationCiftiMerge())); this->commandOperations.push_back(new CommandParser(new AutoOperationCiftiPalette())); this->commandOperations.push_back(new CommandParser(new AutoOperationCiftiResampleDconnMemory())); this->commandOperations.push_back(new CommandParser(new AutoOperationCiftiROIAverage())); this->commandOperations.push_back(new CommandParser(new AutoOperationCiftiStats())); this->commandOperations.push_back(new CommandParser(new AutoOperationCiftiWeightedStats())); this->commandOperations.push_back(new CommandParser(new AutoOperationConvertAffine())); this->commandOperations.push_back(new CommandParser(new AutoOperationConvertFiberOrientations())); this->commandOperations.push_back(new CommandParser(new AutoOperationConvertMatrix4ToMatrix2())); this->commandOperations.push_back(new CommandParser(new AutoOperationConvertMatrix4ToWorkbenchSparse())); this->commandOperations.push_back(new CommandParser(new AutoOperationConvertWarpfield())); this->commandOperations.push_back(new CommandParser(new AutoOperationEstimateFiberBinghams())); this->commandOperations.push_back(new CommandParser(new AutoOperationFileConvert())); this->commandOperations.push_back(new CommandParser(new AutoOperationFileInformation())); this->commandOperations.push_back(new CommandParser(new AutoOperationFociGetProjectionVertex())); this->commandOperations.push_back(new CommandParser(new AutoOperationFociListCoords())); this->commandOperations.push_back(new CommandParser(new AutoOperationLabelExportTable())); this->commandOperations.push_back(new CommandParser(new AutoOperationLabelMask())); this->commandOperations.push_back(new CommandParser(new AutoOperationLabelMerge())); this->commandOperations.push_back(new CommandParser(new AutoOperationMetadataRemoveProvenance())); this->commandOperations.push_back(new CommandParser(new AutoOperationMetadataStringReplace())); this->commandOperations.push_back(new CommandParser(new AutoOperationMetricConvert())); this->commandOperations.push_back(new CommandParser(new AutoOperationMetricLabelImport())); this->commandOperations.push_back(new CommandParser(new AutoOperationMetricMask())); this->commandOperations.push_back(new CommandParser(new AutoOperationMetricMath())); this->commandOperations.push_back(new CommandParser(new AutoOperationMetricMerge())); this->commandOperations.push_back(new CommandParser(new AutoOperationMetricPalette())); this->commandOperations.push_back(new CommandParser(new AutoOperationMetricStats())); this->commandOperations.push_back(new CommandParser(new AutoOperationMetricWeightedStats())); this->commandOperations.push_back(new CommandParser(new AutoOperationNiftiInformation())); this->commandOperations.push_back(new CommandParser(new AutoOperationProbtrackXDotConvert())); this->commandOperations.push_back(new CommandParser(new AutoOperationSetMapNames())); this->commandOperations.push_back(new CommandParser(new AutoOperationSetStructure())); if (OperationShowScene::isShowSceneCommandAvailable()) { this->commandOperations.push_back(new CommandParser(new AutoOperationShowScene())); } this->commandOperations.push_back(new CommandParser(new AutoOperationSpecFileMerge())); this->commandOperations.push_back(new CommandParser(new AutoOperationSurfaceClosestVertex())); this->commandOperations.push_back(new CommandParser(new AutoOperationSurfaceCoordinatesToMetric())); this->commandOperations.push_back(new CommandParser(new AutoOperationSurfaceCutResample())); this->commandOperations.push_back(new CommandParser(new AutoOperationSurfaceFlipNormals())); this->commandOperations.push_back(new CommandParser(new AutoOperationSurfaceGeodesicDistance())); this->commandOperations.push_back(new CommandParser(new AutoOperationSurfaceGeodesicROIs())); this->commandOperations.push_back(new CommandParser(new AutoOperationSurfaceInformation())); this->commandOperations.push_back(new CommandParser(new AutoOperationSurfaceNormals())); this->commandOperations.push_back(new CommandParser(new AutoOperationSurfaceVertexAreas())); this->commandOperations.push_back(new CommandParser(new AutoOperationVolumeCapturePlane())); this->commandOperations.push_back(new CommandParser(new AutoOperationVolumeCopyExtensions())); this->commandOperations.push_back(new CommandParser(new AutoOperationVolumeCreate())); this->commandOperations.push_back(new CommandParser(new AutoOperationVolumeLabelExportTable())); this->commandOperations.push_back(new CommandParser(new AutoOperationVolumeLabelImport())); this->commandOperations.push_back(new CommandParser(new AutoOperationVolumeMath())); this->commandOperations.push_back(new CommandParser(new AutoOperationVolumeMerge())); this->commandOperations.push_back(new CommandParser(new AutoOperationVolumePalette())); this->commandOperations.push_back(new CommandParser(new AutoOperationVolumeReorient())); this->commandOperations.push_back(new CommandParser(new AutoOperationVolumeSetSpace())); this->commandOperations.push_back(new CommandParser(new AutoOperationVolumeStats())); this->commandOperations.push_back(new CommandParser(new AutoOperationVolumeWeightedStats())); this->commandOperations.push_back(new CommandParser(new AutoOperationWbsparseMergeDense())); this->commandOperations.push_back(new CommandParser(new AutoOperationZipSceneFile())); this->commandOperations.push_back(new CommandParser(new AutoOperationZipSpecFile())); this->commandOperations.push_back(new CommandClassAddMember()); this->commandOperations.push_back(new CommandClassCreate()); this->commandOperations.push_back(new CommandClassCreateAlgorithm()); this->commandOperations.push_back(new CommandClassCreateEnum()); this->commandOperations.push_back(new CommandClassCreateOperation()); #ifdef WORKBENCH_HAVE_C11X this->commandOperations.push_back(new CommandC11xTesting()); #endif // WORKBENCH_HAVE_C11X this->commandOperations.push_back(new CommandGiftiConvert()); this->commandOperations.push_back(new CommandUnitTest()); this->deprecatedOperations.push_back(new CommandParser(new AutoOperationCiftiSeparateAll())); this->deprecatedOperations.push_back(new CommandParser(new AutoOperationMetricVertexSum())); this->deprecatedOperations.push_back(new CommandParser(new AutoOperationSetMapName())); } /** * Destructor. */ CommandOperationManager::~CommandOperationManager() { uint64_t numberOfCommands = this->commandOperations.size(); for (uint64_t i = 0; i < numberOfCommands; i++) { delete this->commandOperations[i]; this->commandOperations[i] = NULL; } this->commandOperations.clear(); uint64_t numberOfDeprecated = this->deprecatedOperations.size(); for (uint64_t i = 0; i < numberOfDeprecated; i++) { delete this->deprecatedOperations[i]; this->deprecatedOperations[i] = NULL; } this->deprecatedOperations.clear(); } /** * Run a command. * * @param parameters * Reference to the command's parameters. * @throws CommandException * If the command failed. */ void CommandOperationManager::runCommand(ProgramParameters& parameters) { vector globalOptionArgs; bool preventProvenance = getGlobalOption(parameters, "-disable-provenance", 0, globalOptionArgs);//check these BEFORE we test if we have a command switch, because they remove the switch and arguments from the ProgramParameters if (getGlobalOption(parameters, "-logging", 1, globalOptionArgs)) { bool valid = false; const LogLevelEnum::Enum level = LogLevelEnum::fromName(globalOptionArgs[0], &valid); if (!valid) throw CommandException("unrecognized logging level: '" + globalOptionArgs[0] + "'"); CaretLogger::getLogger()->setLevel(level); } const uint64_t numberOfCommands = this->commandOperations.size(); const uint64_t numberOfDeprecated = this->deprecatedOperations.size(); if (parameters.hasNext() == false) { printHelpInfo(); return; } AString commandSwitch; commandSwitch = parameters.nextString("Command Name"); if (commandSwitch == "-help") { printHelpInfo(); } else if (commandSwitch == "-arguments-help") { printArgumentsHelp("wb_command"); } else if (commandSwitch == "-version") { printVersionInfo(); } else if (commandSwitch == "-list-commands") { printAllCommands(); } else if (commandSwitch == "-list-deprecated-commands") { printDeprecatedCommands(); } else if (commandSwitch == "-all-commands-help") { printAllCommandsHelpInfo("wb_command"); } else { CommandOperation* operation = NULL; for (uint64_t i = 0; i < numberOfCommands; i++) { if (this->commandOperations[i]->getCommandLineSwitch() == commandSwitch) { operation = this->commandOperations[i]; break; } } if (operation == NULL) { for (uint64_t i = 0; i < numberOfDeprecated; i++) { if (this->deprecatedOperations[i]->getCommandLineSwitch() == commandSwitch) { operation = this->deprecatedOperations[i]; break; } } } if (operation == NULL) { if (!parameters.hasNext()) { printAllCommandsMatching(commandSwitch); } else { throw CommandException("Command \"" + commandSwitch + "\" not found."); } } else { if (!parameters.hasNext() && operation->takesParameters()) { cout << operation->getHelpInformation("wb_command") << endl; } else { operation->execute(parameters, preventProvenance); } } } } bool CommandOperationManager::getGlobalOption(ProgramParameters& parameters, const AString& optionString, const int& numArgs, vector& arguments) { parameters.setParameterIndex(0);//this ends up being slightly redundant, but whatever while (parameters.hasNext())//shouldn't be many global options, so do it the simple way { AString test = parameters.nextString("global option"); if (test == optionString) { parameters.remove(); arguments.clear(); for (int i = 0; i < numArgs; ++i) { if (!parameters.hasNext()) { throw CommandException("missing argument #" + AString::number(i + 1) + " to global option '" + optionString + "'"); } arguments.push_back(parameters.nextString("global option argument")); parameters.remove(); } parameters.setParameterIndex(0); return true; } } parameters.setParameterIndex(0); return false; } /** * Print all of the commands. */ void CommandOperationManager::printAllCommands() { map cmdMap; int64_t longestSwitch = 0; const uint64_t numberOfCommands = this->commandOperations.size(); for (uint64_t i = 0; i < numberOfCommands; i++) { CommandOperation* op = this->commandOperations[i]; const AString cmdSwitch = op->getCommandLineSwitch(); const int64_t switchLength = cmdSwitch.length(); if (switchLength > longestSwitch) { longestSwitch = switchLength; } cmdMap.insert(make_pair(cmdSwitch, op->getOperationShortDescription())); #ifndef NDEBUG const AString helpInfo = op->getHelpInformation("");//TSC: generating help info takes a little processing (populating and walking an OperationParameters tree for each command) if (helpInfo.isEmpty()) {//So, test the same define as for asserts and skip this check in release CaretLogSevere("Command has no help info: " + cmdSwitch); } #endif } for (map::iterator iter = cmdMap.begin(); iter != cmdMap.end(); iter++) { AString cmdSwitch = iter->first; cmdSwitch = cmdSwitch.leftJustified(longestSwitch + 2, ' '); AString description = iter->second; cout << qPrintable(cmdSwitch) << qPrintable(description) << endl; } } void CommandOperationManager::printDeprecatedCommands() { map cmdMap; int64_t longestSwitch = 0; const uint64_t numberOfDeprecated = this->deprecatedOperations.size(); for (uint64_t i = 0; i < numberOfDeprecated; i++) { CommandOperation* op = this->deprecatedOperations[i]; const AString cmdSwitch = op->getCommandLineSwitch(); const int64_t switchLength = cmdSwitch.length(); if (switchLength > longestSwitch) { longestSwitch = switchLength; } cmdMap.insert(make_pair(cmdSwitch, op->getOperationShortDescription())); #ifndef NDEBUG const AString helpInfo = op->getHelpInformation("");//TSC: generating help info takes a little processing (populating and walking an OperationParameters tree for each command) if (helpInfo.isEmpty()) {//So, test the same define as for asserts and skip this check in release CaretLogSevere("Command has no help info: " + cmdSwitch); } #endif } for (map::iterator iter = cmdMap.begin(); iter != cmdMap.end(); iter++) { AString cmdSwitch = iter->first; cmdSwitch = cmdSwitch.leftJustified(longestSwitch + 2, ' '); AString description = iter->second; cout << qPrintable(cmdSwitch) << qPrintable(description) << endl; } } /** * Print all of the commands matching a partial switch. */ void CommandOperationManager::printAllCommandsMatching(const AString& partialSwitch) { map cmdMap; int64_t longestSwitch = -1; const uint64_t numberOfCommands = this->commandOperations.size(); for (uint64_t i = 0; i < numberOfCommands; i++) { CommandOperation* op = this->commandOperations[i]; const AString cmdSwitch = op->getCommandLineSwitch(); if (cmdSwitch.startsWith(partialSwitch)) { const int64_t switchLength = cmdSwitch.length(); if (switchLength > longestSwitch) { longestSwitch = switchLength; } cmdMap.insert(make_pair(cmdSwitch, op->getOperationShortDescription())); #ifndef NDEBUG const AString helpInfo = op->getHelpInformation("");//TSC: generating help info takes a little processing (populating and walking an OperationParameters tree for each command) if (helpInfo.isEmpty()) {//So, test the same define as for asserts and skip this check in release CaretLogSevere("Command has no help info: " + cmdSwitch); } #endif } } if (longestSwitch == -1)//no command found { throw CommandException("the switch '" + partialSwitch + "' does not match any processing commands"); } for (map::iterator iter = cmdMap.begin(); iter != cmdMap.end(); iter++) { AString cmdSwitch = iter->first; if (cmdSwitch.startsWith(partialSwitch)) { cmdSwitch = cmdSwitch.leftJustified(longestSwitch + 2, ' '); AString description = iter->second; cout << qPrintable(cmdSwitch) << qPrintable(description) << endl; } } } /** * Get the command operations. * * @return * A vector containing the command operations. * Do not modify the returned value. */ vector CommandOperationManager::getCommandOperations() { return this->commandOperations; } void CommandOperationManager::printHelpInfo() { cout << ApplicationInformation().getSummaryInformationInString("\n"); //guide for wrap, assuming 80 columns: | cout << endl << "Information options:" << endl; cout << " -help show this help info" << endl; cout << " -arguments-help explain the format of subcommand help info" << endl; cout << " -version show extended version information" << endl; cout << " -list-commands list all processing subcommands" << endl; cout << " -list-deprecated-commands list deprecated subcommands" << endl; cout << " -all-commands-help show all processing subcommands and their help" << endl; cout << " info - VERY LONG" << endl; cout << endl << "Global options (can be added to any command):" << endl; cout << " -disable-provenance don't generate provenance info in output files" << endl; cout << " -logging set the logging level, valid values are:" << endl; vector logLevels; LogLevelEnum::getAllEnums(logLevels); for (vector::iterator iter = logLevels.begin(); iter != logLevels.end(); iter++) { cout << " " << LogLevelEnum::toName(*iter) << endl; } cout << endl; cout << "To get the help information on a processing subcommand, run it without any" << endl; cout << " additional arguments." << endl; cout << endl; cout << "If the first argument is not recognized, all processing commands that start" << endl; cout << " with the argument are displayed" << endl; cout << endl; } void CommandOperationManager::printArgumentsHelp(const AString& programName) { //guide for wrap, assuming 80 columns: | cout << " To get the help information on a subcommand, run it without any additional" << endl; cout << " arguments. Options can occur in any position within the correct scope, and" << endl; cout << " can have suboptions, which must occur within the scope of the option. The" << endl; cout << " easiest way to get this right is to specify options and arguments in the" << endl; cout << " order they are listed. As an example, consider this help information:" << endl; cout << "$ " << programName << " -volume-math" << endl; cout << "EVALUATE EXPRESSION ON VOLUME FILES" << endl; cout << " " << programName << " -volume-math" << endl; cout << " - the expression to evaluate, in quotes" << endl; cout << " - output - the output volume" << endl; cout << endl; cout << " [-fixnan] - replace NaN results with a value" << endl; cout << " - value to replace NaN with" << endl; cout << endl; cout << " [-var] (repeatable) - repeatable - a volume file to use as a variable" << endl; cout << " - the name of the variable, as used in the expression" << endl; cout << " - the volume file to use as this variable" << endl; cout << endl; cout << " [-subvolume] - select a single subvolume" << endl; cout << " - the subvolume number or name" << endl; cout << endl; cout << " [-repeat] - reuse a single subvolume for each subvolume of calculation" << endl; cout << "..." << endl; cout << endl; //guide for wrap, assuming 80 columns: | cout << " '' and '' denote mandatory parameters. '[-fixnan]'" << endl; cout << " denotes an option taking one mandatory parameter '', and" << endl; cout << " '[-var] (repeatable)' denotes a repeatable option with mandatory parameters" << endl; cout << " '' and '', and two suboptions: '[-subvolume]', which has a" << endl; cout << " mandatory parameter '', and '[-repeat]', which takes no parameters." << endl; cout << " Commands also provide additional help info below the section in the example." << endl; cout << " Each option starts a new scope, and all options and arguments end any scope" << endl; cout << " that they are not valid in. For example, this command is correct:" << endl; cout << endl; cout << "$ " << programName << " -volume-math 'sin(x)' sin_x.nii.gz -fixnan 0 -var x x.nii.gz -subvolume 1" << endl; cout << endl; cout << " as is this one (though less intuitive):" << endl; cout << endl; cout << "$ " << programName << " -volume-math -fixnan 0 'sin(x)' -var x -subvolume 1 x.nii.gz sin_x.nii.gz" << endl; cout << endl; cout << " while this one is not, because the -fixnan option ends the scope of the -var" << endl; cout << " option before all of its mandatory arguments are given:" << endl; cout << endl; cout << "$ " << programName << " -volume-math 'sin(x)' sin_x.nii.gz -var x -fixnan 0 x.nii.gz -subvolume 1" << endl; cout << endl; //guide for wrap, assuming 80 columns: | cout << " and this one is incorrect because the -subvolume option occurs after the" << endl; cout << " scope of the -var option has ended due to -fixnan:" << endl; cout << endl; cout << "$ " << programName << " -volume-math 'sin(x)' sin_x.nii.gz -var x x.nii.gz -fixnan 0 -subvolume 1" << endl; cout << endl; cout << " and this one is similarly incorrect because the -subvolume option occurs" << endl; cout << " after the scope of the -var option has ended due to the volume-out argument:" << endl; cout << endl; cout << "$ " << programName << " -volume-math 'sin(x)' -fixnan 0 -var x x.nii.gz sin_x.nii.gz -subvolume 1" << endl; cout << endl; } void CommandOperationManager::printVersionInfo() { ApplicationInformation myInfo; vector myLines; myInfo.getAllInformation(myLines); for (int i = 0; i < (int)myLines.size(); ++i) { cout << myLines[i] << endl; } } void CommandOperationManager::printAllCommandsHelpInfo(const AString& programName) { map cmdMap; const uint64_t numberOfCommands = this->commandOperations.size(); for (uint64_t i = 0; i < numberOfCommands; i++) { CommandOperation* op = this->commandOperations[i]; cmdMap[op->getCommandLineSwitch()] = op; } for (map::iterator iter = cmdMap.begin(); iter != cmdMap.end(); iter++) { cout << iter->first << endl; cout << iter->second->getHelpInformation(programName) << endl << endl; } } workbench-1.1.1/src/Commands/CommandOperationManager.h000066400000000000000000000052161255417355300227450ustar00rootroot00000000000000#ifndef __COMMAND_OPERATION_MANAGER_H__ #define __COMMAND_OPERATION_MANAGER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretObject.h" #include "CommandException.h" namespace caret { class CommandOperation; class ProgramParameters; /// Manages all command operations. class CommandOperationManager : public CaretObject { public: static CommandOperationManager* getCommandOperationManager(); static void deleteCommandOperationManager(); ~CommandOperationManager(); void runCommand(ProgramParameters& parameters); std::vector getCommandOperations(); private: CommandOperationManager(); CommandOperationManager(const CommandOperationManager&); CommandOperationManager& operator=(const CommandOperationManager&); void printAllCommands(); void printDeprecatedCommands(); void printAllCommandsMatching(const AString& partialSwitch); void printAllCommandsHelpInfo(const AString& programName); void printHelpInfo(); void printArgumentsHelp(const AString& programName); void printVersionInfo(); bool getGlobalOption(ProgramParameters& parameters, const AString& optionString, const int& numArgs, std::vector& arguments); private: std::vector commandOperations, deprecatedOperations; static CommandOperationManager* singletonCommandOperationManager; }; #ifdef __COMMAND_OPERATION_MANAGER_DEFINE__ CommandOperationManager* CommandOperationManager::singletonCommandOperationManager = NULL; #endif // __COMMAND_OPERATION_MANAGER_DEFINE__ } // namespace #endif // __COMMAND_OPERATION_MANAGER_H__ workbench-1.1.1/src/Commands/CommandParser.cxx000066400000000000000000001166741255417355300213340ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CommandParser.h" #include "AlgorithmException.h" #include "ApplicationInformation.h" #include "BorderFile.h" #include "CaretAssert.h" #include "CaretCommandLine.h" #include "CaretDataFileHelper.h" #include "CaretLogger.h" #include "CiftiFile.h" #include "DataFileException.h" #include "FileInformation.h" #include "FociFile.h" #include "GiftiMetaData.h" #include "LabelFile.h" #include "MetricFile.h" #include "OperationException.h" #include "SurfaceFile.h" #include "VolumeFile.h" #include #include using namespace caret; using namespace std; const AString CommandParser::PROVENANCE_NAME = "Provenance"; const AString CommandParser::PARENT_PROVENANCE_NAME = "ParentProvenance"; const AString CommandParser::PROGRAM_PROVENANCE_NAME = "ProgramProvenance"; const AString CommandParser::CWD_PROVENANCE_NAME = "WorkingDirectory"; CommandParser::CommandParser(AutoOperationInterface* myAutoOper) : CommandOperation(myAutoOper->getCommandSwitch(), myAutoOper->getShortDescription()), OperationParserInterface(myAutoOper) { m_doProvenance = true; } void CommandParser::disableProvenance() { m_doProvenance = false; } void CommandParser::executeOperation(ProgramParameters& parameters) { CaretPointer myAlgParams(m_autoOper->getParameters());//could be an autopointer, but this is safer vector myOutAssoc; m_provenance = caret_global_commandLine; //the idea is to have m_provenance set before the command executes, so it can be overridden, but have m_parentProvenance set AFTER the processing is complete //the parent provenance should never be generated manually m_parentProvenance = "";//in case someone tries to use the same instance more than once m_workingDir = QDir::currentPath();//get the current path, in case some stupid command changes the working directory //these get set on output files during writeOutput (and for on-disk in provenanceBeforeOperation) parseComponent(myAlgParams.getPointer(), parameters, myOutAssoc);//parsing block parameters.verifyAllParametersProcessed(); makeOnDiskOutputs(myOutAssoc);//check for input on-disk files used as output on-disk files //code to show what arguments map to what parameters should go here if (m_doProvenance) provenanceBeforeOperation(myOutAssoc); m_autoOper->useParameters(myAlgParams.getPointer(), NULL);//TODO: progress status for caret_command? would probably get messed up by any command info output vector uncheckedWarnings = myAlgParams->findUncheckedParams("the command"); for (size_t i = 0; i < uncheckedWarnings.size(); ++i) { CaretLogWarning("developer warning: " + uncheckedWarnings[i]); } if (m_doProvenance) provenanceAfterOperation(myOutAssoc); //TODO: deallocate input files - give abstract parameter a virtual deallocate method? use CaretPointer and rely on reference counting? writeOutput(myOutAssoc); } void CommandParser::showParsedOperation(ProgramParameters& parameters) { CaretPointer myAlgParams(m_autoOper->getParameters());//could be an autopointer, but this is safer vector myOutAssoc; parseComponent(myAlgParams.getPointer(), parameters, myOutAssoc, true);//parsing block parameters.verifyAllParametersProcessed(); //don't execute or write parsed output } void CommandParser::parseComponent(ParameterComponent* myComponent, ProgramParameters& parameters, vector& outAssociation, bool debug) { uint32_t i; for (i = 0; i < myComponent->m_paramList.size(); ++i) { AString nextArg = parameters.nextString(myComponent->m_paramList[i]->m_shortName); if (!nextArg.isEmpty() && nextArg[0] == '-') { bool success = parseOption(nextArg, myComponent, parameters, outAssociation, debug); if (!success) { switch (myComponent->m_paramList[i]->getType()) { case OperationParametersEnum::STRING: case OperationParametersEnum::INT: case OperationParametersEnum::DOUBLE: break;//it is probably a negative number, so don't throw an exception unless it fails to parse as one default: throw ProgramParametersException("Invalid option \"" + nextArg + "\" while next required argument is <" + myComponent->m_paramList[i]->m_shortName + ">, option is either incorrect, or incorrectly placed"); }; } else { --i; continue;//so skip trying to parse it as a required argument } } const OperationParametersEnum::Enum nextType = myComponent->m_paramList[i]->getType();// need in catch statement below try { switch (myComponent->m_paramList[i]->getType()) { case OperationParametersEnum::BOOL: { parameters.backup(); ((BooleanParameter*)myComponent->m_paramList[i])->m_parameter = parameters.nextBoolean(myComponent->m_paramList[i]->m_shortName); if (debug) { cout << "Parameter <" << myComponent->m_paramList[i]->m_shortName << "> parsed as "; cout << (((BooleanParameter*)myComponent->m_paramList[i])->m_parameter ? "true" : "false") << endl; } break; } case OperationParametersEnum::BORDER: { CaretPointer myFile(new BorderFile()); myFile->readFile(nextArg); if (m_doProvenance) { const GiftiMetaData* md = myFile->getFileMetaData(); if (md != NULL) { AString prov = md->get(PROVENANCE_NAME); if (prov != "") { m_parentProvenance += nextArg + ":\n" + prov + "\n\n"; } } } ((BorderParameter*)myComponent->m_paramList[i])->m_parameter = myFile; if (debug) { cout << "Parameter <" << myComponent->m_paramList[i]->m_shortName << "> opened file with name "; cout << nextArg << endl; } break; } case OperationParametersEnum::CIFTI: { FileInformation myInfo(nextArg); CaretPointer myFile(new CiftiFile()); myFile->openFile(nextArg); m_inputCiftiNames[myInfo.getCanonicalFilePath()] = myFile;//track input cifti, so we can check their size if (m_doProvenance)//just an optimization, if we aren't going to write provenance, don't generate it, either { const GiftiMetaData* md = myFile->getCiftiXML().getFileMetaData(); if (md != NULL) { if (md->exists(PROVENANCE_NAME)) { AString provenance = md->get(PROVENANCE_NAME); if (provenance != "") { m_parentProvenance += nextArg + ":\n" + provenance + "\n\n"; } } } } ((CiftiParameter*)myComponent->m_paramList[i])->m_parameter = myFile; if (debug) { cout << "Parameter <" << myComponent->m_paramList[i]->m_shortName << "> opened file with name "; cout << nextArg << endl; } break; } case OperationParametersEnum::DOUBLE: { parameters.backup(); ((DoubleParameter*)myComponent->m_paramList[i])->m_parameter = parameters.nextDouble(myComponent->m_paramList[i]->m_shortName); if (debug) { cout << "Parameter <" << myComponent->m_paramList[i]->m_shortName << "> parsed as "; cout << ((DoubleParameter*)myComponent->m_paramList[i])->m_parameter << endl; } break; } case OperationParametersEnum::FOCI: { CaretPointer myFile(new FociFile()); myFile->readFile(nextArg); if (m_doProvenance) { const GiftiMetaData* md = myFile->getFileMetaData(); if (md != NULL) { AString prov = md->get(PROVENANCE_NAME); if (prov != "") { m_parentProvenance += nextArg + ":\n" + prov + "\n\n"; } } } ((FociParameter*)myComponent->m_paramList[i])->m_parameter = myFile; if (debug) { cout << "Parameter <" << myComponent->m_paramList[i]->m_shortName << "> opened file with name "; cout << nextArg << endl; } break; } case OperationParametersEnum::INT: { parameters.backup(); ((IntegerParameter*)myComponent->m_paramList[i])->m_parameter = parameters.nextLong(myComponent->m_paramList[i]->m_shortName); if (debug) { cout << "Parameter <" << myComponent->m_paramList[i]->m_shortName << "> parsed as "; cout << ((IntegerParameter*)myComponent->m_paramList[i])->m_parameter << endl; } break; } case OperationParametersEnum::LABEL: { CaretPointer myFile(new LabelFile()); myFile->readFile(nextArg); if (m_doProvenance) { const GiftiMetaData* md = myFile->getFileMetaData(); if (md != NULL) { AString prov = md->get(PROVENANCE_NAME); if (prov != "") { m_parentProvenance += nextArg + ":\n" + prov + "\n\n"; } } } ((LabelParameter*)myComponent->m_paramList[i])->m_parameter = myFile; if (debug) { cout << "Parameter <" << myComponent->m_paramList[i]->m_shortName << "> opened file with name "; cout << nextArg << endl; } break; } case OperationParametersEnum::METRIC: { CaretPointer myFile(new MetricFile()); myFile->readFile(nextArg); if (m_doProvenance) { const GiftiMetaData* md = myFile->getFileMetaData(); if (md != NULL) { AString prov = md->get(PROVENANCE_NAME); if (prov != "") { m_parentProvenance += nextArg + ":\n" + prov + "\n\n"; } } } ((MetricParameter*)myComponent->m_paramList[i])->m_parameter = myFile; if (debug) { cout << "Parameter <" << myComponent->m_paramList[i]->m_shortName << "> opened file with name "; cout << nextArg << endl; } break; } case OperationParametersEnum::STRING: { ((StringParameter*)myComponent->m_paramList[i])->m_parameter = nextArg; if (debug) { cout << "Parameter <" << myComponent->m_paramList[i]->m_shortName << "> parsed as "; cout << ((StringParameter*)myComponent->m_paramList[i])->m_parameter << endl; } break; } case OperationParametersEnum::SURFACE: { CaretPointer myFile(new SurfaceFile()); myFile->readFile(nextArg); if (m_doProvenance) { const GiftiMetaData* md = myFile->getFileMetaData(); if (md != NULL) { AString prov = md->get(PROVENANCE_NAME); if (prov != "") { m_parentProvenance += nextArg + ":\n" + prov + "\n\n"; } } } ((SurfaceParameter*)myComponent->m_paramList[i])->m_parameter = myFile; if (debug) { cout << "Parameter <" << myComponent->m_paramList[i]->m_shortName << "> opened file with name "; cout << nextArg << endl; } break; } case OperationParametersEnum::VOLUME: { CaretPointer myFile(new VolumeFile()); myFile->readFile(nextArg); if (m_doProvenance) { const GiftiMetaData* md = myFile->getFileMetaData(); if (md != NULL) { AString prov = md->get(PROVENANCE_NAME); if (prov != "") { m_parentProvenance += nextArg + ":\n" + prov + "\n\n"; } } } ((VolumeParameter*)myComponent->m_paramList[i])->m_parameter = myFile; if (debug) { cout << "Parameter <" << myComponent->m_paramList[i]->m_shortName << "> opened file with name "; cout << nextArg << endl; } break; } }; } catch (const bad_alloc&) { switch (nextType) { case OperationParametersEnum::BORDER: case OperationParametersEnum::CIFTI: case OperationParametersEnum::FOCI: case OperationParametersEnum::LABEL: case OperationParametersEnum::METRIC: case OperationParametersEnum::SURFACE: case OperationParametersEnum::VOLUME: /* * Provide information to the user about which * file caused the std::bad_alloc including * the size of the file. */ throw DataFileException(nextArg, CaretDataFileHelper::createBadAllocExceptionMessage(nextArg)); break; case OperationParametersEnum::DOUBLE: case OperationParametersEnum::INT: case OperationParametersEnum::STRING: case OperationParametersEnum::BOOL: throw DataFileException("Unable to allocate memory for input: " + nextArg); break; } } } for (i = 0; i < myComponent->m_outputList.size(); ++i) {//parse the output options of this component AString nextArg = parameters.nextString(myComponent->m_outputList[i]->m_shortName); if (!nextArg.isEmpty() && nextArg[0] == '-') { bool success = parseOption(nextArg, myComponent, parameters, outAssociation, debug); if (!success) { throw ProgramParametersException("Invalid option \"" + nextArg + "\" while next reqired argument is <" + myComponent->m_outputList[i]->m_shortName + ">, option is either incorrect, or incorrectly placed"); } --i;//options do not set required arguments continue;//so rewind the index and skip trying to parse it as a required argument } OutputAssoc tempItem; tempItem.m_fileName = nextArg; tempItem.m_param = myComponent->m_outputList[i]; switch (myComponent->m_outputList[i]->getType())//allocate outputs that only have in-memory implementations { case OperationParametersEnum::BORDER: { CaretPointer& myFile = ((BorderParameter*)(myComponent->m_outputList[i]))->m_parameter; myFile.grabNew(new BorderFile()); break; } case OperationParametersEnum::CIFTI: break;//we create this in makeOnDiskOutputs(), and do the metadata stuff in provenanceForOnDiskOutputs() for this type case OperationParametersEnum::FOCI: { CaretPointer& myFile = ((FociParameter*)(myComponent->m_outputList[i]))->m_parameter; myFile.grabNew(new FociFile()); break; } case OperationParametersEnum::LABEL: { CaretPointer& myFile = ((LabelParameter*)(myComponent->m_outputList[i]))->m_parameter; myFile.grabNew(new LabelFile()); break; } case OperationParametersEnum::METRIC: { CaretPointer& myFile = ((MetricParameter*)(myComponent->m_outputList[i]))->m_parameter; myFile.grabNew(new MetricFile()); break; } case OperationParametersEnum::SURFACE: { CaretPointer& myFile = ((SurfaceParameter*)(myComponent->m_outputList[i]))->m_parameter; myFile.grabNew(new SurfaceFile()); break; } case OperationParametersEnum::VOLUME: { CaretPointer& myFile = ((VolumeParameter*)(myComponent->m_outputList[i]))->m_parameter; myFile.grabNew(new VolumeFile()); break; } case OperationParametersEnum::DOUBLE://ignore these output types case OperationParametersEnum::INT: case OperationParametersEnum::STRING: case OperationParametersEnum::BOOL: CaretLogWarning("encountered ignored output type, " + OperationParametersEnum::toName(myComponent->m_outputList[i]->getType())); break; } outAssociation.push_back(tempItem); if (debug) { cout << "Output parameter <" << tempItem.m_param->m_shortName << "> given output name "; cout << tempItem.m_fileName << endl; } } parseRemainingOptions(myComponent, parameters, outAssociation, debug); } bool CommandParser::parseOption(const AString& mySwitch, ParameterComponent* myComponent, ProgramParameters& parameters, vector& outAssociation, bool debug) { for (uint32_t i = 0; i < myComponent->m_optionList.size(); ++i) { if (mySwitch == myComponent->m_optionList[i]->m_optionSwitch) { if (debug) { cout << "Now parsing option " << myComponent->m_optionList[i]->m_optionSwitch << endl; } if (myComponent->m_optionList[i]->m_present) { throw ProgramParametersException("Option \"" + mySwitch + "\" specified more than once"); } myComponent->m_optionList[i]->m_present = true; parseComponent(myComponent->m_optionList[i], parameters, outAssociation, debug); if (debug) { cout << "Finished parsing option " << myComponent->m_optionList[i]->m_optionSwitch << endl; } return true; } } for (uint32_t i = 0; i < myComponent->m_repeatableOptions.size(); ++i) { if (mySwitch == myComponent->m_repeatableOptions[i]->m_optionSwitch) { if (debug) { cout << "Now parsing repeatable option " << myComponent->m_repeatableOptions[i]->m_optionSwitch << endl; } myComponent->m_repeatableOptions[i]->m_instances.push_back(new ParameterComponent(myComponent->m_repeatableOptions[i]->m_template)); parseComponent(myComponent->m_repeatableOptions[i]->m_instances.back(), parameters, outAssociation, debug); if (debug) { cout << "Finished parsing repeatable option " << myComponent->m_repeatableOptions[i]->m_optionSwitch << endl; } return true; } } return false; } void CommandParser::parseRemainingOptions(ParameterComponent* myComponent, ProgramParameters& parameters, vector& outAssociation, bool debug) { while (parameters.hasNext()) { AString nextArg = parameters.nextString("option"); if (!nextArg.isEmpty() && nextArg[0] == '-') { bool success = parseOption(nextArg, myComponent, parameters, outAssociation, debug); if (!success) { parameters.backup(); return; } } else { parameters.backup(); return; } } } void CommandParser::provenanceBeforeOperation(const vector& outAssociation) { vector versionInfo;//need this for on-disk outputs, because we have to set it before the command executes ApplicationInformation myInfo; myInfo.getAllInformation(versionInfo); AString versionProvenance; for (int i = 0; i < (int)versionInfo.size(); ++i) { versionProvenance += versionInfo[i] + "\n"; } for (uint32_t i = 0; i < outAssociation.size(); ++i) { AbstractParameter* myParam = outAssociation[i].m_param; switch (myParam->getType()) { case OperationParametersEnum::CIFTI: { CiftiFile* myFile = ((CiftiParameter*)myParam)->m_parameter; CiftiXML myXML; CiftiSeriesMap tempMap; tempMap.setLength(1); tempMap.setStep(1.0f); tempMap.setStart(0.0f); tempMap.setUnit(CiftiSeriesMap::SECOND); myXML.setNumberOfDimensions(2); myXML.setMap(0, tempMap); myXML.setMap(1, tempMap); GiftiMetaData* mymd = myXML.getFileMetaData(); mymd->set(PROVENANCE_NAME, m_provenance); mymd->set(PROGRAM_PROVENANCE_NAME, versionProvenance);//cifti is on-disk, so set all provenance now, because we can't later mymd->set(CWD_PROVENANCE_NAME, m_workingDir); if (m_parentProvenance != "") { mymd->set(PARENT_PROVENANCE_NAME, m_parentProvenance); } myFile->setCiftiXML(myXML, false);//tells it to use this new metadata, rather than copying metadata from the old XML (which is default so that provenance metadata persists through naive usage) break; } default: break; } } } void CommandParser::provenanceAfterOperation(const vector& outAssociation) { vector versionInfo;//now we need this information for outputs that are in memory until written ApplicationInformation myInfo; myInfo.getAllInformation(versionInfo); AString versionProvenance; for (int i = 0; i < (int)versionInfo.size(); ++i) { versionProvenance += versionInfo[i] + "\n"; } for (uint32_t i = 0; i < outAssociation.size(); ++i) { AbstractParameter* myParam = outAssociation[i].m_param; GiftiMetaData* md = NULL; switch (myParam->getType()) { case OperationParametersEnum::BORDER: { BorderFile* myFile = ((BorderParameter*)myParam)->m_parameter; md = myFile->getFileMetaData(); break; } case OperationParametersEnum::FOCI: { FociFile* myFile = ((FociParameter*)myParam)->m_parameter; md = myFile->getFileMetaData(); break; } case OperationParametersEnum::LABEL: { LabelFile* myFile = ((LabelParameter*)myParam)->m_parameter; md = myFile->getFileMetaData(); break; } case OperationParametersEnum::METRIC: { MetricFile* myFile = ((MetricParameter*)myParam)->m_parameter; md = myFile->getFileMetaData(); break; } case OperationParametersEnum::SURFACE: { SurfaceFile* myFile = ((SurfaceParameter*)myParam)->m_parameter; md = myFile->getFileMetaData(); break; } case OperationParametersEnum::VOLUME: { VolumeFile* myFile = ((VolumeParameter*)myParam)->m_parameter; md = myFile->getFileMetaData(); break; } default: break; } if (md != NULL) { md->set(PROVENANCE_NAME, m_provenance); md->set(PROGRAM_PROVENANCE_NAME, versionProvenance); md->set(CWD_PROVENANCE_NAME, m_workingDir); if (m_parentProvenance != "") { md->set(PARENT_PROVENANCE_NAME, m_parentProvenance); } } } } void CommandParser::makeOnDiskOutputs(const vector& outAssociation) { for (uint32_t i = 0; i < outAssociation.size(); ++i) { AbstractParameter* myParam = outAssociation[i].m_param; switch (myParam->getType()) { case OperationParametersEnum::CIFTI: { CiftiParameter* myCiftiParam = (CiftiParameter*)myParam; FileInformation myInfo(outAssociation[i].m_fileName); map::iterator iter = m_inputCiftiNames.find(myInfo.getCanonicalFilePath()); if (iter != m_inputCiftiNames.end()) { vector dims = iter->second->getDimensions(); int64_t totalSize = sizeof(float); for (int j = 0; j < (int)dims.size(); ++j) { totalSize *= dims[j]; } if (totalSize > ((int64_t)2) * 1024 * 1024 * 1024)//suppress the message for non-large input files, on the assumption that the output file will be the same size { CaretLogInfo("Computing output file '" + outAssociation[i].m_fileName + "' in memory due to collision with input file"); } myCiftiParam->m_parameter.grabNew(new CiftiFile()); } else { myCiftiParam->m_parameter.grabNew(new CiftiFile()); myCiftiParam->m_parameter->setWritingFile(outAssociation[i].m_fileName); } break; } default: break; } } } void CommandParser::writeOutput(const vector& outAssociation) { for (uint32_t i = 0; i < outAssociation.size(); ++i) { AbstractParameter* myParam = outAssociation[i].m_param; switch (myParam->getType()) { case OperationParametersEnum::BOOL://ignores the name you give the output for now, but what gives primitive type output and how is it used? cout << "Output Boolean \"" << myParam->m_shortName << "\" value is " << ((BooleanParameter*)myParam)->m_parameter << endl; break; case OperationParametersEnum::BORDER: { BorderFile* myFile = ((BorderParameter*)myParam)->m_parameter; myFile->writeFile(outAssociation[i].m_fileName); break; } case OperationParametersEnum::CIFTI: { CiftiFile* myFile = ((CiftiParameter*)myParam)->m_parameter;//we can't set metadata here because the XML is already on disk, see provenanceForOnDiskOutputs myFile->writeFile(outAssociation[i].m_fileName);//this is basically a noop unless outputs and inputs collide, we opened ON_DISK and set cache file to this name back in makeOnDiskOutputs break; } case OperationParametersEnum::DOUBLE: cout << "Output Floating Point \"" << myParam->m_shortName << "\" value is " << ((DoubleParameter*)myParam)->m_parameter << endl; break; case OperationParametersEnum::INT: cout << "Output Integer \"" << myParam->m_shortName << "\" value is " << ((IntegerParameter*)myParam)->m_parameter << endl; break; case OperationParametersEnum::FOCI: { FociFile* myFile = ((FociParameter*)myParam)->m_parameter; myFile->writeFile(outAssociation[i].m_fileName); break; } case OperationParametersEnum::LABEL: { LabelFile* myFile = ((LabelParameter*)myParam)->m_parameter; myFile->writeFile(outAssociation[i].m_fileName); break; } case OperationParametersEnum::METRIC: { MetricFile* myFile = ((MetricParameter*)myParam)->m_parameter; myFile->writeFile(outAssociation[i].m_fileName); break; } case OperationParametersEnum::STRING: cout << "Output String \"" << myParam->m_shortName << "\" value is " << ((StringParameter*)myParam)->m_parameter << endl; break; case OperationParametersEnum::SURFACE: { SurfaceFile* myFile = ((SurfaceParameter*)myParam)->m_parameter; myFile->writeFile(outAssociation[i].m_fileName); break; } case OperationParametersEnum::VOLUME: { VolumeFile* myFile = ((VolumeParameter*)myParam)->m_parameter; myFile->writeFile(outAssociation[i].m_fileName); break; } default: CaretAssertMessage(false, "Writing of this parameter type has not been implemented in this parser");//assert instead of throw because this is a code error, not a user error throw CommandException("Internal parsing error, please let the developers know what you just tried to do");//but don't let release pass by it either } } } AString CommandParser::getHelpInformation(const AString& programName) { m_minIndent = 0; m_indentIncrement = 3; m_maxWidth = 79;//leave a space on the right edge of an 80-wide terminal so that it looks better - TODO: get the terminal width from some system call m_maxIndent = 31;//don't let indenting take up more than this int curIndent = m_minIndent; AString ret; ret = formatString(getOperationShortDescription(), curIndent, true); curIndent += m_indentIncrement; ret += getIndentString(curIndent) + programName + " " + getCommandLineSwitch() + "\n";//DO NOT format the command that people may want to copy and paste, added hyphens would be disastrous curIndent += m_indentIncrement; OperationParameters* myAlgParams = m_autoOper->getParameters(); addComponentDescriptions(ret, myAlgParams, curIndent); ret += "\n";//separate prose with a newline addHelpProse(ret, myAlgParams, curIndent); delete myAlgParams; return ret; } void CommandParser::addHelpComponent(AString& info, ParameterComponent* myComponent, int curIndent) { for (int i = 0; i < (int)myComponent->m_paramList.size(); ++i) { info += formatString("<" + myComponent->m_paramList[i]->m_shortName + ">", curIndent, true); } for (int i = 0; i < (int)myComponent->m_outputList.size(); ++i) { info += formatString("<" + myComponent->m_outputList[i]->m_shortName + ">", curIndent, true); } addHelpOptions(info, myComponent, curIndent); } void CommandParser::addHelpOptions(AString& info, ParameterComponent* myComponent, int curIndent) { for (int i = 0; i < (int)myComponent->m_optionList.size(); ++i) { info += formatString("[" + myComponent->m_optionList[i]->m_optionSwitch + "]", curIndent, true); addHelpComponent(info, myComponent->m_optionList[i], curIndent + m_indentIncrement);//indent arguments to options } for (int i = 0; i < (int)myComponent->m_repeatableOptions.size(); ++i) { info += formatString("[" + myComponent->m_repeatableOptions[i]->m_optionSwitch + "] (repeatable)", curIndent, true); addHelpComponent(info, &(myComponent->m_repeatableOptions[i]->m_template), curIndent + m_indentIncrement);//indent arguments to options } } void CommandParser::addHelpProse(AString& info, OperationParameters* myAlgParams, int curIndent) {//NOTE: does not currently format tabs well, don't use them AString* rawProse = &(myAlgParams->getHelpText());//friendlier name info += formatString(*rawProse, curIndent, false);//don't indent on added newlines in the prose } AString CommandParser::formatString(const AString& in, int curIndent, bool addIndent) {//NOTE: does not currently format tabs well, don't use them AString curIndentString = getIndentString(curIndent); bool haveAddedBreak = false; AString ret; int charMax = m_maxWidth - curIndentString.size(); int curIndex = 0; while (curIndex < in.size()) { if (addIndent) { if (haveAddedBreak) { curIndentString = getIndentString(curIndent + m_indentIncrement); charMax = m_maxWidth - curIndentString.size(); } else { curIndentString = getIndentString(curIndent); charMax = m_maxWidth - curIndentString.size(); } } int endIndex = curIndex; while (endIndex - curIndex < charMax && endIndex < in.size() && in[endIndex] != '\n') {//start by crawling until newline or at max width ++endIndex; } if (endIndex >= in.size()) { ret += curIndentString + in.mid(curIndex, endIndex - curIndex) + "\n"; } else { if (in[endIndex] == '\n') { while (endIndex < in.size() && in[endIndex] == '\n') {//crawl over any additional newlines ++endIndex; } haveAddedBreak = false; ret += curIndentString + in.mid(curIndex, endIndex - curIndex); } else { int savedEnd = endIndex; while (endIndex > curIndex && in[endIndex] != ' ') {//crawl in reverse until a space, or reaching curIndex - change this if you want hyphenation to take place more often than lines without any spaces --endIndex; } if (endIndex > curIndex) {//found a space we can break at while (endIndex > curIndex && in[endIndex] == ' ') {//don't print any of the spaces --endIndex; } if (endIndex > curIndex) { ++endIndex;//print the character before the space } haveAddedBreak = true; ret += curIndentString + in.mid(curIndex, endIndex - curIndex) + "\n"; } else {//hyphenate endIndex = savedEnd - 1; haveAddedBreak = true; ret += curIndentString + in.mid(curIndex, endIndex - curIndex) + "-\n"; } } } curIndex = endIndex; if (haveAddedBreak)//don't skip spaces after literal newlines { while (curIndex < in.size() && in[curIndex] == ' ') {//skip spaces ++curIndex; } } } return ret; } void CommandParser::addComponentDescriptions(AString& info, ParameterComponent* myComponent, int curIndent) { for (int i = 0; i < (int)myComponent->m_paramList.size(); ++i) { info += formatString("<" + myComponent->m_paramList[i]->m_shortName + "> - " + myComponent->m_paramList[i]->m_description, curIndent, true); } for (int i = 0; i < (int)myComponent->m_outputList.size(); ++i) { info += formatString("<" + myComponent->m_outputList[i]->m_shortName + "> - output - " + myComponent->m_outputList[i]->m_description, curIndent, true); } addOptionDescriptions(info, myComponent, curIndent); } void CommandParser::addOptionDescriptions(AString& info, ParameterComponent* myComponent, int curIndent) { for (int i = 0; i < (int)myComponent->m_optionList.size(); ++i) { info += "\n" + formatString("[" + myComponent->m_optionList[i]->m_optionSwitch + "] - " + myComponent->m_optionList[i]->m_description, curIndent, true); addComponentDescriptions(info, myComponent->m_optionList[i], curIndent + m_indentIncrement);//indent arguments to options } for (int i = 0; i < (int)myComponent->m_repeatableOptions.size(); ++i) { info += "\n" + formatString("[" + myComponent->m_repeatableOptions[i]->m_optionSwitch + "] - repeatable - " + myComponent->m_repeatableOptions[i]->m_description, curIndent, true); addComponentDescriptions(info, &(myComponent->m_repeatableOptions[i]->m_template), curIndent + m_indentIncrement);//indent arguments to options } } AString CommandParser::getIndentString(int desired) { AString space(" "); int num = desired; if (num > m_maxIndent) num = m_maxIndent; if (num < m_minIndent) num = m_minIndent; return space.repeated(num); } bool CommandParser::takesParameters() { return m_autoOper->takesParameters(); } workbench-1.1.1/src/Commands/CommandParser.h000066400000000000000000000071711255417355300207500ustar00rootroot00000000000000#ifndef __COMMAND_PARSER_H__ #define __COMMAND_PARSER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationParameters.h" #include "AbstractOperation.h" #include "CommandOperation.h" #include "ProgramParameters.h" #include "CommandException.h" #include "ProgramParametersException.h" #include #include namespace caret { class CommandParser : public CommandOperation, OperationParserInterface { int m_minIndent, m_maxIndent, m_indentIncrement, m_maxWidth; AString m_provenance, m_parentProvenance, m_workingDir; bool m_doProvenance; const static AString PROVENANCE_NAME, PARENT_PROVENANCE_NAME, PROGRAM_PROVENANCE_NAME, CWD_PROVENANCE_NAME;//TODO: put this elsewhere? std::map m_inputCiftiNames; struct OutputAssoc {//how the output is stored is up to the parser, in the GUI it should load into memory without writing to disk AString m_fileName; AbstractParameter* m_param; }; void parseComponent(ParameterComponent* myComponent, ProgramParameters& parameters, std::vector& outAssociation, bool debug = false); bool parseOption(const AString& mySwitch, ParameterComponent* myComponent, ProgramParameters& parameters, std::vector& outAssociation, bool debug); void parseRemainingOptions(ParameterComponent* myAlgParams, ProgramParameters& parameters, std::vector& outAssociation, bool debug); void provenanceBeforeOperation(const std::vector& outAssociation); void provenanceAfterOperation(const std::vector& outAssociation); void makeOnDiskOutputs(const std::vector& outAssociation);//ensures on-disk inputs aren't used as on-disk outputs, converting outputs to in-memory when needed void writeOutput(const std::vector& outAssociation); AString getIndentString(int desired); void addHelpComponent(AString& info, ParameterComponent* myComponent, int curIndent); void addHelpOptions(AString& info, ParameterComponent* myAlgParams, int curIndent); void addHelpProse(AString& info, OperationParameters* myAlgParams, int curIndent); void addComponentDescriptions(AString& info, ParameterComponent* myComponent, int curIndent); void addOptionDescriptions(AString& info, ParameterComponent* myComponent, int curIndent); AString formatString(const AString& in, int curIndent, bool addIndent); public: CommandParser(AutoOperationInterface* myAutoOper); void disableProvenance(); void executeOperation(ProgramParameters& parameters); void showParsedOperation(ProgramParameters& parameters); AString getHelpInformation(const AString& programName); bool takesParameters(); }; }; #endif //__COMMAND_PARSER_H__ workbench-1.1.1/src/Commands/CommandUnitTest.cxx000066400000000000000000000034021255417355300216370ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include "CaretAssertion.h" #include "CommandUnitTest.h" #include "Plane.h" #include "SystemUtilities.h" using namespace caret; /** * Constructor. */ CommandUnitTest::CommandUnitTest() : CommandOperation("-unit-test", "UNIT TESTING") { } /** * Destructor. */ CommandUnitTest::~CommandUnitTest() { } /** * Execute the operation. * * @param parameters * Parameters for the operation. * @throws CommandException * If the command failed. * @throws ProgramParametersException * If there is an error in the parameters. */ void CommandUnitTest::executeOperation(ProgramParameters& /*parameters*/) { std::ostream* stream = &std::cout; CaretAssertion::unitTest(*stream, true); *stream << std::endl; Plane::unitTest(*stream, true); *stream << std::endl; SystemUtilities::unitTest(*stream, true); *stream << std::endl; } workbench-1.1.1/src/Commands/CommandUnitTest.h000066400000000000000000000030671255417355300212730ustar00rootroot00000000000000#ifndef __COMMAND_UNIT_TEST_H__ #define __COMMAND_UNIT_TEST_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CommandOperation.h" namespace caret { /// Command operation for unit testing. class CommandUnitTest : public CommandOperation { public: CommandUnitTest(); virtual ~CommandUnitTest(); virtual void executeOperation(ProgramParameters& parameters); AString getHelpInformation(const AString& /*programName*/) { return " "; }; virtual bool takesParameters() { return false; } private: CommandUnitTest(const CommandUnitTest&); CommandUnitTest& operator=(const CommandUnitTest&); }; } // namespace #endif // __COMMAND_UNIT_TEST_H__ workbench-1.1.1/src/Commands/README.txt000066400000000000000000000021521255417355300175340ustar00rootroot00000000000000Do not write new commands in this directory, that method is obsolete, new commands should be made as either Operations or Algorithms, as follows: The defining difference between Operations and Algorithms is whether they contain a way for other code to use them easily. Algorithms usually provide a "constructor" that takes the inputs and outputs, but really executes the thing the algorithm does (the reasoning for this is that it is the shortest method to access without an instance, so we are abusing the class concept for the purpose of associating several static methods). On the other hand, Operations only operate from arguments given to a parser of some kind. As such, Operations should be used only for things that would not be useful to other code (printing information to stdout or a text file, providing command line access to something that is already trivial to do in code). Once you know whether the command should be an Algorithm or Operation, open either Algorithms/AlgorithmTemplate.h.txt or Operations/OperationTemplate.h.txt, and follow the instructions in the block comment just below the license notice. workbench-1.1.1/src/Common/000077500000000000000000000000001255417355300155255ustar00rootroot00000000000000workbench-1.1.1/src/Common/AString.cxx000066400000000000000000000526001255417355300176230ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "AString.h" #include "CaretLogger.h" #include using namespace caret; std::ostream& operator << (std::ostream &lhs, const AString &rhs) { return lhs << rhs.toStdString(); } /** * Convert a vector of values into a string. * @param v * The vector of values. * @param separator * Inserted between each pair of values. * @return * String containing the vector's values separated * by the separator. */ AString AString::fromNumbers(const std::vector& v, const AString& separator) { AString s; for (uint64_t i = 0; i < v.size(); i++) { if (i > 0) { s += separator; } s += AString::number(v[i]); } return s; } /** * Convert a vector of values into a string. * @param v * The vector of values. * @param separator * Inserted between each pair of values. * @return * String containing the vector's values separated * by the separator. */ AString AString::fromNumbers(const std::vector& v, const AString& separator) { AString s; for (uint64_t i = 0; i < v.size(); i++) { if (i > 0) { s += separator; } s += AString::number(v[i]); } return s; } /** * Convert a vector of values into a string. * @param v * The vector of values. * @param separator * Inserted between each pair of values. * @return * String containing the vector's values separated * by the separator. */ AString AString::fromNumbers(const std::vector& v, const AString& separator) { AString s; for (uint64_t i = 0; i < v.size(); i++) { if (i > 0) { s += separator; } s += AString::number(v[i]); } return s; } /** * Convert a vector of values into a string. * @param v * The vector of values. * @param separator * Inserted between each pair of values. * @return * String containing the vector's values separated * by the separator. */ AString AString::fromNumbers(const std::vector& v, const AString& separator) { AString s; for (uint64_t i = 0; i < v.size(); i++) { if (i > 0) { s += separator; } s += AString::number(v[i]); } return s; } /** * Convert a vector of values into a string. * @param v * The vector of values. * @param separator * Inserted between each pair of values. * @return * String containing the vector's values separated * by the separator. */ AString AString::fromNumbers(const std::vector& v, const AString& separator) { AString s; for (uint64_t i = 0; i < v.size(); i++) { if (i > 0) { s += separator; } s += AString::number(v[i]); } return s; } /** * Convert a vector of values into a string. * @param v * The vector of values. * @param separator * Inserted between each pair of values. * @return * String containing the vector's values separated * by the separator. */ AString AString::fromNumbers(const std::vector& v, const AString& separator) { AString s; for (uint64_t i = 0; i < v.size(); i++) { if (i > 0) { s += separator; } s += AString::number(v[i]); } return s; } /** * Convert a vector of values into a string. * @param v * The vector of values. * @param separator * Inserted between each pair of values. * @return * String containing the vector's values separated * by the separator. */ AString AString::fromNumbers(const std::vector& v, const AString& separator) { AString s; for (uint64_t i = 0; i < v.size(); i++) { if (i > 0) { s += separator; } s += AString::number(v[i]); } return s; } /** * Convert a vector of values into a string. * @param v * The vector of values. * @param separator * Inserted between each pair of values. * @return * String containing the vector's values separated * by the separator. */ AString AString::fromNumbers(const std::vector& v, const AString& separator) { AString s; for (uint64_t i = 0; i < v.size(); i++) { if (i > 0) { s += separator; } s += AString::number(v[i]); } return s; } /** * Convert an array of values into a string. * @param array * The array of values. * @param numberOfElements * Number of elements in the array. * @param separator * Inserted between each pair of values. * @return * String containing the array values separated * by the separator. */ AString AString::fromNumbers(const float* array, const int64_t numberOfElements, const AString& separator) { AString s; for (int64_t i = 0; i < numberOfElements; i++) { if (i > 0) { s += separator; } s += AString::number(array[i]); } return s; } /** * Convert an array of values into a string. * @param array * The array of values. * @param numberOfElements * Number of elements in the array. * @param separator * Inserted between each pair of values. * @return * String containing the array values separated * by the separator. */ AString AString::fromNumbers(const int8_t* array, const int64_t numberOfElements, const AString& separator) { AString s; for (int64_t i = 0; i < numberOfElements; i++) { if (i > 0) { s += separator; } s += AString::number(array[i]); } return s; } /** * Convert an array of values into a string. * @param array * The array of values. * @param numberOfElements * Number of elements in the array. * @param separator * Inserted between each pair of values. * @return * String containing the array values separated * by the separator. */ AString AString::fromNumbers(const uint8_t* array, const int64_t numberOfElements, const AString& separator) { AString s; for (int64_t i = 0; i < numberOfElements; i++) { if (i > 0) { s += separator; } s += AString::number(array[i]); } return s; } /** * Convert an array of values into a string. * @param array * The array of values. * @param numberOfElements * Number of elements in the array. * @param separator * Inserted between each pair of values. * @return * String containing the array values separated * by the separator. */ AString AString::fromNumbers(const int32_t* array, const int64_t numberOfElements, const AString& separator) { AString s; for (int64_t i = 0; i < numberOfElements; i++) { if (i > 0) { s += separator; } s += AString::number(array[i]); } return s; } /** * Convert an array of values into a string. * @param array * The array of values. * @param numberOfElements * Number of elements in the array. * @param separator * Inserted between each pair of values. * @return * String containing the array values separated * by the separator. */ AString AString::fromNumbers(const int64_t* array, const int64_t numberOfElements, const AString& separator) { AString s; for (int64_t i = 0; i < numberOfElements; i++) { if (i > 0) { s += separator; } s += AString::number(array[i]); } return s; } /** * Convert an array of values into a string. * @param array * The array of values. * @param numberOfElements * Number of elements in the array. * @param separator * Inserted between each pair of values. * @return * String containing the array values separated * by the separator. */ AString AString::fromNumbers(const double* array, const int64_t numberOfElements, const AString& separator) { AString s; for (int64_t i = 0; i < numberOfElements; i++) { if (i > 0) { s += separator; } s += AString::number(array[i]); } return s; } /** * Convert the contents of given string to floats. Each * piece of text is converted to float. If a piece of * text does not convert to a float, it is ignored. This * should allow separation with characters other than * whitespace. * * @param s * String convert to floats. * @param numbersOut * Vector that will contain the given of this string * as numbers. */ void AString::toNumbers(const AString& s, std::vector& numbersOut) { AString copy = s; QTextStream stream(©); AString numberString; bool valid = false; while (stream.atEnd() == false) { stream >> numberString; const float floatValue = numberString.toFloat(&valid); if (valid) { numbersOut.push_back(floatValue); } } } /** * Convert the contents of given string to ints. Each * piece of text is converted to int. If a piece of * text does not convert to an int, it is ignored. This * should allow separation with characters other than * whitespace. * * @param s * String convert to ints. * @param numbersOut * Vector that will contain the given of this string * as numbers. */ void AString::toNumbers(const AString& s, std::vector& numbersOut) { AString copy = s; QTextStream stream(©); AString numberString; bool valid = false; while (stream.atEnd() == false) { stream >> numberString; const int32_t intValue = numberString.toInt(&valid); if (valid) { numbersOut.push_back(intValue); } } } /** * Convert the string to a boolean value. * These case insensitive values are considered true: * true, t, 1, 1.0. All others are considered false. * * @return * Boolean value interpreted from contents of * this string. */ bool AString::toBool() const { const AString s = this->toLower(); if ((s == "true") || (s == "t") || (s == "1") || (s == "1.0")) { return true; } return false; } /** * Convert the boolean value to a string. * @param b * The boolean value. * @return "true" if true, else "false". */ AString AString::fromBool(const bool b) { if (b) { return "true"; } return "false"; } /** * Convert any URLs in this string to * HTML hyperlinks. * "http://www.wustl.edu" becomes "http://wwww.wustl.edu" * @param sin * String that may contain URLs. * @return * Input string with any URLs replace with hyperlinks. */ AString AString::convertURLsToHyperlinks() const { std::vector url; std::vector urlStart; const AString& sin = *this; if (sin.indexOf("http://") == -1) { return sin; } else { // // Create a modifiable copy // AString s(sin); // // loop since there may be more than one URL // bool done = false; int startPos = 0; while(done == false) { // // Find the beginning of the URL // const int httpStart = s.indexOf("http://", startPos); // // Was the start of a URL found // if (httpStart == -1) { done = true; } else { // // Find the end of the URL // int httpEnd = s.indexOfAnyChar(" \t\n\r", httpStart + 1); // // May not find end since end of string // int httpLength; if (httpEnd == -1) { httpLength = s.length() - httpStart; } else { httpLength = httpEnd - httpStart; } // // Get the http URL // const AString httpString = s.mid(httpStart, httpLength); url.push_back(httpString); urlStart.push_back(httpStart); // // Prepare for next search // startPos = httpStart; //if (startPos > 0) { startPos = startPos + 1; //} } } if (url.empty() == false) { const int startNum = static_cast(url.size()) - 1; for (int i = startNum; i >= 0; i--) { const int len = url[i].length(); // // Create the trailing part of the hyperlink and insert it // AString trailingHyperLink("\">"); trailingHyperLink.append(url[i]); trailingHyperLink.append(""); s.insert(urlStart[i] + len, trailingHyperLink); // // Insert the beginning of the hyperlink // s.insert(urlStart[i], " " and "" * * Replace some characters with their HTML escaped characters */ AString AString::convertToHtmlPage() const { return convertToHtmlPageWithCssFontHeight(-1); } /** * Convert the text string to an HTML page using the given font size * by enclosing text between: * "" and "" * * Note: This uses the font tag's size attribute which is not supported * by HTML5. * * Also replaces some characters with their HTML escaped characters * * @param fontSize * Size of the font. */ AString AString::convertToHtmlPageWithFontSize(const int fontSize) const { /* * If already HTML (assumes "html" is the first six characters), * no need to convert. */ if (this->startsWith("", Qt::CaseInsensitive)) { return *this; } AString htmlString(""); htmlString.append(""); htmlString.append(this->replaceHtmlSpecialCharactersWithEscapeCharacters()); htmlString.append(""); htmlString.append(""); return htmlString; } /** * Convert the text string to an HTML page using the given font height * by enclosing text between: * "

" * * Note: HTML produced by this method and displayed in a QTextBrowser had * some problems with cutting and pasting on some Macs not working. * * Also replaces some characters with their HTML escaped characters * * @param fontHeight * Height of the font (if negative no font height is applied). */ AString AString::convertToHtmlPageWithCssFontHeight(const int fontHeight) const { /* * If already HTML (assumes "html" is the first six characters), * no need to convert. */ if (this->startsWith("", Qt::CaseInsensitive)) { return *this; } AString htmlString(""); if (fontHeight > 0) { htmlString.append("

"); } htmlString.append(this->replaceHtmlSpecialCharactersWithEscapeCharacters()); if (fontHeight > 0) { htmlString.append("

"); } htmlString.append(""); return htmlString; } /** * @return A copy of this string with any HTML special characters replaced * by their escape sequences. */ AString AString::replaceHtmlSpecialCharactersWithEscapeCharacters() const { AString htmlString; const int64_t length = this->count(); for (int64_t i = 0; i < length; i++) { const QChar ch = this->at(i); switch (ch.toAscii()) { case '&': htmlString.append("&"); break; case '<': htmlString.append("<"); break; case '>': htmlString.append(">"); break; case '\'': htmlString.append("'"); break; case '\"': htmlString.append("""); break; case ' ': htmlString.append(" "); break; case '\n': htmlString.append("
"); break; default: htmlString.append(ch); break; } } return htmlString; } /** * Returns the index position of any character in * 'str' in this string. * @param str Characters that are searched * for in this string. * @param from String position (default is first character). */ int32_t AString::indexOfAnyChar(const AString& str, int from) const { const AString& s = *this; const int len = s.length(); if (from < 0)//use the same "from" logic as Qt { from += len;//-2 starts at second to last character if (from < 0) from = 0;//can't start before the beginning } const int len2 = str.length(); for (int i = from; i < len; i++) { for (int j = 0; j < len2; j++) { if (s[i] == str[j]) { return i; } } } return -1; } /** * @return The index position of the first character that is NOT * the character 'ch'. Returns -1 if all characters in the * string are 'ch'. */ int32_t AString::indexNotOf(const QChar& ch) const { const int32_t len = length(); for (int32_t i = 0; i < len; i++) { if (at(i) != ch) { return i; } } return -1; } /** * Return a 'C' char array containing the value * of the string. This method is necessary since * on an instance of Ubuntu Linux, an invalid ASCII * character is found on the end of data returned * by toLocal8Bit().constData();. This method * will replace any non-ascii characters with "_". * All trailing non ASCII characters will be removed. * * @return * Char array of ASCII characters with a string * terminator '\0' at the end. Caller MUST * free memory by running delete[] on the * returned array. */ char* AString::toCharArray() const { bool haveNonAsciiCharacters = false; /* * Convert to a byte array */ QByteArray byteArray = this->toLocal8Bit(); const int32_t numBytes = byteArray.length(); if (numBytes > 0) { char* charOut = new char[numBytes + 1];//are there any byteArrays that don't already come with a line terminator? int32_t lastAsciiChar = -1; for (int32_t i = 0; i < numBytes; i++) { char c = byteArray.at(i); if ((c >= 32) && (c <= 126)) { charOut[i] = c; lastAsciiChar = i; } else if((c == 0 || c == 10) && i == (numBytes-1)) { charOut[i] = c; } else { charOut[i] = '_'; haveNonAsciiCharacters = true; } } charOut[lastAsciiChar + 1] = '\0'; if (haveNonAsciiCharacters) { CaretLogWarning("Non-ASCII characters were removed, result is \"" + AString(charOut) + "\""); } return charOut; } char* s = new char[1]; s[0] = 0; return s; } /** * If this string is not empty append a newline. * Next, append the given string. */ void AString::appendWithNewLine(const AString& str) { if (isEmpty() == false) { append("\n"); } append(str); } /** * Count the number of matching characters with the other string * starting at the end of the strings. * * Example: this="someText" rhs="moreText" => result=4 * @rhs * The other string. * @return * Number of character that match from the end of this and rhs. */ int64_t AString::countMatchingCharactersFromEnd(const AString& rhs) const { int64_t matchCount = 0; const int64_t myLength = length(); const int64_t rhsLength = rhs.length(); const int64_t minLength = std::min(myLength, rhsLength); for (int32_t offset = 1; offset <= minLength; offset++) { if (at(myLength - offset) == rhs.at(rhsLength - offset)) { matchCount++; } else { break; } } return matchCount; } workbench-1.1.1/src/Common/AString.h000066400000000000000000000142321255417355300172470ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #ifndef ASTRING_H #define ASTRING_H #include #include #include namespace caret { class AString : public QString { public: AString() : QString() {} AString(const QChar *unicode, int size) : QString(unicode, size) {} explicit AString(const QChar *unicode) : QString(unicode) {} // Qt5: merge with the above AString(QChar c) : QString(c) {} AString(int size, QChar c) : QString(size, c) {} AString(const QLatin1String &latin1) : QString(latin1) {} AString(const AString &string) : QString(string) {} AString(const QString &string) : QString(string) {} AString(const char *ch) : QString(ch){} AString(const QByteArray &a) : QString(a) {} AString(const Null &t) : QString(t) {} AString &operator=(const Null &t) { QString::operator=(t); return *this; } //AString(int size, Qt::Initialization) : QString(size,Qt::Initialization) {} //using QString::operator=; AString &operator=(QChar c) { QString::operator=(c); return *this;} AString &operator=(const QString &string) { QString::operator=(string); return *this;} AString &operator=(const QLatin1String &latin1) { QString::operator=(latin1); return *this;} AString &operator=(const char *ch) { QString::operator=(ch); return *this;} AString &operator=(const QByteArray &a) { QString::operator=(a); return *this;} AString &operator=(char c) { QString::operator=(c); return *this;} //std::string compatibility operator std::string () {return this->toStdString(); } //char * compatibility operator const char* () {return this->toAscii(); } //double compatiblity operator double () {return this->toDouble(); } //float compatiblity operator float () { return this->toFloat(); } //int compatiblity operator int () { return this->toInt(); } //long compatiblity operator long () { return this->toLong(); } //long long compatiblity operator long long () { return this->toLongLong(); } //unsigned int compatiblity operator unsigned int () { return this->toUInt(); } //unsigned long compatiblity operator unsigned long () { return this->toULong(); } //unsigned long long compatiblity operator unsigned long long () { return this->toULongLong(); } /// convert to a const char* (the operator() does not work in C++ library I/O functions) //const char* c_str() const { return qPrintable(*this); } char* toCharArray() const; AString convertURLsToHyperlinks() const; AString convertToHtmlPage() const; AString convertToHtmlPageWithFontSize(const int fontSize) const; AString convertToHtmlPageWithCssFontHeight(const int fontHeight) const; int32_t indexOfAnyChar(const AString& str, int from = 0) const; int32_t indexNotOf(const QChar& ch) const; void appendWithNewLine(const AString& str); int64_t countMatchingCharactersFromEnd(const AString& rhs) const; static void toNumbers(const AString& s, std::vector& numbersOut); static void toNumbers(const AString& s, std::vector& numbersOut); bool toBool() const; //I may move these outside the class since they don't require access to the class's internals static AString fromNumbers(const std::vector& v, const AString& separator); static AString fromNumbers(const std::vector& v, const AString& separator); static AString fromNumbers(const std::vector& v, const AString& separator); static AString fromNumbers(const std::vector& v, const AString& separator); static AString fromNumbers(const std::vector& v, const AString& separator); static AString fromNumbers(const std::vector& v, const AString& separator); static AString fromNumbers(const std::vector& v, const AString& separator); static AString fromNumbers(const std::vector& v, const AString& separator); static AString fromNumbers(const float* array, const int64_t numberOfElements, const AString& separator); static AString fromNumbers(const uint8_t* array,const int64_t numberOfElements, const AString& separator); static AString fromNumbers(const int8_t* array,const int64_t numberOfElements, const AString& separator); static AString fromNumbers(const int32_t* array,const int64_t numberOfElements, const AString& separator); static AString fromNumbers(const int64_t* array,const int64_t numberOfElements, const AString& separator); static AString fromNumbers(const double* array, const int64_t numberOfElements, const AString& separator); static AString fromBool(const bool b); AString replaceHtmlSpecialCharactersWithEscapeCharacters() const; }; } #include #include std::ostream& operator << (std::ostream &lhs, const caret::AString &rhs); #endif // ASTRING_H workbench-1.1.1/src/Common/AStringNaturalComparison.cxx000066400000000000000000000153621255417355300232110ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __A_STRING_NATURAL_COMPARISON_DECLARE__ #include "AStringNaturalComparison.h" #undef __A_STRING_NATURAL_COMPARISON_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::AStringNaturalComparison * \brief Class for performing a "natural comparison" of strings. * \ingroup Common * * This class is designed for use as a function object with the * Standard Template Library. However, it also contains a static * method for naturally comparing" two strings. * * Normal string comparison just compares the ASCII values of characters. * However, when the string contains numeric sequences, the strings are * sorted as expected. This class will perform string comparison where * any sequence of numbers is treated as a 'single character'. * * When the two 'characters' being compared are both non-numeric, they are * compared by their ASCII codes. When the two 'characters' being compared * are both numbers, a numeric comparison is performed. When one 'character' * is numeric and the other 'character' is non-numeric, the number is * considered "less than". * * Sequence produced by normal string comparison (note the position of 32abc): * 1xyz, 32abc, 4ab * * Sequence produced by natural string comparison (note the position of 32abc): * 1xyz, 4ab, 32abc */ /** * Constructor. */ AStringNaturalComparison::AStringNaturalComparison() { } /** * Destructor. */ AStringNaturalComparison::~AStringNaturalComparison() { } /** * Function object so that this class can be used as for * comparison in Standard Template Library containers. * * Performs a NATURAL COMPARISON where a contiguous sequence * of digits is treated as a single character so that text * string with numbers in them are properly sorted. * * @param s1 * First string for comparison. * @param s2 * Second string for comparison. * @return * Negative value if (s1 < s2), Positive if (s1 > s2), and * Zero if (s1 == s2). */ bool AStringNaturalComparison::operator() (const AString& s1, const AString& s2) const { const int32_t result = AStringNaturalComparison::compare(s1, s2); // std::cout << "Compare (" // << qPrintable(s1) // << ", " // << qPrintable(s2) // << "): " // << AString::fromBool(result) // << std::endl; if (result < 0) { return true; } return false; } /** * Static method for natural comparison of two strings. * * Performs a NATURAL COMPARISON where a contiguous sequence * of digits is treated as a single character so that text * string with numbers in them are properly sorted. * * @param string1 * First string for comparison. * @param string2 * Second string for comparison. * @return * Negative value if (string1 < string2), Positive if (string1 > string2), and * Zero if (string1 == string2). */ int32_t AStringNaturalComparison::compare(const AString& string1, const AString& string2) { const StringParser s1(string1); const StringParser s2(string2); bool s1IsNumber = false; bool s2IsNumber = false; /* * Loop through the 'characters' until corresponding * 'characters' do not match. * * Note that a consecutive sequence of digits is * considered a single 'character'. */ while (s1.hasMore() && s2.hasMore()) { const int64_t ch1 = s1.nextChar(s1IsNumber); const int64_t ch2 = s2.nextChar(s2IsNumber); CaretAssert(ch1 >= 0); CaretAssert(ch2 >= 0); if (s1IsNumber && s2IsNumber) { /* * Both 'character's are numbers */ if (ch1 < ch2) { return -1; } else if (ch1 > ch2) { return 1; } } else if (s1IsNumber) { return -1; } else if (s2IsNumber) { return 1; } else { /* * Both 'characters' are NOT numbers */ if (ch1 < ch2) { return -1; } else if (ch1 > ch2) { return 1; } } } /* * The shorter string is considered "less than" */ if (s1.hasMore()) { return 1; } else if (s2.hasMore()) { return -1; } /* * Strings must be identical. */ return 0; } /* ===================================================================== */ /** * \class caret::AStringNaturalComparison::StringParser * \brief Class for "String parsing" that treats any consecutive sequence * of numbers as a single character. * \ingroup Common */ /** * Constructor. * * @param s * String that will be parsed. */ AStringNaturalComparison::StringParser::StringParser(const AString& s) : m_s(s), m_pos(0), m_len(s.length()) { } /** * Returns the "next character" in the string. Any consecutive sequence of * digits is considered a single character. * * @param isNumberOut * If the value returned is a number, this parameter will be true, * else false. * @return * The unicode value for the next character or numeric value os a * sequence of digits. */ int64_t AStringNaturalComparison::StringParser::nextChar(bool& isNumberOut) const { isNumberOut = false; if (m_pos >= m_len) { return -1; } const QChar ch = m_s[m_pos]; ++m_pos; if (ch.isDigit()) { int64_t numericValue = ch.digitValue(); while (m_pos < m_len) { const QChar nextChar = m_s[m_pos]; if (nextChar.isDigit()) { ++m_pos; numericValue = (numericValue * 10) + nextChar.digitValue(); } else { break; } } isNumberOut = true; return numericValue; } else { return ch.unicode(); } return -1; } workbench-1.1.1/src/Common/AStringNaturalComparison.h000066400000000000000000000042671255417355300226400ustar00rootroot00000000000000#ifndef __A_STRING_NATURAL_COMPARISON_H__ #define __A_STRING_NATURAL_COMPARISON_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AString.h" namespace caret { class AStringNaturalComparison { public: AStringNaturalComparison(); AStringNaturalComparison(const AStringNaturalComparison&) { } virtual ~AStringNaturalComparison(); bool operator() (const AString& s1, const AString& s2) const; static int32_t compare(const AString& string1, const AString& string2); // ADD_NEW_METHODS_HERE private: AStringNaturalComparison& operator=(const AStringNaturalComparison&); // ADD_NEW_MEMBERS_HERE class StringParser { public: StringParser(const AString& s); int64_t nextChar(bool& isNumberOut) const; inline bool hasMore() const { return (m_pos < m_len); } private: const AString& m_s; mutable int32_t m_pos; int32_t m_len; }; }; #ifdef __A_STRING_NATURAL_COMPARISON_DECLARE__ // #endif // __A_STRING_NATURAL_COMPARISON_DECLARE__ } // namespace #endif //__A_STRING_NATURAL_COMPARISON_H__ workbench-1.1.1/src/Common/ApplicationInformation.cxx.in000066400000000000000000000120641255417355300233320ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __APPLICATION_INFORMATION_DECLARE__ #include "ApplicationInformation.h" #undef __APPLICATION_INFORMATION_DECLARE__ #include "FileInformation.h" using namespace caret; /** * \class caret::ApplicationInformation * \brief Provides application information. * * Provides application information (name, version, etc). */ /** * Constructor. */ ApplicationInformation::ApplicationInformation() : CaretObject() { this->name = "Connectome Workbench"; this->version = "1.1.1"; this->commit = "Commit: @COMMIT@"; this->commitDate = "Commit Date: @COMMIT_DATE@"; #ifdef NDEBUG this->compiledWithDebugOn = "Compiled Debug: NO"; #else this->compiledWithDebugOn = "Compiled Debug: YES"; #endif // NDEBUG this->operatingSystemName = "Operating System: Unknown"; #ifdef CARET_OS_LINUX this->operatingSystemName = "Operating System: Linux"; #endif // CARET_OS_MACOSX #ifdef CARET_OS_MACOSX this->operatingSystemName = "Operating System: Apple OSX"; #endif // CARET_OS_MACOSX #ifdef CARET_OS_WINDOWS this->operatingSystemName = "Operating System: Windows"; #endif // CARET_OS_MACOSX } /** * Destructor. */ ApplicationInformation::~ApplicationInformation() { } /** * @return Name of the application. */ AString ApplicationInformation::getName() const { return this->name; } /** * @return Version of application. */ AString ApplicationInformation::getVersion() const { return this->version; } /** * @return Commit info of application. */ AString ApplicationInformation::getCommit() const { return this->commit; } /** * @return Text indicating if Workbench was compiled with Debug on. */ AString ApplicationInformation::getCompiledWithDebugStatus() const { return this->compiledWithDebugOn; } /** * Get all information. * @param informationValues * Output information. */ void ApplicationInformation::getAllInformation(std::vector& informationValues) const { informationValues.clear(); informationValues.push_back(this->name); informationValues.push_back("Type: " + ApplicationTypeEnum::toGuiName(s_applicationType)); informationValues.push_back("Version: " + this->version); informationValues.push_back("Qt Compiled Version: " + QString(QT_VERSION_STR)); informationValues.push_back("Qt Runtime Version: " + QString(qVersion())); informationValues.push_back(commit); informationValues.push_back(commitDate); #if defined COMPILER_NAME FileInformation fileInfo(COMPILER_NAME); informationValues.push_back(QString("Compiler: ") + fileInfo.getFileNameFollowedByPathNameForGUI()); #endif #if defined COMPILER_VERSION informationValues.push_back(QString("Compiler Version: ") + QString(COMPILER_VERSION)); #endif informationValues.push_back(this->compiledWithDebugOn); informationValues.push_back(this->operatingSystemName); } /** * Get all of the application information in a string with each line * separated by the given 'separator'. * * @param separator * The separator is placed between each line of information. A newline * ("\n") is commonly used for the separator. */ AString ApplicationInformation::getAllInformationInString(const AString& separator) const { std::vector informationValues; getAllInformation(informationValues); AString infoOut; for (std::vector::iterator iter = informationValues.begin(); iter != informationValues.end(); iter++) { infoOut += *iter; infoOut += separator; } return infoOut; } AString ApplicationInformation::getSummaryInformationInString(const AString& separator) const { AString infoOut = "Version: " + version + separator; infoOut += commitDate + separator; infoOut += operatingSystemName + separator; return infoOut; } /** * @return The type of application. */ ApplicationTypeEnum::Enum ApplicationInformation::getApplicationType() { return s_applicationType; } /** * Set the application type. * * @param applicationType * New value for application type. */ void ApplicationInformation::setApplicationType(const ApplicationTypeEnum::Enum applicationType) { s_applicationType = applicationType; } workbench-1.1.1/src/Common/ApplicationInformation.h000066400000000000000000000047201255417355300223520ustar00rootroot00000000000000#ifndef __APPLICATION_INFORMATION__H_ #define __APPLICATION_INFORMATION__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "ApplicationTypeEnum.h" #include "CaretObject.h" namespace caret { class ApplicationInformation : public CaretObject { public: ApplicationInformation(); virtual ~ApplicationInformation(); AString getName() const; AString getVersion() const; AString getCommit() const; void getAllInformation(std::vector& informationValues) const; AString getAllInformationInString(const AString& separator) const; AString getSummaryInformationInString(const AString& separator) const; AString getCompiledWithDebugStatus() const; static ApplicationTypeEnum::Enum getApplicationType(); static void setApplicationType(const ApplicationTypeEnum::Enum applicationType); private: ApplicationInformation(const ApplicationInformation&); ApplicationInformation& operator=(const ApplicationInformation&); AString name; AString version; AString commit; AString commitDate; AString compiledWithDebugOn; AString operatingSystemName; static ApplicationTypeEnum::Enum s_applicationType; }; #ifdef __APPLICATION_INFORMATION_DECLARE__ ApplicationTypeEnum::Enum ApplicationInformation::s_applicationType = ApplicationTypeEnum::APPLICATION_TYPE_INVALID; #endif // __APPLICATION_INFORMATION_DECLARE__ } // namespace #endif //__APPLICATION_INFORMATION__H_ workbench-1.1.1/src/Common/ApplicationTypeEnum.cxx000066400000000000000000000253031255417355300222060ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __APPLICATION_TYPE_ENUM_DECLARE__ #include "ApplicationTypeEnum.h" #undef __APPLICATION_TYPE_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::ApplicationTypeEnum * \brief * * * * Using this enumerated type in the GUI with an EnumComboBoxTemplate * * Header File (.h) * Forward declare the data type: * class EnumComboBoxTemplate; * * Declare the member: * EnumComboBoxTemplate* m_applicationTypeEnumComboBox; * * Declare a slot that is called when user changes selection * private slots: * void applicationTypeEnumComboBoxItemActivated(); * * Implementation File (.cxx) * Include the header files * #include "EnumComboBoxTemplate.h" * #include "ApplicationTypeEnum.h" * * Instatiate: * m_applicationTypeEnumComboBox = new EnumComboBoxTemplate(this); * m_applicationTypeEnumComboBox->setup(); * * Get notified when the user changes the selection: * QObject::connect(m_applicationTypeEnumComboBox, SIGNAL(itemActivated()), * this, SLOT(applicationTypeEnumComboBoxItemActivated())); * * Update the selection: * m_applicationTypeEnumComboBox->setSelectedItem(NEW_VALUE); * * Read the selection: * const ApplicationTypeEnum::Enum VARIABLE = m_applicationTypeEnumComboBox->getSelectedItem(); * */ /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ ApplicationTypeEnum::ApplicationTypeEnum(const Enum enumValue, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCodeCounter++; this->name = name; this->guiName = guiName; } /** * Destructor. */ ApplicationTypeEnum::~ApplicationTypeEnum() { } /** * Initialize the enumerated metadata. */ void ApplicationTypeEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(ApplicationTypeEnum(APPLICATION_TYPE_INVALID, "APPLICATION_TYPE_INVALID", "Invalid or Not Set")); enumData.push_back(ApplicationTypeEnum(APPLICATION_TYPE_COMMAND_LINE, "APPLICATION_TYPE_COMMAND_LINE", "Command Line Application")); enumData.push_back(ApplicationTypeEnum(APPLICATION_TYPE_GRAPHICAL_USER_INTERFACE, "APPLICATION_TYPE_GRAPHICAL_USER_INTERFACE", "Graphical User Interface Application")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const ApplicationTypeEnum* ApplicationTypeEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const ApplicationTypeEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString ApplicationTypeEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const ApplicationTypeEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ ApplicationTypeEnum::Enum ApplicationTypeEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = ApplicationTypeEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ApplicationTypeEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type ApplicationTypeEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString ApplicationTypeEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const ApplicationTypeEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ ApplicationTypeEnum::Enum ApplicationTypeEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = ApplicationTypeEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ApplicationTypeEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type ApplicationTypeEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t ApplicationTypeEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const ApplicationTypeEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ ApplicationTypeEnum::Enum ApplicationTypeEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = ApplicationTypeEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ApplicationTypeEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type ApplicationTypeEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void ApplicationTypeEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void ApplicationTypeEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(ApplicationTypeEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void ApplicationTypeEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(ApplicationTypeEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } workbench-1.1.1/src/Common/ApplicationTypeEnum.h000066400000000000000000000063041255417355300216330ustar00rootroot00000000000000#ifndef __APPLICATION_TYPE_ENUM_H__ #define __APPLICATION_TYPE_ENUM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class ApplicationTypeEnum { public: /** * Enumerated values. */ enum Enum { /** Invalid */ APPLICATION_TYPE_INVALID, /** Command Line Application */ APPLICATION_TYPE_COMMAND_LINE, /** Graphical User Interface Application */ APPLICATION_TYPE_GRAPHICAL_USER_INTERFACE }; ~ApplicationTypeEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); private: ApplicationTypeEnum(const Enum enumValue, const AString& name, const AString& guiName); static const ApplicationTypeEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; #ifdef __APPLICATION_TYPE_ENUM_DECLARE__ std::vector ApplicationTypeEnum::enumData; bool ApplicationTypeEnum::initializedFlag = false; int32_t ApplicationTypeEnum::integerCodeCounter = 0; #endif // __APPLICATION_TYPE_ENUM_DECLARE__ } // namespace #endif //__APPLICATION_TYPE_ENUM_H__ workbench-1.1.1/src/Common/Base64.cxx000066400000000000000000000223521255417355300173010ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ /*========================================================================= Program: Visualization Toolkit Module: $RCSfile: Base64.cxx,v $ Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen All rights reserved. See Copyright.txt or http://www.kitware.com/Copyright.htm for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notice for more information. =========================================================================*/ #include "CaretAssert.h" #include "Base64.h" using namespace caret; //---------------------------------------------------------------------------- static const unsigned char Base64EncodeTable[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789+/"; //---------------------------------------------------------------------------- inline static unsigned char Base64EncodeChar(unsigned char c) { CaretAssert( c < 65 ); return Base64EncodeTable[c]; } Base64::Base64() {} Base64::~Base64() {} //---------------------------------------------------------------------------- void Base64::EncodeTriplet(unsigned char i0, unsigned char i1, unsigned char i2, unsigned char *o0, unsigned char *o1, unsigned char *o2, unsigned char *o3) { *o0 = Base64EncodeChar((i0 >> 2) & 0x3F); *o1 = Base64EncodeChar(((i0 << 4) & 0x30)|((i1 >> 4) & 0x0F)); *o2 = Base64EncodeChar(((i1 << 2) & 0x3C)|((i2 >> 6) & 0x03)); *o3 = Base64EncodeChar(i2 & 0x3F); } //---------------------------------------------------------------------------- void Base64::EncodePair(unsigned char i0, unsigned char i1, unsigned char *o0, unsigned char *o1, unsigned char *o2, unsigned char *o3) { *o0 = Base64EncodeChar((i0 >> 2) & 0x3F); *o1 = Base64EncodeChar(((i0 << 4) & 0x30)|((i1 >> 4) & 0x0F)); *o2 = Base64EncodeChar(((i1 << 2) & 0x3C)); *o3 = '='; } //---------------------------------------------------------------------------- void Base64::EncodeSingle(unsigned char i0, unsigned char *o0, unsigned char *o1, unsigned char *o2, unsigned char *o3) { *o0 = Base64EncodeChar((i0 >> 2) & 0x3F); *o1 = Base64EncodeChar(((i0 << 4) & 0x30)); *o2 = '='; *o3 = '='; } //---------------------------------------------------------------------------- uint64_t Base64::encode(const unsigned char *input, uint64_t length, unsigned char *output, int32_t mark_end) { const unsigned char *ptr = input; const unsigned char *end = input + length; unsigned char *optr = output; // Encode complete triplet while ((end - ptr) >= 3) { Base64::EncodeTriplet(ptr[0], ptr[1], ptr[2], &optr[0], &optr[1], &optr[2], &optr[3]); ptr += 3; optr += 4; } // Encodes a 2-byte ending into 3 bytes and 1 pad byte and writes. if (end - ptr == 2) { Base64::EncodePair(ptr[0], ptr[1], &optr[0], &optr[1], &optr[2], &optr[3]); optr += 4; } // Encodes a 1-byte ending into 2 bytes and 2 pad bytes else if (end - ptr == 1) { Base64::EncodeSingle(ptr[0], &optr[0], &optr[1], &optr[2], &optr[3]); optr += 4; } // Do we need to mark the end else if (mark_end) { optr[0] = optr[1] = optr[2] = optr[3] = '='; optr += 4; } return optr - output; } //---------------------------------------------------------------------------- static const unsigned char Base64DecodeTable[256] = { 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0x3E,0xFF,0xFF,0xFF,0x3F, 0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B, 0x3C,0x3D,0xFF,0xFF,0xFF,0x00,0xFF,0xFF, 0xFF,0x00,0x01,0x02,0x03,0x04,0x05,0x06, 0x07,0x08,0x09,0x0A,0x0B,0x0C,0x0D,0x0E, 0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16, 0x17,0x18,0x19,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x20, 0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28, 0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,0x30, 0x31,0x32,0x33,0xFF,0xFF,0xFF,0xFF,0xFF, //------------------------------------- 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF }; //---------------------------------------------------------------------------- inline static unsigned char Base64DecodeChar(unsigned char c) { return Base64DecodeTable[c]; } //---------------------------------------------------------------------------- int Base64::DecodeTriplet(unsigned char i0, unsigned char i1, unsigned char i2, unsigned char i3, unsigned char *o0, unsigned char *o1, unsigned char *o2) { unsigned char d0, d1, d2, d3; d0 = Base64DecodeChar(i0); d1 = Base64DecodeChar(i1); d2 = Base64DecodeChar(i2); d3 = Base64DecodeChar(i3); // Make sure all characters were valid if (d0 == 0xFF || d1 == 0xFF || d2 == 0xFF || d3 == 0xFF) { return 0; } // Decode the 3 bytes *o0 = ((d0 << 2) & 0xFC) | ((d1 >> 4) & 0x03); *o1 = ((d1 << 4) & 0xF0) | ((d2 >> 2) & 0x0F); *o2 = ((d2 << 6) & 0xC0) | ((d3 >> 0) & 0x3F); // Return the number of bytes actually decoded if (i2 == '=') { return 1; } if (i3 == '=') { return 2; } return 3; } //---------------------------------------------------------------------------- uint64_t Base64::decode(const unsigned char *input, uint64_t length, unsigned char *output, uint64_t max_input_length) { const unsigned char *ptr = input; unsigned char *optr = output; // Decode complete triplet if (max_input_length) { const unsigned char *end = input + max_input_length; while (ptr < end) { int len = Base64::DecodeTriplet(ptr[0], ptr[1], ptr[2], ptr[3], &optr[0], &optr[1], &optr[2]); optr += len; if(len < 3) { return optr - output; } ptr += 4; } } else { unsigned char *oend = output + length; while ((oend - optr) >= 3) { int len = Base64::DecodeTriplet(ptr[0], ptr[1], ptr[2], ptr[3], &optr[0], &optr[1], &optr[2]); optr += len; if(len < 3) { return optr - output; } ptr += 4; } // Decode the last triplet unsigned char temp; if (oend - optr == 2) { int len = Base64::DecodeTriplet(ptr[0], ptr[1], ptr[2], ptr[3], &optr[0], &optr[1], &temp); optr += (len > 2 ? 2 : len); } else if (oend - optr == 1) { unsigned char temp2; int len = Base64::DecodeTriplet(ptr[0], ptr[1], ptr[2], ptr[3], &optr[0], &temp, &temp2); optr += (len > 2 ? 2 : len); } } return optr - output; } workbench-1.1.1/src/Common/Base64.h000066400000000000000000000120461255417355300167250ustar00rootroot00000000000000#ifndef __BASE64_H__ #define __BASE64_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ /*========================================================================= Program: Visualization Toolkit Module: $RCSfile: Base64.h,v $ Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen All rights reserved. See Copyright.txt or http://www.kitware.com/Copyright.htm for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notice for more information. =========================================================================*/ // .NAME Base64 - base64 encode and decode utilities. // .SECTION Description // Base64 implements base64 encoding and decoding. #include #include "CaretObject.h" namespace caret { /** * This is copied directly from VTK's vtkBase64Utilities class. */ class Base64 : public CaretObject { private: Base64(); ~Base64(); public: // Description: // Encode 'length' bytes from the input buffer and store the // encoded stream into the output buffer. Return the length of // the encoded stream. Note that the output buffer must be allocated // by the caller (length * 1.5 should be a safe estimate). // If 'mark_end' is true than an extra set of 4 bytes is added // to the end of the stream if the input is a multiple of 3 bytes. // These bytes are invalid chars and therefore they will stop the decoder // thus enabling the caller to decode a stream without actually knowing // how much data to expect (if the input is not a multiple of 3 bytes then // the extra padding needed to complete the encode 4 bytes will stop the // decoding anyway). static uint64_t encode(const unsigned char *input, uint64_t length, unsigned char *output, int32_t mark_end = 0); // Description: // Decode bytes from the input buffer and store the decoded stream // into the output buffer until 'length' bytes have been decoded. // Return the real length of the decoded stream (which should be equal to // 'length'). Note that the output buffer must be allocated by the caller. // If 'max_input_length' is not null, then it specifies the number of // encoded bytes that should be at most read from the input buffer. In // that case the 'length' parameter is ignored. This enables the caller // to decode a stream without actually knowing how much decoded data to // expect (of course, the buffer must be large enough). static uint64_t decode(const unsigned char *input, uint64_t length, unsigned char *output, uint64_t max_input_length = 0); private: // Description: // Decode 4 bytes into 3 bytes. static int DecodeTriplet(unsigned char i0, unsigned char i1, unsigned char i2, unsigned char i3, unsigned char *o0, unsigned char *o1, unsigned char *o2); // Description: // Encode 3 bytes into 4 bytes static void EncodeTriplet(unsigned char i0, unsigned char i1, unsigned char i2, unsigned char *o0, unsigned char *o1, unsigned char *o2, unsigned char *o3); // Description: // Encode 2 bytes into 4 bytes static void EncodePair(unsigned char i0, unsigned char i1, unsigned char *o0, unsigned char *o1, unsigned char *o2, unsigned char *o3); // Description: // Encode 1 byte into 4 bytes static void EncodeSingle(unsigned char i0, unsigned char *o0, unsigned char *o1, unsigned char *o2, unsigned char *o3); }; } // namespace #endif // __BASE64_H__ workbench-1.1.1/src/Common/BoundingBox.cxx000066400000000000000000000305171255417355300204750ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include "BoundingBox.h" using namespace caret; /** * Constructor setup for update of an update method. * */ BoundingBox::BoundingBox() : CaretObject() { this->initializeMembersBoundingBox(); this->resetForUpdate(); } /** * Create a bounding box from a six-dimensional array containing * min-X, max-X, min-Y, max-Y, min-Z, max-Z. * * @param minMaxXYZ - array described above. * @throws IllegalArgumentException if the array does not contain six * elements or a min value is greater than a max value. * */ BoundingBox::BoundingBox( const float minMaxXYZ[]) : CaretObject() { this->initializeMembersBoundingBox(); for (int i = 0; i < 6; i++) { this->boundingBox[i] = minMaxXYZ[i]; } } /** * Copy constructor. * @param bb * BoundingBox that is copied. */ BoundingBox::BoundingBox(const BoundingBox& bb) : CaretObject(bb) { this->initializeMembersBoundingBox(); this->copyHelper(bb); } /** * Assignment operator. * @param bb * BoundingBox that replace this bounding box. */ BoundingBox& BoundingBox::operator=(const BoundingBox& bb) { if (this != &bb) { CaretObject::operator=(bb); this->copyHelper(bb); } return *this; } /** * Destructor */ BoundingBox::~BoundingBox() { } /** * Initialize data members. */ void BoundingBox::initializeMembersBoundingBox() { } /** * Helps with copy constructor and assignemnt operator. */ void BoundingBox::copyHelper(const BoundingBox& bo) { for (int i = 0; i < 6; i++) { this->boundingBox[i] = bo.boundingBox[i]; } } /** * Reset a new bounding box with the minimum and maximum values * all set to zero. */ void BoundingBox::resetZeros() { this->set(0.0, 0.0, 0.0, 0.0, 0.0, 0.0); } /** * Reset a bounding box with the minimum values initialized to * the minimum float value and the maximum values initialized to the * maximum float value. Use one of the setMax() or setMinY() methods * to update this bounding box. */ void BoundingBox::resetWithMaximumExtent() { const float f = std::numeric_limits::max(); this->set(-f, f, -f, f, -f, f); } /** * Reset a bounding box with the minimum values initialized to * the maximum float value and the maximum values initialized to the * minimum float value. Use the update(float[]) method to update * the bounding box. */ void BoundingBox::resetForUpdate() { const float f = std::numeric_limits::max(); this->set(f, -f, f, -f, f, -f); } /** * Set bounding box using the array of points. * @param points3D * Array of three dimensional points. * @param numPoints * Number of points. */ void BoundingBox::set(const float* points3D, const int64_t numPoints) { this->resetForUpdate(); for (int64_t i = 0; i < numPoints; i++) { this->update(&points3D[i*3]); } } /** * Set a new bounding box. * @param minX Minimum X-coordinate for bounding box. * @param maxX Maximum X-coordinate for bounding box. * @param minY Minimum Y-coordinate for bounding box. * @param maxY Maximum Y-coordinate for bounding box. * @param minZ Minimum Z-coordinate for bounding box. * @param maxZ Maximum Z-coordinate for bounding box. */ void BoundingBox::set( const float minX, const float maxX, const float minY, const float maxY, const float minZ, const float maxZ) { this->boundingBox[0] = minX; this->boundingBox[1] = maxX; this->boundingBox[2] = minY; this->boundingBox[3] = maxY; this->boundingBox[4] = minZ; this->boundingBox[5] = maxZ; } /** * Set a bounding box from a six-dimensional array containing * min-X, max-X, min-Y, max-Y, min-Z, max-Z. * * @param minMaxXYZ - array described above. */ void BoundingBox::set(const float minMaxXYZ[6]) { this->boundingBox[0] = minMaxXYZ[0]; this->boundingBox[1] = minMaxXYZ[1]; this->boundingBox[2] = minMaxXYZ[2]; this->boundingBox[3] = minMaxXYZ[3]; this->boundingBox[4] = minMaxXYZ[4]; this->boundingBox[5] = minMaxXYZ[5]; } /** * Update the bounding box with the XYZ value passed in. The bound box * must have been created with newInstanceForUpdate() or properly * initialized by the user. * * @param xyz - Three dimensional array containing XYZ. * @throws IllegalArgumentException if array does not have three elements. * */ void BoundingBox::update(const float xyz[3]) { if (xyz[0] < this->boundingBox[0]) this->boundingBox[0] = xyz[0]; if (xyz[0] > this->boundingBox[1]) this->boundingBox[1] = xyz[0]; if (xyz[1] < this->boundingBox[2]) this->boundingBox[2] = xyz[1]; if (xyz[1] > this->boundingBox[3]) this->boundingBox[3] = xyz[1]; if (xyz[2] < this->boundingBox[4]) this->boundingBox[4] = xyz[2]; if (xyz[2] > this->boundingBox[5]) this->boundingBox[5] = xyz[2]; } /** * Get the bounds in an array. * @return Array of six containing minX, maxX, minY, maxY, minZ, maxZ. * */ const float* BoundingBox::getBounds() const { return this->boundingBox; } /** * Get the bounds in an array. * @param Output array of six containing minX, maxX, minY, maxY, minZ, maxZ. * */ void BoundingBox::getBounds(float bounds[6]) const { bounds[0] = this->boundingBox[0]; bounds[1] = this->boundingBox[1]; bounds[2] = this->boundingBox[2]; bounds[3] = this->boundingBox[3]; bounds[4] = this->boundingBox[4]; bounds[5] = this->boundingBox[5]; } /** * Get the X-Coordinate difference. * @return X-Coordinate difference. * */ float BoundingBox::getDifferenceX() const { return (this->boundingBox[1] - this->boundingBox[0]); } /** * Get the Y-Coordinate difference. * @return Y-Coordinate difference. * */ float BoundingBox::getDifferenceY() const { return (this->boundingBox[3] - this->boundingBox[2]); } /** * Get the Z-Coordinate difference. * @return Z-Coordinate difference. * */ float BoundingBox::getDifferenceZ() const { return (this->boundingBox[5] - this->boundingBox[4]); } /** * Get the X minimum value. * @return Its value. * */ float BoundingBox::getMinX() const { return this->boundingBox[0]; } /** * Get the X maximum value. * @return Its value. * */ float BoundingBox::getMaxX() const { return this->boundingBox[1]; } /** * Get the Y minimum value. * @return Its value. * */ float BoundingBox::getMinY() const { return this->boundingBox[2]; } /** * Get the Y maximum value. * @return Its value. * */ float BoundingBox::getMaxY() const { return this->boundingBox[3]; } /** * Get the Z minimum value. * @return Its value. * */ float BoundingBox::getMinZ() const { return this->boundingBox[4]; } /** * Get the Z maximum value. * @return Its value. * */ float BoundingBox::getMaxZ() const { return this->boundingBox[5]; } /** * Get the minimum XYZ of the bounding box. * @return Minimum XYZ of the bounding box. * in an array that the caller MUST delete[]; */ float* BoundingBox::getMinXYZ() const { float* f = new float[3]; f[0] = this->boundingBox[0]; f[1] = this->boundingBox[2]; f[2] = this->boundingBox[4]; return f; } /** * Get the maximum XYZ of the bounding box. * @return Maximum XYZ of the bounding box * in an array that the caller MUST delete[]; */ float* BoundingBox::getMaxXYZ() const { float* f = new float[3]; f[0] = this->boundingBox[1]; f[1] = this->boundingBox[3]; f[2] = this->boundingBox[5]; return f; } /** * Set the minimum X value. * @param value - new value. * */ void BoundingBox::setMinX(const float value) { this->boundingBox[0] = value; } /** * Set the maximum X value. * @param value - new value. * */ void BoundingBox::setMaxX(const float value) { this->boundingBox[1] = value; } /** * Set the minimum Y value. * @param value - new value. * */ void BoundingBox::setMinY(const float value) { this->boundingBox[2] = value; } /** * Set the maximum Y value. * @param value - new value. * */ void BoundingBox::setMaxY(const float value) { this->boundingBox[3] = value; } /** * Set the minimum Z value. * @param value - new value. * */ void BoundingBox::setMinZ(const float value) { this->boundingBox[4] = value; } /** * Set the maximum Z value. * @param value - new value. * */ void BoundingBox::setMaxZ(const float value) { this->boundingBox[5] = value; } /** * @return X-Coordinate at center of the bounding box. */ float BoundingBox::getCenterX() const { const float centerX = (this->boundingBox[0] + this->boundingBox[1]) * 0.5; return centerX; } /** * @return Y-Coordinate at center of the bounding box. */ float BoundingBox::getCenterY() const { const float centerY = (this->boundingBox[2] + this->boundingBox[3]) * 0.5; return centerY; } /** * @return Z-Coordinate at center of the bounding box. */ float BoundingBox::getCenterZ() const { const float centerZ = (this->boundingBox[4] + this->boundingBox[5]) * 0.5; return centerZ; } /** * Get the center of the bounding box. * @param centerOut * Three dimensional array into which the center is loaded. */ void BoundingBox::getCenter(float centerOut[3]) const { centerOut[0] = (this->boundingBox[0] + this->boundingBox[1]) * 0.5; centerOut[1] = (this->boundingBox[2] + this->boundingBox[3]) * 0.5; centerOut[2] = (this->boundingBox[4] + this->boundingBox[5]) * 0.5; } /** * Is the coordinate within the bounding box? * @param xyz - The coordinate. * @return True if coordinate is within the bounding box, else false. * */ bool BoundingBox::isCoordinateWithinBoundingBox(const float xyz[]) const { if (xyz[0] < this->boundingBox[0]) return false; if (xyz[0] > this->boundingBox[1]) return false; if (xyz[1] < this->boundingBox[2]) return false; if (xyz[1] > this->boundingBox[3]) return false; if (xyz[2] < this->boundingBox[4]) return false; if (xyz[2] > this->boundingBox[5]) return false; return true; } /** * Limit the given coordinate to the bounding box. * * @param xyz * The coordinate. */ void BoundingBox::limitCoordinateToBoundingBox(float xyz[3]) const { if (xyz[0] < this->boundingBox[0]) xyz[0] = this->boundingBox[0]; if (xyz[0] > this->boundingBox[1]) xyz[0] = this->boundingBox[1]; if (xyz[1] < this->boundingBox[2]) xyz[1] = this->boundingBox[2]; if (xyz[1] > this->boundingBox[3]) xyz[1] = this->boundingBox[3]; if (xyz[2] < this->boundingBox[4]) xyz[2] = this->boundingBox[4]; if (xyz[2] > this->boundingBox[5]) xyz[2] = this->boundingBox[5]; } /** * Limit the given coordinate to the bounding box. * * @param xyz * The coordinate. */ void BoundingBox::limitCoordinateToBoundingBox(double xyz[3]) const { if (xyz[0] < this->boundingBox[0]) xyz[0] = this->boundingBox[0]; if (xyz[0] > this->boundingBox[1]) xyz[0] = this->boundingBox[1]; if (xyz[1] < this->boundingBox[2]) xyz[1] = this->boundingBox[2]; if (xyz[1] > this->boundingBox[3]) xyz[1] = this->boundingBox[3]; if (xyz[2] < this->boundingBox[4]) xyz[2] = this->boundingBox[4]; if (xyz[2] > this->boundingBox[5]) xyz[2] = this->boundingBox[5]; } /** * Get String representation of bounding box. * @return String containing bounding box. * */ AString BoundingBox::toString() const { std::stringstream str; str << "BoundingBox=[" << this->boundingBox[0] << "," << this->boundingBox[1] << "," << this->boundingBox[2] << "," << this->boundingBox[3] << "," << this->boundingBox[4] << "," << this->boundingBox[5] << "]"; AString s = AString::fromStdString(str.str()); return s; } workbench-1.1.1/src/Common/BoundingBox.h000066400000000000000000000056431255417355300201240ustar00rootroot00000000000000#ifndef __BOUNDINGBOX_H__ #define __BOUNDINGBOX_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include namespace caret { /** * A bounding box - minimum and maximum X, Y, and Z values for something. */ class BoundingBox : public CaretObject { public: BoundingBox(); BoundingBox(const float minMaxXYZ[]); BoundingBox(const BoundingBox& bb); BoundingBox& operator=(const BoundingBox& co); virtual ~BoundingBox(); public: void resetZeros(); void resetWithMaximumExtent(); void resetForUpdate(); void set(const float* points3D, const int64_t numPoints); void set(const float minX, const float maxX, const float minY, const float maxY, const float minZ, const float maxZ); void set(const float minMaxXYZ[6]); void update(const float xyz[]); const float* getBounds() const; void getBounds(float bounds[6]) const; float getDifferenceX() const; float getDifferenceY() const; float getDifferenceZ() const; float getMinX() const; float getMaxX() const; float getMinY() const; float getMaxY() const; float getMinZ() const; float getMaxZ() const; float* getMinXYZ() const; float* getMaxXYZ() const; void setMinX(const float value); void setMaxX(const float value); void setMinY(const float value); void setMaxY(const float value); void setMinZ(const float value); void setMaxZ(const float value); float getCenterX() const; float getCenterY() const; float getCenterZ() const; void getCenter(float centerOut[3]) const; bool isCoordinateWithinBoundingBox(const float xyz[]) const; void limitCoordinateToBoundingBox(float xyz[3]) const; void limitCoordinateToBoundingBox(double xyz[3]) const; AString toString() const; private: void initializeMembersBoundingBox(); void copyHelper(const BoundingBox& bo); private: float boundingBox[6]; }; } // namespace #endif // __BOUNDINGBOX_H__ workbench-1.1.1/src/Common/BrainConstants.cxx000066400000000000000000000016521255417355300212050ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __BRAIN_CONSTANTS_DECLARE__ #include "BrainConstants.h" #undef __BRAIN_CONSTANTS_DECLARE__ workbench-1.1.1/src/Common/BrainConstants.h000066400000000000000000000040311255417355300206240ustar00rootroot00000000000000#ifndef __BRAIN_CONSTANTS__H_ #define __BRAIN_CONSTANTS__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include namespace caret { /// Constants for use in the Brain class BrainConstants { public: enum { /// Maximum number of browser windows MAXIMUM_NUMBER_OF_BROWSER_WINDOWS = 10, /// Maximum number of browser tabs MAXIMUM_NUMBER_OF_BROWSER_TABS = 100, /// Maximum number of overlays MAXIMUM_NUMBER_OF_OVERLAYS = 50, /// Minimum number of overlays MINIMUM_NUMBER_OF_OVERLAYS = 3, /// Minimum number of volume surface outlines MINIMUM_NUMBER_OF_VOLUME_SURFACE_OUTLINES = 4, /// Maximum number of volume surface outlines MAXIMUM_NUMBER_OF_VOLUME_SURFACE_OUTLINES = 25 }; private: BrainConstants(); ~BrainConstants(); BrainConstants(const BrainConstants&); BrainConstants& operator=(const BrainConstants&); }; #ifdef __BRAIN_CONSTANTS_DECLARE__ #endif // __BRAIN_CONSTANTS_DECLARE__ } // namespace #endif //__BRAIN_CONSTANTS__H_ workbench-1.1.1/src/Common/ByteOrderEnum.cxx000066400000000000000000000107521255417355300210020ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __BYTE_ORDER_DECLARE__ #include "ByteOrderEnum.h" #undef __BYTE_ORDER_DECLARE__ using namespace caret; /** * Constructor. * * @param e * An enumerated value. * @param name * Name of enumberated value. */ ByteOrderEnum::ByteOrderEnum( const Enum e, const AString& name) { this->e = e; this->name = name; } /** * Destructor. */ ByteOrderEnum::~ByteOrderEnum() { } /** * Get the system byte order. * @return * Byte order of the system. */ ByteOrderEnum::Enum ByteOrderEnum::getSystemEndian() { ByteOrderEnum::initialize(); return ByteOrderEnum::systemEndian; } /** * Is the system byte order little endian? * @return * true if system is little endian byte order. */ bool ByteOrderEnum::isSystemLittleEndian() { ByteOrderEnum::initialize(); return (ByteOrderEnum::systemEndian == ENDIAN_LITTLE); } /** * Is the system byte order big endian? * @return * true if system is big endian byte order. */ bool ByteOrderEnum::isSystemBigEndian() { ByteOrderEnum::initialize(); return (ByteOrderEnum::systemEndian == ENDIAN_BIG); } void ByteOrderEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(ByteOrderEnum(ENDIAN_BIG,"ENDIAN_BIG")); enumData.push_back(ByteOrderEnum(ENDIAN_LITTLE,"ENDIAN_LITTLE")); uint32_t intVal = 0x00000001; unsigned char* c = (unsigned char*)&intVal; ByteOrderEnum::systemEndian = ByteOrderEnum::ENDIAN_BIG; if (*c == 0x01) systemEndian = ByteOrderEnum::ENDIAN_LITTLE; } /** * Get the enum value for this enumerated item. * @return the value for this enumerated item. */ ByteOrderEnum::Enum ByteOrderEnum::getEnum() const { return this->e; } /** * Get the enum name for this enumerated item. * @return the name for this enumerated item. */ AString ByteOrderEnum::getName() const { return this->name; } /** * Find the data for and enumerated value. * @param e * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const ByteOrderEnum* ByteOrderEnum::findData(const Enum e) { initialize(); int64_t num = enumData.size(); for (int64_t i = 0; i < num; i++) { const ByteOrderEnum* d = &enumData[i]; if (d->e == e) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param e * Enumerated value. * @return * String representing enumerated value. */ AString ByteOrderEnum::toName(Enum e) { initialize(); AString s; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ByteOrderEnum& d = *iter; if (d.e == e) { s = d.name; break; } } return s; } /** * Get an enumerated value corresponding to its name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ ByteOrderEnum::Enum ByteOrderEnum::fromName(const AString& s, bool* isValidOut) { initialize(); bool validFlag = false; Enum e = ENDIAN_LITTLE; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ByteOrderEnum& d = *iter; if (d.name == s) { e = d.e; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } return e; } workbench-1.1.1/src/Common/ByteOrderEnum.h000066400000000000000000000040051255417355300204210ustar00rootroot00000000000000#ifndef __BYTE_ORDER_H__ #define __BYTE_ORDER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include namespace caret { /** * Byte order. */ class ByteOrderEnum { public: /** ENDIAN Types */ enum Enum { /** */ ENDIAN_BIG, /** */ ENDIAN_LITTLE }; ~ByteOrderEnum(); static ByteOrderEnum::Enum getSystemEndian(); static bool isSystemLittleEndian(); static bool isSystemBigEndian(); static AString toName(Enum e); static Enum fromName(const AString& s, bool* isValidOut); private: ByteOrderEnum(const Enum e, const AString& name); Enum getEnum() const; AString getName() const; static std::vector enumData; static void initialize(); static bool initializedFlag; static Enum systemEndian; Enum e; AString name; static const ByteOrderEnum* findData(const Enum e); }; #ifdef __BYTE_ORDER_DECLARE__ std::vector ByteOrderEnum::enumData; bool ByteOrderEnum::initializedFlag = false; ByteOrderEnum::Enum ByteOrderEnum::systemEndian; #endif // __BYTE_ORDER_DECLARE__ } // namespace #endif // __BYTE_ORDER_H__ workbench-1.1.1/src/Common/ByteSwapping.cxx000066400000000000000000000055101255417355300206660ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "ByteSwapping.h" using namespace caret; /** * Swap bytes for the specified type. */ void ByteSwapping::swapBytes(int16_t* n, const uint64_t numToSwap) { for (uint64_t i = 0; i < numToSwap; i++) { char* bytes = (char*)&n[i]; char temp = bytes[0]; bytes[0] = bytes[1]; bytes[1] = temp; } } /** * Swap bytes for the specified type. */ void ByteSwapping::swapBytes(uint16_t* n, const uint64_t numToSwap) { swapBytes((int16_t*)n, numToSwap); } /** * Swap bytes for the specified type. */ void ByteSwapping::swapBytes(int32_t* n, const uint64_t numToSwap) { for (uint64_t i = 0; i < numToSwap; i++) { char* bytes = (char*)&n[i]; char temp = bytes[0]; bytes[0] = bytes[3]; bytes[3] = temp; temp = bytes[1]; bytes[1] = bytes[2]; bytes[2] = temp; } } /** * */ void ByteSwapping::swapBytes(uint32_t* n, const uint64_t numToSwap) { swapBytes((int32_t*)n, numToSwap); } /** * Swap bytes for the specified type. */ void ByteSwapping::swapBytes(int64_t* n, const uint64_t numToSwap) { for (uint64_t i = 0; i < numToSwap; i++) { char* bytes = (char*)&n[i]; char temp = bytes[0]; bytes[0] = bytes[7]; bytes[7] = temp; temp = bytes[1]; bytes[1] = bytes[6]; bytes[6] = temp; temp = bytes[2]; bytes[2] = bytes[5]; bytes[5] = temp; temp = bytes[3]; bytes[3] = bytes[4]; bytes[4] = temp; } } /** * Swap bytes for the specified type. */ void ByteSwapping::swapBytes(uint64_t* n, const uint64_t numToSwap) { swapBytes((int64_t*)n, numToSwap); } /** * Swap bytes for the specified type. */ void ByteSwapping::swapBytes(float* n, const uint64_t numToSwap) { swapBytes((int32_t*)n, numToSwap); } /** * Swap bytes for the specified type. */ void ByteSwapping::swapBytes(double* n, const uint64_t numToSwap) { swapBytes((int64_t*)n, numToSwap); } void ByteSwapping::swapBytes(long double* n, const uint64_t numToSwap) { swapArray(n, numToSwap); } workbench-1.1.1/src/Common/ByteSwapping.h000066400000000000000000000055131255417355300203160ustar00rootroot00000000000000#ifndef __BYTE_SWAPPING_H__ #define __BYTE_SWAPPING_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include namespace caret { /** * This class contains static methods for byte swapping data, typically used * when reading binary data files. */ class ByteSwapping { private: ByteSwapping() { } ~ByteSwapping() { } public: static void swapBytes(uint8_t*, const uint64_t) { }//define them for completeness, so templated stuff can just call it without specializing static void swapBytes(int8_t*, const uint64_t) { } static void swapBytes(uint16_t* n, const uint64_t numToSwap); static void swapBytes(int16_t* n, const uint64_t numToSwap); static void swapBytes(int32_t* n, const uint64_t numToSwap); static void swapBytes(uint32_t* n, const uint64_t numToSwap); static void swapBytes(int64_t* n, const uint64_t numToSwap); static void swapBytes(uint64_t* n, const uint64_t numToSwap); static void swapBytes(float* n, const uint64_t numToSwap); static void swapBytes(double* n, const uint64_t numToSwap); static void swapBytes(long double* n, const uint64_t numToSwap); template static void swap(T& toSwap);//templated versions, to replace hand-coding variants template static void swapArray(T* toSwap, const uint64_t& count); }; template void ByteSwapping::swap(T& toSwap) { if (sizeof(T) == 1) return;//we could specialize 1-byte types, but this should optimize out T temp = toSwap; char* from = (char*)&temp; char* to = (char*)&toSwap; for (int i = 0; i < (int)sizeof(T); ++i) { to[i] = from[sizeof(T) - i - 1]; } } template void ByteSwapping::swapArray(T* toSwap, const uint64_t& count) { if (sizeof(T) == 1) return;//ditto for (uint64_t i = 0; i < count; ++i) { swap(toSwap[i]); } } } #endif // __BYTE_SWAPPING_H__ workbench-1.1.1/src/Common/CMakeLists.txt000066400000000000000000000105511255417355300202670ustar00rootroot00000000000000 # # Name of Project # PROJECT(Common) # # QT include files # SET(QT_USE_QTXML TRUE) SET(QT_DONT_USE_QTGUI TRUE) SET(QT_USE_QTNETWORK TRUE) INCLUDE(${QT_USE_FILE}) #message("QT VERSION: " ${QT_VERSION_MAJOR} ${QT_VERSION_MINOR} ${QT_VERSION_PATCH}) SET(MOC_INPUT_HEADER_FILES CaretHttpManager.h ) QT4_WRAP_CPP(MOC_SOURCE_FILES ${MOC_INPUT_HEADER_FILES}) # # Create a library # ADD_LIBRARY(Common ApplicationInformation.h ApplicationTypeEnum.h AString.h AStringNaturalComparison.h Base64.h BoundingBox.h BrainConstants.h ByteOrderEnum.h ByteSwapping.h CaretAssert.h CaretAssertion.h CaretBinaryFile.h CaretColorEnum.h CaretCommandLine.h CaretCompact3DLookup.h CaretCompactLookup.h CaretException.h CaretFunctionName.h CaretHeap.h CaretHttpManager.h CaretLogger.h CaretMathExpression.h CaretMutex.h CaretObject.h CaretObjectTracksModification.h CaretOMP.h CaretPointer.h CaretPointLocator.h CaretPreferences.h CaretTemporaryFile.h CaretUndoCommand.h CaretUndoStack.h CubicSpline.h DataCompressZLib.h DataFile.h DataFileContentInformation.h DataFileException.h DataFileInterface.h DataFileTypeEnum.h DescriptiveStatistics.h DeveloperFlagsEnum.h ElapsedTimer.h Event.h EventAlertUser.h EventBrowserTabIndicesGetAll.h EventListenerInterface.h EventManager.h EventProgressUpdate.h EventTypeEnum.h FastStatistics.h FileAdapter.h FileInformation.h FloatMatrix.h Histogram.h HtmlStringBuilder.h ImageCaptureMethodEnum.h Logger.h LogHandler.h LogHandlerStandardError.h LogLevelEnum.h LogManager.h LogRecord.h MathFunctionEnum.h MathFunctions.h MatrixFunctions.h ModelTransform.h MultiDimArray.h MultiDimIterator.h NetworkException.h NumericTextFormatting.h OctTree.h OpenGLDrawingMethodEnum.h PlainTextStringBuilder.h Plane.h ProgramParameters.h ProgramParametersException.h ProgressObject.h ProgressReportingInterface.h ReductionEnum.h ReductionOperation.h SpecFileDialogViewFilesTypeEnum.h SpeciesEnum.h StereotaxicSpaceEnum.h StringTableModel.h StructureEnum.h SystemUtilities.h TileTabsConfiguration.h TracksModificationInterface.h Vector3D.h VectorOperation.h VoxelIJK.h YokingGroupEnum.h ${MOC_SOURCE_FILES} ${CMAKE_BINARY_DIR}/Common/ApplicationInformation.cxx ApplicationTypeEnum.cxx AString.cxx AStringNaturalComparison.cxx Base64.cxx BoundingBox.cxx BrainConstants.cxx ByteOrderEnum.cxx ByteSwapping.cxx CaretAssertion.cxx CaretBinaryFile.cxx CaretColorEnum.cxx CaretCommandLine.cxx CaretException.cxx CaretHttpManager.cxx CaretLogger.cxx CaretMathExpression.cxx CaretObject.cxx CaretObjectTracksModification.cxx CaretPointLocator.cxx CaretPreferences.cxx CaretTemporaryFile.cxx CaretUndoCommand.cxx CaretUndoStack.cxx CubicSpline.cxx DataCompressZLib.cxx DataFile.cxx DataFileContentInformation.cxx DataFileException.cxx DataFileTypeEnum.cxx DescriptiveStatistics.cxx DeveloperFlagsEnum.cxx ElapsedTimer.cxx Event.cxx EventAlertUser.cxx EventBrowserTabIndicesGetAll.cxx EventManager.cxx EventProgressUpdate.cxx EventTypeEnum.cxx FastStatistics.cxx FileAdapter.cxx FileInformation.cxx FloatMatrix.cxx Histogram.cxx HtmlStringBuilder.cxx ImageCaptureMethodEnum.cxx Logger.cxx LogHandler.cxx LogHandlerStandardError.cxx LogLevelEnum.cxx LogManager.cxx LogRecord.cxx MathFunctionEnum.cxx MathFunctions.cxx ModelTransform.cxx NetworkException.cxx NumericTextFormatting.cxx OpenGLDrawingMethodEnum.cxx PlainTextStringBuilder.cxx Plane.cxx ProgramParameters.cxx ProgramParametersException.cxx ProgressObject.cxx ReductionEnum.cxx ReductionOperation.cxx SpecFileDialogViewFilesTypeEnum.cxx SpeciesEnum.cxx StereotaxicSpaceEnum.cxx StringTableModel.cxx StructureEnum.cxx SystemUtilities.cxx TileTabsConfiguration.cxx Vector3D.cxx VectorOperation.cxx YokingGroupEnum.cxx ) ADD_DEFINITIONS(-DCOMPILER_NAME="${CMAKE_CXX_COMPILER}") ADD_DEFINITIONS(-DCOMPILER_VERSION="${CMAKE_CXX_COMPILER_VERSION}") # # Include directories # INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/Common ) ADD_CUSTOM_COMMAND( OUTPUT ${CMAKE_BINARY_DIR}/Common/ApplicationInformation.cxx COMMAND ${CMAKE_COMMAND} -DINFILE="${CMAKE_SOURCE_DIR}/Common/ApplicationInformation.cxx.in" -DOUTFILE="${CMAKE_BINARY_DIR}/Common/ApplicationInformation.cxx" -P "${CMAKE_SOURCE_DIR}/CMakeScripts/git_commit_info.cmake.in" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/.. DEPENDS ApplicationInformation.cxx.in ${CMAKE_SOURCE_DIR}/CMakeScripts/git_commit_info.cmake.in ${CMAKE_SOURCE_DIR}/../.git/HEAD ${CMAKE_SOURCE_DIR}/../.git/index COMMENT "Setting commit info" ) workbench-1.1.1/src/Common/CaretAssert.h000066400000000000000000000114231255417355300201170ustar00rootroot00000000000000#ifndef __CARET_ASSERT_H__ #define __CARET_ASSERT_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretAssertion.h" #ifdef NDEBUG #define CaretAssert(e) ((void)0) #define CaretAssertToDoWarning() ((void)0) #define CaretAssertToDoFatal() ((void)0) #define CaretAssertMessage(e, m) ((void)0) #define CaretAssertArrayIndex(a, n, i) ((void) 0) #define CaretAssertVectorIndex(v, i) ((void) 0) #else // NDEBUG /** * \def CaretAssert * * If the expression evaluates to zero, a message is printed * showing the file its line number. A call stack may also * be printed. Lastly, abort() is called. * @param e * Expression that is tested. */ #define CaretAssert(e) \ (((e) == 0) \ ? caret::CaretAssertion::assertFailed(#e, __FILE__, __LINE__) \ : (void)0) /** * \def CaretAssertToDoWarning * * Its purpose is to add "easy to find" reminders in the code * for items that will function without crashing but still * need some additional work. It is expected * CaretAssertToDoWarning will rarely, if ever, be in code that * is pushed to the main repository. * * A message is printed showing the file and its line number. * A call stack may also be printed. Does not call abort(). */ #define CaretAssertToDoWarning() \ caret::CaretAssertion::assertToDoWarning(__FILE__, __LINE__) /** * \def CaretAssertToDoFatal * * Its purpose is to add "easy to find" reminders in the code * for items that are unlikely to be encountered and that * will likely crash or cause significant errors * in the functionality. It is expected * CaretAssertToDoFatal will rarely, if ever, be in code that * is pushed to the main repository. * * A message is printed showing the file and its line number. * A call stack may also be printed. WILL CALL abort() * and terminate execution. */ #define CaretAssertToDoFatal() \ caret::CaretAssertion::assertToDoFatal(__FILE__, __LINE__) /** * \def CaretAssertMessage * * If the expression evaluates to zero, a message is printed * showing the file its line number. The users message is * then printed. A call stack may also * be printed. Lastly, abort() is called. * @param e * Expression that is tested. * @param m * Message that is printed. */ #define CaretAssertMessage(e, m) \ (((e) == 0) \ ? caret::CaretAssertion::assertFailed(#e, AString(m), __FILE__, __LINE__) \ : (void)0) /** * \def CaretAssertArrayIndex * * If the array index is out of bounds, a message is printed * listing the array name, the arrays number of elements, the * invalid array index, the name of the file, and the line * number in the file. A call stack may also * be printed. Lastly, abort() is called. * @param a * The array variable. * @param n * Number of elements in the array. * @param i * The index into the array. */ #define CaretAssertArrayIndex(a, n, i) \ ((((i) < 0) || ((i) >= (n))) \ ? caret::CaretAssertion::assertArrayIndexFailed(#a, n, i, __FILE__, __LINE__) \ : (void)0) /** * \def CaretAssertVectorIndex * * If the vector index is out of bounds, a message is printed * listing the vector name, the vector's number of elements, the * invalid vector index, the name of the file, and the line * number in the file. A call stack may also * be printed. Lastly, abort() is called. * @param v * The vector. * @param i * The index into the vector. */ #define CaretAssertVectorIndex(v, i) \ ((((i) < 0) || ((i) >= (static_cast(v.size())))) \ ? caret::CaretAssertion::assertVectorIndexFailed(#v, (static_cast(v.size())), i, __FILE__, __LINE__) \ : (void)0) #endif // NDEBUG #endif // __CARET_ASSERT_H__ workbench-1.1.1/src/Common/CaretAssertion.cxx000066400000000000000000000310501255417355300211760ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #define __CARET_ASSERTION_DEFINE__ #include "CaretAssertion.h" #undef __CARET_ASSERTION_DEFINE__ #include "CaretAssert.h" #include "SystemUtilities.h" using namespace caret; /** * Called when an assertion has failed. * The following events will occur: * Prints the expression that failed, the name of * file, and the line number in the file. If * a callstack (backtrace) is available, it will * also be printed. Lastly, abort() is called. * * * @param expression * Expression that failed assertion testing. * @param filename * Name of file in which assertion failed. * @param lineNumber * Line number where assertion failed. */ void CaretAssertion::assertFailed(const char* expression, const char* filename, const int64_t lineNumber) { CaretAssertion::assertFailed(expression, NULL, filename, lineNumber); } /** * Called for a CaretAssertToDoWarning() * * The following events will occur: * Prints the name of * file, and the line number in the file. If * a callstack (backtrace) is available, it will * also be printed. DOES NOT call abort. * * * @param filename * Name of file in which assertion failed. * @param lineNumber * Line number where assertion failed. */ void CaretAssertion::assertToDoWarning(const char* filename, const int64_t lineNumber) { std::cerr \ << "CaretAssertToDo WARNING" << std::endl << "File: " << filename << std::endl << "Line number: " << lineNumber << std::endl << std::endl; const AString s = SystemUtilities::getBackTrace(); if (s.isEmpty() == false) { std::cerr << s << std::endl << std::endl; } } /** * Called for a CaretAssertToDoFatal() * * The following events will occur: * Prints the name of * file, and the line number in the file. If * a callstack (backtrace) is available, it will * also be printed. Does call abort. * * * @param filename * Name of file in which assertion failed. * @param lineNumber * Line number where assertion failed. */ void CaretAssertion::assertToDoFatal(const char* filename, const int64_t lineNumber) { std::cerr \ << "CaretAssertToDo FATAL" << std::endl << "File: " << filename << std::endl << "Line number: " << lineNumber << std::endl << std::endl; const AString s = SystemUtilities::getBackTrace(); if (s.isEmpty() == false) { std::cerr << s << std::endl << std::endl; } if (CaretAssertion::unitTestFlag == false) { std::abort(); } } /** * Called when an assertion has failed. * The following events will occur: * Prints the expression that failed, the name of * file, and the line number in the file. If * a callstack (backtrace) is available, it will * also be printed. Lastly, abort() is called. * * * @param expression * Expression that failed assertion testing. * @param message * Message that is printed. * @param filename * Name of file in which assertion failed. * @param lineNumber * Line number where assertion failed. */ void CaretAssertion::assertFailed(const char* expression, const char* message, const char* filename, const int64_t lineNumber) { std::cerr \ << "Assertion Failure of expression \"" << expression << "\"" << std::endl << "File: " << filename << std::endl << "Line number: " << lineNumber << std::endl << std::endl; if (message != NULL) { std::cerr << "Message: " << message << std::endl << std::endl; } const AString s = SystemUtilities::getBackTrace(); if (s.isEmpty() == false) { std::cerr << s << std::endl << std::endl; } if (CaretAssertion::unitTestFlag == false) { std::abort(); } } /** * Called when an array index assertion has failed. * The following events will occur: * Prints the name of the array, the number of * elements in the array, the invalid array index, * the name of the file where the assertion failed, * the line number in the file. If * a callstack (backtrace) is available, it will * also be printed. Lastly, abort() is called. * * * @param arrayName * Name of the array. * @param arrayNumberOfElements * Number of elements in the array. * @param arrayIndex * Invalid array index. * @param filename * Name of file in which assertion failed. * @param lineNumber * Line number where assertion failed. */ void CaretAssertion::assertArrayIndexFailed(const char* arrayName, const int64_t arrayNumberOfElements, const int64_t arrayIndex, const char* filename, const int64_t lineNumber) { std::cerr \ << "Assertion of Array Bounds failed for array: " << arrayName << std::endl << "File: " << filename << std::endl << "Line number: " << lineNumber << std::endl; std::cerr << "Index: " << arrayIndex << std::endl << "Number of elements in array: " << arrayNumberOfElements << std::endl << std::endl; const AString s = SystemUtilities::getBackTrace(); if (s.isEmpty() == false) { std::cerr << s << std::endl << std::endl; } if (CaretAssertion::unitTestFlag == false) { std::abort(); } } /** * Called when an vector index assertion has failed. * The following events will occur: * Prints the name of the vector, the number of * elements in the vector, the invalid vector index, * the name of the file where the assertion failed, * the line number in the file. If * a callstack (backtrace) is available, it will * also be printed. Lastly, abort() is called. * * * @param vectorName * Name of the vector. * @param vectorNumberOfElements * Number of elements in the vector. * @param vectorIndex * Invalid vector index. * @param filename * Name of file in which assertion failed. * @param lineNumber * Line number where assertion failed. */ void CaretAssertion::assertVectorIndexFailed(const char* vectorName, const int64_t vectorNumberOfElements, const int64_t vectorIndex, const char* filename, const int64_t lineNumber) { std::cerr \ << "Assertion of Vector Bounds failed for vector: " << vectorName << std::endl << "File: " << filename << std::endl << "Line number: " << lineNumber << std::endl; std::cerr << "Index: " << vectorIndex << std::endl << "Number of elements in vector: " << vectorNumberOfElements << std::endl << std::endl; const AString s = SystemUtilities::getBackTrace(); if (s.isEmpty() == false) { std::cerr << s << std::endl << std::endl; } if (CaretAssertion::unitTestFlag == false) { std::abort(); } } /** * Unit testing of assertions. * * @param stream * Stream to which messages are written. * @param isVerbose * Print detailed messages. */ void CaretAssertion::unitTest(std::ostream& stream, const bool isVerbose) { #ifdef NDEBUG if (isVerbose) { stream << "Unit testing of CaretAssertion will not take place since software is not compiled with debug on." << std::endl; } return; #else CaretAssertion::unitTestFlag = true; stream << "CaretAssertion::unitTest is starting" << std::endl; /* * Redirect std::err to the string stream. */ std::ostringstream str; std::streambuf* cerrSave = std::cerr.rdbuf(); std::cerr.rdbuf(str.rdbuf()); int32_t zero = 0; int32_t one = 1; CaretAssert(zero); CaretAssertion::unitTestHelper(stream, "Assert Zero", str.str(), false, isVerbose); str.str(""); CaretAssert(one); CaretAssertion::unitTestHelper(stream, "Assert One", str.str(), true, isVerbose); str.str(""); CaretAssertMessage(zero, "This test should fail along with this message being printed."); CaretAssertion::unitTestHelper(stream, "Assert Message Zero", str.str(), false, isVerbose); str.str(""); CaretAssertMessage(one, "This test SHOULD NOT fail."); CaretAssertion::unitTestHelper(stream, "Assert Message One", str.str(), true, isVerbose); str.str(""); /*int32_t someArray[] = { 1, 2, 3 }; someArray[1] = 2;//*/ CaretAssertArrayIndex(someArray, 3, -1); CaretAssertion::unitTestHelper(stream, "Assert Array Index -1", str.str(), false, isVerbose); str.str(""); CaretAssertArrayIndex(someArray, 3, 3); CaretAssertion::unitTestHelper(stream, "Assert Array Index 3", str.str(), false, isVerbose); str.str(""); CaretAssertArrayIndex(someArray, 3, 5); CaretAssertion::unitTestHelper(stream, "Assert Array Index 5", str.str(), false, isVerbose); str.str(""); CaretAssertArrayIndex(someArray, 3, 2); CaretAssertion::unitTestHelper(stream, "Assert Aray Index 2", str.str(), true, isVerbose); str.str(""); std::vector someVector; someVector.push_back(1); someVector.push_back(2); CaretAssertVectorIndex(someVector, 1); CaretAssertion::unitTestHelper(stream, "Assert Vector Index 1", str.str(), true, isVerbose); str.str(""); CaretAssertVectorIndex(someVector, -1); // Yes, do get signed/unsigned warning CaretAssertion::unitTestHelper(stream, "Assert Vector Index -1", str.str(), false, isVerbose); str.str(""); CaretAssertVectorIndex(someVector, 2); CaretAssertion::unitTestHelper(stream, "Assert Vector Index 2", str.str(), false, isVerbose); str.str(""); /* * Restore std::err */ std::cerr.rdbuf(cerrSave); stream << "CaretAssertion::unitTest has ended" << std::endl << std::endl;; CaretAssertion::unitTestFlag = false; #endif } void CaretAssertion::unitTestHelper(std::ostream& stream, const std::string& testName, const std::string& testOutput, const bool correctTestStatus, const bool isVerbose) { /* * Should test pass? */ if (correctTestStatus) { if (testOutput.empty() == false) { stream << "ERROR: CaretAssertion unit test failed but should have passed. " << std::endl << "Test Name: " << testName << std::endl << "Test Output: " << testOutput << std::endl << std::endl; } else if (isVerbose) { stream << "Test " << testName << " functioned correclty (passed)." << std::endl; } } else { if (testOutput.empty()) { stream << "ERROR: CaretAssertion unit test passed but should have failed." << std::endl << "Test Name: " << testName << std::endl << std::endl; } else if (isVerbose) { stream << "Test " << testName << " functioned correctly (failed)." << std::endl; } } } workbench-1.1.1/src/Common/CaretAssertion.h000066400000000000000000000062271255417355300206330ustar00rootroot00000000000000#ifndef __CARET_ASSERTION_H__ #define __CARET_ASSERTION_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include namespace caret { /** * Contains static methods for assertion processing. * * DO NOT USE THIS CLASS!!!! * USE THE MACROS DEFINED IN CaretAssert.h */ class CaretAssertion { private: CaretAssertion(); CaretAssertion(const CaretAssertion& o); CaretAssertion& operator=(const CaretAssertion&); ~CaretAssertion(); public: static void assertFailed(const char* expression, const char* filename, const int64_t lineNumber); static void assertFailed(const char* expression, const char* message, const char* filename, const int64_t lineNumber); static void assertToDoWarning(const char* filename, const int64_t lineNumber); static void assertToDoFatal(const char* filename, const int64_t lineNumber); static void assertArrayIndexFailed(const char* arrayName, const int64_t arrayNumberOfElements, const int64_t arrayIndex, const char* filename, const int64_t lineNumber); static void assertVectorIndexFailed(const char* vectorName, const int64_t vectorNumberOfElements, const int64_t vectorIndex, const char* filename, const int64_t lineNumber); static void unitTest(std::ostream& stream, const bool isVerbose); private: static void unitTestHelper(std::ostream& stream, const std::string& testOutput, const std::string& testName, const bool correctTestStatus, const bool isVerbose); static bool unitTestFlag; }; #ifdef __CARET_ASSERTION_DEFINE__ bool CaretAssertion::unitTestFlag = false; #endif // __CARET_ASSERTION_DEFINE__ } // namespace #endif // __CARET_ASSERTION_H__ workbench-1.1.1/src/Common/CaretBinaryFile.cxx000066400000000000000000000250201255417355300212530ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ //try to force large file support from zlib, any other file reading calls #ifndef CARET_OS_MACOSX #define _LARGEFILE64_SOURCE #define _LFS64_LARGEFILE 1 #define _FILE_OFFSET_BITS 64 #endif #include "CaretBinaryFile.h" #include "CaretLogger.h" #include "DataFileException.h" #include #include "zlib.h" using namespace caret; using namespace std; //private implementation classes namespace caret { #ifdef ZLIB_VERSION class ZFileImpl : public CaretBinaryFile::ImplInterface { gzFile m_zfile; public: ZFileImpl() { m_zfile = NULL; } void open(const QString& filename, const CaretBinaryFile::OpenMode& opmode); void close(); void seek(const int64_t& position); int64_t pos(); void read(void* dataOut, const int64_t& count, int64_t* numRead); void write(const void* dataIn, const int64_t& count); ~ZFileImpl(); }; #endif //ZLIB_VERSION class QFileImpl : public CaretBinaryFile::ImplInterface { QFile m_file; public: void open(const QString& filename, const CaretBinaryFile::OpenMode& opmode); void close(); void seek(const int64_t& position); int64_t pos(); void read(void* dataOut, const int64_t& count, int64_t* numRead); void write(const void* dataIn, const int64_t& count); }; } CaretBinaryFile::ImplInterface::~ImplInterface() { } CaretBinaryFile::CaretBinaryFile(const QString& filename, const OpenMode& fileMode) { open(filename, fileMode); } void CaretBinaryFile::close() { m_curMode = NONE; if (m_impl == NULL) return; m_impl->close(); m_impl.grabNew(NULL); } QString CaretBinaryFile::getFilename() const { if (m_impl == NULL) return "";//don't throw, its not really a problem return m_impl->getFilename(); } bool CaretBinaryFile::getOpenForRead() { return (m_curMode | READ) != 0; } bool CaretBinaryFile::getOpenForWrite() { return (m_curMode | WRITE) != 0; } void CaretBinaryFile::open(const QString& filename, const OpenMode& opmode) { close(); if (opmode == NONE) throw DataFileException("can't open file with NONE mode"); if (filename.endsWith(".gz")) { #ifdef ZLIB_VERSION m_impl.grabNew(new ZFileImpl()); #else //ZLIB_VERSION throw DataFileException("can't open .gz file '" + filename + "', compiled without zlib support"); #endif //ZLIB_VERSION } else { m_impl.grabNew(new QFileImpl()); } m_impl->open(filename, opmode); m_curMode = opmode; } void CaretBinaryFile::read(void* dataOut, const int64_t& count, int64_t* numRead) { if (!getOpenForRead()) throw DataFileException("file is not open for reading"); m_impl->read(dataOut, count, numRead); } void CaretBinaryFile::seek(const int64_t& position) { if (m_curMode == NONE) throw DataFileException("file is not open, can't seek"); m_impl->seek(position); } int64_t CaretBinaryFile::pos() { if (m_curMode == NONE) throw DataFileException("file is not open, can't report position"); return m_impl->pos(); } void CaretBinaryFile::write(const void* dataIn, const int64_t& count) { if (!getOpenForWrite()) throw DataFileException("file is not open for writing"); m_impl->write(dataIn, count); } #ifdef ZLIB_VERSION void ZFileImpl::open(const QString& filename, const CaretBinaryFile::OpenMode& opmode) { close();//don't need to, but just because m_fileName = filename; const char* mode = NULL; switch (opmode)//we only support a limited number of combinations, and the string modes are quirky { case CaretBinaryFile::READ: mode = "rb"; break; case CaretBinaryFile::WRITE_TRUNCATE: mode = "wb";//you have to do "r+b" in order to ask it to not truncate, which zlib doesn't support anyway break; default: throw DataFileException("compressed file only supports READ and WRITE_TRUNCATE modes"); } #if !defined(CARET_OS_MACOSX) && ZLIB_VERNUM > 0x1232 m_zfile = gzopen64(filename.toLocal8Bit().constData(), mode); #else m_zfile = gzopen(filename.toLocal8Bit().constData(), mode); #endif if (m_zfile == NULL) { throw DataFileException("error opening compressed file '" + filename + "'"); } } void ZFileImpl::close() { if (m_zfile == NULL) return;//happens when closed and then destroyed, error opening gzflush(m_zfile, Z_FULL_FLUSH); if (gzclose(m_zfile) != 0) throw DataFileException("error closing compressed file '" + m_fileName + "'"); m_zfile = NULL; } void ZFileImpl::read(void* dataOut, const int64_t& count, int64_t* numRead) { if (m_zfile == NULL) throw DataFileException("read called on unopened ZFileImpl");//shouldn't happen const int64_t CHUNK_SIZE = (1<<26);//64MB, should be large enough for good performance, and small enough not to give zlib trouble - needs to convert to unsigned int int64_t totalRead = 0; int readret = 0;//to preserve the info of the read that broke early while (totalRead < count) { int64_t iterSize = count - totalRead; if (iterSize > CHUNK_SIZE) { iterSize = CHUNK_SIZE; } readret = gzread(m_zfile, (uint8_t*)dataOut + totalRead, iterSize); if (readret < 1) break;//0 or -1 indicate eof or error totalRead += readret; } if (numRead == NULL) { if (totalRead != count) { if (readret < 0) throw DataFileException("error while reading compressed file '" + m_fileName + "'"); throw DataFileException("premature end of file in compressed file '" + m_fileName + "'"); } } else { *numRead = totalRead; } } void ZFileImpl::seek(const int64_t& position) { if (m_zfile == NULL) throw DataFileException("seek called on unopened ZFileImpl");//shouldn't happen if (pos() == position) return;//slight hack, since gzseek is slow or nonfunctional for some cases, so don't try it unless necessary #if !defined(CARET_OS_MACOSX) && ZLIB_VERNUM > 0x1232 int64_t ret = gzseek64(m_zfile, position, SEEK_SET); #else int64_t ret = gzseek(m_zfile, position, SEEK_SET); #endif if (ret != position) throw DataFileException("seek failed in compressed file '" + m_fileName + "'"); } int64_t ZFileImpl::pos() { if (m_zfile == NULL) throw DataFileException("pos called on unopened ZFileImpl");//shouldn't happen #if !defined(CARET_OS_MACOSX) && ZLIB_VERNUM > 0x1232 return gztell64(m_zfile); #else return gztell(m_zfile); #endif } void ZFileImpl::write(const void* dataIn, const int64_t& count) { if (m_zfile == NULL) throw DataFileException("read called on unopened ZFileImpl");//shouldn't happen const int64_t CHUNK_SIZE = (1<<26);//64MB, should be large enough for good performance, and small enough not to give zlib trouble - needs to convert to unsigned int int64_t totalWritten = 0; while (totalWritten < count) { int64_t iterSize = count - totalWritten; if (iterSize > CHUNK_SIZE) { iterSize = CHUNK_SIZE; } int writeret = gzwrite(m_zfile, (uint8_t*)dataIn + totalWritten, iterSize); if (writeret < 1) break;//0 or -1 indicate eof or error totalWritten += writeret; } if (totalWritten != count) throw DataFileException("failed to write to compressed file '" + m_fileName + "'"); } ZFileImpl::~ZFileImpl() { try//throwing from a destructor is a bad idea { close(); } catch (CaretException& e) {//handles DataFileException, should be the only culprit CaretLogWarning(e.whatString()); } catch (exception& e) { CaretLogWarning(e.what()); } catch (...) { CaretLogWarning("caught unknown exception type while closing a compressed file"); } } #endif //ZLIB_VERSION void QFileImpl::open(const QString& filename, const CaretBinaryFile::OpenMode& opmode) { close();//don't need to, but just because m_fileName = filename; QIODevice::OpenMode mode = QIODevice::NotOpen;//means 0 if (opmode & CaretBinaryFile::READ) mode |= QIODevice::ReadOnly; if (opmode & CaretBinaryFile::WRITE) mode |= QIODevice::WriteOnly; if (opmode & CaretBinaryFile::TRUNCATE) mode |= QIODevice::Truncate;//expect QFile to recognize silliness like TRUNCATE by itself m_file.setFileName(filename); if (!m_file.open(mode)) { throw DataFileException("failed to open file '" + m_fileName + "'"); } } void QFileImpl::close() { m_file.close(); } void QFileImpl::read(void* dataOut, const int64_t& count, int64_t* numRead) { int64_t readret = m_file.read((char*)dataOut, count);//expect QFile to handle it without manual chunking if (numRead == NULL) { if (readret != count) { if (readret < 0) throw DataFileException("error while reading file '" + m_fileName + "'"); throw DataFileException("premature end of file in '" + m_fileName + "'"); } } else { *numRead = readret; } } void QFileImpl::seek(const int64_t& position) { if (!m_file.seek(position)) throw DataFileException("seek failed in file '" + m_fileName + "'"); } int64_t QFileImpl::pos() { return m_file.pos(); } void QFileImpl::write(const void* dataIn, const int64_t& count) { int64_t writeret = m_file.write((const char*)dataIn, count);//again, expect QFile to handle it in one shot const AString msg = ("failed to write file '" + m_fileName + "'. Tried to write " + AString::number(count) + " bytes but actually wrote " + AString::number(writeret) + " bytes."); if (writeret != count) throw DataFileException(msg); //if (writeret != count) throw DataFileException("failed to write to file '" + m_fileName + "'"); } workbench-1.1.1/src/Common/CaretBinaryFile.h000066400000000000000000000057621255417355300207130ustar00rootroot00000000000000#ifndef __CARET_BINARY_FILE_H__ #define __CARET_BINARY_FILE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretPointer.h" #include #include namespace caret { //class to hide difference between compressed and standard binary file reading, and to automate error checking (throws if problem) class CaretBinaryFile { public: enum OpenMode { NONE = 0, READ = 1, WRITE = 2, READ_WRITE = 3,//for convenience TRUNCATE = 4, WRITE_TRUNCATE = 6,//ditto READ_WRITE_TRUNCATE = 7//ditto }; CaretBinaryFile() { } ///constructor that opens file CaretBinaryFile(const QString& filename, const OpenMode& fileMode = READ); void open(const QString& filename, const OpenMode& opmode = READ); void close(); QString getFilename() const;//not a reference because when no file is open, m_impl is NULL bool getOpenForRead(); bool getOpenForWrite(); void seek(const int64_t& position); int64_t pos(); void read(void* dataOut, const int64_t& count, int64_t* numRead = NULL);//throw if numRead is NULL and (error or end of file reached early) void write(const void* dataIn, const int64_t& count);//failure to complete write is always an exception class ImplInterface { protected: QString m_fileName;//filename is tracked here so error messages can be implementation-specific public: virtual void open(const QString& filename, const OpenMode& opmode) = 0; virtual void close() = 0; const QString& getFilename() const { return m_fileName; } virtual void seek(const int64_t& position) = 0; virtual int64_t pos() = 0; virtual void read(void* dataOut, const int64_t& count, int64_t* numRead) = 0; virtual void write(const void* dataIn, const int64_t& count) = 0; virtual ~ImplInterface(); }; private: CaretPointer m_impl; OpenMode m_curMode;//so implementation classes don't have to track it }; } //namespace caret #endif //__CARET_BINARY_FILE_H__ workbench-1.1.1/src/Common/CaretColorEnum.cxx000066400000000000000000000352341255417355300211420ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __CARET_COLOR_ENUM_DECLARE__ #include "CaretColorEnum.h" #undef __CARET_COLOR_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::CaretColorEnum * \brief Enumerate types for standard colors (HTML 4.01) * * Enumerated types for standard colors */ /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * @param guiName * User-friendly name for use in user-interface. * @param red * Red color component [0 - 1] * @param green * Green color component [0 - 1] * @param blue * Blue color component [0 - 1] */ CaretColorEnum::CaretColorEnum(const Enum enumValue, const AString& name, const AString& guiName, const float red, const float green, const float blue) { this->enumValue = enumValue; this->integerCode = integerCodeCounter++; this->name = name; this->guiName = guiName; this->rgb[0]= red; this->rgb[1]= green; this->rgb[2]= blue; } /** * Destructor. */ CaretColorEnum::~CaretColorEnum() { } /** * Initialize the enumerated metadata. */ void CaretColorEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(CaretColorEnum(AQUA, "AQUA", "Aqua", 0, 1, 1)); enumData.push_back(CaretColorEnum(BLACK, "BLACK", "Black", 0, 0, 0)); enumData.push_back(CaretColorEnum(BLUE, "BLUE", "Blue", 0, 0, 1)); enumData.push_back(CaretColorEnum(FUCHSIA, "FUCHSIA", "Fuchsia", 1, 0, 1)); enumData.push_back(CaretColorEnum(GRAY, "GRAY", "Gray", 0.50, 0.50, 0.50)); enumData.push_back(CaretColorEnum(GREEN, "GREEN", "Green", 0, 0.5, 0)); enumData.push_back(CaretColorEnum(LIME, "LIME", "Lime", 0, 1, 0)); enumData.push_back(CaretColorEnum(MAROON, "MAROON", "Maroon", 0.5, 0, 0)); enumData.push_back(CaretColorEnum(NAVY, "NAVY", "Navy", 0, 0, 0.5)); enumData.push_back(CaretColorEnum(OLIVE, "OLIVE", "Olive", 0.5, 0.5, 0)); enumData.push_back(CaretColorEnum(PURPLE, "PURPLE", "Purple", 0.5, 0, 0.5)); enumData.push_back(CaretColorEnum(RED, "RED", "Red", 1, 0, 0)); enumData.push_back(CaretColorEnum(SILVER, "SILVER", "Silver", 0.75, 0.75, 0.75)); enumData.push_back(CaretColorEnum(TEAL, "TEAL", "Teal", 0, 0.5, 0.5)); enumData.push_back(CaretColorEnum(WHITE, "WHITE", "White", 1, 1, 1)); enumData.push_back(CaretColorEnum(YELLOW, "YELLOW", "Yellow", 1, 1, 0)); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const CaretColorEnum* CaretColorEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const CaretColorEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString CaretColorEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const CaretColorEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ CaretColorEnum::Enum CaretColorEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = BLACK; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const CaretColorEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type CaretColorEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString CaretColorEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const CaretColorEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get the RGB components (ranging zero to one) of the enumerated type. * @param enumValue * Enumerated value. * @return Pointer to RGB components. */ const float* CaretColorEnum::toRGB(Enum enumValue) { if (initializedFlag == false) initialize(); const CaretColorEnum* enumInstance = findData(enumValue); return enumInstance->rgb; } /** * Get the RGB components (ranging zero to one) of the enumerated type. * @param enumValue * Enumerated value. * @param rgbOut * Output with RGB components [0.0, 1.0] */ void CaretColorEnum::toRGBFloat(Enum enumValue, float rgbOut[3]) { if (initializedFlag == false) initialize(); const CaretColorEnum* enumInstance = findData(enumValue); rgbOut[0] = enumInstance->rgb[0]; rgbOut[1] = enumInstance->rgb[1]; rgbOut[2] = enumInstance->rgb[2]; } /** * Get the RGB components (ranging zero to one) of the enumerated type. * @param enumValue * Enumerated value. * @param rgbOut * Output with RGB components [0.0, 1.0] */ void CaretColorEnum::toRGBByte(Enum enumValue, uint8_t rgbOut[3]) { float rgbFloat[3]; toRGBFloat(enumValue, rgbFloat); rgbOut[0] = static_cast(rgbFloat[0] * 255.0); rgbOut[1] = static_cast(rgbFloat[1] * 255.0); rgbOut[2] = static_cast(rgbFloat[2] * 255.0); } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ CaretColorEnum::Enum CaretColorEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = BLACK; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const CaretColorEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type CaretColorEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t CaretColorEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const CaretColorEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ CaretColorEnum::Enum CaretColorEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = BLACK; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const CaretColorEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type CaretColorEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void CaretColorEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void CaretColorEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); std::vector allEnums; CaretColorEnum::getAllEnums(allEnums); for (std::vector::iterator iter = allEnums.begin(); iter != allEnums.end(); iter++) { allNames.push_back(CaretColorEnum::toName(*iter)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void CaretColorEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); std::vector allEnums; CaretColorEnum::getAllEnums(allEnums); for (std::vector::iterator iter = allEnums.begin(); iter != allEnums.end(); iter++) { allGuiNames.push_back(CaretColorEnum::toGuiName(*iter)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } workbench-1.1.1/src/Common/CaretColorEnum.h000066400000000000000000000075501255417355300205670ustar00rootroot00000000000000#ifndef __CARET_COLOR_ENUM__H_ #define __CARET_COLOR_ENUM__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class CaretColorEnum { public: /** * Enumerated values. */ enum Enum { /** AQUA */ AQUA, /** Black */ BLACK, /** Blue */ BLUE, /** Fuchsia */ FUCHSIA, /** Gray */ GRAY, /** Green */ GREEN, /** Lime */ LIME, /** Maroon */ MAROON, /** Navy Blue */ NAVY, /** Olive */ OLIVE, /** Purple */ PURPLE, /** Red */ RED, /** Silver */ SILVER, /** Teal */ TEAL, /** White */ WHITE, /** Yellow */ YELLOW }; ~CaretColorEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); static const float* toRGB(Enum enumValue); static void toRGBFloat(Enum enumValue, float rgbOut[3]); static void toRGBByte(Enum enumValue, uint8_t rgbOut[3]); private: CaretColorEnum(const Enum enumValue, const AString& name, const AString& guiName, const float red, const float green, const float blue); static const CaretColorEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; /** RGB color components */ float rgb[3]; }; #ifdef __CARET_COLOR_ENUM_DECLARE__ std::vector CaretColorEnum::enumData; bool CaretColorEnum::initializedFlag = false; int32_t CaretColorEnum::integerCodeCounter = 0; #endif // __CARET_COLOR_ENUM_DECLARE__ } // namespace #endif //__CARET_COLOR_ENUM__H_ workbench-1.1.1/src/Common/CaretCommandLine.cxx000066400000000000000000000055541255417355300214270ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretCommandLine.h" #include "ProgramParameters.h" using namespace caret; AString caret::caret_global_commandLine; namespace {//private namespace void add_parameter(const AString& param) { if (caret_global_commandLine.size() != 0) { caret_global_commandLine += " "; } if (param.indexOfAnyChar(" $();&<>\"`*?{|") != -1)//check for things that the shell is likely to treat specially EXCEPT for ' itself - assume bash for now, but ignore some more specialized cases {//NOTE: not checking for \ or replacing with \\, because it is rare except in windows native paths where it will wreak havok to double it if (param.indexOf('\'') != -1)//oh joy, ' also {//we COULD check if it is safe to use "", but "" and non-CDATA xml text don't look nice (we avoid CDATA in CIFTI because the matlab GIFTI toolbox at least used to choke on it after conversion) AString replaced = param; replaced.replace('\'', "'\\''");//that is '\'' caret_global_commandLine += "'" + replaced + "'"; } else { caret_global_commandLine += "'" + param + "'"; } } else { if (param.indexOf('\'') != -1)//has ' but no other problems, doesn't need quoting { AString replaced = param; replaced.replace('\'', "\\'");//that is \' caret_global_commandLine += replaced; } else { caret_global_commandLine += param; } } } } void caret::caret_global_commandLine_init(const ProgramParameters& params) { int32_t numParams = params.getNumberOfParameters(); caret_global_commandLine = ""; add_parameter(params.getProgramName()); for (int32_t i = 0; i < numParams; ++i) { add_parameter(params.getParameter(i)); } } void caret::caret_global_commandLine_init(const int& argc, const char *const * argv) { ProgramParameters params(argc, argv); caret_global_commandLine_init(params); } workbench-1.1.1/src/Common/CaretCommandLine.h000066400000000000000000000023251255417355300210450ustar00rootroot00000000000000#ifndef __CARET_COMMAND_LINE_H__ #define __CARET_COMMAND_LINE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AString.h" namespace caret { class ProgramParameters; extern AString caret_global_commandLine; void caret_global_commandLine_init(const ProgramParameters& params); void caret_global_commandLine_init(const int& argc, const char *const * argv); } #endif //__CARET_COMMAND_LINE_H__ workbench-1.1.1/src/Common/CaretCompact3DLookup.h000066400000000000000000000103751255417355300216320ustar00rootroot00000000000000#ifndef __CARET_COMPACT_3D_LOOKUP_H__ #define __CARET_COMPACT_3D_LOOKUP_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretCompactLookup.h" namespace caret { template class CaretCompact3DLookup { CaretCompactLookup > > m_lookup;//the whole point of this class is to deal with this ugliness public: ///creates the element if it didn't exist, and returns a reference to it T& at(const int64_t& index1, const int64_t& index2, const int64_t& index3); ///creates the element if it didn't exist, and returns a reference to it T& at(const int64_t index[3]) { return at(index[0], index[1], index[2]); } ///add or overwrite an element in the lookup void insert(const int64_t& index1, const int64_t& index2, const int64_t& index3, const T& value) { at(index1, index2, index3) = value; } ///add or overwrite an element in the lookup void insert(const int64_t index[3], const T& value) { at(index) = value; } ///returns a pointer to the desired element, or NULL if no such element is found T* find(const int64_t& index1, const int64_t& index2, const int64_t& index3); ///returns a pointer to the desired element, or NULL if no such element is found T* find(const int64_t index[3]) { return find(index[0], index[1], index[2]); } ///returns a pointer to the desired element, or NULL if no such element is found const T* find(const int64_t& index1, const int64_t& index2, const int64_t& index3) const; ///returns a pointer to the desired element, or NULL if no such element is found const T* find(const int64_t index[3]) const { return find(index[0], index[1], index[2]); } ///empties the lookup void clear(); }; template T& CaretCompact3DLookup::at(const int64_t& index1, const int64_t& index2, const int64_t& index3) { return m_lookup[index3][index2][index1];//a lot of complexity is hidden in those operator[]s } template T* CaretCompact3DLookup::find(const int64_t& index1, const int64_t& index2, const int64_t& index3) { typename CaretCompactLookup > >::iterator iter1 = m_lookup.find(index3);//oh the humanity if (iter1 == m_lookup.end()) return NULL; typename CaretCompactLookup >::iterator iter2 = iter1->find(index2); if (iter2 == iter1->end()) return NULL; typename CaretCompactLookup::iterator iter3 = iter2->find(index1); if (iter3 == iter2->end()) return NULL; return &(*iter3); } template const T* CaretCompact3DLookup::find(const int64_t& index1, const int64_t& index2, const int64_t& index3) const { typename CaretCompactLookup > >::const_iterator iter1 = m_lookup.find(index3);//oh the humanity if (iter1 == m_lookup.end()) return NULL; typename CaretCompactLookup >::const_iterator iter2 = iter1->find(index2); if (iter2 == iter1->end()) return NULL; typename CaretCompactLookup::const_iterator iter3 = iter2->find(index1); if (iter3 == iter2->end()) return NULL; return &(*iter3); } template void CaretCompact3DLookup::clear() { m_lookup.clear(); } } #endif //__CARET_COMPACT_3D_LOOKUP_H__ workbench-1.1.1/src/Common/CaretCompactLookup.h000066400000000000000000000225641255417355300214460ustar00rootroot00000000000000#ifndef __CARET_COMPACT_LOOKUP_H__ #define __CARET_COMPACT_LOOKUP_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretAssert.h" #include "stdint.h" #include namespace caret { template class CaretCompactLookup { struct Chunk { int64_t start; std::vector elements; }; std::vector m_chunks; public: class iterator { CaretCompactLookup& m_container; std::size_t m_chunk; int64_t m_elem; iterator(CaretCompactLookup& container, std::size_t chunk, int64_t elem) : m_container(container), m_chunk(chunk), m_elem(elem) { } public: bool operator==(const iterator& rhs) const { if (&m_container != &(rhs.m_container)) return false; if (m_chunk != rhs.m_chunk) return false; if (m_elem != rhs.m_elem) return false; return true; } T& operator*() { CaretAssert(m_chunk < m_container.m_chunks.size()); CaretAssert(m_elem >= 0 && m_elem < (int64_t)m_container.m_chunks[m_chunk].elements.size()); return m_container.m_chunks[m_chunk].elements[m_elem]; } T* operator->() { CaretAssert(m_chunk < m_container.m_chunks.size()); CaretAssert(m_elem >= 0 && m_elem < (int64_t)m_container.m_chunks[m_chunk].elements.size()); return &(m_container.m_chunks[m_chunk].elements[m_elem]); } friend class CaretCompactLookup; }; class const_iterator { const CaretCompactLookup& m_container; std::size_t m_chunk; int64_t m_elem; const_iterator(const CaretCompactLookup& container, std::size_t chunk, std::size_t elem) : m_container(container), m_chunk(chunk), m_elem(elem) { } public: bool operator==(const const_iterator& rhs) const { if (&m_container != &(rhs.m_container)) return false; if (m_chunk != rhs.m_chunk) return false; if (m_elem != rhs.m_elem) return false; return true; } const T& operator*() { CaretAssert(m_chunk < m_container.m_chunks.size()); CaretAssert(m_elem >= 0 && m_elem < (int64_t)m_container.m_chunks[m_chunk].elements.size()); return m_container.m_chunks[m_chunk].elements[m_elem]; } const T* operator->() { CaretAssert(m_chunk < m_container.m_chunks.size()); CaretAssert(m_elem >= 0 && m_elem < (int64_t)m_container.m_chunks[m_chunk].elements.size()); return &(m_container.m_chunks[m_chunk].elements[m_elem]); } friend class CaretCompactLookup; }; ///creates the element if it didn't exist, and returns a reference to it T& operator[](const int64_t& index); ///returns an iterator pointing to the desired element, or one equal to end() if no such element is found iterator find(const int64_t& index); ///returns a const_iterator pointing to the desired element, or one equal to end() if no such element is found const_iterator find(const int64_t& index) const; iterator end(); const_iterator end() const; ///empties the lookup void clear(); }; template T& CaretCompactLookup::operator[](const int64_t& index) { std::size_t numChunks = m_chunks.size(); std::size_t low = 0, high = numChunks, guess;//NOTE: low is 0 because size_t is unsigned, really means -1 bool attach_low = false, attach_high = false; while (low < high)//bisection search for the chunk with the largest start value not greater than { guess = (low + high - 1) / 2;//which is why we subtract 1 here CaretAssert(guess < m_chunks.size()); if (m_chunks[guess].start > index) { high = guess; } else { low = guess + 1; } }//NOTE: low == high after loop ends if (high > 0 && m_chunks[high - 1].start + (int64_t)(m_chunks[high - 1].elements.size()) > index)//element exists, return it { CaretAssertVectorIndex(m_chunks[high -1].elements, index - m_chunks[high - 1].start); return m_chunks[high - 1].elements[index - m_chunks[high - 1].start]; } if (high > 0 && m_chunks[high - 1].start + (int64_t)(m_chunks[high - 1].elements.size()) == index) attach_low = true;//index is 1 beyond the range if (high < numChunks && m_chunks[high].start == index + 1) attach_high = true;//index is 1 before the next range if (attach_low) { std::vector& lowvec = m_chunks[high - 1].elements; std::size_t retIndex = lowvec.size(); lowvec.push_back(T()); if (attach_high) { std::vector& highvec = m_chunks[high].elements; lowvec.insert(lowvec.end(), highvec.begin(), highvec.end()); m_chunks.erase(m_chunks.begin() + high); } return lowvec[retIndex]; } else { if (attach_high) { std::vector& highvec = m_chunks[high].elements; highvec.insert(highvec.begin(), T());//add a new element to the start of the vector (yes, this could be slow) m_chunks[high].start = index;//and change the start value of the chunk return highvec[0]; } else { m_chunks.insert(m_chunks.begin() + high, Chunk()); m_chunks[high].start = index; m_chunks[high].elements.push_back(T()); return m_chunks[high].elements[0]; } } } template typename CaretCompactLookup::iterator CaretCompactLookup::find(const int64_t& index) { std::size_t numChunks = m_chunks.size(); std::size_t low = 0, high = numChunks, guess;//NOTE: low is 0 because size_t is unsigned, really means -1 while (low < high)//bisection search for the chunk with the largest start value not greater than { guess = (low + high - 1) / 2;//which is why we subtract 1 here CaretAssert(guess < m_chunks.size()); if (m_chunks[guess].start > index) { high = guess; } else { low = guess + 1; } }//NOTE: low == high after loop ends if (high > 0 && m_chunks[high - 1].start + (int64_t)(m_chunks[high - 1].elements.size()) > index)//element exists, return it { std::size_t outIndex = index - m_chunks[high - 1].start; CaretAssert(outIndex < m_chunks[high - 1].elements.size()); return iterator(*this, high - 1, outIndex); } return end(); } template typename CaretCompactLookup::const_iterator CaretCompactLookup::find(const int64_t& index) const { std::size_t numChunks = m_chunks.size(); std::size_t low = 0, high = numChunks, guess;//NOTE: low is 0 because size_t is unsigned, really means -1 while (low < high)//bisection search for the chunk with the largest start value not greater than { guess = (low + high - 1) / 2;//which is why we subtract 1 here CaretAssert(guess < m_chunks.size()); if (m_chunks[guess].start > index) { high = guess; } else { low = guess + 1; } }//NOTE: low == high after loop ends if (high > 0 && m_chunks[high - 1].start + (int64_t)(m_chunks[high - 1].elements.size()) > index)//element exists, return it { std::size_t outIndex = index - m_chunks[high - 1].start; CaretAssert(outIndex < m_chunks[high - 1].elements.size()); return const_iterator(*this, high - 1, outIndex); } return end(); } template typename CaretCompactLookup::iterator CaretCompactLookup::end() { return iterator(*this, 0, -1); } template typename CaretCompactLookup::const_iterator CaretCompactLookup::end() const { return const_iterator(*this, 0, -1); } template void CaretCompactLookup::clear() { m_chunks.clear(); } } #endif //__CARET_COMPACT_LOOKUP_H__ workbench-1.1.1/src/Common/CaretException.cxx000066400000000000000000000053231255417355300211710ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretException.h" #include "SystemUtilities.h" #include using namespace caret; /** * Constructor. * */ CaretException::CaretException() : std::runtime_error("") { this->initializeMembersCaretException(); } /** * Constructor. * * @param s Description of the exception. * */ CaretException::CaretException( const AString& s) : std::runtime_error(s.toStdString()) { this->initializeMembersCaretException(); this->exceptionDescription = s; } /** * Copy Constructor. * @param e * Exception that is copied. */ CaretException::CaretException(const CaretException& e) : std::runtime_error(e) { this->exceptionDescription = e.exceptionDescription; this->callStack = e.callStack; } /** * Assignment operator. * @param e * Exception that is copied. * @return * Copy of the exception. */ CaretException& CaretException::operator=(const CaretException& e) { if (this != &e) { std::runtime_error::operator=(e); this->exceptionDescription = e.exceptionDescription; this->callStack = e.callStack; } return *this; } /** * Destructor */ CaretException::~CaretException() throw() { } void CaretException::initializeMembersCaretException() { this->exceptionDescription = ""; this->callStack = SystemUtilities::getBackTrace(); } /** * Get the current call stack. * @return String containing the call stack. */; AString CaretException::getCallStack() const { return callStack; } /** * Get a message describing the exception. * @return A message describing the exception. */ AString CaretException::whatString() const throw() { return this->exceptionDescription; } /** * Allow subclasses to override the exception description. * * @param s Description of the exception. */ void CaretException::setExceptionDescription(const AString& s) { this->exceptionDescription = s; } workbench-1.1.1/src/Common/CaretException.h000066400000000000000000000032441255417355300206160ustar00rootroot00000000000000#ifndef __CARETEXCEPTION_H__ #define __CARETEXCEPTION_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include namespace caret { /** * An exception thrown during Caret processing. */ class CaretException : public std::runtime_error { public: CaretException(); CaretException(const AString& s); CaretException(const CaretException& e); CaretException& operator=(const CaretException& e); virtual ~CaretException() throw(); virtual AString whatString() const throw(); AString getCallStack() const; protected: void setExceptionDescription(const AString& s); private: /// Description of the exception AString exceptionDescription; /// the call stack AString callStack; void initializeMembersCaretException(); }; } // namespace #endif // __CARETEXCEPTION_H__ workbench-1.1.1/src/Common/CaretFunctionName.h000066400000000000000000000023561255417355300212510ustar00rootroot00000000000000#ifndef __CARET_FUNCTION_NAME_H__ #define __CARET_FUNCTION_NAME_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #ifdef CARET_OS_WINDOWS_MSVC /** * \def __CARET_FUNCTION_NAME__ * * A macro that contains the name of the current * function. This macro's definition is platform * dependent (Visual C++ vs. GCC). */ #define __CARET_FUNCTION_NAME__ __FUNCSIG__ #else #define __CARET_FUNCTION_NAME__ __PRETTY_FUNCTION__ #endif #endif //__CARET_FUNCTION_NAME_H__ workbench-1.1.1/src/Common/CaretHeap.h000066400000000000000000000370421255417355300175400ustar00rootroot00000000000000#ifndef __CARET_HEAP__ #define __CARET_HEAP__ #include "CaretAssertion.h" /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "stdint.h" #include "CaretAssert.h" namespace caret { ///heap base used to be able to modify existing data's keys, and to automatically indirect the heap data through indexing, so that all heap reordering involves only integer assignments template class CaretHeapBase { struct DataStruct { K m_key; T m_data; int64_t m_index; DataStruct(const K& key, const T& data) : m_key(key), m_data(data) { } }; std::vector m_datastore; std::vector m_heap; std::vector m_unusedStore; ///primitive used to make the code a bit nicer, modifies the heap and the field in the data to point to it ///DOES NOT GUARANTEE CONSISTENT HEAP STATE void put(const int64_t& dataIndex, const int64_t& heapLoc); //uses curiously recurring template pattern to use child class's comparison without virtual - can't make a typedef missing template parameters, so this is the most convenient way for usage inline bool compare(const K& left, const K& right) { return C::mycompare(left, right); } void heapify_up(const int64_t& start); void heapify_down(const int64_t& start); protected: CaretHeapBase() { }//this is not a usable class by itself - use CaretMinHeap or CaretMaxHeap public: ///this heap is special, save the return value of push() and you can modify the key/data later (constant time for data, log(heapsize) time to change key) int64_t push(const T& data, const K& key); ///look at the data of the top element T& top(K* key = NULL); ///remove and return the top element T pop(K* key = NULL); ///modify the key, and reheapify void changekey(const int64_t& dataIndex, K key); ///delete the element with the key, and reheapify T remove(const int64_t& dataIndex, K* key = NULL); ///modify or just access the data - could be an operator[] but that would be kind of confusing T& data(const int64_t& dataIndex); ///preallocate for efficiency, if you know about how big it will be void reserve(int64_t expectedSize); ///retrieve the key value const K& getKey(const int64_t& dataIndex) const; ///check for empty bool isEmpty() const; ///get number of elements int64_t size() const; ///reset the heap void clear(); }; ///simpler heap base for more basic (and not indirected) use, give it pointers if your data struct is nontrivial template class CaretSimpleHeapBase { struct DataStruct { K m_key; T m_data; DataStruct(const K& key, const T& data) : m_key(key), m_data(data) { } }; std::vector m_heap; //uses curiously recurring template pattern to use child class's comparison without virtual inline bool compare(const K& left, const K& right) { return C::mycompare(left, right); } void heapify_up(const int64_t& start); void heapify_down(const int64_t& start); protected: CaretSimpleHeapBase() { }//this is not a usable class by itself - use CaretMinHeap or CaretMaxHeap public: void push(const T& data, const K& key); ///look at the data of the top element T& top(K* key = NULL); ///remove and return the top element T pop(K* key = NULL); ///preallocate for efficiency, if you know about how big it will be void reserve(int64_t expectedSize); ///check for empty bool isEmpty() const; ///get number of elements int64_t size() const; ///reset the heap void clear(); }; ///minheap with advanced features template class CaretMinHeap : public CaretHeapBase > { public: static inline bool mycompare(const K& left, const K& right) { return left < right; } }; ///maxheap with advanced features template class CaretMaxHeap : public CaretHeapBase > { public: static inline bool mycompare(const K& left, const K& right) { return right < left; } }; ///basic minheap template class CaretSimpleMinHeap : public CaretSimpleHeapBase > { public: static inline bool mycompare(const K& left, const K& right) { return left < right; } }; ///basic maxheap template class CaretSimpleMaxHeap : public CaretSimpleHeapBase > { public: static inline bool mycompare(const K& left, const K& right) { return right < left; } }; template void CaretHeapBase::changekey(const int64_t& dataIndex, K key) { CaretAssertVectorIndex(m_datastore, dataIndex); CaretAssert(m_datastore[dataIndex].m_index != -1); K oldkey = m_datastore[dataIndex].m_key; m_datastore[dataIndex].m_key = key; if (compare(oldkey, key)) { heapify_down(m_datastore[dataIndex].m_index); } else { heapify_up(m_datastore[dataIndex].m_index); } } template T CaretHeapBase::remove(const int64_t& dataIndex, K* key) { CaretAssertVectorIndex(m_datastore, dataIndex); CaretAssert(m_datastore[dataIndex].m_index != -1); int64_t myHeapIndex = m_datastore[dataIndex].m_index; T ret = m_datastore[dataIndex].m_data; if (key != NULL) *key = m_datastore[dataIndex].m_key; if (myHeapIndex < (int64_t)(m_heap.size() - 1))//don't try to do stuff other than removing it if it is the last element { K removedKey = m_datastore[dataIndex].m_key; K newKey = m_datastore[m_heap.back()].m_key; put(m_heap.back(), myHeapIndex);//replace the removed element's position with the last m_heap.pop_back(); if (compare(removedKey, newKey))//and heapify it { heapify_down(myHeapIndex); } else { heapify_up(myHeapIndex); } } else { m_heap.pop_back(); } m_unusedStore.push_back(dataIndex);//mark the removed data location as unused m_datastore[dataIndex].m_index = -1; return ret; } template T& CaretHeapBase::data(const int64_t& dataIndex) { CaretAssertVectorIndex(m_datastore, dataIndex); CaretAssert(m_datastore[dataIndex].m_index != -1); return m_datastore[dataIndex].m_data; } template void CaretHeapBase::heapify_down(const int64_t& start) { CaretAssertVectorIndex(m_heap, start); int64_t cur = start, nextInd = (start << 1) + 1, mySize = (int64_t)m_heap.size(); int64_t temp = m_heap[start];//save current data index, don't swap it around until we stop while (nextInd < mySize) { if (nextInd + 1 < mySize && compare(m_datastore[m_heap[nextInd + 1]].m_key, m_datastore[m_heap[nextInd]].m_key)) { ++nextInd; } if (compare(m_datastore[m_heap[nextInd]].m_key, m_datastore[temp].m_key)) { put(m_heap[nextInd], cur);//move the best child up cur = nextInd;//advance current nextInd = (cur << 1) + 1; } else { break; } } if (cur != start) put(temp, cur);//stopped, now we put it and finish, but only if we moved something } template void CaretHeapBase::heapify_up(const int64_t& start) { CaretAssertVectorIndex(m_heap, start); int64_t cur = start, nextInd = (start - 1) >> 1; int64_t temp = m_heap[start]; while (cur > 0) { if (compare(m_datastore[temp].m_key, m_datastore[m_heap[nextInd]].m_key)) { put(m_heap[nextInd], cur); cur = nextInd; nextInd = (cur - 1) >> 1; } else { break; } } if (cur != start) put(temp, cur); } template T CaretHeapBase::pop(K* key) { CaretAssert(m_heap.size() > 0); T ret = m_datastore[m_heap[0]].m_data; if (key != NULL) *key = m_datastore[m_heap[0]].m_key; m_unusedStore.push_back(m_heap[0]);//should this try garbage collection? currently, the T data will remain until overwritten...would require another level of indirection to fix m_datastore[m_heap[0]].m_index = -1; if (m_heap.size() > 1) { put(m_heap[m_heap.size() - 1], 0); m_heap.pop_back(); heapify_down(0); } else { m_heap.pop_back(); } return ret; } template int64_t CaretHeapBase::push(const T& data, const K& key) { int64_t dataLoc; if (m_unusedStore.size() > 0) { dataLoc = m_unusedStore[m_unusedStore.size() - 1]; m_datastore[dataLoc].m_key = key; m_datastore[dataLoc].m_data = data; m_unusedStore.pop_back(); } else { dataLoc = m_datastore.size(); m_datastore.push_back(DataStruct(key, data)); } m_datastore[dataLoc].m_index = (int64_t)m_heap.size(); m_heap.push_back(dataLoc); heapify_up(m_heap.size() - 1); return dataLoc; } template void CaretHeapBase::put(const int64_t& dataIndex, const int64_t& heapLoc) { CaretAssertVectorIndex(m_datastore, dataIndex); CaretAssertVectorIndex(m_heap, heapLoc); m_datastore[dataIndex].m_index = heapLoc; m_heap[heapLoc] = dataIndex; } template void CaretHeapBase::reserve(int64_t expectedSize) { if (expectedSize <= 0) return; m_heap.reserve(expectedSize); m_datastore.reserve(expectedSize); m_unusedStore.reserve(expectedSize);//expect them to eventually pop() everything } template T& CaretHeapBase::top(K* key) { CaretAssert(m_heap.size() > 0); if (key != NULL) *key = m_datastore[m_heap[0]].m_key; return m_datastore[m_heap[0]].m_data; } template bool CaretHeapBase::isEmpty() const { return m_heap.size() == 0; } template int64_t CaretHeapBase::size() const { return (int64_t)m_heap.size(); } template void CaretHeapBase::clear() { m_heap.clear(); m_datastore.clear(); m_unusedStore.clear(); } template void CaretSimpleHeapBase::heapify_down(const int64_t& start) { CaretAssertVectorIndex(m_heap, start); int64_t cur = start, nextInd = (start << 1) + 1, mySize = (int64_t)m_heap.size(); DataStruct temp = m_heap[start];//save current data, don't swap it around until we stop while (nextInd < mySize) { if (nextInd + 1 < mySize && compare(m_heap[nextInd + 1].m_key, m_heap[nextInd].m_key)) { ++nextInd; } if (compare(m_heap[nextInd].m_key, temp.m_key)) { m_heap[cur] = m_heap[nextInd];//move the best child up cur = nextInd;//advance current nextInd = (cur << 1) + 1; } else { break; } } if (cur != start) m_heap[cur] = temp;//stopped, now we put it and finish, but only if we moved something } template void CaretSimpleHeapBase::heapify_up(const int64_t& start) { CaretAssertVectorIndex(m_heap, start); int64_t cur = start, nextInd = (start - 1) >> 1; DataStruct temp = m_heap[start]; while (cur > 0) { if (compare(temp.m_key, m_heap[nextInd].m_key)) { m_heap[cur] = m_heap[nextInd]; cur = nextInd; nextInd = (cur - 1) >> 1; } else { break; } } if (cur != start) m_heap[cur] = temp; } template T CaretSimpleHeapBase::pop(K* key) { CaretAssert(m_heap.size() > 0); T ret = m_heap[0].m_data; if (key != NULL) *key = m_heap[0].m_key; if (m_heap.size() > 1) { m_heap[0] = m_heap[m_heap.size() - 1]; m_heap.pop_back(); heapify_down(0); } else { m_heap.pop_back(); } return ret; } template void CaretSimpleHeapBase::push(const T& data, const K& key) { m_heap.push_back(DataStruct(key, data)); heapify_up(m_heap.size() - 1); } template void CaretSimpleHeapBase::reserve(int64_t expectedSize) { if (expectedSize <= 0) return; m_heap.reserve(expectedSize); } template T& CaretSimpleHeapBase::top(K* key) { CaretAssert(m_heap.size() > 0); if (key != NULL) *key = m_heap[0].m_key; return m_heap[0].m_data; } template bool CaretSimpleHeapBase::isEmpty() const { return m_heap.size() == 0; } template int64_t CaretSimpleHeapBase::size() const { return (int64_t)m_heap.size(); } template void CaretSimpleHeapBase::clear() { m_heap.clear(); } } #endif //__CARET_HEAP__ workbench-1.1.1/src/Common/CaretHttpManager.cxx000066400000000000000000000176371255417355300214600ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretHttpManager.h" #include "CaretAssert.h" #include "NetworkException.h" #include "CaretLogger.h" #include using namespace caret; using namespace std; CaretHttpManager* CaretHttpManager::m_singleton = NULL; CaretHttpManager::CaretHttpManager() : QObject() { connect(&m_netMgr, SIGNAL(sslErrors(QNetworkReply*, const QList & )), this, SLOT(handleSslErrors(QNetworkReply*, const QList & ))); } CaretHttpManager* CaretHttpManager::getHttpManager() { if (m_singleton == NULL) { m_singleton = new CaretHttpManager(); } return m_singleton; } void CaretHttpManager::deleteHttpManager() { if (m_singleton != NULL) { delete m_singleton; } } QNetworkAccessManager* CaretHttpManager::getQNetManager() { return &(getHttpManager()->m_netMgr); } void CaretHttpManager::httpRequest(const CaretHttpRequest &request, CaretHttpResponse &response) { QEventLoop myLoop; QNetworkRequest myRequest; myRequest.setSslConfiguration(QSslConfiguration::defaultConfiguration()); CaretHttpManager* myCaretMgr = getHttpManager(); AString myServerString = getServerString(request.m_url); bool have_auth = false; for (int i = 0; i < (int)myCaretMgr->m_authList.size(); ++i) { if (myServerString == myCaretMgr->m_authList[i].m_serverString) { QString unencoded = myCaretMgr->m_authList[i].m_user + ":" + myCaretMgr->m_authList[i].m_pass; myRequest.setRawHeader("Authorization", "Basic " + unencoded.toLocal8Bit().toBase64()); CaretLogInfo("Found auth for URL " + request.m_url); have_auth = true; break; } } if (!have_auth) { CaretLogInfo("NO AUTH FOUND for URL " + request.m_url); } QNetworkReply* myReply = NULL; QUrl myUrl = QUrl::fromUserInput(request.m_url); for (int32_t i = 0; i < (int32_t)request.m_queries.size(); ++i) { myUrl.addQueryItem(request.m_queries[i].first, request.m_queries[i].second); } QNetworkAccessManager* myQNetMgr = &(myCaretMgr->m_netMgr); bool first = true; QByteArray postData; switch (request.m_method) { case POST: for (int32_t i = 0; i < (int32_t)request.m_arguments.size(); ++i) { if (!first) postData += "&"; if (request.m_arguments[i].second == "") { postData += request.m_arguments[i].first; } else { postData += request.m_arguments[i].first + "=" + request.m_arguments[i].second; } first = false; } myRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); myRequest.setUrl(myUrl); CaretLogInfo("POST URL: " + myUrl.toString()); myReply = myQNetMgr->post(myRequest, postData); break; case GET: for (int32_t i = 0; i < (int32_t)request.m_arguments.size(); ++i) { myUrl.addQueryItem(request.m_arguments[i].first, request.m_arguments[i].second); } myRequest.setUrl(myUrl); CaretLogInfo("GET URL: " + myUrl.toString()); myReply = myQNetMgr->get(myRequest); break; case HEAD: for (int32_t i = 0; i < (int32_t)request.m_arguments.size(); ++i) { myUrl.addQueryItem(request.m_arguments[i].first, request.m_arguments[i].second); } myRequest.setUrl(myUrl); CaretLogInfo("HEAD URL: " + myUrl.toString()); myReply = myQNetMgr->head(myRequest); break; default: CaretAssertMessage(false, "Unrecognized http request method"); }; //QObject::connect(myReply, SIGNAL(sslErrors(QList)), &myLoop, SLOT(quit())); //QObject::connect(myQNetMgr, SIGNAL(authenticationRequired(QNetworkReply*,QAuthenticator*)), myCaretMgr, SLOT(authenticationCallback(QNetworkReply*,QAuthenticator*))); QObject::connect(myReply, SIGNAL(finished()), &myLoop, SLOT(quit()));//this is safe, because nothing will hand this thread events except queued through this thread's event mechanism /*QObject::connect(myReply, SIGNAL(sslErrors(const QList & )), CaretHttpManager::getHttpManager(), SLOT(handleSslErrors(const QList & )));//*/ myLoop.exec();//so, they can only be delivered after myLoop.exec() starts response.m_method = request.m_method; response.m_ok = false; response.m_responseCode = -1; response.m_responseCode = myReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); if (response.m_responseCode == 200) { response.m_ok = true; } QByteArray myBody = myReply->readAll(); int64_t mySize = myBody.size(); response.m_body.reserve(mySize + 1);//make room for the null terminator that will sometimes be added to the end response.m_body.resize(mySize);//but don't set size to include it for (int64_t i = 0; i < mySize; ++i) { response.m_body[i] = myBody[(int)i];//because QByteArray apparently just uses int - hope we won't need to transfer 2GB on a system that uses int32 for this } delete myReply; } void CaretHttpManager::setAuthentication(const AString& url, const AString& user, const AString& password) { CaretHttpManager* myCaretMgr = getHttpManager(); AString myServerString = getServerString(url); CaretLogInfo("Setting auth for server " + myServerString); for (int i = 0; i < (int)myCaretMgr->m_authList.size(); ++i) { if (myServerString == myCaretMgr->m_authList[i].m_serverString) {//for the moment, only allow one auth token per server in one instance of caret, so replace myCaretMgr->m_authList[i].m_user = user; myCaretMgr->m_authList[i].m_pass = password; return; } }//not found, need to add AuthEntry myAuth; myAuth.m_serverString = myServerString; myAuth.m_user = user; myAuth.m_pass = password; myCaretMgr->m_authList.push_back(myAuth); } void CaretHttpManager::handleSslErrors(QNetworkReply* reply, const QList &/*errors*/) { /*qDebug() << "handleSslErrors: "; foreach (QSslError e, errors) { qDebug() << "ssl error: " << e; }*/ reply->ignoreSslErrors(); } /*void CaretHttpManager::authenticationCallback(QNetworkReply* reply, QAuthenticator* authenticator) { if (reply->url() != QUrl::fromUserInput(m_authURL))//note: a redirect will cause this to break, this is ON PURPOSE so that auth isn't sent to a redirect { throw NetworkException("Authentication requested from different URL than authentication set for"); } authenticator->setUser(m_authUser); authenticator->setPassword(m_authPass); m_authURL = ""; m_authUser = ""; m_authPass = ""; }//*/ //currently not used, because callback doesn't work for the way xnat is set up, and doesn't fit well with synchronous requests AString CaretHttpManager::getServerString(const AString& url) { QUrl fullURL = QUrl::fromUserInput(url); AString ret = fullURL.toEncoded(QUrl::RemovePath | QUrl::StripTrailingSlash | QUrl::RemoveQuery | QUrl::RemoveUserInfo); return ret; } workbench-1.1.1/src/Common/CaretHttpManager.h000066400000000000000000000050671255417355300210770ustar00rootroot00000000000000#ifndef __CARET_HTTP_MANAGER_H__ #define __CARET_HTTP_MANAGER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "stdint.h" #include "AString.h" namespace caret { struct CaretHttpRequest; struct CaretHttpResponse; class CaretHttpManager : public QObject { Q_OBJECT struct AuthEntry { AString m_serverString; AString m_user; AString m_pass; }; QNetworkAccessManager m_netMgr; CaretHttpManager(); static CaretHttpManager* m_singleton; std::vector m_authList; static AString getServerString(const AString& url); public: enum Method { GET, POST, HEAD }; static CaretHttpManager* getHttpManager(); static void deleteHttpManager(); static void httpRequest(const CaretHttpRequest& request, CaretHttpResponse& response); static QNetworkAccessManager* getQNetManager(); static void setAuthentication(const AString& url, const AString& user, const AString& password); public slots: void handleSslErrors(QNetworkReply* reply, const QList &/*errors*/); //void authenticationCallback(QNetworkReply* reply, QAuthenticator* authenticator); }; struct CaretHttpResponse { CaretHttpManager::Method m_method; std::vector m_body; bool m_ok; int32_t m_responseCode; }; struct CaretHttpRequest { CaretHttpManager::Method m_method; AString m_url; std::vector > m_arguments, m_queries;//arguments go to post data if method is post, queries stay as queries }; } #endif // __CARET_HTTP_MANAGER_H__ workbench-1.1.1/src/Common/CaretLogger.cxx000066400000000000000000000017241255417355300204530ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ /* * Need so that static members are allocated */ #define __CARET_LOGGER_DEFINE__ #include "CaretLogger.h" #undef __CARET_LOGGER_DEFINE__ workbench-1.1.1/src/Common/CaretLogger.h000066400000000000000000000142461255417355300201030ustar00rootroot00000000000000#ifndef __CARET_LOGGER__H_ #define __CARET_LOGGER__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretFunctionName.h" #include "CaretObject.h" #include "Logger.h" namespace caret { /** * \brief The Caret Logger. * * This is the Caret Logger. Its single instance is created by * the LogManager. * * Do not use this class. Instead use the macro in this file * to send log messages at various levels. The macros are * designed so that the message is not created unless the * specific level (or below) is enabled. */ class CaretLogger { public: /** * Set the Caret logger * @param logger The logger's value. */ static void setLogger(Logger* logger) { CaretLogger::logger = logger; } /** * Get the Caret Logger. * @return The Caret logger */ inline static Logger* getLogger() { return CaretLogger::logger; } private: CaretLogger() { } ~CaretLogger() { } CaretLogger(const CaretLogger&); CaretLogger& operator=(const CaretLogger&); /** The caret logger. It is created by the LogManager. */ static Logger* logger; }; #ifdef __CARET_LOGGER_DEFINE__ Logger* CaretLogger::logger = NULL; #endif // __CARET_LOGGER_DEFINE__ } // namespace /** * \def CaretLogSevere * * Log a message at the SEVERE level. * Severe items typically prevent normal program execution. * @param TEXT * Text that is logged. */ #define CaretLogSevere(TEXT) \ ((caret::CaretLogger::getLogger()->isSevere()) \ ? caret::CaretLogger::getLogger()->log(caret::LogLevelEnum::SEVERE, __CARET_FUNCTION_NAME__, __FILE__, __LINE__, (TEXT)) \ : (void)0) /** * \def CaretLogWarning * * Log a message at the WARNING level. * Warning messages indicate potential problems. * @param TEXT * Text that is logged. */ #define CaretLogWarning(TEXT) \ ((caret::CaretLogger::getLogger()->isWarning()) \ ? caret::CaretLogger::getLogger()->log(caret::LogLevelEnum::WARNING, __CARET_FUNCTION_NAME__, __FILE__, __LINE__, (TEXT)) \ : (void)0) /** * \def CaretLogInfo * * Log a message at the INFO level. * Informational messages that may be helpful to end users. * @param TEXT * Text that is logged. */ #define CaretLogInfo(TEXT) \ ((caret::CaretLogger::getLogger()->isInfo()) \ ? caret::CaretLogger::getLogger()->log(caret::LogLevelEnum::INFO, __CARET_FUNCTION_NAME__, __FILE__, __LINE__, (TEXT)) \ : (void)0) /** * \def CaretLogConfig * * Log a message at the CONFIG level. * Configuration messages typically involve versions of * libraries, operating system, etc, and may help with * issues on specific configurations. * @param TEXT * Text that is logged. */ #define CaretLogConfig(TEXT) \ ((caret::CaretLogger::getLogger()->isConfig()) \ ? caret::CaretLogger::getLogger()->log(caret::LogLevelEnum::CONFIG, __CARET_FUNCTION_NAME__, __FILE__, __LINE__, (TEXT)) \ : (void)0) /** * \def CaretLogFine * * Log a message at the FINE level. * Fine messages are for developers such as minor, recoverable failures. * @param TEXT * Text that is logged. */ #define CaretLogFine(TEXT) \ ((caret::CaretLogger::getLogger()->isFine()) \ ? caret::CaretLogger::getLogger()->log(caret::LogLevelEnum::FINE, __CARET_FUNCTION_NAME__, __FILE__, __LINE__, (TEXT)) \ : (void)0) /** * \def CaretLogFiner * * Log a message at the FINER level. * Finer messages for for developers to provide detailed tracing messages. * Typically events and exceptions are logged at the this level. * @param TEXT * Text that is logged. */ #define CaretLogFiner(TEXT) \ ((caret::CaretLogger::getLogger()->isFiner()) \ ? caret::CaretLogger::getLogger()->log(caret::LogLevelEnum::FINER, __CARET_FUNCTION_NAME__, __FILE__, __LINE__, (TEXT)) \ : (void)0) /** * \def CaretLogFinest * * Log a message at the FINEST level. * Finest messages are for developers to provide highly detailed tracing messages. * @param TEXT * Text that is logged. */ #define CaretLogFinest(TEXT) \ ((caret::CaretLogger::getLogger()->isFinest()) \ ? caret::CaretLogger::getLogger()->log(caret::LogLevelEnum::FINEST, __CARET_FUNCTION_NAME__, __FILE__, __LINE__, (TEXT)) \ : (void)0) /** * \def CaretLogEntering * * Log a message indicating the function that one is in. * This is typically used when entering the function. * This message is logged at the FINER level. */ #define CaretLogEntering() \ ((caret::CaretLogger::getLogger()->isFiner()) \ ? caret::CaretLogger::getLogger()->entering(__CARET_FUNCTION_NAME__, __FILE__, __LINE__) \ : (void)0) /** * \def CaretLogExiting * * Log a message indicating the function that one is in. * This is typically used when entering the function. * This message is logged at the FINER level. */ #define CaretLogExiting() \ ((caret::CaretLogger::getLogger()->isFiner()) \ ? caret::CaretLogger::getLogger()->exiting(__CARET_FUNCTION_NAME__, __FILE__, __LINE__) \ : (void)0) /** * \def CaretLogThrowing * * Log a message at the FINER level for an exception class that is * derived from CaretException. * @param CARET_EXCEPTION * CaretException that is logged. */ #define CaretLogThrowing(CARET_EXCEPTION) \ ((caret::CaretLogger::getLogger()->isFiner()) \ ? caret::CaretLogger::getLogger()->throwingCaretException(__CARET_FUNCTION_NAME__, __FILE__, __LINE__, CARET_EXCEPTION) \ : (void)0) #endif //__CARET_LOGGER__H_ workbench-1.1.1/src/Common/CaretMathExpression.cxx000066400000000000000000001110461255417355300222040ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretAssert.h" #include "CaretException.h" #include "CaretLogger.h" #include "CaretMathExpression.h" #include using namespace caret; using namespace std; CaretMathExpression::CaretMathExpression(const AString& expression) { m_input = expression; m_position = 0; m_end = m_input.size(); m_root = orExpr(); if (skipWhitespace())//if we DON'T hit the end of input, there are extra characters - like if the input is "x + 1 $blah" { throw CaretException("extra characters on end of expression input: '" + m_input.mid(m_position) + "'"); } CaretLogFiner("parsed '" + expression + "' as '" + toString() + "'"); } double CaretMathExpression::evaluate(const vector& variableValues) const { CaretAssert(variableValues.size() == m_varNames.size()); return m_root->eval(variableValues); } vector CaretMathExpression::getVarNames() const { vector ret(m_varNames.size()); for (map::const_iterator iter = m_varNames.begin(); iter != m_varNames.end(); ++iter) { CaretAssert(iter->second < (int)ret.size()); ret[iter->second] = iter->first; } return ret; } AString CaretMathExpression::getExpressionHelpInfo() { AString ret = AString("Expressions consist of constants, variables, operators, parentheses, and functions, in infix notation, such as 'exp(-x + 3) * scale'. ") + "Variables are strings of any length, using the characters a-z, A-Z, 0-9, and _, but may not take the name of a named constant. " + "Currently, there is only one named constant, PI. " + "The operators are +, -, *, /, ^, >, <, >=, <=, ==, !=, !, &&, ||. " + "These behave as in C, except that ^ is exponentiation, i.e. pow(x, y), and takes higher precedence than other binary operators (also, '-3^-4^-5' means '-(3^(-(4^-5)))'). " + "The <=, >=, ==, and != operators are given a small amount of wiggle room, equal to one millionth of the smaller of the absolute values of the values being compared.\n\n" + "Comparison and logical operators return 0 or 1, you can do masking with expressions like 'x * (mask > 0)'. " + "For all logical operators, an input is considered true iff it is greater than 0. " + "The expression '0 < x < 5' is not syntactically wrong, but it will NOT do what is desired, because it is evaluated left to right, i.e. '((0 < x) < 5)', " + "which will always return 1, as both possible results of a comparison are less than 5. A warning is generated if an expression of this type is detected. " + "Use something like 'x > 0 && x < 5' to get the desired behavior.\n\n" + "Whitespace between elements is ignored, ' sin ( 2 * x ) ' is equivalent to 'sin(2*x)', but 's in(2*x)' is an error. " + "Implied multiplication is not allowed, the expression '2x' will be parsed as a variable. " + "Parentheses are (), do not use [] or {}. " + "Functions require parentheses, the expression 'sin x' is an error.\n\n" + "The following functions are supported:\n\n"; vector myFuncs; MathFunctionEnum::getAllEnums(myFuncs); for (int i = 0; i < (int)myFuncs.size(); ++i) { ret += " " + MathFunctionEnum::toName(myFuncs[i]) + ": " + MathFunctionEnum::toExplanation(myFuncs[i]) + "\n"; } return ret; } double CaretMathExpression::MathNode::eval(const vector& values) const { double ret = 0.0; switch (m_type) { case OR: { int end = (int)m_arguments.size(); CaretAssert(end > 1); bool temp = (m_arguments[0]->eval(values) > 0.0); for (int i = 1; i < end; ++i) { if (temp) break;//lazy evaluation temp = (m_arguments[i]->eval(values) > 0.0);//don't need to actually do ||, we already know all the ones to the left are false } ret = temp ? 1.0 : 0.0; break; } case AND: { int end = (int)m_arguments.size(); CaretAssert(end > 1); bool temp = (m_arguments[0]->eval(values) > 0.0); for (int i = 1; i < end; ++i) { if (!temp) break;//lazy evaluation temp = (m_arguments[i]->eval(values) > 0.0);//don't need to actually do &&, we already know all the ones to the left are true } ret = temp ? 1.0 : 0.0; break; } case EQUAL: { int end = (int)m_arguments.size(); CaretAssert(end > 1); CaretAssert((int)m_invert.size() == end); ret = m_arguments[0]->eval(values); for (int i = 1; i < end; ++i) { double temp = m_arguments[i]->eval(values); float adjust = min(abs(ret), abs(temp)) / 1000000;//because == doesn't always work as expected, include a fudge factor based on the approximate precision of float bool equal = (ret >= m_arguments[i]->eval(values) - adjust) && (ret <= m_arguments[i]->eval(values) + adjust); if (m_invert[i]) { ret = equal ? 0.0 : 1.0; } else { ret = equal ? 1.0 : 0.0; } } break; } case GREATERLESS: { int end = (int)m_arguments.size();//yes, you can chain < and >, but it will produce a parsing warning, as it evaluates left to right CaretAssert(end > 1); CaretAssert((int)m_invert.size() == end); CaretAssert((int)m_inclusive.size() == end); ret = m_arguments[0]->eval(values); for (int i = 1; i < end; ++i) { double temp = m_arguments[i]->eval(values); if (m_inclusive[i]) { float adjust = min(abs(ret), abs(temp)) / 1000000;//because == doesn't always work as expected, include a fudge factor based on the approximate precision of float if (m_invert[i]) { ret = (ret <= temp + adjust ? 1.0 : 0.0);//don't trust booleans to cast to 0 and 1, just because } else { ret = (ret >= temp - adjust ? 1.0 : 0.0); } } else { if (m_invert[i]) { ret = (ret < temp ? 1.0 : 0.0); } else { ret = (ret > temp ? 1.0 : 0.0); } } } break; } case ADDSUB: { int end = (int)m_arguments.size(); CaretAssert(end > 1); CaretAssert((int)m_invert.size() == end); ret = m_arguments[0]->eval(values); for (int i = 1; i < end; ++i) { if (m_invert[i]) { ret -= m_arguments[i]->eval(values); } else { ret += m_arguments[i]->eval(values); } } break; } case MULTDIV: { int end = (int)m_arguments.size(); CaretAssert(end > 1); CaretAssert((int)m_invert.size() == end); ret = m_arguments[0]->eval(values); for (int i = 1; i < end; ++i) { if (m_invert[i]) { ret /= m_arguments[i]->eval(values); } else { ret *= m_arguments[i]->eval(values); } } break; } case NOT: CaretAssert(m_arguments.size() == 1); ret = ((m_arguments[0]->eval(values)) > 0.0) ? 0.0 : 1.0; break; case NEGATE: CaretAssert(m_arguments.size() == 1); ret = -(m_arguments[0]->eval(values)); break; case POW: { CaretAssert(m_arguments.size() == 2);//a^b^c always gets parsed into 2 power nodes, because -a^-b is -(a^(-b)) ret = pow(m_arguments[0]->eval(values), m_arguments[1]->eval(values)); break; } case FUNC: { switch (m_function)//this could be (partly) moved into MathFunctionEnum, but it wouldn't strictly be an enum class then { case MathFunctionEnum::SIN: CaretAssert(m_arguments.size() == 1); ret = sin(m_arguments[0]->eval(values)); break; case MathFunctionEnum::COS: CaretAssert(m_arguments.size() == 1); ret = cos(m_arguments[0]->eval(values)); break; case MathFunctionEnum::TAN: CaretAssert(m_arguments.size() == 1); ret = tan(m_arguments[0]->eval(values)); break; case MathFunctionEnum::ASIN: CaretAssert(m_arguments.size() == 1); ret = asin(m_arguments[0]->eval(values)); break; case MathFunctionEnum::ACOS: CaretAssert(m_arguments.size() == 1); ret = acos(m_arguments[0]->eval(values)); break; case MathFunctionEnum::ATAN: CaretAssert(m_arguments.size() == 1); ret = atan(m_arguments[0]->eval(values)); break; case MathFunctionEnum::SINH: CaretAssert(m_arguments.size() == 1); ret = sinh(m_arguments[0]->eval(values)); break; case MathFunctionEnum::COSH: CaretAssert(m_arguments.size() == 1); ret = cosh(m_arguments[0]->eval(values)); break; case MathFunctionEnum::TANH: CaretAssert(m_arguments.size() == 1); ret = tanh(m_arguments[0]->eval(values)); break; case MathFunctionEnum::ASINH: { CaretAssert(m_arguments.size() == 1); //ret = asinh(m_arguments[0].eval(values));//will work, and be preferred, when we use c++11, but doesn't work on windows with previous standard double arg = m_arguments[0]->eval(values); if (arg > 0) { ret = log(arg + sqrt(arg * arg + 1)); } else { ret = -log(-arg + sqrt(arg * arg + 1));//special case negative for stability in large negatives } break; } case MathFunctionEnum::ACOSH: { CaretAssert(m_arguments.size() == 1); //ret = acosh(m_arguments[0].eval(values)); double arg = m_arguments[0]->eval(values); ret = log(arg + sqrt(arg * arg - 1)); break; } case MathFunctionEnum::ATANH: { CaretAssert(m_arguments.size() == 1); //ret = atanh(m_arguments[0].eval(values)); double arg = m_arguments[0]->eval(values); ret = 0.5 * log((1 + arg) / (1 - arg)); break; } case MathFunctionEnum::LN: CaretAssert(m_arguments.size() == 1); ret = log(m_arguments[0]->eval(values)); break; case MathFunctionEnum::EXP: CaretAssert(m_arguments.size() == 1); ret = exp(m_arguments[0]->eval(values)); break; case MathFunctionEnum::LOG: CaretAssert(m_arguments.size() == 1); ret = log10(m_arguments[0]->eval(values)); break; case MathFunctionEnum::SQRT: CaretAssert(m_arguments.size() == 1); ret = sqrt(m_arguments[0]->eval(values)); break; case MathFunctionEnum::ABS: CaretAssert(m_arguments.size() == 1); ret = abs(m_arguments[0]->eval(values)); break; case MathFunctionEnum::FLOOR: CaretAssert(m_arguments.size() == 1); ret = floor(m_arguments[0]->eval(values)); break; case MathFunctionEnum::ROUND: { CaretAssert(m_arguments.size() == 1); double temp = m_arguments[0]->eval(values);//windows doesn't use c99 when compiling c++ earlier than c++11, so implement manually if (temp > 0.0) { ret = floor(temp + 0.5); } else { ret = ceil(temp - 0.5); } break; } case MathFunctionEnum::CEIL: CaretAssert(m_arguments.size() == 1); ret = ceil(m_arguments[0]->eval(values)); break; case MathFunctionEnum::ATAN2: CaretAssert(m_arguments.size() == 2); ret = atan2(m_arguments[0]->eval(values), m_arguments[1]->eval(values)); break; case MathFunctionEnum::MIN: { CaretAssert(m_arguments.size() == 2); ret = m_arguments[0]->eval(values); double other = m_arguments[1]->eval(values); if (ret > other) ret = other; break; } case MathFunctionEnum::MAX: { CaretAssert(m_arguments.size() == 2); ret = m_arguments[0]->eval(values); double other = m_arguments[1]->eval(values); if (ret < other) ret = other; break; } case MathFunctionEnum::MOD: { CaretAssert(m_arguments.size() == 2); double second = m_arguments[1]->eval(values); if (second == 0.0) { ret = 0.0; } else { double first = m_arguments[0]->eval(values); ret = first - second * floor(first / second); } break; } case MathFunctionEnum::CLAMP: { CaretAssert(m_arguments.size() == 3); ret = m_arguments[0]->eval(values); double low = m_arguments[1]->eval(values); double high = m_arguments[2]->eval(values); if (ret < low) { ret = low; } if (ret > high) { ret = high; } break; } case INVALID: CaretAssertMessage(0, "MathNode is type FUNC but INVALID function"); throw CaretException("parsing problem in CaretMathExpression"); } break; } case VAR: CaretAssertVectorIndex(values, m_varIndex); ret = values[m_varIndex]; break; case CONST: ret = m_constVal; break; case INVALID: CaretAssertMessage(0, "parsing left INVALID MathNode"); throw CaretException("parsing problem in CaretMathExpression"); } return ret; } AString CaretMathExpression::MathNode::toString(const std::vector& varNames) const { AString ret = ""; bool addParens = true; switch (m_type) { case OR: { int end = (int)m_arguments.size(); CaretAssert(end > 1); ret = m_arguments[0]->toString(varNames); for (int i = 1; i < end; ++i) { ret += "||" + m_arguments[i]->toString(varNames); } break; } case AND: { int end = (int)m_arguments.size(); CaretAssert(end > 1); ret = m_arguments[0]->toString(varNames); for (int i = 1; i < end; ++i) { ret += "&&" + m_arguments[i]->toString(varNames); } break; } case EQUAL: { int end = (int)m_arguments.size(); CaretAssert(end > 1); CaretAssert((int)m_invert.size() == end); ret = m_arguments[0]->toString(varNames); for (int i = 1; i < end; ++i) { if (m_invert[i]) { ret += "!=" + m_arguments[i]->toString(varNames); } else { ret += "==" + m_arguments[i]->toString(varNames); } } break; } case GREATERLESS: { int end = (int)m_arguments.size(); CaretAssert(end > 1); CaretAssert((int)m_invert.size() == end); CaretAssert((int)m_inclusive.size() == end); ret = m_arguments[0]->toString(varNames); for (int i = 1; i < end; ++i) { if (m_inclusive[i]) { if (m_invert[i]) { ret += "<=" + m_arguments[i]->toString(varNames); } else { ret += ">=" + m_arguments[i]->toString(varNames); } } else { if (m_invert[i]) { ret += "<" + m_arguments[i]->toString(varNames); } else { ret += ">" + m_arguments[i]->toString(varNames); } } } break; } case ADDSUB: { int end = (int)m_arguments.size(); CaretAssert(end > 1); CaretAssert((int)m_invert.size() == end); ret = m_arguments[0]->toString(varNames); for (int i = 1; i < end; ++i) { if (m_invert[i]) { ret += "-" + m_arguments[i]->toString(varNames); } else { ret += "+" + m_arguments[i]->toString(varNames); } } break; } case MULTDIV: { int end = (int)m_arguments.size(); CaretAssert(end > 1); CaretAssert((int)m_invert.size() == end); ret = m_arguments[0]->toString(varNames); for (int i = 1; i < end; ++i) { if (m_invert[i]) { ret += "/" + m_arguments[i]->toString(varNames); } else { ret += "*" + m_arguments[i]->toString(varNames); } } break; } case NOT: CaretAssert(m_arguments.size() == 1); ret = "!" + m_arguments[0]->toString(varNames); break; case NEGATE: CaretAssert(m_arguments.size() == 1); ret = "-" + m_arguments[0]->toString(varNames); break; case POW: CaretAssert(m_arguments.size() == 2); ret = m_arguments[0]->toString(varNames) + "^" + m_arguments[1]->toString(varNames); break; case FUNC: { addParens = false; int end = (int)m_arguments.size(); ret = MathFunctionEnum::toName(m_function) + "("; if (end > 0) ret += m_arguments[0]->toString(varNames);//allow for functions that take 0 arguments for (int i = 1; i < end; ++i) { ret += "," + m_arguments[i]->toString(varNames); } ret += ")"; break; } case VAR: addParens = false; CaretAssertVectorIndex(varNames, m_varIndex); ret = varNames[m_varIndex]; break; case CONST: addParens = false; if (m_constName != "") { ret = m_constName; } else { ret = AString::number(m_constVal, 'g', 15); } break; case INVALID: CaretAssertMessage(0, "toString called on invalid MathNode"); throw CaretException("parsing problem in CaretMathExpression"); } if (addParens) ret = "(" + ret + ")";//parenthesize almost everything return ret; } AString CaretMathExpression::toString() const { return m_root->toString(getVarNames()); } bool CaretMathExpression::skipWhitespace()//return false if end of input { while (m_position < m_end && m_input[m_position].isSpace()) ++m_position; return m_position < m_end; } bool CaretMathExpression::accept(const char& c) { if (!skipWhitespace()) return false;//end of input if (m_input[m_position] == c) { ++m_position; return true; } return false; } void CaretMathExpression::expect(const char& c, const int& exprStart) { CaretAssert(exprStart < m_end); if (!skipWhitespace()) throw CaretException("unexpected end of input while parsing '" + m_input.mid(exprStart) + "', expected '" + AString(c) + "'"); if (m_input[m_position] != c) throw CaretException("expected '" + AString(c) + "', got '" + m_input[m_position] + "' while parsing '" + m_input.mid(exprStart, m_position - exprStart + 1) + "'"); ++m_position; } CaretPointer CaretMathExpression::orExpr() { int start = m_position;//for error reporting CaretPointer temp = andExpr(); if (accept('|')) { CaretPointer ret(new MathNode(MathNode::OR)); ret->m_arguments.push_back(temp); do { expect('|', start); ret->m_arguments.push_back(andExpr()); } while (accept('|')); return ret; } return temp; } CaretPointer CaretMathExpression::andExpr() { int start = m_position;//for error reporting CaretPointer temp = equalExpr(); if (accept('&')) { CaretPointer ret(new MathNode(MathNode::AND)); ret->m_arguments.push_back(temp); do { expect('&', start); ret->m_arguments.push_back(equalExpr()); } while (accept('&')); return ret; } return temp; } CaretPointer CaretMathExpression::equalExpr() { int start = m_position;//for error reporting CaretPointer temp = greaterLessExpr(); bool good = false, invert;//means not equal if (accept('=')) { good = true; invert = false; } else if (accept('!')) { good = true; invert = true; } if (good) { CaretPointer ret(new MathNode(MathNode::EQUAL)); ret->m_arguments.push_back(temp); ret->m_invert.push_back(false);//first one isn't used, they apply to the operator to the left of the argument do { expect('=', start); ret->m_arguments.push_back(greaterLessExpr()); ret->m_invert.push_back(invert); good = false; if (accept('=')) { good = true; invert = false; } else if (accept('!')) { good = true; invert = true; } } while (good); if (ret->m_arguments.size() > 2) { CaretLogWarning("expression '" + m_input.mid(start, m_position - start) + "' will do equality comparison left to right"); } return ret; } return temp; } CaretPointer CaretMathExpression::greaterLessExpr() { int start = m_position;//for error reporting CaretPointer temp = addSubExpr(); bool good = false, invert, inclusive = false;//invert means less than if (accept('>')) { good = true; invert = false; } else if (accept('<')) { good = true; invert = true; } if (good && accept('=')) inclusive = true; if (good) { CaretPointer ret(new MathNode(MathNode::GREATERLESS)); ret->m_arguments.push_back(temp); ret->m_invert.push_back(false);//first one isn't used, they apply to the operator to the left of the argument ret->m_inclusive.push_back(false);//ditto do { ret->m_arguments.push_back(addSubExpr()); ret->m_invert.push_back(invert); ret->m_inclusive.push_back(inclusive); good = false; inclusive = false; if (accept('>')) { good = true; invert = false; } else if (accept('<')) { good = true; invert = true; } if (good && accept('=')) inclusive = true; } while (good); if (ret->m_arguments.size() > 2) { CaretLogWarning("expression '" + m_input.mid(start, m_position - start) + "' will do inequality comparison left to right"); } return ret; } return temp; } CaretPointer CaretMathExpression::addSubExpr() { CaretPointer temp = multDivExpr(); bool good = false, invert;//means subtract if (accept('+')) { good = true; invert = false; } else if (accept('-')) { good = true; invert = true; } if (good) { CaretPointer ret(new MathNode(MathNode::ADDSUB)); ret->m_arguments.push_back(temp); ret->m_invert.push_back(false);//first one isn't used, they apply to the operator to the left of the argument do { ret->m_arguments.push_back(multDivExpr()); ret->m_invert.push_back(invert); good = false; if (accept('+')) { good = true; invert = false; } else if (accept('-')) { good = true; invert = true; } } while (good); return ret; } return temp; } CaretPointer CaretMathExpression::multDivExpr() { CaretPointer temp = unaryExpr(); bool good = false, invert;//means divide if (accept('*')) { good = true; invert = false; } else if (accept('/')) { good = true; invert = true; } if (good) { CaretPointer ret(new MathNode(MathNode::MULTDIV)); ret->m_arguments.push_back(temp); ret->m_invert.push_back(false);//first one isn't used, they apply to the operator to the left of the argument do { ret->m_arguments.push_back(unaryExpr()); ret->m_invert.push_back(invert); good = false; if (accept('*')) { good = true; invert = false; } else if (accept('/')) { good = true; invert = true; } } while (good); return ret; } return temp; } CaretPointer CaretMathExpression::unaryExpr() { CaretPointer ret; if (accept('-')) { ret.grabNew(new MathNode(MathNode::NEGATE)); ret->m_arguments.push_back(unaryExpr());//allow -----x and other silliness, because it is more complicated to try to prevent it return ret; } else if (accept('!')) { ret.grabNew(new MathNode(MathNode::NOT)); ret->m_arguments.push_back(unaryExpr());//similarly, !!!!x...and !!--!---!!!!-!!-!!!1, etc return ret; } else if (accept('+')) {//unary plus does nothing return unaryExpr();//this is not infinitely recursive, accept() removes a char of input } return powExpr(); } CaretPointer CaretMathExpression::powExpr() { CaretPointer temp = funcExpr(); if (accept('^')) { CaretPointer ret(new MathNode(MathNode::POW)); ret->m_arguments.push_back(temp); ret->m_arguments.push_back(unaryExpr());//because -x^y is -(x^y), but x^-y is x^(-y) return ret; } return temp; } CaretPointer CaretMathExpression::funcExpr() { int start = m_position; if (accept('(')) { CaretPointer ret = orExpr(); expect(')', start); return ret; }//and now a non-predictive bit - checking for valid function name int funcnameEnd = m_input.indexOf('(', m_position); if (funcnameEnd != -1) { bool ok = false; MathFunctionEnum::Enum myfunc = MathFunctionEnum::fromName(m_input.mid(m_position, funcnameEnd - m_position).trimmed(), &ok); if (ok) { int numArgs = -1; switch(myfunc) { case MathFunctionEnum::SIN: case MathFunctionEnum::COS: case MathFunctionEnum::TAN: case MathFunctionEnum::ASIN: case MathFunctionEnum::ACOS: case MathFunctionEnum::ATAN: case MathFunctionEnum::SINH: case MathFunctionEnum::COSH: case MathFunctionEnum::TANH: case MathFunctionEnum::ASINH: case MathFunctionEnum::ACOSH: case MathFunctionEnum::ATANH: case MathFunctionEnum::LN: case MathFunctionEnum::EXP: case MathFunctionEnum::LOG: case MathFunctionEnum::SQRT: case MathFunctionEnum::ABS: case MathFunctionEnum::FLOOR: case MathFunctionEnum::ROUND: case MathFunctionEnum::CEIL: numArgs = 1; break; case MathFunctionEnum::ATAN2: case MathFunctionEnum::MIN: case MathFunctionEnum::MAX: case MathFunctionEnum::MOD: numArgs = 2; break; case MathFunctionEnum::CLAMP: numArgs = 3; break; case MathFunctionEnum::INVALID://this should not happen CaretAssert(false); } CaretPointer ret(new MathNode(MathNode::FUNC)); ret->m_function = myfunc; m_position = funcnameEnd + 1;//skip the ( of the function if (!accept(')'))//zero arguments case { ret->m_arguments.push_back(orExpr()); while (accept(',')) { ret->m_arguments.push_back(orExpr()); } expect(')', start); } if ((int)ret->m_arguments.size() != numArgs) throw CaretException("function '" + MathFunctionEnum::toName(myfunc) + "' takes " + AString::number(numArgs) + " argument(s), but was given " + AString::number(ret->m_arguments.size()) + ": '" + m_input.mid(start, m_position - start) + "'"); return ret; } } return terminal();//if it isn't parenthesis or a function, the only thing left is terminal } CaretPointer CaretMathExpression::terminal() { CaretPointer ret = tryLiteral();//try literal first, our relaxed rules for variable names overlap with integers if (ret != NULL) return ret; if (!skipWhitespace()) throw CaretException("unexpected end of input, expected operand");//now, try named constant/variable int varEnd = m_position; bool onlydigits = true; while (varEnd < m_end) { QChar thisChar = m_input[varEnd];//allow letters, numbers, and underscore if (!thisChar.isLetterOrNumber() && thisChar != '_') { break; } if (!thisChar.isDigit()) onlydigits = false;//if there are only digits, this is not a variable, tryLiteral failed on something that was intended to be a literal ++varEnd; } double constVal = 0.0f; AString identifier = m_input.mid(m_position, varEnd - m_position); if (identifier.size() == 0)//hit an invalid character or end of input before getting any valid characters {//figure out why we stopped, give appropriate error if (varEnd >= m_end) throw CaretException("unexpected end of input, expected operand"); throw CaretException("unexpected character '" + AString(m_input[varEnd]) + "' at beginning of operand");//if it fails for all prefix unary, parens, function, literal, variable, then this character can never start an operand } if (onlydigits) throw CaretException("error parsing literal value beginning with '" + identifier + "'"); m_position = varEnd; if (getNamedConstant(identifier, constVal)) { ret.grabNew(new MathNode(MathNode::CONST)); ret->m_constName = identifier; ret->m_constVal = constVal; return ret; } ret.grabNew(new MathNode(MathNode::VAR)); map::const_iterator iter = m_varNames.find(identifier); if (iter == m_varNames.end()) { int index = (int)m_varNames.size(); m_varNames.insert(make_pair(identifier, index)); ret->m_varIndex = index; } else { ret->m_varIndex = iter->second; } return ret; } CaretPointer CaretMathExpression::tryLiteral() { if (!skipWhitespace()) throw CaretException("unexpected end of input, expected operand");//now, try literal int litstart = m_position, litend = m_position; if (m_input[litend] == '-' || m_input[litend] == '+') ++litend;//allow literals to start with - or + : however, - will not happen, due to unary - (which does that so that -2^-2 works as -(2^(-2)) bool havedot = false, haveexp = false; while (litend < m_end) { QChar mychar = m_input[litend]; if (mychar.isDigit() || mychar == '.')//manually test for multiple '.' for better error messages, it can never be correct as a non-literal { ++litend; if (mychar == '.') { if (havedot) throw CaretException("multiple '.' characters in literal: '" + m_input.mid(litstart, litend - litstart) + "'"); havedot = true; } continue; } if (mychar == 'e' || mychar == 'E') {//scientific notation, don't throw on multiple 'e', because '2e-3e' is subtraction of two variables if (haveexp) return CaretPointer();//but do stop early, as it won't be a literal haveexp = true; ++litend; if (litend >= m_end) break; if (m_input[litend] == '-' || m_input[litend] == '+') ++litend;//allow + or - after e continue; }//spaces are not allowed inside literals break;//yes, break at the end of a while loop - anything we don't recognize as valid ends the loop } bool ok = false; double litVal = m_input.mid(litstart, litend - litstart).toDouble(&ok); if (!ok) return CaretPointer(); m_position = litend; CaretPointer ret(new MathNode(MathNode::CONST)); ret->m_constVal = litVal; return ret; } bool CaretMathExpression::getNamedConstant(const AString& name, double& valueOut) { if (name == "PI") { valueOut = 3.1415926535897932;//double can do about 16 decimal digits, give 17 for rounding return true; } return false;//presumably we will have more named constants later } workbench-1.1.1/src/Common/CaretMathExpression.h000066400000000000000000000066621255417355300216400ustar00rootroot00000000000000#ifndef __CARET_MATH_EXPRESSION_H__ #define __CARET_MATH_EXPRESSION_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AString.h" #include "CaretPointer.h" #include "MathFunctionEnum.h" #include #include namespace caret { class CaretMathExpression { struct MathNode { enum ExprType { INVALID, OR, AND, EQUAL, GREATERLESS, ADDSUB, MULTDIV, NOT, NEGATE, POW, FUNC, VAR, CONST }; ExprType m_type; MathFunctionEnum::Enum m_function; AString m_constName; double m_constVal; int m_varIndex; std::vector m_invert;//whether it is subtract rather than add, or divide instead of multiply, or less instead of greater std::vector m_inclusive;//whether greater/less includes equals std::vector > m_arguments; MathNode() { m_type = INVALID; m_function = MathFunctionEnum::INVALID; } MathNode(const ExprType& type) { m_type = type; m_function = MathFunctionEnum::INVALID; } double eval(const std::vector& values) const; AString toString(const std::vector& varNames) const; }; std::map m_varNames; AString m_input; int m_position, m_end; CaretPointer m_root; bool skipWhitespace(); bool accept(const char& c); void expect(const char& c, const int& exprStart); CaretPointer orExpr();//hopefully we have enough stack space that we won't overflow without a hundred or so levels of parenthesis/functions/exponents CaretPointer andExpr(); CaretPointer equalExpr(); CaretPointer greaterLessExpr(); CaretPointer addSubExpr(); CaretPointer multDivExpr(); CaretPointer unaryExpr();//accepts --x, !!1, !--!-!!PI, etc CaretPointer powExpr(); CaretPointer funcExpr();//also parenthesis CaretPointer terminal();//literal, const, variable CaretPointer tryLiteral();//NOTE: does not throw except on early end of input, returns NULL on failure public: static AString getExpressionHelpInfo(); static bool getNamedConstant(const AString& name, double& valueOut); CaretMathExpression(const AString& expression); double evaluate(const std::vector& variableValues) const; std::vector getVarNames() const; AString toString() const;//the expression, with a lot of parentheses added }; } // namespace #endif //__CARET_MATH_EXPRESSION_H__ workbench-1.1.1/src/Common/CaretMutex.h000066400000000000000000000046351255417355300177670ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #ifndef __CARET_MUTEX_H__ #define __CARET_MUTEX_H__ #include "CaretOMP.h" //omp mutexes are faster than QMutex, especially without contention #ifdef CARET_OMP namespace caret { class CaretMutex { omp_lock_t m_lock; public: CaretMutex(const CaretMutex&) { omp_init_lock(&m_lock); };//allow copy, assign, but make them do nothing other than default construct CaretMutex& operator=(const CaretMutex&) { return *this; }; CaretMutex() { omp_init_lock(&m_lock); } ~CaretMutex() { omp_destroy_lock(&m_lock); } friend class CaretMutexLocker; }; class CaretMutexLocker { CaretMutex* m_mutex; CaretMutexLocker();//disallow default construction, assign CaretMutexLocker& operator=(const CaretMutexLocker& rhs); public: CaretMutexLocker(CaretMutex* mutex) { m_mutex = mutex; omp_set_lock(&(m_mutex->m_lock)); } ~CaretMutexLocker() { omp_unset_lock(&(m_mutex->m_lock)); } }; } #else //if we don't have openmp, fall back to QMutex #include namespace caret { class CaretMutex : public QMutex { public: CaretMutex(RecursionMode mode = NonRecursive) : QMutex(mode) { } CaretMutex(const CaretMutex&) : QMutex() { };//allow copy, assign, but make them do nothing other than default construct CaretMutex& operator=(const CaretMutex&) { return *this; }; }; class CaretMutexLocker : public QMutexLocker { public: CaretMutexLocker(CaretMutex* theMutex) : QMutexLocker(theMutex) { } }; } #endif #endif //__CARET_MUTEX_H__ workbench-1.1.1/src/Common/CaretOMP.h000066400000000000000000000042621255417355300173140ustar00rootroot00000000000000#ifndef __CARET_OMP_H__ #define __CARET_OMP_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ ///this include file is for anything specific to how we use openmp in caret, and also for convenience ///for instance, you don't need #ifdef guards around including this file, unlike omp.h #ifdef _OPENMP #include "omp.h" ///another define to use in guards, might be easier to remember #define CARET_OMP #endif //_OPENMP ///some defines for #pragma omp, in case we want to add default options (for workarounds or global options) ///if we never need to use these for workarounds or global options, so be it ///NOTE: neither "#pragma" nor "omp" can be put into a define and have it work correctly, so they can't be made that much more friendly ///defined regardless so the preprocessor and compiler aren't any more confused if _OPENMP isn't defined (will complain about ignoring pragma if used without guards) ///DO NOT add scheduling to this list, scheduling choice needs to be able to be changed #define CARET_PAR_OPTIONS #define CARET_FOR_OPTIONS #define CARET_SINGLE_OPTIONS ///and defines to combine them with the pragmas they are intended for ///use them as "#pragma omp CARET_PARFOR [other options]" #define CARET_PAR parallel CARET_PAR_OPTIONS #define CARET_FOR for CARET_FOR_OPTIONS #define CARET_PARFOR parallel for CARET_PAR_OPTIONS CARET_FOR_OPTIONS #define CARET_SINGLE single CARET_SINGLE_OPTIONS #endif //__CARET_OMP_H__ workbench-1.1.1/src/Common/CaretObject.cxx000066400000000000000000000103121255417355300204330ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #define __CARET_OBJECT_DECLARE_H__ #include "CaretObject.h" #undef __CARET_OBJECT_DECLARE_H__ #include "SystemUtilities.h" using namespace caret; /** * Constructor. * */ CaretObject::CaretObject() { this->initializeMembersCaretObject(); } /** * Copy constructor. * */ CaretObject::CaretObject(const CaretObject& co) { this->initializeMembersCaretObject(); this->copyHelper(co); } /** * Destructor */ CaretObject::~CaretObject() { #ifndef NDEBUG /* * Erase returns the number of objects deleted. * If zero, then the object has already been deleted. */ uint64_t numDeleted = CaretObject::allocatedObjects.erase(this); if (numDeleted <= 0) { std::cerr << "Destructor for a CaretObject called but the object is not allocated " << "and this implies that the object has already been deleted."; } #endif } CaretObject& CaretObject::operator=(const CaretObject& co) { if (this != &co) { this->copyHelper(co); } return *this; } void CaretObject::initializeMembersCaretObject() { #ifndef NDEBUG SystemBacktrace myBacktrace; SystemUtilities::getBackTrace(myBacktrace); CaretObject::allocatedObjects.insert( std::make_pair(this, myBacktrace)); /*CaretObject::allocatedObjects.insert( std::make_pair(this, SystemUtilities::getBackTrace()));//*/ #endif } void CaretObject::copyHelper(const CaretObject&) { } /** * Get String representation of caret object. * @return String containing caret object. * */ AString CaretObject::toString() const { AString s = "CaretObjectType=" + this->className(); return s; } /** * Get the class name of this object. * @return * Class name of the object. */ AString CaretObject::className() const { AString name(typeid(*this).name()); return name; } /** * Print a list of CaretObjects that were not deleted. */ void CaretObject::printListOfObjectsNotDeleted(const bool showCallStack) { #ifndef NDEBUG int count = 0; if (CaretObject::allocatedObjects.empty() == false) { std::cout << "These Caret Objects were not deleted:" << std::endl; for (CARET_OBJECT_TRACKER_MAP_ITERATOR iter = CaretObject::allocatedObjects.begin(); iter != allocatedObjects.end(); iter++) { const unsigned long objectAddress = (long long)iter->first; //const CaretObject* caretObject = iter->first; const CaretObjectInfo& caretObjectInfo = iter->second; // below will crash if item has been deleted //std::cout << caretObject->toString().toStdString() << std::endl; std::cout << "Address (hex)=" << std::hex << objectAddress << std::endl; if (showCallStack) { std::cout << caretObjectInfo.m_backtrace.toSymbolString() << std::endl; } std::cout << std::endl; count++; } } if (count > 0) { std::cout << std::dec << count << " objects were not deleted." << std::endl; } #endif } /** * Constructor. * @param backtrace * The backtrace. */ CaretObject::CaretObjectInfo::CaretObjectInfo(const SystemBacktrace& backtrace) { m_backtrace = backtrace; } /** * Destructor. */ CaretObject::CaretObjectInfo::~CaretObjectInfo() { } workbench-1.1.1/src/Common/CaretObject.h000066400000000000000000000041751255417355300200720ustar00rootroot00000000000000#ifndef __CARETOBJECT_H__ #define __CARETOBJECT_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "SystemUtilities.h" namespace caret { /** * A base class for all objects that are not derived * from third party libraries. */ class CaretObject { protected: CaretObject(); CaretObject(const CaretObject& o); CaretObject& operator=(const CaretObject& co); public: virtual ~CaretObject(); virtual AString toString() const; AString className() const; static void printListOfObjectsNotDeleted(const bool showCallStack); private: /** * Info about an allocated object. */ class CaretObjectInfo { public: CaretObjectInfo(const SystemBacktrace& backtrace); ~CaretObjectInfo(); SystemBacktrace m_backtrace; }; void copyHelper(const CaretObject& co); void initializeMembersCaretObject(); typedef std::map CARET_OBJECT_TRACKER_MAP; typedef CARET_OBJECT_TRACKER_MAP::iterator CARET_OBJECT_TRACKER_MAP_ITERATOR; static CARET_OBJECT_TRACKER_MAP allocatedObjects; }; #ifdef __CARET_OBJECT_DECLARE_H__ CaretObject::CARET_OBJECT_TRACKER_MAP CaretObject::allocatedObjects; #endif //__CARET_OBJECT_DECLARE_H__ } // namespace #endif // __CARETOBJECT_H__ workbench-1.1.1/src/Common/CaretObjectTracksModification.cxx000066400000000000000000000061651255417355300241440ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CARET_OBJECT_TRACKS_MODIFICATION_DECLARE__ #include "CaretObjectTracksModification.h" #undef __CARET_OBJECT_TRACKS_MODIFICATION_DECLARE__ using namespace caret; /** * \class caret::CaretObjectTracksModification * \brief CaretObject base class with implementation of tracks modification interface. */ /** * Constructor. */ CaretObjectTracksModification::CaretObjectTracksModification() : CaretObject(), TracksModificationInterface() { this->modifiedFlag = false; } /** * Destructor. */ CaretObjectTracksModification::~CaretObjectTracksModification() { } /** * Copy constructor. * @param obj * Object that is copied. */ CaretObjectTracksModification::CaretObjectTracksModification(const CaretObjectTracksModification& obj) : CaretObject(obj), TracksModificationInterface() { this->copyHelperCaretObjectTracksModification(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ CaretObjectTracksModification& CaretObjectTracksModification::operator=(const CaretObjectTracksModification& obj) { if (this != &obj) { CaretObject::operator=(obj); this->copyHelperCaretObjectTracksModification(obj); } return *this; } /** * Helps with copying an object of this type. * @param obj * Object that is copied. */ void CaretObjectTracksModification::copyHelperCaretObjectTracksModification(const CaretObjectTracksModification& /*obj*/) { this->modifiedFlag = false; // do not copy modification status } /** * Set the status to modified. */ void CaretObjectTracksModification::setModified() { this->modifiedFlag = true; } /** * Set the status to unmodified. */ void CaretObjectTracksModification::clearModified() { this->modifiedFlag = false; } /** * Is the object modified? * @return true if modified, else false. */ bool CaretObjectTracksModification::isModified() const { return this->modifiedFlag; } /** * Get a description of this object's content. * @return String describing this object's content. */ AString CaretObjectTracksModification::toString() const { const AString text = (CaretObject::toString() + "\nCaretObjectTracksModification::modifiedFlag=" + AString::fromBool(this->modifiedFlag)); return text; } workbench-1.1.1/src/Common/CaretObjectTracksModification.h000066400000000000000000000037251255417355300235700ustar00rootroot00000000000000#ifndef __CARET_OBJECT_TRACKS_MODIFICATION__H_ #define __CARET_OBJECT_TRACKS_MODIFICATION__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "TracksModificationInterface.h" namespace caret { class CaretObjectTracksModification : public CaretObject, public TracksModificationInterface { public: CaretObjectTracksModification(); virtual ~CaretObjectTracksModification(); CaretObjectTracksModification(const CaretObjectTracksModification& obj); CaretObjectTracksModification& operator=(const CaretObjectTracksModification& obj); virtual AString toString() const; virtual void setModified(); virtual void clearModified(); virtual bool isModified() const; private: void copyHelperCaretObjectTracksModification(const CaretObjectTracksModification& obj); bool modifiedFlag; }; #ifdef __CARET_OBJECT_TRACKS_MODIFICATION_DECLARE__ // #endif // __CARET_OBJECT_TRACKS_MODIFICATION_DECLARE__ } // namespace #endif //__CARET_OBJECT_TRACKS_MODIFICATION__H_ workbench-1.1.1/src/Common/CaretPointLocator.cxx000066400000000000000000000325131255417355300216510ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretPointLocator.h" #include "CaretHeap.h" #include using namespace caret; using namespace std; void CaretPointLocator::addPoint(Oct >* thisOct, const float point[3], const int32_t index, const int32_t pointSet) { if (thisOct->m_leaf) { thisOct->m_data.m_vector->push_back(Point(point, index, pointSet)); int curSize = (int)thisOct->m_data.m_vector->size(); if (curSize > NUM_POINTS_SPLIT) {//test that not all points are the same, or that they have some minimum percentage spread, or... vector& myVecRef = *(thisOct->m_data.m_vector); Vector3D minBox = myVecRef[0].m_point, maxBox = myVecRef[0].m_point, tempvec; tempvec[0] = thisOct->m_bounds[0][2] - thisOct->m_bounds[0][0]; tempvec[1] = thisOct->m_bounds[1][2] - thisOct->m_bounds[1][0]; tempvec[2] = thisOct->m_bounds[2][2] - thisOct->m_bounds[2][0]; float diagonal = tempvec.length(); bool safeToSplit = false; for (int i = 1; i < curSize; ++i) {//this will slow down if lots of stuff is continuously put in an Oct that is far too big - use random sampling? if (myVecRef[i].m_point[0] < minBox[0]) minBox[0] = myVecRef[i].m_point[0]; if (myVecRef[i].m_point[1] < minBox[1]) minBox[1] = myVecRef[i].m_point[1]; if (myVecRef[i].m_point[2] < minBox[2]) minBox[2] = myVecRef[i].m_point[2]; if (myVecRef[i].m_point[0] > maxBox[0]) maxBox[0] = myVecRef[i].m_point[0]; if (myVecRef[i].m_point[1] > maxBox[1]) maxBox[1] = myVecRef[i].m_point[1]; if (myVecRef[i].m_point[2] > maxBox[2]) maxBox[2] = myVecRef[i].m_point[2]; tempvec = minBox - maxBox; if (tempvec.length() > 0.01f * diagonal)//make sure points aren't all identical, would go to infinity recursively { safeToSplit = true; break; } } if (safeToSplit) { thisOct->makeChildren(); for (int i = 0; i < curSize; ++i) { addPoint(thisOct->containingChild(myVecRef[i].m_point), myVecRef[i].m_point, myVecRef[i].m_index, myVecRef[i].m_mySet); } thisOct->m_data.freeData(); } } } else { addPoint(thisOct->containingChild(point), point, index, pointSet); } } int32_t CaretPointLocator::addPointSet(const float* coordsIn, const int32_t numCoords) { CaretMutexLocker locked(&m_modifyMutex); int32_t setNum = newIndex(); if (numCoords < 1) return setNum; if (m_tree == NULL) { Vector3D minBox, maxBox; minBox = maxBox = coordsIn;//hack - first triple for (int32_t i = 1; i < numCoords; ++i) { int32_t i3 = i * 3; if (coordsIn[i3] < minBox[0]) minBox[0] = coordsIn[i3]; if (coordsIn[i3 + 1] < minBox[1]) minBox[1] = coordsIn[i3 + 1]; if (coordsIn[i3 + 2] < minBox[2]) minBox[2] = coordsIn[i3 + 2]; if (coordsIn[i3] > maxBox[0]) maxBox[0] = coordsIn[i3]; if (coordsIn[i3 + 1] > maxBox[1]) maxBox[1] = coordsIn[i3 + 1]; if (coordsIn[i3 + 2] > maxBox[2]) maxBox[2] = coordsIn[i3 + 2]; } m_tree = new Oct >(minBox, maxBox); } for (int32_t i = 0; i < numCoords; ++i) { int32_t i3 = i * 3; m_tree = m_tree->makeContains(coordsIn + i3);//make new root if needed addPoint(m_tree, coordsIn + i3, i, setNum);//and add the point } return setNum; } CaretPointLocator::CaretPointLocator(const float* coordsIn, const int32_t numCoords) { m_nextSetIndex = 1;//next set will be set #1 m_tree = NULL; if (numCoords >= 1) { Vector3D minBox, maxBox; minBox = maxBox = coordsIn;//hack - first triple for (int32_t i = 1; i < numCoords; ++i) { int32_t i3 = i * 3; if (coordsIn[i3] < minBox[0]) minBox[0] = coordsIn[i3]; if (coordsIn[i3 + 1] < minBox[1]) minBox[1] = coordsIn[i3 + 1]; if (coordsIn[i3 + 2] < minBox[2]) minBox[2] = coordsIn[i3 + 2]; if (coordsIn[i3] > maxBox[0]) maxBox[0] = coordsIn[i3]; if (coordsIn[i3 + 1] > maxBox[1]) maxBox[1] = coordsIn[i3 + 1]; if (coordsIn[i3 + 2] > maxBox[2]) maxBox[2] = coordsIn[i3 + 2]; } m_tree = new Oct >(minBox, maxBox); for (int32_t i = 0; i < numCoords; ++i) { int32_t i3 = i * 3; addPoint(m_tree, coordsIn + i3, i, 0);//this is set #0 } } } CaretPointLocator::CaretPointLocator(const float minBounds[3], const float maxBounds[3]) { m_nextSetIndex = 0; m_tree = new Oct >(minBounds, maxBounds); } int32_t CaretPointLocator::closestPoint(const float target[3], LocatorInfo* infoOut) const { if (m_tree == NULL) return -1; CaretSimpleMinHeap >*, float> myHeap; bool first = true; float bestDist2 = -1.0f, bestDist = -1.0f, tempf, curDist = m_tree->distToPoint(target); Vector3D bestPoint; int32_t bestSet = -1, bestIndex = -1; myHeap.push(m_tree, curDist); while (curDist < bestDist || first) { Oct >* thisOct = myHeap.pop(); if (thisOct->m_leaf) { vector& myVecRef = *(thisOct->m_data.m_vector); int curSize = (int)myVecRef.size(); for (int i = 0; i < curSize; ++i) { tempf = MathFunctions::distanceSquared3D(myVecRef[i].m_point, target); if (tempf < bestDist2 || first) { first = false; bestDist2 = tempf; bestPoint = myVecRef[i].m_point; bestSet = myVecRef[i].m_mySet; bestIndex = myVecRef[i].m_index; } } bestDist = sqrt(bestDist2); } else { for (int ii = 0; ii < 2; ++ii) { for (int ij = 0; ij < 2; ++ij) { for (int ik = 0; ik < 2; ++ik) { tempf = thisOct->m_children[ii][ij][ik]->distToPoint(target); if (tempf < bestDist || first) { myHeap.push(thisOct->m_children[ii][ij][ik], tempf); } } } } } if (myHeap.isEmpty()) { break;//allows us to use top() without violating an assertion } myHeap.top(&curDist);//get the key for the next item } if (infoOut != NULL) { infoOut->whichSet = bestSet; infoOut->coords = bestPoint; infoOut->index = bestIndex; } return bestIndex; } int32_t CaretPointLocator::closestPointLimited(const float target[3], const float& maxDist, LocatorInfo* infoOut) const { if (m_tree == NULL) return -1; float curDist2 = m_tree->distSquaredToPoint(target), maxDist2 = maxDist * maxDist; if (curDist2 > maxDist2) { if (infoOut != NULL) { infoOut->whichSet = -1; infoOut->index = -1; } return -1; } CaretSimpleMinHeap >*, float> myHeap; bool first = true; float bestDist2 = -1.0f, tempf; Vector3D bestPoint; int32_t bestSet = -1, bestIndex = -1; myHeap.push(m_tree, curDist2); while (curDist2 < bestDist2 || first) { Oct >* thisOct = myHeap.pop(); if (thisOct->m_leaf) { vector& myVecRef = *(thisOct->m_data.m_vector); int curSize = (int)myVecRef.size(); for (int i = 0; i < curSize; ++i) { tempf = MathFunctions::distanceSquared3D(myVecRef[i].m_point, target); if (tempf < bestDist2 || (first && tempf <= maxDist2)) { first = false; bestDist2 = tempf; bestPoint = myVecRef[i].m_point; bestSet = myVecRef[i].m_mySet; bestIndex = myVecRef[i].m_index; } } } else { for (int ii = 0; ii < 2; ++ii) { for (int ij = 0; ij < 2; ++ij) { for (int ik = 0; ik < 2; ++ik) { tempf = thisOct->m_children[ii][ij][ik]->distSquaredToPoint(target); if (tempf < bestDist2 || (first && tempf <= maxDist2)) { myHeap.push(thisOct->m_children[ii][ij][ik], tempf); } } } } } if (myHeap.isEmpty()) { break;//allows us to use top() without violating an assertion } myHeap.top(&curDist2);//get the key for the next item } if (infoOut != NULL) { infoOut->whichSet = bestSet; infoOut->coords = bestPoint; infoOut->index = bestIndex; } return bestIndex; } set CaretPointLocator::pointsInRange(const float target[3], const float& maxDist) const { set ret; if (m_tree == NULL) return ret; float curDist2 = m_tree->distSquaredToPoint(target), maxDist2 = maxDist * maxDist; if (curDist2 > maxDist2) return ret; vector >*> myStack;//since we don't need the points sorted by distance myStack.push_back(m_tree); while (!myStack.empty()) { Oct >* thisOct = myStack.back(); myStack.pop_back(); if (thisOct->m_leaf) { vector& myVecRef = *(thisOct->m_data.m_vector); int curSize = (int)myVecRef.size(); for (int i = 0; i < curSize; ++i) { float tempf = MathFunctions::distanceSquared3D(myVecRef[i].m_point, target); if (tempf <= maxDist2) { ret.insert(LocatorInfo(myVecRef[i].m_index, myVecRef[i].m_mySet, myVecRef[i].m_point));//let std::set sort out uniqueness } } } else { for (int ii = 0; ii < 2; ++ii) { for (int ij = 0; ij < 2; ++ij) { for (int ik = 0; ik < 2; ++ik) { float tempf = thisOct->m_children[ii][ij][ik]->distSquaredToPoint(target); if (tempf <= maxDist2) { myStack.push_back(thisOct->m_children[ii][ij][ik]); } } } } } } return ret; } int32_t CaretPointLocator::newIndex() { if (m_unusedIndexes.empty()) { return m_nextSetIndex++; } else { int32_t ret = m_unusedIndexes[m_unusedIndexes.size() - 1]; m_unusedIndexes.pop_back(); return ret; } } void CaretPointLocator::removePointSet(int32_t whichSet) { CaretMutexLocker locked(&m_modifyMutex); m_unusedIndexes.push_back(whichSet); removeSetHelper(m_tree, whichSet); } void CaretPointLocator::removeSetHelper(Oct >* thisOct, int32_t thisSet) { if (thisOct == NULL) return; if (thisOct->m_leaf) { vector& myVecRef = *(thisOct->m_data.m_vector); int curSize = (int)myVecRef.size(); bool match = false; for (int i = 0; i < curSize; ++i)//make sure something gets removed, so we don't have to do an allocation if it isn't needed { if (myVecRef[i].m_mySet == thisSet) { match = true; break; } } if (match) { vector tempvec; tempvec.reserve(curSize - 1);//because at least one is getting removed for (int i = 0; i < curSize; ++i) { if (myVecRef[i].m_mySet != thisSet) { tempvec.push_back(myVecRef[i]); } } myVecRef = tempvec; } } else { for (int ii = 0; ii < 2; ++ii) { for (int ij = 0; ij < 2; ++ij) { for (int ik = 0; ik < 2; ++ik) { removeSetHelper(thisOct->m_children[ii][ij][ik], thisSet); } } } } } workbench-1.1.1/src/Common/CaretPointLocator.h000066400000000000000000000073531255417355300213020ustar00rootroot00000000000000#ifndef __CARET_POINT_LOCATOR_H__ #define __CARET_POINT_LOCATOR_H__ #include "CaretAssertion.h" /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretMutex.h" #include "OctTree.h" #include "Vector3D.h" #include #include namespace caret { struct LocatorInfo { int32_t index, whichSet; Vector3D coords; LocatorInfo(const int32_t& indexIn, const int32_t& whichSetIn, const Vector3D& coordsIn) : index(indexIn), whichSet(whichSetIn), coords(coordsIn) { } bool operator==(const LocatorInfo& rhs) const { return (index == rhs.index) && (whichSet == rhs.whichSet); }//ignore coords bool operator<(const LocatorInfo& rhs) const { if (whichSet == rhs.whichSet)//expect multi-set usage with pointsInRange to be rare, but still separate by whichSet { return index < rhs.index; } else { return whichSet < rhs.whichSet; } } }; class CaretPointLocator { struct Point { Vector3D m_point; int32_t m_index, m_mySet; Point(const float point[3], const int32_t index, const int32_t mySet) { m_point = point; m_index = index; m_mySet = mySet; } }; CaretMutex m_modifyMutex;//thread safety, don't let multiple threads modify the point sets at once Oct >* m_tree; int32_t m_nextSetIndex; std::vector m_unusedIndexes; void addPoint(Oct >* thisOct, const float point[3], const int32_t index, const int32_t pointSet); int32_t newIndex(); static const int NUM_POINTS_SPLIT = 100; void removeSetHelper(Oct >* thisOct, const int32_t thisSet); CaretPointLocator(); public: ///make an empty point locator with given bounding box (bounding box can expand later, but may be less efficient CaretPointLocator(const float minBounds[3], const float maxBounds[3]); ///make a point locator with the bounding box of this point set, and use this point set as set #0 CaretPointLocator(const float* coordsIn, const int32_t numCoords); ///add a point set, SAVE THE RETURN VALUE because it is how you identify which point set found points belong to int32_t addPointSet(const float* coordsIn, const int32_t numCoords); ///remove a point set by its set number void removePointSet(const int32_t whichSet); ///returns the index of the closest point, and optionally which point set and the coords int32_t closestPoint(const float target[3], LocatorInfo* infoOut = NULL) const; int32_t closestPointLimited(const float target[3], const float& maxDist, LocatorInfo* infoOut = NULL) const; std::set pointsInRange(const float target[3], const float& maxDist) const; }; } #endif //__CARET_POINT_LOCATOR_H__ workbench-1.1.1/src/Common/CaretPointer.h000066400000000000000000000715201255417355300203020ustar00rootroot00000000000000#ifndef __CARET_POINTER_H__ #define __CARET_POINTER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretMutex.h" #include "CaretAssert.h" //NOTE: AFAIK, shared_ptr and raw pointers don't get along (can't pass to an old ownership-taking object without changing it to use shared_ptr) // so, these smart pointers have .releasePointer() which stops any smart pointer from deleting it (via an extra variable alongside the refcount) namespace caret { namespace _caret_pointer_impl {//namespace to hide things that shouldn't be used outside the header struct CaretPointerShare {//can't be member type because member types of templates on different types are incompatible int64_t m_refCount; bool m_doNotDelete; CaretPointerShare() { m_refCount = 1;//NOTE: don't initialize to 0, this way we don't have to change it every time we make one m_doNotDelete = false; } }; struct CaretPointerSyncShare {//same, but with mutex int64_t m_refCount; CaretMutex m_mutex;//protects m_refCount, m_doNotDelete bool m_doNotDelete; CaretPointerSyncShare() { m_refCount = 1; m_doNotDelete = false; } }; template class CaretPointerCommon {//provides only identical functionality between the four types - having a pointer member, and having ==, !=, a getPointer() method, and decay to pointer protected: T* m_pointer; CaretPointerCommon() { }//prevent standalone use, initialize with the share in derived classes public: template bool operator==(const T2* right) const { return m_pointer == right; } template bool operator!=(const T2* right) const { return !(*this == right); } template bool operator==(const CaretPointerCommon& right) const { return m_pointer == right.m_pointer; } template bool operator!=(const CaretPointerCommon& right) const { return !(*this == right); } operator T *const&() const { return m_pointer; }//never allow modifying the pointer, and also work when object is const template friend class CaretPointerCommon;//because for const compatibility, we need to access a different template's members }; template class CaretPointerBase : public CaretPointerCommon {//provides common functionality between just the 2 pointer types protected: using CaretPointerCommon::m_pointer; CaretPointerBase() { }//prevent standalone use public: T*& getPointer() { return m_pointer; } T *const& getPointer() const { return m_pointer; } T& operator*() const { CaretAssert(m_pointer != NULL); return *(m_pointer); } T *const& operator->() const { CaretAssert(m_pointer != NULL); return m_pointer; } }; template class CaretArrayBase : public CaretPointerCommon {//provides common functionality between just the 2 array types protected: using CaretPointerCommon::m_pointer; int64_t m_size; CaretArrayBase() { }//prevent standalone use, initialize size with share in derived classes public: T *const& getArray() const { return m_pointer; } template T& operator[](const I& index) { CaretAssert(m_pointer != NULL); CaretAssert(index >= 0 && (int64_t)index < m_size); return m_pointer[index]; } template const T& operator[](const I& index) const { CaretAssert(m_pointer != NULL); CaretAssert(index >= 0 && (int64_t)index < m_size); return m_pointer[index]; } const int64_t& size() const { return m_size; } }; } template class CaretPointerNonsync : public _caret_pointer_impl::CaretPointerBase { using _caret_pointer_impl::CaretPointerCommon::m_pointer; _caret_pointer_impl::CaretPointerShare* m_share; public: CaretPointerNonsync(); ~CaretPointerNonsync(); CaretPointerNonsync(const CaretPointerNonsync& right);//because a templated function apparently can't override default copy template CaretPointerNonsync(const CaretPointerNonsync& right); explicit CaretPointerNonsync(T* right); CaretPointerNonsync& operator=(const CaretPointerNonsync& right);//or default = template CaretPointerNonsync& operator=(const CaretPointerNonsync& right); void grabNew(T* right);//substitute for operator= to bare pointer int64_t getReferenceCount() const; ///breaks the hold on the pointer that is currently held by this, NO instances will delete it (setting is per-pointer, not per-instance) T*const& releasePointer(); template friend class CaretPointerNonsync;//because for const compatibility, we need to access a different template's members }; template class CaretPointer : public _caret_pointer_impl::CaretPointerBase { using _caret_pointer_impl::CaretPointerCommon::m_pointer; _caret_pointer_impl::CaretPointerSyncShare* m_share; mutable CaretMutex m_mutex;//protects members from modification while reading, or from reading while modifying public: CaretPointer(); ~CaretPointer(); CaretPointer(const CaretPointer& right); template CaretPointer(const CaretPointer& right); explicit CaretPointer(T* right); CaretPointer& operator=(const CaretPointer& right); template CaretPointer& operator=(const CaretPointer& right); void grabNew(T* right); int64_t getReferenceCount() const; ///breaks the hold on the pointer that is currently held by this, NO instances will delete it (setting is per-pointer, not per-instance) T*const& releasePointer(); template friend class CaretPointer; }; //separate array because delete and delete[] are different, and use indexing on one, and dereference/arrow on the other template class CaretArrayNonsync : public _caret_pointer_impl::CaretArrayBase { using _caret_pointer_impl::CaretPointerCommon::m_pointer; using _caret_pointer_impl::CaretArrayBase::m_size; _caret_pointer_impl::CaretPointerShare* m_share;//same share because it doesn't contain any specific information about what it is counting public: CaretArrayNonsync(); ~CaretArrayNonsync(); CaretArrayNonsync(const CaretArrayNonsync& right); template CaretArrayNonsync(const CaretArrayNonsync& right); CaretArrayNonsync(int64_t size);//for simpler construction CaretArrayNonsync(int64_t size, const T& initializer);//plus initialization CaretArrayNonsync& operator=(const CaretArrayNonsync& right); template CaretArrayNonsync& operator=(const CaretArrayNonsync& right); int64_t getReferenceCount() const; ///breaks the hold on the pointer that is currently held by this, NO instances will delete it (setting is per-pointer, not per-instance) T*const& releasePointer(); template friend class CaretArrayNonsync; }; template class CaretArray : public _caret_pointer_impl::CaretArrayBase { using _caret_pointer_impl::CaretPointerCommon::m_pointer; using _caret_pointer_impl::CaretArrayBase::m_size; _caret_pointer_impl::CaretPointerSyncShare* m_share;//same share because it doesn't contain any specific information about what it is counting mutable CaretMutex m_mutex;//protects members from modification while reading, or from reading while modifying public: CaretArray(); ~CaretArray(); CaretArray(const CaretArray& right); template CaretArray(const CaretArray& right); CaretArray(int64_t size);//for simpler construction CaretArray(int64_t size, const T& initializer);//plus initialization CaretArray& operator=(const CaretArray& right); template CaretArray& operator=(const CaretArray& right); int64_t getReferenceCount() const; ///breaks the hold on the pointer that is currently held by this, NO instances will delete it (setting is per-pointer, not per-instance) T*const& releasePointer(); template friend class CaretArray; }; //NOTE:begin pointer functions template CaretPointerNonsync::CaretPointerNonsync() { m_share = NULL; m_pointer = NULL; } template CaretPointerNonsync::CaretPointerNonsync(const CaretPointerNonsync& right) : _caret_pointer_impl::CaretPointerBase() { m_share = right.m_share; m_pointer = right.m_pointer; if (m_share != NULL) ++(m_share->m_refCount); } template template CaretPointerNonsync::CaretPointerNonsync(const CaretPointerNonsync& right) : _caret_pointer_impl::CaretPointerBase() { m_share = right.m_share; m_pointer = right.m_pointer; if (m_share != NULL) ++(m_share->m_refCount); } template CaretPointerNonsync::CaretPointerNonsync(T* right) { if (right == NULL) { m_share = NULL; m_pointer = NULL; } else { try { m_share = new _caret_pointer_impl::CaretPointerShare();//starts refcount at 1 } catch (...) {//don't leak the passed memory when exceptions happen delete right; throw; } m_pointer = right; } } template CaretPointerNonsync& CaretPointerNonsync::operator=(const CaretPointerNonsync& right) { if (this == &right) return *this;//short circuit self assignment CaretPointerNonsync temp(right);//copy construct from it, takes care type checking _caret_pointer_impl::CaretPointerShare* tempShare = temp.m_share;//swap the members T* tempPointer = temp.m_pointer; temp.m_share = m_share; temp.m_pointer = m_pointer; m_share = tempShare; m_pointer = tempPointer; return *this;//temp destructor takes care of the rest } template template CaretPointerNonsync& CaretPointerNonsync::operator=(const CaretPointerNonsync& right) {//self asignment won't hit this operator= CaretPointerNonsync temp(right);//copy construct from it, takes care of type checking _caret_pointer_impl::CaretPointerShare* tempShare = temp.m_share;//swap the members T* tempPointer = temp.m_pointer; temp.m_share = m_share; temp.m_pointer = m_pointer; m_share = tempShare; m_pointer = tempPointer; return *this;//temp destructor takes care of the rest } template void CaretPointerNonsync::grabNew(T* right) { CaretPointerNonsync temp(right);//construct from the pointer _caret_pointer_impl::CaretPointerShare* tempShare = temp.m_share;//swap the members T* tempPointer = temp.m_pointer; temp.m_share = m_share; temp.m_pointer = m_pointer; m_share = tempShare; m_pointer = tempPointer;//destructor of temp takes care of the rest } template CaretPointerNonsync::~CaretPointerNonsync() { if (m_share == NULL) return; --(m_share->m_refCount); if (m_share->m_refCount == 0) { if (!m_share->m_doNotDelete) delete m_pointer; delete m_share; } } template int64_t CaretPointerNonsync::getReferenceCount() const { if (m_share == NULL) { return 0; } return m_share->m_refCount; } template T*const& CaretPointerNonsync::releasePointer() { if (m_share != NULL) { m_share->m_doNotDelete = true; } return m_pointer; } //NOTE:begin sync pointer functions template CaretPointer::CaretPointer() { m_share = NULL; m_pointer = NULL; } template CaretPointer::CaretPointer(const CaretPointer& right) : _caret_pointer_impl::CaretPointerBase() {//don't need to lock self during constructor CaretMutexLocker locked(&(right.m_mutex));//don't let right modify its share until our reference is counted if (right.m_share == NULL)//guarantees it won't be deleted, because right has a counted reference { m_share = NULL; m_pointer = NULL; } else { CaretMutexLocker locked2(&(right.m_share->m_mutex)); ++(right.m_share->m_refCount); m_share = right.m_share;//now our reference is counted and we have the share, we can unlock everything m_pointer = right.m_pointer; } } template template CaretPointer::CaretPointer(const CaretPointer& right) : _caret_pointer_impl::CaretPointerBase() {//don't need to lock self during constructor CaretMutexLocker locked(&(right.m_mutex));//don't let right modify its share until our reference is counted if (right.m_share == NULL)//guarantees it won't be deleted, because right has a counted reference { m_share = NULL; m_pointer = NULL; } else { CaretMutexLocker locked2(&(right.m_share->m_mutex)); ++(right.m_share->m_refCount); m_share = right.m_share;//now our reference is counted and we have the share, we can unlock everything m_pointer = right.m_pointer; } } template CaretPointer::CaretPointer(T* right) {//don't need to lock self during constructor if (right == NULL) { m_share = NULL; m_pointer = NULL; } else { try { m_share = new _caret_pointer_impl::CaretPointerSyncShare();//starts refcount at 1 } catch (...) { delete right; throw; } m_pointer = right; } } template CaretPointer& CaretPointer::operator=(const CaretPointer& right) { if (this == &right) return *this;//short circuit self assignment CaretPointer temp(right);//copy construct from it, takes care of locking and type checking _caret_pointer_impl::CaretPointerSyncShare* tempShare = temp.m_share;//prepare to swap the members T* tempPointer = temp.m_pointer; CaretMutexLocker locked(&m_mutex);//lock myself before using internal state temp.m_share = m_share; temp.m_pointer = m_pointer; m_share = tempShare; m_pointer = tempPointer; return *this;//temp destructor takes care of the rest } template template CaretPointer& CaretPointer::operator=(const CaretPointer& right) {//self asignment won't hit this operator= CaretPointer temp(right);//copy construct from it, takes care of locking and type checking _caret_pointer_impl::CaretPointerSyncShare* tempShare = temp.m_share;//prepare to swap the members T* tempPointer = temp.m_pointer; CaretMutexLocker locked(&m_mutex);//lock myself before using internal state temp.m_share = m_share; temp.m_pointer = m_pointer; m_share = tempShare; m_pointer = tempPointer; return *this;//temp destructor takes care of the rest } template void CaretPointer::grabNew(T* right) { CaretPointer temp(right);//construct from the pointer _caret_pointer_impl::CaretPointerSyncShare* tempShare = temp.m_share;//prepare to swap the members T* tempPointer = temp.m_pointer; CaretMutexLocker locked(&m_mutex);//lock myself before using internal state temp.m_share = m_share; temp.m_pointer = m_pointer; m_share = tempShare; m_pointer = tempPointer;//destructor of temp takes care of the rest } template CaretPointer::~CaretPointer() {//access during destructor is programmer error, don't lock self if (m_share == NULL) return; bool deleteShare = false; { CaretMutexLocker locked(&(m_share->m_mutex));//do lock the refcount, though --(m_share->m_refCount); if (m_share->m_refCount == 0) { deleteShare = true; if (!m_share->m_doNotDelete) delete m_pointer; } }//unlock refcount mutex before deleting the object that contains it, otherwise Very Bad Things if (deleteShare) { delete m_share; } } template int64_t CaretPointer::getReferenceCount() const { CaretMutexLocker locked(&m_mutex);//lock so that m_share can't be deleted in the middle if (m_share == NULL) { return 0; } return m_share->m_refCount; } template T*const& CaretPointer::releasePointer() { CaretMutexLocker locked(&m_mutex);//lock to keep m_share and m_pointer coherent until after return - must return the pointer that was released if (m_share != NULL) { m_share->m_doNotDelete = true; } return m_pointer; } //NOTE:begin array functions template CaretArrayNonsync::CaretArrayNonsync() { m_share = NULL; m_pointer = NULL; m_size = 0; } template CaretArrayNonsync::CaretArrayNonsync(const CaretArrayNonsync& right) : _caret_pointer_impl::CaretArrayBase() { m_share = right.m_share; m_pointer = right.m_pointer; m_size = right.m_size; if (m_share != NULL) ++(m_share->m_refCount); } template template CaretArrayNonsync::CaretArrayNonsync(const CaretArrayNonsync& right) : _caret_pointer_impl::CaretArrayBase() { m_share = right.m_share; m_pointer = right.m_pointer; m_size = right.m_size; if (m_share != NULL) ++(m_share->m_refCount); } template CaretArrayNonsync::CaretArrayNonsync(int64_t size) { if (size > 0) { m_share = new _caret_pointer_impl::CaretPointerShare(); try { m_pointer = new T[size]; } catch (...) {//don't leak share objects if we can't allocate the memory delete m_share; m_share = NULL;//also keep state consistent throw; } m_size = size; } else { m_share = NULL; m_pointer = NULL; m_size = 0; } } template CaretArrayNonsync::CaretArrayNonsync(int64_t size, const T& initializer) { if (size > 0) { m_share = new _caret_pointer_impl::CaretPointerShare(); try { m_pointer = new T[size]; } catch (...) { delete m_share; m_share = NULL; throw; } m_size = size; T* end = m_pointer + size, *iter = m_pointer; do { *iter = initializer;//somewhat optimized, since this code will probably get used many places ++iter; } while (iter != end); } else { m_share = NULL; m_pointer = NULL; m_size = 0; } } template CaretArrayNonsync& CaretArrayNonsync::operator=(const CaretArrayNonsync& right) { CaretArrayNonsync temp(right); _caret_pointer_impl::CaretPointerShare* tempShare = temp.m_share;//swap the shares and fill members T* tempPointer = temp.m_pointer; int64_t tempSize = temp.m_size; temp.m_share = m_share; temp.m_pointer = m_pointer; temp.m_size = m_size; m_share = tempShare; m_pointer = tempPointer; m_size = tempSize; return *this;//destructor of temp cleans up } template template CaretArrayNonsync& CaretArrayNonsync::operator=(const CaretArrayNonsync& right) { CaretArrayNonsync temp(right); _caret_pointer_impl::CaretPointerShare* tempShare = temp.m_share;//swap the shares and fill members T* tempPointer = temp.m_pointer; int64_t tempSize = temp.m_size; temp.m_share = m_share; temp.m_pointer = m_pointer; temp.m_size = m_size; m_share = tempShare; m_pointer = tempPointer; m_size = tempSize; return *this;//destructor of temp cleans up } template CaretArrayNonsync::~CaretArrayNonsync() { if (m_share == NULL) return; --(m_share->m_refCount); if (m_share->m_refCount == 0) { if (!m_share->m_doNotDelete) delete[] m_pointer; delete m_share; } } template int64_t CaretArrayNonsync::getReferenceCount() const { if (m_share == NULL) { return 0; } return m_share->m_refCount; } template T*const& CaretArrayNonsync::releasePointer() { if (m_share != NULL) { m_share->m_doNotDelete = true; } return m_pointer; } //NOTE:begin sync array functions template CaretArray::CaretArray() { m_share = NULL; m_pointer = NULL; m_size = 0; } template CaretArray::CaretArray(const CaretArray& right) : _caret_pointer_impl::CaretArrayBase() {//don't need to lock self during constructor CaretMutexLocker locked(&(right.m_mutex));//don't let right modify its share until our reference is counted if (right.m_share == NULL)//guarantees it won't be deleted, because right has a counted reference { m_share = NULL; m_pointer = NULL; m_size = 0; } else { CaretMutexLocker locked2(&(right.m_share->m_mutex)); ++(right.m_share->m_refCount); m_share = right.m_share;//now our reference is counted and we have the share, we can unlock everything m_pointer = right.m_pointer; m_size = right.m_size; } } template template CaretArray::CaretArray(const CaretArray& right) : _caret_pointer_impl::CaretArrayBase() {//don't need to lock self during constructor CaretMutexLocker locked(&(right.m_mutex));//don't let right modify its share until our reference is counted if (right.m_share == NULL)//guarantees it won't be deleted, because right has a counted reference { m_share = NULL; m_pointer = NULL; m_size = 0; } else { CaretMutexLocker locked2(&(right.m_share->m_mutex)); ++(right.m_share->m_refCount); m_share = right.m_share;//now our reference is counted and we have the share, we can unlock everything this->m_pointer = right.m_pointer; m_size = right.m_size; } } template CaretArray::CaretArray(int64_t size) { if (size > 0) { m_share = new _caret_pointer_impl::CaretPointerSyncShare(); try { m_pointer = new T[size]; } catch (...) { delete m_share; m_share = NULL; throw; } m_size = size; } else { m_share = NULL; m_pointer = NULL; m_size = 0; } } template CaretArray::CaretArray(int64_t size, const T& initializer) { if (size > 0) { m_share = new _caret_pointer_impl::CaretPointerSyncShare(); try { m_pointer = new T[size]; } catch (...) { delete m_share; m_share = NULL; throw; } m_size = size; T* end = m_pointer + size, *iter = m_pointer; do { *iter = initializer;//somewhat optimized, since this code will probably get used many places ++iter; } while (iter != end); } else { m_share = NULL; m_pointer = NULL; m_size = 0; } } template CaretArray& CaretArray::operator=(const CaretArray& right) { CaretArray temp(right);//copy construct from it, takes care of locking _caret_pointer_impl::CaretPointerSyncShare* tempShare = temp.m_share;//prepare to swap the shares and fill members T* tempPointer = temp.m_pointer; int64_t tempSize = temp.m_size; CaretMutexLocker locked(&m_mutex);//lock myself before using internal state temp.m_share = m_share; temp.m_pointer = m_pointer; temp.m_size = m_size; m_share = tempShare; m_pointer = tempPointer; m_size = tempSize; return *this;//destructor of temp cleans up } template template CaretArray& CaretArray::operator=(const CaretArray& right) { CaretArray temp(right);//copy construct from it, takes care of locking _caret_pointer_impl::CaretPointerSyncShare* tempShare = temp.m_share;//prepare to swap the shares and fill members T* tempPointer = temp.m_pointer; int64_t tempSize = temp.m_size; CaretMutexLocker locked(&m_mutex);//lock myself before using internal state temp.m_share = m_share; temp.m_pointer = m_pointer; temp.m_size = m_size; m_share = tempShare; m_pointer = tempPointer; m_size = tempSize; return *this;//destructor of temp cleans up } template CaretArray::~CaretArray() {//access during destructor is programmer error, don't lock self if (m_share == NULL) return; bool deleteShare = false; { CaretMutexLocker locked(&(m_share->m_mutex));//do lock the refcount, though --(m_share->m_refCount); if (m_share->m_refCount == 0) { deleteShare = true; if (!m_share->m_doNotDelete) delete[] m_pointer; } }//left refcount unlock before deleting the object that contains it if (deleteShare) { delete m_share; } } template int64_t CaretArray::getReferenceCount() const { CaretMutexLocker locked(&m_mutex);//lock to keep m_share from being deleted if (m_share == NULL) { return 0; } return m_share->m_refCount; } template T*const& CaretArray::releasePointer() { CaretMutexLocker locked(&m_mutex);//lock because m_pointer and m_share need to remain coherent if (m_share != NULL) { m_share->m_doNotDelete = true; } return m_pointer; } } #endif //__CARET_POINTER_H__ workbench-1.1.1/src/Common/CaretPreferences.cxx000066400000000000000000001362201255417355300214750ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CARET_PREFERENCES_DECLARE__ #include "CaretPreferences.h" #undef __CARET_PREFERENCES_DECLARE__ #include #include #include #include #include "CaretAssert.h" #include "CaretLogger.h" #include "ModelTransform.h" #include "TileTabsConfiguration.h" using namespace caret; /** * \class caret::CaretPreferences * \brief Preferences for use in Caret. * * Maintains preferences for use in Caret. The * preferences are read only one time, when an * instance is created. If a preference is changed, * it is written. */ /** * Constructor. */ CaretPreferences::CaretPreferences() : CaretObject() { this->qSettings = new QSettings("brainvis.wustl.edu", "Caret7"); this->readPreferences(); } /** * Destructor. */ CaretPreferences::~CaretPreferences() { this->removeAllCustomViews(); this->removeAllTileTabsConfigurations(); delete this->qSettings; } /** * Get the boolean value for the given preference name. * @param name * Name of the preference. * @param defaultValue * Value returned in the preference with the given name was not found. * @return * Boolean value of preference or defaultValue if the * named preference is not found. */ bool CaretPreferences::getBoolean(const AString& name, const bool defaultValue) { bool b = this->qSettings->value(name, defaultValue).toBool(); return b; } /** * Set the given preference name with the boolean value. * @param * Name of the preference. * @param * New value for preference. */ void CaretPreferences::setBoolean(const AString& name, const bool value) { this->qSettings->setValue(name, value); } /** * Get the boolean value for the given preference name. * @param name * Name of the preference. * @param defaultValue * Value returned in the preference with the given name was not found. * @return * Integer value of preference or defaultValue if the * named preference is not found. */ int CaretPreferences::getInteger(const AString& name, const int defaultValue) { int b = this->qSettings->value(name, defaultValue).toInt(); return b; } /** * Set the given preference name with the integer value. * @param * Name of the preference. * @param * New value for preference. */ void CaretPreferences::setInteger(const AString& name, const int value) { this->qSettings->setValue(name, value); } /** * Get the string value for the given preference name. * @param name * Name of the preference. * @param defaultValue * Value returned in the preference with the given name was not found. * @return * String value of preference or defaultValue if the * named preference is not found. */ AString CaretPreferences::getString(const AString& name, const AString& defaultValue) { AString s = this->qSettings->value(name, defaultValue).toString(); return s; } /** * Set the given preference name with the string value. * @param * Name of the preference. * @param * New value for preference. */ void CaretPreferences::setString(const AString& name, const AString& value) { this->qSettings->setValue(name, value); } /** * Remove all custom views. */ void CaretPreferences::removeAllCustomViews() { for (std::vector::iterator iter = this->customViews.begin(); iter != this->customViews.end(); iter++) { delete *iter; } this->customViews.clear(); } /** * @return Names of custom views sorted by name. May want to precede this * method with a call to 'readCustomViews(true)' so that the custom views * are the latest from the settings. */ std::vector CaretPreferences::getCustomViewNames() const { std::vector names; for (std::vector::const_iterator iter = this->customViews.begin(); iter != this->customViews.end(); iter++) { const ModelTransform* mt = *iter; names.push_back(mt->getName()); } std::sort(names.begin(), names.end()); return names; } /** * @return Names and comments of custom views sorted by name. May want to precede this * method with a call to 'readCustomViews(true)' so that the custom views * are the latest from the settings. */ std::vector > CaretPreferences::getCustomViewNamesAndComments() const { std::vector customViewNames = getCustomViewNames(); std::vector > namesAndComments; for (std::vector::const_iterator iter = customViewNames.begin(); iter != customViewNames.end(); iter++) { const AString name = *iter; ModelTransform modelTransform; if (getCustomView(name, modelTransform)) { const AString comment = modelTransform.getComment(); namesAndComments.push_back(std::make_pair(name, comment)); } } return namesAndComments; } /** * Get a custom view with the given name. * * @param customViewName * Name of requested custom view. * @param modelTransformOut * Custom view will be loaded into this model transform. * @return true if a custom view with the name exists. If no * custom view exists with the name, false is returned and * the model transform will be the identity transform. */ bool CaretPreferences::getCustomView(const AString& customViewName, ModelTransform& modelTransformOut) const { for (std::vector::const_iterator iter = this->customViews.begin(); iter != this->customViews.end(); iter++) { const ModelTransform* mt = *iter; if (customViewName == mt->getName()) { modelTransformOut = *mt; return true; } } modelTransformOut.setToIdentity(); return false; } /** * Add or update a custom view. If a custom view exists with the name * in the given model transform it is replaced. * * @param modelTransform * Custom view's model transform. */ void CaretPreferences::addOrReplaceCustomView(const ModelTransform& modelTransform) { bool addNewCustomView = true; for (std::vector::iterator iter = this->customViews.begin(); iter != this->customViews.end(); iter++) { ModelTransform* mt = *iter; if (mt->getName() == modelTransform.getName()) { *mt = modelTransform; addNewCustomView = false; break; } } if (addNewCustomView) { this->customViews.push_back(new ModelTransform(modelTransform)); } this->writeCustomViews(); } /** * Remove the custom view with the given name. * * @param customViewName * Name of custom view. */ void CaretPreferences::removeCustomView(const AString& customViewName) { for (std::vector::iterator iter = this->customViews.begin(); iter != this->customViews.end(); iter++) { ModelTransform* mt = *iter; if (mt->getName() == customViewName) { this->customViews.erase(iter); delete mt; break; } } this->writeCustomViews(); } /** * Write the custom views. */ void CaretPreferences::writeCustomViews() { /* * Remove "userViews" that were replaced by customView */ this->qSettings->remove("userViews"); this->qSettings->beginWriteArray(NAME_CUSTOM_VIEWS); const int32_t numViews = static_cast(this->customViews.size()); for (int32_t i = 0; i < numViews; i++) { this->qSettings->setArrayIndex(i); this->qSettings->setValue(AString::number(i), this->customViews[i]->getAsString()); } this->qSettings->endArray(); this->qSettings->sync(); } /** * Remove all of the tile tabs configurations. */ void CaretPreferences::removeAllTileTabsConfigurations() { for (std::vector::iterator iter = this->tileTabsConfigurations.begin(); iter != this->tileTabsConfigurations.end(); iter++) { delete *iter; } this->tileTabsConfigurations.clear(); } /** * Write the tile tabs configurations. */ void CaretPreferences::writeTileTabsConfigurations() { this->qSettings->beginWriteArray(NAME_TILE_TABS_CONFIGURATIONS); const int32_t numViews = static_cast(this->tileTabsConfigurations.size()); for (int32_t i = 0; i < numViews; i++) { this->qSettings->setArrayIndex(i); this->qSettings->setValue(AString::number(i), this->tileTabsConfigurations[i]->encodeInXML()); } this->qSettings->endArray(); this->qSettings->sync(); } /** * Read the tile tabs configuration. Since user's may want to use them * in multiple instance of workbench that are running, this method allows * the tile tab configurations to be read without affecting other preferences. * * @param performSync * Sync with preferences since preferences may have been changed * by a concurrently running workbench. */ void CaretPreferences::readTileTabsConfigurations(const bool performSync) { if (performSync) { this->qSettings->sync(); } this->removeAllTileTabsConfigurations(); /* * Read Configurations */ const int numConfigurations = this->qSettings->beginReadArray(NAME_TILE_TABS_CONFIGURATIONS); for (int i = 0; i < numConfigurations; i++) { this->qSettings->setArrayIndex(i); const AString configString = this->qSettings->value(AString::number(i)).toString(); TileTabsConfiguration* ttc = new TileTabsConfiguration(); if (ttc->decodeFromXML(configString)) { this->tileTabsConfigurations.push_back(ttc); } else { delete ttc; } } this->qSettings->endArray(); } /** * @return The Tile tabs configurations sorted by name. */ std::vector CaretPreferences::getTileTabsConfigurationsSortedByName() const { /* * Copy the configurations and sort them by name. */ std::vector configurations; configurations.insert(configurations.end(), this->tileTabsConfigurations.begin(), this->tileTabsConfigurations.end()); std::sort(configurations.begin(), configurations.end(), TileTabsConfiguration::lessThanComparisonByName); return configurations; } /** * Get the tile tabs configuration with the given unique identifier. * * @param uniqueIdentifier * Unique identifier of the requested tile tabs configuration. * @return * Pointer to tile tabs configuration with the given unique identifier * or NULL is it does not exist. */ TileTabsConfiguration* CaretPreferences::getTileTabsConfigurationByUniqueIdentifier(const AString& uniqueIdentifier) { for (std::vector::const_iterator iter = this->tileTabsConfigurations.begin(); iter != this->tileTabsConfigurations.end(); iter++) { TileTabsConfiguration* ttc = *iter; if (ttc->getUniqueIdentifier() == uniqueIdentifier) { return ttc; } } return NULL; } /** * Get the tile tabs configuration with the given unique identifier. * * @param uniqueIdentifier * Unique identifier of the requested tile tabs configuration. * @return * Pointer to tile tabs configuration with the given unique identifier * or NULL is it does not exist. */ const TileTabsConfiguration* CaretPreferences::getTileTabsConfigurationByUniqueIdentifier(const AString& uniqueIdentifier) const { for (std::vector::const_iterator iter = this->tileTabsConfigurations.begin(); iter != this->tileTabsConfigurations.end(); iter++) { const TileTabsConfiguration* ttc = *iter; if (ttc->getUniqueIdentifier() == uniqueIdentifier) { return ttc; } } return NULL; } /** * Get the tile tabs configuration with the given name. * * @param name * Name of the requested tile tabs configuration. * @return * Pointer to tile tabs configuration with the given name * or NULL is it does not exist. */ TileTabsConfiguration* CaretPreferences::getTileTabsConfigurationByName(const AString& name) const { for (std::vector::const_iterator iter = this->tileTabsConfigurations.begin(); iter != this->tileTabsConfigurations.end(); iter++) { TileTabsConfiguration* ttc = *iter; if (name == ttc->getName()) { return ttc; } } return NULL; } /** * Add a new tile tabs configuration. * * @param tileTabsConfiguration * New tile tabs configuration that is added. */ void CaretPreferences::addTileTabsConfiguration(TileTabsConfiguration* tileTabsConfiguration) { this->tileTabsConfigurations.push_back(tileTabsConfiguration); this->writeTileTabsConfigurations(); } /** * Remove the tile tabs configuration with the given name. * * @param tileTabsUniqueIdentifier * Unique identifier of configuration that will be removed. */ void CaretPreferences::removeTileTabsConfigurationByUniqueIdentifier(const AString& tileTabsUniqueIdentifier) { for (std::vector::iterator iter = this->tileTabsConfigurations.begin(); iter != this->tileTabsConfigurations.end(); iter++) { TileTabsConfiguration* ttc = *iter; if (ttc->getUniqueIdentifier() == tileTabsUniqueIdentifier) { this->tileTabsConfigurations.erase(iter); delete ttc; break; } } this->writeTileTabsConfigurations(); } /** * Get the foreground color for viewing the ALL model. * * @param colorForeground * RGB color components ranging [0, 255]. */ void CaretPreferences::getColorForegroundAllView(uint8_t colorForeground[3]) const { for (int32_t i = 0; i < 3; i++) { colorForeground[i] = this->colorForegroundAll[i]; } } /** * Set the foreground color for viewing the ALL model. * * @param colorForeground * RGB color components ranging [0, 255]. */ void CaretPreferences::setColorForegroundAllView(const uint8_t colorForeground[3]) { for (int32_t i = 0; i < 3; i++) { this->colorForegroundAll[i] = colorForeground[i]; } writeUnsignedByteArray(NAME_COLOR_FOREGROUND_ALL, colorForegroundAll, 3); } /** * Get the background color for viewing the ALL model. * * @param colorBackground * RGB color components ranging [0, 255]. */ void CaretPreferences::getColorBackgroundAllView(uint8_t colorBackground[3]) const { for (int32_t i = 0; i < 3; i++) { colorBackground[i] = this->colorBackgroundAll[i]; } } /** * Set the background color for viewing the ALL model. * * @param colorBackground * RGB color components ranging [0, 255]. */ void CaretPreferences::setColorBackgroundAllView(const uint8_t colorBackground[3]) { for (int32_t i = 0; i < 3; i++) { this->colorBackgroundAll[i] = colorBackground[i]; } writeUnsignedByteArray(NAME_COLOR_BACKGROUND_ALL, colorBackgroundAll, 3); } /** * Get the foreground color for viewing the CHART model. * * @param colorForeground * RGB color components ranging [0, 255]. */ void CaretPreferences::getColorForegroundChartView(uint8_t colorForeground[3]) const { for (int32_t i = 0; i < 3; i++) { colorForeground[i] = this->colorForegroundChart[i]; } } /** * Set the foreground color for viewing the CHART model. * * @param colorForeground * RGB color components ranging [0, 255]. */ void CaretPreferences::setColorForegroundChartView(const uint8_t colorForeground[3]) { for (int32_t i = 0; i < 3; i++) { this->colorForegroundChart[i] = colorForeground[i]; } writeUnsignedByteArray(NAME_COLOR_FOREGROUND_CHART, colorForegroundChart, 3); } /** * Get the background color for viewing the CHART model. * * @param colorBackground * RGB color components ranging [0, 255]. */ void CaretPreferences::getColorBackgroundChartView(uint8_t colorBackground[3]) const { for (int32_t i = 0; i < 3; i++) { colorBackground[i] = this->colorBackgroundChart[i]; } } /** * Set the background color for viewing the CHART model. * * @param colorBackground * RGB color components ranging [0, 255]. */ void CaretPreferences::setColorBackgroundChartView(const uint8_t colorBackground[3]) { for (int32_t i = 0; i < 3; i++) { this->colorBackgroundChart[i] = colorBackground[i]; } writeUnsignedByteArray(NAME_COLOR_BACKGROUND_CHART, colorBackgroundChart, 3); } /** * Get the foreground color for viewing the SURFACE model. * * @param colorForeground * RGB color components ranging [0, 255]. */ void CaretPreferences::getColorForegroundSurfaceView(uint8_t colorForeground[3]) const { for (int32_t i = 0; i < 3; i++) { colorForeground[i] = this->colorForegroundSurface[i]; } } /** * Set the foreground color for viewing the SURFACE model. * * @param colorForeground * RGB color components ranging [0, 255]. */ void CaretPreferences::setColorForegroundSurfaceView(const uint8_t colorForeground[3]) { for (int32_t i = 0; i < 3; i++) { this->colorForegroundSurface[i] = colorForeground[i]; } writeUnsignedByteArray(NAME_COLOR_FOREGROUND_SURFACE, colorForegroundSurface, 3); } /** * Get the background color for viewing the SURFACE model. * * @param colorBackground * RGB color components ranging [0, 255]. */ void CaretPreferences::getColorBackgroundSurfaceView(uint8_t colorBackground[3]) const { for (int32_t i = 0; i < 3; i++) { colorBackground[i] = this->colorBackgroundSurface[i]; } } /** * Get the background color for viewing the SURFACE model. * * @param colorBackground * RGB color components ranging [0, 255]. */ void CaretPreferences::setColorBackgroundSurfaceView(const uint8_t colorBackground[3]) { for (int32_t i = 0; i < 3; i++) { this->colorBackgroundSurface[i] = colorBackground[i]; } writeUnsignedByteArray(NAME_COLOR_BACKGROUND_SURFACE, colorBackgroundSurface, 3); } /** * Get the foreground color for viewing the VOLUME model. * * @param colorForeground * RGB color components ranging [0, 255]. */ void CaretPreferences::getColorForegroundVolumeView(uint8_t colorForeground[3]) const { for (int32_t i = 0; i < 3; i++) { colorForeground[i] = this->colorForegroundVolume[i]; } } /** * Set the foreground color for viewing the VOLUME model. * * @param colorForeground * RGB color components ranging [0, 255]. */ void CaretPreferences::setColorForegroundVolumeView(const uint8_t colorForeground[3]) { for (int32_t i = 0; i < 3; i++) { this->colorForegroundVolume[i] = colorForeground[i]; } writeUnsignedByteArray(NAME_COLOR_FOREGROUND_VOLUME, colorForegroundVolume, 3); } /** * Get the background color for viewing the VOLUME model. * * @param colorBackground * RGB color components ranging [0, 255]. */ void CaretPreferences::getColorBackgroundVolumeView(uint8_t colorBackground[3]) const { for (int32_t i = 0; i < 3; i++) { colorBackground[i] = this->colorBackgroundVolume[i]; } } /** * Set the background color for viewing the VOLUME model. * * @param colorBackground * RGB color components ranging [0, 255]. */ void CaretPreferences::setColorBackgroundVolumeView(const uint8_t colorBackground[3]) { for (int32_t i = 0; i < 3; i++) { this->colorBackgroundVolume[i] = colorBackground[i]; } writeUnsignedByteArray(NAME_COLOR_BACKGROUND_VOLUME, colorBackgroundVolume, 3); } /** * Get the color for chart matrix grid lines * * @param colorChartMatrixGridLines * RGB color components ranging [0, 255]. */ void CaretPreferences::getColorChartMatrixGridLines(uint8_t colorChartMatrixGridLines[3]) const { for (int32_t i = 0; i < 3; i++) { colorChartMatrixGridLines[i] = this->colorChartMatrixGridLines[i]; } } /** * Set the color for chart matrix grid lines * * @param colorChartMatrixGridLines * RGB color components ranging [0, 255]. */ void CaretPreferences::setColorChartMatrixGridLines(const uint8_t colorChartMatrixGridLines[3]) { for (int32_t i = 0; i < 3; i++) { this->colorChartMatrixGridLines[i] = colorChartMatrixGridLines[i]; } writeUnsignedByteArray(NAME_COLOR_CHART_MATRIX_GRID_LINES, colorChartMatrixGridLines, 3); } /** * Get the previous spec files. * * @param previousSpecFiles * Will contain previous spec files. */ void CaretPreferences::getPreviousSpecFiles(std::vector& previousSpecFiles) const { previousSpecFiles = this->previousSpecFiles; } /** * Add to the previous spec files. * * @param specFileName * Spec file added to the previous spec files. */ void CaretPreferences::addToPreviousSpecFiles(const AString& specFileName) { if (specFileName.isEmpty() == false) { this->addToPrevious(this->previousSpecFiles, specFileName); } const int32_t num = static_cast(this->previousSpecFiles.size()); this->qSettings->beginWriteArray(NAME_PREVIOUS_SPEC_FILES); for (int i = 0; i < num; i++) { this->qSettings->setArrayIndex(i); this->qSettings->setValue(AString::number(i), this->previousSpecFiles[i]); } this->qSettings->endArray(); this->qSettings->sync(); } void CaretPreferences::clearPreviousSpecFiles() { this->previousSpecFiles.clear(); this->addToPreviousSpecFiles(""); } /** * Get the directories that were used in the Open File Dialog. * * @param previousOpenFileDirectories * Will contain previous directories. */ void CaretPreferences::getPreviousOpenFileDirectories(std::vector& previousOpenFileDirectories) const { previousOpenFileDirectories = this->previousOpenFileDirectories; } /** * Get the directories that were used in the Open File Dialog. * * @param previousOpenFileDirectories * Will contain previous directories. */ void CaretPreferences::getPreviousOpenFileDirectories(QStringList& previousOpenFileDirectoriesList) const { previousOpenFileDirectoriesList.clear(); const int32_t numDirectories = static_cast(this->previousOpenFileDirectories.size()); for (int32_t i = 0; i < numDirectories; i++) { previousOpenFileDirectoriesList.append(this->previousOpenFileDirectories[i]); } } /** * Add to the previous directories that were used in the Open File Dialog. * * @param directoryName * Directory added to the previous directories from Open File Dialog. */ void CaretPreferences::addToPreviousOpenFileDirectories(const AString& directoryName) { this->addToPrevious(this->previousOpenFileDirectories, directoryName); const int32_t num = static_cast(this->previousOpenFileDirectories.size()); this->qSettings->beginWriteArray(NAME_PREVIOUS_OPEN_FILE_DIRECTORIES); for (int i = 0; i < num; i++) { this->qSettings->setArrayIndex(i); this->qSettings->setValue(AString::number(i), this->previousOpenFileDirectories[i]); } this->qSettings->endArray(); this->qSettings->sync(); } /** * Add to a list of previous, removing any matching entries * and limiting the size of the list. * * @param previousDeque * Deque containing the previous elements. * @param newName * Name that is added at the front. */ void CaretPreferences::addToPrevious(std::vector& previousVector, const AString& newName) { /* * Note: remove moves duplicate elements to after 'pos' but * does not change the size of the container so use erase * to remove the duplicate elements from the container. */ std::vector::iterator pos = std::remove(previousVector.begin(), previousVector.end(), newName); previousVector.erase(pos, previousVector.end()); const uint64_t MAX_ELEMENTS = 10; if (previousVector.size() > MAX_ELEMENTS) { previousVector.erase(previousVector.begin() + MAX_ELEMENTS, previousVector.end()); } previousVector.insert(previousVector.begin(), newName); } /** * @return The logging level. */ LogLevelEnum::Enum CaretPreferences::getLoggingLevel() const { return this->loggingLevel; } /** * Set the logging level. * Will also update the level in the Caret Logger. * @param loggingLevel * New value for logging level. */ void CaretPreferences::setLoggingLevel(const LogLevelEnum::Enum loggingLevel) { this->loggingLevel = loggingLevel; const AString name = LogLevelEnum::toName(this->loggingLevel); this->qSettings->setValue(NAME_LOGGING_LEVEL, name); this->qSettings->sync(); CaretLogger::getLogger()->setLevel(loggingLevel); } /** * @return The OpenGL Drawing method. */ OpenGLDrawingMethodEnum::Enum CaretPreferences::getOpenDrawingMethod() const { OpenGLDrawingMethodEnum::Enum drawMethod = this->openGLDrawingMethod; /* * Disable vertex buffers for now */ drawMethod = OpenGLDrawingMethodEnum::DRAW_WITH_VERTEX_BUFFERS_OFF; return drawMethod; } /** * Set the OpenGL Drawing method. * * @param openGLDrawingMethod * New value for OpenGL Drawing method. */ void CaretPreferences::setOpenGLDrawingMethod(const OpenGLDrawingMethodEnum::Enum openGLDrawingMethod) { this->openGLDrawingMethod = openGLDrawingMethod; this->setString(NAME_OPENGL_DRAWING_METHOD, OpenGLDrawingMethodEnum::toName(this->openGLDrawingMethod)); } /** * @return The view file type for manage files dialog. */ SpecFileDialogViewFilesTypeEnum::Enum CaretPreferences::getManageFilesViewFileType() const { return this->manageFilesViewFileType; } /** * Set the view file type for manage files dialog. * * @param manageFilesViewFileType * New view file type. */ void CaretPreferences::setManageFilesViewFileType(const SpecFileDialogViewFilesTypeEnum::Enum manageFilesViewFileType) { this->manageFilesViewFileType = manageFilesViewFileType; this->setString(NAME_MANAGE_FILES_VIEW_FILE_TYPE, SpecFileDialogViewFilesTypeEnum::toName(this->manageFilesViewFileType)); } /** * @return Show surface identification symbols? */ bool CaretPreferences::isShowSurfaceIdentificationSymbols() const { return this->showSurfaceIdentificationSymbols; } /** * Set show surface identification symbols. * * @param showSymbols * New status. */ void CaretPreferences::setShowSurfaceIdentificationSymbols(const bool showSymbols) { this->showSurfaceIdentificationSymbols = showSymbols; this->setBoolean(NAME_SHOW_SURFACE_IDENTIFICATION_SYMBOLS, this->showSurfaceIdentificationSymbols); } /** * @return Show volume identification symbols? */ bool CaretPreferences::isShowVolumeIdentificationSymbols() const { return this->showVolumeIdentificationSymbols; } /** * Set show volume identification symbols. * * @param showSymbols * New status. */ void CaretPreferences::setShowVolumeIdentificationSymbols(const bool showSymbols) { this->showVolumeIdentificationSymbols = showSymbols; this->setBoolean(NAME_SHOW_VOLUME_IDENTIFICATION_SYMBOLS, this->showVolumeIdentificationSymbols); } /** * @return The image capture method. */ ImageCaptureMethodEnum::Enum CaretPreferences::getImageCaptureMethod() const { return this->imageCaptureMethod; } /** * Set the image capture method. * * @param imageCaptureMethod * New value for image capture method. */ void CaretPreferences::setImageCaptureMethod(const ImageCaptureMethodEnum::Enum imageCaptureMethod) { this->imageCaptureMethod = imageCaptureMethod; this->setString(NAME_IMAGE_CAPTURE_METHOD, ImageCaptureMethodEnum::toName(this->imageCaptureMethod)); } /** * @return Save remote login to preferences. */ bool CaretPreferences::isRemoteFilePasswordSaved() { return this->remoteFileLoginSaved; } /** * Set saving of remote login and password to preferences. * * @param saveRemoteLoginToPreferences * New status. */ void CaretPreferences::setRemoteFilePasswordSaved(const bool saveRemotePasswordToPreferences) { this->remoteFileLoginSaved = saveRemotePasswordToPreferences; this->setBoolean(NAME_REMOTE_FILE_LOGIN_SAVED, this->remoteFileLoginSaved); this->qSettings->sync(); } /** * Get the remote file username and password * * @param userNameOut * Contains username upon exit * @param passwordOut * Contains password upon exit. */ void CaretPreferences::getRemoteFileUserNameAndPassword(AString& userNameOut, AString& passwordOut) const { userNameOut = this->remoteFileUserName; passwordOut = this->remoteFilePassword; } /** * Set the remote file username and password * * @param userName * New value for username. * @param passwordOut * New value for password. */ void CaretPreferences::setRemoteFileUserNameAndPassword(const AString& userName, const AString& password) { this->remoteFileUserName = userName; this->remoteFilePassword = password; this->setString(NAME_REMOTE_FILE_USER_NAME, userName); this->setString(NAME_REMOTE_FILE_PASSWORD, password); this->qSettings->sync(); } /** * @return Are axes crosshairs displayed? */ bool CaretPreferences::isVolumeAxesCrosshairsDisplayed() const { return this->displayVolumeAxesCrosshairs; } /** * Set axes crosshairs displayed * @param displayed * New status. */ void CaretPreferences::setVolumeAxesCrosshairsDisplayed(const bool displayed) { this->displayVolumeAxesCrosshairs = displayed; this->setBoolean(CaretPreferences::NAME_VOLUME_AXES_CROSSHAIRS, this->displayVolumeAxesCrosshairs); this->qSettings->sync(); } /** * @return Are axes labels displayed? */ bool CaretPreferences::isVolumeAxesLabelsDisplayed() const { return this->displayVolumeAxesLabels; } /** * Set axes labels displayed * @param displayed * New status. */ void CaretPreferences::setVolumeAxesLabelsDisplayed(const bool displayed) { this->displayVolumeAxesLabels = displayed; this->setBoolean(CaretPreferences::NAME_VOLUME_AXES_LABELS, this->displayVolumeAxesLabels); this->qSettings->sync(); } /** * @return Are montage axes coordinates displayed? */ bool CaretPreferences::isVolumeMontageAxesCoordinatesDisplayed() const { return this->displayVolumeAxesCoordinates; } /** * Set montage axes coordinates displayed * @param displayed * New status. */ void CaretPreferences::setVolumeMontageAxesCoordinatesDisplayed(const bool displayed) { this->displayVolumeAxesCoordinates = displayed; this->setBoolean(CaretPreferences::NAME_VOLUME_AXES_COORDINATE, this->displayVolumeAxesCoordinates); this->qSettings->sync(); } /** * @return The volume montage gap. */ int32_t CaretPreferences::getVolumeMontageGap() const { return this->volumeMontageGap; } /** * Set the volume montage gap. * * @param volumeMontageGap * New value for montage gap. */ void CaretPreferences::setVolumeMontageGap(const int32_t volumeMontageGap) { this->volumeMontageGap = volumeMontageGap; this->setInteger(CaretPreferences::NAME_VOLUME_MONTAGE_GAP, this->volumeMontageGap); this->qSettings->sync(); } /** * @return The volume montage coordinate precision */ int32_t CaretPreferences::getVolumeMontageCoordinatePrecision() const { return this->volumeMontageCoordinatePrecision; } /** * Set the volume montage coordinate precision * * @param volumeMontageCoordinatePrecision * New value for montage coordinate precision */ void CaretPreferences::setVolumeMontageCoordinatePrecision(const int32_t volumeMontageCoordinatePrecision) { this->volumeMontageCoordinatePrecision = volumeMontageCoordinatePrecision; this->setInteger(CaretPreferences::NAME_VOLUME_MONTAGE_COORDINATE_PRECISION, this->volumeMontageCoordinatePrecision); this->qSettings->sync(); } /** * @return Is the splash screen enabled? */ bool CaretPreferences::isSplashScreenEnabled() const { return this->splashScreenEnabled; } /** * Set the splash screen enabled. * @param enabled * New status. */ void CaretPreferences::setSplashScreenEnabled(const bool enabled) { this->splashScreenEnabled = enabled; this->setBoolean(CaretPreferences::NAME_SPLASH_SCREEN, this->splashScreenEnabled); this->qSettings->sync(); } /** * @return Is the Develop Menu enabled? */ bool CaretPreferences::isDevelopMenuEnabled() const { return this->developMenuEnabled; } /** * Set the Develop Menu enabled. * @param enabled * New status. */ void CaretPreferences::setDevelopMenuEnabled(const bool enabled) { this->developMenuEnabled = enabled; this->setBoolean(CaretPreferences::NAME_DEVELOP_MENU, this->developMenuEnabled); this->qSettings->sync(); } /** * @param Is yoking defaulted on ? */ bool CaretPreferences::isYokingDefaultedOn() const { return this->yokingDefaultedOn; } /** * Set yoking defaulted on * * @param status * New status for yoking on. */ void CaretPreferences::setYokingDefaultedOn(const bool status) { this->yokingDefaultedOn = status; this->setBoolean(CaretPreferences::NAME_YOKING_DEFAULT_ON, this->yokingDefaultedOn); this->qSettings->sync(); } /** * @param Is volume identification defaulted on ? */ bool CaretPreferences::isVolumeIdentificationDefaultedOn() const { return this->volumeIdentificationDefaultedOn; } /** * Set volume identification defaulted on * * @param status * New status for yoking on. */ void CaretPreferences::setVolumeIdentificationDefaultedOn(const bool status) { this->volumeIdentificationDefaultedOn = status; this->setBoolean(CaretPreferences::NAME_VOLUME_IDENTIFICATION_DEFAULTED_ON, this->volumeIdentificationDefaultedOn); this->qSettings->sync(); } /** * Read an unsigned byte array to the preferences. * * @param name * Name for preferences * @param array * The array that is read. * @param numberOfElements * Number of elements in the array. */ void CaretPreferences::readUnsignedByteArray(const AString& name, uint8_t array[], const int32_t numberOfElements) { const int numAvailable = this->qSettings->beginReadArray(name); const int numToRead = std::min(numAvailable, numberOfElements); for (int i = 0; i < numToRead; i++) { this->qSettings->setArrayIndex(i); array[i] = static_cast(this->qSettings->value(AString::number(i)).toInt()); } this->qSettings->endArray(); } /** * Write an unsigned byte array to the preferences. * * @param name * Name for preferences * @param array * The array that is written. * @param numberOfElements * Number of elements in the array. */ void CaretPreferences::writeUnsignedByteArray(const AString& name, const uint8_t array[], const int32_t numberOfElements) { this->qSettings->beginWriteArray(name); for (int i = 0; i < numberOfElements; i++) { this->qSettings->setArrayIndex(i); this->qSettings->setValue(AString::number(i), array[i]); } this->qSettings->endArray(); this->qSettings->sync(); } /** * Initialize/Read the preferences */ void CaretPreferences::readPreferences() { uint8_t colorForeground[3] = { 255, 255, 255 }; readUnsignedByteArray(NAME_COLOR_FOREGROUND, colorForeground, 3); uint8_t colorBackground[3] = { 0, 0, 0 }; readUnsignedByteArray(NAME_COLOR_BACKGROUND, colorBackground, 3); /* * At one time, there was one foreground and background color that applied * to all model views. Use it to initialize the newer background and * foreground colors for each model type. */ for (int32_t i = 0; i < 3; i++) { this->colorBackgroundAll[i] = colorBackground[i]; this->colorBackgroundChart[i] = colorBackground[i]; this->colorBackgroundSurface[i] = colorBackground[i]; this->colorBackgroundVolume[i] = colorBackground[i]; this->colorForegroundAll[i] = colorForeground[i]; this->colorForegroundChart[i] = colorForeground[i]; this->colorForegroundSurface[i] = colorForeground[i]; this->colorForegroundVolume[i] = colorForeground[i]; this->colorChartMatrixGridLines[i] = colorForeground[i]; } readUnsignedByteArray(NAME_COLOR_FOREGROUND_ALL, this->colorForegroundAll, 3); readUnsignedByteArray(NAME_COLOR_BACKGROUND_ALL, this->colorBackgroundAll, 3); readUnsignedByteArray(NAME_COLOR_FOREGROUND_CHART, this->colorForegroundChart, 3); readUnsignedByteArray(NAME_COLOR_BACKGROUND_CHART, this->colorBackgroundChart, 3); readUnsignedByteArray(NAME_COLOR_FOREGROUND_SURFACE, this->colorForegroundSurface, 3); readUnsignedByteArray(NAME_COLOR_BACKGROUND_SURFACE, this->colorBackgroundSurface, 3); readUnsignedByteArray(NAME_COLOR_FOREGROUND_VOLUME, this->colorForegroundVolume, 3); readUnsignedByteArray(NAME_COLOR_BACKGROUND_VOLUME, this->colorBackgroundVolume, 3); this->colorChartMatrixGridLines[0] = 0; this->colorChartMatrixGridLines[1] = 0; this->colorChartMatrixGridLines[2] = 0; readUnsignedByteArray(NAME_COLOR_CHART_MATRIX_GRID_LINES, this->colorChartMatrixGridLines, 3); this->previousSpecFiles.clear(); const int numPrevSpec = this->qSettings->beginReadArray(NAME_PREVIOUS_SPEC_FILES); for (int i = 0; i < numPrevSpec; i++) { this->qSettings->setArrayIndex(i); previousSpecFiles.push_back(this->qSettings->value(AString::number(i)).toString()); } this->qSettings->endArray(); this->previousOpenFileDirectories.clear(); const int numPrevDir = this->qSettings->beginReadArray(NAME_PREVIOUS_OPEN_FILE_DIRECTORIES); for (int i = 0; i < numPrevDir; i++) { this->qSettings->setArrayIndex(i); previousOpenFileDirectories.push_back(this->qSettings->value(AString::number(i)).toString()); } this->qSettings->endArray(); this->readCustomViews(false); this->readTileTabsConfigurations(); AString levelName = this->qSettings->value(NAME_LOGGING_LEVEL, LogLevelEnum::toName(LogLevelEnum::INFO)).toString(); bool valid = false; LogLevelEnum::Enum logLevel = LogLevelEnum::fromName(levelName, &valid); if (valid == false) { logLevel = LogLevelEnum::INFO; } this->setLoggingLevel(logLevel); AString imageCaptureMethodName = this->qSettings->value(NAME_IMAGE_CAPTURE_METHOD, ImageCaptureMethodEnum::toName(ImageCaptureMethodEnum::IMAGE_CAPTURE_WITH_RENDER_PIXMAP)).toString(); bool validImageCaptureMethodName = false; this->imageCaptureMethod = ImageCaptureMethodEnum::fromName(imageCaptureMethodName, &validImageCaptureMethodName); if ( ! validImageCaptureMethodName) { this->imageCaptureMethod = ImageCaptureMethodEnum::IMAGE_CAPTURE_WITH_RENDER_PIXMAP; } AString openGLDrawingMethodName = this->qSettings->value(NAME_OPENGL_DRAWING_METHOD, OpenGLDrawingMethodEnum::toName(OpenGLDrawingMethodEnum::DRAW_WITH_VERTEX_BUFFERS_OFF)).toString(); bool validDrawingMethod = false; this->openGLDrawingMethod = OpenGLDrawingMethodEnum::fromName(openGLDrawingMethodName, &validDrawingMethod); if ( ! validDrawingMethod) { this->openGLDrawingMethod = OpenGLDrawingMethodEnum::DRAW_WITH_VERTEX_BUFFERS_OFF; } AString viewFileTypesName = this->qSettings->value(NAME_MANAGE_FILES_VIEW_FILE_TYPE, SpecFileDialogViewFilesTypeEnum::toName(SpecFileDialogViewFilesTypeEnum::VIEW_FILES_ALL)).toString(); bool viewFilesTypeValid = false; this->manageFilesViewFileType = SpecFileDialogViewFilesTypeEnum::fromName(viewFileTypesName, &viewFilesTypeValid); if ( ! viewFilesTypeValid) { this->manageFilesViewFileType = SpecFileDialogViewFilesTypeEnum::VIEW_FILES_ALL; } this->displayVolumeAxesLabels = this->getBoolean(CaretPreferences::NAME_VOLUME_AXES_LABELS, true); this->displayVolumeAxesCrosshairs = this->getBoolean(CaretPreferences::NAME_VOLUME_AXES_CROSSHAIRS, true); this->displayVolumeAxesCoordinates = this->getBoolean(CaretPreferences::NAME_VOLUME_AXES_COORDINATE, true); this->volumeMontageGap = this->getInteger(CaretPreferences::NAME_VOLUME_MONTAGE_GAP, 3); this->volumeMontageCoordinatePrecision = this->getInteger(CaretPreferences::NAME_VOLUME_MONTAGE_COORDINATE_PRECISION, 0); this->animationStartTime = 0.0;//this->qSettings->value(CaretPreferences::NAME_ANIMATION_START_TIME).toDouble(); this->splashScreenEnabled = this->getBoolean(CaretPreferences::NAME_SPLASH_SCREEN, true); this->developMenuEnabled = this->getBoolean(CaretPreferences::NAME_DEVELOP_MENU, false); this->yokingDefaultedOn = this->getBoolean(CaretPreferences::NAME_YOKING_DEFAULT_ON, true); this->volumeIdentificationDefaultedOn = this->getBoolean(CaretPreferences::NAME_VOLUME_IDENTIFICATION_DEFAULTED_ON, true); this->remoteFileUserName = this->getString(NAME_REMOTE_FILE_USER_NAME); this->remoteFilePassword = this->getString(NAME_REMOTE_FILE_PASSWORD); this->remoteFileLoginSaved = this->getBoolean(NAME_REMOTE_FILE_LOGIN_SAVED, false); this->showSurfaceIdentificationSymbols = this->getBoolean(NAME_SHOW_SURFACE_IDENTIFICATION_SYMBOLS, true); this->showVolumeIdentificationSymbols = this->getBoolean(NAME_SHOW_VOLUME_IDENTIFICATION_SYMBOLS, true); } /** * Read the custom views. Since user's may want to use them * in multiple instance of workbench that are running, this method allows * the custom views to be read without affecting other preferences. * * @param performSync * Sync with preferences since preferences may have been changed * by a concurrently running workbench. */ void CaretPreferences::readCustomViews(const bool performSync) { if (performSync) { this->qSettings->sync(); } this->removeAllCustomViews(); /* * Previously had "userViews" prior to CustomViews */ const int numUserViews = this->qSettings->beginReadArray("userViews"); for (int i = 0; i < numUserViews; i++) { this->qSettings->setArrayIndex(i); const AString viewString = this->qSettings->value(AString::number(i)).toString(); ModelTransform uv; if (uv.setFromString(viewString)) { this->customViews.push_back(new ModelTransform(uv)); } } this->qSettings->endArray(); /* * Read Custom Views */ const int numCustomViews = this->qSettings->beginReadArray(NAME_CUSTOM_VIEWS); for (int i = 0; i < numCustomViews; i++) { this->qSettings->setArrayIndex(i); const AString viewString = this->qSettings->value(AString::number(i)).toString(); ModelTransform uv; if (uv.setFromString(viewString)) { this->customViews.push_back(new ModelTransform(uv)); } } this->qSettings->endArray(); } void CaretPreferences::getAnimationStartTime(double& time) { time = animationStartTime; } void CaretPreferences::setAnimationStartTime(const double& time) { animationStartTime = time; } /** * Get a description of this object's content. * @return String describing this object's content. */ AString CaretPreferences::toString() const { return "CaretPreferences"; } /** * Convert RGB bytes to Floats. * * @param bytesRGB * Byte RGB color [0, 255] INPUT * @param floatRGB * Float RGB color [0.0, 1.0] OUTPUT */ void CaretPreferences::byteRgbToFloatRgb(const uint8_t byteRGB[3], float floatRGB[3]) { for (int32_t i = 0; i < 3; i++) { floatRGB[i] = static_cast(byteRGB[i]) / 255.0; } } workbench-1.1.1/src/Common/CaretPreferences.h000066400000000000000000000375321255417355300211300ustar00rootroot00000000000000#ifndef __CARET_PREFERENCES__H_ #define __CARET_PREFERENCES__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretObject.h" #include "LogLevelEnum.h" #include "ImageCaptureMethodEnum.h" #include "OpenGLDrawingMethodEnum.h" #include "SpecFileDialogViewFilesTypeEnum.h" class QSettings; class QStringList; namespace caret { class ModelTransform; class TileTabsConfiguration; class CaretPreferences : public CaretObject { public: CaretPreferences(); virtual ~CaretPreferences(); void getColorForegroundAllView(uint8_t colorForeground[3]) const; void setColorForegroundAllView(const uint8_t colorForeground[3]); void getColorBackgroundAllView(uint8_t colorForeground[3]) const; void setColorBackgroundAllView(const uint8_t colorForeground[3]); void getColorForegroundChartView(uint8_t colorForeground[3]) const; void setColorForegroundChartView(const uint8_t colorForeground[3]); void getColorBackgroundChartView(uint8_t colorForeground[3]) const; void setColorBackgroundChartView(const uint8_t colorForeground[3]); void getColorForegroundSurfaceView(uint8_t colorForeground[3]) const; void setColorForegroundSurfaceView(const uint8_t colorForeground[3]); void getColorBackgroundSurfaceView(uint8_t colorForeground[3]) const; void setColorBackgroundSurfaceView(const uint8_t colorForeground[3]); void getColorForegroundVolumeView(uint8_t colorForeground[3]) const; void setColorForegroundVolumeView(const uint8_t colorForeground[3]); void getColorBackgroundVolumeView(uint8_t colorForeground[3]) const; void setColorBackgroundVolumeView(const uint8_t colorForeground[3]); void getColorChartMatrixGridLines(uint8_t colorChartMatrixGridLines[3]) const; void setColorChartMatrixGridLines(const uint8_t colorChartMatrixGridLines[3]); void getPreviousSpecFiles(std::vector& previousSpecFiles) const; void addToPreviousSpecFiles(const AString& specFileName); void clearPreviousSpecFiles(); void getPreviousOpenFileDirectories(std::vector& previousOpenFileDirectories) const; void getPreviousOpenFileDirectories(QStringList& previousOpenFileDirectories) const; void addToPreviousOpenFileDirectories(const AString& directoryName); LogLevelEnum::Enum getLoggingLevel() const; void setLoggingLevel(const LogLevelEnum::Enum loggingLevel); ImageCaptureMethodEnum::Enum getImageCaptureMethod() const; void setImageCaptureMethod(const ImageCaptureMethodEnum::Enum imageCaptureMethod); OpenGLDrawingMethodEnum::Enum getOpenDrawingMethod() const; void setOpenGLDrawingMethod(const OpenGLDrawingMethodEnum::Enum openGLDrawingMethod); bool isVolumeAxesCrosshairsDisplayed() const; void setVolumeAxesCrosshairsDisplayed(const bool displayed); bool isVolumeAxesLabelsDisplayed() const; void setVolumeAxesLabelsDisplayed(const bool displayed); bool isVolumeMontageAxesCoordinatesDisplayed() const; void setVolumeMontageAxesCoordinatesDisplayed(const bool displayed); int32_t getVolumeMontageGap() const; void setVolumeMontageGap(const int32_t volumeMontageGap); int32_t getVolumeMontageCoordinatePrecision() const; void setVolumeMontageCoordinatePrecision(const int32_t volumeMontageCoordinatePrecision); void setAnimationStartTime(const double &time); void getAnimationStartTime(double &time); bool isSplashScreenEnabled() const; void setSplashScreenEnabled(const bool enabled); bool isDevelopMenuEnabled() const; void setDevelopMenuEnabled(const bool enabled); void readTileTabsConfigurations(const bool performSync = true); std::vector getTileTabsConfigurationsSortedByName() const; TileTabsConfiguration* getTileTabsConfigurationByUniqueIdentifier(const AString& uniqueIdentifier); const TileTabsConfiguration* getTileTabsConfigurationByUniqueIdentifier(const AString& uniqueIdentifier) const; TileTabsConfiguration* getTileTabsConfigurationByName(const AString& name) const; void addTileTabsConfiguration(TileTabsConfiguration* tileTabsConfiguration); void removeTileTabsConfigurationByUniqueIdentifier(const AString& tileTabsUniqueIdentifier); void writeTileTabsConfigurations(); void readCustomViews(const bool performSync = true); std::vector getCustomViewNames() const; std::vector > getCustomViewNamesAndComments() const; bool getCustomView(const AString& customViewName, ModelTransform& modelTransformOut) const; void addOrReplaceCustomView(const ModelTransform& modelTransform); void removeCustomView(const AString& customViewName); bool isRemoteFilePasswordSaved(); void setRemoteFilePasswordSaved(const bool saveRemotePasswordToPreferences); void getRemoteFileUserNameAndPassword(AString& userNameOut, AString& passwordOut) const; void setRemoteFileUserNameAndPassword(const AString& userName, const AString& password); static void byteRgbToFloatRgb(const uint8_t byteRGB[3], float floatRGB[3]); bool isYokingDefaultedOn() const; void setYokingDefaultedOn(const bool status); bool isVolumeIdentificationDefaultedOn() const; void setVolumeIdentificationDefaultedOn(const bool status); SpecFileDialogViewFilesTypeEnum::Enum getManageFilesViewFileType() const; void setManageFilesViewFileType(const SpecFileDialogViewFilesTypeEnum::Enum manageFilesViewFileType); bool isShowSurfaceIdentificationSymbols() const; void setShowSurfaceIdentificationSymbols(const bool showSymbols); bool isShowVolumeIdentificationSymbols() const; void setShowVolumeIdentificationSymbols(const bool showSymbols); private: CaretPreferences(const CaretPreferences&); CaretPreferences& operator=(const CaretPreferences&); public: virtual AString toString() const; private: bool getBoolean(const AString& name, const bool defaultValue = false); void setBoolean(const AString& name, const bool value); int getInteger(const AString& name, const int defaultValue = false); void setInteger(const AString& name, const int value); AString getString(const AString& name, const AString& defaultValue = ""); void setString(const AString& name, const AString& value); void addToPrevious(std::vector& previousVector, const AString& newName); void readUnsignedByteArray(const AString& name, uint8_t array[], const int32_t numberOfElements); void writeUnsignedByteArray(const AString& name, const uint8_t array[], const int32_t numberOfElements); void readPreferences(); void removeAllTileTabsConfigurations(); void removeAllCustomViews(); void writeCustomViews(); mutable QSettings* qSettings; uint8_t colorForegroundAll[3]; uint8_t colorBackgroundAll[3]; uint8_t colorForegroundChart[3]; uint8_t colorBackgroundChart[3]; uint8_t colorForegroundSurface[3]; uint8_t colorBackgroundSurface[3]; uint8_t colorForegroundVolume[3]; uint8_t colorBackgroundVolume[3]; uint8_t colorChartMatrixGridLines[3]; std::vector previousSpecFiles; std::vector previousOpenFileDirectories; LogLevelEnum::Enum loggingLevel; ImageCaptureMethodEnum::Enum imageCaptureMethod; OpenGLDrawingMethodEnum::Enum openGLDrawingMethod; std::vector customViews; std::vector tileTabsConfigurations; bool displayVolumeAxesCrosshairs; bool displayVolumeAxesLabels; bool displayVolumeAxesCoordinates; int32_t volumeMontageGap; int32_t volumeMontageCoordinatePrecision; bool splashScreenEnabled; bool developMenuEnabled; double animationStartTime; bool volumeIdentificationDefaultedOn; bool showSurfaceIdentificationSymbols; bool showVolumeIdentificationSymbols; bool yokingDefaultedOn; AString remoteFileUserName; AString remoteFilePassword; bool remoteFileLoginSaved; SpecFileDialogViewFilesTypeEnum::Enum manageFilesViewFileType; static const AString NAME_ANIMATION_START_TIME; static const AString NAME_VOLUME_AXES_CROSSHAIRS; static const AString NAME_VOLUME_AXES_LABELS; static const AString NAME_VOLUME_AXES_COORDINATE; static const AString NAME_VOLUME_MONTAGE_GAP; static const AString NAME_VOLUME_MONTAGE_COORDINATE_PRECISION; static const AString NAME_COLOR_BACKGROUND; static const AString NAME_COLOR_FOREGROUND; static const AString NAME_COLOR_BACKGROUND_ALL; static const AString NAME_COLOR_FOREGROUND_ALL; static const AString NAME_COLOR_BACKGROUND_CHART; static const AString NAME_COLOR_FOREGROUND_CHART; static const AString NAME_COLOR_BACKGROUND_SURFACE; static const AString NAME_COLOR_FOREGROUND_SURFACE; static const AString NAME_COLOR_BACKGROUND_VOLUME; static const AString NAME_COLOR_FOREGROUND_VOLUME; static const AString NAME_COLOR_CHART_MATRIX_GRID_LINES; static const AString NAME_DEVELOP_MENU; static const AString NAME_IMAGE_CAPTURE_METHOD; static const AString NAME_LOGGING_LEVEL; static const AString NAME_MANAGE_FILES_VIEW_FILE_TYPE; static const AString NAME_OPENGL_DRAWING_METHOD; static const AString NAME_PREVIOUS_SPEC_FILES; static const AString NAME_PREVIOUS_OPEN_FILE_DIRECTORIES; static const AString NAME_SPLASH_SCREEN; static const AString NAME_CUSTOM_VIEWS; static const AString NAME_REMOTE_FILE_USER_NAME; static const AString NAME_REMOTE_FILE_PASSWORD; static const AString NAME_REMOTE_FILE_LOGIN_SAVED; static const AString NAME_SHOW_SURFACE_IDENTIFICATION_SYMBOLS; static const AString NAME_SHOW_VOLUME_IDENTIFICATION_SYMBOLS; static const AString NAME_TILE_TABS_CONFIGURATIONS; static const AString NAME_VOLUME_IDENTIFICATION_DEFAULTED_ON; static const AString NAME_YOKING_DEFAULT_ON; }; #ifdef __CARET_PREFERENCES_DECLARE__ const AString CaretPreferences::NAME_ANIMATION_START_TIME = "animationStartTime"; const AString CaretPreferences::NAME_VOLUME_AXES_CROSSHAIRS = "volumeAxesCrosshairs"; const AString CaretPreferences::NAME_VOLUME_AXES_LABELS = "volumeAxesLabels"; const AString CaretPreferences::NAME_VOLUME_AXES_COORDINATE = "volumeAxesCoordinates"; const AString CaretPreferences::NAME_VOLUME_MONTAGE_GAP = "volumeMontageGap"; const AString CaretPreferences::NAME_VOLUME_MONTAGE_COORDINATE_PRECISION = "volumeMontageCoordinatePrecision"; const AString CaretPreferences::NAME_COLOR_BACKGROUND = "colorBackground"; const AString CaretPreferences::NAME_COLOR_FOREGROUND = "colorForeground"; const AString CaretPreferences::NAME_COLOR_BACKGROUND_ALL = "colorBackgroundAll"; const AString CaretPreferences::NAME_COLOR_FOREGROUND_ALL = "colorForegroundAll"; const AString CaretPreferences::NAME_COLOR_BACKGROUND_CHART = "colorBackgroundChart"; const AString CaretPreferences::NAME_COLOR_FOREGROUND_CHART = "colorForegroundChart"; const AString CaretPreferences::NAME_COLOR_BACKGROUND_SURFACE = "colorBackgroundSurface"; const AString CaretPreferences::NAME_COLOR_FOREGROUND_SURFACE = "colorForegroundSurface"; const AString CaretPreferences::NAME_COLOR_BACKGROUND_VOLUME = "colorBackgroundVolume"; const AString CaretPreferences::NAME_COLOR_FOREGROUND_VOLUME = "colorForegroundVolume"; const AString CaretPreferences::NAME_COLOR_CHART_MATRIX_GRID_LINES = "colorChartMatrixGridLines"; const AString CaretPreferences::NAME_DEVELOP_MENU = "developMenu"; const AString CaretPreferences::NAME_IMAGE_CAPTURE_METHOD = "imageCaptureMethod"; const AString CaretPreferences::NAME_LOGGING_LEVEL = "loggingLevel"; const AString CaretPreferences::NAME_MANAGE_FILES_VIEW_FILE_TYPE = "manageFilesViewFileType"; const AString CaretPreferences::NAME_OPENGL_DRAWING_METHOD = "openGLDrawingMethod"; const AString CaretPreferences::NAME_PREVIOUS_SPEC_FILES = "previousSpecFiles"; const AString CaretPreferences::NAME_PREVIOUS_OPEN_FILE_DIRECTORIES = "previousOpenFileDirectories"; const AString CaretPreferences::NAME_SPLASH_SCREEN = "splashScreen"; const AString CaretPreferences::NAME_CUSTOM_VIEWS = "customViews"; const AString CaretPreferences::NAME_REMOTE_FILE_USER_NAME = "remoteFileUserName"; const AString CaretPreferences::NAME_REMOTE_FILE_PASSWORD = "remoteFilePassword"; const AString CaretPreferences::NAME_REMOTE_FILE_LOGIN_SAVED = "removeFileLoginSaved"; const AString CaretPreferences::NAME_SHOW_SURFACE_IDENTIFICATION_SYMBOLS = "showSurfaceIdentificationSymbols"; const AString CaretPreferences::NAME_SHOW_VOLUME_IDENTIFICATION_SYMBOLS = "showVolumeIdentificationSymbols"; const AString CaretPreferences::NAME_TILE_TABS_CONFIGURATIONS = "tileTabsConfigurations"; const AString CaretPreferences::NAME_VOLUME_IDENTIFICATION_DEFAULTED_ON = "volumeIdentificationDefaultedOn"; const AString CaretPreferences::NAME_YOKING_DEFAULT_ON = "yokingDefaultedOn"; #endif // __CARET_PREFERENCES_DECLARE__ } // namespace #endif //__CARET_PREFERENCES__H_ workbench-1.1.1/src/Common/CaretTemporaryFile.cxx000066400000000000000000000210601255417355300220110ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CARET_TEMPORARY_FILE_DECLARE__ #include "CaretTemporaryFile.h" #undef __CARET_TEMPORARY_FILE_DECLARE__ #include #include "CaretHttpManager.h" #include "DataFileException.h" using namespace caret; /** * \class caret::CaretTemporaryFile * \brief Reads and writes a temporary file. * \ingroup Common * * Reads and writes a temporary file. When an instance of this class * goes out of scope, the temporary file will be deleted. This class * is able to read a file that resides on an HTTP server (filename * starts with "http://"). * * QTemporaryFile is encapsulated by this class. */ /** * Constructor. */ CaretTemporaryFile::CaretTemporaryFile() : DataFile() { m_temporaryFile = NULL; initializeCaretTemporaryFile(); } /** * Destructor. */ CaretTemporaryFile::~CaretTemporaryFile() { delete m_temporaryFile; } /** * Initialize the temporary file.. */ void CaretTemporaryFile::initializeCaretTemporaryFile() { if (m_temporaryFile != NULL) { delete m_temporaryFile; } m_temporaryFile = new QTemporaryFile(); setFileName(m_temporaryFile->fileName()); } /** * Clear the temporary file. * Destroys the encapsulated QTemporaryFile. */ void CaretTemporaryFile::clear() { DataFile::clear(); initializeCaretTemporaryFile(); } /** * Is the file empty (contains no data)? * * @return * true if the file is empty, else false. */ bool CaretTemporaryFile::isEmpty() const { const bool fileEmpty = (m_temporaryFile->size() <= 0); return fileEmpty; } AString CaretTemporaryFile::getFileName() const { return m_temporaryFile->fileName(); } AString CaretTemporaryFile::getFileNameNoPath() const { CaretTemporaryFile* ctf = const_cast(this); ctf->setFileName(m_temporaryFile->fileName()); return DataFile::getFileNameNoPath(); } /** * This method does nothing as the temporary file's name * is generated by QTemporaryFile. * * @param filename * Name for file. */ void CaretTemporaryFile::setFileName(const AString& filename) { /* * Needed for getFileNameNoPath() functionality. */ DataFile::setFileName(filename); } /** * Read the file at the given path into the temporary file. * * @param filename * Name of the data file. * @throws DataFileException * If the file was not successfully read. */ void CaretTemporaryFile::readFile(const AString& filename) { if (DataFile::isFileOnNetwork(filename)) { /* * Read file on network. * Sort of a kludge, read from the network as a string of bytes * and then write the bytes to a temporary file. */ CaretHttpRequest request; request.m_method = CaretHttpManager::GET; request.m_url = filename; CaretHttpResponse response; CaretHttpManager::httpRequest(request, response); if (response.m_ok == false) { QString msg = ("HTTP error HTTP Response Code=" + AString::number(response.m_responseCode)); throw DataFileException(filename, msg); } const int64_t numBytes = response.m_body.size(); if (numBytes > 0) { AString tempPath = QDir::tempPath(); if (!tempPath.endsWith('/')) tempPath += '/';//qt decided to make this behavior OS-dependent, for no apparent reason m_temporaryFile->setFileTemplate(tempPath + "qt_temp.XXXXXX." + filename.section('/', -1).section('.', 1));//strip all but the filename, then take all the parts of the extension if (m_temporaryFile->open()) { const int64_t numBytesWritten = m_temporaryFile->write(&response.m_body[0], numBytes); if (numBytesWritten != numBytes) { throw DataFileException(filename, " Tried to write " + QString::number(numBytes) + " bytes to temporary file " + m_temporaryFile->fileName() + " but only wrote " + AString::number(numBytesWritten) + " bytes."); } m_temporaryFile->close(); } else { throw DataFileException(filename, "Unable to open temporary file for writing its content."); } } else { throw DataFileException(filename, "Failed to read any data from file."); } } else { /* * Read local file. */ QFile file(filename); checkFileReadability(filename); if (file.open(QFile::ReadOnly)) { QByteArray byteArray = file.readAll(); file.close(); const int numBytes = byteArray.length(); if (numBytes > 0) { if (m_temporaryFile->open()) { const int64_t numBytesWritten = m_temporaryFile->write(byteArray); if (numBytesWritten != numBytes) { throw DataFileException(filename, " Tried to write " + QString::number(numBytes) + " bytes to temporary file " + m_temporaryFile->fileName() + " but only wrote " + AString::number(numBytesWritten) + " bytes."); } m_temporaryFile->close(); } else { throw DataFileException(m_temporaryFile->fileName(), "Unable to open temporary file for writing its content."); } } else { throw DataFileException(filename, "No data read from file, is it empty?"); } } else { throw DataFileException(filename, "Unable to open file for reading its content."); } } } /** * Write the contents of the temporary file to a local file with * the given name. * * @param filename * Name of the local data file. * @throws DataFileException * If the file was not successfully written. */ void CaretTemporaryFile::writeFile(const AString& filename) { checkFileWritability(filename); if (isEmpty()) { throw DataFileException(filename, "No data (temporary file is empty) to write to file."); } const QString tempFileName = m_temporaryFile->fileName(); QFile fileIn(tempFileName); if (fileIn.open(QFile::ReadOnly)) { QByteArray byteArray = fileIn.readAll(); fileIn.close(); QFile fileOut(filename); if (fileOut.open(QFile::WriteOnly)) { fileOut.write(byteArray); fileOut.close(); } else { fileOut.close(); throw DataFileException(filename, "Unable to open file for writing its content."); } } else { fileIn.close(); throw DataFileException(tempFileName, "Unable to open temporary file for reading its content."); } } workbench-1.1.1/src/Common/CaretTemporaryFile.h000066400000000000000000000040211255417355300214340ustar00rootroot00000000000000#ifndef __CARET_TEMPORARY_FILE_H__ #define __CARET_TEMPORARY_FILE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "DataFile.h" class QTemporaryFile; namespace caret { class CaretTemporaryFile : public DataFile { public: CaretTemporaryFile(); virtual ~CaretTemporaryFile(); virtual void clear(); virtual bool isEmpty() const; virtual AString getFileName() const; virtual AString getFileNameNoPath() const; virtual void setFileName(const AString& filename); virtual void readFile(const AString& filename); virtual void writeFile(const AString& filename); // ADD_NEW_METHODS_HERE private: CaretTemporaryFile(const CaretTemporaryFile&); CaretTemporaryFile& operator=(const CaretTemporaryFile&); private: void initializeCaretTemporaryFile(); QTemporaryFile* m_temporaryFile; // ADD_NEW_MEMBERS_HERE }; #ifdef __CARET_TEMPORARY_FILE_DECLARE__ // #endif // __CARET_TEMPORARY_FILE_DECLARE__ } // namespace #endif //__CARET_TEMPORARY_FILE_H__ workbench-1.1.1/src/Common/CaretUndoCommand.cxx000066400000000000000000000100121255417355300214260ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ /* * NOTE: The Qt license is included because the CaretUndoStack API * including the method comments is copied from QUndoStack. */ /**************************************************************************** ** ** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the QtOpenGL module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Digia. For licensing terms and ** conditions see http://qt.digia.com/licensing. For further information ** use the contact form at http://qt.digia.com/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Digia gives you certain additional ** rights. These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #define __CARET_UNDO_COMMAND_DECLARE__ #include "CaretUndoCommand.h" #undef __CARET_UNDO_COMMAND_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::CaretUndoCommand * \brief Abstract class for a command that supports 'undo' and 'redo'. * \ingroup Common * * Abstract class for commands that allow 'undo' and 'redo'. * * Design is based off Qt's QUndoCommand. * * While using QUndoCommand would be preferred, it cannot be used because: * (1) It is located in Qt's GUI module which is not accessible to * Caret's "lower level" modules. * (2) QUndoCommand is targeted to text documents. * */ /** * Constructor. */ CaretUndoCommand::CaretUndoCommand() : CaretObject() { } /** * Destructor. */ CaretUndoCommand::~CaretUndoCommand() { } /** * @return Description of command. */ AString CaretUndoCommand::getDescription() const { return m_description; } /** * Set description of command. * @param descripton * New value for description of command. */ void CaretUndoCommand::setDescription(const AString& description) { m_description = description; } workbench-1.1.1/src/Common/CaretUndoCommand.h000066400000000000000000000076551255417355300210760ustar00rootroot00000000000000#ifndef __CARET_UNDO_COMMAND_H__ #define __CARET_UNDO_COMMAND_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ /* * NOTE: The Qt license is included because the CaretUndoStack API * including the method comments is copied from QUndoStack. */ /**************************************************************************** ** ** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the QtOpenGL module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Digia. For licensing terms and ** conditions see http://qt.digia.com/licensing. For further information ** use the contact form at http://qt.digia.com/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Digia gives you certain additional ** rights. These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "CaretObject.h" namespace caret { class CaretUndoCommand : public CaretObject { public: CaretUndoCommand(); virtual ~CaretUndoCommand(); AString getDescription() const; void setDescription(const AString& description); /** * Operation that "redoes" the command. */ virtual void redo() = 0; /** * Operation that "undoes" the command. */ virtual void undo() = 0; // ADD_NEW_METHODS_HERE private: CaretUndoCommand(const CaretUndoCommand&); CaretUndoCommand& operator=(const CaretUndoCommand&); AString m_description; // ADD_NEW_MEMBERS_HERE }; #ifdef __CARET_UNDO_COMMAND_DECLARE__ // #endif // __CARET_UNDO_COMMAND_DECLARE__ } // namespace #endif //__CARET_UNDO_COMMAND_H__ workbench-1.1.1/src/Common/CaretUndoStack.cxx000066400000000000000000000263621255417355300211340ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ /* * NOTE: The Qt license is included because the CaretUndoStack API * including the method comments is copied from QUndoStack. */ /**************************************************************************** ** ** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the QtOpenGL module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Digia. For licensing terms and ** conditions see http://qt.digia.com/licensing. For further information ** use the contact form at http://qt.digia.com/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Digia gives you certain additional ** rights. These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #define __CARET_UNDO_STACK_DECLARE__ #include "CaretUndoStack.h" #undef __CARET_UNDO_STACK_DECLARE__ #include "CaretAssert.h" #include "CaretLogger.h" #include "CaretUndoCommand.h" using namespace caret; /** * \class caret::CaretUndoStack * \brief An abstract class for an undo stack implementation., * \ingroup Common * * Stack for undo and redo of commands. * * Design is based off Qt's QUndoStack. * * While using QUndoStack would be preferred, it cannot be used because: * (1) It is located in Qt's GUI module which is not accessible to * Caret's "lower level" modules. * (2) QUndoStack is targeted to text documents. * * An undo stack maintains a stack of commands that have been * applied to a document. * * New commands are pushed on the stack using push(). Commands can be undone * and redone using undo() and redo(). * * CaretUndoStack keeps track of the current command. This is the command which * will be executed by the next call to redo(). The index of this command is * returned by index(). If the top-most command on the stack has already * been redone, index() is equal to count(). */ /** * Constructs an empty undo stack in the "clean" state. */ CaretUndoStack::CaretUndoStack() : CaretObject() { m_undoLimit = 0; m_undoStackIndex = 0; } /** * Destroys the undo stack, deleting any commands that are on it. */ CaretUndoStack::~CaretUndoStack() { clear(); } /** * Returns true if there is a command available for redo; * otherwise returns false. * * This function returns false if the stack is empty or if the top * command on the stack has already been redone. * * While the Qt documentation states "Synonymous with index() == count()" * the actual QUndoStack code indicates the comment should be * "Synonymous with index() < count()". */ bool CaretUndoStack::canRedo() const { if (m_undoStack.empty()) { return false; } if (index() < count()) { return true; } return false; } /** * Returns true if there is a command available for undo; otherwise * returns false. * * This function returns false if the stack is empty, or if the bottom * command on the stack has already been undone. * * While the Qt documentation staties "Synonymous with index() == 0", viewing * the actual QUndoStack code indicates the comment should be * "Synonymous with index() > 0". */ bool CaretUndoStack::canUndo() const { if (m_undoStack.empty()) { return false; } if (index() > 0) { return true; } return false; } /** * @return Number of commands on the undo stack. */ int32_t CaretUndoStack::count() const { return m_undoStack.size(); } /** * Returns the index of the current command. This is the command that will * be executed on the next call to redo(). It is not always the top-most * command on the stack, since a number of commands may have been undone. * * @return Index of current command. This value ranges from 1 to count() * when the stack contains elements and zero when the stack is empty. */ int32_t CaretUndoStack::index() const { return m_undoStackIndex; } /** * Clears the command stack by deleting all commands on it, and returns * the stack to the clean state. * * Commands are not undone or redone; the state of the edited object * remains unchanged. * * This function is usually used when the contents of the document * are abandoned. */ void CaretUndoStack::clear() { for (std::deque::iterator iter = m_undoStack.begin(); iter != m_undoStack.end(); iter++) { delete *iter; } m_undoStack.clear(); m_undoStackIndex = 0; } /** * Returns a const pointer to the command at index. * * This function returns a const pointer, because modifying a command, once * it has been pushed onto the stack and executed, almost always causes * corruption of the state of the document, if the command is later * undone or redone. * * @param index * Index of the item in the undo stack. * @return * Item at the given index or NULL if the index is invalid. */ const CaretUndoCommand* CaretUndoStack::command(const int32_t index) const { if ((index >= 0) || (index < static_cast(m_undoStack.size()))) { return m_undoStack.at(index); } else { CaretLogWarning("CaretUndoStack::command() called with invalid index=" + AString::number(index)); } return NULL; } /** * Pushes the given command onto the stack. Unlike QUndoStack the * command's redo() method IS NOT called. * * If commands were undone before cmd was pushed, the current command and * all commands above it are deleted. Hence cmd always ends up being the * top-most on the stack. * * Once a command is pushed, the stack takes ownership of it. There are * no getters to return the command, since modifying it after it has been * executed will almost always lead to corruption of the document's state. * * @param cmd * Command pushed onto the stack. */ void CaretUndoStack::push(CaretUndoCommand* cmd) { if (index() < count()) { /* * Delete any commands that have been "undone" */ const int numToDeleteAtBack = count() - index(); for (int32_t i = 0; i < numToDeleteAtBack; i++) { delete m_undoStack.back(); m_undoStack.pop_back(); } } m_undoStack.push_back(cmd); if (m_undoLimit > 0) { /* * Delete oldest command(s) when undo limit is exceeded */ const int32_t numToDeleteAtFront = count() - m_undoLimit; for (int32_t i = 0; i < numToDeleteAtFront; i++) { delete m_undoStack.front(); m_undoStack.pop_front(); } } m_undoStackIndex = count(); } /** * Redoes the current command by calling QUndoCommand::redo(). * Increments the current command index. * * If the stack is empty, or if the top command on the stack has already * been redone, this function does nothing. */ void CaretUndoStack::redo() { if (m_undoStack.empty()) { return; } if ((m_undoStackIndex >= 0) && (m_undoStackIndex < count())) { redoCommand(m_undoStack.at(m_undoStackIndex)); ++m_undoStackIndex; } } /** * Undoes the command below the current command by calling QUndoCommand::undo(). * Decrements the current command index. * * If the stack is empty, or if the bottom command on the stack has already * been undone, this function does nothing. */ void CaretUndoStack::undo() { if (m_undoStack.empty()) { return; } if ((m_undoStackIndex > 0) && (m_undoStackIndex <= count())) { --m_undoStackIndex; undoCommand(m_undoStack.at(m_undoStackIndex)); } } /** * When the number of commands on a stack exceedes the stack's undo limit, * commands are deleted from the bottom of the stack. The default * value is 0, which means that there is no limit. * * This property may only be set when the undo stack is empty, since setting * it on a non-empty stack might delete the command at the current index. * Calling setUndoLimit() on a non-empty stack and a warning is logged. * * @param undoLimit * New value for maximum number of undo commands. */ void CaretUndoStack::setUndoLimit(const int32_t undoLimit) { if (m_undoStack.empty()) { if (undoLimit >= 0) { m_undoLimit = undoLimit; } CaretLogWarning("CaretUndoStack::setUndoLimit() called with invalid value=" + AString::number(undoLimit)); } else { CaretLogWarning("CaretUndoStack::setUndoLimit() called while undo stack contains elements." " New undo limit ignored."); } } /** * Apply a 'redo' using the given command. * * Subclasses may override this method. * * @param cmd * Command that performs a 'redo'. */ void CaretUndoStack::redoCommand(CaretUndoCommand* cmd) { CaretAssert(cmd); cmd->redo(); } /** * Apply an 'undo' using the given command. * * Subclasses may override this method. * * @param cmd * Command that performs a 'undo'. */ void CaretUndoStack::undoCommand(CaretUndoCommand* cmd) { CaretAssert(cmd); cmd->undo(); } /** * Undo ALL changes. */ void CaretUndoStack::undoAll() { while (canUndo()) { undo(); } } workbench-1.1.1/src/Common/CaretUndoStack.h000066400000000000000000000110731255417355300205520ustar00rootroot00000000000000#ifndef __CARET_UNDO_STACK_H__ #define __CARET_UNDO_STACK_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ /* * NOTE: The Qt license is included because the CaretUndoStack API * including the method comments is copied from QUndoStack. */ /**************************************************************************** ** ** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the QtOpenGL module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Digia. For licensing terms and ** conditions see http://qt.digia.com/licensing. For further information ** use the contact form at http://qt.digia.com/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Digia gives you certain additional ** rights. These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include "CaretObject.h" namespace caret { class CaretUndoCommand; class CaretUndoStack : public CaretObject { public: CaretUndoStack(); virtual ~CaretUndoStack(); bool canUndo() const; bool canRedo() const; void clear(); int32_t count() const; int32_t index() const; const CaretUndoCommand* command(const int32_t index) const; void push(CaretUndoCommand* cmd); void redo(); void undo(); void undoAll(); void setUndoLimit(const int32_t undoLimit); // ADD_NEW_METHODS_HERE protected: virtual void redoCommand(CaretUndoCommand* cmd); virtual void undoCommand(CaretUndoCommand* cmd); private: CaretUndoStack(const CaretUndoStack&); CaretUndoStack& operator=(const CaretUndoStack&); /** * The undo "stack". A deque is used so that items * can be removed when the size of the "stack" exceeds * the undo limit. */ std::deque m_undoStack; /** * The index of the current command. */ int32_t m_undoStackIndex; int32_t m_undoLimit; // ADD_NEW_MEMBERS_HERE }; #ifdef __CARET_UNDO_STACK_DECLARE__ // #endif // __CARET_UNDO_STACK_DECLARE__ } // namespace #endif //__CARET_UNDO_STACK_H__ workbench-1.1.1/src/Common/CubicSpline.cxx000066400000000000000000000067461255417355300204660ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CubicSpline.h" #include "CaretAssert.h" using namespace caret; CubicSpline::CubicSpline() { } CubicSpline CubicSpline::hermite(float frac, bool lowEdge, bool highEdge) {//these equations are derived from hermite basis functions, plug the commented m0, m1 into the hermite representation to rederive CaretAssert(frac > -0.01f && frac < 1.01f);//give some leeway for rounding errors CubicSpline ret; float frac2 = frac * frac; float frac3 = frac2 * frac; if (lowEdge) {//edge case: m0 = p[2] - p[1] if (highEdge) {//edge case: m1 = p[2] - p[1] - makes it linear interpolation - why are you doing cubic spline with only 2 points? ret.m_weights[0] = 0.0f; ret.m_weights[1] = 1.0f - frac; ret.m_weights[2] = frac; ret.m_weights[3] = 0.0f; } else {//m1 = (p[3] - p[1]) / 2 ret.m_weights[0] = 0.0f; ret.m_weights[1] = 0.5f * frac3 - 0.5 * frac2 - frac + 1.0f;//.5t^3 - .5t^2 - t + 1 ret.m_weights[2] = -frac3 + frac2 + frac;//-t^3 + t^2 + t ret.m_weights[3] = 0.5f * frac3 - frac2;//.5t^3 - t^2 } } else {//m0 = (p[2] - p[0]) / 2 if (highEdge) {//edge case: m1 = p[2] - p[1] ret.m_weights[0] = -0.5f * frac3 + frac2 - 0.5f * frac;//-.5t^3 + t^2 - .5t ret.m_weights[1] = frac3 - 2.0f * frac2 + 1;//t^3 - 2t^2 + 1 ret.m_weights[2] = -0.5f * frac3 + frac2 + 0.5f * frac;//-.5t^3 + t^2 + .5t ret.m_weights[3] = 0.0f; } else {//m1 = (p[3] - p[1]) / 2 -- majority case ret.m_weights[0] = -0.5f * frac3 + frac2 - 0.5f * frac;//-.5t^3 + t^2 - .5t ret.m_weights[1] = 1.5f * frac3 - 2.5f * frac2 + 1.0f;//1.5t^3 - 2.5t^2 + 1 ret.m_weights[2] = -1.5f * frac3 + 2.0f * frac2 + 0.5f * frac;//-1.5t^3 + 2t^2 + .5t ret.m_weights[3] = 0.5f * frac3 - frac2;//.5t^3 - t^2 } } return ret; } CubicSpline CubicSpline::bspline(float frac, bool lowEdge, bool highEdge) { CaretAssert(frac > -0.01f && frac < 1.01f);//give some leeway for rounding errors CubicSpline ret; float frac2 = frac * frac; float frac3 = frac2 * frac; ret.m_weights[1] = (3.0f * frac3 - 6.0f * frac2 + 4.0f) / 6.0f; ret.m_weights[2] = (-3.0f * frac3 + 3.0f * frac2 + 3.0f * frac + 1.0f) / 6.0f; if (lowEdge) { ret.m_weights[0] = 0.0f;//assume outside range is zero } else { ret.m_weights[0] = (-frac3 + 3.0f * frac2 - 3.0f * frac + 1.0f) / 6.0f;//the standard blending function } if (highEdge) { ret.m_weights[3] = 0.0f; } else { ret.m_weights[3] = frac3 / 6.0f; } return ret; } workbench-1.1.1/src/Common/CubicSpline.h000066400000000000000000000051161255417355300201010ustar00rootroot00000000000000#ifndef __CUBIC_SPLINE_H__ #define __CUBIC_SPLINE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ namespace caret { class CubicSpline { float m_weights[4]; CubicSpline(); public: ///takes as input the fraction in [0, 1] along the middle (used) range of the spline, low and high edge set whether it doesn't have p[0] or p[3] to use, respectively static CubicSpline hermite(float frac, bool lowEdge, bool highEdge); ///NOTE: data should be deconvolved before using this spline static CubicSpline bspline(float frac, bool lowEdge, bool highEdge); //splines will be reused, so this part should be fast for the majority case (testing for if it is an edge case would slow it down for the majority case) ///evaluate the spline with these samples inline float evaluate(const float p0, const float p1, const float p2, const float p3) const { return p0 * m_weights[0] + p1 * m_weights[1] + p2 * m_weights[2] + p3 * m_weights[3]; } ///convenience function for edge evaluating without a dummy argument inline float evalLowEdge(const float p1, const float p2, const float p3) { return p1 * m_weights[1] + p2 * m_weights[2] + p3 * m_weights[3]; } ///convenience function for edge evaluating without a dummy argument inline float evalHighEdge(const float p0, const float p1, const float p2) { return p0 * m_weights[0] + p1 * m_weights[1] + p2 * m_weights[2]; } ///convenience function for edge evaluating without dummy arguments inline float evalBothEdge(const float p1, const float p2) { return p1 * m_weights[1] + p2 * m_weights[2]; } }; } #endif //__CUBIC_SPLINE_H__ workbench-1.1.1/src/Common/DataCompressZLib.cxx000066400000000000000000000100551255417355300214200ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ /*========================================================================= Program: Visualization Toolkit Module: $RCSfile: DataCompressZLib.cxx,v $ Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen All rights reserved. See Copyright.txt or http://www.kitware.com/Copyright.htm for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notice for more information. =========================================================================*/ #include "DataCompressZLib.h" #include "MathFunctions.h" #include "zlib.h" using namespace caret; //---------------------------------------------------------------------------- DataCompressZLib::DataCompressZLib() { this->compressionLevel = Z_DEFAULT_COMPRESSION; } //---------------------------------------------------------------------------- DataCompressZLib::~DataCompressZLib() { } int32_t DataCompressZLib::getCompressionLevel() { return this->compressionLevel; } void DataCompressZLib::setCompressionLevel(const int32_t compressionLevel) { this->compressionLevel = MathFunctions::clamp(compressionLevel, 0, 9); } //---------------------------------------------------------------------------- uint64_t DataCompressZLib::compressData(const unsigned char* uncompressedData, uint64_t uncompressedSize, unsigned char* compressedData, const uint64_t compressionSpace) { uLongf compressedSize = compressionSpace; Bytef* cd = reinterpret_cast(compressedData); const Bytef* ud = reinterpret_cast(uncompressedData); // Call zlib's compress function. if(compress2(cd, &compressedSize, ud, uncompressedSize, this->compressionLevel) != Z_OK) { //vtkErrorMacro("Zlib error while compressing data."); return 0; } return compressedSize; } //---------------------------------------------------------------------------- uint64_t DataCompressZLib::uncompressData(const unsigned char* compressedData, uint64_t compressedSize, unsigned char* uncompressedData, uint64_t uncompressedSize) { uLongf decSize = uncompressedSize; Bytef* ud = reinterpret_cast(uncompressedData); const Bytef* cd = reinterpret_cast(compressedData); // Call zlib's uncompress function. if(uncompress(ud, &decSize, cd, compressedSize) != Z_OK) { //vtkErrorMacro("Zlib error while uncompressing data."); return 0; } // Make sure the output size matched that expected. if(decSize != uncompressedSize) { //vtkErrorMacro("Decompression produced incorrect size.\n" // "Expected " << uncompressedSize << " and got " << decSize); return 0; } return decSize; } //---------------------------------------------------------------------------- unsigned long DataCompressZLib::getMaximumCompressionSpace(unsigned long size) { // ZLib specifies that destination buffer must be 0.1% larger + 12 bytes. return size + (size+999)/1000 + 12; } workbench-1.1.1/src/Common/DataCompressZLib.h000066400000000000000000000062041255417355300210460ustar00rootroot00000000000000#ifndef __DATA_COMPRESS_ZLIB_H__ #define __DATA_COMPRESS_ZLIB_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ /*========================================================================= Program: Visualization Toolkit Module: $RCSfile: DataCompressZLib.h,v $ Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen All rights reserved. See Copyright.txt or http://www.kitware.com/Copyright.htm for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notice for more information. =========================================================================*/ // .NAME DataCompressZLib - Data compression using zlib. // .SECTION Description // DataCompressZLib provides a concrete vtkDataCompressor class // using zlib for compressing and uncompressing data. #include #include "CaretObject.h" namespace caret { /* * Copied from vtkZLibDataCompresson. */ class DataCompressZLib : public CaretObject { public: DataCompressZLib(); ~DataCompressZLib(); // Description: // Get the maximum space that may be needed to store data of the // given uncompressed size after compression. This is the minimum // size of the output buffer that can be passed to the four-argument // Compress method. unsigned long getMaximumCompressionSpace(unsigned long size); // Description: // Get/Set the compression level. //vtkSetClampMacro(CompressionLevel, int, 0, 9); //vtkGetMacro(CompressionLevel, int); int32_t getCompressionLevel(); void setCompressionLevel(const int32_t compressionLevel); // Compression method required by vtkDataCompressor. uint64_t compressData(const unsigned char* uncompressedData, uint64_t uncompressedSize, unsigned char* compressedData, const uint64_t compressionSpace); // Decompression method required by vtkDataCompressor. uint64_t uncompressData(const unsigned char* compressedData, uint64_t compressedSize, unsigned char* uncompressedData, uint64_t uncompressedSiz); protected: int compressionLevel; }; } // namespace #endif // __DATA_COMPRESS_ZLIB_H__ workbench-1.1.1/src/Common/DataFile.cxx000066400000000000000000000131541255417355300177260ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "DataFile.h" #include "DataFileContentInformation.h" #include "DataFileException.h" #include "FileInformation.h" using namespace caret; /** * Constructor. */ DataFile::DataFile() : CaretObject(), DataFileInterface() { this->initializeMembersDataFile(); } /** * Destructor. */ DataFile::~DataFile() { this->initializeMembersDataFile(); } /** * Copy constructor. * @param df * Data file that is copied. */ DataFile::DataFile(const DataFile& df) : CaretObject(df), DataFileInterface() { this->copyHelperDataFile(df); } /** * Assignment operator. * @param df * Contents that is assigned to this file. * @return * This file assigned the contents of "df". */ DataFile& DataFile::operator=(const DataFile& df) { if (this != &df) { CaretObject::operator=(df); this->copyHelperDataFile(df); } return *this; } /** * Assists with copying file's contents. */ void DataFile::copyHelperDataFile(const DataFile& df) { this->filename = df.filename; modifiedFlag = false; } /** * Initialize the members of the data file. */ void DataFile::initializeMembersDataFile() { this->filename = ""; modifiedFlag = false; } /** * Clear the contents of the file. */ void DataFile::clear() { this->initializeMembersDataFile(); } /** * @return Name of the data file including any path. */ AString DataFile::getFileName() const { return this->filename; } /** * @return Name of the data file excluding any path. */ AString DataFile::getFileNameNoPath() const { FileInformation fileInfo(this->filename); return fileInfo.getFileName(); } /** * Set the name of the data file. * * @param filename * New name of data file. */ void DataFile::setFileName(const AString& filename) { if (this->filename != filename) { this->filename = filename; this->setModified(); } } /** * Add information about the file to the data file information. * * @param dataFileInformation * Consolidates information about a data file. */ void DataFile::addToDataFileContentInformation(DataFileContentInformation& dataFileInformation) { dataFileInformation.addNameAndValue("Name", getFileName()); } /** * Set the status to modified. */ void DataFile::setModified() { this->modifiedFlag = true; } /** * Set the status to unmodified. */ void DataFile::clearModified() { this->modifiedFlag = false; } /** * Is the object modified? * @return true if modified, else false. */ bool DataFile::isModified() const { return this->modifiedFlag; } /** * Is the filename a path on the network (http:// etc) * @param filename * Name of file. * @return true if filename appears to be a network path. */ bool DataFile::isFileOnNetwork(const AString& filename) { if (filename.startsWith("http://") || filename.startsWith("https://")) { return true; } return false; } /** * If the filename is local, make sure the file exists and * its permissions allow the file to be read. * * @param filename * Name of file. * @throws DataFileException * If there is a problem that will prevent the file from being read. */ void DataFile::checkFileReadability(const AString& filename) { if (filename.isEmpty()) { throw DataFileException("Name of file for reading is empty."); } if (isFileOnNetwork(filename)) { return; } FileInformation fileInfo(filename); if (fileInfo.exists() == false) { throw DataFileException(filename, "File does not exist."); } if (fileInfo.isDirectory()) { throw DataFileException(filename, "Filename is a directory, not a file."); } if (fileInfo.isReadable() == false) { throw DataFileException(filename, "File is not readable due its permissions."); } } /** * If the filename is local, see if the file exists and * its permissions allow the file to be written. * * @param filename * Name of file. * @throws DataFileException * If there is a problem that will prevent the file from being read. */ void DataFile::checkFileWritability(const AString& filename) { if (filename.isEmpty()) { throw DataFileException("Name of file for writing is empty."); } if (isFileOnNetwork(filename)) { return; } FileInformation fileInfo(filename); if (fileInfo.exists()) { if (fileInfo.isDirectory()) { throw DataFileException(filename, "Filename is a directory, not a file."); } if (fileInfo.isWritable() == false) { throw DataFileException(filename, "File is not writable due its permissions."); } } } workbench-1.1.1/src/Common/DataFile.h000066400000000000000000000056151255417355300173560ustar00rootroot00000000000000#ifndef __DATAFILE_H__ #define __DATAFILE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretObject.h" #include "DataFileInterface.h" namespace caret { class DataFileContentInformation; /** * Abstract Data File. */ class DataFile : public CaretObject, public DataFileInterface { protected: DataFile(); virtual ~DataFile(); DataFile(const DataFile& s); DataFile& operator=(const DataFile&); public: virtual AString getFileName() const; virtual AString getFileNameNoPath() const; virtual void setFileName(const AString& filename); virtual void addToDataFileContentInformation(DataFileContentInformation& dataFileInformation); virtual void setPreferOnDiskReading(const bool&) { } /** * Read the data file. * * @param filename * Name of the data file. * @throws DataFileException * If the file was not successfully read. */ virtual void readFile(const AString& filename) = 0; /** * Write the data file. * * @param filename * Name of the data file. * @throws DataFileException * If the file was not successfully written. */ virtual void writeFile(const AString& filename) = 0; virtual void setModified(); virtual void clearModified(); virtual bool isModified() const; virtual void clear(); static bool isFileOnNetwork(const AString& filename); void checkFileReadability(const AString& filename); void checkFileWritability(const AString& filename); private: void copyHelperDataFile(const DataFile& df); void initializeMembersDataFile(); /** name of data file */ AString filename; /** modification status */ bool modifiedFlag; }; } // namespace #endif // __DATAFILE_H__ workbench-1.1.1/src/Common/DataFileContentInformation.cxx000066400000000000000000000137251255417355300234730ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __DATA_FILE_CONTENT_INFORMATION_DECLARE__ #include "DataFileContentInformation.h" #undef __DATA_FILE_CONTENT_INFORMATION_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::DataFileContentInformation * \brief Assembles and provides information about a data file. * \ingroup Common */ /** * Constructor. */ DataFileContentInformation::DataFileContentInformation() : CaretObject() { /* * Initialize options */ setOptionFlag(OPTION_SHOW_MAP_INFORMATION, true); setOptionFlag(OPTION_SHOW_CIFTI_LABEL_MAPPING, false); } /** * Destructor. */ DataFileContentInformation::~DataFileContentInformation() { } /** * Add a name and value pair. * * @param name * The name. * @param value * The value. */ void DataFileContentInformation::addNameAndValue(const AString& name, const AString& value) { QChar colonChar = ' '; if (name.indexNotOf(' ') >= 0) { colonChar = ':'; } m_namesAndValues.push_back(std::make_pair((name + colonChar), value)); } /** * Add a name and value pair. * * @param name * The name. * @param value * The value. */ void DataFileContentInformation::addNameAndValue(const AString& name, const int32_t value) { addNameAndValue(name, AString::number(value)); // m_namesAndValues.push_back(std::make_pair((name + ":"), // AString::number(value))); } /** * Add a name and value pair. * * @param name * The name. * @param value * The value. */ void DataFileContentInformation::addNameAndValue(const AString& name, const int64_t value) { addNameAndValue(name, AString::number(value)); // m_namesAndValues.push_back(std::make_pair((name + ":"), // AString::number(value))); } /** * Add a name and value pair. * * @param name * The name. * @param value * The value. */ void DataFileContentInformation::addNameAndValue(const AString& name, const double value, const int32_t precision) { addNameAndValue(name, AString::number(value, 'f', precision)); // m_namesAndValues.push_back(std::make_pair((name + ":"), // AString::number(value, 'f', precision))); } /** * Add a name and value pair. * * @param name * The name. * @param value * The value. */ void DataFileContentInformation::addNameAndValue(const AString& name, const bool value) { addNameAndValue(name, AString::fromBool(value)); // m_namesAndValues.push_back(std::make_pair((name + ":"), // AString::fromBool(value))); } /** * Add freeform text. * * @param text * Text that is added. */ void DataFileContentInformation::addText(const AString& text) { m_text.append(text); } /** * @return All of the information in a string. * * The names and labels are formatted so that the labels and * values are aligned. */ AString DataFileContentInformation::getInformationInString() const { AString textOut; const int32_t numNamesAndValues = static_cast(m_namesAndValues.size()); if (numNamesAndValues > 0) { int32_t longestLabelLength = 0; for (int32_t i = 0; i < numNamesAndValues; i++) { longestLabelLength = std::max(m_namesAndValues[i].first.length(), longestLabelLength); } longestLabelLength += 3; for (int32_t i = 0; i < numNamesAndValues; i++) { AString label = m_namesAndValues[i].first; textOut.appendWithNewLine(label.leftJustified(longestLabelLength) + m_namesAndValues[i].second); } textOut.append("\n"); } textOut.append(m_text); return textOut; } /** * Is an option flag on? * * @param optionFlag * The option flag. * @return * True if the option flag is on, else false. */ bool DataFileContentInformation::isOptionFlag(const OptionFlag optionFlag) const { std::map::const_iterator iter = m_optionFlags.find(optionFlag); if (iter != m_optionFlags.end()) { return iter->second; } return false; } /** * Set an option flag. * * @param optionFlag * The option flag. * @param flagValue * New value for option flag. */ void DataFileContentInformation::setOptionFlag(const OptionFlag optionFlag, const bool flagValue) { std::map::iterator iter = m_optionFlags.find(optionFlag); if (iter != m_optionFlags.end()) { iter->second = flagValue; } else { m_optionFlags.insert(std::pair(optionFlag, flagValue)); } } workbench-1.1.1/src/Common/DataFileContentInformation.h000066400000000000000000000063331255417355300231150ustar00rootroot00000000000000#ifndef __DATA_FILE_CONTENT_INFORMATION_H__ #define __DATA_FILE_CONTENT_INFORMATION_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretObject.h" namespace caret { class DataFileContentInformation : public CaretObject { public: /** * Option flags for commands. * All options are off by default. * If an option should be on by default, call setOptionFlag() * from this class' constructor. */ enum OptionFlag { /** * Show information about each map for files that support maps. */ OPTION_SHOW_MAP_INFORMATION, /* * Show detailed information about CIFTI Label Mappings */ OPTION_SHOW_CIFTI_LABEL_MAPPING }; DataFileContentInformation(); virtual ~DataFileContentInformation(); void addNameAndValue(const AString& name, const AString& value); void addNameAndValue(const AString& name, const int32_t value); void addNameAndValue(const AString& name, const int64_t value); void addNameAndValue(const AString& name, const double value, const int32_t precision = 3); void addNameAndValue(const AString& name, const bool value); void addText(const AString& text); AString getInformationInString() const; bool isOptionFlag(const OptionFlag optionFlag) const; void setOptionFlag(const OptionFlag optionFlag, const bool flagValue); private: DataFileContentInformation(const DataFileContentInformation&); DataFileContentInformation& operator=(const DataFileContentInformation&); public: // ADD_NEW_METHODS_HERE private: std::vector > m_namesAndValues; AString m_text; std::map m_optionFlags; // ADD_NEW_MEMBERS_HERE }; #ifdef __DATA_FILE_CONTENT_INFORMATION_DECLARE__ // #endif // __DATA_FILE_CONTENT_INFORMATION_DECLARE__ } // namespace #endif //__DATA_FILE_CONTENT_INFORMATION_H__ workbench-1.1.1/src/Common/DataFileException.cxx000066400000000000000000000075041255417355300216070ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "DataFileException.h" #include "FileInformation.h" #include using namespace caret; /** * Constructor. * */ DataFileException::DataFileException() : CaretException() { this->initializeMembersDataFileException(); } /** * Constructor that uses stack trace from the exception * passed in as a parameter. * * @param e Any exception whose stack trace becomes * this exception's stack trace. * */ DataFileException::DataFileException( const CaretException& e) : CaretException(e) { this->initializeMembersDataFileException(); } /** * Constructor that accepts the exception messge. * * @param s Description of the exception. * */ DataFileException::DataFileException(const AString& s) : CaretException(s) { this->initializeMembersDataFileException(); } /** * Constructor that accepts name of file and the exception message. * * The exception message will become name of the file, a newline, * path of the file, a newline, and the description of the * exception. * * @param dataFileName Name of the data file that caused exception. * @param s Description of the exception. * */ DataFileException::DataFileException(const AString& dataFileName, const AString& s) : CaretException(s) { this->initializeMembersDataFileException(); AString msg = s; if ( ! dataFileName.isEmpty()) { msg += "\n"; FileInformation fileInfo(dataFileName); const AString pathName = fileInfo.getPathName(); msg.appendWithNewLine("File: " + fileInfo.getFileName()); if ( ! pathName.isEmpty()) { if (pathName != ".") { msg.appendWithNewLine("Path: " + pathName); } } } this->setExceptionDescription(msg); } /** * Copy Constructor. * @param e * Exception that is copied. */ DataFileException::DataFileException(const DataFileException& e) : CaretException(e) { this->errorInvalidStructure = e.errorInvalidStructure; } /** * Assignment operator. * @param e * Exception that is copied. * @return * Copy of the exception. */ DataFileException& DataFileException::operator=(const DataFileException& e) { if (this != &e) { CaretException::operator=(e); this->errorInvalidStructure = e.errorInvalidStructure; } return *this; } /** * Destructor */ DataFileException::~DataFileException() throw() { } void DataFileException::initializeMembersDataFileException() { this->errorInvalidStructure = false; } /** * @return True if the file could not be read due * to an invalid structure. */ bool DataFileException::isErrorInvalidStructure() const { return this->errorInvalidStructure; } /** * Set the invalid structure status when the file * cannot be read due to the structure being invalid. * * @param status * New invalid structure status (true if invalid). */ void DataFileException::setErrorInvalidStructure(const bool status) { this->errorInvalidStructure = status; } workbench-1.1.1/src/Common/DataFileException.h000066400000000000000000000033441255417355300212320ustar00rootroot00000000000000#ifndef __DATAFILEEXCEPTION_H__ #define __DATAFILEEXCEPTION_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "CaretException.h" namespace caret { /** * An exception thrown during data file operations. */ class DataFileException : public CaretException { public: DataFileException(); DataFileException(const CaretException& e); DataFileException(const AString& s); DataFileException(const AString& dataFileName, const AString& s); DataFileException(const DataFileException& e); DataFileException& operator=(const DataFileException& e); virtual ~DataFileException() throw(); bool isErrorInvalidStructure() const; void setErrorInvalidStructure(const bool status); private: void initializeMembersDataFileException(); bool errorInvalidStructure; }; } // namespace #endif // __DATAFILEEXCEPTION_H__ workbench-1.1.1/src/Common/DataFileInterface.h000066400000000000000000000050741255417355300211760ustar00rootroot00000000000000#ifndef __DATAFILEINTERFACE_H__ #define __DATAFILEINTERFACE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "TracksModificationInterface.h" namespace caret { /** * Abstract class for data file behavior. */ class DataFileInterface : public TracksModificationInterface { protected: DataFileInterface() { } virtual ~DataFileInterface() { } private: DataFileInterface(const DataFileInterface& s); DataFileInterface& operator=(const DataFileInterface&); public: /** * Is the file empty (contains no data)? * * @return * true if the file is empty, else false. */ virtual bool isEmpty() const = 0; /** * Get the name of the data file. * * @return Name of the data file. */ virtual AString getFileName() const = 0; /** * Set the name of the data file. * * @param filename * New name of data file. */ virtual void setFileName(const AString& filename) = 0; /** * Read the data file. * * @param filename * Name of the data file. * @throws DataFileException * If the file was not successfully read. */ virtual void readFile(const AString& filename) = 0; /** * Write the data file. * * @param filename * Name of the data file. * @throws DataFileException * If the file was not successfully written. */ virtual void writeFile(const AString& filename) = 0; }; } // namespace #endif // __DATAFILEINTERFACE_H__ workbench-1.1.1/src/Common/DataFileTypeEnum.cxx000066400000000000000000000670621255417355300214240ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __DATA_FILE_TYPE_ENUM_DECLARE__ #include "DataFileTypeEnum.h" #undef __DATA_FILE_TYPE_ENUM_DECLARE__ #include "CaretAssert.h" #include "CaretLogger.h" using namespace caret; /** * \class caret::DataFileTypeEnum * \brief An enumerated type for data files. */ /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumberated value. * @param guiName * Name displayed in the user-interface * @param overlayTypeName * Name displayed in overlay type combo box * @param fileIsUsedWithOneStructure * True if file is used with ONE structure (eg node file (surface, metric, etc). * @param fileExtensionOne * File extension * @param fileExtensionTwo * Additional File extension * @param fileExtensionThree * Additional File extension */ DataFileTypeEnum::DataFileTypeEnum(const Enum enumValue, const AString& name, const AString& guiName, const AString& overlayTypeName, const bool fileIsUsedWithOneStructure, const AString& fileExtensionOne, const AString& fileExtensionTwo, const AString& fileExtensionThree) { this->enumValue = enumValue; this->integerCode = DataFileTypeEnum::integerCodeGenerator++; this->name = name; this->guiName = guiName; this->overlayTypeName = overlayTypeName; this->oneStructureFlag = fileIsUsedWithOneStructure; if (fileExtensionOne.isEmpty() == false) { this->fileExtensions.push_back(fileExtensionOne); } if (fileExtensionTwo.isEmpty() == false) { this->fileExtensions.push_back(fileExtensionTwo); } if (fileExtensionThree.isEmpty() == false) { this->fileExtensions.push_back(fileExtensionThree); } AString filterText = this->guiName + " Files ("; for (std::vector::const_iterator iter = this->fileExtensions.begin(); iter != this->fileExtensions.end(); iter++) { if (iter != fileExtensions.begin()) { filterText += " "; } filterText += ("*." + *iter); } filterText += ")"; this->qFileDialogNameFilter = filterText; } /** * Destructor. */ DataFileTypeEnum::~DataFileTypeEnum() { } /** * Initialize the enumerated metadata. */ void DataFileTypeEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(DataFileTypeEnum(BORDER, "BORDER", "Border", "BORDER", true, "border")); enumData.push_back(DataFileTypeEnum(CONNECTIVITY_DENSE, "CONNECTIVITY_DENSE", "Connectivity - Dense", "CONNECTIVITY", false, "dconn.nii")); enumData.push_back(DataFileTypeEnum(CONNECTIVITY_DENSE_LABEL, "CONNECTIVITY_DENSE_LABEL", "Connectivity - Dense Label", "CIFTI LABELS", false, "dlabel.nii")); enumData.push_back(DataFileTypeEnum(CONNECTIVITY_DENSE_PARCEL, "CONNECTIVITY_DENSE_PARCEL", "Connectivity - Dense Parcel", "CIFTI DENSE PARCEL", false, "dpconn.nii")); enumData.push_back(DataFileTypeEnum(CONNECTIVITY_DENSE_SCALAR, "CONNECTIVITY_DENSE_SCALAR", "Connectivity - Dense Scalar", "CIFTI SCALARS", false, "dscalar.nii")); enumData.push_back(DataFileTypeEnum(CONNECTIVITY_DENSE_TIME_SERIES, "CONNECTIVITY_DENSE_TIME_SERIES", "Connectivity - Dense Data Series", "DATA SERIES", false, "dtseries.nii")); enumData.push_back(DataFileTypeEnum(CONNECTIVITY_FIBER_ORIENTATIONS_TEMPORARY, "CONNECTIVITY_FIBER_ORIENTATIONS_TEMPORARY", "Connectivity - Fiber Orientations TEMPORARY", "FIBER ORIENTATION TEMPORARY", false, "fiberTEMP.nii")); enumData.push_back(DataFileTypeEnum(CONNECTIVITY_FIBER_TRAJECTORY_TEMPORARY, "CONNECTIVITY_FIBER_TRAJECTORY_TEMPORARY", "Connectivity - Fiber Trajectory TEMPORARY", "FIBER TRAJECTORY TEMPORARY", false, "trajTEMP.wbsparse")); enumData.push_back(DataFileTypeEnum(CONNECTIVITY_PARCEL, "CONNECTIVITY_PARCEL", "Connectivity - Parcel", "CIFTI PARCEL", false, "pconn.nii")); enumData.push_back(DataFileTypeEnum(CONNECTIVITY_PARCEL_DENSE, "CONNECTIVITY_PARCEL_DENSE", "Connectivity - Parcel Dense", "CIFTI PARCEL DENSE", false, "pdconn.nii")); enumData.push_back(DataFileTypeEnum(CONNECTIVITY_PARCEL_LABEL, "CONNECTIVITY_PARCEL_LABEL", "Connectivity - Parcel Label", "CIFTI PARCEL LABEL", false, "plabel.nii")); enumData.push_back(DataFileTypeEnum(CONNECTIVITY_PARCEL_SCALAR, "CONNECTIVITY_PARCEL_SCALAR", "Connectivity - Parcel Scalar", "CIFTI PARCEL SCALAR", false, "pscalar.nii")); enumData.push_back(DataFileTypeEnum(CONNECTIVITY_PARCEL_SERIES, "CONNECTIVITY_PARCEL_SERIES", "Connectivity - Parcel Series", "CIFTI PARCEL SERIES", false, "ptseries.nii")); enumData.push_back(DataFileTypeEnum(CONNECTIVITY_SCALAR_DATA_SERIES, "CONNECTIVITY_SCALAR_DATA_SERIES", "Connectivity - Scalar Data Series", "CIFTI SCALAR DATA SERIES", false, "sdseries.nii")); enumData.push_back(DataFileTypeEnum(FOCI, "FOCI", "Foci", "FOCI", false, "foci")); enumData.push_back(DataFileTypeEnum(IMAGE, "IMAGE", "Image", "IMAGE", false, "png", "ppm")); enumData.push_back(DataFileTypeEnum(LABEL, "LABEL", "Label", "LABEL", true, "label.gii")); enumData.push_back(DataFileTypeEnum(METRIC, "METRIC", "Metric", "METRIC", true, "func.gii", "shape.gii")); enumData.push_back(DataFileTypeEnum(PALETTE, "PALETTE", "Palette", "PALETTE", false, "palette")); enumData.push_back(DataFileTypeEnum(RGBA, "RGBA", "RGBA", "RGBA", true, "rgba.gii")); enumData.push_back(DataFileTypeEnum(SCENE, "SCENE", "Scene", "SCENE", false, "scene")); enumData.push_back(DataFileTypeEnum(SPECIFICATION, "SPECIFICATION", "Specification", "SPECIFICATION", false, "spec")); enumData.push_back(DataFileTypeEnum(SURFACE, "SURFACE", "Surface", "SURFACE", true, "surf.gii")); enumData.push_back(DataFileTypeEnum(UNKNOWN, "UNKNOWN", "Unknown", "UNKNOWN", false, "unknown")); enumData.push_back(DataFileTypeEnum(VOLUME, "VOLUME", "Volume", "VOLUME", false, "nii", "nii.gz")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const DataFileTypeEnum* DataFileTypeEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const DataFileTypeEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString DataFileTypeEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const DataFileTypeEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ DataFileTypeEnum::Enum DataFileTypeEnum::fromName(const AString& nameIn, bool* isValidOut) { AString name = nameIn; if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = UNKNOWN; /* * Maintain compatibility with early spec files */ if (name.startsWith("SURFACE_")) { CaretLogWarning("Obsolete spec file tag \"" + name + "\", replace with SURFACE"); name = "SURFACE"; } else if (name.startsWith("VOLUME_")) { CaretLogWarning("Obsolete spec file tag \"" + name + "\", replace with VOLUME"); name = "VOLUME"; } for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const DataFileTypeEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name \"" + name + "\" failed to match enumerated value for type DataFileTypeEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString DataFileTypeEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const DataFileTypeEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ DataFileTypeEnum::Enum DataFileTypeEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = UNKNOWN; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const DataFileTypeEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName \"" + guiName + "\" failed to match enumerated value for type DataFileTypeEnum")); } return enumValue; } /** * Get a Overlay Type Name representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString DataFileTypeEnum::toOverlayTypeName(Enum enumValue) { if (initializedFlag == false) initialize(); const DataFileTypeEnum* enumInstance = findData(enumValue); return enumInstance->overlayTypeName; } /** * Get an enumerated value corresponding to its overlay type name. * @param s * Overlay Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ DataFileTypeEnum::Enum DataFileTypeEnum::fromOverlayTypeName(const AString& overlayTypeName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = UNKNOWN; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const DataFileTypeEnum& d = *iter; if (d.overlayTypeName == overlayTypeName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName \"" + overlayTypeName + "\" failed to match enumerated value for type DataFileTypeEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t DataFileTypeEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const DataFileTypeEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Get an enumerated value corresponding to its QFileDialog filter name. * @param qFileDialogNameFilter * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ DataFileTypeEnum::Enum DataFileTypeEnum::fromQFileDialogFilter(const AString& qFileDialogNameFilter, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = UNKNOWN; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const DataFileTypeEnum& d = *iter; if (d.qFileDialogNameFilter == qFileDialogNameFilter) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("qFileDialogNameFilter \"" + qFileDialogNameFilter + " \"failed to match enumerated value for type DataFileTypeEnum")); } return enumValue; } /** * Get the file filter text for use in a QFileDialog. * * @param enumValue * Enumerated type for file filter. * @return * Text containing file filter. */ AString DataFileTypeEnum::toQFileDialogFilter(const Enum enumValue) { if (initializedFlag == false) initialize(); const DataFileTypeEnum* enumInstance = findData(enumValue); return enumInstance->qFileDialogNameFilter; } /** * Is the file type used with a one structure (node based file, surface, label, etc.)? * @param enumValue * Enumerated type for file filter. * @return true if used with one structure, else false. */ bool DataFileTypeEnum::isFileUsedWithOneStructure(const Enum enumValue) { if (initializedFlag == false) initialize(); const DataFileTypeEnum* enumInstance = findData(enumValue); return enumInstance->oneStructureFlag; } /** * @return All valid file extensions for the given enum value. * @param enumValue * Enumerated type for file extensions. */ std::vector DataFileTypeEnum::getAllFileExtensions(const Enum enumValue) { if (initializedFlag == false) initialize(); const DataFileTypeEnum* enumInstance = findData(enumValue); return enumInstance->fileExtensions; } /** * @return All valid file extensions for all file types except UNKNOWN. */ std::vector DataFileTypeEnum::getFilesExtensionsForEveryFile() { std::vector allExtensions; for (std::vector::iterator enumIter = enumData.begin(); enumIter != enumData.end(); enumIter++) { if (enumIter->enumValue != DataFileTypeEnum::UNKNOWN) { allExtensions.insert(allExtensions.end(), enumIter->fileExtensions.begin(), enumIter->fileExtensions.end()); } } return allExtensions; } /** * If the given filename does not contain a file extension that is valid * for the given data file type, add the first valid file extension from * the given data file type. * * @param filename * Name of file that may not have the correct file extension. * @param enumValue * The data file type. * @return * Input file name to which a file extension may have been added. */ AString DataFileTypeEnum::addFileExtensionIfMissing(const AString& filenameIn, const Enum enumValue) { AString filename = filenameIn; if (initializedFlag == false) initialize(); const DataFileTypeEnum* enumInstance = findData(enumValue); /* * See if filename ends with any of the available extensions for the * given data file type. */ for (std::vector::const_iterator iter = enumInstance->fileExtensions.begin(); iter != enumInstance->fileExtensions.end(); iter++) { const AString ext = ("." + *iter); if (filename.endsWith(ext)) { return filename; } } /* * Add default extension. */ const AString defaultExtension = DataFileTypeEnum::toFileExtension(enumValue); filename += ("." + defaultExtension); return filename; } /** * Get the primary file extension for the file type. * @param enumValue * The data file type. * @return * Extension for file type. */ AString DataFileTypeEnum::toFileExtension(const Enum enumValue) { if (initializedFlag == false) initialize(); const DataFileTypeEnum* enumInstance = findData(enumValue); AString ext = "file"; if (enumInstance->fileExtensions.empty() == false) { ext = enumInstance->fileExtensions[0]; } return ext; } /*** * Does the filename have a valid extension for the given file type? * * @param filename * Name of file that may not have the correct file extension. * @param enumValue * The data file type. * @return * True if the filename has a valid extension, else false. */ bool DataFileTypeEnum::isValidFileExtension(const AString& filename, const Enum enumValue) { if (initializedFlag == false) initialize(); const DataFileTypeEnum* enumInstance = findData(enumValue); /* * See if filename ends with any of the available extensions for the * given data file type. */ for (std::vector::const_iterator iter = enumInstance->fileExtensions.begin(); iter != enumInstance->fileExtensions.end(); iter++) { const AString ext = ("." + *iter); if (filename.endsWith(ext)) { return true; } } return false; } /** * For the filename, match its extension to a DataFileType enumerated type. * @param filename * Name of file. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ DataFileTypeEnum::Enum DataFileTypeEnum::fromFileExtension(const AString& filename, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = UNKNOWN; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const DataFileTypeEnum& d = *iter; const std::vector extensions = iter->fileExtensions; for (std::vector::const_iterator extIter = extensions.begin(); extIter != extensions.end(); extIter++) { /* * Need to add "." to avoid ambiguous matching ("dconn.nii, pdconn.nii) */ const AString extensionWithDot = ("." + *extIter); if (filename.endsWith(extensionWithDot)) { enumValue = d.enumValue; validFlag = true; break; } } if (validFlag) { break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("filename \"" + filename + " \"has no matching extensions in DataFileTypeEnum")); } return enumValue; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ DataFileTypeEnum::Enum DataFileTypeEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = UNKNOWN; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const DataFileTypeEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code \"" + AString::number(integerCode) + " \"failed to match enumerated value for type DataFileTypeEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. * @param includeUNKNOWN * If true, the UNKNOWN enum is included. */ void DataFileTypeEnum::getAllEnums(std::vector& allEnums, const bool includeUnknown) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { if (iter->enumValue == UNKNOWN) { if ( ! includeUnknown) { continue; } } allEnums.push_back(iter->enumValue); } } /** * Is the enumerated type a connectivity enumerated type? * @param enumValue * The enumerated type value. * @return * true if so, else false. */ bool DataFileTypeEnum::isConnectivityDataType(const Enum enumValue) { const AString name = DataFileTypeEnum::toName(enumValue); if (name.startsWith("CONNECTIVITY")) { return true; } return false; } /** * Get all connectivity enumerated type values. * @param connectivityEnumsOut * Will be loaded with all connectivity enumerated types. */ void DataFileTypeEnum::getAllConnectivityEnums(std::vector& connectivityEnumsOut) { if (initializedFlag == false) initialize(); connectivityEnumsOut.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { if (DataFileTypeEnum::isConnectivityDataType(iter->enumValue)) { connectivityEnumsOut.push_back(iter->enumValue); } } } workbench-1.1.1/src/Common/DataFileTypeEnum.h000066400000000000000000000140361255417355300210420ustar00rootroot00000000000000#ifndef __DATA_FILE_TYPE_ENUM__H_ #define __DATA_FILE_TYPE_ENUM__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class DataFileTypeEnum { public: /** * Enumerated values. */ enum Enum { /** Border */ BORDER, /** Connectivity - Dense */ CONNECTIVITY_DENSE, /** Connectivity - Dense Label */ CONNECTIVITY_DENSE_LABEL, /** Connectivity - Dense Parcel */ CONNECTIVITY_DENSE_PARCEL, /** Connectivity - Dense Scalar */ CONNECTIVITY_DENSE_SCALAR, /** Connectivity - Dense Time Series */ CONNECTIVITY_DENSE_TIME_SERIES, /** Connectivity - Fiber Orientations TEMPORARY */ CONNECTIVITY_FIBER_ORIENTATIONS_TEMPORARY, /** Connectivity - Fiber Trajectory TEMPORARY */ CONNECTIVITY_FIBER_TRAJECTORY_TEMPORARY, /** Connectivity - Parcel */ CONNECTIVITY_PARCEL, /** Connectivity - Parcel Dense*/ CONNECTIVITY_PARCEL_DENSE, /** Connectivity - Parcel Label*/ CONNECTIVITY_PARCEL_LABEL, /** Connectivity - Parcel Scalar */ CONNECTIVITY_PARCEL_SCALAR, /** Connectivity - Parcel Series */ CONNECTIVITY_PARCEL_SERIES, /** Connectivity - Scalar Data Series */ CONNECTIVITY_SCALAR_DATA_SERIES, /** Foci */ FOCI, /** Image */ IMAGE, /** Labels */ LABEL, /** Metric */ METRIC, /** Palette */ PALETTE, /** RGBA */ RGBA, /** Scene */ SCENE, /** Specification */ SPECIFICATION, /** Surface */ SURFACE, /** Unknown */ UNKNOWN, /** Volume */ VOLUME }; ~DataFileTypeEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static AString toOverlayTypeName(Enum enumValue); static Enum fromOverlayTypeName(const AString& overlayTypeName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums, const bool includeUnknown = false); static Enum fromQFileDialogFilter(const AString& qFileDialogNameFilter, bool* isValidOut); static AString toQFileDialogFilter(const Enum enumValue); static Enum fromFileExtension(const AString& filename, bool* isValidOut); static AString toFileExtension(const Enum enumValue); static bool isValidFileExtension(const AString& filename, const Enum enumValue); static std::vector getAllFileExtensions(const Enum enumValue); static std::vector getFilesExtensionsForEveryFile(); static bool isFileUsedWithOneStructure(const Enum enumValue); static bool isConnectivityDataType(const Enum enumValue); static void getAllConnectivityEnums(std::vector& connectivityEnumsOut); static AString addFileExtensionIfMissing(const AString& filename, const Enum enumValue); private: DataFileTypeEnum(const Enum enumValue, const AString& name, const AString& guiName, const AString& overlayTypeName, const bool fileIsUsedWithOneStructure, const AString& fileExtensionOne, const AString& fileExtensionTwo = "", const AString& fileExtensionThree = ""); static const DataFileTypeEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Automatically generates the integer code */ static int32_t integerCodeGenerator; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; /** Name for use in overlay selection */ AString overlayTypeName; /** Extension(s) for the file */ std::vector fileExtensions; /** Name filter for use in a QFileDialog */ AString qFileDialogNameFilter; /** Is file for use with one structure */ bool oneStructureFlag; }; #ifdef __DATA_FILE_TYPE_ENUM_DECLARE__ std::vector DataFileTypeEnum::enumData; bool DataFileTypeEnum::initializedFlag = false; int32_t DataFileTypeEnum::integerCodeGenerator = 0; #endif // __DATA_FILE_TYPE_ENUM_DECLARE__ } // namespace #endif //__DATA_FILE_TYPE_ENUM__H_ workbench-1.1.1/src/Common/DescriptiveStatistics.cxx000066400000000000000000000430571255417355300226160ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __DESCRIPTIVE_STATISTICS_DECLARE__ #include "DescriptiveStatistics.h" #undef __DESCRIPTIVE_STATISTICS_DECLARE__ #include #include #include #include "CaretAssert.h" #include "MathFunctions.h" using namespace caret; using namespace std; /** * \class caret::DescriptiveStatistics * \brief Contains descriptive statics for some group of data. * * Provides descriptive statistics for a group of data * that includes mininmum and maximum values, percentiles, * a histogram, and statistical measurements. */ /** * Constructor that allows setting the number of elements in the histogram. * * @param histogramNumberOfElements * Number of elemnents for the histogram. Must be positive!! * @param percentileDivisions * Number of percentiles. */ DescriptiveStatistics::DescriptiveStatistics(const int64_t histogramNumberOfElements, const int64_t percentileDivisions) : CaretObject() { m_histogramNumberOfElements = histogramNumberOfElements; CaretAssert(m_histogramNumberOfElements > 2); m_percentileDivisions = percentileDivisions; CaretAssert(m_percentileDivisions > 2); m_lastInputNumberOfValues = -1; m_histogram = new int64_t[m_histogramNumberOfElements]; m_positivePercentiles = new float[m_percentileDivisions]; m_negativePercentiles = new float[m_percentileDivisions]; this->invalidateData(); } /** * Destructor. */ DescriptiveStatistics::~DescriptiveStatistics() { if (m_histogram != NULL) { delete[] m_histogram; } if (m_positivePercentiles != NULL) { delete[] m_positivePercentiles; } if (m_negativePercentiles != NULL) { delete[] m_negativePercentiles; } } /** * Invalidate data so that next call to update() * recreates the statistics. */ void DescriptiveStatistics::invalidateData() { m_lastInputNumberOfValues = -1; m_validCount = 0; m_infCount = 0; m_negInfCount = 0; m_nanCount = 0; m_minimumValue = 0.0; m_maximumValue = 0.0; m_containsNegativeValues = false; m_containsPositiveValues = false; m_mean = 0.0; m_median = 0.0; m_standardDeviationPopulation = 0.0; m_standardDeviationSample = 0.0; } /** * Update the statistics with the given data but * limit the range of values to the given * minimum and maximum values. * * @param values * Values for which statistics are calculated. * @param numberOfValues * Number of elements in values array. * @param mostPositiveValueInclusive * Values more positive than this value are excluded. * @param leastPositiveValueInclusive * Values less positive than this value are excluded. * @param leastNegativeValueInclusive * Values less negative than this value are excluded. * @param mostNegativeValueInclusive * Values more negative than this value are excluded. * @param includeZeroValues * If true zero values (very near zero) are included. */ void DescriptiveStatistics::update(const std::vector& values, const float mostPositiveValueInclusive, const float leastPositiveValueInclusive, const float leastNegativeValueInclusive, const float mostNegativeValueInclusive, const bool includeZeroValues) { const float* valuesArray = (values.empty() ? NULL : &values[0]); const int64_t numberOfValues = (values.empty() ? 0 : values.size()); this->update(valuesArray, numberOfValues, mostPositiveValueInclusive, leastPositiveValueInclusive, leastNegativeValueInclusive, mostNegativeValueInclusive, includeZeroValues); } /** * Update the statistics with the given data but * limit the range of values to the given * minimum and maximum values. * * @param values * Values for which statistics are calculated. * @param numberOfValues * Number of elements in values array. * @param mostPositiveValueInclusive * Values more positive than this value are excluded. * @param leastPositiveValueInclusive * Values less positive than this value are excluded. * @param leastNegativeValueInclusive * Values less negative than this value are excluded. * @param mostNegativeValueInclusive * Values more negative than this value are excluded. * @param includeZeroValues * If true zero values (very near zero) are included. */ void DescriptiveStatistics::update(const float* valuesIn, const int64_t numberOfValuesIn, const float mostPositiveValueInclusive, const float leastPositiveValueInclusive, const float leastNegativeValueInclusive, const float mostNegativeValueInclusive, const bool includeZeroValues) { bool needUpdate = false; if (m_lastInputNumberOfValues <= 0) { needUpdate = true; } else { if ((numberOfValuesIn != m_lastInputNumberOfValues) || (mostPositiveValueInclusive != m_lastInputMostPositiveValueInclusive) || (leastPositiveValueInclusive != m_lastInputLeastPositiveValueInclusive) || (leastNegativeValueInclusive != m_lastInputLeastNegativeValueInclusive) || (mostNegativeValueInclusive != m_lastInputMostNegativeValueInclusive) || (includeZeroValues != m_lastInputIncludeZeroValues)) { needUpdate = true; } } if (needUpdate == false) { return; } this->invalidateData(); m_lastInputNumberOfValues = numberOfValuesIn; m_lastInputMostPositiveValueInclusive = mostPositiveValueInclusive; m_lastInputLeastPositiveValueInclusive = leastPositiveValueInclusive; m_lastInputLeastNegativeValueInclusive = leastNegativeValueInclusive; m_lastInputMostNegativeValueInclusive = mostNegativeValueInclusive; m_lastInputIncludeZeroValues = includeZeroValues; std::vector valuesVector; valuesVector.reserve(numberOfValuesIn); for (int64_t i = 0; i < numberOfValuesIn; i++) { bool useIt = false; const float v = valuesIn[i]; if (v >= leastPositiveValueInclusive) { if (v <= mostPositiveValueInclusive) { useIt = true; } } else if (v <= leastNegativeValueInclusive) { if (v >= mostNegativeValueInclusive) { useIt = true; } } if (useIt) { if (includeZeroValues == false) { if (MathFunctions::isZero(v)) { useIt = false; } } if (useIt) { valuesVector.push_back(v); } } } const float* values = (valuesVector.empty() ? NULL : &valuesVector[0]); const int64_t numberOfValues = static_cast(valuesVector.size()); std::fill(m_histogram, m_histogram + m_histogramNumberOfElements, 0.0); std::fill(m_positivePercentiles, m_positivePercentiles + m_percentileDivisions, 0.0); std::fill(m_negativePercentiles, m_negativePercentiles + m_percentileDivisions, 0.0); if (numberOfValues <= 0) { return; } if (numberOfValues == 1) { const float v = values[0]; m_mean = v; m_median = v; m_histogram[m_histogramNumberOfElements / 2] = v; fill(m_positivePercentiles, m_positivePercentiles + m_percentileDivisions, v); fill(m_negativePercentiles, m_negativePercentiles + m_percentileDivisions, v); m_minimumValue = v; m_maximumValue = v; return; } /* * Copy and sort the input data. */ float* sortedValues = new float[numberOfValues]; for (int64_t i = 0; i < numberOfValues; ++i) {//remove and count non-numerical values if (values[i] != values[i]) { ++m_nanCount; continue; } if (values[i] < -1.0f && (values[i] * 2.0f == values[i])) { ++m_negInfCount; continue; } if (values[i] > 1.0f && (values[i] * 2.0f == values[i])) { ++m_infCount; continue; } sortedValues[m_validCount] = values[i]; ++m_validCount; } sort(sortedValues, sortedValues + m_validCount); /* * Minimum and maximum values */ this->m_minimumValue = sortedValues[0]; this->m_maximumValue = sortedValues[numberOfValues - 1]; /* * Find most/least negative/positive indices in sorted data. */ int64_t mostNegativeIndex = -1; int64_t leastNegativeIndex = -1; int64_t leastPositiveIndex = -1; int64_t mostPositiveIndex = -1; if (sortedValues[0] < 0.0f) { mostNegativeIndex = 0; } if (sortedValues[0] > 0.0f) { leastPositiveIndex = 0; } if (sortedValues[m_validCount - 1] > 0.0f) { mostPositiveIndex = m_validCount - 1; } if (sortedValues[m_validCount - 1] < 0.0f) { leastNegativeIndex = m_validCount - 1; } if (leastNegativeIndex == -1 && leastPositiveIndex == -1) {//need to find where the zeros start and end int64_t start = -1, end = m_validCount, guess, nextEnd = m_validCount; while (end - start > 1) {//bisection search for last negative guess = (start + end) / 2; CaretAssertArrayIndex(sortedValues, m_validCount, guess); if (sortedValues[guess] < 0.0f) { start = guess; } else { end = guess; if (sortedValues[guess] > 0.0f) { nextEnd = guess;//save some time on the next search } } } leastNegativeIndex = start; end = nextEnd;//don't reinitialize start, it is just before the first nonnegative already while (end - start > 1) {//bisection search for first positive guess = (start + end) / 2; CaretAssertArrayIndex(sortedValues, m_validCount, guess); if (sortedValues[guess] > 0.0f) { end = guess; } else { start = guess; } } leastPositiveIndex = end; } /* * Determine negative percentiles * Note: that index 0 is least negative, last index is most negative */ const int64_t numNegativeValues = leastNegativeIndex - mostNegativeIndex + 1; if (mostNegativeIndex != -1) { m_containsNegativeValues = true; m_negativePercentiles[0] = sortedValues[leastNegativeIndex]; for (int64_t i = 1; i < m_percentileDivisions - 1; i++) { int64_t indx = leastNegativeIndex - (int64_t)(((double)i * (numNegativeValues - 1)) / m_percentileDivisions + 0.5); CaretAssertArrayIndex(sortedValues, m_validCount, indx); if (indx < 0) indx = 0; if (indx >= m_validCount) indx = m_validCount - 1; m_negativePercentiles[i] = sortedValues[indx]; } m_negativePercentiles[m_percentileDivisions - 1] = sortedValues[mostNegativeIndex]; } /* * Determine positive percentiles */ const int64_t numPositiveValues = mostPositiveIndex - leastPositiveIndex + 1; if (mostPositiveIndex != -1) { this->m_containsPositiveValues = true; m_positivePercentiles[0] = sortedValues[leastPositiveIndex]; for (int64_t i = 1; i < m_percentileDivisions - 1; i++) { int64_t indx = (int64_t)(((double)i * (numPositiveValues - 1)) / m_percentileDivisions + 0.5) + leastPositiveIndex; CaretAssertArrayIndex(sortedValues, m_validCount, indx); if (indx < 0) indx = 0; if (indx >= m_validCount) indx = m_validCount - 1; m_positivePercentiles[i] = sortedValues[indx]; } m_positivePercentiles[m_percentileDivisions - 1] = sortedValues[mostPositiveIndex]; } /* * Prepare for histogram of all data */ const float minValue = sortedValues[0]; const float maxValue = sortedValues[m_validCount - 1]; const float bucketSize = (maxValue - minValue) / m_histogramNumberOfElements; /* * Prepare for statistics */ double sum = 0.0; double sumSQ = 0.0; /* * Create histogram and statistics. */ for (int64_t i = 0; i < m_validCount; i++) { const float v = sortedValues[i]; int64_t indx = (v - minValue) / bucketSize; if (indx >= m_histogramNumberOfElements) indx = m_histogramNumberOfElements - 1;//NEVER trust floats to not have rounding errors when nonzero if (indx < 0) indx = 0;//probably not needed, involves subtracting equals CaretAssertArrayIndex(m_histogram, m_histogramNumberOfElements, indx); m_histogram[indx]++; sum += v; const float v2 = v * v; sumSQ += v2; } /* * Compute statistics of all. * Pop Variance = (sum(x^2) - [(sum(x))^2] / N) / N */ m_mean = sum / m_validCount; m_median = sortedValues[m_validCount / 2]; const double numerator = (sumSQ - ((sum*sum) / m_validCount)); m_standardDeviationPopulation = -1.0; m_standardDeviationSample = -1.0; if (m_validCount > 0) { m_standardDeviationPopulation = sqrt(numerator / m_validCount); if (m_validCount > 1) { m_standardDeviationSample = sqrt(numerator / (m_validCount - 1)); } } delete[] sortedValues; } /** * Update the statistics with the given data. * @param values * Values for which statistics are calculated. * @param numberOfValues * Number of elements in values array. */ void DescriptiveStatistics::update(const float* values, const int64_t numberOfValues) { this->update(values, numberOfValues, std::numeric_limits::max(), 0.0, 0.0, -std::numeric_limits::max(), true); } /** * Update the statistics with the given data. * @param values * Vector of values for which statistics are calculated. */ void DescriptiveStatistics::update(const std::vector& values) { this->update(values, std::numeric_limits::max(), 0.0, 0.0, -std::numeric_limits::max(), true); } /** * Get the value that is greater than 'percent' of positive values. * * @param percent * The percent which ranges inclusively from 0 to 100. * @return * Value that is greater than 'percent' of the positive values. */ float DescriptiveStatistics::getPositivePercentile(const float percent) const { const float myIndex = (percent / 100 * (m_percentileDivisions - 1));//noninteger index to interpolate at int64_t lowIndex = (int64_t)floor(myIndex); int64_t highIndex = (int64_t)ceil(myIndex); if (highIndex <= 0) return m_positivePercentiles[0]; if (lowIndex >= m_percentileDivisions - 1) return m_positivePercentiles[m_percentileDivisions - 1]; if (lowIndex == highIndex) return m_positivePercentiles[lowIndex]; float lowWeight = highIndex - myIndex; float highWeight = myIndex - lowIndex; return (lowWeight * m_positivePercentiles[lowIndex] + highWeight * m_positivePercentiles[highIndex]) / (lowWeight + highWeight); } /** * Get the value that is more negative than 'percent' of negative values. * * @param percent * The percent which ranges inclusively from 0 to 100. * @return * Value that is more negative than 'percent' of the negative values. */ float DescriptiveStatistics::getNegativePercentile(const float percent) const { const float myIndex = (percent / 100 * (m_percentileDivisions - 1));//noninteger index to interpolate at int64_t lowIndex = (int64_t)floor(myIndex); int64_t highIndex = (int64_t)ceil(myIndex); if (highIndex <= 0) return m_negativePercentiles[0]; if (lowIndex >= m_percentileDivisions - 1) return m_negativePercentiles[m_percentileDivisions - 1]; if (lowIndex == highIndex) return m_negativePercentiles[lowIndex]; float lowWeight = highIndex - myIndex; float highWeight = myIndex - lowIndex; return (lowWeight * m_negativePercentiles[lowIndex] + highWeight * m_negativePercentiles[highIndex]) / (lowWeight + highWeight); } /** * Get a description of this object's content. * @return String describing this object's content. */ AString DescriptiveStatistics::toString() const { return "DescriptiveStatistics"; } workbench-1.1.1/src/Common/DescriptiveStatistics.h000066400000000000000000000175731255417355300222470ustar00rootroot00000000000000#ifndef __DESCRIPTIVE_STATISTICS__H_ #define __DESCRIPTIVE_STATISTICS__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretObject.h" namespace caret { class DescriptiveStatistics : public CaretObject { public: DescriptiveStatistics(const int64_t histogramNumberOfElements = 100, const int64_t percentileDivisions = 1001); virtual ~DescriptiveStatistics(); void update(const float* values, const int64_t numberOfValues); void update(const float* values, const int64_t numberOfValues, const float mostPositiveValueInclusive, const float leastPositiveValueInclusive, const float leastNegativeValueInclusive, const float mostNegativeValueInclusive, const bool includeZeroValues); void update(const std::vector& values); void update(const std::vector& values, const float mostPositiveValueInclusive, const float leastPositiveValueInclusive, const float leastNegativeValueInclusive, const float mostNegativeValueInclusive, const bool includeZeroValues); float getPositivePercentile(const float percent) const; float getNegativePercentile(const float percent) const; void invalidateData(); /** * @return Does the data contains positive values. */ bool hasPositiveValues() const { return this->m_containsPositiveValues; } /** * @return Does the data contains negative values. */ bool hasNegativeValues() const { return this->m_containsNegativeValues; } /** * @return The number of elements in the histogram. */ int64_t getHistogramNumberOfElements() const { return this->m_histogramNumberOfElements; } /** * @return Get the histogram for all values. The number of elements * is HISTOGRAM_NUMBER_OF_ELEMENTS. */ const int64_t* getHistogram() const { return this->m_histogram; } /** * @return The most positive value. */ float getMostPositiveValue() const { return this->m_positivePercentiles[m_percentileDivisions - 1]; } /** * @return The least positive value. */ float getLeastPositiveValue() const { return this->m_positivePercentiles[0]; } /** * @return The most negative value. */ float getMostNegativeValue() const { return this->m_negativePercentiles[m_percentileDivisions - 1]; } /** * @return The least negative value. */ float getLeastNegativeValue() const { return this->m_negativePercentiles[0]; } /** * @return The minimum value. */ float getMinimumValue() const { return this->m_minimumValue; } /** * @return The maximum value. */ float getMaximumValue() const { return this->m_maximumValue; } /** * @return The mean (average) value. */ float getMean() const { return this->m_mean; } /** * @return The median value. */ float getMedian() const { return this->m_median; } /** * @return The population standard deviation (divide by N). */ float getPopulationStandardDeviation() const { return this->m_standardDeviationPopulation; } /** * @return The sample standard deviation (divide by N - 1). */ float getStandardDeviationSample() const { return this->m_standardDeviationSample; } private: DescriptiveStatistics(const DescriptiveStatistics&); DescriptiveStatistics& operator=(const DescriptiveStatistics&); public: virtual AString toString() const; private: /** * Contains the histogram which provides the * distribution of the data. */ int64_t* m_histogram; /** * Contains the number of elements in the histograms. */ int64_t m_histogramNumberOfElements; ///contains number of divisions in the percentiles int64_t m_percentileDivisions; /** * Contains the negative percentiles. * * Index 0 contains the least negative value less than zero. * * Index 'X' contains the value that is less than * 'X' percent of values. * * The last index contains the most negative value. */ float* m_negativePercentiles; /** Indicates that negative data is present. */ bool m_containsNegativeValues; /** * Contains the positive percentiles. * * Index 0 contains the least positive value greater than zero. * * Index 'X' contains the value that is greater than * 'X' percent of values. * * The last index contains the greatest positive value. */ float* m_positivePercentiles; /** Indicates that positive data is present. */ bool m_containsPositiveValues; /** minimum value regardless of sign */ float m_minimumValue; /** maximum value regardless of sign */ float m_maximumValue; /** The mean (average) value. */ float m_mean; /** The population standard deviation of all values (divide by N). */ float m_standardDeviationPopulation; /** The sample standard deviation of all values (divide by N - 1). */ float m_standardDeviationSample; /** The median (middle) value. */ float m_median; ///counts of each class of number int64_t m_validCount, m_infCount, m_negInfCount, m_nanCount; /// last input value for number of (prevents unnecessary updates) int64_t m_lastInputNumberOfValues; /// last input value for most positive (prevents unnecessary updates) float m_lastInputMostPositiveValueInclusive; /// last input value for least positive (prevents unnecessary updates) float m_lastInputLeastPositiveValueInclusive; /// last input value for least negative (prevents unnecessary updates) float m_lastInputLeastNegativeValueInclusive; /// last input value for most negative (prevents unnecessary updates) float m_lastInputMostNegativeValueInclusive; /// last input value for include zeros (prevents unnecessary updates) bool m_lastInputIncludeZeroValues; }; #ifdef __DESCRIPTIVE_STATISTICS_DECLARE__ // #endif // __DESCRIPTIVE_STATISTICS_DECLARE__ } // namespace #endif //__DESCRIPTIVE_STATISTICS__H_ workbench-1.1.1/src/Common/DeveloperFlagsEnum.cxx000066400000000000000000000261171255417355300220070ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __DEVELOPER_FLAGS_ENUM_DECLARE__ #include "DeveloperFlagsEnum.h" #undef __DEVELOPER_FLAGS_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::DeveloperFlagsEnum * \brief Flags used during development. * * Using this enumerated type in the GUI with an EnumComboBoxTemplate * * Header File (.h) * Forward declare the data type: * class EnumComboBoxTemplate; * * Declare the member: * EnumComboBoxTemplate* m_developerFlagsEnumComboBox; * * Declare a slot that is called when user changes selection * private slots: * void developerFlagsEnumComboBoxItemActivated(); * * Implementation File (.cxx) * Include the header files * #include "EnumComboBoxTemplate.h" * #include "DeveloperFlagsEnum.h" * * Instatiate: * m_developerFlagsEnumComboBox = new EnumComboBoxTemplate(this); * m_developerFlagsEnumComboBox->setup(); * * Get notified when the user changes the selection: * QObject::connect(m_developerFlagsEnumComboBox, SIGNAL(itemActivated()), * this, SLOT(developerFlagsEnumComboBoxItemActivated())); * * Update the selection: * m_developerFlagsEnumComboBox->setSelectedItem(NEW_VALUE); * * Read the selection: * const DeveloperFlagsEnum::Enum VARIABLE = m_developerFlagsEnumComboBox->getSelectedItem(); * */ /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ DeveloperFlagsEnum::DeveloperFlagsEnum(const Enum enumValue, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCodeCounter++; this->name = name; this->guiName = guiName; this->flagStatus = false; /* * Initialization (true/false) of enums as desired */ switch (this->enumValue) { case DEVELOPER_FLAG_UNUSED: this->flagStatus = false; break; } } /** * Destructor. */ DeveloperFlagsEnum::~DeveloperFlagsEnum() { } /** * Initialize the enumerated metadata. */ void DeveloperFlagsEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(DeveloperFlagsEnum(DEVELOPER_FLAG_UNUSED, "DEVELOPER_FLAG_UNUSED", "Developer flag unused")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ DeveloperFlagsEnum* DeveloperFlagsEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { DeveloperFlagsEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString DeveloperFlagsEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const DeveloperFlagsEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ DeveloperFlagsEnum::Enum DeveloperFlagsEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = DeveloperFlagsEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const DeveloperFlagsEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type DeveloperFlagsEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString DeveloperFlagsEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const DeveloperFlagsEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ DeveloperFlagsEnum::Enum DeveloperFlagsEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = DeveloperFlagsEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const DeveloperFlagsEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type DeveloperFlagsEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t DeveloperFlagsEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const DeveloperFlagsEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ DeveloperFlagsEnum::Enum DeveloperFlagsEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = DeveloperFlagsEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const DeveloperFlagsEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type DeveloperFlagsEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void DeveloperFlagsEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void DeveloperFlagsEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(DeveloperFlagsEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void DeveloperFlagsEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(DeveloperFlagsEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } /** * Is the developer flag set? * * @param enumValue * Enum value for flag * @return * True/False status for flag. */ bool DeveloperFlagsEnum::isFlag(const Enum enumValue) { if (initializedFlag == false) initialize(); const DeveloperFlagsEnum* enumInstance = findData(enumValue); return enumInstance->flagStatus; } /** * Set the developer flag. * * @param enumValue * Enum value for flag * @param flagStatus * True/False status for flag. */ void DeveloperFlagsEnum::setFlag(const Enum enumValue, const bool flagStatus) { if (initializedFlag == false) initialize(); DeveloperFlagsEnum* enumInstance = findData(enumValue); enumInstance->flagStatus = flagStatus; } workbench-1.1.1/src/Common/DeveloperFlagsEnum.h000066400000000000000000000062461255417355300214350ustar00rootroot00000000000000#ifndef __DEVELOPER_FLAGS_ENUM_H__ #define __DEVELOPER_FLAGS_ENUM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class DeveloperFlagsEnum { public: /** * Enumerated values. */ enum Enum { DEVELOPER_FLAG_UNUSED }; ~DeveloperFlagsEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); static bool isFlag(const Enum enumValue); static void setFlag(const Enum enumValue, const bool flagStatus); private: DeveloperFlagsEnum(const Enum enumValue, const AString& name, const AString& guiName); static DeveloperFlagsEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; /** Flag status */ bool flagStatus; }; #ifdef __DEVELOPER_FLAGS_ENUM_DECLARE__ std::vector DeveloperFlagsEnum::enumData; bool DeveloperFlagsEnum::initializedFlag = false; int32_t DeveloperFlagsEnum::integerCodeCounter = 0; #endif // __DEVELOPER_FLAGS_ENUM_DECLARE__ } // namespace #endif //__DEVELOPER_FLAGS_ENUM_H__ workbench-1.1.1/src/Common/ElapsedTimer.cxx000066400000000000000000000063571255417355300206420ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __ELAPSED_TIMER_DECLARE__ #include "ElapsedTimer.h" #undef __ELAPSED_TIMER_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * Constructor. */ ElapsedTimer::ElapsedTimer() : CaretObject() { m_started = false; } /** * Destructor. */ ElapsedTimer::~ElapsedTimer() { } /** * Get a description of this object's content. * @return String describing this object's content. */ AString ElapsedTimer::toString() const { return "ElapsedTimer"; } /** * Start the timer. */ void ElapsedTimer::start() { #ifdef CARET_OS_WINDOWS m_startTime.m_tickCount = GetTickCount();//TODO: find a good way to use getTickCount64() so it doesn't have a reset at 49 days of uptime #else gettimeofday(&(m_startTime.m_timeVal), NULL); #endif m_started = true; } /** * Reset the timer as if no time had elapsed. */ void ElapsedTimer::reset() { /* * Start will change the time to 'now'. */ this->start(); } /** * Get the elapsed time in seconds. * * @return Elapsed time in seconds. */ double ElapsedTimer::getElapsedTimeSeconds() const { return getElapsedTimeMilliseconds() / 1000.0; } /** * Get the elapsed time in milliseconds. * * @return Elapsed time in milliseconds. */ double ElapsedTimer::getElapsedTimeMilliseconds() const { CaretAssertMessage(m_started, "Timer has not been started"); MyTimeStore endTime; #ifdef CARET_OS_WINDOWS endTime.m_tickCount = GetTickCount();//TODO: find a good way to use getTickCount64() when possible so it doesn't have a reset at 49 days of uptime if (endTime.m_tickCount < m_startTime.m_tickCount)//check for the 49 day wrap { endTime.m_tickCount += ((uint64_t)1)<<32;//m_tickCount is 64 bit, so this doesn't overflow } const double diffTimeMilli = (double)(endTime.m_tickCount - m_startTime.m_tickCount);//contrary to its name, it returns milliseconds, not ticks #else gettimeofday(&(endTime.m_timeVal), NULL); double diffSeconds = endTime.m_timeVal.tv_sec - m_startTime.m_timeVal.tv_sec; double diffMicroseconds = endTime.m_timeVal.tv_usec - m_startTime.m_timeVal.tv_usec; /*if (diffMicroseconds < 0) {//this is only needed if we are displaying both parts separately diffMicroseconds += 1000000; diffSeconds -= 1;//don't forget to subtract the second you just added to microseconds }//*/ const double diffTimeMilli = diffSeconds * 1000.0 + (diffMicroseconds / 1000.0); #endif return diffTimeMilli; } workbench-1.1.1/src/Common/ElapsedTimer.h000066400000000000000000000042661255417355300202640ustar00rootroot00000000000000#ifndef __ELAPSED_TIMER__H_ #define __ELAPSED_TIMER__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #ifdef CARET_OS_WINDOWS #include "windows.h" #else #include #endif #include "CaretObject.h" namespace caret { //getTimeOfDay() isn't cross-platform, so use some ifdefs for windows #ifdef CARET_OS_WINDOWS struct MyTimeStore { uint64_t m_tickCount;//can store return from GetTickCount64() which doesn't reset at 49 days, but is vista and above only };//also useful for detecting and correcting for a wrap, can just add (uint64_t)1<<32 #else struct MyTimeStore { struct timeval m_timeVal; }; #endif /// An elapsed timer class ElapsedTimer : public CaretObject { public: ElapsedTimer(); virtual ~ElapsedTimer(); void start(); void reset(); double getElapsedTimeSeconds() const; double getElapsedTimeMilliseconds() const; private: ElapsedTimer(const ElapsedTimer&); ElapsedTimer& operator=(const ElapsedTimer&); public: virtual AString toString() const; private: MyTimeStore m_startTime; bool m_started; }; #ifdef __ELAPSED_TIMER_DECLARE__ // #endif // __ELAPSED_TIMER_DECLARE__ } // namespace #endif //__ELAPSED_TIMER__H_ workbench-1.1.1/src/Common/Event.cxx000066400000000000000000000063371255417355300173430ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "Event.h" using namespace caret; /** * Constructor. * * @param eventType * The type of the event. */ Event::Event(const EventTypeEnum::Enum eventType) { this->eventType = eventType; this->errorMessage = ""; this->errorStatus = false; this->eventProcessedCount = 0; } /** * Destructor. */ Event::~Event() { } /** * Get the type of event. * * @return * The event type. */ EventTypeEnum::Enum Event::getEventType() const { return this->eventType; } /** * Get a pointer to this event. The intention of this * method is to allow the sender of the event to create * this event ?statically? (not with new) and then send * "event"->getPointer() to the event manager since * &eventObject will not cast automatically to an * Event object pointer. * * @return * Pointer to this object with the base Event class. */ Event* Event::getPointer() { return this; } /** * Was there an error processing this event? * * @return * True if there was an error processing the * event else false. */ bool Event::isError() const { return this->errorStatus; } /** * Get the error message which is only * valid if isError() returns true. * * @return * A message describing the error. */ AString Event::getErrorMessage() const { return this->errorMessage; } /** * Consumer of an event can set the error message * to indicate that there was an error processing * the event. * * If the given error message is not empty, * calling this method will result in the error * status being set and the event manager will not * send this event to any other receivers. * * @param errorMessage * The error message. */ void Event::setErrorMessage(const AString& errorMessage) { if (errorMessage.isEmpty() == false) { this->errorMessage = errorMessage; this->errorStatus = true; } } /** * Set the this event was processed by a listener. */ void Event::setEventProcessed() { this->eventProcessedCount++; } /** * Get the number of times the event was processed. * * @preturn Count of receivers that processed this event. */ int32_t Event::getEventProcessCount() const { return this->eventProcessedCount; } /** * Get String representation of caret object. * @return String containing caret object. * */ AString Event::toString() const { AString s = EventTypeEnum::toName(this->eventType); return s; } workbench-1.1.1/src/Common/Event.h000066400000000000000000000040431255417355300167600ustar00rootroot00000000000000#ifndef __EVENT_H__ #define __EVENT_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "EventTypeEnum.h" namespace caret { /// Base class for an event. class Event : public CaretObject { public: EventTypeEnum::Enum getEventType() const; virtual ~Event(); Event* getPointer(); bool isError() const; AString getErrorMessage() const; void setErrorMessage(const AString& errorMessage); virtual AString toString() const; void setEventProcessed(); int32_t getEventProcessCount() const; protected: Event(const EventTypeEnum::Enum eventType); private: Event(const Event&); Event& operator=(const Event&); /** The type of event */ EventTypeEnum::Enum eventType; /** The error message */ AString errorMessage; /** Tracks error status */ bool errorStatus; /** Number of times event was processed. */ int32_t eventProcessedCount; friend class EventManager; }; } // namespace #endif // __EVENT_H__ workbench-1.1.1/src/Common/EventAlertUser.cxx000066400000000000000000000037641255417355300211730ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __EVENT_ALERT_USER_DECLARE__ #include "EventAlertUser.h" #undef __EVENT_ALERT_USER_DECLARE__ #include "CaretAssert.h" #include "EventTypeEnum.h" using namespace caret; /** * \class caret::EventAlertUser * \brief Event that alerts the user to a problem * \ingroup Common * * If there is a problem in non-GUI code, it may be desirable to inform * the user of a problem. However, when this type of problem occurs, * passing this information "up the call stack" may be problematic and * using an exception may not be possible or desired. * * In these instance this event class may be used. When there is a GUI, * this event is received by the GuiManager and a pop-up is presented * to the user. If there is not a GUI, the EventManager will not send * this event and instead, log the message at the severe level. */ /** * Constructor for an alert message. */ EventAlertUser::EventAlertUser(const AString& message) : Event(EventTypeEnum::EVENT_ALERT_USER), m_message(message) { } /** * Destructor. */ EventAlertUser::~EventAlertUser() { } /** * @return The message for the alert. */ AString EventAlertUser::getMessage() const { return m_message; } workbench-1.1.1/src/Common/EventAlertUser.h000066400000000000000000000030351255417355300206070ustar00rootroot00000000000000#ifndef __EVENT_ALERT_USER_H__ #define __EVENT_ALERT_USER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "Event.h" namespace caret { class EventAlertUser : public Event { public: EventAlertUser(const AString& message); virtual ~EventAlertUser(); AString getMessage() const; // ADD_NEW_METHODS_HERE private: EventAlertUser(const EventAlertUser&); EventAlertUser& operator=(const EventAlertUser&); const AString m_message; // ADD_NEW_MEMBERS_HERE }; #ifdef __EVENT_ALERT_USER_DECLARE__ // #endif // __EVENT_ALERT_USER_DECLARE__ } // namespace #endif //__EVENT_ALERT_USER_H__ workbench-1.1.1/src/Common/EventBrowserTabIndicesGetAll.cxx000066400000000000000000000045071255417355300237230ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __EVENT_BROWSER_TAB_INDICES_GET_ALL_DECLARE__ #include "EventBrowserTabIndicesGetAll.h" #undef __EVENT_BROWSER_TAB_INDICES_GET_ALL_DECLARE__ #include "CaretAssert.h" #include "EventTypeEnum.h" using namespace caret; /** * \class caret::EventBrowserTabIndicesGetAll * \brief Event to get indices of all valid browser tabs. * \ingroup Common */ /** * Constructor. */ EventBrowserTabIndicesGetAll::EventBrowserTabIndicesGetAll() : Event(EventTypeEnum::EVENT_BROWSER_TAB_INDICES_GET_ALL) { } /** * Destructor. */ EventBrowserTabIndicesGetAll::~EventBrowserTabIndicesGetAll() { } /** * Add the tab index of a valid browser tab. * * @parm browserTabIndex * Index of the browser tab. */ void EventBrowserTabIndicesGetAll::addBrowserTabIndex(const int32_t browserTabIndex) { m_browserTabIndices.push_back(browserTabIndex); } /** * @return Indices of all valid browser tabs. */ std::vector EventBrowserTabIndicesGetAll::getAllBrowserTabIndices() const { return m_browserTabIndices; } /** * Get the validity of a browser tab. * * @parm browserTabIndex * Index of the browser tab. * @return * True if browser tab index is valid, else false. */ bool EventBrowserTabIndicesGetAll::isValidBrowserTabIndex(const int32_t browserTabIndex) { if (std::find(m_browserTabIndices.begin(), m_browserTabIndices.end(), browserTabIndex) != m_browserTabIndices.end()) { return true; } return false; } workbench-1.1.1/src/Common/EventBrowserTabIndicesGetAll.h000066400000000000000000000036001255417355300233410ustar00rootroot00000000000000#ifndef __EVENT_BROWSER_TAB_INDICES_GET_ALL_H__ #define __EVENT_BROWSER_TAB_INDICES_GET_ALL_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "Event.h" namespace caret { class EventBrowserTabIndicesGetAll : public Event { public: EventBrowserTabIndicesGetAll(); virtual ~EventBrowserTabIndicesGetAll(); void addBrowserTabIndex(const int32_t browserTabIndex); std::vector getAllBrowserTabIndices() const; bool isValidBrowserTabIndex(const int32_t browserTabIndex); // ADD_NEW_METHODS_HERE private: EventBrowserTabIndicesGetAll(const EventBrowserTabIndicesGetAll&); EventBrowserTabIndicesGetAll& operator=(const EventBrowserTabIndicesGetAll&); std::vector m_browserTabIndices; // ADD_NEW_MEMBERS_HERE }; #ifdef __EVENT_BROWSER_TAB_INDICES_GET_ALL_DECLARE__ // #endif // __EVENT_BROWSER_TAB_INDICES_GET_ALL_DECLARE__ } // namespace #endif //__EVENT_BROWSER_TAB_INDICES_GET_ALL_H__ workbench-1.1.1/src/Common/EventListenerInterface.h000066400000000000000000000037211255417355300223110ustar00rootroot00000000000000#ifndef __EVENT_LISTENER_INTERFACE_H__ #define __EVENT_LISTENER_INTERFACE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ namespace caret { class Event; /** * \brief Interface for objects receiving events. * * This interface must be implemented by any object that * wants to receive events. */ class EventListenerInterface { protected: /** * Constructor. */ EventListenerInterface() { } /** * Destructor. */ virtual ~EventListenerInterface() { } private: EventListenerInterface(const EventListenerInterface&) { } EventListenerInterface& operator=(const EventListenerInterface& ei); /*{ return *this; }//*/ //TSC: removed implementation to prevent "parameter unused" warnings, since it is private in an interface class public: /** * Receive an event. * * @param event * The event that the receive can respond to. */ virtual void receiveEvent(Event* event) = 0; }; } // namespace #endif // __EVENT_LISTENER_INTERFACE_H__ workbench-1.1.1/src/Common/EventManager.cxx000066400000000000000000000511041255417355300206260ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #define __EVENT_MANAGER_MAIN__ #include "Event.h" #include "EventManager.h" #undef __EVENT_MANAGER_MAIN__ #include "ApplicationInformation.h" #include "CaretAssert.h" #include "CaretLogger.h" #include "EventAlertUser.h" #include "EventListenerInterface.h" using namespace caret; /** * \class caret::EventManager * \brief The event manager. * * The event manager processes events * from senders to receivers. * * Events are sent by calling this class' sendEvent() * method. * * Objects that wish to receive events must (1) extend * publicly EventListenerInterface, (2) implement * EventListenerInterface's receiveEvent() method, * (3) Call one of two methods in EventManger, * addEventListener() or addProcessedEventListener() which * are typically called from the object's constructor, and * (4) call removeEventFromListener() or removeAllEventsFromListener * to cease listening for events which is typciall called * from the object's constructor. * * In most cases addEventListener() is used to request events. * addProcessedEventListener() is used when an object wants * to be notified of an event but not until after it has been * processed by at least one other receiver. For example, * a event for a new window may be sent. A receiver of the * event will create the new window. Other receivers may * want to know AFTER the window has been created in which * case these receivers will use addProcessedEventListener(). */ /** * Constructor. */ EventManager::EventManager() { m_eventIssuedCounter = 0; m_eventBlockingCounter.resize(EventTypeEnum::EVENT_COUNT, 0); } /** * Destructor. */ EventManager::~EventManager() { /* * Verify that all listeners were removed. */ for (int32_t i = 0; i < EventTypeEnum::EVENT_COUNT; i++) { EVENT_LISTENER_CONTAINER el = m_eventListeners[i]; if (el.empty() == false) { EventTypeEnum::Enum enumValue = static_cast(i); std::cout << "Not all listeners removed for event " << EventTypeEnum::toName(enumValue) << ", count is: " << el.size() << std::endl; } } /* * Verify that all processed listeners were removed. */ for (int32_t i = 0; i < EventTypeEnum::EVENT_COUNT; i++) { EVENT_LISTENER_CONTAINER el = m_eventProcessedListeners[i]; if (el.empty() == false) { EventTypeEnum::Enum enumValue = static_cast(i); std::cout << "Not all listeners removed for processed event " << EventTypeEnum::toName(enumValue) << ", count is: " << el.size() << std::endl; } } } /** * Create the event manager. */ void EventManager::createEventManager() { CaretAssertMessage((EventManager::s_singletonEventManager == NULL), "Event manager has already been created."); EventManager::s_singletonEventManager = new EventManager(); } /** * Delete the event manager. * This may only be called one time after event manager is created. */ void EventManager::deleteEventManager() { CaretAssertMessage((EventManager::s_singletonEventManager != NULL), "Event manager does not exist, cannot delete it."); delete EventManager::s_singletonEventManager; EventManager::s_singletonEventManager = NULL; } /** * Get the one and only event mangers. * * @return Pointer to the event manager. */ EventManager* EventManager::get() { CaretAssertMessage(EventManager::s_singletonEventManager, "Event manager was not created.\n" "It must be created with EventManager::createEventManager()."); return EventManager::s_singletonEventManager; } /** * Add a listener for a specific event. * * @param eventListener * Listener for an event. * @param listenForEventType * Type of event that is wanted. */ void EventManager::addEventListener(EventListenerInterface* eventListener, const EventTypeEnum::Enum listenForEventType) { #ifdef CONTAINER_VECTOR m_eventListeners[listenForEventType].push_back(eventListener); #elif CONTAINER_HASH_SET m_eventListeners[listenForEventType].insert(eventListener); #elif CONTAINER_SET m_eventListeners[listenForEventType].insert(eventListener); #else INTENTIONAL_COMPILER_ERROR_MISSING_CONTAINER_TYPE #endif //std::cout << "Adding listener from class " //<< typeid(*eventListener).name() //<< " for " //<< EventTypeEnum::toName(listenForEventType) //<< std::endl; } /** * Add a listener for a specific event but only receive the * event AFTER it has been processed. * * @param eventListener * Listener for an event. * @param listenForEventType * Type of event that is wanted. */ void EventManager::addProcessedEventListener(EventListenerInterface* eventListener, const EventTypeEnum::Enum listenForEventType) { #ifdef CONTAINER_VECTOR m_eventProcessedListeners[listenForEventType].push_back(eventListener); #elif CONTAINER_HASH_SET m_eventProcessedListeners[listenForEventType].insert(eventListener); #elif CONTAINER_SET m_eventProcessedListeners[listenForEventType].insert(eventListener); #else INTENTIONAL_COMPILER_ERROR_MISSING_CONTAINER_TYPE #endif //std::cout << "Adding listener from class " //<< typeid(*eventListener).name() //<< " for " //<< EventTypeEnum::toName(listenForEventType) //<< std::endl; } /** * Stop listening for an event. * * @param eventListener * Listener for an event. * @param listenForEventType * Type of event that is no longer wanted. */ void EventManager::removeEventFromListener(EventListenerInterface* eventListener, const EventTypeEnum::Enum listenForEventType) { #ifdef CONTAINER_VECTOR /* * Remove from NORMAL listeners */ EVENT_LISTENER_CONTAINER& listeners = m_eventListeners[listenForEventType]; EVENT_LISTENER_CONTAINER_ITERATOR eventIter = std::find(listeners.begin(), listeners.end(), eventListener); if (eventIter != listeners.end()) { listeners.erase(eventIter); } /* * Remove from PROCESSED listeners * These are issued AFTER all of the NORMAL listeners have been notified */ EVENT_LISTENER_CONTAINER& processedListeners = m_eventProcessedListeners[listenForEventType]; EVENT_LISTENER_CONTAINER_ITERATOR processedEventIter = std::find(processedListeners.begin(), processedListeners.end(), eventListener); if (processedEventIter != processedListeners.end()) { processedListeners.erase(processedEventIter); } // EVENT_LISTENER_CONTAINER listeners = m_eventListeners[listenForEventType]; // // /* // * Remove the listener by creating a new container // * of non-matching listeners. // */ // EVENT_LISTENER_CONTAINER updatedListeners; // for (EVENT_LISTENER_CONTAINER_ITERATOR iter = listeners.begin(); // iter != listeners.end(); // iter++) { // if (*iter == eventListener) { // //std::cout << "Removing listener from class " // //<< typeid(*eventListener).name() // //<< " for " // //<< EventTypeEnum::toName(listenForEventType) // //<< std::endl; // } // else { // updatedListeners.push_back(*iter); // } // } // // if (updatedListeners.size() != listeners.size()) { // m_eventListeners[listenForEventType] = updatedListeners; // } // // // EVENT_LISTENER_CONTAINER processedListeners = m_eventProcessedListeners[listenForEventType]; // // /* // * Remove the listener by creating a new container // * of non-matching listeners. // */ // EVENT_LISTENER_CONTAINER updatedProcessedListeners; // for (EVENT_LISTENER_CONTAINER_ITERATOR iter = processedListeners.begin(); // iter != processedListeners.end(); // iter++) { // if (*iter == eventListener) { // //std::cout << "Removing listener from class " // //<< typeid(*eventListener).name() // //<< " for " // //<< EventTypeEnum::toName(listenForEventType) // //<< std::endl; // } // else { // updatedProcessedListeners.push_back(*iter); // } // } // // if (updatedProcessedListeners.size() != processedListeners.size()) { // m_eventProcessedListeners[listenForEventType] = updatedProcessedListeners; // } #elif CONTAINER_HASH_SET m_eventListeners[listenForEventType].erase(eventListener); m_eventProcessedListeners[listenForEventType].erase(eventListener); #elif CONTAINER_SET m_eventListeners[listenForEventType].erase(eventListener); m_eventProcessedListeners[listenForEventType].erase(eventListener); #else INTENTIONAL_COMPILER_ERROR_MISSING_CONTAINER_TYPE #endif } /** * Stop listening for all events. * @param eventListener * Listener for all events. */ void EventManager::removeAllEventsFromListener(EventListenerInterface* eventListener) { for (int32_t i = 0; i < EventTypeEnum::EVENT_COUNT; i++) { removeEventFromListener(eventListener, static_cast(i)); } } /** * Send an event. * * @param event * Event that is sent. */ void EventManager::sendEvent(Event* event) { EventTypeEnum::Enum eventType = event->getEventType(); const AString eventNumberString = AString::number(m_eventIssuedCounter); const AString eventMessagePrefix = ("Event " + eventNumberString + ": " + event->toString() + " from thread: " + AString::number((uint64_t)QThread::currentThread()) + " "); const int32_t eventTypeIndex = static_cast(eventType); CaretAssertVectorIndex(m_eventBlockingCounter, eventTypeIndex); if (m_eventBlockingCounter[eventTypeIndex] > 0) { AString msg = (eventMessagePrefix + " is blocked. Blocking counter=" + AString::number(m_eventBlockingCounter[eventTypeIndex])); CaretLogFiner(msg); } else { if (eventType == EventTypeEnum::EVENT_ALERT_USER) { /* * Only send the ALERT USER event if there is a GUI. * Otherwise, simply log the alert message. */ EventAlertUser* alertEvent = dynamic_cast(event); CaretAssert(alertEvent); if (ApplicationInformation::getApplicationType() != ApplicationTypeEnum::APPLICATION_TYPE_GRAPHICAL_USER_INTERFACE) { CaretLogSevere(alertEvent->getMessage()); return; } } /* * Get listeners for event. */ EVENT_LISTENER_CONTAINER listeners = m_eventListeners[eventType]; const AString eventNumberString = AString::number(m_eventIssuedCounter); // Too many prints (JWH) //AString msg = (eventMessagePrefix + " SENT."); //CaretLogFiner(msg); //std::cout << msg << std::endl; /* * Send event to each of the listeners. */ for (EVENT_LISTENER_CONTAINER_ITERATOR iter = listeners.begin(); iter != listeners.end(); iter++) { EventListenerInterface* listener = *iter; //std::cout << "Sending event from class " //<< typeid(*listener).name() //<< " for " //<< EventTypeEnum::toName(eventType) //<< std::endl; listener->receiveEvent(event); if (event->isError()) { CaretLogWarning("Event " + eventNumberString + " had error: " + event->toString() + ": " + event->getErrorMessage()); break; } } /* * Verify event was processed. */ if (event->getEventProcessCount() > 0) { /* * Send event to each of the PROCESSED listeners. */ EVENT_LISTENER_CONTAINER processedListeners = m_eventProcessedListeners[eventType]; for (EVENT_LISTENER_CONTAINER_ITERATOR iter = processedListeners.begin(); iter != processedListeners.end(); iter++) { EventListenerInterface* listener = *iter; //std::cout << "Sending event from class " //<< typeid(*listener).name() //<< " for " //<< EventTypeEnum::toName(eventType) //<< std::endl; listener->receiveEvent(event); if (event->isError()) { CaretLogWarning("Event " + eventNumberString + " had error: " + event->toString()); break; } } } else { // Too many prints (JWH) CaretLogFine("Event " + eventNumberString + " not processed: " + event->toString()); } m_eventIssuedCounter++; } } /** * Send a "simple" event. A simple event is one for which there is no * specialized subclass of "Event". This method try to prevent sending * a "non-simple" event and it will need to be updated if an event * either has a specialized subclass added or removed. * * @param eventType * Event type that is sent. */ void EventManager::sendSimpleEvent(const EventTypeEnum::Enum eventType) { switch (eventType) { case EventTypeEnum::EVENT_BROWSER_WINDOW_MENUS_UPDATE: { sendEvent(Event(eventType).getPointer()); } break; case EventTypeEnum::EVENT_INVALID: case EventTypeEnum::EVENT_COUNT: { const AString msg(EventTypeEnum::toName(eventType) + " should never be sent as an event."); CaretAssertMessage(0, msg); CaretLogSevere(msg); } return; break; case EventTypeEnum::EVENT_ALERT_USER: case EventTypeEnum::EVENT_BRAIN_RESET: case EventTypeEnum::EVENT_BRAIN_STRUCTURE_GET_ALL: case EventTypeEnum::EVENT_BROWSER_TAB_DELETE: case EventTypeEnum::EVENT_BROWSER_TAB_GET: case EventTypeEnum::EVENT_BROWSER_TAB_GET_ALL: case EventTypeEnum::EVENT_BROWSER_TAB_GET_ALL_VIEWED: case EventTypeEnum::EVENT_BROWSER_TAB_INDICES_GET_ALL: case EventTypeEnum::EVENT_BROWSER_TAB_NEW: case EventTypeEnum::EVENT_BROWSER_WINDOW_CONTENT_GET: case EventTypeEnum::EVENT_BROWSER_WINDOW_CREATE_TABS: case EventTypeEnum::EVENT_BROWSER_WINDOW_GRAPHICS_HAVE_BEEN_REDRAWN: case EventTypeEnum::EVENT_BROWSER_WINDOW_NEW: case EventTypeEnum::EVENT_CARET_MAPPABLE_DATA_FILES_GET: case EventTypeEnum::EVENT_CARET_MAPPABLE_DATA_FILE_MAPS_VIEWED_IN_OVERLAYS: case EventTypeEnum::EVENT_CHART_MATRIX_YOKING_VALIDATION: case EventTypeEnum::EVENT_DATA_FILE_ADD: case EventTypeEnum::EVENT_DATA_FILE_DELETE: case EventTypeEnum::EVENT_DATA_FILE_READ: case EventTypeEnum::EVENT_DATA_FILE_RELOAD: case EventTypeEnum::EVENT_GET_DISPLAYED_DATA_FILES: case EventTypeEnum::EVENT_GET_NODE_DATA_FILES: case EventTypeEnum::EVENT_GET_OR_SET_USER_INPUT_MODE: case EventTypeEnum::EVENT_GRAPHICS_UPDATE_ALL_WINDOWS: case EventTypeEnum::EVENT_GRAPHICS_UPDATE_ONE_WINDOW: case EventTypeEnum::EVENT_HELP_VIEWER_DISPLAY: case EventTypeEnum::EVENT_IDENTIFICATION_HIGHLIGHT_LOCATION: case EventTypeEnum::EVENT_IDENTIFICATION_SYMBOL_REMOVAL: case EventTypeEnum::EVENT_IMAGE_CAPTURE: case EventTypeEnum::EVENT_MAC_DOCK_MENU_UPDATE: case EventTypeEnum::EVENT_MAP_YOKING_SELECT_MAP: case EventTypeEnum::EVENT_MAP_YOKING_VALIDATION: case EventTypeEnum::EVENT_MODEL_ADD: case EventTypeEnum::EVENT_MODEL_DELETE: case EventTypeEnum::EVENT_MODEL_GET_ALL: case EventTypeEnum::EVENT_MODEL_SURFACE_GET: case EventTypeEnum::EVENT_NODE_IDENTIFICATION_COLORS_GET_FROM_CHARTS: case EventTypeEnum::EVENT_OPERATING_SYSTEM_REQUEST_OPEN_DATA_FILE: case EventTypeEnum::EVENT_OVERLAY_SETTINGS_EDITOR_SHOW: case EventTypeEnum::EVENT_OVERLAY_VALIDATE: case EventTypeEnum::EVENT_PALETTE_COLOR_MAPPING_EDITOR_SHOW: case EventTypeEnum::EVENT_PALETTE_GET_BY_NAME: case EventTypeEnum::EVENT_SPEC_FILE_READ_DATA_FILES: case EventTypeEnum::EVENT_SURFACE_COLORING_INVALIDATE: case EventTypeEnum::EVENT_SURFACES_GET: case EventTypeEnum::EVENT_SURFACE_STRUCTURES_VALID_GET: case EventTypeEnum::EVENT_TOOLBOX_SELECTION_DISPLAY: case EventTypeEnum::EVENT_USER_INTERFACE_UPDATE: case EventTypeEnum::EVENT_PROGRESS_UPDATE: case EventTypeEnum::EVENT_UPDATE_INFORMATION_WINDOWS: case EventTypeEnum::EVENT_UPDATE_YOKED_WINDOWS: case EventTypeEnum::EVENT_UPDATE_VOLUME_EDITING_TOOLBAR: { const AString msg(EventTypeEnum::toName(eventType) + " has an special subclass of class Event and should never be sent as an event."); CaretAssertMessage(0, msg); CaretLogSevere(msg); } break; } } /** * Block an event. A counter is used to track blocking of each * event type. Each time a request is made to block an event type, * the counter is incremented for that event type. When a request * is made to un-block the event, the counter is decremented. This * allows multiple requests for blocking an event to come from * different sections of the source code. Thus, anytime the * blocking counter is greater than zero for an event, the event * is blocked. * * @param eventType * Type of event to block. * @param blockStatus * Blocking status (true increments blocking counter, * false decrements blocking counter. */ void EventManager::blockEvent(const EventTypeEnum::Enum eventType, const bool blockStatus) { const int32_t eventTypeIndex = static_cast(eventType); CaretAssertVectorIndex(m_eventBlockingCounter, eventTypeIndex); const AString eventName = EventTypeEnum::toName(eventType); if (blockStatus) { m_eventBlockingCounter[eventTypeIndex]++; CaretLogFiner("Blocking event " + eventName + " blocking counter is now " + AString::number(m_eventBlockingCounter[eventTypeIndex])); } else { if (m_eventBlockingCounter[eventTypeIndex] > 0) { m_eventBlockingCounter[eventTypeIndex]--; CaretLogFiner("Unblocking event " + eventName + " blocking counter is now " + AString::number(m_eventBlockingCounter[eventTypeIndex])); } else { const AString message("Trying to unblock event " + eventName + " but it is not blocked"); CaretAssertMessage(0, message); CaretLogWarning(message); } } } /** * @return The cumulative number of events that have been sent. */ int64_t EventManager::getEventIssuedCounter() const { return m_eventIssuedCounter; } workbench-1.1.1/src/Common/EventManager.h000066400000000000000000000110341255417355300202510ustar00rootroot00000000000000#ifndef __EVENT_MANAGER_H__ #define __EVENT_MANAGER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretObject.h" #include "EventTypeEnum.h" //#define CONTAINER_VECTOR 1 //#define CONTAINER_HASH_SET 1 #define CONTAINER_SET 1 #ifdef CONTAINER_VECTOR #include #elif CONTAINER_HASH_SET #include #include "CaretHashSet.h" #elif CONTAINER_SET #include #else INTENTIONAL_COMPILER_ERROR_MISSING_CONTAINER_TYPE #endif namespace caret { class Event; class EventListenerInterface; #ifdef CONTAINER_HASH_SET class EventListenerCompareHash { public: bool operator()(const EventListenerInterface* e1, const EventListenerInterface* e2) const { return (e1 == e2); } }; class EventListenerInterfaceHash { public: size_t operator()(const EventListenerInterface* p) const { EventListenerInterface* el = const_cast(p); return reinterpret_cast((void*)el); } }; #endif // CONTAINER_HASH_SET class EventManager : public CaretObject { public: static void createEventManager(); static void deleteEventManager(); static EventManager* get(); void addEventListener(EventListenerInterface* eventListener, const EventTypeEnum::Enum listenForEventType); void addProcessedEventListener(EventListenerInterface* eventListener, const EventTypeEnum::Enum listenForEventType); void removeEventFromListener(EventListenerInterface* eventListener, const EventTypeEnum::Enum listenForEventType); void removeAllEventsFromListener(EventListenerInterface* eventListener); void sendEvent(Event* event); void sendSimpleEvent(const EventTypeEnum::Enum eventType); void blockEvent(const EventTypeEnum::Enum eventToBlock, const bool blockStatus); int64_t getEventIssuedCounter() const; private: EventManager(); virtual ~EventManager(); /** * Define the container */ #ifdef CONTAINER_VECTOR typedef std::vector EVENT_LISTENER_CONTAINER; #elif CONTAINER_HASH_SET typedef caret::hash_set EVENT_LISTENER_CONTAINER; #elif CONTAINER_SET typedef std::set EVENT_LISTENER_CONTAINER; #else INTENTIONAL_COMPILER_ERROR_MISSING_CONTAINER_TYPE #endif /** * Iterator for the container */ typedef EVENT_LISTENER_CONTAINER::iterator EVENT_LISTENER_CONTAINER_ITERATOR; /** * The event listeners */ EVENT_LISTENER_CONTAINER m_eventListeners[EventTypeEnum::EVENT_COUNT]; /** * Special listeners that are notified AFTER the eventListeners */ EVENT_LISTENER_CONTAINER m_eventProcessedListeners[EventTypeEnum::EVENT_COUNT]; /** Counter that is incremented each time an event is issued */ int64_t m_eventIssuedCounter; /** A counter for blocking events of each type */ std::vector m_eventBlockingCounter; static EventManager* s_singletonEventManager; }; #ifdef __EVENT_MANAGER_MAIN__ EventManager* EventManager::s_singletonEventManager = NULL; #endif // __EVENT_MANAGER_MAIN__ } // namespace #endif // __EVENT_MANAGER_H__ workbench-1.1.1/src/Common/EventProgressUpdate.cxx000066400000000000000000000075041255417355300222300ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "EventProgressUpdate.h" using namespace caret; /** * \class caret::EventProgressUpdate * \brief Event for updating progress of a task in a progress dialog. */ EventProgressUpdate::EventProgressUpdate(ProgressObject* myObject): Event(EventTypeEnum::EVENT_PROGRESS_UPDATE), m_minimumProgressValue(0), m_maximumProgressValue(100), m_progressValue(0), m_progressMessage(""), m_cancelled(false) { m_amountUpdate = false; m_finished = false; m_textUpdate = false; m_starting = false; m_whichObject = myObject; } /** * Default constructor. * * If this instance is not modified and sent to a progress dialog * no updates will be made to the progress dialog. However, if the * user has cancelled the task, the cancel status will be set in * this instance after returning from the dialog. */ EventProgressUpdate::EventProgressUpdate() : Event(EventTypeEnum::EVENT_PROGRESS_UPDATE), m_minimumProgressValue(-1), m_maximumProgressValue(-1), m_progressValue(-1), m_progressMessage(""), m_cancelled(false) { /* nothing */ } /* * Constructor for display of progress in a progress dialog. * * @param minimumProgressValue * Minimum Progress. * @param maximumProgressValue * Maximum progress. * @param progressValue * Current progress (min <= current <= maximum) * @param progressMessage * Message for display in progress dialog. * */ EventProgressUpdate::EventProgressUpdate(const int minimumProgressValue, const int maximumProgressValue, const int progressValue, const QString& progressMessage) : Event(EventTypeEnum::EVENT_PROGRESS_UPDATE), m_minimumProgressValue(minimumProgressValue), m_maximumProgressValue(maximumProgressValue), m_progressValue(progressValue), m_progressMessage(progressMessage), m_cancelled(false) { } EventProgressUpdate::EventProgressUpdate(const QString& progressMessage) : Event(EventTypeEnum::EVENT_PROGRESS_UPDATE), m_minimumProgressValue(-1), m_maximumProgressValue(-1), m_progressValue(-1), m_progressMessage(progressMessage), m_cancelled(false) { } /** * Destructor. */ EventProgressUpdate::~EventProgressUpdate() { } /* * Set values for display of progress in a progress dialog. This method * allows a progress event to be reused and avoid reallocation, particularly * when updates are frequent. * * @param progressValue * Current progress (min <= current <= maximum) * @param progressMessage * Message for display in progress dialog. * */ void EventProgressUpdate::setProgress(const int progressValue, const QString& progressMessage) { m_progressValue = progressValue; m_progressMessage = progressMessage; } /* * Update only the progress message in the progress dialog. * * @param progressMessage * Message for display in progress dialog. * */ void EventProgressUpdate::setProgressMessage(const QString& progressMessage) { m_progressMessage = progressMessage; } workbench-1.1.1/src/Common/EventProgressUpdate.h000066400000000000000000000057321255417355300216560ustar00rootroot00000000000000#ifndef __EVENT_PROGRESS_UPDATE_H__ #define __EVENT_PROGRESS_UPDATE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "Event.h" namespace caret { class ProgressObject; /// Event for updating the user-interface class EventProgressUpdate : public Event { public: EventProgressUpdate(); EventProgressUpdate(ProgressObject* myObject); EventProgressUpdate(const int minimumProgressValue, const int maximumProgressValue, const int progressValue, const QString& progressMessage); EventProgressUpdate(const QString& progressMessage); virtual ~EventProgressUpdate(); void setProgress(const int progressValue, const QString& progressMessage); void setProgressMessage(const QString& progressMessage); bool m_textUpdate, m_amountUpdate, m_finished, m_starting; ProgressObject* m_whichObject;//idea is for progress elements to check whether their object emitted this event or not, if not, ignore /** @return Did the user request cancellation of the task */ bool isCancelled() const { return m_cancelled; } /** @return Minimum progress value. */ int getMinimumProgressValue() const { return m_minimumProgressValue; } /** @return Maximum progress value */ int getMaximumProgressValue() const { return m_maximumProgressValue; } /** @return Current value of progress */ int getProgressValue() const { return m_progressValue; } /** @return Message displayed describing progress */ QString getProgressMessage() const { return m_progressMessage; } /** Request cancellation of the task */ void setCancelled() { m_cancelled = true; } private: EventProgressUpdate(const EventProgressUpdate&); EventProgressUpdate& operator=(const EventProgressUpdate&); int m_minimumProgressValue; int m_maximumProgressValue; int m_progressValue; QString m_progressMessage; bool m_cancelled; }; } // namespace #endif // __EVENT_PROGRESS_UPDATE_H__ workbench-1.1.1/src/Common/EventTypeEnum.cxx000066400000000000000000000430021255417355300210200ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __EVENT_TYPE_ENUM_DECLARE__ #include "EventTypeEnum.h" #undef __EVENT_TYPE_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumberated value. */ EventTypeEnum::EventTypeEnum(const Enum enumValue, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->name = name; this->guiName = guiName; } /** * Destructor. */ EventTypeEnum::~EventTypeEnum() { } /** * Initialize the enumerated metadata. */ void EventTypeEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(EventTypeEnum(EVENT_INVALID, "EVENT_INVALID", "Invalid Event")); enumData.push_back(EventTypeEnum(EVENT_ALERT_USER, "EVENT_ALERT_USER", "Alert user about something (if gui, a pop is displayed, otherwise logged at severe level")); enumData.push_back(EventTypeEnum(EVENT_BRAIN_RESET, "EVENT_BRAIN_RESET", "Brain has been reset")); enumData.push_back(EventTypeEnum(EVENT_BRAIN_STRUCTURE_GET_ALL, "EVENT_BRAIN_STRUCTURE_GET_ALL", "Get all brain structures")); enumData.push_back(EventTypeEnum(EVENT_BROWSER_TAB_DELETE, "EVENT_BROWSER_TAB_DELETE", "Delete a browser tab")); enumData.push_back(EventTypeEnum(EVENT_BROWSER_TAB_GET, "EVENT_BROWSER_TAB_GET", "Get a browser tab by number")); enumData.push_back(EventTypeEnum(EVENT_BROWSER_TAB_GET_ALL, "EVENT_BROWSER_TAB_GET_ALL", "Get ALL browser tabs")); enumData.push_back(EventTypeEnum(EVENT_BROWSER_TAB_GET_ALL_VIEWED, "EVENT_BROWSER_TAB_GET_ALL_VIEWED", "Get ALL Viewed browser tabs")); enumData.push_back(EventTypeEnum(EVENT_BROWSER_TAB_INDICES_GET_ALL, "EVENT_BROWSER_TAB_INDICES_GET_ALL", "Browser Tab Indices Get All")); enumData.push_back(EventTypeEnum(EVENT_BROWSER_TAB_NEW, "EVENT_BROWSER_TAB_NEW", "Create a browser tab")); enumData.push_back(EventTypeEnum(EVENT_BROWSER_WINDOW_CONTENT_GET, "EVENT_BROWSER_WINDOW_CONTENT_GET", "Get the content in a browser window")); enumData.push_back(EventTypeEnum(EVENT_BROWSER_WINDOW_CREATE_TABS, "EVENT_BROWSER_WINDOW_CREATE_TABS", "Create tabs (if needed) after loading data files")); enumData.push_back(EventTypeEnum(EVENT_BROWSER_WINDOW_GRAPHICS_HAVE_BEEN_REDRAWN, "EVENT_BROWSER_WINDOW_GRAPHICS_HAVE_BEEN_REDRAWN", "A Browser Window's graphics have been redrawn")); enumData.push_back(EventTypeEnum(EVENT_BROWSER_WINDOW_MENUS_UPDATE, "EVENT_BROWSER_WINDOW_MENUS_UPDATE", "Update the browser windows menus")); enumData.push_back(EventTypeEnum(EVENT_BROWSER_WINDOW_NEW, "EVENT_BROWSER_WINDOW_NEW", "Create a new browser window")); enumData.push_back(EventTypeEnum(EVENT_CARET_MAPPABLE_DATA_FILES_GET, "EVENT_CARET_MAPPABLE_DATA_FILES_GET", "Get all Caret Mappable data files")); enumData.push_back(EventTypeEnum(EVENT_CARET_MAPPABLE_DATA_FILE_MAPS_VIEWED_IN_OVERLAYS, "EVENT_CARET_MAPPABLE_DATA_FILE_MAPS_VIEWED_IN_OVERLAYS", "Get Caret Mappable data file maps viewed in overlays")); enumData.push_back(EventTypeEnum(EVENT_CHART_MATRIX_YOKING_VALIDATION, "EVENT_CHART_MATRIX_YOKING_VALIDATION", "Validate Yoking of matrix chart's rows/columns")); enumData.push_back(EventTypeEnum(EVENT_DATA_FILE_ADD, "EVENT_DATA_FILE_ADD", "Add a data file to the Brain")); enumData.push_back(EventTypeEnum(EVENT_DATA_FILE_DELETE, "EVENT_DATA_FILE_DELETE", "Delete a data file from the Brain")); enumData.push_back(EventTypeEnum(EVENT_DATA_FILE_READ, "EVENT_DATA_FILE_READ", "Read a data file into the Brain")); enumData.push_back(EventTypeEnum(EVENT_DATA_FILE_RELOAD, "EVENT_DATA_FILE_RELOAD", "Reopen a data file (replace it with saved version) in the Brain")); enumData.push_back(EventTypeEnum(EVENT_GET_DISPLAYED_DATA_FILES, "EVENT_GET_DISPLAYED_DATA_FILES", "Get data files displayed in windows/tabs")); enumData.push_back(EventTypeEnum(EVENT_GET_NODE_DATA_FILES, "EVENT_GET_NODE_DATA_FILES", "Get node data files")); enumData.push_back(EventTypeEnum(EVENT_GET_OR_SET_USER_INPUT_MODE, "EVENT_GET_OR_SET_USER_INPUT_MODE", "Get or set the user input mode")); enumData.push_back(EventTypeEnum(EVENT_GRAPHICS_UPDATE_ALL_WINDOWS, "EVENT_GRAPHICS_UPDATE_ALL_WINDOWS", "Update all graphics windows")); enumData.push_back(EventTypeEnum(EVENT_GRAPHICS_UPDATE_ONE_WINDOW, "EVENT_GRAPHICS_UPDATE_ONE_WINDOW", "Update graphics in one window")); enumData.push_back(EventTypeEnum(EVENT_HELP_VIEWER_DISPLAY, "EVENT_HELP_VIEWER_DISPLAY", "Display the help viewer")); enumData.push_back(EventTypeEnum(EVENT_IDENTIFICATION_HIGHLIGHT_LOCATION, "EVENT_IDENTIFICATION_HIGHLIGHT_LOCATION", "Highlight the location when identification takes place")); enumData.push_back(EventTypeEnum(EVENT_IDENTIFICATION_SYMBOL_REMOVAL, "EVENT_IDENTIFICATION_SYMBOL_REMOVAL", "Remove all identification symbols")); enumData.push_back(EventTypeEnum(EVENT_IMAGE_CAPTURE, "EVENT_IMAGE_CAPTURE", "Capture an Image of Browser Window Graphics Region")); enumData.push_back(EventTypeEnum(EVENT_MAC_DOCK_MENU_UPDATE, "EVENT_MAC_DOCK_MENU_UPDATE", "Update the Mac Dock Menu")); enumData.push_back(EventTypeEnum(EVENT_MAP_YOKING_SELECT_MAP, "EVENT_MAP_YOKING_SELECT_MAP", "Map Yoking Select Map")); enumData.push_back(EventTypeEnum(EVENT_MAP_YOKING_VALIDATION, "EVENT_MAP_YOKING_VALIDATION", "Map Yoking Validation")); enumData.push_back(EventTypeEnum(EVENT_MODEL_ADD, "EVENT_MODEL_ADD", "Add a model")); enumData.push_back(EventTypeEnum(EVENT_MODEL_DELETE, "EVENT_MODEL_DELETE", "Delete a model")); enumData.push_back(EventTypeEnum(EVENT_MODEL_GET_ALL, "EVENT_MODEL_GET_ALL", "Get all models")); enumData.push_back(EventTypeEnum(EVENT_MODEL_SURFACE_GET, "EVENT_MODEL_SURFACE_GET", "Get a specific model surface")); enumData.push_back(EventTypeEnum(EVENT_NODE_IDENTIFICATION_COLORS_GET_FROM_CHARTS, "EVENT_NODE_IDENTIFICATION_COLORS_GET_FROM_CHARTS", "Get the color for node identification symbols from all charts that contain nodes")); enumData.push_back(EventTypeEnum(EVENT_OPERATING_SYSTEM_REQUEST_OPEN_DATA_FILE, "EVENT_OPERATING_SYSTEM_REQUEST_OPEN_DATA_FILE", "Operating system requests open data file (Mac only)")); enumData.push_back(EventTypeEnum(EVENT_OVERLAY_SETTINGS_EDITOR_SHOW, "EVENT_OVERLAY_SETTINGS_EDITOR_SHOW", "Request display of overlay settings editor")); enumData.push_back(EventTypeEnum(EVENT_OVERLAY_VALIDATE, "EVENT_OVERLAY_VALIDATE", "Validate an overlay for validity (it exists)")); enumData.push_back(EventTypeEnum(EVENT_PALETTE_COLOR_MAPPING_EDITOR_SHOW, "EVENT_PALETTE_COLOR_MAPPING_EDITOR_SHOW", "Request display of palette color mapping editor")); enumData.push_back(EventTypeEnum(EVENT_PALETTE_GET_BY_NAME, "EVENT_PALETTE_GET_BY_NAME", "Read the selected files in a spec file")); enumData.push_back(EventTypeEnum(EVENT_SPEC_FILE_READ_DATA_FILES, "EVENT_SPEC_FILE_READ_DATA_FILES", "Read the selected data files in a spec file")); enumData.push_back(EventTypeEnum(EVENT_SURFACE_COLORING_INVALIDATE, "EVENT_SURFACE_COLORING_INVALIDATE", "Invalidate surface coloring")); enumData.push_back(EventTypeEnum(EVENT_SURFACES_GET, "EVENT_SURFACES_GET", "Get Surfaces")); enumData.push_back(EventTypeEnum(EVENT_SURFACE_STRUCTURES_VALID_GET, "EVENT_SURFACE_STRUCTURES_VALID_GET", "GGet valid surface strucutures and their number of node")); enumData.push_back(EventTypeEnum(EVENT_TOOLBOX_SELECTION_DISPLAY, "EVENT_TOOLBOX_SELECTION_DISPLAY", "Display or hide the selection toolbox")); enumData.push_back(EventTypeEnum(EVENT_USER_INTERFACE_UPDATE, "EVENT_USER_INTERFACE_UPDATE", "Update the user-interface")); enumData.push_back(EventTypeEnum(EVENT_PROGRESS_UPDATE, "EVENT_PROGRESS_UPDATE", "Update the progress amount, text, or finished status")); enumData.push_back(EventTypeEnum(EVENT_UPDATE_INFORMATION_WINDOWS, "EVENT_UPDATE_INFORMATION_WINDOWS", "Update the information windows")); enumData.push_back(EventTypeEnum(EVENT_UPDATE_YOKED_WINDOWS, "EVENT_UPDATE_YOKED_WINDOWS", "Update yoked windows graphics and toolbar")); enumData.push_back(EventTypeEnum(EVENT_UPDATE_VOLUME_EDITING_TOOLBAR, "EVENT_UPDATE_VOLUME_EDITING_TOOLBAR", "Update the volume editing toolbar")); enumData.push_back(EventTypeEnum(EVENT_COUNT, "EVENT_COUNT", "Count of events")); CaretAssertMessage((enumData.size() == static_cast(EVENT_COUNT + 1)), ("Number of EventTypeEnum::Enum values is incorrect.\n" "Have enumerated type been added?\n" "enumData.size()=" + AString::number(enumData.size()) + " EVENT_COUNT+1=" + AString::number(EVENT_COUNT + 1))); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const EventTypeEnum* EventTypeEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const EventTypeEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString EventTypeEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const EventTypeEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ EventTypeEnum::Enum EventTypeEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = EVENT_INVALID; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const EventTypeEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type EventTypeEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString EventTypeEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const EventTypeEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ EventTypeEnum::Enum EventTypeEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = EVENT_INVALID; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const EventTypeEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type EventTypeEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void EventTypeEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } workbench-1.1.1/src/Common/EventTypeEnum.h000066400000000000000000000164651255417355300204620ustar00rootroot00000000000000#ifndef __EVENT_TYPE_ENUM__H_ #define __EVENT_TYPE_ENUM__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { /// Enumerated type for events. class EventTypeEnum { public: /** * Enumerated values. */ enum Enum { /** Invalid event */ EVENT_INVALID, /** Alert user about something */ EVENT_ALERT_USER, /** Inform that Brain has been reset (new spec or scene loaded) */ EVENT_BRAIN_RESET, /** Get all brain structures */ EVENT_BRAIN_STRUCTURE_GET_ALL, /** Delete a browser tab. */ EVENT_BROWSER_TAB_DELETE, /** Get a browser tab by tab number */ EVENT_BROWSER_TAB_GET, /** Get indices of all valid browser tabs */ EVENT_BROWSER_TAB_INDICES_GET_ALL, /** Get ALL (both viewed and not viewed) browser tabs */ EVENT_BROWSER_TAB_GET_ALL, /** Get ALL VIEWED browser tabs (tabs that are viewed in windows) */ EVENT_BROWSER_TAB_GET_ALL_VIEWED, /** Create a new browser tab */ EVENT_BROWSER_TAB_NEW, /** Get the content of a browser window */ EVENT_BROWSER_WINDOW_CONTENT_GET, /** Create tabs after loading a file */ EVENT_BROWSER_WINDOW_CREATE_TABS, /** Issued after a browser window's graphicshave been redrawn */ EVENT_BROWSER_WINDOW_GRAPHICS_HAVE_BEEN_REDRAWN, /** Issued when displayed browser window menu's may change */ EVENT_BROWSER_WINDOW_MENUS_UPDATE, /** Create a new browser window */ EVENT_BROWSER_WINDOW_NEW, /** Get CaretMappable data files */ EVENT_CARET_MAPPABLE_DATA_FILES_GET, /** Get CaretMappableDataFiles and their maps viewed as overlays */ EVENT_CARET_MAPPABLE_DATA_FILE_MAPS_VIEWED_IN_OVERLAYS, /** Event for yoking the loading of matrix chart rows/columns */ EVENT_CHART_MATRIX_YOKING_VALIDATION, /** Add a data file into the Brain*/ EVENT_DATA_FILE_ADD, /** Delete a data file from the brain */ EVENT_DATA_FILE_DELETE, /** Read a data file into the Brain */ EVENT_DATA_FILE_READ, /** Reload (replace) a data file with its saved version in the brain*/ EVENT_DATA_FILE_RELOAD, /** Get data files that are display in windows/tabs */ EVENT_GET_DISPLAYED_DATA_FILES, /** Get node data files */ EVENT_GET_NODE_DATA_FILES, /** get or set the user input mode */ EVENT_GET_OR_SET_USER_INPUT_MODE, /** Update all graphics windows */ EVENT_GRAPHICS_UPDATE_ALL_WINDOWS, /** Update graphics in a window */ EVENT_GRAPHICS_UPDATE_ONE_WINDOW, /** Display the help viewer */ EVENT_HELP_VIEWER_DISPLAY, /** Highlight location when an identification occurs */ EVENT_IDENTIFICATION_HIGHLIGHT_LOCATION, /** Remove all identification symbols */ EVENT_IDENTIFICATION_SYMBOL_REMOVAL, /** Browser window image capture */ EVENT_IMAGE_CAPTURE, /** Update the Mac Dock Menu */ EVENT_MAC_DOCK_MENU_UPDATE, /** Validate when adding a mapped file to mapped yoking */ EVENT_MAP_YOKING_SELECT_MAP, /** Select a map for mapped yoked files */ EVENT_MAP_YOKING_VALIDATION, /** model - ADD */ EVENT_MODEL_ADD, /** model - DELETE */ EVENT_MODEL_DELETE, /** model - get all*/ EVENT_MODEL_GET_ALL, /** model surface - get */ EVENT_MODEL_SURFACE_GET, /** Get the color for a node's identification symbol from a chart that contains the node */ EVENT_NODE_IDENTIFICATION_COLORS_GET_FROM_CHARTS, /** open file request from the operating system (Mac only) for now */ EVENT_OPERATING_SYSTEM_REQUEST_OPEN_DATA_FILE, /** request display of overlay settings editor */ EVENT_OVERLAY_SETTINGS_EDITOR_SHOW, /** Validate that overlay is valid (it exists). */ EVENT_OVERLAY_VALIDATE, /** request display of palette color mapping editor */ EVENT_PALETTE_COLOR_MAPPING_EDITOR_SHOW, /** Get a palette by name from a palette file */ EVENT_PALETTE_GET_BY_NAME, /** Read the selected files in a spec file */ EVENT_SPEC_FILE_READ_DATA_FILES, /** Invalidate surface coloring */ EVENT_SURFACE_COLORING_INVALIDATE, /** Get surfaces */ EVENT_SURFACES_GET, /** Get valid surface strucutures and their number of nodes */ EVENT_SURFACE_STRUCTURES_VALID_GET, /** Display/Hide the selection toolbox */ EVENT_TOOLBOX_SELECTION_DISPLAY, /** Update the User-Interface */ EVENT_USER_INTERFACE_UPDATE, /** Update the progress amount, text, or finished status */ EVENT_PROGRESS_UPDATE, /** Update the information windows */ EVENT_UPDATE_INFORMATION_WINDOWS, /** Event to update yoked windows (graphics and toolbar) */ EVENT_UPDATE_YOKED_WINDOWS, /** Update the volume editing toolbar */ EVENT_UPDATE_VOLUME_EDITING_TOOLBAR, /* THIS MUST ALWAYS BE LAST - NOT an event type but is number of event types */ EVENT_COUNT }; ~EventTypeEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static void getAllEnums(std::vector& allEnums); private: EventTypeEnum(const Enum enumValue, const AString& name, const AString& guiName); static const EventTypeEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** The enumerated type value for an instance */ Enum enumValue; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; #ifdef __EVENT_TYPE_ENUM_DECLARE__ std::vector EventTypeEnum::enumData; bool EventTypeEnum::initializedFlag = false; #endif // __EVENT_TYPE_ENUM_DECLARE__ } // namespace #endif //__EVENT_TYPE_ENUM__H_ workbench-1.1.1/src/Common/FastStatistics.cxx000066400000000000000000000417451255417355300212340ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "FastStatistics.h" #include "CaretPointer.h" #include #include #include using namespace caret; using namespace std; const int64_t NUM_BUCKETS_PERCENTILE_HIST = 10000;//10,000 maximum to deal with some outliers outliers until I think of a better fix FastStatistics::FastStatistics() { reset(); } FastStatistics::FastStatistics(const float* data, const int64_t& dataCount) { update(data, dataCount); } void FastStatistics::reset() { m_posCount = 0; m_zeroCount = 0; m_negCount = 0; m_infCount = 0; m_negInfCount = 0; m_nanCount = 0; m_absCount = 0; m_mean = 0.0f; m_stdDevPop = 0.0f; m_stdDevSample = 0.0f; m_mostNeg = 0.0f; m_leastNeg = -numeric_limits::max(); m_leastPos = numeric_limits::max(); m_mostPos = 0.0f; m_leastAbs = numeric_limits::max(); m_mostAbs = 0.0; m_min = 0.0f; m_max = 0.0f; } void FastStatistics::update(const float* data, const int64_t& dataCount) { reset(); CaretArray positives(dataCount), negatives(dataCount), absolutes(dataCount); double sum = 0.0;//for numerical stability bool first = true;//so min can be positive and max can be negative for (int64_t i = 0; i < dataCount; ++i) { if (data[i] != data[i]) { ++m_nanCount; continue;//skip NaNs } if (data[i] == 0.0f)//test exactly zero (negative zero also tests equal), in case someone wants stats on something with miniscule values (percent of surface area per node?) { ++m_zeroCount; } else { if (data[i] < 0.0f) { if (data[i] * 2.0f == data[i]) { ++m_negInfCount; continue;//skip neg infs } else { negatives[m_negCount] = data[i]; ++m_negCount; if (data[i] > m_leastNeg) m_leastNeg = data[i]; if (data[i] < m_mostNeg) m_mostNeg = data[i]; absolutes[m_absCount] = -data[i]; if (absolutes[m_absCount] > m_mostAbs) m_mostAbs = absolutes[m_absCount]; if (absolutes[m_absCount] < m_leastAbs) m_leastAbs = absolutes[m_absCount]; ++m_absCount; } } else { if (data[i] * 2.0f == data[i]) { ++m_infCount; continue;//skip infs } else { positives[m_posCount] = data[i]; ++m_posCount; if (data[i] > m_mostPos) m_mostPos = data[i]; if (data[i] < m_leastPos) m_leastPos = data[i]; absolutes[m_absCount] = data[i]; if (absolutes[m_absCount] > m_mostAbs) m_mostAbs = absolutes[m_absCount]; if (absolutes[m_absCount] < m_leastAbs) m_leastAbs = absolutes[m_absCount]; ++m_absCount; } } } if (data[i] > m_max || first) m_max = data[i]; if (data[i] < m_min || first) m_min = data[i]; sum += data[i];//use a two-pass method for stability, only do mean this pass first = false; } int64_t totalGood = (m_negCount + m_zeroCount + m_posCount); m_mean = sum / totalGood; float tempf; double sum2 = 0.0; for (int64_t i = 0; i < dataCount; ++i) { if (data[i] != data[i]) continue;//skip NaNs if (data[i] < -1.0f && (data[i] * 2.0f == data[i])) continue;//exclude -inf if (data[i] > 1.0f && (data[i] * 2.0f == data[i])) continue;//exclude inf tempf = data[i] - m_mean; sum2 += tempf * tempf; } if (totalGood > 0) { m_stdDevPop = sqrt(sum2 / totalGood); if (totalGood > 1) { m_stdDevSample = sqrt(sum2 / (totalGood - 1)); } } int usebuckets = min(NUM_BUCKETS_PERCENTILE_HIST, dataCount); m_negPercentHist.update(usebuckets, negatives, m_negCount); m_posPercentHist.update(usebuckets, positives, m_posCount); m_absPercentHist.update(usebuckets, absolutes, m_absCount); if (m_negCount <= 0) { m_leastNeg = 0.0; m_mostNeg = 0.0; } if (m_posCount <= 0) { m_leastPos = 0.0; m_mostPos = 0.0; } if (m_absCount <= 0) { m_leastAbs = 0.0; m_mostAbs = 0.0; } } void FastStatistics::update(const float* data, const int64_t& dataCount, const float& minThreshInclusive, const float& maxThreshInclusive) { reset(); CaretArray positives(dataCount), negatives(dataCount), absolutes(dataCount); double sum = 0.0;//for numerical stability bool first = true;//so min can be positive and max can be negative for (int64_t i = 0; i < dataCount; ++i) { if (data[i] != data[i]) { ++m_nanCount; continue;//skip NaNs } if (data[i] < -1.0f && (data[i] * 2.0f == data[i])) { ++m_negInfCount; continue;//skip and count all infs, ignoring the range for now } if (data[i] > 1.0f && (data[i] * 2.0f == data[i])) { ++m_infCount; continue;//ditto } if (data[i] < minThreshInclusive || data[i] > maxThreshInclusive) {//we now have only numerical values continue;//skip them if they are outside the range } if (data[i] == 0.0f)//test exactly zero (negative zero also tests equal), in case someone wants stats on something with miniscule values (percent of surface area per node?) { ++m_zeroCount; } else { if (data[i] < 0.0f) { negatives[m_negCount] = data[i]; ++m_negCount; if (data[i] > m_leastNeg) m_leastNeg = data[i]; if (data[i] < m_mostNeg) m_mostNeg = data[i]; absolutes[m_absCount] = -data[i]; ++m_absCount; } else { positives[m_posCount] = data[i]; ++m_posCount; if (data[i] > m_mostPos) m_mostPos = data[i]; if (data[i] < m_leastPos) m_leastPos = data[i]; absolutes[m_absCount] = data[i]; ++m_absCount; } } if (data[i] > m_max || first) m_max = data[i]; if (data[i] < m_min || first) m_min = data[i]; sum += data[i];//use a two-pass method for stability, only do mean this pass first = false; } int64_t totalGood = (m_negCount + m_zeroCount + m_posCount); m_mean = sum / totalGood; float tempf; double sum2 = 0.0; for (int64_t i = 0; i < dataCount; ++i) { if (data[i] != data[i]) continue;//skip NaNs if (data[i] < -1.0f && (data[i] * 2.0f == data[i])) continue;//exclude -inf if (data[i] > 1.0f && (data[i] * 2.0f == data[i])) continue;//exclude inf tempf = data[i] - m_mean; sum2 += tempf * tempf; } if (totalGood > 0) { m_stdDevPop = sqrt(sum2 / totalGood); if (totalGood > 1) { m_stdDevSample = sqrt(sum2 / (totalGood - 1)); } } int usebuckets = min(NUM_BUCKETS_PERCENTILE_HIST, dataCount); m_negPercentHist.update(usebuckets, negatives, m_negCount);//10,000 will probably allow us to approximate the percentiles pretty closely, and eats only 80K of memory each m_posPercentHist.update(usebuckets, positives, m_posCount); m_absPercentHist.update(usebuckets, absolutes, m_absCount); if (m_negCount <= 0) { m_leastNeg = 0.0; m_mostNeg = 0.0; } if (m_posCount <= 0) { m_leastPos = 0.0; m_mostPos = 0.0; } if (m_absCount <= 0) { m_leastAbs = 0.0; m_mostAbs = 0.0; } } float FastStatistics::getApproxNegativePercentile(const float& percent) const { float rank = percent / 100.0f * m_negCount;//translate to rank rank = m_negCount - rank;//reverse it because negatives go the other direction, histogram is strictly directional towards positive if (rank <= 0) return m_mostNeg; if (rank >= m_negCount) return m_leastNeg; float histMin, histMax; m_negPercentHist.getRange(histMin, histMax); const vector& cumulative = m_negPercentHist.getHistogramCumulativeCounts(); int numBuckets = (int)cumulative.size(); int lowBound = -1, highBound = numBuckets, guess;//bisection search, "index" -1 is implicitly valued zero while (highBound - lowBound > 1) { guess = (lowBound + highBound) / 2; if (cumulative[guess] <= rank) { lowBound = guess; } else { highBound = guess; } } if (highBound == numBuckets) return m_mostPos;//the count mismatched the histogram somehow float bucketsize = (histMax - histMin) / numBuckets; int64_t curLower, curUpper = cumulative[highBound]; if (lowBound > -1) { curLower = cumulative[lowBound]; } else { curLower = 0; } if (highBound > 0 && ((highBound == 1 && cumulative[0] == 0) || (highBound > 1 && cumulative[highBound - 1] == cumulative[highBound - 2]))) {//tweak the function a bit if there is a bin that collected zero to the immediate left, to reduce discontinuities if (rank - curLower >= 1.0f) { ++curLower;//tweak the low end to start from one higher, to make it continuous with the tweak below } else { --highBound;//move left, because this interpolated rank doesn't fall within the highBound bucket --lowBound; curUpper = curLower + 1;//add one to the right end of the flat spot to give it nonzero slope while (lowBound > 0 && cumulative[highBound] == cumulative[lowBound - 1]) { --lowBound;//slide left boundary over the flat spot } if (lowBound == 0 && cumulative[lowBound] == 0) { --lowBound;//including if first bucket is zero, this shouldn't happen unless all valid values are equal (and a low count of values) } if (lowBound > -1) { curLower = cumulative[lowBound]; } else { curLower = 0; } } } float lowValue = histMin + (lowBound + 1) * bucketsize, highValue = histMin + (highBound + 1) * bucketsize; return lowValue + (highValue - lowValue) * (rank - curLower) / (curUpper - curLower); } float FastStatistics::getApproxPositivePercentile(const float& percent) const { float rank = percent / 100.0f * m_posCount;//translate to rank if (rank <= 0.0f) return m_leastPos; if (rank >= m_posCount) return m_mostPos; float histMin, histMax; m_posPercentHist.getRange(histMin, histMax); const vector& cumulative = m_posPercentHist.getHistogramCumulativeCounts(); int numBuckets = (int)cumulative.size(); int lowBound = -1, highBound = numBuckets, guess;//bisection search, "index" -1 is implicitly valued zero while (highBound - lowBound > 1) { guess = (lowBound + highBound) / 2; if (cumulative[guess] <= rank) { lowBound = guess; } else { highBound = guess; } } if (highBound == numBuckets) return m_mostPos;//the count mismatched the histogram somehow float bucketsize = (histMax - histMin) / numBuckets; int64_t curLower, curUpper = cumulative[highBound]; if (lowBound > -1) { curLower = cumulative[lowBound]; } else { curLower = 0; } if (highBound > 0 && ((highBound == 1 && cumulative[0] == 0) || (highBound > 1 && cumulative[highBound - 1] == cumulative[highBound - 2]))) {//tweak the function a bit if there is a bin that collected zero to the immediate left, to reduce discontinuities if (rank - curLower >= 1.0f) { ++curLower;//tweak the low end to start from one higher, to make it continuous with the tweak below } else { --highBound;//move left, because this interpolated rank doesn't fall within the highBound bucket --lowBound; curUpper = curLower + 1;//add one to the right end of the flat spot to give it nonzero slope while (lowBound > 0 && cumulative[highBound] == cumulative[lowBound - 1]) { --lowBound;//slide left boundary over the flat spot } if (lowBound == 0 && cumulative[lowBound] == 0) { --lowBound;//including if first bucket is zero, this shouldn't happen unless all valid values are equal (and a low count of values) } if (lowBound > -1) { curLower = cumulative[lowBound]; } else { curLower = 0; } } } float lowValue = histMin + (lowBound + 1) * bucketsize, highValue = histMin + (highBound + 1) * bucketsize; return lowValue + (highValue - lowValue) * (rank - curLower) / (curUpper - curLower); } float FastStatistics::getApproxAbsolutePercentile(const float& percent) const { float rank = percent / 100.0f * m_absCount;//translate to rank if (rank <= 0.0f) return m_leastAbs; if (rank >= m_absCount) return m_mostAbs; float histMin, histMax; m_absPercentHist.getRange(histMin, histMax); const vector& cumulative = m_absPercentHist.getHistogramCumulativeCounts(); int numBuckets = (int)cumulative.size(); int lowBound = -1, highBound = numBuckets, guess;//bisection search, "index" -1 is implicitly valued zero while (highBound - lowBound > 1) { guess = (lowBound + highBound) / 2; if (cumulative[guess] <= rank) { lowBound = guess; } else { highBound = guess; } } if (highBound == numBuckets) return m_mostAbs;//the count mismatched the histogram somehow float bucketsize = (histMax - histMin) / numBuckets; int64_t curLower, curUpper = cumulative[highBound]; if (lowBound > -1) { curLower = cumulative[lowBound]; } else { curLower = 0; } if (highBound > 0 && ((highBound == 1 && cumulative[0] == 0) || (highBound > 1 && cumulative[highBound - 1] == cumulative[highBound - 2]))) {//tweak the function a bit if there is a bin that collected zero to the immediate left, to reduce discontinuities if (rank - curLower >= 1.0f) { ++curLower;//tweak the low end to start from one higher, to make it continuous with the tweak below } else { --highBound;//move left, because this interpolated rank doesn't fall within the highBound bucket --lowBound; curUpper = curLower + 1;//add one to the right end of the flat spot to give it nonzero slope while (lowBound > 0 && cumulative[highBound] == cumulative[lowBound - 1]) { --lowBound;//slide left boundary over the flat spot } if (lowBound == 0 && cumulative[lowBound] == 0) { --lowBound;//including if first bucket is zero, this shouldn't happen unless all valid values are equal (and a low count of values) } if (lowBound > -1) { curLower = cumulative[lowBound]; } else { curLower = 0; } } } float lowValue = histMin + (lowBound + 1) * bucketsize, highValue = histMin + (highBound + 1) * bucketsize; return lowValue + (highValue - lowValue) * (rank - curLower) / (curUpper - curLower); } float FastStatistics::getApproximateMedian() const { int64_t totalGood = m_negCount + m_zeroCount + m_posCount; if (m_negCount > m_posCount) { if (m_zeroCount > (m_negCount - m_posCount)) { return 0.0f; } else { return getApproxNegativePercentile((m_negCount - m_posCount - m_zeroCount) * 50.0f / totalGood); } } else { if (m_zeroCount > (m_posCount - m_negCount)) { return 0.0f; } else { return getApproxNegativePercentile((m_posCount - m_negCount - m_zeroCount) * 50.0f / totalGood); } } } workbench-1.1.1/src/Common/FastStatistics.h000066400000000000000000000070511255417355300206510ustar00rootroot00000000000000#ifndef __FAST_STATISTICS_H__ #define __FAST_STATISTICS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "Histogram.h" namespace caret { ///this class does statistics that are linear in complexity only, NO SORTING, this means its percentiles are approximate, using interpolation from a histogram class FastStatistics { Histogram m_posPercentHist, m_negPercentHist, m_absPercentHist; float m_min, m_max, m_mean, m_stdDevPop, m_stdDevSample; float m_mostPos, m_leastPos, m_leastNeg, m_mostNeg, m_leastAbs, m_mostAbs; ///counts of each class of number int64_t m_posCount, m_zeroCount, m_negCount, m_infCount, m_negInfCount, m_nanCount, m_absCount; void reset(); public: FastStatistics(); FastStatistics(const float* data, const int64_t& dataCount); void update(const float* data, const int64_t& dataCount); ///statistics and display are really not that related, so for now, only include a continuous clipping range, excluding the middle from data will do weird things to standard deviation void update(const float* data, const int64_t& dataCount, const float& minThreshInclusive, const float& maxThreshInclusive); float getApproxPositivePercentile(const float& percent) const; float getApproxNegativePercentile(const float& percent) const; float getApproxAbsolutePercentile(const float& percent) const; void getCounts(int64_t& posCount, int64_t& zeroCount, int64_t& negCount, int64_t& infCount, int64_t& negInfCount, int64_t& nanCount) const { posCount = m_posCount; zeroCount = m_zeroCount; negCount = m_negCount; infCount = m_infCount; negInfCount = m_negInfCount; nanCount = m_nanCount; } void getNonzeroRanges(float& mostNegative, float& leastNegative, float& leastPositive, float& mostPositive) const { mostNegative = m_mostNeg; leastNegative = m_leastNeg; leastPositive = m_leastPos; mostPositive = m_mostPos; } float getMostNegativeValue() const { return m_mostNeg; } float getMostPositiveValue() const { return m_mostPos; } float getMin() const { return m_min; } float getMax() const { return m_max; } float getMean() const { return m_mean; } float getApproximateMedian() const; float getSampleStdDev() const { return m_stdDevSample; } float getPopulationStdDev() const { return m_stdDevPop; } }; } #endif //__FAST_STATISTICS_H__ workbench-1.1.1/src/Common/FileAdapter.cxx000066400000000000000000000101441255417355300204310ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #define __FILE_ADAPTER_DECLARE__ #include "FileAdapter.h" #undef __FILE_ADAPTER_DECLARE__ #include "CaretAssert.h" #include "FileInformation.h" using namespace caret; /** * \class caret::FileAdapter * \brief Simplifies opening of files and streams. * * When an open method is called, the file is opened * in the appropriate mode, a stream is created, and * the stream is returned. When the destructor is * called it will take care of cleaning up any resources * and closing the file (if it has not already been * done by calling close(). * * If an instance of this class is created in a * try/catch block, create an instance of the class * statically (not NEW) so that if an exception is * thrown, the instance of this class will go out * of scope which results in the destructor being * called which cleans up resources and closes the * file. */ /** * Constructor. */ FileAdapter::FileAdapter() { m_file = NULL; m_textStream = NULL; } /** * */ FileAdapter::~FileAdapter() { close(); } /** * Open a file with the given name, create a QTextStream for * the file, and return the QTextStream. * * @filename * Name of file. * @errorMessagesOut * Contains information about errors if any occur. * @return * A pointer to the QTextStream that was created. DO NOT * destroy it or else disaster will likely occur. If this * returned value is NULL, it indicates that an error has * occurred and its description will be in the errorMessageOut * parameter. */ QTextStream* FileAdapter::openQTextStreamForWritingFile(const AString& filename, AString& errorMessageOut) { errorMessageOut = ""; if (m_file != NULL) { errorMessageOut = ("A file named " + m_file->fileName() + " is currently open with this FileAdapter"); return NULL; } if (m_file != NULL) { errorMessageOut = "This file is already open and has not been closed."; return NULL; } if (filename.isEmpty()) { errorMessageOut = "Filename contains no characters."; return NULL; } FileInformation fileInfo(filename); if (fileInfo.exists()) { if (fileInfo.isWritable() == false) { errorMessageOut = (filename + " exists but does not have writable permission."); return NULL; } } m_file = new QFile(filename); if (m_file->open(QFile::WriteOnly) == false) { errorMessageOut = ("Unable to open " + filename + " for writing: " + m_file->errorString()); delete m_file; m_file = NULL; return NULL; } m_textStream = new QTextStream(m_file); return m_textStream; } /** * If any streams are valid, they are deleted. * If the file is valid, its is flushed, closed * and deleted. */ void FileAdapter::close() { if (m_textStream != NULL) { m_textStream->flush(); delete m_textStream; m_textStream = NULL; } if (m_file != NULL) { m_file->flush(); m_file->close(); delete m_file; m_file = NULL; } } workbench-1.1.1/src/Common/FileAdapter.h000066400000000000000000000030321255417355300200540ustar00rootroot00000000000000#ifndef __FILE_ADAPTER__H_ #define __FILE_ADAPTER__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" class QFile; class QTextStream; namespace caret { class FileAdapter : public CaretObject { public: FileAdapter(); ~FileAdapter(); QTextStream* openQTextStreamForWritingFile(const AString& filename, AString& errorMessageOut); void close(); private: QFile* m_file; QTextStream* m_textStream; }; #ifdef __FILE_ADAPTER_DECLARE__ // #endif // __FILE_ADAPTER_DECLARE__ } // namespace #endif //__FILE_ADAPTER__H_ workbench-1.1.1/src/Common/FileInformation.cxx000066400000000000000000000446671255417355300213570ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __FILE_INFORMATION_DECLARE__ #include "FileInformation.h" #undef __FILE_INFORMATION_DECLARE__ #include "CaretLogger.h" #include "DataFile.h" #include "DataFileTypeEnum.h" using namespace caret; /** * \class caret::FileInformation * \brief Information about a file path. * \ingroup Common * * Provides information about a path (file, directory, etc). Support for * remote files is provided. Some of the methods are not appropriate for * remote files and the method's documentation indicates any limitations. */ /** * Constructor. * @param file * Name of path for which information is obtained. */ FileInformation::FileInformation(const AString& file) : CaretObject() { m_isLocalFile = false; m_isRemoteFile = false; if (DataFile::isFileOnNetwork(file)) { m_urlInfo.setUrl(file); m_isRemoteFile = true; } else { m_fileInfo.setFile(file); m_isLocalFile = true; } } /** * Constructor. * @param path * Directory containing the file. * @param file * Name of path for which information is obtained. */ FileInformation::FileInformation(const AString& path, const AString& file) : CaretObject() { m_isLocalFile = false; m_isRemoteFile = false; if (DataFile::isFileOnNetwork(file) || DataFile::isFileOnNetwork(path)) { AString pathCopy = path; if (pathCopy.endsWith("/") == false) { /* * With adding a trailing slash: * path: http://brainvis.wustl.edu/john/workbench * file: ParcellationPilot_AverageT1w.nii.gz * CORRECT: http://brainvis.wustl.edu/john/workbench/ParcellationPilot_AverageT1w.nii.gz * * Without adding the trailing slash "workbench" is chopped off * INCORRECT: http://brainvis.wustl.edu/john/ParcellationPilot_AverageT1w.nii.gz */ pathCopy += "/"; } QUrl baseUrl(pathCopy); QUrl relativeUrl(file); m_urlInfo = baseUrl.resolved(relativeUrl); m_isRemoteFile = true; CaretLogFine("Path: " + path + "\n File: " + file + "\n Becomes " + m_urlInfo.toString()); } else { m_fileInfo.setFile(path, file); /* * Clean up path to remove any ".." (up a directory level). * Note that canonicalFilePath() will return an empty string * if the path does not point to a valid file. */ if (getAbsoluteFilePath().contains("..")) { const AString cleanedPath = m_fileInfo.canonicalFilePath(); if (cleanedPath.isEmpty() == false) { m_fileInfo.setFile(cleanedPath); } } m_isLocalFile = true; } } /** * Destructor. */ FileInformation::~FileInformation() { } /** * @return True if the file is a local file, else false. */ bool FileInformation::isLocalFile() const { return m_isLocalFile; } /** * @return True if the file is a remote file, else false. */ bool FileInformation::isRemoteFile() const { return m_isRemoteFile; } /** * @return Absolute path including the name of the file. * * some logic that seems to be missing from QFileInfo: if absolute, * return path() + file() rather than using system call. * * Note: A remote file returns the original, full URL. */ AString FileInformation::getAbsoluteFilePath() const { if (m_isRemoteFile) { return m_urlInfo.toString(); } if (m_fileInfo.isAbsolute()) { return m_fileInfo.filePath(); } else { return m_fileInfo.absoluteFilePath(); } } /** * Removes the file. * Remove files cannot be removed. * * @return * true if file deleted successfully. */ bool FileInformation::remove() { if (m_isRemoteFile) { CaretLogSevere("Deleting remote file is not allowed: " + m_urlInfo.toString()); return false; } bool result = false; if (m_fileInfo.exists()) { result = QFile::remove(m_fileInfo.absoluteFilePath()); } return result; } /** * @return true if it exists, else false. * * A remote file always returns true. */ bool FileInformation::exists() const { if (m_isRemoteFile) { return true; } return m_fileInfo.exists(); } /** * @return true if it is file, else false. * * A remote file always returns true. */ bool FileInformation::isFile() const { if (m_isRemoteFile) { return true; } return m_fileInfo.isFile(); } /** * @return true if it is directory, else false. * * A remote file always returns false. */ bool FileInformation::isDirectory() const { if (m_isRemoteFile) { return false; } return m_fileInfo.isDir(); } /** * @return true if it is symbolic link, else false.0 * * A remote file always returns false. */ bool FileInformation::isSymbolicLink() const { if (m_isRemoteFile) { return false; } return m_fileInfo.isSymLink(); } /** * @return true if it is readable, else false. * * A remote file always returns true. */ bool FileInformation::isReadable() const { if (m_isRemoteFile) { return true; } return m_fileInfo.isReadable(); } /** * @return true if it is writable, else false. * * A remote file always returns false. */ bool FileInformation::isWritable() const { if (m_isRemoteFile) { return false; } return m_fileInfo.isWritable(); } /** * @return true if it is absolute path, else false. * * A remote file always returns true. */ bool FileInformation::isAbsolute() const { if (m_isRemoteFile) { return true; } return m_fileInfo.isAbsolute(); } /** * @return true if it is relative path, else false (remote file is never relative) * * A remote file always returns false. */ bool FileInformation::isRelative() const { if (m_isRemoteFile) { return false; } return m_fileInfo.isRelative(); } /** * @return true if it is hidden, else false. * * A remote file always returns false. */ bool FileInformation::isHidden() const { if (m_isRemoteFile) { return false; } return m_fileInfo.isHidden(); } /** * @return Size of the file in bytes. * * A remote file always returns 0. */ int64_t FileInformation::size() const { if (m_isRemoteFile) { return 0; } return m_fileInfo.size(); } /** * @return name of file followed by path in parenthesis. * * For example: /usr/local/file.txt * returns: file.txt (/usr/local) */ AString FileInformation::getFileNameFollowedByPathNameForGUI() const { AString name = getFileName(); const AString pn = getPathName(); if ( ! pn.isEmpty()) { name += (" (" + pn + ")"); } return name; } /** * @return Name of the file excluding any path. * * A remote file always anything after the last slash (/). If there is * no slash, an emtpy string is returned. */ AString FileInformation::getFileName() const { if (m_isRemoteFile) { QString name = m_urlInfo.toString(); const int indx = name.lastIndexOf('/'); if ((indx >= 0) && (indx < name.length())) { name = name.mid(indx + 1); return name; } else { return ""; } } return m_fileInfo.fileName(); } /** * @return Name of the file excluding any path and WITHOUT any extension. * * A remote file always anything after the last slash (/). If there is * no slash, an emtpy string is returned. */ AString FileInformation::getFileNameNoExtension() const { AString name = getFileName(); const AString ext = getFileExtension(); if ( ! ext.isEmpty()) { const int32_t extStartIndex = name.indexOf(ext); if (extStartIndex > 0) { name = name.left(extStartIndex - 1); } } return name; } /** * @return The file's path excluding the file's name. * * A remote file always everything before the last slash (/). If there is * no slash, the URL is returned. */ AString FileInformation::getPathName() const { if (m_isRemoteFile) { QString path = m_urlInfo.toString(); const int indx = path.lastIndexOf('/'); if (indx >= 1) { path = path.left(indx); return path; } else { return path; } } return m_fileInfo.path(); } /** * @return The full path to the file including the file name, resolving * any symlinks or ".." or "." components. This should give exactly one * string per file, no matter how many ways to get to a file there are * (except for hardlinks). * * Note: A remote file returns the original, full URL. */ AString FileInformation::getCanonicalFilePath() const { if (m_isRemoteFile) { return m_urlInfo.toString(); } return m_fileInfo.canonicalFilePath(); } /** * @return The full path to the file (excluding the file name), * resolving any symlinks or ".." or "." components. * * For a remote file, this returns getPathName(). */ AString FileInformation::getCanonicalPath() const { if (m_isRemoteFile) { return getPathName(); } return m_fileInfo.canonicalPath(); } /** * @return The file name's extension. * * Many Workbench files have filename extensions that include a * dot (nii.gz, pconn.nii, etc) AND users sometimes include dots * in the names of the files (Glasser_PilotIII.L.20k_fs_LR.shape.gii) * so FileInfo::suffix and FileInfo::completeSuffix methods will * not provide the correct file extension. * * This method will compare the end of the file's name to every Workbench * file extension. If there is a match, this extension is returned. * Otherwise FileInfo::suffix is called and that will return anything * after but not including the last dot. */ AString FileInformation::getFileExtension() const { const std::vector workbenchExtensions = DataFileTypeEnum::getFilesExtensionsForEveryFile(); for (std::vector::const_iterator extIter = workbenchExtensions.begin(); extIter != workbenchExtensions.end(); extIter++) { const AString extension = *extIter; if ( ! extension.isEmpty()) { if (getFileName().endsWith(extension)) { return extension; } } } // if (m_isRemoteFile) { // AString ext = getFileName(); // const int indx = ext.lastIndexOf('.'); // if ((indx >= 0) && (indx < ext.length())) { // ext = ext.mid(indx + 1); // return ext; // } // else { // return ""; // } // } return m_fileInfo.suffix(); } /** * Get the components for a filename. * * Example: /Volumes/myelin1/caret7_gui_design/data/HCP_demo/areas.border * Returns * absolutePathOut => /Volumes/myelin1/caret7_gui_design/data/HCP_demo * fileNameWithoutExtensionOut => areas * extensionWithoutDotOut => border * * @param absolutePathOut * Absolute path of file file. Could be empty if this instance * was created using a filename without a path. * @param fileNameWithoutExtensionOut * Name of the file without path and without extention. * @param extensionWithoutDotOut * Extension without the dot. Could be empty if filename does * not have an extension. */ void FileInformation::getFileComponents(AString& absolutePathOut, AString& fileNameWithoutExtensionOut, AString& extensionWithoutDotOut) const { absolutePathOut = getAbsolutePath(); fileNameWithoutExtensionOut = getFileNameNoExtension(); extensionWithoutDotOut = getFileExtension(); } /** * Assemble the file components into a file path and name. * * @param pathName * Path for file (may be absolute, relative, or empty). * @param fileNameWithoutExtension * Name of file without extension. * @param extensionWithoutDot * The file extension without the leading dot. */ AString FileInformation::assembleFileComponents(AString& pathName, AString& fileNameWithoutExtension, AString& extensionWithoutDot) { AString name; if ( ! pathName.isEmpty()) { name += (pathName + "/"); } name += fileNameWithoutExtension; if ( ! extensionWithoutDot.isEmpty()) { name += ("." + extensionWithoutDot); } return name; } /** * Convert, if needed, the file information to a local, absolute path. * * If the file is a remote file, the file name is added to the given current directory. * * If the file is local but a relative path, the filename is added to the given * current directory. * * If the file is local but an absolute path, the result of getAbsoluteFilePath() * is returned. * * @param currentDirectory * The current directory used by remote and relative file paths. * @param dataFileType * The type of data file. If the type is not UNKNOWN, and the * the file name does not end in the proper extension, the extension * is added to the file. @ @return * The local absolute file path. */ AString FileInformation::getAsLocalAbsoluteFilePath(const AString& currentDirectory, const DataFileTypeEnum::Enum dataFileType) const { AString thePath, theName, theExtension; getFileComponents(thePath, theName, theExtension); if (m_isLocalFile) { if (m_fileInfo.isRelative()) { thePath = currentDirectory; } } else if (m_isRemoteFile) { thePath = currentDirectory; theName = theName.replace("?", "_"); theName = theName.replace(":", "_"); theName = theName.replace("=", "_"); theName = theName.replace("@", "_"); } if (dataFileType != DataFileTypeEnum::UNKNOWN) { AString validExtension = ""; const std::vector validExtensions = DataFileTypeEnum::getAllFileExtensions(dataFileType); for (std::vector::const_iterator iter = validExtensions.begin(); iter != validExtensions.end(); iter++) { const AString ext = *iter; if (theExtension == ext) { validExtension = ext; break; } } if (validExtension.isEmpty()) { validExtension = DataFileTypeEnum::toFileExtension(dataFileType); } theExtension = validExtension; } const AString nameOut = assembleFileComponents(thePath, theName, theExtension); return nameOut; } /** * @return The file's absolute path. It DOES NOT include the file's name. * Note: A remote file returns everything up to the last slash. */ AString FileInformation::getAbsolutePath() const { if (m_isRemoteFile) { const AString fullName = m_urlInfo.toString(); const int lastSlashIndex = fullName.lastIndexOf("/"); if (lastSlashIndex > 0) { const AString thePath = fullName.left(lastSlashIndex); return thePath; } return fullName; } return m_fileInfo.absolutePath(); } AString FileInformation::getLastDirectory() const { QStringList myList = getPathName().split('/', QString::SkipEmptyParts);//QT always uses /, even on windows return myList[myList.size() - 1]; } /** * Get a description of this object's content. * @return String describing this object's content. */ AString FileInformation::toString() const { if (m_isRemoteFile) { return ("FileInformation for " + m_urlInfo.toString()); } return ("FileInformation for " + m_fileInfo.absoluteFilePath()); } /** * For a remote file, strip the username and password from the URL * and return the URL (without username and password), the username, * and the password. If the file is local or does not contain * username/password, the equivalent of getFilePath() is the output URL. * * @param urlOut * The URL with the username and password removed. * @param * The username that was in the URL. * @param * The password that was in the URL. */ void FileInformation::getRemoteUrlUsernameAndPassword(AString& urlOut, AString& usernameOut, AString& passwordOut) const { urlOut = ""; usernameOut = ""; passwordOut = ""; if (m_isRemoteFile) { urlOut = m_urlInfo.toString(QUrl::RemoveUserInfo); usernameOut = m_urlInfo.userName(); passwordOut = m_urlInfo.password(); return; } urlOut = getAbsoluteFilePath(); } /** * Convert the number of bytes to a string that includes standard units * (ie: Bytes, Kilobytes, Megabytes, Gigabytes, etc.) * * @param numberOfBytes * The number of bytes. * @return * String with the size in standard units. */ AString FileInformation::fileSizeToStandardUnits(const int64_t numberOfBytes) { double bytes = numberOfBytes; short index = 0; static const char *labels[9] = {" Bytes", " Kilobytes", " Megabytes", " Gigabytes", " Terabytes", " Petabytes", " Exabytes", " Zettabytes", " Yottabytes"}; while (index < 8 && bytes > 1000.0f) { ++index; bytes = bytes / 1000.0f;//using 1024 would make it Kibibytes, etc } AString sizeString = AString::number(bytes, 'f', 2) + labels[index];//2 digits after decimal point return sizeString; } workbench-1.1.1/src/Common/FileInformation.h000066400000000000000000000073271255417355300207740ustar00rootroot00000000000000#ifndef __FILE_INFORMATION__H_ #define __FILE_INFORMATION__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "DataFileTypeEnum.h" #include #include namespace caret { class FileInformation : public CaretObject { public: FileInformation(const AString& file); FileInformation(const AString& path, const AString& file); virtual ~FileInformation(); bool isLocalFile() const; bool isRemoteFile() const; bool exists() const; bool isFile() const; bool isDirectory() const; bool isSymbolicLink() const; bool isReadable() const; bool isWritable() const; bool isAbsolute() const; bool isRelative() const; bool isHidden() const; int64_t size() const; AString getAsLocalAbsoluteFilePath(const AString& currentDirectory, const DataFileTypeEnum::Enum dataFileType) const; AString getFileNameFollowedByPathNameForGUI() const; AString getFileName() const; AString getFileNameNoExtension() const; AString getPathName() const; AString getAbsoluteFilePath() const; AString getCanonicalFilePath() const; AString getCanonicalPath() const; AString getFileExtension() const; AString getAbsolutePath() const; AString getLastDirectory() const; void getFileComponents(AString& absolutePathOut, AString& fileNameWithoutExtensionOut, AString& extensionWithoutDotOut) const; static AString assembleFileComponents(AString& pathName, AString& fileNameWithoutExtension, AString& extensionWithoutDot); bool remove(); void getRemoteUrlUsernameAndPassword(AString& urlOut, AString& usernameOut, AString& passwordOut) const; static AString fileSizeToStandardUnits(const int64_t numberOfBytes); private: FileInformation(const FileInformation&); FileInformation& operator=(const FileInformation&); public: virtual AString toString() const; private: QFileInfo m_fileInfo; QUrl m_urlInfo; bool m_isRemoteFile; bool m_isLocalFile; }; #ifdef __FILE_INFORMATION_DECLARE__ // #endif // __FILE_INFORMATION_DECLARE__ } // namespace #endif //__FILE_INFORMATION__H_ workbench-1.1.1/src/Common/FloatMatrix.cxx000066400000000000000000000210741255417355300205070ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretAssert.h" #include "CaretException.h" #include "FloatMatrix.h" #include "MatrixFunctions.h" using namespace caret; using namespace std; bool FloatMatrix::checkDimensions() const { uint64_t rows = m_matrix.size(), cols; if (rows == 0) return true;//treat it as fine for now cols = m_matrix[0].size(); for (uint64_t i = 1; i < rows; ++i) { if (m_matrix[i].size() != cols) { return false; } } return true; } FloatMatrix::FloatMatrix(const vector >& matrixIn) { m_matrix = matrixIn; CaretAssert(checkDimensions()); } FloatMatrix::FloatMatrix(const int64_t& rows, const int64_t& cols) { resize(rows, cols, true); } bool FloatMatrix::operator!=(const FloatMatrix& right) const { return !(*this == right); } FloatMatrix FloatMatrix::operator*(const FloatMatrix& right) const { FloatMatrix ret; MatrixFunctions::multiply(m_matrix, right.m_matrix, ret.m_matrix); return ret; } FloatMatrix& FloatMatrix::operator*=(const FloatMatrix& right) { MatrixFunctions::multiply(m_matrix, right.m_matrix, m_matrix);//would need a copy anyway, so let it make the copy internally return *this; } FloatMatrix FloatMatrix::concatHoriz(const FloatMatrix& right) const { FloatMatrix ret; MatrixFunctions::horizCat(m_matrix, right.m_matrix, ret.m_matrix); return ret; } FloatMatrix FloatMatrix::concatVert(const FloatMatrix& bottom) const { FloatMatrix ret; MatrixFunctions::vertCat(m_matrix, bottom.m_matrix, ret.m_matrix); return ret; } FloatMatrix FloatMatrix::getRange(const int64_t firstRow, const int64_t afterLastRow, const int64_t firstCol, const int64_t afterLastCol) const { FloatMatrix ret; MatrixFunctions::getChunk(firstRow, afterLastRow, firstCol, afterLastCol, m_matrix, ret.m_matrix); return ret; } FloatMatrix FloatMatrix::identity(const int64_t rows) { FloatMatrix ret; MatrixFunctions::identity(rows, ret.m_matrix); return ret; } FloatMatrix FloatMatrix::inverse() const { FloatMatrix ret; MatrixFunctions::inverse(m_matrix, ret.m_matrix); return ret; } FloatMatrix& FloatMatrix::operator*=(const float& right) { MatrixFunctions::multiply(m_matrix, right, m_matrix);//internally makes a copy return *this; } FloatMatrix FloatMatrix::operator+(const FloatMatrix& right) const { FloatMatrix ret; MatrixFunctions::add(m_matrix, right.m_matrix, ret.m_matrix); return ret; } FloatMatrix& FloatMatrix::operator+=(const FloatMatrix& right) { MatrixFunctions::add(m_matrix, right.m_matrix, m_matrix); return *this; } FloatMatrix& FloatMatrix::operator+=(const float& right) { MatrixFunctions::add(m_matrix, right, m_matrix); return *this; } FloatMatrix FloatMatrix::operator-(const FloatMatrix& right) const { FloatMatrix ret; MatrixFunctions::subtract(m_matrix, right.m_matrix, ret.m_matrix); return ret; } FloatMatrix& FloatMatrix::operator-=(const FloatMatrix& right) { MatrixFunctions::subtract(m_matrix, right.m_matrix, m_matrix); return *this; } FloatMatrix& FloatMatrix::operator-=(const float& right) { MatrixFunctions::add(m_matrix, (-right), m_matrix); return *this; } FloatMatrix& FloatMatrix::operator/=(const float& right) { return ((*this) *= 1.0f / right); } bool FloatMatrix::operator==(const FloatMatrix& right) const { if (this == &right) { return true;//short circuit true on pointer equivalence } int64_t i, j, rows = (int64_t)m_matrix.size(), cols; if (rows != (int64_t)right.m_matrix.size()) { return false; } if (rows == 0) { return true;//don't try to get the second dimension } cols = (int64_t)m_matrix[0].size(); if (cols != (int64_t)right.m_matrix[0].size()) { return false; } for (i = 0; i < rows; ++i) { for (j = 0; j < cols; ++j) { if (m_matrix[i][j] != right.m_matrix[i][j]) { return false; } } } return true; } void FloatMatrix::getDimensions(int64_t& rows, int64_t& cols) const { rows = (int64_t)m_matrix.size(); if (rows == 0) { cols = 0; } else { cols = (int64_t)m_matrix[0].size(); } } FloatMatrixRowRef FloatMatrix::operator[](const int64_t& index) { CaretAssert(index > -1 && index < (int64_t)m_matrix.size()); FloatMatrixRowRef ret(m_matrix[index]); return ret; } ConstFloatMatrixRowRef FloatMatrix::operator[](const int64_t& index) const { CaretAssert(index > -1 && index < (int64_t)m_matrix.size()); ConstFloatMatrixRowRef ret(m_matrix[index]); return ret; } FloatMatrix FloatMatrix::reducedRowEchelon() const { FloatMatrix ret(*this); MatrixFunctions::rref(ret.m_matrix); return ret; } void FloatMatrix::resize(const int64_t rows, const int64_t cols, const bool destructive) { MatrixFunctions::resize(rows, cols, m_matrix, destructive); } FloatMatrix FloatMatrix::transpose() const { FloatMatrix ret; MatrixFunctions::transpose(m_matrix, ret.m_matrix); return ret; } FloatMatrix FloatMatrix::zeros(const int64_t rows, const int64_t cols) { FloatMatrix ret; MatrixFunctions::zeros(rows, cols, ret.m_matrix); return ret; } FloatMatrix FloatMatrix::ones(const int64_t rows, const int64_t cols) { FloatMatrix ret; MatrixFunctions::ones(rows, cols, ret.m_matrix); return ret; } const vector >& FloatMatrix::getMatrix() const { return m_matrix; } void FloatMatrix::getAffineVectors(Vector3D& xvec, Vector3D& yvec, Vector3D& zvec, Vector3D& offset) const { if (m_matrix.size() < 3 || m_matrix.size() > 4 || m_matrix[0].size() != 4) { throw CaretException("getAffineVectors called on incorrectly sized matrix"); } xvec[0] = m_matrix[0][0]; xvec[1] = m_matrix[1][0]; xvec[2] = m_matrix[2][0]; yvec[0] = m_matrix[0][1]; yvec[1] = m_matrix[1][1]; yvec[2] = m_matrix[2][1]; zvec[0] = m_matrix[0][2]; zvec[1] = m_matrix[1][2]; zvec[2] = m_matrix[2][2]; offset[0] = m_matrix[0][3]; offset[1] = m_matrix[1][3]; offset[2] = m_matrix[2][3]; } FloatMatrix FloatMatrix::operator-() const { int64_t rows, cols; getDimensions(rows, cols); FloatMatrix ret = zeros(rows, cols); ret -= *this; return ret; } FloatMatrixRowRef::FloatMatrixRowRef(vector& therow) : m_row(therow) { } FloatMatrixRowRef& FloatMatrixRowRef::operator=(const FloatMatrixRowRef& right) { if (&m_row == &(right.m_row)) {//just in case vector isn't smart enough to check self assignment return *this; } CaretAssert(m_row.size() == right.m_row.size());//maybe this should be an exception, not an assertion? m_row = right.m_row; return *this; } FloatMatrixRowRef& FloatMatrixRowRef::operator=(const float& right) { for (int64_t i = 0; i < (int64_t)m_row.size(); ++i) { m_row[i] = right; } return *this; } float& FloatMatrixRowRef::operator[](const int64_t& index) { CaretAssert(index > -1 && index < (int64_t)m_row.size());//instead of segfaulting, explicitly check in debug return m_row[index]; } FloatMatrixRowRef::FloatMatrixRowRef(FloatMatrixRowRef& right) : m_row(right.m_row) { } FloatMatrixRowRef& FloatMatrixRowRef::operator=(const ConstFloatMatrixRowRef& right) { if (&m_row == &(right.m_row)) {//just in case vector isn't smart enough to check self assignment return *this; } CaretAssert(m_row.size() == right.m_row.size()); m_row = right.m_row; return *this; } const float& ConstFloatMatrixRowRef::operator[](const int64_t& index) { CaretAssert(index > -1 && index < (int64_t)m_row.size());//instead of segfaulting, explicitly check in debug return m_row[index]; } ConstFloatMatrixRowRef::ConstFloatMatrixRowRef(const ConstFloatMatrixRowRef& right) : m_row(right.m_row) { } ConstFloatMatrixRowRef::ConstFloatMatrixRowRef(const vector& therow) : m_row(therow) { } workbench-1.1.1/src/Common/FloatMatrix.h000066400000000000000000000134751255417355300201420ustar00rootroot00000000000000 #ifndef __FLOAT_MATRIX_H__ #define __FLOAT_MATRIX_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "stdint.h" #include "Vector3D.h" namespace caret { class ConstFloatMatrixRowRef {//needed to do [][] on a const FloatMatrix const std::vector& m_row; ConstFloatMatrixRowRef();//disallow default construction, this contains a reference public: ConstFloatMatrixRowRef(const ConstFloatMatrixRowRef& right);//copy constructor ConstFloatMatrixRowRef(const std::vector& therow); const float& operator[](const int64_t& index);//access element friend class FloatMatrixRowRef;//so it can check if it points to the same row }; class FloatMatrixRowRef {//needed to ensure some joker doesn't call mymatrix[1].resize();, while still allowing mymatrix[1][2] = 5; and mymatrix[1] = mymatrix[2]; std::vector& m_row; FloatMatrixRowRef();//disallow default construction, this contains a reference public: FloatMatrixRowRef(FloatMatrixRowRef& right);//copy constructor FloatMatrixRowRef(std::vector& therow); FloatMatrixRowRef& operator=(const FloatMatrixRowRef& right);//NOTE: copy row contents! FloatMatrixRowRef& operator=(const ConstFloatMatrixRowRef& right);//NOTE: copy row contents! FloatMatrixRowRef& operator=(const float& right);//NOTE: set all row values! float& operator[](const int64_t& index);//access element }; ///class for using single precision matrices (insulates other code from the MatrixFunctions templated header) ///errors will result in a matrix of size 0x0, or an assertion failure if the underlying vector isn't rectangular class FloatMatrix { std::vector > m_matrix; bool checkDimensions() const;//put this inside asserts at the end of functions public: FloatMatrix() { };//to make the compiler happy ///construct from a simple vector > FloatMatrix(const std::vector >& matrixIn); ///construct uninitialized with given size FloatMatrix(const int64_t& rows, const int64_t& cols); FloatMatrixRowRef operator[](const int64_t& index);//allow direct indexing to rows ConstFloatMatrixRowRef operator[](const int64_t& index) const;//allow direct indexing to rows while const FloatMatrix& operator+=(const FloatMatrix& right);//add to FloatMatrix& operator-=(const FloatMatrix& right);//subtract from FloatMatrix& operator*=(const FloatMatrix& right);//multiply by FloatMatrix& operator+=(const float& right);//add scalar to FloatMatrix& operator-=(const float& right);//subtract scalar from FloatMatrix& operator*=(const float& right);//multiply by scalar FloatMatrix& operator/=(const float& right);//divide by scalar FloatMatrix operator+(const FloatMatrix& right) const;//add FloatMatrix operator-(const FloatMatrix& right) const;//subtract FloatMatrix operator-() const;//negate FloatMatrix operator*(const FloatMatrix& right) const;//multiply bool operator==(const FloatMatrix& right) const;//compare bool operator!=(const FloatMatrix& right) const;//anti-compare ///return the inverse FloatMatrix inverse() const; ///return the reduced row echelon form FloatMatrix reducedRowEchelon() const; ///return the transpose FloatMatrix transpose() const; ///resize the matrix - keeps contents within bounds unless destructive is true (destructive is faster) void resize(const int64_t rows, const int64_t cols, const bool destructive = false); ///return a matrix of zeros static FloatMatrix zeros(const int64_t rows, const int64_t cols); ///return a matrix of ones static FloatMatrix ones(const int64_t rows, const int64_t cols); ///return square identity matrix static FloatMatrix identity(const int64_t rows); ///get the range of values from first until one before afterLast, as a new matrix FloatMatrix getRange(const int64_t firstRow, const int64_t afterLastRow, const int64_t firstCol, const int64_t afterLastCol) const; ///return a matrix formed by concatenating right to the right of this FloatMatrix concatHoriz(const FloatMatrix& right) const; ///returns a matrix formed by concatenating bottom to the bottom of this FloatMatrix concatVert(const FloatMatrix& bottom) const; ///get the dimensions void getDimensions(int64_t& rows, int64_t& cols) const; ///get the matrix as a vector const std::vector >& getMatrix() const; ///separate 3x4 or 4x4 into Vector3Ds, throw on wrong dimensions void getAffineVectors(Vector3D& xvec, Vector3D& yvec, Vector3D& zvec, Vector3D& offset) const; ///get number of rows int64_t getNumberOfRows() { return (int64_t)m_matrix.size(); } ///get number of columns int64_t getNumberOfColumns() { if (m_matrix.size() == 0) return 0; return (int64_t)m_matrix[0].size(); } }; } #endif //__FLOAT_MATRIX_H__ workbench-1.1.1/src/Common/Histogram.cxx000066400000000000000000000256371255417355300202230ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "Histogram.h" #include "CaretAssert.h" #include using namespace caret; using namespace std; Histogram::Histogram(const int& numBuckets) { resize(numBuckets); reset(); } Histogram::Histogram(const float* data, const int64_t& dataCount) { CaretAssert(dataCount > 0); const int MIN_BUCKET_COUNT = 10; const int MAX_BUCKET_COUNT = 10000;//hits this max at 100 million int numBuckets = (int)sqrt((float)dataCount); if (numBuckets < MIN_BUCKET_COUNT) numBuckets = MIN_BUCKET_COUNT; if (numBuckets > MAX_BUCKET_COUNT) numBuckets = MAX_BUCKET_COUNT; resize(numBuckets); update(data, dataCount); } Histogram::Histogram(const int& numBuckets, const float* data, const int64_t& dataCount) { resize(numBuckets); update(data, dataCount); } void Histogram::resize(const int& buckets) { CaretAssert(buckets > 0); m_buckets.resize(buckets); m_cumulative.resize(buckets); m_display.resize(buckets); } void Histogram::reset() { m_posCount = 0; m_zeroCount = 0; m_negCount = 0; m_infCount = 0; m_negInfCount = 0; m_nanCount = 0; int numBuckets = (int)m_buckets.size(); for (int i = 0; i < numBuckets; ++i) { m_buckets[i] = 0; m_cumulative[i] = 0; m_display[i] = 0.0f; } } void Histogram::update(const int& numBuckets, const float* data, const int64_t& dataCount) { resize(numBuckets); update(data, dataCount); } void Histogram::update(const float* data, const int64_t& dataCount) { int numBuckets = (int)m_buckets.size(); reset(); bool first = true; for (int64_t i = 0; i < dataCount; ++i) {//count value classes if (data[i] != data[i]) { ++m_nanCount; continue;//skip NaNs } if (data[i] == 0.0f)//test exactly zero (negative zero also tests equal), in case someone wants stats on something with miniscule values (percent of surface area per node?) { ++m_zeroCount; } else { if (data[i] < 0.0f) { if (data[i] * 2.0f == data[i]) { ++m_negInfCount; continue;//skip neg infs } else { ++m_negCount; } } else { if (data[i] * 2.0f == data[i]) { ++m_infCount; continue;//skip infs } else { ++m_posCount; } } } if (first) { first = false; m_bucketMin = data[i]; m_bucketMax = data[i]; } else { if (data[i] > m_bucketMax) { m_bucketMax = data[i]; } else if (data[i] < m_bucketMin) {//skip testing for new minimum if we found a new maximum m_bucketMin = data[i]; } } } if (first) { m_bucketMin = m_bucketMax = 0.0f; return;//our arrays are already zeroed, so just return if no valid data } if (m_bucketMin == m_bucketMax) { int64_t totalValid = m_negCount + m_posCount + m_zeroCount; for (int i = 0; i < numBuckets - 1; ++i) { m_cumulative[i] = (i + 1) * totalValid / numBuckets;//so, its not particularly useful if our range is zero, but split them evenly among buckets just for kicks if (i == 0) { m_buckets[i] = m_cumulative[i]; } else { m_buckets[i] = m_cumulative[i] - m_cumulative[i - 1]; } }//display is already zeroed, so just return m_cumulative[numBuckets - 1] = totalValid;//make sure the last one has all of them if (numBuckets > 1) { m_buckets[numBuckets - 1] = m_cumulative[numBuckets - 1] - m_cumulative[numBuckets - 2]; } else { m_buckets[numBuckets - 1] = m_cumulative[numBuckets - 1]; } return; } float bucketsize = (m_bucketMax - m_bucketMin) / numBuckets; for (int64_t i = 0; i < dataCount; ++i) {//determine histogram if (data[i] != data[i]) continue;//exclude NaN if (data[i] < -1.0f && (data[i] * 2.0f == data[i])) continue;//exclude -inf if (data[i] > 1.0f && (data[i] * 2.0f == data[i])) continue;//exclude inf int bucket = (int)((data[i] - m_bucketMin) / bucketsize);//doesn't really matter whether small negative floats truncate to a 0 integer if (bucket < 0) bucket = 0;//because of this if (bucket >= numBuckets) bucket = numBuckets - 1; CaretAssertVectorIndex(m_buckets, bucket); ++m_buckets[bucket]; } computeCumulative(); for (int i = 0; i < numBuckets; ++i) {//compute display values by normalizing by bucket size m_display[i] = m_buckets[i] / bucketsize; } } void Histogram::update(const float* data, const int64_t& dataCount, float mostPositiveValueInclusive, float leastPositiveValueInclusive, float leastNegativeValueInclusive, float mostNegativeValueInclusive, const bool& includeZeroValues) { int numBuckets = (int)m_buckets.size(); reset(); if (mostNegativeValueInclusive > 0.0f) mostNegativeValueInclusive = 0.0f;//sanity check the inputs without asserting if (mostPositiveValueInclusive < 0.0f) mostPositiveValueInclusive = 0.0f; if (leastNegativeValueInclusive > 0.0f) leastNegativeValueInclusive = 0.0f; if (leastPositiveValueInclusive < 0.0f) leastPositiveValueInclusive = 0.0f; if ((mostPositiveValueInclusive >= leastPositiveValueInclusive && mostPositiveValueInclusive != 0.0f) || includeZeroValues) { m_bucketMax = mostPositiveValueInclusive; } else { m_bucketMax = leastNegativeValueInclusive; } if (mostNegativeValueInclusive != 0.0f || includeZeroValues) { m_bucketMin = mostNegativeValueInclusive; } else { m_bucketMin = leastPositiveValueInclusive; } float sanity = m_bucketMax + m_bucketMin; if (m_bucketMax <= m_bucketMin || sanity != sanity) {//bad input ranges, so collect counts, make a mock histogram if equal, and return (display values will be zeros) int64_t equalCount = 0; for (int64_t i = 0; i < dataCount; ++i) { if (data[i] != data[i]) { ++m_nanCount; continue; } if (data[i] < -1.0f && (data[i] * 2.0f == data[i])) { ++m_negInfCount; continue; } if (data[i] > 1.0f && (data[i] * 2.0f == data[i])) { ++m_infCount; continue; } if (data[i] == m_bucketMax) { ++equalCount; } } if (m_bucketMax == m_bucketMin) { if (m_bucketMax == 0.0f) { m_zeroCount = equalCount; } else { if (m_bucketMax < 0.0f) { m_negCount = equalCount; } else { m_posCount = equalCount; } } for (int i = 0; i < numBuckets - 1; ++i) { m_cumulative[i] = (i + 1) * equalCount / numBuckets;//so, its not particularly useful if our range is zero, but split them evenly among buckets just for kicks if (i == 0) { m_buckets[i] = m_cumulative[i]; } else { m_buckets[i] = m_cumulative[i] - m_cumulative[i - 1]; } }//display is already zeroed, so just return m_cumulative[numBuckets - 1] = equalCount;//make sure the last one has all of them if (numBuckets > 1) { m_buckets[numBuckets - 1] = m_cumulative[numBuckets - 1] - m_cumulative[numBuckets - 2]; } else { m_buckets[numBuckets - 1] = m_cumulative[numBuckets - 1]; } } return; } float bucketsize = (m_bucketMax - m_bucketMin) / numBuckets; for (int64_t i = 0; i < dataCount; ++i)//do the histogram {//count value classes if (data[i] != data[i]) { ++m_nanCount; continue;//skip NaNs } if (data[i] == 0.0f)//test exactly zero (negative zero also tests equal), in case someone wants stats on something with miniscule values (percent of surface area per node?) { if (!includeZeroValues) continue;//don't count what is excluded ++m_zeroCount; } else { if (data[i] < 0.0f) { if (data[i] * 2.0f == data[i]) { ++m_negInfCount; continue;//skip neg infs } else { if (data[i] > leastNegativeValueInclusive || data[i] < mostNegativeValueInclusive) continue;//exclude negatives outside range ++m_negCount; } } else { if (data[i] * 2.0f == data[i]) { ++m_infCount; continue;//skip infs } else { if (data[i] > mostPositiveValueInclusive || data[i] < leastPositiveValueInclusive) continue;//exclude negatives outside range ++m_posCount; } } } int bucket = (int)((data[i] - m_bucketMin) / bucketsize);//doesn't really matter whether small negative floats truncate to a 0 integer if (bucket < 0) bucket = 0;//because of this if (bucket >= numBuckets) bucket = numBuckets - 1; CaretAssertVectorIndex(m_buckets, bucket); ++m_buckets[bucket]; } computeCumulative(); for (int i = 0; i < numBuckets; ++i) {//compute display values by normalizing by bucket size m_display[i] = m_buckets[i] / bucketsize; } } void Histogram::computeCumulative() { int numBuckets = (int)m_buckets.size(); int64_t accum = 0; for (int i = 0; i < numBuckets; ++i) { accum += m_buckets[i]; m_cumulative[i] = accum; } } workbench-1.1.1/src/Common/Histogram.h000066400000000000000000000066451255417355300176460ustar00rootroot00000000000000#ifndef __HISTOGRAM_H__ #define __HISTOGRAM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "stdint.h" namespace caret { class Histogram { std::vector m_buckets, m_cumulative; std::vector m_display; float m_bucketMin, m_bucketMax; ///counts of each class of number int64_t m_posCount, m_zeroCount, m_negCount, m_infCount, m_negInfCount, m_nanCount; void resize(const int& buckets); void reset(); void computeCumulative(); public: Histogram(const int& numBuckets = 100); Histogram(const float* data, const int64_t& dataCount);//NOTE: automatically determines number of buckets by square root of dataCount, but with set minimum and maximum Histogram(const int& numBuckets, const float* data, const int64_t& dataCount); void update(const float* data, const int64_t& dataCount); void update(const int& numBuckets, const float* data, const int64_t& dataCount); void update(const float* data, const int64_t& dataCount, float mostPositiveValueInclusive, float leastPositiveValueInclusive, float leastNegativeValueInclusive, float mostNegativeValueInclusive, const bool& includeZeroValues); ///get raw counts (useful mathematically) const std::vector& getHistogramCounts() const { return m_buckets; } const std::vector& getHistogramCumulativeCounts() const { return m_cumulative; } ///get display values - counts divided by bucket widths - will be consistent on the same data regardless of number of buckets or const std::vector& getHistogramDisplay() const { return m_display; } int getNumberOfBuckets() const { return (int)m_buckets.size(); } void getCounts(int64_t& posCount, int64_t& zeroCount, int64_t& negCount, int64_t& infCount, int64_t& negInfCount, int64_t& nanCount) const { posCount = m_posCount; zeroCount = m_zeroCount; negCount = m_negCount; infCount = m_infCount; negInfCount = m_negInfCount; nanCount = m_nanCount; } ///returns the low edge of the low bucket, and the high edge of the high bucket void getRange(float& histMin, float& histMax) const { histMin = m_bucketMin; histMax = m_bucketMax; } }; } #endif //__HISTOGRAM_H__ workbench-1.1.1/src/Common/HtmlStringBuilder.cxx000066400000000000000000000121101255417355300216460ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "HtmlStringBuilder.h" using namespace caret; /** * \class caret::HtmlStringBuilder * \brief Assists with constructing HTML text. */ /** * Constructor.mp * */ HtmlStringBuilder::HtmlStringBuilder() : CaretObject() { this->clear(); } /** * Destructor */ HtmlStringBuilder::~HtmlStringBuilder() { } /** * Append plain text (same as addText()). * * @param text - Plain text to add. * */ void HtmlStringBuilder::append(const AString& text) { this->stringBuilder.append(text); } /** * Clear the text. * */ void HtmlStringBuilder::clear() { this->stringBuilder.reserve(4096); this->stringBuilder = ""; } /** * Add plain text. * * @param text - Plain text to add. * */ void HtmlStringBuilder::add(const AString& text) { this->stringBuilder.append(text); } /** * Add plain text followed by a line break. * @param text - Plain text to add. * */ void HtmlStringBuilder::addLine(const AString& text) { this->stringBuilder.append(text); this->addLineBreak(); } /** * Append an integer. * * @param num Integer that is to be appended. * */ void HtmlStringBuilder::add(const int32_t num) { this->stringBuilder.append(AString::number(num)); } /** * Append a float. * * @param num Float that is to be appended. * */ void HtmlStringBuilder::add(const float num) { this->stringBuilder.append(AString::number(num)); } /** * Add bold text. * * @param text - Text that is made bold. * */ void HtmlStringBuilder::addBold(const AString& text) { this->stringBuilder.append(""); this->stringBuilder.append(text); this->stringBuilder.append(""); } /** * Add bold float. * * @param num - Number that is made bold. * */ void HtmlStringBuilder::addBold(const float num) { this->stringBuilder.append(""); this->stringBuilder.append(AString::number(num)); this->stringBuilder.append(""); } /** * Add bold int. * * @param num - Number that is made bold. * */ void HtmlStringBuilder::addBold(const int32_t num) { this->stringBuilder.append(""); this->stringBuilder.append(AString::number(num)); this->stringBuilder.append(""); } /** * Add a hyperlink to the text. * @param urlText - URL that is target. * @param linkText - Text displayed as hyperlink. * */ void HtmlStringBuilder::addHyperlink( const AString& urlText, const AString& linkText) { this->stringBuilder.append("
stringBuilder.append("http://"); this->stringBuilder.append(urlText); this->stringBuilder.append("/"); this->stringBuilder.append(linkText); this->stringBuilder.append("\">"); this->stringBuilder.append(linkText); this->stringBuilder.append(""); } /** * Ends a paragraph. * */ void HtmlStringBuilder::addParagraph() { this->stringBuilder.append("

"); } /** * Add a line break. * */ void HtmlStringBuilder::addLineBreak() { this->stringBuilder.append("

"); } /** * Add a line breaks. * @param numLineBreaks - Number of line breaks to add. * */ void HtmlStringBuilder::addLineBreaks(const int32_t numLineBreaks) { for (int i = 0; i < numLineBreaks; i++) { this->addLineBreak(); } } /** * Add a space. * */ void HtmlStringBuilder::addSpace() { this->stringBuilder.append(" "); } /** * Add a spaces. * @param numSpaces - Number of spaces. * */ void HtmlStringBuilder::addSpaces(const int32_t numSpaces) { for (int i = 0; i < numSpaces; i++) { this->addSpace(); } } /** * Get the total text length (includes HTML markup). * @return Length of text. * */ int32_t HtmlStringBuilder::length() { return this->stringBuilder.length(); } /** * Convert to a string in HTML format WITHOUT leading and trailing * HTML and BODY tags. * * @return String containing text. * */ AString HtmlStringBuilder::toString() const { return this->stringBuilder; } /** * Convert to a string in HTML format WITH leading and trailing * HTML and BODY tags. * * @return String containing text. * */ AString HtmlStringBuilder::toStringWithHtmlBody() { AString sb; sb.reserve(this->stringBuilder.length() + 100); sb.append(""); sb.append(this->stringBuilder); sb.append(""); return sb; } workbench-1.1.1/src/Common/HtmlStringBuilder.h000066400000000000000000000044631255417355300213070ustar00rootroot00000000000000#ifndef __HTMLSTRINGBUILDER_H__ #define __HTMLSTRINGBUILDER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include class StringBuilder; namespace caret { class HtmlStringBuilder : public CaretObject { public: HtmlStringBuilder(); virtual ~HtmlStringBuilder(); private: HtmlStringBuilder(const HtmlStringBuilder& o); HtmlStringBuilder& operator=(const HtmlStringBuilder& o); public: void append(const AString& text); void clear(); void add(const AString& text); void addLine(const AString& text); void add(const int32_t num); void add(const float num); void addBold(const AString& text); void addBold(const float num); void addBold(const int32_t num); void addHyperlink( const AString& urlText, const AString& linkText); void addParagraph(); void addLineBreak(); void addLineBreaks(const int32_t numLineBreaks); void addSpace(); void addSpaces(const int32_t numSpaces); int32_t length(); AString toString() const; AString toStringWithHtmlBody(); private: AString stringBuilder; }; } // namespace #endif // __HTMLSTRINGBUILDER_H__ workbench-1.1.1/src/Common/ImageCaptureMethodEnum.cxx000066400000000000000000000252061255417355300226120ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __IMAGE_CAPTURE_METHOD_ENUM_DECLARE__ #include "ImageCaptureMethodEnum.h" #undef __IMAGE_CAPTURE_METHOD_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::ImageCaptureMethodEnum * \brief Enumerated type for image capture method used in QGLWidget * * Using this enumerated type in the GUI with an EnumComboBoxTemplate * * Header File (.h) * Forward declare the data type: * class EnumComboBoxTemplate; * * Declare the member: * EnumComboBoxTemplate* m_imageCaptureMethodEnumComboBox; * * Declare a slot that is called when user changes selection * private slots: * void imageCaptureMethodEnumComboBoxItemActivated(); * * Implementation File (.cxx) * Include the header files * #include "EnumComboBoxTemplate.h" * #include "ImageCaptureMethodEnum.h" * * Instatiate: * m_imageCaptureMethodEnumComboBox = new EnumComboBoxTemplate(this); * m_imageCaptureMethodEnumComboBox->setup(); * * Get notified when the user changes the selection: * QObject::connect(m_imageCaptureMethodEnumComboBox, SIGNAL(itemActivated()), * this, SLOT(imageCaptureMethodEnumComboBoxItemActivated())); * * Update the selection: * m_imageCaptureMethodEnumComboBox->setSelectedItem(NEW_VALUE); * * Read the selection: * const ImageCaptureMethodEnum::Enum VARIABLE = m_imageCaptureMethodEnumComboBox->getSelectedItem(); * */ /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ ImageCaptureMethodEnum::ImageCaptureMethodEnum(const Enum enumValue, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCodeCounter++; this->name = name; this->guiName = guiName; } /** * Destructor. */ ImageCaptureMethodEnum::~ImageCaptureMethodEnum() { } /** * Initialize the enumerated metadata. */ void ImageCaptureMethodEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(ImageCaptureMethodEnum(IMAGE_CAPTURE_WITH_RENDER_PIXMAP, "IMAGE_CAPTURE_WITH_RENDER_PIXMAP", "Render Pixmap")); enumData.push_back(ImageCaptureMethodEnum(IMAGE_CAPTURE_WITH_GRAB_FRAME_BUFFER, "IMAGE_CAPTURE_WITH_GRAB_FRAME_BUFFER", "Grab Frame Buffer")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const ImageCaptureMethodEnum* ImageCaptureMethodEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const ImageCaptureMethodEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString ImageCaptureMethodEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const ImageCaptureMethodEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ ImageCaptureMethodEnum::Enum ImageCaptureMethodEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = ImageCaptureMethodEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ImageCaptureMethodEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type ImageCaptureMethodEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString ImageCaptureMethodEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const ImageCaptureMethodEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ ImageCaptureMethodEnum::Enum ImageCaptureMethodEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = ImageCaptureMethodEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ImageCaptureMethodEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type ImageCaptureMethodEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t ImageCaptureMethodEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const ImageCaptureMethodEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ ImageCaptureMethodEnum::Enum ImageCaptureMethodEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = ImageCaptureMethodEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ImageCaptureMethodEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type ImageCaptureMethodEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void ImageCaptureMethodEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void ImageCaptureMethodEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(ImageCaptureMethodEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void ImageCaptureMethodEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(ImageCaptureMethodEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } workbench-1.1.1/src/Common/ImageCaptureMethodEnum.h000066400000000000000000000063401255417355300222350ustar00rootroot00000000000000#ifndef __IMAGE_CAPTURE_METHOD_ENUM_H__ #define __IMAGE_CAPTURE_METHOD_ENUM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class ImageCaptureMethodEnum { public: /** * Enumerated values. */ enum Enum { /** Capture the image with QGLWidget::renderPixmap() */ IMAGE_CAPTURE_WITH_RENDER_PIXMAP, /** Capture the image with QGLWidget::grabFrameBuffer() */ IMAGE_CAPTURE_WITH_GRAB_FRAME_BUFFER }; ~ImageCaptureMethodEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); private: ImageCaptureMethodEnum(const Enum enumValue, const AString& name, const AString& guiName); static const ImageCaptureMethodEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; #ifdef __IMAGE_CAPTURE_METHOD_ENUM_DECLARE__ std::vector ImageCaptureMethodEnum::enumData; bool ImageCaptureMethodEnum::initializedFlag = false; int32_t ImageCaptureMethodEnum::integerCodeCounter = 0; #endif // __IMAGE_CAPTURE_METHOD_ENUM_DECLARE__ } // namespace #endif //__IMAGE_CAPTURE_METHOD_ENUM_H__ workbench-1.1.1/src/Common/LogHandler.cxx000066400000000000000000000023541255417355300202740ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __LOG_HANDLER_DECLARE__ #include "LogHandler.h" #undef __LOG_HANDLER_DECLARE__ using namespace caret; /** * Constructor. */ LogHandler::LogHandler() : CaretObject() { } /** * Destructor. */ LogHandler::~LogHandler() { } /** * Get a description of this object's content. * @return String describing this object's content. */ AString LogHandler::toString() const { return "LogHandler"; } workbench-1.1.1/src/Common/LogHandler.h000066400000000000000000000037621255417355300177250ustar00rootroot00000000000000#ifndef __LOG_HANDLER__H_ #define __LOG_HANDLER__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" namespace caret { class LogRecord; /** * \brief Processes log record sent by a Logger. * * A Handler takes log record messages and * does something with them. This is an abstract * class and must be subclassed. * * This class emulates Java's java.util.logging.Handler. */ class LogHandler : public CaretObject { public: /// close the handler and free resources virtual void close() = 0; /// flush any buffered output virtual void flush() = 0; /// Publish a log record virtual void publish(const LogRecord& logRecord) = 0; virtual ~LogHandler(); protected: LogHandler(); private: LogHandler(const LogHandler&); LogHandler& operator=(const LogHandler&); public: virtual AString toString() const; private: }; #ifdef __LOG_HANDLER_DECLARE__ // #endif // __LOG_HANDLER_DECLARE__ } // namespace #endif //__LOG_HANDLER__H_ workbench-1.1.1/src/Common/LogHandlerStandardError.cxx000066400000000000000000000054331255417355300227700ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __LOG_HANDLER_STANDARD_ERROR_DECLARE__ #include "LogHandlerStandardError.h" #undef __LOG_HANDLER_STANDARD_ERROR_DECLARE__ #include "FileInformation.h" #include "LogRecord.h" using namespace caret; using namespace std; /** * Constructor. */ LogHandlerStandardError::LogHandlerStandardError() : LogHandler() { } /** * Destructor. */ LogHandlerStandardError::~LogHandlerStandardError() { } /** * Get a description of this object's content. * @return String describing this object's content. */ AString LogHandlerStandardError::toString() const { return "LogHandlerStandardError"; } /** * close the handler and free resources. */ void LogHandlerStandardError::close() { // nothing to close } /** * Flush any buffered output. */ void LogHandlerStandardError::flush() { std::cerr.flush(); } /** * Publish a log record. * * @param logRecord * Logging record that is sent to standard error. */ void LogHandlerStandardError::publish(const LogRecord& logRecord) { cerr << endl; cerr << LogLevelEnum::toHintedName(logRecord.getLevel()) << ": " << logRecord.getText() << endl; #ifndef NDEBUG if (logRecord.getMethodName().isEmpty() == false) {//in debug, also give method name and source location cerr << "Method: " << logRecord.getMethodName() << endl; } cerr << "Location: " << logRecord.getFilename() << ":" << AString::number(logRecord.getLineNumber()) << endl; #else if (LogLevelEnum::toIntegerCode(logRecord.getLevel()) >= LogLevelEnum::toIntegerCode(LogLevelEnum::SEVERE))//in release, give method and simplified source location only for severe or worse { if (logRecord.getMethodName().isEmpty() == false) { cerr << "Method: " << logRecord.getMethodName() << endl; } cerr << "Location: " << FileInformation(logRecord.getFilename()).getFileName() << ":" << AString::number(logRecord.getLineNumber()) << endl; } #endif cerr << endl; } workbench-1.1.1/src/Common/LogHandlerStandardError.h000066400000000000000000000035341255417355300224150ustar00rootroot00000000000000#ifndef __LOG_HANDLER_STANDARD_ERROR__H_ #define __LOG_HANDLER_STANDARD_ERROR__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "LogHandler.h" namespace caret { class LogRecord; /** * \brief * * */ class LogHandlerStandardError : public LogHandler { public: LogHandlerStandardError(); virtual ~LogHandlerStandardError(); virtual void close(); virtual void flush(); virtual void publish(const LogRecord& logRecord); private: LogHandlerStandardError(const LogHandlerStandardError&); LogHandlerStandardError& operator=(const LogHandlerStandardError&); public: virtual AString toString() const; private: }; #ifdef __LOG_HANDLER_STANDARD_ERROR_DECLARE__ // #endif // __LOG_HANDLER_STANDARD_ERROR_DECLARE__ } // namespace #endif //__LOG_HANDLER_STANDARD_ERROR__H_ workbench-1.1.1/src/Common/LogLevelEnum.cxx000066400000000000000000000242571255417355300206210ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __LOG_LEVEL_ENUM_DECLARE__ #include "LogLevelEnum.h" #undef __LOG_LEVEL_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * Constructor. * * @param enumValue * An enumerated value. * @param integerCode * Integer code for this enumerated value. * * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ LogLevelEnum::LogLevelEnum(const Enum enumValue, const int32_t integerCode, const AString& name, const AString& guiName, const AString& hintedName) { this->enumValue = enumValue; this->integerCode = integerCode; this->name = name; this->guiName = guiName; this->hintedName = hintedName; } /** * Destructor. */ LogLevelEnum::~LogLevelEnum() { } /** * Initialize the enumerated metadata. */ void LogLevelEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(LogLevelEnum(SEVERE, 800, "SEVERE", "Severe", "SEVERE")); enumData.push_back(LogLevelEnum(WARNING, 700, "WARNING", "Warning", "WARNING")); enumData.push_back(LogLevelEnum(INFO, 600, "INFO", "Information", "Info")); enumData.push_back(LogLevelEnum(CONFIG, 500, "CONFIG", "Configuration", "Config")); enumData.push_back(LogLevelEnum(FINE, 400, "FINE", "Fine (Tracing)", "Fine")); enumData.push_back(LogLevelEnum(FINER, 300, "FINER", "Finer (Detailed Tracing)", "Finer")); enumData.push_back(LogLevelEnum(FINEST, 200, "FINEST", "Finest (Very Detailed Tracing)", "Finest")); enumData.push_back(LogLevelEnum(ALL, 100, "ALL", "All", "ALL"));//shouldn't get used in messages - do we even need this? FINEST should show everything enumData.push_back(LogLevelEnum(OFF, 0, "OFF", "Off", "Off"));//also shouldn't get used in messages } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const LogLevelEnum* LogLevelEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const LogLevelEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString LogLevelEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const LogLevelEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ LogLevelEnum::Enum LogLevelEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = OFF; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const LogLevelEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + " failed to match enumerated value for type LogLevelEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString LogLevelEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const LogLevelEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ LogLevelEnum::Enum LogLevelEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = OFF; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const LogLevelEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + " failed to match enumerated value for type LogLevelEnum")); } return enumValue; } AString LogLevelEnum::toHintedName(LogLevelEnum::Enum enumValue) { if (initializedFlag == false) initialize(); const LogLevelEnum* enumInstance = findData(enumValue); CaretAssert(enumInstance != NULL); return enumInstance->hintedName; } LogLevelEnum::Enum LogLevelEnum::fromHintedName(const AString& hintedName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = OFF; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const LogLevelEnum& d = *iter; if (d.hintedName == hintedName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("hintedName " + hintedName + " failed to match enumerated value for type LogLevelEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t LogLevelEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const LogLevelEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ LogLevelEnum::Enum LogLevelEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = OFF; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const LogLevelEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type LogLevelEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void LogLevelEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } workbench-1.1.1/src/Common/LogLevelEnum.h000066400000000000000000000073021255417355300202360ustar00rootroot00000000000000#ifndef __LOG_LEVEL_ENUM__H_ #define __LOG_LEVEL_ENUM__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { /** * \brief Log level. * * The log level defines a standard logging levels that are used to * control logging output. The levels are ordered and enabling * logging at a given level enables logging at all higher levels. * This class emulates the Java class java.util.logging.Level. */ class LogLevelEnum { public: /** * Enumerated values. */ enum Enum { /** serious failure */ SEVERE, /** potential problem */ WARNING, /** informational messages */ INFO, /** configuration messages, versions of libraries etc. */ CONFIG, /** Detailed information */ FINE, /** More detailed information */ FINER, /** Very detailed information, all Events are logged at this level */ FINEST, /** Log all records */ ALL, /** */ OFF }; ~LogLevelEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static AString toHintedName(Enum enumValue); static Enum fromHintedName(const AString& hintedName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); private: LogLevelEnum(const Enum enumValue, const int32_t integerCode, const AString& name, const AString& guiName, const AString& hintedName); static const LogLevelEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; /** A name that emphasizes important levels with all-caps */ AString hintedName; }; #ifdef __LOG_LEVEL_ENUM_DECLARE__ std::vector LogLevelEnum::enumData; bool LogLevelEnum::initializedFlag = false; #endif // __LOG_LEVEL_ENUM_DECLARE__ } // namespace #endif //__LOG_LEVEL_ENUM__H_ workbench-1.1.1/src/Common/LogManager.cxx000066400000000000000000000076311255417355300202740ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __LOG_MANAGER_DECLARE__ #include "CaretLogger.h" #include "LogManager.h" #undef __LOG_MANAGER_DECLARE__ #include "CaretAssert.h" #include "Logger.h" #include "LogHandlerStandardError.h" using namespace caret; /** * Constructor. */ LogManager::LogManager() : CaretObject() { } /** * Destructor. */ LogManager::~LogManager() { /* * Delete all of the loggers. */ for (std::vector::iterator iter = this->loggers.begin(); iter != this->loggers.end(); iter++) { delete (*iter); } } /** * Add a named logger. This does nothing an returns false * if a logger with the same name is already registered. * The Logger factory methods call this method to register * newly created loggers. The LogManager will 'delete' * any registed loggers when the LogManager is deleted. * * @param logger * Logger that is added. * @return * true if logger was successfully registered, false if * a logger with the same name already exists. */ bool LogManager::addLogger(Logger* logger) { CaretAssert(logger); Logger* existingLogger = this->getLogger(logger->getName()); if (existingLogger != NULL) { return false; } this->loggers.push_back(logger); return true; } /** * Find a named logger. * * @param name * Name of the logger. * @return * Matching logger or NULL if none found. */ Logger* LogManager::getLogger(const AString& name) { /* * Find logger with matching name. */ for (std::vector::iterator iter = this->loggers.begin(); iter != this->loggers.end(); iter++) { Logger* logger = *iter; if (logger->getName() == name) { return logger; } } return NULL; } /** * Return the global LogManager object. * * @return The global LogManager object. */ LogManager* LogManager::getLogManager() { return LogManager::singletonLogManager; } /** * Get a description of this object's content. * @return String describing this object's content. */ AString LogManager::toString() const { return "LogManager"; } /** * Create the log manager. * This must be called one AND ONLY one time. */ void LogManager::createLogManager() { CaretAssertMessage((LogManager::singletonLogManager == NULL), "Log manager has already been created."); LogManager::singletonLogManager = new LogManager(); Logger* caretLoggerInstance = Logger::getLogger("CaretLogger"); caretLoggerInstance->setLevel(LogLevelEnum::CONFIG); //caretLoggerInstance->setLevel(LogLevelEnum::FINEST); caretLoggerInstance->addLogHandler(new LogHandlerStandardError()); CaretLogger::setLogger(caretLoggerInstance); } /** * Delete the log manager. * This may only be called one time after log manager is created. */ void LogManager::deleteLogManager() { CaretAssertMessage((LogManager::singletonLogManager != NULL), "Log manager does not exist, cannot delete it."); delete LogManager::singletonLogManager; LogManager::singletonLogManager = NULL; } workbench-1.1.1/src/Common/LogManager.h000066400000000000000000000042611255417355300177150ustar00rootroot00000000000000#ifndef __LOG_MANAGER__H_ #define __LOG_MANAGER__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" namespace caret { class Logger; /** * \brief The logging manager. * * There is one global LogManager that keeps track of all * Loggers. The LogManager must be initialized prior to * creating any Loggers. Whn the LogManager is deleted * all loggers are also deleted. * This class emulates Java's java.util.logging.LogManager. */ class LogManager : public CaretObject { public: static void createLogManager(); static void deleteLogManager(); bool addLogger(Logger* logger); Logger* getLogger(const AString& name); static LogManager* getLogManager(); private: LogManager(); virtual ~LogManager(); LogManager(const LogManager&); LogManager& operator=(const LogManager&); /** All loggers */ std::vector loggers; /** Global log manager */ static LogManager* singletonLogManager; public: virtual AString toString() const; private: }; #ifdef __LOG_MANAGER_DECLARE__ LogManager* LogManager::singletonLogManager = NULL; #endif // __LOG_MANAGER_DECLARE__ } // namespace #endif //__LOG_MANAGER__H_ workbench-1.1.1/src/Common/LogRecord.cxx000066400000000000000000000043621255417355300201360ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __LOG_RECORD_DECLARE__ #include "LogRecord.h" #undef __LOG_RECORD_DECLARE__ using namespace caret; /** * Constructor. * * @param level * Logging level for message. * @param methodName * Method that logged the message. * @param filename * Name of file that originated the message. * @param lineNumber * Line number of message. * @param text * Text description. */ LogRecord::LogRecord(const LogLevelEnum::Enum level, const AString& methodName, const AString& filename, const int32_t lineNumber, const AString& text) : CaretObject() { this->level = level; this->methodName = methodName; this->filename = filename; this->lineNumber = lineNumber; this->text = text; } /** * Destructor. */ LogRecord::~LogRecord() { } /** * Get a description of this log record. * @return String describing this log record. */ AString LogRecord::toString() const { AString s = "Level=" + LogLevelEnum::toName(this->level); if (this->methodName.isEmpty() == false) { s += " Method=" + this->methodName; } if (this->filename.isEmpty() == false) { s += " File=" + this->filename; } if (this->lineNumber >= 0) { s += " Line=" + AString::number(this->lineNumber); } if (this->text.isEmpty() == false) { s += " Text=" + this->text; } return s; } workbench-1.1.1/src/Common/LogRecord.h000066400000000000000000000050701255417355300175600ustar00rootroot00000000000000#ifndef __LOG_RECORD__H_ #define __LOG_RECORD__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "LogLevelEnum.h" namespace caret { /** * \brief Log record. * * A record for a single logging message * This class emulates the Java calss java.util.logging. */ class LogRecord : public CaretObject { public: LogRecord(const LogLevelEnum::Enum level, const AString& methodName, const AString& filename, const int32_t lineNumber, const AString& text); virtual ~LogRecord(); LogLevelEnum::Enum getLevel() const { return level; } /** * @return Text message. */ AString getText() const { return text; } /** * @return Method name that logged message. */ AString getMethodName() const { return methodName; } /** * @return Filename in which message was logged. */ AString getFilename() const { return filename; } /** * @return Line number at which message was logged. */ int32_t getLineNumber() const { return lineNumber; } private: LogRecord(const LogRecord&); LogRecord& operator=(const LogRecord&); public: virtual AString toString() const; private: LogLevelEnum::Enum level; AString text; AString methodName; AString filename; int32_t lineNumber; }; #ifdef __LOG_RECORD_DECLARE__ // #endif // __LOG_RECORD_DECLARE__ } // namespace #endif //__LOG_RECORD__H_ workbench-1.1.1/src/Common/Logger.cxx000066400000000000000000000201371255417355300174730ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __LOGGER_DECLARE__ #include "Logger.h" #undef __LOGGER_DECLARE__ #include "CaretAssert.h" #include "LogHandler.h" #include "LogManager.h" #include "LogRecord.h" using namespace caret; /** * Constructor. */ Logger::Logger(const AString& name) : CaretObject() { this->name = name; this->setLevel(LogLevelEnum::ALL); } /** * Destructor. */ Logger::~Logger() { for (std::vector::iterator iter = this->logHandlers.begin(); iter != this->logHandlers.end(); iter++) { LogHandler* lh = *iter; delete lh; } } /** * Find or create a logger with the specified name. * If a logger exists with the given name, it is returned. * Otherwise, a new Logger is created and returned. * Never delete the returned Logger as it will be * deleted when the LogManager is deleted. */ Logger* Logger::getLogger(const AString& name) { CaretAssertMessage((name.isEmpty() == false), "Logger name must not be empty string."); Logger* existingLogger = LogManager::getLogManager()->getLogger(name); if (existingLogger != NULL) { return existingLogger; } Logger* logger = new Logger(name); bool exists = LogManager::getLogManager()->addLogger(logger); CaretAssertMessage(exists, "Trying to add logger and logger with name exists."); return logger; } /** * Get a description of this object's content. * @return String describing this object's content. */ AString Logger::toString() const { return ("Logger " + this->name); } /** * Log a message. * * @param logLevel * Logging level for message. The levels OFF and ALL * are not permitted. * @param methodName * Name of method. * @param filename * Name of file that originated the message. * @param lineNumber * Line number of message. * @param text * Text description. * */ void Logger::log(const LogLevelEnum::Enum logLevel, const AString& methodName, const AString& filename, const int32_t lineNumber, const AString& text) { switch (logLevel) { case LogLevelEnum::OFF: case LogLevelEnum::ALL: Logger::log(LogLevelEnum::SEVERE, methodName, filename, lineNumber, "Cannot log record with level = OFF or ALL for: " + text); break; case LogLevelEnum::FINEST: if (this->finestLoggingEnabled == false) return; break; case LogLevelEnum::FINER: if (this->finerLoggingEnabled == false) return; break; case LogLevelEnum::FINE: if (this->fineLoggingEnabled == false) return; break; case LogLevelEnum::CONFIG: if (this->configLoggingEnabled == false) return; break; case LogLevelEnum::INFO: if (this->infoLoggingEnabled == false) return; break; case LogLevelEnum::WARNING: if (this->warningLoggingEnabled == false) return; break; case LogLevelEnum::SEVERE: if (this->severeLoggingEnabled == false) return; break; } const LogRecord logRecord(logLevel, methodName, filename, lineNumber, text); /* * Send to all of the handlers. */ for (std::vector::iterator iter = this->logHandlers.begin(); iter != this->logHandlers.end(); iter++) { LogHandler* lh = *iter; lh->publish(logRecord); } } /** * Log throwing of a CaretException derived class. * A log record with the message THROW at the level * FINER is logged. * @param methodName * Name of method. * @param filename * Name of file that originated the message. * @param lineNumber * Line number of message. * */ void Logger::throwingCaretException(const AString& methodName, const AString& filename, const int32_t lineNumber, CaretException& caretException) { Logger::log(LogLevelEnum::FINER, methodName, filename, lineNumber, "THROW " + AString(typeid(caretException).name()) + ": " + caretException.whatString()); } /** * Log a method entry. A log record with * the message ENTRY at the level FINER * is logged. * @param methodName * Name of method. * @param filename * Name of file that originated the message. * @param lineNumber * Line number of message. */ void Logger::entering(const AString& methodName, const AString& filename, const int32_t lineNumber) { Logger::log(LogLevelEnum::FINER, "", filename, lineNumber, "ENTRY: " + methodName); } /** * Log a method return. A log record with * the message RETURN at the level FINER * is logged. * @param methodName * Name of method. * @param filename * Name of file that originated the message. * @param lineNumber * Line number of message. */ void Logger::exiting(const AString& methodName, const AString& filename, const int32_t lineNumber) { Logger::log(LogLevelEnum::FINER, "", filename, lineNumber, "RETURN: " + methodName); } /** * Get the current logging level. * * @return Current logging level. */ LogLevelEnum::Enum Logger::getLevel() const { return this->level; } /** * Set the logging level. * * @param level * New level for logging. */ void Logger::setLevel(const LogLevelEnum::Enum level) { this->level = level; this->severeLoggingEnabled = false; this->warningLoggingEnabled = false; this->infoLoggingEnabled = false; this->configLoggingEnabled = false; this->fineLoggingEnabled = false; this->finerLoggingEnabled = false; this->finestLoggingEnabled = false; /* * Notice that levels are arranged from * from LOWEST to HIGHEST and that most * have not break statements. Thus * 'falling through' the 'case' statements * sets the higher levels of logging. */ switch (this->level) { case LogLevelEnum::OFF: break; case LogLevelEnum::ALL: case LogLevelEnum::FINEST: this->finestLoggingEnabled = true; case LogLevelEnum::FINER: this->finerLoggingEnabled = true; case LogLevelEnum::FINE: this->fineLoggingEnabled = true; case LogLevelEnum::CONFIG: this->configLoggingEnabled = true; case LogLevelEnum::INFO: this->infoLoggingEnabled = true; case LogLevelEnum::WARNING: this->warningLoggingEnabled = true; case LogLevelEnum::SEVERE: this->severeLoggingEnabled = true; break; } } /** * Add a log handler to a logger. This object * will take care deleting the handlers that * it uses. * * @param logHandler * Handler that is added. */ void Logger::addLogHandler(LogHandler* logHandler) { this->logHandlers.push_back(logHandler); } workbench-1.1.1/src/Common/Logger.h000066400000000000000000000104651255417355300171230ustar00rootroot00000000000000#ifndef __LOGGER_H__ #define __LOGGER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "CaretException.h" #include "LogLevelEnum.h" namespace caret { class LogHandler; /*! * \brief Logs messages. * * Logger logs messages that are sent to registered handlers. * Each logger has an associated level. Only messages with * a level equal to or 'above' the level are forwarded to * handlers. If the message is to be forwarded, a LogRecord * is created and then forwarded to the registered handlers. * This class emulates the Java class java.util.logging. */ class Logger : public CaretObject { public: virtual ~Logger(); static Logger* getLogger(const AString& name); void log(const LogLevelEnum::Enum logLevel, const AString& methodName, const AString& filename, const int32_t lineNumber, const AString& text); void entering(const AString& methodName, const AString& filename, const int32_t lineNumber); void exiting(const AString& methodName, const AString& filename, const int32_t lineNumber); void throwingCaretException(const AString& methodName, const AString& filename, const int32_t lineNumber, CaretException& caretException); LogLevelEnum::Enum getLevel() const; void setLevel(const LogLevelEnum::Enum level); void addLogHandler(LogHandler* logHandler); /** @return Is severe logging enabled? */ inline bool isSevere() const { return this->severeLoggingEnabled; } /** @return Is warning logging enabled? */ inline bool isWarning() const { return this->warningLoggingEnabled; } /** @return Is info logging enabled? */ inline bool isInfo() const { return this->infoLoggingEnabled; } /** @return Is config logging enabled? */ inline bool isConfig() const { return this->configLoggingEnabled; } /** @return Is fine logging enabled? */ inline bool isFine() const { return this->fineLoggingEnabled; } /** @return Is finer logging enabled? */ inline bool isFiner() const { return this->finerLoggingEnabled; } /** @return Is finest logging enabled? */ inline bool isFinest() const { return this->finestLoggingEnabled; } /** @return Name of this logger. */ AString getName() const { return this->name; } private: Logger(const AString& name); Logger(const Logger&); Logger& operator=(const Logger&); AString name; bool severeLoggingEnabled; bool warningLoggingEnabled; bool infoLoggingEnabled; bool configLoggingEnabled; bool fineLoggingEnabled; bool finerLoggingEnabled; bool finestLoggingEnabled; LogLevelEnum::Enum level; std::vector logHandlers; public: virtual AString toString() const; private: }; #ifdef __LOGGER_DECLARE__ #endif // __LOGGER_DECLARE__ } // namespace #endif //__LOGGER_H__ workbench-1.1.1/src/Common/MathFunctionEnum.cxx000066400000000000000000000172501255417355300215020ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __MATH_FUNCTION_ENUM_DECLARE__ #include "MathFunctionEnum.h" #undef __MATH_FUNCTION_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * Constructor. * * @param enumValue * An enumerated value. * @param integerCode * Integer code for this enumerated value. * * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ MathFunctionEnum::MathFunctionEnum(const Enum enumValue, const AString& name, const AString& explanation) { this->enumValue = enumValue; this->name = name; this->explanation = explanation; } /** * Destructor. */ MathFunctionEnum::~MathFunctionEnum() { } /** * Initialize the enumerated metadata. */ void MathFunctionEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; //enumData.push_back(MathFunctionEnum(INVALID, "INVALID"));//should this be in the data? I don't think it should, it is a placeholder for "no matching enum value" enumData.push_back(MathFunctionEnum(SIN, "sin", "1 argument, the sine of the argument (units are radians)")); enumData.push_back(MathFunctionEnum(COS, "cos", "1 argument, the cosine of the argument (units are radians)")); enumData.push_back(MathFunctionEnum(TAN, "tan", "1 argument, the tangent of the argument (units are radians)")); enumData.push_back(MathFunctionEnum(ASIN, "asin", "1 argument, the inverse of sine of the argument, in radians")); enumData.push_back(MathFunctionEnum(ACOS, "acos", "1 argument, the inverse of cosine of the argument, in radians")); enumData.push_back(MathFunctionEnum(ATAN, "atan", "1 argument, the inverse of tangent of the argument, in radians")); enumData.push_back(MathFunctionEnum(ATAN2, "atan2", "2 arguments, atan2(y, x) returns the inverse of tangent of (y/x), in radians, determining quadrant by the sign of both arguments")); enumData.push_back(MathFunctionEnum(SINH, "sinh", "1 argument, the hyperbolic sine of the argument")); enumData.push_back(MathFunctionEnum(COSH, "cosh", "1 argument, the hyperbolic cosine of the argument")); enumData.push_back(MathFunctionEnum(TANH, "tanh", "1 argument, the hyperboloc tangent of the argument")); enumData.push_back(MathFunctionEnum(ASINH, "asinh", "1 argument, the inverse hyperbolic sine of the argument")); enumData.push_back(MathFunctionEnum(ACOSH, "acosh", "1 argument, the inverse hyperbolic cosine of the argument")); enumData.push_back(MathFunctionEnum(ATANH, "atanh", "1 argument, the inverse hyperboloc tangent of the argument")); enumData.push_back(MathFunctionEnum(LN, "ln", "1 argument, the natural logarithm of the argument")); enumData.push_back(MathFunctionEnum(EXP, "exp", "1 argument, the constant e raised to the power of the argument")); enumData.push_back(MathFunctionEnum(LOG, "log", "1 argument, the base 10 logarithm of the argument")); enumData.push_back(MathFunctionEnum(SQRT, "sqrt", "1 argument, the square root of the argument")); enumData.push_back(MathFunctionEnum(ABS, "abs", "1 argument, the absolute value of the argument")); enumData.push_back(MathFunctionEnum(FLOOR, "floor", "1 argument, the largest integer not greater than the argument")); enumData.push_back(MathFunctionEnum(ROUND, "round", "1 argument, the nearest integer, with ties rounded away from zero")); enumData.push_back(MathFunctionEnum(CEIL, "ceil", "1 argument, the smallest integer not less than the argument")); enumData.push_back(MathFunctionEnum(MIN, "min", "2 arguments, min(x, y) returns y if (x > y), x otherwise")); enumData.push_back(MathFunctionEnum(MAX, "max", "2 arguments, max(x, y) returns y if (x < y), x otherwise")); enumData.push_back(MathFunctionEnum(MOD, "mod", "2 arguments, mod(x, y) = x - y * floor(x / y), or 0 if y == 0")); enumData.push_back(MathFunctionEnum(CLAMP, "clamp", "3 arguments, clamp(x, low, high) = min(max(x, low), high)")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const MathFunctionEnum* MathFunctionEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const MathFunctionEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString MathFunctionEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const MathFunctionEnum* enumInstance = findData(enumValue); if (enumInstance == NULL) return ""; return enumInstance->name; } /** * Get a string explaining the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString MathFunctionEnum::toExplanation(Enum enumValue) { if (initializedFlag == false) initialize(); const MathFunctionEnum* enumInstance = findData(enumValue); if (enumInstance == NULL) return ""; return enumInstance->explanation; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ MathFunctionEnum::Enum MathFunctionEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = INVALID; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const MathFunctionEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type MathFunctionEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values * except ALL. */ void MathFunctionEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } workbench-1.1.1/src/Common/MathFunctionEnum.h000066400000000000000000000056011255417355300211240ustar00rootroot00000000000000#ifndef __MATH_FUNCTION_ENUM__H_ #define __MATH_FUNCTION_ENUM__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { /** * \brief Enumerated type for a structure in a brain. * * Enumerated types for math functions. */ class MathFunctionEnum { public: /** * Enumerated values. */ enum Enum { INVALID, SIN, COS, TAN, ASIN, ACOS, ATAN, ATAN2, SINH, COSH, TANH, ASINH, ACOSH, ATANH, LN, EXP, LOG, SQRT, ABS, FLOOR, ROUND, CEIL, MIN, MAX, MOD, CLAMP }; ~MathFunctionEnum(); static AString toName(Enum enumValue); static AString toExplanation(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static void getAllEnums(std::vector& allEnums); private: MathFunctionEnum(const Enum enumValue, const AString& name, const AString& explanation); static const MathFunctionEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** The enumerated type value for an instance */ Enum enumValue; /** The name, a text string that is identical to the enumerated value */ AString name; /** An explanation of the function */ AString explanation; }; #ifdef __MATH_FUNCTION_ENUM_DECLARE__ std::vector MathFunctionEnum::enumData; bool MathFunctionEnum::initializedFlag = false; #endif // __MATH_FUNCTION_ENUM_DECLARE__ } // namespace #endif //__MATH_FUNCTION_ENUM__H_ workbench-1.1.1/src/Common/MathFunctions.cxx000066400000000000000000001537321255417355300210460ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ /*========================================================================= Program: Visualization Toolkit Module: $RCSfile: vtkBase64Utilities.h,v $ Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen All rights reserved. See Copyright.txt or http://www.kitware.com/Copyright.htm for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notice for more information. =========================================================================*/ #include #include "MathFunctions.h" #include "CaretAssert.h" using namespace caret; using namespace std; MathFunctions::MathFunctions() : CaretObject() { } /** * Destructor */ MathFunctions::~MathFunctions() { } /** * Calulate the number of combinations for choosing "k" elements * from a total of "n" elements. * * Formula: [n!/(k!*(n-k!)) * If k > (n-k): [n(n-1)(n-2)...(k+1)] / (n-k)! * If k < (n-k): [n(n-1)(n-2)...(n-k+1)] / k! * * @param n - total number of elements. * @param k - number of elements to choose. * @return - number of combinations. * */ int64_t MathFunctions::combinations( const int64_t n, const int64_t k) { int64_t denominator = 1; int64_t nmk = n - k; int64_t iStart = 1; if (k > nmk) { iStart = k + 1; denominator = MathFunctions::factorial(nmk); } else { iStart = nmk + 1; denominator = MathFunctions::factorial(k); } int64_t numerator = 1; for (int64_t i = iStart; i <= n; i++) { numerator *= i; } int64_t numCombosLong = numerator / denominator; int64_t numCombos = (int)numCombosLong; return numCombos; } /** * Calulate the number of permuations for choosing "k" elements * from a total of "n" elements. * * Formula: [n!/(n-m)!] = n(n-1)(n-2)...(n-m+1). * * @param n - total number of elements. * @param k - number of elements to choose. * @return - number of combinations. * */ int64_t MathFunctions::permutations( const int64_t n, const int64_t k) { int64_t iStart = n - k + 1; int numPerms = 1; for (int i = iStart; i <= n; i++) { numPerms *= i; } return numPerms; } /** * Calculate the factorial for a number. * * @param n - the number. * @return - its factiorial * */ int64_t MathFunctions::factorial(const int64_t n) { int64_t num = 1; for (int64_t i = 1; i <= n; i++) { num *= i; } return num; } /** * Compute a normal vector from three vertices and make it a unit vector. * * @param v1 the first vertex, an array of three floats. * @param v2 the first vertex, an array of three floats. * @param v3 the first vertex, an array of three floats. * @param normalVectorOut A three-dimensional array passed into which * the normal vector is loaded. * @return true if vector is valid (non-zero length). * */ bool MathFunctions::normalVector( const float v1[3], const float v2[3], const float v3[3], float normalVectorOut[3]) { /* * DOUBLE PRECISION is needed when points are a small or sliver triangle. */ double a0 = v3[0] - v2[0]; double a1 = v3[1] - v2[1]; double a2 = v3[2] - v2[2]; double b0 = v1[0] - v2[0]; double b1 = v1[1] - v2[1]; double b2 = v1[2] - v2[2]; double nv0 = (a1 * b2 - a2 * b1); double nv1 = (a2 * b0 - a0 * b2); double nv2 = (a0 * b1 - a1 * b0); double length = std::sqrt(nv0*nv0 + nv1*nv1 + nv2*nv2); bool valid = false; if (length != 0.0) { nv0 /= length; nv1 /= length; nv2 /= length; valid = true; } normalVectorOut[0] = (float)nv0; normalVectorOut[1] = (float)nv1; normalVectorOut[2] = (float)nv2; return valid; } /** * Compute a normal vector from three vertices and make it a unit vector. * * @param v1 the first vertex, an array of three floats. * @param v2 the first vertex, an array of three floats. * @param v3 the first vertex, an array of three floats. * @param normalVectorOut A three-dimensional array passed into which * the normal vector is loaded. * @return true if vector is valid (non-zero length). * */ bool MathFunctions::normalVector( const double v1[3], const double v2[3], const double v3[3], double normalVectorOut[3]) { double a0 = v3[0] - v2[0]; double a1 = v3[1] - v2[1]; double a2 = v3[2] - v2[2]; double b0 = v1[0] - v2[0]; double b1 = v1[1] - v2[1]; double b2 = v1[2] - v2[2]; double nv0 = (a1 * b2 - a2 * b1); double nv1 = (a2 * b0 - a0 * b2); double nv2 = (a0 * b1 - a1 * b0); double length = std::sqrt(nv0*nv0 + nv1*nv1 + nv2*nv2); bool valid = false; if (length != 0.0) { nv0 /= length; nv1 /= length; nv2 /= length; valid = true; } normalVectorOut[0] = nv0; normalVectorOut[1] = nv1; normalVectorOut[2] = nv2; return valid; } /** * Compute a normal vector from three vertices and but the returned * vector IS NOT a unit vector. * * @param v1 the first vertex, an array of three floats. * @param v2 the first vertex, an array of three floats. * @param v3 the first vertex, an array of three floats. * @return The normal vector, an array of three floats. * */ void MathFunctions::normalVectorDirection( const float v1[3], const float v2[3], const float v3[3], float directionOut[3]) { float a[] = { v3[0] - v2[0], v3[1] - v2[1], v3[2] - v2[2] }; float b[] = { v1[0] - v2[0], v1[1] - v2[1], v1[2] - v2[2] }; directionOut[0] = (a[1] * b[2] - a[2] * b[1]); directionOut[1] = (a[2] * b[0] - a[0] * b[2]); directionOut[2] = (a[0] * b[1] - a[1] * b[0]); } /** * Cross product of two 3D vectors. * @param v1 The first vector, an array of three floats. * @param v2 The first vector, an array of three floats. * @param resultOut Output containing the cross product. * */ void MathFunctions::crossProduct( const float v1[], const float v2[], float resultOut[]) { resultOut[0] = v1[1] * v2[2] - v1[2] * v2[1]; resultOut[1] = v1[2] * v2[0] - v1[0] * v2[2]; resultOut[2] = v1[0] * v2[1] - v1[1] * v2[0]; } /** * Cross product of two 3D vectors. * @param v1 The first vector, an array of three floats. * @param v2 The first vector, an array of three floats. * @param resultOut Output containing the cross product. * */ void MathFunctions::crossProduct( const double v1[], const double v2[], double resultOut[]) { resultOut[0] = v1[1] * v2[2] - v1[2] * v2[1]; resultOut[1] = v1[2] * v2[0] - v1[0] * v2[2]; resultOut[2] = v1[0] * v2[1] - v1[1] * v2[0]; } /** * Cross product of two 3D vectors with normalizing both the * input and output vectors. * * @param x1 The first vector, an array of three floats. * @param x2 The first vector, an array of three floats. * @return The cross product, an array of three floats. * */ void MathFunctions::normalizedCrossProduct( const float x1[], const float x2[], float resultOut[]) { float v1[3] = { x1[0], x1[1], x1[2] }; MathFunctions::normalizeVector(v1); float v2[3] = { x2[0], x2[1], x2[2] }; MathFunctions::normalizeVector(v2); MathFunctions::crossProduct(v1, v2, resultOut); MathFunctions::normalizeVector(resultOut); } /** * Normalize a 3D vector (make its length 1.0). * * @param vectorsAll Array containing the XYZ components * of the vectors. * @param offset Offset of the vector's X-component in the * vectorsAll array. * @return The length of the vector prior to normalization. * */ float MathFunctions::normalizeVector( float vectorsAll[], const int32_t offset) { float len = MathFunctions::vectorLength(vectorsAll, offset); if (len != 0.0) { vectorsAll[offset] /= len; vectorsAll[offset+1] /= len; vectorsAll[offset+2] /= len; } return len; } /** * Normalize a 3D vector (make its length 1.0). * * @param vectorInOut vector that is normalized. * @return The length of the vector prior to normalization. * */ float MathFunctions::normalizeVector(float vector[3]) { float len = vectorLength(vector); if (len != 0.0) { vector[0] /= len; vector[1] /= len; vector[2] /= len; } return len; } /** * Normalize a 3D vector (make its length 1.0). * * @param vectorInOut vector that is normalized. * @return The length of the vector prior to normalization. * */ double MathFunctions::normalizeVector(double vector[3]) { double len = vectorLength(vector); if (len != 0.0) { vector[0] /= len; vector[1] /= len; vector[2] /= len; } return len; } /** * Get length of vector. * * @param vector Vector whose length is computed. * * @return The length of the vector. * */ float MathFunctions::vectorLength(const float vector[3]) { float len = (float)std::sqrt(vector[0]*vector[0] + vector[1]*vector[1] + vector[2]*vector[2]); return len; } /** * Get length of vector. * * @param vectorsAll Array containing three-dimensional vectors. * @param offset Offset of vector's X-component in vectorsAll array. * * @return The length of the vector. * */ float MathFunctions::vectorLength( const float vectorsAll[], const int32_t offset) { float len = (float)std::sqrt(vectorsAll[offset]*vectorsAll[offset] + vectorsAll[offset+1]*vectorsAll[offset+1] + vectorsAll[offset+2]*vectorsAll[offset+2]); return len; } /** * Get length of vector. * * @param vector Vector whose length is computed. * * @return The length of the vector. * */ double MathFunctions::vectorLength(const double vector[3]) { double len = std::sqrt(vector[0]*vector[0] + vector[1]*vector[1] + vector[2]*vector[2]); return len; } /** * Get the squared distance between two 3D points. * * @param p1 Point 1 (3 element array) * @param p2 Point 2 (3 element array) * @return Distance squared between the two points. * */ float MathFunctions::distanceSquared3D( const float p1[3], const float p2[3]) { float dx = p1[0] - p2[0]; float dy = p1[1] - p2[1]; float dz = p1[2] - p2[2]; float distSQ = dx*dx + dy*dy + dz*dz; return distSQ; } /** * Get the squared distance between two 3D coordinates. * * @param xyzAll Array containing all of the XYZ coordinates. * @param offsetCoord1 Offset of the first coordinates X-coordinate. * @param offsetCoord2 Offset of the second coordinates X-coordinate. * @return Distance squared between the two coordinates. * */ float MathFunctions::distanceSquared3D( const float xyzAll[], const int32_t offsetCoord1, const int32_t offsetCoord2) { float dx = xyzAll[offsetCoord1] - xyzAll[offsetCoord2]; float dy = xyzAll[offsetCoord1+1] - xyzAll[offsetCoord2+1]; float dz = xyzAll[offsetCoord1+2] - xyzAll[offsetCoord2+2]; float distSQ = dx*dx + dy*dy + dz*dz; return distSQ; } /** * Get the distance between two 3D points. * * @param p1 Point 1 (3 element array) * @param p2 Point 2 (3 element array) * @return Distance between the two points. * */ float MathFunctions::distance3D( const float p1[3], const float p2[3]) { float dist = distanceSquared3D(p1, p2); if (dist != 0.0f) { dist = (float)std::sqrt(dist); } return dist; } /** * Get the squared distance between two 3D points. * * @param p1 Point 1 (3 element array) * @param p2 Point 2 (3 element array) * @return Distance squared between the two points. * */ double MathFunctions::distanceSquared3D( const double p1[3], const double p2[3]) { double dx = p1[0] - p2[0]; double dy = p1[1] - p2[1]; double dz = p1[2] - p2[2]; double distSQ = dx*dx + dy*dy + dz*dz; return distSQ; // double dist = distanceSquared3D(p1, p2); // if (dist != 0.0f) { // dist = std::sqrt(dist); // } // return dist; } /** * Get the distance between two 3D points. * * @param p1 Point 1 (3 element array) * @param p2 Point 2 (3 element array) * @return Distance between the two points. * */ double MathFunctions::distance3D( const double p1[3], const double p2[3]) { double dist = distanceSquared3D(p1, p2); if (dist != 0.0f) { dist = std::sqrt(dist); } return dist; } /** * subtract vectors (3d) result = v1 - v2. * @param v1 1st vector input * @param v2 2nd vector input * @return 3D vector containing result of subtraction. * */ void MathFunctions::subtractVectors( const float v1[3], const float v2[3], float resultOut[3]) { resultOut[0] = v1[0] - v2[0]; resultOut[1] = v1[1] - v2[1]; resultOut[2] = v1[2] - v2[2]; } void MathFunctions::addVectors(const float v1[3], const float v2[3], float resultOut[3]) { resultOut[0] = v1[0] + v2[0]; resultOut[1] = v1[1] + v2[1]; resultOut[2] = v1[2] + v2[2]; } /** * Create the unit vector for a vector that starts at startXYZ and * ends at endXYZ. * * @param startXYZ - Starting position of vector. * @param endXYZ - Ending position of vector. * @param Unit vector starting at startXYZ and pointing to endXYZ. * */ void MathFunctions::createUnitVector( const float startXYZ[3], const float endXYZ[3], float resultOut[3]) { resultOut[0] = endXYZ[0] - startXYZ[0]; resultOut[1] = endXYZ[1] - startXYZ[1]; resultOut[2] = endXYZ[2] - startXYZ[2]; MathFunctions::normalizeVector(resultOut); } /** * Create the unit vector for a vector that starts at startXYZ and * ends at endXYZ. * * @param startXYZ - Starting position of vector. * @param endXYZ - Ending position of vector. * @param Unit vector starting at startXYZ and pointing to endXYZ. * */ void MathFunctions::createUnitVector( const double startXYZ[3], const double endXYZ[3], double resultOut[3]) { resultOut[0] = endXYZ[0] - startXYZ[0]; resultOut[1] = endXYZ[1] - startXYZ[1]; resultOut[2] = endXYZ[2] - startXYZ[2]; MathFunctions::normalizeVector(resultOut); } /** * Dot produce of three dimensional vectors. * @param p1 vector 1 * @param p2 vector 2 * @return Dot product of the two vectors. * */ float MathFunctions::dotProduct( const float p1[3], const float p2[3]) { float dot = p1[0]*p2[0] + p1[1]*p2[1] + p1[2]*p2[2]; return dot; } /** * Dot produce of three dimensional vectors. * @param p1 vector 1 * @param p2 vector 2 * @return Dot product of the two vectors. * */ float MathFunctions::dotProduct( const double p1[3], const double p2[3]) { float dot = p1[0]*p2[0] + p1[1]*p2[1] + p1[2]*p2[2]; return dot; } /** * Calculate the area for a triangle. * @param v1 - XYZ coordinates for vertex 1 * @param v2 - XYZ coordinates for vertex 2 * @param v3 - XYZ coordinates for vertex 3 * * @return Area of triangle. * */ float MathFunctions::triangleArea( const float v1[3], const float v2[3], const float v3[3]) { /* * Using doubles for the intermediate calculations * produces results different from that if floats * were used in the "area" equation. I'm * assuming double is more accurate (JWH). */ double a = MathFunctions::distanceSquared3D(v1,v2); double b = MathFunctions::distanceSquared3D(v2,v3); double c = MathFunctions::distanceSquared3D(v3,v1); float area = (float)(0.25f* std::sqrt(std::abs(4.0*a*c - (a-b+c)*(a-b+c)))); return area; } /** * Calculate the area for a triangle (with doubles) * @param v1 - XYZ coordinates for vertex 1 * @param v2 - XYZ coordinates for vertex 2 * @param v3 - XYZ coordinates for vertex 3 * * @return Area of triangle. * */ float MathFunctions::triangleArea(const double v1[3], const double v2[3], const double v3[3]) { /* * Using doubles for the intermediate calculations * produces results different from that if floats * were used in the "area" equation. I'm * assuming double is more accurate (JWH). */ double a = MathFunctions::distanceSquared3D(v1,v2); double b = MathFunctions::distanceSquared3D(v2,v3); double c = MathFunctions::distanceSquared3D(v3,v1); float area = (float)(0.25f* std::sqrt(std::abs(4.0*a*c - (a-b+c)*(a-b+c)))); return area; } /** * Calculate the area of a triangle formed by 3 coordinates. * @param xyzAll One-dimensional array containing the XYZ coordinates. * @param offsetCoord1 Offset of node 1's X-coordinate which is * followed by the Y- and Z-coordinates. * @param offsetCoord2 Offset of node 2's X-coordinate which is * followed by the Y- and Z-coordinates. * @param offsetCoord3 Offset of node 3's X-coordinate which is * followed by the Y- and Z-coordinates. * @return Area of the triangle formed by the coordinates. * */ float MathFunctions::triangleArea( const float xyzAll[], const int32_t offsetCoord1, const int32_t offsetCoord2, const int32_t offsetCoord3) { /* * Using doubles for the intermediate calculations * produces results different from that if floats * were used in the "area" equation. I'm * assuming double is more accurate (JWH). */ double a = MathFunctions::distanceSquared3D(xyzAll, offsetCoord1, offsetCoord2); double b = MathFunctions::distanceSquared3D(xyzAll, offsetCoord2, offsetCoord3); double c = MathFunctions::distanceSquared3D(xyzAll, offsetCoord3, offsetCoord1); float area = (float)(0.25f* std::sqrt(std::abs(4.0*a*c - (a-b+c)*(a-b+c)))); return area; } /** * Compute the signed area of a triangle in 2D. * @param p1 - 1st coordinate of triangle * @param p2 - 2nd coordinate of triangle * @param p3 - 3rd coordinate of triangle * @return Signed area of triangle which is positive if the vertices * are in counter-clockwise orientation or negative if the vertices * are in clockwise orientation. * */ float MathFunctions::triangleAreaSigned2D( const float p1[2], const float p2[2], const float p3[2]) { float area = ( p1[0]*p2[1] + p2[0]*p3[1] + p3[0]*p1[1] - p1[1]*p2[0] - p2[1]*p3[0] - p3[1]*p1[0] ) * 0.5f; return area; } /** * Compute the signed area of a triangle in 3D. * @param referenceNormal - Normal vector. * @param p1 - 1st coordinate of triangle * @param p2 - 2nd coordinate of triangle * @param p3 - 3rd coordinate of triangle * @return Signed area of triangle which is positive if the vertices * are in counter-clockwise orientation or negative if the vertices * are in clockwise orientation. * */ float MathFunctions::triangleAreaSigned3D( const float referenceNormal[3], const float p1[3], const float p2[3], const float p3[3]) { // // Area of the triangle formed by the three points // float area = triangleArea(p1, p2, p3); // // Normal for the three points // float triangleNormal[3]; MathFunctions::normalVector(p1, p2, p3, triangleNormal); // // Dot Product is the cosine of the angle between the two normals. When this value is less // than zero, the absolute angle between the normals is greater than 90 degrees. // float dot = MathFunctions::dotProduct(referenceNormal, triangleNormal); if (dot < 0.0) { area = -area; } return area; } void MathFunctions::vtkLinearSolve3x3( const float A[3][3], const float x[3], float y[3]) { int index[3]; float B[3][3]; for (int i = 0; i < 3; i++) { B[i][0] = A[i][0]; B[i][1] = A[i][1]; B[i][2] = A[i][2]; y[i] = x[i]; } vtkLUFactor3x3(B,index); vtkLUSolve3x3(B,index,y); } void MathFunctions::vtkLUSolve3x3( const float A[3][3], const int32_t index[3], float x[3]) { float sum; // forward substitution sum = x[index[0]]; x[index[0]] = x[0]; x[0] = sum; sum = x[index[1]]; x[index[1]] = x[1]; x[1] = sum - A[1][0]*x[0]; sum = x[index[2]]; x[index[2]] = x[2]; x[2] = sum - A[2][0]*x[0] - A[2][1]*x[1]; // back substitution x[2] = x[2]*A[2][2]; x[1] = (x[1] - A[1][2]*x[2])*A[1][1]; x[0] = (x[0] - A[0][1]*x[1] - A[0][2]*x[2])*A[0][0]; } void MathFunctions::vtkLUFactor3x3( float A[3][3], int32_t index[3]) { int i,maxI; float tmp,largest; float scale[3]; // Loop over rows to get implicit scaling information for ( i = 0; i < 3; i++ ) { largest = std::abs(A[i][0]); if ((tmp = std::abs(A[i][1])) > largest) { largest = tmp; } if ((tmp = std::abs(A[i][2])) > largest) { largest = tmp; } scale[i] = (1.0f)/largest; } // Loop over all columns using Crout's method // first column largest = scale[0]*std::abs(A[0][0]); maxI = 0; if ((tmp = scale[1]*std::abs(A[1][0])) >= largest) { largest = tmp; maxI = 1; } if ((tmp = scale[2]*std::abs(A[2][0])) >= largest) { maxI = 2; } if (maxI != 0) { //vtkSwapVectors3(A[maxI],A[0]); float tmpSwap[3] = { A[maxI][0], A[maxI][1], A[maxI][2] }; A[maxI][0] = A[0][0]; A[maxI][1] = A[0][1]; A[maxI][2] = A[0][2]; A[0][0] = tmpSwap[0]; A[0][1] = tmpSwap[1]; A[0][2] = tmpSwap[2]; scale[maxI] = scale[0]; } index[0] = maxI; A[0][0] = (1.0f)/A[0][0]; A[1][0] *= A[0][0]; A[2][0] *= A[0][0]; // second column A[1][1] -= A[1][0]*A[0][1]; A[2][1] -= A[2][0]*A[0][1]; largest = scale[1]*std::abs(A[1][1]); maxI = 1; if ((tmp = scale[2]*std::abs(A[2][1])) >= largest) { maxI = 2; //vtkSwapVectors3(A[2],A[1]); float tmpSwap[3] = { A[2][0], A[2][1], A[2][2] }; A[2][0] = A[1][0]; A[2][1] = A[1][1]; A[2][2] = A[1][2]; A[1][0] = tmpSwap[0]; A[1][1] = tmpSwap[1]; A[1][2] = tmpSwap[2]; scale[2] = scale[1]; } index[1] = maxI; A[1][1] = (1.0f)/A[1][1]; A[2][1] *= A[1][1]; // third column A[1][2] -= A[1][0]*A[0][2]; A[2][2] -= A[2][0]*A[0][2] + A[2][1]*A[1][2]; //largest = scale[2]*std::abs(A[2][2]); index[2] = 2; A[2][2] = (1.0f)/A[2][2]; } /** * Determine if 2D line segments intersect. * Algorithm from http://mathworld.wolfram.com/Line-LineIntersection.html * * @param p1 Line 1 end point 1. * @param p2 Line 1 end point 2. * @param q1 Line 2 end point 1. * @param q2 Line 2 end point 2. * @param tolerance Tolerance around the vertices (essentially * lengthens lines by this quantity). Caret5 set this * parameter to 0.01. * @param intersectionOut Location of intersection. * @return true if the line segments intersect else false. * */ bool MathFunctions::lineIntersection2D( const float p1[2], const float p2[2], const float q1[2], const float q2[2], const float tolerance, float intersectionOut[2]) { double tol = tolerance; double x1 = p1[0]; double y1 = p1[1]; double x2 = p2[0]; double y2 = p2[1]; double x3 = q1[0]; double y3 = q1[1]; double x4 = q2[0]; double y4 = q2[1]; double denom = ((x1 - x2) * (y3 - y4)) - ((x3 - x4) * (y1 - y2)); if (denom != 0.0) { double a = (x1 * y2) - (x2 * y1); double c = (x3 * y4) - (x4 * y3); double x = ((a * (x3 - x4)) - (c * (x1 - x2))) / denom; double y = ((a * (y3 - y4)) - (c * (y1 - y2))) / denom; double pxMax = std::max(x1, x2) + tol; double pxMin = std::min(x1, x2) - tol; double pyMax = std::max(y1, y2) + tol; double pyMin = std::min(y1, y2) - tol; double qxMax = std::max(x3, x4) + tol; double qxMin = std::min(x3, x4) - tol; double qyMax = std::max(y3, y4) + tol; double qyMin = std::min(y3, y4) - tol; intersectionOut[0] = (float)x; intersectionOut[1] = (float)y; if ((x >= pxMin) && (x <= pxMax) && (x >= qxMin) && (x <= qxMax) && (y >= pyMin) && (y <= pyMax) && (y >= qyMin) && (y <= qyMax)) { return true; } } return false; } /** * Determine if a ray intersects a plane. * @param p1 - 1st point defining the plane * @param p2 - 2nd point defining the plane * @param p3 - 3rd point defining the plane * @param rayOrigin - origin of the ray * @param rayVector - vector defining the ray * @param intersectionXYZandDistance - An array of four that will contain * the XYZ or the intersection point and the distance from the plane. * @return true if the ray intersects the plane, else false. * */ bool MathFunctions::rayIntersectPlane( const float p1[3], const float p2[3], const float p3[3], const float rayOrigin[3], const float rayVector[3], float intersectionXYZandDistance[4]) { // Convert the ray into a unit vector // double ray[3] = { rayVector[0], rayVector[1], rayVector[2] }; MathFunctions::normalizeVector(ray); // // Normal of plane // float normal[3]; MathFunctions::normalVector(p1, p2, p3, normal); // // Compute the plane equation // double A = normal[0]; double B = normal[1]; double C = normal[2]; double D = -(A*p1[0] + B*p1[1] + C*p1[2]); // // Parametric coordinate of where ray intersects plane // double denom = A * ray[0] + B * ray[1] + C * ray[2]; if (denom != 0) { const double t = -(A * rayOrigin[0] + B * rayOrigin[1] + C * rayOrigin[2] + D) / denom; intersectionXYZandDistance[0] = (float)(rayOrigin[0] + ray[0] * t); intersectionXYZandDistance[1] = (float)(rayOrigin[1] + ray[1] * t); intersectionXYZandDistance[2] = (float)(rayOrigin[2] + ray[2] * t); intersectionXYZandDistance[3] = (float)t; return true; } return false; } /** * Project a point to a plane. * @param pt - the point to project. * @param origin - point in the plane. * @param normal - normal vector of plane. * @return The projected position of "pt" on the plane. * */ void MathFunctions::projectPoint( const float pt[3], const float origin[3], const float normal[3], float projectedPointOut[3]) { float xo[3] = { pt[0] - origin[0], pt[1] - origin[1], pt[2] - origin[2] }; float t = MathFunctions::dotProduct(normal, xo); projectedPointOut[0] = pt[0] - t * normal[0]; projectedPointOut[1] = pt[1] - t * normal[1]; projectedPointOut[2] = pt[2] - t * normal[2]; } /** * Get the signed distance from the plane to the "queryPoint". * A positive distance indicates "queryPoint" is above the plane * and a negative distance indicates "queryPoint" is below * the plane. * * @param planeNormal - plane's normal vector. * @param pointInPlane - point on the plane. * @param queryPoint - the query point for which distance to plane is sought. * * @return Distance from the plane to the query point. * */ float MathFunctions::signedDistanceFromPlane( const float planeNormal[3], const float pointInPlane[3], const float queryPoint[3]) { // Find out where query point projects on the plane // float queryPointProjectedOntoPlane[3]; MathFunctions::projectPoint(queryPoint, pointInPlane, planeNormal, queryPointProjectedOntoPlane); float dx = planeNormal[0] * (queryPoint[0] - queryPointProjectedOntoPlane[0]); float dy = planeNormal[1] * (queryPoint[1] - queryPointProjectedOntoPlane[1]); float dz = planeNormal[2] * (queryPoint[2] - queryPointProjectedOntoPlane[2]); float dist = dx + dy + dz; return dist; } /** * Limit the "value" to be inclusively between the minimum and maximum. * @param value - Value for testing. * @param minimumValue - Minimum inclusive value. * @param maximumValue - Maximum inclusive value. * @return Value limited inclusively to the minimum and maximum values. * */ int32_t MathFunctions::limitRange( const int32_t value, const int32_t minimumValue, const int32_t maximumValue) { if (value < minimumValue) { return minimumValue; } if (value > maximumValue) { return maximumValue; } return value; } /** * Limit the "value" to be inclusively between the minimum and maximum. * @param value - Value for testing. * @param minimumValue - Minimum inclusive value. * @param maximumValue - Maximum inclusive value. * @return Value limited inclusively to the minimum and maximum values. * */ float MathFunctions::limitRange( const float value, const float minimumValue, const float maximumValue) { if (value < minimumValue) { return minimumValue; } if (value > maximumValue) { return maximumValue; } return value; } /** * Limit the "value" to be inclusively between the minimum and maximum. * @param value - Value for testing. * @param minimumValue - Minimum inclusive value. * @param maximumValue - Maximum inclusive value. * @return Value limited inclusively to the minimum and maximum values. * */ double MathFunctions::limitRange( const double value, const double minimumValue, const double maximumValue) { if (value < minimumValue) { return minimumValue; } if (value > maximumValue) { return maximumValue; } return value; } /** * Find the distance from the point to the line defined by p1 and p2. * Formula is from * "http://mathworld.wolfram.com/Point-LineDistance3-Dimensional.html". * * @param p1 - First coordinate in line. * @param p2 - Second coordinate in line. * @param point - coordinate for which distance to line is sought. * @return Distance from point to the line (p1, p2). * */ float MathFunctions::distanceToLine3D( const float p1[3], const float p2[3], const float point[3]) { float dv2v1[3]; MathFunctions::subtractVectors(p2, p1, dv2v1); float dv1pt[3]; MathFunctions::subtractVectors(p1, point, dv1pt); float crossed[3]; MathFunctions::crossProduct(dv2v1, dv1pt, crossed); float numerator = MathFunctions::vectorLength(crossed); float denomenator = MathFunctions::vectorLength(dv2v1); float dist = numerator / denomenator; return dist; } /** * Determine if two arrays are equal, same number of elements and * corresponding elements equal. * * @param a - first array. * @param b - second array. * @return true if arrays are equal, else false. * */ bool MathFunctions::arraysEqual( const float a[], const float b[], const int numElements) { for (int i = 0; i < numElements; i++) { if (a[i] != b[i]) { return false; } } return true; } /** * Get the average of three coordinates. * @param c1 - coordinate 1 * @param c2 - coordinate 2 * @param c3 - coordinate 3 * @param outputAverage A three-dimensional array into * which the average of the three coordinates is * placed. * */ void MathFunctions::averageOfThreeCoordinates( const float c1[3], const float c2[3], const float c3[3], float outputAverage[3]) { outputAverage[0] = (c1[0] + c2[0] + c3[0]) / 3.0f; outputAverage[1] = (c1[1] + c2[1] + c3[1]) / 3.0f; outputAverage[2] = (c1[2] + c2[2] + c3[2]) / 3.0f; } /** * Calculate the average of 3 coordinates. * @param xyzAll One-dimensional array containing the XYZ coordinates. * @param offsetCoord1 Offset of node 1's X-coordinate which is * followed by the Y- and Z-coordinates. * @param offsetCoord2 Offset of node 2's X-coordinate which is * followed by the Y- and Z-coordinates. * @param offsetCoord3 Offset of node 3's X-coordinate which is * followed by the Y- and Z-coordinates. * @param outputAverage 3 dimensional array passed in, into which * the average is placed. * @param outputOffset Offset of average into outputAverage array. * */ void MathFunctions::averageOfThreeCoordinates( const float xyzAll[], const int32_t offsetCoord1, const int32_t offsetCoord2, const int32_t offsetCoord3, float outputAverage[], const int32_t outputOffset) { outputAverage[outputOffset] = (xyzAll[offsetCoord1] + xyzAll[offsetCoord2] + xyzAll[offsetCoord3]) / 3.0f; outputAverage[outputOffset+1] = (xyzAll[offsetCoord1+1] + xyzAll[offsetCoord2+1] + xyzAll[offsetCoord3+1]) / 3.0f; outputAverage[outputOffset+2] = (xyzAll[offsetCoord1+2] + xyzAll[offsetCoord2+2] + xyzAll[offsetCoord3+2]) / 3.0f; } /** * Angle formed by p1, p2, p3 (angle at p2). Returned angle is in radians. * This method uses Java Math.acos() and produces highly accurate results. * @param p1 - point. * @param p2 - point. * @param p3 - point. * @return Angle formed by points. * */ float MathFunctions::angle( const float p1[3], const float p2[3], const float p3[3]) { // // Vector from P2 to P1 // float v21[3] = { p1[0] - p2[0], p1[1] - p2[1], p1[2] - p2[2] }; // // Vector from P2 to P3 // float v23[3] = { p3[0] - p2[0], p3[1] - p2[1], p3[2] - p2[2] }; // // Normalize the vectors // float v21len = MathFunctions::normalizeVector(v21); float v23len = MathFunctions::normalizeVector(v23); float angleOut = 0.0f; if ((v21len > 0.0) && (v23len > 0.0)) { // // angle is inverse cosine of the dot product // and be sure to handle numerical errors. // float dot = MathFunctions::dotProduct(v21, v23); if (dot > 1.0f) dot = 1.0f; else if (dot < -1.0f) dot = -1.0f; angleOut = (float)std::acos(dot); } return angleOut; } /** * Signed angle for "jik". * @param pi - point. * @param pj - point. * @param pk - point. * @param n - normal * @return signed angle formed by the points. * */ float MathFunctions::signedAngle( const float pi[3], const float pj[3], const float pk[3], const float n[3]) { float x1 = pj[0] - pi[0]; float y1 = pj[1] - pi[1]; float z1 = pj[2] - pi[2]; float x2 = pk[0] - pi[0]; float y2 = pk[1] - pi[1]; float z2 = pk[2] - pi[2]; /* s = |(ji)||(ki)| sin(phi) by cross product */ float dx = y1*z2 - y2*z1; float dy = x2*z1 - x1*z2; float dz = x1*y2 - x2*y1; float t = (dx*n[0]) + (dy*n[1]) + (dz*n[2]); float s = (float)std::sqrt((dx*dx) + (dy*dy) + (dz*dz)); if (t < 0.0f) { s = -s; } /* c = |(ji)||(ki)| cos(phi) by inner product */ float c = x1*x2 + y1*y2 + z1*z2; float phi = (float)std::atan2(s,c); return phi; } /** * Determine if an integer is an odd number. * @param number Integer to test. * @return true if integer is odd, else false. * */ bool MathFunctions::isOddNumber(const int32_t number) { bool result = ((number & 1) != 0); return result; } /** * Determine if an integer is an odd number. * @param number Integer to test. * @return true if integer is odd, else false. * */ bool MathFunctions::isEvenNumber(const int32_t number) { bool result = ((number & 1) == 0); return result; } /** * Determine if two arrays are equal. * @param a1 First array. * @param a2 Second array. * @param tolerance Allowable difference in elements at same index. * @return true if arrays are of same length and corresponding * elements have a difference less than tolerance. * */ bool MathFunctions::compareArrays( const float a1[], const float a2[], const int numElements, const float tolerance) { for (int i = 0; i < numElements; i++) { float diff = a1[i] - a2[i]; if (diff < 0.0f) diff = -diff; if (diff > tolerance) { return false; } } return true; } /** * Clamp a value to the range minimum to maximum. * @param value Value for clamping. * @param minimum Minimum allowed value. * @param maximum Maximum allowed value. * @return Value clamped to minimum and maximum. * */ int32_t MathFunctions::clamp( const int32_t value, const int32_t minimum, const int32_t maximum) { return MathFunctions::limitRange(value, minimum, maximum); } /** * Clamp a value to the range minimum to maximum. * @param value Value for clamping. * @param minimum Minimum allowed value. * @param maximum Maximum allowed value. * @return Value clamped to minimum and maximum. * */ float MathFunctions::clamp( const float value, const float minimum, const float maximum) { return MathFunctions::limitRange(value, minimum, maximum); } /** * convert degrees to radians. * @param * degrees value converted to radians. * @return * the corresponding radians value. */ float MathFunctions::toRadians(float degrees) { float radians = degrees * (M_PI / 180.0f); return radians; } /** * convert radians to degrees. * @param * degrees value converted to degrees. * @return * the corresponding degrees value. */ float MathFunctions::toDegrees(float radians) { float degrees = radians * (180.0f / M_PI); return degrees; } /** * Distance SQUARED from (x1, y1) to (x2, y2) * @param X-coordinate of first point. * @param Y-coordinate of first point. * @param X-coordinate of second point. * @param Y-coordinate of second point. * @return Distance squared between the points. */ double MathFunctions::distanceSquared2D(const double x1, const double y1, const double x2, const double y2) { const double dx = x2 - x1; const double dy = y2 - y1; const double d = (dx*dx) + (dy*dy); return d; } uint32_t MathFunctions::gcd(uint32_t num1, uint32_t num2) { if (num1 == 0 || num2 == 0) {//catch zeros return 0;//gcd(0,x)=gcd(x,0)=0, seems less confusing than returning x } //modulus method for good worst-case asymptotic performance uint32_t temp; if (num2 > num1)//num1 kept as the larger number to simplify the code { temp = num1; num1 = num2; num2 = temp; } while (num2) {//maintain num2 as the smaller number temp = num1 % num2;//modulus to reduce the larger as much as possible, result will be smaller than num2 num1 = num2;//so, we need to swap them num2 = temp;//when result becomes zero, num1 is our gcd } return num1; } bool MathFunctions::isInf(const float number) { return (abs(number) > 1.0f && number * 2.0f == number); } bool MathFunctions::isNaN(const float number) { return (number != number); } bool MathFunctions::isNegInf(const float number) { return (number < -1.0f && number * 2.0f == number); } bool MathFunctions::isNumeric(const float number) { return (!isNaN(number) && !isInf(number)); } bool MathFunctions::isPosInf(const float number) { return (number > 1.0f && number * 2.0f == number); } void MathFunctions::quaternToMatrix(const float cijk[4], float matrix[3][3]) {//formula from http://en.wikipedia.org/wiki/Rotation_matrix#Quaternion double qlengthsqr = cijk[0] * cijk[0] + cijk[1] * cijk[1] + cijk[2] * cijk[2] + cijk[3] * cijk[3]; double mult = 0.0; if (qlengthsqr > 0.0f) { mult = 2.0f / qlengthsqr; } double ijkmult[4] = { cijk[1] * mult, cijk[2] * mult, cijk[3] * mult }; double wX = cijk[0] * ijkmult[0], wY = cijk[0] * ijkmult[1], wZ = cijk[0] * ijkmult[2]; double xX = cijk[1] * ijkmult[0], xY = cijk[1] * ijkmult[1], xZ = cijk[1] * ijkmult[2]; double yY = cijk[2] * ijkmult[1], yZ = cijk[2] * ijkmult[2]; double zZ = cijk[3] * ijkmult[2]; matrix[0][0] = 1.0 - (yY + zZ);//equals nifti1 formula because for unit quaternion, a*a + b*b + c*c + d*d = 1, and yY = 2 * c*c matrix[0][1] = xY - wZ; matrix[0][2] = xZ + wY; matrix[1][0] = xY + wZ; matrix[1][1] = 1.0 - (xX + zZ); matrix[1][2] = yZ - wX; matrix[2][0] = xZ - wY; matrix[2][1] = yZ + wX; matrix[2][2] = 1.0 - (xX + yY); } void MathFunctions::quaternToMatrix(const double cijk[4], double matrix[3][3]) {//formula from http://en.wikipedia.org/wiki/Rotation_matrix#Quaternion double qlengthsqr = cijk[0] * cijk[0] + cijk[1] * cijk[1] + cijk[2] * cijk[2] + cijk[3] * cijk[3]; double mult = 0.0; if (qlengthsqr > 0.0f) { mult = 2.0f / qlengthsqr; } double ijkmult[4] = { cijk[1] * mult, cijk[2] * mult, cijk[3] * mult }; double wX = cijk[0] * ijkmult[0], wY = cijk[0] * ijkmult[1], wZ = cijk[0] * ijkmult[2]; double xX = cijk[1] * ijkmult[0], xY = cijk[1] * ijkmult[1], xZ = cijk[1] * ijkmult[2]; double yY = cijk[2] * ijkmult[1], yZ = cijk[2] * ijkmult[2]; double zZ = cijk[3] * ijkmult[2]; matrix[0][0] = 1.0 - (yY + zZ);//equals nifti1 formula because for unit quaternion, a*a + b*b + c*c + d*d = 1, and yY = 2 * c*c matrix[0][1] = xY - wZ; matrix[0][2] = xZ + wY; matrix[1][0] = xY + wZ; matrix[1][1] = 1.0 - (xX + zZ); matrix[1][2] = yZ - wX; matrix[2][0] = xZ - wY; matrix[2][1] = yZ + wX; matrix[2][2] = 1.0 - (xX + yY); } bool MathFunctions::matrixToQuatern(const float matrix[3][3], float cijk[4]) {//formulas from http://en.wikipedia.org/wiki/Rotation_matrix#Quaternion const float toler = 0.0001f; float ivec[3] = { matrix[0][0], matrix[1][0], matrix[2][0] }; float jvec[3] = { matrix[0][1], matrix[1][1], matrix[2][1] }; float kvec[3] = { matrix[0][2], matrix[1][2], matrix[2][2] }; if (!(std::abs(1.0f - normalizeVector(ivec)) <= toler)) return false;//use the "not less than or equal to" trick to catch NaNs if (!(std::abs(1.0f - normalizeVector(jvec)) <= toler)) return false; if (!(std::abs(1.0f - normalizeVector(kvec)) <= toler)) return false; if (!(dotProduct(ivec, jvec) <= toler)) return false; if (!(dotProduct(ivec, kvec) <= toler)) return false; if (!(dotProduct(jvec, kvec) <= toler)) return false; float tempvec[3]; crossProduct(ivec, jvec, tempvec); if (!(dotProduct(tempvec, kvec) >= 0.9f)) return false;//i cross j must be k, otherwise it contains a flip int method = 0; double trace = matrix[0][0] + matrix[1][1] + matrix[2][2]; if (trace < 0.0) { method = 1; float tempf = matrix[0][0]; if (matrix[1][1] > tempf) { method = 2; tempf = matrix[1][1]; } if (matrix[2][2] > tempf) { method = 3; } } switch (method) { case 0: { double r = std::sqrt(1.0 + trace); double s = 0.5 / r; cijk[0] = 0.5 * r; cijk[1] = (matrix[2][1] - matrix[1][2]) * s; cijk[2] = (matrix[0][2] - matrix[2][0]) * s; cijk[3] = (matrix[1][0] - matrix[0][1]) * s; } break; case 1: { double r = std::sqrt(1.0 + matrix[0][0] - matrix[1][1] - matrix[2][2]); double s = 0.5 / r; cijk[0] = (matrix[2][1] - matrix[1][2]) * s; cijk[1] = 0.5 * r; cijk[2] = (matrix[0][1] + matrix[1][0]) * s; cijk[3] = (matrix[2][0] + matrix[0][2]) * s; } break; case 2: {//DISCLAIMER: these last two were worked out by pattern since they aren't on wikipedia double r = std::sqrt(1.0 - matrix[0][0] + matrix[1][1] - matrix[2][2]); double s = 0.5 / r; cijk[0] = (matrix[0][2] - matrix[2][0]) * s; cijk[1] = (matrix[0][1] + matrix[1][0]) * s; cijk[2] = 0.5 * r; cijk[3] = (matrix[1][2] + matrix[2][1]) * s; } break; case 3: { double r = std::sqrt(1.0 - matrix[0][0] - matrix[1][1] + matrix[2][2]); double s = 0.5 / r; cijk[0] = (matrix[1][0] - matrix[0][1]) * s; cijk[1] = (matrix[2][0] + matrix[0][2]) * s; cijk[2] = (matrix[1][2] + matrix[2][1]) * s; cijk[3] = 0.5 * r; } break; default: return false; } if (cijk[0] < 0.0f) { cijk[0] = -cijk[0]; cijk[1] = -cijk[1]; cijk[2] = -cijk[2]; cijk[3] = -cijk[3]; } return true; } bool MathFunctions::matrixToQuatern(const double matrix[3][3], double cijk[4]) {//formulas from http://en.wikipedia.org/wiki/Rotation_matrix#Quaternion const float toler = 0.0001f; float ivec[3] = { matrix[0][0], matrix[1][0], matrix[2][0] }; float jvec[3] = { matrix[0][1], matrix[1][1], matrix[2][1] }; float kvec[3] = { matrix[0][2], matrix[1][2], matrix[2][2] }; if (!(std::abs(1.0f - normalizeVector(ivec)) <= toler)) return false;//use the "not less than or equal to" trick to catch NaNs if (!(std::abs(1.0f - normalizeVector(jvec)) <= toler)) return false; if (!(std::abs(1.0f - normalizeVector(kvec)) <= toler)) return false; if (!(dotProduct(ivec, jvec) <= toler)) return false; if (!(dotProduct(ivec, kvec) <= toler)) return false; if (!(dotProduct(jvec, kvec) <= toler)) return false; float tempvec[3]; crossProduct(ivec, jvec, tempvec); if (!(dotProduct(tempvec, kvec) >= 0.9f)) return false;//i cross j must be k, otherwise it contains a flip int method = 0; double trace = matrix[0][0] + matrix[1][1] + matrix[2][2]; if (trace < 0.0) { method = 1; float tempf = matrix[0][0]; if (matrix[1][1] > tempf) { method = 2; tempf = matrix[1][1]; } if (matrix[2][2] > tempf) { method = 3; } } switch (method) { case 0: { double r = std::sqrt(1.0 + trace); double s = 0.5 / r; cijk[0] = 0.5 * r; cijk[1] = (matrix[2][1] - matrix[1][2]) * s; cijk[2] = (matrix[0][2] - matrix[2][0]) * s; cijk[3] = (matrix[1][0] - matrix[0][1]) * s; } break; case 1: { double r = std::sqrt(1.0 + matrix[0][0] - matrix[1][1] - matrix[2][2]); double s = 0.5 / r; cijk[0] = (matrix[2][1] - matrix[1][2]) * s; cijk[1] = 0.5 * r; cijk[2] = (matrix[0][1] + matrix[1][0]) * s; cijk[3] = (matrix[2][0] + matrix[0][2]) * s; } break; case 2: {//DISCLAIMER: these last two were worked out by pattern since they aren't on wikipedia double r = std::sqrt(1.0 - matrix[0][0] + matrix[1][1] - matrix[2][2]); double s = 0.5 / r; cijk[0] = (matrix[0][2] - matrix[2][0]) * s; cijk[1] = (matrix[0][1] + matrix[1][0]) * s; cijk[2] = 0.5 * r; cijk[3] = (matrix[1][2] + matrix[2][1]) * s; } break; case 3: { double r = std::sqrt(1.0 - matrix[0][0] - matrix[1][1] + matrix[2][2]); double s = 0.5 / r; cijk[0] = (matrix[1][0] - matrix[0][1]) * s; cijk[1] = (matrix[2][0] + matrix[0][2]) * s; cijk[2] = (matrix[1][2] + matrix[2][1]) * s; cijk[3] = 0.5 * r; } break; default: return false; } if (cijk[0] < 0.0f) { cijk[0] = -cijk[0]; cijk[1] = -cijk[1]; cijk[2] = -cijk[2]; cijk[3] = -cijk[3]; } return true; } /** * Return the remainder from the resulting division using the given values. * * This method is written to match the result produced by the remainder() * function that is part of C99 but not supported on all platforms. * * Code may appear verbose but it avoid functions calls to fabs(), ceil(), * and floor(). * * Note: X is the numerator. * Y is the denominator. * * The remainder() functions compute the value r such that r = x - n*y, * where n is the integer nearest the exact value of x/y. * * If there are two integers closest to x/y, n shall be the even one. * * @param numerator * The numerator. * @param denominator * The denominator. * @return * The remainder from numerator divided by denominator. */ double MathFunctions::remainder(const double numerator, const double denominator) { if (denominator == 0.0) { return 0.0; } const double quotient = numerator / denominator; /* * Integer value greater than or equal to the quotient * and its difference with the quotient (ceiling) */ const int64_t nearestIntegerOne = static_cast(quotient + 0.5); double diffOne = quotient - nearestIntegerOne; if (diffOne < 0.0) diffOne = -diffOne; /* * Integer value less than or equal to the quotient * and its difference with the quotient (floor) */ const int64_t nearestIntegerTwo = static_cast(quotient - 0.5); double diffTwo = quotient - nearestIntegerTwo; if (diffTwo < 0.0) diffTwo = -diffTwo; /* * Helps determine if the two integer value are the same * distance from the quotient (value will be very close * to zero). */ double diffOneTwo = diffOne - diffTwo; if (diffOneTwo < 0.0) diffOneTwo = -diffOneTwo; int64_t nearestInteger = 0; /* * If the two integer values are the same distance from zero */ if (diffOneTwo < 0.000001) { /* * Use the integer that is even. * Note that if an integer is even, first bit is zero. */ if ((nearestIntegerOne & 1) == 0) { nearestInteger = nearestIntegerOne; } else { nearestInteger = nearestIntegerTwo; } } else if (diffOne < diffTwo) { nearestInteger = nearestIntegerOne; } else { nearestInteger = nearestIntegerTwo; } const double remainderValue = numerator - nearestInteger * denominator; return remainderValue; } /** * Return the value rounded to the nearest integral (integer) value. * * @param value * Value that is rounded. * @return * Value rounded to nearest integral value. */ double MathFunctions::round(const double value) { if (value < 0.0) { return std::ceil(value - 0.5f); } return std::floor(value + 0.5f); } float MathFunctions::q_func(const float& x) {//when using c++11 or later, could use erfc instead of this approximation if (x == 0.0f) return 0.5f;//below approximation is NaN for 0! if (isInf(x)) { if (x > 0.0f) return 0;//inf return 1;//-inf } float ret; if (x < 0.0f) { ret = 1.0f - (1.0f - exp(1.4f * x)) * exp(-x * x / 2) / (x * -1.135f * sqrt(2 * 3.1415926f)); } else { ret = (1.0f - exp(-1.4f * x)) * exp(-x * x / 2) / (x * 1.135f * sqrt(2 * 3.1415926f)); } //formula from http://en.wikipedia.org/wiki/Q-function //references http://users.auth.gr/users/9/3/028239/public_html/pdf/Q_Approxim.pdf //which gives formula and constants for erfc, need to substitute and simplify //however, is wrong for negatives, so we substitute -x and subtract from 1 if (!isNumeric(ret)) { CaretAssert(abs(x) < 0.00001f);//should only be possible for very small inputs, so check before returning the answer for 0 return 0.5f; } return ret; } workbench-1.1.1/src/Common/MathFunctions.h000066400000000000000000000265641255417355300204750ustar00rootroot00000000000000#ifndef __MATHFUNCTIONS_H__ #define __MATHFUNCTIONS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ /*========================================================================= Program: Visualization Toolkit Module: $RCSfile: vtkBase64Utilities.h,v $ Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen All rights reserved. See Copyright.txt or http://www.kitware.com/Copyright.htm for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notice for more information. =========================================================================*/ #include "CaretObject.h" #include namespace caret { /** * Various mathematical functions. */ class MathFunctions : public CaretObject { private: MathFunctions(); public: virtual ~MathFunctions(); public: static int64_t combinations( const int64_t n, const int64_t k); static int64_t permutations( const int64_t n, const int64_t k); static int64_t factorial(const int64_t n); static bool normalVector( const float v1[3], const float v2[3], const float v3[3], float normalVectorOut[3]); static bool normalVector( const double v1[3], const double v2[3], const double v3[3], double normalVectorOut[3]); static void normalVectorDirection( const float v1[3], const float v2[3], const float v3[3], float directionOut[3]); static void crossProduct( const float v1[], const float v2[], float resultOut[]); static void crossProduct( const double v1[], const double v2[], double resultOut[]); static void normalizedCrossProduct( const float x1[], const float x2[], float resultOut[]); static float normalizeVector( float vectorsAll[], const int32_t offset); static float normalizeVector(float vectorInOut[3]); static double normalizeVector(double vectorInOut[3]); static float vectorLength(const float vector[3]); static float vectorLength( const float vectorsAll[], const int32_t offset); static double vectorLength(const double vector[3]); static float distanceSquared3D( const float p1[3], const float p2[3]); static float distanceSquared3D( const float xyzAll[], const int32_t offsetCoord1, const int32_t offsetCoord2); static float distance3D( const float p1[3], const float p2[3]); static double distanceSquared3D( const double p1[3], const double p2[3]); static double distance3D( const double p1[3], const double p2[3]); static double distanceSquared2D(const double x1, const double y1, const double x2, const double y2); static void subtractVectors( const float v1[3], const float v2[3], float resultOut[3]); static void addVectors( const float v1[3], const float v2[3], float resultOut[3]); static void createUnitVector( const float startXYZ[3], const float endXYZ[3], float unitVectorOut[3]); static void createUnitVector( const double startXYZ[3], const double endXYZ[3], double unitVectorOut[3]); static float dotProduct( const float p1[3], const float p2[3]); static float dotProduct( const double p1[3], const double p2[3]); static float triangleArea( const float v1[3], const float v2[3], const float v3[3]); static float triangleArea(const double v1[3], const double v2[3], const double v3[3]); static float triangleArea( const float xyzAll[], const int32_t offsetCoord1, const int32_t offsetCoord2, const int32_t offsetCoord3); static float triangleAreaSigned2D( const float p1[3], const float p2[3], const float p3[3]); static float triangleAreaSigned3D( const float referenceNormal[3], const float p1[3], const float p2[3], const float p3[3]); static void vtkLinearSolve3x3( const float A[3][3], const float x[], float y[]); static void vtkLUSolve3x3( const float A[3][3], const int32_t index[], float x[]); static void vtkLUFactor3x3( float A[3][3], int32_t index[]); static bool lineIntersection2D( const float p1[3], const float p2[3], const float q1[3], const float q2[3], const float tolerance, float intersectionOut[3]); static bool rayIntersectPlane( const float p1[3], const float p2[3], const float p3[3], const float rayOrigin[3], const float rayVector[3], float intersectionXYZandDistance[3]); static void projectPoint( const float pt[3], const float origin[3], const float normal[3], float projectedPointOut[3]); static float signedDistanceFromPlane( const float planeNormal[3], const float pointInPlane[3], const float queryPoint[3]); static int32_t limitRange( const int32_t value, const int32_t minimumValue, const int32_t maximumValue); static float limitRange( const float value, const float minimumValue, const float maximumValue); static double limitRange( const double value, const double minimumValue, const double maximumValue); static float distanceToLine3D( const float p1[3], const float p2[3], const float point[3]); static bool arraysEqual( const float a[], const float b[], const int32_t numElements); static void averageOfThreeCoordinates( const float c1[3], const float c2[3], const float c3[3], float outputAverage[3]); static void averageOfThreeCoordinates( const float xyzAll[], const int32_t offsetCoord1, const int32_t offsetCoord2, const int32_t offsetCoord3, float outputAverage[], const int32_t outputOffset); static float angle( const float p1[3], const float p2[3], const float p3[3]); static float signedAngle( const float pi[3], const float pj[3], const float pk[3], const float n[3]); static bool isOddNumber(const int32_t number); static bool isEvenNumber(const int32_t number); static bool isNaN(const float number); static bool isPosInf(const float number); static bool isNegInf(const float number); ///true if either inf or -inf static bool isInf(const float number); ///true only if not NaN, inf, or -inf static bool isNumeric(const float number); static bool compareArrays( const float a1[], const float a2[], const int32_t numElemets, const float tolerance); static int32_t clamp( const int32_t value, const int32_t minimum, const int32_t maximum); static float clamp( const float value, const float minimum, const float maximum); static float toRadians(float angle); static float toDegrees(float radians); ///greatest common divisor static uint32_t gcd(uint32_t num1, uint32_t num2); /** * Is the value very, very close to zero? * @param value * Value to test. * @return true if approximately zero, else false. */ static inline bool isZero(const float value) { if (value > 0.00001) return false; if (value < -0.00001) return false; return true; } ///convert quaternion to rotation matrix static void quaternToMatrix(const float cijk[4], float matrix[3][3]); ///convert quaternion to rotation matrix static void quaternToMatrix(const double cijk[4], double matrix[3][3]); ///try to convert 3x3 matrix to quaternion (return false if not a rotation matrix) static bool matrixToQuatern(const float matrix[3][3], float cijk[4]); ///try to convert 3x3 matrix to quaternion (return false if not a rotation matrix) static bool matrixToQuatern(const double matrix[3][3], double cijk[4]); static double remainder(const double numerator, const double denominator); static double round(const double value); ///one minus cdf of standard normal distribution static float q_func(const float& x); }; } // namespace #endif // __MATHFUNCTIONS_H__ workbench-1.1.1/src/Common/MatrixFunctions.h000066400000000000000000000571401255417355300210420ustar00rootroot00000000000000#ifndef __MATRIX_UTILITIES_H__ #define __MATRIX_UTILITIES_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "stdint.h" using namespace std; //because I don't want to type std:: every other line //NOTICE: this is not intended to be used outisde of FloatMatrix.cxx and DoubleMatrix.cxx, use at your own risk //NOTICE: this is NOT meant to be as error friendly as matlab, it will check some things, error condition is a 0x0 matrix result //if a matrix has a row shorter than the first row, expect a segfault. Calling checkDim will look for this, but it is a relatively slow operation to do on every input, so it is not used internally. namespace caret { class MatrixFunctions { typedef int64_t msize_t;//NOTE: must be signed due to using -1 as a sentinel public: /// /// matrix multiplication /// template static void multiply(const vector > &left, const vector > &right, vector > &result); /// /// scalar multiplication /// template static void multiply(const vector > &left, const T2 right, vector > &result); /// /// reduced row echelon form /// template static void rref(vector > &inout); /// /// matrix inversion - wrapper to rref for now /// template static void inverse(const vector > &in, vector > &result); /// /// matrix addition - for simple code /// template static void add(const vector > &left, const vector > &right, vector > &result); /// /// scalar addition - for simple code /// template static void add(const vector > &left, const T2 right, vector > &result); /// /// matrix subtraction - for simple code /// template static void subtract(const vector > &left, const vector > &right, vector > &result); /// /// transpose - for simple code /// template static void transpose(const vector > &in, vector > &result); /// /// debugging - verify matrix is rectangular and show its dimensions - returns true if rectangular /// template static bool checkDim(const vector > &in); /// /// allocate a matrix, don't initialize /// template static void resize(const msize_t rows, const msize_t columns, vector > &result, bool destructive = false); /// /// allocate a matrix of specified size /// template static void zeros(const msize_t rows, const msize_t columns, vector > &result); /// /// allocate a matrix of specified size /// template static void ones(const msize_t rows, const msize_t columns, vector > &result); /// /// make an identity matrix /// template static void identity(const msize_t size, vector > &result); /// /// horizontally concatenate matrices /// template static void horizCat(const vector > &left, const vector > &right, vector > &result); /// /// vertically concatenate matrices /// template static void vertCat(const vector > &top, const vector > &bottom, vector > &result); /// /// grab a piece of a matrix /// template static void getChunk(const msize_t firstrow, const msize_t lastrow, const msize_t firstcol, const msize_t lastcol, const vector > &in, vector > &result); private: /// /// reduced row echelon form that is faster on larger matrices, is called by rref() if the matrix is big enough /// template static void rref_big(vector > &inout); }; template void MatrixFunctions::multiply(const vector >& left, const vector >& right, vector >& result) {//the stupid multiply O(n^3) - the O(n^2.78) version might not be that hard to implement with the other functions here, but not as stable msize_t leftrows = (msize_t)left.size(), rightrows = (msize_t)right.size(), leftcols, rightcols; vector > tempstorage, *tresult = &result;//pointer because you can't change a reference bool copyout = false; if (&left == &result || &right == &result) { copyout = true; tresult = &tempstorage; } if (leftrows && rightrows) { leftcols = (msize_t)left[0].size(); rightcols = (msize_t)right[0].size(); if (leftcols && rightcols && (rightrows == leftcols)) { resize(leftrows, rightcols, (*tresult), true);//could use zeros(), but common index last lets us zero at the same time msize_t i, j, k; for (i = 0; i < leftrows; ++i) { for (j = 0; j < rightcols; ++j) { A accum = 0; for (k = 0; k < leftcols; ++k) { accum += left[i][k] * right[k][j]; } (*tresult)[i][j] = accum; } } } else { result.resize(0); return; } } else { result.resize(0); return; } if (copyout) { result = tempstorage; } } template void MatrixFunctions::multiply(const vector > &left, const T2 right, vector > &result) { msize_t leftrows = (msize_t)left.size(), leftcols; bool doresize = true; if (&left == &result) { doresize = false;//don't resize if an input is an output } if (leftrows) { leftcols = (msize_t)left[0].size(); if (leftcols) { if (doresize) resize(leftrows, leftcols, result, true); msize_t i, j; for (i = 0; i < leftrows; ++i) { for (j = 0; j < leftcols; ++j) { result[i][j] = left[i][j] * right; } } } else { result.resize(0); return; } } else { result.resize(0); return; } } template void MatrixFunctions::rref_big(vector > &inout) { msize_t rows = (msize_t)inout.size(), cols; if (rows > 0) { cols = (msize_t)inout[0].size(); if (cols > 0) { vector pivots(rows, -1), missingPivots; msize_t i, j, k, myrow = 0; msize_t pivotrow; T tempval; for (i = 0; i < cols; ++i) { if (myrow >= rows) break;//no pivots left tempval = 0; pivotrow = -1; for (j = myrow; j < rows; ++j) {//only search below for new pivot if (abs(inout[j][i]) > tempval) { pivotrow = (msize_t)j; tempval = abs(inout[j][i]); } } if (pivotrow == -1) {//naively expect linearly dependence to show as an exact zero missingPivots.push_back(i);//record the missing pivot continue;//move to the next column } inout[pivotrow].swap(inout[myrow]);//STL swap via pointers for constant time row swap pivots[myrow] = i;//save the pivot location for back substitution tempval = inout[myrow][i]; inout[myrow][i] = (T)1; for (j = i + 1; j < cols; ++j) { inout[myrow][j] /= tempval;//divide row by pivot } for (j = myrow + 1; j < rows; ++j) {//zero ONLY below pivot for now tempval = inout[j][i]; inout[j][i] = (T)0; for (k = i + 1; k < cols; ++k) { inout[j][k] -= tempval * inout[myrow][k]; } } ++myrow;//increment row on successful pivot } msize_t numMissing = (msize_t)missingPivots.size(); if (myrow > 1)//if there is only 1 pivot, there is no back substitution to do { msize_t lastPivotCol = pivots[myrow - 1]; for (i = myrow - 1; i > 0; --i)//loop through pivots, can't zero above the top pivot so exclude it { msize_t pivotCol = pivots[i]; for (j = i - 1; j >= 0; --j)//loop through rows above pivot { tempval = inout[j][pivotCol]; inout[j][pivotCol] = (T)0;//flat zero the entry above the pivot for (k = numMissing - 1; k >= 0; --k)//back substitute within pivot range where pivots are missing { msize_t missingCol = missingPivots[k]; if (missingCol <= pivotCol) break;//equals will never trip, but whatever inout[j][missingCol] -= tempval * inout[i][missingCol]; } for (k = lastPivotCol + 1; k < cols; ++k)//loop through elements that are outside the pivot area { inout[j][k] -= tempval * inout[i][k]; } } } } } else { inout.resize(0); return; } } else { inout.resize(0); return; } } template void MatrixFunctions::rref(vector > &inout) { msize_t rows = (msize_t)inout.size(), cols; if (rows) { cols = (msize_t)inout[0].size(); if (cols) { if (rows > 7 || cols > 7)//when the matrix has this many rows/columns, it is faster to allocate storage for tracking pivots, and back substitute { rref_big(inout); return; } msize_t i, j, k, myrow = 0; msize_t pivotrow; T tempval; for (i = 0; i < cols; ++i) { if (myrow >= rows) break;//no pivots left tempval = 0; pivotrow = -1; for (j = myrow; j < rows; ++j) {//only search below for new pivot if (abs(inout[j][i]) > tempval) { pivotrow = (msize_t)j; tempval = abs(inout[j][i]); } } if (pivotrow == -1)//it may be a good idea to include a "very small value" check here, but it could mess up if used on a matrix with all values very small {//naively expect linearly dependence to show as an exact zero continue;//move to the next column } inout[pivotrow].swap(inout[myrow]);//STL swap via pointers for constant time row swap tempval = inout[myrow][i]; inout[myrow][i] = 1; for (j = i + 1; j < cols; ++j) { inout[myrow][j] /= tempval;//divide row by pivot } for (j = 0; j < myrow; ++j) {//zero above pivot tempval = inout[j][i]; inout[j][i] = 0; for (k = i + 1; k < cols; ++k) { inout[j][k] -= tempval * inout[myrow][k]; } } for (j = myrow + 1; j < rows; ++j) {//zero below pivot tempval = inout[j][i]; inout[j][i] = 0; for (k = i + 1; k < cols; ++k) { inout[j][k] -= tempval * inout[myrow][k]; } } ++myrow;//increment row on successful pivot } } else { inout.resize(0); return; } } else { inout.resize(0); return; } } template void MatrixFunctions::inverse(const vector > &in, vector > &result) {//rref implementation, there are faster (more complicated) ways - if it isn't invertible, it will hand back something strange msize_t inrows = (msize_t)in.size(), incols; if (inrows) { incols = (msize_t)in[0].size(); if (incols == inrows) { vector > inter, inter2; identity(incols, inter2); horizCat(in, inter2, inter); rref(inter); getChunk(0, inrows, incols, incols * 2, inter, result);//already using a local variable, doesn't need to check for reference duplicity } else { result.resize(0); return; } } else { result.resize(0); return; } } template void MatrixFunctions::add(const vector >& left, const vector >& right, vector >& result) { msize_t inrows = (msize_t)left.size(), incols; bool doresize = true; if (&left == &result || &right == &result) { doresize = false;//don't resize if an input is an output - this is ok for addition, don't need a copy } if (inrows) { incols = (msize_t)left[0].size(); if (inrows == (msize_t)right.size() && incols == (msize_t)right[0].size())//short circuit evaluation will protect against segfault { if (doresize) resize(inrows, incols, result, true); for (msize_t i = 0; i < inrows; ++i) { for (msize_t j = 0; j < incols; ++j) { result[i][j] = left[i][j] + right[i][j]; } } } else { result.resize(0);//use empty matrix for error condition return; } } else { result.resize(0); return; } } template void MatrixFunctions::add(const vector >& left, const T2 right, vector >& result) { msize_t inrows = (msize_t)left.size(), incols; bool doresize = true; if (&left == &result) { doresize = false;//don't resize if an input is an output - this is ok for addition, don't need a copy } if (inrows) { incols = (msize_t)left[0].size(); if (doresize) resize(inrows, incols, result, true); for (msize_t i = 0; i < inrows; ++i) { for (msize_t j = 0; j < incols; ++j) { result[i][j] = left[i][j] + right; } } } else { result.resize(0); return; } } template void MatrixFunctions::subtract(const vector >& left, const vector >& right, vector >& result) { msize_t inrows = (msize_t)left.size(), incols; bool doresize = true; if (&left == &result || &right == &result) { doresize = false;//don't resize if an input is an output } if (inrows) { incols = (msize_t)left[0].size(); if (inrows == (msize_t)right.size() && incols == (msize_t)right[0].size())//short circuit evaluation will protect against segfault { if (doresize) resize(inrows, incols, result, true); for (msize_t i = 0; i < inrows; ++i) { for (msize_t j = 0; j < incols; ++j) { result[i][j] = left[i][j] - right[i][j]; } } } else { result.resize(0); return; } } else { result.resize(0); return; } } template void MatrixFunctions::transpose(const vector > &in, vector > &result) { msize_t inrows = (msize_t)in.size(), incols; vector > tempstorage, *tresult = &result; bool copyout = false; if (&in == &result) { copyout = true; tresult = &tempstorage; } if (inrows) { incols = (msize_t)in[0].size(); resize(incols, inrows, (*tresult), true); for (msize_t i = 0; i < inrows; ++i) { for (msize_t j = 0; j < incols; ++j) { (*tresult)[j][i] = in[i][j]; } } } else { result.resize(0); } if (copyout) { result = tempstorage; } } template bool MatrixFunctions::checkDim(const vector > &in) { bool ret = true; msize_t rows = (msize_t)in.size(), columns; if (rows) { columns = (msize_t)in[0].size(); for (msize_t i = 1; i < rows; ++i) { if (in[i].size() != columns) { ret = false; } } } return ret; } template void MatrixFunctions::resize(const msize_t rows, const msize_t columns, vector >& result, bool destructive) { if (destructive && result.size() && ((msize_t)result.capacity() < rows || (msize_t)result[0].capacity() < columns)) {//for large matrices, copying to preserve contents is slow result.resize(0);//not intended to dealloc, just to set number of items to copy to zero }//default is nondestructive resize, copies everything result.resize(rows); for (msize_t i = 0; i < (const msize_t)rows; ++i) {//naive method, may end up copying everything twice if both row and col resizes require realloc result[i].resize(columns); } } template void MatrixFunctions::zeros(const msize_t rows, const msize_t columns, vector >& result) { resize(rows, columns, result, true); for (msize_t i = 0; i < rows; ++i) { for (msize_t j = 0; j < columns; ++j) { result[i][j] = 0;//should cast to float or double fine } } } template void MatrixFunctions::ones(const msize_t rows, const msize_t columns, vector >& result) { resize(rows, columns, result, true); for (msize_t i = 0; i < rows; ++i) { for (msize_t j = 0; j < columns; ++j) { result[i][j] = 1;//should cast to float or double fine } } } template void MatrixFunctions::identity(const msize_t size, vector >& result) { resize(size, size, result, true); for (msize_t i = 0; i < (const msize_t)size; ++i) { for (msize_t j = 0; j < (const msize_t)size; ++j) { result[i][j] = ((i == j) ? 1 : 0);//ditto, forgive the ternary } } } template void MatrixFunctions::horizCat(const vector >& left, const vector >& right, vector >& result) { msize_t inrows = (msize_t)left.size(), leftcols, rightcols; vector > tempstorage, *tresult = &result; bool copyout = false; if (&left == &result || &right == &result) { copyout = true; tresult = &tempstorage; } if (inrows && inrows == (msize_t)right.size()) { leftcols = (msize_t)left[0].size(); rightcols = (msize_t)right[0].size(); (*tresult) = left;//use STL copy to start resize(inrows, leftcols + rightcols, (*tresult));//values survive nondestructive resize for (msize_t i = 0; i < inrows; ++i) { for (msize_t j = 0; j < rightcols; ++j) { (*tresult)[i][j + leftcols] = right[i][j]; } } } else { result.resize(0); return; } if (copyout) { result = tempstorage; } } template void MatrixFunctions::vertCat(const vector >& top, const vector >& bottom, vector >& result) { msize_t toprows = (msize_t)top.size(), botrows = (msize_t)bottom.size(), incols; vector > tempstorage, *tresult = &result; bool copyout = false; if (&top == &result || &bottom == &result) { copyout = true; tresult = &tempstorage; } if (toprows && botrows) { incols = (msize_t)top[0].size(); if (incols == (msize_t)bottom[0].size()) { (*tresult) = top; resize(toprows + botrows, incols, (*tresult));//nondestructive resize for (msize_t i = 0; i < botrows; ++i) { for (msize_t j = 0; j < incols; ++j) { (*tresult)[i + toprows][j] = bottom[i][j]; } } } else { result.resize(0); return; } } else { result.resize(0); return; } if (copyout) { result = tempstorage; } } template void MatrixFunctions::getChunk(const msize_t firstrow, const msize_t lastrow, const msize_t firstcol, const msize_t lastcol, const vector >& in, vector >& result) { msize_t outrows = lastrow - firstrow; msize_t outcols = lastcol - firstcol; if (lastrow <= firstrow || lastcol <= firstcol || firstrow < 0 || firstcol < 0 || lastrow > (msize_t)in.size() || lastcol > (msize_t)in[0].size()) { result.resize(0); return; } vector > tempstorage, *tresult = &result; bool copyout = false; if (&in == &result) { copyout = true; tresult = &tempstorage; } resize(outrows, outcols, (*tresult), true); for (msize_t i = 0; i < outrows; ++i) { for (msize_t j = 0; j < outcols; ++j) { (*tresult)[i][j] = in[i + firstrow][j + firstcol]; } } if (copyout) { result = tempstorage; } } } #endif workbench-1.1.1/src/Common/ModelTransform.cxx000066400000000000000000000276771255417355300212300ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __MODEL_TRANSFORM_DECLARE__ #include "ModelTransform.h" #undef __MODEL_TRANSFORM_DECLARE__ #include #include "CaretLogger.h" using namespace caret; /** * \class caret::ModelTransform * \brief Translation, Rotation, and Scaling for a model. * * Translation, Rotation, and Scaling for a model. */ /** * Constructor. */ ModelTransform::ModelTransform() : CaretObject() { this->name = ""; this->comment = ""; setToIdentity(); } /** * Destructor. */ ModelTransform::~ModelTransform() { } /** * Set the view to the identity matrix. */ void ModelTransform::setToIdentity() { this->translation[0] = 0.0; this->translation[1] = 0.0; this->translation[2] = 0.0; for (int32_t i = 0; i < 4; i++) { for (int32_t j = 0; j < 4; j++) { if (i == j) { this->rotation[i][j] = 1.0; this->obliqueRotation[i][j] = 1.0; } else { this->rotation[i][j] = 0.0; this->obliqueRotation[i][j] = 0.0; } } } this->scaling = 1.0; } /** * Copy constructor. * @param ModelTransform * View that is copied. */ ModelTransform::ModelTransform(const ModelTransform& ModelTransform) : CaretObject(ModelTransform) { this->copyHelper(ModelTransform); } /** * Assignment operator. * @param ModelTransform * View that is copied to this view. * @return * Reference to this object. */ ModelTransform& ModelTransform::operator=(const ModelTransform& ModelTransform) { if (this != &ModelTransform) { CaretObject::operator=(ModelTransform); this->copyHelper(ModelTransform); } return *this; } /** * Less than operator. * @param view * View compared to this view. * @return * Returns result of a name comparison. */ bool ModelTransform::operator<(const ModelTransform& view) const { return (this->name < view.name); } /** * Equality operator. * @param view * View compared to this view. * @return * Returns true if views have same name. */ bool ModelTransform::operator==(const ModelTransform& view) const { return (this->name == view.name); } /** * Get the translation * @param translation * Output translation. */ void ModelTransform::getTranslation(float translation[3]) const { translation[0] = this->translation[0]; translation[1] = this->translation[1]; translation[2] = this->translation[2]; } /** * Get the rotation matrix. * @param rotation * Output rotation matrix. */ void ModelTransform::getRotation(float rotation[4][4]) const { for (int32_t i = 0; i < 4; i++) { for (int32_t j = 0; j < 4; j++) { rotation[i][j] = this->rotation[i][j]; } } } /** * Get the oblique rotation matrix. * @param obliqueRotation * Output oblique rotation matrix. */ void ModelTransform::getObliqueRotation(float obliqueRotation[4][4]) const { for (int32_t i = 0; i < 4; i++) { for (int32_t j = 0; j < 4; j++) { obliqueRotation[i][j] = this->obliqueRotation[i][j]; } } } /** * @return The scaling. */ float ModelTransform::getScaling() const { return this->scaling; } /** * @return Name of the view. */ AString ModelTransform::getName() const { return this->name; } /** * Set the name of the view. * @param name * New name for view. */ void ModelTransform::setName(const AString& name) { this->name = name; } /** * @return Comment of the view. */ AString ModelTransform::getComment() const { return this->comment; } /** * Set the comment of the view. * @param comment * New comment for view. */ void ModelTransform::setComment(const AString& comment) { this->comment = comment; } /** * Set the translation * @param translation * New translation. */ void ModelTransform::setTranslation(const float translation[3]) { this->translation[0] = translation[0]; this->translation[1] = translation[1]; this->translation[2] = translation[2]; } /** * Set the translation * @param translationX * New translation X-value. * @param translationY * New translation Y-value. * @param translationZ * New translation Z-value. */ void ModelTransform::setTranslation(const float translationX, const float translationY, const float translationZ) { this->translation[0] = translationX; this->translation[1] = translationY; this->translation[2] = translationZ; } /** * Set the rotation matrix. * @param rotation * New rotation matrix. */ void ModelTransform::setRotation(const float rotation[4][4]) { for (int32_t i = 0; i < 4; i++) { for (int32_t j = 0; j < 4; j++) { this->rotation[i][j] = rotation[i][j]; } } } /** * Set the oblique rotation matrix. * @param obliqueRotation * New oblique rotation matrix. */ void ModelTransform::setObliqueRotation(const float obliqueRotation[4][4]) { for (int32_t i = 0; i < 4; i++) { for (int32_t j = 0; j < 4; j++) { this->obliqueRotation[i][j] = obliqueRotation[i][j]; } } } /** * Set the scaling * @param scaling * New value for scaling. */ void ModelTransform::setScaling(const float scaling) { this->scaling = scaling; } /** * Returns the user view in a string that contains, * separated by commas: View Name, translation[3], * rotation[4][4], scaling, and obliqueRotation[4][4]. */ AString ModelTransform::getAsString() const { AString s = (this->name + s_separatorInPreferences + this->comment + s_separatorInPreferences + AString::number(this->translation[0]) + s_separatorInPreferences + AString::number(this->translation[1]) + s_separatorInPreferences + AString::number(this->translation[2])); for (int32_t i = 0; i < 4; i++) { for (int32_t j = 0; j < 4; j++) { s += (s_separatorInPreferences + AString::number(this->rotation[i][j])); } } s += (s_separatorInPreferences + AString::number(this->scaling)); for (int32_t i = 0; i < 4; i++) { for (int32_t j = 0; j < 4; j++) { s += (s_separatorInPreferences + AString::number(this->obliqueRotation[i][j])); } } return s; } /** * Set the user view from a string that contains, * separated by commas: View Name, translation[3], * rotation[4][4], scaling, and obliqueRotation[4][4]. */ bool ModelTransform::setFromString(const AString& s) { bool hasComment = false; bool hasObliqueRotation = false; QStringList sl; if (s.contains(s_separatorInPreferences)) { sl = s.split(s_separatorInPreferences, QString::KeepEmptyParts); const int numElements = sl.count(); if (numElements == 38) { hasComment = true; hasObliqueRotation = true; } else if (numElements == 22) { hasComment = true; } else { CaretLogSevere("User view string does not contain 22 elements"); return false; } } else { sl = s.split(",", QString::KeepEmptyParts); const int numElements = sl.count(); if (numElements != 21) { CaretLogSevere("User view string does not contain 21 elements"); return false; } } int ctr = 0; this->name = sl.at(ctr++); if (hasComment) { this->comment = sl.at(ctr++); } else { this->comment = ""; } this->translation[0] = sl.at(ctr++).toFloat(); this->translation[1] = sl.at(ctr++).toFloat(); this->translation[2] = sl.at(ctr++).toFloat(); for (int32_t i = 0; i < 4; i++) { for (int32_t j = 0; j < 4; j++) { this->rotation[i][j] = sl.at(ctr++).toFloat(); } } this->scaling = sl.at(ctr++).toFloat(); if (hasObliqueRotation) { for (int32_t i = 0; i < 4; i++) { for (int32_t j = 0; j < 4; j++) { this->obliqueRotation[i][j] = sl.at(ctr++).toFloat(); } } } else { for (int32_t i = 0; i < 4; i++) { for (int32_t j = 0; j < 4; j++) { if (i == j) { this->obliqueRotation[i][j] = 1.0; } else { this->obliqueRotation[i][j] = 0.0; } } } } return true; } /** * Copy all data from the given user view to this user view. * @param ModelTransform * View from which data is copied. */ void ModelTransform::copyHelper(const ModelTransform& modelTransform) { this->name = modelTransform.name; this->comment = modelTransform.comment; this->translation[0] = modelTransform.translation[0]; this->translation[1] = modelTransform.translation[1]; this->translation[2] = modelTransform.translation[2]; for (int32_t i = 0; i < 4; i++) { for (int32_t j = 0; j < 4; j++) { this->rotation[i][j] = modelTransform.rotation[i][j]; this->obliqueRotation[i][j] = modelTransform.obliqueRotation[i][j]; } } this->scaling = modelTransform.scaling; } /** * Set panning, rotation, oblique rotation, and zoom. * * @param panX * X-Panning. * @param panY * Y-Panning. * @param panZ * Z-Panning. * @param rotationMatrix * 4x4 rotation matrix. * @param obliqueRotationMatrix * 4x4 oblique rotation matrix. * @param zoom * Zooming. */ void ModelTransform::setPanningRotationMatrixAndZoom(const float panX, const float panY, const float panZ, const float rotationMatrix[4][4], const float obliqueRotationMatrix[4][4], const float zoom) { this->setTranslation(panX, panY, panZ); setRotation(rotationMatrix); setObliqueRotation(obliqueRotationMatrix); this->setScaling(zoom); } /** * Get pan, rotation, oblique rotation, and zoom. * * @param panX * X-Panning. * @param panY * Y-Panning. * @param rotationMatrix * 4x4 rotation matrix. * @param obliqueRotationMatrix * 4x4 oblique rotation matrix. * @param zoom * Zooming. */ void ModelTransform::getPanningRotationMatrixAndZoom(float& panX, float& panY, float& panZ, float rotationMatrix[4][4], float obliqueRotationMatrix[4][4], float& zoom) const { panX = this->translation[0]; panY = this->translation[1]; panZ = this->translation[2]; getRotation(rotationMatrix); getObliqueRotation(obliqueRotationMatrix); zoom = getScaling(); } /** * Get a description of this object's content. * @return String describing this object's content. */ AString ModelTransform::toString() const { return ("ModelTransform: " + this->getAsString()); } workbench-1.1.1/src/Common/ModelTransform.h000066400000000000000000000073601255417355300206400ustar00rootroot00000000000000#ifndef __MODEL_TRANSFORM_H__ #define __MODEL_TRANSFORM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" namespace caret { class ModelTransform : public CaretObject { public: ModelTransform(); virtual ~ModelTransform(); ModelTransform(const ModelTransform&); ModelTransform& operator=(const ModelTransform&); AString getName() const; void getTranslation(float translation[3]) const; void getRotation(float rotation[4][4]) const; void getObliqueRotation(float obliqueRotation[4][4]) const; float getScaling() const; void setName(const AString& name); AString getComment() const; void setComment(const AString& comment); void setTranslation(const float translation[3]); void setTranslation(const float translationX, const float translationY, const float translationZ); void setRotation(const float rotation[4][4]); void setObliqueRotation(const float obliqueRotation[4][4]); void setScaling(const float scaling); void setPanningRotationMatrixAndZoom(const float panX, const float panY, const float panZ, const float rotationMatrix[4][4], const float obliqueRotationMatrix[4][4], const float zoom); void getPanningRotationMatrixAndZoom(float& panX, float& panY, float& panZ, float rotationMatrix[4][4], float obliqueRotationMatrix[4][4], float& zoom) const; AString getAsString() const; bool setFromString(const AString& s); void setToIdentity(); bool operator<(const ModelTransform& view) const; bool operator==(const ModelTransform& view) const; public: virtual AString toString() const; private: void copyHelper(const ModelTransform& ModelTransform); AString name; AString comment; float translation[3]; float rotation[4][4]; float obliqueRotation[4][4]; float scaling; static const QString s_separatorInPreferences; }; #ifdef __MODEL_TRANSFORM_DECLARE__ const QString ModelTransform::s_separatorInPreferences = "::::"; #endif // __MODEL_TRANSFORM_DECLARE__ } // namespace #endif //__MODEL_TRANSFORM_H__ workbench-1.1.1/src/Common/MultiDimArray.h000066400000000000000000000073411255417355300204260ustar00rootroot00000000000000#ifndef __MULTI_DIM_ARRAY_H__ #define __MULTI_DIM_ARRAY_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretAssert.h" #include "stdint.h" #include namespace caret { template class MultiDimArray { std::vector m_dims, m_skip;//always use int64_t for indexes internally std::vector m_data; template int64_t index(const int& fullDims, const std::vector& indexSelect) const;//assume we never need over 2 billion dimensions public: const std::vector& getDimensions() const { return m_dims; } template void resize(const std::vector& dims);//destructive resize template T& at(const std::vector& pos); template const T& at(const std::vector& pos) const; template T* get(const int& fullDims, const std::vector& indexSelect);//subarray reference selection template const T* get(const int& fullDims, const std::vector& indexSelect) const; }; template template void MultiDimArray::resize(const std::vector& dims) { m_dims = std::vector(dims.begin(), dims.end()); m_skip.resize(m_dims.size()); if (dims.size() == 0) { m_data.clear(); return; } int64_t numElems = 1; for (int i = 0; i < (int)m_dims.size(); ++i) { CaretAssert(m_dims[i] > 0); m_skip[i] = numElems; numElems *= m_dims[i]; } m_data.resize(numElems); } template template int64_t MultiDimArray::index(const int& fullDims, const std::vector& indexSelect) const { CaretAssert(fullDims + indexSelect.size() == m_dims.size()); int64_t ret = 0; for (int i = fullDims; i < (int)m_dims.size(); ++i) { CaretAssert(indexSelect[i - fullDims] >= 0 && indexSelect[i - fullDims] < m_dims[i]); ret += m_skip[i] * indexSelect[i - fullDims]; } return ret; } template template T& MultiDimArray::at(const std::vector& pos) { return m_data[index(0, pos)]; } template template const T& MultiDimArray::at(const std::vector& pos) const { return m_data[index(0, pos)]; } template template T* MultiDimArray::get(const int& fullDims, const std::vector& indexSelect) { return m_data.data() + index(fullDims, indexSelect); } template template const T* MultiDimArray::get(const int& fullDims, const std::vector& indexSelect) const { return m_data.data() + index(fullDims, indexSelect); } } #endif //__MULTI_DIM_ARRAY_H__ workbench-1.1.1/src/Common/MultiDimIterator.h000066400000000000000000000076621255417355300211470ustar00rootroot00000000000000#ifndef __MULTI_DIM_ITERATOR_H__ #define __MULTI_DIM_ITERATOR_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "stdint.h" #include namespace caret { template class MultiDimIterator { std::vector m_dims, m_pos; bool m_atEnd; void gotoBegin(); void gotoLast(); public: explicit MultiDimIterator(const std::vector& dimensions); void operator++(); void operator++(int); void operator--(); void operator--(int); const std::vector& operator*() const { return m_pos; } bool atEnd() const { return m_atEnd; } }; template MultiDimIterator::MultiDimIterator(const std::vector& dimensions) { m_dims = dimensions; gotoBegin(); } template void MultiDimIterator::gotoBegin() { m_pos = std::vector(m_dims.size(), 0); m_atEnd = false; size_t numDims = m_dims.size(); for (size_t i = 0; i < numDims; ++i) { if (m_dims[i] < 1) { m_atEnd = true; break; } } } template void MultiDimIterator::gotoLast() { m_pos = std::vector(m_dims.size()); m_atEnd = false; size_t numDims = m_dims.size(); for (size_t i = 0; i < numDims; ++i) { m_pos[i] = m_dims[i] - 1; if (m_dims[i] < 1) { m_atEnd = true; } } } template void MultiDimIterator::operator++() { if (atEnd())//wrap around { gotoBegin(); return; } if (m_dims.size() == 0) { m_atEnd = true;//special case: no dimensions works the same as 1 dimension of length 1 return; } size_t numDims = m_dims.size(); for (size_t i = 0; i < numDims; ++i) { ++m_pos[i]; if (m_pos[i] < m_dims[i]) return; m_pos[i] = 0; } m_atEnd = true;//if we didn't return already, all of them wrapped, so we are at the end } template void MultiDimIterator::operator++(int) { ++(*this); } template void MultiDimIterator::operator--() { if (atEnd())//wrap around { gotoLast(); return; } if (m_dims.size() == 0) { m_atEnd = true;//special case: no dimensions works the same as 1 dimension of length 1 return; } size_t numDims = m_dims.size(); for (size_t i = 0; i < numDims; ++i) { if (m_pos[i] > 0) { --m_pos[i]; return; } else { m_pos[i] = m_dims[i] - 1; } } m_atEnd = true;//if we didn't return already, all of them wrapped, so we are at the end } template void MultiDimIterator::operator--(int) { --(*this); } } #endif //__MULTI_DIM_ITERATOR_H__ workbench-1.1.1/src/Common/NetworkException.cxx000066400000000000000000000026201255417355300215610ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "NetworkException.h" using namespace caret; /** * Constructor. * */ NetworkException::NetworkException() : CaretException() { } /** * Constructor that uses stack trace from the exception * passed in as a parameter. * * @param e Any exception whose stack trace becomes * this exception's stack trace. * */ NetworkException::NetworkException( const CaretException& e) : CaretException(e) { } /** * Constructor. * * @param s Description of the exception. * */ NetworkException::NetworkException(const AString& s) : CaretException(s) { } workbench-1.1.1/src/Common/NetworkException.h000066400000000000000000000023641255417355300212130ustar00rootroot00000000000000#ifndef __NETWORK_EXCEPTION_H__ #define __NETWORK_EXCEPTION_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretException.h" namespace caret { /** * An exception thrown during network operations */ class NetworkException : public CaretException { public: NetworkException(); NetworkException(const CaretException& e); NetworkException(const AString& s); }; } // namespace #endif // __NETWORK_EXCEPTION_H__ workbench-1.1.1/src/Common/NumericTextFormatting.cxx000066400000000000000000000251741255417355300225640ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __NUMERIC_TEXT_FORMATTING_DECLARE__ #include "NumericTextFormatting.h" #undef __NUMERIC_TEXT_FORMATTING_DECLARE__ #include "CaretAssert.h" #include "CaretLogger.h" using namespace caret; #include "MathFunctions.h" /** * \class caret::NumericTextFormatting * \brief Formats numeric values for display as text. * \ingroup Common */ /** * Constructor. */ NumericTextFormatting::NumericTextFormatting() : CaretObject() { } /** * Destructor. */ NumericTextFormatting::~NumericTextFormatting() { } /** * If there are trailing zeros after the decimal point, remove * any after the first zero. Change a negative zero (-0) to zero (0); * * @param textValueIn * Text representation of the value. * @reutrn * Value after cleanup of zeros. */ AString NumericTextFormatting::cleanZerosInValueText(const AString& textValueIn) { AString textValue = textValueIn; const int dotIndex = textValue.indexOf('.'); if (dotIndex > 0) { /* * Remove any trailing zeros */ for (int32_t i = (textValue.length() - 1); i >= (dotIndex + 2); --i) { if (textValue[i] == '0') { textValue[i] = ' '; } else { break; } } } textValue = textValue.trimmed(); if (textValue == "-0.0") { textValue = "0"; } else if (textValue == "-0") { textValue = "0"; } else if (textValue == "0.0") { textValue = "0"; } textValue = textValue.trimmed(); return textValue; } /** * If there is a leading zero in the exponent, remove it * to save space (-2.1e+03 => -2.1e+3) * * @param textValueIn * Text representation of the value. * @reutrn * Value after cleanup of zeros. */ AString NumericTextFormatting::removeLeadingZeroFromExponent(const AString& textValueIn) { AString textValue = textValueIn; AString ePlusMinus = ""; int eIndex = textValue.indexOf("e+"); if (eIndex < 0) { eIndex = textValue.indexOf("e-"); } /* * Regular expression that matches something like 0.000e+00 (zero !!!) */ static QRegExp zeroRegExp("^0\\.0+e[\\+-]0+$"); if (eIndex > 0) { if (textValue.indexOf(zeroRegExp) >= 0) { textValue = "0"; } else { AString eText = textValue.mid(eIndex, 2); AString mantissaText = textValue.left(eIndex); AString exponentText = textValue.mid(eIndex + 2); /* * Remove trailing zeros from mantissa */ mantissaText = cleanZerosInValueText(mantissaText); /* * Remove leading zeros from exponent and * keep last zero */ for (int32_t i = 0; i < (exponentText.length() - 1); i++) { if (exponentText[i] == '0') { exponentText[i] = ' '; } else { break; } } exponentText = exponentText.trimmed(); textValue = (mantissaText + eText + exponentText); // std::cout << " Sci Value " // << textValueIn // << " Mantissa: " << mantissaText // << " e: " << eText // << " Exponent: " << exponentText << std::endl; } } return textValue; } /** * Format a number for display. * * @param value * The value for formatting. * @param format * Formatting to use for number ('f' fixed, 'e' scientific). * @param fieldWidth * * @param precision * Digits right of the decimal point * @return * Text representation of number with formatting applied. */ AString NumericTextFormatting::formatNumberForDisplay(const double value, const char format, const int fieldWidth, const int precision) { if (MathFunctions::isNaN(value)) { return "NaN"; } else if (MathFunctions::isInf(value)) { return "Inf"; } else if (MathFunctions::isNegInf(value)) { return "-Inf"; } else if (MathFunctions::isPosInf(value)) { return "Inf"; } const double absValue = ((value < 0.0) ? -value : value); AString numberValue = QString("%1").arg(absValue, fieldWidth, format, precision); numberValue = removeLeadingZeroFromExponent(numberValue); AString textValue; if (value < 0.0) { textValue = "-"; } textValue += numberValue; textValue = cleanZerosInValueText(textValue); return textValue; } /** * Get format and precision based upon value of the number. * * @param valueIn * The number. * @param formatOut * Output format (fixed 'f' or scientific 'e') * @param precisionOut * Number of digits right of decimal point. */ void NumericTextFormatting::getFormatAndPrecision(const float valueIn, char& formatOut, int& precisionOut) { const float value = ((valueIn < 0.0) ? -valueIn : valueIn); if (value >= 10000) { formatOut = 'e'; precisionOut = 3; } else if (value >= 100) { formatOut = 'f'; precisionOut = 0; } else if (value >= 10) { formatOut = 'f'; precisionOut = 1; } else if (value >= 1) { formatOut = 'f'; precisionOut = 2; } else if (value >= 0.01) { formatOut = 'f'; precisionOut = 3; } else { formatOut = 'e'; precisionOut = 3; } } /** * Format the values by using the value of the range (min to max) * to set format and precision for all values. * * @param valuesIn * The input values array. Must be at least two elements and * the values must be sorted from smallest to largest. * @param formattedValuesOut * Output containing values formatted as text. * @param numberOfValues * Number of values in the arrays and both the input and output * arrays must sized to this value. */ void NumericTextFormatting::formatValueRange(const float valuesIn[], AString formattedValuesOut[], const int32_t numberOfValues) { for (int32_t i = 0; i < numberOfValues; i++) { formattedValuesOut[i] = ""; } if (numberOfValues < 2) { CaretLogSevere("NumericTextFormatting::formatValueRange requires at least two values."); return; } const double range = valuesIn[numberOfValues - 1] - valuesIn[0]; if (MathFunctions::isNaN(range)) { for (int32_t i = 0; i < numberOfValues; i++) { formattedValuesOut[i] = "0"; } formattedValuesOut[0] = "NaN"; formattedValuesOut[numberOfValues - 1] = "NaN"; return; } char format = 'f'; int precision = 0; getFormatAndPrecision(range, format, precision); const int FIELD_WIDTH = 0; for (int32_t i = 0; i < numberOfValues; i++) { const double value = valuesIn[i]; AString textValue = formatNumberForDisplay(value, format, FIELD_WIDTH, precision); formattedValuesOut[i] = textValue; } } /** * Format the values by formatting the negative and positive * ranges separately. * * @param negMaxNegMinPosMinPosMaxValuesIn * [0] => most negative value * [1] => least negative value * [2] => least positive value * [3] => most positive value * [0] <= [1] <= 0.0 <= [2] <= [3] * @param formattedValuesOut * Output containing values formatted as text. */ void NumericTextFormatting::formatValueRangeNegativeAndPositive(const float negMaxNegMinPosMinPosMaxValuesIn[4], AString formattedValuesOut[4]) { formatValueRange(&negMaxNegMinPosMinPosMaxValuesIn[0], &formattedValuesOut[0], 2); formatValueRange(&negMaxNegMinPosMinPosMaxValuesIn[2], &formattedValuesOut[2], 2); } /** * Format the value. * * @param valuesn * The input values. * @return * Value formatted as text. */ AString NumericTextFormatting::formatValue(const float value) { char format = 'f'; int precision = 0; getFormatAndPrecision(value, format, precision); const int FIELD_WIDTH = 0; AString textValue = formatNumberForDisplay(value, format, FIELD_WIDTH, precision); return textValue; } /** * Format each of the values. * * @param valuesIn * The input values array. * @param formattedValuesOut * Output containing values formatted as text. * @param numberOfValues * Number of values in the arrays and both the input and output * arrays must sized to this value. */ void NumericTextFormatting::formatValuesIndividually(const float valuesIn[], AString formattedValuesOut[], const int32_t numberOfValues) { for (int32_t i = 0; i < numberOfValues; i++) { formattedValuesOut[i] = formatValue(valuesIn[i]); } } workbench-1.1.1/src/Common/NumericTextFormatting.h000066400000000000000000000055401255417355300222040ustar00rootroot00000000000000#ifndef __NUMERIC_TEXT_FORMATTING_H__ #define __NUMERIC_TEXT_FORMATTING_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" namespace caret { class NumericTextFormatting : public CaretObject { public: static void formatValueRangeNegativeAndPositive(const float negMaxNegMinPosMinPosMaxValuesIn[4], AString formattedValuesOut[4]); static void formatValueRange(const float valuesIn[], AString formattedValuesOut[], const int32_t numberOfValues); static AString formatValue(const float valueIn); static void formatValuesIndividually(const float valuesIn[], AString formattedValuesOut[], const int32_t numberOfValues); // ADD_NEW_METHODS_HERE private: NumericTextFormatting(); virtual ~NumericTextFormatting(); NumericTextFormatting(const NumericTextFormatting&); NumericTextFormatting& operator=(const NumericTextFormatting&); static AString cleanZerosInValueText(const AString& textValueIn); static AString removeLeadingZeroFromExponent(const AString& textValueIn); static AString formatNumberForDisplay(const double value, const char format, const int fieldWidth, const int precision); static void getFormatAndPrecision(const float valueIn, char& formatOut, int& precisionOut); // ADD_NEW_MEMBERS_HERE }; #ifdef __NUMERIC_TEXT_FORMATTING_DECLARE__ // #endif // __NUMERIC_TEXT_FORMATTING_DECLARE__ } // namespace #endif //__NUMERIC_TEXT_FORMATTING_H__ workbench-1.1.1/src/Common/OctTree.h000066400000000000000000000356721255417355300172600ustar00rootroot00000000000000#ifndef __OCT_TREE_H__ #define __OCT_TREE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "MathFunctions.h" #include namespace caret { ///low level Oct structure with a bunch of helper members, use it to build your own tree of Octs, possibly by extension template struct Oct { //data T m_data; //children Oct* m_children[2][2][2]; Oct* m_parent; bool m_leaf; float m_bounds[3][3]; Oct(); Oct(const float minCoords[3], const float maxCoords[3]); ~Oct(); void makeChildren(); void makeChildrenExcept(const int octant[3]); void deleteChildren(); ///makes an Oct with this node as the child specified by octant Oct* makeParent(const int octant[3]); Oct* makeContains(const float pointToContain[3]); float distToPoint(const float point[3]); float distSquaredToPoint(const float point[3]); bool lineIntersects(const float p1[3], const float p2[3]); bool rayIntersects(const float start[3], const float p2[3]); bool lineSegmentIntersects(const float start[3], const float end[3]); bool pointInside(const float point[3]); bool boundsOverlaps(const float minCoords[3], const float maxCoords[3]); ///returns which child Oct the point would be contained in if the point were inside this Oct Oct* containingChild(const float point[3], int* whichOct = NULL); }; ///simple templated vector pointer that can be deleted, since you shouldn't rely on any method for actually deleting a vector's memory, for convenience template struct LeafVector { std::vector* m_vector; LeafVector() { m_vector = new std::vector(); } ~LeafVector() { freeData(); } void freeData() { if (m_vector != NULL) { delete m_vector; m_vector = NULL; } } T& operator[](const int64_t index) { return (*m_vector)[index]; } const T& operator[](const int64_t index) const { return (*m_vector)[index]; } }; template Oct::Oct() { for (int i = 0; i < 2; ++i) { m_children[i][0][0] = NULL; m_children[i][0][1] = NULL; m_children[i][1][0] = NULL; m_children[i][1][1] = NULL; } m_parent = NULL; m_leaf = true; } template Oct::Oct(const float minCoords[3], const float maxCoords[3]) { for (int i = 0; i < 2; ++i) { m_children[i][0][0] = NULL; m_children[i][0][1] = NULL; m_children[i][1][0] = NULL; m_children[i][1][1] = NULL; } m_parent = NULL; m_leaf = true; for (int i = 0; i < 3; ++i) { m_bounds[i][0] = minCoords[i]; m_bounds[i][2] = maxCoords[i]; m_bounds[i][1] = (m_bounds[i][0] + m_bounds[i][2]) * 0.5f; } } template Oct::~Oct() { deleteChildren(); } template void Oct::makeChildren() { m_leaf = false; int ijk[3]; for (ijk[0] = 0; ijk[0] < 2; ++ijk[0]) { for (ijk[1] = 0; ijk[1] < 2; ++ijk[1]) { for (ijk[2] = 0; ijk[2] < 2; ++ijk[2]) { Oct* temp = new Oct(); m_children[ijk[0]][ijk[1]][ijk[2]] = temp; temp->m_parent = this; for (int m = 0; m < 3; ++m) { temp->m_bounds[m][0] = m_bounds[m][ijk[m]]; temp->m_bounds[m][2] = m_bounds[m][ijk[m] + 1]; temp->m_bounds[m][1] = (temp->m_bounds[m][0] + temp->m_bounds[m][2]) * 0.5f; } } } } } template void Oct::makeChildrenExcept(const int octant[3]) { m_leaf = false; int ijk[3]; for (ijk[0] = 0; ijk[0] < 2; ++ijk[0]) { for (ijk[1] = 0; ijk[1] < 2; ++ijk[1]) { for (ijk[2] = 0; ijk[2] < 2; ++ijk[2]) { if (ijk[0] != octant[0] && ijk[1] != octant[1] && ijk[2] != octant[2]) {//avoiding one new/delete pair should be worth 8 times this conditional Oct* temp = new Oct(); m_children[ijk[0]][ijk[1]][ijk[2]] = temp; temp->m_parent = this; for (int m = 0; m < 3; ++m) { temp->m_bounds[m][0] = m_bounds[m][ijk[m]]; temp->m_bounds[m][2] = m_bounds[m][ijk[m] + 1]; temp->m_bounds[m][1] = (temp->m_bounds[m][0] + temp->m_bounds[m][2]) * 0.5f; } } } } } } template void Oct::deleteChildren() { m_leaf = true; for (int i = 0; i < 2; ++i) { for (int j = 0; j < 2; ++j) { for (int k = 0; k < 2; ++k) { if (m_children[i][j][k] != NULL) { delete m_children[i][j][k]; m_children[i][j][k] = NULL; } } } } } template Oct* Oct::makeParent(const int octant[3]) { Oct* ret = new Oct(); for (int i = 0; i < 3; ++i) { ret->m_bounds[i][octant[i]] = m_bounds[i][0]; ret->m_bounds[i][octant[i] + 1] = m_bounds[i][2]; ret->m_bounds[i][(octant[i] + 2) % 3] = (octant[i] ? (2.0f * m_bounds[i][0] - m_bounds[i][2]) : (2.0f * m_bounds[i][2] - m_bounds[i][0])); } ret->makeChildrenExcept(octant); ret->m_children[octant[0]][octant[1]][octant[2]] = this; m_parent = ret; return ret; } template Oct* Oct::makeContains(const float pointToContain[3]) { Oct* ret = this; while (!ret->pointInside(pointToContain)) { int octant[3]; octant[0] = (pointToContain[0] < m_bounds[0][1] ? 1 : 0);//use midpoint to intelligently pick best division when more than one division would contain it octant[1] = (pointToContain[1] < m_bounds[1][1] ? 1 : 0); octant[2] = (pointToContain[2] < m_bounds[2][1] ? 1 : 0); ret = ret->makeParent(octant); } return ret; } template float Oct::distToPoint(const float point[3]) { float temp[3]; for (int i = 0; i < 3; ++i) { if (point[i] < m_bounds[i][0]) { temp[i] = m_bounds[i][0] - point[i]; } else { if (point[i] > m_bounds[i][2]) { temp[i] = m_bounds[i][2] - point[i]; } else { temp[i] = 0.0f; } } } return MathFunctions::vectorLength(temp); } template float Oct::distSquaredToPoint(const float point[3]) { float temp[3]; for (int i = 0; i < 3; ++i) { if (point[i] < m_bounds[i][0]) { temp[i] = m_bounds[i][0] - point[i]; } else { if (point[i] > m_bounds[i][2]) { temp[i] = m_bounds[i][2] - point[i]; } else { temp[i] = 0.0f; } } } return temp[0] * temp[0] + temp[1] * temp[1] + temp[2] * temp[2]; } template bool Oct::lineIntersects(const float p1[3], const float p2[3]) { float direction[3]; float curlow = 1.0f, curhigh = -1.0f;//quiet compiler, make default say "false", but we use pointInside logic on zero length queries MathFunctions::subtractVectors(p2, p1, direction); bool first = true; for (int i = 0; i < 3; ++i) { if (direction[i] != 0.0f) { float templow; float temphigh; if (direction[i] > 0.0f) { templow = (m_bounds[i][0] - p1[i]) / direction[i];//compute the range of t over which this line lies between the planes for this axis temphigh = (m_bounds[i][2] - p1[i]) / direction[i]; } else { templow = (m_bounds[i][2] - p1[i]) / direction[i];//compute the range of t over which this line lies between the planes for this axis temphigh = (m_bounds[i][0] - p1[i]) / direction[i]; } if (first) { first = false; curlow = templow; curhigh = temphigh; } else { if (templow > curlow) curlow = templow;//intersect the ranges if (temphigh < curhigh) curhigh = temphigh; } if (curhigh < curlow) return false;//if intersection is null, false } else { if (p1[i] < m_bounds[i][0] || p1[i] > m_bounds[i][2]) return false; } } return true; } template bool Oct::rayIntersects(const float start[3], const float p2[3]) { float direction[3]; float curlow = 1.0f, curhigh = -1.0f;//quiet compiler, make default say "false", but we use pointInside logic on zero length queries MathFunctions::subtractVectors(p2, start, direction); bool first = true; for (int i = 0; i < 3; ++i) { if (direction[i] != 0.0f) { float templow; float temphigh; if (direction[i] > 0.0f) { templow = (m_bounds[i][0] - start[i]) / direction[i];//compute the range of t over which this line lies between the planes for this axis temphigh = (m_bounds[i][2] - start[i]) / direction[i]; } else { templow = (m_bounds[i][2] - start[i]) / direction[i];//compute the range of t over which this line lies between the planes for this axis temphigh = (m_bounds[i][0] - start[i]) / direction[i]; } if (first) { first = false; curlow = templow; curhigh = temphigh; } else { if (templow > curlow) curlow = templow;//intersect the ranges if (temphigh < curhigh) curhigh = temphigh; } if (curhigh < curlow || curhigh < 0.0f) return false;//if intersection is null or has no positive range, false } else { if (start[i] < m_bounds[i][0] || start[i] > m_bounds[i][2]) return false; } } return true; } template bool Oct::lineSegmentIntersects(const float start[3], const float end[3]) { float direction[3]; float curlow = 1.0f, curhigh = -1.0f;//quiet compiler, make default say "false", but we use pointInside logic on zero length queries MathFunctions::subtractVectors(end, start, direction);//parameterize the line segment to the range [0, 1] of t bool first = true; for (int i = 0; i < 3; ++i) { if (direction[i] != 0.0f) { float templow; float temphigh; if (direction[i] > 0.0f) { templow = (m_bounds[i][0] - start[i]) / direction[i];//compute the range of t over which this line lies between the planes for this axis temphigh = (m_bounds[i][2] - start[i]) / direction[i]; } else { templow = (m_bounds[i][2] - start[i]) / direction[i];//compute the range of t over which this line lies between the planes for this axis temphigh = (m_bounds[i][0] - start[i]) / direction[i]; } if (first) { first = false; curlow = templow; curhigh = temphigh; } else { if (templow > curlow) curlow = templow;//intersect the ranges if (temphigh < curhigh) curhigh = temphigh; } if (curhigh < curlow || curhigh < 0.0f || curlow > 1.0f) return false;//if intersection is null or has no positive range, or has no range less than 1, false } else { if (start[i] < m_bounds[i][0] || start[i] > m_bounds[i][2]) return false; } } return true; } template bool Oct::pointInside(const float point[3]) { for (int i = 0; i < 3; ++i) { if (point[i] < m_bounds[i][0] || point[i] > m_bounds[i][2]) return false;//be permissive, equal to boundary falls into both, though for traversal, strictly less than the boundary is the test condition } return true; } template bool Oct::boundsOverlaps(const float minCoords[3], const float maxCoords[3]) { for (int i = 0; i < 3; ++i) { if (maxCoords[i] < m_bounds[i][0] || minCoords[i] > m_bounds[i][2]) return false;//be permissive, equal to boundary falls into both } return true; } template Oct* Oct::containingChild(const float point[3], int* whichOct) { int myOct[3]; for (int i = 0; i < 3; ++i) { myOct[i] = (point[i] < m_bounds[i][1] ? 0 : 1);//strictly less than, using only the midpoint is how traversal works, even if the point isn't inside the Oct if (whichOct != NULL) whichOct[i] = myOct[i]; } return m_children[myOct[0]][myOct[1]][myOct[2]]; } } #endif //__OCT_TREE_H__ workbench-1.1.1/src/Common/OpenGLDrawingMethodEnum.cxx000066400000000000000000000252501255417355300227030ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __OPEN_G_L_DRAWING_METHOD_ENUM_DECLARE__ #include "OpenGLDrawingMethodEnum.h" #undef __OPEN_G_L_DRAWING_METHOD_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::OpenGLDrawingMethodEnum * \brief * * * * Using this enumerated type in the GUI with an EnumComboBoxTemplate * * Header File (.h) * Forward declare the data type: * class EnumComboBoxTemplate; * * Declare the member: * EnumComboBoxTemplate* m_openGLDrawingMethodEnumComboBox; * * Declare a slot that is called when user changes selection * private slots: * void openGLDrawingMethodEnumComboBoxItemActivated(); * * Implementation File (.cxx) * Include the header files * #include "EnumComboBoxTemplate.h" * #include "OpenGLDrawingMethodEnum.h" * * Instatiate: * m_openGLDrawingMethodEnumComboBox = new EnumComboBoxTemplate(this); * m_openGLDrawingMethodEnumComboBox->setup(); * * Get notified when the user changes the selection: * QObject::connect(m_openGLDrawingMethodEnumComboBox, SIGNAL(itemActivated()), * this, SLOT(openGLDrawingMethodEnumComboBoxItemActivated())); * * Update the selection: * m_openGLDrawingMethodEnumComboBox->setSelectedItem(NEW_VALUE); * * Read the selection: * const OpenGLDrawingMethodEnum::Enum VARIABLE = m_openGLDrawingMethodEnumComboBox->getSelectedItem(); * */ /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ OpenGLDrawingMethodEnum::OpenGLDrawingMethodEnum(const Enum enumValue, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCodeCounter++; this->name = name; this->guiName = guiName; } /** * Destructor. */ OpenGLDrawingMethodEnum::~OpenGLDrawingMethodEnum() { } /** * Initialize the enumerated metadata. */ void OpenGLDrawingMethodEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(OpenGLDrawingMethodEnum(DRAW_WITH_VERTEX_BUFFERS_OFF, "DRAW_WITH_VERTEX_BUFFERS_OFF", "Off")); enumData.push_back(OpenGLDrawingMethodEnum(DRAW_WITH_VERTEX_BUFFERS_ON, "DRAW_WITH_VERTEX_BUFFERS_ON", "On")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const OpenGLDrawingMethodEnum* OpenGLDrawingMethodEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const OpenGLDrawingMethodEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString OpenGLDrawingMethodEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const OpenGLDrawingMethodEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ OpenGLDrawingMethodEnum::Enum OpenGLDrawingMethodEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = OpenGLDrawingMethodEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const OpenGLDrawingMethodEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type OpenGLDrawingMethodEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString OpenGLDrawingMethodEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const OpenGLDrawingMethodEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ OpenGLDrawingMethodEnum::Enum OpenGLDrawingMethodEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = OpenGLDrawingMethodEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const OpenGLDrawingMethodEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type OpenGLDrawingMethodEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t OpenGLDrawingMethodEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const OpenGLDrawingMethodEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ OpenGLDrawingMethodEnum::Enum OpenGLDrawingMethodEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = OpenGLDrawingMethodEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const OpenGLDrawingMethodEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type OpenGLDrawingMethodEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void OpenGLDrawingMethodEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void OpenGLDrawingMethodEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(OpenGLDrawingMethodEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void OpenGLDrawingMethodEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(OpenGLDrawingMethodEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } workbench-1.1.1/src/Common/OpenGLDrawingMethodEnum.h000066400000000000000000000062711255417355300223320ustar00rootroot00000000000000#ifndef __OPEN_G_L_DRAWING_METHOD_ENUM_H__ #define __OPEN_G_L_DRAWING_METHOD_ENUM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class OpenGLDrawingMethodEnum { public: /** * Enumerated values. */ enum Enum { /** OpenGL Vertex Buffers Off */ DRAW_WITH_VERTEX_BUFFERS_OFF, /** OpenGL Vertex Buffers On */ DRAW_WITH_VERTEX_BUFFERS_ON }; ~OpenGLDrawingMethodEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); private: OpenGLDrawingMethodEnum(const Enum enumValue, const AString& name, const AString& guiName); static const OpenGLDrawingMethodEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; #ifdef __OPEN_G_L_DRAWING_METHOD_ENUM_DECLARE__ std::vector OpenGLDrawingMethodEnum::enumData; bool OpenGLDrawingMethodEnum::initializedFlag = false; int32_t OpenGLDrawingMethodEnum::integerCodeCounter = 0; #endif // __OPEN_G_L_DRAWING_METHOD_ENUM_DECLARE__ } // namespace #endif //__OPEN_G_L_DRAWING_METHOD_ENUM_H__ workbench-1.1.1/src/Common/PlainTextStringBuilder.cxx000066400000000000000000000051451255417355300226640ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __PLAIN_TEXT_STRING_BUILDER_DECLARE__ #include "PlainTextStringBuilder.h" #undef __PLAIN_TEXT_STRING_BUILDER_DECLARE__ #include "CaretAssert.h" #include "CaretLogger.h" using namespace caret; /** * \class caret::PlainTextStringBuilder * \brief * \ingroup Common * * */ /** * Constructor. */ PlainTextStringBuilder::PlainTextStringBuilder() : CaretObject() { m_currentIndentationCount = 0; m_indentationNumberOfSpaces = 3; } /** * Constructor. */ PlainTextStringBuilder::PlainTextStringBuilder(const int32_t indentationNumberOfSpaces) : CaretObject() { m_currentIndentationCount = 0; m_indentationNumberOfSpaces = indentationNumberOfSpaces; } /** * Destructor. */ PlainTextStringBuilder::~PlainTextStringBuilder() { } /** * Clear the text. Does not change amount of indentation. */ void PlainTextStringBuilder::clear() { m_text = ""; } /** * Increase the indentation. */ void PlainTextStringBuilder::pushIndentation() { m_currentIndentationCount += m_indentationNumberOfSpaces; } /** * Decrease the indentation. */ void PlainTextStringBuilder::popIndentation() { m_currentIndentationCount -= m_indentationNumberOfSpaces; if (m_currentIndentationCount < 0) { CaretLogSevere("Indentation pops exceeds pushes (indent < 0)"); m_currentIndentationCount = 0; } } /** * Add the text preceded by indentation and followed by a newline. * * @param text * Text that is added. */ void PlainTextStringBuilder::addLine(const AString& text) { m_text += (AString().fill(' ', m_currentIndentationCount) + text + "\n"); } /** * @return The text */ AString PlainTextStringBuilder::getText() const { return m_text; } workbench-1.1.1/src/Common/PlainTextStringBuilder.h000066400000000000000000000040371255417355300223100ustar00rootroot00000000000000#ifndef __PLAIN_TEXT_STRING_BUILDER_H__ #define __PLAIN_TEXT_STRING_BUILDER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" namespace caret { class PlainTextStringBuilder : public CaretObject { public: PlainTextStringBuilder(); PlainTextStringBuilder(const int32_t indentationNumberOfSpaces); virtual ~PlainTextStringBuilder(); void clear(); void pushIndentation(); void popIndentation(); /** Add the text preceded by indentation and followed by a newline */ void addLine(const AString& text); /** Get the text */ AString getText() const; private: PlainTextStringBuilder(const PlainTextStringBuilder&); PlainTextStringBuilder& operator=(const PlainTextStringBuilder&); AString m_text; int32_t m_indentationNumberOfSpaces; int32_t m_currentIndentationCount; // ADD_NEW_MEMBERS_HERE }; #ifdef __PLAIN_TEXT_STRING_BUILDER_DECLARE__ // #endif // __PLAIN_TEXT_STRING_BUILDER_DECLARE__ } // namespace #endif //__PLAIN_TEXT_STRING_BUILDER_H__ workbench-1.1.1/src/Common/Plane.cxx000066400000000000000000000353571255417355300173250ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "MathFunctions.h" #include "Plane.h" using namespace caret; /** * Construct an invalid plane. * Intended for use by the assignement operator. */ Plane::Plane() : CaretObject() { m_pointOnPlane[0] = 0.0; m_pointOnPlane[1] = 0.0; m_pointOnPlane[2] = 0.0; m_normalVector[0] = 0.0; m_normalVector[1] = 0.0; m_normalVector[2] = 0.0; m_validPlaneFlag = false; m_A = m_normalVector[0]; m_B = m_normalVector[1]; m_C = m_normalVector[2]; m_D = 0.0; } /** * Construct a plane from three points that are on the plane. * These points should be in counter-clockwise order. * * @param p1 Point on plane. * @param p2 Point on plane. * @param p3 Point on plane. * */ Plane::Plane(const float p1[3], const float p2[3], const float p3[3]) : CaretObject() { m_pointOnPlane[0] = p1[0]; m_pointOnPlane[1] = p1[1]; m_pointOnPlane[2] = p1[2]; double p2d[3] = { p2[0], p2[1], p2[2] }; double p3d[3] = { p3[0], p3[1], p3[2] }; m_validPlaneFlag = MathFunctions::normalVector(m_pointOnPlane, p2d, p3d, m_normalVector); // // Compute the plane equation // m_A = m_normalVector[0]; m_B = m_normalVector[1]; m_C = m_normalVector[2]; m_D = -(m_A*p1[0] + m_B*p1[1] + m_C*p1[2]); } /** * Construct a plane from a unit normal vector (length = 1) and a point on the plane. * * @param normalVector * The normal vector of the plane. * @param pointOnPlane * A point on the plane. */ Plane::Plane(const float unitNormalVector[3], const float pointOnPlane[3]) { m_pointOnPlane[0] = pointOnPlane[0]; m_pointOnPlane[1] = pointOnPlane[1]; m_pointOnPlane[2] = pointOnPlane[2]; m_normalVector[0] = unitNormalVector[0]; m_normalVector[1] = unitNormalVector[1]; m_normalVector[2] = unitNormalVector[2]; m_A = m_normalVector[0]; m_B = m_normalVector[1]; m_C = m_normalVector[2]; m_D = (-m_A * m_pointOnPlane[0] -m_B * m_pointOnPlane[1] -m_C * m_pointOnPlane[2]); m_validPlaneFlag = (MathFunctions::vectorLength(m_normalVector) > 0.0); } /** * Copy constructor. * @param p * Object that is copied. */ Plane::Plane(const Plane& p) : CaretObject(p) { this->copyHelperPlane(p); } /** * Assignment operator. * @param p * Data copied from obj to this. * @return * Reference to this object. */ Plane& Plane::operator=(const Plane& p) { if (this != &p) { CaretObject::operator=(p); this->copyHelperPlane(p); } return *this; } /** * Helps with copying an object of this type. * @param p * Object that is copied. */ void Plane::copyHelperPlane(const Plane& p) { m_A = p.m_A; m_B = p.m_B; m_C = p.m_C; m_D = p.m_D; m_normalVector[0] = p.m_normalVector[0]; m_normalVector[1] = p.m_normalVector[1]; m_normalVector[2] = p.m_normalVector[2]; m_pointOnPlane[0] = p.m_pointOnPlane[0]; m_pointOnPlane[1] = p.m_pointOnPlane[1]; m_pointOnPlane[2] = p.m_pointOnPlane[2]; m_validPlaneFlag = p.m_validPlaneFlag; } /** * Destructor */ Plane::~Plane() { } /** * Is the plane valid? * @return true if plane is valid, else false. * */ bool Plane::isValidPlane() const { return m_validPlaneFlag; } /** * Find the points where a triangle intersects the plane. * @param t1 First point in triangle. * @param t2 Second point in triangle. * @param t3 Third point in triangle. * @param intersectionPointOut1 If there is intersection, * this will be loaded with one intersection point. * @param intersectionPointOut2 If there is intersection, * this will be loaded with other intersection point. * @return true if any two edges of the triangle intersect * the plane, else false. * */ bool Plane::triangleIntersectPlane( const float t1[3], const float t2[3], const float t3[3], float intersectionPointOut1[3], float intersectionPointOut2[3]) const { float* intersection = intersectionPointOut1; int count = 0; if (lineSegmentIntersectPlane(t1, t2, intersection)) { count++; intersection = intersectionPointOut2; } if (lineSegmentIntersectPlane(t2, t3, intersection)) { count++; if (count == 2) { return true; } intersection = intersectionPointOut2; } if (lineSegmentIntersectPlane(t3, t1, intersection)) { count++; if (count == 2) { return true; } } return false; } /** * Get the plane. * * @param aOut * The value of 'A' * @param bOut * The value of 'B' * @param cOut * The value of 'C' * @param dOut * The value of 'D' */ void Plane::getPlane(double& aOut, double& bOut, double& cOut, double& dOut) const { aOut = m_A; bOut = m_B; cOut = m_C; dOut = m_D; } /** * Get the plane's normal vector. * * @param normalVectorOut * On exit, contains the plane's normal vector. */ void Plane::getNormalVector(double normalVectorOut[3]) const { normalVectorOut[0] = m_normalVector[0]; normalVectorOut[1] = m_normalVector[1]; normalVectorOut[2] = m_normalVector[2]; } /** * Get the plane's normal vector. * * @param normalVectorOut * On exit, contains the plane's normal vector. */ void Plane::getNormalVector(float normalVectorOut[3]) const { normalVectorOut[0] = m_normalVector[0]; normalVectorOut[1] = m_normalVector[1]; normalVectorOut[2] = m_normalVector[2]; } /** * Get absolute distance of point from the plane. * @param p Point. * @return Absolute distance of "p" from plane. * */ double Plane::absoluteDistanceToPlane(const float p[3]) const { double dist = (m_A * p[0] + m_B * p[1] + m_C * p[2] + m_D); if (dist < 0.0f) dist = -dist; return dist; } /** * Get signed distance of point from the plane. * @param p Point. * @return Signed distance of "p" from plane. * */ double Plane::signedDistanceToPlane(const float p[3]) const { double dist = (m_A * p[0] + m_B * p[1] + m_C * p[2] + m_D); return dist; } /** * Find the intersection of a line segment with the plane. * @param lp1 start of line. * @param lp2 end of line. * @param intersectionOut If the line intersects the plane, * the point of intersection will be loaded into this. If * there is no intersection, the values of this array will * not be altered. * @return true if the line segment intersects the plane, * else false. * */ bool Plane::lineSegmentIntersectPlane( const float lp1[3], const float lp2[3], float intersectionOut[3]) const { /* * Ray formed by lp1 ==> lp2 (NOT a unit vector; do not normalize) */ double r0 = lp2[0] - lp1[0]; double r1 = lp2[1] - lp1[1]; double r2 = lp2[2] - lp1[2]; /* * Denominator is dot product of the plane's normal * and the ray. If it is zero, that means the ray * is parallel to the plane. * * Perhaps should test for value near zero, and, * if it is, see if line end points are very * close to plane, and, if so, use the line * endpoints, projected to the plane, as the * intersection. */ double denominator = (m_A * r0) + (m_B * r1) + (m_C * r2); if (denominator == 0.0) { return false; } /* * "t" is the normalized distance of plane from the line origin to * the endpoint of the line. * * (0 <= t <= 1): The line intersects the plane with one point * above the plane and one point below the plane. * * (t > 1): Vector (P1, P2) point towards plane but both are * on same side of the plane. * * (t < 0): Vector (P1, P2) point away from plane but both are * on same side of the plane. */ double numerator = -((m_A * lp1[0]) + (m_B * lp1[1]) + (m_C * lp1[2]) + m_D); double t = numerator / denominator; if ((t >= 0.0f) && (t <= 1.0f)) { intersectionOut[0] = (float)(lp1[0] + r0 * t); intersectionOut[1] = (float)(lp1[1] + r1 * t); intersectionOut[2] = (float)(lp1[2] + r2 * t); return true; } return false; } /** * Project the given point to the plane. * @param pointIn * Point that will be projected. * @param pointProjectedOut * Coordinates of point after projection to the plane. */ void Plane::projectPointToPlane(const float pointIn[3], float pointProjectedOut[3]) const { double xo[3] = { pointIn[0] - m_pointOnPlane[0], pointIn[1] - m_pointOnPlane[1], pointIn[2] - m_pointOnPlane[2] }; float t = MathFunctions::dotProduct(m_normalVector, xo); pointProjectedOut[0] = pointIn[0] - (t * m_normalVector[0]); pointProjectedOut[1] = pointIn[1] - (t * m_normalVector[1]); pointProjectedOut[2] = pointIn[2] - (t * m_normalVector[2]); } /** * Determine if and where a ray intersects the plane. * * @param rayOrigin * Origin of the ray * @param rayVector * Vector defining the ray. * @param intersectionXYZandDistance * Coordinate of where the ray intersects the plane (XYZ) and the * distance of the ray origin from the plane. * @return * True if the ray intersects the plane, else false. */ bool Plane::rayIntersection(const float rayOrigin[3], const float rayVector[3], float intersectionXYZandDistance[4]) { /* Convert the ray into a unit vector * */ double ray[3] = { rayVector[0], rayVector[1], rayVector[2] }; MathFunctions::normalizeVector(ray); /* * Parametric coordinate of where ray intersects plane */ double denom = m_A * ray[0] + m_B * ray[1] + m_C * ray[2]; if (denom != 0) { const double t = -(m_A * rayOrigin[0] + m_B * rayOrigin[1] + m_C * rayOrigin[2] + m_D) / denom; intersectionXYZandDistance[0] = (float)(rayOrigin[0] + ray[0] * t); intersectionXYZandDistance[1] = (float)(rayOrigin[1] + ray[1] * t); intersectionXYZandDistance[2] = (float)(rayOrigin[2] + ray[2] * t); intersectionXYZandDistance[3] = (float)t; return true; } return false; } /** * @return String describing the plane. * */ AString Plane::toString() const { AString s; if (isValidPlane()) { s = (AString::number(m_A) + "x + " + AString::number(m_B) + "y + " + AString::number(m_C) + "z + " + AString::number(m_D)); } else { s = "invalid"; } return s; } /** * Unit test the class. * */ void Plane::unitTest(std::ostream& stream, const bool /*isVerbose*/) { stream << "Plane::unitTest is starting" << std::endl; Plane::unitTest1(stream); Plane::unitTest2(stream); Plane::unitTest3(stream); stream << "Plane::unitTest has ended" << std::endl; } /** * Unit test a plane and line intersection. * @param testName Name of test. * @param p1 Plane Point 1. * @param p2 Plane Point 2. * @param p3 Plane Point 3. * @param l1 Line End Point 1. * @param l2 Line End Point 2. * @param correctIntersectionPoint * @param intersectionValid * */ void Plane::unitTestLineIntersectPlane(std::ostream& stream, const AString& testName, const float p1[3], const float p2[3], const float p3[3], const float l1[3], const float l2[3], const float correctIntersectionPoint[3], const bool intersectionValid) { float intersection[3]; Plane p(p1, p2, p3); bool result = p.lineSegmentIntersectPlane(l1, l2, intersection); AString sb; if (intersectionValid && result) { if (MathFunctions::compareArrays(correctIntersectionPoint, intersection, 3, 0.001f)) { return; } sb.append("Intersection should be " + AString::fromNumbers(correctIntersectionPoint, 3, ",")); sb.append(" but is " + AString::fromNumbers(intersection, 3, ",")); } else if (intersectionValid != result) { sb.append("intersection " + AString::fromBool(result) + " but should be " + AString::fromBool(intersectionValid)); } if (sb.length() > 0) { sb = ("Line/Plane Intersection Test " + testName + " FAILED: " + sb); stream << sb.toStdString() << std::endl; } } void Plane::unitTest1(std::ostream& stream) { float p1[] = { -50.0f, -60.0f, 2.0f }; float p2[] = { 50.0f, -60.0f, 2.0f }; float p3[] = { 50.0f, 60.0f, 2.0f }; float l1[] = { 25.0f, 10.0f, -3.0f }; float l2[] = { 25.0f, 10.0f, 7.0f }; float correctIntersectionPoint[] = { 25.0f, 10.0f, 2.0f }; Plane::unitTestLineIntersectPlane(stream, "1", p1, p2, p3, l1, l2, correctIntersectionPoint, true); } void Plane::unitTest2(std::ostream& stream) { float p1[] = { -50.0f, -60.0f, 2.0f }; float p2[] = { 50.0f, -60.0f, 2.0f }; float p3[] = { 50.0f, 60.0f, 2.0f }; float l1[] = { 25.0f, 10.0f, 7.0f }; float l2[] = { 25.0f, 10.0f, 9.0f }; float correctIntersectionPoint[3]; Plane::unitTestLineIntersectPlane(stream, "2", p1, p2, p3, l1, l2, correctIntersectionPoint, false);} void Plane::unitTest3(std::ostream& stream) { float p1[] = { -50.0f, -60.0f, 2.0f }; float p2[] = { 50.0f, -60.0f, 2.0f }; float p3[] = { 50.0f, 60.0f, 2.0f }; float l1[] = { 25.0f, 10.0f, -5.0f }; float l2[] = { 25.0f, 10.0f, -3.0f }; float correctIntersectionPoint[3]; Plane::unitTestLineIntersectPlane(stream, "3", p1, p2, p3, l1, l2, correctIntersectionPoint, false); } workbench-1.1.1/src/Common/Plane.h000066400000000000000000000077451255417355300167520ustar00rootroot00000000000000#ifndef __PLANE_H__ #define __PLANE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" namespace caret { ///Operations on a plane. class Plane : public CaretObject { public: Plane(); Plane(const float p1[3], const float p2[3], const float p3[3]); Plane(const float unitNormalVector[3], const float pointOnPlane[3]); virtual ~Plane(); Plane(const Plane& p); Plane& operator=(const Plane& p); bool isValidPlane() const; bool triangleIntersectPlane( const float t1[3], const float t2[3], const float t3[3], float intersectionPointOut1[3], float intersectionPointOut2[3]) const; double absoluteDistanceToPlane(const float p[3]) const; double signedDistanceToPlane(const float p[3]) const; bool lineSegmentIntersectPlane( const float lp1[3], const float lp2[3], float intersectionOut[3]) const; void projectPointToPlane(const float pointIn[3], float pointProjectedOut[3]) const; void getPlane(double& aOut, double& bOut, double& cOut, double& dOut) const; void getNormalVector(double normalVectorOut[3]) const; void getNormalVector(float normalVectorOut[3]) const; bool rayIntersection(const float rayOrigin[3], const float rayVector[3], float intersectionXYZandDistance[4]); virtual AString toString() const; static void unitTest(std::ostream& stream, const bool isVerbose); private: static void unitTestLineIntersectPlane(std::ostream& stream, const AString& testName, const float p1[3], const float p2[3], const float p3[3], const float l1[3], const float l2[3], const float correctIntersectionPoint[3], const bool intersectionValid); static void unitTest1(std::ostream& stream); static void unitTest2(std::ostream& stream); static void unitTest3(std::ostream& stream); void copyHelperPlane(const Plane& p); double m_pointOnPlane[3]; double m_normalVector[3]; bool m_validPlaneFlag; double m_A; double m_B; double m_C; double m_D; }; } // namespace #endif // __PLANE_H__ workbench-1.1.1/src/Common/ProgramParameters.cxx000066400000000000000000000215271255417355300217130ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretAssert.h" #include "ProgramParameters.h" using namespace caret; /** * Constructor. * * @param argc * Number of parameters. * @param argv * Array containing the parameters. */ ProgramParameters::ProgramParameters(int argc, const char *const argv[]) : CaretObject() { this->initializeMembersProgramParameters(); if (argc > 0) { this->programName = argv[0]; } for (int32_t i = 1; i < argc; i++) { this->addParameter(argv[i]); } } /** * Constructor. */ ProgramParameters::ProgramParameters() : CaretObject() { this->initializeMembersProgramParameters(); } /** * Destructor */ ProgramParameters::~ProgramParameters() { this->initializeMembersProgramParameters(); } void ProgramParameters::initializeMembersProgramParameters() { this->parameterIndex = 0; this->parameters.clear(); } /** * Add a parameter. * @param p New parameter. * */ void ProgramParameters::addParameter(const AString& p) { this->parameters.push_back(p); } /** * See if there are more parameters available. * * @return true if more parameters available else false. * */ bool ProgramParameters::hasNext() const { return (this->parameterIndex < static_cast(this->parameters.size())); } /** * Verifies that all parameters have been processed. * * @throws ProgramParametersException If there are more parameters. * */ void ProgramParameters::verifyAllParametersProcessed() { if (this->hasNext()) { throw ProgramParametersException("Unexpected parameter: " + nextString("unexpected")); } } /** * Get the next parameter as a string. * * @param parameterName - name of the parameter. * @return The parameter. * @throws ProgramParametersException If there are no more parameters. * */ AString ProgramParameters::nextString(const AString& parameterName) { if (this->hasNext() == false) { throw ProgramParametersException(parameterName + " is missing (No more parameters)."); } CaretAssertVectorIndex(this->parameters, this->parameterIndex); AString s = this->parameters[this->parameterIndex]; this->parameterIndex++; return s; } /** * Get the next parameter as a boolean * * @param parameterName - name of the parameter. * @return boolean value of parameter. * * @throws ProgramParametersException If there are no more parameters or the * next parameter does not represent a boolean value. * */ bool ProgramParameters::nextBoolean(const AString& parameterName) { AString s = this->nextString(parameterName).toLower(); if ((s == "true") || (s == "t")) { return true; } else if ((s == "false") || (s == "f")) { return false; } throw ProgramParametersException(parameterName + " is not a boolean value (true/false) but is \"" + s + "\"."); return false; } /** * Get the next parameter as a int. * * @param parameterName - name of the parameter. * @return int value of parameter. * @throws ProgramParametersException If there are no more parameters or the * next parameter does not represent an integer value. * */ int32_t ProgramParameters::nextInt(const AString& parameterName) { AString s = this->nextString(parameterName); bool ok = false; int32_t i = s.toInt(&ok); if (!ok) { throw ProgramParametersException(parameterName + " needs an integer, got \"" + s + "\"."); } return i; } /** * Get the next parameter as a long. * * @param parameterName - name of the parameter. * @return inlongt value of parameter. * @throws ProgramParametersException If there are no more parameters or the * next parameter does not represent an integer value. * */ int64_t ProgramParameters::nextLong(const AString& parameterName) { AString s = this->nextString(parameterName); bool ok = false; int64_t i = s.toLong(&ok); if (!ok) { throw ProgramParametersException(parameterName + " needs an integer, got \"" + s + "\"."); } return i; } /** * Get the next parameter as a float. * * @param parameterName - name of the parameter. * @return float value of parameter. * @throws ProgramParametersException If there are no more parameters or the * next parameter does not represent an float value. * */ float ProgramParameters::nextFloat(const AString& parameterName) { AString s = this->nextString(parameterName); bool ok = false; float f = s.toFloat(&ok); if (!ok) { throw ProgramParametersException(parameterName + " needs a floating point, got \"" + s + "\"."); } return f; } /** * Get the next parameter as a double. * * @param parameterName - name of the parameter. * @return double value of parameter. * @throws ProgramParametersException If there are no more parameters or the * next parameter does not represent an double value. * */ double ProgramParameters::nextDouble(const AString& parameterName) { AString s = this->nextString(parameterName); bool ok = false; double d = s.toDouble(&ok); if (!ok) { throw ProgramParametersException(parameterName + " needs a floating point, got \"" + s + "\"."); } return d; } /** * Backup to the previous parameter. If currently at the first * parameter, this command does nothing. * */ void ProgramParameters::backup() { if (this->parameterIndex > 0) { this->parameterIndex--; } } /** * Remove the parameter that was last retrieved with one of the * "next()" methods. */ void ProgramParameters::remove() { this->backup(); CaretAssertVectorIndex(this->parameters, this->parameterIndex); this->parameters.erase(this->parameters.begin() + this->parameterIndex); } /** * Get the current index of the parameter iterator. * @return Index of parameter iterator. * */ int32_t ProgramParameters::getParameterIndex() const { return this->parameterIndex; } /** * Set the current index of the parameter iterator. * @param indx New index of parameter iterator. * @throws ParameterException If index is not valid for parameters * unless the index is zero and there are no parameters. * */ void ProgramParameters::setParameterIndex(const int32_t indx) { CaretAssert(indx >= 0 && indx <= (int32_t)this->parameters.size());//less or equal to size is intentional, having an index of one past the end is a valid state, notably needed by setParameterIndex(0) on empty ProgramParameters this->parameterIndex = indx; } /** * Get the number of parameters. * * @return The number of parameters. * */ int32_t ProgramParameters::getNumberOfParameters() const { return this->parameters.size(); } /** * Get a parameter at the specified index. * * @param index - index of parameter. * @return The parameter at the specified index. * */ AString ProgramParameters::getParameter(const int32_t indx) const { CaretAssertVectorIndex(this->parameters, indx); return this->parameters[indx]; } /** * Get all of the parameters, separated by a space. * @return All parameters in a String. * */ AString ProgramParameters::getAllParametersInString() const { AString str; int num = this->getNumberOfParameters(); for (int i = 0; i < num; i++) { if (i > 0) { str += " "; } str += this->getParameter(i); } return str; } /** * Get all of the parameters, separated by a space. * @return All parameters in a String. * */ AString ProgramParameters::getAllParametersQuotedInString() const { AString str; int num = this->getNumberOfParameters(); for (int i = 0; i < num; i++) { if (i > 0) { str += " "; } str += "\"" + this->getParameter(i) + "\""; } return str; } /** * Get the name of the program. * @return * Name of the program. */ AString ProgramParameters::getProgramName() const { return this->programName; } workbench-1.1.1/src/Common/ProgramParameters.h000066400000000000000000000046641255417355300213430ustar00rootroot00000000000000#ifndef __PROGRAMPARAMETERS_H__ #define __PROGRAMPARAMETERS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "ProgramParametersException.h" #include #include #include namespace caret { /** * Simplifies access to program parameters. */ class ProgramParameters : public CaretObject { public: ProgramParameters(int argc, const char *const argv[]); ProgramParameters(); virtual ~ProgramParameters(); private: ProgramParameters(const ProgramParameters&); ProgramParameters& operator=(const ProgramParameters&); private: void initializeMembersProgramParameters(); public: void addParameter(const AString& p); bool hasNext() const; void verifyAllParametersProcessed(); AString nextString(const AString& parameterName); bool nextBoolean(const AString& parameterName); int32_t nextInt(const AString& parameterName); int64_t nextLong(const AString& parameterName); float nextFloat(const AString& parameterName); double nextDouble(const AString& parameterName); void backup(); void remove(); int32_t getParameterIndex() const; void setParameterIndex(const int32_t index); int32_t getNumberOfParameters() const; AString getParameter(const int32_t index) const; AString getAllParametersInString() const; AString getAllParametersQuotedInString() const; AString getProgramName() const; private: /**The parameters. */ std::vector parameters; /**Current index in parameters. */ int32_t parameterIndex; AString programName; }; } // namespace #endif // __PROGRAMPARAMETERS_H__ workbench-1.1.1/src/Common/ProgramParametersException.cxx000066400000000000000000000045031255417355300235650ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "ProgramParametersException.h" #include using namespace caret; /** * Constructor. * */ ProgramParametersException::ProgramParametersException() : CaretException() { this->initializeMembersProgramParametersException(); } /** * Constructor that uses stack trace from the exception * passed in as a parameter. * * @param e Any exception whose stack trace becomes * this exception's stack trace. * */ ProgramParametersException::ProgramParametersException( const CaretException& e) : CaretException(e) { this->initializeMembersProgramParametersException(); } /** * Constructor. * * @param s Description of the exception. * */ ProgramParametersException::ProgramParametersException(const AString& s) : CaretException(s) { this->initializeMembersProgramParametersException(); } /** * Copy Constructor. * @param e * Exception that is copied. */ ProgramParametersException::ProgramParametersException(const ProgramParametersException& e) : CaretException(e) { } /** * Assignment operator. * @param e * Exception that is copied. * @return * Copy of the exception. */ ProgramParametersException& ProgramParametersException::operator=(const ProgramParametersException& e) { if (this != &e) { CaretException::operator=(e); } return *this; } /** * Destructor */ ProgramParametersException::~ProgramParametersException() throw() { } void ProgramParametersException::initializeMembersProgramParametersException() { } workbench-1.1.1/src/Common/ProgramParametersException.h000066400000000000000000000031501255417355300232070ustar00rootroot00000000000000#ifndef __PROGRAM_PARAMETERS_EXCEPTION_H__ #define __PROGRAM_PARAMETERS_EXCEPTION_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "CaretException.h" namespace caret { /** * An exception thrown during parameter processing. */ class ProgramParametersException : public CaretException { public: ProgramParametersException(); ProgramParametersException(const CaretException& e); ProgramParametersException(const AString& s); ProgramParametersException(const ProgramParametersException& e); ProgramParametersException& operator=(const ProgramParametersException& e); virtual ~ProgramParametersException() throw(); private: void initializeMembersProgramParametersException(); }; } // namespace #endif // __PROGRAM_PARAMETERS_EXCEPTION_H__ workbench-1.1.1/src/Common/ProgressObject.cxx000066400000000000000000000202121255417355300212010ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "ProgressObject.h" #include "CaretAssert.h" #include "EventProgressUpdate.h" #include "EventManager.h" #include using namespace std; using namespace caret; ///decrease these values to reduce progress bar overhead everywhere ///or, manually set them for any fast algorithm that calls reportProgress a lot (or change it so it calls reportProgress less often) const float ProgressObject::MAX_CHILD_RESOLUTION = 0.01f;//up to 100 calls per child algorithm const float ProgressObject::MAX_INTERNAL_RESOLUTION = 0.001f;//up to 1000 calls during internal processing ProgressObject* ProgressObject::addAlgorithm(const float weight, const float childResolution) { CaretAssertMessage(weight > 0.0f, "nonpositive weight in ProgressObject::addAlgorithm"); if (m_disabled) return this;//disabled short circuits everything, can't track progress if an algorithm ignores and forwards the pointer ProgressInfo newInfo; newInfo.completed = false; newInfo.curProgress = 0.0f; newInfo.weight = weight; newInfo.progObjRef = new ProgressObject(weight, childResolution); newInfo.progObjRef->m_parent = this; newInfo.progObjRef->m_parentIndex = m_children.size(); m_children.push_back(newInfo); float childWeight = 0.0f; vector::iterator myend = m_children.end(); for (vector::iterator iter = m_children.begin(); iter != myend; ++iter) { childWeight += iter->weight; } m_totalWeight = childWeight + m_nonChildWeight; return newInfo.progObjRef; } void ProgressObject::algorithmStartSentinel() { if (m_sentinelPassed) { m_disabled = true;//if it hits start twice (passed through an algorithm without interaction), disable it } else { m_sentinelPassed = true; } } void ProgressObject::finishLevel() { if (m_finished) return;//don't finish twice m_currentProgress = m_totalWeight; m_finished = true; if (m_parent != NULL) { m_parent->m_children[m_parentIndex].completed = true; m_parent->updateProgress(); } EventProgressUpdate myUpdate(this); myUpdate.m_finished = true; EventManager::get()->sendEvent(myUpdate.getPointer()); } void ProgressObject::forceFinish() { finishLevel();//this function is mostly to insert the word "force" so people think twice about using it } float ProgressObject::getCurrentProgressFraction() { if (m_totalWeight <= 0.0f) return 0.0f; return m_currentProgress / m_totalWeight; } float ProgressObject::getCurrentProgressPercent() { return getCurrentProgressFraction() * 100.0f; } const AString& ProgressObject::getTaskDescription() { return m_description; } ProgressObject::ProgressObject(const float weight, const float childResolution) { m_currentProgress = 0.0f; m_disabled = false; m_finished = false; m_lastReported = 0.0f; m_nonChildProgress = 0.0f; m_nonChildWeight = weight; m_parent = NULL; m_sentinelPassed = false; m_totalWeight = weight; m_childResolution = childResolution; } LevelProgress::LevelProgress(ProgressObject* myProgObj, const float finishedProgress, const float internalWeight, const float internalResolution) { CaretAssertMessage(internalWeight > 0.0f, "nonpositive weight in ProgressObject::startLevel"); m_lastReported = 0.0f; m_maximum = finishedProgress; m_progObjRef = myProgObj; m_internalResolution = max(internalResolution, ProgressObject::MAX_INTERNAL_RESOLUTION);//the lower the value, the more often it updates if (m_progObjRef != NULL) { m_progObjRef->setInternalWeight(internalWeight); EventProgressUpdate myUpdate(myProgObj); myUpdate.m_starting = true; EventManager::get()->sendEvent(myUpdate.getPointer()); } } void ProgressObject::setInternalWeight(const float& myInternalWeight) { m_nonChildWeight = myInternalWeight; float childWeight = 0.0f; vector::iterator myend = m_children.end(); for (vector::iterator iter = m_children.begin(); iter != myend; ++iter) { childWeight += iter->weight; } m_totalWeight = childWeight + m_nonChildWeight; } void ProgressObject::updateProgress() { if (m_disabled) return; if (m_finished) { return;//nothing to do, finishLevel() should have taken care of everything } float totalWeightComplete = 0.0f;//recalculating the sum is thread-safe, as long as float assignment is atomic vector::iterator myend = m_children.end(); for (vector::iterator iter = m_children.end(); iter < myend; ++iter) { if (iter->completed) { totalWeightComplete += iter->weight; } else { totalWeightComplete += iter->curProgress * iter->weight; } } if (m_nonChildProgress > 0.0f && m_nonChildWeight > 0.0f) { totalWeightComplete += m_nonChildProgress * m_nonChildWeight; } if (totalWeightComplete < m_totalWeight) { if (totalWeightComplete < 0.0f) { m_currentProgress = 0.0f; } else { m_currentProgress = totalWeightComplete / m_totalWeight; } } else { m_currentProgress = 1.0f; } if (m_parent != NULL) { m_parent->m_children[m_parentIndex].curProgress = m_currentProgress; if (m_currentProgress - m_lastReported > m_childResolution) {//don't recurse unless progress has changed more than the resolution specified m_lastReported = m_currentProgress; m_parent->updateProgress(); } } EventProgressUpdate myUpdate(this);//just send the event, LevelProgress should already have checked if the amount of change was significant myUpdate.m_amountUpdate = true; EventManager::get()->sendEvent(myUpdate.getPointer()); } bool ProgressObject::isDisabled() { return m_disabled; } ProgressObject::~ProgressObject() { finishLevel();//so that things listening for progress events are kept consistent vector::iterator myend = m_children.end(); for (vector::iterator iter = m_children.begin(); iter != myend; ++iter) { if (iter->progObjRef != NULL) { delete iter->progObjRef; iter->progObjRef = NULL; } } } void LevelProgress::reportProgress(const float currentTotal) { if (m_progObjRef == NULL || m_progObjRef->m_disabled) return; float curProgress = currentTotal / m_maximum; if (curProgress > 1.0f) { curProgress = 1.0f; } if (curProgress < m_lastReported) { curProgress = m_lastReported; } m_progObjRef->m_nonChildProgress = curProgress; if (curProgress - m_lastReported > m_internalResolution) { m_lastReported = curProgress; m_progObjRef->updateProgress(); } } void LevelProgress::setTask(const AString& taskDescription) {//maybe this should be in a setter in m_progObjRef, here for coherence with progress reporting if (m_progObjRef == NULL) return; m_progObjRef->m_description = taskDescription; EventProgressUpdate myUpdate(m_progObjRef); myUpdate.m_textUpdate = true; EventManager::get()->sendEvent(myUpdate.getPointer()); } LevelProgress::~LevelProgress() { if (m_progObjRef == NULL) return; m_progObjRef->finishLevel();//finish level on destruction of the object, for automatic detection of algorithm finishing } workbench-1.1.1/src/Common/ProgressObject.h000066400000000000000000000121161255417355300206320ustar00rootroot00000000000000#ifndef __PROGRESS_OBJECT_H__ #define __PROGRESS_OBJECT_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "stdint.h" #include #include "AString.h" namespace caret { class LevelProgress; //NOTE: this tries to intelligently avoid doing recursive progress updates when the value doesn't change much class ProgressObject { struct ProgressInfo { ProgressObject* progObjRef;//used to clean up the memory on finish() float weight; float curProgress; bool completed; }; std::vector m_children; float m_totalWeight; float m_nonChildWeight; float m_nonChildProgress; float m_currentProgress; float m_lastReported; float m_childResolution; AString m_description; ProgressObject* m_parent; int32_t m_parentIndex;//which index in parent's vector this object is bool m_sentinelPassed; bool m_disabled;//disables itself if sentinel called twice bool m_finished; void updateProgress();//used by LevelProgress to report changes void finishLevel();//moves this progress object to 100%, then updates parent if not NULL void setInternalWeight(const float& myInternalWeight);//used by LevelProgress when you start a level ProgressObject(); public: //don't always report progress, in case someone uses this in an inner loop const static float MAX_CHILD_RESOLUTION; const static float MAX_INTERNAL_RESOLUTION; ///fill in weight with the ...Algorithm::getAlgorithmWeight() function at the root level (before starting the algorithm) ///if using multiple algorithms at the root level (shame!), sum their weights first, then use addAlgorithm to get objects for each ProgressObject(const float weight, const float childResolution = MAX_CHILD_RESOLUTION); ~ProgressObject(); ///call this on progress objects you make after an algorithm returns to ensure it finishes (in case it ignores the object) void forceFinish(); ///add an algorithm to this algorithm's progress status, and get a pointer to give to that algorithm ///fill in weight with the ...Algorithm::getAlgorithmWeight() function ProgressObject* addAlgorithm(const float weight, const float childResolution = MAX_CHILD_RESOLUTION); ///DO NOT USE: used by AbstractAlgorithm constructor to check for algorithms that ignore the object void algorithmStartSentinel(); ///get progress as a fraction of 1 (in range [0, 1]) float getCurrentProgressFraction(); ///get progress as percent (in range [0, 100]) float getCurrentProgressPercent(); ///get the description of the current task const AString& getTaskDescription(); ///true if algorithmStartSentinel disabled the object bool isDisabled(); //TODO: make something to return the statuses of all in-progress (nonzero curProgress) tasks for the entire tree, for detailed progress info //TODO: set up callbacks so progress changes don't have to be polled for friend class LevelProgress;//so that LevelProgress can report progress, but nothing else can }; class LevelProgress {//reports progress on processing done in this level float m_maximum; float m_lastReported; float m_internalResolution; ProgressObject* m_progObjRef; LevelProgress(); public: LevelProgress(ProgressObject* myProgObj, const float finishedProgress = 1.0f, const float internalWeight = 1.0f, const float internalResolution = ProgressObject::MAX_INTERNAL_RESOLUTION); ///call with the fraction of finishedProgress passed to ProgressObject::startLevel (default 1.0) that this algorithm has done internally ///work done by subalgorithms is automatically added and updated as progress is made, you do not need to call this unless the current algorithm does direct processing void reportProgress(const float currentTotal); ///set a description for current task, like the name of the subalgorithm you are about to call void setTask(const AString& taskDescription);//yes, this reaches through the class, but it is better to have both reporting functions on the same object ~LevelProgress();//automatically finishes level }; } #endif //__PROGRESS_OBJECT_H__ workbench-1.1.1/src/Common/ProgressReportingInterface.h000066400000000000000000000071671255417355300232300ustar00rootroot00000000000000#ifndef __PROGRESS_REPORTING_INTERFACE_H__ #define __PROGRESS_REPORTING_INTERFACE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ /** * \class caret::ProgressReportingInterface * \brief Interface for any process that may display progress in the GUI * * This interface is intended for any task that takes a non-trivial * amount of time to run. It allows display of the task's progress, * usually in the user-interface's progress dialog and provides feedback * to the user about how much work has been and still needs to be completed. * The interface also provides a mechanism for the user to cancel the * task and the task to detect that the user has requested the task * be cancelled. */ #include "AString.h" namespace caret { class ProgressReportingInterface { public: ProgressReportingInterface() { /* nothing */ } virtual ~ProgressReportingInterface() { /* nothing */ } /** * Used by task to set the range of progress reporting. * * @param minimumProgress * The minimum amount reported (typically zero). * @param maximumProgress * The maximum amount of progress. */ virtual void setProgressRange(const int minimumProgress, const int maximumProgress) = 0; /** * Used by task to set the current progress. * * @param progress * The current progress within range of the minimum and maximum. */ virtual void setProgressValue(const int progress) = 0; /** * Used by task to set the message describing the task's activity. * * @param message * Message that is displayed. */ virtual void setProgressMessage(const AString& message) = 0; /** * Used by the user-interface to request that the task end as * soon as possible. */ virtual void setCancelRequested() = 0; /** * @return "true" if a request been made to cancel the task, else false. * * Used by the task to see if the task should end as soon as * possible. If so, the task should clean up after itself * (release resources); */ virtual bool isCancelRequested() const = 0; private: ProgressReportingInterface(const ProgressReportingInterface&); ProgressReportingInterface& operator=(const ProgressReportingInterface&); public: // ADD_NEW_METHODS_HERE private: // ADD_NEW_MEMBERS_HERE }; #ifdef __PROGRESS_REPORTING_INTERFACE_DECLARE__ // #endif // __PROGRESS_REPORTING_INTERFACE_DECLARE__ } // namespace #endif //__PROGRESS_REPORTING_INTERFACE_H__ workbench-1.1.1/src/Common/ReductionEnum.cxx000066400000000000000000000133371255417355300210410ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "ReductionEnum.h" #include "CaretAssert.h" using namespace caret; using namespace std; vector ReductionEnum::enumData; bool ReductionEnum::initializedFlag = false; /** * Constructor. * * @param enumValue * An enumerated value. * @param integerCode * Integer code for this enumerated value. * * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ ReductionEnum::ReductionEnum(const Enum enumValue, const AString& name, const AString& explanation) { this->enumValue = enumValue; this->name = name; this->explanation = explanation; } /** * Destructor. */ ReductionEnum::~ReductionEnum() { } /** * Initialize the enumerated metadata. */ void ReductionEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(ReductionEnum(MAX, "MAX", "the maximum value")); enumData.push_back(ReductionEnum(MIN, "MIN", "the minimum value")); enumData.push_back(ReductionEnum(INDEXMAX, "INDEXMAX", "the 1-based index of the maximum value")); enumData.push_back(ReductionEnum(INDEXMIN, "INDEXMIN", "the 1-based index of the minimum value")); enumData.push_back(ReductionEnum(SUM, "SUM", "add all values")); enumData.push_back(ReductionEnum(PRODUCT, "PRODUCT", "multiply all values")); enumData.push_back(ReductionEnum(MEAN, "MEAN", "the mean of the data")); enumData.push_back(ReductionEnum(STDEV, "STDEV", "the standard deviation (N denominator)")); enumData.push_back(ReductionEnum(SAMPSTDEV, "SAMPSTDEV", "the sample standard deviation (N-1 denominator)")); enumData.push_back(ReductionEnum(VARIANCE, "VARIANCE", "the variance of the data")); enumData.push_back(ReductionEnum(MEDIAN, "MEDIAN", "the median of the data")); enumData.push_back(ReductionEnum(MODE, "MODE", "the mode of the data")); enumData.push_back(ReductionEnum(COUNT_NONZERO, "COUNT_NONZERO", "the number of nonzero elements in the data")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const ReductionEnum* ReductionEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const ReductionEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString ReductionEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const ReductionEnum* enumInstance = findData(enumValue); if (enumInstance == NULL) return ""; return enumInstance->name; } /** * Get a string explaining the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString ReductionEnum::toExplanation(Enum enumValue) { if (initializedFlag == false) initialize(); const ReductionEnum* enumInstance = findData(enumValue); if (enumInstance == NULL) return ""; return enumInstance->explanation; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ ReductionEnum::Enum ReductionEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = INVALID; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ReductionEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type ReductionEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values * except ALL. */ void ReductionEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } workbench-1.1.1/src/Common/ReductionEnum.h000066400000000000000000000047721255417355300204710ustar00rootroot00000000000000#ifndef __REDUCTION_ENUM_H__ #define __REDUCTION_ENUM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { /** * \brief Enumerated type for a structure in a brain. * * Enumerated types for reduction operators. */ class ReductionEnum { public: /** * Enumerated values. */ enum Enum { INVALID, MAX, MIN, INDEXMAX, INDEXMIN, SUM, MEAN, STDEV, SAMPSTDEV, VARIANCE, PRODUCT, MEDIAN, MODE, COUNT_NONZERO }; ~ReductionEnum(); static AString toName(Enum enumValue); static AString toExplanation(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static void getAllEnums(std::vector& allEnums); private: ReductionEnum(const Enum enumValue, const AString& name, const AString& explanation); static const ReductionEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** The enumerated type value for an instance */ Enum enumValue; /** The name, a text string that is identical to the enumerated value */ AString name; /** An explanation of the function */ AString explanation; }; } // namespace #endif //__REDUCTION_ENUM_H__ workbench-1.1.1/src/Common/ReductionOperation.cxx000066400000000000000000000173761255417355300221040ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "ReductionOperation.h" #include "CaretAssert.h" #include "CaretException.h" #include "MathFunctions.h" #include #include #include using namespace caret; using namespace std; float ReductionOperation::reduce(const float* data, const int64_t& numElems, const ReductionEnum::Enum& type) { CaretAssert(numElems > 0); switch (type) { case ReductionEnum::INVALID: throw CaretException("reduction requested with INVALID operator"); case ReductionEnum::SAMPSTDEV://all of these start by taking the average, for stability if (numElems < 2) throw CaretException("SAMPSTDEV reduction would require dividing by zero"); case ReductionEnum::MEAN: case ReductionEnum::STDEV: case ReductionEnum::VARIANCE: case ReductionEnum::SUM: { double sum = 0.0; for (int64_t i = 0; i < numElems; ++i) sum += data[i]; switch (type) { case ReductionEnum::SUM: return sum; case ReductionEnum::MEAN: return sum / numElems; default: { float mean = sum / numElems; double residsqr = 0.0; for (int64_t i = 0; i < numElems; ++i) { float tempf = data[i] - mean; residsqr += tempf * tempf; } switch(type) { case ReductionEnum::STDEV: return sqrt(residsqr / numElems); case ReductionEnum::SAMPSTDEV: return sqrt(residsqr / (numElems - 1)); case ReductionEnum::VARIANCE: return residsqr / numElems; default: CaretAssertMessage(0, "unhandled type in sum-based reduction"); return 0.0f; } } } } case ReductionEnum::PRODUCT: { double prod = 1.0; for (int64_t i = 0; i < numElems; ++i) prod *= data[i]; return prod; } case ReductionEnum::MAX: { float max = data[0]; for (int64_t i = 1; i < numElems; ++i) if (data[i] > max) max = data[i]; return max; } case ReductionEnum::MIN: { float min = data[0]; for (int64_t i = 1; i < numElems; ++i) if (data[i] < min) min = data[i]; return min; } case ReductionEnum::INDEXMAX: { float max = data[0]; int64_t index = 0; for (int64_t i = 1; i < numElems; ++i) { if (data[i] > max) { max = data[i]; index = i; } } return index + 1;//1-based, to match gui and column arguments } case ReductionEnum::INDEXMIN: { float min = data[0]; int64_t index = 0; for (int64_t i = 1; i < numElems; ++i) { if (data[i] < min) { min = data[i]; index = i; } } return index + 1; } case ReductionEnum::MEDIAN: { vector dataCopy(numElems); for (int64_t i = 0; i < numElems; ++i) dataCopy[i] = data[i]; sort(dataCopy.begin(), dataCopy.end()); if ((numElems & 1) == 0)//if even, average middle two { return (dataCopy[numElems / 2 - 1] + dataCopy[numElems / 2]) / 2.0f; } else { return dataCopy[numElems / 2];//otherwise, take the center } } case ReductionEnum::MODE: { vector dataCopy(numElems); for (int64_t i = 0; i < numElems; ++i) dataCopy[i] = data[i]; sort(dataCopy.begin(), dataCopy.end());//sort to put same-value next to each other, a hash based map could be faster for large arrays, but oh well int bestCount = 0, curCount = 1; float bestval = -1.0f, curval = dataCopy[0]; for (int64_t i = 1; i < numElems; ++i)//search for largest contiguous region { if (dataCopy[i] == curval) { ++curCount; } else { if (curCount > bestCount) { bestval = curval; bestCount = curCount; } curval = dataCopy[i]; curCount = 1; } } if (curCount > bestCount) { bestval = curval; bestCount = curCount; } return bestval; } case ReductionEnum::COUNT_NONZERO: { int64_t count = 0; for (int64_t i = 0; i < numElems; ++i) { if (data[i] != 0.0f) { ++count; } } return count; } } return 0.0f; } float ReductionOperation::reduceExcludeDev(const float* data, const int64_t& numElems, const ReductionEnum::Enum& type, const float& numDevBelow, const float& numDevAbove) { CaretAssert(numElems > 0); double sum = 0.0; int64_t validNum = 0; for (int64_t i = 0; i < numElems; ++i) { if (MathFunctions::isNumeric(data[i])) { ++validNum; sum += data[i]; } } if (validNum == 0) throw CaretException("all input values to reduceExcludeDev were non-numeric"); float mean = sum / validNum; double residsqr = 0.0; for (int64_t i = 0; i < numElems; ++i) { if (MathFunctions::isNumeric(data[i])) { float tempf = data[i] - mean; residsqr += tempf * tempf; } } float stdev = sqrt(residsqr / validNum); float low = mean - numDevBelow * stdev, high = mean + numDevAbove * stdev; vector excluded; excluded.reserve(validNum); for (int64_t i = 0; i < numElems; ++i) { if (MathFunctions::isNumeric(data[i]) && data[i] >= low && data[i] <= high) excluded.push_back(data[i]); } if (excluded.size() == 0) throw CaretException("exclusion parameters to reduceExcludeDev resulted in no usable data"); return reduce(excluded.data(), excluded.size(), type); } AString ReductionOperation::getHelpInfo() { AString ret; vector myEnums; ReductionEnum::getAllEnums(myEnums); int numEnums = (int)myEnums.size(); for (int i = 0; i < numEnums; ++i) { ret += ReductionEnum::toName(myEnums[i]) + ": " + ReductionEnum::toExplanation(myEnums[i]) + "\n"; } return ret; } workbench-1.1.1/src/Common/ReductionOperation.h000066400000000000000000000026651255417355300215240ustar00rootroot00000000000000#ifndef __REDUCTION_OPERATION_H__ #define __REDUCTION_OPERATION_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AString.h" #include "ReductionEnum.h" namespace caret { class ReductionOperation { public: static float reduce(const float* data, const int64_t& numElems, const ReductionEnum::Enum& type); ///reduce, with exclusion based on number of standard deviations static float reduceExcludeDev(const float* data, const int64_t& numElems, const ReductionEnum::Enum& type, const float& numDevBelow, const float& numDevAbove); static AString getHelpInfo(); }; } #endif //__REDUCTION_OPERATION_H__ workbench-1.1.1/src/Common/SpecFileDialogViewFilesTypeEnum.cxx000066400000000000000000000273411255417355300243770ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __SPEC_FILE_DIALOG_VIEW_FILES_TYPE_ENUM_DECLARE__ #include "SpecFileDialogViewFilesTypeEnum.h" #undef __SPEC_FILE_DIALOG_VIEW_FILES_TYPE_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::SpecFileDialogViewFilesTypeEnum * \brief Enumerated type for view files selection in Spec File Dialog * * Using this enumerated type in the GUI with an EnumComboBoxTemplate * * Header File (.h) * Forward declare the data type: * class EnumComboBoxTemplate; * * Declare the member: * EnumComboBoxTemplate* m_specFileDialogViewFilesTypeEnumComboBox; * * Declare a slot that is called when user changes selection * private slots: * void specFileDialogViewFilesTypeEnumComboBoxItemActivated(); * * Implementation File (.cxx) * Include the header files * #include "EnumComboBoxTemplate.h" * #include "SpecFileDialogViewFilesTypeEnum.h" * * Instatiate: * m_specFileDialogViewFilesTypeEnumComboBox = new EnumComboBoxTemplate(this); * m_specFileDialogViewFilesTypeEnumComboBox->setup(); * * Get notified when the user changes the selection: * QObject::connect(m_specFileDialogViewFilesTypeEnumComboBox, SIGNAL(itemActivated()), * this, SLOT(specFileDialogViewFilesTypeEnumComboBoxItemActivated())); * * Update the selection: * m_specFileDialogViewFilesTypeEnumComboBox->setSelectedItem(NEW_VALUE); * * Read the selection: * const SpecFileDialogViewFilesTypeEnum::Enum VARIABLE = m_specFileDialogViewFilesTypeEnumComboBox->getSelectedItem(); * */ /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ SpecFileDialogViewFilesTypeEnum::SpecFileDialogViewFilesTypeEnum(const Enum enumValue, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCodeCounter++; this->name = name; this->guiName = guiName; } /** * Destructor. */ SpecFileDialogViewFilesTypeEnum::~SpecFileDialogViewFilesTypeEnum() { } /** * Initialize the enumerated metadata. */ void SpecFileDialogViewFilesTypeEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(SpecFileDialogViewFilesTypeEnum(VIEW_FILES_ALL, "VIEW_FILES_ALL", "All")); enumData.push_back(SpecFileDialogViewFilesTypeEnum(VIEW_FILES_LOADED, "VIEW_FILES_LOADED", "Loaded")); enumData.push_back(SpecFileDialogViewFilesTypeEnum(VIEW_FILES_LOADED_MODIFIED, "VIEW_FILES_LOADED_MODIFIED", "Loaded:Modified")); enumData.push_back(SpecFileDialogViewFilesTypeEnum(VIEW_FILES_LOADED_NOT_MODIFIED, "VIEW_FILES_LOADED_NOT_MODIFIED", "Loaded:Not Modified")); enumData.push_back(SpecFileDialogViewFilesTypeEnum(VIEW_FILES_NOT_LOADED, "VIEW_FILES_NOT_LOADED", "Not Loaded")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const SpecFileDialogViewFilesTypeEnum* SpecFileDialogViewFilesTypeEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const SpecFileDialogViewFilesTypeEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString SpecFileDialogViewFilesTypeEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const SpecFileDialogViewFilesTypeEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ SpecFileDialogViewFilesTypeEnum::Enum SpecFileDialogViewFilesTypeEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = SpecFileDialogViewFilesTypeEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const SpecFileDialogViewFilesTypeEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type SpecFileDialogViewFilesTypeEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString SpecFileDialogViewFilesTypeEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const SpecFileDialogViewFilesTypeEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ SpecFileDialogViewFilesTypeEnum::Enum SpecFileDialogViewFilesTypeEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = SpecFileDialogViewFilesTypeEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const SpecFileDialogViewFilesTypeEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type SpecFileDialogViewFilesTypeEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t SpecFileDialogViewFilesTypeEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const SpecFileDialogViewFilesTypeEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ SpecFileDialogViewFilesTypeEnum::Enum SpecFileDialogViewFilesTypeEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = SpecFileDialogViewFilesTypeEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const SpecFileDialogViewFilesTypeEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type SpecFileDialogViewFilesTypeEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void SpecFileDialogViewFilesTypeEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void SpecFileDialogViewFilesTypeEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(SpecFileDialogViewFilesTypeEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void SpecFileDialogViewFilesTypeEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(SpecFileDialogViewFilesTypeEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } workbench-1.1.1/src/Common/SpecFileDialogViewFilesTypeEnum.h000066400000000000000000000065721255417355300240270ustar00rootroot00000000000000#ifndef __SPEC_FILE_DIALOG_VIEW_FILES_TYPE_ENUM_H__ #define __SPEC_FILE_DIALOG_VIEW_FILES_TYPE_ENUM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class SpecFileDialogViewFilesTypeEnum { public: /** * Enumerated values. */ enum Enum { /** */ VIEW_FILES_ALL, /** */ VIEW_FILES_LOADED, /** */ VIEW_FILES_LOADED_MODIFIED, /** */ VIEW_FILES_LOADED_NOT_MODIFIED, /** */ VIEW_FILES_NOT_LOADED }; ~SpecFileDialogViewFilesTypeEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); private: SpecFileDialogViewFilesTypeEnum(const Enum enumValue, const AString& name, const AString& guiName); static const SpecFileDialogViewFilesTypeEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; #ifdef __SPEC_FILE_DIALOG_VIEW_FILES_TYPE_ENUM_DECLARE__ std::vector SpecFileDialogViewFilesTypeEnum::enumData; bool SpecFileDialogViewFilesTypeEnum::initializedFlag = false; int32_t SpecFileDialogViewFilesTypeEnum::integerCodeCounter = 0; #endif // __SPEC_FILE_DIALOG_VIEW_FILES_TYPE_ENUM_DECLARE__ } // namespace #endif //__SPEC_FILE_DIALOG_VIEW_FILES_TYPE_ENUM_H__ workbench-1.1.1/src/Common/SpeciesEnum.cxx000066400000000000000000000254411255417355300204770ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __SPECIES_ENUM_DECLARE__ #include "SpeciesEnum.h" #undef __SPECIES_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * Constructor. * * @param enumValue * An enumerated value. * @param integerCode * Integer code for this enumerated value. * * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ SpeciesEnum::SpeciesEnum(const Enum enumValue, const int32_t integerCode, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCode; this->name = name; this->guiName = guiName; } /** * Destructor. */ SpeciesEnum::~SpeciesEnum() { } /** * Initialize the enumerated metadata. */ void SpeciesEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(SpeciesEnum(TYPE_UNKNOWN, 0, "TYPE_UNKNOWN", "Unknown")); enumData.push_back(SpeciesEnum(TYPE_BABOON, 1, "TYPE_BABOON", "Baboon")); enumData.push_back(SpeciesEnum(TYPE_CHIMPANZEE, 2, "TYPE_CHIMPANZEE", "Chimpanzee")); enumData.push_back(SpeciesEnum(TYPE_FERRET, 3, "TYPE_FERRET", "Ferret")); enumData.push_back(SpeciesEnum(TYPE_GALAGO, 4, "TYPE_GALAGO", "Galago")); enumData.push_back(SpeciesEnum(TYPE_GIBBON, 5, "TYPE_GIBBON", "Gibbon")); enumData.push_back(SpeciesEnum(TYPE_GORILLA, 6, "TYPE_GORILLA", "Gorilla")); enumData.push_back(SpeciesEnum(TYPE_HUMAN, 7, "TYPE_HUMAN", "Human")); enumData.push_back(SpeciesEnum(TYPE_MACAQUE, 8, "TYPE_MACAQUE", "Macaque")); enumData.push_back(SpeciesEnum(TYPE_MOUSE, 9, "TYPE_MOUSE", "Mouse")); enumData.push_back(SpeciesEnum(TYPE_ORANGUTAN, 10, "TYPE_ORANGUTAN", "Orangutan")); enumData.push_back(SpeciesEnum(TYPE_RAT, 11, "TYPE_RAT", "Rat")); enumData.push_back(SpeciesEnum(TYPE_OTHER, 12, "TYPE_OTHER", "Other not specified")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const SpeciesEnum* SpeciesEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const SpeciesEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString SpeciesEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const SpeciesEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ SpeciesEnum::Enum SpeciesEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = TYPE_UNKNOWN; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const SpeciesEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type SpeciesEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString SpeciesEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const SpeciesEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ SpeciesEnum::Enum SpeciesEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = TYPE_UNKNOWN; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const SpeciesEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type SpeciesEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t SpeciesEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const SpeciesEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ SpeciesEnum::Enum SpeciesEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = TYPE_UNKNOWN; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const SpeciesEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type SpeciesEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void SpeciesEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void SpeciesEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(SpeciesEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void SpeciesEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(SpeciesEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } workbench-1.1.1/src/Common/SpeciesEnum.h000066400000000000000000000065611255417355300201260ustar00rootroot00000000000000#ifndef __SPECIES_ENUM__H_ #define __SPECIES_ENUM__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { /** * \brief Enumerated type for species. * * Species enumerated type. */ class SpeciesEnum { public: /** * Enumerated values. */ enum Enum { /// unknown TYPE_UNKNOWN, /// baboon TYPE_BABOON, /// chimpanzee TYPE_CHIMPANZEE, /// ferret TYPE_FERRET, /// galago TYPE_GALAGO, /// gibbon TYPE_GIBBON, /// gorilla TYPE_GORILLA, /// human TYPE_HUMAN, /// macaque monkey TYPE_MACAQUE, /// mouse TYPE_MOUSE, /// orangutan TYPE_ORANGUTAN, /// rat TYPE_RAT, /// other TYPE_OTHER }; ~SpeciesEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); private: SpeciesEnum(const Enum enumValue, const int32_t integerCode, const AString& name, const AString& guiName); static const SpeciesEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; #ifdef __SPECIES_ENUM_DECLARE__ std::vector SpeciesEnum::enumData; bool SpeciesEnum::initializedFlag = false; #endif // __SPECIES_ENUM_DECLARE__ } // namespace #endif //__SPECIES_ENUM__H_ workbench-1.1.1/src/Common/StereotaxicSpaceEnum.cxx000066400000000000000000000555761255417355300223660ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __STEREOTAXIC_SPACE_ENUM_DECLARE__ #include "StereotaxicSpaceEnum.h" #undef __STEREOTAXIC_SPACE_ENUM_DECLARE__ #include "CaretAssert.h" #include "CaretLogger.h" using namespace caret; /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ StereotaxicSpaceEnum::StereotaxicSpaceEnum(const Enum enumValue, const AString& name, const AString& guiName, const int32_t dimI, const int32_t dimJ, const int32_t dimK, const float voxelSizeX, const float voxelSizeY, const float voxelSizeZ, const float originX, const float originY, const float originZ) { this->enumValue = enumValue; this->name = name; this->guiName = guiName; this->volumeDimensions[0] = dimI; this->volumeDimensions[1] = dimJ; this->volumeDimensions[2] = dimK; this->volumeVoxelSizes[0] = voxelSizeX; this->volumeVoxelSizes[1] = voxelSizeY; this->volumeVoxelSizes[2] = voxelSizeZ; this->volumeOrigin[0] = originX; this->volumeOrigin[1] = originY; this->volumeOrigin[2] = originZ; } /** * Destructor. */ StereotaxicSpaceEnum::~StereotaxicSpaceEnum() { } /** * Get the dimensions for a stereotaxic space. * * @param enumValue * Input - Enumerated type for stereotaxic space. * @param dimensionsOut * Output - Dimensions for the stereotaxic space. */ void StereotaxicSpaceEnum::toDimensions(const Enum enumValue, int32_t dimensionsOut[3]) { const StereotaxicSpaceEnum* enumInstance = findData(enumValue); dimensionsOut[0] = enumInstance->volumeDimensions[0]; dimensionsOut[1] = enumInstance->volumeDimensions[1]; dimensionsOut[2] = enumInstance->volumeDimensions[2]; } /** * Get the voxel sizes for a stereotaxic space. * * @param enumValue * Input - Enumerated type for stereotaxic space. * @param voxelSizesOut * Output - Voxel sizes for the stereotaxic space. */ void StereotaxicSpaceEnum::toVoxelSizes(const Enum enumValue, float voxelSizesOut[3]) { const StereotaxicSpaceEnum* enumInstance = findData(enumValue); voxelSizesOut[0] = enumInstance->volumeVoxelSizes[0]; voxelSizesOut[1] = enumInstance->volumeVoxelSizes[1]; voxelSizesOut[2] = enumInstance->volumeVoxelSizes[2]; } /** * Get the origin for a stereotaxic space. * * @param enumValue * Input - Enumerated type for stereotaxic space. * @param originOut * Output - Origin for the stereotaxic space. */ void StereotaxicSpaceEnum::toOrigin(const Enum enumValue, float originOut[3]) { const StereotaxicSpaceEnum* enumInstance = findData(enumValue); originOut[0] = enumInstance->volumeOrigin[0]; originOut[1] = enumInstance->volumeOrigin[1]; originOut[2] = enumInstance->volumeOrigin[2]; } /** * Initialize the enumerated metadata. */ void StereotaxicSpaceEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(StereotaxicSpaceEnum(SPACE_UNKNOWN, "SPACE_UNKNOWN", "Unknown", 0, 0, 0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)); enumData.push_back(StereotaxicSpaceEnum(SPACE_OTHER, "SPACE_OTHER", "Other not specified", 0, 0, 0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)); enumData.push_back(StereotaxicSpaceEnum(SPACE_AFNI_TALAIRACH, "SPACE_AFNI_TALAIRACH", "AFNI", 161, 191, 151, 1.0, 1.0, 1.0, -80.0, -110.0, -65.0)); enumData.push_back(StereotaxicSpaceEnum(SPACE_FLIRT, "SPACE_FLIRT", "FLIRT", 182, 217, 182, 1.0, 1.0, 1.0, -90.0, -126.0, -72.0)); enumData.push_back(StereotaxicSpaceEnum(SPACE_FLIRT_222, "SPACE_FLIRT_222", "FLIRT-222", 91, 109, 91, 2.0, 2.0, 2.0, -90.0, -126.0, -72.0)); enumData.push_back(StereotaxicSpaceEnum(SPACE_MACAQUE_F6, "SPACE_MACAQUE_F6", "MACAQUE-F6", 143, 187, 118, 0.5, 0.5, 0.5, -35.75, -54.75, -30.25)); enumData.push_back(StereotaxicSpaceEnum(SPACE_MACAQUE_F99, "SPACE_MACAQUE_F99", "MACAQUE-F99", 143, 187, 118, 0.5, 0.5, 0.5, -35.75, -54.75, -30.25)); enumData.push_back(StereotaxicSpaceEnum(SPACE_MRITOTAL, "SPACE_MRITOTAL", "MRITOTAL", 182, 217, 182, 1.0, 1.0, 1.0, -90.0, -126.0, -72.0)); enumData.push_back(StereotaxicSpaceEnum(SPACE_SPM, "SPACE_SPM", "SPM", 182, 217, 182, 1.0, 1.0, 1.0, -90.0, -126.0, -72.0)); enumData.push_back(StereotaxicSpaceEnum(SPACE_SPM_95, "SPACE_SPM_95", "SPM95", 182, 217, 182, 1.0, 1.0, 1.0, -90.0, -126.0, -72.0)); enumData.push_back(StereotaxicSpaceEnum(SPACE_SPM_96, "SPACE_SPM_96", "SPM96", 182, 217, 182, 1.0, 1.0, 1.0, -90.0, -126.0, -72.0)); enumData.push_back(StereotaxicSpaceEnum(SPACE_SPM_99, "SPACE_SPM_99", "SPM99", 182, 217, 182, 1.0, 1.0, 1.0, -90.0, -126.0, -72.0)); enumData.push_back(StereotaxicSpaceEnum(SPACE_SPM_2, "SPACE_SPM_2", "SPM2", 182, 217, 182, 1.0, 1.0, 1.0, -90.0, -126.0, -72.0)); enumData.push_back(StereotaxicSpaceEnum(SPACE_SPM_5, "SPACE_SPM_5", "SPM5", 182, 217, 182, 1.0, 1.0, 1.0, -90.0, -126.0, -72.0)); enumData.push_back(StereotaxicSpaceEnum(SPACE_T88, "SPACE_T88", "T88", 161, 191, 151, 1.0, 1.0, 1.0, -80.0, -110.0, -65.0)); enumData.push_back(StereotaxicSpaceEnum(SPACE_WU_7112B, "SPACE_WU_7112B", "711-2B", 176, 208, 176, 1.0, 1.0, 1.0, -88.5, -123.5, -75.5)); enumData.push_back(StereotaxicSpaceEnum(SPACE_WU_7112B_111, "SPACE_WU_7112B_111", "711-2B-111", 176, 208, 176, 1.0, 1.0, 1.0, -88.5, -123.5, -75.5)); enumData.push_back(StereotaxicSpaceEnum(SPACE_WU_7112B_222, "SPACE_WU_7112B_222", "711-2B-222", 128, 128, 75, 2.0, 2.0, 2.0, -128, -128, -69)); enumData.push_back(StereotaxicSpaceEnum(SPACE_WU_7112B_333, "SPACE_WU_7112B_333", "711-2B-333", 48, 64, 48, 3.0, 3.0, 3.0, -72.0, -106.5, -61.5)); enumData.push_back(StereotaxicSpaceEnum(SPACE_WU_7112C, "SPACE_WU_7112C", "711-2C", 176, 208, 176, 1.0, 1.0, 1.0, -88.5, -123.5, -75.5)); enumData.push_back(StereotaxicSpaceEnum(SPACE_WU_7112C_111, "SPACE_WU_7112C_111", "711-2C-111", 176, 208, 176, 1.0, 1.0, 1.0, -88.5, -123.5, -75.5)); enumData.push_back(StereotaxicSpaceEnum(SPACE_WU_7112C_222, "SPACE_WU_7112C_222", "711-2C-222", 128, 128, 75, 2.0, 2.0, 2.0, -128, -128, -69)); enumData.push_back(StereotaxicSpaceEnum(SPACE_WU_7112C_333, "SPACE_WU_7112C_333", "711-2C-333", 48, 64, 48, 3.0, 3.0, 3.0, -72.0, -106.5, -61.5)); enumData.push_back(StereotaxicSpaceEnum(SPACE_WU_7112O, "SPACE_WU_7112O", "711-2O", 176, 208, 176, 1.0, 1.0, 1.0, -88.5, -123.5, -75.5)); enumData.push_back(StereotaxicSpaceEnum(SPACE_WU_7112O_111, "SPACE_WU_7112O_111", "711-2O-111", 176, 208, 176, 1.0, 1.0, 1.0, -88.5, -123.5, -75.5)); enumData.push_back(StereotaxicSpaceEnum(SPACE_WU_7112O_222, "SPACE_WU_7112O_222", "711-2O-222", 128, 128, 75, 2.0, 2.0, 2.0, -128, -128, -69)); enumData.push_back(StereotaxicSpaceEnum(SPACE_WU_7112O_333, "SPACE_WU_7112O_333", "711-2O-333", 48, 64, 48, 3.0, 3.0, 3.0, -72.0, -106.5, -61.5)); enumData.push_back(StereotaxicSpaceEnum(SPACE_WU_7112Y, "SPACE_WU_7112Y", "711-2Y", 176, 208, 176, 1.0, 1.0, 1.0, -88.5, -123.5, -75.5)); enumData.push_back(StereotaxicSpaceEnum(SPACE_WU_7112Y_111, "SPACE_WU_7112Y_111", "711-2Y-111", 176, 208, 176, 1.0, 1.0, 1.0, -88.5, -123.5, -75.5)); enumData.push_back(StereotaxicSpaceEnum(SPACE_WU_7112Y_222, "SPACE_WU_7112Y_222", "711-2Y-222", 128, 128, 75, 2.0, 2.0, 2.0, -128, -128, -69)); enumData.push_back(StereotaxicSpaceEnum(SPACE_WU_7112Y_333, "SPACE_WU_7112Y_333", "711-2Y-333", 48, 64, 48, 3.0, 3.0, 3.0, -72.0, -106.5, -61.5)); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const StereotaxicSpaceEnum* StereotaxicSpaceEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const StereotaxicSpaceEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString StereotaxicSpaceEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const StereotaxicSpaceEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ StereotaxicSpaceEnum::Enum StereotaxicSpaceEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = SPACE_UNKNOWN; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const StereotaxicSpaceEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type StereotaxicSpaceEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString StereotaxicSpaceEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const StereotaxicSpaceEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ StereotaxicSpaceEnum::Enum StereotaxicSpaceEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = SPACE_UNKNOWN; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const StereotaxicSpaceEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type StereotaxicSpaceEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t StereotaxicSpaceEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const StereotaxicSpaceEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ StereotaxicSpaceEnum::Enum StereotaxicSpaceEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = SPACE_UNKNOWN; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const StereotaxicSpaceEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type StereotaxicSpaceEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void StereotaxicSpaceEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void StereotaxicSpaceEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(StereotaxicSpaceEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void StereotaxicSpaceEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(StereotaxicSpaceEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } workbench-1.1.1/src/Common/StereotaxicSpaceEnum.h000066400000000000000000000136531255417355300220010ustar00rootroot00000000000000#ifndef __STEREOTAXIC_SPACE_ENUM__H_ #define __STEREOTAXIC_SPACE_ENUM__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { /** * \brief * * */ class StereotaxicSpaceEnum { public: /** * Enumerated values. */ enum Enum { /// unknown space SPACE_UNKNOWN, /// other space SPACE_OTHER, /// AFNI Talairach space SPACE_AFNI_TALAIRACH, /// FLIRT space SPACE_FLIRT, /// FLIRT 222 space SPACE_FLIRT_222, /// macaque atlas SPACE_MACAQUE_F6, /// Macaque F99 SPACE_MACAQUE_F99, /// MRITOTAL space SPACE_MRITOTAL, /// SPM space SPACE_SPM, /// SPM 95 space SPACE_SPM_95, /// SPM 95 space SPACE_SPM_96, /// SPM 99 Template space SPACE_SPM_99, /// SPM 2 Template space SPACE_SPM_2, /// SPM 5 space SPACE_SPM_5, /// Talairach 88 space (same as AFNI) SPACE_T88, /// Washington University 711-2B space SPACE_WU_7112B, /// Washington University 711-2B 1mm voxelspace SPACE_WU_7112B_111, /// Washington University 711-2B 2mm voxelspace SPACE_WU_7112B_222, /// Washington University 711-2B 3mm voxelspace SPACE_WU_7112B_333, /// Washington University 711-2C space SPACE_WU_7112C, /// Washington University 711-2C 1mm voxelspace SPACE_WU_7112C_111, /// Washington University 711-2C 2mm voxelspace SPACE_WU_7112C_222, /// Washington University 711-2C 3mm voxelspace SPACE_WU_7112C_333, /// Washington University 711-2O space SPACE_WU_7112O, /// Washington University 711-2O 1mm voxelspace SPACE_WU_7112O_111, /// Washington University 711-2O 2mm voxelspace SPACE_WU_7112O_222, /// Washington University 711-2O 3mm voxelspace SPACE_WU_7112O_333, /// Washington University 711-2Y space SPACE_WU_7112Y, /// Washington University 711-2Y 1mm voxelspace SPACE_WU_7112Y_111, /// Washington University 711-2Y 2mm voxelspace SPACE_WU_7112Y_222, /// Washington University 711-2Y 3mm voxelspace SPACE_WU_7112Y_333 }; ~StereotaxicSpaceEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); static void toDimensions(const Enum enumValue, int32_t dimensionsOut[3]); static void toVoxelSizes(const Enum enumValue, float voxelSizesOut[3]); static void toOrigin(const Enum enumValue, float originOut[3]); private: StereotaxicSpaceEnum(const Enum enumValue, const AString& name, const AString& guiName, const int32_t dimI, const int32_t dimJ, const int32_t dimK, const float voxelSizeX, const float voxelSizeY, const float voxelSizeZ, const float originX, const float originY, const float originZ); static const StereotaxicSpaceEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; /** Volume Dimensions */ int32_t volumeDimensions[3]; /** Volume Origin */ float volumeOrigin[3]; /** Volume Voxel Sizes */ float volumeVoxelSizes[3]; }; #ifdef __STEREOTAXIC_SPACE_ENUM_DECLARE__ std::vector StereotaxicSpaceEnum::enumData; bool StereotaxicSpaceEnum::initializedFlag = false; int32_t StereotaxicSpaceEnum::integerCodeCounter = 0; #endif // __STEREOTAXIC_SPACE_ENUM_DECLARE__ } // namespace #endif //__STEREOTAXIC_SPACE_ENUM__H_ workbench-1.1.1/src/Common/StringTableModel.cxx000066400000000000000000000157201255417355300214550ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __STRING_TABLE_MODEL_DECLARE__ #include "StringTableModel.h" #undef __STRING_TABLE_MODEL_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::StringTableModel * \brief Creates a two-dimension table of strings. * \ingroup Common */ /** * Constructor that creates a table of the specified dimensions * with all column alignment set to right. * * @param numberOfRows * Number of rows. * @param numColumns * Number of columns. * @param floatingPointPrecision * Precision for floating point data. */ StringTableModel::StringTableModel(const int32_t numberOfRows, const int32_t numberOfColumns, const int32_t floatingPointPrecision) : CaretObject(), m_numberOfRows(numberOfRows), m_numberOfColumns(numberOfColumns), m_floatingPointPrecsion(floatingPointPrecision) { CaretAssert(m_numberOfRows > 0); CaretAssert(m_numberOfColumns > 0); m_stringTable.resize(m_numberOfRows * m_numberOfColumns); m_columnAlignment.resize(m_numberOfColumns, ALIGN_RIGHT); } /** * Destructor. */ StringTableModel::~StringTableModel() { } /** * Set column alignment. * * @param column * Column for which alignment is set. * @param alignment * New alignment value. */ void StringTableModel::setColumnAlignment(const int32_t column, const Alignment alignment) { CaretAssertVectorIndex(m_columnAlignment, column); m_columnAlignment[column] = alignment; } /** * Set an element in the table with a string. * * @param row * Row index. * @param column * Column. * @param value * String value for given row/column. */ void StringTableModel::setElement(const int32_t row, const int32_t column, const AString& value) { const int32_t offset = getOffset(row, column); CaretAssertVectorIndex(m_stringTable, offset); m_stringTable[offset] = value; } /** * Set an element in the table with a C-style string. * * @param row * Row index. * @param column * Column. * @param value * C-Style string value for given row/column. */ void StringTableModel::setElement(const int32_t row, const int32_t column, const char* value) { setElement(row, column, AString(value)); } /** * Set an element in the table with a boolean value. * * @param row * Row index. * @param column * Column. * @param value * Boolean value for given row/column. */ void StringTableModel::setElement(const int32_t row, const int32_t column, const bool value) { setElement(row, column, AString::fromBool(value)); } /** * Set an element in the table with a 32-bit integer. * * @param row * Row index. * @param column * Column. * @param value * Integer value for given row/column. */ void StringTableModel::setElement(const int32_t row, const int32_t column, const int32_t value) { setElement(row, column, AString::number(value)); } /** * Set an element in the table with a 64-bit integer. * * @param row * Row index. * @param column * Column. * @param value * 64-Bit integer value for given row/column. */ void StringTableModel::setElement(const int32_t row, const int32_t column, const int64_t value) { setElement(row, column, AString::number(value)); } /** * Set an element in the table with floating point value. * * @param row * Row index. * @param column * Column. * @param value * Floating point value for given row/column. */ void StringTableModel::setElement(const int32_t row, const int32_t column, const double value) { setElement(row, column, AString::number(value, 'f', m_floatingPointPrecsion)); } /** * Get the offset to an element in the table. * * @param row * Row index. * @param column * Column. * @return * Offset of element and given row and column. */ int32_t StringTableModel::getOffset(const int32_t row, const int32_t column) const { CaretAssertArrayIndex(m_stringTable, m_numberOfRows, row); CaretAssertArrayIndex(m_stringTable, m_numberOfColumns, column); const int32_t offset = ((row * m_numberOfColumns) + column); return offset; } /** * @return The table formatted into a text string. */ AString StringTableModel::getInString() const { std::vector columnWidths(m_numberOfColumns, 0); for (int32_t j = 0; j < m_numberOfColumns; j++) { int32_t width = 0; for (int32_t i = 0; i < m_numberOfRows; i++) { width = std::max(m_stringTable[getOffset(i, j)].length(), width); } // if (width > 0) { // width += 3; // } columnWidths[j] = width; } AString textOut; for (int32_t i = 0; i < m_numberOfRows; i++) { for (int32_t j = 0; j < m_numberOfColumns; j++) { const int32_t offset = getOffset(i, j); CaretAssertVectorIndex(m_stringTable, offset); AString txt = m_stringTable[offset]; CaretAssertVectorIndex(m_columnAlignment, j); switch (m_columnAlignment[j]) { case ALIGN_LEFT: textOut.append(txt.leftJustified(columnWidths[j])); break; case ALIGN_RIGHT: textOut.append(txt.rightJustified(columnWidths[j])); break; } textOut.append(" "); } textOut.append("\n"); } return textOut; } workbench-1.1.1/src/Common/StringTableModel.h000066400000000000000000000060371255417355300211030ustar00rootroot00000000000000#ifndef __STRING_TABLE_MODEL_H__ #define __STRING_TABLE_MODEL_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" namespace caret { class StringTableModel : public CaretObject { public: enum Alignment { ALIGN_LEFT, ALIGN_RIGHT }; StringTableModel(const int32_t numberOfRows, const int32_t numberOfColumns, const int32_t floatingPointPrecision = 3); virtual ~StringTableModel(); void setElement(const int32_t row, const int32_t column, const AString& value); void setElement(const int32_t row, const int32_t column, const char* value); void setElement(const int32_t row, const int32_t column, const int32_t value); void setElement(const int32_t row, const int32_t column, const int64_t value); void setElement(const int32_t row, const int32_t column, const double value); void setElement(const int32_t row, const int32_t column, const bool value); void setColumnAlignment(const int32_t column, const Alignment alignment); AString getInString() const; private: StringTableModel(const StringTableModel&); StringTableModel& operator=(const StringTableModel&); int32_t getOffset(const int32_t row, const int32_t column) const; const int32_t m_numberOfRows; const int32_t m_numberOfColumns; const int32_t m_floatingPointPrecsion; std::vector m_stringTable; std::vector m_columnAlignment; // ADD_NEW_MEMBERS_HERE }; #ifdef __STRING_TABLE_MODEL_DECLARE__ // #endif // __STRING_TABLE_MODEL_DECLARE__ } // namespace #endif //__STRING_TABLE_MODEL_H__ workbench-1.1.1/src/Common/StructureEnum.cxx000066400000000000000000000617431255417355300211110ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __STRUCTURE_ENUM_DECLARE__ #include "StructureEnum.h" #undef __STRUCTURE_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * Constructor. * * @param enumValue * An enumerated value. * @param integerCode * Integer code for this enumerated value. * * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ StructureEnum::StructureEnum(const Enum enumValue, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = StructureEnum::integerCodeGenerator++; this->name = name; this->guiName = guiName; } /** * Destructor. */ StructureEnum::~StructureEnum() { } /** * Initialize the enumerated metadata. */ void StructureEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(StructureEnum(CORTEX_LEFT, "CORTEX_LEFT", "CortexLeft")); enumData.push_back(StructureEnum(CORTEX_RIGHT, "CORTEX_RIGHT", "CortexRight")); enumData.push_back(StructureEnum(CEREBELLUM, "CEREBELLUM", "Cerebellum")); enumData.push_back(StructureEnum(ACCUMBENS_LEFT, "ACCUMBENS_LEFT", "AccumbensLeft")); enumData.push_back(StructureEnum(ACCUMBENS_RIGHT, "ACCUMBENS_RIGHT", "AccumbensRight")); enumData.push_back(StructureEnum(ALL, "ALL", "All")); enumData.push_back(StructureEnum(ALL_GREY_MATTER, "ALL_GREY_MATTER", "AllGreyMatter")); enumData.push_back(StructureEnum(ALL_WHITE_MATTER, "ALL_WHITE_MATTER", "AllWhiteMatter")); enumData.push_back(StructureEnum(AMYGDALA_LEFT, "AMYGDALA_LEFT", "AmygdalaLeft")); enumData.push_back(StructureEnum(AMYGDALA_RIGHT, "AMYGDALA_RIGHT", "AmygdalaRight")); enumData.push_back(StructureEnum(BRAIN_STEM, "BRAIN_STEM", "BrainStem")); enumData.push_back(StructureEnum(CAUDATE_LEFT, "CAUDATE_LEFT", "CaudateLeft")); enumData.push_back(StructureEnum(CAUDATE_RIGHT, "CAUDATE_RIGHT", "CaudateRight")); enumData.push_back(StructureEnum(CEREBELLAR_WHITE_MATTER_LEFT, "CEREBELLAR_WHITE_MATTER_LEFT", "CerebellarWhiteMatterLeft")); enumData.push_back(StructureEnum(CEREBELLAR_WHITE_MATTER_RIGHT, "CEREBELLAR_WHITE_MATTER_RIGHT", "CerebellarWhiteMatterRight")); enumData.push_back(StructureEnum(CEREBELLUM_LEFT, "CEREBELLUM_LEFT", "CerebellumLeft")); enumData.push_back(StructureEnum(CEREBELLUM_RIGHT, "CEREBELLUM_RIGHT", "CerebellumRight")); enumData.push_back(StructureEnum(CEREBRAL_WHITE_MATTER_LEFT, "CEREBRAL_WHITE_MATTER_LEFT", "CerebralWhiteMatterLeft")); enumData.push_back(StructureEnum(CEREBRAL_WHITE_MATTER_RIGHT, "CEREBRAL_WHITE_MATTER_RIGHT", "CerebralWhiteMatterRight")); enumData.push_back(StructureEnum(CORTEX, "CORTEX", "Cortex")); enumData.push_back(StructureEnum(DIENCEPHALON_VENTRAL_LEFT, "DIENCEPHALON_VENTRAL_LEFT", "DiencephalonVentralLeft")); enumData.push_back(StructureEnum(DIENCEPHALON_VENTRAL_RIGHT, "DIENCEPHALON_VENTRAL_RIGHT", "DiencephalonVentralRight")); enumData.push_back(StructureEnum(HIPPOCAMPUS_LEFT, "HIPPOCAMPUS_LEFT", "HippocampusLeft")); enumData.push_back(StructureEnum(HIPPOCAMPUS_RIGHT, "HIPPOCAMPUS_RIGHT", "HippocampusRight")); enumData.push_back(StructureEnum(INVALID, "INVALID", "Invalid")); enumData.push_back(StructureEnum(OTHER, "OTHER", "Other")); enumData.push_back(StructureEnum(OTHER_GREY_MATTER, "OTHER_GREY_MATTER", "OtherGreyMatter")); enumData.push_back(StructureEnum(OTHER_WHITE_MATTER, "OTHER_WHITE_MATTER", "OtherWhiteMatter")); enumData.push_back(StructureEnum(PALLIDUM_LEFT, "PALLIDUM_LEFT", "PallidumLeft")); enumData.push_back(StructureEnum(PALLIDUM_RIGHT, "PALLIDUM_RIGHT", "PallidumRight")); enumData.push_back(StructureEnum(PUTAMEN_LEFT, "PUTAMEN_LEFT", "PutamenLeft")); enumData.push_back(StructureEnum(PUTAMEN_RIGHT, "PUTAMEN_RIGHT", "PutamenRight")); // enumData.push_back(StructureEnum(SUBCORTICAL_WHITE_MATTER_LEFT, // "SUBCORTICAL_WHITE_MATTER_LEFT", // "SubcorticalWhiteMatterLeft")); // // enumData.push_back(StructureEnum(SUBCORTICAL_WHITE_MATTER_RIGHT, // "SUBCORTICAL_WHITE_MATTER_RIGHT", // "SubcorticalWhiteMatterRight")); enumData.push_back(StructureEnum(THALAMUS_LEFT, "THALAMUS_LEFT", "ThalamusLeft")); enumData.push_back(StructureEnum(THALAMUS_RIGHT, "THALAMUS_RIGHT", "ThalamusRight")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const StructureEnum* StructureEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const StructureEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString StructureEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const StructureEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ StructureEnum::Enum StructureEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = INVALID; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const StructureEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type StructureEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString StructureEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const StructureEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ StructureEnum::Enum StructureEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = INVALID; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const StructureEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type StructureEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString StructureEnum::toCiftiName(Enum enumValue) { if (initializedFlag == false) initialize(); const StructureEnum* enumInstance = findData(enumValue); return "CIFTI_STRUCTURE_" + enumInstance->name; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ StructureEnum::Enum StructureEnum::fromCiftiName(const AString& ciftiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = INVALID; if (ciftiName.startsWith("CIFTI_STRUCTURE_")) { QString toMatch = ciftiName.mid(16); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const StructureEnum& d = *iter; if (toMatch == d.name) { enumValue = d.enumValue; validFlag = true; break; } } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + ciftiName + "failed to match enumerated value for type StructureEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t StructureEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const StructureEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ StructureEnum::Enum StructureEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = INVALID; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const StructureEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type StructureEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values * except ALL. */ void StructureEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { StructureEnum::Enum value =iter->enumValue; if (value == ALL) { // nothing } else { allEnums.push_back(iter->enumValue); } } } /** * Is this 'right' structure? * @param enumValue * The enumerated type. * @return * true if the enumerated value represents a 'right' structure, else false. */ bool StructureEnum::isRight(const Enum enumValue) { switch (enumValue) { case ACCUMBENS_RIGHT: case AMYGDALA_RIGHT: case CAUDATE_RIGHT: case CEREBELLAR_WHITE_MATTER_RIGHT: case CEREBELLUM_RIGHT: case CEREBRAL_WHITE_MATTER_RIGHT: case CORTEX_RIGHT: case DIENCEPHALON_VENTRAL_RIGHT: case HIPPOCAMPUS_RIGHT: case PALLIDUM_RIGHT: case PUTAMEN_RIGHT: case THALAMUS_RIGHT: return true; case ALL://avoid default so smart compilers can warn when a new structure isn't added here case ALL_GREY_MATTER: case ALL_WHITE_MATTER: case BRAIN_STEM: case CEREBELLUM: case CORTEX: case INVALID: case OTHER: case OTHER_GREY_MATTER: case OTHER_WHITE_MATTER: return false;//visually separate none/both from opposite cases case ACCUMBENS_LEFT: case AMYGDALA_LEFT: case CAUDATE_LEFT: case CEREBELLAR_WHITE_MATTER_LEFT: case CEREBELLUM_LEFT: case CEREBRAL_WHITE_MATTER_LEFT: case CORTEX_LEFT: case DIENCEPHALON_VENTRAL_LEFT: case HIPPOCAMPUS_LEFT: case PALLIDUM_LEFT: case PUTAMEN_LEFT: case THALAMUS_LEFT: return false; } CaretAssert(false); return false; } /** * Is this 'left' structure? * @param enumValue * The enumerated type. * @return * true if the enumerated value represents a 'left' structure, else false. */ bool StructureEnum::isLeft(const Enum enumValue) { switch (enumValue) { case ACCUMBENS_LEFT: case AMYGDALA_LEFT: case CAUDATE_LEFT: case CEREBELLAR_WHITE_MATTER_LEFT: case CEREBELLUM_LEFT: case CEREBRAL_WHITE_MATTER_LEFT: case CORTEX_LEFT: case DIENCEPHALON_VENTRAL_LEFT: case HIPPOCAMPUS_LEFT: case PALLIDUM_LEFT: case PUTAMEN_LEFT: case THALAMUS_LEFT: return true; case ALL://avoid default so smart compilers can warn when a new structure isn't added here case ALL_GREY_MATTER: case ALL_WHITE_MATTER: case BRAIN_STEM: case CEREBELLUM: case CORTEX: case INVALID: case OTHER: case OTHER_GREY_MATTER: case OTHER_WHITE_MATTER: return false;//visually separate none/both from opposite cases case ACCUMBENS_RIGHT: case AMYGDALA_RIGHT: case CAUDATE_RIGHT: case CEREBELLAR_WHITE_MATTER_RIGHT: case CEREBELLUM_RIGHT: case CEREBRAL_WHITE_MATTER_RIGHT: case CORTEX_RIGHT: case DIENCEPHALON_VENTRAL_RIGHT: case HIPPOCAMPUS_RIGHT: case PALLIDUM_RIGHT: case PUTAMEN_RIGHT: case THALAMUS_RIGHT: return false; } CaretAssert(false); return false; } /** * Is this a 'single' structure? The structure must be a valid type. * * @param enumValue * The enumerated type. * @return * true if the enumerated value represents a 'single' structure, else false. */ bool StructureEnum::isSingleStructure(const Enum enumValue) { bool singleStructureFlag = true; switch (enumValue) { case ACCUMBENS_LEFT: break; case ACCUMBENS_RIGHT: break; case ALL: singleStructureFlag = false; break; case ALL_GREY_MATTER: singleStructureFlag = false; break; case ALL_WHITE_MATTER: singleStructureFlag = false; break; case AMYGDALA_LEFT: break; case AMYGDALA_RIGHT: break; case BRAIN_STEM: break; case CAUDATE_LEFT: break; case CAUDATE_RIGHT: break; case CEREBELLAR_WHITE_MATTER_LEFT: break; case CEREBELLAR_WHITE_MATTER_RIGHT: break; case CEREBELLUM: break; case CEREBELLUM_LEFT: break; case CEREBELLUM_RIGHT: break; case CEREBRAL_WHITE_MATTER_LEFT: break; case CEREBRAL_WHITE_MATTER_RIGHT: break; case CORTEX: break; case CORTEX_LEFT: break; case CORTEX_RIGHT: break; case DIENCEPHALON_VENTRAL_LEFT: break; case DIENCEPHALON_VENTRAL_RIGHT: break; case HIPPOCAMPUS_LEFT: break; case HIPPOCAMPUS_RIGHT: break; case INVALID: singleStructureFlag = false; break; case PALLIDUM_LEFT: break; case PALLIDUM_RIGHT: break; case OTHER: singleStructureFlag = false; break; case OTHER_GREY_MATTER: singleStructureFlag = false; break; case OTHER_WHITE_MATTER: singleStructureFlag = false; break; case PUTAMEN_LEFT: break; case PUTAMEN_RIGHT: break; // case SUBCORTICAL_WHITE_MATTER_LEFT: // contralateralStructure = SUBCORTICAL_WHITE_MATTER_RIGHT; // break; // case SUBCORTICAL_WHITE_MATTER_RIGHT: // contralateralStructure = SUBCORTICAL_WHITE_MATTER_LEFT; // break; case THALAMUS_LEFT: break; case THALAMUS_RIGHT: break; } return singleStructureFlag; } /** * Are the two structure's cortices and contralateral (is one CortexLeft * and one CortexRight)? * * @param enumValueA * First structure enumerated type. * @param enumValueB * Second structure enumerated type. * @return * True if one is CORTEX_LEFT and one is CORTEX_LEFT. */ bool StructureEnum::isCortexContralateral(const Enum enumValueA, const Enum enumValueB) { if ((enumValueA == CORTEX_LEFT) && (enumValueB == CORTEX_RIGHT)) { return true; } if ((enumValueA == CORTEX_RIGHT) && (enumValueB == CORTEX_LEFT)) { return true; } return false; } /** * For the given structure return its contralateral structure. * Thats is, if this is a left/right structure return its * corresponding structure from the other side. * * @param enumValue * Structure for which contralateral structure is desired. * @return The contralateral structure or NULL if it does * not have a contralateral structure. */ StructureEnum::Enum StructureEnum::getContralateralStructure(const Enum enumValue) { StructureEnum::Enum contralateralStructure = INVALID; switch (enumValue) { case ACCUMBENS_LEFT: contralateralStructure = ACCUMBENS_RIGHT; break; case ACCUMBENS_RIGHT: contralateralStructure = ACCUMBENS_LEFT; break; case ALL: contralateralStructure = INVALID; break; case ALL_GREY_MATTER: break; case ALL_WHITE_MATTER: break; case AMYGDALA_LEFT: contralateralStructure = AMYGDALA_RIGHT; break; case AMYGDALA_RIGHT: contralateralStructure = AMYGDALA_LEFT; break; case BRAIN_STEM: contralateralStructure = INVALID; break; case CAUDATE_LEFT: contralateralStructure = CAUDATE_RIGHT; break; case CAUDATE_RIGHT: contralateralStructure = CAUDATE_LEFT; break; case CEREBELLAR_WHITE_MATTER_LEFT: contralateralStructure= CEREBELLAR_WHITE_MATTER_RIGHT; break; case CEREBELLAR_WHITE_MATTER_RIGHT: contralateralStructure = CEREBELLAR_WHITE_MATTER_LEFT; break; case CEREBELLUM: contralateralStructure = INVALID; break; case CEREBELLUM_LEFT: contralateralStructure = CEREBELLUM_RIGHT; break; case CEREBELLUM_RIGHT: contralateralStructure = CEREBELLUM_LEFT; break; case CEREBRAL_WHITE_MATTER_LEFT: contralateralStructure = CEREBELLAR_WHITE_MATTER_RIGHT; break; case CEREBRAL_WHITE_MATTER_RIGHT: contralateralStructure = CEREBELLAR_WHITE_MATTER_LEFT; break; case CORTEX: break; case CORTEX_LEFT: contralateralStructure = CORTEX_RIGHT; break; case CORTEX_RIGHT: contralateralStructure = CORTEX_LEFT; break; case DIENCEPHALON_VENTRAL_LEFT: contralateralStructure = DIENCEPHALON_VENTRAL_RIGHT; break; case DIENCEPHALON_VENTRAL_RIGHT: contralateralStructure = DIENCEPHALON_VENTRAL_LEFT; break; case HIPPOCAMPUS_LEFT: contralateralStructure = HIPPOCAMPUS_RIGHT; break; case HIPPOCAMPUS_RIGHT: contralateralStructure = HIPPOCAMPUS_LEFT; break; case INVALID: contralateralStructure = INVALID; break; case PALLIDUM_LEFT: contralateralStructure = PALLIDUM_RIGHT; break; case PALLIDUM_RIGHT: contralateralStructure = PALLIDUM_LEFT; break; case OTHER: contralateralStructure = INVALID; break; case OTHER_GREY_MATTER: break; case OTHER_WHITE_MATTER: break; case PUTAMEN_LEFT: contralateralStructure = PUTAMEN_RIGHT; break; case PUTAMEN_RIGHT: contralateralStructure = PUTAMEN_LEFT; break; // case SUBCORTICAL_WHITE_MATTER_LEFT: // contralateralStructure = SUBCORTICAL_WHITE_MATTER_RIGHT; // break; // case SUBCORTICAL_WHITE_MATTER_RIGHT: // contralateralStructure = SUBCORTICAL_WHITE_MATTER_LEFT; // break; case THALAMUS_LEFT: contralateralStructure = THALAMUS_RIGHT; break; case THALAMUS_RIGHT: contralateralStructure = THALAMUS_LEFT; break; } return contralateralStructure; } workbench-1.1.1/src/Common/StructureEnum.h000066400000000000000000000126241255417355300205300ustar00rootroot00000000000000#ifndef __STRUCTURE_ENUM__H_ #define __STRUCTURE_ENUM__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { /** * \brief Enumerated type for a structure in a brain. * * Enumerated types for the individual structures in a brain. */ class StructureEnum { public: /** * Enumerated values. */ enum Enum { /** Invalid */ INVALID, /** All Strucures */ ALL, /** All white matter */ ALL_WHITE_MATTER, /** All grey matter */ ALL_GREY_MATTER, /** Left Nucleus Accumbens */ ACCUMBENS_LEFT, /** Right Nucleus Accumbens */ ACCUMBENS_RIGHT, /** Left Amygdala */ AMYGDALA_LEFT, /** Right Amygdala */ AMYGDALA_RIGHT, /** Brain Stem */ BRAIN_STEM, /** Left Caudate */ CAUDATE_LEFT, /** Right Caudate */ CAUDATE_RIGHT, /** Cerebellar white matter left */ CEREBELLAR_WHITE_MATTER_LEFT, /** Cerebellar white matter right */ CEREBELLAR_WHITE_MATTER_RIGHT, /** Cerebellum */ CEREBELLUM, /** Left Cerebellum */ CEREBELLUM_LEFT, /** Right Cerebellum */ CEREBELLUM_RIGHT, /** Cerebral white matter left */ CEREBRAL_WHITE_MATTER_LEFT, /** Cerebral white matter right */ CEREBRAL_WHITE_MATTER_RIGHT, /** Cortex not specified */ CORTEX, /** Left Cerebral Cortex */ CORTEX_LEFT, /** Right Cerebral Cortex*/ CORTEX_RIGHT, /** Left Ventral Diencephalon */ DIENCEPHALON_VENTRAL_LEFT, /** Right Ventral Diencephalon */ DIENCEPHALON_VENTRAL_RIGHT, /** Left Hippocampus */ HIPPOCAMPUS_LEFT, /** Right Hippocampus */ HIPPOCAMPUS_RIGHT, /** Left Pallidum */ PALLIDUM_LEFT, /** Right Pallidum */ PALLIDUM_RIGHT, /** Other structure not specified */ OTHER, /** Other grey matter */ OTHER_GREY_MATTER, /** Other white matter */ OTHER_WHITE_MATTER, /** Left Putamen */ PUTAMEN_LEFT, /** Right Putamen */ PUTAMEN_RIGHT, // /** Left Subcortical White Matter */ // SUBCORTICAL_WHITE_MATTER_LEFT, // /** Right Subcortical White Matter */ // SUBCORTICAL_WHITE_MATTER_RIGHT, /** Left Thalamus */ THALAMUS_LEFT, /** Right Thalamus */ THALAMUS_RIGHT }; ~StructureEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static AString toCiftiName(Enum enumValue); static Enum fromCiftiName(const AString& ciftiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static bool isRight(const Enum enumValue); static bool isLeft(const Enum enumValue); static bool isSingleStructure(const Enum enumValue); static bool isCortexContralateral(const Enum enumValueA, const Enum enumValueB); static Enum getContralateralStructure(const Enum enumValue); private: StructureEnum(const Enum enumValue, const AString& name, const AString& guiName); static const StructureEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; static int32_t integerCodeGenerator; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; #ifdef __STRUCTURE_ENUM_DECLARE__ std::vector StructureEnum::enumData; bool StructureEnum::initializedFlag = false; int32_t StructureEnum::integerCodeGenerator = 0; #endif // __STRUCTURE_ENUM_DECLARE__ } // namespace #endif //__STRUCTURE_ENUM__H_ workbench-1.1.1/src/Common/SystemUtilities.cxx000066400000000000000000000407031255417355300214350ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #include #include #include #include "CaretOMP.h" #ifndef _WIN32 #include "execinfo.h" #else #include "Windows.h" #endif #include "CaretCommandLine.h" #include "CaretLogger.h" #include "SystemUtilities.h" using namespace caret; AString commandLine;//used to store the command line for output by unexpected handler /** * Constructor. */ SystemUtilities::SystemUtilities() { } /** * Destructor */ SystemUtilities::~SystemUtilities() { } #include /** * Get the backtrace in a string with each frame * separated by a newline character. * * @return * String containing the backtrace. */ AString SystemUtilities::getBackTrace() { /* #ifdef CARET_OS_WINDOWS return ""; #else // CARET_OS_WINDOWS std::stringstream str; void* callstack[1024]; int numFrames = backtrace(callstack, 1024); char** symbols = backtrace_symbols(callstack, numFrames); for (int i = 0; i < numFrames; i++) { str << symbols[i] << std::endl; } return AString::fromStdString(str.str()); #endif // CARET_OS_WINDOWS */ std::vector backTrace; SystemUtilities::getBackTrace(backTrace); std::stringstream str; for (std::vector::const_iterator iter = backTrace.begin(); iter != backTrace.end(); iter++) { str << *iter << std::endl; } return AString::fromStdString(str.str()); } /** * Get the backtrace with the frames in a vector of strings. * * @param backTraceOut * Vector of string containg the call stack. */ void SystemUtilities::getBackTrace(std::vector& backTraceOut) { backTraceOut.clear(); #ifdef CARET_OS_WINDOWS #else // CARET_OS_WINDOWS void* callstack[1024]; int numFrames = backtrace(callstack, 1024); char** symbols = backtrace_symbols(callstack, numFrames); for (int i = 0; i < numFrames; i++) { backTraceOut.push_back(symbols[i]); } free(symbols); #endif // CARET_OS_WINDOWS } void SystemUtilities::getBackTrace(SystemBacktrace& backTraceOut) { #ifdef CARET_OS_WINDOWS #else // CARET_OS_WINDOWS backTraceOut.m_numFrames = backtrace(backTraceOut.m_callstack, 1024); #endif // CARET_OS_WINDOWS } SystemBacktrace::SystemBacktrace() { #ifndef CARET_OS_WINDOWS m_numFrames = 0; #endif } /** * @return String containing the backtrace symbols. */ AString SystemBacktrace::toSymbolString() const { std::stringstream str; #ifdef CARET_OS_WINDOWS #else // CARET_OS_WINDOWS char** symbols = backtrace_symbols(m_callstack, m_numFrames); for (int i = 0; i < m_numFrames; ++i) { str << symbols[i] << std::endl; } free(symbols); #endif // CARET_OS_WINDOWS return AString::fromStdString(str.str()); } /** * Get the temporary directory. * * @return Path of temporary directory. * */ AString SystemUtilities::getTempDirectory() { return QDir::tempPath(); } /** * Get the user's name. * * @return Name of user. * */ AString SystemUtilities::getUserName() { #ifdef CARET_OS_WINDOWS const QString name(getenv("USERNAME")); #else // CARET_OS_WINDOWS QString name(getlogin()); if (name.isEmpty()) { name = getenv("USERNAME"); } #endif // CARET_OS_WINDOWS return name; } /** * @return The four digit year. */ AString SystemUtilities::getYear() { QDateTime dateTime = QDateTime::currentDateTime(); AString s = dateTime.toString("yyyy"); return s; } /** * Get the date as ISO format yyyy-mm-dd (2009-12-09) * * @return A string containing the date. * */ AString SystemUtilities::getDate() { QDateTime dateTime = QDateTime::currentDateTime(); AString s = dateTime.toString("yyyy:MM:dd"); return s; } /** * Get the time as ISO format hh:mm:ss (11:42:28) * * @return A string containing the time. * */ AString SystemUtilities::getTime() { QDateTime dateTime = QDateTime::currentDateTime(); AString s = dateTime.toString("hh:mm:ss"); return s; } /** * Get the date and time as month, day, year, hour:min:sec AM/PM * * @return Data and Time. * */ AString SystemUtilities::getDateAndTime() { AString s = (SystemUtilities::getDate() + "T" + SystemUtilities::getTime()); return s; } /** * Is the operating system the Microsoft Windows operating system? * * @return true if the operating system is Microsoft Windows. * */ bool SystemUtilities::isWindowsOperatingSystem() { #ifdef CARET_OS_WINDOWS return true; #endif return false; } /** * Is the operating system the Mac OSX operating system? * * @return true if the operating system is Mac OSX. * */ bool SystemUtilities::isMacOperatingSystem() { #ifdef CARET_OS_MACOSX return true; #endif return false; } /** * Get the number of processors in the computer. * * @return The number of processors. * */ int32_t SystemUtilities::getNumberOfProcessors() { #ifdef CARET_OMP return omp_get_num_procs(); #endif return 1; } /** * Unit testing of assertions. * * @param stream * Stream to which messages are written. * @param isVerbose * Print detailed messages. */ void SystemUtilities::unitTest(std::ostream& stream, const bool /*isVerbose*/) { #ifdef NDEBUG stream << "Unit testing of CaretAssertion will not take place since software is not compiled with debug on." << std::endl; return; #endif stream << "SystemUtilities::unitTest is starting" << std::endl; /* * Redirect std::err to the string stream. */ std::ostringstream str; std::streambuf* cerrSave = std::cerr.rdbuf(); std::cerr.rdbuf(str.rdbuf()); testRelativePath("/surface02/john/caret_data/HUMAN.COLIN.ATLAS", "/surface02/john/caret_data/HUMAN.COLIN.ATLAS/RIGHT_HEM", ".."); testRelativePath("/surface02/john/caret_data/HUMAN.COLIN.ATLAS/RIGHT_HEM", "/surface02/john/caret_data/HUMAN.COLIN.ATLAS", "RIGHT_HEM"); testRelativePath("/surface02/john/caret_data/HUMAN.COLIN.ATLAS/RIGHT_HEM/subdir", "/surface02/john/caret_data/HUMAN.COLIN.ATLAS/RIGHT_HEM", "subdir"); testRelativePath("/surface02/john/caret_data/HUMAN.COLIN.ATLAS/LEFT_HEM/subdir", "/surface02/john/caret_data/HUMAN.COLIN.ATLAS/RIGHT_HEM", "../LEFT_HEM/subdir"); testRelativePath("root:/var/etc", "remove:/usr/local", "root:/var/etc"); // testRelativePath("/Volumes/DS4600/caret7_gui_design/data/HCP_demo/Glasser_PilotIII.L.very_inflated.20k_fs_LR.surf.gii", // "/Volumes/DS4600/caret7_gui_design/data/HCP_demo/border.spec", // "border.spec"); /* * Restore std::err */ std::cerr.rdbuf(cerrSave); stream << "SystemUtilities::unitTest has ended" << std::endl << std::endl;; } /** * Test the relative path method. * @param otherPath - determine relative path to otherpath * @param myPath - from myPath * @param correctResult - The correct result * @return true if test passes. * */ bool SystemUtilities::testRelativePath( const AString& otherPath, const AString& myPath, const AString& correctResult) { bool correctFlag = false; AString result = relativePath(otherPath, myPath); if (result == correctResult) { correctFlag = true; } else { std::cerr << "SystemUtilities.relativePath() failed:" << std::endl; std::cerr << " otherPath: " + otherPath << std::endl; std::cerr << " myPath: " + myPath << std::endl; std::cerr << " result: " + result << std::endl; std::cerr << " correct: " + correctResult << std::endl; } return correctFlag; } /** * @return A Universally Unique Identifier (UUID). */ AString SystemUtilities::createUniqueID() { const AString uuid = QUuid::createUuid().toString(); return uuid; } /** * Given the directory "mypath", determine the relative path to "otherpath". * Both input paths must be absolute paths, otherwise, otherPathIn is * returned. * * Examples: * otherpath - "/surface02/john/caret_data/HUMAN.COLIN.ATLAS" * mypath - "/surface02/john/caret_data/HUMAN.COLIN.ATLAS/RIGHT_HEM" * result - ".."; * * otherpath - "/surface02/john/caret_data/HUMAN.COLIN.ATLAS/RIGHT_HEM/subdir" * mypath - "/surface02/john/caret_data/HUMAN.COLIN.ATLAS/RIGHT_HEM" * result - "subdir"; * * otherpath - "/surface02/john/caret_data/HUMAN.COLIN.ATLAS/LEFT_HEM/subdir" * mypath - "/surface02/john/caret_data/HUMAN.COLIN.ATLAS/RIGHT_HEM" * result - "../LEFT_HEM/subdir"; * * @param otherPathIn - The path for which relative path is sought. * @param myPathIn - Get the path from this * @return The relative path * */ AString SystemUtilities::relativePath( const AString& otherPathIn, const AString& myPathIn) { AString result = otherPathIn; // // Check for either path being empty // if (otherPathIn.isEmpty() || myPathIn.isEmpty()) { return result; } #ifdef CARET_OS_WINDOWS // // Both paths must be absolute paths // if (otherPathIn.indexOf(":") < 0) { return result; } if (myPathIn.indexOf(":") < 0) { return result; } #else // // Both paths must be absolute paths // if ((otherPathIn[0] != '/') || (myPathIn[0] != '/')) { return result; } #endif QStringList otherPath = QDir::cleanPath(otherPathIn).split(QRegExp("[/\\\\]"), QString::SkipEmptyParts); QStringList myPath = QDir::cleanPath(myPathIn).split(QRegExp("[/\\\\]"), QString::SkipEmptyParts); const unsigned int minLength = std::min(myPath.size(), otherPath.size()); int sameCount = 0; for (unsigned int i = 0; i < minLength; i++) { if (myPath[i] == otherPath[i]) { //cout << "Match: |" << myPath[i] << "|" << std::endl; sameCount++; } else { break; } } //cout << "same count: " << sameCount << std::endl; // // Is root of both paths different // if (sameCount == 0) { result = otherPathIn; } //const char separator[2] = { QDir::separator(), '\0' }; // // Is other path a subdirectory of mypath // if (sameCount == myPath.size()) { result = ""; for (int j = sameCount; j < otherPath.size(); j++) { result.append(otherPath[j]); if (j < (otherPath.size() - 1)) { result.append(QDir::separator()); } } } // // otherpath is above this one // result = ""; for (int j = sameCount; j < myPath.size(); j++) { result.append(".."); if (j < (myPath.size() - 1)) { result.append(QDir::separator()); } } for (int k = sameCount; k < otherPath.size(); k++) { if (result.isEmpty() == false) { result.append(QDir::separator()); } result.append(otherPath[k]); } return result; } /** * Unexpected handler */ static void unexpectedHandler() { std::cerr << "While running '" << caret_global_commandLine << "':" << std::endl; std::cerr << std::endl; std::cerr << "ERROR: unhandled exception." << std::endl; //if (theMainWindow != NULL) { const AString msg("Workbench will be terminating due to an unexpected exception.\n" "abort() will be called and a core file may be created."); std::cerr << msg << std::endl; //QMessageBox::critical(theMainWindow, "ERROR", msg); //} std::cerr << SystemUtilities::getBackTrace() << std::endl; abort(); } /** * New handler */ static void newHandler() { std::cerr << "While running '" << caret_global_commandLine << "':" << std::endl; std::ostringstream str; str << "\n" << "OUT OF MEMORY\n" << "\n" << "This means that Workbench is unable to get memory that it needs.\n" << "Possible causes:\n" << " (1) Your computer lacks sufficient RAM.\n" << " (2) Swap space is too small (you might increase it).\n" << " (3) Your computer may be using an non-English character \n" << " set. Try switching to the English character set.\n" << "\n"; std::cerr << str.str().c_str() << std::endl; std::cerr << SystemUtilities::getBackTrace() << std::endl; abort(); //if (theMainWindow != NULL) { // QMessageBox::critical(theMainWindow, "OUT OF MEMORY", // "Out of memory, Caret terminating"); // std::exit(-1); //} } /** * Set the handler for an unexpected (uncaught) exception. */ void SystemUtilities::setUnexpectedHandler() { std::set_unexpected(unexpectedHandler); } /** * Set the handler for when "operator new" is unable to allocate memory. * This new handler will print a message to the terminal containing a * backtrace and then calls abort to end the program. * * NOTE: If this new handler is set, "operator new" WILL NOT * throw a std::bad_alloc exception. */ void SystemUtilities::setNewHandler() { std::set_new_handler(newHandler); } /** * Return the current directory as indicated * by the system. In most cases, use the * methods in Brain to get and set the current * directory since it may be possible to have * multiple Brains each of which has its current * directory set to the directory containing the * SpecFile that was read. * * @return The path of the current directory. */ AString SystemUtilities::systemCurrentDirectory() { return QDir::currentPath(); } /* * From http://developer.qt.nokia.com/wiki/How_to_create_a_splash_screen_with_an_induced_delay */ class Sleeper : public QThread { public: static void sleepSeconds(const float seconds) { if (seconds > 0.0) { const unsigned long milliseconds = seconds * 1000.0; QThread::msleep(milliseconds); } } }; /** * Sleep for the specified number of seconds. The minimum * is one millisecond (0.001). * @param numberOfSeconds * Number of seconds to sleep. */ void SystemUtilities::sleepSeconds(const float numberOfSeconds) { Sleeper::sleepSeconds(numberOfSeconds); } /** * Get the workbench home directory * @return workbenchHomeDirectory * The path to the workbench installation */ AString SystemUtilities::getWorkbenchHome() { static QString workbenchHomeDirectory; if(workbenchHomeDirectory.isEmpty() == false) { return workbenchHomeDirectory; } QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); workbenchHomeDirectory = env.value ( AString("WORKBENCH_HOME") ); if (workbenchHomeDirectory.isEmpty()) { workbenchHomeDirectory = QCoreApplication::applicationDirPath(); if (workbenchHomeDirectory.isEmpty() == false) { #ifdef CARET_OS_MACOSX const bool appFlag = (workbenchHomeDirectory.indexOf(".app/") > 0); if (appFlag) { QDir dir(workbenchHomeDirectory); dir.cdUp(); dir.cdUp(); dir.cdUp(); workbenchHomeDirectory = dir.absolutePath(); } #endif } CaretLogFine("Workbench Home Directory: " + workbenchHomeDirectory); } return workbenchHomeDirectory = QDir::toNativeSeparators(workbenchHomeDirectory); } workbench-1.1.1/src/Common/SystemUtilities.h000066400000000000000000000050731255417355300210630ustar00rootroot00000000000000#ifndef __SYSTEMUTILITIES_H__ #define __SYSTEMUTILITIES_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include namespace caret { class SystemBacktrace { #ifdef CARET_OS_WINDOWS #else // CARET_OS_WINDOWS void* m_callstack[1024]; int m_numFrames; #endif // CARET_OS_WINDOWS public: SystemBacktrace(); AString toSymbolString() const; friend class SystemUtilities; }; /** * Methods to help out with files and directories. */ class SystemUtilities { private: SystemUtilities(); public: virtual ~SystemUtilities(); public: static AString getBackTrace(); static void getBackTrace(std::vector& backTraceOut); static void getBackTrace(SystemBacktrace& backTraceOut); static AString getTempDirectory(); static AString getUserName(); static AString getYear(); static AString getDate(); static AString getTime(); static AString getDateAndTime(); static bool isWindowsOperatingSystem(); static bool isMacOperatingSystem(); static int32_t getNumberOfProcessors(); static AString createUniqueID(); static void unitTest(std::ostream& stream, const bool isVerbose); static bool testRelativePath( const AString& otherPath, const AString& myPath, const AString& correctResult); static AString relativePath( const AString& otherPathIn, const AString& myPathIn); static void setUnexpectedHandler(); static void setNewHandler(); static AString systemCurrentDirectory(); static void sleepSeconds(const float numberOfSeconds); static AString getWorkbenchHome(); }; } // namespace #endif // __SYSTEMUTILITIES_H__ workbench-1.1.1/src/Common/TileTabsConfiguration.cxx000066400000000000000000000552651255417355300225250ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __TILE_TABS_CONFIGURATION_DECLARE__ #include "TileTabsConfiguration.h" #undef __TILE_TABS_CONFIGURATION_DECLARE__ #include #include "CaretAssert.h" #include "CaretLogger.h" #include "SystemUtilities.h" using namespace caret; /** * \class caret::TileTabsConfiguration * \brief Defines a tile tabs configuration * \ingroup Common */ /** * Constructor that creates a 2 by 2 configuration. */ TileTabsConfiguration::TileTabsConfiguration() : CaretObject() { initialize(); } /** * Destructor. */ TileTabsConfiguration::~TileTabsConfiguration() { } /** * Copy constructor. * @param obj * Object that is copied. */ TileTabsConfiguration::TileTabsConfiguration(const TileTabsConfiguration& obj) : CaretObject(obj) { this->copyHelperTileTabsConfiguration(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ TileTabsConfiguration& TileTabsConfiguration::operator=(const TileTabsConfiguration& obj) { if (this != &obj) { CaretObject::operator=(obj); this->copyHelperTileTabsConfiguration(obj); } return *this; } /** * Initialize an instance of a tile tabs configuration. */ void TileTabsConfiguration::initialize() { m_rowStretchFactors.resize(getMaximumNumberOfRows(), 1.0); m_columnStretchFactors.resize(getMaximumNumberOfColumns(), 1.0); setNumberOfRows(2); setNumberOfColumns(2); m_defaultConfigurationFlag = false; m_uniqueIdentifier = SystemUtilities::createUniqueID(); } /** * Helps with copying an object of this type. * @param obj * Object that is copied. */ void TileTabsConfiguration::copyHelperTileTabsConfiguration(const TileTabsConfiguration& obj) { m_numberOfColumns = obj.m_numberOfColumns; m_numberOfRows = obj.m_numberOfRows; m_rowStretchFactors = obj.m_rowStretchFactors; m_columnStretchFactors = obj.m_columnStretchFactors; m_name = obj.m_name; //DO NOT CHANGE THE UNIQUE IDENTIFIER: m_uniqueIdentifier } /** * Get the row heights and column widths for this tile tabs configuration using the * given window width and height. * * @param windowWidth * Width of window. * @param windowHeight * Height of window. * @param numberOfModelsToDraw * Number of models to draw. * @param rowHeightsOut * Output containing height of each row. * @param columnWidthsOut * Output containing width of each column. * @return * True if the ouput is valid, else false. */ bool TileTabsConfiguration::getRowHeightsAndColumnWidthsForWindowSize(const int32_t windowWidth, const int32_t windowHeight, const int32_t numberOfModelsToDraw, std::vector& rowHeightsOut, std::vector& columnWidthsOut) { /* * NOTE: When computing widths and heights, do not round. * Rounding may cause the bottom most row or column to extend * outside the graphics region. Shrinking the last row or * column is not desired since it might cause the last model * to be drawn slightly smaller than the others. */ int32_t numRows = 0; int32_t numCols = 0; rowHeightsOut.clear(); columnWidthsOut.clear(); if (isDefaultConfiguration()) { /* * Update number of rows/columns in the default configuration * so that if a scene is saved, the correct number of rows * and columns are saved to the scene. */ updateDefaultConfigurationRowsAndColumns(numberOfModelsToDraw); numRows = getNumberOfRows(); numCols = getNumberOfColumns(); for (int32_t i = 0; i < numRows; i++) { rowHeightsOut.push_back(windowHeight / numRows); } for (int32_t i = 0; i < numCols; i++) { columnWidthsOut.push_back(windowWidth / numCols); } } else { /* * Rows/columns from user configuration */ numRows = getNumberOfRows(); numCols = getNumberOfColumns(); /* * Determine height of each row */ float rowStretchTotal = 0.0; for (int32_t i = 0; i < numRows; i++) { rowStretchTotal += getRowStretchFactor(i); } CaretAssert(rowStretchTotal > 0.0); for (int32_t i = 0; i < numRows; i++) { const int32_t h = static_cast((getRowStretchFactor(i) / rowStretchTotal) * windowHeight); rowHeightsOut.push_back(h); } /* * Determine width of each column */ float columnStretchTotal = 0.0; for (int32_t i = 0; i < numCols; i++) { columnStretchTotal += getColumnStretchFactor(i); } CaretAssert(columnStretchTotal > 0.0); for (int32_t i = 0; i < numCols; i++) { const int32_t w = static_cast((getColumnStretchFactor(i) / columnStretchTotal) * windowWidth); columnWidthsOut.push_back(w); } } if ((numRows == static_cast(rowHeightsOut.size())) && (numCols == static_cast(columnWidthsOut.size()))) { /* * Verify all rows fit within the window */ int32_t rowHeightsSum = 0; for (int32_t i = 0; i < numRows; i++) { rowHeightsSum += rowHeightsOut[i]; } if (rowHeightsSum > windowHeight) { CaretLogSevere("PROGRAM ERROR: Tile Tabs total row heights exceed window height"); rowHeightsOut[numRows - 1] -= (rowHeightsSum - windowHeight); } /* * Adjust width of last column so that it does not extend beyond viewport */ int32_t columnWidthsSum = 0; for (int32_t i = 0; i < numCols; i++) { columnWidthsSum += columnWidthsOut[i]; } if (columnWidthsSum > windowWidth) { CaretLogSevere("PROGRAM ERROR: Tile Tabs total row heights exceed window height"); columnWidthsOut[numCols - 1] = columnWidthsSum - windowWidth; } CaretLogFiner("Tile Tabs Row Heights: " + AString::fromNumbers(rowHeightsOut, ", ")); CaretLogFiner("Tile Tabs Column Widths: " + AString::fromNumbers(columnWidthsOut, ", ")); return true; } const QString msg("Row and heights failed rows=" + AString::number(numRows) + " rowHeights=" + AString::number(rowHeightsOut.size()) + " cols=" + AString::number(numCols) + " rowHeights=" + AString::number(columnWidthsOut.size())); CaretAssertMessage(0, msg); CaretLogSevere(msg); return false; } /** * @return the name of the tile tabs configuration. */ AString TileTabsConfiguration::getName() const { return m_name; } /** * @return Get the unique identifier that uniquely identifies each configuration. */ AString TileTabsConfiguration::getUniqueIdentifier() const { return m_uniqueIdentifier; } /** * Set the name of the tile tabs configuration. * * @param name * New name for configuration. */ void TileTabsConfiguration::setName(const AString& name) { m_name = name; } /** * @return Number of rows. */ int32_t TileTabsConfiguration::getNumberOfRows() const { return m_numberOfRows; } /** * Set number of rows. * * @param numberOfRows * New number of rows. */ void TileTabsConfiguration::setNumberOfRows(const int32_t numberOfRows) { CaretAssert(numberOfRows >= 1); m_numberOfRows = numberOfRows; if (m_numberOfRows > getMaximumNumberOfRows()) { CaretLogSevere("Requested number of rows is " + AString::number(m_numberOfRows) + " but maximum is " + getMaximumNumberOfRows()); m_numberOfRows = getMaximumNumberOfRows(); } } /** * @return Number of columns. */ int32_t TileTabsConfiguration::getNumberOfColumns() const { return m_numberOfColumns; } /** * Set number of rows. * * @param numberOfColumns * New number of rows. */ void TileTabsConfiguration::setNumberOfColumns(const int32_t numberOfColumns) { CaretAssert(numberOfColumns >= 1); m_numberOfColumns = numberOfColumns; if (m_numberOfColumns > getMaximumNumberOfColumns()) { CaretLogSevere("Requested number of columns is " + AString::number(m_numberOfColumns) + " but maximum is " + getMaximumNumberOfColumns()); m_numberOfColumns = getMaximumNumberOfColumns(); } } /** * Get stretch factor for a column. * * @param columnIndex * Index of the column. * @return * Stretch factor for the column. */ float TileTabsConfiguration::getColumnStretchFactor(const int32_t columnIndex) const { CaretAssertVectorIndex(m_columnStretchFactors, columnIndex); return m_columnStretchFactors[columnIndex]; } /** * Set stretch factor for a column. * * @param columnIndex * Index of the column. * @param stretchFactor * Stretch factor for the column. */ void TileTabsConfiguration::setColumnStretchFactor(const int32_t columnIndex, const float stretchFactor) { CaretAssertVectorIndex(m_columnStretchFactors, columnIndex); m_columnStretchFactors[columnIndex] = stretchFactor; } /** * Get stretch factor for a column. * * @param columnIndex * Index of the column. * @return * Stretch factor for the column. */ float TileTabsConfiguration::getRowStretchFactor(const int32_t rowIndex) const { CaretAssertVectorIndex(m_rowStretchFactors, rowIndex); return m_rowStretchFactors[rowIndex]; } /** * Set stretch factor for a column. * * @param rowIndex * Index of the row. * @param stretchFactor * Stretch factor for the column. */ void TileTabsConfiguration::setRowStretchFactor(const int32_t rowIndex, const float stretchFactor) { CaretAssertVectorIndex(m_rowStretchFactors, rowIndex); m_rowStretchFactors[rowIndex] = stretchFactor; } /** * @return Is this the default configuration? Each browser window * always has ONE default configuration which displays all tabs. */ bool TileTabsConfiguration::isDefaultConfiguration() const { return m_defaultConfigurationFlag; } /** * Set the default configuration status. This should only be called by * the browser window which contains one tile tabs configuration that is * used by the browser window for display of all tabs. */ void TileTabsConfiguration::setDefaultConfiguration(const bool defaultConfiguration) { m_defaultConfigurationFlag = defaultConfiguration; } /** * Updates the number of rows and columns in this default configuration * based upon the number of tabs. If this is NOT the default configuration * (isDefaultConfiguration() returns false), no action is taken. * * Since screen width typically exceeds height, ensure the number of * columns is always greater than the number of rows. */ void TileTabsConfiguration::updateDefaultConfigurationRowsAndColumns(const int32_t numberOfTabs) { if (isDefaultConfiguration()) { int32_t numRows = (int)std::sqrt((double)numberOfTabs); int32_t numCols = numRows; int32_t row2 = numRows * numRows; if (row2 < numberOfTabs) { numCols++; } if ((numRows * numCols) < numberOfTabs) { numRows++; } setNumberOfRows(numRows); setNumberOfColumns(numCols); } } /** * @return Encoded tile tabs configuration in XML */ AString TileTabsConfiguration::encodeInXML() const { QDomDocument doc(s_rootTagName); QDomElement root = doc.createElement(s_rootTagName); doc.appendChild(root); QDomElement versionTag = doc.createElement(s_versionTagName); versionTag.setAttribute(s_versionNumberAttributeName, (int)1); root.appendChild(versionTag); QDomElement nameTag = doc.createElement(s_nameTagName); nameTag.appendChild(doc.createTextNode(m_name)); root.appendChild(nameTag); QDomElement uniqueIdentifierTag = doc.createElement(s_uniqueIdentifierTagName); uniqueIdentifierTag.appendChild(doc.createTextNode(m_uniqueIdentifier)); root.appendChild(uniqueIdentifierTag); QDomElement rowStretchFactorsTag = doc.createElement(s_rowStretchFactorsTagName); rowStretchFactorsTag.setAttribute(s_rowStretchFactorsTotalCountAttributeName, static_cast(m_rowStretchFactors.size())); rowStretchFactorsTag.setAttribute(s_rowStretchFactorsSelectedCountAttributeName, static_cast(m_numberOfRows)); rowStretchFactorsTag.appendChild(doc.createTextNode(AString::fromNumbers(m_rowStretchFactors, " "))); root.appendChild(rowStretchFactorsTag); QDomElement columnStretchFactorsTag = doc.createElement(s_columnStretchFactorsTagName); columnStretchFactorsTag.setAttribute(s_columnStretchFactorsTotalCountAttributeName, static_cast(m_columnStretchFactors.size())); columnStretchFactorsTag.setAttribute(s_columnStretchFactorsSelectedCountAttributeName, static_cast(m_numberOfColumns)); columnStretchFactorsTag.appendChild(doc.createTextNode(AString::fromNumbers(m_columnStretchFactors, " "))); root.appendChild(columnStretchFactorsTag); const AString xmlString = doc.toString(); return xmlString; } /** * Decode the tile tabs configuration from XML. * * @param xmlString * String containing XML. * @return * True if configuration was successfully read from the XML, else false. */ bool TileTabsConfiguration::decodeFromXML(const AString& xmlString) { m_defaultConfigurationFlag = false; setNumberOfRows(2); setNumberOfColumns(2); try { QDomDocument doc(s_rootTagName); if (doc.setContent(xmlString) == false) { throw CaretException("Error parsing DomDocument"); } QDomNodeList nodeList = doc.elementsByTagName(s_versionTagName); if (nodeList.isEmpty()) { throw CaretException("Error finding version tag"); } QDomElement versionElement = nodeList.at(0).toElement(); if (versionElement.isNull()) { throw CaretException("Error finding version element"); } const AString versionNumberString = versionElement.attribute(s_versionNumberAttributeName, ""); if (versionNumberString.isEmpty()) { throw CaretException("Error finding version number attribute"); } const int versionNumber = versionNumberString.toInt(); if (versionNumber == 1) { parseVersionOneXML(doc); } else { throw CaretException("Invalid version number attribute " + versionNumberString); } } catch (const CaretException& e) { CaretLogSevere("Error parsing tile tabs configuration XML:\n" + e.whatString() + "\n\n" + xmlString); return false; } return true; } /** * Parse XML for Version One. * * @param doc * XML DOM document. */ void TileTabsConfiguration::parseVersionOneXML(QDomDocument& doc) { QDomNodeList nameNodeList = doc.elementsByTagName(s_nameTagName); if (nameNodeList.isEmpty()) { throw CaretException("Error finding name tag"); } QDomElement nameElement = nameNodeList.at(0).toElement(); if (nameElement.isNull()) { throw CaretException("Error finding name element"); } m_name = nameElement.text(); QDomNodeList uniqueIdNodeList = doc.elementsByTagName(s_uniqueIdentifierTagName); if (uniqueIdNodeList.isEmpty()) { CaretLogWarning("Tile Tabs Configuration " + m_name + " is missing its unique identifier"); m_uniqueIdentifier = SystemUtilities::createUniqueID(); } else { QDomElement uniqueIdElement = uniqueIdNodeList.at(0).toElement(); if (uniqueIdElement.isNull()) { throw CaretException("Error finding unique identifier element"); } m_uniqueIdentifier = uniqueIdElement.text(); } QDomNodeList rowNodeList = doc.elementsByTagName(s_rowStretchFactorsTagName); if (rowNodeList.isEmpty()) { throw CaretException("Error finding row stretch factors tag"); } QDomElement rowElement = rowNodeList.at(0).toElement(); if (rowElement.isNull()) { throw CaretException("Error finding row element"); } const AString numberOfRowsString = rowElement.attribute(s_rowStretchFactorsSelectedCountAttributeName, ""); if (numberOfRowsString.isEmpty()) { throw CaretException("Error finding number of rows attribute"); } const int32_t selectedNumberOfRows = numberOfRowsString.toInt(); if (selectedNumberOfRows <= 0) { throw CaretException("Invalid number of rows attribute " + numberOfRowsString); } const AString totalNumberOfRowsString = rowElement.attribute(s_rowStretchFactorsTotalCountAttributeName, ""); int32_t totalNumberOfRows = 0; if (totalNumberOfRowsString.isEmpty()) { CaretLogWarning("Total number of rows attribute is missing."); } else { totalNumberOfRows = totalNumberOfRowsString.toInt(); } const AString rowStretchFactorsText = rowElement.text(); std::vector rowStretchFactors; AString::toNumbers(rowStretchFactorsText, rowStretchFactors); if (static_cast(rowStretchFactors.size()) != totalNumberOfRows) { throw CaretException("Stretch factor number of rows is " + AString::number(totalNumberOfRows) + " but have " + AString::number(static_cast(rowStretchFactors.size())) + " stretch factors."); } QDomNodeList columnNodeList = doc.elementsByTagName(s_columnStretchFactorsTagName); if (columnNodeList.isEmpty()) { throw CaretException("Error finding column stretch factors tag"); } QDomElement columnElement = columnNodeList.at(0).toElement(); if (columnElement.isNull()) { throw CaretException("Error finding column element"); } const AString numberOfColumnsString = columnElement.attribute(s_columnStretchFactorsSelectedCountAttributeName, ""); if (numberOfColumnsString.isEmpty()) { throw CaretException("Error finding number of columns attribute"); } const int32_t selectedNumberOfColumns = numberOfColumnsString.toInt(); if (selectedNumberOfColumns <= 0) { throw CaretException("Invalid number of columns attribute " + numberOfColumnsString); } const AString totalNumberOfColumnsString = columnElement.attribute(s_columnStretchFactorsTotalCountAttributeName, ""); int32_t totalNumberOfColumns = 0; if (totalNumberOfColumnsString.isEmpty()) { CaretLogWarning("Total number of columns attribute is missing."); } else { totalNumberOfColumns = totalNumberOfColumnsString.toInt(); } const AString columnStretchFactorsText = columnElement.text(); std::vector columnStretchFactors; AString::toNumbers(columnStretchFactorsText, columnStretchFactors); if (static_cast(columnStretchFactors.size()) != totalNumberOfColumns) { throw CaretException("Stretch factor number of columns is " + AString::number(totalNumberOfColumns) + " but have " + AString::number(static_cast(columnStretchFactors.size())) + " stretch factors."); } setNumberOfRows(selectedNumberOfRows); setNumberOfColumns(selectedNumberOfColumns); const int32_t maxRowStretchFactors = std::min(totalNumberOfRows, static_cast(m_rowStretchFactors.size())); for (int32_t i = 0; i < maxRowStretchFactors; i++) { m_rowStretchFactors[i] = rowStretchFactors[i]; } const int32_t maxColumnStretchFactors = std::min(totalNumberOfColumns, static_cast(m_columnStretchFactors.size())); for (int32_t i = 0; i < maxColumnStretchFactors; i++) { m_columnStretchFactors[i] = columnStretchFactors[i]; } } /** * Compare two tile tabs configurations by name. * * @param ttc1 * First tile tab configuration. * @param ttc2 * Second tile tab configuration. * @return * True if ttc1 is "less than" when compared by name, else false. */ bool TileTabsConfiguration::lessThanComparisonByName(const TileTabsConfiguration* ttc1, const TileTabsConfiguration* ttc2) { if (ttc1->getName() < ttc2->getName()) { return true; } return false; } workbench-1.1.1/src/Common/TileTabsConfiguration.h000066400000000000000000000134641255417355300221450ustar00rootroot00000000000000#ifndef __TILE_TABS_CONFIGURATION_H__ #define __TILE_TABS_CONFIGURATION_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretException.h" #include "CaretObject.h" class QDomDocument; namespace caret { class TileTabsConfiguration : public CaretObject { public: TileTabsConfiguration(); virtual ~TileTabsConfiguration(); TileTabsConfiguration(const TileTabsConfiguration& obj); TileTabsConfiguration& operator=(const TileTabsConfiguration& obj); bool getRowHeightsAndColumnWidthsForWindowSize(const int32_t windowWidth, const int32_t windowHeight, const int32_t numberOfModelsToDraw, std::vector& rowHeightsOut, std::vector& columnWidthsOut); AString getName() const; void setName(const AString& name); AString getUniqueIdentifier() const; int32_t getNumberOfRows() const; void setNumberOfRows(const int32_t numberOfRows); int32_t getNumberOfColumns() const; void setNumberOfColumns(const int32_t numberOfColumns); float getColumnStretchFactor(const int32_t columnIndex) const; void setColumnStretchFactor(const int32_t columnIndex, const float stretchFactor); float getRowStretchFactor(const int32_t rowIndex) const; void setRowStretchFactor(const int32_t rowIndex, const float stretchFactor); bool isDefaultConfiguration() const; void setDefaultConfiguration(const bool defaultConfiguration); AString encodeInXML() const; bool decodeFromXML(const AString& xmlString); static bool lessThanComparisonByName(const TileTabsConfiguration* ttc1, const TileTabsConfiguration* ttc2); /** * @return Maximum number of rows in a tile tabs configuration */ static inline int32_t getMaximumNumberOfRows() { return 20; } /** * @return Maximum number of columns in a tile tabs configuration */ static inline int32_t getMaximumNumberOfColumns() { return 20; } void updateDefaultConfigurationRowsAndColumns(const int32_t numberOfTabs); // ADD_NEW_METHODS_HERE private: void copyHelperTileTabsConfiguration(const TileTabsConfiguration& obj); void parseVersionOneXML(QDomDocument& doc); void initialize(); // ADD_NEW_MEMBERS_HERE AString m_name; bool m_defaultConfigurationFlag; /** Unique identifier does not get copied */ AString m_uniqueIdentifier; int32_t m_numberOfRows; int32_t m_numberOfColumns; std::vector m_rowStretchFactors; std::vector m_columnStretchFactors; static const AString s_rootTagName; static const AString s_versionTagName; static const AString s_nameTagName; static const AString s_uniqueIdentifierTagName; static const AString s_versionNumberAttributeName; static const AString s_columnStretchFactorsTagName; static const AString s_columnStretchFactorsSelectedCountAttributeName; static const AString s_columnStretchFactorsTotalCountAttributeName; static const AString s_rowStretchFactorsTagName; static const AString s_rowStretchFactorsSelectedCountAttributeName; static const AString s_rowStretchFactorsTotalCountAttributeName; }; #ifdef __TILE_TABS_CONFIGURATION_DECLARE__ const AString TileTabsConfiguration::s_rootTagName = "TileTabsConfiguration"; const AString TileTabsConfiguration::s_versionTagName = "Version"; const AString TileTabsConfiguration::s_versionNumberAttributeName = "Number"; const AString TileTabsConfiguration::s_nameTagName = "Name"; const AString TileTabsConfiguration::s_uniqueIdentifierTagName = "UniqueIdentifier"; const AString TileTabsConfiguration::s_columnStretchFactorsTagName = "ColumnStretchFactors"; const AString TileTabsConfiguration::s_columnStretchFactorsSelectedCountAttributeName = "SelectedRowCount"; const AString TileTabsConfiguration::s_columnStretchFactorsTotalCountAttributeName = "TotalRowCount"; const AString TileTabsConfiguration::s_rowStretchFactorsTagName = "RowStretchFactors"; const AString TileTabsConfiguration::s_rowStretchFactorsSelectedCountAttributeName = "SelectedColumnCount"; const AString TileTabsConfiguration::s_rowStretchFactorsTotalCountAttributeName = "TotalColumnCount"; #endif // __TILE_TABS_CONFIGURATION_DECLARE__ } // namespace #endif //__TILE_TABS_CONFIGURATION_H__ workbench-1.1.1/src/Common/TracksModificationInterface.h000066400000000000000000000073751255417355300233100ustar00rootroot00000000000000#ifndef __TRACKSMODIFICATION_H__ #define __TRACKSMODIFICATION_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ namespace caret { /** * \brief Interface for tracking object modification status. * * Interface for tracking an objects modification status. *
* setModified() should set a boolean that indicates * the modified status. *
* clearModified() should clear the boolean that indicates * the modified status AND should call clearModified() * on any members that implement this interface. *
* isModified() should return the boolean that indicates * the modified status. *
*
* When a class DOES extend a class that implements this * interface, it must implement the clearModified() * and the isModified() ONLY IF it contains members * that implement this interface. *
* The clearMethod() must call the parent's clearModified() * method and the clearModified() method on any member * classes that implement this interface. *
* The isModified() method must first call the parent's * isModified() method, and, if true, true true. Otherwise, * return true if a member is modified. * *
* An alternative model of this interface is to add * methods such as addTrackable(TracksModificationInterface) * and removeTrackable(TracksModificationInterface) that * could be used to query and reset the modification status * of any members in the implementing class and subclasses. * In this case, the isModified(), setModified(), and * clearModified() methods would only need to be implemented * in the top-level class. In this case the * addTrackable() and removeTrackable() classes may be * better off in a separate interface. */ class TracksModificationInterface { protected: /** * Constructor. */ TracksModificationInterface() { } /** * Destructor. */ virtual ~TracksModificationInterface() { } private: TracksModificationInterface(const TracksModificationInterface&); TracksModificationInterface& operator=(const TracksModificationInterface&); public: /** * Set the status to modified. */ virtual void setModified() = 0; /** * Set the status to unmodified. */ virtual void clearModified() = 0; /** * Is the object modified? * @return true if modified, else false. */ virtual bool isModified() const = 0; }; class TracksModification : public TracksModificationInterface { /** modification status */ bool modifiedFlag; protected: TracksModification() { modifiedFlag = false; } public: /** * Set the status to modified. */ virtual void setModified() { modifiedFlag = true; } /** * Set the status to unmodified. */ virtual void clearModified() { modifiedFlag = false; } /** * Is the object modified? * @return true if modified, else false. */ virtual bool isModified() const { return modifiedFlag; } }; } // namespace #endif // __TRACKSMODIFICATION_H__ workbench-1.1.1/src/Common/Vector3D.cxx000066400000000000000000000124001255417355300176770ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "MathFunctions.h" #include "Vector3D.h" using namespace std; using namespace caret; Vector3D Vector3D::cross(const Vector3D& right) const { Vector3D ret; MathFunctions::crossProduct(m_vec, right.m_vec, ret.m_vec); return ret; } float Vector3D::dot(const Vector3D& right) const { return MathFunctions::dotProduct(m_vec, right.m_vec); } float Vector3D::length() const { return MathFunctions::vectorLength(m_vec); } float Vector3D::lengthsquared() const { return m_vec[0] * m_vec[0] + m_vec[1] * m_vec[1] + m_vec[2] * m_vec[2]; } Vector3D Vector3D::normal(float* origLength) const { Vector3D ret = *this; if (origLength != NULL) { *origLength = MathFunctions::normalizeVector(ret.m_vec); } else { MathFunctions::normalizeVector(ret.m_vec); } return ret; } float Vector3D::distToLine(const Vector3D& p1, const Vector3D& p2, Vector3D* closePointOut) const { Vector3D diff = p2 - p1; float origLength; Vector3D diffHat = diff.normal(&origLength); if (origLength == 0.0f)//line is degenerate, return distance to point - we could return zero, but this seems like it would be better behaved { if (closePointOut != NULL) *closePointOut = p1; return (*this - p1).length(); } float distAlong = diffHat.dot(*this - p1); Vector3D closePoint = p1 + diffHat * distAlong; if (closePointOut != NULL) *closePointOut = closePoint; return (*this - closePoint).length(); } float Vector3D::distToLineSegment(const Vector3D& p1, const Vector3D& p2, Vector3D* closePointOut) const { float origLength; Vector3D diffHat = (p2 - p1).normal(&origLength); if (origLength == 0.0f)//line segment is degenerate, return distance to point { if (closePointOut != NULL) *closePointOut = p1; return (*this - p1).length(); } float distAlong = diffHat.dot(*this - p1); if (distAlong < 0.0f) distAlong = 0.0f; if (distAlong > origLength) distAlong = origLength; Vector3D closePoint = p1 + diffHat * distAlong; if (closePointOut != NULL) *closePointOut = closePoint; return (*this - closePoint).length(); } Vector3D::Vector3D() { m_vec[0] = 0.0f; m_vec[1] = 0.0f; m_vec[2] = 0.0f; } Vector3D::Vector3D(const float& x, const float& y, const float& z) { m_vec[0] = x; m_vec[1] = y; m_vec[2] = z; } Vector3D::Vector3D(const float* right) { m_vec[0] = right[0]; m_vec[1] = right[1]; m_vec[2] = right[2]; } float& Vector3D::operator[](const int64_t& index) { CaretAssert(index > -1 && index < 3); return m_vec[index]; } const float& Vector3D::operator[](const int64_t& index) const { CaretAssert(index > -1 && index < 3); return m_vec[index]; } float& Vector3D::operator[](const int32_t& index) { CaretAssert(index > -1 && index < 3); return m_vec[index]; } const float& Vector3D::operator[](const int32_t& index) const { CaretAssert(index > -1 && index < 3); return m_vec[index]; } Vector3D Vector3D::operator*(const float& right) const { Vector3D ret = *this; ret *= right; return ret; } Vector3D& Vector3D::operator*=(const float& right) { m_vec[0] *= right; m_vec[1] *= right; m_vec[2] *= right; return *this; } Vector3D caret::operator*(const float& left, const Vector3D& right) { return right * left; } Vector3D Vector3D::operator+(const Vector3D& right) const { Vector3D ret = *this; ret += right; return ret; } Vector3D& Vector3D::operator+=(const Vector3D& right) { m_vec[0] += right.m_vec[0]; m_vec[1] += right.m_vec[1]; m_vec[2] += right.m_vec[2]; return *this; } Vector3D Vector3D::operator-(const Vector3D& right) const { Vector3D ret = *this; ret -= right; return ret; } Vector3D Vector3D::operator-() const { Vector3D ret; ret.m_vec[0] = -m_vec[0]; ret.m_vec[1] = -m_vec[1]; ret.m_vec[2] = -m_vec[2]; return ret; } Vector3D& Vector3D::operator-=(const Vector3D& right) { m_vec[0] -= right.m_vec[0]; m_vec[1] -= right.m_vec[1]; m_vec[2] -= right.m_vec[2]; return *this; } Vector3D Vector3D::operator/(const float& right) const { Vector3D ret = *this; ret /= right; return ret; } Vector3D& Vector3D::operator/=(const float& right) { m_vec[0] /= right; m_vec[1] /= right; m_vec[2] /= right; return *this; } Vector3D& Vector3D::operator=(const float* right) { m_vec[0] = right[0]; m_vec[1] = right[1]; m_vec[2] = right[2]; return *this; } workbench-1.1.1/src/Common/Vector3D.h000066400000000000000000000051651255417355300173360ustar00rootroot00000000000000#ifndef __VECTOR_3D_H__ #define __VECTOR_3D_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretAssert.h" namespace caret { class Vector3D { float m_vec[3]; public: //vector functions float dot(const Vector3D& right) const; Vector3D cross(const Vector3D& right) const; Vector3D normal(float* origLength = NULL) const; float length() const; float lengthsquared() const; //geometry functions float distToLine(const Vector3D& p1, const Vector3D& p2, Vector3D* closePointOut = NULL) const; float distToLineSegment(const Vector3D& p1, const Vector3D& p2, Vector3D* closePointOut = NULL) const; //constructors Vector3D(); Vector3D(const float& x, const float& y, const float& z); Vector3D(const float* right); //compatibility operators float& operator[](const int64_t& index); const float& operator[](const int64_t& index) const; float& operator[](const int32_t& index); const float& operator[](const int32_t& index) const; Vector3D& operator=(const float* right); //numerical operators Vector3D& operator+=(const Vector3D& right); Vector3D& operator-=(const Vector3D& right); Vector3D& operator*=(const float& right); Vector3D& operator/=(const float& right); Vector3D operator+(const Vector3D& right) const; Vector3D operator-(const Vector3D& right) const; Vector3D operator-() const; Vector3D operator*(const float& right) const; Vector3D operator/(const float& right) const;//NOTE: doesn't really make sense to have the other division, unlike multiplication inline operator float*() { return m_vec; } }; Vector3D operator*(const float& left, const Vector3D& right); } #endif //__VECTOR_3D_H__ workbench-1.1.1/src/Common/VectorOperation.cxx000066400000000000000000000053441255417355300214020ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "VectorOperation.h" #include "CaretAssert.h" using namespace std; using namespace caret; VectorOperation::Operation VectorOperation::stringToOperation(const AString& string, bool& ok) { ok = true; if (string == "DOT") { return DOT; } else if (string == "CROSS") { return CROSS; } else if (string == "ADD") { return ADD; } else if (string == "SUBTRACT") { return SUBTRACT; } ok = false; return DOT;//have to return something } AString VectorOperation::operationToString(const VectorOperation::Operation& myOp) { switch (myOp) { case DOT: return "DOT"; case CROSS: return "CROSS"; case ADD: return "ADD"; case SUBTRACT: return "SUBTRACT"; } return ""; } vector VectorOperation::getAllOperations() { vector ret; ret.push_back(DOT); ret.push_back(CROSS); ret.push_back(ADD); ret.push_back(SUBTRACT); return ret; } bool VectorOperation::operationReturnsScalar(const VectorOperation::Operation& myOp) { switch (myOp) { case DOT: return true; default: return false; } } Vector3D VectorOperation::doVectorOperation(const Vector3D& first, const Vector3D& second, const Operation& myOp) { switch (myOp) { case CROSS: return first.cross(second); case ADD: return first + second; case SUBTRACT: return first - second; default: CaretAssert(false); return Vector3D(); } } float VectorOperation::doScalarOperation(const Vector3D& first, const Vector3D& second, const VectorOperation::Operation& myOp) { switch (myOp) { case DOT: return first.dot(second); default: CaretAssert(false); return 0.0f; } } workbench-1.1.1/src/Common/VectorOperation.h000066400000000000000000000033341255417355300210240ustar00rootroot00000000000000#ifndef __VECTOR_OPERATION_H__ #define __VECTOR_OPERATION_H__ /*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AString.h" #include "Vector3D.h" #include namespace caret { ///helper class for doing user-specified operations on 3D vectors class VectorOperation { public: enum Operation { DOT, CROSS, ADD, SUBTRACT }; static Operation stringToOperation(const AString& string, bool& ok); static AString operationToString(const Operation& myOp); static std::vector getAllOperations(); static bool operationReturnsScalar(const Operation& myOp); static Vector3D doVectorOperation(const Vector3D& first, const Vector3D& second, const Operation& myOp); static float doScalarOperation(const Vector3D& first, const Vector3D& second, const Operation& myOp); }; } #endif //__VECTOR_OPERATION_H__ workbench-1.1.1/src/Common/VoxelIJK.h000066400000000000000000000037501255417355300173360ustar00rootroot00000000000000#ifndef __VOXEL_IJK_H__ #define __VOXEL_IJK_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "stdint.h" namespace caret { struct VoxelIJK { int64_t m_ijk[3]; VoxelIJK() { } VoxelIJK(int64_t i, int64_t j, int64_t k) { m_ijk[0] = i; m_ijk[1] = j; m_ijk[2] = k; } template VoxelIJK(const T ijk[3]) { m_ijk[0] = ijk[0]; m_ijk[1] = ijk[1]; m_ijk[2] = ijk[2]; } bool operator<(const VoxelIJK& rhs) const//so it kan be the key of a map { if (m_ijk[2] < rhs.m_ijk[2]) return true;//compare such that when sorted, m_ijk[0] moves fastest if (m_ijk[2] > rhs.m_ijk[2]) return false; if (m_ijk[1] < rhs.m_ijk[1]) return true; if (m_ijk[1] > rhs.m_ijk[1]) return false; return (m_ijk[0] < rhs.m_ijk[0]); } bool operator==(const VoxelIJK& rhs) const { return (m_ijk[0] == rhs.m_ijk[0] && m_ijk[1] == rhs.m_ijk[1] && m_ijk[2] == rhs.m_ijk[2]); } bool operator!=(const VoxelIJK& rhs) const { return !((*this) == rhs); } }; } #endif //__VOXEL_IJK_H__ workbench-1.1.1/src/Common/YokingGroupEnum.cxx000066400000000000000000000236661255417355300213700ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __YOKING_GROUP_ENUM_DECLARE__ #include "YokingGroupEnum.h" #undef __YOKING_GROUP_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::YokingGroupEnum * \brief Enumerated type for yoking views of model. */ /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ YokingGroupEnum::YokingGroupEnum(const Enum enumValue, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCodeCounter++; this->name = name; this->guiName = guiName; } /** * Destructor. */ YokingGroupEnum::~YokingGroupEnum() { } /** * Initialize the enumerated metadata. */ void YokingGroupEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(YokingGroupEnum(YOKING_GROUP_OFF, "YOKING_GROUP_OFF", "Off")); enumData.push_back(YokingGroupEnum(YOKING_GROUP_A, "YOKING_GROUP_A", "Group A")); enumData.push_back(YokingGroupEnum(YOKING_GROUP_B, "YOKING_GROUP_B", "Group B")); enumData.push_back(YokingGroupEnum(YOKING_GROUP_C, "YOKING_GROUP_C", "Group C")); enumData.push_back(YokingGroupEnum(YOKING_GROUP_D, "YOKING_GROUP_D", "Group D")); enumData.push_back(YokingGroupEnum(YOKING_GROUP_E, "YOKING_GROUP_E", "Group E")); enumData.push_back(YokingGroupEnum(YOKING_GROUP_F, "YOKING_GROUP_F", "Group F")); enumData.push_back(YokingGroupEnum(YOKING_GROUP_G, "YOKING_GROUP_G", "Group G")); enumData.push_back(YokingGroupEnum(YOKING_GROUP_H, "YOKING_GROUP_H", "Group H")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const YokingGroupEnum* YokingGroupEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const YokingGroupEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString YokingGroupEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const YokingGroupEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ YokingGroupEnum::Enum YokingGroupEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = YOKING_GROUP_OFF; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const YokingGroupEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type YokingGroupEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString YokingGroupEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const YokingGroupEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ YokingGroupEnum::Enum YokingGroupEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = YOKING_GROUP_OFF; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const YokingGroupEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type YokingGroupEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t YokingGroupEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const YokingGroupEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ YokingGroupEnum::Enum YokingGroupEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = YOKING_GROUP_OFF; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const YokingGroupEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type YokingGroupEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void YokingGroupEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void YokingGroupEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(YokingGroupEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void YokingGroupEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(YokingGroupEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } workbench-1.1.1/src/Common/YokingGroupEnum.h000066400000000000000000000065031255417355300210040ustar00rootroot00000000000000#ifndef __YOKING_GROUP_ENUM_H__ #define __YOKING_GROUP_ENUM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class YokingGroupEnum { public: /** * Enumerated values. */ enum Enum { /** Off */ YOKING_GROUP_OFF, /** Group A */ YOKING_GROUP_A, /** Group B */ YOKING_GROUP_B, /** Group C */ YOKING_GROUP_C, /** Group D */ YOKING_GROUP_D, /** Group E */ YOKING_GROUP_E, /** Group F */ YOKING_GROUP_F, /** Group G */ YOKING_GROUP_G, /** Group H */ YOKING_GROUP_H }; ~YokingGroupEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); private: YokingGroupEnum(const Enum enumValue, const AString& name, const AString& guiName); static const YokingGroupEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; #ifdef __YOKING_GROUP_ENUM_DECLARE__ std::vector YokingGroupEnum::enumData; bool YokingGroupEnum::initializedFlag = false; int32_t YokingGroupEnum::integerCodeCounter = 0; #endif // __YOKING_GROUP_ENUM_DECLARE__ } // namespace #endif //__YOKING_GROUP_ENUM_H__ workbench-1.1.1/src/Desktop/000077500000000000000000000000001255417355300157065ustar00rootroot00000000000000workbench-1.1.1/src/Desktop/CMakeLists.txt000066400000000000000000000112031255417355300204430ustar00rootroot00000000000000# # Name of Project # PROJECT(Desktop) # # Name of executable # set (EXE_NAME wb_view) # # QT Libraries # SET(QT_USE_QTXML TRUE) SET(QT_USE_QTOPENGL TRUE) SET(QT_USE_QTNETWORK TRUE) SET(QT_USE_QTWEBKIT TRUE) # # QT Include directories # INCLUDE (${QT_USE_FILE}) # # Added by JWH to eliminate OpenGL linking errors in Ubuntu Linux # IF (UNIX) IF (NOT APPLE) FIND_PACKAGE(OpenGL) ENDIF (NOT APPLE) ENDIF (UNIX) # # Resources # SET (RESOURCES_QRC_FILE ../Resources/resources.qrc) QT4_ADD_RESOURCES(IMAGE_RCS_SRCS ${RESOURCES_QRC_FILE}) # # Create the executable # Apple creates a bundle # IF (APPLE) ADD_EXECUTABLE(${EXE_NAME} MACOSX_BUNDLE desktop.cxx ${IMAGE_RCS_SRCS} ) # # This is a customized Info.Plist for Mac so that a spec # file can be opened using Finder # SET_TARGET_PROPERTIES( ${EXE_NAME} PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/../mac_plist/MacOSXBundleInfo.plist.in ) # SET_TARGET_PROPERTIES( # ${EXE_NAME} # PROPERTIES # RESOURCE # ${QT_BINARY_DIR}/../src/gui/mac/qt_menu.nib # ) ENDIF (APPLE) IF (WIN32) ADD_EXECUTABLE(${EXE_NAME} desktop.cxx ${IMAGE_RCS_SRCS} ${CMAKE_SOURCE_DIR}/../icons/windows/workbench.rc ) ENDIF (WIN32) IF (NOT APPLE) IF (UNIX) ADD_EXECUTABLE(${EXE_NAME} desktop.cxx ${IMAGE_RCS_SRCS} ) ENDIF (UNIX) ENDIF (NOT APPLE) # # Libraries that are linked # TARGET_LINK_LIBRARIES(${EXE_NAME} GuiQt Commands Operations Algorithms OperationsBase Qwt OSMesaDummy Brain ${FTGL_FONT_MODULE_FOR_LINKING} Files Charting Palette Cifti Gifti Nifti FilesBase Scenes Xml Common Quazip ${FREETYPE_LIBRARIES} ${QT_LIBRARIES} ${ZLIB_LIBRARIES} #${LIBS} ) INSTALL(TARGETS ${EXE_NAME} DESTINATION bin) IF(WIN32) TARGET_LINK_LIBRARIES(${EXE_NAME} opengl32 glu32 ) ENDIF(WIN32) IF (UNIX) IF (NOT APPLE) TARGET_LINK_LIBRARIES(${EXE_NAME} ${OPENGL_LIBRARIES} gobject-2.0 ) ENDIF (NOT APPLE) # EXECUTE_PROCESS(COMMAND uname -n OUTPUT_VARIABLE MACHINE_NAME) # MESSAGE("MACHINE_NAME: ${MACHINE_NAME}") # IF (${MACHINE_NAME} MATCHES "linuxbuild") # MESSAGE("is linuxbuild") # SET_TARGET_PROPERTIES(${EXE_NAME} # PROPERTIES # LINK_FLAGS "-Wl,-E" # LINK_FLAGS_DEBUG "-Wl,-E" # LINK_FLAGS_RELEASE "-Wl,-E") # ENDIF() ENDIF (UNIX) # # At this time, Cocoa needs to be explicitly added for Apple Mac # IF (APPLE) #SET (QT_MAC_USE_COCOA TRUE) TARGET_LINK_LIBRARIES(${EXE_NAME} "-framework Cocoa" "-framework OpenGL" ) ENDIF (APPLE) # # Find Headers # INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/Desktop ${CMAKE_SOURCE_DIR}/Algorithms ${CMAKE_SOURCE_DIR}/Commands ${CMAKE_SOURCE_DIR}/GuiQt ${CMAKE_SOURCE_DIR}/Brain ${CMAKE_SOURCE_DIR}/FilesBase ${CMAKE_SOURCE_DIR}/Files ${CMAKE_SOURCE_DIR}/Palette ${CMAKE_SOURCE_DIR}/Cifti ${CMAKE_SOURCE_DIR}/Gifti ${CMAKE_SOURCE_DIR}/Nifti ${CMAKE_SOURCE_DIR}/Qwt ${CMAKE_SOURCE_DIR}/Scenes ${CMAKE_SOURCE_DIR}/Xml ${CMAKE_SOURCE_DIR}/Common ) # # Apple needs qt_menu.nib directory copied # into application's Resources directory # # Apple needs framework # IF (APPLE) MESSAGE("EXE: " ${CMAKE_BUILD_DIR} " " ${EXE_NAME}) ADD_CUSTOM_COMMAND( TARGET ${EXE_NAME} POST_BUILD COMMAND ${CMAKE_SOURCE_DIR}/CMakeScripts/copy_mac_nib.sh ${EXE_NAME} ###COMMAND ${CMAKE_SOURCE_DIR}/CMakeScripts/copy_mac_frameworks.sh ${EXE_NAME} ) ENDIF (APPLE) IF (APPLE) SET (MACOSX_BUNDLE_INFO_STRING wb_view Copyright 2015 ) SET (MACOSX_BUNDLE_ICON_FILE workbench.icns ) ##SET (MACOSX_BUNDLE_GUI_IDENTIFIER wb_view ) ## Underscore is not valid in MACOSX_BUNDLE_GUI_IDENTIFIER SET (MACOSX_BUNDLE_GUI_IDENTIFIER workbench ) SET (MACOSX_BUNDLE_LONG_VERSION_STRING wb_view) SET (MACOSX_BUNDLE_BUNDLE_NAME wb_view) SET (MACOSX_BUNDLE_SHORT_VERSION_STRING 1.1.1) SET (MACOSX_BUNDLE_BUNDLE_VERSION 1.1.1) SET (MACOSX_BUNDLE_COPYRIGHT 2015 ) ADD_CUSTOM_COMMAND( TARGET ${EXE_NAME} POST_BUILD COMMAND ${CMAKE_SOURCE_DIR}/CMakeScripts/copy_mac_icon.sh ${EXE_NAME} ${CMAKE_SOURCE_DIR}/../icons/mac/wb_view.icns COMMAND ${CMAKE_SOURCE_DIR}/CMakeScripts/copy_mac_icon.sh ${EXE_NAME} ${CMAKE_SOURCE_DIR}/../icons/mac/workbench.icns COMMAND ${CMAKE_SOURCE_DIR}/CMakeScripts/copy_mac_icon.sh ${EXE_NAME} ${CMAKE_SOURCE_DIR}/../icons/mac/spec_file.icns COMMAND ${CMAKE_SOURCE_DIR}/CMakeScripts/copy_mac_icon.sh ${EXE_NAME} ${CMAKE_SOURCE_DIR}/../icons/mac/data_file.icns ) ENDIF (APPLE) workbench-1.1.1/src/Desktop/desktop.cxx000066400000000000000000000713121255417355300201070ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #include #include #include #include #include #include "ApplicationInformation.h" #include "BrainBrowserWindow.h" #include "BrainOpenGLWidget.h" #include "CaretAssert.h" #include "CaretCommandLine.h" #include "CaretHttpManager.h" #include "CaretLogger.h" #include "CaretPreferences.h" #include "CommandOperationManager.h" #include "EventBrowserWindowNew.h" #include "EventManager.h" #include "FileInformation.h" #include "GuiManager.h" #include "MacApplication.h" #include "ProgramParameters.h" #include "SessionManager.h" #include "SplashScreen.h" #include "SystemUtilities.h" #include "WuQMessageBox.h" #include "WuQtUtilities.h" static bool caretLoggerIsValid = false; using namespace caret; using namespace std; /** * Handles message produced by Qt. */ static void messageHandlerForQt(QtMsgType type, const char* msg) { const AString backtrace = SystemUtilities::getBackTrace(); const AString message = (AString(msg) + "\n" + backtrace); const char* messageChars = message.toCharArray(); if (caretLoggerIsValid) { bool abortFlag = false; switch (type) { case QtDebugMsg: if (CaretLogger::getLogger()->isInfo()) { CaretLogInfo(message); } else { std::cerr << "Qt Debug: " << messageChars << std::endl; } break; case QtWarningMsg: if (CaretLogger::getLogger()->isWarning()) { CaretLogWarning(message); } else { std::cerr << "Qt Warning: " << messageChars << std::endl; } break; case QtCriticalMsg: if (CaretLogger::getLogger()->isSevere()) { CaretLogSevere(message); } else { std::cerr << "Qt Critical: " << messageChars << std::endl; } break; case QtFatalMsg: if (CaretLogger::getLogger()->isSevere()) { CaretLogSevere(message); } else { std::cerr << "Qt Fatal: " << messageChars << std::endl; } abortFlag = true; break; } /* * Beep to alert user about an error!!! */ GuiManager::get()->beep(5); if (abortFlag) { std::abort(); } } else { switch (type) { case QtDebugMsg: std::cerr << "Qt Debug: " << messageChars << std::endl; break; case QtWarningMsg: std::cerr << "Qt Warning: " << messageChars << std::endl; break; case QtCriticalMsg: std::cerr << "Qt Critical: " << messageChars << std::endl; break; case QtFatalMsg: std::cerr << "Qt Fatal: " << messageChars << std::endl; std::abort(); break; } } delete[] messageChars; } //struct for communicating stuff back to main from parseCommandLine struct ProgramState { vector fileList; int specLoadType; int windowSizeXY[2]; int windowPosXY[2]; int graphicsSizeXY[2]; bool showSplash; AString sceneFileName; AString sceneNameOrNumber; ProgramState(); }; /* // maximum mumber of lines the output console should have static const WORD MAX_CONSOLE_LINES = 500; #include #include #include #include #include #include void RedirectIOToConsole() { int hConHandle; long lStdHandle; CONSOLE_SCREEN_BUFFER_INFO coninfo; FILE *fp; // allocate a console for this app AllocConsole(); // set the screen buffer to be big enough to let us scroll text GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo); coninfo.dwSize.Y = MAX_CONSOLE_LINES; SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize); // redirect unbuffered STDOUT to the console lStdHandle = (long)GetStdHandle(STD_OUTPUT_HANDLE); hConHandle = _open_osfhandle(lStdHandle, _O_TEXT); fp = _fdopen( hConHandle, "w" ); *stdout = *fp; setvbuf( stdout, NULL, _IONBF, 0 ); // redirect unbuffered STDIN to the console lStdHandle = (long)GetStdHandle(STD_INPUT_HANDLE); hConHandle = _open_osfhandle(lStdHandle, _O_TEXT); fp = _fdopen( hConHandle, "r" ); *stdin = *fp; setvbuf( stdin, NULL, _IONBF, 0 ); // redirect unbuffered STDERR to the console lStdHandle = (long)GetStdHandle(STD_ERROR_HANDLE); hConHandle = _open_osfhandle(lStdHandle, _O_TEXT); fp = _fdopen( hConHandle, "w" ); *stderr = *fp; setvbuf( stderr, NULL, _IONBF, 0 ); // make cout, wcout, cin, wcin, wcerr, cerr, wclog and clog // point to console as well ios::sync_with_stdio(); }*/ //#pragma comment(linker, "/SUBSYSTEM:windows /ENTRY:mainCRTStartup") //declare the functions associated with command line void printHelp(const AString& progName); void parseCommandLine(const AString& progName, ProgramParameters* myParams, ProgramState& myState); int main(int argc, char* argv[]) { srand(time(NULL)); //MS Windows code to allocate a new console, will have a preference to set this up //RedirectIOToConsole(); int result; { /* * Handle uncaught exceptions */ SystemUtilities::setUnexpectedHandler(); /* * Create the session manager. */ SessionManager::createSessionManager(ApplicationTypeEnum::APPLICATION_TYPE_GRAPHICAL_USER_INTERFACE); caretLoggerIsValid = true; /* * Parameters for the program. */ ProgramParameters parameters(argc, argv); caret_global_commandLine_init(parameters); //begin parsing command line ProgramState myState; FileInformation progInfo(argv[0]); AString progName = progInfo.getFileName(); parseCommandLine(progName, ¶meters, myState); /* * Log the command parameters. */ CaretLogFine("Running: " + caret_global_commandLine); //change the default graphics system on mac to avoid rendering performance issues with qwtplotter #ifdef CARET_OS_MACOSX QApplication::setGraphicsSystem("raster"); MacApplication app(argc, argv); #else //CARET_OS_MACOSX QApplication app(argc, argv); #endif //CARET_OS_MACOSX ApplicationInformation applicationInformation; QApplication::addLibraryPath( QApplication::applicationDirPath() + QDir::separator() + "plugins"); QApplication::setApplicationName(applicationInformation.getName()); QApplication::setApplicationVersion(applicationInformation.getVersion()); QApplication::setOrganizationDomain("brainvis.wustl.edu"); QApplication::setOrganizationName("Van Essen Lab"); /* * Override the system local to US - English */ QLocale::setDefault(QLocale(QLocale::English, QLocale::UnitedStates)); /* * Make sure OpenGL is available. */ if (QGLFormat::hasOpenGL() == false) { QString msg = "This computer does not support OpenGL (3D graphics system).\n" "You will need to install OpenGL to run Workbench.\n" "Please see your system administrator."; app.processEvents(); WuQMessageBox::errorOk(NULL, msg); app.processEvents(); return -1; } /* * Setup OpenGL */ BrainOpenGLWidget::initializeDefaultGLFormat(); qInstallMsgHandler(messageHandlerForQt);//this handler uses CaretLogger and GuiManager, so we must install it after the logger is available and the application is created /* * Log debug status */ CaretLogConfig(applicationInformation.getCompiledWithDebugStatus()); //sanity check command line bool haveSpec = false; bool haveFiles = false; for (int i = 0; i < (int)myState.fileList.size(); ++i) { if (myState.fileList[i].endsWith(".spec")) { if (haveSpec) { cerr << "error, cannot load multiple spec files at this time" << endl; return -1; } haveSpec = true; } else { haveFiles = true; } } //if error to have both data and spec files /*if (haveFiles && haveSpec) { cerr << "error, cannot specify both spec files and data files on the command line" << endl; return -1; }//*/ /* * Enabled the desired splash screen based upon user preferences * and command line options. Do not show selection splash screen * if there has listed files on the command line. */ CaretPreferences* preferences = SessionManager::get()->getCaretPreferences(); bool showSelectionSplashScreen = preferences->isSplashScreenEnabled(); if (myState.fileList.empty() == false) { showSelectionSplashScreen = false; } if (myState.sceneFileName.isEmpty() == false) { showSelectionSplashScreen = false; } bool showImageSplashScreen = (! showSelectionSplashScreen); if (myState.showSplash == false) { showSelectionSplashScreen = false; showImageSplashScreen = false; } /* * DISABLE IMAGE SPLASH SCREEN */ showImageSplashScreen = false; /* * Splash Screen */ QPixmap splashPixmap; QSplashScreen splashScreen; if (showImageSplashScreen) { if (WuQtUtilities::loadPixmap(":/Splash/hcp.png", splashPixmap)) { splashScreen.setPixmap(splashPixmap); splashScreen.showMessage("Starting Workbench..."); splashScreen.show(); app.processEvents(); SystemUtilities::sleepSeconds(2); } } /* * Create the GUI Manager. */ GuiManager::createGuiManager(); /* * Letting the App process events will allow the message for a * double-clicked spec file in Mac OSX to get processed. */ app.processEvents(); /* * Now that events have processed, see if there was a request for * a data file to open */ const AString dataFileNameFromOS = GuiManager::get()->getNameOfDataFileToOpenAfterStartup(); if (dataFileNameFromOS.isEmpty() == false) { myState.fileList.push_back(dataFileNameFromOS); showSelectionSplashScreen = false; if (dataFileNameFromOS.endsWith(DataFileTypeEnum::toFileExtension(DataFileTypeEnum::SPECIFICATION))) { haveSpec = true; haveFiles = false; myState.specLoadType = 0; } else { haveSpec = false; haveFiles = true; } } /* * Show file selection splash screen if enabled via user's preferences */ if (showSelectionSplashScreen) { /* * Show selection splash screen. * Need to process events since QApplication::exec() has not * been called. */ SplashScreen splashScreen(NULL); app.processEvents(); if (splashScreen.exec()) { const QString specFileName = splashScreen.getSelectedSpecFileName(); if (specFileName.isEmpty() == false) { myState.fileList.clear(); myState.fileList.push_back(specFileName); myState.specLoadType = 0; // which means use BrainBrowserWindow::LOAD_SPEC_FILE_WITH_DIALOG_VIA_COMMAND_LINE; haveSpec = true; haveFiles = false; } } } /* * Create and display a main window. * If not done as pointer, the event object is listed as an * object that was not deleted by CaretObject::printListOfObjectsNotDeleted * since it does not go out of scope. */ EventBrowserWindowNew newBrowserWindow(NULL, NULL); EventManager::get()->sendEvent(newBrowserWindow.getPointer()); splashScreen.close(); BrainBrowserWindow* myWindow = GuiManager::get()->getBrowserWindowByWindowIndex(0); if ((myState.windowSizeXY[0] > 0) && (myState.windowSizeXY[1] > 0)) { if (myState.windowPosXY[0] > 0 && myState.windowPosXY[1] > 0) { myWindow->setGeometry(myState.windowPosXY[0], myState.windowPosXY[1], myState.windowSizeXY[0], myState.windowSizeXY[1]); } else { myWindow->setFixedSize(myState.windowSizeXY[0], myState.windowSizeXY[1]); } } else { if (myState.windowPosXY[0] > 0 && myState.windowPosXY[1] > 0) { myState.windowSizeXY[0] = myWindow->width(); myState.windowSizeXY[1] = myWindow->height(); myWindow->setGeometry(myState.windowPosXY[0], myState.windowPosXY[1], myState.windowSizeXY[0], myState.windowSizeXY[1]); } } if (myState.graphicsSizeXY[0] > 0 && myState.graphicsSizeXY[1] > 0) { myWindow->setGraphicsWidgetFixedSize(myState.graphicsSizeXY[0], myState.graphicsSizeXY[1]); } //use command line if (haveFiles) { myWindow->loadFilesFromCommandLine(myState.fileList, BrainBrowserWindow::LOAD_SPEC_FILE_WITH_DIALOG);//second parameter unused in this case } if (haveSpec) { switch (myState.specLoadType) { case 0://dialog myWindow->loadFilesFromCommandLine(myState.fileList, BrainBrowserWindow::LOAD_SPEC_FILE_WITH_DIALOG_VIA_COMMAND_LINE); break; case 1://load all myWindow->loadFilesFromCommandLine(myState.fileList, BrainBrowserWindow::LOAD_SPEC_FILE_CONTENTS_VIA_COMMAND_LINE); break; default: CaretAssert(false); } } if (myState.sceneFileName.isEmpty() == false) { myWindow->loadSceneFromCommandLine(myState.sceneFileName, myState.sceneNameOrNumber); } if (QGLPixelBuffer::hasOpenGLPbuffers()) { CaretLogConfig("OpenGL PBuffers are supported"); } else { CaretLogConfig("OpenGL PBuffers are NOT supported"); } /* * Log local (language, country) */ QLocale sytemLocale = QLocale::system(); CaretLogConfig("Local Language=" + QLocale::languageToString(sytemLocale.language()) + " Country=" + QLocale::countryToString(sytemLocale.country())); /* * Resolution of screens */ AString screenSizeText = "Screen Sizes: "; QDesktopWidget* dw = QApplication::desktop(); const int numScreens = dw->screenCount(); for (int i = 0; i < numScreens; i++) { const QRect rect = dw->screenGeometry(i); const int x = rect.x(); const int y = rect.y(); const int w = rect.width(); const int h = rect.height(); screenSizeText += ("(index=" + AString::number(i) + ", x=" + AString::number(x) + ", y=" + AString::number(y) + ", w=" + AString::number(w) + ", h=" + AString::number(h) + ") "); } screenSizeText += ("(Primary Screen=" + AString::number(dw->primaryScreen()) + ") "); if (dw->isVirtualDesktop()) { screenSizeText += ("(Virtual Desktop=YES) "); } else { screenSizeText += ("(Virtual Desktop=NO) "); } QWidget* screenWidget = dw->screen(); QRect screenWidgetRect = screenWidget->geometry(); screenSizeText += ("(Desktop: x=" + AString::number(screenWidgetRect.x()) + ", y=" + AString::number(screenWidgetRect.y()) + ", w=" + AString::number(screenWidgetRect.width()) + ", h=" + AString::number(screenWidgetRect.height()) + ") "); CaretLogConfig(screenSizeText); /* * Start the app which will launch the main window. */ result = app.exec(); /* * Hiding the window removes it from the event loop on Windows, which is necessary to * prevent paint events from causing assertion errors when the Window is destroyed * Although this is a Window's only bug, it's probably good practice to do on all platforms */ //theMainWindow->hide(); /* * Delete the GUI Manager. */ GuiManager::deleteGuiManager(); /* * Delete the command manager */ CommandOperationManager::deleteCommandOperationManager(); /* * Delete the session manager. */ SessionManager::deleteSessionManager(); CaretHttpManager::deleteHttpManager(); } /* * See if any objects were not deleted. */ CaretObject::printListOfObjectsNotDeleted(true); //FreeConsole(); return result; } void printHelp(const AString& progName) { cout << "Usage: " << progName << " [options] [files]" << endl << endl << " [files], if present, can be a single spec file, or multiple data files" << endl << endl << "Options:" << endl << " -help" << endl << " display this usage text" << endl << endl << " -graphics-size " << endl << " Set the size of the graphics region." << endl << " If this option is used you WILL NOT be able" << endl << " to change the size of the graphic region. It" << endl << " may be useful when image captures of a particular" << endl << " size are desired." << endl << endl << " -logging " << endl << " Set the logging level." << endl << " Valid Levels are:" << endl; std::vector logLevels; LogLevelEnum::getAllEnums(logLevels); for (std::vector::iterator iter = logLevels.begin(); iter != logLevels.end(); iter++) { cout << " " << qPrintable(LogLevelEnum::toName(*iter)) << endl; } // foreach (LogLevelEnum::Enum level , logLevels) { // for (LogLevelEnum::Enum level : logLevels) { // cout << " " << qPrintable(LogLevelEnum::toName(level)) << endl; // } cout << endl << " -no-splash" << endl << " disable all splash screens" << endl << endl << " -scene-load " << endl << " load the specified scene file and display the scene " << endl << " in the file that matches by name or number. Name" << endl << " takes precedence over number. The scene numbers " << endl << " start at one." << endl << " " << endl << endl << " -style " << endl << " change the window style to the specified style" << endl << " the following styles are valid on this system:" << endl; QStringList styleList = QStyleFactory::keys(); QStringListIterator styleListIterator(styleList); while (styleListIterator.hasNext()) { cout << " " << qPrintable(styleListIterator.next()) << endl; } cout << " The selected style is listed on the About wb_view dialog" << endl << " available from the File Menu (On Macs: wb_view Menu). " << endl << " Press the \"More\" button to see the selected style." << endl << " Other styles may be available on other systems." << endl << endl << " -spec-load-all" << endl << " load all files in the given spec file, don't show spec file dialog" << endl << endl << " -window-size " << endl << " Set the size of the browser window" << endl << endl << " -window-pos " << endl << " Set the position of the browser window" << endl << endl; } void parseCommandLine(const AString& progName, ProgramParameters* myParams, ProgramState& myState) { bool hasFatalError = false; try { while (myParams->hasNext()) { AString thisParam = myParams->nextString("option"); if (thisParam[0] == '-') { if (thisParam == "-style") { myParams->nextString("style");//discard, QApplication handles this } else if (thisParam == "-help") { printHelp(progName); exit(0); } else if (thisParam == "-logging") { if (myParams->hasNext()) { const AString logLevelName = myParams->nextString("Logging Level").toUpper(); bool valid = false; const LogLevelEnum::Enum level = LogLevelEnum::fromName(logLevelName, &valid); if (valid) { /* * Note settings logging level in preferences will also * set logging level in the caret logger. */ CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); prefs->setLoggingLevel(level); } else { cerr << "Invalid logging level \"" << qPrintable(logLevelName) << "\" for \"-logging\" option" << std::endl; hasFatalError = true; } } } else if (thisParam == "-no-splash") { myState.showSplash = false; } else if (thisParam == "-scene-load") { if (myParams->hasNext()) { myState.sceneFileName = myParams->nextString("Scene File Name"); if (myParams->hasNext()) { myState.sceneNameOrNumber = myParams->nextString("Scene Name or Number"); } else { cerr << "Missing scene name/number for \"-scene\" option" << std::endl; hasFatalError = true; } } else { cerr << "Missing scene file name for \"-scene\" option" << std::endl; hasFatalError = true; } } else if (thisParam == "-spec-load-all") { myState.specLoadType = 1; } else if (thisParam == "-graphics-size") { if (myParams->hasNext()) { myState.graphicsSizeXY[0] = myParams->nextInt("Graphics Size X"); } else { cerr << "Missing X & Y sizes for graphics" << endl; hasFatalError = true; } if (myParams->hasNext()) { myState.graphicsSizeXY[1] = myParams->nextInt("Graphics Size Y"); } else { cerr << "Missing Y sizes for graphics" << endl; hasFatalError = true; } } else if (thisParam == "-window-size") { if (myParams->hasNext()) { myState.windowSizeXY[0] = myParams->nextInt("Window Size X"); } else { cerr << "Missing X & Y sizes for window" << endl; hasFatalError = true; } if (myParams->hasNext()) { myState.windowSizeXY[1] = myParams->nextInt("Window Size Y"); } else { cerr << "Missing Y sizes for window" << endl; hasFatalError = true; } } else if (thisParam == "-window-pos") { if (myParams->hasNext()) { myState.windowPosXY[0] = myParams->nextInt("Window Position X"); } else { cerr << "Missing X & Y position for window" << endl; hasFatalError = true; } if (myParams->hasNext()) { myState.windowPosXY[1] = myParams->nextInt("Window Position Y"); } else { cerr << "Missing Y position for window" << endl; hasFatalError = true; } } else if (thisParam.startsWith("-psn")) { /* * 21 April 2014 (Did not have this problem before this date) * * IGNORE this parameter. For some reason, when a Mac * version is started from Finder, a "-psn" parameter * is being added to the parameters. If this parameter * is not ignored, Workbench starts, the icon bounces * a few times, and then Workbench quits (due to * "unrecognized option", below), and the user is * not given any error message. * * http://stackoverflow.com/questions/10242115/os-x-strange-psn-command-line-parameter-when-launched-from-finder * http://trac.wxwidgets.org/ticket/15432 */ } else { cerr << "unrecognized option \"" << thisParam << "\"" << endl; printHelp(progName); hasFatalError = true; } } else { myState.fileList.push_back(thisParam); } } } catch (const ProgramParametersException& e) { cerr << e.whatString() << std::endl; hasFatalError = true; } if (hasFatalError) { exit(-1); } } ProgramState::ProgramState() { sceneFileName = ""; sceneNameOrNumber = ""; specLoadType = 0;//0: use spec window, 1: all windowSizeXY[0] = -1; windowSizeXY[1] = -1; windowPosXY[0] = -1; windowPosXY[1] = -1; graphicsSizeXY[0] = -1; graphicsSizeXY[1] = -1; showSplash = true; } workbench-1.1.1/src/Files/000077500000000000000000000000001255417355300153375ustar00rootroot00000000000000workbench-1.1.1/src/Files/AffineFile.cxx000066400000000000000000000107361255417355300200620ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AffineFile.h" #include "CaretAssert.h" #include "DataFileException.h" #include "FileInformation.h" #include "NiftiIO.h" #include #include using namespace caret; using namespace std; AffineFile::AffineFile() { m_matrix = FloatMatrix::identity(4);//use 4x4 convention for in-memory } void AffineFile::setMatrix(const FloatMatrix& matrix) { int64_t inRows, inCols; matrix.getDimensions(inRows, inCols); CaretAssert(inCols == 4 && inRows > 2 && inRows < 5); m_matrix = FloatMatrix::identity(4);//set the final 0 0 0 1 row m_matrix[0] = matrix[0];//copy ONLY the rows that should change m_matrix[1] = matrix[1]; m_matrix[2] = matrix[2]; } FloatMatrix AffineFile::read34(const AString& filename) { FileInformation affineInfo(filename); if (!affineInfo.exists()) throw DataFileException("affine file '" + filename + "' does not exist"); fstream affineFile(filename.toLocal8Bit().constData(), fstream::in); if (!affineFile.good()) throw DataFileException("error opening file '" + filename + "' for reading"); FloatMatrix ret = FloatMatrix::identity(4);//to ensure the right size and the fourth 0 0 0 1 row for (int i = 0; i < 3; ++i)//DO NOT read the fourth row from the file into the matrix { for (int j = 0; j < 4; ++j) { affineFile >> ret[i][j]; if (!affineFile) throw DataFileException("error while reading file '" + filename + "'"); } } return ret; } void AffineFile::write44(const FloatMatrix& out, const AString& filename) { fstream affineFile(filename.toLocal8Bit().constData(), fstream::out); if (!affineFile.good()) { throw DataFileException("error opening file '" + filename + "' for writing"); } affineFile.setf(ios::fixed, ios::floatfield); affineFile.precision(10);//flirt appears to use 10, so do the same for (int i = 0; i < 4; ++i) { for (int j = 0; j < 4; ++j) { affineFile << out[i][j]; if (j < 3) affineFile << " ";//double space like flirt if (!affineFile) throw DataFileException("error while writing file '" + filename + "'"); } affineFile << endl; } } void AffineFile::readWorld(const AString& filename) { m_matrix = read34(filename);//and that is it, no quirks } void AffineFile::writeWorld(const AString& filename) { write44(m_matrix, filename);//ditto } void AffineFile::readFlirt(const AString& filename, const AString& sourceName, const AString& targetName) { FloatMatrix flirtMat = read34(filename); FloatMatrix sourceMat, sourceScale, targetMat, targetScale; getFSLQuirks(sourceName, sourceMat, sourceScale); getFSLQuirks(targetName, targetMat, targetScale); //via aff_conv : world = targmat * trgscale^-1 * input * srcscale * sourcemat^-1 m_matrix = targetMat * targetScale.inverse() * flirtMat * sourceScale * sourceMat.inverse(); } void AffineFile::writeFlirt(const AString& filename, const AString& sourceName, const AString& targetName) const { FloatMatrix sourceMat, sourceScale, targetMat, targetScale; getFSLQuirks(sourceName, sourceMat, sourceScale); getFSLQuirks(targetName, targetMat, targetScale); FloatMatrix flirtMat = targetScale * targetMat.inverse() * m_matrix * sourceMat * sourceScale.inverse(); write44(flirtMat, filename); } void AffineFile::getFSLQuirks(const AString& niftiName, FloatMatrix& outSform, FloatMatrix& outScale) { NiftiIO myIO; myIO.openRead(niftiName); outSform = FloatMatrix(myIO.getHeader().getSForm());//NOTE: this is expected to return a 4x4 matrix with the 0 0 0 1 row intact outScale = FloatMatrix(myIO.getHeader().getFSLSpace()); } workbench-1.1.1/src/Files/AffineFile.h000066400000000000000000000037701255417355300175070ustar00rootroot00000000000000#ifndef __AFFINE_FILE_H__ #define __AFFINE_FILE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AString.h" #include "FloatMatrix.h" namespace caret { class AffineFile { FloatMatrix m_matrix; static FloatMatrix read34(const AString& filename);//helper to read a simple text affine static void write44(const FloatMatrix& out, const AString& filename);//helper for writing static void getFSLQuirks(const AString& niftiName, FloatMatrix& outSform, FloatMatrix& outScale);//just a convenience wrapper around the vector > version in NiftiHeaderIO public: AffineFile(); void readWorld(const AString& filename);//forward nifti coordinate transform void writeWorld(const AString& filename); void readFlirt(const AString& filename, const AString& sourceName, const AString& targetName);//flirt convention matrix, requires source/target volumes void writeFlirt(const AString& filename, const AString& sourceName, const AString& targetName) const; const FloatMatrix& getMatrix() { return m_matrix; } void setMatrix(const FloatMatrix& matrix);//needs to do sanity checking, so don't inline }; } #endif //__AFFINE_FILE_H__ workbench-1.1.1/src/Files/Border.cxx000066400000000000000000001573021255417355300173100ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __BORDER_DECLARE__ #include "Border.h" #undef __BORDER_DECLARE__ #include #include #include #include #include #include #include #include "CaretAssert.h" #include "CaretLogger.h" #include "CaretPointer.h" #include "DataFileException.h" #include "GeodesicHelper.h" #include "MathFunctions.h" #include "SurfaceFile.h" #include "SurfaceProjectedItem.h" #include "SurfaceProjectionBarycentric.h" #include "XmlWriter.h" using namespace caret; using namespace std; /** * \class caret::Border * \brief A border is a connected line segment on a source or volume. */ /** * Constructor. */ Border::Border() : CaretObjectTracksModification() { m_copyOfBorderPriorToLastEditing = NULL; clear(); // m_color = CaretColorEnum::BLACK; // m_selectionClassNameModificationStatus = true; // name/class is new!! } /** * Create a new border using the given surface's node indices. * * @param borderName * Name for border. * @param surfaceFile * The surface file. * @param nodeIndices * Indices of the surface nodes. * @return * Pointer to the newly created border. */ Border* Border::newInstanceFromSurfaceNodes(const AString& borderName, const SurfaceFile* surfaceFile, std::vector& nodeIndices) { CaretAssert(surfaceFile); Border* border = new Border(); border->setName(borderName); const int32_t numNodes = static_cast(nodeIndices.size()); for (int32_t i = 0; i < numNodes; i++) { const int32_t nodeIndex = nodeIndices[i]; const float* xyz = surfaceFile->getCoordinate(nodeIndex); SurfaceProjectedItem* spi = new SurfaceProjectedItem(); spi->setStereotaxicXYZ(xyz); spi->setStructure(surfaceFile->getStructure()); border->addPoint(spi); } return border; } /** * Destructor. */ Border::~Border() { clear(); } /** * Copy constructor. * @param obj * Object that is copied. */ Border::Border(const Border& obj) : CaretObjectTracksModification(obj) { m_copyOfBorderPriorToLastEditing = NULL; copyHelperBorder(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ Border& Border::operator=(const Border& obj) { if (this != &obj) { CaretObjectTracksModification::operator=(obj); copyHelperBorder(obj); } return *this; } /** * Helps with copying an object of this type. * @param obj * Object that is copied. */ void Border::copyHelperBorder(const Border& obj) { clear(); const int32_t numPoints = obj.getNumberOfPoints(); for (int32_t i = 0; i < numPoints; i++) { SurfaceProjectedItem* spi = new SurfaceProjectedItem(*obj.m_points[i]); addPoint(spi); } m_name = obj.m_name; m_className = obj.m_className; m_closed = obj.m_closed; clearModified(); setNameOrClassModified(); // new name/class so modified } /** * Clear the border. * Removes all points, resets names, etc. */ void Border::clear() { removeAllPoints(); m_closed = false; m_groupNameSelectionItem = NULL; m_classRgbaColor[0] = 0.0; m_classRgbaColor[1] = 0.0; m_classRgbaColor[2] = 0.0; m_classRgbaColor[3] = 1.0; m_classRgbaColorValid = false; m_nameRgbaColor[0] = 0.0; m_nameRgbaColor[1] = 0.0; m_nameRgbaColor[2] = 0.0; m_nameRgbaColor[3] = 1.0; m_nameRgbaColorValid = false; if (m_copyOfBorderPriorToLastEditing != NULL) { delete m_copyOfBorderPriorToLastEditing; m_copyOfBorderPriorToLastEditing = NULL; } m_name = ""; m_className = ""; setNameOrClassModified(); // new name/class so modified } /** * @return Structure to which this border is assigned. */ StructureEnum::Enum Border::getStructure() const { StructureEnum::Enum structure = StructureEnum::INVALID; if (m_points.empty() == false) { structure = m_points[0]->getStructure(); } return structure; } void Border::setStructure(const StructureEnum::Enum& structure) { int numPoints = getNumberOfPoints(); for (int i = 0; i < numPoints; ++i) { getPoint(i)->setStructure(structure); } } /** * @return Is the class RGBA color valid? */ bool Border::isClassRgbaValid() const { return m_classRgbaColorValid; } /** * Set then class RGBA color invalid. */ void Border::setClassRgbaInvalid() { m_classRgbaColorValid = false; } /** * @return The class RGBA color components * ranging zero to one. */ const float* Border::getClassRgba() const { return m_classRgbaColor; } /** * Get the class RGBA color components * ranging zero to one. */ void Border::getClassRgba(float rgba[4]) const { rgba[0] = m_classRgbaColor[0]; rgba[1] = m_classRgbaColor[1]; rgba[2] = m_classRgbaColor[2]; rgba[3] = m_classRgbaColor[3]; } /** * Set the RGBA color components assigned to the class. * @param rgba * Red, green, blue, alpha ranging zero to one. */ void Border::setClassRgba(const float rgba[3]) { m_classRgbaColor[0] = rgba[0]; m_classRgbaColor[1] = rgba[1]; m_classRgbaColor[2] = rgba[2]; m_classRgbaColor[3] = rgba[3]; m_classRgbaColorValid = true; } /** * @return Is the name RGBA color valid? */ bool Border::isNameRgbaValid() const { return m_nameRgbaColorValid; } /** * Set then name RGBA color invalid. */ void Border::setNameRgbaInvalid() { m_nameRgbaColorValid = false; } /** * @return The name RGBA color components * ranging zero to one. */ const float* Border::getNameRgba() const { return m_nameRgbaColor; } /** * Get the name RGBA color components * ranging zero to one. */ void Border::getNameRgba(float rgba[4]) const { rgba[0] = m_nameRgbaColor[0]; rgba[1] = m_nameRgbaColor[1]; rgba[2] = m_nameRgbaColor[2]; rgba[3] = m_nameRgbaColor[3]; } /** * Set the RGBA color components assigned to the name. * @param rgba * Red, green, blue, alpha ranging zero to one. */ void Border::setNameRgba(const float rgba[4]) { m_nameRgbaColor[0] = rgba[0]; m_nameRgbaColor[1] = rgba[1]; m_nameRgbaColor[2] = rgba[2]; m_nameRgbaColor[3] = rgba[3]; m_nameRgbaColorValid = true; } /** * @return True if all points are on the * same structure, else false. */ bool Border::verifyAllPointsOnSameStructure() const { const int32_t numPoints = getNumberOfPoints(); if (numPoints <= 1) { return true; } StructureEnum::Enum structure = m_points[0]->getStructure(); for (int32_t i = 1; i < numPoints; i++) { if (m_points[i]->getStructure() != structure) { return false; } } return true; } bool Border::verifyForSurfaceNumberOfNodes(const int32_t& numNodes) const { int32_t numPoints = getNumberOfPoints(); for (int j = 0; j < numPoints; ++j) { const SurfaceProjectionBarycentric* thisProj = getPoint(j)->getBarycentricProjection();//addPoint makes sure these are always valid const int32_t* nodes = thisProj->getTriangleNodes(); for (int k = 0; k < 3; ++k) { if (nodes[k] >= numNodes) { return false; } } } return true; } /** * Remove all points in this border. */ void Border::removeAllPoints() { const int32_t numPoints =getNumberOfPoints(); for (int32_t i = 0; i < numPoints; i++) { delete m_points[i]; } m_points.clear(); setModified(); } /** * @return the name of the border. */ AString Border::getName() const { return m_name; } /** * Set the name of the border. * @param name * New name for border. */ void Border::setName(const AString& name) { if (m_name != name) { m_name = name; setModified(); setNameOrClassModified(); } } /** * @return the class name of the border. */ AString Border::getClassName() const { return m_className; } /** * Set the class name of the border. * @param className * New class name for border. */ void Border::setClassName(const AString& className) { if (m_className != className) { m_className = className; setModified(); setNameOrClassModified(); } } /** * @return Number of points in the border. */ int32_t Border::getNumberOfPoints() const { return m_points.size(); } /** * Get the border point at the given index. * @param indx * Index of desired border point. * @return * Pointer to border point. */ const SurfaceProjectedItem* Border::getPoint(const int32_t indx) const { CaretAssertVectorIndex(m_points, indx); return m_points[indx]; } /** * Get the border point at the given index. * @param indx * Index of desired border point. * @return * Pointer to border point. */ SurfaceProjectedItem* Border::getPoint(const int32_t indx) { CaretAssertVectorIndex(m_points, indx); return m_points[indx]; } /** * Returns the index of the border point nearest * the given XYZ coordinate and within the * given maximum distance. * * @param surfaceFile * Surface file used for unprojecting border points * and producing XYZ coordinates. * @param xyz * The XYZ coordinates for which nearest border * point is desired. * @param maximumDistance * Border points searched are limited to those * within this distance from the XYZ coordinate. * @param distanceToNearestPointOut * If a point is found within the maximum distance * @return * Index of nearest border point or negative if * no border points is within the maximum distance. */ int32_t Border::findPointIndexNearestXYZ(const SurfaceFile* surfaceFile, const float xyz[3], const float maximumDistance, float& distanceToNearestPointOut) const { CaretAssert(surfaceFile); const int32_t numPoints = getNumberOfPoints(); if (numPoints <= 0) { return -1; } if (surfaceFile->getStructure() != getStructure()) { return -1; } int32_t nearestIndex = -1; float nearestDistanceSQ = maximumDistance * maximumDistance; float pointXYZ[3]; for (int32_t i = 0; i < numPoints; i++) { if (m_points[i]->getProjectedPosition(*surfaceFile, pointXYZ, true)) { const float distSQ = MathFunctions::distanceSquared3D(xyz, pointXYZ); if (distSQ <= nearestDistanceSQ) { nearestDistanceSQ = distSQ; nearestIndex = i; } } } if (nearestIndex >= 0) { distanceToNearestPointOut = std::sqrt(nearestDistanceSQ); } return nearestIndex; } /** * Add a point to the border. NOTE: the border * takes ownership of the point and will delete * it. After calling this method DO NOT ever * use the point passed to this method. * * @param point * Point that is added to the border. */ void Border::addPoint(SurfaceProjectedItem* point) { if (m_points.size() != 0 && m_points[0]->getStructure() != point->getStructure()) { delete point;//keep our word and handle deleting the argument throw DataFileException("attempt to add point of different structure to a border"); } if (!point->getBarycentricProjection()->isValid()) { delete point; throw DataFileException("attempt to add point without valid barycentric projection to border"); } const int32_t* nodes = point->getBarycentricProjection()->getTriangleNodes(); for (int k = 0; k < 3; ++k) { if (nodes[k] < 0) { delete point; throw DataFileException("attempt to add point using negative node number"); } } m_points.push_back(point); setModified(); } /** * Add copies of points from the given border starting * at startPointIndex and adding a total of pointCount * points. * * @param border * Border from which points are copied. * @param startPointIndex * Index of first point that is copied from border. * If this value is negative, points will be copied * starting from the first point in the border. * @param pointCount * Number of points that are copied. If this value * is negative all of the remaining points in the * border are copied. If zero, none are copied. */ void Border::addPoints(const Border* border, const int32_t startPointIndex, const int32_t pointCount) { CaretAssert(border); const int32_t startIndex = (startPointIndex >= 0) ? startPointIndex : 0; const int32_t endIndex = (pointCount >= 0) ? (startIndex + pointCount) : border->getNumberOfPoints(); for (int32_t i = startIndex; i < endIndex; i++) { SurfaceProjectedItem* spi = new SurfaceProjectedItem(*border->getPoint(i)); addPoint(spi); } } bool Border::isClosed() const { return m_closed; } void Border::setClosed(const bool& closed) { m_closed = closed; } /** * Add points to the border so that the last point * connects to the first point. */ void Border::addPointsToCloseBorderWithGeodesic(const SurfaceFile* surfaceFile) { const int32_t numberOfPoints = getNumberOfPoints(); if (numberOfPoints < 3) { return; } /* * Index of surface node nearest first border point */ float firstBorderPointXYZ[3]; m_points[0]->getProjectedPosition(*surfaceFile, firstBorderPointXYZ, true); const int firstNodeIndex = surfaceFile->closestNode(firstBorderPointXYZ); if (firstNodeIndex < 0) { return; } /* * Index of surface node nearest last border point */ float lastBorderPointXYZ[3]; m_points[numberOfPoints - 1]->getProjectedPosition(*surfaceFile, lastBorderPointXYZ, true); const int lastNodeIndex = surfaceFile->closestNode(lastBorderPointXYZ); if (lastNodeIndex < 0) { return; } /* * Geodesics from node nearest last border point */ std::vector nodeParents; std::vector nodeDistances; CaretPointer geoHelp = surfaceFile->getGeodesicHelper(); geoHelp->getGeoFromNode(lastNodeIndex, nodeDistances, nodeParents, true); /* * Get path along border points */ const int32_t numberOfSurfaceNodes = surfaceFile->getNumberOfNodes(); std::vector pathFromFirstNodeToLastNode; int32_t geoNodeIndex = firstNodeIndex; int32_t failCounter = 0; while (geoNodeIndex >= 0) { geoNodeIndex = nodeParents[geoNodeIndex]; if (geoNodeIndex == lastNodeIndex) { geoNodeIndex = -1; } else if (geoNodeIndex >= 0) { pathFromFirstNodeToLastNode.push_back(geoNodeIndex); } failCounter ++; if (failCounter > numberOfSurfaceNodes) { CaretLogWarning("Geodesic path for closing border failed."); pathFromFirstNodeToLastNode.clear(); } } /* * Add points to border. */ const float triangleAreas[3] = { 1.0, 0.0, 0.0 }; const StructureEnum::Enum structure = surfaceFile->getStructure(); const int32_t numNewPoints = static_cast(pathFromFirstNodeToLastNode.size()); for (int32_t i = (numNewPoints - 1); i >= 0; i--) { const int32_t nodeIndex = pathFromFirstNodeToLastNode[i]; const float* xyz = surfaceFile->getCoordinate(nodeIndex); SurfaceProjectedItem* spi = new SurfaceProjectedItem(); spi->setStereotaxicXYZ(xyz); spi->setStructure(structure); SurfaceProjectionBarycentric* bp = spi->getBarycentricProjection(); bp->setTriangleAreas(triangleAreas); const int32_t triangleNodes[3] = { nodeIndex, nodeIndex, nodeIndex }; bp->setTriangleNodes(triangleNodes); bp->setValid(true); addPoint(spi); } } /** * Remove the point at the given index. * @param indx * Index of point for removal. */ void Border::removePoint(const int32_t indx) { CaretAssertVectorIndex(m_points, indx); delete m_points[indx]; m_points.erase(m_points.begin() + indx); setModified(); } /** * Remove the first point from the border. */ void Border::removeFirstPoint() { const int numPoints = getNumberOfPoints(); if (numPoints > 0) { removePoint(0); } } /** * Remove the last point from the border. */ void Border::removeLastPoint() { const int numPoints = getNumberOfPoints(); if (numPoints > 0) { removePoint(numPoints - 1); } } /** * Reverse the order of points in a border. */ void Border::reverse() { std::reverse(m_points.begin(), m_points.end()); setModified(); } /** * Revise a border by extending from a of point border. * * @param surfaceFile * Surface on which border extension is performed. * @param pointIndex * Point nearest the first point in the segment. * @param segment * A border segment containing the extension. * @throws BorderException * If there is an error revising the border. */ void Border::reviseExtendFromPointIndex(SurfaceFile* surfaceFile, const int32_t pointIndex, const Border* segment) { const int32_t numPoints = getNumberOfPoints(); if (numPoints <= 2) { throw BorderException("Border being update contains less than two points"); } const int numberOfSegmentPoints = segment->getNumberOfPoints(); if (numberOfSegmentPoints <= 0) { throw BorderException("Border segment for extending contains no points"); } if ((pointIndex < 0) || (pointIndex >= numPoints)) { throw BorderException("Point index for extending border is invalid."); } /* * Copy the border just in case something goes wrong */ /* * Lengths from point index to start and end points */ const float distToStart = getSegmentLength(surfaceFile, 0, pointIndex); const float distToEnd = getSegmentLength(surfaceFile, pointIndex, numPoints - 1); /* * Create a temporary border */ Border tempBorder; /* * Add on to start or ending end of border */ if (distToStart < distToEnd) { /* * Reverse the new segment and it becomes the first part of the border */ Border segmentCopy = *segment; segmentCopy.reverse(); tempBorder.addPoints(&segmentCopy); /* * Add points from this border starting AFTER pointIndex * to the last point) */ const int32_t startPointIndex = pointIndex + 1; if (startPointIndex < numPoints) { tempBorder.addPoints(this, startPointIndex); } } else { /* * Add points from this border from the first point to * the point BEFORE pointIndex */ const int32_t pointCount = pointIndex; if (pointCount > 0) { tempBorder.addPoints(this, 0, pointCount); } /* * Add the new segment */ tempBorder.addPoints(segment); } replacePointsWithUndoSaving(&tempBorder); } /** * Revise a border by extending from the end of a border. * * @param surfaceFile * Surface on which border extension is performed. * @param segment * A border segment containing the extension. * @throws BorderException * If there is an error revising the border. */ void Border::reviseExtendFromEnd(SurfaceFile* surfaceFile, const Border* segment) { const int32_t numPoints = getNumberOfPoints(); if (numPoints <= 0) { throw BorderException("Border being update contains no points"); } const int numberOfSegmentPoints = segment->getNumberOfPoints(); if (numberOfSegmentPoints <= 0) { throw BorderException("Border segment for extending contains no points"); } /* * Get coordinate of first point in new segment */ float segmentStartXYZ[3]; if (segment->getPoint(0)->getProjectedPosition(*surfaceFile, segmentStartXYZ, true) == false) { throw BorderException("First point in extending segment has invalid coordinate. Redraw."); } /* * Find point in this border nearest start of new segment */ const float distanceTolerance = 5.0; float distanceToStartOfNewSegment = 0.0; const int32_t borderPointNearestNewSegmentStart = findPointIndexNearestXYZ(surfaceFile, segmentStartXYZ, distanceTolerance, distanceToStartOfNewSegment); if (borderPointNearestNewSegmentStart < 0) { throw BorderException("New segment does not start near an existing border"); } /* * Get distance from both ends of existing border to first * point in new segment */ float borderStartXYZ[3]; getPoint(0)->getProjectedPosition(*surfaceFile, borderStartXYZ, true); const float distToStart = MathFunctions::distance3D(borderStartXYZ, segmentStartXYZ); float borderEndXYZ[3]; getPoint(numPoints - 1)->getProjectedPosition(*surfaceFile, borderEndXYZ, true); const float distToEnd = MathFunctions::distance3D(borderEndXYZ, segmentStartXYZ); /* * Add on to start or ending end of border */ int32_t startPointIndex = -1; int32_t endPointIndex = -1; bool reverseOrderFlag = false; if (distToStart < distToEnd) { if (distToStart > distanceTolerance) { throw BorderException("New segment does not start near the end of a border."); } endPointIndex = borderPointNearestNewSegmentStart; reverseOrderFlag = true; } else { if (distToEnd > distanceTolerance) { throw BorderException("New segment does not start near the end of a border."); } startPointIndex = borderPointNearestNewSegmentStart; } /* * If needed, swap point indices * if (reverseOrderFlag) { std::swap(startPointIndex, endPointIndex); } */ /* * Create a temporary border */ Border tempBorder; /* * Add in points prior to updated points */ if (startPointIndex >= 0) { tempBorder.addPoints(this, 0, startPointIndex); } /* * Add new points */ Border segmentCopy = *segment; if (reverseOrderFlag) { segmentCopy.reverse(); } tempBorder.addPoints(&segmentCopy); /* * Add in points after updated points */ if (endPointIndex >= 0) { tempBorder.addPoints(this, (endPointIndex + 1)); } replacePointsWithUndoSaving(&tempBorder); } /** * Revise a border by erasing from the end of a border. * * @param surfaceFile * Surface on which border erasing is performed. * @param segment * A border segment containing the erasing. * @throws BorderException * If there is an error revising the border. */ void Border::reviseEraseFromEnd(SurfaceFile* surfaceFile, const Border* segment) { /* * Get coordinate of first and last points in the segment */ const int numberOfSegmentPoints = segment->getNumberOfPoints(); if (numberOfSegmentPoints <= 0) { throw BorderException("Border segment for erasing contains no points"); } float segmentStartXYZ[3]; if (segment->getPoint(0)->getProjectedPosition(*surfaceFile, segmentStartXYZ, true) == false) { throw BorderException("First point in erase segment has invalid coordinate. Redraw."); } float segmentEndXYZ[3]; if (segment->getPoint(numberOfSegmentPoints - 1)->getProjectedPosition(*surfaceFile, segmentEndXYZ, true) == false) { throw BorderException("End point in erase segment has invalid coordinate. Redraw."); } const float tolerance = 10.0; /* * Find points in this border nearest the first and * last points in the erase segment */ float distanceToStartPoint = 0.0; int32_t startPointIndex = findPointIndexNearestXYZ(surfaceFile, segmentStartXYZ, tolerance, distanceToStartPoint); if (startPointIndex < 0) { throw BorderException("Start of segment drawn for erasing is not close enough to existing border"); } float distanceToEndPoint = 0.0; int32_t endPointIndex = findPointIndexNearestXYZ(surfaceFile, segmentEndXYZ, tolerance, distanceToEndPoint); if (endPointIndex < 0) { throw BorderException("End of segment drawn for erasing is not close enough to existing border"); } /* * If needed, swap point indices */ const bool reverseOrderFlag = (startPointIndex > endPointIndex); if (reverseOrderFlag) { std::swap(startPointIndex, endPointIndex); } /* * Create a temporary border */ Border tempBorder; /* * Add in points prior to updated points */ if (startPointIndex >= 0) { tempBorder.addPoints(this, 0, startPointIndex); } /* * Add in points after updated points */ if (endPointIndex >= 0) { tempBorder.addPoints(this, (endPointIndex + 1)); } saveBorderForUndoEditing(); replacePoints(&tempBorder); } /** * Revise a border by replacing a segment in a border. * * @param surfaceFile * Surface on which border segment replacement is performed. * @param segment * A border containing the new segment. * @throws BorderException * If there is an error replacing the segment in the border. */ void Border::reviseReplaceSegment(SurfaceFile* surfaceFile, const Border* segment) { /* * Get coordinate of first and last points in the segment */ const int numberOfSegmentPoints = segment->getNumberOfPoints(); if (numberOfSegmentPoints < 2) { throw BorderException("Border segment for replacing contains less than 2 points"); } float segmentStartXYZ[3]; if (segment->getPoint(0)->getProjectedPosition(*surfaceFile, segmentStartXYZ, true) == false) { throw BorderException("First point in replace segment has invalid coordinate. Redraw."); } float segmentEndXYZ[3]; if (segment->getPoint(numberOfSegmentPoints - 1)->getProjectedPosition(*surfaceFile, segmentEndXYZ, true) == false) { throw BorderException("End point in replace segment has invalid coordinate. Redraw."); } const float tolerance = 10.0; /* * Locate points in this border that are nearest the start * and end points in the new border segment. */ float distanceOfFirstSegmentPointToThisBorder = 0.0; int32_t lowestPointIndex = findPointIndexNearestXYZ(surfaceFile, segmentStartXYZ, tolerance, distanceOfFirstSegmentPointToThisBorder); if (lowestPointIndex < 0) { throw BorderException("Start of segment drawn for replacing is not close enough to existing border"); } float distanceOfLastSegmentPointToThisBorder = 0.0; int32_t highestPointIndex = findPointIndexNearestXYZ(surfaceFile, segmentEndXYZ, tolerance, distanceOfLastSegmentPointToThisBorder); if (highestPointIndex < 0) { throw BorderException("End of segment drawn for replacing is not close enough to existing border"); } /* * Swap lowest and highest point indexes so that lowest < highest */ if (lowestPointIndex > highestPointIndex) { std::swap(lowestPointIndex, highestPointIndex); } /* * Length in this border from lowest to highest point index */ const float lowestToHighestLength = getSegmentLength(surfaceFile, lowestPointIndex, highestPointIndex); /* * Length in this border from highest to lowest point index (assumes border * is closed. */ const float highestToLowestLength = getSegmentLength(surfaceFile, highestPointIndex, lowestPointIndex); /* * Create a temporary border */ Border newBorder; Border newBorderSecondSegment; /* * Keep part of this border that between low and high * indices that is the LONGEST */ if (lowestToHighestLength > highestToLowestLength) { /* * Keep part of this border from lowest index to highest index */ const int32_t lowToHighCount = highestPointIndex - lowestPointIndex + 1; newBorder.addPoints(this, lowestPointIndex, lowToHighCount); } else { /* * Keep segment from highest index to end */ const int32_t highToEndCount = getNumberOfPoints() - highestPointIndex; newBorderSecondSegment.addPoints(this, highestPointIndex, highToEndCount); /* * Keep segment from start to lowest point index * NOTE: Segments need to be separate otherwise * linear border will become closed. */ const int32_t startToLowCount = lowestPointIndex + 1; newBorder.addPoints(this, 0, startToLowCount); } const int32_t newBorderNumberOfPoints = newBorder.getNumberOfPoints(); if (newBorderNumberOfPoints > 0) { float newBorderLastXYZ[3]; if (newBorder.getPoint(newBorderNumberOfPoints - 1)->getProjectedPosition(*surfaceFile, newBorderLastXYZ, true)) { /* * Get position of first and last points in the new segment. */ float segmentFirstPointXYZ[3]; const bool validFirstPoint = segment->getPoint(0)->getProjectedPosition(*surfaceFile, segmentFirstPointXYZ, true); float segmentLastPointXYZ[3]; const bool validLastPoint = segment->getPoint(numberOfSegmentPoints - 1)->getProjectedPosition(*surfaceFile, segmentLastPointXYZ, true); if (validFirstPoint && validLastPoint) { /* * Distance to last point in border being created * to first and last point in new segment */ const float firstDistance = MathFunctions::distance3D(newBorderLastXYZ, segmentFirstPointXYZ); const float lastDistance = MathFunctions::distance3D(newBorderLastXYZ, segmentLastPointXYZ); /* * Remove endpoint(s) of new segment if they are very * close to a point in this border that is being updated */ Border trimmedSegment(*segment); const float distanceTolerance = 1.0; if (distanceOfFirstSegmentPointToThisBorder < distanceTolerance) { trimmedSegment.removeLastPoint(); } if (distanceOfLastSegmentPointToThisBorder < distanceTolerance) { trimmedSegment.removeFirstPoint(); } const int32_t numTrimmedSegmentPoints = trimmedSegment.getNumberOfPoints(); if (numTrimmedSegmentPoints > 0) { if (firstDistance < lastDistance) { /* * Add new segment onto the end of the existing border piece */ newBorder.addPoints(&trimmedSegment, 0, numTrimmedSegmentPoints); } else { /* * New segment is probably opposite orientation * (clockwise/counter-clockwise) that border that is * being edited. */ Border reversedSegment(trimmedSegment); reversedSegment.reverse(); newBorder.addPoints(&reversedSegment, 0, reversedSegment.getNumberOfPoints()); } } if (newBorderSecondSegment.getNumberOfPoints() > 0) { newBorder.addPoints(&newBorderSecondSegment, 0, newBorderSecondSegment.getNumberOfPoints()); } /* * Replace this border with the newly created border */ replacePointsWithUndoSaving(&newBorder); } else { throw BorderException("Border replacement failed: First or last point in new segment failed to project."); } } else { throw BorderException("Border replacement failed: Failed to project original border segment."); } } else { throw BorderException("Border replacement failed: No points were kept from original border."); } } ///** // * Revise a border by replacing a segment in a border. // * // * @param surfaceFile // * Surface on which border segment replacement is performed. // * @param segment // * A border containing the new segment. // * @throws BorderException // * If there is an error replacing the segment in the border. // */ //void //Border::reviseReplaceSegment(SurfaceFile* surfaceFile, // const Border* segment) //{ // /* // * Get coordinate of first and last points in the segment // */ // const int numberOfSegmentPoints = segment->getNumberOfPoints(); // if (numberOfSegmentPoints <= 0) { // throw BorderException("Border segment for erasing contains no points"); // } // float segmentStartXYZ[3]; // if (segment->getPoint(0)->getProjectedPosition(*surfaceFile, // segmentStartXYZ, // true) == false) { // throw BorderException("First point in erase segment has invalid coordinate. Redraw."); // } // // float segmentEndXYZ[3]; // if (segment->getPoint(numberOfSegmentPoints - 1)->getProjectedPosition(*surfaceFile, // segmentEndXYZ, // true) == false) { // throw BorderException("End point in erase segment has invalid coordinate. Redraw."); // } // // const float tolerance = 10.0; // // /* // * Find points in this border nearest the first and // * last points in the erase segment // */ // float distanceToStartPoint = 0.0; // int32_t segmentStartPointIndex = findPointIndexNearestXYZ(surfaceFile, // segmentStartXYZ, // tolerance, // distanceToStartPoint); // if (segmentStartPointIndex < 0) { // throw BorderException("Start of segment drawn for erasing is not close enough to existing border"); // } // float distanceToEndPoint = 0.0; // int32_t segmentEndPointIndex = findPointIndexNearestXYZ(surfaceFile, // segmentEndXYZ, // tolerance, // distanceToEndPoint); // if (segmentEndPointIndex < 0) { // throw BorderException("End of segment drawn for erasing is not close enough to existing border"); // } // // /* // * Make copy of segment // */ // Border replacementSegment(*segment); // // /* // * If needed, swap point indices and reverse order in segment // */ // const bool reverseOrderFlag = (segmentStartPointIndex > segmentEndPointIndex); // if (reverseOrderFlag) { // std::swap(segmentStartPointIndex, segmentEndPointIndex); // } // // /* // * Determine which segment in border to replace by using the // * segment with the minimal length. // */ // const float startToEndLength = getSegmentLength(surfaceFile, // segmentStartPointIndex, // segmentEndPointIndex); // const float endToStartLength = getSegmentLength(surfaceFile, // segmentEndPointIndex, // segmentStartPointIndex); // // /* // * Create a temporary border // */ // Border tempBorder; // // if (startToEndLength < endToStartLength) { // /* // * Add in points from start of border // */ // if (segmentStartPointIndex >= 0) { // tempBorder.addPoints(this, // 0, // segmentStartPointIndex); // } // // /* // * Add new points // */ // if (reverseOrderFlag) { // replacementSegment.reverse(); // } // tempBorder.addPoints(&replacementSegment); // // /* // * Add in points from end of border // */ // if (segmentEndPointIndex >= 0) { // tempBorder.addPoints(this, // (segmentEndPointIndex + 1)); // } // } // else { // /* // * Add new points // */ // tempBorder.addPoints(&replacementSegment); // // /* // * Add points from start to end // */ // const int32_t numSegmentPoints = (segmentEndPointIndex // - segmentStartPointIndex // + 1); // tempBorder.addPoints(this, // segmentStartPointIndex, // numSegmentPoints); // } // // replacePoints(&tempBorder); //} /** * Get the length of the border segment formed by the all of the points * using the starting and ending point indices (inclusively). * * When (segmentStartPointIndex < endPointIndex), the segment length is that * formed by the points from startPointIndex to endPointIndex. * * When (segmentStartPointIndex > endPointIndex), the segment length is that * formed by startPointIndex to the last point, last point to first point * and first point to endPointIndex. (Assumes border is a circular border). * * @param segmentStartPointIndex * Index of first border point in the border segement. * @param segmentEndPointIndex * Index of last border point in the border segment. * @param surfaceFile * Surface on which straightline distance between border points * is calculated. */ float Border::getSegmentLength(SurfaceFile* surfaceFile, const int32_t segmentStartPointIndex, const int32_t segmentEndPointIndex) { CaretAssert(surfaceFile); const int32_t numPoints = getNumberOfPoints(); if (numPoints <= 1) { return 0.0; } const int32_t lastPointIndex = numPoints - 1; CaretAssert((segmentStartPointIndex >= 0) && (segmentStartPointIndex < numPoints)); CaretAssert((segmentEndPointIndex >= 0) && (segmentEndPointIndex < numPoints)); float segmentLength = 0.0; if (segmentStartPointIndex > segmentEndPointIndex) { CaretAssert(segmentStartPointIndex <= lastPointIndex); const float d1 = getSegmentLength(surfaceFile, segmentStartPointIndex, lastPointIndex); float d2 = 0.0; { float xyz[3], xyzNext[3]; if (m_points[lastPointIndex]->getProjectedPosition(*surfaceFile, xyz, true) && m_points[0]->getProjectedPosition(*surfaceFile, xyzNext, true)) { d2 = MathFunctions::distance3D(xyz, xyzNext); } } CaretAssert(0 <= segmentEndPointIndex); const float d3 = getSegmentLength(surfaceFile, 0, segmentEndPointIndex); segmentLength = (d1 + d2 + d3); } else { for (int32_t i = segmentStartPointIndex; i < segmentEndPointIndex; i++) { float xyz[3], xyzNext[3]; const int32_t iNext = i + 1; CaretAssert(iNext < numPoints); if (m_points[i]->getProjectedPosition(*surfaceFile, xyz, true) && m_points[iNext]->getProjectedPosition(*surfaceFile, xyzNext, true)) { segmentLength += MathFunctions::distance3D(xyz, xyzNext); } } } return segmentLength; } /** * Replace the points in this border with points from the given border. * An "undo" copy of the border is also created. * * @param border Border whose points are copied into this border. */ void Border::replacePointsWithUndoSaving(const Border* border) { saveBorderForUndoEditing(); replacePoints(border); } /** * Replace the points in this border with * the given border. * @param border Border whose points are copied * into this border. */ void Border::replacePoints(const Border* border) { removeAllPoints(); const int32_t numPoints = border->getNumberOfPoints(); for (int i = 0; i < numPoints; i++) { SurfaceProjectedItem* spi = new SurfaceProjectedItem(*border->getPoint(i)); addPoint(spi); } } /** * Set modification status of name/class to modified. * * Name/Class modification status is used * by the selection controls that display * borders based upon selected classes and * names. */ void Border::setNameOrClassModified() { m_groupNameSelectionItem = NULL; m_nameRgbaColorValid = false; m_classRgbaColorValid = false; } /** * Set the selection item for the group/name hierarchy. * * @param item * The selection item from the group/name hierarchy. */ void Border::setGroupNameSelectionItem(GroupAndNameHierarchyItem* item) { m_groupNameSelectionItem = item; } /** * @return The selection item for the Group/Name selection hierarchy. * May be NULL in some circumstances. */ const GroupAndNameHierarchyItem* Border::getGroupNameSelectionItem() const { return m_groupNameSelectionItem; } /** * Save the border for undo editing. */ void Border::saveBorderForUndoEditing() { if (m_copyOfBorderPriorToLastEditing == NULL) { m_copyOfBorderPriorToLastEditing = new Border(*this); } else { m_copyOfBorderPriorToLastEditing->replacePoints(this); } } /** * @return True if last editing of border can be "undone". */ bool Border::isUndoBorderValid() const { if (m_copyOfBorderPriorToLastEditing != NULL) { return true; } return false; } /** * Undo the last editing of this border. */ void Border::undoLastBorderEditing() { if (m_copyOfBorderPriorToLastEditing != NULL) { replacePoints(m_copyOfBorderPriorToLastEditing); delete m_copyOfBorderPriorToLastEditing; m_copyOfBorderPriorToLastEditing = NULL; } } /** * Get a description of this object's content. * @return String describing this object's content. */ AString Border::toString() const { return "Border " + m_name; } /** * Write the border to the XML Writer. * @param xmlWriter * Writer for XML output. */ void Border::writeAsXML(XmlWriter& xmlWriter) { xmlWriter.writeStartElement(XML_TAG_BORDER); xmlWriter.writeElementCharacters(XML_TAG_NAME, m_name); if (m_className.isEmpty() == false) { xmlWriter.writeElementCharacters(XML_TAG_CLASS_NAME, m_className); } const int32_t numPoints = getNumberOfPoints(); for (int32_t i = 0; i < numPoints; i++) { m_points[i]->writeAsXML(xmlWriter); } xmlWriter.writeEndElement(); } void Border::writeXML3(QXmlStreamWriter& xml) const { xml.writeStartElement("BorderPart"); xml.writeAttribute("Closed", (isClosed() ? "True" : "False")); int numPoints = getNumberOfPoints(); xml.writeStartElement("Vertices"); for (int p = 0; p < numPoints; ++p) { const SurfaceProjectionBarycentric* thisBary = getPoint(p)->getBarycentricProjection(); const int32_t* nodes = thisBary->getTriangleNodes(); xml.writeCharacters(AString::number(nodes[0]) + " " + AString::number(nodes[1]) + " " + AString::number(nodes[2]) + "\n"); } xml.writeEndElement(); xml.writeStartElement("Weights"); for (int p = 0; p < numPoints; ++p) { const SurfaceProjectionBarycentric* thisBary = getPoint(p)->getBarycentricProjection(); const float* weights = thisBary->getTriangleAreas(); xml.writeCharacters(AString::number(weights[0]) + " " + AString::number(weights[1]) + " " + AString::number(weights[2]) + "\n"); } xml.writeEndElement(); xml.writeEndElement();//BorderPart } void Border::readXML1(QXmlStreamReader& xml) { clear(); CaretAssert(xml.isStartElement() && xml.name() == "Border"); bool haveName = false, haveClass = false, haveColorType = false; for (xml.readNext(); !xml.atEnd() && !xml.isEndElement(); xml.readNext()) { if (xml.isStartElement()) { QStringRef name = xml.name(); if (name == "Name") { if (haveName) throw DataFileException("multiple Name elements in one Border element"); m_name = xml.readElementText();//sets error on unexpected child element if (xml.hasError()) throw DataFileException("XML parsing error in Name: " + xml.errorString()); haveName = true; } else if (name == "ClassName") { if (haveClass) throw DataFileException("multiple ClassName elements in one Border element"); m_className = xml.readElementText();//sets error on unexpected child element if (xml.hasError()) throw DataFileException("XML parsing error in ClassName: " + xml.errorString()); haveClass = true; } else if (name == "ColorName") {//a gui setting that caret5 wrote into the border file, so ignore it if (haveColorType) throw DataFileException("multiple ColorName elements in one Border element"); xml.readElementText();//errors on unexpected element if (xml.hasError()) throw DataFileException("XML parsing error in ColorName: " + xml.errorString()); haveColorType = true; } else if (name == "SurfaceProjectedItem") { CaretPointer myItem(new SurfaceProjectedItem());//again, because current interface requires ownership passing of pointer myItem->readBorderFileXML1(xml); addPoint(myItem.releasePointer()); } else { throw DataFileException("unexpected element in Border: " + name.toString()); } } } if (xml.hasError()) throw DataFileException("XML parsing error in Border: " + xml.errorString()); CaretAssert(xml.isEndElement() && xml.name() == "Border"); if (getNumberOfPoints() > 1 && (*getPoint(0) == *getPoint(getNumberOfPoints() - 1))) { m_closed = true; removeLastPoint(); } } void Border::readXML3(QXmlStreamReader& xml) { clear(); CaretAssert(xml.isStartElement() && xml.name() == "BorderPart"); QXmlStreamAttributes myAttrs = xml.attributes(); if (!myAttrs.hasAttribute("Closed")) throw DataFileException("BorderPart element missing required attribute Closed"); QStringRef closedStr = myAttrs.value("Closed"); if (closedStr == "True") { setClosed(true); } else if (closedStr == "False") { setClosed(false); } else { throw DataFileException("unrecognized value for Closed attribute in BorderPart: " + closedStr.toString()); } vector vertices; vector weights; bool haveVertices = false, haveWeights = false; for (xml.readNext(); !xml.atEnd() && !xml.isEndElement(); xml.readNext()) { if (xml.isStartElement()) { QStringRef name = xml.name(); if (name == "Vertices") { if (haveVertices) throw DataFileException("multiple Vertices elements in one BorderPart element"); QString vertexText = xml.readElementText();//errors on unexpected element if (xml.hasError()) throw DataFileException("XML parsing error in Vertices: " + xml.errorString()); QStringList vertexStrings = vertexText.split(QRegExp("\\s+"), QString::SkipEmptyParts); int numItems = (int)vertexStrings.size(); if (numItems % 3 != 0) throw DataFileException("number of items in Vertices element text is not a multiple of 3"); for (int i = 0; i < numItems; ++i) { bool ok = false; int tempVal = vertexStrings[i].toInt(&ok); if (!ok) throw DataFileException("non-integer item in Vertices text: " + vertexStrings[i]); if (tempVal < 0) throw DataFileException("negative value in Vertices"); vertices.push_back(tempVal); } haveVertices = true; } else if (name == "Weights") { if (haveWeights) throw DataFileException("multiple Weights elements in one BorderPart element"); QString vertexText = xml.readElementText();//errors on unexpected element if (xml.hasError()) throw DataFileException("XML parsing error in Weights: " + xml.errorString()); QStringList vertexStrings = vertexText.split(QRegExp("\\s+"), QString::SkipEmptyParts); int numItems = (int)vertexStrings.size(); if (numItems % 3 != 0) throw DataFileException("number of items in Weights element text is not a multiple of 3"); for (int i = 0; i < numItems; ++i) { bool ok = false; float tempVal = vertexStrings[i].toFloat(&ok); if (!ok) throw DataFileException("non-numeric item in Weights text: " + vertexStrings[i]); if (tempVal < 0.0f) { CaretLogWarning("negative value in Weights, set to zero"); tempVal = 0.0f; } weights.push_back(tempVal); } haveWeights = true; } else { throw DataFileException("unexpected element in BorderPart: " + name.toString()); } } } if (xml.hasError()) throw DataFileException("XML parsing error in BorderPart: " + xml.errorString()); CaretAssert(xml.isEndElement() && xml.name() == "BorderPart"); if (!haveVertices || !haveWeights) throw DataFileException("BorderPart missing required Vertices or Weights element"); if (vertices.size() != weights.size()) throw DataFileException("Vertices and Weights don't contain the same number of elements"); int numPoints = (int)vertices.size() / 3; for (int i = 0; i < numPoints; ++i) { int i3 = i * 3; CaretPointer myItem(new SurfaceProjectedItem());//because addPoint takes ownership of a raw pointer myItem->setStructure(StructureEnum::ALL);//HACK: placeholder because structure is a file attribute in v3, not a border attribute SurfaceProjectionBarycentric* myBary = myItem->getBarycentricProjection(); myBary->setTriangleNodes(vertices.data() + i3); myBary->setTriangleAreas(weights.data() + i3); myBary->setValid(true);//signed distance from surface iniializes to 0 addPoint(myItem.releasePointer()); } } workbench-1.1.1/src/Files/Border.h000066400000000000000000000153211255417355300167270ustar00rootroot00000000000000#ifndef __BORDER__H_ #define __BORDER__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "BorderException.h" #include "CaretObjectTracksModification.h" #include "StructureEnum.h" #include "XmlException.h" class QXmlStreamReader; class QXmlStreamWriter; namespace caret { class GroupAndNameHierarchyItem; class SurfaceFile; class SurfaceProjectedItem; class XmlWriter; class Border : public CaretObjectTracksModification { public: Border(); virtual ~Border(); Border(const Border& obj); Border& operator=(const Border& obj); static Border* newInstanceFromSurfaceNodes(const AString& borderName, const SurfaceFile* surfaceFile, std::vector& nodeIndices); virtual AString toString() const; void clear(); AString getName() const; void setName(const AString& name); AString getClassName() const; void setClassName(const AString& name); StructureEnum::Enum getStructure() const; void setStructure(const StructureEnum::Enum& structure); bool verifyAllPointsOnSameStructure() const; bool verifyForSurfaceNumberOfNodes(const int32_t& numNodes) const; int32_t getNumberOfPoints() const; const SurfaceProjectedItem* getPoint(const int32_t indx) const; SurfaceProjectedItem* getPoint(const int32_t indx); int32_t findPointIndexNearestXYZ(const SurfaceFile* surfaceFile, const float xyz[3], const float maximumDistance, float& distanceToNearestPointOut) const; void addPoint(SurfaceProjectedItem* point); void addPoints(const Border* border, const int32_t startPointIndex = -1, const int32_t pointCount = -1); bool isClosed() const; void setClosed(const bool& closed); void addPointsToCloseBorderWithGeodesic(const SurfaceFile* surfaceFile); void removeAllPoints(); void removePoint(const int32_t indx); void removeFirstPoint(); void removeLastPoint(); void replacePointsWithUndoSaving(const Border* border); void reverse(); void reviseExtendFromPointIndex(SurfaceFile* surfaceFile, const int32_t pointIndex, const Border* segment); void reviseExtendFromEnd(SurfaceFile* surfaceFile, const Border* segment); void reviseEraseFromEnd(SurfaceFile* surfaceFile, const Border* segment); void reviseReplaceSegment(SurfaceFile* surfaceFile, const Border* segment); void writeAsXML(XmlWriter& xmlWriter); void writeXML3(QXmlStreamWriter& xml) const; void readXML1(QXmlStreamReader& xml); void readXML3(QXmlStreamReader& xml); void setGroupNameSelectionItem(GroupAndNameHierarchyItem* item); const GroupAndNameHierarchyItem* getGroupNameSelectionItem() const; bool isClassRgbaValid() const; void setClassRgbaInvalid(); const float* getClassRgba() const; void getClassRgba(float rgba[4]) const; void setClassRgba(const float rgba[4]); bool isNameRgbaValid() const; void setNameRgbaInvalid(); const float* getNameRgba() const; void getNameRgba(float rgba[4]) const; void setNameRgba(const float rgba[4]); bool isUndoBorderValid() const; void undoLastBorderEditing(); static const AString XML_TAG_BORDER; static const AString XML_TAG_NAME; static const AString XML_TAG_CLASS_NAME; private: void copyHelperBorder(const Border& obj); void setNameOrClassModified(); float getSegmentLength(SurfaceFile* surfaceFile, const int32_t startPointIndex, const int32_t endPointIndex); void replacePoints(const Border* border); void saveBorderForUndoEditing(); AString m_name; AString m_className; std::vector m_points; bool m_closed; /** RGBA color component assigned to border's class name */ float m_classRgbaColor[4]; /** RGBA color components assigned to border's class name validity */ bool m_classRgbaColorValid; /** RGBA color components assigned to focus' name */ float m_nameRgbaColor[4]; /** RGBA color components assigned to focus' name validity */ bool m_nameRgbaColorValid; /** Selection status of this border in the group/name hierarchy */ GroupAndNameHierarchyItem* m_groupNameSelectionItem; /** * When "this" border is edited, a copy of "this" is placed into * this member so that the border can be restored if the editing * was not satisfactory. */ Border* m_copyOfBorderPriorToLastEditing; }; #ifdef __BORDER_DECLARE__ const AString Border::XML_TAG_BORDER = "Border"; const AString Border::XML_TAG_CLASS_NAME = "ClassName"; const AString Border::XML_TAG_NAME = "Name"; #endif // __BORDER_DECLARE__ } // namespace #endif //__BORDER__H_ workbench-1.1.1/src/Files/BorderException.cxx000066400000000000000000000034541255417355300211650ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "BorderException.h" #include using namespace caret; /** * Constructor. * */ BorderException::BorderException() : CaretException() { this->initializeMembersBorderException(); } /** * Constructor. * * @param s Description of the exception. * */ BorderException::BorderException( const AString& s) : CaretException(s) { this->initializeMembersBorderException(); } /** * Copy Constructor. * @param e * Exception that is copied. */ BorderException::BorderException(const BorderException& e) : CaretException(e) { } /** * Assignment operator. * @param e * Exception that is copied. * @return * Copy of the exception. */ BorderException& BorderException::operator=(const BorderException& e) { if (this != &e) { CaretException::operator=(e); } return *this; } /** * Destructor */ BorderException::~BorderException() throw() { } void BorderException::initializeMembersBorderException() { } workbench-1.1.1/src/Files/BorderException.h000066400000000000000000000026461255417355300206140ustar00rootroot00000000000000#ifndef __BORDER_EXCEPTION_H__ #define __BORDER_EXCEPTION_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "CaretException.h" namespace caret { /** * An exception thrown during Border processing. */ class BorderException : public CaretException { public: BorderException(); BorderException(const AString& s); BorderException(const BorderException& e); BorderException& operator=(const BorderException& e); virtual ~BorderException() throw(); private: void initializeMembersBorderException(); }; } // namespace #endif // __BORDER_EXCEPTION_H__ workbench-1.1.1/src/Files/BorderFile.cxx000066400000000000000000002747021255417355300201140ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #include #include #include #include #include #define __BORDER_FILE_DECLARE__ #include "BorderFile.h" #undef __BORDER_FILE_DECLARE__ #include "Border.h" #include "BorderPointFromSearch.h" #include "CaretAssert.h" #include "CaretLogger.h" #include "DataFileContentInformation.h" #include "DataFileException.h" #include "GroupAndNameHierarchyModel.h" #include "FileAdapter.h" #include "FileInformation.h" #include "GiftiLabel.h" #include "GiftiLabelTable.h" #include "GiftiMetaData.h" #include "MathFunctions.h" #include "SurfaceFile.h" #include "SurfaceProjectedItem.h" #include "SurfaceProjectionBarycentric.h" #include "TextFile.h" #include "XmlAttributes.h" #include "XmlSaxParser.h" #include "XmlWriter.h" using namespace caret; using namespace std; /** * \class caret::BorderFile * \brief File containing borders. */ /** * Constructor. */ BorderFile::BorderFile() : CaretDataFile(DataFileTypeEnum::BORDER) { initializeBorderFile(); } /** * Destructor. */ BorderFile::~BorderFile() { delete m_classColorTable; delete m_nameColorTable; delete m_metadata; for (std::vector::iterator iter = m_borders.begin(); iter != m_borders.end(); iter++) { delete *iter; } m_borders.clear(); delete m_classNameHierarchy; } /** * Initialize members of a border file. */ void BorderFile::initializeBorderFile() { m_classColorTable = new GiftiLabelTable(); m_nameColorTable = new GiftiLabelTable(); m_classNameHierarchy = new GroupAndNameHierarchyModel(); m_metadata = new GiftiMetaData(); m_forceUpdateOfGroupAndNameHierarchy = true; m_structure = StructureEnum::ALL; m_numNodes = -1; } /** * Copy constructor. * @param obj * Object that is copied. */ BorderFile::BorderFile(const BorderFile& obj) : CaretDataFile(obj) { initializeBorderFile(); copyHelperBorderFile(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ BorderFile& BorderFile::operator=(const BorderFile& obj) { if (this != &obj) { clear(); CaretDataFile::operator=(obj); copyHelperBorderFile(obj); } return *this; } /** * Helps with copying an object of this type. * @param obj * Object that is copied. */ void BorderFile::copyHelperBorderFile(const BorderFile& obj) { *m_classColorTable = *obj.m_classColorTable; *m_nameColorTable = *obj.m_nameColorTable; if (m_classNameHierarchy != NULL) { delete m_classNameHierarchy; } m_classNameHierarchy = new GroupAndNameHierarchyModel(); *m_metadata = *obj.m_metadata; const int32_t numBorders = obj.getNumberOfBorders(); for (int32_t i = 0; i < numBorders; i++) { m_borders.push_back(new Border(*obj.getBorder(i))); } m_forceUpdateOfGroupAndNameHierarchy = true; setModified(); } /** * @return Is this border file empty (contains zero borders)? */ bool BorderFile::isEmpty() const { return m_borders.empty(); } /** * @return The structure for this file. */ StructureEnum::Enum BorderFile::getStructure() const { return m_structure; } /** * @return Structures of all borders. In most cases, this method * is equivalent to getStructure(). However, there are some older, * obsolete border files that contain borders for multiple structures * in which case this returns the structures for those borders such as * CORTEX_LEFT and CORTEX_RIGHT. */ std::vector BorderFile::getAllBorderStructures() const { std::set uniqueStructures; const int32_t numBorders = getNumberOfBorders(); for (int32_t i = 0; i < numBorders; i++) { uniqueStructures.insert(getBorder(i)->getStructure()); } std::vector structures(uniqueStructures.begin(), uniqueStructures.end()); return structures; } /** * Create new border files each of which contains a border for a single structure. * If there is not a border file name for for a structure, no file will be * produced for that file and it is not considered an error. * * @param singleStructureFileNames * Each 'pair' is the filename for a structure. * @param structureNumberOfNodes * Each pair is the number of nodes for a structure. This parameter is * optional and when available, is used to validate the barycentric * node indices in a border. * @param singleStructureBorderFilesOut * On output contains border files for each of the structures. * @param errorMessageOut * If unsuccessful will contain description of error(s). * @return * True if successful and all files were created, else false. */ bool BorderFile::splitIntoSingleStructureFiles(const std::map& singleStructureFileNames, const std::map& structureNumberOfNodes, std::vector& singleStructureBorderFilesOut, AString& errorMessageOut) const { singleStructureBorderFilesOut.clear(); errorMessageOut.clear(); /* * Create single structure border files. */ std::map structureBorderFiles; for (std::map::const_iterator nameIter = singleStructureFileNames.begin(); nameIter != singleStructureFileNames.end(); nameIter++) { const StructureEnum::Enum structure = nameIter->first; if (StructureEnum::isSingleStructure(structure)) { /* * Create the border file and set the structure */ BorderFile* borderFile = new BorderFile(); borderFile->setFileName(nameIter->second); borderFile->setStructure(structure); /* * Set number of nodes for border file (if available) */ const std::map::const_iterator structNumNodesIter = structureNumberOfNodes.find(structure); if (structNumNodesIter != structureNumberOfNodes.end()) { const int32_t numNodes = structNumNodesIter->second; borderFile->setNumberOfNodes(numNodes); } /* * Add to border file for each structure. */ structureBorderFiles.insert(std::make_pair(structure, borderFile)); } else { errorMessageOut.appendWithNewLine("Structure " + StructureEnum::toGuiName(structure) + " is not a 'single' type structure."); } } /* * Copy borders to the appropriate single-structure files. */ const int32_t numBorders = getNumberOfBorders(); for (int32_t i = 0; i < numBorders; i++) { const Border* border = getBorder(i); CaretAssert(border); const StructureEnum::Enum structure = border->getStructure(); /* * Find output file with same structure as this border */ std::map::iterator fileIter = structureBorderFiles.find(structure); if (fileIter != structureBorderFiles.end()) { if (border->verifyAllPointsOnSameStructure()) { /* * Surface number of nodes may be available */ int32_t surfaceNumberOfNodes = 0; const std::map::const_iterator structNumNodesIter = structureNumberOfNodes.find(structure); if (structNumNodesIter != structureNumberOfNodes.end()) { surfaceNumberOfNodes = structNumNodesIter->second; } /* * If possible verify border node indices valid for structure */ bool copyBorderFlag = false; if (surfaceNumberOfNodes > 0) { if (border->verifyForSurfaceNumberOfNodes(surfaceNumberOfNodes)) { copyBorderFlag = true; } else { errorMessageOut.appendWithNewLine("Border index=" + AString::number(i) + " name=" + border->getName() + " contains an incompatible number of nodes for structure " + StructureEnum::toGuiName(structure)); } } else { /* * Unable to verify number of nodes so assume okay */ copyBorderFlag = true; } if (copyBorderFlag) { Border* borderCopy = new Border(*border); fileIter->second->addBorder(borderCopy); const GiftiLabel* nameLabel = m_nameColorTable->getLabelBestMatching(border->getName()); fileIter->second->getNameColorTable()->addLabel(nameLabel); const GiftiLabel* classLabel = m_classColorTable->getLabelBestMatching(border->getClassName()); fileIter->second->getClassColorTable()->addLabel(classLabel); } } else { errorMessageOut.appendWithNewLine("Border index=" + AString::number(i) + " name=" + border->getName() + " is an invalid border that contains points on multiple structures"); } } } if (errorMessageOut.isEmpty()) { /* * Success: return the single structure border files that were created. */ for (std::map::iterator fileIter = structureBorderFiles.begin(); fileIter != structureBorderFiles.end(); fileIter++) { singleStructureBorderFilesOut.push_back(fileIter->second); } return true; } /* * Had an error so delete any single structure border files that were created. */ for (std::map::iterator fileIter = structureBorderFiles.begin(); fileIter != structureBorderFiles.end(); fileIter++) { delete fileIter->second; } return false; } /** * Set the structure for this file. * @param structure * New structure for this file. */ void BorderFile::setStructure(const StructureEnum::Enum structure) { if (m_structure == StructureEnum::ALL && m_borders.size() != 0) {//not really sure what this should do, so throw an error for now throw DataFileException(getFileName(), "attempt to set structure on multi-structure border file"); } int numBorders = (int)m_borders.size(); for (int i = 0; i < numBorders; ++i) { Border* thisBorder = m_borders[i]; int numPoints = thisBorder->getNumberOfPoints(); for (int j = 0; j < numPoints; ++j) { thisBorder->getPoint(j)->setStructure(structure); } } m_structure = structure; setModified(); } /** * @return Get access to the file's metadata. */ GiftiMetaData* BorderFile::getFileMetaData() { return m_metadata; } /** * @return Get access to unmodifiable file's metadata. */ const GiftiMetaData* BorderFile::getFileMetaData() const { return m_metadata; } /** * Clear the border file. */ void BorderFile::clear() { CaretDataFile::clear(); m_classNameHierarchy->clear(); m_classColorTable->clear(); m_nameColorTable->clear(); m_metadata->clear(); const int32_t numBorders = getNumberOfBorders(); for (int32_t i = 0; i < numBorders; i++) { delete m_borders[i]; } m_borders.clear(); m_structure = StructureEnum::ALL; m_numNodes = -1; m_borderMDKeys.clear(); m_borderMDValues.clear(); } int32_t BorderFile::getNumberOfNodes() const { if (m_structure == StructureEnum::ALL) return -1;//TSC: i think multi-structure should always return -1 return m_numNodes; } void BorderFile::setNumberOfNodes(const int32_t& numNodes) { if (numNodes < 1) { throw DataFileException(getFileName(), "attempt to set non-positive number of vertices on border file"); } int numBorders = (int)m_borders.size(); for (int i = 0; i < numBorders; ++i) { if (!m_borders[i]->verifyForSurfaceNumberOfNodes(numNodes)) { throw DataFileException(getFileName(), "cannot set border file number of vertices less than the vertices used by its borders"); } } m_numNodes = numNodes;//even if we are currently multi-structure, remember the number of nodes that was set setModified(); } /** * If the number of nodes is not valid, it may indicate an old border file * that allows borders from multiple structures. So, examine all of the * borders and if ALL of the borders resized on the same structure, set the * number of nodes. * * @param structureToNodeCount * A map containing the number of nodes for each valid structure. * @throw DataFileException * If a border is not valid for its corresponding structure * (meaning, a node index used by a border is greater than the * number of nodes in the corresponding structure). */ void BorderFile::updateNumberOfNodesIfSingleStructure(const std::map& structureToNodeCount) { if (getNumberOfNodes() > 0) { return; } const int32_t numBorders = getNumberOfBorders(); /* * Verify that all borders in this file are for the same structure */ bool allStructuresMatchFlag = true; StructureEnum::Enum firstBorderStructure = StructureEnum::INVALID; for (int32_t i = 0; i < numBorders; i++) { const StructureEnum::Enum structure = m_borders[i]->getStructure(); if (i == 0) { firstBorderStructure = structure; } else { if (structure != firstBorderStructure) { allStructuresMatchFlag = false; break; } } } /* * If all of the borders in this file are for the same structure, * see if the number of nodes is available for the structure, and if so, * set the number of borders for the file. */ if (allStructuresMatchFlag) { if ((firstBorderStructure != StructureEnum::INVALID) && (firstBorderStructure != StructureEnum::ALL)) { const std::map::const_iterator iter = structureToNodeCount.find(firstBorderStructure); if (iter != structureToNodeCount.end()) { const int32_t numNodes = iter->second; setNumberOfNodes(numNodes); setStructure(firstBorderStructure); CaretLogInfo("Updated border file: " + getFileNameNoPath() + " structure=" + StructureEnum::toGuiName(firstBorderStructure) + " number-of-nodes=" + AString::number(numNodes)); } } } } /** * @return the number of borders. */ int32_t BorderFile::getNumberOfBorders() const { return m_borders.size(); } /** * Get the border at the given index. * @param indx * Index of the border. * @return * Border at the given index. */ Border* BorderFile::getBorder(const int32_t indx) { CaretAssertVectorIndex(m_borders, indx); return m_borders[indx]; } /** * Is the given border in this border file? * * @param border * The border being queried * @return * True if the border is in this file, else false. */ bool BorderFile::containsBorder(const Border* border) const { if (std::find(m_borders.begin(), m_borders.end(), border) != m_borders.end()) { return true; } return false; } /** * Get the border at the given index. * @param indx * Index of the border. * @return * Border at the given index. */ const Border* BorderFile::getBorder(const int32_t indx) const { CaretAssertVectorIndex(m_borders, indx); return m_borders[indx]; } /** * Find ALL borders that have one endpoint within the given distance * of the first point of the given border segment. * * @param displayGroup * Display group in which border is tested for display. * @param browserTabIndex * Tab index in which border is displayed. * @param surfaceFile * Surface file used for unprojection of border points. * @param borderSegment * The border segment. * @param maximumDistance * Maximum distance coordinate can be from a border point. * @param borderPointsOut * Contains result of search. */ void BorderFile::findAllBordersWithEndPointNearSegmentFirstPoint(const DisplayGroupEnum::Enum displayGroup, const int32_t browserTabIndex, const SurfaceFile* surfaceFile, const Border* borderSegment, const float maximumDistance, std::vector& borderPointsOut) const { CaretAssert(surfaceFile); CaretAssert(borderSegment); borderPointsOut.clear(); if (borderSegment->getNumberOfPoints() < 2) { return; } float segFirstXYZ[3]; if (! borderSegment->getPoint(0)->getProjectedPosition(*surfaceFile, segFirstXYZ, false)) { /* * Point did not unproject */ return; } BorderFile* nonConstBorderFile = const_cast(this); const int32_t numBorders = getNumberOfBorders(); for (int32_t borderIndex = 0; borderIndex < numBorders; borderIndex++) { Border* border = m_borders[borderIndex]; if (nonConstBorderFile->isBorderDisplayed(displayGroup, browserTabIndex, border) == false) { continue; } if (border->getStructure() == surfaceFile->getStructure()) { /* * Test first endpoint */ const int32_t numPoints = border->getNumberOfPoints(); float nearestPointDistanceSquared = -1.0; int32_t neartestPointIndex = -1; if (numPoints > 0) { float pointXYZ[3]; if (border->getPoint(0)->getProjectedPosition(*surfaceFile, pointXYZ, false)) { nearestPointDistanceSquared = MathFunctions::distanceSquared3D(pointXYZ, segFirstXYZ); neartestPointIndex = 0; } } /* * Test last endpoint */ if (numPoints > 1) { const int32_t lastPointIndex = numPoints - 1; float pointXYZ[3]; if (border->getPoint(lastPointIndex)->getProjectedPosition(*surfaceFile, pointXYZ, false)) { const float dist2 = MathFunctions::distanceSquared3D(pointXYZ, segFirstXYZ); if (nearestPointDistanceSquared >= 0.0) { if (dist2 < nearestPointDistanceSquared) { neartestPointIndex = lastPointIndex; nearestPointDistanceSquared = dist2; } } else { neartestPointIndex = lastPointIndex; nearestPointDistanceSquared = dist2; } } } if (neartestPointIndex >= 0) { const float nearestPointDistance = std::sqrt(nearestPointDistanceSquared); if (nearestPointDistance <= maximumDistance) { BorderPointFromSearch bpo; bpo.setData(const_cast(this), border, borderIndex, neartestPointIndex, nearestPointDistance); borderPointsOut.push_back(bpo); } } } } } /** * Find ALL borders that have any point within the given distance * of the first point of the given border segment. * * @param displayGroup * Display group in which border is tested for display. * @param browserTabIndex * Tab index in which border is displayed. * @param surfaceFile * Surface file used for unprojection of border points. * @param borderSegment * The border segment. * @param maximumDistance * Maximum distance coordinate can be from a border point. * @param borderPointsOut * Contains result of search. */ void BorderFile::findAllBordersWithAnyPointNearSegmentFirstPoint(const DisplayGroupEnum::Enum displayGroup, const int32_t browserTabIndex, const SurfaceFile* surfaceFile, const Border* borderSegment, const float maximumDistance, std::vector& borderPointsOut) const { CaretAssert(surfaceFile); CaretAssert(borderSegment); borderPointsOut.clear(); if (borderSegment->getNumberOfPoints() < 2) { return; } float segFirstXYZ[3]; if (! borderSegment->getPoint(0)->getProjectedPosition(*surfaceFile, segFirstXYZ, false)) { /* * Point did not unproject */ return; } BorderFile* nonConstBorderFile = const_cast(this); const int32_t numBorders = getNumberOfBorders(); for (int32_t borderIndex = 0; borderIndex < numBorders; borderIndex++) { Border* border = m_borders[borderIndex]; if (nonConstBorderFile->isBorderDisplayed(displayGroup, browserTabIndex, border) == false) { continue; } if (border->getStructure() == surfaceFile->getStructure()) { /* * Test all points */ float nearestPointDistanceSquared = std::numeric_limits::max(); int32_t nearestPointIndex = -1; const int32_t numPoints = border->getNumberOfPoints(); for (int32_t pointIndex = 0; pointIndex < numPoints; pointIndex++) { float pointXYZ[3]; if (border->getPoint(pointIndex)->getProjectedPosition(*surfaceFile, pointXYZ, false)) { const float distSQ = MathFunctions::distanceSquared3D(pointXYZ, segFirstXYZ); if (distSQ < nearestPointDistanceSquared) { nearestPointDistanceSquared = distSQ; nearestPointIndex = pointIndex; } } } if (nearestPointIndex >= 0) { const float nearestPointDistance = std::sqrt(nearestPointDistanceSquared); if (nearestPointDistance <= maximumDistance) { BorderPointFromSearch bpo; bpo.setData(const_cast(this), border, borderIndex, nearestPointIndex, nearestPointDistance); borderPointsOut.push_back(bpo); } } } } } /** * Find ALL borders that have ANY points within the given distance * of the two given coordinates. * * @param displayGroup * Display group in which border is tested for display. * @param browserTabIndex * Tab index in which border is displayed. * @param surfaceFile * Surface file used for unprojection of border points. * @param borderSegment * The border segment. * @param maximumDistance * Maximum distance coordinate can be from a border point. * @param borderPointsOut * Contains result of search. */ void BorderFile::findAllBordersWithPointsNearBothSegmentEndPoints(const DisplayGroupEnum::Enum displayGroup, const int32_t browserTabIndex, const SurfaceFile* surfaceFile, const Border* borderSegment, const float maximumDistance, std::vector& borderPointsOut) const { CaretAssert(surfaceFile); CaretAssert(borderSegment); borderPointsOut.clear(); if (borderSegment->getNumberOfPoints() < 2) { return; } float segFirstXYZ[3], segLastXYZ[3]; const int32_t segLpIndex = borderSegment->getNumberOfPoints() - 1; if (borderSegment->getPoint(0)->getProjectedPosition(*surfaceFile, segFirstXYZ, false) && borderSegment->getPoint(segLpIndex)->getProjectedPosition(*surfaceFile, segLastXYZ, false)) { /* OK - both points have valid coordinates */ } else { /* One or both points failed to project */ return; } BorderFile* nonConstBorderFile = const_cast(this); const int32_t numBorders = getNumberOfBorders(); for (int32_t borderIndex = 0; borderIndex < numBorders; borderIndex++) { Border* border = m_borders[borderIndex]; if (nonConstBorderFile->isBorderDisplayed(displayGroup, browserTabIndex, border) == false) { continue; } if (border->getStructure() == surfaceFile->getStructure()) { /* * Test first query point */ float distance1 = 0.0; const int32_t nearestIndex1 = border->findPointIndexNearestXYZ(surfaceFile, segFirstXYZ, maximumDistance, distance1); float distance2 = 0.0; const int32_t nearestIndex2 = border->findPointIndexNearestXYZ(surfaceFile, segLastXYZ, maximumDistance, distance2); if ((nearestIndex1 >= 0) && (nearestIndex2 >= 0)) { const float averageDistance = (distance1 + distance2) / 2.0; BorderPointFromSearch bpo; bpo.setData(const_cast(this), border, borderIndex, nearestIndex1, averageDistance); borderPointsOut.push_back(bpo); } } } } /** * Find ALL borders that have ANY points within the given * region of interest. Since all borders are projected to * the surface, * * @param displayGroup * Display group in which border is tested for display. * @param browserTabIndex * Tab index in which border is displayed. * @param surfaceFile * Surface file used for unprojection of border points. * @param nodesInROI * Indices if a node is inside (true) or outside (false) the ROI. * Number of elements MUST BE the number of nodes in the surface. * @param insideCountAndBorderOut * Output vector pair with first being number of border points inside the ROI and * and second is the corresponding border. */ void BorderFile::findBordersInsideRegionOfInterest(const DisplayGroupEnum::Enum displayGroup, const int32_t browserTabIndex, const SurfaceFile* surfaceFile, const std::vector& nodesInROI, std::vector >& insideCountAndBorderOut) const { CaretAssert(surfaceFile); const int32_t surfaceNumberOfNodes = surfaceFile->getNumberOfNodes(); CaretAssert(surfaceNumberOfNodes == static_cast(nodesInROI.size())); insideCountAndBorderOut.clear(); const StructureEnum::Enum surfaceStructure = surfaceFile->getStructure(); BorderFile* nonConstBorderFile = const_cast(this); const int32_t numBorders = getNumberOfBorders(); for (int32_t borderIndex = 0; borderIndex < numBorders; borderIndex++) { Border* border = m_borders[borderIndex]; if (nonConstBorderFile->isBorderDisplayed(displayGroup, browserTabIndex, border) == false) { continue; } if (border->getStructure() == surfaceStructure) { // const int32_t numberOfPoints = border->getNumberOfPoints(); // for (int32_t iPoint = 0; iPoint < numberOfPoints; iPoint++) { // const SurfaceProjectedItem* spi = border->getPoint(iPoint); // CaretAssert(spi); // const SurfaceProjectionBarycentric* bary = spi->getBarycentricProjection(); // if (bary != NULL) { // const int32_t* pointNodes = bary->getTriangleNodes(); // const int32_t p1 = pointNodes[0]; // const int32_t p2 = pointNodes[1]; // const int32_t p3 = pointNodes[2]; // if ((p1 < surfaceNumberOfNodes) // && (p2 < surfaceNumberOfNodes) // && (p3 < surfaceNumberOfNodes)) { // if (nodesInROI[p1] // || nodesInROI[p2] // || nodesInROI[p3]) { // bordersOut.push_back(border); // break; // } // } // } // } /* * Get all node indices from the border * Note that the node indices may be used by more than one border point barycentric projection * so first get all UNIQUE node indices that are used by the border points */ std::set borderNodeIndicesInsideROI; const int32_t numberOfPoints = border->getNumberOfPoints(); for (int32_t iPoint = 0; iPoint < numberOfPoints; iPoint++) { const SurfaceProjectedItem* spi = border->getPoint(iPoint); CaretAssert(spi); const SurfaceProjectionBarycentric* bary = spi->getBarycentricProjection(); if (bary != NULL) { const int32_t* pointNodes = bary->getTriangleNodes(); borderNodeIndicesInsideROI.insert(pointNodes[0]); borderNodeIndicesInsideROI.insert(pointNodes[1]); borderNodeIndicesInsideROI.insert(pointNodes[2]); } } int32_t borderNodesInsideROICount = 0; for (std::set::iterator iter = borderNodeIndicesInsideROI.begin(); iter != borderNodeIndicesInsideROI.end(); iter++) { const int32_t nodeIndex = *iter; CaretAssertVectorIndex(nodesInROI, nodeIndex); if (nodesInROI[nodeIndex]) { borderNodesInsideROICount++; } } if (borderNodesInsideROICount > 0) { insideCountAndBorderOut.push_back(std::make_pair(borderNodesInsideROICount, border)); } } } } /** * Add a border. NOTE: This border file * takes ownership of the 'border' and * will handle deleting it. After calling * this method, the caller must never * do anything with the border that was passed * to this method. * * @param border * Border added to this border file. */ void BorderFile::addBorder(Border* border) { int numPoints = border->getNumberOfPoints(); if (numPoints == 0) { delete border;//keep our word and handle deleting the argument throw DataFileException(getFileName(), "attempt to add border with zero points"); }//NOTE: Border itself makes sure all points are on one structure, and have barycentric projections if (m_borders.empty())//TSC: i'm not actually sure if we want border files to automatically set their structure from the borders { m_structure = border->getStructure(); } else { if (m_structure != StructureEnum::ALL && m_structure != border->getPoint(0)->getStructure()) { m_structure = StructureEnum::ALL; } } if (m_numNodes != -1) { if (!border->verifyForSurfaceNumberOfNodes(m_numNodes)) { delete border; throw DataFileException(getFileName(), "attempt to add border that has too large vertex indices for surface"); } } m_borders.push_back(border); // DO NOT WANT to add entries to name and class tables when the names are // not an exact match as partial matches are acceptable. // const AString name = border->getName(); // if (name.isEmpty() == false) { // const int32_t nameColorKey = m_nameColorTable->getLabelKeyFromName(name); // if (nameColorKey < 0) { // m_nameColorTable->addLabel(name, 0.0f, 0.0f, 0.0f, 1.0f); // } // } // const AString className = border->getClassName(); // if (className.isEmpty() == false) { // const int32_t classColorKey = m_classColorTable->getLabelKeyFromName(className); // if (classColorKey < 0) { // m_classColorTable->addLabel(className, 0.0f, 0.0f, 0.0f, 1.0f); // } // } m_forceUpdateOfGroupAndNameHierarchy = true; setModified(); } /** * Remove the border at the given index. * @param indx * Index of border for removal. */ void BorderFile::removeBorder(const int32_t indx) { CaretAssertVectorIndex(m_borders, indx); Border* border = getBorder(indx); m_borders.erase(m_borders.begin() + indx); delete border; if (m_structure == StructureEnum::ALL) { int numBorders = (int)m_borders.size(); bool singleStructure = true; for (int i = 1; i < numBorders; ++i) { if (m_borders[i]->getStructure() != m_borders[0]->getStructure()) { singleStructure = false; break; } } if (singleStructure) { if (numBorders != 0)//have it remember its structure when the last border is removed { m_structure = m_borders[0]->getStructure(); } } } m_forceUpdateOfGroupAndNameHierarchy = true; setModified(); } /** * Remove the border. * @param border * Border that will be removed and DELETED. */ void BorderFile::removeBorder(Border* border) { const int32_t numBorders = getNumberOfBorders(); for (int32_t i = 0;i < numBorders; i++) { if (m_borders[i] == border) { removeBorder(i); return; } } CaretLogWarning("Attempting to delete border not in border file with name: " + border->getName()); } /** * Is the given border, that MUST be in this file, displayed? * @param displayGroup * Display group in which border is tested for display. * @param browserTabIndex * Tab index in which border is displayed. * @param border * Border that is tested to see if it is displayed. * @return * true if border is displayed, else false. */ bool BorderFile::isBorderDisplayed(const DisplayGroupEnum::Enum displayGroup, const int32_t browserTabIndex, const Border* border) { const GroupAndNameHierarchyItem* selectionItem = border->getGroupNameSelectionItem(); if (selectionItem != NULL) { if (selectionItem->isSelected(displayGroup, browserTabIndex) == false) { return false; } } return true; } /** * @return The class and name hierarchy. */ GroupAndNameHierarchyModel* BorderFile::getGroupAndNameHierarchyModel() { m_classNameHierarchy->update(this, m_forceUpdateOfGroupAndNameHierarchy); m_forceUpdateOfGroupAndNameHierarchy = false; return m_classNameHierarchy; } /** * @return The class color table. */ GiftiLabelTable* BorderFile::getClassColorTable() { return m_classColorTable; } /** * @return The class color table. */ const GiftiLabelTable* BorderFile::getClassColorTable() const { return m_classColorTable; } /** * @return The name color table. */ GiftiLabelTable* BorderFile::getNameColorTable() { return m_nameColorTable; } /** * @return The name color table. */ const GiftiLabelTable* BorderFile::getNameColorTable() const { return m_nameColorTable; } int BorderFile::getIndexForBorderMetadataKey(const AString& key) const { for (int i = 0; i < (int)m_borderMDKeys.size(); ++i)//for now, the dumb way, we don't expect hundreds of keys - can speed it up with a secondary map if needed { if (m_borderMDKeys[i] == key) return i;//NOTE: if we use a second structure to speed this up, make sure parseBorderMDNames3 also updates it! } return -1; } const AString& BorderFile::getBorderMetadataKey(const int& index) const { CaretAssert(index >= 0 && index < (int)m_borderMDKeys.size()); return m_borderMDKeys[index]; } int BorderFile::addBorderMetadataKey(const AString& key) { int checkKey = getIndexForBorderMetadataKey(key); if (checkKey != -1) return checkKey; m_borderMDKeys.push_back(key);//NOTE: if we use a second structure to speed this up, make sure parseBorderMDNames3 also updates it! for (map, vector >::iterator iter = m_borderMDValues.begin(); iter != m_borderMDValues.end(); ++iter) { iter->second.push_back(AString()); CaretAssert(iter->second.size() == m_borderMDKeys.size()); } setModified(); return m_borderMDKeys.size() - 1; } void BorderFile::removeBorderMetadataKey(const int& index) { CaretAssert(index >= 0 && index < (int)m_borderMDKeys.size()); m_borderMDKeys.erase(m_borderMDKeys.begin() + index); for (map, vector >::iterator iter = m_borderMDValues.begin(); iter != m_borderMDValues.end(); ++iter) { iter->second.erase(iter->second.begin() + index); CaretAssert(iter->second.size() == m_borderMDKeys.size()); } setModified(); } void BorderFile::clearBorderMetaData() { m_borderMDKeys.clear(); m_borderMDValues.clear(); } AString BorderFile::getBorderMetadataValue(const AString& name, const AString& className, const int& index) const { CaretAssert(index >= 0 && index < (int)m_borderMDKeys.size()); map, vector >::const_iterator iter = m_borderMDValues.find(make_pair(name, className)); if (iter == m_borderMDValues.end()) return AString(); CaretAssert(iter->second.size() == m_borderMDKeys.size()); return iter->second[index]; } void BorderFile::setBorderMetadataValue(const AString& name, const AString& className, const int& index, const AString& value) { CaretAssert(index >= 0 && index < (int)m_borderMDKeys.size()); //TODO: check if such a border actually exists? maybe not needed map, vector >::iterator iter = m_borderMDValues.find(make_pair(name, className)); if (iter == m_borderMDValues.end()) { if (value == "") return;//don't create the metadata values array if we only store the empty string iter = m_borderMDValues.insert(make_pair(make_pair(name, className), vector(m_borderMDKeys.size()))).first; } CaretAssert(iter->second.size() == m_borderMDKeys.size()); iter->second[index] = value; setModified(); } /** * Version 1 foci files contained one color table for both names * and classes. Newer versions of the foci file keep them in * separate tables. * * @param oldColorTable * Old color table that is split into name and class color tables. */ void BorderFile::createNameAndClassColorTables(const GiftiLabelTable* oldColorTable) { CaretAssert(oldColorTable); m_classColorTable->clear(); m_nameColorTable->clear(); std::set nameSet; std::set classSet; const int numBorders = getNumberOfBorders(); for (int32_t i = 0; i < numBorders; i++) { const Border* border = getBorder(i); nameSet.insert(border->getName()); classSet.insert(border->getClassName()); } /* * Create colors for only the "best matching" color. */ for (std::set::iterator iter = nameSet.begin(); iter != nameSet.end(); iter++) { const AString colorName = *iter; const GiftiLabel* oldLabel = oldColorTable->getLabelBestMatching(colorName); if (oldLabel != NULL) { const AString bestMatchingName = oldLabel->getName(); const int32_t labelKey = m_nameColorTable->getLabelKeyFromName(bestMatchingName); if (labelKey < 0) { float rgba[4] = { 0.0, 0.0, 0.0, 1.0 }; oldLabel->getColor(rgba); m_nameColorTable->addLabel(bestMatchingName, rgba[0], rgba[1], rgba[2], rgba[3]); } } } /* * Create a color for each class name using the best matching color */ for (std::set::iterator iter = classSet.begin(); iter != classSet.end(); iter++) { const AString colorName = *iter; const GiftiLabel* label = oldColorTable->getLabelBestMatching(colorName); float rgba[4] = { 0.0, 0.0, 0.0, 1.0 }; if (label != NULL) { label->getColor(rgba); } m_classColorTable->addLabel(colorName, rgba[0], rgba[1], rgba[2], rgba[3]); } } /** * @return A string list containing all border names * sorted in alphabetical order. */ QStringList BorderFile::getAllBorderNamesSorted() const { std::set nameSet; const int32_t numFoci = getNumberOfBorders(); for (int32_t i = 0;i < numFoci; i++) { nameSet.insert(m_borders[i]->getName()); } QStringList sl; for (std::set::iterator iter = nameSet.begin(); iter != nameSet.end(); iter++) { sl += *iter; } return sl; } /** * Invalidate all assigned colors. */ void BorderFile::invalidateAllAssignedColors() { const int32_t numBorders = getNumberOfBorders(); for (int32_t i = 0; i < numBorders; i++) { m_borders[i]->setClassRgbaInvalid(); m_borders[i]->setNameRgbaInvalid(); } m_forceUpdateOfGroupAndNameHierarchy = true; } /** * @return The version of the file as a number. */ int32_t BorderFile::getFileVersion() { return s_borderFileVersion; } /** * @return The version of the file as a string. */ AString BorderFile::getFileVersionAsString() { return AString::number(s_borderFileVersion); } /** * Read the data file. * * @param filename * Name of the data file. * @throws DataFileException * If the file was not successfully read. */ void BorderFile::readFile(const AString& filename) { clear(); checkFileReadability(filename); setFileName(filename); { QFile inFile(filename); if (!inFile.open(QIODevice::ReadOnly)) throw DataFileException(filename, "failed to open file for reading"); QXmlStreamReader myReader(&inFile); readXML(myReader); } /*BorderFileSaxReader saxReader(this); std::auto_ptr parser(XmlSaxParser::createXmlParser()); try { parser->parseFile(filename, &saxReader); } catch (const XmlSaxParserException& e) { clear(); setFileName(""); int lineNum = e.getLineNumber(); int colNum = e.getColumnNumber(); AString msg = "Parse Error while reading " + filename; if ((lineNum >= 0) && (colNum >= 0)) { msg += (" line/col (" + AString::number(e.getLineNumber()) + "/" + AString::number(e.getColumnNumber()) + ")"); } msg += (": " + e.whatString()); DataFileException dfe(msg); CaretLogThrowing(dfe); throw dfe; }//*/ setFileName(filename); m_classNameHierarchy->update(this, true); m_forceUpdateOfGroupAndNameHierarchy = false; m_classNameHierarchy->setAllSelected(true); CaretLogFiner("CLASS/NAME Table for : " + getFileNameNoPath() + "\n" + m_classNameHierarchy->toString()); clearModified(); } /** * Write the data file. * * @param filename * Name of the data file. * @throws DataFileException * If the file was not successfully written. */ void BorderFile::writeFile(const AString& filename) { if (canWriteAsVersion(3)) { writeFile(filename, 3); } else { CaretLogWarning("border file missing information required for writing as version 3, falling back to older format"); writeFile(filename, 1); } } /** * @return Error message for attempting a gui operation on an obsolete, multi-structure border file. */ AString BorderFile::getObsoleteMultiStructureFormatMessage() { return ("This border file (" + getFileNameNoPath() + ") contains borders for multiple structures and must be split into single-structure border files. " "This can be done in the gui, using a selection in the Data menu, " "or on the command line using -file-convert with the -border-version-convert option."); } void BorderFile::writeFile(const AString& filename, const int& version) { if ( ! isSingleStructure()) { throw DataFileException(filename, "Writing multi-structure border files is no longer supported. " "Any existing multi-structure border files should be split into single-structure border files. " "This can be done on the command line using -file-convert with the -border-version-convert option, " "or in the gui, using a selection in the Data menu."); } if (!canWriteAsVersion(version)) throw DataFileException(filename, "cannot write border file as version '" + AString::number(version) + "'"); checkFileWritability(filename); setFileName(filename); switch (version) { case 3: { QFile myFile(filename); if (!myFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) throw DataFileException(filename, "could not open for writing"); QXmlStreamWriter myXML(&myFile); myXML.setAutoFormatting(true); writeVersion3(myXML); break; } case 1: { // // Format the version string so that it ends with at most one zero // const AString versionString = AString::number(1.0); // // Open the file // FileAdapter file; AString errorMessage; QTextStream* textStream = file.openQTextStreamForWritingFile(getFileName(), errorMessage); if (textStream == NULL) { throw DataFileException(getFileName(), errorMessage); } // // Create the xml writer // XmlWriter xmlWriter(*textStream); // // Write header info // xmlWriter.writeStartDocument("1.0"); // // Write GIFTI root element // XmlAttributes attributes; //attributes.addAttribute("xmlns:xsi", // "http://www.w3.org/2001/XMLSchema-instance"); //attributes.addAttribute("xsi:noNamespaceSchemaLocation", // "http://brainvis.wustl.edu/caret6/xml_schemas/GIFTI_Caret.xsd"); attributes.addAttribute(BorderFile::XML_ATTRIBUTE_VERSION, versionString); xmlWriter.writeStartElement(BorderFile::XML_TAG_BORDER_FILE, attributes); // // Write Metadata // if (m_metadata != NULL) { m_metadata->writeAsXML(xmlWriter); } // // Write the class color table // xmlWriter.writeStartElement(XML_TAG_CLASS_COLOR_TABLE); m_classColorTable->writeAsXML(xmlWriter); xmlWriter.writeEndElement(); // // Write the name color table // xmlWriter.writeStartElement(XML_TAG_NAME_COLOR_TABLE); m_nameColorTable->writeAsXML(xmlWriter); xmlWriter.writeEndElement(); // // Write borders // const int32_t numBorders = getNumberOfBorders(); for (int32_t i = 0; i < numBorders; i++) { if (m_borders[i]->getNumberOfPoints() < 1) { CaretLogWarning("skipped writing zero-point border: '" + m_borders[i]->getName() + "'"); continue; } m_borders[i]->writeAsXML(xmlWriter); } xmlWriter.writeEndElement(); xmlWriter.writeEndDocument(); file.close(); break; } default: CaretAssertMessage(0, "unimplemented writer for claimed supported version"); break; } clearModified(); } bool BorderFile::canWriteAsVersion(const int& version) const { switch (version) { case 1: return true; case 3: if (!StructureEnum::isSingleStructure(m_structure)) return false; if (m_numNodes == -1) return false; return true; default: return false; } } void BorderFile::writeVersion3(QXmlStreamWriter& output) const { CaretAssert(canWriteAsVersion(3));//if this function is made public, this should also throw output.writeStartDocument(); output.writeStartElement("BorderFile"); output.writeAttribute("Version", "3"); output.writeAttribute("Structure", StructureEnum::toName(m_structure)); output.writeAttribute("SurfaceNumberOfVertices", AString::number(m_numNodes)); m_metadata->writeBorderFileXML3(output); int numBorderMDKeys = (int)m_borderMDKeys.size(); if (numBorderMDKeys > 0) { output.writeStartElement("BorderMetaDataNames"); for (int i = 0; i < numBorderMDKeys; ++i) { output.writeStartElement("Name"); output.writeCharacters(m_borderMDKeys[i]);//CDATA? output.writeEndElement(); } output.writeEndElement(); } int numBorders = getNumberOfBorders(); vector used(numBorders, false);//multi-part border behavior for (int i = 0; i < numBorders; ++i) { if (used[i]) continue; const Border* classBorder = getBorder(i); AString thisClass = classBorder->getClassName();//hierarchical representation in file output.writeStartElement("Class"); output.writeAttribute("Name", thisClass); writeColorHelper(output, getClassColorTable()->getLabelBestMatching(thisClass)); //writeColorHelper(output, getClassColorTable()->getLabel(thisClass)); for (int j = i; j < numBorders; ++j) { if (used[j]) continue; const Border* nameBorder = getBorder(j); if (nameBorder->getClassName() == thisClass) { AString thisName = nameBorder->getName();//multipart borders output.writeStartElement("Border"); output.writeAttribute("Name", thisName); writeColorHelper(output, getNameColorTable()->getLabelBestMatching(thisName)); //writeColorHelper(output, getNameColorTable()->getLabel(thisName)); map, vector >::const_iterator iter = m_borderMDValues.find(make_pair(thisName, thisClass)); if (iter != m_borderMDValues.end()) { CaretAssert(iter->second.size() == m_borderMDKeys.size()); output.writeStartElement("BorderMetaDataValues"); for (int k = 0; k < (int)m_borderMDKeys.size(); ++k) { output.writeStartElement("Value"); output.writeCharacters(iter->second[k]);//CDATA? output.writeEndElement(); } output.writeEndElement(); } for (int k = j; k < numBorders; ++k) { if (used[k]) continue; const Border* thisBorder = getBorder(k); if (thisBorder->getNumberOfPoints() < 1) { used[k] = true; CaretLogWarning("skipped writing zero-point border: '" + thisBorder->getName() + "'"); continue; } if (thisBorder->getName() == thisName && thisBorder->getClassName() == thisClass) { used[k] = true; thisBorder->writeXML3(output); } } output.writeEndElement();//Border } } output.writeEndElement();//Class } output.writeEndElement();//BorderFile } void BorderFile::writeColorHelper(QXmlStreamWriter& output, const GiftiLabel* colorLabel) const { if (colorLabel == NULL)//default to black if we somehow have no color { output.writeAttribute("Red", AString::number(0.0f)); output.writeAttribute("Green", AString::number(0.0f)); output.writeAttribute("Blue", AString::number(0.0f)); } else { output.writeAttribute("Red", AString::number(colorLabel->getRed())); output.writeAttribute("Green", AString::number(colorLabel->getGreen())); output.writeAttribute("Blue", AString::number(colorLabel->getBlue())); } } void BorderFile::readXML(QXmlStreamReader& xml) { clear(); bool haveRoot = false; while (!xml.atEnd()) { switch(xml.tokenType()) { case QXmlStreamReader::StartElement: { if (xml.name() != "BorderFile") throw DataFileException(getFileName(), "unexpected root element: " + xml.name().toString()); if (haveRoot) throw DataFileException(getFileName(), "multiple BorderFile elements in one file"); QXmlStreamAttributes myAttrs = xml.attributes(); if (!myAttrs.hasAttribute("Version")) throw DataFileException(getFileName(), "missing required attribute Version of element BorderFile"); QStringRef versionStr = myAttrs.value("Version"); if (versionStr == "1" || versionStr == "1.0") { parseBorderFile1(xml); } else if (versionStr == "3") { parseBorderFile3(xml); } else { throw DataFileException(getFileName(), "unrecognized border file version: " + versionStr.toString()); } haveRoot = true; break; } default: break; } xml.readNext(); } if (xml.hasError()) throw DataFileException(getFileName(), "XML parsing error in root of border file: " + xml.errorString()); if (!haveRoot) throw DataFileException(getFileName(), "BorderFile root element not found"); clearModified(); } void BorderFile::parseBorderFile1(QXmlStreamReader& xml) { CaretAssert(xml.isStartElement() && xml.name() == "BorderFile"); bool haveSingleTable = false, haveClassTable = false, haveNameTable = false; GiftiLabelTable singleTable; for (xml.readNext(); !xml.atEnd() && !xml.isEndElement(); xml.readNext()) { switch(xml.tokenType()) { case QXmlStreamReader::StartElement: { QStringRef name = xml.name(); if (name == "MetaData") { m_metadata->readBorderFileXML1(xml); if (xml.hasError()) throw DataFileException(getFileName(), "XML parsing error in MetaData: " + xml.errorString()); } else if (name == "BorderClassColorTable") { if (haveSingleTable) throw DataFileException(getFileName(), "file has both single-table and split-table coloring information"); if (haveClassTable) throw DataFileException(getFileName(), "file has multiple BorderClassColorTable elements"); if (!xml.readNextStartElement()) { if (xml.hasError()) throw DataFileException(getFileName(), "XML parsing error in BorderClassColorTable: " + xml.errorString()); throw DataFileException(getFileName(), "empty BorderClassColorTable found"); } m_classColorTable->readFromQXmlStreamReader(xml); xml.readNextStartElement();//find the end element of BorderClassColorTable if (xml.hasError()) throw DataFileException(getFileName(), "XML parsing error in BorderClassColorTable: " + xml.errorString()); haveClassTable = true; } else if (name == "BorderNameColorTable") { if (haveSingleTable) throw DataFileException(getFileName(), "file has both single-table and split-table coloring information"); if (haveNameTable) throw DataFileException(getFileName(), "file has multiple BorderNameColorTable elements"); if (!xml.readNextStartElement()) { if (xml.hasError()) throw DataFileException(getFileName(), "XML parsing error in BorderNameColorTable: " + xml.errorString()); throw DataFileException(getFileName(), "empty BorderNameColorTable found"); } m_nameColorTable->readFromQXmlStreamReader(xml); xml.readNextStartElement();//find the end element of BorderNameColorTable if (xml.hasError()) throw DataFileException(getFileName(), "XML parsing error in BorderNameColorTable: " + xml.errorString()); haveNameTable = true; } else if (name == "LabelTable") { if (haveNameTable || haveClassTable) throw DataFileException(getFileName(), "file has both single-table and split-table coloring information"); if (haveSingleTable) throw DataFileException(getFileName(), "file has multiple LabelTable elements"); singleTable.readFromQXmlStreamReader(xml); if (xml.hasError()) throw DataFileException(getFileName(), "XML parsing error in LabelTable: " + xml.errorString()); haveSingleTable = true; } else if (name == "Border") { CaretPointer toParse(new Border());//so throw can clean up, but we can also release the Border pointer toParse->readXML1(xml); if (toParse->getNumberOfPoints() > 0) { addBorder(toParse.releasePointer()); } else { CaretLogWarning("ignored border with zero points: '" + toParse->getName() + "'"); } } else { throw DataFileException(getFileName(), "unexpected element in BorderFile: " + name.toString()); } break; } default: break; } } if (haveSingleTable) { createNameAndClassColorTables(&singleTable); } if (xml.hasError()) throw DataFileException(getFileName(), "XML parsing error in BorderFile: " + xml.errorString()); CaretAssert(xml.isEndElement() && xml.name() == "BorderFile"); if (!haveSingleTable && (!haveClassTable || !haveNameTable)) { throw DataFileException(getFileName(), "border file is missing a required color table"); } } void BorderFile::parseBorderFile3(QXmlStreamReader& xml) { CaretAssert(xml.isStartElement() && xml.name() == "BorderFile"); QXmlStreamAttributes myAttrs = xml.attributes(); bool ok = false; if (!myAttrs.hasAttribute("Structure")) throw DataFileException(getFileName(), "BorderFile is missing required attribute Structure"); StructureEnum::Enum myStructure = StructureEnum::fromName(myAttrs.value("Structure").toString(), &ok); if (!ok) throw DataFileException(getFileName(), "unrecognized structure: " + myAttrs.value("Structure").toString()); setStructure(myStructure); if (!myAttrs.hasAttribute("SurfaceNumberOfVertices")) throw DataFileException(getFileName(), "BorderFile is missing required attribute SurfaceNumberOfVertices"); int myNumNodes = myAttrs.value("SurfaceNumberOfVertices").toString().toInt(&ok); if (!ok) throw DataFileException(getFileName(), "non-integer number of vertices: " + myAttrs.value("SurfaceNumberOfVertices").toString()); if (myNumNodes < 1) throw DataFileException(getFileName(), "number of vertices too small: "); setNumberOfNodes(myNumNodes); bool haveFileMD = false, haveBorderMDNames = false; set classNames; for (xml.readNext(); !xml.atEnd() && !xml.isEndElement(); xml.readNext()) { if (xml.isStartElement()) { QStringRef name = xml.name(); if (name == "MetaData") { if (haveFileMD) throw DataFileException(getFileName(), "file has multiple MetaData elements"); m_metadata->readBorderFileXML3(xml); if (xml.hasError()) throw DataFileException(getFileName(), "XML parsing error in MetaData: " + xml.errorString()); haveFileMD = true; } else if (name == "BorderMetaDataNames") { if (haveBorderMDNames) throw DataFileException(getFileName(), "file has multiple BorderMetaDataNames elements"); parseBorderMDNames3(xml); haveBorderMDNames = true; } else if (name == "Class") { AString className = parseClass3(xml); if (!classNames.insert(className).second) throw DataFileException(getFileName(), "multiple classes using same name: " + className); } else { throw DataFileException(getFileName(), "unexpected element in BorderFile: " + name.toString()); } } } if (xml.hasError()) throw DataFileException(getFileName(), "XML parsing error in BorderFile: " + xml.errorString()); CaretAssert(xml.isEndElement() && xml.name() == "BorderFile"); for (map, vector >::const_iterator iter = m_borderMDValues.begin(); iter != m_borderMDValues.end(); ++iter) {//someone could put the BorderMetaDataNames after a class, so check at the very end if (iter->second.size() != m_borderMDKeys.size()) { throw DataFileException(getFileName(), "wrong number of border metadata values for border " + iter->first.first + ", class " + iter->first.second); } } } void BorderFile::parseBorderMDNames3(QXmlStreamReader& xml) { CaretAssert(xml.isStartElement() && xml.name() == "BorderMetaDataNames"); for (xml.readNext(); !xml.atEnd() && !xml.isEndElement(); xml.readNext()) { if (xml.isStartElement()) { QStringRef name = xml.name(); if (name == "Name") { QString mdName = xml.readElementText();//errors on unexpected element if (xml.hasError()) throw DataFileException(getFileName(), "XML parsing error in Name: " + xml.errorString()); int checkIndex = getIndexForBorderMetadataKey(mdName); if (checkIndex != -1) throw DataFileException(getFileName(), "duplicate border metadata name: " + mdName); m_borderMDKeys.push_back(mdName);//NOTE: do NOT use addBorderMetadataKey, as if there are borders with metadata before this, it will mess things up } else { throw DataFileException(getFileName(), "unexpected element in BorderMetaDataNames: " + name.toString()); } } } if (xml.hasError()) throw DataFileException(getFileName(), "XML parsing error in BorderMetaDataNames: " + xml.errorString()); CaretAssert(xml.isEndElement() && xml.name() == "BorderMetaDataNames"); } AString BorderFile::parseClass3(QXmlStreamReader& xml) { CaretAssert(xml.isStartElement() && xml.name() == "Class"); QXmlStreamAttributes myAttrs = xml.attributes(); if (!myAttrs.hasAttribute("Name")) throw DataFileException(getFileName(), "Class is missing required attribute Name"); AString className = myAttrs.value("Name").toString(); float colorRGB[3]; colorAttribHelper3(getFileName(), xml, colorRGB); m_classColorTable->addLabel(className, colorRGB[0], colorRGB[1], colorRGB[2]); set borderNames; for (xml.readNext(); !xml.atEnd() && !xml.isEndElement(); xml.readNext()) { if (xml.isStartElement()) { QStringRef name = xml.name(); if (name == "Border") { AString borderName = parseBorder3(xml, className); if (!borderNames.insert(borderName).second) throw DataFileException(getFileName(), "multiple borders in one class using same name: " + borderName); } else { throw DataFileException(getFileName(), "unexpected element in Class: " + name.toString()); } } } if (xml.hasError()) throw DataFileException(getFileName(), "XML parsing error in Class: " + xml.errorString()); CaretAssert(xml.isEndElement() && xml.name() == "Class"); if (borderNames.size() == 0) throw DataFileException(getFileName(), "Class " + className + " has no Border elements"); return className; } AString BorderFile::parseBorder3(QXmlStreamReader& xml, const AString& className) { CaretAssert(xml.isStartElement() && xml.name() == "Border"); bool haveMDValues = false; int numBorderParts = 0; QXmlStreamAttributes myAttrs = xml.attributes(); if (!myAttrs.hasAttribute("Name")) throw DataFileException(getFileName(), "Class is missing required attribute Name"); AString borderName = myAttrs.value("Name").toString(); float colorRGB[3]; colorAttribHelper3(getFileName(), xml, colorRGB); m_nameColorTable->addLabel(borderName, colorRGB[0], colorRGB[1], colorRGB[2]); for (xml.readNext(); !xml.atEnd() && !xml.isEndElement(); xml.readNext()) { if (xml.isStartElement()) { QStringRef name = xml.name(); if (name == "BorderPart") { CaretPointer thisBorder(new Border());//again, because the current interface/internals take ownership of raw pointers thisBorder->readXML3(xml); thisBorder->setStructure(getStructure()); thisBorder->setClassName(className); thisBorder->setName(borderName); if (!thisBorder->verifyForSurfaceNumberOfNodes(getNumberOfNodes())) throw DataFileException(getFileName(), "BorderPart uses node numbers larger than are valid for its surface"); if (thisBorder->getNumberOfPoints() > 0) { addBorder(thisBorder.releasePointer()); } else { CaretLogWarning("ignored border with zero points: '" + thisBorder->getName() + "'"); } ++numBorderParts; } else if (name == "BorderMetaDataValues") { if (haveMDValues) throw DataFileException(getFileName(), "Border has multiple BorderMetaDataValues elements"); m_borderMDValues[make_pair(borderName, className)] = parseBorderMDValues3(getFileName(), xml); haveMDValues = true; } else { throw DataFileException(getFileName(), "unexpected element in Border: " + name.toString()); } } } if (xml.hasError()) throw DataFileException(getFileName(), "XML parsing error in Border: " + xml.errorString()); CaretAssert(xml.isEndElement() && xml.name() == "Border"); if (numBorderParts == 0) throw DataFileException(getFileName(), "Border has no BorderPart elements"); return borderName; } void BorderFile::colorAttribHelper3(const AString& filename, QXmlStreamReader& xml, float rgbOut[3]) { QXmlStreamAttributes myAttrs = xml.attributes(); bool ok = false; if (!myAttrs.hasAttribute("Red")) throw DataFileException(filename, xml.name().toString() + " element missing required attribute Red"); rgbOut[0] = myAttrs.value("Red").toString().toFloat(&ok); if (!ok) throw DataFileException(filename, "non-numeric Red attribute of " + xml.name().toString() + ": " + myAttrs.value("Red").toString()); if (!myAttrs.hasAttribute("Green")) throw DataFileException(filename, xml.name().toString() + " element missing required attribute Green"); rgbOut[1] = myAttrs.value("Green").toString().toFloat(&ok); if (!ok) throw DataFileException(filename, "non-numeric Green attribute of " + xml.name().toString() + ": " + myAttrs.value("Green").toString()); if (!myAttrs.hasAttribute("Blue")) throw DataFileException(filename, xml.name().toString() + " element missing required attribute Blue"); rgbOut[2] = myAttrs.value("Blue").toString().toFloat(&ok); if (!ok) throw DataFileException(filename, "non-numeric Blue attribute of " + xml.name().toString() + ": " + myAttrs.value("Blue").toString()); } vector BorderFile::parseBorderMDValues3(const AString& filename, QXmlStreamReader& xml) { CaretAssert(xml.isStartElement() && xml.name() == "BorderMetaDataValues"); vector ret; for (xml.readNext(); !xml.atEnd() && !xml.isEndElement(); xml.readNext()) { if (xml.isStartElement()) { QStringRef name = xml.name(); if (name == "Value") { ret.push_back(xml.readElementText());//errors on unexpected element if (xml.hasError()) throw DataFileException(filename, "XML parsing error in BorderMetaDataValues: " + xml.errorString()); } else { throw DataFileException(filename, "unexpected element in BorderMetaDataValues: " + name.toString()); } } } if (xml.hasError()) throw DataFileException(filename, "XML parsing error in BorderMetaDataValues: " + xml.errorString()); CaretAssert(xml.isEndElement() && xml.name() == "BorderMetaDataValues"); return ret; } /** * @return Is this border file modified? */ bool BorderFile::isModified() const { if (CaretDataFile::isModified()) { return true; } if (m_metadata->isModified()) { return true; } if (m_classColorTable->isModified()) { return true; } if (m_nameColorTable->isModified()) { return true; } /* * Note, these members do not affect modification status: * classNameHierarchy */ const int32_t numBorders = getNumberOfBorders(); for (int32_t i = 0; i < numBorders; i++) { if (m_borders[i]->isModified()) { return true; } } return false; } /** * Clear the modification status of this border file. */ void BorderFile::clearModified() { CaretDataFile::clearModified(); m_metadata->clearModified(); m_classColorTable->clearModified(); m_nameColorTable->clearModified(); const int32_t numBorders = getNumberOfBorders(); for (int32_t i = 0; i < numBorders; i++) { m_borders[i]->clearModified(); } } /** * Add information about the file to the data file information. * * @param dataFileInformation * Consolidates information about a data file. */ void BorderFile::addToDataFileContentInformation(DataFileContentInformation& dataFileInformation) { CaretDataFile::addToDataFileContentInformation(dataFileInformation); const std::vector allStructures = getAllBorderStructures(); if (allStructures.size() >= 2) { AString structuresText; for (std::vector::const_iterator iter = allStructures.begin(); iter != allStructures.end(); iter++) { structuresText += (StructureEnum::toGuiName(*iter) + " "); } dataFileInformation.addNameAndValue("Border Structures", structuresText); } int nameSize = 4, classSize = 0;//reserve space for headings, but classSize is only to know how much to reserve() BorderMultiPartHelper myHelp(this); for (int i = 0; i < (int)myHelp.borderPieceList.size(); ++i) { CaretAssert(myHelp.borderPieceList[i].size() > 0); const Border* thisPart = getBorder(myHelp.borderPieceList[i][0]); nameSize = max(nameSize, thisPart->getName().length()); classSize = max(classSize, thisPart->getClassName().length()); } nameSize += 3;//minimum number spaces between fields int numberSize = max(8, AString::number(myHelp.borderPieceList.size()).length() + 3);//spacing for border index AString header = AString("INDEX").leftJustified(numberSize) + AString("NAME").leftJustified(nameSize) + "CLASS"; dataFileInformation.addText(header); for (int i = 0; i < (int)myHelp.borderPieceList.size(); ++i) { CaretAssert(myHelp.borderPieceList[i].size() > 0); const Border* thisPart = getBorder(myHelp.borderPieceList[i][0]); AString line; line.reserve(numberSize + nameSize + classSize + 1); line = "\n" + AString::number(i + 1).leftJustified(numberSize); line += thisPart->getName().leftJustified(nameSize); line += thisPart->getClassName(); dataFileInformation.addText(line); } } /** * Export this border file to Caret5 formatted border color and * border projection files. * * @param surfaceFiles * Surface files for unprojection of borders. * @param outputCaret5FilesPrefix * Prefix for Caret5 output files. */ void BorderFile::exportToCaret5Format(const std::vector& surfaceFiles, const AString& outputCaret5FilesPrefix) { AString errorMessage; if (getNumberOfBorders() <= 0) { errorMessage.appendWithNewLine("This border file contains zero borders."); } if (outputCaret5FilesPrefix.isEmpty()) { errorMessage.appendWithNewLine("Caret5 output file prefix is empty."); } if ( ! errorMessage.isEmpty()) { throw DataFileException(getFileName(), errorMessage); } /* * In Caret7, each border contains a Structure attribute and a Caret7 * border file may contain borders from more than one structure. However, * Caret5 borders do not contain a structure attribute and so each * Caret5 border file contains borders for one structure only. * * So, group borders by structure. * */ std::map > bordersPerStructuresMap; for (std::vector::iterator borderIter = m_borders.begin(); borderIter != m_borders.end(); borderIter++) { Border* border = *borderIter; if (border->getNumberOfPoints() > 0) { const StructureEnum::Enum structure = border->getStructure(); std::map >::iterator iter = bordersPerStructuresMap.find(structure); if (iter != bordersPerStructuresMap.end()) { iter->second.push_back(border); } else { std::vector borderVector; borderVector.push_back(border); bordersPerStructuresMap.insert(std::pair >(structure, borderVector)); } } } int32_t filesWrittenCount = 0; /* * Surface are needed for unprojecting to create border files. * This will track missing surface structure types. */ std::set missingSurfaceStructures; /* * Place borders for each structure in separate files. */ for (std::map >::iterator iter = bordersPerStructuresMap.begin(); iter != bordersPerStructuresMap.end(); iter++) { const StructureEnum::Enum structure = iter->first; const std::vector& borderVector = iter->second; const int32_t numberOfBorders = static_cast(borderVector.size()); AString structureName = "unknown"; if (structure == StructureEnum::CEREBELLUM) { structureName = "cerebellum"; } else if (StructureEnum::isLeft(structure)) { structureName = "left"; } else if (StructureEnum::isRight(structure)) { structureName = "right"; } AString headerText; headerText.appendWithNewLine("BeginHeader"); headerText.appendWithNewLine("comment exported from wb_view file " + getFileNameNoPath()); headerText.appendWithNewLine("encoding ASCII"); headerText.appendWithNewLine("structure " + structureName); headerText.appendWithNewLine("EndHeader"); if (numberOfBorders > 0) { bool allBorderProjectionsValid = true; AString borderProjFileText; borderProjFileText.appendWithNewLine(headerText); borderProjFileText.appendWithNewLine(AString::number(numberOfBorders)); bool allBordersValid = true; AString borderFileText; borderFileText.appendWithNewLine(headerText); borderFileText.appendWithNewLine(AString::number(numberOfBorders)); for (int32_t iBorder = 0; iBorder < numberOfBorders; iBorder++) { const Border* border = borderVector[iBorder]; const int32_t numPoints = border->getNumberOfPoints(); const AString name = border->getName(); /* * Border Projection * Write index, number of points, name, * sampling density/variance/topography/uncertainty * * Center (0, 0, 0) */ borderProjFileText.appendWithNewLine(AString::number(iBorder) + " " + AString::number(numPoints) + " " + name + " 20.0 1.0 0.0 1.0"); borderProjFileText.appendWithNewLine("0.0 0.0 0.0"); /* * Border * Write index, number of points, name, * sampling density/variance/topography/uncertainty * * Center (0, 0, 0) */ borderFileText.appendWithNewLine(AString::number(iBorder) + " " + AString::number(numPoints) + " " + name + " 20.0 1.0 0.0 1.0"); borderFileText.appendWithNewLine("0.0 0.0 0.0"); for (int32_t jPoint = 0; jPoint < numPoints; jPoint++) { const SurfaceProjectedItem* spi = border->getPoint(jPoint); const SurfaceProjectionBarycentric* baryProj = spi->getBarycentricProjection(); if (baryProj->isValid()) { const float* triangleAreas = baryProj->getTriangleAreas(); const int32_t* triangleNodes = baryProj->getTriangleNodes(); /* * Add points nodes, section, areas, and radius */ borderProjFileText.appendWithNewLine(AString::number(triangleNodes[0]) + " " + AString::number(triangleNodes[1]) + " " + AString::number(triangleNodes[2]) + " 0 " + AString::number(triangleAreas[0], 'f', 6) + " " + AString::number(triangleAreas[1], 'f', 6) + " " + AString::number(triangleAreas[2], 'f', 6) + " 0.0"); } else { allBorderProjectionsValid = false; } if ( ! surfaceFiles.empty()) { SurfaceFile* surface = NULL; for (std::vector::const_iterator surfaceIter = surfaceFiles.begin(); surfaceIter != surfaceFiles.end(); surfaceIter++) { SurfaceFile* sf = *surfaceIter; if (sf->getStructure() == structure) { surface = sf; break; } } if (surface != NULL) { float xyz[3]; if (spi->getProjectedPosition(*surface, xyz, false)) { /* * Add point index, section, xyz, and radius */ borderFileText.appendWithNewLine(AString::number(jPoint) + " 0 " + AString::number(xyz[0], 'f', 3) + " " + AString::number(xyz[1], 'f', 3) + " " + AString::number(xyz[2], 'f', 3) + " 0.0"); } else { allBordersValid = false; } } else { missingSurfaceStructures.insert(structure); allBordersValid = false; } } else { missingSurfaceStructures.insert(structure); allBordersValid = false; } } } if (allBorderProjectionsValid) { try { const AString filename = (outputCaret5FilesPrefix + "_" + StructureEnum::toName(structure) + ".borderproj"); TextFile borderProjectionFile; borderProjectionFile.addText(borderProjFileText); borderProjectionFile.writeFile(filename); filesWrittenCount++; } catch (const DataFileException& dfe) { errorMessage.appendWithNewLine(dfe.whatString()); } } else { errorMessage.appendWithNewLine("There were failures creating at least one border projection for structure: " + StructureEnum::toName(structure)); } if (allBordersValid) { try { const AString filename = (outputCaret5FilesPrefix + "_" + StructureEnum::toName(structure) + ".border"); TextFile borderFile; borderFile.addText(borderFileText); borderFile.writeFile(filename); filesWrittenCount++; } catch (const DataFileException& dfe) { errorMessage.appendWithNewLine(dfe.whatString()); } } else { errorMessage.appendWithNewLine("There were failures creating at least one border for structure: " + StructureEnum::toName(structure)); } } } for (std::set::iterator missingStructureIter = missingSurfaceStructures.begin(); missingStructureIter != missingSurfaceStructures.end(); missingStructureIter++) { errorMessage.appendWithNewLine("No surface was available for structure: " + StructureEnum::toName(*missingStructureIter)); } if (filesWrittenCount > 0) { try { const AString filename = (outputCaret5FilesPrefix + ".bordercolor"); GiftiLabelTable* colorTable = getNameColorTable(); colorTable->exportToCaret5ColorFile(filename); } catch (const GiftiException& ge) { errorMessage.appendWithNewLine(ge.whatString()); } } if ( ! errorMessage.isEmpty()) { throw DataFileException(getFileName(), errorMessage); } } BorderMultiPartHelper::BorderMultiPartHelper(const BorderFile* bf) { int numBorderParts = bf->getNumberOfBorders(); for (int i = 0; i < numBorderParts; ++i) { const Border* thisPart = bf->getBorder(i); map, int>::const_iterator iter = stringLookup.find(make_pair(thisPart->getName(), thisPart->getClassName())); if (iter == stringLookup.end()) { stringLookup.insert(make_pair(make_pair(thisPart->getName(), thisPart->getClassName()), (int)borderPieceList.size())); borderPieceList.push_back(vector(1, i)); } else { borderPieceList[iter->second].push_back(i); } } } int BorderMultiPartHelper::fromNumberOrName(const AString& ident) const { bool ok = false; int whichBorder = ident.toInt(&ok) - 1;//first border is "1" if (ok) { if (whichBorder < 0 || whichBorder >= (int)borderPieceList.size()) return -1; return whichBorder; } else {//only search for name if the string isn't a number, to prevent surprises for (std::map, int>::const_iterator iter = stringLookup.begin(); iter != stringLookup.end(); ++iter) { if (iter->first.first == ident) { return iter->second; } } return -1; } } workbench-1.1.1/src/Files/BorderFile.h000066400000000000000000000251351255417355300175330ustar00rootroot00000000000000#ifndef __BORDER_FILE__H_ #define __BORDER_FILE__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretDataFile.h" #include "CaretPointer.h" #include "DisplayGroupEnum.h" #include #include class QXmlStreamReader; class QXmlStreamWriter; namespace caret { class Border; class BorderPointFromSearch; class GroupAndNameHierarchyModel; class GiftiLabel; class GiftiLabelTable; class SurfaceFile; class SurfaceProjectedItem; class BorderFile : public CaretDataFile { public: BorderFile(); virtual ~BorderFile(); BorderFile(const BorderFile& obj); BorderFile& operator=(const BorderFile& obj); virtual void addToDataFileContentInformation(DataFileContentInformation& dataFileInformation); StructureEnum::Enum getStructure() const; void setStructure(const StructureEnum::Enum structure); std::vector getAllBorderStructures() const; bool splitIntoSingleStructureFiles(const std::map& singleStructureFileNames, const std::map& structureNumberOfNodes, std::vector& singleStructureBorderFilesOut, AString& errorMessageOut) const; GiftiMetaData* getFileMetaData(); const GiftiMetaData* getFileMetaData() const; void readFile(const AString& filename); void writeFile(const AString& filename); void writeFile(const AString& filename, const int& version); void clear(); bool isEmpty() const; int32_t getNumberOfNodes() const; void setNumberOfNodes(const int32_t& numNodes); void updateNumberOfNodesIfSingleStructure(const std::map& structureToNodeCount); int32_t getNumberOfBorders() const; Border* getBorder(const int32_t indx); const Border* getBorder(const int32_t indx) const; bool containsBorder(const Border* border) const; void findAllBordersWithEndPointNearSegmentFirstPoint(const DisplayGroupEnum::Enum displayGroup, const int32_t browserTabIndex, const SurfaceFile* surfaceFile, const Border* borderSegment, const float maximumDistance, std::vector& borderPointsOut) const; void findAllBordersWithAnyPointNearSegmentFirstPoint(const DisplayGroupEnum::Enum displayGroup, const int32_t browserTabIndex, const SurfaceFile* surfaceFile, const Border* borderSegment, const float maximumDistance, std::vector& borderPointsOut) const; void findAllBordersWithPointsNearBothSegmentEndPoints(const DisplayGroupEnum::Enum displayGroup, const int32_t browserTabIndex, const SurfaceFile* surfaceFile, const Border* borderSegment, const float maximumDistance, std::vector& borderPointsOut) const; void findBordersInsideRegionOfInterest(const DisplayGroupEnum::Enum displayGroup, const int32_t browserTabIndex, const SurfaceFile* surfaceFile, const std::vector& nodesInROI, std::vector >& insideCountAndBorderOut) const; void addBorder(Border* border); void removeBorder(const int32_t indx); void removeBorder(Border* border); bool isBorderDisplayed(const DisplayGroupEnum::Enum displayGroup, const int32_t browserTabIndex, const Border* border); GiftiLabelTable* getClassColorTable(); const GiftiLabelTable* getClassColorTable() const; GiftiLabelTable* getNameColorTable(); const GiftiLabelTable* getNameColorTable() const; int getNumberOfBorderMetadataKeys() const { return (int)m_borderMDKeys.size(); } int getIndexForBorderMetadataKey(const AString& key) const; const AString& getBorderMetadataKey(const int& index) const; //only adds if it doesn't already exist, and returns index int addBorderMetadataKey(const AString& key); void removeBorderMetadataKey(const int& index); void clearBorderMetaData(); AString getBorderMetadataValue(const AString& name, const AString& className, const int& index) const; void setBorderMetadataValue(const AString& name, const AString& className, const int& index, const AString& value); void createNameAndClassColorTables(const GiftiLabelTable* oldColorTable); GroupAndNameHierarchyModel* getGroupAndNameHierarchyModel(); const GroupAndNameHierarchyModel* getGroupAndNameHierarchyModel() const; QStringList getAllBorderNamesSorted() const; static int32_t getFileVersion(); static AString getFileVersionAsString(); void exportToCaret5Format(const std::vector& surfaceFiles, const AString& outputCaret5FilesPrefix); AString getObsoleteMultiStructureFormatMessage(); /** XML Tag for BorderFile element */ static const AString XML_TAG_BORDER_FILE; /** XML Tag for Version attribute */ static const AString XML_ATTRIBUTE_VERSION; /** XML Tag for Name Color Table */ static const AString XML_TAG_NAME_COLOR_TABLE; /** XML Tag for Class Color Table */ static const AString XML_TAG_CLASS_COLOR_TABLE; virtual bool isModified() const; virtual void clearModified(); void invalidateAllAssignedColors(); private: void copyHelperBorderFile(const BorderFile& obj); void initializeBorderFile(); bool canWriteAsVersion(const int& version) const; void writeVersion3(QXmlStreamWriter& output) const; void writeColorHelper(QXmlStreamWriter& output, const GiftiLabel* colorLabel) const; void readXML(QXmlStreamReader& xml); void parseBorderFile1(QXmlStreamReader& xml);//there is no version 2, because the SAX parser pretended to support version 2 when it didn't exist void parseBorderFile3(QXmlStreamReader& xml);//so, to make the new format give reasonable error messages in old releases, make the new format version 3 void parseBorderMDNames3(QXmlStreamReader& xml); AString parseClass3(QXmlStreamReader& xml); AString parseBorder3(QXmlStreamReader& xml, const AString& className); static void colorAttribHelper3(const AString& filename, QXmlStreamReader& xml, float rgbOut[3]); static std::vector parseBorderMDValues3(const AString& filename, QXmlStreamReader& xml); GiftiMetaData* m_metadata; std::vector m_borders; /** Holds colors assigned to classes */ GiftiLabelTable* m_classColorTable; /** Holds colors assigned to names */ GiftiLabelTable* m_nameColorTable; /** Holds class and name hierarchy used for display selection */ mutable GroupAndNameHierarchyModel* m_classNameHierarchy; /** force an update of the class and name hierarchy */ bool m_forceUpdateOfGroupAndNameHierarchy; StructureEnum::Enum m_structure; int32_t m_numNodes; std::vector m_borderMDKeys; std::map, std::vector > m_borderMDValues;//because each "Border" is really just a part of a border /** Version of this BorderFile */ static const int32_t s_borderFileVersion; }; struct BorderMultiPartHelper { std::map, int> stringLookup; std::vector > borderPieceList; BorderMultiPartHelper(const BorderFile* bf); int fromNumberOrName(const AString& ident) const; }; #ifdef __BORDER_FILE_DECLARE__ const AString BorderFile::XML_TAG_BORDER_FILE = "BorderFile"; const AString BorderFile::XML_ATTRIBUTE_VERSION = "Version"; const AString BorderFile::XML_TAG_NAME_COLOR_TABLE = "BorderNameColorTable"; const AString BorderFile::XML_TAG_CLASS_COLOR_TABLE = "BorderClassColorTable"; const int32_t BorderFile::s_borderFileVersion = 2; #endif // __BORDER_FILE_DECLARE__ } // namespace #endif //__BORDER_FILE__H_ workbench-1.1.1/src/Files/BorderPointFromSearch.h000066400000000000000000000151401255417355300217120ustar00rootroot00000000000000#ifndef __BORDER_POINT_FROM_SEARCH_H__ #define __BORDER_POINT_FROM_SEARCH_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretAssert.h" #include "CaretObject.h" /** * \class caret::BorderPointFromSearch * \brief Contains result of searching for a border point. * \ingroup Files */ namespace caret { class BorderPointFromSearch : public CaretObject { public: /** * Constructor. */ BorderPointFromSearch() { reset(); } /** * Copy constructor. * * @param rhs * Instance copied to this instance. */ BorderPointFromSearch(const BorderPointFromSearch& rhs) : CaretObject(rhs) { this->copyHelperBorderPointFromSearch(rhs); } /** * Destructor. */ virtual ~BorderPointFromSearch() { } /** * Copy constructor. * * @param rhs * Instance copied to this instance. * @return * Reference to this instance. */ BorderPointFromSearch& operator=(const BorderPointFromSearch& rhs) { if (this != &rhs) { CaretObject::operator=(rhs); this->copyHelperBorderPointFromSearch(rhs); } return *this; } /** * Comparison operator using distance. * * @param rhs * Compared to rhs * @param * True if 'this' is less than 'rhs'. */ bool operator<(const BorderPointFromSearch& rhs) const { if (isValid() && rhs.isValid()) { return (m_distance < rhs.m_distance); } else if (isValid()) { return true; } return false; } /* * Reset the data to invalid. */ void reset() { m_borderFile = NULL; m_border = NULL; m_borderIndex = -1; m_borderPointIndex = -1; m_distance = -1.0; } /** * Replace this instance with the given instance if the given * instance's distance is less than this instance's distance * or if this instance is invalid. * * @param rhs * The other instance. */ void replaceWithNearerDistance(const BorderPointFromSearch& rhs) { if ( ! rhs.isValid()) { return; } bool replaceMeFlag = false; if (isValid()) { if (rhs.m_distance < m_distance) { replaceMeFlag = true; } } else { replaceMeFlag = true; } if (replaceMeFlag) { copyHelperBorderPointFromSearch(rhs); } } /** * Set the data. * * @param borderFile * The border file. * @param border * The border * @param borderIndex * Index of border in the border file * @param borderPointIndex * Index of point in the border * @param distance * Distance of border point from the search point. */ void setData(BorderFile* borderFile, Border* border, const int32_t borderIndex, const int32_t borderPointIndex, const float distance) { m_borderFile = borderFile; m_border = border; m_borderIndex = borderIndex; m_borderPointIndex = borderPointIndex; m_distance = distance; CaretAssert(m_borderFile); CaretAssert(m_border); CaretAssert(m_borderIndex >= 0); CaretAssert(m_borderPointIndex >= 0); CaretAssert(m_distance >= 0.0); } /** @return Is this item valid */ inline bool isValid() const { return ((m_borderFile != NULL) && (m_border != NULL) && (m_borderIndex >= 0) && (m_borderPointIndex >= 0) && (m_distance >= 0.0)); } /** @return Border file containing the point */ inline BorderFile* borderFile() { return m_borderFile; } /** @return The border */ inline Border* border() { return m_border; } /** @return Index of the border in the border file */ inline int32_t borderIndex() { return m_borderIndex; } /** @return Index of the border point */ inline int32_t borderPointIndex() { return m_borderPointIndex; } /** @return Distance to the border point from the search point */ inline float distance() { return m_distance; } private: /** * Helps with copying an object of this type. * @param rhs * Object that is copied. */ void copyHelperBorderPointFromSearch(const BorderPointFromSearch& rhs) { m_borderFile = rhs.m_borderFile; m_border = rhs.m_border; m_borderIndex = rhs.m_borderIndex; m_borderPointIndex = rhs.m_borderPointIndex; m_distance = rhs.m_distance; } BorderFile* m_borderFile; Border* m_border; int32_t m_borderIndex; int32_t m_borderPointIndex; float m_distance; }; #ifdef __BORDER_POINT_FROM_SEARCH_DECLARE__ // #endif // __BORDER_POINT_FROM_SEARCH_DECLARE__ } // namespace #endif //__BORDER_POINT_FROM_SEARCH_H__ workbench-1.1.1/src/Files/BorderTracingHelper.cxx000066400000000000000000000132541255417355300217550ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "BorderTracingHelper.h" #include "Border.h" #include "BorderFile.h" #include "SurfaceProjectedItem.h" #include "SurfaceProjectionBarycentric.h" #include "SurfaceFile.h" #include "TopologyHelper.h" using namespace caret; using namespace std; BorderTracingHelper::BorderTracingHelper(const SurfaceFile* surfIn) { m_numNodes = surfIn->getNumberOfNodes(); m_structure = surfIn->getStructure(); m_topoHelp = surfIn->getTopologyHelper();//doesn't need sorted, because we may need to walk in "reverse", so don't rely on sorting } vector > BorderTracingHelper::tracePrivate(vector& marked, const float& placement) { vector > ret; const vector& myEdgeInfo = m_topoHelp->getEdgeInfo(); const vector& myTileInfo = m_topoHelp->getTileInfo(); vector edgeUsed(myEdgeInfo.size(), 0); float nodeWeights[3] = { 1.0f - placement, placement, 0.0f }; while (true) { bool foundStart = false, closed = true; int curInNode = -1, curEdge = -1, curOutNode = -1; for (int i = 0; i < m_numNodes; ++i) { if (marked[i] != 0) { const vector& edges = m_topoHelp->getNodeEdges(i); int numEdges = (int)edges.size(); for (int j = 0; j < numEdges; ++j) { const TopologyEdgeInfo& thisEdge = myEdgeInfo[edges[j]]; int testNode = (thisEdge.node2 == i ? thisEdge.node1 : thisEdge.node2); if (marked[testNode] == 0 && edgeUsed[edges[j]] == 0) { if (!foundStart || thisEdge.numTiles == 1) { foundStart = true; curInNode = i; curEdge = edges[j]; curOutNode = testNode; if (thisEdge.numTiles == 1) { closed = false; break;//if we found the end of an open border, stop searching } } } } } } if (!foundStart) break; CaretPointer newBorder(new Border());//in case something throws newBorder->setClosed(closed); int startInNode = curInNode, startOutNode = curOutNode; int prevThirdNode = -1; do { edgeUsed[curEdge] = 1;//don't start another border from this edge const TopologyEdgeInfo& myEdge = myEdgeInfo[curEdge];//to remove some redundant indexing CaretPointer newPoint(new SurfaceProjectedItem());//ditto newPoint->setStructure(m_structure); newPoint->getBarycentricProjection()->setProjectionSurfaceNumberOfNodes(m_numNodes); int32_t triNodes[3] = { curInNode, curOutNode, myEdge.tiles[0].node3 };//always use the first tile, as it will always exist newPoint->getBarycentricProjection()->setTriangleNodes(triNodes); newPoint->getBarycentricProjection()->setTriangleAreas(nodeWeights); newPoint->getBarycentricProjection()->setValid(true); newBorder->addPoint(newPoint.releasePointer());//NOTE: addPoint takes ownership of a RAW POINTER - shared_ptr won't release a pointer int useTile = 0; if (myEdge.tiles[0].node3 == prevThirdNode) { if (myEdge.numTiles == 1) break; useTile = 1; } int nextNode = myEdge.tiles[useTile].node3; int edgeMove = 0; if (marked[nextNode] == 0) { edgeMove = (myEdge.tiles[useTile].edgeReversed == (myEdge.node1 == curInNode) ? 1 : 2);//some magic to find the next edge via the lookups prevThirdNode = curOutNode; curOutNode = nextNode; } else { edgeMove = (myEdge.tiles[useTile].edgeReversed == (myEdge.node1 == curInNode) ? 2 : 1);//rather than searching the edges on the tile or such prevThirdNode = curInNode; curInNode = nextNode; } int edgeIndex = (myEdge.tiles[useTile].whichEdge + edgeMove) % 3;//modify the edge index within the tile by the magic offset curEdge = myTileInfo[myEdge.tiles[useTile].tile].edges[edgeIndex].edge;//and pull out the global index of the edge CaretAssert(myEdgeInfo[curEdge].node1 == curInNode || myEdgeInfo[curEdge].node1 == curOutNode);//assert to make sure the magic worked CaretAssert(myEdgeInfo[curEdge].node2 == curInNode || myEdgeInfo[curEdge].node2 == curOutNode); } while (curInNode != startInNode || curOutNode != startOutNode); ret.push_back(newBorder); } return ret; } workbench-1.1.1/src/Files/BorderTracingHelper.h000066400000000000000000000072461255417355300214060ustar00rootroot00000000000000#ifndef __BORDER_TRACING_HELPER_H__ #define __BORDER_TRACING_HELPER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretAssert.h" #include "CaretPointer.h" #include "StructureEnum.h" #include "TopologyHelper.h" #include namespace caret { class Border; class SurfaceFile; class BorderTracingHelper { int m_numNodes; StructureEnum::Enum m_structure; CaretPointer m_topoHelp; BorderTracingHelper();//no default BorderTracingHelper(const BorderTracingHelper&);//no copy BorderTracingHelper& operator=(const BorderTracingHelper&);//no assign std::vector > tracePrivate(std::vector& marked, const float& placement); public: BorderTracingHelper(const SurfaceFile* surfIn); template std::vector > traceData(T* data, const Test& myTester, const float& placement = 0.33f); //some useful selection objects class LabelSelect { int32_t m_select; LabelSelect(); public: LabelSelect(const int32_t& value) : m_select(value) { } bool operator() (const int32_t& toTest) const { return toTest == m_select; } }; template class LessThan { T m_threshold; bool m_inclusive; LessThan(); public: LessThan(const T& value, const bool& inclusive) : m_threshold(value), m_inclusive(inclusive) { } bool operator() (const T& toTest) const { if (m_inclusive) { return toTest <= m_threshold; } else { return toTest < m_threshold; } } }; template class GreaterThan { T m_threshold; bool m_inclusive; GreaterThan(); public: GreaterThan(const T& value, const bool& inclusive) : m_threshold(value), m_inclusive(inclusive) { } bool operator() (const T& toTest) const { if (m_inclusive) { return toTest >= m_threshold; } else { return toTest > m_threshold; } } }; }; template std::vector > BorderTracingHelper::traceData(T* data, const Test& myTester, const float& placement) { CaretAssert(placement >= 0.0f && placement <= 1.0f); std::vector marked(m_numNodes); for (int i = 0; i < m_numNodes; ++i) { marked[i] = (myTester(data[i]) ? 1 : 0); } return tracePrivate(marked, placement); } }//namespace #endif //__BORDER_TRACING_HELPER_H__ workbench-1.1.1/src/Files/BrainordinateRegionOfInterest.cxx000066400000000000000000000625041255417355300240220ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __BRAINORDINATE_REGION_OF_INTEREST_DECLARE__ #include "BrainordinateRegionOfInterest.h" #undef __BRAINORDINATE_REGION_OF_INTEREST_DECLARE__ #include "BoundingBox.h" #include "CaretAssert.h" #include "CiftiBrainordinateLabelFile.h" #include "CiftiMappableDataFile.h" #include "CiftiParcelsMap.h" #include "EventSurfaceStructuresValidGet.h" #include "EventManager.h" #include "GiftiLabelTable.h" #include "LabelFile.h" #include "SceneClass.h" #include "SceneClassAssistant.h" using namespace caret; /** * \class caret::BrainordinateRegionOfInterest * \brief Contains a region of interest * \ingroup Files * * Contains a region of interest that may include nodes from multiple * surfaces and voxels within a volume space. */ /** * Constructor. */ BrainordinateRegionOfInterest::BrainordinateRegionOfInterest() : CaretObject() { clear(); m_sceneAssistant = new SceneClassAssistant(); } /** * Destructor. */ BrainordinateRegionOfInterest::~BrainordinateRegionOfInterest() { clear(); delete m_sceneAssistant; } /** * Copy constructor. * @param obj * Object that is copied. */ BrainordinateRegionOfInterest::BrainordinateRegionOfInterest(const BrainordinateRegionOfInterest& obj) : CaretObject(obj), SceneableInterface(obj) { this->copyHelperBrainordinateRegionOfInterest(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ BrainordinateRegionOfInterest& BrainordinateRegionOfInterest::operator=(const BrainordinateRegionOfInterest& obj) { if (this != &obj) { CaretObject::operator=(obj); this->copyHelperBrainordinateRegionOfInterest(obj); } return *this; } /** * Helps with copying an object of this type. * @param obj * Object that is copied. */ void BrainordinateRegionOfInterest::copyHelperBrainordinateRegionOfInterest(const BrainordinateRegionOfInterest& obj) { clear(); m_highlighingEnabled = obj.m_highlighingEnabled; for (std::vector::const_iterator iter = obj.m_surfaceNodesInfo.begin(); iter != obj.m_surfaceNodesInfo.end(); iter++) { const SurfaceNodesInfo* sni = *iter; m_surfaceNodesInfo.push_back(new SurfaceNodesInfo(*sni)); } m_voxelSize[0] = obj.m_voxelSize[0]; m_voxelSize[1] = obj.m_voxelSize[1]; m_voxelSize[2] = obj.m_voxelSize[2]; /* * Voxel sizes must be non-negative */ CaretAssert(m_voxelSize[0] >= 0.0); CaretAssert(m_voxelSize[1] >= 0.0); CaretAssert(m_voxelSize[2] >= 0.0); m_voxelXYZ = obj.m_voxelXYZ; } /** * Clear this region of interest. */ void BrainordinateRegionOfInterest::clear() { for (std::vector::iterator iter = m_surfaceNodesInfo.begin(); iter != m_surfaceNodesInfo.end(); iter++) { SurfaceNodesInfo* sni = *iter; CaretAssert(sni); delete sni; } m_surfaceNodesInfo.clear(); m_voxelSize[0] = 0.0; m_voxelSize[1] = 0.0; m_voxelSize[2] = 0.0; m_voxelXYZ.clear(); m_highlighingEnabled = false; } /** * @return True if this region of interest contains a surface with * valid nodes, else false. */ bool BrainordinateRegionOfInterest::hasSurfaceNodes() const { for (std::vector::const_iterator iter = m_surfaceNodesInfo.begin(); iter != m_surfaceNodesInfo.end(); iter++) { const SurfaceNodesInfo* sni = *iter; CaretAssert(sni); if ( ! sni->m_surfaceNodeIndices.empty()) { return true; } } return false; } /** * Does this region of interest contains surface nodes for the surface * with the given structure and number of nodes? * * @param structure * The surface's structure. * @param surfaceNumberOfNodes * Number of nodes in surface. * @return * True if found, else false. */ bool BrainordinateRegionOfInterest::hasNodesForSurfaceStructure(const StructureEnum::Enum structure, const int64_t surfaceNumberOfNodes) const { for (std::vector::const_iterator iter = m_surfaceNodesInfo.begin(); iter != m_surfaceNodesInfo.end(); iter++) { const SurfaceNodesInfo* sni = *iter; CaretAssert(sni); if ( ! sni->m_surfaceNodeIndices.empty()) { if ((sni->m_structure == structure) && (sni->m_surfaceNumberOfNodes == surfaceNumberOfNodes)) { return true; } } } return false; } /** * Get the nodes in the region of interest for the surface * with the given structure and number of nodes? * * @param structure * The surface's structure. * @param surfaceNumberOfNodes * Number of nodes in surface. * @return * Vector containing node indices. Empty if surface node found. */ const std::vector& BrainordinateRegionOfInterest::getNodesForSurfaceStructure(const StructureEnum::Enum structure, const int64_t surfaceNumberOfNodes) const { for (std::vector::const_iterator iter = m_surfaceNodesInfo.begin(); iter != m_surfaceNodesInfo.end(); iter++) { const SurfaceNodesInfo* sni = *iter; CaretAssert(sni); if ( ! sni->m_surfaceNodeIndices.empty()) { if ((sni->m_structure == structure) && (sni->m_surfaceNumberOfNodes == surfaceNumberOfNodes)) { return sni->m_surfaceNodeIndices; } } } return s_emptySurfaceNodes; } /** * @return True if the region of interest contains volume voxels. */ bool BrainordinateRegionOfInterest::hasVolumeVoxels() const { if ( ! m_voxelXYZ.empty()) { return true; } return false; } /** * Get the volume voxel size. * * @param voxelSizeOut * Size of the volume voxels. */ void BrainordinateRegionOfInterest::getVolumeVoxelSize(float voxelSizeOut[3]) const { /* * Voxel sizes must be non-negative */ CaretAssert(m_voxelSize[0] >= 0.0); CaretAssert(m_voxelSize[1] >= 0.0); CaretAssert(m_voxelSize[2] >= 0.0); voxelSizeOut[0] = m_voxelSize[0]; voxelSizeOut[1] = m_voxelSize[1]; voxelSizeOut[2] = m_voxelSize[2]; } /** * Get the volume voxels in the region-of-interest. * * @return * XYZ coordinates of the voxels. (3 per voxel ==> XYZ) * Empty if no voxels ni region of interest. */ const std::vector& BrainordinateRegionOfInterest::getVolumeVoxelsXYZ() const { return m_voxelXYZ; } /** * Set the nodes in the region of interest for the surface * with the given structure and number of nodes. If there is an * ROI for the struture and number of nodes, it is replaced. * * @param structure * The surface's structure. * @param surfaceNumberOfNodes * Number of nodes in surface. * @param surfaceNodeIndices * The surface node indices. */ void BrainordinateRegionOfInterest::setSurfaceNodes(const StructureEnum::Enum structure, const int64_t surfaceNumberOfNodes, const std::vector& surfaceNodeIndices) { int64_t replaceAtIndex = -1; const int64_t numItems = static_cast(m_surfaceNodesInfo.size()); for (int64_t i = 0; i < numItems; i++) { const SurfaceNodesInfo* sni = m_surfaceNodesInfo[i]; CaretAssert(sni); if ((sni->m_structure == structure) && (sni->m_surfaceNumberOfNodes == surfaceNumberOfNodes)) { delete sni; m_surfaceNodesInfo[i] = NULL; replaceAtIndex = i; break; } } SurfaceNodesInfo* sni = new SurfaceNodesInfo(structure, surfaceNumberOfNodes, surfaceNodeIndices); if (replaceAtIndex >= 0) { CaretAssertVectorIndex(m_surfaceNodesInfo, replaceAtIndex); CaretAssert(m_surfaceNodesInfo[replaceAtIndex] = NULL); m_surfaceNodesInfo[replaceAtIndex] = sni; } else { m_surfaceNodesInfo.push_back(sni); } } /** * Set the volume voxels in the region of interest. Replaces all * voxels in the region of interest. * * @param voxelSize * Size of the voxels. * @param voxelsXYZ * Coordinates of the voxels (3 per voxel ==> XYZ) */ void BrainordinateRegionOfInterest::setVolumeVoxels(const float voxelSize[3], const std::vector& voxelsXYZ) { m_voxelSize[0] = std::fabs(voxelSize[0]); m_voxelSize[1] = std::fabs(voxelSize[1]); m_voxelSize[2] = std::fabs(voxelSize[2]); m_voxelXYZ = voxelsXYZ; /* * Voxel sizes must be non-negative */ CaretAssert(m_voxelSize[0] >= 0.0); CaretAssert(m_voxelSize[1] >= 0.0); CaretAssert(m_voxelSize[2] >= 0.0); } /** * @return Is brainordinate highlighting enabled * */ bool BrainordinateRegionOfInterest::isBrainordinateHighlightingEnabled() const { return m_highlighingEnabled; } /** * Set brainordinate highlighting. * * @param enabled * New status for highlighting. */ void BrainordinateRegionOfInterest::setBrainordinateHighlightingEnabled(const bool enabled) { m_highlighingEnabled = enabled; } /** * Set the region of interest to the brainordinates in the given named label * from the labels used for loading data in the given CIFTI file. * * @param ciftiMappableDataFile * The CIFTI file. * @param mapIndex * Index of the map. * @param labelName * Name of the parcel. * @param errorMessageOut * Will contain error message upon exit. * @return * True if successful. If failure, false is returned and errorMessageOut * will contain cause of failure. */ bool BrainordinateRegionOfInterest::setWithLabelFileLabel(const CaretMappableDataFile* caretMappableDataFile, const int32_t mapIndex, const AString& labelName, AString& errorMessageOut) { clear(); errorMessageOut.clear(); if (caretMappableDataFile == NULL) { errorMessageOut = "File is not valid (NULL)"; return false; } if ( ! caretMappableDataFile->isMappedWithLabelTable()) { errorMessageOut = "File is not mapped with a label table."; return false; } if ((mapIndex < 0) || (mapIndex >= caretMappableDataFile->getNumberOfMaps())) { errorMessageOut = ("Map index=" + AString::number(mapIndex) + " is invalid for file " + caretMappableDataFile->getFileNameNoPath()); return false; } if (labelName.isEmpty()) { errorMessageOut = "Label name is empty."; return false; } const GiftiLabelTable* labelTable = caretMappableDataFile->getMapLabelTable(mapIndex); CaretAssert(labelTable); if (labelTable != NULL) { const int64_t labelKey = labelTable->getLabelKeyFromName(labelName); if (labelKey < 0) { errorMessageOut = ("Label " + labelName + " not found in label table."); return false; } const LabelFile* labelFile = dynamic_cast(caretMappableDataFile); const CiftiBrainordinateLabelFile* ciftiLabelFile = dynamic_cast(caretMappableDataFile); if (labelFile != NULL) { const StructureEnum::Enum structure = labelFile->getStructure(); int64_t surfaceNumberOfNodes = labelFile->getNumberOfNodes(); std::vector nodeIndices; labelFile->getNodeIndicesWithLabelKey(mapIndex, labelKey, nodeIndices); if (nodeIndices.empty()) { errorMessageOut = ("No vertices found for label " + labelName); return false; } std::vector nodeIndices64(nodeIndices.begin(), nodeIndices.end()); setSurfaceNodes(structure, surfaceNumberOfNodes, nodeIndices64); return true; } else if (ciftiLabelFile != NULL) { bool haveBrainordinatesFlag = false; EventSurfaceStructuresValidGet structureNumberOfNodesEvent; EventManager::get()->sendEvent(structureNumberOfNodesEvent.getPointer()); std::map structNodes = structureNumberOfNodesEvent.getStructuresAndNumberOfNodes(); for (std::map::iterator structNodeIter = structNodes.begin(); structNodeIter != structNodes.end(); structNodeIter++) { const StructureEnum::Enum structure = structNodeIter->first; const int32_t surfaceNumberOfNodes = structNodeIter->second; std::vector nodeIndices; ciftiLabelFile->getNodeIndicesWithLabelKey(structure, surfaceNumberOfNodes, mapIndex, labelKey, nodeIndices); if ( ! nodeIndices.empty()) { std::vector nodeIndices64(nodeIndices.begin(), nodeIndices.end()); setSurfaceNodes(structure, surfaceNumberOfNodes, nodeIndices64); haveBrainordinatesFlag = true; } } std::vector voxelsXYZ; ciftiLabelFile->getVoxelCoordinatesWithLabelKey(mapIndex, labelKey, voxelsXYZ); if ( ! voxelsXYZ.empty()) { m_voxelXYZ.insert(m_voxelXYZ.end(), voxelsXYZ.begin(), voxelsXYZ.end()); ciftiLabelFile->getVoxelSpacing(m_voxelSize[0], m_voxelSize[1], m_voxelSize[2]); m_voxelSize[0] = std::fabs(m_voxelSize[0]); m_voxelSize[1] = std::fabs(m_voxelSize[1]); m_voxelSize[2] = std::fabs(m_voxelSize[2]); haveBrainordinatesFlag = true; } if (haveBrainordinatesFlag) { return true; } errorMessageOut = ("No brainordinates found for label " + labelName); return false; } } errorMessageOut = (caretMappableDataFile->getFileNameNoPath() + " is not a label type file or not recognized as label file (programming error)."); return false; } /** * Set the region of interest to the brainordinates in the given named parcel * from the parcels used for loading data in the given CIFTI file. * * @param ciftiMappableDataFile * The CIFTI file. * @param mapIndex * Index of the map. * @param parcelName * Name of the parcel. * @param errorMessageOut * Will contain error message upon exit. * @return * True if successful. If failure, false is returned and errorMessageOut * will contain cause of failure. */ bool BrainordinateRegionOfInterest::setWithCiftiParcelLoadingBrainordinates(const CiftiMappableDataFile* ciftiMappableDataFile, const int32_t mapIndex, const AString& parcelName, AString& errorMessageOut) { clear(); errorMessageOut.clear(); if (ciftiMappableDataFile == NULL) { errorMessageOut = "File is not valid (NULL)"; return false; } if ((mapIndex < 0) || (mapIndex >= ciftiMappableDataFile->getNumberOfMaps())) { errorMessageOut = ("Map index=" + AString::number(mapIndex) + " is invalid for file " + ciftiMappableDataFile->getFileNameNoPath()); return false; } const CiftiParcelsMap* ciftiParcelsMap = ciftiMappableDataFile->getCiftiParcelsMapForLoading(); return setWithCiftiParcelBrainordinates(ciftiMappableDataFile, ciftiParcelsMap, parcelName, errorMessageOut); } /** * Set the region of interest to the brainordinates in the given named parcel * from the parcels used for mapping data in the given CIFTI file. * * @param ciftiMappableDataFile * The CIFTI file. * @param mapIndex * Index of the map. * @param parcelName * Name of the parcel. * @param errorMessageOut * Will contain error message upon exit. * @return * True if successful. If failure, false is returned and errorMessageOut * will contain cause of failure. */ bool BrainordinateRegionOfInterest::setWithCiftiParcelMappingBrainordinates(const CiftiMappableDataFile* ciftiMappableDataFile, const int32_t mapIndex, const AString& parcelName, AString& errorMessageOut) { clear(); errorMessageOut.clear(); if (ciftiMappableDataFile == NULL) { errorMessageOut = "File is not valid (NULL)"; return false; } if ((mapIndex < 0) || (mapIndex >= ciftiMappableDataFile->getNumberOfMaps())) { errorMessageOut = ("Map index=" + AString::number(mapIndex) + " is invalid for file " + ciftiMappableDataFile->getFileNameNoPath()); return false; } const CiftiParcelsMap* ciftiParcelsMap = ciftiMappableDataFile->getCiftiParcelsMapForBrainordinateMapping(); return setWithCiftiParcelBrainordinates(ciftiMappableDataFile, ciftiParcelsMap, parcelName, errorMessageOut); } /** * Set the region of interest to the brainordinates in the given named parcel * from the given CIFTI parcels map in the given CIFTI file. * * @param ciftiMappableDataFile * The CIFTI file. * @param ciftiParcelsMap * The CIFTI Parcels Map. * @param parcelName * Name of the parcel. * @param errorMessageOut * Will contain error message upon exit. * @return * True if successful. If failure, false is returned and errorMessageOut * will contain cause of failure. */ bool BrainordinateRegionOfInterest::setWithCiftiParcelBrainordinates(const CiftiMappableDataFile* ciftiMappableDataFile, const CiftiParcelsMap* ciftiParcelsMap, const AString& parcelName, AString& errorMessageOut) { if (parcelName.isEmpty()) { errorMessageOut = "Parcel name is empty."; return false; } if (ciftiParcelsMap == NULL) { errorMessageOut = ("No parcels map in " + ciftiMappableDataFile->getFileNameNoPath()); return false; } const int64_t parcelIndex = ciftiParcelsMap->getIndexFromNumberOrName(parcelName); if (parcelIndex < 0) { errorMessageOut = ("Parcel name=" + parcelName + " not found in parcels map for file " + ciftiMappableDataFile->getFileNameNoPath()); return false; } const std::vector& allParcels = ciftiParcelsMap->getParcels(); CaretAssertVectorIndex(allParcels, parcelIndex); const CiftiParcelsMap::Parcel& parcel = allParcels[parcelIndex]; for (std::map >::const_iterator iter = parcel.m_surfaceNodes.begin(); iter != parcel.m_surfaceNodes.end(); iter++) { const StructureEnum::Enum structure = iter->first; const std::set& nodeSet = iter->second; int64_t surfaceNumberOfNodes = ciftiParcelsMap->getSurfaceNumberOfNodes(structure); std::vector nodeVector(nodeSet.begin(), nodeSet.end()); setSurfaceNodes(structure, surfaceNumberOfNodes, nodeVector); } if (ciftiParcelsMap->hasVolumeData()) { const VolumeSpace& volumeSpace = ciftiParcelsMap->getVolumeSpace(); const std::set& voxelSetIJK = parcel.m_voxelIndices; for (std::set::iterator voxelIter = voxelSetIJK.begin(); voxelIter != voxelSetIJK.end(); voxelIter++) { const VoxelIJK& voxelIJK = *voxelIter; float xyz[3]; volumeSpace.indexToSpace(voxelIJK.m_ijk, xyz); m_voxelXYZ.push_back(xyz[0]); m_voxelXYZ.push_back(xyz[1]); m_voxelXYZ.push_back(xyz[2]); } if ( ! m_voxelXYZ.empty()) { VolumeSpace::OrientTypes orientation[3]; float origin[3]; volumeSpace.getOrientAndSpacingForPlumb(orientation, m_voxelSize, origin); m_voxelSize[0] = std::fabs(m_voxelSize[0]); m_voxelSize[1] = std::fabs(m_voxelSize[1]); m_voxelSize[2] = std::fabs(m_voxelSize[2]); /* * Voxel sizes must be non-negative */ CaretAssert(m_voxelSize[0] >= 0.0); CaretAssert(m_voxelSize[1] >= 0.0); CaretAssert(m_voxelSize[2] >= 0.0); } } return true; } /** * Get a description of this object's content. * @return String describing this object's content. */ AString BrainordinateRegionOfInterest::toString() const { return "BrainordinateRegionOfInterest"; } /** * Save information specific to this type of model to the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @param instanceName * Name of instance in the scene. */ SceneClass* BrainordinateRegionOfInterest::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "BrainordinateRegionOfInterest", 1); m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); // Uncomment if sub-classes must save to scene //saveSubClassDataToScene(sceneAttributes, // sceneClass); return sceneClass; } /** * Restore information specific to the type of model from the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass from which model specific information is obtained. */ void BrainordinateRegionOfInterest::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); //Uncomment if sub-classes must restore from scene //restoreSubClassDataFromScene(sceneAttributes, // sceneClass); } workbench-1.1.1/src/Files/BrainordinateRegionOfInterest.h000066400000000000000000000145031255417355300234430ustar00rootroot00000000000000#ifndef __BRAINORDINATE_REGION_OF_INTEREST_H__ #define __BRAINORDINATE_REGION_OF_INTEREST_H__ /*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "SceneableInterface.h" #include "StructureEnum.h" namespace caret { class CaretMappableDataFile; class CiftiMappableDataFile; class CiftiParcelsMap; class SceneClassAssistant; class BrainordinateRegionOfInterest : public CaretObject, public SceneableInterface { public: BrainordinateRegionOfInterest(); virtual ~BrainordinateRegionOfInterest(); BrainordinateRegionOfInterest(const BrainordinateRegionOfInterest& obj); BrainordinateRegionOfInterest& operator=(const BrainordinateRegionOfInterest& obj); void clear(); bool hasSurfaceNodes() const; bool hasNodesForSurfaceStructure(const StructureEnum::Enum structure, const int64_t surfaceNumberOfNodes) const; const std::vector& getNodesForSurfaceStructure(const StructureEnum::Enum structure, const int64_t surfaceNumberOfNodes) const; bool hasVolumeVoxels() const; void getVolumeVoxelSize(float voxelSizeOut[3]) const; const std::vector& getVolumeVoxelsXYZ() const; void setSurfaceNodes(const StructureEnum::Enum structure, const int64_t surfaceNumberOfNodes, const std::vector& surfaceNodeIndices); void setVolumeVoxels(const float voxelSize[3], const std::vector& voxelsXYZ); bool isBrainordinateHighlightingEnabled() const; void setBrainordinateHighlightingEnabled(const bool highlighting); bool setWithLabelFileLabel(const CaretMappableDataFile* caretMappableDataFile, const int32_t mapIndex, const AString& labelName, AString& errorMessageOut); bool setWithCiftiParcelLoadingBrainordinates(const CiftiMappableDataFile* ciftiMappableDataFile, const int32_t mapIndex, const AString& parcelName, AString& errorMessageOut); bool setWithCiftiParcelMappingBrainordinates(const CiftiMappableDataFile* ciftiMappableDataFile, const int32_t mapIndex, const AString& parcelName, AString& errorMessageOut); // ADD_NEW_METHODS_HERE virtual AString toString() const; virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); // If there will be sub-classes of this class that need to save // and restore data from scenes, these pure virtual methods can // be uncommented to force their implemetation by sub-classes. // protected: // virtual void saveSubClassDataToScene(const SceneAttributes* sceneAttributes, // SceneClass* sceneClass) = 0; // // virtual void restoreSubClassDataFromScene(const SceneAttributes* sceneAttributes, // const SceneClass* sceneClass) = 0; private: void copyHelperBrainordinateRegionOfInterest(const BrainordinateRegionOfInterest& obj); bool setWithCiftiParcelBrainordinates(const CiftiMappableDataFile* ciftiMappableDataFile, const CiftiParcelsMap* ciftiParcelsMap, const AString& parcelName, AString& errorMessageOut); SceneClassAssistant* m_sceneAssistant; class SurfaceNodesInfo { public: SurfaceNodesInfo(const StructureEnum::Enum structure, const int64_t surfaceNumberOfNodes, const std::vector& surfaceNodeIndices) : m_structure(structure), m_surfaceNumberOfNodes(surfaceNumberOfNodes), m_surfaceNodeIndices(surfaceNodeIndices) { /* nothing */ } ~SurfaceNodesInfo() { } const StructureEnum::Enum m_structure; const int64_t m_surfaceNumberOfNodes; const std::vector m_surfaceNodeIndices; }; std::vector m_surfaceNodesInfo; float m_voxelSize[3]; std::vector m_voxelXYZ; static std::vector s_emptySurfaceNodes; bool m_highlighingEnabled; // ADD_NEW_MEMBERS_HERE }; #ifdef __BRAINORDINATE_REGION_OF_INTEREST_DECLARE__ std::vector BrainordinateRegionOfInterest::s_emptySurfaceNodes; #endif // __BRAINORDINATE_REGION_OF_INTEREST_DECLARE__ } // namespace #endif //__BRAINORDINATE_REGION_OF_INTEREST_H__ workbench-1.1.1/src/Files/CMakeLists.txt000077500000000000000000000136331255417355300201100ustar00rootroot00000000000000# # Name of project # PROJECT (Files) # # Need XML from Qt # SET(QT_DONT_USE_QTGUI) SET(QT_USE_QTNETWORK TRUE) # # Add QT for includes # INCLUDE (${QT_USE_FILE}) # # Files Library # ADD_LIBRARY(Files AffineFile.h Border.h BorderException.h BorderFile.h BorderPointFromSearch.h BorderTracingHelper.h BrainordinateRegionOfInterest.h CaretDataFile.h CaretDataFileHelper.h CaretMappableDataFile.h CaretSparseFile.h CaretVolumeExtension.h ChartableLineSeriesBrainordinateInterface.h ChartableLineSeriesInterface.h ChartableLineSeriesRowColumnInterface.h ChartableMatrixInterface.h ChartableMatrixParcelInterface.h ChartableMatrixSeriesInterface.h CiftiBrainordinateDataSeriesFile.h CiftiBrainordinateLabelFile.h CiftiBrainordinateScalarFile.h CiftiConnectivityMatrixDenseFile.h CiftiConnectivityMatrixDenseParcelFile.h CiftiConnectivityMatrixParcelFile.h CiftiConnectivityMatrixParcelDenseFile.h CiftiFiberOrientationFile.h CiftiFiberTrajectoryFile.h CiftiMappableDataFile.h CiftiMappableConnectivityMatrixDataFile.h CiftiParcelColoringModeEnum.h CiftiParcelLabelFile.h CiftiParcelReordering.h CiftiParcelReorderingModel.h CiftiParcelSeriesFile.h CiftiParcelScalarFile.h CiftiScalarDataSeriesFile.h ConnectivityDataLoaded.h EventCaretMappableDataFilesGet.h EventChartMatrixParcelYokingValidation.h EventGetDisplayedDataFiles.h EventMapYokingSelectMap.h EventMapYokingValidation.h EventPaletteGetByName.h EventSurfaceColoringInvalidate.h EventSurfaceStructuresValidGet.h Fiber.h FiberOrientation.h FiberOrientationColoringTypeEnum.h FiberOrientationTrajectory.h FiberTrajectoryColorModel.h FiberTrajectoryMapProperties.h FiberTrajectoryDisplayModeEnum.h FilePathNamePrefixCompactor.h FociFile.h FociFileSaxReader.h Focus.h GeodesicHelper.h GiftiTypeFile.h GroupAndNameCheckStateEnum.h GroupAndNameHierarchyGroup.h GroupAndNameHierarchyItem.h GroupAndNameHierarchyModel.h GroupAndNameHierarchyName.h ImageDimensionsModel.h ImageFile.h ImagePixelsPerSpatialUnitsEnum.h ImageSpatialUnitsEnum.h LabelDrawingProperties.h LabelDrawingTypeEnum.h LabelFile.h MapYokingGroupEnum.h MetricFile.h MetricSmoothingObject.h NodeAndVoxelColoring.h OxfordSparseThreeFile.h PaletteFile.h RgbaFile.h RibbonMappingHelper.h SceneFile.h SceneFileSaxReader.h SignedDistanceHelper.h SparseVolumeIndexer.h SpecFile.h SpecFileDataFileTypeGroup.h SpecFileDataFile.h SpecFileSaxReader.h StudyMetaDataLink.h StudyMetaDataLinkSet.h StudyMetaDataLinkSetSaxReader.h SurfaceFile.h SurfaceProjectedItem.h SurfaceProjectedItemSaxReader.h SurfaceProjection.h SurfaceProjectionBarycentric.h SurfaceProjectionVanEssen.h SurfaceProjector.h SurfaceProjectorException.h SurfaceResamplingHelper.h SurfaceResamplingMethodEnum.h SurfaceTypeEnum.h TextFile.h TopologyHelper.h VolumeEditingModeEnum.h VolumeFile.h VolumeFileEditorDelegate.h VolumeFileVoxelColorizer.h VolumeMapUndoCommand.h VolumePaddingHelper.h VolumeSliceProjectionTypeEnum.h VolumeSpline.h VtkFileExporter.h WarpfieldFile.h AffineFile.cxx Border.cxx BorderException.cxx BorderFile.cxx BorderTracingHelper.cxx BrainordinateRegionOfInterest.cxx CaretDataFile.cxx CaretDataFileHelper.cxx CaretMappableDataFile.cxx CaretSparseFile.cxx CaretVolumeExtension.cxx ChartableLineSeriesInterface.cxx ChartableMatrixInterface.cxx CiftiBrainordinateDataSeriesFile.cxx CiftiBrainordinateLabelFile.cxx CiftiBrainordinateScalarFile.cxx CiftiConnectivityMatrixDenseFile.cxx CiftiConnectivityMatrixDenseParcelFile.cxx CiftiConnectivityMatrixParcelFile.cxx CiftiConnectivityMatrixParcelDenseFile.cxx CiftiFiberOrientationFile.cxx CiftiFiberTrajectoryFile.cxx CiftiMappableDataFile.cxx CiftiMappableConnectivityMatrixDataFile.cxx CiftiParcelColoringModeEnum.cxx CiftiParcelLabelFile.cxx CiftiParcelReordering.cxx CiftiParcelReorderingModel.cxx CiftiParcelSeriesFile.cxx CiftiParcelScalarFile.cxx CiftiScalarDataSeriesFile.cxx ConnectivityDataLoaded.cxx EventCaretMappableDataFilesGet.cxx EventChartMatrixParcelYokingValidation.cxx EventGetDisplayedDataFiles.cxx EventMapYokingSelectMap.cxx EventMapYokingValidation.cxx EventPaletteGetByName.cxx EventSurfaceColoringInvalidate.cxx EventSurfaceStructuresValidGet.cxx Fiber.cxx FiberOrientation.cxx FiberOrientationColoringTypeEnum.cxx FiberOrientationTrajectory.cxx FiberTrajectoryColorModel.cxx FiberTrajectoryDisplayModeEnum.cxx FiberTrajectoryMapProperties.cxx FilePathNamePrefixCompactor.cxx FociFile.cxx FociFileSaxReader.cxx Focus.cxx GeodesicHelper.cxx GiftiTypeFile.cxx GroupAndNameCheckStateEnum.cxx GroupAndNameHierarchyGroup.cxx GroupAndNameHierarchyItem.cxx GroupAndNameHierarchyModel.cxx GroupAndNameHierarchyName.cxx ImageDimensionsModel.cxx ImageFile.cxx ImagePixelsPerSpatialUnitsEnum.cxx ImageSpatialUnitsEnum.cxx LabelDrawingProperties.cxx LabelDrawingTypeEnum.cxx LabelFile.cxx MapYokingGroupEnum.cxx MetricFile.cxx MetricSmoothingObject.cxx NodeAndVoxelColoring.cxx OxfordSparseThreeFile.cxx PaletteFile.cxx RgbaFile.cxx RibbonMappingHelper.cxx SceneFile.cxx SceneFileSaxReader.cxx SignedDistanceHelper.cxx SparseVolumeIndexer.cxx SpecFile.cxx SpecFileDataFileTypeGroup.cxx SpecFileDataFile.cxx SpecFileSaxReader.cxx StudyMetaDataLink.cxx StudyMetaDataLinkSet.cxx StudyMetaDataLinkSetSaxReader.cxx SurfaceFile.cxx SurfaceProjectedItem.cxx SurfaceProjectedItemSaxReader.cxx SurfaceProjection.cxx SurfaceProjectionBarycentric.cxx SurfaceProjectionVanEssen.cxx SurfaceProjector.cxx SurfaceProjectorException.cxx SurfaceResamplingHelper.cxx SurfaceResamplingMethodEnum.cxx SurfaceTypeEnum.cxx TextFile.cxx TopologyHelper.cxx VolumeEditingModeEnum.cxx VolumeFile.cxx VolumeFileEditorDelegate.cxx VolumeFileVoxelColorizer.cxx VolumeMapUndoCommand.cxx VolumePaddingHelper.cxx VolumeSliceProjectionTypeEnum.cxx VolumeSpline.cxx VtkFileExporter.cxx WarpfieldFile.cxx ) # # Find Headers # INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/Charting ${CMAKE_SOURCE_DIR}/Files ${CMAKE_SOURCE_DIR}/FilesBase ${CMAKE_SOURCE_DIR}/Cifti ${CMAKE_SOURCE_DIR}/Palette ${CMAKE_SOURCE_DIR}/Gifti ${CMAKE_SOURCE_DIR}/Nifti ${CMAKE_SOURCE_DIR}/Scenes ${CMAKE_SOURCE_DIR}/Xml ${CMAKE_SOURCE_DIR}/Common ) workbench-1.1.1/src/Files/CaretDataFile.cxx000066400000000000000000000264351255417355300205250ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CARET_DATA_FILE_DECLARE__ #include "CaretDataFile.h" #undef __CARET_DATA_FILE_DECLARE__ #include "CaretMappableDataFile.h" #include "DataFileContentInformation.h" #include "SceneClass.h" using namespace caret; /** * \class caret::CaretDataFile * \brief A data file with abstract methods for caret data * * This class is essentially an interface that defines methods * that are supported by most Caret Data Files. */ /** * Constructor. */ CaretDataFile::CaretDataFile(const DataFileTypeEnum::Enum dataFileType) : DataFile(), SceneableInterface() { m_dataFileType = dataFileType; AString name = (DataFileTypeEnum::toName(m_dataFileType).toLower() + "_file_" + AString::number(s_defaultFileNameCounter) + "." + DataFileTypeEnum::toFileExtension(m_dataFileType)); s_defaultFileNameCounter++; setFileName(name); } /** * Destructor. */ CaretDataFile::~CaretDataFile() { } /** * @return The type of this data file. */ DataFileTypeEnum::Enum CaretDataFile::getDataFileType() const { return m_dataFileType; } /** * Override the default data type for the file. * Use this with extreme caution as using a type invalid * with the file may cause disaster. * * @param dataFileType * New value for file's data type. */ void CaretDataFile::setDataFileType(const DataFileTypeEnum::Enum dataFileType) { m_dataFileType = dataFileType; } /** * Copy constructor. * @param cdf * Instance that is copied to this. */ CaretDataFile::CaretDataFile(const CaretDataFile& cdf) : DataFile(cdf), SceneableInterface(cdf) { copyDataCaretDataFile(cdf); } /** * Assignment operator. * @param cdf * Instance that is assigned to this. */ CaretDataFile& CaretDataFile::operator=(const CaretDataFile& cdf) { if (this != &cdf) { DataFile::operator=(cdf); copyDataCaretDataFile(cdf); } return *this; } /** * Assists with copying instances of this class. * @param cdf * Instance that is copied to this. */ void CaretDataFile::copyDataCaretDataFile(const CaretDataFile& cdf) { m_dataFileType = cdf.m_dataFileType; } /** * @return Is this file mapped to a valid single structure ? */ bool CaretDataFile::isSingleStructure() const { return StructureEnum::isSingleStructure(getStructure()); } /** * Add information about the file to the data file information. * * @param dataFileInformation * Consolidates information about a data file. */ void CaretDataFile::addToDataFileContentInformation(DataFileContentInformation& dataFileInformation) { DataFile::addToDataFileContentInformation(dataFileInformation); dataFileInformation.addNameAndValue("Type", DataFileTypeEnum::toGuiName(m_dataFileType)); std::vector allStructures; StructureEnum::getAllEnums(allStructures); CaretMappableDataFile* cmdf = dynamic_cast(this); if (cmdf != NULL) { AString structureNames; for (std::vector::iterator iter = allStructures.begin(); iter != allStructures.end(); iter++) { if (cmdf->isMappableToSurfaceStructure(*iter)) { structureNames.append(StructureEnum::toGuiName(*iter) + " "); } } dataFileInformation.addNameAndValue("Structure", structureNames); } else { dataFileInformation.addNameAndValue("Structure", StructureEnum::toGuiName(getStructure())); } } /** * Set the username and password for reading files, typically from * a database or website. * * @param username * Account's username. * @param password * Account's password. */ void CaretDataFile::setFileReadingUsernameAndPassword(const AString& username, const AString& password) { s_fileReadingUsername = username; s_fileReadingPassword = password; } /** * @return The username for file reading from database or website. */ AString CaretDataFile::getFileReadingUsername() { return s_fileReadingUsername; } /** * @return The password for file reading from database or website. */ AString CaretDataFile::getFileReadingPassword() { return s_fileReadingPassword; } /** * Create a scene for an instance of a class. * * NOTE: In most cases, subclasses should not override this method but * instead override saveFileDataToScene() use it to add data to the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @return Pointer to SceneClass object representing the state of * this object. If there is no data for the scene, a NULL pointer * will be returned. */ SceneClass* CaretDataFile::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "CaretDataFile", 1); saveFileDataToScene(sceneAttributes, sceneClass); if (sceneClass->getNumberOfObjects() <= 0) { delete sceneClass; sceneClass = NULL; } // const int32_t numMaps = getNumberOfMaps(); // if (numMaps > 0) { // bool* mapEnabledArray = new bool[numMaps]; // for (int32_t i = 0; i < numMaps; i++) { // mapEnabledArray[i] = m_mapContent[i]->m_dataLoadingEnabled; // } // // sceneClass->addBooleanArray("mapEnabled", // mapEnabledArray, // numMaps); // delete[] mapEnabledArray; // } return sceneClass; } /** * Restore the state of an instance of a class. * * * NOTE: In most cases, subclasses should not override this method but * instead override restoreFileDataFromScene() use it to access * data from the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass for the instance of a class that implements * this interface. May be NULL for some types of scenes. */ void CaretDataFile::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } restoreFileDataFromScene(sceneAttributes, sceneClass); } /** * Save file data from the scene. For subclasses that need to * save to a scene, this method should be overriden. sceneClass * will be valid and any scene data should be added to it. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass to which data members should be added. */ void CaretDataFile::saveFileDataToScene(const SceneAttributes* /*sceneAttributes*/, SceneClass* /*sceneClass*/) { /* Nothing as subclasses needing to save to scenes will override. */ } /** * Restore file data from the scene. For subclasses that need to * restore from a scene, this method should be overridden. The scene class * will be valid and any scene data may be obtained from it. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass for the instance of a class that implements * this interface. Will NEVER be NULL. */ void CaretDataFile::restoreFileDataFromScene(const SceneAttributes* /*sceneAttributes*/, const SceneClass* /*sceneClass*/) { /* Nothing as subclasses needing to restore from scenes will override. */ } /** * @return True if this file type supports writing, else false. * * By default, this method returns true. Files that do not support * writing should override this method and return false. */ bool CaretDataFile::supportsWriting() const { return true; } /** * @return The name (and path if present) of the file with the * filename's extension removed. * * First, the filename is tested to see if it ends with any of the valid * file extensions for the DataFileTypeEnum. If any of the extensions match, * the filename, with the extension removed, is returned. The valud * file extensions are used since many GIFTI and CIFTI files contain * a "." in their extensions (eg: .surf.gii .dconn.nii). * * /mnt/path/anatomical.surf.gii "returns" /mnt/path/anatomical * * Second, the last "/" (directory separator) is found to locate where * the name of the file, excluding the path, is located. Using just * the name of the file, anything before the last "." is returned. * removed. * * Third, if there is not "." in the name of the file * the equivalent of getFileName() is returned. */ AString CaretDataFile::getFileNameNoExtension() const { AString name = getFileName(); std::vector dataFileTypeExtensions = DataFileTypeEnum::getAllFileExtensions(getDataFileType()); /* * Test using file type extensions */ for (std::vector::iterator iter = dataFileTypeExtensions.begin(); iter != dataFileTypeExtensions.end(); iter++) { const AString ext = *iter; const int offset = name.lastIndexOf(ext); if (offset > 0) { name.resize(offset - 1); return name; } } /* * Look for the last "." and the last forward slash or back slash * * Dont' was to chop off a "." in the path (eg: /mnt/back.up/file) */ const int lastSlashIndex = std::max(name.lastIndexOf("/"), name.lastIndexOf("\\")); const int dotIndex = name.lastIndexOf("."); if (lastSlashIndex > 0) { if (dotIndex > lastSlashIndex) { name.resize(dotIndex); } } else { if (dotIndex > 0) { name.resize(dotIndex); } } return name; } workbench-1.1.1/src/Files/CaretDataFile.h000066400000000000000000000077621255417355300201540ustar00rootroot00000000000000#ifndef __CARET_DATA_FILE__H_ #define __CARET_DATA_FILE__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "DataFile.h" #include "DataFileTypeEnum.h" #include "SceneableInterface.h" #include "StructureEnum.h" namespace caret { class GiftiMetaData; class CaretDataFile : public DataFile, public SceneableInterface { public: CaretDataFile(const DataFileTypeEnum::Enum dataFileType); virtual ~CaretDataFile(); virtual bool isSingleStructure() const; /** * @return The structure for this file. */ virtual StructureEnum::Enum getStructure() const = 0; /** * Set the structure for this file. * @param structure * New structure for this file. */ virtual void setStructure(const StructureEnum::Enum structure) = 0; DataFileTypeEnum::Enum getDataFileType() const; /** * @return Get access to the file's metadata. */ virtual GiftiMetaData* getFileMetaData() = 0; /** * @return Get access to unmodifiable file's metadata. */ virtual const GiftiMetaData* getFileMetaData() const = 0; virtual AString getFileNameNoExtension() const; virtual void addToDataFileContentInformation(DataFileContentInformation& dataFileInformation); virtual bool supportsWriting() const; virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); static void setFileReadingUsernameAndPassword(const AString& username, const AString& password); static AString getFileReadingUsername(); static AString getFileReadingPassword(); protected: CaretDataFile(const CaretDataFile& cdf); CaretDataFile& operator=(const CaretDataFile& cdf); void setDataFileType(const DataFileTypeEnum::Enum dataFileType); virtual void saveFileDataToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass); virtual void restoreFileDataFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private: void copyDataCaretDataFile(const CaretDataFile& cdf); DataFileTypeEnum::Enum m_dataFileType; /** A counter that is used when creating default file names */ static int64_t s_defaultFileNameCounter; static AString s_fileReadingUsername; static AString s_fileReadingPassword; }; #ifdef __CARET_DATA_FILE_DECLARE__ int64_t CaretDataFile::s_defaultFileNameCounter = 1; AString CaretDataFile::s_fileReadingUsername = ""; AString CaretDataFile::s_fileReadingPassword = ""; #endif // __CARET_DATA_FILE_DECLARE__ } // namespace #endif //__CARET_DATA_FILE__H_ workbench-1.1.1/src/Files/CaretDataFileHelper.cxx000066400000000000000000000204541255417355300216600ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CARET_DATA_FILE_HELPER_DECLARE__ #include "CaretDataFileHelper.h" #undef __CARET_DATA_FILE_HELPER_DECLARE__ #include "CaretAssert.h" #include "DataFileTypeEnum.h" using namespace caret; /** * \class caret::CaretDataFileHelper * \brief Contains static methods to help out with CaretDataFile subclasses. * \ingroup Files */ /** * Constructor. */ CaretDataFileHelper::CaretDataFileHelper() { } /** * Destructor. */ CaretDataFileHelper::~CaretDataFileHelper() { } #include "BorderFile.h" #include "CiftiBrainordinateDataSeriesFile.h" #include "CiftiBrainordinateLabelFile.h" #include "CiftiBrainordinateScalarFile.h" #include "CiftiConnectivityMatrixDenseFile.h" #include "CiftiConnectivityMatrixDenseParcelFile.h" #include "CiftiConnectivityMatrixParcelDenseFile.h" #include "CiftiConnectivityMatrixParcelFile.h" #include "CiftiParcelLabelFile.h" #include "CiftiParcelScalarFile.h" #include "CiftiParcelSeriesFile.h" #include "CiftiFiberOrientationFile.h" #include "CiftiFiberTrajectoryFile.h" #include "CiftiScalarDataSeriesFile.h" #include "FileInformation.h" #include "FociFile.h" #include "ImageFile.h" #include "LabelFile.h" #include "MetricFile.h" #include "PaletteFile.h" #include "RgbaFile.h" #include "SceneFile.h" #include "SpecFile.h" #include "SurfaceFile.h" #include "VolumeFile.h" /** * Read any CaretDataFile subclass. * * @param filename * Name of file. * @return * Pointer to the data file after reading the file. * @throw * DataFileException if unable to read the file for any reason. */ CaretDataFile* CaretDataFileHelper::readAnyCaretDataFile(const AString& filename, const bool& preferOnDisk) { bool isValid = false; const DataFileTypeEnum::Enum dataFileType = DataFileTypeEnum::fromFileExtension(filename, &isValid); if (( ! isValid) || (dataFileType == DataFileTypeEnum::UNKNOWN)) { throw DataFileException(filename, "Filename extension does not match any supported data file."); } CaretDataFile* caretDataFile = NULL; switch (dataFileType) { case DataFileTypeEnum::BORDER: caretDataFile = new BorderFile(); break; case DataFileTypeEnum::CONNECTIVITY_DENSE: caretDataFile = new CiftiConnectivityMatrixDenseFile(); break; case DataFileTypeEnum::CONNECTIVITY_DENSE_LABEL: caretDataFile = new CiftiBrainordinateLabelFile(); break; case DataFileTypeEnum::CONNECTIVITY_DENSE_PARCEL: caretDataFile = new CiftiConnectivityMatrixDenseParcelFile(); break; case DataFileTypeEnum::CONNECTIVITY_DENSE_SCALAR: caretDataFile = new CiftiBrainordinateScalarFile(); break; case DataFileTypeEnum::CONNECTIVITY_DENSE_TIME_SERIES: caretDataFile = new CiftiBrainordinateDataSeriesFile(); break; case DataFileTypeEnum::CONNECTIVITY_FIBER_ORIENTATIONS_TEMPORARY: caretDataFile = new CiftiFiberOrientationFile(); break; case DataFileTypeEnum::CONNECTIVITY_FIBER_TRAJECTORY_TEMPORARY: caretDataFile = new CiftiFiberTrajectoryFile(); break; case DataFileTypeEnum::CONNECTIVITY_PARCEL: caretDataFile = new CiftiConnectivityMatrixParcelFile(); break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_DENSE: caretDataFile = new CiftiConnectivityMatrixParcelDenseFile(); break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_LABEL: caretDataFile = new CiftiParcelLabelFile(); break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_SCALAR: caretDataFile = new CiftiParcelScalarFile(); break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_SERIES: caretDataFile = new CiftiParcelSeriesFile(); break; case DataFileTypeEnum::CONNECTIVITY_SCALAR_DATA_SERIES: caretDataFile = new CiftiScalarDataSeriesFile(); break; case DataFileTypeEnum::FOCI: caretDataFile = new FociFile(); break; case DataFileTypeEnum::IMAGE: caretDataFile = new ImageFile(); break; case DataFileTypeEnum::LABEL: caretDataFile = new LabelFile(); break; case DataFileTypeEnum::METRIC: caretDataFile = new MetricFile(); break; case DataFileTypeEnum::PALETTE: caretDataFile = new PaletteFile(); break; case DataFileTypeEnum::RGBA: caretDataFile = new RgbaFile(); break; case DataFileTypeEnum::SCENE: caretDataFile = new SceneFile(); break; case DataFileTypeEnum::SPECIFICATION: caretDataFile = new SpecFile(); break; case DataFileTypeEnum::SURFACE: caretDataFile = new SurfaceFile(); break; case DataFileTypeEnum::UNKNOWN: CaretAssert(0); break; case DataFileTypeEnum::VOLUME: caretDataFile = new VolumeFile(); break; } CaretAssert(caretDataFile); try { if (preferOnDisk) caretDataFile->setPreferOnDiskReading(true);//NOTE: because Dense Connectivity also pays attention to this, never change default behaviors away from on disk try { caretDataFile->readFile(filename); } catch (const std::bad_alloc& badAlloc) { /* * This DataFileException will be caught * in the outer try/catch and it will * clean up to avoid memory leaks. */ throw DataFileException(filename, createBadAllocExceptionMessage(filename)); } } catch (const DataFileException& dfe) { delete caretDataFile; caretDataFile = NULL; throw dfe; } return caretDataFile; } /** * Creates a useful error message when a std::bad_alloc exception occurs. * * @param filename * Name of file that caused the std::bad_alloc exception. * @return * Message with info about the file. */ AString CaretDataFileHelper::createBadAllocExceptionMessage(const AString& filename) { FileInformation fileInfo(filename); AString message("Unable to allocate memory for reading the file."); if (fileInfo.exists()) { // float bytes = (float)fileInfo.size(); // short index = 0; // static const char *labels[9] = {" Bytes", " Kilobytes", " Megabytes", " Gigabytes", " Terabytes", " Petabytes", " Exabytes", " Zettabytes", " Yottabytes"}; // while (index < 8 && bytes > 1000.0f) // { // ++index; // bytes = bytes / 1000.0f;//using 1024 would make it Kibibytes, etc // } // AString sizeString = AString::number(bytes, 'f', 2) + labels[index];//2 digits after decimal point const AString sizeString = FileInformation::fileSizeToStandardUnits(fileInfo.size()); message.appendWithNewLine("File Size: " + sizeString); message.appendWithNewLine(""); message.appendWithNewLine("Note: The amount of memory required to read a data file may be " "substantially larger than the size of the file due to the way the " "file's data is organized in memory or compression of data within the file."); } return message; } workbench-1.1.1/src/Files/CaretDataFileHelper.h000066400000000000000000000033641255417355300213060ustar00rootroot00000000000000#ifndef __CARET_DATA_FILE_HELPER_H__ #define __CARET_DATA_FILE_HELPER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AString.h" namespace caret { class CaretDataFile; class CaretDataFileHelper { public: static CaretDataFile* readAnyCaretDataFile(const AString& filename, const bool& preferOnDisk = false); static AString createBadAllocExceptionMessage(const AString& filename); private: CaretDataFileHelper(); virtual ~CaretDataFileHelper(); CaretDataFileHelper(const CaretDataFileHelper&); CaretDataFileHelper& operator=(const CaretDataFileHelper&); public: // ADD_NEW_METHODS_HERE private: // ADD_NEW_MEMBERS_HERE }; #ifdef __CARET_DATA_FILE_HELPER_DECLARE__ // #endif // __CARET_DATA_FILE_HELPER_DECLARE__ } // namespace #endif //__CARET_DATA_FILE_HELPER_H__ workbench-1.1.1/src/Files/CaretMappableDataFile.cxx000066400000000000000000001033311255417355300221560ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CARET_MAPPABLE_DATA_FILE_DECLARE__ #include "CaretMappableDataFile.h" #undef __CARET_MAPPABLE_DATA_FILE_DECLARE__ #include #include "CaretLogger.h" #include "ChartDataCartesian.h" #include "CiftiMappableConnectivityMatrixDataFile.h" #include "DataFileContentInformation.h" #include "FastStatistics.h" #include "FileInformation.h" #include "GiftiLabelTable.h" #include "Histogram.h" #include "LabelDrawingProperties.h" #include "PaletteColorMapping.h" #include "SceneClass.h" #include "SceneClassArray.h" #include "SceneAttributes.h" #include "StringTableModel.h" using namespace caret; /** * Constructor. */ CaretMappableDataFile::CaretMappableDataFile(const DataFileTypeEnum::Enum dataFileType) : CaretDataFile(dataFileType) { m_labelDrawingProperties.grabNew(new LabelDrawingProperties()); m_paletteNormalizationMode = PaletteNormalizationModeEnum::NORMALIZATION_SELECTED_MAP_DATA; } /** * Destructor. */ CaretMappableDataFile::~CaretMappableDataFile() { } /** * Constructor. * @param cmdf * Instance that is copied. */ CaretMappableDataFile::CaretMappableDataFile(const CaretMappableDataFile& cmdf) : CaretDataFile(cmdf) { this->copyCaretMappableDataFile(cmdf); } /** * Constructor. * @param cmdf * Instance that is assigned to this. * @return * Reference to this instance. */ CaretMappableDataFile& CaretMappableDataFile::operator=(const CaretMappableDataFile& cmdf) { if (this != &cmdf) { CaretDataFile::operator=(cmdf); this->copyCaretMappableDataFile(cmdf); } return *this; } /** * Assists with copying instances of this class. */ void CaretMappableDataFile::copyCaretMappableDataFile(const CaretMappableDataFile& cmdf) { m_paletteNormalizationMode = cmdf.m_paletteNormalizationMode; } // note: method is documented in header file bool CaretMappableDataFile::hasMapAttributes() const { return true; } /** * Is this file able to map to the given structure? Some data files, such * as CIFTI files, are able to map to multiple surface structure. The default * implementation of this method simply compares the given structure to * getStructure() and returns true if they are the same value, else false. * * @param structure * Structure for testing mappability status. * @return True if this file is able to map to the given structure, else false. */ bool CaretMappableDataFile::isMappableToSurfaceStructure(const StructureEnum::Enum structure) const { if (getStructure() == StructureEnum::ALL) { return true; } if (structure == getStructure()) { return true; } return false; } // note: method is documented in header file int32_t CaretMappableDataFile::getMapIndexFromNameOrNumber(const AString& mapName) const { bool ok = false; int32_t ret = mapName.toInt(&ok) - 1;//compensate for 1-indexing that command line parsing uses if (ok) { if (ret < 0 || ret >= getNumberOfMaps()) { ret = -1; } } else {//DO NOT search by name if the string was parsed as an integer correctly, or some idiot who names their maps as integers will get confused //when getting map "12" out of a file after the file expands to more than 12 elements suddenly does something different ret = getMapIndexFromName(mapName); } return ret; } // note: method is documented in header file int32_t CaretMappableDataFile::getMapIndexFromName(const AString& mapName) const { int32_t numMaps = getNumberOfMaps(); for (int32_t i = 0; i < numMaps; ++i) { if (mapName == getMapName(i)) { return i; } } return -1; } // note: method is documented in header file int32_t CaretMappableDataFile::getMapIndexFromUniqueID(const AString& uniqueID) const { int32_t numMaps = getNumberOfMaps(); for (int32_t i = 0; i < numMaps; ++i) { if (uniqueID == getMapUniqueID(i)) { return i; } } return -1; } // note: method is documented in header file void CaretMappableDataFile::updateScalarColoringForAllMaps(const PaletteFile* paletteFile) { const int32_t numMaps = getNumberOfMaps(); for (int32_t iMap = 0; iMap < numMaps; iMap++) { updateScalarColoringForMap(iMap, paletteFile); } } /** * @return True if this file is mapped using a palette and all palettes * are equal, otherwise false. */ bool CaretMappableDataFile::isPaletteColorMappingEqualForAllMaps() const { if ( ! isMappedWithPalette()) { return false; } const int32_t numMaps = getNumberOfMaps(); if (numMaps <= 0) { return false; } /* * Some files use one palette color mapping for all maps * and this can be detected if the pointer to palette * color mapping is the same for all maps. */ bool pointerTheSameFlag = true; const PaletteColorMapping* firstPCM = getMapPaletteColorMapping(0); for (int32_t iMap = 1; iMap < numMaps; iMap++) { if (firstPCM != getMapPaletteColorMapping(iMap)) { pointerTheSameFlag = false; break; } } if (pointerTheSameFlag) { return true; } /* * Compare each palette color mapping to the first palette color mapping */ for (int32_t iMap = 1; iMap < numMaps; iMap++) { if (*firstPCM != *getMapPaletteColorMapping(iMap)) { return false; } } return true; } // note: method is documented in header file NiftiTimeUnitsEnum::Enum CaretMappableDataFile::getMapIntervalUnits() const { return NiftiTimeUnitsEnum::NIFTI_UNITS_UNKNOWN; } // note: method is documented in header file void CaretMappableDataFile::getMapIntervalStartAndStep(float& firstMapUnitsValueOut, float& mapIntervalStepValueOut) const { firstMapUnitsValueOut = 1.0; mapIntervalStepValueOut = 1.0; } /** * Get the minimum and maximum values from ALL maps in this file. * Note that not all files (due to size of file) are able to provide * the minimum and maximum values from the file. The return value * indicates success/failure. If the failure (false) is returned * the returned values are likely +/- the maximum float values. * * @param dataRangeMinimumOut * Minimum data value found. * @param dataRangeMaximumOut * Maximum data value found. * @return * True if the values are valid, else false. */ bool CaretMappableDataFile::getDataRangeFromAllMaps(float& dataRangeMinimumOut, float& dataRangeMaximumOut) const { dataRangeMaximumOut = std::numeric_limits::max(); dataRangeMinimumOut = -dataRangeMaximumOut; return false; } /** * Save file data from the scene. For subclasses that need to * save to a scene, this method should be overriden. sceneClass * will be valid and any scene data should be added to it. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass to which data members should be added. */ void CaretMappableDataFile::saveFileDataToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass) { CaretDataFile::saveFileDataToScene(sceneAttributes, sceneClass); sceneClass->addClass(m_labelDrawingProperties->saveToScene(sceneAttributes, "m_labelDrawingProperties")); if (isMappedWithPalette()) { sceneClass->addEnumeratedType("m_paletteNormalizationMode", m_paletteNormalizationMode); if (sceneAttributes->isModifiedPaletteSettingsSavedToScene()) { std::vector pcmClassVector; const int32_t numMaps = getNumberOfMaps(); for (int32_t i = 0; i < numMaps; i++) { const PaletteColorMapping* pcmConst = getMapPaletteColorMapping(i); if (pcmConst->isModified()) { PaletteColorMapping* pcm = const_cast(pcmConst); try { const AString xml = pcm->encodeInXML(); SceneClass* pcmClass = new SceneClass("savedPaletteColorMapping", "SavedPaletteColorMapping", 1); pcmClass->addString("mapName", getMapName(i)); pcmClass->addInteger("mapIndex", i); pcmClass->addInteger("mapCount", numMaps); pcmClass->addString("mapColorMapping", xml); pcmClassVector.push_back(pcmClass); } catch (const XmlException& e) { sceneAttributes->addToErrorMessage("Failed to encode palette color mapping for file: " + getFileNameNoPath() + " Map Name: " + getMapName(i) + " Map Index: " + AString::number(i) + ". " + e.whatString()); } } } if ( ! pcmClassVector.empty()) { SceneClassArray* pcmArray = new SceneClassArray("savedPaletteColorMappingArray", pcmClassVector); sceneClass->addChild(pcmArray); } } } } /** * Restore file data from the scene. For subclasses that need to * restore from a scene, this method should be overridden. The scene class * will be valid and any scene data may be obtained from it. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass for the instance of a class that implements * this interface. Will NEVER be NULL. */ void CaretMappableDataFile::restoreFileDataFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { CaretDataFile::restoreFileDataFromScene(sceneAttributes, sceneClass); m_labelDrawingProperties->restoreFromScene(sceneAttributes, sceneClass->getClass("m_labelDrawingProperties")); if (isMappedWithPalette()) { std::vector paletteNormalizationModes; getPaletteNormalizationModesSupported(paletteNormalizationModes); if ( ! paletteNormalizationModes.empty()) { const PaletteNormalizationModeEnum::Enum defValue = paletteNormalizationModes[0]; m_paletteNormalizationMode = sceneClass->getEnumeratedTypeValue("m_paletteNormalizationMode", defValue); } const int32_t numMaps = getNumberOfMaps(); const SceneClassArray* pcmArray = sceneClass->getClassArray("savedPaletteColorMappingArray"); if (pcmArray != NULL) { const int32_t numElements = pcmArray->getNumberOfArrayElements(); for (int32_t i = 0; i < numElements; i++) { const SceneClass* pcmClass = pcmArray->getClassAtIndex(i); const AString mapName = pcmClass->getStringValue("mapName"); const int32_t mapIndex = pcmClass->getIntegerValue("mapIndex", -1); const int32_t mapCount = pcmClass->getIntegerValue("mapCount", -1); const AString pcmString = pcmClass->getStringValue("mapColorMapping"); int32_t restoreMapIndex = -1; /* * Try to find map that has the saved name AND index. */ if (restoreMapIndex < 0) { if ((mapIndex >= 0) && (mapIndex < numMaps)) { if (getMapName(mapIndex) == mapName) { restoreMapIndex = mapIndex; } } } /* * If map count has not changed, give preference to * map index over map name */ if (mapCount == numMaps) { /* * Try to find map that has the saved map index. */ if (restoreMapIndex < 0) { if ((mapIndex >= 0) && (mapIndex < numMaps)) { restoreMapIndex = mapIndex; } } /* * Try to find map that has the saved map name. */ if (restoreMapIndex < 0) { if ( ! mapName.isEmpty()) { restoreMapIndex = getMapIndexFromName(mapName); } } } else { /* * Try to find map that has the saved map name. */ if (restoreMapIndex < 0) { if ( ! mapName.isEmpty()) { restoreMapIndex = getMapIndexFromName(mapName); } } /* * Try to find map that has the saved map index. */ if (restoreMapIndex < 0) { if ((mapIndex >= 0) && (mapIndex < numMaps)) { restoreMapIndex = mapIndex; } } } if (restoreMapIndex >= 0) { try { PaletteColorMapping pcm; pcm.decodeFromStringXML(pcmString); PaletteColorMapping* pcmMap = getMapPaletteColorMapping(restoreMapIndex); pcmMap->copy(pcm); pcmMap->clearModified(); } catch (const XmlException& e) { sceneAttributes->addToErrorMessage("Failed to decode palette color mapping for file: " + getFileNameNoPath() + " Map Name: " + getMapName(i) + " Map Index: " + AString::number(i) + ". " + e.whatString()); } } else { const AString msg = ("Unable to find map for restoring palette settings for file: " + getFileNameNoPath() + " Map Name: " + mapName + " Map Index: " + AString::number(mapIndex)); sceneAttributes->addToErrorMessage(msg); } } } } } /** * Add information about the file to the data file information. * * @param dataFileInformation * Consolidates information about a data file. */ void CaretMappableDataFile::addToDataFileContentInformation(DataFileContentInformation& dataFileInformation) { CaretDataFile::addToDataFileContentInformation(dataFileInformation); const int64_t dataSizeInBytes = getDataSizeUncompressedInBytes(); if (dataSizeInBytes >= 0) { dataFileInformation.addNameAndValue("Data Size", FileInformation::fileSizeToStandardUnits(dataSizeInBytes)); } dataFileInformation.addNameAndValue("Maps to Surface", isSurfaceMappable()); dataFileInformation.addNameAndValue("Maps to Volume", isVolumeMappable()); dataFileInformation.addNameAndValue("Maps with LabelTable", isMappedWithLabelTable()); dataFileInformation.addNameAndValue("Maps with Palette", isMappedWithPalette()); if (isMappedWithPalette()) { dataFileInformation.addNameAndValue("All Map Palettes Equal", isPaletteColorMappingEqualForAllMaps()); NiftiTimeUnitsEnum::Enum timeUnits = getMapIntervalUnits(); switch (timeUnits) { case NiftiTimeUnitsEnum::NIFTI_UNITS_HZ: break; case NiftiTimeUnitsEnum::NIFTI_UNITS_MSEC: break; case NiftiTimeUnitsEnum::NIFTI_UNITS_PPM: break; case NiftiTimeUnitsEnum::NIFTI_UNITS_SEC: break; case NiftiTimeUnitsEnum::NIFTI_UNITS_UNKNOWN: break; case NiftiTimeUnitsEnum::NIFTI_UNITS_USEC: break; } dataFileInformation.addNameAndValue("Map Interval Units", NiftiTimeUnitsEnum::toName(timeUnits)); if (timeUnits != NiftiTimeUnitsEnum::NIFTI_UNITS_UNKNOWN) { float mapIntervalStart, mapIntervalStep; getMapIntervalStartAndStep(mapIntervalStart, mapIntervalStep); dataFileInformation.addNameAndValue("Map Interval Start", mapIntervalStart); dataFileInformation.addNameAndValue("Map Interval Step", mapIntervalStep); } } bool showMapFlag = (isMappedWithLabelTable() || isMappedWithPalette()); /* * Do not show maps on CIFTI connectivity matrix data because * they do not have maps, they have a matrix. */ const bool ciftiMatrixFlag = (dynamic_cast(this) != NULL); if (ciftiMatrixFlag) { showMapFlag = false; } /* * Did user override display of map information? */ if ( ! dataFileInformation.isOptionFlag(DataFileContentInformation::OPTION_SHOW_MAP_INFORMATION)) { showMapFlag = false; } if (showMapFlag) { const int32_t numMaps = getNumberOfMaps(); dataFileInformation.addNameAndValue("Number of Maps", numMaps); if (numMaps > 0) { int columnCount = 0; const int COL_INDEX = columnCount++; int32_t COL_MIN = -1; int32_t COL_MAX = -1; int32_t COL_MEAN = -1; int32_t COL_DEV = -1; int32_t COL_PCT_POS = -1; int32_t COL_PCT_NEG = -1; int32_t COL_INF_NAN = -1; if (isMappedWithPalette()) { COL_MIN = columnCount++; COL_MAX = columnCount++; COL_MEAN = columnCount++; COL_DEV = columnCount++; COL_PCT_POS = columnCount++; COL_PCT_NEG = columnCount++; COL_INF_NAN = columnCount++; } const int COL_NAME = columnCount++; /* * Include a row for the column titles */ const int32_t tableRowCount = numMaps + 1; StringTableModel stringTable(tableRowCount, columnCount); stringTable.setElement(0, COL_INDEX, "Map"); if (COL_MIN >= 0) { stringTable.setElement(0, COL_MIN, "Minimum"); } if (COL_MAX >= 0) { stringTable.setElement(0, COL_MAX, "Maximum"); } if (COL_MEAN >= 0) { stringTable.setElement(0, COL_MEAN, "Mean"); } if (COL_DEV >= 0) { stringTable.setElement(0, COL_DEV, "Sample Dev"); } if (COL_PCT_POS >= 0) { stringTable.setElement(0, COL_PCT_POS, "% Positive"); } if (COL_PCT_NEG >= 0) { stringTable.setElement(0, COL_PCT_NEG, "% Negative"); } if (COL_INF_NAN >= 0) { stringTable.setElement(0, COL_INF_NAN, "Inf/NaN"); } stringTable.setElement(0, COL_NAME, "Map Name"); stringTable.setColumnAlignment(COL_NAME, StringTableModel::ALIGN_LEFT); for (int32_t mapIndex = 0; mapIndex < numMaps; mapIndex++) { const int32_t tableRow = mapIndex + 1; CaretAssert(COL_INDEX >= 0); CaretAssert(COL_NAME >= 0); stringTable.setElement(tableRow, COL_INDEX, (mapIndex + 1)); stringTable.setElement(tableRow, COL_NAME, getMapName(mapIndex)); const FastStatistics* stats = const_cast(this)->getMapFastStatistics(mapIndex); if (isMappedWithPalette() && (stats != NULL)) { const Histogram* histogram = getMapHistogram(mapIndex); int64_t posCount = 0; int64_t zeroCount = 0; int64_t negCount = 0; int64_t infCount = 0; int64_t negInfCount = 0; int64_t nanCount = 0; histogram->getCounts(posCount, zeroCount, negCount, infCount, negInfCount, nanCount); const int64_t numInfinityAndNotANumber = (infCount + negInfCount + nanCount); const double totalCount = (posCount + zeroCount + negCount + numInfinityAndNotANumber); const double pctPositive = (posCount / totalCount) * 100.0; const double pctNegative = (negCount / totalCount) * 100.0; if (COL_MIN >= 0) { stringTable.setElement(tableRow, COL_MIN, stats->getMin()); } if (COL_MAX >= 0) { stringTable.setElement(tableRow, COL_MAX, stats->getMax()); } if (COL_MEAN >= 0) { stringTable.setElement(tableRow, COL_MEAN, stats->getMean()); } if (COL_DEV >= 0) { stringTable.setElement(tableRow, COL_DEV, stats->getSampleStdDev()); } if (COL_PCT_POS >= 0) { stringTable.setElement(tableRow, COL_PCT_POS, pctPositive); } if (COL_PCT_NEG >= 0) { stringTable.setElement(tableRow, COL_PCT_NEG, pctNegative); } if (COL_INF_NAN >= 0) { stringTable.setElement(tableRow, COL_INF_NAN, numInfinityAndNotANumber); } } } dataFileInformation.addText("\n" + stringTable.getInString() + "\n"); } } if (showMapFlag) { if (isMappedWithLabelTable()) { /* * Show label table for each map. * However, some files contain only a single label table used * for all maps and this condition is detected if the first * two label tables use the same pointer. */ const int32_t numMaps = getNumberOfMaps(); bool haveLabelTableForEachMap = false; if (numMaps > 1) { if (getMapLabelTable(0) != getMapLabelTable(1)) { haveLabelTableForEachMap = true; } } for (int32_t mapIndex = 0; mapIndex < numMaps; mapIndex++) { const AString labelTableName = ("Label table for " + (haveLabelTableForEachMap ? ("map " + AString::number(mapIndex + 1) + ": " + getMapName(mapIndex)) : ("ALL maps")) + "\n"); dataFileInformation.addText(labelTableName + getMapLabelTable(mapIndex)->toFormattedString(" ") + "\n"); if ( ! haveLabelTableForEachMap) { break; } } } } } /** * @return True if any of the maps in this file contain a * color mapping that possesses a modified status. */ bool CaretMappableDataFile::isModifiedPaletteColorMapping() const { if (isMappedWithPalette()) { const int32_t numMaps = getNumberOfMaps(); for (int32_t i = 0; i < numMaps; i++) { if (getMapPaletteColorMapping(i)->isModified()) { return true; } } } return false; } /** * @return True if the file is modified in any way EXCEPT for * the palette color mapping. Also see isModified(). */ bool CaretMappableDataFile::isModifiedExcludingPaletteColorMapping() const { if (CaretDataFile::isModified()) { return true; } return false; } /** * @return True if the file is modified in any way including * the palette color mapping. * * NOTE: While this method overrides that in the super class, * it is NOT virtual here. Thus subclasses cannot override * this method and instead, subclasses should overrride * isModifiedExcludingPaletteColorMapping(). */ bool CaretMappableDataFile::isModified() const { if (isModifiedExcludingPaletteColorMapping()) { return true; } if (isModifiedPaletteColorMapping()) { return true; } return false; } /** * Create cartesian chart data from the given data. * * @param * Data for the Y-axis. * @return * Pointer to the ChartDataCartesian instance. */ ChartDataCartesian* CaretMappableDataFile::helpCreateCartesianChartData(const std::vector& data) { const int64_t numData = static_cast(data.size()); /* * Some files may have time data but initially assume data-series */ bool timeSeriesFlag = false; float convertTimeToSeconds = 1.0; switch (getMapIntervalUnits()) { case NiftiTimeUnitsEnum::NIFTI_UNITS_HZ: break; case NiftiTimeUnitsEnum::NIFTI_UNITS_MSEC: timeSeriesFlag = true; convertTimeToSeconds = 1000.0; break; case NiftiTimeUnitsEnum::NIFTI_UNITS_PPM: break; case NiftiTimeUnitsEnum::NIFTI_UNITS_SEC: convertTimeToSeconds = 1.0; timeSeriesFlag = true; break; case NiftiTimeUnitsEnum::NIFTI_UNITS_UNKNOWN: break; case NiftiTimeUnitsEnum::NIFTI_UNITS_USEC: convertTimeToSeconds = 1000000.0; timeSeriesFlag = true; break; } ChartDataCartesian* chartData = NULL; if (timeSeriesFlag) { chartData = new ChartDataCartesian(ChartDataTypeEnum::CHART_DATA_TYPE_LINE_TIME_SERIES, ChartAxisUnitsEnum::CHART_AXIS_UNITS_TIME_SECONDS, ChartAxisUnitsEnum::CHART_AXIS_UNITS_NONE); } else { chartData = new ChartDataCartesian(ChartDataTypeEnum::CHART_DATA_TYPE_LINE_DATA_SERIES, ChartAxisUnitsEnum::CHART_AXIS_UNITS_NONE, ChartAxisUnitsEnum::CHART_AXIS_UNITS_NONE); } if (chartData != NULL) { float timeStart = 0.0; float timeStep = 1.0; if (timeSeriesFlag) { getMapIntervalStartAndStep(timeStart, timeStep); timeStart *= convertTimeToSeconds; timeStep *= convertTimeToSeconds; chartData->setTimeStartInSecondsAxisX(timeStart); chartData->setTimeStepInSecondsAxisX(timeStep); } for (int64_t i = 0; i < numData; i++) { float xValue = i; if (timeSeriesFlag) { /* * X-Value is "time" */ xValue = timeStart + (i * timeStep); } else { /* * X-Value is the map index and map indices start at one */ xValue = i + 1; } chartData->addPoint(xValue, data[i]); } } return chartData; } /** * Helper for getting chart data types supported by files that create * charts from brainordinates (multi-map files). * The chart data types are a function of the map interval units. * * @param chartDataTypesOut * Chart types supported by this file. */ void CaretMappableDataFile::helpGetSupportedLineSeriesChartDataTypes(std::vector& chartDataTypesOut) const { chartDataTypesOut.clear(); switch (getMapIntervalUnits()) { case NiftiTimeUnitsEnum::NIFTI_UNITS_HZ: chartDataTypesOut.push_back(ChartDataTypeEnum::CHART_DATA_TYPE_LINE_FREQUENCY_SERIES); break; case NiftiTimeUnitsEnum::NIFTI_UNITS_MSEC: chartDataTypesOut.push_back(ChartDataTypeEnum::CHART_DATA_TYPE_LINE_TIME_SERIES); break; case NiftiTimeUnitsEnum::NIFTI_UNITS_PPM: CaretLogSevere("Units - PPM not supported"); CaretAssertMessage(0, "Units - PPM not supported"); break; case NiftiTimeUnitsEnum::NIFTI_UNITS_SEC: chartDataTypesOut.push_back(ChartDataTypeEnum::CHART_DATA_TYPE_LINE_TIME_SERIES); break; case NiftiTimeUnitsEnum::NIFTI_UNITS_UNKNOWN: chartDataTypesOut.push_back(ChartDataTypeEnum::CHART_DATA_TYPE_LINE_DATA_SERIES); break; case NiftiTimeUnitsEnum::NIFTI_UNITS_USEC: chartDataTypesOut.push_back(ChartDataTypeEnum::CHART_DATA_TYPE_LINE_TIME_SERIES); break; } } /** * @return The label drawing properties for this file. A valid pointer * will always be returned even if the file does not provide label data. */ LabelDrawingProperties* CaretMappableDataFile::getLabelDrawingProperties() { return m_labelDrawingProperties; } /** * @return The label drawing properties for this file. A valid pointer * will always be returned even if the file does not provide label data. */ const LabelDrawingProperties* CaretMappableDataFile::getLabelDrawingProperties() const { return m_labelDrawingProperties; } /** * @return The palette normalization mode for the file. * The default is NORMALIZATION_SELECTED_MAP_DATA. */ PaletteNormalizationModeEnum::Enum CaretMappableDataFile::getPaletteNormalizationMode() const { return m_paletteNormalizationMode; } /** * Set the palette normalization mode for the file. * * @param mode * New value for palette normalization mode. */ void CaretMappableDataFile::setPaletteNormalizationMode(const PaletteNormalizationModeEnum::Enum mode) { m_paletteNormalizationMode = mode; } workbench-1.1.1/src/Files/CaretMappableDataFile.h000066400000000000000000000432631255417355300216120ustar00rootroot00000000000000#ifndef __CARET_MAPPABLE_DATA_FILE__H_ #define __CARET_MAPPABLE_DATA_FILE__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretDataFile.h" #include "ChartDataTypeEnum.h" #include "CaretPointer.h" #include "NiftiEnums.h" #include "PaletteNormalizationModeEnum.h" namespace caret { class ChartDataCartesian; class FastStatistics; class GiftiMetaData; class GiftiLabelTable; class Histogram; class LabelDrawingProperties; class PaletteColorMapping; class PaletteFile; /** * \class caret::CaretMappableDataFile * \brief A Caret data file that is mappable to surfaces and/or volumes. * * This class is essentially an interface that defines methods for * files that are 'mappable', as an overlay, to surfaces and/or volumes. * Use of a common interface simplifies selection and application * of these data files. * * For a GIFTI File, the number of maps is the number of data arrays * in the GIFTI file. For a volume, it may be the number of time points. * * Note that Caret5 used the term 'column'. */ class CaretMappableDataFile : public CaretDataFile { public: CaretMappableDataFile(const DataFileTypeEnum::Enum dataFileType); virtual ~CaretMappableDataFile(); virtual void addToDataFileContentInformation(DataFileContentInformation& dataFileInformation); /** * @return Is the data mappable to a surface? */ virtual bool isSurfaceMappable() const = 0; /** * @return Is the data mappable to a volume? */ virtual bool isVolumeMappable() const = 0; /** * @return The number of maps in the file. * Note: Caret5 used the term 'columns'. */ virtual int32_t getNumberOfMaps() const = 0; virtual bool isMappableToSurfaceStructure(const StructureEnum::Enum structure) const; /** * @return True if the file has map attributes (name and metadata). * For files that do not have map attributes, they should override * this method and return false. If not overriden, this method * returns true. * * Some files (such as CIFTI Connectivity Matrix Files and CIFTI * Data-Series Files) do not have Map Attributes and thus there * is no map name nor map metadata and options to edit these * attributes should not be presented to the user. * * These CIFTI files do contain palette color mapping but it is * associated with the file. To simplify palette color mapping editing * these file will return the file's palette color mapping for any * calls to getMapPaletteColorMapping(). */ virtual bool hasMapAttributes() const; /** * Get the name of the map at the given index. * * @param mapIndex * Index of the map. * @return * Name of the map. */ virtual AString getMapName(const int32_t mapIndex) const = 0; /** * Find the index of the map that uses the given name. * * @param mapName * Name of the desired map. * @return * Index of the map using the given name. If there is more * than one map with the given name, this method is likely * to return the index of the first map with the name. */ virtual int32_t getMapIndexFromName(const AString& mapName) const; /** * Find the index of the map that uses the given name. * * @param mapName * Name of the desired map. * @return * Index of the map using the given name. If there is more * than one map with the given name, this method is likely * to return the index of the first map with the name. */ virtual int32_t getMapIndexFromNameOrNumber(const AString& mapName) const; /** * Set the name of the map at the given index. * * If the file does not have map attributes (hasMapAttributes()) * calling this method will have not change the file. * * @param mapIndex * Index of the map. * @param mapName * New name for the map. */ virtual void setMapName(const int32_t mapIndex, const AString& mapName) = 0; /** * Get the metadata for the map at the given index * * If the file does not have map attributes (hasMapAttributes()) * a valid metadata object will be returned but changing its * content will have no effect on the file. * * @param mapIndex * Index of the map. * @return * Metadata for the map (const value). */ virtual const GiftiMetaData* getMapMetaData(const int32_t mapIndex) const = 0; /** * Get the metadata for the map at the given index * * If the file does not have map attributes (hasMapAttributes()) * a valid metadata object will be returned but changing its * content will have no effect on the file. * * @param mapIndex * Index of the map. * @return * Metadata for the map. */ virtual GiftiMetaData* getMapMetaData(const int32_t mapIndex) = 0; /** * Get the unique ID (UUID) for the map at the given index. * * @param mapIndex * Index of the map. * @return * String containing UUID for the map. */ virtual AString getMapUniqueID(const int32_t mapIndex) const = 0; /** * Find the index of the map that uses the given unique ID (UUID). * * @param uniqueID * Unique ID (UUID) of the desired map. * @return * Index of the map using the given UUID. */ virtual int32_t getMapIndexFromUniqueID(const AString& uniqueID) const; /** * @return Is the data in the file mapped to colors using * a palette. */ virtual bool isMappedWithPalette() const = 0; /** * @return The estimated size of data after it is uncompressed * and loaded into RAM. A negative value indicates that the * file size cannot be computed. */ virtual int64_t getDataSizeUncompressedInBytes() const = 0; /** * Get statistics describing the distribution of data * mapped with a color palette at the given index. * * @param mapIndex * Index of the map. * @return * Fast statistics for data (will be NULL for data * not mapped using a palette). */ virtual const FastStatistics* getMapFastStatistics(const int32_t mapIndex) = 0; /** * Get a histogram for the map at the given index. * * @param mapIndex * Index of the map. * @return * Histogram for data (will be NULL for data * not mapped using a palette). */ virtual const Histogram* getMapHistogram(const int32_t mapIndex) = 0; /** * Get a histogram for the map at the given index of data * mapped with a color palette at the given index for * data within the given ranges. * * @param mapIndex * Index of the map. * @param mostPositiveValueInclusive * Values more positive than this value are excluded. * @param leastPositiveValueInclusive * Values less positive than this value are excluded. * @param leastNegativeValueInclusive * Values less negative than this value are excluded. * @param mostNegativeValueInclusive * Values more negative than this value are excluded. * @param includeZeroValues * If true zero values (very near zero) are included. * @return * Histogram for data (will be NULL for data * not mapped using a palette). */ virtual const Histogram* getMapHistogram(const int32_t mapIndex, const float mostPositiveValueInclusive, const float leastPositiveValueInclusive, const float leastNegativeValueInclusive, const float mostNegativeValueInclusive, const bool includeZeroValues) = 0; /** * Get statistics describing the distribution of data * mapped with a color palette for all data within the file. * * @return * Fast statistics for data (will be NULL for data * not mapped using a palette). */ virtual const FastStatistics* getFileFastStatistics() = 0; /** * Get histogram describing the distribution of data * mapped with a color palette for all data within * the file. * * @return * Histogram for data (will be NULL for data * not mapped using a palette). */ virtual const Histogram* getFileHistogram() = 0; /** * Get histogram describing the distribution of data * mapped with a color palette for all data in the file * within the given range of values. * * @param mostPositiveValueInclusive * Values more positive than this value are excluded. * @param leastPositiveValueInclusive * Values less positive than this value are excluded. * @param leastNegativeValueInclusive * Values less negative than this value are excluded. * @param mostNegativeValueInclusive * Values more negative than this value are excluded. * @param includeZeroValues * If true zero values (very near zero) are included. * @return * Descriptive statistics for data (will be NULL for data * not mapped using a palette). */ virtual const Histogram* getFileHistogram(const float mostPositiveValueInclusive, const float leastPositiveValueInclusive, const float leastNegativeValueInclusive, const float mostNegativeValueInclusive, const bool includeZeroValues) = 0; /** * Get the palette color mapping for the map at the given index. * * @param mapIndex * Index of the map. * @return * Palette color mapping for the map (will be NULL for data * not mapped using a palette). */ virtual PaletteColorMapping* getMapPaletteColorMapping(const int32_t mapIndex) = 0; /** * Get the palette color mapping for the map at the given index. * * @param mapIndex * Index of the map. * @return * Palette color mapping for the map (constant) (will be NULL for data * not mapped using a palette). */ virtual const PaletteColorMapping* getMapPaletteColorMapping(const int32_t mapIndex) const = 0; /** * @return Is the data in the file mapped to colors using * a label table. */ virtual bool isMappedWithLabelTable() const = 0; /** * Get the label table for the map at the given index. * * @param mapIndex * Index of the map. * @return * Label table for the map (will be NULL for data * not mapped using a label table). */ virtual GiftiLabelTable* getMapLabelTable(const int32_t mapIndex) = 0; /** * Get the label table for the map at the given index. * * @param mapIndex * Index of the map. * @return * Label table for the map (constant) (will be NULL for data * not mapped using a label table). */ virtual const GiftiLabelTable* getMapLabelTable(const int32_t mapIndex) const = 0; /** * Get the palette normalization modes that are supported by the file. * * @param modesSupportedOut * Palette normalization modes supported by a file. Will be * empty for files that are not mapped with a palette. If there * is more than one suppported mode, the first mode in the * vector is assumed to be the default mode. */ virtual void getPaletteNormalizationModesSupported(std::vector& modesSupportedOut) = 0; /** * @return The palette normalization mode for the file. */ virtual PaletteNormalizationModeEnum::Enum getPaletteNormalizationMode() const; /** * Set the palette normalization mode for the file. * * @param mode * New value for palette normalization mode. */ virtual void setPaletteNormalizationMode(const PaletteNormalizationModeEnum::Enum mode); /** * Update coloring for all maps. * * @param paletteFile * Palette file containing palettes. */ virtual void updateScalarColoringForAllMaps(const PaletteFile* paletteFile); /** * Update coloring for a map. * * @param mapIndex * Index of map. * @param paletteFile * Palette file containing palettes. */ virtual void updateScalarColoringForMap(const int32_t mapIndex, const PaletteFile* paletteFile) = 0; virtual bool isPaletteColorMappingEqualForAllMaps() const; /** * @return The units for the 'interval' between two consecutive maps. */ virtual NiftiTimeUnitsEnum::Enum getMapIntervalUnits() const; /** * Get the units value for the first map and the * quantity of units between consecutive maps. If the * units for the maps is unknown, value of one (1) are * returned for both output values. * * @param firstMapUnitsValueOut * Output containing units value for first map. * @param mapIntervalStepValueOut * Output containing number of units between consecutive maps. */ virtual void getMapIntervalStartAndStep(float& firstMapUnitsValueOut, float& mapIntervalStepValueOut) const; /* documented in cxx file */ virtual bool getDataRangeFromAllMaps(float& dataRangeMinimumOut, float& dataRangeMaximumOut) const; /* documented in cxx file */ virtual bool isModifiedExcludingPaletteColorMapping() const; /* documented in cxx file. */ bool isModifiedPaletteColorMapping() const; /* documented in cxx file. */ bool isModified() const; LabelDrawingProperties* getLabelDrawingProperties(); const LabelDrawingProperties* getLabelDrawingProperties() const; protected: CaretMappableDataFile(const CaretMappableDataFile&); CaretMappableDataFile& operator=(const CaretMappableDataFile&); ChartDataCartesian* helpCreateCartesianChartData(const std::vector& data); void helpGetSupportedLineSeriesChartDataTypes(std::vector& chartDataTypesOut) const; virtual void saveFileDataToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass); virtual void restoreFileDataFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private: void copyCaretMappableDataFile(const CaretMappableDataFile&); CaretPointer m_labelDrawingProperties; PaletteNormalizationModeEnum::Enum m_paletteNormalizationMode; }; #ifdef __CARET_MAPPABLE_DATA_FILE_DECLARE__ // #endif // __CARET_MAPPABLE_DATA_FILE_DECLARE__ } // namespace #endif //__CARET_MAPPABLE_DATA_FILE__H_ workbench-1.1.1/src/Files/CaretSparseFile.cxx000066400000000000000000000337341255417355300211110ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #ifdef CARET_OS_WINDOWS #define MYSEEK _fseeki64 #define MYTELL _ftelli64 #else #define MYSEEK fseek #define MYTELL ftell #endif #include "CaretSparseFile.h" #include "ByteOrderEnum.h" #include "ByteSwapping.h" #include "CaretAssert.h" #include "FileInformation.h" #include #include using namespace caret; using namespace std; const char magic[] = "\0\0\0\0cst\0"; CaretSparseFile::CaretSparseFile() { m_file = NULL; } CaretSparseFile::CaretSparseFile(const AString& fileName) { m_file = NULL; readFile(fileName); } void CaretSparseFile::readFile(const AString& filename) { if (m_file != NULL) { fclose(m_file); m_file = NULL; } FileInformation fileInfo(filename); if (!fileInfo.exists()) throw DataFileException("file doesn't exist"); m_file = fopen(filename.toLocal8Bit().constData(), "rb"); if (m_file == NULL) throw DataFileException("error opening file"); char buf[8]; if (fread(buf, 1, 8, m_file) != 8) throw DataFileException("error reading from file"); for (int i = 0; i < 8; ++i) { if (buf[i] != magic[i]) throw DataFileException("file has the wrong magic string"); } if (fread(m_dims, sizeof(int64_t), 2, m_file) != 2) throw DataFileException("error reading from file"); if (ByteOrderEnum::isSystemBigEndian()) { ByteSwapping::swapBytes(m_dims, 2); } if (m_dims[0] < 1 || m_dims[1] < 1) throw DataFileException("both dimensions must be positive"); m_indexArray.resize(m_dims[1] + 1); vector lengthArray(m_dims[1]); if (fread(lengthArray.data(), sizeof(int64_t), m_dims[1], m_file) != (size_t)m_dims[1]) throw DataFileException("error reading from file"); if (ByteOrderEnum::isSystemBigEndian()) { ByteSwapping::swapBytes(lengthArray.data(), m_dims[1]); } m_indexArray[0] = 0; for (int64_t i = 0; i < m_dims[1]; ++i) { if (lengthArray[i] > m_dims[0] || lengthArray[i] < 0) throw DataFileException("impossible value found in length array"); m_indexArray[i + 1] = m_indexArray[i] + lengthArray[i]; } m_valuesOffset = 8 + 2 * sizeof(int64_t) + m_dims[1] * sizeof(int64_t); int64_t xml_offset = m_valuesOffset + m_indexArray[m_dims[1]] * 2 * sizeof(int64_t); if (xml_offset >= fileInfo.size()) throw DataFileException("file is truncated"); int64_t xml_length = fileInfo.size() - xml_offset; if (xml_length < 1) throw DataFileException("file is truncated"); if (MYSEEK(m_file, xml_offset, SEEK_SET) != 0) throw DataFileException("error seeking to XML"); const int64_t seekResult = MYTELL(m_file); if (seekResult != xml_offset) { const AString msg = ("Tried to seek to " + AString::number(xml_offset) + " but got an offset of " + AString::number(seekResult)); throw DataFileException(msg); } QByteArray myXMLBytes(xml_length, '\0'); if (fread(myXMLBytes.data(), 1, xml_length, m_file) != (size_t)xml_length) throw DataFileException("error reading from file"); m_xml.readXML(myXMLBytes); if (m_xml.getDimensionLength(CiftiXML::ALONG_ROW) != m_dims[0] || m_xml.getDimensionLength(CiftiXML::ALONG_COLUMN) != m_dims[1]) { throw DataFileException("cifti XML doesn't match dimensions of sparse file"); } } CaretSparseFile::~CaretSparseFile() { if (m_file != NULL) fclose(m_file); } void CaretSparseFile::getRow(const int64_t& index, int64_t* rowOut) { CaretAssert(index >= 0 && index < m_dims[1]); int64_t start = m_indexArray[index], end = m_indexArray[index + 1]; int64_t numToRead = (end - start) * 2; m_scratchArray.resize(numToRead); if (MYSEEK(m_file, m_valuesOffset + start * sizeof(int64_t) * 2, SEEK_SET) != 0) throw DataFileException("failed to seek in file"); if (fread(m_scratchArray.data(), sizeof(int64_t), numToRead, m_file) != (size_t)numToRead) throw DataFileException("error reading from file"); if (ByteOrderEnum::isSystemBigEndian()) { ByteSwapping::swapBytes(m_scratchArray.data(), numToRead); } int64_t curIndex = 0; for (int64_t i = 0; i < numToRead; i += 2) { int64_t index = m_scratchArray[i]; if (index < curIndex || index >= m_dims[0]) throw DataFileException("impossible index value found in file"); while (curIndex < index) { rowOut[curIndex] = 0; ++curIndex; } ++curIndex; rowOut[index] = m_scratchArray[i + 1]; } while (curIndex < m_dims[0]) { rowOut[curIndex] = 0; ++curIndex; } } void CaretSparseFile::getRowSparse(const int64_t& index, vector& indicesOut, vector& valuesOut) { CaretAssert(index >= 0 && index < m_dims[1]); int64_t start = m_indexArray[index], end = m_indexArray[index + 1]; int64_t numToRead = (end - start) * 2, numNonzero = end - start; m_scratchArray.resize(numToRead); if (MYSEEK(m_file, m_valuesOffset + start * sizeof(int64_t) * 2, SEEK_SET) != 0) throw DataFileException("failed to seek in file"); if (fread(m_scratchArray.data(), sizeof(int64_t), numToRead, m_file) != (size_t)numToRead) throw DataFileException("error reading from file"); if (ByteOrderEnum::isSystemBigEndian()) { ByteSwapping::swapBytes(m_scratchArray.data(), numToRead); } indicesOut.resize(numNonzero); valuesOut.resize(numNonzero); int64_t lastIndex = -1; for (int64_t i = 0; i < numNonzero; ++i) { indicesOut[i] = m_scratchArray[i * 2]; valuesOut[i] = m_scratchArray[i * 2 + 1]; if (indicesOut[i] <= lastIndex || indicesOut[i] >= m_dims[0]) throw DataFileException("impossible index value found in file"); lastIndex = indicesOut[i]; } } void CaretSparseFile::getFibersRow(const int64_t& index, FiberFractions* rowOut) { if (m_scratchRow.size() != (size_t)m_dims[0]) m_scratchRow.resize(m_dims[0]); getRow(index, (int64_t*)m_scratchRow.data()); for (int64_t i = 0; i < m_dims[0]; ++i) { if (m_scratchRow[i] == 0) { rowOut[i].zero(); } else { decodeFibers(m_scratchRow[i], rowOut[i]); } } } void CaretSparseFile::getFibersRowSparse(const int64_t& index, vector& indicesOut, vector& valuesOut) { getRowSparse(index, indicesOut, m_scratchSparseRow); size_t numNonzero = m_scratchSparseRow.size(); valuesOut.resize(numNonzero); for (size_t i = 0; i < numNonzero; ++i) { decodeFibers(((uint64_t*)m_scratchSparseRow.data())[i], valuesOut[i]); } } void CaretSparseFile::decodeFibers(const uint64_t& coded, FiberFractions& decoded) { decoded.fiberFractions.resize(3); decoded.totalCount = coded>>32; uint32_t temp = coded & ((1LL<<32) - 1); const static uint32_t MASK = ((1<<10) - 1); decoded.distance = (temp & MASK); decoded.fiberFractions[1] = ((temp>>10) & MASK) / 1000.0f; decoded.fiberFractions[0] = ((temp>>20) & MASK) / 1000.0f; decoded.fiberFractions[2] = 1.0f - decoded.fiberFractions[0] - decoded.fiberFractions[1]; if (decoded.fiberFractions[2] < -0.002f || (temp & (3<<30))) { throw DataFileException("error decoding value '" + AString::number(coded) + "' from workbench sparse trajectory file"); } if (decoded.fiberFractions[2] < 0.0f) decoded.fiberFractions[2] = 0.0f; } void FiberFractions::zero() { totalCount = 0; fiberFractions.clear(); distance = 0.0f; } CaretSparseFileWriter::CaretSparseFileWriter(const AString& fileName, const CiftiXML& xml) { m_file = NULL; m_finished = false; int64_t dimensions[2] = { xml.getDimensionLength(CiftiXML::ALONG_ROW), xml.getDimensionLength(CiftiXML::ALONG_COLUMN) }; if (dimensions[0] < 1 || dimensions[1] < 1) throw DataFileException("both dimensions must be positive"); m_xml = xml; m_dims[0] = dimensions[0];//CiftiXML doesn't support 3 dimensions yet, so we do this m_dims[1] = dimensions[1]; m_file = fopen(fileName.toLocal8Bit().constData(), "wb"); if (m_file == NULL) throw DataFileException("error opening file for writing"); if (fwrite(magic, 1, 8, m_file) != 8) throw DataFileException("error writing to file"); int64_t tempdims[2] = { m_dims[0], m_dims[1] }; if (ByteOrderEnum::isSystemBigEndian()) { ByteSwapping::swapBytes(tempdims, 2); } if (fwrite(tempdims, sizeof(int64_t), 2, m_file) != 2) throw DataFileException("error writing to file"); m_lengthArray.resize(m_dims[1], 0);//initialize the memory so that valgrind won't complain if (fwrite(m_lengthArray.data(), sizeof(uint64_t), m_dims[1], m_file) != (size_t)m_dims[1]) throw DataFileException("error writing to file");//write it to get the file to the correct length m_nextRowIndex = 0; m_valuesOffset = 8 + 2 * sizeof(int64_t) + m_dims[1] * sizeof(int64_t); } void CaretSparseFileWriter::writeRow(const int64_t& index, const int64_t* row) { CaretAssert(index < m_dims[1]); CaretAssert(index >= m_nextRowIndex); while (m_nextRowIndex < index) { m_lengthArray[m_nextRowIndex] = 0; ++m_nextRowIndex; } m_scratchArray.clear(); int64_t count = 0; for (int64_t i = 0; i < m_dims[0]; ++i) { if (row[i] != 0) { m_scratchArray.push_back(i); m_scratchArray.push_back(row[i]); ++count; } } m_lengthArray[index] = count; if (ByteOrderEnum::isSystemBigEndian()) { ByteSwapping::swapBytes(m_scratchArray.data(), m_scratchArray.size()); } if (fwrite(m_scratchArray.data(), sizeof(int64_t), m_scratchArray.size(), m_file) != m_scratchArray.size()) throw DataFileException("error writing to file"); m_nextRowIndex = index + 1; if (m_nextRowIndex == m_dims[1]) finish(); } void CaretSparseFileWriter::writeRowSparse(const int64_t& index, const vector& indices, const vector& values) { CaretAssert(index < m_dims[1]); CaretAssert(index >= m_nextRowIndex); CaretAssert(indices.size() == values.size()); while (m_nextRowIndex < index) { m_lengthArray[m_nextRowIndex] = 0; ++m_nextRowIndex; } m_scratchArray.clear(); size_t numNonzero = indices.size();//assume no zeros m_lengthArray[index] = numNonzero; int64_t lastIndex = -1; for (size_t i = 0; i < numNonzero; ++i) { if (indices[i] <= lastIndex || indices[i] >= m_dims[0]) throw DataFileException("indices must be sorted when writing sparse rows"); lastIndex = indices[i]; m_scratchArray.push_back(indices[i]); m_scratchArray.push_back(values[i]); } if (ByteOrderEnum::isSystemBigEndian()) { ByteSwapping::swapBytes(m_scratchArray.data(), m_scratchArray.size()); } if (fwrite(m_scratchArray.data(), sizeof(int64_t), m_scratchArray.size(), m_file) != m_scratchArray.size()) throw DataFileException("error writing to file"); m_nextRowIndex = index + 1; if (m_nextRowIndex == m_dims[1]) finish(); } void CaretSparseFileWriter::writeFibersRow(const int64_t& index, const FiberFractions* row) { if (m_scratchRow.size() != (size_t)m_dims[0]) m_scratchRow.resize(m_dims[0]); for (int64_t i = 0; i < m_dims[0]; ++i) { if (row[i].totalCount == 0) { m_scratchRow[i] = 0; } else { encodeFibers(row[i], m_scratchRow[i]); } } writeRow(index, (int64_t*)m_scratchRow.data()); } void CaretSparseFileWriter::writeFibersRowSparse(const int64_t& index, const vector& indices, const vector& values) { size_t numNonzero = values.size();//assume no zeros m_scratchSparseRow.resize(numNonzero); for (size_t i = 0; i < numNonzero; ++i) { encodeFibers(values[i], ((uint64_t*)m_scratchSparseRow.data())[i]); } writeRowSparse(index, indices, m_scratchSparseRow); } void CaretSparseFileWriter::finish() { if (m_finished) return; m_finished = true; while (m_nextRowIndex < m_dims[1]) { m_lengthArray[m_nextRowIndex] = 0; ++m_nextRowIndex; } QByteArray myXMLBytes = m_xml.writeXMLToQByteArray(); if (fwrite(myXMLBytes.constData(), 1, myXMLBytes.size(), m_file) != (size_t)myXMLBytes.size()) throw DataFileException("error writing to file"); if (MYSEEK(m_file, 8 + 2 * sizeof(int64_t), SEEK_SET) != 0) throw DataFileException("error seeking in file"); if (ByteOrderEnum::isSystemBigEndian()) { ByteSwapping::swapBytes(m_lengthArray.data(), m_lengthArray.size()); } if (fwrite(m_lengthArray.data(), sizeof(uint64_t), m_lengthArray.size(), m_file) != m_lengthArray.size()) throw DataFileException("error writing to file"); int ret = fclose(m_file); m_file = NULL; if (ret != 0) throw DataFileException("error closing file"); } CaretSparseFileWriter::~CaretSparseFileWriter() { finish(); } void CaretSparseFileWriter::encodeFibers(const FiberFractions& orig, uint64_t& coded) { coded = (((uint64_t)orig.totalCount)<<32) | (myclamp(orig.fiberFractions[0] * 1000.0f + 0.5f)<<20) | (myclamp(orig.fiberFractions[1] * 1000.0f + 0.5f)<<10) | (myclamp(orig.distance)); } uint32_t CaretSparseFileWriter::myclamp(const int& x) { if (x >= 1000) return 1000; if (x <= 0) return 0; return x; } workbench-1.1.1/src/Files/CaretSparseFile.h000066400000000000000000000103461255417355300205300ustar00rootroot00000000000000#ifndef __CARET_SPARSE_FILE_H__ #define __CARET_SPARSE_FILE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "stdint.h" #include "AString.h" #include "DataFile.h" #include "DataFileException.h" #include "CiftiXML.h" namespace caret { struct FiberFractions { uint32_t totalCount; // total number of streamline that go through the voxel std::vector fiberFractions; // fraction of totalCount for each fiber (3 orientation for now, could be zero) float distance; // average distance from seed across all streamlines void zero(); }; class CaretSparseFile /* : public DataFile */ { static void decodeFibers(const uint64_t& coded, FiberFractions& decoded);//takes a uint because right shift on signed is implementation dependent FILE* m_file; int64_t m_dims[2], m_valuesOffset; std::vector m_indexArray, m_scratchRow; std::vector m_scratchArray, m_scratchSparseRow; CaretSparseFile(const CaretSparseFile& rhs); CiftiXML m_xml; public: const int64_t* getDimensions() { return m_dims; } CaretSparseFile(); virtual void readFile(const AString& filename); virtual void writeFile(const AString&) { throw DataFileException("writeFile not implemented for CaretSparseFile"); } CaretSparseFile(const AString& fileName); ///get a reference to the XML data const CiftiXML& getCiftiXML() const { return m_xml; } void getRow(const int64_t& index, int64_t* rowOut); void getRowSparse(const int64_t& index, std::vector& indicesOut, std::vector& valuesOut); void getFibersRow(const int64_t& index, FiberFractions* rowOut); void getFibersRowSparse(const int64_t& index, std::vector& indicesOut, std::vector& valuesOut); virtual ~CaretSparseFile(); }; class CaretSparseFileWriter { static void encodeFibers(const FiberFractions& orig, uint64_t& coded); static uint32_t myclamp(const int& x); FILE* m_file; int64_t m_dims[2], m_valuesOffset, m_nextRowIndex; bool m_finished; std::vector m_lengthArray, m_scratchRow; std::vector m_scratchArray, m_scratchSparseRow; CaretSparseFileWriter(const CaretSparseFileWriter& rhs); CiftiXML m_xml; public: CaretSparseFileWriter(const AString& fileName, const CiftiXML& xml); ~CaretSparseFileWriter(); ///you must write the rows in order, though you can skip empty rows void writeRow(const int64_t& index, const int64_t* row); ///you must write the rows in order, though you can skip empty rows void writeRowSparse(const int64_t& index, const std::vector& indices, const std::vector& values); ///you must write the rows in order, though you can skip empty rows void writeFibersRow(const int64_t& index, const FiberFractions* row); ///you must write the rows in order, though you can skip empty rows void writeFibersRowSparse(const int64_t& index, const std::vector& indices, const std::vector& values); ///call this if no rows remain to be written void finish(); }; } #endif //__CARET_SPARSE_FILE_H__ workbench-1.1.1/src/Files/CaretVolumeExtension.cxx000066400000000000000000000343311255417355300222120ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretVolumeExtension.h" #include "XmlSaxParser.h" #include "GiftiXmlElements.h" #include "PaletteColorMapping.h" #include "PaletteColorMappingSaxReader.h" #include "PaletteColorMappingXmlElements.h" #include "CaretLogger.h" #include using namespace caret; using namespace std; //where should these go? static const AString CARET_VOL_EXT_ROOT = "CaretExtension"; static const AString CARET_VOL_EXT_COMMENT = "Comment"; static const AString CARET_VOL_EXT_DATE = "Date"; static const AString CARET_VOL_EXT_VOL_INFO = "VolumeInformation"; static const AString CARET_VOL_EXT_VI_COMMENT = "Comment"; static const AString CARET_VOL_EXT_VI_GUI_LABEL = "GuiLabel"; static const AString CARET_VOL_EXT_VI_STUDY_META_SET = "StudyMetaDataLinkSet"; static const AString CARET_VOL_EXT_VI_STUDY_META_LINK = "StudyMetaDataLink"; static const AString CARET_VOL_EXT_VI_TYPE = "VolumeType"; void CaretVolumeExtension::clear() { m_comment.clear(); m_date.clear(); m_attributes.clear(); } void CaretVolumeExtension::readFromXmlString(const AString& s) { CaretVolumeExtensionXMLReader myReader(this); CaretPointer myParser(XmlSaxParser::createXmlParser()); try { myParser->parseString(s, &myReader); } catch (XmlSaxParserException& e) { CaretLogWarning(AString("Failed to parse caret volume extension: ") + e.whatString()); } } void CaretVolumeExtension::writeAsXML(XmlWriter& xmlWriter) { xmlWriter.writeStartDocument("1.0"); xmlWriter.writeStartElement(CARET_VOL_EXT_ROOT); if (!m_comment.isEmpty()) xmlWriter.writeElementCData(CARET_VOL_EXT_COMMENT, m_comment); time_t mytime = time(NULL);//we don't have a class to deal with ISO 8601 dates, so use some C struct tm* timeinfo = localtime(&mytime);//note: this is a pointer to a static global in C library code, don't try to delete char buf[101];//we actually only need 20 bytes, but hey strftime(buf, 100, "%Y-%m-%dT%H:%M:%S", timeinfo); xmlWriter.writeElementCData(CARET_VOL_EXT_DATE, AString(buf)); int numVols = (int)m_attributes.size(); for (int i = 0; i < numVols; ++i) { m_attributes[i]->writeAsXML(xmlWriter, i); } xmlWriter.writeEndElement();//just to make it clean xmlWriter.writeEndDocument();//so, this just flushes } void SubvolumeAttributes::writeAsXML(XmlWriter& xmlWriter, int index) { XmlAttributes myattrs; myattrs.addAttribute("Index", AString::number(index)); xmlWriter.writeStartElement(CARET_VOL_EXT_VOL_INFO, myattrs); if (!m_comment.isEmpty()) xmlWriter.writeElementCData(CARET_VOL_EXT_VI_COMMENT, m_comment); if (!m_guiLabel.isEmpty()) xmlWriter.writeElementCData(CARET_VOL_EXT_VI_GUI_LABEL, m_guiLabel); if (m_labelTable != NULL) m_labelTable->writeAsXML(xmlWriter);//expect the extension to not have stuff it doesn't need, so just write everything it has m_studyMetadata.writeAsXML(xmlWriter); if (m_palette != NULL) m_palette->writeAsXML(xmlWriter); AString typeString; switch (m_type) { case ANATOMY: typeString = "Anatomy"; break; case FUNCTIONAL: typeString = "Functional"; break; case LABEL: typeString = "Label"; break; case RGB: typeString = "RGB"; break; case SEGMENTATION: typeString = "Segmentation"; break; case VECTOR: typeString = "Vector"; break; default: typeString = "Unknown"; } xmlWriter.writeElementCData(CARET_VOL_EXT_VI_TYPE, typeString); xmlWriter.writeEndElement(); } void StudyMetadataLinkSet::writeAsXML(XmlWriter& xmlWriter) {//TODO: something xmlWriter.writeStartElement(CARET_VOL_EXT_VI_STUDY_META_SET); xmlWriter.writeEndElement(); } CaretVolumeExtensionXMLReader::CaretVolumeExtensionXMLReader(CaretVolumeExtension* toFill): XmlSaxParserHandlerInterface() { CaretAssert(toFill != NULL); m_toFill = toFill; m_viIndex = -1; } void CaretVolumeExtensionXMLReader::characters(const char* ch) { CaretAssert(m_charDataStack.size() != 0); CaretAssert(m_stateStack.size() != 0); switch (m_stateStack.back()) { case LABEL_TABLE: CaretAssert(m_labelReader != NULL); m_labelReader->characters(ch); break; case PALETTE_COLOR_MAPPING: CaretAssert(m_paletteReader != NULL); m_paletteReader->characters(ch); break; default: m_charDataStack.back() += ch; } } void CaretVolumeExtensionXMLReader::endDocument() { if (m_stateStack.size() != 0) { throw XmlSaxParserException("end of document while still in an element state"); } } void CaretVolumeExtensionXMLReader::endElement(const AString& namespaceURI, const AString& localName, const AString& qualifiedName) { CaretAssert(m_charDataStack.size() != 0); CaretAssert(m_stateStack.size() != 0); AString elemCharData = m_charDataStack.back(); State myState = m_stateStack.back(); bool popState = true; switch (myState) { case INVALID: throw XmlSaxParserException("encountered end element in INVALID state"); break; case CARET_EXTENSION: break; case ROOT_COMMENT: m_toFill->m_comment = elemCharData; break; case DATE: m_toFill->m_date = elemCharData; break; case VOLUME_INFORMATION: m_viIndex = -1; break; case VI_COMMENT: CaretAssertVectorIndex(m_toFill->m_attributes, m_viIndex); m_toFill->m_attributes[m_viIndex]->m_comment = elemCharData; break; case GUI_LABEL: CaretAssertVectorIndex(m_toFill->m_attributes, m_viIndex); m_toFill->m_attributes[m_viIndex]->m_guiLabel = elemCharData; break; case LABEL_TABLE: CaretAssert(m_labelReader != NULL); m_labelReader->endElement(namespaceURI, localName, qualifiedName); if (qualifiedName == GiftiXmlElements::TAG_LABEL_TABLE) { m_labelReader->endDocument(); m_labelReader.grabNew(NULL);//make it delete now so that it can't make related bugs more confusing } else { popState = false; } break; case STUDY_META_DATA_LINK_SET: if (qualifiedName == CARET_VOL_EXT_VI_STUDY_META_SET) {//TODO: something } else { popState = false; } break; case PALETTE_COLOR_MAPPING: CaretAssert(m_paletteReader != NULL); m_paletteReader->endElement(namespaceURI, localName, qualifiedName); if (qualifiedName == PaletteColorMappingXmlElements::XML_TAG_PALETTE_COLOR_MAPPING) { m_paletteReader->endDocument(); m_paletteReader.grabNew(NULL);//ditto } else { popState = false; } break; case VOLUME_TYPE: CaretAssertVectorIndex(m_toFill->m_attributes, m_viIndex); if (elemCharData == "Anatomy") { m_toFill->m_attributes[m_viIndex]->m_type = SubvolumeAttributes::ANATOMY; } else if (elemCharData == "Functional") { m_toFill->m_attributes[m_viIndex]->m_type = SubvolumeAttributes::FUNCTIONAL; } else if (elemCharData == "Label") { m_toFill->m_attributes[m_viIndex]->m_type = SubvolumeAttributes::LABEL; } else if (elemCharData == "RGB") { m_toFill->m_attributes[m_viIndex]->m_type = SubvolumeAttributes::RGB; } else if (elemCharData == "Segmentation") { m_toFill->m_attributes[m_viIndex]->m_type = SubvolumeAttributes::SEGMENTATION; } else if (elemCharData == "Vector") { m_toFill->m_attributes[m_viIndex]->m_type = SubvolumeAttributes::VECTOR; } else { m_toFill->m_attributes[m_viIndex]->m_type = SubvolumeAttributes::UNKNOWN; } break; } if (popState) { m_stateStack.pop_back(); m_charDataStack.pop_back(); } } void CaretVolumeExtensionXMLReader::error(const XmlSaxParserException& exception) { CaretLogWarning(AString("encountered non-fatal XML error in CaretVolumeExtension: ") + exception.whatString()); } void CaretVolumeExtensionXMLReader::fatalError(const XmlSaxParserException& exception) {//all of our members are self-deleting, no worries, just throw throw XmlSaxParserException(exception);//throw a copy of it rather than the original reference, not sure if it matters } void CaretVolumeExtensionXMLReader::startDocument() { } void CaretVolumeExtensionXMLReader::startElement(const AString& uri, const AString& localName, const AString& qName, const XmlAttributes& atts) { bool addState = true; State nextState = INVALID; AString invalidInfo = qName; if(m_stateStack.size() == 0) { if (qName != CARET_VOL_EXT_ROOT) { throw XmlSaxParserException(AString("CaretVolumeExtension encountered unexpected root element: ") + qName); } nextState = CARET_EXTENSION; } else { switch (m_stateStack.back()) { case INVALID://should NEVER happen, INVALID throws instead of pushing, so should never be on the stack throw XmlSaxParserException("something has gone wrong in the CaretVolumeExtension parser"); break; case ROOT_COMMENT://these should not have child elements, let INVALID catch them case DATE: case VI_COMMENT: case GUI_LABEL: case VOLUME_TYPE: break; case CARET_EXTENSION: if (qName == CARET_VOL_EXT_COMMENT) { nextState = ROOT_COMMENT; } else if (qName == CARET_VOL_EXT_DATE) { nextState = DATE; } else if (qName == CARET_VOL_EXT_VOL_INFO) { nextState = VOLUME_INFORMATION; m_viIndex = atts.getValueAsInt("Index"); if (m_viIndex < 0) { throw XmlSaxParserException("negative number encountered in VolumeInformation index"); } if ((int)m_toFill->m_attributes.size() <= m_viIndex) { m_toFill->m_attributes.resize(m_viIndex + 1);//don't worry, CaretPointer copy is relatively cheap } m_toFill->m_attributes[m_viIndex].grabNew(new SubvolumeAttributes()); }//anything else gets caught in INVALID below break; case VOLUME_INFORMATION: if (qName == CARET_VOL_EXT_VI_COMMENT) { nextState = VI_COMMENT; } else if (qName == CARET_VOL_EXT_VI_GUI_LABEL) { nextState = GUI_LABEL; } else if (qName == GiftiXmlElements::TAG_LABEL_TABLE) { nextState = LABEL_TABLE; CaretAssertVectorIndex(m_toFill->m_attributes, m_viIndex); m_toFill->m_attributes[m_viIndex]->m_labelTable.grabNew(new GiftiLabelTable()); m_labelReader.grabNew(new GiftiLabelTableSaxReader(m_toFill->m_attributes[m_viIndex]->m_labelTable)); m_labelReader->startDocument(); m_labelReader->startElement(uri, localName, qName, atts); } else if (qName == CARET_VOL_EXT_VI_STUDY_META_SET) { nextState = STUDY_META_DATA_LINK_SET;//TODO: something } else if (qName == PaletteColorMappingXmlElements::XML_TAG_PALETTE_COLOR_MAPPING) { nextState = PALETTE_COLOR_MAPPING; CaretAssertVectorIndex(m_toFill->m_attributes, m_viIndex); m_toFill->m_attributes[m_viIndex]->m_palette.grabNew(new PaletteColorMapping); m_paletteReader.grabNew(new PaletteColorMappingSaxReader(m_toFill->m_attributes[m_viIndex]->m_palette)); m_paletteReader->startDocument(); m_paletteReader->startElement(uri, localName, qName, atts); } else if (qName == CARET_VOL_EXT_VI_TYPE) { nextState = VOLUME_TYPE; } break; case LABEL_TABLE: addState = false; CaretAssert(m_labelReader != NULL); m_labelReader->startElement(uri, localName, qName, atts); break; case STUDY_META_DATA_LINK_SET: addState = false;//TODO: something break; case PALETTE_COLOR_MAPPING: addState = false; CaretAssert(m_paletteReader != NULL); m_paletteReader->startElement(uri, localName, qName, atts); break; } } if (addState) { if (nextState == INVALID) { throw XmlSaxParserException(AString("CaretVolumeExtension encountered an unexpected element: ") + invalidInfo); } m_stateStack.push_back(nextState); m_charDataStack.push_back(AString()); } } void CaretVolumeExtensionXMLReader::warning(const caret::XmlSaxParserException& exception) { CaretLogWarning(AString("encountered XML warning in CaretVolumeExtension: ") + exception.whatString()); } workbench-1.1.1/src/Files/CaretVolumeExtension.h000066400000000000000000000101271255417355300216340ustar00rootroot00000000000000#ifndef __CARET_VOLUME_EXTENSION__ #define __CARET_VOLUME_EXTENSION__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "GiftiLabelTable.h" #include "PaletteColorMapping.h" #include "CaretPointer.h" #include #include "XmlWriter.h" #include "XmlSaxParserHandlerInterface.h" #include "PaletteColorMappingSaxReader.h" #include "GiftiLabelTableSaxReader.h" namespace caret { struct StudyMetadataLinkSet { void writeAsXML(XmlWriter& xmlWriter); };//TODO: make this do something useful struct SubvolumeAttributes { enum VolumeType { UNKNOWN, ANATOMY, FUNCTIONAL, LABEL, RGB, SEGMENTATION, VECTOR };//TODO: make this into a caret enum class? AString m_comment; AString m_guiLabel; CaretPointer m_labelTable; StudyMetadataLinkSet m_studyMetadata; CaretPointer m_palette; VolumeType m_type; SubvolumeAttributes() { m_type = UNKNOWN; } void writeAsXML(XmlWriter& xmlWriter, int index); }; struct CaretVolumeExtension { AString m_comment; AString m_date;//TODO: make a class to handle ISO-8601 dates std::vector > m_attributes; void writeAsXML(XmlWriter& xmlWriter); void readFromXmlString(const AString& s); void clear(); }; class CaretVolumeExtensionXMLReader : public XmlSaxParserHandlerInterface { enum State { INVALID, CARET_EXTENSION, ROOT_COMMENT, DATE, VOLUME_INFORMATION, VI_COMMENT, GUI_LABEL, LABEL_TABLE, STUDY_META_DATA_LINK_SET, PALETTE_COLOR_MAPPING, VOLUME_TYPE }; std::vector m_stateStack; CaretVolumeExtension* m_toFill; std::vector m_charDataStack; int m_viIndex; CaretPointer m_paletteReader; CaretPointer m_labelReader; CaretVolumeExtensionXMLReader();//disallow default construction CaretVolumeExtensionXMLReader(const CaretVolumeExtensionXMLReader&);//disallow copy CaretVolumeExtensionXMLReader& operator=(const CaretVolumeExtensionXMLReader&);//disallow assignment public: CaretVolumeExtensionXMLReader(CaretVolumeExtension* toFill); virtual void startElement(const AString& uri, const AString& localName, const AString& qName, const XmlAttributes& atts) ; virtual void endElement(const AString& namespaceURI, const AString& localName, const AString& qualifiedName) ; virtual void characters(const char* ch); virtual void warning(const XmlSaxParserException& exception); virtual void error(const XmlSaxParserException& exception); virtual void fatalError(const XmlSaxParserException& exception); virtual void startDocument(); virtual void endDocument(); }; } #endif //__CARET_VOLUME_EXTENSION__ workbench-1.1.1/src/Files/ChartableLineSeriesBrainordinateInterface.h000066400000000000000000000077011255417355300257100ustar00rootroot00000000000000#ifndef __CHARTABLE_BRAINORDINATE_INTERFACE_H__ #define __CHARTABLE_BRAINORDINATE_INTERFACE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "ChartableLineSeriesInterface.h" #include "StructureEnum.h" namespace caret { class ChartDataCartesian; /** * \class caret::ChartableLineSeriesBrainordinateInterface * \brief Interface for files that are able to produce line series brainordinate charts. * \ingroup Files */ class ChartableLineSeriesBrainordinateInterface : public ChartableLineSeriesInterface { protected: ChartableLineSeriesBrainordinateInterface() { } virtual ~ChartableLineSeriesBrainordinateInterface() { } public: /** * Load charting data for the surface with the given structure and node index. * * @param structure * The surface's structure. * @param nodeIndex * Index of the node. * @return * Pointer to the chart data. If the data FAILED to load, * the returned pointer will be NULL. Caller takes ownership * of the pointer and must delete it when no longer needed. */ virtual ChartDataCartesian* loadLineSeriesChartDataForSurfaceNode(const StructureEnum::Enum structure, const int32_t nodeIndex) = 0; /** * Load average charting data for the surface with the given structure and node indices. * * @param structure * The surface's structure. * @param nodeIndices * Indices of the node. * @return * Pointer to the chart data. If the data FAILED to load, * the returned pointer will be NULL. Caller takes ownership * of the pointer and must delete it when no longer needed. */ virtual ChartDataCartesian* loadAverageLineSeriesChartDataForSurfaceNodes(const StructureEnum::Enum structure, const std::vector& nodeIndices) = 0; /** * Load charting data for the voxel enclosing the given coordinate. * * @param xyz * Coordinate of voxel. * @return * Pointer to the chart data. If the data FAILED to load, * the returned pointer will be NULL. Caller takes ownership * of the pointer and must delete it when no longer needed. */ virtual ChartDataCartesian* loadLineSeriesChartDataForVoxelAtCoordinate(const float xyz[3]) = 0; private: // ChartableLineSeriesBrainordinateInterface(const ChartableLineSeriesBrainordinateInterface&); // // ChartableLineSeriesBrainordinateInterface& operator=(const ChartableLineSeriesBrainordinateInterface&); public: // ADD_NEW_METHODS_HERE private: // ADD_NEW_MEMBERS_HERE }; #ifdef __CHARTABLE_BRAINORDINATE_INTERFACE_DECLARE__ // #endif // __CHARTABLE_BRAINORDINATE_INTERFACE_DECLARE__ } // namespace #endif //__CHARTABLE_BRAINORDINATE_INTERFACE_H__ workbench-1.1.1/src/Files/ChartableLineSeriesInterface.cxx000066400000000000000000000050501255417355300235540ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CHARTABLE_LINE_SERIES_INTERFACE_DECLARE__ #include "ChartableLineSeriesInterface.h" #undef __CHARTABLE_LINE_SERIES_INTERFACE_DECLARE__ #include "CaretMappableDataFile.h" using namespace caret; /** * \class caret::ChartableLineSeriesInterface * \brief Interface for files that are able to produce line charts charts. * \ingroup Files */ /** * Is the given chart data type supported by this file. * * @param chartDataType * Chart data type for testing support. * @return * True if chart data type is supported by the file, else false. */ bool ChartableLineSeriesInterface::isLineSeriesChartDataTypeSupported(const ChartDataTypeEnum::Enum chartDataType) const { std::vector validTypes; getSupportedLineSeriesChartDataTypes(validTypes); if (std::find(validTypes.begin(), validTypes.end(), chartDataType) != validTypes.end()) { return true; } return false; } /** * @return The CaretMappableDataFile that implements this interface. * Will be NULL if this interface is not implemented by a CaretMappableDataFile. */ CaretMappableDataFile* ChartableLineSeriesInterface::getLineSeriesChartCaretMappableDataFile() { CaretMappableDataFile* cmdf = dynamic_cast(this); CaretAssert(cmdf); return cmdf; } /** * @return The CaretMappableDataFile that implements this interface. * Will be NULL if this interface is not implemented by a CaretMappableDataFile. */ const CaretMappableDataFile* ChartableLineSeriesInterface::getLineSeriesChartCaretMappableDataFile() const { const CaretMappableDataFile* cmdf = dynamic_cast(this); CaretAssert(cmdf); return cmdf; } workbench-1.1.1/src/Files/ChartableLineSeriesInterface.h000066400000000000000000000065341255417355300232110ustar00rootroot00000000000000#ifndef __CHARTABLE_LINE_SERIES_INTERFACE_H__ #define __CHARTABLE_LINE_SERIES_INTERFACE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "ChartDataTypeEnum.h" #include "StructureEnum.h" namespace caret { //class CaretMappableDataFile; class ChartDataCartesian; class CaretMappableDataFile; class ChartableLineSeriesInterface { protected: ChartableLineSeriesInterface() { } virtual ~ChartableLineSeriesInterface() { } public: /** * @return The CaretMappableDataFile that implements this interface. */ virtual CaretMappableDataFile* getLineSeriesChartCaretMappableDataFile(); /** * @return The CaretMappableDataFile that implements this interface. */ virtual const CaretMappableDataFile* getLineSeriesChartCaretMappableDataFile() const; /** * @return Is charting enabled for this file in the given tab? */ virtual bool isLineSeriesChartingEnabled(const int32_t tabIndex) const = 0; /** * @return Return true if the file's current state supports * charting data, else false. Typically a brainordinate file * is chartable if it contains more than one map. */ virtual bool isLineSeriesChartingSupported() const = 0; /** * Set charting enabled for this file in the given tab * * @param enabled * New status for charting enabled. */ virtual void setLineSeriesChartingEnabled(const int32_t tabIndex, const bool enabled) = 0; /** * Get chart data types supported by the file. * * @param chartDataTypesOut * Chart types supported by this file. */ virtual void getSupportedLineSeriesChartDataTypes(std::vector& chartDataTypesOut) const = 0; bool isLineSeriesChartDataTypeSupported(const ChartDataTypeEnum::Enum chartDataType) const; private: // ChartableLineSeriesInterface(const ChartableLineSeriesInterface&); // // ChartableLineSeriesInterface& operator=(const ChartableLineSeriesInterface&); public: // ADD_NEW_METHODS_HERE private: // ADD_NEW_MEMBERS_HERE }; #ifdef __CHARTABLE_LINE_SERIES_INTERFACE_DECLARE__ // #endif // __CHARTABLE_LINE_SERIES_INTERFACE_DECLARE__ } // namespace #endif //__CHARTABLE_LINE_SERIES_INTERFACE_H__ workbench-1.1.1/src/Files/ChartableLineSeriesRowColumnInterface.h000066400000000000000000000056721255417355300250610ustar00rootroot00000000000000#ifndef __CHARTABLE_LINE_SERIES_ROW_COLUMN_INTERFACE_H__ #define __CHARTABLE_LINE_SERIES_ROW_COLUMN_INTERFACE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "ChartableLineSeriesInterface.h" namespace caret { class ChartDataCartesian; /** * \class caret::ChartableLineSeriesRowColumnInterface * \brief Interface for charts that load a row and/or column of data. * \ingroup Files */ class ChartableLineSeriesRowColumnInterface : public ChartableLineSeriesInterface { public: ChartableLineSeriesRowColumnInterface() { } virtual ~ChartableLineSeriesRowColumnInterface() { }; /** * Load charting data for the given column index. * * @param columnIndex * Index of the column. * @return * Pointer to the chart data. If the data FAILED to load, * the returned pointer will be NULL. Caller takes ownership * of the pointer and must delete it when no longer needed. */ virtual ChartDataCartesian* loadLineSeriesChartDataForColumn(const int32_t columnIndex) = 0; /** * Load charting data for the given row index. * * @param rowIndex * Index of the row. * @return * Pointer to the chart data. If the data FAILED to load, * the returned pointer will be NULL. Caller takes ownership * of the pointer and must delete it when no longer needed. */ virtual ChartDataCartesian* loadLineSeriesChartDataForRow(const int32_t rowIndex) = 0; // ADD_NEW_METHODS_HERE private: ChartableLineSeriesRowColumnInterface(const ChartableLineSeriesRowColumnInterface&); ChartableLineSeriesRowColumnInterface& operator=(const ChartableLineSeriesRowColumnInterface&); // ADD_NEW_MEMBERS_HERE }; #ifdef __CHARTABLE_LINE_SERIES_ROW_COLUMN_INTERFACE_DECLARE__ // #endif // __CHARTABLE_LINE_SERIES_ROW_COLUMN_INTERFACE_DECLARE__ } // namespace #endif //__CHARTABLE_LINE_SERIES_ROW_COLUMN_INTERFACE_H__ workbench-1.1.1/src/Files/ChartableMatrixInterface.cxx000066400000000000000000000064121255417355300227610ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CHARTABLE_MATRIX_INTERFACE_DECLARE__ #include "ChartableMatrixInterface.h" #undef __CHARTABLE_MATRIX_INTERFACE_DECLARE__ #include "CaretMappableDataFile.h" #include "CiftiMappableDataFile.h" using namespace caret; /** * \class caret::ChartableMatrixInterface * \brief Interface for files that are able to produce charts. * \ingroup Files */ /** * Is the given chart data type supported by this file. * * @param chartDataType * Chart data type for testing support. * @return * True if chart data type is supported by the file, else false. */ bool ChartableMatrixInterface::isMatrixChartDataTypeSupported(const ChartDataTypeEnum::Enum chartDataType) const { std::vector validTypes; getSupportedMatrixChartDataTypes(validTypes); if (std::find(validTypes.begin(), validTypes.end(), chartDataType) != validTypes.end()) { return true; } return false; } /** * @return The CaretMappableDataFile that implements this interface. * Will be NULL if this interface is not implemented by a CaretMappableDataFile. */ CaretMappableDataFile* ChartableMatrixInterface::getMatrixChartCaretMappableDataFile() { CaretMappableDataFile* cmdf = dynamic_cast(this); CaretAssert(cmdf); return cmdf; } /** * @return The CaretMappableDataFile that implements this interface. * Will be NULL if this interface is not implemented by a CaretMappableDataFile. */ const CaretMappableDataFile* ChartableMatrixInterface::getMatrixChartCaretMappableDataFile() const { const CaretMappableDataFile* cmdf = dynamic_cast(this); CaretAssert(cmdf); return cmdf; } /** * @return The CaretMappableDataFile that implements this interface. * Will be NULL if this interface is not implemented by a CaretMappableDataFile. */ CiftiMappableDataFile* ChartableMatrixInterface::getMatrixChartCiftiMappableDataFile() { CiftiMappableDataFile* cmdf = dynamic_cast(this); CaretAssert(cmdf); return cmdf; } /** * @return The CaretMappableDataFile that implements this interface. * Will be NULL if this interface is not implemented by a CaretMappableDataFile. */ const CiftiMappableDataFile* ChartableMatrixInterface::getMatrixChartCiftiMappableDataFile() const { const CiftiMappableDataFile* cmdf = dynamic_cast(this); CaretAssert(cmdf); return cmdf; } workbench-1.1.1/src/Files/ChartableMatrixInterface.h000066400000000000000000000147031255417355300224100ustar00rootroot00000000000000#ifndef __CHARTABLE_MATRIX_INTERFACE_H__ #define __CHARTABLE_MATRIX_INTERFACE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretColorEnum.h" #include "ChartDataTypeEnum.h" #include "ChartMatrixLoadingDimensionEnum.h" #include "CiftiParcelColoringModeEnum.h" #include "YokingGroupEnum.h" namespace caret { class CaretMappableDataFile; class ChartMatrixDisplayProperties; class CiftiMappableDataFile; class CiftiParcelsMap; class ChartableMatrixInterface { protected: ChartableMatrixInterface() { } virtual ~ChartableMatrixInterface() { } public: /** * Get the matrix dimensions. * * @param numberOfRowsOut * Number of rows in the matrix. * @param numberOfColumnsOut * Number of rows in the matrix. */ virtual void getMatrixDimensions(int32_t& numberOfRowsOut, int32_t& numberOfColumnsOut) const = 0; /** * Get the matrix RGBA coloring for this matrix data creator. * * @param numberOfRowsOut * Number of rows in the coloring matrix. * @param numberOfColumnsOut * Number of rows in the coloring matrix. * @param rgbaOut * RGBA coloring output with number of elements * (numberOfRowsOut * numberOfColumnsOut * 4). * @return * True if data output data is valid, else false. */ virtual bool getMatrixDataRGBA(int32_t& numberOfRowsOut, int32_t& numberOfColumnsOut, std::vector& rgbaOut) const = 0; /** * Get the value, row name, and column name for a cell in the matrix. * * @param rowIndex * The row index. * @param columnIndex * The column index. * @param cellValueOut * Output containing value in the cell. * @param rowNameOut * Name of row corresponding to row index. * @param columnNameOut * Name of column corresponding to column index. * @return * True if the output values are valid (valid row/column indices). */ virtual bool getMatrixCellAttributes(const int32_t rowIndex, const int32_t columnIndex, AString& cellValueOut, AString& rowNameOut, AString& columnNameOut) const = 0; /** * @return The matrix display properties for the given tab. * @param tabIndex * Index of tab. */ virtual const ChartMatrixDisplayProperties* getChartMatrixDisplayProperties(const int32_t tabIndex) const = 0; /** * @return The matrix display properties for the given tab. * @param tabIndex * Index of tab. */ virtual ChartMatrixDisplayProperties* getChartMatrixDisplayProperties(const int32_t tabIndex) = 0; /** * @return The CaretMappableDataFile that implements this interface. */ virtual CaretMappableDataFile* getMatrixChartCaretMappableDataFile(); /** * @return The CaretMappableDataFile that implements this interface (const methdod). */ virtual const CaretMappableDataFile* getMatrixChartCaretMappableDataFile() const; /** * @return The CiftiMappableDataFile that implements this interface. * May be NULL ! */ virtual CiftiMappableDataFile* getMatrixChartCiftiMappableDataFile(); /** * @return The CiftiMappableDataFile that implements this interface (const methdod). * May be NULL ! */ virtual const CiftiMappableDataFile* getMatrixChartCiftiMappableDataFile() const; /** * @return Is charting enabled for this file in the given tab? */ virtual bool isMatrixChartingEnabled(const int32_t tabIndex) const = 0; /** * @return Return true if the file's current state supports * charting data, else false. Typically a brainordinate file * is chartable if it contains more than one map. */ virtual bool isMatrixChartingSupported() const = 0; /** * Set charting enabled for this file in the given tab * * @param enabled * New status for charting enabled. */ virtual void setMatrixChartingEnabled(const int32_t tabIndex, const bool enabled) = 0; /** * Get chart data types supported by the file. * * @param chartDataTypesOut * Chart types supported by this file. */ virtual void getSupportedMatrixChartDataTypes(std::vector& chartDataTypesOut) const = 0; bool isMatrixChartDataTypeSupported(const ChartDataTypeEnum::Enum chartDataType) const; // ADD_NEW_METHODS_HERE private: ChartableMatrixInterface(const ChartableMatrixInterface&); ChartableMatrixInterface& operator=(const ChartableMatrixInterface&); // ADD_NEW_MEMBERS_HERE }; #ifdef __CHARTABLE_MATRIX_INTERFACE_DECLARE__ // #endif // __CHARTABLE_MATRIX_INTERFACE_DECLARE__ } // namespace #endif //__CHARTABLE_MATRIX_INTERFACE_H__ workbench-1.1.1/src/Files/ChartableMatrixParcelInterface.h000066400000000000000000000156541255417355300235450ustar00rootroot00000000000000#ifndef __CHARTABLE_MATRIX_PARCEL_REORDER_INTERFACE_H__ #define __CHARTABLE_MATRIX_PARCEL_REORDER_INTERFACE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "ChartableMatrixInterface.h" namespace caret { class CiftiParcelLabelFile; class CiftiParcelReordering; class ChartableMatrixParcelInterface : public ChartableMatrixInterface { protected: ChartableMatrixParcelInterface() { } virtual ~ChartableMatrixParcelInterface() { } public: /** * Get the selected parcel label file used for reordering of parcels. * * @param compatibleParcelLabelFilesOut * All Parcel Label files that are compatible with file implementing * this interface. * @param selectedParcelLabelFileOut * The selected parcel label file used for reordering the parcels. * May be NULL! * @param selectedParcelLabelFileMapIndexOut * Map index in the selected parcel label file. * @param enabledStatusOut * Enabled status of reordering. */ virtual void getSelectedParcelLabelFileAndMapForReordering(std::vector& compatibleParcelLabelFilesOut, CiftiParcelLabelFile* &selectedParcelLabelFileOut, int32_t& selectedParcelLabelFileMapIndexOut, bool& enabledStatusOut) const = 0; /** * Set the selected parcel label file used for reordering of parcels. * * @param selectedParcelLabelFile * The selected parcel label file used for reordering the parcels. * May be NULL! * @param selectedParcelLabelFileMapIndex * Map index in the selected parcel label file. * @param enabledStatus * Enabled status of reordering. */ virtual void setSelectedParcelLabelFileAndMapForReordering(CiftiParcelLabelFile* selectedParcelLabelFile, const int32_t selectedParcelLabelFileMapIndex, const bool enabledStatus) = 0; /** * Create the parcel reordering for the given map index using * the given parcel label file and its map index. * * @param parcelLabelFile * The selected parcel label file used for reordering the parcels. * @param parcelLabelFileMapIndex * Map index in the selected parcel label file. * @param errorMessageOut * Error message output. Will only be non-empty if NULL is returned. * @return * Pointer to parcel reordering or NULL if not found. */ virtual bool createParcelReordering(const CiftiParcelLabelFile* parcelLabelFile, const int32_t parcelLabelFileMapIndex, AString& errorMessageOut) = 0; /** * Get the parcel reordering for the given map index that was created using * the given parcel label file and its map index. * * @param parcelLabelFile * The selected parcel label file used for reordering the parcels. * @param parcelLabelFileMapIndex * Map index in the selected parcel label file. * @return * Pointer to parcel reordering or NULL if not found. */ virtual const CiftiParcelReordering* getParcelReordering(const CiftiParcelLabelFile* parcelLabelFile, const int32_t parcelLabelFileMapIndex) const = 0; /** * @return Coloring mode for selected parcel. */ virtual CiftiParcelColoringModeEnum::Enum getSelectedParcelColoringMode() const = 0; /** * Set the coloring mode for selected parcel. * * @param coloringMode * New value for coloring mode. */ virtual void setSelectedParcelColoringMode(const CiftiParcelColoringModeEnum::Enum coloringMode) = 0; /** * @return Color for selected parcel. */ virtual CaretColorEnum::Enum getSelectedParcelColor() const = 0; /** * @return True if loading attributes (column/row, yoking) are * supported by this file type. */ virtual bool isSupportsLoadingAttributes() = 0; /** * @return The matrix loading type (by row/column). */ virtual ChartMatrixLoadingDimensionEnum::Enum getMatrixLoadingDimension() const = 0; /** * Set the matrix loading type (by row/column). * * @param matrixLoadingType * New value for matrix loading type. */ virtual void setMatrixLoadingDimension(const ChartMatrixLoadingDimensionEnum::Enum matrixLoadingType) = 0; /** * Set color for selected parcel. * * @param color * New color for selected parcel. */ virtual void setSelectedParcelColor(const CaretColorEnum::Enum color) = 0; /** * @return Selected yoking group. */ virtual YokingGroupEnum::Enum getYokingGroup() const = 0; /** * Set the selected yoking group. * * @param yokingGroup * New value for yoking group. */ virtual void setYokingGroup(const YokingGroupEnum::Enum yokingType) = 0; private: ChartableMatrixParcelInterface(const ChartableMatrixParcelInterface&); ChartableMatrixParcelInterface& operator=(const ChartableMatrixParcelInterface&); // ADD_NEW_MEMBERS_HERE }; #ifdef __CHARTABLE_MATRIX_INTERFACE_PARCEL_REORDER_DECLARE__ // #endif // __CHARTABLE_MATRIX_INTERFACE_PARCEL_REORDER_DECLARE__ } // namespace #endif //__CHARTABLE_MATRIX_PARCEL_REORDER_INTERFACE_H__ workbench-1.1.1/src/Files/ChartableMatrixSeriesInterface.h000066400000000000000000000043141255417355300235600ustar00rootroot00000000000000#ifndef __CHARTABLE_MATRIX_SERIES_INTERFACE_H__ #define __CHARTABLE_MATRIX_SERIES_INTERFACE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "ChartableMatrixInterface.h" #include "MapYokingGroupEnum.h" namespace caret { class ChartableMatrixSeriesInterface : public ChartableMatrixInterface { public: ChartableMatrixSeriesInterface() { } virtual ~ChartableMatrixSeriesInterface() { } virtual MapYokingGroupEnum::Enum getMatrixRowColumnMapYokingGroup(const int32_t tabIndex) const = 0; virtual void setMatrixRowColumnMapYokingGroup(const int32_t tabIndex, const MapYokingGroupEnum::Enum yokingType) = 0; virtual int32_t getSelectedMapIndex(const int32_t tabIndex) = 0; virtual void setSelectedMapIndex(const int32_t tabIndex, const int32_t mapIndex) = 0; // ADD_NEW_METHODS_HERE private: ChartableMatrixSeriesInterface(const ChartableMatrixSeriesInterface&); ChartableMatrixSeriesInterface& operator=(const ChartableMatrixSeriesInterface&); // ADD_NEW_MEMBERS_HERE }; #ifdef __CHARTABLE_MATRIX_SERIES_INTERFACE_DECLARE__ // #endif // __CHARTABLE_MATRIX_SERIES_INTERFACE_DECLARE__ } // namespace #endif //__CHARTABLE_MATRIX_SERIES_INTERFACE_H__ workbench-1.1.1/src/Files/CiftiBrainordinateDataSeriesFile.cxx000066400000000000000000000271211255417355300243730ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CIFTI_BRAINORDINATE_DATA_SERIES_FILE_DECLARE__ #include "CiftiBrainordinateDataSeriesFile.h" #undef __CIFTI_BRAINORDINATE_DATA_SERIES_FILE_DECLARE__ #include "CaretLogger.h" #include "ChartDataCartesian.h" #include "SceneClass.h" using namespace caret; /** * \class caret::CiftiBrainordinateDataSeriesFile * \brief CIFTI Brainordinate by Data-Series File. * \ingroup Files */ /** * Constructor. */ CiftiBrainordinateDataSeriesFile::CiftiBrainordinateDataSeriesFile() : CiftiMappableDataFile(DataFileTypeEnum::CONNECTIVITY_DENSE_TIME_SERIES) { for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_chartingEnabledForTab[i] = false; } } /** * Destructor. */ CiftiBrainordinateDataSeriesFile::~CiftiBrainordinateDataSeriesFile() { } /** * @return Is charting enabled for this file? */ bool CiftiBrainordinateDataSeriesFile::isLineSeriesChartingEnabled(const int32_t tabIndex) const { CaretAssertArrayIndex(m_chartingEnabledForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_chartingEnabledForTab[tabIndex]; } /** * @return Return true if the file's current state supports * charting data, else false. Typically a brainordinate file * is chartable if it contains more than one map. */ bool CiftiBrainordinateDataSeriesFile::isLineSeriesChartingSupported() const { if (getNumberOfMaps() > 1) { return true; } return false; } /** * Set charting enabled for this file. * * @param enabled * New status for charting enabled. */ void CiftiBrainordinateDataSeriesFile::setLineSeriesChartingEnabled(const int32_t tabIndex, const bool enabled) { CaretAssertArrayIndex(m_chartingEnabledForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_chartingEnabledForTab[tabIndex] = enabled; } /** * Load charting data for the surface with the given structure and node index. * * @param structure * The surface's structure. * @param nodeIndex * Index of the node. * @return * Pointer to the chart data. If the data FAILED to load, * the returned pointer will return true. Caller takes ownership * of the pointer and must delete it when no longer needed. */ ChartDataCartesian* CiftiBrainordinateDataSeriesFile::loadLineSeriesChartDataForSurfaceNode(const StructureEnum::Enum structure, const int32_t nodeIndex) { ChartDataCartesian* chartData = helpLoadChartDataForSurfaceNode(structure, nodeIndex); return chartData; // ChartDataCartesian* chartData = NULL; // // try { // std::vector data; // if (getSeriesDataForSurfaceNode(structure, // nodeIndex, // data)) { // const int64_t numData = static_cast(data.size()); // // bool timeSeriesFlag = false; // bool dataSeriesFlag = false; // float convertTimeToSeconds = 1.0; // switch (getMapIntervalUnits()) { // case NiftiTimeUnitsEnum::NIFTI_UNITS_HZ: // break; // case NiftiTimeUnitsEnum::NIFTI_UNITS_MSEC: // timeSeriesFlag = true; // convertTimeToSeconds = 1000.0; // break; // case NiftiTimeUnitsEnum::NIFTI_UNITS_PPM: // break; // case NiftiTimeUnitsEnum::NIFTI_UNITS_SEC: // convertTimeToSeconds = 1.0; // timeSeriesFlag = true; // break; // case NiftiTimeUnitsEnum::NIFTI_UNITS_UNKNOWN: // dataSeriesFlag = true; // break; // case NiftiTimeUnitsEnum::NIFTI_UNITS_USEC: // convertTimeToSeconds = 1000000.0; // timeSeriesFlag = true; // break; // } // // if (dataSeriesFlag) { // chartData = new ChartDataCartesian(ChartDataTypeEnum::CHART_DATA_TYPE_LINE_DATA_SERIES, // ChartAxisUnitsEnum::CHART_AXIS_UNITS_NONE, // ChartAxisUnitsEnum::CHART_AXIS_UNITS_NONE); // } // else if (timeSeriesFlag) { // chartData = new ChartDataCartesian(ChartDataTypeEnum::CHART_DATA_TYPE_LINE_TIME_SERIES, // ChartAxisUnitsEnum::CHART_AXIS_UNITS_TIME_SECONDS, // ChartAxisUnitsEnum::CHART_AXIS_UNITS_NONE); // } // // if (chartData != NULL) { // float timeStart = 0.0; // float timeStep = 1.0; // if (timeSeriesFlag) { // getMapIntervalStartAndStep(timeStart, // timeStep); // timeStart *= convertTimeToSeconds; // timeStep *= convertTimeToSeconds; // chartData->setTimeStartInSecondsAxisX(timeStart); // chartData->setTimeStepInSecondsAxisX(timeStep); // } // // for (int64_t i = 0; i < numData; i++) { // float xValue = i; // // if (timeSeriesFlag) { // xValue = timeStart + (i * timeStep); // } // // chartData->addPoint(xValue, // data[i]); // } // // const AString description = (getFileNameNoPath() // + " node " // + AString::number(nodeIndex)); // chartData->setDescription(description); // } // else { // const AString msg = "New type of units for data series flag, needs updating for charting"; // CaretAssertMessage(0, msg); // throw DataFileException(msg); // } // } // } // catch (const DataFileException& dfe) { // if (chartData != NULL) { // delete chartData; // chartData = NULL; // } // // throw dfe; // } // // return chartData; } /** * Load average charting data for the surface with the given structure and node indices. * * @param structure * The surface's structure. * @param nodeIndices * Indices of the node. * @return * Pointer to the chart data. If the data FAILED to load, * the returned pointer will be NULL. Caller takes ownership * of the pointer and must delete it when no longer needed. */ ChartDataCartesian* CiftiBrainordinateDataSeriesFile::loadAverageLineSeriesChartDataForSurfaceNodes(const StructureEnum::Enum structure, const std::vector& nodeIndices) { ChartDataCartesian* chartData = helpLoadChartDataForSurfaceNodeAverage(structure, nodeIndices); return chartData; } /** * Load charting data for the voxel enclosing the given coordinate. * * @param xyz * Coordinate of voxel. * @return * Pointer to the chart data. If the data FAILED to load, * the returned pointer will be NULL. Caller takes ownership * of the pointer and must delete it when no longer needed. */ ChartDataCartesian* CiftiBrainordinateDataSeriesFile::loadLineSeriesChartDataForVoxelAtCoordinate(const float xyz[3]) { ChartDataCartesian* chartData = helpLoadChartDataForVoxelAtCoordinate(xyz); return chartData; } /** * Get chart data types supported by the file. * * @param chartDataTypesOut * Chart types supported by this file. */ void CiftiBrainordinateDataSeriesFile::getSupportedLineSeriesChartDataTypes(std::vector& chartDataTypesOut) const { helpGetSupportedLineSeriesChartDataTypes(chartDataTypesOut); } /** * Save file data from the scene. For subclasses that need to * save to a scene, this method should be overriden. sceneClass * will be valid and any scene data should be added to it. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass to which data members should be added. */ void CiftiBrainordinateDataSeriesFile::saveFileDataToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass) { CiftiMappableDataFile::saveFileDataToScene(sceneAttributes, sceneClass); sceneClass->addBooleanArray("m_chartingEnabledForTab", m_chartingEnabledForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS); } /** * Restore file data from the scene. For subclasses that need to * restore from a scene, this method should be overridden. The scene class * will be valid and any scene data may be obtained from it. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass for the instance of a class that implements * this interface. Will NEVER be NULL. */ void CiftiBrainordinateDataSeriesFile::restoreFileDataFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { CiftiMappableDataFile::restoreFileDataFromScene(sceneAttributes, sceneClass); const ScenePrimitiveArray* tabArray = sceneClass->getPrimitiveArray("m_chartingEnabledForTab"); if (tabArray != NULL) { sceneClass->getBooleanArrayValue("m_chartingEnabledForTab", m_chartingEnabledForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS); } else { /* * Obsolete value when charting was not 'per tab' */ const bool chartingEnabled = sceneClass->getBooleanValue("m_chartingEnabled", false); for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_chartingEnabledForTab[i] = chartingEnabled; } } } workbench-1.1.1/src/Files/CiftiBrainordinateDataSeriesFile.h000066400000000000000000000064021255417355300240170ustar00rootroot00000000000000#ifndef __CIFTI_BRAINORDINATE_DATA_SERIES_FILE_H__ #define __CIFTI_BRAINORDINATE_DATA_SERIES_FILE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "BrainConstants.h" #include "ChartableLineSeriesBrainordinateInterface.h" #include "CiftiMappableDataFile.h" namespace caret { class PaletteFile; class CiftiBrainordinateDataSeriesFile : public CiftiMappableDataFile, public ChartableLineSeriesBrainordinateInterface { public: CiftiBrainordinateDataSeriesFile(); virtual ~CiftiBrainordinateDataSeriesFile(); virtual bool isLineSeriesChartingEnabled(const int32_t tabIndex) const; virtual void setLineSeriesChartingEnabled(const int32_t tabIndex, const bool enabled); virtual bool isLineSeriesChartingSupported() const; virtual ChartDataCartesian* loadLineSeriesChartDataForSurfaceNode(const StructureEnum::Enum structure, const int32_t nodeIndex); virtual ChartDataCartesian* loadAverageLineSeriesChartDataForSurfaceNodes(const StructureEnum::Enum structure, const std::vector& nodeIndices); virtual ChartDataCartesian* loadLineSeriesChartDataForVoxelAtCoordinate(const float xyz[3]); virtual void getSupportedLineSeriesChartDataTypes(std::vector& chartDataTypesOut) const; private: CiftiBrainordinateDataSeriesFile(const CiftiBrainordinateDataSeriesFile&); CiftiBrainordinateDataSeriesFile& operator=(const CiftiBrainordinateDataSeriesFile&); virtual void saveFileDataToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass); virtual void restoreFileDataFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); public: // ADD_NEW_METHODS_HERE private: // ADD_NEW_MEMBERS_HERE bool m_chartingEnabledForTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; }; #ifdef __CIFTI_BRAINORDINATE_DATA_SERIES_FILE_DECLARE__ // #endif // __CIFTI_BRAINORDINATE_DATA_SERIES_FILE_DECLARE__ } // namespace #endif //__CIFTI_BRAINORDINATE_DATA_SERIES_FILE_H__ workbench-1.1.1/src/Files/CiftiBrainordinateLabelFile.cxx000066400000000000000000000133261255417355300233700ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CIFTI_BRAINORDINATE_LABEL_FILE_DECLARE__ #include "CiftiBrainordinateLabelFile.h" #undef __CIFTI_BRAINORDINATE_LABEL_FILE_DECLARE__ #include "CiftiFile.h" #include "CiftiLabelsMap.h" using namespace caret; /** * \class caret::CiftiBrainordinateLabelFile * \brief CIFTI Brainordinate by Label File * \ingroup Files */ /** * Constructor. */ CiftiBrainordinateLabelFile::CiftiBrainordinateLabelFile() : CiftiMappableDataFile(DataFileTypeEnum::CONNECTIVITY_DENSE_LABEL) { } /** * Destructor. */ CiftiBrainordinateLabelFile::~CiftiBrainordinateLabelFile() { } /** * For the given structure with the given number of nodes, get indices of all * nodes in the map that have the same label key (node value). * * @param structure * Structure of surface. * @param surfaceNumberOfNodes * Number of nodes in surface. * @param mapIndex * Index of the map. * @param labelKey * Desired label key. */ void CiftiBrainordinateLabelFile::getNodeIndicesWithLabelKey(const StructureEnum::Enum structure, const int32_t surfaceNumberOfNodes, const int32_t mapIndex, const int32_t labelKey, std::vector& nodeIndicesOut) const { nodeIndicesOut.clear(); std::vector dataIndices; if (getSurfaceDataIndicesForMappingToBrainordinates(structure, surfaceNumberOfNodes, dataIndices)) { std::vector mapData; getMapData(mapIndex, mapData); for (int32_t i = 0; i < surfaceNumberOfNodes; i++) { const int64_t dataIndex = dataIndices[i]; if (dataIndex >= 0) { CaretAssertVectorIndex(mapData, dataIndex); const int32_t dataKey = static_cast(mapData[dataIndex]); if (dataKey == labelKey) { nodeIndicesOut.push_back(i); } } } } } /** * Get the voxel indices of all voxels in the given map with the given label key. * * @param mapIndex * Index of map. * @param labelKey * Key of the label. * @param voxelIndicesOut * Output containing indices of voxels with the given label key. */ void CiftiBrainordinateLabelFile::getVoxelIndicesWithLabelKey(const int32_t mapIndex, const int32_t labelKey, std::vector& voxelIndicesOut) const { voxelIndicesOut.clear(); const CiftiXML& myXML = m_ciftiFile->getCiftiXML(); if (myXML.getMappingType(CiftiXML::ALONG_COLUMN) != CiftiMappingType::BRAIN_MODELS) { return; } std::vector volumeMaps = myXML.getBrainModelsMap(CiftiXML::ALONG_COLUMN).getFullVolumeMap(); std::vector mapData; getMapData(mapIndex, mapData); for (std::vector::iterator iter = volumeMaps.begin(); iter != volumeMaps.end(); iter++) { const CiftiBrainModelsMap::VolumeMap& vm = *iter; const int64_t dataOffset = vm.m_ciftiIndex; CaretAssertVectorIndex(mapData, dataOffset); const int32_t key = static_cast(mapData[dataOffset]); if (key == labelKey) { voxelIndicesOut.push_back(VoxelIJK(vm.m_ijk)); } } } /** * Get the voxel indices of all voxels in the given map with the given label key. * * @param mapIndex * Index of map. * @param labelKey * Key of the label. * @param voxelXyzOut * Output containing coordinates of voxels with the given label key. */ void CiftiBrainordinateLabelFile::getVoxelCoordinatesWithLabelKey(const int32_t mapIndex, const int32_t labelKey, std::vector& voxelXyzOut) const { voxelXyzOut.clear(); std::vector voxelIJK; getVoxelIndicesWithLabelKey(mapIndex, labelKey, voxelIJK); if (voxelIJK.empty()) { return; } const CiftiXML& myXML = m_ciftiFile->getCiftiXML(); if (myXML.getMappingType(CiftiXML::ALONG_COLUMN) != CiftiMappingType::BRAIN_MODELS) { return; } const VolumeSpace volumeSpace = myXML.getBrainModelsMap(CiftiXML::ALONG_COLUMN).getVolumeSpace(); const int64_t numVoxels = static_cast(voxelIJK.size()); for (int64_t i = 0; i < numVoxels; i++) { float xyz[3]; volumeSpace.indexToSpace(voxelIJK[i].m_ijk, xyz); voxelXyzOut.push_back(xyz[0]); voxelXyzOut.push_back(xyz[1]); voxelXyzOut.push_back(xyz[2]); } } workbench-1.1.1/src/Files/CiftiBrainordinateLabelFile.h000066400000000000000000000047501255417355300230160ustar00rootroot00000000000000#ifndef __CIFTI_BRAINORDINATE_LABEL_FILE_H__ #define __CIFTI_BRAINORDINATE_LABEL_FILE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CiftiMappableDataFile.h" #include "VoxelIJK.h" namespace caret { class CiftiBrainordinateLabelFile : public CiftiMappableDataFile { public: CiftiBrainordinateLabelFile(); virtual ~CiftiBrainordinateLabelFile(); void getNodeIndicesWithLabelKey(const StructureEnum::Enum structure, const int32_t surfaceNumberOfNodes, const int32_t mapIndex, const int32_t labelKey, std::vector& nodeIndicesOut) const; void getVoxelIndicesWithLabelKey(const int32_t mapIndex, const int32_t labelKey, std::vector& voxelIndicesOut) const; void getVoxelCoordinatesWithLabelKey(const int32_t mapIndex, const int32_t labelKey, std::vector& voxelXyzOut) const; private: CiftiBrainordinateLabelFile(const CiftiBrainordinateLabelFile&); CiftiBrainordinateLabelFile& operator=(const CiftiBrainordinateLabelFile&); public: // ADD_NEW_METHODS_HERE private: // ADD_NEW_MEMBERS_HERE }; #ifdef __CIFTI_BRAINORDINATE_LABEL_FILE_DECLARE__ // #endif // __CIFTI_BRAINORDINATE_LABEL_FILE_DECLARE__ } // namespace #endif //__CIFTI_BRAINORDINATE_LABEL_FILE_H__ workbench-1.1.1/src/Files/CiftiBrainordinateScalarFile.cxx000066400000000000000000000332131255417355300235530ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CIFTI_BRAINORDINATE_SCALAR_FILE_DECLARE__ #include "CiftiBrainordinateScalarFile.h" #undef __CIFTI_BRAINORDINATE_SCALAR_FILE_DECLARE__ #include "CiftiMappableConnectivityMatrixDataFile.h" #include "CiftiConnectivityMatrixDenseFile.h" #include "CaretLogger.h" #include "ChartDataCartesian.h" #include "CiftiFile.h" #include "CiftiXML.h" #include "DataFileException.h" #include "FileInformation.h" #include "SceneClass.h" #include "SceneClassArray.h" using namespace caret; /** * \class caret::CiftiBrainordinateScalarFile * \brief CIFTI Brainordinate by Scalar File * \ingroup Files * */ /** * Constructor. */ CiftiBrainordinateScalarFile::CiftiBrainordinateScalarFile() : CiftiMappableDataFile(DataFileTypeEnum::CONNECTIVITY_DENSE_SCALAR) { for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_chartingEnabledForTab[i] = false; } } /** * Destructor. */ CiftiBrainordinateScalarFile::~CiftiBrainordinateScalarFile() { } /** * Create a Cifti Scalar File using the currently loaded row in a Cifti * connectivity matrix file. * * @param sourceCiftiMatrixFile * Cifti connectivity matrix file. * @param destinationDirectory * Directory in which file is placed if the input matrix file is not * in a valid local (user's file system) directory. * @param errorMessageOut * Will describe problem if there is an error. * @return * Pointer to the newly created Cifti Scalar File. If there is an error, * NULL will be returned and errorMessageOut will describe the problem. */ CiftiBrainordinateScalarFile* CiftiBrainordinateScalarFile::newInstanceFromRowInCiftiConnectivityMatrixFile(const CiftiMappableConnectivityMatrixDataFile* sourceCiftiMatrixFile, const AString& destinationDirectory, AString& errorMessageOut) { errorMessageOut.clear(); const CiftiConnectivityMatrixDenseFile* denseFile = dynamic_cast(sourceCiftiMatrixFile); if (denseFile == NULL) { errorMessageOut = "Only Cifti Dense Matrix Files are supported for conversion to Cifti Scalar Files."; return NULL; } const CiftiFile* sourceCiftiFile = sourceCiftiMatrixFile->m_ciftiFile; if (sourceCiftiMatrixFile->getNumberOfMaps() <= 0) { errorMessageOut = "No data appears to be loaded in the Cifti Matrix File (No Maps)."; return NULL; } std::vector data; sourceCiftiMatrixFile->getMapData(0, data); if (data.empty()) { errorMessageOut = "No data appears to be loaded in the Cifti Matrix File (mapData empty)."; return NULL; } CiftiBrainordinateScalarFile* scalarFile = NULL; try { CiftiFile* ciftiFile = new CiftiFile(); /* * Copy XML from matrix file * and update to be a scalar file. */ const CiftiXML& ciftiMatrixXML = sourceCiftiFile->getCiftiXML(); CiftiXML ciftiScalarXML = ciftiMatrixXML; CiftiBrainModelsMap brainModelsMap = ciftiMatrixXML.getBrainModelsMap(CiftiXML::ALONG_ROW); CiftiScalarsMap scalarsMap; scalarsMap.setLength(1); scalarsMap.setMapName(0, sourceCiftiMatrixFile->getMapName(0)); ciftiScalarXML.setMap(CiftiXML::ALONG_ROW, scalarsMap); ciftiScalarXML.setMap(CiftiXML::ALONG_COLUMN, brainModelsMap); ciftiFile->setCiftiXML(ciftiScalarXML); /* * Add data to the file */ ciftiFile->setColumn(&data[0], 0); /* * Create a scalar file */ CiftiBrainordinateScalarFile* scalarFile = new CiftiBrainordinateScalarFile(); scalarFile->m_ciftiFile.grabNew(ciftiFile); /* * May need to convert a remote path to a local path */ FileInformation initialFileNameInfo(sourceCiftiMatrixFile->getFileName()); const AString scalarFileName = initialFileNameInfo.getAsLocalAbsoluteFilePath(destinationDirectory, scalarFile->getDataFileType()); /* * Create name of scalar file with row/column information */ FileInformation scalarFileInfo(scalarFileName); AString thePath, theName, theExtension; scalarFileInfo.getFileComponents(thePath, theName, theExtension); theName.append("_" + sourceCiftiMatrixFile->getRowLoadedText()); const AString newFileName = FileInformation::assembleFileComponents(thePath, theName, theExtension); scalarFile->setFileName(newFileName); scalarFile->initializeAfterReading(newFileName); scalarFile->setModified(); return scalarFile; } catch (const DataFileException& de) { if (scalarFile != NULL) { delete scalarFile; } errorMessageOut = de.whatString(); } return NULL; } /** * @return Is charting enabled for this file? */ bool CiftiBrainordinateScalarFile::isLineSeriesChartingEnabled(const int32_t tabIndex) const { CaretAssertArrayIndex(m_chartingEnabledForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_chartingEnabledForTab[tabIndex]; } /** * @return Return true if the file's current state supports * charting data, else false. Typically a brainordinate file * is chartable if it contains more than one map. */ bool CiftiBrainordinateScalarFile::isLineSeriesChartingSupported() const { if (getNumberOfMaps() > 1) { return true; } return false; } /** * Set charting enabled for this file. * * @param enabled * New status for charting enabled. */ void CiftiBrainordinateScalarFile::setLineSeriesChartingEnabled(const int32_t tabIndex, const bool enabled) { CaretAssertArrayIndex(m_chartingEnabledForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_chartingEnabledForTab[tabIndex] = enabled; } /** * Get chart data types supported by the file. * * @param chartDataTypesOut * Chart types supported by this file. */ void CiftiBrainordinateScalarFile::getSupportedLineSeriesChartDataTypes(std::vector& chartDataTypesOut) const { helpGetSupportedLineSeriesChartDataTypes(chartDataTypesOut); } /** * Load charting data for the surface with the given structure and node index. * * @param structure * The surface's structure. * @param nodeIndex * Index of the node. * @return * Pointer to the chart data. If the data FAILED to load, * the returned pointer will return true. Caller takes ownership * of the pointer and must delete it when no longer needed. */ ChartDataCartesian* CiftiBrainordinateScalarFile::loadLineSeriesChartDataForSurfaceNode(const StructureEnum::Enum structure, const int32_t nodeIndex) { ChartDataCartesian* chartData = helpLoadChartDataForSurfaceNode(structure, nodeIndex); return chartData; // ChartDataCartesian* chartData = NULL; // // try { // std::vector data; // if (getSeriesDataForSurfaceNode(structure, // nodeIndex, // data)) { // const int64_t numData = static_cast(data.size()); // // chartData = new ChartDataCartesian(ChartDataTypeEnum::CHART_DATA_TYPE_LINE_DATA_SERIES, // ChartAxisUnitsEnum::CHART_AXIS_UNITS_NONE, // ChartAxisUnitsEnum::CHART_AXIS_UNITS_NONE); // for (int64_t i = 0; i < numData; i++) { // float xValue = i; // chartData->addPoint(xValue, // data[i]); // } // // const AString description = (getFileNameNoPath() // + " node " // + AString::number(nodeIndex)); // chartData->setDescription(description); // } // } // catch (const DataFileException& dfe) { // if (chartData != NULL) { // delete chartData; // chartData = NULL; // } // // throw dfe; // } // // return chartData; } /** * Load average charting data for the surface with the given structure and node indices. * * @param structure * The surface's structure. * @param nodeIndices * Indices of the node. * @return * Pointer to the chart data. If the data FAILED to load, * the returned pointer will be NULL. Caller takes ownership * of the pointer and must delete it when no longer needed. */ ChartDataCartesian* CiftiBrainordinateScalarFile::loadAverageLineSeriesChartDataForSurfaceNodes(const StructureEnum::Enum structure, const std::vector& nodeIndices) { ChartDataCartesian* chartData = helpLoadChartDataForSurfaceNodeAverage(structure, nodeIndices); return chartData; } /** * Load charting data for the voxel enclosing the given coordinate. * * @param xyz * Coordinate of voxel. * @return * Pointer to the chart data. If the data FAILED to load, * the returned pointer will be NULL. Caller takes ownership * of the pointer and must delete it when no longer needed. */ ChartDataCartesian* CiftiBrainordinateScalarFile::loadLineSeriesChartDataForVoxelAtCoordinate(const float xyz[3]) { ChartDataCartesian* chartData = helpLoadChartDataForVoxelAtCoordinate(xyz); return chartData; } /** * Save file data from the scene. For subclasses that need to * save to a scene, this method should be overriden. sceneClass * will be valid and any scene data should be added to it. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass to which data members should be added. */ void CiftiBrainordinateScalarFile::saveFileDataToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass) { CiftiMappableDataFile::saveFileDataToScene(sceneAttributes, sceneClass); sceneClass->addBooleanArray("m_chartingEnabledForTab", m_chartingEnabledForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS); } /** * Restore file data from the scene. For subclasses that need to * restore from a scene, this method should be overridden. The scene class * will be valid and any scene data may be obtained from it. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass for the instance of a class that implements * this interface. Will NEVER be NULL. */ void CiftiBrainordinateScalarFile::restoreFileDataFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { CiftiMappableDataFile::restoreFileDataFromScene(sceneAttributes, sceneClass); const ScenePrimitiveArray* tabArray = sceneClass->getPrimitiveArray("m_chartingEnabledForTab"); if (tabArray != NULL) { sceneClass->getBooleanArrayValue("m_chartingEnabledForTab", m_chartingEnabledForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS); } else { /* * Obsolete value when charting was not 'per tab' */ const bool chartingEnabled = sceneClass->getBooleanValue("m_chartingEnabled", false); for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_chartingEnabledForTab[i] = chartingEnabled; } } } workbench-1.1.1/src/Files/CiftiBrainordinateScalarFile.h000066400000000000000000000072061255417355300232030ustar00rootroot00000000000000#ifndef __CIFTI_BRAINORDINATE_SCALAR_FILE_H__ #define __CIFTI_BRAINORDINATE_SCALAR_FILE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "BrainConstants.h" #include "ChartableLineSeriesBrainordinateInterface.h" #include "CiftiMappableDataFile.h" namespace caret { class CiftiMappableConnectivityMatrixDataFile; class CiftiBrainordinateScalarFile : public CiftiMappableDataFile, public ChartableLineSeriesBrainordinateInterface { public: CiftiBrainordinateScalarFile(); virtual ~CiftiBrainordinateScalarFile(); static CiftiBrainordinateScalarFile* newInstanceFromRowInCiftiConnectivityMatrixFile(const CiftiMappableConnectivityMatrixDataFile* ciftiMatrixFile, const AString& destinationDirectory, AString& errorMessageOut); virtual bool isLineSeriesChartingEnabled(const int32_t tabIndex) const; virtual void setLineSeriesChartingEnabled(const int32_t tabIndex, const bool enabled); virtual bool isLineSeriesChartingSupported() const; virtual ChartDataCartesian* loadLineSeriesChartDataForSurfaceNode(const StructureEnum::Enum structure, const int32_t nodeIndex); virtual ChartDataCartesian* loadAverageLineSeriesChartDataForSurfaceNodes(const StructureEnum::Enum structure, const std::vector& nodeIndices); virtual ChartDataCartesian* loadLineSeriesChartDataForVoxelAtCoordinate(const float xyz[3]); virtual void getSupportedLineSeriesChartDataTypes(std::vector& chartDataTypesOut) const; private: CiftiBrainordinateScalarFile(const CiftiBrainordinateScalarFile&); CiftiBrainordinateScalarFile& operator=(const CiftiBrainordinateScalarFile&); virtual void saveFileDataToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass); virtual void restoreFileDataFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); public: // ADD_NEW_METHODS_HERE private: bool m_chartingEnabledForTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; // ADD_NEW_MEMBERS_HERE }; #ifdef __CIFTI_BRAINORDINATE_SCALAR_FILE_DECLARE__ // #endif // __CIFTI_BRAINORDINATE_SCALAR_FILE_DECLARE__ } // namespace #endif //__CIFTI_BRAINORDINATE_SCALAR_FILE_H__ workbench-1.1.1/src/Files/CiftiConnectivityMatrixDenseFile.cxx000066400000000000000000000033141255417355300244650ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CIFTI_CONNECTIVITY_MATRIX_DENSE_FILE_DECLARE__ #include "CiftiConnectivityMatrixDenseFile.h" #undef __CIFTI_CONNECTIVITY_MATRIX_DENSE_FILE_DECLARE__ using namespace caret; /** * \class caret::CiftiConnectivityMatrixDenseFile * \brief Connectivity Dense x Dense File * \ingroup Files * * Contains connectivity matrix that measures connectivity from brainordinates * to brainordinates. */ /** * Constructor. */ CiftiConnectivityMatrixDenseFile::CiftiConnectivityMatrixDenseFile() : CiftiMappableConnectivityMatrixDataFile(DataFileTypeEnum::CONNECTIVITY_DENSE) { } /** * Destructor. */ CiftiConnectivityMatrixDenseFile::~CiftiConnectivityMatrixDenseFile() { } /** * @return True if this file type supports writing, else false. * * Dense files do NOT support writing. */ bool CiftiConnectivityMatrixDenseFile::supportsWriting() const { return false; } workbench-1.1.1/src/Files/CiftiConnectivityMatrixDenseFile.h000066400000000000000000000034601255417355300241140ustar00rootroot00000000000000#ifndef __CIFTI_CONNECTIVITY_MATRIX_DENSE_FILE_H__ #define __CIFTI_CONNECTIVITY_MATRIX_DENSE_FILE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CiftiMappableConnectivityMatrixDataFile.h" namespace caret { class CiftiConnectivityMatrixDenseFile : public CiftiMappableConnectivityMatrixDataFile { public: CiftiConnectivityMatrixDenseFile(); virtual ~CiftiConnectivityMatrixDenseFile(); virtual bool supportsWriting() const; private: CiftiConnectivityMatrixDenseFile(const CiftiConnectivityMatrixDenseFile&); CiftiConnectivityMatrixDenseFile& operator=(const CiftiConnectivityMatrixDenseFile&); public: // ADD_NEW_METHODS_HERE private: // ADD_NEW_MEMBERS_HERE }; #ifdef __CIFTI_CONNECTIVITY_MATRIX_DENSE_FILE_DECLARE__ // #endif // __CIFTI_CONNECTIVITY_MATRIX_DENSE_FILE_DECLARE__ } // namespace #endif //__CIFTI_CONNECTIVITY_MATRIX_DENSE_FILE_H__ workbench-1.1.1/src/Files/CiftiConnectivityMatrixDenseParcelFile.cxx000066400000000000000000000030661255417355300256200ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CIFTI_CONNECTIVITY_MATRIX_DENSE_PARCEL_FILE_DECLARE__ #include "CiftiConnectivityMatrixDenseParcelFile.h" #undef __CIFTI_CONNECTIVITY_MATRIX_DENSE_PARCEL_FILE_DECLARE__ using namespace caret; /** * \class caret::CiftiConnectivityMatrixDenseParcelFile * \brief Connectivity Dense x Parcel File * \ingroup Files * * Contains connectivity matrix that measures connectivity from brainordinates * to parcels. */ /** * Constructor. */ CiftiConnectivityMatrixDenseParcelFile::CiftiConnectivityMatrixDenseParcelFile() : CiftiMappableConnectivityMatrixDataFile(DataFileTypeEnum::CONNECTIVITY_DENSE_PARCEL) { } /** * Destructor. */ CiftiConnectivityMatrixDenseParcelFile::~CiftiConnectivityMatrixDenseParcelFile() { } workbench-1.1.1/src/Files/CiftiConnectivityMatrixDenseParcelFile.h000066400000000000000000000035061255417355300252440ustar00rootroot00000000000000#ifndef __CIFTI_CONNECTIVITY_MATRIX_DENSE_PARCEL_FILE_H__ #define __CIFTI_CONNECTIVITY_MATRIX_DENSE_PARCEL_FILE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CiftiMappableConnectivityMatrixDataFile.h" namespace caret { class CiftiConnectivityMatrixDenseParcelFile : public CiftiMappableConnectivityMatrixDataFile { public: CiftiConnectivityMatrixDenseParcelFile(); virtual ~CiftiConnectivityMatrixDenseParcelFile(); private: CiftiConnectivityMatrixDenseParcelFile(const CiftiConnectivityMatrixDenseParcelFile&); CiftiConnectivityMatrixDenseParcelFile& operator=(const CiftiConnectivityMatrixDenseParcelFile&); public: // ADD_NEW_METHODS_HERE private: // ADD_NEW_MEMBERS_HERE }; #ifdef __CIFTI_CONNECTIVITY_MATRIX_DENSE_PARCEL_FILE_DECLARE__ // #endif // __CIFTI_CONNECTIVITY_MATRIX_DENSE_PARCEL_FILE_DECLARE__ } // namespace #endif //__CIFTI_CONNECTIVITY_MATRIX_DENSE_PARCEL_FILE_H__ workbench-1.1.1/src/Files/CiftiConnectivityMatrixParcelDenseFile.cxx000066400000000000000000000030661255417355300256200ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CIFTI_CONNECTIVITY_MATRIX_PARCEL_DENSE_FILE_DECLARE__ #include "CiftiConnectivityMatrixParcelDenseFile.h" #undef __CIFTI_CONNECTIVITY_MATRIX_PARCEL_DENSE_FILE_DECLARE__ using namespace caret; /** * \class caret::CiftiConnectivityMatrixParcelDenseFile * \brief Connectivity Parcel x Dense File * \ingroup Files * * Contains connectivity matrix that measures connectivity from parcels * to brainordinates. */ /** * Constructor. */ CiftiConnectivityMatrixParcelDenseFile::CiftiConnectivityMatrixParcelDenseFile() : CiftiMappableConnectivityMatrixDataFile(DataFileTypeEnum::CONNECTIVITY_PARCEL_DENSE) { } /** * Destructor. */ CiftiConnectivityMatrixParcelDenseFile::~CiftiConnectivityMatrixParcelDenseFile() { } workbench-1.1.1/src/Files/CiftiConnectivityMatrixParcelDenseFile.h000066400000000000000000000035061255417355300252440ustar00rootroot00000000000000#ifndef __CIFTI_CONNECTIVITY_MATRIX_PARCEL_DENSE_FILE_H__ #define __CIFTI_CONNECTIVITY_MATRIX_PARCEL_DENSE_FILE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CiftiMappableConnectivityMatrixDataFile.h" namespace caret { class CiftiConnectivityMatrixParcelDenseFile : public CiftiMappableConnectivityMatrixDataFile { public: CiftiConnectivityMatrixParcelDenseFile(); virtual ~CiftiConnectivityMatrixParcelDenseFile(); private: CiftiConnectivityMatrixParcelDenseFile(const CiftiConnectivityMatrixParcelDenseFile&); CiftiConnectivityMatrixParcelDenseFile& operator=(const CiftiConnectivityMatrixParcelDenseFile&); public: // ADD_NEW_METHODS_HERE private: // ADD_NEW_MEMBERS_HERE }; #ifdef __CIFTI_CONNECTIVITY_MATRIX_PARCEL_DENSE_FILE_DECLARE__ // #endif // __CIFTI_CONNECTIVITY_MATRIX_PARCEL_DENSE_FILE_DECLARE__ } // namespace #endif //__CIFTI_CONNECTIVITY_MATRIX_PARCEL_DENSE_FILE_H__ workbench-1.1.1/src/Files/CiftiConnectivityMatrixParcelFile.cxx000066400000000000000000000730631255417355300246450ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CIFTI_CONNECTIVITY_MATRIX_PARCEL_FILE_DECLARE__ #include "CiftiConnectivityMatrixParcelFile.h" #undef __CIFTI_CONNECTIVITY_MATRIX_PARCEL_FILE_DECLARE__ #include "CaretLogger.h" #include "ChartMatrixDisplayProperties.h" #include "CiftiFile.h" #include "CiftiParcelReordering.h" #include "CiftiParcelReorderingModel.h" #include "ConnectivityDataLoaded.h" #include "EventChartMatrixParcelYokingValidation.h" #include "EventManager.h" #include "FastStatistics.h" #include "NodeAndVoxelColoring.h" #include "Palette.h" #include "PaletteColorMapping.h" #include "PaletteFile.h" #include "SceneAttributes.h" #include "SceneClass.h" #include "SceneClassArray.h" #include "SceneClassAssistant.h" using namespace caret; /** * \class caret::CiftiConnectivityMatrixParcelFile * \brief Connectivity Parcel x Parcel File * \ingroup Files * * Contains connectivity matrix that measures connectivity from parcels * to parcels. */ /** * Constructor. */ CiftiConnectivityMatrixParcelFile::CiftiConnectivityMatrixParcelFile() : CiftiMappableConnectivityMatrixDataFile(DataFileTypeEnum::CONNECTIVITY_PARCEL) { for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_chartingEnabledForTab[i] = false; m_chartMatrixDisplayProperties[i] = new ChartMatrixDisplayProperties(); } m_selectedParcelColoringMode = CiftiParcelColoringModeEnum::CIFTI_PARCEL_COLORING_OUTLINE; m_selectedParcelColor = CaretColorEnum::WHITE; m_parcelReorderingModel = new CiftiParcelReorderingModel(this); m_chartLoadingYokingGroup = YokingGroupEnum::YOKING_GROUP_OFF; m_sceneAssistant = new SceneClassAssistant(); m_sceneAssistant->add("m_selectedParcelColoringMode", &m_selectedParcelColoringMode); m_sceneAssistant->add("m_selectedParcelColor", &m_selectedParcelColor); m_sceneAssistant->add("m_parcelReorderingModel", "CiftiParcelReorderingModel", m_parcelReorderingModel); m_sceneAssistant->add("m_chartLoadingYokingGroup", &m_chartLoadingYokingGroup); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_CHART_MATRIX_YOKING_VALIDATION); } /** * Destructor. */ CiftiConnectivityMatrixParcelFile::~CiftiConnectivityMatrixParcelFile() { EventManager::get()->removeAllEventsFromListener(this); for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { delete m_chartMatrixDisplayProperties[i]; } delete m_parcelReorderingModel; delete m_sceneAssistant; } /** * Receive an event. * * @param event * The event that the receive can respond to. */ void CiftiConnectivityMatrixParcelFile::receiveEvent(Event* event) { if (event->getEventType() == EventTypeEnum::EVENT_CHART_MATRIX_YOKING_VALIDATION) { EventChartMatrixParcelYokingValidation* yokeEvent = dynamic_cast(event); CaretAssert(yokeEvent); if (yokeEvent->getChartableMatrixParcelInterface() != this) { switch (yokeEvent->getMode()) { case EventChartMatrixParcelYokingValidation::MODE_APPLY_YOKING: { // YokingGroupEnum::Enum yokingGroup = YokingGroupEnum::YOKING_GROUP_OFF; // int32_t rowOrColumnIndex = -1; // yokeEvent->getApplyYokingSelections(yokingGroup, // rowOrColumnIndex); // if ((yokingGroup != YokingGroupEnum::YOKING_GROUP_OFF) // && (rowOrColumnIndex >= 0)) { // int32_t numRows = -1; // int32_t numCols = -1; // getMatrixDimensions(numRows, // numCols); // switch (getMatrixLoadingDimension()) { // case ChartMatrixLoadingDimensionEnum::CHART_MATRIX_LOADING_BY_COLUMN: // if (rowOrColumnIndex < numCols) { // loadDataForColumnIndex(rowOrColumnIndex); // } // else { // } // break; // case ChartMatrixLoadingDimensionEnum::CHART_MATRIX_LOADING_BY_ROW: // if (rowOrColumnIndex < numRows) { // loadDataForRowIndex(rowOrColumnIndex); // } // break; // } // } } break; case EventChartMatrixParcelYokingValidation::MODE_VALIDATE_YOKING: { const ConnectivityDataLoaded* connData = getConnectivityDataLoaded(); int64_t rowIndex = -1; int64_t columnIndex = -1; connData->getRowColumnLoading(rowIndex, columnIndex); int64_t selectedRowColumnIndex = -1; if (rowIndex >= 0) { selectedRowColumnIndex = rowIndex; } else if (columnIndex >= 0) { selectedRowColumnIndex = columnIndex; } yokeEvent->addValidateYokingChartableInterface(this, selectedRowColumnIndex); } break; } } } } /** * Get the matrix dimensions. * * @param numberOfRowsOut * Number of rows in the matrix. * @param numberOfColumnsOut * Number of columns in the matrix. */ void CiftiConnectivityMatrixParcelFile::getMatrixDimensions(int32_t& numberOfRowsOut, int32_t& numberOfColumnsOut) const { helpMapFileGetMatrixDimensions(numberOfRowsOut, numberOfColumnsOut); } /** * Get the matrix RGBA coloring for this matrix data creator. * * @param numberOfRowsOut * Number of rows in the coloring matrix. * @param numberOfColumnsOut * Number of rows in the coloring matrix. * @param rgbaOut * RGBA coloring output with number of elements * (numberOfRowsOut * numberOfColumnsOut * 4). * @return * True if data output data is valid, else false. */ bool CiftiConnectivityMatrixParcelFile::getMatrixDataRGBA(int32_t& numberOfRowsOut, int32_t& numberOfColumnsOut, std::vector& rgbaOut) const { CiftiParcelLabelFile* parcelLabelFile = NULL; int32_t parcelLabelFileMapIndex = -1; bool enabled = false; std::vector parcelLabelFiles; getSelectedParcelLabelFileAndMapForReordering(parcelLabelFiles, parcelLabelFile, parcelLabelFileMapIndex, enabled); std::vector rowIndices; if (enabled) { const CiftiParcelReordering* parcelReordering = getParcelReordering(parcelLabelFile, parcelLabelFileMapIndex); if (parcelReordering != NULL) { rowIndices = parcelReordering->getReorderedParcelIndices(); } } return helpMatrixFileLoadChartDataMatrixRGBA(numberOfRowsOut, numberOfColumnsOut, rowIndices, rgbaOut); } /** * Get the value, row name, and column name for a cell in the matrix. * * @param rowIndex * The row index. * @param columnIndex * The column index. * @param cellValueOut * Output containing value in the cell. * @param rowNameOut * Name of row corresponding to row index. * @param columnNameOut * Name of column corresponding to column index. * @return * True if the output values are valid (valid row/column indices). */ bool CiftiConnectivityMatrixParcelFile::getMatrixCellAttributes(const int32_t rowIndex, const int32_t columnIndex, AString& cellValueOut, AString& rowNameOut, AString& columnNameOut) const { if ((rowIndex >= 0) && (rowIndex < m_ciftiFile->getNumberOfRows()) && (columnIndex >= 0) && (columnIndex < m_ciftiFile->getNumberOfColumns())) { const CiftiXML& xml = m_ciftiFile->getCiftiXML(); const std::vector& rowsParcelsMap = xml.getParcelsMap(CiftiXML::ALONG_COLUMN).getParcels(); CaretAssertVectorIndex(rowsParcelsMap, rowIndex); rowNameOut = rowsParcelsMap[rowIndex].m_name; const std::vector& columnsParcelsMap = xml.getParcelsMap(CiftiXML::ALONG_ROW).getParcels(); CaretAssertVectorIndex(columnsParcelsMap, columnIndex); columnNameOut = columnsParcelsMap[columnIndex].m_name; const int32_t numberOfElementsInRow = m_ciftiFile->getNumberOfColumns(); std::vector rowData(numberOfElementsInRow); m_ciftiFile->getRow(&rowData[0], rowIndex); CaretAssertVectorIndex(rowData, columnIndex); cellValueOut = AString::number(rowData[columnIndex], 'f', 6); return true; } return false; } /** * @return Is charting enabled for this file? */ bool CiftiConnectivityMatrixParcelFile::isMatrixChartingEnabled(const int32_t tabIndex) const { CaretAssertArrayIndex(m_chartingEnabledForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_chartingEnabledForTab[tabIndex]; } /** * @return Return true if the file's current state supports * charting data, else false. Typically a brainordinate file * is chartable if it contains more than one map. */ bool CiftiConnectivityMatrixParcelFile::isMatrixChartingSupported() const { return true; } /** * Set charting enabled for this file. * * @param enabled * New status for charting enabled. */ void CiftiConnectivityMatrixParcelFile::setMatrixChartingEnabled(const int32_t tabIndex, const bool enabled) { CaretAssertArrayIndex(m_chartingEnabledForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_chartingEnabledForTab[tabIndex] = enabled; } /** * Get chart data types supported by the file. * * @param chartDataTypesOut * Chart types supported by this file. */ void CiftiConnectivityMatrixParcelFile::getSupportedMatrixChartDataTypes(std::vector& chartDataTypesOut) const { chartDataTypesOut.clear(); chartDataTypesOut.push_back(ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_LAYER); } /** * @return Chart matrix display properties (const method). */ const ChartMatrixDisplayProperties* CiftiConnectivityMatrixParcelFile::getChartMatrixDisplayProperties(const int32_t tabIndex) const { CaretAssertArrayIndex(m_chartMatrixDisplayProperties, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_chartMatrixDisplayProperties[tabIndex]; } /** * @return Chart matrix display properties. */ ChartMatrixDisplayProperties* CiftiConnectivityMatrixParcelFile::getChartMatrixDisplayProperties(const int32_t tabIndex) { CaretAssertArrayIndex(m_chartMatrixDisplayProperties, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_chartMatrixDisplayProperties[tabIndex]; } /** * Save subclass data to the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass to which data members should be added. Will always * be valid (non-NULL). */ void CiftiConnectivityMatrixParcelFile::saveSubClassDataToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass) { m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); sceneClass->addBooleanArray("m_chartingEnabledForTab", m_chartingEnabledForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS); /* * Save chart matrix properties */ SceneObjectMapIntegerKey* chartMatrixPropertiesMap = new SceneObjectMapIntegerKey("m_chartMatrixDisplayPropertiesMap", SceneObjectDataTypeEnum::SCENE_CLASS); const std::vector tabIndices = sceneAttributes->getIndicesOfTabsForSavingToScene(); for (std::vector::const_iterator tabIter = tabIndices.begin(); tabIter != tabIndices.end(); tabIter++) { const int32_t tabIndex = *tabIter; chartMatrixPropertiesMap->addClass(tabIndex, m_chartMatrixDisplayProperties[tabIndex]->saveToScene(sceneAttributes, "m_chartMatrixDisplayProperties")); } sceneClass->addChild(chartMatrixPropertiesMap); } /** * Restore file data from the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass for the instance of a class that implements * this interface. Will NEVER be NULL. */ void CiftiConnectivityMatrixParcelFile::restoreSubClassDataFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); // CiftiMappableConnectivityMatrixDataFile::restoreFileDataFromScene(sceneAttributes, // sceneClass); for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_chartingEnabledForTab[i] = false; } const ScenePrimitiveArray* tabArray = sceneClass->getPrimitiveArray("m_chartingEnabledForTab"); if (tabArray != NULL) { sceneClass->getBooleanArrayValue("m_chartingEnabledForTab", m_chartingEnabledForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS); } else { /* * Obsolete value when charting was not 'per tab' */ const bool chartingEnabled = sceneClass->getBooleanValue("m_chartingEnabled", false); for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_chartingEnabledForTab[i] = chartingEnabled; } } /* * Restore chart matrix properties */ const SceneObjectMapIntegerKey* chartMatrixPropertiesMap = sceneClass->getMapIntegerKey("m_chartMatrixDisplayPropertiesMap"); if (chartMatrixPropertiesMap != NULL) { const std::vector tabIndices = chartMatrixPropertiesMap->getKeys(); for (std::vector::const_iterator tabIter = tabIndices.begin(); tabIter != tabIndices.end(); tabIter++) { const int32_t tabIndex = *tabIter; const SceneClass* sceneClass = chartMatrixPropertiesMap->classValue(tabIndex); m_chartMatrixDisplayProperties[tabIndex]->restoreFromScene(sceneAttributes, sceneClass); } } } ///** // * Save file data from the scene. For subclasses that need to // * save to a scene, this method should be overriden. sceneClass // * will be valid and any scene data should be added to it. // * // * @param sceneAttributes // * Attributes for the scene. Scenes may be of different types // * (full, generic, etc) and the attributes should be checked when // * restoring the scene. // * // * @param sceneClass // * sceneClass to which data members should be added. // */ //void //CiftiConnectivityMatrixParcelFile::saveFileDataToScene(const SceneAttributes* sceneAttributes, // SceneClass* sceneClass) //{ // CiftiMappableConnectivityMatrixDataFile::saveFileDataToScene(sceneAttributes, // sceneClass); // // m_sceneAssistant->saveMembers(sceneAttributes, // sceneClass); // // sceneClass->addBooleanArray("m_chartingEnabledForTab", // m_chartingEnabledForTab, // BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS); // // /* // * Save chart matrix properties // */ // SceneObjectMapIntegerKey* chartMatrixPropertiesMap = new SceneObjectMapIntegerKey("m_chartMatrixDisplayPropertiesMap", // SceneObjectDataTypeEnum::SCENE_CLASS); // const std::vector tabIndices = sceneAttributes->getIndicesOfTabsForSavingToScene(); // for (std::vector::const_iterator tabIter = tabIndices.begin(); // tabIter != tabIndices.end(); // tabIter++) { // const int32_t tabIndex = *tabIter; // // chartMatrixPropertiesMap->addClass(tabIndex, // m_chartMatrixDisplayProperties[tabIndex]->saveToScene(sceneAttributes, // "m_chartMatrixDisplayProperties")); // } // sceneClass->addChild(chartMatrixPropertiesMap); //} // ///** // * Restore file data from the scene. For subclasses that need to // * restore from a scene, this method should be overridden. The scene class // * will be valid and any scene data may be obtained from it. // * // * @param sceneAttributes // * Attributes for the scene. Scenes may be of different types // * (full, generic, etc) and the attributes should be checked when // * restoring the scene. // * // * @param sceneClass // * sceneClass for the instance of a class that implements // * this interface. Will NEVER be NULL. // */ //void //CiftiConnectivityMatrixParcelFile::restoreFileDataFromScene(const SceneAttributes* sceneAttributes, // const SceneClass* sceneClass) //{ // CiftiMappableConnectivityMatrixDataFile::restoreFileDataFromScene(sceneAttributes, // sceneClass); // // m_sceneAssistant->restoreMembers(sceneAttributes, // sceneClass); // // /* // * The chart loading type is restored by the scene assistant. // * Swap its value so that calling setMatrixLoadingDimension requires // * the value to change for it to have any affect including // * setting size of data. // */ // switch (m_chartLoadingType) { // case ChartMatrixLoadingDimensionEnum::CHART_MATRIX_LOADING_BY_COLUMN: // m_chartLoadingType = ChartMatrixLoadingDimensionEnum::CHART_MATRIX_LOADING_BY_ROW; // break; // case ChartMatrixLoadingDimensionEnum::CHART_MATRIX_LOADING_BY_ROW: // m_chartLoadingType = ChartMatrixLoadingDimensionEnum::CHART_MATRIX_LOADING_BY_COLUMN; // break; // } // setMatrixLoadingDimension(m_chartLoadingType); // //// CiftiMappableConnectivityMatrixDataFile::restoreFileDataFromScene(sceneAttributes, //// sceneClass); // // for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { // m_chartingEnabledForTab[i] = false; // } // // const ScenePrimitiveArray* tabArray = sceneClass->getPrimitiveArray("m_chartingEnabledForTab"); // if (tabArray != NULL) { // sceneClass->getBooleanArrayValue("m_chartingEnabledForTab", // m_chartingEnabledForTab, // BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS); // } // else { // /* // * Obsolete value when charting was not 'per tab' // */ // const bool chartingEnabled = sceneClass->getBooleanValue("m_chartingEnabled", // false); // for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { // m_chartingEnabledForTab[i] = chartingEnabled; // } // } // // /* // * Restore chart matrix properties // */ // const SceneObjectMapIntegerKey* chartMatrixPropertiesMap = sceneClass->getMapIntegerKey("m_chartMatrixDisplayPropertiesMap"); // if (chartMatrixPropertiesMap != NULL) { // const std::vector tabIndices = chartMatrixPropertiesMap->getKeys(); // for (std::vector::const_iterator tabIter = tabIndices.begin(); // tabIter != tabIndices.end(); // tabIter++) { // const int32_t tabIndex = *tabIter; // const SceneClass* sceneClass = chartMatrixPropertiesMap->classValue(tabIndex); // m_chartMatrixDisplayProperties[tabIndex]->restoreFromScene(sceneAttributes, // sceneClass); // } // } // //} /** * @return Coloring mode for selected parcel. */ CiftiParcelColoringModeEnum::Enum CiftiConnectivityMatrixParcelFile::getSelectedParcelColoringMode() const { return m_selectedParcelColoringMode; } /** * Set the coloring mode for selected parcel. * * @param coloringMode * New value for coloring mode. */ void CiftiConnectivityMatrixParcelFile::setSelectedParcelColoringMode(const CiftiParcelColoringModeEnum::Enum coloringMode) { m_selectedParcelColoringMode = coloringMode; } /** * @return Color for selected parcel. */ CaretColorEnum::Enum CiftiConnectivityMatrixParcelFile::getSelectedParcelColor() const { return m_selectedParcelColor; } /** * Set color for selected parcel. * * @param color * New color for selected parcel. */ void CiftiConnectivityMatrixParcelFile::setSelectedParcelColor(const CaretColorEnum::Enum color) { m_selectedParcelColor = color; } /** * Get the selected parcel label file used for reordering of parcels. * * @param compatibleParcelLabelFilesOut * All Parcel Label files that are compatible with file implementing * this interface * @param selectedParcelLabelFileOut * The selected parcel label file used for reordering the parcels. * May be NULL! * @param selectedParcelLabelFileMapIndexOut * Map index in the selected parcel label file. * @param enabledStatusOut * Enabled status of reordering. */ void CiftiConnectivityMatrixParcelFile::getSelectedParcelLabelFileAndMapForReordering(std::vector& compatibleParcelLabelFilesOut, CiftiParcelLabelFile* &selectedParcelLabelFileOut, int32_t& selectedParcelLabelFileMapIndexOut, bool& enabledStatusOut) const { m_parcelReorderingModel->getSelectedParcelLabelFileAndMapForReordering(compatibleParcelLabelFilesOut, selectedParcelLabelFileOut, selectedParcelLabelFileMapIndexOut, enabledStatusOut); } /** * Set the selected parcel label file used for reordering of parcels. * * @param selectedParcelLabelFile * The selected parcel label file used for reordering the parcels. * May be NULL! * @param selectedParcelLabelFileMapIndex * Map index in the selected parcel label file. * @param enabledStatus * Enabled status of reordering. */ void CiftiConnectivityMatrixParcelFile::setSelectedParcelLabelFileAndMapForReordering(CiftiParcelLabelFile* selectedParcelLabelFile, const int32_t selectedParcelLabelFileMapIndex, const bool enabledStatus) { m_parcelReorderingModel->setSelectedParcelLabelFileAndMapForReordering(selectedParcelLabelFile, selectedParcelLabelFileMapIndex, enabledStatus); } /** * Get the parcel reordering for the given map index that was created using * the given parcel label file and its map index. * * @param parcelLabelFile * The selected parcel label file used for reordering the parcels. * @param parcelLabelFileMapIndex * Map index in the selected parcel label file. * @return * Pointer to parcel reordering or NULL if not found. */ const CiftiParcelReordering* CiftiConnectivityMatrixParcelFile::getParcelReordering(const CiftiParcelLabelFile* parcelLabelFile, const int32_t parcelLabelFileMapIndex) const { return m_parcelReorderingModel->getParcelReordering(parcelLabelFile, parcelLabelFileMapIndex); } /** * Create the parcel reordering for the given map index using * the given parcel label file and its map index. * * @param parcelLabelFile * The selected parcel label file used for reordering the parcels. * @param parcelLabelFileMapIndex * Map index in the selected parcel label file. * @param ciftiParcelsMap * The CIFTI parcels map that will or has been reordered. * @param errorMessageOut * Error message output. Will only be non-empty if NULL is returned. * @return * Pointer to parcel reordering or NULL if not found. */ bool CiftiConnectivityMatrixParcelFile::createParcelReordering(const CiftiParcelLabelFile* parcelLabelFile, const int32_t parcelLabelFileMapIndex, AString& errorMessageOut) { return m_parcelReorderingModel->createParcelReordering(parcelLabelFile, parcelLabelFileMapIndex, errorMessageOut); } /** * @return True if loading attributes (column/row, yoking) are * supported by this file type. */ bool CiftiConnectivityMatrixParcelFile::isSupportsLoadingAttributes() { return true; } /** * @return The matrix loading type (by row/column). */ ChartMatrixLoadingDimensionEnum::Enum CiftiConnectivityMatrixParcelFile::getMatrixLoadingDimension() const { return getChartMatrixLoadingDimension(); } /** * Set the matrix loading type (by row/column). * * @param matrixLoadingType * New value for matrix loading type. */ void CiftiConnectivityMatrixParcelFile::setMatrixLoadingDimension(const ChartMatrixLoadingDimensionEnum::Enum matrixLoadingType) { /* * Ignore when the loading dimension does not change */ if (matrixLoadingType != getMatrixLoadingDimension()) { setChartMatrixLoadingDimension(matrixLoadingType); } } /** * @return Selected yoking group. */ YokingGroupEnum::Enum CiftiConnectivityMatrixParcelFile::getYokingGroup() const { return m_chartLoadingYokingGroup; } /** * Set the selected yoking group. * * @param yokingGroup * New value for yoking group. */ void CiftiConnectivityMatrixParcelFile::setYokingGroup(const YokingGroupEnum::Enum yokingGroup) { m_chartLoadingYokingGroup = yokingGroup; if (m_chartLoadingYokingGroup == YokingGroupEnum::YOKING_GROUP_OFF) { return; } /* * Updated selected row/column to match yoking. */ } workbench-1.1.1/src/Files/CiftiConnectivityMatrixParcelFile.h000066400000000000000000000153021255417355300242620ustar00rootroot00000000000000#ifndef __CIFTI_CONNECTIVITY_MATRIX_PARCEL_FILE_H__ #define __CIFTI_CONNECTIVITY_MATRIX_PARCEL_FILE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "BrainConstants.h" #include "CiftiMappableConnectivityMatrixDataFile.h" #include "ChartableMatrixParcelInterface.h" #include "EventListenerInterface.h" namespace caret { class ChartMatrixDisplayProperties; class CiftiParcelReorderingModel; class PaletteFile; class CiftiConnectivityMatrixParcelFile : public CiftiMappableConnectivityMatrixDataFile, public ChartableMatrixParcelInterface, public EventListenerInterface { public: CiftiConnectivityMatrixParcelFile(); virtual ~CiftiConnectivityMatrixParcelFile(); private: CiftiConnectivityMatrixParcelFile(const CiftiConnectivityMatrixParcelFile&); CiftiConnectivityMatrixParcelFile& operator=(const CiftiConnectivityMatrixParcelFile&); public: virtual void receiveEvent(Event* event); virtual void getMatrixDimensions(int32_t& numberOfRowsOut, int32_t& numberOfColumnsOut) const; virtual bool getMatrixDataRGBA(int32_t& numberOfRowsOut, int32_t& numberOfColumnsOut, std::vector& rgbaOut) const; virtual bool getMatrixCellAttributes(const int32_t rowIndex, const int32_t columnIndex, AString& cellValueOut, AString& rowNameOut, AString& columnNameOut) const; virtual bool isMatrixChartingEnabled(const int32_t tabIndex) const; virtual bool isMatrixChartingSupported() const; virtual void setMatrixChartingEnabled(const int32_t tabIndex, const bool enabled); virtual void getSupportedMatrixChartDataTypes(std::vector& chartDataTypesOut) const; const ChartMatrixDisplayProperties* getChartMatrixDisplayProperties(const int32_t tabIndex) const; ChartMatrixDisplayProperties* getChartMatrixDisplayProperties(const int32_t tabIndex); virtual CiftiParcelColoringModeEnum::Enum getSelectedParcelColoringMode() const; virtual void setSelectedParcelColoringMode(const CiftiParcelColoringModeEnum::Enum coloringMode); virtual CaretColorEnum::Enum getSelectedParcelColor() const; virtual void setSelectedParcelColor(const CaretColorEnum::Enum color); virtual void getSelectedParcelLabelFileAndMapForReordering(std::vector& compatibleParcelLabelFilesOut, CiftiParcelLabelFile* &selectedParcelLabelFileOut, int32_t& selectedParcelLabelFileMapIndexOut, bool& enabledStatusOut) const; virtual void setSelectedParcelLabelFileAndMapForReordering(CiftiParcelLabelFile* selectedParcelLabelFile, const int32_t selectedParcelLabelFileMapIndex, const bool enabledStatus); virtual bool createParcelReordering(const CiftiParcelLabelFile* parcelLabelFile, const int32_t parcelLabelFileMapIndex, AString& errorMessageOut); virtual const CiftiParcelReordering* getParcelReordering(const CiftiParcelLabelFile* parcelLabelFile, const int32_t parcelLabelFileMapIndex) const; virtual bool isSupportsLoadingAttributes(); virtual ChartMatrixLoadingDimensionEnum::Enum getMatrixLoadingDimension() const; virtual void setMatrixLoadingDimension(const ChartMatrixLoadingDimensionEnum::Enum matrixLoadingType); virtual YokingGroupEnum::Enum getYokingGroup() const; virtual void setYokingGroup(const YokingGroupEnum::Enum yokingType); public: // ADD_NEW_METHODS_HERE protected: virtual void saveSubClassDataToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass); virtual void restoreSubClassDataFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); // virtual void saveFileDataToScene(const SceneAttributes* sceneAttributes, // SceneClass* sceneClass); // // virtual void restoreFileDataFromScene(const SceneAttributes* sceneAttributes, // const SceneClass* sceneClass); private: // ADD_NEW_MEMBERS_HERE SceneClassAssistant* m_sceneAssistant; bool m_chartingEnabledForTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; ChartMatrixDisplayProperties* m_chartMatrixDisplayProperties[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; CiftiParcelColoringModeEnum::Enum m_selectedParcelColoringMode; CaretColorEnum::Enum m_selectedParcelColor; CiftiParcelReorderingModel* m_parcelReorderingModel; YokingGroupEnum::Enum m_chartLoadingYokingGroup; }; #ifdef __CIFTI_CONNECTIVITY_MATRIX_PARCEL_FILE_DECLARE__ // #endif // __CIFTI_CONNECTIVITY_MATRIX_PARCEL_FILE_DECLARE__ } // namespace #endif //__CIFTI_CONNECTIVITY_MATRIX_PARCEL_FILE_H__ workbench-1.1.1/src/Files/CiftiFiberOrientationFile.cxx000066400000000000000000000403741255417355300231150ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CIFTI_FIBER_ORIENTATION_FILE_DECLARE__ #include "CiftiFiberOrientationFile.h" #undef __CIFTI_FIBER_ORIENTATION_FILE_DECLARE__ #include "CaretAssert.h" #include "CiftiFile.h" #include "CiftiMappableDataFile.h" #include "CaretLogger.h" #include "DataFileException.h" #include "Fiber.h" #include "FiberOrientation.h" #include "GiftiMetaData.h" #include "MathFunctions.h" #include using namespace caret; /** * \class caret::CiftiFiberOrientationFile * \brief Data file for Fiber Orientations * */ /** * Constructor. */ CiftiFiberOrientationFile::CiftiFiberOrientationFile() : CaretDataFile(DataFileTypeEnum::CONNECTIVITY_FIBER_ORIENTATIONS_TEMPORARY) { m_metadata = new GiftiMetaData(); m_ciftiXML = NULL; for (int32_t i = 0; i < DisplayGroupEnum::NUMBER_OF_GROUPS; i++) { m_displayStatusInDisplayGroup[i] = true; } for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_displayStatusInTab[i] = true; } } /** * Destructor. */ CiftiFiberOrientationFile::~CiftiFiberOrientationFile() { clearPrivate(); delete m_metadata; } /** * Cleare data in this file. */ void CiftiFiberOrientationFile::clear() { CaretDataFile::clear(); clearPrivate(); } /** * Cleare data in this file but not the parent class. */ void CiftiFiberOrientationFile::clearPrivate() { m_metadata->clear(); if (m_ciftiXML != NULL) { delete m_ciftiXML; m_ciftiXML = NULL; } for (std::vector::iterator iter = m_fiberOrientations.begin(); iter != m_fiberOrientations.end(); iter++) { delete *iter; } m_fiberOrientations.clear(); } /** * @return True if the file is empty. */ bool CiftiFiberOrientationFile::isEmpty() const { return true; } /** * @return The structure for this file. */ StructureEnum::Enum CiftiFiberOrientationFile::getStructure() const { return StructureEnum::ALL; } /** * Set the structure for this file. * @param structure * New structure for this file. */ void CiftiFiberOrientationFile::setStructure(const StructureEnum::Enum /*structure*/) { /* nothing */ } /** * @return Get access to the file's metadata. */ GiftiMetaData* CiftiFiberOrientationFile::getFileMetaData() { return m_metadata; } /** * @return Get access to unmodifiable file's metadata. */ const GiftiMetaData* CiftiFiberOrientationFile::getFileMetaData() const { return m_metadata; } /** * Initialize with test data. */ void CiftiFiberOrientationFile::initializeWithTestData() { const int64_t fiberDataSizeInFloats = (Fiber::NUMBER_OF_ELEMENTS_PER_FIBER_IN_FILE * 3) + 3; { float* fiberData = new float[fiberDataSizeInFloats]; int64_t offset = 0; /* * Coordinate of fiber orientation */ fiberData[offset+0] = 12.8; fiberData[offset+1] = 125.8; fiberData[offset+2] = 2.4; offset += 3; /* * Along Positive X-Axis */ fiberData[offset+0] = 0.3; // meanF fiberData[offset+1] = 2.0; // varF fiberData[offset+2] = MathFunctions::toRadians(90.0); // theta fiberData[offset+3] = 0.0; // phi fiberData[offset+4] = MathFunctions::toRadians(50.0); // k1 fiberData[offset+5] = MathFunctions::toRadians(10.0); // k2 fiberData[offset+6] = 0.0; // psi offset += 7; /* * Along Positive Y-Axis */ fiberData[offset+0] = 0.6; // meanF fiberData[offset+1] = 2.0; // varF fiberData[offset+2] = MathFunctions::toRadians(90.0); // theta fiberData[offset+3] = MathFunctions::toRadians(90.0); // phi fiberData[offset+4] = MathFunctions::toRadians(50.0); // k1 fiberData[offset+5] = MathFunctions::toRadians(10.0); // k2 fiberData[offset+6] = MathFunctions::toRadians(20.0); // psi offset += 7; /* * Along Positive Z-Axis */ fiberData[offset+0] = 1.0; // meanF fiberData[offset+1] = 2.0; // varF fiberData[offset+2] = 0.0; // theta fiberData[offset+3] = 0.0; // phi fiberData[offset+4] = MathFunctions::toRadians(20.0); // k1 fiberData[offset+5] = MathFunctions::toRadians(10.0); // k2 fiberData[offset+6] = MathFunctions::toRadians(70.0); // psi offset += 7; FiberOrientation* fiberOrientation = new FiberOrientation(3, fiberData); m_fiberOrientations.push_back(fiberOrientation); } { float* fiberData = new float[fiberDataSizeInFloats]; int64_t offset = 0; /* * Coordinate of fiber orientation */ fiberData[offset+0] = -60.8; fiberData[offset+1] = -55.8; fiberData[offset+2] = -2.4; offset += 3; /* * Pointing towards forward right and up */ fiberData[offset+0] = 0.3; // meanF fiberData[offset+1] = 2.0; // varF fiberData[offset+2] = MathFunctions::toRadians(45.0); // theta fiberData[offset+3] = MathFunctions::toRadians(45.0); // phi fiberData[offset+4] = MathFunctions::toRadians(40.0); // k1 fiberData[offset+5] = MathFunctions::toRadians(10.0); // k2 fiberData[offset+6] = 0.0; // psi offset += 7; /* * Pointing towards forward left and down */ fiberData[offset+0] = 0.6; // meanF fiberData[offset+1] = 2.0; // varF fiberData[offset+2] = MathFunctions::toRadians(45.0); // theta fiberData[offset+3] = MathFunctions::toRadians(135.0); // phi fiberData[offset+4] = MathFunctions::toRadians(20.0); // k1 fiberData[offset+5] = MathFunctions::toRadians(15.0); // k2 fiberData[offset+6] = 0.0; // psi offset += 7; /* * Pointing towards backward right and up */ fiberData[offset+0] = 1.0; // meanF fiberData[offset+1] = 2.0; // varF fiberData[offset+2] = MathFunctions::toRadians( 45.0); // theta fiberData[offset+3] = MathFunctions::toRadians(-45.0); // phi fiberData[offset+4] = MathFunctions::toRadians(40.0); // k1 fiberData[offset+5] = MathFunctions::toRadians(20.0); // k2 fiberData[offset+6] = MathFunctions::toRadians(25.0); // psi offset += 7; FiberOrientation* fiberOrientation = new FiberOrientation(3, fiberData); m_fiberOrientations.push_back(fiberOrientation); } } /** * @return The number of orientation fiber groups. */ int64_t CiftiFiberOrientationFile::getNumberOfFiberOrientations() const { return m_fiberOrientations.size(); } /** * Get the orientation fiber group at the given index. * @param indx * Index of the desired fiber orientation group. */ FiberOrientation* CiftiFiberOrientationFile::getFiberOrientations(const int64_t indx) { return m_fiberOrientations[indx]; } /** * Get Fiber orientation nearest coordinate and within the maximum * distance. * * @param xyz * The coordinate. * @param maximumDistance * The maximum distance. If negative, any distance is allowed. * * @return Fiber found or NULL if not found. */ FiberOrientation* CiftiFiberOrientationFile::getFiberOrientationNearestCoordinate(const float xyz[3], const float maximumDistance) const { FiberOrientation* nearestFiberOrientation = NULL; float nearestDistance = std::numeric_limits::max(); const int64_t numFiberOrientations = getNumberOfFiberOrientations(); for (int64_t i = 0; i < numFiberOrientations; i++) { const float distance = MathFunctions::distanceSquared3D(xyz, m_fiberOrientations[i]->m_xyz); if (distance < nearestDistance) { if (maximumDistance > 0.0) { if (distance > maximumDistance) { continue; } } nearestDistance = distance; nearestFiberOrientation = m_fiberOrientations[i]; } } return nearestFiberOrientation; } /** * Get the orientation fiber group at the given index. * @param indx * Index of the desired fiber orientation group. */ const FiberOrientation* CiftiFiberOrientationFile::getFiberOrientations(const int64_t indx) const { return m_fiberOrientations[indx]; } /** * @return The display status. */ bool CiftiFiberOrientationFile::isDisplayed(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const { const int32_t displayIndex = (int32_t)displayGroup; CaretAssertArrayIndex(m_displayStatusInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, displayIndex); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_displayStatusInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_displayStatusInTab[tabIndex]; } return m_displayStatusInDisplayGroup[displayIndex]; } /** * Set the display status. * @param displayed * New display status. */ void CiftiFiberOrientationFile::setDisplayed(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const bool displayed) { const int32_t displayIndex = (int32_t)displayGroup; CaretAssertArrayIndex(m_displayStatusInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, displayIndex); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_displayStatusInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_displayStatusInTab[tabIndex] = displayed; } else { m_displayStatusInDisplayGroup[displayIndex] = displayed; } } /** * Get the volume spacing. * @param volumeSpacingOut * Will contain volume spacing for (I, J, K) axes upon exit. */ void CiftiFiberOrientationFile::getVolumeSpacing(float volumeSpacingOut[3]) const { volumeSpacingOut[0] = m_volumeSpacing[0]; volumeSpacingOut[1] = m_volumeSpacing[1]; volumeSpacingOut[2] = m_volumeSpacing[2]; } /** * @return a pointer to the CIFTI XML. * May be NULL if a file is not loaded. */ const CiftiXML* CiftiFiberOrientationFile::getCiftiXML() const { return m_ciftiXML; } /** * Read the data file. * * @param filename * Name of the data file. * @throws DataFileException * If the file was not successfully read. */ void CiftiFiberOrientationFile::readFile(const AString& filename) { clear(); checkFileReadability(filename); try { CiftiFile ciftiFile; ciftiFile.openFile(filename); ciftiFile.convertToInMemory(); const int64_t numRows = ciftiFile.getNumberOfRows(); if (numRows <= 0) { throw DataFileException(getFileNameNoPath() + " does not contain any data (no rows)"); } const int64_t numCols = ciftiFile.getNumberOfColumns(); if (numCols <= 0) { throw DataFileException(getFileNameNoPath() + " does not contain any data (no columns)"); } /* * Each set of fibers contains XYZ (3 elements) * plus number of elements per fiber. */ const int64_t numberOfFibers = ((numCols - FiberOrientation::NUMBER_OF_ELEMENTS_IN_FILE) / Fiber::NUMBER_OF_ELEMENTS_PER_FIBER_IN_FILE); const int64_t expectedNumberOfColumns = (numberOfFibers * Fiber::NUMBER_OF_ELEMENTS_PER_FIBER_IN_FILE) + FiberOrientation::NUMBER_OF_ELEMENTS_IN_FILE; if (expectedNumberOfColumns != numCols) { throw DataFileException(filename, "Validation of column count failed: expected " + AString::number(expectedNumberOfColumns) + " but have " + AString::number(numCols) + " columns."); } /* * Create the fiber groups */ std::vector rowData(numCols); float* rowPointer = &rowData[0]; m_fiberOrientations.reserve(numRows); for (int64_t i = 0; i < numRows; i++) { ciftiFile.getRow(rowPointer, i); FiberOrientation* fiberOrient = new FiberOrientation(numberOfFibers, rowPointer); if (fiberOrient->m_valid) { m_fiberOrientations.push_back(fiberOrient); } else { CaretLogSevere("Fiber invalid at row " + QString::number(i) + " is invalid: " + fiberOrient->m_invalidMessage); delete fiberOrient; } } const CiftiXML& ciftiXML = ciftiFile.getCiftiXML(); m_ciftiXML = new CiftiXML(ciftiXML); VolumeSpace::OrientTypes orient[3]; float origin[3]; if (ciftiXML.getMappingType(CiftiXML::ALONG_COLUMN) != CiftiMappingType::BRAIN_MODELS) throw DataFileException(getFileNameNoPath() + " does not have brain models along column"); const CiftiBrainModelsMap& myMap = ciftiXML.getBrainModelsMap(CiftiXML::ALONG_COLUMN); if (!myMap.hasVolumeData()) throw DataFileException(getFileNameNoPath() + " has no volume data, cannot be a fiber orientation file"); myMap.getVolumeSpace().getOrientAndSpacingForPlumb(orient, m_volumeSpacing, origin);//NOTE: will assert/throw if not plumb setFileName(filename); clearModified(); } catch (const DataFileException& dfe) { clear(); throw dfe; } } /** * Add information about the file to the data file information. * * @param dataFileInformation * Consolidates information about a data file. */ void CiftiFiberOrientationFile::addToDataFileContentInformation(DataFileContentInformation& dataFileInformation) { CaretDataFile::addToDataFileContentInformation(dataFileInformation); if (m_ciftiXML != NULL) { CiftiMappableDataFile::addCiftiXmlToDataFileContentInformation(dataFileInformation, *m_ciftiXML); } } /** * Write the data file. * * @param filename * Name of the data file. * @throws DataFileException * If the file was not successfully written. */ void CiftiFiberOrientationFile::writeFile(const AString& filename) { throw DataFileException(filename, "Writing of Cifti Orientation Files not supported."); } /** * @return True if this file type supports writing, else false. * * Fiber orientation files do NOT support writing. */ bool CiftiFiberOrientationFile::supportsWriting() const { return false; } workbench-1.1.1/src/Files/CiftiFiberOrientationFile.h000066400000000000000000000073051255417355300225370ustar00rootroot00000000000000#ifndef __CIFTI_FIBER_ORIENTATION_FILE_H__ #define __CIFTI_FIBER_ORIENTATION_FILE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "BrainConstants.h" #include "CaretDataFile.h" #include "DisplayGroupEnum.h" namespace caret { class CiftiXML; class FiberOrientation; class CiftiFiberOrientationFile : public CaretDataFile { public: CiftiFiberOrientationFile(); virtual ~CiftiFiberOrientationFile(); void initializeWithTestData(); int64_t getNumberOfFiberOrientations() const; FiberOrientation* getFiberOrientations(const int64_t indx); const FiberOrientation* getFiberOrientations(const int64_t indx) const; FiberOrientation* getFiberOrientationNearestCoordinate(const float xyz[3], const float maximumDistance) const; void getVolumeSpacing(float volumeSpacingOut[3]) const; void setDisplayed(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const bool displayed); bool isDisplayed(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const; virtual void clear(); bool isEmpty() const; const CiftiXML* getCiftiXML() const; virtual StructureEnum::Enum getStructure() const; virtual void setStructure(const StructureEnum::Enum structure); virtual GiftiMetaData* getFileMetaData(); virtual const GiftiMetaData* getFileMetaData() const; virtual void readFile(const AString& filename); virtual void writeFile(const AString& filename); bool supportsWriting() const; void addToDataFileContentInformation(DataFileContentInformation& dataFileInformation); // ADD_NEW_METHODS_HERE private: CiftiFiberOrientationFile(const CiftiFiberOrientationFile&); CiftiFiberOrientationFile& operator=(const CiftiFiberOrientationFile&); private: void clearPrivate(); CiftiXML* m_ciftiXML; GiftiMetaData* m_metadata; std::vector m_fiberOrientations; /** Display status in display group */ bool m_displayStatusInDisplayGroup[DisplayGroupEnum::NUMBER_OF_GROUPS]; /** Display status in tab */ bool m_displayStatusInTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; float m_volumeSpacing[3]; // ADD_NEW_MEMBERS_HERE }; #ifdef __CIFTI_FIBER_ORIENTATION_FILE_DECLARE__ // #endif // __CIFTI_FIBER_ORIENTATION_FILE_DECLARE__ } // namespace #endif //__CIFTI_FIBER_ORIENTATION_FILE_H__ workbench-1.1.1/src/Files/CiftiFiberTrajectoryFile.cxx000066400000000000000000001732331255417355300227510ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #define __CIFTI_FIBER_TRAJECTORY_FILE_DECLARE__ #include "CiftiFiberTrajectoryFile.h" #undef __CIFTI_FIBER_TRAJECTORY_FILE_DECLARE__ #include "CaretAssert.h" #include "CaretLogger.h" #include "CaretSparseFile.h" #include "CiftiFiberOrientationFile.h" #include "CiftiMappableDataFile.h" #include "ConnectivityDataLoaded.h" #include "DataFileContentInformation.h" #include "EventManager.h" #include "EventProgressUpdate.h" #include "FiberOrientationTrajectory.h" #include "FiberTrajectoryMapProperties.h" #include "FileInformation.h" #include "GiftiMetaData.h" #include "PaletteColorMapping.h" #include "SceneClass.h" #include "SceneClassAssistant.h" using namespace caret; /** * \class caret::CiftiFiberTrajectoryFile * \brief File that contains trajectories */ /** * Constructor. */ CiftiFiberTrajectoryFile::CiftiFiberTrajectoryFile() : CaretMappableDataFile(DataFileTypeEnum::CONNECTIVITY_FIBER_TRAJECTORY_TEMPORARY) { m_connectivityDataLoaded = new ConnectivityDataLoaded(); m_fiberTrajectoryMapProperties = new FiberTrajectoryMapProperties(); m_metadata = new GiftiMetaData(); m_sparseFile = NULL; m_matchingFiberOrientationFile = NULL; m_matchingFiberOrientationFileName = ""; m_dataLoadingEnabled = true; m_fiberTrajectoryFileType = FIBER_TRAJECTORY_LOAD_BY_BRAINORDINATE; m_sceneAssistant = new SceneClassAssistant(); m_sceneAssistant->add("m_dataLoadingEnabled", &m_dataLoadingEnabled); m_sceneAssistant->add("m_matchingFiberOrientationFileName", &m_matchingFiberOrientationFileName); m_sceneAssistant->add("m_fiberTrajectoryMapProperties", "FiberTrajectoryMapProperties", m_fiberTrajectoryMapProperties); m_sceneAssistant->add("m_connectivityDataLoaded", "ConnectivityDataLoaded", m_connectivityDataLoaded); } /** * Destructor. */ CiftiFiberTrajectoryFile::~CiftiFiberTrajectoryFile() { clearPrivate(); delete m_fiberTrajectoryMapProperties; delete m_metadata; // DO NOT DELETE (owned by Brain): m_matchingFiberOrientationFile. delete m_sceneAssistant; delete m_connectivityDataLoaded; } /** * Cleare data in this file. */ void CiftiFiberTrajectoryFile::clear() { CaretMappableDataFile::clear(); clearPrivate(); } /** * Cleare data in this file but not the parent class. */ void CiftiFiberTrajectoryFile::clearPrivate() { m_metadata->clear(); clearLoadedFiberOrientations(); if (m_sparseFile != NULL) { delete m_sparseFile; m_sparseFile = NULL; } m_matchingFiberOrientationFile = NULL; m_matchingFiberOrientationFileName = ""; m_fiberTrajectoryFileType = FIBER_TRAJECTORY_LOAD_BY_BRAINORDINATE; } /** * @return True if the file is empty. */ bool CiftiFiberTrajectoryFile::isEmpty() const { if (m_sparseFile != NULL) { return false; } return true; } /** * @return Is data loading enabled? */ bool CiftiFiberTrajectoryFile::isDataLoadingEnabled() const { return m_dataLoadingEnabled; } /** * Set data loading enabled. * * @param loadingEnabled * New status of data loading. */ void CiftiFiberTrajectoryFile::setDataLoadingEnabled(const bool loadingEnabled) { m_dataLoadingEnabled = loadingEnabled; } /** * @return The selected matching fiber orientation file. May be NULL. */ const CiftiFiberOrientationFile* CiftiFiberTrajectoryFile::getMatchingFiberOrientationFile() const { return m_matchingFiberOrientationFile; } /** * @return The selected matching fiber orientation file. May be NULL. */ CiftiFiberOrientationFile* CiftiFiberTrajectoryFile::getMatchingFiberOrientationFile() { return m_matchingFiberOrientationFile; } /** * Is the given fiber orientation file compatible with this fiber trajectory file * * @param fiberOrientationFile * File tested for compatibilty * @return * True if file is compatible, else false. */ bool CiftiFiberTrajectoryFile::isFiberOrientationFileCombatible(const CiftiFiberOrientationFile* fiberOrientationFile) const { CaretAssert(fiberOrientationFile); const CiftiXML& trajXML = m_sparseFile->getCiftiXML(); const CiftiXML* orientXML = fiberOrientationFile->getCiftiXML(); if (*(trajXML.getMap(CiftiXML::ALONG_ROW)) == *(orientXML->getMap(CiftiXML::ALONG_COLUMN))) { return true; } return false; } /** * Set the selected matching fiber orientation file. No test of compatibility * is made. If this is a "single row" trajectory file, the data for the single * row is loaded. * * @param matchingFiberOrientationFile * New selection for matching fiber orientation file. */ void CiftiFiberTrajectoryFile::setMatchingFiberOrientationFile(CiftiFiberOrientationFile* matchingFiberOrientationFile) { m_matchingFiberOrientationFile = matchingFiberOrientationFile; if (m_matchingFiberOrientationFile != NULL) { // m_matchingFiberOrientationFileName = m_matchingFiberOrientationFile->getFileNameNoPath(); m_matchingFiberOrientationFileName = m_matchingFiberOrientationFile->getFileName(); switch (m_fiberTrajectoryFileType) { case FIBER_TRAJECTORY_LOAD_BY_BRAINORDINATE: break; case FIBER_TRAJECTORY_LOAD_SINGLE_ROW: loadDataForRowIndex(0); const CiftiXML& sparseXML = m_sparseFile->getCiftiXML(); if (sparseXML.getMappingType(CiftiXML::ALONG_COLUMN) == CiftiMappingType::SCALARS) { m_loadedDataDescriptionForMapName = sparseXML.getScalarsMap(CiftiXML::ALONG_COLUMN).getMapName(0); } else { m_loadedDataDescriptionForMapName = ""; } break; } } else { m_matchingFiberOrientationFileName = ""; } } /** * Update the matching fiber orientation file from the first compatible file in the list. * If none are found, the matching file will become NULL. If the current matching file * is valid, no action is taken. * * @param matchingFiberOrientationFiles * The fiber orientation files. */ void CiftiFiberTrajectoryFile::updateMatchingFiberOrientationFileFromList(std::vector matchingFiberOrientationFiles) { /* * If a scene has been restored, we want to match to the fiber orientation * file name that was restored from the scene */ if (m_matchingFiberOrientationFileNameFromRestoredScene.isEmpty() == false) { bool matched = false; CiftiFiberOrientationFile* matchedOrientFile = NULL; int64_t matchedOrientFileCount = 0; const FileInformation fileInfo(m_matchingFiberOrientationFileNameFromRestoredScene); const AString matchingFileNameNoPath = fileInfo.getFileName(); for (std::vector::iterator iter = matchingFiberOrientationFiles.begin(); iter != matchingFiberOrientationFiles.end(); iter++) { /* * Try and see if it matches for this file * (1) Verify compatibility * (2) Match name of file without path * (3) Match path starting at end of path */ CiftiFiberOrientationFile* orientationFile = *iter; CaretAssert(orientationFile); if (isFiberOrientationFileCombatible(orientationFile)) { if (matchingFileNameNoPath == orientationFile->getFileNameNoPath()) { const AString orientationFileName = orientationFile->getFileName(); const int64_t endMatchCount = orientationFileName.countMatchingCharactersFromEnd(m_matchingFiberOrientationFileNameFromRestoredScene); if (endMatchCount > matchedOrientFileCount) { matchedOrientFile = orientationFile; matchedOrientFileCount = endMatchCount; } } } // if (orientationFile->getFileNameNoPath() == m_matchingFiberOrientationFileNameFromRestoredScene) { // if (isFiberOrientationFileCombatible(orientationFile)) { // setMatchingFiberOrientationFile(orientationFile); // matched = true; // } // } } if (matchedOrientFileCount > 0) { setMatchingFiberOrientationFile(matchedOrientFile); } /* * Clear name so no attempt to use again */ m_matchingFiberOrientationFileNameFromRestoredScene = ""; if (matched) { return; } } /* * See if selected orientation file is still valid */ for (std::vector::iterator iter = matchingFiberOrientationFiles.begin(); iter != matchingFiberOrientationFiles.end(); iter++) { if (*iter == m_matchingFiberOrientationFile) { return; } } /* * Invalidate matching file */ m_matchingFiberOrientationFile = NULL; m_matchingFiberOrientationFileName = ""; clearLoadedFiberOrientations(); /* * Try to find a matching file */ for (std::vector::iterator iter = matchingFiberOrientationFiles.begin(); iter != matchingFiberOrientationFiles.end(); iter++) { /* * Try and see if it matches for this file */ if (isFiberOrientationFileCombatible(*iter)) { setMatchingFiberOrientationFile(*iter); return; } } } /** * @return The structure for this file. */ StructureEnum::Enum CiftiFiberTrajectoryFile::getStructure() const { return StructureEnum::ALL; } /** * Set the structure for this file. * @param structure * New structure for this file. */ void CiftiFiberTrajectoryFile::setStructure(const StructureEnum::Enum /*structure*/) { /* nothing */ } /** * @return Get access to the file's metadata. */ GiftiMetaData* CiftiFiberTrajectoryFile::getFileMetaData() { return m_metadata; } /** * @return Get access to unmodifiable file's metadata. */ const GiftiMetaData* CiftiFiberTrajectoryFile::getFileMetaData() const { return m_metadata; } /** * @return Is the data mappable to a surface? */ bool CiftiFiberTrajectoryFile::isSurfaceMappable() const { return false; } /** * @return Is the data mappable to a volume? */ bool CiftiFiberTrajectoryFile::isVolumeMappable() const { return true; } /** * @return The number of maps in the file. * Note: Caret5 used the term 'columns'. */ int32_t CiftiFiberTrajectoryFile::getNumberOfMaps() const { /* * Always return 1. * If zero is returned, it will never appear in the overlays because * zero is interpreted as "nothing available". */ return 1; } /** * @return True if the file has map attributes (name and metadata). * For files that do not have map attributes, they should override * this method and return false. If not overriden, this method * returns true. * * Some files (such as CIFTI Connectivity Matrix Files and CIFTI * Data-Series Files) do not have Map Attributes and thus there * is no map name nor map metadata and options to edit these * attributes should not be presented to the user. * * These CIFTI files do contain palette color mapping but it is * associated with the file. To simplify palette color mapping editing * these file will return the file's palette color mapping for any * calls to getMapPaletteColorMapping(). */ bool CiftiFiberTrajectoryFile::hasMapAttributes() const { return false; } /** * Get the name of the map at the given index. * * @param mapIndex * Index of the map. * @return * Name of the map. */ AString CiftiFiberTrajectoryFile::getMapName(const int32_t /*mapIndex*/) const { return m_loadedDataDescriptionForMapName; } /** * Set the name of the map at the given index. * * @param mapIndex * Index of the map. * @param mapName * New name for the map. */ void CiftiFiberTrajectoryFile::setMapName(const int32_t /*mapIndex*/, const AString& /*mapName*/) { } /** * Get the metadata for the map at the given index * * @param mapIndex * Index of the map. * @return * Metadata for the map (const value). */ const GiftiMetaData* CiftiFiberTrajectoryFile::getMapMetaData(const int32_t /*mapIndex*/) const { return getFileMetaData(); } /** * Get the metadata for the map at the given index * * @param mapIndex * Index of the map. * @return * Metadata for the map. */ GiftiMetaData* CiftiFiberTrajectoryFile::getMapMetaData(const int32_t /*mapIndex*/) { return getFileMetaData(); } /** * Get the unique ID (UUID) for the map at the given index. * * @param mapIndex * Index of the map. * @return * String containing UUID for the map. */ AString CiftiFiberTrajectoryFile::getMapUniqueID(const int32_t mapIndex) const { const GiftiMetaData* md = getMapMetaData(mapIndex); const AString uniqueID = md->getUniqueID(); return uniqueID; } /** * @return Is the data in the file mapped to colors using * a palette. */ bool CiftiFiberTrajectoryFile::isMappedWithPalette() const { return false; } /** * Get statistics describing the distribution of data * mapped with a color palette at the given index. * * @param mapIndex * Index of the map. * @return * Fast statistics for data (will be NULL for data * not mapped using a palette). */ const FastStatistics* CiftiFiberTrajectoryFile::getMapFastStatistics(const int32_t /*mapIndex*/) { return NULL; } /** * Get histogram describing the distribution of data * mapped with a color palette at the given index. * * @param mapIndex * Index of the map. * @return * Histogram for data (will be NULL for data * not mapped using a palette). */ const Histogram* CiftiFiberTrajectoryFile::getMapHistogram(const int32_t /*mapIndex*/) { return NULL; } /** * Get histogram describing the distribution of data * mapped with a color palette at the given index for * data within the given ranges. * * @param mapIndex * Index of the map. * @param mostPositiveValueInclusive * Values more positive than this value are excluded. * @param leastPositiveValueInclusive * Values less positive than this value are excluded. * @param leastNegativeValueInclusive * Values less negative than this value are excluded. * @param mostNegativeValueInclusive * Values more negative than this value are excluded. * @param includeZeroValues * If true zero values (very near zero) are included. * @return * Descriptive statistics for data (will be NULL for data * not mapped using a palette). */ const Histogram* CiftiFiberTrajectoryFile::getMapHistogram(const int32_t /*mapIndex*/, const float /*mostPositiveValueInclusive*/, const float /*leastPositiveValueInclusive*/, const float /*leastNegativeValueInclusive*/, const float /*mostNegativeValueInclusive*/, const bool /*includeZeroValues*/) { return NULL; } /** * @return The estimated size of data after it is uncompressed * and loaded into RAM. A negative value indicates that the * file size cannot be computed. */ int64_t CiftiFiberTrajectoryFile::getDataSizeUncompressedInBytes() const { return -1; } /** * Get statistics describing the distribution of data * mapped with a color palette for all data within the file. * * @return * Fast statistics for data (will be NULL for data * not mapped using a palette). */ const FastStatistics* CiftiFiberTrajectoryFile::getFileFastStatistics() { return NULL; } /** * Get histogram describing the distribution of data * mapped with a color palette for all data within * the file. * * @return * Histogram for data (will be NULL for data * not mapped using a palette). */ const Histogram* CiftiFiberTrajectoryFile::getFileHistogram() { return NULL; } /** * Get histogram describing the distribution of data * mapped with a color palette for all data in the file * within the given range of values. * * @param mostPositiveValueInclusive * Values more positive than this value are excluded. * @param leastPositiveValueInclusive * Values less positive than this value are excluded. * @param leastNegativeValueInclusive * Values less negative than this value are excluded. * @param mostNegativeValueInclusive * Values more negative than this value are excluded. * @param includeZeroValues * If true zero values (very near zero) are included. * @return * Descriptive statistics for data (will be NULL for data * not mapped using a palette). */ const Histogram* CiftiFiberTrajectoryFile::getFileHistogram(const float /*mostPositiveValueInclusive*/, const float /*leastPositiveValueInclusive*/, const float /*leastNegativeValueInclusive*/, const float /*mostNegativeValueInclusive*/, const bool /*includeZeroValues*/) { return NULL; } /** * Get the palette color mapping for the map at the given index. * * @param mapIndex * Index of the map. * @return * Palette color mapping for the map (will be NULL for data * not mapped using a palette). */ PaletteColorMapping* CiftiFiberTrajectoryFile::getMapPaletteColorMapping(const int32_t /*mapIndex*/) { return NULL; } /** * Get the palette color mapping for the map at the given index. * * @param mapIndex * Index of the map. * @return * Palette color mapping for the map (constant) (will be NULL for data * not mapped using a palette). */ const PaletteColorMapping* CiftiFiberTrajectoryFile::getMapPaletteColorMapping(const int32_t /*mapIndex*/) const { return NULL; } /** * @return Is the data in the file mapped to colors using * a label table. */ bool CiftiFiberTrajectoryFile::isMappedWithLabelTable() const { return false; } /** * Get the label table for the map at the given index. * * @param mapIndex * Index of the map. * @return * Label table for the map (will be NULL for data * not mapped using a label table). */ GiftiLabelTable* CiftiFiberTrajectoryFile::getMapLabelTable(const int32_t /*mapIndex*/) { return NULL; } /** * Get the label table for the map at the given index. * * @param mapIndex * Index of the map. * @return * Label table for the map (constant) (will be NULL for data * not mapped using a label table). */ const GiftiLabelTable* CiftiFiberTrajectoryFile::getMapLabelTable(const int32_t /*mapIndex*/) const { return NULL; } /** * Get the palette normalization modes that are supported by the file. * * @param modesSupportedOut * Palette normalization modes supported by a file. Will be * empty for files that are not mapped with a palette. If there * is more than one suppported mode, the first mode in the * vector is assumed to be the default mode. */ void CiftiFiberTrajectoryFile::getPaletteNormalizationModesSupported(std::vector& modesSupportedOut) { modesSupportedOut.clear(); } /** * Update scalar coloring for a map. * * Note that some CIFTI files can be slow to color due to the need to * retrieve data for the map. Use isMapColoringValid() to avoid * unnecessary calls to isMapColoringValid. * * @param mapIndex * Index of map. * @param paletteFile * Palette file containing palettes. */ void CiftiFiberTrajectoryFile::updateScalarColoringForMap(const int32_t /*mapIndex*/, const PaletteFile* /*paletteFile*/) { } /** * @return The fiber trajectory map properties (const method). */ FiberTrajectoryMapProperties* CiftiFiberTrajectoryFile::getFiberTrajectoryMapProperties() { return m_fiberTrajectoryMapProperties; } /** * @return The fiber trajectory map properties. */ const FiberTrajectoryMapProperties* CiftiFiberTrajectoryFile::getFiberTrajectoryMapProperties() const { return m_fiberTrajectoryMapProperties; } /** * Read the data file. * * @param filename * Name of the data file. * @throws DataFileException * If the file was not successfully read. */ void CiftiFiberTrajectoryFile::readFile(const AString& filename) { clear(); checkFileReadability(filename); try { m_sparseFile = new CaretSparseFile(); m_sparseFile->readFile(filename); setFileName(filename); m_fiberTrajectoryFileType = FIBER_TRAJECTORY_LOAD_BY_BRAINORDINATE; const CiftiXML& xml = m_sparseFile->getCiftiXML(); if (xml.getMappingType(CiftiXML::ALONG_COLUMN) == CiftiMappingType::SCALARS) { m_fiberTrajectoryFileType = FIBER_TRAJECTORY_LOAD_SINGLE_ROW; } clearModified(); } catch (const DataFileException& e) { clear(); throw e; } } /** * Write the data file. * * @param filename * Name of the data file. * @throws DataFileException * If the file was not successfully written. */ void CiftiFiberTrajectoryFile::writeFile(const AString& filename) { switch (m_fiberTrajectoryFileType) { case FIBER_TRAJECTORY_LOAD_BY_BRAINORDINATE: throw DataFileException(filename, "Writing of Cifti Trajectory Files that load by brainordinate is not supported."); break; case FIBER_TRAJECTORY_LOAD_SINGLE_ROW: writeLoadedDataToFile(filename); break; } } class FiberFractionAndIndex { public: FiberFractionAndIndex(const FiberFractions& fiberFraction, const int64_t fiberIndex) { m_fiberFraction = fiberFraction; m_fiberIndex = fiberIndex; } bool operator<(const FiberFractionAndIndex& other) const { return (m_fiberIndex < other.m_fiberIndex); } FiberFractions m_fiberFraction; int64_t m_fiberIndex; }; class FiberTrajectoryComparison { public: bool operator() (const FiberOrientationTrajectory* left, const FiberOrientationTrajectory* right) const { return (left->getFiberOrientationIndex() < right->getFiberOrientationIndex()); } }; /** * Create a new fiber trajectory file from the loaded data of this file. * * @param errorMessageOut * Error message if creation of new fiber trajectory file failed. * @param * Pointer to new file that was created or NULL if creation failed. */ CiftiFiberTrajectoryFile* CiftiFiberTrajectoryFile::newFiberTrajectoryFileFromLoadedRowData(const AString& destinationDirectory, AString& errorMessageOut) const { errorMessageOut = ""; const int64_t numTraj = static_cast(m_fiberOrientationTrajectories.size()); if (numTraj <= 0) { errorMessageOut = "No data is loaded so cannot create file."; return NULL; } CiftiFiberTrajectoryFile* newFile = NULL; try { newFile = new CiftiFiberTrajectoryFile(); AString rowInfo = ""; if (m_loadedDataDescriptionForFileCopy.isEmpty() == false) { rowInfo = ("_" + m_loadedDataDescriptionForFileCopy); } /* * May need to convert a remote path to a local path */ FileInformation initialFileNameInfo(getFileName()); const AString scalarFileName = initialFileNameInfo.getAsLocalAbsoluteFilePath(destinationDirectory, getDataFileType()); /* * Create name of scalar file with row/column information */ FileInformation scalarFileInfo(scalarFileName); AString thePath, theName, theExtension; scalarFileInfo.getFileComponents(thePath, theName, theExtension); theName.append(rowInfo); const AString newFileName = FileInformation::assembleFileComponents(thePath, theName, theExtension); const AString tempFileName = (QDir::tempPath() + QDir::separator() + newFile->getFileNameNoPath()); std::cout << "Filename: " << qPrintable(tempFileName) << std::endl; writeLoadedDataToFile(tempFileName); newFile->readFile(tempFileName); newFile->setFileName(newFileName); newFile->setMatchingFiberOrientationFile(const_cast(getMatchingFiberOrientationFile())); newFile->m_fiberTrajectoryMapProperties->copy(*getFiberTrajectoryMapProperties()); newFile->setModified(); return newFile; } catch (const DataFileException& dfe) { if (newFile != NULL) { delete newFile; } errorMessageOut = dfe.whatString(); return NULL; } return NULL; } /** * Write the loaded data to a file. * * @param filename * Name of file to write. * @throw DataFileException * If an error occurs. */ void CiftiFiberTrajectoryFile::writeLoadedDataToFile(const AString& filename) const { CiftiXML xml = m_sparseFile->getCiftiXML(); /* * Copy the pointers to the fiber orientation trajectories and sort * by fiber orientation index. */ std::vector trajectories(m_fiberOrientationTrajectories.begin(), m_fiberOrientationTrajectories.end()); bool isWriteFullRow = false; if (static_cast(trajectories.size()) == xml.getDimensionLength(CiftiXML::ALONG_ROW)) { isWriteFullRow = true; } else { /* * Sort by fiber orientation index. */ std::sort(trajectories.begin(), trajectories.end(), FiberTrajectoryComparison()); } std::vector fiberIndices; std::vector fiberFractions; int ctr = 0; for (std::vector::const_iterator iter = trajectories.begin(); iter != trajectories.end(); iter++) { const FiberOrientationTrajectory* fot = *iter; std::vector proportions = fot->getFiberFractions(); if (proportions.size() < 3) { proportions.resize(3, 0.0); } const float totalCount = fot->getFiberFractionTotalCount(); FiberFractions ff; ff.totalCount = totalCount; //(totalCount + 0.5); ff.distance = fot->getFiberFractionDistance(); ff.fiberFractions = proportions; fiberIndices.push_back(fot->getFiberOrientationIndex()); fiberFractions.push_back(ff); // // for (int64_t i = 0; i < 3; i++) { // if (vec[i] < -0.002f) { // std::cout << "Fiber " << ctr << vec[i] << std::endl; // } // } ctr++; } /* * Write to temp file!!!!! */ CiftiScalarsMap tempMap; tempMap.setLength(1); tempMap.setMapName(0, m_loadedDataDescriptionForMapName); xml.setMap(CiftiXML::ALONG_COLUMN, tempMap); CaretSparseFileWriter sparseWriter(filename, xml); const int64_t rowIndex = 0; if (isWriteFullRow) { sparseWriter.writeFibersRow(rowIndex, &fiberFractions[0]); } else { sparseWriter.writeFibersRowSparse(rowIndex, fiberIndices, fiberFractions); } sparseWriter.finish(); } /** * Clear the loaded fiber orientations. */ void CiftiFiberTrajectoryFile::clearLoadedFiberOrientations() { const int64_t numFibers = static_cast(m_fiberOrientationTrajectories.size()); for (int64_t i = 0; i < numFibers; i++) { delete m_fiberOrientationTrajectories[i]; } m_fiberOrientationTrajectories.clear(); m_loadedDataDescriptionForMapName = ""; m_loadedDataDescriptionForFileCopy = ""; m_connectivityDataLoaded->reset(); } /** * Validate that the assigned matching fiber orientation file is valid * (not NULL and row/column is compatible). * * @throws DataFileException * If fiber orientation file is NULL or incompatible. */ void CiftiFiberTrajectoryFile::validateAssignedMatchingFiberOrientationFile() { if (m_sparseFile == NULL) { throw DataFileException(getFileName(), "No data has been loaded."); } if (m_matchingFiberOrientationFile == NULL) { throw DataFileException(getFileName(), "No fiber orientation file is assigned."); } const CiftiXML& trajXML = m_sparseFile->getCiftiXML(); const CiftiXML* orientXML = m_matchingFiberOrientationFile->getCiftiXML(); if (*(trajXML.getMap(CiftiXML::ALONG_ROW)) != *(orientXML->getMap(CiftiXML::ALONG_COLUMN))) { QString msg = ("Row to Columns do not match: rows=" + QString::number(trajXML.getDimensionLength(CiftiXML::ALONG_COLUMN)) + " cols=" + QString::number(trajXML.getDimensionLength(CiftiXML::ALONG_ROW)) + " " + m_matchingFiberOrientationFile->getFileNameNoPath() + " rows=" + QString::number(orientXML->getDimensionLength(CiftiXML::ALONG_COLUMN)) + " cols=" + QString::number(orientXML->getDimensionLength(CiftiXML::ALONG_ROW))); throw DataFileException(getFileName(), msg); } } /** * Get the brainordinate from the given row. * * @param rowIndex * Index of the row. * @param surfaceStructureOut * Will contain structure of surface if row is a surface node. * @param surfaceNodeIndexOut * Will contain index of surface node if row is a surface node. * @param surfaceNumberOfNodesOut * Will contain surfaces number of nodes if row is a surface node. * @param surfaceNodeValidOut * Will be true upon exit if the row corresponded to a surface node. * @param voxelIJKOut * Will contain the voxel's IJK indices if row is a surface node. * @param voxelXYZOut * Will contain the voxel's XYZ coordinate if row is a surface node. * @param voxelValidOut * Will be true upon exit if the row corresponded to a surface node. * @throw DataFileException * If the rows are not for brainordinates or the row index is invalid. */ void CiftiFiberTrajectoryFile::getBrainordinateFromRowIndex(const int64_t rowIndex, StructureEnum::Enum& surfaceStructureOut, int32_t& surfaceNodeIndexOut, int32_t& surfaceNumberOfNodesOut, bool& surfaceNodeValidOut, int64_t voxelIJKOut[3], float voxelXYZOut[3], bool& voxelValidOut) const { surfaceNodeValidOut = false; voxelValidOut = false; if (m_sparseFile == NULL) { return; } const CiftiXML& ciftiXML = m_sparseFile->getCiftiXML(); if (ciftiXML.getMappingType(CiftiXML::ALONG_COLUMN) != CiftiMappingType::BRAIN_MODELS) { throw DataFileException(getFileName(), "File does not have brainordinate data for rows."); return; } const CiftiBrainModelsMap& brainMap = ciftiXML.getBrainModelsMap(CiftiXML::ALONG_COLUMN); const int numRows = ciftiXML.getDimensionLength(CiftiXML::ALONG_COLUMN); if ((rowIndex < 0) || (rowIndex >= numRows)) { throw DataFileException(getFileName(), "Row index " + AString::number(rowIndex) + " is invalid. Number of rows is " + AString::number(numRows)); } const CiftiBrainModelsMap::IndexInfo indexInfo = brainMap.getInfoForIndex(rowIndex); switch (indexInfo.m_type) { case CiftiBrainModelsMap::SURFACE: surfaceStructureOut = indexInfo.m_structure; surfaceNodeIndexOut = indexInfo.m_surfaceNode; surfaceNumberOfNodesOut = brainMap.getSurfaceNumberOfNodes(surfaceStructureOut); surfaceNodeValidOut = true; break; case CiftiBrainModelsMap::VOXELS: { const VolumeSpace& colSpace = brainMap.getVolumeSpace(); voxelIJKOut[0] = indexInfo.m_ijk[0]; voxelIJKOut[1] = indexInfo.m_ijk[1]; voxelIJKOut[2] = indexInfo.m_ijk[2]; colSpace.indexToSpace(voxelIJKOut, voxelXYZOut); voxelValidOut = true; } break; } } /** * Load data for the given surface node. * @param structure * Structure in which surface node is located. * @param surfaceNumberOfNodes * Number of nodes in the surface. * @param nodeIndex * Index of the surface node. * @return * Index of row that was loaded or -1 if no data was found for node. */ int64_t CiftiFiberTrajectoryFile::loadDataForSurfaceNode(const StructureEnum::Enum structure, const int32_t surfaceNumberOfNodes, const int32_t nodeIndex) { switch (m_fiberTrajectoryFileType) { case FIBER_TRAJECTORY_LOAD_BY_BRAINORDINATE: break; case FIBER_TRAJECTORY_LOAD_SINGLE_ROW: return -1; break; } if (m_dataLoadingEnabled == false) { return -1; } clearLoadedFiberOrientations(); validateAssignedMatchingFiberOrientationFile(); const CiftiXML& trajXML = m_sparseFile->getCiftiXML(); const CiftiBrainModelsMap& colMap = trajXML.getBrainModelsMap(CiftiXML::ALONG_COLUMN); if (colMap.hasSurfaceData(structure) == false) { return -1; } if (colMap.getSurfaceNumberOfNodes(structure) != surfaceNumberOfNodes) { return -1; } const int64_t rowIndex = colMap.getIndexForNode(nodeIndex, structure); if (rowIndex < 0) { return -1; } std::vector fiberIndices; std::vector fiberFractions; bool rowTest = false; if (rowTest) { /* * Test loading a full row instead of sparse. */ const int numCols = trajXML.getDimensionLength(CiftiXML::ALONG_ROW); fiberFractions.resize(numCols); m_sparseFile->getFibersRow(rowIndex, &fiberFractions[0]); for (int64_t i = 0; i < numCols; i++) { fiberIndices.push_back(i); } } else { m_sparseFile->getFibersRowSparse(rowIndex, fiberIndices, fiberFractions); } CaretAssert(fiberIndices.size() == fiberFractions.size()); const int64_t numFibers = static_cast(fiberIndices.size()); CaretLogFine("For node " + AString::number(nodeIndex) + " number of rows loaded: " + AString::number(numFibers)); if (numFibers > 0) { m_fiberOrientationTrajectories.reserve(numFibers); for (int64_t iFiber = 0; iFiber < numFibers; iFiber++) { const int64_t numFiberOrientations = m_matchingFiberOrientationFile->getNumberOfFiberOrientations(); const int64_t fiberIndex = fiberIndices[iFiber]; if (fiberIndex < numFiberOrientations) { const FiberOrientation* fiberOrientation = m_matchingFiberOrientationFile->getFiberOrientations(fiberIndex); FiberOrientationTrajectory* fot = new FiberOrientationTrajectory(fiberIndex, fiberOrientation); fot->setFiberFractions(fiberFractions[iFiber]); m_fiberOrientationTrajectories.push_back(fot); } else{ CaretLogSevere("Invalid index=" + QString::number(fiberIndex) + " into fiber orientations"); } } m_loadedDataDescriptionForMapName = ("Row: " + AString::number(rowIndex) + ", Node Index: " + AString::number(nodeIndex) + ", Structure: " + StructureEnum::toName(structure)); m_loadedDataDescriptionForFileCopy = ("Row_" + AString::number(rowIndex)); m_connectivityDataLoaded->setSurfaceNodeLoading(structure, surfaceNumberOfNodes, nodeIndex, rowIndex, -1); } else { m_connectivityDataLoaded->reset(); return -1; } return rowIndex; } void CiftiFiberTrajectoryFile::finishFiberOrientationTrajectoriesAveraging() { for (std::vector::iterator iter = m_fiberOrientationTrajectories.begin(); iter != m_fiberOrientationTrajectories.end(); iter++) { FiberOrientationTrajectory* fot = *iter; fot->finishAveraging(); } } /** * Load average data for the given surface nodes. * * @param structure * Structure in which surface node is located. * @param surfaceNumberOfNodes * Number of nodes in surface. * @param nodeIndices * Indices of the surface nodes. */ void CiftiFiberTrajectoryFile::loadDataAverageForSurfaceNodes(const StructureEnum::Enum structure, const int32_t surfaceNumberOfNodes, const std::vector& nodeIndices) { switch (m_fiberTrajectoryFileType) { case FIBER_TRAJECTORY_LOAD_BY_BRAINORDINATE: break; case FIBER_TRAJECTORY_LOAD_SINGLE_ROW: return; break; } if (m_dataLoadingEnabled == false) { return; } clearLoadedFiberOrientations(); if (surfaceNumberOfNodes <= 0) { return; } validateAssignedMatchingFiberOrientationFile(); const CiftiXML& trajXML = m_sparseFile->getCiftiXML(); const CiftiBrainModelsMap& colMap = trajXML.getBrainModelsMap(CiftiXML::ALONG_COLUMN); if (colMap.hasSurfaceData(structure) == false) { return; } if (colMap.getSurfaceNumberOfNodes(structure) != surfaceNumberOfNodes) { return; } /* * This map uses the index of a fiber orientation (from the Fiber Orientation File) * to a FiberOrientationTrajectory instance. For averaging, items that have * a matching fiber orientation index are averaged. */ std::map fiberOrientationIndexMapToFiberTrajectory; std::vector rowIndicesToLoad; const int32_t numberOfNodes = static_cast(nodeIndices.size()); for (int32_t i = 0; i < numberOfNodes; i++) { const int32_t nodeIndex = nodeIndices[i]; /* * Get and load row for node */ const int64_t rowIndex = colMap.getIndexForNode(nodeIndex, structure); if (rowIndex >= 0) { rowIndicesToLoad.push_back(rowIndex); } } if (loadRowsForAveraging(rowIndicesToLoad)) { m_connectivityDataLoaded->setSurfaceAverageNodeLoading(structure, surfaceNumberOfNodes, nodeIndices); m_loadedDataDescriptionForMapName = ("Structure: " + StructureEnum::toName(structure) + ", Averaged Node Count: " + AString::number(numberOfNodes)); m_loadedDataDescriptionForFileCopy = ("Averaged_Node_Count_" + AString::number(numberOfNodes)); } } /** * Load the given rows for averaging. * * @param rowIndices * Indices of rows for averaging. * @return * True if data was loaded else false if no data or user cancelled. * @throw * DataFileException if there is an error. */ bool CiftiFiberTrajectoryFile::loadRowsForAveraging(const std::vector& rowIndices) { const CiftiXML& trajXML = m_sparseFile->getCiftiXML(); const int64_t numberOfColumns = trajXML.getDimensionLength(CiftiXML::ALONG_ROW); std::vector fiberFractionsForRowVector(numberOfColumns); FiberFractions* fiberFractionsForRow = &fiberFractionsForRowVector[0]; const int64_t numberOfRowsToLoad = static_cast(rowIndices.size()); if (numberOfRowsToLoad <= 0) { return false; } const int32_t progressUpdateInterval = 1; EventProgressUpdate progressEvent(0, numberOfRowsToLoad, 0, ("Loading data for " + QString::number(numberOfRowsToLoad) + " brainordinates in file ") + getFileNameNoPath()); EventManager::get()->sendEvent(progressEvent.getPointer()); for (int64_t iCol = 0; iCol < numberOfColumns; iCol++) { const FiberOrientation* fiberOrientation = m_matchingFiberOrientationFile->getFiberOrientations(iCol); CaretAssert(fiberOrientation); m_fiberOrientationTrajectories.push_back(new FiberOrientationTrajectory(iCol, fiberOrientation)); } bool userCancelled = false; for (int64_t iRow = 0; iRow < numberOfRowsToLoad; iRow++) { const int64_t rowIndex = rowIndices[iRow]; if ((iRow % progressUpdateInterval) == 0) { progressEvent.setProgress(iRow, ""); EventManager::get()->sendEvent(progressEvent.getPointer()); if (progressEvent.isCancelled()) { userCancelled = true; break; } } m_sparseFile->getFibersRow(rowIndex, fiberFractionsForRow); for (int64_t iCol = 0; iCol < numberOfColumns; iCol++) { FiberOrientationTrajectory* fot = m_fiberOrientationTrajectories[iCol]; fot->addFiberFractionsForAveraging(fiberFractionsForRow[iCol]); } } if (userCancelled) { clearLoadedFiberOrientations(); return false; } finishFiberOrientationTrajectoriesAveraging(); return true; } /** * Load data for a voxel at the given coordinate. * * @param xyz * Coordinate of voxel. * @return * Index of row that was loaded or -1 if no data was found for coordinate. * @throw * DataFileException if there is an error. */ int64_t CiftiFiberTrajectoryFile::loadMapDataForVoxelAtCoordinate(const float xyz[3]) { m_connectivityDataLoaded->reset(); switch (m_fiberTrajectoryFileType) { case FIBER_TRAJECTORY_LOAD_BY_BRAINORDINATE: break; case FIBER_TRAJECTORY_LOAD_SINGLE_ROW: return -1; break; } if (m_dataLoadingEnabled == false) { return -1; } clearLoadedFiberOrientations(); validateAssignedMatchingFiberOrientationFile(); const CiftiXML& trajXML = m_sparseFile->getCiftiXML(); const CiftiBrainModelsMap& colMap = trajXML.getBrainModelsMap(CiftiXML::ALONG_COLUMN); if (!colMap.hasVolumeData()) return -1; const VolumeSpace& colSpace = colMap.getVolumeSpace(); int64_t ijk[3]; colSpace.enclosingVoxel(xyz, ijk); const int64_t rowIndex = colMap.getIndexForVoxel(ijk); if (rowIndex < 0) { return -1; } std::vector fiberIndices; std::vector fiberFractions; m_sparseFile->getFibersRowSparse(rowIndex, fiberIndices, fiberFractions); CaretAssert(fiberIndices.size() == fiberFractions.size()); const int64_t numFibers = static_cast(fiberIndices.size()); CaretLogFine("For voxel at coordinate " + AString::fromNumbers(xyz, 3, ",") + " number of rows loaded: " + AString::number(numFibers)); if (numFibers > 0) { m_fiberOrientationTrajectories.reserve(numFibers); for (int64_t iFiber = 0; iFiber < numFibers; iFiber++) { const int64_t numFiberOrientations = m_matchingFiberOrientationFile->getNumberOfFiberOrientations(); const int64_t fiberIndex = fiberIndices[iFiber]; if (fiberIndex < numFiberOrientations) { const FiberOrientation* fiberOrientation = m_matchingFiberOrientationFile->getFiberOrientations(fiberIndex); FiberOrientationTrajectory* fot = new FiberOrientationTrajectory(fiberIndex, fiberOrientation); fot->setFiberFractions(fiberFractions[iFiber]); m_fiberOrientationTrajectories.push_back(fot); } else{ CaretLogSevere("Invalid index=" + QString::number(fiberIndex) + " into fiber orientations"); } } m_loadedDataDescriptionForMapName = ("Row: " + AString::number(rowIndex) + ", Voxel XYZ: " + AString::fromNumbers(xyz, 3, ",") + ", Structure: "); m_loadedDataDescriptionForFileCopy = ("Row_" + AString::number(rowIndex)); m_connectivityDataLoaded->setVolumeXYZLoading(xyz, rowIndex, -1); } else { return -1; } return rowIndex; } /** * Load connectivity data for the voxel indices and then average the data. * * @param volumeDimensionIJK * Dimensions of the volume. * @param voxelIndices * Indices of voxels. * @throw * DataFileException if there is an error. */ void CiftiFiberTrajectoryFile::loadMapAverageDataForVoxelIndices(const int64_t volumeDimensionIJK[3], const std::vector& voxelIndices) { switch (m_fiberTrajectoryFileType) { case FIBER_TRAJECTORY_LOAD_BY_BRAINORDINATE: break; case FIBER_TRAJECTORY_LOAD_SINGLE_ROW: return; break; } if (m_dataLoadingEnabled == false) { return; } clearLoadedFiberOrientations(); validateAssignedMatchingFiberOrientationFile(); const CiftiXML& trajXML = m_sparseFile->getCiftiXML(); const CiftiBrainModelsMap& colMap = trajXML.getBrainModelsMap(CiftiXML::ALONG_COLUMN); if (colMap.hasVolumeData() == false) { return; } std::vector rowIndicesToLoad; const int32_t numberOfVoxels = static_cast(voxelIndices.size()); for (int32_t i = 0; i < numberOfVoxels; i++) { /* * Get and load row for voxel */ const int64_t rowIndex = colMap.getIndexForVoxel(voxelIndices[i].m_ijk); if (rowIndex >= 0) { rowIndicesToLoad.push_back(rowIndex); } } if (loadRowsForAveraging(rowIndicesToLoad)) { m_connectivityDataLoaded->setVolumeAverageVoxelLoading(volumeDimensionIJK, voxelIndices); m_loadedDataDescriptionForMapName = ("Averaged Voxel Count: " + AString::number(numberOfVoxels)); m_loadedDataDescriptionForFileCopy = ("Average_Voxel_Count_" + AString::number(numberOfVoxels)); } } /** * Load the given row index from the file even if the file is disabled for data loading * * @param rowIndex * Index of row that is loaded. * @throw DataFileException * If an error occurs. */ void CiftiFiberTrajectoryFile::loadDataForRowIndex(const int64_t rowIndex) { clearLoadedFiberOrientations(); validateAssignedMatchingFiberOrientationFile(); std::vector fiberIndices; std::vector fiberFractions; m_sparseFile->getFibersRowSparse(rowIndex, fiberIndices, fiberFractions); CaretAssert(fiberIndices.size() == fiberFractions.size()); const int64_t numFibers = static_cast(fiberIndices.size()); if (numFibers > 0) { m_fiberOrientationTrajectories.reserve(numFibers); for (int64_t iFiber = 0; iFiber < numFibers; iFiber++) { const int64_t numFiberOrientations = m_matchingFiberOrientationFile->getNumberOfFiberOrientations(); const int64_t fiberIndex = fiberIndices[iFiber]; if (fiberIndex < numFiberOrientations) { const FiberOrientation* fiberOrientation = m_matchingFiberOrientationFile->getFiberOrientations(fiberIndex); FiberOrientationTrajectory* fot = new FiberOrientationTrajectory(fiberIndex, fiberOrientation); fot->setFiberFractions(fiberFractions[iFiber]); m_fiberOrientationTrajectories.push_back(fot); } else{ CaretLogSevere("Invalid index=" + QString::number(fiberIndex) + " into fiber orientations"); } } m_loadedDataDescriptionForMapName = ("Row: " + AString::number(rowIndex)); m_loadedDataDescriptionForFileCopy = ("Row_" + AString::number(rowIndex)); m_connectivityDataLoaded->setRowColumnLoading(rowIndex, -1); } else { throw DataFileException(getFileName(), "Row " + AString::number(rowIndex) + " is invalid or contains no data."); } } /** * Finish restoration of scene. * In this file's circumstances, the fiber orientation files were not * available at the time the scene was restored. * * @throws DataFileException * If there was an error restoring the data. */ void CiftiFiberTrajectoryFile::finishRestorationOfScene() { /* * Loading of data may be disabled in the scene * so temporarily enabled loading and then * restore the status. */ const bool loadingEnabledStatus = isDataLoadingEnabled(); setDataLoadingEnabled(true); switch (m_connectivityDataLoaded->getMode()) { case ConnectivityDataLoaded::MODE_NONE: break; case ConnectivityDataLoaded::MODE_ROW: { int64_t rowIndex; int64_t columnIndex; m_connectivityDataLoaded->getRowColumnLoading(rowIndex, columnIndex); loadDataForRowIndex(rowIndex); } break; case ConnectivityDataLoaded::MODE_COLUMN: { /* * Never load by column !!! */ CaretAssertMessage(0, "Fiber Trajectory never loads by column."); } break; case ConnectivityDataLoaded::MODE_SURFACE_NODE: { StructureEnum::Enum structure; int32_t surfaceNumberOfNodes; int32_t surfaceNodeIndex; int64_t rowIndex; int64_t columnIndex; m_connectivityDataLoaded->getSurfaceNodeLoading(structure, surfaceNumberOfNodes, surfaceNodeIndex, rowIndex, columnIndex); loadDataForSurfaceNode(structure, surfaceNumberOfNodes, surfaceNodeIndex); } break; case ConnectivityDataLoaded::MODE_SURFACE_NODE_AVERAGE: { StructureEnum::Enum structure; int32_t surfaceNumberOfNodes; std::vector surfaceNodeIndices; m_connectivityDataLoaded->getSurfaceAverageNodeLoading(structure, surfaceNumberOfNodes, surfaceNodeIndices); loadDataAverageForSurfaceNodes(structure, surfaceNumberOfNodes, surfaceNodeIndices); } break; case ConnectivityDataLoaded::MODE_VOXEL_XYZ: { float volumeXYZ[3]; int64_t rowIndex; int64_t columnIndex; m_connectivityDataLoaded->getVolumeXYZLoading(volumeXYZ, rowIndex, columnIndex); loadMapDataForVoxelAtCoordinate(volumeXYZ); } break; case ConnectivityDataLoaded::MODE_VOXEL_IJK_AVERAGE: { int64_t volumeDimensionsIJK[3]; std::vector voxelIndicesIJK; m_connectivityDataLoaded->getVolumeAverageVoxelLoading(volumeDimensionsIJK, voxelIndicesIJK); loadMapAverageDataForVoxelIndices(volumeDimensionsIJK, voxelIndicesIJK); } break; } setDataLoadingEnabled(loadingEnabledStatus); } /** * @return a REFERENCE to the fiber fractions that were loaded. */ const std::vector& CiftiFiberTrajectoryFile::getLoadedFiberOrientationTrajectories() const { return m_fiberOrientationTrajectories; } /** * Save file data from the scene. For subclasses that need to * save to a scene, this method should be overriden. sceneClass * will be valid and any scene data should be added to it. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass to which data members should be added. */ void CiftiFiberTrajectoryFile::saveFileDataToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass) { CaretMappableDataFile::saveFileDataToScene(sceneAttributes, sceneClass); m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); } /** * Restore file data from the scene. For subclasses that need to * restore from a scene, this method should be overridden. The scene class * will be valid and any scene data may be obtained from it. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass for the instance of a class that implements * this interface. Will NEVER be NULL. */ void CiftiFiberTrajectoryFile::restoreFileDataFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { m_connectivityDataLoaded->reset(); m_matchingFiberOrientationFile = NULL; m_matchingFiberOrientationFileName = ""; CaretMappableDataFile::restoreFileDataFromScene(sceneAttributes, sceneClass); m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); m_matchingFiberOrientationFileNameFromRestoredScene = m_matchingFiberOrientationFileName; } /** * @return True if this file type supports writing, else false. * * Fiber trajectory files do NOT support writing. */ bool CiftiFiberTrajectoryFile::supportsWriting() const { switch (m_fiberTrajectoryFileType) { case FIBER_TRAJECTORY_LOAD_BY_BRAINORDINATE: break; case FIBER_TRAJECTORY_LOAD_SINGLE_ROW: return true; break; } return false; } /** * Add information about the file to the data file information. * * @param dataFileInformation * Consolidates information about a data file. */ void CiftiFiberTrajectoryFile::addToDataFileContentInformation(DataFileContentInformation& dataFileInformation) { CaretMappableDataFile::addToDataFileContentInformation(dataFileInformation); if (m_sparseFile != NULL) { const CiftiXML& ciftiXML = m_sparseFile->getCiftiXML(); const CiftiBrainModelsMap& colMap = ciftiXML.getBrainModelsMap(CiftiXML::ALONG_COLUMN); //ciftiXML.getVoxelInfoInDataFileContentInformation(CiftiXML::ALONG_COLUMN, // dataFileInformation); if (colMap.hasVolumeData()) { VolumeSpace volumeSpace = colMap.getVolumeSpace();//TSC: copied/reimplemented from CiftiXML Old - I don't think it belongs in CiftiXML or CiftiBrainModelsMap const int64_t* dims = volumeSpace.getDims(); dataFileInformation.addNameAndValue("Dimensions", AString::fromNumbers(dims, 3, ",")); VolumeSpace::OrientTypes orientation[3]; float spacing[3]; float origin[3]; volumeSpace.getOrientAndSpacingForPlumb(orientation, spacing, origin); dataFileInformation.addNameAndValue("Spacing", AString::fromNumbers(spacing, 3, ",")); dataFileInformation.addNameAndValue("Origin", AString::fromNumbers(origin, 3, ",")); const std::vector >& sform = volumeSpace.getSform(); for (uint32_t i = 0; i < sform.size(); i++) { dataFileInformation.addNameAndValue(("sform row " + AString::number(i)), AString::fromNumbers(sform[i], ",")); } std::vector volStructs = colMap.getVolumeStructureList(); for (int i = 0; i < (int)volStructs.size(); ++i) { std::vector voxels = colMap.getVolumeStructureMap(volStructs[i]); for (int j = 0; j < (int)voxels.size(); ++j) { float xyz[3]; volumeSpace.indexToSpace(voxels[i].m_ijk, xyz); const AString msg = ("ijk=(" + AString::fromNumbers(voxels[j].m_ijk, 3, ", ") + "), xyz=(" + AString::fromNumbers(xyz, 3, ", ") + "), row=" + AString::number(voxels[j].m_ciftiIndex) + " ");//TSC: huh? dataFileInformation.addNameAndValue(StructureEnum::toGuiName(volStructs[i]), msg);//TSC: huh? } } } CiftiMappableDataFile::addCiftiXmlToDataFileContentInformation(dataFileInformation, ciftiXML); } } workbench-1.1.1/src/Files/CiftiFiberTrajectoryFile.h000066400000000000000000000233551255417355300223750ustar00rootroot00000000000000#ifndef __CIFTI_FIBER_TRAJECTORY_FILE__H_ #define __CIFTI_FIBER_TRAJECTORY_FILE__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "BrainConstants.h" #include "CaretMappableDataFile.h" #include "CaretSparseFile.h" #include "DisplayGroupEnum.h" #include "SceneClassAssistant.h" #include "VoxelIJK.h" namespace caret { class CiftiFiberOrientationFile; class ConnectivityDataLoaded; class FiberOrientationTrajectory; class FiberTrajectoryMapProperties; class GiftiMetaData; class CiftiFiberTrajectoryFile : public CaretMappableDataFile { public: CiftiFiberTrajectoryFile(); virtual ~CiftiFiberTrajectoryFile(); virtual void clear(); bool isEmpty() const; virtual StructureEnum::Enum getStructure() const; virtual void setStructure(const StructureEnum::Enum structure); virtual GiftiMetaData* getFileMetaData(); virtual const GiftiMetaData* getFileMetaData() const; virtual bool isSurfaceMappable() const; virtual bool isVolumeMappable() const; virtual int32_t getNumberOfMaps() const; virtual bool hasMapAttributes() const; virtual AString getMapName(const int32_t mapIndex) const; virtual void setMapName(const int32_t mapIndex, const AString& mapName); virtual const GiftiMetaData* getMapMetaData(const int32_t mapIndex) const; virtual GiftiMetaData* getMapMetaData(const int32_t mapIndex); virtual AString getMapUniqueID(const int32_t mapIndex) const; virtual bool isMappedWithPalette() const; virtual const FastStatistics* getMapFastStatistics(const int32_t mapIndex); virtual const Histogram* getMapHistogram(const int32_t mapIndex); virtual const Histogram* getMapHistogram(const int32_t mapIndex, const float mostPositiveValueInclusive, const float leastPositiveValueInclusive, const float leastNegativeValueInclusive, const float mostNegativeValueInclusive, const bool includeZeroValues); virtual int64_t getDataSizeUncompressedInBytes() const; virtual const FastStatistics* getFileFastStatistics(); virtual const Histogram* getFileHistogram(); virtual const Histogram* getFileHistogram(const float mostPositiveValueInclusive, const float leastPositiveValueInclusive, const float leastNegativeValueInclusive, const float mostNegativeValueInclusive, const bool includeZeroValues); virtual PaletteColorMapping* getMapPaletteColorMapping(const int32_t mapIndex); virtual const PaletteColorMapping* getMapPaletteColorMapping(const int32_t mapIndex) const; virtual bool isMappedWithLabelTable() const; virtual GiftiLabelTable* getMapLabelTable(const int32_t mapIndex); virtual const GiftiLabelTable* getMapLabelTable(const int32_t mapIndex) const; virtual void getPaletteNormalizationModesSupported(std::vector& modesSupportedOut); virtual void updateScalarColoringForMap(const int32_t mapIndex, const PaletteFile* paletteFile); virtual void readFile(const AString& filename); virtual void writeFile(const AString& filename); void getBrainordinateFromRowIndex(const int64_t rowIndex, StructureEnum::Enum& surfaceStructureOut, int32_t& surfaceNodeIndexOut, int32_t& surfaceNumberOfNodesOut, bool& surfaceNodeValidOut, int64_t voxelIJKOut[3], float voxelXYZOut[3], bool& voxelValidOut) const; int64_t loadDataForSurfaceNode(const StructureEnum::Enum structure, const int32_t surfaceNumberOfNodes, const int32_t nodeIndex); void loadDataAverageForSurfaceNodes(const StructureEnum::Enum structure, const int32_t surfaceNumberOfNodes, const std::vector& nodeIndices); virtual int64_t loadMapDataForVoxelAtCoordinate(const float xyz[3]); virtual void loadMapAverageDataForVoxelIndices(const int64_t volumeDimensionIJK[3], const std::vector& voxelIndices); void loadDataForRowIndex(const int64_t rowIndex); const std::vector& getLoadedFiberOrientationTrajectories() const; void clearLoadedFiberOrientations(); FiberTrajectoryMapProperties* getFiberTrajectoryMapProperties(); const FiberTrajectoryMapProperties* getFiberTrajectoryMapProperties() const; bool isDataLoadingEnabled() const; void setDataLoadingEnabled(const bool loadingEnabled); CiftiFiberOrientationFile* getMatchingFiberOrientationFile(); const CiftiFiberOrientationFile* getMatchingFiberOrientationFile() const; bool isFiberOrientationFileCombatible(const CiftiFiberOrientationFile* fiberOrientationFile) const; void setMatchingFiberOrientationFile(CiftiFiberOrientationFile* matchingFiberOrientationFile); void updateMatchingFiberOrientationFileFromList(std::vector matchingFiberOrientationFiles); void finishRestorationOfScene(); bool supportsWriting() const; CiftiFiberTrajectoryFile* newFiberTrajectoryFileFromLoadedRowData(const AString& destinationDirectory, AString& errorMessageOut) const; void addToDataFileContentInformation(DataFileContentInformation& dataFileInformation); // ADD_NEW_METHODS_HERE private: CiftiFiberTrajectoryFile(const CiftiFiberTrajectoryFile&); CiftiFiberTrajectoryFile& operator=(const CiftiFiberTrajectoryFile&); protected: virtual void saveFileDataToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass); virtual void restoreFileDataFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private: /** * Type of fiber trajectory file */ enum FiberTrajectoryFileType { /** Load data by brainordinate */ FIBER_TRAJECTORY_LOAD_BY_BRAINORDINATE, /** Load single row (does not map to a brainordinate) */ FIBER_TRAJECTORY_LOAD_SINGLE_ROW }; bool loadRowsForAveraging(const std::vector& rowIndices); void clearPrivate(); void validateAssignedMatchingFiberOrientationFile(); void finishFiberOrientationTrajectoriesAveraging(); void writeLoadedDataToFile(const AString& filename) const; /** True if file supports loading of data by row */ FiberTrajectoryFileType m_fiberTrajectoryFileType; CaretSparseFile* m_sparseFile; GiftiMetaData* m_metadata; CiftiFiberOrientationFile* m_matchingFiberOrientationFile; AString m_matchingFiberOrientationFileName; AString m_matchingFiberOrientationFileNameFromRestoredScene; std::vector m_fiberOrientationTrajectories; FiberTrajectoryMapProperties* m_fiberTrajectoryMapProperties; bool m_dataLoadingEnabled; AString m_loadedDataDescriptionForFileCopy; AString m_loadedDataDescriptionForMapName; ConnectivityDataLoaded* m_connectivityDataLoaded; SceneClassAssistant* m_sceneAssistant; // ADD_NEW_MEMBERS_HERE }; #ifdef __CIFTI_FIBER_TRAJECTORY_FILE_DECLARE__ // #endif // __CIFTI_FIBER_TRAJECTORY_FILE_DECLARE__ } // namespace #endif //__CIFTI_FIBER_TRAJECTORY_FILE__H_ workbench-1.1.1/src/Files/CiftiMappableConnectivityMatrixDataFile.cxx000066400000000000000000001603631255417355300257520ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CIFTI_MAPPABLE_CONNECTIVITY_MATRIX_DATA_FILE_DECLARE__ #include "CiftiMappableConnectivityMatrixDataFile.h" #undef __CIFTI_MAPPABLE_CONNECTIVITY_MATRIX_DATA_FILE_DECLARE__ #include "CaretAssert.h" #include "CiftiFile.h" #include "CaretLogger.h" #include "ChartableMatrixParcelInterface.h" #include "ConnectivityDataLoaded.h" #include "DataFileException.h" #include "EventManager.h" #include "EventProgressUpdate.h" #include "SceneClass.h" #include "SceneClassAssistant.h" using namespace caret; /** * \class caret::CiftiMappableConnectivityMatrixDataFile * \brief Data file for Cifti Connectivity Matrix Files. * \ingroup Files */ /** * Constructor. */ CiftiMappableConnectivityMatrixDataFile::CiftiMappableConnectivityMatrixDataFile(const DataFileTypeEnum::Enum dataFileType) : CiftiMappableDataFile(dataFileType) { m_connectivityDataLoaded = new ConnectivityDataLoaded(); /* * This method initializes some members */ clearPrivate(); m_sceneAssistant = new SceneClassAssistant(); m_sceneAssistant->add("m_connectivityDataLoaded", "ConnectivityDataLoaded", m_connectivityDataLoaded); m_sceneAssistant->add("m_dataLoadingEnabled", &m_dataLoadingEnabled); // m_sceneAssistant->add("m_chartLoadingDimension", // &m_chartLoadingDimension); } /** * Destructor. */ CiftiMappableConnectivityMatrixDataFile::~CiftiMappableConnectivityMatrixDataFile() { clearPrivate(); delete m_connectivityDataLoaded; delete m_sceneAssistant; } /** * Clear the contents of the file. */ void CiftiMappableConnectivityMatrixDataFile::clear() { CiftiMappableDataFile::clear(); clearPrivate(); } /** * Clear the contents of the file. * Note that "clear()" is virtual and cannot be called from destructor. */ void CiftiMappableConnectivityMatrixDataFile::clearPrivate() { m_loadedRowData.clear(); m_rowLoadedTextForMapName = ""; m_rowLoadedText = ""; m_dataLoadingEnabled = true; m_connectivityDataLoaded->reset(); m_chartLoadingDimension = ChartMatrixLoadingDimensionEnum::CHART_MATRIX_LOADING_BY_ROW; } /** * @return Pointer to the information about last loaded connectivity data. */ const ConnectivityDataLoaded* CiftiMappableConnectivityMatrixDataFile::getConnectivityDataLoaded() const { return m_connectivityDataLoaded; } /** * Get the nodes for the parcel for the given structure that corresponds * to the last selected row. * * @param parcelNodesOut * Ouput containing the node indices. * @param structure * The surface structure. */ bool CiftiMappableConnectivityMatrixDataFile::getParcelNodesElementForSelectedParcel(std::set &parcelNodesOut, const StructureEnum::Enum &structure) const { bool validFlag = false; int64_t rowIndex = -1; int64_t columnIndex = -1; m_connectivityDataLoaded->getRowColumnLoading(rowIndex, columnIndex); if (rowIndex >= 0) { validFlag = CiftiMappableDataFile::getParcelNodesElementForSelectedParcel(parcelNodesOut, structure, rowIndex); } return validFlag; } /** * @return Is this file empty? */ bool CiftiMappableConnectivityMatrixDataFile::isEmpty() const { if (CiftiMappableDataFile::isEmpty()) { return true; } return false; } /** * @return Is loading of data enabled. Note that if * disabled, any previously loaded data is NOT removed * so that it can still be displayed but not updated. */ bool CiftiMappableConnectivityMatrixDataFile::isMapDataLoadingEnabled(const int32_t /*mapIndex*/) const { return m_dataLoadingEnabled; } /** * Set loading of data enabled. Note that if * disabled, any previously loaded data is NOT removed * so that it can still be displayed but not updated. * * @param dataLoadingEnabled * New data loading enabled status. */ void CiftiMappableConnectivityMatrixDataFile::setMapDataLoadingEnabled(const int32_t /*mapIndex*/, const bool dataLoadingEnabled) { m_dataLoadingEnabled = dataLoadingEnabled; } /** * Get the data for the given map index. * * @param mapIndex * Index of the map. * @param dataOut * A vector that will contain the data for the map upon exit. */ void CiftiMappableConnectivityMatrixDataFile::getMapData(const int32_t /*mapIndex*/, std::vector& dataOut) const { //int nCols = m_ciftiInterface->getNumberOfColumns(); //dataOut.resize(nCols); //m_ciftiInterface->getColumn(&dataOut[0],mapIndex); dataOut = m_loadedRowData; } /** * Get the index of a row or column when loading data for a surface node. * * @param structure * Structure of the surface. * @param surfaceNumberOfNodes * Number of nodes in the surface. * @param nodeIndex * Index of the node. * @param rowIndexOut * Index of row corresponding to node or -1 if no row in the * matrix corresponds to the node. * @param columnIndexOut * Index of column corresponding to node or -1 if no column in the * matrix corresponds to the node. */ void CiftiMappableConnectivityMatrixDataFile::getRowColumnIndexForNodeWhenLoading(const StructureEnum::Enum structure, const int64_t surfaceNumberOfNodes, const int64_t nodeIndex, int64_t& rowIndexOut, int64_t& columnIndexOut) { rowIndexOut = -1; columnIndexOut = -1; if (m_ciftiFile == NULL) { return; } const CiftiXML& ciftiXML = m_ciftiFile->getCiftiXML(); const int32_t ciftiDirection = getCifitDirectionForLoadingRowOrColumn(); /* * Make sure number of nodes matches. */ switch (ciftiXML.getMappingType(ciftiDirection)) { case CiftiMappingType::BRAIN_MODELS: if (ciftiXML.getBrainModelsMap(ciftiDirection).getSurfaceNumberOfNodes(structure) != surfaceNumberOfNodes) return; break; case CiftiMappingType::PARCELS: if (ciftiXML.getParcelsMap(ciftiDirection).getSurfaceNumberOfNodes(structure) != surfaceNumberOfNodes) return; break; default: return; } /* * Get the mapping type */ const CiftiMappingType::MappingType rowMappingType = ciftiXML.getMappingType(ciftiDirection); int64_t rowOrColumnIndex = -1; /* * Get the row/column index for the node. */ switch (rowMappingType) { case CiftiMappingType::BRAIN_MODELS: rowOrColumnIndex = ciftiXML.getBrainModelsMap(ciftiDirection).getIndexForNode(nodeIndex, structure); break; case CiftiMappingType::PARCELS: rowOrColumnIndex = ciftiXML.getParcelsMap(ciftiDirection).getIndexForNode(nodeIndex, structure); break; case CIFTI_INDEX_TYPE_SCALARS: break; case CIFTI_INDEX_TYPE_TIME_POINTS: break; default: CaretAssert(0); CaretLogSevere("Invalid row mapping type for connectivity file " + DataFileTypeEnum::toName(getDataFileType())); break; } switch (ciftiDirection) { case CiftiXML::ALONG_COLUMN: rowIndexOut = rowOrColumnIndex; break; case CiftiXML::ALONG_ROW: columnIndexOut = rowOrColumnIndex; break; default: CaretAssert(0); break; } } ///** // * Get the index of a row when loading data for a surface node. // * @param structure // * Structure of the surface. // * @param surfaceNumberOfNodes // * Number of nodes in the surface. // * @param nodeIndex // * Index of the node. // * @return // * Index of row corresponding to node or -1 if no row in the // * matrix corresponds to the node. // */ //int64_t //CiftiMappableConnectivityMatrixDataFile::getRowIndexForNodeWhenLoading(const StructureEnum::Enum structure, // const int64_t surfaceNumberOfNodes, // const int64_t nodeIndex) //{ // if (m_ciftiFile == NULL) { // return -1; // } // // int64_t rowIndex = -1; // // const CiftiXML& ciftiXML = m_ciftiFile->getCiftiXML(); // // switch (ciftiXML.getMappingType(CiftiXML::ALONG_COLUMN)) // { // case CiftiMappingType::BRAIN_MODELS: // if (ciftiXML.getBrainModelsMap(CiftiXML::ALONG_COLUMN).getSurfaceNumberOfNodes(structure) != surfaceNumberOfNodes) return -1; // break; // case CiftiMappingType::PARCELS: // if (ciftiXML.getParcelsMap(CiftiXML::ALONG_COLUMN).getSurfaceNumberOfNodes(structure) != surfaceNumberOfNodes) return -1; // break; // default: // return -1; // } // /* // * Get the mapping type // */ // const CiftiMappingType::MappingType rowMappingType = ciftiXML.getMappingType(CiftiXML::ALONG_COLUMN); // // switch (rowMappingType) { // case CiftiMappingType::BRAIN_MODELS: // rowIndex = ciftiXML.getBrainModelsMap(CiftiXML::ALONG_COLUMN).getIndexForNode(nodeIndex, structure); // break; // case CiftiMappingType::PARCELS: // rowIndex = ciftiXML.getParcelsMap(CiftiXML::ALONG_COLUMN).getIndexForNode(nodeIndex, structure); // break; // case CIFTI_INDEX_TYPE_SCALARS: // break; // case CIFTI_INDEX_TYPE_TIME_POINTS: // break; // default: // CaretAssert(0); // CaretLogSevere("Invalid row mapping type for connectivity file " // + DataFileTypeEnum::toName(getDataFileType())); // break; // } // return rowIndex; //} /** * @return The CIFTI Direction (ALONG_COLUMN, ALONG_ROW) for loading data. */ int32_t CiftiMappableConnectivityMatrixDataFile::getCifitDirectionForLoadingRowOrColumn() { /* * Default to loading by row (ALONG_COLUMN) which is how the files normally load. */ int32_t ciftiDirection = CiftiXML::ALONG_COLUMN; /** * Parcel File is able to load by either and implements the * ChartableMatrixInterface. */ ChartableMatrixParcelInterface* matrixParcelInterface = dynamic_cast(this); if (matrixParcelInterface != NULL) { switch (matrixParcelInterface->getMatrixLoadingDimension()) { case ChartMatrixLoadingDimensionEnum::CHART_MATRIX_LOADING_BY_COLUMN: ciftiDirection = CiftiXML::ALONG_ROW; break; case ChartMatrixLoadingDimensionEnum::CHART_MATRIX_LOADING_BY_ROW: ciftiDirection = CiftiXML::ALONG_COLUMN; break; } } return ciftiDirection; } /** * Get the index of a row or column when loading data for a voxel at a coordinate. * @param xyz * Coordinate of the voxel. * @param rowIndexOut * Index of row corresponding to voxel or -1 if no row in the * matrix corresponds to the voxel. * @param columnIndexOut * Index of column corresponding to voxel or -1 if no column in the * matrix corresponds to the voxel. */ void CiftiMappableConnectivityMatrixDataFile::getRowColumnIndexForVoxelAtCoordinateWhenLoading(const float xyz[3], int64_t& rowIndexOut, int64_t& columnIndexOut) { rowIndexOut = -1; columnIndexOut = -1; if (m_ciftiFile == NULL) { return; } int64_t ijk[3]; enclosingVoxel(xyz[0], xyz[1], xyz[2], ijk[0], ijk[1], ijk[2]); return getRowColumnIndexForVoxelIndexWhenLoading(ijk, rowIndexOut, columnIndexOut); } /** * Get the index of a row or column when loading data for a voxel index. * @param ijk * Indicies of the voxel. * @param rowIndexOut * Index of row corresponding to voxel or -1 if no row in the * matrix corresponds to the voxel. * @param columnIndexOut * Index of column corresponding to voxel or -1 if no column in the * matrix corresponds to the voxel. */ void CiftiMappableConnectivityMatrixDataFile::getRowColumnIndexForVoxelIndexWhenLoading(const int64_t ijk[3], int64_t& rowIndexOut, int64_t& columnIndexOut) { rowIndexOut = -1; columnIndexOut = -1; if (m_ciftiFile == NULL) { return; } const CiftiXML& ciftiXML = m_ciftiFile->getCiftiXML(); const int32_t ciftiDirection = getCifitDirectionForLoadingRowOrColumn(); const CiftiMappingType::MappingType rowMappingType = ciftiXML.getMappingType(CiftiXML::ALONG_COLUMN); int64_t rowOrColumnIndex = -1; /* * Get the mapping type */ if (indexValid(ijk[0], ijk[1], ijk[2])) { switch (rowMappingType) { case CIFTI_INDEX_TYPE_BRAIN_MODELS: rowOrColumnIndex = ciftiXML.getBrainModelsMap(ciftiDirection).getIndexForVoxel(ijk); break; case CIFTI_INDEX_TYPE_PARCELS: rowOrColumnIndex = ciftiXML.getParcelsMap(ciftiDirection).getIndexForVoxel(ijk); break; default: CaretAssert(0); CaretLogSevere("Invalid row mapping type for connectivity file " + DataFileTypeEnum::toName(getDataFileType())); break; } } switch (ciftiDirection) { case CiftiXML::ALONG_COLUMN: rowIndexOut = rowOrColumnIndex; break; case CiftiXML::ALONG_ROW: columnIndexOut = rowOrColumnIndex; break; default: CaretAssert(0); break; } } ///** // * Get the index of a row when loading data for a voxel at a coordinate. // * @param xyz // * Coordinate of the voxel. // * @return // * Index of row corresponding to voxel or negative if no row in the // * matrix corresponds to the voxel. // */ //int64_t //CiftiMappableConnectivityMatrixDataFile::getRowIndexForVoxelAtCoordinateWhenLoading(const float xyz[3]) //{ // if (m_ciftiFile == NULL) { // return -1; // } // int64_t ijk[3]; // enclosingVoxel(xyz[0], xyz[1], xyz[2], ijk[0], ijk[1], ijk[2]); // return getRowIndexForVoxelIndexWhenLoading(ijk); //} // ///** // * Get the index of a row when loading data for a voxel index. // * // * @param ijk // * Indices of the voxel. // * @return // * Index of row corresponding to voxel or negative if no row in the // * matrix corresponds to the voxel. // */ //int64_t //CiftiMappableConnectivityMatrixDataFile::getRowIndexForVoxelIndexWhenLoading(const int64_t ijk[3]) //{ // if (m_ciftiFile == NULL) { // return -1; // } // // int64_t rowIndex = -1; // // const CiftiXML& ciftiXML = m_ciftiFile->getCiftiXML(); // const CiftiMappingType::MappingType rowMappingType = ciftiXML.getMappingType(CiftiXML::ALONG_COLUMN); // // /* // * Get the mapping type // */ // if (indexValid(ijk[0], ijk[1], ijk[2])) { // switch (rowMappingType) { // case CIFTI_INDEX_TYPE_BRAIN_MODELS: // rowIndex = ciftiXML.getBrainModelsMap(CiftiXML::ALONG_COLUMN).getIndexForVoxel(ijk); // break; // case CIFTI_INDEX_TYPE_PARCELS: // rowIndex = ciftiXML.getParcelsMap(CiftiXML::ALONG_COLUMN).getIndexForVoxel(ijk); // break; // default: // CaretAssert(0); // CaretLogSevere("Invalid row mapping type for connectivity file " // + DataFileTypeEnum::toName(getDataFileType())); // break; // } // } // // return rowIndex; //} /** * Set the loaded row data to zeros. */ void CiftiMappableConnectivityMatrixDataFile::setLoadedRowDataToAllZeros() { if ( ! m_loadedRowData.empty()){ std::fill(m_loadedRowData.begin(), m_loadedRowData.end(), 0.0); } updateForChangeInMapDataWithMapIndex(0); // if (m_mapContent.empty() == false) { // m_mapContent[0]->updateForChangeInMapData(); // } m_connectivityDataLoaded->reset(); m_rowLoadedText.clear(); m_rowLoadedTextForMapName.clear(); } /** * Reset the loaded row data to empty. * * Resets the loaded row data to zero elements and also * clears loaded data attributes. * * One usage of this method is when a parcel connectivity file has * its loading direction changed between row and parcel loading. */ void CiftiMappableConnectivityMatrixDataFile::resetLoadedRowDataToEmpty() { m_loadedRowData.clear(); setLoadedRowDataToAllZeros(); } /** * Load the given row from the file even if the file is disabled. * * NOTE: Afterwards, it will be necessary to update this file's color mapping * with updateScalarColoringForMap(). * * * @param rowIndex * Index of row that is loaded. * @throw DataFileException * If an error occurs. */ void CiftiMappableConnectivityMatrixDataFile::loadDataForRowIndex(const int64_t rowIndex) { setLoadedRowDataToAllZeros(); const int64_t dataCount = m_ciftiFile->getNumberOfColumns(); if (dataCount > 0) { if ((rowIndex >= 0) && (rowIndex < m_ciftiFile->getNumberOfRows())) { m_rowLoadedTextForMapName = ("Row: " + AString::number(rowIndex)); m_rowLoadedText = ("Row_" + AString::number(rowIndex)); CaretAssert((rowIndex >= 0) && (rowIndex < m_ciftiFile->getNumberOfRows())); m_loadedRowData.resize(dataCount); m_ciftiFile->getRow(&m_loadedRowData[0], rowIndex); CaretLogFine("Read row " + AString::number(rowIndex)); m_connectivityDataLoaded->setRowColumnLoading(rowIndex, -1); } } // else { // throw DataFileException("Row " // + AString::number(rowIndex) // + " is invalid or contains no data."); // } updateForChangeInMapDataWithMapIndex(0); } /** * Load the given column from the file even if the file is disabled. * * NOTE: Afterwards, it will be necessary to update this file's color mapping * with updateScalarColoringForMap(). * * * @param columnIndex * Index of row that is loaded. * @throw DataFileException * If an error occurs. */ void CiftiMappableConnectivityMatrixDataFile::loadDataForColumnIndex(const int64_t columnIndex) { setLoadedRowDataToAllZeros(); const int64_t dataCount = m_ciftiFile->getNumberOfRows(); if (dataCount > 0) { if ((columnIndex >= 0) && (columnIndex < m_ciftiFile->getNumberOfColumns())) { m_rowLoadedTextForMapName = ("Column: " + AString::number(columnIndex)); m_rowLoadedText = ("Column_" + AString::number(columnIndex)); CaretAssert((columnIndex >= 0) && (columnIndex < m_ciftiFile->getNumberOfColumns())); m_loadedRowData.resize(dataCount); m_ciftiFile->getColumn(&m_loadedRowData[0], columnIndex); CaretLogFine("Read column " + AString::number(columnIndex)); m_connectivityDataLoaded->setRowColumnLoading(-1, columnIndex); } } // else { // throw DataFileException("Column " // + AString::number(columnIndex) // + " is invalid or contains no data."); // } updateForChangeInMapDataWithMapIndex(0); } /** * Load connectivity data for the surface's node. * * @param mapIndex * Index of map. * @param surfaceNumberOfNodes * Number of nodes in surface. * @param structure * Surface's structure. * @param nodeIndex * Index of node number. * @param rowIndexOut * Index of row corresponding to node or -1 if no row in the * matrix corresponds to the node. * @param columnIndexOut * Index of column corresponding to node or -1 if no column in the * matrix corresponds to the node. * @throw * DataFileException if there is an error. */ void CiftiMappableConnectivityMatrixDataFile::loadMapDataForSurfaceNode(const int32_t /*mapIndex*/, const int32_t surfaceNumberOfNodes, const StructureEnum::Enum structure, const int32_t nodeIndex, int64_t& rowIndexOut, int64_t& columnIndexOut) { rowIndexOut = -1; columnIndexOut = -1; if (m_ciftiFile == NULL) { setLoadedRowDataToAllZeros(); return; } /* * Loading of data disabled? */ if (m_dataLoadingEnabled == false) { return; } /* * Zero out here so that data only gets cleared when data * is to be loaded. */ setLoadedRowDataToAllZeros(); int64_t rowIndex = -1; int64_t columnIndex = -1; try { bool dataWasLoaded = false; getRowColumnIndexForNodeWhenLoading(structure, surfaceNumberOfNodes, nodeIndex, rowIndex, columnIndex); if (rowIndex >= 0) { const int64_t dataCount = m_ciftiFile->getNumberOfColumns(); if (dataCount > 0) { m_rowLoadedTextForMapName = ("Row: " + AString::number(rowIndex) + ", Node Index: " + AString::number(nodeIndex) + ", Structure: " + StructureEnum::toName(structure)); m_rowLoadedText = ("Row_" + AString::number(rowIndex) + "_Node_Index_" + AString::number(nodeIndex) + "_Structure_" + StructureEnum::toGuiName(structure)); CaretAssert((rowIndex >= 0) && (rowIndex < m_ciftiFile->getNumberOfRows())); m_loadedRowData.resize(dataCount); m_ciftiFile->getRow(&m_loadedRowData[0], rowIndex); CaretLogFine("Read row for node " + AString::number(nodeIndex)); m_connectivityDataLoaded->setSurfaceNodeLoading(structure, surfaceNumberOfNodes, nodeIndex, rowIndex, -1); rowIndexOut = rowIndex; dataWasLoaded = true; } } else if (columnIndex >= 0) { const int64_t dataCount = m_ciftiFile->getNumberOfRows(); if (dataCount > 0) { m_rowLoadedTextForMapName = ("Column: " + AString::number(columnIndex) + ", Node Index: " + AString::number(nodeIndex) + ", Structure: " + StructureEnum::toName(structure)); m_rowLoadedText = ("Column_" + AString::number(columnIndex) + "_Node_Index_" + AString::number(nodeIndex) + "_Structure_" + StructureEnum::toGuiName(structure)); CaretAssert((columnIndex >= 0) && (columnIndex < m_ciftiFile->getNumberOfColumns())); m_loadedRowData.resize(dataCount); m_ciftiFile->getColumn(&m_loadedRowData[0], columnIndex); CaretLogFine("Read column for node " + AString::number(nodeIndex)); m_connectivityDataLoaded->setSurfaceNodeLoading(structure, surfaceNumberOfNodes, nodeIndex, -1, columnIndex); columnIndexOut = columnIndex; dataWasLoaded = true; } } if (dataWasLoaded == false) { CaretLogFine("FAILED to read data for node " + AString::number(nodeIndex)); m_connectivityDataLoaded->reset(); } } catch (DataFileException& e) { m_connectivityDataLoaded->reset(); throw e; } updateForChangeInMapDataWithMapIndex(0); } ///** // * Load connectivity data for the given map index. // * // * @param mapIndex // * Index of map. // * @param surfaceNumberOfNodes // * Number of nodes in surface. // * @param structure // * Surface's structure. // * @param nodeIndex // * Index of node number. // * @return // * Index of row that was loaded or -1 if no data was loaded. // * @throw // * DataFileException if there is an error. // */ //bool //CiftiMappableConnectivityMatrixDataFile::loadMapData(const int32_t selectionIndex) //{ // if (m_ciftiFile == NULL) { // return false; // } // // /* // * Loading of data disabled? // */ // if (m_dataLoadingEnabled == false) { // return false; // } // // // // bool dataWasLoaded = false; // // if (selectionIndex >= 0) { // const int64_t dataCount = m_ciftiFile->getNumberOfColumns(); // if (dataCount > 0) { // m_rowLoadedTextForMapName = ("Row: " // + AString::number(selectionIndex+1) // ); // // m_rowLoadedText = ("Row_" // + AString::number(selectionIndex+1) // ); // CaretAssert((selectionIndex >= 0) && (selectionIndex < m_ciftiFile->getNumberOfRows())); // m_loadedRowData.resize(dataCount); // m_ciftiFile->getRow(&m_loadedRowData[0], // selectionIndex); // // CaretLogFine("Read row " + AString::number(selectionIndex+1)); // // dataWasLoaded = true; // } // } // // if (dataWasLoaded == false) { // CaretLogFine("FAILED to read row " + AString::number(selectionIndex+1)); // } // // updateForChangeInMapDataWithMapIndex(0); // // return true; //} /** * Load connectivity data for the surface's nodes and then average the data. * * @param mapIndex * Index of map. * @param surfaceFile * Surface file used for structure. * @param surfaceNumberOfNodes * Number of nodes in surface. * @param structure * Surface's structure. * @param nodeIndices * Indices of nodes. * @throw * DataFileException if there is an error. */ void CiftiMappableConnectivityMatrixDataFile::loadMapAverageDataForSurfaceNodes(const int32_t /*mapIndex*/, const int32_t surfaceNumberOfNodes, const StructureEnum::Enum structure, const std::vector& nodeIndices) { if (m_ciftiFile == NULL) { setLoadedRowDataToAllZeros(); return; } /* * Loading of data disabled? */ if (m_dataLoadingEnabled == false) { return; } /* * Zero out here so that data only gets cleared when data * is to be loaded. */ setLoadedRowDataToAllZeros(); const int32_t numberOfNodeIndices = static_cast(nodeIndices.size()); if (numberOfNodeIndices <= 0) { return; } const bool isDenseMatrix = (getDataFileType() == DataFileTypeEnum::CONNECTIVITY_DENSE); const int32_t progressUpdateInterval = 1; bool dataWasLoaded = false; int64_t dataCount = 0; switch (getCifitDirectionForLoadingRowOrColumn()) { case CiftiXML::ALONG_COLUMN: dataCount = m_ciftiFile->getNumberOfColumns(); break; case CiftiXML::ALONG_ROW: dataCount = m_ciftiFile->getNumberOfRows(); break; case CiftiXML::ALONG_STACK: break; } if (dataCount > 0) { /* * Contains the average */ std::vector dataAverageVector(dataCount, 0.0); float* dataAverage = &dataAverageVector[0]; /* * Contains row/column for a node */ std::vector dataRowColumnVector(dataCount, 0.0); float* dataRowColumn = &dataRowColumnVector[0]; int64_t successCount = 0; bool userCancelled = false; EventProgressUpdate progressEvent(0, numberOfNodeIndices, 0, ("Loading data for " + QString::number(numberOfNodeIndices) + " vertices in file ") + getFileNameNoPath()); EventManager::get()->sendEvent(progressEvent.getPointer()); /* * Read rows for each node */ for (int32_t i = 0; i < numberOfNodeIndices; i++) { const int32_t nodeIndex = nodeIndices[i]; if (isDenseMatrix) { if ((i % progressUpdateInterval) == 0) { progressEvent.setProgress(i, ""); EventManager::get()->sendEvent(progressEvent.getPointer()); if (progressEvent.isCancelled()) { userCancelled = true; break; } } } int64_t rowIndex = -1; int64_t columnIndex = -1; getRowColumnIndexForNodeWhenLoading(structure, surfaceNumberOfNodes, nodeIndex, rowIndex, columnIndex); // const int64_t rowIndex = getRowIndexForNodeWhenLoading(structure, // surfaceNumberOfNodes, // nodeIndex); if (rowIndex >= 0) { CaretAssert((rowIndex >= 0) && (rowIndex < m_ciftiFile->getNumberOfRows())); m_ciftiFile->getRow(dataRowColumn, rowIndex); for (int64_t j = 0; j < dataCount; j++) { dataAverage[j] += dataRowColumn[j]; } successCount++; CaretLogFine("Read row for node " + AString::fromNumbers(nodeIndices, ",")); } else if (columnIndex >= 0) { CaretAssert((columnIndex >= 0) && (columnIndex < m_ciftiFile->getNumberOfColumns())); m_ciftiFile->getColumn(dataRowColumn, columnIndex); for (int64_t j = 0; j < dataCount; j++) { dataAverage[j] += dataRowColumn[j]; } successCount++; CaretLogFine("Read column for node " + AString::fromNumbers(nodeIndices, ",")); } else { CaretLogFine("Failed reading data for node " + AString::number(nodeIndex)); } } if (userCancelled) { m_loadedRowData.clear(); m_loadedRowData.resize(dataCount, 0.0); } else if (successCount > 0) { /* * Average the data */ for (int64_t i = 0; i < dataCount; i++) { dataAverage[i] /= successCount; } m_rowLoadedTextForMapName = ("Structure: " + StructureEnum::toName(structure) + ", Averaged Node Count: " + AString::number(numberOfNodeIndices)); m_rowLoadedText = ("Structure_" + StructureEnum::toGuiName(structure) + "_Averaged_Node_Count_" + AString::number(numberOfNodeIndices)); /* * Update the viewed data */ m_loadedRowData = dataAverageVector; dataWasLoaded = true; } } if (dataWasLoaded == false) { CaretLogFine("FAILED to read data for node average" + AString::fromNumbers(nodeIndices, ",")); } updateForChangeInMapDataWithMapIndex(0); if (dataWasLoaded) { m_connectivityDataLoaded->setSurfaceAverageNodeLoading(structure, surfaceNumberOfNodes, nodeIndices); } } /** * Load data for a voxel at the given coordinate. * * @param mapIndex * Index of map. * @param xyz * Coordinate of voxel. * @param rowIndexOut * Index of row corresponding to voxel or -1 if no row in the * matrix corresponds to the voxel. * @param columnIndexOut * Index of column corresponding to voxel or -1 if no column in the * matrix corresponds to the voxel. * @throw * DataFileException if there is an error. */ void CiftiMappableConnectivityMatrixDataFile::loadMapDataForVoxelAtCoordinate(const int32_t mapIndex, const float xyz[3], int64_t& rowIndexOut, int64_t& columnIndexOut) { rowIndexOut = -1; columnIndexOut = -1; if (mapIndex != 0) { setLoadedRowDataToAllZeros(); CaretAssertMessage(0, "Map index must be zero."); return; } if (m_ciftiFile == NULL) { setLoadedRowDataToAllZeros(); return; } /* * Loading of data disabled? */ if (m_dataLoadingEnabled == false) { return; } /* * Zero out here so that data only gets cleared when data * is to be loaded. */ setLoadedRowDataToAllZeros(); int64_t rowIndex = -1; int64_t columnIndex = -1; bool dataWasLoaded = false; getRowColumnIndexForVoxelAtCoordinateWhenLoading(xyz, rowIndex, columnIndex); if (rowIndex >= 0) { const int64_t dataCount = m_ciftiFile->getNumberOfColumns(); if (dataCount > 0) { m_loadedRowData.resize(dataCount); CaretAssert((rowIndex >= 0) && (rowIndex < m_ciftiFile->getNumberOfRows())); m_ciftiFile->getRow(&m_loadedRowData[0], rowIndex); m_rowLoadedTextForMapName = ("Row: " + AString::number(rowIndex) + ", Voxel XYZ: (" + AString::fromNumbers(xyz, 3, ",") + ")"); m_rowLoadedText = ("Row_" + AString::number(rowIndex) + "_Voxel_XYZ_" + AString::fromNumbers(xyz, 3, "_").replace('-', 'm')); CaretLogFine("Read row for voxel " + AString::fromNumbers(xyz, 3, ",")); rowIndexOut = rowIndex; dataWasLoaded = true; } } else if (columnIndex >= 0) { const int64_t dataCount = m_ciftiFile->getNumberOfRows(); if (dataCount > 0) { m_loadedRowData.resize(dataCount); CaretAssert((columnIndex >= 0) && (columnIndex < m_ciftiFile->getNumberOfColumns())); m_ciftiFile->getColumn(&m_loadedRowData[0], columnIndex); m_rowLoadedTextForMapName = ("Column: " + AString::number(columnIndex) + ", Voxel XYZ: (" + AString::fromNumbers(xyz, 3, ",") + ")"); m_rowLoadedText = ("Column_" + AString::number(columnIndex) + "_Voxel_XYZ_" + AString::fromNumbers(xyz, 3, "_").replace('-', 'm')); CaretLogFine("Read column for voxel " + AString::fromNumbers(xyz, 3, ",")); columnIndexOut = columnIndex; dataWasLoaded = true; } } if (dataWasLoaded == false) { CaretLogFine("FAILED to read row/column for voxel " + AString::fromNumbers(xyz, 3, ",")); } updateForChangeInMapDataWithMapIndex(0); m_connectivityDataLoaded->setVolumeXYZLoading(xyz, rowIndex, columnIndex); } /** * Load connectivity data for the voxel indices and then average the data. * * @param mapIndex * Index of map. * @param volumeDimensionIJK * Dimensions of the volume. * @param voxelIndices * Indices of voxels. * @throw * DataFileException if there is an error. */ bool CiftiMappableConnectivityMatrixDataFile::loadMapAverageDataForVoxelIndices(const int32_t mapIndex, const int64_t volumeDimensionIJK[3], const std::vector& voxelIndices) { if (mapIndex != 0) { // eliminates compilation warning when compiled for release CaretAssert(mapIndex == 0); setLoadedRowDataToAllZeros(); } if (m_ciftiFile == NULL) { setLoadedRowDataToAllZeros(); return false; } /* * Loading of data disabled? */ if (m_dataLoadingEnabled == false) { return false; } /* * Zero out here so that data only gets cleared when data * is to be loaded. */ setLoadedRowDataToAllZeros(); /* * Match dimensions */ std::vector volumeDimensions; getDimensions(volumeDimensions); if (volumeDimensions.size() < 3) { return false; } if ((volumeDimensions[0] != volumeDimensionIJK[0]) || (volumeDimensions[1] != volumeDimensionIJK[1]) || (volumeDimensions[2] != volumeDimensionIJK[2])) { return false; } /* * Get content for map. */ int64_t dataCount = 0; switch (getCifitDirectionForLoadingRowOrColumn()) { case CiftiXML::ALONG_COLUMN: dataCount = m_ciftiFile->getNumberOfColumns(); break; case CiftiXML::ALONG_ROW: dataCount = m_ciftiFile->getNumberOfRows(); break; case CiftiXML::ALONG_STACK: break; } if (dataCount <= 0) { return false; } const int64_t numberOfVoxelIndices = static_cast(voxelIndices.size()); bool userCancelled = false; const int32_t progressUpdateInterval = 1; EventProgressUpdate progressEvent(0, numberOfVoxelIndices, 0, ("Loading data for " + QString::number(numberOfVoxelIndices) + " voxels in file ") + getFileNameNoPath()); EventManager::get()->sendEvent(progressEvent.getPointer()); std::vector rowColumnData(dataCount); std::vector rowColumnSum(dataCount, 0.0); /* * Load and sum the data for all rows/columns */ int64_t numberOfRowColumnsLoaded = 0; for (int64_t i = 0; i < numberOfVoxelIndices; i++) { if ((i % progressUpdateInterval) == 0) { progressEvent.setProgress(i, ""); EventManager::get()->sendEvent(progressEvent.getPointer()); if (progressEvent.isCancelled()) { userCancelled = true; break; } } const VoxelIJK& voxelIJK = voxelIndices[i]; int64_t rowIndex; int64_t columnIndex; getRowColumnIndexForVoxelIndexWhenLoading(voxelIJK.m_ijk, rowIndex, columnIndex); if (rowIndex >= 0) { CaretAssert((rowIndex >= 0) && (rowIndex < m_ciftiFile->getNumberOfRows())); m_ciftiFile->getRow(&rowColumnData[0], rowIndex); for (int64_t j = 0; j < dataCount; j++) { rowColumnSum[j] += rowColumnData[j]; } numberOfRowColumnsLoaded++; } else if (columnIndex >= 0) { CaretAssert((columnIndex >= 0) && (columnIndex < m_ciftiFile->getNumberOfColumns())); m_ciftiFile->getColumn(&rowColumnData[0], columnIndex); for (int64_t j = 0; j < dataCount; j++) { rowColumnSum[j] += rowColumnData[j]; } numberOfRowColumnsLoaded++; } } bool dataWasLoadedFlag = false; if (userCancelled) { m_loadedRowData.clear(); m_loadedRowData.resize(dataCount, 0.0); } else if (numberOfRowColumnsLoaded > 0) { progressEvent.setProgress(numberOfVoxelIndices - 1, "Averaging voxel data"); EventManager::get()->sendEvent(progressEvent.getPointer()); m_loadedRowData.resize(dataCount, 0.0); for (int64_t j = 0; j < dataCount; j++) { m_loadedRowData[j] = rowColumnSum[j] / numberOfRowColumnsLoaded; } m_rowLoadedTextForMapName = ("Averaged Voxel Count: " + AString::number(numberOfVoxelIndices)); m_rowLoadedText = ("Averaged_Voxel_Count_" + AString::number(numberOfVoxelIndices)); m_connectivityDataLoaded->setVolumeAverageVoxelLoading(volumeDimensionIJK, voxelIndices); dataWasLoadedFlag = true; } updateForChangeInMapDataWithMapIndex(0); return dataWasLoadedFlag; } /** * @return Text describing row loaded that uses * underscores as separators. */ AString CiftiMappableConnectivityMatrixDataFile::getRowLoadedText() const { return m_rowLoadedText; } /** * Get the name of the map at the given index. For connectivity matrix * files this always returns a description of the last data row that * was loaded. * * @param mapIndex * Index of the map. * @return * Name of the map. */ AString CiftiMappableConnectivityMatrixDataFile::getMapName(const int32_t /*mapIndex*/) const { return m_rowLoadedTextForMapName; } AString CiftiMappableConnectivityMatrixDataFile::getRowName(const int32_t rowIndex) const { const CiftiXML& xml = m_ciftiFile->getCiftiXML(); if (xml.getMappingType(CiftiXML::ALONG_COLUMN) != CiftiMappingType::PARCELS) return "";//TSC: this was originally implemented only for parcels, dunno why const std::vector& plist = xml.getParcelsMap(CiftiXML::ALONG_COLUMN).getParcels(); CaretAssertVectorIndex(plist, rowIndex); return plist[rowIndex].m_name; } AString CiftiMappableConnectivityMatrixDataFile::getColumnName(const int32_t columnIndex) const { const CiftiXML& xml = m_ciftiFile->getCiftiXML(); if (xml.getMappingType(CiftiXML::ALONG_ROW) != CiftiMappingType::PARCELS) return "";//ditto const std::vector& plist = xml.getParcelsMap(CiftiXML::ALONG_ROW).getParcels(); CaretAssertVectorIndex(plist, columnIndex); return plist[columnIndex].m_name; } /** * @return The matrix loading type (by row/column). */ ChartMatrixLoadingDimensionEnum::Enum CiftiMappableConnectivityMatrixDataFile::getChartMatrixLoadingDimension() const { return m_chartLoadingDimension; } /** * Set the matrix loading type (by row/column). * * @param matrixLoadingType * New value for matrix loading type. */ void CiftiMappableConnectivityMatrixDataFile::setChartMatrixLoadingDimension(const ChartMatrixLoadingDimensionEnum::Enum matrixLoadingType) { m_chartLoadingDimension = matrixLoadingType; resetDataLoadingMembers(); switch (m_chartLoadingDimension) { case ChartMatrixLoadingDimensionEnum::CHART_MATRIX_LOADING_BY_COLUMN: m_dataReadingAccessMethod = DATA_ACCESS_FILE_COLUMNS_OR_XML_ALONG_ROW; m_dataMappingAccessMethod = DATA_ACCESS_FILE_ROWS_OR_XML_ALONG_COLUMN; break; case ChartMatrixLoadingDimensionEnum::CHART_MATRIX_LOADING_BY_ROW: m_dataReadingAccessMethod = DATA_ACCESS_FILE_ROWS_OR_XML_ALONG_COLUMN; m_dataMappingAccessMethod = DATA_ACCESS_FILE_COLUMNS_OR_XML_ALONG_ROW; break; } initializeAfterReading(getFileName()); resetLoadedRowDataToEmpty(); } /** * Save file data from the scene. For subclasses that need to * save to a scene, this method should be overriden. sceneClass * will be valid and any scene data should be added to it. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass to which data members should be added. */ void CiftiMappableConnectivityMatrixDataFile::saveFileDataToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass) { CiftiMappableDataFile::saveFileDataToScene(sceneAttributes, sceneClass); sceneClass->addEnumeratedType("m_chartLoadingDimension", m_chartLoadingDimension); m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); saveSubClassDataToScene(sceneAttributes, sceneClass); } /** * Restore file data from the scene. For subclasses that need to * restore from a scene, this method should be overridden. The scene class * will be valid and any scene data may be obtained from it. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass for the instance of a class that implements * this interface. Will NEVER be NULL. */ void CiftiMappableConnectivityMatrixDataFile::restoreFileDataFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { CiftiMappableDataFile::restoreFileDataFromScene(sceneAttributes, sceneClass); m_connectivityDataLoaded->reset(); /* * The chart loading dimension is restored by the scene assistant but * we need to call setChartMatrixLoadingDimension() so that loading * by row or column is properly setup. */ m_chartLoadingDimension = sceneClass->getEnumeratedTypeValue("m_chartLoadingDimension", ChartMatrixLoadingDimensionEnum::CHART_MATRIX_LOADING_BY_ROW); setChartMatrixLoadingDimension(m_chartLoadingDimension); m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); restoreSubClassDataFromScene(sceneAttributes, sceneClass); // /* // * The chart loading dimension is restored by the scene assistant but // * we need to call setChartMatrixLoadingDimension() so that loading // * by row or column is properly setup. // */ // setChartMatrixLoadingDimension(m_chartLoadingDimension); /* * Loading of data may be disabled in the scene * so temporarily enabled loading and then * restore the status. */ const int32_t mapIndex = 0; const bool loadingEnabledStatus = isMapDataLoadingEnabled(mapIndex); setMapDataLoadingEnabled(mapIndex, true); switch (m_connectivityDataLoaded->getMode()) { case ConnectivityDataLoaded::MODE_NONE: setLoadedRowDataToAllZeros(); break; case ConnectivityDataLoaded::MODE_ROW: { int64_t rowIndex; int64_t columnIndex; m_connectivityDataLoaded->getRowColumnLoading(rowIndex, columnIndex); loadDataForRowIndex(rowIndex); } break; case ConnectivityDataLoaded::MODE_COLUMN: { int64_t rowIndex; int64_t columnIndex; m_connectivityDataLoaded->getRowColumnLoading(rowIndex, columnIndex); loadDataForColumnIndex(columnIndex); } break; case ConnectivityDataLoaded::MODE_SURFACE_NODE: { StructureEnum::Enum structure; int32_t surfaceNumberOfNodes; int32_t surfaceNodeIndex; int64_t rowIndex; int64_t columnIndex; m_connectivityDataLoaded->getSurfaceNodeLoading(structure, surfaceNumberOfNodes, surfaceNodeIndex, rowIndex, columnIndex); loadMapDataForSurfaceNode(mapIndex, surfaceNumberOfNodes, structure, surfaceNodeIndex, rowIndex, columnIndex); } break; case ConnectivityDataLoaded::MODE_SURFACE_NODE_AVERAGE: { StructureEnum::Enum structure; int32_t surfaceNumberOfNodes; std::vector surfaceNodeIndices; m_connectivityDataLoaded->getSurfaceAverageNodeLoading(structure, surfaceNumberOfNodes, surfaceNodeIndices); loadMapAverageDataForSurfaceNodes(mapIndex, surfaceNumberOfNodes, structure, surfaceNodeIndices); } break; case ConnectivityDataLoaded::MODE_VOXEL_XYZ: { float volumeXYZ[3]; int64_t rowIndex; int64_t columnIndex; m_connectivityDataLoaded->getVolumeXYZLoading(volumeXYZ, rowIndex, columnIndex); loadMapDataForVoxelAtCoordinate(mapIndex, volumeXYZ, rowIndex, columnIndex); } break; case ConnectivityDataLoaded::MODE_VOXEL_IJK_AVERAGE: { int64_t volumeDimensionsIJK[3]; std::vector voxelIndicesIJK; m_connectivityDataLoaded->getVolumeAverageVoxelLoading(volumeDimensionsIJK, voxelIndicesIJK); loadMapAverageDataForVoxelIndices(mapIndex, volumeDimensionsIJK, voxelIndicesIJK); } break; } setMapDataLoadingEnabled(mapIndex, loadingEnabledStatus); } /** * Save subclass data to the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass to which data members should be added. Will always * be valid (non-NULL). */ void CiftiMappableConnectivityMatrixDataFile::saveSubClassDataToScene(const SceneAttributes* /*sceneAttributes*/, SceneClass* /*sceneClass*/) { /* This method is intended only for subclasses to override */ } /** * Restore file data from the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass for the instance of a class that implements * this interface. Will NEVER be NULL. */ void CiftiMappableConnectivityMatrixDataFile::restoreSubClassDataFromScene(const SceneAttributes* /*sceneAttributes*/, const SceneClass* /*sceneClass*/) { /* This method is intended only for subclasses to override */ } workbench-1.1.1/src/Files/CiftiMappableConnectivityMatrixDataFile.h000066400000000000000000000171571255417355300254010ustar00rootroot00000000000000#ifndef __CIFTI_MAPPABLE_CONNECTIVITY_MATRIX_DATA_FILE_H__ #define __CIFTI_MAPPABLE_CONNECTIVITY_MATRIX_DATA_FILE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "BrainConstants.h" #include "ChartMatrixLoadingDimensionEnum.h" #include "CiftiMappableDataFile.h" #include "VoxelIJK.h" namespace caret { class ConnectivityDataLoaded; class SceneClassAssistant; class CiftiMappableConnectivityMatrixDataFile : public CiftiMappableDataFile { protected: CiftiMappableConnectivityMatrixDataFile(const DataFileTypeEnum::Enum dataFileType); public: virtual ~CiftiMappableConnectivityMatrixDataFile(); bool isMapDataLoadingEnabled(const int32_t mapIndex) const; void setMapDataLoadingEnabled(const int32_t mapIndex, const bool enabled); virtual void loadMapDataForSurfaceNode(const int32_t mapIndex, const int32_t surfaceNumberOfNodes, const StructureEnum::Enum structure, const int32_t nodeIndex, int64_t& rowIndexOut, int64_t& columnIndexOut); virtual void loadMapAverageDataForSurfaceNodes(const int32_t mapIndex, const int32_t surfaceNumberOfNodes, const StructureEnum::Enum structure, const std::vector& nodeIndices); virtual void loadMapDataForVoxelAtCoordinate(const int32_t mapIndex, const float xyz[3], int64_t& rowIndexOut, int64_t& columnIndexOut); virtual bool loadMapAverageDataForVoxelIndices(const int32_t mapIndex, const int64_t volumeDimensionIJK[3], const std::vector& voxelIndices); void loadDataForRowIndex(const int64_t rowIndex); void loadDataForColumnIndex(const int64_t rowIndex); virtual void clear(); virtual bool isEmpty() const; virtual AString getMapName(const int32_t mapIndex) const; AString getRowName(const int32_t rowIndex) const; AString getColumnName(const int32_t rowIndex) const; AString getRowLoadedText() const; //int64_t getRowLoadedIndex() const; virtual void getMapData(const int32_t mapIndex, std::vector& dataOut) const; // bool loadMapData(const int32_t rowIndex); const ConnectivityDataLoaded* getConnectivityDataLoaded() const; bool getParcelNodesElementForSelectedParcel(std::set &parcelNodesOut, const StructureEnum::Enum &structure) const; ChartMatrixLoadingDimensionEnum::Enum getChartMatrixLoadingDimension() const; private: CiftiMappableConnectivityMatrixDataFile(const CiftiMappableConnectivityMatrixDataFile&); CiftiMappableConnectivityMatrixDataFile& operator=(const CiftiMappableConnectivityMatrixDataFile&); public: // ADD_NEW_METHODS_HERE protected: virtual void saveFileDataToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass); virtual void restoreFileDataFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); virtual void saveSubClassDataToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass); virtual void restoreSubClassDataFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); void resetLoadedRowDataToEmpty(); void setChartMatrixLoadingDimension(const ChartMatrixLoadingDimensionEnum::Enum matrixLoadingType); private: void setLoadedRowDataToAllZeros(); void clearPrivate(); // int64_t getRowIndexForNodeWhenLoading(const StructureEnum::Enum structure, // const int64_t surfaceNumberOfNodes, // const int64_t nodeIndex); void getRowColumnIndexForNodeWhenLoading(const StructureEnum::Enum structure, const int64_t surfaceNumberOfNodes, const int64_t nodeIndex, int64_t& rowIndexOut, int64_t& columnIndexOut); void getRowColumnIndexForVoxelAtCoordinateWhenLoading(const float xyz[3], int64_t& rowIndexOut, int64_t& columnIndexOut); void getRowColumnIndexForVoxelIndexWhenLoading(const int64_t ijk[3], int64_t& rowIndexOut, int64_t& columnIndexOut); // int64_t getRowIndexForVoxelAtCoordinateWhenLoading(const float xyz[3]); // // int64_t getRowIndexForVoxelIndexWhenLoading(const int64_t ijk[3]); int32_t getCifitDirectionForLoadingRowOrColumn(); // ADD_NEW_MEMBERS_HERE SceneClassAssistant* m_sceneAssistant; bool m_dataLoadingEnabled; std::vector m_loadedRowData; AString m_rowLoadedTextForMapName; AString m_rowLoadedText; ConnectivityDataLoaded* m_connectivityDataLoaded; /* * This is really a member of parcel file since it the parcel * file is the only file that can load by row or column. * However, because of scenes, the chart loading dimension needs * to be restored in this class. */ ChartMatrixLoadingDimensionEnum::Enum m_chartLoadingDimension; friend class CiftiBrainordinateScalarFile; }; #ifdef __CIFTI_MAPPABLE_CONNECTIVITY_MATRIX_DATA_FILE_DECLARE__ // #endif // __CIFTI_MAPPABLE_CONNECTIVITY_MATRIX_DATA_FILE_DECLARE__ } // namespace #endif //__CIFTI_MAPPABLE_CONNECTIVITY_MATRIX_DATA_FILE_H__ workbench-1.1.1/src/Files/CiftiMappableDataFile.cxx000066400000000000000000007530521255417355300221710ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __CIFTI_MAPPABLE_DATA_FILE_DECLARE__ #include "CiftiMappableDataFile.h" #undef __CIFTI_MAPPABLE_DATA_FILE_DECLARE__ #include "BoundingBox.h" #include "CaretAssert.h" #include "CaretLogger.h" #include "ChartDataCartesian.h" #include "CiftiBrainordinateLabelFile.h" #include "CiftiBrainordinateScalarFile.h" #include "CiftiFiberTrajectoryFile.h" #include "CiftiFile.h" #include "CiftiMappableConnectivityMatrixDataFile.h" #include "CiftiParcelLabelFile.h" #include "CaretTemporaryFile.h" #include "CiftiXML.h" #include "DataFileContentInformation.h" #include "EventManager.h" #include "EventPaletteGetByName.h" #include "FastStatistics.h" #include "FileInformation.h" #include "GiftiLabel.h" #include "GiftiLabelTable.h" #include "GiftiMetaData.h" #include "GroupAndNameHierarchyModel.h" #include "Histogram.h" #include "NodeAndVoxelColoring.h" #include "PaletteColorMapping.h" #include "PaletteFile.h" #include "SparseVolumeIndexer.h" using namespace caret; /** * \class caret::CiftiMappableDataFile * \brief Abstract class for CIFTI files that are mapped to surfaces and volumes * \ingroup Files */ /** * Constructor. * * @param dataFileType * Type of data file. */ CiftiMappableDataFile::CiftiMappableDataFile(const DataFileTypeEnum::Enum dataFileType) : CaretMappableDataFile(dataFileType) { m_ciftiFile.grabNew(NULL); m_voxelIndicesToOffset.grabNew(NULL); m_classNameHierarchy.grabNew(NULL); m_fileDataReadingType = FILE_READ_DATA_ALL; m_containsSurfaceData = false; m_containsVolumeData = false; m_mappingTimeStart = 0.0; m_mappingTimeStep = 0.0; m_mappingTimeUnits = NiftiTimeUnitsEnum::NIFTI_UNITS_UNKNOWN; m_dataReadingAccessMethod = DATA_ACCESS_METHOD_INVALID; m_dataMappingAccessMethod = DATA_ACCESS_METHOD_INVALID; m_colorMappingMethod = COLOR_MAPPING_METHOD_INVALID; m_paletteColorMappingSource = PALETTE_COLOR_MAPPING_SOURCE_INVALID; m_fileMapDataType = FILE_MAP_DATA_TYPE_INVALID; m_dataMappingDirectionForCiftiXML = S_CIFTI_XML_ALONG_INVALID; m_dataReadingDirectionForCiftiXML = S_CIFTI_XML_ALONG_INVALID; m_fileFastStatistics.grabNew(NULL); m_fileHistogram.grabNew(NULL); m_fileHistorgramLimitedValues.grabNew(NULL); /* * Note: The first palette normalization mode is assumed to * be the default mode. */ m_paletteNormalizationModesSupported.clear(); /* * Note: Force an update of the class and name hierarchy */ m_forceUpdateOfGroupAndNameHierarchy = true; switch (dataFileType) { case DataFileTypeEnum::CONNECTIVITY_DENSE: m_dataReadingAccessMethod = DATA_ACCESS_FILE_ROWS_OR_XML_ALONG_COLUMN; m_dataMappingAccessMethod = DATA_ACCESS_FILE_COLUMNS_OR_XML_ALONG_ROW; m_colorMappingMethod = COLOR_MAPPING_METHOD_PALETTE; m_paletteColorMappingSource = PALETTE_COLOR_MAPPING_SOURCE_FROM_FILE; m_paletteNormalizationModesSupported.push_back(PaletteNormalizationModeEnum::NORMALIZATION_SELECTED_MAP_DATA); m_paletteNormalizationModesSupported.push_back(PaletteNormalizationModeEnum::NORMALIZATION_ALL_MAP_DATA); m_fileMapDataType = FILE_MAP_DATA_TYPE_MATRIX; break; case DataFileTypeEnum::CONNECTIVITY_DENSE_LABEL: m_dataReadingAccessMethod = DATA_ACCESS_FILE_COLUMNS_OR_XML_ALONG_ROW; m_dataMappingAccessMethod = DATA_ACCESS_FILE_ROWS_OR_XML_ALONG_COLUMN; m_colorMappingMethod = COLOR_MAPPING_METHOD_LABEL_TABLE; m_fileMapDataType = FILE_MAP_DATA_TYPE_MULTI_MAP; break; case DataFileTypeEnum::CONNECTIVITY_DENSE_PARCEL: m_dataReadingAccessMethod = DATA_ACCESS_FILE_ROWS_OR_XML_ALONG_COLUMN; m_dataMappingAccessMethod = DATA_ACCESS_FILE_COLUMNS_OR_XML_ALONG_ROW; m_colorMappingMethod = COLOR_MAPPING_METHOD_PALETTE; m_paletteColorMappingSource = PALETTE_COLOR_MAPPING_SOURCE_FROM_FILE; m_paletteNormalizationModesSupported.push_back(PaletteNormalizationModeEnum::NORMALIZATION_ALL_MAP_DATA); m_fileMapDataType = FILE_MAP_DATA_TYPE_MATRIX; break; case DataFileTypeEnum::CONNECTIVITY_DENSE_SCALAR: m_dataReadingAccessMethod = DATA_ACCESS_FILE_COLUMNS_OR_XML_ALONG_ROW; m_dataMappingAccessMethod = DATA_ACCESS_FILE_ROWS_OR_XML_ALONG_COLUMN; m_colorMappingMethod = COLOR_MAPPING_METHOD_PALETTE; m_paletteColorMappingSource = PALETTE_COLOR_MAPPING_SOURCE_FROM_MAP; m_paletteNormalizationModesSupported.push_back(PaletteNormalizationModeEnum::NORMALIZATION_SELECTED_MAP_DATA); m_paletteNormalizationModesSupported.push_back(PaletteNormalizationModeEnum::NORMALIZATION_ALL_MAP_DATA); m_fileMapDataType = FILE_MAP_DATA_TYPE_MULTI_MAP; break; case DataFileTypeEnum::CONNECTIVITY_DENSE_TIME_SERIES: m_dataReadingAccessMethod = DATA_ACCESS_FILE_COLUMNS_OR_XML_ALONG_ROW; m_dataMappingAccessMethod = DATA_ACCESS_FILE_ROWS_OR_XML_ALONG_COLUMN; m_colorMappingMethod = COLOR_MAPPING_METHOD_PALETTE; m_paletteColorMappingSource = PALETTE_COLOR_MAPPING_SOURCE_FROM_FILE; m_paletteNormalizationModesSupported.push_back(PaletteNormalizationModeEnum::NORMALIZATION_SELECTED_MAP_DATA); m_paletteNormalizationModesSupported.push_back(PaletteNormalizationModeEnum::NORMALIZATION_ALL_MAP_DATA); m_fileMapDataType = FILE_MAP_DATA_TYPE_MULTI_MAP; break; case DataFileTypeEnum::CONNECTIVITY_PARCEL: m_dataReadingAccessMethod = DATA_ACCESS_FILE_ROWS_OR_XML_ALONG_COLUMN; m_dataMappingAccessMethod = DATA_ACCESS_FILE_COLUMNS_OR_XML_ALONG_ROW; m_colorMappingMethod = COLOR_MAPPING_METHOD_PALETTE; m_paletteColorMappingSource = PALETTE_COLOR_MAPPING_SOURCE_FROM_FILE; m_paletteNormalizationModesSupported.push_back(PaletteNormalizationModeEnum::NORMALIZATION_ALL_MAP_DATA); m_fileMapDataType = FILE_MAP_DATA_TYPE_MATRIX; break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_DENSE: m_dataReadingAccessMethod = DATA_ACCESS_FILE_ROWS_OR_XML_ALONG_COLUMN; m_dataMappingAccessMethod = DATA_ACCESS_FILE_COLUMNS_OR_XML_ALONG_ROW; m_colorMappingMethod = COLOR_MAPPING_METHOD_PALETTE; m_paletteColorMappingSource = PALETTE_COLOR_MAPPING_SOURCE_FROM_FILE; m_paletteNormalizationModesSupported.push_back(PaletteNormalizationModeEnum::NORMALIZATION_ALL_MAP_DATA); m_fileMapDataType = FILE_MAP_DATA_TYPE_MATRIX; break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_LABEL: m_dataReadingAccessMethod = DATA_ACCESS_FILE_COLUMNS_OR_XML_ALONG_ROW; m_dataMappingAccessMethod = DATA_ACCESS_FILE_ROWS_OR_XML_ALONG_COLUMN; m_colorMappingMethod = COLOR_MAPPING_METHOD_LABEL_TABLE; m_fileMapDataType = FILE_MAP_DATA_TYPE_MULTI_MAP; break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_SCALAR: m_dataReadingAccessMethod = DATA_ACCESS_FILE_COLUMNS_OR_XML_ALONG_ROW; m_dataMappingAccessMethod = DATA_ACCESS_FILE_ROWS_OR_XML_ALONG_COLUMN; m_colorMappingMethod = COLOR_MAPPING_METHOD_PALETTE; m_paletteColorMappingSource = PALETTE_COLOR_MAPPING_SOURCE_FROM_MAP; m_paletteNormalizationModesSupported.push_back(PaletteNormalizationModeEnum::NORMALIZATION_ALL_MAP_DATA); m_paletteNormalizationModesSupported.push_back(PaletteNormalizationModeEnum::NORMALIZATION_SELECTED_MAP_DATA); m_fileMapDataType = FILE_MAP_DATA_TYPE_MULTI_MAP; break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_SERIES: m_dataReadingAccessMethod = DATA_ACCESS_FILE_COLUMNS_OR_XML_ALONG_ROW; m_dataMappingAccessMethod = DATA_ACCESS_FILE_ROWS_OR_XML_ALONG_COLUMN; m_colorMappingMethod = COLOR_MAPPING_METHOD_PALETTE; m_paletteColorMappingSource = PALETTE_COLOR_MAPPING_SOURCE_FROM_FILE; m_paletteNormalizationModesSupported.push_back(PaletteNormalizationModeEnum::NORMALIZATION_ALL_MAP_DATA); m_paletteNormalizationModesSupported.push_back(PaletteNormalizationModeEnum::NORMALIZATION_SELECTED_MAP_DATA); m_fileMapDataType = FILE_MAP_DATA_TYPE_MULTI_MAP; break; case DataFileTypeEnum::CONNECTIVITY_SCALAR_DATA_SERIES: m_dataReadingAccessMethod = DATA_ACCESS_FILE_ROWS_OR_XML_ALONG_COLUMN; m_dataMappingAccessMethod = DATA_ACCESS_NONE; m_colorMappingMethod = COLOR_MAPPING_METHOD_PALETTE; m_paletteColorMappingSource = PALETTE_COLOR_MAPPING_SOURCE_FROM_FILE; m_paletteNormalizationModesSupported.push_back(PaletteNormalizationModeEnum::NORMALIZATION_ALL_MAP_DATA); m_fileMapDataType = FILE_MAP_DATA_TYPE_MULTI_MAP; break; case DataFileTypeEnum::BORDER: case DataFileTypeEnum::CONNECTIVITY_FIBER_ORIENTATIONS_TEMPORARY: case DataFileTypeEnum::CONNECTIVITY_FIBER_TRAJECTORY_TEMPORARY: case DataFileTypeEnum::FOCI: case DataFileTypeEnum::IMAGE: case DataFileTypeEnum::LABEL: case DataFileTypeEnum::METRIC: case DataFileTypeEnum::PALETTE: case DataFileTypeEnum::RGBA: case DataFileTypeEnum::SCENE: case DataFileTypeEnum::SPECIFICATION: case DataFileTypeEnum::SURFACE: case DataFileTypeEnum::UNKNOWN: case DataFileTypeEnum::VOLUME: CaretAssertMessage(0, (DataFileTypeEnum::toGuiName(dataFileType) + " is not a CIFTI Mappable Data File.")); break; } /* * Data from matrix files is read as needed since * Dense can be very large */ switch (m_fileMapDataType) { case FILE_MAP_DATA_TYPE_INVALID: break; case FILE_MAP_DATA_TYPE_MATRIX: m_fileDataReadingType = FILE_READ_DATA_AS_NEEDED; break; case FILE_MAP_DATA_TYPE_MULTI_MAP: m_fileDataReadingType = FILE_READ_DATA_ALL; break; } /* * Note: The first palette normalization mode is assumed to * be the default mode. The method called is in a parent class. */ if ( ! m_paletteNormalizationModesSupported.empty()) { setPaletteNormalizationMode(m_paletteNormalizationModesSupported[0]); } CaretAssert(m_dataReadingAccessMethod != DATA_ACCESS_METHOD_INVALID); CaretAssert(m_dataMappingAccessMethod != DATA_ACCESS_METHOD_INVALID); CaretAssert(m_colorMappingMethod != COLOR_MAPPING_METHOD_INVALID); CaretAssert(m_fileMapDataType != FILE_MAP_DATA_TYPE_INVALID); if (m_colorMappingMethod != COLOR_MAPPING_METHOD_LABEL_TABLE) { CaretAssert(m_paletteColorMappingSource != PALETTE_COLOR_MAPPING_SOURCE_INVALID); } setupCiftiReadingMappingDirection(); m_classNameHierarchy.grabNew(new GroupAndNameHierarchyModel()); } /** * Destructor. */ CiftiMappableDataFile::~CiftiMappableDataFile() { clearPrivate(); } /** * Create a new instance of a CIFTI file from the given CIFTI data file type * for the given surface structure and number of nodes. NOT all CIFTI file * types are supported. * * @param ciftiFileType * Data file type for the returned CIFTI file. * @param structure * The surface structure. * @param numberOfNodes * Number of nodes in the surface. * @param errorMessageOut * Will describe problem if there was an error such as an unsupported * CIFTI file type. * @param * Pointer to the newly created CIFTI file. If there is an * error, NULL will be returned and errorMessageOut will describe the * problem. User will need to 'dynamic_cast' the returned pointer to * the class corresponding to the CIFTI file type. */ CiftiMappableDataFile* CiftiMappableDataFile::newInstanceForCiftiFileTypeAndSurface(const DataFileTypeEnum::Enum ciftiFileType, const StructureEnum::Enum structure, const int32_t numberOfNodes, AString& errorMessageOut) { errorMessageOut.clear(); CiftiFile* ciftiFile = NULL; CiftiMappableDataFile* ciftiMappableFile = NULL; try { bool hasLabelsFlag = false; bool hasScalarsFlag = false; /* * Create the appropriate file type. */ switch (ciftiFileType) { case DataFileTypeEnum::CONNECTIVITY_DENSE_LABEL: ciftiMappableFile = new CiftiBrainordinateLabelFile(); hasLabelsFlag = true; break; case DataFileTypeEnum::CONNECTIVITY_DENSE_SCALAR: ciftiMappableFile = new CiftiBrainordinateScalarFile(); hasScalarsFlag = true; break; default: errorMessageOut = ("Creation of " + DataFileTypeEnum::toGuiName(ciftiFileType) + " is not supported."); CaretAssertMessage(0, errorMessageOut); return NULL; break; } /* * Create the XML. */ CiftiXML myXML; myXML.setNumberOfDimensions(2); /* * Add labels or scalars to XML. */ if (hasLabelsFlag) { CiftiLabelsMap labelsMap; labelsMap.setLength(1); myXML.setMap(CiftiXML::ALONG_ROW, labelsMap); } else if (hasScalarsFlag) { CiftiScalarsMap scalarsMap; scalarsMap.setLength(1); myXML.setMap(CiftiXML::ALONG_ROW, scalarsMap); } else { CaretAssert(0); } /* * Add brainordinates to the XML. */ CiftiBrainModelsMap brainModelsMap; brainModelsMap.addSurfaceModel(numberOfNodes, structure); myXML.setMap(CiftiXML::ALONG_COLUMN, brainModelsMap); /* * Add XML to the CIFTI file. */ ciftiFile = new CiftiFile(); ciftiFile->setCiftiXML(myXML); /* * Fill with zeros */ std::vector zeroData(numberOfNodes, 0.0); ciftiFile->setColumn(&zeroData[0], 0); /* * Add the CiftiFile to the Cifti Mappable File */ const AString defaultFileName = ciftiMappableFile->getFileName(); ciftiMappableFile->m_ciftiFile.grabNew(ciftiFile); ciftiMappableFile->setFileName(defaultFileName); ciftiMappableFile->initializeAfterReading(defaultFileName); ciftiMappableFile->setModified(); return ciftiMappableFile; } catch (const DataFileException& de) { errorMessageOut = de.whatString(); } if (ciftiMappableFile != NULL) { delete ciftiMappableFile; ciftiMappableFile = NULL; } if (ciftiFile != NULL) { delete ciftiFile; ciftiFile = NULL; } return NULL; } /** * Clear the contents of the file. */ void CiftiMappableDataFile::clear() { CaretMappableDataFile::clear(); clearPrivate(); } /** * Clear the contents of the file. * Note that "clear()" is virtual and cannot be called from destructor. */ void CiftiMappableDataFile::clearPrivate() { /* * Do not clear this items as they are setup in the constructor. * m_dataReadingAccessMethod * m_dataMappingAccessMethod * m_colorMappingMethod * m_fileMapDataType */ m_ciftiFile.grabNew(NULL); resetDataLoadingMembers(); m_containsSurfaceData = false; m_containsVolumeData = false; m_mappingTimeStart = 0.0; m_mappingTimeStep = 0.0; m_mappingTimeUnits = NiftiTimeUnitsEnum::NIFTI_UNITS_UNKNOWN; } /** * Reset data loading members. * Also used when parcel row/column loading is changed. */ void CiftiMappableDataFile::resetDataLoadingMembers() { const int64_t num = static_cast(m_mapContent.size()); for (int64_t i = 0; i < num; i++) { delete m_mapContent[i]; } m_mapContent.clear(); m_classNameHierarchy->clear(); m_forceUpdateOfGroupAndNameHierarchy = true; } /** * @return Is this file empty? */ bool CiftiMappableDataFile::isEmpty() const { if (getNumberOfMaps() > 0) { return false; } return true; } /** * Set preference for reading. Reading all data from a "matrix" type file * is not supported and if requested, it will be ignored. * * @param prefer * When true, only header is read and no data is read. * When false, both header and all data is read. */ void CiftiMappableDataFile::setPreferOnDiskReading(const bool& prefer) { if (prefer) { m_fileDataReadingType = FILE_READ_DATA_AS_NEEDED; } else { switch (m_fileMapDataType) { case FILE_MAP_DATA_TYPE_INVALID: break; case FILE_MAP_DATA_TYPE_MATRIX: CaretLogSevere("CIFTI Matrix files do not support reading of all data."); break; case FILE_MAP_DATA_TYPE_MULTI_MAP: m_fileDataReadingType = FILE_READ_DATA_ALL; break; } } } /** * @return structure file maps to. */ StructureEnum::Enum CiftiMappableDataFile::getStructure() const { /* * CIFTI files apply to all structures. */ return StructureEnum::ALL; } /** * Set the structure to which file maps. * @param structure * New structure to which file maps. */ void CiftiMappableDataFile::setStructure(const StructureEnum::Enum /*structure*/) { /* CIFTI files may apply to all structures */ } /** * Is this file able to map to the given structure? Some data files, such * as CIFTI files, are able to map to multiple surface structure. The default * implementation of this method simply compares the given structure to * getStructure() and returns true if they are the same value, else false. * * @param structure * Structure for testing mappability status. * @return True if this file is able to map to the given structure, else false. */ bool CiftiMappableDataFile::isMappableToSurfaceStructure(const StructureEnum::Enum structure) const { /* * Validate number of nodes are correct */ int32_t numCiftiNodes = getMappingSurfaceNumberOfNodes(structure); if (numCiftiNodes > 0) { return true; } return false; } /** * @return Metadata for the file. */ GiftiMetaData* CiftiMappableDataFile::getFileMetaData() { CaretAssert(m_ciftiFile); const CiftiXML& ciftiXML = m_ciftiFile->getCiftiXML(); return ciftiXML.getFileMetaData(); } /** * @return Metadata for the file. */ const GiftiMetaData* CiftiMappableDataFile:: getFileMetaData() const { CaretAssert(m_ciftiFile); const CiftiXML& ciftiXML = m_ciftiFile->getCiftiXML(); return ciftiXML.getFileMetaData(); } /** * Read the file. * * @param ciftiMapFileName * Name of the file to read. * @throw * DataFileException if there is an error reading the file. */ void CiftiMappableDataFile::readFile(const AString& ciftiMapFileName) { clear(); try { /* * Is the file on the network (name begins with http, ftp, etc.). */ if (DataFile::isFileOnNetwork(ciftiMapFileName)) { /* * Data in Xnat does not end with a valid file extension * but ends with HTTP search parameters. Thus, if the * filename does not have a valid extension, assume that * the data is in Xnat. */ bool isValidFileExtension = false; DataFileTypeEnum::fromFileExtension(ciftiMapFileName, &isValidFileExtension); if (isValidFileExtension) { switch (m_fileMapDataType) { case FILE_MAP_DATA_TYPE_INVALID: break; case FILE_MAP_DATA_TYPE_MATRIX: throw DataFileException(ciftiMapFileName + " of type " + DataFileTypeEnum::toGuiName(getDataFileType()) + " cannot be read over the network. The file must be" " accessed by reading individual rows and/or columns" " and this cannot be performed over a network."); break; case FILE_MAP_DATA_TYPE_MULTI_MAP: break; } CaretTemporaryFile tempFile; tempFile.readFile(ciftiMapFileName); m_ciftiFile.grabNew(new CiftiFile()); m_ciftiFile->openFile(tempFile.getFileName()); m_ciftiFile->convertToInMemory(); } else { m_ciftiFile.grabNew(new CiftiFile()); AString username = ""; AString password = ""; AString filenameToOpen = ""; /* * Username and password may be embedded in URL, so extract them. */ FileInformation fileInfo(ciftiMapFileName); fileInfo.getRemoteUrlUsernameAndPassword(filenameToOpen, username, password); /* * Always override with a password entered by the user. */ if (CaretDataFile::getFileReadingUsername().isEmpty() == false) { username = CaretDataFile::getFileReadingUsername(); password = CaretDataFile::getFileReadingPassword(); } m_ciftiFile->openURL(filenameToOpen, username, password); } } else { m_ciftiFile.grabNew(new CiftiFile()); switch (m_fileMapDataType) { case FILE_MAP_DATA_TYPE_INVALID: break; case FILE_MAP_DATA_TYPE_MATRIX: m_ciftiFile->openFile(ciftiMapFileName); break; case FILE_MAP_DATA_TYPE_MULTI_MAP: m_ciftiFile->openFile(ciftiMapFileName); switch (m_fileDataReadingType) { case FILE_READ_DATA_ALL: m_ciftiFile->convertToInMemory(); break; case FILE_READ_DATA_AS_NEEDED: break; } break; } } if (m_ciftiFile != NULL) { initializeAfterReading(ciftiMapFileName); } } catch (DataFileException& e) { clear(); throw e; } catch (CaretException& e) { clear(); throw DataFileException(ciftiMapFileName, e.whatString()); } setFileName(ciftiMapFileName); clearModified(); } /** * Validate the mapping types for each dimension. * * @param filename * Name of file. */ void CiftiMappableDataFile::validateMappingTypes(const AString& filename) { CaretAssert(m_ciftiFile); CiftiMappingType::MappingType expectedAlongColumnMapType = CiftiMappingType::BRAIN_MODELS; CiftiMappingType::MappingType expectedAlongRowMapType = CiftiMappingType::BRAIN_MODELS; const DataFileTypeEnum::Enum dataFileType = getDataFileType(); switch (dataFileType) { case DataFileTypeEnum::CONNECTIVITY_DENSE: expectedAlongColumnMapType = CiftiMappingType::BRAIN_MODELS; expectedAlongRowMapType = CiftiMappingType::BRAIN_MODELS; break; case DataFileTypeEnum::CONNECTIVITY_DENSE_LABEL: expectedAlongColumnMapType = CiftiMappingType::BRAIN_MODELS; expectedAlongRowMapType = CiftiMappingType::LABELS; break; case DataFileTypeEnum::CONNECTIVITY_DENSE_PARCEL: expectedAlongColumnMapType = CiftiMappingType::BRAIN_MODELS; expectedAlongRowMapType = CiftiMappingType::PARCELS; break; case DataFileTypeEnum::CONNECTIVITY_DENSE_SCALAR: expectedAlongColumnMapType = CiftiMappingType::BRAIN_MODELS; expectedAlongRowMapType = CiftiMappingType::SCALARS; break; case DataFileTypeEnum::CONNECTIVITY_DENSE_TIME_SERIES: expectedAlongColumnMapType = CiftiMappingType::BRAIN_MODELS; expectedAlongRowMapType = CiftiMappingType::SERIES; break; case DataFileTypeEnum::CONNECTIVITY_PARCEL: expectedAlongColumnMapType = CiftiMappingType::PARCELS; expectedAlongRowMapType = CiftiMappingType::PARCELS; break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_DENSE: expectedAlongColumnMapType = CiftiMappingType::PARCELS; expectedAlongRowMapType = CiftiMappingType::BRAIN_MODELS; break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_LABEL: expectedAlongColumnMapType = CiftiMappingType::PARCELS; expectedAlongRowMapType = CiftiMappingType::LABELS; break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_SCALAR: expectedAlongColumnMapType = CiftiMappingType::PARCELS; expectedAlongRowMapType = CiftiMappingType::SCALARS; break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_SERIES: expectedAlongColumnMapType = CiftiMappingType::PARCELS; expectedAlongRowMapType = CiftiMappingType::SERIES; break; case DataFileTypeEnum::CONNECTIVITY_SCALAR_DATA_SERIES: expectedAlongColumnMapType = CiftiMappingType::SCALARS; expectedAlongRowMapType = CiftiMappingType::SERIES; break; case DataFileTypeEnum::BORDER: case DataFileTypeEnum::CONNECTIVITY_FIBER_ORIENTATIONS_TEMPORARY: case DataFileTypeEnum::CONNECTIVITY_FIBER_TRAJECTORY_TEMPORARY: case DataFileTypeEnum::FOCI: case DataFileTypeEnum::IMAGE: case DataFileTypeEnum::LABEL: case DataFileTypeEnum::METRIC: case DataFileTypeEnum::PALETTE: case DataFileTypeEnum::RGBA: case DataFileTypeEnum::SCENE: case DataFileTypeEnum::SPECIFICATION: case DataFileTypeEnum::SURFACE: case DataFileTypeEnum::UNKNOWN: case DataFileTypeEnum::VOLUME: throw DataFileException(filename, DataFileTypeEnum::toGuiName(dataFileType) + " is not a CIFTI Mappable Data File."); break; } const CiftiXML& ciftiXML = m_ciftiFile->getCiftiXML(); const CiftiMappingType::MappingType alongColumnMapType = ciftiXML.getMappingType(CiftiXML::ALONG_COLUMN); const CiftiMappingType::MappingType alongRowMapType = ciftiXML.getMappingType(CiftiXML::ALONG_ROW); AString errorMessage; if (alongColumnMapType != expectedAlongColumnMapType) { errorMessage.appendWithNewLine("Along column mapping type is " + CiftiMappableDataFile::mappingTypeToName(alongColumnMapType) + " but should be " + CiftiMappableDataFile::mappingTypeToName(expectedAlongColumnMapType) + " for file type \"" + DataFileTypeEnum::toGuiName(dataFileType) + "\""); } if (alongRowMapType != expectedAlongRowMapType) { errorMessage.appendWithNewLine("Along row mapping type is " + CiftiMappableDataFile::mappingTypeToName(alongRowMapType) + " but should be " + CiftiMappableDataFile::mappingTypeToName(expectedAlongRowMapType) + " for file type \"" + DataFileTypeEnum::toGuiName(dataFileType) + "\""); } if ( ! errorMessage.isEmpty()) { throw DataFileException(filename, errorMessage); } } /** * Setup the CIFTI mapping and reading directions. */ void CiftiMappableDataFile::setupCiftiReadingMappingDirection() { switch (m_dataMappingAccessMethod) { case DATA_ACCESS_METHOD_INVALID: CaretAssert(0); break; case DATA_ACCESS_NONE: break; case DATA_ACCESS_FILE_ROWS_OR_XML_ALONG_COLUMN: m_dataMappingDirectionForCiftiXML = CiftiXML::ALONG_COLUMN; break; case DATA_ACCESS_FILE_COLUMNS_OR_XML_ALONG_ROW: m_dataMappingDirectionForCiftiXML = CiftiXML::ALONG_ROW; break; } switch (m_dataReadingAccessMethod) { case DATA_ACCESS_METHOD_INVALID: CaretAssert(0); break; case DATA_ACCESS_NONE: break; case DATA_ACCESS_FILE_ROWS_OR_XML_ALONG_COLUMN: m_dataReadingDirectionForCiftiXML = CiftiXML::ALONG_COLUMN; break; case DATA_ACCESS_FILE_COLUMNS_OR_XML_ALONG_ROW: m_dataReadingDirectionForCiftiXML = CiftiXML::ALONG_ROW; break; } } /** * Initialize the CIFTI file. * * @param filename * Name of file. */ void CiftiMappableDataFile::initializeAfterReading(const AString& filename) { CaretAssert(m_ciftiFile); setupCiftiReadingMappingDirection(); validateMappingTypes(filename); const CiftiXML& ciftiXML = m_ciftiFile->getCiftiXML(); if (m_dataMappingDirectionForCiftiXML != S_CIFTI_XML_ALONG_INVALID) { switch (ciftiXML.getMappingType(m_dataMappingDirectionForCiftiXML)) { case CiftiMappingType::BRAIN_MODELS: { const CiftiBrainModelsMap& map = ciftiXML.getBrainModelsMap(m_dataMappingDirectionForCiftiXML); if (! map.getSurfaceStructureList().empty()) { m_containsSurfaceData = true; } if (map.hasVolumeData()) { m_containsVolumeData = true; } } break; case CiftiMappingType::LABELS: CaretAssertMessage(0, "Mapping type should never be LABELS"); throw DataFileException(filename, "Mapping type should never be LABELS"); break; case CiftiMappingType::PARCELS: { const CiftiParcelsMap& map = ciftiXML.getParcelsMap(m_dataMappingDirectionForCiftiXML); if (! map.getParcelSurfaceStructures().empty()) { m_containsSurfaceData = true; } if (map.hasVolumeData()) { m_containsVolumeData = true; } } break; case CiftiMappingType::SCALARS: CaretAssertMessage(0, "Mapping type should never be SCALARS"); throw DataFileException(filename, "Mapping type should never be SCALARS"); break; case CiftiMappingType::SERIES: CaretAssertMessage(0, "Mapping type should never be SERIES"); throw DataFileException(filename, "Mapping type should never be SERIES"); break; } } switch (ciftiXML.getMappingType(m_dataReadingDirectionForCiftiXML)) { case CiftiMappingType::BRAIN_MODELS: break; case CiftiMappingType::LABELS: break; case CiftiMappingType::PARCELS: break; case CiftiMappingType::SCALARS: break; case CiftiMappingType::SERIES: { const CiftiSeriesMap& map = ciftiXML.getSeriesMap(m_dataReadingDirectionForCiftiXML); CiftiSeriesMap::Unit units = map.getUnit(); switch (units) { case CiftiSeriesMap::HERTZ: m_mappingTimeUnits = NiftiTimeUnitsEnum::NIFTI_UNITS_HZ; break; case CiftiSeriesMap::METER: CaretLogWarning("CIFTI Units METER not implemented"); break; case CiftiSeriesMap::RADIAN: CaretLogWarning("CIFTI Units RADIAN not implemented"); break; case CiftiSeriesMap::SECOND: m_mappingTimeUnits = NiftiTimeUnitsEnum::NIFTI_UNITS_SEC; break; } m_mappingTimeStart = map.getStart(); m_mappingTimeStep = map.getStep(); } break; } switch (m_dataMappingAccessMethod) { case DATA_ACCESS_METHOD_INVALID: CaretAssert(0); break; case DATA_ACCESS_NONE: break; case DATA_ACCESS_FILE_ROWS_OR_XML_ALONG_COLUMN: if (ciftiXML.getMappingType(CiftiXML::ALONG_COLUMN) == CiftiMappingType::BRAIN_MODELS) { m_voxelIndicesToOffset.grabNew(new SparseVolumeIndexer(ciftiXML.getBrainModelsMap(CiftiXML::ALONG_COLUMN))); } else if (ciftiXML.getMappingType(CiftiXML::ALONG_COLUMN) == CiftiMappingType::PARCELS) { m_voxelIndicesToOffset.grabNew(new SparseVolumeIndexer(ciftiXML.getParcelsMap(CiftiXML::ALONG_COLUMN))); } else { CaretAssertMessage(0, "Invalid mapping type for mapping data to brainordinates"); throw DataFileException(filename, "Invalid mapping type for mapping data to brainordinates"); } break; case DATA_ACCESS_FILE_COLUMNS_OR_XML_ALONG_ROW: if (ciftiXML.getMappingType(CiftiXML::ALONG_ROW) == CiftiMappingType::BRAIN_MODELS) { m_voxelIndicesToOffset.grabNew(new SparseVolumeIndexer(ciftiXML.getBrainModelsMap(CiftiXML::ALONG_ROW))); } else if (ciftiXML.getMappingType(CiftiXML::ALONG_ROW) == CiftiMappingType::PARCELS) { m_voxelIndicesToOffset.grabNew(new SparseVolumeIndexer(ciftiXML.getParcelsMap(CiftiXML::ALONG_ROW))); } else { CaretAssertMessage(0, "Invalid mapping type for mapping data to brainordinates"); throw DataFileException(filename, "Invalid mapping type for mapping data to brainordinates"); } break; } /* * May not have mappings to voxels */ if (m_voxelIndicesToOffset == NULL) { m_voxelIndicesToOffset.grabNew(new SparseVolumeIndexer()); } int32_t numberOfMaps = 0; switch (m_fileMapDataType) { case FILE_MAP_DATA_TYPE_INVALID: break; case FILE_MAP_DATA_TYPE_MATRIX: numberOfMaps = 1; break; case FILE_MAP_DATA_TYPE_MULTI_MAP: switch (m_dataReadingAccessMethod) { case DATA_ACCESS_METHOD_INVALID: CaretAssert(0); break; case DATA_ACCESS_NONE: break; case DATA_ACCESS_FILE_COLUMNS_OR_XML_ALONG_ROW: numberOfMaps = m_ciftiFile->getNumberOfColumns(); break; case DATA_ACCESS_FILE_ROWS_OR_XML_ALONG_COLUMN: numberOfMaps = m_ciftiFile->getNumberOfRows(); break; } break; } /* * Get data for maps. */ for (int32_t i = 0; i < numberOfMaps; i++) { MapContent* mc = new MapContent(m_ciftiFile, m_fileMapDataType, m_dataReadingDirectionForCiftiXML, m_dataMappingDirectionForCiftiXML, i); m_mapContent.push_back(mc); } m_classNameHierarchy->update(this, true); m_forceUpdateOfGroupAndNameHierarchy = false; m_classNameHierarchy->setAllSelected(true); m_fileFastStatistics.grabNew(NULL); m_fileHistogram.grabNew(NULL); m_fileHistorgramLimitedValues.grabNew(NULL); CaretLogFiner("CLASS/NAME Table for : " + this->getFileNameNoPath() + "\n" + m_classNameHierarchy->toString()); validateKeysAndLabels(); validateAfterFileReading(); } /** * This method is intended for overriding by subclasess so that they * can examine and verify the data that was read. This method is * called after successfully reading a file. */ void CiftiMappableDataFile::validateAfterFileReading() { /* nothing - see method comment. */ } /** * Write the file. * * @param ciftiMapFileName * Name of the file to write. * @throw * DataFileException if there is an error writing the file. */ void CiftiMappableDataFile::writeFile(const AString& ciftiMapFileName) { if (m_ciftiFile == NULL) { throw DataFileException(ciftiMapFileName + " cannot be written because no file is loaded"); } if (getDataFileType() == DataFileTypeEnum::CONNECTIVITY_DENSE) { throw DataFileException(ciftiMapFileName + " dense connectivity files cannot be written to files due to their large sizes."); } m_ciftiFile->writeFile(ciftiMapFileName); setFileName(ciftiMapFileName); clearModified(); } ///** // * @return The string name of the CIFTI index type. // * @param ciftiIndexType // */ //AString //CiftiMappableDataFile::ciftiIndexTypeToName(const IndicesMapToDataType ciftiIndexType) //{ // AString name = "Invalid"; // // switch (ciftiIndexType) { // case CIFTI_INDEX_TYPE_BRAIN_MODELS: // name = "CIFTI_INDEX_TYPE_BRAIN_MODELS"; // break; // case CIFTI_INDEX_TYPE_FIBERS: // name = "CIFTI_INDEX_TYPE_FIBERS"; // break; // case CIFTI_INDEX_TYPE_INVALID: // name = "CIFTI_INDEX_TYPE_INVALID"; // break; // case CIFTI_INDEX_TYPE_LABELS: // name = "CIFTI_INDEX_TYPE_LABELS"; // break; // case CIFTI_INDEX_TYPE_PARCELS: // name = "CIFTI_INDEX_TYPE_PARCELS"; // break; // case CIFTI_INDEX_TYPE_SCALARS: // name = "CIFTI_INDEX_TYPE_SCALARS"; // break; // case CIFTI_INDEX_TYPE_TIME_POINTS: // name = "CIFTI_INDEX_TYPE_TIME_POINTS"; // break; // } // // return name; //} // // /** * @return Is the data mappable to a surface? */ bool CiftiMappableDataFile::isSurfaceMappable() const { return m_containsSurfaceData; } /** * @return Is the data mappable to a volume? */ bool CiftiMappableDataFile::isVolumeMappable() const { return m_containsVolumeData; } /** * @return The number of maps in the file. * Note: Caret5 used the term 'columns'. */ int32_t CiftiMappableDataFile::getNumberOfMaps() const { return m_mapContent.size(); } /** * @return True if the file has map attributes (name and metadata). * For files that do not have map attributes, they should override * this method and return false. If not overriden, this method * returns true. * * Some files (such as CIFTI Connectivity Matrix Files and CIFTI * Data-Series Files) do not have Map Attributes and thus there * is no map name nor map metadata and options to edit these * attributes should not be presented to the user. * * These CIFTI files do contain palette color mapping but it is * associated with the file. To simplify palette color mapping editing * these file will return the file's palette color mapping for any * calls to getMapPaletteColorMapping(). */ bool CiftiMappableDataFile::hasMapAttributes() const { switch (m_fileMapDataType) { case FILE_MAP_DATA_TYPE_INVALID: break; case FILE_MAP_DATA_TYPE_MATRIX: break; case FILE_MAP_DATA_TYPE_MULTI_MAP: return true; break; } return false; } /** * Get the name of the map at the given index. * * @param mapIndex * Index of the map. * @return * Name of the map. */ AString CiftiMappableDataFile::getMapName(const int32_t mapIndex) const { CaretAssertVectorIndex(m_mapContent, mapIndex); return m_mapContent[mapIndex]->getName(); } /** * Set the name of the map at the given index. * * @param mapIndex * Index of the map. * @param mapName * New name for the map. */ void CiftiMappableDataFile::setMapName(const int32_t mapIndex, const AString& mapName) { CaretAssertVectorIndex(m_mapContent, mapIndex); m_mapContent[mapIndex]->setName(mapName); } /** * Get the metadata for the map at the given index * * @param mapIndex * Index of the map. * @return * Metadata for the map (const value). */ const GiftiMetaData* CiftiMappableDataFile::getMapMetaData(const int32_t mapIndex) const { CaretAssertVectorIndex(m_mapContent, mapIndex); return m_mapContent[mapIndex]->m_metadata; } /** * Get the metadata for the map at the given index * * @param mapIndex * Index of the map. * @return * Metadata for the map. */ GiftiMetaData* CiftiMappableDataFile::getMapMetaData(const int32_t mapIndex) { CaretAssertVectorIndex(m_mapContent, mapIndex); return m_mapContent[mapIndex]->m_metadata; } /** * Get the unique ID (UUID) for the map at the given index. * * @param mapIndex * Index of the map. * @return * String containing UUID for the map. */ AString CiftiMappableDataFile::getMapUniqueID(const int32_t mapIndex) const { CaretAssertVectorIndex(m_mapContent, mapIndex); const GiftiMetaData* md = getMapMetaData(mapIndex); const AString uniqueID = md->getUniqueID(); return uniqueID; } /** * @return Is the data in the file mapped to colors using * a palette. */ bool CiftiMappableDataFile::isMappedWithPalette() const { switch (m_colorMappingMethod) { case COLOR_MAPPING_METHOD_INVALID: break; case COLOR_MAPPING_METHOD_LABEL_TABLE: break; case COLOR_MAPPING_METHOD_PALETTE: return true; break; } return false; } /** * Get the data for the given map index. * * @param mapIndex * Index of the map. * @param dataOut * A vector that will contain the data for the map upon exit. */ void CiftiMappableDataFile::getMapData(const int32_t mapIndex, std::vector& dataOut) const { CaretAssertVectorIndex(m_mapContent, mapIndex); CaretAssert(m_ciftiFile); CaretAssert(mapIndex >= 0); switch (m_dataReadingAccessMethod) { case DATA_ACCESS_METHOD_INVALID: CaretAssert(0); break; case DATA_ACCESS_NONE: break; case DATA_ACCESS_FILE_COLUMNS_OR_XML_ALONG_ROW: CaretAssert(mapIndex < m_ciftiFile->getNumberOfColumns()); dataOut.resize(m_ciftiFile->getNumberOfRows()); m_ciftiFile->getColumn(&dataOut[0], mapIndex); break; case DATA_ACCESS_FILE_ROWS_OR_XML_ALONG_COLUMN: CaretAssert(mapIndex < m_ciftiFile->getNumberOfRows()); dataOut.resize(m_ciftiFile->getNumberOfColumns()); m_ciftiFile->getRow(&dataOut[0], mapIndex); break; } } /** * Set the data for the given map index. * * @param mapIndex * Index of the map. * @param dataOut * A vector that contains the data for the map. */ void CiftiMappableDataFile::setMapData(const int32_t mapIndex, const std::vector& data) { CaretAssertVectorIndex(m_mapContent, mapIndex); CaretAssert(m_ciftiFile); CaretAssert(mapIndex >= 0); switch (m_dataReadingAccessMethod) { case DATA_ACCESS_METHOD_INVALID: CaretAssert(0); break; case DATA_ACCESS_NONE: break; case DATA_ACCESS_FILE_COLUMNS_OR_XML_ALONG_ROW: CaretAssert(mapIndex < m_ciftiFile->getNumberOfColumns()); m_ciftiFile->setColumn(&data[0], mapIndex); break; case DATA_ACCESS_FILE_ROWS_OR_XML_ALONG_COLUMN: CaretAssert(mapIndex < m_ciftiFile->getNumberOfRows()); m_ciftiFile->setRow(&data[0], mapIndex); break; } m_forceUpdateOfGroupAndNameHierarchy = true; m_mapContent[mapIndex]->updateForChangeInMapData(); } /** * Update after a change in map data. * * @param mapIndex * Index of map whose data has changed. */ void CiftiMappableDataFile::updateForChangeInMapDataWithMapIndex(const int32_t mapIndex) { CaretAssertVectorIndex(m_mapContent, mapIndex); m_mapContent[mapIndex]->updateForChangeInMapData(); } /** * Invalidate coloring in all maps */ void CiftiMappableDataFile::invalidateColoringInAllMaps() { const int64_t numMaps = static_cast(getNumberOfMaps()); for (int64_t i = 0; i < numMaps; i++) { CaretAssertVectorIndex(m_mapContent, i); m_mapContent[i]->m_rgbaValid = false; } } /** * Get all data within the file. * * @param data * Filled with data and will contain (number-of-rows * number-of-columns) * of data. */ void CiftiMappableDataFile::getFileData(std::vector& data) const { switch (m_fileMapDataType) { case FILE_MAP_DATA_TYPE_INVALID: CaretAssert(0); break; case FILE_MAP_DATA_TYPE_MATRIX: break; case FILE_MAP_DATA_TYPE_MULTI_MAP: break; } CaretAssert(m_ciftiFile); const int64_t numRows = m_ciftiFile->getNumberOfRows(); const int64_t numCols = m_ciftiFile->getNumberOfColumns(); const int64_t dataSize = numRows * numCols; data.resize(dataSize); for (int64_t iRow = 0; iRow < numRows; iRow++) { m_ciftiFile->getRow(&data[iRow * numCols], iRow); } } /** * Get the RGBA mapped version of the file's data matrix. * * @param rgba * RGBA for file's matrix content. * @param paletteFile * File containg palettes for mapping data to RGBA. */ void CiftiMappableDataFile::getMatrixRGBA(std::vector &rgba, PaletteFile *paletteFile) { std::vector data; getFileData(data); const CiftiXML& ciftiXML = m_ciftiFile->getCiftiXML(); PaletteColorMapping *paletteColorMapping = ciftiXML.getFilePalette(); CaretAssert(paletteColorMapping); CaretPointer fastStatistics(new FastStatistics()); const AString paletteName = paletteColorMapping->getSelectedPaletteName(); const Palette* palette = paletteFile->getPaletteByName(paletteName); if (( ! data.empty()) && (palette != NULL)) { fastStatistics->update(&data[0], data.size()); rgba.resize(data.size() * 4); NodeAndVoxelColoring::colorScalarsWithPalette(fastStatistics, paletteColorMapping, palette, &data[0], &data[0], data.size(), &rgba[0]); } else { std::fill(rgba.begin(), rgba.end(), 0.0); } } /** * Get the dimensions of the file (rows and columns). * * @param dim * Contains dimensions upon exit. */ void CiftiMappableDataFile::getMapDimensions(std::vector &dim) const { CaretAssert(m_ciftiFile); dim.clear(); dim.push_back(m_ciftiFile->getNumberOfColumns()); dim.push_back(m_ciftiFile->getNumberOfRows()); } /** * Get statistics describing the distribution of data * mapped with a color palette at the given index. * * @param mapIndex * Index of the map. * @return * Fast statistics for data (will be NULL for data * not mapped using a palette). */ const FastStatistics* CiftiMappableDataFile::getMapFastStatistics(const int32_t mapIndex) { FastStatistics* fastStatsOut = NULL; if (isMappedWithPalette()) { CaretAssertVectorIndex(m_mapContent, mapIndex); if ( ! m_mapContent[mapIndex]->isFastStatisticsValid()) { std::vector data; getMapData(mapIndex, data); m_mapContent[mapIndex]->updateFastStatistics(data); } fastStatsOut = m_mapContent[mapIndex]->m_fastStatistics; } return fastStatsOut; } /** * Get histogram describing the distribution of data * mapped with a color palette at the given index. * * @param mapIndex * Index of the map. * @return * Histogram for data (will be NULL for data * not mapped using a palette). */ const Histogram* CiftiMappableDataFile::getMapHistogram(const int32_t mapIndex) { Histogram* histogramOut = NULL; if (isMappedWithPalette()) { CaretAssertVectorIndex(m_mapContent, mapIndex); if ( ! m_mapContent[mapIndex]->isHistogramValid()) { std::vector data; getMapData(mapIndex, data); m_mapContent[mapIndex]->updateHistogram(data); } histogramOut = m_mapContent[mapIndex]->m_histogram; } return histogramOut; } /** * Get histogram describing the distribution of data * mapped with a color palette at the given index for * data within the given ranges. * * @param mapIndex * Index of the map. * @param mostPositiveValueInclusive * Values more positive than this value are excluded. * @param leastPositiveValueInclusive * Values less positive than this value are excluded. * @param leastNegativeValueInclusive * Values less negative than this value are excluded. * @param mostNegativeValueInclusive * Values more negative than this value are excluded. * @param includeZeroValues * If true zero values (very near zero) are included. * @return * Descriptive statistics for data (will be NULL for data * not mapped using a palette). */ const Histogram* CiftiMappableDataFile::getMapHistogram(const int32_t mapIndex, const float mostPositiveValueInclusive, const float leastPositiveValueInclusive, const float leastNegativeValueInclusive, const float mostNegativeValueInclusive, const bool includeZeroValues) { Histogram* histogramOut = NULL; if (isMappedWithPalette()) { CaretAssertVectorIndex(m_mapContent, mapIndex); if ( ! m_mapContent[mapIndex]->isHistogramLimitedValuesValid(mostPositiveValueInclusive, leastPositiveValueInclusive, leastNegativeValueInclusive, mostNegativeValueInclusive, includeZeroValues)) { std::vector data; getMapData(mapIndex, data); m_mapContent[mapIndex]->updateHistogramLimitedValues(data, mostPositiveValueInclusive, leastPositiveValueInclusive, leastNegativeValueInclusive, mostNegativeValueInclusive, includeZeroValues); } histogramOut = m_mapContent[mapIndex]->m_histogramLimitedValues; } return histogramOut; } /** * @return The estimated size of data after it is uncompressed * and loaded into RAM. A negative value indicates that the * file size cannot be computed. */ int64_t CiftiMappableDataFile::getDataSizeUncompressedInBytes() const { int64_t dataSize = 0; if (m_ciftiFile != NULL) { dataSize = (m_ciftiFile->getNumberOfColumns() * m_ciftiFile->getNumberOfRows() * sizeof(float)); } return dataSize; } /** * Get statistics describing the distribution of data * mapped with a color palette for all data within the file. * * @return * Fast statistics for data (will be NULL for data * not mapped using a palette). */ const FastStatistics* CiftiMappableDataFile::getFileFastStatistics() { if (m_fileFastStatistics == NULL) { std::vector fileData; getFileData(fileData); if ( ! fileData.empty()) { m_fileFastStatistics.grabNew(new FastStatistics()); m_fileFastStatistics->update(&fileData[0], fileData.size()); } } return m_fileFastStatistics; } /** * Get histogram describing the distribution of data * mapped with a color palette for all data within * the file. * * @return * Histogram for data (will be NULL for data * not mapped using a palette). */ const Histogram* CiftiMappableDataFile::getFileHistogram() { if (m_fileHistogram == NULL) { std::vector fileData; getFileData(fileData); if ( ! fileData.empty()) { m_fileHistogram.grabNew(new Histogram()); m_fileHistogram->update(&fileData[0], fileData.size()); } } return m_fileHistogram; } /** * Get histogram describing the distribution of data * mapped with a color palette for all data in the file * within the given range of values. * * @param mostPositiveValueInclusive * Values more positive than this value are excluded. * @param leastPositiveValueInclusive * Values less positive than this value are excluded. * @param leastNegativeValueInclusive * Values less negative than this value are excluded. * @param mostNegativeValueInclusive * Values more negative than this value are excluded. * @param includeZeroValues * If true zero values (very near zero) are included. * @return * Descriptive statistics for data (will be NULL for data * not mapped using a palette). */ const Histogram* CiftiMappableDataFile::getFileHistogram(const float mostPositiveValueInclusive, const float leastPositiveValueInclusive, const float leastNegativeValueInclusive, const float mostNegativeValueInclusive, const bool includeZeroValues) { bool updateHistogramFlag = false; if (m_fileHistorgramLimitedValues != NULL) { if ((mostPositiveValueInclusive != m_fileHistogramLimitedValuesMostPositiveValueInclusive) || (leastPositiveValueInclusive != m_fileHistogramLimitedValuesLeastPositiveValueInclusive) || (leastNegativeValueInclusive != m_fileHistogramLimitedValuesLeastNegativeValueInclusive) || (mostNegativeValueInclusive != m_fileHistogramLimitedValuesMostNegativeValueInclusive) || (includeZeroValues != m_fileHistogramLimitedValuesIncludeZeroValues)) { updateHistogramFlag = true; } } else { updateHistogramFlag = true; } if (updateHistogramFlag) { std::vector fileData; getFileData(fileData); if ( ! fileData.empty()) { if (m_fileHistorgramLimitedValues == NULL) { m_fileHistorgramLimitedValues.grabNew(new Histogram()); } m_fileHistorgramLimitedValues->update(&fileData[0], fileData.size(), mostPositiveValueInclusive, leastPositiveValueInclusive, leastNegativeValueInclusive, mostNegativeValueInclusive, includeZeroValues); m_fileHistogramLimitedValuesMostPositiveValueInclusive = mostPositiveValueInclusive; m_fileHistogramLimitedValuesLeastPositiveValueInclusive = leastPositiveValueInclusive; m_fileHistogramLimitedValuesLeastNegativeValueInclusive = leastNegativeValueInclusive; m_fileHistogramLimitedValuesMostNegativeValueInclusive = mostNegativeValueInclusive; m_fileHistogramLimitedValuesIncludeZeroValues = includeZeroValues; } } return m_fileHistorgramLimitedValues; } /** * Get the palette color mapping for the map at the given index. * * @param mapIndex * Index of the map. * @return * Palette color mapping for the map (will be NULL for data * not mapped using a palette). */ PaletteColorMapping* CiftiMappableDataFile::getMapPaletteColorMapping(const int32_t mapIndex) { CaretAssert(m_ciftiFile); const CiftiXML& ciftiXML = m_ciftiFile->getCiftiXML(); PaletteColorMapping* pcm = NULL; switch (m_colorMappingMethod) { case COLOR_MAPPING_METHOD_INVALID: break; case COLOR_MAPPING_METHOD_LABEL_TABLE: break; case COLOR_MAPPING_METHOD_PALETTE: switch (m_paletteColorMappingSource) { case PALETTE_COLOR_MAPPING_SOURCE_INVALID: break; case PALETTE_COLOR_MAPPING_SOURCE_FROM_FILE: pcm = ciftiXML.getFilePalette(); break; case PALETTE_COLOR_MAPPING_SOURCE_FROM_MAP: CaretAssertVectorIndex(m_mapContent, mapIndex); pcm = m_mapContent[mapIndex]->m_paletteColorMapping; break; } break; } return pcm; } /** * Get the palette color mapping for the map at the given index. * * @param mapIndex * Index of the map. * @return * Palette color mapping for the map (constant) (will be NULL for data * not mapped using a palette). */ const PaletteColorMapping* CiftiMappableDataFile::getMapPaletteColorMapping(const int32_t mapIndex) const { CaretAssert(m_ciftiFile); const CiftiXML& ciftiXML = m_ciftiFile->getCiftiXML(); PaletteColorMapping* pcm = NULL; switch (m_colorMappingMethod) { case COLOR_MAPPING_METHOD_INVALID: break; case COLOR_MAPPING_METHOD_LABEL_TABLE: break; case COLOR_MAPPING_METHOD_PALETTE: switch (m_paletteColorMappingSource) { case PALETTE_COLOR_MAPPING_SOURCE_INVALID: break; case PALETTE_COLOR_MAPPING_SOURCE_FROM_FILE: pcm = ciftiXML.getFilePalette(); break; case PALETTE_COLOR_MAPPING_SOURCE_FROM_MAP: CaretAssertVectorIndex(m_mapContent, mapIndex); pcm = m_mapContent[mapIndex]->m_paletteColorMapping; break; } break; } return pcm; } /** * Get the CIFTI parcels map used for brainordinate mapping. * * @return * Pointer to the map's Cifti Parcels Map or NULL if the file is not * mapped using parcels. */ const CiftiParcelsMap* CiftiMappableDataFile::getCiftiParcelsMapForBrainordinateMapping() const { CaretAssert((m_dataMappingDirectionForCiftiXML == CiftiXML::ALONG_ROW) || (m_dataMappingDirectionForCiftiXML == CiftiXML::ALONG_COLUMN)); return getCiftiParcelsMapForDirection(m_dataMappingDirectionForCiftiXML); } /** * Get the CIFTI parcels map used for data loading. * * @return * Pointer to the map's Cifti Parcels Map or NULL if the file is not * loaded using parcels. */ const CiftiParcelsMap* CiftiMappableDataFile::getCiftiParcelsMapForLoading() const { CaretAssert((m_dataReadingDirectionForCiftiXML == CiftiXML::ALONG_ROW) || (m_dataReadingDirectionForCiftiXML == CiftiXML::ALONG_COLUMN)); return getCiftiParcelsMapForDirection(m_dataReadingDirectionForCiftiXML); } /** * Get the CIFTI parcels for the given direction. * * @param direction * Direction of mapping. MUST BE one of CiftiXML::ALONG_ROW or * CiftiXML::ALONG_COLUMN. * @return * Pointer to the map's Cifti Parcels Map or NULL if the file is not * mapped using parcels or NULL if direction is invalid. */ const CiftiParcelsMap* CiftiMappableDataFile::getCiftiParcelsMapForDirection(const int direction) const { if (m_ciftiFile != NULL) { if ((direction != CiftiXML::ALONG_ROW) && (direction != CiftiXML::ALONG_COLUMN)) { CaretAssert(0); return NULL; } const CiftiXML& ciftiXML = m_ciftiFile->getCiftiXML(); const CiftiMappingType* mapping = ciftiXML.getMap(direction); if (mapping->getType() == CiftiMappingType::PARCELS) { const CiftiParcelsMap* cpm = dynamic_cast(mapping); CaretAssert(cpm); return cpm; } } return NULL; } /** * @return Is the data in the file mapped to colors using * a label table. */ bool CiftiMappableDataFile::isMappedWithLabelTable() const { switch (m_colorMappingMethod) { case COLOR_MAPPING_METHOD_INVALID: break; case COLOR_MAPPING_METHOD_LABEL_TABLE: return true; break; case COLOR_MAPPING_METHOD_PALETTE: break; } return false; } /** * Get the label table for the map at the given index. * * @param mapIndex * Index of the map. * @return * Label table for the map (will be NULL for data * not mapped using a label table). */ GiftiLabelTable* CiftiMappableDataFile::getMapLabelTable(const int32_t mapIndex) { CaretAssertVectorIndex(m_mapContent, mapIndex); switch (m_colorMappingMethod) { case COLOR_MAPPING_METHOD_INVALID: break; case COLOR_MAPPING_METHOD_LABEL_TABLE: return m_mapContent[mapIndex]->m_labelTable; break; case COLOR_MAPPING_METHOD_PALETTE: break; } return NULL; } /** * Get the label table for the map at the given index. * * @param mapIndex * Index of the map. * @return * Label table for the map (constant) (will be NULL for data * not mapped using a label table). */ const GiftiLabelTable* CiftiMappableDataFile::getMapLabelTable(const int32_t mapIndex) const { CaretAssertVectorIndex(m_mapContent, mapIndex); switch (m_colorMappingMethod) { case COLOR_MAPPING_METHOD_INVALID: break; case COLOR_MAPPING_METHOD_LABEL_TABLE: return m_mapContent[mapIndex]->m_labelTable; break; case COLOR_MAPPING_METHOD_PALETTE: break; } return NULL; } /** * Get the palette normalization modes that are supported by the file. * * @param modesSupportedOut * Palette normalization modes supported by a file. Will be * empty for files that are not mapped with a palette. If there * is more than one suppported mode, the first mode in the * vector is assumed to be the default mode. */ void CiftiMappableDataFile::getPaletteNormalizationModesSupported(std::vector& modesSupportedOut) { modesSupportedOut = m_paletteNormalizationModesSupported; } /** * Update coloring for all maps. * * Note: Overridden since Data-Series files have one palette that is * applied to ALL maps. For data-series, just invalidate the coloring * for all maps (data points). * * @param paletteFile * Palette file containing palettes. */ void CiftiMappableDataFile::updateScalarColoringForAllMaps(const PaletteFile* /*paletteFile*/) { /* * Just need to invalidate coloring. * Updating coloring for all maps would take time. * Coloring update is triggered by code that colors nodes/voxels * when drawing. */ invalidateColoringInAllMaps(); } /** * Update scalar coloring for a map. * * Note that some CIFTI files can be slow to color due to the need to * retrieve data for the map. Use isMapColoringValid() to avoid * unnecessary calls to isMapColoringValid. * * @param mapIndex * Index of map. * @param paletteFile * Palette file containing palettes. */ void CiftiMappableDataFile::updateScalarColoringForMap(const int32_t mapIndex, const PaletteFile* paletteFile) { CaretAssertVectorIndex(m_mapContent, mapIndex); std::vector data; getMapData(mapIndex, data); m_mapContent[mapIndex]->m_rgbaValid = false; if (isMappedWithPalette()) { FastStatistics* statistics = NULL; switch (getPaletteNormalizationMode()) { case PaletteNormalizationModeEnum::NORMALIZATION_ALL_MAP_DATA: statistics = const_cast(getFileFastStatistics()); break; case PaletteNormalizationModeEnum::NORMALIZATION_SELECTED_MAP_DATA: statistics = const_cast(getMapFastStatistics(mapIndex)); break; } m_mapContent[mapIndex]->updateColoring(data, paletteFile, statistics); } else if (isMappedWithLabelTable()) { m_mapContent[mapIndex]->updateColoring(data, paletteFile, NULL); } else { CaretAssert(0); } } /** * Note that some CIFTI files can be slow to color due to the need to * retrieve data for the map. This method can be used to avoid calls * to updateScalarColoringForMap. * * @param mapIndex * Index of the map. * @return * True if the coloring for the given map index is valid. */ bool CiftiMappableDataFile::isMapColoringValid(const int32_t mapIndex) const { CaretAssertVectorIndex(m_mapContent, mapIndex); return m_mapContent[mapIndex]->m_rgbaValid; } /** * Get the node ins the parcel of the given index. * @param parcelNodes * Output containing the node indices. * @param structure * Structure for which the node indices are requested. * @param selectionIndex * Index of the parcel. * @return true if parcel is valid, else false. */ bool CiftiMappableDataFile::getParcelNodesElementForSelectedParcel(std::set &parcelNodesOut, const StructureEnum::Enum &structure, const int64_t &selectionIndex) const { if (m_ciftiFile->getCiftiXML().getMappingType(CiftiXML::ALONG_COLUMN) != CiftiMappingType::PARCELS) return false; const std::vector& parcels = m_ciftiFile->getCiftiXML().getParcelsMap(CiftiXML::ALONG_COLUMN).getParcels(); if(selectionIndex >= 0 && selectionIndex < (int64_t)parcels.size()) { const CiftiParcelsMap::Parcel& parcelOut = parcels[selectionIndex]; std::map >::const_iterator findStruct = parcelOut.m_surfaceNodes.find(structure); if (findStruct != parcelOut.m_surfaceNodes.end()) { parcelNodesOut = findStruct->second; return true; } } return false; } /** * Get the dimensions of the volume. * * @param dimOut1 * First dimension (i) out. * @param dimOut2 * Second dimension (j) out. * @param dimOut3 * Third dimension (k) out. * @param dimTimeOut * Time dimensions out (number of maps) * @param numComponentsOut * Number of components per voxel. */ void CiftiMappableDataFile::getDimensions(int64_t& dimOut1, int64_t& dimOut2, int64_t& dimOut3, int64_t& dimTimeOut, int64_t& numComponentsOut) const { CaretAssert(m_ciftiFile); dimOut1 = 0; dimOut2 = 0; dimOut3 = 0; dimTimeOut = 0; numComponentsOut = 0; if (m_dataMappingDirectionForCiftiXML != S_CIFTI_XML_ALONG_INVALID) { switch (m_ciftiFile->getCiftiXML().getMappingType(m_dataMappingDirectionForCiftiXML)) { case CiftiMappingType::BRAIN_MODELS: { const CiftiBrainModelsMap& myDenseMap = m_ciftiFile->getCiftiXML().getBrainModelsMap(m_dataMappingDirectionForCiftiXML); if (!myDenseMap.hasVolumeData()) return; const VolumeSpace& mySpace = myDenseMap.getVolumeSpace(); const int64_t* dims = mySpace.getDims(); dimOut1 = dims[0]; dimOut2 = dims[1]; dimOut3 = dims[2]; dimTimeOut = 1;//??? numComponentsOut = 1; break; } case CiftiMappingType::PARCELS: { const CiftiParcelsMap& myParcelMap = m_ciftiFile->getCiftiXML().getParcelsMap(m_dataMappingDirectionForCiftiXML); if (!myParcelMap.hasVolumeData()) return; const VolumeSpace& mySpace = myParcelMap.getVolumeSpace(); const int64_t* dims = mySpace.getDims(); dimOut1 = dims[0]; dimOut2 = dims[1]; dimOut3 = dims[2]; dimTimeOut = 1;//??? numComponentsOut = 1; break; } default://nothing else has volume dimensions break; } } } /** * Get the dimensions of the volume. * * @param dimsOut * Will contain 5 elements: (0) X-dimension, (1) Y-dimension * (2) Z-dimension, (3) time, (4) components. */ void CiftiMappableDataFile::getDimensions(std::vector& dimsOut) const { dimsOut.resize(5); int64_t dimI, dimJ, dimK, dimTime, dimComp; getDimensions(dimI, dimJ, dimK, dimTime, dimComp); dimsOut[0] = dimI; dimsOut[1] = dimJ; dimsOut[2] = dimK; dimsOut[3] = dimTime; dimsOut[4] = dimComp; } /** * @return The number of componenents per voxel in the volume data. */ const int64_t& CiftiMappableDataFile::getNumberOfComponents() const { int64_t dimI, dimJ, dimK, dimTime; static int64_t dimComp = 0; getDimensions(dimI, dimJ, dimK, dimTime, dimComp); return dimComp; } /** * Convert an index to space (coordinates). * * @param indexIn1 * First dimension (i). * @param indexIn2 * Second dimension (j). * @param indexIn3 * Third dimension (k). * @param coordOut1 * Output first (x) coordinate. * @param coordOut2 * Output first (y) coordinate. * @param coordOut3 * Output first (z) coordinate. */ void CiftiMappableDataFile::indexToSpace(const float& indexIn1, const float& indexIn2, const float& indexIn3, float& coordOut1, float& coordOut2, float& coordOut3) const { CaretAssert(m_voxelIndicesToOffset); m_voxelIndicesToOffset->indicesToCoordinate(indexIn1, indexIn2, indexIn3, coordOut1, coordOut2, coordOut3); } /** * Convert an index to space (coordinates). * * @param indexIn1 * First dimension (i). * @param indexIn2 * Second dimension (j). * @param indexIn3 * Third dimension (k). * @param coordOut * Output XYZ coordinates. */ void CiftiMappableDataFile::indexToSpace(const float& indexIn1, const float& indexIn2, const float& indexIn3, float* coordOut) const { CaretAssert(m_voxelIndicesToOffset); m_voxelIndicesToOffset->indicesToCoordinate(indexIn1, indexIn2, indexIn3, coordOut[0], coordOut[1], coordOut[2]); } /** * Convert an index to space (coordinates). * * @param indexIn * IJK indices * @param coordOut * Output XYZ coordinates. */ void CiftiMappableDataFile::indexToSpace(const int64_t* indexIn, float* coordOut) const { CaretAssert(m_voxelIndicesToOffset); m_voxelIndicesToOffset->indicesToCoordinate(indexIn[0], indexIn[1], indexIn[2], coordOut[0], coordOut[1], coordOut[2]); } /** * Convert a coordinate to indices. Note that output indices * MAY NOT BE WITHIN THE VALID VOXEL DIMENSIONS. * * @param coordIn1 * First (x) input coordinate. * @param coordIn2 * Second (y) input coordinate. * @param coordIn3 * Third (z) input coordinate. * @param indexOut1 * First output index (i). * @param indexOut2 * First output index (j). * @param indexOut3 * First output index (k). */ void CiftiMappableDataFile::enclosingVoxel(const float& coordIn1, const float& coordIn2, const float& coordIn3, int64_t& indexOut1, int64_t& indexOut2, int64_t& indexOut3) const { CaretAssert(m_voxelIndicesToOffset); m_voxelIndicesToOffset->coordinateToIndices(coordIn1, coordIn2, coordIn3, indexOut1, indexOut2, indexOut3); } /** * Determine in the given voxel indices are valid (within the volume dimensions). * * @param indexIn1 * First dimension (i). * @param indexIn2 * Second dimension (j). * @param indexIn3 * Third dimension (k). * @param coordOut1 * Output first (x) coordinate. * @param brickIndex * Time/map index (default 0). * @param component * Voxel component (default 0). */ bool CiftiMappableDataFile::indexValid(const int64_t& indexIn1, const int64_t& indexIn2, const int64_t& indexIn3, const int64_t /*brickIndex*/, const int64_t /*component*/) const { std::vector volumeDimensions; getDimensions(volumeDimensions); CaretAssertVectorIndex(volumeDimensions, 2); if ((indexIn1 >= 0) && (indexIn1 < volumeDimensions[0]) && (indexIn2 >= 0) && (indexIn2 < volumeDimensions[1]) && (indexIn3 >= 0) && (indexIn3 < volumeDimensions[2])) { return true; } return false; } /** * Get a bounding box for the voxel coordinate ranges. * * @param boundingBoxOut * The output bounding box. */ void CiftiMappableDataFile::getVoxelSpaceBoundingBox(BoundingBox& boundingBoxOut) const { CaretAssert(m_voxelIndicesToOffset); boundingBoxOut.resetForUpdate(); std::vector volumeDimensions(5, 0); getDimensions(volumeDimensions); CaretAssertVectorIndex(volumeDimensions, 2); if (m_voxelIndicesToOffset->isValid()) { float xyz[3]; indexToSpace(0, 0, 0, xyz); boundingBoxOut.update(xyz); indexToSpace(volumeDimensions[0] - 1, volumeDimensions[1] - 1, volumeDimensions[2] - 1, xyz); boundingBoxOut.update(xyz); } else { boundingBoxOut.resetZeros(); } } /** * Get the voxel colors for a slice in the map. * * @param paletteFile * The palette file. * @param mapIndex * Index of the map. * @param slicePlane * The slice plane. * @param sliceIndex * Index of the slice. * @param displayGroup * The selected display group. * @param tabIndex * Index of selected tab. * @param rgbaOut * Output containing the rgba values (must have been allocated * by caller to sufficient count of elements in the slice). * @return * Number of voxels with alpha greater than zero */ int64_t CiftiMappableDataFile::getVoxelColorsForSliceInMap(const PaletteFile* paletteFile, const int32_t mapIndex, const VolumeSliceViewPlaneEnum::Enum slicePlane, const int64_t sliceIndex, const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, uint8_t* rgbaOut) const { CaretAssertVectorIndex(m_mapContent, mapIndex); CaretAssertMessage((sliceIndex >= 0), "Slice index is invalid."); if (sliceIndex < 0) { return 0; } if (isMapColoringValid(mapIndex) == false) { CiftiMappableDataFile* nonConstThis = const_cast(this); nonConstThis->updateScalarColoringForMap(mapIndex, paletteFile); } int64_t dimI, dimJ, dimK, dimTime, dimComp; getDimensions(dimI, dimJ, dimK, dimTime, dimComp); int64_t voxelCount = 0; switch (slicePlane) { case VolumeSliceViewPlaneEnum::ALL: CaretAssert(0); break; case VolumeSliceViewPlaneEnum::AXIAL: voxelCount = dimI * dimJ; CaretAssert((sliceIndex < dimK)); if (sliceIndex >= dimK) { return 0; } break; case VolumeSliceViewPlaneEnum::CORONAL: voxelCount = dimI * dimK; CaretAssert((sliceIndex < dimJ)); if (sliceIndex >= dimJ) { return 0; } break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: voxelCount = dimJ * dimK; CaretAssert((sliceIndex < dimI)); if (sliceIndex >= dimI) { return 0; } break; } if (voxelCount <= 0) { return 0; } const int64_t componentCount = voxelCount * 4; /* * Clear the slice rgba coloring. */ for (int64_t i = 0; i < componentCount; i++) { rgbaOut[i] = 0; } const int64_t mapRgbaCount = m_mapContent[mapIndex]->m_rgba.size(); /* * RGBA size will be zero if no data has been loaded for a CIFTI * matrix type file (user clicking brainordinate). */ if (mapRgbaCount <= 0) { return 0; } const uint8_t* mapRGBA = &m_mapContent[mapIndex]->m_rgba[0]; CaretAssert(m_voxelIndicesToOffset); /* * Data values are only needed when a label volume * is being drawn so that we can determine if the * label is displayed. */ std::vector dataValues; const GiftiLabelTable* labelTable = (isMappedWithLabelTable() ? getMapLabelTable(mapIndex) : NULL); if (isMappedWithLabelTable()) { CaretAssert(labelTable); getMapData(mapIndex, dataValues); } int64_t validVoxelCount = 0; /* * Set the rgba components for the slice. */ switch (slicePlane) { case VolumeSliceViewPlaneEnum::ALL: CaretAssert(0); break; case VolumeSliceViewPlaneEnum::AXIAL: for (int64_t j = 0; j < dimJ; j++) { for (int64_t i = 0; i < dimI; i++) { const int64_t dataOffset = m_voxelIndicesToOffset->getOffsetForIndices(i, j, sliceIndex); if (dataOffset >= 0) { const int64_t dataOffset4 = dataOffset * 4; CaretAssert(dataOffset4 < mapRgbaCount); const int64_t rgbaOffset = ((j * dimI) + i) * 4; CaretAssert(rgbaOffset < componentCount); rgbaOut[rgbaOffset] = mapRGBA[dataOffset4]; rgbaOut[rgbaOffset+1] = mapRGBA[dataOffset4+1]; rgbaOut[rgbaOffset+2] = mapRGBA[dataOffset4+2]; /* * A negative value for alpha indicates "do not draw". * Since unsigned bytes do not have negative values, * change the value to zero (which indicates "transparent"). */ float alpha = mapRGBA[dataOffset4+3]; if (alpha < 0.0) { alpha = 0.0; } if (alpha > 0.0) { if (labelTable != NULL) { /* * For label data, verify that the label is displayed. * If NOT displayed, zero out the alpha value to * prevent display of the data. */ CaretAssertVectorIndex(dataValues, dataOffset); const int32_t dataValue = dataValues[dataOffset]; const GiftiLabel* label = labelTable->getLabel(dataValue); if (label != NULL) { const GroupAndNameHierarchyItem* item = label->getGroupNameSelectionItem(); CaretAssert(item); if (item->isSelected(displayGroup, tabIndex) == false) { alpha = 0.0; } } } } if (alpha > 0.0) { ++validVoxelCount; } rgbaOut[rgbaOffset+3] = (alpha * 255.0); } } } break; case VolumeSliceViewPlaneEnum::CORONAL: for (int64_t k = 0; k < dimK; k++) { for (int64_t i = 0; i < dimI; i++) { const int64_t dataOffset = m_voxelIndicesToOffset->getOffsetForIndices(i, sliceIndex, k); if (dataOffset >= 0) { const int64_t dataOffset4 = dataOffset * 4; CaretAssert(dataOffset4 < mapRgbaCount); const int64_t rgbaOffset = ((k * dimI) + i) * 4; CaretAssert(rgbaOffset < componentCount); rgbaOut[rgbaOffset] = mapRGBA[dataOffset4]; rgbaOut[rgbaOffset+1] = mapRGBA[dataOffset4+1]; rgbaOut[rgbaOffset+2] = mapRGBA[dataOffset4+2]; /* * A negative value for alpha indicates "do not draw". * Since unsigned bytes do not have negative values, * change the value to zero (which indicates "transparent"). */ float alpha = mapRGBA[dataOffset4+3]; if (alpha < 0.0) { alpha = 0.0; } if (alpha > 0.0) { if (labelTable != NULL) { /* * For label data, verify that the label is displayed. * If NOT displayed, zero out the alpha value to * prevent display of the data. */ CaretAssertVectorIndex(dataValues, dataOffset); const int32_t dataValue = dataValues[dataOffset]; const GiftiLabel* label = labelTable->getLabel(dataValue); if (label != NULL) { const GroupAndNameHierarchyItem* item = label->getGroupNameSelectionItem(); CaretAssert(item); if (item->isSelected(displayGroup, tabIndex) == false) { alpha = 0.0; } } } } if (alpha > 0.0) { ++validVoxelCount; } rgbaOut[rgbaOffset+3] = (alpha * 255.0); } } } break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: for (int64_t k = 0; k < dimK; k++) { for (int64_t j = 0; j < dimJ; j++) { const int64_t dataOffset = m_voxelIndicesToOffset->getOffsetForIndices(sliceIndex, j, k); if (dataOffset >= 0) { const int64_t dataOffset4 = dataOffset * 4; CaretAssert(dataOffset4 < mapRgbaCount); const int64_t rgbaOffset = ((k * dimJ) + j) * 4; CaretAssert(rgbaOffset < componentCount); rgbaOut[rgbaOffset] = mapRGBA[dataOffset4]; rgbaOut[rgbaOffset+1] = mapRGBA[dataOffset4+1]; rgbaOut[rgbaOffset+2] = mapRGBA[dataOffset4+2]; /* * A negative value for alpha indicates "do not draw". * Since unsigned bytes do not have negative values, * change the value to zero (which indicates "transparent"). */ float alpha = mapRGBA[dataOffset4+3]; if (alpha < 0.0) { alpha = 0.0; } if (alpha > 0.0) { if (labelTable != NULL) { /* * For label data, verify that the label is displayed. * If NOT displayed, zero out the alpha value to * prevent display of the data. */ CaretAssertVectorIndex(dataValues, dataOffset); const int32_t dataValue = dataValues[dataOffset]; const GiftiLabel* label = labelTable->getLabel(dataValue); if (label != NULL) { const GroupAndNameHierarchyItem* item = label->getGroupNameSelectionItem(); CaretAssert(item); if (item->isSelected(displayGroup, tabIndex) == false) { alpha = 0.0; } } } } if (alpha > 0.0) { ++validVoxelCount; } rgbaOut[rgbaOffset+3] = (alpha * 255.0); } } } break; } return validVoxelCount; } /** * Get the voxel colors for a sub slice in the map. * * @param paletteFile * The palette file. * @param mapIndex * Index of the map. * @param slicePlane * The slice plane. * @param sliceIndex * Index of the slice. * @param firstCornerVoxelIndex * Indices of voxel for first corner of sub-slice (inclusive). * @param lastCornerVoxelIndex * Indices of voxel for last corner of sub-slice (inclusive). * @param voxelCountIJK * Voxel counts for each axis. * @param displayGroup * The selected display group. * @param tabIndex * Index of selected tab. * @param rgbaOut * Output containing the rgba values (must have been allocated * by caller to sufficient count of elements in the slice). * @return * Number of voxels with alpha greater than zero */ int64_t CiftiMappableDataFile::getVoxelColorsForSubSliceInMap(const PaletteFile* paletteFile, const int32_t mapIndex, const VolumeSliceViewPlaneEnum::Enum slicePlane, const int64_t sliceIndex, const int64_t firstCornerVoxelIndex[3], const int64_t lastCornerVoxelIndex[3], const int64_t voxelCountIJK[3], const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, uint8_t* rgbaOut) const { CaretAssertVectorIndex(m_mapContent, mapIndex); CaretAssertMessage((sliceIndex >= 0), "Slice index is invalid."); if (sliceIndex < 0) { return 0; } if (isMapColoringValid(mapIndex) == false) { CiftiMappableDataFile* nonConstThis = const_cast(this); nonConstThis->updateScalarColoringForMap(mapIndex, paletteFile); } const int64_t iStart = firstCornerVoxelIndex[0]; const int64_t jStart = firstCornerVoxelIndex[1]; const int64_t kStart = firstCornerVoxelIndex[2]; const int64_t iEnd = lastCornerVoxelIndex[0]; const int64_t jEnd = lastCornerVoxelIndex[1]; const int64_t kEnd = lastCornerVoxelIndex[2]; const int64_t voxelCountI = voxelCountIJK[0]; const int64_t voxelCountJ = voxelCountIJK[1]; const int64_t voxelCountK = voxelCountIJK[2]; int64_t dimI, dimJ, dimK, dimTime, dimComp; getDimensions(dimI, dimJ, dimK, dimTime, dimComp); int64_t voxelCount = 0; switch (slicePlane) { case VolumeSliceViewPlaneEnum::ALL: CaretAssert(0); break; case VolumeSliceViewPlaneEnum::AXIAL: voxelCount = voxelCountI * voxelCountJ; CaretAssert((sliceIndex < dimK)); if (sliceIndex >= dimK) { return 0; } break; case VolumeSliceViewPlaneEnum::CORONAL: voxelCount = voxelCountI * voxelCountK; CaretAssert((sliceIndex < dimJ)); if (sliceIndex >= dimJ) { return 0; } break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: voxelCount = voxelCountJ * voxelCountK; CaretAssert((sliceIndex < dimI)); if (sliceIndex >= dimI) { return 0; } break; } if (voxelCount <= 0) { return 0; } const int64_t componentCount = voxelCount * 4; /* * Clear the slice rgba coloring. */ for (int64_t i = 0; i < componentCount; i++) { rgbaOut[i] = 0; } const int64_t mapRgbaCount = m_mapContent[mapIndex]->m_rgba.size(); /* * RGBA size will be zero if no data has been loaded for a CIFTI * matrix type file (user clicking brainordinate). */ if (mapRgbaCount <= 0) { return 0; } const uint8_t* mapRGBA = &m_mapContent[mapIndex]->m_rgba[0]; CaretAssert(m_voxelIndicesToOffset); /* * Data values are only needed when a label volume * is being drawn so that we can determine if the * label is displayed. */ std::vector dataValues; const GiftiLabelTable* labelTable = (isMappedWithLabelTable() ? getMapLabelTable(mapIndex) : NULL); if (isMappedWithLabelTable()) { CaretAssert(labelTable); getMapData(mapIndex, dataValues); } /* * Note that step indices may be positive or negative */ const int64_t kStep = ((kEnd < kStart) ? -1 : 1); const int64_t jStep = ((jEnd < jStart) ? -1 : 1); const int64_t iStep = ((iEnd < iStart) ? -1 : 1); int64_t validVoxelCount = 0; /* * Set the rgba components for the slice. */ switch (slicePlane) { case VolumeSliceViewPlaneEnum::ALL: CaretAssert(0); break; case VolumeSliceViewPlaneEnum::AXIAL: { int64_t rgbaOffset = 0; int64_t j = jStart; bool jLoopFlag = true; while (jLoopFlag) { int64_t i = iStart; bool iLoopFlag = true; while (iLoopFlag) { const int64_t dataOffset = m_voxelIndicesToOffset->getOffsetForIndices(i, j, sliceIndex); if (dataOffset >= 0) { const int64_t dataOffset4 = dataOffset * 4; CaretAssert(dataOffset4 < mapRgbaCount); CaretAssert(rgbaOffset < componentCount); rgbaOut[rgbaOffset] = mapRGBA[dataOffset4]; rgbaOut[rgbaOffset+1] = mapRGBA[dataOffset4+1]; rgbaOut[rgbaOffset+2] = mapRGBA[dataOffset4+2]; /* * A negative value for alpha indicates "do not draw". * Since unsigned bytes do not have negative values, * change the value to zero (which indicates "transparent"). */ float alpha = mapRGBA[dataOffset4+3]; if (alpha < 0.0) { alpha = 0.0; } if (alpha > 0.0) { if (labelTable != NULL) { /* * For label data, verify that the label is displayed. * If NOT displayed, zero out the alpha value to * prevent display of the data. */ CaretAssertVectorIndex(dataValues, dataOffset); const int32_t dataValue = dataValues[dataOffset]; const GiftiLabel* label = labelTable->getLabel(dataValue); if (label != NULL) { const GroupAndNameHierarchyItem* item = label->getGroupNameSelectionItem(); CaretAssert(item); if (item->isSelected(displayGroup, tabIndex) == false) { alpha = 0.0; } } } } if (alpha > 0.0) { ++validVoxelCount; } rgbaOut[rgbaOffset+3] = (alpha * 255.0); } if (i == iEnd) { iLoopFlag = false; } else { i += iStep; } rgbaOffset += 4; } if (j == jEnd) { jLoopFlag = false; } else { j += jStep; } } } break; case VolumeSliceViewPlaneEnum::CORONAL: { int64_t rgbaOffset = 0; int64_t k = kStart; bool kLoopFlag = true; while (kLoopFlag) { int64_t i = iStart; bool iLoopFlag = true; while (iLoopFlag) { const int64_t dataOffset = m_voxelIndicesToOffset->getOffsetForIndices(i, sliceIndex, k); if (dataOffset >= 0) { const int64_t dataOffset4 = dataOffset * 4; CaretAssert(dataOffset4 < mapRgbaCount); CaretAssert(rgbaOffset < componentCount); rgbaOut[rgbaOffset] = mapRGBA[dataOffset4]; rgbaOut[rgbaOffset+1] = mapRGBA[dataOffset4+1]; rgbaOut[rgbaOffset+2] = mapRGBA[dataOffset4+2]; /* * A negative value for alpha indicates "do not draw". * Since unsigned bytes do not have negative values, * change the value to zero (which indicates "transparent"). */ float alpha = mapRGBA[dataOffset4+3]; if (alpha < 0.0) { alpha = 0.0; } if (alpha > 0.0) { if (labelTable != NULL) { /* * For label data, verify that the label is displayed. * If NOT displayed, zero out the alpha value to * prevent display of the data. */ CaretAssertVectorIndex(dataValues, dataOffset); const int32_t dataValue = dataValues[dataOffset]; const GiftiLabel* label = labelTable->getLabel(dataValue); if (label != NULL) { const GroupAndNameHierarchyItem* item = label->getGroupNameSelectionItem(); CaretAssert(item); if (item->isSelected(displayGroup, tabIndex) == false) { alpha = 0.0; } } } } if (alpha > 0.0) { ++validVoxelCount; } rgbaOut[rgbaOffset+3] = (alpha * 255.0); } if (i == iEnd) { iLoopFlag = false; } else { i += iStep; } rgbaOffset += 4; } if (k == kEnd) { kLoopFlag = false; } else { k += kStep; } } } break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: { int64_t rgbaOffset = 0; int64_t k = kStart; bool kLoopFlag = true; while (kLoopFlag) { int64_t j = jStart; bool jLoopFlag = true; while (jLoopFlag) { const int64_t dataOffset = m_voxelIndicesToOffset->getOffsetForIndices(sliceIndex, j, k); if (dataOffset >= 0) { const int64_t dataOffset4 = dataOffset * 4; CaretAssert(dataOffset4 < mapRgbaCount); CaretAssert(rgbaOffset < componentCount); rgbaOut[rgbaOffset] = mapRGBA[dataOffset4]; rgbaOut[rgbaOffset+1] = mapRGBA[dataOffset4+1]; rgbaOut[rgbaOffset+2] = mapRGBA[dataOffset4+2]; /* * A negative value for alpha indicates "do not draw". * Since unsigned bytes do not have negative values, * change the value to zero (which indicates "transparent"). */ float alpha = mapRGBA[dataOffset4+3]; if (alpha < 0.0) { alpha = 0.0; } if (alpha > 0.0) { if (labelTable != NULL) { /* * For label data, verify that the label is displayed. * If NOT displayed, zero out the alpha value to * prevent display of the data. */ CaretAssertVectorIndex(dataValues, dataOffset); const int32_t dataValue = dataValues[dataOffset]; const GiftiLabel* label = labelTable->getLabel(dataValue); if (label != NULL) { const GroupAndNameHierarchyItem* item = label->getGroupNameSelectionItem(); CaretAssert(item); if (item->isSelected(displayGroup, tabIndex) == false) { alpha = 0.0; } } } } if (alpha > 0.0) { ++validVoxelCount; } rgbaOut[rgbaOffset+3] = (alpha * 255.0); } if (j == jEnd) { jLoopFlag = false; } else { j += jStep; } rgbaOffset += 4; } if (k == kEnd) { kLoopFlag = false; } else { k += kStep; } } } for (int64_t k = kStart; k <= kEnd; k++) { for (int64_t j = jStart; j < jEnd; j++) { const int64_t dataOffset = m_voxelIndicesToOffset->getOffsetForIndices(sliceIndex, j, k); if (dataOffset >= 0) { const int64_t dataOffset4 = dataOffset * 4; CaretAssert(dataOffset4 < mapRgbaCount); const int64_t rgbaOffset = (((k - kStart) * voxelCountJ) + (j - jStart)) * 4; CaretAssert(rgbaOffset < componentCount); rgbaOut[rgbaOffset] = mapRGBA[dataOffset4]; rgbaOut[rgbaOffset+1] = mapRGBA[dataOffset4+1]; rgbaOut[rgbaOffset+2] = mapRGBA[dataOffset4+2]; /* * A negative value for alpha indicates "do not draw". * Since unsigned bytes do not have negative values, * change the value to zero (which indicates "transparent"). */ float alpha = mapRGBA[dataOffset4+3]; if (alpha < 0.0) { alpha = 0.0; } if (alpha > 0.0) { if (labelTable != NULL) { /* * For label data, verify that the label is displayed. * If NOT displayed, zero out the alpha value to * prevent display of the data. */ CaretAssertVectorIndex(dataValues, dataOffset); const int32_t dataValue = dataValues[dataOffset]; const GiftiLabel* label = labelTable->getLabel(dataValue); if (label != NULL) { const GroupAndNameHierarchyItem* item = label->getGroupNameSelectionItem(); CaretAssert(item); if (item->isSelected(displayGroup, tabIndex) == false) { alpha = 0.0; } } } } if (alpha > 0.0) { ++validVoxelCount; } rgbaOut[rgbaOffset+3] = (alpha * 255.0); } } } break; } return validVoxelCount; } /** * Get the voxel coloring for the voxel at the given indices. * This method is for label data. Accessing the actual voxel values is * needed for coloring labels. But, one can only access the entire set * of values for a map. Since this method is typically called many times * when coloring slices in ALL view, get the map data value before calling * this and then pass them in. * * This will work for non-label data. * * @param paletteFile * The palette file. * @param dataForMap * Data for the map. * @param indexIn1 * First dimension (i). * @param indexIn2 * Second dimension (j). * @param indexIn3 * Third dimension (k). * @param mapIndex * Time/map index. * @param displayGroup * The selected display group. * @param tabIndex * Index of selected tab. * @param rgbaOut * Output containing RGBA values for voxel at the given indices. */ void CiftiMappableDataFile::getVoxelColorInMapForLabelData(const PaletteFile* paletteFile, const std::vector& dataForMap, const int64_t indexIn1, const int64_t indexIn2, const int64_t indexIn3, const int64_t mapIndex, const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, uint8_t rgbaOut[4]) const { getVoxelColorInMap(paletteFile, indexIn1, indexIn2, indexIn3, mapIndex, displayGroup, tabIndex, rgbaOut); if (isMappedWithLabelTable()) { if (rgbaOut[3] > 0.0) { const GiftiLabelTable* labelTable = getMapLabelTable(mapIndex); CaretAssert(labelTable); const int64_t dataOffset = m_voxelIndicesToOffset->getOffsetForIndices(indexIn1, indexIn2, indexIn3); if (dataOffset >= 0) { /* * If the label is NOT selected for the given display * group and tab, inhibit its display by setting the * alpha component to zero. */ CaretAssertVectorIndex(dataForMap, dataOffset); const int32_t labelKey = dataForMap[dataOffset]; const GiftiLabel* label = labelTable->getLabel(labelKey); if (label != NULL) { const GroupAndNameHierarchyItem* item = label->getGroupNameSelectionItem(); if (item != NULL) { if (item->isSelected(displayGroup, tabIndex) == false) { rgbaOut[3] = 0.0; } } } } } } } /** * Get the voxel coloring for the voxel at the given indices. * * @see getVoxelColorInMapForLabelData * * @param paletteFile * The palette file. * @param indexIn1 * First dimension (i). * @param indexIn2 * Second dimension (j). * @param indexIn3 * Third dimension (k). * @param mapIndex * Time/map index. * @param displayGroup * The selected display group. * @param tabIndex * Index of selected tab. * @param rgbaOut * Output containing RGBA values for voxel at the given indices. */ void CiftiMappableDataFile::getVoxelColorInMap(const PaletteFile* paletteFile, const int64_t indexIn1, const int64_t indexIn2, const int64_t indexIn3, const int64_t mapIndex, const DisplayGroupEnum::Enum /*displayGroup*/, const int32_t /*tabIndex*/, uint8_t rgbaOut[4]) const { rgbaOut[0] = 0; rgbaOut[1] = 0; rgbaOut[2] = 0; rgbaOut[3] = 0; if ( ! isMapColoringValid(mapIndex)) { CiftiMappableDataFile* nonConstThis = const_cast(this); nonConstThis->updateScalarColoringForMap(mapIndex, paletteFile); } CaretAssert(m_voxelIndicesToOffset); const int64_t mapRgbaCount = m_mapContent[mapIndex]->m_rgba.size(); if (mapRgbaCount <= 0) { return; } const uint8_t* mapRGBA = &m_mapContent[mapIndex]->m_rgba[0]; const int64_t dataOffset = m_voxelIndicesToOffset->getOffsetForIndices(indexIn1, indexIn2, indexIn3); if (dataOffset >= 0) { const int64_t dataOffset4 = dataOffset * 4; CaretAssert(dataOffset4 < mapRgbaCount); rgbaOut[0] = mapRGBA[dataOffset4]; rgbaOut[1] = mapRGBA[dataOffset4+1]; rgbaOut[2] = mapRGBA[dataOffset4+2]; rgbaOut[3] = mapRGBA[dataOffset4+3]; } } /** * Get the brainordinate from the given row. * * @param rowIndex * Index of the row. * @param surfaceStructureOut * Will contain structure of surface if row is a surface node. * @param surfaceNodeIndexOut * Will contain index of surface node if row is a surface node. * @param surfaceNumberOfNodesOut * Will contain surfaces number of nodes if row is a surface node. * @param surfaceNodeValidOut * Will be true upon exit if the row corresponded to a surface node. * @param voxelIJKOut * Will contain the voxel's IJK indices if row is a surface node. * @param voxelXYZOut * Will contain the voxel's XYZ coordinate if row is a surface node. * @param voxelValidOut * Will be true upon exit if the row corresponded to a surface node. * @throw DataFileException * If the rows are not for brainordinates or the row index is invalid. */ void CiftiMappableDataFile::getBrainordinateFromRowIndex(const int64_t rowIndex, StructureEnum::Enum& surfaceStructureOut, int32_t& surfaceNodeIndexOut, int32_t& surfaceNumberOfNodesOut, bool& surfaceNodeValidOut, int64_t voxelIJKOut[3], float voxelXYZOut[3], bool& voxelValidOut) const { surfaceNodeValidOut = false; voxelValidOut = false; if (m_ciftiFile == NULL) { return; } const CiftiXML& ciftiXML = m_ciftiFile->getCiftiXML(); if (ciftiXML.getMappingType(CiftiXML::ALONG_COLUMN) != CiftiMappingType::BRAIN_MODELS) { throw DataFileException(getFileName(), "File does not have brainordinate data for rows."); return; } const CiftiBrainModelsMap& brainMap = ciftiXML.getBrainModelsMap(CiftiXML::ALONG_COLUMN); if ((rowIndex < 0) || (rowIndex >= m_ciftiFile->getNumberOfRows())) { throw DataFileException(getFileName(), "Row index " + AString::number(rowIndex) + " is out of range [0, " + AString::number(m_ciftiFile->getNumberOfRows() - 1) + "]"); } const CiftiBrainModelsMap::IndexInfo indexInfo = brainMap.getInfoForIndex(rowIndex); switch (indexInfo.m_type) { case CiftiBrainModelsMap::SURFACE: surfaceStructureOut = indexInfo.m_structure; surfaceNodeIndexOut = indexInfo.m_surfaceNode; surfaceNumberOfNodesOut = brainMap.getSurfaceNumberOfNodes(surfaceStructureOut); surfaceNodeValidOut = true; break; case CiftiBrainModelsMap::VOXELS: voxelIJKOut[0] = indexInfo.m_ijk[0]; voxelIJKOut[1] = indexInfo.m_ijk[1]; voxelIJKOut[2] = indexInfo.m_ijk[2]; indexToSpace(voxelIJKOut, voxelXYZOut); voxelValidOut = true; break; } } /** * Get the unique label keys in the given map. * @param mapIndex * Index of the map. * @return * Keys used by the map. */ std::vector CiftiMappableDataFile::getUniqueLabelKeysUsedInMap(const int32_t mapIndex) const { CaretAssertVectorIndex(m_mapContent, mapIndex); std::vector data; getMapData(mapIndex, data); std::set uniqueKeys; const int64_t numItems = static_cast(data.size()); if (numItems > 0) { const float* dataPtr = &data[0]; for (int64_t i = 0; i < numItems; i++) { const int32_t key = static_cast(dataPtr[i]); uniqueKeys.insert(key); } } std::vector keyVector; keyVector.insert(keyVector.end(), uniqueKeys.begin(), uniqueKeys.end()); return keyVector; } /** * @return The class and name hierarchy. */ GroupAndNameHierarchyModel* CiftiMappableDataFile::getGroupAndNameHierarchyModel() { CaretAssert(m_classNameHierarchy); m_classNameHierarchy->update(this, m_forceUpdateOfGroupAndNameHierarchy); m_forceUpdateOfGroupAndNameHierarchy = false; return m_classNameHierarchy; } /** * @return The class and name hierarchy. */ const GroupAndNameHierarchyModel* CiftiMappableDataFile::getGroupAndNameHierarchyModel() const { CaretAssert(m_classNameHierarchy); m_classNameHierarchy->update(const_cast(this), m_forceUpdateOfGroupAndNameHierarchy); m_forceUpdateOfGroupAndNameHierarchy = false; return m_classNameHierarchy; } /** * Validate keys and labels in the file. */ void CiftiMappableDataFile::validateKeysAndLabels() const { /* * Skip if not label file */ if (isMappedWithLabelTable() == false) { return; } /* * Skip if logging is not fine or less. */ if (CaretLogger::getLogger()->isFine() == false) { return; } AString messages; /* * Find the label keys that are in the data */ std::set dataKeys; const int32_t numMaps = getNumberOfMaps(); for (int32_t jMap = 0; jMap < numMaps; jMap++) { AString mapMessage; std::vector data; getMapData(jMap, data); const int64_t numItems = static_cast(data.size()); for (int32_t i = 0; i < numItems; i++) { const int32_t key = static_cast(data[i]); dataKeys.insert(key); } /* * Find any keys that are not in the label table */ const GiftiLabelTable* labelTable = getMapLabelTable(jMap); std::set missingLabelKeys; for (std::set::iterator dataKeyIter = dataKeys.begin(); dataKeyIter != dataKeys.end(); dataKeyIter++) { const int32_t dataKey = *dataKeyIter; const GiftiLabel* label = labelTable->getLabel(dataKey); if (label == NULL) { missingLabelKeys.insert(dataKey); } } if (missingLabelKeys.empty() == false) { for (std::set::iterator missingKeyIter = missingLabelKeys.begin(); missingKeyIter != missingLabelKeys.end(); missingKeyIter++) { const int32_t missingKey = *missingKeyIter; mapMessage.appendWithNewLine(" Missing Label for Key: " + AString::number(missingKey)); } } /* * Find any label table names that are not used */ std::map labelTableKeysAndNames; labelTable->getKeysAndNames(labelTableKeysAndNames); for (std::map::const_iterator ltIter = labelTableKeysAndNames.begin(); ltIter != labelTableKeysAndNames.end(); ltIter++) { const int32_t ltKey = ltIter->first; if (std::find(dataKeys.begin(), dataKeys.end(), ltKey) == dataKeys.end()) { mapMessage.appendWithNewLine(" Label Not Used Key=" + AString::number(ltKey) + ": " + ltIter->second); } } if (mapMessage.isEmpty() == false) { mapMessage = (" Map: " + getMapName(jMap) + ":\n" + mapMessage + "\n" + labelTable->toFormattedString(" ")); messages += mapMessage; } } AString msg = ("File: " + getFileName() + "\n" + messages); CaretLogFine(msg); } /** * Get connectivity value for a surface's node. When the data is mapped * to parcels, the numerical value will not be valid. * * @param mapIndex * Index of the map. * @param structure * Surface's structure. * @param nodeIndex * Index of the node * @param numberOfNodes * Number of nodes in the surface. * @param numericalValueOut * Numerical value out. * @param numericalValueOutValid * Output that indicates the numerical value output is valid. * For label data, this value will be the lable key. * @param textValueOut * Text containing node' value will always be valid if the method * returns true. For parcel data, this will contain the name of the * parcel. For label data, this will contain the name of the label. * For numerical data, this will contain the text representation * of the numerical value. * @return * True if the text value is valid. The numerical value may or may not * also be valid. */ bool CiftiMappableDataFile::getMapSurfaceNodeValue(const int32_t mapIndex, const StructureEnum::Enum structure, const int nodeIndex, const int32_t numberOfNodes, float& numericalValueOut, bool& numericalValueOutValid, AString& textValueOut) const { numericalValueOut = 0.0; numericalValueOutValid = false; CaretAssertVectorIndex(m_mapContent, mapIndex); CaretAssert(m_ciftiFile); const CiftiXML& ciftiXML = m_ciftiFile->getCiftiXML(); CaretAssert((m_dataMappingDirectionForCiftiXML == CiftiXML::ALONG_ROW) || (m_dataMappingDirectionForCiftiXML == CiftiXML::ALONG_COLUMN)); switch (ciftiXML.getMappingType(m_dataMappingDirectionForCiftiXML)) { case CiftiMappingType::BRAIN_MODELS: { const CiftiBrainModelsMap& map = ciftiXML.getBrainModelsMap(m_dataMappingDirectionForCiftiXML); if (map.getSurfaceNumberOfNodes(structure) == numberOfNodes) { const int64_t dataIndex = map.getIndexForNode(nodeIndex, structure); if (dataIndex >= 0) { std::vector mapData; getMapData(mapIndex, mapData); if (dataIndex < static_cast(mapData.size())) { numericalValueOut = mapData[dataIndex]; numericalValueOutValid = true; if (ciftiXML.getMappingType(m_dataReadingDirectionForCiftiXML) == CiftiMappingType::LABELS) { const GiftiLabelTable* glt = getMapLabelTable(mapIndex); const int32_t labelKey = static_cast(numericalValueOut); const GiftiLabel* gl = glt->getLabel(labelKey); if (gl != NULL) { textValueOut += gl->getName(); } else { textValueOut += ("InvalidLabelKey=" + AString::number(labelKey)); } } else { textValueOut = AString::number(numericalValueOut, 'f'); } return true; } } } } break; case CiftiMappingType::LABELS: CaretAssertMessage(0, "Mapping type should never be LABELS"); break; case CiftiMappingType::PARCELS: { int64_t parcelIndex = -1; const CiftiParcelsMap& map = ciftiXML.getParcelsMap(m_dataMappingDirectionForCiftiXML); if (map.getSurfaceNumberOfNodes(structure) == numberOfNodes) { const std::vector& parcels = map.getParcels(); parcelIndex = map.getIndexForNode(nodeIndex, structure); if ((parcelIndex >= 0) && (parcelIndex < static_cast(parcels.size()))) { textValueOut = parcels[parcelIndex].m_name; std::vector mapData; getMapData(mapIndex, mapData); // if (parcelIndex < static_cast(mapData.size())) { // textValueOut += (" " // + AString::number(mapData[parcelIndex])); // } } } if (parcelIndex >= 0) { int64_t itemIndex = -1; switch (ciftiXML.getMappingType(m_dataReadingDirectionForCiftiXML)) { case CiftiMappingType::BRAIN_MODELS: { const CiftiBrainModelsMap& map = ciftiXML.getBrainModelsMap(m_dataReadingDirectionForCiftiXML); if (map.getSurfaceNumberOfNodes(structure) == numberOfNodes) { itemIndex = map.getIndexForNode(nodeIndex, structure); } } break; case CiftiMappingType::LABELS: break; case CiftiMappingType::PARCELS: itemIndex = mapIndex; break; case CiftiMappingType::SCALARS: itemIndex = mapIndex; break; case CiftiMappingType::SERIES: itemIndex = mapIndex; break; } if (itemIndex >= 0) { const int64_t numRows = m_ciftiFile->getNumberOfRows(); const int64_t numCols = m_ciftiFile->getNumberOfColumns(); switch (m_dataReadingDirectionForCiftiXML) { case CiftiXML::ALONG_COLUMN: { std::vector data; data.resize(numRows); CaretAssert(parcelIndex < numCols); m_ciftiFile->getColumn(&data[0], parcelIndex); CaretAssertVectorIndex(data, itemIndex); textValueOut += (" " + AString::number(data[itemIndex])); } break; case CiftiXML::ALONG_ROW: { std::vector data; data.resize(numCols); CaretAssert(parcelIndex < numRows); m_ciftiFile->getRow(&data[0], parcelIndex); CaretAssertVectorIndex(data, itemIndex); textValueOut += (" " + AString::number(data[itemIndex])); } break; } } } } return true; break; case CiftiMappingType::SCALARS: CaretAssertMessage(0, "Mapping type should never be SCALARS"); break; case CiftiMappingType::SERIES: CaretAssertMessage(0, "Mapping type should never be SERIES"); break; } return false; } /** * Get connectivity value for a surface's node. When the data is mapped * to parcels, the numerical value will not be valid. * * @param mapIndices * Index of the map. * @param structure * Surface's structure. * @param nodeIndex * Index of the node * @param numberOfNodes * Number of nodes in the surface. * @param numericalValuesOut * Numerical values out for all map indices * @param numericalValuesOutValid * Output that indicates the numerical value output is valid. * For label data, this value will be the lable key. * @param textValueOut * Text containing node' value will always be valid if the method * returns true. For parcel data, this will contain the name of the * parcel. For label data, this will contain the name of the label. * For numerical data, this will contain the text representation * of the numerical value. * @return * True if the text value is valid. The numerical values may or may not * also be valid. */ bool CiftiMappableDataFile::getMapSurfaceNodeValues(const std::vector& mapIndices, const StructureEnum::Enum structure, const int nodeIndex, const int32_t numberOfNodes, std::vector& numericalValuesOut, std::vector& numericalValuesOutValid, AString& textValueOut) const { numericalValuesOut.clear(); numericalValuesOutValid.clear(); CaretAssert(m_ciftiFile); const CiftiXML& ciftiXML = m_ciftiFile->getCiftiXML(); CaretAssert((m_dataMappingDirectionForCiftiXML == CiftiXML::ALONG_ROW) || (m_dataMappingDirectionForCiftiXML == CiftiXML::ALONG_COLUMN)); switch (ciftiXML.getMappingType(m_dataMappingDirectionForCiftiXML)) { case CiftiMappingType::BRAIN_MODELS: { const CiftiBrainModelsMap& map = ciftiXML.getBrainModelsMap(m_dataMappingDirectionForCiftiXML); if (map.getSurfaceNumberOfNodes(structure) == numberOfNodes) { const int64_t dataIndex = map.getIndexForNode(nodeIndex, structure); if (dataIndex >= 0) { for (std::vector::const_iterator mapIter = mapIndices.begin(); mapIter != mapIndices.end(); mapIter++) { const int32_t mapIndex = *mapIter; std::vector mapData; CaretAssertVectorIndex(m_mapContent, mapIndex); getMapData(mapIndex, mapData); if (dataIndex < static_cast(mapData.size())) { const float value = mapData[dataIndex]; numericalValuesOut.push_back(mapData[dataIndex]); numericalValuesOutValid.push_back(true); if (ciftiXML.getMappingType(m_dataReadingDirectionForCiftiXML) == CiftiMappingType::LABELS) { const GiftiLabelTable* glt = getMapLabelTable(mapIndex); const int32_t labelKey = static_cast(value); const GiftiLabel* gl = glt->getLabel(labelKey); if (gl != NULL) { textValueOut += gl->getName(); } else { textValueOut += ("InvalidLabelKey=" + AString::number(labelKey)); } } else { textValueOut = AString::number(value, 'f'); } } } return true; } } } break; case CiftiMappingType::LABELS: CaretAssertMessage(0, "Mapping type should never be LABELS"); break; case CiftiMappingType::PARCELS: { int64_t parcelIndex = -1; const CiftiParcelsMap& map = ciftiXML.getParcelsMap(m_dataMappingDirectionForCiftiXML); if (map.getSurfaceNumberOfNodes(structure) == numberOfNodes) { const std::vector& parcels = map.getParcels(); parcelIndex = map.getIndexForNode(nodeIndex, structure); if ((parcelIndex >= 0) && (parcelIndex < static_cast(parcels.size()))) { textValueOut = parcels[parcelIndex].m_name; } } for (std::vector::const_iterator mapIter = mapIndices.begin(); mapIter != mapIndices.end(); mapIter++) { const int32_t mapIndex = *mapIter; if (parcelIndex >= 0) { int64_t itemIndex = -1; switch (ciftiXML.getMappingType(m_dataReadingDirectionForCiftiXML)) { case CiftiMappingType::BRAIN_MODELS: { const CiftiBrainModelsMap& map = ciftiXML.getBrainModelsMap(m_dataReadingDirectionForCiftiXML); if (map.getSurfaceNumberOfNodes(structure) == numberOfNodes) { itemIndex = map.getIndexForNode(nodeIndex, structure); } } break; case CiftiMappingType::LABELS: break; case CiftiMappingType::PARCELS: itemIndex = mapIndex; break; case CiftiMappingType::SCALARS: itemIndex = mapIndex; break; case CiftiMappingType::SERIES: itemIndex = mapIndex; break; } if (itemIndex >= 0) { const int64_t numRows = m_ciftiFile->getNumberOfRows(); const int64_t numCols = m_ciftiFile->getNumberOfColumns(); switch (m_dataReadingDirectionForCiftiXML) { case CiftiXML::ALONG_COLUMN: { std::vector data; data.resize(numRows); CaretAssert(parcelIndex < numCols); m_ciftiFile->getColumn(&data[0], parcelIndex); CaretAssertVectorIndex(data, itemIndex); textValueOut += (" " + AString::number(data[itemIndex])); } break; case CiftiXML::ALONG_ROW: { std::vector data; data.resize(numCols); CaretAssert(parcelIndex < numRows); m_ciftiFile->getRow(&data[0], parcelIndex); CaretAssertVectorIndex(data, itemIndex); textValueOut += (" " + AString::number(data[itemIndex])); } break; } } } } } return true; break; case CiftiMappingType::SCALARS: CaretAssertMessage(0, "Mapping type should never be SCALARS"); break; case CiftiMappingType::SERIES: CaretAssertMessage(0, "Mapping type should never be SERIES"); break; } return false; } /** * Get Parcel Label File value for a surface's node. When the data is mapped * to parcels, the numerical value will not be valid. * * @param mapIndex * Index of the map. * @param structure * Surface's structure. * @param nodeIndex * Index of the node * @param numberOfNodes * Number of nodes in the surface. * @param numericalValueOut * Numerical value out. * @param numericalValueOutValid * Output that indicates the numerical value output is valid. * For label data, this value will be the lable key. * @param textValueOut * Text containing node' value will always be valid if the method * returns true. For parcel data, this will contain the name of the * parcel. For label data, this will contain the name of the label. * For numerical data, this will contain the text representation * of the numerical value. * @return * True if the text value is valid. The numerical value may or may not * also be valid. */ bool CiftiMappableDataFile::getParcelLabelMapSurfaceNodeValue(const int32_t mapIndex, const StructureEnum::Enum structure, const int nodeIndex, const int32_t numberOfNodes, float& numericalValueOut, bool& numericalValueOutValid, AString& textValueOut) const { numericalValueOut = 0.0; numericalValueOutValid = false; textValueOut = ""; CaretAssertVectorIndex(m_mapContent, mapIndex); CaretAssert(m_ciftiFile); const CiftiXML& ciftiXML = m_ciftiFile->getCiftiXML(); AString areaName = ""; CaretAssert((m_dataMappingDirectionForCiftiXML == CiftiXML::ALONG_ROW) || (m_dataMappingDirectionForCiftiXML == CiftiXML::ALONG_COLUMN)); const CiftiParcelsMap& map = ciftiXML.getParcelsMap(m_dataMappingDirectionForCiftiXML); if (map.getSurfaceNumberOfNodes(structure) == numberOfNodes) { const std::vector& parcels = map.getParcels(); const int64_t parcelIndex = map.getIndexForNode(nodeIndex, structure); if ((parcelIndex >= 0) && (parcelIndex < static_cast(parcels.size()))) { areaName = parcels[parcelIndex].m_name; const AString networkName = getMapName(mapIndex); textValueOut = ("Area: " + areaName + ", Network: " + networkName); return true; } } return false; } /** * Get the identification information for a surface node in the given maps. * * @param mapIndices * Indices of maps for which identification information is requested. * @param structure * Structure of the surface. * @param nodeIndex * Index of the node. * @param numberOfNodes * Number of nodes in the surface. * @param textOut * Output containing identification information. */ bool CiftiMappableDataFile::getSurfaceNodeIdentificationForMaps(const std::vector& mapIndices, const StructureEnum::Enum structure, const int nodeIndex, const int32_t numberOfNodes, AString& textOut) const { CaretAssert(m_ciftiFile); if (mapIndices.empty()) { return false; } bool useMapData = false; bool useParcelLabelMapData = false; bool useSeriesData = false; switch (getDataFileType()) { case DataFileTypeEnum::BORDER: CaretAssert(0); break; case DataFileTypeEnum::CONNECTIVITY_DENSE: useMapData = true; break; case DataFileTypeEnum::CONNECTIVITY_DENSE_LABEL: useSeriesData = true; break; case DataFileTypeEnum::CONNECTIVITY_DENSE_PARCEL: useMapData = true; break; case DataFileTypeEnum::CONNECTIVITY_DENSE_SCALAR: useSeriesData = true; break; case DataFileTypeEnum::CONNECTIVITY_DENSE_TIME_SERIES: useSeriesData = true; break; case DataFileTypeEnum::CONNECTIVITY_FIBER_ORIENTATIONS_TEMPORARY: CaretAssert(0); break; case DataFileTypeEnum::CONNECTIVITY_FIBER_TRAJECTORY_TEMPORARY: CaretAssert(0); break; case DataFileTypeEnum::CONNECTIVITY_PARCEL: useMapData = true; break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_DENSE: useMapData = true; break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_LABEL: useParcelLabelMapData = true; break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_SCALAR: useMapData = true; break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_SERIES: useMapData = true; break; case DataFileTypeEnum::CONNECTIVITY_SCALAR_DATA_SERIES: break; case DataFileTypeEnum::FOCI: CaretAssert(0); break; case DataFileTypeEnum::IMAGE: CaretAssert(0); break; case DataFileTypeEnum::LABEL: CaretAssert(0); break; case DataFileTypeEnum::METRIC: CaretAssert(0); break; case DataFileTypeEnum::PALETTE: CaretAssert(0); break; case DataFileTypeEnum::RGBA: CaretAssert(0); break; case DataFileTypeEnum::SCENE: CaretAssert(0); break; case DataFileTypeEnum::SPECIFICATION: CaretAssert(0); break; case DataFileTypeEnum::SURFACE: CaretAssert(0); break; case DataFileTypeEnum::UNKNOWN: CaretAssert(0); break; case DataFileTypeEnum::VOLUME: CaretAssert(0); break; } const int32_t numberOfMapIndices = static_cast(mapIndices.size()); textOut = ""; bool validID = false; if (useMapData) { std::vector numericalValues; std::vector numericalValuesValid; AString textValue; if (getMapSurfaceNodeValues(mapIndices, structure, nodeIndex, numberOfNodes, numericalValues, numericalValuesValid, textValue)) { textOut += textValue; textOut += " "; validID = true; } // for (int32_t i = 0; i < numberOfMapIndices; i++) { // const int32_t mapIndex = mapIndices[i]; // // float numericalValue; // AString textValue; // bool numericalValueValid; // if (getMapSurfaceNodeValue(mapIndex, // structure, // nodeIndex, // numberOfNodes, // numericalValue, // numericalValueValid, // textValue)) { // textOut += textValue; // textOut += " "; // validID = true; // } // } } else if (useSeriesData) { /* * Use series data which contains values for node from all maps. */ std::vector seriesData; if (getSeriesDataForSurfaceNode(structure, nodeIndex, seriesData)) { for (int32_t i = 0; i < numberOfMapIndices; i++) { const int32_t mapIndex = mapIndices[i]; CaretAssertVectorIndex(seriesData, mapIndex); const float value = seriesData[mapIndex]; if (isMappedWithLabelTable()) { const GiftiLabelTable* glt = getMapLabelTable(mapIndex); const int32_t labelKey = static_cast(value); const GiftiLabel* gl = glt->getLabel(labelKey); if (gl != NULL) { textOut += gl->getName(); } else { textOut += ("InvalidLabelKey=" + AString::number(value)); } validID = true; } else if (isMappedWithPalette()) { textOut += AString::number(value); validID = true; } else { CaretAssert(0); } textOut += " "; } } } else if (useParcelLabelMapData) { for (int32_t i = 0; i < numberOfMapIndices; i++) { const int32_t mapIndex = mapIndices[i]; float numericalValue; AString textValue; bool numericalValueValid; if (getParcelLabelMapSurfaceNodeValue(mapIndex, structure, nodeIndex, numberOfNodes, numericalValue, numericalValueValid, textValue)) { textOut += textValue; textOut += " "; validID = true; } } } return validID; } /** * Get the series data (one data value from each map) for a surface node. * * @param structure * Surface's structure. * @param nodeIndex * Index of the node. * @param seriesDataOut * Series data for given node. * @return * True if output data is valid, else false. */ bool CiftiMappableDataFile::getSeriesDataForSurfaceNode(const StructureEnum::Enum structure, const int32_t nodeIndex, std::vector& seriesDataOut) const { CaretAssert(m_ciftiFile); bool valid = false; switch (m_dataMappingAccessMethod) { case DATA_ACCESS_METHOD_INVALID: CaretAssert(0); break; case DATA_ACCESS_NONE: break; case DATA_ACCESS_FILE_COLUMNS_OR_XML_ALONG_ROW: seriesDataOut.resize(m_ciftiFile->getNumberOfRows()); valid = m_ciftiFile->getColumnFromNode(&seriesDataOut[0], nodeIndex, structure); break; case DATA_ACCESS_FILE_ROWS_OR_XML_ALONG_COLUMN: seriesDataOut.resize(m_ciftiFile->getNumberOfColumns()); valid = m_ciftiFile->getRowFromNode(&seriesDataOut[0], nodeIndex, structure); break; } return valid; } /** * Get the series data (oone data value for each map) for a voxel at the * given coordinate. * * @param xyz * Coordinate of the voxel. * @param seriesDataOut * Series data for the given voxel. * @return * True if output data is valid, else false. */ bool CiftiMappableDataFile::getSeriesDataForVoxelAtCoordinate(const float xyz[3], std::vector& seriesDataOut) const { CaretAssert(m_ciftiFile); bool valid = false; switch (m_dataMappingAccessMethod) { case DATA_ACCESS_METHOD_INVALID: CaretAssert(0); break; case DATA_ACCESS_NONE: break; case DATA_ACCESS_FILE_COLUMNS_OR_XML_ALONG_ROW: seriesDataOut.resize(m_ciftiFile->getNumberOfRows()); valid = m_ciftiFile->getColumnFromVoxelCoordinate(&seriesDataOut[0], xyz); break; case DATA_ACCESS_FILE_ROWS_OR_XML_ALONG_COLUMN: seriesDataOut.resize(m_ciftiFile->getNumberOfColumns()); valid = m_ciftiFile->getRowFromVoxelCoordinate(&seriesDataOut[0], xyz); break; } return valid; } /** * Get the node coloring for the surface. * @param surface * Surface whose nodes are colored. * @param surfaceRGBAOut * Filled with RGBA coloring for the surface's nodes. * Contains numberOfNodes * 4 elements. * @param dataValuesOut * Data values for the nodes (elements are valid when the alpha value in * the RGBA colors is valid (greater than zero). * @param surfaceNumberOfNodes * Number of nodes in the surface. * @return * True if coloring is valid, else false. */ bool CiftiMappableDataFile::getMapSurfaceNodeColoring(const PaletteFile* paletteFile, const int32_t mapIndex, const StructureEnum::Enum structure, float* surfaceRGBAOut, float* dataValuesOut, const int32_t surfaceNumberOfNodes) { CaretAssert(m_ciftiFile); CaretAssertVectorIndex(m_mapContent, mapIndex); const int32_t numCiftiNodes = getMappingSurfaceNumberOfNodes(structure); if (numCiftiNodes != surfaceNumberOfNodes) { return false; } std::vector mapData; getMapData(mapIndex, mapData); /* * Map data may be empty for connectivity matrix files with no rows loaded. */ if (mapData.empty()) { return false; } std::vector surfaceMap; switch (m_dataMappingAccessMethod) { case DATA_ACCESS_METHOD_INVALID: CaretAssert(0); break; case DATA_ACCESS_NONE: break; case DATA_ACCESS_FILE_COLUMNS_OR_XML_ALONG_ROW: m_ciftiFile->getSurfaceMapForRows(surfaceMap, structure); break; case DATA_ACCESS_FILE_ROWS_OR_XML_ALONG_COLUMN: m_ciftiFile->getSurfaceMapForColumns(surfaceMap, structure); break; } const MapContent* mc = m_mapContent[mapIndex]; /* * May need to update map coloring */ if ( ! mc->m_rgbaValid) { updateScalarColoringForMap(mapIndex, paletteFile); } std::vector dataIndicesForNodes; bool validColorsFlag = false; if (getSurfaceDataIndicesForMappingToBrainordinates(structure, surfaceNumberOfNodes, dataIndicesForNodes)) { for (int64_t iNode = 0; iNode < surfaceNumberOfNodes; iNode++) { CaretAssertVectorIndex(dataIndicesForNodes, iNode); const int64_t dataIndex = dataIndicesForNodes[iNode]; const int64_t node4 = iNode * 4; CaretAssertArrayIndex(surfaceRGBA, (surfaceNumberOfNodes * 4), node4); if (dataIndex >= 0) { CaretAssert(dataIndex < mc->m_dataCount); const int64_t data4 = dataIndex * 4; CaretAssertArrayIndex(this->dataRGBA, (mc->m_dataCount * 4), data4); surfaceRGBAOut[node4] = mc->m_rgba[data4] / 255.0; surfaceRGBAOut[node4+1] = mc->m_rgba[data4+1] / 255.0; surfaceRGBAOut[node4+2] = mc->m_rgba[data4+2] / 255.0; surfaceRGBAOut[node4+3] = mc->m_rgba[data4+3] / 255.0; dataValuesOut[iNode] = mapData[dataIndex]; validColorsFlag = true; } else { surfaceRGBAOut[node4] = 0.0; surfaceRGBAOut[node4+1] = 0.0; surfaceRGBAOut[node4+2] = 0.0; surfaceRGBAOut[node4+3] = -1.0; dataValuesOut[iNode] = 0.0; } } } return validColorsFlag; } /** * Get the data indices corresponding to all nodes in the given surface. * * @param structure * Surface's structure. * @param surfaceNumberOfNodes * Number of nodes in the surface. * @param dataIndicesForNodes * Will containg "surfaceNumberOfNodes" element where the values are * indices into the CIFTI data. * @return True if valid, else false. */ bool CiftiMappableDataFile::getSurfaceDataIndicesForMappingToBrainordinates(const StructureEnum::Enum structure, const int64_t surfaceNumberOfNodes, std::vector& dataIndicesForNodes) const { CaretAssert(m_ciftiFile); const CiftiXML& ciftiXML = m_ciftiFile->getCiftiXML(); CaretAssert((m_dataMappingDirectionForCiftiXML == CiftiXML::ALONG_ROW) || (m_dataMappingDirectionForCiftiXML == CiftiXML::ALONG_COLUMN)); switch (ciftiXML.getMappingType(m_dataMappingDirectionForCiftiXML)) { case CiftiMappingType::BRAIN_MODELS: { const CiftiBrainModelsMap& map = ciftiXML.getBrainModelsMap(m_dataMappingDirectionForCiftiXML); if (map.getSurfaceNumberOfNodes(structure) == surfaceNumberOfNodes) { const std::vector surfaceMap = map.getSurfaceMap(structure); dataIndicesForNodes.resize(surfaceNumberOfNodes); std::fill(dataIndicesForNodes.begin(), dataIndicesForNodes.end(), -1); for (std::vector::const_iterator iter = surfaceMap.begin(); iter != surfaceMap.end(); iter++) { const CiftiBrainModelsMap::SurfaceMap& nodeMap = *iter; CaretAssertVectorIndex(dataIndicesForNodes, nodeMap.m_surfaceNode); dataIndicesForNodes[nodeMap.m_surfaceNode] = nodeMap.m_ciftiIndex; } return true; } } break; case CiftiMappingType::LABELS: CaretAssertMessage(0, "Mapping type should never be LABELS"); break; case CiftiMappingType::PARCELS: { const CiftiParcelsMap& map = ciftiXML.getParcelsMap(m_dataMappingDirectionForCiftiXML); if (map.getSurfaceNumberOfNodes(structure) == surfaceNumberOfNodes) { dataIndicesForNodes.resize(surfaceNumberOfNodes); for (int64_t i = 0; i < surfaceNumberOfNodes; i++) { dataIndicesForNodes[i] = map.getIndexForNode(i, structure); } return true; } } break; case CiftiMappingType::SCALARS: CaretAssertMessage(0, "Mapping type should never be SCALARS"); break; case CiftiMappingType::SERIES: CaretAssertMessage(0, "Mapping type should never be SERIES"); break; } return false; } /** * Get connectivity value for a voxel. When the data is mapped * to parcels, the numerical value will not be valid. * * @param mapIndex * Index of the map. * @param xyz * Coordinate of voxel. * @param ijkOut * Voxel indices of value. * @param numericalValueOut * Numerical value out. * @param numericalValueOutValid * Output that indicates the numerical value output is valid. * For label data, this value will be the label key. * @param textValueOut * Text containing node' value will always be valid if the method * returns true. For parcel data, this will contain the name of the * parcel. For label data, this will contain the name of the label. * For numerical data, this will contain the text representation * of the numerical value. * @return * True if the text value is valid. The numerical value may or may not * also be valid. */ bool CiftiMappableDataFile::getMapVolumeVoxelValue(const int32_t mapIndex, const float xyz[3], int64_t ijkOut[3], float& numericalValueOut, bool& numericalValueOutValid, AString& textValueOut) const { textValueOut = ""; numericalValueOutValid = false; CaretAssert(m_ciftiFile); /* * Get content for map. */ CaretAssertVectorIndex(m_mapContent, mapIndex); int64_t ijk[3]; enclosingVoxel(xyz[0], xyz[1], xyz[2], ijk[0], ijk[1], ijk[2]); if (indexValid(ijk[0], ijk[1], ijk[2])) { /* * Only set the IJK if the index is valid. */ ijkOut[0] = ijk[0]; ijkOut[1] = ijk[1]; ijkOut[2] = ijk[2]; const int64_t dataOffset = m_voxelIndicesToOffset->getOffsetForIndices(ijk[0], ijk[1], ijk[2]); if (dataOffset >= 0) { CaretAssert((m_dataMappingDirectionForCiftiXML == CiftiXML::ALONG_ROW) || (m_dataMappingDirectionForCiftiXML == CiftiXML::ALONG_COLUMN)); const CiftiXML& ciftiXML = m_ciftiFile->getCiftiXML(); const CiftiParcelLabelFile* parcelLabelFile = dynamic_cast(this); if (parcelLabelFile != NULL) { const CiftiParcelsMap& map = ciftiXML.getParcelsMap(m_dataMappingDirectionForCiftiXML); const int64_t parcelMapIndex = map.getIndexForVoxel(ijk); const std::vector& parcels = map.getParcels(); AString areaName = ""; if ((parcelMapIndex >= 0) && (parcelMapIndex < static_cast(parcels.size()))) { CaretAssertVectorIndex(parcels, parcelMapIndex); areaName = parcels[parcelMapIndex].m_name; } const AString networkName = getMapName(mapIndex); textValueOut = ("Area: " + areaName + ", Network: " + networkName); return true; } else { switch (ciftiXML.getMappingType(m_dataMappingDirectionForCiftiXML)) { case CiftiMappingType::BRAIN_MODELS: { /* * Note: For a Dense connectivity file, it may not have * data loaded since data is loaded upon demand. */ std::vector mapData; getMapData(mapIndex, mapData); if ( ! mapData.empty()) { CaretAssertVectorIndex(mapData, dataOffset); numericalValueOut = mapData[dataOffset]; if (isMappedWithLabelTable()) { textValueOut = "Invalid Label Index"; const GiftiLabelTable* glt = getMapLabelTable(mapIndex); const int32_t labelKey = static_cast(numericalValueOut); const GiftiLabel* gl = glt->getLabel(labelKey); if (gl != NULL) { textValueOut = gl->getName(); } else { textValueOut += ("InvalidLabelKey=" + AString::number(labelKey)); } numericalValueOutValid = true; } else if (isMappedWithPalette()) { numericalValueOutValid = true; textValueOut = AString::number(numericalValueOut); } else { CaretAssert(0); } return true; } } break; case CiftiMappingType::LABELS: CaretAssertMessage(0, "Mapping type should never be LABELS"); break; case CiftiMappingType::PARCELS: { const CiftiParcelsMap& map = ciftiXML.getParcelsMap(m_dataMappingDirectionForCiftiXML); const int64_t parcelMapIndex = map.getIndexForVoxel(ijk); const std::vector& parcels = map.getParcels(); if ((parcelMapIndex >= 0) && (parcelMapIndex < static_cast(parcels.size()))) { CaretAssertVectorIndex(parcels, parcelMapIndex); textValueOut = parcels[parcelMapIndex].m_name; // std::vector mapData; // getMapData(mapIndex, mapData); // // if (parcelMapIndex < static_cast(mapData.size())) { // textValueOut += (" " // + AString::number(mapData[parcelMapIndex])); // } } if (parcelMapIndex >= 0) { int64_t itemIndex = -1; switch (ciftiXML.getMappingType(m_dataReadingDirectionForCiftiXML)) { case CiftiMappingType::BRAIN_MODELS: { const CiftiBrainModelsMap& map = ciftiXML.getBrainModelsMap(m_dataReadingDirectionForCiftiXML); itemIndex = map.getIndexForVoxel(ijk); } break; case CiftiMappingType::LABELS: break; case CiftiMappingType::PARCELS: break; case CiftiMappingType::SCALARS: itemIndex = mapIndex; break; case CiftiMappingType::SERIES: itemIndex = mapIndex; break; } if (itemIndex >= 0) { const int64_t numRows = m_ciftiFile->getNumberOfRows(); const int64_t numCols = m_ciftiFile->getNumberOfColumns(); switch (m_dataReadingDirectionForCiftiXML) { case CiftiXML::ALONG_COLUMN: { std::vector data; data.resize(numRows); CaretAssert(parcelMapIndex < numCols); m_ciftiFile->getColumn(&data[0], parcelMapIndex); CaretAssertVectorIndex(data, itemIndex); textValueOut += (" " + AString::number(data[itemIndex])); } break; case CiftiXML::ALONG_ROW: { std::vector data; data.resize(numCols); CaretAssert(parcelMapIndex < numRows); m_ciftiFile->getRow(&data[0], parcelMapIndex); CaretAssertVectorIndex(data, itemIndex); textValueOut += (" " + AString::number(data[itemIndex])); } break; } } } } return true; break; case CiftiMappingType::SCALARS: CaretAssertMessage(0, "Mapping type should never be SCALARS"); break; case CiftiMappingType::SERIES: CaretAssertMessage(0, "Mapping type should never be SERIES"); break; } } } } return false; } /** * Get connectivity value for a voxel. When the data is mapped * to parcels, the numerical value will not be valid. * * @param mapIndices * Indices of the maps. * @param xyz * Coordinate of voxel. * @param ijkOut * Voxel indices of value. * @param numericalValuesOut * Numerical values out. * @param numericalValuesOutValid * Output that indicates the numerical values output is valid. * For label data, this value will be the label key. * @param textValueOut * Text containing node' value will always be valid if the method * returns true. For parcel data, this will contain the name of the * parcel. For label data, this will contain the name of the label. * For numerical data, this will contain the text representation * of the numerical value. * @return * True if the text value is valid. The numerical value may or may not * also be valid. */ bool CiftiMappableDataFile::getMapVolumeVoxelValues(const std::vector mapIndices, const float xyz[3], int64_t ijkOut[3], std::vector& numericalValuesOut, std::vector& numericalValuesOutValid, AString& textValueOut) const { textValueOut = ""; numericalValuesOut.clear(); numericalValuesOutValid.clear(); if (mapIndices.empty()) { return false; } CaretAssert(m_ciftiFile); int64_t ijk[3]; enclosingVoxel(xyz[0], xyz[1], xyz[2], ijk[0], ijk[1], ijk[2]); if (indexValid(ijk[0], ijk[1], ijk[2])) { /* * Only set the IJK if the index is valid. */ ijkOut[0] = ijk[0]; ijkOut[1] = ijk[1]; ijkOut[2] = ijk[2]; const int64_t dataOffset = m_voxelIndicesToOffset->getOffsetForIndices(ijk[0], ijk[1], ijk[2]); if (dataOffset >= 0) { CaretAssert((m_dataMappingDirectionForCiftiXML == CiftiXML::ALONG_ROW) || (m_dataMappingDirectionForCiftiXML == CiftiXML::ALONG_COLUMN)); const CiftiXML& ciftiXML = m_ciftiFile->getCiftiXML(); const CiftiParcelLabelFile* parcelLabelFile = dynamic_cast(this); if (parcelLabelFile != NULL) { const CiftiParcelsMap& map = ciftiXML.getParcelsMap(m_dataMappingDirectionForCiftiXML); const int64_t parcelMapIndex = map.getIndexForVoxel(ijk); const std::vector& parcels = map.getParcels(); AString areaName = ""; if ((parcelMapIndex >= 0) && (parcelMapIndex < static_cast(parcels.size()))) { CaretAssertVectorIndex(parcels, parcelMapIndex); areaName = parcels[parcelMapIndex].m_name; } textValueOut = ("Area: " + areaName + ", Network(s): "); for (std::vector::const_iterator mapIter = mapIndices.begin(); mapIter != mapIndices.end(); mapIter++) { textValueOut += (getMapName(*mapIter) + " "); } // const AString networkName = getMapName(mapIndex); // // textValueOut = ("Area: " // + areaName // + ", Network: " // + networkName); return true; } else { switch (ciftiXML.getMappingType(m_dataMappingDirectionForCiftiXML)) { case CiftiMappingType::BRAIN_MODELS: { for (std::vector::const_iterator mapIter = mapIndices.begin(); mapIter != mapIndices.end(); mapIter++) { const int32_t mapIndex = *mapIter; /* * Note: For a Dense connectivity file, it may not have * data loaded since data is loaded upon demand. */ std::vector mapData; getMapData(mapIndex, mapData); if ( ! mapData.empty()) { CaretAssertVectorIndex(mapData, dataOffset); const float value = mapData[dataOffset]; if (isMappedWithLabelTable()) { textValueOut = "Invalid Label Index"; const GiftiLabelTable* glt = getMapLabelTable(mapIndex); const int32_t labelKey = static_cast(value); const GiftiLabel* gl = glt->getLabel(labelKey); if (gl != NULL) { textValueOut += (gl->getName() + " "); } else { textValueOut += ("InvalidLabelKey=" + AString::number(labelKey) + " "); } numericalValuesOut.push_back(value); numericalValuesOutValid.push_back(false); // NOT VALID ! } else if (isMappedWithPalette()) { numericalValuesOut.push_back(value); numericalValuesOutValid.push_back(true); textValueOut = (AString::number(value) + " "); } else { CaretAssert(0); } return true; } } } break; case CiftiMappingType::LABELS: CaretAssertMessage(0, "Mapping type should never be LABELS"); break; case CiftiMappingType::PARCELS: { const CiftiParcelsMap& map = ciftiXML.getParcelsMap(m_dataMappingDirectionForCiftiXML); const int64_t parcelMapIndex = map.getIndexForVoxel(ijk); const std::vector& parcels = map.getParcels(); if ((parcelMapIndex >= 0) && (parcelMapIndex < static_cast(parcels.size()))) { CaretAssertVectorIndex(parcels, parcelMapIndex); textValueOut = parcels[parcelMapIndex].m_name; // std::vector mapData; // getMapData(mapIndex, mapData); // // if (parcelMapIndex < static_cast(mapData.size())) { // textValueOut += (" " // + AString::number(mapData[parcelMapIndex])); // } } for (std::vector::const_iterator mapIter = mapIndices.begin(); mapIter != mapIndices.end(); mapIter++) { const int32_t mapIndex = *mapIter; if (parcelMapIndex >= 0) { int64_t itemIndex = -1; switch (ciftiXML.getMappingType(m_dataReadingDirectionForCiftiXML)) { case CiftiMappingType::BRAIN_MODELS: { const CiftiBrainModelsMap& map = ciftiXML.getBrainModelsMap(m_dataReadingDirectionForCiftiXML); itemIndex = map.getIndexForVoxel(ijk); } break; case CiftiMappingType::LABELS: break; case CiftiMappingType::PARCELS: break; case CiftiMappingType::SCALARS: itemIndex = mapIndex; break; case CiftiMappingType::SERIES: itemIndex = mapIndex; break; } if (itemIndex >= 0) { const int64_t numRows = m_ciftiFile->getNumberOfRows(); const int64_t numCols = m_ciftiFile->getNumberOfColumns(); switch (m_dataReadingDirectionForCiftiXML) { case CiftiXML::ALONG_COLUMN: { std::vector data; data.resize(numRows); CaretAssert(parcelMapIndex < numCols); m_ciftiFile->getColumn(&data[0], parcelMapIndex); CaretAssertVectorIndex(data, itemIndex); textValueOut += (" " + AString::number(data[itemIndex])); } break; case CiftiXML::ALONG_ROW: { std::vector data; data.resize(numCols); CaretAssert(parcelMapIndex < numRows); m_ciftiFile->getRow(&data[0], parcelMapIndex); CaretAssertVectorIndex(data, itemIndex); textValueOut += (" " + AString::number(data[itemIndex])); } break; } } } } } return true; break; case CiftiMappingType::SCALARS: CaretAssertMessage(0, "Mapping type should never be SCALARS"); break; case CiftiMappingType::SERIES: CaretAssertMessage(0, "Mapping type should never be SERIES"); break; } } } } return false; } /** * Get the value of the voxel containing the given coordinate. * * @param coordinateIn * The 3D coordinate * @param validOut * If not NULL, will indicate if the coordinate (and hence the * returned value) is valid. * @param mapIndex * Index of map. * @param component * Voxel component. * @return * Value of voxel containing the given coordinate. */ float CiftiMappableDataFile::getVoxelValue(const float* coordinateIn, bool* validOut, const int64_t mapIndex, const int64_t component) const { return getVoxelValue(coordinateIn[0], coordinateIn[1], coordinateIn[2], validOut, mapIndex, component); } /** * Get the value of the voxel containing the given coordinate. * * @param coordinateX * The X coordinate * @param coordinateY * The Y coordinate * @param coordinateZ * The Z coordinate * @param validOut * If not NULL, will indicate if the coordinate (and hence the * returned value) is valid. * @param mapIndex * Index of map. * @param component * Voxel component. * @return * Value of voxel containing the given coordinate. */ float CiftiMappableDataFile::getVoxelValue(const float coordinateX, const float coordinateY, const float coordinateZ, bool* validOut, const int64_t mapIndex, const int64_t component) const { if (validOut != NULL) { *validOut = false; } CaretAssert(m_ciftiFile); /* * Get content for map. */ CaretAssertVectorIndex(m_mapContent, mapIndex); int64_t voxelI, voxelJ, voxelK; enclosingVoxel(coordinateX, coordinateY, coordinateZ, voxelI, voxelJ, voxelK); if (indexValid(voxelI, voxelJ, voxelK, mapIndex, component)) { const int64_t dataOffset = m_voxelIndicesToOffset->getOffsetForIndices(voxelI, voxelJ, voxelK); if (dataOffset >= 0) { std::vector mapData; getMapData(mapIndex, mapData); CaretAssertVectorIndex(mapData, dataOffset); const float value = mapData[dataOffset]; if (validOut != NULL) { *validOut = true; } return value; } } return 0.0; } /** * @param coordinate * Coordinate for which enclosing voxel is located. * @param mapIndex * Index of the map. * @return * Offset in map data for enclosing voxel or NULL if not within a voxel. */ int64_t CiftiMappableDataFile::getMapDataOffsetForVoxelAtCoordinate(const float coordinate[3], const int32_t mapIndex) const { int64_t dataOffset = -1; CaretAssert(m_ciftiFile); /* * Get content for map. */ CaretAssertVectorIndex(m_mapContent, mapIndex); int64_t voxelI, voxelJ, voxelK; enclosingVoxel(coordinate[0], coordinate[1], coordinate[2], voxelI, voxelJ, voxelK); if (indexValid(voxelI, voxelJ, voxelK, mapIndex, 0)) { dataOffset = m_voxelIndicesToOffset->getOffsetForIndices(voxelI, voxelJ, voxelK); } return dataOffset; } /** * Get the identification information for a surface node in the given maps. * * @param mapIndices * Indices of maps for which identification information is requested. * @param xyz * Coordinate of voxel. * @param ijkOut * Voxel indices of value. * @param textOut * Output containing identification information. */ bool CiftiMappableDataFile::getVolumeVoxelIdentificationForMaps(const std::vector& mapIndices, const float xyz[3], int64_t ijkOut[3], AString& textOut) const { CaretAssert(m_ciftiFile); const int32_t numberOfMapIndices = static_cast(mapIndices.size()); if (numberOfMapIndices <= 0) { return false; } textOut = ""; std::vector numericalValues; std::vector numericalValuesValid; AString textValue; if (getMapVolumeVoxelValues(mapIndices, xyz, ijkOut, numericalValues, numericalValuesValid, textValue)) { textOut = textValue; } // for (int32_t i = 0; i < numberOfMapIndices; i++) { // const int32_t mapIndex = mapIndices[i]; // // float numericalValue; // AString textValue; // bool numericalValueValid; // if (getMapVolumeVoxelValue(mapIndex, // xyz, // ijkOut, // numericalValue, // numericalValueValid, // textValue)) { // textOut += textValue; // textOut += " "; // } // } if (textOut.isEmpty() == false) { return true; } return false; } /** * Set the status to unmodified. */ void CiftiMappableDataFile::clearModified() { CaretMappableDataFile::clearModified(); CaretAssert(m_ciftiFile); const CiftiXML& ciftiXML = m_ciftiFile->getCiftiXML(); ciftiXML.getFileMetaData()->clearModified(); const int32_t numMaps = getNumberOfMaps(); for (int32_t i = 0; i < numMaps; i++) { m_mapContent[i]->clearModified(); } } /** * @return True if the file is modified in any way EXCEPT for * the palette color mapping. Also see isModified(). */ bool CiftiMappableDataFile::isModifiedExcludingPaletteColorMapping() const { if (CaretMappableDataFile::isModifiedExcludingPaletteColorMapping()) { return true; } CaretAssert(m_ciftiFile); const CiftiXML& ciftiXML = m_ciftiFile->getCiftiXML(); if (ciftiXML.getFileMetaData()->isModified()) { return true; } const int32_t numMaps = getNumberOfMaps(); for (int32_t i = 0; i < numMaps; i++) { if (m_mapContent[i]->isModified()) { return true; } } return false; } /** * @return The units for the 'interval' between two consecutive maps. */ NiftiTimeUnitsEnum::Enum CiftiMappableDataFile::getMapIntervalUnits() const { return m_mappingTimeUnits; } /** * Get the units value for the first map and the * quantity of units between consecutive maps. If the * units for the maps is unknown, value of one (1) are * returned for both output values. * * @param firstMapUnitsValueOut * Output containing units value for first map. * @param mapIntervalStepValueOut * Output containing number of units between consecutive maps. */ void CiftiMappableDataFile::getMapIntervalStartAndStep(float& firstMapUnitsValueOut, float& mapIntervalStepValueOut) const { firstMapUnitsValueOut = m_mappingTimeStart; mapIntervalStepValueOut = m_mappingTimeStep; } /** * Get the minimum and maximum values from ALL maps in this file. * Note that not all files (due to size of file) are able to provide * the minimum and maximum values from the file. The return value * indicates success/failure. If the failure (false) is returned * the returned values are likely +/- the maximum float values. * * @param dataRangeMinimumOut * Minimum data value found. * @param dataRangeMaximumOut * Maximum data value found. * @return * True if the values are valid, else false. */ bool CiftiMappableDataFile::getDataRangeFromAllMaps(float& dataRangeMinimumOut, float& dataRangeMaximumOut) const { CaretAssert(m_ciftiFile); /* * Dense is very large but at this time is [-1, 1] */ if (getDataFileType() == DataFileTypeEnum::CONNECTIVITY_DENSE) { dataRangeMaximumOut = 1.0; dataRangeMinimumOut = -1; return true; } else { if (m_ciftiFile->getDataRangeFromAllMaps(dataRangeMinimumOut, dataRangeMaximumOut)) { return true; } } /* * Default */ dataRangeMaximumOut = std::numeric_limits::max(); dataRangeMinimumOut = -dataRangeMaximumOut; return false; } /** * Get the number of nodes for the structure for mapping data. * * @param structure * Structure for which number of nodes is requested. * @return * Number of nodes corresponding to structure. If no matching structure * is found, a negative value is returned. */ int32_t CiftiMappableDataFile::getMappingSurfaceNumberOfNodes(const StructureEnum::Enum structure) const { int32_t numCiftiNodes = -1; if (m_dataMappingAccessMethod == DATA_ACCESS_NONE) { return numCiftiNodes; } CaretAssert((m_dataMappingDirectionForCiftiXML == CiftiXML::ALONG_ROW) || (m_dataMappingDirectionForCiftiXML == CiftiXML::ALONG_COLUMN)); const CiftiXML& ciftiXML = m_ciftiFile->getCiftiXML(); switch (ciftiXML.getMappingType(m_dataMappingDirectionForCiftiXML)) { case CiftiMappingType::BRAIN_MODELS: { const CiftiBrainModelsMap& map = ciftiXML.getBrainModelsMap(m_dataMappingDirectionForCiftiXML); if (map.hasSurfaceData(structure)) { numCiftiNodes = map.getSurfaceNumberOfNodes(structure); } } break; case CiftiMappingType::LABELS: CaretAssertMessage(0, "Mapping type should never be LABELS"); break; case CiftiMappingType::PARCELS: { const CiftiParcelsMap& map = ciftiXML.getParcelsMap(m_dataMappingDirectionForCiftiXML); if (map.hasSurfaceData(structure)) { numCiftiNodes = map.getSurfaceNumberOfNodes(structure); } } break; case CiftiMappingType::SCALARS: CaretAssertMessage(0, "Mapping type should never be SCALARS"); break; case CiftiMappingType::SERIES: CaretAssertMessage(0, "Mapping type should never be SERIES"); break; } return numCiftiNodes; } /** * Save file data from the scene. For subclasses that need to * save to a scene, this method should be overriden. sceneClass * will be valid and any scene data should be added to it. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass to which data members should be added. */ void CiftiMappableDataFile::saveFileDataToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass) { CaretMappableDataFile::saveFileDataToScene(sceneAttributes, sceneClass); if (isMappedWithLabelTable()) { sceneClass->addClass(m_classNameHierarchy->saveToScene(sceneAttributes, "m_classNameHierarchy")); } } /** * Restore file data from the scene. For subclasses that need to * restore from a scene, this method should be overridden. The scene class * will be valid and any scene data may be obtained from it. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass for the instance of a class that implements * this interface. Will NEVER be NULL. */ void CiftiMappableDataFile::restoreFileDataFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { CaretMappableDataFile::restoreFileDataFromScene(sceneAttributes, sceneClass); if (isMappedWithLabelTable()) { m_classNameHierarchy->restoreFromScene(sceneAttributes, sceneClass->getClass("m_classNameHierarchy")); } /* * When a scene is created and there is a modified palette, the user may choose * to save the modified palette to the scene so that the file does not need to * be saved with a changed palette. When scenes are loaded and a file in the * scene is already in memory, the file is NOT reloaded to save time. However, * since the palette may be saved to the scene, the coloring will needd to be * updated. If this is not done, the coloring of the file's data prior to * loading the scene remains and may be incorrect. */ invalidateColoringInAllMaps(); } /** * Add information about the file to the data file information. * * @param dataFileInformation * Consolidates information about a data file. */ void CiftiMappableDataFile::addToDataFileContentInformation(DataFileContentInformation& dataFileInformation) { CaretAssert(m_ciftiFile); CaretMappableDataFile::addToDataFileContentInformation(dataFileInformation); dataFileInformation.addNameAndValue("Number of Rows", m_ciftiFile->getNumberOfRows()); dataFileInformation.addNameAndValue("Number of Columns", m_ciftiFile->getNumberOfColumns()); int64_t dimI, dimJ, dimK, dimTime, dimNumComp; getDimensions(dimI, dimJ, dimK, dimTime, dimNumComp); dataFileInformation.addNameAndValue("Volume Dim[0]", dimI); dataFileInformation.addNameAndValue("Volume Dim[1]", dimJ); dataFileInformation.addNameAndValue("Volume Dim[2]", dimK); AString paletteType; switch (m_paletteColorMappingSource) { case PALETTE_COLOR_MAPPING_SOURCE_INVALID: paletteType = "None"; break; case PALETTE_COLOR_MAPPING_SOURCE_FROM_FILE: paletteType = "File (One for all maps)"; break; case PALETTE_COLOR_MAPPING_SOURCE_FROM_MAP: paletteType = "Map (Unique for each map)"; break; } dataFileInformation.addNameAndValue("Palette Type", paletteType); const CiftiXML& ciftiXML = m_ciftiFile->getCiftiXML(); CiftiMappableDataFile::addCiftiXmlToDataFileContentInformation(dataFileInformation, ciftiXML); } /** * Get a text name for a CIFTI mapping type. * * @param mappingType * The CIFTI mapping type. * @return * String containing text name. */ AString CiftiMappableDataFile::mappingTypeToName(const CiftiMappingType::MappingType mappingType) { AString mapTypeName; switch (mappingType) { case CiftiMappingType::BRAIN_MODELS: mapTypeName = "BRAIN_MODELS"; break; case CiftiMappingType::LABELS: mapTypeName = "LABELS"; break; case CiftiMappingType::PARCELS: mapTypeName = "PARCELS"; break; case CiftiMappingType::SCALARS: mapTypeName = "SCALARS"; break; case CiftiMappingType::SERIES: mapTypeName = "SERIES"; break; } return mapTypeName; } /** * Add information about the file to the data file information. * * @param dataFileInformation * Consolidates information about a data file. * @param ciftiXML * The CIFTI XML. * @param numberOfCiftiDimensions * Number of Dimensions of the CIFTI File. */ void CiftiMappableDataFile::addCiftiXmlToDataFileContentInformation(DataFileContentInformation& dataFileInformation, const CiftiXML& ciftiXML) { std::vector dims = ciftiXML.getDimensions(); const int32_t numDims = static_cast(dims.size()); for (int32_t i = 0; i < numDims; i++) { dataFileInformation.addNameAndValue(("CIFTI Dim[" + AString::number(i) + "]"), dims[i]); } const bool showLabelMappingsFlag = dataFileInformation.isOptionFlag(DataFileContentInformation::OPTION_SHOW_CIFTI_LABEL_MAPPING); for (int32_t alongType = 0; alongType < numDims; alongType++) { AString alongName; CiftiMappingType::MappingType mapType = CiftiMappingType::BRAIN_MODELS; switch (alongType) { case CiftiXML::ALONG_ROW: alongName = "ALONG_ROW"; mapType = ciftiXML.getMappingType(CiftiXML::ALONG_ROW); break; case CiftiXML::ALONG_COLUMN: alongName = "ALONG_COLUMN"; mapType = ciftiXML.getMappingType(CiftiXML::ALONG_COLUMN); break; case CiftiXML::ALONG_STACK: alongName = "ALONG_STACK"; mapType = ciftiXML.getMappingType(CiftiXML::ALONG_STACK); break; } AString mapInfoString; if ( ! alongName.isEmpty()) { AString mapTypeName; switch (mapType) { case CiftiMappingType::BRAIN_MODELS: mapTypeName = "BRAIN_MODELS"; break; case CiftiMappingType::LABELS: mapTypeName = "LABELS"; break; case CiftiMappingType::PARCELS: mapTypeName = "PARCELS"; break; case CiftiMappingType::SCALARS: mapTypeName = "SCALARS"; break; case CiftiMappingType::SERIES: mapTypeName = "SERIES"; break; } dataFileInformation.addNameAndValue((alongName + " map type"), mapTypeName); switch (mapType) { case CiftiMappingType::BRAIN_MODELS: { const CiftiBrainModelsMap& bmm = ciftiXML.getBrainModelsMap(alongType); dataFileInformation.addNameAndValue(" Has Volume Data", bmm.hasVolumeData()); if (bmm.hasVolumeData()) { const VolumeSpace volumeSpace = bmm.getVolumeSpace(); const int64_t* dims = volumeSpace.getDims(); dataFileInformation.addNameAndValue(" Volume Dims", AString::fromNumbers(dims, 3, ",")); const std::vector >& sform = volumeSpace.getSform(); dataFileInformation.addNameAndValue(" Volume Space", AString::fromNumbers(sform[0].data(), 4, ",") + ";" + AString::fromNumbers(sform[1].data(), 4, ",") + ";" + AString::fromNumbers(sform[2].data(), 4, ",")); } std::vector modelInfo = bmm.getModelInfo();//allows us to visit the models in the order they are in the file for (int i = 0; i < (int)modelInfo.size(); ++i) { if (modelInfo[i].m_type == CiftiBrainModelsMap::SURFACE) { dataFileInformation.addNameAndValue((" " + StructureEnum::toGuiName(modelInfo[i].m_structure)), (AString::number(modelInfo[i].m_indexCount) + " out of " + AString::number(bmm.getSurfaceNumberOfNodes(modelInfo[i].m_structure)) + " vertices")); } else { CaretAssert(modelInfo[i].m_type == CiftiBrainModelsMap::VOXELS); dataFileInformation.addNameAndValue((" " + StructureEnum::toGuiName(modelInfo[i].m_structure)), (AString::number(modelInfo[i].m_indexCount) + " voxels")); } } } break; case CiftiMappingType::LABELS: if (showLabelMappingsFlag) { const CiftiLabelsMap& clm = ciftiXML.getLabelsMap(alongType); const int32_t numItems = clm.getLength(); for (int32_t i = 0; i < numItems; i++) { dataFileInformation.addText(" Index=" + AString::number(i) + " Name=" + clm.getMapName(i) + "\n" + clm.getMapLabelTable(i)->toFormattedString(" ") + "\n"); } } break; case CiftiMappingType::PARCELS: { const CiftiParcelsMap& cpm = ciftiXML.getParcelsMap(alongType); dataFileInformation.addNameAndValue(" Has Volume Data", cpm.hasVolumeData()); if (cpm.hasVolumeData()) { const VolumeSpace volumeSpace = cpm.getVolumeSpace(); const int64_t* dims = volumeSpace.getDims(); dataFileInformation.addNameAndValue(" Volume Dims", AString::fromNumbers(dims, 3, ",")); } const std::vector surfaceStructures = cpm.getParcelSurfaceStructures(); for (std::vector::const_iterator surfaceIter = surfaceStructures.begin(); surfaceIter != surfaceStructures.end(); surfaceIter++) { const StructureEnum::Enum structure = *surfaceIter; dataFileInformation.addNameAndValue((" " + StructureEnum::toGuiName(structure)), (AString::number(cpm.getSurfaceNumberOfNodes(structure)) + " vertices")); } const std::vector& parcels = cpm.getParcels(); for (std::vector::const_iterator parcelIter = parcels.begin(); parcelIter != parcels.end(); parcelIter++) { const CiftiParcelsMap::Parcel parcel = *parcelIter; dataFileInformation.addNameAndValue(" Parcel " + AString::number(parcelIter - parcels.begin() + 1), parcel.m_name); for (std::map >::const_iterator surfIter = parcel.m_surfaceNodes.begin(); surfIter != parcel.m_surfaceNodes.end(); surfIter++) { const StructureEnum::Enum structure = surfIter->first; const std::set& nodeIndices = surfIter->second; dataFileInformation.addNameAndValue(" " + StructureEnum::toGuiName(structure), AString::number(nodeIndices.size()) + " vertices"); } if (parcel.m_voxelIndices.size() != 0) { dataFileInformation.addNameAndValue(" ", AString::number(parcel.m_voxelIndices.size()) + " voxels"); } } } break; case CiftiMappingType::SCALARS: break; case CiftiMappingType::SERIES: { const CiftiSeriesMap& csm = ciftiXML.getSeriesMap(alongType); dataFileInformation.addNameAndValue(" Start", csm.getStart()); dataFileInformation.addNameAndValue(" Step", csm.getStep()); QString unitsName = "Unknown"; switch (csm.getUnit()) { case CiftiSeriesMap::HERTZ: unitsName = "Hertz"; break; case CiftiSeriesMap::METER: unitsName = "Meters"; break; case CiftiSeriesMap::RADIAN: unitsName = "Radians"; break; case CiftiSeriesMap::SECOND: unitsName = "Seconds"; break; } dataFileInformation.addNameAndValue(" Units", unitsName); } break; } } } } /** * Get information about the content of a generic CIFTI file that is * not a Workbench supported CIFTI file type. * * @param filename * Name of the file. * @param dataFileInformation * Consolidates information about a data file. */ void CiftiMappableDataFile::getDataFileContentInformationForGenericCiftiFile(const AString& filename, DataFileContentInformation& dataFileInformation) { CiftiFile ciftiFile(filename); const CiftiXML& ciftiXML = ciftiFile.getCiftiXML(); std::vector dims = ciftiXML.getDimensions(); const int32_t numDims = static_cast(dims.size()); int64_t dataSizeInBytes = 1; for (int32_t i = 0; i < numDims; i++) { dataSizeInBytes *= dims[i]; } dataSizeInBytes *= sizeof(float); dataFileInformation.addNameAndValue("Name", filename); dataFileInformation.addNameAndValue("Type", AString("Connectivity Unknown (Could be Unsupported CIFTI File)")); dataFileInformation.addNameAndValue("Data Size", FileInformation::fileSizeToStandardUnits(dataSizeInBytes)); dataFileInformation.setOptionFlag(DataFileContentInformation::OPTION_SHOW_CIFTI_LABEL_MAPPING, true); CiftiMappableDataFile::addCiftiXmlToDataFileContentInformation(dataFileInformation, ciftiXML); } /** * Help load charting data for the surface with the given structure and node average. * * @param structure * The surface's structure. * @param nodeIndices * Indices of nodes for averaging. * @return * Pointer to the chart data. If the data FAILED to load, * the returned pointer will return true. Caller takes ownership * of the pointer and must delete it when no longer needed. */ ChartDataCartesian* CiftiMappableDataFile::helpLoadChartDataForSurfaceNodeAverage(const StructureEnum::Enum structure, const std::vector& nodeIndices) { ChartDataCartesian* chartData = NULL; try { const int32_t numberOfNodes = static_cast(nodeIndices.size()); if (numberOfNodes > 0) { std::vector dataSum; int32_t dataSumSize = 0; int32_t dataAverageCount = 0; std::vector data; bool firstNodeFlag = true; for (int32_t i = 0; i < numberOfNodes; i++) { if (getSeriesDataForSurfaceNode(structure, nodeIndices[i], data)) { if (firstNodeFlag) { firstNodeFlag = false; dataSumSize = static_cast(data.size()); if (dataSumSize > 0) { dataSum.resize(dataSumSize, 0.0); } } CaretAssert(dataSumSize == static_cast(data.size())); for (int32_t j = 0; j < dataSumSize; j++) { dataSum[j] += data[j]; } dataAverageCount++; } } if ((dataAverageCount > 0) && (dataSumSize > 0)) { std::vector dataAverage(dataSumSize); for (int32_t k = 0; k < dataSumSize; k++) { dataAverage[k] = dataSum[k] / dataAverageCount; } chartData = helpCreateCartesianChartData(dataAverage); } } } catch (const DataFileException& dfe) { if (chartData != NULL) { delete chartData; chartData = NULL; } throw dfe; } return chartData; } /** * Help load charting data for the voxel at the given coordinate. * * @param xyz * The voxel coordinate. * @return * Pointer to the chart data. If the data FAILED to load, * the returned pointer will return true. Caller takes ownership * of the pointer and must delete it when no longer needed. */ ChartDataCartesian* CiftiMappableDataFile::helpLoadChartDataForVoxelAtCoordinate(const float xyz[3]) { ChartDataCartesian* chartData = NULL; try { std::vector data; if (getSeriesDataForVoxelAtCoordinate(xyz, data)) { chartData = helpCreateCartesianChartData(data); } } catch (const DataFileException& dfe) { if (chartData != NULL) { delete chartData; chartData = NULL; } throw dfe; } return chartData; } /** * Help load charting data for the surface with the given structure and node index. * * @param structure * The surface's structure. * @param nodeIndex * Index of the node. * @return * Pointer to the chart data. If the data FAILED to load, * the returned pointer will return true. Caller takes ownership * of the pointer and must delete it when no longer needed. */ ChartDataCartesian* CiftiMappableDataFile::helpLoadChartDataForSurfaceNode(const StructureEnum::Enum structure, const int32_t nodeIndex) { ChartDataCartesian* chartData = NULL; try { std::vector data; if (getSeriesDataForSurfaceNode(structure, nodeIndex, data)) { chartData = helpCreateCartesianChartData(data); } } catch (const DataFileException& dfe) { if (chartData != NULL) { delete chartData; chartData = NULL; } throw dfe; } return chartData; } bool CiftiMappableDataFile::getMapDataForSurface(const int32_t mapIndex, const StructureEnum::Enum structure, std::vector& surfaceMapData, std::vector* roiData) const { surfaceMapData.clear();//empty data is secondary hint at failure CaretAssert(m_ciftiFile); CaretAssertVectorIndex(m_mapContent, mapIndex); const int32_t surfaceNumNodes = getMappingSurfaceNumberOfNodes(structure); if (surfaceNumNodes < 1) return false; std::vector mapData; getMapData(mapIndex, mapData); /* * Map data may be empty for connectivity matrix files with no rows loaded. */ if (mapData.empty()) { return false; } std::vector dataIndicesForNodes; if (!getSurfaceDataIndicesForMappingToBrainordinates(structure, surfaceNumNodes, dataIndicesForNodes)) { return false;//currently should never happen, this currently works for parcellated files } CaretAssert((int)dataIndicesForNodes.size() == surfaceNumNodes); surfaceMapData.resize(surfaceNumNodes, 0.0f); if (roiData != NULL) { roiData->clear();//make sure all values get initialized before setting the roi nodes roiData->resize(surfaceNumNodes, 0.0f); } for (int32_t iNode = 0; iNode < surfaceNumNodes; iNode++) { CaretAssertVectorIndex(dataIndicesForNodes, iNode); const int64_t dataIndex = dataIndicesForNodes[iNode]; if (dataIndex >= 0) { surfaceMapData[iNode] = mapData[dataIndex]; if (roiData != NULL) { (*roiData)[iNode] = 1.0f; } } } return true; } /** * Set the map data for the given structure. * * @param mapIndex * Index of the map. * @param structure * The surface structure. * @param surfaceMapData * Data for surface map that must contain same number of elements as * in the brain models map for the surface. * @throw * DataFileException if there is an error. * */ void CiftiMappableDataFile::setMapDataForSurface(const int32_t mapIndex, const StructureEnum::Enum structure, const std::vector surfaceMapData) { CaretAssert(m_ciftiFile); CaretAssertVectorIndex(m_mapContent, mapIndex); const int32_t surfaceNumberOfNodes = static_cast(surfaceMapData.size()); const int32_t numCiftiNodes = getMappingSurfaceNumberOfNodes(structure); if (numCiftiNodes != surfaceNumberOfNodes) { return; } std::vector mapData; getMapData(mapIndex, mapData); /* * Map data may be empty for connectivity matrix files with no rows loaded. */ if (mapData.empty()) { return; } std::vector dataIndicesForNodes; if (getSurfaceDataIndicesForMappingToBrainordinates(structure, surfaceNumberOfNodes, dataIndicesForNodes)) { for (int32_t iNode = 0; iNode < surfaceNumberOfNodes; iNode++) { CaretAssertVectorIndex(dataIndicesForNodes, iNode); const int64_t dataIndex = dataIndicesForNodes[iNode]; if (dataIndex >= 0) { mapData[dataIndex] = surfaceMapData[iNode]; } } setMapData(mapIndex, mapData); } } /** * Get the matrix dimensions. * * @param numberOfRowsOut * Output number of rows in rgba matrix. * @param numberOfColumnsOut * Output number of Columns in rgba matrix. */ void CiftiMappableDataFile::helpMapFileGetMatrixDimensions(int32_t& numberOfRowsOut, int32_t& numberOfColumnsOut) const { CaretAssert(m_ciftiFile); /* * Dimensions of matrix. */ numberOfRowsOut = m_ciftiFile->getNumberOfRows(); numberOfColumnsOut = m_ciftiFile->getNumberOfColumns(); } /** * Help load matrix chart data and order in the given row indices * for a file with multi-mapped file that uses a unqiue palette * or label table for each column (row) in the file. * * @param numberOfRowsOut * Output number of rows in rgba matrix. * @param numberOfColumnsOut * Output number of Columns in rgba matrix. * @param rowIndicesIn * Indices of rows inserted into matrix. * @param rgbaOut * RGBA matrix (number of elements is rows * columns * 4). * @return * True if output data is valid, else false. */ bool CiftiMappableDataFile::helpMapFileLoadChartDataMatrixRGBA(int32_t& numberOfRowsOut, int32_t& numberOfColumnsOut, const std::vector& rowIndicesIn, std::vector& rgbaOut) const { CaretAssert(m_ciftiFile); /* * Dimensions of matrix. */ numberOfRowsOut = m_ciftiFile->getNumberOfRows(); numberOfColumnsOut = m_ciftiFile->getNumberOfColumns(); const int32_t numberOfData = numberOfRowsOut * numberOfColumnsOut; if (numberOfData <= 0) { return false; } const bool useLabelTableFlag = isMappedWithLabelTable(); const bool usePaletteFlag = isMappedWithPalette(); if (( ! useLabelTableFlag) && ( ! usePaletteFlag)) { CaretAssert(0); return false; } const CiftiXML& ciftiXML = m_ciftiFile->getCiftiXML(); const AString badMapTypeMessage("Matrix charts supports only maps in columns at this time for LABEL and SCALAR data"); if (useLabelTableFlag) { if (ciftiXML.getMappingType(CiftiXML::ALONG_ROW) != CiftiMappingType::LABELS) { CaretAssertMessage(0, badMapTypeMessage); CaretLogSevere(badMapTypeMessage); return false; } } if (usePaletteFlag) { if (ciftiXML.getMappingType(CiftiXML::ALONG_ROW) != CiftiMappingType::SCALARS) { CaretAssertMessage(0, badMapTypeMessage); CaretLogSevere(badMapTypeMessage); return false; } } std::vector rowIndices = rowIndicesIn; if (rowIndices.empty()) { rowIndices.resize(numberOfRowsOut); for (int32_t i = 0; i < numberOfRowsOut; i++) { rowIndices[i] = i; } } else { if (static_cast(rowIndices.size()) != numberOfRowsOut) { const AString msg = AString("rowIndices size=%1 is different than " "number of rows in the matrix=%2.").arg(rowIndices.size()).arg(numberOfRowsOut); CaretAssertMessage(0, msg); CaretLogSevere(msg); return false; } } /* * Set up Fast Stats for files that use all data for * statistics and color mapping. */ CiftiMappableDataFile* nonConstMapFile = const_cast(this); /* * Allocate rgba output */ const int32_t numberOfRgba = numberOfData * 4; rgbaOut.resize(numberOfRgba); /* * Get each column, color it using its label table, and then * add the column's coloring into the output coloring. */ std::vector columnData(numberOfRowsOut); std::vector columnRGBA(numberOfRowsOut * 4); for (int32_t iCol = 0; iCol < numberOfColumnsOut; iCol++) { CaretAssertVectorIndex(m_mapContent, iCol); m_ciftiFile->getColumn(&columnData[0], iCol); if (useLabelTableFlag) { const GiftiLabelTable* labelTable = getMapLabelTable(iCol); NodeAndVoxelColoring::colorIndicesWithLabelTable(labelTable, &columnData[0], numberOfRowsOut, &columnRGBA[0]); } else if (usePaletteFlag) { const PaletteColorMapping* pcm = getMapPaletteColorMapping(iCol); CaretAssert(pcm); const AString paletteName = pcm->getSelectedPaletteName(); if (paletteName.isEmpty()) { CaretLogSevere("No palette name for coloring matrix chart data."); return false; } EventPaletteGetByName eventPaletteGetName(paletteName); EventManager::get()->sendEvent(eventPaletteGetName.getPointer()); const Palette* palette = eventPaletteGetName.getPalette(); if (palette == NULL) { CaretLogSevere("No palette named " + paletteName + " found for coloring matrix chart data."); return false; } NodeAndVoxelColoring::colorScalarsWithPalette(nonConstMapFile->getFileFastStatistics(), pcm, palette, &columnData[0], &columnData[0], numberOfRowsOut, &columnRGBA[0]); } else { CaretAssert(0); } for (int32_t iRow = 0; iRow < numberOfRowsOut; iRow++) { const int32_t rgbaOffset = (((iRow * numberOfColumnsOut) + iCol) * 4); CaretAssertVectorIndex(rgbaOut, rgbaOffset + 3); const int32_t columnRgbaOffset = (iRow * 4); CaretAssertVectorIndex(columnRGBA, columnRgbaOffset + 3); rgbaOut[rgbaOffset] = columnRGBA[columnRgbaOffset]; rgbaOut[rgbaOffset+1] = columnRGBA[columnRgbaOffset+1]; rgbaOut[rgbaOffset+2] = columnRGBA[columnRgbaOffset+2]; rgbaOut[rgbaOffset+3] = columnRGBA[columnRgbaOffset+3]; } } return true; } /** * Help load matrix chart data and order in the given row indices * for a connectivity matrix file where one palette is used * for all data in the file. * * @param numberOfRowsOut * Output number of rows in rgba matrix. * @param numberOfColumnsOut * Output number of Columns in rgba matrix. * @param rowIndicesIn * Indices of rows inserted into matrix. * @param rgbaOut * RGBA matrix (number of elements is rows * columns * 4). * @return * True if output data is valid, else false. */ bool CiftiMappableDataFile::helpMatrixFileLoadChartDataMatrixRGBA(int32_t& numberOfRowsOut, int32_t& numberOfColumnsOut, const std::vector& rowIndicesIn, std::vector& rgbaOut) const { CaretAssert(m_ciftiFile); /* * Dimensions of matrix. */ numberOfRowsOut = m_ciftiFile->getNumberOfRows(); numberOfColumnsOut = m_ciftiFile->getNumberOfColumns(); const int32_t numberOfData = numberOfRowsOut * numberOfColumnsOut; if (numberOfData <= 0) { return false; } std::vector rowIndices = rowIndicesIn; if (rowIndices.empty()) { rowIndices.resize(numberOfRowsOut); for (int32_t i = 0; i < numberOfRowsOut; i++) { rowIndices[i] = i; } } else { if (static_cast(rowIndices.size()) != numberOfRowsOut) { const AString msg = AString("rowIndices size=%1 is different than " "number of rows in the matrix=%2.").arg(rowIndices.size()).arg(numberOfRowsOut); CaretAssertMessage(0, msg); CaretLogSevere(msg); return false; } } /* * Get the data. */ std::vector data(numberOfData); for (int32_t iRow = 0; iRow < numberOfRowsOut; iRow++) { CaretAssertVectorIndex(rowIndices, iRow); const int32_t rowIndex = rowIndices[iRow]; const int32_t rowOffset = rowIndex * numberOfColumnsOut; CaretAssertVectorIndex(data, rowOffset + numberOfColumnsOut - 1); m_ciftiFile->getRow(&data[rowOffset], iRow); } /* * Get palette for color mapping. */ if (isMappedWithPalette()) { const CiftiXML& ciftiXML = m_ciftiFile->getCiftiXML(); const PaletteColorMapping* pcm = ciftiXML.getFilePalette(); CaretAssert(pcm); const AString paletteName = pcm->getSelectedPaletteName(); if (paletteName.isEmpty()) { CaretLogSevere("No palette name for coloring matrix chart data."); return false; } EventPaletteGetByName eventPaletteGetName(paletteName); EventManager::get()->sendEvent(eventPaletteGetName.getPointer()); const Palette* palette = eventPaletteGetName.getPalette(); if (palette == NULL) { CaretLogSevere("No palette named " + paletteName + " found for coloring matrix chart data."); return false; } /* * Set up Fast Stats for files that use all data for * statistics and color mapping. * Map "0" will return the file fast statistics */ CiftiMappableDataFile* nonConstMapFile = const_cast(this); const FastStatistics* fileFastStats = nonConstMapFile->getFileFastStatistics(); /* * Color the data. */ const int32_t numRGBA = numberOfData * 4; rgbaOut.resize(numRGBA); NodeAndVoxelColoring::colorScalarsWithPalette(fileFastStats, pcm, palette, &data[0], &data[0], numberOfData, &rgbaOut[0]); return true; } else { CaretAssertMessage(0, "Only palette mapped files supported at this time."); } return false; } ///* ========================================================================== */ /** * Constructor. * * @param ciftiFile * The CIFTI data file * @param fileMapDataType * Type of CIFTI file (matrix or multi-map). * @param readingDirectionForCiftiXML * Reading direction for CIFTI XML access * @param mappingDirectionForCiftiXML * Mapping direction for CIFTI XML access * @param mapIndex * Index of this map. */ CiftiMappableDataFile::MapContent::MapContent(CiftiFile* ciftiFile, const FileMapDataType fileMapDataType, const int32_t readingDirectionForCiftiXML, const int32_t mappingDirectionForCiftiXML, const int32_t mapIndex) : CaretObjectTracksModification(), m_ciftiFile(ciftiFile), m_fileMapDataType(fileMapDataType), m_readingDirectionForCiftiXML(readingDirectionForCiftiXML), m_mappingDirectionForCiftiXML(mappingDirectionForCiftiXML), m_mapIndex(mapIndex) { CaretAssert(ciftiFile); CaretAssert(mapIndex >= 0); m_fastStatistics.grabNew(NULL); m_histogram.grabNew(NULL); m_histogramLimitedValues.grabNew(NULL); m_metadata = NULL; m_paletteColorMapping = NULL; m_labelTable = NULL; m_dataCount = 0; m_rgbaValid = false; m_dataIsMappedWithLabelTable = false; const CiftiXML& ciftiXML = m_ciftiFile->getCiftiXML(); switch (m_mappingDirectionForCiftiXML) { case CiftiXML::ALONG_COLUMN: m_dataCount = ciftiFile->getNumberOfRows(); break; case CiftiXML::ALONG_ROW: m_dataCount = ciftiFile->getNumberOfColumns(); break; case CiftiXML::ALONG_STACK: break; } switch (m_fileMapDataType) { case FILE_MAP_DATA_TYPE_INVALID: CaretAssert(0); break; case FILE_MAP_DATA_TYPE_MATRIX: m_metadata = ciftiXML.getFileMetaData(); m_paletteColorMapping = ciftiXML.getFilePalette(); break; case FILE_MAP_DATA_TYPE_MULTI_MAP: switch (ciftiXML.getMappingType(m_readingDirectionForCiftiXML)) { case CiftiMappingType::BRAIN_MODELS: { //const CiftiBrainModelsMap& map = ciftiXML.getBrainModelsMap(m_readingDirectionForCiftiXML); CaretAssert(0); } break; case CiftiMappingType::LABELS: { const CiftiLabelsMap& map = ciftiXML.getLabelsMap(m_readingDirectionForCiftiXML); m_dataIsMappedWithLabelTable = true; m_metadata = map.getMapMetadata(mapIndex); CaretAssert(m_metadata); m_labelTable = map.getMapLabelTable(mapIndex); CaretAssert(m_labelTable); m_mapName = map.getMapName(m_mapIndex); } break; case CiftiMappingType::PARCELS: { //const CiftiParcelsMap& map = ciftiXML.getParcelsMap(m_readingDirectionForCiftiXML); CaretAssert(0); } break; case CiftiMappingType::SCALARS: { const CiftiScalarsMap& map = ciftiXML.getScalarsMap(m_readingDirectionForCiftiXML); m_metadata = map.getMapMetadata(mapIndex); CaretAssert(m_metadata); m_paletteColorMapping = map.getMapPalette(mapIndex); CaretAssert(m_paletteColorMapping); m_mapName = map.getMapName(m_mapIndex); } break; case CiftiMappingType::SERIES: { /* * Series Data has no map metadata but still need valid metadata instance */ m_metadataForMapsWithNoMetaData.grabNew(new GiftiMetaData()); m_metadata = m_metadataForMapsWithNoMetaData; /* * Series data usings the file's palette */ m_paletteColorMapping = ciftiXML.getFilePalette(); CaretAssert(m_paletteColorMapping); /* * Data series do not have map names but the map name is * a function of units and map index. */ const CiftiSeriesMap& map = ciftiXML.getSeriesMap(m_readingDirectionForCiftiXML); AString unitsSuffix; switch (map.getUnit()) { case CiftiSeriesMap::HERTZ: unitsSuffix = " hertz"; break; case CiftiSeriesMap::METER: unitsSuffix = " meters"; break; case CiftiSeriesMap::RADIAN: unitsSuffix = " radians"; break; case CiftiSeriesMap::SECOND: unitsSuffix = " seconds"; break; } if (unitsSuffix.isEmpty()) { m_mapName = ("Map Index: " + AString::number(m_mapIndex + 1)); } else { const float value = (map.getStart() + (m_mapIndex * map.getStep())); m_mapName = (AString::number(value) + unitsSuffix); } } break; } break; } } /** * Destructor. */ CiftiMappableDataFile::MapContent::~MapContent() { /** * Do not delete these as they point to data in CIFTI XML: * m_labelTable * m_paletteColorMapping * m_metadata; */ } /** * Clear the modification status of this map. */ void CiftiMappableDataFile::MapContent::clearModified() { CaretObjectTracksModification::clearModified(); if (m_labelTable != NULL) { m_labelTable->clearModified(); } m_metadata->clearModified(); if (m_paletteColorMapping != NULL) { m_paletteColorMapping->clearModified(); } } /** * @return Modification status. * * DOES NOT include modification status of palette. */ bool CiftiMappableDataFile::MapContent::isModified() const { if (CaretObjectTracksModification::isModified()) { return true; } if (m_labelTable != NULL) { if (m_labelTable->isModified()) { return true; } } if (m_metadata->isModified()) { return true; } /* DO NOT include palette color mapping status ! */ return false; } /** * @return Name of the map. */ AString CiftiMappableDataFile::MapContent::getName() const { return m_mapName; } /** * Set the name of the map. * * @param name * New name for map. */ void CiftiMappableDataFile::MapContent::setName(const AString& name) { if (name == m_mapName) { return; } m_mapName = name; const CiftiXML& ciftiXML = m_ciftiFile->getCiftiXML(); switch (ciftiXML.getMappingType(m_readingDirectionForCiftiXML)) { case CiftiMappingType::BRAIN_MODELS: { //const CiftiBrainModelsMap& map = ciftiXML.getBrainModelsMap(m_readingDirectionForCiftiXML); CaretAssert(0); } break; case CiftiMappingType::LABELS: { const CiftiLabelsMap& map = ciftiXML.getLabelsMap(m_readingDirectionForCiftiXML); map.setMapName(m_mapIndex, m_mapName); setModified(); } break; case CiftiMappingType::PARCELS: { //const CiftiParcelsMap& map = ciftiXML.getParcelsMap(m_readingDirectionForCiftiXML); // mapName = map.getMapName(m_mapIndex); CaretAssert(0); } break; case CiftiMappingType::SCALARS: { const CiftiScalarsMap& map = ciftiXML.getScalarsMap(m_readingDirectionForCiftiXML); map.setMapName(m_mapIndex, m_mapName); setModified(); } break; case CiftiMappingType::SERIES: /* * Data series do not have map names but the map name is * a function of units and map index. */ break; } } /** * Invalidate the coloring (usually due to palette or data changes). */ void CiftiMappableDataFile::MapContent::updateForChangeInMapData() { m_fastStatistics.grabNew(NULL); m_histogram.grabNew(NULL); m_histogramLimitedValues.grabNew(NULL); m_rgbaValid = false; } /** * @return True if fast statistics is valid, else false. */ bool CiftiMappableDataFile::MapContent::isFastStatisticsValid() const { return (m_fastStatistics != NULL); } /** * Update the Fast Statistics but only when needed. * * @param data * Data for fast statistics. */ void CiftiMappableDataFile::MapContent::updateFastStatistics(const std::vector& data) { if (data.empty()) { m_fastStatistics.grabNew(NULL); } else { if (m_fastStatistics == NULL) { m_fastStatistics.grabNew(new FastStatistics()); m_fastStatistics->update(&data[0], data.size()); } } } /** * @return True if histogram is valid, else false. */ bool CiftiMappableDataFile::MapContent::isHistogramValid() const { return (m_histogram != NULL); } /** * Update the Histogram but only when needed. * * @param data * Data for histogram. */ void CiftiMappableDataFile::MapContent::updateHistogram(const std::vector& data) { if (data.empty()) { m_histogram.grabNew(NULL); } else { if (m_histogram == NULL) { m_histogram.grabNew(new Histogram()); m_histogram->update(&data[0], data.size()); } } } /** * Is limited values histogram valid? Is is valid when it exists and * the limited value parameters have not changed. * * @param mostPositiveValueInclusive * Values more positive than this value are excluded. * @param leastPositiveValueInclusive * Values less positive than this value are excluded. * @param leastNegativeValueInclusive * Values less negative than this value are excluded. * @param mostNegativeValueInclusive * Values more negative than this value are excluded. * @param includeZeroValues * If true zero values (very near zero) are included. * * @return true if valid, else false. */ bool CiftiMappableDataFile::MapContent::isHistogramLimitedValuesValid(const float mostPositiveValueInclusive, const float leastPositiveValueInclusive, const float leastNegativeValueInclusive, const float mostNegativeValueInclusive, const bool includeZeroValues) const { if (m_histogramLimitedValues == NULL) { return false; } else if ((mostPositiveValueInclusive != m_histogramLimitedValuesMostPositiveValueInclusive) || (leastPositiveValueInclusive != m_histogramLimitedValuesLeastPositiveValueInclusive) || (leastNegativeValueInclusive != m_histogramLimitedValuesLeastNegativeValueInclusive) || (mostNegativeValueInclusive != m_histogramLimitedValuesMostNegativeValueInclusive) || (includeZeroValues != m_histogramLimitedValuesIncludeZeroValues)) { return false; } return true; } /** * Update the Histogram for limited values. * * @param data * Data for histogram. * @param mostPositiveValueInclusive * Values more positive than this value are excluded. * @param leastPositiveValueInclusive * Values less positive than this value are excluded. * @param leastNegativeValueInclusive * Values less negative than this value are excluded. * @param mostNegativeValueInclusive * Values more negative than this value are excluded. * @param includeZeroValues * If true zero values (very near zero) are included. */ void CiftiMappableDataFile::MapContent::updateHistogramLimitedValues(const std::vector& data, const float mostPositiveValueInclusive, const float leastPositiveValueInclusive, const float leastNegativeValueInclusive, const float mostNegativeValueInclusive, const bool includeZeroValues) { if (data.empty()) { m_histogramLimitedValues.grabNew(NULL); } else { if (m_histogramLimitedValues == NULL) { m_histogramLimitedValues.grabNew(new Histogram()); } m_histogramLimitedValues->update(&data[0], data.size(), mostPositiveValueInclusive, leastPositiveValueInclusive, leastNegativeValueInclusive, mostNegativeValueInclusive, includeZeroValues); m_histogramLimitedValuesMostPositiveValueInclusive = mostPositiveValueInclusive; m_histogramLimitedValuesLeastPositiveValueInclusive = leastPositiveValueInclusive; m_histogramLimitedValuesLeastNegativeValueInclusive = leastNegativeValueInclusive; m_histogramLimitedValuesMostNegativeValueInclusive = mostNegativeValueInclusive; m_histogramLimitedValuesIncludeZeroValues = includeZeroValues; } } /** * Update coloring for this map. If the paletteFile is NOT NULL, * color using a palette; otherwise, color with label table. * * @param data * Data contained in the map. * @param paletteFile * File containing the palettes. * @param fastStatistics * Fast statistics used for palette coloring. While map content contains * a fast statistics member, it is calculated on the data within the map. * However, some files need the statistics calculated on the entire file * so that a particular data value from one map is colored exactly the * same as the particular data value in another map (the user may have * a min/max coloring selected and the two maps may a have different * min/max values). */ void CiftiMappableDataFile::MapContent::updateColoring(const std::vector& data, const PaletteFile* paletteFile, const FastStatistics* fastStatistics) { if (data.empty()) { return; } if (m_rgbaValid) { return; } if (m_dataCount != static_cast(data.size())) { m_dataCount = static_cast(data.size()); } // CaretAssert(m_dataCount == static_cast(data.size())); const uint64_t rgbaCount = m_dataCount * 4; if (m_rgba.size() != rgbaCount) { m_rgba.resize(rgbaCount, 0); } if (m_dataIsMappedWithLabelTable) { NodeAndVoxelColoring::colorIndicesWithLabelTable(m_labelTable, &data[0], data.size(), &m_rgba[0]); } else if (paletteFile != NULL) { CaretAssert(m_paletteColorMapping); const AString paletteName = m_paletteColorMapping->getSelectedPaletteName(); const Palette* palette = paletteFile->getPaletteByName(paletteName); if ((palette != NULL) && (fastStatistics != NULL)) { NodeAndVoxelColoring::colorScalarsWithPalette(fastStatistics, m_paletteColorMapping, palette, &data[0], &data[0], m_dataCount, &m_rgba[0]); } else { std::fill(m_rgba.begin(), m_rgba.end(), 0); } } else { const AString msg("NULL palette for coloring scalar data."); CaretAssertMessage(0, msg); CaretLogSevere(msg); } m_rgbaValid = true; } workbench-1.1.1/src/Files/CiftiMappableDataFile.h000066400000000000000000001013241255417355300216030ustar00rootroot00000000000000#ifndef __CIFTI_MAPPABLE_DATA_FILE_H__ #define __CIFTI_MAPPABLE_DATA_FILE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretMappableDataFile.h" #include "CaretPointer.h" #include "CaretObjectTracksModification.h" #include "CiftiMappingType.h" #include "CiftiXMLElements.h" #include "DisplayGroupEnum.h" #include "VolumeMappableInterface.h" #include namespace caret { class ChartData; class ChartDataCartesian; class CiftiFile; class CiftiParcelsMap; class CiftiXML; class FastStatistics; class GroupAndNameHierarchyModel; class Histogram; class SparseVolumeIndexer; class CiftiMappableDataFile : public CaretMappableDataFile, public VolumeMappableInterface { protected: /** * Method for accessing data with CIFTI */ enum DataAccessMethod { /** * Invalid data access method. */ DATA_ACCESS_METHOD_INVALID, /** * No access to data */ DATA_ACCESS_NONE, /** * The data is accessed from CiftiFile using ROW Methods * and from the XML using ALONG_COLUMN */ DATA_ACCESS_FILE_ROWS_OR_XML_ALONG_COLUMN, /** * Use ALONG_ROW to access CIFTI data * which means one is accessing COLUMNS of data */ DATA_ACCESS_FILE_COLUMNS_OR_XML_ALONG_ROW }; /** * Method for color mapping a map */ enum ColorMappingMethod { /** * Invalid color mapping method. */ COLOR_MAPPING_METHOD_INVALID, /** * Color data using a label table */ COLOR_MAPPING_METHOD_LABEL_TABLE, /** * Color data using a color palette */ COLOR_MAPPING_METHOD_PALETTE }; /** * Source of palette color mapping. * Some CIFTI files provide one palette color mapping per file. * Others have one palette color mapping for each map in the file. */ enum PaletteColorMappingSourceType { PALETTE_COLOR_MAPPING_SOURCE_INVALID, /** * Use file's palette color mapping. */ PALETTE_COLOR_MAPPING_SOURCE_FROM_FILE, /** * Use map's palette color mapping. */ PALETTE_COLOR_MAPPING_SOURCE_FROM_MAP }; /** * Type of map data in the file. */ enum FileMapDataType { /** * Invalid file map data type */ FILE_MAP_DATA_TYPE_INVALID, /** * The file contains a connectivity matrix. There is only * 'one map' and the data for the map is selectively read * and replaced based upon user actions. */ FILE_MAP_DATA_TYPE_MATRIX, /** * The file contains one or more maps and all maps are loaded * when the file is read. */ FILE_MAP_DATA_TYPE_MULTI_MAP }; /** How to read the file */ enum FileDataReadingType { /** Read all data in the file */ FILE_READ_DATA_ALL, /** Open the file but only read data as needed */ FILE_READ_DATA_AS_NEEDED }; CiftiMappableDataFile(const DataFileTypeEnum::Enum dataFileType); public: virtual ~CiftiMappableDataFile(); static CiftiMappableDataFile* newInstanceForCiftiFileTypeAndSurface(const DataFileTypeEnum::Enum ciftiFileType, const StructureEnum::Enum structure, const int32_t numberOfNodes, AString& errorMessageOut); virtual void addToDataFileContentInformation(DataFileContentInformation& dataFileInformation); static void addCiftiXmlToDataFileContentInformation(DataFileContentInformation& dataFileInformation, const CiftiXML& ciftiXML); static void getDataFileContentInformationForGenericCiftiFile(const AString& filename, DataFileContentInformation& dataFileInformation); virtual void clear(); virtual bool isEmpty() const; virtual bool isMappableToSurfaceStructure(const StructureEnum::Enum structure) const; virtual StructureEnum::Enum getStructure() const; virtual void setStructure(const StructureEnum::Enum structure); virtual GiftiMetaData* getFileMetaData(); virtual const GiftiMetaData* getFileMetaData() const; virtual void setPreferOnDiskReading(const bool& prefer); virtual void readFile(const AString& ciftiMapFileName); virtual void writeFile(const AString& filename); virtual bool isSurfaceMappable() const; virtual bool isVolumeMappable() const; virtual int32_t getNumberOfMaps() const; virtual bool hasMapAttributes() const; virtual AString getMapName(const int32_t mapIndex) const; virtual void setMapName(const int32_t mapIndex, const AString& mapName); virtual const GiftiMetaData* getMapMetaData(const int32_t mapIndex) const; virtual GiftiMetaData* getMapMetaData(const int32_t mapIndex); virtual AString getMapUniqueID(const int32_t mapIndex) const; virtual bool isMappedWithPalette() const; virtual const FastStatistics* getMapFastStatistics(const int32_t mapIndex); virtual const Histogram* getMapHistogram(const int32_t mapIndex); virtual const Histogram* getMapHistogram(const int32_t mapIndex, const float mostPositiveValueInclusive, const float leastPositiveValueInclusive, const float leastNegativeValueInclusive, const float mostNegativeValueInclusive, const bool includeZeroValues); virtual void getPaletteNormalizationModesSupported(std::vector& modesSupportedOut); virtual int64_t getDataSizeUncompressedInBytes() const; virtual const FastStatistics* getFileFastStatistics(); virtual const Histogram* getFileHistogram(); virtual const Histogram* getFileHistogram(const float mostPositiveValueInclusive, const float leastPositiveValueInclusive, const float leastNegativeValueInclusive, const float mostNegativeValueInclusive, const bool includeZeroValues); virtual PaletteColorMapping* getMapPaletteColorMapping(const int32_t mapIndex); virtual const PaletteColorMapping* getMapPaletteColorMapping(const int32_t mapIndex) const; const CiftiParcelsMap* getCiftiParcelsMapForBrainordinateMapping() const; const CiftiParcelsMap* getCiftiParcelsMapForLoading() const; const CiftiParcelsMap* getCiftiParcelsMapForDirection(const int direction) const; virtual bool isMappedWithLabelTable() const; virtual GiftiLabelTable* getMapLabelTable(const int32_t mapIndex); virtual const GiftiLabelTable* getMapLabelTable(const int32_t mapIndex) const; virtual void updateScalarColoringForAllMaps(const PaletteFile* paletteFile); virtual void updateScalarColoringForMap(const int32_t mapIndex, const PaletteFile* paletteFile); virtual bool isMapColoringValid(const int32_t mapIndex) const; virtual void getDimensions(int64_t& dimOut1, int64_t& dimOut2, int64_t& dimOut3, int64_t& dimTimeOut, int64_t& numComponents) const; virtual void getDimensions(std::vector& dimsOut) const; virtual void getMapDimensions(std::vector &dim) const; virtual const int64_t& getNumberOfComponents() const; virtual void indexToSpace(const float& indexIn1, const float& indexIn2, const float& indexIn3, float& coordOut1, float& coordOut2, float& coordOut3) const; virtual void indexToSpace(const float& indexIn1, const float& indexIn2, const float& indexIn3, float* coordOut) const; virtual void indexToSpace(const int64_t* indexIn, float* coordOut) const; virtual void enclosingVoxel(const float& coordIn1, const float& coordIn2, const float& coordIn3, int64_t& indexOut1, int64_t& indexOut2, int64_t& indexOut3) const; virtual bool indexValid(const int64_t& indexIn1, const int64_t& indexIn2, const int64_t& indexIn3, const int64_t brickIndex = 0, const int64_t component = 0) const; virtual void getVoxelSpaceBoundingBox(BoundingBox& boundingBoxOut) const; virtual int64_t getVoxelColorsForSliceInMap(const PaletteFile* paletteFile, const int32_t mapIndex, const VolumeSliceViewPlaneEnum::Enum slicePlane, const int64_t sliceIndex, const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, uint8_t* rgbaOut) const; virtual int64_t getVoxelColorsForSubSliceInMap(const PaletteFile* paletteFile, const int32_t mapIndex, const VolumeSliceViewPlaneEnum::Enum slicePlane, const int64_t sliceIndex, const int64_t firstCornerVoxelIndex[3], const int64_t lastCornerVoxelIndex[3], const int64_t voxelCountIJK[3], const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, uint8_t* rgbaOut) const; virtual void getVoxelColorInMap(const PaletteFile* paletteFile, const int64_t indexIn1, const int64_t indexIn2, const int64_t indexIn3, const int64_t mapIndex, const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, uint8_t rgbaOut[4]) const; virtual void getVoxelColorInMapForLabelData(const PaletteFile* paletteFile, const std::vector& dataForMap, const int64_t indexIn1, const int64_t indexIn2, const int64_t indexIn3, const int64_t mapIndex, const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, uint8_t rgbaOut[4]) const; virtual bool getMapVolumeVoxelValue(const int32_t mapIndex, const float xyz[3], int64_t ijkOut[3], float& numericalValueOut, bool& numericalValueOutValid, AString& textValueOut) const; virtual bool getMapVolumeVoxelValues(const std::vector mapIndices, const float xyz[3], int64_t ijkOut[3], std::vector& numericalValuesOut, std::vector& numericalValuesOutValid, AString& textValueOut) const; int64_t getMapDataOffsetForVoxelAtCoordinate(const float coordinate[3], const int32_t mapIndex) const; virtual float getVoxelValue(const float* coordinateIn, bool* validOut = NULL, const int64_t mapIndex = 0, const int64_t component = 0) const; virtual float getVoxelValue(const float coordinateX, const float coordinateY, const float coordinateZ, bool* validOut = NULL, const int64_t mapIndex = 0, const int64_t component = 0) const; virtual bool getVolumeVoxelIdentificationForMaps(const std::vector& mapIndices, const float xyz[3], int64_t ijkOut[3], AString& textOut) const; std::vector getUniqueLabelKeysUsedInMap(const int32_t mapIndex) const; GroupAndNameHierarchyModel* getGroupAndNameHierarchyModel(); const GroupAndNameHierarchyModel* getGroupAndNameHierarchyModel() const; virtual bool getMapSurfaceNodeValue(const int32_t mapIndex, const StructureEnum::Enum structure, const int nodeIndex, const int32_t numberOfNodes, float& numericalValueOut, bool& numericalValueOutValid, AString& textValueOut) const; virtual bool getMapSurfaceNodeValues(const std::vector& mapIndices, const StructureEnum::Enum structure, const int nodeIndex, const int32_t numberOfNodes, std::vector& numericalValuesOut, std::vector& numericalValuesOutValid, AString& textValueOut) const; virtual bool getSurfaceNodeIdentificationForMaps(const std::vector& mapIndices, const StructureEnum::Enum structure, const int nodeIndex, const int32_t numberOfNodes, AString& textOut) const; int32_t getMappingSurfaceNumberOfNodes(const StructureEnum::Enum structure) const; bool getSeriesDataForSurfaceNode(const StructureEnum::Enum structure, const int32_t nodeIndex, std::vector& seriesDataOut) const; bool getSeriesDataForVoxelAtCoordinate(const float xyz[3], std::vector& seriesDataOut) const; virtual bool getMapSurfaceNodeColoring(const PaletteFile* paletteFile, const int32_t mapIndex, const StructureEnum::Enum structure, float* surfaceRGBAOut, float* dataValuesOut, const int32_t surfaceNumberOfNodes); virtual void clearModified(); virtual bool isModifiedExcludingPaletteColorMapping() const; virtual NiftiTimeUnitsEnum::Enum getMapIntervalUnits() const; virtual void getMapIntervalStartAndStep(float& firstMapUnitsValueOut, float& mapIntervalStepValueOut) const; virtual bool getDataRangeFromAllMaps(float& dataRangeMinimumOut, float& dataRangeMaximumOut) const; bool getMapDataForSurface(const int32_t mapIndex, const StructureEnum::Enum structure, std::vector& surfaceMapData, std::vector* roiData = NULL) const; void setMapDataForSurface(const int32_t mapIndex, const StructureEnum::Enum structure, const std::vector surfaceMapData); bool getParcelNodesElementForSelectedParcel(std::set &parcelNodesOut, const StructureEnum::Enum &structure, const int64_t &selectionIndex) const; void invalidateColoringInAllMaps(); void getBrainordinateFromRowIndex(const int64_t rowIndex, StructureEnum::Enum& surfaceStructureOut, int32_t& surfaceNodeIndexOut, int32_t& surfaceNumberOfNodesOut, bool& surfaceNodeValidOut, int64_t voxelIJKOut[3], float voxelXYZOut[3], bool& voxelValidOut) const; private: CiftiMappableDataFile(const CiftiMappableDataFile&); CiftiMappableDataFile& operator=(const CiftiMappableDataFile&); public: virtual void getMapData(const int32_t mapIndex, std::vector& dataOut) const; virtual void setMapData(const int32_t mapIndex, const std::vector& data); virtual void getMatrixRGBA(std::vector& rgba, PaletteFile *paletteFile); virtual void getFileData(std::vector& data) const; const CiftiFile* getCiftiFile() const { return m_ciftiFile; } protected: virtual bool getParcelLabelMapSurfaceNodeValue(const int32_t mapIndex, const StructureEnum::Enum structure, const int nodeIndex, const int32_t numberOfNodes, float& numericalValueOut, bool& numericalValueOutValid, AString& textValueOut) const; void updateForChangeInMapDataWithMapIndex(const int32_t mapIndex); ChartDataCartesian* helpLoadChartDataForSurfaceNode(const StructureEnum::Enum structure, const int32_t nodeIndex); ChartDataCartesian* helpLoadChartDataForSurfaceNodeAverage(const StructureEnum::Enum structure, const std::vector& nodeIndices); ChartDataCartesian* helpLoadChartDataForVoxelAtCoordinate(const float xyz[3]); void helpMapFileGetMatrixDimensions(int32_t& numberOfRowsOut, int32_t& numberOfColumnsOut) const; bool helpMapFileLoadChartDataMatrixRGBA(int32_t& numberOfRowsOut, int32_t& numberOfColumnsOut, const std::vector& rowIndicesIn, std::vector& rgbaOut) const; bool helpMatrixFileLoadChartDataMatrixRGBA(int32_t& numberOfRowsOut, int32_t& numberOfColumnsOut, const std::vector& rowIndicesIn, std::vector& rgbaOut) const; // bool helpLoadChartDataMatrixRGBA(int32_t& numberOfRowsOut, // int32_t& numberOfColumnsOut, // std::vector& rgbaOut) const; // // bool helpLoadChartDataMatrixRGBAWithRowIndicese(int32_t& numberOfRowsOut, // int32_t& numberOfColumnsOut, // const std::vector& rowIndices, // std::vector& rgbaOut) const; private: class MapContent : public CaretObjectTracksModification { public: MapContent(CiftiFile* ciftiFile, const FileMapDataType fileMapDataType, const int32_t readingDirectionForCiftiXML, const int32_t mappingDirectionForCiftiXML, const int32_t mapIndex); ~MapContent(); virtual void clearModified(); virtual bool isModified() const; void updateForChangeInMapData(); void updateColoring(const std::vector& data, const PaletteFile* paletteFile, const FastStatistics* fastStatistics); bool isFastStatisticsValid() const; void updateFastStatistics(const std::vector& data); bool isHistogramValid() const; void updateHistogram(const std::vector& data); bool isHistogramLimitedValuesValid(const float mostPositiveValueInclusive, const float leastPositiveValueInclusive, const float leastNegativeValueInclusive, const float mostNegativeValueInclusive, const bool includeZeroValues) const; void updateHistogramLimitedValues(const std::vector& data, const float mostPositiveValueInclusive, const float leastPositiveValueInclusive, const float leastNegativeValueInclusive, const float mostNegativeValueInclusive, const bool includeZeroValues); AString getName() const; void setName(const AString& name); /** The CIFTI file pointer */ CiftiFile *m_ciftiFile; /** Maps or Matrix file */ const FileMapDataType m_fileMapDataType; /** Direction for obtaining reading information (CiftiXML::ALONG_ROW, etc) */ const int32_t m_readingDirectionForCiftiXML; /** Direction for obtaining mapping information (CiftiXML::ALONG_ROW, etc) */ const int32_t m_mappingDirectionForCiftiXML; /** Index of this map */ const int32_t m_mapIndex; /** Name of the map */ AString m_mapName; /** Count of data elements in map. */ int64_t m_dataCount; /** Metadata for the map. Points to data in CiftiFile so DO NOT delete */ GiftiMetaData* m_metadata; /** Palette color mapping for map. Points to data in CiftiFile so DO NOT delete */ PaletteColorMapping* m_paletteColorMapping; /** Label table for map. Points to data in CiftiFile so DO NOT delete */ GiftiLabelTable* m_labelTable; /** Indicates data is mapped with a lable table */ bool m_dataIsMappedWithLabelTable; /** RGBA coloring for map */ std::vector m_rgba; /** RGBA coloring is valid */ bool m_rgbaValid; /** fast statistics for map */ CaretPointer m_fastStatistics; /** histogram for all of map map */ CaretPointer m_histogram; /** histogram for limited values from map */ CaretPointer m_histogramLimitedValues; float m_histogramLimitedValuesMostPositiveValueInclusive; float m_histogramLimitedValuesLeastPositiveValueInclusive; float m_histogramLimitedValuesLeastNegativeValueInclusive; float m_histogramLimitedValuesMostNegativeValueInclusive; bool m_histogramLimitedValuesIncludeZeroValues; private: /** Name of map */ AString m_name; /** * For maps that do not have metadata, a metadata instance * is still needed even though it essentially does nothing. */ CaretPointer m_metadataForMapsWithNoMetaData; }; void clearPrivate(); protected: void initializeAfterReading(const AString& filename); void validateMappingTypes(const AString& filename); void resetDataLoadingMembers(); void validateKeysAndLabels() const; virtual void validateAfterFileReading(); virtual void saveFileDataToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass); virtual void restoreFileDataFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); bool getSurfaceDataIndicesForMappingToBrainordinates(const StructureEnum::Enum structure, const int64_t surfaceNumberOfNodes, std::vector& dataIndicesForNodes) const; void setupCiftiReadingMappingDirection(); static AString mappingTypeToName(const CiftiMappingType::MappingType mappingType); /** * Point to the CIFTI file object. */ CaretPointer m_ciftiFile; /** * How to read data from the file */ FileDataReadingType m_fileDataReadingType; /** * Method used when reading data from the file. */ DataAccessMethod m_dataReadingAccessMethod; /** Direction for obtaining mapping information (CiftiXML::ALONG_ROW, etc) */ int32_t m_dataReadingDirectionForCiftiXML; /** * Method used when mapping loaded data to brainordinates. */ DataAccessMethod m_dataMappingAccessMethod; /** Direction for obtaining mapping information (CiftiXML::ALONG_ROW, etc) */ int32_t m_dataMappingDirectionForCiftiXML; /** * Method used when mapping data to colors (LabelTable or Palette). */ ColorMappingMethod m_colorMappingMethod; /** * Source of color palette (file or per map) */ PaletteColorMappingSourceType m_paletteColorMappingSource; /* * Supported palette normalization modes */ std::vector m_paletteNormalizationModesSupported; /** * Type of data in the file's map(s). */ FileMapDataType m_fileMapDataType; /** Contains data related to each map */ std::vector m_mapContent; /** True if the file contains surface data */ bool m_containsSurfaceData; /** True if the file contains surface data */ bool m_containsVolumeData; float m_mappingTimeStart; float m_mappingTimeStep; NiftiTimeUnitsEnum::Enum m_mappingTimeUnits; /** Fast statistics used when statistics computed on all data in file */ CaretPointer m_fileFastStatistics; /** Histogram used when statistics computed on all data in file */ CaretPointer m_fileHistogram; /** Histogram with limited values used when statistics computed on all data in file */ CaretPointer m_fileHistorgramLimitedValues; float m_fileHistogramLimitedValuesMostPositiveValueInclusive; float m_fileHistogramLimitedValuesLeastPositiveValueInclusive; float m_fileHistogramLimitedValuesLeastNegativeValueInclusive; float m_fileHistogramLimitedValuesMostNegativeValueInclusive; bool m_fileHistogramLimitedValuesIncludeZeroValues; /** Fast conversion of IJK to data offset */ CaretPointer m_voxelIndicesToOffset; /** Holds class and name hierarchy used for display selection */ mutable CaretPointer m_classNameHierarchy; /** force an update of the class and name hierarchy */ mutable bool m_forceUpdateOfGroupAndNameHierarchy; static const int32_t S_CIFTI_XML_ALONG_INVALID; // std::vector m_ciftiDimensions; // ADD_NEW_MEMBERS_HERE }; #ifdef __CIFTI_MAPPABLE_DATA_FILE_DECLARE__ const int32_t CiftiMappableDataFile::S_CIFTI_XML_ALONG_INVALID = -1; #endif // __CIFTI_MAPPABLE_DATA_FILE_DECLARE__ } // namespace #endif //__CIFTI_MAPPABLE_DATA_FILE_H__ workbench-1.1.1/src/Files/CiftiParcelColoringModeEnum.cxx000066400000000000000000000262071255417355300234060ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __CIFTI_PARCEL_COLORING_MODE_ENUM_DECLARE__ #include "CiftiParcelColoringModeEnum.h" #undef __CIFTI_PARCEL_COLORING_MODE_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::CiftiParcelColoringModeEnum * \brief Enumerated type for selected parcel coloring. * * Enumerated type for coloring of selected CIFTI parcel * * Using this enumerated type in the GUI with an EnumComboBoxTemplate * * Header File (.h) * Forward declare the data type: * class EnumComboBoxTemplate; * * Declare the member: * EnumComboBoxTemplate* m_ciftiParcelColoringModeEnumComboBox; * * Declare a slot that is called when user changes selection * private slots: * void ciftiParcelColoringModeEnumComboBoxItemActivated(); * * Implementation File (.cxx) * Include the header files * #include "EnumComboBoxTemplate.h" * #include "CiftiParcelColoringModeEnum.h" * * Instatiate: * m_ciftiParcelColoringModeEnumComboBox = new EnumComboBoxTemplate(this); * m_ciftiParcelColoringModeEnumComboBox->setup(); * * Get notified when the user changes the selection: * QObject::connect(m_ciftiParcelColoringModeEnumComboBox, SIGNAL(itemActivated()), * this, SLOT(ciftiParcelColoringModeEnumComboBoxItemActivated())); * * Update the selection: * m_ciftiParcelColoringModeEnumComboBox->setSelectedItem(NEW_VALUE); * * Read the selection: * const CiftiParcelColoringModeEnum::Enum VARIABLE = m_ciftiParcelColoringModeEnumComboBox->getSelectedItem(); * */ /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ CiftiParcelColoringModeEnum::CiftiParcelColoringModeEnum(const Enum enumValue, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCodeCounter++; this->name = name; this->guiName = guiName; } /** * Destructor. */ CiftiParcelColoringModeEnum::~CiftiParcelColoringModeEnum() { } /** * Initialize the enumerated metadata. */ void CiftiParcelColoringModeEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(CiftiParcelColoringModeEnum(CIFTI_PARCEL_COLORING_OFF, "CIFTI_PARCEL_COLORING_OFF", "Off")); enumData.push_back(CiftiParcelColoringModeEnum(CIFTI_PARCEL_COLORING_FILL, "CIFTI_PARCEL_COLORING_FILL", "Fill")); enumData.push_back(CiftiParcelColoringModeEnum(CIFTI_PARCEL_COLORING_OUTLINE, "CIFTI_PARCEL_COLORING_OUTLINE", "Outline")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const CiftiParcelColoringModeEnum* CiftiParcelColoringModeEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const CiftiParcelColoringModeEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString CiftiParcelColoringModeEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const CiftiParcelColoringModeEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ CiftiParcelColoringModeEnum::Enum CiftiParcelColoringModeEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = CiftiParcelColoringModeEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const CiftiParcelColoringModeEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type CiftiParcelColoringModeEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString CiftiParcelColoringModeEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const CiftiParcelColoringModeEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ CiftiParcelColoringModeEnum::Enum CiftiParcelColoringModeEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = CiftiParcelColoringModeEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const CiftiParcelColoringModeEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type CiftiParcelColoringModeEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t CiftiParcelColoringModeEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const CiftiParcelColoringModeEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ CiftiParcelColoringModeEnum::Enum CiftiParcelColoringModeEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = CiftiParcelColoringModeEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const CiftiParcelColoringModeEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type CiftiParcelColoringModeEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void CiftiParcelColoringModeEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void CiftiParcelColoringModeEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(CiftiParcelColoringModeEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void CiftiParcelColoringModeEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(CiftiParcelColoringModeEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } workbench-1.1.1/src/Files/CiftiParcelColoringModeEnum.h000066400000000000000000000065011255417355300230260ustar00rootroot00000000000000#ifndef __CIFTI_PARCEL_COLORING_MODE_ENUM_H__ #define __CIFTI_PARCEL_COLORING_MODE_ENUM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class CiftiParcelColoringModeEnum { public: /** * Enumerated values. */ enum Enum { /** off (no special coloring) */ CIFTI_PARCEL_COLORING_OFF, /** fill with user defined color */ CIFTI_PARCEL_COLORING_FILL, /** outline with user defined color */ CIFTI_PARCEL_COLORING_OUTLINE }; ~CiftiParcelColoringModeEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); private: CiftiParcelColoringModeEnum(const Enum enumValue, const AString& name, const AString& guiName); static const CiftiParcelColoringModeEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; #ifdef __CIFTI_PARCEL_COLORING_MODE_ENUM_DECLARE__ std::vector CiftiParcelColoringModeEnum::enumData; bool CiftiParcelColoringModeEnum::initializedFlag = false; int32_t CiftiParcelColoringModeEnum::integerCodeCounter = 0; #endif // __CIFTI_PARCEL_COLORING_MODE_ENUM_DECLARE__ } // namespace #endif //__CIFTI_PARCEL_COLORING_MODE_ENUM_H__ workbench-1.1.1/src/Files/CiftiParcelLabelFile.cxx000066400000000000000000000614121255417355300220140ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CIFTI_PARCEL_LABEL_FILE_DECLARE__ #include "CiftiParcelLabelFile.h" #undef __CIFTI_PARCEL_LABEL_FILE_DECLARE__ #include "CaretAssert.h" #include "CaretLogger.h" #include "ChartMatrixDisplayProperties.h" #include "CiftiFile.h" #include "CiftiParcelReordering.h" #include "CiftiParcelReorderingModel.h" #include "GiftiLabel.h" #include "GiftiLabelTable.h" #include "NodeAndVoxelColoring.h" #include "SceneClass.h" #include "SceneClassAssistant.h" using namespace caret; /** * \class caret::CiftiParcelLabelFile * \brief CIFTI Parcellated Label File * \ingroup Files */ /** * Constructor. */ CiftiParcelLabelFile::CiftiParcelLabelFile() : CiftiMappableDataFile(DataFileTypeEnum::CONNECTIVITY_PARCEL_LABEL) { for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_chartingEnabledForTab[i] = false; m_chartMatrixDisplayProperties[i] = new ChartMatrixDisplayProperties(); } m_selectedParcelColoringMode = CiftiParcelColoringModeEnum::CIFTI_PARCEL_COLORING_OUTLINE; m_selectedParcelColor = CaretColorEnum::WHITE; m_parcelReorderingModel = new CiftiParcelReorderingModel(this); m_sceneAssistant = new SceneClassAssistant(); m_sceneAssistant->add("m_selectedParcelColoringMode", &m_selectedParcelColoringMode); m_sceneAssistant->add("m_selectedParcelColor", &m_selectedParcelColor); m_sceneAssistant->add("m_parcelReorderingModel", "CiftiParcelReorderingModel", m_parcelReorderingModel); } /** * Destructor. */ CiftiParcelLabelFile::~CiftiParcelLabelFile() { for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { delete m_chartMatrixDisplayProperties[i]; } delete m_parcelReorderingModel; delete m_sceneAssistant; } /** * Get the matrix dimensions. * * @param numberOfRowsOut * Number of rows in the matrix. * @param numberOfColumnsOut * Number of rows in the matrix. */ void CiftiParcelLabelFile::getMatrixDimensions(int32_t& numberOfRowsOut, int32_t& numberOfColumnsOut) const { helpMapFileGetMatrixDimensions(numberOfRowsOut, numberOfColumnsOut); } /** * Get the matrix RGBA coloring for this matrix data creator. * * @param numberOfRowsOut * Number of rows in the coloring matrix. * @param numberOfColumnsOut * Number of rows in the coloring matrix. * @param rgbaOut * RGBA coloring output with number of elements * (numberOfRowsOut * numberOfColumnsOut * 4). * @return * True if data output data is valid, else false. */ bool CiftiParcelLabelFile::getMatrixDataRGBA(int32_t& numberOfRowsOut, int32_t& numberOfColumnsOut, std::vector& rgbaOut) const { CiftiParcelLabelFile* parcelLabelFile = NULL; int32_t parcelLabelFileMapIndex = -1; bool enabled = false; std::vector parcelLabelFiles; getSelectedParcelLabelFileAndMapForReordering(parcelLabelFiles, parcelLabelFile, parcelLabelFileMapIndex, enabled); std::vector rowIndices; if (enabled) { const CiftiParcelReordering* parcelReordering = getParcelReordering(parcelLabelFile, parcelLabelFileMapIndex); if (parcelReordering != NULL) { rowIndices = parcelReordering->getReorderedParcelIndices(); } } return helpMapFileLoadChartDataMatrixRGBA(numberOfRowsOut, numberOfColumnsOut, rowIndices, rgbaOut); // CaretAssert(m_ciftiFile); // // /* // * Dimensions of matrix. // */ // numberOfRowsOut = m_ciftiFile->getNumberOfRows(); // numberOfColumnsOut = m_ciftiFile->getNumberOfColumns(); // const int32_t numberOfData = numberOfRowsOut * numberOfColumnsOut; // if (numberOfData <= 0) { // return false; // } // // /* // * Allocate rgba output // */ // const int32_t numberOfRgba = numberOfData * 4; // rgbaOut.resize(numberOfRgba); // // /* // * Get each column, color it using its label table, and then // * add the column's coloring into the output coloring. // */ // std::vector columnData(numberOfRowsOut); // std::vector columnRGBA(numberOfRowsOut * 4); // for (int32_t iCol = 0; iCol < numberOfColumnsOut; iCol++) { // CaretAssertVectorIndex(m_mapContent, iCol); // m_ciftiFile->getColumn(&columnData[0], // iCol); // const GiftiLabelTable* labelTable = getMapLabelTable(iCol); // NodeAndVoxelColoring::colorIndicesWithLabelTable(labelTable, // &columnData[0], // numberOfRowsOut, // &columnRGBA[0]); // // for (int32_t iRow = 0; iRow < numberOfRowsOut; iRow++) { // const int32_t rgbaOffset = (((iRow * numberOfColumnsOut) // + iCol) * 4); // CaretAssertVectorIndex(rgbaOut, rgbaOffset + 3); // const int32_t columnRgbaOffset = (iRow * 4); // CaretAssertVectorIndex(columnRGBA, columnRgbaOffset + 3); // rgbaOut[rgbaOffset] = columnRGBA[columnRgbaOffset]; // rgbaOut[rgbaOffset+1] = columnRGBA[columnRgbaOffset+1]; // rgbaOut[rgbaOffset+2] = columnRGBA[columnRgbaOffset+2]; // rgbaOut[rgbaOffset+3] = columnRGBA[columnRgbaOffset+3]; // } // } // // return true; } /** * Get the value, row name, and column name for a cell in the matrix. * * @param rowIndex * The row index. * @param columnIndex * The column index. * @param cellValueOut * Output containing value in the cell. * @param rowNameOut * Name of row corresponding to row index. * @param columnNameOut * Name of column corresponding to column index. * @return * True if the output values are valid (valid row/column indices). */ bool CiftiParcelLabelFile::getMatrixCellAttributes(const int32_t rowIndex, const int32_t columnIndex, AString& cellValueOut, AString& rowNameOut, AString& columnNameOut) const { if ((rowIndex >= 0) && (rowIndex < m_ciftiFile->getNumberOfRows()) && (columnIndex >= 0) && (columnIndex < m_ciftiFile->getNumberOfColumns())) { const CiftiXML& xml = m_ciftiFile->getCiftiXML(); const std::vector& rowsParcelsMap = xml.getParcelsMap(CiftiXML::ALONG_COLUMN).getParcels(); CaretAssertVectorIndex(rowsParcelsMap, rowIndex); rowNameOut = rowsParcelsMap[rowIndex].m_name; CaretAssertVectorIndex(m_mapContent, columnIndex); columnNameOut = getMapName(columnIndex); const int32_t numberOfElementsInRow = m_ciftiFile->getNumberOfColumns(); std::vector rowData(numberOfElementsInRow); m_ciftiFile->getRow(&rowData[0], rowIndex); CaretAssertVectorIndex(rowData, columnIndex); const int32_t labelKey = rowData[columnIndex]; const GiftiLabel* label = getMapLabelTable(columnIndex)->getLabel(labelKey); if (label != NULL) { cellValueOut = ("(key=" + AString::number(labelKey) + ")" + label->getName()); } else { cellValueOut = ("Invalid Key=" + AString::number(labelKey)); } return true; } return false; } /** * @return Is charting enabled for this file? */ bool CiftiParcelLabelFile::isMatrixChartingEnabled(const int32_t tabIndex) const { CaretAssertArrayIndex(m_chartingEnabledForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_chartingEnabledForTab[tabIndex]; } /** * @return Return true if the file's current state supports * charting data, else false. Typically a brainordinate file * is chartable if it contains more than one map. */ bool CiftiParcelLabelFile::isMatrixChartingSupported() const { return true; } /** * Set charting enabled for this file. * * @param enabled * New status for charting enabled. */ void CiftiParcelLabelFile::setMatrixChartingEnabled(const int32_t tabIndex, const bool enabled) { CaretAssertArrayIndex(m_chartingEnabledForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_chartingEnabledForTab[tabIndex] = enabled; } /** * Get chart data types supported by the file. * * @param chartDataTypesOut * Chart types supported by this file. */ void CiftiParcelLabelFile::getSupportedMatrixChartDataTypes(std::vector& chartDataTypesOut) const { chartDataTypesOut.clear(); chartDataTypesOut.push_back(ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_LAYER); } /** * @return Chart matrix display properties (const method). */ const ChartMatrixDisplayProperties* CiftiParcelLabelFile::getChartMatrixDisplayProperties(const int32_t tabIndex) const { CaretAssertArrayIndex(m_chartMatrixDisplayProperties, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_chartMatrixDisplayProperties[tabIndex]; } /** * @return Chart matrix display properties. */ ChartMatrixDisplayProperties* CiftiParcelLabelFile::getChartMatrixDisplayProperties(const int32_t tabIndex) { CaretAssertArrayIndex(m_chartMatrixDisplayProperties, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_chartMatrixDisplayProperties[tabIndex]; } /** * Save file data from the scene. For subclasses that need to * save to a scene, this method should be overriden. sceneClass * will be valid and any scene data should be added to it. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass to which data members should be added. */ void CiftiParcelLabelFile::saveFileDataToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass) { CiftiMappableDataFile::saveFileDataToScene(sceneAttributes, sceneClass); m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); sceneClass->addBooleanArray("m_chartingEnabledForTab", m_chartingEnabledForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS); /* * Save chart matrix properties */ SceneObjectMapIntegerKey* chartMatrixPropertiesMap = new SceneObjectMapIntegerKey("m_chartMatrixDisplayPropertiesMap", SceneObjectDataTypeEnum::SCENE_CLASS); const std::vector tabIndices = sceneAttributes->getIndicesOfTabsForSavingToScene(); for (std::vector::const_iterator tabIter = tabIndices.begin(); tabIter != tabIndices.end(); tabIter++) { const int32_t tabIndex = *tabIter; chartMatrixPropertiesMap->addClass(tabIndex, m_chartMatrixDisplayProperties[tabIndex]->saveToScene(sceneAttributes, "m_chartMatrixDisplayProperties")); } sceneClass->addChild(chartMatrixPropertiesMap); } /** * Restore file data from the scene. For subclasses that need to * restore from a scene, this method should be overridden. The scene class * will be valid and any scene data may be obtained from it. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass for the instance of a class that implements * this interface. Will NEVER be NULL. */ void CiftiParcelLabelFile::restoreFileDataFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { CiftiMappableDataFile::restoreFileDataFromScene(sceneAttributes, sceneClass); m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_chartingEnabledForTab[i] = false; } const ScenePrimitiveArray* tabArray = sceneClass->getPrimitiveArray("m_chartingEnabledForTab"); if (tabArray != NULL) { sceneClass->getBooleanArrayValue("m_chartingEnabledForTab", m_chartingEnabledForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS); } else { /* * Obsolete value when charting was not 'per tab' */ const bool chartingEnabled = sceneClass->getBooleanValue("m_chartingEnabled", false); for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_chartingEnabledForTab[i] = chartingEnabled; } } /* * Restore chart matrix properties */ const SceneObjectMapIntegerKey* chartMatrixPropertiesMap = sceneClass->getMapIntegerKey("m_chartMatrixDisplayPropertiesMap"); if (chartMatrixPropertiesMap != NULL) { const std::vector tabIndices = chartMatrixPropertiesMap->getKeys(); for (std::vector::const_iterator tabIter = tabIndices.begin(); tabIter != tabIndices.end(); tabIter++) { const int32_t tabIndex = *tabIter; const SceneClass* sceneClass = chartMatrixPropertiesMap->classValue(tabIndex); m_chartMatrixDisplayProperties[tabIndex]->restoreFromScene(sceneAttributes, sceneClass); } } } /** * @return Coloring mode for selected parcel. */ CiftiParcelColoringModeEnum::Enum CiftiParcelLabelFile::getSelectedParcelColoringMode() const { return m_selectedParcelColoringMode; } /** * Set the coloring mode for selected parcel. * * @param coloringMode * New value for coloring mode. */ void CiftiParcelLabelFile::setSelectedParcelColoringMode(const CiftiParcelColoringModeEnum::Enum coloringMode) { m_selectedParcelColoringMode = coloringMode; } /** * @return Color for selected parcel. */ CaretColorEnum::Enum CiftiParcelLabelFile::getSelectedParcelColor() const { return m_selectedParcelColor; } /** * Set color for selected parcel. * * @param color * New color for selected parcel. */ void CiftiParcelLabelFile::setSelectedParcelColor(const CaretColorEnum::Enum color) { m_selectedParcelColor = color; } /** * Get the selected parcel label file used for reordering of parcels. * * @param compatibleParcelLabelFilesOut * All Parcel Label files that are compatible with file implementing * this interface. * @param selectedParcelLabelFileOut * The selected parcel label file used for reordering the parcels. * May be NULL! * @param selectedParcelLabelFileMapIndexOut * Map index in the selected parcel label file. * @param enabledStatusOut * Enabled status of reordering. */ void CiftiParcelLabelFile::getSelectedParcelLabelFileAndMapForReordering(std::vector& compatibleParcelLabelFilesOut, CiftiParcelLabelFile* &selectedParcelLabelFileOut, int32_t& selectedParcelLabelFileMapIndexOut, bool& enabledStatusOut) const { m_parcelReorderingModel->getSelectedParcelLabelFileAndMapForReordering(compatibleParcelLabelFilesOut, selectedParcelLabelFileOut, selectedParcelLabelFileMapIndexOut, enabledStatusOut); } /** * Set the selected parcel label file used for reordering of parcels. * * @param selectedParcelLabelFile * The selected parcel label file used for reordering the parcels. * May be NULL! * @param selectedParcelLabelFileMapIndex * Map index in the selected parcel label file. * @param enabledStatus * Enabled status of reordering. */ void CiftiParcelLabelFile::setSelectedParcelLabelFileAndMapForReordering(CiftiParcelLabelFile* selectedParcelLabelFile, const int32_t selectedParcelLabelFileMapIndex, const bool enabledStatus) { m_parcelReorderingModel->setSelectedParcelLabelFileAndMapForReordering(selectedParcelLabelFile, selectedParcelLabelFileMapIndex, enabledStatus); } /** * Get the parcel reordering for the given map index that was created using * the given parcel label file and its map index. * * @param parcelLabelFile * The selected parcel label file used for reordering the parcels. * @param parcelLabelFileMapIndex * Map index in the selected parcel label file. * @return * Pointer to parcel reordering or NULL if not found. */ const CiftiParcelReordering* CiftiParcelLabelFile::getParcelReordering(const CiftiParcelLabelFile* parcelLabelFile, const int32_t parcelLabelFileMapIndex) const { return m_parcelReorderingModel->getParcelReordering(parcelLabelFile, parcelLabelFileMapIndex); } /** * Create the parcel reordering for the given map index using * the given parcel label file and its map index. * * @param parcelLabelFile * The selected parcel label file used for reordering the parcels. * @param parcelLabelFileMapIndex * Map index in the selected parcel label file. * @param errorMessageOut * Error message output. Will only be non-empty if NULL is returned. * @return * Pointer to parcel reordering or NULL if not found. */ bool CiftiParcelLabelFile::createParcelReordering(const CiftiParcelLabelFile* parcelLabelFile, const int32_t parcelLabelFileMapIndex, AString& errorMessageOut) { return m_parcelReorderingModel->createParcelReordering(parcelLabelFile, parcelLabelFileMapIndex, errorMessageOut); } /** * @return True if loading attributes (column/row, yoking) are * supported by this file type. */ bool CiftiParcelLabelFile::isSupportsLoadingAttributes() { return false; } /** * @return The matrix loading type (by row/column). */ ChartMatrixLoadingDimensionEnum::Enum CiftiParcelLabelFile::getMatrixLoadingDimension() const { /* * This file supports loading by column only ! */ return ChartMatrixLoadingDimensionEnum::CHART_MATRIX_LOADING_BY_COLUMN; } /** * Set the matrix loading type (by row/column). * * @param matrixLoadingType * New value for matrix loading type. */ void CiftiParcelLabelFile::setMatrixLoadingDimension(const ChartMatrixLoadingDimensionEnum::Enum /* matrixLoadingType */) { CaretLogSevere("Attempting to change matrix loading type for a file that only supports loading by column"); } /** * @return Selected yoking group. */ YokingGroupEnum::Enum CiftiParcelLabelFile::getYokingGroup() const { /* not supported in this file */ return YokingGroupEnum::YOKING_GROUP_OFF; } /** * Set the selected yoking group. * * @param yokingGroup * New value for yoking group. */ void CiftiParcelLabelFile::setYokingGroup(const YokingGroupEnum::Enum /* yokingGroup */) { /* not supported in this file */ } /** * Reorder and map and return the matching parcel indices. * * @param mapIndex * Index of the map. * @param reorderedParcelIndicesOut * The parcel indices with reordering applied * @param errorMessageOut * Contains description of error. * @return * True if reordering successful, otherwise, false is returned * and errorMessageOut will contain a descrption of the error. */ bool CiftiParcelLabelFile::getReorderedParcelIndicesFromMap(const int32_t mapIndex, std::vector& reorderedParcelIndicesOut, AString& errorMessageOut) const { reorderedParcelIndicesOut.clear(); errorMessageOut.clear(); if ((mapIndex < 0) || (mapIndex >= getNumberOfMaps())) { errorMessageOut.appendWithNewLine("Invalid map index=" + AString::number(mapIndex)); } const int32_t numberOfRows = m_ciftiFile->getNumberOfRows(); if (numberOfRows <= 0) { errorMessageOut.appendWithNewLine("File contains no rows."); } if ( ! errorMessageOut.isEmpty()) { return false; } std::vector columnData(numberOfRows); m_ciftiFile->getColumn(&columnData[0], mapIndex); CaretLogFine("Column values size=" + AString::number(columnData.size()) + ": " + AString::fromNumbers(columnData, ",")); std::vector indexProcessed(numberOfRows, false); /* * Reorder row indices so that identical values are grouped together */ for (int32_t iRow = 0; iRow < numberOfRows; iRow++) { if ( ! indexProcessed[iRow]) { reorderedParcelIndicesOut.push_back(iRow); indexProcessed[iRow] = true; const int32_t valueI = columnData[iRow]; for (int32_t jRow = (iRow + 1); jRow < numberOfRows; jRow++) { if ( ! indexProcessed[jRow]) { if (valueI == static_cast(columnData[jRow])) { reorderedParcelIndicesOut.push_back(jRow); indexProcessed[jRow] = true; } } } } } if (reorderedParcelIndicesOut.empty()) { errorMessageOut.appendWithNewLine("No parcel indices for reordering."); return false; } CaretLogFine("Reordered parcel indices (not values!) size=" + AString::number(reorderedParcelIndicesOut.size()) + ": " + AString::fromNumbers(reorderedParcelIndicesOut, ",")); return true; } workbench-1.1.1/src/Files/CiftiParcelLabelFile.h000066400000000000000000000143431255417355300214420ustar00rootroot00000000000000#ifndef __CIFTI_PARCEL_LABEL_FILE_H__ #define __CIFTI_PARCEL_LABEL_FILE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "BrainConstants.h" #include "CiftiMappableDataFile.h" #include "ChartableMatrixParcelInterface.h" namespace caret { class CiftiParcelReorderingModel; class SceneClassAssistant; class CiftiParcelLabelFile : public CiftiMappableDataFile, public ChartableMatrixParcelInterface { public: CiftiParcelLabelFile(); virtual ~CiftiParcelLabelFile(); virtual void getMatrixDimensions(int32_t& numberOfRowsOut, int32_t& numberOfColumnsOut) const; virtual bool getMatrixDataRGBA(int32_t& numberOfRowsOut, int32_t& numberOfColumnsOut, std::vector& rgbaOut) const; virtual bool getMatrixCellAttributes(const int32_t rowIndex, const int32_t columnIndex, AString& cellValueOut, AString& rowNameOut, AString& columnNameOut) const; virtual bool isMatrixChartingEnabled(const int32_t tabIndex) const; virtual bool isMatrixChartingSupported() const; virtual void setMatrixChartingEnabled(const int32_t tabIndex, const bool enabled); virtual void getSupportedMatrixChartDataTypes(std::vector& chartDataTypesOut) const; const ChartMatrixDisplayProperties* getChartMatrixDisplayProperties(const int32_t tabIndex) const; ChartMatrixDisplayProperties* getChartMatrixDisplayProperties(const int32_t tabIndex); virtual CiftiParcelColoringModeEnum::Enum getSelectedParcelColoringMode() const; virtual void setSelectedParcelColoringMode(const CiftiParcelColoringModeEnum::Enum coloringMode); virtual CaretColorEnum::Enum getSelectedParcelColor() const; virtual void setSelectedParcelColor(const CaretColorEnum::Enum color); virtual void getSelectedParcelLabelFileAndMapForReordering(std::vector& compatibleParcelLabelFilesOut, CiftiParcelLabelFile* &selectedParcelLabelFileOut, int32_t& selectedParcelLabelFileMapIndexOut, bool& enabledStatusOut) const; virtual void setSelectedParcelLabelFileAndMapForReordering(CiftiParcelLabelFile* selectedParcelLabelFile, const int32_t selectedParcelLabelFileMapIndex, const bool enabledStatus); virtual bool createParcelReordering(const CiftiParcelLabelFile* parcelLabelFile, const int32_t parcelLabelFileMapIndex, AString& errorMessageOut); virtual const CiftiParcelReordering* getParcelReordering(const CiftiParcelLabelFile* parcelLabelFile, const int32_t parcelLabelFileMapIndex) const; virtual bool isSupportsLoadingAttributes(); virtual ChartMatrixLoadingDimensionEnum::Enum getMatrixLoadingDimension() const; virtual void setMatrixLoadingDimension(const ChartMatrixLoadingDimensionEnum::Enum matrixLoadingType); virtual YokingGroupEnum::Enum getYokingGroup() const; virtual void setYokingGroup(const YokingGroupEnum::Enum yokingType); bool getReorderedParcelIndicesFromMap(const int32_t mapIndex, std::vector& reorderedParcelIndicesOut, AString& errorMessageOut) const; // ADD_NEW_METHODS_HERE protected: virtual void saveFileDataToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass); virtual void restoreFileDataFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private: CiftiParcelLabelFile(const CiftiParcelLabelFile&); CiftiParcelLabelFile& operator=(const CiftiParcelLabelFile&); SceneClassAssistant* m_sceneAssistant; bool m_chartingEnabledForTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; ChartMatrixDisplayProperties* m_chartMatrixDisplayProperties[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; CiftiParcelColoringModeEnum::Enum m_selectedParcelColoringMode; CaretColorEnum::Enum m_selectedParcelColor; CiftiParcelReorderingModel* m_parcelReorderingModel; // ADD_NEW_MEMBERS_HERE }; #ifdef __CIFTI_PARCEL_LABEL_FILE_DECLARE__ // #endif // __CIFTI_PARCEL_LABEL_FILE_DECLARE__ } // namespace #endif //__CIFTI_PARCEL_LABEL_FILE_H__ workbench-1.1.1/src/Files/CiftiParcelReordering.cxx000066400000000000000000000426111255417355300222750ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CIFTI_PARCEL_REORDERING_DECLARE__ #include "CiftiParcelReordering.h" #undef __CIFTI_PARCEL_REORDERING_DECLARE__ #include "CaretAssert.h" #include "CiftiParcelLabelFile.h" #include "CiftiParcelsMap.h" #include "ElapsedTimer.h" #include "GiftiLabel.h" #include "GiftiLabelTable.h" #include "SceneClass.h" #include "ScenePrimitiveArray.h" #include "SceneClassAssistant.h" using namespace caret; /** * \class caret::CiftiParcelReordering * \brief Contains a parcel reordering. * \ingroup Files */ /** * Constructs an invalid instance. */ CiftiParcelReordering::CiftiParcelReordering() : CaretObject() { m_sourceParcelLabelFile = NULL; m_sourceParcelLabelFileMapIndex = -1; m_sceneAssistant = new SceneClassAssistant(); m_sceneAssistant->add("m_sourceParcelLabelFileMapIndex", &m_sourceParcelLabelFileMapIndex); } /** * Destructor. */ CiftiParcelReordering::~CiftiParcelReordering() { delete m_sceneAssistant; } /** * Copy constructor. * @param obj * Object that is copied. */ CiftiParcelReordering::CiftiParcelReordering(const CiftiParcelReordering& obj) : CaretObject(obj), SceneableInterface(obj) { this->copyHelperCiftiParcelReordering(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ CiftiParcelReordering& CiftiParcelReordering::operator=(const CiftiParcelReordering& obj) { if (this != &obj) { CaretObject::operator=(obj); this->copyHelperCiftiParcelReordering(obj); } return *this; } /** * Helps with copying an object of this type. * @param obj * Object that is copied. */ void CiftiParcelReordering::copyHelperCiftiParcelReordering(const CiftiParcelReordering& obj) { m_sourceParcelLabelFile = obj.m_sourceParcelLabelFile; m_sourceParcelLabelFileMapIndex = obj.m_sourceParcelLabelFileMapIndex; m_reorderedParcelIndices = obj.m_reorderedParcelIndices; } /** * Get a description of this object's content. * @return String describing this object's content. */ AString CiftiParcelReordering::toString() const { return "CiftiParcelReordering"; } /** * Equality operator that compares this instance to another instance. * * @param parcelReordering * Compared to "this" * @return * True if this instance and the other instance are "equal", else false. */ bool CiftiParcelReordering::operator==(const CiftiParcelReordering& obj) const { if ((m_sourceParcelLabelFile == obj.m_sourceParcelLabelFile) && (m_sourceParcelLabelFileMapIndex == obj.m_sourceParcelLabelFileMapIndex)) { return true; } return false; } /** * Does this parcel reordering key match? * * @param sourceParcelLabelFile * The source parcel label file for the reordering. * @param sourceParcelLabelFileMapIndex * The map index from the source parcel map file. * @return * True if this parcel reordering is for the given file and map indices, * else false. */ bool CiftiParcelReordering::isMatch(const CiftiParcelLabelFile* sourceParcelLabelFile, const int32_t sourceParcelLabelFileMapIndex) const { if ((sourceParcelLabelFile == m_sourceParcelLabelFile) && (sourceParcelLabelFileMapIndex == m_sourceParcelLabelFileMapIndex)) { return true; } return false; } /** * @ Is this parcel reordering valid? */ bool CiftiParcelReordering::isValid() const { if (m_reorderedParcelIndices.empty()) { return false; } return true; } ///** // * Create the parcel reordering. // * // * @param sourceParcelLabelFile // * Parcel label file used to create the reordering // * @param sourceParcelLabelFileMapIndex // * Index of map in parcel label file used to create reordering // * @param targetParcelsMap // * Parcels map for which a reordering is created. // * @param errorMessageOut // * Error message if there was a problem creating the // * reordering. // * @return // * True if the reordering was successfully created, else false. // */ //bool //CiftiParcelReordering::createReordering(const CiftiParcelLabelFile* sourceParcelLabelFile, // const int32_t sourceParcelLabelFileMapIndex, // const CiftiParcelsMap& targetParcelsMap, // AString& errorMessageOut) //{ // m_reorderedParcelIndices.clear(); // // errorMessageOut = ""; // // if (sourceParcelLabelFile != NULL) { // if ((sourceParcelLabelFileMapIndex < 0) // || (sourceParcelLabelFileMapIndex >= sourceParcelLabelFile->getNumberOfMaps())) { // errorMessageOut.appendWithNewLine("Source Parcel Label File map index=" // + AString::number(sourceParcelLabelFileMapIndex) // + " is invalid."); // } // } // else { // errorMessageOut.appendWithNewLine("Source Parcel Label File is invalid."); // } // // /* // * The target parcels are those whose indices will be reordered // */ // const std::vector& allTargetParcels = targetParcelsMap.getParcels(); // const int32_t numTargetParcels = static_cast(allTargetParcels.size()); // if (numTargetParcels <= 0) { // errorMessageOut.appendWithNewLine("Parcels map that is to be reordered is empty."); // } // // /* // * These source parcels are those used to create the reordering // */ // const CiftiParcelsMap* sourceParcelsMap = sourceParcelLabelFile->getCiftiParcelsMapForBrainordinateMapping(); // CaretAssert(sourceParcelsMap); // const std::vector& allSourceParcels = sourceParcelsMap->getParcels(); // const int32_t numSourceParcels = static_cast(allSourceParcels.size()); // if (numSourceParcels <= 0) { // errorMessageOut.appendWithNewLine("Parcels map from that Parcel Label File is used to reorder the parcels is empty."); // } // // if ( ! errorMessageOut.isEmpty()) { // return false; // } // // /* // * Tracks parcels that are in the reordering so that they can be // * skipped and save time by avoiding parcel comparisons // */ // std::vector targetParcelRemapped(numTargetParcels, // false); // // /* // * Loop through the source parcels used to create the reordering // */ // for (int32_t iSource = 0; // iSource < numSourceParcels; // iSource++) { // CaretAssertVectorIndex(allSourceParcels, // iSource); // const CiftiParcelsMap::Parcel& sourceParcel = allSourceParcels[iSource]; // // /* // * Loop through target parcels to find those that // * have the exact same mapping by using the // * parcel equality operator. // */ // for (int32_t iTarget = 0; // iTarget < numTargetParcels; // iTarget++) { // CaretAssertVectorIndex(targetParcelRemapped, iTarget); // if ( ! targetParcelRemapped[iTarget]) { // CaretAssertVectorIndex(allTargetParcels, iTarget); // if (allTargetParcels[iTarget] == sourceParcel) { // m_reorderedParcelIndices.push_back(iTarget); // // targetParcelRemapped[iTarget] = true; // } // } // } // } // // if (m_reorderedParcelIndices.empty()) { // return false; // } // // m_sourceParcelLabelFile = const_cast(sourceParcelLabelFile); // m_sourceParcelLabelFileMapIndex = sourceParcelLabelFileMapIndex; // // std::cout << "New parcel reorder: "; // for (std::vector::iterator iter = m_reorderedParcelIndices.begin(); // iter != m_reorderedParcelIndices.end(); // iter++) { // std::cout << " " << *iter; // } // std::cout << std::endl; // // return true; //} /** * Create the parcel reordering. * * @param sourceParcelLabelFile * Parcel label file used to create the reordering * @param sourceParcelLabelFileMapIndex * Index of map in parcel label file used to create reordering * @param targetParcelsMap * Parcels map for which a reordering is created. * @param errorMessageOut * Error message if there was a problem creating the * reordering. * @return * True if the reordering was successfully created, else false. */ bool CiftiParcelReordering::createReordering(const CiftiParcelLabelFile* sourceParcelLabelFile, const int32_t sourceParcelLabelFileMapIndex, const CiftiParcelsMap& targetParcelsMap, AString& errorMessageOut) { ElapsedTimer timer; timer.start(); m_reorderedParcelIndices.clear(); errorMessageOut = ""; if (sourceParcelLabelFile != NULL) { if ((sourceParcelLabelFileMapIndex < 0) || (sourceParcelLabelFileMapIndex >= sourceParcelLabelFile->getNumberOfMaps())) { errorMessageOut.appendWithNewLine("Source Parcel Label File map index=" + AString::number(sourceParcelLabelFileMapIndex) + " is invalid."); } } else { errorMessageOut.appendWithNewLine("Source Parcel Label File is invalid."); } /* * The target parcels are those whose indices will be reordered */ const std::vector& allTargetParcels = targetParcelsMap.getParcels(); const int32_t numTargetParcels = static_cast(allTargetParcels.size()); if (numTargetParcels <= 0) { errorMessageOut.appendWithNewLine("Parcels map that is to be reordered is empty."); } /* * These source's parcels map. */ const CiftiParcelsMap* sourceParcelsMap = sourceParcelLabelFile->getCiftiParcelsMapForBrainordinateMapping(); if (sourceParcelsMap == NULL) { errorMessageOut.appendWithNewLine("No parcels map in source Parcel Label File."); } if ( ! errorMessageOut.isEmpty()) { return false; } /* * Reorder the indices for the Parcels Map in the selected map * of the Parcel Labels File. */ std::vector reorderedSourceParcelIndices; if ( ! sourceParcelLabelFile->getReorderedParcelIndicesFromMap(sourceParcelLabelFileMapIndex, reorderedSourceParcelIndices, errorMessageOut)) { return false; } const std::vector& allSourceParcels = sourceParcelsMap->getParcels(); const int32_t numberOfSourceParcels = static_cast(allSourceParcels.size()); if (numberOfSourceParcels <= 0) { errorMessageOut.appendWithNewLine("No parcels in source Parcel Label File."); return false; } /* * One a target parcel is remapped, it no never needs to * be tested again. */ std::vector targetParcelRemapped(numTargetParcels, false); /* * Loop through the source parcels used to create the reordering */ const int32_t numReorderedSourceParcelIndices = static_cast(reorderedSourceParcelIndices.size()); for (int32_t iSource = 0; iSource < numReorderedSourceParcelIndices; iSource++) { CaretAssertVectorIndex(reorderedSourceParcelIndices, iSource); const int32_t sourceParcelIndex = reorderedSourceParcelIndices[iSource]; CaretAssertVectorIndex(allSourceParcels, sourceParcelIndex); const CiftiParcelsMap::Parcel& sourceParcel = allSourceParcels[sourceParcelIndex]; /* * Loop through target parcels to find those that * have the exact same mapping by using the * parcel's approximate match method which * compares the brainordinate mapping of the parcels. */ for (int32_t iTarget = 0; iTarget < numTargetParcels; iTarget++) { CaretAssertVectorIndex(targetParcelRemapped, iTarget); if ( ! targetParcelRemapped[iTarget]) { CaretAssertVectorIndex(allTargetParcels, iTarget); if (allTargetParcels[iTarget].approximateMatch(sourceParcel)) { m_reorderedParcelIndices.push_back(iTarget); targetParcelRemapped[iTarget] = true; } } } } const int32_t matchCount = std::count(targetParcelRemapped.begin(), targetParcelRemapped.end(), true); const int32_t notMatchCount = numTargetParcels - matchCount; if (notMatchCount > 0) { errorMessageOut.appendWithNewLine("Failed to match " + AString::number(notMatchCount) + " of " + AString::number(numTargetParcels)); return false; } //std::cout << "Time to match parcels was: " << timer.getElapsedTimeSeconds() << " seconds." << std::endl; m_sourceParcelLabelFile = const_cast(sourceParcelLabelFile); m_sourceParcelLabelFileMapIndex = sourceParcelLabelFileMapIndex; return true; } /** * @return The reordered indices of the parcels. */ std::vector CiftiParcelReordering::getReorderedParcelIndices() const { return m_reorderedParcelIndices; } /** * Save information specific to this type of model to the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @param instanceName * Name of instance in the scene. */ SceneClass* CiftiParcelReordering::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "CiftiParcelReordering", 1); m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); if (m_sourceParcelLabelFile != NULL) { sceneClass->addPathName("m_sourceParcelLabelFile", m_sourceParcelLabelFile->getFileName()); if ( ! m_reorderedParcelIndices.empty()) { sceneClass->addIntegerArray("m_reorderedParcelIndices", &m_reorderedParcelIndices[0], m_reorderedParcelIndices.size()); } } // Uncomment if sub-classes must save to scene //saveSubClassDataToScene(sceneAttributes, // sceneClass); return sceneClass; } /** * Restore information specific to the type of model from the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass from which model specific information is obtained. */ void CiftiParcelReordering::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } m_sourceParcelLabelFile = NULL; m_reorderedParcelIndices.clear(); m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); const ScenePathName* parcelLabelPathName = sceneClass->getPathName("m_sourceParcelLabelFile"); if (parcelLabelPathName != NULL) { } const ScenePrimitiveArray* reorderArray = sceneClass->getPrimitiveArray("m_reorderedParcelIndices"); if (reorderArray != NULL) { const int32_t numberOfElements = reorderArray->getNumberOfArrayElements(); if (numberOfElements > 0) { m_reorderedParcelIndices.reserve(numberOfElements); for (int32_t i = 0; i < numberOfElements; i++) { m_reorderedParcelIndices.push_back(reorderArray->integerValue(i)); } } } //Uncomment if sub-classes must restore from scene //restoreSubClassDataFromScene(sceneAttributes, // sceneClass); } workbench-1.1.1/src/Files/CiftiParcelReordering.h000066400000000000000000000075101255417355300217210ustar00rootroot00000000000000#ifndef __CIFTI_PARCEL_REORDERING_H__ #define __CIFTI_PARCEL_REORDERING_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "SceneableInterface.h" namespace caret { class CiftiParcelLabelFile; class CiftiParcelsMap; class SceneClassAssistant; class CiftiParcelReordering : public CaretObject, public SceneableInterface { public: CiftiParcelReordering(); virtual ~CiftiParcelReordering(); CiftiParcelReordering(const CiftiParcelReordering& obj); CiftiParcelReordering& operator=(const CiftiParcelReordering& obj); bool operator==(const CiftiParcelReordering& obj) const; bool isValid() const; bool isMatch(const CiftiParcelLabelFile* sourceParcelLabelFile, const int32_t sourceParcelLabelFileMapIndex) const; bool createReordering(const CiftiParcelLabelFile* sourceParcelLabelFile, const int32_t sourceParcelLabelFileMapIndex, const CiftiParcelsMap& targetParcelsMap, AString& errorMessageOut); std::vector getReorderedParcelIndices() const; // ADD_NEW_METHODS_HERE virtual AString toString() const; virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); // If there will be sub-classes of this class that need to save // and restore data from scenes, these pure virtual methods can // be uncommented to force their implemetation by sub-classes. // protected: // virtual void saveSubClassDataToScene(const SceneAttributes* sceneAttributes, // SceneClass* sceneClass) = 0; // // virtual void restoreSubClassDataFromScene(const SceneAttributes* sceneAttributes, // const SceneClass* sceneClass) = 0; private: void copyHelperCiftiParcelReordering(const CiftiParcelReordering& obj); SceneClassAssistant* m_sceneAssistant; /** * Parcel label file used to create the reordering */ CiftiParcelLabelFile* m_sourceParcelLabelFile; /** * Index of map in parcel label file used to create reordering */ int32_t m_sourceParcelLabelFileMapIndex; /** * Reordered parcel indices in data file that has its parcel's * reordered. */ std::vector m_reorderedParcelIndices; // ADD_NEW_MEMBERS_HERE }; #ifdef __CIFTI_PARCEL_REORDERING_DECLARE__ // #endif // __CIFTI_PARCEL_REORDERING_DECLARE__ } // namespace #endif //__CIFTI_PARCEL_REORDERING_H__ workbench-1.1.1/src/Files/CiftiParcelReorderingModel.cxx000066400000000000000000000464601255417355300232640ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CIFTI_PARCEL_REORDERING_MODEL_DECLARE__ #include "CiftiParcelReorderingModel.h" #undef __CIFTI_PARCEL_REORDERING_MODEL_DECLARE__ #include "CaretAssert.h" #include "CiftiMappableDataFile.h" #include "CiftiParcelLabelFile.h" #include "CiftiParcelReordering.h" #include "CiftiParcelsMap.h" #include "CiftiXML.h" #include "EventCaretMappableDataFilesGet.h" #include "EventManager.h" #include "SceneClass.h" #include "SceneClassAssistant.h" #include "ScenePathName.h" using namespace caret; /** * \class caret::CiftiParcelReorderingModel * \brief Controls reordering of parcels for a CIFTI data file. * \ingroup Files */ /** * Constructor. */ CiftiParcelReorderingModel::CiftiParcelReorderingModel(const CiftiMappableDataFile* parentCiftiMappableDataFile) : CaretObject(), m_parentCiftiMappableDataFile(parentCiftiMappableDataFile) { CaretAssert(parentCiftiMappableDataFile); switch (parentCiftiMappableDataFile->getDataFileType()) { case DataFileTypeEnum::CONNECTIVITY_PARCEL: case DataFileTypeEnum::CONNECTIVITY_PARCEL_SCALAR: case DataFileTypeEnum::CONNECTIVITY_PARCEL_LABEL: break; default: CaretAssert(0); } m_parcelReorderingEnabledStatus = false; m_selectedParcelLabelFile = NULL; m_selectedParcelLabelFileMapIndex = -1; m_sceneAssistant = new SceneClassAssistant(); m_sceneAssistant->add("m_selectedParcelLabelFileMapIndex", &m_selectedParcelLabelFileMapIndex); m_sceneAssistant->add("m_parcelReorderingEnabledStatus", &m_parcelReorderingEnabledStatus); } /** * Destructor. */ CiftiParcelReorderingModel::~CiftiParcelReorderingModel() { delete m_sceneAssistant; for (std::vector::iterator iter = m_parcelReordering.begin(); iter != m_parcelReordering.end(); iter++) { delete *iter; } m_parcelReordering.clear(); } /** * Copy constructor. * @param obj * Object that is copied. */ CiftiParcelReorderingModel::CiftiParcelReorderingModel(const CiftiParcelReorderingModel& obj) : CaretObject(obj), SceneableInterface(obj) { this->copyHelperCiftiParcelReorderingModel(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ CiftiParcelReorderingModel& CiftiParcelReorderingModel::operator=(const CiftiParcelReorderingModel& obj) { if (this != &obj) { CaretObject::operator=(obj); this->copyHelperCiftiParcelReorderingModel(obj); } return *this; } /** * Helps with copying an object of this type. * @param obj * Object that is copied. */ void CiftiParcelReorderingModel::copyHelperCiftiParcelReorderingModel(const CiftiParcelReorderingModel& obj) { std::vector compatibleParcelLabelFiles; CiftiParcelLabelFile* selectedParcelLabelFile; int32_t selectedParcelLabelFileMapIndex; bool enabledStatus; obj.getSelectedParcelLabelFileAndMapForReordering(compatibleParcelLabelFiles, selectedParcelLabelFile, selectedParcelLabelFileMapIndex, enabledStatus); setSelectedParcelLabelFileAndMapForReordering(selectedParcelLabelFile, selectedParcelLabelFileMapIndex, enabledStatus); } /** * (1) Since the user may load/unload files at will, any current * selection requests will need to validate against available * parcel label files. * * (2) The parcel label files must test for compatibility by * matching their CiftiParcelMaps. * * @return Parcel label files that contain at least one map and * contain compatible CiftiParcelMaps. */ std::vector CiftiParcelReorderingModel::getParcelLabelFiles() const { EventCaretMappableDataFilesGet parcelLabelFilesEvent(DataFileTypeEnum::CONNECTIVITY_PARCEL_LABEL); EventManager::get()->sendEvent(parcelLabelFilesEvent.getPointer()); std::vector mappableFiles; parcelLabelFilesEvent.getAllFiles(mappableFiles); std::vector parcelLabelFiles; for (std::vector::iterator iter = mappableFiles.begin(); iter != mappableFiles.end(); iter++) { CaretMappableDataFile* cmdf = *iter; if (cmdf->getNumberOfMaps() > 0) { CiftiParcelLabelFile* parcelLabelFile = dynamic_cast(*iter); CaretAssert(parcelLabelFile); std::map::iterator fileStatusIter = m_parcelLabelFileCompatibilityStatus.find(parcelLabelFile); if (fileStatusIter != m_parcelLabelFileCompatibilityStatus.end()) { const bool compatibleFlag = fileStatusIter->second; if (compatibleFlag) { /* * File was previously verified for parcel compatibility. */ parcelLabelFiles.push_back(parcelLabelFile); } } else { const CiftiParcelsMap* parcelsMap = parcelLabelFile->getCiftiParcelsMapForDirection(CiftiXML::ALONG_COLUMN); CaretAssert(parcelsMap); bool testAlongRow = false; bool testAlongColumn = false; switch (m_parentCiftiMappableDataFile->getDataFileType()) { case DataFileTypeEnum::CONNECTIVITY_PARCEL: testAlongColumn = true; testAlongRow = true; break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_SCALAR: testAlongColumn = true; break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_LABEL: testAlongColumn = true; break; default: CaretAssert(0); } int32_t passCount = 0; int32_t failCount = 0; if (testAlongColumn) { if (m_parentCiftiMappableDataFile->getCiftiParcelsMapForDirection(CiftiXML::ALONG_COLUMN)->approximateMatch(*parcelsMap)) { ++passCount; } else { ++failCount; } } if (testAlongRow) { if (m_parentCiftiMappableDataFile->getCiftiParcelsMapForDirection(CiftiXML::ALONG_ROW)->approximateMatch(*parcelsMap)) { ++passCount; } else { ++failCount; } } bool compatibleFlag = false; if ((passCount > 0) && (failCount <= 0)) { parcelLabelFiles.push_back(parcelLabelFile); /* * NOTE: Since file was found to be compatible it will * always be compatible. */ compatibleFlag = true; } /* * NOTE: Compatiblity status will never change so cache it * to avoid retesting. */ m_parcelLabelFileCompatibilityStatus.insert(std::make_pair(parcelLabelFile, compatibleFlag)); } } } return parcelLabelFiles; } /** * Validate the selected parcel label file and map. * * @param optionalParcelLabelFilesOut * If not NULL, the matching parcel labels files are inserted into this. */ void CiftiParcelReorderingModel::validateSelectedParcelLabelFileAndMap(std::vector* optionalParcelLabelFilesOut) const { std::vector parcelLabelFiles = getParcelLabelFiles(); bool foundFile = false; for (std::vector::iterator iter = parcelLabelFiles.begin(); iter != parcelLabelFiles.end(); iter++) { CiftiParcelLabelFile* plf = *iter; if (m_selectedParcelLabelFile != NULL) { if (m_selectedParcelLabelFile == plf) { foundFile = true; break; } } } if (foundFile) { const int32_t numMaps = m_selectedParcelLabelFile->getNumberOfMaps(); if (m_selectedParcelLabelFileMapIndex >= numMaps) { m_selectedParcelLabelFileMapIndex = numMaps - 1; } else if (m_selectedParcelLabelFileMapIndex < 0) { m_selectedParcelLabelFileMapIndex = 0; } } if ( ! foundFile) { if ( ! parcelLabelFiles.empty()) { m_selectedParcelLabelFile = parcelLabelFiles[0]; m_selectedParcelLabelFileMapIndex = 0; } else { m_selectedParcelLabelFile = NULL; m_selectedParcelLabelFileMapIndex = -1; m_parcelReorderingEnabledStatus = false; } } if (optionalParcelLabelFilesOut != NULL) { *optionalParcelLabelFilesOut = parcelLabelFiles; } } /** * Get a description of this object's content. * @return String describing this object's content. */ AString CiftiParcelReorderingModel::toString() const { return "CiftiParcelReorderingModel"; } /** * Get the selected parcel label file used for reordering of parcels. * * @param parcelLabelFilesOut * The compatible parcel label files. * @param selectedParcelLabelFileOut * The selected parcel label file used for reordering the parcels. * May be NULL! * @param selectedParcelLabelFileMapIndexOut * Map index in the selected parcel label file. * @param enabledStatusOut * Enabled status of reordering. */ void CiftiParcelReorderingModel::getSelectedParcelLabelFileAndMapForReordering(std::vector& parcelLabelFilesOut, CiftiParcelLabelFile* &selectedParcelLabelFileOut, int32_t& selectedParcelLabelFileMapIndexOut, bool& enabledStatusOut) const { validateSelectedParcelLabelFileAndMap(&parcelLabelFilesOut); selectedParcelLabelFileOut = m_selectedParcelLabelFile; selectedParcelLabelFileMapIndexOut = m_selectedParcelLabelFileMapIndex; enabledStatusOut = m_parcelReorderingEnabledStatus; } /** * Set the selected parcel label file used for reordering of parcels. * * @param selectedParcelLabelFile * The selected parcel label file used for reordering the parcels. * May be NULL! * @param selectedParcelLabelFileMapIndex * Map index in the selected parcel label file. * @param enabledStatus * Enabled status of reordering. */ void CiftiParcelReorderingModel::setSelectedParcelLabelFileAndMapForReordering(CiftiParcelLabelFile* selectedParcelLabelFile, const int32_t selectedParcelLabelFileMapIndex, const bool enabledStatus) { m_selectedParcelLabelFile = selectedParcelLabelFile; m_selectedParcelLabelFileMapIndex = selectedParcelLabelFileMapIndex; m_parcelReorderingEnabledStatus = enabledStatus; } /** * Get the parcel reordering for the given map index that was created using * the given parcel label file and its map index. * * @param parcelLabelFile * The selected parcel label file used for reordering the parcels. * @param parcelLabelFileMapIndex * Map index in the selected parcel label file. * @return * Pointer to parcel reordering or NULL if not found. */ const CiftiParcelReordering* CiftiParcelReorderingModel::getParcelReordering(const CiftiParcelLabelFile* parcelLabelFile, const int32_t parcelLabelFileMapIndex) const { for (std::vector::const_iterator iter = m_parcelReordering.begin(); iter != m_parcelReordering.end(); iter++) { const CiftiParcelReordering* parcelReordering = *iter; if (parcelReordering->isMatch(parcelLabelFile, parcelLabelFileMapIndex)) { return parcelReordering; } } return NULL; } /** * Get the parcel reordering for the given map index that was created using * the given parcel label file and its map index. * * @param parcelLabelFile * The selected parcel label file used for reordering the parcels. * @param parcelLabelFileMapIndex * Map index in the selected parcel label file. * @return * Pointer to parcel reordering or NULL if not found. */ CiftiParcelReordering* CiftiParcelReorderingModel::getParcelReordering(const CiftiParcelLabelFile* parcelLabelFile, const int32_t parcelLabelFileMapIndex) { for (std::vector::iterator iter = m_parcelReordering.begin(); iter != m_parcelReordering.end(); iter++) { CiftiParcelReordering* parcelReordering = *iter; if (parcelReordering->isMatch(parcelLabelFile, parcelLabelFileMapIndex)) { return parcelReordering; } } return NULL; } /** * Create the parcel reordering for the parent file's parcels map index using * the given parcel label file and its map index. * * @param parcelLabelFile * The selected parcel label file used for reordering the parcels. * @param parcelLabelFileMapIndex * Map index in the selected parcel label file. * @param errorMessageOut * Error message output. Will only be non-empty if NULL is returned. * @return * Pointer to parcel reordering or NULL if not found. */ bool CiftiParcelReorderingModel::createParcelReordering(const CiftiParcelLabelFile* parcelLabelFile, const int32_t parcelLabelFileMapIndex, AString& errorMessageOut) { if (getParcelReordering(parcelLabelFile, parcelLabelFileMapIndex) != NULL) { return true; } const CiftiParcelsMap* ciftiParcelsMap = m_parentCiftiMappableDataFile->getCiftiParcelsMapForBrainordinateMapping(); CaretAssert(ciftiParcelsMap); CiftiParcelReordering* parcelReordering = new CiftiParcelReordering(); if (parcelReordering->createReordering(parcelLabelFile, parcelLabelFileMapIndex, *ciftiParcelsMap, errorMessageOut)) { m_parcelReordering.push_back(parcelReordering); return true; } return false; } /** * Save information specific to this type of model to the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @param instanceName * Name of instance in the scene. */ SceneClass* CiftiParcelReorderingModel::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { validateSelectedParcelLabelFileAndMap(NULL); SceneClass* sceneClass = new SceneClass(instanceName, "CiftiParcelReorderingModel", 1); m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); if (m_selectedParcelLabelFile != NULL) { sceneClass->addPathName("m_selectedParcelLabelFile", m_selectedParcelLabelFile->getFileName()); } /* * NOTE: the parcel reorderings are not saved to the scene. * When the scene is restored, a parcel reordering is created * for the restored parcel label file and map index. */ // Uncomment if sub-classes must save to scene //saveSubClassDataToScene(sceneAttributes, // sceneClass); return sceneClass; } /** * Restore information specific to the type of model from the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass from which model specific information is obtained. */ void CiftiParcelReorderingModel::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); m_selectedParcelLabelFile = NULL; m_parcelReordering.clear(); const AString parcelLabelFileName = sceneClass->getPathNameValue("m_selectedParcelLabelFile"); if ( ! parcelLabelFileName.isEmpty()) { std::vector parcelLabelFiles = getParcelLabelFiles(); for (std::vector::iterator iter = parcelLabelFiles.begin(); iter != parcelLabelFiles.end(); iter++) { CiftiParcelLabelFile* plf = *iter; if (plf->getFileName() == parcelLabelFileName) { m_selectedParcelLabelFile = plf; break; } } } validateSelectedParcelLabelFileAndMap(NULL); /* * If there is a valid selected parcel label file, * create the reordering for it. */ if (m_selectedParcelLabelFile != NULL) { if (m_selectedParcelLabelFileMapIndex >= 0) { AString errorMessage; if ( ! createParcelReordering(m_selectedParcelLabelFile, m_selectedParcelLabelFileMapIndex, errorMessage)) { sceneAttributes->addToErrorMessage(errorMessage); } } } //Uncomment if sub-classes must restore from scene //restoreSubClassDataFromScene(sceneAttributes, // sceneClass); } workbench-1.1.1/src/Files/CiftiParcelReorderingModel.h000066400000000000000000000117061255417355300227040ustar00rootroot00000000000000#ifndef __CIFTI_PARCEL_REORDERING_MODEL_H__ #define __CIFTI_PARCEL_REORDERING_MODEL_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretObject.h" #include "SceneableInterface.h" namespace caret { class CiftiMappableDataFile; class CiftiParcelLabelFile; class CiftiParcelReordering; class CiftiParcelsMap; class SceneClassAssistant; class CiftiParcelReorderingModel : public CaretObject, public SceneableInterface { public: CiftiParcelReorderingModel(const CiftiMappableDataFile* ciftiMappableDataFile); virtual ~CiftiParcelReorderingModel(); CiftiParcelReorderingModel(const CiftiParcelReorderingModel& obj); CiftiParcelReorderingModel& operator=(const CiftiParcelReorderingModel& obj); void getSelectedParcelLabelFileAndMapForReordering(std::vector& parcelLabelFilesOut, CiftiParcelLabelFile* &selectedParcelLabelFileOut, int32_t& selectedParcelLabelFileMapIndexOut, bool& enabledStatusOut) const; void setSelectedParcelLabelFileAndMapForReordering(CiftiParcelLabelFile* selectedParcelLabelFile, const int32_t selectedParcelLabelFileMapIndex, const bool enabledStatus); bool createParcelReordering(const CiftiParcelLabelFile* parcelLabelFile, const int32_t parcelLabelFileMapIndex, AString& errorMessageOut); const CiftiParcelReordering* getParcelReordering(const CiftiParcelLabelFile* parcelLabelFile, const int32_t parcelLabelFileMapIndex) const; CiftiParcelReordering* getParcelReordering(const CiftiParcelLabelFile* parcelLabelFile, const int32_t parcelLabelFileMapIndex); // ADD_NEW_METHODS_HERE virtual AString toString() const; virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); // If there will be sub-classes of this class that need to save // and restore data from scenes, these pure virtual methods can // be uncommented to force their implemetation by sub-classes. // protected: // virtual void saveSubClassDataToScene(const SceneAttributes* sceneAttributes, // SceneClass* sceneClass) = 0; // // virtual void restoreSubClassDataFromScene(const SceneAttributes* sceneAttributes, // const SceneClass* sceneClass) = 0; private: void copyHelperCiftiParcelReorderingModel(const CiftiParcelReorderingModel& obj); void validateSelectedParcelLabelFileAndMap(std::vector* optionalParcelLabelFilesOut) const; std::vector getParcelLabelFiles() const; mutable std::map m_parcelLabelFileCompatibilityStatus; const CiftiMappableDataFile* m_parentCiftiMappableDataFile; SceneClassAssistant* m_sceneAssistant; std::vector m_parcelReordering; mutable CiftiParcelLabelFile* m_selectedParcelLabelFile; mutable int32_t m_selectedParcelLabelFileMapIndex; mutable bool m_parcelReorderingEnabledStatus; // ADD_NEW_MEMBERS_HERE }; #ifdef __CIFTI_PARCEL_REORDERING_MODEL_DECLARE__ // #endif // __CIFTI_PARCEL_REORDERING_MODEL_DECLARE__ } // namespace #endif //__CIFTI_PARCEL_REORDERING_MODEL_H__ workbench-1.1.1/src/Files/CiftiParcelScalarFile.cxx000066400000000000000000000637741255417355300222170ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CIFTI_PARCEL_SCALAR_FILE_DECLARE__ #include "CiftiParcelScalarFile.h" #undef __CIFTI_PARCEL_SCALAR_FILE_DECLARE__ #include "CaretLogger.h" #include "ChartDataCartesian.h" #include "ChartMatrixDisplayProperties.h" #include "CiftiFile.h" #include "CiftiParcelReordering.h" #include "CiftiParcelReorderingModel.h" #include "CiftiXML.h" #include "FastStatistics.h" #include "NodeAndVoxelColoring.h" #include "Palette.h" #include "PaletteColorMapping.h" #include "PaletteFile.h" #include "SceneClass.h" #include "SceneClassArray.h" #include "SceneClassAssistant.h" using namespace caret; /** * \class caret::CiftiParcelScalarFile * \brief CIFTI Parcel by Scalar File * \ingroup Files * */ /** * Constructor. */ CiftiParcelScalarFile::CiftiParcelScalarFile() : CiftiMappableDataFile(DataFileTypeEnum::CONNECTIVITY_PARCEL_SCALAR) { for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_brainordinateChartingEnabledForTab[i] = false; m_matrixChartingEnabledForTab[i] = false; m_chartMatrixDisplayProperties[i] = new ChartMatrixDisplayProperties(); } m_selectedParcelColoringMode = CiftiParcelColoringModeEnum::CIFTI_PARCEL_COLORING_OUTLINE; m_selectedParcelColor = CaretColorEnum::WHITE; m_parcelReorderingModel = new CiftiParcelReorderingModel(this); m_sceneAssistant = new SceneClassAssistant(); m_sceneAssistant->add("m_selectedParcelColoringMode", &m_selectedParcelColoringMode); m_sceneAssistant->add("m_selectedParcelColor", &m_selectedParcelColor); m_sceneAssistant->add("m_parcelReorderingModel", "CiftiParcelReorderingModel", m_parcelReorderingModel); } /** * Destructor. */ CiftiParcelScalarFile::~CiftiParcelScalarFile() { for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { delete m_chartMatrixDisplayProperties[i]; } delete m_parcelReorderingModel; delete m_sceneAssistant; } /** * @return Is charting enabled for this file? */ bool CiftiParcelScalarFile::isLineSeriesChartingEnabled(const int32_t tabIndex) const { CaretAssertArrayIndex(m_chartingEnabledForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_brainordinateChartingEnabledForTab[tabIndex]; } /** * @return Return true if the file's current state supports * charting data, else false. Typically a brainordinate file * is chartable if it contains more than one map. */ bool CiftiParcelScalarFile::isLineSeriesChartingSupported() const { if (getNumberOfMaps() > 1) { return true; } return false; } /** * Get chart data types supported by the file. * * @param chartDataTypesOut * Chart types supported by this file. */ void CiftiParcelScalarFile::getSupportedLineSeriesChartDataTypes(std::vector& chartDataTypesOut) const { helpGetSupportedLineSeriesChartDataTypes(chartDataTypesOut); } /** * Set charting enabled for this file. * * @param enabled * New status for charting enabled. */ void CiftiParcelScalarFile::setLineSeriesChartingEnabled(const int32_t tabIndex, const bool enabled) { CaretAssertArrayIndex(m_chartingEnabledForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_brainordinateChartingEnabledForTab[tabIndex] = enabled; } /** * Load charting data for the surface with the given structure and node index. * * @param structure * The surface's structure. * @param nodeIndex * Index of the node. * @return * Pointer to the chart data. If the data FAILED to load, * the returned pointer will return true. Caller takes ownership * of the pointer and must delete it when no longer needed. */ ChartDataCartesian* CiftiParcelScalarFile::loadLineSeriesChartDataForSurfaceNode(const StructureEnum::Enum structure, const int32_t nodeIndex) { ChartDataCartesian* chartData = helpLoadChartDataForSurfaceNode(structure, nodeIndex); return chartData; // ChartDataCartesian* chartData = NULL; // // try { // std::vector data; // if (getSeriesDataForSurfaceNode(structure, // nodeIndex, // data)) { // const int64_t numData = static_cast(data.size()); // // chartData = new ChartDataCartesian(ChartDataTypeEnum::CHART_DATA_TYPE_LINE_DATA_SERIES, // ChartAxisUnitsEnum::CHART_AXIS_UNITS_NONE, // ChartAxisUnitsEnum::CHART_AXIS_UNITS_NONE); // for (int64_t i = 0; i < numData; i++) { // float xValue = i; // chartData->addPoint(xValue, // data[i]); // } // // const AString description = (getFileNameNoPath() // + " node " // + AString::number(nodeIndex)); // chartData->setDescription(description); // } // } // catch (const DataFileException& dfe) { // if (chartData != NULL) { // delete chartData; // chartData = NULL; // } // // throw dfe; // } // // return chartData; } /** * Load average charting data for the surface with the given structure and node indices. * * @param structure * The surface's structure. * @param nodeIndices * Indices of the node. * @return * Pointer to the chart data. If the data FAILED to load, * the returned pointer will be NULL. Caller takes ownership * of the pointer and must delete it when no longer needed. */ ChartDataCartesian* CiftiParcelScalarFile::loadAverageLineSeriesChartDataForSurfaceNodes(const StructureEnum::Enum structure, const std::vector& nodeIndices) { ChartDataCartesian* chartData = helpLoadChartDataForSurfaceNodeAverage(structure, nodeIndices); return chartData; } /** * Load charting data for the voxel enclosing the given coordinate. * * @param xyz * Coordinate of voxel. * @return * Pointer to the chart data. If the data FAILED to load, * the returned pointer will be NULL. Caller takes ownership * of the pointer and must delete it when no longer needed. */ ChartDataCartesian* CiftiParcelScalarFile::loadLineSeriesChartDataForVoxelAtCoordinate(const float xyz[3]) { ChartDataCartesian* chartData = helpLoadChartDataForVoxelAtCoordinate(xyz); return chartData; } /** * Get the matrix dimensions. * * @param numberOfRowsOut * Number of rows in the matrix. * @param numberOfColumnsOut * Number of rows in the matrix. */ void CiftiParcelScalarFile::getMatrixDimensions(int32_t& numberOfRowsOut, int32_t& numberOfColumnsOut) const { helpMapFileGetMatrixDimensions(numberOfRowsOut, numberOfColumnsOut); } /** * Get the matrix RGBA coloring for this matrix data creator. * * @param numberOfRowsOut * Number of rows in the coloring matrix. * @param numberOfColumnsOut * Number of rows in the coloring matrix. * @param rgbaOut * RGBA coloring output with number of elements * (numberOfRowsOut * numberOfColumnsOut * 4). * @return * True if data output data is valid, else false. */ bool CiftiParcelScalarFile::getMatrixDataRGBA(int32_t& numberOfRowsOut, int32_t& numberOfColumnsOut, std::vector& rgbaOut) const { CiftiParcelLabelFile* parcelLabelFile = NULL; int32_t parcelLabelFileMapIndex = -1; bool enabled = false; std::vector parcelLabelFiles; getSelectedParcelLabelFileAndMapForReordering(parcelLabelFiles, parcelLabelFile, parcelLabelFileMapIndex, enabled); std::vector rowIndices; if (enabled) { const CiftiParcelReordering* parcelReordering = getParcelReordering(parcelLabelFile, parcelLabelFileMapIndex); if (parcelReordering != NULL) { rowIndices = parcelReordering->getReorderedParcelIndices(); } } return helpMapFileLoadChartDataMatrixRGBA(numberOfRowsOut, numberOfColumnsOut, rowIndices, rgbaOut); } /** * Get the value, row name, and column name for a cell in the matrix. * * @param rowIndex * The row index. * @param columnIndex * The column index. * @param cellValueOut * Output containing value in the cell. * @param rowNameOut * Name of row corresponding to row index. * @param columnNameOut * Name of column corresponding to column index. * @return * True if the output values are valid (valid row/column indices). */ bool CiftiParcelScalarFile::getMatrixCellAttributes(const int32_t rowIndex, const int32_t columnIndex, AString& cellValueOut, AString& rowNameOut, AString& columnNameOut) const { if ((rowIndex >= 0) && (rowIndex < m_ciftiFile->getNumberOfRows()) && (columnIndex >= 0) && (columnIndex < m_ciftiFile->getNumberOfColumns())) { const CiftiXML& xml = m_ciftiFile->getCiftiXML(); const std::vector& rowsParcelsMap = xml.getParcelsMap(CiftiXML::ALONG_COLUMN).getParcels(); CaretAssertVectorIndex(rowsParcelsMap, rowIndex); rowNameOut = rowsParcelsMap[rowIndex].m_name; columnNameOut = ("Map " + AString::number(columnIndex + 1)); const int32_t numberOfElementsInRow = m_ciftiFile->getNumberOfColumns(); std::vector rowData(numberOfElementsInRow); m_ciftiFile->getRow(&rowData[0], rowIndex); CaretAssertVectorIndex(rowData, columnIndex); cellValueOut = AString::number(rowData[columnIndex], 'f', 6); return true; } return false; } /** * @return Is charting enabled for this file? */ bool CiftiParcelScalarFile::isMatrixChartingEnabled(const int32_t tabIndex) const { CaretAssertArrayIndex(m_matrixChartingEnabledForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_matrixChartingEnabledForTab[tabIndex]; } /** * @return Return true if the file's current state supports * charting data, else false. Typically a brainordinate file * is chartable if it contains more than one map. */ bool CiftiParcelScalarFile::isMatrixChartingSupported() const { return true; } /** * Set charting enabled for this file. * * @param enabled * New status for charting enabled. */ void CiftiParcelScalarFile::setMatrixChartingEnabled(const int32_t tabIndex, const bool enabled) { CaretAssertArrayIndex(m_matrixChartingEnabledForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_matrixChartingEnabledForTab[tabIndex] = enabled; } /** * Get chart data types supported by the file. * * @param chartDataTypesOut * Chart types supported by this file. */ void CiftiParcelScalarFile::getSupportedMatrixChartDataTypes(std::vector& chartDataTypesOut) const { chartDataTypesOut.clear(); chartDataTypesOut.push_back(ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_LAYER); } /** * @return Chart matrix display properties (const method). */ const ChartMatrixDisplayProperties* CiftiParcelScalarFile::getChartMatrixDisplayProperties(const int32_t tabIndex) const { CaretAssertArrayIndex(m_chartMatrixDisplayProperties, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_chartMatrixDisplayProperties[tabIndex]; } /** * @return Chart matrix display properties. */ ChartMatrixDisplayProperties* CiftiParcelScalarFile::getChartMatrixDisplayProperties(const int32_t tabIndex) { CaretAssertArrayIndex(m_chartMatrixDisplayProperties, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_chartMatrixDisplayProperties[tabIndex]; } /** * @return Coloring mode for selected parcel. */ CiftiParcelColoringModeEnum::Enum CiftiParcelScalarFile::getSelectedParcelColoringMode() const { return m_selectedParcelColoringMode; } /** * Set the coloring mode for selected parcel. * * @param coloringMode * New value for coloring mode. */ void CiftiParcelScalarFile::setSelectedParcelColoringMode(const CiftiParcelColoringModeEnum::Enum coloringMode) { m_selectedParcelColoringMode = coloringMode; } /** * @return Color for selected parcel. */ CaretColorEnum::Enum CiftiParcelScalarFile::getSelectedParcelColor() const { return m_selectedParcelColor; } /** * Set color for selected parcel. * * @param color * New color for selected parcel. */ void CiftiParcelScalarFile::setSelectedParcelColor(const CaretColorEnum::Enum color) { m_selectedParcelColor = color; } /** * Save file data from the scene. For subclasses that need to * save to a scene, this method should be overriden. sceneClass * will be valid and any scene data should be added to it. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass to which data members should be added. */ void CiftiParcelScalarFile::saveFileDataToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass) { CiftiMappableDataFile::saveFileDataToScene(sceneAttributes, sceneClass); m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); sceneClass->addBooleanArray("m_brainordinateChartingEnabledForTab", m_brainordinateChartingEnabledForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS); sceneClass->addBooleanArray("m_matrixChartingEnabledForTab", m_matrixChartingEnabledForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS); /* * Save chart matrix properties */ SceneObjectMapIntegerKey* chartMatrixPropertiesMap = new SceneObjectMapIntegerKey("m_chartMatrixDisplayPropertiesMap", SceneObjectDataTypeEnum::SCENE_CLASS); const std::vector tabIndices = sceneAttributes->getIndicesOfTabsForSavingToScene(); for (std::vector::const_iterator tabIter = tabIndices.begin(); tabIter != tabIndices.end(); tabIter++) { const int32_t tabIndex = *tabIter; chartMatrixPropertiesMap->addClass(tabIndex, m_chartMatrixDisplayProperties[tabIndex]->saveToScene(sceneAttributes, "m_chartMatrixDisplayProperties")); } sceneClass->addChild(chartMatrixPropertiesMap); } /** * Restore file data from the scene. For subclasses that need to * restore from a scene, this method should be overridden. The scene class * will be valid and any scene data may be obtained from it. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass for the instance of a class that implements * this interface. Will NEVER be NULL. */ void CiftiParcelScalarFile::restoreFileDataFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { CiftiMappableDataFile::restoreFileDataFromScene(sceneAttributes, sceneClass); m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); /* * Originally, charting was "per file": m_chartingEnabled * Later, charting became "per tab": m_chartingEnabledForTab * Even Later, it needed to clearly be for brainordinates: m_brainordinateChartingEnabledForTab */ const ScenePrimitiveArray* brainChartingForTab = sceneClass->getPrimitiveArray("m_brainordinateChartingEnabledForTab"); const ScenePrimitiveArray* oldChartingEnabledForTab = sceneClass->getPrimitiveArray("m_chartingEnabledForTab"); if (brainChartingForTab != NULL) { sceneClass->getBooleanArrayValue("brainChartingForTab", m_brainordinateChartingEnabledForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS); } else if (oldChartingEnabledForTab != NULL) { sceneClass->getBooleanArrayValue("m_chartingEnabledForTab", m_brainordinateChartingEnabledForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS); } else { /* * Obsolete value when charting was not 'per tab' */ const bool chartingEnabled = sceneClass->getBooleanValue("m_chartingEnabled", false); for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_brainordinateChartingEnabledForTab[i] = chartingEnabled; } } for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_matrixChartingEnabledForTab[i] = false; m_chartMatrixDisplayProperties[i]->resetPropertiesToDefault(); } sceneClass->getBooleanArrayValue("m_matrixChartingEnabledForTab", m_matrixChartingEnabledForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS); /* * Restore chart matrix properties */ const SceneObjectMapIntegerKey* chartMatrixPropertiesMap = sceneClass->getMapIntegerKey("m_chartMatrixDisplayPropertiesMap"); if (chartMatrixPropertiesMap != NULL) { const std::vector tabIndices = chartMatrixPropertiesMap->getKeys(); for (std::vector::const_iterator tabIter = tabIndices.begin(); tabIter != tabIndices.end(); tabIter++) { const int32_t tabIndex = *tabIter; const SceneClass* sceneClass = chartMatrixPropertiesMap->classValue(tabIndex); m_chartMatrixDisplayProperties[tabIndex]->restoreFromScene(sceneAttributes, sceneClass); } } } /** * Get the selected parcel label file used for reordering of parcels. * * @param compatibleParcelLabelFilesOut * All Parcel Label files that are compatible with file implementing * this interface. * @param selectedParcelLabelFileOut * The selected parcel label file used for reordering the parcels. * May be NULL! * @param selectedParcelLabelFileMapIndexOut * Map index in the selected parcel label file. * @param enabledStatusOut * Enabled status of reordering. */ void CiftiParcelScalarFile::getSelectedParcelLabelFileAndMapForReordering(std::vector& compatibleParcelLabelFilesOut, CiftiParcelLabelFile* &selectedParcelLabelFileOut, int32_t& selectedParcelLabelFileMapIndexOut, bool& enabledStatusOut) const { m_parcelReorderingModel->getSelectedParcelLabelFileAndMapForReordering(compatibleParcelLabelFilesOut, selectedParcelLabelFileOut, selectedParcelLabelFileMapIndexOut, enabledStatusOut); } /** * Set the selected parcel label file used for reordering of parcels. * * @param selectedParcelLabelFile * The selected parcel label file used for reordering the parcels. * May be NULL! * @param selectedParcelLabelFileMapIndex * Map index in the selected parcel label file. * @param enabledStatus * Enabled status of reordering. */ void CiftiParcelScalarFile::setSelectedParcelLabelFileAndMapForReordering(CiftiParcelLabelFile* selectedParcelLabelFile, const int32_t selectedParcelLabelFileMapIndex, const bool enabledStatus) { m_parcelReorderingModel->setSelectedParcelLabelFileAndMapForReordering(selectedParcelLabelFile, selectedParcelLabelFileMapIndex, enabledStatus); } /** * Get the parcel reordering for the given map index that was created using * the given parcel label file and its map index. * * @param parcelLabelFile * The selected parcel label file used for reordering the parcels. * @param parcelLabelFileMapIndex * Map index in the selected parcel label file. * @return * Pointer to parcel reordering or NULL if not found. */ const CiftiParcelReordering* CiftiParcelScalarFile::getParcelReordering(const CiftiParcelLabelFile* parcelLabelFile, const int32_t parcelLabelFileMapIndex) const { return m_parcelReorderingModel->getParcelReordering(parcelLabelFile, parcelLabelFileMapIndex); } /** * Create the parcel reordering for the given map index using * the given parcel label file and its map index. * * @param parcelLabelFile * The selected parcel label file used for reordering the parcels. * @param parcelLabelFileMapIndex * Map index in the selected parcel label file. * @param ciftiParcelsMap * The CIFTI parcels map that will or has been reordered. * @param errorMessageOut * Error message output. Will only be non-empty if NULL is returned. * @return * Pointer to parcel reordering or NULL if not found. */ bool CiftiParcelScalarFile::createParcelReordering(const CiftiParcelLabelFile* parcelLabelFile, const int32_t parcelLabelFileMapIndex, AString& errorMessageOut) { return m_parcelReorderingModel->createParcelReordering(parcelLabelFile, parcelLabelFileMapIndex, errorMessageOut); } /** * @return True if loading attributes (column/row, yoking) are * supported by this file type. */ bool CiftiParcelScalarFile::isSupportsLoadingAttributes() { return false; } /** * @return The matrix loading type (by row/column). */ ChartMatrixLoadingDimensionEnum::Enum CiftiParcelScalarFile::getMatrixLoadingDimension() const { /* * This file supports loading by column only ! */ return ChartMatrixLoadingDimensionEnum::CHART_MATRIX_LOADING_BY_COLUMN; } /** * Set the matrix loading type (by row/column). * * @param matrixLoadingType * New value for matrix loading type. */ void CiftiParcelScalarFile::setMatrixLoadingDimension(const ChartMatrixLoadingDimensionEnum::Enum /* matrixLoadingType */) { CaretLogSevere("Attempting to change matrix loading type for a file that only supports loading by column"); } /** * @return Selected yoking group. */ YokingGroupEnum::Enum CiftiParcelScalarFile::getYokingGroup() const { /* not supported in this file */ return YokingGroupEnum::YOKING_GROUP_OFF; } /** * Set the selected yoking group. * * @param yokingGroup * New value for yoking group. */ void CiftiParcelScalarFile::setYokingGroup(const YokingGroupEnum::Enum /* yokingGroup */) { /* not supported in this file */ } workbench-1.1.1/src/Files/CiftiParcelScalarFile.h000066400000000000000000000162071255417355300216310ustar00rootroot00000000000000#ifndef __CIFTI_PARCEL_SCALAR_FILE_H__ #define __CIFTI_PARCEL_SCALAR_FILE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "BrainConstants.h" #include "ChartableLineSeriesBrainordinateInterface.h" #include "ChartableMatrixParcelInterface.h" #include "CiftiMappableDataFile.h" namespace caret { class CiftiParcelReorderingModel; class SceneClassAssistant; class CiftiParcelScalarFile : public CiftiMappableDataFile, public ChartableLineSeriesBrainordinateInterface, public ChartableMatrixParcelInterface { public: CiftiParcelScalarFile(); virtual ~CiftiParcelScalarFile(); virtual bool isLineSeriesChartingEnabled(const int32_t tabIndex) const; virtual void setLineSeriesChartingEnabled(const int32_t tabIndex, const bool enabled); virtual bool isLineSeriesChartingSupported() const; virtual ChartDataCartesian* loadLineSeriesChartDataForSurfaceNode(const StructureEnum::Enum structure, const int32_t nodeIndex); virtual ChartDataCartesian* loadAverageLineSeriesChartDataForSurfaceNodes(const StructureEnum::Enum structure, const std::vector& nodeIndices); virtual ChartDataCartesian* loadLineSeriesChartDataForVoxelAtCoordinate(const float xyz[3]); virtual void getSupportedLineSeriesChartDataTypes(std::vector& chartDataTypesOut) const; virtual void getMatrixDimensions(int32_t& numberOfRowsOut, int32_t& numberOfColumnsOut) const; virtual bool getMatrixDataRGBA(int32_t& numberOfRowsOut, int32_t& numberOfColumnsOut, std::vector& rgbaOut) const; virtual bool getMatrixCellAttributes(const int32_t rowIndex, const int32_t columnIndex, AString& cellValueOut, AString& rowNameOut, AString& columnNameOut) const; virtual bool isMatrixChartingEnabled(const int32_t tabIndex) const; virtual bool isMatrixChartingSupported() const; virtual void setMatrixChartingEnabled(const int32_t tabIndex, const bool enabled); virtual void getSupportedMatrixChartDataTypes(std::vector& chartDataTypesOut) const; const ChartMatrixDisplayProperties* getChartMatrixDisplayProperties(const int32_t tabIndex) const; ChartMatrixDisplayProperties* getChartMatrixDisplayProperties(const int32_t tabIndex); virtual CiftiParcelColoringModeEnum::Enum getSelectedParcelColoringMode() const; virtual void setSelectedParcelColoringMode(const CiftiParcelColoringModeEnum::Enum coloringMode); virtual CaretColorEnum::Enum getSelectedParcelColor() const; virtual void setSelectedParcelColor(const CaretColorEnum::Enum color); virtual void getSelectedParcelLabelFileAndMapForReordering(std::vector& compatibleParcelLabelFilesOut, CiftiParcelLabelFile* &selectedParcelLabelFileOut, int32_t& selectedParcelLabelFileMapIndexOut, bool& enabledStatusOut) const; virtual void setSelectedParcelLabelFileAndMapForReordering(CiftiParcelLabelFile* selectedParcelLabelFile, const int32_t selectedParcelLabelFileMapIndex, const bool enabledStatus); virtual bool createParcelReordering(const CiftiParcelLabelFile* parcelLabelFile, const int32_t parcelLabelFileMapIndex, AString& errorMessageOut); virtual const CiftiParcelReordering* getParcelReordering(const CiftiParcelLabelFile* parcelLabelFile, const int32_t parcelLabelFileMapIndex) const; virtual bool isSupportsLoadingAttributes(); virtual ChartMatrixLoadingDimensionEnum::Enum getMatrixLoadingDimension() const; virtual void setMatrixLoadingDimension(const ChartMatrixLoadingDimensionEnum::Enum matrixLoadingType); virtual YokingGroupEnum::Enum getYokingGroup() const; virtual void setYokingGroup(const YokingGroupEnum::Enum yokingType); private: CiftiParcelScalarFile(const CiftiParcelScalarFile&); CiftiParcelScalarFile& operator=(const CiftiParcelScalarFile&); virtual void saveFileDataToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass); virtual void restoreFileDataFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); public: // ADD_NEW_METHODS_HERE private: SceneClassAssistant* m_sceneAssistant; bool m_brainordinateChartingEnabledForTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; bool m_matrixChartingEnabledForTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; ChartMatrixDisplayProperties* m_chartMatrixDisplayProperties[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; CiftiParcelColoringModeEnum::Enum m_selectedParcelColoringMode; CaretColorEnum::Enum m_selectedParcelColor; CiftiParcelReorderingModel* m_parcelReorderingModel; // ADD_NEW_MEMBERS_HERE }; #ifdef __CIFTI_PARCEL_SCALAR_FILE_DECLARE__ // #endif // __CIFTI_PARCEL_SCALAR_FILE_DECLARE__ } // namespace #endif //__CIFTI_PARCEL_SCALAR_FILE_H__ workbench-1.1.1/src/Files/CiftiParcelSeriesFile.cxx000066400000000000000000000266371255417355300222410ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CIFTI_PARCEL_SERIES_FILE_DECLARE__ #include "CiftiParcelSeriesFile.h" #undef __CIFTI_PARCEL_SERIES_FILE_DECLARE__ #include "CaretLogger.h" #include "ChartDataCartesian.h" #include "SceneClass.h" #include "SceneClassArray.h" using namespace caret; /** * \class caret::CiftiParcelSeriesFile * \brief CIFTI Parcel by Data-Series File. * \ingroup Files */ /** * Constructor. */ CiftiParcelSeriesFile::CiftiParcelSeriesFile() : CiftiMappableDataFile(DataFileTypeEnum::CONNECTIVITY_PARCEL_SERIES) { for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_chartingEnabledForTab[i] = false; } } /** * Destructor. */ CiftiParcelSeriesFile::~CiftiParcelSeriesFile() { } /** * @return Is charting enabled for this file? */ bool CiftiParcelSeriesFile::isLineSeriesChartingEnabled(const int32_t tabIndex) const { CaretAssertArrayIndex(m_chartingEnabledForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_chartingEnabledForTab[tabIndex]; } /** * @return Return true if the file's current state supports * charting data, else false. Typically a brainordinate file * is chartable if it contains more than one map. */ bool CiftiParcelSeriesFile::isLineSeriesChartingSupported() const { if (getNumberOfMaps() > 1) { return true; } return false; } /** * Set charting enabled for this file. * * @param enabled * New status for charting enabled. */ void CiftiParcelSeriesFile::setLineSeriesChartingEnabled(const int32_t tabIndex, const bool enabled) { CaretAssertArrayIndex(m_chartingEnabledForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_chartingEnabledForTab[tabIndex] = enabled; } /** * Get chart data types supported by the file. * * @param chartDataTypesOut * Chart types supported by this file. */ void CiftiParcelSeriesFile::getSupportedLineSeriesChartDataTypes(std::vector& chartDataTypesOut) const { helpGetSupportedLineSeriesChartDataTypes(chartDataTypesOut); } /** * Load charting data for the surface with the given structure and node index. * * @param structure * The surface's structure. * @param nodeIndex * Index of the node. * @return * Pointer to the chart data. If the data FAILED to load, * the returned pointer will return true. Caller takes ownership * of the pointer and must delete it when no longer needed. */ ChartDataCartesian* CiftiParcelSeriesFile::loadLineSeriesChartDataForSurfaceNode(const StructureEnum::Enum structure, const int32_t nodeIndex) { ChartDataCartesian* chartData = helpLoadChartDataForSurfaceNode(structure, nodeIndex); return chartData; // ChartDataCartesian* chartData = NULL; // // try { // std::vector data; // if (getSeriesDataForSurfaceNode(structure, // nodeIndex, // data)) { // const int64_t numData = static_cast(data.size()); // // bool timeSeriesFlag = false; // bool dataSeriesFlag = false; // float convertTimeToSeconds = 1.0; // switch (getMapIntervalUnits()) { // case NiftiTimeUnitsEnum::NIFTI_UNITS_HZ: // break; // case NiftiTimeUnitsEnum::NIFTI_UNITS_MSEC: // timeSeriesFlag = true; // convertTimeToSeconds = 1000.0; // break; // case NiftiTimeUnitsEnum::NIFTI_UNITS_PPM: // break; // case NiftiTimeUnitsEnum::NIFTI_UNITS_SEC: // convertTimeToSeconds = 1.0; // timeSeriesFlag = true; // break; // case NiftiTimeUnitsEnum::NIFTI_UNITS_UNKNOWN: // dataSeriesFlag = true; // break; // case NiftiTimeUnitsEnum::NIFTI_UNITS_USEC: // convertTimeToSeconds = 1000000.0; // timeSeriesFlag = true; // break; // } // // if (dataSeriesFlag) { // chartData = new ChartDataCartesian(ChartDataTypeEnum::CHART_DATA_TYPE_LINE_DATA_SERIES, // ChartAxisUnitsEnum::CHART_AXIS_UNITS_NONE, // ChartAxisUnitsEnum::CHART_AXIS_UNITS_NONE); // } // else if (timeSeriesFlag) { // chartData = new ChartDataCartesian(ChartDataTypeEnum::CHART_DATA_TYPE_LINE_TIME_SERIES, // ChartAxisUnitsEnum::CHART_AXIS_UNITS_TIME_SECONDS, // ChartAxisUnitsEnum::CHART_AXIS_UNITS_NONE); // } // // if (chartData != NULL) { // float timeStart = 0.0; // float timeStep = 1.0; // if (timeSeriesFlag) { // getMapIntervalStartAndStep(timeStart, // timeStep); // timeStart *= convertTimeToSeconds; // timeStep *= convertTimeToSeconds; // chartData->setTimeStartInSecondsAxisX(timeStart); // chartData->setTimeStepInSecondsAxisX(timeStep); // } // // for (int64_t i = 0; i < numData; i++) { // float xValue = i; // // if (timeSeriesFlag) { // xValue = timeStart + (i * timeStep); // } // // chartData->addPoint(xValue, // data[i]); // } // // const AString description = (getFileNameNoPath() // + " node " // + AString::number(nodeIndex)); // chartData->setDescription(description); // } // else { // const AString msg = "New type of units for data series flag, needs updating for charting"; // CaretAssertMessage(0, msg); // throw DataFileException(msg); // } // } // } // catch (const DataFileException& dfe) { // if (chartData != NULL) { // delete chartData; // chartData = NULL; // } // // throw dfe; // } // // return chartData; } /** * Load average charting data for the surface with the given structure and node indices. * * @param structure * The surface's structure. * @param nodeIndices * Indices of the node. * @return * Pointer to the chart data. If the data FAILED to load, * the returned pointer will be NULL. Caller takes ownership * of the pointer and must delete it when no longer needed. */ ChartDataCartesian* CiftiParcelSeriesFile::loadAverageLineSeriesChartDataForSurfaceNodes(const StructureEnum::Enum structure, const std::vector& nodeIndices) { ChartDataCartesian* chartData = helpLoadChartDataForSurfaceNodeAverage(structure, nodeIndices); return chartData; } /** * Load charting data for the voxel enclosing the given coordinate. * * @param xyz * Coordinate of voxel. * @return * Pointer to the chart data. If the data FAILED to load, * the returned pointer will be NULL. Caller takes ownership * of the pointer and must delete it when no longer needed. */ ChartDataCartesian* CiftiParcelSeriesFile::loadLineSeriesChartDataForVoxelAtCoordinate(const float xyz[3]) { ChartDataCartesian* chartData = helpLoadChartDataForVoxelAtCoordinate(xyz); return chartData; } /** * Save file data from the scene. For subclasses that need to * save to a scene, this method should be overriden. sceneClass * will be valid and any scene data should be added to it. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass to which data members should be added. */ void CiftiParcelSeriesFile::saveFileDataToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass) { CiftiMappableDataFile::saveFileDataToScene(sceneAttributes, sceneClass); sceneClass->addBooleanArray("m_chartingEnabledForTab", m_chartingEnabledForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS); } /** * Restore file data from the scene. For subclasses that need to * restore from a scene, this method should be overridden. The scene class * will be valid and any scene data may be obtained from it. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass for the instance of a class that implements * this interface. Will NEVER be NULL. */ void CiftiParcelSeriesFile::restoreFileDataFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { CiftiMappableDataFile::restoreFileDataFromScene(sceneAttributes, sceneClass); const ScenePrimitiveArray* tabArray = sceneClass->getPrimitiveArray("m_chartingEnabledForTab"); if (tabArray != NULL) { sceneClass->getBooleanArrayValue("m_chartingEnabledForTab", m_chartingEnabledForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS); } else { /* * Obsolete value when charting was not 'per tab' */ const bool chartingEnabled = sceneClass->getBooleanValue("m_chartingEnabled", false); for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_chartingEnabledForTab[i] = chartingEnabled; } } } workbench-1.1.1/src/Files/CiftiParcelSeriesFile.h000066400000000000000000000061421255417355300216530ustar00rootroot00000000000000#ifndef __CIFTI_PARCEL_SERIES_FILE_H__ #define __CIFTI_PARCEL_SERIES_FILE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "BrainConstants.h" #include "ChartableLineSeriesBrainordinateInterface.h" #include "CiftiMappableDataFile.h" namespace caret { class CiftiParcelSeriesFile : public CiftiMappableDataFile, public ChartableLineSeriesBrainordinateInterface { public: CiftiParcelSeriesFile(); virtual ~CiftiParcelSeriesFile(); virtual bool isLineSeriesChartingEnabled(const int32_t tabIndex) const; virtual void setLineSeriesChartingEnabled(const int32_t tabIndex, const bool enabled); virtual bool isLineSeriesChartingSupported() const; virtual ChartDataCartesian* loadLineSeriesChartDataForSurfaceNode(const StructureEnum::Enum structure, const int32_t nodeIndex); virtual ChartDataCartesian* loadAverageLineSeriesChartDataForSurfaceNodes(const StructureEnum::Enum structure, const std::vector& nodeIndices); virtual ChartDataCartesian* loadLineSeriesChartDataForVoxelAtCoordinate(const float xyz[3]); virtual void getSupportedLineSeriesChartDataTypes(std::vector& chartDataTypesOut) const; private: CiftiParcelSeriesFile(const CiftiParcelSeriesFile&); CiftiParcelSeriesFile& operator=(const CiftiParcelSeriesFile&); virtual void saveFileDataToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass); virtual void restoreFileDataFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); public: // ADD_NEW_METHODS_HERE private: // ADD_NEW_MEMBERS_HERE bool m_chartingEnabledForTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; }; #ifdef __CIFTI_PARCEL_SERIES_FILE_DECLARE__ // #endif // __CIFTI_PARCEL_SERIES_FILE_DECLARE__ } // namespace #endif //__CIFTI_PARCEL_SERIES_FILE_H__ workbench-1.1.1/src/Files/CiftiScalarDataSeriesFile.cxx000066400000000000000000000563241255417355300230260ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CIFTI_SCALAR_DATA_SERIES_FILE_DECLARE__ #include "CiftiScalarDataSeriesFile.h" #undef __CIFTI_SCALAR_DATA_SERIES_FILE_DECLARE__ #include "CaretAssert.h" #include "CaretLogger.h" #include "ChartDataCartesian.h" #include "ChartMatrixDisplayProperties.h" #include "CiftiFile.h" #include "CiftiSeriesMap.h" #include "CiftiXML.h" #include "DataFileException.h" #include "EventBrowserTabIndicesGetAll.h" #include "EventManager.h" #include "EventMapYokingSelectMap.h" #include "EventMapYokingValidation.h" #include "FastStatistics.h" #include "NodeAndVoxelColoring.h" #include "SceneClassAssistant.h" using namespace caret; /** * \class caret::CiftiScalarDataSeriesFile * \brief CIFTI Scalar Data Series File * \ingroup Files */ /** * Constructor. */ CiftiScalarDataSeriesFile::CiftiScalarDataSeriesFile() : CiftiMappableDataFile(DataFileTypeEnum::CONNECTIVITY_SCALAR_DATA_SERIES) { for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_lineSeriesChartingEnabledForTab[i] = false; m_matrixChartingEnabledForTab[i] = false; m_chartMatrixDisplayPropertiesForTab[i] = new ChartMatrixDisplayProperties(); m_chartMatrixDisplayPropertiesForTab[i]->setGridLinesDisplayed(false); m_yokingGroupForTab[i] = MapYokingGroupEnum::MAP_YOKING_GROUP_OFF; m_selectedMapIndices[i] = 0; } m_sceneAssistant = new SceneClassAssistant(); m_sceneAssistant->addTabIndexedEnumeratedTypeArray("m_yokingGroupForTab", m_yokingGroupForTab); m_sceneAssistant->addTabIndexedBooleanArray("m_lineSeriesChartingEnabledForTab", m_lineSeriesChartingEnabledForTab); m_sceneAssistant->addTabIndexedBooleanArray("m_matrixChartingEnabledForTab", m_matrixChartingEnabledForTab); m_sceneAssistant->addTabIndexedIntegerArray("m_selectedMapIndices", m_selectedMapIndices); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_MAP_YOKING_SELECT_MAP); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_MAP_YOKING_VALIDATION); } /** * Destructor. */ CiftiScalarDataSeriesFile::~CiftiScalarDataSeriesFile() { EventManager::get()->removeAllEventsFromListener(this); for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { delete m_chartMatrixDisplayPropertiesForTab[i]; } delete m_sceneAssistant; } /** * Receive an event. * * @param event * The event. */ void CiftiScalarDataSeriesFile::receiveEvent(Event* event) { if (event->getEventType() == EventTypeEnum::EVENT_MAP_YOKING_VALIDATION) { EventMapYokingValidation* yokeMapEvent = dynamic_cast(event); CaretAssert(yokeMapEvent); yokeMapEvent->addMapYokedFileAllTabs(this, m_yokingGroupForTab); yokeMapEvent->setEventProcessed(); } else if (event->getEventType() == EventTypeEnum::EVENT_MAP_YOKING_SELECT_MAP) { /* * The events intended for overlays are received here so that * only DISPLAYED overlays are updated. */ EventMapYokingSelectMap* selectMapEvent = dynamic_cast(event); CaretAssert(selectMapEvent); const MapYokingGroupEnum::Enum mapYokingGroup = selectMapEvent->getMapYokingGroup(); if (mapYokingGroup != MapYokingGroupEnum::MAP_YOKING_GROUP_OFF) { const int32_t yokedMapIndex = selectMapEvent->getMapIndex(); EventBrowserTabIndicesGetAll tabIndicesEvent; EventManager::get()->sendEvent(tabIndicesEvent.getPointer()); const std::vector tabIndices = tabIndicesEvent.getAllBrowserTabIndices(); for (std::vector::const_iterator iter = tabIndices.begin(); iter != tabIndices.end(); iter++) { const int32_t tabIndex = *iter; if (getMatrixRowColumnMapYokingGroup(tabIndex) == mapYokingGroup) { setSelectedMapIndex(tabIndex, yokedMapIndex); } } } selectMapEvent->setEventProcessed(); } } /** * @param tabIndex * Index of tab. * @return * Selected yoking group for the given tab. */ MapYokingGroupEnum::Enum CiftiScalarDataSeriesFile::getMatrixRowColumnMapYokingGroup(const int32_t tabIndex) const { CaretAssertArrayIndex(m_yokingGroupForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_yokingGroupForTab[tabIndex]; } /** * Set the selected yoking group for the given tab. * * @param tabIndex * Index of tab. * @param yokingGroup * New value for yoking group. */ void CiftiScalarDataSeriesFile::setMatrixRowColumnMapYokingGroup(const int32_t tabIndex, const MapYokingGroupEnum::Enum yokingGroup) { CaretAssertArrayIndex(m_yokingGroupForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_yokingGroupForTab[tabIndex] = yokingGroup; if (m_yokingGroupForTab[tabIndex] == MapYokingGroupEnum::MAP_YOKING_GROUP_OFF) { return; } } /** * @param tabIndex * Index of tab. * @return * Selected map index in the given tab. */ int32_t CiftiScalarDataSeriesFile::getSelectedMapIndex(const int32_t tabIndex) { CaretAssertArrayIndex(m_selectedMapIndices, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_selectedMapIndices[tabIndex]; } /** * Set the selected map index for the given tab. * * @param tabIndex * Index of tab. * @param mapIndex * New value for selected map index. */ void CiftiScalarDataSeriesFile::setSelectedMapIndex(const int32_t tabIndex, const int32_t mapIndex) { CaretAssertArrayIndex(m_selectedMapIndices, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_selectedMapIndices[tabIndex] = mapIndex; } /** * Get the matrix dimensions. * * @param numberOfRowsOut * Number of rows in the matrix. * @param numberOfColumnsOut * Number of columns in the matrix. */ void CiftiScalarDataSeriesFile::getMatrixDimensions(int32_t& numberOfRowsOut, int32_t& numberOfColumnsOut) const { helpMapFileGetMatrixDimensions(numberOfRowsOut, numberOfColumnsOut); } /** * Get the matrix RGBA coloring for this matrix data creator. * * @param numberOfRowsOut * Number of rows in the coloring matrix. * @param numberOfColumnsOut * Number of rows in the coloring matrix. * @param rgbaOut * RGBA coloring output with number of elements * (numberOfRowsOut * numberOfColumnsOut * 4). * @return * True if data output data is valid, else false. */ bool CiftiScalarDataSeriesFile::getMatrixDataRGBA(int32_t& numberOfRowsOut, int32_t& numberOfColumnsOut, std::vector& rgbaOut) const { std::vector rowIndices; return helpMatrixFileLoadChartDataMatrixRGBA(numberOfRowsOut, numberOfColumnsOut, rowIndices, rgbaOut); } /** * Get the value, row name, and column name for a cell in the matrix. * * @param rowIndex * The row index. * @param columnIndex * The column index. * @param cellValueOut * Output containing value in the cell. * @param rowNameOut * Name of row corresponding to row index. * @param columnNameOut * Name of column corresponding to column index. * @return * True if the output values are valid (valid row/column indices). */ bool CiftiScalarDataSeriesFile::getMatrixCellAttributes(const int32_t rowIndex, const int32_t columnIndex, AString& cellValueOut, AString& rowNameOut, AString& columnNameOut) const { rowNameOut = " "; columnNameOut = " "; if ((rowIndex >= 0) && (rowIndex < m_ciftiFile->getNumberOfRows()) && (columnIndex >= 0) && (columnIndex < m_ciftiFile->getNumberOfColumns())) { const CiftiXML& xml = m_ciftiFile->getCiftiXML(); const CiftiScalarsMap& scalarsMap = xml.getScalarsMap(CiftiXML::ALONG_COLUMN); CaretAssertArrayIndex(scalarsMap, scalarsMap.getLength(), rowIndex); rowNameOut = scalarsMap.getMapName(rowIndex); const CiftiSeriesMap& seriesMap = xml.getSeriesMap(CiftiXML::ALONG_ROW); CaretAssertArrayIndex(seriesMap, seriesMap.getLength(), columnIndex); const float time = seriesMap.getStart() + seriesMap.getStep() * columnIndex; AString timeUnitsString; switch (seriesMap.getUnit()) { case CiftiSeriesMap::HERTZ: timeUnitsString = NiftiTimeUnitsEnum::toGuiName(NiftiTimeUnitsEnum::NIFTI_UNITS_HZ); break; case CiftiSeriesMap::METER: CaretLogWarning("CIFTI Units METER not implemented"); break; case CiftiSeriesMap::RADIAN: CaretLogWarning("CIFTI Units RADIAN not implemented"); break; case CiftiSeriesMap::SECOND: timeUnitsString = NiftiTimeUnitsEnum::toGuiName(NiftiTimeUnitsEnum::NIFTI_UNITS_SEC); break; } columnNameOut = (AString::number(time, 'f', 3) + " " + timeUnitsString); // columnNameOut = (AString::number(time, 'f', 3) // + " " // + NiftiTimeUnitsEnum::toGuiName(getMapIntervalUnits()) // + " Map Name: " // + getMapName(columnIndex)); const int32_t numberOfElementsInRow = m_ciftiFile->getNumberOfColumns(); std::vector rowData(numberOfElementsInRow); m_ciftiFile->getRow(&rowData[0], rowIndex); CaretAssertVectorIndex(rowData, columnIndex); cellValueOut = AString::number(rowData[columnIndex], 'f', 6); return true; } return false; } /** * @return Is charting enabled for this file? */ bool CiftiScalarDataSeriesFile::isMatrixChartingEnabled(const int32_t tabIndex) const { CaretAssertArrayIndex(m_matrixChartingEnabledForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_matrixChartingEnabledForTab[tabIndex]; } /** * @return Return true if the file's current state supports * charting data, else false. Typically a brainordinate file * is chartable if it contains more than one map. */ bool CiftiScalarDataSeriesFile::isMatrixChartingSupported() const { return true; } /** * Set charting enabled for this file. * * @param enabled * New status for charting enabled. */ void CiftiScalarDataSeriesFile::setMatrixChartingEnabled(const int32_t tabIndex, const bool enabled) { CaretAssertArrayIndex(m_matrixChartingEnabledForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_matrixChartingEnabledForTab[tabIndex] = enabled; } /** * Get chart data types supported by the file. * * @param chartDataTypesOut * Chart types supported by this file. */ void CiftiScalarDataSeriesFile::getSupportedMatrixChartDataTypes(std::vector& chartDataTypesOut) const { chartDataTypesOut.clear(); chartDataTypesOut.push_back(ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_SERIES); } /** * @return Chart matrix display properties (const method). */ const ChartMatrixDisplayProperties* CiftiScalarDataSeriesFile::getChartMatrixDisplayProperties(const int32_t tabIndex) const { CaretAssertArrayIndex(m_chartMatrixDisplayProperties, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_chartMatrixDisplayPropertiesForTab[tabIndex]; } /** * @return Chart matrix display properties. */ ChartMatrixDisplayProperties* CiftiScalarDataSeriesFile::getChartMatrixDisplayProperties(const int32_t tabIndex) { CaretAssertArrayIndex(m_chartMatrixDisplayProperties, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_chartMatrixDisplayPropertiesForTab[tabIndex]; } /** * Load charting data for the given column index. * * @param columnIndex * Index of the column. * @return * Pointer to the chart data. If the data FAILED to load, * the returned pointer will be NULL. Caller takes ownership * of the pointer and must delete it when no longer needed. */ ChartDataCartesian* CiftiScalarDataSeriesFile::loadLineSeriesChartDataForColumn(const int32_t /*columnIndex*/) { CaretAssertMessage(0, "Loading of columns is not used at this time"); return NULL; } /** * Load charting data for the given row index. * * @param rowIndex * Index of the row. * @return * Pointer to the chart data. If the data FAILED to load, * the returned pointer will be NULL. Caller takes ownership * of the pointer and must delete it when no longer needed. */ ChartDataCartesian* CiftiScalarDataSeriesFile::loadLineSeriesChartDataForRow(const int32_t rowIndex) { ChartDataCartesian* chartData = NULL; try { if ((rowIndex >= 0) && (rowIndex < m_ciftiFile->getNumberOfRows())) { const int32_t numberOfElementsInRow = m_ciftiFile->getNumberOfColumns(); if (numberOfElementsInRow > 0) { std::vector rowData(numberOfElementsInRow); m_ciftiFile->getRow(&rowData[0], rowIndex); const CiftiXML& ciftiXML = m_ciftiFile->getCiftiXML(); const CiftiSeriesMap& seriesMap = ciftiXML.getSeriesMap(CiftiXML::ALONG_ROW); ChartDataTypeEnum::Enum chartDataType = ChartDataTypeEnum::CHART_DATA_TYPE_INVALID; AString timeUnitsString; switch (seriesMap.getUnit()) { case CiftiSeriesMap::HERTZ: chartDataType = ChartDataTypeEnum::CHART_DATA_TYPE_LINE_FREQUENCY_SERIES; break; case CiftiSeriesMap::METER: CaretLogWarning("CIFTI Units METER not implemented"); break; case CiftiSeriesMap::RADIAN: CaretLogWarning("CIFTI Units RADIAN not implemented"); break; case CiftiSeriesMap::SECOND: chartDataType = ChartDataTypeEnum::CHART_DATA_TYPE_LINE_TIME_SERIES; break; } if (chartDataType != ChartDataTypeEnum::CHART_DATA_TYPE_INVALID) { chartData = new ChartDataCartesian(chartDataType, ChartAxisUnitsEnum::CHART_AXIS_UNITS_NONE, ChartAxisUnitsEnum::CHART_AXIS_UNITS_NONE); if (chartData != NULL) { float timeStart = seriesMap.getStart(); float timeStep = seriesMap.getStep(); chartData->setTimeStartInSecondsAxisX(timeStart); chartData->setTimeStepInSecondsAxisX(timeStep); for (int64_t i = 0; i < numberOfElementsInRow; i++) { const float xValue = timeStart + (i * timeStep); chartData->addPoint(xValue, rowData[i]); } } } } } } catch (const DataFileException& dfe) { if (chartData != NULL) { delete chartData; chartData = NULL; } throw dfe; } return chartData; } /** * @return Is charting enabled for this file? */ bool CiftiScalarDataSeriesFile::isLineSeriesChartingEnabled(const int32_t tabIndex) const { CaretAssertArrayIndex(m_lineSeriesChartingEnabledForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_lineSeriesChartingEnabledForTab[tabIndex]; } /** * @return Return true if the file's current state supports * charting data, else false. Typically a brainordinate file * is chartable if it contains more than one map. */ bool CiftiScalarDataSeriesFile::isLineSeriesChartingSupported() const { if ((m_ciftiFile->getNumberOfColumns() > 0) && (m_ciftiFile->getNumberOfRows() > 0)) { return true; } return false; } /** * Get chart data types supported by the file. * * @param chartDataTypesOut * Chart types supported by this file. */ void CiftiScalarDataSeriesFile::getSupportedLineSeriesChartDataTypes(std::vector& chartDataTypesOut) const { chartDataTypesOut.clear(); const CiftiXML& ciftiXML = m_ciftiFile->getCiftiXML(); CiftiMappingType::MappingType mapType = ciftiXML.getMappingType(CiftiXML::ALONG_ROW); const AString message("Mapping type should always be SERIES for CIFTI Scalar Data Series File"); CaretAssertMessage(mapType == CiftiMappingType::SERIES, message); if (mapType == CiftiMappingType::SERIES) { const CiftiSeriesMap& seriesMap = ciftiXML.getSeriesMap(CiftiXML::ALONG_ROW); switch (seriesMap.getUnit()) { case CiftiSeriesMap::HERTZ: chartDataTypesOut.push_back(ChartDataTypeEnum::CHART_DATA_TYPE_LINE_FREQUENCY_SERIES); break; case CiftiSeriesMap::METER: CaretLogWarning("CIFTI Units METER not implemented"); break; case CiftiSeriesMap::RADIAN: CaretLogWarning("CIFTI Units RADIAN not implemented"); break; case CiftiSeriesMap::SECOND: chartDataTypesOut.push_back(ChartDataTypeEnum::CHART_DATA_TYPE_LINE_TIME_SERIES); break; } } else { CaretLogSevere(message); } } /** * Set charting enabled for this file. * * @param enabled * New status for charting enabled. */ void CiftiScalarDataSeriesFile::setLineSeriesChartingEnabled(const int32_t tabIndex, const bool enabled) { CaretAssertArrayIndex(m_lineSeriesChartingEnabledForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_lineSeriesChartingEnabledForTab[tabIndex] = enabled; } /** * Save file data from the scene. For subclasses that need to * save to a scene, this method should be overriden. sceneClass * will be valid and any scene data should be added to it. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass to which data members should be added. */ void CiftiScalarDataSeriesFile::saveFileDataToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass) { m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); /* * Save chart matrix properties */ SceneObjectMapIntegerKey* chartMatrixPropertiesMap = new SceneObjectMapIntegerKey("m_chartMatrixDisplayPropertiesMap", SceneObjectDataTypeEnum::SCENE_CLASS); const std::vector tabIndices = sceneAttributes->getIndicesOfTabsForSavingToScene(); for (std::vector::const_iterator tabIter = tabIndices.begin(); tabIter != tabIndices.end(); tabIter++) { const int32_t tabIndex = *tabIter; chartMatrixPropertiesMap->addClass(tabIndex, m_chartMatrixDisplayPropertiesForTab[tabIndex]->saveToScene(sceneAttributes, "m_chartMatrixDisplayProperties")); } sceneClass->addChild(chartMatrixPropertiesMap); } /** * Restore file data from the scene. For subclasses that need to * restore from a scene, this method should be overridden. The scene class * will be valid and any scene data may be obtained from it. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass for the instance of a class that implements * this interface. Will NEVER be NULL. */ void CiftiScalarDataSeriesFile::restoreFileDataFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); /* * Restore chart matrix properties */ const SceneObjectMapIntegerKey* chartMatrixPropertiesMap = sceneClass->getMapIntegerKey("m_chartMatrixDisplayPropertiesMap"); if (chartMatrixPropertiesMap != NULL) { const std::vector tabIndices = chartMatrixPropertiesMap->getKeys(); for (std::vector::const_iterator tabIter = tabIndices.begin(); tabIter != tabIndices.end(); tabIter++) { const int32_t tabIndex = *tabIter; const SceneClass* sceneClass = chartMatrixPropertiesMap->classValue(tabIndex); m_chartMatrixDisplayPropertiesForTab[tabIndex]->restoreFromScene(sceneAttributes, sceneClass); } } } workbench-1.1.1/src/Files/CiftiScalarDataSeriesFile.h000066400000000000000000000126151255417355300224460ustar00rootroot00000000000000#ifndef __CIFTI_SCALAR_DATA_SERIES_FILE_H__ #define __CIFTI_SCALAR_DATA_SERIES_FILE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "BrainConstants.h" #include "ChartableLineSeriesRowColumnInterface.h" #include "ChartableMatrixSeriesInterface.h" #include "CiftiMappableDataFile.h" #include "EventListenerInterface.h" namespace caret { class SceneClassAssistant; class CiftiScalarDataSeriesFile : public CiftiMappableDataFile, public ChartableLineSeriesRowColumnInterface, public ChartableMatrixSeriesInterface, public EventListenerInterface { public: CiftiScalarDataSeriesFile(); virtual ~CiftiScalarDataSeriesFile(); virtual MapYokingGroupEnum::Enum getMatrixRowColumnMapYokingGroup(const int32_t tabIndex) const; virtual void setMatrixRowColumnMapYokingGroup(const int32_t tabIndex, const MapYokingGroupEnum::Enum yokingType); virtual int32_t getSelectedMapIndex(const int32_t tabIndex); virtual void setSelectedMapIndex(const int32_t tabIndex, const int32_t mapIndex); virtual void receiveEvent(Event* event); virtual void getMatrixDimensions(int32_t& numberOfRowsOut, int32_t& numberOfColumnsOut) const; virtual bool getMatrixDataRGBA(int32_t& numberOfRowsOut, int32_t& numberOfColumnsOut, std::vector& rgbaOut) const; virtual bool getMatrixCellAttributes(const int32_t rowIndex, const int32_t columnIndex, AString& cellValueOut, AString& rowNameOut, AString& columnNameOut) const; virtual bool isMatrixChartingEnabled(const int32_t tabIndex) const; virtual bool isMatrixChartingSupported() const; virtual void setMatrixChartingEnabled(const int32_t tabIndex, const bool enabled); virtual void getSupportedMatrixChartDataTypes(std::vector& chartDataTypesOut) const; const ChartMatrixDisplayProperties* getChartMatrixDisplayProperties(const int32_t tabIndex) const; ChartMatrixDisplayProperties* getChartMatrixDisplayProperties(const int32_t tabIndex); virtual bool isLineSeriesChartingEnabled(const int32_t tabIndex) const; virtual void setLineSeriesChartingEnabled(const int32_t tabIndex, const bool enabled); virtual bool isLineSeriesChartingSupported() const; virtual ChartDataCartesian* loadLineSeriesChartDataForColumn(const int32_t columnIndex); virtual ChartDataCartesian* loadLineSeriesChartDataForRow(const int32_t rowIndex); virtual void getSupportedLineSeriesChartDataTypes(std::vector& chartDataTypesOut) const; // ADD_NEW_METHODS_HERE protected: virtual void saveFileDataToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass); virtual void restoreFileDataFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private: CiftiScalarDataSeriesFile(const CiftiScalarDataSeriesFile&); CiftiScalarDataSeriesFile& operator=(const CiftiScalarDataSeriesFile&); SceneClassAssistant* m_sceneAssistant; /** yoking status */ MapYokingGroupEnum::Enum m_yokingGroupForTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; bool m_lineSeriesChartingEnabledForTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; bool m_matrixChartingEnabledForTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; ChartMatrixDisplayProperties* m_chartMatrixDisplayPropertiesForTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; int32_t m_selectedMapIndices[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; // ADD_NEW_MEMBERS_HERE }; #ifdef __CIFTI_SCALAR_DATA_SERIES_FILE_DECLARE__ // #endif // __CIFTI_SCALAR_DATA_SERIES_FILE_DECLARE__ } // namespace #endif //__CIFTI_SCALAR_DATA_SERIES_FILE_H__ workbench-1.1.1/src/Files/ConnectivityDataLoaded.cxx000066400000000000000000000360271255417355300224540ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CONNECTIVITY_DATA_LOADED_DECLARE__ #include "ConnectivityDataLoaded.h" #undef __CONNECTIVITY_DATA_LOADED_DECLARE__ #include "CaretAssert.h" #include "SceneClassAssistant.h" #include "SceneAttributes.h" #include "SceneClass.h" #include "ScenePrimitiveArray.h" #include "SurfaceFile.h" using namespace caret; /** * \class caret::ConnectivityDataLoaded * \brief Maintains information on loaded brainordinate data. * \ingroup Brain */ /** * Constructor. */ ConnectivityDataLoaded::ConnectivityDataLoaded() : CaretObject() { reset(); m_sceneAssistant = new SceneClassAssistant(); m_sceneAssistant->add("m_rowIndex", &m_rowIndex); m_sceneAssistant->add("m_columnIndex", &m_columnIndex); m_sceneAssistant->add("m_surfaceNumberOfNodes", &m_surfaceNumberOfNodes); m_sceneAssistant->add("m_surfaceStructure", &m_surfaceStructure); m_sceneAssistant->addArray("m_volumeDimensionsIJK", m_volumeDimensionsIJK, 3, -1); reset(); } /** * Destructor. */ ConnectivityDataLoaded::~ConnectivityDataLoaded() { reset(); delete m_sceneAssistant; } /** * Reset the data. */ void ConnectivityDataLoaded::reset() { m_mode = MODE_NONE; m_rowIndex = -1; m_columnIndex = -1; m_surfaceNodeIndices.clear(); m_surfaceNumberOfNodes = 0; m_surfaceStructure = StructureEnum::INVALID; m_voxelIndices.clear(); } /** * @return The mode. */ ConnectivityDataLoaded::Mode ConnectivityDataLoaded::getMode() const { return m_mode; } /** * Get the row that were loaded. * * @param rowIndex * Row that was loaded (may be -1 if none). * @param columnIndex * Column that was loaded (may be -1 if none). */ void ConnectivityDataLoaded::getRowColumnLoading(int64_t& rowIndex, int64_t& columnIndex) const { rowIndex = m_rowIndex; columnIndex = m_columnIndex; } /** * Set the row that were loaded. * * @param rowIndex * Row that was loaded (may be -1 if none). * @param columnIndex * Column that was loaded (may be -1 if none) */ void ConnectivityDataLoaded::setRowColumnLoading(const int64_t rowIndex, const int64_t columnIndex) { reset(); if (rowIndex >= 0) { m_mode = MODE_ROW; m_rowIndex = rowIndex; } else if (columnIndex >= 0) { m_mode = MODE_COLUMN; m_columnIndex = columnIndex; } else { CaretAssertMessage(0, "One or row index or column index should be negative indicating that dimension was not loaded."); } } /** * Get the surface loading information (MODE_SURFACE_NODE) * One of rowIndex or columnIndex will be negative. * * @param structure * The surface structure. * @param surfaceNumberOfNodes * Number of nodes in surface. * @param surfaceNodeIndex * Index of the surface node. * @param rowIndex * Index of row corresponding to the surface node (may be -1 if none). * @param columnIndex * Index of row corresponding to the surface node (may be -1 if none). */ void ConnectivityDataLoaded::getSurfaceNodeLoading(StructureEnum::Enum& structure, int32_t& surfaceNumberOfNodes, int32_t& surfaceNodeIndex, int64_t& rowIndex, int64_t& columnIndex) const { structure = m_surfaceStructure; surfaceNumberOfNodes = m_surfaceNumberOfNodes; if (m_surfaceNodeIndices.empty() == false) { surfaceNodeIndex = m_surfaceNodeIndices[0]; } else { surfaceNodeIndex = -1; } rowIndex = m_rowIndex; columnIndex = m_columnIndex; } /** * Set the surface loading information (MODE_SURFACE_NODE) * One of rowIndex or columnIndex must be negative. * * @param structure * The surface structure. * @param surfaceNumberOfNodes * Number of nodes in surface. * @param surfaceNodeIndex * Index of the surface node. * @param rowIndex * Index of row corresponding to the surface node (may be -1 if none). * @param columnIndex * Index of column corresponding to the surface node (may be -1 if none). */ void ConnectivityDataLoaded::setSurfaceNodeLoading(const StructureEnum::Enum structure, const int32_t surfaceNumberOfNodes, const int32_t surfaceNodeIndex, const int64_t rowIndex, const int64_t columnIndex) { reset(); m_mode = MODE_SURFACE_NODE; m_surfaceStructure = structure; m_surfaceNumberOfNodes = surfaceNumberOfNodes; m_surfaceNodeIndices.push_back(surfaceNodeIndex); m_rowIndex = rowIndex; m_columnIndex = columnIndex; if ((rowIndex >= 0) && (columnIndex >= 0)) { CaretAssertMessage(0, "One of row or column index must be negative."); } } /** * Get the surface average node loading information (MODE_SURFACE_NODE_AVERAGE) * * @param structure * The surface structure. * @param surfaceNumberOfNodes * Number of nodes in surface. * @param surfaceNodeIndices * Indices of the surface nodes. */ void ConnectivityDataLoaded::getSurfaceAverageNodeLoading(StructureEnum::Enum& structure, int32_t& surfaceNumberOfNodes, std::vector& surfaceNodeIndices) const { structure = m_surfaceStructure; surfaceNumberOfNodes = m_surfaceNumberOfNodes; surfaceNodeIndices = m_surfaceNodeIndices; } /** * Set the surface average node loading information (MODE_SURFACE_NODE_AVERAGE) * * @param structure * The surface structure. * @param surfaceNumberOfNodes * Number of nodes in surface. * @param surfaceNodeIndices * Indices of the surface nodes. */ void ConnectivityDataLoaded::setSurfaceAverageNodeLoading(const StructureEnum::Enum structure, const int32_t surfaceNumberOfNodes, const std::vector& surfaceNodeIndices) { reset(); m_mode = MODE_SURFACE_NODE_AVERAGE; m_surfaceStructure = structure; m_surfaceNumberOfNodes = surfaceNumberOfNodes; m_surfaceNodeIndices = surfaceNodeIndices; m_rowIndex = -1; m_columnIndex = -1; } /** * Get the volume loading XYZ coordinate (MODE_VOXEL_XYZ). * * @param volumeXYZ * Coordinate of location. * @param rowIndex * Index of row corresponding to the voxel (may be -1 if none). * @param columnIndex * Index of column corresponding to the voxel (may be -1 if none). */ void ConnectivityDataLoaded::getVolumeXYZLoading(float volumeXYZ[3], int64_t& rowIndex, int64_t& columnIndex) const { volumeXYZ[0] = m_volumeXYZ[0]; volumeXYZ[1] = m_volumeXYZ[1]; volumeXYZ[2] = m_volumeXYZ[2]; rowIndex = m_rowIndex; columnIndex = m_columnIndex; } /** * Set the volume loading XYZ coordinate (MODE_VOXEL_XYZ). * * @param volumeXYZ * Coordinate of location. * @param rowIndex * Index of row corresponding to the voxel(may be -1 if none). * @param columnIndex * Index of column corresponding to the voxel (may be -1 if none). */ void ConnectivityDataLoaded::setVolumeXYZLoading(const float volumeXYZ[3], const int64_t rowIndex, const int64_t columnIndex) { reset(); m_mode = MODE_VOXEL_XYZ; m_volumeXYZ[0] = volumeXYZ[0]; m_volumeXYZ[1] = volumeXYZ[1]; m_volumeXYZ[2] = volumeXYZ[2]; m_rowIndex = rowIndex; m_columnIndex = columnIndex; } /** * Get the voxel average loading voxel IJK indices (MODE_VOXEL_IJK_AVERAGE) * * @param voxelIndicesIJK * Indices of the voxels. */ void ConnectivityDataLoaded::getVolumeAverageVoxelLoading(int64_t volumeDimensionsIJK[3], std::vector& voxelIndicesIJK) const { volumeDimensionsIJK[0] = m_volumeDimensionsIJK[0]; volumeDimensionsIJK[1] = m_volumeDimensionsIJK[1]; volumeDimensionsIJK[2] = m_volumeDimensionsIJK[2]; voxelIndicesIJK = m_voxelIndices; } /** * Set the voxel average loading voxel IJK indices (MODE_VOXEL_IJK_AVERAGE) * * @param voxelIndicesIJK * Indices of the voxels. */ void ConnectivityDataLoaded::setVolumeAverageVoxelLoading(const int64_t volumeDimensionsIJK[3], const std::vector& voxelIndicesIJK) { reset(); m_volumeDimensionsIJK[0] = volumeDimensionsIJK[0]; m_volumeDimensionsIJK[1] = volumeDimensionsIJK[1]; m_volumeDimensionsIJK[2] = volumeDimensionsIJK[2]; m_mode = MODE_VOXEL_IJK_AVERAGE; m_voxelIndices = voxelIndicesIJK; m_rowIndex = -1; m_columnIndex = -1; } /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass for the instance of a class that implements * this interface. May be NULL for some types of scenes. */ void ConnectivityDataLoaded::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { reset(); if (sceneClass == NULL) { return; } m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); m_mode = MODE_NONE; const AString modeName = sceneClass->getStringValue("m_mode"); if (modeName == "MODE_NONE") { m_mode = MODE_NONE; } else if (modeName == "MODE_ROW") { m_mode = MODE_ROW; } else if (modeName == "MODE_COLUMN") { m_mode = MODE_COLUMN; } else if (modeName == "MODE_SURFACE_NODE_AVERAGE") { m_mode = MODE_SURFACE_NODE_AVERAGE; } else if (modeName == "MODE_SURFACE_NODE") { m_mode = MODE_SURFACE_NODE; } else if (modeName == "MODE_VOXEL_XYZ") { m_mode = MODE_VOXEL_XYZ; } else if (modeName == "MODE_VOXEL_IJK_AVERAGE") { m_mode = MODE_VOXEL_IJK_AVERAGE; } else { sceneAttributes->addToErrorMessage("Unrecognized mode=" + modeName); return; } const ScenePrimitiveArray* surfaceNodeIndicesArray = sceneClass->getPrimitiveArray("m_surfaceNodeIndices"); if (surfaceNodeIndicesArray != NULL) { const int32_t numNodeIndices = surfaceNodeIndicesArray->getNumberOfArrayElements(); m_surfaceNodeIndices.reserve(numNodeIndices); for (int32_t i = 0; i < numNodeIndices; i++) { m_surfaceNodeIndices.push_back(surfaceNodeIndicesArray->integerValue(i)); } } const ScenePrimitiveArray* voxelIndicesArray = sceneClass->getPrimitiveArray("m_voxelIndices"); if (voxelIndicesArray != NULL) { const int64_t numIndices = voxelIndicesArray->getNumberOfArrayElements(); const int64_t numVoxelIndices = numIndices / 3; for (int64_t i = 0; i < numVoxelIndices; i++) { const int64_t i3 = i * 3; VoxelIJK voxelIJK(voxelIndicesArray->integerValue(i3), voxelIndicesArray->integerValue(i3 + 1), voxelIndicesArray->integerValue(i3 + 2)); m_voxelIndices.push_back(voxelIJK); } } } /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ SceneClass* ConnectivityDataLoaded::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "ConnectivityDataLoaded", 1); m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); AString modeName = "MODE_NONE"; switch (m_mode) { case MODE_NONE: modeName = "MODE_NONE"; break; case MODE_ROW: modeName = "MODE_ROW"; break; case MODE_COLUMN: modeName = "MODE_COLUMN"; break; case MODE_SURFACE_NODE: modeName = "MODE_SURFACE_NODE"; break; case MODE_SURFACE_NODE_AVERAGE: modeName = "MODE_SURFACE_NODE_AVERAGE"; break; case MODE_VOXEL_XYZ: modeName = "MODE_VOXEL_XYZ"; break; case MODE_VOXEL_IJK_AVERAGE: modeName = "MODE_VOXEL_IJK_AVERAGE"; break; } sceneClass->addString("m_mode", modeName); if (m_surfaceNodeIndices.empty() == false) { sceneClass->addIntegerArray("m_surfaceNodeIndices", &m_surfaceNodeIndices[0], m_surfaceNodeIndices.size()); } if (m_voxelIndices.empty() == false) { const int32_t numVoxels = m_voxelIndices.size(); std::vector indices; for (int32_t i = 0; i < numVoxels; i++) { indices.push_back(m_voxelIndices[i].m_ijk[0]); indices.push_back(m_voxelIndices[i].m_ijk[1]); indices.push_back(m_voxelIndices[i].m_ijk[2]); } sceneClass->addIntegerArray("m_voxelIndices", &indices[0], indices.size()); } return sceneClass; } workbench-1.1.1/src/Files/ConnectivityDataLoaded.h000066400000000000000000000115171255417355300220760ustar00rootroot00000000000000#ifndef __CONNECTIVITY_DATA_LOADED_H__ #define __CONNECTIVITY_DATA_LOADED_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "SceneableInterface.h" #include "StructureEnum.h" #include "VoxelIJK.h" namespace caret { class SceneClassAssistant; class SurfaceFile; class ConnectivityDataLoaded : public CaretObject, public SceneableInterface { public: ConnectivityDataLoaded(); virtual ~ConnectivityDataLoaded(); private: ConnectivityDataLoaded(const ConnectivityDataLoaded&); ConnectivityDataLoaded& operator=(const ConnectivityDataLoaded&); public: enum Mode { MODE_NONE, MODE_ROW, MODE_COLUMN, MODE_SURFACE_NODE, MODE_SURFACE_NODE_AVERAGE, MODE_VOXEL_XYZ, MODE_VOXEL_IJK_AVERAGE }; void reset(); Mode getMode() const; void getRowColumnLoading(int64_t& rowIndex, int64_t& columnIndex) const; void setRowColumnLoading(const int64_t rowIndex, const int64_t columnIndex); void getSurfaceNodeLoading(StructureEnum::Enum& structure, int32_t& surfaceNumberOfNodes, int32_t& surfaceNodeIndex, int64_t& rowIndex, int64_t& columnIndex) const; void setSurfaceNodeLoading(const StructureEnum::Enum structure, const int32_t surfaceNumberOfNodes, const int32_t surfaceNodeIndex, const int64_t rowIndex, const int64_t columnIndex); void getSurfaceAverageNodeLoading(StructureEnum::Enum& structure, int32_t& surfaceNumberOfNode, std::vector& surfaceNodeIndices) const; void setSurfaceAverageNodeLoading(const StructureEnum::Enum structure, const int32_t surfaceNumberOfNodes, const std::vector& surfaceNodeIndices); void getVolumeXYZLoading(float volumeXYZ[3], int64_t& rowIndex, int64_t& columnIndex) const; void setVolumeXYZLoading(const float volumeXYZ[3], const int64_t rowIndex, const int64_t columnIndex); void getVolumeAverageVoxelLoading(int64_t volumeDimensionsIJK[3], std::vector& voxelIndicesIJK) const; void setVolumeAverageVoxelLoading(const int64_t volumeDimensionsIJK[3], const std::vector& voxelIndicesIJK); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); private: // ADD_NEW_MEMBERS_HERE SceneClassAssistant* m_sceneAssistant; Mode m_mode; int32_t m_rowIndex; int32_t m_columnIndex; StructureEnum::Enum m_surfaceStructure; int32_t m_surfaceNumberOfNodes; std::vector m_surfaceNodeIndices; std::vector m_voxelIndices; int32_t m_volumeDimensionsIJK[3]; float m_volumeXYZ[3]; }; #ifdef __CONNECTIVITY_DATA_LOADED_DECLARE__ // #endif // __CONNECTIVITY_DATA_LOADED_DECLARE__ } // namespace #endif // __CONNECTIVITY_DATA_LOADED_H__ workbench-1.1.1/src/Files/EventCaretMappableDataFilesGet.cxx000066400000000000000000000054201255417355300240030ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretAssert.h" #include "CaretMappableDataFile.h" #include "EventCaretMappableDataFilesGet.h" using namespace caret; /** * Constructor for ALL map data files. */ EventCaretMappableDataFilesGet::EventCaretMappableDataFilesGet() : Event(EventTypeEnum::EVENT_CARET_MAPPABLE_DATA_FILES_GET), m_mode(MODE_ANY_DATA_FILE_TYPE), m_oneDataFileType(DataFileTypeEnum::UNKNOWN) { } /** * Constructor for map data files of the given file type. * * @param dataFileType * Type of data files requested. */ EventCaretMappableDataFilesGet::EventCaretMappableDataFilesGet(const DataFileTypeEnum::Enum dataFileType) : Event(EventTypeEnum::EVENT_CARET_MAPPABLE_DATA_FILES_GET), m_mode(MODE_ONE_DATA_FILE_TYPE), m_oneDataFileType(dataFileType) { } /** * Destructor. */ EventCaretMappableDataFilesGet::~EventCaretMappableDataFilesGet() { } /** * Add a map data file. * @param mapDataFile * Map data file that is added. */ void EventCaretMappableDataFilesGet::addFile(CaretMappableDataFile* mapDataFile) { CaretAssert(mapDataFile); if (mapDataFile->getNumberOfMaps() <= 0) { return; } const DataFileTypeEnum::Enum mapDataFileType = mapDataFile->getDataFileType(); /* * No surface files */ if (mapDataFileType == DataFileTypeEnum::SURFACE) { return; } /* * Based upon mode, perform additional filtering of file data type */ switch (m_mode) { case MODE_ONE_DATA_FILE_TYPE: if (mapDataFileType != m_oneDataFileType) { return; } break; case MODE_ANY_DATA_FILE_TYPE: break; } m_allCaretMappableDataFiles.push_back(mapDataFile); } /** * Get all map data files. * * @param allFilesOut * All map data files output. */ void EventCaretMappableDataFilesGet::getAllFiles(std::vector& allFilesOut) const { allFilesOut = m_allCaretMappableDataFiles; } workbench-1.1.1/src/Files/EventCaretMappableDataFilesGet.h000066400000000000000000000040431255417355300234300ustar00rootroot00000000000000#ifndef __EVENT_CARET_MAPPABLE_DATA_FILES_GET_H__ #define __EVENT_CARET_MAPPABLE_DATA_FILES_GET_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "Event.h" namespace caret { class CaretMappableDataFile; /// Event that gets all caret mappable data files. class EventCaretMappableDataFilesGet : public Event { public: EventCaretMappableDataFilesGet(); EventCaretMappableDataFilesGet(const DataFileTypeEnum::Enum dataFileType); virtual ~EventCaretMappableDataFilesGet(); void addFile(CaretMappableDataFile* mapDataFile); void getAllFiles(std::vector& allFilesOut) const; private: enum Mode { MODE_ANY_DATA_FILE_TYPE, MODE_ONE_DATA_FILE_TYPE }; EventCaretMappableDataFilesGet(const EventCaretMappableDataFilesGet&); EventCaretMappableDataFilesGet& operator=(const EventCaretMappableDataFilesGet&); const Mode m_mode; const DataFileTypeEnum::Enum m_oneDataFileType; std::vector m_allCaretMappableDataFiles; }; } // namespace #endif // __EVENT_CARET_MAPPABLE_DATA_FILES_GET_H__ workbench-1.1.1/src/Files/EventChartMatrixParcelYokingValidation.cxx000066400000000000000000000175011255417355300256420ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __EVENT_CHART_MATRIX_PARCEL_LOADING_YOKING_DECLARE__ #include "EventChartMatrixParcelYokingValidation.h" #undef __EVENT_CHART_MATRIX_PARCEL_LOADING_YOKING_DECLARE__ #include "CaretAssert.h" #include "CaretMappableDataFile.h" #include "ChartableMatrixParcelInterface.h" #include "EventTypeEnum.h" using namespace caret; /** * \class caret::EventChartMatrixParcelYokingValidation * \brief Event for coordination of Matrix Chart yoking. * \ingroup Files */ /** * Constructor for validating that the given file is compatible with any * current yoking for the given yoking group. After sending this event, * call isYokingCompatible() to validate the yoking selection. * * @param chartableInterface * The file for which yoking compatibility is verified. * @param yokingGroup * The selected yoking group. */ EventChartMatrixParcelYokingValidation::EventChartMatrixParcelYokingValidation(const ChartableMatrixParcelInterface* chartableInterface, const YokingGroupEnum::Enum yokingGroup) : Event(EventTypeEnum::EVENT_CHART_MATRIX_YOKING_VALIDATION), m_mode(MODE_VALIDATE_YOKING), m_chartableInterface(chartableInterface), m_yokingGroup(yokingGroup) { CaretAssert(chartableInterface); } /** * Destructor. */ EventChartMatrixParcelYokingValidation::~EventChartMatrixParcelYokingValidation() { } /** * @return The mode of this event (apply or validate). */ EventChartMatrixParcelYokingValidation::Mode EventChartMatrixParcelYokingValidation::getMode() const { return m_mode; } /** * Add chartable interface for validating yoking compatibility. * * @param chartableInterface * A file for which yoking compatibility is verified with the file passed * to the constructor. * @param selectedRowOrColumnIndex * Selected row or column index in the file. */ void EventChartMatrixParcelYokingValidation::addValidateYokingChartableInterface(const ChartableMatrixParcelInterface* chartableInterface, const int32_t selectedRowOrColumnIndex) { CaretAssert(chartableInterface); int32_t numberOfRowsColumns = -1; AString loadingDimensionName; int32_t numRows = -1; int32_t numCols = -1; m_chartableInterface->getMatrixDimensions(numRows, numCols); switch (m_chartableInterface->getMatrixLoadingDimension()) { case ChartMatrixLoadingDimensionEnum::CHART_MATRIX_LOADING_BY_COLUMN: numberOfRowsColumns = numCols; loadingDimensionName = "columns"; break; case ChartMatrixLoadingDimensionEnum::CHART_MATRIX_LOADING_BY_ROW: numberOfRowsColumns = numRows; loadingDimensionName = "rows"; break; } if (chartableInterface != m_chartableInterface) { if ((chartableInterface->getYokingGroup() == m_yokingGroup) && (chartableInterface->getYokingGroup() != YokingGroupEnum::YOKING_GROUP_OFF)) { int32_t chartInterNumRowsColumns = -1; AString chartDimensionsName; int32_t chartRows = -1; int32_t chartCols = -1; chartableInterface->getMatrixDimensions(chartRows, chartCols); switch (chartableInterface->getMatrixLoadingDimension()) { case ChartMatrixLoadingDimensionEnum::CHART_MATRIX_LOADING_BY_COLUMN: chartInterNumRowsColumns = chartCols; chartDimensionsName = "columns"; break; case ChartMatrixLoadingDimensionEnum::CHART_MATRIX_LOADING_BY_ROW: chartInterNumRowsColumns = chartRows; chartDimensionsName = "rows"; break; } if (numberOfRowsColumns != chartInterNumRowsColumns) { if (m_incompatibilityMessage.isEmpty()) { m_incompatibilityMessage.appendWithNewLine(m_chartableInterface->getMatrixChartCaretMappableDataFile()->getFileNameNoPath() + " is loading from " + AString::number(numberOfRowsColumns) + " " + loadingDimensionName); m_incompatibilityMessage.appendWithNewLine("and is incompatible with: "); } m_incompatibilityMessage.appendWithNewLine(" " + chartableInterface->getMatrixChartCaretMappableDataFile()->getFileNameNoPath() + " is loading from " + AString::number(chartInterNumRowsColumns) + " " + chartDimensionsName); } /* * For the map key is row/column index and value is number of times that * row column index is used. */ std::map::iterator iter = m_compatibleRowColumnIndicesCount.find(selectedRowOrColumnIndex); if (iter != m_compatibleRowColumnIndicesCount.end()) { iter->second++; } else { m_compatibleRowColumnIndicesCount.insert(std::make_pair(selectedRowOrColumnIndex, 1)); } } } } /** * Is the yoking for the file passed to the constructor validated with * other files yoked to the yoking group? * * @param messageOut * Contains information on compatibility. * @param selectedRowOrColumnIndexOut * Row or column index that should be selected if yoking is compatible. * @return * True if compatible, else false. */ bool EventChartMatrixParcelYokingValidation::isValidateYokingCompatible(AString& messageOut, int32_t& selectedRowOrColumnIndexOut) const { selectedRowOrColumnIndexOut = -1; messageOut = m_incompatibilityMessage; /* * For the map key is row/column index and value is number of times that * row column index is used. */ int32_t maxCount = 0; for (std::map::const_iterator iter = m_compatibleRowColumnIndicesCount.begin(); iter != m_compatibleRowColumnIndicesCount.end(); iter++) { if (iter->second > maxCount) { maxCount = iter->second; selectedRowOrColumnIndexOut = iter->first; } } if (messageOut.isEmpty()) { return true; } return false; } /** * @return Chartable interface for which this event was issued. */ const ChartableMatrixParcelInterface* EventChartMatrixParcelYokingValidation::getChartableMatrixParcelInterface() const { return m_chartableInterface; } workbench-1.1.1/src/Files/EventChartMatrixParcelYokingValidation.h000066400000000000000000000054741255417355300252750ustar00rootroot00000000000000#ifndef __EVENT_CHART_MATRIX_PARCEL_LOADING_YOKING_H__ #define __EVENT_CHART_MATRIX_PARCEL_LOADING_YOKING_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "Event.h" #include "YokingGroupEnum.h" namespace caret { class ChartableMatrixParcelInterface; class EventChartMatrixParcelYokingValidation : public Event { public: enum Mode { MODE_APPLY_YOKING, MODE_VALIDATE_YOKING }; EventChartMatrixParcelYokingValidation(const ChartableMatrixParcelInterface* chartableInterface, const YokingGroupEnum::Enum yokingGroup); virtual ~EventChartMatrixParcelYokingValidation(); Mode getMode() const; void addValidateYokingChartableInterface(const ChartableMatrixParcelInterface* chartableInterface, const int32_t selectedRowOrColumnIndex); bool isValidateYokingCompatible(AString& messageOut, int32_t& selectedRowOrColumnIndexOut) const; const ChartableMatrixParcelInterface* getChartableMatrixParcelInterface() const; // ADD_NEW_METHODS_HERE private: EventChartMatrixParcelYokingValidation(const EventChartMatrixParcelYokingValidation&); EventChartMatrixParcelYokingValidation& operator=(const EventChartMatrixParcelYokingValidation&); const Mode m_mode; const ChartableMatrixParcelInterface* m_chartableInterface; const YokingGroupEnum::Enum m_yokingGroup; AString m_incompatibilityMessage; std::map m_compatibleRowColumnIndicesCount; // ADD_NEW_MEMBERS_HERE }; #ifdef __EVENT_CHART_MATRIX_PARCEL_LOADING_YOKING_DECLARE__ // #endif // __EVENT_CHART_MATRIX_PARCEL_LOADING_YOKING_DECLARE__ } // namespace #endif //__EVENT_CHART_MATRIX_PARCEL_LOADING_YOKING_H__ workbench-1.1.1/src/Files/EventGetDisplayedDataFiles.cxx000066400000000000000000000063171255417355300232270ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __EVENT_GET_DISPLAYED_DATA_FILES_DECLARE__ #include "EventGetDisplayedDataFiles.h" #undef __EVENT_GET_DISPLAYED_DATA_FILES_DECLARE__ #include "CaretAssert.h" #include "EventTypeEnum.h" using namespace caret; /** * \class caret::EventGetDisplayedDataFiles * \brief Find data files displayed in some or all tabs. * \ingroup Files */ /** * Constructor for finding data files displayed in all tabs. */ EventGetDisplayedDataFiles::EventGetDisplayedDataFiles() : Event(EventTypeEnum::EVENT_GET_DISPLAYED_DATA_FILES) { m_allTabsMode = true; } /** * Constructor for finding data files displayed in the given tab indices. * * param tabIndices * Indices of tabs for displayed data files. */ EventGetDisplayedDataFiles::EventGetDisplayedDataFiles(const std::vector& tabIndices) : Event(EventTypeEnum::EVENT_GET_DISPLAYED_DATA_FILES) { m_allTabsMode = false; m_tabIndices.insert(tabIndices.begin(), tabIndices.end()); } /** * Destructor. */ EventGetDisplayedDataFiles::~EventGetDisplayedDataFiles() { } /** * Is the tab index one for determining displayed data files. * * @param tabIndex * Index for displayed data files. * @return * True if the tab is one determining displayed data files. */ bool EventGetDisplayedDataFiles::isTestForDisplayedDataFileInTabIndex(const int32_t tabIndex) const { if (m_allTabsMode) { return true; } if (m_tabIndices.find(tabIndex) != m_tabIndices.end()) { return true; } return false; } /** * Add the given file as a displayed data file. * * @param caretDataFile * Data file that is displayed. */ void EventGetDisplayedDataFiles::addDisplayedDataFile(const CaretDataFile* caretDataFile) { m_displayedDataFiles.insert(caretDataFile); } /* * Is the given data file displayed? * * @param caretDataFile * Caret data file for testing displayed in a tab. * @return * True if the file is displayed, else false. */ bool EventGetDisplayedDataFiles::isDataFileDisplayed(const CaretDataFile* caretDataFile) const { if (m_displayedDataFiles.find(caretDataFile) != m_displayedDataFiles.end()) { return true; } return false; } /** * @return The displayed data files in a set. */ std::set EventGetDisplayedDataFiles::getDisplayedDataFiles() const { return m_displayedDataFiles; } workbench-1.1.1/src/Files/EventGetDisplayedDataFiles.h000066400000000000000000000043111255417355300226440ustar00rootroot00000000000000#ifndef __EVENT_GET_DISPLAYED_DATA_FILES_H__ #define __EVENT_GET_DISPLAYED_DATA_FILES_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "Event.h" namespace caret { class CaretDataFile; class EventGetDisplayedDataFiles : public Event { public: EventGetDisplayedDataFiles(); EventGetDisplayedDataFiles(const std::vector& tabIndices); virtual ~EventGetDisplayedDataFiles(); bool isTestForDisplayedDataFileInTabIndex(const int32_t tabIndex) const; void addDisplayedDataFile(const CaretDataFile* caretDataFile); bool isDataFileDisplayed(const CaretDataFile* caretDataFile) const; std::set getDisplayedDataFiles() const; private: EventGetDisplayedDataFiles(const EventGetDisplayedDataFiles&); EventGetDisplayedDataFiles& operator=(const EventGetDisplayedDataFiles&); public: // ADD_NEW_METHODS_HERE private: // ADD_NEW_MEMBERS_HERE bool m_allTabsMode; std::set m_tabIndices; std::set m_displayedDataFiles; }; #ifdef __EVENT_GET_DISPLAYED_DATA_FILES_DECLARE__ // #endif // __EVENT_GET_DISPLAYED_DATA_FILES_DECLARE__ } // namespace #endif //__EVENT_GET_DISPLAYED_DATA_FILES_H__ workbench-1.1.1/src/Files/EventMapYokingSelectMap.cxx000066400000000000000000000055571255417355300225750ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __EVENT_MAP_YOKING_SELECT_MAP_DECLARE__ #include "EventMapYokingSelectMap.h" #undef __EVENT_MAP_YOKING_SELECT_MAP_DECLARE__ #include "CaretAssert.h" #include "EventTypeEnum.h" using namespace caret; /** * \class caret::EventMapYokingSelectMap * \brief Event sent when a yoked overlay or file selection changes. * \ingroup Files */ /** * Constructor for change in map yoking. * * @param caretMappableDataFile * Caret mappable file that is causing this event. * @param mapYokingGroup * Map yoking group that has a status change (selected map or enabled status) */ EventMapYokingSelectMap::EventMapYokingSelectMap(const MapYokingGroupEnum::Enum mapYokingGroup, const CaretMappableDataFile* caretMappableDataFile, const int32_t mapIndex, const bool selectionStatus) : Event(EventTypeEnum::EVENT_MAP_YOKING_SELECT_MAP), m_mapYokingGroup(mapYokingGroup), m_caretMappableDataFile(caretMappableDataFile), m_mapIndex(mapIndex), m_selectionStatus(selectionStatus) { if (mapYokingGroup != MapYokingGroupEnum::MAP_YOKING_GROUP_OFF) { MapYokingGroupEnum::setSelectedMapIndex(mapYokingGroup, mapIndex); MapYokingGroupEnum::setEnabled(mapYokingGroup, selectionStatus); } } /** * Destructor. */ EventMapYokingSelectMap::~EventMapYokingSelectMap() { } /** * @return The map yoking group. */ MapYokingGroupEnum::Enum EventMapYokingSelectMap::getMapYokingGroup() const { return m_mapYokingGroup; } /** * @return Caret Mappable Data File for which event was issued. * Might be NULL. */ const CaretMappableDataFile* EventMapYokingSelectMap::getCaretMappableDataFile() const { return m_caretMappableDataFile; } /** * @return Map index selected. */ int32_t EventMapYokingSelectMap::getMapIndex() const { return m_mapIndex; } /** * @return Selection status but ONLY for SAME FILE ! */ bool EventMapYokingSelectMap::getSelectionStatus() const { return m_selectionStatus; } workbench-1.1.1/src/Files/EventMapYokingSelectMap.h000066400000000000000000000044601255417355300222120ustar00rootroot00000000000000#ifndef __EVENT_MAP_YOKING_SELECT_MAP_H__ #define __EVENT_MAP_YOKING_SELECT_MAP_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "Event.h" #include "MapYokingGroupEnum.h" namespace caret { class CaretMappableDataFile; class EventMapYokingSelectMap : public Event { public: EventMapYokingSelectMap(const MapYokingGroupEnum::Enum mapYokingGroup, const CaretMappableDataFile* caretMappableDataFile, const int32_t mapIndex, const bool selectionStatus); virtual ~EventMapYokingSelectMap(); MapYokingGroupEnum::Enum getMapYokingGroup() const; const CaretMappableDataFile* getCaretMappableDataFile() const; int32_t getMapIndex() const; bool getSelectionStatus() const; // ADD_NEW_METHODS_HERE private: EventMapYokingSelectMap(const EventMapYokingSelectMap&); EventMapYokingSelectMap& operator=(const EventMapYokingSelectMap&); const MapYokingGroupEnum::Enum m_mapYokingGroup; const CaretMappableDataFile* m_caretMappableDataFile; const int32_t m_mapIndex; const bool m_selectionStatus; // ADD_NEW_MEMBERS_HERE }; #ifdef __EVENT_MAP_YOKING_SELECT_MAP_DECLARE__ // #endif // __EVENT_MAP_YOKING_SELECT_MAP_DECLARE__ } // namespace #endif //__EVENT_MAP_YOKING_SELECT_MAP_H__ workbench-1.1.1/src/Files/EventMapYokingValidation.cxx000066400000000000000000000153231255417355300230020ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __EVENT_MAP_YOKING_VALIDATION_DECLARE__ #include "EventMapYokingValidation.h" #undef __EVENT_MAP_YOKING_VALIDATION_DECLARE__ #include "BrainConstants.h" #include "CaretAssert.h" #include "CaretMappableDataFile.h" #include "EventBrowserTabIndicesGetAll.h" #include "EventManager.h" #include "EventTypeEnum.h" using namespace caret; /** * \class caret::EventMapYokingValidation * \brief Get map files yoked to a mapping group for validation. * \ingroup Files * * When the user selects map yoking for a file, verify that the new * file contains the same number of maps as any other files using * the same yoking group. If not, warn the user. */ /** * Constructor for validation files with map yoked to the given yoking group. * * @param mapYokingGroup * The map yoking group that will be validated for compatibility. */ EventMapYokingValidation::EventMapYokingValidation(const MapYokingGroupEnum::Enum mapYokingGroup) : Event(EventTypeEnum::EVENT_MAP_YOKING_VALIDATION), m_mapYokingGroup(mapYokingGroup) { EventBrowserTabIndicesGetAll allTabsEvent; EventManager::get()->sendEvent(allTabsEvent.getPointer()); m_validTabIndices = allTabsEvent.getAllBrowserTabIndices(); } /** * Destructor. */ EventMapYokingValidation::~EventMapYokingValidation() { } /** * Add a map file, if it is yoked to the same yoking group, so that it * may be used in the compatibility test. * * @param caretMapFile * The map file. * @param mapYokingGroup * Yoking group status of the file * @param tabIndex * Index of tab in which the file is displayed. */ void EventMapYokingValidation::addMapYokedFile(const CaretMappableDataFile* caretMapFile, const MapYokingGroupEnum::Enum mapYokingGroup, const int32_t tabIndex) { CaretAssert(caretMapFile); if (mapYokingGroup == MapYokingGroupEnum::MAP_YOKING_GROUP_OFF) { return; } if (mapYokingGroup != m_mapYokingGroup) { return; } if (std::find(m_validTabIndices.begin(), m_validTabIndices.end(), tabIndex) == m_validTabIndices.end()) { return; } m_yokedFileInfo.insert(YokedFileInfo(caretMapFile, tabIndex)); } /** * Add a map file, if it is yoked to the same yoking group, so that it * may be used in the compatibility test. * * @param caretMapFile * The map file. * @param mapYokingGroupsForAllTabs * Yoking group status of the file for all tabs. Number of elements * MUST BE BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS. */ void EventMapYokingValidation::addMapYokedFileAllTabs(const CaretMappableDataFile* caretMapFile, const MapYokingGroupEnum::Enum* mapYokingGroupsForAllTabs) { CaretAssert(caretMapFile); for (int32_t iTab = 0; iTab < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; iTab++) { if (mapYokingGroupsForAllTabs[iTab] == MapYokingGroupEnum::MAP_YOKING_GROUP_OFF) { continue; } if (mapYokingGroupsForAllTabs[iTab] != m_mapYokingGroup) { continue; } if (std::find(m_validTabIndices.begin(), m_validTabIndices.end(), iTab) == m_validTabIndices.end()) { continue; } m_yokedFileInfo.insert(YokedFileInfo(caretMapFile, iTab)); } } /** * @return The map yoking group. */ MapYokingGroupEnum::Enum EventMapYokingValidation::getMapYokingGroup() const { return m_mapYokingGroup; } /** * Validate the file for compatibility. * * @param caretMapFile * The map file. * @param numberOfYokedFilesOut * Number of files, excluding the given file, currently yoked * to this yoking group. * @param messageOut * Message containing information about any incompatibility. * @return * True if new file is compatible with any existing yoked files, else false. */ bool EventMapYokingValidation::validateCompatibility(const CaretMappableDataFile* caretMapFile, int32_t& numberOfYokedFilesOut, AString& messageOut) const { numberOfYokedFilesOut = 0; messageOut = ""; CaretAssert(caretMapFile); const int32_t numberOfMaps = caretMapFile->getNumberOfMaps(); for (std::set::const_iterator iter = m_yokedFileInfo.begin(); iter != m_yokedFileInfo.end(); iter++) { const YokedFileInfo& yfi = *iter; numberOfYokedFilesOut++; if (yfi.m_numberOfMaps != numberOfMaps) { messageOut.appendWithNewLine(" " + yfi.m_infoText); } } if (messageOut.isEmpty()) { return true; } const AString fileInfo("Incompatible number of map for yoking:\n" + AString::number(numberOfMaps) + " in " + caretMapFile->getFileNameNoPath() + "\n\n"); messageOut.insert(0, fileInfo); return false; } /** * Constructor for yoked file information. * * @param caretMapFile * The map file. * @param tabIndex * Index of tab in which the file is displayed. */ EventMapYokingValidation::YokedFileInfo::YokedFileInfo(const CaretMappableDataFile* caretMapFile, const int32_t tabIndex) : m_mapFile(caretMapFile), m_tabIndex(tabIndex) { CaretAssert(caretMapFile); m_numberOfMaps = caretMapFile->getNumberOfMaps(); m_infoText = (AString::number(m_numberOfMaps) + " maps in tab " + AString::number(tabIndex) + " file: " + caretMapFile->getFileNameNoPath()); } workbench-1.1.1/src/Files/EventMapYokingValidation.h000066400000000000000000000060321255417355300224240ustar00rootroot00000000000000#ifndef __EVENT_MAP_YOKING_VALIDATION_H__ #define __EVENT_MAP_YOKING_VALIDATION_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "Event.h" #include "MapYokingGroupEnum.h" namespace caret { class CaretMappableDataFile; class EventMapYokingValidation : public Event { public: EventMapYokingValidation(const MapYokingGroupEnum::Enum mapYokingGroup); virtual ~EventMapYokingValidation(); void addMapYokedFile(const CaretMappableDataFile* caretMapFile, const MapYokingGroupEnum::Enum mapYokingGroup, const int32_t tabIndex); void addMapYokedFileAllTabs(const CaretMappableDataFile* caretMapFile, const MapYokingGroupEnum::Enum* mapYokingGroupsForAllTabs); MapYokingGroupEnum::Enum getMapYokingGroup() const; bool validateCompatibility(const CaretMappableDataFile* caretMapFile, int32_t& numberOfYokedFilesOut, AString& messageOut) const; // ADD_NEW_METHODS_HERE private: class YokedFileInfo { public: YokedFileInfo(const CaretMappableDataFile* caretMapFile, const int32_t tabIndex); const CaretMappableDataFile* m_mapFile; const int32_t m_tabIndex; int32_t m_numberOfMaps; AString m_infoText; bool operator<(const YokedFileInfo& rhs) const { return (m_tabIndex < rhs.m_tabIndex); } }; EventMapYokingValidation(const EventMapYokingValidation&); EventMapYokingValidation& operator=(const EventMapYokingValidation&); std::set m_yokedFileInfo; std::vector m_validTabIndices; const MapYokingGroupEnum::Enum m_mapYokingGroup; // ADD_NEW_MEMBERS_HERE }; #ifdef __EVENT_MAP_YOKING_VALIDATION_DECLARE__ // #endif // __EVENT_MAP_YOKING_VALIDATION_DECLARE__ } // namespace #endif //__EVENT_MAP_YOKING_VALIDATION_H__ workbench-1.1.1/src/Files/EventPaletteGetByName.cxx000066400000000000000000000044341255417355300222240ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __EVENT_PALETTE_GET_BY_NAME_DECLARE__ #include "EventPaletteGetByName.h" #undef __EVENT_PALETTE_GET_BY_NAME_DECLARE__ #include "CaretAssert.h" #include "CaretLogger.h" #include "EventTypeEnum.h" #include "Palette.h" using namespace caret; /** * \class caret::EventPaletteGetByName * \brief Find a palette by name * \ingroup Files */ /** * Constructor. */ EventPaletteGetByName::EventPaletteGetByName(const AString& paletteName) : Event(EventTypeEnum::EVENT_PALETTE_GET_BY_NAME), m_paletteName(paletteName), m_palette(NULL) { } /** * Destructor. */ EventPaletteGetByName::~EventPaletteGetByName() { } /** * @return Name of desired palette. */ AString EventPaletteGetByName::getPaletteName() const { return m_paletteName; } /** * @return Palette that was found (NULL if no matching palette was found). */ Palette* EventPaletteGetByName::getPalette() const { return m_palette; } /** * Set the palette that matches by name. * * @param palette * Palette that matches name of desired palette. */ void EventPaletteGetByName::setPalette(Palette* palette) { CaretAssert(palette); if (palette->getName() != m_paletteName) { CaretAssertMessage(0, "Palette name does not match!"); CaretLogSevere("Palette name does not match!"); return; } if (m_palette != NULL) { CaretLogWarning("More that one palette with name " + m_paletteName); } m_palette = palette; } workbench-1.1.1/src/Files/EventPaletteGetByName.h000066400000000000000000000034501255417355300216460ustar00rootroot00000000000000#ifndef __EVENT_PALETTE_GET_BY_NAME_H__ #define __EVENT_PALETTE_GET_BY_NAME_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "Event.h" namespace caret { class Palette; class EventPaletteGetByName : public Event { public: EventPaletteGetByName(const AString& paletteName); virtual ~EventPaletteGetByName(); AString getPaletteName() const; Palette* getPalette() const; void setPalette(Palette* palette); // ADD_NEW_METHODS_HERE private: EventPaletteGetByName(const EventPaletteGetByName&); EventPaletteGetByName& operator=(const EventPaletteGetByName&); const AString m_paletteName; Palette* m_palette; // ADD_NEW_MEMBERS_HERE }; #ifdef __EVENT_PALETTE_GET_BY_NAME_DECLARE__ // #endif // __EVENT_PALETTE_GET_BY_NAME_DECLARE__ } // namespace #endif //__EVENT_PALETTE_GET_BY_NAME_H__ workbench-1.1.1/src/Files/EventSurfaceColoringInvalidate.cxx000066400000000000000000000023371255417355300241600ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretAssert.h" #include "EventSurfaceColoringInvalidate.h" using namespace caret; /** * Construct an event for invalidating surface coloring. */ EventSurfaceColoringInvalidate::EventSurfaceColoringInvalidate() : Event(EventTypeEnum::EVENT_SURFACE_COLORING_INVALIDATE) { } /** * Destructore. */ EventSurfaceColoringInvalidate::~EventSurfaceColoringInvalidate() { } workbench-1.1.1/src/Files/EventSurfaceColoringInvalidate.h000066400000000000000000000027421255417355300236050ustar00rootroot00000000000000#ifndef __EVENT_SURFACE_COLORING_INVALIDATE_H__ #define __EVENT_SURFACE_COLORING_INVALIDATE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "Event.h" namespace caret { class BrainStructure; /// Invalidate all surface coloring class EventSurfaceColoringInvalidate : public Event { public: EventSurfaceColoringInvalidate(); virtual ~EventSurfaceColoringInvalidate(); private: EventSurfaceColoringInvalidate(const EventSurfaceColoringInvalidate&); EventSurfaceColoringInvalidate& operator=(const EventSurfaceColoringInvalidate&); }; } // namespace #endif // __EVENT_SURFACE_COLORING_INVALIDATE_H__ workbench-1.1.1/src/Files/EventSurfaceStructuresValidGet.cxx000066400000000000000000000060531255417355300242050ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __EVENT_SURFACE_STRUCTURES_VALID_GET_DECLARE__ #include "EventSurfaceStructuresValidGet.h" #undef __EVENT_SURFACE_STRUCTURES_VALID_GET_DECLARE__ #include "CaretAssert.h" #include "CaretLogger.h" #include "EventTypeEnum.h" using namespace caret; /** * \class caret::EventSurfaceStructuresValidGet * \brief Get valid surface structures and their number of nodes * \ingroup Files */ /** * Constructor. */ EventSurfaceStructuresValidGet::EventSurfaceStructuresValidGet() : Event(EventTypeEnum::EVENT_SURFACE_STRUCTURES_VALID_GET) { } /** * Destructor. */ EventSurfaceStructuresValidGet::~EventSurfaceStructuresValidGet() { } /** * Add a structure and its number of nodes. If the structure already * has been added with the same number of nodes, no action is taken. * If a structure already has been added with a DIFFERENT number of nodes, * a warning will be logged. * * @param structure * The structure. * @param numberOfNodes * Number of nodes associated with the structure. */ void EventSurfaceStructuresValidGet::addStructure(const StructureEnum::Enum structure, const int32_t numberOfNodes) { std::map::iterator iter = m_structureAndNumberOfNodes.find(structure); if (iter != m_structureAndNumberOfNodes.end()) { const int32_t structNumNodes = iter->second; if (structNumNodes != numberOfNodes) { const AString message("Structure " + StructureEnum::toGuiName(structure) + " has different node counts: " + AString::number(numberOfNodes) + " and " + AString::number(structNumNodes)); CaretAssertMessage(0, message); CaretLogSevere(message); } } else { m_structureAndNumberOfNodes.insert(std::make_pair(structure, numberOfNodes)); } } /** * @return A map containing structures and their number of nodes. */ std::map EventSurfaceStructuresValidGet::getStructuresAndNumberOfNodes() const { return m_structureAndNumberOfNodes; } workbench-1.1.1/src/Files/EventSurfaceStructuresValidGet.h000066400000000000000000000037171255417355300236360ustar00rootroot00000000000000#ifndef __EVENT_SURFACE_STRUCTURES_VALID_GET_H__ #define __EVENT_SURFACE_STRUCTURES_VALID_GET_H__ /*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "Event.h" #include "StructureEnum.h" namespace caret { class EventSurfaceStructuresValidGet : public Event { public: EventSurfaceStructuresValidGet(); virtual ~EventSurfaceStructuresValidGet(); void addStructure(const StructureEnum::Enum structure, const int32_t numberOfNodes); std::map getStructuresAndNumberOfNodes() const; // ADD_NEW_METHODS_HERE private: EventSurfaceStructuresValidGet(const EventSurfaceStructuresValidGet&); EventSurfaceStructuresValidGet& operator=(const EventSurfaceStructuresValidGet&); std::map m_structureAndNumberOfNodes; // ADD_NEW_MEMBERS_HERE }; #ifdef __EVENT_SURFACE_STRUCTURES_VALID_GET_DECLARE__ // #endif // __EVENT_SURFACE_STRUCTURES_VALID_GET_DECLARE__ } // namespace #endif //__EVENT_SURFACE_STRUCTURES_VALID_GET_H__ workbench-1.1.1/src/Files/Fiber.cxx000066400000000000000000000132301255417355300171110ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #define __FIBER_DECLARE__ #include "Fiber.h" #undef __FIBER_DECLARE__ #include "AString.h" #include "CaretAssert.h" #include "CaretLogger.h" #include "MathFunctions.h" using namespace caret; /** * \class caret::Fiber * \brief Data for a single fiber */ /** * Constructor. */ Fiber::Fiber(const float* pointerToData) { m_opacityForDrawing = 1.0; /* * Retrieve values from the file */ m_meanF = pointerToData[0]; m_varF = pointerToData[1]; m_theta = pointerToData[2]; m_phi = pointerToData[3]; m_k1 = pointerToData[4]; m_k2 = pointerToData[5]; m_psi = pointerToData[6]; /* * Validate inputs */ if (m_meanF != m_meanF) { m_invalidMessage += " meanF=NaN"; } if (m_varF != m_varF) { m_invalidMessage += " varF=NaN"; } if (m_theta != m_theta) { m_invalidMessage += " theta=NaN"; } if (m_phi != m_phi) { m_invalidMessage += " phi=NaN"; } if (m_k1 != m_k1) { m_invalidMessage += " k1=NaN"; } else if (m_k1 < 0.0) { m_invalidMessage += " k1=negative=" + QString::number(m_k1); } if (m_k2 != m_k2) { m_invalidMessage += " k2=NaN"; } else if (m_k1 < 0.0) { m_invalidMessage += " k2=negative=" + QString::number(m_k2); } if (m_psi != m_psi) { m_invalidMessage += " psi=NaN"; } m_valid = m_invalidMessage.isEmpty(); if (m_valid) { /* * Set computed values used for visualization */ m_fanningMajorAxisAngle = fanningEigenvalueToAngle(m_k1); m_fanningMinorAxisAngle = fanningEigenvalueToAngle(m_k2); /* * m_theta is angle from Positive-Z Axis rotated about a line * in the XY-Plane * * m_phi is angle from positive X-Axis rotated about Z-Axis * looking to negative Z. * * NOTE: 'X' is in radiological space (positive X is left) * so flip the sign of the X-coordinate. */ m_directionUnitVector[0] = -std::sin(m_theta) * std::cos(m_phi); m_directionUnitVector[1] = std::sin(m_theta) * std::sin(m_phi); m_directionUnitVector[2] = std::cos(m_theta); /* * Use absolute values of directional unit vector as RGB color components. */ m_directionUnitVectorRGB[0] = std::fabs(m_directionUnitVector[0]); m_directionUnitVectorRGB[1] = std::fabs(m_directionUnitVector[1]); m_directionUnitVectorRGB[2] = std::fabs(m_directionUnitVector[2]); // const float nearZero = 0.0001; // float oppositePhi = 0.0; // if (std::fabs(m_directionUnitVector[0]) > nearZero) { // oppositePhi = M_PI - std::atan2(-m_directionUnitVector[1], // -m_directionUnitVector[0]); // } // else { // oppositePhi = M_PI_2; // } // // float oppositeTheta = std::acos(-m_directionUnitVector[2]); // // const float radiansToDegrees = 180.0 / M_PI; // std::cout << "Vector: " << qPrintable(AString::fromNumbers(m_directionUnitVector, 3, ",")) << std::endl; // std::cout << "Theta/OppTheta/Phi/OppPhi: " // << (m_theta * radiansToDegrees) << " " // << (oppositeTheta * radiansToDegrees) << " " // << (m_phi * radiansToDegrees) << " " // << (oppositePhi * radiansToDegrees) << std::endl << std::endl; } } void vectorToAngles(const float vector[3], float& azimuthOut, float& elevationOut) { const float nearZero = 0.001; azimuthOut = 0.0; if (std::fabs(vector[0]) > nearZero) { azimuthOut = std::atan2(vector[1], vector[0]); } else if (vector[1] > nearZero) { azimuthOut = M_PI_2; } else if (vector[1] < -nearZero) { azimuthOut = -M_PI_2; } if (azimuthOut > M_PI_2) azimuthOut -= M_PI_2; else if (azimuthOut < -M_PI_2) azimuthOut += M_PI_2; elevationOut = std::acos(vector[2]); } /** * Destructor. */ Fiber::~Fiber() { } /** * Convert the fanning eigenvalue to an angle. * @param eigenvalue * The eigenvalue * @return * Angle derived from the eigenvalue. */ float Fiber::fanningEigenvalueToAngle(const float eigenvalue) { float angle = 0.0; if (eigenvalue > 0.0) { float sigma = 1.0 / std::sqrt(2.0 * eigenvalue); if (sigma > 1.0) sigma = 1.0; angle = std::asin(sigma); angle = angle / 2; if (angle < 0.0) { angle = -angle; } } else { CaretLogSevere("Have a negative eigenvalue=" + AString::number(eigenvalue) + " for a fiber."); } return angle; } workbench-1.1.1/src/Files/Fiber.h000066400000000000000000000100061255417355300165340ustar00rootroot00000000000000#ifndef __FIBER_H__ #define __FIBER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include namespace caret { /** * \struct caret::Fiber * \brief Attributes of a single fiber */ class Fiber { public: Fiber(const float* pointerToData); ~Fiber(); /** * Spatial magnitude of distribution/distance from center * This value is from the fiber orientation file. */ float m_meanF; /** * Spatial variance in magnitude of distribution/distance from center * This value is from the fiber orientation file. */ float m_varF; /** * First spatial angle of distribution. * Angle from Positive Z-Axis rotated about a line * in the XY-Plane * Units is radians. * This value is from the fiber orientation file. */ float m_theta; /** * Second spatial angle of distribution. * Aximuthal angle in X-Y Plane, counter-clockwise * around positive Z-Axis starting at positive X-Axis. * Units is radians. * This value is from the fiber orientation file. */ float m_phi; /** * Major fanning eigenvalue * This value is from the fiber orientation file. */ float m_k1; /** * Minor fanning eigenvalue * This value is from the fiber orientation file. */ float m_k2; /** * Angle of anisotropy in uncertainty/fanning distribution on sphere * Units is radians. * This value is from the fiber orientation file. */ float m_psi; /** * Angle of fanning for the major axis. * Units is radians. * This value is computed and is NOT from the fiber orientation file. */ float m_fanningMajorAxisAngle; /** * Angle of fanning for the minor axis. * Units is radians. * This value is computed and is NOT from the fiber orientation file. */ float m_fanningMinorAxisAngle; /** * Directional unit vector of fiber */ float m_directionUnitVector[3]; /** * RGB Color for directional unit vector of fiber */ float m_directionUnitVectorRGB[3]; /** * True if the fiber is valid, else false. */ bool m_valid; /** * Describes why fiber is invalid. */ QString m_invalidMessage; /** * Opacity of fiber drawing used by drawing code. * This value IS NOT stored in the file. */ float m_opacityForDrawing; /** Number of elements per fiber in a fiber orientation's file */ static const int32_t NUMBER_OF_ELEMENTS_PER_FIBER_IN_FILE; private: float fanningEigenvalueToAngle(const float k); }; #ifdef __FIBER_DECLARE__ const int32_t Fiber::NUMBER_OF_ELEMENTS_PER_FIBER_IN_FILE = 7; #endif // __FIBER_DECLARE__ } // namespace #endif //__FIBER_H__ workbench-1.1.1/src/Files/FiberOrientation.cxx000066400000000000000000000045621255417355300213350ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __FIBER_ORIENTATION_DECLARE__ #include "FiberOrientation.h" #undef __FIBER_ORIENTATION_DECLARE__ #include "Fiber.h" using namespace caret; /** * \class caret::FiberOrientation * \brief Groups fibers at a particular spatial coordinate */ /** * Constructor. */ FiberOrientation::FiberOrientation(const int32_t numberOfFibers, float* pointerToData) { m_xyz[0] = pointerToData[0]; m_xyz[1] = pointerToData[1]; m_xyz[2] = pointerToData[2]; m_numberOfFibers = numberOfFibers; /* * Point to 1st element after XYZ in CIFTI file row */ float* offset = pointerToData + FiberOrientation::NUMBER_OF_ELEMENTS_IN_FILE; /* * Point to each fiber in the CIFTI file row */ for (int32_t i = 0; i < m_numberOfFibers; i++) { Fiber* fiber = new Fiber(offset); m_fibers.push_back(fiber); offset += Fiber::NUMBER_OF_ELEMENTS_PER_FIBER_IN_FILE; if (fiber->m_valid == false) { if (m_invalidMessage.isEmpty() == false) { m_invalidMessage += "; "; } m_invalidMessage += ("Index=" + AString::number(i) + ": " + fiber->m_invalidMessage); } } m_valid = m_invalidMessage.isEmpty(); } /** * Destructor. */ FiberOrientation::~FiberOrientation() { for (int32_t i = 0; i < m_numberOfFibers; i++) { delete m_fibers[i]; } m_fibers.clear(); } workbench-1.1.1/src/Files/FiberOrientation.h000066400000000000000000000056001255417355300207540ustar00rootroot00000000000000#ifndef __FIBER_ORIENTATION__H__ #define __FIBER_ORIENTATION__H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" namespace caret { class Fiber; class FiberOrientation /* : public CaretObject */ { public: FiberOrientation(const int32_t numberOfFibers, float* pointerToData); virtual ~FiberOrientation(); /** * XYZ coordinates at spatial center of distribution. * When valid, points to memory in a CIFTI file. */ float m_xyz[3]; /** * Number of fibers in this group. * (number of elements in member 'fibers'). */ int32_t m_numberOfFibers; /** * Pointers to all fibers in this group. * When valid, points to memory in a CIFTI file */ std::vector m_fibers; /** * Fiber orientations are drawn using blending (alpha values). For * blending to work correctly in OpenGL, items must be drawn in * "depth" order from furthest to nearest. */ mutable float m_drawingDepth; /** * True if the fiber is valid, else false. */ bool m_valid; /** * Describes why fiber is invalid. */ QString m_invalidMessage; /** * Number of elements per fiber in a fiber orientation's file * (excluding the Fibers). * * At this time, this is the XYZ. * The value for this constant MUST be updated if elements are * added to a fiber orientation. */ static const int32_t NUMBER_OF_ELEMENTS_IN_FILE; private: FiberOrientation(const FiberOrientation&); FiberOrientation& operator=(const FiberOrientation&); // ADD_NEW_MEMBERS_HERE }; #ifdef __FIBER_ORIENTATION_DECLARE__ const int32_t FiberOrientation::NUMBER_OF_ELEMENTS_IN_FILE = 3; #endif // __FIBER_ORIENTATION_DECLARE__ } // namespace #endif //__FIBER_ORIENTATION__H__ workbench-1.1.1/src/Files/FiberOrientationColoringTypeEnum.cxx000066400000000000000000000232551255417355300245210ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __FIBER_ORIENTATION_COLORING_TYPE_ENUM_DECLARE__ #include "FiberOrientationColoringTypeEnum.h" #undef __FIBER_ORIENTATION_COLORING_TYPE_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::FiberOrientationColoringTypeEnum * \brief Coloring type for foci orientations (and trajectory) * \ingroup Brain */ /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ FiberOrientationColoringTypeEnum::FiberOrientationColoringTypeEnum(const Enum enumValue, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCodeCounter++; this->name = name; this->guiName = guiName; } /** * Destructor. */ FiberOrientationColoringTypeEnum::~FiberOrientationColoringTypeEnum() { } /** * Initialize the enumerated metadata. */ void FiberOrientationColoringTypeEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(FiberOrientationColoringTypeEnum(FIBER_COLORING_FIBER_INDEX_AS_RGB, "FIBER_COLORING_FIBER_INDEX_AS_RGB", "Fiber 1,2,3 as RBG")); enumData.push_back(FiberOrientationColoringTypeEnum(FIBER_COLORING_XYZ_AS_RGB, "FIBER_COLORING_XYZ_AS_RGB", "XYZ as RGB")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const FiberOrientationColoringTypeEnum* FiberOrientationColoringTypeEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const FiberOrientationColoringTypeEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString FiberOrientationColoringTypeEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const FiberOrientationColoringTypeEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ FiberOrientationColoringTypeEnum::Enum FiberOrientationColoringTypeEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = FIBER_COLORING_XYZ_AS_RGB; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const FiberOrientationColoringTypeEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type FiberOrientationColoringTypeEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString FiberOrientationColoringTypeEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const FiberOrientationColoringTypeEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ FiberOrientationColoringTypeEnum::Enum FiberOrientationColoringTypeEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = FIBER_COLORING_XYZ_AS_RGB; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const FiberOrientationColoringTypeEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type FiberOrientationColoringTypeEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t FiberOrientationColoringTypeEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const FiberOrientationColoringTypeEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ FiberOrientationColoringTypeEnum::Enum FiberOrientationColoringTypeEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = FIBER_COLORING_XYZ_AS_RGB; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const FiberOrientationColoringTypeEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type FiberOrientationColoringTypeEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void FiberOrientationColoringTypeEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void FiberOrientationColoringTypeEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(FiberOrientationColoringTypeEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void FiberOrientationColoringTypeEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(FiberOrientationColoringTypeEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } workbench-1.1.1/src/Files/FiberOrientationColoringTypeEnum.h000066400000000000000000000064021255417355300241410ustar00rootroot00000000000000#ifndef __FIBER_ORIENTATION_COLORING_TYPE_ENUM__H_ #define __FIBER_ORIENTATION_COLORING_TYPE_ENUM__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class FiberOrientationColoringTypeEnum { public: /** * Enumerated values. */ enum Enum { /** */ FIBER_COLORING_XYZ_AS_RGB, /** */ FIBER_COLORING_FIBER_INDEX_AS_RGB }; ~FiberOrientationColoringTypeEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); private: FiberOrientationColoringTypeEnum(const Enum enumValue, const AString& name, const AString& guiName); static const FiberOrientationColoringTypeEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; #ifdef __FIBER_ORIENTATION_COLORING_TYPE_ENUM_DECLARE__ std::vector FiberOrientationColoringTypeEnum::enumData; bool FiberOrientationColoringTypeEnum::initializedFlag = false; int32_t FiberOrientationColoringTypeEnum::integerCodeCounter = 0; #endif // __FIBER_ORIENTATION_COLORING_TYPE_ENUM_DECLARE__ } // namespace #endif //__FIBER_ORIENTATION_COLORING_TYPE_ENUM__H_ workbench-1.1.1/src/Files/FiberOrientationTrajectory.cxx000066400000000000000000000134421255417355300234010ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __FIBER_ORIENTATION_TRAJECTORY_DECLARE__ #include "FiberOrientationTrajectory.h" #undef __FIBER_ORIENTATION_TRAJECTORY_DECLARE__ #include "CaretAssert.h" #include "CaretSparseFile.h" #include "MathFunctions.h" using namespace caret; /** * \class caret::FiberOrientationTrajectory * \brief Containts fiber trajectory and corresponding fiber orientation. */ /** * Constructor that accepts a fiber orientation. * * @param fiberOrientationIndex * Index of the fiber orientation. * @param fiberOrientation * Fiber orientation that is associated with this fiber trajectory. */ FiberOrientationTrajectory::FiberOrientationTrajectory(const int64_t fiberOrientationIndex, const FiberOrientation* fiberOrientation) : m_fiberOrientationIndex(fiberOrientationIndex), m_fiberOrientation(fiberOrientation) { CaretAssert(fiberOrientation); m_fiberFraction = new FiberFractions(); m_fiberFractionTotalCountFloat = 0.0; m_totalCountSum = 0.0; m_fiberCountsSum.clear(); m_distanceSum = 0.0; m_countForAveraging = 0; } /** * Destructor. */ FiberOrientationTrajectory::~FiberOrientationTrajectory() { delete m_fiberFraction; } /** * Add a fiber fraction for averaging. * * @param fiberFraction * Fiber fraction that is added. */ void FiberOrientationTrajectory::addFiberFractionsForAveraging(const FiberFractions& fiberFraction) { const bool includeZeroTotalCountWhenAveraging = true; const int64_t numFractions = fiberFraction.fiberFractions.size(); if ((fiberFraction.totalCount > 0) && (numFractions > 0)) { // const float len = MathFunctions::vectorLength(&fiberFraction.fiberFractions[0]); // if (len < 1.0) { // std::cout << "Fraction len < 1: " << len << std::endl; // } if (m_fiberCountsSum.empty()) { m_fiberCountsSum.resize(numFractions, 0.0); } else if (static_cast(m_fiberCountsSum.size()) != numFractions) { CaretAssertMessage(0, "Sizes should be the same"); return; } m_totalCountSum += fiberFraction.totalCount; for (int64_t i = 0; i < numFractions; i++) { m_fiberCountsSum[i] += fiberFraction.fiberFractions[i] * fiberFraction.totalCount; } m_distanceSum += fiberFraction.distance; m_countForAveraging += 1; } else if (includeZeroTotalCountWhenAveraging) { m_countForAveraging += 1; } } /** * Set a fiber fraction. * * @param fiberFraction * Fiber fraction that is replaced. */ void FiberOrientationTrajectory::setFiberFractions(const FiberFractions& fiberFraction) { *m_fiberFraction = fiberFraction; if (m_fiberFraction->fiberFractions.empty()) { m_fiberFraction->zero(); } m_fiberFractionTotalCountFloat = m_fiberFraction->totalCount; } /** * Finish, which will update the fiber fraction using the average of all * of the fiber fractions that were added. */ void FiberOrientationTrajectory::finishAveraging() { if (m_countForAveraging > 0) { m_fiberFraction->distance = m_distanceSum / m_countForAveraging; m_fiberFraction->totalCount = m_totalCountSum / m_countForAveraging; m_fiberFractionTotalCountFloat = m_totalCountSum / m_countForAveraging; const int64_t numFiberCounts = static_cast(m_fiberCountsSum.size()); m_fiberFraction->fiberFractions.resize(numFiberCounts); if (numFiberCounts > 0) { for (int64_t i = 0; i < numFiberCounts; i++) { if (m_fiberFractionTotalCountFloat > 0.0) { const float averageCount = m_fiberCountsSum[i] / m_countForAveraging; m_fiberFraction->fiberFractions[i] = (averageCount / m_fiberFractionTotalCountFloat); float sum = (m_fiberFraction->fiberFractions[0] + m_fiberFraction->fiberFractions[1] + m_fiberFraction->fiberFractions[2]); if (sum > 1.0) { const float divisor = 1.0 / sum; m_fiberFraction->fiberFractions[0] *= divisor; m_fiberFraction->fiberFractions[1] *= divisor; m_fiberFraction->fiberFractions[2] *= divisor; // float newSum = (m_fiberFraction->fiberFractions[0] // + m_fiberFraction->fiberFractions[1] // + m_fiberFraction->fiberFractions[2]); // std::cout << "Sum too big: " << sum << " new sum: " << newSum << std::endl; } } else { m_fiberFraction->fiberFractions[i] = 0.0; } } } } else { m_fiberFraction->zero(); } } workbench-1.1.1/src/Files/FiberOrientationTrajectory.h000066400000000000000000000073341255417355300230310ustar00rootroot00000000000000#ifndef __FIBER_ORIENTATION_TRAJECTORY__H_ #define __FIBER_ORIENTATION_TRAJECTORY__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "CaretAssert.h" #include "CaretSparseFile.h" namespace caret { class FiberOrientation; class FiberOrientationTrajectory { public: FiberOrientationTrajectory(const int64_t fiberOrientationIndex, const FiberOrientation* fiberOrientation); virtual ~FiberOrientationTrajectory(); void addFiberFractionsForAveraging(const FiberFractions& fiberFraction); void setFiberFractions(const FiberFractions& fiberFraction); /** * @return the Fiber Orientation. */ inline const FiberOrientation* getFiberOrientation() const { return m_fiberOrientation; } // /** // * Get the fiber fraction. // * // * @param indx // * Index of the fiber fraction. // * @return // * Fiber fraction at the given index. // */ // inline const FiberFractions* getFiberFraction() const { // return m_fiberFraction; // } /** * @return The fiber orientation index. */ inline int64_t getFiberOrientationIndex() const { return m_fiberOrientationIndex; } /** * @return The total count as a float. */ inline float getFiberFractionTotalCount() const { return m_fiberFractionTotalCountFloat; } /** * @return The fiber fractions (proportions). */ inline const std::vector& getFiberFractions() const { return m_fiberFraction->fiberFractions; } /** * @return The fiber fractions distance. */ inline float getFiberFractionDistance() const { return m_fiberFraction->distance; } void finishAveraging(); private: FiberOrientationTrajectory(const FiberOrientationTrajectory&); FiberOrientationTrajectory& operator=(const FiberOrientationTrajectory&); public: // ADD_NEW_METHODS_HERE private: const int64_t m_fiberOrientationIndex; const FiberOrientation* m_fiberOrientation; FiberFractions* m_fiberFraction; float m_fiberFractionTotalCountFloat; double m_totalCountSum; std::vector m_fiberCountsSum; double m_distanceSum; int64_t m_countForAveraging; // ADD_NEW_MEMBERS_HERE friend class CiftiFiberTrajectoryFile; }; #ifdef __FIBER_ORIENTATION_TRAJECTORY_DECLARE__ // #endif // __FIBER_ORIENTATION_TRAJECTORY_DECLARE__ } // namespace #endif //__FIBER_ORIENTATION_TRAJECTORY__H_ workbench-1.1.1/src/Files/FiberTrajectoryColorModel.cxx000066400000000000000000000301241255417355300231410ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __FIBER_TRAJECTORY_COLOR_MODEL_DECLARE__ #include "FiberTrajectoryColorModel.h" #undef __FIBER_TRAJECTORY_COLOR_MODEL_DECLARE__ #include "CaretAssert.h" #include "CaretLogger.h" #include "SceneClass.h" #include "SceneClassAssistant.h" using namespace caret; /** * \class caret::FiberTrajectoryColorModel * \brief Combines Fiber Coloring Type and Caret Color enumerated types. * \ingroup Files * * Fiber trajectories may be colored using either the fiber coloring * type or a caret color. This model allows these types to be * treated as a single type. */ /** * Constructor. */ FiberTrajectoryColorModel::FiberTrajectoryColorModel() : CaretObject() { m_selectedItem = NULL; Item* item = new Item(); m_allItems.push_back(item); std::vector allColors; CaretColorEnum::getAllEnums(allColors); for (std::vector::iterator iter = allColors.begin(); iter != allColors.end(); iter++) { Item* item = new Item(*iter); m_allItems.push_back(item); } if (m_allItems.empty()) { CaretAssert(0); } else { m_selectedItem = m_allItems[0]; } m_sceneAssistant = new SceneClassAssistant(); } /** * Destructor. */ FiberTrajectoryColorModel::~FiberTrajectoryColorModel() { for (std::vector::iterator iter = m_allItems.begin(); iter != m_allItems.end(); iter++) { Item* item = *iter; delete item; } m_allItems.clear(); delete m_sceneAssistant; } /** * @return All items in this model. */ std::vector FiberTrajectoryColorModel::getValidItems() { std::vector items = m_allItems; return m_allItems; } /** * @return Pointer to selected item. */ FiberTrajectoryColorModel::Item* FiberTrajectoryColorModel::getSelectedItem() { if (m_selectedItem == NULL) { if (m_allItems.empty() == false) { m_selectedItem = m_allItems[0]; } } return m_selectedItem; } /** * @return Pointer to selected item. */ const FiberTrajectoryColorModel::Item* FiberTrajectoryColorModel::getSelectedItem() const { FiberTrajectoryColorModel* nonConstThis = const_cast(this); const Item* item = nonConstThis->getSelectedItem(); return item; } /** * Set the selected item. * @param item * New selected item. */ void FiberTrajectoryColorModel::setSelectedItem(const Item* item) { std::vector allItems = getValidItems(); const int32_t numItems = static_cast(allItems.size()); for (int32_t i = 0; i < numItems; i++) { if (item->equals(*allItems[i])) { m_selectedItem = allItems[i]; break; } } } /** * Set the selection to the given caret color. * @param color * Color that is to be selected. */ void FiberTrajectoryColorModel::setCaretColor(const CaretColorEnum::Enum color) { std::vector allItems = getValidItems(); const int32_t numItems = static_cast(allItems.size()); for (int32_t i = 0; i < numItems; i++) { if (allItems[i]->getItemType() == Item::ITEM_TYPE_CARET_COLOR) { if (allItems[i]->getCaretColor() == color) { setSelectedItem(allItems[i]); break; } } } } /** * @return Is the fiber coloring type selected? */ bool FiberTrajectoryColorModel::isFiberOrientationColoringTypeSelected() const { if (m_selectedItem->getItemType() == Item::ITEM_TYPE_FIBER_ORIENTATION_COLORING_TYPE) { return true; } return false; } /** * Set the selection to the given fiber coloring type.. * @param fiberColorType * Fiber coloring type that is to be selected. */ void FiberTrajectoryColorModel::setFiberOrientationColoringTypeSelected() { std::vector allItems = getValidItems(); const int32_t numItems = static_cast(allItems.size()); for (int32_t i = 0; i < numItems; i++) { if (allItems[i]->getItemType() == Item::ITEM_TYPE_FIBER_ORIENTATION_COLORING_TYPE) { setSelectedItem(allItems[i]); break; } } } /** * Copy the selection from the other model. * * @param other * The other model. */ void FiberTrajectoryColorModel::copy(const FiberTrajectoryColorModel& other) { const Item* otherItem = other.getSelectedItem(); switch (otherItem->getItemType()) { case Item::ITEM_TYPE_CARET_COLOR: setCaretColor(otherItem->getCaretColor()); break; case Item::ITEM_TYPE_FIBER_ORIENTATION_COLORING_TYPE: setFiberOrientationColoringTypeSelected(); break; } } /** * Save information specific to this type of model to the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @param instanceName * Name of instance in the scene. */ SceneClass* FiberTrajectoryColorModel::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "FiberTrajectoryColorModel", 1); m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); if (m_selectedItem != NULL) { sceneClass->addChild(m_selectedItem->saveToScene(sceneAttributes, "m_selectedItem")); } return sceneClass; } /** * Restore information specific to the type of model from the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass from which model specific information is obtained. */ void FiberTrajectoryColorModel::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); Item item; item.restoreFromScene(sceneAttributes, sceneClass->getClass("m_selectedItem")); setSelectedItem(&item); } /* ========================================================================== */ /** * Constructor for a caret color. * * @param caretColor * The caret color enum. */ FiberTrajectoryColorModel::Item::Item(const CaretColorEnum::Enum caretColor) { m_caretColor = caretColor; m_itemType = ITEM_TYPE_CARET_COLOR; initializeAtEndOfConstruction(); } /** * Constructor for a fiber coloring type. * * @param fiberColoringType * The fiber coloring type. */ FiberTrajectoryColorModel::Item::Item() { m_caretColor = CaretColorEnum::BLACK; m_itemType = ITEM_TYPE_FIBER_ORIENTATION_COLORING_TYPE; initializeAtEndOfConstruction(); } /** * Destructor. */ FiberTrajectoryColorModel::Item::~Item() { delete m_sceneAssistant; } /** * Initialize at the end of construction. */ void FiberTrajectoryColorModel::Item::initializeAtEndOfConstruction() { m_sceneAssistant = new SceneClassAssistant(); m_sceneAssistant->add("m_caretColor", &m_caretColor); } /** * Equality test for Item. * * @param item * Item for comparison. * @return * True if the same, else false. */ bool FiberTrajectoryColorModel::Item::equals(const Item& item) const { if (m_itemType == item.m_itemType) { switch (m_itemType) { case ITEM_TYPE_CARET_COLOR: if (m_caretColor == item.m_caretColor) { return true; } break; case ITEM_TYPE_FIBER_ORIENTATION_COLORING_TYPE: return true; break; } } return false; } /** * @return Name of the item. */ AString FiberTrajectoryColorModel::Item::getName() const { AString name = "PROGRAM_ERROR"; switch (m_itemType) { case ITEM_TYPE_CARET_COLOR: name = CaretColorEnum::toGuiName(m_caretColor); break; case ITEM_TYPE_FIBER_ORIENTATION_COLORING_TYPE: name = "Fiber"; break; } return name; } /** * @return Type of the item. */ FiberTrajectoryColorModel::Item::ItemType FiberTrajectoryColorModel::Item::getItemType() const { return m_itemType; } /** * @return The caret color for this item. */ CaretColorEnum::Enum FiberTrajectoryColorModel::Item::getCaretColor() const { return m_caretColor; } /** * Save information specific to this type of model to the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @param instanceName * Name of instance in the scene. */ SceneClass* FiberTrajectoryColorModel::Item::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "FiberTrajectoryColorModel::Item", 1); m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); switch (m_itemType) { case ITEM_TYPE_CARET_COLOR: sceneClass->addString("m_itemType", "ITEM_TYPE_CARET_COLOR"); break; case ITEM_TYPE_FIBER_ORIENTATION_COLORING_TYPE: sceneClass->addString("m_itemType", "ITEM_TYPE_FIBER_ORIENTATION_COLORING_TYPE"); break; } return sceneClass; } /** * Restore information specific to the type of model from the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass from which model specific information is obtained. */ void FiberTrajectoryColorModel::Item::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); const AString itemTypeName = sceneClass->getStringValue("m_itemType", "ITEM_TYPE_FIBER_ORIENTATION_COLORING_TYPE"); m_itemType = ITEM_TYPE_FIBER_ORIENTATION_COLORING_TYPE; if (itemTypeName == "ITEM_TYPE_CARET_COLOR") { m_itemType = ITEM_TYPE_CARET_COLOR; } else if (itemTypeName == "ITEM_TYPE_FIBER_ORIENTATION_COLORING_TYPE") { m_itemType = ITEM_TYPE_FIBER_ORIENTATION_COLORING_TYPE; } else { CaretLogWarning(("Unrecognized value: " + itemTypeName)); } } workbench-1.1.1/src/Files/FiberTrajectoryColorModel.h000066400000000000000000000075331255417355300225760ustar00rootroot00000000000000#ifndef __FIBER_TRAJECTORY_COLOR_MODEL_H__ #define __FIBER_TRAJECTORY_COLOR_MODEL_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretColorEnum.h" #include "CaretObject.h" #include "SceneableInterface.h" namespace caret { class SceneClassAssistant; class FiberTrajectoryColorModel : public CaretObject, public SceneableInterface { public: class Item : public SceneableInterface { public: enum ItemType { /** * Type of item */ ITEM_TYPE_FIBER_ORIENTATION_COLORING_TYPE, ITEM_TYPE_CARET_COLOR }; Item(const CaretColorEnum::Enum caretColor); Item(); ~Item(); bool equals(const Item& item) const; AString getName() const; ItemType getItemType() const; CaretColorEnum::Enum getCaretColor() const; virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private: void initializeAtEndOfConstruction(); SceneClassAssistant* m_sceneAssistant; CaretColorEnum::Enum m_caretColor; ItemType m_itemType; }; FiberTrajectoryColorModel(); virtual ~FiberTrajectoryColorModel(); std::vector getValidItems(); Item* getSelectedItem(); const Item* getSelectedItem() const; void setSelectedItem(const Item* item); void setCaretColor(const CaretColorEnum::Enum color); bool isFiberOrientationColoringTypeSelected() const; void setFiberOrientationColoringTypeSelected(); void copy(const FiberTrajectoryColorModel& other); private: FiberTrajectoryColorModel(const FiberTrajectoryColorModel&); FiberTrajectoryColorModel& operator=(const FiberTrajectoryColorModel&); public: // ADD_NEW_METHODS_HERE virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private: SceneClassAssistant* m_sceneAssistant; std::vector m_allItems; Item* m_selectedItem; // ADD_NEW_MEMBERS_HERE }; #ifdef __FIBER_TRAJECTORY_COLOR_MODEL_DECLARE__ // #endif // __FIBER_TRAJECTORY_COLOR_MODEL_DECLARE__ } // namespace #endif //__FIBER_TRAJECTORY_COLOR_MODEL_H__ workbench-1.1.1/src/Files/FiberTrajectoryDisplayModeEnum.cxx000066400000000000000000000241341255417355300241450ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __FIBER_TRAJECTORY_DISPLAY_MODE_ENUM_DECLARE__ #include "FiberTrajectoryDisplayModeEnum.h" #undef __FIBER_TRAJECTORY_DISPLAY_MODE_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::FiberTrajectoryDisplayModeEnum * \brief Enumerated type for fiber trajectory display mode */ /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ FiberTrajectoryDisplayModeEnum::FiberTrajectoryDisplayModeEnum(const Enum enumValue, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCodeCounter++; this->name = name; this->guiName = guiName; } /** * Destructor. */ FiberTrajectoryDisplayModeEnum::~FiberTrajectoryDisplayModeEnum() { } /** * Initialize the enumerated metadata. */ void FiberTrajectoryDisplayModeEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(FiberTrajectoryDisplayModeEnum(FIBER_TRAJECTORY_DISPLAY_ABSOLUTE, "FIBER_TRAJECTORY_DISPLAY_ABSOLUTE", "Absolute")); enumData.push_back(FiberTrajectoryDisplayModeEnum(FIBER_TRAJECTORY_DISPLAY_DISTANCE_WEIGHTED, "FIBER_TRAJECTORY_DISPLAY_DISTANCE_WEIGHTED", "Distance Weighted")); enumData.push_back(FiberTrajectoryDisplayModeEnum(FIBER_TRAJECTORY_DISPLAY_DISTANCE_WEIGHTED_LOG, "FIBER_TRAJECTORY_DISPLAY_DISTANCE_WEIGHTED_LOG", "Distance Weighted (Log)")); enumData.push_back(FiberTrajectoryDisplayModeEnum(FIBER_TRAJECTORY_DISPLAY_PROPORTION, "FIBER_TRAJECTORY_DISPLAY_PROPORTION", "Proportion")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const FiberTrajectoryDisplayModeEnum* FiberTrajectoryDisplayModeEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const FiberTrajectoryDisplayModeEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString FiberTrajectoryDisplayModeEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const FiberTrajectoryDisplayModeEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ FiberTrajectoryDisplayModeEnum::Enum FiberTrajectoryDisplayModeEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = FIBER_TRAJECTORY_DISPLAY_PROPORTION; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const FiberTrajectoryDisplayModeEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type FiberTrajectoryDisplayModeEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString FiberTrajectoryDisplayModeEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const FiberTrajectoryDisplayModeEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ FiberTrajectoryDisplayModeEnum::Enum FiberTrajectoryDisplayModeEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = FIBER_TRAJECTORY_DISPLAY_PROPORTION; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const FiberTrajectoryDisplayModeEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type FiberTrajectoryDisplayModeEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t FiberTrajectoryDisplayModeEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const FiberTrajectoryDisplayModeEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ FiberTrajectoryDisplayModeEnum::Enum FiberTrajectoryDisplayModeEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = FIBER_TRAJECTORY_DISPLAY_PROPORTION; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const FiberTrajectoryDisplayModeEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type FiberTrajectoryDisplayModeEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void FiberTrajectoryDisplayModeEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void FiberTrajectoryDisplayModeEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(FiberTrajectoryDisplayModeEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void FiberTrajectoryDisplayModeEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(FiberTrajectoryDisplayModeEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } workbench-1.1.1/src/Files/FiberTrajectoryDisplayModeEnum.h000066400000000000000000000067511255417355300235770ustar00rootroot00000000000000#ifndef __FIBER_TRAJECTORY_DISPLAY_MODE_ENUM__H_ #define __FIBER_TRAJECTORY_DISPLAY_MODE_ENUM__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class FiberTrajectoryDisplayModeEnum { public: /** * Enumerated values. */ enum Enum { /** Absolute display mode */ FIBER_TRAJECTORY_DISPLAY_ABSOLUTE, /** Distance weighted display mode */ FIBER_TRAJECTORY_DISPLAY_DISTANCE_WEIGHTED, /** Distance weighted Log display mode */ FIBER_TRAJECTORY_DISPLAY_DISTANCE_WEIGHTED_LOG, /** Proportion display mode*/ FIBER_TRAJECTORY_DISPLAY_PROPORTION }; ~FiberTrajectoryDisplayModeEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); private: FiberTrajectoryDisplayModeEnum(const Enum enumValue, const AString& name, const AString& guiName); static const FiberTrajectoryDisplayModeEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; #ifdef __FIBER_TRAJECTORY_DISPLAY_MODE_ENUM_DECLARE__ std::vector FiberTrajectoryDisplayModeEnum::enumData; bool FiberTrajectoryDisplayModeEnum::initializedFlag = false; int32_t FiberTrajectoryDisplayModeEnum::integerCodeCounter = 0; #endif // __FIBER_TRAJECTORY_DISPLAY_MODE_ENUM_DECLARE__ } // namespace #endif //__FIBER_TRAJECTORY_DISPLAY_MODE_ENUM__H_ workbench-1.1.1/src/Files/FiberTrajectoryMapProperties.cxx000066400000000000000000000270711255417355300237030ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __FIBER_TRAJECTORY_MAP_PROPERTIES_DECLARE__ #include "FiberTrajectoryMapProperties.h" #undef __FIBER_TRAJECTORY_MAP_PROPERTIES_DECLARE__ #include "CaretAssert.h" #include "FiberTrajectoryColorModel.h" #include "SceneAttributes.h" #include "SceneClass.h" #include "SceneClassAssistant.h" using namespace caret; /** * \class caret::FiberTrajectoryMapProperties * \brief Contains display properties for a fiber trajectory file. */ /** * Constructor. */ FiberTrajectoryMapProperties::FiberTrajectoryMapProperties() { m_sceneAssistant = new SceneClassAssistant(); m_fiberTrajectoryColoringModel = new FiberTrajectoryColorModel(); const float thresholdStreamline = 5; const float maximumProportionOpacity = 0.80; const float minimumProportionOpacity = 0.05; const FiberTrajectoryDisplayModeEnum::Enum displayMode = FiberTrajectoryDisplayModeEnum::FIBER_TRAJECTORY_DISPLAY_ABSOLUTE; const float countMaximum = 50; const float countMinimum = 5; m_displayStatus = false; m_displayMode = displayMode; m_proportionStreamline = thresholdStreamline; m_maximumProportionOpacity = maximumProportionOpacity; m_minimumProportionOpacity = minimumProportionOpacity; m_countStreamline = thresholdStreamline; m_countMaximumOpacity = countMaximum; m_countMinimumOpacity = countMinimum; m_distanceStreamline = thresholdStreamline; m_distanceMaximumOpacity = countMaximum; m_distanceMinimumOpacity = countMinimum; m_sceneAssistant->add("m_displayStatus", &m_displayStatus); m_sceneAssistant->add("m_displayMode", &m_displayMode); m_sceneAssistant->add("m_proportionStreamline", &m_proportionStreamline); m_sceneAssistant->add("m_maximumProportionOpacity", &m_maximumProportionOpacity); m_sceneAssistant->add("m_minimumProportionOpacity", &m_minimumProportionOpacity); m_sceneAssistant->add("m_countStreamline", &m_countStreamline); m_sceneAssistant->add("m_countMaximumOpacity", &m_countMaximumOpacity); m_sceneAssistant->add("m_countMinimumOpacity", &m_countMinimumOpacity); m_sceneAssistant->add("m_distanceStreamline", &m_distanceStreamline); m_sceneAssistant->add("m_distanceMaximumOpacity", &m_distanceMaximumOpacity); m_sceneAssistant->add("m_distanceMinimumOpacity", &m_distanceMinimumOpacity); m_sceneAssistant->add("m_fiberTrajectoryColoringModel", "FiberTrajectoryColorModel", m_fiberTrajectoryColoringModel); } /** * Destructor. */ FiberTrajectoryMapProperties::~FiberTrajectoryMapProperties() { delete m_fiberTrajectoryColoringModel; delete m_sceneAssistant; } /** * @return Display status of trajectory. */ bool FiberTrajectoryMapProperties::isDisplayed() const { return m_displayStatus; } /** * Set the display status for trajectory for the given display group. * @param displayStatus * New status. */ void FiberTrajectoryMapProperties::setDisplayed(const bool displayStatus) { m_displayStatus = displayStatus; } /** * @return The display mode. */ FiberTrajectoryDisplayModeEnum::Enum FiberTrajectoryMapProperties::getDisplayMode() const { return m_displayMode; } /** * Set the display mode to the given value. * @param displayMode * New value for display mode. */ void FiberTrajectoryMapProperties::setDisplayMode(const FiberTrajectoryDisplayModeEnum::Enum displayMode) { m_displayMode = displayMode; } /** * @return The proportion streamline count */ float FiberTrajectoryMapProperties::getProportionStreamline() const { return m_proportionStreamline; } /** * Set the proportion streamline count. * @param pointSize * New value for below limit. */ void FiberTrajectoryMapProperties::setProportionStreamline(const float proportionStreamline) { m_proportionStreamline = proportionStreamline; } /** * @return The proporation maximum opacity. */ float FiberTrajectoryMapProperties::getProportionMaximumOpacity() const { return m_maximumProportionOpacity; } /** * Set the proporation maximum opacity. * @param minimumMagnitude * New value for minimum magnitude. */ void FiberTrajectoryMapProperties::setProportionMaximumOpacity(const float maximumMagnitude) { m_maximumProportionOpacity = maximumMagnitude; } /** * @return The proporation minimum opacity. */ float FiberTrajectoryMapProperties::getProportionMinimumOpacity() const { return m_minimumProportionOpacity; } /** * Set the proporation minimum opacity. * @param minimumOpacity * New value for minimum opacity */ void FiberTrajectoryMapProperties::setProportionMinimumOpacity(const float minimumOpacity) { m_minimumProportionOpacity = minimumOpacity; } /** * @return The count streamline threshold. */ float FiberTrajectoryMapProperties::getCountStreamline() const { return m_countStreamline; } /** * Set the count streamline threshold. * @param countStreamline * New value for count streamline threshold */ void FiberTrajectoryMapProperties::setCountStreamline(const float countStreamline) { m_countStreamline = countStreamline; } /** * @return The count value mapped to maximum opacity. */ float FiberTrajectoryMapProperties::getCountMaximumOpacity() const { return m_countMaximumOpacity; } /** * Set the count maximum opacity. * @param countMaximumOpacity * New value for count mapped to maximum opacity */ void FiberTrajectoryMapProperties::setCountMaximumOpacity(const float countMaximumOpacity) { m_countMaximumOpacity = countMaximumOpacity; } /** * @return The count value mapped to minimum opacity. */ float FiberTrajectoryMapProperties::getCountMinimumOpacity() const { return m_countMinimumOpacity; } /** * Set the count minimum opacity. * @param countMinimumOpacity * New value for count mapped to minimum opacity */ void FiberTrajectoryMapProperties::setCountMinimumOpacity(const float countMinimumOpacity) { m_countMinimumOpacity = countMinimumOpacity; } /** * @return The distance streamline threshold. */ float FiberTrajectoryMapProperties::getDistanceStreamline() const { return m_distanceStreamline; } /** * Set the distance streamline threshold. * @param distanceStreamline * New value for distance streamline threshold */ void FiberTrajectoryMapProperties::setDistanceStreamline(const float distanceStreamline) { m_distanceStreamline = distanceStreamline; } /** * @return The distance value mapped to maximum opacity. */ float FiberTrajectoryMapProperties::getDistanceMaximumOpacity() const { return m_distanceMaximumOpacity; } /** * Set the distance maximum opacity. * @param distanceMaximumOpacity * New value for distance mapped to maximum opacity */ void FiberTrajectoryMapProperties::setDistanceMaximumOpacity(const float distanceMaximumOpacity) { m_distanceMaximumOpacity = distanceMaximumOpacity; } /** * @return The distance value mapped to minimum opacity. */ float FiberTrajectoryMapProperties::getDistanceMinimumOpacity() const { return m_distanceMinimumOpacity; } /** * Set the distance minimum opacity. * @param distanceMinimumOpacity * New value for distance mapped to minimum opacity */ void FiberTrajectoryMapProperties::setDistanceMinimumOpacity(const float distanceMinimumOpacity) { m_distanceMinimumOpacity = distanceMinimumOpacity; } /** * @return the fiber trajectory coloring model. */ FiberTrajectoryColorModel* FiberTrajectoryMapProperties::getFiberTrajectoryColorModel() { return m_fiberTrajectoryColoringModel; } /** * @return the fiber trajectory coloring model. */ const FiberTrajectoryColorModel* FiberTrajectoryMapProperties::getFiberTrajectoryColorModel() const { return m_fiberTrajectoryColoringModel; } /** * Copy the other map properties. * * @param other * The map properties that are copied. */ void FiberTrajectoryMapProperties::copy(const FiberTrajectoryMapProperties& other) { m_displayMode = other.m_displayMode; m_displayStatus = other.m_displayStatus; m_proportionStreamline = other.m_proportionStreamline; m_maximumProportionOpacity = other.m_maximumProportionOpacity; m_minimumProportionOpacity = other.m_minimumProportionOpacity; m_countStreamline = other.m_countStreamline; m_countMaximumOpacity = other.m_countMaximumOpacity; m_countMinimumOpacity = other.m_countMinimumOpacity; m_distanceStreamline = other.m_distanceStreamline; m_distanceMaximumOpacity = other.m_distanceMaximumOpacity; m_distanceMinimumOpacity = other.m_distanceMinimumOpacity; m_fiberTrajectoryColoringModel->copy(*other.getFiberTrajectoryColorModel()); } /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ SceneClass* FiberTrajectoryMapProperties::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "FiberTrajectoryMapProperties", 1); m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); return sceneClass; } /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * SceneClass containing the state that was previously * saved and should be restored. */ void FiberTrajectoryMapProperties::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); switch (sceneAttributes->getSceneType()) { case SceneTypeEnum::SCENE_TYPE_FULL: break; case SceneTypeEnum::SCENE_TYPE_GENERIC: break; } } workbench-1.1.1/src/Files/FiberTrajectoryMapProperties.h000066400000000000000000000105501255417355300233220ustar00rootroot00000000000000#ifndef __FIBER_TRAJECTORY_MAP_PROPERTIES_H__ #define __FIBER_TRAJECTORY_MAP_PROPERTIES_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "BrainConstants.h" #include "FiberTrajectoryDisplayModeEnum.h" #include "SceneableInterface.h" namespace caret { class FiberTrajectoryColorModel; class SceneClassAssistant; class FiberTrajectoryMapProperties : public SceneableInterface { public: FiberTrajectoryMapProperties(); virtual ~FiberTrajectoryMapProperties(); bool isDisplayed() const; void setDisplayed(const bool displayStatus); FiberTrajectoryDisplayModeEnum::Enum getDisplayMode() const; void setDisplayMode(const FiberTrajectoryDisplayModeEnum::Enum displayMode); float getProportionStreamline() const; void setProportionStreamline(const float thresholdStreamline); float getProportionMaximumOpacity() const; void setProportionMaximumOpacity(const float maximumProportionOpacity); float getProportionMinimumOpacity() const; void setProportionMinimumOpacity(const float minimumProportionOpacity); float getCountStreamline() const; void setCountStreamline(const float countStreamline); float getCountMaximumOpacity() const; void setCountMaximumOpacity(const float countMaximumOpacity); float getCountMinimumOpacity() const; void setCountMinimumOpacity(const float countMinimumOpacity); float getDistanceStreamline() const; void setDistanceStreamline(const float distanceStreamline); float getDistanceMaximumOpacity() const; void setDistanceMaximumOpacity(const float distanceMaximumOpacity); float getDistanceMinimumOpacity() const; void setDistanceMinimumOpacity(const float distanceMinimumOpacity); FiberTrajectoryColorModel* getFiberTrajectoryColorModel(); const FiberTrajectoryColorModel* getFiberTrajectoryColorModel() const; virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); void copy(const FiberTrajectoryMapProperties& other); private: FiberTrajectoryMapProperties(const FiberTrajectoryMapProperties&); FiberTrajectoryMapProperties& operator=(const FiberTrajectoryMapProperties&); SceneClassAssistant* m_sceneAssistant; FiberTrajectoryDisplayModeEnum::Enum m_displayMode; bool m_displayStatus; float m_proportionStreamline; float m_maximumProportionOpacity; float m_minimumProportionOpacity; float m_countStreamline; float m_countMaximumOpacity; float m_countMinimumOpacity; float m_distanceStreamline; float m_distanceMaximumOpacity; float m_distanceMinimumOpacity; FiberTrajectoryColorModel* m_fiberTrajectoryColoringModel; }; #ifdef __FIBER_TRAJECTORY_MAP_PROPERTIES_DECLARE__ // #endif // __FIBER_TRAJECTORY_MAP_PROPERTIES_DECLARE__ } // namespace #endif //__FIBER_TRAJECTORY_MAP_PROPERTIES_H__ workbench-1.1.1/src/Files/FilePathNamePrefixCompactor.cxx000066400000000000000000000225631255417355300234160ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __FILE_PATH_NAME_PREFIX_COMPACTOR_DECLARE__ #include "FilePathNamePrefixCompactor.h" #undef __FILE_PATH_NAME_PREFIX_COMPACTOR_DECLARE__ #include #include #include #include "CaretAssert.h" #include "CaretMappableDataFile.h" #include "FileInformation.h" using namespace caret; /** * \class caret::FilePathNamePrefixCompactor * \brief Remove matching prefixes from a group of file names. * \ingroup Files * * In the graphical user-interface names of files with a full path * are frequently presented to the user. In some instances, these * paths may be very long, too long to display in the user-interface. * One solution is to place the path after the name of the file. * However, there are instance in which there are file with identical * names but different paths and when the paths are long, the user * may not see the entire path and is unable to fully identify * a file. * * The static methods in this class are used to remove the matching * prefixes (first of characters) from the path * of each file. Do so, allows the user to see the name of the file * followed by the unique portion of the file's path. */ /** * Constructor. */ FilePathNamePrefixCompactor::FilePathNamePrefixCompactor() : CaretObject() { } /** * Destructor. */ FilePathNamePrefixCompactor::~FilePathNamePrefixCompactor() { } /** * Create names that show the filename followed by the path BUT remove * any matching prefix from the paths for a group of CaretDataFiles. * * Example Input File Names: * /mnt/myelin/data/subject2/rsfmri/activity.dscalar.nii * /mnt/myelin/data/subject1/rsfmri/activity.dscalar.nii * Output: * actitivity.dscalar.nii (../subject2/rsfmri) * actitivity.dscalar.nii (../subject1/rsfmri) * * @param caretMappableDataFiles * The caret mappable data files from which names are obtained. * @param prefixRemovedNamesOut * Names of files with matching prefixes removed. Number of elements * will match the number of elements in caretDataFiles. */ void FilePathNamePrefixCompactor::removeMatchingPathPrefixFromCaretDataFiles(const std::vector& caretMappableDataFiles, std::vector& prefixRemovedNamesOut) { std::vector fileNames; for (std::vector::const_iterator iter = caretMappableDataFiles.begin(); iter != caretMappableDataFiles.end(); iter++) { const CaretDataFile* cdf = *iter; CaretAssert(cdf); fileNames.push_back(cdf->getFileName()); } removeMatchingPathPrefixFromFileNames(fileNames, prefixRemovedNamesOut); } /** * Create names that show the filename followed by the path BUT remove * any matching prefix from the paths for a group of CaretDataFiles. * * Example Input File Names: * /mnt/myelin/data/subject2/rsfmri/activity.dscalar.nii * /mnt/myelin/data/subject1/rsfmri/activity.dscalar.nii * Output: * actitivity.dscalar.nii (../subject2/rsfmri) * actitivity.dscalar.nii (../subject1/rsfmri) * * @param caretDataFiles * The caret data files from which names are obtained. * @param prefixRemovedNamesOut * Names of files with matching prefixes removed. Number of elements * will match the number of elements in caretDataFiles. */ void FilePathNamePrefixCompactor::removeMatchingPathPrefixFromCaretDataFiles(const std::vector& caretDataFiles, std::vector& prefixRemovedNamesOut) { std::vector fileNames; for (std::vector::const_iterator iter = caretDataFiles.begin(); iter != caretDataFiles.end(); iter++) { const CaretDataFile* cdf = *iter; CaretAssert(cdf); fileNames.push_back(cdf->getFileName()); } removeMatchingPathPrefixFromFileNames(fileNames, prefixRemovedNamesOut); } /** * Create names that show the filename followed by the path BUT remove * any matching prefix from the paths for a group of file names. * * Example Input File Names: * /mnt/myelin/data/subject2/rsfmri/activity.dscalar.nii * /mnt/myelin/data/subject1/rsfmri/activity.dscalar.nii * Output: * actitivity.dscalar.nii (../subject2/rsfmri) * actitivity.dscalar.nii (../subject1/rsfmri) * * @param caretDataFiles * The caret data files from which names are obtained. * @param prefixRemovedNamesOut * Names of files with matching prefixes removed. Number of elements * will match the number of elements in caretDataFiles. */ void FilePathNamePrefixCompactor::removeMatchingPathPrefixFromFileNames(const std::vector& fileNames, std::vector& prefixRemovedNamesOut) { prefixRemovedNamesOut.clear(); const int32_t numFiles = static_cast(fileNames.size()); if (numFiles == 1) { FileInformation fileInfo(fileNames[0]); prefixRemovedNamesOut.push_back(fileInfo.getFileName()); return; } else if (numFiles < 1){ return; } std::vector > pathComponentEachFile; int32_t mininumComponentCount = std::numeric_limits::max(); /* * For each file, split its path components (parts between '/') and * place them into a vector. Also find the minimum number from * all of the paths as that will be the maximum number of matching * components. */ for (std::vector::const_iterator iter = fileNames.begin(); iter != fileNames.end(); iter++) { FileInformation fileInfo(*iter); const QString path = fileInfo.getPathName(); QStringList pathComponentsList = path.split('/'); mininumComponentCount = std::min(mininumComponentCount, pathComponentsList.size()); std::vector pathComponents; for (int32_t ip = 0; ip < pathComponentsList.size(); ip++) { pathComponents.push_back(pathComponentsList[ip]); } pathComponentEachFile.push_back(pathComponents); } CaretAssert(static_cast(pathComponentEachFile.size()) == numFiles); /* * For each of the file names, examine and compare its path * components to all of the other file path components to * determine the matching prefix. */ int32_t numMatchingLeadingComponents = 0; for (int32_t iComp = 0; iComp < mininumComponentCount; iComp++) { CaretAssertVectorIndex(pathComponentEachFile, 0); const std::vector& firstFileCompontents = pathComponentEachFile[0]; const AString component = firstFileCompontents[iComp]; bool doneFlag = false; for (int32_t jFile = 1; jFile < numFiles; jFile++) { CaretAssertVectorIndex(pathComponentEachFile, jFile); const std::vector& fileComps = pathComponentEachFile[jFile]; CaretAssertVectorIndex(fileComps, iComp); if (fileComps[iComp] != component) { doneFlag = true; break; } } if (doneFlag) { break; } else { numMatchingLeadingComponents++; } } /* * For each of the file names, create a name in the form of: * filename (unique-suffix-of-path). */ for (int32_t iFile = 0; iFile < numFiles; iFile++) { CaretAssertVectorIndex(fileNames, iFile); FileInformation fileInfo(fileNames[iFile]); AString name = fileInfo.getFileName() + " "; CaretAssertVectorIndex(pathComponentEachFile, iFile); const std::vector& fileComps = pathComponentEachFile[iFile]; const int32_t numComps = static_cast(fileComps.size()); bool addedComponentsFlag = false; for (int32_t iComp = numMatchingLeadingComponents; iComp < numComps; iComp++) { if (iComp == numMatchingLeadingComponents) { name.append("(..../"); } CaretAssertVectorIndex(fileComps, iComp); name.append(fileComps[iComp] + "/"); addedComponentsFlag = true; } if (addedComponentsFlag) { name.append(")"); } prefixRemovedNamesOut.push_back(name); } CaretAssert(fileNames.size() == prefixRemovedNamesOut.size()); } workbench-1.1.1/src/Files/FilePathNamePrefixCompactor.h000066400000000000000000000044241255417355300230370ustar00rootroot00000000000000#ifndef __FILE_PATH_NAME_PREFIX_COMPACTOR_H__ #define __FILE_PATH_NAME_PREFIX_COMPACTOR_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" namespace caret { class CaretDataFile; class CaretMappableDataFile; class FilePathNamePrefixCompactor : public CaretObject { public: static void removeMatchingPathPrefixFromCaretDataFiles(const std::vector& caretMappableDataFiles, std::vector& prefixRemovedNamesOut); static void removeMatchingPathPrefixFromCaretDataFiles(const std::vector& caretDataFiles, std::vector& prefixRemovedNamesOut); static void removeMatchingPathPrefixFromFileNames(const std::vector& fileNames, std::vector& prefixRemovedNamesOut); private: FilePathNamePrefixCompactor(); virtual ~FilePathNamePrefixCompactor(); FilePathNamePrefixCompactor(const FilePathNamePrefixCompactor&); FilePathNamePrefixCompactor& operator=(const FilePathNamePrefixCompactor&); }; #ifdef __FILE_PATH_NAME_PREFIX_COMPACTOR_DECLARE__ // #endif // __FILE_PATH_NAME_PREFIX_COMPACTOR_DECLARE__ } // namespace #endif //__FILE_PATH_NAME_PREFIX_COMPACTOR_H__ workbench-1.1.1/src/Files/FociFile.cxx000066400000000000000000000422211255417355300175440ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #define __FOCI_FILE_DECLARE__ #include "FociFile.h" #undef __FOCI_FILE_DECLARE__ #include "CaretAssert.h" #include "CaretLogger.h" #include "DataFileContentInformation.h" #include "DataFileException.h" #include "GroupAndNameHierarchyModel.h" #include "FileAdapter.h" #include "FociFileSaxReader.h" #include "Focus.h" #include "GiftiLabel.h" #include "GiftiLabelTable.h" #include "GiftiMetaData.h" #include "SurfaceProjectedItem.h" #include "XmlAttributes.h" #include "XmlSaxParser.h" #include "XmlWriter.h" using namespace caret; /** * \class caret::FociFile * \brief A foci (plural of focus) file. */ /** * Constructor. */ FociFile::FociFile() : CaretDataFile(DataFileTypeEnum::FOCI) { initializeFociFile(); } /** * Destructor. */ FociFile::~FociFile() { delete m_nameColorTable; delete m_classColorTable; delete m_metadata; for (std::vector::iterator iter = m_foci.begin(); iter != m_foci.end(); iter++) { delete *iter; } m_foci.clear(); delete m_classNameHierarchy; } /** * Copy constructor. * @param obj * Object that is copied. */ FociFile::FociFile(const FociFile& obj) : CaretDataFile(obj) { initializeFociFile(); copyHelperFociFile(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ FociFile& FociFile::operator=(const FociFile& obj) { if (this != &obj) { clear(); CaretDataFile::operator=(obj); copyHelperFociFile(obj); } return *this; } void FociFile::initializeFociFile() { m_classColorTable = new GiftiLabelTable(); m_nameColorTable = new GiftiLabelTable(); m_classNameHierarchy = new GroupAndNameHierarchyModel(); m_metadata = new GiftiMetaData(); m_forceUpdateOfGroupAndNameHierarchy = true; } /** * Helps with copying an object of this type. * @param ff * Object that is copied. */ void FociFile::copyHelperFociFile(const FociFile& ff) { *m_classColorTable = *ff.m_classColorTable; *m_nameColorTable = *ff.m_nameColorTable; if (m_classNameHierarchy != NULL) { delete m_classNameHierarchy; } m_classNameHierarchy = new GroupAndNameHierarchyModel(); *m_metadata = *ff.m_metadata; const int32_t numFoci = getNumberOfFoci(); for (int32_t i = 0; i < numFoci; i++) { m_foci.push_back(new Focus(*ff.getFocus(i))); } m_forceUpdateOfGroupAndNameHierarchy = true; setModified(); } /** * @return Is this foci file empty (contains zero focuss)? */ bool FociFile::isEmpty() const { return m_foci.empty(); } /** * @return The structure for this file. */ StructureEnum::Enum FociFile::getStructure() const { return StructureEnum::ALL; } /** * Set the structure for this file. * @param structure * New structure for this file. */ void FociFile::setStructure(const StructureEnum::Enum /*structure*/) { // does nothing since focuss apply to all structures } /** * @return Get access to the file's metadata. */ GiftiMetaData* FociFile::getFileMetaData() { return m_metadata; } /** * @return Get access to unmodifiable file's metadata. */ const GiftiMetaData* FociFile::getFileMetaData() const { return m_metadata; } /** * Clear the focus file. */ void FociFile::clear() { CaretDataFile::clear(); m_classNameHierarchy->clear(); m_classColorTable->clear(); m_nameColorTable->clear(); m_metadata->clear(); const int32_t numFoci = getNumberOfFoci(); for (int32_t i = 0; i < numFoci; i++) { delete m_foci[i]; } m_foci.clear(); } /** * @return the number of foci. */ int32_t FociFile::getNumberOfFoci() const { return m_foci.size(); } /** * Get the focus at the given index. * @param indx * Index of the focus. * @return * focus at the given index. */ Focus* FociFile::getFocus(const int32_t indx) { CaretAssertVectorIndex(m_foci, indx); return m_foci[indx]; } /** * Get the focus at the given index. * @param indx * Index of the focus. * @return * focus at the given index. */ const Focus* FociFile::getFocus(const int32_t indx) const { CaretAssertVectorIndex(m_foci, indx); return m_foci[indx]; } /** * Add a focus. NOTE: This focus file * takes ownership of the 'focus' and * will handle deleting it. After calling * this method, the caller must never * do anything with the focus that was passed * to this method. * * @param focus * Focus added to this focus file. */ void FociFile::addFocus(Focus* focus) { m_foci.push_back(focus); const AString name = focus->getName(); if (name.isEmpty() == false) { const int32_t nameColorKey = m_nameColorTable->getLabelKeyFromName(name); if (nameColorKey < 0) { m_nameColorTable->addLabel(name, 0.0f, 0.0f, 0.0f, 1.0f); } } AString className = focus->getClassName(); if (className.isEmpty() == false) { const int32_t classColorKey = m_classColorTable->getLabelKeyFromName(className); if (classColorKey < 0) { m_classColorTable->addLabel(className, 0.0f, 0.0f, 0.0f, 1.0f); } } m_forceUpdateOfGroupAndNameHierarchy = true; setModified(); } /** * Remove the focus at the given index. * @param indx * Index of focus for removal. */ void FociFile::removeFocus(const int32_t indx) { CaretAssertVectorIndex(m_foci, indx); Focus* focus = getFocus(indx); m_foci.erase(m_foci.begin() + indx); delete focus; m_forceUpdateOfGroupAndNameHierarchy = true; setModified(); } /** * Remove the focus. * @param focus * Focus that will be removed and DELETED. */ void FociFile::removeFocus(Focus* focus) { const int32_t numFoci = getNumberOfFoci(); for (int32_t i = 0;i < numFoci; i++) { if (m_foci[i] == focus) { removeFocus(i); return; } } CaretLogWarning("Attempting to delete focus not in focus file with name: " + focus->getName()); } /** * @return The class and name hierarchy. */ GroupAndNameHierarchyModel* FociFile::getGroupAndNameHierarchyModel() { m_classNameHierarchy->update(this, m_forceUpdateOfGroupAndNameHierarchy); m_forceUpdateOfGroupAndNameHierarchy = false; return m_classNameHierarchy; } /** * @return The class color table. */ GiftiLabelTable* FociFile::getClassColorTable() { return m_classColorTable; } /** * @return The class color table. */ const GiftiLabelTable* FociFile::getClassColorTable() const { return m_classColorTable; } /** * @return The name color table. */ GiftiLabelTable* FociFile::getNameColorTable() { return m_nameColorTable; } /** * @return The name color table. */ const GiftiLabelTable* FociFile::getNameColorTable() const { return m_nameColorTable; } /** * Version 1 foci files contained one color table for both names * and classes. Newer versions of the foci file keep them in * separate tables. * * @param oldColorTable * Old color table that is split into name and class color tables. */ void FociFile::createNameAndClassColorTables(const GiftiLabelTable* oldColorTable) { CaretAssert(oldColorTable); m_classColorTable->clear(); m_nameColorTable->clear(); std::set nameSet; std::set classSet; const int numFoci = getNumberOfFoci(); for (int32_t i = 0; i < numFoci; i++) { const Focus* focus = getFocus(i); nameSet.insert(focus->getName()); classSet.insert(focus->getClassName()); } /* * Create colors for only the "best matching" color. */ for (std::set::iterator iter = nameSet.begin(); iter != nameSet.end(); iter++) { const AString colorName = *iter; const GiftiLabel* oldLabel = oldColorTable->getLabelBestMatching(colorName); if (oldLabel != NULL) { const AString bestMatchingName = oldLabel->getName(); const int32_t labelKey = m_nameColorTable->getLabelKeyFromName(bestMatchingName); if (labelKey < 0) { float rgba[4] = { 0.0, 0.0, 0.0, 1.0 }; oldLabel->getColor(rgba); m_nameColorTable->addLabel(bestMatchingName, rgba[0], rgba[1], rgba[2], rgba[3]); } } } /* * Create a color for each class name using the best matching color */ for (std::set::iterator iter = classSet.begin(); iter != classSet.end(); iter++) { const AString colorName = *iter; const GiftiLabel* label = oldColorTable->getLabelBestMatching(colorName); float rgba[4] = { 0.0, 0.0, 0.0, 1.0 }; if (label != NULL) { label->getColor(rgba); } m_classColorTable->addLabel(colorName, rgba[0], rgba[1], rgba[2], rgba[3]); } } /** * @return A string list containing all foci names * sorted in alphabetical order. */ QStringList FociFile::getAllFociNamesSorted() const { std::set nameSet; const int32_t numFoci = getNumberOfFoci(); for (int32_t i = 0;i < numFoci; i++) { nameSet.insert(m_foci[i]->getName()); } QStringList sl; for (std::set::iterator iter = nameSet.begin(); iter != nameSet.end(); iter++) { sl += *iter; } return sl; } /** * @return The version of the file as a number. */ int32_t FociFile::getFileVersion() { return FociFile::s_fociFileVersion; } /** * @return The version of the file as a string. */ AString FociFile::getFileVersionAsString() { return AString::number(FociFile::getFileVersion()); } /** * Invalidate all assigned colors. */ void FociFile::invalidateAllAssignedColors() { const int32_t numFoci = getNumberOfFoci(); for (int32_t i = 0;i < numFoci; i++) { m_foci[i]->setNameRgbaInvalid(); m_foci[i]->setClassRgbaInvalid(); } m_forceUpdateOfGroupAndNameHierarchy = true; } /** * Read the data file. * * @param filename * Name of the data file. * @throws DataFileException * If the file was not successfully read. */ void FociFile::readFile(const AString& filename) { clear(); checkFileReadability(filename); FociFileSaxReader saxReader(this); std::auto_ptr parser(XmlSaxParser::createXmlParser()); try { parser->parseFile(filename, &saxReader); } catch (const XmlSaxParserException& e) { clear(); setFileName(""); int lineNum = e.getLineNumber(); int colNum = e.getColumnNumber(); AString msg = "Parse Error while reading:"; if ((lineNum >= 0) && (colNum >= 0)) { msg += (" line/col (" + AString::number(e.getLineNumber()) + "/" + AString::number(e.getColumnNumber()) + ")"); } msg += (": " + e.whatString()); DataFileException dfe(filename, msg); CaretLogThrowing(dfe); throw dfe; } setFileName(filename); m_classNameHierarchy->update(this, true); m_forceUpdateOfGroupAndNameHierarchy = false; m_classNameHierarchy->setAllSelected(true); CaretLogFiner("CLASS/NAME Table for : " + getFileNameNoPath() + "\n" + m_classNameHierarchy->toString()); clearModified(); } /** * Write the data file. * * @param filename * Name of the data file. * @throws DataFileException * If the file was not successfully written. */ void FociFile::writeFile(const AString& filename) { checkFileWritability(filename); setFileName(filename); try { // // Format the version string so that it ends with at most one zero // const AString versionString = FociFile::getFileVersionAsString(); // // Open the file // FileAdapter file; AString errorMessage; QTextStream* textStream = file.openQTextStreamForWritingFile(getFileName(), errorMessage); if (textStream == NULL) { throw DataFileException(getFileName(), errorMessage); } // // Create the xml writer // XmlWriter xmlWriter(*textStream); // // Write header info // xmlWriter.writeStartDocument("1.0"); // // Write GIFTI root element // XmlAttributes attributes; //attributes.addAttribute("xmlns:xsi", // "http://www.w3.org/2001/XMLSchema-instance"); //attributes.addAttribute("xsi:noNamespaceSchemaLocation", // "http://brainvis.wustl.edu/caret6/xml_schemas/GIFTI_Caret.xsd"); attributes.addAttribute(FociFile::XML_ATTRIBUTE_VERSION, versionString); xmlWriter.writeStartElement(FociFile::XML_TAG_FOCI_FILE, attributes); // // Write Metadata // if (m_metadata != NULL) { m_metadata->writeAsXML(xmlWriter); } // // Write the class color table // xmlWriter.writeStartElement(XML_TAG_CLASS_COLOR_TABLE); m_classColorTable->writeAsXML(xmlWriter); xmlWriter.writeEndElement(); // // Write the name color table // xmlWriter.writeStartElement(XML_TAG_NAME_COLOR_TABLE); m_nameColorTable->writeAsXML(xmlWriter); xmlWriter.writeEndElement(); // // Write foci // const int32_t numFoci = getNumberOfFoci(); for (int32_t i = 0; i < numFoci; i++) { m_foci[i]->writeAsXML(xmlWriter, i); } xmlWriter.writeEndElement(); xmlWriter.writeEndDocument(); file.close(); clearModified(); } catch (const GiftiException& e) { throw DataFileException(e); } catch (const XmlException& e) { throw DataFileException(e); } } /** * @return Is this foci file modified? */ bool FociFile::isModified() const { if (CaretDataFile::isModified()) { return true; } if (m_metadata->isModified()) { return true; } if (m_classColorTable->isModified()) { return true; } if (m_nameColorTable->isModified()) { return true; } /* * Note, these members do not affect modification status: * classNameHierarchy */ const int32_t numFoci = getNumberOfFoci(); for (int32_t i = 0; i < numFoci; i++) { if (m_foci[i]->isModified()) { return true; } } return false; } /** * Clear the modification status of this foci file. */ void FociFile::clearModified() { CaretDataFile::clearModified(); m_metadata->clearModified(); m_classColorTable->clearModified(); m_nameColorTable->clearModified(); const int32_t numFoci = getNumberOfFoci(); for (int32_t i = 0; i < numFoci; i++) { m_foci[i]->clearModified(); } } /** * Add information about the file to the data file information. * * @param dataFileInformation * Consolidates information about a data file. */ void FociFile::addToDataFileContentInformation(DataFileContentInformation& dataFileInformation) { CaretDataFile::addToDataFileContentInformation(dataFileInformation); QStringList fociNames = getAllFociNamesSorted(); const int32_t numNames = fociNames.size(); if (numNames > 0) { AString namesListText = "FOCI NAMES"; for (int32_t i = 0; i < numNames; i++) { namesListText.appendWithNewLine(" " + fociNames.at(i)); } dataFileInformation.addText(namesListText); } } workbench-1.1.1/src/Files/FociFile.h000066400000000000000000000106301255417355300171700ustar00rootroot00000000000000#ifndef __FOCI_FILE__H_ #define __FOCI_FILE__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretDataFile.h" namespace caret { class GroupAndNameHierarchyModel; class Focus; class GiftiLabelTable; class GiftiMetaData; class FociFile : public CaretDataFile { public: FociFile(); virtual ~FociFile(); FociFile(const FociFile& obj); FociFile& operator=(const FociFile& obj); virtual void addToDataFileContentInformation(DataFileContentInformation& dataFileInformation); GiftiMetaData* getFileMetaData(); const GiftiMetaData* getFileMetaData() const; StructureEnum::Enum getStructure() const; void setStructure(const StructureEnum::Enum structure); void readFile(const AString& filename); void writeFile(const AString& filename); void clear(); bool isEmpty() const; int32_t getNumberOfFoci() const; void addFocus(Focus* focus); Focus* getFocus(const int32_t indx); const Focus* getFocus(const int32_t indx) const; void removeFocus(const int32_t indx); void removeFocus(Focus* focus); GiftiLabelTable* getClassColorTable(); const GiftiLabelTable* getClassColorTable() const; GiftiLabelTable* getNameColorTable(); const GiftiLabelTable* getNameColorTable() const; void createNameAndClassColorTables(const GiftiLabelTable* oldColorTable); GroupAndNameHierarchyModel* getGroupAndNameHierarchyModel(); QStringList getAllFociNamesSorted() const; void invalidateAllAssignedColors(); virtual bool isModified() const; virtual void clearModified(); static int32_t getFileVersion(); static AString getFileVersionAsString(); /** XML Tag for foci file */ static const AString XML_TAG_FOCI_FILE; /** XML Tag for Version attribute */ static const AString XML_ATTRIBUTE_VERSION; /** XML Tag for Name Color Table */ static const AString XML_TAG_NAME_COLOR_TABLE; /** XML Tag for Class Color Table */ static const AString XML_TAG_CLASS_COLOR_TABLE; private: void copyHelperFociFile(const FociFile& obj); void initializeFociFile(); GiftiMetaData* m_metadata; std::vector m_foci; /** Holds colors assigned to classes */ GiftiLabelTable* m_classColorTable; /** Holds colors assigned to names */ GiftiLabelTable* m_nameColorTable; /** Holds class and name hierarchy used for display selection */ mutable GroupAndNameHierarchyModel* m_classNameHierarchy; /** force an update of the class and name hierarchy */ bool m_forceUpdateOfGroupAndNameHierarchy; /** Version of this FociFile */ static const int32_t s_fociFileVersion; }; #ifdef __FOCI_FILE_DECLARE__ const AString FociFile::XML_TAG_FOCI_FILE = "FociFile"; const AString FociFile::XML_ATTRIBUTE_VERSION = "Version"; const AString FociFile::XML_TAG_NAME_COLOR_TABLE = "FociNameColorTable"; const AString FociFile::XML_TAG_CLASS_COLOR_TABLE = "FociClassColorTable"; const int32_t FociFile::s_fociFileVersion = 2; #endif // __FOCI_FILE_DECLARE__ } // namespace #endif //__FOCI_FILE__H_ workbench-1.1.1/src/Files/FociFileSaxReader.cxx000066400000000000000000000352071255417355300213510ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretAssert.h" #include "GiftiLabelTable.h" #include "CaretLogger.h" #include "GiftiXmlElements.h" #include "FociFile.h" #include "FociFileSaxReader.h" #include "Focus.h" #include "GiftiLabelTableSaxReader.h" #include "GiftiMetaDataSaxReader.h" #include "StudyMetaDataLinkSet.h" #include "StudyMetaDataLinkSetSaxReader.h" #include "SurfaceProjectedItem.h" #include "SurfaceProjectedItemSaxReader.h" #include "XmlAttributes.h" #include "XmlException.h" #include "XmlUtilities.h" using namespace caret; /** * \class caret::FociFileSaxReader * \brief Reads a foci file using a SAX XML Parser. */ /** * constructor. */ FociFileSaxReader::FociFileSaxReader(FociFile* fociFile) { CaretAssert(fociFile); m_fociFile = fociFile; m_state = STATE_NONE; m_stateStack.push(m_state); m_elementText = ""; m_metaDataSaxReader = NULL; m_labelTableSaxReader = NULL; m_surfaceProjectedItemSaxReader = NULL; m_focus = NULL; m_surfaceProjectedItem = NULL; m_studyMetaDataLinkSetSaxReader = NULL; m_projectionCounter = 0; m_versionOneColorTable = NULL; } /** * destructor. */ FociFileSaxReader::~FociFileSaxReader() { /* * If reading fails, allocated items need to be deleted. */ if (m_metaDataSaxReader != NULL) { delete m_metaDataSaxReader; } if (m_labelTableSaxReader != NULL) { delete m_labelTableSaxReader; } if (m_surfaceProjectedItemSaxReader != NULL) { delete m_surfaceProjectedItemSaxReader; } if (m_surfaceProjectedItem != NULL) { delete m_surfaceProjectedItem; } if (m_focus != NULL) { delete m_focus; } if (m_studyMetaDataLinkSetSaxReader != NULL) { delete m_studyMetaDataLinkSetSaxReader; } if (m_versionOneColorTable != NULL) { delete m_versionOneColorTable; } } /** * start an element. */ void FociFileSaxReader::startElement(const AString& namespaceURI, const AString& localName, const AString& qName, const XmlAttributes& attributes) { const STATE previousState = m_state; switch (m_state) { case STATE_NONE: if (qName == FociFile::XML_TAG_FOCI_FILE) { m_state = STATE_FOCI_FILE; /* * At one time version was float, but now integer so if * getting the version fails as integer get as float * and convert to integer. */ int32_t versionBeingRead = 0; try { versionBeingRead = attributes.getValueAsInt(FociFile::XML_ATTRIBUTE_VERSION); } catch (const XmlSaxParserException& /*e*/) { const float floatVersion = attributes.getValueAsFloat(FociFile::XML_ATTRIBUTE_VERSION); versionBeingRead = static_cast(floatVersion); } if (versionBeingRead > FociFile::getFileVersion()) { AString msg = XmlUtilities::createInvalidVersionMessage(FociFile::getFileVersion(), versionBeingRead); XmlSaxParserException e(msg); CaretLogThrowing(e); throw e; } } else { const AString msg = XmlUtilities::createInvalidRootElementMessage(FociFile::XML_TAG_FOCI_FILE, qName); XmlSaxParserException e(msg); CaretLogThrowing(e); throw e; } break; case STATE_FOCUS: if (qName == SurfaceProjectedItem::XML_TAG_SURFACE_PROJECTED_ITEM) { m_state = STATE_SURFACE_PROJECTED_ITEM; m_surfaceProjectedItem = new SurfaceProjectedItem(); m_surfaceProjectedItemSaxReader = new SurfaceProjectedItemSaxReader(m_surfaceProjectedItem); m_surfaceProjectedItemSaxReader->startElement(namespaceURI, localName, qName, attributes); } else if (qName == StudyMetaDataLinkSet::XML_TAG_STUDY_META_DATA_LINK_SET) { m_state = STATE_STUDY_META_DATA_LINK_SET; m_studyMetaDataLinkSetSaxReader = new StudyMetaDataLinkSetSaxReader(m_focus->getStudyMetaDataLinkSet()); m_studyMetaDataLinkSetSaxReader->startElement(namespaceURI, localName, qName, attributes); } break; case STATE_FOCI_FILE: if (qName == FociFile::XML_TAG_CLASS_COLOR_TABLE) { m_state = STATE_CLASS_COLOR_TABLE; } else if (qName == FociFile::XML_TAG_NAME_COLOR_TABLE) { m_state = STATE_NAME_COLOR_TABLE; } else if (qName == GiftiXmlElements::TAG_LABEL_TABLE) { m_state = STATE_VERSION_ONE_COLOR_TABLE; m_versionOneColorTable = new GiftiLabelTable(); m_labelTableSaxReader = new GiftiLabelTableSaxReader(m_versionOneColorTable); m_labelTableSaxReader->startElement(namespaceURI, localName, qName, attributes); } else if (qName == GiftiXmlElements::TAG_METADATA) { m_state = STATE_METADATA; m_metaDataSaxReader = new GiftiMetaDataSaxReader(m_fociFile->getFileMetaData()); m_metaDataSaxReader->startElement(namespaceURI, localName, qName, attributes); } else if (qName == Focus::XML_TAG_FOCUS) { m_state = STATE_FOCUS; m_focus = new Focus(); m_projectionCounter = 0; } else { const AString msg = XmlUtilities::createInvalidChildElementMessage(FociFile::XML_TAG_FOCI_FILE, qName); XmlSaxParserException e(msg); CaretLogThrowing(e); throw e; } break; case STATE_METADATA: m_metaDataSaxReader->startElement(namespaceURI, localName, qName, attributes); break; case STATE_VERSION_ONE_COLOR_TABLE: m_labelTableSaxReader->startElement(namespaceURI, localName, qName, attributes); break; case STATE_CLASS_COLOR_TABLE: if (qName == GiftiXmlElements::TAG_LABEL_TABLE) { m_labelTableSaxReader = new GiftiLabelTableSaxReader(m_fociFile->getClassColorTable()); m_labelTableSaxReader->startElement(namespaceURI, localName, qName, attributes); } else if (qName == GiftiXmlElements::TAG_LABEL) { m_labelTableSaxReader->startElement(namespaceURI, localName, qName, attributes); } else { const AString msg = XmlUtilities::createInvalidChildElementMessage(FociFile::XML_TAG_CLASS_COLOR_TABLE, qName); XmlSaxParserException e(msg); CaretLogThrowing(e); throw e; } break; case STATE_NAME_COLOR_TABLE: if (qName == GiftiXmlElements::TAG_LABEL_TABLE) { m_labelTableSaxReader = new GiftiLabelTableSaxReader(m_fociFile->getNameColorTable()); m_labelTableSaxReader->startElement(namespaceURI, localName, qName, attributes); } else if (qName == GiftiXmlElements::TAG_LABEL) { m_labelTableSaxReader->startElement(namespaceURI, localName, qName, attributes); } else { const AString msg = XmlUtilities::createInvalidChildElementMessage(FociFile::XML_TAG_NAME_COLOR_TABLE, qName); XmlSaxParserException e(msg); CaretLogThrowing(e); throw e; } break; case STATE_SURFACE_PROJECTED_ITEM: m_surfaceProjectedItemSaxReader->startElement(namespaceURI, localName, qName, attributes); break; case STATE_STUDY_META_DATA_LINK_SET: m_studyMetaDataLinkSetSaxReader->startElement(namespaceURI, localName, qName, attributes); break; } // // Save previous state // m_stateStack.push(previousState); m_elementText = ""; } /** * end an element. */ void FociFileSaxReader::endElement(const AString& namespaceURI, const AString& localName, const AString& qName) { switch (m_state) { case STATE_NONE: break; case STATE_FOCUS: CaretAssert(m_focus); if (qName == Focus::XML_TAG_FOCUS) { m_fociFile->addFocus(m_focus); m_focus = NULL; // do not delete since added to foci file m_projectionCounter = 0; } else { m_focus->setElementFromText(qName, m_elementText.trimmed()); } break; case STATE_FOCI_FILE: break; case STATE_METADATA: CaretAssert(m_metaDataSaxReader); m_metaDataSaxReader->endElement(namespaceURI, localName, qName); if (qName == GiftiXmlElements::TAG_METADATA) { delete m_metaDataSaxReader; m_metaDataSaxReader = NULL; } break; case STATE_VERSION_ONE_COLOR_TABLE: CaretAssert(m_labelTableSaxReader); m_labelTableSaxReader->endElement(namespaceURI, localName, qName); if (qName == GiftiXmlElements::TAG_LABEL_TABLE) { delete m_labelTableSaxReader; m_labelTableSaxReader = NULL; } break; case STATE_CLASS_COLOR_TABLE: if (qName != FociFile::XML_TAG_CLASS_COLOR_TABLE) { CaretAssert(m_labelTableSaxReader); m_labelTableSaxReader->endElement(namespaceURI, localName, qName); if (qName == GiftiXmlElements::TAG_LABEL_TABLE) { delete m_labelTableSaxReader; m_labelTableSaxReader = NULL; } } break; case STATE_NAME_COLOR_TABLE: if (qName != FociFile::XML_TAG_NAME_COLOR_TABLE) { CaretAssert(m_labelTableSaxReader); m_labelTableSaxReader->endElement(namespaceURI, localName, qName); if (qName == GiftiXmlElements::TAG_LABEL_TABLE) { delete m_labelTableSaxReader; m_labelTableSaxReader = NULL; } } break; case STATE_SURFACE_PROJECTED_ITEM: CaretAssert(m_surfaceProjectedItemSaxReader); m_surfaceProjectedItemSaxReader->endElement(namespaceURI, localName, qName); if (qName == SurfaceProjectedItem::XML_TAG_SURFACE_PROJECTED_ITEM) { if (m_projectionCounter == 0) { /* * Remove all projections when the first projection * is read so that projections can be added as * they are read. */ m_focus->removeAllProjections(); } m_projectionCounter++; m_focus->addProjection(m_surfaceProjectedItem); m_surfaceProjectedItem = NULL; // do not delete since added to focus delete m_surfaceProjectedItemSaxReader; m_surfaceProjectedItemSaxReader = NULL; } break; case STATE_STUDY_META_DATA_LINK_SET: CaretAssert(m_studyMetaDataLinkSetSaxReader); m_studyMetaDataLinkSetSaxReader->endElement(namespaceURI, localName, qName); if (qName == StudyMetaDataLinkSet::XML_TAG_STUDY_META_DATA_LINK_SET) { delete m_studyMetaDataLinkSetSaxReader; m_studyMetaDataLinkSetSaxReader = NULL; } } // // Clear out for new elements // m_elementText = ""; // // Go to previous state // if (m_stateStack.empty()) { throw XmlSaxParserException("State stack is empty while reading XML NiftDataFile."); } m_state = m_stateStack.top(); m_stateStack.pop(); } /** * get characters in an element. */ void FociFileSaxReader::characters(const char* ch) { if (m_metaDataSaxReader != NULL) { m_metaDataSaxReader->characters(ch); } else if (m_labelTableSaxReader != NULL) { m_labelTableSaxReader->characters(ch); } else if (m_surfaceProjectedItemSaxReader != NULL) { m_surfaceProjectedItemSaxReader->characters(ch); } else { m_elementText += ch; } } /** * a fatal error occurs. */ void FociFileSaxReader::fatalError(const XmlSaxParserException& e) { /* std::ostringstream str; str << "Fatal Error at line number: " << e.getLineNumber() << "\n" << "Column number: " << e.getColumnNumber() << "\n" << "Message: " << e.whatString(); if (errorMessage.isEmpty() == false) { str << "\n" << errorMessage; } errorMessage = str.str(); */ // // Stop parsing // throw e; } // a warning occurs void FociFileSaxReader::warning(const XmlSaxParserException& e) { CaretLogWarning("XML Parser Warning: " + e.whatString()); } // an error occurs void FociFileSaxReader::error(const XmlSaxParserException& e) { CaretLogSevere("XML Parser Error: " + e.whatString()); throw e; } void FociFileSaxReader::startDocument() { } void FociFileSaxReader::endDocument() { if (m_versionOneColorTable != NULL) { m_fociFile->createNameAndClassColorTables(m_versionOneColorTable); } } workbench-1.1.1/src/Files/FociFileSaxReader.h000066400000000000000000000105231255417355300207700ustar00rootroot00000000000000 #ifndef __FOCI_FILE_SAX_READER_H__ #define __FOCI_FILE_SAX_READER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include "XmlSaxParserException.h" #include "XmlSaxParserHandlerInterface.h" namespace caret { class Focus; class FociFile; class GiftiLabelTableSaxReader; class GiftiMetaDataSaxReader; class StudyMetaDataLinkSetSaxReader; class SurfaceProjectedItem; class SurfaceProjectedItemSaxReader; class XmlAttributes; class XmlException; class FociFileSaxReader : public CaretObject, public XmlSaxParserHandlerInterface { public: FociFileSaxReader(FociFile* fociFile); virtual ~FociFileSaxReader(); void startElement(const AString& namespaceURI, const AString& localName, const AString& qName, const XmlAttributes& attributes); void endElement(const AString& namspaceURI, const AString& localName, const AString& qName); void characters(const char* ch); void fatalError(const XmlSaxParserException& e); void warning(const XmlSaxParserException& e); void error(const XmlSaxParserException& e); void startDocument(); void endDocument(); protected: /// file reading states enum STATE { /// no state STATE_NONE, /// processing FociFile tag STATE_FOCI_FILE, /// processing MetaData tag STATE_METADATA, /// processing version one color table tag STATE_VERSION_ONE_COLOR_TABLE, /// processing class color table tag STATE_CLASS_COLOR_TABLE, /// processing name color table tag STATE_NAME_COLOR_TABLE, /// processing focus STATE_FOCUS, /// processing StudyMetaDataLinkSet tag STATE_STUDY_META_DATA_LINK_SET, /// processing SurfaceProjectedItem tag STATE_SURFACE_PROJECTED_ITEM }; /// file reading state STATE m_state; /// the state stack used when reading a file std::stack m_stateStack; /// the error message AString m_errorMessage; /// Foci file that is being read FociFile* m_fociFile; /// Focus that is being read Focus* m_focus; /// Counts projections as they are read int32_t m_projectionCounter; /// surface projected item that is being read SurfaceProjectedItem* m_surfaceProjectedItem; /// Reads a SurfaceProjectedItem SurfaceProjectedItemSaxReader* m_surfaceProjectedItemSaxReader; /// element text AString m_elementText; /// GIFTI meta data sax reader GiftiMetaDataSaxReader* m_metaDataSaxReader; /// GIFTI Label Table SAX Reader; GiftiLabelTableSaxReader* m_labelTableSaxReader; /** * Version 1 had only one color table that contained names and classes * After reading, split into name and class color tables */ GiftiLabelTable* m_versionOneColorTable; /// Study meta data link set reader StudyMetaDataLinkSetSaxReader* m_studyMetaDataLinkSetSaxReader; }; } // namespace #endif // __FOCI_FILE_SAX_READER_H__ workbench-1.1.1/src/Files/Focus.cxx000066400000000000000000000454461255417355300171570ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __FOCUS_DECLARE__ #include "Focus.h" #undef __FOCUS_DECLARE__ #include "CaretAssert.h" #include "CaretLogger.h" #include "StudyMetaDataLinkSet.h" #include "SurfaceProjectedItem.h" #include "XmlAttributes.h" #include "XmlWriter.h" using namespace caret; /** * \class caret::Focus * \brief A Focus. */ /** * Constructor. */ Focus::Focus() : CaretObjectTracksModification() { m_studyMetaDataLinkSet = new StudyMetaDataLinkSet(); clear(); } /** * Destructor. */ Focus::~Focus() { clear(); removeAllProjections(); delete m_studyMetaDataLinkSet; } /** * Copy constructor. * @param obj * Object that is copied. */ Focus::Focus(const Focus& obj) : CaretObjectTracksModification(obj) { m_studyMetaDataLinkSet = new StudyMetaDataLinkSet(); clear(); this->copyHelperFocus(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ Focus& Focus::operator=(const Focus& obj) { if (this != &obj) { CaretObjectTracksModification::operator=(obj); this->copyHelperFocus(obj); } return *this; } /** * Clear the focus. */ void Focus::clear() { m_area = ""; m_className = ""; m_comment = ""; m_extent = 0.0; m_geography = ""; m_name = ""; m_regionOfInterest = ""; m_searchXYZ[0] = 0.0; m_searchXYZ[1] = 0.0; m_searchXYZ[2] = 0.0; m_statistic = ""; m_studyMetaDataLinkSet->clear(); m_sumsIdNumber = ""; m_sumsRepeatNumber = ""; m_sumsParentFocusBaseId = ""; m_sumsVersionNumber = ""; m_sumsMSLID = ""; m_attributeID = ""; m_groupNameSelectionItem = NULL; m_nameRgbaColor[0] = 0.0; m_nameRgbaColor[1] = 0.0; m_nameRgbaColor[2] = 0.0; m_nameRgbaColor[3] = 1.0; m_nameRgbaColorValid = false; m_classRgbaColor[0] = 0.0; m_classRgbaColor[1] = 0.0; m_classRgbaColor[2] = 0.0; m_classRgbaColor[3] = 1.0; m_classRgbaColorValid = false; setNameOrClassModified(); // new name/class so modified removeAllProjections(); addProjection(new SurfaceProjectedItem()); } /** * Helps with copying an object of this type. * @param obj * Object that is copied. */ void Focus::copyHelperFocus(const Focus& focus) { clear(); m_area = focus.m_area; m_className = focus.m_className; m_comment = focus.m_comment; m_extent = focus.m_extent; m_geography = focus.m_geography; m_name = focus.m_name; m_regionOfInterest = focus.m_regionOfInterest; m_searchXYZ[0] = focus.m_searchXYZ[0]; m_searchXYZ[1] = focus.m_searchXYZ[1]; m_searchXYZ[2] = focus.m_searchXYZ[2]; m_statistic = focus.m_statistic; delete m_studyMetaDataLinkSet; m_studyMetaDataLinkSet = new StudyMetaDataLinkSet(*focus.m_studyMetaDataLinkSet); m_sumsIdNumber = focus.m_sumsIdNumber; m_sumsRepeatNumber = focus.m_sumsRepeatNumber; m_sumsParentFocusBaseId = focus.m_sumsParentFocusBaseId; m_sumsVersionNumber = focus.m_sumsVersionNumber; m_sumsMSLID = focus.m_sumsMSLID; m_attributeID = focus.m_attributeID; this->removeAllProjections(); const int numProj = focus.getNumberOfProjections(); for (int32_t i = 0; i < numProj; i++) { SurfaceProjectedItem* spi = new SurfaceProjectedItem(*focus.getProjection(i)); this->addProjection(spi); } if (m_projections.empty()) { this->addProjection(new SurfaceProjectedItem()); } setNameOrClassModified(); // new name/class so modified } /** * @return Class name */ AString Focus::getClassName() const { return m_className; } /** * Set the class name * @param className */ void Focus::setClassName(const AString& className) { if (m_className != className) { m_className = className; setNameOrClassModified(); setModified(); } } /** * @return Area */ AString Focus::getArea() const { return m_area; } /** * Set the area * @param area */ void Focus::setArea(const AString& area) { if (m_area != area) { m_area = area; setModified(); } } /** * @return Comment */ AString Focus::getComment() const { return m_comment; } /** * Set the comment * @param */ void Focus::setComment(const AString& comment) { if (m_comment != comment) { m_comment = comment; setModified(); } } /** * @return Extent */ float Focus::getExtent() const { return m_extent; } /** * Set the extent * @param extent */ void Focus::setExtent(const float extent) { if (m_extent != extent) { m_extent = extent; setModified(); } } /** * @return Geography */ AString Focus::getGeography() const { return m_geography; } /** * Set the geography. * @param geography */ void Focus::setGeography(const AString& geography) { m_geography = geography; } /** * @return Name */ AString Focus::getName() const { return m_name; } /** * Set the name * @param name */ void Focus::setName(const AString& name) { if (m_name != name) { m_name = name; setNameOrClassModified(); setModified(); } } /** * @return Region of interest */ AString Focus::getRegionOfInterest() const { return m_regionOfInterest; } /** * Set the region of interest * @param regionOfInterest */ void Focus::setRegionOfInterest(const AString& regionOfInterest) { if (m_regionOfInterest != regionOfInterest) { m_regionOfInterest = regionOfInterest; setModified(); } } /** * @return Search coordinate */ const float* Focus::getSearchXYZ() const { /* * If not set, return stereotaxic coordinate */ if ((m_searchXYZ[0] == 0.0) && (m_searchXYZ[1] == 0.0) && (m_searchXYZ[2] == 0.0)) { const float* stereoXYZ = m_projections[0]->getStereotaxicXYZ(); return stereoXYZ; } return m_searchXYZ; } /** * Set the search XYZ * @param searchXYZ */ void Focus::setSearchXYZ(const float searchXYZ[3]) { if ((m_searchXYZ[0] != searchXYZ[0]) || (m_searchXYZ[1] != searchXYZ[1]) || (m_searchXYZ[2] != searchXYZ[2])) { m_searchXYZ[0] = searchXYZ[0]; m_searchXYZ[1] = searchXYZ[1]; m_searchXYZ[2] = searchXYZ[2]; setModified(); } } /** * @return statistic */ AString Focus::getStatistic() const { return m_statistic; } /** * Set the statistic * @param statistic */ void Focus::setStatistic(const AString& statistic) { if (m_statistic != statistic) { m_statistic = statistic; setModified(); } } /** * @return Is the class RGBA color valid? */ bool Focus::isClassRgbaValid() const { return m_classRgbaColorValid; } /** * Set then class RGBA color invalid. */ void Focus::setClassRgbaInvalid() { m_classRgbaColorValid = false; } /** * @return The class RGBA color components * ranging zero to one. */ const float* Focus::getClassRgba() const { return m_classRgbaColor; } /** * Get the class RGBA color components * ranging zero to one. */ void Focus::getClassRgba(float rgba[4]) const { rgba[0] = m_classRgbaColor[0]; rgba[1] = m_classRgbaColor[1]; rgba[2] = m_classRgbaColor[2]; rgba[3] = m_classRgbaColor[3]; } /** * Set the RGBA color components assigned to the class. * @param rgba * Red, green, blue, alpha ranging zero to one. */ void Focus::setClassRgba(const float rgba[3]) { m_classRgbaColor[0] = rgba[0]; m_classRgbaColor[1] = rgba[1]; m_classRgbaColor[2] = rgba[2]; m_classRgbaColor[3] = rgba[3]; m_classRgbaColorValid = true; } /** * @return Is the name RGBA color valid? */ bool Focus::isNameRgbaValid() const { return m_nameRgbaColorValid; } /** * Set then name RGBA color invalid. */ void Focus::setNameRgbaInvalid() { m_nameRgbaColorValid = false; } /** * @return The name RGBA color components * ranging zero to one. */ const float* Focus::getNameRgba() const { return m_nameRgbaColor; } /** * Get the name RGBA color components * ranging zero to one. */ void Focus::getNameRgba(float rgba[4]) const { rgba[0] = m_nameRgbaColor[0]; rgba[1] = m_nameRgbaColor[1]; rgba[2] = m_nameRgbaColor[2]; rgba[3] = m_nameRgbaColor[3]; } /** * Set the RGBA color components assigned to the name. * @param rgba * Red, green, blue, alpha ranging zero to one. */ void Focus::setNameRgba(const float rgba[4]) { m_nameRgbaColor[0] = rgba[0]; m_nameRgbaColor[1] = rgba[1]; m_nameRgbaColor[2] = rgba[2]; m_nameRgbaColor[3] = rgba[3]; m_nameRgbaColorValid = true; } /** * Set the selection item for the group/name hierarchy. * * @param item * The selection item from the group/name hierarchy. */ void Focus::setGroupNameSelectionItem(GroupAndNameHierarchyItem* item) { m_groupNameSelectionItem = item; } /** * @return The selection item for the Group/Name selection hierarchy. * May be NULL in some circumstances. */ const GroupAndNameHierarchyItem* Focus::getGroupNameSelectionItem() const { return m_groupNameSelectionItem; } /** * @return Sums ID Number */ AString Focus::getSumsIdNumber() const { return m_sumsIdNumber; } /** * Set the Sums ID Number * @param sumsIdNumber */ void Focus::setSumsIdNumber(const AString& sumsIdNumber) { if (m_sumsIdNumber != sumsIdNumber) { m_sumsIdNumber = sumsIdNumber; setModified(); } } /** * @return Sums Repeat number */ AString Focus::getSumsRepeatNumber() const { return m_sumsRepeatNumber; } /** * Set the Sums Repeat Number * @param sumsRepeatNumber */ void Focus::setSumsRepeatNumber(const AString& sumsRepeatNumber) { if (m_sumsRepeatNumber != sumsRepeatNumber) { m_sumsRepeatNumber = sumsRepeatNumber; setModified(); } } /** * @return Sums parent focus base id */ AString Focus::getSumsParentFocusBaseId() const { return m_sumsParentFocusBaseId; } /** * Set the Sums Parent Focus Base ID * @param sumsParentFocusBaseId */ void Focus::setSumsParentFocusBaseId(const AString& sumsParentFocusBaseId) { if (m_sumsParentFocusBaseId != sumsParentFocusBaseId) { m_sumsParentFocusBaseId = sumsParentFocusBaseId; setModified(); } } /** * @return Sums version number */ AString Focus::getSumsVersionNumber() const { return m_sumsVersionNumber; } /** * Set the Sums version number * @param sumsVersionNumber */ void Focus::setSumsVersionNumber(const AString& sumsVersionNumber) { if (m_sumsVersionNumber != sumsVersionNumber) { m_sumsVersionNumber = sumsVersionNumber; setModified(); } } /** * @return Sums MSLID */ AString Focus::getSumsMSLID() const { return m_sumsMSLID; } /** * Set the Sums MSLID * @param sumsMSLID */ void Focus::setSumsMSLID(const AString& sumsMSLID) { if (m_sumsMSLID != sumsMSLID) { m_sumsMSLID = sumsMSLID; setModified(); } } /** * @return Sums attribute ID */ AString Focus::getSumsAttributeID() const { return m_attributeID; } /** * Set the Atribute ID * @param attributeID */ void Focus::setSumsAttributeID(const AString& attributeID) { if (m_attributeID != attributeID) { m_attributeID = attributeID; setModified(); } } /** * @return Number of projections */ int32_t Focus::getNumberOfProjections() const { return m_projections.size(); } /** * Get the projection at the given index. * Note: Index 0 will ALWAYS return a valid projection. * * @param indx * Index of projection * @return * Projection at given index. */ const SurfaceProjectedItem* Focus::getProjection(const int32_t indx) const { CaretAssertVectorIndex(m_projections, indx); return m_projections[indx]; } /** * Get the projection at the given index. * Note: Index 0 will ALWAYS return a valid projection. * * @param indx * Index of projection * @return * Projection at given index. */ SurfaceProjectedItem* Focus::getProjection(const int32_t indx) { CaretAssertVectorIndex(m_projections, indx); return m_projections[indx]; } /** * Add the projection. Note: the focus * takes ownership of the projection and will * delete it. After calling this method DO NOT * ever use the projection passed to this method. * * @param projection * Projection that is added. */ void Focus::addProjection(SurfaceProjectedItem* projection) { CaretAssert(projection); m_projections.push_back(projection); } /** * Remove all of the projections. A focus always * has one projection but this method removes all * projections so caller will need to to add a * projection. */ void Focus::removeAllProjections() { const int32_t numProj = getNumberOfProjections(); for (int32_t i = 0; i < numProj; i++) { delete m_projections[i]; } m_projections.clear(); } /** * Remove all but the first projection. */ void Focus::removeExtraProjections() { const int32_t numProj = getNumberOfProjections(); for (int32_t i = 1; i < numProj; i++) { delete m_projections[i]; } m_projections.resize(1); } /** * @return The study meta data link set. */ StudyMetaDataLinkSet* Focus::getStudyMetaDataLinkSet() { return m_studyMetaDataLinkSet; } /** * @return The study meta data link set. */ const StudyMetaDataLinkSet* Focus::getStudyMetaDataLinkSet() const { return m_studyMetaDataLinkSet; } /** * Write the focus to XML. * @param xmlWriter * Writer to which focus is written. * @param focusIndex * Index of the focus. */ void Focus::writeAsXML(XmlWriter& xmlWriter, const int32_t focusIndex) { XmlAttributes atts; atts.addAttribute(XML_ATTRIBUTE_FOCUS_INDEX, focusIndex); xmlWriter.writeStartElement(XML_TAG_FOCUS, atts); xmlWriter.writeElementCData(XML_TAG_AREA, m_area); xmlWriter.writeElementCData(XML_TAG_CLASS_NAME, m_className); xmlWriter.writeElementCData(XML_TAG_COMMENT, m_comment); xmlWriter.writeElementCharacters(XML_TAG_EXTENT, m_extent); xmlWriter.writeElementCData(XML_TAG_GEOGRAPHY, m_geography); xmlWriter.writeElementCData(XML_TAG_NAME, m_name); xmlWriter.writeElementCData(XML_TAG_REGION_OF_INTEREST, m_regionOfInterest); xmlWriter.writeElementCharacters(XML_TAG_SEARCH_XYZ, m_searchXYZ, 3); xmlWriter.writeElementCData(XML_TAG_STATISTIC, m_statistic); xmlWriter.writeElementCData(XML_TAG_SUMS_ID_NUMBER, m_sumsIdNumber); xmlWriter.writeElementCData(XML_TAG_SUMS_REPEAT_NUMBER, m_sumsRepeatNumber); xmlWriter.writeElementCData(XML_TAG_SUMS_PARENT_FOCUS_BASE_ID, m_sumsParentFocusBaseId); xmlWriter.writeElementCData(XML_TAG_SUMS_VERSION_NUMBER, m_sumsVersionNumber); xmlWriter.writeElementCData(XML_TAG_SUMS_MSLID, m_sumsMSLID); xmlWriter.writeElementCData(XML_TAG_SUMS_ATTRIBUTE_ID, m_attributeID); m_studyMetaDataLinkSet->writeXML(xmlWriter); const int32_t numProj = getNumberOfProjections(); for (int32_t i = 0; i < numProj; i++) { m_projections[i]->writeAsXML(xmlWriter); } xmlWriter.writeEndElement(); } /** * Set modification status of name/class to modified. * * Name/Class modification status is used * by the selection controls that display * borders based upon selected classes and * names. */ void Focus::setNameOrClassModified() { m_groupNameSelectionItem = NULL; m_nameRgbaColorValid = false; m_classRgbaColorValid = false; } /** * Set an element from its XML tag name * @param elementName * Name of XML element. * @param textValue * Value of element's text. * @return * True if element was valid, else false. */ bool Focus::setElementFromText(const AString& elementName, const AString& textValue) { if (elementName == Focus::XML_TAG_AREA) { m_area = textValue; } else if (elementName == Focus::XML_TAG_CLASS_NAME) { m_className = textValue; } else if (elementName == "Color") { // obsolete element } else if (elementName == Focus::XML_TAG_COMMENT) { m_comment = textValue; } else if (elementName == Focus::XML_TAG_EXTENT) { m_extent = textValue.toFloat(); } else if (elementName == Focus::XML_TAG_GEOGRAPHY) { m_geography = textValue; } else if (elementName == Focus::XML_TAG_NAME) { m_name = textValue; } else if (elementName == Focus::XML_TAG_REGION_OF_INTEREST) { m_regionOfInterest = textValue; } else if (elementName == Focus::XML_TAG_SEARCH_XYZ) { std::vector xyz; AString::toNumbers(textValue, xyz); if (xyz.size() == 3) { m_searchXYZ[0] = xyz[0]; m_searchXYZ[1] = xyz[1]; m_searchXYZ[2] = xyz[2]; } } else if (elementName == Focus::XML_TAG_STATISTIC) { m_statistic = textValue; } else if (elementName == Focus::XML_TAG_SUMS_ID_NUMBER) { m_sumsIdNumber = textValue; } else if (elementName == Focus::XML_TAG_SUMS_REPEAT_NUMBER) { m_sumsRepeatNumber = textValue; } else if (elementName == Focus::XML_TAG_SUMS_PARENT_FOCUS_BASE_ID) { m_sumsParentFocusBaseId = textValue; } else if (elementName == Focus::XML_TAG_SUMS_VERSION_NUMBER) { m_sumsVersionNumber = textValue; } else if (elementName == Focus::XML_TAG_SUMS_MSLID) { m_sumsMSLID = textValue; } else if (elementName == Focus::XML_TAG_SUMS_ATTRIBUTE_ID) { m_attributeID = textValue; } else { return false; } return true; } /** * Clear the modification status of the focus. */ void Focus::clearModified() { CaretObjectTracksModification::clearModified(); const int numProj = getNumberOfProjections(); for (int32_t i = 0; i < numProj; i++) { SurfaceProjectedItem* spi = getProjection(i); spi->clearModified(); } } /** * @return The modification status. */ bool Focus::isModified() const { if (CaretObjectTracksModification::isModified()) { return true; } const int numProj = getNumberOfProjections(); for (int32_t i = 0; i < numProj; i++) { const SurfaceProjectedItem* spi = getProjection(i); if (spi->isModified()) { return true; } } return false; } workbench-1.1.1/src/Files/Focus.h000066400000000000000000000177771255417355300166120ustar00rootroot00000000000000#ifndef __FOCUS__H_ #define __FOCUS__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObjectTracksModification.h" #include "SurfaceProjectedItem.h" namespace caret { class FociFileSaxReader; class GroupAndNameHierarchyItem; class StudyMetaDataLinkSet; class Focus : public CaretObjectTracksModification { public: Focus(); virtual ~Focus(); Focus(const Focus& obj); Focus& operator=(const Focus& obj); void clear(); AString getClassName() const; void setClassName(const AString& name); AString getArea() const; void setArea(const AString& area); AString getComment() const; void setComment(const AString& area); float getExtent() const; void setExtent(const float extent); AString getGeography() const; void setGeography(const AString& geography); AString getName() const; void setName(const AString& name); AString getRegionOfInterest() const; void setRegionOfInterest(const AString& regionOfInterest); const float* getSearchXYZ() const; void setSearchXYZ(const float searchXYZ[3]); AString getStatistic() const; void setStatistic(const AString& statistic); bool isClassRgbaValid() const; void setClassRgbaInvalid(); const float* getClassRgba() const; void getClassRgba(float rgba[4]) const; void setClassRgba(const float rgba[4]); bool isNameRgbaValid() const; void setNameRgbaInvalid(); const float* getNameRgba() const; void getNameRgba(float rgba[4]) const; void setNameRgba(const float rgba[4]); AString getSumsIdNumber() const; void setSumsIdNumber(const AString& sumsIdNumber); AString getSumsRepeatNumber() const; void setSumsRepeatNumber(const AString& sumsRepeatNumber); AString getSumsParentFocusBaseId() const; void setSumsParentFocusBaseId(const AString& sumsParentFocusBaseId); AString getSumsVersionNumber() const; void setSumsVersionNumber(const AString& sumsVersionNumber); AString getSumsMSLID() const; void setSumsMSLID(const AString& sumsMSLID); AString getSumsAttributeID() const; void setSumsAttributeID(const AString& attributeID); int32_t getNumberOfProjections() const; const SurfaceProjectedItem* getProjection(const int32_t indx) const; SurfaceProjectedItem* getProjection(const int32_t indx); void addProjection(SurfaceProjectedItem* projection); void removeExtraProjections(); StudyMetaDataLinkSet* getStudyMetaDataLinkSet(); const StudyMetaDataLinkSet* getStudyMetaDataLinkSet() const; void setGroupNameSelectionItem(GroupAndNameHierarchyItem* item); const GroupAndNameHierarchyItem* getGroupNameSelectionItem() const; void writeAsXML(XmlWriter& xmlWriter, const int32_t focusIndex); bool setElementFromText(const AString& elementName, const AString& textValue); virtual void clearModified(); virtual bool isModified() const; static const AString XML_ATTRIBUTE_FOCUS_INDEX; static const AString XML_TAG_FOCUS; static const AString XML_TAG_AREA; static const AString XML_TAG_CLASS_NAME; static const AString XML_TAG_COMMENT; static const AString XML_TAG_EXTENT; static const AString XML_TAG_GEOGRAPHY; static const AString XML_TAG_NAME; static const AString XML_TAG_REGION_OF_INTEREST; static const AString XML_TAG_SEARCH_XYZ; static const AString XML_TAG_STATISTIC; static const AString XML_TAG_SUMS_ID_NUMBER; static const AString XML_TAG_SUMS_REPEAT_NUMBER; static const AString XML_TAG_SUMS_PARENT_FOCUS_BASE_ID; static const AString XML_TAG_SUMS_VERSION_NUMBER; static const AString XML_TAG_SUMS_MSLID; static const AString XML_TAG_SUMS_ATTRIBUTE_ID; private: void copyHelperFocus(const Focus& obj); void setNameOrClassModified(); void removeAllProjections(); AString m_area; AString m_className; AString m_comment; float m_extent; AString m_geography; AString m_name; AString m_regionOfInterest; float m_searchXYZ[3]; AString m_statistic; StudyMetaDataLinkSet* m_studyMetaDataLinkSet; AString m_sumsIdNumber; AString m_sumsRepeatNumber; AString m_sumsParentFocusBaseId; AString m_sumsVersionNumber; AString m_sumsMSLID; AString m_attributeID; /** RGBA color components assigned to focus' name */ float m_nameRgbaColor[4]; /** RGBA color components assigned to focus' name validity */ bool m_nameRgbaColorValid; /** RGBA color component assigned to focus' class name */ float m_classRgbaColor[4]; /** RGBA color components assigned to focus' name validity */ bool m_classRgbaColorValid; /** May project to more than one surface */ std::vector m_projections; /** Selection status of this border in the group/name hierarchy */ GroupAndNameHierarchyItem* m_groupNameSelectionItem; /** Allow foci file SAX reader to remove all projections */ friend class FociFileSaxReader; }; #ifdef __FOCUS_DECLARE__ const AString Focus::XML_ATTRIBUTE_FOCUS_INDEX = "Index"; const AString Focus::XML_TAG_FOCUS = "Focus"; const AString Focus::XML_TAG_AREA = "Area"; const AString Focus::XML_TAG_CLASS_NAME = "ClassName"; const AString Focus::XML_TAG_COMMENT = "Comment"; const AString Focus::XML_TAG_EXTENT = "Extent"; const AString Focus::XML_TAG_GEOGRAPHY = "Geography"; const AString Focus::XML_TAG_NAME = "Name"; const AString Focus::XML_TAG_REGION_OF_INTEREST = "RegionOfInterest"; const AString Focus::XML_TAG_SEARCH_XYZ = "SearchXYZ"; const AString Focus::XML_TAG_STATISTIC = "Statistic"; const AString Focus::XML_TAG_SUMS_ID_NUMBER = "SumsIDNumber"; const AString Focus::XML_TAG_SUMS_REPEAT_NUMBER = "SumsRepeatNumber"; const AString Focus::XML_TAG_SUMS_PARENT_FOCUS_BASE_ID = "SumsParentFocusBaseID"; const AString Focus::XML_TAG_SUMS_VERSION_NUMBER = "SumsVersionNumber"; const AString Focus::XML_TAG_SUMS_MSLID = "SumsMSLID"; const AString Focus::XML_TAG_SUMS_ATTRIBUTE_ID = "AttributeID"; #endif // __FOCUS_DECLARE__ } // namespace #endif //__FOCUS__H_ workbench-1.1.1/src/Files/GeodesicHelper.cxx000066400000000000000000002042051255417355300207500ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "GeodesicHelper.h" #include "CaretAssert.h" #include "CaretHeap.h" #include "CaretMutex.h" #include "FastStatistics.h" #include "SurfaceFile.h" #include "TopologyHelper.h" #include #include #include using namespace caret; using namespace std; GeodesicHelperBase::GeodesicHelperBase(const SurfaceFile* surfaceIn, const float* correctedAreas) { CaretPointer topoBase(new TopologyHelperBase(surfaceIn)); TopologyHelper topoHelpIn(topoBase);//leave this building one privately, to not introduce even worse dependencies regarding SurfaceFile m_corrAreaSmallestFactor = 1.0f; numNodes = surfaceIn->getNumberOfNodes(); nodeNeighbors.resize(numNodes); distances.resize(numNodes); nodeCoords.resize(numNodes); vector sqrtCorrAreas;//each edge has 2 vertices that influence it - assume that each influences a piece of the edge with a ratio depending on the square roots of the vertex areas vector sqrtVertAreas;//we also assume isometric expansion at each vertex if (correctedAreas != NULL)//this gives an estimated original length of curLength * (sqrt(origA) + sqrt(origB))/(sqrt(curA) + sqrt(curB)) { surfaceIn->computeNodeAreas(sqrtVertAreas); sqrtCorrAreas.resize(numNodes); for (int i = 0; i < numNodes; ++i) { sqrtCorrAreas[i] = sqrt(correctedAreas[i]); sqrtVertAreas[i] = sqrt(sqrtVertAreas[i]); } } Vector3D tempvec; float tempf, abmag, efmag, cdmag; double nodeSpacingAccum = 0.0f;//since we may be using corrected areas, find average node spacing manually int32_t numEdges = 0; bool firstCorrArea = true;//if all corrected vertex areas are significantly larger than 1, we can make A* faster by multiplying all euclidean distances by it, so find the actual smallest for (int32_t i = 0; i < numNodes; ++i) {//get neighbors vector& neighbors = nodeNeighbors[i]; neighbors = topoHelpIn.getNodeNeighbors(i); nodeCoords[i] = surfaceIn->getCoordinate(i); const Vector3D baseCoord = nodeCoords[i]; int numNeigh = (int)neighbors.size(); distances[i].resize(numNeigh); for (int32_t j = 0; j < numNeigh; ++j) { Vector3D neighCoord = surfaceIn->getCoordinate(neighbors[j]); tempvec = baseCoord - neighCoord; distances[i][j] = tempvec.length();//precompute for speed in other calls if (correctedAreas != NULL) { float correctionFactor = (sqrtCorrAreas[i] + sqrtCorrAreas[neighbors[j]]) / (sqrtVertAreas[i] + sqrtVertAreas[neighbors[j]]); if (firstCorrArea || correctionFactor < m_corrAreaSmallestFactor) { m_corrAreaSmallestFactor = correctionFactor;//if this is zero anywhere, it just means that the euclidean part of the heuristic must be ignored (worst case, it does dijkstra) firstCorrArea = false; } distances[i][j] *= correctionFactor; } if (i < neighbors[j]) { nodeSpacingAccum += distances[i][j]; ++numEdges; } }//so few floating point operations, this should turn out symmetric } m_avgNodeSpacing = nodeSpacingAccum / numEdges; std::vector tempneigh2; std::vector tempdist2; nodeNeighbors2.resize(numNodes); distances2.resize(numNodes); neighbors2PathInfo.resize(numNodes); const vector& myEdgeInfo = topoHelpIn.getEdgeInfo(); CaretAssert(numEdges == (int32_t)myEdgeInfo.size());//SurfaceFile checks for triangles with duplicated nodes for (int i = 0; i < numEdges; ++i) { if (myEdgeInfo[i].numTiles < 2) { continue;//skip edges that have only one triangle } int32_t neigh1Node, neigh2Node, baseNode, farNode; neigh1Node = myEdgeInfo[i].node1; neigh2Node = myEdgeInfo[i].node2; baseNode = myEdgeInfo[i].tiles[0].node3; farNode = myEdgeInfo[i].tiles[1].node3; Vector3D neigh1Coord = nodeCoords[neigh1Node]; Vector3D neigh2Coord = nodeCoords[neigh2Node]; Vector3D baseCoord = nodeCoords[baseNode]; Vector3D farCoord = nodeCoords[farNode]; CrawlInfo tempInfo; tempInfo.edgeNodes[0] = neigh1Node; tempInfo.edgeNodes[1] = neigh2Node; const int32_t num_reserve = 8;//uses 8 in case it is used on a mesh with haphazard topology nodeNeighbors2[baseNode].reserve(num_reserve);//reserve should be fast if capacity is already num_reserve, and better than reallocating at 2 and 4, if vector allocation is naive doubling nodeNeighbors2[farNode].reserve(num_reserve);//in the extremely rare case of a node with more than num_reserve neighbors, a second allocation plus copy isn't much of a cost distances2[baseNode].reserve(num_reserve); distances2[farNode].reserve(num_reserve); neighbors2PathInfo[baseNode].reserve(num_reserve); neighbors2PathInfo[farNode].reserve(num_reserve); Vector3D abhat = (neigh2Coord - neigh1Coord).normal(&abmag);//a is neigh1, b is neigh2, b - a = (vector)ab Vector3D ac = farCoord - neigh1Coord;//c is farnode, c - a = (vector)ac Vector3D ad = abhat * abhat.dot(ac);//d is the point on the shared edge that farnode (c) is closest to Vector3D d = neigh1Coord + ad;//this way we can "unfold" the triangles by projecting the distance of cd, from point d, along the unit vector of the base node to closest point on shared edge Vector3D ea = neigh1Coord - baseCoord;//e is the base node, a - e = (vector)ea tempvec = abhat * abhat.dot(ea);//find vector fa, f being the point on shared edge closest to e, the base node Vector3D efhat = (ea - tempvec).normal(&efmag);//and subtract it to obtain only the perpendicular, normalize to get unit vector cdmag = (d - farCoord).length();//get the length from shared edge to far point Vector3D g = d + efhat * cdmag;//get point g, the unfolded position of farnode Vector3D eg = g - baseCoord;//this is the vector from base (e) to far node after unfolding (g), this is our distance, as long as the tetralateral is convex tempf = efmag / (efmag + cdmag);//now we need to check that the path stays inside the tetralateral (ie, that it is convex) Vector3D eh = eg * tempf;//this is a vector from e (base node) to the point on the shared edge that the full path (eg) crosses Vector3D ah = eh - ea;//eh - ea = eh + ae = ae + eh = ah, vector from neigh1 to the point on shared edge the path goes through tempf = ah.dot(abhat);//get the component along ab so we can test that it is positive and less than |ab| if (tempf <= 0.0f || tempf >= abmag) continue;//tetralateral is concave or triangular (degenerate), our path is invalid or not shorter, so consider next edge tempInfo.edgeWeight = 1.0f - tempf / abmag;//if tempf is almost zero, then the weight of point a (neigh1) is almost 1 tempf = eg.length();//this is our path length tempInfo.pieceDists[1] = eh.length();//we currently add things to farNode's neighbor info before baseNode's if (correctedAreas != NULL)//apply area correction approximation { float correctionFactor = (sqrtCorrAreas[baseNode] + sqrtCorrAreas[farNode]) / (sqrtVertAreas[baseNode] + sqrtVertAreas[farNode]); if (correctionFactor < m_corrAreaSmallestFactor) { m_corrAreaSmallestFactor = correctionFactor;//if this is zero anywhere, it just means that the euclidean part of the heuristic must be ignored (worst case, it does dijkstra) } tempf *= correctionFactor; tempInfo.pieceDists[1] *= correctionFactor; }//for now, assume it only depends on the expansion of the endpoints, and affects each part equally tempInfo.pieceDists[0] = tempf - tempInfo.pieceDists[1]; nodeNeighbors2[farNode].push_back(baseNode);//record it at both ends, because we are looping through edges distances2[farNode].push_back(tempf); neighbors2PathInfo[farNode].push_back(tempInfo); float tempf2 = tempInfo.pieceDists[0];//swap the piece distances around for the baseNode info tempInfo.pieceDists[0] = tempInfo.pieceDists[1]; tempInfo.pieceDists[1] = tempf2; nodeNeighbors2[baseNode].push_back(farNode); distances2[baseNode].push_back(tempf); neighbors2PathInfo[baseNode].push_back(tempInfo); } } GeodesicHelper::GeodesicHelper(const CaretPointer& baseIn) { m_myBase = baseIn;//copy the pointer so it doesn't get changed or deleted while we get its members //get references and info from base numNodes = m_myBase->numNodes; m_avgNodeSpacing = m_myBase->m_avgNodeSpacing; m_corrAreaSmallestFactor = m_myBase->m_corrAreaSmallestFactor; distances = m_myBase->distances.data(); distances2 = m_myBase->distances2.data(); nodeNeighbors = m_myBase->nodeNeighbors.data(); nodeNeighbors2 = m_myBase->nodeNeighbors2.data(); nodeCoords = m_myBase->nodeCoords.data(); neighbors2PathInfo = m_myBase->neighbors2PathInfo.data(); //allocate private scratch space marked.resize(numNodes, 0);//initialize once, each internal function (dijkstra methods) tracks elements changed, and resets only those (except in the case of whole surface) m_heapIdent.resize(numNodes);//the idea is to make it faster for the more likely case of small areas of the surface for functions that have limits, by removing the runtime term based solely on surface size outputStore.resize(numNodes); output = outputStore.data();//because we have a function that does a pointer swap to compute distances directly in the output array changed.resize(numNodes); parentStore.resize(numNodes); parent = parentStore.data();//ditto for parents heurVal.resize(numNodes); } void GeodesicHelper::getNodesToGeoDist(const int32_t node, const float maxdist, std::vector& nodesOut, std::vector& distsOut, const bool smoothflag) {//public methods sanity check, private methods process nodesOut.clear(); distsOut.clear(); CaretAssert(node < numNodes && node >= 0); if (node >= numNodes || maxdist < 0.0f || node < 0) return;//check what we asserted so release doesn't do strange things CaretMutexLocker locked(&inUse);//let sanity checks go multithreaded, as if it mattered dijkstra(node, maxdist, nodesOut, distsOut, smoothflag); } void GeodesicHelper::getNodesToGeoDist(const int32_t node, const float maxdist, std::vector& nodesOut, std::vector& distsOut, std::vector& parentsOut, const bool smoothflag) {//public methods sanity check, private methods process nodesOut.clear(); distsOut.clear(); CaretAssert(node < numNodes && node >= 0); if (node >= numNodes || maxdist < 0.0f || node < 0) return; CaretMutexLocker locked(&inUse);//we need the parents array to stay put, so don't scope this dijkstra(node, maxdist, nodesOut, distsOut, smoothflag); int32_t mysize = (int32_t)nodesOut.size(); parentsOut.resize(mysize); for (int32_t i = 0; i < mysize; ++i) { parentsOut[i] = parent[nodesOut[i]]; } } void GeodesicHelper::dijkstra(const int32_t root, const float maxdist, std::vector& nodes, std::vector& dists, bool smooth) { int32_t i, j, whichnode, whichneigh, numNeigh, numChanged = 0; const int32_t* neighbors; float tempf; output[root] = 0.0f; marked[root] |= 4; parent[root] = -1;//idiom for end of path changed[numChanged++] = root; m_active.clear(); m_heapIdent[root] = m_active.push(root, 0.0f); //we keep values greater than maxdist off the heap, so anything pulled from the heap which is unmarked belongs in the list while (!m_active.isEmpty()) { whichnode = m_active.pop(); nodes.push_back(whichnode); dists.push_back(output[whichnode]); marked[whichnode] |= 1;//anything pulled from heap will already be marked as having a valid value (flag 4) neighbors = nodeNeighbors[whichnode].data(); numNeigh = (int32_t)nodeNeighbors[whichnode].size(); for (j = 0; j < numNeigh; ++j) { whichneigh = neighbors[j]; if (!(marked[whichneigh] & 1)) {//skip floating point math if frozen tempf = output[whichnode] + distances[whichnode][j];//isn't precomputation wonderful if (tempf <= maxdist) {//keep it off the heap if it is too far if (!(marked[whichneigh] & 4)) { marked[whichneigh] |= 4; changed[numChanged++] = whichneigh; output[whichneigh] = tempf; parent[whichneigh] = whichnode; m_heapIdent[whichneigh] = m_active.push(whichneigh, tempf); } else if (tempf < output[whichneigh]) { output[whichneigh] = tempf; parent[whichneigh] = whichnode; m_active.changekey(m_heapIdent[whichneigh], tempf); } } } } if (smooth)//repeat with numNeighbors2, nodeNeighbors2, distance2 { neighbors = nodeNeighbors2[whichnode].data(); numNeigh = (int32_t)nodeNeighbors2[whichnode].size(); for (j = 0; j < numNeigh; ++j) { whichneigh = neighbors[j]; if (!(marked[whichneigh] & 1)) {//skip floating point math if frozen tempf = output[whichnode] + distances2[whichnode][j]; if (tempf <= maxdist) {//keep it off the heap if it is too far if (!(marked[whichneigh] & 4)) { marked[whichneigh] |= 4; changed[numChanged++] = whichneigh; output[whichneigh] = tempf; parent[whichneigh] = whichnode; m_heapIdent[whichneigh] = m_active.push(whichneigh, tempf); } else if (tempf < output[whichneigh]) { output[whichneigh] = tempf; parent[whichneigh] = whichnode; m_active.changekey(m_heapIdent[whichneigh], tempf); } } } } } } for (i = 0; i < numChanged; ++i) { marked[changed[i]] = 0;//minimize reinitialization of arrays } } void GeodesicHelper::dijkstra(const int32_t root, bool smooth) {//straightforward dijkstra, no cutoffs, full surface int32_t i, j, whichnode, whichneigh, numNeigh; const int32_t* neighbors; float tempf; output[root] = 0.0f; parent[root] = -1;//idiom for end of path m_active.clear(); m_heapIdent[root] = m_active.push(root, 0.0f); while (!m_active.isEmpty()) { whichnode = m_active.pop(); marked[whichnode] |= 1; neighbors = nodeNeighbors[whichnode].data(); numNeigh = (int32_t)nodeNeighbors[whichnode].size(); for (j = 0; j < numNeigh; ++j) { whichneigh = neighbors[j]; if (!(marked[whichneigh] & 1)) {//skip floating point math if frozen tempf = output[whichnode] + distances[whichnode][j]; if (!(marked[whichneigh] & 4)) { marked[whichneigh] |= 4; output[whichneigh] = tempf; parent[whichneigh] = whichnode; m_heapIdent[whichneigh] = m_active.push(whichneigh, tempf); } else if (tempf < output[whichneigh]) { output[whichneigh] = tempf; parent[whichneigh] = whichnode; m_active.changekey(m_heapIdent[whichneigh], tempf); } } } if (smooth) { neighbors = nodeNeighbors2[whichnode].data(); numNeigh = (int32_t)nodeNeighbors2[whichnode].size(); for (j = 0; j < numNeigh; ++j) { whichneigh = neighbors[j]; if (!(marked[whichneigh] & 1)) {//skip floating point math if frozen tempf = output[whichnode] + distances2[whichnode][j]; if (!(marked[whichneigh] & 4)) { marked[whichneigh] |= 4; output[whichneigh] = tempf; parent[whichneigh] = whichnode; m_heapIdent[whichneigh] = m_active.push(whichneigh, tempf); } else if (tempf < output[whichneigh]) { output[whichneigh] = tempf; parent[whichneigh] = whichnode; m_active.changekey(m_heapIdent[whichneigh], tempf); } } } } } for (i = 0; i < numNodes; ++i) { marked[i] = 0; } } float** GeodesicHelper::getGeoAllToAll(const bool smooth) { float bytes = (float)(((long long)numNodes) * numNodes * (sizeof(float) + sizeof(int32_t)) + numNodes * (sizeof(float*) + sizeof(int32_t*))); short index = 0; static const char *labels[9] = {" Bytes", " Kilobytes", " Megabytes", " Gigabytes", " Terabytes", " Petabytes", " Exabytes", " Zettabytes", " Yottabytes"}; while (index < 8 && bytes > 1000.0f) { ++index; bytes = bytes / 1000.0f;//using 1024 would make it Kibibytes, etc } CaretMutexLocker locked(&inUse);//don't sit there with memory allocated but locked out of computation, lock early - also before status messages std::cout << "attempting to allocate " << AString::number(bytes, 'f', 2) << labels[index] << "..."; std::cout.flush(); int32_t i = -1, j; bool fail = false; float** ret = NULL; int32_t** parents = NULL; try { ret = new float*[numNodes]; if (ret != NULL) { for (i = 0; i < numNodes; ++i) { ret[i] = new float[numNodes]; if (ret[i] == NULL) { fail = true; break;//have to break so it doesn't increment i } } } } catch (std::bad_alloc e) {//should catch if new likes to throw exceptions instead of returning null fail = true; } if (ret == NULL) { std::cout << "failed" << std::endl; return NULL; } if (fail) { std::cout << "failed" << std::endl; for (j = 0; j < i; ++j) delete[] ret[j]; if (i > -1) delete[] ret; return NULL; } i = -1; try { parents = new int32_t*[numNodes]; if (parents != NULL) { for (i = 0; i < numNodes; ++i) { parents[i] = new int32_t[numNodes]; if (parents[i] == NULL) { fail = true; break;//have to break so it doesn't increment i } } } } catch (std::bad_alloc e) {//should catch if new likes to throw exceptions instead of returning null fail = true; } if (parents == NULL) { std::cout << "failed" << std::endl; for (i = 0; i < numNodes; ++i) delete[] ret[i]; delete[] ret; return NULL; } if (fail) { std::cout << "failed" << std::endl; for (j = 0; j < i; ++j) delete[] parents[j]; if (i > -1) delete[] parents; for (i = 0; i < numNodes; ++i) delete[] ret[i]; delete[] ret; return NULL; } std::cout << "success" << std::endl; alltoall(ret, parents, smooth); for (i = 0; i < numNodes; ++i) delete[] parents[i]; delete[] parents; return ret; } void GeodesicHelper::alltoall(float** out, int32_t** parents, bool smooth) {//propagates info about shortest paths not containing root to other roots, hopefully making the problem tractable int32_t root, i, j, whichnode, whichneigh, numNeigh, remain, midpoint, midrevparent, endparent, prevdots = 0, dots; const int32_t* neighbors; float tempf, tempf2; for (i = 0; i < numNodes; ++i) { for (j = 0; j < numNodes; ++j) { out[i][j] = -1.0f; } } remain = numNodes; std::cout << "|0% calculating geodesic distances 100%|" << std::endl; // .................................................. for (root = 0; root < numNodes; ++root) { dots = (50 * root) / numNodes;//simple progress indicator while (prevdots < dots) { std::cout << '.'; std::cout.flush(); ++prevdots; } if (root != 0) { remain = 0; for (i = 0; i < numNodes; ++i)//find known values { if (out[root][i] > 0.0f) { marked[i] = 2;//mark that we already have a value, skip calculation, but not yet added to active list } else { marked[i] = 0; ++remain;//count how many more we need to compute so we can stop early } } }//marking done, dijkstra time m_active.clear(); out[root][root] = 0.0f; parents[root][root] = -1;//idiom for end of path m_heapIdent[root] = m_active.push(root, 0.0f); while (remain && !m_active.isEmpty()) { whichnode = m_active.pop(); if (!(marked[whichnode] & 1)) { if (!(marked[whichnode] & 2)) --remain; marked[whichnode] |= 1; neighbors = nodeNeighbors[whichnode].data(); numNeigh = (int32_t)nodeNeighbors[whichnode].size(); for (j = 0; j < numNeigh; ++j) { whichneigh = neighbors[j]; if (marked[whichneigh] & 2) {//already has a value and parent for this root if (!(marked[whichneigh] & 8)) {//not yet in active list m_heapIdent[whichneigh] = m_active.push(whichneigh, out[root][whichneigh]); marked[whichneigh] |= 8; } } else { if (!(marked[whichneigh] & 1)) {//skip floating point math if marked tempf = out[root][whichnode] + distances[whichnode][j]; if (!(marked[whichneigh] & 4)) { out[root][whichneigh] = tempf; parents[root][whichneigh] = whichnode; marked[whichneigh] |= 4; m_heapIdent[whichneigh] = m_active.push(whichneigh, tempf); } else if (tempf < out[root][whichneigh]) { out[root][whichneigh] = tempf; parents[root][whichneigh] = whichnode; m_active.changekey(m_heapIdent[whichneigh], tempf); } } } } if (smooth) { neighbors = nodeNeighbors2[whichnode].data(); numNeigh = (int32_t)nodeNeighbors2[whichnode].size(); for (j = 0; j < numNeigh; ++j) { whichneigh = neighbors[j]; if (marked[whichneigh] & 2) {//already has a value and parent for this root if (!(marked[whichneigh] & 8)) {//not yet in active list m_heapIdent[whichneigh] = m_active.push(whichneigh, out[root][whichneigh]); marked[whichneigh] |= 8; } } else { if (!(marked[whichneigh] & 1)) {//skip floating point math if marked tempf = out[root][whichnode] + distances2[whichnode][j]; if (!(marked[whichneigh] & 4)) { out[root][whichneigh] = tempf; parents[root][whichneigh] = whichnode; marked[whichneigh] |= 4; m_heapIdent[whichneigh] = m_active.push(whichneigh, tempf); } else if (tempf < out[root][whichneigh]) { out[root][whichneigh] = tempf; parents[root][whichneigh] = whichnode; m_active.changekey(m_heapIdent[whichneigh], tempf); } } } } } } }//dijkstra done...lotsa brackets...now to propagate the information gained to other roots for (i = root + 1; i < numNodes; ++i) {//any node smaller than root already has all distances calculated for entire surface if (!(marked[i] & 2)) {//if endpoint already had distance to root precomputed, all available info from this node has been propagated previously midrevparent = i; midpoint = parents[root][i]; endparent = midpoint; tempf = out[root][i]; while (midpoint != root) { tempf2 = tempf - out[root][midpoint]; if (midpoint > root) {//try to be swap-friendly by not setting values in finished columns out[midpoint][i] = tempf2;//use midpoint as root, parent of endpoint is endparent parents[midpoint][i] = endparent; } out[i][midpoint] = tempf2;//use endpoint as root (so, reverse the path), parent of midpoint is midrevparent parents[i][midpoint] = midrevparent; midrevparent = midpoint;//step along path midpoint = parents[root][midpoint]; } out[i][root] = out[root][i];//finally, fill the transpose parents[i][root] = midrevparent; } }//propagation of best paths to other roots complete, dijkstra again } while (prevdots < 50) { std::cout << '.'; ++prevdots; } std::cout << std::endl; for (i = 0; i < numNodes; ++i) { marked[i] = 0; } } void GeodesicHelper::getGeoFromNode(const int32_t node, float* valuesOut, const bool smoothflag) { CaretAssert(node >= 0 && node < numNodes && valuesOut != NULL); if (node < 0 || node >= numNodes || !valuesOut) { return; } CaretMutexLocker locked(&inUse);//don't screw with member variables while in use float* temp = output;//swap out the output pointer to avoid allocation output = valuesOut; dijkstra(node, smoothflag); output = temp;//restore the pointer to the original memory } void GeodesicHelper::getGeoFromNode(const int32_t node, float* valuesOut, int32_t* parentsOut, const bool smoothflag) { CaretAssert(node >= 0 && node < numNodes && valuesOut != NULL && parentsOut != NULL); if (node < 0 || node >= numNodes || !valuesOut || !parentsOut) { return; } CaretMutexLocker locked(&inUse);//don't screw with member variables while in use float* temp = output;//swap out the output pointer to avoid allocation int32_t* tempi = parent; output = valuesOut; parent = parentsOut; dijkstra(node, smoothflag); output = temp;//restore the pointers to the original memory parent = tempi; } void GeodesicHelper::getGeoFromNode(const int32_t node, std::vector& valuesOut, const bool smoothflag) { CaretAssert(node >= 0 && node < numNodes); if (node < 0 || node >= numNodes) { valuesOut.clear();//empty array is error condition return; } CaretMutexLocker locked(&inUse); float* temp = output;//swap the output pointer to avoid copy valuesOut.resize(numNodes); output = valuesOut.data(); dijkstra(node, smoothflag); output = temp;//restore } void GeodesicHelper::getGeoFromNode(const int32_t node, std::vector& valuesOut, std::vector& parentsOut, const bool smoothflag) { CaretAssert(node >= 0 && node < numNodes); if (node < 0 || node >= numNodes) { return; } CaretMutexLocker locked(&inUse); float* temp = output;//swap out the output pointer to avoid copying into the vector afterwards int32_t* tempi = parent; valuesOut.resize(numNodes); parentsOut.resize(numNodes); output = valuesOut.data(); parent = parentsOut.data(); dijkstra(node, smoothflag); output = temp;//restore the pointers to the original memory parent = tempi; } void GeodesicHelper::dijkstra(const int32_t root, const std::vector& interested, bool smooth) { int32_t i, j, whichnode, whichneigh, numNeigh, numChanged = 0, remain = 0; const int32_t* neighbors; float tempf; j = interested.size(); for (i = 0; i < j; ++i) { whichnode = interested[i]; if (!marked[whichnode]) { ++remain; marked[whichnode] = 2;//interested, not expanded, no valid value changed[numChanged++] = whichnode; } } output[root] = 0.0f; if (!marked[root]) { changed[numChanged++] = root; } marked[root] |= 4; parent[root] = -1;//idiom for end of path m_active.clear(); m_heapIdent[root] = m_active.push(root, 0.0f); while (remain && !m_active.isEmpty()) { whichnode = m_active.pop(); if (marked[whichnode] & 2) { --remain; } marked[whichnode] |= 1;//anything pulled from heap will already be marked as having a valid value (flag 4), so already in changed list neighbors = nodeNeighbors[whichnode].data(); numNeigh = (int32_t)nodeNeighbors[whichnode].size(); for (j = 0; j < numNeigh; ++j) { whichneigh = neighbors[j]; if (!(marked[whichneigh] & 1)) {//skip floating point math if frozen tempf = output[whichnode] + distances[whichnode][j];//isn't precomputation wonderful if (!(marked[whichneigh] & 4)) { if (!marked[whichneigh]) { changed[numChanged++] = whichneigh; } marked[whichneigh] |= 4; output[whichneigh] = tempf; parent[whichneigh] = whichnode; m_heapIdent[whichneigh] = m_active.push(whichneigh, tempf); } else if (tempf < output[whichneigh]) { output[whichneigh] = tempf; parent[whichneigh] = whichnode; m_active.changekey(m_heapIdent[whichneigh], tempf); } } } if (smooth)//repeat with numNeighbors2, nodeNeighbors2, distance2 { neighbors = nodeNeighbors2[whichnode].data(); numNeigh = (int32_t)nodeNeighbors2[whichnode].size(); for (j = 0; j < numNeigh; ++j) { whichneigh = neighbors[j]; if (!(marked[whichneigh] & 1)) {//skip floating point math if frozen tempf = output[whichnode] + distances2[whichnode][j]; if (!(marked[whichneigh] & 4)) { if (!marked[whichneigh]) { changed[numChanged++] = whichneigh; } marked[whichneigh] |= 4; output[whichneigh] = tempf; parent[whichneigh] = whichnode; m_heapIdent[whichneigh] = m_active.push(whichneigh, tempf); } else if (tempf < output[whichneigh]) { output[whichneigh] = tempf; parent[whichneigh] = whichnode; m_active.changekey(m_heapIdent[whichneigh], tempf); } } } } } for (i = 0; i < numChanged; ++i) { marked[changed[i]] = 0;//minimize reinitialization of arrays } } int32_t GeodesicHelper::dijkstra(const vector& startList, const vector& endList, const float& maxDist, bool smooth) { int32_t i, j, whichnode, whichneigh, numNeigh, numChanged = 0, ret = -1; const int32_t* neighbors; float tempf; m_active.clear(); j = (int32_t)startList.size(); for (i = 0; i < j; ++i) { if (marked[startList[i]] == 0) { output[startList[i]] = 0.0f; changed[numChanged++] = startList[i]; marked[startList[i]] = 4;//has valid value parent[startList[i]] = -1;//idiom for end of path m_heapIdent[startList[i]] = m_active.push(startList[i], 0.0f); } } j = (int32_t)endList.size(); for (i = 0; i < j; ++i) { if (marked[endList[i]] == 0) { changed[numChanged++] = endList[i]; marked[endList[i]] = 8;//stopping point } } while (!m_active.isEmpty()) { whichnode = m_active.pop(); if ((marked[whichnode] & 8) != 0)//we have found the closest node in the endList, we are done { ret = whichnode; break; } marked[whichnode] |= 1;//anything pulled from heap will already be marked as having a valid value (flag 4), so already in changed list neighbors = nodeNeighbors[whichnode].data(); numNeigh = (int32_t)nodeNeighbors[whichnode].size(); for (j = 0; j < numNeigh; ++j) { whichneigh = neighbors[j]; if (!(marked[whichneigh] & 1)) {//skip floating point math if frozen tempf = output[whichnode] + distances[whichnode][j]; if (tempf <= maxDist) { if (!(marked[whichneigh] & 4)) { parent[whichneigh] = whichnode; if (!marked[whichneigh]) { changed[numChanged++] = whichneigh; } marked[whichneigh] |= 4; output[whichneigh] = tempf; m_heapIdent[whichneigh] = m_active.push(whichneigh, tempf); } else if (tempf < output[whichneigh]) { m_active.changekey(m_heapIdent[whichneigh], tempf); output[whichneigh] = tempf; parent[whichneigh] = whichnode; } } } } if (smooth)//repeat with numNeighbors2, nodeNeighbors2, distance2 { neighbors = nodeNeighbors2[whichnode].data(); numNeigh = (int32_t)nodeNeighbors2[whichnode].size(); for (j = 0; j < numNeigh; ++j) { whichneigh = neighbors[j]; if (!(marked[whichneigh] & 1)) {//skip floating point math if frozen tempf = output[whichnode] + distances2[whichnode][j]; if (tempf <= maxDist) { if (!(marked[whichneigh] & 4)) { parent[whichneigh] = whichnode; if (!marked[whichneigh]) { changed[numChanged++] = whichneigh; } marked[whichneigh] |= 4; output[whichneigh] = tempf; m_heapIdent[whichneigh] = m_active.push(whichneigh, tempf); } else if (tempf < output[whichneigh]) { m_active.changekey(m_heapIdent[whichneigh], tempf); output[whichneigh] = tempf; parent[whichneigh] = whichnode; } } } } } } for (i = 0; i < numChanged; ++i) { marked[changed[i]] = 0;//minimize reinitialization of arrays } return ret; } int32_t GeodesicHelper::closest(const int32_t& root, const char* roi, const float& maxdist, float& distOut, bool smooth) { int32_t i, j, whichnode, whichneigh, numNeigh, numChanged = 0, ret = -1; const int32_t* neighbors; float tempf; output[root] = 0.0f; changed[numChanged++] = root; marked[root] |= 4; parent[root] = -1;//idiom for end of path m_active.clear(); m_heapIdent[root] = m_active.push(root, 0.0f); while (!m_active.isEmpty()) { whichnode = m_active.pop(); if (roi[whichnode] != 0)//we have found the closest node in the roi to the root, we are done { distOut = output[whichnode]; ret = whichnode; break; } marked[whichnode] |= 1;//anything pulled from heap will already be marked as having a valid value (flag 4), so already in changed list neighbors = nodeNeighbors[whichnode].data(); numNeigh = (int32_t)nodeNeighbors[whichnode].size(); for (j = 0; j < numNeigh; ++j) { whichneigh = neighbors[j]; if (!(marked[whichneigh] & 1)) {//skip floating point math if frozen tempf = output[whichnode] + distances[whichnode][j];//isn't precomputation wonderful if (tempf <= maxdist) { if (!(marked[whichneigh] & 4)) { parent[whichneigh] = whichnode; if (!marked[whichneigh]) { changed[numChanged++] = whichneigh; } marked[whichneigh] |= 4; output[whichneigh] = tempf; m_heapIdent[whichneigh] = m_active.push(whichneigh, tempf); } else if (tempf < output[whichneigh]) { m_active.changekey(m_heapIdent[whichneigh], tempf); output[whichneigh] = tempf; parent[whichneigh] = whichnode; } } } } if (smooth)//repeat with numNeighbors2, nodeNeighbors2, distance2 { neighbors = nodeNeighbors2[whichnode].data(); numNeigh = (int32_t)nodeNeighbors2[whichnode].size(); for (j = 0; j < numNeigh; ++j) { whichneigh = neighbors[j]; if (!(marked[whichneigh] & 1)) {//skip floating point math if frozen tempf = output[whichnode] + distances2[whichnode][j];//isn't precomputation wonderful if (tempf <= maxdist) { if (!(marked[whichneigh] & 4)) { parent[whichneigh] = whichnode; if (!marked[whichneigh]) { changed[numChanged++] = whichneigh; } marked[whichneigh] |= 4; output[whichneigh] = tempf; m_heapIdent[whichneigh] = m_active.push(whichneigh, tempf); } else if (tempf < output[whichneigh]) { m_active.changekey(m_heapIdent[whichneigh], tempf); output[whichneigh] = tempf; parent[whichneigh] = whichnode; } } } } } } for (i = 0; i < numChanged; ++i) { marked[changed[i]] = 0;//minimize reinitialization of arrays } return ret; } int32_t GeodesicHelper::closest(const int32_t& root, const char* roi, bool smooth) { int32_t i, j, whichnode, whichneigh, numNeigh, numChanged = 0, ret = -1; const int32_t* neighbors; float tempf; output[root] = 0.0f; changed[numChanged++] = root; marked[root] |= 4; parent[root] = -1;//idiom for end of path m_active.clear(); m_heapIdent[root] = m_active.push(root, 0.0f); while (!m_active.isEmpty()) { whichnode = m_active.pop(); if (roi[whichnode] != 0)//we have found the closest node in the roi to the root, we are done { ret = whichnode; break; } marked[whichnode] |= 1;//anything pulled from heap will already be marked as having a valid value (flag 4), so already in changed list neighbors = nodeNeighbors[whichnode].data(); numNeigh = (int32_t)nodeNeighbors[whichnode].size(); for (j = 0; j < numNeigh; ++j) { whichneigh = neighbors[j]; if (!(marked[whichneigh] & 1)) {//skip floating point math if frozen tempf = output[whichnode] + distances[whichnode][j];//isn't precomputation wonderful if (!(marked[whichneigh] & 4)) { parent[whichneigh] = whichnode; if (!marked[whichneigh]) { changed[numChanged++] = whichneigh; } marked[whichneigh] |= 4; output[whichneigh] = tempf; m_heapIdent[whichneigh] = m_active.push(whichneigh, tempf); } else if (tempf < output[whichneigh]) { m_active.changekey(m_heapIdent[whichneigh], tempf); output[whichneigh] = tempf; parent[whichneigh] = whichnode; } } } if (smooth)//repeat with numNeighbors2, nodeNeighbors2, distance2 { neighbors = nodeNeighbors2[whichnode].data(); numNeigh = (int32_t)nodeNeighbors2[whichnode].size(); for (j = 0; j < numNeigh; ++j) { whichneigh = neighbors[j]; if (!(marked[whichneigh] & 1)) {//skip floating point math if frozen tempf = output[whichnode] + distances2[whichnode][j];//isn't precomputation wonderful if (!(marked[whichneigh] & 4)) { parent[whichneigh] = whichnode; if (!marked[whichneigh]) { changed[numChanged++] = whichneigh; } marked[whichneigh] |= 4; output[whichneigh] = tempf; m_heapIdent[whichneigh] = m_active.push(whichneigh, tempf); } else if (tempf < output[whichneigh]) { m_active.changekey(m_heapIdent[whichneigh], tempf); output[whichneigh] = tempf; parent[whichneigh] = whichnode; } } } } } for (i = 0; i < numChanged; ++i) { marked[changed[i]] = 0;//minimize reinitialization of arrays } return ret; } void GeodesicHelper::aStar(const int32_t root, const int32_t endpoint, bool smooth) { int32_t whichnode, whichneigh, numNeigh, numChanged = 0; const int32_t* neighbors; float tempf; output[root] = 0.0f; changed[numChanged++] = root; marked[root] |= 4;//has value in output parent[root] = -1;//idiom for end of path m_active.clear(); float remainEucl = (nodeCoords[root] - nodeCoords[endpoint]).length(); heurVal[root] = remainEucl * m_corrAreaSmallestFactor; m_heapIdent[root] = m_active.push(root, heurVal[root]); while (!m_active.isEmpty()) { whichnode = m_active.pop();//we use a modifiable heap, so we don't need to check for duplicates marked[whichnode] |= 1;//frozen - will already be in changed list, due to being in heap if (whichnode == endpoint) break; neighbors = nodeNeighbors[whichnode].data(); numNeigh = (int32_t)nodeNeighbors[whichnode].size(); for (int32_t j = 0; j < numNeigh; ++j) { whichneigh = neighbors[j]; if (!(marked[whichneigh] & 1)) {//skip floating point math if frozen tempf = output[whichnode] + distances[whichnode][j]; if (!(marked[whichneigh] & 4)) { heurVal[whichneigh] = m_corrAreaSmallestFactor * (nodeCoords[whichneigh] - nodeCoords[endpoint]).length(); output[whichneigh] = tempf; parent[whichneigh] = whichnode; changed[numChanged++] = whichneigh;//having a valid value will be the first marking, so set changed marked[whichneigh] |= 4; m_heapIdent[whichneigh] = m_active.push(whichneigh, tempf + heurVal[whichneigh]); } else if (tempf < output[whichneigh]) { m_active.changekey(m_heapIdent[whichneigh], tempf + heurVal[whichneigh]); output[whichneigh] = tempf; parent[whichneigh] = whichnode; } } } if (smooth)//repeat with numNeighbors2, nodeNeighbors2, distance2 { neighbors = nodeNeighbors2[whichnode].data(); numNeigh = (int32_t)nodeNeighbors2[whichnode].size(); for (int32_t j = 0; j < numNeigh; ++j) { whichneigh = neighbors[j]; if (!(marked[whichneigh] & 1)) {//skip floating point math if frozen tempf = output[whichnode] + distances2[whichnode][j]; if (!(marked[whichneigh] & 4)) { heurVal[whichneigh] = m_corrAreaSmallestFactor * (nodeCoords[whichneigh] - nodeCoords[endpoint]).length(); output[whichneigh] = tempf; parent[whichneigh] = whichnode; changed[numChanged++] = whichneigh;//having a valid value will be the first marking, so set changed marked[whichneigh] |= 4; m_heapIdent[whichneigh] = m_active.push(whichneigh, tempf + heurVal[whichneigh]); } else if (tempf < output[whichneigh]) { m_active.changekey(m_heapIdent[whichneigh], tempf + heurVal[whichneigh]); output[whichneigh] = tempf; parent[whichneigh] = whichnode; } } } } } for (int32_t i = 0; i < numChanged; ++i) { marked[changed[i]] = 0;//minimize reinitialization of marked array } } float GeodesicHelper::linePenalty(const Vector3D& pos, const Vector3D& linep1, const Vector3D& linep2, const bool& segment) { if (segment) { return pos.distToLineSegment(linep1, linep2); } else { return pos.distToLine(linep1, linep2); } } float GeodesicHelper::lineHeuristic(const Vector3D& pos, const Vector3D& linep1, const Vector3D& linep2, const float& remainEucl, const bool& segment) { float tempf;//to make it consistent, assume it minimizes the line penalty by taking a straight line towards the line (segment), encountering the endpoint en route, or on the line (segment) if (segment)//the distance heuristic assumes a different path to make it consistent versus path length, and the sum of consistent heuristics is consistent for the sum of the penalties { tempf = m_corrAreaSmallestFactor * pos.distToLineSegment(linep1, linep2);//all euclidean distances must be modified by the smallest area correction factor } else { tempf = m_corrAreaSmallestFactor * pos.distToLine(linep1, linep2); } if (remainEucl < tempf) { return m_corrAreaSmallestFactor * remainEucl * (2.0f * tempf - remainEucl);//hits the endpoint en route } else { return tempf * tempf;//reaches the line (segment), then finds the endpoint with no additional penalty } } void GeodesicHelper::aStarLine(const int32_t& root, const int32_t& endpoint, const Vector3D& linep1, const Vector3D& linep2, const bool& segment) { int32_t whichnode, whichneigh, numNeigh, numChanged = 0; float penaltyScale = 0.5f / m_avgNodeSpacing;//to prevent change in scale from changing the optimal path - 0.5f is ostensibly for averaging between endpoints, but is largely arbitrary const int32_t* neighbors; float tempf; output[root] = 0.0f; changed[numChanged++] = root; marked[root] |= 4;//has value in output parent[root] = -1;//idiom for end of path m_active.clear(); float remainEucl = (nodeCoords[root] - nodeCoords[endpoint]).length(); heurVal[root] = m_corrAreaSmallestFactor * remainEucl + penaltyScale * lineHeuristic(nodeCoords[root], linep1, linep2, remainEucl, segment); m_heapIdent[root] = m_active.push(root, heurVal[root]);//to get consistent heuristic, assume it can take straight line to endpoint for total distance, and straight line to target line (segment) at the same time while (!m_active.isEmpty()) { whichnode = m_active.pop();//we use a modifiable heap, so we don't need to check for duplicates marked[whichnode] |= 1;//frozen - will already be in changed list, due to being in heap if (whichnode == endpoint) break; neighbors = nodeNeighbors[whichnode].data(); numNeigh = (int32_t)nodeNeighbors[whichnode].size(); for (int32_t j = 0; j < numNeigh; ++j) { whichneigh = neighbors[j]; if (!(marked[whichneigh] & 1)) {//skip floating point math if frozen tempf = output[whichnode] + distances[whichnode][j] + penaltyScale * distances[whichnode][j] * (linePenalty(nodeCoords[whichnode], linep1, linep2, segment) + linePenalty(nodeCoords[whichneigh], linep1, linep2, segment)); if (!(marked[whichneigh] & 4)) { remainEucl = (nodeCoords[whichneigh] - nodeCoords[endpoint]).length(); heurVal[whichneigh] = m_corrAreaSmallestFactor * remainEucl + penaltyScale * lineHeuristic(nodeCoords[whichneigh], linep1, linep2, remainEucl, segment); output[whichneigh] = tempf; parent[whichneigh] = whichnode; changed[numChanged++] = whichneigh;//having a valid value will be the first marking, so set changed marked[whichneigh] |= 4; m_heapIdent[whichneigh] = m_active.push(whichneigh, tempf + heurVal[whichneigh]); } else if (tempf < output[whichneigh]) { m_active.changekey(m_heapIdent[whichneigh], tempf + heurVal[whichneigh]); output[whichneigh] = tempf; parent[whichneigh] = whichnode; } } } } for (int32_t i = 0; i < numChanged; ++i) { marked[changed[i]] = 0;//minimize reinitialization of marked array } } void GeodesicHelper::aStarData(const int32_t& root, const int32_t& endpoint, const float* data, const float& followStrength, const float* roiData, const bool& smooth) {//NOTE: for consistent behavior, data must not contain negatives (or anything non-numeric) int32_t whichnode, whichneigh, numNeigh, numChanged = 0; const int32_t* neighbors; float tempf; output[root] = 0.0f; changed[numChanged++] = root; marked[root] |= 4;//has value in output parent[root] = -1;//idiom for end of path m_active.clear(); heurVal[root] = m_corrAreaSmallestFactor * (nodeCoords[root] - nodeCoords[endpoint]).length();//data could be zero all the way along the optimal path, so euclidean distance is the only consistent heuristic m_heapIdent[root] = m_active.push(root, heurVal[root]); while (!m_active.isEmpty()) { whichnode = m_active.pop();//we use a modifiable heap, so we don't need to check for duplicates marked[whichnode] |= 1;//frozen - will already be in changed list, due to being in heap if (whichnode == endpoint) break; neighbors = nodeNeighbors[whichnode].data(); numNeigh = (int32_t)nodeNeighbors[whichnode].size(); for (int32_t j = 0; j < numNeigh; ++j) { whichneigh = neighbors[j]; if ((roiData == NULL || roiData[whichneigh] > 0.0f) && !(marked[whichneigh] & 1)) {//skip floating point math if frozen or outside roi tempf = output[whichnode] + distances[whichnode][j] * (1.0f + followStrength * (data[whichnode] + data[whichneigh]));//integrate 1 + strength * value to get distance plus path-integrated data if (!(marked[whichneigh] & 4)) { heurVal[whichneigh] = m_corrAreaSmallestFactor * (nodeCoords[whichneigh] - nodeCoords[endpoint]).length(); output[whichneigh] = tempf; parent[whichneigh] = whichnode; changed[numChanged++] = whichneigh;//having a valid value will be the first marking, so set changed marked[whichneigh] |= 4; m_heapIdent[whichneigh] = m_active.push(whichneigh, tempf + heurVal[whichneigh]); } else if (tempf < output[whichneigh]) { m_active.changekey(m_heapIdent[whichneigh], tempf + heurVal[whichneigh]); output[whichneigh] = tempf; parent[whichneigh] = whichnode; } } } if (smooth)//repeat with numNeighbors2, nodeNeighbors2, distance2 { neighbors = nodeNeighbors2[whichnode].data(); numNeigh = (int32_t)nodeNeighbors2[whichnode].size(); const GeodesicHelperBase::CrawlInfo* pathInfo = neighbors2PathInfo[whichnode].data(); for (int32_t j = 0; j < numNeigh; ++j) { whichneigh = neighbors[j]; if ((roiData == NULL || roiData[whichneigh] > 0.0f) && !(marked[whichneigh] & 1)) {//skip floating point math if frozen or outside roi tempf = output[whichnode] + distances2[whichnode][j] + followStrength * (data[whichnode] * pathInfo[j].pieceDists[0] + data[whichneigh] * pathInfo[j].pieceDists[1] + distances2[whichnode][j] * (data[pathInfo[j].edgeNodes[0]] * pathInfo[j].edgeWeight + data[pathInfo[j].edgeNodes[1]] * (1.0f - pathInfo[j].edgeWeight))); if (!(marked[whichneigh] & 4)) { heurVal[whichneigh] = m_corrAreaSmallestFactor * (nodeCoords[whichneigh] - nodeCoords[endpoint]).length(); output[whichneigh] = tempf; parent[whichneigh] = whichnode; changed[numChanged++] = whichneigh;//having a valid value will be the first marking, so set changed marked[whichneigh] |= 4; m_heapIdent[whichneigh] = m_active.push(whichneigh, tempf + heurVal[whichneigh]); } else if (tempf < output[whichneigh]) { m_active.changekey(m_heapIdent[whichneigh], tempf + heurVal[whichneigh]); output[whichneigh] = tempf; parent[whichneigh] = whichnode; } } } } } for (int32_t i = 0; i < numChanged; ++i) { marked[changed[i]] = 0;//minimize reinitialization of marked array } } void GeodesicHelper::getGeoToTheseNodes(const int32_t root, const std::vector& ofInterest, std::vector& distsOut, bool smoothflag) { CaretAssert(root >= 0 && root < numNodes); if (root < 0 || root >= numNodes) { distsOut.clear();//empty array is error condition return; } int32_t i, mysize = ofInterest.size(), node; for (i = 0; i < mysize; ++i) {//needs to do a linear scan of this array later anyway, so lets sanity check it node = ofInterest[i]; if (node < 0 || node >= numNodes) { distsOut.clear();//empty array is error condition return; } } CaretMutexLocker locked(&inUse);//let sanity checks fail without locking dijkstra(root, ofInterest, smoothflag); distsOut.resize(mysize); for (i = 0; i < mysize; ++i) { distsOut[i] = output[ofInterest[i]]; } } void GeodesicHelper::getPathToNode(const int32_t root, const int32_t endpoint, vector& pathNodesOut, vector& pathDistsOut, bool smoothflag) { CaretAssert(root >= 0 && root < numNodes && endpoint >= 0 && endpoint < numNodes); pathNodesOut.clear(); pathDistsOut.clear(); if (root < 0 || root >= numNodes || endpoint < 0 || endpoint >= numNodes) { return; } CaretMutexLocker locked(&inUse);//let sanity checks fail without locking parent[endpoint] = -2;//sentinel value that DOESN'T mean end of path aStar(root, endpoint, smoothflag); if (parent[endpoint] == -2)//check for invalid value { return; } vector tempReverse; int32_t next = endpoint; while (next != root) { tempReverse.push_back(next); next = parent[next]; } tempReverse.push_back(next); int32_t tempSize = (int32_t)tempReverse.size(); for (int32_t i = tempSize - 1; i >= 0; --i) { int32_t tempNode = tempReverse[i]; pathNodesOut.push_back(tempNode); pathDistsOut.push_back(output[tempNode]); } } void GeodesicHelper::getPathBetweenNodeLists(const vector& startList, const vector& endList, const float& maxDist, vector& pathNodesOut, vector& pathDistsOut, bool smoothflag) { pathNodesOut.clear(); pathDistsOut.clear(); for (size_t i = 0; i < startList.size(); ++i) { if (startList[i] < 0 || startList[i] >= numNodes) return; } for (size_t i = 0; i < endList.size(); ++i) { if (endList[i] < 0 || endList[i] >= numNodes) return; } CaretMutexLocker locked(&inUse);//let sanity checks fail without locking int32_t pathEnd = dijkstra(startList, endList, maxDist, smoothflag);//not sure if A* with a PointLocator would be any faster, so don't add a dependency if (pathEnd == -1) return;//no path found vector tempReverse; int32_t next = pathEnd; while (next != -1)//-1 is "parent" of start node { tempReverse.push_back(next); next = parent[next]; } tempReverse.push_back(next); int32_t tempSize = (int32_t)tempReverse.size(); for (int32_t i = tempSize - 1; i >= 0; --i) { int32_t tempNode = tempReverse[i]; pathNodesOut.push_back(tempNode); pathDistsOut.push_back(output[tempNode]); } } void GeodesicHelper::getPathAlongLine(const int32_t root, const int32_t endpoint, const Vector3D& linep1, const Vector3D& linep2, vector& pathNodesOut, vector& pathDistsOut) { CaretAssert(root >= 0 && root < numNodes && endpoint >= 0 && endpoint < numNodes); pathNodesOut.clear(); pathDistsOut.clear(); if (root < 0 || root >= numNodes || endpoint < 0 || endpoint >= numNodes) { return; } CaretMutexLocker locked(&inUse);//let sanity checks fail without locking parent[endpoint] = -2;//sentinel value that DOESN'T mean end of path aStarLine(root, endpoint, linep1, linep2, false); if (parent[endpoint] == -2)//check for invalid value { return; } vector tempReverse; int32_t next = endpoint; while (next != root) { tempReverse.push_back(next); next = parent[next]; } tempReverse.push_back(next); int32_t tempSize = (int32_t)tempReverse.size(); for (int32_t i = tempSize - 1; i >= 0; --i) { int32_t tempNode = tempReverse[i]; pathNodesOut.push_back(tempNode); pathDistsOut.push_back(output[tempNode]); } } void GeodesicHelper::getPathAlongLineSegment(const int32_t root, const int32_t endpoint, const Vector3D& linep1, const Vector3D& linep2, vector& pathNodesOut, vector& pathDistsOut) { CaretAssert(root >= 0 && root < numNodes && endpoint >= 0 && endpoint < numNodes); pathNodesOut.clear(); pathDistsOut.clear(); if (root < 0 || root >= numNodes || endpoint < 0 || endpoint >= numNodes) { return; } vector ofInterest(1, endpoint); CaretMutexLocker locked(&inUse);//let sanity checks fail without locking parent[endpoint] = -2;//sentinel value that DOESN'T mean end of path aStarLine(root, endpoint, linep1, linep2, true); if (parent[endpoint] == -2)//check for invalid value { return; } vector tempReverse; int32_t next = endpoint; while (next != root) { tempReverse.push_back(next); next = parent[next]; } tempReverse.push_back(next); int32_t tempSize = (int32_t)tempReverse.size(); for (int32_t i = tempSize - 1; i >= 0; --i) { int32_t tempNode = tempReverse[i]; pathNodesOut.push_back(tempNode); pathDistsOut.push_back(output[tempNode]); } } void GeodesicHelper::getPathFollowingData(const int32_t root, const int32_t endpoint, const float* data, vector& pathNodesOut, vector& pathDistsOut, const float& followStrength, const float* roiData, const bool& followMaximum, const bool& smoothFlag) { CaretAssert(root >= 0 && root < numNodes && endpoint >= 0 && endpoint < numNodes); pathNodesOut.clear(); pathDistsOut.clear(); if (root < 0 || root >= numNodes || endpoint < 0 || endpoint >= numNodes) { return; } if (roiData != NULL && !(roiData[root] > 0.0f && roiData[endpoint] > 0.0f)) { return; } vector rescaledData(numNodes); float minimum = 0.0f, maximum = 0.0f; bool first = true; for (int i = 0; i < numNodes; ++i)//first normalize contrast in the roi, so we are not sensitive to the absolute values of the data { if (roiData == NULL || roiData[i] > 0.0f) { if (first) { first = false; minimum = data[i]; maximum = data[i]; } else { if (data[i] < minimum) minimum = data[i]; if (data[i] > maximum) maximum = data[i]; } } } if (minimum == maximum)//no contrast, will do simple shortest path { maximum = minimum + 1.0f;//so map it all to minimum value to prevent divide by 0 }//doesn't matter what value it is mapped to if there is no contrast, rescaled data is integrated along path length, and path length is always a factor float range = maximum - minimum; for (int i = 0; i < numNodes; ++i) { if (roiData == NULL || roiData[i] > 0.0f) { if (followMaximum)//also deal with follow minimum vs maximum { rescaledData[i] = (maximum - data[i]) / range; } else { rescaledData[i] = (data[i] - minimum) / range; } } } CaretMutexLocker locked(&inUse);//let sanity checks fail without locking parent[endpoint] = -2;//sentinel value that DOESN'T mean end of path aStarData(root, endpoint, rescaledData.data(), followStrength, roiData, smoothFlag); if (parent[endpoint] == -2)//check for invalid value { return; } vector tempReverse; int32_t next = endpoint; while (next != root) { tempReverse.push_back(next); next = parent[next]; } tempReverse.push_back(next); int32_t tempSize = (int32_t)tempReverse.size(); for (int32_t i = tempSize - 1; i >= 0; --i) { int32_t tempNode = tempReverse[i]; pathNodesOut.push_back(tempNode); pathDistsOut.push_back(output[tempNode]); } } int32_t GeodesicHelper::getClosestNodeInRoi(const int32_t& root, const char* roi, const float& maxdist, float& distOut, bool smoothflag) { CaretAssert(root >= 0 && root < numNodes && maxdist >= 0.0f); if (root < 0 || root >= numNodes || maxdist < 0.0f) { return -1; } CaretMutexLocker locked(&inUse);//let sanity checks fail without locking return closest(root, roi, maxdist, distOut, smoothflag); } int32_t GeodesicHelper::getClosestNodeInRoi(const int32_t& root, const char* roi, vector& pathNodesOut, vector& pathDistsOut, bool smoothflag) { CaretAssert(root >= 0 && root < numNodes); pathNodesOut.clear(); pathDistsOut.clear(); if (root < 0 || root >= numNodes) { return -1; } CaretMutexLocker locked(&inUse);//let sanity checks fail without locking int32_t ret = closest(root, roi, smoothflag); if (ret == -1) return ret; vector tempReverse; int32_t next = ret; while (next != root) { tempReverse.push_back(next); next = parent[next]; } tempReverse.push_back(next); int32_t tempSize = (int32_t)tempReverse.size(); for (int32_t i = tempSize - 1; i >= 0; --i) { int32_t tempNode = tempReverse[i]; pathNodesOut.push_back(tempNode); pathDistsOut.push_back(output[tempNode]); } return ret; } workbench-1.1.1/src/Files/GeodesicHelper.h000066400000000000000000000233711255417355300204000ustar00rootroot00000000000000 #ifndef __GEODESIC_HELPER_H__ #define __GEODESIC_HELPER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include //for inlining #include "CaretMutex.h" #include "CaretPointer.h" #include "CaretHeap.h" #include "Vector3D.h" namespace caret { class SurfaceFile; //NOTE: this class does NOT stay associated with the coord passed into it, it takes a snapshot of the surface in the constructor //This is because it is designed to be fast on repeated calls on a single surface class GeodesicHelperBase {//This does the neighbor computation, create a GeodesicHelper to contain the temporary arrays and actually do stuff public: struct CrawlInfo { int32_t edgeNodes[2]; float edgeWeight, pieceDists[2]; }; private: GeodesicHelperBase();//can't construct without arguments GeodesicHelperBase& operator=(const GeodesicHelperBase& right);//can't assign GeodesicHelperBase(const GeodesicHelperBase& right);//can't use copy constructor std::vector > distances, distances2; std::vector > nodeNeighbors, nodeNeighbors2; std::vector > neighbors2PathInfo; std::vector nodeCoords;//for line-following and A* int32_t numNodes; float m_avgNodeSpacing;//to use for balancing line following penalty float m_corrAreaSmallestFactor;//so that heuristics can be consistent despite corrected areas public: explicit GeodesicHelperBase(const SurfaceFile* surfaceIn, const float* correctedAreas = NULL);//NOTE: this is only an APPROXIMATE correction, use the real surface whenever possible friend class GeodesicHelper;//let it grab the private variables it needs }; class GeodesicHelper { CaretPointer m_myBase;//mostly just for automatic memory management CaretMutex inUse;//could add a function and a locker pointer to be able to lock to thread once, then call repeatedly without locking, if mutex overhead is actually a factor CaretMinHeap m_active;//save and reuse the allocated space const std::vector* distances, *distances2; const std::vector* nodeNeighbors, *nodeNeighbors2; const std::vector* neighbors2PathInfo; const Vector3D* nodeCoords; float* output; int32_t* parent; std::vector outputStore; std::vector heurVal; std::vector marked, changed, parentStore; std::vector m_heapIdent; int32_t numNodes; float m_avgNodeSpacing; float m_corrAreaSmallestFactor; GeodesicHelper();//Don't allow construction without arguments GeodesicHelper& operator=(const GeodesicHelper& right);//can't assign GeodesicHelper(const GeodesicHelper&);//can't use copy constructor void dijkstra(const int32_t root, const float maxdist, std::vector& nodes, std::vector& dists, bool smooth);//geodesic distance restricted void dijkstra(const int32_t root, bool smooth);//full surface void dijkstra(const int32_t root, const std::vector& interested, bool smooth);//partial surface int32_t dijkstra(const std::vector& startList, const std::vector& endList, const float& maxDist, bool smooth);//one path that connects lists void alltoall(float** out, int32_t** parents, bool smooth);//must be fully allocated int32_t closest(const int32_t& root, const char* roi, const float& maxdist, float& distOut, bool smooth);//just closest node int32_t closest(const int32_t& root, const char* roi, bool smooth);//just closest node void aStar(const int32_t root, const int32_t endpoint, bool smooth);//faster method for path float linePenalty(const Vector3D& pos, const Vector3D& linep1, const Vector3D& linep2, const bool& segment); float lineHeuristic(const Vector3D& pos, const Vector3D& linep1, const Vector3D& linep2, const float& remainEucl, const bool& segment); void aStarLine(const int32_t& root, const int32_t& endpoint, const Vector3D& linep1, const Vector3D& linep2, const bool& segment);//to single endpoint, following line void aStarData(const int32_t& root, const int32_t& endpoint, const float* data, const float& followStrength, const float* roiData, const bool& smooth);//to single endpoint, following data public: explicit GeodesicHelper(const CaretPointer& baseIn); /// Get distances from root node, up to a geodesic distance cutoff (stops computing when no more nodes are within that distance) void getNodesToGeoDist(const int32_t node, const float maxdist, std::vector& neighborsOut, std::vector& distsOut, const bool smoothflag = true); /// Get distances from root node, up to a geodesic distance cutoff, and also return their parents (root node has -1 as parent) void getNodesToGeoDist(const int32_t node, const float maxdist, std::vector& neighborsOut, std::vector& distsOut, std::vector& parentsOut, const bool smoothflag = true); /// Get distances from root node to entire surface - allocate the array first void getGeoFromNode(const int32_t node, float* valuesOut, const bool smoothflag = true);//MUST be already allocated to number of nodes /// Get distances from root node to entire surface, vector method void getGeoFromNode(const int32_t node, std::vector& valuesOut, const bool smoothflag = true); /// Get distances from root node to entire surface and parents - allocate both arrays first (root node has -1 as parent) void getGeoFromNode(const int32_t node, float* valuesOut, int32_t* parentsOut, const bool smoothflag = true); /// Get distances from root node to entire surface, and their parents, vector method (root node has -1 as parent) void getGeoFromNode(const int32_t node, std::vector& valuesOut, std::vector& parentsOut, const bool smoothflag = true); /// Get distances from all nodes to all nodes, passes back NULL if cannot allocate, if successful you must eventually delete the memory float** getGeoAllToAll(const bool smooth = true);//i really don't think this needs an overloaded function that outputs parents /// Get distances to a restricted set of nodes - output vector is in the SAME ORDER and same size as the input vector ofInterest void getGeoToTheseNodes(const int32_t root, const std::vector& ofInterest, std::vector& distsOut, bool smoothflag = true); ///get the distances and nodes along the path to a node - NOTE: default is not smooth distances, so that all nodes in the path are connected in the surface void getPathToNode(const int32_t root, const int32_t endpoint, std::vector& pathNodesOut, std::vector& pathDistsOut, bool smoothflag = false); ///shortest path between two sets of nodes (for instance, clusters), with distance limit void getPathBetweenNodeLists(const std::vector& startList, const std::vector& endList, const float& maxDist, std::vector& pathNodesOut, std::vector& pathDistsOut, bool smoothflag); ///get the distances and nodes along the path to a node - NOTE: does not do smooth distances, so that all nodes in the path are connected in the surface void getPathAlongLine(const int32_t root, const int32_t endpoint, const Vector3D& linep1, const Vector3D& linep2, std::vector& pathNodesOut, std::vector& pathDistsOut); ///get the distances and nodes along the path to a node - NOTE: does not do smooth distances, so that all nodes in the path are connected in the surface void getPathAlongLineSegment(const int32_t root, const int32_t endpoint, const Vector3D& linep1, const Vector3D& linep2, std::vector& pathNodesOut, std::vector& pathDistsOut); ///path drawing by peaks or troughs of supplied data, controlled by followMaximum void getPathFollowingData(const int32_t root, const int32_t endpoint, const float* data, std::vector& pathNodesOut, std::vector& pathDistsOut, const float& followStrength = 5.0f, const float* roiData = NULL, const bool& followMaximum = true, const bool& smoothFlag = false); ///get just the closest node in the region and max distance given, returns -1 if no such node found - roi value of 0 means not in region, anything else is in region int32_t getClosestNodeInRoi(const int32_t& root, const char* roi, const float& maxdist, float& distOut, bool smoothflag = true); int32_t getClosestNodeInRoi(const int32_t& root, const char* roi, std::vector& pathNodesOut, std::vector& pathDistsOut, bool smoothflag); }; } //namespace caret #endif workbench-1.1.1/src/Files/GiftiTypeFile.cxx000066400000000000000000000631661255417355300206030ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretLogger.h" #include "DataFileContentInformation.h" #include "DataFileException.h" #include "FastStatistics.h" #include "GiftiDataArray.h" #include "GiftiFile.h" #include "GiftiMetaData.h" #include "GiftiTypeFile.h" #include "GiftiMetaDataXmlElements.h" #include "Histogram.h" #include "PaletteColorMapping.h" #include "PaletteColorMappingSaxReader.h" #include "SurfaceFile.h" using namespace caret; /** * Constructor. */ GiftiTypeFile::GiftiTypeFile(const DataFileTypeEnum::Enum dataFileType) : CaretMappableDataFile(dataFileType) { this->initializeMembersGiftiTypeFile(); } /** * Destructor. */ GiftiTypeFile::~GiftiTypeFile() { if (this->giftiFile != NULL) { delete this->giftiFile; this->giftiFile = NULL; } } /** * Copy Constructor. * * @param gtf * File that is copied. */ GiftiTypeFile::GiftiTypeFile(const GiftiTypeFile& gtf) : CaretMappableDataFile(gtf) { this->giftiFile = new GiftiFile(*gtf.giftiFile);//NOTE: while CONSTRUCTING, this has virtual type GiftiTypeFile*, NOT MetricFile*, or whatever }//so, validateDataArraysAfterReading will ABORT due to pure virtual /** * Assignment operator. * * @param gtf * File whose contents are copied to this file. */ GiftiTypeFile& GiftiTypeFile::operator=(const GiftiTypeFile& gtf) { if (this != >f) { CaretMappableDataFile::operator=(gtf); this->copyHelperGiftiTypeFile(gtf); } return *this; } /** * Clear the contents of this file. */ void GiftiTypeFile::clear() { DataFile::clear(); this->giftiFile->clear(); } /** * Clear modified status. */ void GiftiTypeFile::clearModified() { CaretDataFile::clearModified(); this->giftiFile->clearModified(); } /** * @return True if any of the maps in this file contain a * color mapping that possesses a modified status. */ bool GiftiTypeFile::isModifiedExcludingPaletteColorMapping() const { if (CaretMappableDataFile::isModifiedExcludingPaletteColorMapping()) { return true; } if (this->giftiFile->isModified()) { return true; } return false; } /** * Is this file empty? * * @return true if file is empty, else false. */ bool GiftiTypeFile::isEmpty() const { return this->giftiFile->isEmpty(); } /** * Read the file. * * @param filename * Name of file to read. * * @throws DataFileException * If there is an error reading the file. */ void GiftiTypeFile::readFile(const AString& filename) { clear(); checkFileReadability(filename); this->setFileName(filename); this->giftiFile->readFile(filename); this->validateDataArraysAfterReading(); this->clearModified(); } /** * Write the file. * * @param filename * Name of file to read. * * @throws DataFileException * If there is an error writing the file. */ void GiftiTypeFile::writeFile(const AString& filename) { checkFileWritability(filename); this->giftiFile->writeFile(filename); this->clearModified(); } /** * Helps with file copying. * * @param gtf * File that is copied. */ void GiftiTypeFile::copyHelperGiftiTypeFile(const GiftiTypeFile& gtf) { if (this->giftiFile != NULL) { delete this->giftiFile; } this->giftiFile = new GiftiFile(*gtf.giftiFile); this->validateDataArraysAfterReading(); } /** * Initialize members of this class. */ void GiftiTypeFile::initializeMembersGiftiTypeFile() { this->giftiFile = new GiftiFile(); } /** * Get information about this file's contents. * @return * Information about the file's contents. */ AString GiftiTypeFile::toString() const { return this->giftiFile->toString(); } StructureEnum::Enum GiftiTypeFile::getStructure() const { AString structurePrimaryName; /* * Surface contains anatomical structure in pointset array. */ const SurfaceFile* surfaceFile = dynamic_cast(this); if (surfaceFile != NULL) { const GiftiDataArray* gda = this->giftiFile->getDataArrayWithIntent(NiftiIntentEnum::NIFTI_INTENT_POINTSET); const GiftiMetaData* metadata = gda->getMetaData(); structurePrimaryName = metadata->get(GiftiMetaDataXmlElements::METADATA_NAME_ANATOMICAL_STRUCTURE_PRIMARY); } else { const GiftiMetaData* metadata = this->giftiFile->getMetaData(); structurePrimaryName = metadata->get(GiftiMetaDataXmlElements::METADATA_NAME_ANATOMICAL_STRUCTURE_PRIMARY); } bool isValid = false; StructureEnum::Enum structure = StructureEnum::fromGuiName(structurePrimaryName, &isValid); return structure; } /** * Set the structure. * @param structure * New value for file's structure. */ void GiftiTypeFile::setStructure(const StructureEnum::Enum structure) { const AString structureName = StructureEnum::toGuiName(structure); /* * Surface contains anatomical structure in pointset array. */ SurfaceFile* surfaceFile = dynamic_cast(this); if (surfaceFile != NULL) { GiftiDataArray* gda = this->giftiFile->getDataArrayWithIntent(NiftiIntentEnum::NIFTI_INTENT_POINTSET); GiftiMetaData* metadata = gda->getMetaData(); metadata->set(GiftiMetaDataXmlElements::METADATA_NAME_ANATOMICAL_STRUCTURE_PRIMARY, structureName); } else { GiftiMetaData* metadata = this->giftiFile->getMetaData(); metadata->set(GiftiMetaDataXmlElements::METADATA_NAME_ANATOMICAL_STRUCTURE_PRIMARY, structureName); } } /** * Add map(s) to this GIFTI file. * @param numberOfNodes * Number of nodes. If file is not empty, this value must * match the number of nodes that are in the file. * @param numberOfMaps * Number of maps to add. */ void GiftiTypeFile::addMaps(const int32_t /*numberOfNodes*/, const int32_t /*numberOfMaps*/) { throw DataFileException(getFileName(), "This file does not support adding additional maps"); } /** * @return Get access to the file's metadata. */ GiftiMetaData* GiftiTypeFile::getFileMetaData() { return this->giftiFile->getMetaData(); } /** * @return Get access to unmodifiable file's metadata. */ const GiftiMetaData* GiftiTypeFile::getFileMetaData() const { return this->giftiFile->getMetaData(); } /** * Verify that all of the data arrays have the same number of rows. * @throws DataFileException * If there are data arrays that have a different number of rows. */ void GiftiTypeFile::verifyDataArraysHaveSameNumberOfRows(const int32_t minimumSecondDimension, const int32_t maximumSecondDimension) const { const int32_t numberOfArrays = this->giftiFile->getNumberOfDataArrays(); if (numberOfArrays > 1) { /* * Verify all arrays contain the same number of rows. */ int64_t numberOfRows = this->giftiFile->getDataArray(0)->getNumberOfRows(); for (int32_t i = 1; i < numberOfArrays; i++) { const int32_t arrayNumberOfRows = this->giftiFile->getDataArray(i)->getNumberOfRows(); if (numberOfRows != arrayNumberOfRows) { AString message = "All data arrays (columns) in the file must have the same number of rows."; message += " The first array (column) contains " + AString::number(numberOfRows) + " rows."; message += " Array " + AString::number(i + 1) + " contains " + AString::number(arrayNumberOfRows) + " rows."; DataFileException e(getFileName(), message); CaretLogThrowing(e); throw e; } } /* * Verify that second dimensions is within valid range. */ for (int32_t i = 0; i < numberOfArrays; i++) { const GiftiDataArray* gda = this->giftiFile->getDataArray(i); const int32_t numberOfDimensions = gda->getNumberOfDimensions(); if (numberOfDimensions > 2) { DataFileException e(getFileName(), "Data array " + AString::number(i + 1) + " contains " + AString::number(numberOfDimensions) + " dimensions. Two is the maximum allowed."); CaretLogThrowing(e); throw e; } int32_t secondDimension = 0; if (numberOfDimensions > 1) { secondDimension = gda->getDimension(1); if (secondDimension == 1) { secondDimension = 0; } } if ((secondDimension < minimumSecondDimension) || (secondDimension > maximumSecondDimension)) { DataFileException e(getFileName(), "Data array " + AString::number(i + 1) + " second dimension is " + AString::number(numberOfDimensions) + ". Minimum allowed is " + AString::number(minimumSecondDimension) + ". Maximum allowed is " + AString::number(maximumSecondDimension)); CaretLogThrowing(e); throw e; } } } } /** * Get the name of a file column. * @param columnIndex * Index of column. * @return * Name of column. */ AString GiftiTypeFile::getColumnName(const int columnIndex) const { return this->giftiFile->getDataArrayName(columnIndex); } /** * Find the first column with the given column name. * @param columnName * Name of column. * @return * Index of column with name or negative if no match. */ int32_t GiftiTypeFile::getColumnIndexFromColumnName(const AString& columnName) const { return this->giftiFile->getDataArrayWithNameIndex(columnName); } /** * Set the name of a column. * @param columnIndex * Index of column. * @param columnName * New name for column. */ void GiftiTypeFile::setColumnName(const int32_t columnIndex, const AString& columnName) { this->giftiFile->setDataArrayName(columnIndex, columnName); } /** * @return The palette color mapping for a data column. */ PaletteColorMapping* GiftiTypeFile::getPaletteColorMapping(const int32_t columnIndex) { GiftiDataArray* gda = this->giftiFile->getDataArray(columnIndex); return gda->getPaletteColorMapping(); } /** * @return The palette color mapping for a data column. */ const PaletteColorMapping* GiftiTypeFile::getPaletteColorMapping(const int32_t columnIndex) const { const GiftiDataArray* gda = this->giftiFile->getDataArray(columnIndex); return gda->getPaletteColorMapping(); } /** * @return Is the data mappable to a surface? */ bool GiftiTypeFile::isSurfaceMappable() const { return true; } /** * @return Is the data mappable to a volume? */ bool GiftiTypeFile::isVolumeMappable() const { return false; } /** * @return The number of maps in the file. * Note: Caret5 used the term 'columns'. */ int32_t GiftiTypeFile::getNumberOfMaps() const { return this->giftiFile->getNumberOfDataArrays(); } /** * Get the name of the map at the given index. * * @param mapIndex * Index of the map. * @return * Name of the map. */ AString GiftiTypeFile::getMapName(const int32_t mapIndex) const { return this->giftiFile->getDataArrayName(mapIndex); } /** * Set the name of the map at the given index. * * @param mapIndex * Index of the map. * @param mapName * New name for the map. */ void GiftiTypeFile::setMapName(const int32_t mapIndex, const AString& mapName) { this->giftiFile->setDataArrayName(mapIndex, mapName); } /** * Get the metadata for the map at the given index * * @param mapIndex * Index of the map. * @return * Metadata for the map (const value). */ const GiftiMetaData* GiftiTypeFile::getMapMetaData(const int32_t mapIndex) const { return this->giftiFile->getDataArray(mapIndex)->getMetaData(); } /** * Get the metadata for the map at the given index * * @param mapIndex * Index of the map. * @return * Metadata for the map. */ GiftiMetaData* GiftiTypeFile::getMapMetaData(const int32_t mapIndex) { return this->giftiFile->getDataArray(mapIndex)->getMetaData(); } const FastStatistics* GiftiTypeFile::getMapFastStatistics(const int32_t mapIndex) { const GiftiDataArray* gda = this->giftiFile->getDataArray(mapIndex); return gda->getFastStatistics(); } const Histogram* GiftiTypeFile::getMapHistogram(const int32_t mapIndex) { const GiftiDataArray* gda = this->giftiFile->getDataArray(mapIndex); return gda->getHistogram(); } const Histogram* GiftiTypeFile::getMapHistogram(const int32_t mapIndex, const float mostPositiveValueInclusive, const float leastPositiveValueInclusive, const float leastNegativeValueInclusive, const float mostNegativeValueInclusive, const bool includeZeroValues) { const GiftiDataArray* gda = this->giftiFile->getDataArray(mapIndex); return gda->getHistogram(mostPositiveValueInclusive, leastPositiveValueInclusive, leastNegativeValueInclusive, mostNegativeValueInclusive, includeZeroValues); } /** * @return The estimated size of data after it is uncompressed * and loaded into RAM. A negative value indicates that the * file size cannot be computed. */ int64_t GiftiTypeFile::getDataSizeUncompressedInBytes() const { const int32_t numDataArrays = getNumberOfMaps(); int64_t dataSizeInBytes = 0; for (int32_t iMap = 0; iMap < numDataArrays; iMap++) { const GiftiDataArray* gda = this->giftiFile->getDataArray(iMap); dataSizeInBytes += gda->getDataSizeInBytes(); } return dataSizeInBytes; } /** * Get all data for a file that contains floats. If the file is very * large this method may take a large amount of time! * * @param dataOut * Output with all data for a float file. Empty if no data in file * or data is not float. */ void GiftiTypeFile::getFileDataFloat(std::vector& dataOut) const { int64_t dataSize = 0; /* * Get the size of the data */ const int64_t numberOfDataArrays = this->giftiFile->getNumberOfDataArrays(); for (int64_t i = 0; i < numberOfDataArrays; i++) { const GiftiDataArray* gda = this->giftiFile->getDataArray(i); if (gda->getDataType() == NiftiDataTypeEnum::NIFTI_TYPE_FLOAT32) { dataSize += gda->getTotalNumberOfElements(); } else { dataOut.clear(); return; } } if (dataSize <= 0) { dataOut.clear(); return; } dataOut.resize(dataSize); int64_t dataOffset = 0; /* * Copy the data. */ for (int64_t i = 0; i < numberOfDataArrays; i++) { const GiftiDataArray* gda = this->giftiFile->getDataArray(i); const int64_t arraySize = gda->getTotalNumberOfElements(); const float* arrayPointer = gda->getDataPointerFloat(); for (int64_t j = 0; j < arraySize; j++) { CaretAssertVectorIndex(dataOut, dataOffset); dataOut[dataOffset] = arrayPointer[j]; ++dataOffset; } } CaretAssert(dataOffset == static_cast(dataOut.size())); } /** * Get statistics describing the distribution of data * mapped with a color palette for all data within the file. * * @return * Fast statistics for data (will be NULL for data * not mapped using a palette). */ const FastStatistics* GiftiTypeFile::getFileFastStatistics() { if (m_fileFastStatistics == NULL) { std::vector fileData; getFileDataFloat(fileData); if ( ! fileData.empty()) { m_fileFastStatistics.grabNew(new FastStatistics()); m_fileFastStatistics->update(&fileData[0], fileData.size()); } } return m_fileFastStatistics; } /** * Get histogram describing the distribution of data * mapped with a color palette for all data within * the file. * * @return * Histogram for data (will be NULL for data * not mapped using a palette). */ const Histogram* GiftiTypeFile::getFileHistogram() { if (m_fileHistogram == NULL) { std::vector fileData; getFileDataFloat(fileData); if ( ! fileData.empty()) { m_fileHistogram.grabNew(new Histogram()); m_fileHistogram->update(&fileData[0], fileData.size()); } } return m_fileHistogram; } /** * Get histogram describing the distribution of data * mapped with a color palette for all data in the file * within the given range of values. * * @param mostPositiveValueInclusive * Values more positive than this value are excluded. * @param leastPositiveValueInclusive * Values less positive than this value are excluded. * @param leastNegativeValueInclusive * Values less negative than this value are excluded. * @param mostNegativeValueInclusive * Values more negative than this value are excluded. * @param includeZeroValues * If true zero values (very near zero) are included. * @return * Descriptive statistics for data (will be NULL for data * not mapped using a palette). */ const Histogram* GiftiTypeFile::getFileHistogram(const float mostPositiveValueInclusive, const float leastPositiveValueInclusive, const float leastNegativeValueInclusive, const float mostNegativeValueInclusive, const bool includeZeroValues) { bool updateHistogramFlag = false; if (m_fileHistorgramLimitedValues != NULL) { if ((mostPositiveValueInclusive != m_fileHistogramLimitedValuesMostPositiveValueInclusive) || (leastPositiveValueInclusive != m_fileHistogramLimitedValuesLeastPositiveValueInclusive) || (leastNegativeValueInclusive != m_fileHistogramLimitedValuesLeastNegativeValueInclusive) || (mostNegativeValueInclusive != m_fileHistogramLimitedValuesMostNegativeValueInclusive) || (includeZeroValues != m_fileHistogramLimitedValuesIncludeZeroValues)) { updateHistogramFlag = true; } } else { updateHistogramFlag = true; } if (updateHistogramFlag) { std::vector fileData; getFileDataFloat(fileData); if ( ! fileData.empty()) { if (m_fileHistorgramLimitedValues == NULL) { m_fileHistorgramLimitedValues.grabNew(new Histogram()); } m_fileHistorgramLimitedValues->update(&fileData[0], fileData.size(), mostPositiveValueInclusive, leastPositiveValueInclusive, leastNegativeValueInclusive, mostNegativeValueInclusive, includeZeroValues); m_fileHistogramLimitedValuesMostPositiveValueInclusive = mostPositiveValueInclusive; m_fileHistogramLimitedValuesLeastPositiveValueInclusive = leastPositiveValueInclusive; m_fileHistogramLimitedValuesLeastNegativeValueInclusive = leastNegativeValueInclusive; m_fileHistogramLimitedValuesMostNegativeValueInclusive = mostNegativeValueInclusive; m_fileHistogramLimitedValuesIncludeZeroValues = includeZeroValues; } } return m_fileHistorgramLimitedValues; } /** * @return Is the data in the file mapped to colors using * a palette. */ bool GiftiTypeFile::isMappedWithPalette() const { if (this->getDataFileType() == DataFileTypeEnum::METRIC) { return true; } return false; } /** * Get the palette normalization modes that are supported by the file. * * @param modesSupportedOut * Palette normalization modes supported by a file. Will be * empty for files that are not mapped with a palette. If there * is more than one suppported mode, the first mode in the * vector is assumed to be the default mode. */ void GiftiTypeFile::getPaletteNormalizationModesSupported(std::vector& modesSupportedOut) { modesSupportedOut.clear(); if (getDataFileType() == DataFileTypeEnum::METRIC) { modesSupportedOut.push_back(PaletteNormalizationModeEnum::NORMALIZATION_SELECTED_MAP_DATA); modesSupportedOut.push_back(PaletteNormalizationModeEnum::NORMALIZATION_ALL_MAP_DATA); } } /** * Get the palette color mapping for the map at the given index. * * @param mapIndex * Index of the map. * @return * Palette color mapping for the map (will be NULL for data * not mapped using a palette). */ PaletteColorMapping* GiftiTypeFile::getMapPaletteColorMapping(const int32_t mapIndex) { GiftiDataArray* gda = this->giftiFile->getDataArray(mapIndex); return gda->getPaletteColorMapping(); } /** * Get the palette color mapping for the map at the given index. * * @param mapIndex * Index of the map. * @return * Palette color mapping for the map (constant) (will be NULL for data * not mapped using a palette). */ const PaletteColorMapping* GiftiTypeFile::getMapPaletteColorMapping(const int32_t mapIndex) const { const GiftiDataArray* gda = this->giftiFile->getDataArray(mapIndex); return gda->getPaletteColorMapping(); } /** * @return Is the data in the file mapped to colors using * a label table. */ bool GiftiTypeFile::isMappedWithLabelTable() const { if (this->getDataFileType() == DataFileTypeEnum::LABEL) { return true; } return false; } /** * Get the label table for the map at the given index. * * @param mapIndex * Index of the map. * @return * Label table for the map (will be NULL for data * not mapped using a label table). */ GiftiLabelTable* GiftiTypeFile::getMapLabelTable(const int32_t /*mapIndex*/) { /* * Use file's label table since GIFTI uses one * label table for all data arrays. */ return this->giftiFile->getLabelTable(); } /** * Get the label table for the map at the given index. * * @param mapIndex * Index of the map. * @return * Label table for the map (constant) (will be NULL for data * not mapped using a label table). */ const GiftiLabelTable* GiftiTypeFile::getMapLabelTable(const int32_t /*mapIndex*/) const { /* * Use file's label table since GIFTI uses one * label table for all data arrays. */ return this->giftiFile->getLabelTable(); } /** * Get the unique ID (UUID) for the map at the given index. * * @param mapIndex * Index of the map. * @return * String containing UUID for the map. */ AString GiftiTypeFile::getMapUniqueID(const int32_t mapIndex) const { const GiftiMetaData* md = this->giftiFile->getDataArray(mapIndex)->getMetaData(); return md->getUniqueID(); } /** * Find the index of the map that uses the given unique ID (UUID). * * @param uniqueID * Unique ID (UUID) of the desired map. * @return * Index of the map using the given UUID. */ int32_t GiftiTypeFile::getMapIndexFromUniqueID(const AString& uniqueID) const { const int32_t numberOfArrays = this->giftiFile->getNumberOfDataArrays(); for (int32_t i = 0; i < numberOfArrays; i++) { if (this->getMapUniqueID(i) == uniqueID) { return i; } } return -1; } /** * Update coloring for a map. * * @param mapIndex * Index of map. * @param paletteFile * Palette file containing palettes. */ void GiftiTypeFile::updateScalarColoringForMap(const int32_t /*mapIndex*/, const PaletteFile* /*paletteFile*/) { /* no volumes in gifti */ } /** * Add information about the file to the data file information. * * @param dataFileInformation * Consolidates information about a data file. */ void GiftiTypeFile::addToDataFileContentInformation(DataFileContentInformation& dataFileInformation) { CaretMappableDataFile::addToDataFileContentInformation(dataFileInformation); dataFileInformation.addNameAndValue("Number of Vertices", getNumberOfNodes()); } workbench-1.1.1/src/Files/GiftiTypeFile.h000066400000000000000000000171021255417355300202150ustar00rootroot00000000000000#ifndef __GIFTI_TYPE_FILE_H__ #define __GIFTI_TYPE_FILE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretMappableDataFile.h" #include "StructureEnum.h" namespace caret { class GiftiFile; class PaletteColorMapping; /// Encapsulates a GiftiFile for use by specific types of GIFTI data files. class GiftiTypeFile : public CaretMappableDataFile { protected: GiftiTypeFile(const DataFileTypeEnum::Enum dataFileType); virtual ~GiftiTypeFile(); GiftiTypeFile(const GiftiTypeFile& s); GiftiTypeFile& operator=(const GiftiTypeFile&); virtual void addToDataFileContentInformation(DataFileContentInformation& dataFileInformation); /** * Validate the contents of the file after it * has been read such as correct number of * data arrays and proper data types/dimensions. * * @throws DataFileException * If the file is not valid. */ virtual void validateDataArraysAfterReading() = 0; void verifyDataArraysHaveSameNumberOfRows(const int32_t minimumSecondDimension, const int32_t maximumSecondDimension) const; public: virtual void clear(); virtual void clearModified(); virtual bool isModifiedExcludingPaletteColorMapping() const; virtual bool isEmpty() const; virtual void readFile(const AString& filename); virtual void writeFile(const AString& filename); virtual AString toString() const; virtual GiftiMetaData* getFileMetaData(); virtual const GiftiMetaData* getFileMetaData() const; virtual StructureEnum::Enum getStructure() const; virtual void setStructure(const StructureEnum::Enum structure); virtual void addMaps(const int32_t numberOfNodes, const int32_t numberOfMaps); /** @return Number of nodes in the file. */ virtual int32_t getNumberOfNodes() const = 0; /** @return Number of columns (data arrays) in the file. */ virtual int32_t getNumberOfColumns() const = 0; //virtual void setNumberOfNodesAndColumns(int32_t nodes, int32_t columns) = 0; virtual AString getColumnName(const int32_t columnIndex) const; int32_t getColumnIndexFromColumnName(const AString& columnName) const; virtual void setColumnName(const int32_t columnIndex, const AString& columnName); PaletteColorMapping* getPaletteColorMapping(const int32_t columnIndex); const PaletteColorMapping* getPaletteColorMapping(const int32_t columnIndex) const; virtual bool isSurfaceMappable() const; virtual bool isVolumeMappable() const; virtual int32_t getNumberOfMaps() const; virtual AString getMapName(const int32_t mapIndex) const; virtual void setMapName(const int32_t mapIndex, const AString& mapName); virtual const GiftiMetaData* getMapMetaData(const int32_t mapIndex) const; virtual GiftiMetaData* getMapMetaData(const int32_t mapIndex); void getFileDataFloat(std::vector& dataOut) const; virtual const FastStatistics* getMapFastStatistics(const int32_t mapIndex); virtual const Histogram* getMapHistogram(const int32_t mapIndex); virtual const Histogram* getMapHistogram(const int32_t mapIndex, const float mostPositiveValueInclusive, const float leastPositiveValueInclusive, const float leastNegativeValueInclusive, const float mostNegativeValueInclusive, const bool includeZeroValues); virtual int64_t getDataSizeUncompressedInBytes() const; virtual const FastStatistics* getFileFastStatistics(); virtual const Histogram* getFileHistogram(); virtual const Histogram* getFileHistogram(const float mostPositiveValueInclusive, const float leastPositiveValueInclusive, const float leastNegativeValueInclusive, const float mostNegativeValueInclusive, const bool includeZeroValues); virtual bool isMappedWithPalette() const; virtual void getPaletteNormalizationModesSupported(std::vector& modesSupportedOut); virtual PaletteColorMapping* getMapPaletteColorMapping(const int32_t mapIndex); virtual const PaletteColorMapping* getMapPaletteColorMapping(const int32_t mapIndex) const; virtual bool isMappedWithLabelTable() const; virtual GiftiLabelTable* getMapLabelTable(const int32_t mapIndex); virtual const GiftiLabelTable* getMapLabelTable(const int32_t mapIndex) const; virtual AString getMapUniqueID(const int32_t mapIndex) const; virtual int32_t getMapIndexFromUniqueID(const AString& uniqueID) const; virtual void updateScalarColoringForMap(const int32_t mapIndex, const PaletteFile* paletteFile); private: void copyHelperGiftiTypeFile(const GiftiTypeFile& gtf); void initializeMembersGiftiTypeFile(); /** Fast statistics used when statistics computed on all data in file */ CaretPointer m_fileFastStatistics; /** Histogram used when statistics computed on all data in file */ CaretPointer m_fileHistogram; /** Histogram with limited values used when statistics computed on all data in file */ CaretPointer m_fileHistorgramLimitedValues; float m_fileHistogramLimitedValuesMostPositiveValueInclusive; float m_fileHistogramLimitedValuesLeastPositiveValueInclusive; float m_fileHistogramLimitedValuesLeastNegativeValueInclusive; float m_fileHistogramLimitedValuesMostNegativeValueInclusive; bool m_fileHistogramLimitedValuesIncludeZeroValues; protected: GiftiFile* giftiFile; }; } // namespace #endif // __GIFTI_TYPE_FILE_H__ workbench-1.1.1/src/Files/GroupAndNameCheckStateEnum.cxx000066400000000000000000000235001255417355300231670ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __GROUP_AND_NAME_CHECK_STATE_ENUM_DECLARE__ #include "GroupAndNameCheckStateEnum.h" #undef __GROUP_AND_NAME_CHECK_STATE_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::GroupAndNameCheckStateEnum * \brief * * */ /** * Constructor. * * @param enumValue * An enumerated value. * @param integerCode * Integer code for this enumerated value. * * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ GroupAndNameCheckStateEnum::GroupAndNameCheckStateEnum(const Enum enumValue, const int32_t integerCode, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCode; this->name = name; this->guiName = guiName; } /** * Destructor. */ GroupAndNameCheckStateEnum::~GroupAndNameCheckStateEnum() { } /** * Initialize the enumerated metadata. */ void GroupAndNameCheckStateEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(GroupAndNameCheckStateEnum(UNCHECKED, Qt::Unchecked, "UNCHECKED", "Unchecked")); enumData.push_back(GroupAndNameCheckStateEnum(PARTIALLY_CHECKED, Qt::PartiallyChecked, "PARTIALLY_CHECKED", "PartiallyChecked")); enumData.push_back(GroupAndNameCheckStateEnum(CHECKED, Qt::Checked, "CHECKED", "Checked")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const GroupAndNameCheckStateEnum* GroupAndNameCheckStateEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const GroupAndNameCheckStateEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString GroupAndNameCheckStateEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const GroupAndNameCheckStateEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ GroupAndNameCheckStateEnum::Enum GroupAndNameCheckStateEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = UNCHECKED; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const GroupAndNameCheckStateEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type GroupAndNameCheckStateEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString GroupAndNameCheckStateEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const GroupAndNameCheckStateEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ GroupAndNameCheckStateEnum::Enum GroupAndNameCheckStateEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = UNCHECKED; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const GroupAndNameCheckStateEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type GroupAndNameCheckStateEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t GroupAndNameCheckStateEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const GroupAndNameCheckStateEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ GroupAndNameCheckStateEnum::Enum GroupAndNameCheckStateEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = UNCHECKED; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const GroupAndNameCheckStateEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type GroupAndNameCheckStateEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void GroupAndNameCheckStateEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void GroupAndNameCheckStateEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(GroupAndNameCheckStateEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void GroupAndNameCheckStateEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(GroupAndNameCheckStateEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } workbench-1.1.1/src/Files/GroupAndNameCheckStateEnum.h000066400000000000000000000063041255417355300226170ustar00rootroot00000000000000#ifndef __GROUP_AND_NAME_CHECK_STATE_ENUM_H__ #define __GROUP_AND_NAME_CHECK_STATE_ENUM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class GroupAndNameCheckStateEnum { public: /** * Enumerated values. */ enum Enum { /** Not selected */ UNCHECKED, /** Selected and at least one, but not all children selected */ PARTIALLY_CHECKED, /** Selected and all children (if any) are selected*/ CHECKED }; ~GroupAndNameCheckStateEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); private: GroupAndNameCheckStateEnum(const Enum enumValue, const int32_t integerCode, const AString& name, const AString& guiName); static const GroupAndNameCheckStateEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; #ifdef __GROUP_AND_NAME_CHECK_STATE_ENUM_DECLARE__ std::vector GroupAndNameCheckStateEnum::enumData; bool GroupAndNameCheckStateEnum::initializedFlag = false; #endif // __GROUP_AND_NAME_CHECK_STATE_ENUM_DECLARE__ } // namespace #endif //__GROUP_AND_NAME_CHECK_STATE_ENUM_H__ workbench-1.1.1/src/Files/GroupAndNameHierarchyGroup.cxx000066400000000000000000000032511255417355300232600ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CLASS_AND_NAME_HIERARCHY_GROUP_DECLARE__ #include "GroupAndNameHierarchyGroup.h" #undef __CLASS_AND_NAME_HIERARCHY_GROUP_DECLARE__ #include "CaretAssert.h" #include "GroupAndNameHierarchyName.h" using namespace caret; /** * \class caret::GroupAndNameHierarchyGroup * \brief Maintains selection of a class and name in each 'DisplayGroupEnum'. */ /** * Constructor. * @param name * The name. * @param idNumber * ID number assigned to the name. */ GroupAndNameHierarchyGroup::GroupAndNameHierarchyGroup(const AString& name, const int32_t idNumber) : GroupAndNameHierarchyItem(GroupAndNameHierarchyItem::ITEM_TYPE_GROUP, name, idNumber) { } /** * Destructor. */ GroupAndNameHierarchyGroup::~GroupAndNameHierarchyGroup() { } workbench-1.1.1/src/Files/GroupAndNameHierarchyGroup.h000066400000000000000000000033711255417355300227100ustar00rootroot00000000000000#ifndef __CLASS_AND_NAME_HIERARCHY_GROUP__H_ #define __CLASS_AND_NAME_HIERARCHY_GROUP__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "GroupAndNameHierarchyItem.h" namespace caret { class GroupAndNameHierarchyName; class GroupAndNameHierarchyGroup : public GroupAndNameHierarchyItem { public: GroupAndNameHierarchyGroup(const AString& name, const int32_t idNumber); virtual ~GroupAndNameHierarchyGroup(); // ADD_NEW_METHODS_HERE private: GroupAndNameHierarchyGroup(const GroupAndNameHierarchyGroup&); GroupAndNameHierarchyGroup& operator=(const GroupAndNameHierarchyGroup&); // ADD_NEW_MEMBERS_HERE }; #ifdef __CLASS_AND_NAME_HIERARCHY_GROUP_DECLARE__ // #endif // __CLASS_AND_NAME_HIERARCHY_GROUP_DECLARE__ } // namespace #endif //__CLASS_AND_NAME_HIERARCHY_GROUP__H_ workbench-1.1.1/src/Files/GroupAndNameHierarchyItem.cxx000066400000000000000000000672621255417355300230760ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __GROUP_AND_NAME_HIERARCHY_ITEM_DECLARE__ #include "GroupAndNameHierarchyItem.h" #undef __GROUP_AND_NAME_HIERARCHY_ITEM_DECLARE__ #include "AStringNaturalComparison.h" #include "CaretAssert.h" #include "GroupAndNameHierarchyGroup.h" #include "GroupAndNameHierarchyName.h" #include "SceneAttributes.h" #include "SceneClass.h" #include "SceneClassArray.h" #include "SceneClassAssistant.h" #include using namespace caret; /** * \class caret::GroupAndNameHierarchyItem * \brief Base class for items in a hierarchy tree. */ /** * Constructor. * * @param itemType * Type of this item. * @param name * Name of the item. * @param idNumber * Id number for the item. */ GroupAndNameHierarchyItem::GroupAndNameHierarchyItem(const ItemType itemType, const AString& name, const int32_t idNumber) : CaretObject(), m_itemType(itemType), m_name(name), m_idNumber(idNumber), m_parent(0) { m_sceneAssistant = new SceneClassAssistant(); m_iconRGBA[0] = 0.0; m_iconRGBA[1] = 0.0; m_iconRGBA[2] = 0.0; m_iconRGBA[3] = 0.0; clearPrivate(); m_sceneAssistant->addTabIndexedBooleanArray("m_selectedInTab", m_selectedInTab); m_sceneAssistant->addTabIndexedBooleanArray("m_expandedStatusInTab", m_expandedStatusInTab); m_sceneAssistant->addArray("m_selectedInDisplayGroup", m_selectedInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, m_selectedInDisplayGroup[0]); m_sceneAssistant->addArray("m_expandedStatusInDisplayGroup", m_expandedStatusInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, m_expandedStatusInDisplayGroup[0]); } /** * Destructor. */ GroupAndNameHierarchyItem::~GroupAndNameHierarchyItem() { delete m_sceneAssistant; clearPrivate(); } /** * Clear the contents of this class selector. */ void GroupAndNameHierarchyItem::clear() { clearPrivate(); } /** * Clear the contents of this class selector. */ void GroupAndNameHierarchyItem::clearPrivate() { for (std::vector::iterator iter = m_children.begin(); iter != m_children.end(); iter++) { GroupAndNameHierarchyItem* item = *iter; item->m_parent = NULL; delete item; } m_children.clear(); m_childrenNameIdMap.clear(); for (int32_t i = 0; i < DisplayGroupEnum::NUMBER_OF_GROUPS; i++) { m_selectedInDisplayGroup[i] = true; } for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_selectedInTab[i] = true; } bool defaultExpandStatus = false; switch (m_itemType) { case ITEM_TYPE_NAME: break; case ITEM_TYPE_GROUP: break; case ITEM_TYPE_MODEL: defaultExpandStatus = true; break; } for (int32_t i = 0; i < DisplayGroupEnum::NUMBER_OF_GROUPS; i++) { m_expandedStatusInDisplayGroup[i] = defaultExpandStatus; } for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_expandedStatusInTab[i] = defaultExpandStatus; } m_counter = 0; } /** * @return The type of the item. */ GroupAndNameHierarchyItem::ItemType GroupAndNameHierarchyItem::getItemType() const { return m_itemType; } /** * @return The name of the item. */ AString GroupAndNameHierarchyItem::getName() const { return m_name; } /** * Set the name of the item. This should only be used * to set the name of an item without a parent as names * are used when creating a hierarchy. * * @param name * Name of item. */ void GroupAndNameHierarchyItem::setName(const AString& name) { m_name = name; } /** * @return The parent of this item. */ GroupAndNameHierarchyItem* GroupAndNameHierarchyItem::getParent() { return m_parent; } /** * @return The parent of this item. */ const GroupAndNameHierarchyItem* GroupAndNameHierarchyItem::getParent() const { return m_parent; } /** * @return The ancestors of this item. */ std::vector GroupAndNameHierarchyItem::getAncestors() const { std::vector ancestors; if (m_parent != NULL) { ancestors.push_back(m_parent); std::vector parentsAncestors = m_parent->getAncestors(); ancestors.insert(ancestors.end(), parentsAncestors.begin(), parentsAncestors.end()); } return ancestors; } /** * @return The descendants of this item. */ std::vector GroupAndNameHierarchyItem::getDescendants() const { std::vector descendants; if (m_parent != NULL) { for (std::vector::const_iterator iter = m_children.begin(); iter != m_children.end(); iter++) { GroupAndNameHierarchyItem* child = *iter; descendants.push_back(child); std::vector childDescendants = child->getDescendants(); descendants.insert(descendants.end(), childDescendants.begin(), childDescendants.end()); } } return descendants; } /** * @return The children of this item. */ std::vector GroupAndNameHierarchyItem::getChildren() const { return m_children; } static bool lessName(const GroupAndNameHierarchyItem* itemOne, const GroupAndNameHierarchyItem* itemTwo) { const int32_t result = AStringNaturalComparison::compare(itemOne->getName(), itemTwo->getName()); if (result < 0) { return true; } return false; // return (itemOne->getName() < itemTwo->getName()); } /** * Sort the descendants by name */ void GroupAndNameHierarchyItem::sortDescendantsByName() { std::sort(m_children.begin(), m_children.end(), lessName); for (std::vector::iterator iter = m_children.begin(); iter != m_children.end(); iter++) { GroupAndNameHierarchyItem* child = *iter; child->sortDescendantsByName(); } } /** * Get the child with the given name and ID number. * * @param name * Name of child. * @return Child with the given name and ID number or NULL if no * child with the name and ID number. */ GroupAndNameHierarchyItem* GroupAndNameHierarchyItem::getChildWithNameAndIdNumber(const AString& name, const int32_t idNumber) { ChildMapKey childMapKey(idNumber, name); std::map::iterator iter; iter = m_childrenNameIdMap.find(childMapKey); if (iter != m_childrenNameIdMap.end()) { GroupAndNameHierarchyItem* item = iter->second; return item; } return NULL; } /** * Add the given child to this item. * * @param child * Child to add. */ void GroupAndNameHierarchyItem::addChild(GroupAndNameHierarchyItem* child) { CaretAssertMessage((getChildWithNameAndIdNumber(child->getName(), child->getIdNumber()) != NULL), ("Child with name=" + child->getName() + ", idNumber=" + AString::number(child->getIdNumber()) + " already is in item named=" + getName())); addChildPrivate(child); } /** * Add the given child to this item. * * @param child * Child to add. */ void GroupAndNameHierarchyItem::addChildPrivate(GroupAndNameHierarchyItem* child) { child->m_parent = this; m_children.push_back(child); ChildMapKey childMapKey(child->getIdNumber(), child->getName()); m_childrenNameIdMap.insert(std::make_pair(childMapKey, child)); } /** * Add a child with the given name and id number. * * @param itemType * Type of the item. * @param name * Name of item. * @param idNumber * ID Number for item. * @return If a child with the given name and id number exists, * it is returned. Otherwise, a new child with the given * name and id number is created and returned. */ GroupAndNameHierarchyItem* GroupAndNameHierarchyItem::addChild(const ItemType itemType, const AString& name, const int32_t idNumber) { GroupAndNameHierarchyItem* child = getChildWithNameAndIdNumber(name, idNumber); if (child != NULL) { child->incrementCounter(); return child; } switch (itemType) { case ITEM_TYPE_GROUP: child = new GroupAndNameHierarchyGroup(name, idNumber); break; case ITEM_TYPE_NAME: child = new GroupAndNameHierarchyName(name, idNumber); break; case ITEM_TYPE_MODEL: CaretAssertMessage(0, "Model should never be a child"); break; } child->incrementCounter(); addChildPrivate(child); return child; } /** * Remove the given child from this item. The child IS NOT deleted. * * @param child * Child to remove. */ void GroupAndNameHierarchyItem::removeChild(GroupAndNameHierarchyItem* child) { std::vector::iterator iter = std::find(m_children.begin(), m_children.end(), child); if (iter != m_children.end()) { child->m_parent = NULL; m_children.erase(iter); } for (std::map::iterator mapIter = m_childrenNameIdMap.begin(); mapIter != m_childrenNameIdMap.end(); mapIter++) { GroupAndNameHierarchyItem* item = mapIter->second; if (item == child) { m_childrenNameIdMap.erase(mapIter); break; } } } /** * Is this item selected? * * @param displayGroup * The display group in which the item is controlled/viewed. * @param tabIndex * Index of browser tab in which item is controlled/viewed. * @return * True if item is selected, else false. */ bool GroupAndNameHierarchyItem::isSelected(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const { const int32_t displayIndex = (int32_t)displayGroup; CaretAssertArrayIndex(m_selectedInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, displayIndex); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_selectedInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_selectedInTab[tabIndex]; } return m_selectedInDisplayGroup[displayIndex]; } /** * Get the "check state" of an item. * * @param displayGroup * The display group in which the item is controlled/viewed. * @param tabIndex * Index of browser tab in which item is controlled/viewed. * @return * CHECKED if this item and ALL of its children are selected. * PARTIALLY_CHECKED if this item is selected and any of its * children, but not all of its children, are selected. * UNCHECKED if this item is not selected. */ GroupAndNameCheckStateEnum::Enum GroupAndNameHierarchyItem::getCheckState(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const { if (isSelected(displayGroup, tabIndex)) { int64_t numChildren = 0; int64_t numChildrenChecked = 0; for (std::vector::const_iterator iter = m_children.begin(); iter != m_children.end(); iter++) { numChildren++; GroupAndNameHierarchyItem* child = *iter; const GroupAndNameCheckStateEnum::Enum childStatus = child->getCheckState(displayGroup, tabIndex); switch (childStatus) { case GroupAndNameCheckStateEnum::CHECKED: numChildrenChecked++; break; case GroupAndNameCheckStateEnum::PARTIALLY_CHECKED: return GroupAndNameCheckStateEnum::PARTIALLY_CHECKED; break; case GroupAndNameCheckStateEnum::UNCHECKED: break; } } if (numChildrenChecked == numChildren) { return GroupAndNameCheckStateEnum::CHECKED; } else if (numChildrenChecked > 0) { return GroupAndNameCheckStateEnum::PARTIALLY_CHECKED; } } return GroupAndNameCheckStateEnum::UNCHECKED; } /** * Set the selected status of this item only. It does not alter * the status of ancestors and children. * * @param displayGroup * The display group in which the item is controlled/viewed. * @param tabIndex * Index of browser tab in which item is controlled/viewed. * @param status * True if item is selected, else false. */ void GroupAndNameHierarchyItem::setSelected(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const bool status) { const int32_t displayIndex = (int32_t)displayGroup; CaretAssertArrayIndex(m_selectedInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, displayIndex); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_selectedInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_selectedInTab[tabIndex] = status; } else { m_selectedInDisplayGroup[displayIndex] = status; } } /** * Set the selected status for all of this item's descendants. * * @param displayGroup * The display group in which the item is controlled/viewed. * @param tabIndex * Index of browser tab in which item is controlled/viewed. * @param status * True if item is selected, else false. */ void GroupAndNameHierarchyItem::setDescendantsSelected(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const bool status) { for (std::vector::const_iterator iter = m_children.begin(); iter != m_children.end(); iter++) { GroupAndNameHierarchyItem* child = *iter; child->setSelected(displayGroup, tabIndex, status); child->setDescendantsSelected(displayGroup, tabIndex, status); } } /** * Set the selected status of this item's ancestor's (parent, * its parent, etc). * * @param displayGroup * The display group in which the item is controlled/viewed. * @param tabIndex * Index of browser tab in which item is controlled/viewed. * @param status * True if item is selected, else false. */ void GroupAndNameHierarchyItem::setAncestorsSelected(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const bool status) { if (m_parent != NULL) { m_parent->setSelected(displayGroup, tabIndex, status); m_parent->setAncestorsSelected(displayGroup, tabIndex, status); } } /** * Set the selected status of this item, its ancestors, and all * of its children. * * @param displayGroup * The display group in which the item is controlled/viewed. * @param tabIndex * Index of browser tab in which item is controlled/viewed. * @param status * True if item is selected, else false. */ void GroupAndNameHierarchyItem::setSelfAncestorsAndDescendantsSelected(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const bool status) { setSelected(displayGroup, tabIndex, status); setAncestorsSelected(displayGroup, tabIndex, status); setDescendantsSelected(displayGroup, tabIndex, status); } /** * Get the RGBA color for an Icon that is displayed * in the selection control for this item. * * @return * Pointer to the Red, Green, Blue, and Alpha * color components for this item's icon. * If no icon is to be displayed, the alpha component is zero. */ const float* GroupAndNameHierarchyItem::getIconColorRGBA() const { return m_iconRGBA; } /** * Set the RGBA color components for an icon displayed in the * selection control for this item. * * @param rgba * The Red, Green, Blue, and Alpha color components. * If no icon is to be displayed, the alpha component is zero. */ void GroupAndNameHierarchyItem::setIconColorRGBA(const float rgba[4]) { m_iconRGBA[0] = rgba[0]; m_iconRGBA[1] = rgba[1]; m_iconRGBA[2] = rgba[2]; m_iconRGBA[3] = rgba[3]; } /** * Is this item expanded to display its children in the * selection controls? * * @param displayGroup * The display group in which the item is controlled/viewed. * @param tabIndex * Index of browser tab in which item is controlled/viewed. * @return * True if children should be visible, else false. */ bool GroupAndNameHierarchyItem::isExpandedToDisplayChildren(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const { const int32_t displayIndex = (int32_t)displayGroup; CaretAssertArrayIndex(m_expandedStatusInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, displayIndex); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_expandedStatusInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_expandedStatusInTab[tabIndex]; } return m_expandedStatusInDisplayGroup[displayIndex]; } /** * Set this item expanded to display its children in the * selection controls. * * @param displayGroup * The display group in which the item is controlled/viewed. * @param tabIndex * Index of browser tab in which item is controlled/viewed. * @param expanded * True if children should be visible, else false. */ void GroupAndNameHierarchyItem::setExpandedToDisplayChildren(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const bool expanded) { const int32_t displayIndex = (int32_t)displayGroup; CaretAssertArrayIndex(m_expandedStatusInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, displayIndex); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(m_expandedStatusInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_expandedStatusInTab[tabIndex] = expanded; } else { m_expandedStatusInDisplayGroup[displayIndex] = expanded; } } /** * Copy the selections from one tab to another tab. * Also copies selections in all descendants. * * @param sourceTabIndex * Index of source tab (copy "from") * @param targetTabIndex * Index of target tab (copy "to") */ void GroupAndNameHierarchyItem::copySelections(const int32_t sourceTabIndex, const int32_t targetTabIndex) { m_selectedInTab[targetTabIndex] = m_selectedInTab[sourceTabIndex]; m_expandedStatusInTab[targetTabIndex] = m_expandedStatusInTab[sourceTabIndex]; for (std::vector::const_iterator iter = m_children.begin(); iter != m_children.end(); iter++) { GroupAndNameHierarchyItem* child = *iter; child->copySelections(sourceTabIndex, targetTabIndex); } } /** * @return The Id Number. */ int32_t GroupAndNameHierarchyItem::getIdNumber() const { return m_idNumber; } /** * Clear the counter. Also clears counters in descendants. */ void GroupAndNameHierarchyItem::clearCounters() { m_counter = 0; for (std::vector::const_iterator iter = m_children.begin(); iter != m_children.end(); iter++) { GroupAndNameHierarchyItem* child = *iter; child->clearCounters(); } } /** * Increment the counter. */ void GroupAndNameHierarchyItem::incrementCounter() { m_counter++; } /** * @return The value of the counter. */ int32_t GroupAndNameHierarchyItem::getCounter() const { return m_counter; } /** * Remove all descendants with counters equal to zero. */ void GroupAndNameHierarchyItem::removeDescendantsWithCountersEqualToZeros() { /* * Process all descendants. */ for (std::vector::const_iterator iter = m_children.begin(); iter != m_children.end(); iter++) { GroupAndNameHierarchyItem* child = *iter; child->removeDescendantsWithCountersEqualToZeros(); } /* * Find children with zero counters indicating item not used. */ std::vector childrenWithCountGreaterThanZero; std::vector childrenWithCountEqualToZero; for (std::vector::const_iterator iter = m_children.begin(); iter != m_children.end(); iter++) { GroupAndNameHierarchyItem* child = *iter; if (child->getCounter() > 0) { childrenWithCountGreaterThanZero.push_back(child); } else { childrenWithCountEqualToZero.push_back(child); } } /* * If no children with zero counter, return. */ if (childrenWithCountEqualToZero.empty()) { return; } /* * Remove children with zero counters */ for (std::vector::iterator iter = childrenWithCountEqualToZero.begin(); iter != childrenWithCountEqualToZero.end(); iter++) { GroupAndNameHierarchyItem* item = *iter; item->m_parent = NULL; delete item; } /* * Clear children */ m_children.clear(); m_childrenNameIdMap.clear(); /* * Read children so maps properly created. */ for (std::vector::iterator iter = childrenWithCountGreaterThanZero.begin(); iter != childrenWithCountGreaterThanZero.end(); iter++) { addChildPrivate(*iter); } } /** * Get a description of this object's content. * @return String describing this object's content. */ AString GroupAndNameHierarchyItem::toString() const { AString info = (CaretObject::toString() + "\n name=" + m_name + ", type" + AString::number(m_itemType) + ", idNumber=" + AString::number(m_idNumber) + ", counter=" + AString::number(m_counter) + "\n"); AString childInfo; for (std::vector::const_iterator iter = m_children.begin(); iter != m_children.end(); iter++) { GroupAndNameHierarchyItem* child = *iter; childInfo += child->toString(); } if (childInfo.isEmpty() == false) { childInfo = childInfo.replace("\n", "\n "); } info += childInfo; return info; } /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @param instanceName * Name of the class' instance. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ SceneClass* GroupAndNameHierarchyItem::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "GroupAndNameHierarchyItem", 1); sceneClass->addString("m_name", m_name); m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); for (std::vector::iterator iter = m_children.begin(); iter != m_children.end(); iter++) { GroupAndNameHierarchyItem* child = *iter; sceneClass->addClass(child->saveToScene(sceneAttributes, child->getName())); } return sceneClass; } /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * SceneClass containing the state that was previously * saved and should be restored. */ void GroupAndNameHierarchyItem::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); for (std::vector::iterator iter = m_children.begin(); iter != m_children.end(); iter++) { GroupAndNameHierarchyItem* child = *iter; child->restoreFromScene(sceneAttributes, sceneClass->getClass(child->getName())); } } workbench-1.1.1/src/Files/GroupAndNameHierarchyItem.h000066400000000000000000000213431255417355300225110ustar00rootroot00000000000000#ifndef __GROUP_AND_NAME_HIERARCHY_ITEM_H__ #define __GROUP_AND_NAME_HIERARCHY_ITEM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "BrainConstants.h" #include "CaretObject.h" #include "DisplayGroupEnum.h" #include "GroupAndNameCheckStateEnum.h" #include "SceneableInterface.h" namespace caret { class SceneClassAssistant; class GroupAndNameHierarchyItem : public CaretObject, SceneableInterface { public: /** * Type of item. */ enum ItemType { /** Model (eg: file) */ ITEM_TYPE_MODEL, /** Group (eg: class or map) */ ITEM_TYPE_GROUP, /** Name (eg: border, focus, or label) */ ITEM_TYPE_NAME }; protected: GroupAndNameHierarchyItem(const ItemType itemType, const AString& name, const int32_t idNumber); public: virtual ~GroupAndNameHierarchyItem(); virtual void clear(); ItemType getItemType() const; AString getName() const; GroupAndNameHierarchyItem* getParent(); const GroupAndNameHierarchyItem* getParent() const; std::vector getAncestors() const; std::vector getChildren() const; void sortDescendantsByName(); GroupAndNameHierarchyItem* getChildWithNameAndIdNumber(const AString& name, const int32_t idNumber); std::vector getDescendants() const; GroupAndNameHierarchyItem* addChild(const ItemType itemType, const AString& name, const int32_t idNumber); void addChild(GroupAndNameHierarchyItem* child); void removeChild(GroupAndNameHierarchyItem* child); bool isSelected(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const; GroupAndNameCheckStateEnum::Enum getCheckState(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const; void setSelected(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const bool status); void setDescendantsSelected(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const bool status); void setAncestorsSelected(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const bool status); void setSelfAncestorsAndDescendantsSelected(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const bool status); const float* getIconColorRGBA() const; void setIconColorRGBA(const float rgba[4]); bool isExpandedToDisplayChildren(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const; void setExpandedToDisplayChildren(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const bool expanded); virtual void copySelections(const int32_t sourceTabIndex, const int32_t targetTabIndex); int32_t getIdNumber() const; void clearCounters(); void incrementCounter(); int32_t getCounter() const; void removeDescendantsWithCountersEqualToZeros(); // ADD_NEW_METHODS_HERE virtual AString toString() const; virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); protected: void setName(const AString& name); private: GroupAndNameHierarchyItem(const GroupAndNameHierarchyItem&); GroupAndNameHierarchyItem& operator=(const GroupAndNameHierarchyItem&); void addChildPrivate(GroupAndNameHierarchyItem* child); void clearPrivate(); class ChildMapKey { public: ChildMapKey(const int32_t idNumber, const AString& name) : m_idNumber(idNumber), m_name(name) { } const int32_t m_idNumber; const AString m_name; bool operator==(const ChildMapKey& childMapKey) const { if (m_idNumber == childMapKey.m_idNumber) { if (m_name == childMapKey.m_name) { return true; } } return false; } bool operator<(const ChildMapKey& childMapKey) const { if (m_idNumber < childMapKey.m_idNumber) { return true; } else if (m_idNumber == childMapKey.m_idNumber) { if (m_name < childMapKey.m_name) { return true; } // else if (m_name == childMapKey.m_name) { // return 0; // } } // return 1; return false; } }; // bool operator<(const ChildMapKey& a, // const ChildMapKey& b) { // if (a.m_idNumber < b.m_idNumber) { // return true; // } // else if (a.m_idNumber == b.m_idNumber) { // if (a.m_name < b.m_name) { // return true; // } // } // return false; // } /** Type of item */ const ItemType m_itemType; /** Name of this item */ AString m_name; /** ID Number */ const int32_t m_idNumber; /** Parent of this item */ GroupAndNameHierarchyItem* m_parent; /** Children of this item */ std::vector m_children; /** For fast access to children by name and id number of child */ std::map m_childrenNameIdMap; /** Selection for each display group */ bool m_selectedInDisplayGroup[DisplayGroupEnum::NUMBER_OF_GROUPS]; /** Selection for each tab */ bool m_selectedInTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; /** Color for icon, valid when (iconRGBA[3] > 0.0) */ float m_iconRGBA[4]; /** Expanded (collapsed) status in display group */ bool m_expandedStatusInDisplayGroup[DisplayGroupEnum::NUMBER_OF_GROUPS]; /** Expanded (collapsed) status in tab */ bool m_expandedStatusInTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; /** Counter for tracking usage of item */ int32_t m_counter; /** Assists with scenes */ SceneClassAssistant* m_sceneAssistant; // ADD_NEW_MEMBERS_HERE }; #ifdef __GROUP_AND_NAME_HIERARCHY_ITEM_DECLARE__ #endif // __GROUP_AND_NAME_HIERARCHY_ITEM_DECLARE__ } // namespace #endif //__GROUP_AND_NAME_HIERARCHY_ITEM_H__ workbench-1.1.1/src/Files/GroupAndNameHierarchyModel.cxx000066400000000000000000001071111255417355300232240ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CLASS_AND_NAME_HIERARCHY_MODEL_DECLARE__ #include "GroupAndNameHierarchyModel.h" #undef __CLASS_AND_NAME_HIERARCHY_MODEL_DECLARE__ #include "Border.h" #include "BorderFile.h" #include "CaretAssert.h" #include "CaretLogger.h" #include "CiftiMappableDataFile.h" #include "GroupAndNameHierarchyGroup.h" #include "GroupAndNameHierarchyName.h" #include "FociFile.h" #include "Focus.h" #include "GiftiLabel.h" #include "GiftiLabelTable.h" #include "LabelFile.h" #include "VolumeFile.h" using namespace caret; /** * \class caret::GroupAndNameHierarchySelection * \brief Maintains a 'group' and 'name' hierarchy for selection. * * Each group maps to one or more names. Identical names * may be children of more than one group. However, each * group holds its child names independently from the children * of all other groups. * * Note: Two containers are used to hold the data. One, * a vector, maps group keys to groups and a second, a map * group names to groups. * use 'maps'. A map is typically constructed using a * balanced tree so retrieval can be fast. However, adding * or removing items may be slow due to tree rebalancing. * As a result, unused groups and children names are only * removed when the entire instance is cleared (via clear()) * or by calling removeUnusedNamesAndGroupes(). * * Each group or name supports a 'count'. If the for a group * or name is zero, that indicates that the item is unused. * * Attributes are available for every tab and also a * few 'display groups'. A number of methods in this group accept * both display group and tab index parameters. When the display * group is set to 'Tab', the tab index is used meaning that the * attribute requeted/sent is for use with a specifc tab. For an * other display group value, the attribute is for a display group * and the tab index is ignored. */ /** * Constructor. */ GroupAndNameHierarchyModel::GroupAndNameHierarchyModel() : GroupAndNameHierarchyItem(GroupAndNameHierarchyItem::ITEM_TYPE_MODEL, "", -1) { this->clearModelPrivate(); } /** * Destructor. */ GroupAndNameHierarchyModel::~GroupAndNameHierarchyModel() { this->clearModelPrivate(); } /** * Clear the group/name hierarchy. */ void GroupAndNameHierarchyModel::clear() { GroupAndNameHierarchyItem::clear(); clearModelPrivate(); } /** * Clear the group/name hierarchy. */ void GroupAndNameHierarchyModel::clearModelPrivate() { setUserInterfaceUpdateNeeded(); } /** * Set the selected status for self and all children. * @param status * The selection status. */ void GroupAndNameHierarchyModel::setAllSelected(const bool status) { for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { setAllSelected(DisplayGroupEnum::DISPLAY_GROUP_TAB, i, status); } for (int32_t i = 0; i < DisplayGroupEnum::NUMBER_OF_GROUPS; i++) { const DisplayGroupEnum::Enum displayGroup = DisplayGroupEnum::fromIntegerCode(i, NULL); if (displayGroup != DisplayGroupEnum::DISPLAY_GROUP_TAB) { setAllSelected(displayGroup, -1, status); } } } /** * Set the selection status of this hierarchy model for the display group/tab. * @param displayGroup * Display group selected. * @param tabIndex * Index of tab used when displayGroup is DisplayGroupEnum::DISPLAY_GROUP_TAB. * @param selectionStatus * New selection status. */ void GroupAndNameHierarchyModel::setAllSelected(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const bool selectionStatus) { setSelfAncestorsAndDescendantsSelected(displayGroup, tabIndex, selectionStatus); } /** * Update this group hierarchy with the border names * and classes. * * @param borderFile * The border file from which classes and names are from. * @parm forceUpdate * If true, force an update. */ void GroupAndNameHierarchyModel::update(BorderFile* borderFile, const bool forceUpdate) { bool needToGenerateKeys = forceUpdate; setName(borderFile->getFileNameNoPath()); const int32_t numBorders = borderFile->getNumberOfBorders(); if (needToGenerateKeys == false) { for (int32_t i = 0; i < numBorders; i++) { const Border* border = borderFile->getBorder(i); if (border->getGroupNameSelectionItem() == NULL) { needToGenerateKeys = true; } } } /* * ID for groups and names is not used */ const int32_t ID_NOT_USED = 0; if (needToGenerateKeys) { /* * Clear the counters */ clearCounters(); /* * Names for missing group names or border names. */ const AString missingGroupName = "NoGroup"; const AString missingBorderName = "NoName"; /* * For icons */ const GiftiLabelTable* classLabelTable = borderFile->getClassColorTable(); const GiftiLabelTable* nameLabelTable = borderFile->getNameColorTable(); const float rgbaBlack[4] = { 0.0, 0.0, 0.0, 1.0 }; /* * Update with all borders. */ for (int32_t i = 0; i < numBorders; i++) { Border* border = borderFile->getBorder(i); /* * Get the group. If it is empty, use the default name. */ AString theGroupName = border->getClassName(); if (theGroupName.isEmpty()) { theGroupName = missingGroupName; } /* * Get the name. */ AString name = border->getName(); if (name.isEmpty()) { name = missingBorderName; } /* * Class */ GroupAndNameHierarchyItem* groupItem = addChild(GroupAndNameHierarchyItem::ITEM_TYPE_GROUP, theGroupName, ID_NOT_USED); CaretAssert(groupItem); const GiftiLabel* groupLabel = classLabelTable->getLabelBestMatching(theGroupName); if (groupLabel != NULL) { float tempcolor[4]; groupLabel->getColor(tempcolor); groupItem->setIconColorRGBA(tempcolor); } else { groupItem->setIconColorRGBA(rgbaBlack); } /* * Name */ GroupAndNameHierarchyItem* nameItem = groupItem->addChild(GroupAndNameHierarchyItem::ITEM_TYPE_NAME, name, ID_NOT_USED); const GiftiLabel* nameLabel = nameLabelTable->getLabelBestMatching(name); if (nameLabel != NULL) { float tempcolor[4]; nameLabel->getColor(tempcolor); nameItem->setIconColorRGBA(tempcolor); } else { nameItem->setIconColorRGBA(rgbaBlack); } /* * Place the name selector into the border. */ border->setGroupNameSelectionItem(nameItem); } removeDescendantsWithCountersEqualToZeros(); sortDescendantsByName(); setUserInterfaceUpdateNeeded(); CaretLogFine("BORDER HIERARCHY:" + toString()); } } /** * Update this group hierarchy with the label names * and maps (as group). Groupes and names are done * differently for LabelFiles. Use the map index as * the group key and the label index as the name key. * * @param labelFile * The label file from which groups (map) and names are from. * @parm forceUpdate * If true, force an update. */ void GroupAndNameHierarchyModel::update(LabelFile* labelFile, const bool forceUpdate) { bool needToGenerateKeys = forceUpdate; setName(labelFile->getFileNameNoPath()); /* * Names for missing group names or foci names. */ const AString missingGroupName = "NoGroup"; const AString missingName = "NoName"; /* * The label table */ GiftiLabelTable* labelTable = labelFile->getLabelTable(); std::map labelKeysAndNames; if (needToGenerateKeys == false) { /* * Check to see if any group (map) names have changed. */ const std::vector groups = getChildren(); const int numGroups = static_cast(groups.size()); if (numGroups != labelFile->getNumberOfMaps()) { needToGenerateKeys = true; } else { for (int32_t i = 0; i < numGroups; i++) { AString mapName = labelFile->getMapName(i); if (mapName.isEmpty()) { mapName = missingGroupName; } if (groups[i]->getName() != mapName) { needToGenerateKeys = true; break; } } if (needToGenerateKeys == false) { labelTable->getKeysAndNames(labelKeysAndNames); if (m_previousLabelFileKeysAndNames.size() != labelKeysAndNames.size()) { needToGenerateKeys = true; } else { std::map::const_iterator prevIter = m_previousLabelFileKeysAndNames.begin(); for (std::map::const_iterator labelIter = labelKeysAndNames.begin(); labelIter != labelKeysAndNames.end(); labelIter++) { if (prevIter->first != labelIter->first) { needToGenerateKeys = true; break; } else if (prevIter->second != labelIter->second) { needToGenerateKeys = true; break; } prevIter++; } // if (std::equal(labelKeysAndNames.begin(), // labelKeysAndNames.end(), // m_previousLabelKeysAndNames) == false) { // needToGenerateKeys = true; // } } // if (labelTable->hasLabelsWithInvalidGroupNameHierarchy()) { // needToGenerateKeys = true; // } } } } if (needToGenerateKeys) { //const int32_t ID_NOT_USED = 0; /* * Save keys and names for comparison in next update test */ m_previousLabelFileKeysAndNames = labelKeysAndNames; if (m_previousLabelFileKeysAndNames.empty()) { labelTable->getKeysAndNames(m_previousLabelFileKeysAndNames); } /* * Clear everything */ this->clear(); /* * Update with labels from maps */ const int32_t numMaps = labelFile->getNumberOfMaps(); for (int32_t iMap = 0; iMap < numMaps; iMap++) { /* * Get the group. If it is empty, use the default name. */ AString theGroupName = labelFile->getMapName(iMap); if (theGroupName.isEmpty()) { theGroupName = missingGroupName; } /* * Find/create group */ GroupAndNameHierarchyItem* groupItem = addChild(GroupAndNameHierarchyItem::ITEM_TYPE_GROUP, theGroupName, iMap); CaretAssert(groupItem); /* * Get indices of labels used in this map */ std::vector labelKeys = labelFile->getUniqueLabelKeysUsedInMap(iMap); const int32_t numLabelKeys = static_cast(labelKeys.size()); for (int32_t iLabel = 0; iLabel < numLabelKeys; iLabel++) { const int32_t labelKey = labelKeys[iLabel]; GiftiLabel* label = labelTable->getLabel(labelKey); if (label == NULL) { continue; } AString labelName = label->getName(); if (labelName.isEmpty()) { labelName = missingName; } float rgba[4]; label->getColor(rgba); /* * Adding focus to class */ GroupAndNameHierarchyItem* nameItem = groupItem->addChild(GroupAndNameHierarchyItem::ITEM_TYPE_NAME, labelName, labelKey); nameItem->setIconColorRGBA(rgba); /* * Place the name selector into the label. */ label->setGroupNameSelectionItem(nameItem); } } /* * Sort names in each group */ std::vector groups = getChildren(); for (std::vector::iterator iter = groups.begin(); iter != groups.end(); iter++) { GroupAndNameHierarchyItem* nameItem = *iter; nameItem->sortDescendantsByName(); } setUserInterfaceUpdateNeeded(); CaretLogFine("LABEL HIERARCHY:" + toString()); } } /** * Update this group hierarchy with the label names * and groups. * * @param ciftiMappableDataFile * The cifti mappable data file from which classes and names are from. * @parm forceUpdate * If true, force an update. */ void GroupAndNameHierarchyModel::update(CiftiMappableDataFile* ciftiMappableDataFile, const bool forceUpdate) { /* * If it is not a label file, there is nothing to do. */ if (ciftiMappableDataFile->isMappedWithLabelTable() == false) { this->clear(); return; } /* * Names for missing group names or foci names. */ const AString missingGroupName = "NoGroup"; const AString missingName = "NoName"; bool needToGenerateKeys = forceUpdate; setName(ciftiMappableDataFile->getFileNameNoPath()); std::vector > labelMapKeysAndNames; if (needToGenerateKeys == false) { /* * Check to see if any group (map) names have changed. */ const std::vector groups = getChildren(); const int numGroups = static_cast(groups.size()); if (numGroups != ciftiMappableDataFile->getNumberOfMaps()) { /* * Number of maps has changed. */ needToGenerateKeys = true; } else { for (int32_t i = 0; i < numGroups; i++) { AString mapName = ciftiMappableDataFile->getMapName(i); if (mapName.isEmpty()) { mapName = missingGroupName; } if (groups[i]->getName() != mapName) { needToGenerateKeys = true; break; } } if (needToGenerateKeys == false) { if (static_cast(m_previousCiftiLabelFileMapKeysAndNames.size()) != ciftiMappableDataFile->getNumberOfMaps()) { needToGenerateKeys = true; } } if (needToGenerateKeys == false) { for (int32_t i = 0; i < numGroups; i++) { const GiftiLabelTable* labelTable = ciftiMappableDataFile->getMapLabelTable(i); std::map labelKeysAndNames; labelTable->getKeysAndNames(labelKeysAndNames); labelMapKeysAndNames.push_back(labelKeysAndNames); } for (int32_t i = 0; i < numGroups; i++) { const std::map& labelKeysAndNames = labelMapKeysAndNames[i]; const std::map& previousLabelKeysAndNames = m_previousCiftiLabelFileMapKeysAndNames[i]; if (previousLabelKeysAndNames.size() != labelKeysAndNames.size()) { needToGenerateKeys = true; break; } else { std::map::const_iterator prevIter = previousLabelKeysAndNames.begin(); for (std::map::const_iterator labelIter = labelKeysAndNames.begin(); labelIter != labelKeysAndNames.end(); labelIter++) { if (prevIter->first != labelIter->first) { needToGenerateKeys = true; break; } else if (prevIter->second != labelIter->second) { needToGenerateKeys = true; break; } prevIter++; } } } } } } if (needToGenerateKeys) { //const int32_t ID_NOT_USED = 0; /* * Save keys and names for comparison in next update test */ const int numMaps = ciftiMappableDataFile->getNumberOfMaps(); m_previousCiftiLabelFileMapKeysAndNames = labelMapKeysAndNames; if (m_previousCiftiLabelFileMapKeysAndNames.empty()) { for (int32_t i = 0; i < numMaps; i++) { const GiftiLabelTable* labelTable = ciftiMappableDataFile->getMapLabelTable(i); std::map labelKeysAndNames; labelTable->getKeysAndNames(labelKeysAndNames); m_previousCiftiLabelFileMapKeysAndNames.push_back(labelKeysAndNames); } } /* * Clear everything */ this->clear(); /* * Update with labels from maps */ for (int32_t iMap = 0; iMap < numMaps; iMap++) { /* * The label table */ GiftiLabelTable* labelTable = ciftiMappableDataFile->getMapLabelTable(iMap); /* * Get the group. If it is empty, use the default name. */ AString theGroupName = ciftiMappableDataFile->getMapName(iMap); if (theGroupName.isEmpty()) { theGroupName = missingGroupName; } /* * Find/create group */ GroupAndNameHierarchyItem* groupItem = addChild(GroupAndNameHierarchyItem::ITEM_TYPE_GROUP, theGroupName, iMap); CaretAssert(groupItem); /* * Get indices of labels used in this map */ std::vector labelKeys = ciftiMappableDataFile->getUniqueLabelKeysUsedInMap(iMap); const int32_t numLabelKeys = static_cast(labelKeys.size()); for (int32_t iLabel = 0; iLabel < numLabelKeys; iLabel++) { const int32_t labelKey = labelKeys[iLabel]; GiftiLabel* label = labelTable->getLabel(labelKey); if (label == NULL) { continue; } AString labelName = label->getName(); if (labelName.isEmpty()) { labelName = missingName; } float rgba[4]; label->getColor(rgba); /* * Adding focus to class */ GroupAndNameHierarchyItem* nameItem = groupItem->addChild(GroupAndNameHierarchyItem::ITEM_TYPE_NAME, labelName, labelKey); nameItem->setIconColorRGBA(rgba); /* * Place the name selector into the label. */ label->setGroupNameSelectionItem(nameItem); } } /* * Sort names in each group */ std::vector groups = getChildren(); for (std::vector::iterator iter = groups.begin(); iter != groups.end(); iter++) { GroupAndNameHierarchyItem* nameItem = *iter; nameItem->sortDescendantsByName(); } setUserInterfaceUpdateNeeded(); CaretLogFine("LABEL HIERARCHY:" + toString()); } } /** * Update this group hierarchy with the label names * and groups. * * @param ciftiMappableDataFile * The volume file from which classes and names are from. * @parm forceUpdate * If true, force an update. */ void GroupAndNameHierarchyModel::update(VolumeFile* volumeFile, const bool forceUpdate) { /* * If it is not a label file, there is nothing to do. */ if (volumeFile->isMappedWithLabelTable() == false) { this->clear(); return; } /* * Names for missing group names or foci names. */ const AString missingGroupName = "NoGroup"; const AString missingName = "NoName"; bool needToGenerateKeys = forceUpdate; setName(volumeFile->getFileNameNoPath()); std::vector > labelMapKeysAndNames; if (needToGenerateKeys == false) { /* * Check to see if any group (map) names have changed. */ const std::vector groups = getChildren(); const int numGroups = static_cast(groups.size()); if (numGroups != volumeFile->getNumberOfMaps()) { /* * Number of maps has changed. */ needToGenerateKeys = true; } else { for (int32_t i = 0; i < numGroups; i++) { AString mapName = volumeFile->getMapName(i); if (mapName.isEmpty()) { mapName = missingGroupName; } if (groups[i]->getName() != mapName) { needToGenerateKeys = true; break; } } if (needToGenerateKeys == false) { if (static_cast(m_previousCiftiLabelFileMapKeysAndNames.size()) != volumeFile->getNumberOfMaps()) { needToGenerateKeys = true; } } if (needToGenerateKeys == false) { for (int32_t i = 0; i < numGroups; i++) { const GiftiLabelTable* labelTable = volumeFile->getMapLabelTable(i); std::map labelKeysAndNames; labelTable->getKeysAndNames(labelKeysAndNames); labelMapKeysAndNames.push_back(labelKeysAndNames); } for (int32_t i = 0; i < numGroups; i++) { const std::map& labelKeysAndNames = labelMapKeysAndNames[i]; const std::map& previousLabelKeysAndNames = m_previousCiftiLabelFileMapKeysAndNames[i]; if (previousLabelKeysAndNames.size() != labelKeysAndNames.size()) { needToGenerateKeys = true; break; } else { std::map::const_iterator prevIter = previousLabelKeysAndNames.begin(); for (std::map::const_iterator labelIter = labelKeysAndNames.begin(); labelIter != labelKeysAndNames.end(); labelIter++) { if (prevIter->first != labelIter->first) { needToGenerateKeys = true; break; } else if (prevIter->second != labelIter->second) { needToGenerateKeys = true; break; } prevIter++; } } } } } } if (needToGenerateKeys) { //const int32_t ID_NOT_USED = 0; /* * Save keys and names for comparison in next update test */ const int numMaps = volumeFile->getNumberOfMaps(); m_previousCiftiLabelFileMapKeysAndNames = labelMapKeysAndNames; if (m_previousCiftiLabelFileMapKeysAndNames.empty()) { for (int32_t i = 0; i < numMaps; i++) { const GiftiLabelTable* labelTable = volumeFile->getMapLabelTable(i); std::map labelKeysAndNames; labelTable->getKeysAndNames(labelKeysAndNames); m_previousCiftiLabelFileMapKeysAndNames.push_back(labelKeysAndNames); } } /* * Clear everything */ this->clear(); /* * Update with labels from maps */ for (int32_t iMap = 0; iMap < numMaps; iMap++) { /* * The label table */ GiftiLabelTable* labelTable = volumeFile->getMapLabelTable(iMap); /* * Get the group. If it is empty, use the default name. */ AString theGroupName = volumeFile->getMapName(iMap); if (theGroupName.isEmpty()) { theGroupName = missingGroupName; } /* * Find/create group */ GroupAndNameHierarchyItem* groupItem = addChild(GroupAndNameHierarchyItem::ITEM_TYPE_GROUP, theGroupName, iMap); CaretAssert(groupItem); /* * Get indices of labels used in this map */ std::vector labelKeys = volumeFile->getUniqueLabelKeysUsedInMap(iMap); const int32_t numLabelKeys = static_cast(labelKeys.size()); for (int32_t iLabel = 0; iLabel < numLabelKeys; iLabel++) { const int32_t labelKey = labelKeys[iLabel]; GiftiLabel* label = labelTable->getLabel(labelKey); if (label == NULL) { continue; } AString labelName = label->getName(); if (labelName.isEmpty()) { labelName = missingName; } float rgba[4]; label->getColor(rgba); /* * Adding focus to class */ GroupAndNameHierarchyItem* nameItem = groupItem->addChild(GroupAndNameHierarchyItem::ITEM_TYPE_NAME, labelName, labelKey); nameItem->setIconColorRGBA(rgba); /* * Place the name selector into the label. */ label->setGroupNameSelectionItem(nameItem); } } /* * Sort names in each group */ std::vector groups = getChildren(); for (std::vector::iterator iter = groups.begin(); iter != groups.end(); iter++) { GroupAndNameHierarchyItem* nameItem = *iter; nameItem->sortDescendantsByName(); } setUserInterfaceUpdateNeeded(); CaretLogFine("LABEL HIERARCHY:" + toString()); } } /** * Update this group hierarchy with the foci names * and groups. * * @param fociFile * The foci file from which classes and names are from. * @parm forceUpdate * If true, force an update. */ void GroupAndNameHierarchyModel::update(FociFile* fociFile, const bool forceUpdate) { bool needToGenerateKeys = forceUpdate; setName(fociFile->getFileNameNoPath()); const int32_t numFoci = fociFile->getNumberOfFoci(); if (needToGenerateKeys == false) { for (int32_t i = 0; i < numFoci; i++) { const Focus* focus = fociFile->getFocus(i); if (focus->getGroupNameSelectionItem() == NULL) { needToGenerateKeys = true; } } } /* * ID for groups and names is not used */ const int32_t ID_NOT_USED = 0; if (needToGenerateKeys) { /* * Names for missing group names or foci names. */ const AString missingGroupName = "NoGroup"; const AString missingName = "NoName"; /* * Reset the counts for all group and children names. */ clearCounters(); /* * For icons */ const GiftiLabelTable* classLabelTable = fociFile->getClassColorTable(); const GiftiLabelTable* nameLabelTable = fociFile->getNameColorTable(); const float rgbaBlack[4] = { 0.0, 0.0, 0.0, 1.0 }; /* * Update with all foci. */ for (int32_t i = 0; i < numFoci; i++) { Focus* focus = fociFile->getFocus(i); /* * Get the group. If it is empty, use the default name. */ AString theGroupName = focus->getClassName(); if (theGroupName.isEmpty()) { theGroupName = missingGroupName; } /* * Get the name. */ AString name = focus->getName(); if (name.isEmpty()) { name = missingName; } /* * Class */ GroupAndNameHierarchyItem* groupItem = addChild(GroupAndNameHierarchyItem::ITEM_TYPE_GROUP, theGroupName, ID_NOT_USED); CaretAssert(groupItem); CaretAssert(groupItem); const GiftiLabel* groupLabel = classLabelTable->getLabelBestMatching(theGroupName); if (groupLabel != NULL) { float rgba[4]; groupLabel->getColor(rgba); groupItem->setIconColorRGBA(rgba); } else { groupItem->setIconColorRGBA(rgbaBlack); } /* * Name */ GroupAndNameHierarchyItem* nameItem = groupItem->addChild(GroupAndNameHierarchyItem::ITEM_TYPE_NAME, name, ID_NOT_USED); const GiftiLabel* nameLabel = nameLabelTable->getLabelBestMatching(name); if (nameLabel != NULL) { float rgba[4]; nameLabel->getColor(rgba); nameItem->setIconColorRGBA(rgba); } else { nameItem->setIconColorRGBA(rgbaBlack); } /* * Place the name selector into the border. */ focus->setGroupNameSelectionItem(nameItem); } removeDescendantsWithCountersEqualToZeros(); sortDescendantsByName(); setUserInterfaceUpdateNeeded(); CaretLogFine("FOCI HIERARCHY:" + toString()); } } /** * Is a User-Interface needed in the given display group and tab? * This occurs when the hierarchy has changed (group and name). * After calling this method, the status for the display group/tab is cleared. * * @param displayGroup * Display group. * @param tabIndex * Index of tab. * @return True if update needed, else false. */ bool GroupAndNameHierarchyModel::needsUserInterfaceUpdate(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const { bool needUpdate = false; const int32_t displayIndex = (int32_t)displayGroup; CaretAssertArrayIndex(this->expandedStatusInDisplayGroup, DisplayGroupEnum::NUMBER_OF_GROUPS, displayIndex); if (displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { CaretAssertArrayIndex(this->expandedStatusInTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); needUpdate = m_updateNeededInTab[tabIndex]; m_updateNeededInTab[tabIndex] = false; } else { needUpdate = m_updateNeededInDisplayGroupAndTab[displayIndex][tabIndex]; m_updateNeededInDisplayGroupAndTab[displayIndex][tabIndex] = false; } return needUpdate; } /** * Set user interface updates needed. */ void GroupAndNameHierarchyModel::setUserInterfaceUpdateNeeded() { for (int32_t i = 0; i < DisplayGroupEnum::NUMBER_OF_GROUPS; i++) { for (int32_t j = 0; j < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; j++) { m_updateNeededInDisplayGroupAndTab[i][j] = true; } } for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_updateNeededInTab[i] = true; } } workbench-1.1.1/src/Files/GroupAndNameHierarchyModel.h000066400000000000000000000073201255417355300226520ustar00rootroot00000000000000#ifndef __CLASS_AND_NAME_HIERARCHY_MODEL_H_ #define __CLASS_AND_NAME_HIERARCHY_MODEL_H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "GroupAndNameHierarchyItem.h" #include "GroupAndNameCheckStateEnum.h" namespace caret { class BorderFile; class CiftiMappableDataFile; class FociFile; class LabelFile; class VolumeFile; class GroupAndNameHierarchyModel : public GroupAndNameHierarchyItem { public: GroupAndNameHierarchyModel(); virtual ~GroupAndNameHierarchyModel(); virtual void clear(); bool isGroupValid(const int32_t groupKey) const; void setAllSelected(const bool status); void setAllSelected(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const bool status); void update(BorderFile* borderFile, const bool forceUpdate); void update(FociFile* fociFile, const bool forceUpdate); void update(LabelFile* labelFile, const bool forceUpdate); void update(CiftiMappableDataFile* ciftiMappableDataFile, const bool forceUpdate); void update(VolumeFile* volumeFile, const bool forceUpdate); bool needsUserInterfaceUpdate(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) const; private: GroupAndNameHierarchyModel(const GroupAndNameHierarchyModel&); GroupAndNameHierarchyModel& operator=(const GroupAndNameHierarchyModel&); void clearModelPrivate(); void setUserInterfaceUpdateNeeded(); /** * Contains label keys and names from previous update with Label File. */ std::map m_previousLabelFileKeysAndNames; /** * Contains label keys and names from previous update with CIFTI label file. */ std::vector > m_previousCiftiLabelFileMapKeysAndNames; /** * Update needed status of DISPLAY GROUP in EACH TAB. * Used when user has set to a display group. * Indicates that an update is needed for the given display group in the given tab. */ mutable bool m_updateNeededInDisplayGroupAndTab[DisplayGroupEnum::NUMBER_OF_GROUPS][BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; /** * Update needed in TAB. */ mutable bool m_updateNeededInTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; }; #ifdef __CLASS_AND_NAME_HIERARCHY_MODEL_DECLARE__ // #endif // __CLASS_AND_NAME_HIERARCHY_MODEL_DECLARE__ } // namespace #endif //__CLASS_AND_NAME_HIERARCHY_MODEL_H_ workbench-1.1.1/src/Files/GroupAndNameHierarchyName.cxx000066400000000000000000000035621255417355300230510ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CLASS_AND_NAME_HIERARCHY_NAME_DECLARE__ #include "GroupAndNameHierarchyName.h" #undef __CLASS_AND_NAME_HIERARCHY_NAME_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::GroupAndNameHierarchyName * \brief Maintains selection of a name in each 'DisplayGroupEnum'. * * Methods that operate on a boolean value are used to query and set * the selected status. A separate method is provided to query * the 'check state'. The 'check state' may be unchecked, checked, or * partially checked (some children checked but not all). */ /** * Constructor. * @param name * The name. * @param idNumber * ID number assigned to the name. */ GroupAndNameHierarchyName::GroupAndNameHierarchyName(const AString& name, const int32_t idNumber) : GroupAndNameHierarchyItem(GroupAndNameHierarchyItem::ITEM_TYPE_NAME, name, idNumber) { } /** * Destructor. */ GroupAndNameHierarchyName::~GroupAndNameHierarchyName() { } workbench-1.1.1/src/Files/GroupAndNameHierarchyName.h000066400000000000000000000032651255417355300224760ustar00rootroot00000000000000#ifndef __CLASS_AND_NAME_HIERARCHY_NAME__H_ #define __CLASS_AND_NAME_HIERARCHY_NAME__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "GroupAndNameHierarchyItem.h" namespace caret { class GroupAndNameHierarchyName : public GroupAndNameHierarchyItem { public: GroupAndNameHierarchyName(const AString& name, const int32_t idNumber); ~GroupAndNameHierarchyName(); // ADD_NEW_METHODS_HERE private: GroupAndNameHierarchyName(const GroupAndNameHierarchyName&); GroupAndNameHierarchyName& operator=(const GroupAndNameHierarchyName&); // ADD_NEW_MEMBERS_HERE }; #ifdef __CLASS_AND_NAME_HIERARCHY_NAME_DECLARE__ // #endif // __CLASS_AND_NAME_HIERARCHY_NAME_DECLARE__ } // namespace #endif //__CLASS_AND_NAME_HIERARCHY_NAME__H_ workbench-1.1.1/src/Files/ImageDimensionsModel.cxx000066400000000000000000000270461255417355300221300ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __IMAGE_DIMENSIONS_MODEL_DECLARE__ #include "ImageDimensionsModel.h" #undef __IMAGE_DIMENSIONS_MODEL_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::ImageDimensionsModel * \brief Model for image sizing in both pixels and spatial units. * \ingroup Files * * Allows one to adjust dimensions for both pixels and spatial units. * Internally, spatial processing is in centimeters. */ /** * Constructor. */ ImageDimensionsModel::ImageDimensionsModel() : CaretObject() { m_aspectRatio = 1.0; m_pixelsPerCentimeter = 72.0 / CENTIMETERS_PER_INCH; setPixelWidthAndHeight(512, 512); } /** * Destructor. */ ImageDimensionsModel::~ImageDimensionsModel() { } /** * Copy constructor. * @param obj * Object that is copied. */ ImageDimensionsModel::ImageDimensionsModel(const ImageDimensionsModel& obj) : CaretObject(obj) { this->copyHelperImageDimensionsModel(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ ImageDimensionsModel& ImageDimensionsModel::operator=(const ImageDimensionsModel& obj) { if (this != &obj) { CaretObject::operator=(obj); this->copyHelperImageDimensionsModel(obj); } return *this; } /** * Helps with copying an object of this type. * @param obj * Object that is copied. */ void ImageDimensionsModel::copyHelperImageDimensionsModel(const ImageDimensionsModel& obj) { m_pixelWidth = obj.m_pixelWidth; m_pixelHeight = obj.m_pixelHeight; m_centimetersWidth = obj.m_centimetersWidth; m_centimetersHeight = obj.m_centimetersHeight; m_pixelsPerCentimeter = obj.m_pixelsPerCentimeter; } /** * Get a description of this object's content. * @return String describing this object's content. */ AString ImageDimensionsModel::toString() const { return "ImageDimensionsModel"; } /** * @return The width in pixels. */ int32_t ImageDimensionsModel::getPixelWidth() const { /* * Internally, pixels are float, so round (by adding 0.5) * and return as integer. */ return static_cast(m_pixelWidth + 0.5); } /** * @return The height in pixels. */ int32_t ImageDimensionsModel::getPixelHeight() const { /* * Internally, pixels are float, so round (by adding 0.5) * and return as integer. */ return static_cast(m_pixelHeight + 0.5); } /** * Get the spatial width. * * @param spatialUnits * Spatial units for the returned width. * @return * Width in spatial units. */ float ImageDimensionsModel::getSpatialWidth(const ImageSpatialUnitsEnum::Enum spatialUnits) const { float width = 1.0; switch (spatialUnits) { case ImageSpatialUnitsEnum::CENTIMETERS: width = m_centimetersWidth; break; case ImageSpatialUnitsEnum::INCHES: width = m_centimetersWidth / CENTIMETERS_PER_INCH; break; case ImageSpatialUnitsEnum::MILLIMETERS: width = m_centimetersWidth * MILLIMETERS_PER_CENTIMETER; break; } return width; } /** * Get the spatial height. * * @param spatialUnits * Spatial units for the returned height. * @return * Height in spatial units. */ float ImageDimensionsModel::getSpatialHeight(const ImageSpatialUnitsEnum::Enum spatialUnits) const { float height = 1.0; switch (spatialUnits) { case ImageSpatialUnitsEnum::CENTIMETERS: height = m_centimetersHeight; break; case ImageSpatialUnitsEnum::INCHES: height = m_centimetersHeight / CENTIMETERS_PER_INCH; break; case ImageSpatialUnitsEnum::MILLIMETERS: height = m_centimetersHeight * MILLIMETERS_PER_CENTIMETER; break; } return height; } /** * Get the number of pixels per given spatial unit. * * @param pixelsPerSpatialUnit * Pixels per spatial unit type. * @return * Number of pixels per spatial unit. */ float ImageDimensionsModel::getNumberOfPixelsPerSpatialUnit(const ImagePixelsPerSpatialUnitsEnum::Enum pixelsPerSpatialUnit) const { float pixelsPerUnit = 1.0; switch (pixelsPerSpatialUnit) { case ImagePixelsPerSpatialUnitsEnum::PIXEL_PER_CENTIMETER: pixelsPerUnit = m_pixelsPerCentimeter; break; case ImagePixelsPerSpatialUnitsEnum::PIXELS_PER_INCH: pixelsPerUnit = m_pixelsPerCentimeter * CENTIMETERS_PER_INCH; break; } return pixelsPerUnit; } /** * Set the width and height of the image in pixels. * * @param pixelWidth * New pixel width. * @param pixelHeight * New pixel height. */ void ImageDimensionsModel::setPixelWidthAndHeight(const int32_t pixelWidth, const int32_t pixelHeight) { CaretAssert(pixelWidth > 0); CaretAssert(pixelHeight > 0); m_pixelWidth = pixelWidth; m_pixelHeight = pixelHeight; updateSpatialWidthAndHeightFromPixelWidthAndHeight(); } /** * Set the pixel width to the given width. * * @param pixelWidth * New pixel width. * @param preserveAspectRatio * Maintain image aspect ratio. */ void ImageDimensionsModel::setPixelWidth(const int32_t pixelWidth, const bool preserveAspectRatio) { CaretAssert(pixelWidth > 0); const float aspectRatio = getAspectRatio(); m_pixelWidth = pixelWidth; if (preserveAspectRatio) { m_pixelHeight = m_pixelWidth * aspectRatio; } updateSpatialWidthAndHeightFromPixelWidthAndHeight(); } /** * Set the pixel height to the given height. * * @param pixelHeight * New pixel height. * @param preserveAspectRatio * Maintain image aspect ratio. */ void ImageDimensionsModel::setPixelHeight(const int32_t pixelHeight, const bool preserveAspectRatio) { CaretAssert(pixelHeight > 0); const float aspectRatio = getAspectRatio(); m_pixelHeight = pixelHeight; if (preserveAspectRatio) { m_pixelWidth = m_pixelHeight / aspectRatio; } updateSpatialWidthAndHeightFromPixelWidthAndHeight(); } /** * Set the spatial width to the given width. * * @param spatialWidth * New spatial width. * @param spatialUnit * The spatial unit of the given width. * @param preserveAspectRatio * Maintain image aspect ratio. */ void ImageDimensionsModel::setSpatialWidth(const float spatialWidth, const ImageSpatialUnitsEnum::Enum spatialUnit, const bool preserveAspectRatio) { CaretAssert(spatialWidth > 0); const float aspectRatio = getAspectRatio(); switch (spatialUnit) { case ImageSpatialUnitsEnum::MILLIMETERS: m_centimetersWidth = spatialWidth / MILLIMETERS_PER_CENTIMETER; break; case ImageSpatialUnitsEnum::INCHES: m_centimetersWidth = spatialWidth * CENTIMETERS_PER_INCH; break; case ImageSpatialUnitsEnum::CENTIMETERS: m_centimetersWidth = spatialWidth; break; } if (preserveAspectRatio) { m_centimetersHeight = m_centimetersWidth * aspectRatio; } updatePixelWidthAndHeightFromSpatialWidthAndHeight(); } /** * Set the spatial height to the given height. * * @param spatialHeight * New spatial height. * @param spatialUnit * The spatial unit of the given height. * @param preserveAspectRatio * Maintain image aspect ratio. */ void ImageDimensionsModel::setSpatialHeight(const float spatialHeight, const ImageSpatialUnitsEnum::Enum spatialUnit, const bool preserveAspectRatio) { CaretAssert(spatialHeight > 0); const float aspectRatio = getAspectRatio(); switch (spatialUnit) { case ImageSpatialUnitsEnum::MILLIMETERS: m_centimetersHeight = spatialHeight / MILLIMETERS_PER_CENTIMETER; break; case ImageSpatialUnitsEnum::INCHES: m_centimetersHeight = spatialHeight * CENTIMETERS_PER_INCH; break; case ImageSpatialUnitsEnum::CENTIMETERS: m_centimetersHeight = spatialHeight; break; } if (preserveAspectRatio) { m_centimetersWidth = m_centimetersHeight / aspectRatio; } updatePixelWidthAndHeightFromSpatialWidthAndHeight(); } /** * Set the number of pixels per spatial unit to the given value. * * @param numberOfPixelsPerSpatialUnit * New value for number of pixels per spatial unit. * @param pixelsPerSpatialUnit * Pixels per spatial unit type. */ void ImageDimensionsModel::setNumberOfPixelsPerSpatialUnit(const float numberOfPixelsPerSpatialUnit, const ImagePixelsPerSpatialUnitsEnum::Enum pixelsPerSpatialUnit) { CaretAssert(numberOfPixelsPerSpatialUnit > 0); switch (pixelsPerSpatialUnit) { case ImagePixelsPerSpatialUnitsEnum::PIXELS_PER_INCH: m_pixelsPerCentimeter = numberOfPixelsPerSpatialUnit / CENTIMETERS_PER_INCH; break; case ImagePixelsPerSpatialUnitsEnum::PIXEL_PER_CENTIMETER: m_pixelsPerCentimeter = numberOfPixelsPerSpatialUnit; break; } updatePixelWidthAndHeightFromSpatialWidthAndHeight(); } /** * Update the pixel width and height after a change to the spatial width * and/or height. */ void ImageDimensionsModel::updatePixelWidthAndHeightFromSpatialWidthAndHeight() { m_pixelWidth = m_centimetersWidth * m_pixelsPerCentimeter; m_pixelHeight = m_centimetersHeight * m_pixelsPerCentimeter; } /** * Update the spatial width and height after a change to the pixel width * and/or height. */ void ImageDimensionsModel::updateSpatialWidthAndHeightFromPixelWidthAndHeight() { m_centimetersWidth = m_pixelWidth / m_pixelsPerCentimeter; m_centimetersHeight = m_pixelHeight / m_pixelsPerCentimeter; } /** * @return the aspect ration (height / width). */ float ImageDimensionsModel::getAspectRatio() const { return m_aspectRatio; } /** * Update the pixel and image dimensions for the aspect ratio * determined by the given width and height. Aspect ratio * is (height / width) and the height will be changed using * the aspect ratio. * * @param width * Width used in denominator of aspect ratio. * @param height * Height used in numerator of aspect ratio. */ void ImageDimensionsModel::updateForAspectRatio(const float width, const float height) { if ((width > 0.0) && (height > 0.0)) { m_aspectRatio = height / width; m_pixelHeight = m_aspectRatio * m_pixelWidth; updateSpatialWidthAndHeightFromPixelWidthAndHeight(); } } workbench-1.1.1/src/Files/ImageDimensionsModel.h000066400000000000000000000077551255417355300215620ustar00rootroot00000000000000#ifndef __IMAGE_DIMENSIONS_MODEL_H__ #define __IMAGE_DIMENSIONS_MODEL_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "ImagePixelsPerSpatialUnitsEnum.h" #include "ImageSpatialUnitsEnum.h" namespace caret { class ImageDimensionsModel : public CaretObject { public: ImageDimensionsModel(); virtual ~ImageDimensionsModel(); ImageDimensionsModel(const ImageDimensionsModel& obj); ImageDimensionsModel& operator=(const ImageDimensionsModel& obj); int32_t getPixelWidth() const; int32_t getPixelHeight() const; float getSpatialWidth(const ImageSpatialUnitsEnum::Enum spatialUnits) const; float getSpatialHeight(const ImageSpatialUnitsEnum::Enum spatialUnits) const; float getNumberOfPixelsPerSpatialUnit(const ImagePixelsPerSpatialUnitsEnum::Enum pixelsPerSpatialUnit) const; void setPixelWidthAndHeight(const int32_t pixelWidth, const int32_t pixelHeight); void setPixelWidth(const int32_t pixelWidth, const bool preserveAspectRatio); void setPixelHeight(const int32_t pixelHeight, const bool preserveAspectRatio); void setSpatialWidth(const float spatialWidth, const ImageSpatialUnitsEnum::Enum spatialUnit, const bool preserveAspectRatio); void setSpatialHeight(const float spatialHeight, const ImageSpatialUnitsEnum::Enum spatialUnit, const bool preserveAspectRatio); void setNumberOfPixelsPerSpatialUnit(const float numberOfPixelsPerSpatialUnit, const ImagePixelsPerSpatialUnitsEnum::Enum pixelsPerSpatialUnit); void updateForAspectRatio(const float width, const float height); // ADD_NEW_METHODS_HERE virtual AString toString() const; private: void copyHelperImageDimensionsModel(const ImageDimensionsModel& obj); void updatePixelWidthAndHeightFromSpatialWidthAndHeight(); void updateSpatialWidthAndHeightFromPixelWidthAndHeight(); float getAspectRatio() const; // ADD_NEW_MEMBERS_HERE /** * Width/height of pixels are float. Otherwise, small changes in * pixel dimensions will get lost (truncated). */ float m_pixelWidth; float m_pixelHeight; float m_centimetersWidth; float m_centimetersHeight; float m_pixelsPerCentimeter; float m_aspectRatio; static const float CENTIMETERS_PER_INCH; static const float MILLIMETERS_PER_CENTIMETER; }; #ifdef __IMAGE_DIMENSIONS_MODEL_DECLARE__ const float ImageDimensionsModel::CENTIMETERS_PER_INCH = 2.54; const float ImageDimensionsModel::MILLIMETERS_PER_CENTIMETER = 10.0; #endif // __IMAGE_DIMENSIONS_MODEL_DECLARE__ } // namespace #endif //__IMAGE_DIMENSIONS_MODEL_H__ workbench-1.1.1/src/Files/ImageFile.cxx000066400000000000000000001030651255417355300177120ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #include #include "CaretAssert.h" #include "CaretLogger.h" #include "DataFileException.h" #include "FileInformation.h" #include "GiftiMetaData.h" #include "ImageFile.h" using namespace caret; /** * Constructor. */ ImageFile::ImageFile() : CaretDataFile(DataFileTypeEnum::IMAGE) { m_fileMetaData.grabNew(new GiftiMetaData()); m_image = new QImage(); } /** * Constructor * @param qimage * QImage that is copied to this image file. */ ImageFile::ImageFile(const QImage& qimage) : CaretDataFile(DataFileTypeEnum::IMAGE) { m_fileMetaData.grabNew(new GiftiMetaData()); m_image = new QImage(qimage); } /** * Constructs an image file from image data. * * @param imageDataRGBA * Image data unsigned bytes with one byte for each * red, green, blue, alpha. * @param imageWidth * Width of image. * @param imageHeight * Height of image. * @param imageOrigin * Location of first pixel in the image data. */ ImageFile::ImageFile(const unsigned char* imageDataRGBA, const int imageWidth, const int imageHeight, const IMAGE_DATA_ORIGIN_LOCATION imageOrigin) : CaretDataFile(DataFileTypeEnum::IMAGE) { m_fileMetaData.grabNew(new GiftiMetaData()); m_image = new QImage(imageWidth, imageHeight, QImage::Format_RGB32); bool isOriginAtTop = false; switch (imageOrigin) { case IMAGE_DATA_ORIGIN_AT_BOTTOM: isOriginAtTop = false; break; case IMAGE_DATA_ORIGIN_AT_TOP: isOriginAtTop = true; break; } /* * Documentation for QImage states that setPixel may be very costly * and recommends using the scanLine() method to access pixel data. */ for (int y = 0; y < imageHeight; y++) { const int scanLineIndex = (isOriginAtTop ? y : imageHeight -y - 1); QRgb* rgbScanLine = (QRgb*)m_image->scanLine(scanLineIndex); for (int x = 0; x < imageWidth; x++) { const int32_t contentOffset = (((y * imageWidth) * 4) + (x * 4)); const int red = imageDataRGBA[contentOffset]; const int green = imageDataRGBA[contentOffset+1]; const int blue = imageDataRGBA[contentOffset+2]; const int alpha = imageDataRGBA[contentOffset+3]; QColor color(red, green, blue, alpha); QRgb* pixel = &rgbScanLine[x]; *pixel = color.rgba(); } } } /** * Destructor. */ ImageFile::~ImageFile() { if (m_image != NULL) { delete m_image; m_image = NULL; } } /** * Clears current file data in memory. */ void ImageFile::clear() { if (m_image != NULL) { delete m_image; } m_image = new QImage(); this->clearModified(); } /** * @return The structure for this file. */ StructureEnum::Enum ImageFile::getStructure() const { return StructureEnum::INVALID; } /** * Set the structure for this file. * @param structure * New structure for this file. */ void ImageFile::setStructure(const StructureEnum::Enum /*structure */) { /* File does not support structures */ } /** * @return Get access to the file's metadata. */ GiftiMetaData* ImageFile::getFileMetaData() { return m_fileMetaData; } /** * @return Get access to unmodifiable file's metadata. */ const GiftiMetaData* ImageFile::getFileMetaData() const { return m_fileMetaData; } /** * @return true if the file is is empty (image contains no pixels). */ bool ImageFile::isEmpty() const { return (m_image->width() <= 0); } ///** // * @return A pointer to the QImage in this file. // * Note that manipulating the pointer's data will // * alter the contents of this file. // */ //QImage* //ImageFile::getAsQImage() //{ // return m_image; //} /** * @return A pointer to the QImage in this file. */ const QImage* ImageFile::getAsQImage() const { return m_image; } /** * Set the image in this file from a QImage. * @param qimage * Image that is copied to this file. */ void ImageFile::setFromQImage(const QImage& qimage) { if (m_image != NULL) { delete m_image; } m_image = new QImage(qimage); this->setModified(); } /** * Set the dots per meter. * * @param x * Dots per meter for X dimension. * @param y * Dots per meter for Y dimension. */ void ImageFile::setDotsPerMeter(const int x, const int y) { m_image->setDotsPerMeterX(x); m_image->setDotsPerMeterY(y); } /** * Examines the image to find the rectangular of the object in the image * by examining pixels in the background color. * @param backgroundColor * RGB components range 0-255. * @param objectBoundsOut * 4-dimensional array containing the region that excludes * the backround around the image's object. */ void ImageFile::findImageObject(const uint8_t backgroundColor[3], int objectBoundsOut[4]) const { // // Dimensions of image // const int numX = m_image->width(); const int numY = m_image->height(); // // Initialize output // objectBoundsOut[0] = 0; objectBoundsOut[1] = 0; objectBoundsOut[2] = numX - 1; objectBoundsOut[3] = numY - 1; // // Find left // bool gotPixelFlag = false; for (int i = 0; i < numX; i++) { for (int j = 0; j < numY; j++) { const QRgb pixel = m_image->pixel(i, j); if ((qRed(pixel) != backgroundColor[0]) || (qGreen(pixel) != backgroundColor[1]) || (qBlue(pixel) != backgroundColor[2])) { objectBoundsOut[0] = i; gotPixelFlag = true; break; } } if (gotPixelFlag) { break; } } // // Find right // gotPixelFlag = false; for (int i = (numX - 1); i >= 0; i--) { for (int j = 0; j < numY; j++) { const QRgb pixel = m_image->pixel(i, j); if ((qRed(pixel) != backgroundColor[0]) || (qGreen(pixel) != backgroundColor[1]) || (qBlue(pixel) != backgroundColor[2])) { objectBoundsOut[2] = i; gotPixelFlag = true; break; } } if (gotPixelFlag) { break; } } // // Find top // gotPixelFlag = false; for (int j = 0; j < numY; j++) { for (int i = 0; i < numX; i++) { const QRgb pixel = m_image->pixel(i, j); if ((qRed(pixel) != backgroundColor[0]) || (qGreen(pixel) != backgroundColor[1]) || (qBlue(pixel) != backgroundColor[2])) { objectBoundsOut[1] = j; gotPixelFlag = true; break; } } if (gotPixelFlag) { break; } } // // Find bottom // gotPixelFlag = false; for (int j = (numY - 1); j >= 0; j--) { for (int i = 0; i < numX; i++) { const QRgb pixel = m_image->pixel(i, j); if ((qRed(pixel) != backgroundColor[0]) || (qGreen(pixel) != backgroundColor[1]) || (qBlue(pixel) != backgroundColor[2])) { objectBoundsOut[3] = j; gotPixelFlag = true; break; } } if (gotPixelFlag) { break; } } } /** * Add a margin to this image. * @param marginSize * Number of pixels in the margin. * @param backgroundColor used for the added pixels. * RGB components range 0-255. */ void ImageFile::addMargin(const int marginSize, const uint8_t backgroundColor[3]) { this->addMargin(marginSize, marginSize, backgroundColor); /* if (marginSize <= 0) { return; } // // Add margin // const int width = image.width(); const int height = image.height(); const int newWidth = width + marginSize * 2; const int newHeight = height + marginSize * 2; QRgb backgroundColorRGB = qRgba(backgroundColor[0], backgroundColor[1], backgroundColor[2], 0); // // Insert image // ImageFile imageFile; imageFile.setImage(QImage(newWidth, newHeight, image.format())); imageFile.getImage()->fill(backgroundColorRGB); try { imageFile.insertImage(image, marginSize, marginSize); image = (*imageFile.getImage()); } catch (DataFileException&) { } */ } /** * Add a margin to this image. * @param image * Image to which margin is added. * @param marginSizeX * Number of pixels in the margin along x-axis. * @param marginSizeY * Number of pixels in the margin along y-axis. * @param backgroundColor used for the added pixels. * RGB components range 0-255. */ void ImageFile::addMargin(const int marginSizeX, const int marginSizeY, const uint8_t backgroundColor[3]) { if ((marginSizeX <= 0) && (marginSizeY <= 0)) { return; } // // Add margin // const int width = m_image->width(); const int height = m_image->height(); const int newWidth = width + marginSizeX * 2; const int newHeight = height + marginSizeY * 2; QRgb backgroundColorRGB = qRgba(backgroundColor[0], backgroundColor[1], backgroundColor[2], 0); // // Insert image // ImageFile imageFile; imageFile.setFromQImage(QImage(newWidth, newHeight, m_image->format())); imageFile.m_image->fill(backgroundColorRGB); try { imageFile.insertImage(*m_image, marginSizeX, marginSizeY); this->setFromQImage(*imageFile.getAsQImage()); } catch (DataFileException& e) { CaretLogWarning(e.whatString()); } this->setModified(); } /** * Crop an image by removing the background from the object in the image * but keeping a margin of the given size around the image. * @param marginSize * Number of pixels in the margin around the image's object. * @param backgroundColor * Color of background that ranges 0-255. */ void ImageFile::cropImageRemoveBackground(const int marginSize, const uint8_t backgroundColor[3]) { // // Get cropping bounds // int leftTopRightBottom[4]; this->findImageObject(backgroundColor, leftTopRightBottom); CaretLogFine("cropping: " + AString::fromNumbers(leftTopRightBottom, 4, " ")); const int currentWidth = m_image->width(); const int currentHeight = m_image->height(); // // If cropping is valid // const int width = leftTopRightBottom[2] - leftTopRightBottom[0] + 1; const int height = leftTopRightBottom[3] - leftTopRightBottom[1] + 1; if ((width != currentWidth) || (height != currentHeight)) { if ((width > 1) && (height > 1)) { QImage copyImage = this->getAsQImage()->copy(leftTopRightBottom[0], leftTopRightBottom[1], width, height); if (copyImage.isNull() == false) { if ((copyImage.width() > 0) && (copyImage.height() > 0)) { this->setFromQImage(copyImage); } } this->setModified(); } } // // Process margin // if (marginSize > 0) { this->addMargin(marginSize, backgroundColor); } } /** * Replace the contents of this image by combining the input images and * retaining aspect and stretching and filling if needed. * @param imageFiles * Images that are combined. * @param numImagesPerRow * Number of images in each row. * @param backgroundColor * Color of background that ranges 0-255. */ void ImageFile::combinePreservingAspectAndFillIfNeeded(const std::vector& imageFiles, const int numImagesPerRow, const uint8_t backgroundColor[3]) { const int numImages = static_cast(imageFiles.size()); if (numImages <= 0) { return; } if (numImages == 1) { this->setFromQImage(*imageFiles[0]->m_image); return; } QRgb backgroundColorRGB = qRgba(backgroundColor[0], backgroundColor[1], backgroundColor[2], 0); // // Resize all images but do not stretch // need to retain aspect ratio but all must // be the same size in X & Y // // // Find max width and height of input images // int maxImageWidth = 0; int maxImageHeight = 0; for (int i = 0; i < numImages; i++) { // // Track max width/height // maxImageWidth = std::max(maxImageWidth, imageFiles[i]->m_image->width()); maxImageHeight = std::max(maxImageHeight, imageFiles[i]->m_image->height()); } // // Compute size of output image and create it // const int outputImageSizeX = maxImageWidth * numImagesPerRow; const int numberOfRows = (numImages / numImagesPerRow) + (((numImages % numImagesPerRow) != 0) ? 1 : 0); const int outputImageSizeY = maxImageHeight * numberOfRows; QImage combinedImage(outputImageSizeX, outputImageSizeY, imageFiles[0]->m_image->format()); combinedImage.fill(backgroundColorRGB); // // Loop through the images // int rowCounter = 0; int columnCounter = 0; for (int i = 0; i < numImages; i++) { // // Scale image // const QImage imageScaled = imageFiles[i]->m_image->scaled(maxImageWidth, maxImageHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation); // // Compute position of where image should be inserted // const int marginX = (maxImageWidth - imageScaled.width()) / 2; const int marginY = (maxImageHeight - imageScaled.height()) / 2; const int positionX = columnCounter * maxImageWidth + marginX; const int positionY = rowCounter * maxImageHeight + marginY; // // Insert into output image // try { ImageFile::insertImage(imageScaled, combinedImage, positionX, positionY); } catch (DataFileException& e) { CaretLogWarning("QImageFile::insertImage() error: " + e.whatString()); } // // Update row and column counters // columnCounter++; if (columnCounter >= numImagesPerRow) { columnCounter = 0; rowCounter++; } } this->setFromQImage(combinedImage); } /** * Read the image file. * @param filename * Name of image file. * @throws DataFileException * If error reading image. */ void ImageFile::readFile(const AString& filename) { clear(); checkFileReadability(filename); if (filename.isEmpty()) { throw DataFileException(filename + "Filename for reading is isEmpty"); } this->setFileName(filename); if (m_image->load(filename) == false) { clear(); throw DataFileException(filename + "Unable to load file."); } this->clearModified(); } /** * Append an image file to the bottom of this image file. * @param img * Image that is appended. */ void ImageFile::appendImageAtBottom(const ImageFile& img) { // // Determine size of new image // const QImage* otherImage = img.getAsQImage(); const int newWidth = std::max(m_image->width(), otherImage->width()); const int newHeight = m_image->height() + otherImage->height(); const int oldHeight = m_image->height(); // // Copy the current image // const QImage currentImage = *m_image; // std::cout << "cw: " << currentImage.width() << std::endl; // std::cout << "ch: " << currentImage.height() << std::endl; // // Create the new image and make it "this" image // QImage newImage(newWidth, newHeight, QImage::Format_RGB32); // std::cout << "nw: " << newImage.width() << std::endl; // std::cout << "nh: " << newImage.height() << std::endl; setFromQImage(newImage); // std::cout << "iw2: " << image.width() << std::endl; // std::cout << "ih2: " << image.height() << std::endl; // // Insert current image into new image // insertImage(currentImage, 0, 0); // // Insert other image into new image // insertImage(*otherImage, 0, oldHeight); this->setModified(); } /** * Insert an image into this image which must be large enough for insertion of image. * @param otherImage * Image that is inserted into this image. * @param x * X position of where image is inserted. * @param y * Y position of where image is inserted. * @throws DataFileException * If error inserting image. */ void ImageFile::insertImage(const QImage& otherImage, const int x, const int y) { ImageFile::insertImage(otherImage, *m_image, x, y); this->setModified(); } /** * insert an image into another image. * * Insert an image into another image which must be large enough for insertion of image. * @param insertThisImage * Image that is inserted into other image. * @param intoThisImage * Image that receives insertions of other image. * @param x * X position of where image is inserted. * @param y * Y position of where image is inserted. * @throws DataFileException * If error inserting image. */ void ImageFile::insertImage(const QImage& insertThisImage, QImage& intoThisImage, const int positionX, const int positionY) { if (positionX < 0) { throw DataFileException("X position is less than zero."); } if (positionY < 0) { throw DataFileException("Y position is less than zero."); } const int otherWidth = insertThisImage.width(); const int otherHeight = insertThisImage.height(); const int myWidth = intoThisImage.width(); const int myHeight = intoThisImage.height(); if ((otherWidth + positionX) > myWidth) { throw DataFileException("This image is not large enough to insert other image."); } if ((otherHeight + positionY) > myHeight) { throw DataFileException("This image is not large enough to insert other image."); } for (int i = 0; i < otherWidth; i++) { for (int j = 0; j < otherHeight; j++) { intoThisImage.setPixel(positionX + i, positionY + j, insertThisImage.pixel(i, j)); } } } /** * Compare a file for unit testing (tolerance ignored). * * @param dataFile * Data files that is compared to this data file. * @param tolerance * Allowable difference at each pixel. * @param messageOut * Message describing differences. * @return * True if files are within tolerance, else false. */ bool ImageFile::compareFileForUnitTesting(const DataFile* dataFile, const float tolerance, AString& messageOut) const { // // Cast to an image file // const ImageFile* img = dynamic_cast(dataFile); if (img == NULL) { messageOut = ("ERROR: File for comparison (" + dataFile->getFileName() + " does not appear to be an image file."); return false; } // // Get the image from the other file // const QImage* otherImage = img->getAsQImage(); // // Confirm width/height // const int width = m_image->width(); const int height = m_image->height(); if ((width != otherImage->width()) || (height != otherImage->height())) { messageOut = "The images are of different height and/or width."; return false; } // // compare pixels // int pixelCount = 0; for (int i = 0; i < width; i++) { for (int j = 0; j < height; j++) { QColor im1 = m_image->pixel(i, j); QColor im2 = otherImage->pixel(i, j); if ((abs(im1.red() - im2.red()) > tolerance) || (abs(im1.green() - im2.green()) > tolerance) || (abs(im1.blue() - im2.blue()) > tolerance)) { pixelCount++; } } } if (pixelCount > 0) { const float pct = static_cast(pixelCount * 100.0) / static_cast(width * height); messageOut = QString::number(pct, 'f', 2) + "% pixels in the image do not match."; return false; } return true; } /** * Write the image file. * @param filename * Name of image file. * @throws DataFileException * If error writing image. */ void ImageFile::writeFile(const AString& filename) { checkFileWritability(filename); this->setFileName(filename); AString errorMessage; if (m_image->width() <= 0) { errorMessage = "Image width is zero."; } if (m_image->height() <= 0) { if (errorMessage.isEmpty() == false) errorMessage += "\n"; errorMessage = "Image height is zero."; } if (errorMessage.isEmpty() == false) { throw DataFileException(filename + " " + errorMessage); } FileInformation fileInfo(this->getFileName()); AString format = fileInfo.getFileExtension().toUpper(); if (format == "JPG") { format = "JPEG"; } QImageWriter writer(filename, format.toAscii()); if (writer.supportsOption(QImageIOHandler::Quality)) { if (format.compare("png", Qt::CaseInsensitive) == 0) { const int quality = 1; writer.setQuality(quality); } else { const int quality = 100; writer.setQuality(quality); } } if (writer.supportsOption(QImageIOHandler::CompressionRatio)) { writer.setCompression(1); } if (writer.write(*m_image) == false) { throw DataFileException(writer.errorString()); } this->clearModified(); } /** * Get the image file extensions for the supported image types. * The extensions do not include the leading period. * * @param imageFileExtensions * Output filled with extensions for supported image types. * @param defaultExtension * The default extension (preference is png, jpg, jpeg) */ void ImageFile::getImageFileExtensions(std::vector& imageFileExtensions, AString& defaultExtension) { imageFileExtensions.clear(); defaultExtension = ""; QString firstExtension; QString pngExtension; QString jpegExtension; QString jpgExtension; QString tifExtension; QString tiffExtension; QList imageFormats = QImageWriter::supportedImageFormats(); const int numFormats = imageFormats.count(); for (int i = 0; i < numFormats; i++) { AString extension = QString(imageFormats.at(i)).toLower(); imageFileExtensions.push_back(extension); if (i == 0) { firstExtension = extension; } if (extension == "png") { pngExtension = extension; } else if (extension == "jpg") { jpgExtension = extension; } else if (extension == "jpeg") { jpegExtension = extension; } else if (extension == "tif") { tifExtension = extension; } else if (extension == "tiff") { tiffExtension = extension; } } if (pngExtension.isEmpty() == false) { defaultExtension = pngExtension; } else if (jpgExtension.isEmpty() == false) { defaultExtension = jpgExtension; } else if (jpegExtension.isEmpty() == false) { defaultExtension = jpegExtension; } else if (tifExtension.isEmpty() == false) { defaultExtension = tifExtension; } else if (tiffExtension.isEmpty() == false) { defaultExtension = tiffExtension; } else { defaultExtension = firstExtension; } } /** * Get the image file filters for the supported image types. * * @param imageFileFilters * Output filled with the filters for supported image types. * @param defaultFilter * Filter for the preferred image type. */ void ImageFile::getImageFileFilters(std::vector& imageFileFilters, AString& defaultFilter) { imageFileFilters.clear(); defaultFilter.clear(); std::vector imageFileExtensions; AString defaultExtension; ImageFile::getImageFileExtensions(imageFileExtensions, defaultExtension); const int32_t numExtensions = static_cast(imageFileExtensions.size()); for (int32_t i = 0; i < numExtensions; i++) { const AString ext = imageFileExtensions[i]; const AString filter = (ext.toUpper() + " Image File (*." + ext + ")"); imageFileFilters.push_back(filter); if (ext == defaultExtension) { defaultFilter = filter; } } if (defaultFilter.isEmpty()) { if (imageFileFilters.empty() == false) { defaultFilter = imageFileFilters[0]; } } } /** * Resize the image to the given width while preserving the aspect ratio * of the image. * * @param width * Width for image. */ void ImageFile::resizeToWidth(const int32_t width) { CaretAssert(m_image); *m_image = m_image->scaledToWidth(width, Qt::SmoothTransformation); } /** * Resize the image so that its width is no larger than the given value. * If the image's current width is less than the given value, no * resizing takes place. * * @param maximumWidth * Maximum width for the image. */ void ImageFile::resizeToMaximumWidth(const int32_t maximumWidth) { CaretAssert(m_image); const int32_t width = m_image->width(); if (width > maximumWidth) { *m_image = m_image->scaledToWidth(maximumWidth, Qt::SmoothTransformation); } } /** * Resize the image so that its height is no larger than the given value. * If the image's current height is less than the given value, no * resizing takes place. * * @param maximumHeight * Maximum height for the image. */ void ImageFile::resizeToMaximumHeight(const int32_t maximumHeight) { CaretAssert(m_image); const int32_t height = m_image->height(); if (height > maximumHeight) { *m_image = m_image->scaledToHeight(maximumHeight, Qt::SmoothTransformation); } } /** * Resize the image so that its maximum dimension is the given value * yet preserves the aspect ratio of the image. If the maximum dimension * is less than the given value, no resizing takes place. * * @param maximumWidthOrHeight * Maximum dimension for the image. */ void ImageFile::resizeToMaximumWidthOrHeight(const int32_t maximumWidthOrHeight) { CaretAssert(m_image); const int32_t width = m_image->width(); const int32_t height = m_image->height(); if ((width > 0) && (height > 0)) { if (width > height) { resizeToMaximumWidth(maximumWidthOrHeight); } else { if (height > maximumWidthOrHeight) { resizeToMaximumWidth(maximumWidthOrHeight); } } } } /** * Get the RGBA bytes from the image. * * @param bytesRGBA * The RGBA bytes in the image. * @param widthOut * Width of the image. * @param heightOut * Height of the image. * @param imageOrigin * Location of first pixel in the image data. * @return * True if the bytes, width, and height are valid, else false. */ bool ImageFile::getImageBytesRGBA(const IMAGE_DATA_ORIGIN_LOCATION imageOrigin, std::vector& bytesRGBA, int32_t& widthOut, int32_t& heightOut) const { bytesRGBA.clear(); widthOut = 0; heightOut = 0; if (m_image != NULL) { widthOut = m_image->width(); heightOut = m_image->height(); if ((widthOut > 0) && (heightOut > 0)) { bytesRGBA.resize(widthOut * heightOut * 4); bool isOriginAtTop = false; switch (imageOrigin) { case IMAGE_DATA_ORIGIN_AT_BOTTOM: isOriginAtTop = false; break; case IMAGE_DATA_ORIGIN_AT_TOP: isOriginAtTop = true; break; } /* * Documentation for QImage states that setPixel may be very costly * and recommends using the scanLine() method to access pixel data. */ for (int y = 0; y < heightOut; y++) { const int scanLineIndex = (isOriginAtTop ? y : heightOut -y - 1); const uchar* scanLine = m_image->scanLine(scanLineIndex); QRgb* rgbScanLine = (QRgb*)scanLine; for (int x = 0; x < widthOut; x++) { const int32_t contentOffset = (((y * widthOut) * 4) + (x * 4)); QRgb& rgb = rgbScanLine[x]; bytesRGBA[contentOffset] = static_cast(qRed(rgb)); bytesRGBA[contentOffset+1] = static_cast(qGreen(rgb)); bytesRGBA[contentOffset+2] = static_cast(qBlue(rgb)); bytesRGBA[contentOffset+3] = 255; } } return true; } } return false; } /** * Essentially writes the image file to a byte array using the given format. * * @param byteArrayOut * Byte array into which the image is written. * @param format * Format for the image (jpg, ppm, etc.). */ void ImageFile::getImageInByteArray(QByteArray& byteArrayOut, const AString& format) const { byteArrayOut.clear(); if (m_image != NULL) { QBuffer buffer(&byteArrayOut); if ( ! buffer.open(QIODevice::WriteOnly)) { throw DataFileException(getFileName(), "PROGRAM ERROR: Unable to open byte array for output of image."); } bool successFlag = false; if (format.isEmpty()) { successFlag = m_image->save(&buffer); } else { successFlag = m_image->save(&buffer, format.toAscii().data()); } if ( ! successFlag) { throw DataFileException(getFileName(), "Failed to write image to byte array. " + buffer.errorString()); } } } /** * Essentially reads the image file from a byte array using the given format. * * @param byteArray * Byte array from which the image is read. * @param format * Format for the image (jpg, ppm, etc.) or empty if unknown. */ void ImageFile::setImageFromByteArray(const QByteArray& byteArray, const AString& format) { bool successFlag = false; if (format.isEmpty()) { successFlag = m_image->loadFromData(byteArray); } else { successFlag = m_image->loadFromData(byteArray, format.toAscii().data()); } if ( ! successFlag) { throw DataFileException(getFileName(), "Failed to create image from byte array."); } } workbench-1.1.1/src/Files/ImageFile.h000066400000000000000000000120561255417355300173360ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #ifndef __IMAGE_FILE_H__ #define __IMAGE_FILE_H__ #include "CaretDataFile.h" #include "CaretPointer.h" class QImage; namespace caret { /// File for images class ImageFile : public CaretDataFile { public: /** * Location of origin in image data. */ enum IMAGE_DATA_ORIGIN_LOCATION { /** Origin at bottom (OpenGL has origin at bottom) */ IMAGE_DATA_ORIGIN_AT_BOTTOM, /** Origin at top (most image formats have origin at top) */ IMAGE_DATA_ORIGIN_AT_TOP }; ImageFile(); ImageFile(const unsigned char* imageDataRGBA, const int imageWidth, const int imageHeight, const IMAGE_DATA_ORIGIN_LOCATION imageOrigin); ImageFile(const QImage& img); ~ImageFile(); void appendImageAtBottom(const ImageFile& img); void clear(); /** * @return The structure for this file. */ virtual StructureEnum::Enum getStructure() const; /** * Set the structure for this file. * @param structure * New structure for this file. */ virtual void setStructure(const StructureEnum::Enum structure); /** * @return Get access to the file's metadata. */ virtual GiftiMetaData* getFileMetaData(); /** * @return Get access to unmodifiable file's metadata. */ virtual const GiftiMetaData* getFileMetaData() const; virtual bool compareFileForUnitTesting(const DataFile* df, const float tolerance, AString& messageOut) const; bool isEmpty() const; //QImage* getAsQImage(); const QImage* getAsQImage() const; void setFromQImage(const QImage& img); bool getImageBytesRGBA(const IMAGE_DATA_ORIGIN_LOCATION imageOrigin, std::vector& bytesRGBA, int32_t& widthOut, int32_t& heightOut) const; virtual void readFile(const AString& filename); virtual void writeFile(const AString& filename); void cropImageRemoveBackground(const int marginSize, const uint8_t backgroundColor[3]); void findImageObject(const uint8_t backgroundColor[3], int objectBoundsOut[4]) const; void addMargin(const int marginSize, const uint8_t backgroundColor[3]); void addMargin(const int marginSizeX, const int marginSizeY, const uint8_t backgroundColor[3]); void setDotsPerMeter(const int x, const int y); void resizeToMaximumWidthOrHeight(const int32_t maximumWidthOrHeight); void resizeToMaximumWidth(const int32_t maximumWidth); void resizeToMaximumHeight(const int32_t maximumHeight); void resizeToWidth(const int32_t width); void getImageInByteArray(QByteArray& byteArrayOut, const AString& format) const; void setImageFromByteArray(const QByteArray& byteArray, const AString& format); void combinePreservingAspectAndFillIfNeeded(const std::vector& imageFiles, const int numImagesPerRow, const uint8_t backgroundColor[3]); static void getImageFileExtensions(std::vector& imageFileExtensions, AString& defaultExtension); static void getImageFileFilters(std::vector& imageFileFilters, AString& defaultFilter); private: ImageFile(const ImageFile&); ImageFile& operator=(const ImageFile&); void insertImage(const QImage& otherImage, const int x, const int y); static void insertImage(const QImage& insertThisImage, QImage& intoThisImage, const int positionX, const int positionY); QImage* m_image; CaretPointer m_fileMetaData; }; } // namespace #endif // __IMAGE_FILE_H__ workbench-1.1.1/src/Files/ImagePixelsPerSpatialUnitsEnum.cxx000066400000000000000000000261061255417355300241340ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __IMAGE_PIXELS_PER_SPATIAL_UNITS_ENUM_DECLARE__ #include "ImagePixelsPerSpatialUnitsEnum.h" #undef __IMAGE_PIXELS_PER_SPATIAL_UNITS_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::ImagePixelsPerSpatialUnitsEnum * \brief Pixel per spatial unit (inches, cm) * * Using this enumerated type in the GUI with an EnumComboBoxTemplate * * Header File (.h) * Forward declare the data type: * class EnumComboBoxTemplate; * * Declare the member: * EnumComboBoxTemplate* m_imagePixelsPerSpatialUnitsEnumComboBox; * * Declare a slot that is called when user changes selection * private slots: * void imagePixelsPerSpatialUnitsEnumComboBoxItemActivated(); * * Implementation File (.cxx) * Include the header files * #include "EnumComboBoxTemplate.h" * #include "ImagePixelsPerSpatialUnitsEnum.h" * * Instatiate: * m_imagePixelsPerSpatialUnitsEnumComboBox = new EnumComboBoxTemplate(this); * m_imagePixelsPerSpatialUnitsEnumComboBox->setup(); * * Get notified when the user changes the selection: * QObject::connect(m_imagePixelsPerSpatialUnitsEnumComboBox, SIGNAL(itemActivated()), * this, SLOT(imagePixelsPerSpatialUnitsEnumComboBoxItemActivated())); * * Update the selection: * m_imagePixelsPerSpatialUnitsEnumComboBox->setSelectedItem(NEW_VALUE); * * Read the selection: * const ImagePixelsPerSpatialUnitsEnum::Enum VARIABLE = m_imagePixelsPerSpatialUnitsEnumComboBox->getSelectedItem(); * */ /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ ImagePixelsPerSpatialUnitsEnum::ImagePixelsPerSpatialUnitsEnum(const Enum enumValue, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCodeCounter++; this->name = name; this->guiName = guiName; } /** * Destructor. */ ImagePixelsPerSpatialUnitsEnum::~ImagePixelsPerSpatialUnitsEnum() { } /** * Initialize the enumerated metadata. */ void ImagePixelsPerSpatialUnitsEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(ImagePixelsPerSpatialUnitsEnum(PIXELS_PER_INCH, "PIXELS_PER_INCH", "pixels/inch")); enumData.push_back(ImagePixelsPerSpatialUnitsEnum(PIXEL_PER_CENTIMETER, "PIXEL_PER_CENTIMETER", "pixels/cm")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const ImagePixelsPerSpatialUnitsEnum* ImagePixelsPerSpatialUnitsEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const ImagePixelsPerSpatialUnitsEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString ImagePixelsPerSpatialUnitsEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const ImagePixelsPerSpatialUnitsEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ ImagePixelsPerSpatialUnitsEnum::Enum ImagePixelsPerSpatialUnitsEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = ImagePixelsPerSpatialUnitsEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ImagePixelsPerSpatialUnitsEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type ImagePixelsPerSpatialUnitsEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString ImagePixelsPerSpatialUnitsEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const ImagePixelsPerSpatialUnitsEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ ImagePixelsPerSpatialUnitsEnum::Enum ImagePixelsPerSpatialUnitsEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = ImagePixelsPerSpatialUnitsEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ImagePixelsPerSpatialUnitsEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type ImagePixelsPerSpatialUnitsEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t ImagePixelsPerSpatialUnitsEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const ImagePixelsPerSpatialUnitsEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ ImagePixelsPerSpatialUnitsEnum::Enum ImagePixelsPerSpatialUnitsEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = ImagePixelsPerSpatialUnitsEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ImagePixelsPerSpatialUnitsEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type ImagePixelsPerSpatialUnitsEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void ImagePixelsPerSpatialUnitsEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void ImagePixelsPerSpatialUnitsEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(ImagePixelsPerSpatialUnitsEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void ImagePixelsPerSpatialUnitsEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(ImagePixelsPerSpatialUnitsEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } workbench-1.1.1/src/Files/ImagePixelsPerSpatialUnitsEnum.h000066400000000000000000000063721255417355300235640ustar00rootroot00000000000000#ifndef __IMAGE_PIXELS_PER_SPATIAL_UNITS_ENUM_H__ #define __IMAGE_PIXELS_PER_SPATIAL_UNITS_ENUM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class ImagePixelsPerSpatialUnitsEnum { public: /** * Enumerated values. */ enum Enum { /** Pixels per inch */ PIXELS_PER_INCH, /** Pixels per centimeter */ PIXEL_PER_CENTIMETER }; ~ImagePixelsPerSpatialUnitsEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); private: ImagePixelsPerSpatialUnitsEnum(const Enum enumValue, const AString& name, const AString& guiName); static const ImagePixelsPerSpatialUnitsEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; #ifdef __IMAGE_PIXELS_PER_SPATIAL_UNITS_ENUM_DECLARE__ std::vector ImagePixelsPerSpatialUnitsEnum::enumData; bool ImagePixelsPerSpatialUnitsEnum::initializedFlag = false; int32_t ImagePixelsPerSpatialUnitsEnum::integerCodeCounter = 0; #endif // __IMAGE_PIXELS_PER_SPATIAL_UNITS_ENUM_DECLARE__ } // namespace #endif //__IMAGE_PIXELS_PER_SPATIAL_UNITS_ENUM_H__ workbench-1.1.1/src/Files/ImageSpatialUnitsEnum.cxx000066400000000000000000000252511255417355300223000ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __IMAGE_SPATIAL_UNITS_ENUM_DECLARE__ #include "ImageSpatialUnitsEnum.h" #undef __IMAGE_SPATIAL_UNITS_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::ImageSpatialUnitsEnum * \brief * * * * Using this enumerated type in the GUI with an EnumComboBoxTemplate * * Header File (.h) * Forward declare the data type: * class EnumComboBoxTemplate; * * Declare the member: * EnumComboBoxTemplate* m_imageSpatialUnitsEnumComboBox; * * Declare a slot that is called when user changes selection * private slots: * void imageSpatialUnitsEnumComboBoxItemActivated(); * * Implementation File (.cxx) * Include the header files * #include "EnumComboBoxTemplate.h" * #include "ImageSpatialUnitsEnum.h" * * Instatiate: * m_imageSpatialUnitsEnumComboBox = new EnumComboBoxTemplate(this); * m_imageSpatialUnitsEnumComboBox->setup(); * * Get notified when the user changes the selection: * QObject::connect(m_imageSpatialUnitsEnumComboBox, SIGNAL(itemActivated()), * this, SLOT(imageSpatialUnitsEnumComboBoxItemActivated())); * * Update the selection: * m_imageSpatialUnitsEnumComboBox->setSelectedItem(NEW_VALUE); * * Read the selection: * const ImageSpatialUnitsEnum::Enum VARIABLE = m_imageSpatialUnitsEnumComboBox->getSelectedItem(); * */ /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ ImageSpatialUnitsEnum::ImageSpatialUnitsEnum(const Enum enumValue, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCodeCounter++; this->name = name; this->guiName = guiName; } /** * Destructor. */ ImageSpatialUnitsEnum::~ImageSpatialUnitsEnum() { } /** * Initialize the enumerated metadata. */ void ImageSpatialUnitsEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(ImageSpatialUnitsEnum(INCHES, "INCHES", "inches")); enumData.push_back(ImageSpatialUnitsEnum(CENTIMETERS, "CENTIMETERS", "cm")); enumData.push_back(ImageSpatialUnitsEnum(MILLIMETERS, "MILLIMETERS", "mm")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const ImageSpatialUnitsEnum* ImageSpatialUnitsEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const ImageSpatialUnitsEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString ImageSpatialUnitsEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const ImageSpatialUnitsEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ ImageSpatialUnitsEnum::Enum ImageSpatialUnitsEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = ImageSpatialUnitsEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ImageSpatialUnitsEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type ImageSpatialUnitsEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString ImageSpatialUnitsEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const ImageSpatialUnitsEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ ImageSpatialUnitsEnum::Enum ImageSpatialUnitsEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = ImageSpatialUnitsEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ImageSpatialUnitsEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type ImageSpatialUnitsEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t ImageSpatialUnitsEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const ImageSpatialUnitsEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ ImageSpatialUnitsEnum::Enum ImageSpatialUnitsEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = ImageSpatialUnitsEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ImageSpatialUnitsEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type ImageSpatialUnitsEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void ImageSpatialUnitsEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void ImageSpatialUnitsEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(ImageSpatialUnitsEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void ImageSpatialUnitsEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(ImageSpatialUnitsEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } workbench-1.1.1/src/Files/ImageSpatialUnitsEnum.h000066400000000000000000000061751255417355300217310ustar00rootroot00000000000000#ifndef __IMAGE_SPATIAL_UNITS_ENUM_H__ #define __IMAGE_SPATIAL_UNITS_ENUM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class ImageSpatialUnitsEnum { public: /** * Enumerated values. */ enum Enum { /** Inches */ INCHES, /** Centimeters */ CENTIMETERS, /** Millimeters */ MILLIMETERS }; ~ImageSpatialUnitsEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); private: ImageSpatialUnitsEnum(const Enum enumValue, const AString& name, const AString& guiName); static const ImageSpatialUnitsEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; #ifdef __IMAGE_SPATIAL_UNITS_ENUM_DECLARE__ std::vector ImageSpatialUnitsEnum::enumData; bool ImageSpatialUnitsEnum::initializedFlag = false; int32_t ImageSpatialUnitsEnum::integerCodeCounter = 0; #endif // __IMAGE_SPATIAL_UNITS_ENUM_DECLARE__ } // namespace #endif //__IMAGE_SPATIAL_UNITS_ENUM_H__ workbench-1.1.1/src/Files/LabelDrawingProperties.cxx000066400000000000000000000116521255417355300225000ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __LABEL_DRAWING_PROPERTIES_DECLARE__ #include "LabelDrawingProperties.h" #undef __LABEL_DRAWING_PROPERTIES_DECLARE__ #include "CaretAssert.h" #include "SceneClass.h" #include "SceneClassAssistant.h" using namespace caret; /** * \class caret::LabelDrawingProperties * \brief File properties for labels. * \ingroup Files */ /** * Constructor. */ LabelDrawingProperties::LabelDrawingProperties() : CaretObject() { m_drawingType = LabelDrawingTypeEnum::DRAW_FILLED; m_outlineColor = CaretColorEnum::BLACK; m_drawMedialWallFilled = true; m_sceneAssistant = new SceneClassAssistant(); m_sceneAssistant->add("m_drawingType", &m_drawingType); m_sceneAssistant->add("m_outlineColor", &m_outlineColor); m_sceneAssistant->add("m_drawMedialWallFilled", &m_drawMedialWallFilled); } /** * Destructor. */ LabelDrawingProperties::~LabelDrawingProperties() { delete m_sceneAssistant; } /** * @return The drawing type. */ LabelDrawingTypeEnum::Enum LabelDrawingProperties::getDrawingType() const { return m_drawingType; } /** * Set the drawing type to the given value. * @param drawingType * New value for drawing type. */ void LabelDrawingProperties::setDrawingType(const LabelDrawingTypeEnum::Enum drawingType) { m_drawingType = drawingType; } /** * @param displayGroup * Display group. * @return The outline color. */ CaretColorEnum::Enum LabelDrawingProperties::getOutlineColor() const { return m_outlineColor; } /** * Set the outline color to the given value. * @param outlineColor * New value for outline color. */ void LabelDrawingProperties::setOutlineColor(const CaretColorEnum::Enum outlineColor) { m_outlineColor = outlineColor; } /** * @return medial wall is drawn filled */ bool LabelDrawingProperties::isDrawMedialWallFilled() const { return m_drawMedialWallFilled; } /** * Set medial wall is drawn filled * @param drawMedialWallFilled * New value for medial wall is drawn filled */ void LabelDrawingProperties::setDrawMedialWallFilled(const bool drawMedialWallFilled) { m_drawMedialWallFilled = drawMedialWallFilled; } /** * Save information specific to this type of model to the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @param instanceName * Name of instance in the scene. */ SceneClass* LabelDrawingProperties::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "LabelDrawingProperties", 1); m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); // Uncomment if sub-classes must save to scene //saveSubClassDataToScene(sceneAttributes, // sceneClass); return sceneClass; } /** * Restore information specific to the type of model from the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass from which model specific information is obtained. */ void LabelDrawingProperties::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); //Uncomment if sub-classes must restore from scene //restoreSubClassDataFromScene(sceneAttributes, // sceneClass); } workbench-1.1.1/src/Files/LabelDrawingProperties.h000066400000000000000000000063501255417355300221240ustar00rootroot00000000000000#ifndef __LABEL_DRAWING_PROPERTIES_H__ #define __LABEL_DRAWING_PROPERTIES_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "CaretColorEnum.h" #include "LabelDrawingTypeEnum.h" #include "SceneableInterface.h" namespace caret { class SceneClassAssistant; class LabelDrawingProperties : public CaretObject, public SceneableInterface { public: LabelDrawingProperties(); virtual ~LabelDrawingProperties(); LabelDrawingTypeEnum::Enum getDrawingType() const; void setDrawingType(const LabelDrawingTypeEnum::Enum drawingType); CaretColorEnum::Enum getOutlineColor() const; void setOutlineColor(const CaretColorEnum::Enum outlineColor); bool isDrawMedialWallFilled() const; void setDrawMedialWallFilled(const bool drawMedialWallFilled); // ADD_NEW_METHODS_HERE virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); // If there will be sub-classes of this class that need to save // and restore data from scenes, these pure virtual methods can // be uncommented to force their implemetation by sub-classes. // protected: // virtual void saveSubClassDataToScene(const SceneAttributes* sceneAttributes, // SceneClass* sceneClass) = 0; // // virtual void restoreSubClassDataFromScene(const SceneAttributes* sceneAttributes, // const SceneClass* sceneClass) = 0; private: LabelDrawingProperties(const LabelDrawingProperties&); LabelDrawingProperties& operator=(const LabelDrawingProperties&); SceneClassAssistant* m_sceneAssistant; LabelDrawingTypeEnum::Enum m_drawingType; CaretColorEnum::Enum m_outlineColor; /** medial wall is drawn filled*/ bool m_drawMedialWallFilled; // ADD_NEW_MEMBERS_HERE }; #ifdef __LABEL_DRAWING_PROPERTIES_DECLARE__ // #endif // __LABEL_DRAWING_PROPERTIES_DECLARE__ } // namespace #endif //__LABEL_DRAWING_PROPERTIES_H__ workbench-1.1.1/src/Files/LabelDrawingTypeEnum.cxx000066400000000000000000000227371255417355300221200ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __LABEL_DRAWING_TYPE_ENUM_DECLARE__ #include "LabelDrawingTypeEnum.h" #undef __LABEL_DRAWING_TYPE_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::LabelDrawingTypeEnum * \brief Drawing type for labels. * \ingroup Files */ /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ LabelDrawingTypeEnum::LabelDrawingTypeEnum(const Enum enumValue, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCodeCounter++; this->name = name; this->guiName = guiName; } /** * Destructor. */ LabelDrawingTypeEnum::~LabelDrawingTypeEnum() { } /** * Initialize the enumerated metadata. */ void LabelDrawingTypeEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(LabelDrawingTypeEnum(DRAW_FILLED, "DRAW_FILLED", "Filled")); enumData.push_back(LabelDrawingTypeEnum(DRAW_FILLED_WITH_OUTLINE_COLOR, "DRAW_FILLED_WITH_OUTLINE_COLOR", "Filled and Outline Color")); enumData.push_back(LabelDrawingTypeEnum(DRAW_OUTLINE_COLOR, "DRAW_OUTLINE_COLOR", "Outline Color")); enumData.push_back(LabelDrawingTypeEnum(DRAW_OUTLINE_LABEL_COLOR, "DRAW_OUTLINE_LABEL_COLOR", "Outline Label Color")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const LabelDrawingTypeEnum* LabelDrawingTypeEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const LabelDrawingTypeEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString LabelDrawingTypeEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const LabelDrawingTypeEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ LabelDrawingTypeEnum::Enum LabelDrawingTypeEnum::fromName(const AString& nameIn, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = DRAW_FILLED; AString name = nameIn; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const LabelDrawingTypeEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type LabelDrawingTypeEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString LabelDrawingTypeEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const LabelDrawingTypeEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ LabelDrawingTypeEnum::Enum LabelDrawingTypeEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = DRAW_FILLED; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const LabelDrawingTypeEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type LabelDrawingTypeEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t LabelDrawingTypeEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const LabelDrawingTypeEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ LabelDrawingTypeEnum::Enum LabelDrawingTypeEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = DRAW_FILLED; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const LabelDrawingTypeEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type LabelDrawingTypeEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void LabelDrawingTypeEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void LabelDrawingTypeEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(LabelDrawingTypeEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void LabelDrawingTypeEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(LabelDrawingTypeEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } workbench-1.1.1/src/Files/LabelDrawingTypeEnum.h000066400000000000000000000064271255417355300215430ustar00rootroot00000000000000#ifndef __LABEL_DRAWING_TYPE_ENUM__H_ #define __LABEL_DRAWING_TYPE_ENUM__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class LabelDrawingTypeEnum { public: /** * Enumerated values. */ enum Enum { /** Filled in label's color */ DRAW_FILLED, /** Filled in label's color with outline color */ DRAW_FILLED_WITH_OUTLINE_COLOR, /** Outline with Outline Color */ DRAW_OUTLINE_COLOR, /** Outline with label color */ DRAW_OUTLINE_LABEL_COLOR }; ~LabelDrawingTypeEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); private: LabelDrawingTypeEnum(const Enum enumValue, const AString& name, const AString& guiName); static const LabelDrawingTypeEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; #ifdef __LABEL_DRAWING_TYPE_ENUM_DECLARE__ std::vector LabelDrawingTypeEnum::enumData; bool LabelDrawingTypeEnum::initializedFlag = false; int32_t LabelDrawingTypeEnum::integerCodeCounter = 0; #endif // __LABEL_DRAWING_TYPE_ENUM_DECLARE__ } // namespace #endif //__LABEL_DRAWING_TYPE_ENUM__H_ workbench-1.1.1/src/Files/LabelFile.cxx000066400000000000000000000377201255417355300177130ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretAssert.h" #include "CaretLogger.h" #include "GroupAndNameHierarchyModel.h" #include "DataFileException.h" #include "DataFileTypeEnum.h" #include "GiftiFile.h" #include "GiftiLabel.h" #include "MathFunctions.h" #include "LabelFile.h" using namespace caret; /** * Constructor. */ LabelFile::LabelFile() : GiftiTypeFile(DataFileTypeEnum::LABEL) { m_classNameHierarchy = NULL; this->initializeMembersLabelFile(); } /** * Copy constructor. * * @param sf * Surface file that is copied. */ LabelFile::LabelFile(const LabelFile& sf) : GiftiTypeFile(sf) { m_classNameHierarchy = NULL; this->copyHelperLabelFile(sf); } /** * Assignment operator. * * @param sf * Surface file that is copied. * @return * This surface file with content replaced * by the LabelFile parameter. */ LabelFile& LabelFile::operator=(const LabelFile& sf) { if (this != &sf) { GiftiTypeFile::operator=(sf); this->copyHelperLabelFile(sf); } return *this; } /** * Destructor. */ LabelFile::~LabelFile() { this->columnDataPointers.clear(); delete m_classNameHierarchy; } /** * Clear the surface file. */ void LabelFile::clear() { GiftiTypeFile::clear(); this->columnDataPointers.clear(); m_classNameHierarchy->clear(); } /** * @return Return the GIFTI Label Table. */ GiftiLabelTable* LabelFile::getLabelTable() { return this->giftiFile->getLabelTable(); } /** * @return Return the GIFTI Label Table. */ const GiftiLabelTable* LabelFile::getLabelTable() const { return this->giftiFile->getLabelTable(); } /** * @return The class and name hierarchy. */ GroupAndNameHierarchyModel* LabelFile::getGroupAndNameHierarchyModel() { m_classNameHierarchy->update(this, m_forceUpdateOfGroupAndNameHierarchy); m_forceUpdateOfGroupAndNameHierarchy = false; return m_classNameHierarchy; } /** * @return The class and name hierarchy. */ const GroupAndNameHierarchyModel* LabelFile::getGroupAndNameHierarchyModel() const { m_classNameHierarchy->update(const_cast(this), m_forceUpdateOfGroupAndNameHierarchy); m_forceUpdateOfGroupAndNameHierarchy = false; return m_classNameHierarchy; } /** * Validate the contents of the file after it * has been read such as correct number of * data arrays and proper data types/dimensions. */ void LabelFile::validateDataArraysAfterReading() { this->columnDataPointers.clear(); this->initializeMembersLabelFile(); this->verifyDataArraysHaveSameNumberOfRows(0, 0); bool haveWarned = false; const int32_t numberOfDataArrays = this->giftiFile->getNumberOfDataArrays(); for (int32_t i = 0; i < numberOfDataArrays; i++) { GiftiDataArray* thisArray = this->giftiFile->getDataArray(i); if (thisArray->getDataType() != NiftiDataTypeEnum::NIFTI_TYPE_INT32) { thisArray->convertToDataType(NiftiDataTypeEnum::NIFTI_TYPE_INT32); if (!haveWarned) { CaretLogWarning("label file '" + getFileName() + "' contains data array with data type other than int32"); haveWarned = true; } } int32_t* tempPointer = thisArray->getDataPointerInt(); CaretAssert(tempPointer != NULL); if (tempPointer == NULL) throw DataFileException(getFileName(), "failed to convert data array to int32.");//shouldn't happen, can probably be removed this->columnDataPointers.push_back(tempPointer); } validateKeysAndLabels(); m_classNameHierarchy->update(this, true); m_forceUpdateOfGroupAndNameHierarchy = false; m_classNameHierarchy->setAllSelected(true); CaretLogFiner("CLASS/NAME Table for : " + this->getFileNameNoPath() + "\n" + m_classNameHierarchy->toString()); } /** * Validate keys and labels in the file. */ void LabelFile::validateKeysAndLabels() const { /* * Skip if logging is not fine or less. */ if (CaretLogger::getLogger()->isFine() == false) { return; } AString message; /* * Find the label keys that are in the data */ std::set dataKeys; const int32_t numNodes = getNumberOfNodes(); const int32_t numMaps = getNumberOfMaps(); for (int32_t iNode = 0; iNode < numNodes; iNode++) { for (int32_t jMap = 0; jMap < numMaps; jMap++) { const int32_t key = getLabelKey(iNode, jMap); dataKeys.insert(key); } } /* * Find any keys that are not in the label table */ const GiftiLabelTable* labelTable = getLabelTable(); std::set missingLabelKeys; for (std::set::iterator dataKeyIter = dataKeys.begin(); dataKeyIter != dataKeys.end(); dataKeyIter++) { const int32_t dataKey = *dataKeyIter; const GiftiLabel* label = labelTable->getLabel(dataKey); if (label == NULL) { missingLabelKeys.insert(dataKey); } } if (missingLabelKeys.empty() == false) { for (std::set::iterator missingKeyIter = missingLabelKeys.begin(); missingKeyIter != missingLabelKeys.end(); missingKeyIter++) { const int32_t missingKey = *missingKeyIter; message.appendWithNewLine(" Missing Label for Key: " + AString::number(missingKey)); } } /* * Find any label table names that are not used */ std::map labelTableKeysAndNames; labelTable->getKeysAndNames(labelTableKeysAndNames); for (std::map::const_iterator ltIter = labelTableKeysAndNames.begin(); ltIter != labelTableKeysAndNames.end(); ltIter++) { const int32_t ltKey = ltIter->first; if (std::find(dataKeys.begin(), dataKeys.end(), ltKey) == dataKeys.end()) { message.appendWithNewLine(" Label Not Used Key=" + AString::number(ltKey) + ": " + ltIter->second); } } AString msg = ("File: " + getFileName() + "\n" + labelTable->toFormattedString(" ") + message); CaretLogFine(msg); } /** * Get the number of nodes. * * @return * The number of nodes. */ int32_t LabelFile::getNumberOfNodes() const { int32_t numNodes = 0; int32_t numDataArrays = this->giftiFile->getNumberOfDataArrays(); if (numDataArrays > 0) { numNodes = this->giftiFile->getDataArray(0)->getNumberOfRows(); } return numNodes; } /** * Get the number of columns. * * @return * The number of columns. */ int32_t LabelFile::getNumberOfColumns() const { const int32_t numCols = this->giftiFile->getNumberOfDataArrays(); return numCols; } /** * Initialize members of this class. */ void LabelFile::initializeMembersLabelFile() { if (m_classNameHierarchy != NULL) { delete m_classNameHierarchy; } m_classNameHierarchy = new GroupAndNameHierarchyModel(); m_forceUpdateOfGroupAndNameHierarchy = true; } /** * Helps copying files. * * @param sf * File that is copied. */ void LabelFile::copyHelperLabelFile(const LabelFile& /*sf*/) { if (m_classNameHierarchy != NULL) { delete m_classNameHierarchy; } m_classNameHierarchy = new GroupAndNameHierarchyModel(); m_forceUpdateOfGroupAndNameHierarchy = true; this->validateDataArraysAfterReading(); } /** * Get label name for a node. * * @param nodeIndex * Node index. * @param columnIndex * Column index. * @return * Label name at the given node and column indices * Empty string if label is not available. */ AString LabelFile::getLabelName(const int32_t nodeIndex, const int32_t columnIndex) const { const int32_t labelKey = this->getLabelKey(nodeIndex, columnIndex); AString label = this->giftiFile->getLabelTable()->getLabelName(labelKey); return label; } /** * Get label key for a node. * * @param nodeIndex * Node index. * @param columnIndex * Column index. * @return * Label key at the given node and column indices. */ int32_t LabelFile::getLabelKey(const int32_t nodeIndex, const int32_t columnIndex) const { CaretAssertVectorIndex(this->columnDataPointers, columnIndex); CaretAssertMessage((nodeIndex >= 0) && (nodeIndex < this->getNumberOfNodes()), "Node Index out of range."); return this->columnDataPointers[columnIndex][nodeIndex]; } /** * set label key for a node. * * @param nodeIndex * Node index. * @param columnIndex * Column index. * param labelKey * Label key inserted at the given node and column indices. */ void LabelFile::setLabelKey(const int32_t nodeIndex, const int32_t columnIndex, const int32_t labelKey) { CaretAssertVectorIndex(this->columnDataPointers, columnIndex); CaretAssertMessage((nodeIndex >= 0) && (nodeIndex < this->getNumberOfNodes()), "Node Index out of range."); this->columnDataPointers[columnIndex][nodeIndex] = labelKey; this->setModified(); m_forceUpdateOfGroupAndNameHierarchy = true; } /** * Get nodes in the given column with the given lable key. * @param columnIndex * Index of column * @param labelKey * Key of label that is desired. * @param nodeIndicesOut * On exit, will contain indices of nodes that have the * given label key in the given column. */ void LabelFile::getNodeIndicesWithLabelKey(const int32_t columnIndex, const int32_t labelKey, std::vector& nodeIndicesOut) const { const int32_t numberOfNodes = this->getNumberOfNodes(); nodeIndicesOut.clear(); nodeIndicesOut.reserve(numberOfNodes); for (int32_t i = 0; i < numberOfNodes; i++) { if (this->getLabelKey(i, columnIndex) == labelKey) { nodeIndicesOut.push_back(i); } } } /** * Get a pointer to the keys for a label file column. * @param columnIndex * Index of the column. * @return * Pointer to keys for the given column. */ const int32_t* LabelFile::getLabelKeyPointerForColumn(const int32_t columnIndex) const { CaretAssertVectorIndex(this->columnDataPointers, columnIndex); return this->columnDataPointers[columnIndex]; } void LabelFile::setNumberOfNodesAndColumns(int32_t nodes, int32_t columns) { giftiFile->clearAndKeepMetadata(); columnDataPointers.clear(); const int32_t unassignedKey = this->getLabelTable()->getUnassignedLabelKey(); std::vector dimensions; dimensions.push_back(nodes); for (int32_t i = 0; i < columns; ++i) { giftiFile->addDataArray(new GiftiDataArray(NiftiIntentEnum::NIFTI_INTENT_LABEL, NiftiDataTypeEnum::NIFTI_TYPE_INT32, dimensions, GiftiEncodingEnum::GZIP_BASE64_BINARY)); columnDataPointers.push_back(giftiFile->getDataArray(i)->getDataPointerInt()); int32_t* ptr = giftiFile->getDataArray(i)->getDataPointerInt(); for (int32_t j = 0; j < nodes; j++) { ptr[j] = unassignedKey; } } setModified(); m_forceUpdateOfGroupAndNameHierarchy = true; } /** * Add map(s) to this GIFTI file. * @param numberOfNodes * Number of nodes. If file is not empty, this value must * match the number of nodes that are in the file. * @param numberOfMaps * Number of maps to add. */ void LabelFile::addMaps(const int32_t numberOfNodes, const int32_t numberOfMaps) { if (numberOfNodes <= 0) { throw DataFileException(getFileName(), "When adding maps the number of nodes must be greater than zero"); } if (this->getNumberOfNodes() > 0) { if (numberOfNodes != this->getNumberOfNodes()) { throw DataFileException(getFileName(), "When adding maps the requested number of nodes is " + AString::number(numberOfNodes) + " but the file contains " + AString::number(this->getNumberOfNodes()) + " nodes."); } } if (numberOfMaps <= 0) { throw DataFileException(getFileName(), "When adding maps, the number of maps must be greater than zero."); } const int32_t unassignedKey = this->getLabelTable()->getUnassignedLabelKey(); if ((this->getNumberOfNodes() > 0) && (this->getNumberOfMaps() > 0)) { std::vector dimensions; dimensions.push_back(numberOfNodes); for (int32_t i = 0; i < numberOfMaps; ++i) { this->giftiFile->addDataArray(new GiftiDataArray(NiftiIntentEnum::NIFTI_INTENT_LABEL, NiftiDataTypeEnum::NIFTI_TYPE_INT32, dimensions, GiftiEncodingEnum::GZIP_BASE64_BINARY)); const int32_t mapIndex = giftiFile->getNumberOfDataArrays() - 1; this->columnDataPointers.push_back(giftiFile->getDataArray(mapIndex)->getDataPointerInt()); int32_t* ptr = giftiFile->getDataArray(mapIndex)->getDataPointerInt(); for (int32_t j = 0; j < numberOfNodes; j++) { ptr[j] = unassignedKey; } } } else { this->setNumberOfNodesAndColumns(numberOfNodes, numberOfMaps); } m_forceUpdateOfGroupAndNameHierarchy = true; this->setModified(); } void LabelFile::setLabelKeysForColumn(const int32_t columnIndex, const int32_t* valuesIn) { CaretAssertVectorIndex(this->columnDataPointers, columnIndex); int32_t* myColumn = columnDataPointers[columnIndex]; int numNodes = (int)getNumberOfNodes(); for (int i = 0; i < numNodes; ++i) { myColumn[i] = valuesIn[i]; } m_forceUpdateOfGroupAndNameHierarchy = true; setModified(); } /** * Return a vector containing the keys used in a map. Each key is listed * once and the keys will be in ascending order. * @param mapIndex * Index of map. * @return * Vector containing the keys. */ std::vector LabelFile::getUniqueLabelKeysUsedInMap(const int32_t mapIndex) const { CaretAssertVectorIndex(this->columnDataPointers, mapIndex); std::set uniqueKeys; const int32_t numNodes = getNumberOfNodes(); for (int32_t i = 0; i < numNodes; i++) { const int32_t key = getLabelKey(i, mapIndex); uniqueKeys.insert(key); } std::vector keyVector; keyVector.insert(keyVector.end(), uniqueKeys.begin(), uniqueKeys.end()); return keyVector; } workbench-1.1.1/src/Files/LabelFile.h000066400000000000000000000073241255417355300173350ustar00rootroot00000000000000 #ifndef __LABEL_FILE_H__ #define __LABEL_FILE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "GiftiTypeFile.h" namespace caret { class GroupAndNameHierarchyModel; class GiftiDataArray; class GiftiLabelTable; /** * \brief A Label data file. */ class LabelFile : public GiftiTypeFile { public: LabelFile(); LabelFile(const LabelFile& sf); LabelFile& operator=(const LabelFile& sf); virtual ~LabelFile(); void clear(); int32_t getNumberOfNodes() const; int32_t getNumberOfColumns() const; void setNumberOfNodesAndColumns(int32_t nodes, int32_t columns); virtual void addMaps(const int32_t numberOfNodes, const int32_t numberOfMaps); GiftiLabelTable* getLabelTable(); const GiftiLabelTable* getLabelTable() const; int32_t getLabelKey(const int32_t nodeIndex, const int32_t columnIndex) const; AString getLabelName(const int32_t nodeIndex, const int32_t columnIndex) const; void setLabelKey(const int32_t nodeIndex, const int32_t columnIndex, const int32_t labelIndex); void getNodeIndicesWithLabelKey(const int32_t columnIndex, const int32_t labelKey, std::vector& nodeIndicesOut) const; const int32_t* getLabelKeyPointerForColumn(const int32_t columnIndex) const; void setLabelKeysForColumn(const int32_t columnIndex, const int32_t* keysIn); std::vector getUniqueLabelKeysUsedInMap(const int32_t mapIndex) const; GroupAndNameHierarchyModel* getGroupAndNameHierarchyModel(); const GroupAndNameHierarchyModel* getGroupAndNameHierarchyModel() const; protected: /** * Validate the contents of the file after it * has been read such as correct number of * data arrays and proper data types/dimensions. */ virtual void validateDataArraysAfterReading(); void copyHelperLabelFile(const LabelFile& sf); void initializeMembersLabelFile(); private: void validateKeysAndLabels() const; /** Points to actual data in each Gifti Data Array */ std::vector columnDataPointers; /** Holds class and name hierarchy used for display selection */ mutable GroupAndNameHierarchyModel* m_classNameHierarchy; /** force an update of the class and name hierarchy */ mutable bool m_forceUpdateOfGroupAndNameHierarchy; }; } // namespace #endif // __LABEL_FILE_H__ workbench-1.1.1/src/Files/MapYokingGroupEnum.cxx000066400000000000000000000357061255417355300216360ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __MAP_YOKING_GROUP_ENUM_DECLARE__ #include "MapYokingGroupEnum.h" #undef __MAP_YOKING_GROUP_ENUM_DECLARE__ #include "CaretAssert.h" #include "EventManager.h" using namespace caret; /** * \class caret::MapYokingGroupEnum * \brief Enumerated types for map yoking selection. */ /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ MapYokingGroupEnum::MapYokingGroupEnum(const Enum enumValue, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCodeCounter++; this->name = name; this->guiName = guiName; this->mapIndex = 0; this->enabledStatus = false; } /** * Destructor. */ MapYokingGroupEnum::~MapYokingGroupEnum() { } /** * Initialize the enumerated metadata. */ void MapYokingGroupEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(MapYokingGroupEnum(MAP_YOKING_GROUP_OFF, "MAP_YOKING_GROUP_OFF", "Off")); enumData.push_back(MapYokingGroupEnum(MAP_YOKING_GROUP_1, "MAP_YOKING_GROUP_1", "I")); enumData.push_back(MapYokingGroupEnum(MAP_YOKING_GROUP_2, "MAP_YOKING_GROUP_2", "II")); enumData.push_back(MapYokingGroupEnum(MAP_YOKING_GROUP_3, "MAP_YOKING_GROUP_3", "III")); enumData.push_back(MapYokingGroupEnum(MAP_YOKING_GROUP_4, "MAP_YOKING_GROUP_4", "IV")); enumData.push_back(MapYokingGroupEnum(MAP_YOKING_GROUP_5, "MAP_YOKING_GROUP_5", "V")); enumData.push_back(MapYokingGroupEnum(MAP_YOKING_GROUP_6, "MAP_YOKING_GROUP_6", "VI")); enumData.push_back(MapYokingGroupEnum(MAP_YOKING_GROUP_7, "MAP_YOKING_GROUP_7", "VII")); enumData.push_back(MapYokingGroupEnum(MAP_YOKING_GROUP_8, "MAP_YOKING_GROUP_8", "VIII")); enumData.push_back(MapYokingGroupEnum(MAP_YOKING_GROUP_9, "MAP_YOKING_GROUP_9", "IX")); enumData.push_back(MapYokingGroupEnum(MAP_YOKING_GROUP_10, "MAP_YOKING_GROUP_10", "X")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ MapYokingGroupEnum* MapYokingGroupEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { MapYokingGroupEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString MapYokingGroupEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const MapYokingGroupEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ MapYokingGroupEnum::Enum MapYokingGroupEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = MAP_YOKING_GROUP_OFF; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const MapYokingGroupEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type MapYokingGroupEnum")); } return enumValue; } /** * Get an enumerated value corresponding to the * obsolete OverlayYokingGroupEnum that has been replaced * with MapYokingGroupEnum. * * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ MapYokingGroupEnum::Enum MapYokingGroupEnum::fromOverlayYokingGroupEnumName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = MAP_YOKING_GROUP_OFF; if (name == "OVERLAY_YOKING_GROUP_OFF") { enumValue = MAP_YOKING_GROUP_OFF; validFlag = true; } else if (name == "OVERLAY_YOKING_GROUP_1") { enumValue = MAP_YOKING_GROUP_1; validFlag = true; } else if (name == "OVERLAY_YOKING_GROUP_2") { enumValue = MAP_YOKING_GROUP_2; validFlag = true; } else if (name == "OVERLAY_YOKING_GROUP_3") { enumValue = MAP_YOKING_GROUP_3; validFlag = true; } else if (name == "OVERLAY_YOKING_GROUP_4") { enumValue = MAP_YOKING_GROUP_4; validFlag = true; } else if (name == "OVERLAY_YOKING_GROUP_5") { enumValue = MAP_YOKING_GROUP_5; validFlag = true; } else if (name == "OVERLAY_YOKING_GROUP_6") { enumValue = MAP_YOKING_GROUP_6; validFlag = true; } else if (name == "OVERLAY_YOKING_GROUP_7") { enumValue = MAP_YOKING_GROUP_7; validFlag = true; } else if (name == "OVERLAY_YOKING_GROUP_8") { enumValue = MAP_YOKING_GROUP_8; validFlag = true; } else if (name == "OVERLAY_YOKING_GROUP_9") { enumValue = MAP_YOKING_GROUP_9; validFlag = true; } else if (name == "OVERLAY_YOKING_GROUP_10") { enumValue = MAP_YOKING_GROUP_10; validFlag = true; } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type MapYokingGroupEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString MapYokingGroupEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const MapYokingGroupEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ MapYokingGroupEnum::Enum MapYokingGroupEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = MAP_YOKING_GROUP_OFF; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const MapYokingGroupEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type MapYokingGroupEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t MapYokingGroupEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const MapYokingGroupEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ MapYokingGroupEnum::Enum MapYokingGroupEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = MAP_YOKING_GROUP_OFF; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const MapYokingGroupEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type MapYokingGroupEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void MapYokingGroupEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void MapYokingGroupEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(MapYokingGroupEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void MapYokingGroupEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(MapYokingGroupEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } /** * @return The selected map index associated with the given value. * * @param enumValue * Value for which map index is requested. */ int32_t MapYokingGroupEnum::getSelectedMapIndex(const Enum enumValue) { CaretAssertMessage(enumValue != MAP_YOKING_GROUP_OFF, "Never should be called with MAP_YOKING_GROUP_OFF"); if (initializedFlag == false) initialize(); MapYokingGroupEnum* enumInstance = findData(enumValue); return enumInstance->mapIndex; } /** * Set the map index for the given enum value. * * @param enumValue * Value for which map index is requested. * @param mapIndex * New value for map index. */ void MapYokingGroupEnum::setSelectedMapIndex(const Enum enumValue, const int32_t mapIndex) { CaretAssertMessage(enumValue != MAP_YOKING_GROUP_OFF, "Never should be called with MAP_YOKING_GROUP_OFF"); if (initializedFlag == false) initialize(); MapYokingGroupEnum* enumInstance = findData(enumValue); enumInstance->mapIndex = mapIndex; } /** * @return The enabled status associated with the given value. * * @param enumValue * Value for which map index is requested. */ bool MapYokingGroupEnum::isEnabled(const Enum enumValue) { CaretAssertMessage(enumValue != MAP_YOKING_GROUP_OFF, "Never should be called with MAP_YOKING_GROUP_OFF"); if (initializedFlag == false) initialize(); MapYokingGroupEnum* enumInstance = findData(enumValue); return enumInstance->enabledStatus; } /** * Set the enabled status for the given enum value. * * @param enumValue * Value for which map index is requested. * @param enabled * New value for enabled status. */ void MapYokingGroupEnum::setEnabled(const Enum enumValue, const bool enabled) { CaretAssertMessage(enumValue != MAP_YOKING_GROUP_OFF, "Never should be called with MAP_YOKING_GROUP_OFF"); if (initializedFlag == false) initialize(); MapYokingGroupEnum* enumInstance = findData(enumValue); enumInstance->enabledStatus = enabled; } workbench-1.1.1/src/Files/MapYokingGroupEnum.h000066400000000000000000000101421255417355300212460ustar00rootroot00000000000000#ifndef __MAP_YOKING_GROUP_ENUM_H__ #define __MAP_YOKING_GROUP_ENUM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class MapYokingGroupEnum { public: /** * Enumerated values. */ enum Enum { /** Off */ MAP_YOKING_GROUP_OFF, /** Group 1 */ MAP_YOKING_GROUP_1, /** Group 2 */ MAP_YOKING_GROUP_2, /** Group 3*/ MAP_YOKING_GROUP_3, /** Group 4 */ MAP_YOKING_GROUP_4, /** Group 5 */ MAP_YOKING_GROUP_5, /** Group 6 */ MAP_YOKING_GROUP_6, /** Group 7 */ MAP_YOKING_GROUP_7, /** Group 8 */ MAP_YOKING_GROUP_8, /** Group 9 */ MAP_YOKING_GROUP_9, /** Group 10 */ MAP_YOKING_GROUP_10 }; ~MapYokingGroupEnum(); static Enum fromOverlayYokingGroupEnumName(const AString& name, bool* isValidOut); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); static int32_t getSelectedMapIndex(const Enum enumValue); static void setSelectedMapIndex( const Enum enumValue, const int32_t mapIndex); static bool isEnabled(const Enum enumValue); static void setEnabled(const Enum enumValue, const bool enabled); private: MapYokingGroupEnum(const Enum enumValue, const AString& name, const AString& guiName); static MapYokingGroupEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; /** Index of the selected map */ int32_t mapIndex; /** Enabled status of the map */ bool enabledStatus; }; #ifdef __MAP_YOKING_GROUP_ENUM_DECLARE__ std::vector MapYokingGroupEnum::enumData; bool MapYokingGroupEnum::initializedFlag = false; int32_t MapYokingGroupEnum::integerCodeCounter = 0; #endif // __MAP_YOKING_GROUP_ENUM_DECLARE__ } // namespace #endif //__MAP_YOKING_GROUP_ENUM_H__ workbench-1.1.1/src/Files/MetricFile.cxx000066400000000000000000000520301255417355300201060ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretAssert.h" #include "CaretLogger.h" #include "ChartDataCartesian.h" #include "ChartDataSource.h" #include "DataFileException.h" #include "DataFileTypeEnum.h" #include "GiftiFile.h" #include "MathFunctions.h" #include "MetricFile.h" #include "NiftiEnums.h" #include "PaletteColorMapping.h" #include "SceneClass.h" #include using namespace caret; /** * Constructor. */ MetricFile::MetricFile() : GiftiTypeFile(DataFileTypeEnum::METRIC) { this->initializeMembersMetricFile(); } /** * Copy constructor. * * @param sf * Surface file that is copied. */ MetricFile::MetricFile(const MetricFile& sf) : GiftiTypeFile(sf), ChartableLineSeriesBrainordinateInterface() { this->copyHelperMetricFile(sf); } /** * Assignment operator. * * @param sf * Surface file that is copied. * @return * This surface file with content replaced * by the MetricFile parameter. */ MetricFile& MetricFile::operator=(const MetricFile& sf) { if (this != &sf) { GiftiTypeFile::operator=(sf); this->copyHelperMetricFile(sf); } return *this; } /** * Destructor. */ MetricFile::~MetricFile() { this->columnDataPointers.clear(); } /** * Clear the surface file. */ void MetricFile::clear() { GiftiTypeFile::clear(); this->columnDataPointers.clear(); } /** * Validate the contents of the file after it * has been read such as correct number of * data arrays and proper data types/dimensions. */ void MetricFile::validateDataArraysAfterReading() { this->columnDataPointers.clear(); this->initializeMembersMetricFile(); this->verifyDataArraysHaveSameNumberOfRows(0, 0); bool isLabelData = false; const int32_t numberOfDataArrays = this->giftiFile->getNumberOfDataArrays(); for (int32_t i = 0; i < numberOfDataArrays; i++) { GiftiDataArray* gda = this->giftiFile->getDataArray(i); if (gda->getDataType() != NiftiDataTypeEnum::NIFTI_TYPE_FLOAT32) { if (gda->getIntent() == NiftiIntentEnum::NIFTI_INTENT_LABEL) { isLabelData = true; } gda->convertToDataType(NiftiDataTypeEnum::NIFTI_TYPE_FLOAT32); } int numDims = gda->getNumberOfDimensions(); std::vector dims = gda->getDimensions(); if (numDims == 1 || (numDims == 2 && dims[1] == 1)) { this->columnDataPointers.push_back(gda->getDataPointerFloat()); } else { if (numDims != 2) { throw DataFileException(getFileName(), "Invalid number of dimensions in metric file: " + AString::number(numDims)); } if (numberOfDataArrays != 1) { throw DataFileException(getFileName(), "Two dimensional data arrays are not allowed in metric files with multiple data arrays"); } std::vector newdims = dims; newdims[1] = 1; GiftiFile* newFile = new GiftiFile();//convert to multiple 1-d arrays on the fly *(newFile->getMetaData()) = *(giftiFile->getMetaData()); int32_t indices[2], newindices[2] = {0, 0}; for (indices[1] = 0; indices[1] < dims[1]; ++indices[1]) { GiftiDataArray* tempArray = new GiftiDataArray(NiftiIntentEnum::NIFTI_INTENT_NORMAL, NiftiDataTypeEnum::NIFTI_TYPE_FLOAT32, newdims, GiftiEncodingEnum::GZIP_BASE64_BINARY); for (indices[0] = 0; indices[0] < dims[0]; ++indices[0]) { newindices[0] = indices[0]; tempArray->setDataFloat32(newindices, gda->getDataFloat32(indices)); } newFile->addDataArray(tempArray); newFile->setDataArrayName(indices[1], "#" + AString::number(indices[1] + 1)); columnDataPointers.push_back(tempArray->getDataPointerFloat()); } delete giftiFile;//delete old 2D file giftiFile = newFile;//drop new 1D file in } } if (isLabelData) { CaretLogWarning("Metric File: " + this->getFileName() + " contains data array with NIFTI_INTENT_LABEL !!!"); } } /** * Get the number of nodes. * * @return * The number of nodes. */ int32_t MetricFile::getNumberOfNodes() const { int32_t numNodes = 0; int32_t numDataArrays = this->giftiFile->getNumberOfDataArrays(); if (numDataArrays > 0) { numNodes = this->giftiFile->getDataArray(0)->getNumberOfRows(); } return numNodes; } /** * Get the number of columns. * * @return * The number of columns. */ int32_t MetricFile::getNumberOfColumns() const { const int32_t numCols = this->giftiFile->getNumberOfDataArrays(); return numCols; } /** * Initialize members of this class. */ void MetricFile::initializeMembersMetricFile() { for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_chartingEnabledForTab[i] = false; } } /** * Helps copying files. * * @param sf * File that is copied. */ void MetricFile::copyHelperMetricFile(const MetricFile& mf) { this->validateDataArraysAfterReading(); for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_chartingEnabledForTab[i] = mf.m_chartingEnabledForTab[i]; } } /** * Get value for a node. * * @param nodeIndex * Node index. * @param columnIndex * Column index. * @return * Value at the given node and column indices. */ float MetricFile::getValue(const int32_t nodeIndex, const int32_t columnIndex) const { CaretAssertVectorIndex(this->columnDataPointers, columnIndex); CaretAssertMessage((nodeIndex >= 0) && (nodeIndex < this->getNumberOfNodes()), "Node Index out of range."); return this->columnDataPointers[columnIndex][nodeIndex]; } /** * set label key for a node. * * @param nodeIndex * Node index. * @param columnIndex * Column index. * param value * Value inserted at the given node and column indices. */ void MetricFile::setValue(const int32_t nodeIndex, const int32_t columnIndex, const float value) { CaretAssertVectorIndex(this->columnDataPointers, columnIndex); CaretAssertMessage((nodeIndex >= 0) && (nodeIndex < this->getNumberOfNodes()), "Node Index out of range."); this->columnDataPointers[columnIndex][nodeIndex] = value; setModified(); } const float* MetricFile::getValuePointerForColumn(const int32_t columnIndex) const { CaretAssertVectorIndex(this->columnDataPointers, columnIndex); return this->columnDataPointers[columnIndex]; } void MetricFile::setNumberOfNodesAndColumns(int32_t nodes, int32_t columns) { giftiFile->clearAndKeepMetadata(); columnDataPointers.clear(); std::vector dimensions; dimensions.push_back(nodes); for (int32_t i = 0; i < columns; ++i) { giftiFile->addDataArray(new GiftiDataArray(NiftiIntentEnum::NIFTI_INTENT_NORMAL, NiftiDataTypeEnum::NIFTI_TYPE_FLOAT32, dimensions, GiftiEncodingEnum::GZIP_BASE64_BINARY)); columnDataPointers.push_back(giftiFile->getDataArray(i)->getDataPointerFloat()); } setModified(); } /** * Add map(s) to this GIFTI file. * @param numberOfNodes * Number of nodes. If file is not empty, this value must * match the number of nodes that are in the file. * @param numberOfMaps * Number of maps to add. */ void MetricFile::addMaps(const int32_t numberOfNodes, const int32_t numberOfMaps) { if (numberOfNodes <= 0) { throw DataFileException(getFileName(), "When adding maps the number of nodes must be greater than zero"); } if (this->getNumberOfNodes() > 0) { if (numberOfNodes != this->getNumberOfNodes()) { throw DataFileException(getFileName(), "When adding maps the requested number of nodes is " + AString::number(numberOfNodes) + " but the file contains " + AString::number(this->getNumberOfNodes()) + " nodes."); } } if (numberOfMaps <= 0) { throw DataFileException(getFileName(), "When adding maps, the number of maps must be greater than zero."); } if ((this->getNumberOfNodes() > 0) && (this->getNumberOfMaps() > 0)) { std::vector dimensions; dimensions.push_back(numberOfNodes); for (int32_t i = 0; i < numberOfMaps; ++i) { this->giftiFile->addDataArray(new GiftiDataArray(NiftiIntentEnum::NIFTI_INTENT_NORMAL, NiftiDataTypeEnum::NIFTI_TYPE_FLOAT32, dimensions, GiftiEncodingEnum::GZIP_BASE64_BINARY)); const int32_t mapIndex = giftiFile->getNumberOfDataArrays() - 1; this->columnDataPointers.push_back(giftiFile->getDataArray(mapIndex)->getDataPointerFloat()); } } else { this->setNumberOfNodesAndColumns(numberOfNodes, numberOfMaps); } this->setModified(); } void MetricFile::setValuesForColumn(const int32_t columnIndex, const float* valuesIn) { CaretAssertVectorIndex(this->columnDataPointers, columnIndex); float* myColumn = columnDataPointers[columnIndex]; int numNodes = (int)getNumberOfNodes(); for (int i = 0; i < numNodes; ++i) { myColumn[i] = valuesIn[i]; } setModified(); } void MetricFile::initializeColumn(const int32_t columnIndex, const float& value) { CaretAssertVectorIndex(this->columnDataPointers, columnIndex); float* myColumn = columnDataPointers[columnIndex]; int numNodes = (int)getNumberOfNodes(); for (int i = 0; i < numNodes; ++i) { myColumn[i] = value; } setModified(); } /** * Get the minimum and maximum values from ALL maps in this file. * Note that not all files (due to size of file) are able to provide * the minimum and maximum values from the file. The return value * indicates success/failure. If the failure (false) is returned * the returned values are likely +/- the maximum float values. * * @param dataRangeMinimumOut * Minimum data value found. * @param dataRangeMaximumOut * Maximum data value found. * @return * True if the values are valid, else false. */ bool MetricFile::getDataRangeFromAllMaps(float& dataRangeMinimumOut, float& dataRangeMaximumOut) const { const int32_t numberOfMaps = getNumberOfMaps(); if (numberOfMaps > 0) { dataRangeMaximumOut = -std::numeric_limits::max(); dataRangeMinimumOut = std::numeric_limits::max(); for (int32_t i = 0; i < numberOfMaps; ++i) { GiftiDataArray* gda = this->giftiFile->getDataArray(i); float mapMin, mapMax; gda->getMinMaxValuesFloat(mapMin, mapMax); if (mapMin < dataRangeMinimumOut) { dataRangeMinimumOut = mapMin; } if (mapMax > dataRangeMaximumOut) { dataRangeMaximumOut = mapMax; } } } else { dataRangeMaximumOut = std::numeric_limits::max(); dataRangeMinimumOut = -dataRangeMaximumOut; } return true; } /** * @return Is charting enabled for this file? */ bool MetricFile::isLineSeriesChartingEnabled(const int32_t tabIndex) const { CaretAssertArrayIndex(m_chartingEnabledForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_chartingEnabledForTab[tabIndex]; } /** * @return Return true if the file's current state supports * charting data, else false. Typically a brainordinate file * is chartable if it contains more than one map. */ bool MetricFile::isLineSeriesChartingSupported() const { if (getNumberOfMaps() > 1) { return true; } return false; } /** * Set charting enabled for this file. * * @param enabled * New status for charting enabled. */ void MetricFile::setLineSeriesChartingEnabled(const int32_t tabIndex, const bool enabled) { CaretAssertArrayIndex(m_chartingEnabledForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_chartingEnabledForTab[tabIndex] = enabled; } /** * Get chart data types supported by the file. * * @param chartDataTypesOut * Chart types supported by this file. */ void MetricFile::getSupportedLineSeriesChartDataTypes(std::vector& chartDataTypesOut) const { helpGetSupportedLineSeriesChartDataTypes(chartDataTypesOut); } /** * Load charting data for the surface with the given structure and node index. * * @param structure * The surface's structure. * @param nodeIndex * Index of the node. * @return * Pointer to the chart data. If the data FAILED to load, * the returned pointer will be NULL. Caller takes ownership * of the pointer and must delete it when no longer needed. */ ChartDataCartesian* MetricFile::loadLineSeriesChartDataForSurfaceNode(const StructureEnum::Enum structure, const int32_t nodeIndex) { ChartDataCartesian* chartData = NULL; if (getStructure() == structure) { try { const int32_t numMaps = getNumberOfMaps(); std::vector data; for (int64_t iMap = 0; iMap < numMaps; iMap++) { data.push_back(getValue(nodeIndex, iMap)); } chartData = helpCreateCartesianChartData(data); ChartDataSource* dataSource = chartData->getChartDataSource(); dataSource->setSurfaceNode(getFileName(), StructureEnum::toName(structure), getNumberOfNodes(), nodeIndex); } catch (const DataFileException& dfe) { if (chartData != NULL) { delete chartData; chartData = NULL; } throw dfe; } } return chartData; } /** * Load average charting data for the surface with the given structure and node indices. * * @param structure * The surface's structure. * @param nodeIndices * Indices of the node. * @return * Pointer to the chart data. If the data FAILED to load, * the returned pointer will be NULL. Caller takes ownership * of the pointer and must delete it when no longer needed. */ ChartDataCartesian* MetricFile::loadAverageLineSeriesChartDataForSurfaceNodes(const StructureEnum::Enum structure, const std::vector& nodeIndices) { ChartDataCartesian* chartData = NULL; if (getStructure() == structure) { ChartDataCartesian* chartData = NULL; try { const int32_t numberOfNodeIndices = static_cast(nodeIndices.size()); const int32_t numberOfMaps = getNumberOfMaps(); if ((numberOfNodeIndices > 0) && (numberOfMaps > 0)) { std::vector dataSum(numberOfMaps, 0.0); for (int32_t iMap = 0; iMap < numberOfMaps; iMap++) { CaretAssertVectorIndex(dataSum, iMap); for (int32_t iNode = 0; iNode < numberOfNodeIndices; iNode++) { const int32_t nodeIndex = nodeIndices[iNode]; dataSum[iMap] += getValue(nodeIndex, iMap); } } std::vector data; for (int32_t iMap = 0; iMap < numberOfMaps; iMap++) { CaretAssertVectorIndex(dataSum, iMap); const float mapAverageValue = dataSum[iMap] / numberOfNodeIndices; data.push_back(mapAverageValue); } chartData = helpCreateCartesianChartData(data); ChartDataSource* dataSource = chartData->getChartDataSource(); dataSource->setSurfaceNodeAverage(getFileName(), StructureEnum::toName(structure), numberOfNodeIndices, nodeIndices); } } catch (const DataFileException& dfe) { if (chartData != NULL) { delete chartData; chartData = NULL; } throw dfe; } return chartData; } return chartData; } /** * Load charting data for the voxel enclosing the given coordinate. * * @param xyz * Coordinate of voxel. * @return * Pointer to the chart data. If the data FAILED to load, * the returned pointer will be NULL. Caller takes ownership * of the pointer and must delete it when no longer needed. */ ChartDataCartesian* MetricFile::loadLineSeriesChartDataForVoxelAtCoordinate(const float * /*xyz[3]*/) { ChartDataCartesian* chartData = NULL; //helpLoadChartDataForVoxelAtCoordinate(xyz); return chartData; } /** * Save file data from the scene. For subclasses that need to * save to a scene, this method should be overriden. sceneClass * will be valid and any scene data should be added to it. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass to which data members should be added. */ void MetricFile::saveFileDataToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass) { GiftiTypeFile::saveFileDataToScene(sceneAttributes, sceneClass); sceneClass->addBooleanArray("m_chartingEnabledForTab", m_chartingEnabledForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS); } /** * Restore file data from the scene. For subclasses that need to * restore from a scene, this method should be overridden. The scene class * will be valid and any scene data may be obtained from it. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass for the instance of a class that implements * this interface. Will NEVER be NULL. */ void MetricFile::restoreFileDataFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { GiftiTypeFile::restoreFileDataFromScene(sceneAttributes, sceneClass); const ScenePrimitiveArray* tabArray = sceneClass->getPrimitiveArray("m_chartingEnabledForTab"); if (tabArray != NULL) { sceneClass->getBooleanArrayValue("m_chartingEnabledForTab", m_chartingEnabledForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS); } else { for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_chartingEnabledForTab[i] = false; } } } workbench-1.1.1/src/Files/MetricFile.h000066400000000000000000000106061255417355300175360ustar00rootroot00000000000000 #ifndef __METRIC_FILE_H__ #define __METRIC_FILE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "ChartableLineSeriesBrainordinateInterface.h" #include "BrainConstants.h" #include "GiftiTypeFile.h" namespace caret { class GiftiDataArray; /** * \brief A Metric data file. */ class MetricFile : public GiftiTypeFile, public ChartableLineSeriesBrainordinateInterface { public: MetricFile(); MetricFile(const MetricFile& sf); MetricFile& operator=(const MetricFile& sf); virtual ~MetricFile(); virtual void clear(); virtual int32_t getNumberOfNodes() const; virtual int32_t getNumberOfColumns() const; virtual void setNumberOfNodesAndColumns(int32_t nodes, int32_t columns); virtual void addMaps(const int32_t numberOfNodes, const int32_t numberOfMaps); float getValue(const int32_t nodeIndex, const int32_t columnIndex) const; void setValue(const int32_t nodeIndex, const int32_t columnIndex, const float value); const float* getValuePointerForColumn(const int32_t columnIndex) const; void setValuesForColumn(const int32_t columnIndex, const float* valuesIn); void initializeColumn(const int32_t columnIndex, const float& value = 0.0f); virtual bool getDataRangeFromAllMaps(float& dataRangeMinimumOut, float& dataRangeMaximumOut) const; virtual bool isLineSeriesChartingEnabled(const int32_t tabIndex) const; virtual void setLineSeriesChartingEnabled(const int32_t tabIndex, const bool enabled); virtual bool isLineSeriesChartingSupported() const; virtual ChartDataCartesian* loadLineSeriesChartDataForSurfaceNode(const StructureEnum::Enum structure, const int32_t nodeIndex); virtual ChartDataCartesian* loadAverageLineSeriesChartDataForSurfaceNodes(const StructureEnum::Enum structure, const std::vector& nodeIndices); virtual ChartDataCartesian* loadLineSeriesChartDataForVoxelAtCoordinate(const float xyz[3]); virtual void getSupportedLineSeriesChartDataTypes(std::vector& chartDataTypesOut) const; protected: /** * Validate the contents of the file after it * has been read such as correct number of * data arrays and proper data types/dimensions. */ virtual void validateDataArraysAfterReading(); void copyHelperMetricFile(const MetricFile& sf); void initializeMembersMetricFile(); virtual void saveFileDataToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass); virtual void restoreFileDataFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private: /** Points to actual data in each Gifti Data Array */ std::vector columnDataPointers; bool m_chartingEnabledForTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; }; } // namespace #endif // __METRIC_FILE_H__ workbench-1.1.1/src/Files/MetricSmoothingObject.cxx000066400000000000000000001002061255417355300223240ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "MetricSmoothingObject.h" #include "CaretAssert.h" #include "CaretException.h" #include "SurfaceFile.h" #include "MetricFile.h" #include "GeodesicHelper.h" #include "TopologyHelper.h" #include "CaretOMP.h" #include using namespace std; using namespace caret; MetricSmoothingObject::MetricSmoothingObject(const SurfaceFile* mySurf, const float& kernel, const MetricFile* myRoi, Method myMethod, const float* nodeAreas) { CaretAssert(mySurf != NULL); if (myRoi != NULL && mySurf->getNumberOfNodes() != myRoi->getNumberOfNodes()) { throw CaretException("roi number of nodes doesn't match the surface"); } precomputeWeights(mySurf, kernel, myRoi, myMethod, nodeAreas); } void MetricSmoothingObject::smoothColumn(const MetricFile* metricIn, const int& whichColumn, MetricFile* columnOut, const MetricFile* roi, const bool& fixZeros) const { CaretAssert(metricIn != NULL); CaretAssert(columnOut != NULL); if (metricIn->getNumberOfNodes() != (int32_t)m_weightLists.size()) { throw CaretException("metric does not match surface number of nodes"); } if (whichColumn < -1 || whichColumn >= metricIn->getNumberOfColumns()) { throw CaretException("invalid column number"); } if (columnOut->getNumberOfNodes() != (int32_t)m_weightLists.size() || columnOut->getNumberOfColumns() != 1) { columnOut->setNumberOfNodesAndColumns(m_weightLists.size(), 1); } vector scratch(metricIn->getNumberOfNodes()); if (roi != NULL) { if (roi->getNumberOfNodes() != (int32_t)m_weightLists.size()) { throw CaretException("roi does not match surface number of nodes"); } smoothColumnInternal(scratch.data(), metricIn, whichColumn, columnOut, 0, roi, 0, fixZeros); } else { smoothColumnInternal(scratch.data(), metricIn, whichColumn, columnOut, 0, fixZeros); } } void MetricSmoothingObject::smoothColumn(const MetricFile* metricIn, const int& whichColumn, MetricFile* metricOut, const int& whichOutColumn, const MetricFile* roi, const int& whichRoiColumn, const bool& fixZeros) const { CaretAssert(metricIn != NULL); CaretAssert(metricOut != NULL); if (metricIn->getNumberOfNodes() != (int32_t)m_weightLists.size()) { throw CaretException("metric does not match surface number of nodes"); } if (metricOut->getNumberOfNodes() != (int32_t)m_weightLists.size()) { throw CaretException("output metric does not match surface number of nodes"); } if (roi != NULL && (roi->getNumberOfNodes() != (int32_t)m_weightLists.size())) { throw CaretException("roi does not match surface number of nodes"); } if (whichColumn < -1 || whichColumn >= metricIn->getNumberOfColumns()) { throw CaretException("invalid input column number"); } if (whichOutColumn < -1 || whichOutColumn >= metricOut->getNumberOfColumns()) { throw CaretException("invalid output column number"); } if (roi != NULL && (whichRoiColumn < -1 || whichRoiColumn >= roi->getNumberOfColumns())) { throw CaretException("invalid input column number"); } vector scratch(metricIn->getNumberOfNodes()); if (roi != NULL) { smoothColumnInternal(scratch.data(), metricIn, whichColumn, metricOut, whichOutColumn, roi, whichRoiColumn, fixZeros); } else { smoothColumnInternal(scratch.data(), metricIn, whichColumn, metricOut, whichOutColumn, fixZeros); } } void MetricSmoothingObject::smoothMetric(const MetricFile* metricIn, MetricFile* metricOut, const MetricFile* roi, const bool& fixZeros) const { CaretAssert(metricIn != NULL); CaretAssert(metricOut != NULL); int32_t numCols = metricIn->getNumberOfColumns(); if (metricIn->getNumberOfNodes() != (int32_t)m_weightLists.size()) { throw CaretException("metric does not match surface number of nodes"); } if (metricOut->getNumberOfNodes() != (int32_t)m_weightLists.size() || metricOut->getNumberOfColumns() != numCols) { metricOut->setNumberOfNodesAndColumns(m_weightLists.size(), numCols); } vector scratch(metricIn->getNumberOfNodes()); if (roi != NULL) { if (roi->getNumberOfNodes() != (int32_t)m_weightLists.size()) { throw CaretException("roi does not match surface number of nodes"); } for (int32_t i = 0; i < numCols; ++i) { smoothColumnInternal(scratch.data(), metricIn, i, metricOut, i, roi, 0, fixZeros); } } else { for (int32_t i = 0; i < numCols; ++i) { smoothColumnInternal(scratch.data(), metricIn, i, metricOut, i, fixZeros); } } } void MetricSmoothingObject::smoothColumnInternal(float* scratch, const MetricFile* metricIn, const int& whichColumn, MetricFile* metricOut, const int& whichOutColumn, const bool& fixZeros) const { CaretAssert(metricIn != NULL);//asserts only, and only basic checks, these functions are private CaretAssert(metricOut != NULL); CaretAssert(scratch != NULL); CaretAssert(whichColumn >= 0 && whichColumn < metricIn->getNumberOfColumns()); CaretAssert(whichOutColumn >= 0 && whichOutColumn < metricOut->getNumberOfColumns()); const float* myColumn = metricIn->getValuePointerForColumn(whichColumn); int32_t numNodes = metricIn->getNumberOfNodes(); if (fixZeros)//special case early to keep branching down { #pragma omp CARET_PARFOR schedule(dynamic) for (int32_t i = 0; i < numNodes; ++i) { const WeightList& myWeightRef = m_weightLists[i]; if (myWeightRef.m_weightSum != 0.0f)//skip nodes with no neighbors quickly { float sum = 0.0f, weightsum = 0.0f; int32_t numWeights = myWeightRef.m_nodes.size(); for (int32_t j = 0; j < numWeights; ++j) { float value = myColumn[myWeightRef.m_nodes[j]]; if (value != 0.0f) { float weight = myWeightRef.m_weights[j]; sum += weight * value; weightsum += weight; } } if (weightsum != 0.0f) { scratch[i] = sum / weightsum; } else { scratch[i] = 0.0f; } } else { scratch[i] = 0.0f;//but we do need to zero what we skip, so a list of nodes to check may not help } } } else { #pragma omp CARET_PARFOR schedule(dynamic) for (int32_t i = 0; i < numNodes; ++i) { const WeightList& myWeightRef = m_weightLists[i]; if (myWeightRef.m_weightSum != 0.0f) { float sum = 0.0f; int32_t numWeights = myWeightRef.m_nodes.size(); for (int32_t j = 0; j < numWeights; ++j) { sum += myWeightRef.m_weights[j] * myColumn[myWeightRef.m_nodes[j]]; } scratch[i] = sum / myWeightRef.m_weightSum; } else { scratch[i] = 0.0f; } } } metricOut->setValuesForColumn(whichOutColumn, scratch); } void MetricSmoothingObject::smoothColumnInternal(float* scratch, const MetricFile* metricIn, const int& whichColumn, MetricFile* metricOut, const int& whichOutColumn, const MetricFile* roi, const int& whichRoiColumn, const bool& fixZeros) const { CaretAssert(metricIn != NULL);//asserts only, and only basic checks, these functions are private CaretAssert(metricOut != NULL); CaretAssert(scratch != NULL); CaretAssert(roi != NULL); CaretAssert(whichColumn >= 0 && whichColumn < metricIn->getNumberOfColumns()); CaretAssert(whichOutColumn >= 0 && whichOutColumn < metricOut->getNumberOfColumns()); CaretAssert(whichRoiColumn >= 0 && whichRoiColumn < roi->getNumberOfColumns()); const float* myColumn = metricIn->getValuePointerForColumn(whichColumn); const float* roiColumn = roi->getValuePointerForColumn(whichRoiColumn); int32_t numNodes = metricIn->getNumberOfNodes(); if (fixZeros)//special case early to keep branching down { #pragma omp CARET_PARFOR schedule(dynamic) for (int32_t i = 0; i < numNodes; ++i) { const WeightList& myWeightRef = m_weightLists[i]; if (roiColumn[i] > 0.0f && myWeightRef.m_weightSum != 0.0f)//skip nodes with no neighbors quickly { float sum = 0.0f, weightsum = 0.0f; int32_t numWeights = myWeightRef.m_nodes.size(); for (int32_t j = 0; j < numWeights; ++j) { int32_t neighbor = myWeightRef.m_nodes[j]; float value = myColumn[neighbor]; if (roiColumn[neighbor] > 0.0f && value != 0.0f) { float weight = myWeightRef.m_weights[j]; sum += weight * value; weightsum += weight; } } if (weightsum != 0.0f) { scratch[i] = sum / weightsum; } else { scratch[i] = 0.0f; } } else { scratch[i] = 0.0f;//but we do need to zero what we skip, so a list of nodes to check may not help } } } else { #pragma omp CARET_PARFOR schedule(dynamic) for (int32_t i = 0; i < numNodes; ++i) { const WeightList& myWeightRef = m_weightLists[i]; if (roiColumn[i] > 0.0f && myWeightRef.m_weightSum != 0.0f) { float sum = 0.0f, weightsum = 0.0f; int32_t numWeights = myWeightRef.m_nodes.size(); for (int32_t j = 0; j < numWeights; ++j) { int32_t neighbor = myWeightRef.m_nodes[j]; if (roiColumn[neighbor] > 0.0f) { float weight = myWeightRef.m_weights[j]; sum += weight * myColumn[neighbor]; weightsum += weight; } } if (weightsum != 0.0f) { scratch[i] = sum / weightsum; } else { scratch[i] = 0.0f; } } else { scratch[i] = 0.0f; } } } metricOut->setValuesForColumn(whichOutColumn, scratch); } void MetricSmoothingObject::precomputeWeightsGeoGauss(const SurfaceFile* mySurf, float myKernel) { int32_t numNodes = mySurf->getNumberOfNodes(); float myGeoDist = myKernel * 3.0f; float gaussianDenom = -0.5f / myKernel / myKernel; m_weightLists.resize(numNodes); #pragma omp CARET_PAR { CaretPointer myTopoHelp = mySurf->getTopologyHelper();//don't really need one per thread here, but good practice in case we want getNeighborsToDepth CaretPointer myGeoHelp = mySurf->getGeodesicHelper(); vector distances; #pragma omp CARET_FOR schedule(dynamic) for (int32_t i = 0; i < numNodes; ++i) { myGeoHelp->getNodesToGeoDist(i, myGeoDist, m_weightLists[i].m_nodes, distances, true); if (distances.size() < 7) { m_weightLists[i].m_nodes = myTopoHelp->getNodeNeighbors(i); m_weightLists[i].m_nodes.push_back(i); myGeoHelp->getGeoToTheseNodes(i, m_weightLists[i].m_nodes, distances, true); } int32_t numNeigh = (int32_t)distances.size(); m_weightLists[i].m_weights.resize(numNeigh); m_weightLists[i].m_weightSum = 0.0f; for (int32_t j = 0; j < numNeigh; ++j) { float weight = exp(distances[j] * distances[j] * gaussianDenom);//exp(- dist ^ 2 / (2 * sigma ^ 2)) m_weightLists[i].m_weights[j] = weight; m_weightLists[i].m_weightSum += weight; } } } } void MetricSmoothingObject::precomputeWeightsROIGeoGauss(const SurfaceFile* mySurf, float myKernel, const MetricFile* theRoi) { int32_t numNodes = mySurf->getNumberOfNodes(); float myGeoDist = myKernel * 3.0f; float gaussianDenom = -0.5f / myKernel / myKernel; m_weightLists.resize(numNodes); const float* myRoiColumn = theRoi->getValuePointerForColumn(0); #pragma omp CARET_PAR { CaretPointer myTopoHelp = mySurf->getTopologyHelper(); CaretPointer myGeoHelp = mySurf->getGeodesicHelper(); vector distances; vector nodes; #pragma omp CARET_FOR schedule(dynamic) for (int32_t i = 0; i < numNodes; ++i) { if (myRoiColumn[i] > 0.0f) { myGeoHelp->getNodesToGeoDist(i, myGeoDist, nodes, distances, true); if (distances.size() < 7) { nodes = myTopoHelp->getNodeNeighbors(i); nodes.push_back(i); myGeoHelp->getGeoToTheseNodes(i, nodes, distances, true); } int32_t numNeigh = (int32_t)distances.size(); m_weightLists[i].m_weights.reserve(numNeigh); m_weightLists[i].m_nodes.reserve(numNeigh); m_weightLists[i].m_weightSum = 0.0f; for (int32_t j = 0; j < numNeigh; ++j) { if (myRoiColumn[nodes[j]] > 0.0f) { float weight = exp(distances[j] * distances[j] * gaussianDenom);//exp(- dist ^ 2 / (2 * sigma ^ 2)) m_weightLists[i].m_weights.push_back(weight); m_weightLists[i].m_nodes.push_back(nodes[j]); m_weightLists[i].m_weightSum += weight; } } } } } } void MetricSmoothingObject::precomputeWeightsGeoGaussArea(const SurfaceFile* mySurf, float myKernel, const float* nodeAreas) {//this method is normalized in two ways to provide evenly diffusing smoothing with equivalent sum of areas * values as input int32_t numNodes = mySurf->getNumberOfNodes(); float myGeoDist = myKernel * 3.0f; float gaussianDenom = -0.5f / myKernel / myKernel; vector tempList;//this is used to compute scattering kernels because it is easier to normalize scattering kernels correctly, and then convert to gathering kernels tempList.resize(numNodes); CaretPointer myGeoBase(new GeodesicHelperBase(mySurf, nodeAreas));//NOTE: if these are equal to the surface's areas, then it does some extra operations, but gets the same answer #pragma omp CARET_PAR { CaretPointer myTopoHelp = mySurf->getTopologyHelper();//don't really need one per thread here, but good practice in case we want getNeighborsToDepth CaretPointer myGeoHelp(new GeodesicHelper(myGeoBase)); vector distances; #pragma omp CARET_FOR schedule(dynamic) for (int32_t i = 0; i < numNodes; ++i) { myGeoHelp->getNodesToGeoDist(i, myGeoDist, tempList[i].m_nodes, distances, true); const vector& tempneighbors = myTopoHelp->getNodeNeighbors(i); if (distances.size() <= tempneighbors.size())//because neighbors doesn't include center, so if they are equal, geo is missing a neighbor { tempList[i].m_nodes = tempneighbors; tempList[i].m_nodes.push_back(i); myGeoHelp->getGeoToTheseNodes(i, tempList[i].m_nodes, distances, true); } int32_t numNeigh = (int32_t)distances.size(); tempList[i].m_weights.resize(numNeigh); tempList[i].m_weightSum = 0.0f; for (int32_t j = 0; j < numNeigh; ++j) { float weight = exp(distances[j] * distances[j] * gaussianDenom) * nodeAreas[tempList[i].m_nodes[j]];//exp(- dist ^ 2 / (2 * sigma ^ 2)) * area tempList[i].m_weights[j] = weight;//we multiply by area so that a node scattering to a dense region on one side and a sparse region on the other tempList[i].m_weightSum += weight;//gives similar areal influence to each direction rather than giving a more influence on the dense region (simply because nodes are more numerous) } float myFactor = nodeAreas[i] / tempList[i].m_weightSum;//make each scattering kernel sum to the area of the node it scatters from for (int32_t j = 0; j < numNeigh; ++j) { tempList[i].m_weights[j] *= myFactor; } tempList[i].m_weightSum = nodeAreas[i]; } } m_weightLists.resize(numNodes);//now convert it to gathering kernels for (int32_t i = 0; i < numNodes; ++i)//sadly, this is VERY hard to parallelize in a manner that is efficient, since it needs random access modification { m_weightLists[i].m_weightSum = 0.0f;//memory initialization may not go much faster in parallel size_t neighborCount = tempList[i].m_nodes.size(); m_weightLists[i].m_nodes.reserve(neighborCount);//also preallocate the expected number of nodes (geodesic distance should be symmetric except for rounding errors, so it should usually be exact) m_weightLists[i].m_weights.reserve(neighborCount); } for (int32_t i = 0; i < numNodes; ++i)//and this needs to push onto random vectors in the weight list { int32_t numNeigh = tempList[i].m_nodes.size(); for (int32_t j = 0; j < numNeigh; ++j) { int32_t node = tempList[i].m_nodes[j]; float weight = tempList[i].m_weights[j]; m_weightLists[node].m_nodes.push_back(i); m_weightLists[node].m_weights.push_back(weight); m_weightLists[node].m_weightSum += weight; } } } void MetricSmoothingObject::precomputeWeightsROIGeoGaussArea(const SurfaceFile* mySurf, float myKernel, const MetricFile* theRoi, const float* nodeAreas) { int32_t numNodes = mySurf->getNumberOfNodes(); float myGeoDist = myKernel * 3.0f; float gaussianDenom = -0.5f / myKernel / myKernel; vector tempList;//this is used to compute scattering kernels because it is easier to normalize scattering kernels correctly, and then convert to gathering kernels tempList.resize(numNodes); const float* myRoiColumn = theRoi->getValuePointerForColumn(0); CaretPointer myGeoBase(new GeodesicHelperBase(mySurf, nodeAreas));//NOTE: if these are equal to the surface's areas, then it does some extra operations, but gets the same answer #pragma omp CARET_PAR { CaretPointer myTopoHelp = mySurf->getTopologyHelper(); CaretPointer myGeoHelp(new GeodesicHelper(myGeoBase)); vector distances; vector nodes; #pragma omp CARET_FOR schedule(dynamic) for (int32_t i = 0; i < numNodes; ++i) { if (myRoiColumn[i] > 0.0f)//we don't need to scatter from things outside the ROI { myGeoHelp->getNodesToGeoDist(i, myGeoDist, nodes, distances, true); const vector& tempneighbors = myTopoHelp->getNodeNeighbors(i); if (distances.size() <= tempneighbors.size())//because neighbors doesn't include center, so if they are equal, geo is missing a neighbor { nodes = tempneighbors; nodes.push_back(i); myGeoHelp->getGeoToTheseNodes(i, nodes, distances, true); } int32_t numNeigh = (int32_t)distances.size(); tempList[i].m_weightSum = 0.0f; for (int32_t j = 0; j < numNeigh; ++j) {//but we DO need to compute scattering TO things outside the ROI, so that our normalization doesn't increase the in-ROI influence of edge nodes float weight = exp(distances[j] * distances[j] * gaussianDenom) * nodeAreas[nodes[j]];//exp(- dist ^ 2 / (2 * sigma ^ 2)) * area tempList[i].m_weightSum += weight;//add it to the total weight in order to normalize correctly if (myRoiColumn[nodes[j]] > 0.0f) {//BUT, don't add it to the list if it is outside the ROI tempList[i].m_nodes.push_back(nodes[j]); tempList[i].m_weights.push_back(weight); } } float myFactor = nodeAreas[i] / tempList[i].m_weightSum;//make each scattering kernel sum to the area of the node it scatters from int32_t numUsed = (int32_t)tempList[i].m_nodes.size(); for (int32_t j = 0; j < numUsed; ++j) { tempList[i].m_weights[j] *= myFactor; } tempList[i].m_weightSum = 0.0f;//this is never actually used again, but make sure it is wrong in case anything tries to use it } } } m_weightLists.resize(numNodes);//now convert it to gathering kernels for (int32_t i = 0; i < numNodes; ++i)//sadly, this is VERY hard to parallelize in a manner that is efficient, since it needs random access modification { m_weightLists[i].m_weightSum = 0.0f;//memory initialization may not go much faster in parallel size_t neighborCount = tempList[i].m_nodes.size(); m_weightLists[i].m_nodes.reserve(neighborCount);//also preallocate the expected number of nodes, again, should be exact except for rounding errors in geodesic distance m_weightLists[i].m_weights.reserve(neighborCount); } for (int32_t i = 0; i < numNodes; ++i)//and this needs to push onto random vectors in the weight list { int32_t numNeigh = tempList[i].m_nodes.size(); for (int32_t j = 0; j < numNeigh; ++j) { int32_t node = tempList[i].m_nodes[j]; float weight = tempList[i].m_weights[j]; m_weightLists[node].m_nodes.push_back(i); m_weightLists[node].m_weights.push_back(weight); m_weightLists[node].m_weightSum += weight; } } } void MetricSmoothingObject::precomputeWeightsGeoGaussEqual(const SurfaceFile* mySurf, float myKernel) {//this method is normalized in two ways to provide evenly diffusing smoothing with equivalent sum of values as input - this special purpose smoothing is for things that should not be integrated across the surface int32_t numNodes = mySurf->getNumberOfNodes(); float myGeoDist = myKernel * 3.0f; float gaussianDenom = -0.5f / myKernel / myKernel; vector tempList;//this is used to compute scattering kernels because it is easier to normalize scattering kernels correctly, and then convert to gathering kernels tempList.resize(numNodes); #pragma omp CARET_PAR { CaretPointer myTopoHelp = mySurf->getTopologyHelper();//don't really need one per thread here, but good practice in case we want getNeighborsToDepth CaretPointer myGeoHelp = mySurf->getGeodesicHelper(); vector distances; #pragma omp CARET_FOR schedule(dynamic) for (int32_t i = 0; i < numNodes; ++i) { myGeoHelp->getNodesToGeoDist(i, myGeoDist, tempList[i].m_nodes, distances, true); const vector& tempneighbors = myTopoHelp->getNodeNeighbors(i); if (distances.size() <= tempneighbors.size())//because neighbors doesn't include center, so if they are equal, geo is missing a neighbor { tempList[i].m_nodes = tempneighbors; tempList[i].m_nodes.push_back(i); myGeoHelp->getGeoToTheseNodes(i, tempList[i].m_nodes, distances, true); } int32_t numNeigh = (int32_t)distances.size(); tempList[i].m_weights.resize(numNeigh); tempList[i].m_weightSum = 0.0f; for (int32_t j = 0; j < numNeigh; ++j) { float weight = exp(distances[j] * distances[j] * gaussianDenom);//exp(- dist ^ 2 / (2 * sigma ^ 2)) tempList[i].m_weights[j] = weight;//we multiply by area so that a node scattering to a dense region on one side and a sparse region on the other tempList[i].m_weightSum += weight;//gives similar areal influence to each direction rather than giving a more influence on the dense region (simply because nodes are more numerous) } float myFactor = 1.0f / tempList[i].m_weightSum;//make each scattering kernel sum to 1 for (int32_t j = 0; j < numNeigh; ++j) { tempList[i].m_weights[j] *= myFactor; } tempList[i].m_weightSum = 1.0f; } } m_weightLists.resize(numNodes);//now convert it to gathering kernels for (int32_t i = 0; i < numNodes; ++i)//sadly, this is VERY hard to parallelize in a manner that is efficient, since it needs random access modification { m_weightLists[i].m_weightSum = 0.0f;//memory initialization may not go much faster in parallel size_t neighborCount = tempList[i].m_nodes.size(); m_weightLists[i].m_nodes.reserve(neighborCount);//also preallocate the expected number of nodes (geodesic distance should be symmetric except for rounding errors, so it should usually be exact) m_weightLists[i].m_weights.reserve(neighborCount); } for (int32_t i = 0; i < numNodes; ++i)//and this needs to push onto random vectors in the weight list { int32_t numNeigh = tempList[i].m_nodes.size(); for (int32_t j = 0; j < numNeigh; ++j) { int32_t node = tempList[i].m_nodes[j]; float weight = tempList[i].m_weights[j]; m_weightLists[node].m_nodes.push_back(i); m_weightLists[node].m_weights.push_back(weight); m_weightLists[node].m_weightSum += weight; } } } void MetricSmoothingObject::precomputeWeightsROIGeoGaussEqual(const SurfaceFile* mySurf, float myKernel, const MetricFile* theRoi) { int32_t numNodes = mySurf->getNumberOfNodes(); float myGeoDist = myKernel * 3.0f; float gaussianDenom = -0.5f / myKernel / myKernel; vector tempList;//this is used to compute scattering kernels because it is easier to normalize scattering kernels correctly, and then convert to gathering kernels tempList.resize(numNodes); const float* myRoiColumn = theRoi->getValuePointerForColumn(0); #pragma omp CARET_PAR { CaretPointer myTopoHelp = mySurf->getTopologyHelper(); CaretPointer myGeoHelp = mySurf->getGeodesicHelper(); vector distances; vector nodes; #pragma omp CARET_FOR schedule(dynamic) for (int32_t i = 0; i < numNodes; ++i) { if (myRoiColumn[i] > 0.0f)//we don't need to scatter from things outside the ROI { myGeoHelp->getNodesToGeoDist(i, myGeoDist, nodes, distances, true); const vector& tempneighbors = myTopoHelp->getNodeNeighbors(i); if (distances.size() <= tempneighbors.size())//because neighbors doesn't include center, so if they are equal, geo is missing a neighbor { nodes = tempneighbors; nodes.push_back(i); myGeoHelp->getGeoToTheseNodes(i, nodes, distances, true); } int32_t numNeigh = (int32_t)distances.size(); tempList[i].m_weightSum = 0.0f; for (int32_t j = 0; j < numNeigh; ++j) {//but we DO need to compute scattering TO things outside the ROI, so that our normalization doesn't increase the in-ROI influence of edge nodes float weight = exp(distances[j] * distances[j] * gaussianDenom);//exp(- dist ^ 2 / (2 * sigma ^ 2)) tempList[i].m_weightSum += weight;//add it to the total weight in order to normalize correctly if (myRoiColumn[nodes[j]] > 0.0f) {//BUT, don't add it to the list if it is outside the ROI tempList[i].m_nodes.push_back(nodes[j]); tempList[i].m_weights.push_back(weight); } } float myFactor = 1.0f / tempList[i].m_weightSum;//make each scattering kernel sum to 1 int32_t numUsed = (int32_t)tempList[i].m_nodes.size(); for (int32_t j = 0; j < numUsed; ++j) { tempList[i].m_weights[j] *= myFactor; } tempList[i].m_weightSum = 0.0f;//this is never actually used again, but make sure it is wrong in case anything tries to use it } } } m_weightLists.resize(numNodes);//now convert it to gathering kernels for (int32_t i = 0; i < numNodes; ++i)//sadly, this is VERY hard to parallelize in a manner that is efficient, since it needs random access modification { m_weightLists[i].m_weightSum = 0.0f;//memory initialization may not go much faster in parallel size_t neighborCount = tempList[i].m_nodes.size(); m_weightLists[i].m_nodes.reserve(neighborCount);//also preallocate the expected number of nodes, again, should be exact except for rounding errors in geodesic distance m_weightLists[i].m_weights.reserve(neighborCount); } for (int32_t i = 0; i < numNodes; ++i)//and this needs to push onto random vectors in the weight list { int32_t numNeigh = tempList[i].m_nodes.size(); for (int32_t j = 0; j < numNeigh; ++j) { int32_t node = tempList[i].m_nodes[j]; float weight = tempList[i].m_weights[j]; m_weightLists[node].m_nodes.push_back(i); m_weightLists[node].m_weights.push_back(weight); m_weightLists[node].m_weightSum += weight; } } } void MetricSmoothingObject::precomputeWeights(const SurfaceFile* mySurf, float myKernel, const MetricFile* theRoi, Method myMethod, const float* nodeAreas) { const float* passAreas = nodeAreas; vector areasTemp; switch (myMethod) { case GEO_GAUSS_AREA://currently, only this method needs them if (passAreas == NULL) { mySurf->computeNodeAreas(areasTemp); passAreas = areasTemp.data(); } break; default: break; } if (theRoi != NULL) { switch (myMethod) { case GEO_GAUSS_AREA: precomputeWeightsROIGeoGaussArea(mySurf, myKernel, theRoi, passAreas); break; case GEO_GAUSS_EQUAL: precomputeWeightsROIGeoGaussEqual(mySurf, myKernel, theRoi); break; case GEO_GAUSS: precomputeWeightsROIGeoGauss(mySurf, myKernel, theRoi); break; default: throw CaretException("unknown smoothing method specified"); }; } else { switch (myMethod) { case GEO_GAUSS_AREA: precomputeWeightsGeoGaussArea(mySurf, myKernel, passAreas); break; case GEO_GAUSS_EQUAL: precomputeWeightsGeoGaussEqual(mySurf, myKernel); break; case GEO_GAUSS: precomputeWeightsGeoGauss(mySurf, myKernel); break; default: throw CaretException("unknown smoothing method specified"); }; } } workbench-1.1.1/src/Files/MetricSmoothingObject.h000066400000000000000000000104451255417355300217560ustar00rootroot00000000000000#ifndef __METRIC_SMOOTHING_OBJECT_H__ #define __METRIC_SMOOTHING_OBJECT_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ //NOTE: this is largely for special-purpose smoothing by providing greater efficiency and flexibility, since it can be reused with different rois or on different metric objects // after being constructed once (constructor takes a while). If you just want to smooth one metric object with one (or no) ROI, you probably want AlgorithmMetricSmoothing. // //NOTE: this object contains no mutable members, multiple threads can call the same function on the same instance and expect consistent behavior, while running concurrently, // as long as they don't call it with output arguments that overlap (same instance, same row, or one row plus full metric, etc) // //NOTE: for a static ROI, it is (sometimes much) more efficient to use it in the constructor, and provide no ROI (NULL) to the functions, using both an ROI in constructor and in method // will result in the effective ROI being the logical AND of the two (intersection). #include "stdint.h" #include "stddef.h" #include namespace caret { class SurfaceFile; class MetricFile; class MetricSmoothingObject { public: enum Method { GEO_GAUSS_AREA, GEO_GAUSS_EQUAL, GEO_GAUSS }; MetricSmoothingObject(const SurfaceFile* mySurf, const float& kernel, const MetricFile* myRoi = NULL, Method myMethod = GEO_GAUSS_AREA, const float* nodeAreas = NULL); void smoothColumn(const MetricFile* metricIn, const int& whichColumn, MetricFile* columnOut, const MetricFile* roi = NULL, const bool& fixZeros = false) const; void smoothColumn(const MetricFile* metricIn, const int& whichColumn, MetricFile* metricOut, const int& whichOutColumn, const MetricFile* roi = NULL, const int& whichRoiColumn = 0, const bool& fixZeros = false) const; void smoothMetric(const MetricFile* metricIn, MetricFile* metricOut, const MetricFile* roi = NULL, const bool& fixZeros = false) const; private: struct WeightList { std::vector m_nodes; std::vector m_weights; float m_weightSum; }; std::vector m_weightLists; void smoothColumnInternal(float* scratch, const MetricFile* metricIn, const int& whichColumn, MetricFile* metricOut, const int& whichOutColumn, const bool& fixZeros) const; void smoothColumnInternal(float* scratch, const MetricFile* metricIn, const int& whichColumn, MetricFile* metricOut, const int& whichOutColumn, const MetricFile* roi, const int& whichRoiColumn, const bool& fixZeros) const; void precomputeWeights(const SurfaceFile* mySurf, float myKernel, const MetricFile* theRoi, Method myMethod, const float* nodeAreas); void precomputeWeightsGeoGauss(const SurfaceFile* mySurf, float myKernel); void precomputeWeightsROIGeoGauss(const SurfaceFile* mySurf, float myKernel, const MetricFile* theRoi); void precomputeWeightsGeoGaussArea(const SurfaceFile* mySurf, float myKernel, const float* nodeAreas); void precomputeWeightsROIGeoGaussArea(const SurfaceFile* mySurf, float myKernel, const MetricFile* theRoi, const float* nodeAreas); void precomputeWeightsGeoGaussEqual(const SurfaceFile* mySurf, float myKernel); void precomputeWeightsROIGeoGaussEqual(const SurfaceFile* mySurf, float myKernel, const MetricFile* theRoi); MetricSmoothingObject(); }; } #endif //__METRIC_SMOOTHING_OBJECT_H__ workbench-1.1.1/src/Files/NodeAndVoxelColoring.cxx000066400000000000000000000771461255417355300221250ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include //#include //#include //#include #define __NODE_AND_VOXEL_COLORING_DECLARE__ #include "NodeAndVoxelColoring.h" #undef __NODE_AND_VOXEL_COLORING_DECLARE__ #include "CaretAssert.h" #include "CaretLogger.h" #include "CaretOMP.h" #include "GiftiLabel.h" #include "GiftiLabelTable.h" #include "GroupAndNameHierarchyItem.h" #include "Palette.h" #include "PaletteColorMapping.h" #include "CaretOMP.h" using namespace caret; static const float positiveThresholdGreenColor[4] = { 115.0f / 255.0f, 255.0f / 255.0f, 180.0f / 255.0f, 255.0f / 255.0f }; static const float negativeThresholdGreenColor[] = { 180.0f / 255.0f, 255.0f / 255.0f, 115.0f / 255.0f, 255.0f / 255.0f }; /** * \class NodeAndVoxelColoring * \brief Static methods for coloring nodes and voxels. * * Provides methods for coloring nodes and voxels. */ /** * Color scalars using a palette that accepts a void* type for the * color array to that multiple data types are supported without * having to allocate memory for conversion to one data type or * the other nor duplicate lots of code for each data type. * * @param statistics * Descriptive statistics for min/max values. * @param paletteColorMapping * Specifies mapping of scalars to palette colors. * @param palette * Color palette used to map scalars to colors. * @param scalarValues * Scalars that are used to color the values. * Number of elements is 'numberOfScalars'. * @param thresholdValues * Thresholds for inhibiting coloring. * Number of elements is 'numberOfScalars'. * @param numberOfScalars * Number of scalars and thresholds. * @param colorDataType * Data type of the rgbaOut parameter * @param rgbaOutPointer * RGBA Colors that are output. This is a VOID type and its * true type is provided by the previous parameter colorDataType. * @param ignoreThresholding * If true, skip all threshold testing */ void NodeAndVoxelColoring::colorScalarsWithPalettePrivate(const FastStatistics* statistics, const PaletteColorMapping* paletteColorMapping, const Palette* palette, const float* scalarValues, const float* thresholdValues, const int64_t numberOfScalars, const ColorDataType colorDataType, void* rgbaOutPointer, const bool ignoreThresholding) { if (numberOfScalars <= 0) { return; } CaretAssert(statistics); CaretAssert(paletteColorMapping); CaretAssert(palette); CaretAssert(scalarValues); CaretAssert(thresholdValues); CaretAssert(rgbaOutPointer); /* * Cast to data type for rgba coloring */ float* rgbaFloat = NULL; uint8_t* rgbaUnsignedByte = NULL; switch (colorDataType) { case COLOR_TYPE_FLOAT: rgbaFloat = (float*)rgbaOutPointer; break; case COLOR_TYPE_UNSIGNED_BTYE: rgbaUnsignedByte = (uint8_t*)rgbaOutPointer; break; } /* * Type of threshold testing */ bool showOutsideFlag = false; const PaletteThresholdTestEnum::Enum thresholdTest = paletteColorMapping->getThresholdTest(); switch (thresholdTest) { case PaletteThresholdTestEnum::THRESHOLD_TEST_SHOW_OUTSIDE: showOutsideFlag = true; break; case PaletteThresholdTestEnum::THRESHOLD_TEST_SHOW_INSIDE: showOutsideFlag = false; break; } /* * Range of values allowed by thresholding */ const PaletteThresholdTypeEnum::Enum thresholdType = paletteColorMapping->getThresholdType(); const float thresholdMinimum = paletteColorMapping->getThresholdMinimum(thresholdType); const float thresholdMaximum = paletteColorMapping->getThresholdMaximum(thresholdType); const float thresholdMappedPositive = paletteColorMapping->getThresholdMappedMaximum(); const float thresholdMappedPositiveAverageArea = paletteColorMapping->getThresholdMappedAverageAreaMaximum(); const float thresholdMappedNegative = paletteColorMapping->getThresholdMappedMinimum(); const float thresholdMappedNegativeAverageArea = paletteColorMapping->getThresholdMappedAverageAreaMinimum(); const bool showMappedThresholdFailuresInGreen = paletteColorMapping->isShowThresholdFailureInGreen(); /* * Skip threshold testing? */ const bool skipThresholdTesting = (ignoreThresholding || (thresholdType == PaletteThresholdTypeEnum::THRESHOLD_TYPE_OFF)); /* * Display of negative, zero, and positive values allowed. */ const bool hidePositiveValues = (paletteColorMapping->isDisplayPositiveDataFlag() == false); const bool hideNegativeValues = (paletteColorMapping->isDisplayNegativeDataFlag() == false); const bool hideZeroValues = (paletteColorMapping->isDisplayZeroDataFlag() == false); const bool interpolateFlag = paletteColorMapping->isInterpolatePaletteFlag(); /* * Convert data values to normalized palette values. */ std::vector normalizedValues(numberOfScalars); paletteColorMapping->mapDataToPaletteNormalizedValues(statistics, scalarValues, &normalizedValues[0], numberOfScalars); /* * Get color for normalized values of -1.0 and 1.0. * Since there may be a large number of values that are -1.0 or 1.0 * we can compute the color only once for these values and save time. */ float rgbaPositiveOne[4], rgbaNegativeOne[4]; palette->getPaletteColor(1.0, interpolateFlag, rgbaPositiveOne); const bool rgbaPositiveOneValid = (rgbaPositiveOne[3] > 0.0); palette->getPaletteColor(-1.0, interpolateFlag, rgbaNegativeOne); const bool rgbaNegativeOneValid = (rgbaNegativeOne[3] > 0.0); /* * Color all scalars. */ #pragma omp CARET_FOR for (int64_t i = 0; i < numberOfScalars; i++) { const int64_t i4 = i * 4; /* * Initialize coloring for node since one of the * continue statements below may cause moving * on to next node */ switch (colorDataType) { case COLOR_TYPE_FLOAT: rgbaFloat[i4] = 0.0; rgbaFloat[i4+1] = 0.0; rgbaFloat[i4+2] = 0.0; rgbaFloat[i4+3] = 0.0; break; case COLOR_TYPE_UNSIGNED_BTYE: rgbaUnsignedByte[i4] = 0; rgbaUnsignedByte[i4+1] = 0; rgbaUnsignedByte[i4+2] = 0; rgbaUnsignedByte[i4+3] = 0; break; } float scalar = scalarValues[i]; const float threshold = thresholdValues[i]; /* * Positive/Zero/Negative Test */ if (scalar > PaletteColorMapping::SMALL_POSITIVE) { // JWH 24 April 2015 NodeAndVoxelColoring::SMALL_POSITIVE) { if (hidePositiveValues) { continue; } } else if (scalar < PaletteColorMapping::SMALL_NEGATIVE) { // JWH 24 April 2015 NodeAndVoxelColoring::SMALL_NEGATIVE) { if (hideNegativeValues) { continue; } } else { /* * May be very near zero so force to zero. */ normalizedValues[i] = 0.0; if (hideZeroValues) { continue; } } /* * Temporary for rgba coloring now that past possible * continue statements */ float rgbaOut[4] = { 0.0, 0.0, 0.0, 0.0 }; const float normalValue = normalizedValues[i]; /* * RGBA colors have been mapped for extreme values */ if (normalValue >= 1.0) { if (rgbaPositiveOneValid) { rgbaOut[0] = rgbaPositiveOne[0]; rgbaOut[1] = rgbaPositiveOne[1]; rgbaOut[2] = rgbaPositiveOne[2]; rgbaOut[3] = rgbaPositiveOne[3]; } } else if (normalValue <= -1.0) { if (rgbaNegativeOneValid) { rgbaOut[0] = rgbaNegativeOne[0]; rgbaOut[1] = rgbaNegativeOne[1]; rgbaOut[2] = rgbaNegativeOne[2]; rgbaOut[3] = rgbaNegativeOne[3]; } } else { /* * Color scalar using palette */ float rgba[4]; palette->getPaletteColor(normalValue, interpolateFlag, rgba); if (rgba[3] > 0.0f) { rgbaOut[0] = rgba[0]; rgbaOut[1] = rgba[1]; rgbaOut[2] = rgba[2]; rgbaOut[3] = rgba[3]; } } /* * Threshold Test * Threshold is done last so colors are still set * but if threshold test fails, alpha is set invalid. */ bool thresholdPassedFlag = false; if (skipThresholdTesting) { thresholdPassedFlag = true; } else if (showOutsideFlag) { if (threshold > thresholdMaximum) { thresholdPassedFlag = true; } else if (threshold < thresholdMinimum) { thresholdPassedFlag = true; } } else { if ((threshold >= thresholdMinimum) && (threshold <= thresholdMaximum)) { thresholdPassedFlag = true; } } if (thresholdPassedFlag == false) { rgbaOut[3] = 0.0; if (showMappedThresholdFailuresInGreen) { if (thresholdType == PaletteThresholdTypeEnum::THRESHOLD_TYPE_MAPPED) { if (threshold > 0.0f) { if ((threshold < thresholdMappedPositive) && (threshold > thresholdMappedPositiveAverageArea)) { rgbaOut[0] = positiveThresholdGreenColor[0]; rgbaOut[1] = positiveThresholdGreenColor[1]; rgbaOut[2] = positiveThresholdGreenColor[2]; rgbaOut[3] = positiveThresholdGreenColor[3]; } } else if (threshold < 0.0f) { if ((threshold > thresholdMappedNegative) && (threshold < thresholdMappedNegativeAverageArea)) { rgbaOut[0] = negativeThresholdGreenColor[0]; rgbaOut[1] = negativeThresholdGreenColor[1]; rgbaOut[2] = negativeThresholdGreenColor[2]; rgbaOut[3] = negativeThresholdGreenColor[3]; } } } } } switch (colorDataType) { case COLOR_TYPE_FLOAT: CaretAssertArrayIndex(rgbaFloat, numberOfScalars * 4, i*4+3); rgbaFloat[i4] = rgbaOut[0]; rgbaFloat[i4+1] = rgbaOut[1]; rgbaFloat[i4+2] = rgbaOut[2]; rgbaFloat[i4+3] = rgbaOut[3]; break; case COLOR_TYPE_UNSIGNED_BTYE: CaretAssertArrayIndex(rgbaUnsignedByte, numberOfScalars * 4, i*4+3); rgbaUnsignedByte[i4] = rgbaOut[0] * 255.0; rgbaUnsignedByte[i4+1] = rgbaOut[1] * 255.0; rgbaUnsignedByte[i4+2] = rgbaOut[2] * 255.0; if (rgbaOut[3] > 0.0) { rgbaUnsignedByte[i4+3] = rgbaOut[3] * 255.0; } else { rgbaUnsignedByte[i4+3] = 0; } break; } } } /** * Color scalars using a palette. * * @param statistics * Descriptive statistics for min/max values. * @param paletteColorMapping * Specifies mapping of scalars to palette colors. * @param palette * Color palette used to map scalars to colors. * @param scalarValues * Scalars that are used to color the values. * Number of elements is 'numberOfScalars'. * @param thresholdValues * Thresholds for inhibiting coloring. * Number of elements is 'numberOfScalars'. * @param numberOfScalars * Number of scalars and thresholds. * @param rgbaOut * RGBA Colors that are output. The alpha * value will be negative if the scalar does * not receive any coloring. * Number of elements is 'numberOfScalars' * 4. * @param ignoreThresholding * If true, skip all threshold testing */ void NodeAndVoxelColoring::colorScalarsWithPalette(const FastStatistics* statistics, const PaletteColorMapping* paletteColorMapping, const Palette* palette, const float* scalarValues, const float* thresholdValues, const int64_t numberOfScalars, float* rgbaOut, const bool ignoreThresholding) { colorScalarsWithPalettePrivate(statistics, paletteColorMapping, palette, scalarValues, thresholdValues, numberOfScalars, COLOR_TYPE_FLOAT, (void*)rgbaOut, ignoreThresholding); } /** * Color scalars using a palette. * * @param statistics * Descriptive statistics for min/max values. * @param paletteColorMapping * Specifies mapping of scalars to palette colors. * @param palette * Color palette used to map scalars to colors. * @param scalarValues * Scalars that are used to color the values. * Number of elements is 'numberOfScalars'. * @param thresholdValues * Thresholds for inhibiting coloring. * Number of elements is 'numberOfScalars'. * @param numberOfScalars * Number of scalars and thresholds. * @param rgbaOut * RGBA Colors that are output. The alpha * value will be negative if the scalar does * not receive any coloring. * Number of elements is 'numberOfScalars' * 4. * @param ignoreThresholding * If true, skip all threshold testing */ void NodeAndVoxelColoring::colorScalarsWithPalette(const FastStatistics* statistics, const PaletteColorMapping* paletteColorMapping, const Palette* palette, const float* scalarValues, const float* thresholdValues, const int64_t numberOfScalars, uint8_t* rgbaOut, const bool ignoreThresholding) { colorScalarsWithPalettePrivate(statistics, paletteColorMapping, palette, scalarValues, thresholdValues, numberOfScalars, COLOR_TYPE_UNSIGNED_BTYE, (void*)rgbaOut, ignoreThresholding); } /** * Assign colors to label indices using a GIFTI label table. * * @param labelTabl * Label table used for coloring and indexing with label indices. * @param labelIndices * The indices are are used to access colors in the label table. * @param numberOfIndices * Number of indices. * @param displayGroup * The selected display group. * @param tabIndex * Index of selected tab. * @param rgbv * Output with assigned colors. Number of elements is (numberOfIndices * 4). */ void NodeAndVoxelColoring::colorIndicesWithLabelTableForDisplayGroupTab(const GiftiLabelTable* labelTable, const float* labelIndices, const int64_t numberOfIndices, const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, float* rgbv) { NodeAndVoxelColoring::colorIndicesWithLabelTableForDisplayGroupTabPrivate(labelTable, labelIndices, numberOfIndices, displayGroup, tabIndex, COLOR_TYPE_FLOAT, (void*)rgbv); } /** * Assign colors to label indices using a GIFTI label table. * * @param labelTabl * Label table used for coloring and indexing with label indices. * @param labelIndices * The indices are are used to access colors in the label table. * @param numberOfIndices * Number of indices. * @param displayGroup * The selected display group. * @param tabIndex * Index of selected tab. * @param rgbv * Output with assigned colors. Number of elements is (numberOfIndices * 4). */ void NodeAndVoxelColoring::colorIndicesWithLabelTableForDisplayGroupTab(const GiftiLabelTable* labelTable, const float* labelIndices, const int64_t numberOfIndices, const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, uint8_t* rgbv) { NodeAndVoxelColoring::colorIndicesWithLabelTableForDisplayGroupTabPrivate(labelTable, labelIndices, numberOfIndices, displayGroup, tabIndex, COLOR_TYPE_UNSIGNED_BTYE, (void*)rgbv); } /** * Assign colors to label indices using a GIFTI label table. * * @param labelTabl * Label table used for coloring and indexing with label indices. * @param labelIndices * The indices are are used to access colors in the label table. * @param numberOfIndices * Number of indices. * @param displayGroup * The selected display group. * @param tabIndex * Index of selected tab. * @param colorDataType * Data type of the rgbaOut parameter * @param rgbaOutPointer * RGBA Colors that are output. This is a VOID type and its * true type is provided by the previous parameter colorDataType. * @param rgbv * Output with assigned colors. Number of elements is (numberOfIndices * 4). */ void NodeAndVoxelColoring::colorIndicesWithLabelTableForDisplayGroupTabPrivate(const GiftiLabelTable* labelTable, const float* labelIndices, const int64_t numberOfIndices, const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const ColorDataType colorDataType, void* rgbaOutPointer) { /* * Cast to data type for rgba coloring */ float* rgbaFloat = NULL; uint8_t* rgbaUnsignedByte = NULL; switch (colorDataType) { case COLOR_TYPE_FLOAT: rgbaFloat = (float*)rgbaOutPointer; break; case COLOR_TYPE_UNSIGNED_BTYE: rgbaUnsignedByte = (uint8_t*)rgbaOutPointer; break; } /* * Invalidate all coloring. */ switch (colorDataType) { case COLOR_TYPE_FLOAT: for (int64_t i = 0; i < numberOfIndices; i++) { rgbaFloat[i*4+3] = 0.0; } break; case COLOR_TYPE_UNSIGNED_BTYE: for (int64_t i = 0; i < numberOfIndices; i++) { rgbaUnsignedByte[i*4+3] = 0; } break; } /* * Assign colors from labels to nodes */ float labelRGBA[4]; for (int64_t i = 0; i < numberOfIndices; i++) { const int64_t labelKey = static_cast(labelIndices[i]); const GiftiLabel* gl = labelTable->getLabel(labelKey); if (gl != NULL) { const GroupAndNameHierarchyItem* item = gl->getGroupNameSelectionItem(); bool colorDataFlag = false; if (item != NULL) { if (tabIndex == NodeAndVoxelColoring::INVALID_TAB_INDEX) { colorDataFlag = true; } else if (item->isSelected(displayGroup, tabIndex)) { colorDataFlag = true; } } else { colorDataFlag = true; } if (colorDataFlag) { gl->getColor(labelRGBA); if (labelRGBA[3] > 0.0) { const int64_t i4 = i * 4; switch (colorDataType) { case COLOR_TYPE_FLOAT: CaretAssertArrayIndex(rgbaFloat, numberOfIndices * 4, i*4+3); rgbaFloat[i*4] = labelRGBA[0]; rgbaFloat[i*4+1] = labelRGBA[1]; rgbaFloat[i*4+2] = labelRGBA[2]; rgbaFloat[i*4+3] = labelRGBA[3]; break; case COLOR_TYPE_UNSIGNED_BTYE: CaretAssertArrayIndex(rgbaUnsignedByte, numberOfIndices * 4, i*4+3); rgbaUnsignedByte[i4] = labelRGBA[0] * 255.0; rgbaUnsignedByte[i4+1] = labelRGBA[1] * 255.0; rgbaUnsignedByte[i4+2] = labelRGBA[2] * 255.0; if (labelRGBA[3] > 0.0) { rgbaUnsignedByte[i4+3] = labelRGBA[3] * 255.0; } else { rgbaUnsignedByte[i4+3] = 0; } break; } } } } } } /** * Assign colors to label indices using a GIFTI label table. * * @param labelTabl * Label table used for coloring and indexing with label indices. * @param labelIndices * The indices are are used to access colors in the label table. * @param numberOfIndices * Number of indices. * @param displayGroup * The selected display group. * @param tabIndex * Index of selected tab. * @param rgbv * Output with assigned colors. Number of elements is (numberOfIndices * 4). */ void NodeAndVoxelColoring::colorIndicesWithLabelTable(const GiftiLabelTable* labelTable, const float* labelIndices, const int64_t numberOfIndices, float* rgbv) { NodeAndVoxelColoring::colorIndicesWithLabelTableForDisplayGroupTabPrivate(labelTable, labelIndices, numberOfIndices, DisplayGroupEnum::DISPLAY_GROUP_TAB, NodeAndVoxelColoring::INVALID_TAB_INDEX, COLOR_TYPE_FLOAT, (void*)rgbv); } /** * Assign colors to label indices using a GIFTI label table. * * @param labelTabl * Label table used for coloring and indexing with label indices. * @param labelIndices * The indices are are used to access colors in the label table. * @param numberOfIndices * Number of indices. * @param displayGroup * The selected display group. * @param tabIndex * Index of selected tab. * @param rgbv * Output with assigned colors. Number of elements is (numberOfIndices * 4). */ void NodeAndVoxelColoring::colorIndicesWithLabelTable(const GiftiLabelTable* labelTable, const float* labelIndices, const int64_t numberOfIndices, uint8_t* rgbv) { NodeAndVoxelColoring::colorIndicesWithLabelTableForDisplayGroupTabPrivate(labelTable, labelIndices, numberOfIndices, DisplayGroupEnum::DISPLAY_GROUP_TAB, NodeAndVoxelColoring::INVALID_TAB_INDEX, COLOR_TYPE_UNSIGNED_BTYE, (void*)rgbv); } /** * Convert the slice coloring to outline mode. * * @param rgbaInOut * Coloring for the slice (input and output) * @param labelDrawingType * Type of drawing for label filling and outline. * @param labelOutlineColor * Outline color of label. * @param xdim * X-dimension of slice (number of columns) * @param ydim * Y-dimension of slice (number of rows). */ void NodeAndVoxelColoring::convertSliceColoringToOutlineMode(uint8_t* rgbaInOut, const LabelDrawingTypeEnum::Enum labelDrawingType, const CaretColorEnum::Enum labelOutlineColor, const int64_t xdim, const int64_t ydim) { /* * Copy the rgba colors */ const int64_t numRGBA = xdim * ydim * 4; if (numRGBA <= 0) { return; } std::vector sliceCopyVector(numRGBA); uint8_t* rgba = &sliceCopyVector[0]; for (int64_t i = 0; i < numRGBA; i++) { rgba[i] = rgbaInOut[i]; } uint8_t outlineRGBA[4]; CaretColorEnum::toRGBByte(labelOutlineColor, outlineRGBA); outlineRGBA[3] = 255; /* * Examine coloring for all voxels except those along the edge */ const int64_t lastX = xdim - 1; const int64_t lastY = ydim - 1; for (int64_t i = 1; i < lastX; i++) { for (int64_t j = 1; j < lastY; j++) { const int iStart = i - 1; const int iEnd = i + 1; const int jStart = j - 1; const int jEnd = j + 1; const int64_t myOffset = (i + (xdim * j)) * 4; CaretAssert(myOffset < numRGBA); const uint8_t* myRGBA = &rgba[myOffset]; if (myRGBA[3] <= 0) { continue; } /* * Determine if voxel colors match voxel coloring * of ALL immediate neighbors (8-connected). */ bool isLabelBoundaryVoxel = false; for (int64_t iNeigh = iStart; iNeigh <= iEnd; iNeigh++) { for (int64_t jNeigh = jStart; jNeigh <= jEnd; jNeigh++) { const int64_t neighOffset = (iNeigh + (xdim * jNeigh)) * 4; CaretAssert(neighOffset < numRGBA); const uint8_t* neighRGBA = &rgba[neighOffset]; for (int64_t k = 0; k < 4; k++) { if (myRGBA[k] != neighRGBA[k]) { isLabelBoundaryVoxel = true; break; } } if (isLabelBoundaryVoxel) { break; } } if (isLabelBoundaryVoxel) { break; } } /* * Override the coloring as needed. */ switch (labelDrawingType) { case LabelDrawingTypeEnum::DRAW_FILLED: break; case LabelDrawingTypeEnum::DRAW_FILLED_WITH_OUTLINE_COLOR: if (isLabelBoundaryVoxel) { rgbaInOut[myOffset] = outlineRGBA[0]; rgbaInOut[myOffset + 1] = outlineRGBA[1]; rgbaInOut[myOffset + 2] = outlineRGBA[2]; rgbaInOut[myOffset + 3] = outlineRGBA[3]; } break; case LabelDrawingTypeEnum::DRAW_OUTLINE_COLOR: if (isLabelBoundaryVoxel) { rgbaInOut[myOffset] = outlineRGBA[0]; rgbaInOut[myOffset + 1] = outlineRGBA[1]; rgbaInOut[myOffset + 2] = outlineRGBA[2]; rgbaInOut[myOffset + 3] = outlineRGBA[3]; } else { rgbaInOut[myOffset + 3] = 0; } break; case LabelDrawingTypeEnum::DRAW_OUTLINE_LABEL_COLOR: if ( ! isLabelBoundaryVoxel) { rgbaInOut[myOffset + 3] = 0; } break; } } } } workbench-1.1.1/src/Files/NodeAndVoxelColoring.h000066400000000000000000000150331255417355300215350ustar00rootroot00000000000000#ifndef __NODE_AND_VOXEL_COLORING__H_ #define __NODE_AND_VOXEL_COLORING__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretColorEnum.h" #include "DisplayGroupEnum.h" #include "LabelDrawingTypeEnum.h" namespace caret { class FastStatistics; class GiftiLabelTable; class Palette; class PaletteColorMapping; class NodeAndVoxelColoring { public: static void colorScalarsWithPalette(const FastStatistics* statistics, const PaletteColorMapping* paletteColorMapping, const Palette* palette, const float* scalars, const float* scalarThresholds, const int64_t numberOfScalars, float* rgbaOut, const bool ignoreThresholding = false); static void colorScalarsWithPalette(const FastStatistics* statistics, const PaletteColorMapping* paletteColorMapping, const Palette* palette, const float* scalars, const float* scalarThresholds, const int64_t numberOfScalars, uint8_t* rgbaOut, const bool ignoreThresholding = false); // JWH 24 April 2015 static const float SMALL_POSITIVE; // JWH 24 April 2015 static const float SMALL_NEGATIVE; static void colorIndicesWithLabelTableForDisplayGroupTab(const GiftiLabelTable* labelTable, const float* labelIndices, const int64_t numberOfIndices, const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, float* rgbv); static void colorIndicesWithLabelTableForDisplayGroupTab(const GiftiLabelTable* labelTable, const float* labelIndices, const int64_t numberOfIndices, const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, uint8_t* rgbv); static void colorIndicesWithLabelTable(const GiftiLabelTable* labelTable, const float* labelIndices, const int64_t numberOfIndices, float* rgbv); static void colorIndicesWithLabelTable(const GiftiLabelTable* labelTable, const float* labelIndices, const int64_t numberOfIndices, uint8_t* rgbv); static void convertSliceColoringToOutlineMode(uint8_t* rgbaInOut, const LabelDrawingTypeEnum::Enum labelDrawingType, const CaretColorEnum::Enum labelOutlineColor, const int64_t xdim, const int64_t ydim); private: enum ColorDataType { COLOR_TYPE_FLOAT, COLOR_TYPE_UNSIGNED_BTYE }; static void colorScalarsWithPalettePrivate(const FastStatistics* statistics, const PaletteColorMapping* paletteColorMapping, const Palette* palette, const float* scalars, const float* scalarThresholds, const int64_t numberOfScalars, const ColorDataType colorDataType, void* rgbaOutPointer, const bool ignoreThresholding); static void colorIndicesWithLabelTableForDisplayGroupTabPrivate(const GiftiLabelTable* labelTable, const float* labelIndices, const int64_t numberOfIndices, const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const ColorDataType colorDataType, void* rgbaOutPointer); NodeAndVoxelColoring(); virtual ~NodeAndVoxelColoring(); NodeAndVoxelColoring(const NodeAndVoxelColoring&); NodeAndVoxelColoring& operator=(const NodeAndVoxelColoring&); static const int32_t INVALID_TAB_INDEX; }; #ifdef __NODE_AND_VOXEL_COLORING_DECLARE__ // JWH 24 April 2015 const float NodeAndVoxelColoring::SMALL_POSITIVE = 0.00001; // JWH 24 April 2015 const float NodeAndVoxelColoring::SMALL_NEGATIVE = -0.00001; const int32_t NodeAndVoxelColoring::INVALID_TAB_INDEX = -1; #endif // __NODE_AND_VOXEL_COLORING_DECLARE__ } // namespace #endif //__NODE_AND_VOXEL_COLORING__H_ workbench-1.1.1/src/Files/OxfordSparseThreeFile.cxx000066400000000000000000000161471255417355300223030ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OxfordSparseThreeFile.h" #include "ByteOrderEnum.h" #include "ByteSwapping.h" #include "CaretAssert.h" #include "FileInformation.h" #include using namespace caret; using namespace std; OxfordSparseThreeFile::OxfordSparseThreeFile(const AString& dimFileName, const AString& indexFileName, const AString& valueFileName) { m_valueFile = NULL; fstream dimFile(dimFileName.toLocal8Bit().constData(), fstream::in); if (!dimFile) throw DataFileException("error opening dimensions file"); dimFile >> m_dims[0]; if (!dimFile) throw DataFileException("error reading dimensions from file"); dimFile >> m_dims[1]; if (!dimFile) throw DataFileException("error reading dimensions from file"); if (m_dims[0] < 1 || m_dims[1] < 1) throw DataFileException("both dimensions must be positive"); m_indexArray.resize(m_dims[1] + 1); vector lengthArray(m_dims[1]); FileInformation indexFileInfo(indexFileName); if (!indexFileInfo.exists()) throw DataFileException("index file doesn't exist"); if (indexFileInfo.size() != 8 * m_dims[1]) throw DataFileException("index file is the wrong size"); FILE* indexFile = fopen(indexFileName.toLocal8Bit().constData(), "rb"); if (indexFile == NULL) throw DataFileException("error opening index file"); if (fread(lengthArray.data(), sizeof(int64_t), m_dims[1], indexFile) != (size_t)m_dims[1]) throw DataFileException("error reading index file"); if (ByteOrderEnum::isSystemBigEndian()) { ByteSwapping::swapBytes(lengthArray.data(), m_dims[1]); } m_indexArray[0] = 0; for (int64_t i = 0; i < m_dims[1]; ++i) { if (lengthArray[i] > m_dims[0] || lengthArray[i] < 0) throw DataFileException("impossible value found in length array"); m_indexArray[i + 1] = m_indexArray[i] + lengthArray[i]; } FileInformation valueFileInfo(valueFileName); if (!valueFileInfo.exists()) throw DataFileException("value file doesn't exist"); if (valueFileInfo.size() != (int64_t)(2 * sizeof(uint64_t) * m_indexArray[m_dims[1]])) throw DataFileException("value file is the wrong size"); m_valueFile = fopen(valueFileName.toLocal8Bit().constData(), "rb"); if (m_valueFile == NULL) throw DataFileException("error opening value file"); } OxfordSparseThreeFile::~OxfordSparseThreeFile() { if (m_valueFile != NULL) fclose(m_valueFile); } void OxfordSparseThreeFile::getRow(const int64_t& index, int64_t* rowOut) { CaretAssert(index >= 0 && index < m_dims[1]); int64_t start = m_indexArray[index], end = m_indexArray[index + 1]; int64_t numToRead = (end - start) * 2; m_scratchArray.resize(numToRead); if (fseek(m_valueFile, start * sizeof(int64_t) * 2, SEEK_SET) != 0) throw DataFileException("failed to seek in value file"); if (fread(m_scratchArray.data(), sizeof(int64_t), numToRead, m_valueFile) != (size_t)numToRead) throw DataFileException("error reading from value file"); if (ByteOrderEnum::isSystemBigEndian()) { ByteSwapping::swapBytes(m_scratchArray.data(), numToRead); } int64_t curIndex = 0; for (int64_t i = 0; i < numToRead; i += 2) { int64_t index = m_scratchArray[i]; if (index < curIndex || index >= m_dims[0]) throw DataFileException("impossible index value found in value file"); while (curIndex < index) { rowOut[curIndex] = 0; ++curIndex; } ++curIndex; rowOut[index] = m_scratchArray[i + 1]; } while (curIndex < m_dims[0]) { rowOut[curIndex] = 0; ++curIndex; } } void OxfordSparseThreeFile::getRowSparse(const int64_t& index, vector& indicesOut, vector& valuesOut) { CaretAssert(index >= 0 && index < m_dims[1]); int64_t start = m_indexArray[index], end = m_indexArray[index + 1]; int64_t numToRead = (end - start) * 2, numNonzero = end - start; m_scratchArray.resize(numToRead); if (fseek(m_valueFile, start * sizeof(int64_t) * 2, SEEK_SET) != 0) throw DataFileException("failed to seek in value file"); if (fread(m_scratchArray.data(), sizeof(int64_t), numToRead, m_valueFile) != (size_t)numToRead) throw DataFileException("error reading from value file"); if (ByteOrderEnum::isSystemBigEndian()) { ByteSwapping::swapBytes(m_scratchArray.data(), numToRead); } indicesOut.resize(numNonzero); valuesOut.resize(numNonzero); int64_t lastIndex = -1; for (int64_t i = 0; i < numNonzero; ++i) { indicesOut[i] = m_scratchArray[i * 2]; valuesOut[i] = m_scratchArray[i * 2 + 1]; if (indicesOut[i] <= lastIndex || indicesOut[i] >= m_dims[0]) { throw DataFileException("impossible index value found in file"); } lastIndex = indicesOut[i]; } } void OxfordSparseThreeFile::getFibersRow(const int64_t& index, FiberFractions* rowOut) { if (m_scratchRow.size() != (size_t)m_dims[0]) m_scratchRow.resize(m_dims[0]); getRow(index, (int64_t*)m_scratchRow.data()); for (int64_t i = 0; i < m_dims[0]; ++i) { if (m_scratchRow[i] == 0) { rowOut[i].zero(); } else { decodeFibers(m_scratchRow[i], rowOut[i]); } } } void OxfordSparseThreeFile::getFibersRowSparse(const int64_t& index, vector& indicesOut, vector& valuesOut) { getRowSparse(index, indicesOut, m_scratchSparseRow); size_t numNonzero = m_scratchSparseRow.size(); valuesOut.resize(numNonzero); for (size_t i = 0; i < numNonzero; ++i) { decodeFibers(((uint64_t*)m_scratchSparseRow.data())[i], valuesOut[i]); } } void OxfordSparseThreeFile::decodeFibers(const uint64_t& coded, FiberFractions& decoded) { decoded.fiberFractions.resize(3); decoded.totalCount = coded>>32; uint32_t temp = coded & ((1LL<<32) - 1); decoded.distance = (temp % 1001); temp = temp / 1001; decoded.fiberFractions[1] = (temp % 1001) / 1000.0f; temp = temp / 1001; decoded.fiberFractions[0] = temp / 1000.0f; decoded.fiberFractions[2] = 1.0f - decoded.fiberFractions[0] - decoded.fiberFractions[1]; if (decoded.fiberFractions[2] < -0.002f || temp > 1000) { throw DataFileException("error decoding value '" + AString::number(coded) + "' from oxford 3-file"); } if (decoded.fiberFractions[2] < 0.0f) decoded.fiberFractions[2] = 0.0f; } workbench-1.1.1/src/Files/OxfordSparseThreeFile.h000066400000000000000000000042041255417355300217170ustar00rootroot00000000000000#ifndef __OXFORD_SPARSE_THREE_FILE_H__ #define __OXFORD_SPARSE_THREE_FILE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretSparseFile.h" //for the Fibers struct namespace caret { class OxfordSparseThreeFile { static void decodeFibers(const uint64_t& coded, FiberFractions& decoded);//takes a uint because right shift on signed is implementation dependent FILE* m_valueFile;//we read the other two into memory, so we only need one handle int64_t m_dims[2]; std::vector m_indexArray, m_scratchRow; std::vector m_scratchArray, m_scratchSparseRow; OxfordSparseThreeFile(); OxfordSparseThreeFile(const OxfordSparseThreeFile& rhs); public: const int64_t* getDimensions() { return m_dims; } OxfordSparseThreeFile(const AString& dimFileName, const AString& indexFileName, const AString& valueFileName); void getRow(const int64_t& index, int64_t* rowOut); void getRowSparse(const int64_t& index, std::vector& indicesOut, std::vector& valuesOut); void getFibersRow(const int64_t& index, FiberFractions* rowOut); void getFibersRowSparse(const int64_t& index, std::vector& indicesOut, std::vector& valuesOut); ~OxfordSparseThreeFile(); }; } #endif //__OXFORD_SPARSE_THREE_FILE_H__ workbench-1.1.1/src/Files/PaletteFile.cxx000066400000000000000000002570331255417355300202730ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretLogger.h" #include "DataFileException.h" #include "GiftiLabel.h" #include "GiftiMetaData.h" #include "Palette.h" #include "PaletteColorMapping.h" #include "PaletteFile.h" #include "PaletteScalarAndColor.h" #include using namespace caret; /** * Constructor. * */ PaletteFile::PaletteFile() : CaretDataFile(DataFileTypeEnum::PALETTE) { this->metadata = new GiftiMetaData(); this->initializeMembersPaletteFile(); this->addDefaultPalettes(); this->clearModified(); } /** * Destructor */ PaletteFile::~PaletteFile() { this->clearAll(); delete this->metadata; } void PaletteFile::initializeMembersPaletteFile() { } /** * Get the label table used for color storage. * @return LabelTable used for color storage. * */ GiftiLabelTable* PaletteFile::getLabelTable() { return &this->labelTable; } /** * Clear everything. */ void PaletteFile::clearAll() { int64_t numberOfPalettes = this->palettes.size(); for (int64_t i = 0; i < numberOfPalettes; i++) { delete this->palettes[i]; } this->palettes.clear(); this->labelTable.clear(); this->metadata->clear(); } /** * Clear the file but add default palettes. */ void PaletteFile::clear() { this->clearAll(); this->addDefaultPalettes(); } /** * Add a palette color. * * @param pc - color to add. * */ void PaletteFile::addColor(const GiftiLabel& pc) { this->labelTable.addLabel(&pc); } /** * Add a palette color. * * @param name - name of color. * @param red - red component. * @param green - red component. * @param blue - red component. * */ void PaletteFile::addColor( const AString& name, const int32_t red, const int32_t green, const int32_t blue) { this->labelTable.addLabel(name, red, green, blue); } /** * Add a palette color. * * @param name - Name of color. * @param rgb - RGB components of color. * */ void PaletteFile::addColor( const AString& name, const int32_t rgb[]) { this->addColor(name, rgb[0], rgb[1], rgb[2]); } /** * Get a color via its index. * * @param index - index of color. * @return Reference to color at index or the default color * if the index is invalid. * */ const GiftiLabel* PaletteFile::getColor(const int32_t indx) const { return this->labelTable.getLabel(indx); } /** * Get a color via its index. * * @param colorName - Name of color. * @return Reference to color with name or the default color * if the name does not match any colors. * */ const GiftiLabel* PaletteFile::getColorByName(const AString& colorName) const { const GiftiLabel* gl = this->labelTable.getLabel(colorName); return gl; } /** * Get index for a color. * * @param colorName - Name of color. * @return Index to color or -1 if not found. * */ int32_t PaletteFile::getColorIndex(const AString& colorName) const { return this->labelTable.getLabelKeyFromName(colorName); } /** * Get the number of palettes. * * @return The number of palettes. * */ int32_t PaletteFile::getNumberOfPalettes() const { return this->palettes.size(); } /** * Add a palette. * * @param p - palette to add. * */ void PaletteFile::addPalette(const Palette& p) { Palette* pal = new Palette(p); this->assignColorsToPalette(*pal); this->palettes.push_back(pal); this->setModified(); } /** * Get a palette. * * @param index - index of palette. * @return Reference to palette or null if invalid index. * */ Palette* PaletteFile::getPalette(const int32_t indx) const { return this->palettes[indx]; } /** * Find a palette by the specified name. * * @param name Name of palette to search for. * @return Reference to palette with name or null if not found. * */ Palette* PaletteFile::getPaletteByName(const AString& name) const { int64_t numberOfPalettes = this->palettes.size(); for (int64_t i = 0; i < numberOfPalettes; i++) { if (this->palettes[i]->getName() == name) { return this->palettes[i]; } } return NULL; } /** * Remove a palette. * * @param index - index of palette to remove. * */ void PaletteFile::removePalette(const int32_t indx) { this->palettes.erase(this->palettes.begin() + indx); this->setModified(); } /** * Is this file empty? * * @return true if the file is empty, else false. * */ bool PaletteFile::isEmpty() const { return this->palettes.empty(); } /** * String description of this class. */ AString PaletteFile::toString() const { AString s; int64_t numberOfPalettes = this->palettes.size(); for (int64_t i = 0; i < numberOfPalettes; i++) { s += (this->palettes[i]->toString() + "\n"); } return s; } /** * Is this palette modified? * @return * true if modified, else false. */ bool PaletteFile::isModified() const { if (DataFile::isModified()) { return true; } if (this->labelTable.isModified()) { return true; } const int64_t numberOfPalettes = this->getNumberOfPalettes(); for (int i = 0; i < numberOfPalettes; i++) { if (this->palettes[i]->isModified()) { return true; } } return false; } /** * Set this object as not modified. Object should also * clear the modification status of its children. * */ void PaletteFile::clearModified() { DataFile::clearModified(); const int64_t numberOfPalettes = this->getNumberOfPalettes(); for (int i = 0; i < numberOfPalettes; i++) { this->palettes[i]->clearModified(); } this->labelTable.clearModified(); } /** * Assign colors to the palette. * @param * p Palette to which colors are assigned. */ void PaletteFile::assignColorsToPalette(Palette& p) { int64_t numberOfScalars = p.getNumberOfScalarsAndColors(); for (int64_t i = 0; i < numberOfScalars; i++) { PaletteScalarAndColor* psac = p.getScalarAndColor(i); const AString& colorName = psac->getColorName(); const GiftiLabel* gl = this->getColorByName(colorName); if (gl != NULL) { float rgba[4]; gl->getColor(rgba); psac->setColor(rgba); } else { CaretLogSevere(("Missing color \"" + colorName + "\" in palette \"" + p.getName() + "\"")); } } } /** * Read the data file. * * @param filename * Name of the data file. * @throws DataFileException * If the file was not successfully read. */ void PaletteFile::readFile(const AString& filename) { clear(); // checkFileReadability(filename); throw DataFileException(filename, "Reading of PaletteFile not implemented."); } /** * Write the data file. * * @param filename * Name of the data file. * @throws DataFileException * If the file was not successfully written. */ void PaletteFile::writeFile(const AString& filename) { // checkFileWritability(filename); throw DataFileException(filename, "Reading of PaletteFile not implemented."); } /** * Add the default palettes. * */ void PaletteFile::addDefaultPalettes() { bool modifiedStatus = this->isModified(); this->addColor("none", 0xff, 0xff, 0xff ); this->addColor("_yellow", 0xff, 0xff, 0x00 ); this->addColor("_black", 0x00, 0x00, 0x00 ); this->addColor("_orange", 0xff, 0x69, 0x00 ); //---------------------------------------------------------------------- // Psych palette // if (this->getPaletteByName("PSYCH") == NULL) { this->addColor("_pyell-oran", 0xff, 0xcc, 0x00 ); this->addColor("_poran-red", 0xff, 0x44, 0x00 ); this->addColor("_pblue", 0x00, 0x44, 0xff ); this->addColor("_pltblue1", 0x00, 0x69, 0xff ); this->addColor("_pltblue2", 0x00, 0x99, 0xff ); this->addColor("_pbluecyan", 0x00, 0xcc, 0xff ); Palette psych; psych.setName("PSYCH"); //psych.setPositiveOnly(false); psych.addScalarAndColor(1.00f, "_yellow"); psych.addScalarAndColor(0.75f, "_pyell-oran"); psych.addScalarAndColor(0.50f, "_orange"); psych.addScalarAndColor(0.25f, "_poran-red"); psych.addScalarAndColor(0.05f, "none"); psych.addScalarAndColor(-0.05f, "_pblue"); psych.addScalarAndColor(-0.25f, "_pltblue1"); psych.addScalarAndColor(-0.50f, "_pltblue2"); psych.addScalarAndColor(-0.75f, "_pbluecyan"); addPalette(psych); } //---------------------------------------------------------------------- // Psych no-none palette // if (this->getPaletteByName("PSYCH-NO-NONE") == NULL) { this->addColor("_pyell-oran", 0xff, 0xcc, 0x00 ); this->addColor("_poran-red", 0xff, 0x44, 0x00 ); this->addColor("_pblue", 0x00, 0x44, 0xff ); this->addColor("_pltblue1", 0x00, 0x69, 0xff ); this->addColor("_pltblue2", 0x00, 0x99, 0xff ); this->addColor("_pbluecyan", 0x00, 0xcc, 0xff ); Palette psychNoNone; psychNoNone.setName("PSYCH-NO-NONE"); //psychNoNone.setPositiveOnly(false); psychNoNone.addScalarAndColor(1.00f, "_yellow"); psychNoNone.addScalarAndColor(0.75f, "_pyell-oran"); psychNoNone.addScalarAndColor(0.50f, "_orange"); psychNoNone.addScalarAndColor(0.25f, "_poran-red"); psychNoNone.addScalarAndColor(0.0f, "_pblue"); psychNoNone.addScalarAndColor(-0.25f, "_pltblue1"); psychNoNone.addScalarAndColor(-0.50f, "_pltblue2"); psychNoNone.addScalarAndColor(-0.75f, "_pbluecyan"); addPalette(psychNoNone); } //---------------------------------------------------------------------- // ROY-BIG palette // if (this->getPaletteByName("ROY-BIG") == NULL) { this->addColor("_RGB_255_255_0", 255, 255, 0 ); //#ffff00 this->addColor("_RGB_255_200_0", 255, 200, 0 ); //#ffc800 this->addColor("_RGB_255_120_0", 255, 120, 0 ); //#ff7800 this->addColor("_RGB_255_0_0", 255, 0, 0 ); //#ff0000 this->addColor("_RGB_200_0_0", 200, 0, 0 ); //#c80000 this->addColor("_RGB_150_0_0", 150, 0, 0 ); //#960000 this->addColor("_RGB_100_0_0", 100, 0, 0 ); //#640000 this->addColor("_RGB_60_0_0", 60, 0, 0 ); //#3c0000 this->addColor("_RGB_0_0_80", 0, 0, 80 ); //#000050 this->addColor("_RGB_0_0_170", 0, 0, 170 ); //#0000aa this->addColor("_RGB_75_0_150", 75, 0, 125 ); //#4b007d this->addColor("_RGB_125_0_160", 125, 0, 160 ); //#7d00a0 this->addColor("_RGB_75_125_0", 75, 125, 0 ); //#4b7d00 this->addColor("_RGB_0_200_0", 0, 200, 0 ); //#00c800 this->addColor("_RGB_0_255_0", 0, 255, 0 ); //#00ff00 this->addColor("_RGB_0_255_255", 0, 255, 255 ); //#00ffff Palette royBig; royBig.setName("ROY-BIG"); royBig.addScalarAndColor(1.00f, "_RGB_255_255_0"); royBig.addScalarAndColor(0.875f, "_RGB_255_200_0"); royBig.addScalarAndColor(0.750f, "_RGB_255_120_0"); royBig.addScalarAndColor(0.625f, "_RGB_255_0_0"); royBig.addScalarAndColor(0.500f, "_RGB_200_0_0"); royBig.addScalarAndColor(0.375f, "_RGB_150_0_0"); royBig.addScalarAndColor(0.250f, "_RGB_100_0_0"); royBig.addScalarAndColor(0.125f, "_RGB_60_0_0"); royBig.addScalarAndColor(0.000f, "none"); royBig.addScalarAndColor(-0.125f, "_RGB_0_0_80"); royBig.addScalarAndColor(-0.250f, "_RGB_0_0_170"); royBig.addScalarAndColor(-0.375f, "_RGB_75_0_150"); royBig.addScalarAndColor(-0.500f, "_RGB_125_0_160"); royBig.addScalarAndColor(-0.625f, "_RGB_75_125_0"); royBig.addScalarAndColor(-0.750f, "_RGB_0_200_0"); royBig.addScalarAndColor(-0.875f, "_RGB_0_255_0"); royBig.addScalarAndColor(-0.990f, "_RGB_0_255_255"); royBig.addScalarAndColor(-1.00f, "_RGB_0_255_255"); addPalette(royBig); Palette royBigBL; royBigBL.setName(Palette::ROY_BIG_BL_PALETTE_NAME); royBigBL.addScalarAndColor(1.00f, "_RGB_255_255_0"); royBigBL.addScalarAndColor(0.875f, "_RGB_255_200_0"); royBigBL.addScalarAndColor(0.750f, "_RGB_255_120_0"); royBigBL.addScalarAndColor(0.625f, "_RGB_255_0_0"); royBigBL.addScalarAndColor(0.500f, "_RGB_200_0_0"); royBigBL.addScalarAndColor(0.375f, "_RGB_150_0_0"); royBigBL.addScalarAndColor(0.250f, "_RGB_100_0_0"); royBigBL.addScalarAndColor(0.125f, "_RGB_60_0_0"); royBigBL.addScalarAndColor(0.000f, "_black"); royBigBL.addScalarAndColor(-0.125f, "_RGB_0_0_80"); royBigBL.addScalarAndColor(-0.250f, "_RGB_0_0_170"); royBigBL.addScalarAndColor(-0.375f, "_RGB_75_0_150"); royBigBL.addScalarAndColor(-0.500f, "_RGB_125_0_160"); royBigBL.addScalarAndColor(-0.625f, "_RGB_75_125_0"); royBigBL.addScalarAndColor(-0.750f, "_RGB_0_200_0"); royBigBL.addScalarAndColor(-0.875f, "_RGB_0_255_0"); royBigBL.addScalarAndColor(-0.990f, "_RGB_0_255_255"); royBigBL.addScalarAndColor(-1.00f, "_RGB_0_255_255"); addPalette(royBigBL); } //---------------------------------------------------------------------- // Orange-Yellow palette // if (this->getPaletteByName("Orange-Yellow") == NULL) { this->addColor("_oy1", 0, 0, 0 ); this->addColor("_oy2", 130, 2, 0 ); this->addColor("_oy3", 254, 130, 2 ); this->addColor("_oy4", 254, 254, 126 ); this->addColor("_oy5", 254, 254, 254 ); Palette orangeYellow; orangeYellow.setName("Orange-Yellow"); orangeYellow.addScalarAndColor( 1.0f, "_oy5"); orangeYellow.addScalarAndColor( 0.5f, "_oy4"); orangeYellow.addScalarAndColor( 0.0f, "_oy3"); orangeYellow.addScalarAndColor(-0.5f, "_oy2"); orangeYellow.addScalarAndColor(-1.0f, "_oy1"); addPalette(orangeYellow); } // // Create a palette with just white and black designed to be used // with the interpolate option // if (this->getPaletteByName(Palette::GRAY_INTERP_PALETTE_NAME) == NULL) { this->addColor("_white_gray_interp", 255, 255, 255 ); this->addColor("_black_gray_interp", 0, 0, 0 ); Palette palGrayPositiveInterp; palGrayPositiveInterp.setName(Palette::GRAY_INTERP_POSITIVE_PALETTE_NAME); palGrayPositiveInterp.addScalarAndColor( 1.0f, "_white_gray_interp"); palGrayPositiveInterp.addScalarAndColor(0.0f, "_black_gray_interp"); addPalette(palGrayPositiveInterp); Palette palGrayInterp; palGrayInterp.setName(Palette::GRAY_INTERP_PALETTE_NAME); palGrayInterp.addScalarAndColor( 1.0f, "_white_gray_interp"); palGrayInterp.addScalarAndColor(-1.0f, "_black_gray_interp"); addPalette(palGrayInterp); } //------------------------------------------------------------------------ // // Palette by David Van Essen // int oran_yell[3] = { 0xff, 0x99, 0x00 }; this->addColor("_oran-yell", oran_yell); int red[3] = { 0xff, 0x00, 0x00 }; this->addColor("_red", red); int cyan[3] = { 0x00, 0xff, 0xff }; this->addColor("_cyan", cyan); int green[3] = { 0x00, 0xff, 0x00 }; this->addColor("_green", green); int limegreen[3] = { 0x10, 0xb0, 0x10 }; this->addColor("_limegreen", limegreen); int violet[3] = { 0xe2, 0x51, 0xe2 }; this->addColor("_violet", violet); int hotpink[3] = { 0xff, 0x38, 0x8d }; this->addColor("_hotpink", hotpink); int white[3] = { 0xff, 0xff, 0xff }; this->addColor("_white", white); int gry_dd[3] = { 0xdd, 0xdd, 0xdd }; this->addColor("_gry-dd", gry_dd ); int gry_bb[3] = { 0xbb, 0xbb, 0xbb }; this->addColor("_gry-bb", gry_bb); int purple2[3] = { 0x66, 0x00, 0x33 }; this->addColor("_purple2", purple2); int blue_videen11[3] = { 0x33, 0x33, 0x4c }; this->addColor("_blue_videen11", blue_videen11); int blue_videen9[3] = { 0x4c, 0x4c, 0x7f }; this->addColor("_blue_videen9", blue_videen9); int blue_videen7[3] = { 0x7f, 0x7f, 0xcc }; this->addColor("_blue_videen7", blue_videen7); if (this->getPaletteByName("clear_brain") == NULL) { Palette clearBrain; clearBrain.setName("clear_brain"); clearBrain.addScalarAndColor(1.0f , "_red"); clearBrain.addScalarAndColor(0.9f , "_orange"); clearBrain.addScalarAndColor(0.8f , "_oran-yell"); clearBrain.addScalarAndColor(0.7f , "_yellow"); clearBrain.addScalarAndColor(0.6f , "_limegreen"); clearBrain.addScalarAndColor(0.5f , "_green"); clearBrain.addScalarAndColor(0.4f , "_blue_videen7"); clearBrain.addScalarAndColor(0.3f , "_blue_videen9"); clearBrain.addScalarAndColor(0.2f , "_blue_videen11"); clearBrain.addScalarAndColor(0.1f , "_purple2"); clearBrain.addScalarAndColor(0.0f , "none"); clearBrain.addScalarAndColor(-0.1f , "_cyan"); clearBrain.addScalarAndColor(-0.2f , "_green"); clearBrain.addScalarAndColor(-0.3f , "_limegreen"); clearBrain.addScalarAndColor(-0.4f , "_violet"); clearBrain.addScalarAndColor(-0.5f , "_hotpink"); clearBrain.addScalarAndColor(-0.6f , "_white"); clearBrain.addScalarAndColor(-0.7f , "_gry-dd"); clearBrain.addScalarAndColor(-0.8f , "_gry-bb"); clearBrain.addScalarAndColor(-0.9f , "_black"); addPalette(clearBrain); } if (this->getPaletteByName("videen_style") == NULL) { Palette videenStyle; videenStyle.setName("videen_style"); videenStyle.addScalarAndColor(1.0f, "_red"); videenStyle.addScalarAndColor(0.9f, "_orange"); videenStyle.addScalarAndColor(0.8f, "_oran-yell"); videenStyle.addScalarAndColor(0.7f, "_yellow"); videenStyle.addScalarAndColor(0.6f, "_limegreen"); videenStyle.addScalarAndColor(0.5f, "_green"); videenStyle.addScalarAndColor(0.4f, "_blue_videen7"); videenStyle.addScalarAndColor(0.3f, "_blue_videen9"); videenStyle.addScalarAndColor(0.2f, "_blue_videen11"); videenStyle.addScalarAndColor(0.1f, "_purple2"); videenStyle.addScalarAndColor(0.0f, "_black"); videenStyle.addScalarAndColor(-0.1f, "_cyan"); videenStyle.addScalarAndColor(-0.2f, "_green"); videenStyle.addScalarAndColor(-0.3f, "_limegreen"); videenStyle.addScalarAndColor(-0.4f, "_violet"); videenStyle.addScalarAndColor(-0.5f, "_hotpink"); videenStyle.addScalarAndColor(-0.6f, "_white"); videenStyle.addScalarAndColor(-0.7f, "_gry-dd"); videenStyle.addScalarAndColor(-0.8f, "_gry-bb"); videenStyle.addScalarAndColor(-0.9f, "_black"); addPalette(videenStyle); } if (this->getPaletteByName("fidl") == NULL) { int Bright_Yellow[3] = { 0xee, 0xee, 0x55 }; this->addColor("_Bright_Yellow", Bright_Yellow); int Mustard[3] = { 0xdd, 0xdd, 0x66 }; this->addColor("_Mustard", Mustard); int Brown_Mustard[3] = { 0xdd, 0x99, 0x00 }; this->addColor("_Brown_Mustard", Brown_Mustard); int Bright_Red[3] = { 0xff, 0x00, 0x00 }; this->addColor("_Bright_Red", Bright_Red); int Fire_Engine_Red[3] = { 0xdd, 0x00, 0x00 }; this->addColor("_Fire_Engine_Red", Fire_Engine_Red); int Brick[3] = { 0xbb, 0x00, 0x00 }; this->addColor("_Brick", Brick); int Beet[3] = { 0x99, 0x00, 0x00 }; this->addColor("_Beet", Beet); int Beaujolais[3] = { 0x77, 0x00, 0x00 }; this->addColor("_Beaujolais", Beaujolais); int Burgundy[3] = { 0x55, 0x00, 0x00 }; this->addColor("_Burgundy", Burgundy); int Thrombin[3] = { 0x11, 0x00, 0x00 }; this->addColor("_Thrombin", Thrombin); int Deep_Green[3] = { 0x00, 0x11, 0x00 }; this->addColor("_Deep_Green", Deep_Green); int British_Racing_Green[3] = { 0x00, 0x55, 0x00 }; this->addColor("_British_Racing_Green", British_Racing_Green); int Kelp[3] = { 0x00, 0x77, 0x00 }; this->addColor("_Kelp", Kelp); int Lime[3] = { 0x00, 0x99, 0x00 }; this->addColor("_Lime", Lime); int Mint[3] = { 0x00, 0xbb, 0x00 }; this->addColor("_Mint", Mint); int Brussell_Sprout[3] = { 0x00, 0xdd, 0x00 }; this->addColor("_Brussell_Sprout", Brussell_Sprout); int Bright_Green[3] = { 0x00, 0xff, 0x00 }; this->addColor("_Bright_Green", Bright_Green); int Periwinkle[3] = { 0x66, 0x66, 0xbb }; this->addColor("_Periwinkle", Periwinkle); int Azure[3] = { 0x88, 0x88, 0xee }; this->addColor("_Azure", Azure); int Turquoise[3] = { 0x00, 0xcc, 0xcc }; this->addColor("_Turquoise", Turquoise); Palette fidl; fidl.setName("fidl"); fidl.addScalarAndColor(1.0f, "_Bright_Yellow"); fidl.addScalarAndColor(0.9f, "_Mustard"); fidl.addScalarAndColor(0.8f, "_Brown_Mustard"); fidl.addScalarAndColor(0.7f, "_Bright_Red"); fidl.addScalarAndColor(0.6f, "_Fire_Engine_Red"); fidl.addScalarAndColor(0.5f, "_Brick"); fidl.addScalarAndColor(0.4f, "_Beet"); fidl.addScalarAndColor(0.3f, "_Beaujolais"); fidl.addScalarAndColor(0.2f, "_Burgundy"); fidl.addScalarAndColor(0.1f, "_Thrombin"); fidl.addScalarAndColor(0.0f, "none"); fidl.addScalarAndColor(-0.1f, "_Deep_Green"); fidl.addScalarAndColor(-0.2f, "_British_Racing_Green"); fidl.addScalarAndColor(-0.3f, "_Kelp"); fidl.addScalarAndColor(-0.4f, "_Lime"); fidl.addScalarAndColor(-0.5f, "_Mint"); fidl.addScalarAndColor(-0.6f, "_Brussell_Sprout"); fidl.addScalarAndColor(-0.7f, "_Bright_Green"); fidl.addScalarAndColor(-0.8f, "_Periwinkle"); fidl.addScalarAndColor(-0.9f, "_Azure"); fidl.addScalarAndColor(-1.0f, "_Turquoise"); addPalette(fidl); } //------------------------------------------------------------------------ // // Colors by Russ H. // int _rbgyr20_10[3] = { 0x00, 0xff, 0x00 }; this->addColor("_rbgyr20_10", _rbgyr20_10); int _rbgyr20_15[3] = { 0xff, 0xff, 0x00 }; this->addColor("_rbgyr20_15", _rbgyr20_15); int _rbgyr20_20[3] = { 0xff, 0x00, 0x00 }; this->addColor("_rbgyr20_20", _rbgyr20_20); int _rbgyr20_21[3] = { 0x9d, 0x22, 0xc1 }; this->addColor("_rbgyr20_21", _rbgyr20_21); int _rbgyr20_22[3] = { 0x81, 0x06, 0xa5 }; this->addColor("_rbgyr20_22", _rbgyr20_22); int _rbgyr20_23[3] = { 0xff, 0xec, 0x00 }; this->addColor("_rbgyr20_23", _rbgyr20_23); int _rbgyr20_24[3] = { 0xff, 0xd6, 0x00 }; this->addColor("_rbgyr20_24", _rbgyr20_24); int _rbgyr20_25[3] = { 0xff, 0xbc, 0x00 }; this->addColor("_rbgyr20_25", _rbgyr20_25); int _rbgyr20_26[3] = { 0xff, 0x9c, 0x00 }; this->addColor("_rbgyr20_26", _rbgyr20_26); int _rbgyr20_27[3] = { 0xff, 0x7c, 0x00 }; this->addColor("_rbgyr20_27", _rbgyr20_27); int _rbgyr20_28[3] = { 0xff, 0x5c, 0x00 }; this->addColor("_rbgyr20_28", _rbgyr20_28); int _rbgyr20_29[3] = { 0xff, 0x3d, 0x00 }; this->addColor("_rbgyr20_29", _rbgyr20_29); int _rbgyr20_30[3] = { 0xff, 0x23, 0x00 }; this->addColor("_rbgyr20_30", _rbgyr20_30); int _rbgyr20_31[3] = { 0x00, 0xed, 0x12 }; this->addColor("_rbgyr20_31", _rbgyr20_31); int _rbgyr20_32[3] = { 0x00, 0xd5, 0x2a }; this->addColor("_rbgyr20_32", _rbgyr20_32); int _rbgyr20_33[3] = { 0x00, 0xb9, 0x46 }; this->addColor("_rbgyr20_33", _rbgyr20_33); int _rbgyr20_34[3] = { 0x00, 0x9b, 0x64 }; this->addColor("_rbgyr20_34", _rbgyr20_34); int _rbgyr20_35[3] = { 0x00, 0x7b, 0x84 }; this->addColor("_rbgyr20_35", _rbgyr20_35); int _rbgyr20_36[3] = { 0x00, 0x5b, 0xa4 }; this->addColor("_rbgyr20_36", _rbgyr20_36); int _rbgyr20_37[3] = { 0x00, 0x44, 0xbb }; this->addColor("_rbgyr20_37", _rbgyr20_37); int _rbgyr20_38[3] = { 0x00, 0x24, 0xdb }; this->addColor("_rbgyr20_38", _rbgyr20_38); int _rbgyr20_39[3] = { 0x00, 0x00, 0xff }; this->addColor("_rbgyr20_39", _rbgyr20_39); int _rbgyr20_40[3] = { 0xff, 0xf1, 0x00 }; this->addColor("_rbgyr20_40", _rbgyr20_40); int _rbgyr20_41[3] = { 0xff, 0xdc, 0x00 }; this->addColor("_rbgyr20_41", _rbgyr20_41); int _rbgyr20_42[3] = { 0xff, 0xcb, 0x00 }; this->addColor("_rbgyr20_42", _rbgyr20_42); int _rbgyr20_43[3] = { 0xff, 0xc2, 0x00 }; this->addColor("_rbgyr20_43", _rbgyr20_43); int _rbgyr20_44[3] = { 0xff, 0xae, 0x00 }; this->addColor("_rbgyr20_44", _rbgyr20_44); int _rbgyr20_45[3] = { 0xff, 0x9f, 0x00 }; this->addColor("_rbgyr20_45", _rbgyr20_45); int _rbgyr20_46[3] = { 0xff, 0x86, 0x00 }; this->addColor("_rbgyr20_46", _rbgyr20_46); int _rbgyr20_47[3] = { 0xff, 0x59, 0x00 }; this->addColor("_rbgyr20_47", _rbgyr20_47); int _rbgyr20_48[3] = { 0x00, 0xff, 0x2d }; this->addColor("_rbgyr20_48", _rbgyr20_48); int _rbgyr20_49[3] = { 0x00, 0xff, 0x65 }; this->addColor("_rbgyr20_49", _rbgyr20_49); int _rbgyr20_50[3] = { 0x00, 0xff, 0xa5 }; this->addColor("_rbgyr20_50", _rbgyr20_50); int _rbgyr20_51[3] = { 0x00, 0xff, 0xdd }; this->addColor("_rbgyr20_51", _rbgyr20_51); int _rbgyr20_52[3] = { 0x00, 0xff, 0xff }; this->addColor("_rbgyr20_52", _rbgyr20_52); int _rbgyr20_53[3] = { 0x00, 0xe9, 0xff }; this->addColor("_rbgyr20_53", _rbgyr20_53); int _rbgyr20_54[3] = { 0x00, 0xad, 0xff }; this->addColor("_rbgyr20_54", _rbgyr20_54); int _rbgyr20_55[3] = { 0x00, 0x69, 0xff }; this->addColor("_rbgyr20_55", _rbgyr20_55); int _rbgyr20_56[3] = { 0xff, 0x00, 0xb9 }; this->addColor("_rbgyr20_56", _rbgyr20_56); int _rbgyr20_57[3] = { 0xff, 0x00, 0x63 }; this->addColor("_rbgyr20_57", _rbgyr20_57); int _rbgyr20_58[3] = { 0xff, 0x05, 0x00 }; this->addColor("_rbgyr20_58", _rbgyr20_58); int _rbgyr20_59[3] = { 0xff, 0x32, 0x00 }; this->addColor("_rbgyr20_59", _rbgyr20_59); int _rbgyr20_60[3] = { 0xff, 0x70, 0x00 }; this->addColor("_rbgyr20_60", _rbgyr20_60); int _rbgyr20_61[3] = { 0xff, 0xa4, 0x00 }; this->addColor("_rbgyr20_61", _rbgyr20_61); int _rbgyr20_62[3] = { 0xff, 0xba, 0x00 }; this->addColor("_rbgyr20_62", _rbgyr20_62); int _rbgyr20_63[3] = { 0xff, 0xd3, 0x00 }; this->addColor("_rbgyr20_63", _rbgyr20_63); int _rbgyr20_64[3] = { 0x42, 0x21, 0xdb }; this->addColor("_rbgyr20_64", _rbgyr20_64); int _rbgyr20_65[3] = { 0x10, 0x08, 0xf6 }; this->addColor("_rbgyr20_65", _rbgyr20_65); int _rbgyr20_66[3] = { 0x00, 0x13, 0xff }; this->addColor("_rbgyr20_66", _rbgyr20_66); int _rbgyr20_67[3] = { 0x00, 0x5b, 0xff }; this->addColor("_rbgyr20_67", _rbgyr20_67); int _rbgyr20_68[3] = { 0x00, 0xb3, 0xff }; this->addColor("_rbgyr20_68", _rbgyr20_68); int _rbgyr20_69[3] = { 0x00, 0xfc, 0xff }; this->addColor("_rbgyr20_69", _rbgyr20_69); int _rbgyr20_70[3] = { 0x00, 0xff, 0xcd }; this->addColor("_rbgyr20_70", _rbgyr20_70); int _rbgyr20_71[3] = { 0x00, 0xff, 0x74 }; this->addColor("_rbgyr20_71", _rbgyr20_71); int _rbgyr20_72[3] = { 0xff, 0x00, 0xf9 }; this->addColor("_rbgyr20_72", _rbgyr20_72); int _rbgyr20_73[3] = { 0x62, 0x31, 0xc9 }; this->addColor("_rbgyr20_73", _rbgyr20_73); //------------------------------------------------------------------------ // // Palette by Russ H. // if (this->getPaletteByName("raich4_clrmid") == NULL) { Palette r4; r4.setName("raich4_clrmid"); r4.addScalarAndColor(1.000000f, "_rbgyr20_20"); r4.addScalarAndColor(0.900000f, "_rbgyr20_30"); r4.addScalarAndColor(0.800000f, "_rbgyr20_29"); r4.addScalarAndColor(0.700000f, "_rbgyr20_28"); r4.addScalarAndColor(0.600000f, "_rbgyr20_27"); r4.addScalarAndColor(0.500000f, "_rbgyr20_26"); r4.addScalarAndColor(0.400000f, "_rbgyr20_25"); r4.addScalarAndColor(0.300000f, "_rbgyr20_24"); r4.addScalarAndColor(0.200000f, "_rbgyr20_23"); r4.addScalarAndColor(0.100000f, "_rbgyr20_15"); r4.addScalarAndColor(0.000000f, "none"); r4.addScalarAndColor(-0.100000f, "_rbgyr20_10"); r4.addScalarAndColor(-0.200000f, "_rbgyr20_31"); r4.addScalarAndColor(-0.300000f, "_rbgyr20_32"); r4.addScalarAndColor(-0.400000f, "_rbgyr20_33"); r4.addScalarAndColor(-0.500000f, "_rbgyr20_34"); r4.addScalarAndColor(-0.600000f, "_rbgyr20_35"); r4.addScalarAndColor(-0.700000f, "_rbgyr20_36"); r4.addScalarAndColor(-0.800000f, "_rbgyr20_37"); r4.addScalarAndColor(-0.900000f, "_rbgyr20_38"); r4.addScalarAndColor(-1.000000f, "_rbgyr20_39"); addPalette(r4); } //------------------------------------------------------------------------ // // Palette by Russ H. // if (this->getPaletteByName("raich6_clrmid") == NULL) { Palette r6; r6.setName("raich6_clrmid"); r6.addScalarAndColor(1.000000f, "_rbgyr20_20"); r6.addScalarAndColor(0.900000f, "_rbgyr20_47"); r6.addScalarAndColor(0.800000f, "_rbgyr20_46"); r6.addScalarAndColor(0.700000f, "_rbgyr20_45"); r6.addScalarAndColor(0.600000f, "_rbgyr20_44"); r6.addScalarAndColor(0.500000f, "_rbgyr20_43"); r6.addScalarAndColor(0.400000f, "_rbgyr20_42"); r6.addScalarAndColor(0.300000f, "_rbgyr20_41"); r6.addScalarAndColor(0.200000f, "_rbgyr20_40"); r6.addScalarAndColor(0.100000f, "_rbgyr20_15"); r6.addScalarAndColor(0.000000f, "none"); r6.addScalarAndColor(-0.100000f, "_rbgyr20_10"); r6.addScalarAndColor(-0.200000f, "_rbgyr20_48"); r6.addScalarAndColor(-0.300000f, "_rbgyr20_49"); r6.addScalarAndColor(-0.400000f, "_rbgyr20_50"); r6.addScalarAndColor(-0.500000f, "_rbgyr20_51"); r6.addScalarAndColor(-0.600000f, "_rbgyr20_52"); r6.addScalarAndColor(-0.700000f, "_rbgyr20_53"); r6.addScalarAndColor(-0.800000f, "_rbgyr20_54"); r6.addScalarAndColor(-0.900000f, "_rbgyr20_55"); r6.addScalarAndColor(-1.000000f, "_rbgyr20_39"); addPalette(r6); } //------------------------------------------------------------------------ // // Palette by Russ H. // if (this->getPaletteByName("HSB8_clrmid") == NULL) { Palette hsb8; hsb8.setName("HSB8_clrmid"); hsb8.addScalarAndColor(1.000000f, "_rbgyr20_15"); hsb8.addScalarAndColor(0.900000f, "_rbgyr20_63"); hsb8.addScalarAndColor(0.800000f, "_rbgyr20_62"); hsb8.addScalarAndColor(0.700000f, "_rbgyr20_61"); hsb8.addScalarAndColor(0.600000f, "_rbgyr20_60"); hsb8.addScalarAndColor(0.500000f, "_rbgyr20_59"); hsb8.addScalarAndColor(0.400000f, "_rbgyr20_58"); hsb8.addScalarAndColor(0.300000f, "_rbgyr20_57"); hsb8.addScalarAndColor(0.200000f, "_rbgyr20_56"); hsb8.addScalarAndColor(0.100000f, "_rbgyr20_72"); hsb8.addScalarAndColor(0.000000f, "none"); hsb8.addScalarAndColor(-0.100000f, "_rbgyr20_73"); hsb8.addScalarAndColor(-0.200000f, "_rbgyr20_64"); hsb8.addScalarAndColor(-0.300000f, "_rbgyr20_65"); hsb8.addScalarAndColor(-0.400000f, "_rbgyr20_66"); hsb8.addScalarAndColor(-0.500000f, "_rbgyr20_67"); hsb8.addScalarAndColor(-0.600000f, "_rbgyr20_68"); hsb8.addScalarAndColor(-0.700000f, "_rbgyr20_69"); hsb8.addScalarAndColor(-0.800000f, "_rbgyr20_70"); hsb8.addScalarAndColor(-0.900000f, "_rbgyr20_71"); hsb8.addScalarAndColor(-1.000000f, "_rbgyr20_10"); addPalette(hsb8); } //------------------------------------------------------------------------ // // Palette by Jon Wieser @ mcw // int rbgyr20_01[3] = { 0xCC, 0x10, 0x33 }; this->addColor("_rbgyr20_01", rbgyr20_01); int rbgyr20_02[3] = { 0x99, 0x20, 0x66 }; this->addColor("_rbgyr20_02", rbgyr20_02); int rbgyr20_03[3] = { 0x66, 0x31, 0x99 }; this->addColor("_rbgyr20_03", rbgyr20_03); int rbgyr20_04[3] = { 0x34, 0x41, 0xCC }; this->addColor("_rbgyr20_04", rbgyr20_04); int rbgyr20_05[3] = { 0x00, 0x51, 0xFF }; this->addColor("_rbgyr20_05", rbgyr20_05); int rbgyr20_06[3] = { 0x00, 0x74, 0xCC }; this->addColor("_rbgyr20_06", rbgyr20_06); int rbgyr20_07[3] = { 0x00, 0x97, 0x99 }; this->addColor("_rbgyr20_07", rbgyr20_07); int rbgyr20_08[3] = { 0x00, 0xB9, 0x66 }; this->addColor("_rbgyr20_08", rbgyr20_08); int rbgyr20_09[3] = { 0x00, 0xDC, 0x33 }; this->addColor("_rbgyr20_09", rbgyr20_09); int rbgyr20_10[3] = { 0x00, 0xFF, 0x00 }; this->addColor("_rbgyr20_10", rbgyr20_10); int rbgyr20_11[3] = { 0x33, 0xFF, 0x00 }; this->addColor("_rbgyr20_11", rbgyr20_11); int rbgyr20_12[3] = { 0x66, 0xFF, 0x00 }; this->addColor("_rbgyr20_12", rbgyr20_12); int rbgyr20_13[3] = { 0x99, 0xFF, 0x00 }; this->addColor("_rbgyr20_13", rbgyr20_13); int rbgyr20_14[3] = { 0xCC, 0xFF, 0x00 }; this->addColor("_rbgyr20_14", rbgyr20_14); int rbgyr20_15[3] = { 0xFF, 0xFF, 0x00 }; this->addColor("_rbgyr20_15", rbgyr20_15); int rbgyr20_16[3] = { 0xFF, 0xCC, 0x00 }; this->addColor("_rbgyr20_16", rbgyr20_16); int rbgyr20_17[3] = { 0xFF, 0x99, 0x00 }; this->addColor("_rbgyr20_17", rbgyr20_17); int rbgyr20_18[3] = { 0xFF, 0x66, 0x00 }; this->addColor("_rbgyr20_18", rbgyr20_18); int rbgyr20_19[3] = { 0xFF, 0x33, 0x00 }; this->addColor("_rbgyr20_19", rbgyr20_19); int rbgyr20_20[3] = { 0xFF, 0x00, 0x00 }; this->addColor("_rbgyr20_20", rbgyr20_20); if (this->getPaletteByName("RBGYR20") == NULL) { Palette pal2; pal2.setName("RBGYR20"); pal2.addScalarAndColor( 1.0f, "_rbgyr20_01"); pal2.addScalarAndColor( 0.9f, "_rbgyr20_02"); pal2.addScalarAndColor( 0.8f, "_rbgyr20_03"); pal2.addScalarAndColor( 0.7f, "_rbgyr20_04"); pal2.addScalarAndColor( 0.6f, "_rbgyr20_05"); pal2.addScalarAndColor( 0.5f, "_rbgyr20_06"); pal2.addScalarAndColor( 0.4f, "_rbgyr20_07"); pal2.addScalarAndColor( 0.3f, "_rbgyr20_08"); pal2.addScalarAndColor( 0.2f, "_rbgyr20_09"); pal2.addScalarAndColor( 0.1f, "_rbgyr20_10"); pal2.addScalarAndColor( 0.0f, "_rbgyr20_11"); pal2.addScalarAndColor(-0.1f, "_rbgyr20_12"); pal2.addScalarAndColor(-0.2f, "_rbgyr20_13"); pal2.addScalarAndColor(-0.3f, "_rbgyr20_14"); pal2.addScalarAndColor(-0.4f, "_rbgyr20_15"); pal2.addScalarAndColor(-0.5f, "_rbgyr20_16"); pal2.addScalarAndColor(-0.6f, "_rbgyr20_17"); pal2.addScalarAndColor(-0.7f, "_rbgyr20_18"); pal2.addScalarAndColor(-0.8f, "_rbgyr20_19"); pal2.addScalarAndColor(-0.9f, "_rbgyr20_20"); addPalette(pal2); Palette pal3; pal3.setName("RBGYR20P"); pal3.addScalarAndColor(1.00f, "_rbgyr20_01"); pal3.addScalarAndColor(0.95f, "_rbgyr20_02"); pal3.addScalarAndColor(0.90f, "_rbgyr20_03"); pal3.addScalarAndColor(0.85f, "_rbgyr20_04"); pal3.addScalarAndColor(0.80f, "_rbgyr20_05"); pal3.addScalarAndColor(0.75f, "_rbgyr20_06"); pal3.addScalarAndColor(0.70f, "_rbgyr20_07"); pal3.addScalarAndColor(0.65f, "_rbgyr20_08"); pal3.addScalarAndColor(0.60f, "_rbgyr20_09"); pal3.addScalarAndColor(0.55f, "_rbgyr20_10"); pal3.addScalarAndColor(0.50f, "_rbgyr20_11"); pal3.addScalarAndColor(0.45f, "_rbgyr20_12"); pal3.addScalarAndColor(0.40f, "_rbgyr20_13"); pal3.addScalarAndColor(0.35f, "_rbgyr20_14"); pal3.addScalarAndColor(0.30f, "_rbgyr20_15"); pal3.addScalarAndColor(0.25f, "_rbgyr20_16"); pal3.addScalarAndColor(0.20f, "_rbgyr20_17"); pal3.addScalarAndColor(0.15f, "_rbgyr20_18"); pal3.addScalarAndColor(0.10f, "_rbgyr20_19"); pal3.addScalarAndColor(0.05f, "_rbgyr20_20"); pal3.addScalarAndColor(0.0f, "none"); addPalette(pal3); } //---------------------------------------------------------------------- // Positive/Negative palette // if (this->getPaletteByName("POS_NEG") == NULL) { this->addColor("pos_neg_blue", 0x00, 0x00, 0xff ); this->addColor("pos_neg_red", 0xff, 0x00, 0x00 ); Palette posNeg; posNeg.setName("POS_NEG"); posNeg.addScalarAndColor(1.00f, "pos_neg_red"); posNeg.addScalarAndColor(0.0001f, "none"); posNeg.addScalarAndColor(-0.0001f, "pos_neg_blue"); addPalette(posNeg); } if (this->getPaletteByName("red-yellow") == NULL) { this->addColor("_red_yellow_interp_red", 255, 0, 0 ); this->addColor("_red_yellow_interp_yellow", 255, 255, 0 ); this->addColor("_blue_lightblue_interp_blue", 0, 0, 255 ); this->addColor("_blue_lightblue_interp_lightblue", 0, 255, 255 ); this->addColor("_fslview_zero", 0, 0, 0); Palette palRedYellowInterp; palRedYellowInterp.setName("red-yellow"); palRedYellowInterp.addScalarAndColor(1.0f, "_red_yellow_interp_yellow"); palRedYellowInterp.addScalarAndColor(0.0f, "_red_yellow_interp_red"); addPalette(palRedYellowInterp); Palette palBlueLightblueInterp; palBlueLightblueInterp.setName("blue-lightblue"); palBlueLightblueInterp.addScalarAndColor(1.0f, "_blue_lightblue_interp_lightblue"); palBlueLightblueInterp.addScalarAndColor(0.0f, "_blue_lightblue_interp_blue"); addPalette(palBlueLightblueInterp); Palette palFSLView; palFSLView.setName("FSL"); palFSLView.addScalarAndColor( 1.0f, "_red_yellow_interp_yellow"); palFSLView.addScalarAndColor( 0.00001f, "_red_yellow_interp_red"); palFSLView.addScalarAndColor( 0.0000099f, "_fslview_zero"); palFSLView.addScalarAndColor(-0.0000099f, "_fslview_zero"); palFSLView.addScalarAndColor(-0.00001f, "_blue_lightblue_interp_blue"); palFSLView.addScalarAndColor(-1.0f, "_blue_lightblue_interp_lightblue"); addPalette(palFSLView); } if (this->getPaletteByName("power_surf") == NULL) { this->addColor("_ps_0", 1.0 *255.0, 0.0 * 255.0, 0.0 * 255.0 ); this->addColor("_ps_059", 0.0 * 255.0, 0.0 * 255.0, 0.6 * 255.0 ); this->addColor("_ps_118", 1.0 * 255.0, 1.0 * 255.0, 0.0 * 255.0 ); this->addColor("_ps_176", 1.0 * 255.0, 0.7 * 255.0, 0.4 * 255.0); this->addColor("_ps_235", 0.0 * 255.0, 0.8 * 255.0, 0.0 * 255.0 ); this->addColor("_ps_294", 1.0 * 255.0, 0.6 * 255.0, 1.0 * 255.0 ); this->addColor("_ps_353", 0.0 * 255.0, 0.6 * 255.0, 0.6 * 255.0 ); this->addColor("_ps_412", 0.0 * 255.0, 0.0 * 255.0, 0.0 * 255.0 ); this->addColor("_ps_471", 0.3 * 255.0, 0.0 * 255.0, 0.6 * 255.0 ); this->addColor("_ps_529", 0.2 * 255.0, 1.0 * 255.0, 1.0 * 255.0 ); this->addColor("_ps_588", 1.0 * 255.0, 0.5 * 255.0, 0.0 * 255.0 ); this->addColor("_ps_647", 0.6 * 255.0, 0.2 * 255.0, 1.0 * 255.0 ); this->addColor("_ps_706", 0.0 * 255.0, 0.2 * 255.0, 0.4 * 255.0 ); this->addColor("_ps_765", 0.2 * 255.0, 1.0 * 255.0, 0.2 * 255.0 ); this->addColor("_ps_824", 0.0 * 255.0, 0.0 * 255.0, 1.0 * 255.0 ); this->addColor("_ps_882", 1.0 * 255.0, 1.0 * 255.0, 0.8 * 255.0 ); this->addColor("_ps_941", 0.0 * 255.0, 0.4 * 255.0, 0.0 * 255.0 ); this->addColor("_ps_1000", 0.25 * 255.0, 0.25 * 255.0, 0.25 * 255.0 ); Palette powerSurf; powerSurf.setName("power_surf"); powerSurf.addScalarAndColor( 1.0, "_ps_1000"); powerSurf.addScalarAndColor( 0.941, "_ps_941"); powerSurf.addScalarAndColor( 0.882, "_ps_882"); powerSurf.addScalarAndColor( 0.824, "_ps_824"); powerSurf.addScalarAndColor( 0.765, "_ps_765"); powerSurf.addScalarAndColor( 0.706, "_ps_706"); powerSurf.addScalarAndColor( 0.647, "_ps_647"); powerSurf.addScalarAndColor( 0.588, "_ps_588"); powerSurf.addScalarAndColor( 0.529, "_ps_529"); powerSurf.addScalarAndColor( 0.471, "_ps_471"); powerSurf.addScalarAndColor( 0.412, "_ps_412"); powerSurf.addScalarAndColor( 0.353, "_ps_353"); powerSurf.addScalarAndColor( 0.294, "_ps_294"); powerSurf.addScalarAndColor( 0.235, "_ps_235"); powerSurf.addScalarAndColor( 0.176, "_ps_176"); powerSurf.addScalarAndColor( 0.118, "_ps_118"); powerSurf.addScalarAndColor( 0.059, "_ps_059"); powerSurf.addScalarAndColor( 0.0, "_ps_0"); addPalette(powerSurf); } /* * FSL Red palette from WB-289 * * float offset = 100.0; * float step = (255.0 - offset) / 255.0; * for(unsigned char i = 0; i < 255; ++i) * { int red = int(((i + 1) * step) + offset); lut->pushValue(red, 0, 0, i); } * * lut->m_lutName = std::string("Red"); */ if (this->getPaletteByName("fsl_red") == NULL) { Palette fslRed; fslRed.setName("fsl_red"); float offset = 100.0; float step = (255 - offset) / 255.0; for (int32_t i = 254; i >= 0; i--) { const int32_t red = int(((i + 1) * step) + offset); const AString colorName = ("fsl_red_" + AString::number(i)); this->addColor(colorName, red, 0.0, 0.0); const float scalar = (red / 255.0); fslRed.addScalarAndColor(scalar, colorName); } addPalette(fslRed); } if (this->getPaletteByName("fsl_green") == NULL) { Palette fslYellow; fslYellow.setName("fsl_green"); float offset = 100.0; float step = (255 - offset) / 255.0; for (int32_t i = 254; i >= 0; i--) { const int32_t green = int(((i + 1) * step) + offset); const AString colorName = ("fsl_green_" + AString::number(i)); this->addColor(colorName, 0.0, green, 0.0); const float scalar = (green / 255.0); fslYellow.addScalarAndColor(scalar, colorName); } addPalette(fslYellow); } if (this->getPaletteByName("fsl_blue") == NULL) { Palette fslYellow; fslYellow.setName("fsl_blue"); float offset = 100.0; float step = (255 - offset) / 255.0; for (int32_t i = 254; i >= 0; i--) { const int32_t blue = int(((i + 1) * step) + offset); const AString colorName = ("fsl_blue_" + AString::number(i)); this->addColor(colorName, 0.0, 0.0, blue); const float scalar = (blue / 255.0); fslYellow.addScalarAndColor(scalar, colorName); } addPalette(fslYellow); } if (this->getPaletteByName("fsl_yellow") == NULL) { Palette fslYellow; fslYellow.setName("fsl_yellow"); float offset = 100.0; float step = (255 - offset) / 255.0; for (int32_t i = 254; i >= 0; i--) { const int32_t yellow = int(((i + 1) * step) + offset); const AString colorName = ("fsl_yellow_" + AString::number(i)); this->addColor(colorName, yellow, yellow, 0.0); const float scalar = (yellow / 255.0); fslYellow.addScalarAndColor(scalar, colorName); } addPalette(fslYellow); } if (this->getPaletteByName("JET256") == NULL) { this->addColor("_J0", 0.00 *255.0, 0.00 * 255.0, 0.52 * 255.0 ); this->addColor("_J3", 0.00 *255.0, 0.00 * 255.0, 0.53 * 255.0 ); this->addColor("_J7", 0.00 *255.0, 0.00 * 255.0, 0.55 * 255.0 ); this->addColor("_J11", 0.00 *255.0, 0.00 * 255.0, 0.56 * 255.0 ); this->addColor("_J15", 0.00 *255.0, 0.00 * 255.0, 0.58 * 255.0 ); this->addColor("_J19", 0.00 *255.0, 0.00 * 255.0, 0.59 * 255.0 ); this->addColor("_J23", 0.00 *255.0, 0.00 * 255.0, 0.61 * 255.0 ); this->addColor("_J27", 0.00 *255.0, 0.00 * 255.0, 0.63 * 255.0 ); this->addColor("_J31", 0.00 *255.0, 0.00 * 255.0, 0.64 * 255.0 ); this->addColor("_J35", 0.00 *255.0, 0.00 * 255.0, 0.66 * 255.0 ); this->addColor("_J39", 0.00 *255.0, 0.00 * 255.0, 0.67 * 255.0 ); this->addColor("_J43", 0.00 *255.0, 0.00 * 255.0, 0.69 * 255.0 ); this->addColor("_J47", 0.00 *255.0, 0.00 * 255.0, 0.70 * 255.0 ); this->addColor("_J50", 0.00 *255.0, 0.00 * 255.0, 0.72 * 255.0 ); this->addColor("_J54", 0.00 *255.0, 0.00 * 255.0, 0.73 * 255.0 ); this->addColor("_J58", 0.00 *255.0, 0.00 * 255.0, 0.75 * 255.0 ); this->addColor("_J62", 0.00 *255.0, 0.00 * 255.0, 0.77 * 255.0 ); this->addColor("_J66", 0.00 *255.0, 0.00 * 255.0, 0.78 * 255.0 ); this->addColor("_J70", 0.00 *255.0, 0.00 * 255.0, 0.80 * 255.0 ); this->addColor("_J74", 0.00 *255.0, 0.00 * 255.0, 0.81 * 255.0 ); this->addColor("_J78", 0.00 *255.0, 0.00 * 255.0, 0.83 * 255.0 ); this->addColor("_J82", 0.00 *255.0, 0.00 * 255.0, 0.84 * 255.0 ); this->addColor("_J86", 0.00 *255.0, 0.00 * 255.0, 0.86 * 255.0 ); this->addColor("_J90", 0.00 *255.0, 0.00 * 255.0, 0.88 * 255.0 ); this->addColor("_J94", 0.00 *255.0, 0.00 * 255.0, 0.89 * 255.0 ); this->addColor("_J98", 0.00 *255.0, 0.00 * 255.0, 0.91 * 255.0 ); this->addColor("_J101", 0.00 *255.0, 0.00 * 255.0, 0.92 * 255.0 ); this->addColor("_J105", 0.00 *255.0, 0.00 * 255.0, 0.94 * 255.0 ); this->addColor("_J109", 0.00 *255.0, 0.00 * 255.0, 0.95 * 255.0 ); this->addColor("_J113", 0.00 *255.0, 0.00 * 255.0, 0.97 * 255.0 ); this->addColor("_J117", 0.00 *255.0, 0.00 * 255.0, 0.98 * 255.0 ); this->addColor("_J121", 0.00 *255.0, 0.00 * 255.0, 1.00 * 255.0 ); this->addColor("_J125", 0.00 *255.0, 0.02 * 255.0, 1.00 * 255.0 ); this->addColor("_J129", 0.00 *255.0, 0.03 * 255.0, 1.00 * 255.0 ); this->addColor("_J133", 0.00 *255.0, 0.05 * 255.0, 1.00 * 255.0 ); this->addColor("_J137", 0.00 *255.0, 0.06 * 255.0, 1.00 * 255.0 ); this->addColor("_J141", 0.00 *255.0, 0.08 * 255.0, 1.00 * 255.0 ); this->addColor("_J145", 0.00 *255.0, 0.09 * 255.0, 1.00 * 255.0 ); this->addColor("_J149", 0.00 *255.0, 0.11 * 255.0, 1.00 * 255.0 ); this->addColor("_J152", 0.00 *255.0, 0.13 * 255.0, 1.00 * 255.0 ); this->addColor("_J156", 0.00 *255.0, 0.14 * 255.0, 1.00 * 255.0 ); this->addColor("_J160", 0.00 *255.0, 0.16 * 255.0, 1.00 * 255.0 ); this->addColor("_J164", 0.00 *255.0, 0.17 * 255.0, 1.00 * 255.0 ); this->addColor("_J168", 0.00 *255.0, 0.19 * 255.0, 1.00 * 255.0 ); this->addColor("_J172", 0.00 *255.0, 0.20 * 255.0, 1.00 * 255.0 ); this->addColor("_J176", 0.00 *255.0, 0.22 * 255.0, 1.00 * 255.0 ); this->addColor("_J180", 0.00 *255.0, 0.23 * 255.0, 1.00 * 255.0 ); this->addColor("_J184", 0.00 *255.0, 0.25 * 255.0, 1.00 * 255.0 ); this->addColor("_J188", 0.00 *255.0, 0.27 * 255.0, 1.00 * 255.0 ); this->addColor("_J192", 0.00 *255.0, 0.28 * 255.0, 1.00 * 255.0 ); this->addColor("_J196", 0.00 *255.0, 0.30 * 255.0, 1.00 * 255.0 ); this->addColor("_J200", 0.00 *255.0, 0.31 * 255.0, 1.00 * 255.0 ); this->addColor("_J203", 0.00 *255.0, 0.33 * 255.0, 1.00 * 255.0 ); this->addColor("_J207", 0.00 *255.0, 0.34 * 255.0, 1.00 * 255.0 ); this->addColor("_J211", 0.00 *255.0, 0.36 * 255.0, 1.00 * 255.0 ); this->addColor("_J215", 0.00 *255.0, 0.38 * 255.0, 1.00 * 255.0 ); this->addColor("_J219", 0.00 *255.0, 0.39 * 255.0, 1.00 * 255.0 ); this->addColor("_J223", 0.00 *255.0, 0.41 * 255.0, 1.00 * 255.0 ); this->addColor("_J227", 0.00 *255.0, 0.42 * 255.0, 1.00 * 255.0 ); this->addColor("_J231", 0.00 *255.0, 0.44 * 255.0, 1.00 * 255.0 ); this->addColor("_J235", 0.00 *255.0, 0.45 * 255.0, 1.00 * 255.0 ); this->addColor("_J239", 0.00 *255.0, 0.47 * 255.0, 1.00 * 255.0 ); this->addColor("_J243", 0.00 *255.0, 0.48 * 255.0, 1.00 * 255.0 ); this->addColor("_J247", 0.00 *255.0, 0.50 * 255.0, 1.00 * 255.0 ); this->addColor("_J250", 0.00 *255.0, 0.52 * 255.0, 1.00 * 255.0 ); this->addColor("_J254", 0.00 *255.0, 0.53 * 255.0, 1.00 * 255.0 ); this->addColor("_J258", 0.00 *255.0, 0.55 * 255.0, 1.00 * 255.0 ); this->addColor("_J262", 0.00 *255.0, 0.56 * 255.0, 1.00 * 255.0 ); this->addColor("_J266", 0.00 *255.0, 0.58 * 255.0, 1.00 * 255.0 ); this->addColor("_J270", 0.00 *255.0, 0.59 * 255.0, 1.00 * 255.0 ); this->addColor("_J274", 0.00 *255.0, 0.61 * 255.0, 1.00 * 255.0 ); this->addColor("_J278", 0.00 *255.0, 0.63 * 255.0, 1.00 * 255.0 ); this->addColor("_J282", 0.00 *255.0, 0.64 * 255.0, 1.00 * 255.0 ); this->addColor("_J286", 0.00 *255.0, 0.66 * 255.0, 1.00 * 255.0 ); this->addColor("_J290", 0.00 *255.0, 0.67 * 255.0, 1.00 * 255.0 ); this->addColor("_J294", 0.00 *255.0, 0.69 * 255.0, 1.00 * 255.0 ); this->addColor("_J298", 0.00 *255.0, 0.70 * 255.0, 1.00 * 255.0 ); this->addColor("_J301", 0.00 *255.0, 0.72 * 255.0, 1.00 * 255.0 ); this->addColor("_J305", 0.00 *255.0, 0.73 * 255.0, 1.00 * 255.0 ); this->addColor("_J309", 0.00 *255.0, 0.75 * 255.0, 1.00 * 255.0 ); this->addColor("_J313", 0.00 *255.0, 0.77 * 255.0, 1.00 * 255.0 ); this->addColor("_J317", 0.00 *255.0, 0.78 * 255.0, 1.00 * 255.0 ); this->addColor("_J321", 0.00 *255.0, 0.80 * 255.0, 1.00 * 255.0 ); this->addColor("_J325", 0.00 *255.0, 0.81 * 255.0, 1.00 * 255.0 ); this->addColor("_J329", 0.00 *255.0, 0.83 * 255.0, 1.00 * 255.0 ); this->addColor("_J333", 0.00 *255.0, 0.84 * 255.0, 1.00 * 255.0 ); this->addColor("_J337", 0.00 *255.0, 0.86 * 255.0, 1.00 * 255.0 ); this->addColor("_J341", 0.00 *255.0, 0.88 * 255.0, 1.00 * 255.0 ); this->addColor("_J345", 0.00 *255.0, 0.89 * 255.0, 1.00 * 255.0 ); this->addColor("_J349", 0.00 *255.0, 0.91 * 255.0, 1.00 * 255.0 ); this->addColor("_J352", 0.00 *255.0, 0.92 * 255.0, 1.00 * 255.0 ); this->addColor("_J356", 0.00 *255.0, 0.94 * 255.0, 1.00 * 255.0 ); this->addColor("_J360", 0.00 *255.0, 0.95 * 255.0, 1.00 * 255.0 ); this->addColor("_J364", 0.00 *255.0, 0.97 * 255.0, 1.00 * 255.0 ); this->addColor("_J368", 0.00 *255.0, 0.98 * 255.0, 1.00 * 255.0 ); this->addColor("_J372", 0.00 *255.0, 1.00 * 255.0, 1.00 * 255.0 ); this->addColor("_J376", 0.02 *255.0, 1.00 * 255.0, 0.98 * 255.0 ); this->addColor("_J380", 0.03 *255.0, 1.00 * 255.0, 0.97 * 255.0 ); this->addColor("_J384", 0.05 *255.0, 1.00 * 255.0, 0.95 * 255.0 ); this->addColor("_J388", 0.06 *255.0, 1.00 * 255.0, 0.94 * 255.0 ); this->addColor("_J392", 0.08 *255.0, 1.00 * 255.0, 0.92 * 255.0 ); this->addColor("_J396", 0.09 *255.0, 1.00 * 255.0, 0.91 * 255.0 ); this->addColor("_J400", 0.11 *255.0, 1.00 * 255.0, 0.89 * 255.0 ); this->addColor("_J403", 0.13 *255.0, 1.00 * 255.0, 0.88 * 255.0 ); this->addColor("_J407", 0.14 *255.0, 1.00 * 255.0, 0.86 * 255.0 ); this->addColor("_J411", 0.16 *255.0, 1.00 * 255.0, 0.84 * 255.0 ); this->addColor("_J415", 0.17 *255.0, 1.00 * 255.0, 0.83 * 255.0 ); this->addColor("_J419", 0.19 *255.0, 1.00 * 255.0, 0.81 * 255.0 ); this->addColor("_J423", 0.20 *255.0, 1.00 * 255.0, 0.80 * 255.0 ); this->addColor("_J427", 0.22 *255.0, 1.00 * 255.0, 0.78 * 255.0 ); this->addColor("_J431", 0.23 *255.0, 1.00 * 255.0, 0.77 * 255.0 ); this->addColor("_J435", 0.25 *255.0, 1.00 * 255.0, 0.75 * 255.0 ); this->addColor("_J439", 0.27 *255.0, 1.00 * 255.0, 0.73 * 255.0 ); this->addColor("_J443", 0.28 *255.0, 1.00 * 255.0, 0.72 * 255.0 ); this->addColor("_J447", 0.30 *255.0, 1.00 * 255.0, 0.70 * 255.0 ); this->addColor("_J450", 0.31 *255.0, 1.00 * 255.0, 0.69 * 255.0 ); this->addColor("_J454", 0.33 *255.0, 1.00 * 255.0, 0.67 * 255.0 ); this->addColor("_J458", 0.34 *255.0, 1.00 * 255.0, 0.66 * 255.0 ); this->addColor("_J462", 0.36 *255.0, 1.00 * 255.0, 0.64 * 255.0 ); this->addColor("_J466", 0.38 *255.0, 1.00 * 255.0, 0.63 * 255.0 ); this->addColor("_J470", 0.39 *255.0, 1.00 * 255.0, 0.61 * 255.0 ); this->addColor("_J474", 0.41 *255.0, 1.00 * 255.0, 0.59 * 255.0 ); this->addColor("_J478", 0.42 *255.0, 1.00 * 255.0, 0.58 * 255.0 ); this->addColor("_J482", 0.44 *255.0, 1.00 * 255.0, 0.56 * 255.0 ); this->addColor("_J486", 0.45 *255.0, 1.00 * 255.0, 0.55 * 255.0 ); this->addColor("_J490", 0.47 *255.0, 1.00 * 255.0, 0.53 * 255.0 ); this->addColor("_J494", 0.48 *255.0, 1.00 * 255.0, 0.52 * 255.0 ); this->addColor("_J498", 0.50 *255.0, 1.00 * 255.0, 0.50 * 255.0 ); this->addColor("_J501", 0.52 *255.0, 1.00 * 255.0, 0.48 * 255.0 ); this->addColor("_J505", 0.53 *255.0, 1.00 * 255.0, 0.47 * 255.0 ); this->addColor("_J509", 0.55 *255.0, 1.00 * 255.0, 0.45 * 255.0 ); this->addColor("_J513", 0.56 *255.0, 1.00 * 255.0, 0.44 * 255.0 ); this->addColor("_J517", 0.58 *255.0, 1.00 * 255.0, 0.42 * 255.0 ); this->addColor("_J521", 0.59 *255.0, 1.00 * 255.0, 0.41 * 255.0 ); this->addColor("_J525", 0.61 *255.0, 1.00 * 255.0, 0.39 * 255.0 ); this->addColor("_J529", 0.63 *255.0, 1.00 * 255.0, 0.38 * 255.0 ); this->addColor("_J533", 0.64 *255.0, 1.00 * 255.0, 0.36 * 255.0 ); this->addColor("_J537", 0.66 *255.0, 1.00 * 255.0, 0.34 * 255.0 ); this->addColor("_J541", 0.67 *255.0, 1.00 * 255.0, 0.33 * 255.0 ); this->addColor("_J545", 0.69 *255.0, 1.00 * 255.0, 0.31 * 255.0 ); this->addColor("_J549", 0.70 *255.0, 1.00 * 255.0, 0.30 * 255.0 ); this->addColor("_J552", 0.72 *255.0, 1.00 * 255.0, 0.28 * 255.0 ); this->addColor("_J556", 0.73 *255.0, 1.00 * 255.0, 0.27 * 255.0 ); this->addColor("_J560", 0.75 *255.0, 1.00 * 255.0, 0.25 * 255.0 ); this->addColor("_J564", 0.77 *255.0, 1.00 * 255.0, 0.23 * 255.0 ); this->addColor("_J568", 0.78 *255.0, 1.00 * 255.0, 0.22 * 255.0 ); this->addColor("_J572", 0.80 *255.0, 1.00 * 255.0, 0.20 * 255.0 ); this->addColor("_J576", 0.81 *255.0, 1.00 * 255.0, 0.19 * 255.0 ); this->addColor("_J580", 0.83 *255.0, 1.00 * 255.0, 0.17 * 255.0 ); this->addColor("_J584", 0.84 *255.0, 1.00 * 255.0, 0.16 * 255.0 ); this->addColor("_J588", 0.86 *255.0, 1.00 * 255.0, 0.14 * 255.0 ); this->addColor("_J592", 0.88 *255.0, 1.00 * 255.0, 0.13 * 255.0 ); this->addColor("_J596", 0.89 *255.0, 1.00 * 255.0, 0.11 * 255.0 ); this->addColor("_J600", 0.91 *255.0, 1.00 * 255.0, 0.09 * 255.0 ); this->addColor("_J603", 0.92 *255.0, 1.00 * 255.0, 0.08 * 255.0 ); this->addColor("_J607", 0.94 *255.0, 1.00 * 255.0, 0.06 * 255.0 ); this->addColor("_J611", 0.95 *255.0, 1.00 * 255.0, 0.05 * 255.0 ); this->addColor("_J615", 0.97 *255.0, 1.00 * 255.0, 0.03 * 255.0 ); this->addColor("_J619", 0.98 *255.0, 1.00 * 255.0, 0.02 * 255.0 ); this->addColor("_J623", 1.00 *255.0, 1.00 * 255.0, 0.00 * 255.0 ); this->addColor("_J627", 1.00 *255.0, 0.98 * 255.0, 0.00 * 255.0 ); this->addColor("_J631", 1.00 *255.0, 0.97 * 255.0, 0.00 * 255.0 ); this->addColor("_J635", 1.00 *255.0, 0.95 * 255.0, 0.00 * 255.0 ); this->addColor("_J639", 1.00 *255.0, 0.94 * 255.0, 0.00 * 255.0 ); this->addColor("_J643", 1.00 *255.0, 0.92 * 255.0, 0.00 * 255.0 ); this->addColor("_J647", 1.00 *255.0, 0.91 * 255.0, 0.00 * 255.0 ); this->addColor("_J650", 1.00 *255.0, 0.89 * 255.0, 0.00 * 255.0 ); this->addColor("_J654", 1.00 *255.0, 0.88 * 255.0, 0.00 * 255.0 ); this->addColor("_J658", 1.00 *255.0, 0.86 * 255.0, 0.00 * 255.0 ); this->addColor("_J662", 1.00 *255.0, 0.84 * 255.0, 0.00 * 255.0 ); this->addColor("_J666", 1.00 *255.0, 0.83 * 255.0, 0.00 * 255.0 ); this->addColor("_J670", 1.00 *255.0, 0.81 * 255.0, 0.00 * 255.0 ); this->addColor("_J674", 1.00 *255.0, 0.80 * 255.0, 0.00 * 255.0 ); this->addColor("_J678", 1.00 *255.0, 0.78 * 255.0, 0.00 * 255.0 ); this->addColor("_J682", 1.00 *255.0, 0.77 * 255.0, 0.00 * 255.0 ); this->addColor("_J686", 1.00 *255.0, 0.75 * 255.0, 0.00 * 255.0 ); this->addColor("_J690", 1.00 *255.0, 0.73 * 255.0, 0.00 * 255.0 ); this->addColor("_J694", 1.00 *255.0, 0.72 * 255.0, 0.00 * 255.0 ); this->addColor("_J698", 1.00 *255.0, 0.70 * 255.0, 0.00 * 255.0 ); this->addColor("_J701", 1.00 *255.0, 0.69 * 255.0, 0.00 * 255.0 ); this->addColor("_J705", 1.00 *255.0, 0.67 * 255.0, 0.00 * 255.0 ); this->addColor("_J709", 1.00 *255.0, 0.66 * 255.0, 0.00 * 255.0 ); this->addColor("_J713", 1.00 *255.0, 0.64 * 255.0, 0.00 * 255.0 ); this->addColor("_J717", 1.00 *255.0, 0.63 * 255.0, 0.00 * 255.0 ); this->addColor("_J721", 1.00 *255.0, 0.61 * 255.0, 0.00 * 255.0 ); this->addColor("_J725", 1.00 *255.0, 0.59 * 255.0, 0.00 * 255.0 ); this->addColor("_J729", 1.00 *255.0, 0.58 * 255.0, 0.00 * 255.0 ); this->addColor("_J733", 1.00 *255.0, 0.56 * 255.0, 0.00 * 255.0 ); this->addColor("_J737", 1.00 *255.0, 0.55 * 255.0, 0.00 * 255.0 ); this->addColor("_J741", 1.00 *255.0, 0.53 * 255.0, 0.00 * 255.0 ); this->addColor("_J745", 1.00 *255.0, 0.52 * 255.0, 0.00 * 255.0 ); this->addColor("_J749", 1.00 *255.0, 0.50 * 255.0, 0.00 * 255.0 ); this->addColor("_J752", 1.00 *255.0, 0.48 * 255.0, 0.00 * 255.0 ); this->addColor("_J756", 1.00 *255.0, 0.47 * 255.0, 0.00 * 255.0 ); this->addColor("_J760", 1.00 *255.0, 0.45 * 255.0, 0.00 * 255.0 ); this->addColor("_J764", 1.00 *255.0, 0.44 * 255.0, 0.00 * 255.0 ); this->addColor("_J768", 1.00 *255.0, 0.42 * 255.0, 0.00 * 255.0 ); this->addColor("_J772", 1.00 *255.0, 0.41 * 255.0, 0.00 * 255.0 ); this->addColor("_J776", 1.00 *255.0, 0.39 * 255.0, 0.00 * 255.0 ); this->addColor("_J780", 1.00 *255.0, 0.38 * 255.0, 0.00 * 255.0 ); this->addColor("_J784", 1.00 *255.0, 0.36 * 255.0, 0.00 * 255.0 ); this->addColor("_J788", 1.00 *255.0, 0.34 * 255.0, 0.00 * 255.0 ); this->addColor("_J792", 1.00 *255.0, 0.33 * 255.0, 0.00 * 255.0 ); this->addColor("_J796", 1.00 *255.0, 0.31 * 255.0, 0.00 * 255.0 ); this->addColor("_J800", 1.00 *255.0, 0.30 * 255.0, 0.00 * 255.0 ); this->addColor("_J803", 1.00 *255.0, 0.28 * 255.0, 0.00 * 255.0 ); this->addColor("_J807", 1.00 *255.0, 0.27 * 255.0, 0.00 * 255.0 ); this->addColor("_J811", 1.00 *255.0, 0.25 * 255.0, 0.00 * 255.0 ); this->addColor("_J815", 1.00 *255.0, 0.23 * 255.0, 0.00 * 255.0 ); this->addColor("_J819", 1.00 *255.0, 0.22 * 255.0, 0.00 * 255.0 ); this->addColor("_J823", 1.00 *255.0, 0.20 * 255.0, 0.00 * 255.0 ); this->addColor("_J827", 1.00 *255.0, 0.19 * 255.0, 0.00 * 255.0 ); this->addColor("_J831", 1.00 *255.0, 0.17 * 255.0, 0.00 * 255.0 ); this->addColor("_J835", 1.00 *255.0, 0.16 * 255.0, 0.00 * 255.0 ); this->addColor("_J839", 1.00 *255.0, 0.14 * 255.0, 0.00 * 255.0 ); this->addColor("_J843", 1.00 *255.0, 0.13 * 255.0, 0.00 * 255.0 ); this->addColor("_J847", 1.00 *255.0, 0.11 * 255.0, 0.00 * 255.0 ); this->addColor("_J850", 1.00 *255.0, 0.09 * 255.0, 0.00 * 255.0 ); this->addColor("_J854", 1.00 *255.0, 0.08 * 255.0, 0.00 * 255.0 ); this->addColor("_J858", 1.00 *255.0, 0.06 * 255.0, 0.00 * 255.0 ); this->addColor("_J862", 1.00 *255.0, 0.05 * 255.0, 0.00 * 255.0 ); this->addColor("_J866", 1.00 *255.0, 0.03 * 255.0, 0.00 * 255.0 ); this->addColor("_J870", 1.00 *255.0, 0.02 * 255.0, 0.00 * 255.0 ); this->addColor("_J874", 1.00 *255.0, 0.00 * 255.0, 0.00 * 255.0 ); this->addColor("_J878", 0.98 *255.0, 0.00 * 255.0, 0.00 * 255.0 ); this->addColor("_J882", 0.97 *255.0, 0.00 * 255.0, 0.00 * 255.0 ); this->addColor("_J886", 0.95 *255.0, 0.00 * 255.0, 0.00 * 255.0 ); this->addColor("_J890", 0.94 *255.0, 0.00 * 255.0, 0.00 * 255.0 ); this->addColor("_J894", 0.92 *255.0, 0.00 * 255.0, 0.00 * 255.0 ); this->addColor("_J898", 0.91 *255.0, 0.00 * 255.0, 0.00 * 255.0 ); this->addColor("_J901", 0.89 *255.0, 0.00 * 255.0, 0.00 * 255.0 ); this->addColor("_J905", 0.88 *255.0, 0.00 * 255.0, 0.00 * 255.0 ); this->addColor("_J909", 0.86 *255.0, 0.00 * 255.0, 0.00 * 255.0 ); this->addColor("_J913", 0.84 *255.0, 0.00 * 255.0, 0.00 * 255.0 ); this->addColor("_J917", 0.83 *255.0, 0.00 * 255.0, 0.00 * 255.0 ); this->addColor("_J921", 0.81 *255.0, 0.00 * 255.0, 0.00 * 255.0 ); this->addColor("_J925", 0.80 *255.0, 0.00 * 255.0, 0.00 * 255.0 ); this->addColor("_J929", 0.78 *255.0, 0.00 * 255.0, 0.00 * 255.0 ); this->addColor("_J933", 0.77 *255.0, 0.00 * 255.0, 0.00 * 255.0 ); this->addColor("_J937", 0.75 *255.0, 0.00 * 255.0, 0.00 * 255.0 ); this->addColor("_J941", 0.73 *255.0, 0.00 * 255.0, 0.00 * 255.0 ); this->addColor("_J945", 0.72 *255.0, 0.00 * 255.0, 0.00 * 255.0 ); this->addColor("_J949", 0.70 *255.0, 0.00 * 255.0, 0.00 * 255.0 ); this->addColor("_J952", 0.69 *255.0, 0.00 * 255.0, 0.00 * 255.0 ); this->addColor("_J956", 0.67 *255.0, 0.00 * 255.0, 0.00 * 255.0 ); this->addColor("_J960", 0.66 *255.0, 0.00 * 255.0, 0.00 * 255.0 ); this->addColor("_J964", 0.64 *255.0, 0.00 * 255.0, 0.00 * 255.0 ); this->addColor("_J968", 0.63 *255.0, 0.00 * 255.0, 0.00 * 255.0 ); this->addColor("_J972", 0.61 *255.0, 0.00 * 255.0, 0.00 * 255.0 ); this->addColor("_J976", 0.59 *255.0, 0.00 * 255.0, 0.00 * 255.0 ); this->addColor("_J980", 0.58 *255.0, 0.00 * 255.0, 0.00 * 255.0 ); this->addColor("_J984", 0.56 *255.0, 0.00 * 255.0, 0.00 * 255.0 ); this->addColor("_J988", 0.55 *255.0, 0.00 * 255.0, 0.00 * 255.0 ); this->addColor("_J992", 0.53 *255.0, 0.00 * 255.0, 0.00 * 255.0 ); this->addColor("_J996", 0.52 *255.0, 0.00 * 255.0, 0.00 * 255.0 ); this->addColor("_J1000", 0.50 *255.0, 0.00 * 255.0, 0.00 * 255.0 ); Palette JET256; JET256.setName("JET256"); JET256.addScalarAndColor( 1.000, "_J1000"); JET256.addScalarAndColor( 0.996, "_J996"); JET256.addScalarAndColor( 0.992, "_J992"); JET256.addScalarAndColor( 0.988, "_J988"); JET256.addScalarAndColor( 0.984, "_J984"); JET256.addScalarAndColor( 0.980, "_J980"); JET256.addScalarAndColor( 0.976, "_J976"); JET256.addScalarAndColor( 0.973, "_J972"); JET256.addScalarAndColor( 0.969, "_J968"); JET256.addScalarAndColor( 0.965, "_J964"); JET256.addScalarAndColor( 0.961, "_J960"); JET256.addScalarAndColor( 0.957, "_J956"); JET256.addScalarAndColor( 0.953, "_J952"); JET256.addScalarAndColor( 0.949, "_J949"); JET256.addScalarAndColor( 0.945, "_J945"); JET256.addScalarAndColor( 0.941, "_J941"); JET256.addScalarAndColor( 0.937, "_J937"); JET256.addScalarAndColor( 0.933, "_J933"); JET256.addScalarAndColor( 0.929, "_J929"); JET256.addScalarAndColor( 0.925, "_J925"); JET256.addScalarAndColor( 0.922, "_J921"); JET256.addScalarAndColor( 0.918, "_J917"); JET256.addScalarAndColor( 0.914, "_J913"); JET256.addScalarAndColor( 0.910, "_J909"); JET256.addScalarAndColor( 0.906, "_J905"); JET256.addScalarAndColor( 0.902, "_J901"); JET256.addScalarAndColor( 0.898, "_J898"); JET256.addScalarAndColor( 0.894, "_J894"); JET256.addScalarAndColor( 0.890, "_J890"); JET256.addScalarAndColor( 0.886, "_J886"); JET256.addScalarAndColor( 0.882, "_J882"); JET256.addScalarAndColor( 0.878, "_J878"); JET256.addScalarAndColor( 0.875, "_J874"); JET256.addScalarAndColor( 0.871, "_J870"); JET256.addScalarAndColor( 0.867, "_J866"); JET256.addScalarAndColor( 0.863, "_J862"); JET256.addScalarAndColor( 0.859, "_J858"); JET256.addScalarAndColor( 0.855, "_J854"); JET256.addScalarAndColor( 0.851, "_J850"); JET256.addScalarAndColor( 0.847, "_J847"); JET256.addScalarAndColor( 0.843, "_J843"); JET256.addScalarAndColor( 0.839, "_J839"); JET256.addScalarAndColor( 0.835, "_J835"); JET256.addScalarAndColor( 0.831, "_J831"); JET256.addScalarAndColor( 0.827, "_J827"); JET256.addScalarAndColor( 0.824, "_J823"); JET256.addScalarAndColor( 0.820, "_J819"); JET256.addScalarAndColor( 0.816, "_J815"); JET256.addScalarAndColor( 0.812, "_J811"); JET256.addScalarAndColor( 0.808, "_J807"); JET256.addScalarAndColor( 0.804, "_J803"); JET256.addScalarAndColor( 0.800, "_J800"); JET256.addScalarAndColor( 0.796, "_J796"); JET256.addScalarAndColor( 0.792, "_J792"); JET256.addScalarAndColor( 0.788, "_J788"); JET256.addScalarAndColor( 0.784, "_J784"); JET256.addScalarAndColor( 0.780, "_J780"); JET256.addScalarAndColor( 0.776, "_J776"); JET256.addScalarAndColor( 0.773, "_J772"); JET256.addScalarAndColor( 0.769, "_J768"); JET256.addScalarAndColor( 0.765, "_J764"); JET256.addScalarAndColor( 0.761, "_J760"); JET256.addScalarAndColor( 0.757, "_J756"); JET256.addScalarAndColor( 0.753, "_J752"); JET256.addScalarAndColor( 0.749, "_J749"); JET256.addScalarAndColor( 0.745, "_J745"); JET256.addScalarAndColor( 0.741, "_J741"); JET256.addScalarAndColor( 0.737, "_J737"); JET256.addScalarAndColor( 0.733, "_J733"); JET256.addScalarAndColor( 0.729, "_J729"); JET256.addScalarAndColor( 0.725, "_J725"); JET256.addScalarAndColor( 0.722, "_J721"); JET256.addScalarAndColor( 0.718, "_J717"); JET256.addScalarAndColor( 0.714, "_J713"); JET256.addScalarAndColor( 0.710, "_J709"); JET256.addScalarAndColor( 0.706, "_J705"); JET256.addScalarAndColor( 0.702, "_J701"); JET256.addScalarAndColor( 0.698, "_J698"); JET256.addScalarAndColor( 0.694, "_J694"); JET256.addScalarAndColor( 0.690, "_J690"); JET256.addScalarAndColor( 0.686, "_J686"); JET256.addScalarAndColor( 0.682, "_J682"); JET256.addScalarAndColor( 0.678, "_J678"); JET256.addScalarAndColor( 0.675, "_J674"); JET256.addScalarAndColor( 0.671, "_J670"); JET256.addScalarAndColor( 0.667, "_J666"); JET256.addScalarAndColor( 0.663, "_J662"); JET256.addScalarAndColor( 0.659, "_J658"); JET256.addScalarAndColor( 0.655, "_J654"); JET256.addScalarAndColor( 0.651, "_J650"); JET256.addScalarAndColor( 0.647, "_J647"); JET256.addScalarAndColor( 0.643, "_J643"); JET256.addScalarAndColor( 0.639, "_J639"); JET256.addScalarAndColor( 0.635, "_J635"); JET256.addScalarAndColor( 0.631, "_J631"); JET256.addScalarAndColor( 0.627, "_J627"); JET256.addScalarAndColor( 0.624, "_J623"); JET256.addScalarAndColor( 0.620, "_J619"); JET256.addScalarAndColor( 0.616, "_J615"); JET256.addScalarAndColor( 0.612, "_J611"); JET256.addScalarAndColor( 0.608, "_J607"); JET256.addScalarAndColor( 0.604, "_J603"); JET256.addScalarAndColor( 0.600, "_J600"); JET256.addScalarAndColor( 0.596, "_J596"); JET256.addScalarAndColor( 0.592, "_J592"); JET256.addScalarAndColor( 0.588, "_J588"); JET256.addScalarAndColor( 0.584, "_J584"); JET256.addScalarAndColor( 0.580, "_J580"); JET256.addScalarAndColor( 0.576, "_J576"); JET256.addScalarAndColor( 0.573, "_J572"); JET256.addScalarAndColor( 0.569, "_J568"); JET256.addScalarAndColor( 0.565, "_J564"); JET256.addScalarAndColor( 0.561, "_J560"); JET256.addScalarAndColor( 0.557, "_J556"); JET256.addScalarAndColor( 0.553, "_J552"); JET256.addScalarAndColor( 0.549, "_J549"); JET256.addScalarAndColor( 0.545, "_J545"); JET256.addScalarAndColor( 0.541, "_J541"); JET256.addScalarAndColor( 0.537, "_J537"); JET256.addScalarAndColor( 0.533, "_J533"); JET256.addScalarAndColor( 0.529, "_J529"); JET256.addScalarAndColor( 0.525, "_J525"); JET256.addScalarAndColor( 0.522, "_J521"); JET256.addScalarAndColor( 0.518, "_J517"); JET256.addScalarAndColor( 0.514, "_J513"); JET256.addScalarAndColor( 0.510, "_J509"); JET256.addScalarAndColor( 0.506, "_J505"); JET256.addScalarAndColor( 0.502, "_J501"); JET256.addScalarAndColor( 0.498, "_J498"); JET256.addScalarAndColor( 0.494, "_J494"); JET256.addScalarAndColor( 0.490, "_J490"); JET256.addScalarAndColor( 0.486, "_J486"); JET256.addScalarAndColor( 0.482, "_J482"); JET256.addScalarAndColor( 0.478, "_J478"); JET256.addScalarAndColor( 0.475, "_J474"); JET256.addScalarAndColor( 0.471, "_J470"); JET256.addScalarAndColor( 0.467, "_J466"); JET256.addScalarAndColor( 0.463, "_J462"); JET256.addScalarAndColor( 0.459, "_J458"); JET256.addScalarAndColor( 0.455, "_J454"); JET256.addScalarAndColor( 0.451, "_J450"); JET256.addScalarAndColor( 0.447, "_J447"); JET256.addScalarAndColor( 0.443, "_J443"); JET256.addScalarAndColor( 0.439, "_J439"); JET256.addScalarAndColor( 0.435, "_J435"); JET256.addScalarAndColor( 0.431, "_J431"); JET256.addScalarAndColor( 0.427, "_J427"); JET256.addScalarAndColor( 0.424, "_J423"); JET256.addScalarAndColor( 0.420, "_J419"); JET256.addScalarAndColor( 0.416, "_J415"); JET256.addScalarAndColor( 0.412, "_J411"); JET256.addScalarAndColor( 0.408, "_J407"); JET256.addScalarAndColor( 0.404, "_J403"); JET256.addScalarAndColor( 0.400, "_J400"); JET256.addScalarAndColor( 0.396, "_J396"); JET256.addScalarAndColor( 0.392, "_J392"); JET256.addScalarAndColor( 0.388, "_J388"); JET256.addScalarAndColor( 0.384, "_J384"); JET256.addScalarAndColor( 0.380, "_J380"); JET256.addScalarAndColor( 0.376, "_J376"); JET256.addScalarAndColor( 0.373, "_J372"); JET256.addScalarAndColor( 0.369, "_J368"); JET256.addScalarAndColor( 0.365, "_J364"); JET256.addScalarAndColor( 0.361, "_J360"); JET256.addScalarAndColor( 0.357, "_J356"); JET256.addScalarAndColor( 0.353, "_J352"); JET256.addScalarAndColor( 0.349, "_J349"); JET256.addScalarAndColor( 0.345, "_J345"); JET256.addScalarAndColor( 0.341, "_J341"); JET256.addScalarAndColor( 0.337, "_J337"); JET256.addScalarAndColor( 0.333, "_J333"); JET256.addScalarAndColor( 0.329, "_J329"); JET256.addScalarAndColor( 0.325, "_J325"); JET256.addScalarAndColor( 0.322, "_J321"); JET256.addScalarAndColor( 0.318, "_J317"); JET256.addScalarAndColor( 0.314, "_J313"); JET256.addScalarAndColor( 0.310, "_J309"); JET256.addScalarAndColor( 0.306, "_J305"); JET256.addScalarAndColor( 0.302, "_J301"); JET256.addScalarAndColor( 0.298, "_J298"); JET256.addScalarAndColor( 0.294, "_J294"); JET256.addScalarAndColor( 0.290, "_J290"); JET256.addScalarAndColor( 0.286, "_J286"); JET256.addScalarAndColor( 0.282, "_J282"); JET256.addScalarAndColor( 0.278, "_J278"); JET256.addScalarAndColor( 0.275, "_J274"); JET256.addScalarAndColor( 0.271, "_J270"); JET256.addScalarAndColor( 0.267, "_J266"); JET256.addScalarAndColor( 0.263, "_J262"); JET256.addScalarAndColor( 0.259, "_J258"); JET256.addScalarAndColor( 0.255, "_J254"); JET256.addScalarAndColor( 0.251, "_J250"); JET256.addScalarAndColor( 0.247, "_J247"); JET256.addScalarAndColor( 0.243, "_J243"); JET256.addScalarAndColor( 0.239, "_J239"); JET256.addScalarAndColor( 0.235, "_J235"); JET256.addScalarAndColor( 0.231, "_J231"); JET256.addScalarAndColor( 0.227, "_J227"); JET256.addScalarAndColor( 0.224, "_J223"); JET256.addScalarAndColor( 0.220, "_J219"); JET256.addScalarAndColor( 0.216, "_J215"); JET256.addScalarAndColor( 0.212, "_J211"); JET256.addScalarAndColor( 0.208, "_J207"); JET256.addScalarAndColor( 0.204, "_J203"); JET256.addScalarAndColor( 0.200, "_J200"); JET256.addScalarAndColor( 0.196, "_J196"); JET256.addScalarAndColor( 0.192, "_J192"); JET256.addScalarAndColor( 0.188, "_J188"); JET256.addScalarAndColor( 0.184, "_J184"); JET256.addScalarAndColor( 0.180, "_J180"); JET256.addScalarAndColor( 0.176, "_J176"); JET256.addScalarAndColor( 0.173, "_J172"); JET256.addScalarAndColor( 0.169, "_J168"); JET256.addScalarAndColor( 0.165, "_J164"); JET256.addScalarAndColor( 0.161, "_J160"); JET256.addScalarAndColor( 0.157, "_J156"); JET256.addScalarAndColor( 0.153, "_J152"); JET256.addScalarAndColor( 0.149, "_J149"); JET256.addScalarAndColor( 0.145, "_J145"); JET256.addScalarAndColor( 0.141, "_J141"); JET256.addScalarAndColor( 0.137, "_J137"); JET256.addScalarAndColor( 0.133, "_J133"); JET256.addScalarAndColor( 0.129, "_J129"); JET256.addScalarAndColor( 0.125, "_J125"); JET256.addScalarAndColor( 0.122, "_J121"); JET256.addScalarAndColor( 0.118, "_J117"); JET256.addScalarAndColor( 0.114, "_J113"); JET256.addScalarAndColor( 0.110, "_J109"); JET256.addScalarAndColor( 0.106, "_J105"); JET256.addScalarAndColor( 0.102, "_J101"); JET256.addScalarAndColor( 0.098, "_J98"); JET256.addScalarAndColor( 0.094, "_J94"); JET256.addScalarAndColor( 0.090, "_J90"); JET256.addScalarAndColor( 0.086, "_J86"); JET256.addScalarAndColor( 0.082, "_J82"); JET256.addScalarAndColor( 0.078, "_J78"); JET256.addScalarAndColor( 0.075, "_J74"); JET256.addScalarAndColor( 0.071, "_J70"); JET256.addScalarAndColor( 0.067, "_J66"); JET256.addScalarAndColor( 0.063, "_J62"); JET256.addScalarAndColor( 0.059, "_J58"); JET256.addScalarAndColor( 0.055, "_J54"); JET256.addScalarAndColor( 0.051, "_J50"); JET256.addScalarAndColor( 0.047, "_J47"); JET256.addScalarAndColor( 0.043, "_J43"); JET256.addScalarAndColor( 0.039, "_J39"); JET256.addScalarAndColor( 0.035, "_J35"); JET256.addScalarAndColor( 0.031, "_J31"); JET256.addScalarAndColor( 0.027, "_J27"); JET256.addScalarAndColor( 0.024, "_J23"); JET256.addScalarAndColor( 0.020, "_J19"); JET256.addScalarAndColor( 0.016, "_J15"); JET256.addScalarAndColor( 0.012, "_J11"); JET256.addScalarAndColor( 0.008, "_J7"); JET256.addScalarAndColor( 0.004, "_J3"); JET256.addScalarAndColor( 0.000, "_J0"); addPalette(JET256); } if (modifiedStatus == false) { this->clearModified(); } } /** * @return The structure for this file. */ StructureEnum::Enum PaletteFile::getStructure() const { // palette files do not have structure return StructureEnum::INVALID; } /** * Set the structure for this file. * @param structure * New structure for this file. */ void PaletteFile::setStructure(const StructureEnum::Enum /*structure*/) { // palette files do not have structure } /** * @return Get access to the file's metadata. */ GiftiMetaData* PaletteFile::getFileMetaData() { return this->metadata; } /** * @return Get access to unmodifiable file's metadata. */ const GiftiMetaData* PaletteFile::getFileMetaData() const { return this->metadata; } /** * Set the palette mapping based upon the given file type, * file name, data name, and data. * * @param paletteColorMapping * Palette color mapping that is setup. * @param dataFileType * Type of data file. * @param fileName * Name of file. * @param dataName * Name of data. * @param data * The data. * @param numberOfDataElements * Number of elements in data. */ void PaletteFile::setDefaultPaletteColorMapping(PaletteColorMapping* paletteColorMapping, const DataFileTypeEnum::Enum& dataFileType, const AString& fileNameIn, const AString& dataNameIn, const float* data, const int32_t numberOfDataElements) { bool isShapeCurvatureData = false; bool isShapeDepthData = false; bool isShapeData = false; bool isVolumeAnatomyData = false; const AString fileName = fileNameIn.toLower(); const AString dataName = dataNameIn.toLower(); bool invalid = false; bool checkShapeFile = false; bool checkVolume = false; switch (dataFileType) { case DataFileTypeEnum::BORDER: invalid = true; break; case DataFileTypeEnum::CONNECTIVITY_DENSE: break; case DataFileTypeEnum::CONNECTIVITY_DENSE_LABEL: invalid = true; break; case DataFileTypeEnum::CONNECTIVITY_DENSE_PARCEL: break; case DataFileTypeEnum::CONNECTIVITY_PARCEL: break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_DENSE: break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_LABEL: break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_SCALAR: break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_SERIES: break; case DataFileTypeEnum::CONNECTIVITY_DENSE_SCALAR: checkShapeFile = true; break; case DataFileTypeEnum::CONNECTIVITY_DENSE_TIME_SERIES: break; case DataFileTypeEnum::CONNECTIVITY_FIBER_ORIENTATIONS_TEMPORARY: invalid = true; break; case DataFileTypeEnum::CONNECTIVITY_FIBER_TRAJECTORY_TEMPORARY: invalid = true; break; case DataFileTypeEnum::CONNECTIVITY_SCALAR_DATA_SERIES: break; case DataFileTypeEnum::FOCI: invalid = true; break; case DataFileTypeEnum::IMAGE: invalid = true; break; case DataFileTypeEnum::LABEL: invalid = true; break; case DataFileTypeEnum::METRIC: checkShapeFile = true; break; case DataFileTypeEnum::PALETTE: invalid = true; break; case DataFileTypeEnum::RGBA: invalid = true; break; case DataFileTypeEnum::SCENE: invalid = true; break; case DataFileTypeEnum::SPECIFICATION: invalid = true; break; case DataFileTypeEnum::SURFACE: invalid = true; break; case DataFileTypeEnum::UNKNOWN: invalid = true; break; case DataFileTypeEnum::VOLUME: checkVolume = true; break; } if (invalid) { return; } if (checkShapeFile) { if (dataName.contains("curv")) { isShapeData = true; isShapeCurvatureData = true; } else if (dataName.contains("depth")) { isShapeData = true; isShapeDepthData = true; } else if (dataName.contains("shape")) { isShapeData = true; } else if (fileName.contains("curv")) { isShapeData = true; isShapeCurvatureData = true; } else if (fileName.contains("depth")) { isShapeData = true; isShapeDepthData = true; } else if (fileName.contains("shape")) { isShapeData = true; } } float minValue = std::numeric_limits::max(); float maxValue = -minValue; for (int32_t i = 0; i < numberOfDataElements; i++) { const float d = data[i]; if (d > maxValue) { maxValue = d; } if (d < minValue) { minValue = d; } } //bool havePositiveData = (maxValue > 0);//unused, commenting out to prevent compiler warning bool haveNegativeData = (minValue < 0); if (checkVolume) { if ((minValue >= 0) && (maxValue <= 255.0)) { isVolumeAnatomyData = true; } } if (isVolumeAnatomyData) { paletteColorMapping->setThresholdType(PaletteThresholdTypeEnum::THRESHOLD_TYPE_OFF); paletteColorMapping->setSelectedPaletteName("Gray_Interp_Positive"); paletteColorMapping->setInterpolatePaletteFlag(true); paletteColorMapping->setScaleMode(PaletteScaleModeEnum::MODE_AUTO_SCALE_PERCENTAGE); paletteColorMapping->setAutoScalePercentageNegativeMaximum(98.0); paletteColorMapping->setAutoScalePercentageNegativeMinimum(2.0); paletteColorMapping->setAutoScalePercentagePositiveMinimum(2.0); paletteColorMapping->setAutoScalePercentagePositiveMaximum(98.0); } else if (isShapeData) { paletteColorMapping->setThresholdType(PaletteThresholdTypeEnum::THRESHOLD_TYPE_OFF); paletteColorMapping->setSelectedPaletteName("Gray_Interp"); paletteColorMapping->setInterpolatePaletteFlag(true); if (isShapeDepthData) { paletteColorMapping->setScaleMode(PaletteScaleModeEnum::MODE_USER_SCALE); paletteColorMapping->setUserScaleNegativeMaximum(-30.0); paletteColorMapping->setUserScaleNegativeMinimum(0.0); paletteColorMapping->setUserScalePositiveMinimum(0.0); paletteColorMapping->setUserScalePositiveMaximum(10.0); } else if (isShapeCurvatureData) { // paletteColorMapping->setScaleMode(PaletteScaleModeEnum::MODE_USER_SCALE); // paletteColorMapping->setUserScaleNegativeMaximum(-1.5); // paletteColorMapping->setUserScaleNegativeMinimum(0.0); // paletteColorMapping->setUserScalePositiveMinimum(0.0); // paletteColorMapping->setUserScalePositiveMaximum(1.5); paletteColorMapping->setScaleMode(PaletteScaleModeEnum::MODE_AUTO_SCALE_PERCENTAGE); paletteColorMapping->setAutoScalePercentageNegativeMaximum(98.0); paletteColorMapping->setAutoScalePercentageNegativeMinimum(2.0); paletteColorMapping->setAutoScalePercentagePositiveMinimum(2.0); paletteColorMapping->setAutoScalePercentagePositiveMaximum(98.0); } else { paletteColorMapping->setScaleMode(PaletteScaleModeEnum::MODE_AUTO_SCALE_PERCENTAGE); paletteColorMapping->setAutoScalePercentageNegativeMaximum(98.0); paletteColorMapping->setAutoScalePercentageNegativeMinimum(2.0); paletteColorMapping->setAutoScalePercentagePositiveMinimum(2.0); paletteColorMapping->setAutoScalePercentagePositiveMaximum(98.0); } paletteColorMapping->setDisplayNegativeDataFlag(true); paletteColorMapping->setDisplayPositiveDataFlag(true); paletteColorMapping->setDisplayZeroDataFlag(true); } else { if (haveNegativeData) { paletteColorMapping->setThresholdType(PaletteThresholdTypeEnum::THRESHOLD_TYPE_OFF); paletteColorMapping->setSelectedPaletteName("videen-style"); paletteColorMapping->setSelectedPaletteName("ROY-BIG-BL"); paletteColorMapping->setInterpolatePaletteFlag(true); paletteColorMapping->setScaleMode(PaletteScaleModeEnum::MODE_AUTO_SCALE_PERCENTAGE); paletteColorMapping->setAutoScalePercentageNegativeMaximum(98.0); paletteColorMapping->setAutoScalePercentageNegativeMinimum(2.0); paletteColorMapping->setAutoScalePercentagePositiveMinimum(2.0); paletteColorMapping->setAutoScalePercentagePositiveMaximum(98.0); paletteColorMapping->setDisplayNegativeDataFlag(true); paletteColorMapping->setDisplayPositiveDataFlag(true); paletteColorMapping->setDisplayZeroDataFlag(false); } else { paletteColorMapping->setThresholdType(PaletteThresholdTypeEnum::THRESHOLD_TYPE_OFF); paletteColorMapping->setSelectedPaletteName("videen-style"); paletteColorMapping->setSelectedPaletteName("ROY-BIG-BL"); paletteColorMapping->setInterpolatePaletteFlag(true); paletteColorMapping->setScaleMode(PaletteScaleModeEnum::MODE_AUTO_SCALE_PERCENTAGE); paletteColorMapping->setAutoScalePercentageNegativeMaximum(98.0); paletteColorMapping->setAutoScalePercentageNegativeMinimum(2.0); paletteColorMapping->setAutoScalePercentagePositiveMinimum(2.0); paletteColorMapping->setAutoScalePercentagePositiveMaximum(98.0); paletteColorMapping->setDisplayNegativeDataFlag(true); paletteColorMapping->setDisplayPositiveDataFlag(true); paletteColorMapping->setDisplayZeroDataFlag(false); } } paletteColorMapping->clearModified(); } workbench-1.1.1/src/Files/PaletteFile.h000066400000000000000000000100161255417355300177040ustar00rootroot00000000000000#ifndef __PALETTEFILE_H__ #define __PALETTEFILE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretDataFile.h" #include #include #include #include "GiftiLabelTable.h" namespace caret { class GiftiLabel; class GiftiLabelTable; class GiftiMetaData; class Palette; class PaletteColorMapping; /** * File for storing color palettes. */ class PaletteFile : public CaretDataFile { public: PaletteFile(); virtual ~PaletteFile(); public: PaletteFile(const PaletteFile& o); PaletteFile& operator=(const PaletteFile& o); private: void initializeMembersPaletteFile(); public: GiftiLabelTable* getLabelTable(); void clear(); void addColor(const GiftiLabel& pc); void addColor( const AString& name, const int32_t red, const int32_t green, const int32_t blue); void addColor( const AString& name, const int32_t rgb[]); const GiftiLabel* getColor(const int32_t index) const; const GiftiLabel* getColorByName(const AString& colorName) const; int32_t getColorIndex(const AString& colorName) const; int32_t getNumberOfPalettes() const; void addPalette(const Palette& p); Palette* getPalette(const int32_t index) const; Palette* getPaletteByName(const AString& name) const; void removePalette(const int32_t index); virtual bool isEmpty() const; AString toString() const; bool isModified() const; void clearModified(); virtual void readFile(const AString& filename); virtual void writeFile(const AString& filename); virtual StructureEnum::Enum getStructure() const; virtual void setStructure(const StructureEnum::Enum structure); DataFileTypeEnum::Enum getDataFileType() const; virtual GiftiMetaData* getFileMetaData(); virtual const GiftiMetaData* getFileMetaData() const; static void setDefaultPaletteColorMapping(PaletteColorMapping* paletteColorMapping, const DataFileTypeEnum::Enum& dataFileType, const AString& fileName, const AString& dataName, const float* data, const int32_t numberOfDataElements); private: void assignColorsToPalette(Palette& p); void addDefaultPalettes(); void clearAll(); private: /**the colors for the palettes */ GiftiLabelTable labelTable; /**the palettes */ std::vector palettes; GiftiMetaData* metadata; }; } // namespace #endif // __PALETTEFILE_H__ workbench-1.1.1/src/Files/RgbaFile.cxx000066400000000000000000000056051255417355300175440ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretAssert.h" #include "DataFileTypeEnum.h" #include "GiftiFile.h" #include "MathFunctions.h" #include "RgbaFile.h" using namespace caret; /** * Constructor. */ RgbaFile::RgbaFile() : GiftiTypeFile(DataFileTypeEnum::RGBA) { this->initializeMembersRgbaFile(); } /** * Copy constructor. * * @param sf * Surface file that is copied. */ RgbaFile::RgbaFile(const RgbaFile& sf) : GiftiTypeFile(sf) { this->copyHelperRgbaFile(sf); } /** * Assignment operator. * * @param sf * Surface file that is copied. * @return * This surface file with content replaced * by the RgbaFile parameter. */ RgbaFile& RgbaFile::operator=(const RgbaFile& sf) { if (this != &sf) { GiftiTypeFile::operator=(sf); this->copyHelperRgbaFile(sf); } return *this; } /** * Destructor. */ RgbaFile::~RgbaFile() { } /** * Clear the surface file. */ void RgbaFile::clear() { GiftiTypeFile::clear(); } /** * Validate the contents of the file after it * has been read such as correct number of * data arrays and proper data types/dimensions. */ void RgbaFile::validateDataArraysAfterReading() { this->initializeMembersRgbaFile(); this->verifyDataArraysHaveSameNumberOfRows(3, 4); } /** * Get the number of nodes. * * @return * The number of nodes. */ int32_t RgbaFile::getNumberOfNodes() const { int32_t numNodes = 0; int32_t numDataArrays = this->giftiFile->getNumberOfDataArrays(); if (numDataArrays > 0) { numNodes = this->giftiFile->getDataArray(0)->getNumberOfRows(); } return numNodes; } /** * Get the number of columns. * * @return * The number of columns. */ int32_t RgbaFile::getNumberOfColumns() const { const int32_t numCols = this->giftiFile->getNumberOfDataArrays(); return numCols; } /** * Initialize members of this class. */ void RgbaFile::initializeMembersRgbaFile() { } /** * Helps copying files. * * @param sf * File that is copied. */ void RgbaFile::copyHelperRgbaFile(const RgbaFile& /*sf*/) { this->validateDataArraysAfterReading(); } workbench-1.1.1/src/Files/RgbaFile.h000066400000000000000000000035441255417355300171710ustar00rootroot00000000000000 #ifndef __RGBA_FILE_H__ #define __RGBA_FILE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "GiftiTypeFile.h" namespace caret { class GiftiDataArray; /** * \brief An RGBA data file. */ class RgbaFile : public GiftiTypeFile { public: RgbaFile(); RgbaFile(const RgbaFile& sf); RgbaFile& operator=(const RgbaFile& sf); virtual ~RgbaFile(); virtual void clear(); virtual int32_t getNumberOfNodes() const; virtual int32_t getNumberOfColumns() const; protected: /** * Validate the contents of the file after it * has been read such as correct number of * data arrays and proper data types/dimensions. */ virtual void validateDataArraysAfterReading(); void copyHelperRgbaFile(const RgbaFile& sf); void initializeMembersRgbaFile(); private: }; } // namespace #endif // __RGBA_FILE_H__ workbench-1.1.1/src/Files/RibbonMappingHelper.cxx000066400000000000000000000352271255417355300217630ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ /* * For the function TriInfi::vertRayHit(): * Original copyright for PNPOLY, though my version is entirely rewritten and modified * Source: http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html */ /* Copyright (c) 1970-2003, Wm. Randolph Franklin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimers. 2. Redistributions in binary form must reproduce the above copyright notice in the documentation and/or other materials provided with the distribution. 3. The name of W. Randolph Franklin may not be used to endorse or promote products derived from this Software without specific prior written permission. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "RibbonMappingHelper.h" #include "CaretException.h" #include "FloatMatrix.h" #include "MathFunctions.h" #include "SurfaceFile.h" #include "TopologyHelper.h" #include "VolumeSpace.h" #include using namespace caret; using namespace std; //private namespace for implementation details namespace { struct TriInfo { Vector3D m_xyz[3]; float m_planeEq[3];//x coef, y coef, const : z = [0] * x + [1] * y + [2] bool vertRayHit(const float* xyz);//true if a +z ray from point hits this triangle TriInfo(const float* xyz1, const float* xyz2, const float* xyz3); TriInfo() {}; }; struct QuadInfo { TriInfo m_tris[2][2]; int vertRayHit(const float* xyz);//+z ray intersect: 0 if never, 1 if only 1 of the 2 triangulations, 2 if both QuadInfo(const float* xyz1, const float* xyz2, const float* xyz3, const float* xyz4); QuadInfo() {}; }; struct PolyInfo { std::vector m_tris; std::vector m_quads; PolyInfo(const SurfaceFile* innerSurf, const SurfaceFile* outerSurf, const int32_t node);//surfaces MUST be in node correspondence, otherwise SEVERE strangeness, possible crashes PolyInfo() {}; int isInside(const float* xyz);//0 for no, 2 for yes, 1 for if only half the triangulations (between the two triangulations of one of the quad faces) private: void addTri(const SurfaceFile* innerSurf, const SurfaceFile* outerSurf, const int32_t root, const int32_t node2, const int32_t node3);//adds the tri for each surface, plus the quad }; void PolyInfo::addTri(const SurfaceFile* innerSurf, const SurfaceFile* outerSurf, const int32_t root, const int32_t node2, const int32_t node3) { m_tris.push_back(TriInfo(innerSurf->getCoordinate(root), innerSurf->getCoordinate(node2), innerSurf->getCoordinate(node3))); m_tris.push_back(TriInfo(outerSurf->getCoordinate(root), outerSurf->getCoordinate(node2), outerSurf->getCoordinate(node3))); m_quads.push_back(QuadInfo(innerSurf->getCoordinate(node2), innerSurf->getCoordinate(node3), outerSurf->getCoordinate(node3), outerSurf->getCoordinate(node2))); } int PolyInfo::isInside(const float* xyz) { int i, temp, numQuads = (int)m_quads.size(); bool toggle = false; for (i = 0; i < numQuads; ++i) { temp = m_quads[i].vertRayHit(xyz); if (temp == 1) return 1;//means only one of the two triangulations has a hit, therefore, we can return early if (temp) toggle = !toggle;//even/odd winding rule } int numTris = (int)m_tris.size(); for (i = 0; i < numTris; ++i) { if (m_tris[i].vertRayHit(xyz)) toggle = !toggle; } if (toggle) return 2; return 0; } PolyInfo::PolyInfo(const caret::SurfaceFile* innerSurf, const caret::SurfaceFile* outerSurf, const int32_t node) { CaretPointer myTopoHelp = innerSurf->getTopologyHelper(); int numTiles; const int* myTiles = myTopoHelp->getNodeTiles(node, numTiles); for (int i = 0; i < numTiles; ++i) { const int32_t* myTri = innerSurf->getTriangle(myTiles[i]); if (myTri[0] == node) { addTri(innerSurf, outerSurf, myTri[0], myTri[1], myTri[2]); } else { if (myTri[1] == node) { addTri(innerSurf, outerSurf, myTri[1], myTri[2], myTri[0]); } else { addTri(innerSurf, outerSurf, myTri[2], myTri[0], myTri[1]); } } } } QuadInfo::QuadInfo(const float* xyz1, const float* xyz2, const float* xyz3, const float* xyz4) { m_tris[0][0] = TriInfo(xyz1, xyz2, xyz3); m_tris[0][1] = TriInfo(xyz1, xyz3, xyz4); m_tris[1][0] = TriInfo(xyz1, xyz2, xyz4); m_tris[1][1] = TriInfo(xyz2, xyz3, xyz4); } int QuadInfo::vertRayHit(const float* xyz) { int ret = 0; if (m_tris[0][0].vertRayHit(xyz) != m_tris[0][1].vertRayHit(xyz)) ++ret; if (m_tris[1][0].vertRayHit(xyz) != m_tris[1][1].vertRayHit(xyz)) ++ret; return ret; } TriInfo::TriInfo(const float* xyz1, const float* xyz2, const float* xyz3) { m_xyz[0] = xyz1; m_xyz[1] = xyz2; m_xyz[2] = xyz3; FloatMatrix myRref; myRref.resize(3, 4); for (int i = 0; i < 3; ++i)//ax + by + c = z { myRref[i][0] = m_xyz[i][0];//coefficient of a myRref[i][1] = m_xyz[i][1];//coefficient of b myRref[i][2] = 1;//coefficient of c myRref[i][3] = m_xyz[i][2];//what it equals } FloatMatrix myResult = myRref.reducedRowEchelon(); m_planeEq[0] = myResult[0][3];//a m_planeEq[1] = myResult[1][3];//b m_planeEq[2] = myResult[2][3];//c float sanity = m_planeEq[0] + m_planeEq[1] + m_planeEq[2]; if (!MathFunctions::isNumeric(sanity)) { m_planeEq[0] = sanity;//make sure the first element easily identifies vertical triangles } } bool TriInfo::vertRayHit(const float* xyz) { if (!MathFunctions::isNumeric(m_planeEq[0])) {//plane is vertical, nothing can hit it return false; } float planeZ = xyz[0] * m_planeEq[0] + xyz[1] * m_planeEq[1] + m_planeEq[2];//ax + by + c = z if (xyz[2] >= planeZ) {//point is above the plane return false; }//test if the x, y projection has the point inside the triangle //below logic copied from PNPOLY by Wm. Randolph Franklin, swapped x for y, and slightly rewritten, for the special case of 3 vertices bool inside = false; for (int j = 2, i = 0; i < 3; ++i)//start with the wraparound case { if ((m_xyz[i][0] < xyz[0]) != (m_xyz[j][0] < xyz[0])) {//if one vertex is on one side of the point in the x direction, and the other is on the other side (equal case is treated as greater) int ti, tj; if (m_xyz[i][0] < m_xyz[j][0])//reorient the segment consistently to get a consistent answer { ti = i; tj = j; } else { ti = j; tj = i; } if ((m_xyz[ti][1] - m_xyz[tj][1]) / (m_xyz[ti][0] - m_xyz[tj][0]) * (xyz[0] - m_xyz[tj][0]) + m_xyz[tj][1] > xyz[1]) {//if the point on the line described by the two vertices with the same x coordinate is above (greater y) than the test point inside = !inside;//even/odd winding rule again } } j = i;//consecutive vertices, does 2,0 then 0,1 then 1,2 } return inside; } float computeVoxelFraction(const VolumeSpace& myVolSpace, const int64_t* ijk, PolyInfo& myPoly, const int divisions, const Vector3D& ivec, const Vector3D& jvec, const Vector3D& kvec) { Vector3D myLowCorner; myVolSpace.indexToSpace(ijk[0] - 0.5f, ijk[1] - 0.5f, ijk[2] - 0.5f, myLowCorner); int inside = 0; Vector3D istep = ivec / divisions; Vector3D jstep = jvec / divisions; Vector3D kstep = kvec / divisions; myLowCorner += istep * 0.5f + jstep * 0.5f + kstep * 0.5f; for (int i = 0; i < divisions; ++i) { Vector3D tempVeci = myLowCorner + istep * i; for (int j = 0; j < divisions; ++j) { Vector3D tempVecj = tempVeci + jstep * j; for (int k = 0; k < divisions; ++k) { Vector3D thisPoint = tempVecj + kstep * k; inside += myPoly.isInside(thisPoint); } } } return ((float)inside) / (divisions * divisions * divisions * 2); } } void RibbonMappingHelper::computeWeightsRibbon(vector >& myWeightsOut, const VolumeSpace& myVolSpace, const SurfaceFile* innerSurf, const SurfaceFile* outerSurf, const float* roiFrame, const int& numDivisions) { if (!innerSurf->hasNodeCorrespondence(*outerSurf)) { throw CaretException("input surfaces to ribbon mapping do not have vertex correspondence"); } if (numDivisions < 1) { throw CaretException("number of voxel subdivisions must be positive for ribbon mapping"); } int64_t numNodes = outerSurf->getNumberOfNodes(); myWeightsOut.resize(numNodes); Vector3D origin, ivec, jvec, kvec;//these are the spatial projections of the ijk unit vectors (also, the offset that specifies the origin) myVolSpace.getSpacingVectors(ivec, jvec, kvec, origin); const float* outerCoords = outerSurf->getCoordinateData(); const float* innerCoords = innerSurf->getCoordinateData(); const int64_t* myDims = myVolSpace.getDims(); #pragma omp CARET_PAR { int maxVoxelCount = 10;//guess for preallocating vectors CaretPointer myTopoHelp = innerSurf->getTopologyHelper(); #pragma omp CARET_FOR schedule(dynamic) for (int64_t node = 0; node < numNodes; ++node) { myWeightsOut[node].clear(); myWeightsOut[node].reserve(maxVoxelCount); float tempf; int64_t node3 = node * 3; PolyInfo myPoly(innerSurf, outerSurf, node);//build the polygon Vector3D minIndex, maxIndex, tempvec; myVolSpace.spaceToIndex(innerCoords + node3, minIndex);//find the bounding box in VOLUME INDEX SPACE, starting with the center nodes maxIndex = minIndex; myVolSpace.spaceToIndex(outerCoords + node3, tempvec); for (int i = 0; i < 3; ++i) { if (tempvec[i] < minIndex[i]) minIndex[i] = tempvec[i]; if (tempvec[i] > maxIndex[i]) maxIndex[i] = tempvec[i]; } int numNeigh; const int* myNeighList = myTopoHelp->getNodeNeighbors(node, numNeigh);//and now the neighbors for (int j = 0; j < numNeigh; ++j) { int neigh3 = myNeighList[j] * 3; myVolSpace.spaceToIndex(outerCoords + neigh3, tempvec); for (int i = 0; i < 3; ++i) { if (tempvec[i] < minIndex[i]) minIndex[i] = tempvec[i]; if (tempvec[i] > maxIndex[i]) maxIndex[i] = tempvec[i]; } myVolSpace.spaceToIndex(innerCoords + neigh3, tempvec); for (int i = 0; i < 3; ++i) { if (tempvec[i] < minIndex[i]) minIndex[i] = tempvec[i]; if (tempvec[i] > maxIndex[i]) maxIndex[i] = tempvec[i]; } } int startIndex[3], endIndex[3]; for (int i = 0; i < 3; ++i) { startIndex[i] = (int)ceil(minIndex[i] - 0.5f);//give an extra half voxel in order to get anything which could have some polygon in it endIndex[i] = (int)floor(maxIndex[i] + 0.5f) + 1;//ditto, plus the one-after end convention if (startIndex[i] < 0) startIndex[i] = 0;//keep it inside the volume boundaries if (endIndex[i] > myDims[i]) endIndex[i] = myDims[i]; } int64_t ijk[3]; for (ijk[0] = startIndex[0]; ijk[0] < endIndex[0]; ++ijk[0]) { for (ijk[1] = startIndex[1]; ijk[1] < endIndex[1]; ++ijk[1]) { for (ijk[2] = startIndex[2]; ijk[2] < endIndex[2]; ++ijk[2]) { if (roiFrame == NULL || roiFrame[myVolSpace.getIndex(ijk)] > 0.0f) { tempf = computeVoxelFraction(myVolSpace, ijk, myPoly, numDivisions, ivec, jvec, kvec); if (tempf != 0.0f) { myWeightsOut[node].push_back(VoxelWeight(tempf, ijk)); } } } } } if ((int)myWeightsOut[node].size() > maxVoxelCount) {//capacity() would use more memory maxVoxelCount = myWeightsOut[node].size(); } } } } workbench-1.1.1/src/Files/RibbonMappingHelper.h000066400000000000000000000036241255417355300214040ustar00rootroot00000000000000#ifndef __RIBBON_MAPPING_HELPER_H__ #define __RIBBON_MAPPING_HELPER_H__ /*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "stdint.h" #include #include namespace caret { class SurfaceFile; class VolumeSpace; struct VoxelWeight {//for precomputation in ribbon/myelin style volume to surface mapping float weight; int64_t ijk[3]; VoxelWeight() { }; VoxelWeight(const float weightIn, const int64_t* ijkIn) { weight = weightIn; ijk[0] = ijkIn[0]; ijk[1] = ijkIn[1]; ijk[2] = ijkIn[2]; } }; class RibbonMappingHelper { public: ///compute per-vertex ribbon mapping weights - surfaces must have vertex correspondence, or an exception is thrown static void computeWeightsRibbon(std::vector >& myWeightsOut, const VolumeSpace& myVolSpace, const SurfaceFile* innerSurf, const SurfaceFile* outerSurf, const float* roiFrame = NULL, const int& numDivisions = 3); }; } #endif //__RIBBON_MAPPING_HELPER_H__ workbench-1.1.1/src/Files/SceneFile.cxx000066400000000000000000000311061255417355300177210ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #define __SCENE_FILE_DECLARE__ #include "SceneFile.h" #undef __SCENE_FILE_DECLARE__ #include "CaretAssert.h" #include "CaretLogger.h" #include "DataFileContentInformation.h" #include "DataFileException.h" #include "FileAdapter.h" #include "FileInformation.h" #include "GiftiMetaData.h" #include "Scene.h" #include "SceneFileSaxReader.h" #include "SceneInfo.h" #include "SceneWriterXml.h" #include "XmlSaxParser.h" #include "XmlWriter.h" using namespace caret; /** * \class caret::SceneFile * \brief Contains scenes that reproduce Workbench state */ /** * Constructor. */ SceneFile::SceneFile() : CaretDataFile(DataFileTypeEnum::SCENE) { m_metadata = new GiftiMetaData(); } /** * Destructor. */ SceneFile::~SceneFile() { delete m_metadata; for (std::vector::iterator iter = m_scenes.begin(); iter != m_scenes.end(); iter++) { delete *iter; } m_scenes.clear(); } /** * Clear the contents of this file. */ void SceneFile::clear() { CaretDataFile::clear(); m_metadata->clear(); for (std::vector::iterator iter = m_scenes.begin(); iter != m_scenes.end(); iter++) { delete *iter; } m_scenes.clear(); } /** * @return true if the file is empty (no scenes) else false. */ bool SceneFile::isEmpty() const { return m_scenes.empty(); } /** * Add the given scene to the file. The file then * takes ownership of the scene. * * @param scene * Scene that is added. */ void SceneFile::addScene(Scene* scene) { CaretAssert(scene); m_scenes.push_back(scene); setModified(); } /** * Insert a scene above the given scene. The file then * takes ownership of the scene. * * @param newScene * New scene that is inserted. * @param insertAboveThisScene * The new scene is inserted above (before) this scene. */ void SceneFile::insertScene(Scene* newScene, const Scene* insertAboveThisScene) { CaretAssert(newScene); CaretAssert(insertAboveThisScene); std::vector tempSceneVector; bool newSceneInsertedFlag = false; for (std::vector::iterator iter = m_scenes.begin(); iter != m_scenes.end(); iter++) { Scene* scene = *iter; if (scene == insertAboveThisScene) { newSceneInsertedFlag = true; tempSceneVector.push_back(newScene); } tempSceneVector.push_back(scene); } if ( ! newSceneInsertedFlag) { m_scenes.push_back(newScene); CaretLogSevere("Scene insertion did not find \"insert above scene\""); } m_scenes = tempSceneVector; } /** * Replace a scene. * @param newScene * New scene * @param sceneThatIsReplacedAndDeleted * Scene that is replaced and delete so DO NOT * reference this scene after calling this method. */ void SceneFile::replaceScene(Scene* newScene, Scene* sceneThatIsReplacedAndDeleted) { CaretAssert(newScene); CaretAssert(sceneThatIsReplacedAndDeleted); const int32_t numScenes = getNumberOfScenes(); for (int32_t i = 0; i < numScenes; i++) { if (m_scenes[i] == sceneThatIsReplacedAndDeleted) { delete m_scenes[i]; m_scenes[i] = newScene; setModified(); return; } } CaretAssertMessage(0, "Replacing scene failed due to scene not found."); CaretLogSevere("Replacing scene failed due to scene not found."); } /** * @return The number of scenes. */ int32_t SceneFile::getNumberOfScenes() const { return m_scenes.size(); } /** * Get the scene at the given index. * @param indx * Index of the scene. * @return * Scene at the given index. */ Scene* SceneFile::getSceneAtIndex(const int32_t indx) { CaretAssertVectorIndex(m_scenes, indx); return m_scenes[indx]; } /** * Get the scene with the given name. * @param sceneName * Name of scene. * @return * Scene with given name or NULL if no scene with * the given name. */ Scene* SceneFile::getSceneWithName(const AString& sceneName) { const int32_t numScenes = getNumberOfScenes(); for (int32_t i = 0; i < numScenes; i++) { Scene* scene = getSceneAtIndex(i); if (scene->getName() == sceneName) { return scene; } } return NULL; } /** * Remove the given scene. * @param scene * Scene that should be removed. */ void SceneFile::removeScene(Scene* scene) { CaretAssert(scene); std::vector::iterator iter = std::find(m_scenes.begin(), m_scenes.end(), scene); if (iter != m_scenes.end()) { m_scenes.erase(iter); delete scene; setModified(); } } /** * Remove the scene at the given index. * @param indx * Index of the scene. */ void SceneFile::removeSceneAtIndex(const int32_t indx) { CaretAssertVectorIndex(m_scenes, indx); Scene* scene = getSceneAtIndex(indx); removeScene(scene); } /** * Reorder the scenes given the newly ordered scenes. * Any existing scenes not in the newly ordered scenes are * removed. * * @param orderedScenes * Newly ordered scenes. */ void SceneFile::reorderScenes(std::vector& orderedScenes) { /* * Make copy of pointers to existing scenes */ std::vector oldSceneVector = m_scenes; /* * Replace scenes with newly ordered scenes */ m_scenes = orderedScenes; /* * If an existing scene is not in the newly ordered scenes, * remove it. */ for (std::vector::iterator iter = oldSceneVector.begin(); iter != oldSceneVector.end(); iter++) { Scene* scene = *iter; if (std::find(m_scenes.begin(), m_scenes.end(), scene) == m_scenes.end()) { delete scene; } } setModified(); } /** * @return The structure for this file. */ StructureEnum::Enum SceneFile::getStructure() const { return StructureEnum::ALL; } /** * Set the structure for this file. * @param structure * New structure for this file. */ void SceneFile::setStructure(const StructureEnum::Enum /*structure*/) { /* ignore, not a structure related file */ } /** * @return Get access to the file's metadata. */ GiftiMetaData* SceneFile::getFileMetaData() { return m_metadata; } /** * @return Get access to unmodifiable file's metadata. */ const GiftiMetaData* SceneFile::getFileMetaData() const { return m_metadata; } /** * Read the scene file. * @param filenameIn * Name of scene file. * @throws DataFileException * If there is an error reading the file. */ void SceneFile::readFile(const AString& filenameIn) { clear(); AString filename = filenameIn; if (DataFile::isFileOnNetwork(filename) == false) { FileInformation specInfo(filename); filename = specInfo.getAbsoluteFilePath(); } checkFileReadability(filename); this->setFileName(filename); SceneFileSaxReader saxReader(this); std::auto_ptr parser(XmlSaxParser::createXmlParser()); try { parser->parseFile(filename, &saxReader); } catch (const XmlSaxParserException& e) { clear(); this->setFileName(""); int lineNum = e.getLineNumber(); int colNum = e.getColumnNumber(); AString msg = "Parse Error while reading:"; if ((lineNum >= 0) && (colNum >= 0)) { msg += (" line/col (" + AString::number(e.getLineNumber()) + "/" + AString::number(e.getColumnNumber()) + ")"); } msg += (": " + e.whatString()); DataFileException dfe(filenameIn, msg); CaretLogThrowing(dfe); throw dfe; } this->setFileName(filename); this->clearModified(); } /** * Write the scene file. * @param filename * Name of scene file. * @throws DataFileException * If there is an error writing the file. */ void SceneFile::writeFile(const AString& filename) { checkFileWritability(filename); this->setFileName(filename); try { // // Format the version string so that it ends with at most one zero // const AString versionString = AString::number(SceneFile::getFileVersion(), 'f', 1); // // Open the file // FileAdapter file; AString errorMessage; QTextStream* textStream = file.openQTextStreamForWritingFile(this->getFileName(), errorMessage); if (textStream == NULL) { throw DataFileException(filename, errorMessage); } // // Create the xml writer // XmlWriter xmlWriter(*textStream); // // Write header info // xmlWriter.writeStartDocument("1.0"); // // Write root element // XmlAttributes attributes; //attributes.addAttribute("xmlns:xsi", // "http://www.w3.org/2001/XMLSchema-instance"); //attributes.addAttribute("xsi:noNamespaceSchemaLocation", // "http://brainvis.wustl.edu/caret6/xml_schemas/GIFTI_Caret.xsd"); attributes.addAttribute(SceneFile::XML_ATTRIBUTE_VERSION, versionString); xmlWriter.writeStartElement(SceneFile::XML_TAG_SCENE_FILE, attributes); // // Write Metadata // if (m_metadata != NULL) { m_metadata->writeAsXML(xmlWriter); } const int32_t numScenes = this->getNumberOfScenes(); /* * Write the scene info directory */ xmlWriter.writeStartElement(SceneFile::XML_TAG_SCENE_INFO_DIRECTORY_TAG); for (int32_t i = 0; i < numScenes; i++) { m_scenes[i]->getSceneInfo()->writeSceneInfo(xmlWriter, i); } xmlWriter.writeEndElement(); // // Write scenes // SceneWriterXml sceneWriter(xmlWriter, this->getFileName()); for (int32_t i = 0; i < numScenes; i++) { sceneWriter.writeScene(*m_scenes[i], i); } xmlWriter.writeEndElement(); xmlWriter.writeEndDocument(); file.close(); this->clearModified(); } catch (const GiftiException& e) { throw DataFileException(e); } catch (const XmlException& e) { throw DataFileException(e); } } /** * Add information about the file to the data file information. * * @param dataFileInformation * Consolidates information about a data file. */ void SceneFile::addToDataFileContentInformation(DataFileContentInformation& dataFileInformation) { CaretDataFile::addToDataFileContentInformation(dataFileInformation); const int32_t numScenes = getNumberOfScenes(); if (numScenes > 0) { AString sceneNamesText = "SCENE NAMES"; for (int32_t i = 0; i < numScenes; i++) { const Scene* scene = getSceneAtIndex(i); sceneNamesText.appendWithNewLine(" " + scene->getName()); } dataFileInformation.addText(sceneNamesText); } } workbench-1.1.1/src/Files/SceneFile.h000066400000000000000000000067021255417355300173520ustar00rootroot00000000000000#ifndef __SCENE_FILE__H_ #define __SCENE_FILE__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretDataFile.h" namespace caret { class Scene; class SceneFile : public CaretDataFile { public: SceneFile(); virtual ~SceneFile(); private: SceneFile(const SceneFile&); SceneFile& operator=(const SceneFile&); public: virtual void addToDataFileContentInformation(DataFileContentInformation& dataFileInformation); void clear(); void readFile(const AString& filename); void writeFile(const AString& filename); bool isEmpty() const; void addScene(Scene* scene); void insertScene(Scene* newScene, const Scene* insertAboveThisScene); void replaceScene(Scene* newScene, Scene* sceneThatIsReplacedAndDeleted); int32_t getNumberOfScenes() const; Scene* getSceneAtIndex(const int32_t indx); Scene* getSceneWithName(const AString& sceneName); void removeScene(Scene* scene); void removeSceneAtIndex(const int32_t indx); StructureEnum::Enum getStructure() const; void setStructure(const StructureEnum::Enum structure); GiftiMetaData* getFileMetaData(); const GiftiMetaData* getFileMetaData() const; void reorderScenes(std::vector& orderedScenes); // ADD_NEW_METHODS_HERE /** Version of file */ static float getFileVersion() { return s_sceneFileVersion; } /** XML Tag for scene file */ static const AString XML_TAG_SCENE_FILE; /** * XML Tag for Scene Info Directory element. */ static const AString XML_TAG_SCENE_INFO_DIRECTORY_TAG; /** XML Tag for Version attribute */ static const AString XML_ATTRIBUTE_VERSION; private: /** the scenes*/ std::vector m_scenes; /** the metadata */ GiftiMetaData* m_metadata; // ADD_NEW_MEMBERS_HERE /** Version of this SceneFile */ static const float s_sceneFileVersion; }; #ifdef __SCENE_FILE_DECLARE__ const AString SceneFile::XML_TAG_SCENE_FILE = "SceneFile"; const AString SceneFile::XML_ATTRIBUTE_VERSION = "Version"; const AString SceneFile::XML_TAG_SCENE_INFO_DIRECTORY_TAG = "SceneInfoDirectory"; const float SceneFile::s_sceneFileVersion = 2.0; #endif // __SCENE_FILE_DECLARE__ } // namespace #endif //__SCENE_FILE__H_ workbench-1.1.1/src/Files/SceneFileSaxReader.cxx000066400000000000000000000264231255417355300215260ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretAssert.h" #include "CaretLogger.h" #include "GiftiMetaDataSaxReader.h" #include "GiftiXmlElements.h" #include "Scene.h" #include "SceneFile.h" #include "SceneFileSaxReader.h" #include "SceneInfo.h" #include "SceneInfoSaxReader.h" #include "SceneXmlElements.h" #include "XmlAttributes.h" #include "XmlException.h" #include "XmlUtilities.h" using namespace caret; /** * \class caret::SceneFileSaxReader * \brief Reads a scene file using a SAX XML Parser. */ /** * constructor. */ SceneFileSaxReader::SceneFileSaxReader(SceneFile* sceneFile) { CaretAssert(sceneFile); m_sceneFile = sceneFile; m_state = STATE_NONE; m_stateStack.push(m_state); m_elementText = ""; m_metaDataSaxReader = NULL; m_sceneSaxReader = NULL; m_sceneInfoSaxReader = NULL; m_scene = NULL; } /** * destructor. */ SceneFileSaxReader::~SceneFileSaxReader() { /* * If reading fails, allocated items need to be deleted. */ if (m_metaDataSaxReader != NULL) { delete m_metaDataSaxReader; } if (m_sceneSaxReader != NULL) { delete m_sceneSaxReader; } if (m_sceneInfoSaxReader != NULL) { delete m_sceneInfoSaxReader; } if (m_scene != NULL) { delete m_scene; } } /** * start an element. */ void SceneFileSaxReader::startElement(const AString& namespaceURI, const AString& localName, const AString& qName, const XmlAttributes& attributes) { const STATE previousState = m_state; switch (m_state) { case STATE_NONE: if (qName == SceneFile::XML_TAG_SCENE_FILE) { m_state = STATE_SCENE_FILE; // // Check version of file being read // const float version = attributes.getValueAsFloat(SceneFile::XML_ATTRIBUTE_VERSION); if (version > SceneFile::getFileVersion()) { AString msg = XmlUtilities::createInvalidVersionMessage(SceneFile::getFileVersion(), version); XmlSaxParserException e(msg); CaretLogThrowing(e); throw e; } } else { const AString msg = XmlUtilities::createInvalidRootElementMessage(SceneFile::XML_TAG_SCENE_FILE, qName); XmlSaxParserException e(msg); CaretLogThrowing(e); throw e; } break; case STATE_SCENE_INFO_DIRECTORY: if (qName == SceneXmlElements::SCENE_INFO_TAG) { m_state = STATE_SCENE_INFO; m_sceneInfoIndex = attributes.getValueAsIntRequired(SceneXmlElements::SCENE_INFO_INDEX_ATTRIBUTE); m_sceneInfo = new SceneInfo(); m_sceneInfoSaxReader = new SceneInfoSaxReader(m_sceneFile->getFileName(), m_sceneInfo); m_sceneInfoSaxReader->startElement(namespaceURI, localName, qName, attributes); } else { const AString msg = XmlUtilities::createInvalidChildElementMessage(SceneXmlElements::SCENE_INFO_TAG, qName); XmlSaxParserException e(msg); CaretLogThrowing(e); throw e; } break; case STATE_SCENE_INFO: m_sceneInfoSaxReader->startElement(namespaceURI, localName, qName, attributes); break; case STATE_SCENE: m_sceneSaxReader->startElement(namespaceURI, localName, qName, attributes); break; case STATE_SCENE_FILE: if (qName == GiftiXmlElements::TAG_METADATA) { m_state = STATE_METADATA; m_metaDataSaxReader = new GiftiMetaDataSaxReader(m_sceneFile->getFileMetaData()); m_metaDataSaxReader->startElement(namespaceURI, localName, qName, attributes); } else if (qName == SceneFile::XML_TAG_SCENE_INFO_DIRECTORY_TAG) { m_state = STATE_SCENE_INFO_DIRECTORY; break; } else if (qName == SceneXmlElements::SCENE_TAG) { m_state = STATE_SCENE; const AString sceneTypeName = attributes.getValue(SceneXmlElements::SCENE_TYPE_ATTRIBUTE); bool validName = false; const SceneTypeEnum::Enum sceneType = SceneTypeEnum::fromName(sceneTypeName, &validName); if (validName == false) { const AString msg = XmlUtilities::createInvalidAttributeMessage(SceneXmlElements::SCENE_TAG, SceneXmlElements::SCENE_TYPE_ATTRIBUTE, sceneTypeName); XmlSaxParserException e(msg); CaretLogThrowing(e); throw e; } m_scene = new Scene(sceneType); m_sceneSaxReader = new SceneSaxReader(m_sceneFile->getFileName(), m_scene); m_sceneSaxReader->startElement(namespaceURI, localName, qName, attributes); } else { const AString msg = XmlUtilities::createInvalidChildElementMessage(SceneXmlElements::SCENE_TAG, qName); XmlSaxParserException e(msg); CaretLogThrowing(e); throw e; } break; case STATE_METADATA: m_metaDataSaxReader->startElement(namespaceURI, localName, qName, attributes); break; } // // Save previous state // m_stateStack.push(previousState); m_elementText = ""; } /** * end an element. */ void SceneFileSaxReader::endElement(const AString& namespaceURI, const AString& localName, const AString& qName) { switch (m_state) { case STATE_NONE: break; case STATE_SCENE: CaretAssert(m_scene); CaretAssert(m_sceneSaxReader); m_sceneSaxReader->endElement(namespaceURI, localName, qName); if (qName == SceneXmlElements::SCENE_TAG) { m_sceneFile->addScene(m_scene); m_scene = NULL; // do not delete since added to border file delete m_sceneSaxReader; m_sceneSaxReader = NULL; } break; case STATE_SCENE_INFO_DIRECTORY: break; case STATE_SCENE_INFO: CaretAssert(m_sceneInfo); CaretAssert(m_sceneInfoSaxReader); m_sceneInfoSaxReader->endElement(namespaceURI, localName, qName); if (qName == SceneXmlElements::SCENE_INFO_TAG) { m_sceneInfoMap.insert(std::make_pair(m_sceneInfoIndex, m_sceneInfo)); delete m_sceneInfoSaxReader; m_sceneInfoSaxReader = NULL; } break; case STATE_SCENE_FILE: { for (std::map::iterator iter = m_sceneInfoMap.begin(); iter != m_sceneInfoMap.end(); iter++) { const int32_t sceneIndex = iter->first; SceneInfo* sceneInfo = iter->second; CaretAssert(sceneInfo); if ((sceneIndex >= 0) && (sceneIndex < m_sceneFile->getNumberOfScenes())) { Scene* scene = m_sceneFile->getSceneAtIndex(sceneIndex); scene->setSceneInfo(sceneInfo); } else { const AString msg = ("SceneInfo has bad index=" + AString::number(sceneIndex) + " in file " + m_sceneFile->getFileName()); CaretAssertMessage(0, msg); CaretLogSevere(msg); } } } break; case STATE_METADATA: CaretAssert(m_metaDataSaxReader); m_metaDataSaxReader->endElement(namespaceURI, localName, qName); if (qName == GiftiXmlElements::TAG_METADATA) { delete m_metaDataSaxReader; m_metaDataSaxReader = NULL; } break; } // // Clear out for new elements // m_elementText = ""; // // Go to previous state // if (m_stateStack.empty()) { throw XmlSaxParserException("State stack is empty while reading XML NiftDataFile."); } m_state = m_stateStack.top(); m_stateStack.pop(); } /** * get characters in an element. */ void SceneFileSaxReader::characters(const char* ch) { if (m_metaDataSaxReader != NULL) { m_metaDataSaxReader->characters(ch); } else if (m_sceneSaxReader != NULL) { m_sceneSaxReader->characters(ch); } else if (m_sceneInfoSaxReader != NULL) { m_sceneInfoSaxReader->characters(ch); } else { m_elementText += ch; } } /** * a fatal error occurs. */ void SceneFileSaxReader::fatalError(const XmlSaxParserException& e) { /* std::ostringstream str; str << "Fatal Error at line number: " << e.getLineNumber() << "\n" << "Column number: " << e.getColumnNumber() << "\n" << "Message: " << e.whatString(); if (errorMessage.isEmpty() == false) { str << "\n" << errorMessage; } errorMessage = str.str(); */ // // Stop parsing // throw e; } // a warning occurs void SceneFileSaxReader::warning(const XmlSaxParserException& e) { CaretLogWarning("XML Parser Warning: " + e.whatString()); } // an error occurs void SceneFileSaxReader::error(const XmlSaxParserException& e) { CaretLogSevere("XML Parser Error: " + e.whatString()); throw e; } void SceneFileSaxReader::startDocument() { } void SceneFileSaxReader::endDocument() { } workbench-1.1.1/src/Files/SceneFileSaxReader.h000066400000000000000000000073011255417355300211450ustar00rootroot00000000000000 #ifndef __SCENE_FILE_SAX_READER_H__ #define __SCENE_FILE_SAX_READER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include "AString.h" #include "SceneSaxReader.h" #include "XmlSaxParserException.h" #include "XmlSaxParserHandlerInterface.h" namespace caret { class Scene; class SceneFile; class SceneInfo; class SceneInfoSaxReader; class GiftiMetaDataSaxReader; class XmlAttributes; class XmlException; class SceneFileSaxReader : public CaretObject, public XmlSaxParserHandlerInterface { public: SceneFileSaxReader(SceneFile* sceneFile); virtual ~SceneFileSaxReader(); void startElement(const AString& namespaceURI, const AString& localName, const AString& qName, const XmlAttributes& attributes); void endElement(const AString& namspaceURI, const AString& localName, const AString& qName); void characters(const char* ch); void fatalError(const XmlSaxParserException& e); void warning(const XmlSaxParserException& e); void error(const XmlSaxParserException& e); void startDocument(); void endDocument(); protected: /// file reading states enum STATE { /// no state STATE_NONE, /// processing SceneFile tag STATE_SCENE_FILE, /// processing MetaData tag STATE_METADATA, /// processing SceneInfoDirectory tag STATE_SCENE_INFO_DIRECTORY, /// processing SceneInfo tag STATE_SCENE_INFO, /// processing Scene tag STATE_SCENE }; /// file reading state STATE m_state; /// the state stack used when reading a file std::stack m_stateStack; /// the error message AString m_errorMessage; /// scene file that is being read SceneFile* m_sceneFile; /// scene that is being read Scene* m_scene; /// scene info that is being read SceneInfo* m_sceneInfo; /// index attribute of scene info being read int32_t m_sceneInfoIndex; /// element text AString m_elementText; /// GIFTI meta data sax reader GiftiMetaDataSaxReader* m_metaDataSaxReader; /// Scene sax reader SceneSaxReader* m_sceneSaxReader; /// Scene info sax reader SceneInfoSaxReader* m_sceneInfoSaxReader; /// map that stores scene info by index std::map m_sceneInfoMap; }; } // namespace #endif // __SCENE_FILE_SAX_READER_H__ workbench-1.1.1/src/Files/SignedDistanceHelper.cxx000066400000000000000000001213241255417355300221120ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ /* * For the function pointInTri(): * Original copyright for PNPOLY, though my version is entirely rewritten and modified * Source: http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html */ /* Copyright (c) 1970-2003, Wm. Randolph Franklin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimers. 2. Redistributions in binary form must reproduce the above copyright notice in the documentation and/or other materials provided with the distribution. 3. The name of W. Randolph Franklin may not be used to endorse or promote products derived from this Software without specific prior written permission. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "BoundingBox.h" #include "CaretHeap.h" #include "SignedDistanceHelper.h" #include "SurfaceFile.h" #include "TopologyHelper.h" #include using namespace std; using namespace caret; float SignedDistanceHelper::dist(const float coord[3], WindingLogic myWinding) { CaretMutexLocker locked(&m_mutex); CaretSimpleMinHeap*, float> myHeap; myHeap.push(m_base->m_indexRoot, m_base->m_indexRoot->distToPoint(coord)); ClosestPointInfo tempInfo, bestInfo; float tempf = -1.0f, bestTriDist = -1.0f; bool first = true; int numChanged = 0; while (!myHeap.isEmpty()) { Oct* curOct = myHeap.pop(&tempf); if (first || tempf < bestTriDist) { if (curOct->m_leaf) { vector& myVecRef = *(curOct->m_data.m_triList); int numTris = (int)myVecRef.size(); for (int i = 0; i < numTris; ++i) { if (m_triMarked[myVecRef[i]] != 1) { m_triMarked[myVecRef[i]] = 1; m_triMarkChanged[numChanged++] = myVecRef[i]; tempf = unsignedDistToTri(coord, myVecRef[i], tempInfo); if (first || tempf < bestTriDist) { bestInfo = tempInfo; bestTriDist = tempf; first = false; } } } } else { for (int ci = 0; ci < 2; ++ci) { for (int cj = 0; cj < 2; ++cj) { for (int ck = 0; ck < 2; ++ck) { tempf = curOct->m_children[ci][cj][ck]->distToPoint(coord); if (first || tempf < bestTriDist) { myHeap.push(curOct->m_children[ci][cj][ck], tempf); } } } } } } } while (numChanged) { m_triMarked[m_triMarkChanged[--numChanged]] = 0;//need to do this before computeSign } return bestTriDist * computeSign(coord, bestInfo, myWinding); } void SignedDistanceHelper::barycentricWeights(const float coord[3], BarycentricInfo& baryInfoOut) { CaretMutexLocker locked(&m_mutex); CaretSimpleMinHeap*, float> myHeap; myHeap.push(m_base->m_indexRoot, m_base->m_indexRoot->distToPoint(coord)); ClosestPointInfo tempInfo, bestInfo; float tempf = -1.0f, bestTriDist = -1.0f; bool first = true; int numChanged = 0; while (!myHeap.isEmpty()) { Oct* curOct = myHeap.pop(&tempf); if (first || tempf < bestTriDist) { if (curOct->m_leaf) { vector& myVecRef = *(curOct->m_data.m_triList); int numTris = (int)myVecRef.size(); for (int i = 0; i < numTris; ++i) { if (m_triMarked[myVecRef[i]] != 1) { m_triMarked[myVecRef[i]] = 1; m_triMarkChanged[numChanged++] = myVecRef[i]; tempf = unsignedDistToTri(coord, myVecRef[i], tempInfo); if (first || tempf < bestTriDist) { bestInfo = tempInfo; bestTriDist = tempf; first = false; } } } } else { for (int ci = 0; ci < 2; ++ci) { for (int cj = 0; cj < 2; ++cj) { for (int ck = 0; ck < 2; ++ck) { tempf = curOct->m_children[ci][cj][ck]->distToPoint(coord); if (first || tempf < bestTriDist) { myHeap.push(curOct->m_children[ci][cj][ck], tempf); } } } } } } } while (numChanged) { m_triMarked[m_triMarkChanged[--numChanged]] = 0;//clean up } baryInfoOut.triangle = bestInfo.triangle; baryInfoOut.point = bestInfo.tempPoint; baryInfoOut.absDistance = bestTriDist; const int32_t* triNodes = m_base->getTriangle(bestInfo.triangle); baryInfoOut.nodes[0] = triNodes[0]; baryInfoOut.nodes[1] = triNodes[1]; baryInfoOut.nodes[2] = triNodes[2]; switch (bestInfo.type) { case 2: { baryInfoOut.type = BarycentricInfo::TRIANGLE; Vector3D vert1 = m_base->getCoordinate(triNodes[0]); Vector3D vert2 = m_base->getCoordinate(triNodes[1]); Vector3D vert3 = m_base->getCoordinate(triNodes[2]); Vector3D vp1 = vert1 - bestInfo.tempPoint; Vector3D vp2 = vert2 - bestInfo.tempPoint; Vector3D vp3 = vert3 - bestInfo.tempPoint; float weight1 = vp2.cross(vp3).length(); float weight2 = vp1.cross(vp3).length(); float weight3 = vp1.cross(vp2).length(); float weightsum = weight1 + weight2 + weight3; baryInfoOut.baryWeights[0] = weight1 / weightsum; baryInfoOut.baryWeights[1] = weight2 / weightsum; baryInfoOut.baryWeights[2] = weight3 / weightsum; } break; case 1: { baryInfoOut.type = BarycentricInfo::EDGE; Vector3D vert1 = m_base->getCoordinate(bestInfo.node1); Vector3D vert2 = m_base->getCoordinate(bestInfo.node2); Vector3D v21hat = vert2 - vert1; float origLength; v21hat = v21hat.normal(&origLength); float tempf = v21hat.dot(bestInfo.tempPoint - vert1); float weight2 = tempf / origLength; float weight1 = 1.0f - weight2; for (int i = 0; i < 3; ++i) { if (triNodes[i] == bestInfo.node1) { baryInfoOut.baryWeights[i] = weight1; } else if (triNodes[i] == bestInfo.node2) { baryInfoOut.baryWeights[i] = weight2; } else { baryInfoOut.baryWeights[i] = 0.0f; } } } break; case 0: baryInfoOut.type = BarycentricInfo::NODE; for (int i = 0; i < 3; ++i) { if (triNodes[i] == bestInfo.node1) { baryInfoOut.baryWeights[i] = 1.0f; } else { baryInfoOut.baryWeights[i] = 0.0f; } } break; }; for (int i = 0; i < 3; ++i) { if (baryInfoOut.baryWeights[i] < 0.0f) { baryInfoOut.baryWeights[i] = 0.0f; } } } int SignedDistanceHelper::computeSign(const float coord[3], SignedDistanceHelper::ClosestPointInfo myInfo, WindingLogic myWinding) { Vector3D point = coord; Vector3D result = point - myInfo.tempPoint; float tempf; switch (myWinding) { case EVEN_ODD: case NEGATIVE: case NONZERO: { int numChanged = 0; float positiveZ[3] = {0, 0, 1}; Vector3D point2 = point + positiveZ; int crossCount = 0; vector*> myStack; myStack.push_back(m_base->m_indexRoot); while (!myStack.empty()) { Oct* curOct = myStack[myStack.size() - 1]; myStack.pop_back(); if (curOct->m_leaf) { vector& myVecRef = *(curOct->m_data.m_triList); int numTris = (int)myVecRef.size(); for (int i = 0; i < numTris; ++i) { if (m_triMarked[myVecRef[i]] != 1) { m_triMarked[myVecRef[i]] = 1; m_triMarkChanged[numChanged++] = myVecRef[i]; const int32_t* myTileNodes = m_base->getTriangle(myVecRef[i]); Vector3D verts[3]; verts[0] = m_base->getCoordinate(myTileNodes[0]); verts[1] = m_base->getCoordinate(myTileNodes[1]); verts[2] = m_base->getCoordinate(myTileNodes[2]); Vector3D triNormal; MathFunctions::normalVector(verts[0], verts[1], verts[2], triNormal); float factor = triNormal[2];//equivalent to dot product with positiveZ if (factor != 0.0f) { if (triNormal.dot(verts[0] - point) / factor > 0.0f && pointInTri(verts, point, 0, 1)) { if (triNormal[2] < 0.0f) { ++crossCount; } else { --crossCount; } } } } } } else { for (int ci = 0; ci < 2; ++ci) { for (int cj = 0; cj < 2; ++cj) { for (int ck = 0; ck < 2; ++ck) { if (curOct->m_children[ci][cj][ck]->rayIntersects(coord, point2)) { myStack.push_back(curOct->m_children[ci][cj][ck]); } } } } } } while (numChanged) { m_triMarked[m_triMarkChanged[--numChanged]] = 0; } switch (myWinding) { case EVEN_ODD: if ((abs(crossCount) & 1) == 1) return -1;//& 1 instead of % 2 return 1; break; case NEGATIVE: if (crossCount < 0) return -1; return 1; break; case NONZERO: if (crossCount != 0) return -1; return 1; break; default: return 1;//because compiler can't handle when a switch doesn't accound for an enum value... } } break; case NORMALS: switch (myInfo.type) { case 0://node { int curSign = 0; int numChanged = 0; const vector& myTiles = m_base->m_topoHelp->getNodeTiles(myInfo.node1); bool first = true; float bestNorm = 0; Vector3D tempvec, tempvec2, bestCent; for (int i = 0; i < (int)myTiles.size(); ++i)//find the tile of the node with the normal most parallel to the line segment between centroid and point {//should be least likely to have an intervening triangle const int32_t* myTileNodes = m_base->getTriangle(myTiles[i]); Vector3D vert1 = m_base->getCoordinate(myTileNodes[0]); Vector3D vert2 = m_base->getCoordinate(myTileNodes[1]); Vector3D vert3 = m_base->getCoordinate(myTileNodes[2]); Vector3D centroid = (vert1 + vert2 + vert3) / 3.0f; if (MathFunctions::normalVector(vert1, vert2, vert3, tempvec))//make sure the triangle has a valid normal { tempvec2 = point - centroid; tempf = tempvec.dot(tempvec2.normal()); if (first || abs(tempf) > abs(bestNorm)) { if (tempf > 0.0f) { curSign = 1; } else { curSign = -1; } first = false; bestNorm = tempf; bestCent = centroid; } } } Vector3D mySeg = point - bestCent; float bestDist = mySeg.length(); Vector3D segNormal = mySeg.normal();//from the surface to the point, to match the convention of triangles with normals oriented outwards int majAxis = 0, midAxis = 1;//find the axes to use for projecting the triangles, discard the one most aligned with the line segment if (abs(mySeg[1]) < abs(mySeg[0])) { majAxis = 1; midAxis = 0; } if (abs(mySeg[2]) < abs(mySeg[midAxis])) { midAxis = 2; } vector*> myStack; myStack.push_back(m_base->m_indexRoot); while (!myStack.empty()) { Oct* curOct = myStack[myStack.size() - 1]; myStack.pop_back(); if (curOct->m_leaf) { vector& myVecRef = *(curOct->m_data.m_triList); int numTris = (int)myVecRef.size(); for (int i = 0; i < numTris; ++i) { if (m_triMarked[myVecRef[i]] != 1) { m_triMarked[myVecRef[i]] = 1; m_triMarkChanged[numChanged++] = myVecRef[i]; const int32_t* myTileNodes = m_base->getTriangle(myVecRef[i]); Vector3D verts[3]; verts[0] = m_base->getCoordinate(myTileNodes[0]); verts[1] = m_base->getCoordinate(myTileNodes[1]); verts[2] = m_base->getCoordinate(myTileNodes[2]); Vector3D triNormal; MathFunctions::normalVector(verts[0], verts[1], verts[2], triNormal); float factor = triNormal.dot(segNormal); if (factor == 0.0f) { continue;//skip triangles parallel to the line segment } float intersectDist = triNormal.dot(point - verts[0]) / factor; if (intersectDist > 0.0f && intersectDist < bestDist) { Vector3D inPlane = point - intersectDist * segNormal; if (pointInTri(verts, inPlane, majAxis, midAxis)) { bestDist = intersectDist; if (triNormal.dot(mySeg) > 0.0f) { curSign = 1; } else { curSign = -1; } } } } } } else { for (int ci = 0; ci < 2; ++ci) { for (int cj = 0; cj < 2; ++cj) { for (int ck = 0; ck < 2; ++ck) { if (curOct->m_children[ci][cj][ck]->lineSegmentIntersects(coord, bestCent)) { myStack.push_back(curOct->m_children[ci][cj][ck]); } } } } } } while (numChanged) { m_triMarked[m_triMarkChanged[--numChanged]] = 0; } return curSign; } break; case 1://edge { const vector& edgeInfo = m_base->m_topoHelp->getEdgeInfo(); const vector& edges = m_base->m_topoHelp->getNodeEdges(myInfo.node1); int whichEdge = -1, numEdges = (int)edges.size(); for (int i = 0; i < numEdges; ++i) { if (edgeInfo[edges[i]].node1 == myInfo.node2 || edgeInfo[edges[i]].node2 == myInfo.node2) { whichEdge = edges[i]; } } CaretAssert(whichEdge != -1); int tile1 = edgeInfo[whichEdge].tiles[0].tile, tile2 = edgeInfo[whichEdge].tiles[1].tile; Vector3D normalaccum, tempvec;//default constructor initializes it to the zero vector if (tile1 > -1) { const int32_t* tile1nodes = m_base->getTriangle(tile1); MathFunctions::normalVector(m_base->getCoordinate(tile1nodes[0]), m_base->getCoordinate(tile1nodes[1]), m_base->getCoordinate(tile1nodes[2]), tempvec); normalaccum += tempvec; } if (tile2 > -1) { const int32_t *tile2nodes = m_base->getTriangle(tile2); MathFunctions::normalVector(m_base->getCoordinate(tile2nodes[0]), m_base->getCoordinate(tile2nodes[1]), m_base->getCoordinate(tile2nodes[2]), tempvec); normalaccum += tempvec; } if (normalaccum.dot(result) < 0.0f) { return -1; } } break; case 2://face { Vector3D triNormal; const int32_t* triNodes = m_base->getTriangle(myInfo.triangle); Vector3D vert1 = m_base->getCoordinate(triNodes[0]); Vector3D vert2 = m_base->getCoordinate(triNodes[1]); Vector3D vert3 = m_base->getCoordinate(triNodes[2]); MathFunctions::normalVector(vert1, vert2, vert3, triNormal); if (triNormal.dot(result) < 0.0f) { return -1; } } break; } break; } return 1; } bool SignedDistanceHelper::pointInTri(Vector3D verts[3], Vector3D inPlane, int majAxis, int midAxis) { bool inside = false; for (int j = 2, i = 0; i < 3; ++i)//start with the wraparound case { if ((verts[i][majAxis] < inPlane[majAxis]) != (verts[j][majAxis] < inPlane[majAxis])) {//if one vertex is on one side of the point in the x direction, and the other is on the other side (equal case is treated as greater) int ti, tj; if (verts[i][majAxis] < verts[j][majAxis])//reorient the segment consistently to get a consistent answer { ti = i; tj = j; } else { ti = j; tj = i; } if ((verts[ti][midAxis] - verts[tj][midAxis]) / (verts[ti][majAxis] - verts[tj][majAxis]) * (inPlane[majAxis] - verts[tj][majAxis]) + verts[tj][midAxis] > inPlane[midAxis]) {//if the point on the line described by the two vertices with the same x coordinate is above (greater y) than the test point inside = !inside;//even/odd winding rule } } j = i;//consecutive vertices, does 2,0 then 0,1 then 1,2 } return inside; } ///"dumb" implementation, projects to plane, test if inside while finding closest point on each edge ///there are faster implementations out there, but this is easier to follow float SignedDistanceHelper::unsignedDistToTri(const float coord[3], int32_t triangle, ClosestPointInfo& myInfo) { const int32_t* triNodes = m_base->getTriangle(triangle); Vector3D point = coord; Vector3D verts[3]; int type = 0;//tracks whether it is closest to a node, an edge, or the face int32_t node1 = -1, node2 = -1;//tracks which nodes are involved Vector3D bestPoint; verts[0] = m_base->getCoordinate(triNodes[0]); verts[1] = m_base->getCoordinate(triNodes[1]); verts[2] = m_base->getCoordinate(triNodes[2]); Vector3D v10 = verts[1] - verts[0]; Vector3D xhat = v10.normal(); Vector3D v20 = verts[2] - verts[0]; float sanity; Vector3D yhat = (v20 - xhat * xhat.dot(v20)).normal(&sanity);//now we have our orthogonal basis vectors for projection if (sanity == 0.0f || abs(xhat.dot(yhat)) > 0.01f)//if our triangle is (mostly) degenerate, find the closest point on its edges instead of trying to project to the NaN plane { bool first = true; float bestLengthSqr = -1.0f;//track best squared length from edge to original point for (int j = 2, i = 0; i < 3; ++i)//start with the wraparound case { float length; Vector3D norm = (verts[j] - verts[i]).normal(&length); Vector3D mypoint; int temptype = 0, tempnode1, tempnode2 = -1; if (length > 0.0f) { Vector3D diff = point - verts[i]; float dot = norm.dot(diff); if (dot <= 0.0f) { mypoint = verts[i]; tempnode1 = triNodes[i]; } else if (dot >= length) { mypoint = verts[j]; tempnode1 = triNodes[j]; } else { mypoint = verts[i] + dot * norm; temptype = 1; tempnode1 = triNodes[i]; tempnode2 = triNodes[j]; } } else { temptype = 0; tempnode1 = triNodes[i]; mypoint = verts[i]; } float tempdistsqr = (point - mypoint).lengthsquared(); if (first || tempdistsqr < bestLengthSqr) { first = false; type = temptype; bestLengthSqr = tempdistsqr; bestPoint = mypoint; node1 = tempnode1; node2 = tempnode2; } j = i;//consecutive vertices, does 2,0 then 0,1 then 1,2 } } else { float vertxy[3][2]; for (int i = 0; i < 3; ++i)//project everything to the new plane with basis vectors xhat, yhat { vertxy[i][0] = xhat.dot(verts[i] - verts[0]); vertxy[i][1] = yhat.dot(verts[i] - verts[0]); } bool inside = true; float p[2] = { xhat.dot(point - verts[0]), yhat.dot(point - verts[0]) }; float bestxy[2]; float bestDist = -1.0f; for (int i = 0, j = 2, k = 1; i < 3; ++i)//start with the wraparound case { float norm[2] = { vertxy[j][0] - vertxy[i][0], vertxy[j][1] - vertxy[i][1] }; float diff[2] = { p[0] - vertxy[i][0], p[1] - vertxy[i][1] }; float direction[2] = { vertxy[k][0] - vertxy[i][0], vertxy[k][1] - vertxy[i][1] }; float edgelen = sqrt(norm[0] * norm[0] + norm[1] * norm[1]); if (edgelen != 0.0f) { norm[0] /= edgelen; norm[1] /= edgelen; float dot = direction[0] * norm[0] + direction[1] * norm[1]; direction[0] -= dot * norm[0];//direction is orthogonal to norm, in the direction of the third vertex direction[1] -= dot * norm[1]; if (diff[0] * direction[0] + diff[1] * direction[1] < 0.0f)//if dot product with (projected point - vert[i]) is negative {//we are outside the triangle, find the projection to this edge and break if it is the second time or otherwise known to be finished if (bestDist < 0.0f) { inside = false; dot = diff[0] * norm[0] + diff[1] * norm[1]; if (dot <= 0.0f)//if closest point on this edge is an endpoint, it is possible for another edge that we count as outside of to have a closer point { type = 0; node1 = triNodes[i]; bestPoint = verts[i]; bestxy[0] = vertxy[i][0]; bestxy[1] = vertxy[i][1]; } else if (dot >= edgelen) { type = 0; node1 = triNodes[j]; bestPoint = verts[j]; bestxy[0] = vertxy[j][0]; bestxy[1] = vertxy[j][1]; } else {//if closest point on the edge is in the middle of the edge, nothing can be closer, break type = 1; node1 = triNodes[i]; node2 = triNodes[j]; bestxy[0] = vertxy[i][0] + dot * norm[0]; bestxy[1] = vertxy[i][1] + dot * norm[1]; break; } diff[0] = p[0] - bestxy[0]; diff[1] = p[1] - bestxy[1]; bestDist = diff[0] * diff[0] + diff[1] * diff[1]; } else { int tempnode1; Vector3D tempbestPoint; float tempxy[2]; inside = false; dot = diff[0] * norm[0] + diff[1] * norm[1]; if (dot <= 0.0f) { tempnode1 = triNodes[i]; tempbestPoint = verts[i]; tempxy[0] = vertxy[i][0]; tempxy[1] = vertxy[i][1]; } else if (dot >= edgelen) { tempnode1 = triNodes[j]; tempbestPoint = verts[j]; tempxy[0] = vertxy[j][0]; tempxy[1] = vertxy[j][1]; } else {//again, middle of edge always wins, don't bother with the extra test type = 1; node1 = triNodes[i]; node2 = triNodes[j]; bestxy[0] = vertxy[i][0] + dot * norm[0]; bestxy[1] = vertxy[i][1] + dot * norm[1]; break; } diff[0] = p[0] - tempxy[0]; diff[1] = p[1] - tempxy[1]; float tempdist = diff[0] * diff[0] + diff[1] * diff[1]; if (tempdist < bestDist) { type = 0;//if it were in the middle of the edge, we wouldn't be here node1 = tempnode1; bestPoint = tempbestPoint; bestxy[0] = tempxy[0]; bestxy[0] = tempxy[0]; } break;//this is our second time outside an edge, we have now covered all 3 possible endpoints, so break } } } else { if (diff[0] * direction[0] + diff[1] * direction[1] < 0.0f)//since we don't have an edge, we don't need to othrogonalize direction, or project to the edge { inside = false; type = 0; node1 = triNodes[i]; bestPoint = verts[i]; break; } } k = j; j = i;//consecutive vertices, does 2,0 then 0,1 then 1,2 } if (inside) { bestxy[0] = p[0]; bestxy[1] = p[1]; type = 2; } if (type != 0) { bestPoint = bestxy[0] * xhat + bestxy[1] * yhat + verts[0]; } } Vector3D result = point - bestPoint; myInfo.type = type; myInfo.node1 = node1; myInfo.node2 = node2; myInfo.triangle = triangle; myInfo.tempPoint = bestPoint; return result.length(); } SignedDistanceHelper::SignedDistanceHelper(CaretPointer myBase) { m_base = myBase; int32_t numTris = m_base->m_numTris; m_triMarked = CaretArray(numTris); m_triMarkChanged = CaretArray(numTris); for (int32_t i = 0; i < numTris; ++i) { m_triMarked[i] = 0; } } SignedDistanceHelperBase::SignedDistanceHelperBase(const SurfaceFile* mySurf) { m_topoHelp = mySurf->getTopologyHelper(); const float* myBB = mySurf->getBoundingBox()->getBounds(); Vector3D minCoord, maxCoord; minCoord[0] = myBB[0]; maxCoord[0] = myBB[1]; minCoord[1] = myBB[2]; maxCoord[1] = myBB[3]; minCoord[2] = myBB[4]; maxCoord[2] = myBB[5]; m_indexRoot.grabNew(new Oct(minCoord, maxCoord)); const float* myCoordData = mySurf->getCoordinateData(); m_numNodes = mySurf->getNumberOfNodes(); int32_t numNodes3 = m_numNodes * 3; m_coordList.resize(numNodes3); for (int32_t i = 0; i < numNodes3; ++i) { m_coordList[i] = myCoordData[i]; } m_numTris = mySurf->getNumberOfTriangles(); m_triangleList.resize(m_numTris * 3); for (int32_t i = 0; i < m_numTris; ++i) { int32_t i3 = i * 3; const int32_t* thisTri = mySurf->getTriangle(i); m_triangleList[i3] = thisTri[0]; m_triangleList[i3 + 1] = thisTri[1]; m_triangleList[i3 + 2] = thisTri[2]; maxCoord = minCoord = myCoordData + thisTri[0] * 3;//set both to the coordinates of the first node in the triangle for (int j = 1; j < 3; ++j) { int32_t thisNode3 = thisTri[j] * 3; if (myCoordData[thisNode3] < minCoord[0]) minCoord[0] = myCoordData[thisNode3]; if (myCoordData[thisNode3 + 1] < minCoord[1]) minCoord[1] = myCoordData[thisNode3 + 1]; if (myCoordData[thisNode3 + 2] < minCoord[2]) minCoord[2] = myCoordData[thisNode3 + 2]; if (myCoordData[thisNode3] > maxCoord[0]) maxCoord[0] = myCoordData[thisNode3]; if (myCoordData[thisNode3 + 1] > maxCoord[1]) maxCoord[1] = myCoordData[thisNode3 + 1]; if (myCoordData[thisNode3 + 2] > maxCoord[2]) maxCoord[2] = myCoordData[thisNode3 + 2]; } addTriangle(m_indexRoot, i, minCoord, maxCoord);//use bounding box for now as an easy test to capture any chance of the triangle intersecting the Oct } } void SignedDistanceHelperBase::addTriangle(Oct* thisOct, int32_t triangle, float minCoord[3], float maxCoord[3]) { if (thisOct->m_leaf) { thisOct->m_data.m_triList->push_back(triangle); int numTris = (int)thisOct->m_data.m_triList->size(); if (numTris >= NUM_TRIS_TO_TEST && numTris % NUM_TRIS_TEST_INCR == NUM_TRIS_TO_TEST % NUM_TRIS_TEST_INCR)//the second modulus should const out { Vector3D tempMinCoord, tempMaxCoord; const float* myCoordData = m_coordList.data(); int totalSize = 0; int numSplit = 0; for (int i = 0; i < numTris; ++i)//gather data on how it would end up splitting { const int32_t* tempTri = getTriangle((*(thisOct->m_data.m_triList))[i]); tempMaxCoord = tempMinCoord = myCoordData + tempTri[0] * 3;//set both to the coordinates of the first node in the triangle for (int j = 1; j < 3; ++j) { int32_t thisNode3 = tempTri[j] * 3; if (myCoordData[thisNode3] < tempMinCoord[0]) tempMinCoord[0] = myCoordData[thisNode3]; if (myCoordData[thisNode3 + 1] < tempMinCoord[1]) tempMinCoord[1] = myCoordData[thisNode3 + 1]; if (myCoordData[thisNode3 + 2] < tempMinCoord[2]) tempMinCoord[2] = myCoordData[thisNode3 + 2]; if (myCoordData[thisNode3] > tempMaxCoord[0]) tempMaxCoord[0] = myCoordData[thisNode3]; if (myCoordData[thisNode3 + 1] > tempMaxCoord[1]) tempMaxCoord[1] = myCoordData[thisNode3 + 1]; if (myCoordData[thisNode3 + 2] > tempMaxCoord[2]) tempMaxCoord[2] = myCoordData[thisNode3 + 2]; } int minOct[3], maxOct[3]; thisOct->containingChild(tempMinCoord, minOct); thisOct->containingChild(tempMaxCoord, maxOct); int splitSize = 8; if (minOct[0] == maxOct[0]) splitSize >>= 1; if (minOct[1] == maxOct[1]) splitSize >>= 1; if (minOct[2] == maxOct[2]) splitSize >>= 1; totalSize += splitSize; if (splitSize != 8) ++numSplit; } if (numSplit > 0 && totalSize < 3.0f * numTris)//don't split if all triangles end up in all child octs, and try to balance speedup with memory usage { thisOct->makeChildren();//do the split for (int i = 0; i < numTris; ++i)//gather data on how it would end up splitting { const int32_t* tempTri = getTriangle((*(thisOct->m_data.m_triList))[i]); tempMaxCoord = tempMinCoord = myCoordData + tempTri[0] * 3;//set both to the coordinates of the first node in the triangle for (int j = 1; j < 3; ++j) { int32_t thisNode3 = tempTri[j] * 3; if (myCoordData[thisNode3] < tempMinCoord[0]) tempMinCoord[0] = myCoordData[thisNode3]; if (myCoordData[thisNode3 + 1] < tempMinCoord[1]) tempMinCoord[1] = myCoordData[thisNode3 + 1]; if (myCoordData[thisNode3 + 2] < tempMinCoord[2]) tempMinCoord[2] = myCoordData[thisNode3 + 2]; if (myCoordData[thisNode3] > tempMaxCoord[0]) tempMaxCoord[0] = myCoordData[thisNode3]; if (myCoordData[thisNode3 + 1] > tempMaxCoord[1]) tempMaxCoord[1] = myCoordData[thisNode3 + 1]; if (myCoordData[thisNode3 + 2] > tempMaxCoord[2]) tempMaxCoord[2] = myCoordData[thisNode3 + 2]; } for (int ci = 0; ci < 2; ++ci) { for (int cj = 0; cj < 2; ++cj) { for (int ck = 0; ck < 2; ++ck) { if (thisOct->m_children[ci][cj][ck]->boundsOverlaps(tempMinCoord, tempMaxCoord)) { addTriangle(thisOct->m_children[ci][cj][ck], (*(thisOct->m_data.m_triList))[i], tempMinCoord, tempMaxCoord); } } } } } thisOct->m_data.freeData();//and free up some memory } } } else { for (int ci = 0; ci < 2; ++ci) { for (int cj = 0; cj < 2; ++cj) { for (int ck = 0; ck < 2; ++ck) { if (thisOct->m_children[ci][cj][ck]->boundsOverlaps(minCoord, maxCoord)) { addTriangle(thisOct->m_children[ci][cj][ck], triangle, minCoord, maxCoord); } } } } } } const float* SignedDistanceHelperBase::getCoordinate(const int32_t nodeIndex) const { CaretAssert(nodeIndex >= 0 && nodeIndex < m_numNodes); return m_coordList.data() + (nodeIndex * 3); } const int32_t* SignedDistanceHelperBase::getTriangle(const int32_t tileIndex) const { CaretAssert(tileIndex >= 0 && tileIndex < m_numTris); return m_triangleList.data() + (tileIndex * 3); } SignedDistanceHelperBase::~SignedDistanceHelperBase() {//so we don't need to put TopologyHelper.h in our header, due to the smart pointer destructor } workbench-1.1.1/src/Files/SignedDistanceHelper.h000066400000000000000000000105411255417355300215350ustar00rootroot00000000000000#ifndef __SIGNED_DISTANCE_HELPER_H__ #define __SIGNED_DISTANCE_HELPER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "Vector3D.h" #include "CaretMutex.h" #include "CaretPointer.h" #include "OctTree.h" #include namespace caret { class SurfaceFile; class TopologyHelper; class SignedDistanceHelperBase { struct TriVector {//specifically so we can cleanly deallocate the vector from non-leaf nodes when they split std::vector* m_triList; TriVector() { m_triList = new std::vector(); } ~TriVector() { freeData(); } void freeData() { if (m_triList != NULL) { delete m_triList; m_triList = NULL; } } }; static const int NUM_TRIS_TO_TEST = 50;//test for whether to split leaf at this number static const int NUM_TRIS_TEST_INCR = 50;//and again at further multiples of this CaretPointer > m_indexRoot; int32_t m_numTris, m_numNodes; std::vector m_coordList;//make a copy of what we need from SurfaceFile so that if the SurfaceFile gets destroyed, we don't crash std::vector m_triangleList; CaretPointer m_topoHelp; SignedDistanceHelperBase(); void addTriangle(Oct* thisOct, int32_t triangle, float minCoord[3], float maxCoord[3]); const float* getCoordinate(const int32_t nodeIndex) const;//make these public? probably don't want them to be widely used, that is what SurfaceFile is for (but we don't want to store a SurfaceFile pointer) const int32_t* getTriangle(const int32_t tileIndex) const; public: ~SignedDistanceHelperBase();//in order to let us not include TopologyHelper SignedDistanceHelperBase(const SurfaceFile* mySurf); friend class SignedDistanceHelper; }; struct BarycentricInfo { enum POINT_TYPE { NODE, EDGE, TRIANGLE }; int32_t triangle; Vector3D point; POINT_TYPE type; float absDistance; int32_t nodes[3]; float baryWeights[3]; }; class SignedDistanceHelper { public: enum WindingLogic { EVEN_ODD, NEGATIVE, NONZERO, NORMALS }; private: CaretMutex m_mutex; CaretPointer m_base; CaretArray m_triMarked; CaretArray m_triMarkChanged; SignedDistanceHelper(); struct ClosestPointInfo { int type; int32_t node1, node2, triangle; Vector3D tempPoint; }; float unsignedDistToTri(const float coord[3], int32_t triangle, ClosestPointInfo& myInfo); int computeSign(const float coord[3], ClosestPointInfo myInfo, WindingLogic myWinding); bool pointInTri(Vector3D verts[3], Vector3D inPlane, int majAxis, int midAxis); public: SignedDistanceHelper(CaretPointer myBase); ///return the signed distance value at the point float dist(const float coord[3], WindingLogic myWinding); ///find the closest point ON the surface, and return information about it ///will never have negative barycentric weights, or a point outside the triangle void barycentricWeights(const float coordIn[3], BarycentricInfo& baryInfoOut); }; } #endif //__SIGNED_DISTANCE_HELPER_H__ workbench-1.1.1/src/Files/SparseVolumeIndexer.cxx000066400000000000000000000262731255417355300220410ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __SPARSE_VOLUME_INDEXER_DECLARE__ #include "SparseVolumeIndexer.h" #undef __SPARSE_VOLUME_INDEXER_DECLARE__ #include "CaretLogger.h" using namespace caret; /** * \class caret::SparseVolumeIndexer * \brief Maps IJK indices or XYZ coordinates to a sparse volume data * \ingroup Files * * Some data sources (such as CIFTI files) contain data for a subset * (or sparse representation) of volume data. In a CIFTI file, there * is a list of IJK indices that indicate the offset of each voxel * in the data (and note that the data may be for both surface vertices * and volume voxels). Finding a particular voxel by its IJK indices * requires a sequential search through the CIFTI's list of voxels. * This class permits a much faster location of a particular CIFTI file's * voxel by creating what is essentially a lookup table that maps the * full non-sparse volume IJK indices to an offset in the CIFTI data * or a negative value if the voxel is not present in the CIFTI data. */ /** * Default constructor with invalid state. */ SparseVolumeIndexer::SparseVolumeIndexer() : CaretObject() { m_dataValid = false; } /** * Constructs instance with the given CIFTI Brain Models Map. * * @param ciftiBrainModelsMap * The CIFTI brain models map. */ SparseVolumeIndexer::SparseVolumeIndexer(const CiftiBrainModelsMap& ciftiBrainModelsMap) : CaretObject() { m_dataValid = false; if ( ! ciftiBrainModelsMap.hasVolumeData()) { return; } m_volumeSpace = ciftiBrainModelsMap.getVolumeSpace(); std::vector ciftiVoxelMapping = ciftiBrainModelsMap.getFullVolumeMap(); const int32_t numberOfCiftiVolumeVoxels = static_cast(ciftiVoxelMapping.size()); if (numberOfCiftiVolumeVoxels <= 0) { return; } /* * Make sure orthogonal */ if ( ! m_volumeSpace.isPlumb()) { CaretLogWarning("CIFTI Volume is not Plumb!"); return; } const int64_t* dimIJK = m_volumeSpace.getDims(); const int64_t numberOfVoxels = (dimIJK[0] * dimIJK[1] * dimIJK[2]); if (numberOfVoxels <= 0) { return; } for (std::vector::const_iterator iter = ciftiVoxelMapping.begin(); iter != ciftiVoxelMapping.end(); iter++) { const CiftiBrainModelsMap::VolumeMap& vm = *iter; m_voxelIndexLookup.at(vm.m_ijk) = vm.m_ciftiIndex; } bool validateFlag = true; if (validateFlag) { AString validateString; for (std::vector::const_iterator iter = ciftiVoxelMapping.begin(); iter != ciftiVoxelMapping.end(); iter++) { const CiftiBrainModelsMap::VolumeMap& vm = *iter; const int64_t* foundOffset = m_voxelIndexLookup.find(vm.m_ijk); if (foundOffset != NULL) { if (*foundOffset != vm.m_ciftiIndex) { validateString.appendWithNewLine("IJK (" + AString::fromNumbers(vm.m_ijk, 3, ",") + " should have lookup value " + AString::number(vm.m_ciftiIndex) + " but has value " + AString::number(*foundOffset)); } } else { validateString.appendWithNewLine("IJK (" + AString::fromNumbers(vm.m_ijk, 3, ",") + " should have lookup value " + AString::number(vm.m_ciftiIndex) + " but was not found."); } } if (validateString.isEmpty() == false) { CaretLogSevere("Sparse Indexer Errors:\n" + validateString); } } m_dataValid = true; } /** * Constructs instance with the given CIFTI Parcel Map. * * @param parcel * The CIFTI parcel map. */ SparseVolumeIndexer::SparseVolumeIndexer(const CiftiParcelsMap& ciftiParcelsMap) : CaretObject() { m_dataValid = false; if ( ! ciftiParcelsMap.hasVolumeData()) { return; } m_volumeSpace = ciftiParcelsMap.getVolumeSpace(); /* * Make sure orthogonal */ if ( ! m_volumeSpace.isPlumb()) { CaretLogWarning("CIFTI Volume is not Plumb!"); return; } const std::vector allParcels = ciftiParcelsMap.getParcels(); for (std::vector::const_iterator parcelIter = allParcels.begin(); parcelIter != allParcels.end(); parcelIter++) { const CiftiParcelsMap::Parcel& parcel = *parcelIter; // const int32_t numberOfCiftiVolumeVoxels = static_cast(parcel.m_voxelIndices.size()); // if (numberOfCiftiVolumeVoxels <= 0) { // return; // } // // const int64_t* dimIJK = m_volumeSpace.getDims(); // const int64_t numberOfVoxels = (dimIJK[0] * dimIJK[1] * dimIJK[2]); // if (numberOfVoxels <= 0) { // return; // } for (std::set::const_iterator iter = parcel.m_voxelIndices.begin(); iter != parcel.m_voxelIndices.end(); iter++) { const VoxelIJK& vm = *iter; m_voxelIndexLookup.at(vm.m_ijk) = ciftiParcelsMap.getIndexForVoxel(vm.m_ijk); } } // bool validateFlag = true; // if (validateFlag) { // AString validateString; // for (std::set::const_iterator iter = ciftiParcel.m_voxelIndices.begin(); // iter != ciftiParcel.m_voxelIndices.end(); // iter++) { // const VoxelIJK& vm = *iter; // const int64_t* foundOffset = m_voxelIndexLookup.find(vm.m_ijk); // const int64_t voxelOffset = ciftiParcelsMap.getIndexForVoxel(vm.m_ijk); // if (foundOffset != NULL) { // if (*foundOffset != voxelOffset) { // validateString.appendWithNewLine("IJK (" // + AString::fromNumbers(vm.m_ijk, 3, ",") // + " should have lookup value " // + AString::number(voxelOffset) // + " but has value " // + AString::number(*foundOffset)); // } // } // else { // validateString.appendWithNewLine("IJK (" // + AString::fromNumbers(vm.m_ijk, 3, ",") // + " should have lookup value " // + AString::number(voxelOffset) // + " but was not found."); // } // } // if (validateString.isEmpty() == false) { // CaretLogSevere("Sparse Indexer Errors:\n" // + validateString); // } // } m_dataValid = true; } /** * Destructor. */ SparseVolumeIndexer::~SparseVolumeIndexer() { } /** * @return True if this instance is valid. */ bool SparseVolumeIndexer::isValid() const { return m_dataValid; } /** * Get the offset for the given IJK indices. * * @param i * I index. * @param j * J index. * @param k * K index. * @return * Offset for given indices or -1 if no data for the given indices. */ int64_t SparseVolumeIndexer::getOffsetForIndices(const int64_t i, const int64_t j, const int64_t k) const { if (m_dataValid) { const int64_t* offset = m_voxelIndexLookup.find(i, j, k); if (offset != NULL) { return *offset; } } return -1; } /** * Convert the coordinates to volume indices. Any coordinates are accepted * and output indices are not necessarily within the volume. * * @param x * X coordinate. * @param y * y coordinate. * @param z * z coordinate. * @param iOut * I index. * @param jOut * J index. * @param kOut * K index. * @return * True if volume attributes (origin/spacing/dimensions) are valid. */ bool SparseVolumeIndexer::coordinateToIndices(const float x, const float y, const float z, int64_t& iOut, int64_t& jOut, int64_t& kOut) const { if (m_dataValid) { m_volumeSpace.enclosingVoxel(x, y, z, iOut, jOut, kOut); return true; } return false; } /** * Get the offset for the given XYZ coordinates. * * @param x * X coordinate. * @param y * y coordinate. * @param z * z coordinate. * @return * Offset for given coordinates or -1 if no data for the given coordinates. */ int64_t SparseVolumeIndexer::getOffsetForCoordinate(const float x, const float y, const float z) const { if (m_dataValid) { int64_t i, j, k; m_volumeSpace.enclosingVoxel(x, y, z, i, j, k); return getOffsetForIndices(i, j, k); } return -1; } /** * Get the XYZ coordinate for the given indices. * Any indices are accepted. * * @param i * I index. * @param j * J index. * @param k * K index. * @param xOut * X coordinate. * @param yOut * y coordinate. * @param zOut * z coordinate. * @return * True if volume attributes (origin/spacing/dimensions) are valid. */ bool SparseVolumeIndexer::indicesToCoordinate(const int64_t i, const int64_t j, const int64_t k, float& xOut, float& yOut, float& zOut) const { if (m_dataValid) { m_volumeSpace.indexToSpace(i, j, k, xOut, yOut, zOut); return true; } return false; } workbench-1.1.1/src/Files/SparseVolumeIndexer.h000066400000000000000000000056541255417355300214660ustar00rootroot00000000000000#ifndef __SPARSE_VOLUME_INDEXER_H__ #define __SPARSE_VOLUME_INDEXER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretCompact3DLookup.h" #include "CaretObject.h" #include "CiftiBrainModelsMap.h" #include "CiftiParcelsMap.h" #include "VolumeSpace.h" namespace caret { class SparseVolumeIndexer : public CaretObject { public: SparseVolumeIndexer(); SparseVolumeIndexer(const CiftiBrainModelsMap& ciftiBrainModelsMap); SparseVolumeIndexer(const CiftiParcelsMap& ciftiParcelsMap); virtual ~SparseVolumeIndexer(); bool isValid() const; bool coordinateToIndices(const float x, const float y, const float z, int64_t& iOut, int64_t& jOut, int64_t& kOut) const; bool indicesToCoordinate(const int64_t i, const int64_t j, const int64_t k, float& xOut, float& yOut, float& zOut) const; int64_t getOffsetForIndices(const int64_t i, const int64_t j, const int64_t k) const; int64_t getOffsetForCoordinate(const float x, const float y, const float z) const; private: SparseVolumeIndexer(const SparseVolumeIndexer&); SparseVolumeIndexer& operator=(const SparseVolumeIndexer&); public: // ADD_NEW_METHODS_HERE private: // ADD_NEW_MEMBERS_HERE bool m_dataValid; CaretCompact3DLookup m_voxelIndexLookup; VolumeSpace m_volumeSpace; }; #ifdef __SPARSE_VOLUME_INDEXER_DECLARE__ // #endif // __SPARSE_VOLUME_INDEXER_DECLARE__ } // namespace #endif //__SPARSE_VOLUME_INDEXER_H__ workbench-1.1.1/src/Files/SpecFile.cxx000066400000000000000000001523001255417355300175560ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include "CaretAssert.h" #include "CaretLogger.h" #include "CaretPointer.h" #include "DataFileContentInformation.h" #include "DataFileException.h" #include "EventGetDisplayedDataFiles.h" #include "EventManager.h" #include "FileAdapter.h" #include "FileInformation.h" #include "GiftiMetaData.h" #include "SceneAttributes.h" #include "SceneClass.h" #include "SceneClassArray.h" #define __SPEC_FILE_DEFINE__ #include "SpecFile.h" #undef __SPEC_FILE_DEFINE__ #include "SpecFileDataFile.h" #include "SpecFileDataFileTypeGroup.h" #include "SpecFileSaxReader.h" #include "StringTableModel.h" #include "SystemUtilities.h" #include "XmlSaxParser.h" #include "XmlWriter.h" using namespace caret; /** * \class caret::SpecFile * \brief A spec file groups caret data files. * \ingroup Files */ /** * Constructor. */ SpecFile::SpecFile() : CaretDataFile(DataFileTypeEnum::SPECIFICATION) { this->initializeSpecFile(); } /** * Destructor. */ SpecFile::~SpecFile() { for (std::vector::const_iterator iter = dataFileTypeGroups.begin(); iter != dataFileTypeGroups.end(); iter++) { delete *iter; } this->dataFileTypeGroups.clear(); delete this->metadata; this->metadata = NULL; } /** * Copy constructor. * @param sf * Spec file whose data is copied. */ SpecFile::SpecFile(const SpecFile& sf) : CaretDataFile(sf) { this->initializeSpecFile(); this->copyHelperSpecFile(sf); } /** * Assignment operator. * @param sf * Spec file whose data is copied. * @return * Reference to this file. */ SpecFile& SpecFile::operator=(const SpecFile& sf) { if (this != &sf) { CaretDataFile::operator=(sf); this->copyHelperSpecFile(sf); } return *this; } /** * @return The structure for this file. */ StructureEnum::Enum SpecFile::getStructure() const { return StructureEnum::ALL; } /** * Set the structure for this file. * @param structure * New structure for this file. */ void SpecFile::setStructure(const StructureEnum::Enum /* structure */) { /* nothing since spec file not structure type file */ } /** * @return Get access to the file's metadata. */ GiftiMetaData* SpecFile::getFileMetaData() { return metadata; } /** * @return Get access to unmodifiable file's metadata. */ const GiftiMetaData* SpecFile::getFileMetaData() const { return metadata; } /** * Initialize this spec file. */ void SpecFile::initializeSpecFile() { this->metadata = new GiftiMetaData(); std::vector allEnums; DataFileTypeEnum::getAllEnums(allEnums, false); /* * Do surface files first since they need to be loaded before other files */ for (std::vector::iterator iter = allEnums.begin(); iter != allEnums.end(); iter++) { DataFileTypeEnum::Enum dataFileType = *iter; const AString typeName = DataFileTypeEnum::toName(dataFileType); if (typeName.startsWith("SURFACE")) { SpecFileDataFileTypeGroup* dftg = new SpecFileDataFileTypeGroup(dataFileType); this->dataFileTypeGroups.push_back(dftg); } } /* * Do remaining file types excluding surfaces */ for (std::vector::iterator iter = allEnums.begin(); iter != allEnums.end(); iter++) { DataFileTypeEnum::Enum dataFileType = *iter; if (dataFileType == DataFileTypeEnum::UNKNOWN) { // ignore } else { const AString typeName = DataFileTypeEnum::toName(dataFileType); if (typeName.startsWith("SURFACE") == false) { SpecFileDataFileTypeGroup* dftg = new SpecFileDataFileTypeGroup(dataFileType); this->dataFileTypeGroups.push_back(dftg); } } } } /** * Copy helper. * @param sf * Spec file whose data is copied. */ void SpecFile::copyHelperSpecFile(const SpecFile& sf) { this->clearData(); if (this->metadata != NULL) { delete this->metadata; } this->metadata = new GiftiMetaData(*sf.metadata); for (std::vector::const_iterator iter = sf.dataFileTypeGroups.begin(); iter != sf.dataFileTypeGroups.end(); iter++) { SpecFileDataFileTypeGroup* group = *iter; const int numFiles = group->getNumberOfFiles(); for (int32_t i = 0; i < numFiles; i++) { SpecFileDataFile* file = group->getFileInformation(i); this->addDataFile(group->getDataFileType(), file->getStructure(), file->getFileName(), file->isLoadingSelected(), file->isSavingSelected(), file->isSpecFileMember()); } } this->setFileName(sf.getFileName()); this->clearModified(); } /** * Change the name of a file. The existing SpecFileDataFile remains with * its CaretDataFile removed and a new SpecFileDataFile is created with * the CaretDataFile. * * @param specFileDataFile * The SpecFileDataFile that has its name changed. * @param newFileName * New name for file. * @return New SpecFileDataFile that is created or NULL if filename did not * change of there is an error. */ SpecFileDataFile* SpecFile::changeFileName(SpecFileDataFile* specFileDataFile, const AString& newFileName) { CaretAssert(specFileDataFile); /* * Make sure name changed. */ if (specFileDataFile->getFileName() == newFileName) { return NULL; } /* * Create a new SpecFileDataFile */ SpecFileDataFile* newSpecFileDataFile = new SpecFileDataFile(*specFileDataFile); newSpecFileDataFile->setFileName(newFileName); newSpecFileDataFile->setCaretDataFile(specFileDataFile->getCaretDataFile()); /* * Remove CaretDataFile from previous SpecFileDataFile */ specFileDataFile->setCaretDataFile(NULL); specFileDataFile->setSavingSelected(false); /* * Add new SpecFileDataFile to appropriate group. */ for (std::vector::const_iterator iter = dataFileTypeGroups.begin(); iter != dataFileTypeGroups.end(); iter++) { SpecFileDataFileTypeGroup* group = *iter; if (group->getDataFileType() == newSpecFileDataFile->getDataFileType()) { group->addFileInformation(newSpecFileDataFile); return newSpecFileDataFile; } } CaretAssert(0); CaretLogSevere("PROGRAM ERROR: Failed to match DataFileType"); return NULL; } /** * Add a Caret Data File. * * @param If there is a a spec file entry with the same name as the Caret * Data File, the caret data file is added to the spec file entry. * Otherwise, a new Spec File Entry is created with the given Caret * Data File. * * @param caretDataFile * Caret data file that is added to, or creates, a spec file entry. */ void SpecFile::addCaretDataFile(CaretDataFile* caretDataFile) { CaretAssert(caretDataFile); /* * Matches to first file found that has matching name and data file type */ SpecFileDataFile* matchedSpecFileDataFile = NULL; /* * Matches to first file found that has matching name and data file type * AND has its caret data file with a NULL value. */ SpecFileDataFile* matchedSpecFileDataFileWithNULL = NULL; /* * Group that matches data file type */ SpecFileDataFileTypeGroup* matchedDataFileTypeGroup = NULL; /* * Find matches */ for (std::vector::const_iterator iter = dataFileTypeGroups.begin(); iter != dataFileTypeGroups.end(); iter++) { SpecFileDataFileTypeGroup* dataFileTypeGroup = *iter; if (dataFileTypeGroup->getDataFileType() == caretDataFile->getDataFileType()) { matchedDataFileTypeGroup = dataFileTypeGroup; const int32_t numFiles = dataFileTypeGroup->getNumberOfFiles(); for (int32_t i = 0; i < numFiles; i++) { SpecFileDataFile* sfdf = dataFileTypeGroup->getFileInformation(i); if (sfdf->getCaretDataFile() == caretDataFile) { return; } if (sfdf->getFileName() == caretDataFile->getFileName()) { matchedSpecFileDataFile = sfdf; if (sfdf->getCaretDataFile() == NULL) { matchedSpecFileDataFileWithNULL = sfdf; break; } } } } if (matchedSpecFileDataFileWithNULL != NULL) { break; } } CaretAssert(matchedDataFileTypeGroup); SpecFileDataFile* specFileDataFileToUpdate = NULL; if (matchedSpecFileDataFileWithNULL != NULL) { /* * Found item that matched with a NULL value for caret data file */ specFileDataFileToUpdate = matchedSpecFileDataFileWithNULL; } else if (matchedSpecFileDataFile != NULL) { /* * Found item that matched but had non-NULL value for caret data file. * This means that there is a copy of a file loaded (two files same name) */ specFileDataFileToUpdate = new SpecFileDataFile(*matchedSpecFileDataFile); matchedDataFileTypeGroup->addFileInformation(specFileDataFileToUpdate); } else { /* * No matches found, file is not in spec file */ specFileDataFileToUpdate = new SpecFileDataFile(caretDataFile->getFileName(), caretDataFile->getDataFileType(), caretDataFile->getStructure(), false); matchedDataFileTypeGroup->addFileInformation(specFileDataFileToUpdate); } specFileDataFileToUpdate->setStructure(caretDataFile->getStructure()); specFileDataFileToUpdate->setCaretDataFile(caretDataFile); } /** * Remove a Caret Data File. * * @param If there is a a spec file entry with the given caret data file * remove it. Note: file has likely already been deleted so use only the * the caret data file pointer but to not deference it. * * @param caretDataFile * Caret data file that is removed from a spec file entry. */ void SpecFile::removeCaretDataFile(const CaretDataFile* caretDataFile) { CaretAssert(caretDataFile); /* * Get the entry */ for (std::vector::const_iterator iter = dataFileTypeGroups.begin(); iter != dataFileTypeGroups.end(); iter++) { SpecFileDataFileTypeGroup* dataFileTypeGroup = *iter; const int32_t numFiles = dataFileTypeGroup->getNumberOfFiles(); for (int32_t i = 0; i < numFiles; i++) { SpecFileDataFile* sfdf = dataFileTypeGroup->getFileInformation(i); if (sfdf->getCaretDataFile() == caretDataFile) { sfdf->setCaretDataFile(NULL); return; } } } CaretLogSevere("Failed to remove CaretDataFile at address " + AString::number((qulonglong)caretDataFile) + " from SpecFile: " + getFileName()); } /** * Add a data file to this spec file. * * @param dataFileType * Type of data file. * @param structure * Structure of data file (not all files use structure). * @param filename * Name of the file. * @param fileLoadingSelectionStatus * Selection status for loading of the file. * @param fileSavingSelectionStatus * Selection status for saving of the file. * @param specFileMemberStatus * True if the file is a member of the spec file and is written * into the spec file. * * @throws DataFileException * If data file type is UNKNOWN. */ void SpecFile::addDataFile(const DataFileTypeEnum::Enum dataFileType, const StructureEnum::Enum structure, const AString& filename, const bool fileLoadingSelectionStatus, const bool fileSavingSelectionStatus, const bool specFileMemberStatus) { addDataFilePrivate(dataFileType, structure, filename, fileLoadingSelectionStatus, fileSavingSelectionStatus, specFileMemberStatus); } /** * Add a data file to this spec file. * * @param dataFileType * Type of data file. * @param structure * Structure of data file (not all files use structure). * @param filename * Name of the file. * @param fileLoadingSelectionStatus * Selection status for loading of the file. * @param fileSavingSelectionStatus * Selection status for saving of the file. * @param specFileMemberStatus * True if the file is a member of the spec file and is written * into the spec file. * @return * SpecFileDataFile that was created or matched. NULL if error. * * @throws DataFileException * If data file type is UNKNOWN. */ SpecFileDataFile* SpecFile::addDataFilePrivate(const DataFileTypeEnum::Enum dataFileType, const StructureEnum::Enum structure, const AString& filename, const bool fileLoadingSelectionStatus, const bool fileSavingSelectionStatus, const bool specFileMemberStatus) { AString name = filename; const bool dataFileOnNetwork = DataFile::isFileOnNetwork(name); const bool specFileOnNetwork = DataFile::isFileOnNetwork(getFileName()); if (dataFileOnNetwork) { /* nothing */ } else if (specFileOnNetwork) { const int32_t lastSlashIndex = getFileName().lastIndexOf("/"); if (lastSlashIndex >= 0) { const AString newName = (getFileName().left(lastSlashIndex) + "/" + name); name = newName; } else { CaretAssert(0); } } else { FileInformation specFileInfo(getFileName()); if (specFileInfo.isAbsolute()) { FileInformation fileInfo(name); if (fileInfo.isRelative()) { FileInformation fileInfo(specFileInfo.getPathName(), name); name = fileInfo.getAbsoluteFilePath(); } } } // const AString message = ("After adding, " // + filename // + " becomes " // + name); // CaretLogFine(message); // if (this->getFileName().isEmpty() == false) { // name = SystemUtilities::relativePath(name, FileInformation(this->getFileName()).getPathName()); // } for (std::vector::const_iterator iter = dataFileTypeGroups.begin(); iter != dataFileTypeGroups.end(); iter++) { SpecFileDataFileTypeGroup* dataFileTypeGroup = *iter; if (dataFileTypeGroup->getDataFileType() == dataFileType) { /* * If already in file, no need to add it a second time but do update * its selection status if new entry has file selected */ const int32_t numFiles = dataFileTypeGroup->getNumberOfFiles(); for (int32_t i = 0; i < numFiles; i++) { SpecFileDataFile* sfdf = dataFileTypeGroup->getFileInformation(i); if (sfdf->getFileName() == name) { if (fileLoadingSelectionStatus) { sfdf->setLoadingSelected(fileLoadingSelectionStatus); } if (fileSavingSelectionStatus) { sfdf->setSavingSelected(fileSavingSelectionStatus); } return sfdf; } } SpecFileDataFile* sfdf = new SpecFileDataFile(name, dataFileType, structure, specFileMemberStatus); sfdf->setLoadingSelected(fileLoadingSelectionStatus); sfdf->setSavingSelected(fileSavingSelectionStatus); dataFileTypeGroup->addFileInformation(sfdf); return sfdf; } } DataFileException e(getFileName(), "Data File Type: " + DataFileTypeEnum::toName(dataFileType) + " not allowed " + " for file " + filename); CaretLogThrowing(e); throw e; return NULL; // will never get here since exception thrown } /** * @return ALL of the connectivity file types (NEVER delete contents of returned vector. */ void SpecFile::getAllConnectivityFileTypes(std::vector& connectivityDataFilesOut) { connectivityDataFilesOut.clear(); for (std::vector::const_iterator iter = dataFileTypeGroups.begin(); iter != dataFileTypeGroups.end(); iter++) { SpecFileDataFileTypeGroup* dataFileTypeGroup = *iter; if (DataFileTypeEnum::isConnectivityDataType(dataFileTypeGroup->getDataFileType())) { const int32_t numFiles = dataFileTypeGroup->getNumberOfFiles(); for (int32_t i = 0; i < numFiles; i++) { connectivityDataFilesOut.push_back(dataFileTypeGroup->getFileInformation(i)); } } } } /** * Set the selection status of a data file. * @param dataFileTypeName * Name of type of data file. * @param structure * Name of Structure of data file (not all files use structure). * @param filename * Name of the file. * @param fileSelectionStatus * Selection status of file. */ void SpecFile::setFileLoadingSelectionStatus(const DataFileTypeEnum::Enum dataFileType, const StructureEnum::Enum structure, const AString& filename, const bool fileSelectionStatus) { for (std::vector::const_iterator iter = dataFileTypeGroups.begin(); iter != dataFileTypeGroups.end(); iter++) { SpecFileDataFileTypeGroup* dataFileTypeGroup = *iter; if (dataFileTypeGroup->getDataFileType() == dataFileType) { const int32_t numFiles = dataFileTypeGroup->getNumberOfFiles(); for (int32_t i = 0; i < numFiles; i++) { SpecFileDataFile* sfdf = dataFileTypeGroup->getFileInformation(i); if (sfdf->getStructure() == structure) { if (sfdf->getFileName().endsWith(filename)) { sfdf->setLoadingSelected(fileSelectionStatus); } } } } } } /** * Add a data file to this spec file. * @param dataFileTypeName * Name of type of data file. * @param structure * Name of Structure of data file (not all files use structure). * @param filename * Name of the file. * @param fileLoadingSelectionStatus * Selection status for loading of file. * @param fileSavingSelectionStatus * Selection status for saving of file. * @param specFileMemberStatus * True if the file is a member of the spec file and is written * into the spec file. * * @throws DataFileException * If data file type is UNKNOWN. */ void SpecFile::addDataFile(const AString& dataFileTypeName, const AString& structureName, const AString& filename, const bool fileLoadingSelectionStatus, const bool fileSavingSelectionStatus, const bool specFileMemberStatus) { bool validType = false; DataFileTypeEnum::Enum dataFileType = DataFileTypeEnum::fromName(dataFileTypeName, &validType); bool validStructure = false; StructureEnum::Enum structure = StructureEnum::fromGuiName(structureName, &validStructure); this->addDataFilePrivate(dataFileType, structure, filename, fileLoadingSelectionStatus, fileSavingSelectionStatus, specFileMemberStatus); } /** * Clear the file.. */ void SpecFile::clear() { DataFile::clear(); this->clearData(); } /** * Clear the spec file's data as if there were no files loaded. */ void SpecFile::clearData() { this->metadata->clear(); /* * Do not clear the vector, just remove file information from all types */ for (std::vector::const_iterator iter = dataFileTypeGroups.begin(); iter != dataFileTypeGroups.end(); iter++) { SpecFileDataFileTypeGroup* dataFileTypeGroup = *iter; dataFileTypeGroup->removeAllFileInformation(); } } /** * Is this file empty? * * @return true if file is empty, else false. */ bool SpecFile::isEmpty() const { return (this->getNumberOfFiles() <= 0); } /** * @return The number of files. */ int32_t SpecFile::getNumberOfFiles() const { int count = 0; for (std::vector::const_iterator iter = dataFileTypeGroups.begin(); iter != dataFileTypeGroups.end(); iter++) { SpecFileDataFileTypeGroup* dataFileTypeGroup = *iter; count += dataFileTypeGroup->getNumberOfFiles(); } return count; } /** * @return The number of files selected for loading. */ int32_t SpecFile::getNumberOfFilesSelectedForLoading() const { int count = 0; for (std::vector::const_iterator iter = dataFileTypeGroups.begin(); iter != dataFileTypeGroups.end(); iter++) { SpecFileDataFileTypeGroup* dataFileTypeGroup = *iter; count += dataFileTypeGroup->getNumberOfFilesSelectedForLoading(); } return count; } /** * @return The number of files selected for saving. */ int32_t SpecFile::getNumberOfFilesSelectedForSaving() const { int count = 0; for (std::vector::const_iterator iter = dataFileTypeGroups.begin(); iter != dataFileTypeGroups.end(); iter++) { SpecFileDataFileTypeGroup* dataFileTypeGroup = *iter; count += dataFileTypeGroup->getNumberOfFilesSelectedForSaving(); } return count; } /** * @return True if there is at least one file with a remote path * (http...) AND the file is selected for loading. */ bool SpecFile::hasFilesWithRemotePathSelectedForLoading() const { for (std::vector::const_iterator iter = dataFileTypeGroups.begin(); iter != dataFileTypeGroups.end(); iter++) { const SpecFileDataFileTypeGroup* dataFileTypeGroup = *iter; const int32_t numFiles = dataFileTypeGroup->getNumberOfFiles(); for (int32_t i = 0; i < numFiles; i++) { const SpecFileDataFile* sfdf = dataFileTypeGroup->getFileInformation(i); if (sfdf->isLoadingSelected()) { if (DataFile::isFileOnNetwork(sfdf->getFileName())) { return true; } } } } return false; } /** * @return A vector containing all file names. */ std::vector SpecFile::getAllDataFileNames() const { std::vector allFileNames; for (std::vector::const_iterator iter = dataFileTypeGroups.begin(); iter != dataFileTypeGroups.end(); iter++) { SpecFileDataFileTypeGroup* dataFileTypeGroup = *iter; const int32_t numFiles = dataFileTypeGroup->getNumberOfFiles(); for (int32_t i = 0; i < numFiles; i++) { const AString filename = dataFileTypeGroup->getFileInformation(i)->getFileName(); allFileNames.push_back(filename); } } return allFileNames; } /** * @return True if the only files selected for loading are scene files. */ bool SpecFile::areAllFilesSelectedForLoadingSceneFiles() const { int32_t sceneFileCount = 0; int32_t allFilesCount = 0; for (std::vector::const_iterator iter = dataFileTypeGroups.begin(); iter != dataFileTypeGroups.end(); iter++) { SpecFileDataFileTypeGroup* dataFileTypeGroup = *iter; allFilesCount += dataFileTypeGroup->getNumberOfFilesSelectedForLoading(); if (dataFileTypeGroup->getDataFileType() == DataFileTypeEnum::SCENE) { sceneFileCount += dataFileTypeGroup->getNumberOfFilesSelectedForLoading(); } } if (sceneFileCount > 0) { if (sceneFileCount == allFilesCount) { return true; } } return false; } /** * Remove any files that are not "in spec" and do not have an * associated caret data file. */ void SpecFile::removeAnyFileInformationIfNotInSpecAndNoCaretDataFile() { for (std::vector::const_iterator iter = dataFileTypeGroups.begin(); iter != dataFileTypeGroups.end(); iter++) { SpecFileDataFileTypeGroup* dataFileTypeGroup = *iter; dataFileTypeGroup->removeFileInformationIfNotInSpecAndNoCaretDataFile(); } setModified(); } /** * Read the file. * * @param filenameIn * Name of file to read. * * @throws DataFileException * If there is an error reading the file. */ void SpecFile::readFile(const AString& filenameIn) { clear(); AString filename = filenameIn; if (DataFile::isFileOnNetwork(filename) == false) { FileInformation specInfo(filename); filename = specInfo.getAbsoluteFilePath(); } this->setFileName(filename); checkFileReadability(filename); SpecFileSaxReader saxReader(this); std::auto_ptr parser(XmlSaxParser::createXmlParser()); try { parser->parseFile(filename, &saxReader); } catch (const XmlSaxParserException& e) { clear(); this->setFileName(""); int lineNum = e.getLineNumber(); int colNum = e.getColumnNumber(); AString msg = "Parse Error while reading:"; if ((lineNum >= 0) && (colNum >= 0)) { msg += (" line/col (" + AString::number(e.getLineNumber()) + "/" + AString::number(e.getColumnNumber()) + ")"); } msg += (": " + e.whatString()); DataFileException dfe(filename, msg); CaretLogThrowing(dfe); throw dfe; } this->setFileName(filename); this->setAllFilesSelectedForLoading(true); this->setAllFilesSelectedForSaving(false); this->clearModified(); } /** * Read the spec file from a string containing the files content. * @param string * String containing the file's content. * @throws DataFileException * If there is an error reading the file from the string. */ void SpecFile::readFileFromString(const AString& string) { SpecFileSaxReader saxReader(this); std::auto_ptr parser(XmlSaxParser::createXmlParser()); try { parser->parseString(string, &saxReader); } catch (const XmlSaxParserException& e) { clear(); this->setFileName(""); int lineNum = e.getLineNumber(); int colNum = e.getColumnNumber(); AString msg = "Parse Error while reading Spec File from string."; if ((lineNum >= 0) && (colNum >= 0)) { msg += (" line/col (" + AString::number(e.getLineNumber()) + "/" + AString::number(e.getColumnNumber()) + ")"); } msg += (": " + e.whatString()); DataFileException dfe(getFileName(), msg); CaretLogThrowing(dfe); throw dfe; } this->clearModified(); } /** * Write the file. * * @param filename * Name of file to read. * * @throws DataFileException * If there is an error writing the file. */ void SpecFile::writeFile(const AString& filename) { checkFileWritability(filename); FileInformation specInfo(filename); AString absFileName = specInfo.getAbsoluteFilePath(); this->setFileName(absFileName); try { // // Format the version string so that it ends with at most one zero // const AString versionString = AString::number(1.0); // // Open the file // FileAdapter file; AString errorMessage; QTextStream* textStream = file.openQTextStreamForWritingFile(this->getFileName(), errorMessage); if (textStream == NULL) { throw DataFileException(getFileName(), errorMessage); } // // Create the xml writer // XmlWriter xmlWriter(*textStream); /* * Write the XML and include metadata */ this->writeFileContentToXML(xmlWriter, WRITE_META_DATA_YES, WRITE_IN_SPEC_FILES); file.close(); this->clearModified(); } catch (const GiftiException& e) { throw DataFileException(getFileName(), e.whatString()); } catch (const XmlException& e) { throw DataFileException(getFileName(), e.whatString()); } } /** * Write the file's content to the XML Writer. * @param xmlWriter * XML Writer to which file content is written. * @param writeMetaDataStatus * Yes of no to write metadata. * @throws DataFileException * If there is an error writing to the XML writer. */ void SpecFile::writeFileContentToXML(XmlWriter& xmlWriter, const WriteMetaDataType writeMetaDataStatus, const WriteFilesSelectedType writeFilesSelectedStatus) { // // Write header info // xmlWriter.writeStartDocument("1.0"); // // Write GIFTI root element // XmlAttributes attributes; //attributes.addAttribute("xmlns:xsi", // "http://www.w3.org/2001/XMLSchema-instance"); //attributes.addAttribute("xsi:noNamespaceSchemaLocation", // "http://brainvis.wustl.edu/caret6/xml_schemas/GIFTI_Caret.xsd"); attributes.addAttribute(SpecFile::XML_ATTRIBUTE_VERSION, SpecFile::getFileVersionAsString()); xmlWriter.writeStartElement(SpecFile::XML_TAG_SPEC_FILE, attributes); // // Write Metadata // if (writeMetaDataStatus == WRITE_META_DATA_YES) { if (metadata != NULL) { metadata->writeAsXML(xmlWriter); } } // // Write files // const int32_t numGroups = this->getNumberOfDataFileTypeGroups(); for (int32_t i = 0; i < numGroups; i++) { SpecFileDataFileTypeGroup* group = this->getDataFileTypeGroupByIndex(i); const int32_t numFiles = group->getNumberOfFiles(); for (int32_t j = 0; j < numFiles; j++) { SpecFileDataFile* file = group->getFileInformation(j); bool writeIt = true; switch (writeFilesSelectedStatus) { case WRITE_ALL_FILES: break; case WRITE_IN_SPEC_FILES: writeIt = file->isSpecFileMember(); break; } if (writeIt) { const AString name = updateFileNameAndPathForWriting(file->getFileName()); XmlAttributes atts; atts.addAttribute(SpecFile::XML_ATTRIBUTE_STRUCTURE, StructureEnum::toGuiName(file->getStructure())); atts.addAttribute(SpecFile::XML_ATTRIBUTE_DATA_FILE_TYPE, DataFileTypeEnum::toName(group->getDataFileType())); atts.addAttribute(SpecFile::XML_ATTRIBUTE_SELECTED, file->isLoadingSelected()); xmlWriter.writeStartElement(SpecFile::XML_TAG_DATA_FILE, atts); xmlWriter.writeCharacters(" " + name + "\n"); xmlWriter.writeEndElement(); } } } xmlWriter.writeEndElement(); xmlWriter.writeEndDocument(); } /** * Update the file name for writing to a spec file * (makes file relative to spec file location). */ AString SpecFile::updateFileNameAndPathForWriting(const AString& dataFileNameIn) { AString dataFileName = dataFileNameIn; FileInformation fileInfo(dataFileName); if (fileInfo.isAbsolute()) { const AString specFileName = getFileName(); FileInformation specFileInfo(specFileName); if (specFileInfo.isAbsolute()) { const AString newPath = SystemUtilities::relativePath(fileInfo.getPathName(), specFileInfo.getPathName()); if (newPath.isEmpty()) { dataFileName = fileInfo.getFileName(); } else { dataFileName = (newPath + "/" + fileInfo.getFileName()); } } } AString message = ("When writing, " + dataFileNameIn + " becomes " + dataFileName); CaretLogFine(message); return dataFileName; } /** * Write the file to a XML string. * @param writeMetaDataStatus * Write the metadata to the file. * @return * String containing XML. * @throws DataFileException * If error writing to XML. */ //AString //SpecFile::writeFileToString(const WriteMetaDataType writeMetaDataStatus, // const WriteFilesSelectedType writeFilesSelectedStatus) //{ // /* // * Create a TextStream that writes to a string. // */ // AString xmlString; // QTextStream textStream(&xmlString); // // /* // * Create the xml writer // */ // XmlWriter xmlWriter(textStream); // // /* // * Write file to XML. // */ // this->writeFileContentToXML(xmlWriter, // writeMetaDataStatus, // writeFilesSelectedStatus); // // return xmlString; //} /** * Get information about this file's contents. * @return * Information about the file's contents. */ AString SpecFile::toString() const { AString info = "name=" + this->getFileName() + "\n"; info += this->metadata->toString() + "\n"; for (std::vector::const_iterator iter = dataFileTypeGroups.begin(); iter != dataFileTypeGroups.end(); iter++) { SpecFileDataFileTypeGroup* dataFileTypeGroup = *iter; info += (dataFileTypeGroup->toString() + "\n"); } return info; } /** * @return The number of data file type groups. */ int32_t SpecFile::getNumberOfDataFileTypeGroups() const { return this->dataFileTypeGroups.size(); } /** * Get the data file type group for the given index. * @param dataFileTypeGroupIndex * Index of data file type group. * @return Data file type group at given index. */ SpecFileDataFileTypeGroup* SpecFile::getDataFileTypeGroupByIndex(const int32_t dataFileTypeGroupIndex) { CaretAssertVectorIndex(this->dataFileTypeGroups, dataFileTypeGroupIndex); return this->dataFileTypeGroups[dataFileTypeGroupIndex]; } /** * Get the data file type group for the given index. * @param dataFileTypeGroupIndex * Index of data file type group. * @return Data file type group at given index. */ const SpecFileDataFileTypeGroup* SpecFile::getDataFileTypeGroupByIndex(const int32_t dataFileTypeGroupIndex) const { CaretAssertVectorIndex(this->dataFileTypeGroups, dataFileTypeGroupIndex); return this->dataFileTypeGroups[dataFileTypeGroupIndex]; } /** * Get the data file type group for the given data file type. * @param dataFileType * Data file type requested. * @return Data file type group for requested data file type or * NULL if no matching item found. */ SpecFileDataFileTypeGroup* SpecFile::getDataFileTypeGroupByType(const DataFileTypeEnum::Enum dataFileType) const { for (std::vector::const_iterator iter = dataFileTypeGroups.begin(); iter != dataFileTypeGroups.end(); iter++) { SpecFileDataFileTypeGroup* dataFileTypeGroup = *iter; if (dataFileTypeGroup->getDataFileType() == dataFileType) { return dataFileTypeGroup; } } return NULL; } /** * Set all file's selection status for loading. * @param selected * New selection status for loading. */ void SpecFile::setAllFilesSelectedForLoading(bool selectionStatus) { for (std::vector::iterator iter = dataFileTypeGroups.begin(); iter != dataFileTypeGroups.end(); iter++) { SpecFileDataFileTypeGroup* dataFileTypeGroup = *iter; dataFileTypeGroup->setAllFilesSelectedForLoading(selectionStatus); } } /** * Set all file's selection status for saving. * @param selected * New selection status for saving. */ void SpecFile::setAllFilesSelectedForSaving(bool selectionStatus) { for (std::vector::iterator iter = dataFileTypeGroups.begin(); iter != dataFileTypeGroups.end(); iter++) { SpecFileDataFileTypeGroup* dataFileTypeGroup = *iter; dataFileTypeGroup->setAllFilesSelectedForSaving(selectionStatus); } } /** * Set all scene files selected and all other files not selected. */ void SpecFile::setAllSceneFilesSelectedForLoadingAndAllOtherFilesNotSelected() { for (std::vector::iterator iter = dataFileTypeGroups.begin(); iter != dataFileTypeGroups.end(); iter++) { SpecFileDataFileTypeGroup* dataFileTypeGroup = *iter; const bool selectionStatus = (dataFileTypeGroup->getDataFileType() == DataFileTypeEnum::SCENE); dataFileTypeGroup->setAllFilesSelectedForLoading(selectionStatus); } } /** * Set the save status to on for any files that are modified. */ void SpecFile::setModifiedFilesSelectedForSaving() { for (std::vector::iterator iter = dataFileTypeGroups.begin(); iter != dataFileTypeGroups.end(); iter++) { SpecFileDataFileTypeGroup* dataFileTypeGroup = *iter; dataFileTypeGroup->setModifiedFilesSelectedForSaving(); } } /** * @return The version of the file as a number. */ float SpecFile::getFileVersion() { return SpecFile::specFileVersion; } /** * @return The version of the file as a string. */ AString SpecFile::getFileVersionAsString() { return AString::number(SpecFile::specFileVersion, 'f', 1); } /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @param instanceName * Name of the class' instance. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ SceneClass* SpecFile::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "SpecFile", 1); AString specFileNameForScene; if (sceneAttributes->isSpecFileNameSavedToScene()) { specFileNameForScene = getFileName(); } const bool allLoadedFilesFlag = sceneAttributes->isAllLoadedFilesSavedToScene(); std::set displayedDataFiles; if ( ! allLoadedFilesFlag) { const std::vector tabIndicesForScene = sceneAttributes->getIndicesOfTabsForSavingToScene(); EventGetDisplayedDataFiles displayedFilesEvent(tabIndicesForScene); EventManager::get()->sendEvent(displayedFilesEvent.getPointer()); displayedDataFiles = displayedFilesEvent.getDisplayedDataFiles(); } sceneClass->addPathName("specFileName", specFileNameForScene); std::vector dataFileClasses; // // Write files (except Scene and Palette files) // const int32_t numGroups = this->getNumberOfDataFileTypeGroups(); for (int32_t i = 0; i < numGroups; i++) { SpecFileDataFileTypeGroup* group = this->getDataFileTypeGroupByIndex(i); const DataFileTypeEnum::Enum dataFileType = group->getDataFileType(); if (dataFileType == DataFileTypeEnum::SCENE) { //CaretLogInfo("Note: Scene files not added to scene at this time"); } else if (dataFileType == DataFileTypeEnum::PALETTE) { CaretLogInfo("Note: Palette files not added to scene at this time"); } else { const int32_t numFiles = group->getNumberOfFiles(); for (int32_t j = 0; j < numFiles; j++) { SpecFileDataFile* file = group->getFileInformation(j); /* * Only write files that are loaded (indicated by its * "caretDataFile" not NULL. */ const CaretDataFile* caretDataFile = file->getCaretDataFile(); if (caretDataFile != NULL) { bool addFileToSceneFlag = false; if (allLoadedFilesFlag) { addFileToSceneFlag = true; } else { if (displayedDataFiles.find(caretDataFile) != displayedDataFiles.end()) { addFileToSceneFlag = true; } } if (addFileToSceneFlag) { SceneClass* fileClass = new SceneClass("specFileDataFile", "SpecFileDataFile", 1); fileClass->addEnumeratedType("dataFileType", dataFileType); fileClass->addEnumeratedType("structure", file->getStructure()); const AString name = updateFileNameAndPathForWriting(file->getFileName()); fileClass->addPathName("fileName", file->getFileName()); fileClass->addBoolean("selected", file->isLoadingSelected()); dataFileClasses.push_back(fileClass); } } } } } sceneClass->addChild(new SceneClassArray("dataFilesArray", dataFileClasses)); return sceneClass; } /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * SceneClass containing the state that was previously * saved and should be restored. */ void SpecFile::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } this->clear(); const AString specFileName = sceneClass->getPathNameValue("specFileName", ""); this->setFileName(specFileName); /* * If spec file name is path to a valid file, * load the spec file and then deselect all * of the files in the spec file. Since the * scene may contain a subset of the files in * the spec file, not doing this would result * in the spec file missing data file if the * user saves files after loading the scene. */ if (specFileName.isEmpty() == false) { FileInformation specFileInfo(specFileName); if (specFileInfo.exists()) { try { readFile(specFileName); } catch (const DataFileException& e) { sceneAttributes->addToErrorMessage("Error reading spec file " + specFileName + " for displaying scene: " + e.whatString()); } setAllFilesSelectedForLoading(false); setAllFilesSelectedForSaving(false); } } const SceneClassArray* dataFileClassArray = sceneClass->getClassArray("dataFilesArray"); if (dataFileClassArray != NULL) { const int32_t numberOfFiles = dataFileClassArray->getNumberOfArrayElements(); for (int32_t i = 0; i < numberOfFiles; i++) { const SceneClass* dataFileClass = dataFileClassArray->getClassAtIndex(i); const bool selectedForLoading = dataFileClass->getBooleanValue("selected"); const AString dataFileName = dataFileClass->getPathNameValue("fileName"); const DataFileTypeEnum::Enum dataFileType = dataFileClass->getEnumeratedTypeValue("dataFileType", DataFileTypeEnum::UNKNOWN); const StructureEnum::Enum structure = dataFileClass->getEnumeratedTypeValue("structure", StructureEnum::INVALID); this->addDataFile(dataFileType, structure, dataFileName, selectedForLoading, false, // not selected for saving true); } } } /** * Append content of a spec file to this spec file. * @param toAppend * Spec file that is appended. */ void SpecFile::appendSpecFile(const SpecFile& toAppend) { int numOtherGroups = (int)toAppend.dataFileTypeGroups.size(); AString otherDirectory = FileInformation(toAppend.getFileName()).getAbsolutePath();//hopefully the filename is already absolute, if it isn't and we changed directory, we can't recover the correct path if (!otherDirectory.endsWith('/'))//deal with the root directory { otherDirectory += "/"; } for (int i = 0; i < numOtherGroups; ++i) { const SpecFileDataFileTypeGroup* thisGroup = toAppend.dataFileTypeGroups[i]; int numOtherFiles = thisGroup->getNumberOfFiles(); for (int j = 0; j < numOtherFiles; ++j) { const SpecFileDataFile* fileData = thisGroup->getFileInformation(j); AString fileName = fileData->getFileName(); FileInformation fileInfo(fileName);//do not trust exists, we don't have the right working directory, this is ONLY to check whether the path is absolute if (fileInfo.isRelative()) { fileName = otherDirectory + fileName;//don't trust the file to exist from current directory, use string manipulation only } addDataFile(thisGroup->getDataFileType(), fileData->getStructure(), fileName, fileData->isLoadingSelected(), fileData->isSavingSelected(), fileData->isSpecFileMember());//absolute paths should get converted to relative on writing } } } /** * Set this file modified. */ void SpecFile::setModified() { CaretDataFile::setModified(); } /** * @return true if this file has been modified. */ bool SpecFile::isModified() const { if (CaretDataFile::isModified()) { return true; } for (std::vector::const_iterator iter = dataFileTypeGroups.begin(); iter != dataFileTypeGroups.end(); iter++) { const SpecFileDataFileTypeGroup* dataFileTypeGroup = *iter; if (dataFileTypeGroup->isModified()) { return true; } } return false; } /** * Clear the modification status. */ void SpecFile::clearModified() { CaretDataFile::clearModified(); for (std::vector::iterator iter = dataFileTypeGroups.begin(); iter != dataFileTypeGroups.end(); iter++) { SpecFileDataFileTypeGroup* dataFileTypeGroup = *iter; dataFileTypeGroup->clearModified(); } } /** * Add information about the file to the data file information. * * @param dataFileInformation * Consolidates information about a data file. */ void SpecFile::addToDataFileContentInformation(DataFileContentInformation& dataFileInformation) { CaretDataFile::addToDataFileContentInformation(dataFileInformation); const int32_t numberOfFiles = getNumberOfFiles(); if (numberOfFiles <= 0) { return; } int32_t columnCounter = 0; const int32_t COL_TYPE = columnCounter++; const int32_t COL_STRUCTURE = columnCounter++; const int32_t COL_NAME = columnCounter++; const int32_t COL_PATH = columnCounter++; StringTableModel table((numberOfFiles + 1), columnCounter); int32_t rowIndex = 0; table.setElement(rowIndex, COL_TYPE, "TYPE"); table.setElement(rowIndex, COL_STRUCTURE, "STRUCTURE"); table.setElement(rowIndex, COL_NAME, "NAME"); table.setElement(rowIndex, COL_PATH, "PATH"); rowIndex++; table.setColumnAlignment(COL_TYPE, StringTableModel::ALIGN_LEFT); table.setColumnAlignment(COL_STRUCTURE, StringTableModel::ALIGN_LEFT); table.setColumnAlignment(COL_NAME, StringTableModel::ALIGN_LEFT); table.setColumnAlignment(COL_PATH, StringTableModel::ALIGN_LEFT); for (std::vector::const_iterator iter = dataFileTypeGroups.begin(); iter != dataFileTypeGroups.end(); iter++) { SpecFileDataFileTypeGroup* dataFileTypeGroup = *iter; const DataFileTypeEnum::Enum dataFileType = dataFileTypeGroup->getDataFileType(); const int32_t numFiles = dataFileTypeGroup->getNumberOfFiles(); for (int32_t i = 0; i < numFiles; i++) { SpecFileDataFile* sfdf = dataFileTypeGroup->getFileInformation(i); FileInformation fileInfo(sfdf->getFileName()); table.setElement(rowIndex, COL_TYPE, DataFileTypeEnum::toGuiName(dataFileType)); table.setElement(rowIndex, COL_STRUCTURE, StructureEnum::toGuiName(sfdf->getStructure())); table.setElement(rowIndex, COL_NAME, fileInfo.getFileName()); table.setElement(rowIndex, COL_PATH, fileInfo.getPathName()); rowIndex++; } } dataFileInformation.addText("\n" + table.getInString()); } workbench-1.1.1/src/Files/SpecFile.h000066400000000000000000000200611255417355300172010ustar00rootroot00000000000000#ifndef __SPEC_FILE_H__ #define __SPEC_FILE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretDataFile.h" #include "DataFileTypeEnum.h" #include "StructureEnum.h" namespace caret { class GiftiMetaData; class SpecFileDataFile; class SpecFileDataFileTypeGroup; class XmlWriter; class SpecFile : public CaretDataFile { public: SpecFile(); virtual ~SpecFile(); SpecFile(const SpecFile&); SpecFile& operator=(const SpecFile&); public: virtual void clear(); virtual bool isEmpty() const; virtual StructureEnum::Enum getStructure() const; virtual void setStructure(const StructureEnum::Enum structure); virtual GiftiMetaData* getFileMetaData(); virtual const GiftiMetaData* getFileMetaData() const; virtual void addToDataFileContentInformation(DataFileContentInformation& dataFileInformation); void addCaretDataFile(CaretDataFile* caretDataFile); void removeCaretDataFile(const CaretDataFile* caretDataFile); SpecFileDataFile* changeFileName(SpecFileDataFile* specFileDataFile, const AString& newFileName); void addDataFile(const DataFileTypeEnum::Enum dataFileType, const StructureEnum::Enum structure, const AString& filename, const bool fileLoadingSelectionStatus, const bool fileSavingSelectionStatus, const bool specFileMemberStatus); void addDataFile(const AString& dataFileTypeName, const AString& structureName, const AString& filename, const bool fileLoadingSelectionStatus, const bool fileSavingSelectionStatus, const bool specFileMemberStatus); void setFileLoadingSelectionStatus(const DataFileTypeEnum::Enum dataFileType, const StructureEnum::Enum structure, const AString& filename, const bool fileSelectionStatus); bool hasFilesWithRemotePathSelectedForLoading() const; int32_t getNumberOfFiles() const; int32_t getNumberOfFilesSelectedForLoading() const; int32_t getNumberOfFilesSelectedForSaving() const; std::vector getAllDataFileNames() const; bool areAllFilesSelectedForLoadingSceneFiles() const; void removeAnyFileInformationIfNotInSpecAndNoCaretDataFile(); virtual void readFile(const AString& filename); virtual void writeFile(const AString& filename); virtual AString toString() const; AString getText() const; int32_t getNumberOfDataFileTypeGroups() const; SpecFileDataFileTypeGroup* getDataFileTypeGroupByIndex(const int32_t dataFileTypeGroupIndex); const SpecFileDataFileTypeGroup* getDataFileTypeGroupByIndex(const int32_t dataFileTypeGroupIndex) const; SpecFileDataFileTypeGroup* getDataFileTypeGroupByType(const DataFileTypeEnum::Enum dataFileType) const; void getAllConnectivityFileTypes(std::vector& connectivityDataFiles); void setAllFilesSelectedForLoading(bool selectionStatus); void setAllFilesSelectedForSaving(bool selectionStatus); void setAllSceneFilesSelectedForLoadingAndAllOtherFilesNotSelected(); void setModifiedFilesSelectedForSaving(); static float getFileVersion(); static AString getFileVersionAsString(); virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); void appendSpecFile(const SpecFile& toAppend); virtual void setModified(); virtual bool isModified() const; virtual void clearModified(); /** XML Tag for SpecFile element */ static const AString XML_TAG_SPEC_FILE; /** XML Tag for DataFile element */ static const AString XML_TAG_DATA_FILE; /** XML Tag for Structure attribute */ static const AString XML_ATTRIBUTE_STRUCTURE; /** XML Tag for DataFileType attribute */ static const AString XML_ATTRIBUTE_DATA_FILE_TYPE; /** XML Tag for data file selection status */ static const AString XML_ATTRIBUTE_SELECTED; /** XML Tag for Version attribute */ static const AString XML_ATTRIBUTE_VERSION; /** Version of this SpecFile */ static const float specFileVersion; private: enum WriteMetaDataType { WRITE_META_DATA_YES, WRITE_META_DATA_NO }; enum WriteFilesSelectedType { WRITE_ALL_FILES, WRITE_IN_SPEC_FILES }; void copyHelperSpecFile(const SpecFile& sf); void initializeSpecFile(); void clearData(); SpecFileDataFile* addDataFilePrivate(const DataFileTypeEnum::Enum dataFileType, const StructureEnum::Enum structure, const AString& filename, const bool fileLoadingSelectionStatus, const bool fileSavingSelectionStatus, const bool specFileMemberStatus); void readFileFromString(const AString& string); AString updateFileNameAndPathForWriting(const AString& dataFileName); // AString writeFileToString(const WriteMetaDataType writeMetaDataStatus, // const WriteFilesSelectedType writeFilesSelectedStatus); void writeFileContentToXML(XmlWriter& xmlWriter, const WriteMetaDataType writeMetaDataStatus, const WriteFilesSelectedType writeFilesSelectedStatus); std::vector dataFileTypeGroups; GiftiMetaData* metadata; }; #ifdef __SPEC_FILE_DEFINE__ const AString SpecFile::XML_TAG_SPEC_FILE = "CaretSpecFile"; const AString SpecFile::XML_TAG_DATA_FILE = "DataFile"; const AString SpecFile::XML_ATTRIBUTE_STRUCTURE = "Structure"; const AString SpecFile::XML_ATTRIBUTE_DATA_FILE_TYPE = "DataFileType"; const AString SpecFile::XML_ATTRIBUTE_SELECTED = "Selected"; const AString SpecFile::XML_ATTRIBUTE_VERSION = "Version"; const float SpecFile::specFileVersion = 1.0; #endif // __SPEC_FILE_DEFINE__ } // namespace #endif // __SPEC_FILE_H__ workbench-1.1.1/src/Files/SpecFileDataFile.cxx000066400000000000000000000156021255417355300211530ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SPEC_FILE_GROUP_FILE_DECLARE__ #include "SpecFileDataFile.h" #undef __SPEC_FILE_GROUP_FILE_DECLARE__ #include "FileInformation.h" using namespace caret; /** * \class caret::SpecFileDataFile * \brief Name of file and its attributes including structure. * \ingroup Files * * Contains the name of a file and is attributes including * the file's structure. Note that not all files use a * structure. */ /** * Constructor. * * @param filename * Name of file. * @param dataFileType * Type of data file. * @param structure * Structure file represents (not all files have structure). * @param specFileMember * True if this file is a member of the spec file and is thus * written to the spec file. */ SpecFileDataFile::SpecFileDataFile(const AString& filename, const DataFileTypeEnum::Enum dataFileType, const StructureEnum::Enum structure, const bool specFileMember) : CaretObjectTracksModification() { m_caretDataFile = NULL; m_filename = filename; m_dataFileType = dataFileType; m_structure = structure; m_loadingSelected = true; m_savingSelected = false; m_specFileMember = specFileMember; } /** * Copy constructor. * @param sfdf * Object of this type that is copied. */ SpecFileDataFile::SpecFileDataFile(const SpecFileDataFile& sfdf) : CaretObjectTracksModification(sfdf) { copyHelper(sfdf); } /** * Assignment operator. * @parm sfdf * Object that is assigned to this object. * @return * Reference to this object. */ SpecFileDataFile& SpecFileDataFile::operator=(const SpecFileDataFile& sfdf) { if (this != &sfdf) { CaretObjectTracksModification::operator=(sfdf); copyHelper(sfdf); } return *this; } /** * Copy from the given object to this object. * @param sfdf * Object from which data is copied. */ void SpecFileDataFile::copyHelper(const SpecFileDataFile& sfdf) { m_caretDataFile = sfdf.m_caretDataFile; m_filename = sfdf.m_filename; m_dataFileType = sfdf.m_dataFileType; m_structure = sfdf.m_structure; m_loadingSelected = sfdf.m_loadingSelected; m_savingSelected = sfdf.m_savingSelected; m_specFileMember = sfdf.m_specFileMember; } /** * Destructor. */ SpecFileDataFile::~SpecFileDataFile() { } /** * @return The file's name; */ AString SpecFileDataFile::getFileName() const { if (m_caretDataFile != NULL) { m_filename = m_caretDataFile->getFileName(); } return m_filename; } void SpecFileDataFile::setFileName(const AString& fileName) { m_filename = fileName; if (m_caretDataFile != NULL) { m_caretDataFile->setFileName(fileName); } setModified(); } /** * @return The caret data file that is loaded for this spec file entry. * Will return NULL if the file has not been loaded. */ CaretDataFile* SpecFileDataFile::getCaretDataFile() { return m_caretDataFile; } /** * Set the caret data file for this spec file entry. * @param caretDataFile * The caret data file. */ void SpecFileDataFile::setCaretDataFile(CaretDataFile* caretDataFile) { m_caretDataFile = caretDataFile; } /** * @return The file's structure. */ StructureEnum::Enum SpecFileDataFile::getStructure() const { if (m_caretDataFile != NULL) { m_structure = m_caretDataFile->getStructure(); } return m_structure; } /** * Set the structure. * @param structure * New value for structure. */ void SpecFileDataFile::setStructure(const StructureEnum::Enum structure) { if (m_structure != structure) { m_structure = structure; setModified(); } } /** * @return The data file type for this group. */ DataFileTypeEnum::Enum SpecFileDataFile::getDataFileType() const { return m_dataFileType; } /** * @return The file's saving selection status. */ bool SpecFileDataFile::isSavingSelected() const { return m_savingSelected; } /** * Set the file's saving selection status. * DOES NOT alter modification status. * @param selected * New selection status. */ void SpecFileDataFile::setSavingSelected(const bool selected) { if (m_savingSelected != selected) { m_savingSelected = selected; } } /** * @return The file's loading selection status. */ bool SpecFileDataFile::isLoadingSelected() const { return m_loadingSelected; } /** * Set the file's loading selection status. * @param selected * New selection status. */ void SpecFileDataFile::setLoadingSelected(const bool selected) { // note: does not cause modification status to change m_loadingSelected = selected; } /** * Get a description of this object's content. * @return String describing this object's content. */ AString SpecFileDataFile::toString() const { const AString info = "name=" + m_filename + ", structure=" + StructureEnum::toGuiName(m_structure) + ", dataFileType=" + DataFileTypeEnum::toGuiName(m_dataFileType); //+ ", selected=" + AString::fromBool(m_selected); return info; } /** * @return True if this file is a selected as a member of the spec file. * If true, then the spec file is written, this file will be listed in * the spec file. */ bool SpecFileDataFile::isSpecFileMember() const { return m_specFileMember; } /** * Set this file is a selected as a member of the spec file using the given * status. * * @param status If true, then the spec file is written, this file will * be listed in the spec file. */ void SpecFileDataFile::setSpecFileMember(const bool status) { if (m_specFileMember != status) { m_specFileMember = status; setModified(); } } /** * @return True if the file "exists". * * If the file is on the file system, true is returned if the file exists, * else false. * * If the file is remote (http, ftp, etc), true is ALWAYS returned. */ bool SpecFileDataFile::exists() const { if (DataFile::isFileOnNetwork(getFileName())) { return true; } FileInformation fileInfo(getFileName()); if (fileInfo.exists()) { return true; } return false; } workbench-1.1.1/src/Files/SpecFileDataFile.h000066400000000000000000000060161255417355300205770ustar00rootroot00000000000000#ifndef __SPEC_FILE_DATA_FILE_H__ #define __SPEC_FILE_DATA_FILE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretDataFile.h" #include "CaretObjectTracksModification.h" #include "DataFileTypeEnum.h" #include "StructureEnum.h" namespace caret { class SpecFileDataFile : public CaretObjectTracksModification { public: SpecFileDataFile(const AString& filename, const DataFileTypeEnum::Enum dataFileType, const StructureEnum::Enum structure, const bool specFileMember); virtual ~SpecFileDataFile(); SpecFileDataFile(const SpecFileDataFile& sfdf); SpecFileDataFile& operator=(const SpecFileDataFile& sfdf); AString getFileName() const; void setFileName(const AString& fileName); CaretDataFile* getCaretDataFile(); void setCaretDataFile(CaretDataFile* caretDataFile); DataFileTypeEnum::Enum getDataFileType() const; StructureEnum::Enum getStructure() const; void setStructure(const StructureEnum::Enum structure); bool isLoadingSelected() const; void setLoadingSelected(const bool selected); bool isSavingSelected() const; void setSavingSelected(const bool selected); bool isSpecFileMember() const; void setSpecFileMember(const bool status); bool exists() const; public: virtual AString toString() const; private: SpecFileDataFile(); // not implemented void copyHelper(const SpecFileDataFile& sfdf); mutable AString m_filename; CaretDataFile* m_caretDataFile; mutable StructureEnum::Enum m_structure; DataFileTypeEnum::Enum m_dataFileType; bool m_loadingSelected; bool m_savingSelected; bool m_specFileMember; }; #ifdef __SPEC_FILE_GROUP_FILE_DECLARE__ // #endif // __SPEC_FILE_GROUP_FILE_DECLARE__ } // namespace #endif // __SPEC_FILE_DATA_FILE_H__ workbench-1.1.1/src/Files/SpecFileDataFileTypeGroup.cxx000066400000000000000000000171521255417355300230340ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SPEC_FILE_GROUP_DECLARE__ #include "SpecFileDataFileTypeGroup.h" #undef __SPEC_FILE_GROUP_DECLARE__ #include "CaretAssert.h" #include "SpecFileDataFile.h" using namespace caret; /** * \class caret::SpecFileDataFileTypeGroup * \brief Groups files of the same DataFileType. * * Groups files of the same data type in a SpecFile. */ /** * Constructor. * @param dataFileType * Type of data file. */ SpecFileDataFileTypeGroup::SpecFileDataFileTypeGroup(const DataFileTypeEnum::Enum dataFileType) : CaretObjectTracksModification() { this->dataFileType = dataFileType; } /** * Destructor. */ SpecFileDataFileTypeGroup::~SpecFileDataFileTypeGroup() { this->removeAllFileInformation(); } /** * @return The data file type for this group. */ DataFileTypeEnum::Enum SpecFileDataFileTypeGroup::getDataFileType() const { return this->dataFileType; } /** * @return The number of files. */ int32_t SpecFileDataFileTypeGroup::getNumberOfFiles() const { return this->files.size(); } /** * @return The number of files selected for loading. */ int32_t SpecFileDataFileTypeGroup::getNumberOfFilesSelectedForLoading() const { int count = 0; for (std::vector::const_iterator iter = this->files.begin(); iter != this->files.end(); iter++) { SpecFileDataFile* file = *iter; if (file->isLoadingSelected()) { count++; } } return count; } /** * @return The number of files selected for saving. */ int32_t SpecFileDataFileTypeGroup::getNumberOfFilesSelectedForSaving() const { int count = 0; for (std::vector::const_iterator iter = this->files.begin(); iter != this->files.end(); iter++) { SpecFileDataFile* file = *iter; if (file->isSavingSelected()) { count++; } } return count; } /** * Add a file. * @param fileInformation * New file information. */ void SpecFileDataFileTypeGroup::addFileInformation(SpecFileDataFile* fileInformation) { CaretAssert(fileInformation); this->files.push_back(fileInformation); /* * Do not set the modfication status when a data file is added */ } /** * Remove file at given index. * @param fileIndex * Index of file for removal. */ void SpecFileDataFileTypeGroup::removeFileInformation(const int32_t fileIndex) { CaretAssertVectorIndex(this->files, fileIndex); delete this->files[fileIndex]; this->files.erase(this->files.begin() + fileIndex); setModified(); } /** * Remove any files that are not "in spec" and do not have an * associated caret data file. */ void SpecFileDataFileTypeGroup::removeFileInformationIfNotInSpecAndNoCaretDataFile() { const int32_t numFiles = getNumberOfFiles(); for (int32_t i = (numFiles - 1); i >= 0; i--) { SpecFileDataFile* sfdf = getFileInformation(i); if (sfdf->isSpecFileMember() == false) { if (sfdf->getCaretDataFile() == NULL) { removeFileInformation(i); } } } } /** * Remove all files. */ void SpecFileDataFileTypeGroup::removeAllFileInformation() { for (std::vector::iterator iter = this->files.begin(); iter != this->files.end(); iter++) { delete *iter; } this->files.clear(); setModified(); } /** * Get information for a file. * @param fileIndex * Index of file for which information is requested. * @return * Information for file. */ SpecFileDataFile* SpecFileDataFileTypeGroup::getFileInformation(const int32_t fileIndex) { CaretAssertVectorIndex(this->files, fileIndex); return this->files[fileIndex]; } /** * Get information for a file. * @param fileIndex * Index of file for which information is requested. * @return * Information for file. */ const SpecFileDataFile* SpecFileDataFileTypeGroup::getFileInformation(const int32_t fileIndex) const { CaretAssertVectorIndex(this->files, fileIndex); return this->files[fileIndex]; } /** * Set the loading selection status of all files. * @param selectionStatus * New loading selection status for all files. */ void SpecFileDataFileTypeGroup::setAllFilesSelectedForLoading(bool selectionStatus) { for (std::vector::iterator iter = this->files.begin(); iter != this->files.end(); iter++) { SpecFileDataFile* file = *iter; file->setLoadingSelected(selectionStatus); } } /** * Set the saving selection status of all files. * @param selectionStatus * New saving selection status for all files. */ void SpecFileDataFileTypeGroup::setAllFilesSelectedForSaving(bool selectionStatus) { for (std::vector::iterator iter = this->files.begin(); iter != this->files.end(); iter++) { SpecFileDataFile* file = *iter; file->setSavingSelected(selectionStatus); } } /** * Set the save status to on for any files that are modified. */ void SpecFileDataFileTypeGroup::setModifiedFilesSelectedForSaving() { for (std::vector::iterator iter = this->files.begin(); iter != this->files.end(); iter++) { SpecFileDataFile* file = *iter; if (file->getCaretDataFile() != NULL) { if (file->getCaretDataFile()->isModified()) { file->setSavingSelected(true); } } } } /** * Get a description of this object's content. * @return String describing this object's content. */ AString SpecFileDataFileTypeGroup::toString() const { AString info = "DataFileType=" + DataFileTypeEnum::toName(this->dataFileType) + "\n"; for (std::vector::const_iterator iter = this->files.begin(); iter != this->files.end(); iter++) { SpecFileDataFile* file = *iter; info += (" " + file->toString() + "\n"); } return info; } /** * Set the status to unmodified. */ void SpecFileDataFileTypeGroup::clearModified() { CaretObjectTracksModification::clearModified(); for (std::vector::iterator iter = this->files.begin(); iter != this->files.end(); iter++) { SpecFileDataFile* sfdf = *iter; sfdf->clearModified(); } } /** * Is the object modified? * @return true if modified, else false. */ bool SpecFileDataFileTypeGroup::isModified() const { if (CaretObjectTracksModification::isModified()) { return true; } for (std::vector::const_iterator iter = this->files.begin(); iter != this->files.end(); iter++) { const SpecFileDataFile* sfdf = *iter; if (sfdf->isModified()) { return true; } } return false; } workbench-1.1.1/src/Files/SpecFileDataFileTypeGroup.h000066400000000000000000000054631255417355300224630ustar00rootroot00000000000000#ifndef __SPEC_FILE_DATA_FILE_TYPE_GROUP_H__ #define __SPEC_FILE_DATA_FILE_TYPE_GROUP_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObjectTracksModification.h" #include "DataFileTypeEnum.h" namespace caret { class SpecFileDataFile; class SpecFileDataFileTypeGroup : public CaretObjectTracksModification { public: SpecFileDataFileTypeGroup(const DataFileTypeEnum::Enum dataFileType); virtual ~SpecFileDataFileTypeGroup(); DataFileTypeEnum::Enum getDataFileType() const; int32_t getNumberOfFiles() const; int32_t getNumberOfFilesSelectedForLoading() const; int32_t getNumberOfFilesSelectedForSaving() const; void addFileInformation(SpecFileDataFile* fileInformation); void removeFileInformation(const int32_t fileIndex); void removeAllFileInformation(); void removeFileInformationIfNotInSpecAndNoCaretDataFile(); SpecFileDataFile* getFileInformation(const int32_t fileIndex); const SpecFileDataFile* getFileInformation(const int32_t fileIndex) const; void setAllFilesSelectedForLoading(bool selectionStatus); void setAllFilesSelectedForSaving(bool selectionStatus); void setModifiedFilesSelectedForSaving(); virtual void clearModified(); virtual bool isModified() const; private: SpecFileDataFileTypeGroup(const SpecFileDataFileTypeGroup&); SpecFileDataFileTypeGroup& operator=(const SpecFileDataFileTypeGroup&); public: virtual AString toString() const; private: std::vector files; DataFileTypeEnum::Enum dataFileType; }; #ifdef __SPEC_FILE_GROUP_DECLARE__ // #endif // __SPEC_FILE_GROUP_DECLARE__ } // namespace #endif // __SPEC_FILE_DATA_FILE_TYPE_GROUP_H__ workbench-1.1.1/src/Files/SpecFileSaxReader.cxx000066400000000000000000000202501255417355300213530ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretAssert.h" #include "CaretLogger.h" #include "DataFileException.h" #include "GiftiXmlElements.h" #include "SpecFile.h" #include "SpecFileSaxReader.h" #include "GiftiMetaDataSaxReader.h" #include "XmlAttributes.h" #include "XmlException.h" using namespace caret; /** * constructor. */ SpecFileSaxReader::SpecFileSaxReader(SpecFile* specFileIn) { CaretAssert(specFileIn); this->specFile = specFileIn; this->state = STATE_NONE; this->stateStack.push(this->state); this->elementText = ""; this->metaDataSaxReader = NULL; } /** * destructor. */ SpecFileSaxReader::~SpecFileSaxReader() { } /** * start an element. */ void SpecFileSaxReader::startElement(const AString& namespaceURI, const AString& localName, const AString& qName, const XmlAttributes& attributes) { const STATE previousState = this->state; switch (this->state) { case STATE_NONE: if (qName == SpecFile::XML_TAG_SPEC_FILE) { this->state = STATE_SPEC_FILE; // // Check version of file being read // const float version = attributes.getValueAsFloat(SpecFile::XML_ATTRIBUTE_VERSION); if (version > SpecFile::getFileVersion()) { AString msg = "File version is " + AString::number(version) + " but versions newer than " + SpecFile::getFileVersionAsString() + " are not supported. Update your software."; XmlSaxParserException e(msg); CaretLogThrowing(e); throw e; } else if (version < 1.0) { AString msg = "File version is " + AString::number(version) + " but versions before" + SpecFile::getFileVersionAsString() + " are not supported. Update your software."; XmlSaxParserException e(msg); CaretLogThrowing(e); throw e; } } else if (qName == "Spec_File") { const AString msg = ("You are trying to read a Spec File from Caret5 " "that is incompatible with Workbench. Use the " "program wb_import (included in the " "Workbench distribution) to convert files from " "Caret5 formats to Workbench formats"); XmlSaxParserException e(msg); CaretLogThrowing(e); throw e; } else { const AString msg = "Root elements is " + qName + " but should be " + SpecFile::XML_TAG_SPEC_FILE; XmlSaxParserException e(msg); CaretLogThrowing(e); throw e; } break; case STATE_SPEC_FILE: if (qName == GiftiXmlElements::TAG_METADATA) { this->state = STATE_METADATA; this->metaDataSaxReader = new GiftiMetaDataSaxReader(this->specFile->getFileMetaData()); this->metaDataSaxReader->startElement(namespaceURI, localName, qName, attributes); } else if (qName == SpecFile::XML_TAG_DATA_FILE) { this->state = STATE_DATA_FILE; this->fileAttributeStructureName = attributes.getValue(SpecFile::XML_ATTRIBUTE_STRUCTURE); this->fileAttributeTypeName = attributes.getValue(SpecFile::XML_ATTRIBUTE_DATA_FILE_TYPE); this->fileAttributeSelectionStatus = attributes.getValueAsBoolean(SpecFile::XML_ATTRIBUTE_SELECTED, false); } else { const AString msg = "Invalid child of " + SpecFile::XML_TAG_SPEC_FILE + " is " + qName; XmlSaxParserException e(msg); CaretLogThrowing(e); throw e; } break; case STATE_METADATA: this->metaDataSaxReader->startElement(namespaceURI, localName, qName, attributes); break; case STATE_DATA_FILE: break; } // // Save previous state // stateStack.push(previousState); elementText = ""; } /** * end an element. */ void SpecFileSaxReader::endElement(const AString& namespaceURI, const AString& localName, const AString& qName) { switch (this->state) { case STATE_NONE: break; case STATE_SPEC_FILE: break; case STATE_METADATA: this->metaDataSaxReader->endElement(namespaceURI, localName, qName); if (qName == GiftiXmlElements::TAG_METADATA) { delete this->metaDataSaxReader; this->metaDataSaxReader = NULL; } break; case STATE_DATA_FILE: { try { const AString filename = this->elementText.trimmed(); this->specFile->addDataFile(this->fileAttributeTypeName, this->fileAttributeStructureName, filename, this->fileAttributeSelectionStatus, false, // not selected for saving true); // is a member of spec file since read from spec file this->fileAttributeTypeName = ""; this->fileAttributeStructureName = ""; this->fileAttributeSelectionStatus = false; } catch (const DataFileException& e) { throw XmlSaxParserException(e); } } break; } // // Clear out for new elements // this->elementText = ""; // // Go to previous state // if (this->stateStack.empty()) { throw XmlSaxParserException("State stack is empty while reading XML NiftDataFile."); } this->state = stateStack.top(); this->stateStack.pop(); } /** * get characters in an element. */ void SpecFileSaxReader::characters(const char* ch) { if (this->metaDataSaxReader != NULL) { this->metaDataSaxReader->characters(ch); } else { elementText += ch; } } /** * a fatal error occurs. */ void SpecFileSaxReader::fatalError(const XmlSaxParserException& e) { /* std::ostringstream str; str << "Fatal Error at line number: " << e.getLineNumber() << "\n" << "Column number: " << e.getColumnNumber() << "\n" << "Message: " << e.whatString(); if (errorMessage.isEmpty() == false) { str << "\n" << errorMessage; } errorMessage = str.str(); */ // // Stop parsing // throw e; } // a warning occurs void SpecFileSaxReader::warning(const XmlSaxParserException& e) { CaretLogWarning("XML Parser Warning: " + e.whatString()); } // an error occurs void SpecFileSaxReader::error(const XmlSaxParserException& e) { CaretLogSevere("XML Parser Error: " + e.whatString()); throw e; } void SpecFileSaxReader::startDocument() { } void SpecFileSaxReader::endDocument() { } workbench-1.1.1/src/Files/SpecFileSaxReader.h000066400000000000000000000064111255417355300210030ustar00rootroot00000000000000 #ifndef __SPEC_FILE_SAX_READER_H__ #define __SPEC_FILE_SAX_READER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include "XmlSaxParserException.h" #include "XmlSaxParserHandlerInterface.h" namespace caret { class SpecFile; class SpecFileDataFileTypeGroup; class GiftiMetaDataSaxReader; class XmlAttributes; class XmlException; /// class for reading a Spec File with a SAX Parser class SpecFileSaxReader : public CaretObject, public XmlSaxParserHandlerInterface { public: SpecFileSaxReader(SpecFile* specFileIn); virtual ~SpecFileSaxReader(); void startElement(const AString& namespaceURI, const AString& localName, const AString& qName, const XmlAttributes& attributes); void endElement(const AString& namspaceURI, const AString& localName, const AString& qName); void characters(const char* ch); void fatalError(const XmlSaxParserException& e); void warning(const XmlSaxParserException& e); void error(const XmlSaxParserException& e); void startDocument(); void endDocument(); protected: /// file reading states enum STATE { /// no state STATE_NONE, /// processing SpecFile tag STATE_SPEC_FILE, /// processing MetaData tag STATE_METADATA, /// processing DataFile tag STATE_DATA_FILE }; /// file reading state STATE state; /// the state stack used when reading a file std::stack stateStack; /// the error message AString errorMessage; /// Spec file that is being read SpecFile* specFile; /// element text AString elementText; /// GIFTI meta data sax reader GiftiMetaDataSaxReader* metaDataSaxReader; /** value of File Structure attribute */ AString fileAttributeStructureName; /** value of File type attribute */ AString fileAttributeTypeName; /** value of File selection status */ bool fileAttributeSelectionStatus; }; } // namespace #endif // __SPEC_FILE_SAX_READER_H__ workbench-1.1.1/src/Files/StudyMetaDataLink.cxx000066400000000000000000000205611255417355300214160ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #define __STUDY_META_DATA_LINK_MAIN__ #include "StudyMetaDataLink.h" #undef __STUDY_META_DATA_LINK_MAIN__ #include "CaretLogger.h" #include "XmlWriter.h" using namespace caret; /** * \class caret::StudyMetaDataLink * \brief A link to study data. */ /** * constructor. */ StudyMetaDataLink::StudyMetaDataLink() { clear(); } /** * destructor. */ StudyMetaDataLink::~StudyMetaDataLink() { } /** * copy constructor. */ StudyMetaDataLink::StudyMetaDataLink(const StudyMetaDataLink& smdl) : CaretObject(smdl){ copyHelper(smdl); } /** * assignment opertator. */ StudyMetaDataLink& StudyMetaDataLink::operator=(const StudyMetaDataLink& smdl) { if (this != &smdl) { CaretObject::operator=(smdl); copyHelper(smdl); } return *this; } /** * copy helper. */ void StudyMetaDataLink::copyHelper(const StudyMetaDataLink& smdl) { m_pubMedID = smdl.m_pubMedID; m_tableNumber = smdl.m_tableNumber; m_tableSubHeaderNumber = smdl.m_tableSubHeaderNumber; m_figureNumber = smdl.m_figureNumber; m_panelNumberOrLetter = smdl.m_panelNumberOrLetter; m_pageReferencePageNumber = smdl.m_pageReferencePageNumber; m_pageReferenceSubHeaderNumber = smdl.m_pageReferenceSubHeaderNumber; } /** * equality operator. */ bool StudyMetaDataLink::operator==(const StudyMetaDataLink& smdl) const { const bool theSame = ((m_pubMedID == smdl.m_pubMedID) && (m_tableNumber == smdl.m_tableNumber) && (m_tableSubHeaderNumber == smdl.m_tableSubHeaderNumber) && (m_figureNumber == smdl.m_figureNumber) && (m_panelNumberOrLetter == smdl.m_panelNumberOrLetter) && (m_pageReferencePageNumber == smdl.m_pageReferencePageNumber) && (m_pageReferenceSubHeaderNumber == smdl.m_pageReferenceSubHeaderNumber)); return theSame; } /** * clear the link. */ void StudyMetaDataLink::clear() { m_pubMedID = "0"; m_tableNumber = ""; m_tableSubHeaderNumber = ""; m_figureNumber = ""; m_panelNumberOrLetter = ""; m_pageReferencePageNumber = ""; m_pageReferenceSubHeaderNumber = ""; } /** * set the table number (blank if invalid). */ void StudyMetaDataLink::setTableNumber(const AString& tn) { if (tn == "-1") { m_tableNumber = ""; } else { m_tableNumber = tn; } } /** * set the table sub header number (blank if invalid). */ void StudyMetaDataLink::setTableSubHeaderNumber(const AString& tshn) { if (tshn == "-1") { m_tableSubHeaderNumber = ""; } else { m_tableSubHeaderNumber = tshn; } } /** * set the panel letter/number (blank if invalid). */ void StudyMetaDataLink::setFigurePanelNumberOrLetter(const AString& pnl) { if (pnl == "-1") { m_panelNumberOrLetter = ""; } else { m_panelNumberOrLetter = pnl; } } /** * set the figure number (blank if invalid). */ void StudyMetaDataLink::setFigureNumber(const AString& fn) { if (fn == "-1") { m_figureNumber = ""; } else { m_figureNumber = fn; } } /** * set the page reference page number (blank if invalid). */ void StudyMetaDataLink::setPageReferencePageNumber(const AString& prpn) { if (prpn == "-1") { m_pageReferencePageNumber = ""; } else { m_pageReferencePageNumber = prpn; } } /** * set the page reference sub header number (blank if invalid). */ void StudyMetaDataLink::setPageReferenceSubHeaderNumber(const AString& tshn) { if (tshn == "-1") { m_pageReferenceSubHeaderNumber = ""; } else { m_pageReferenceSubHeaderNumber = tshn; } } /** * set element from text (used by SAX XML parser). */ void StudyMetaDataLink::setElementFromText(const AString& elementName, const AString& textValue) { if (elementName == XML_TAG_PUBMED_ID) { setPubMedID(textValue); } else if (elementName == XML_TAG_TABLE_NUMBER) { setTableNumber(textValue); } else if (elementName == XML_TAG_TABLE_SUB_HEADER_NUMBER) { setTableSubHeaderNumber(textValue); } else if (elementName == XML_TAG_FIGURE_NUMBER) { setFigureNumber(textValue); } else if (elementName == XML_TAG_PANEL_NUMBER_OR_LETTER) { setFigurePanelNumberOrLetter(textValue); } else if (elementName == XML_TAG_PAGE_REFERENCE_PAGE_NUMBER) { setPageReferencePageNumber(textValue); } else if (elementName == XML_TAG_PAGE_REFERENCE_SUB_HEADER_NUMBER) { setPageReferenceSubHeaderNumber(textValue); } else { CaretLogWarning("Unrecognized StudyMetaDataLink element ignored: " + elementName); } } /** * called to write XML. */ void StudyMetaDataLink::writeXML(XmlWriter& xmlWriter) const { xmlWriter.writeStartElement(XML_TAG_STUDY_META_DATA_LINK); xmlWriter.writeElementCData(XML_TAG_PUBMED_ID, m_pubMedID); xmlWriter.writeElementCData(XML_TAG_TABLE_NUMBER, m_tableNumber); xmlWriter.writeElementCData(XML_TAG_TABLE_SUB_HEADER_NUMBER, m_tableSubHeaderNumber); xmlWriter.writeElementCData(XML_TAG_FIGURE_NUMBER, m_figureNumber); xmlWriter.writeElementCData(XML_TAG_PANEL_NUMBER_OR_LETTER, m_panelNumberOrLetter); xmlWriter.writeElementCData(XML_TAG_PAGE_REFERENCE_PAGE_NUMBER, m_pageReferencePageNumber); xmlWriter.writeElementCData(XML_TAG_PAGE_REFERENCE_SUB_HEADER_NUMBER, m_pageReferenceSubHeaderNumber); xmlWriter.writeEndElement(); } /** * get the entire link in an "coded" text form. */ AString StudyMetaDataLink::getLinkAsCodedText() const { // // Assemble into one string containing key/value pairs separated by a semi-colon // QStringList sl; sl << ("pubMedID=" + m_pubMedID) << ("tableNumber=" + m_tableNumber) << ("tableSubHeaderNumber=" + m_tableSubHeaderNumber) << ("figureNumber=" + m_figureNumber) << ("panelNumberOrLetter=" + m_panelNumberOrLetter) << ("pageReferencePageNumber=" + m_pageReferencePageNumber) << ("pageReferenceSubHeaderNumber=" + m_pageReferenceSubHeaderNumber); const AString s = sl.join(";"); return s; } /** * set the link from "coded" text form. */ void StudyMetaDataLink::setLinkFromCodedText(const AString& txt) { // // Clear this link // clear(); // // Extract the key/value pairs that are separated by a semi-colon // const QStringList sl = txt.split(";", AString::SkipEmptyParts); for (int i = 0; i < sl.size(); i++) { const AString keyValueString = sl.at(i); // // Split with "=" into key/value pairs // const QStringList keyValueList = keyValueString.split("=", AString::SkipEmptyParts); if (keyValueList.size() == 2) { const AString key = keyValueList.at(0); const AString value = keyValueList.at(1).trimmed(); if (key == "pubMedID") { setPubMedID(value); } else if (key == "tableNumber") { setTableNumber(value); } else if (key == "tableSubHeaderNumber") { setTableSubHeaderNumber(value); } else if (key == "figureNumber") { setFigureNumber(value); } else if (key == "panelNumberOrLetter") { setFigurePanelNumberOrLetter(value); } else if (key == "pageReferencePageNumber") { setPageReferencePageNumber(value); } else if (key == "pageReferenceSubHeaderNumber") { setPageReferenceSubHeaderNumber(value); } else { std::cout << "Unrecognized StudyMetaDataLink key: " << key.toAscii().constData() << std::endl; } } } } workbench-1.1.1/src/Files/StudyMetaDataLink.h000066400000000000000000000154621255417355300210470ustar00rootroot00000000000000 #ifndef __STUDY_META_DATA_LINK_H__ #define __STUDY_META_DATA_LINK_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "XmlException.h" namespace caret { class XmlWriter; class StudyMetaDataLink : public CaretObject { public: // constructor StudyMetaDataLink(); // destructor ~StudyMetaDataLink(); // copy constructor StudyMetaDataLink(const StudyMetaDataLink& smdl); // assignment opertator StudyMetaDataLink& operator=(const StudyMetaDataLink& smdl); // equality operator bool operator==(const StudyMetaDataLink& smdl) const; // clear the link void clear(); /// get the PubMed ID (negative if project ID, zero if invalid) AString getPubMedID() const { return m_pubMedID; } /// set the PubMed ID (negative if project ID) void setPubMedID(const AString& pmid) { m_pubMedID = pmid; } /// get the table number (blank if invalid) AString getTableNumber() const { return m_tableNumber; } /// set the table number (blank if invalid) void setTableNumber(const AString& tn); /// get the table sub header number (blank if invalid) AString getTableSubHeaderNumber() const { return m_tableSubHeaderNumber; } /// set the table sub header number (blank if invalid) void setTableSubHeaderNumber(const AString& tshn); /// get the figure number (blank if invalid) AString getFigureNumber() const { return m_figureNumber; } /// set the figure number (blank if invalid) void setFigureNumber(const AString& fn); /// get the panel letter/number (blank if invalid) AString getFigurePanelNumberOrLetter() const { return m_panelNumberOrLetter; } /// set the panel letter/number (blank if invalid) void setFigurePanelNumberOrLetter(const AString& pnl); /// get the page reference page number (blank if invalid) AString getPageReferencePageNumber() const { return m_pageReferencePageNumber; } /// set the page reference page number (blank if invalid) void setPageReferencePageNumber(const AString& prpn); /// get the page reference sub header number (blank if invalid) AString getPageReferenceSubHeaderNumber() const { return m_pageReferenceSubHeaderNumber; } /// set the page reference sub header number (blank if invalid) void setPageReferenceSubHeaderNumber(const AString& tshn); /// get the entire link in an "coded" text form AString getLinkAsCodedText() const; /// set the link from "coded" text form void setLinkFromCodedText(const AString& txt); // called to write XML void writeXML(XmlWriter& xmlWriter) const; /// set element from text (used by SAX XML parser) void setElementFromText(const AString& elementName, const AString& textValue); // //----- tags for reading and writing // /// tag for reading and writing study metadata static const AString XML_TAG_STUDY_META_DATA_LINK; /// tag for reading and writing study metadata static const AString XML_TAG_PUBMED_ID; /// tag for reading and writing study metadata static const AString XML_TAG_TABLE_NUMBER; /// tag for reading and writing study metadata static const AString XML_TAG_TABLE_SUB_HEADER_NUMBER; /// tag for reading and writing study metadata static const AString XML_TAG_FIGURE_NUMBER; /// tag for reading and writing study metadata static const AString XML_TAG_PANEL_NUMBER_OR_LETTER; /// tag for reading and writing study metadata //static const AString tagPageNumber; /// tag for reading and writing study metadata static const AString XML_TAG_PAGE_REFERENCE_PAGE_NUMBER; /// tag for reading and writing study metadata static const AString XML_TAG_PAGE_REFERENCE_SUB_HEADER_NUMBER; protected: /// copy helper void copyHelper(const StudyMetaDataLink& smdl); /// the PubMed ID (negative if project ID, 0 if invalid) AString m_pubMedID; /// the table number (blank if invalid) AString m_tableNumber; /// the table sub header number (blank if invalid) AString m_tableSubHeaderNumber; /// the figure number (blank if invalid) AString m_figureNumber; /// the panel letter/number (blank if invalid) AString m_panelNumberOrLetter; /// page reference page number (blank if invalid) AString m_pageReferencePageNumber; /// page reference sub header number (blank if invalid) AString m_pageReferenceSubHeaderNumber; // NOTE: IF MEMBERS ADDED UPDATE THE COPY HELPER friend class StudyMetaDataLinkSet; }; #ifdef __STUDY_META_DATA_LINK_MAIN__ const AString StudyMetaDataLink::XML_TAG_STUDY_META_DATA_LINK = "StudyMetaDataLink"; const AString StudyMetaDataLink::XML_TAG_PUBMED_ID = "pubMedID"; const AString StudyMetaDataLink::XML_TAG_TABLE_NUMBER = "tableNumber"; const AString StudyMetaDataLink::XML_TAG_TABLE_SUB_HEADER_NUMBER = "tableSubHeaderNumber"; const AString StudyMetaDataLink::XML_TAG_FIGURE_NUMBER = "figureNumber"; const AString StudyMetaDataLink::XML_TAG_PANEL_NUMBER_OR_LETTER = "panelNumberOrLetter"; const AString StudyMetaDataLink::XML_TAG_PAGE_REFERENCE_PAGE_NUMBER = "pageReferencePageNumber"; const AString StudyMetaDataLink::XML_TAG_PAGE_REFERENCE_SUB_HEADER_NUMBER = "pageReferenceSubHeaderNumber"; #endif // __STUDY_META_DATA_LINK_MAIN__ } // namespace caret #endif // __STUDY_META_DATA_LINK_H__ workbench-1.1.1/src/Files/StudyMetaDataLinkSet.cxx000066400000000000000000000073561255417355300221010ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #define __STUDY_META_DATA_LINK_SET_MAIN__ #include "StudyMetaDataLinkSet.h" #undef __STUDY_META_DATA_LINK_SET_MAIN__ #include "XmlWriter.h" using namespace caret; /** * constructor. */ StudyMetaDataLinkSet::StudyMetaDataLinkSet() { clear(); } /** * destructor. */ StudyMetaDataLinkSet::~StudyMetaDataLinkSet() { clear(); } /** * add a StudyMetaDataLink. */ void StudyMetaDataLinkSet::addStudyMetaDataLink(const StudyMetaDataLink& smdl) { m_links.push_back(smdl); } /** * remove all links. */ void StudyMetaDataLinkSet::clear() { m_links.clear(); } /** * get a StudyMetaDataLink. */ StudyMetaDataLink StudyMetaDataLinkSet::getStudyMetaDataLink(const int indx) const { return m_links[indx]; } /** * get a pointer to a StudyMetaDataLink. */ StudyMetaDataLink* StudyMetaDataLinkSet::getStudyMetaDataLinkPointer(const int indx) { if ((indx >= 0) && (indx < getNumberOfStudyMetaDataLinks())) { return &m_links[indx]; } return NULL; } /** * set a study meta data link. */ void StudyMetaDataLinkSet::setStudyMetaDataLink(const int indx, const StudyMetaDataLink& smdl) { m_links[indx] = smdl; } /** * get all linked PubMed IDs. */ void StudyMetaDataLinkSet::getAllLinkedPubMedIDs(std::vector& pmidsOut) const { std::set pmidSet; const int num = getNumberOfStudyMetaDataLinks(); for (int i = 0; i < num; i++) { const AString pmid = getStudyMetaDataLink(i).getPubMedID(); pmidSet.insert(pmid); } pmidsOut.clear(); pmidsOut.insert(pmidsOut.end(), pmidSet.begin(), pmidSet.end()); } /** * remove a study meta data link. */ void StudyMetaDataLinkSet::removeStudyMetaDataLink(const int indx) { m_links.erase(m_links.begin() + indx); } /** * get the entire link set in an "coded" text form. */ AString StudyMetaDataLinkSet::getLinkSetAsCodedText() const { QStringList sl; const int num = getNumberOfStudyMetaDataLinks(); for (int i = 0; i < num; i++) { sl << getStudyMetaDataLink(i).getLinkAsCodedText(); } const AString s = sl.join(encodedTextLinkSeparator); return s; } /** * set the link set from "coded" text form. */ void StudyMetaDataLinkSet::setLinkSetFromCodedText(const AString& txt) { clear(); const QStringList sl = txt.split(encodedTextLinkSeparator, AString::SkipEmptyParts); for (int i = 0; i < sl.count(); i++) { StudyMetaDataLink smdl; smdl.setLinkFromCodedText(sl.at(i)); m_links.push_back(smdl); } } /** * called to write XML. */ void StudyMetaDataLinkSet::writeXML(XmlWriter& xmlWriter) const { xmlWriter.writeStartElement(XML_TAG_STUDY_META_DATA_LINK_SET); const int num = getNumberOfStudyMetaDataLinks(); for (int i = 0; i < num; i++) { StudyMetaDataLink smdl = getStudyMetaDataLink(i); smdl.writeXML(xmlWriter); } xmlWriter.writeEndElement(); } workbench-1.1.1/src/Files/StudyMetaDataLinkSet.h000066400000000000000000000064331255417355300215210ustar00rootroot00000000000000 #ifndef __STUDY_META_DATA_LINK_SET_H__ #define __STUDY_META_DATA_LINK_SET_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretObject.h" #include "StudyMetaDataLink.h" #include "XmlException.h" namespace caret { class XmlWriter; /// class for accessing and storing a group of StudyMetaDataLink class StudyMetaDataLinkSet : public CaretObject { public: // constructor StudyMetaDataLinkSet(); // destructor ~StudyMetaDataLinkSet(); // add a StudyMetaDataLink void addStudyMetaDataLink(const StudyMetaDataLink& smdl); // remove all links void clear(); /// get the number of study meta data links int getNumberOfStudyMetaDataLinks() const { return m_links.size(); } // get a StudyMetaDataLink StudyMetaDataLink getStudyMetaDataLink(const int indx) const; // get a pointer to a StudyMetaDataLink StudyMetaDataLink* getStudyMetaDataLinkPointer(const int indx); // get all linked PubMed IDs void getAllLinkedPubMedIDs(std::vector& pmidsOut) const; // remove a study meta data link void removeStudyMetaDataLink(const int indx); // set a study meta data link void setStudyMetaDataLink(const int indx, const StudyMetaDataLink& smdl); /// get the entire link set in an "coded" text form AString getLinkSetAsCodedText() const; /// set the link set from "coded" text form void setLinkSetFromCodedText(const AString& txt); // called to write XML void writeXML(XmlWriter& xmlWriter) const; // //----- tags for reading and writing // /// tag for reading and writing study metadata static const AString XML_TAG_STUDY_META_DATA_LINK_SET; /// get the link separator for when stored as a string static const AString encodedTextLinkSeparator; protected: /// the StudyMetaDataLink std::vector m_links; friend class CellBase; }; #ifdef __STUDY_META_DATA_LINK_SET_MAIN__ const AString StudyMetaDataLinkSet::XML_TAG_STUDY_META_DATA_LINK_SET = "StudyMetaDataLinkSet"; const AString StudyMetaDataLinkSet::encodedTextLinkSeparator = ":::::"; #endif // __STUDY_META_DATA_LINK_SET_MAIN__ } // namespace #endif // __STUDY_META_DATA_LINK_SET_H__ workbench-1.1.1/src/Files/StudyMetaDataLinkSetSaxReader.cxx000066400000000000000000000123621255417355300236710ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretLogger.h" #include "StudyMetaDataLink.h" #include "StudyMetaDataLinkSet.h" #include "StudyMetaDataLinkSetSaxReader.h" #include "XmlAttributes.h" #include "XmlException.h" #include "XmlUtilities.h" using namespace caret; /** * constructor. */ StudyMetaDataLinkSetSaxReader::StudyMetaDataLinkSetSaxReader(StudyMetaDataLinkSet* studyMetaDataLinkSet) { m_state = STATE_NONE; m_stateStack.push(this->m_state); m_elementText = ""; m_studyMetaDataLinkSet = studyMetaDataLinkSet; m_studyMetaDataLinkBeingRead = NULL; } /** * destructor. */ StudyMetaDataLinkSetSaxReader::~StudyMetaDataLinkSetSaxReader() { } /** * start an element. */ void StudyMetaDataLinkSetSaxReader::startElement(const AString& /* namespaceURI */, const AString& /* localName */, const AString& qName, const XmlAttributes& /*attributes*/) { const STATE previousState = m_state; switch (m_state) { case STATE_NONE: if (qName == StudyMetaDataLinkSet::XML_TAG_STUDY_META_DATA_LINK_SET) { m_state = STATE_STUDY_META_DATA_LINK_SET; } else { AString txt = XmlUtilities::createInvalidRootElementMessage(StudyMetaDataLinkSet::XML_TAG_STUDY_META_DATA_LINK_SET, qName); XmlSaxParserException e(txt); CaretLogThrowing(e); throw e; } break; case STATE_STUDY_META_DATA_LINK_SET: if (qName == StudyMetaDataLink::XML_TAG_STUDY_META_DATA_LINK) { m_state = STATE_STUDY_META_DATA_LINK; m_studyMetaDataLinkBeingRead = new StudyMetaDataLink(); } else { AString txt = XmlUtilities::createInvalidChildElementMessage(StudyMetaDataLink::XML_TAG_STUDY_META_DATA_LINK, qName); XmlSaxParserException e(txt); CaretLogThrowing(e); throw e; } break; case STATE_STUDY_META_DATA_LINK: break; } // // Save previous state // m_stateStack.push(previousState); m_elementText = ""; } /** * end an element. */ void StudyMetaDataLinkSetSaxReader::endElement(const AString& /* namspaceURI */, const AString& /* localName */, const AString& qName) { const AString text = m_elementText.trimmed(); switch (m_state) { case STATE_NONE: break; case STATE_STUDY_META_DATA_LINK_SET: if (m_studyMetaDataLinkBeingRead != NULL) { this->m_studyMetaDataLinkSet->addStudyMetaDataLink(*m_studyMetaDataLinkBeingRead); delete m_studyMetaDataLinkBeingRead; m_studyMetaDataLinkBeingRead = NULL; } break; case STATE_STUDY_META_DATA_LINK: if (qName != StudyMetaDataLink::XML_TAG_STUDY_META_DATA_LINK) { m_studyMetaDataLinkBeingRead->setElementFromText(qName, text); } break; } // // Clear out for new elements // m_elementText = ""; // // Go to previous state // if (m_stateStack.empty()) { throw XmlSaxParserException("State stack is empty while reading XML MetaData."); } m_state = m_stateStack.top(); m_stateStack.pop(); } /** * get characters in an element. */ void StudyMetaDataLinkSetSaxReader::characters(const char* ch) { m_elementText += ch; } /** * a fatal error occurs. */ void StudyMetaDataLinkSetSaxReader::fatalError(const XmlSaxParserException& e) { // // Stop parsing // CaretLogSevere("XML Parser Fatal Error: " + e.whatString()); throw e; } // a warning occurs void StudyMetaDataLinkSetSaxReader::warning(const XmlSaxParserException& e) { CaretLogWarning("XML Parser Warning: " + e.whatString()); } // an error occurs void StudyMetaDataLinkSetSaxReader::error(const XmlSaxParserException& e) { throw e; } void StudyMetaDataLinkSetSaxReader::startDocument() { } void StudyMetaDataLinkSetSaxReader::endDocument() { } workbench-1.1.1/src/Files/StudyMetaDataLinkSetSaxReader.h000066400000000000000000000065551255417355300233250ustar00rootroot00000000000000 #ifndef __STUDY_META_DATA_LINK_SET_SAX_READER_H__ #define __STUDY_META_DATA_LINK_SET_SAX_READER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" #include "XmlSaxParserException.h" #include "XmlSaxParserHandlerInterface.h" namespace caret { class StudyMetaDataLink; class StudyMetaDataLinkSet; class XmlAttributes; /** * class for reading StudyMetaDataLinkSet with a SAX Parser */ class StudyMetaDataLinkSetSaxReader : public CaretObject, public XmlSaxParserHandlerInterface { public: StudyMetaDataLinkSetSaxReader(StudyMetaDataLinkSet* studyMetaDataLinKSet); virtual ~StudyMetaDataLinkSetSaxReader(); void startElement(const AString& namespaceURI, const AString& localName, const AString& qName, const XmlAttributes& attributes); void endElement(const AString& namspaceURI, const AString& localName, const AString& qName); void characters(const char* ch); void fatalError(const XmlSaxParserException& e); void warning(const XmlSaxParserException& e); void error(const XmlSaxParserException& e); void startDocument(); void endDocument(); private: StudyMetaDataLinkSetSaxReader(const StudyMetaDataLinkSetSaxReader&); StudyMetaDataLinkSetSaxReader& operator=(const StudyMetaDataLinkSetSaxReader&); protected: /// file reading states enum STATE { /// no state STATE_NONE, /// processing SurfaceProjectedItem tags STATE_STUDY_META_DATA_LINK_SET, /// processing StudyMetaDataLink tags STATE_STUDY_META_DATA_LINK, }; /// file reading state STATE m_state; /// the state stack used when reading a file std::stack m_stateStack; /// the error message AString m_errorMessage; /// meta data name AString m_metaDataName; /// meta data value AString m_metaDataValue; /// element text AString m_elementText; /// GIFTI meta data being read StudyMetaDataLinkSet* m_studyMetaDataLinkSet; StudyMetaDataLink* m_studyMetaDataLinkBeingRead; }; } // namespace #endif // __STUDY_META_DATA_LINK_SET_SAX_READER_H__ workbench-1.1.1/src/Files/SurfaceFile.cxx000066400000000000000000001551311255417355300202610ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include "BoundingBox.h" #include "DataFileException.h" #include "DataFileTypeEnum.h" #include "SurfaceFile.h" #include "CaretAssert.h" #include "CaretOMP.h" #include "DataFileContentInformation.h" #include "DescriptiveStatistics.h" #include "FastStatistics.h" #include "EventSurfaceColoringInvalidate.h" #include "GiftiFile.h" #include "GiftiMetaDataXmlElements.h" #include "MathFunctions.h" #include "Matrix4x4.h" #include "Vector3D.h" #include "CaretPointLocator.h" #include "GeodesicHelper.h" #include "PlainTextStringBuilder.h" #include "SignedDistanceHelper.h" #include "TopologyHelper.h" using namespace caret; /** * Constructor. */ SurfaceFile::SurfaceFile() : GiftiTypeFile(DataFileTypeEnum::SURFACE) { m_skipSanityCheck = false;//NOTE: this is NOT in the initializeMembersSurfaceFile method, because that method gets used at the top of the validate function, //which is used by setNumberOfNodesAndTriangles, which temporarily puts it the triangles into an invalid state, which is why this flag exists this->initializeMembersSurfaceFile(); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_SURFACE_COLORING_INVALIDATE); } /** * Copy constructor. * * @param sf * Surface file that is copied. */ SurfaceFile::SurfaceFile(const SurfaceFile& sf) : GiftiTypeFile(sf), EventListenerInterface() { m_skipSanityCheck = false;//see above this->initializeMembersSurfaceFile(); this->copyHelperSurfaceFile(sf); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_SURFACE_COLORING_INVALIDATE); } /** * Assignment operator. * * @param sf * Surface file that is copied. * @return * This surface file with content replaced * by the SurfaceFile parameter. */ SurfaceFile& SurfaceFile::operator=(const SurfaceFile& sf) { if (this != &sf) { GiftiTypeFile::operator=(sf); this->copyHelperSurfaceFile(sf); } return *this; } /** * Destructor. */ SurfaceFile::~SurfaceFile() { EventManager::get()->removeAllEventsFromListener(this); if (this->boundingBox != NULL) { delete this->boundingBox; this->boundingBox = NULL; } this->invalidateNodeColoringForBrowserTabs(); } /** * Clear the surface file. */ void SurfaceFile::clear() { if (this->boundingBox != NULL) { delete this->boundingBox; this->boundingBox = NULL; } coordinateDataArray = NULL; coordinatePointer = NULL; triangleDataArray = NULL; trianglePointer = NULL; GiftiTypeFile::clear(); invalidateHelpers(); this->invalidateNodeColoringForBrowserTabs(); } /** * Runs topology helper creation in a thread */ class CreateTopologyHelperThread : public QThread { public: CreateTopologyHelperThread(SurfaceFile* surfaceFile) { this->surfaceFile = surfaceFile; } ~CreateTopologyHelperThread() { std::cout << "Delete topology helper for " << this->surfaceFile->getFileNameNoPath() << std::endl; } void run() { /* * NEED to prevent SurfaceFile destructor from completing until this is done * Perhaps before starting this thread, set a variable in SurfaceFile and * then have this method clear the variable after topology helper is created. */ this->surfaceFile->getTopologyHelper(); this->deleteLater(); } SurfaceFile* surfaceFile; }; /** * Validate the contents of the file after it * has been read such as correct number of * data arrays and proper data types/dimensions. */ void SurfaceFile::validateDataArraysAfterReading() { this->initializeMembersSurfaceFile(); int numDataArrays = this->giftiFile->getNumberOfDataArrays(); if (numDataArrays != 2) { throw DataFileException(getFileName(), "Number of data arrays MUST be two in a SurfaceFile."); } /* * Find the coordinate and topology data arrays. */ for (int i = 0; i < numDataArrays; i++) { GiftiDataArray* gda = this->giftiFile->getDataArray(i); if (gda->getIntent() == NiftiIntentEnum::NIFTI_INTENT_POINTSET) { if (gda->getDataType() == NiftiDataTypeEnum::NIFTI_TYPE_FLOAT32) { if (gda->getNumberOfDimensions() == 2) { int64_t dim0 = gda->getDimension(0); int64_t dim1 = gda->getDimension(1); if ((dim0 > 0) && (dim1 == 3)) { this->coordinateDataArray = gda; this->coordinatePointer = gda->getDataPointerFloat(); } } } } else if (gda->getIntent() == NiftiIntentEnum::NIFTI_INTENT_TRIANGLE) { if (gda->getDataType() == NiftiDataTypeEnum::NIFTI_TYPE_INT32) { if (gda->getNumberOfDimensions() == 2) { int64_t dim0 = gda->getDimension(0); int64_t dim1 = gda->getDimension(1); if ((dim0 > 0) && (dim1 == 3)) { this->triangleDataArray = gda; this->trianglePointer = gda->getDataPointerInt(); } } } } } AString errorMessage; if (this->coordinateDataArray == NULL) { errorMessage += "Unable to find coordinate data array which " " contains data type FLOAT32, Intent POINTSET, and two " " dimensions with the second dimension set to three. "; } if (this->triangleDataArray == NULL) { errorMessage += "Unable to find topology data array which " " contains data type INT32, Intent TRIANGLE, and two " " dimensions with the second dimension set to three."; } const int32_t numNodes = this->getNumberOfNodes(); if (!m_skipSanityCheck) { const int numTris = getNumberOfTriangles();//sanity check the triangle data array for (int i = 0; i < numTris; ++i) { const int32_t* thisTri = getTriangle(i); for (int j = 0; j < 3; ++j) { if (thisTri[j] < 0 || thisTri[j] >= numNodes) { errorMessage += "Invalid vertex in triangle array: triangle " + AString::number(i) + ", vertex " + AString::number(thisTri[j]); break; } for (int k = j + 1; k < 3; ++k) { if (thisTri[j] == thisTri[k]) { errorMessage += "Vertex used twice in one triangle: triangle " + AString::number(i) + ", vertex " + AString::number(thisTri[j]); break; } } } } if (errorMessage.isEmpty() == false) { throw DataFileException(getFileName(), errorMessage); } } this->computeNormals(); const uint64_t numColorComponents = numNodes * 4; if (numColorComponents != this->nodeColoring.size()) { this->nodeColoring.resize(numColorComponents); for (int32_t i = 0; i < numNodes; i++) { const int64_t i4 = i * 4; this->nodeColoring[i4] = 0.75f; this->nodeColoring[i4+1] = 0.75f; this->nodeColoring[i4+2] = 0.75f; this->nodeColoring[i4+3] = 1.0f; } } /* * Apply the first transformation matrix that transforms to * Talairach space. */ // Disable as FreeSurfer is inserting a matrix that causes issues in // HCP Pipeline // const AString talairachName = NiftiTransformEnum::toName(NiftiTransformEnum::NIFTI_XFORM_TALAIRACH); // for (int32_t im = 0; im < this->coordinateDataArray->getNumberOfMatrices(); im++) { // const Matrix4x4* m = this->coordinateDataArray->getMatrix(im); // if (m->getTransformedSpaceName() == talairachName) { // applyMatrix(*m); // break; // } // } /* * Create the topology helper in a thread so files dont' take * too long to read. */ // (new CreateTopologyHelperThread(this))->start(); } /** * Get the number of nodes. * * @return * The number of nodes. */ int32_t SurfaceFile::getNumberOfNodes() const { if (this->coordinatePointer == NULL) { return 0; } CaretAssert(this->coordinateDataArray); return this->coordinateDataArray->getDimension(0); } /** * Get the number of columns. * * @return * The number of columns. */ int32_t SurfaceFile::getNumberOfColumns() const { if (this->getNumberOfNodes() > 0) { return 1; } return 0; } void SurfaceFile::setNumberOfNodesAndTriangles(const int32_t& nodes, const int32_t& triangles) { if (this->boundingBox != NULL) { delete this->boundingBox; this->boundingBox = NULL; } coordinateDataArray = NULL; coordinatePointer = NULL; triangleDataArray = NULL; trianglePointer = NULL; giftiFile->clearAndKeepMetadata(); invalidateHelpers(); this->invalidateNodeColoringForBrowserTabs(); std::vector dims(2); dims[1] = 3; dims[0] = nodes; giftiFile->addDataArray(new GiftiDataArray(NiftiIntentEnum::NIFTI_INTENT_POINTSET, NiftiDataTypeEnum::NIFTI_TYPE_FLOAT32, dims, GiftiEncodingEnum::GZIP_BASE64_BINARY)); dims[0] = triangles; giftiFile->addDataArray(new GiftiDataArray(NiftiIntentEnum::NIFTI_INTENT_TRIANGLE, NiftiDataTypeEnum::NIFTI_TYPE_INT32, dims, GiftiEncodingEnum::GZIP_BASE64_BINARY)); m_skipSanityCheck = true; validateDataArraysAfterReading(); m_skipSanityCheck = false; setModified(); } /** * Get a coordinate. * * @param * nodeIndex of coordinate. * * @return * Pointer to memory containing the XYZ coordinate. */ const float* SurfaceFile::getCoordinate(const int32_t nodeIndex) const { CaretAssert(this->coordinatePointer); const int32_t offset = nodeIndex * 3; CaretAssert((offset >= 0) && (offset < (this->getNumberOfNodes() * 3))); return &(this->coordinatePointer[offset]); } /** * Get a coordinate. * * @param * nodeIndex of coordinate. * @param xyzOut * Will contain coordinate upon exit. */ void SurfaceFile::getCoordinate(const int32_t nodeIndex, float xyzOut[3]) const { CaretAssert(this->coordinatePointer); const int32_t offset = nodeIndex * 3; CaretAssert((offset >= 0) && (offset < (this->getNumberOfNodes() * 3))); xyzOut[0] = this->coordinatePointer[offset]; xyzOut[1] = this->coordinatePointer[offset+1]; xyzOut[2] = this->coordinatePointer[offset+2]; } const float* SurfaceFile::getCoordinateData() const { CaretAssert(this->coordinatePointer); return this->coordinatePointer; } void SurfaceFile::setCoordinate(const int32_t nodeIndex, const float xyzIn[3]) { setCoordinate(nodeIndex, xyzIn[0], xyzIn[1], xyzIn[2]); } void SurfaceFile::setCoordinate(const int32_t nodeIndex, const float xIn, const float yIn, const float zIn) { CaretAssert(this->coordinatePointer); const int32_t offset = nodeIndex * 3; CaretAssert((offset >= 0) && (offset < (this->getNumberOfNodes() * 3))); this->coordinatePointer[offset] = xIn; this->coordinatePointer[offset+1] = yIn; this->coordinatePointer[offset+2] = zIn; invalidateNormals(); invalidateHelpers(); setModified(); } void SurfaceFile::setCoordinates(const float *coordinates) { CaretAssert(this->coordinatePointer); memcpy(this->coordinatePointer, coordinates, 3 * sizeof(float) * getNumberOfNodes()); invalidateHelpers(); invalidateNormals(); //setModified(); } /** * Get the number of triangles. * * @return * Number of triangles. */ int SurfaceFile::getNumberOfTriangles() const { if (this->trianglePointer == NULL) { return 0; } CaretAssert(this->triangleDataArray); return this->triangleDataArray->getDimension(0); } /** * Get a triangle. * * @param indx * Index of triangle. * * @return * Pointer to memory containing the three nodes * in the triangle. */ const int32_t* SurfaceFile::getTriangle(const int32_t indx) const { CaretAssert(this->trianglePointer != NULL); CaretAssert((indx >= 0) && (indx < this->getNumberOfTriangles())); const int32_t offset = indx * 3; return &(this->trianglePointer[offset]); } void SurfaceFile::setTriangle(const int32_t& index, const int32_t* nodes) { setTriangle(index, nodes[0], nodes[1], nodes[2]); } void SurfaceFile::setTriangle(const int32_t& index, const int32_t& node1, const int32_t& node2, const int32_t& node3) { CaretAssert(trianglePointer != NULL); CaretAssert((index >= 0) && (index < getNumberOfTriangles())); const int32_t offset = index * 3; trianglePointer[offset] = node1; trianglePointer[offset + 1] = node2; trianglePointer[offset + 2] = node3; invalidateHelpers(); invalidateNormals(); setModified(); } /** * Find the triangle that has an edge formed by "n1" and "n2" * but its not "oppositeTriangle". * * @param n1 * First node in the edge. * @param n2 * Second node in the edge. * @param oppositeTriangle * Triangle that on opposite side of the edge. * @return * Index of triangle or -1 if not found. If not * found surface must be open/cut. */ int32_t SurfaceFile::getTriangleThatSharesEdge(const int32_t n1, const int32_t n2, const int32_t oppositeTriangle) const { CaretPointer topoHelp = getTopologyHelper(); /* * Get the triangles used by one of the nodes */ int32_t numTriangles = 0; const int32_t* triangles = topoHelp->getNodeTiles(n1, numTriangles); for (int32_t i = 0; i < numTriangles; i++) { const int32_t t = triangles[i]; if (t != oppositeTriangle) { const int* nodes = this->getTriangle(t); if ((n1 == nodes[0]) || (n1 == nodes[1]) || (n1 == nodes[2])) { if ((n2 == nodes[0]) || (n2 == nodes[1]) || (n2 == nodes[2])) { return t; } } } } return -1; } /** * Initialize members of this class. */ void SurfaceFile::initializeMembersSurfaceFile() { this->coordinateDataArray = NULL; this->coordinatePointer = NULL; this->triangleDataArray = NULL; this->trianglePointer = NULL; this->boundingBox = NULL; m_distHelperIndex = 0; m_geoHelperIndex = 0; m_topoHelperIndex = 0; m_normalsComputed = false; } /** * Helps copying files. * * @param sf * File that is copied. */ void SurfaceFile::copyHelperSurfaceFile(const SurfaceFile& /*sf*/) { this->validateDataArraysAfterReading(); } /** * Get a normal vector for a coordinate. * * @param * Index of coordinate. * * @return * Pointer to memory containing the normal vector. */ const float* SurfaceFile::getNormalVector(const int32_t nodeIndex) const { const int32_t offset = nodeIndex * 3; CaretAssert((offset >= 0) && (offset < static_cast(this->normalVectors.size()))); return &(this->normalVectors[offset]); } const float* SurfaceFile::getNormalData() const { return normalVectors.data(); } void SurfaceFile::invalidateNormals() { m_normalsComputed = false; } /** * Compute surface normals. */ void SurfaceFile::computeNormals() { if (m_normalsComputed)//don't recompute when not needed { return; } m_normalsComputed = true; int32_t numCoords = this->getNumberOfNodes(); if (numCoords > 0) { this->normalVectors.resize(numCoords * 3); } else { this->normalVectors.clear(); } const int32_t numTriangles = this->getNumberOfTriangles(); if ((numCoords > 0) && (numTriangles > 0)) { float* normalPointer = &this->normalVectors[0]; std::vector numContribute(numCoords, 0); float triangleNormal[3]; for (int32_t i = 0; i < numTriangles; i++) { const int32_t it3 = i * 3; const int n1 = this->trianglePointer[it3]; const int n2 = this->trianglePointer[it3 + 1]; const int n3 = this->trianglePointer[it3 + 2]; const int32_t c1 = n1 * 3; const int32_t c2 = n2 * 3; const int32_t c3 = n3 * 3; if ((n1 >= 0) && (n2 >= 0) && (n3 >= 0)) { MathFunctions::normalVector(&this->coordinatePointer[c1], &this->coordinatePointer[c2], &this->coordinatePointer[c3], triangleNormal); normalPointer[c1 + 0] += triangleNormal[0];//+= is not guaranteed to be atomic, do not parallelize normalPointer[c1 + 1] += triangleNormal[1]; normalPointer[c1 + 2] += triangleNormal[2]; numContribute[n1] += 1; normalPointer[c2 + 0] += triangleNormal[0]; normalPointer[c2 + 1] += triangleNormal[1]; normalPointer[c2 + 2] += triangleNormal[2]; numContribute[n2] += 1; normalPointer[c3 + 0] += triangleNormal[0]; normalPointer[c3 + 1] += triangleNormal[1]; normalPointer[c3 + 2] += triangleNormal[2]; numContribute[n3] += 1; } } for (int i = 0; i < numCoords; i++) { const int i3 = i * 3; if (numContribute[i] > 0) { MathFunctions::normalizeVector(normalPointer + i3); } else { normalPointer[i3 + 0] = 0.0f;//zero the normals for unconnected nodes normalPointer[i3 + 1] = 0.0f; normalPointer[i3 + 2] = 0.0f; } } } } std::vector SurfaceFile::computeAverageNormals() { computeNormals(); const float* normalPointer = getNormalData(); int numCoords = getNumberOfNodes(); std::vector ret(numCoords * 3); CaretPointer myTopoHelp = getTopologyHelper();//TODO: make this not circular - separate base that doesn't handle helpers (and is used by helpers) from file that handles helpers and normals? for (int i = 0; i < numCoords; ++i) { int i3 = i * 3; Vector3D accum; const std::vector& neighbors = myTopoHelp->getNodeNeighbors(i); int numNeigh = (int)neighbors.size(); for (int j = 0; j < numNeigh; ++j) { accum += normalPointer + neighbors[j] * 3; } Vector3D outVec = accum.normal(); ret[i3] = outVec[0]; ret[i3 + 1] = outVec[1]; ret[i3 + 2] = outVec[2]; } return ret; } /** * Get the normal vector for a triangle. * @param triangleIndex * Index of the triangle. * @param normalOut * Output containing the normal for the triangle. */ void SurfaceFile::getTriangleNormalVector(const int32_t triangleIndex, float normalOut[3]) const { const int32_t it3 = triangleIndex * 3; const int n1 = this->trianglePointer[it3]; const int n2 = this->trianglePointer[it3 + 1]; const int n3 = this->trianglePointer[it3 + 2]; const int32_t c1 = n1 * 3; const int32_t c2 = n2 * 3; const int32_t c3 = n3 * 3; MathFunctions::normalVector(&this->coordinatePointer[c1], &this->coordinatePointer[c2], &this->coordinatePointer[c3], normalOut); } /** * Get the coloring for a node. * * @param nodeIndex * Index of node for color components. * @return * A pointer to 4 elements that are the * red, green, blue, and alpha components * each of which ranges zero to one. */ const float* SurfaceFile::getNodeColor(int32_t nodeIndex) const { CaretAssertMessage((nodeIndex >= 0) && (nodeIndex < this->getNumberOfNodes()), "Invalid index for vertex coloring."); return &this->nodeColoring[nodeIndex * 4]; } /** * @return The type of this surface. */ SurfaceTypeEnum::Enum SurfaceFile::getSurfaceType() const { if (this->coordinateDataArray == NULL) { return SurfaceTypeEnum::UNKNOWN; } const AString geometricTypeName = this->coordinateDataArray->getMetaData()->get(GiftiMetaDataXmlElements::METADATA_NAME_GEOMETRIC_TYPE); SurfaceTypeEnum::Enum surfaceType = SurfaceTypeEnum::fromGiftiName(geometricTypeName, NULL); return surfaceType; } /** * Sets the type of this surface. * @param surfaceType * New type for surface. */ void SurfaceFile::setSurfaceType(const SurfaceTypeEnum::Enum surfaceType) { if (this->coordinateDataArray == NULL) { return; } const AString geometricTypeName = SurfaceTypeEnum::toGiftiName(surfaceType); this->coordinateDataArray->getMetaData()->set(GiftiMetaDataXmlElements::METADATA_NAME_GEOMETRIC_TYPE, geometricTypeName); } /** * @return The secondary type of this surface. */ SecondarySurfaceTypeEnum::Enum SurfaceFile::getSecondaryType() const { if (this->coordinateDataArray == NULL) { return SecondarySurfaceTypeEnum::INVALID; } const AString secondaryTypeName = this->coordinateDataArray->getMetaData()->get(GiftiMetaDataXmlElements::METADATA_NAME_ANATOMICAL_STRUCTURE_SECONDARY); SecondarySurfaceTypeEnum::Enum surfaceType = SecondarySurfaceTypeEnum::fromGiftiName(secondaryTypeName, NULL); return surfaceType; } /** * Sets the type of this surface. * @param surfaceType * New type for surface. */ void SurfaceFile::setSecondaryType(const SecondarySurfaceTypeEnum::Enum secondaryType) { if (this->coordinateDataArray == NULL) { return; } const AString secondaryTypeName = SecondarySurfaceTypeEnum::toGiftiName(secondaryType); this->coordinateDataArray->getMetaData()->set(GiftiMetaDataXmlElements::METADATA_NAME_ANATOMICAL_STRUCTURE_SECONDARY, secondaryTypeName); } void SurfaceFile::getGeodesicHelper(CaretPointer& helpOut) const { {//lock before modifying member (base) CaretMutexLocker myLock(&m_geoHelperMutex); if (m_geoBase == NULL) { m_geoHelpers.clear();//just to be sure m_geoHelperIndex = 0; m_geoBase.grabNew(new GeodesicHelperBase(this));//yes, this takes some time, and is single threaded at the moment }//keep locked while searching int32_t& myIndex = m_geoHelperIndex; int32_t myEnd = m_geoHelpers.size(); for (int32_t i = 0; i < myEnd; ++i) { if (myIndex >= myEnd) myIndex = 0; if (m_geoHelpers[myIndex].getReferenceCount() == 1)//1 reference: in this class, so unused elsewhere { helpOut = m_geoHelpers[myIndex]; ++myIndex; return; } ++myIndex; } }//UNLOCK before building a new one, so they can be built in parallel - this actually just involves initializing the marked array CaretPointer ret(new GeodesicHelper(m_geoBase)); CaretMutexLocker myLock(&m_geoHelperMutex);//relock before modifying the array m_geoHelpers.push_back(ret); helpOut = ret; } CaretPointer SurfaceFile::getGeodesicHelper() const {//this convenience function is here because in order to guarantee thread safety, the real function explicitly copies to a reference argument before letting the mutex unlock CaretPointer ret;//the copy of a return should take place before destructors (including the locker for the helper mutex), but just to be safe getGeodesicHelper(ret);//this call is therefore thread safe and guaranteed to modify reference count before it unlocks the mutex for helpers return ret;//so we are already safe by here, at the expense of a second copy constructor/operator= of a CaretPointer } void SurfaceFile::getTopologyHelper(CaretPointer& helpOut, bool infoSorted) const { { CaretMutexLocker myLock(&m_topoHelperMutex);//lock before searching with the shared index int32_t& myIndex = m_topoHelperIndex; int32_t myEnd = m_topoHelpers.size(); for (int32_t i = 0; i < myEnd; ++i) { if (myIndex >= myEnd) myIndex = 0; if (m_topoHelpers[myIndex].getReferenceCount() == 1 && (!infoSorted || m_topoHelpers[myIndex]->isNodeInfoSorted()))//1 reference: in this class, so unused elsewhere { helpOut = m_topoHelpers[myIndex];//NOTE: can give sorted info to something that doesn't ask for sorted, if it already exists ++myIndex; return; } ++myIndex; } if (m_topoBase == NULL || (infoSorted && !m_topoBase->isNodeInfoSorted())) { m_topoBase.grabNew(new TopologyHelperBase(this, infoSorted)); } } CaretPointer ret(new TopologyHelper(m_topoBase)); CaretMutexLocker myLock(&m_topoHelperMutex);//lock before modifying the array m_topoHelpers.push_back(ret); helpOut = ret; } CaretPointer SurfaceFile::getTopologyHelper(bool infoSorted) const {//see convenience function for geo helper for explanation CaretPointer ret; getTopologyHelper(ret, infoSorted); return ret; } void SurfaceFile::getSignedDistanceHelper(CaretPointer& helpOut) const { { CaretMutexLocker myLock(&m_distHelperMutex);//lock before searching with the shared index int32_t& myIndex = m_distHelperIndex; int32_t myEnd = m_distHelpers.size(); for (int32_t i = 0; i < myEnd; ++i) { if (myIndex >= myEnd) myIndex = 0; if (m_distHelpers[myIndex].getReferenceCount() == 1)//1 reference: in this class, so unused elsewhere { helpOut = m_distHelpers[myIndex]; ++myIndex; return; } ++myIndex; } if (m_distBase == NULL) { m_distBase.grabNew(new SignedDistanceHelperBase(this)); } } CaretPointer ret(new SignedDistanceHelper(m_distBase)); CaretMutexLocker myLock(&m_distHelperMutex);//lock before modifying the array m_distHelpers.push_back(ret); helpOut = ret; } CaretPointer SurfaceFile::getSignedDistanceHelper() const {//see convenience function for geo helper for explanation CaretPointer ret; getSignedDistanceHelper(ret); return ret; } void SurfaceFile::invalidateHelpers() { if (m_geoBase != NULL) { CaretMutexLocker myLock(&m_geoHelperMutex);//make this function threadsafe m_geoHelperIndex = 0; m_geoHelpers.clear();//CaretPointers make this nice, if they are still in use elsewhere, they don't vanish, even though this class is supposed to "control" them to some extent m_geoBase.grabNew(NULL); } if (m_topoBase != NULL) { CaretMutexLocker myLock2(&m_topoHelperMutex); m_topoHelperIndex = 0; m_topoHelpers.clear(); m_topoBase.grabNew(NULL); } if (m_distBase != NULL) { CaretMutexLocker myLock4(&m_distHelperMutex); m_distHelperIndex = 0; m_distHelpers.clear(); m_distBase.grabNew(NULL); } if (m_locator != NULL) { CaretMutexLocker myLock3(&m_locatorMutex); m_locator.grabNew(NULL); } } /** * @return A bounding box for this surface. */ const BoundingBox* SurfaceFile::getBoundingBox() const { if (this->boundingBox == NULL) { this->boundingBox = new BoundingBox(); /* * For bounding box, must make sure each node is connected. */ CaretPointer th = this->getTopologyHelper(); const int32_t numberOfNodes = this->getNumberOfNodes(); for (int32_t i = 0; i < numberOfNodes; i++) { if (th->getNodeHasNeighbors(i)) { this->boundingBox->update(&this->coordinatePointer[i*3]); } } } return this->boundingBox; } /** * Match this surface to the given surface. That is, after this * method is called, this surface and the given surface will * fit within the same bounding box. * @param surfaceFile * Match to this surface file. */ void SurfaceFile::matchSurfaceBoundingBox(const SurfaceFile* surfaceFile) { CaretAssert(surfaceFile); const BoundingBox* targetBoundingBox = surfaceFile->getBoundingBox(); const BoundingBox* myBoundingBox = getBoundingBox(); Matrix4x4 matrix; /* * Translate min x/y/z to origin */ matrix.translate(-myBoundingBox->getMinX(), -myBoundingBox->getMinY(), -myBoundingBox->getMinZ()); /* * Scale to match size of match surface */ float scaleX = (targetBoundingBox->getDifferenceX() / myBoundingBox->getDifferenceX()); float scaleY = (targetBoundingBox->getDifferenceY() / myBoundingBox->getDifferenceY()); float scaleZ = (targetBoundingBox->getDifferenceZ() / myBoundingBox->getDifferenceZ()); if (getSurfaceType() == SurfaceTypeEnum::FLAT) { /* * For a flat surface, need to retain overall shape. * Thus, just one scale factor that matches flat X-range * to 3D Y-range. */ const float scale = (targetBoundingBox->getDifferenceY() / myBoundingBox->getDifferenceX()); scaleX = scale; scaleY = scale; scaleZ = scale; } matrix.scale(scaleX, scaleY, scaleZ); /* * Translate to min x/y/z of match surface so that * the two surfaces are now within the same "shoebox". */ matrix.translate(targetBoundingBox->getMinX(), targetBoundingBox->getMinY(), targetBoundingBox->getMinZ()); applyMatrix(matrix); } /** * Apply the given matrix to the coordinates of this surface. * * @param matrix * Transformation matrix that is used. */ void SurfaceFile::applyMatrix(const Matrix4x4& matrix) { CaretPointer th = this->getTopologyHelper(); const int32_t numberOfNodes = getNumberOfNodes(); for (int32_t i = 0; i < numberOfNodes; i++) { if (th->getNodeHasNeighbors(i)) { matrix.multiplyPoint3(&coordinatePointer[i*3]); } } computeNormals(); setModified(); } /** * Translate to the surface center of mass. */ void SurfaceFile::translateToCenterOfMass() { const int32_t numberOfNodes = getNumberOfNodes(); CaretPointer th = this->getTopologyHelper(); double cx = 0.0; double cy = 0.0; double cz = 0.0; double numberOfNodesWithNeighbors = 0.0; for (int32_t i = 0; i < numberOfNodes; i++) { if (th->getNodeHasNeighbors(i)) { const float* xyz = getCoordinate(i); cx += xyz[0]; cy += xyz[1]; cz += xyz[2]; numberOfNodesWithNeighbors += 1.0; } } if (numberOfNodesWithNeighbors > 0.0) { cx /= numberOfNodesWithNeighbors; cy /= numberOfNodesWithNeighbors; cz /= numberOfNodesWithNeighbors; Matrix4x4 matrix; matrix.setTranslation(-cx, -cy, -cz); applyMatrix(matrix); } } /** * Flip the normal vectors. */ void SurfaceFile::flipNormals() { if (trianglePointer == NULL) return; const int numTiles = getNumberOfTriangles(); int32_t tempvert; for (int i = 0; i < numTiles; ++i)//swap first and second verts of all triangles { int offset = i * 3; tempvert = trianglePointer[offset]; trianglePointer[offset] = trianglePointer[offset + 1]; trianglePointer[offset + 1] = tempvert; } invalidateNormals(); invalidateHelpers();//sorted topology helpers would change, so just for completeness setModified(); } /** * @return True if normal vectors are correct, else false. * * Find the node with the greatest Z-coordinate. If the Z-component * of the this node's normal vector is positive, then the normal vectors * point out of the surface and they are correct. Otherwise, the normal * vectors are pointing into the surface. */ bool SurfaceFile::areNormalVectorsCorrect() const { CaretPointer th = this->getTopologyHelper(); float maxZ = -std::numeric_limits::max(); int32_t indxMaxZ = -1; const int32_t numberOfNodes = getNumberOfNodes(); for (int32_t i = 0; i < numberOfNodes; i++) { if (th->getNodeHasNeighbors(i)) { const float* xyz = getCoordinate(i); if (xyz[2] > maxZ) { maxZ = xyz[2]; indxMaxZ = i; } } } if (indxMaxZ >= 0) { const float* normal = getNormalVector(indxMaxZ); if (normal[2] > 0.0) { return true; } } return false; } /** * @return The radius of the spherical surface. * Surface is assumed spherical. */ float SurfaceFile::getSphericalRadius() const { const BoundingBox* bb = getBoundingBox(); const float radius = bb->getMaxX() - bb->getCenterX(); return radius; } /** * @return Area of the surface. */ float SurfaceFile::getSurfaceArea() const { float areaOut = 0.0; CaretAssert(this->trianglePointer); const int32_t numberOfTriangles = getNumberOfTriangles(); for (int32_t i = 0; i < numberOfTriangles; ++i) { const int32_t* triangleNodeIndices = getTriangle(i); const float* node1 = getCoordinate(triangleNodeIndices[0]); const float* node2 = getCoordinate(triangleNodeIndices[1]); const float* node3 = getCoordinate(triangleNodeIndices[2]); areaOut += MathFunctions::triangleArea(node1, node2, node3); } return areaOut; } void SurfaceFile::computeNodeAreas(std::vector& areasOut) const { CaretAssert(this->trianglePointer); int32_t triEnd = getNumberOfTriangles(); int32_t numNodes = getNumberOfNodes(); areasOut.resize(numNodes); for (int32_t i = 0; i < numNodes; ++i) { areasOut[i] = 0.0f; } for (int32_t i = 0; i < triEnd; ++i) { const int32_t* thisTri = getTriangle(i); const float* node1 = getCoordinate(thisTri[0]); const float* node2 = getCoordinate(thisTri[1]); const float* node3 = getCoordinate(thisTri[2]); float area3 = MathFunctions::triangleArea(node1, node2, node3) / 3.0f; areasOut[thisTri[0]] += area3; areasOut[thisTri[1]] += area3; areasOut[thisTri[2]] += area3; } } /** * Is the object modified? * @return true if modified, else false. */ void SurfaceFile::setModified() { if (this->boundingBox != NULL) { delete this->boundingBox; this->boundingBox = NULL; } GiftiTypeFile::setModified(); } int32_t SurfaceFile::closestNode(const float target[3], const float maxDist) const { if (maxDist > 0.0f) { return getPointLocator()->closestPointLimited(target, maxDist); } else { return getPointLocator()->closestPoint(target); } } CaretPointer SurfaceFile::getPointLocator() const { if (m_locator == NULL)//try to avoid locking even once { CaretMutexLocker myLock(&m_locatorMutex); if (m_locator == NULL)//test again AFTER lock to avoid race conditions { m_locator.grabNew(new CaretPointLocator(getCoordinateData(), getNumberOfNodes())); } } return m_locator; } /** * @return Information about the surface. */ AString SurfaceFile::getInformation() const { AString txt; txt += ("Name: " + this->getFileNameNoPath() + "\n"); txt += ("Type: " + SurfaceTypeEnum::toGuiName(this->getSurfaceType()) + "\n"); const int32_t numberOfNodes = this->getNumberOfNodes(); txt += ("Number of Vertices: " + AString::number(numberOfNodes) + "\n"); txt += ("Number of Triangles: " + AString::number(this->getNumberOfTriangles()) + "\n"); txt += ("Bounds: (" + AString::fromNumbers(this->getBoundingBox()->getBounds(), 6, ", ") + ")\n"); if (numberOfNodes > 0) { // std::vector nodeSpacing; // nodeSpacing.reserve(numberOfNodes * 10); // CaretPointer th = this->getTopologyHelper(); // int numberOfNeighbors; // for (int32_t i = 0; i < numberOfNodes; i++) { // const int* neighbors = th->getNodeNeighbors(i, numberOfNeighbors); // for (int32_t j = 0; j < numberOfNeighbors; j++) { // const int n = neighbors[j]; // if (n > i) { // const float dist = MathFunctions::distance3D(this->getCoordinate(i), // this->getCoordinate(n)); // nodeSpacing.push_back(dist); // } // } // } // // DescriptiveStatistics stats; // stats.update(nodeSpacing); DescriptiveStatistics stats; getNodesSpacingStatistics(stats); const float mean = stats.getMean(); const float stdDev = stats.getStandardDeviationSample(); const float minValue = stats.getMinimumValue(); const float maxValue = stats.getMaximumValue(); txt += ("Spacing:\n"); txt += (" Mean: " + AString::number(mean, 'f', 6) + "\n"); txt += (" Std Dev: " + AString::number(stdDev, 'f', 6) + "\n"); txt += (" Minimum: " + AString::number(minValue, 'f', 6) + "\n"); txt += (" Maximum: " + AString::number(maxValue, 'f', 6) + "\n"); } return txt; } /** * Get statistics on node spacing. * @param statsOut * Upon exit, contains node spacing descriptive statistics. */ void SurfaceFile::getNodesSpacingStatistics(DescriptiveStatistics& statsOut) const { const int32_t numberOfNodes = this->getNumberOfNodes(); std::vector nodeSpacing; nodeSpacing.reserve(numberOfNodes * 10); CaretPointer th = this->getTopologyHelper(); int numberOfNeighbors; for (int32_t i = 0; i < numberOfNodes; i++) { const int* neighbors = th->getNodeNeighbors(i, numberOfNeighbors); for (int32_t j = 0; j < numberOfNeighbors; j++) { const int n = neighbors[j]; if (n > i) { const float dist = MathFunctions::distance3D(this->getCoordinate(i), this->getCoordinate(n)); nodeSpacing.push_back(dist); } } } statsOut.update(nodeSpacing); } void SurfaceFile::getNodesSpacingStatistics(FastStatistics& statsOut) const { const int32_t numberOfNodes = this->getNumberOfNodes(); std::vector nodeSpacing; nodeSpacing.reserve(numberOfNodes * 10); CaretPointer th = this->getTopologyHelper(); int numberOfNeighbors; for (int32_t i = 0; i < numberOfNodes; i++) { const int* neighbors = th->getNodeNeighbors(i, numberOfNeighbors); for (int32_t j = 0; j < numberOfNeighbors; j++) { const int n = neighbors[j]; if (n > i) { const float dist = MathFunctions::distance3D(this->getCoordinate(i), this->getCoordinate(n)); nodeSpacing.push_back(dist); } } } statsOut.update(nodeSpacing.data(), nodeSpacing.size()); } /** * Invalidate surface coloring. */ void SurfaceFile::invalidateNodeColoringForBrowserTabs() { /* * Free memory since could have many tabs and many surfaces equals lots of memory */ for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { this->surfaceNodeColoringForBrowserTabs[i].clear(); this->surfaceMontageNodeColoringForBrowserTabs[i].clear(); this->wholeBrainNodeColoringForBrowserTabs[i].clear(); } } /** * Allocate node coloring for a single surface in a browser tab. * @param browserTabIndex * Index of browser tab. * @param zeroizeColorsFlag * If true and memory is allocated for colors, the color components * are set to all zeros, otherwise the memory may be left unitialized. */ void SurfaceFile::allocateSurfaceNodeColoringForBrowserTab(const int32_t browserTabIndex, const bool zeroizeColorsFlag) { CaretAssertArrayIndex(this->surfaceNodeColoringForBrowserTabs, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, browserTabIndex); const uint64_t numberOfComponentsRGBA = this->getNumberOfNodes() * 4; if (this->surfaceNodeColoringForBrowserTabs[browserTabIndex].size() != numberOfComponentsRGBA) { if (zeroizeColorsFlag) { this->surfaceNodeColoringForBrowserTabs[browserTabIndex].resize(numberOfComponentsRGBA, 0.0); } else { this->surfaceNodeColoringForBrowserTabs[browserTabIndex].resize(numberOfComponentsRGBA); } } } /** * Allocate node coloring for a surface montage in a browser tab. * @param browserTabIndex * Index of browser tab. * @param zeroizeColorsFlag * If true and memory is allocated for colors, the color components * are set to all zeros, otherwise the memory may be left unitialized. */ void SurfaceFile::allocateSurfaceMontageNodeColoringForBrowserTab(const int32_t browserTabIndex, const bool zeroizeColorsFlag) { CaretAssertArrayIndex(this->surfaceMontageNodeColoringForBrowserTabs, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, browserTabIndex); const uint64_t numberOfComponentsRGBA = this->getNumberOfNodes() * 4; if (this->surfaceMontageNodeColoringForBrowserTabs[browserTabIndex].size() != numberOfComponentsRGBA) { if (zeroizeColorsFlag) { this->surfaceMontageNodeColoringForBrowserTabs[browserTabIndex].resize(numberOfComponentsRGBA, 0.0); } else { this->surfaceMontageNodeColoringForBrowserTabs[browserTabIndex].resize(numberOfComponentsRGBA); } } } /** * Allocate node coloring for a whole brain surface in a browser tab. * @param browserTabIndex * Index of browser tab. * @param zeroizeColorsFlag * If true and memory is allocated for colors, the color components * are set to all zeros, otherwise the memory may be left unitialized. */ void SurfaceFile::allocateWholeBrainNodeColoringForBrowserTab(const int32_t browserTabIndex, const bool zeroizeColorsFlag) { CaretAssertArrayIndex(this->wholeBrainNodeColoringForBrowserTabs, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, browserTabIndex); const uint64_t numberOfComponentsRGBA = this->getNumberOfNodes() * 4; if (this->wholeBrainNodeColoringForBrowserTabs[browserTabIndex].size() != numberOfComponentsRGBA) { if (zeroizeColorsFlag) { this->wholeBrainNodeColoringForBrowserTabs[browserTabIndex].resize(numberOfComponentsRGBA, 0.0); } else { this->wholeBrainNodeColoringForBrowserTabs[browserTabIndex].resize(numberOfComponentsRGBA); } } } /** * Get the RGBA color components for this single surface in the given tab. * @param browserTabIndex * Index of browser tab. * @return * Coloring for the tab or NULL if coloring is invalid and needs to be * set. */ float* SurfaceFile::getSurfaceNodeColoringRgbaForBrowserTab(const int32_t browserTabIndex) { CaretAssertArrayIndex(this->surfaceNodeColoringForBrowserTabs, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, browserTabIndex); std::vector& rgba = this->surfaceNodeColoringForBrowserTabs[browserTabIndex]; if (rgba.empty()) { return NULL; } return &rgba[0]; } /** * Set the RGBA color components for this a single surface in the given tab. * @param browserTabIndex * Index of browser tab. * @param rgbaNodeColorComponents * RGBA color components for this surface in the given tab. */ void SurfaceFile::setSurfaceNodeColoringRgbaForBrowserTab(const int32_t browserTabIndex, const float* rgbaNodeColorComponents) { CaretAssertArrayIndex(this->surfaceNodeColoringForBrowserTabs, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, browserTabIndex); this->allocateSurfaceNodeColoringForBrowserTab(browserTabIndex, false); const int numberOfComponentsRGBA = this->getNumberOfNodes() * 4; std::vector& rgba = this->surfaceNodeColoringForBrowserTabs[browserTabIndex]; for (int32_t i = 0; i < numberOfComponentsRGBA; i++) { rgba[i] = rgbaNodeColorComponents[i]; } } /** * Get the RGBA color components for this surface montage in the given tab. * @param browserTabIndex * Index of browser tab. * @return * Coloring for the tab or NULL if coloring is invalid and needs to be * set. */ float* SurfaceFile::getSurfaceMontageNodeColoringRgbaForBrowserTab(const int32_t browserTabIndex) { CaretAssertArrayIndex(this->surfaceMontageNodeColoringForBrowserTabs, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, browserTabIndex); std::vector& rgba = this->surfaceMontageNodeColoringForBrowserTabs[browserTabIndex]; if (rgba.empty()) { return NULL; } return &rgba[0]; } /** * Set the RGBA color components for this a surface montage in the given tab. * @param browserTabIndex * Index of browser tab. * @param rgbaNodeColorComponents * RGBA color components for this surface montage in the given tab. */ void SurfaceFile::setSurfaceMontageNodeColoringRgbaForBrowserTab(const int32_t browserTabIndex, const float* rgbaNodeColorComponents) { CaretAssertArrayIndex(this->surfaceMontageNodeColoringForBrowserTabs, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, browserTabIndex); this->allocateSurfaceMontageNodeColoringForBrowserTab(browserTabIndex, false); const int numberOfComponentsRGBA = this->getNumberOfNodes() * 4; std::vector& rgba = this->surfaceMontageNodeColoringForBrowserTabs[browserTabIndex]; for (int32_t i = 0; i < numberOfComponentsRGBA; i++) { rgba[i] = rgbaNodeColorComponents[i]; } } /** * Get the RGBA color components for this whole brain surface in the given tab. * @param browserTabIndex * Index of browser tab. * @return * Coloring for the tab or NULL if coloring is invalid and needs to be set. */ float* SurfaceFile::getWholeBrainNodeColoringRgbaForBrowserTab(const int32_t browserTabIndex) { CaretAssertArrayIndex(this->wholeBrainNodeColoringForBrowserTabs, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, browserTabIndex); std::vector& rgba = this->wholeBrainNodeColoringForBrowserTabs[browserTabIndex]; if (rgba.empty()) { return NULL; } return &rgba[0]; } /** * Set the RGBA color components for this a whole brain surface in the given tab. * @param browserTabIndex * Index of browser tab. * @param rgbaNodeColorComponents * RGBA color components for this surface in the given tab. */ void SurfaceFile::setWholeBrainNodeColoringRgbaForBrowserTab(const int32_t browserTabIndex, const float* rgbaNodeColorComponents) { CaretAssertArrayIndex(this->wholeBrainNodeColoringForBrowserTabs, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, browserTabIndex); this->allocateWholeBrainNodeColoringForBrowserTab(browserTabIndex, false); const int numberOfComponentsRGBA = this->getNumberOfNodes() * 4; std::vector& rgba = this->wholeBrainNodeColoringForBrowserTabs[browserTabIndex]; for (int32_t i = 0; i < numberOfComponentsRGBA; i++) { rgba[i] = rgbaNodeColorComponents[i]; } } /** * Receive an event. * * @param event * The event that the receive can respond to. */ void SurfaceFile::receiveEvent(Event* event) { if (event->getEventType() == EventTypeEnum::EVENT_SURFACE_COLORING_INVALIDATE) { EventSurfaceColoringInvalidate* invalidateEvent = dynamic_cast(event); CaretAssert(invalidateEvent); invalidateEvent->setEventProcessed(); this->invalidateNodeColoringForBrowserTabs(); } } bool SurfaceFile::matchesTopology(const SurfaceFile& rhs) const { if (getNumberOfNodes() != rhs.getNumberOfNodes()) return false; int numTriangles = getNumberOfTriangles(); if (numTriangles != rhs.getNumberOfTriangles()) return false; for (int i = 0; i < numTriangles; ++i) { int i3 = i * 3; if (trianglePointer[i3] != rhs.trianglePointer[i3]) return false;//naively assume exactly same order of triangles and nodes, for strictest topology equivalence if (trianglePointer[i3 + 1] != rhs.trianglePointer[i3 + 1]) return false; if (trianglePointer[i3 + 2] != rhs.trianglePointer[i3 + 2]) return false; } return true; } bool SurfaceFile::hasNodeCorrespondence(const SurfaceFile& rhs) const { int numNodes = getNumberOfNodes(); if (numNodes != rhs.getNumberOfNodes()) return false; if (getNumberOfTriangles() != rhs.getNumberOfTriangles()) return false; CaretPointer myHelp = getTopologyHelper(), rightHelp = rhs.getTopologyHelper(); for (int i = 0; i < numNodes; ++i) { const std::vector& myNeigh = myHelp->getNodeNeighbors(i); const std::vector& rightNeigh = rightHelp->getNodeNeighbors(i); int mySize = (int)myNeigh.size(); if (mySize != (int)rightNeigh.size()) return false; std::set myUsed; for (int j = 0; j < mySize; ++j) { myUsed.insert(myNeigh[j]); } for (int j = 0; j < mySize; ++j) { if (myUsed.find(rightNeigh[j]) == myUsed.end()) return false; } } return true; } /** * Add information about the file to the data file information. * * @param dataFileInformation * Consolidates information about a data file. */ void SurfaceFile::addToDataFileContentInformation(DataFileContentInformation& dataFileInformation) { GiftiTypeFile::addToDataFileContentInformation(dataFileInformation); dataFileInformation.addNameAndValue("Number of Triangles", getNumberOfTriangles()); dataFileInformation.addNameAndValue("Normal Vectors Correct", areNormalVectorsCorrect()); dataFileInformation.addNameAndValue("Surface Type (Primary)", SurfaceTypeEnum::toGuiName(getSurfaceType())); dataFileInformation.addNameAndValue("Surface Type (Secondary)", SecondarySurfaceTypeEnum::toGuiName(getSecondaryType())); const BoundingBox* boundingBox = getBoundingBox(); dataFileInformation.addNameAndValue("X-minimum", boundingBox->getMinX()); dataFileInformation.addNameAndValue("X-maximum", boundingBox->getMaxX()); dataFileInformation.addNameAndValue("Y-minimum", boundingBox->getMinY()); dataFileInformation.addNameAndValue("Y-maximum", boundingBox->getMaxY()); dataFileInformation.addNameAndValue("Z-minimum", boundingBox->getMinZ()); dataFileInformation.addNameAndValue("Z-maximum", boundingBox->getMaxZ()); if (getSurfaceType() == SurfaceTypeEnum::SPHERICAL) { dataFileInformation.addNameAndValue("Spherical Radius", getSphericalRadius()); } dataFileInformation.addNameAndValue("Surface Area", getSurfaceArea()); DescriptiveStatistics stats; getNodesSpacingStatistics(stats); dataFileInformation.addNameAndValue("Spacing Mean", stats.getMean()); dataFileInformation.addNameAndValue("Spacing Std Dev", stats.getStandardDeviationSample()); dataFileInformation.addNameAndValue("Spacing Minimum", stats.getMinimumValue()); dataFileInformation.addNameAndValue("Spacing Maximum", stats.getMaximumValue()); } /** * @return A String describing the content of this object. */ AString SurfaceFile::toString() const { PlainTextStringBuilder tb; getDescriptionOfContent(tb); return tb.getText(); } /** * Get a text description of the instance's content. * * @param descriptionOut * Description of the instance's content. */ void SurfaceFile::getDescriptionOfContent(PlainTextStringBuilder& descriptionOut) const { descriptionOut.addLine("Surface: " + getFileNameNoPath()); descriptionOut.addLine(" Structure: " + StructureEnum::toGuiName(getStructure())); descriptionOut.addLine(" Primary Type: " + SurfaceTypeEnum::toGuiName(getSurfaceType())); descriptionOut.addLine(" Secondary Type: " + SecondarySurfaceTypeEnum::toGuiName(getSecondaryType())); } workbench-1.1.1/src/Files/SurfaceFile.h000066400000000000000000000266341255417355300177130ustar00rootroot00000000000000 #ifndef __SURFACE_FILE_H__ #define __SURFACE_FILE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "BrainConstants.h" #include "CaretMutex.h" #include "CaretPointer.h" #include "EventManager.h" #include "EventListenerInterface.h" #include "GiftiTypeFile.h" #include "SurfaceTypeEnum.h" namespace caret { class BoundingBox; class CaretPointLocator; class DescriptiveStatistics; class FastStatistics; class GeodesicHelper; class GeodesicHelperBase; class GiftiDataArray; class Matrix4x4; class PlainTextStringBuilder; class SignedDistanceHelper; class SignedDistanceHelperBase; class TopologyHelper; class TopologyHelperBase; /** * A surface data file. */ class SurfaceFile : public GiftiTypeFile, EventListenerInterface { public: SurfaceFile(); SurfaceFile(const SurfaceFile& sf); SurfaceFile& operator=(const SurfaceFile& sf); virtual ~SurfaceFile(); virtual void addToDataFileContentInformation(DataFileContentInformation& dataFileInformation); virtual void receiveEvent(Event* event); virtual void clear(); virtual int32_t getNumberOfNodes() const; virtual int32_t getNumberOfColumns() const; void setNumberOfNodesAndTriangles(const int32_t& nodes, const int32_t& triangles); const float* getCoordinate(const int32_t nodeIndex) const; void getCoordinate(const int32_t nodeIndex, float xyzOut[3]) const; void setCoordinate(const int32_t nodeIndex, const float xyzIn[3]); void setCoordinate(const int32_t nodeIndex, const float xIn, const float yIn, const float zIn); void setCoordinates(const float *coordinates); const float* getCoordinateData() const; const float* getNormalVector(const int32_t nodeIndex) const; const float* getNormalData() const; int getNumberOfTriangles() const; const int32_t* getTriangle(const int32_t index) const; void setTriangle(const int32_t& index, const int32_t* nodes); void setTriangle(const int32_t& index, const int32_t& node1, const int32_t& node2, const int32_t& node3); int32_t getTriangleThatSharesEdge(const int32_t n1, const int32_t n2, const int32_t oppositeTriangle) const; void computeNormals(); std::vector computeAverageNormals(); const float* getNodeColor(const int32_t nodeIndex) const; void getTriangleNormalVector(const int32_t triangleIndex, float normalOut[3]) const; SurfaceTypeEnum::Enum getSurfaceType() const; void setSurfaceType(const SurfaceTypeEnum::Enum surfaceType); SecondarySurfaceTypeEnum::Enum getSecondaryType() const; void setSecondaryType(const SecondarySurfaceTypeEnum::Enum secondaryType); float getSphericalRadius() const; float getSurfaceArea() const; CaretPointer getTopologyHelper(bool infoSorted = false) const; void getTopologyHelper(CaretPointer& helpOut, bool infoSorted = false) const; CaretPointer getGeodesicHelper() const; void getGeodesicHelper(CaretPointer& helpOut) const; CaretPointer getSignedDistanceHelper() const; void getSignedDistanceHelper(CaretPointer& helpOut) const; CaretPointer getPointLocator() const; const BoundingBox* getBoundingBox() const; void matchSurfaceBoundingBox(const SurfaceFile* surfaceFile); void applyMatrix(const Matrix4x4& matrix); void getNodesSpacingStatistics(DescriptiveStatistics& statsOut) const; void getNodesSpacingStatistics(FastStatistics& statsOut) const; void computeNodeAreas(std::vector& areasOut) const; ///find the closest node on the surface, within maxDist if maxDist is positive int32_t closestNode(const float target[3], const float maxDist = -1.0f) const; virtual void setModified(); AString getInformation() const; float* getSurfaceNodeColoringRgbaForBrowserTab(const int32_t browserTabIndex); void setSurfaceNodeColoringRgbaForBrowserTab(const int32_t browserTabIndex, const float* rgbaNodeColorComponents); float* getSurfaceMontageNodeColoringRgbaForBrowserTab(const int32_t browserTabIndex); void setSurfaceMontageNodeColoringRgbaForBrowserTab(const int32_t browserTabIndex, const float* rgbaNodeColorComponents); float* getWholeBrainNodeColoringRgbaForBrowserTab(const int32_t browserTabIndex); void setWholeBrainNodeColoringRgbaForBrowserTab(const int32_t browserTabIndex, const float* rgbaNodeColorComponents); void invalidateNormals(); void translateToCenterOfMass(); void flipNormals(); bool areNormalVectorsCorrect() const; ///check that it has EXACTLY the same topology, with no flipped normals or rotated or reordered triangles bool matchesTopology(const SurfaceFile& rhs) const; ///check only that each node is connected to the same set of other nodes, allow any other form of mischief bool hasNodeCorrespondence(const SurfaceFile& rhs) const; virtual AString toString() const; virtual void getDescriptionOfContent(PlainTextStringBuilder& descriptionOut) const; protected: /** * Validate the contents of the file after it * has been read such as correct number of * data arrays and proper data types/dimensions. */ virtual void validateDataArraysAfterReading(); void copyHelperSurfaceFile(const SurfaceFile& sf); void initializeMembersSurfaceFile(); private: void invalidateNodeColoringForBrowserTabs(); void allocateSurfaceNodeColoringForBrowserTab(const int32_t browserTabIndex, const bool zeroizeColorsFlag); void allocateSurfaceMontageNodeColoringForBrowserTab(const int32_t browserTabIndex, const bool zeroizeColorsFlag); void allocateWholeBrainNodeColoringForBrowserTab(const int32_t browserTabIndex, const bool zeroizeColorsFlag); /** Data array containing the coordinates. */ GiftiDataArray* coordinateDataArray; /** * This coloring is used when a ONE surface is displayed. * Node color components Red, Green, Blue, Alpha for each browser tab. * Each element of the vector points to the coloring * for a browser tab with the corresponding index. */ std::vector surfaceNodeColoringForBrowserTabs[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; /** * This coloring is used when a surface montage is displayed. * Node color components Red, Green, Blue, Alpha for each browser tab. * Each element of the vector points to the coloring * for a browser tab with the corresponding index. */ std::vector surfaceMontageNodeColoringForBrowserTabs[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; /** * This coloring is used when a Whole Brain is displayed. * Node color components Red, Green, Blue, Alpha for each browser tab. * Each element of the vector points to the coloring * for a browser tab with the corresponding index. */ std::vector wholeBrainNodeColoringForBrowserTabs[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; /** Points to memory containing the coordinates. */ float* coordinatePointer; /** Data array containing the triangles. */ GiftiDataArray* triangleDataArray; /** Points to memory containing the triangles. */ int32_t* trianglePointer; /** surface normal vectors. */ std::vector normalVectors; bool m_normalsComputed; bool m_skipSanityCheck; /** The node coloring. */ std::vector nodeColoring; ///topology base for surface mutable CaretPointer m_topoBase; ///tracks allocated TopologyHelpers for this class mutable std::vector > m_topoHelpers; ///used to search through topology helpers without starting from 0 every time, wraps around mutable int32_t m_topoHelperIndex; ///the geodesic base for this surface mutable CaretPointer m_geoBase; ///tracks allocated geodesic helpers for this class mutable std::vector > m_geoHelpers; ///used to search through geodesic helpers without starting from 0 every time, wraps around mutable int32_t m_geoHelperIndex; ///the geodesic base for this surface mutable CaretPointer m_distBase; ///tracks allocated geodesic helpers for this class mutable std::vector > m_distHelpers; ///used to search through geodesic helpers without starting from 0 every time, wraps around mutable int32_t m_distHelperIndex; ///used to search for the closest point in the surface mutable CaretPointer m_locator; ///used to track when the surface file gets changed void invalidateHelpers(); mutable BoundingBox* boundingBox; mutable CaretMutex m_topoHelperMutex, m_geoHelperMutex, m_locatorMutex, m_distHelperMutex; }; } // namespace #endif // __SURFACE_FILE_H__ workbench-1.1.1/src/Files/SurfaceProjectedItem.cxx000066400000000000000000000437321255417355300221430ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SURFACE_PROJECTED_ITEM_DEFINE__ #include "SurfaceProjectedItem.h" #undef __SURFACE_PROJECTED_ITEM_DEFINE__ #include "CaretAssert.h" #include "CaretLogger.h" #include "DataFileException.h" #include "SurfaceProjectionBarycentric.h" #include "SurfaceProjectionVanEssen.h" #include "XmlWriter.h" #include using namespace caret; /** * \class caret::SurfaceProjectedItem * \brief Maintains position of an item projected to a surface. * * Multiple projections are supported and may be valid at one time. * (1) Barycentric projects to a surface triangle. (2) VanEssen * projects to an edget of a triangle. (3) Stereotaxic is a * three-dimensional coordinate. A volume coordinate is also * available. */ /** * Constructor. * */ SurfaceProjectedItem::SurfaceProjectedItem() : CaretObjectTracksModification() { this->initializeMembersSurfaceProjectedItem(); } /** * Destructor */ SurfaceProjectedItem::~SurfaceProjectedItem() { delete this->barycentricProjection; delete this->vanEssenProjection; } /** * Copy Constructor * @param Object that is copied. */ SurfaceProjectedItem::SurfaceProjectedItem(const SurfaceProjectedItem& o) : CaretObjectTracksModification(o) { this->initializeMembersSurfaceProjectedItem(); this->copyHelper(o); } /** * Assignment operator. */ SurfaceProjectedItem& SurfaceProjectedItem::operator=(const SurfaceProjectedItem& o) { if (this != &o) { CaretObject::operator=(o); this->copyHelper(o); }; return *this; } bool SurfaceProjectedItem::operator==(const SurfaceProjectedItem& rhs) const { if (structure != rhs.structure) return false; if (stereotaxicXYZValid != rhs.stereotaxicXYZValid) return false; if (volumeXYZValid != rhs.volumeXYZValid) return false; if (stereotaxicXYZValid) { for (int i = 0; i < 3; ++i) { if (stereotaxicXYZ[i] != rhs.stereotaxicXYZ[i]) return false; } } if (volumeXYZValid) { for (int i = 0; i < 3; ++i) { if (volumeXYZ[i] != rhs.volumeXYZ[i]) return false; } } if (*barycentricProjection != *rhs.barycentricProjection) return false; return (*vanEssenProjection == *rhs.vanEssenProjection); } /** * Helps with copy constructor and assignment operator. */ void SurfaceProjectedItem::copyHelper(const SurfaceProjectedItem& spi) { this->setStereotaxicXYZ(spi.getStereotaxicXYZ()); this->stereotaxicXYZValid = spi.stereotaxicXYZValid; this->setVolumeXYZ(spi.getVolumeXYZ()); this->volumeXYZValid = spi.volumeXYZValid; this->structure = spi.structure; *this->barycentricProjection = *spi.barycentricProjection; *this->vanEssenProjection = *spi.vanEssenProjection; } /** * Reset to default values as if no projection of any type. */ void SurfaceProjectedItem::reset() { this->stereotaxicXYZ[0] = 0.0; this->stereotaxicXYZ[1] = 0.0; this->stereotaxicXYZ[2] = 0.0; this->stereotaxicXYZValid = false; this->volumeXYZ[0] = 0.0; this->volumeXYZ[1] = 0.0; this->volumeXYZ[2] = 0.0; this->volumeXYZValid = false; this->structure = StructureEnum::INVALID; this->barycentricProjection->reset(); this->vanEssenProjection->reset(); } void SurfaceProjectedItem::initializeMembersSurfaceProjectedItem() { this->barycentricProjection = new SurfaceProjectionBarycentric(); this->vanEssenProjection = new SurfaceProjectionVanEssen(); this->reset(); } /** * Unproject the item to the stereotaxic XYZ coordinates. * * @param sf - Surface on which unprojection takes place. * @param pasteOntoSurfaceFlag - place item directly on surface. * */ void SurfaceProjectedItem::unprojectToStereotaxicXYZ(const SurfaceFile& sf, const bool isUnprojectedOntoSurface) { float xyz[3]; if (getProjectedPosition(sf, xyz, isUnprojectedOntoSurface)) { this->setStereotaxicXYZ(xyz); } } /** * Unproject the item to the volume XYZ coordinates. * * @param sf - Surface on which unprojection takes place. * @param pasteOntoSurfaceFlag - place item directly on surface. * */ void SurfaceProjectedItem::unprojectToVolumeXYZ(const SurfaceFile& sf, const bool isUnprojectedOntoSurface) { float xyz[3]; if (getProjectedPosition(sf, xyz, isUnprojectedOntoSurface)) { this->setVolumeXYZ(xyz); } } /** * Get the projected position of this item. * The first valid of this positions is used: (1) Barycentric, * (2) VanEssen, (3) Stereotaxic. * * @param surfaceFile * Surface File for positioning. * @param xyzOut * Output containing the projected position. * @param pasteOntoSurfaceFlag * Place directly on the surface. * @return true if the position is valid, else false. * */ bool SurfaceProjectedItem::getProjectedPosition(const SurfaceFile& surfaceFile, float xyzOut[3], const bool isUnprojectedOntoSurface) const { bool valid = false; if (valid == false) { if (this->barycentricProjection->isValid()) { valid = this->barycentricProjection->unprojectToSurface(surfaceFile, xyzOut, 0.0, isUnprojectedOntoSurface); } } if (valid == false) { if (this->vanEssenProjection->isValid()) { valid = this->vanEssenProjection->unprojectToSurface(surfaceFile, xyzOut, 0.0, isUnprojectedOntoSurface); } } if (valid == false) { if (this->stereotaxicXYZValid) { this->getStereotaxicXYZ(xyzOut); valid = true; } } return valid; } /** * Get the projected position of this item. The item is unprojected * to the surface and then it is placed above (below if negative) * the surface by the amount specified by 'distanceAboveSurface'. * * The first valid of this positions is used: (1) Barycentric, * (2) VanEssen, (3) Stereotaxic. * * @param surfaceFile * Surface File for positioning. * @param xyzOut * Output containing the projected position. * @param distanceAboveSurface * Unproje * @return true if the position is valid, else false. * */ bool SurfaceProjectedItem::getProjectedPositionAboveSurface(const SurfaceFile& surfaceFile, float xyzOut[3], const float distanceAboveSurface) const { bool valid = false; if (valid == false) { if (this->barycentricProjection->isValid()) { valid = this->barycentricProjection->unprojectToSurface(surfaceFile, xyzOut, distanceAboveSurface, true); } } if (valid == false) { if (this->vanEssenProjection->isValid()) { valid = this->vanEssenProjection->unprojectToSurface(surfaceFile, xyzOut, distanceAboveSurface, true); } } if (valid == false) { if (this->stereotaxicXYZValid) { this->getStereotaxicXYZ(xyzOut); valid = true; } } return valid; } /** * Get the stereotaxic position. * * @return Stereotaxic position. * */ const float* SurfaceProjectedItem::getStereotaxicXYZ() const { return this->stereotaxicXYZ; } /** * Get the Stereotaxic XYZ position. * @param stereotaxicXYZOut Position placed into here. * */ void SurfaceProjectedItem::getStereotaxicXYZ(float stereotaxicXYZOut[3]) const { stereotaxicXYZOut[0] = this->stereotaxicXYZ[0]; stereotaxicXYZOut[1] = this->stereotaxicXYZ[1]; stereotaxicXYZOut[2] = this->stereotaxicXYZ[2]; } /** * Get the validity of the Stereotaxic XYZ coordinate. * @return Validity of Stereotaxic XYZ coordinate. * */ bool SurfaceProjectedItem::isStereotaxicXYZValid() const { return this->stereotaxicXYZValid; } /** * Set the items stereotaxic coordinates and sets the validity * of the stereotaxic coordinates to true. * * @param stereotaxicXYZ New position. * */ void SurfaceProjectedItem::setStereotaxicXYZ(const float stereotaxicXYZ[3]) { if (this->stereotaxicXYZValid) { } this->stereotaxicXYZ[0] = stereotaxicXYZ[0]; this->stereotaxicXYZ[1] = stereotaxicXYZ[1]; this->stereotaxicXYZ[2] = stereotaxicXYZ[2]; this->stereotaxicXYZValid = true; if (this->volumeXYZValid == false) { this->setVolumeXYZ(stereotaxicXYZ); } this->setModified(); } /** * Get the value of volumeXYZ * * @return the value of volumeXYZ * */ const float* SurfaceProjectedItem::getVolumeXYZ() const { /* * If not set, return stereotaxic coordinate */ if ((volumeXYZ[0] == 0.0) && (volumeXYZ[1] == 0.0) && (volumeXYZ[2] == 0.0)) { const float* stereoXYZ = getStereotaxicXYZ(); return stereoXYZ; } return this->volumeXYZ; } /** * Get the volume XYZ coordinates. * @param xyzOut Volume XYZ coordinates. * */ void SurfaceProjectedItem::getVolumeXYZ(float xyzOut[3]) const { /* * If not set, return stereotaxic coordinate */ if ((volumeXYZ[0] == 0.0) && (volumeXYZ[1] == 0.0) && (volumeXYZ[2] == 0.0)) { getStereotaxicXYZ(xyzOut); return; } xyzOut[0] = this->volumeXYZ[0]; xyzOut[1] = this->volumeXYZ[1]; xyzOut[2] = this->volumeXYZ[2]; } /** * Get the validity of the volume XYZ coordinate. * @return Validity of volume XYZ coordinate. * */ bool SurfaceProjectedItem::isVolumeXYZValid() const { return this->volumeXYZValid; } /** * Set the item's volume coordinates and sets the validity * of the volume coordinates to true. * * @param volumeXYZ new value of volumeXYZ * */ void SurfaceProjectedItem::setVolumeXYZ(const float volumeXYZ[3]) { this->volumeXYZ[0] = volumeXYZ[0]; this->volumeXYZ[1] = volumeXYZ[1]; this->volumeXYZ[2] = volumeXYZ[2]; this->volumeXYZValid = true; this->setModified(); } /** * Get the structure of this projected item. * @return The structure. * */ StructureEnum::Enum SurfaceProjectedItem::getStructure() const { return this->structure; } /** * Set the structure of this projected item. * @param s - new structure. * */ void SurfaceProjectedItem::setStructure(const StructureEnum::Enum structure) { this->structure = structure; this->setModified(); } /** * @return the barycentric projection */ SurfaceProjectionBarycentric* SurfaceProjectedItem::getBarycentricProjection() { return this->barycentricProjection; } /** * @return the barycentric projection */ const SurfaceProjectionBarycentric* SurfaceProjectedItem::getBarycentricProjection() const { return this->barycentricProjection; } /** * @return the Van Essen projection */ SurfaceProjectionVanEssen* SurfaceProjectedItem::getVanEssenProjection() { return this->vanEssenProjection; } /** * @return the Van Essen projection */ const SurfaceProjectionVanEssen* SurfaceProjectedItem::getVanEssenProjection() const { return this->vanEssenProjection; } /** * Write the border to the XML Writer. * @param xmlWriter * Writer for XML output. */ void SurfaceProjectedItem::writeAsXML(XmlWriter& xmlWriter) { xmlWriter.writeStartElement(XML_TAG_SURFACE_PROJECTED_ITEM); xmlWriter.writeElementCharacters(XML_TAG_STRUCTURE, StructureEnum::toName(this->structure)); if (this->stereotaxicXYZValid) { xmlWriter.writeElementCharacters(XML_TAG_STEREOTAXIC_XYZ, this->stereotaxicXYZ, 3); } if (this->volumeXYZValid) { xmlWriter.writeElementCharacters(XML_TAG_VOLUME_XYZ, this->volumeXYZ, 3); } this->barycentricProjection->writeAsXML(xmlWriter); this->vanEssenProjection->writeAsXML(xmlWriter); xmlWriter.writeEndElement(); } void SurfaceProjectedItem::readBorderFileXML1(QXmlStreamReader& xml) { reset(); CaretAssert(xml.isStartElement() && xml.name() == "SurfaceProjectedItem"); bool haveStructure = false, haveVanEssen = false, haveStereo = false, haveVolume = false;//track the barycentric projection for being specified more than once by its valid flag for (xml.readNext(); !xml.atEnd() && !xml.isEndElement(); xml.readNext()) { switch (xml.tokenType()) { case QXmlStreamReader::StartElement: { QStringRef name = xml.name(); if (name == "Structure") { if (haveStructure) throw DataFileException("multiple Structure elements in one SurfaceProjectedItem element"); QString structString = xml.readElementText();//sets error on unexpected child element if (xml.hasError()) throw DataFileException("XML parsing error in Structure: " + xml.errorString()); bool ok = false; structure = StructureEnum::fromName(structString, &ok); if (!ok) CaretLogWarning("unrecognized string in Structure: " + structString);//HACK: this is what the SAX reader did, don't look at me haveStructure = true; } else if (name == "ProjectionBarycentric") { if (barycentricProjection->isValid()) throw DataFileException("multiple ProjectionBarycentric elements in one SurfaceProjectedItem element"); barycentricProjection->readBorderFileXML1(xml); } else if (name == "VanEssenProjection") { if (haveVanEssen) throw DataFileException("multiple VanEssenProjection elements in one SurfaceProjectedItem element"); CaretLogFine("found Van Essen projection in border file, ignoring"); xml.readElementText(QXmlStreamReader::SkipChildElements);//HACK: border files never use this projection type, so don't try to parse it if (xml.hasError()) throw DataFileException("XML parsing error in VanEssenProjection: " + xml.errorString()); haveVanEssen = true; } else if (name == "StereotaxicXYZ") { if (haveStereo) throw DataFileException("multiple StereotaxicXYZ elements in one SurfaceProjectedItem element"); CaretLogFine("found stereotaxic coordinates in border file, ignoring"); xml.readElementText(QXmlStreamReader::SkipChildElements);//HACK: ditto if (xml.hasError()) throw DataFileException("XML parsing error in StereotaxicXYZ: " + xml.errorString()); haveStereo = true; } else if (name == "VolumeXYZ") { if (haveVolume) throw DataFileException("multiple VolumeXYZ elements in one SurfaceProjectedItem element"); CaretLogFine("found volume coordinates in border file, ignoring"); xml.readElementText(QXmlStreamReader::SkipChildElements);//HACK: ditto if (xml.hasError()) throw DataFileException("XML parsing error in VolumeXYZ: " + xml.errorString()); haveVolume = true; } else { throw DataFileException("unexpected element in SurfaceProjectedItem: " + name.toString()); } break; } default: break; } } if (xml.hasError()) throw DataFileException("XML parsing error in SurfaceProjectedItem: " + xml.errorString()); CaretAssert(xml.isEndElement() && xml.name() == "SurfaceProjectedItem"); if (!haveStructure) throw DataFileException("SurfaceProjectedItem is missing Structure element"); } /** * Set the status to unmodified. */ void SurfaceProjectedItem::clearModified() { CaretObjectTracksModification::clearModified(); this->barycentricProjection->clearModified(); this->vanEssenProjection->clearModified(); } /** * Is the object modified? * @return true if modified, else false. */ bool SurfaceProjectedItem::isModified() const { if (CaretObjectTracksModification::isModified()) { return true; } if (this->barycentricProjection->isModified()) { return true; } if (this->vanEssenProjection->isModified()) { return true; } return false; } /** * @return True if a projection (barycentric, vanessen) * is valid. Otherwise, false. */ bool SurfaceProjectedItem::hasValidProjection() const { if (barycentricProjection->isValid()) { return true; } if (vanEssenProjection->isValid()) { return true; } return false; } workbench-1.1.1/src/Files/SurfaceProjectedItem.h000066400000000000000000000120121255417355300215530ustar00rootroot00000000000000#ifndef __SURFACE_PROJECTED_ITEM_H__ #define __SURFACE_PROJECTED_ITEM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObjectTracksModification.h" #include "StructureEnum.h" #include "XmlException.h" class QXmlStreamReader; namespace caret { class SurfaceFile; class SurfaceProjectionBarycentric; class SurfaceProjectionVanEssen; class XmlWriter; class SurfaceProjectedItem : public CaretObjectTracksModification { public: SurfaceProjectedItem(); SurfaceProjectedItem(const SurfaceProjectedItem& o); SurfaceProjectedItem& operator=(const SurfaceProjectedItem& o); bool operator==(const SurfaceProjectedItem& rhs) const; bool operator!=(const SurfaceProjectedItem& rhs) const { return !(*this == rhs); } virtual ~SurfaceProjectedItem(); private: void copyHelper(const SurfaceProjectedItem& o); void initializeMembersSurfaceProjectedItem(); public: void unprojectToStereotaxicXYZ(const SurfaceFile& sf, const bool isUnprojectedOntoSurface); void unprojectToVolumeXYZ(const SurfaceFile& sf, const bool isUnprojectedOntoSurface); bool getProjectedPosition(const SurfaceFile& sf, float xyzOut[3], const bool isUnprojectedOntoSurface) const; bool getProjectedPositionAboveSurface(const SurfaceFile& sf, float xyzOut[3], const float distanceAboveSurface) const; const float* getStereotaxicXYZ() const; void getStereotaxicXYZ(float stereotaxicXYZOut[3]) const; bool isStereotaxicXYZValid() const; void setStereotaxicXYZ(const float stereotaxicXYZ[3]); const float* getVolumeXYZ() const; void getVolumeXYZ(float xyzOut[3]) const; bool isVolumeXYZValid() const; void setVolumeXYZ(const float volumeXYZ[3]); StructureEnum::Enum getStructure() const; void setStructure(const StructureEnum::Enum structure); const SurfaceProjectionBarycentric* getBarycentricProjection() const; SurfaceProjectionBarycentric* getBarycentricProjection(); const SurfaceProjectionVanEssen* getVanEssenProjection() const; SurfaceProjectionVanEssen* getVanEssenProjection(); bool hasValidProjection() const; void reset(); void writeAsXML(XmlWriter& xmlWriter); void readBorderFileXML1(QXmlStreamReader& xml); virtual void clearModified(); virtual bool isModified() const; static AString XML_TAG_SURFACE_PROJECTED_ITEM; static AString XML_TAG_STEREOTAXIC_XYZ; static AString XML_TAG_VOLUME_XYZ; static AString XML_TAG_STRUCTURE; protected: /** stereotaxic position of projected item. */ float stereotaxicXYZ[3]; /** stereotaxic position of projected item valid */ bool stereotaxicXYZValid; /** position in volume */ float volumeXYZ[3]; /** position in volume valid */ bool volumeXYZValid; /** Structure to which projected. */ StructureEnum::Enum structure; /** The barycentric projection */ SurfaceProjectionBarycentric* barycentricProjection; /** The Van Essen projection */ SurfaceProjectionVanEssen* vanEssenProjection; }; #ifdef __SURFACE_PROJECTED_ITEM_DEFINE__ AString SurfaceProjectedItem::XML_TAG_SURFACE_PROJECTED_ITEM = "SurfaceProjectedItem"; AString SurfaceProjectedItem::XML_TAG_STEREOTAXIC_XYZ = "StereotaxicXYZ"; AString SurfaceProjectedItem::XML_TAG_VOLUME_XYZ = "VolumeXYZ"; AString SurfaceProjectedItem::XML_TAG_STRUCTURE = "Structure"; #endif // __SURFACE_PROJECTED_ITEM_DEFINE__ } // namespace #endif // __SURFACE_PROJECTED_ITEM_H__ workbench-1.1.1/src/Files/SurfaceProjectedItemSaxReader.cxx000066400000000000000000000334151255417355300237370ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretLogger.h" #include "SurfaceProjectedItem.h" #include "SurfaceProjectedItemSaxReader.h" #include "SurfaceProjectionBarycentric.h" #include "SurfaceProjectionVanEssen.h" #include "XmlAttributes.h" #include "XmlException.h" #include "XmlUtilities.h" using namespace caret; /** * constructor. */ SurfaceProjectedItemSaxReader::SurfaceProjectedItemSaxReader(SurfaceProjectedItem* surfaceProjectedItem) { this->state = STATE_NONE; this->stateStack.push(this->state); this->elementText = ""; this->surfaceProjectedItem = surfaceProjectedItem; } /** * destructor. */ SurfaceProjectedItemSaxReader::~SurfaceProjectedItemSaxReader() { } /** * start an element. */ void SurfaceProjectedItemSaxReader::startElement(const AString& /* namespaceURI */, const AString& /* localName */, const AString& qName, const XmlAttributes& /*attributes*/) { const STATE previousState = this->state; switch (state) { case STATE_NONE: if (qName == SurfaceProjectedItem::XML_TAG_SURFACE_PROJECTED_ITEM) { this->state = STATE_SURFACE_PROJECTED_ITEM; } else { AString txt = XmlUtilities::createInvalidRootElementMessage(SurfaceProjectedItem::XML_TAG_SURFACE_PROJECTED_ITEM, qName); XmlSaxParserException e(txt); CaretLogThrowing(e); throw e; } break; case STATE_SURFACE_PROJECTED_ITEM: if (qName == SurfaceProjectionBarycentric::XML_TAG_PROJECTION_BARYCENTRIC) { state = STATE_BARYCENTRIC; } else if (qName == SurfaceProjectionVanEssen::XML_TAG_PROJECTION_VAN_ESSEN) { state = STATE_VAN_ESSEN; } else if ((qName == SurfaceProjectedItem::XML_TAG_STEREOTAXIC_XYZ) || (qName == SurfaceProjectedItem::XML_TAG_STRUCTURE) || (qName == SurfaceProjectedItem::XML_TAG_VOLUME_XYZ)) { // nothing } else { AString txt = XmlUtilities::createInvalidChildElementMessage(SurfaceProjectedItem::XML_TAG_SURFACE_PROJECTED_ITEM, qName); XmlSaxParserException e(txt); CaretLogThrowing(e); throw e; } break; case STATE_BARYCENTRIC: if ((qName == SurfaceProjectionBarycentric::XML_TAG_SIGNED_DISTANCE_ABOVE_SURFACE) || (qName == SurfaceProjectionBarycentric::XML_TAG_TRIANGLE_AREAS) || (qName == SurfaceProjectionBarycentric::XML_TAG_TRIANGLE_NODES)) { // nothing } else { AString txt = XmlUtilities::createInvalidChildElementMessage(SurfaceProjectionBarycentric::XML_TAG_PROJECTION_BARYCENTRIC, qName); XmlSaxParserException e(txt); CaretLogThrowing(e); throw e; } break; case STATE_VAN_ESSEN: if ((qName == SurfaceProjectionVanEssen::XML_TAG_DR) || (qName == SurfaceProjectionVanEssen::XML_TAG_FRAC_RI) || (qName == SurfaceProjectionVanEssen::XML_TAG_FRAC_RJ) || (qName == SurfaceProjectionVanEssen::XML_TAG_PHI_R) || (qName == SurfaceProjectionVanEssen::XML_TAG_POS_ANATOMICAL) || (qName == SurfaceProjectionVanEssen::XML_TAG_PROJECTION_VAN_ESSEN) || (qName == SurfaceProjectionVanEssen::XML_TAG_THETA_R) || (qName == SurfaceProjectionVanEssen::XML_TAG_TRI_ANATOMICAL) || (qName == SurfaceProjectionVanEssen::XML_TAG_TRI_VERTICES) || (qName == SurfaceProjectionVanEssen::XML_TAG_VERTEX) || (qName == SurfaceProjectionVanEssen::XML_TAG_VERTEX_ANATOMICAL)) { // nothing } else { AString txt = XmlUtilities::createInvalidChildElementMessage(SurfaceProjectionVanEssen::XML_TAG_PROJECTION_VAN_ESSEN, qName); XmlSaxParserException e(txt); CaretLogThrowing(e); throw e; } break; } // // Save previous state // this->stateStack.push(previousState); this->elementText = ""; } /** * end an element. */ void SurfaceProjectedItemSaxReader::endElement(const AString& /* namspaceURI */, const AString& /* localName */, const AString& qName) { const AString text = this->elementText.trimmed(); switch (state) { case STATE_NONE: break; case STATE_SURFACE_PROJECTED_ITEM: if (qName == SurfaceProjectedItem::XML_TAG_STEREOTAXIC_XYZ) { std::vector xyz; XmlUtilities::getArrayOfNumbersFromText(qName, text, 3, xyz); this->surfaceProjectedItem->setStereotaxicXYZ(xyz.data()); } else if (qName == SurfaceProjectedItem::XML_TAG_STRUCTURE) { bool isValid = false; this->surfaceProjectedItem->setStructure(StructureEnum::fromName(text, &isValid)); if (isValid == false) { CaretLogWarning("Invalid structure name: " + text); throw XmlSaxParserException("Invalid structure name: " + text); } } else if (qName == SurfaceProjectedItem::XML_TAG_VOLUME_XYZ) { std::vector xyz; XmlUtilities::getArrayOfNumbersFromText(qName, text, 3, xyz); this->surfaceProjectedItem->setVolumeXYZ(xyz.data()); } break; case STATE_BARYCENTRIC: { SurfaceProjectionBarycentric* bp = this->surfaceProjectedItem->getBarycentricProjection(); if (qName == SurfaceProjectionBarycentric::XML_TAG_SIGNED_DISTANCE_ABOVE_SURFACE) { bp->setSignedDistanceAboveSurface(text.toFloat()); } else if (qName == SurfaceProjectionBarycentric::XML_TAG_TRIANGLE_AREAS) { std::vector areas; XmlUtilities::getArrayOfNumbersFromText(qName, text, 3, areas); bp->setTriangleAreas(areas.data()); } else if (qName == SurfaceProjectionBarycentric::XML_TAG_TRIANGLE_NODES) { std::vector nodes; XmlUtilities::getArrayOfNumbersFromText(qName, text, 3, nodes); bp->setTriangleNodes(nodes.data()); } bp->setValid(true); } break; case STATE_VAN_ESSEN: { SurfaceProjectionVanEssen* ve = this->surfaceProjectedItem->getVanEssenProjection(); if (qName == SurfaceProjectionVanEssen::XML_TAG_DR) { ve->setDR(text.toFloat()); } else if (qName == SurfaceProjectionVanEssen::XML_TAG_FRAC_RI) { ve->setFracRI(text.toFloat()); } else if (qName == SurfaceProjectionVanEssen::XML_TAG_FRAC_RJ) { ve->setFracRJ(text.toFloat()); } else if (qName == SurfaceProjectionVanEssen::XML_TAG_PHI_R) { ve->setPhiR(text.toFloat()); } else if (qName == SurfaceProjectionVanEssen::XML_TAG_POS_ANATOMICAL) { std::vector xyz; XmlUtilities::getArrayOfNumbersFromText(qName, text, 3, xyz); ve->setPosAnatomical(xyz.data()); } else if (qName == SurfaceProjectionVanEssen::XML_TAG_THETA_R) { ve->setThetaR(text.toFloat()); } else if (qName == SurfaceProjectionVanEssen::XML_TAG_TRI_ANATOMICAL) { std::vector data; XmlUtilities::getArrayOfNumbersFromText(qName, text, 18, data); float ta[2][3][3]; int32_t ctr = 0; for (int32_t i = 0; i < 2; i++) { for (int32_t j = 0; j < 3; j++) { for (int32_t k = 0; k < 3; k++) { ta[i][j][k] = data[ctr]; ctr++; } } } ve->setTriAnatomical(ta); } else if (qName == SurfaceProjectionVanEssen::XML_TAG_TRI_VERTICES) { std::vector data; XmlUtilities::getArrayOfNumbersFromText(qName, text, 6, data); int32_t tv[2][3]; int32_t ctr = 0; for (int32_t i = 0; i < 2; i++) { for (int32_t j = 0; j < 3; j++) { tv[i][j] = data[ctr]; ctr++; } } ve->setTriVertices(tv); } else if (qName == SurfaceProjectionVanEssen::XML_TAG_VERTEX) { std::vector data; XmlUtilities::getArrayOfNumbersFromText(qName, text, 2, data); int32_t tv[2]; int32_t ctr = 0; for (int32_t i = 0; i < 2; i++) { tv[i] = data[ctr]; ctr++; } ve->setVertex(tv); } else if (qName == SurfaceProjectionVanEssen::XML_TAG_VERTEX_ANATOMICAL) { std::vector data; XmlUtilities::getArrayOfNumbersFromText(qName, text, 6, data); float va[2][3]; int32_t ctr = 0; for (int32_t i = 0; i < 2; i++) { for (int32_t j = 0; j < 3; j++) { va[i][j] = data[ctr]; ctr++; } } ve->setVertexAnatomical(va); } ve->setValid(true); } break; } // // Clear out for new elements // this->elementText = ""; // // Go to previous state // if (this->stateStack.empty()) { throw XmlSaxParserException("State stack is empty while reading XML MetaData."); } this->state = this->stateStack.top(); this->stateStack.pop(); } /** * get characters in an element. */ void SurfaceProjectedItemSaxReader::characters(const char* ch) { this->elementText += ch; } /** * a fatal error occurs. */ void SurfaceProjectedItemSaxReader::fatalError(const XmlSaxParserException& e) { // // Stop parsing // CaretLogSevere("XML Parser Fatal Error: " + e.whatString()); throw e; } // a warning occurs void SurfaceProjectedItemSaxReader::warning(const XmlSaxParserException& e) { CaretLogWarning("XML Parser Warning: " + e.whatString()); } // an error occurs void SurfaceProjectedItemSaxReader::error(const XmlSaxParserException& e) { throw e; } void SurfaceProjectedItemSaxReader::startDocument() { } void SurfaceProjectedItemSaxReader::endDocument() { } workbench-1.1.1/src/Files/SurfaceProjectedItemSaxReader.h000066400000000000000000000064571255417355300233720ustar00rootroot00000000000000 #ifndef __SURFACE_PROJECTED_ITEM_SAX_READER_H__ #define __SURFACE_PROJECTED_ITEM_SAX_READER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include "XmlSaxParserException.h" #include "XmlSaxParserHandlerInterface.h" namespace caret { class SurfaceProjectedItem; class XmlAttributes; /** * class for reading SurfaceProjectedItem with a SAX Parser */ class SurfaceProjectedItemSaxReader : public CaretObject, public XmlSaxParserHandlerInterface { public: SurfaceProjectedItemSaxReader(SurfaceProjectedItem* surfaceProjectedItem); virtual ~SurfaceProjectedItemSaxReader(); void startElement(const AString& namespaceURI, const AString& localName, const AString& qName, const XmlAttributes& attributes); void endElement(const AString& namspaceURI, const AString& localName, const AString& qName); void characters(const char* ch); void fatalError(const XmlSaxParserException& e); void warning(const XmlSaxParserException& e); void error(const XmlSaxParserException& e); void startDocument(); void endDocument(); private: SurfaceProjectedItemSaxReader(const SurfaceProjectedItemSaxReader&); SurfaceProjectedItemSaxReader& operator=(const SurfaceProjectedItemSaxReader&); protected: /// file reading states enum STATE { /// no state STATE_NONE, /// processing SurfaceProjectedItem tags STATE_SURFACE_PROJECTED_ITEM, /// processing Barycentric tags STATE_BARYCENTRIC, /// processing Van Essen tags STATE_VAN_ESSEN }; /// file reading state STATE state; /// the state stack used when reading a file std::stack stateStack; /// the error message AString errorMessage; /// meta data name AString metaDataName; /// meta data value AString metaDataValue; /// element text AString elementText; /// GIFTI meta data being read SurfaceProjectedItem* surfaceProjectedItem; }; } // namespace #endif // __SURFACE_PROJECTED_ITEM_SAX_READER_H__ workbench-1.1.1/src/Files/SurfaceProjection.cxx000066400000000000000000000054571255417355300215230ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SURFACE_PROJECTION_DECLARE__ #include "SurfaceProjection.h" #undef __SURFACE_PROJECTION_DECLARE__ using namespace caret; /** * \class caret::SurfaceProjection * \brief Abstract class for Surface Projections. */ /** * Constructor. */ SurfaceProjection::SurfaceProjection() : CaretObjectTracksModification() { this->projectionSurfaceNumberOfNodes = 0; } /** * Destructor. */ SurfaceProjection::~SurfaceProjection() { } /** * Copy constructor. * @param obj * Object that is copied. */ SurfaceProjection::SurfaceProjection(const SurfaceProjection& obj) : CaretObjectTracksModification(obj) { this->copyHelperSurfaceProjection(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ SurfaceProjection& SurfaceProjection::operator=(const SurfaceProjection& obj) { if (this != &obj) { CaretObjectTracksModification::operator=(obj); this->copyHelperSurfaceProjection(obj); } return *this; } /** * Helps with copying an object of this type. * @param obj * Object that is copied. */ void SurfaceProjection::copyHelperSurfaceProjection(const SurfaceProjection& obj) { this->projectionSurfaceNumberOfNodes = obj.projectionSurfaceNumberOfNodes; } /** * @return The number of nodes in the surface to which this projection is made. */ int32_t SurfaceProjection::getProjectionSurfaceNumberOfNodes() const { return this->projectionSurfaceNumberOfNodes; } /** * Set the number of nodes in the surface to which this projection is made. * @param projectionSurfaceNumberOfNodes * Number of nodes in the surface. */ void SurfaceProjection::setProjectionSurfaceNumberOfNodes(const int projectionSurfaceNumberOfNodes) { this->projectionSurfaceNumberOfNodes = projectionSurfaceNumberOfNodes; } /** * @return a string describing the projection */ AString SurfaceProjection::toString() const { return CaretObjectTracksModification::toString(); } workbench-1.1.1/src/Files/SurfaceProjection.h000066400000000000000000000072531255417355300211440ustar00rootroot00000000000000#ifndef __SURFACE_PROJECTION__H_ #define __SURFACE_PROJECTION__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObjectTracksModification.h" #include "XmlException.h" namespace caret { class SurfaceFile; class XmlWriter; class SurfaceProjection : public CaretObjectTracksModification { public: SurfaceProjection(); virtual ~SurfaceProjection(); SurfaceProjection(const SurfaceProjection& obj); SurfaceProjection& operator=(const SurfaceProjection& obj); /** * @return Is the projection valid? */ virtual bool isValid() const = 0; /** * Set the validity of the projection. * @param valid * New validity status. */ virtual void setValid(const bool valid) = 0; /** * Reset the surface projection to its initial state. */ virtual void reset() = 0; /** * Unproject to the surface using 'this' projection. * * @param surfaceFile * Surface file used for unprojecting. * @param xyzOut * Output containing coordinate created by unprojecting. * @param offsetFromSurface * If 'unprojectWithOffsetFromSurface' is true, unprojected * position will be this distance above (negative=below) * the surface. * @param unprojectWithOffsetFromSurface * If true, ouput coordinate will be offset 'offsetFromSurface' * distance from the surface. * @return * True if unprojection is successful, else false. */ virtual bool unprojectToSurface(const SurfaceFile& surfaceFile, float xyzOut[3], const float offsetFromSurface, const bool unprojectWithOffsetFromSurface) const = 0; int32_t getProjectionSurfaceNumberOfNodes() const; void setProjectionSurfaceNumberOfNodes(const int surfaceNumberOfNodes); /** * Write the projection to XML. * @param xmlWriter * The XML Writer. * @throw XmlException * If an error occurs. */ virtual void writeAsXML(XmlWriter& xmlWriter) = 0; /* @return a string describing the projection */ virtual AString toString() const; protected: /** Number of nodes in surface to which item is projected. */ int32_t projectionSurfaceNumberOfNodes; private: void copyHelperSurfaceProjection(const SurfaceProjection& obj); }; #ifdef __SURFACE_PROJECTION_DECLARE__ // #endif // __SURFACE_PROJECTION_DECLARE__ } // namespace #endif //__SURFACE_PROJECTION__H_ workbench-1.1.1/src/Files/SurfaceProjectionBarycentric.cxx000066400000000000000000000366771255417355300237210ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SURFACE_PROJECTION_BARYCENTRIC_DECLARE__ #include "SurfaceProjectionBarycentric.h" #undef __SURFACE_PROJECTION_BARYCENTRIC_DECLARE__ #include "CaretAssert.h" #include "DataFileException.h" #include "MathFunctions.h" #include "SurfaceFile.h" #include "TopologyHelper.h" #include "XmlWriter.h" #include #include using namespace caret; /** * \class caret::SurfaceProjectionBarycentric * \brief Maintains a barycentric projection. * */ /** * Constructor. */ SurfaceProjectionBarycentric::SurfaceProjectionBarycentric() : SurfaceProjection() { this->resetAllValues(); } /** * Destructor. */ SurfaceProjectionBarycentric::~SurfaceProjectionBarycentric() { } /** * Copy constructor. * @param obj * Object that is copied. */ SurfaceProjectionBarycentric::SurfaceProjectionBarycentric(const SurfaceProjectionBarycentric& obj) : SurfaceProjection(obj) { this->copyHelperSurfaceProjectionBarycentric(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ SurfaceProjectionBarycentric& SurfaceProjectionBarycentric::operator=(const SurfaceProjectionBarycentric& obj) { if (this != &obj) { SurfaceProjection::operator=(obj); this->copyHelperSurfaceProjectionBarycentric(obj); } return *this; } bool SurfaceProjectionBarycentric::operator==(const SurfaceProjectionBarycentric& rhs) { if (projectionValid != rhs.projectionValid) return false; if (projectionValid) { for (int i = 0; i < 3; ++i) { if (triangleAreas[i] != rhs.triangleAreas[i]) return false; if (triangleNodes[i] != rhs.triangleNodes[i]) return false; } if (m_degenerate != rhs.m_degenerate) return false; return (signedDistanceAboveSurface == rhs.signedDistanceAboveSurface); } return true; } /** * Helps with copying an object of this type. * @param obj * Object that is copied. */ void SurfaceProjectionBarycentric::copyHelperSurfaceProjectionBarycentric(const SurfaceProjectionBarycentric& obj) { this->setTriangleAreas(obj.getTriangleAreas()); this->setTriangleNodes(obj.getTriangleNodes()); this->signedDistanceAboveSurface = obj.signedDistanceAboveSurface; this->projectionValid = obj.projectionValid; this->m_degenerate = obj.m_degenerate; } /** * Get a description of this object's content. * @return String describing this object's content. */ AString SurfaceProjectionBarycentric::toString() const { AString txt = SurfaceProjection::toString(); if (txt.isEmpty() == false) { txt += ", "; } txt += ("projectionValid=" + AString::fromBool(projectionValid) + ", triangleAreas=(" + AString::fromNumbers(this->triangleAreas, 3, ",") + "), triangleNodes=(" + AString::fromNumbers(this->triangleNodes, 3, ",") + "), signedDistanceAboveSurface=" + AString::number(this->signedDistanceAboveSurface)); return txt; } /** * @return The signed distance above the surface. */ float SurfaceProjectionBarycentric::getSignedDistanceAboveSurface() const { return this->signedDistanceAboveSurface; } /** * Set the signed distance above the surface. * * @param signedDistanceAboveSurface * New value. */ void SurfaceProjectionBarycentric::setSignedDistanceAboveSurface(const float signedDistanceAboveSurface) { this->signedDistanceAboveSurface = signedDistanceAboveSurface; this->setModified(); } /** * @return The triangle nodes (3 elements). */ const int32_t* SurfaceProjectionBarycentric::getTriangleNodes() const { return this->triangleNodes; } /** * Set the triangle nodes. * * @param triangleNodes * New values for nodes. */ void SurfaceProjectionBarycentric::setTriangleNodes(const int32_t triangleNodes[3]) { this->triangleNodes[0] = triangleNodes[0]; this->triangleNodes[1] = triangleNodes[1]; this->triangleNodes[2] = triangleNodes[2]; this->setModified(); } /** * @return The triangle areas (3 elements). */ const float* SurfaceProjectionBarycentric::getTriangleAreas() const { return this->triangleAreas; } int32_t SurfaceProjectionBarycentric::getNodeWithLargestWeight() const { int32_t ret = -1; float largestWeight = 0.0f;//there must be a positive weight for (int i = 0; i < 3; ++i) { if (triangleAreas[i] > largestWeight) { ret = triangleNodes[i]; largestWeight = triangleAreas[i]; } } return ret; } /** * Set the triangle areas. * * @param triangleAreas * New values for triangle areas. */ void SurfaceProjectionBarycentric::setTriangleAreas(const float triangleAreas[3]) { this->triangleAreas[0] = triangleAreas[0]; this->triangleAreas[1] = triangleAreas[1]; this->triangleAreas[2] = triangleAreas[2]; this->setModified(); } /** * Unproject to the surface using 'this' projection. * * @param surfaceFile * Surface file used for unprojecting. * @param xyzOut * Output containing coordinate created by unprojecting. * @param offsetFromSurface * If 'unprojectWithOffsetFromSurface' is true, unprojected * position will be this distance above (negative=below) * the surface. * @param unprojectWithOffsetFromSurface * If true, ouput coordinate will be offset 'offsetFromSurface' * distance from the surface. * @return * True if unprojection was successful. */ bool SurfaceProjectionBarycentric::unprojectToSurface(const SurfaceFile& surfaceFile, float xyzOut[3], const float offsetFromSurface, const bool unprojectWithOffsetFromSurface) const { /* * Make sure projection surface number of nodes matches surface. */ if (this->projectionSurfaceNumberOfNodes > 0) { if (surfaceFile.getNumberOfNodes() != this->projectionSurfaceNumberOfNodes) { return false; } } const int32_t n1 = this->triangleNodes[0]; const int32_t n2 = this->triangleNodes[1]; const int32_t n3 = this->triangleNodes[2]; CaretAssert(n1 < surfaceFile.getNumberOfNodes()); CaretAssert(n2 < surfaceFile.getNumberOfNodes()); CaretAssert(n3 < surfaceFile.getNumberOfNodes()); /* * All nodes MUST have neighbors (connected) */ const TopologyHelper* topologyHelper = surfaceFile.getTopologyHelper().getPointer(); if ((topologyHelper->getNodeHasNeighbors(n1) == false) || (topologyHelper->getNodeHasNeighbors(n2) == false) || (topologyHelper->getNodeHasNeighbors(n3) == false)) { return false; } const float* c1 = surfaceFile.getCoordinate(n1); const float* c2 = surfaceFile.getCoordinate(n2); const float* c3 = surfaceFile.getCoordinate(n3); float barycentricXYZ[3]; float barycentricNormal[3]; /* * If all the nodes are the same (object projects to a single node, not triangle) */ if ((n1 == n2) && (n2 == n3)) { /* * Use node's normal vector and position */ barycentricXYZ[0] = c1[0]; barycentricXYZ[1] = c1[1]; barycentricXYZ[2] = c1[2]; const float* nodeNormal = surfaceFile.getNormalVector(n1); barycentricNormal[0] = nodeNormal[0]; barycentricNormal[1] = nodeNormal[1]; barycentricNormal[2] = nodeNormal[2]; } else { /* * Compute position using barycentric coordinates */ float t1[3]; float t2[3]; float t3[3]; for (int i = 0; i < 3; i++) { t1[i] = triangleAreas[0] * c1[i]; t2[i] = triangleAreas[1] * c2[i]; t3[i] = triangleAreas[2] * c3[i]; } float area = (triangleAreas[0] + triangleAreas[1] + triangleAreas[2]); if (area != 0) { for (int i = 0; i < 3; i++) { barycentricXYZ[i] = (t1[i] + t2[i] + t3[i]) / area; } } else { return false; } if (MathFunctions::normalVector(c1, c2, c3, barycentricNormal) == false) { return false; } } /* * Set output coordinate, possibly offsetting from surface. */ for (int j = 0; j < 3; j++) { if (unprojectWithOffsetFromSurface) { xyzOut[j] = (barycentricXYZ[j] + (barycentricNormal[j] * offsetFromSurface)); } else { xyzOut[j] = (barycentricXYZ[j] + (barycentricNormal[j] * signedDistanceAboveSurface)); } } return true; } /** * Reset the surface projection to its initial state. */ void SurfaceProjectionBarycentric::reset() { this->resetAllValues(); } /** * @return Is the projection valid? */ bool SurfaceProjectionBarycentric::isValid() const { return this->projectionValid; } /** * Set the validity of the projection. * @param valid * New validity status. */ void SurfaceProjectionBarycentric::setValid(const bool valid) { this->projectionValid = valid; } /** * Set the projection is degenerate (on * an edge or just outside the edge). * @param degenerate * New status. */ void SurfaceProjectionBarycentric::setDegenerate(const bool degenerate) { m_degenerate = degenerate; } /** * @return Is the projection degenerate (on * an edge or just outside the edge). */ bool SurfaceProjectionBarycentric::isDegenerate() const { return m_degenerate; } /** * Since reset overrides the 'super' class it should * never be called from a constructor. So, this * method does the actual reset, and since it does * not override a method from the 'super' class, it * may be called from this class' constructor. */ void SurfaceProjectionBarycentric::resetAllValues() { this->projectionValid = false; m_degenerate = false; this->triangleAreas[0] = -1.0; this->triangleAreas[1] = -1.0; this->triangleAreas[2] = -1.0; this->triangleNodes[0] = -1; this->triangleNodes[1] = -1; this->triangleNodes[2] = -1; this->signedDistanceAboveSurface = 0.0; } /** * Write the projection to XML. * @param xmlWriter * The XML Writer. * @throw XmlException * If an error occurs. */ void SurfaceProjectionBarycentric::writeAsXML(XmlWriter& xmlWriter) { /* * Note: Degenerate status is not saved! */ if (this->projectionValid) { xmlWriter.writeStartElement(XML_TAG_PROJECTION_BARYCENTRIC); xmlWriter.writeElementCharacters(XML_TAG_TRIANGLE_AREAS, this->triangleAreas, 3); xmlWriter.writeElementCharacters(XML_TAG_TRIANGLE_NODES, this->triangleNodes, 3); xmlWriter.writeElementCharacters(XML_TAG_SIGNED_DISTANCE_ABOVE_SURFACE, this->signedDistanceAboveSurface); xmlWriter.writeEndElement(); } } void SurfaceProjectionBarycentric::readBorderFileXML1(QXmlStreamReader& xml) { reset(); CaretAssert(xml.isStartElement() && xml.name() == "ProjectionBarycentric"); bool haveAreas = false, haveNodes = false, haveDist = false; for (xml.readNext(); !xml.atEnd() && !xml.isEndElement(); xml.readNext()) { switch (xml.tokenType()) { case QXmlStreamReader::StartElement: { QStringRef name = xml.name(); if (name == "TriangleAreas") { if (haveAreas) throw DataFileException("multiple TriangleAreas elements in one ProjectionBarycentric element"); QString text = xml.readElementText();//errors on unexpected element if (xml.hasError()) throw DataFileException("XML parsing error in TriangleAreas: " + xml.errorString()); QStringList areaStrings = text.split(QRegExp("\\s+"), QString::SkipEmptyParts); if (areaStrings.size() != 3) throw DataFileException("TriangleAreas element must contain 3 numbers separated by whitespace"); bool ok = false; for (int i = 0; i < 3; ++i) { triangleAreas[i] = areaStrings[i].toFloat(&ok); if (!ok) throw DataFileException("found non-numeric string in TriangleAreas: " + areaStrings[i]); } haveAreas = true; } else if (name == "TriangleNodes") { if (haveNodes) throw DataFileException("multiple TriangleNodes elements in one ProjectionBarycentric element"); QString text = xml.readElementText();//errors on unexpected element if (xml.hasError()) throw DataFileException("XML parsing error in TriangleNodes: " + xml.errorString()); QStringList nodeStrings = text.split(QRegExp("\\s+"), QString::SkipEmptyParts); if (nodeStrings.size() != 3) throw DataFileException("TriangleNodes element must contain 3 integers separated by whitespace"); bool ok = false; for (int i = 0; i < 3; ++i) { triangleNodes[i] = nodeStrings[i].toInt(&ok); if (!ok) throw DataFileException("found non-integer string in TriangleNodes: " + nodeStrings[i]); } haveNodes = true; } else if (name == "SignedDistanceAboveSurface") { if (haveDist) throw DataFileException("multiple SignedDistanceAboveSurface elements in one ProjectionBarycentric element"); QString text = xml.readElementText();//errors on unexpected element if (xml.hasError()) throw DataFileException("XML parsing error in SignedDistanceAboveSurface: " + xml.errorString()); bool ok = false; signedDistanceAboveSurface = text.toFloat(&ok); if (!ok) throw DataFileException("found non-numeric string in SignedDistanceAboveSurface: " + text); haveDist = true; } else { throw DataFileException("unexpected element in ProjectionBarycentric: " + name.toString()); } break; } default: break; } } if (xml.hasError()) throw DataFileException("XML parsing error in ProjectionBarycentric: " + xml.errorString()); CaretAssert(xml.isEndElement() && xml.name() == "ProjectionBarycentric"); if (!haveAreas || !haveNodes)//ignore missing distance? should always be zero for BorderFile anyway { throw DataFileException("SurfaceProjectionBarycentric element missing TriangleNodes and/or TriangleAreas"); } projectionValid = true; } workbench-1.1.1/src/Files/SurfaceProjectionBarycentric.h000066400000000000000000000075301255417355300233300ustar00rootroot00000000000000#ifndef __SURFACE_PROJECTION_BARYCENTRIC__H_ #define __SURFACE_PROJECTION_BARYCENTRIC__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "SurfaceProjection.h" class QXmlStreamReader; namespace caret { class SurfaceProjectionBarycentric : public SurfaceProjection { public: SurfaceProjectionBarycentric(); virtual ~SurfaceProjectionBarycentric(); SurfaceProjectionBarycentric(const SurfaceProjectionBarycentric& obj); SurfaceProjectionBarycentric& operator=(const SurfaceProjectionBarycentric& obj); bool operator==(const SurfaceProjectionBarycentric& rhs); bool operator!=(const SurfaceProjectionBarycentric& rhs) { return !(*this == rhs); } virtual AString toString() const; float getSignedDistanceAboveSurface() const; void setSignedDistanceAboveSurface(const float signedDistanceAboveSurface); const int32_t* getTriangleNodes() const; void setTriangleNodes(const int32_t triangleNodes[3]); const float* getTriangleAreas() const; void setTriangleAreas(const float triangleAreas[3]); int32_t getNodeWithLargestWeight() const; bool unprojectToSurface(const SurfaceFile& surfaceFile, float xyzOut[3], const float offsetFromSurface, const bool unprojectWithOffsetFromSurface) const; void reset(); bool isValid() const; void setDegenerate(const bool degenerate); bool isDegenerate() const; void setValid(const bool valid); void writeAsXML(XmlWriter& xmlWriter); void readBorderFileXML1(QXmlStreamReader& xml); static const AString XML_TAG_PROJECTION_BARYCENTRIC; static const AString XML_TAG_TRIANGLE_NODES; static const AString XML_TAG_TRIANGLE_AREAS; static const AString XML_TAG_SIGNED_DISTANCE_ABOVE_SURFACE; private: void copyHelperSurfaceProjectionBarycentric(const SurfaceProjectionBarycentric& obj); void resetAllValues(); int32_t triangleNodes[3]; float triangleAreas[3]; float signedDistanceAboveSurface; bool projectionValid; bool m_degenerate; }; #ifdef __SURFACE_PROJECTION_BARYCENTRIC_DECLARE__ const AString SurfaceProjectionBarycentric::XML_TAG_PROJECTION_BARYCENTRIC = "ProjectionBarycentric"; const AString SurfaceProjectionBarycentric::XML_TAG_TRIANGLE_NODES = "TriangleNodes"; const AString SurfaceProjectionBarycentric::XML_TAG_TRIANGLE_AREAS = "TriangleAreas"; const AString SurfaceProjectionBarycentric::XML_TAG_SIGNED_DISTANCE_ABOVE_SURFACE = "SignedDistanceAboveSurface"; #endif // __SURFACE_PROJECTION_BARYCENTRIC_DECLARE__ } // namespace #endif //__SURFACE_PROJECTION_BARYCENTRIC__H_ workbench-1.1.1/src/Files/SurfaceProjectionVanEssen.cxx000066400000000000000000000570131255417355300231610ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #define __SURFACE_PROJECTION_VAN_ESSEN_DECLARE__ #include "SurfaceProjectionVanEssen.h" #undef __SURFACE_PROJECTION_VAN_ESSEN_DECLARE__ #include "CaretAssert.h" #include "CaretLogger.h" #include "MathFunctions.h" #include "SurfaceFile.h" #include "TopologyHelper.h" #include "XmlWriter.h" using namespace caret; /** * \class caret::SurfaceProjectionVanEssen * \brief Maintains a VanEssen Projection that projects to an edge with offset. * */ /** * Constructor. */ SurfaceProjectionVanEssen::SurfaceProjectionVanEssen() : SurfaceProjection() { this->resetAllValues(); } /** * Destructor. */ SurfaceProjectionVanEssen::~SurfaceProjectionVanEssen() { } /** * Copy constructor. * @param obj * Object that is copied. */ SurfaceProjectionVanEssen::SurfaceProjectionVanEssen(const SurfaceProjectionVanEssen& obj) : SurfaceProjection(obj) { this->copyHelperSurfaceProjectionVanEssen(obj); } /** * Assignment operator. * @param obj * Data copied from obj to this. * @return * Reference to this object. */ SurfaceProjectionVanEssen& SurfaceProjectionVanEssen::operator=(const SurfaceProjectionVanEssen& obj) { if (this != &obj) { SurfaceProjection::operator=(obj); this->copyHelperSurfaceProjectionVanEssen(obj); } return *this; } bool SurfaceProjectionVanEssen::operator==(const SurfaceProjectionVanEssen& rhs) { if (projectionValid != rhs.projectionValid) return false; if (projectionValid) { for (int i = 0; i < 2; i++) { for (int j = 0; j < 3; j++) { for (int k = 0; k < 3; k++) { if (triAnatomical[i][j][k] != rhs.triAnatomical[i][j][k]) return false; } if (triVertices[i][j] != rhs.triVertices[i][j]) return false; if (vertexAnatomical[i][j] != rhs.vertexAnatomical[i][j]) return false; } if (vertex[i] != rhs.vertex[i]) return false; } for (int i = 0; i < 3; ++i) { if (posAnatomical[i] != rhs.posAnatomical[i]) return false; } if (thetaR != rhs.thetaR) return false; if (phiR != rhs.phiR) return false; if (fracRI != rhs.fracRI) return false; if (fracRJ != rhs.fracRJ) return false; } return true; } /** * Helps with copying an object of this type. * @param obj * Object that is copied. */ void SurfaceProjectionVanEssen::copyHelperSurfaceProjectionVanEssen(const SurfaceProjectionVanEssen& obj) { this->dR = obj.dR; for (int32_t i = 0; i < 2; i++) { for (int32_t j = 0; j < 3; j++) { for (int32_t k = 0; k < 3; k++) { this->triAnatomical[i][j][k] = obj.triAnatomical[i][j][k]; } this->triVertices[i][j] = obj.triVertices[i][j]; } this->vertex[i] = obj.vertex[i]; } this->vertexAnatomical[0][0] = this->vertexAnatomical[0][0]; this->vertexAnatomical[0][1] = this->vertexAnatomical[0][1]; this->vertexAnatomical[0][2] = this->vertexAnatomical[0][2]; this->vertexAnatomical[1][0] = this->vertexAnatomical[1][0]; this->vertexAnatomical[1][1] = this->vertexAnatomical[1][1]; this->vertexAnatomical[1][2] = this->vertexAnatomical[1][2]; this->posAnatomical[0] = this->posAnatomical[0]; this->posAnatomical[1] = this->posAnatomical[1]; this->posAnatomical[2] = this->posAnatomical[2]; this->thetaR = obj.thetaR; this->phiR = obj.phiR; this->fracRI = obj.fracRI; this->fracRJ = obj.fracRJ; this->projectionValid = obj.projectionValid; } /** * Unproject to the surface using 'this' projection. * * @param surfaceFile * Surface file used for unprojecting. * @param xyzOut * Output containing coordinate created by unprojecting. * @param offsetFromSurface * If 'unprojectWithOffsetFromSurface' is true, unprojected * position will be this distance above (negative=below) * the surface. * @param unprojectWithOffsetFromSurface * If true, ouput coordinate will be offset 'offsetFromSurface' * distance from the surface. */ bool SurfaceProjectionVanEssen::unprojectToSurface(const SurfaceFile& surfaceFile, float xyzOut[3], const float offsetFromSurface, const bool unprojectWithOffsetFromSurface) const { /* * Make sure projection surface number of nodes matches surface. */ if (this->projectionSurfaceNumberOfNodes > 0) { if (surfaceFile.getNumberOfNodes() != this->projectionSurfaceNumberOfNodes) { return false; } } const int is = 0; const int js = 1; const int32_t n1 = this->vertex[is]; const int32_t n2 = this->vertex[js]; CaretAssert(n1 < surfaceFile.getNumberOfNodes()); CaretAssert(n2 < surfaceFile.getNumberOfNodes()); /* * All nodes MUST have neighbors (connected) */ const TopologyHelper* topologyHelper = surfaceFile.getTopologyHelper().getPointer(); if ((topologyHelper->getNodeHasNeighbors(n1) == false) || (topologyHelper->getNodeHasNeighbors(n2) == false)) { return false; } float v[3]; float v_t1[3]; MathFunctions::subtractVectors(this->vertexAnatomical[js], this->vertexAnatomical[is], v); MathFunctions::subtractVectors(this->posAnatomical, this->vertexAnatomical[is], v_t1); float s_t2 = MathFunctions::dotProduct(v, v); float s_t3 = MathFunctions::dotProduct(v_t1, v); float QR[3]; for (int j = 0; j < 3; j++) { QR[j] = this->vertexAnatomical[is][j] + ((s_t3/s_t2) * v[j]); } const int pis = this->vertex[0]; const int pjs = this->vertex[1]; const float* posPIS = surfaceFile.getCoordinate(pis); const float* posPJS = surfaceFile.getCoordinate(pjs); if (unprojectWithOffsetFromSurface) { xyzOut[0] = (posPIS[0] + posPJS[0]) / 2.0f; xyzOut[1] = (posPIS[1] + posPJS[1]) / 2.0f; xyzOut[2] = (posPIS[2] + posPJS[2]) / 2.0f; if (offsetFromSurface != 0.0) { const float* normalI = surfaceFile.getNormalVector(pis); const float* normalJ = surfaceFile.getNormalVector(pjs); float avgNormal[3] = { ((normalI[0] + normalJ[0]) / 2.0), ((normalI[1] + normalJ[1]) / 2.0), ((normalI[2] + normalJ[2]) / 2.0), }; MathFunctions::normalizeVector(avgNormal); const float offsetX = avgNormal[0] * offsetFromSurface; const float offsetY = avgNormal[1] * offsetFromSurface; const float offsetZ = avgNormal[2] * offsetFromSurface; xyzOut[0] += offsetX; xyzOut[1] += offsetY; xyzOut[2] += offsetZ; } return true; } MathFunctions::subtractVectors(posPJS, posPIS, v); float QS[3]; if ((this->fracRI <= 1.0) && (this->fracRJ <= 1.0)) { for (int j = 0; j < 3; j++) { QS[j] = posPIS[j] + this->fracRI * v[j]; } } else if ((this->fracRI > 1.0) && (this->fracRI > this->fracRJ)) { MathFunctions::subtractVectors(QR, this->vertexAnatomical[js], v_t1); s_t2 = MathFunctions::vectorLength(v_t1); MathFunctions::subtractVectors(posPJS, posPIS, v); s_t3 = MathFunctions::vectorLength(v); for (int j = 0; j < 3; j++) { QS[j] = posPJS[j] + s_t2 * (v[j]/s_t3); } } else if ((this->fracRJ > 1.0) && (this->fracRJ > this->fracRI)) { MathFunctions::subtractVectors(QR, this->vertexAnatomical[is], v_t1); s_t2 = MathFunctions::vectorLength(v_t1); MathFunctions::subtractVectors(posPIS, posPJS, v); s_t3 = MathFunctions::vectorLength(v); for (int j = 0; j < 3; j++) { QS[j] = posPIS[j] + s_t2 * (v[j]/s_t3); } } else { CaretLogWarning(("VanEssen Projection: Unrecognized case for fracRI and fracRJ: " + AString::number(this->fracRI) + ", " + AString::number(this->fracRJ))); return false; } if ((this->triVertices[0][0] < 0) || (this->triVertices[1][0] < 0)) { return false; } float normalB[3]; MathFunctions::normalVector( surfaceFile.getCoordinate(this->triVertices[1][0]), surfaceFile.getCoordinate(this->triVertices[1][1]), surfaceFile.getCoordinate(this->triVertices[1][2]), normalB); float normalA[3]; MathFunctions::normalVector( surfaceFile.getCoordinate(this->triVertices[0][0]), surfaceFile.getCoordinate(this->triVertices[0][1]), surfaceFile.getCoordinate(this->triVertices[0][2]), normalA); s_t2 = MathFunctions::dotProduct(normalA, normalB); s_t2 = std::min(s_t2, 1.0f); // limit to <= 1.0 float phiS = (float)std::acos(s_t2); float thetaS = 0.0f; if (this->phiR > 0.0f) { thetaS = (this->thetaR / this->phiR) * phiS; } else { thetaS = 0.5f * phiS; } /* * Fixes unprojection when thetaR is zero NOT ALL CASES YET */ if (thetaR == 0.0) { // thetaS = M_PI / 2.0; // if (this->phiR > 0.0) { // thetaS = ((M_PI / 2.0) / this->phiR) * phiS; // } } MathFunctions::subtractVectors(posPJS, posPIS, v); MathFunctions::normalizeVector(v); float projection[3] = { 0.0f, 0.0f, 0.0f }; this->computeProjectionPoint(projection); MathFunctions::subtractVectors(projection, QR, v_t1); MathFunctions::normalizeVector(v_t1); MathFunctions::subtractVectors(this->vertexAnatomical[js], this->vertexAnatomical[is], v); MathFunctions::normalizeVector(v); float normalA_3D[3]; MathFunctions::normalVector(this->triAnatomical[0][0], this->triAnatomical[0][1], this->triAnatomical[0][2], normalA_3D); float v_t2[3]; MathFunctions::crossProduct(normalA_3D, v, v_t2); s_t3 = MathFunctions::dotProduct(v_t1, v_t2); float TS[3]; for (int k = 0; k < 3; k++) { TS[k] = QS[k] + (s_t3 * (this->dR * (float)std::sin(thetaS)) * v_t2[k]); } MathFunctions::subtractVectors(this->posAnatomical, projection, v); MathFunctions::normalizeVector(v); s_t3 = MathFunctions::dotProduct(normalA_3D, v); for (int i = 0; i < 3; i++) { xyzOut[i] = TS[i] + (this->dR * s_t3 * (float)std::cos(thetaS)) * normalA[i]; } return true; } /** * Compute a projection point?? * @param * Projection that is computed and set by this method. */ void SurfaceProjectionVanEssen::computeProjectionPoint(float projection[3]) const { float v[3]; MathFunctions::subtractVectors(this->triAnatomical[0][1], this->triAnatomical[0][0], v); float w[3]; MathFunctions::subtractVectors(this->triAnatomical[0][1], this->triAnatomical[0][2], w); float tnormal[3]; MathFunctions::crossProduct(w, v, tnormal); float a[3][3]; for (int k = 0; k < 3; k++) { a[0][k] = v[k]; a[1][k] = w[k]; a[2][k] = tnormal[k]; } float b[3]; b[0] = MathFunctions::dotProduct(v, this->posAnatomical); b[1] = MathFunctions::dotProduct(w, this->posAnatomical); b[2] = MathFunctions::dotProduct(tnormal, this->triAnatomical[0][2]); MathFunctions::vtkLinearSolve3x3(a, b, projection); } /** * @return dR */ float SurfaceProjectionVanEssen::getDR() const { return this->dR; } /** * Set dR * @param dR * New value. */ void SurfaceProjectionVanEssen::setDR(const float dR) { this->dR = dR; this->setModified(); } /** * @return thetaR */ float SurfaceProjectionVanEssen::getThetaR() const { return this->thetaR; } /** * Set thetaR * @param thetaR * New value. */ void SurfaceProjectionVanEssen::setThetaR(const float thetaR) { this->thetaR = thetaR; this->setModified(); } /** * @return phiR */ float SurfaceProjectionVanEssen::getPhiR() const { return this->phiR; } /** * Set phiR * @param phiR * New value. */ void SurfaceProjectionVanEssen::setPhiR(const float phiR) { this->phiR = phiR; this->setModified(); } /** * @return fracRI */ float SurfaceProjectionVanEssen::getFracRI() const { return this->fracRI; } /** * Set fracRI * @param fracRI * New value. */ void SurfaceProjectionVanEssen::setFracRI(const float fracRI) { this->fracRI = fracRI; this->setModified(); } /** * @return fracRJ */ float SurfaceProjectionVanEssen::getFracRJ() const { return this->fracRJ; } /** * Set fracRJ * @param fracRJ * New value. */ void SurfaceProjectionVanEssen::setFracRJ(const float fracRJ) { this->fracRJ = fracRJ; this->setModified(); } /** * Set triVertices * @param triVertices * New values. */ void SurfaceProjectionVanEssen::setTriVertices(const int32_t triVertices[2][3]) { for (int32_t i = 0; i < 2; i++) { for (int32_t j = 0; j < 3; j++) { this->triVertices[i][j] = triVertices[i][j]; } } this->setModified(); } /** * Set triVertices * @param indx1 * Index of vertices being set. * @param triVertices * New values. */ void SurfaceProjectionVanEssen::setTriVertices(const int32_t indx1, const int32_t vertices[3]) { CaretAssertArrayIndex(this->triVertices, 2, indx1); for (int32_t j = 0; j < 3; j++) { this->triVertices[indx1][j] = vertices[j]; } setModified(); } /** * Get triVertices * @param triVertices * Output values. */ void SurfaceProjectionVanEssen::getTriVertices(int32_t triVertices[2][3]) const { for (int32_t i = 0; i < 2; i++) { for (int32_t j = 0; j < 3; j++) { triVertices[i][j] = this->triVertices[i][j]; } } } /** * Set vertex * @param vertex * New values. */ void SurfaceProjectionVanEssen::setVertex(const int32_t vertex[2]) { this->vertex[0] = vertex[0]; this->vertex[1] = vertex[1]; this->setModified(); } /** * Set vertex * @param vertex * New values. */ void SurfaceProjectionVanEssen::setVertex(const int32_t indx1, const int32_t vertex) { CaretAssertArrayIndex(this->vertex, 2, indx1); this->vertex[indx1] = vertex; this->setModified(); } /** * Get vertex * @param vertex * Output values. */ void SurfaceProjectionVanEssen::getVertex(int32_t vertex[2]) const { vertex[0] = this->vertex[0]; vertex[1] = this->vertex[1]; } /** * Set triAnatomical * @param triAnatomical * New values. */ void SurfaceProjectionVanEssen::setTriAnatomical(const float triAnatomical[2][3][3]) { for (int32_t i = 0; i < 2; i++) { for (int32_t j = 0; j < 3; j++) { for (int32_t k = 0; k < 3; k++) { this->triAnatomical[i][j][k] = triAnatomical[i][j][k]; } } } this->setModified(); } /** * Set triAnatomical * @param triAnatomical * New values. */ void SurfaceProjectionVanEssen::setTriAnatomical(const int32_t indx1, const int32_t indx2, const float triAnatomical[3]) { CaretAssertArrayIndex(this->triAnatomical, 2, indx1); CaretAssertArrayIndex(this->triAnatomical, 3, indx2); for (int32_t k = 0; k < 3; k++) { this->triAnatomical[indx1][indx2][k] = triAnatomical[k]; } this->setModified(); } /** * Get triAnatomical * @param triAnatomical * Output values. */ void SurfaceProjectionVanEssen::getTriAnatomical(float triAnatomical[2][3][3]) const { for (int32_t i = 0; i < 2; i++) { for (int32_t j = 0; j < 3; j++) { for (int32_t k = 0; k < 3; k++) { triAnatomical[i][j][k] = this->triAnatomical[i][j][k]; } } } } /** * Set vertexAnatomical * @param vertexAnatomical * New values. */ void SurfaceProjectionVanEssen::setVertexAnatomical(const float vertexAnatomical[2][3]) { this->vertexAnatomical[0][0] = vertexAnatomical[0][0]; this->vertexAnatomical[0][1] = vertexAnatomical[0][1]; this->vertexAnatomical[0][2] = vertexAnatomical[0][2]; this->vertexAnatomical[1][0] = vertexAnatomical[1][0]; this->vertexAnatomical[1][1] = vertexAnatomical[1][1]; this->vertexAnatomical[1][2] = vertexAnatomical[1][2]; this->setModified(); } /** * Set vertexAnatomical * @param vertexAnatomical * New values. */ void SurfaceProjectionVanEssen::setVertexAnatomical(const int32_t indx1, const float vertexAnatomical[3]) { CaretAssertArrayIndex(this->vertexAnatomical, 2, indx1); this->vertexAnatomical[indx1][0] = vertexAnatomical[0]; this->vertexAnatomical[indx1][1] = vertexAnatomical[1]; this->vertexAnatomical[indx1][2] = vertexAnatomical[2]; this->setModified(); } /** * Get vertexAnatomical * @param vertexAnatomical * Output values. */ void SurfaceProjectionVanEssen::getVertexAnatomical(float vertexAnatomical[2][3]) const { vertexAnatomical[0][0] = this->vertexAnatomical[0][0]; vertexAnatomical[0][1] = this->vertexAnatomical[0][1]; vertexAnatomical[0][2] = this->vertexAnatomical[0][2]; vertexAnatomical[1][0] = this->vertexAnatomical[1][0]; vertexAnatomical[1][1] = this->vertexAnatomical[1][1]; vertexAnatomical[1][2] = this->vertexAnatomical[1][2]; } /** * Get posAnatomical * @param posAnatomical * Output values. */ void SurfaceProjectionVanEssen::getPosAnatomical(float posAnatomical[3]) const { posAnatomical[0] = this->posAnatomical[0]; posAnatomical[1] = this->posAnatomical[1]; posAnatomical[2] = this->posAnatomical[2]; } /** * Set posAnatomical * @param posAnatomical * New values. */ void SurfaceProjectionVanEssen::setPosAnatomical(const float posAnatomical[3]) { this->posAnatomical[0] = posAnatomical[0]; this->posAnatomical[1] = posAnatomical[1]; this->posAnatomical[2] = posAnatomical[2]; this->setModified(); } /** * Reset the surface projection to its initial state. */ void SurfaceProjectionVanEssen::reset() { this->resetAllValues(); } /** * Since reset overrides the 'super' class it should * never be called from a constructor. So, this * method does the actual reset, and since it does * not override a method from the 'super' class, it * may be called from this class' constructor. */ void SurfaceProjectionVanEssen::resetAllValues() { this->projectionValid = false; this->dR = 0.0; for (int32_t i = 0; i < 2; i++) { for (int32_t j = 0; j < 3; j++) { for (int32_t k = 0; k < 3; k++) { this->triAnatomical[i][j][k] = 0; } this->triVertices[i][j] = 0.0; } this->vertex[i] = 0.0; } this->vertexAnatomical[0][0] = 0.0; this->vertexAnatomical[0][1] = 0.0; this->vertexAnatomical[0][2] = 0.0; this->vertexAnatomical[1][0] = 0.0; this->vertexAnatomical[1][1] = 0.0; this->vertexAnatomical[1][2] = 0.0; this->posAnatomical[0] = 0.0; this->posAnatomical[1] = 0.0; this->posAnatomical[2] = 0.0; this->thetaR = 0.0; this->phiR = 0.0; this->fracRI = 0.0; this->fracRJ = 0.0; } /** * @return Is the projection valid? */ bool SurfaceProjectionVanEssen::isValid() const { return this->projectionValid; } /** * Set the validity of the projection. * @param valid * New validity status. */ void SurfaceProjectionVanEssen::setValid(const bool valid) { this->projectionValid = valid; setModified(); } /** * Write the projection to XML. * @param xmlWriter * The XML Writer. * @throw XmlException * If an error occurs. */ void SurfaceProjectionVanEssen::writeAsXML(XmlWriter& xmlWriter) { if (this->projectionValid) { xmlWriter.writeStartElement(XML_TAG_PROJECTION_VAN_ESSEN); xmlWriter.writeElementCharacters(XML_TAG_DR, this->dR); xmlWriter.writeElementCharacters(XML_TAG_TRI_ANATOMICAL, (float*)this->triAnatomical, 18); xmlWriter.writeElementCharacters(XML_TAG_THETA_R, this->thetaR); xmlWriter.writeElementCharacters(XML_TAG_PHI_R, this->phiR); xmlWriter.writeElementCharacters(XML_TAG_TRI_VERTICES, (int32_t*)this->triVertices, 6); xmlWriter.writeElementCharacters(XML_TAG_VERTEX, (int32_t*)this->vertex, 2); xmlWriter.writeElementCharacters(XML_TAG_VERTEX_ANATOMICAL, (float*)this->vertexAnatomical, 6); xmlWriter.writeElementCharacters(XML_TAG_POS_ANATOMICAL, this->posAnatomical, 3); xmlWriter.writeElementCharacters(XML_TAG_FRAC_RI, this->fracRI); xmlWriter.writeElementCharacters(XML_TAG_FRAC_RJ, this->fracRJ); xmlWriter.writeEndElement(); } } /** * Get a description of this object's content. * @return String describing this object's content. */ AString SurfaceProjectionVanEssen::toString() const { AString txt = SurfaceProjection::toString(); if (txt.isEmpty() == false) { txt += ", "; } txt += ("dR=" + AString::number(dR) + ", thetaR=" + AString::number(thetaR) + ", phiR=" + AString::number(phiR) + ", fracRI=" + AString::number(fracRI) + ", fracRJ=" + AString::number(fracRJ) + ", triVertices=" + AString::fromNumbers((int32_t*)triVertices, 6, ",") + ", vertex=" + AString::fromNumbers(vertex, 2, ",") + ", triAnatomical=" + AString::fromNumbers((float*)triAnatomical, 18, ",") + ", vertexAnatomical=" + AString::fromNumbers((float*)vertexAnatomical, 6, ",") + ", posAnatomical=" + AString::fromNumbers(posAnatomical, 3, ",") ); return txt; } workbench-1.1.1/src/Files/SurfaceProjectionVanEssen.h000066400000000000000000000136531255417355300226100ustar00rootroot00000000000000#ifndef __SURFACE_PROJECTION_VAN_ESSEN__H_ #define __SURFACE_PROJECTION_VAN_ESSEN__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "SurfaceProjection.h" namespace caret { class SurfaceProjectionVanEssen : public SurfaceProjection { public: SurfaceProjectionVanEssen(); virtual ~SurfaceProjectionVanEssen(); SurfaceProjectionVanEssen(const SurfaceProjectionVanEssen& obj); SurfaceProjectionVanEssen& operator=(const SurfaceProjectionVanEssen& obj); bool operator==(const SurfaceProjectionVanEssen& rhs); bool operator!=(const SurfaceProjectionVanEssen& rhs) { return !(*this == rhs); } bool unprojectToSurface(const SurfaceFile& surfaceFile, float xyzOut[3], const float offsetFromSurface, const bool unprojectWithOffsetFromSurface) const; float getDR() const; void setDR(const float dR); float getThetaR() const; void setThetaR(const float thetaR); float getPhiR() const; void setPhiR(const float phiR); float getFracRI() const; void setFracRI(const float fracRI); float getFracRJ() const; void setFracRJ(const float fracRJ); void setTriVertices(const int32_t triVertices[2][3]); void setTriVertices(const int32_t indx1, const int32_t vertices[3]); void getTriVertices(int32_t triVertices[2][3]) const; void setVertex(const int32_t vertex[2]); void setVertex(const int32_t indx1, const int32_t vertex); void getVertex(int32_t vertex[2]) const; void setTriAnatomical(const float triAnatomical[2][3][3]); void setTriAnatomical(const int32_t indx1, const int32_t indx2, const float anatomical[3]); void getTriAnatomical(float triAnatomical[2][3][3]) const; void setVertexAnatomical(const float vertexAnatomical[2][3]); void setVertexAnatomical(const int32_t indx1, const float anatomical[3]); void getVertexAnatomical(float vertexAnatomical[2][3]) const; void getPosAnatomical(float posAnatomical[3]) const; void setPosAnatomical(const float posAnatomical[3]); void reset(); bool isValid() const; void setValid(const bool valid); virtual AString toString() const; void writeAsXML(XmlWriter& xmlWriter); static const AString XML_TAG_PROJECTION_VAN_ESSEN; static const AString XML_TAG_DR; static const AString XML_TAG_TRI_ANATOMICAL; static const AString XML_TAG_THETA_R; static const AString XML_TAG_PHI_R; static const AString XML_TAG_TRI_VERTICES; static const AString XML_TAG_VERTEX; static const AString XML_TAG_VERTEX_ANATOMICAL; static const AString XML_TAG_POS_ANATOMICAL; static const AString XML_TAG_FRAC_RI; static const AString XML_TAG_FRAC_RJ; private: void copyHelperSurfaceProjectionVanEssen(const SurfaceProjectionVanEssen& obj); void computeProjectionPoint(float projection[3]) const; void resetAllValues(); float dR; float thetaR; float phiR; float fracRI; float fracRJ; int32_t triVertices[2][3]; int32_t vertex[2]; float triAnatomical[2][3][3]; float vertexAnatomical[2][3]; float posAnatomical[3]; bool projectionValid; }; #ifdef __SURFACE_PROJECTION_VAN_ESSEN_DECLARE__ const AString SurfaceProjectionVanEssen::XML_TAG_PROJECTION_VAN_ESSEN = "VanEssenProjection"; const AString SurfaceProjectionVanEssen::XML_TAG_DR = "DR"; const AString SurfaceProjectionVanEssen::XML_TAG_TRI_ANATOMICAL = "TriAnatomical"; const AString SurfaceProjectionVanEssen::XML_TAG_THETA_R = "ThetaR"; const AString SurfaceProjectionVanEssen::XML_TAG_PHI_R = "PhiR"; const AString SurfaceProjectionVanEssen::XML_TAG_TRI_VERTICES = "TriVertices"; const AString SurfaceProjectionVanEssen::XML_TAG_VERTEX = "Vertex"; const AString SurfaceProjectionVanEssen::XML_TAG_VERTEX_ANATOMICAL = "VertexAnatomical"; const AString SurfaceProjectionVanEssen::XML_TAG_POS_ANATOMICAL = "PosAnatomical"; const AString SurfaceProjectionVanEssen::XML_TAG_FRAC_RI = "FracRI"; const AString SurfaceProjectionVanEssen::XML_TAG_FRAC_RJ = "FracRJ"; #endif // __SURFACE_PROJECTION_VAN_ESSEN_DECLARE__ } // namespace #endif //__SURFACE_PROJECTION_VAN_ESSEN__H_ workbench-1.1.1/src/Files/SurfaceProjector.cxx000066400000000000000000001642031255417355300213510ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #define __SURFACE_PROJECTOR_DEFINE__ #include "SurfaceProjector.h" #undef __SURFACE_PROJECTOR_DEFINE__ #include "CaretLogger.h" #include "FociFile.h" #include "Focus.h" #include "MathFunctions.h" #include "SignedDistanceHelper.h" #include "SurfaceFile.h" #include "SurfaceProjectedItem.h" #include "SurfaceProjectionBarycentric.h" #include "SurfaceProjectionVanEssen.h" #include "TopologyHelper.h" using namespace caret; /** * \class caret::SurfaceProjector * \brief Project points to a surface. */ /** * Constructor for projection to a given surface. * * @param surfaceFile * Surface to which projection takes place. For proper * projection (particularly flat and spherical) surfaces, it is important * that the surface's type is correctly set. */ SurfaceProjector::SurfaceProjector(const SurfaceFile* surfaceFile) : CaretObject(), m_surfaceFileLeft(NULL), m_surfaceFileRight(NULL), m_surfaceFileCerebellum(NULL), m_mode(MODE_SURFACES) { CaretAssert(surfaceFile); m_surfaceFiles.push_back(surfaceFile); initializeMembersSurfaceProjector(); } /** * Constructor for projection to closest of a group of surfaces. * * @param surfaceFiles * Vector of Surfaces to which projection takes place. For proper * projection (particularly flat and spherical) surfaces, it is important * that the surface's type is correctly set. */ SurfaceProjector::SurfaceProjector(const std::vector& surfaceFiles) : CaretObject(), m_surfaceFileLeft(NULL), m_surfaceFileRight(NULL), m_surfaceFileCerebellum(NULL), m_mode(MODE_SURFACES) { const int32_t numberOfSurfaces = static_cast(surfaceFiles.size()); for (int32_t i = 0; i < numberOfSurfaces; i++) { m_surfaceFiles.push_back(surfaceFiles[i]); } initializeMembersSurfaceProjector(); } /** * Constructor that allows ambiguous projections for items between a cortical * surface and the cerebellum. Items that have a positive X-coordinate * are projected to the right cortex or the cerebellum. Items that have * a negative X-coordinate are projected to the left cortex or the cerebellum. * Some of the surface files may be NULL but at least one must be valid. * * @param leftSurfaceFile * Surface file for left cortex. * @param rightSurfaceFile * Surface file for right cortex. * @param cerebellumSurfaceFile * Surface file for cerebellum cortex. */ SurfaceProjector::SurfaceProjector(const SurfaceFile* leftSurfaceFile, const SurfaceFile* rightSurfaceFile, const SurfaceFile* cerebellumSurfaceFile) : CaretObject(), m_surfaceFileLeft(leftSurfaceFile), m_surfaceFileRight(rightSurfaceFile), m_surfaceFileCerebellum(cerebellumSurfaceFile), m_mode(MODE_LEFT_RIGHT_CEREBELLUM) { } /** * Destructor */ SurfaceProjector::~SurfaceProjector() { } /** * Initialize members of this instance. */ void SurfaceProjector::initializeMembersSurfaceProjector() { m_surfaceOffset = 0.0; m_surfaceOffsetValid = false; /* * Validate when logger is set at a specified level * If the level is changed, all need to change level * where validation message is logged. */ m_validateFlag = CaretLogger::getLogger()->isFine(); m_validateItemName = ""; } /** * Set the desired offset of projected items from the surface-> * * @param surfaceOffset - distance above the surface-> * */ void SurfaceProjector::setSurfaceOffset(const float surfaceOffset) { m_surfaceOffset = surfaceOffset; m_surfaceOffsetValid = true; } /** * Project all foci in a foci file. * @param fociFile * The foci file. * @throws SurfaceProjectorException * If projecting an item failed. */ void SurfaceProjector::projectFociFile(FociFile* fociFile) { CaretAssert(fociFile); const int32_t numberOfFoci = fociFile->getNumberOfFoci(); AString errorMessage = ""; for (int32_t i = 0; i < numberOfFoci; i++) { Focus* focus = fociFile->getFocus(i); try { if (m_validateFlag) { m_validateItemName = ("Focus " + AString::number(i) + ", " + focus->getName()); } projectFocus(i, focus); } catch (const SurfaceProjectorException& spe) { if (errorMessage.isEmpty() == false) { errorMessage += "\n"; } errorMessage += (focus->getName() + ", index=" + AString::number(i) + ": " + spe.whatString()); } } if (errorMessage.isEmpty() == false) { throw SurfaceProjectorException(errorMessage); } } /** * Project a focus. * @param focusIndex * Index of the focus (negative indicates no index) * @param focus * The focus. * @throws SurfaceProjectorException * If projecting an item failed. */ void SurfaceProjector::projectFocus(const int32_t focusIndex, Focus* focus) { const int32_t numberOfProjections = focus->getNumberOfProjections(); CaretAssert(numberOfProjections > 0); if (numberOfProjections < 0) { throw SurfaceProjectorException("Focus has no projections, no stereotaxic coordinate."); } focus->removeExtraProjections(); SurfaceProjectedItem* spi = focus->getProjection(0); SurfaceProjectedItem* spiSecond = NULL; if (m_surfaceFileCerebellum != NULL) { spiSecond = new SurfaceProjectedItem(); } m_allowEdgeProjection = true; projectItem(spi, spiSecond); if (spiSecond != NULL) { if (spiSecond->hasValidProjection()) { focus->addProjection(spiSecond); } else { delete spiSecond; spiSecond = NULL; } } if (m_projectionWarning.isEmpty() == false) { AString msg = ("Focus: Name=" + focus->getName()); if (focusIndex >= 0) { msg += (", Index=" + AString::number(focusIndex)); } msg += (": " + m_projectionWarning); CaretLogWarning(msg);; } } /** * Project to the surface(s) triangles (barycentric projection) * * @param spi * Item that is to be projected. Its contents will be * updated to reflect the projection. This item's stereotaxic coordinate * is used for the projection point. * * @throws SurfaceProjectorException * If projecting an item failed. */ void SurfaceProjector::projectItemToTriangle(SurfaceProjectedItem* spi) { CaretAssert(spi); m_allowEdgeProjection = false; projectItem(spi, NULL); } /** * Project to the surface(s) triangles (barycentric projection) * or edges (van-essen projection). * * @param spi * Item that is to be projected. Its contents will be * updated to reflect the projection. This item's stereotaxic coordinate * is used for the projection point. * * @throws SurfaceProjectorException * If projecting an item failed. */ void SurfaceProjector::projectItemToTriangleOrEdge(SurfaceProjectedItem* spi) { CaretAssert(spi); m_allowEdgeProjection = true; projectItem(spi, NULL); } /** * Project to the appropriate surface(s). * * @param spi * Item that is to be projected. Its contents will be * updated to reflect the projection. This item's stereotaxic coordinate * is used for the projection point. * @param secondSpi * For left/right/cerebellum projections, if there is ambiguity for an * item (between cortex and cerebellum), this projection will be set if * it is not NULL. * @throws SurfaceProjectorException * If projecting an item failed. */ void SurfaceProjector::projectItem(SurfaceProjectedItem* spi, SurfaceProjectedItem* secondSpi) { m_projectionWarning = ""; /* * Get position of item. */ float xyz[3]; if (spi->isStereotaxicXYZValid() == false) { throw SurfaceProjectorException( "Stereotaxic position is invalid, cannot project."); } spi->getStereotaxicXYZ(xyz); if (secondSpi != NULL) { secondSpi->setStereotaxicXYZ(xyz); } switch (m_mode) { case MODE_LEFT_RIGHT_CEREBELLUM: { if (xyz[0] < 0.0) { if (m_surfaceFileLeft != NULL) { if (m_surfaceFileCerebellum != NULL) { const float leftDist = m_surfaceFileLeft->getSignedDistanceHelper()->dist(xyz, SignedDistanceHelper::NORMALS); const float cerebellumDist = m_surfaceFileCerebellum->getSignedDistanceHelper()->dist(xyz, SignedDistanceHelper::NORMALS); float ratio = 1000000.0; if (cerebellumDist != 0.0) { ratio = leftDist / cerebellumDist; } if (ratio > s_cerebellumSurfaceCutoff) { projectItemToSurfaceFile(m_surfaceFileCerebellum, spi); } else if (ratio < s_corticalSurfaceCutoff) { projectItemToSurfaceFile(m_surfaceFileLeft, spi); } else { projectItemToSurfaceFile(m_surfaceFileLeft, spi); if (secondSpi != NULL) { projectItemToSurfaceFile(m_surfaceFileCerebellum, secondSpi); } } } else { projectItemToSurfaceFile(m_surfaceFileLeft, spi); } } else if (m_surfaceFileCerebellum != NULL) { projectItemToSurfaceFile(m_surfaceFileCerebellum, spi); } } else { if (m_surfaceFileRight != NULL) { if (m_surfaceFileCerebellum != NULL) { const float rightDist = m_surfaceFileRight->getSignedDistanceHelper()->dist(xyz, SignedDistanceHelper::NORMALS); const float cerebellumDist = m_surfaceFileCerebellum->getSignedDistanceHelper()->dist(xyz, SignedDistanceHelper::NORMALS); float ratio = 1000000.0; if (cerebellumDist != 0.0) { ratio = rightDist / cerebellumDist; } if (ratio > s_cerebellumSurfaceCutoff) { projectItemToSurfaceFile(m_surfaceFileCerebellum, spi); } else if (ratio < s_corticalSurfaceCutoff) { projectItemToSurfaceFile(m_surfaceFileRight, spi); } else { projectItemToSurfaceFile(m_surfaceFileRight, spi); if (secondSpi != NULL) { projectItemToSurfaceFile(m_surfaceFileCerebellum, secondSpi); } } } else { projectItemToSurfaceFile(m_surfaceFileRight, spi); } } else if (m_surfaceFileCerebellum != NULL) { projectItemToSurfaceFile(m_surfaceFileCerebellum, spi); } } } break; case MODE_SURFACES: { const int32_t numberOfSurfaceFiles = static_cast(m_surfaceFiles.size()); if (numberOfSurfaceFiles <= 0) { throw SurfaceProjectorException("No surface for projection!"); } int32_t nearestSurfaceIndex = -1; if (numberOfSurfaceFiles == 1) { nearestSurfaceIndex = 0; } else { /* * Find surface closest to node. */ float nearestDistance = std::numeric_limits::max(); for (int32_t i = 0; i < numberOfSurfaceFiles; i++) { const SurfaceFile* sf = m_surfaceFiles[i]; CaretPointer sdh = sf->getSignedDistanceHelper(); const float absDist = std::fabs(sdh->dist(xyz, SignedDistanceHelper::NORMALS)); if (absDist < nearestDistance) { nearestDistance = absDist; nearestSurfaceIndex = i; } } } if (nearestSurfaceIndex < 0) { throw SurfaceProjectorException("Failed to find surface for projection."); } const SurfaceFile* projectionSurfaceFile = m_surfaceFiles[nearestSurfaceIndex]; projectItemToSurfaceFile(projectionSurfaceFile, spi); } break; } } /** * Project to the given surface * @param surfaceFile * Surface to which triangle projection is made. * @param spi * Item that is to be projected. Its contents will be * updated to reflect the projection. This items XYZ coordinate * is used for the projection point. * * @throws SurfaceProjectorException If projecting an item * failed. */ void SurfaceProjector::projectItemToSurfaceFile(const SurfaceFile* surfaceFile, SurfaceProjectedItem* spi) { float originalXYZ[3]; spi->getStereotaxicXYZ(originalXYZ); float xyz[3] = { originalXYZ[0], originalXYZ[1], originalXYZ[2] }; float projXYZ[3]; float stereoXYZ[3]; float distanceError = 0.0; bool projectionValid = false; projectToSurface(surfaceFile, xyz, spi); if (spi->getBarycentricProjection()->isValid() || spi->getVanEssenProjection()->isValid()) { spi->getProjectedPosition(*surfaceFile, projXYZ, false); spi->getStereotaxicXYZ(stereoXYZ); distanceError = MathFunctions::distance3D(projXYZ, stereoXYZ); projectionValid = true; } if (distanceError > s_projectionDistanceError) { bool perturbIfError = true; if (perturbIfError) { /* * Initialize with first try */ float bestXYZ[3] = { xyz[0], xyz[1], xyz[2] }; float bestDistance = distanceError; bool bestValid = true; const float originalDistanceError = distanceError; for (int32_t iTry = 0; iTry < 10; iTry++) { const float randomZeroToOne = ((float)std::rand()) / ((float)RAND_MAX); const float randomPlusMinusOneHalf = randomZeroToOne - 0.5; const float moveLittleBit = randomPlusMinusOneHalf * 0.5; xyz[0] = originalXYZ[0] + moveLittleBit; xyz[1] = originalXYZ[1] + moveLittleBit; xyz[2] = originalXYZ[2] + moveLittleBit; SurfaceProjectedItem spiTest; spiTest.setStereotaxicXYZ(originalXYZ); projectToSurface(surfaceFile, xyz, &spiTest); if (spiTest.getBarycentricProjection()->isValid() || spiTest.getVanEssenProjection()->isValid()) { spiTest.getProjectedPosition(*surfaceFile, projXYZ, false); spiTest.getStereotaxicXYZ(stereoXYZ); distanceError = MathFunctions::distance3D(projXYZ, stereoXYZ); projectionValid = true; // std::cout << "Moved from original (" // << AString::fromNumbers(originalXYZ, 3, ",") // << ") to (" // << AString::fromNumbers(xyz, 3, ",") // << ") distance error now=" // << distanceError // << std::endl; if (distanceError < bestDistance) { bestXYZ[0] = xyz[0]; bestXYZ[1] = xyz[1]; bestXYZ[2] = xyz[2]; bestDistance = distanceError; bestValid = true; } } } if (bestValid) { projectToSurface(surfaceFile, bestXYZ, spi); if (spi->getBarycentricProjection()->isValid() || spi->getVanEssenProjection()->isValid()) { spi->getProjectedPosition(*surfaceFile, projXYZ, false); spi->getStereotaxicXYZ(stereoXYZ); distanceError = MathFunctions::distance3D(projXYZ, stereoXYZ); m_projectionWarning += ("Was moved due to projection error from (" + AString::fromNumbers(originalXYZ, 3, ",") + ") to (" + AString::fromNumbers(bestXYZ, 3, ",") + ") with distance error reduced from " + AString::number(originalDistanceError) + " to " + AString::number(distanceError)); } } } } if (m_validateFlag == false) { if (distanceError > s_projectionDistanceError) { m_projectionWarning += ("Projection Warning: Error=" + AString::number(distanceError) + "mm, Stereotaxic=(" + AString::fromNumbers(stereoXYZ, 3, ",") + "), Projected=(" + AString::fromNumbers(projXYZ, 3, ",") + ")"); } } if (m_validateFlag) { bool errorFlag = false; AString validateString; if (projectionValid) { AString projTypeString = "Unprojected"; AString projInfo; if (spi->getBarycentricProjection()->isValid()) { projTypeString = "Triangle"; if (spi->getBarycentricProjection()->isDegenerate()) { projTypeString = "-degenerate"; } projInfo = spi->getBarycentricProjection()->toString(); } else if (spi->getVanEssenProjection()->isValid()) { projTypeString = "Edge"; projInfo = spi->getVanEssenProjection()->toString(); } AString matchString = ""; if (distanceError > 0.001) { matchString = " FAILED *************************"; errorFlag = true; } if (validateString.isEmpty() == false) { validateString += "\n"; } if (spi->getStructure() == StructureEnum::CEREBELLUM) { if (spi->getVanEssenProjection()->isValid()) { //errorFlag = true; } } validateString += (m_validateItemName + ": projType=" + projTypeString + ": structure=" + StructureEnum::toName(spi->getStructure()) + ", stereoPos=(" + AString::fromNumbers(stereoXYZ, 3, ",") + "), projPos=(" + AString::fromNumbers(projXYZ, 3, ",") + "): stereo/proj positions differ by " + AString::number(distanceError, 'f', 3) + matchString + "\n"); if (projInfo.isEmpty() == false) { validateString += (projInfo + "\n"); } } else { validateString += (m_validateItemName + ": failed to project\n"); errorFlag = true; } if (errorFlag && (validateString.isEmpty() == false)) { CaretLogFine(validateString); } } } /** * Project to the surface * @param surfaceFile * Surface to which triangle projection is made. * @param xyz * Coordinate that is being projected. * @param spi * Item that is to be projected. Its contents will be * updated to reflect the projection. This items XYZ coordinate * is used for the projection point. * * @throws SurfaceProjectorException If projecting an item * failed. */ void SurfaceProjector::projectToSurface(const SurfaceFile* surfaceFile, const float xyz[3], SurfaceProjectedItem* spi) { // // If needed, create node locator // if (surfaceFile->getNumberOfNodes() <= 0) { throw SurfaceProjectorException("Surface file contains no nodes: " + surfaceFile->getFileNameNoPath()); } if (surfaceFile->getNumberOfTriangles() <= 0) { throw SurfaceProjectorException("Surface topology contains no triangles: " + surfaceFile->getFileNameNoPath()); } m_sphericalSurfaceRadius = 0.0; m_surfaceTypeHint = SURFACE_HINT_THREE_DIMENSIONAL; switch (surfaceFile->getSurfaceType()) { case SurfaceTypeEnum::FLAT: m_surfaceTypeHint = SURFACE_HINT_FLAT; break; case SurfaceTypeEnum::SPHERICAL: m_surfaceTypeHint = SURFACE_HINT_SPHERE; m_sphericalSurfaceRadius = surfaceFile->getSphericalRadius(); break; default: m_surfaceTypeHint = SURFACE_HINT_THREE_DIMENSIONAL; break; } // // Default to invalid projection // SurfaceProjectionBarycentric* baryProj = spi->getBarycentricProjection(); baryProj->setValid(false); SurfaceProjectionVanEssen* vanEssenProj = spi->getVanEssenProjection(); vanEssenProj->setValid(false); /* * Determine if projected to node/edge/triangle */ ProjectionLocation projectionLocation; getProjectionLocation(surfaceFile, xyz, projectionLocation); if (m_validateFlag) { if (m_validateItemName.isEmpty() == false) { m_validateItemName += "\n"; } m_validateItemName += ("ORIGINAL: " + projectionLocation.toString(surfaceFile)); } /* * If projected to edge and edge projection allowed */ if (m_allowEdgeProjection && (projectionLocation.m_type == ProjectionLocation::EDGE)) { vanEssenProj->setPosAnatomical(xyz); projectWithVanEssenAlgorithm(surfaceFile, projectionLocation, vanEssenProj); if (vanEssenProj->isValid() == false) { throw SurfaceProjectorException("Edge projection failed."); } } else { /* * Convert the projection to a triangle projection. */ if ((projectionLocation.m_type == ProjectionLocation::EDGE) || (projectionLocation.m_type == ProjectionLocation::NODE)) { convertToTriangleProjection(surfaceFile, projectionLocation); if (m_validateFlag) { if (m_validateItemName.isEmpty() == false) { m_validateItemName += "\n"; } m_validateItemName += ("ALTERED: " + projectionLocation.toString(surfaceFile)); } } projectToSurfaceTriangle(surfaceFile, projectionLocation, spi->getBarycentricProjection()); if (baryProj->isValid() == false) { throw SurfaceProjectorException("Triangle projection failed."); } } spi->setStructure(surfaceFile->getStructure()); } /** * Convert an edge or node projection to a triangle projection which * may become a degenerate triangle projection. * @param surfaceFile * Surface to which triangle projection is made. * @param projectionLocation * Contains informaiton about item on the surface file. * * @throws SurfaceProjectorException If projecting an item * failed. */ void SurfaceProjector::convertToTriangleProjection(const SurfaceFile* surfaceFile, ProjectionLocation& projectionLocation) { bool doIt = false; switch (projectionLocation.m_type) { case ProjectionLocation::EDGE: doIt = true; break; case ProjectionLocation::INVALID: break; case ProjectionLocation::NODE: doIt = true; break; case ProjectionLocation::TRIANGLE: break; } if (doIt) { SurfaceProjectionBarycentric baryProj; checkItemInTriangle(surfaceFile, projectionLocation.m_triangleIndices[0], projectionLocation.m_pointXYZ, s_extremeTriangleAreaTolerance, &baryProj); if (baryProj.isValid()) { projectionLocation.m_type = ProjectionLocation::TRIANGLE; const int32_t* nodes = baryProj.getTriangleNodes(); projectionLocation.m_nodes[0] = nodes[0]; projectionLocation.m_nodes[1] = nodes[1]; projectionLocation.m_nodes[2] = nodes[2]; const float* areas = baryProj.getTriangleAreas(); projectionLocation.m_weights[0] = areas[0]; projectionLocation.m_weights[1] = areas[1]; projectionLocation.m_weights[2] = areas[2]; projectionLocation.m_signedDistance = baryProj.getSignedDistanceAboveSurface(); projectionLocation.m_absoluteDistance = std::fabs(projectionLocation.m_signedDistance); } else { throw SurfaceProjectorException("Failed to convert from edge/node projection to triangle projection"); } } } /** * Project a coordinate to the surface using a barycentric projection. * @param surfaceFile * Surface to which triangle projection is made. * @param projectionLocation * Contains informaiton about item on the surface file. * @param baryProj * The barycentric projection that will be updated. * * @throws SurfaceProjectorException If projecting an item * failed. * */ void SurfaceProjector::projectToSurfaceTriangle(const SurfaceFile* surfaceFile, const ProjectionLocation& projectionLocation, SurfaceProjectionBarycentric* baryProj) { /* * At one time, there was a need to 'perturb' (slightly move) the * surface, probably for registration. */ projectToSurfaceTriangleAux(surfaceFile, projectionLocation, baryProj); if (baryProj->isValid()) { if (m_surfaceOffsetValid) { baryProj->setSignedDistanceAboveSurface(m_surfaceOffset); } } } /** * Get the location on the surface nearest the given coordinate. * @param surfaceFile * Surface for location. * @param xyz * The coordinate. * @param projectionLocation * Output containing location on surface information. */ void SurfaceProjector::getProjectionLocation(const SurfaceFile* surfaceFile, const float xyz[3], ProjectionLocation& projectionLocation) const { /* * Find nearest point on the surface */ CaretPointer sdh = surfaceFile->getSignedDistanceHelper(); BarycentricInfo baryInfo; sdh->barycentricWeights(xyz, baryInfo); int32_t nearestNode = -1; float maxWeight = -1; std::vector nodes; std::vector weights; for (int32_t i = 0; i < 3; i++) { if (baryInfo.baryWeights[i] > 0.0) { nodes.push_back(baryInfo.nodes[i]); const float w = baryInfo.baryWeights[i]; weights.push_back(w); if (w > maxWeight) { nearestNode = baryInfo.nodes[i]; maxWeight = w; } } } if (nearestNode < 0) { throw SurfaceProjectorException("ERROR: Nearest node is invalid"); } float signedDistance = 0.0; switch (baryInfo.type) { case BarycentricInfo::NODE: { if (nodes.size() != 1) { throw SurfaceProjectorException("ERROR: project to node number of weights incorrect=" + AString::number(nodes.size())); } else { const float* nodeNormal = surfaceFile->getNormalVector(nodes[0]); const float* c1 = surfaceFile->getCoordinate(nodes[0]); const float aboveBelowPlane = MathFunctions::signedDistanceFromPlane(nodeNormal, c1, xyz); const float signValue = ((aboveBelowPlane > 0.0) ? 1.0 : -1.0); signedDistance = (MathFunctions::distance3D(xyz, c1) * signValue); } } break; case BarycentricInfo::EDGE: { if (nodes.size() != 2) { throw SurfaceProjectorException("ERROR: project to edge number weights incorrect=" + AString::number(nodes.size())); } else { const float* n1 = surfaceFile->getNormalVector(nodes[0]); const float* n2 = surfaceFile->getNormalVector(nodes[1]); float avgNormal[3]; MathFunctions::addVectors(n1, n2, avgNormal); MathFunctions::normalizeVector(avgNormal); const float* c1 = surfaceFile->getCoordinate(nodes[0]); const float* c2 = surfaceFile->getCoordinate(nodes[1]); MathFunctions::distanceToLine3D(c1, c2, xyz); const float aboveBelowPlane = MathFunctions::signedDistanceFromPlane(avgNormal, baryInfo.point, xyz); const float signValue = ((aboveBelowPlane > 0.0) ? 1.0 : -1.0); signedDistance = (MathFunctions::distance3D(xyz, baryInfo.point) * signValue); } } break; case BarycentricInfo::TRIANGLE: { if (nodes.size() != 3) { throw SurfaceProjectorException("ERROR: project to triangle number of weights incorrect=" + AString::number(nodes.size())); } else { float triangleNormal[3]; surfaceFile->getTriangleNormalVector(baryInfo.triangle, triangleNormal); const float* c1 = surfaceFile->getCoordinate(nodes[0]); signedDistance = MathFunctions::signedDistanceFromPlane(triangleNormal, c1, xyz); } } break; } /* * Topology helper */ CaretPointer topologyHelper = surfaceFile->getTopologyHelper(); /* * Triangle(s) near projection point on surface */ std::vector nearbyTriangles; /* * Load up the projection information. */ projectionLocation.m_type = ProjectionLocation::INVALID; switch (baryInfo.type) { case BarycentricInfo::NODE: { projectionLocation.m_type = ProjectionLocation::NODE; int32_t numTriangles = 0; const int32_t* nodesTriangles = topologyHelper->getNodeTiles(nodes[0], numTriangles); /* * Make sure nearest triangle is first and * keep triangles ordering */ int32_t iStart = 0; for (int32_t i = 0; i < numTriangles; i++) { if (nodesTriangles[i] == baryInfo.triangle) { iStart = i; break; } } if (iStart < 0) { throw SurfaceProjectorException("PROGRAM ERROR: Nearest triangle node found to be associated with nearest node"); } for (int32_t i = iStart; i < numTriangles; i++) { nearbyTriangles.push_back(nodesTriangles[i]); } for (int32_t i = 0; i < iStart; i++) { nearbyTriangles.push_back(nodesTriangles[i]); } } break; case BarycentricInfo::EDGE: { projectionLocation.m_type = ProjectionLocation::EDGE; const int32_t oppositeTriangle = surfaceFile->getTriangleThatSharesEdge(nodes[0], nodes[1], baryInfo.triangle); nearbyTriangles.push_back(baryInfo.triangle); nearbyTriangles.push_back(oppositeTriangle); } break; case BarycentricInfo::TRIANGLE: projectionLocation.m_type = ProjectionLocation::TRIANGLE; nearbyTriangles.push_back(baryInfo.triangle); break; } for (int32_t i = 0; i < 3; i++) { projectionLocation.m_pointXYZ[i] = xyz[i]; projectionLocation.m_surfaceXYZ[i] = baryInfo.point[i]; if (i < static_cast(nodes.size())) { projectionLocation.m_nodes[i] = nodes[i]; projectionLocation.m_weights[i] = weights[i]; } else { projectionLocation.m_nodes[i] = -1; projectionLocation.m_weights[i] = 0.0; } } projectionLocation.m_numberOfTriangles = static_cast(nearbyTriangles.size()); projectionLocation.m_triangleIndices = new int32_t[projectionLocation.m_numberOfTriangles]; for (int32_t i = 0; i < projectionLocation.m_numberOfTriangles; i++) { projectionLocation.m_triangleIndices[i] = nearbyTriangles[i]; } projectionLocation.m_absoluteDistance = baryInfo.absDistance; projectionLocation.m_signedDistance = signedDistance; projectionLocation.m_nearestNode = nearestNode; AString distErrorMessage = ""; float distError = std::fabs(signedDistance) - baryInfo.absDistance; if (distError > 0.01) { throw SurfaceProjectorException("ERROR: signed/abs distance mismatch: " + projectionLocation.toString(surfaceFile)); } } /** * Project a coordinate to the surface * @param surfaceFile * Surface file to which item is projected. * @param projectionLocation * Contains informaiton about item on the surface file. * @param baryProj * The barycentric projection that will be set. * @return * The node nearest the coordinate. * @throws SurfaceProjectorException * If projecting an item failed. */ int32_t SurfaceProjector::projectToSurfaceTriangleAux(const SurfaceFile* surfaceFile, const ProjectionLocation& projectionLocation, SurfaceProjectionBarycentric* baryProj) { /* * Set the projection. */ baryProj->setTriangleAreas(projectionLocation.m_weights); baryProj->setTriangleNodes(projectionLocation.m_nodes); baryProj->setProjectionSurfaceNumberOfNodes(surfaceFile->getNumberOfNodes()); if (m_surfaceOffsetValid) { baryProj->setSignedDistanceAboveSurface(m_surfaceOffset); } else { baryProj->setSignedDistanceAboveSurface(projectionLocation.m_signedDistance); } baryProj->setValid(true); return projectionLocation.m_nearestNode; } /** * See if the coordinate is within the triangle. * @param surfaceFile * Surface file to which item is projected. * @param triangleNumber * Triangle to check. * @param xyz * The coordinate * @param degenerateTolerance * If the point is outside the triangle, an output area will be negative. * In most cases use zero or a negative value (-0.01) very near zero. A very * negative value can be used to allow degenerate cases. * @param baryProj * Barycentric projection into triangle. */ void SurfaceProjector::checkItemInTriangle(const SurfaceFile* surfaceFile, const int32_t triangleNumber, const float xyz[3], const float degenerateTolerance, SurfaceProjectionBarycentric* baryProj) { // // Vertices of the triangle // const int32_t* tn = surfaceFile->getTriangle(triangleNumber); const float* v1 = surfaceFile->getCoordinate(tn[0]); const float* v2 = surfaceFile->getCoordinate(tn[1]); const float* v3 = surfaceFile->getCoordinate(tn[2]); // // coordinate that may be pushed to a plane depending upon surfac type // float queryXYZ[3] = { xyz[0], xyz[1], xyz[2] }; // // Initialize normal vector to normal of triangle // float normal[3]; MathFunctions::normalVector(v1, v2, v3, normal); // // Adjust the query coordinate based upon the surface type // switch (m_surfaceTypeHint) { case SURFACE_HINT_FLAT: // // Override normal with flat surface normal // normal[0] = 0.0f; normal[1] = 0.0f; normal[2] = 1.0f; queryXYZ[2] = 0.0f; // place on plane break; case SURFACE_HINT_SPHERE: { if (m_sphericalSurfaceRadius > 0.0) { MathFunctions::normalizeVector(queryXYZ); queryXYZ[0] *= m_sphericalSurfaceRadius; queryXYZ[1] *= m_sphericalSurfaceRadius; queryXYZ[2] *= m_sphericalSurfaceRadius; } float origin[3] = { 0.0f, 0.0f, 0.0f }; float xyzDistance[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; if (MathFunctions::rayIntersectPlane(v1, v2, v3, origin, queryXYZ, xyzDistance) == false) { // // Ray does not intersect, must be parallel to plane // return; } // // Use intersection point // queryXYZ[0] = xyzDistance[0]; queryXYZ[1] = xyzDistance[1]; queryXYZ[2] = xyzDistance[2]; } break; case SURFACE_HINT_THREE_DIMENSIONAL: { // // Project point to the triangle // float xyzOnPlane[3]; MathFunctions::projectPoint(queryXYZ, v1, normal, xyzOnPlane); queryXYZ[0] = xyzOnPlane[0]; queryXYZ[1] = xyzOnPlane[1]; queryXYZ[2] = xyzOnPlane[2]; } break; } // // Note that if tolerance is a small negative number (which is done to handle // degenerate cases - projected point on vertex or edge of triangle) an area may // be negative and we continue searching tiles. If all areas are positive // then there is no need to continue searching. // float areas[3] = { 0.0f, 0.0f, 0.0f }; int result = triangleAreas(v1, v2, v3, normal, queryXYZ, degenerateTolerance, areas); if (result != 0) { baryProj->setValid(true); if (result < 0) { baryProj->setDegenerate(true); } float signedDistanceToTriangle = MathFunctions::signedDistanceFromPlane(normal, v1, xyz); baryProj->setTriangleAreas(areas); baryProj->setTriangleNodes(tn); baryProj->setSignedDistanceAboveSurface(signedDistanceToTriangle); baryProj->setProjectionSurfaceNumberOfNodes(surfaceFile->getNumberOfNodes()); } } /** * Compute the signed areas formed by assuming "xyz" is contained in the triangle formed * by the points "p1, p2, p3". "area2" and "area3" may not be set if "xyz" is not * within the triangle. * * @param p1 * Coordinate of triangle node1. * @param p2 * Coordinate of triangle node2. * @param p3 * Coordinate of triangle node3. * @param normal * Triangle's normal vector. * @param xyz * The coordinate being examined. * @param degenerateTolerance * If the point is outside the triangle, an output area will be negative. * In most cases use zero or a negative value (-0.01) very near zero. A very * negative value can be used to allow degenerate cases. * @param areasOut * Output barycentric areas of xyz in the triangle OUTPUT. * @return * Returns 1 if all areas are positive (point32_t inside the triangle). * Returns -1 if all areas are greater than the tolerance * (point32_t may be on edge or vertex) * Returns 0 if not in the triangle. * */ int32_t SurfaceProjector::triangleAreas( const float p1[3], const float p2[3], const float p3[3], const float normal[3], const float xyz[3], const float degenerateTolerance, float areasOut[3]) { float area1 = 0.0f; float area2 = 0.0f; float area3 = 0.0f; int32_t result = 0; float triangleArea = 0.0f; bool inside = false; switch (m_surfaceTypeHint) { case SURFACE_HINT_FLAT: area1 = MathFunctions::triangleAreaSigned2D(p2, p3, xyz); if (area1 > degenerateTolerance) { area2 = MathFunctions::triangleAreaSigned2D(p3, p1, xyz); if (area2 > degenerateTolerance) { area3 = MathFunctions::triangleAreaSigned2D(p1, p2, xyz); if (area3 > degenerateTolerance) { inside = true; triangleArea = MathFunctions::triangleAreaSigned2D(p1, p2, p3); } } } break; case SURFACE_HINT_SPHERE: case SURFACE_HINT_THREE_DIMENSIONAL: area1 = MathFunctions::triangleAreaSigned3D(normal, p2, p3, xyz); if (area1 >= degenerateTolerance) { area2 = MathFunctions::triangleAreaSigned3D(normal, p3, p1, xyz); if (area2 >= degenerateTolerance) { area3 = MathFunctions::triangleAreaSigned3D(normal, p1,p2,xyz); if (area3 >= degenerateTolerance) { inside = true; triangleArea = MathFunctions::triangleArea(p1, p2, p3); } } } break; } if (inside) { if ((area1 > 0.0) && (area2 > 0.0) && (area3 > 0.0)) { result = 1; } else { result = -1; } // if (area1 < 0.0) area1 = -area1; // if (area2 < 0.0) area2 = -area2; // if (area3 < 0.0) area3 = -area3; if (triangleArea > 0.0) { //area1 /= triangleArea; //area2 /= triangleArea; //area3 /= triangleArea; } else { area1 = 1.0f; area2 = 0.0f; area3 = 0.0f; } } areasOut[0] = area1; areasOut[1] = area2; areasOut[2] = area3; return result; } /** * Perform a VanEssen Projection that projects to the edge of two triangles. * * @param surfaceFile * File to which item is projected. * @param projectionLocation * Contains informaiton about item on the surface file. * @param spve * The Van Essen Projection that is setup. * @throws SurfaceProjectorException If projection failure. * */ void SurfaceProjector::projectWithVanEssenAlgorithm(const SurfaceFile* surfaceFile, const ProjectionLocation& projectionLocation, SurfaceProjectionVanEssen* spve) { float xyz[3] = { projectionLocation.m_pointXYZ[0], projectionLocation.m_pointXYZ[1], projectionLocation.m_pointXYZ[2] }; const bool pointIsUnderSurface = (projectionLocation.m_signedDistance < 0.0); // // Find nearest triangle to coordinate // const int32_t nearestTriangle = projectionLocation.m_triangleIndices[0]; if (nearestTriangle < 0) { throw SurfaceProjectorException( "Unable to find nearest triangle for VanEssen projection."); } // // Get triangle's nodes and their coordinates // const int32_t* tn = surfaceFile->getTriangle(nearestTriangle); int32_t n1 = tn[0]; int32_t n2 = tn[1]; int32_t n3 = tn[2]; const float* p1 = surfaceFile->getCoordinate(n1); const float* p2 = surfaceFile->getCoordinate(n2); const float* p3 = surfaceFile->getCoordinate(n3); // // Project the coordinate to the plane of nearest triangle // float planeNormal[3]; MathFunctions::normalVector(p1, p2, p3, planeNormal); float xyzOnPlane[3]; MathFunctions::projectPoint(xyz, p1, planeNormal, xyzOnPlane); // // Adjust for surface offset // if (m_surfaceOffsetValid) { for (int32_t i = 0; i < 3; i++) { xyz[i] = xyzOnPlane[i] + planeNormal[i] * m_surfaceOffset; } } /* * With the nearest triangle, determine which edge is closest * to the coordinate */ const int32_t closestVertices[2] = { projectionLocation.m_nodes[0], projectionLocation.m_nodes[1] }; /* * Nodes and triangles using the edge */ int32_t iR = closestVertices[0]; int32_t jR = closestVertices[1]; int32_t triA = nearestTriangle; int32_t triB = projectionLocation.m_triangleIndices[1]; const float* coordJR = surfaceFile->getCoordinate(jR); const float* coordIR = surfaceFile->getCoordinate(iR); /* * Normal vector for triangle nearest the coordinate */ float normalA[3]; surfaceFile->getTriangleNormalVector(triA, normalA); /* * When point is under surface, need to flip the normal vector */ if (pointIsUnderSurface) { for (int32_t i = 0; i < 3; i++) { normalA[i] *= -1.0; } } /* * Second triangle might not be found if topology is open or cut. */ float normalB[3] = { 0.0f, 0.0f, 0.0f }; if (triB >= 0) { /* * Normal vector for triangle sharing edge with nearest triangle */ surfaceFile->getTriangleNormalVector(triB, normalB); /* * When point is under surface, need to flip the normal vector */ if (pointIsUnderSurface) { for (int32_t i = 0; i < 3; i++) { normalB[i] *= -1.0; } } } else { float dR = (float)std::sqrt(MathFunctions::distance3D(xyzOnPlane, xyz)); float v[3]; MathFunctions::subtractVectors(coordJR, coordIR, v); float t1[3]; MathFunctions::subtractVectors(xyz, coordIR, t1); float t2 = MathFunctions::dotProduct(v, v); float t3 = MathFunctions::dotProduct(t1, v); float QR[3] = { 0.0f, 0.0f, 0.0f }; for (int32_t j = 0; j < 3; j++) { QR[j] = coordIR[j] + ((t3/t2) * v[j]); } MathFunctions::subtractVectors(coordJR, coordIR, v); t2 = MathFunctions::vectorLength(v); MathFunctions::subtractVectors(QR, coordIR, t1); t3 = MathFunctions::vectorLength(t1); float fracRI = 0.0f; if (t2 > 0.0f) { fracRI = t3/t2; } MathFunctions::subtractVectors(coordIR, coordJR, v); t2 = MathFunctions::vectorLength(v); MathFunctions::subtractVectors(QR, coordJR, t1); t3 = MathFunctions::vectorLength(t1); float fracRJ = 0.0f; if (t2 > 0.0f) { fracRJ = t3/t2; } else { fracRI = 0.0f; // uses fracRI seems wrong but like this in OLD code } if (fracRI > 1.0f) { for (int32_t j = 0; j < 3; j++) { QR[j] = coordJR[j]; } } if (fracRJ > 1.0f) { for (int32_t j = 0; j < 3; j++) { QR[j] = coordIR[j]; } } MathFunctions::subtractVectors(xyz, xyzOnPlane, t1); t2 = MathFunctions::vectorLength(t1); if (t2 > 0.0f) { for (int32_t j = 0; j < 3; j++) { t1[j] = t1[j]/t2; } } t3 = MathFunctions::dotProduct(t1, normalA); for (int32_t j = 0; j < 3; j++) { xyz[j] = QR[j] + (dR * t3 * normalA[j]); } } /* * Vector from "IR" to "JR" */ float v[3]; MathFunctions::subtractVectors(coordJR, coordIR, v); /* * Vector from "IR" to "xyz" */ float t1[3]; MathFunctions::subtractVectors(xyz, coordIR, t1); float t2 = MathFunctions::dotProduct(v, v); float t3 = MathFunctions::dotProduct(t1, v); float QR[3] = { 0.0f, 0.0f, 0.0f }; for (int32_t j = 0; j < 3; j++) { QR[j] = coordIR[j] + ((t3/t2) * v[j]); } if ((triA >= 0) && (triB >= 0)) { /* * t2 is arccos of angle between the normal vectors of the two triangles */ t2 = MathFunctions::dotProduct(normalA, normalB); t2 = std::min(t2, 1.0f); /* * Angle formed by the normal vectors of the two triangles */ spve->setPhiR((float)std::acos(t2)); if (m_validateFlag) { m_validateItemName += (", t2=" + AString::number(t2) + ", angleDegrees=" + AString::number(MathFunctions::toDegrees(spve->getPhiR()))); } } else { spve->setPhiR(0.0f); } /* * Vector from "QR" to "xyz" */ MathFunctions::subtractVectors(xyz, QR, t1); MathFunctions::normalizeVector(t1); /* * t3 is arccos of nearest triangle and "t1" */ t3 = MathFunctions::dotProduct(normalA, t1); if (t3 > 0.0f) { spve->setThetaR((float)std::acos(t3 * (t3/std::fabs(t3)))); if ((spve->getThetaR() > -0.001) && (spve->getThetaR() < 0.001)) { const float oneDegreeRadians = (1.0 * M_PI) / 180.0; if (spve->getPhiR() < oneDegreeRadians) { m_validateItemName += (",t3=" + AString::number(t3) + ",thetaR=" + AString::number(spve->getThetaR(), 'f', 10) + "THETAR=NEAR0,t2=SMALL-POSITIVE***"); } } } else { spve->setThetaR(0.0f); if (m_validateFlag) { if (t2 < 0.0) { m_validateItemName += (" ***THETAR=0,t2=NEG***"); } } } MathFunctions::subtractVectors(coordJR, coordIR, v); t2 = MathFunctions::vectorLength(v); MathFunctions::subtractVectors(QR, coordIR, t1); t3 = MathFunctions::vectorLength(t1); if (t2 > 0.0f) { spve->setFracRI(t3/t2); } else { spve->setFracRI(0.0f); } MathFunctions::subtractVectors(coordIR, coordJR, v); t2 = MathFunctions::vectorLength(v); MathFunctions::subtractVectors(QR, coordJR, t1); t3 = MathFunctions::vectorLength(t1); if (t2 > 0.0f) { spve->setFracRJ(t3/t2); } else { spve->setFracRJ(0.0f); } spve->setDR(MathFunctions::distance3D(QR, xyz)); const int32_t* triANodes = surfaceFile->getTriangle(triA); int32_t nodesA[3] = { triANodes[0], triANodes[1], triANodes[2] }; int32_t swapA = nodesA[0]; nodesA[0] = nodesA[2]; nodesA[2] = swapA; spve->setTriVertices(0, nodesA); spve->setTriAnatomical(0,0,surfaceFile->getCoordinate(nodesA[0])); spve->setTriAnatomical(0,1,surfaceFile->getCoordinate(nodesA[1])); spve->setTriAnatomical(0,2,surfaceFile->getCoordinate(nodesA[2])); if (triB >= 0) { const int32_t* triBNodes = surfaceFile->getTriangle(triB); int32_t nodesB[3] = { triBNodes[0], triBNodes[1], triBNodes[2] }; int32_t swapB = nodesB[0]; nodesB[0] = nodesB[2]; nodesB[2] = swapB; spve->setTriVertices(1, nodesB); spve->setTriAnatomical(1,0,surfaceFile->getCoordinate(nodesB[0])); spve->setTriAnatomical(1,1,surfaceFile->getCoordinate(nodesB[1])); spve->setTriAnatomical(1,2,surfaceFile->getCoordinate(nodesB[2])); } else { int32_t intZeros[3] = { 0, 0, 0 }; spve->setTriVertices(1, intZeros); float zeros[3] = { 0.0f, 0.0f, 0.0f }; spve->setTriAnatomical(1, 0, zeros); spve->setTriAnatomical(1, 1, zeros); spve->setTriAnatomical(1, 2, zeros); } spve->setVertexAnatomical(0, coordIR); spve->setVertexAnatomical(1, coordJR); spve->setVertex(0, iR); spve->setVertex(1, jR); spve->setProjectionSurfaceNumberOfNodes(surfaceFile->getNumberOfNodes()); spve->setValid(true); } /* ========================================================================== */ /** * \class caret::SurfaceProjector::ProjectionLocation * \brief Contains information about nearby point on surface */ /** * Constructor. */ SurfaceProjector::ProjectionLocation::ProjectionLocation() { m_type = INVALID; m_triangleIndices = NULL; m_numberOfTriangles = 0; } /** * Destructor. */ SurfaceProjector::ProjectionLocation::~ProjectionLocation() { if (m_triangleIndices != NULL) { delete[] m_triangleIndices; } } /** * Get String describing content. * @param surfaceFile * Surface file used for projection * @return * Description. */ AString SurfaceProjector::ProjectionLocation::toString(const SurfaceFile* surfaceFile) const { AString typeString; switch (m_type) { case EDGE: typeString = "EDGE"; break; case INVALID: typeString = "INVALID"; break; case NODE: typeString = "NODE"; break; case TRIANGLE: typeString = "TRIANGLE"; break; } AString msg = (" Type=" + typeString + " Pos=(" + AString::fromNumbers(m_pointXYZ, 3, ",") + ") SurfacePos=(" + AString::fromNumbers(m_surfaceXYZ, 3, ",") + ") Triangles=(" + AString::fromNumbers(m_triangleIndices, m_numberOfTriangles, ",") + ") AbsDistance=" + AString::number(m_absoluteDistance) + " SignedDistance=" + AString::number(m_signedDistance) + " Nodes=(" + AString::fromNumbers(m_nodes, 3, ",") + ") Weights=(" + AString::fromNumbers(m_weights, 3, ",") + ") NearestNode=" + AString::number(m_nearestNode) + " Node-XYZ=(" + AString::fromNumbers(surfaceFile->getCoordinate(m_nearestNode), 3,",") + ")"); return msg; } workbench-1.1.1/src/Files/SurfaceProjector.h000066400000000000000000000200031255417355300207630ustar00rootroot00000000000000#ifndef __SURFACE_PROJECTOR_H__ #define __SURFACE_PROJECTOR_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "SurfaceProjectorException.h" #include #include namespace caret { class FociFile; class Focus; class SurfaceFile; class SurfaceProjectedItem; class SurfaceProjectionBarycentric; class SurfaceProjectionVanEssen; class TopologyHelper; /** * Class for projecting items to the surface. */ class SurfaceProjector : public CaretObject { public: SurfaceProjector(const SurfaceFile* surfaceFile); SurfaceProjector(const std::vector& surfaceFiles); SurfaceProjector(const SurfaceFile* leftSurfaceFile, const SurfaceFile* rightSurfaceFile, const SurfaceFile* cerebellumSurfaceFile); virtual ~SurfaceProjector(); void projectItemToTriangle(SurfaceProjectedItem* spi); void projectItemToTriangleOrEdge(SurfaceProjectedItem* spi); void projectFociFile(FociFile* fociFile); void projectFocus(const int32_t focusIndex, Focus* focus); void setSurfaceOffset(const float surfaceOffset); private: enum SurfaceHintType { SURFACE_HINT_FLAT, SURFACE_HINT_SPHERE, SURFACE_HINT_THREE_DIMENSIONAL }; enum Mode { MODE_LEFT_RIGHT_CEREBELLUM, MODE_SURFACES }; class ProjectionLocation { public: enum Type { EDGE, INVALID, NODE, TRIANGLE }; ProjectionLocation(); ~ProjectionLocation(); AString toString(const SurfaceFile* surfaceFile) const; /** Type of surface item projected to */ Type m_type; /** Coordinate that was projected */ float m_pointXYZ[3]; /** Nearest coordinate on surface */ float m_surfaceXYZ[3]; /** Nearest triangle(s) indices (closest triangle always first) */ int32_t* m_triangleIndices; /** Number of triangles */ int32_t m_numberOfTriangles; /** Absolute distance to the surface */ float m_absoluteDistance; /** Signed distance to surface (positive=>above, negative=>below) */ float m_signedDistance; /** Nodes of node/edge/triangle (node has 1 element, edge 2, triangle 3) */ int32_t m_nodes[3]; /** Weights cooresponding to nodes (node has 1 element, edge 2, triangle 3) */ float m_weights[3]; /** Node nearest to the coordinate that was projected */ int32_t m_nearestNode; }; SurfaceProjector(const SurfaceProjector& o); SurfaceProjector& operator=(const SurfaceProjector& o); void initializeMembersSurfaceProjector(); void getProjectionLocation(const SurfaceFile* surfaceFile, const float xyz[3], ProjectionLocation& projectionLocation) const; void projectItem(SurfaceProjectedItem* spi, SurfaceProjectedItem* secondSpi); void projectItemToSurfaceFile(const SurfaceFile* surfaceFile, SurfaceProjectedItem* spi); void projectToSurface(const SurfaceFile* surfaceFile, const float xyz[3], SurfaceProjectedItem* spi) ; void projectToSurfaceTriangle(const SurfaceFile* surfaceFile, const ProjectionLocation& projectionLocation, SurfaceProjectionBarycentric* baryProj) ; int32_t projectToSurfaceTriangleAux(const SurfaceFile* surfaceFile, const ProjectionLocation& projectionLocation, SurfaceProjectionBarycentric* baryProj) ; void checkItemInTriangle(const SurfaceFile* surfaceFile, const int32_t triangleNumber, const float xyz[3], const float degenerateTolerance, SurfaceProjectionBarycentric* baryProj); int32_t triangleAreas( const float p1[3], const float p2[3], const float p3[3], const float normal[3], const float xyz[3], const float degenerateTolerance, float areasOut[3]); void convertToTriangleProjection(const SurfaceFile* surfaceFile, ProjectionLocation& projectionLocation); void projectWithVanEssenAlgorithm(const SurfaceFile* surfaceFile, const ProjectionLocation& projectionLocation, SurfaceProjectionVanEssen* spve) ; std::vector m_surfaceFiles; const SurfaceFile* m_surfaceFileLeft; const SurfaceFile* m_surfaceFileRight; const SurfaceFile* m_surfaceFileCerebellum; const Mode m_mode; SurfaceHintType m_surfaceTypeHint; float m_sphericalSurfaceRadius; float m_surfaceOffset; bool m_surfaceOffsetValid; bool m_allowEdgeProjection; bool m_validateFlag; AString m_validateItemName; AString m_projectionWarning; /** Point in triangle test tolerance that requires point inside triangle */ static float s_normalTriangleAreaTolerance; /** Point in triangle test tolerance that allows point outside triangle (degenerate case) */ static float s_extremeTriangleAreaTolerance; /** Projection distance error used to compare original and after projection/unprojection */ static float s_projectionDistanceError; /** Cutoff for item that that is projected to cortex and not cerebellum */ static float s_corticalSurfaceCutoff; /** Cutoff for item that that is projected to cerebellum and not cortex */ static float s_cerebellumSurfaceCutoff; }; #ifdef __SURFACE_PROJECTOR_DEFINE__ float SurfaceProjector::s_normalTriangleAreaTolerance = -0.01; float SurfaceProjector::s_extremeTriangleAreaTolerance = -10000000.0; float SurfaceProjector::s_projectionDistanceError = 0.5; float SurfaceProjector::s_corticalSurfaceCutoff = 2.0; float SurfaceProjector::s_cerebellumSurfaceCutoff = 4.0; #endif // __SURFACE_PROJECTOR_DEFINE__ } // namespace #endif // __SURFACE_PROJECTOR_H__ workbench-1.1.1/src/Files/SurfaceProjectorException.cxx000066400000000000000000000037261255417355300232320ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "SurfaceProjectorException.h" #include using namespace caret; /** * Constructor. * */ SurfaceProjectorException::SurfaceProjectorException() : CaretException() { this->initializeMembersSurfaceProjectorException(); } /** * Constructor. * * @param s Description of the exception. * */ SurfaceProjectorException::SurfaceProjectorException( const AString& s) : CaretException(s) { this->initializeMembersSurfaceProjectorException(); } /** * Copy Constructor. * @param e * Exception that is copied. */ SurfaceProjectorException::SurfaceProjectorException(const SurfaceProjectorException& e) : CaretException(e) { } /** * Assignment operator. * @param e * Exception that is copied. * @return * Copy of the exception. */ SurfaceProjectorException& SurfaceProjectorException::operator=(const SurfaceProjectorException& e) { if (this != &e) { CaretException::operator=(e); } return *this; } /** * Destructor */ SurfaceProjectorException::~SurfaceProjectorException() throw() { } void SurfaceProjectorException::initializeMembersSurfaceProjectorException() { } workbench-1.1.1/src/Files/SurfaceProjectorException.h000066400000000000000000000030541255417355300226510ustar00rootroot00000000000000#ifndef __SURFACE_PROJECTOR_EXCEPTION_H__ #define __SURFACE_PROJECTOR_EXCEPTION_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "CaretException.h" namespace caret { /** * An exception thrown during surface projector processing. */ class SurfaceProjectorException : public CaretException { public: SurfaceProjectorException(); SurfaceProjectorException(const AString& s); SurfaceProjectorException(const SurfaceProjectorException& e); SurfaceProjectorException& operator=(const SurfaceProjectorException& e); virtual ~SurfaceProjectorException() throw(); private: void initializeMembersSurfaceProjectorException(); }; } // namespace #endif // __SURFACE_PROJECTOR_EXCEPTION_H__ workbench-1.1.1/src/Files/SurfaceResamplingHelper.cxx000066400000000000000000000641161255417355300226450ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*LICENSE_END*/ #include "SurfaceResamplingHelper.h" #include "CaretAssert.h" #include "CaretException.h" #include "CaretOMP.h" #include "GeodesicHelper.h" #include "SignedDistanceHelper.h" #include "SurfaceFile.h" #include "TopologyHelper.h" #include "Vector3D.h" #include #include using namespace std; using namespace caret; SurfaceResamplingHelper::SurfaceResamplingHelper(const SurfaceResamplingMethodEnum::Enum& myMethod, const SurfaceFile* currentSphere, const SurfaceFile* newSphere, const float* currentAreas, const float* newAreas, const float* currentRoi) { if (!checkSphere(currentSphere) || !checkSphere(newSphere)) throw CaretException("input surfaces to SurfaceResamplingHelper must be spheres"); SurfaceFile currentSphereMod, newSphereMod; changeRadius(100.0f, currentSphere, ¤tSphereMod); changeRadius(100.0f, newSphere, &newSphereMod); switch (myMethod) { case SurfaceResamplingMethodEnum::ADAP_BARY_AREA: CaretAssert(currentAreas != NULL && newAreas != NULL); if (currentAreas == NULL || newAreas == NULL) throw CaretException("ADAP_BARY_AREA method requires area surfaces"); computeWeightsAdapBaryArea(¤tSphereMod, &newSphereMod, currentAreas, newAreas, currentRoi); break; case SurfaceResamplingMethodEnum::BARYCENTRIC: computeWeightsBarycentric(¤tSphereMod, &newSphereMod, currentRoi); break; } } void SurfaceResamplingHelper::resampleNormal(const float* input, float* output, const float& invalidVal) const { int numNodes = (int)m_weights.size() - 1; #pragma omp CARET_PARFOR schedule(dynamic) for (int i = 0; i < numNodes; ++i) { WeightElem* end = m_weights[i + 1], *elem = m_weights[i]; if (elem != end) { double accum = 0.0; for (; elem != end; ++elem) { accum += input[elem->node] * elem->weight;//don't need to divide afterwards, because the weights already sum to 1 } output[i] = accum; } else { output[i] = invalidVal; } } } void SurfaceResamplingHelper::resample3DCoord(const float* input, float* output) const { int numNodes = (int)m_weights.size() - 1; #pragma omp CARET_PARFOR schedule(dynamic) for (int i = 0; i < numNodes; ++i) { double tempvec[3] = { 0.0, 0.0, 0.0 }; WeightElem* end = m_weights[i + 1]; for (WeightElem* elem = m_weights[i]; elem != end; ++elem) { const float* coord = input + elem->node * 3; tempvec[0] += coord[0] * elem->weight;//don't need to divide afterwards, because the weights already sum to 1 tempvec[1] += coord[1] * elem->weight; tempvec[2] += coord[2] * elem->weight; } int i3 = i * 3; output[i3] = tempvec[0]; output[i3 + 1] = tempvec[1]; output[i3 + 2] = tempvec[2]; } } void SurfaceResamplingHelper::resamplePopular(const int32_t* input, int32_t* output, const int32_t& invalidVal) const { int numNodes = (int)m_weights.size() - 1; #pragma omp CARET_PARFOR schedule(dynamic) for (int i = 0; i < numNodes; ++i) { map accum; float maxweight = -1.0f; int32_t bestlabel = invalidVal; WeightElem* end = m_weights[i + 1]; for (WeightElem* elem = m_weights[i]; elem != end; ++elem) { int32_t label = input[elem->node]; map::iterator iter = accum.find(label); if (iter == accum.end()) { accum[label] = elem->weight; if (elem->weight > maxweight) { maxweight = elem->weight; bestlabel = label; } } else { iter->second += elem->weight; if (iter->second > maxweight) { maxweight = iter->second; bestlabel = label; } } } output[i] = bestlabel; } } void SurfaceResamplingHelper::resampleLargest(const float* input, float* output, const float& invalidVal) const { int numNodes = (int)m_weights.size() - 1; #pragma omp CARET_PARFOR schedule(dynamic) for (int i = 0; i < numNodes; ++i) { WeightElem* end = m_weights[i + 1]; float largest = -1.0f; int largestNode = -1; for (WeightElem* elem = m_weights[i]; elem != end; ++elem) { if (elem->weight > largest) { largest = elem->weight; largestNode = elem->node; } } if (largestNode != -1) { output[i] = input[largestNode]; } else { output[i] = invalidVal; } } } void SurfaceResamplingHelper::resampleLargest(const int32_t* input, int32_t* output, const int32_t& invalidVal) const { int numNodes = (int)m_weights.size() - 1; #pragma omp CARET_PARFOR schedule(dynamic) for (int i = 0; i < numNodes; ++i) { WeightElem* end = m_weights[i + 1]; float largest = -1.0f; int largestNode = -1; for (WeightElem* elem = m_weights[i]; elem != end; ++elem) { if (elem->weight > largest) { largest = elem->weight; largestNode = elem->node; } } if (largestNode != -1) { output[i] = input[largestNode]; } else { output[i] = invalidVal; } } } void SurfaceResamplingHelper::getResampleValidROI(float* output) const { int numNodes = (int)m_weights.size() - 1; for (int i = 0; i < numNodes; ++i) { if (m_weights[i] != m_weights[i + 1]) { output[i] = 1.0f; } else { output[i] = 0.0f; } } } void SurfaceResamplingHelper::resampleCutSurface(const SurfaceFile* cutSurfaceIn, const SurfaceFile* currentSphere, const SurfaceFile* newSphere, SurfaceFile* surfaceOut) { if (cutSurfaceIn->getNumberOfNodes() != currentSphere->getNumberOfNodes()) throw CaretException("input surface has different number of nodes than input sphere"); if (!checkSphere(currentSphere) || !checkSphere(newSphere)) throw CaretException("input surfaces to SurfaceResamplingHelper must be spheres"); SurfaceFile currentSphereMod, newSphereMod; changeRadius(100.0f, currentSphere, ¤tSphereMod); changeRadius(100.0f, newSphere, &newSphereMod); SurfaceFile cutCurSphere = *cutSurfaceIn; cutCurSphere.setCoordinates(currentSphereMod.getCoordinateData()); int newNodes = newSphere->getNumberOfNodes(); vector newInfo(newSphere->getNumberOfNodes()); #pragma omp CARET_PAR { CaretPointer mySignedHelp = cutCurSphere.getSignedDistanceHelper(); #pragma omp CARET_FOR schedule(dynamic) for (int i = 0; i < newNodes; ++i) { mySignedHelp->barycentricWeights(newSphereMod.getCoordinate(i), newInfo[i]); } } vector isOnEdge(newNodes, 0);//really used as bool, but avoid bitpacking so it can be modified in parallel CaretPointer cutTopoHelp = cutSurfaceIn->getTopologyHelper();//because topology didn't change, and it might have one already - also, don't need separate helpers per thread, not using neighbors to depth CaretPointer closedTopoHelp = currentSphere->getTopologyHelper();//ditto CaretPointer newTopoHelp = newSphere->getTopologyHelper();//tritto? const vector& cutEdgeInfo = cutTopoHelp->getEdgeInfo(); vector largestNode(newNodes, -1); #pragma omp CARET_PARFOR for (int i = 0; i < newNodes; ++i) { float largestWeight = 0.0f, secondWeight = 0.0f;//locate the two nodes with largest barycentric weights largestNode[i] = -1; int secondNode = -1; for (int j = 0; j < 3; ++j) { if (newInfo[i].baryWeights[j] > largestWeight) { secondNode = largestNode[i];//shift largest to second secondWeight = largestWeight; largestWeight = newInfo[i].baryWeights[j];//update largest largestNode[i] = newInfo[i].nodes[j]; } else if (newInfo[i].baryWeights[j] > secondWeight) { secondWeight = newInfo[i].baryWeights[j]; secondNode = newInfo[i].nodes[j]; } } switch (newInfo[i].type) { case BarycentricInfo::NODE: if (cutTopoHelp->getNodeTiles(largestNode[i]).size() != closedTopoHelp->getNodeTiles(largestNode[i]).size()) { isOnEdge[i] = 1; } break; case BarycentricInfo::EDGE: { const vector& cutEdges = cutTopoHelp->getNodeEdges(largestNode[i]); for (int j = 0; j < (int)cutEdges.size(); ++j) { const TopologyEdgeInfo& myInfo = cutEdgeInfo[cutEdges[j]]; if (myInfo.node1 == largestNode[i]) { if (myInfo.node2 == secondNode) { if (myInfo.numTiles == 1)//NOTE: assumes 1 tile means that it is an edge of a cut, could compare to closed instead, but need to search it separately { isOnEdge[i] = 1; } break; } } else { if (myInfo.node1 == secondNode && myInfo.node2 == largestNode[i]) { if (myInfo.numTiles == 1)//ditto { isOnEdge[i] = 1; } break; } } } break; } case BarycentricInfo::TRIANGLE://is never on a cut edge, do nothing break; } } int numNewTris = newSphere->getNumberOfTriangles(); vector triRemove(numNewTris, 0), nodeDisconnect(newNodes, 0);//again, avoid bitpacking #pragma omp CARET_PAR { CaretPointer closedGeoHelp = currentSphereMod.getGeodesicHelper(); CaretPointer cutGeoHelp = cutCurSphere.getGeodesicHelper(); #pragma omp CARET_FOR schedule(dynamic) for (int32_t i = 0; i < newNodes; ++i) { const vector& neighbors = newTopoHelp->getNodeNeighbors(i); if (isOnEdge[i]) { bool hasInteriorNeighbor = false; for (int j = 0; j < (int)neighbors.size(); ++j) { if (!isOnEdge[neighbors[j]]) { hasInteriorNeighbor = true; break; } } if (hasInteriorNeighbor) { for (int j = 0; j < (int)neighbors.size(); ++j) { vector closedPath, cutPath; vector closedPathDists, cutPathDists; closedGeoHelp->getPathToNode(largestNode[i], largestNode[neighbors[j]], closedPath, closedPathDists); cutGeoHelp->getPathToNode(largestNode[i], largestNode[neighbors[j]], cutPath, cutPathDists); if (cutPathDists.size() == 0 || cutPathDists.back() > 2.0f * closedPathDists.back())//maybe this cutoff should be tunable { const vector& myTiles = newTopoHelp->getNodeTiles(i);//find tiles on new mesh that share this edge, remove them for (int k = 0; k < (int)myTiles.size(); ++k) { const int32_t* thisTile = newSphere->getTriangle(myTiles[k]); if (thisTile[0] == neighbors[j] || thisTile[1] == neighbors[j] || thisTile[2] == neighbors[j]) { triRemove[myTiles[k]] = 1; } } } } } else { nodeDisconnect[i] = 1;//disconnect it completely if it has no interior neighbors const vector& nodeTiles = newTopoHelp->getNodeTiles(i); for (int j = 0; j < (int)nodeTiles.size(); ++j) { triRemove[nodeTiles[j]] = 1; } } } else {//interior nodes also have to be checked for crossing the cut, since there may not be a node that falls inside the cut for (int j = 0; j < (int)neighbors.size(); ++j) { vector closedPath, cutPath; vector closedPathDists, cutPathDists; closedGeoHelp->getPathToNode(largestNode[i], largestNode[neighbors[j]], closedPath, closedPathDists); cutGeoHelp->getPathToNode(largestNode[i], largestNode[neighbors[j]], cutPath, cutPathDists);//note: path length of zero means no connection if (cutPathDists.size() == 0 || cutPathDists.back() > 2.0f * closedPathDists.back())//maybe this cutoff should be tunable { const vector& myTiles = newTopoHelp->getNodeTiles(i);//find tiles on new mesh that share this edge, remove them for (int k = 0; k < (int)myTiles.size(); ++k) { const int32_t* thisTile = newSphere->getTriangle(myTiles[k]); if (thisTile[0] == neighbors[j] || thisTile[1] == neighbors[j] || thisTile[2] == neighbors[j]) { triRemove[myTiles[k]] = 1; } } } } } } } int triRemoveCount = 0; for (int i = 0; i < numNewTris; ++i) { if (triRemove[i] != 0) ++triRemoveCount;//parallelizing counting would be silly } surfaceOut->setNumberOfNodesAndTriangles(newNodes, numNewTris - triRemoveCount); surfaceOut->setStructure(cutSurfaceIn->getStructure()); surfaceOut->setSurfaceType(cutSurfaceIn->getSurfaceType()); surfaceOut->setSecondaryType(cutSurfaceIn->getSecondaryType()); int outTri = 0; for (int i = 0; i < numNewTris; ++i) { if (triRemove[i] == 0) { surfaceOut->setTriangle(outTri, newSphere->getTriangle(i)); ++outTri;//means this can't be parallel, but it would also be silly } } CaretAssert(outTri == numNewTris - triRemoveCount); Vector3D origin(0.0f, 0.0f, 0.0f);//where we move disconnected nodes to for (int i = 0; i < newNodes; ++i)//could be parallel, but probably not needed { if (nodeDisconnect[i] != 0) { surfaceOut->setCoordinate(i, origin); } else { Vector3D coord1 = cutSurfaceIn->getCoordinate(newInfo[i].nodes[0]); Vector3D coord2 = cutSurfaceIn->getCoordinate(newInfo[i].nodes[1]); Vector3D coord3 = cutSurfaceIn->getCoordinate(newInfo[i].nodes[2]); Vector3D outCoord = coord1 * newInfo[i].baryWeights[0] + coord2 * newInfo[i].baryWeights[1] + coord3 * newInfo[i].baryWeights[2]; surfaceOut->setCoordinate(i, outCoord); } } } void SurfaceResamplingHelper::computeWeightsAdapBaryArea(const SurfaceFile* currentSphere, const SurfaceFile* newSphere, const float* currentAreas, const float* newAreas, const float* currentRoi) { vector > forward, reverse, reverse_gather; makeBarycentricWeights(currentSphere, newSphere, forward, NULL);//don't use an roi until after we have done area correction, because area correction MUST ignore ROI makeBarycentricWeights(newSphere, currentSphere, reverse, NULL); int numNewNodes = (int)forward.size(), numOldNodes = currentSphere->getNumberOfNodes(); reverse_gather.resize(numNewNodes); for (int oldNode = 0; oldNode < numOldNodes; ++oldNode)//this loop can't be parallelized { for (map::iterator iter = reverse[oldNode].begin(); iter != reverse[oldNode].end(); ++iter)//convert scattering weights to gathering weights { reverse_gather[iter->first][oldNode] = iter->second; } } vector > adap_gather(numNewNodes); #pragma omp CARET_PARFOR schedule(dynamic) for (int newNode = 0; newNode < numNewNodes; ++newNode) { set forwardused;//build set of all nodes used by forward weights (really only 3, but this is a bit cleaner and more generic) for (map::iterator iter = forward[newNode].begin(); iter != forward[newNode].end(); ++iter) { forwardused.insert(iter->first); } bool useforward = true; for (map::iterator iter = reverse_gather[newNode].begin(); iter != reverse_gather[newNode].end(); ++iter) { if (forwardused.find(iter->first) == forwardused.end()) { useforward = false;//if the reverse scatter weights include something the forward gather weights don't, use reverse scatter break; } } if (useforward) { adap_gather[newNode] = forward[newNode]; } else { adap_gather[newNode] = reverse_gather[newNode]; } for (map::iterator iter = adap_gather[newNode].begin(); iter != adap_gather[newNode].end(); ++iter)//begin area correction by multiplying by target node area { iter->second *= newAreas[newNode];//begin the process of area correction by multiplying by gathering node areas } } vector correctionSum(numOldNodes, 0.0f); for (int newNode = 0; newNode < numNewNodes; ++newNode)//this loop is separate because it can't be parallelized { for (map::iterator iter = adap_gather[newNode].begin(); iter != adap_gather[newNode].end(); ++iter) { correctionSum[iter->first] += iter->second;//now, sum the scattering weights to prepare for first normalization } } #pragma omp CARET_PARFOR schedule(dynamic) for (int newNode = 0; newNode < numNewNodes; ++newNode) { double weightsum = 0.0f; vector::iterator> toRemove; for (map::iterator iter = adap_gather[newNode].begin(); iter != adap_gather[newNode].end(); ++iter) { if (currentRoi == NULL || currentRoi[iter->first] > 0.0f) { iter->second *= currentAreas[iter->first] / correctionSum[iter->first];//divide the weights by their scatter sum, then multiply by current areas weightsum += iter->second;//and compute the sum } else { toRemove.push_back(iter); } } int numToRemove = (int)toRemove.size(); for (int i = 0; i < numToRemove; ++i) { adap_gather[newNode].erase(toRemove[i]); } if (weightsum != 0.0f)//this shouldn't happen unless no nodes remain due to roi, or node areas can be zero { for (map::iterator iter = adap_gather[newNode].begin(); iter != adap_gather[newNode].end(); ++iter) { iter->second /= weightsum;//and normalize to a sum of 1 } } } compactWeights(adap_gather);//and compact them into the internal weight storage } void SurfaceResamplingHelper::computeWeightsBarycentric(const SurfaceFile* currentSphere, const SurfaceFile* newSphere, const float* currentRoi) { vector > forward; makeBarycentricWeights(currentSphere, newSphere, forward, currentRoi);//this should ensure they sum to 1, so we are done compactWeights(forward); } bool SurfaceResamplingHelper::checkSphere(const SurfaceFile* surface) { int numNodes = surface->getNumberOfNodes(); CaretAssert(numNodes > 1); int numNodes3 = numNodes * 3; const float* coordData = surface->getCoordinateData(); float mindist = Vector3D(coordData).length(); if (mindist != mindist) throw CaretException("found NaN coordinate in an input sphere"); float maxdist = mindist; const float TOLERANCE = 1.001f; for (int i = 3; i < numNodes3; i += 3) { float tempf = Vector3D(coordData + i).length(); if (tempf != tempf) throw CaretException("found NaN coordinate in an input sphere"); if (tempf < mindist) { mindist = tempf; } if (tempf > maxdist) { maxdist = tempf; } } return (mindist * TOLERANCE > maxdist); } void SurfaceResamplingHelper::changeRadius(const float& radius, const SurfaceFile* input, SurfaceFile* output) { *output = *input; int numNodes = output->getNumberOfNodes(); int numNodes3 = numNodes * 3; vector newCoordData(numNodes3); const float* oldCoordData = output->getCoordinateData(); #pragma omp CARET_PARFOR schedule(dynamic) for (int i = 0; i < numNodes3; i += 3) { Vector3D tempvec1 = oldCoordData + i, tempvec2; tempvec2 = tempvec1 * (radius / tempvec1.length()); newCoordData[i] = tempvec2[0]; newCoordData[i + 1] = tempvec2[1]; newCoordData[i + 2] = tempvec2[2]; } output->setCoordinates(newCoordData.data()); } void SurfaceResamplingHelper::compactWeights(const vector >& weights) { int compactsize = 0; int numNodes = (int)weights.size(); m_weights = CaretArray(numNodes + 1);//include a "one-after" pointer for (int i = 0; i < numNodes; ++i) { compactsize += (int)weights[i].size(); } m_storagechunk = CaretArray(compactsize); int curpos = 0; for (int i = 0; i < numNodes; ++i) { m_weights[i] = m_storagechunk + curpos; for (map::const_iterator iter = weights[i].begin(); iter != weights[i].end(); ++iter) { m_storagechunk[curpos] = WeightElem(iter->first, iter->second); ++curpos; } } CaretAssert(curpos == compactsize); m_weights[numNodes] = m_storagechunk + compactsize; } void SurfaceResamplingHelper::makeBarycentricWeights(const SurfaceFile* from, const SurfaceFile* to, vector >& weights, const float* currentRoi) { int numToNodes = to->getNumberOfNodes(); weights.resize(numToNodes); const float* toCoordData = to->getCoordinateData(); if (currentRoi == NULL) { #pragma omp CARET_PAR { CaretPointer mySignedHelp = from->getSignedDistanceHelper(); #pragma omp CARET_FOR schedule(dynamic) for (int i = 0; i < numToNodes; ++i) { BarycentricInfo myInfo; mySignedHelp->barycentricWeights(toCoordData + i * 3, myInfo); if (myInfo.baryWeights[0] != 0.0f) weights[i][myInfo.nodes[0]] = myInfo.baryWeights[0]; if (myInfo.baryWeights[1] != 0.0f) weights[i][myInfo.nodes[1]] = myInfo.baryWeights[1]; if (myInfo.baryWeights[2] != 0.0f) weights[i][myInfo.nodes[2]] = myInfo.baryWeights[2]; } } } else { #pragma omp CARET_PAR { CaretPointer mySignedHelp = from->getSignedDistanceHelper(); #pragma omp CARET_FOR schedule(dynamic) for (int i = 0; i < numToNodes; ++i) { BarycentricInfo myInfo; float weightsum = 0.0f;//there are only 3 weights, so don't bother with double precision mySignedHelp->barycentricWeights(toCoordData + i * 3, myInfo); if (myInfo.baryWeights[0] != 0.0f && currentRoi[myInfo.nodes[0]] > 0.0f) { weights[i][myInfo.nodes[0]] = myInfo.baryWeights[0]; weightsum += myInfo.baryWeights[0]; } if (myInfo.baryWeights[1] != 0.0f && currentRoi[myInfo.nodes[1]] > 0.0f) { weights[i][myInfo.nodes[1]] = myInfo.baryWeights[1]; weightsum += myInfo.baryWeights[1]; } if (myInfo.baryWeights[2] != 0.0f && currentRoi[myInfo.nodes[2]] > 0.0f) { weights[i][myInfo.nodes[2]] = myInfo.baryWeights[2]; weightsum += myInfo.baryWeights[2]; } if (weightsum != 0.0f) { for (map::iterator iter = weights[i].begin(); iter != weights[i].end(); ++iter) { iter->second /= weightsum; } } } } } } workbench-1.1.1/src/Files/SurfaceResamplingHelper.h000066400000000000000000000072211255417355300222640ustar00rootroot00000000000000#ifndef __SURFACE_RESAMPLING_HELPER_H__ #define __SURFACE_RESAMPLING_HELPER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretPointer.h" #include "SurfaceResamplingMethodEnum.h" #include #include namespace caret { class SurfaceFile; class SurfaceResamplingHelper { struct WeightElem { int node; float weight; WeightElem() { } WeightElem(const int& nodeIn, const float& weightIn) : node(nodeIn), weight(weightIn) { } }; CaretArray m_storagechunk; CaretArray m_weights; static bool checkSphere(const SurfaceFile* surface); static void changeRadius(const float& radius, const SurfaceFile* input, SurfaceFile* output); void computeWeightsAdapBaryArea(const SurfaceFile* currentSphere, const SurfaceFile* newSphere, const float* currentAreas, const float* newAreas, const float* currentRoi); void computeWeightsBarycentric(const SurfaceFile* currentSphere, const SurfaceFile* newSphere, const float* currentRoi); static void makeBarycentricWeights(const SurfaceFile* from, const SurfaceFile* to, std::vector >& weights, const float* currentRoi); void compactWeights(const std::vector >& weights); public: SurfaceResamplingHelper() { } SurfaceResamplingHelper(const SurfaceResamplingMethodEnum::Enum& myMethod, const SurfaceFile* currentSphere, const SurfaceFile* newSphere, const float* currentAreas = NULL, const float* newAreas = NULL, const float* currentRoi = NULL); ///resample real-valued data by means of weights void resampleNormal(const float* input, float* output, const float& invalidVal = 0.0f) const; ///resample 3D coordinate data by means of weights void resample3DCoord(const float* input, float* output) const; ///resample label-like data according to which value gets the largest weight sum void resamplePopular(const int32_t* input, int32_t* output, const int32_t& invalidVal = 0) const; ///resample float data according to what weight is largest void resampleLargest(const float* input, float* output, const float& invalidVal = 0.0f) const; ///resample int data according to what weight is largest void resampleLargest(const int32_t* input, int32_t* output, const int32_t& invalidVal = 0) const; ///get the ROI of nodes that have data within the input ROI void getResampleValidROI(float* output) const; ///resample a cut surface - not something you will apply multiple times, so static method static void resampleCutSurface(const SurfaceFile* cutSurfaceIn, const SurfaceFile* curSphere, const SurfaceFile* newSphere, SurfaceFile* surfaceOut); }; } #endif //__SURFACE_RESAMPLING_HELPER_H__ workbench-1.1.1/src/Files/SurfaceResamplingMethodEnum.cxx000066400000000000000000000231201255417355300234610ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "SurfaceResamplingMethodEnum.h" #include "CaretAssert.h" using namespace caret; std::vector SurfaceResamplingMethodEnum::enumData; bool SurfaceResamplingMethodEnum::initializedFlag = false; /** * \class caret::SurfaceResamplingMethodEnum * \brief * * */ /** * Constructor. * * @param enumValue * An enumerated value. * @param integerCode * Integer code for this enumerated value. * * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ SurfaceResamplingMethodEnum::SurfaceResamplingMethodEnum(const Enum enumValue, const int32_t integerCode, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCode; this->name = name; this->guiName = guiName; } /** * Destructor. */ SurfaceResamplingMethodEnum::~SurfaceResamplingMethodEnum() { } /** * Initialize the enumerated metadata. */ void SurfaceResamplingMethodEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(SurfaceResamplingMethodEnum(ADAP_BARY_AREA, 0, "ADAP_BARY_AREA", "adaptive barycentric with area correction")); enumData.push_back(SurfaceResamplingMethodEnum(BARYCENTRIC, 1, "BARYCENTRIC", "barycentric")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const SurfaceResamplingMethodEnum* SurfaceResamplingMethodEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const SurfaceResamplingMethodEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString SurfaceResamplingMethodEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const SurfaceResamplingMethodEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ SurfaceResamplingMethodEnum::Enum SurfaceResamplingMethodEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = ADAP_BARY_AREA; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const SurfaceResamplingMethodEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type SurfaceResamplingMethodEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString SurfaceResamplingMethodEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const SurfaceResamplingMethodEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ SurfaceResamplingMethodEnum::Enum SurfaceResamplingMethodEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = ADAP_BARY_AREA; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const SurfaceResamplingMethodEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type SurfaceResamplingMethodEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t SurfaceResamplingMethodEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const SurfaceResamplingMethodEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ SurfaceResamplingMethodEnum::Enum SurfaceResamplingMethodEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = ADAP_BARY_AREA; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const SurfaceResamplingMethodEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type SurfaceResamplingMethodEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void SurfaceResamplingMethodEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void SurfaceResamplingMethodEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(SurfaceResamplingMethodEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void SurfaceResamplingMethodEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(SurfaceResamplingMethodEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } workbench-1.1.1/src/Files/SurfaceResamplingMethodEnum.h000066400000000000000000000054411255417355300231140ustar00rootroot00000000000000#ifndef __SURFACE_RESAMPLING_METHOD_ENUM_H__ #define __SURFACE_RESAMPLING_METHOD_ENUM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class SurfaceResamplingMethodEnum { public: /** * Enumerated values. */ enum Enum { ADAP_BARY_AREA, BARYCENTRIC }; ~SurfaceResamplingMethodEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); private: SurfaceResamplingMethodEnum(const Enum enumValue, const int32_t integerCode, const AString& name, const AString& guiName); static const SurfaceResamplingMethodEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; } // namespace #endif //__SURFACE_RESAMPLING_METHOD_ENUM_H__ workbench-1.1.1/src/Files/SurfaceTypeEnum.cxx000066400000000000000000000427671255417355300211620ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SURFACE_TYPE_ENUM_DECLARE__ #include "SurfaceTypeEnum.h" #undef __SURFACE_TYPE_ENUM_DECLARE__ using namespace caret; /** * Constructor. * * @param e * An enumerated value. * @param name * Name of enumberated value. */ SurfaceTypeEnum::SurfaceTypeEnum(const Enum e, const AString& name, const AString& guiName, const AString& giftiName) { this->e = e; this->integerCode = SurfaceTypeEnum::integerCodeGenerator++; this->name = name; this->guiName = guiName; this->giftiName = giftiName; } /** * Destructor. */ SurfaceTypeEnum::~SurfaceTypeEnum() { } /** * Initialize the enumerated metadata. */ void SurfaceTypeEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(SurfaceTypeEnum(UNKNOWN, "UNKNOWN", "Unknown", "Unknown")); enumData.push_back(SurfaceTypeEnum(RECONSTRUCTION, "RECONSTRUCTION", "Reconstruction", "Reconstruction")); enumData.push_back(SurfaceTypeEnum(ANATOMICAL, "ANATOMICAL", "Anatomical", "Anatomical")); enumData.push_back(SurfaceTypeEnum(INFLATED, "INFLATED", "Inflated", "Inflated")); enumData.push_back(SurfaceTypeEnum(VERY_INFLATED, "VERY_INFLATED", "VeryInflated", "VeryInflated")); enumData.push_back(SurfaceTypeEnum(SPHERICAL, "SPHERICAL", "Spherical", "Spherical")); enumData.push_back(SurfaceTypeEnum(SEMI_SPHERICAL, "SEMI_SPHERICAL", "SemiSpherical", "SemiSpherical")); enumData.push_back(SurfaceTypeEnum(ELLIPSOID, "ELLIPSOID", "Ellipsoid", "Ellipsoid")); enumData.push_back(SurfaceTypeEnum(FLAT, "FLAT", "Flat", "Flat")); enumData.push_back(SurfaceTypeEnum(HULL, "HULL", "Hull", "Hull")); } /** * Find the data for and enumerated value. * @param e * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const SurfaceTypeEnum* SurfaceTypeEnum::findData(const Enum e) { initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const SurfaceTypeEnum* d = &enumData[i]; if (d->e == e) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param e * Enumerated value. * @return * String representing enumerated value. */ AString SurfaceTypeEnum::toName(Enum e) { initialize(); const SurfaceTypeEnum* st = findData(e); return st->name; } /** * Get an enumerated value corresponding to its name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ SurfaceTypeEnum::Enum SurfaceTypeEnum::fromName(const AString& s, bool* isValidOut) { initialize(); bool validFlag = false; Enum e = UNKNOWN; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const SurfaceTypeEnum& d = *iter; if (d.name == s) { e = d.e; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } return e; } /** * Get a GUI string representation of the enumerated type. * @param e * Enumerated value. * @return * String representing enumerated value. */ AString SurfaceTypeEnum::toGuiName(Enum e) { initialize(); const SurfaceTypeEnum* st = findData(e); return st->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ SurfaceTypeEnum::Enum SurfaceTypeEnum::fromGuiName(const AString& s, bool* isValidOut) { initialize(); bool validFlag = false; Enum e = UNKNOWN; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const SurfaceTypeEnum& d = *iter; if (d.guiName == s) { e = d.e; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } return e; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t SurfaceTypeEnum::toIntegerCode(Enum e) { initialize(); const SurfaceTypeEnum* ndt = findData(e); return ndt->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ SurfaceTypeEnum::Enum SurfaceTypeEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { initialize(); bool validFlag = false; Enum e = UNKNOWN; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const SurfaceTypeEnum& ndt = *iter; if (ndt.integerCode == integerCode) { e = ndt.e; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } return e; } /** * Get a GIFTI Name string representation of the enumerated type. * @param e * Enumerated value. * @return * String representing enumerated value. */ AString SurfaceTypeEnum::toGiftiName(Enum e) { initialize(); const SurfaceTypeEnum* st = findData(e); return st->giftiName; } /** * Get an enumerated value corresponding to its GIFTI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ SurfaceTypeEnum::Enum SurfaceTypeEnum::fromGiftiName(const AString& s, bool* isValidOut) { initialize(); bool validFlag = false; Enum e = UNKNOWN; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const SurfaceTypeEnum& d = *iter; if (d.giftiName == s) { e = d.e; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } return e; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void SurfaceTypeEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->e); } } /** * Get all of the enumerated type values EXCEPT FLAT. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param enumsOut * A vector that is OUTPUT containing all of the enumerated values EXCEPT FLAT */ void SurfaceTypeEnum::getAllEnumsExceptFlat(std::vector& enumsOut) { std::vector allEnums; SurfaceTypeEnum::getAllEnums(allEnums); enumsOut.clear(); for (std::vector::iterator iter = allEnums.begin(); iter != allEnums.end(); iter++) { const SurfaceTypeEnum::Enum st = *iter; if (st != FLAT) { enumsOut.push_back(st); } } } /** * Get the enumerated type values that are three-dimensional in shape * but still have an anatomical-shaped appearance. * * @param anatomicalEnumsOut * A vector that is OUTPUT containing the anatomically shaped enums. */ void SurfaceTypeEnum::getAllAnatomicallyShapedEnums(std::vector& anatomicalEnumsOut) { anatomicalEnumsOut.clear(); anatomicalEnumsOut.push_back(ANATOMICAL); anatomicalEnumsOut.push_back(INFLATED); anatomicalEnumsOut.push_back(VERY_INFLATED); anatomicalEnumsOut.push_back(RECONSTRUCTION); } /** * Constructor. * * @param e * An enumerated value. * @param name * Name of enumberated value. */ SecondarySurfaceTypeEnum::SecondarySurfaceTypeEnum(const Enum e, const AString& name, const AString& guiName, const AString& giftiName) { this->e = e; this->integerCode = SecondarySurfaceTypeEnum::integerCodeGenerator++; this->name = name; this->guiName = guiName; this->giftiName = giftiName; } /** * Initialize the enumerated metadata. */ void SecondarySurfaceTypeEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(SecondarySurfaceTypeEnum(INVALID, "INVALID", "Invalid", "Invalid")); enumData.push_back(SecondarySurfaceTypeEnum(GRAY_WHITE, "GRAY_WHITE", "GrayWhite", "GrayWhite")); enumData.push_back(SecondarySurfaceTypeEnum(MIDTHICKNESS, "MIDTHICKNESS", "Midthickness",//I'm assuming that middle capital T is not desired in the gui "MidThickness"));//but it is used in the data format for caret6 enumData.push_back(SecondarySurfaceTypeEnum(PIAL, "PIAL", "Pial", "Pial")); } /** * Find the data for and enumerated value. * @param e * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const SecondarySurfaceTypeEnum* SecondarySurfaceTypeEnum::findData(const Enum e) { initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const SecondarySurfaceTypeEnum* d = &enumData[i]; if (d->e == e) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param e * Enumerated value. * @return * String representing enumerated value. */ AString SecondarySurfaceTypeEnum::toName(Enum e) { initialize(); const SecondarySurfaceTypeEnum* st = findData(e); return st->name; } /** * Get an enumerated value corresponding to its name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ SecondarySurfaceTypeEnum::Enum SecondarySurfaceTypeEnum::fromName(const AString& s, bool* isValidOut) { initialize(); bool validFlag = false; Enum e = INVALID; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const SecondarySurfaceTypeEnum& d = *iter; if (d.name == s) { e = d.e; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } return e; } /** * Get a GUI string representation of the enumerated type. * @param e * Enumerated value. * @return * String representing enumerated value. */ AString SecondarySurfaceTypeEnum::toGuiName(Enum e) { initialize(); const SecondarySurfaceTypeEnum* st = findData(e); return st->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ SecondarySurfaceTypeEnum::Enum SecondarySurfaceTypeEnum::fromGuiName(const AString& s, bool* isValidOut) { initialize(); bool validFlag = false; Enum e = INVALID; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const SecondarySurfaceTypeEnum& d = *iter; if (d.guiName == s) { e = d.e; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } return e; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t SecondarySurfaceTypeEnum::toIntegerCode(Enum e) { initialize(); const SecondarySurfaceTypeEnum* ndt = findData(e); return ndt->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ SecondarySurfaceTypeEnum::Enum SecondarySurfaceTypeEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { initialize(); bool validFlag = false; Enum e = INVALID; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const SecondarySurfaceTypeEnum& ndt = *iter; if (ndt.integerCode == integerCode) { e = ndt.e; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } return e; } /** * Get a GIFTI Name string representation of the enumerated type. * @param e * Enumerated value. * @return * String representing enumerated value. */ AString SecondarySurfaceTypeEnum::toGiftiName(Enum e) { initialize(); const SecondarySurfaceTypeEnum* st = findData(e); return st->giftiName; } /** * Get an enumerated value corresponding to its GIFTI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ SecondarySurfaceTypeEnum::Enum SecondarySurfaceTypeEnum::fromGiftiName(const AString& s, bool* isValidOut) { initialize(); bool validFlag = false; Enum e = INVALID; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const SecondarySurfaceTypeEnum& d = *iter; if (d.giftiName == s) { e = d.e; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } return e; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void SecondarySurfaceTypeEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->e); } } workbench-1.1.1/src/Files/SurfaceTypeEnum.h000066400000000000000000000106141255417355300205710ustar00rootroot00000000000000#ifndef __SURFACE_TYPE_ENUM__H_ #define __SURFACE_TYPE_ENUM__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { /** * Enumerated type for */ class SurfaceTypeEnum { public: /** * Enumerated values. */ enum Enum { /** UNKNOWN */ UNKNOWN, /** Reconstruction (raw) */ RECONSTRUCTION, /** Anatomical */ ANATOMICAL, /** Inflated */ INFLATED, /** Very Inflated */ VERY_INFLATED, /** Spherical */ SPHERICAL, /** Semi-Spherical (CMW) */ SEMI_SPHERICAL, /** Ellipsoid */ ELLIPSOID, /** Flat */ FLAT, /** Hull */ HULL }; ~SurfaceTypeEnum(); static AString toName(Enum e); static Enum fromName(const AString& s, bool* isValidOut); static AString toGuiName(Enum e); static Enum fromGuiName(const AString& s, bool* isValidOut); static AString toGiftiName(Enum e); static Enum fromGiftiName(const AString& s, bool* isValidOut); static int32_t toIntegerCode(Enum e); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllEnumsExceptFlat(std::vector& enumsOut); static void getAllAnatomicallyShapedEnums(std::vector& threeDimEnums); private: SurfaceTypeEnum(const Enum e, const AString& name, const AString& guiName, const AString& giftiName); static const SurfaceTypeEnum* findData(const Enum e); static std::vector enumData; static void initialize(); static bool initializedFlag; static int32_t integerCodeGenerator; Enum e; int32_t integerCode; AString name; AString guiName; AString giftiName; }; class SecondarySurfaceTypeEnum { public: enum Enum { INVALID, GRAY_WHITE, MIDTHICKNESS, PIAL }; static AString toName(Enum e); static Enum fromName(const AString& s, bool* isValidOut); static AString toGuiName(Enum e); static Enum fromGuiName(const AString& s, bool* isValidOut); static AString toGiftiName(Enum e); static Enum fromGiftiName(const AString& s, bool* isValidOut); static int32_t toIntegerCode(Enum e); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); private: SecondarySurfaceTypeEnum(const Enum e, const AString& name, const AString& guiName, const AString& giftiName); static const SecondarySurfaceTypeEnum* findData(const Enum e); static std::vector enumData; static void initialize(); static bool initializedFlag; static int32_t integerCodeGenerator; Enum e; int32_t integerCode; AString name; AString guiName; AString giftiName; }; #ifdef __SURFACE_TYPE_ENUM_DECLARE__ std::vector SurfaceTypeEnum::enumData; bool SurfaceTypeEnum::initializedFlag = false; int32_t SurfaceTypeEnum::integerCodeGenerator = 0; std::vector SecondarySurfaceTypeEnum::enumData; bool SecondarySurfaceTypeEnum::initializedFlag = false; int32_t SecondarySurfaceTypeEnum::integerCodeGenerator = 0; #endif // __SURFACE_TYPE_ENUM_DECLARE__ } // namespace #endif //__SURFACE_TYPE_ENUM__H_ workbench-1.1.1/src/Files/TextFile.cxx000066400000000000000000000067431255417355300176210ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "CaretLogger.h" #include "DataFileException.h" #include "TextFile.h" using namespace caret; /** * Constructor. */ TextFile::TextFile() : DataFile() { this->text = ""; } /** * Destructor. */ TextFile::~TextFile() { this->text = ""; } /** * Clear the contents of this file. */ void TextFile::clear() { DataFile::clear(); this->text = ""; } /** * Is this file empty? * * @return true if file is empty, else false. */ bool TextFile::isEmpty() const { return this->text.isEmpty(); } /** * Read the file. * * @param filename * Name of file to read. * * @throws DataFileException * If there is an error reading the file. */ void TextFile::readFile(const AString& filename) { clear(); checkFileReadability(filename); QFile file(filename); if (file.open(QFile::ReadOnly) == false) { throw DataFileException(filename, "Unable to open for reading."); } QTextStream textStream(&file); this->text = textStream.readAll(); file.close(); this->setFileName(filename); this->clearModified(); } /** * Write the file. * * @param filename * Name of file to read. * * @throws DataFileException * If there is an error writing the file. */ void TextFile::writeFile(const AString& filename) { checkFileWritability(filename); QFile file(filename); if (file.open(QFile::WriteOnly) == false) { throw DataFileException(filename, "Unable to open for writing."); } QTextStream textStream(&file); textStream << this->text; file.close(); this->setFileName(filename); this->clearModified(); } /** * Get information about this file's contents. * @return * Information about the file's contents. */ AString TextFile::toString() const { return "TextFile"; } /** * Get the file's text. * * @return The file's text. */ AString TextFile::getText() const { return this->text; } /** * Replace the file's text. * * @param text * Replaces text in the file. */ void TextFile::replaceText(const AString& text) { if (text != this->text) { this->text = text; this->setModified(); } } /** * Add to the file's text. * * @param text * Text added. */ void TextFile::addText(const AString& text) { this->text += text; this->setModified(); } /** * Add to the file's text and then add * a newline character. * * @param text * Text added. */ void TextFile::addLine(const AString& text) { this->text += text; this->text += "\n"; this->setModified(); } workbench-1.1.1/src/Files/TextFile.h000066400000000000000000000034211255417355300172340ustar00rootroot00000000000000#ifndef __TEXT_FILE_H__ #define __TEXT_FILE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "DataFile.h" namespace caret { /** * A simple text file. */ class TextFile : public DataFile { public: TextFile(); virtual ~TextFile(); private: TextFile(const TextFile&); TextFile& operator=(const TextFile&); public: virtual void clear(); virtual bool isEmpty() const; virtual void readFile(const AString& filename); virtual void writeFile(const AString& filename); virtual AString toString() const; AString getText() const; void replaceText(const AString& text); void addText(const AString& text); void addLine(const AString& text); private: AString text; }; } // namespace #endif // __TEXT_FILE_H__ workbench-1.1.1/src/Files/TopologyHelper.cxx000066400000000000000000000361671255417355300210540ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "SurfaceFile.h" #include "TopologyHelper.h" #include "CaretAssert.h" #include using namespace caret; using namespace std; TopologyHelperBase::TopologyHelperBase(const SurfaceFile* surfIn, bool sortFlag) { m_numNodes = surfIn->getNumberOfNodes(); m_numTris = surfIn->getNumberOfTriangles(); m_nodeInfo.resize(m_numNodes); m_boundaryCount.resize(m_numNodes); m_tileInfo.resize(m_numTris); vector tempEdgeInfo; tempEdgeInfo.reserve(m_numTris * 3);//worst case, to prevent reallocs, we will copy it over later to the exact right size for (int32_t i = 0; i < m_numNodes; ++i) {//preallocate what should be enough size for most nodes on most surfaces, including freesurfer m_nodeInfo[i].m_edges.reserve(8); m_nodeInfo[i].m_neighbors.reserve(8); m_nodeInfo[i].m_tiles.reserve(8); m_nodeInfo[i].m_whichVertex.reserve(8); } for (int32_t i = 0; i < m_numTris; ++i) { const int32_t* thisTri = surfIn->getTriangle(i); m_nodeInfo[thisTri[0]].addTileInfo(i, 0); m_nodeInfo[thisTri[1]].addTileInfo(i, 1); m_nodeInfo[thisTri[2]].addTileInfo(i, 2); }//node tiles complete, now we can sweep over nodes instead of triangles, making it easier to build node info m_maxNeigh = -1; m_maxTiles = -1; CaretArray scratch(m_numNodes, -1);//mark array for added neighbors for (int32_t i = 0; i < m_numNodes; ++i) { vector& tileList = m_nodeInfo[i].m_tiles; vector& vertexList = m_nodeInfo[i].m_whichVertex; int neighTiles = (int)tileList.size(); if (neighTiles > m_maxTiles) { m_maxTiles = neighTiles; } for (int j = 0; j < neighTiles; ++j) { int32_t myTile = tileList[j]; const int32_t* thisTri = surfIn->getTriangle(myTile); int32_t myVert = vertexList[j]; switch (myVert) { case 0: if (thisTri[1] > i) processTileNeighbor(tempEdgeInfo, scratch, i, thisTri[1], thisTri[2], myTile, 0, false);//boolean signifies if root, neighbor is same ordering as the cycle of tile nodes if (thisTri[2] > i) processTileNeighbor(tempEdgeInfo, scratch, i, thisTri[2], thisTri[1], myTile, 2, true); break;//the if statement is a trick: processTileNeighbor adds neighbor to both nodes, so by checking that root is less, it does every edge exactly once case 1://this allows edge info building in a linear pass if (thisTri[2] > i) processTileNeighbor(tempEdgeInfo, scratch, i, thisTri[2], thisTri[0], myTile, 1, false); if (thisTri[0] > i) processTileNeighbor(tempEdgeInfo, scratch, i, thisTri[0], thisTri[2], myTile, 0, true); break; case 2: if (thisTri[0] > i) processTileNeighbor(tempEdgeInfo, scratch, i, thisTri[0], thisTri[1], myTile, 2, false); if (thisTri[1] > i) processTileNeighbor(tempEdgeInfo, scratch, i, thisTri[1], thisTri[0], myTile, 1, true); } } vector& myNeighList = m_nodeInfo[i].m_neighbors; vector& myEdgeList = m_nodeInfo[i].m_edges; int numNeigh = (int)myNeighList.size(); if (numNeigh > m_maxNeigh) { m_maxNeigh = numNeigh; } m_boundaryCount[i] = 0; for (int j = 0; j < numNeigh; ++j) { if (tempEdgeInfo[myEdgeList[j]].numTiles == 1) ++m_boundaryCount[i]; scratch[myNeighList[j]] = -1;//NOTE: -1 as sentinel because 0 is a valid edge number } }//neighbor, edge and tile info done m_edgeInfo = tempEdgeInfo;//copy edge info into member to get allocation correct CaretArray scratch2(m_numTris, -1); if (sortFlag) { for (int32_t i = 0; i < m_numNodes; ++i) { sortNeighbors(surfIn, i, scratch, scratch2);//not a member function of node info object because I need m_edgeInfo and m_nodeInfo } m_neighborsSorted = true; } else { m_neighborsSorted = false; } } //1) check mark array // a) if marked, find edge, add triangle to edge // b) if unmarked, make edge from triangle, add neighbor, add reverse neighbor void TopologyHelperBase::processTileNeighbor(vector& tempEdgeInfo, CaretArray& scratch, const int32_t& root, const int32_t& neighbor, const int32_t& thirdNode, const int32_t& tile, const int32_t& tileEdge, const bool& reversed) { if (scratch[neighbor] == -1) { TopologyEdgeInfo tempInfo(root, neighbor, thirdNode, tile, tileEdge, reversed); int32_t myEdge = (int32_t)tempEdgeInfo.size(); tempEdgeInfo.push_back(tempInfo); m_nodeInfo[root].addNeighborInfo(neighbor, myEdge); m_nodeInfo[neighbor].addNeighborInfo(root, myEdge); scratch[neighbor] = myEdge;//use mark array both as "have this neighbor" AND "this is this neighbor's edge" m_tileInfo[tile].edges[tileEdge].edge = myEdge; } else { tempEdgeInfo[scratch[neighbor]].addTile(thirdNode, tile, tileEdge, reversed); m_tileInfo[tile].edges[tileEdge].edge = scratch[neighbor]; } m_tileInfo[tile].edges[tileEdge].reversed = reversed; } void TopologyHelperBase::sortNeighbors(const SurfaceFile* mySurf, const int32_t& node, CaretArray& nodeScratch, CaretArray& tileScratch) { TopologyHelperBase::NodeInfo& myNodeInfo = m_nodeInfo[node]; if (myNodeInfo.m_neighbors.size() == 0) return; int firstIndex = 0, numNeigh = (int)myNodeInfo.m_neighbors.size(); for (int i = 0; i < numNeigh; ++i) { int32_t thisEdge = myNodeInfo.m_edges[i]; if (m_edgeInfo[thisEdge].numTiles == 1)//there cannot be edge info with zero tiles, we are looking for the edge of a cut { firstIndex = i; if ((m_edgeInfo[thisEdge].node1 == node) != m_edgeInfo[thisEdge].tiles[0].edgeReversed)//this checks if the edge from center to neighbor is oriented with the tile {//since edge info is always node1 < node2, we have to xor (this center is node1) and (this edge is reversed compared to its only triangle) break;//found one }//the reason the break is in the additional if, is so that if we don't find a correctly oriented edge, we still find an edge if one exists } } vector tempNeigh; vector tempEdges, tempTiles;//why not sort everything? verts get regenerated in place int numTiles = myNodeInfo.m_tiles.size(); tempNeigh.reserve(numNeigh); tempEdges.reserve(numNeigh); tempTiles.reserve(numTiles); int32_t nextNode = myNodeInfo.m_neighbors[firstIndex]; int32_t nextEdge = myNodeInfo.m_edges[firstIndex]; int32_t nextTile; bool foundNext = true; int tileToUse = 0; if (m_edgeInfo[nextEdge].numTiles > 1) { if (m_edgeInfo[nextEdge].tiles[0].edgeReversed != (m_edgeInfo[nextEdge].node1 == nextNode)) { tileToUse = 1; } } do { nextTile = m_edgeInfo[nextEdge].tiles[tileToUse].tile; int32_t node3 = m_edgeInfo[nextEdge].tiles[tileToUse].node3; nextEdge = -1; for (int i = 0; i < 3; ++i) { if (m_edgeInfo[m_tileInfo[nextTile].edges[i].edge].node1 == node) { if (m_edgeInfo[m_tileInfo[nextTile].edges[i].edge].node2 == node3) { nextEdge = m_tileInfo[nextTile].edges[i].edge; break; } } else { if (m_edgeInfo[m_tileInfo[nextTile].edges[i].edge].node1 == node3 && m_edgeInfo[m_tileInfo[nextTile].edges[i].edge].node2 == node) { nextEdge = m_tileInfo[nextTile].edges[i].edge; break; } } } CaretAssert(nextEdge != -1); tempNeigh.push_back(nextNode); tempEdges.push_back(nextEdge); nodeScratch[nextNode] = 0;//remember, -1 is "unused" tempTiles.push_back(nextTile); nextNode = node3;//update the next node we are going to use tileScratch[nextTile] = 0; tileToUse = 0; if (tileScratch[m_edgeInfo[nextEdge].tiles[0].tile] == 0 && m_edgeInfo[nextEdge].numTiles > 1) { tileToUse = 1; } if (tileScratch[m_edgeInfo[nextEdge].tiles[tileToUse].tile] == 0) { foundNext = false; } } while (foundNext); for (int i = 0; i < numNeigh; ++i)//clean up scratch array, find any neighbors that are gap-separated or on third+ tile of an edge { if (nodeScratch[myNodeInfo.m_neighbors[i]] == 0) { nodeScratch[myNodeInfo.m_neighbors[i]] = -1; } else { tempNeigh.push_back(myNodeInfo.m_neighbors[i]); tempEdges.push_back(myNodeInfo.m_edges[i]); } } CaretAssert(tempNeigh.size() == myNodeInfo.m_neighbors.size());//check against original size CaretAssert(tempEdges.size() == myNodeInfo.m_edges.size()); myNodeInfo.m_neighbors = tempNeigh;//copy over myNodeInfo.m_edges = tempEdges; for (int i = 0; i < numTiles; ++i)//and find similar tiles { if (tileScratch[myNodeInfo.m_tiles[i]] == 0) { tileScratch[myNodeInfo.m_tiles[i]] = -1; } else { tempTiles.push_back(myNodeInfo.m_tiles[i]); } } CaretAssert(tempTiles.size() == myNodeInfo.m_tiles.size()); myNodeInfo.m_tiles = tempTiles; for (int i = 0; i < numTiles; ++i)//finally, regenerate verts { const int32_t* myTri = mySurf->getTriangle(myNodeInfo.m_tiles[i]); if (myTri[0] == node) { myNodeInfo.m_whichVertex[i] = 0; } else if (myTri[1] == node) { myNodeInfo.m_whichVertex[i] = 1; } else { myNodeInfo.m_whichVertex[i] = 2; } } } TopologyHelper::TopologyHelper(CaretPointer myBase) : m_base(myBase), m_nodeInfo(myBase->m_nodeInfo), m_edgeInfo(myBase->m_edgeInfo), m_tileInfo(myBase->m_tileInfo), m_boundaryCount(myBase->m_boundaryCount) {//pointer is by-value so that it makes a private copy that can't be pointed elsewhere during this constructor m_maxNeigh = m_base->m_maxNeigh; m_neighborsSorted = m_base->m_neighborsSorted; m_numNodes = m_base->m_numNodes; } const vector& TopologyHelper::getNumberOfBoundaryEdgesForAllNodes() const { return m_boundaryCount; } int32_t TopologyHelper::getMaximumNumberOfNeighbors() const { return m_maxNeigh; } bool TopologyHelper::getNodeHasNeighbors(const int32_t nodeNum) const { CaretAssertVectorIndex(m_nodeInfo, nodeNum); return m_nodeInfo[nodeNum].m_neighbors.size() != 0; } const vector& TopologyHelper::getNodeNeighbors(const int32_t nodeNum) const { CaretAssertVectorIndex(m_nodeInfo, nodeNum); return m_nodeInfo[nodeNum].m_neighbors; } const int32_t* TopologyHelper::getNodeNeighbors(const int32_t nodeNum, int32_t& numNeighborsOut) const { CaretAssertVectorIndex(m_nodeInfo, nodeNum); numNeighborsOut = (int32_t)m_nodeInfo[nodeNum].m_neighbors.size(); return m_nodeInfo[nodeNum].m_neighbors.data(); } int32_t TopologyHelper::getNodeNumberOfNeighbors(const int32_t nodeNum) const { CaretAssertVectorIndex(m_nodeInfo, nodeNum); return m_nodeInfo[nodeNum].m_neighbors.size(); } const vector& TopologyHelper::getNodeTiles(const int32_t nodeNum) const { CaretAssertVectorIndex(m_nodeInfo, nodeNum); return m_nodeInfo[nodeNum].m_tiles; } const int32_t* TopologyHelper::getNodeTiles(const int32_t nodeNum, int32_t& numTilesOut) const { CaretAssertVectorIndex(m_nodeInfo, nodeNum); numTilesOut = (int32_t)m_nodeInfo[nodeNum].m_tiles.size(); return m_nodeInfo[nodeNum].m_tiles.data(); } const vector& TopologyHelper::getNodeEdges(const int32_t nodeNum) const { CaretAssertVectorIndex(m_nodeInfo, nodeNum); return m_nodeInfo[nodeNum].m_edges; } void TopologyHelper::checkArrays() const { if (m_markNodes.size() != m_numNodes) { m_markNodes = CaretArray(m_numNodes, 0); m_nodelist[0] = CaretArray(m_numNodes); m_nodelist[1] = CaretArray(m_numNodes); } } void TopologyHelper::getNodeNeighborsToDepth(const int32_t nodeNum, const int32_t depth, vector& neighborsOut) const { if (depth < 2) { neighborsOut = getNodeNeighbors(nodeNum); return; } int32_t expected = (7 * depth * (depth + 1)) / 2; if (expected > m_numNodes / 2) expected = m_numNodes / 2; neighborsOut.clear(); neighborsOut.reserve(expected); CaretArray* curlist = &(m_nodelist[0]), *nextlist = &(m_nodelist[1]), *templist;//using raw pointers instead of CaretArray::operator= because this is single threaded int32_t curNum = 1, nextNum = 0;//curnum gets initialized to 1 because it starts with the root node CaretMutexLocker locked(&m_usingMarkNodes);//lock before possibly constructing this object's scratch space checkArrays(); m_markNodes[nodeNum] = 1; (*curlist)[0] = nodeNum;//just use iterative, because depth-first recursive does unneeded work, and has very little reason to ever be faster for (int32_t curdepth = 0; curdepth < depth; ++curdepth) { for (int32_t i = 0; i < curNum; ++i) { const vector& nodeNeighbors = m_nodeInfo[(*curlist)[i]].m_neighbors; int numNeigh = (int)nodeNeighbors.size(); for (int j = 0; j < numNeigh; ++j) { int32_t thisNode = nodeNeighbors[j]; if (m_markNodes[thisNode] == 0) { m_markNodes[thisNode] = 1; (*nextlist)[nextNum] = thisNode; ++nextNum; neighborsOut.push_back(thisNode); } } } templist = curlist; curlist = nextlist; nextlist = templist; curNum = nextNum; nextNum = 0;//we restart the list by zeroing the count, and just overwrite the old values } m_markNodes[nodeNum] = 0;//clean up the mark array int32_t numNeigh = (int32_t)neighborsOut.size(); for (int32_t i = 0; i < numNeigh; ++i) { m_markNodes[neighborsOut[i]] = 0; } } workbench-1.1.1/src/Files/TopologyHelper.h000066400000000000000000000201641255417355300204670ustar00rootroot00000000000000#ifndef __TOPOLOGY_HELPER_H__ #define __TOPOLOGY_HELPER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretPointer.h" namespace caret { class SurfaceFile; struct TopologyEdgeInfo { struct Tile { int32_t tile; int32_t node3; int32_t whichEdge;//whether this is edge 0 (0-1), 1 (1-2), or 2 (2-0) bool edgeReversed;//whether ordering the nodes as 1, 2, 3 results in a flipped tile compared to topology }; int32_t node1, node2; int32_t numTiles; Tile tiles[2];//should be so amazingly rare (and inherently bad) for an edge to have 3 triangles that it isn't worth making this a vector TopologyEdgeInfo()//also, a vector would have poor data locality, each edge would have its tiles list in an unrelated spot to the previous edge { numTiles = 0; } TopologyEdgeInfo(const int32_t& firstNode, const int32_t& secondNode, const int32_t& thirdNode, const int32_t& tile, const int32_t& whichEdge, const bool& reversed) { node1 = firstNode;//always called with firstNode less than secondNode, so don't need to swap node2 = secondNode; tiles[0].tile = tile; tiles[0].node3 = thirdNode; tiles[0].edgeReversed = reversed; tiles[0].whichEdge = whichEdge; numTiles = 1; } void addTile(const int32_t& thirdNode, const int32_t& tile, const int32_t& whichEdge, const bool& reversed) { if (numTiles < 2) { tiles[numTiles].tile = tile; tiles[numTiles].node3 = thirdNode; tiles[numTiles].edgeReversed = reversed; tiles[numTiles].whichEdge = whichEdge; } ++numTiles; } }; struct TopologyTileInfo { struct Edge { int32_t edge; bool reversed; }; Edge edges[3]; }; class TopologyHelperBase { TopologyHelperBase();//prevent default, copy, assign TopologyHelperBase(const TopologyHelperBase&); TopologyHelperBase& operator=(const TopologyHelperBase&); void processTileNeighbor(std::vector& tempEdgeInfo, CaretArray& scratch, const int32_t& root, const int32_t& neighbor, const int32_t& thirdNode, const int32_t& tile, const int32_t& tileEdge, const bool& reversed); void sortNeighbors(const SurfaceFile* mySurf, const int32_t& node, CaretArray& nodeScratch, CaretArray& tileScratch); struct NodeInfo { std::vector m_neighbors; std::vector m_edges;//index into the topology edges vector, matched with neighbors std::vector m_tiles; std::vector m_whichVertex;//stores which tile vertex this node is, matched to m_tiles void addTileInfo(int32_t tile, int32_t vertexNum)//don't take edge info yet because it is built after neighbor info { m_tiles.push_back(tile); m_whichVertex.push_back(vertexNum); } void addNeighborInfo(int32_t neighbor, int32_t edge)//after we have tile info, we then generate neighbor info with the help of a mark array { m_neighbors.push_back(neighbor); m_edges.push_back(edge); } }; std::vector m_nodeInfo; std::vector m_edgeInfo; std::vector m_tileInfo; std::vector m_boundaryCount; int32_t m_maxNeigh, m_maxTiles, m_numNodes, m_numTris; bool m_neighborsSorted; public: TopologyHelperBase(const SurfaceFile* surfIn, bool sortNeighbors = false); bool isNodeInfoSorted() const { return m_neighborsSorted; } friend class TopologyHelper; }; /// This class is used to determine the node neighbors and edges for a Topology File. class TopologyHelper { CaretPointer m_base; mutable CaretArray m_markNodes, m_nodelist[2];//persistent, never cleared, only initialized once, saving bazillions of nanoseconds mutable CaretMutex m_usingMarkNodes; bool m_neighborsSorted; int32_t m_numNodes, m_maxNeigh; const std::vector& m_nodeInfo;//references for convenience instead of using the m_base pointer const std::vector& m_edgeInfo; const std::vector& m_tileInfo; const std::vector& m_boundaryCount; void checkArrays() const;//used to make the thread arrays for neighbors to depth lazy (not allocated until first needed) TopologyHelper();//prevent default, copy, assign, prolly not needed since there are reference members TopologyHelper(const TopologyHelper& right); TopologyHelper& operator=(const TopologyHelper& right); public: /// Constructor for use with a TopologyHelperBase (the only way, for now) TopologyHelper(CaretPointer myBase); /// Get the number of nodes int32_t getNumberOfNodes() const { return m_numNodes; } /// See if a node has neighbors bool getNodeHasNeighbors(const int32_t nodeNum) const; /// Get the number of neighbors for a node int32_t getNodeNumberOfNeighbors(const int32_t nodeNum) const; /// Get the neighbors of a node const std::vector& getNodeNeighbors(const int32_t nodeNum) const; /// Get the neighboring nodes for a node. Returns a pointer to an array /// containing the neighbors. const int32_t* getNodeNeighbors(const int32_t nodeNum, int32_t& numNeighborsOut) const; ///get the edges of a node const std::vector& getNodeEdges(const int32_t nodeNum) const; /// Get the neighbors to a specified depth void getNodeNeighborsToDepth(const int32_t nodeNum, const int32_t depth, std::vector& neighborsOut) const; /// Get the number of boundary edges used by node const std::vector& getNumberOfBoundaryEdgesForAllNodes() const; /// Get the maximum number of neighbors of all nodes int32_t getMaximumNumberOfNeighbors() const; /// Get the tiles used by a node const std::vector& getNodeTiles(const int32_t nodeNum) const; /// Get the tiles for a node. Returns a pointer to an array /// containing the tiles. const int32_t* getNodeTiles(const int32_t nodeNum, int32_t& numTilesOut) const; /// get node sorted info validity bool isNodeInfoSorted() const { return m_neighborsSorted; } /// Get the edge information const std::vector& getEdgeInfo() const { return m_edgeInfo; } /// Get the tile information const std::vector& getTileInfo() const { return m_tileInfo; } /// Get the number of edges int32_t getNumberOfEdges() const { return m_edgeInfo.size(); } }; } #endif //__TOPOLOGY_HELPER_H__ workbench-1.1.1/src/Files/VolumeEditingModeEnum.cxx000066400000000000000000000355771255417355300223110ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __VOLUME_EDITING_MODE_DECLARE__ #include "VolumeEditingModeEnum.h" #undef __VOLUME_EDITING_MODE_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::VolumeEditingModeEnum * \brief Enumerated type for volume editing. * * * * Using this enumerated type in the GUI with an EnumComboBoxTemplate * * Header File (.h) * Forward declare the data type: * class EnumComboBoxTemplate; * * Declare the member: * EnumComboBoxTemplate* m_volumeEditingModeEnumComboBox; * * Declare a slot that is called when user changes selection * private slots: * void volumeEditingModeEnumComboBoxItemActivated(); * * Implementation File (.cxx) * Include the header files * #include "EnumComboBoxTemplate.h" * #include "VolumeEditingModeEnum.h" * * Instatiate: * m_volumeEditingModeEnumComboBox = new EnumComboBoxTemplate(this); * m_volumeEditingModeEnumComboBox->setup(); * * Get notified when the user changes the selection: * QObject::connect(m_volumeEditingModeEnumComboBox, SIGNAL(itemActivated()), * this, SLOT(volumeEditingModeEnumComboBoxItemActivated())); * * Update the selection: * m_volumeEditingModeEnumComboBox->setSelectedItem(NEW_VALUE); * * Read the selection: * const VolumeEditingModeEnum::Enum VARIABLE = m_volumeEditingModeEnumComboBox->getSelectedItem(); * */ /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * @param guiName * User-friendly name for use in user-interface. * @param toolTipText * Text for the tooltip. */ VolumeEditingModeEnum::VolumeEditingModeEnum(const Enum enumValue, const AString& name, const AString& guiName, const AString& toolTipText) { this->enumValue = enumValue; this->integerCode = integerCodeCounter++; this->name = name; this->guiName = guiName; this->toolTipText = toolTipText; } /** * Destructor. */ VolumeEditingModeEnum::~VolumeEditingModeEnum() { } /** * Initialize the enumerated metadata. */ void VolumeEditingModeEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(VolumeEditingModeEnum(VOLUME_EDITING_MODE_ON, "VOLUME_EDITING_MODE_ON", "On", "Turn voxels on at mouse click. Or move the mouse with the left " "mouse button down while holding down the CTRL (Apple key on Mac) " "and SHIFT keys.")); enumData.push_back(VolumeEditingModeEnum(VOLUME_EDITING_MODE_OFF, "VOLUME_EDITING_MODE_OFF", "Off", "Turn voxels off at mouse click. Or move the mouse with the left " "mouse button down while holding down the CTRL (Apple key on Mac) " "and SHIFT keys.")); enumData.push_back(VolumeEditingModeEnum(VOLUME_EDITING_MODE_DILATE, "VOLUME_EDITING_MODE_DILATE", "Dilate", "Dilate voxels at mouse click")); enumData.push_back(VolumeEditingModeEnum(VOLUME_EDITING_MODE_ERODE, "VOLUME_EDITING_MODE_ERODE", "Erode", "Erode voxels at mouse click")); enumData.push_back(VolumeEditingModeEnum(VOLUME_EDITING_MODE_FLOOD_FILL_2D, "VOLUME_EDITING_MODE_FLOOD_FILL_2D", "Fill 2D", "Fill closed region (2D) at mouse click")); enumData.push_back(VolumeEditingModeEnum(VOLUME_EDITING_MODE_FLOOD_FILL_3D, "VOLUME_EDITING_MODE_FLOOD_FILL_3D", "Fill 3D", "Fill closed region (3D) at mouse click")); enumData.push_back(VolumeEditingModeEnum(VOLUME_EDITING_MODE_REMOVE_CONNECTED_2D, "VOLUME_EDITING_MODE_REMOVE_CONNECTED_2D", "Remove 2D", "Remove connected region (2D) at mouse click")); enumData.push_back(VolumeEditingModeEnum(VOLUME_EDITING_MODE_REMOVE_CONNECTED_3D, "VOLUME_EDITING_MODE_REMOVE_CONNECTED_3D", "Remove 3D", "Remove connected region (3D) at mouse click")); enumData.push_back(VolumeEditingModeEnum(VOLUME_EDITING_MODE_RETAIN_CONNECTED_3D, "VOLUME_EDITING_MODE_RETAIN_CONNECTED_3D", "Retain 3D", "Remove voxel not connected to region (3D) at mouse click")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const VolumeEditingModeEnum* VolumeEditingModeEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const VolumeEditingModeEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a tool tip string of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing tooltip. */ AString VolumeEditingModeEnum::toToolTip(Enum enumValue) { if (initializedFlag == false) initialize(); const VolumeEditingModeEnum* enumInstance = findData(enumValue); return enumInstance->toolTipText; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString VolumeEditingModeEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const VolumeEditingModeEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ VolumeEditingModeEnum::Enum VolumeEditingModeEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = VolumeEditingModeEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const VolumeEditingModeEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type VolumeEditingModeEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString VolumeEditingModeEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const VolumeEditingModeEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ VolumeEditingModeEnum::Enum VolumeEditingModeEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = VolumeEditingModeEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const VolumeEditingModeEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type VolumeEditingModeEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t VolumeEditingModeEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const VolumeEditingModeEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ VolumeEditingModeEnum::Enum VolumeEditingModeEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = VolumeEditingModeEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const VolumeEditingModeEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type VolumeEditingModeEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void VolumeEditingModeEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void VolumeEditingModeEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(VolumeEditingModeEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void VolumeEditingModeEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(VolumeEditingModeEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } /** * Does the enum value allow oblique editing? * * @param enumValue * The editing mode. * @return * True if the enum value allows oblique slice editing, else false. */ bool VolumeEditingModeEnum::isObliqueEditingAllowed(const Enum enumValue) { bool obliqueEditingSupportedFlag = false; switch (enumValue) { case VolumeEditingModeEnum::VOLUME_EDITING_MODE_ON: case VolumeEditingModeEnum::VOLUME_EDITING_MODE_OFF: obliqueEditingSupportedFlag = true; break; case VolumeEditingModeEnum::VOLUME_EDITING_MODE_DILATE: case VolumeEditingModeEnum::VOLUME_EDITING_MODE_ERODE: case VolumeEditingModeEnum::VOLUME_EDITING_MODE_FLOOD_FILL_2D: case VolumeEditingModeEnum::VOLUME_EDITING_MODE_FLOOD_FILL_3D: case VolumeEditingModeEnum::VOLUME_EDITING_MODE_REMOVE_CONNECTED_2D: case VolumeEditingModeEnum::VOLUME_EDITING_MODE_REMOVE_CONNECTED_3D: case VolumeEditingModeEnum::VOLUME_EDITING_MODE_RETAIN_CONNECTED_3D: break; } return obliqueEditingSupportedFlag; } workbench-1.1.1/src/Files/VolumeEditingModeEnum.h000066400000000000000000000075271255417355300217300ustar00rootroot00000000000000#ifndef __VOLUME_EDITING_MODE_ENUM_H__ #define __VOLUME_EDITING_MODE_ENUM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class VolumeEditingModeEnum { public: /** * Enumerated values. */ enum Enum { /** Turn voxels on */ VOLUME_EDITING_MODE_ON, /** Turn voxels off */ VOLUME_EDITING_MODE_OFF, /** Dilate */ VOLUME_EDITING_MODE_DILATE, /** Erode */ VOLUME_EDITING_MODE_ERODE, /** Flood Fill 2D */ VOLUME_EDITING_MODE_FLOOD_FILL_2D, /** Flood Fill 3D */ VOLUME_EDITING_MODE_FLOOD_FILL_3D, /** Remove connected voxels */ VOLUME_EDITING_MODE_REMOVE_CONNECTED_2D, /** Remove connected voxels 3D */ VOLUME_EDITING_MODE_REMOVE_CONNECTED_3D, /** Retain connected voxels 3D */ VOLUME_EDITING_MODE_RETAIN_CONNECTED_3D }; ~VolumeEditingModeEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static AString toToolTip(Enum enumValue); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); static bool isObliqueEditingAllowed(const Enum enumValue); private: VolumeEditingModeEnum(const Enum enumValue, const AString& name, const AString& guiName, const AString& toolTipText); static const VolumeEditingModeEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; AString toolTipText; }; #ifdef __VOLUME_EDITING_MODE_DECLARE__ std::vector VolumeEditingModeEnum::enumData; bool VolumeEditingModeEnum::initializedFlag = false; int32_t VolumeEditingModeEnum::integerCodeCounter = 0; #endif // __VOLUME_EDITING_MODE_DECLARE__ } // namespace #endif //__VOLUME_EDITING_MODE_ENUM_H__ workbench-1.1.1/src/Files/VolumeFile.cxx000066400000000000000000002124301255417355300201340ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #include "CaretHttpManager.h" #include "CaretLogger.h" #include "CaretTemporaryFile.h" #include "ChartDataCartesian.h" #include "ChartDataSource.h" #include "DataFileContentInformation.h" #include "ElapsedTimer.h" #include "GroupAndNameHierarchyModel.h" #include "FastStatistics.h" #include "Histogram.h" #include "MultiDimIterator.h" #include "NiftiIO.h" #include "Palette.h" #include "SceneClass.h" #include "VolumeFile.h" #include "VolumeFileEditorDelegate.h" #include "VolumeFileVoxelColorizer.h" #include "VolumeSpline.h" #include using namespace caret; using namespace std; const float VolumeFile::INVALID_INTERP_VALUE = 0.0f;//we may want NaN or something more obvious bool VolumeFile::s_voxelColoringEnabled = true; /** * Static method that sets the status of voxel coloring. Coloring may take * time and is almost never needed during command line operations (wb_command). * * DO NOT CHANGE THIS VALUE if there are any instance of VolumeFile since * the coloring object is created when a VolumeFile instance is created. * * @param enabled * New status for coloring. */ void VolumeFile::setVoxelColoringEnabled(const bool enabled) { s_voxelColoringEnabled = enabled; CaretLogConfig(AString(s_voxelColoringEnabled ? "Volume coloring is enabled." : "Volume coloring is disabled.")); } VolumeFile::VolumeFile() : VolumeBase(), CaretMappableDataFile(DataFileTypeEnum::VOLUME) { m_fileFastStatistics.grabNew(NULL); m_fileHistogram.grabNew(NULL); m_fileHistorgramLimitedValues.grabNew(NULL); m_forceUpdateOfGroupAndNameHierarchy = true; for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_chartingEnabledForTab[i] = false; } m_volumeFileEditorDelegate.grabNew(NULL); validateMembers(); } VolumeFile::VolumeFile(const vector& dimensionsIn, const vector >& indexToSpace, const int64_t numComponents, SubvolumeAttributes::VolumeType whatType) : VolumeBase(dimensionsIn, indexToSpace, numComponents), CaretMappableDataFile(DataFileTypeEnum::VOLUME) { m_fileFastStatistics.grabNew(NULL); m_fileHistogram.grabNew(NULL); m_fileHistorgramLimitedValues.grabNew(NULL); m_forceUpdateOfGroupAndNameHierarchy = true; for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_chartingEnabledForTab[i] = false; } m_volumeFileEditorDelegate.grabNew(NULL); validateMembers(); setType(whatType); } void VolumeFile::reinitialize(const vector& dimensionsIn, const vector >& indexToSpace, const int64_t numComponents, SubvolumeAttributes::VolumeType whatType) { clear(); VolumeBase::reinitialize(dimensionsIn, indexToSpace, numComponents); validateMembers(); setType(whatType); } void VolumeFile::reinitialize(const VolumeSpace& volSpaceIn, const int64_t numFrames, const int64_t numComponents, SubvolumeAttributes::VolumeType whatType) { CaretAssert(numFrames > 0); const int64_t* dimsPtr = volSpaceIn.getDims(); vector dims(dimsPtr, dimsPtr + 3); if (numFrames > 1) { dims.push_back(numFrames); } reinitialize(dims, volSpaceIn.getSform(), numComponents, whatType); } void VolumeFile::addSubvolumes(const int64_t& numToAdd) { VolumeBase::addSubvolumes(numToAdd); validateMembers(); } SubvolumeAttributes::VolumeType VolumeFile::getType() const { CaretAssertVectorIndex(m_caretVolExt.m_attributes, 0); CaretAssert(m_caretVolExt.m_attributes[0] != NULL); return m_caretVolExt.m_attributes[0]->m_type; } void VolumeFile::setType(SubvolumeAttributes::VolumeType whatType) { int numAttrs = (int)m_caretVolExt.m_attributes.size(); for (int i = 0; i < numAttrs; ++i) { CaretAssert(m_caretVolExt.m_attributes[i] != NULL); if (m_caretVolExt.m_attributes[i]->m_type != whatType) { m_caretVolExt.m_attributes[i]->m_type = whatType; if (whatType == SubvolumeAttributes::LABEL) { m_caretVolExt.m_attributes[i]->m_palette.grabNew(NULL); m_caretVolExt.m_attributes[i]->m_labelTable.grabNew(new GiftiLabelTable()); } else { m_caretVolExt.m_attributes[i]->m_palette.grabNew(new PaletteColorMapping()); m_caretVolExt.m_attributes[i]->m_labelTable.grabNew(NULL); if (whatType == SubvolumeAttributes::ANATOMY) { m_caretVolExt.m_attributes[i]->m_palette->setSelectedPaletteName(Palette::GRAY_INTERP_POSITIVE_PALETTE_NAME); m_caretVolExt.m_attributes[i]->m_palette->setScaleMode(PaletteScaleModeEnum::MODE_AUTO_SCALE_PERCENTAGE); } } } } } VolumeFile::~VolumeFile() { clear(); } /** * Clear the file. */ void VolumeFile::clear() { CaretMappableDataFile::clear(); m_voxelColorizer.grabNew(NULL); m_classNameHierarchy.grabNew(NULL); m_forceUpdateOfGroupAndNameHierarchy = true; m_fileFastStatistics.grabNew(NULL); m_fileHistogram.grabNew(NULL); m_fileHistorgramLimitedValues.grabNew(NULL); m_caretVolExt.clear(); m_brickAttributes.clear(); m_brickStatisticsValid = false; m_splinesValid = false; m_frameSplineValid.clear(); m_frameSplines.clear(); m_dataRangeValid = false; VolumeBase::clear(); m_volumeFileEditorDelegate->clear(); } void VolumeFile::readFile(const AString& filename) { ElapsedTimer timer; timer.start(); clear(); { /* * CaretTemporaryFile must be outside of the "if" statment block of code. * Otherwise, at the end of the "if" statement block, the * CaretTemporaryFile object will go out of scope and the temporary * file will be deleted so there will be no file to read. */ AString fileToRead; CaretTemporaryFile tempFile; if (DataFile::isFileOnNetwork(filename)) { tempFile.readFile(filename); fileToRead = tempFile.getFileName(); setFileName(filename); } else { setFileName(filename); fileToRead = filename; } checkFileReadability(fileToRead); NiftiIO myIO;//begin nifti specific code - should this go somewhere else? myIO.openRead(fileToRead); const NiftiHeader& inHeader = myIO.getHeader(); int numComponents = myIO.getNumComponents(); vector myDims = myIO.getDimensions(); int fullDims = 3;//deal with nifti with less than 3 dimensions if (myDims.size() < 3) fullDims = (int)myDims.size(); vector extraDims;//non-spatial dims if (myDims.size() > 3) { extraDims = vector(myDims.begin() + 3, myDims.end()); } while (myDims.size() < 3) myDims.push_back(1);//pretend we have 3 dimensions in header, always, things that use getOriginalDimensions assume this (because "VolumeFile") reinitialize(myDims, inHeader.getSForm(), numComponents); setFileName(filename); // must be donw after reinitialize() since it calls clear() which clears the name of the file int64_t frameSize = myDims[0] * myDims[1] * myDims[2]; if (numComponents != 1) { vector tempFrame(frameSize), readBuffer(frameSize * numComponents); for (MultiDimIterator myiter(extraDims); !myiter.atEnd(); ++myiter) { myIO.readData(readBuffer.data(), fullDims, *myiter); for (int c = 0; c < numComponents; ++c) { for (int64_t i = 0; i < frameSize; ++i) { tempFrame[i] = readBuffer[i * numComponents + c]; } setFrame(tempFrame.data(), getBrickIndexFromNonSpatialIndexes(*myiter), c); } } } else {//avoid the added allocation for separating components vector tempFrame(frameSize); for (MultiDimIterator myiter(extraDims); !myiter.atEnd(); ++myiter) { myIO.readData(tempFrame.data(), fullDims, *myiter); setFrame(tempFrame.data(), getBrickIndexFromNonSpatialIndexes(*myiter)); } } CaretLogFine("Time to read volume data is " + AString::number(timer.getElapsedTimeSeconds(), 'f', 3) + " seconds."); m_header.grabNew(new NiftiHeader(inHeader));//end nifti-specific code parseExtensions(); clearModified(); } m_volumeFileEditorDelegate->updateIfVolumeFileChangedNumberOfMaps(); /* * This will update the map name/label hierarchy */ if (isMappedWithLabelTable()) { m_forceUpdateOfGroupAndNameHierarchy = true; getGroupAndNameHierarchyModel(); } CaretLogFine("Total Time to read and process volume is " + AString::number(timer.getElapsedTimeSeconds(), 'f', 3) + " seconds."); } /** * Write the data file. * * @param filename * Name of the data file. * @throws DataFileException * If the file was not successfully written. */ void VolumeFile::writeFile(const AString& filename) { checkFileWritability(filename); if (getNumberOfComponents() != 1) { throw DataFileException(filename, "writing multi-component volumes is not currently supported");//its a hassle, and uncommon, and there is only one 3-component type, restricted to 0-255 } updateCaretExtension(); NiftiHeader outHeader;//begin nifti-specific code if (m_header != NULL && (m_header->getType() == AbstractHeader::NIFTI)) { outHeader = *((NiftiHeader*)m_header.getPointer());//also shallow copies extensions } outHeader.clearDataScaling(); outHeader.setSForm(getVolumeSpace().getSform()); outHeader.setDimensions(getOriginalDimensions()); outHeader.setDataType(NIFTI_TYPE_FLOAT32); NiftiIO myIO; int outVersion = 1; if (!outHeader.canWriteVersion(1)) outVersion = 2; myIO.writeNew(filename, outHeader, outVersion); const vector& origDims = getOriginalDimensions(); vector extraDims;//non-spatial dims if (origDims.size() > 3) { extraDims = vector(origDims.begin() + 3, origDims.end()); } for (MultiDimIterator myiter(extraDims); !myiter.atEnd(); ++myiter) { myIO.writeData(getFrame(getBrickIndexFromNonSpatialIndexes(*myiter)), 3, *myiter);//NOTE: does not deal with multi-component volumes } m_header.grabNew(new NiftiHeader(outHeader));//update header to last written version, end nifti-specific code m_volumeFileEditorDelegate->clear(); m_volumeFileEditorDelegate->updateIfVolumeFileChangedNumberOfMaps(); } float VolumeFile::interpolateValue(const float* coordIn, InterpType interp, bool* validOut, const int64_t brickIndex, const int64_t component) const { return interpolateValue(coordIn[0], coordIn[1], coordIn[2], interp, validOut, brickIndex, component); } float VolumeFile::interpolateValue(const float coordIn1, const float coordIn2, const float coordIn3, InterpType interp, bool* validOut, const int64_t brickIndex, const int64_t component) const { const int64_t* dimensions = getDimensionsPtr(); switch (interp) { case CUBIC: { float indexSpace[3]; spaceToIndex(coordIn1, coordIn2, coordIn3, indexSpace); int64_t ind1low = floor(indexSpace[0]); int64_t ind2low = floor(indexSpace[1]); int64_t ind3low = floor(indexSpace[2]); int64_t ind1high = ind1low + 1; int64_t ind2high = ind2low + 1; int64_t ind3high = ind3low + 1; if (!indexValid(ind1low, ind2low, ind3low, brickIndex, component) || !indexValid(ind1high, ind2high, ind3high, brickIndex, component)) { if (validOut != NULL) *validOut = false; return INVALID_INTERP_VALUE;//check for valid coord before deconvolving the frame } int64_t whichFrame = component * dimensions[3] + brickIndex; validateSpline(brickIndex, component); if (validOut != NULL) *validOut = true; return m_frameSplines[whichFrame].sample(indexSpace); } case TRILINEAR: { float index1, index2, index3; spaceToIndex(coordIn1, coordIn2, coordIn3, index1, index2, index3); int64_t ind1low = floor(index1); int64_t ind2low = floor(index2); int64_t ind3low = floor(index3); int64_t ind1high = ind1low + 1; int64_t ind2high = ind2low + 1; int64_t ind3high = ind3low + 1; if (!indexValid(ind1low, ind2low, ind3low, brickIndex, component) || !indexValid(ind1high, ind2high, ind3high, brickIndex, component)) { if (validOut != NULL) *validOut = false; return INVALID_INTERP_VALUE; } float xhighWeight = index1 - ind1low; float xlowWeight = 1.0f - xhighWeight; float xinterp[2][2];//manually unrolled, because loops of 2 seem silly xinterp[0][0] = xlowWeight * getValue(ind1low, ind2low, ind3low, brickIndex, component) + xhighWeight * getValue(ind1high, ind2low, ind3low, brickIndex, component); xinterp[1][0] = xlowWeight * getValue(ind1low, ind2high, ind3low, brickIndex, component) + xhighWeight * getValue(ind1high, ind2high, ind3low, brickIndex, component); xinterp[0][1] = xlowWeight * getValue(ind1low, ind2low, ind3high, brickIndex, component) + xhighWeight * getValue(ind1high, ind2low, ind3high, brickIndex, component); xinterp[1][1] = xlowWeight * getValue(ind1low, ind2high, ind3high, brickIndex, component) + xhighWeight * getValue(ind1high, ind2high, ind3high, brickIndex, component); float yhighWeight = index2 - ind2low; float ylowWeight = 1.0f - yhighWeight; float yinterp[2]; yinterp[0] = ylowWeight * xinterp[0][0] + yhighWeight * xinterp[1][0]; yinterp[1] = ylowWeight * xinterp[0][1] + yhighWeight * xinterp[1][1]; float zhighWeight = index3 - ind3low; float zlowWeight = 1.0f - zhighWeight; float ret = zlowWeight * yinterp[0] + zhighWeight * yinterp[1]; if (validOut != NULL) *validOut = true; return ret; } break; case ENCLOSING_VOXEL: { int64_t index1, index2, index3; enclosingVoxel(coordIn1, coordIn2, coordIn3, index1, index2, index3); if (indexValid(index1, index2, index3, brickIndex, component)) { if (validOut != NULL) *validOut = true; return getValue(index1, index2, index3, brickIndex, component); } else { if (validOut != NULL) *validOut = false; return INVALID_INTERP_VALUE; } } break; } if (validOut != NULL) *validOut = false; return INVALID_INTERP_VALUE; } void VolumeFile::validateSpline(const int64_t brickIndex, const int64_t component) const { const int64_t* dimensions = getDimensionsPtr(); CaretAssert(brickIndex >= 0 && brickIndex < dimensions[3]);//function is public, so check inputs CaretAssert(component >= 0 && component < dimensions[4]); int64_t numFrames = dimensions[3] * dimensions[4], whichFrame = component * dimensions[3] + brickIndex; if (!m_splinesValid) { CaretMutexLocker locked(&m_splineMutex);//prevent concurrent modify access to spline state if (!m_splinesValid)//double check { m_frameSplineValid = vector(numFrames, false); m_frameSplines = vector(numFrames);//release the old spline memory m_splinesValid = true;//the only purpose of this flag is for setModified to be fast, don't worry about it becoming false again before the below happens } } CaretAssert((int64_t)m_frameSplineValid.size() == numFrames); CaretAssert((int64_t)m_frameSplines.size() == numFrames); if (!m_frameSplineValid[whichFrame]) { CaretMutexLocker locked(&m_splineMutex);//prevent concurrent modify access to spline state if (!m_frameSplineValid[whichFrame])//double check { m_frameSplines[whichFrame] = VolumeSpline(getFrame(brickIndex, component), dimensions); if (m_frameSplines[whichFrame].ignoredNonNumeric()) { CaretLogWarning("ignored non-numeric input value when calculating cubic splines in volume '" + getFileName() + "', frame #" + AString::number(brickIndex + 1)); } m_frameSplineValid[whichFrame] = true; } } } void VolumeFile::freeSpline(const int64_t brickIndex, const int64_t component) const { const int64_t* dimensions = getDimensionsPtr(); CaretAssert(brickIndex >= 0 && brickIndex < dimensions[3]);//function is public, so check inputs CaretAssert(component >= 0 && component < dimensions[4]); int64_t numFrames = dimensions[3] * dimensions[4], whichFrame = component * dimensions[3] + brickIndex; if (!m_splinesValid) { CaretMutexLocker locked(&m_splineMutex);//prevent concurrent modify access to spline state if (!m_splinesValid)//double check { m_frameSplineValid = vector(numFrames, false); m_frameSplines = vector(numFrames);//release the old spline memory m_splinesValid = true;//the only purpose of this flag is for setModified to be fast } return;//already freed, we are done } CaretAssert((int64_t)m_frameSplineValid.size() == numFrames); CaretAssert((int64_t)m_frameSplines.size() == numFrames); if (m_frameSplineValid[whichFrame]) { CaretMutexLocker locked(&m_splineMutex);//prevent concurrent modify access to spline state if (m_frameSplineValid[whichFrame])//double check { m_frameSplines[whichFrame] = VolumeSpline(); m_frameSplineValid[whichFrame] = false; } } } bool VolumeFile::matchesVolumeSpace(const VolumeFile* right) const { return getVolumeSpace().matches(right->getVolumeSpace()); } bool VolumeFile::matchesVolumeSpace(const VolumeSpace& otherSpace) const { return getVolumeSpace().matches(otherSpace); } bool VolumeFile::matchesVolumeSpace(const int64_t dims[3], const vector >& sform) const { return getVolumeSpace().matches(VolumeSpace(dims, sform)); } void VolumeFile::parseExtensions() { const int NIFTI_ECODE_CARET = 30;//this should probably go in nifti1.h if (m_header != NULL && m_header->getType() == AbstractHeader::NIFTI) { const NiftiHeader& myHeader = *((NiftiHeader*)m_header.getPointer()); int numExtensions = (int)myHeader.m_extensions.size(); int whichExt = -1, whichType = -1;//type will track caret's preference in which extension to read, the greater the type, the more it prefers it for (int i = 0; i < numExtensions; ++i) { const NiftiExtension& myNiftiExtension = *(myHeader.m_extensions[i]); switch (myNiftiExtension.m_ecode) { case NIFTI_ECODE_CARET: if (100 > whichType)//mostly to make it use the first caret extension it finds in the list of extensions { whichExt = i; whichType = 100;//caret extension gets maximum priority } break; default: break; } break; } if (whichExt != -1) { switch (whichType) { case 100://caret extension { QByteArray myByteArray(myHeader.m_extensions[whichExt]->m_bytes.data(), myHeader.m_extensions[whichExt]->m_bytes.size()); AString myString(myByteArray); m_caretVolExt.readFromXmlString(myString); break; } default: break; } } } validateMembers(); } void VolumeFile::updateCaretExtension() { const int NIFTI_ECODE_CARET = 30;//this should probably go in nifti1.h stringstream mystream; XmlWriter myWriter(mystream); m_caretVolExt.writeAsXML(myWriter); string myStr = mystream.str(); if (m_header == NULL) m_header.grabNew(new NiftiHeader()); switch (m_header->getType()) { case AbstractHeader::NIFTI: { NiftiHeader& myHeader = *((NiftiHeader*)m_header.getPointer()); int numExtensions = (int)myHeader.m_extensions.size(); for (int i = 0; i < numExtensions; ++i)//erase all existing caret extensions { NiftiExtension* myNiftiExtension = myHeader.m_extensions[i]; if (myNiftiExtension->m_ecode == NIFTI_ECODE_CARET) { myHeader.m_extensions.erase(myHeader.m_extensions.begin() + i); --i; --numExtensions; } } CaretPointer newExt(new NiftiExtension()); newExt->m_ecode = NIFTI_ECODE_CARET; int length = myStr.length(); newExt->m_bytes.resize(length + 1);//allocate a null byte for safety for (int i = 0; i < length; ++i) { newExt->m_bytes[i] = myStr[i]; } newExt->m_bytes[length] = '\0'; myHeader.m_extensions.push_back(newExt); break; } } } void VolumeFile::validateMembers() { m_dataRangeValid = false; const int64_t* dimensions = getDimensionsPtr(); m_frameSplineValid = vector(dimensions[3] * dimensions[4], false); m_frameSplines = vector(dimensions[3] * dimensions[4]);//release any previous spline memory m_splinesValid = true;//this now indicates only if they need to all be recalculated - the frame vectors will always have the correct length int numMaps = getNumberOfMaps(); m_brickAttributes.resize(numMaps);//only resize, if this was called from reinitialize, it has called clear() beforehand m_brickStatisticsValid = true; int curAttribNum = (int)m_caretVolExt.m_attributes.size(); if (curAttribNum != numMaps) { m_caretVolExt.m_attributes.resize(numMaps); } bool isLabel = false; SubvolumeAttributes::VolumeType theType = SubvolumeAttributes::ANATOMY; if (numMaps > 0 && curAttribNum > 0 && m_caretVolExt.m_attributes[0] != NULL) { theType = m_caretVolExt.m_attributes[0]->m_type; if (theType == SubvolumeAttributes::UNKNOWN) { theType = SubvolumeAttributes::ANATOMY; } if (theType == SubvolumeAttributes::LABEL) { isLabel = true; } } for (int i = 0; i < numMaps; ++i) { if (m_caretVolExt.m_attributes[i] == NULL) { m_caretVolExt.m_attributes[i].grabNew(new SubvolumeAttributes()); } m_caretVolExt.m_attributes[i]->m_type = theType; if (isLabel) { m_caretVolExt.m_attributes[i]->m_palette.grabNew(NULL); if (m_caretVolExt.m_attributes[i]->m_labelTable == NULL) { m_caretVolExt.m_attributes[i]->m_labelTable.grabNew(new GiftiLabelTable());//TODO: populate the label table by means of the frame values? } } else { m_caretVolExt.m_attributes[i]->m_labelTable.grabNew(NULL); if (m_caretVolExt.m_attributes[i]->m_palette == NULL) { m_caretVolExt.m_attributes[i]->m_palette.grabNew(new PaletteColorMapping()); if (theType == SubvolumeAttributes::ANATOMY) { m_caretVolExt.m_attributes[i]->m_palette->setSelectedPaletteName(Palette::GRAY_INTERP_POSITIVE_PALETTE_NAME); m_caretVolExt.m_attributes[i]->m_palette->setScaleMode(PaletteScaleModeEnum::MODE_AUTO_SCALE_PERCENTAGE); } } } } setPaletteNormalizationMode(PaletteNormalizationModeEnum::NORMALIZATION_SELECTED_MAP_DATA); /* * Will handle colorization of voxel data. */ if (m_voxelColorizer != NULL) { m_voxelColorizer.grabNew(NULL); } if (s_voxelColoringEnabled) { m_voxelColorizer.grabNew(new VolumeFileVoxelColorizer(this)); } if (m_classNameHierarchy == NULL) { m_classNameHierarchy.grabNew(new GroupAndNameHierarchyModel()); } m_classNameHierarchy->clear(); m_forceUpdateOfGroupAndNameHierarchy = true; m_volumeFileEditorDelegate.grabNew(new VolumeFileEditorDelegate(this)); m_volumeFileEditorDelegate->updateIfVolumeFileChangedNumberOfMaps(); } /** * Set this file as modified. */ void VolumeFile::setModified() { DataFile::setModified(); VolumeBase::setModified(); m_brickStatisticsValid = false; m_splinesValid = false; m_fileFastStatistics.grabNew(NULL); m_fileHistogram.grabNew(NULL); m_fileHistorgramLimitedValues.grabNew(NULL); } /** * Clear the modified status of this file. */ void VolumeFile::clearModified() { CaretMappableDataFile::clearModified(); clearModifiedVolumeBase(); const int32_t numMaps = getNumberOfMaps(); if (isMappedWithPalette()) { for (int32_t i = 0; i < numMaps; i++) { PaletteColorMapping* pcm = getMapPaletteColorMapping(i); pcm->clearModified(); } } else if (isMappedWithLabelTable()) { for (int32_t i = 0; i < numMaps; i++) { getMapLabelTable(i)->clearModified(); } } } /** * @eturn The modified status of this file. * * NOTE: DOES NOT include palette color mapping modified status. */ bool VolumeFile::isModifiedExcludingPaletteColorMapping() const { if (CaretMappableDataFile::isModifiedExcludingPaletteColorMapping()) { return true; } if (isModifiedVolumeBase()) { return true; } const int32_t numMaps = getNumberOfMaps(); if (isMappedWithLabelTable()) { for (int32_t i = 0; i < numMaps; i++) { if (getMapLabelTable(i)->isModified()) { return true; } } } return false; } /** * @return The structure for this file. */ StructureEnum::Enum VolumeFile::getStructure() const { return StructureEnum::INVALID; } /** * Set the structure for this file. * @param structure * New structure for this file. */ void VolumeFile::setStructure(const StructureEnum::Enum /*structure*/) { /* no structure in volulme file */ } /** * Get the name of the map at the given index. * * @param mapIndex * Index of the map. * @return * Name of the map. */ AString VolumeFile::getMapName(const int32_t mapIndex) const { CaretAssertVectorIndex(m_caretVolExt.m_attributes, mapIndex); CaretAssert(m_caretVolExt.m_attributes[mapIndex] != NULL); AString name = m_caretVolExt.m_attributes[mapIndex]->m_guiLabel; return name; } /** * Set the name of the map at the given index. * * @param mapIndex * Index of the map. * @param mapName * New name for the map. */ void VolumeFile::setMapName(const int32_t mapIndex, const AString& mapName) { CaretAssertVectorIndex(m_caretVolExt.m_attributes, mapIndex); CaretAssert(m_caretVolExt.m_attributes[mapIndex] != NULL); m_caretVolExt.m_attributes[mapIndex]->m_guiLabel = mapName; setModified(); } const GiftiMetaData* VolumeFile::getMapMetaData(const int32_t mapIndex) const { CaretAssertVectorIndex(m_brickAttributes, mapIndex); if (m_brickAttributes[mapIndex].m_metadata == NULL) { m_brickAttributes[mapIndex].m_metadata.grabNew(new GiftiMetaData()); } return m_brickAttributes[mapIndex].m_metadata; } GiftiMetaData* VolumeFile::getMapMetaData(const int32_t mapIndex) { CaretAssertVectorIndex(m_brickAttributes, mapIndex); if (m_brickAttributes[mapIndex].m_metadata == NULL) { m_brickAttributes[mapIndex].m_metadata.grabNew(new GiftiMetaData()); } return m_brickAttributes[mapIndex].m_metadata; } void VolumeFile::checkStatisticsValid() { if (m_brickStatisticsValid == false) { int32_t numMaps = getNumberOfMaps(); for (int i = 0; i < numMaps; ++i) { m_brickAttributes[i].m_fastStatistics.grabNew(NULL); m_brickAttributes[i].m_histogram.grabNew(NULL); m_brickAttributes[i].m_histogramLimitedValues.grabNew(NULL); } m_brickStatisticsValid = true; } } const FastStatistics* VolumeFile::getMapFastStatistics(const int32_t mapIndex) { CaretAssertVectorIndex(m_brickAttributes, mapIndex); checkStatisticsValid(); const int64_t* dimensions = getDimensionsPtr(); if (m_brickAttributes[mapIndex].m_fastStatistics == NULL) { m_brickAttributes[mapIndex].m_fastStatistics.grabNew(new FastStatistics(getFrame(mapIndex), dimensions[0] * dimensions[1] * dimensions[2])); } return m_brickAttributes[mapIndex].m_fastStatistics; } const Histogram* VolumeFile::getMapHistogram(const int32_t mapIndex) { CaretAssertVectorIndex(m_brickAttributes, mapIndex); checkStatisticsValid(); const int64_t* dimensions = getDimensionsPtr(); if (m_brickAttributes[mapIndex].m_histogram == NULL) { m_brickAttributes[mapIndex].m_histogram.grabNew(new Histogram(100, getFrame(mapIndex), dimensions[0] * dimensions[1] * dimensions[2])); } return m_brickAttributes[mapIndex].m_histogram; } /** * Update the Histogram for limited values. * * @param data * Data for histogram. * @param mostPositiveValueInclusive * Values more positive than this value are excluded. * @param leastPositiveValueInclusive * Values less positive than this value are excluded. * @param leastNegativeValueInclusive * Values less negative than this value are excluded. * @param mostNegativeValueInclusive * Values more negative than this value are excluded. * @param includeZeroValues * If true zero values (very near zero) are included. */ const Histogram* VolumeFile::getMapHistogram(const int32_t mapIndex, const float mostPositiveValueInclusive, const float leastPositiveValueInclusive, const float leastNegativeValueInclusive, const float mostNegativeValueInclusive, const bool includeZeroValues) { CaretAssertVectorIndex(m_brickAttributes, mapIndex); checkStatisticsValid(); const int64_t* dimensions = getDimensionsPtr(); bool updateHistogramFlag = false; if (m_brickAttributes[mapIndex].m_histogramLimitedValues == NULL) { m_brickAttributes[mapIndex].m_histogramLimitedValues.grabNew(new Histogram(100)); updateHistogramFlag = true; } else if ((mostPositiveValueInclusive != m_brickAttributes[mapIndex].m_histogramLimitedValuesMostPositiveValueInclusive) || (leastPositiveValueInclusive != m_brickAttributes[mapIndex].m_histogramLimitedValuesLeastPositiveValueInclusive) || (leastNegativeValueInclusive != m_brickAttributes[mapIndex].m_histogramLimitedValuesLeastNegativeValueInclusive) || (mostNegativeValueInclusive != m_brickAttributes[mapIndex].m_histogramLimitedValuesMostNegativeValueInclusive) || (includeZeroValues != m_brickAttributes[mapIndex].m_histogramLimitedValuesIncludeZeroValues)) { updateHistogramFlag = true; } if (updateHistogramFlag) { m_brickAttributes[mapIndex].m_histogramLimitedValues->update(getFrame(mapIndex), dimensions[0] * dimensions[1] * dimensions[2], mostPositiveValueInclusive, leastPositiveValueInclusive, leastNegativeValueInclusive, mostNegativeValueInclusive, includeZeroValues); m_brickAttributes[mapIndex].m_histogramLimitedValuesMostPositiveValueInclusive = mostPositiveValueInclusive; m_brickAttributes[mapIndex].m_histogramLimitedValuesLeastPositiveValueInclusive = leastPositiveValueInclusive; m_brickAttributes[mapIndex].m_histogramLimitedValuesLeastNegativeValueInclusive = leastNegativeValueInclusive; m_brickAttributes[mapIndex].m_histogramLimitedValuesMostNegativeValueInclusive = mostNegativeValueInclusive; m_brickAttributes[mapIndex].m_histogramLimitedValuesIncludeZeroValues = includeZeroValues; } return m_brickAttributes[mapIndex].m_histogramLimitedValues; } /** * @return The estimated size of data after it is uncompressed * and loaded into RAM. A negative value indicates that the * file size cannot be computed. */ int64_t VolumeFile::getDataSizeUncompressedInBytes() const { int64_t dimI, dimJ, dimK, dimTime, dimComp; getDimensions(dimI, dimJ, dimK, dimTime, dimComp); const int64_t numBytes = (dimI * dimJ * dimK * dimTime * dimComp * sizeof(float)); return numBytes; } /** * Get statistics describing the distribution of data * mapped with a color palette for all data within the file. * * @return * Fast statistics for data (will be NULL for data * not mapped using a palette). */ const FastStatistics* VolumeFile::getFileFastStatistics() { if (m_fileFastStatistics == NULL) { std::vector fileData; getFileData(fileData); if ( ! fileData.empty()) { m_fileFastStatistics.grabNew(new FastStatistics()); m_fileFastStatistics->update(&fileData[0], fileData.size()); } } return m_fileFastStatistics; } /** * Get histogram describing the distribution of data * mapped with a color palette for all data within * the file. * * @return * Histogram for data (will be NULL for data * not mapped using a palette). */ const Histogram* VolumeFile::getFileHistogram() { if (m_fileHistogram == NULL) { std::vector fileData; getFileData(fileData); if ( ! fileData.empty()) { m_fileHistogram.grabNew(new Histogram()); m_fileHistogram->update(&fileData[0], fileData.size()); } } return m_fileHistogram; } /** * Get histogram describing the distribution of data * mapped with a color palette for all data in the file * within the given range of values. * * @param mostPositiveValueInclusive * Values more positive than this value are excluded. * @param leastPositiveValueInclusive * Values less positive than this value are excluded. * @param leastNegativeValueInclusive * Values less negative than this value are excluded. * @param mostNegativeValueInclusive * Values more negative than this value are excluded. * @param includeZeroValues * If true zero values (very near zero) are included. * @return * Descriptive statistics for data (will be NULL for data * not mapped using a palette). */ const Histogram* VolumeFile::getFileHistogram(const float mostPositiveValueInclusive, const float leastPositiveValueInclusive, const float leastNegativeValueInclusive, const float mostNegativeValueInclusive, const bool includeZeroValues) { bool updateHistogramFlag = false; if (m_fileHistorgramLimitedValues != NULL) { if ((mostPositiveValueInclusive != m_fileHistogramLimitedValuesMostPositiveValueInclusive) || (leastPositiveValueInclusive != m_fileHistogramLimitedValuesLeastPositiveValueInclusive) || (leastNegativeValueInclusive != m_fileHistogramLimitedValuesLeastNegativeValueInclusive) || (mostNegativeValueInclusive != m_fileHistogramLimitedValuesMostNegativeValueInclusive) || (includeZeroValues != m_fileHistogramLimitedValuesIncludeZeroValues)) { updateHistogramFlag = true; } } else { updateHistogramFlag = true; } if (updateHistogramFlag) { std::vector fileData; getFileData(fileData); if ( ! fileData.empty()) { if (m_fileHistorgramLimitedValues == NULL) { m_fileHistorgramLimitedValues.grabNew(new Histogram()); } m_fileHistorgramLimitedValues->update(&fileData[0], fileData.size(), mostPositiveValueInclusive, leastPositiveValueInclusive, leastNegativeValueInclusive, mostNegativeValueInclusive, includeZeroValues); m_fileHistogramLimitedValuesMostPositiveValueInclusive = mostPositiveValueInclusive; m_fileHistogramLimitedValuesLeastPositiveValueInclusive = leastPositiveValueInclusive; m_fileHistogramLimitedValuesLeastNegativeValueInclusive = leastNegativeValueInclusive; m_fileHistogramLimitedValuesMostNegativeValueInclusive = mostNegativeValueInclusive; m_fileHistogramLimitedValuesIncludeZeroValues = includeZeroValues; } } return m_fileHistorgramLimitedValues; } /** * Get all data for a volume file. If the file is very * large this method may take a large amount of time! * * @param dataOut * Output with all data for a file. Empty if no data in file * or data is not float. */ void VolumeFile::getFileData(std::vector& dataOut) const { int64_t dimI, dimJ, dimK, dimTime, dimComp; getDimensions(dimI, dimJ, dimK, dimTime, dimComp); const int64_t mapSize = dimI * dimJ * dimK * dimComp; const int64_t numMaps = dimTime; const int64_t dataSize = mapSize * numMaps; if (dataSize <= 0) { dataOut.clear(); return; } dataOut.resize(dataSize); int64_t dataOffset = 0; for (int iMap = 0; iMap < numMaps; iMap++) { const float* mapData = getFrame(iMap); for (int64_t i = 0; i < mapSize; i++) { CaretAssertVectorIndex(dataOut, dataOffset); dataOut[dataOffset] = mapData[i]; ++dataOffset; } } CaretAssert(dataOffset == static_cast(dataOut.size())); } /** * @return Is the data in the file mapped to colors using * a palette. */ bool VolumeFile::isMappedWithPalette() const { CaretAssertVectorIndex(m_caretVolExt.m_attributes, 0); CaretAssert(m_caretVolExt.m_attributes[0] != NULL); return (m_caretVolExt.m_attributes[0]->m_type != SubvolumeAttributes::LABEL); } /** * Get the palette normalization modes that are supported by the file. * * @param modesSupportedOut * Palette normalization modes supported by a file. Will be * empty for files that are not mapped with a palette. If there * is more than one suppported mode, the first mode in the * vector is assumed to be the default mode. */ void VolumeFile::getPaletteNormalizationModesSupported(std::vector& modesSupportedOut) { modesSupportedOut.clear(); modesSupportedOut.push_back(PaletteNormalizationModeEnum::NORMALIZATION_SELECTED_MAP_DATA); modesSupportedOut.push_back(PaletteNormalizationModeEnum::NORMALIZATION_ALL_MAP_DATA); } /** * Get the palette color mapping for the map at the given index. * * @param mapIndex * Index of the map. * @return * Palette color mapping for the map (will be NULL for data * not mapped using a palette). */ PaletteColorMapping* VolumeFile::getMapPaletteColorMapping(const int32_t mapIndex) { CaretAssertVectorIndex(m_caretVolExt.m_attributes, mapIndex); CaretAssert(m_caretVolExt.m_attributes[mapIndex] != NULL); CaretAssert(m_caretVolExt.m_attributes[mapIndex]->m_palette != NULL); return m_caretVolExt.m_attributes[mapIndex]->m_palette; } /** * Get the palette color mapping for the map at the given index. * * @param mapIndex * Index of the map. * @return * Palette color mapping for the map (constant) (will be NULL for data * not mapped using a palette). */ const PaletteColorMapping* VolumeFile::getMapPaletteColorMapping(const int32_t mapIndex) const { CaretAssertVectorIndex(m_caretVolExt.m_attributes, mapIndex); CaretAssert(m_caretVolExt.m_attributes[mapIndex] != NULL); CaretAssert(m_caretVolExt.m_attributes[mapIndex]->m_palette != NULL); return m_caretVolExt.m_attributes[mapIndex]->m_palette; } /** * @return Is the data in the file mapped to colors using * a label table. */ bool VolumeFile::isMappedWithLabelTable() const { CaretAssertVectorIndex(m_caretVolExt.m_attributes, 0); CaretAssert(m_caretVolExt.m_attributes[0] != NULL); return (m_caretVolExt.m_attributes[0]->m_type == SubvolumeAttributes::LABEL); } /** * Get the label table for the map at the given index. * * @param mapIndex * Index of the map. * @return * Label table for the map (will be NULL for data * not mapped using a label table). */ GiftiLabelTable* VolumeFile::getMapLabelTable(const int32_t mapIndex) { CaretAssertVectorIndex(m_caretVolExt.m_attributes, mapIndex); CaretAssert(m_caretVolExt.m_attributes[mapIndex] != NULL); CaretAssert(m_caretVolExt.m_attributes[mapIndex]->m_labelTable != NULL); return m_caretVolExt.m_attributes[mapIndex]->m_labelTable; } /** * Get the label table for the map at the given index. * * @param mapIndex * Index of the map. * @return * Label table for the map (constant) (will be NULL for data * not mapped using a label table). */ const GiftiLabelTable* VolumeFile::getMapLabelTable(const int32_t mapIndex) const { CaretAssertVectorIndex(m_caretVolExt.m_attributes, mapIndex); CaretAssert(m_caretVolExt.m_attributes[mapIndex] != NULL); CaretAssert(m_caretVolExt.m_attributes[mapIndex]->m_labelTable != NULL); return m_caretVolExt.m_attributes[mapIndex]->m_labelTable; } /** * Get the unique ID (UUID) for the map at the given index. * * @param mapIndex * Index of the map. * @return * String containing UUID for the map. */ AString VolumeFile::getMapUniqueID(const int32_t mapIndex) const { CaretAssertVectorIndex(m_brickAttributes, mapIndex); if (m_brickAttributes[mapIndex].m_metadata == NULL) { m_brickAttributes[mapIndex].m_metadata.grabNew(new GiftiMetaData()); } return m_brickAttributes[mapIndex].m_metadata->getUniqueID(); } /** * @return Bounding box of the volumes spatial coordinates. */ void VolumeFile::getVoxelSpaceBoundingBox(BoundingBox& boundingBoxOut) const { boundingBoxOut.resetForUpdate(); float coordinates[3]; const int64_t* dimensions = getDimensionsPtr(); for (int i = 0; i < 2; ++i)//if the volume isn't plumb, we need to test all corners, so just always test all corners { for (int j = 0; j < 2; ++j) { for (int k = 0; k < 2; ++k) { this->indexToSpace(i * dimensions[0] - 0.5f, j * dimensions[1] - 0.5f, k * dimensions[2] - 0.5f, coordinates);//accounts for extra half voxel on each side of each center boundingBoxOut.update(coordinates); } } } } /** * Update coloring for a map. * Does nothing if coloring is not enabled. * * @param mapIndex * Index of map. * @param paletteFile * File containing the palettes. */ void VolumeFile::updateScalarColoringForMap(const int32_t mapIndex, const PaletteFile* paletteFile) { if (s_voxelColoringEnabled == false) { return; } CaretAssertVectorIndex(m_caretVolExt.m_attributes, mapIndex); CaretAssert(m_voxelColorizer); const bool usesPalette = isMappedWithPalette(); const PaletteColorMapping* pcm = (usesPalette ? getMapPaletteColorMapping(mapIndex) : NULL); const AString paletteName = (usesPalette ? pcm->getSelectedPaletteName() : ""); const Palette* palette = (usesPalette ? paletteFile->getPaletteByName(paletteName) : NULL); if (usesPalette && (palette == NULL)) { CaretLogSevere("No palette named \"" + paletteName + "\" found for coloring map index=" + AString::number(mapIndex) + " in " + getFileNameNoPath()); } m_voxelColorizer->assignVoxelColorsForMap(mapIndex, palette, this, mapIndex); } /** * Get the voxel RGBA coloring for a map. * Does nothing if coloring is not enabled and output colors are undefined * in this case. * * @param paletteFile * The palette file. * @param mapIndex * Index of the map. * @param slicePlane * Plane for which colors are requested. * @param sliceIndex * Index of the slice. * @param displayGroup * The selected display group. * @param tabIndex * Index of selected tab. * @param rgbaOut * Contains colors upon exit. * @return * Number of voxels with alpha greater than zero */ int64_t VolumeFile::getVoxelColorsForSliceInMap(const PaletteFile* /*paletteFile*/, const int32_t mapIndex, const VolumeSliceViewPlaneEnum::Enum slicePlane, const int64_t sliceIndex, const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, uint8_t* rgbaOut) const { if (s_voxelColoringEnabled == false) { return 0; } CaretAssert(m_voxelColorizer); return m_voxelColorizer->getVoxelColorsForSliceInMap(mapIndex, slicePlane, sliceIndex, displayGroup, tabIndex, rgbaOut); } /** * Get the voxel colors for a sub slice in the map. * * @param paletteFile * The palette file. * @param mapIndex * Index of the map. * @param slicePlane * The slice plane. * @param sliceIndex * Index of the slice. * @param firstCornerVoxelIndex * Indices of voxel for first corner of sub-slice (inclusive). * @param lastCornerVoxelIndex * Indices of voxel for last corner of sub-slice (inclusive). * @param voxelCountIJK * Voxel counts for each axis. * @param displayGroup * The selected display group. * @param tabIndex * Index of selected tab. * @param rgbaOut * Output containing the rgba values (must have been allocated * by caller to sufficient count of elements in the slice). * @return * Number of voxels with alpha greater than zero */ int64_t VolumeFile::getVoxelColorsForSubSliceInMap(const PaletteFile* /*paletteFile*/, const int32_t mapIndex, const VolumeSliceViewPlaneEnum::Enum slicePlane, const int64_t sliceIndex, const int64_t firstCornerVoxelIndex[3], const int64_t lastCornerVoxelIndex[3], const int64_t voxelCountIJK[3], const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, uint8_t* rgbaOut) const { if (s_voxelColoringEnabled == false) { return 0; } CaretAssert(m_voxelColorizer); return m_voxelColorizer->getVoxelColorsForSubSliceInMap(mapIndex, slicePlane, sliceIndex, firstCornerVoxelIndex, lastCornerVoxelIndex, voxelCountIJK, displayGroup, tabIndex, rgbaOut); } /** * Get the voxel values for a slice in a map. * * @param mapIndex * Index of the map. * @param slicePlane * Plane for which colors are requested. * @param sliceIndex * Index of the slice. * @param sliceValuesOut * Slice values output must be correct number of elements. */ void VolumeFile::getVoxelValuesForSliceInMap(const int32_t mapIndex, const VolumeSliceViewPlaneEnum::Enum slicePlane, const int64_t sliceIndex, float* sliceValuesOut) const { CaretAssert(sliceValuesOut); if (s_voxelColoringEnabled == false) { return; } int64_t dimI, dimJ, dimK, dimTime, dimMaps; getDimensions(dimI, dimJ, dimK, dimTime, dimMaps); switch (slicePlane) { case VolumeSliceViewPlaneEnum::ALL: return; break; case VolumeSliceViewPlaneEnum::AXIAL: { int64_t counter = 0; for (int64_t j = 0; j < dimJ; j++) { for (int64_t i = 0; i < dimI; i++) { sliceValuesOut[counter] = getValue(i, j, sliceIndex, 0, mapIndex); counter++; } } } break; case VolumeSliceViewPlaneEnum::CORONAL: { int64_t counter = 0; for (int64_t k = 0; k < dimK; k++) { for (int64_t i = 0; i < dimI; i++) { sliceValuesOut[counter] = getValue(i, sliceIndex, k, 0, mapIndex); counter++; } } } break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: { int64_t counter = 0; for (int64_t k = 0; k < dimK; k++) { for (int64_t j = 0; j < dimJ; j++) { sliceValuesOut[counter] = getValue(sliceIndex, j, k, 0, mapIndex); counter++; } } } break; } } /** * Get the RGBA color components for voxel. * Does nothing if coloring is not enabled and output colors are undefined * in this case. * * @param paletteFile * The palette file. * @param i * Parasaggital index * @param j * Coronal index * @param k * Axial index * @param mapIndex * Index of map. * @param displayGroup * The selected display group. * @param tabIndex * Index of selected tab. * @param rgbaOut * Contains voxel coloring on exit. */ void VolumeFile::getVoxelColorInMap(const PaletteFile* /*paletteFile*/, const int64_t i, const int64_t j, const int64_t k, const int64_t mapIndex, const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, uint8_t rgbaOut[4]) const { if (s_voxelColoringEnabled == false) { return; } CaretAssert(m_voxelColorizer); m_voxelColorizer->getVoxelColorInMap(i, j, k, mapIndex, displayGroup, tabIndex, rgbaOut); } /** * Clear the voxel coloring for the given map. * Does nothing if coloring is not enabled. * * @param mapIndex * Index of map. */ void VolumeFile::clearVoxelColoringForMap(const int64_t mapIndex) { if (s_voxelColoringEnabled == false) { return; } CaretAssert(m_voxelColorizer); m_voxelColorizer->clearVoxelColoringForMap(mapIndex); if (isMappedWithLabelTable()) { m_forceUpdateOfGroupAndNameHierarchy = true; } } /** * Get the minimum and maximum values from ALL maps in this file. * Note that not all files (due to size of file) are able to provide * the minimum and maximum values from the file. The return value * indicates success/failure. If the failure (false) is returned * the returned values are likely +/- the maximum float values. * * @param dataRangeMinimumOut * Minimum data value found. * @param dataRangeMaximumOut * Maximum data value found. * @return * True if the values are valid, else false. */ bool VolumeFile::getDataRangeFromAllMaps(float& dataRangeMinimumOut, float& dataRangeMaximumOut) const { /* * No data? */ if (isEmpty()) { dataRangeMaximumOut = std::numeric_limits::max(); dataRangeMinimumOut = -dataRangeMaximumOut; return false; } /* * If valid, no need to update */ if (m_dataRangeValid) { dataRangeMinimumOut = m_dataRangeMinimum; dataRangeMaximumOut = m_dataRangeMaximum; return true; } /* * Update range. */ m_dataRangeMaximum = -std::numeric_limits::max(); m_dataRangeMinimum = std::numeric_limits::max(); const int64_t* dimensions = getDimensionsPtr(); int64_t m_dataSize = dimensions[0] * dimensions[1] * dimensions[2] * dimensions[3] * dimensions[4]; const float* data = getFrame();//HACK: use first frame knowing all data is contiguous after it for (int64_t i = 0; i < m_dataSize; i++) { if (data[i] > m_dataRangeMaximum) { m_dataRangeMaximum = data[i]; } if (data[i] < m_dataRangeMinimum) { m_dataRangeMinimum = data[i]; } } dataRangeMinimumOut = m_dataRangeMinimum; dataRangeMaximumOut = m_dataRangeMaximum; m_dataRangeValid = true; return true; } /** * Get the voxel indices of all voxels in the given map with the given label key. * * @param mapIndex * Index of map. * @param labelKey * Key of the label. * @param voxelIndicesOut * Output containing indices of voxels with the given label key. */ void VolumeFile::getVoxelIndicesWithLabelKey(const int32_t mapIndex, const int32_t labelKey, std::vector& voxelIndicesOut) const { voxelIndicesOut.clear(); std::vector dims; getDimensions(dims); const int64_t dimI = dims[0]; const int64_t dimJ = dims[1]; const int64_t dimK = dims[2]; for (int64_t i = 0; i < dimI; i++) { for (int64_t j = 0; j < dimJ; j++) { for (int64_t k = 0; k < dimK; k++) { const float keyValue = static_cast(getValue(i, j, k, mapIndex)); if (keyValue == labelKey) { voxelIndicesOut.push_back(VoxelIJK(i, j, k)); } } } } } /** * Get the unique label keys in the given map. * @param mapIndex * Index of the map. * @return * Keys used by the map. */ std::vector VolumeFile::getUniqueLabelKeysUsedInMap(const int32_t mapIndex) const { std::vector dims; getDimensions(dims); const int64_t dimI = dims[0]; const int64_t dimJ = dims[1]; const int64_t dimK = dims[2]; std::set uniqueKeys; for (int64_t i = 0; i < dimI; i++) { for (int64_t j = 0; j < dimJ; j++) { for (int64_t k = 0; k < dimK; k++) { const float keyValue = static_cast(getValue(i, j, k, mapIndex)); uniqueKeys.insert(keyValue); } } } std::vector keyVector; keyVector.insert(keyVector.end(), uniqueKeys.begin(), uniqueKeys.end()); return keyVector; } /** * @return The class and name hierarchy. */ GroupAndNameHierarchyModel* VolumeFile::getGroupAndNameHierarchyModel() { m_classNameHierarchy->update(this, m_forceUpdateOfGroupAndNameHierarchy); m_forceUpdateOfGroupAndNameHierarchy = false; return m_classNameHierarchy; } /** * @return The class and name hierarchy. */ const GroupAndNameHierarchyModel* VolumeFile::getGroupAndNameHierarchyModel() const { m_classNameHierarchy->update(const_cast(this), m_forceUpdateOfGroupAndNameHierarchy); m_forceUpdateOfGroupAndNameHierarchy = false; return m_classNameHierarchy; } /** * @return The volume file editor delegate used for interactive * editing of a volume's voxels. */ VolumeFileEditorDelegate* VolumeFile::getVolumeFileEditorDelegate() { CaretAssert(m_volumeFileEditorDelegate); return m_volumeFileEditorDelegate; } /** * Save file data from the scene. For subclasses that need to * save to a scene, this method should be overriden. sceneClass * will be valid and any scene data should be added to it. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass to which data members should be added. */ void VolumeFile::saveFileDataToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass) { CaretMappableDataFile::saveFileDataToScene(sceneAttributes, sceneClass); sceneClass->addBooleanArray("m_chartingEnabledForTab", m_chartingEnabledForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS); if (isMappedWithLabelTable()) { sceneClass->addClass(m_classNameHierarchy->saveToScene(sceneAttributes, "m_classNameHierarchy")); } } /** * Restore file data from the scene. For subclasses that need to * restore from a scene, this method should be overridden. The scene class * will be valid and any scene data may be obtained from it. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass for the instance of a class that implements * this interface. Will NEVER be NULL. */ void VolumeFile::restoreFileDataFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { CaretMappableDataFile::restoreFileDataFromScene(sceneAttributes, sceneClass); const ScenePrimitiveArray* tabArray = sceneClass->getPrimitiveArray("m_chartingEnabledForTab"); if (tabArray != NULL) { sceneClass->getBooleanArrayValue("m_chartingEnabledForTab", m_chartingEnabledForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS); } else { for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS; i++) { m_chartingEnabledForTab[i] = false; } } if (isMappedWithLabelTable()) { const SceneClass* sc = sceneClass->getClass("m_classNameHierarchy"); m_classNameHierarchy->restoreFromScene(sceneAttributes, sc); m_forceUpdateOfGroupAndNameHierarchy = false; } } /** * Add information about the file to the data file information. * * @param dataFileInformation * Consolidates information about a data file. */ void VolumeFile::addToDataFileContentInformation(DataFileContentInformation& dataFileInformation) { CaretMappableDataFile::addToDataFileContentInformation(dataFileInformation); dataFileInformation.addNameAndValue("Orthogonal", isPlumb()); if (m_header != NULL && m_header->getType() == AbstractHeader::NIFTI) { const NiftiHeader& myHeader = *((NiftiHeader*)m_header.getPointer()); dataFileInformation.addNameAndValue("NIFTI Version", myHeader.version()); bool ok = false; dataFileInformation.addNameAndValue("NIFTI Data Type", NiftiDataTypeEnum::toName(NiftiDataTypeEnum::fromIntegerCode(myHeader.getDataType(), &ok)));//fromIntegerCode basically just ignores invalid values if (!ok) { CaretLogWarning("found invalid NIFTI datatype code while adding file information"); } } AString dimString; vector dims = getOriginalDimensions(); for (int i = 0; i < (int)dims.size(); ++i) { if (i != 0) dimString += ", "; dimString += AString::number(dims[i]); } dataFileInformation.addNameAndValue("Dimensions", dimString); const int64_t zero64 = 0; if (indexValid(zero64, zero64, zero64)) { float x, y, z; indexToSpace(zero64, zero64, zero64, x, y, z); dataFileInformation.addNameAndValue("IJK = (0,0,0)", ("XYZ = (" + AString::number(x) + ", " + AString::number(y) + ", " + AString::number(z) + ")")); } BoundingBox boundingBox; getVoxelSpaceBoundingBox(boundingBox); dataFileInformation.addNameAndValue("X-minimum", boundingBox.getMinX()); dataFileInformation.addNameAndValue("X-maximum", boundingBox.getMaxX()); dataFileInformation.addNameAndValue("Y-minimum", boundingBox.getMinY()); dataFileInformation.addNameAndValue("Y-maximum", boundingBox.getMaxY()); dataFileInformation.addNameAndValue("Z-minimum", boundingBox.getMinZ()); dataFileInformation.addNameAndValue("Z-maximum", boundingBox.getMaxZ()); VolumeSpace::OrientTypes orientation[3]; getOrientation(orientation); for (int32_t i = 0; i < 3; i++) { AString orientName; switch (orientation[i]) { case VolumeSpace::ANTERIOR_TO_POSTERIOR: orientName = "Anterior to Posterior"; break; case VolumeSpace::INFERIOR_TO_SUPERIOR: orientName = "Inferior to Superior"; break; case VolumeSpace::LEFT_TO_RIGHT: orientName = "Left to Right"; break; case VolumeSpace::POSTERIOR_TO_ANTERIOR: orientName = "Posterior to Anterior"; break; case VolumeSpace::RIGHT_TO_LEFT: orientName = "Right to Left"; break; case VolumeSpace::SUPERIOR_TO_INFERIOR: orientName = "Superior to Inferior"; break; } dataFileInformation.addNameAndValue(("Orientation[" + AString::number(i) + "]"), orientName); } float spacing[3]; getVoxelSpacing(spacing[0], spacing[1], spacing[2]); spacing[0] = std::fabs(spacing[0]); spacing[1] = std::fabs(spacing[1]); spacing[2] = std::fabs(spacing[2]); dataFileInformation.addNameAndValue("Spacing", AString::fromNumbers(spacing, 3, ", ")); } /** * @return Is charting enabled for this file? */ bool VolumeFile::isLineSeriesChartingEnabled(const int32_t tabIndex) const { CaretAssertArrayIndex(m_chartingEnabledForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); return m_chartingEnabledForTab[tabIndex]; } /** * @return Return true if the file's current state supports * charting data, else false. Typically a brainordinate file * is chartable if it contains more than one map. */ bool VolumeFile::isLineSeriesChartingSupported() const { if (getNumberOfMaps() > 1) { return true; } return false; } /** * Set charting enabled for this file. * * @param enabled * New status for charting enabled. */ void VolumeFile::setLineSeriesChartingEnabled(const int32_t tabIndex, const bool enabled) { CaretAssertArrayIndex(m_chartingEnabledForTab, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS, tabIndex); m_chartingEnabledForTab[tabIndex] = enabled; } /** * Get chart data types supported by the file. * * @param chartDataTypesOut * Chart types supported by this file. */ void VolumeFile::getSupportedLineSeriesChartDataTypes(std::vector& chartDataTypesOut) const { helpGetSupportedLineSeriesChartDataTypes(chartDataTypesOut); } /** * Load charting data for the surface with the given structure and node index. * * @param structure * The surface's structure. * @param nodeIndex * Index of the node. * @return * Pointer to the chart data. If the data FAILED to load, * the returned pointer will be NULL. Caller takes ownership * of the pointer and must delete it when no longer needed. */ ChartDataCartesian* VolumeFile::loadLineSeriesChartDataForSurfaceNode(const StructureEnum::Enum /*structure*/, const int32_t /*nodeIndex*/) { ChartDataCartesian* chartData = NULL; return chartData; } /** * Load average charting data for the surface with the given structure and node indices. * * @param structure * The surface's structure. * @param nodeIndices * Indices of the node. * @return * Pointer to the chart data. If the data FAILED to load, * the returned pointer will be NULL. Caller takes ownership * of the pointer and must delete it when no longer needed. */ ChartDataCartesian* VolumeFile::loadAverageLineSeriesChartDataForSurfaceNodes(const StructureEnum::Enum /*structure*/, const std::vector& /*nodeIndices*/) { ChartDataCartesian* chartData = NULL; return chartData; } /** * Load charting data for the voxel enclosing the given coordinate. * * @param xyz * Coordinate of voxel. * @return * Pointer to the chart data. If the data FAILED to load, * the returned pointer will be NULL. Caller takes ownership * of the pointer and must delete it when no longer needed. */ ChartDataCartesian* VolumeFile::loadLineSeriesChartDataForVoxelAtCoordinate(const float xyz[3]) { ChartDataCartesian* chartData = NULL; if (isMappedWithPalette()) { int64_t ijk[3]; enclosingVoxel(xyz, ijk); if (indexValid(ijk)) { std::vector data; const int32_t numMaps = getNumberOfMaps(); for (int32_t iMap = 0; iMap < numMaps; iMap++) { data.push_back(getValue(ijk, iMap)); } try { chartData = helpCreateCartesianChartData(data); ChartDataSource* dataSource = chartData->getChartDataSource(); dataSource->setVolumeVoxel(getFileName(), xyz); } catch (const DataFileException& dfe) { if (chartData != NULL) { delete chartData; chartData = NULL; } throw dfe; } } } return chartData; } workbench-1.1.1/src/Files/VolumeFile.h000066400000000000000000000371631255417355300175710ustar00rootroot00000000000000 #ifndef __VOLUME_FILE_H__ #define __VOLUME_FILE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "BrainConstants.h" #include "VolumeBase.h" #include "CaretMappableDataFile.h" #include "CaretMutex.h" #include "CaretVolumeExtension.h" #include "ChartableLineSeriesBrainordinateInterface.h" #include "StructureEnum.h" #include "GiftiMetaData.h" #include "BoundingBox.h" #include "PaletteFile.h" #include "VolumeFileVoxelColorizer.h" #include "VoxelIJK.h" namespace caret { class GroupAndNameHierarchyModel; class VolumeFileEditorDelegate; class VolumeFileVoxelColorizer; class VolumeSpline; class VolumeFile : public VolumeBase, public CaretMappableDataFile, public ChartableLineSeriesBrainordinateInterface { VolumeFile(const VolumeFile&); VolumeFile& operator=(const VolumeFile&); CaretVolumeExtension m_caretVolExt; void parseExtensions();//called after reading a file, in order to populate m_caretVolExt with best guesses void validateMembers();//called to ensure extension agrees with number of subvolumes void updateCaretExtension();//called before writing a file, erases all existing caret extensions from m_extensions, and rebuilds one from m_caretVolExt void checkStatisticsValid(); struct BrickAttributes//for storing ONLY stuff that doesn't get saved to the caret extension {//TODO: prune this once statistics gets straightened out CaretPointer m_fastStatistics; CaretPointer m_histogram; CaretPointer m_histogramLimitedValues; float m_histogramLimitedValuesMostPositiveValueInclusive; float m_histogramLimitedValuesLeastPositiveValueInclusive; float m_histogramLimitedValuesLeastNegativeValueInclusive; float m_histogramLimitedValuesMostNegativeValueInclusive; bool m_histogramLimitedValuesIncludeZeroValues; CaretPointer m_metadata;//NOTE: does not get saved currently! }; mutable std::vector m_brickAttributes;//because statistics and metadata construct lazily bool m_brickStatisticsValid;//so that setModified() doesn't do something slow /** Fast statistics used when statistics computed on all data in file */ CaretPointer m_fileFastStatistics; /** Histogram used when statistics computed on all data in file */ CaretPointer m_fileHistogram; /** Histogram with limited values used when statistics computed on all data in file */ CaretPointer m_fileHistorgramLimitedValues; float m_fileHistogramLimitedValuesMostPositiveValueInclusive; float m_fileHistogramLimitedValuesLeastPositiveValueInclusive; float m_fileHistogramLimitedValuesLeastNegativeValueInclusive; float m_fileHistogramLimitedValuesMostNegativeValueInclusive; bool m_fileHistogramLimitedValuesIncludeZeroValues; /** Performs coloring of voxels. Will be NULL if coloring is disabled. */ CaretPointer m_voxelColorizer; mutable CaretMutex m_splineMutex; mutable bool m_splinesValid; mutable std::vector m_frameSplineValid; mutable std::vector m_frameSplines; bool m_chartingEnabledForTab[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS]; mutable bool m_dataRangeValid; mutable float m_dataRangeMinimum; mutable float m_dataRangeMaximum; /** Holds class and name hierarchy used for display selection */ mutable CaretPointer m_classNameHierarchy; /** force an update of the class and name hierarchy */ mutable bool m_forceUpdateOfGroupAndNameHierarchy; CaretPointer m_volumeFileEditorDelegate; protected: virtual void saveFileDataToScene(const SceneAttributes* sceneAttributes, SceneClass* sceneClass); virtual void restoreFileDataFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); public: enum InterpType { ENCLOSING_VOXEL, TRILINEAR, CUBIC }; const static float INVALID_INTERP_VALUE; /** Enables coloring. Coloring is almost always not needed for command line operations */ static bool s_voxelColoringEnabled; static void setVoxelColoringEnabled(const bool enabled); VolumeFile(); VolumeFile(const std::vector& dimensionsIn, const std::vector >& indexToSpace, const int64_t numComponents = 1, SubvolumeAttributes::VolumeType whatType = SubvolumeAttributes::ANATOMY); ~VolumeFile(); virtual void clear(); virtual void addToDataFileContentInformation(DataFileContentInformation& dataFileInformation); ///recreates the volume file storage with new size and spacing void reinitialize(const std::vector& dimensionsIn, const std::vector >& indexToSpace, const int64_t numComponents = 1, SubvolumeAttributes::VolumeType whatType = SubvolumeAttributes::ANATOMY); ///convenient version for 3D or 4D from a VolumeSpace void reinitialize(const VolumeSpace& volSpaceIn, const int64_t numFrames = 1, const int64_t numComponents = 1, SubvolumeAttributes::VolumeType whatType = SubvolumeAttributes::ANATOMY); void addSubvolumes(const int64_t& numToAdd); void setType(SubvolumeAttributes::VolumeType whatType); SubvolumeAttributes::VolumeType getType() const; void validateSpline(const int64_t brickIndex = 0, const int64_t component = 0) const; void freeSpline(const int64_t brickIndex = 0, const int64_t component = 0) const; float interpolateValue(const float* coordIn, InterpType interp = TRILINEAR, bool* validOut = NULL, const int64_t brickIndex = 0, const int64_t component = 0) const; float interpolateValue(const float coordIn1, const float coordIn2, const float coordIn3, InterpType interp = TRILINEAR, bool* validOut = NULL, const int64_t brickIndex = 0, const int64_t component = 0) const; ///returns true if volume space matches in spatial dimensions and sform bool matchesVolumeSpace(const VolumeFile* right) const; ///returns true if volume space matches in spatial dimensions and sform bool matchesVolumeSpace(const VolumeSpace& otherSpace) const; ///returns true if volume space matches in spatial dimensions and sform bool matchesVolumeSpace(const int64_t dims[3], const std::vector >& sform) const; void readFile(const AString& filename); void writeFile(const AString& filename); bool isEmpty() const { return VolumeBase::isEmpty(); } virtual void setModified(); virtual void clearModified(); virtual bool isModifiedExcludingPaletteColorMapping() const; void getVoxelSpaceBoundingBox(BoundingBox& boundingBoxOut) const; /** * @return The structure for this file. */ StructureEnum::Enum getStructure() const; /** * Set the structure for this file. * @param structure * New structure for this file. */ void setStructure(const StructureEnum::Enum structure); /** * @return Get access to the file's metadata. */ GiftiMetaData* getFileMetaData() { return NULL; }//doesn't seem to be a spot for generic metadata in the nifti caret extension /** * @return Get access to unmodifiable file's metadata. */ const GiftiMetaData* getFileMetaData() const { return NULL; } bool isSurfaceMappable() const { return false; } bool isVolumeMappable() const { return true; } int32_t getNumberOfMaps() const { return getDimensionsPtr()[3]; } AString getMapName(const int32_t mapIndex) const; void setMapName(const int32_t mapIndex, const AString& mapName); const GiftiMetaData* getMapMetaData(const int32_t mapIndex) const; GiftiMetaData* getMapMetaData(const int32_t mapIndex); const FastStatistics* getMapFastStatistics(const int32_t mapIndex); const Histogram* getMapHistogram(const int32_t mapIndex); const Histogram* getMapHistogram(const int32_t mapIndex, const float mostPositiveValueInclusive, const float leastPositiveValueInclusive, const float leastNegativeValueInclusive, const float mostNegativeValueInclusive, const bool includeZeroValues); virtual int64_t getDataSizeUncompressedInBytes() const; virtual const FastStatistics* getFileFastStatistics(); virtual const Histogram* getFileHistogram(); virtual const Histogram* getFileHistogram(const float mostPositiveValueInclusive, const float leastPositiveValueInclusive, const float leastNegativeValueInclusive, const float mostNegativeValueInclusive, const bool includeZeroValues); void getFileData(std::vector& dataOut) const; bool isMappedWithPalette() const; virtual void getPaletteNormalizationModesSupported(std::vector& modesSupportedOut); PaletteColorMapping* getMapPaletteColorMapping(const int32_t mapIndex); const PaletteColorMapping* getMapPaletteColorMapping(const int32_t mapIndex) const; bool isMappedWithLabelTable() const; GiftiLabelTable* getMapLabelTable(const int32_t mapIndex); const GiftiLabelTable* getMapLabelTable(const int32_t mapIndex) const; void getVoxelIndicesWithLabelKey(const int32_t mapIndex, const int32_t labelKey, std::vector& voxelIndicesOut) const; std::vector getUniqueLabelKeysUsedInMap(const int32_t mapIndex) const; AString getMapUniqueID(const int32_t mapIndex) const; void updateScalarColoringForMap(const int32_t mapIndex, const PaletteFile* paletteFile); virtual int64_t getVoxelColorsForSliceInMap(const PaletteFile* paletteFile, const int32_t mapIndex, const VolumeSliceViewPlaneEnum::Enum slicePlane, const int64_t sliceIndex, const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, uint8_t* rgbaOut) const; virtual int64_t getVoxelColorsForSubSliceInMap(const PaletteFile* paletteFile, const int32_t mapIndex, const VolumeSliceViewPlaneEnum::Enum slicePlane, const int64_t sliceIndex, const int64_t firstCornerVoxelIndex[3], const int64_t lastCornerVoxelIndex[3], const int64_t voxelCountIJK[3], const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, uint8_t* rgbaOut) const; void getVoxelValuesForSliceInMap(const int32_t mapIndex, const VolumeSliceViewPlaneEnum::Enum slicePlane, const int64_t sliceIndex, float* sliceValues) const; void getVoxelColorInMap(const PaletteFile* paletteFile, const int64_t i, const int64_t j, const int64_t k, const int64_t mapIndex, const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, uint8_t rgbaOut[4]) const; void clearVoxelColoringForMap(const int64_t mapIndex); virtual bool getDataRangeFromAllMaps(float& dataRangeMinimumOut, float& dataRangeMaximumOut) const; GroupAndNameHierarchyModel* getGroupAndNameHierarchyModel(); const GroupAndNameHierarchyModel* getGroupAndNameHierarchyModel() const; VolumeFileEditorDelegate* getVolumeFileEditorDelegate(); virtual bool isLineSeriesChartingEnabled(const int32_t tabIndex) const; virtual void setLineSeriesChartingEnabled(const int32_t tabIndex, const bool enabled); virtual bool isLineSeriesChartingSupported() const; virtual ChartDataCartesian* loadLineSeriesChartDataForSurfaceNode(const StructureEnum::Enum structure, const int32_t nodeIndex); virtual ChartDataCartesian* loadAverageLineSeriesChartDataForSurfaceNodes(const StructureEnum::Enum structure, const std::vector& nodeIndices); virtual ChartDataCartesian* loadLineSeriesChartDataForVoxelAtCoordinate(const float xyz[3]); virtual void getSupportedLineSeriesChartDataTypes(std::vector& chartDataTypesOut) const; }; } #endif //__VOLUME_FILE_H__ workbench-1.1.1/src/Files/VolumeFileEditorDelegate.cxx000066400000000000000000001253131255417355300227410ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __VOLUME_FILE_EDITOR_DELEGATE_DECLARE__ #include "VolumeFileEditorDelegate.h" #undef __VOLUME_FILE_EDITOR_DELEGATE_DECLARE__ #include "CaretAssert.h" #include "CaretPointer.h" #include "CaretUndoStack.h" #include "Matrix4x4.h" #include "VolumeFile.h" #include "VolumeMapUndoCommand.h" #include "VoxelIJK.h" using namespace caret; /** * \class caret::VolumeFileEditorDelegate * \brief Delegate for performing editing operations on a volume file's voxels. * \ingroup Files * * Perform interactive editing operations in a GUI on a volume file * including the ability to undo, redo, and reset the editing operations. */ /** * Constructor. * * @param volumeFile * Volume that 'owns' this editor and on which editing is performed. */ VolumeFileEditorDelegate::VolumeFileEditorDelegate(VolumeFile* volumeFile) : CaretObject(), m_volumeFile(volumeFile) { CaretAssert(volumeFile); m_volumeDimensions[0] = 0; m_volumeDimensions[1] = 0; m_volumeDimensions[2] = 0; updateIfVolumeFileChangedNumberOfMaps(); } /** * Destructor. */ VolumeFileEditorDelegate::~VolumeFileEditorDelegate() { clear(); } /** * Clear the instance. */ void VolumeFileEditorDelegate::clear() { /* * The undo stacks are only created when needed so some * entries may be NULL */ for (std::vector::iterator mapIter = m_volumeMapUndoStacks.begin(); mapIter != m_volumeMapUndoStacks.end(); mapIter++) { CaretUndoStack* undoStack = *mapIter; delete undoStack; } m_volumeMapUndoStacks.clear(); m_volumeDimensions[0] = 0; m_volumeDimensions[1] = 0; m_volumeDimensions[2] = 0; m_volumeMapEditingLocked.clear(); } /** * Perform an editing operation. * * @param mapIndex * Index of map (brick) within the volume that is being edited. * @param mode * The editing mode. * @param slicePlane * The selected slice plane. * @param voxelIJK * Indices of voxel selected by the user. * @param brushSize * Size of brush used by some operations. * @param voxelValueOn * Value that is assigned by a "turn on" operation. * @param voxelValueOff * Value that is assigned by a "turn off" operation. * @param errorMessageOut * Will contain error information. * @return * True if successful, else false and errorMessageOut will be set. */ bool VolumeFileEditorDelegate::performEditingOperation(const int64_t mapIndex, const VolumeEditingModeEnum::Enum mode, const VolumeSliceViewPlaneEnum::Enum slicePlane, const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const Matrix4x4& obliqueRotationMatrix, const float voxelDiffXYZ[3], const int64_t voxelIJK[3], const int64_t brushSize[3], const float voxelValueOn, const float voxelValueOff, AString& errorMessageOut) { errorMessageOut.clear(); CaretAssert(m_volumeFile); int64_t dimNumComponents = 0; int64_t dimNumMaps = 0; m_volumeFile->getDimensions(m_volumeDimensions[0], m_volumeDimensions[1], m_volumeDimensions[2], dimNumMaps, dimNumComponents); if ((mapIndex < 0) || (mapIndex >= dimNumMaps)) { errorMessageOut = ("Invalid map index=" + AString::number(mapIndex) + ", number of maps=" + AString::number(dimNumMaps)); return false; } if (isLocked(mapIndex)) { errorMessageOut = "Volume must be unlocked (Press \"Lock\" in toolbar) to allow editing."; return false; } switch (sliceProjectionType) { case VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_OBLIQUE: if ( ! VolumeEditingModeEnum::isObliqueEditingAllowed(mode)) { errorMessageOut = (VolumeEditingModeEnum::toGuiName(mode) + " does not support editing voxels when the volume " "is in an oblique view."); return false; } break; case VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_ORTHOGONAL: break; } int64_t iHalf = brushSize[0] / 2; int64_t jHalf = brushSize[1] / 2; int64_t kHalf = brushSize[2] / 2; int64_t ijkMin[3] = { voxelIJK[0] - iHalf, voxelIJK[1] - jHalf, voxelIJK[2] - kHalf }; clampVoxelIndices(ijkMin); int64_t ijkMax[3] = { voxelIJK[0] + iHalf, voxelIJK[1] + jHalf, voxelIJK[2] + kHalf }; clampVoxelIndices(ijkMax); const EditInfo editInfo(mapIndex, mode, slicePlane, sliceProjectionType, obliqueRotationMatrix, voxelDiffXYZ, voxelIJK, ijkMin, ijkMax, brushSize, voxelValueOn, voxelValueOff); bool result = false; switch (mode) { case VolumeEditingModeEnum::VOLUME_EDITING_MODE_ON: case VolumeEditingModeEnum::VOLUME_EDITING_MODE_OFF: switch (sliceProjectionType) { case VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_OBLIQUE: result = performTurnOnOrOffOblique(editInfo, errorMessageOut); break; case VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_ORTHOGONAL: result = performTurnOnOrOffOrthogonal(editInfo, errorMessageOut); break; } break; case VolumeEditingModeEnum::VOLUME_EDITING_MODE_DILATE: result = performDilateOrErode(editInfo, errorMessageOut); break; case VolumeEditingModeEnum::VOLUME_EDITING_MODE_ERODE: result = performDilateOrErode(editInfo, errorMessageOut); break; case VolumeEditingModeEnum::VOLUME_EDITING_MODE_FLOOD_FILL_2D: result = performFloodFill2D(editInfo, errorMessageOut); break; case VolumeEditingModeEnum::VOLUME_EDITING_MODE_FLOOD_FILL_3D: result = performFloodFill3D(editInfo, errorMessageOut); break; case VolumeEditingModeEnum::VOLUME_EDITING_MODE_REMOVE_CONNECTED_2D: result = performRemoveConnected2D(editInfo, errorMessageOut); break; case VolumeEditingModeEnum::VOLUME_EDITING_MODE_REMOVE_CONNECTED_3D: result = performRemoveConnected3D(editInfo, errorMessageOut); break; case VolumeEditingModeEnum::VOLUME_EDITING_MODE_RETAIN_CONNECTED_3D: result = performRetainConnected3D(editInfo, errorMessageOut); break; } return result; } /** * @return Is the volume file locked (does not allow editing)? * * @param mapIndex * Index of map that is tested for locked. */ bool VolumeFileEditorDelegate::isLocked(const int64_t mapIndex) const { CaretAssertVectorIndex(m_volumeMapEditingLocked, mapIndex); return m_volumeMapEditingLocked[mapIndex]; } /** * Set the volume file's lock status (does not allow editing). * * If the locked status transitions to 'locked', * then CLEAR THE UNDO STACK since by locking, * the user is satisfied with changes made to the volume. * * * @param mapIndex * Index of map that has lock status set. * @param locked * New locked status. */ void VolumeFileEditorDelegate::setLocked(const int64_t mapIndex, const bool locked) { CaretAssertVectorIndex(m_volumeMapEditingLocked, mapIndex); if (locked != m_volumeMapEditingLocked[mapIndex]) { m_volumeMapEditingLocked[mapIndex] = locked; if (m_volumeMapEditingLocked[mapIndex]) { CaretAssertVectorIndex(m_volumeMapUndoStacks, mapIndex); m_volumeMapUndoStacks[mapIndex]->clear(); } } } /** * Adjust the voxel indices so that they are within the volume. * * @param ijk * Voxel indices that are adjusted to be in the range * 0 to dimension minus one. */ void VolumeFileEditorDelegate::clampVoxelIndices(int64_t ijk[3]) const { for (int32_t i = 0; i < 3; i++) { ijk[i] = clampDimensionIndex(m_volumeDimensions[i], ijk[i]); } } /** * Clamp a voxel dimensions index (zero to dim-1) * * @param maxDim * Maximum dimension value. * @param dimIndex * Value that is clamped. */ int64_t VolumeFileEditorDelegate::clampDimensionIndex(const int64_t maxDim, int64_t dimIndex) const { if (dimIndex < 0) { dimIndex = 0; } else if (dimIndex >= maxDim) { dimIndex = maxDim - 1; } return dimIndex; } /** * Clamp voxel indices. * * @param i * Index for dimension 0. * @param j * Index for dimension 1. * @param k * Index for dimension 2. */ void VolumeFileEditorDelegate::clampVoxelIndices(int64_t& i, int64_t& j, int64_t& k) const { i = clampDimensionIndex(m_volumeDimensions[0], i); j = clampDimensionIndex(m_volumeDimensions[1], j); k = clampDimensionIndex(m_volumeDimensions[2], k); } /** * Add to the modified undo stacks for the given map. * * @param mapIndex * Index of the map that was modified. * @param modifiedVoxels * Voxels that were modified. */ void VolumeFileEditorDelegate::addToMapUndoStacks(const int32_t mapIndex, VolumeMapUndoCommand* modifiedVoxels) { if (modifiedVoxels->count() <= 0) { delete modifiedVoxels; return; } CaretAssertVectorIndex(m_volumeMapUndoStacks, mapIndex); m_volumeMapUndoStacks[mapIndex]->push(modifiedVoxels); } /** * Update in case number of maps in volume file has changed. */ void VolumeFileEditorDelegate::updateIfVolumeFileChangedNumberOfMaps() { const int32_t oldNumMaps = static_cast(m_volumeMapUndoStacks.size()); const int32_t numMaps = m_volumeFile->getNumberOfMaps(); const int32_t numMapsToAdd = numMaps - oldNumMaps; if (numMapsToAdd > 0) { for (int32_t i = 0; i < numMapsToAdd; i++) { m_volumeMapUndoStacks.push_back(new CaretUndoStack()); m_volumeMapEditingLocked.push_back(true); } } CaretAssert(static_cast(m_volumeMapUndoStacks.size()) == numMaps); CaretAssert(static_cast(m_volumeMapEditingLocked.size()) == numMaps); } /** * Undo the last voxel editing operation for the given map index. * * @param mapIndex * Index of map that has last voxel operation 'undone'. */ void VolumeFileEditorDelegate::undo(const int64_t mapIndex) { CaretAssertVectorIndex(m_volumeMapUndoStacks, mapIndex); m_volumeMapUndoStacks[mapIndex]->undo(); } /** * Reset all voxel editing since last lock. * * Note that when the lock status transitions to lock, * the undo stack is cleared. * * @param mapIndex * Index of map that has last voxel operation 'undone'. */ void VolumeFileEditorDelegate::reset(const int64_t mapIndex) { CaretAssertVectorIndex(m_volumeMapUndoStacks, mapIndex); m_volumeMapUndoStacks[mapIndex]->undoAll(); } /** * Redo the last voxel editing operation for the given map index. * * @param mapIndex * Index of map that has last voxel operation 'redone'. */ void VolumeFileEditorDelegate::redo(const int64_t mapIndex) { CaretAssertVectorIndex(m_volumeMapUndoStacks, mapIndex); m_volumeMapUndoStacks[mapIndex]->redo(); } /** * Perform an editing operation that turns voxels on or off * for orthogonal slice viewing. * * @param editInfo * The editing information. * @param errorMessageOut * Will contain error information. * @return * True if there was an error, else false. */ bool VolumeFileEditorDelegate::performTurnOnOrOffOrthogonal(const EditInfo& editInfo, AString& errorMessageOut) { float redoVoxelValue = 0.0; switch (editInfo.m_mode){ case VolumeEditingModeEnum::VOLUME_EDITING_MODE_OFF: redoVoxelValue = editInfo.m_voxelValueOff; break; case VolumeEditingModeEnum::VOLUME_EDITING_MODE_ON: redoVoxelValue = editInfo.m_voxelValueOn; break; case VolumeEditingModeEnum::VOLUME_EDITING_MODE_DILATE: case VolumeEditingModeEnum::VOLUME_EDITING_MODE_ERODE: case VolumeEditingModeEnum::VOLUME_EDITING_MODE_FLOOD_FILL_2D: case VolumeEditingModeEnum::VOLUME_EDITING_MODE_FLOOD_FILL_3D: case VolumeEditingModeEnum::VOLUME_EDITING_MODE_REMOVE_CONNECTED_2D: case VolumeEditingModeEnum::VOLUME_EDITING_MODE_REMOVE_CONNECTED_3D: case VolumeEditingModeEnum::VOLUME_EDITING_MODE_RETAIN_CONNECTED_3D: CaretAssert(0); errorMessageOut = "Program error in performTurnOnOrOff but mode not valid."; return false; break; } CaretPointer modifiedVoxels; modifiedVoxels.grabNew(new VolumeMapUndoCommand(m_volumeFile, editInfo.m_mapIndex)); for (int64_t i = editInfo.m_ijkMin[0]; i <= editInfo.m_ijkMax[0]; i++) { for (int64_t j = editInfo.m_ijkMin[1]; j <= editInfo.m_ijkMax[1]; j++) { for (int64_t k = editInfo.m_ijkMin[2]; k <= editInfo.m_ijkMax[2]; k++) { const int64_t ijk[3] = { i, j, k }; modifiedVoxels->addVoxelRedoUndo(ijk, redoVoxelValue, m_volumeFile->getValue(ijk, editInfo.m_mapIndex)); m_volumeFile->setValue(redoVoxelValue, ijk, editInfo.m_mapIndex); } } } addToMapUndoStacks(editInfo.m_mapIndex, modifiedVoxels.releasePointer()); return true; } /** * Perform an editing operation that turns voxels on or off * for oblique slice viewing. * * @param editInfo * The editing information. * @param errorMessageOut * Will contain error information. * @return * True if there was an error, else false. */ bool VolumeFileEditorDelegate::performTurnOnOrOffOblique(const EditInfo& editInfo, AString& errorMessageOut) { float redoVoxelValue = 0.0; switch (editInfo.m_mode){ case VolumeEditingModeEnum::VOLUME_EDITING_MODE_OFF: redoVoxelValue = editInfo.m_voxelValueOff; break; case VolumeEditingModeEnum::VOLUME_EDITING_MODE_ON: redoVoxelValue = editInfo.m_voxelValueOn; break; case VolumeEditingModeEnum::VOLUME_EDITING_MODE_DILATE: case VolumeEditingModeEnum::VOLUME_EDITING_MODE_ERODE: case VolumeEditingModeEnum::VOLUME_EDITING_MODE_FLOOD_FILL_2D: case VolumeEditingModeEnum::VOLUME_EDITING_MODE_FLOOD_FILL_3D: case VolumeEditingModeEnum::VOLUME_EDITING_MODE_REMOVE_CONNECTED_2D: case VolumeEditingModeEnum::VOLUME_EDITING_MODE_REMOVE_CONNECTED_3D: case VolumeEditingModeEnum::VOLUME_EDITING_MODE_RETAIN_CONNECTED_3D: CaretAssert(0); errorMessageOut = "Program error in performTurnOnOrOff but mode not valid."; return false; break; } // float voxelDiffXYZ[3]; float voxelXYZ[3]; m_volumeFile->indexToSpace(editInfo.m_voxelIJK, voxelXYZ); // const int64_t planeVoxelsDI = (editInfo.m_ijkMax[0] - editInfo.m_ijkMin[0]) / 2 ; // const int64_t planeVoxelsDJ = (editInfo.m_ijkMax[1] - editInfo.m_ijkMin[1]) / 2 ; // const int64_t planeVoxelsDK = (editInfo.m_ijkMax[2] - editInfo.m_ijkMin[2]) / 2 ; CaretPointer modifiedVoxels; modifiedVoxels.grabNew(new VolumeMapUndoCommand(m_volumeFile, editInfo.m_mapIndex)); // const float planeZ = voxelXYZ[2]; // for (int64_t i = -planeVoxelsDI; i <= planeVoxelsDI; i++) { // const float planeX = voxelXYZ[0] + i * editInfo.m_voxelDiffXYZ[0]; // for (int64_t j = -planeVoxelsDJ; j <= planeVoxelsDJ; j++) { // const float planeY = voxelXYZ[1] + j * editInfo.m_voxelDiffXYZ[1]; // const float xyz[3] = { planeX, planeY, planeZ }; // float ijkFloat[3]; // m_volumeFile->spaceToIndex(xyz, ijkFloat); // int64_t ijk[3] = { ijkFloat[0], ijkFloat[1], ijkFloat[2] }; // modifiedVoxels->addVoxelRedoUndo(ijk, // redoVoxelValue, // m_volumeFile->getValue(ijk, editInfo.m_mapIndex)); // m_volumeFile->setValue(redoVoxelValue, // ijk, // editInfo.m_mapIndex); // } // } const int64_t halfBrushI = editInfo.m_brushSize[0] / 2; const int64_t halfBrushJ = editInfo.m_brushSize[1] / 2; const int64_t halfBrushK = editInfo.m_brushSize[2] / 2; VolumeSpace::OrientTypes orient[3]; float spacing[3]; float origin[3]; m_volumeFile->getVolumeSpace().getOrientAndSpacingForPlumb(orient, spacing, origin); for (int64_t k = -halfBrushK; k <= halfBrushK; k++) { for (int64_t i = -halfBrushI; i <= halfBrushI; i++) { for (int64_t j = -halfBrushJ; j <= halfBrushJ; j++) { float localXYZ[3] = { i * spacing[0], j * spacing[1], k * spacing[2] }; editInfo.m_obliqueRotationMatrix.multiplyPoint3(localXYZ); float brushXYZ[3] = { voxelXYZ[0] + localXYZ[0], voxelXYZ[1] + localXYZ[1], voxelXYZ[2] + localXYZ[2] }; float ijkFloat[3]; m_volumeFile->spaceToIndex(brushXYZ, ijkFloat); int64_t ijk[3] = { ijkFloat[0], ijkFloat[1], ijkFloat[2] }; modifiedVoxels->addVoxelRedoUndo(ijk, redoVoxelValue, m_volumeFile->getValue(ijk, editInfo.m_mapIndex)); m_volumeFile->setValue(redoVoxelValue, ijk, editInfo.m_mapIndex); } } } // for (int64_t k = -planeVoxelsDZ; k <= planeVoxelsDZ; ++k) { // // } // CaretPointer modifiedVoxels; // modifiedVoxels.grabNew(new VolumeMapUndoCommand(m_volumeFile, // editInfo.m_mapIndex)); // for (int64_t i = editInfo.m_ijkMin[0]; i <= editInfo.m_ijkMax[0]; i++) { // for (int64_t j = editInfo.m_ijkMin[1]; j <= editInfo.m_ijkMax[1]; j++) { // for (int64_t k = editInfo.m_ijkMin[2]; k <= editInfo.m_ijkMax[2]; k++) { // const int64_t ijk[3] = { i, j, k }; // modifiedVoxels->addVoxelRedoUndo(ijk, // redoVoxelValue, // m_volumeFile->getValue(ijk, editInfo.m_mapIndex)); // m_volumeFile->setValue(redoVoxelValue, // ijk, // editInfo.m_mapIndex); // } // } // } addToMapUndoStacks(editInfo.m_mapIndex, modifiedVoxels.releasePointer()); return true; } /** * Perform an editing operation that dilates voxels * connected to the selected voxel. * * @param editInfo * The editing information. * @param errorMessageOut * Will contain error information. * @return * True if there was an error, else false. */ bool VolumeFileEditorDelegate::performDilateOrErode(const EditInfo& editInfo, AString& errorMessageOut) { bool dilateFlag = false; switch (editInfo.m_mode){ case VolumeEditingModeEnum::VOLUME_EDITING_MODE_DILATE: dilateFlag = true; break; case VolumeEditingModeEnum::VOLUME_EDITING_MODE_ERODE: dilateFlag = false; break; case VolumeEditingModeEnum::VOLUME_EDITING_MODE_FLOOD_FILL_2D: case VolumeEditingModeEnum::VOLUME_EDITING_MODE_FLOOD_FILL_3D: case VolumeEditingModeEnum::VOLUME_EDITING_MODE_OFF: case VolumeEditingModeEnum::VOLUME_EDITING_MODE_ON: case VolumeEditingModeEnum::VOLUME_EDITING_MODE_REMOVE_CONNECTED_2D: case VolumeEditingModeEnum::VOLUME_EDITING_MODE_REMOVE_CONNECTED_3D: case VolumeEditingModeEnum::VOLUME_EDITING_MODE_RETAIN_CONNECTED_3D: CaretAssert(0); errorMessageOut = "Program error in dilate/erode but mode not valid."; return false; break; } const int64_t STRUCTURE_ELEMENT_SIZE = 1; CaretPointer modifiedVoxels; modifiedVoxels.grabNew(new VolumeMapUndoCommand(m_volumeFile, editInfo.m_mapIndex)); /* * Check each voxel in the desired region */ for (int64_t i = editInfo.m_ijkMin[0]; i <= editInfo.m_ijkMax[0]; i++) { for (int64_t j = editInfo.m_ijkMin[1]; j <= editInfo.m_ijkMax[1]; j++) { for (int64_t k = editInfo.m_ijkMin[2]; k <= editInfo.m_ijkMax[2]; k++) { const int64_t ijk[3] = { i, j, k }; /* * Get the value of the voxel */ float value = m_volumeFile->getValue(ijk, editInfo.m_mapIndex); bool voxelMatches = false; /* * If eroding, look for voxels with "turn on" value */ if (! dilateFlag) { if (value == editInfo.m_voxelValueOn) { voxelMatches = true; } } /* * If dilating, look for "OFF" voxels */ if (dilateFlag) { if (value == editInfo.m_voxelValueOff) { voxelMatches = true; } } /* * Should we continue processing this voxel */ if (voxelMatches) { /* * Create Structuring Element based upon the axis */ int64_t iMin = ijk[0]; int64_t iMax = ijk[0]; int64_t jMin = ijk[1]; int64_t jMax = ijk[1]; int64_t kMin = ijk[2]; int64_t kMax = ijk[2]; switch (editInfo.m_slicePlane) { case VolumeSliceViewPlaneEnum::ALL: CaretAssert(0); errorMessageOut = "Program Error: Cannot edit an ALL slice"; return false; break; case VolumeSliceViewPlaneEnum::AXIAL: iMin -= STRUCTURE_ELEMENT_SIZE; iMax += STRUCTURE_ELEMENT_SIZE; jMin -= STRUCTURE_ELEMENT_SIZE; jMax += STRUCTURE_ELEMENT_SIZE; break; case VolumeSliceViewPlaneEnum::CORONAL: iMin -= STRUCTURE_ELEMENT_SIZE; iMax += STRUCTURE_ELEMENT_SIZE; kMin -= STRUCTURE_ELEMENT_SIZE; kMax += STRUCTURE_ELEMENT_SIZE; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: jMin -= STRUCTURE_ELEMENT_SIZE; jMax += STRUCTURE_ELEMENT_SIZE; kMin -= STRUCTURE_ELEMENT_SIZE; kMax += STRUCTURE_ELEMENT_SIZE; break; } clampVoxelIndices(iMin, jMin, kMin); clampVoxelIndices(iMax, jMax, kMax); /* * Check all voxels "under" the structuring element */ bool foundVoxelFlag = false; for (int64_t ii = iMin; ii <= iMax; ii++) { for (int64_t jj = jMin; jj <= jMax; jj++) { for (int64_t kk = kMin; kk <= kMax; kk++) { /* * Ignore the voxel under the center of the * structuring element */ if ((ii != i) || (jj != j) || (kk != k)) { /* * Make sure voxel is valid since structuring element * may exceed bounds of the volume */ const int64_t iijjkk[3] = { ii, jj, kk }; float value = m_volumeFile->getValue(iijjkk); /* * If dilating, look for voxels that are the * turn on value under the structuring * element. */ if (dilateFlag) { if (value == editInfo.m_voxelValueOn) { foundVoxelFlag = true; break; } } /* * If eroding look for voxels that are "OFF" * under the structuring element */ if ( ! dilateFlag) { if (value != editInfo.m_voxelValueOn) { foundVoxelFlag = true; break; } } } } if (foundVoxelFlag) { break; } } if (foundVoxelFlag) { break; } } if (foundVoxelFlag) { /* * For now, just note which voxels need to be set since * we do not want to modify the volume until after all voxels * under structuring element have been checked. */ if (dilateFlag) { modifiedVoxels->addVoxelRedoUndo(ijk, editInfo.m_voxelValueOn, m_volumeFile->getValue(ijk, editInfo.m_mapIndex)); } else { modifiedVoxels->addVoxelRedoUndo(ijk, editInfo.m_voxelValueOff, m_volumeFile->getValue(ijk, editInfo.m_mapIndex)); } } } } } } /* * Calling 'redo' will apply the changes to the volume file. */ modifiedVoxels->redo(); addToMapUndoStacks(editInfo.m_mapIndex, modifiedVoxels.releasePointer()); return true; } /** * Perform an editing operation that flood fills the region * containing the selected voxel in the selected slice. * * @param editInfo * The editing information. * @param errorMessageOut * Will contain error information. * @return * True if there was an error, else false. */ bool VolumeFileEditorDelegate::performFloodFill2D(const EditInfo& editInfo, AString& errorMessageOut) { return performFloodFillAndRemoveConnected(editInfo, errorMessageOut); } /** * Perform an editing operation that flood fills the region * containing the selected voxel in all dimensions. * * @param editInfo * The editing information. * @param errorMessageOut * Will contain error information. * @return * True if there was an error, else false. */ bool VolumeFileEditorDelegate::performFloodFill3D(const EditInfo& editInfo, AString& errorMessageOut) { return performFloodFillAndRemoveConnected(editInfo, errorMessageOut); } /** * Perform an editing operation that removes all voxels connected * to the selected voxel in the slice. * * @param editInfo * The editing information. * @param errorMessageOut * Will contain error information. * @return * True if there was an error, else false. */ bool VolumeFileEditorDelegate::performRemoveConnected2D(const EditInfo& editInfo, AString& errorMessageOut) { return performFloodFillAndRemoveConnected(editInfo, errorMessageOut); } /** * Perform an editing operation that removes all voxels connected * to the selected voxel in all dimensions. * * @param editInfo * The editing information. * @param errorMessageOut * Will contain error information. * @return * True if there was an error, else false. */ bool VolumeFileEditorDelegate::performRemoveConnected3D(const EditInfo& editInfo, AString& errorMessageOut) { return performFloodFillAndRemoveConnected(editInfo, errorMessageOut); } /** * Perform an editing operation that rmeoves all voxels that * are not connected to the selected voxel. * * @param editInfo * The editing information. * @param errorMessageOut * Will contain error information. * @return * True if there was an error, else false. */ bool VolumeFileEditorDelegate::performRetainConnected3D(const EditInfo& editInfo, AString& errorMessageOut) { if (m_volumeFile->getValue(editInfo.m_voxelIJK, editInfo.m_mapIndex) == editInfo.m_voxelValueOff) { errorMessageOut = "Voxel value is zero or the unassigned label."; return false; } CaretPointer modifiedVoxels; modifiedVoxels.grabNew(new VolumeMapUndoCommand(m_volumeFile, editInfo.m_mapIndex)); /* * Tracks visited voxels */ const int64_t numVoxels = (m_volumeDimensions[0] * m_volumeDimensions[1] * m_volumeDimensions[2]); std::vector visitedVoxelFlags(numVoxels, false); /* * Tracks voxels that are connected */ std::vector connectedVoxelFlags(numVoxels, false); /* * Initialize to the staring voxel */ std::stack st; st.push(VoxelIJK(editInfo.m_voxelIJK)); /* * While there are voxels to process */ while (st.empty() == false) { /* * Get the next voxel to process */ const VoxelIJK v = st.top(); st.pop(); const int64_t visitedFlagsOffset = (v.m_ijk[0] + (v.m_ijk[1] * (m_volumeDimensions[0])) + (v.m_ijk[2] * m_volumeDimensions[0] * m_volumeDimensions[1])); CaretAssertVectorIndex(visitedVoxelFlags, visitedFlagsOffset); if (visitedVoxelFlags[visitedFlagsOffset]) { continue; } visitedVoxelFlags[visitedFlagsOffset] = true; if (m_volumeFile->getValue(v.m_ijk, editInfo.m_mapIndex) == editInfo.m_voxelValueOff) { continue; } connectedVoxelFlags[visitedFlagsOffset] = true; int64_t ijkMin[3] = { v.m_ijk[0] - 1, v.m_ijk[1] - 1, v.m_ijk[2] - 1 }; clampVoxelIndices(ijkMin); int64_t ijkMax[3] = { v.m_ijk[0] + 1, v.m_ijk[1] + 1, v.m_ijk[2] + 1 }; clampVoxelIndices(ijkMax); /* * Add neighbors to search */ for (int64_t i = ijkMin[0]; i <= ijkMax[0]; i++) { for (int64_t j = ijkMin[1]; j <= ijkMax[1]; j++) { for (int64_t k = ijkMin[2]; k <= ijkMax[2]; k++) { const int64_t flagsOffset = (i + (j * (m_volumeDimensions[0])) + (k * m_volumeDimensions[0] * m_volumeDimensions[1])); if (visitedVoxelFlags[flagsOffset]) { continue; } if (m_volumeFile->getValue(i, j, k, editInfo.m_mapIndex) != editInfo.m_voxelValueOff) { st.push(VoxelIJK(i, j, k)); } } } } } /* * Turn off not connected voxels */ for (int64_t i = 0; i < m_volumeDimensions[0]; i++) { for (int64_t j = 0; j < m_volumeDimensions[1]; j++) { for (int64_t k = 0; k < m_volumeDimensions[2]; k++) { const int64_t flagsOffset = (i + (j * (m_volumeDimensions[0])) + (k * m_volumeDimensions[0] * m_volumeDimensions[1])); if ( ! connectedVoxelFlags[flagsOffset]) { modifiedVoxels->addVoxelRedoUndo(i, j, k, editInfo.m_voxelValueOff, m_volumeFile->getValue(i, j, k, editInfo.m_mapIndex)); m_volumeFile->setValue(editInfo.m_voxelValueOff, i, j, k, editInfo.m_mapIndex); } } } } addToMapUndoStacks(editInfo.m_mapIndex, modifiedVoxels.releasePointer()); return true; } /** * Perform an editing operation that rmeoves all voxels that * are not connected to the selected voxel. * * @param editInfo * The editing information. * @param errorMessageOut * Will contain error information. * @return * True if there was an error, else false. */ bool VolumeFileEditorDelegate::performFloodFillAndRemoveConnected(const EditInfo& editInfo, AString& errorMessageOut) { bool fillingFlag = false; bool threeDimensionalFlag = false; switch (editInfo.m_mode){ case VolumeEditingModeEnum::VOLUME_EDITING_MODE_DILATE: case VolumeEditingModeEnum::VOLUME_EDITING_MODE_ERODE: case VolumeEditingModeEnum::VOLUME_EDITING_MODE_OFF: case VolumeEditingModeEnum::VOLUME_EDITING_MODE_ON: case VolumeEditingModeEnum::VOLUME_EDITING_MODE_RETAIN_CONNECTED_3D: CaretAssert(0); errorMessageOut = "Program error in performFloodFillAndRemoveConnected but mode not valid."; return false; break; case VolumeEditingModeEnum::VOLUME_EDITING_MODE_FLOOD_FILL_2D: fillingFlag = true; break; case VolumeEditingModeEnum::VOLUME_EDITING_MODE_FLOOD_FILL_3D: fillingFlag = true; threeDimensionalFlag = true; break; case VolumeEditingModeEnum::VOLUME_EDITING_MODE_REMOVE_CONNECTED_2D: break; case VolumeEditingModeEnum::VOLUME_EDITING_MODE_REMOVE_CONNECTED_3D: threeDimensionalFlag = true; break; } CaretPointer modifiedVoxels; modifiedVoxels.grabNew(new VolumeMapUndoCommand(m_volumeFile, editInfo.m_mapIndex)); float newVoxelValue = editInfo.m_voxelValueOff; if (fillingFlag) { newVoxelValue = editInfo.m_voxelValueOn; } /* * Initialize to the staring voxel */ std::stack st; st.push(VoxelIJK(editInfo.m_voxelIJK)); /* * While there are voxels to process */ while (st.empty() == false) { /* * Get the next voxel to process */ const VoxelIJK v = st.top(); st.pop(); int64_t i = v.m_ijk[0]; int64_t j = v.m_ijk[1]; int64_t k = v.m_ijk[2]; /* * If the voxel has valid indices */ if ((i >= 0) && (i < m_volumeDimensions[0]) && (j >= 0) && (j < m_volumeDimensions[1]) && (k >= 0) && (k < m_volumeDimensions[2])) { const int64_t ijk[3] = { i, j, k }; float currentValue = m_volumeFile->getValue(ijk, editInfo.m_mapIndex); /* * See if voxel has proper value for operation */ bool matchingVoxel = false; if (fillingFlag) { matchingVoxel = (currentValue == editInfo.m_voxelValueOff); } else { matchingVoxel = (currentValue == editInfo.m_voxelValueOn); } /* * If the voxel should be modified */ if (matchingVoxel) { /* * Update the voxels value */ modifiedVoxels->addVoxelRedoUndo(ijk, newVoxelValue, m_volumeFile->getValue(ijk, editInfo.m_mapIndex)); m_volumeFile->setValue(newVoxelValue, ijk, editInfo.m_mapIndex); /* * Determine neighboring voxels */ int64_t iDelta = 0; int64_t jDelta = 0; int64_t kDelta = 0; switch (editInfo.m_slicePlane) { case VolumeSliceViewPlaneEnum::PARASAGITTAL: if (threeDimensionalFlag) { iDelta = 1; } else { iDelta = 0; } jDelta = 1; kDelta = 1; break; case VolumeSliceViewPlaneEnum::CORONAL: iDelta = 1; if (threeDimensionalFlag) { jDelta = 1; } else { jDelta = 0; } kDelta = 1; break; case VolumeSliceViewPlaneEnum::AXIAL: iDelta = 1; jDelta = 1; if (threeDimensionalFlag) { kDelta = 1; } else { kDelta = 0; } break; case VolumeSliceViewPlaneEnum::ALL: break; } /* * Add neighboring voxels for search */ if (iDelta != 0) { st.push(VoxelIJK(i - iDelta, j, k)); st.push(VoxelIJK(i + iDelta, j, k)); } if (jDelta != 0) { st.push(VoxelIJK(i, j - jDelta, k)); st.push(VoxelIJK(i, j + jDelta, k)); } if (kDelta != 0) { st.push(VoxelIJK(i, j, k - kDelta)); st.push(VoxelIJK(i, j, k + kDelta)); } } } } addToMapUndoStacks(editInfo.m_mapIndex, modifiedVoxels.releasePointer()); return true; } workbench-1.1.1/src/Files/VolumeFileEditorDelegate.h000066400000000000000000000165441255417355300223730ustar00rootroot00000000000000#ifndef __VOLUME_FILE_EDITOR_DELEGATE_H__ #define __VOLUME_FILE_EDITOR_DELEGATE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "Matrix4x4.h" #include "VolumeEditingModeEnum.h" #include "VolumeSliceProjectionTypeEnum.h" #include "VolumeSliceViewPlaneEnum.h" namespace caret { class CaretUndoStack; class VolumeFile; class VolumeMapUndoCommand; class VolumeFileEditorDelegate : public CaretObject { public: VolumeFileEditorDelegate(VolumeFile* volumeFile); virtual ~VolumeFileEditorDelegate(); void clear(); void updateIfVolumeFileChangedNumberOfMaps(); bool performEditingOperation(const int64_t mapIndex, const VolumeEditingModeEnum::Enum mode, const VolumeSliceViewPlaneEnum::Enum slicePlane, const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const Matrix4x4& obliqueRotationMatrix, const float voxelDiffXYZ[3], const int64_t voxelIJK[3], const int64_t brushSize[3], const float voxelValueOn, const float voxelValueOff, AString& errorMessageOut); void undo(const int64_t mapIndex); void reset(const int64_t mapIndex); void redo(const int64_t mapIndex); bool isLocked(const int64_t mapIndex) const; void setLocked(const int64_t mapIndex, const bool locked); // ADD_NEW_METHODS_HERE private: class EditInfo { public: EditInfo(const int32_t mapIndex, const VolumeEditingModeEnum::Enum mode, const VolumeSliceViewPlaneEnum::Enum slicePlane, const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType, const Matrix4x4& obliqueRotationMatrix, const float voxelDiffXYZ[3], const int64_t voxelIJK[3], const int64_t ijkMin[3], const int64_t ijkMax[3], const int64_t brushSize[3], const float voxelValueOn, const float voxelValueOff) : m_mapIndex(mapIndex), m_mode(mode), m_slicePlane(slicePlane), m_sliceProjectionType(sliceProjectionType), m_obliqueRotationMatrix(obliqueRotationMatrix), m_voxelValueOn(voxelValueOn), m_voxelValueOff(voxelValueOff) { m_voxelDiffXYZ[0] = voxelDiffXYZ[0]; m_voxelDiffXYZ[1] = voxelDiffXYZ[1]; m_voxelDiffXYZ[2] = voxelDiffXYZ[2]; m_voxelIJK[0] = voxelIJK[0]; m_voxelIJK[1] = voxelIJK[1]; m_voxelIJK[2] = voxelIJK[2]; m_brushSize[0] = brushSize[0]; m_brushSize[1] = brushSize[1]; m_brushSize[2] = brushSize[2]; m_ijkMin[0] = ijkMin[0]; m_ijkMin[1] = ijkMin[1]; m_ijkMin[2] = ijkMin[2]; m_ijkMax[0] = ijkMax[0]; m_ijkMax[1] = ijkMax[1]; m_ijkMax[2] = ijkMax[2]; } const int32_t m_mapIndex; const VolumeEditingModeEnum::Enum m_mode; const VolumeSliceViewPlaneEnum::Enum m_slicePlane; const VolumeSliceProjectionTypeEnum::Enum m_sliceProjectionType; const Matrix4x4 m_obliqueRotationMatrix; float m_voxelDiffXYZ[3]; int64_t m_voxelIJK[3]; int64_t m_ijkMin[3]; int64_t m_ijkMax[3]; int64_t m_brushSize[3]; const float m_voxelValueOn; const float m_voxelValueOff; }; VolumeFileEditorDelegate(const VolumeFileEditorDelegate&); VolumeFileEditorDelegate& operator=(const VolumeFileEditorDelegate&); bool performTurnOnOrOffOblique(const EditInfo& editInfo, AString& errorMessageOut); bool performTurnOnOrOffOrthogonal(const EditInfo& editInfo, AString& errorMessageOut); bool performDilateOrErode(const EditInfo& editInfo, AString& errorMessageOut); bool performFloodFill2D(const EditInfo& editInfo, AString& errorMessageOut); bool performFloodFill3D(const EditInfo& editInfo, AString& errorMessageOut); bool performRemoveConnected2D(const EditInfo& editInfo, AString& errorMessageOut); bool performRemoveConnected3D(const EditInfo& editInfo, AString& errorMessageOut); bool performRetainConnected3D(const EditInfo& editInfo, AString& errorMessageOut); bool performFloodFillAndRemoveConnected(const EditInfo& editInfo, AString& errorMessageOut); int64_t clampDimensionIndex(const int64_t maxDim, int64_t dimIndex) const; void clampVoxelIndices(int64_t ijk[3]) const; void clampVoxelIndices(int64_t& i, int64_t& j, int64_t& k) const; void addToMapUndoStacks(const int32_t mapIndex, VolumeMapUndoCommand* modifiedVoxels); VolumeFile* m_volumeFile; /** * Holds modifications for undo/redo operations. * Index into vector is the map index. */ std::vector m_volumeMapUndoStacks; /** * IJK dimensions of the volume. */ int64_t m_volumeDimensions[3]; /** * A "lock" to prevent editing of a volume's map. */ std::vector m_volumeMapEditingLocked; // ADD_NEW_MEMBERS_HERE }; #ifdef __VOLUME_FILE_EDITOR_DELEGATE_DECLARE__ // #endif // __VOLUME_FILE_EDITOR_DELEGATE_DECLARE__ } // namespace #endif //__VOLUME_FILE_EDITOR_DELEGATE_H__ workbench-1.1.1/src/Files/VolumeFileVoxelColorizer.cxx000066400000000000000000000516601255417355300230510ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __VOLUME_FILE_VOXEL_COLORIZER_DECLARE__ #include "VolumeFileVoxelColorizer.h" #undef __VOLUME_FILE_VOXEL_COLORIZER_DECLARE__ #include "CaretAssert.h" #include "CaretLogger.h" #include "ElapsedTimer.h" #include "GiftiLabel.h" #include "GroupAndNameHierarchyItem.h" #include "NodeAndVoxelColoring.h" #include "VolumeFile.h" using namespace caret; /** * \class caret::VolumeFileVoxelColorizer * \brief Delegate for coloring a volumes voxels. */ /** * Constructor. * * @param volumeFile * Volume file on which this instance colors voxels. */ VolumeFileVoxelColorizer::VolumeFileVoxelColorizer(VolumeFile* volumeFile) : CaretObject() { CaretAssert(volumeFile); m_volumeFile = volumeFile; int64_t dimNumberOfComponents; m_volumeFile->getDimensions(m_dimI, m_dimJ, m_dimK, m_mapCount, dimNumberOfComponents); m_voxelCountPerMap = m_dimI * m_dimJ * m_dimK; m_mapRGBACount = m_voxelCountPerMap * 4; for (int64_t i = 0; i < m_mapCount; i++) { m_mapRGBA.push_back(new uint8_t[m_mapRGBACount]); m_mapColoringValid.push_back(false); } } /** * Destructor. */ VolumeFileVoxelColorizer::~VolumeFileVoxelColorizer() { for (int64_t i = 0; i < m_mapCount; i++) { delete[] m_mapRGBA[i]; } m_mapRGBA.clear(); } /** * Assign voxel coloring for a map. * * @param mapIndex * Index of map. * @param palette * Palette used for scalar color assignment. May be NULL for data * not mapped with a palette. * @param thresholdVolume * Volume that contains thresholding (if NULL indicates no thresholding). * @param thresholdVolumeMapIndex * Index of map in thresholding volume. */ void VolumeFileVoxelColorizer::assignVoxelColorsForMap(const int32_t mapIndex, const Palette* palette, const VolumeFile* thresholdVolume, const int32_t /*thresholdVolumeMapIndex*/) { CaretAssertVectorIndex(m_mapRGBA, mapIndex); ElapsedTimer timer; timer.start(); /* * Pointer to map's data */ const float* mapDataPointer = m_volumeFile->getFrame(mapIndex); /* * Get access to threshold data */ //float* thresholdDataPointer = NULL; bool ignoreThresholding = true; if (thresholdVolume != NULL) { int64_t threshI, threshJ, threshK, threshMapCount, threshNumberOfComponents; thresholdVolume->getDimensions(threshI, threshJ, threshK, threshMapCount, threshNumberOfComponents); if ((threshI != m_dimI) || (threshJ != m_dimJ) || (threshK != m_dimK)) { CaretLogSevere("Threshold volume (" + thresholdVolume->getFileNameNoPath() + ") dimensions do not match " + m_volumeFile->getFileNameNoPath()); } else { /* * Can use same voxel counter per map since volumes are * identical dimensions; */ //thresholdDataPointer = thresholdVolume->m_data + m_voxelCountPerMap;//TSC: this is unused, and can easily be an invalid pointer - commenting out for unused warning ignoreThresholding = false; } } switch (m_volumeFile->getType()) { case SubvolumeAttributes::UNKNOWN: case SubvolumeAttributes::ANATOMY: case SubvolumeAttributes::FUNCTIONAL: { CaretAssert(palette); FastStatistics* statistics = NULL; switch (m_volumeFile->getPaletteNormalizationMode()) { case PaletteNormalizationModeEnum::NORMALIZATION_ALL_MAP_DATA: statistics = const_cast(m_volumeFile->getFileFastStatistics()); break; case PaletteNormalizationModeEnum::NORMALIZATION_SELECTED_MAP_DATA: statistics = const_cast(m_volumeFile->getMapFastStatistics(mapIndex)); break; } CaretAssert(statistics); NodeAndVoxelColoring::colorScalarsWithPalette(statistics, //m_volumeFile->getMapFastStatistics(mapIndex), m_volumeFile->getMapPaletteColorMapping(mapIndex), palette, mapDataPointer, mapDataPointer, m_voxelCountPerMap, m_mapRGBA[mapIndex], ignoreThresholding); m_mapColoringValid[mapIndex] = true; } break; case SubvolumeAttributes::LABEL: if (m_voxelCountPerMap > 0) { NodeAndVoxelColoring::colorIndicesWithLabelTable(m_volumeFile->getMapLabelTable(mapIndex), &mapDataPointer[0], m_voxelCountPerMap, m_mapRGBA[mapIndex]); m_mapColoringValid[mapIndex] = true; } break; case SubvolumeAttributes::RGB: break; case SubvolumeAttributes::SEGMENTATION: break; case SubvolumeAttributes::VECTOR: break; } CaretLogFine("Time to color map named \"" + m_volumeFile->getMapName(mapIndex) + " in volume file " + m_volumeFile->getFileNameNoPath() + " was " + AString::number(timer.getElapsedTimeMilliseconds()) + " milliseconds"); } /** * Invalidate the RGBA coloring for all maps. */ void VolumeFileVoxelColorizer::invalidateColoring() { std::fill(m_mapColoringValid.begin(), m_mapColoringValid.end(), false); } /** * Get voxel coloring for a slice in a map. If voxel coloring is not ready * (it may be running in a different thread) this method will wait until the * coloring is valid prior to returning the slice's coloring. * * @param mapIndex * Index of map. * @param slicePlane * Plane of the slice. * @param sliceIndex * Index of the slice. * @param displayGroup * The selected display group. * @param tabIndex * Index of selected tab. * @param rgbaOut * RGBA color components out. * @return * Number of voxels with alpha greater than zero */ int64_t VolumeFileVoxelColorizer::getVoxelColorsForSliceInMap(const int32_t mapIndex, const VolumeSliceViewPlaneEnum::Enum slicePlane, const int64_t sliceIndex, const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, uint8_t* rgbaOut) const { CaretAssertVectorIndex(m_mapRGBA, mapIndex); CaretAssert(sliceIndex >= 0); CaretAssert(rgbaOut); int64_t iStart = 0; int64_t iEnd = m_dimI - 1; int64_t jStart = 0; int64_t jEnd = m_dimJ - 1; int64_t kStart = 0; int64_t kEnd = m_dimK - 1; switch (slicePlane) { case VolumeSliceViewPlaneEnum::ALL: CaretAssert(0); break; case VolumeSliceViewPlaneEnum::AXIAL: kStart = sliceIndex; kEnd = sliceIndex; break; case VolumeSliceViewPlaneEnum::CORONAL: jStart = sliceIndex; jEnd = sliceIndex; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: iStart = sliceIndex; iEnd = sliceIndex; break; } /* * Pointer to maps RGBA values */ const uint8_t* mapRGBA = m_mapRGBA[mapIndex]; const GiftiLabelTable* labelTable = (m_volumeFile->isMappedWithLabelTable() ? m_volumeFile->getMapLabelTable(mapIndex) : NULL); if (m_volumeFile->isMappedWithLabelTable()) { CaretAssert(labelTable); } int64_t validVoxelCount = 0; /* * Output RGBA values for slice */ int64_t rgbaOutIndex = 0; for (int64_t k = kStart; k <= kEnd; k++) { for (int64_t j = jStart; j <= jEnd; j++) { for (int64_t i = iStart; i <= iEnd; i++) { const int64_t rgbaOffset = getRgbaOffsetForVoxelIndex(i, j, k); CaretAssertArrayIndex(mapRGBA, m_mapRGBACount, rgbaOffset); rgbaOut[rgbaOutIndex] = mapRGBA[rgbaOffset]; rgbaOut[rgbaOutIndex+1] = mapRGBA[rgbaOffset+1]; rgbaOut[rgbaOutIndex+2] = mapRGBA[rgbaOffset+2]; uint8_t alpha = mapRGBA[rgbaOffset+3]; if (alpha > 0) { if (labelTable != NULL) { /* * For label data, verify that the label is displayed. * If NOT displayed, zero out the alpha value to * prevent display of the data. */ const int32_t dataValue = static_cast(m_volumeFile->getValue(i, j, k, mapIndex)); const GiftiLabel* label = labelTable->getLabel(dataValue); if (label != NULL) { const GroupAndNameHierarchyItem* item = label->getGroupNameSelectionItem(); if (item != NULL) { if (item->isSelected(displayGroup, tabIndex) == false) { alpha = 0; } } } } } if (alpha > 0.0) { ++validVoxelCount; } rgbaOut[rgbaOutIndex+3] = alpha; rgbaOutIndex += 4; } } } return validVoxelCount; } /** * Get voxel coloring for a sub-slice in a map. If voxel coloring is not ready * (it may be running in a different thread) this method will wait until the * coloring is valid prior to returning the slice's coloring. * * @param mapIndex * Index of map. * @param slicePlane * Plane of the slice. * @param sliceIndex * Index of the slice. * @param firstCornerVoxelIndex * Indices of voxel for first corner of sub-slice (inclusive). * @param lastCornerVoxelIndex * Indices of voxel for last corner of sub-slice (inclusive). * @param voxelCountIJK * Voxel counts for each axis. * @param displayGroup * The selected display group. * @param tabIndex * Index of selected tab. * @param rgbaOut * RGBA color components out. * @return * Number of voxels with alpha greater than zero */ int64_t VolumeFileVoxelColorizer::getVoxelColorsForSubSliceInMap(const int32_t mapIndex, const VolumeSliceViewPlaneEnum::Enum slicePlane, const int64_t sliceIndex, const int64_t firstCornerVoxelIndex[3], const int64_t lastCornerVoxelIndex[3], const int64_t voxelCountIJK[3], const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, uint8_t* rgbaOut) const { CaretAssertVectorIndex(m_mapRGBA, mapIndex); CaretAssert(sliceIndex >= 0); CaretAssert(rgbaOut); int64_t iStart = firstCornerVoxelIndex[0]; int64_t iEnd = lastCornerVoxelIndex[0]; int64_t jStart = firstCornerVoxelIndex[1]; int64_t jEnd = lastCornerVoxelIndex[1]; int64_t kStart = firstCornerVoxelIndex[2]; int64_t kEnd = lastCornerVoxelIndex[2]; switch (slicePlane) { case VolumeSliceViewPlaneEnum::ALL: CaretAssert(0); break; case VolumeSliceViewPlaneEnum::AXIAL: kStart = sliceIndex; kEnd = sliceIndex; break; case VolumeSliceViewPlaneEnum::CORONAL: jStart = sliceIndex; jEnd = sliceIndex; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: iStart = sliceIndex; iEnd = sliceIndex; break; } const int64_t voxelCount = (voxelCountIJK[0] * voxelCountIJK[1] * voxelCountIJK[2]); const int64_t rgbaCount = voxelCount * 4; /* * Pointer to maps RGBA values */ const uint8_t* mapRGBA = m_mapRGBA[mapIndex]; const GiftiLabelTable* labelTable = (m_volumeFile->isMappedWithLabelTable() ? m_volumeFile->getMapLabelTable(mapIndex) : NULL); if (m_volumeFile->isMappedWithLabelTable()) { CaretAssert(labelTable); } int64_t validVoxelCount = 0; { int64_t rgbaOutIndex = 0; /* * Note that step indices may be positive or negative */ const int64_t kStep = ((kEnd < kStart) ? -1 : 1); const int64_t jStep = ((jEnd < jStart) ? -1 : 1); const int64_t iStep = ((iEnd < iStart) ? -1 : 1); int64_t k = kStart; bool kLoopFlag = true; while (kLoopFlag) { int64_t j = jStart; bool jLoopFlag = true; while (jLoopFlag) { int64_t i = iStart; bool iLoopFlag = true; while (iLoopFlag) { /* * Zero indices are */ const int64_t rgbaOffset = getRgbaOffsetForVoxelIndex(i, j, k); CaretAssertArrayIndex(mapRGBA, m_mapRGBACount, rgbaOffset); CaretAssertArrayIndex(rgbaOut, rgbaCount, rgbaOutIndex + 3); rgbaOut[rgbaOutIndex] = mapRGBA[rgbaOffset]; rgbaOut[rgbaOutIndex+1] = mapRGBA[rgbaOffset+1]; rgbaOut[rgbaOutIndex+2] = mapRGBA[rgbaOffset+2]; uint8_t alpha = mapRGBA[rgbaOffset+3]; if (alpha > 0) { if (labelTable != NULL) { /* * For label data, verify that the label is displayed. * If NOT displayed, zero out the alpha value to * prevent display of the data. */ const int32_t dataValue = static_cast(m_volumeFile->getValue(i, j, k, mapIndex)); const GiftiLabel* label = labelTable->getLabel(dataValue); if (label != NULL) { const GroupAndNameHierarchyItem* item = label->getGroupNameSelectionItem(); if (item != NULL) { if (item->isSelected(displayGroup, tabIndex) == false) { alpha = 0; } } } } } if (alpha > 0.0) { ++validVoxelCount; } rgbaOut[rgbaOutIndex+3] = alpha; rgbaOutIndex += 4; if (i == iEnd) { iLoopFlag = false; } else { i += iStep; } } if (j == jEnd) { jLoopFlag = false; } else { j += jStep; } } if (k == kEnd) { kLoopFlag = false; } else { k += kStep; } } } return validVoxelCount; } /** * Get the RGBA color components for voxel. * * @param i * Parasaggital index * @param j * Coronal index * @param k * Axial index * @param mapIndex * Index of map. * @param displayGroup * The selected display group. * @param tabIndex * Index of selected tab. * @param rgbaOut * Contains voxel coloring on exit. */ void VolumeFileVoxelColorizer::getVoxelColorInMap(const int64_t i, const int64_t j, const int64_t k, const int64_t mapIndex, const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, uint8_t rgbaOut[4]) const { /* * Pointer to maps RGBA values */ CaretAssertVectorIndex(m_mapRGBA, mapIndex); const uint8_t* mapRGBA = m_mapRGBA[mapIndex]; const int64_t rgbaOffset = getRgbaOffsetForVoxelIndex(i, j, k); CaretAssertArrayIndex(mapRGBA, m_mapRGBACount, rgbaOffset); rgbaOut[0] = mapRGBA[rgbaOffset]; rgbaOut[1] = mapRGBA[rgbaOffset+1]; rgbaOut[2] = mapRGBA[rgbaOffset+2]; uint8_t alpha = mapRGBA[rgbaOffset+3]; if (alpha > 0) { if (m_volumeFile->isMappedWithLabelTable()) { const GiftiLabelTable* labelTable = m_volumeFile->getMapLabelTable(mapIndex); CaretAssert(labelTable); /* * For label data, verify that the label is displayed. * If NOT displayed, zero out the alpha value to * prevent display of the data. */ const int32_t dataValue = static_cast(m_volumeFile->getValue(i, j, k, mapIndex)); const GiftiLabel* label = labelTable->getLabel(dataValue); if (label != NULL) { const GroupAndNameHierarchyItem* item = label->getGroupNameSelectionItem(); if (item != NULL) { if (item->isSelected(displayGroup, tabIndex) == false) { alpha = 0; } } } } } rgbaOut[3] = alpha; } /** * Clear the voxel coloring for the given map. * @param mapIndex * Index of map. */ void VolumeFileVoxelColorizer::clearVoxelColoringForMap(const int64_t mapIndex) { CaretAssertVectorIndex(m_mapRGBA, mapIndex); uint8_t* mapRGBA = m_mapRGBA[mapIndex]; for (int64_t i = 0; i < m_mapRGBACount; i++) { mapRGBA[i] = 0.0; } } workbench-1.1.1/src/Files/VolumeFileVoxelColorizer.h000066400000000000000000000104171255417355300224710ustar00rootroot00000000000000#ifndef __VOLUME_FILE_VOXEL_COLORIZER_H__ #define __VOLUME_FILE_VOXEL_COLORIZER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "DisplayGroupEnum.h" #include "VolumeSliceViewPlaneEnum.h" namespace caret { class Palette; class VolumeFile; class VolumeFileVoxelColorizer : public CaretObject { public: VolumeFileVoxelColorizer(VolumeFile* volumeFile); virtual ~VolumeFileVoxelColorizer(); void assignVoxelColorsForMap(const int32_t mapIndex, const Palette* palette, const VolumeFile* thresholdVolume, const int32_t thresholdVolumeMapIndex); int64_t getVoxelColorsForSliceInMap(const int32_t mapIndex, const VolumeSliceViewPlaneEnum::Enum slicePlane, const int64_t sliceIndex, const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, uint8_t* rgbaOut) const; int64_t getVoxelColorsForSubSliceInMap(const int32_t mapIndex, const VolumeSliceViewPlaneEnum::Enum slicePlane, const int64_t sliceIndex, const int64_t firstCornerVoxelIndex[3], const int64_t lastCornerVoxelIndex[3], const int64_t voxelCountIJK[3], const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, uint8_t* rgbaOut) const; void getVoxelColorInMap(const int64_t i, const int64_t j, const int64_t k, const int64_t mapIndex, const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, uint8_t rgbaOut[4]) const; void clearVoxelColoringForMap(const int64_t mapIndex); void invalidateColoring(); private: VolumeFileVoxelColorizer(const VolumeFileVoxelColorizer&); VolumeFileVoxelColorizer& operator=(const VolumeFileVoxelColorizer&); /** * Get theRGBA offset for a voxel index */ inline int64_t getRgbaOffsetForVoxelIndex(const int64_t i, const int64_t j, const int64_t k) const { return (4 * (i + (j * m_dimI) + ((k * m_dimI * m_dimJ)))); } // ADD_NEW_MEMBERS_HERE VolumeFile* m_volumeFile; int64_t m_dimI; int64_t m_dimJ; int64_t m_dimK; int64_t m_voxelCountPerMap; int64_t m_mapCount; int64_t m_mapRGBACount; std::vector m_mapColoringValid; std::vector m_mapRGBA; }; #ifdef __VOLUME_FILE_VOXEL_COLORIZER_DECLARE__ // #endif // __VOLUME_FILE_VOXEL_COLORIZER_DECLARE__ } // namespace #endif //__VOLUME_FILE_VOXEL_COLORIZER_H__ workbench-1.1.1/src/Files/VolumeMapUndoCommand.cxx000066400000000000000000000111051255417355300221130ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __VOLUME_MAP_UNDO_COMMAND_DECLARE__ #include "VolumeMapUndoCommand.h" #undef __VOLUME_MAP_UNDO_COMMAND_DECLARE__ #include "CaretAssert.h" #include "VolumeFile.h" using namespace caret; /** * \class caret::VolumeMapUndoCommand * \brief Command pattern for volume map modifications that undo and redo. * \ingroup Files */ /** * Constructor. */ VolumeMapUndoCommand::VolumeMapUndoCommand(VolumeFile* volumeFile, const int32_t mapIndex) : CaretUndoCommand(), m_volumeFile(volumeFile), m_mapIndex(mapIndex) { CaretAssert(volumeFile); CaretAssert((mapIndex >= 0) && (mapIndex < volumeFile->getNumberOfMaps())); } /** * Destructor. */ VolumeMapUndoCommand::~VolumeMapUndoCommand() { for (std::vector::iterator iter = m_voxelMementos.begin(); iter != m_voxelMementos.end(); iter++) { delete *iter; } m_voxelMementos.clear(); } /** * Operation that "redoes" the command. */ void VolumeMapUndoCommand::redo() { for (std::vector::iterator iter = m_voxelMementos.begin(); iter != m_voxelMementos.end(); iter++) { const VoxelMemento* voxelMod = *iter; m_volumeFile->setValue(voxelMod->m_redoValue, voxelMod->m_ijk, m_mapIndex); } } /** * Operation that "undoes" the command. */ void VolumeMapUndoCommand::undo() { for (std::vector::iterator iter = m_voxelMementos.begin(); iter != m_voxelMementos.end(); iter++) { const VoxelMemento* voxelMod = *iter; m_volumeFile->setValue(voxelMod->m_undoValue, voxelMod->m_ijk, m_mapIndex); } } /** * @return Number of modified voxels. */ int32_t VolumeMapUndoCommand::count() const { return m_voxelMementos.size(); } /** * Add the redo and undo values for a voxel. * * @param ijk * The voxel's indices. * @param redoValue * Value for redo operation. * @param undoValue * Value for undo operation. */ void VolumeMapUndoCommand::addVoxelRedoUndo(const int64_t ijk[3], const float redoValue, const float undoValue) { m_voxelMementos.push_back(new VoxelMemento(ijk, redoValue, undoValue)); } /** * Add the redo and undo values for a voxel. * * @param i * The voxel's "i" index. * @param j * The voxel's "j" index. * @param k * The voxel's "k" index. * @param redoValue * Value for redo operation. * @param undoValue * Value for undo operation. */ void VolumeMapUndoCommand::addVoxelRedoUndo(const int64_t i, const int64_t j, const int64_t k, const float redoValue, const float undoValue) { const int64_t ijk[3] = { i, j, k }; addVoxelRedoUndo(ijk, redoValue, undoValue); } /* ------------------------------------------------------------------ */ /** * Constructor. * * @param ijk * The voxel's indices. * @param redoValue * Value for redo operation. * @param undoValue * Value for undo operation. */ VolumeMapUndoCommand::VoxelMemento::VoxelMemento(const int64_t ijk[3], const float redoValue, const float undoValue) { m_ijk[0] = ijk[0]; m_ijk[1] = ijk[1]; m_ijk[2] = ijk[2]; m_redoValue = redoValue; m_undoValue = undoValue; } VolumeMapUndoCommand::VoxelMemento::~VoxelMemento() { } workbench-1.1.1/src/Files/VolumeMapUndoCommand.h000066400000000000000000000052601255417355300215450ustar00rootroot00000000000000#ifndef __VOLUME_MAP_UNDO_COMMAND_H__ #define __VOLUME_MAP_UNDO_COMMAND_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretUndoCommand.h" namespace caret { class VolumeFile; class VolumeMapUndoCommand : public CaretUndoCommand { public: VolumeMapUndoCommand(VolumeFile* volumeFile, const int32_t mapIndex); virtual ~VolumeMapUndoCommand(); virtual void redo(); virtual void undo(); int32_t count() const; void addVoxelRedoUndo(const int64_t ijk[3], const float redoValue, const float undoValue); void addVoxelRedoUndo(const int64_t i, const int64_t j, const int64_t k, const float redoValue, const float undoValue); // ADD_NEW_METHODS_HERE private: class VoxelMemento { public: VoxelMemento(const int64_t ijk[3], const float redoValue, const float undoValue); ~VoxelMemento(); int64_t m_ijk[3]; float m_redoValue; float m_undoValue; }; VolumeMapUndoCommand(const VolumeMapUndoCommand&); VolumeMapUndoCommand& operator=(const VolumeMapUndoCommand&); VolumeFile* m_volumeFile; const int32_t m_mapIndex; std::vector m_voxelMementos; // ADD_NEW_MEMBERS_HERE }; #ifdef __VOLUME_MAP_UNDO_COMMAND_DECLARE__ // #endif // __VOLUME_MAP_UNDO_COMMAND_DECLARE__ } // namespace #endif //__VOLUME_MAP_UNDO_COMMAND_H__ workbench-1.1.1/src/Files/VolumePaddingHelper.cxx000066400000000000000000000150431255417355300217640ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "VolumePaddingHelper.h" #include "CaretAssert.h" #include "CaretException.h" #include "FloatMatrix.h" #include "GiftiLabelTable.h" #include "PaletteColorMapping.h" #include "Vector3D.h" #include "VolumeFile.h" #include using namespace caret; using namespace std; VolumePaddingHelper VolumePaddingHelper::padVoxels(const VolumeFile* orig, const int& ipad, const int& jpad, const int& kpad) { VolumePaddingHelper ret; orig->getDimensions(ret.m_origDims); ret.m_ipad = ipad; ret.m_jpad = jpad; ret.m_kpad = kpad; ret.m_origDims.resize(3);//we only care about spatial dimensions ret.m_paddedDims = ret.m_origDims; ret.m_paddedDims[0] += ipad * 2; ret.m_paddedDims[1] += jpad * 2; ret.m_paddedDims[2] += kpad * 2; ret.m_origSform = orig->getSform(); FloatMatrix origSpace(ret.m_origSform); FloatMatrix padSpace = origSpace; for (int i = 0; i < origSpace.getNumberOfRows(); ++i) { padSpace[i][3] -= ipad * origSpace[i][0] + jpad * origSpace[i][1] + kpad * origSpace[i][2]; } ret.m_paddedSform = padSpace.getMatrix(); return ret; } VolumePaddingHelper VolumePaddingHelper::padMM(const VolumeFile* orig, const float& mmpad) { vector > volSpace = orig->getSform(); Vector3D ivec, jvec, kvec, origin, ijorth, jkorth, kiorth; FloatMatrix(volSpace).getAffineVectors(ivec, jvec, kvec, origin); ijorth = ivec.cross(jvec).normal();//conceptually put a sphere on each corner of the volume, find how many voxels are needed to fully enclose them jkorth = jvec.cross(kvec).normal(); kiorth = kvec.cross(ivec).normal(); int ipad = (int)floor(abs(mmpad / ivec.dot(jkorth))) + 1; int jpad = (int)floor(abs(mmpad / jvec.dot(kiorth))) + 1; int kpad = (int)floor(abs(mmpad / kvec.dot(ijorth))) + 1; return padVoxels(orig, ipad, jpad, kpad); } void VolumePaddingHelper::doPadding(const VolumeFile* orig, VolumeFile* padded, const float& padval) { CaretAssert(padded != orig); if (!orig->matchesVolumeSpace(m_origDims.data(), m_origSform)) throw CaretException("attempted to pad a volume that doesn't match the one initialized with"); vector newdims = m_paddedDims, curdims = orig->getOriginalDimensions(); while (newdims.size() < curdims.size()) newdims.push_back(curdims[newdims.size()]);//add the nonspatial dimensions from orig padded->reinitialize(newdims, m_paddedSform, orig->getNumberOfComponents(), orig->getType()); vector padframe(m_paddedDims[0] * m_paddedDims[1] * m_paddedDims[2], padval); vector loopdims; orig->getDimensions(loopdims); for (int c = 0; c < loopdims[4]; ++c) { for (int s = 0; s < loopdims[3]; ++s) { if (c == 0) { if (orig->getType() == SubvolumeAttributes::LABEL) { *(padded->getMapLabelTable(s)) = *(orig->getMapLabelTable(s)); } else { *(padded->getMapPaletteColorMapping(s)) = *(orig->getMapPaletteColorMapping(s)); } padded->setMapName(s, orig->getMapName(s)); } int64_t ijk[3], inIndex = 0;//we scan the frame linearly, so we can do this const float* inFrame = orig->getFrame(s, c); for (ijk[2] = 0; ijk[2] < m_origDims[2]; ++ijk[2]) { for (ijk[1] = 0; ijk[1] < m_origDims[1]; ++ijk[1]) { for (ijk[0] = 0; ijk[0] < m_origDims[0]; ++ijk[0]) { int64_t outIndex = padded->getIndex(ijk[0] + m_ipad, ijk[1] + m_jpad, ijk[2] + m_kpad); padframe[outIndex] = inFrame[inIndex];//I could use pointer math instead, but that is needlessly obtuse ++inIndex; } } } padded->setFrame(padframe.data(), s, c); } } } void VolumePaddingHelper::undoPadding(const VolumeFile* padded, VolumeFile* orig) { CaretAssert(orig != padded); if (!padded->matchesVolumeSpace(m_paddedDims.data(), m_paddedSform)) throw CaretException("attempted to unpad a volume that doesn't match padding"); vector newdims = m_origDims, curdims = padded->getOriginalDimensions(); while (newdims.size() < curdims.size()) newdims.push_back(curdims[newdims.size()]);//add the nonspatial dimensions from padded orig->reinitialize(newdims, m_origSform, padded->getNumberOfComponents(), padded->getType()); vector unpadframe(m_origDims[0] * m_origDims[1] * m_origDims[2]); vector loopdims; padded->getDimensions(loopdims); for (int c = 0; c < loopdims[4]; ++c) { for (int s = 0; s < loopdims[3]; ++s) { if (c == 0) { if (padded->getType() == SubvolumeAttributes::LABEL) { *(orig->getMapLabelTable(s)) = *(padded->getMapLabelTable(s)); } else { *(orig->getMapPaletteColorMapping(s)) = *(padded->getMapPaletteColorMapping(s)); } orig->setMapName(s, padded->getMapName(s)); } int64_t ijk[3], outIndex = 0;//we scan the frame linearly, so we can do this for (ijk[2] = 0; ijk[2] < m_origDims[2]; ++ijk[2]) { for (ijk[1] = 0; ijk[1] < m_origDims[1]; ++ijk[1]) { for (ijk[0] = 0; ijk[0] < m_origDims[0]; ++ijk[0]) { unpadframe[outIndex] = padded->getValue(ijk[0] + m_ipad, ijk[1] + m_jpad, ijk[2] + m_kpad, s, c); ++outIndex; } } } orig->setFrame(unpadframe.data(), s, c); } } } workbench-1.1.1/src/Files/VolumePaddingHelper.h000066400000000000000000000032471255417355300214140ustar00rootroot00000000000000#ifndef __VOLUME_PADDING_HELPER_H__ #define __VOLUME_PADDING_HELPER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "stdint.h" #include namespace caret { class VolumeFile; class VolumePaddingHelper { std::vector > m_origSform, m_paddedSform; std::vector m_origDims, m_paddedDims; int64_t m_ipad, m_jpad, m_kpad;//hopefully apple doesn't sue us public: VolumePaddingHelper() { } static VolumePaddingHelper padMM(const VolumeFile* orig, const float& mmpad); static VolumePaddingHelper padVoxels(const VolumeFile* orig, const int& ipad, const int& jpad, const int& kpad); void doPadding(const VolumeFile* orig, VolumeFile* padded, const float& padval = 0.0f); void undoPadding(const VolumeFile* padded, VolumeFile* orig); }; } #endif //__VOLUME_PADDING_HELPER_H__ workbench-1.1.1/src/Files/VolumeSliceProjectionTypeEnum.cxx000066400000000000000000000261411255417355300240420ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __VOLUME_SLICE_PROJECTION_TYPE_ENUM_DECLARE__ #include "VolumeSliceProjectionTypeEnum.h" #undef __VOLUME_SLICE_PROJECTION_TYPE_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::VolumeSliceProjectionTypeEnum * \brief Type of projection for drawing a volume slice * * Draw volume slice with an oblique or orthogonal projection * * Using this enumerated type in the GUI with an EnumComboBoxTemplate * * Header File (.h) * Forward declare the data type: * class EnumComboBoxTemplate; * * Declare the member: * EnumComboBoxTemplate* m_volumeSliceProjectionTypeEnumComboBox; * * Declare a slot that is called when user changes selection * private slots: * void volumeSliceProjectionTypeEnumComboBoxItemActivated(); * * Implementation File (.cxx) * Include the header files * #include "EnumComboBoxTemplate.h" * #include "VolumeSliceProjectionTypeEnum.h" * * Instatiate: * m_volumeSliceProjectionTypeEnumComboBox = new EnumComboBoxTemplate(this); * m_volumeSliceProjectionTypeEnumComboBox->setup(); * * Get notified when the user changes the selection: * QObject::connect(m_volumeSliceProjectionTypeEnumComboBox, SIGNAL(itemActivated()), * this, SLOT(volumeSliceProjectionTypeEnumComboBoxItemActivated())); * * Update the selection: * m_volumeSliceProjectionTypeEnumComboBox->setSelectedItem(NEW_VALUE); * * Read the selection: * const VolumeSliceProjectionTypeEnum::Enum VARIABLE = m_volumeSliceProjectionTypeEnumComboBox->getSelectedItem(); * */ /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ VolumeSliceProjectionTypeEnum::VolumeSliceProjectionTypeEnum(const Enum enumValue, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCodeCounter++; this->name = name; this->guiName = guiName; } /** * Destructor. */ VolumeSliceProjectionTypeEnum::~VolumeSliceProjectionTypeEnum() { } /** * Initialize the enumerated metadata. */ void VolumeSliceProjectionTypeEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(VolumeSliceProjectionTypeEnum(VOLUME_SLICE_PROJECTION_OBLIQUE, "VOLUME_SLICE_PROJECTION_OBLIQUE", "Oblique")); enumData.push_back(VolumeSliceProjectionTypeEnum(VOLUME_SLICE_PROJECTION_ORTHOGONAL, "VOLUME_SLICE_PROJECTION_ORTHOGONAL", "Orthogonal")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const VolumeSliceProjectionTypeEnum* VolumeSliceProjectionTypeEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const VolumeSliceProjectionTypeEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString VolumeSliceProjectionTypeEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const VolumeSliceProjectionTypeEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ VolumeSliceProjectionTypeEnum::Enum VolumeSliceProjectionTypeEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = VolumeSliceProjectionTypeEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const VolumeSliceProjectionTypeEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type VolumeSliceProjectionTypeEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString VolumeSliceProjectionTypeEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const VolumeSliceProjectionTypeEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ VolumeSliceProjectionTypeEnum::Enum VolumeSliceProjectionTypeEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = VolumeSliceProjectionTypeEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const VolumeSliceProjectionTypeEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type VolumeSliceProjectionTypeEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t VolumeSliceProjectionTypeEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const VolumeSliceProjectionTypeEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ VolumeSliceProjectionTypeEnum::Enum VolumeSliceProjectionTypeEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = VolumeSliceProjectionTypeEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const VolumeSliceProjectionTypeEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type VolumeSliceProjectionTypeEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void VolumeSliceProjectionTypeEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void VolumeSliceProjectionTypeEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(VolumeSliceProjectionTypeEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void VolumeSliceProjectionTypeEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(VolumeSliceProjectionTypeEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } workbench-1.1.1/src/Files/VolumeSliceProjectionTypeEnum.h000066400000000000000000000064731255417355300234750ustar00rootroot00000000000000#ifndef __VOLUME_SLICE_PROJECTION_TYPE_ENUM_H__ #define __VOLUME_SLICE_PROJECTION_TYPE_ENUM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class VolumeSliceProjectionTypeEnum { public: /** * Enumerated values. */ enum Enum { /** Draw volume slice with an oblique projection */ VOLUME_SLICE_PROJECTION_OBLIQUE, /** Draw volume slice with an orthogonal projection */ VOLUME_SLICE_PROJECTION_ORTHOGONAL }; ~VolumeSliceProjectionTypeEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); private: VolumeSliceProjectionTypeEnum(const Enum enumValue, const AString& name, const AString& guiName); static const VolumeSliceProjectionTypeEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; #ifdef __VOLUME_SLICE_PROJECTION_TYPE_ENUM_DECLARE__ std::vector VolumeSliceProjectionTypeEnum::enumData; bool VolumeSliceProjectionTypeEnum::initializedFlag = false; int32_t VolumeSliceProjectionTypeEnum::integerCodeCounter = 0; #endif // __VOLUME_SLICE_PROJECTION_TYPE_ENUM_DECLARE__ } // namespace #endif //__VOLUME_SLICE_PROJECTION_TYPE_ENUM_H__ workbench-1.1.1/src/Files/VolumeSpline.cxx000066400000000000000000000233771255417355300205210ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretOMP.h" #include "CubicSpline.h" #include "MathFunctions.h" #include "VolumeSpline.h" #include #include #include using namespace std; using namespace caret; VolumeSpline::VolumeSpline() { m_ignoredNonNumeric = false; m_dims[0] = 0; m_dims[1] = 0; m_dims[2] = 0; } VolumeSpline::VolumeSpline(const float* frame, const int64_t framedims[3]) { m_ignoredNonNumeric = false; m_dims[0] = framedims[0]; m_dims[1] = framedims[1]; m_dims[2] = framedims[2]; m_deconv = CaretArray(m_dims[0] * m_dims[1] * m_dims[2]); CaretArray scratchArray(m_dims[0] * max(m_dims[1], m_dims[2])), deconvScratch(max(m_dims[0], max(m_dims[1], m_dims[2])));//allocate as much as we will need, even if we don't use it all yet predeconvolve(deconvScratch, m_dims[0]); for (int k = 0; k < m_dims[2]; ++k) { int64_t index = m_dims[0] * m_dims[1] * k; int64_t index2 = 0; for (int j = 0; j < m_dims[1]; ++j) { for (int i = 0; i < m_dims[0]; ++i) { float tempf = frame[index]; if (MathFunctions::isNumeric(tempf)) { scratchArray[index2] = tempf; } else { scratchArray[index2] = 0.0f; m_ignoredNonNumeric = true; } ++index; ++index2; } } #pragma omp CARET_PARFOR schedule(dynamic) for (int j = 0; j < m_dims[1]; ++j) { int64_t privIndex = j * m_dims[0]; deconvolve(scratchArray.getArray() + privIndex, deconvScratch, m_dims[0]); } index = m_dims[0] * m_dims[1] * k; index2 = 0; for (int j = 0; j < m_dims[1]; ++j) { for (int i = 0; i < m_dims[0]; ++i) { m_deconv[index] = scratchArray[index2]; ++index; ++index2; } } } predeconvolve(deconvScratch, m_dims[1]); for (int k = 0; k < m_dims[2]; ++k) { int64_t indexbase = k * m_dims[1] * m_dims[0]; int64_t index = indexbase; for (int j = 0; j < m_dims[1]; ++j) { int64_t index2 = j; for (int i = 0; i < m_dims[0]; ++i) { scratchArray[index2] = m_deconv[index];//read linearly from frame while writing transposed should be slightly faster, because cache can stay dirty? index2 += m_dims[1]; ++index; } } #pragma omp CARET_PARFOR schedule(dynamic) for (int i = 0; i < m_dims[0]; ++i) { int64_t privindex = i * m_dims[1]; deconvolve(scratchArray.getArray() + privindex, deconvScratch, m_dims[1]); } index = 0; for (int i = 0; i < m_dims[0]; ++i) { int64_t index2 = i + indexbase; for (int j = 0; j < m_dims[1]; ++j) { m_deconv[index2] = scratchArray[index];//even though scratch should be cached now, if writing to frame collides, reading linearly should give better behavior ++index; index2 += m_dims[0]; } } } predeconvolve(deconvScratch, m_dims[2]); for (int j = 0; j < m_dims[1]; ++j)//finally, use a similar strategy to do linear reads instead of widely interleaved reads for k-rows { int64_t indexbase = j * m_dims[0]; int64_t increment = m_dims[1] * m_dims[0]; for (int k = 0; k < m_dims[2]; ++k) { int64_t index = indexbase + k * increment; int64_t index2 = k; for (int i = 0; i < m_dims[0]; ++i) { scratchArray[index2] = m_deconv[index]; index2 += m_dims[2]; ++index; } } #pragma omp CARET_PARFOR schedule(dynamic) for (int i = 0; i < m_dims[0]; ++i) { int64_t privindex = i * m_dims[2]; deconvolve(scratchArray.getArray() + privindex, deconvScratch, m_dims[2]); } for (int i = 0; i < m_dims[0]; ++i) { int64_t index = indexbase + i; int64_t index2 = i * m_dims[2]; for (int k = 0; k < m_dims[2]; ++k) { m_deconv[index] = scratchArray[index2]; index += increment; ++index2; } } } } float VolumeSpline::sample(const float& ifloat, const float& jfloat, const float& kfloat) { if (m_dims[0] < 1 || ifloat < 0.0f || jfloat < 0.0f || kfloat < 0.0f || ifloat > m_dims[0] - 1 || jfloat > m_dims[1] - 1 || kfloat > m_dims[2] - 1) return 0.0f;//yeesh const int64_t zstep = m_dims[0] * m_dims[1]; float iparti, ipartj, ipartk; float fparti = modf(ifloat, &iparti); float fpartj = modf(jfloat, &ipartj); float fpartk = modf(kfloat, &ipartk); int64_t lowi = (int64_t)iparti; int64_t lowj = (int64_t)ipartj; int64_t lowk = (int64_t)ipartk; bool lowedgei = (lowi < 1); bool lowedgej = (lowj < 1); bool lowedgek = (lowk < 1); bool highedgei = (lowi >= m_dims[0] - 2); bool highedgej = (lowj >= m_dims[1] - 2); bool highedgek = (lowk >= m_dims[2] - 2); CubicSpline ispline = CubicSpline::bspline(fparti, lowedgei, highedgei); CubicSpline jspline = CubicSpline::bspline(fpartj, lowedgej, highedgej); CubicSpline kspline = CubicSpline::bspline(fpartk, lowedgek, highedgek); float jtemp[4], ktemp[4];//the weights of the splines are zero for off-the edge values, but zero the data anyway jtemp[0] = 0.0f; jtemp[3] = 0.0f; ktemp[0] = 0.0f; ktemp[3] = 0.0f; if (lowedgei || lowedgej || lowedgek || highedgei || highedgej || highedgek) {//there is an edge nearby, use the generic version with more conditionals int jstart = lowedgej ? 1 : 0; int kstart = lowedgek ? 1 : 0; int jend = highedgej ? 3 : 4; int kend = highedgek ? 3 : 4; for (int k = kstart; k < kend; ++k) { int64_t indexk = (k + lowk - 1) * zstep; for (int j = jstart; j < jend; ++j) { int64_t indexj = indexk + (j + lowj - 1) * m_dims[0] + lowi - 1; if (lowedgei)//have to do these tests for the simple reason that otherwise we might access off the end of the array in two of the 8 corners { if (highedgei) { jtemp[j] = ispline.evalBothEdge(m_deconv[indexj + 1], m_deconv[indexj + 2]); } else { jtemp[j] = ispline.evalLowEdge(m_deconv[indexj + 1], m_deconv[indexj + 2], m_deconv[indexj + 3]); } } else { if (highedgei) { jtemp[j] = ispline.evalHighEdge(m_deconv[indexj], m_deconv[indexj + 1], m_deconv[indexj + 2]); } else { jtemp[j] = ispline.evaluate(m_deconv[indexj], m_deconv[indexj + 1], m_deconv[indexj + 2], m_deconv[indexj + 3]); } } } ktemp[k] = jspline.evaluate(jtemp[0], jtemp[1], jtemp[2], jtemp[3]); } return kspline.evaluate(ktemp[0], ktemp[1], ktemp[2], ktemp[3]); } else {//we are clear of all edges, we can use fewer conditionals int64_t indexbase = lowi - 1 + m_dims[0] * (lowj - 1 + m_dims[1] * (lowk - 1)); const float* basePtr = m_deconv.getArray() + indexbase; int64_t indexk = 0; for (int k = 0; k < 4; ++k) { int64_t indexj = indexk; for (int j = 0; j < 4; ++j) { jtemp[j] = ispline.evaluate(basePtr[indexj], basePtr[indexj + 1], basePtr[indexj + 2], basePtr[indexj + 3]); indexj += m_dims[0]; } ktemp[k] = jspline.evaluate(jtemp[0], jtemp[1], jtemp[2], jtemp[3]); indexk += zstep; } return kspline.evaluate(ktemp[0], ktemp[1], ktemp[2], ktemp[3]); } } void VolumeSpline::deconvolve(float* data, const float* backsubs, const int64_t& length) { if (length < 1) return; const float A = 1.0f / 6.0f, B = 2.0f / 3.0f;//the coefficients of a bspline at center and +/-1 //forward pass simulating gaussian elimination on matrix of bspline kernels and data data[0] /= B; for (int i = 1; i < length; ++i)//the first row is handled slightly differently { data[i] = (data[i] - A * data[i - 1]) / (B - A * backsubs[i - 1]); }//back substitution, making it gauss-jordan for (int i = length - 2; i >= 0; --i)//the last row doesn't need back-substitution { data[i] -= backsubs[i] * data[i + 1]; } } void VolumeSpline::predeconvolve(float* backsubs, const int64_t& length) { if (length < 1) return; const float A = 1.0f / 6.0f, B = 2.0f / 3.0f; backsubs[0] = A / B; for (int i = 1; i < length; ++i) { backsubs[i] = A / (B - A * backsubs[i - 1]); } } workbench-1.1.1/src/Files/VolumeSpline.h000066400000000000000000000036511255417355300201370ustar00rootroot00000000000000#ifndef __VOLUME_SPLINE_H__ #define __VOLUME_SPLINE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "stdint.h" #include "CaretPointer.h" namespace caret { class VolumeSpline { bool m_ignoredNonNumeric; int64_t m_dims[3]; CaretArray m_deconv;//don't do lazy deconvolution, it doesn't save much time, and takes more memory and slightly longer if you have to do the whole volume anyway void deconvolve(float* data, const float* backsubs, const int64_t& length);//use CaretArray so that it doesn't reallocate like a vector on copy, and the data is static once computed void predeconvolve(float* backsubs, const int64_t& length);//since the back substitution on the same size array uses the same coefficients, precompute them public: VolumeSpline(); VolumeSpline(const float* frame, const int64_t framedims[3]); float sample(const float& i, const float& j, const float& k); float sample(const float ijk[3]) { return sample(ijk[0], ijk[1], ijk[2]); } bool ignoredNonNumeric() const { return m_ignoredNonNumeric; } }; } #endif //__VOLUME_SPLINE_H__ workbench-1.1.1/src/Files/VtkFileExporter.cxx000066400000000000000000000366741255417355300212000ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __VTK_FILE_EXPORTER_DECLARE__ #include "VtkFileExporter.h" #undef __VTK_FILE_EXPORTER_DECLARE__ #include "AString.h" #include "ByteOrderEnum.h" #include "CaretAssert.h" #include "DataFileException.h" #include "FileAdapter.h" #include "SurfaceFile.h" #include "XmlWriter.h" #include using namespace caret; /** * \class caret::VtkFileExporter * \brief Exports data into VTK file formats. * \ingroup Files */ /** * Write the given surface files using the coloring from the given browser tab. * * File format documented at: http://www.vtk.org/VTK/img/file-formats.pdf * * @param surfaceFile * The surface file. * @param surfaceFilesColoring * RGBA coloring for each of the surfaces. * @param vtkFileName * Name of the VTK file. */ void VtkFileExporter::writeSurfaces(const std::vector& surfaceFiles, const std::vector& surfaceFilesColoring, const AString& vtkFileName) { try { const int32_t numberOfSurfaceFiles = static_cast(surfaceFiles.size()); if (numberOfSurfaceFiles <= 0) { throw DataFileException(vtkFileName, "No surfaces provided for export to VTK."); } CaretAssert(surfaceFiles.size() == surfaceFilesColoring.size()); int32_t totalNodes = 0; int32_t totalTriangles = 0; float coordinateMinimum = std::numeric_limits::max(); float coordinateMaximum = -std::numeric_limits::max(); for (int32_t iSurface = 0; iSurface < numberOfSurfaceFiles; iSurface++) { SurfaceFile* surfaceFile = surfaceFiles[iSurface]; const int32_t numberOfNodes = surfaceFile->getNumberOfNodes(); totalNodes += numberOfNodes; const int32_t numberOfTriangles = surfaceFile->getNumberOfTriangles(); totalTriangles += numberOfTriangles; for (int32_t iNode = 0; iNode < numberOfNodes; iNode++) { const float* xyz = surfaceFile->getCoordinate(iNode); for (int32_t i = 0; i < 3; i++) { if (xyz[i] < coordinateMinimum) coordinateMinimum = xyz[i]; if (xyz[i] > coordinateMaximum) coordinateMaximum = xyz[i]; } } } if (totalNodes <= 0) { throw DataFileException(vtkFileName, "Surfaces contain no nodes"); } if (totalNodes <= 0) { throw DataFileException(vtkFileName, "Surfaces contain no triangles"); } /* * Open a text stream */ FileAdapter fileAdapter; AString errorMessage; QTextStream* textStream = fileAdapter.openQTextStreamForWritingFile(vtkFileName, errorMessage); if (textStream == NULL) { throw DataFileException(vtkFileName, errorMessage); } /* * Create the XML Writer */ XmlWriter xmlWriter(*textStream); xmlWriter.writeStartDocument("1.0"); /* * Write the root element */ XmlAttributes rootAttributes; rootAttributes.addAttribute("type", "PolyData"); rootAttributes.addAttribute("version", "0.1"); switch (ByteOrderEnum::getSystemEndian()) { case ByteOrderEnum::ENDIAN_BIG: rootAttributes.addAttribute("byte_order", "BigEndian"); break; case ByteOrderEnum::ENDIAN_LITTLE: rootAttributes.addAttribute("byte_order", "LittleEndian"); break; } rootAttributes.addAttribute("compressor", "vtkZLibDataCompressor"); xmlWriter.writeStartElement("VTKFile", rootAttributes); /* * Start PolyData element */ xmlWriter.writeStartElement("PolyData"); /* * Start Piece element */ XmlAttributes pieceAttributes; pieceAttributes.addAttribute("NumberOfPoints", totalNodes); pieceAttributes.addAttribute("NumberOfVerts", 0); pieceAttributes.addAttribute("NumberOfLines", 0); pieceAttributes.addAttribute("NumberOfStrips", 0); pieceAttributes.addAttribute("NumberOfPolys", totalTriangles); xmlWriter.writeStartElement("Piece", pieceAttributes); /* * Start PointData */ XmlAttributes pointDataAttributes; pointDataAttributes.addAttribute("Scalars", "Scalars_"); pointDataAttributes.addAttribute("Normals", "Normals"); xmlWriter.writeStartElement("PointData", pointDataAttributes); /* * Start Normal Vectors DataArray element */ XmlAttributes dataArrayNormalsAttributes; dataArrayNormalsAttributes.addAttribute("type", "Float32"); dataArrayNormalsAttributes.addAttribute("Name", "Normals"); dataArrayNormalsAttributes.addAttribute("NumberOfComponents", 3); dataArrayNormalsAttributes.addAttribute("format", "ascii"); dataArrayNormalsAttributes.addAttribute("RangeMin", -1.0); dataArrayNormalsAttributes.addAttribute("RangeMax", 1.0); xmlWriter.writeStartElement("DataArray", dataArrayNormalsAttributes); for (int32_t iSurface = 0; iSurface < numberOfSurfaceFiles; iSurface++) { const SurfaceFile* sf = surfaceFiles[iSurface]; const int32_t numberOfNodes = sf->getNumberOfNodes(); for (int32_t iNode = 0; iNode < numberOfNodes; iNode++) { const float* normalVector = sf->getNormalVector(iNode); xmlWriter.writeCharactersWithIndent(AString::number(normalVector[0])); xmlWriter.writeCharacters(" "); xmlWriter.writeCharacters(AString::number(normalVector[1])); xmlWriter.writeCharacters(" "); xmlWriter.writeCharacters(AString::number(normalVector[2])); xmlWriter.writeCharacters("\n"); } } /* * End DataArray Normals element */ xmlWriter.writeEndElement(); /* * Start Scalars DataArray element * that contains the RGB colors */ XmlAttributes dataArrayScalarsAttributes; dataArrayScalarsAttributes.addAttribute("type", "UInt8"); dataArrayScalarsAttributes.addAttribute("Name", "Scalars_"); dataArrayScalarsAttributes.addAttribute("NumberOfComponents", 3); dataArrayScalarsAttributes.addAttribute("format", "ascii"); dataArrayScalarsAttributes.addAttribute("RangeMin", 0); dataArrayScalarsAttributes.addAttribute("RangeMax", 255); xmlWriter.writeStartElement("DataArray", dataArrayScalarsAttributes); for (int32_t iSurface = 0; iSurface < numberOfSurfaceFiles; iSurface++) { SurfaceFile* sf = surfaceFiles[iSurface]; const float* surfaceRGBA = surfaceFilesColoring[iSurface]; const int32_t numberOfNodes = sf->getNumberOfNodes(); for (int32_t iNode = 0; iNode < numberOfNodes; iNode++) { const float* rgbaFloat = &surfaceRGBA[iNode * 4]; uint8_t rgb[3]; for (int32_t k = 0; k < 3; k++) { float value = rgbaFloat[k] * 255.0; if (value > 255.0) value = 255.0; if (value < 0.0) value = 0.0; const uint8_t byteValue = static_cast(value); rgb[k] = byteValue; } xmlWriter.writeCharactersWithIndent(AString::number(rgb[0])); xmlWriter.writeCharacters(" "); xmlWriter.writeCharacters(AString::number(rgb[1])); xmlWriter.writeCharacters(" "); xmlWriter.writeCharacters(AString::number(rgb[2])); xmlWriter.writeCharacters("\n"); } } /* * End DataArray Scalars element */ xmlWriter.writeEndElement(); /* * End PointData */ xmlWriter.writeEndElement(); /* * No Cell Data */ xmlWriter.writeStartElement("CellData"); xmlWriter.writeEndElement(); /* * Start Points element */ xmlWriter.writeStartElement("Points"); /* * Start Coordinates DataArray element */ XmlAttributes dataArrayPointsAttributes; dataArrayPointsAttributes.addAttribute("type", "Float32"); dataArrayPointsAttributes.addAttribute("Name", "Points"); dataArrayPointsAttributes.addAttribute("NumberOfComponents", 3); dataArrayPointsAttributes.addAttribute("format", "ascii"); dataArrayPointsAttributes.addAttribute("RangeMin", coordinateMinimum); dataArrayPointsAttributes.addAttribute("RangeMax", coordinateMaximum); xmlWriter.writeStartElement("DataArray", dataArrayPointsAttributes); for (int32_t iSurface = 0; iSurface < numberOfSurfaceFiles; iSurface++) { const SurfaceFile* sf = surfaceFiles[iSurface]; const int32_t numberOfNodes = sf->getNumberOfNodes(); for (int32_t iNode = 0; iNode < numberOfNodes; iNode++) { const float* normalVector = sf->getCoordinate(iNode); xmlWriter.writeCharactersWithIndent(AString::number(normalVector[0])); xmlWriter.writeCharacters(" "); xmlWriter.writeCharacters(AString::number(normalVector[1])); xmlWriter.writeCharacters(" "); xmlWriter.writeCharacters(AString::number(normalVector[2])); xmlWriter.writeCharacters("\n"); } } /* * End DataArray Coordinates */ xmlWriter.writeEndElement(); /* * End Points element */ xmlWriter.writeEndElement(); /* * Empty Verts Element */ xmlWriter.writeStartElement("Verts"); xmlWriter.writeEndElement(); /* * Empty Lines Element */ xmlWriter.writeStartElement("Lines"); xmlWriter.writeEndElement(); /* * Empty Strips Element */ xmlWriter.writeStartElement("Strips"); xmlWriter.writeEndElement(); /* * Start Polys Element */ xmlWriter.writeStartElement("Polys"); /* * Start DataArray for nodes in every triangle */ XmlAttributes dataArrayTrianglesAttributes; dataArrayTrianglesAttributes.addAttribute("type", "Int64"); dataArrayTrianglesAttributes.addAttribute("Name", "connectivity"); dataArrayTrianglesAttributes.addAttribute("format", "ascii"); dataArrayTrianglesAttributes.addAttribute("RangeMin", 0); dataArrayTrianglesAttributes.addAttribute("RangeMax", totalNodes - 1); xmlWriter.writeStartElement("DataArray", dataArrayTrianglesAttributes); /* * Write the nodes in every triangle */ int32_t triangleNodeOffset = 0; for (int32_t iSurface = 0; iSurface < numberOfSurfaceFiles; iSurface++) { const SurfaceFile* sf = surfaceFiles[iSurface]; const int32_t numberOfTriangles = sf->getNumberOfTriangles(); for (int32_t iTriangle = 0; iTriangle < numberOfTriangles; iTriangle++) { const int32_t* triangleNodes = sf->getTriangle(iTriangle); xmlWriter.writeCharactersWithIndent(AString::number(triangleNodes[0] + triangleNodeOffset)); xmlWriter.writeCharacters(" "); xmlWriter.writeCharacters(AString::number(triangleNodes[1] + triangleNodeOffset)); xmlWriter.writeCharacters(" "); xmlWriter.writeCharacters(AString::number(triangleNodes[2] + triangleNodeOffset)); xmlWriter.writeCharacters("\n"); } /* * All surface nodes are in one list so offset for additional surfaces */ triangleNodeOffset += sf->getNumberOfNodes(); } /* * End DataArray for nodes in every triangle */ xmlWriter.writeEndElement(); /* * Start DataArray for offset of each triangle */ XmlAttributes dataArrayTriangleOffsetAttributes; dataArrayTriangleOffsetAttributes.addAttribute("type", "Int64"); dataArrayTriangleOffsetAttributes.addAttribute("Name", "offsets"); dataArrayTriangleOffsetAttributes.addAttribute("format", "ascii"); dataArrayTriangleOffsetAttributes.addAttribute("RangeMin", 0); dataArrayTriangleOffsetAttributes.addAttribute("RangeMax", totalTriangles - 1); xmlWriter.writeStartElement("DataArray", dataArrayTriangleOffsetAttributes); /* * Write offset of each triangle */ for (int32_t i = 1; i <= totalTriangles; i++) { if ((i % 6) == 0) { if (i > 0) { xmlWriter.writeCharacters("\n"); } xmlWriter.writeCharactersWithIndent(AString::number(i * 3)); } else { xmlWriter.writeCharacters(AString::number(i * 3)); } xmlWriter.writeCharacters(" "); } xmlWriter.writeCharacters("\n"); /* * End DataArray for offset of each triangle */ xmlWriter.writeEndElement(); /* * End Polys Element */ xmlWriter.writeEndElement(); /* * End Piece element */ xmlWriter.writeEndElement(); /* * End PolyData element */ xmlWriter.writeEndElement(); /* * End root element */ xmlWriter.writeEndElement(); /* * Finish XML and close file */ xmlWriter.writeEndDocument(); fileAdapter.close(); } catch (const XmlException& e) { throw DataFileException(vtkFileName, e.whatString()); } } workbench-1.1.1/src/Files/VtkFileExporter.h000066400000000000000000000033621255417355300206110ustar00rootroot00000000000000#ifndef __VTK_FILE_EXPORTER_H__ #define __VTK_FILE_EXPORTER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AString.h" #include namespace caret { class SurfaceFile; class VtkFileExporter { public: static void writeSurfaces(const std::vector& surfaceFiles, const std::vector& surfaceFilesColoring, const AString& vtkFileName); private: VtkFileExporter(); virtual ~VtkFileExporter(); VtkFileExporter(const VtkFileExporter&); VtkFileExporter& operator=(const VtkFileExporter&); public: // ADD_NEW_METHODS_HERE private: // ADD_NEW_MEMBERS_HERE }; #ifdef __VTK_FILE_EXPORTER_DECLARE__ // #endif // __VTK_FILE_EXPORTER_DECLARE__ } // namespace #endif //__VTK_FILE_EXPORTER_H__ workbench-1.1.1/src/Files/WarpfieldFile.cxx000066400000000000000000000156031255417355300206050ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "WarpfieldFile.h" #include "CaretAssert.h" #include "FileInformation.h" #include "FloatMatrix.h" #include "NiftiIO.h" using namespace caret; using namespace std; void WarpfieldFile::readWorld(const AString& warpname) { CaretPointer newFile(new VolumeFile()); newFile->readFile(warpname); vector dims; newFile->getDimensions(dims); if (dims[3] != 3) { throw DataFileException("volume file '" + warpname + "' has the wrong number of subvolumes for a warpfield"); } if (dims[4] != 1) { throw DataFileException("volume file '" + warpname + "' has multiple components, which is not allowed in a warpfield"); } m_warpfield = newFile;//drop the previous warpfield, and replace with the new one m_warpfield->setMapName(0, "x displacement"); m_warpfield->setMapName(1, "y displacement"); m_warpfield->setMapName(2, "z displacement"); } void WarpfieldFile::readFnirt(const AString& warpName, const AString& sourceName) { FloatMatrix sourceSform, sourceFSL, refSform, refFSL; NiftiIO myIO; myIO.openRead(warpName); refSform = FloatMatrix(myIO.getHeader().getSForm()); refFSL = FloatMatrix(myIO.getHeader().getFSLSpace()); myIO.openRead(sourceName); sourceSform = FloatMatrix(myIO.getHeader().getSForm()); sourceFSL = FloatMatrix(myIO.getHeader().getFSLSpace()); CaretPointer newFile(new VolumeFile()); newFile->readFile(warpName); vector dims; newFile->getDimensions(dims); if (dims[3] != 3) { throw DataFileException("volume file '" + warpName + "' has the wrong number of subvolumes for a warpfield"); } if (dims[4] != 1) { throw DataFileException("volume file '" + warpName + "' has multiple components, which is not allowed in a warpfield"); } FloatMatrix sourceTransform = sourceSform * sourceFSL.inverse();//goes from FSL source space to real source space Vector3D sourceTransX, sourceTransY, sourceTransZ, sourceTransOff; sourceTransform.getAffineVectors(sourceTransX, sourceTransY, sourceTransZ, sourceTransOff); Vector3D fslX, fslY, fslZ, fslOff; refFSL.getAffineVectors(fslX, fslY, fslZ, fslOff); for (int k = 0; k < dims[2]; ++k) { for (int j = 0; j < dims[1]; ++j) { for (int i = 0; i < dims[0]; ++i) { Vector3D fslcoord = i * fslX + j * fslY + k * fslZ + fslOff; Vector3D coord, fsldisplacement; newFile->indexToSpace(i, j, k, coord); fsldisplacement[0] = newFile->getValue(i, j, k, 0); fsldisplacement[1] = newFile->getValue(i, j, k, 1); fsldisplacement[2] = newFile->getValue(i, j, k, 2); Vector3D fslTransAbsolute = fslcoord + fsldisplacement; Vector3D transAbsolute = fslTransAbsolute[0] * sourceTransX + fslTransAbsolute[1] * sourceTransY + fslTransAbsolute[2] * sourceTransZ + sourceTransOff; Vector3D transdisplace = transAbsolute - coord; newFile->setValue(transdisplace[0], i, j, k, 0);//overwrite vectors in place to save memory newFile->setValue(transdisplace[1], i, j, k, 1); newFile->setValue(transdisplace[2], i, j, k, 2); } } } m_warpfield = newFile;//drop the previous warpfield, and replace with the new one m_warpfield->setMapName(0, "x displacement"); m_warpfield->setMapName(1, "y displacement"); m_warpfield->setMapName(2, "z displacement"); } void WarpfieldFile::writeWorld(const AString& warpname) { if (m_warpfield == NULL) throw DataFileException("writeWorld called on uninitialized warpfield"); m_warpfield->writeFile(warpname); } void WarpfieldFile::writeFnirt(const AString& warpname, const AString& sourceName) { if (m_warpfield == NULL) throw DataFileException("writeFnirt called on uninitialized warpfield"); FloatMatrix sourceSform, sourceFSL, refSform, refFSL; NiftiHeader myHeader; myHeader.setSForm(m_warpfield->getSform()); myHeader.setDimensions(m_warpfield->getOriginalDimensions()); refSform = FloatMatrix(myHeader.getSForm()); refFSL = FloatMatrix(myHeader.getFSLSpace()); NiftiIO sourceIO; sourceIO.openRead(sourceName); sourceSform = FloatMatrix(sourceIO.getHeader().getSForm()); sourceFSL = FloatMatrix(sourceIO.getHeader().getFSLSpace()); VolumeFile outFile; vector dims; m_warpfield->getDimensions(dims); dims.resize(4);//drop number of components outFile.reinitialize(dims, m_warpfield->getSform()); outFile.setMapName(0, "x displacement"); outFile.setMapName(1, "y displacement"); outFile.setMapName(2, "z displacement"); FloatMatrix FSLTransform = sourceFSL * sourceSform.inverse();//goes from real space to FSL source space Vector3D FSLTransX, FSLTransY, FSLTransZ, FSLTransOff; FSLTransform.getAffineVectors(FSLTransX, FSLTransY, FSLTransZ, FSLTransOff); Vector3D fslX, fslY, fslZ, fslOff; refFSL.getAffineVectors(fslX, fslY, fslZ, fslOff); for (int k = 0; k < dims[2]; ++k) { for (int j = 0; j < dims[1]; ++j) { for (int i = 0; i < dims[0]; ++i) { Vector3D fslcoord = i * fslX + j * fslY + k * fslZ + fslOff; Vector3D coord, realdisplacement; m_warpfield->indexToSpace(i, j, k, coord); realdisplacement[0] = m_warpfield->getValue(i, j, k, 0); realdisplacement[1] = m_warpfield->getValue(i, j, k, 1); realdisplacement[2] = m_warpfield->getValue(i, j, k, 2); Vector3D realabsolute = coord + realdisplacement; Vector3D fslabsolute = realabsolute[0] * FSLTransX + realabsolute[1] * FSLTransY + realabsolute[2] * FSLTransZ + FSLTransOff; Vector3D fsldisplace = fslabsolute - fslcoord; outFile.setValue(fsldisplace[0], i, j, k, 0); outFile.setValue(fsldisplace[1], i, j, k, 1); outFile.setValue(fsldisplace[2], i, j, k, 2); } } } outFile.writeFile(warpname); } workbench-1.1.1/src/Files/WarpfieldFile.h000066400000000000000000000027011255417355300202250ustar00rootroot00000000000000#ifndef __WARPFIELD_FILE_H__ #define __WARPFIELD_FILE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AString.h" #include "CaretPointer.h" #include "VolumeFile.h" namespace caret { class WarpfieldFile { CaretPointer m_warpfield; public: const VolumeFile* getWarpfield() { return m_warpfield.getPointer(); } void readFnirt(const AString& warpName, const AString& sourceName); void readWorld(const AString& warpname); void writeFnirt(const AString& warpname, const AString& sourceName);//for completeness void writeWorld(const AString& warpname); }; } #endif //__WARPFIELD_FILE_H__ workbench-1.1.1/src/FilesBase/000077500000000000000000000000001255417355300161325ustar00rootroot00000000000000workbench-1.1.1/src/FilesBase/CMakeLists.txt000066400000000000000000000014561255417355300207000ustar00rootroot00000000000000# # The FilesBase Project # project (FilesBase) # # Need XML from Qt # SET(QT_DONT_USE_QTGUI) # # Add QT for includes # INCLUDE (${QT_USE_FILE}) # # Create the NIFTI library # ADD_LIBRARY(FilesBase DisplayGroupEnum.h GiftiException.h GiftiLabel.h GiftiLabelTable.h GiftiMetaData.h GiftiMetaDataXmlElements.h GiftiXmlElements.h nifti1.h nifti2.h NiftiEnums.h VolumeBase.h VolumeMappableInterface.h VolumeSliceViewPlaneEnum.h VolumeSpace.h DisplayGroupEnum.cxx GiftiException.cxx GiftiLabel.cxx GiftiLabelTable.cxx GiftiMetaData.cxx GiftiXmlElements.cxx NiftiEnums.cxx VolumeBase.cxx VolumeMappableInterface.cxx VolumeSliceViewPlaneEnum.cxx VolumeSpace.cxx ) # # Find Headers # INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/FilesBase ${CMAKE_SOURCE_DIR}/Common ${CMAKE_SOURCE_DIR}/Palette ${CMAKE_SOURCE_DIR}/Xml ) workbench-1.1.1/src/FilesBase/DisplayGroupEnum.cxx000066400000000000000000000235301255417355300221300ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __DISPLAY_GROUP_ENUM_DECLARE__ #include "DisplayGroupEnum.h" #undef __DISPLAY_GROUP_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::DisplayGroupEnum * \brief Enumerated types for grouping related data. * * This class provides an enumerated type that * can be used by other classes to group related * properties, typically display properties. */ /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ DisplayGroupEnum::DisplayGroupEnum(const Enum enumValue, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCodeCounter++; this->name = name; this->guiName = guiName; } /** * Destructor. */ DisplayGroupEnum::~DisplayGroupEnum() { } /** * Initialize the enumerated metadata. */ void DisplayGroupEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(DisplayGroupEnum(DISPLAY_GROUP_TAB, "DISPLAY_GROUP_TAB", "Tab")); enumData.push_back(DisplayGroupEnum(DISPLAY_GROUP_A, "DISPLAY_GROUP_A", "Group A")); enumData.push_back(DisplayGroupEnum(DISPLAY_GROUP_B, "DISPLAY_GROUP_B", "Group B")); enumData.push_back(DisplayGroupEnum(DISPLAY_GROUP_C, "DISPLAY_GROUP_C", "Group C")); enumData.push_back(DisplayGroupEnum(DISPLAY_GROUP_D, "DISPLAY_GROUP_D", "Group D")); if (static_cast(enumData.size()) != DisplayGroupEnum::NUMBER_OF_GROUPS) { CaretAssertMessage(0, "NUMBER_OF_GROUPS constant is incorrect. New ENUMs added?"); } } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const DisplayGroupEnum* DisplayGroupEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const DisplayGroupEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString DisplayGroupEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const DisplayGroupEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ DisplayGroupEnum::Enum DisplayGroupEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = getDefaultValue(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const DisplayGroupEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type DisplayGroupEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString DisplayGroupEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const DisplayGroupEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ DisplayGroupEnum::Enum DisplayGroupEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = getDefaultValue(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const DisplayGroupEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type DisplayGroupEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t DisplayGroupEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const DisplayGroupEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ DisplayGroupEnum::Enum DisplayGroupEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = getDefaultValue(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const DisplayGroupEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type DisplayGroupEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void DisplayGroupEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void DisplayGroupEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(DisplayGroupEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void DisplayGroupEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(DisplayGroupEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } /** * @return The default value for a display group. */ DisplayGroupEnum::Enum DisplayGroupEnum::getDefaultValue() { return DISPLAY_GROUP_A; } workbench-1.1.1/src/FilesBase/DisplayGroupEnum.h000066400000000000000000000066441255417355300215640ustar00rootroot00000000000000#ifndef __DISPLAY_GROUP_ENUM__H_ #define __DISPLAY_GROUP_ENUM__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class DisplayGroupEnum { public: /** * Enumerated values. */ enum Enum { /** Unique to Tab */ DISPLAY_GROUP_TAB, /** Group A */ DISPLAY_GROUP_A, /** Group B */ DISPLAY_GROUP_B, /** Group C */ DISPLAY_GROUP_C, /** Group D */ DISPLAY_GROUP_D /* IF A NEW ENUM IS ADDED UPDATE NUMBER_OF_GROUPS ENUM BELOW */ }; enum Misc { /** Number of groups, update if new enums are added */ NUMBER_OF_GROUPS = 5 }; ~DisplayGroupEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); static DisplayGroupEnum::Enum getDefaultValue(); private: DisplayGroupEnum(const Enum enumValue, const AString& name, const AString& guiName); static const DisplayGroupEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; #ifdef __DISPLAY_GROUP_ENUM_DECLARE__ std::vector DisplayGroupEnum::enumData; bool DisplayGroupEnum::initializedFlag = false; int32_t DisplayGroupEnum::integerCodeCounter = 0; #endif // __DISPLAY_GROUP_ENUM_DECLARE__ } // namespace #endif //__DISPLAY_GROUP_ENUM__H_ workbench-1.1.1/src/FilesBase/GiftiException.cxx000066400000000000000000000041501255417355300215770ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "GiftiException.h" #include using namespace caret; /** * Constructor. * */ GiftiException::GiftiException() : CaretException() { this->initializeMembersGiftiException(); } /** * Constructor that uses stack trace from the exception * passed in as a parameter. * * @param e Any exception whose stack trace becomes * this exception's stack trace. * */ GiftiException::GiftiException( const CaretException& e) : CaretException(e) { this->initializeMembersGiftiException(); } /** * Constructor. * * @param s Description of the exception. * */ GiftiException::GiftiException( const AString& s) : CaretException(s) { this->initializeMembersGiftiException(); } /** * Copy Constructor. * @param e * Exception that is copied. */ GiftiException::GiftiException(const GiftiException& e) : CaretException(e) { } /** * Assignment operator. * @param e * Exception that is copied. * @return * Copy of the exception. */ GiftiException& GiftiException::operator=(const GiftiException& e) { if (this != &e) { CaretException::operator=(e); } return *this; } /** * Destructor */ GiftiException::~GiftiException() throw() { } void GiftiException::initializeMembersGiftiException() { } workbench-1.1.1/src/FilesBase/GiftiException.h000066400000000000000000000027111255417355300212250ustar00rootroot00000000000000#ifndef __GIFTIEXCEPTION_H__ #define __GIFTIEXCEPTION_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "CaretException.h" namespace caret { /** * An exception thrown during GIFTI file processing. */ class GiftiException : public CaretException { public: GiftiException(); GiftiException(const CaretException& e); GiftiException(const AString& s); GiftiException(const GiftiException& e); GiftiException& operator=(const GiftiException& e); virtual ~GiftiException() throw(); private: void initializeMembersGiftiException(); }; } // namespace #endif // __GIFTIEXCEPTION_H__ workbench-1.1.1/src/FilesBase/GiftiLabel.cxx000066400000000000000000000407341255417355300206700ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __GIFTI_LABEL_DECLARE__ #include "GiftiLabel.h" #undef __GIFTI_LABEL_DECLARE__ #include "CaretLogger.h" using namespace caret; /** * Constructor. * * @param key - key of the label. * @param name - name of label. * */ GiftiLabel::GiftiLabel( const int32_t key, const AString& name) : CaretObject() { this->initializeMembersGiftiLabel(); this->key = key; setNamePrivate(name); } /** * Constructor. * * @param key - Key of the label. * @param name - name of label. * @param red - red color component, zero to one. * @param green - green color component, zero to one. * @param blue - blue color component, zero to one. * @param alpha - alpha color component, zero to one. * */ GiftiLabel::GiftiLabel( const int32_t key, const AString& name, const float red, const float green, const float blue, const float alpha) : CaretObject() { this->initializeMembersGiftiLabel(); this->key = key; setNamePrivate(name); this->red = colorClamp(red); this->green = colorClamp(green); this->blue = colorClamp(blue); this->alpha = colorClamp(alpha); } /** * Constructor. * * @param key - Key of the label. * @param name - name of label. * @param red - red color component, zero to one. * @param green - green color component, zero to one. * @param blue - blue color component, zero to one. * @param alpha - alpha color component, zero to one. * */ GiftiLabel::GiftiLabel(const int32_t key, const AString& name, const float red, const float green, const float blue, const float alpha, const float x, const float y, const float z) : CaretObject() { this->initializeMembersGiftiLabel(); this->key = key; setNamePrivate(name); this->red = colorClamp(red); this->green = colorClamp(green); this->blue = colorClamp(blue); this->alpha = colorClamp(alpha); this->x = x; this->y = y; this->z = z; } /** * Constructor. * * @param key - Key of the label. * @param name - name of label. * @param red - red color component, zero to one. * @param green - green color component, zero to one. * @param blue - blue color component, zero to one. * @param alpha - alpha color component, zero to one. * */ GiftiLabel::GiftiLabel( const int32_t key, const AString& name, const double red, const double green, const double blue, const double alpha) : CaretObject() { this->initializeMembersGiftiLabel(); this->key = key; setNamePrivate(name); this->red = colorClamp(red); this->green = colorClamp(green); this->blue = colorClamp(blue); this->alpha = colorClamp(alpha); } /** * Constructor. * * @param key - Key of the label. * @param name - name of label. * @param rgba - red, green, blue, alpha color componenents, zero to one. * */ GiftiLabel::GiftiLabel( const int32_t key, const AString& name, const float rgba[]) : CaretObject() { this->initializeMembersGiftiLabel(); this->key = key; setNamePrivate(name); this->red = colorClamp(rgba[0]); this->green = colorClamp(rgba[1]); this->blue = colorClamp(rgba[2]); this->alpha = colorClamp(rgba[3]); } /** * Constructor. * * @param key - Key of the label. * @param name - name of label. * @param red - red color component, zero to two-fifty-five. * @param green - green color component, zero to two-fifty-five. * @param blue - blue color component, zero to two-fifty-five. * @param alpha - alpha color component, zero to two-fifty-five. * */ GiftiLabel::GiftiLabel( const int32_t key, const AString& name, const int32_t red, const int32_t green, const int32_t blue, const int32_t alpha) : CaretObject() { this->initializeMembersGiftiLabel(); this->key = key; setNamePrivate(name); this->red = colorClamp(red / 255.0); this->green = colorClamp(green / 255.0); this->blue = colorClamp(blue / 255.0); this->alpha = colorClamp(alpha / 255.0); } /** * Constructor. * * @param key - Key of the label. * @param name - name of label. * @param rgba - red, green, blue, alpha color componenents, zero to 255. * */ GiftiLabel::GiftiLabel( const int32_t key, const AString& name, const int32_t rgba[]) : CaretObject() { this->initializeMembersGiftiLabel(); this->key = key; setNamePrivate(name); this->red = colorClamp(rgba[0] / 255.0); this->green = colorClamp(rgba[1] / 255.0); this->blue = colorClamp(rgba[2] / 255.0); this->alpha = colorClamp(rgba[3] / 255.0); } /** * Constructor. * * @param key - Key of the label. * */ GiftiLabel::GiftiLabel( const int32_t key) : CaretObject() { this->initializeMembersGiftiLabel(); this->key = key; if (this->key == 0) { setNamePrivate("???"); } else { std::stringstream str; str << "???" << this->key; setNamePrivate(AString::fromStdString(str.str())); } } /** * Destructor */ GiftiLabel::~GiftiLabel() { } float GiftiLabel::colorClamp(const float& in) { if (in < 0.0f) return 0.0f; if (in > 1.0f) return 1.0f; if (in != in) { CaretLogWarning("GiftiLabel was given NaN as a color, changing to 1.0"); return 1.0f; } return in; } /** * Copy Constructor * @param Object that is copied. */ GiftiLabel::GiftiLabel(const GiftiLabel& o) : CaretObject(o), TracksModificationInterface() { this->initializeMembersGiftiLabel(); this->copyHelper(o); } /** * Assignment operator. */ GiftiLabel& GiftiLabel::operator=(const GiftiLabel& o) { if (this != &o) { CaretObject::operator=(o); this->copyHelper(o); }; return *this; } /** * Helps with copy constructor and assignment operator. */ void GiftiLabel::copyHelper(const GiftiLabel& gl) { this->initializeMembersGiftiLabel(); setNamePrivate(gl.name); this->key = gl.key; this->selected = gl.selected; this->red = gl.red; this->green = gl.green; this->blue = gl.blue; this->alpha = gl.alpha; this->x = gl.x; this->y = gl.y; this->z = gl.z; this->count = 0; m_groupNameSelectionItem = gl.m_groupNameSelectionItem; } /** * Initialize data members. */ void GiftiLabel::initializeMembersGiftiLabel() { this->modifiedFlag = false; this->medialWallNameFlag = false; this->name = ""; this->key = s_invalidLabelKey; this->selected = true; this->red = 1.0; this->green = 1.0; this->blue = 1.0; this->alpha = 1.0; this->x = 0.0; this->y = 0.0; this->z = 0.0; this->count = 0; m_groupNameSelectionItem = NULL; } /** * Determine if two objects are equal. Two GiftiLabels are equal if they * have the same "key". * @param obj Object for comparison. * @return true if equal, else false. * */ bool GiftiLabel::equals(const GiftiLabel& gl) { return (this->key == gl.key); } /** * Compare this label to another label using the indices of the labels. * @param gl - Compare to this GiftiLabel. * @return negative if "this" is less, positive if "this" is greater, * else zero. * */ int32_t GiftiLabel::operator<(const GiftiLabel& gl) { return (this->key < gl.key); } /** * Get the key of this label. * @return key of the label. * */ int32_t GiftiLabel::getKey() const { return this->key; } /** * Set the key of this label. DO NOT call this method on a label * retrieved from the label table. * * @param key - New key for this label. * */ void GiftiLabel::setKey(const int32_t key) { this->key = key; this->setModified(); } /** * Get the name. * @return Name of label. * */ AString GiftiLabel::getName() const { return this->name; } /** * Set the name. * @param name - new name for label. * */ void GiftiLabel::setName(const AString& name) { setNamePrivate(name); this->setModified(); } /** * (1) Sets the name of the label. * (2) Examines the name of the label to see if the label * is a "medial wall" name which is defined as a name that * contains substring "medial", followed by zero or more * characters, followed by the substring "wall". * (3) DOES NOT change the modfified * status for this label. * * @param name * New name for label. */ void GiftiLabel::setNamePrivate(const AString& name) { this->name = name; this->medialWallNameFlag = false; const int32_t medialIndex = name.indexOf("medial", 0, Qt::CaseInsensitive); if (medialIndex >= 0) { const int32_t wallIndex = name.indexOf("wall", 6, Qt::CaseInsensitive); if (wallIndex > medialIndex) { this->medialWallNameFlag = true; } } } /** * @return A string that contains both the key and name * for use in the label editor. */ AString GiftiLabel::getNameAndKeyForLabelEditor() const { const AString keyAndNameText(QString::number(this->key).rightJustified(4, ' ', false) + ": " + (this->name)); return keyAndNameText; } /** * Is this label selected (for display)? * * @return true if label selected for display, else false. * */ bool GiftiLabel::isSelected() const { return this->selected; } /** * Set the label selected (for display). * * @param selected - new selection status. * */ void GiftiLabel::setSelected(const bool selected) { this->selected = selected; } /** * Get the color components. * @param rgbaOut four dimensional array into which are loaded, * red, green, blue, and alpha components ranging 0.0. to 1.0. * */ void GiftiLabel::getColor(float rgbaOut[4]) const { rgbaOut[0] = this->red; rgbaOut[1] = this->green; rgbaOut[2] = this->blue; rgbaOut[3] = this->alpha; } /** * Set the color components. * * @param rgba - A four-dimensional array of floats containing the red, * green, blue, and alpha components with values ranging from 0.0 to 1.0. * */ void GiftiLabel::setColor(const float rgba[4]) { this->red = colorClamp(rgba[0]); this->green = colorClamp(rgba[1]); this->blue = colorClamp(rgba[2]); this->alpha = colorClamp(rgba[3]); this->setModified(); } /** * Get the default color. * * @param Output with a four-dimensional array of floats * containing the red, green, blue, and alpha components with values * ranging from 0.0 to 1.0. */ void GiftiLabel::getDefaultColor(float rgbaOut[4]) { rgbaOut[0] = 1.0; rgbaOut[1] = 1.0; rgbaOut[2] = 1.0; rgbaOut[3] = 1.0; } /** * Get the red color component for this label. * @return red color component. * */ float GiftiLabel::getRed() const { return this->red; } /** * Get the green color component for this label. * @return green color component. * */ float GiftiLabel::getGreen() const { return this->green; } /** * Get the blue color component for this label. * @return blue color component. * */ float GiftiLabel::getBlue() const { return this->blue; } /** * Get the alpha color component for this label. * @return alpha color component. * */ float GiftiLabel::getAlpha() const { return this->alpha; } /** * Get the X-Coordinate. * @return * The X-coordinate. */ float GiftiLabel::getX() const { return this->x; } /** * Get the Y-Coordinate. * @return * The Y-coordinate. */ float GiftiLabel::getY() const { return this->y; } /** * Get the Z-Coordinate. * @return * The Z-coordinate. */ float GiftiLabel::getZ() const { return this->z; } /** * Get the XYZ coordiantes. * @param xyz * Array into which coordinates are loaded. */ void GiftiLabel::getXYZ(float xyz[3]) const { xyz[0] = this->x; xyz[1] = this->y; xyz[2] = this->z; } /** * Set the X-coordinate. * @param x * New value for X-coordinate. */ void GiftiLabel::setX(const float x) { this->x = x; this->setModified(); } /** * Set the Y-coordinate. * @param y * New value for Y-coordinate. */ void GiftiLabel::setY(const float y) { this->y = y; this->setModified(); } /** * Set the Z-coordinate. * @param z * New value for Z-coordinate. */ void GiftiLabel::setZ(const float z) { this->z = z; this->setModified(); } /** * Set the XYZ coordinates. * @param xyz * Array containing XYZ coordiantes. */ void GiftiLabel::setXYZ(const float xyz[3]) { this->x = xyz[0]; this->y = xyz[1]; this->z = xyz[2]; this->setModified(); } /** * Set this object has been modified. * */ void GiftiLabel::setModified() { this->modifiedFlag = true; } /** * Set this object as not modified. Object should also * clear the modification status of its children. * */ void GiftiLabel::clearModified() { this->modifiedFlag = false; } /** * Get the modification status. Returns true if this object or * any of its children have been modified. * @return - The modification status. * */ bool GiftiLabel::isModified() const { return this->modifiedFlag; } /** * Get information about this label. * * @return Information about the label. * */ AString GiftiLabel::toString() const { AString s; s += "[GiftiLabel=(key=" + AString::number(this->getKey()) + "," + this->getName() + "," + AString::number(this->getRed()) + "," + AString::number(this->getGreen()) + "," + AString::number(this->getBlue()) + "," + AString::number(this->getAlpha()) + "," + AString::number(this->getX()) + "," + AString::number(this->getY()) + "," + AString::number(this->getZ()) + ") "; return s; } /** * Get the count. * @return Count value. * */ int32_t GiftiLabel::getCount() const { return this->count; } /** * Set the count. * @param count - new value for count. * */ void GiftiLabel::setCount(const int32_t count) { this->count = count; } /** * Increment the count. * */ void GiftiLabel::incrementCount() { this->count++; } bool GiftiLabel::matches(const GiftiLabel& rhs, const bool checkColor, const bool checkCoord) const { if (key != rhs.key) return false; if (name != rhs.name) return false; if (checkColor) { if (red != rhs.red) return false; if (green != rhs.green) return false; if (blue != rhs.blue) return false; if (alpha != rhs.alpha) return false; } if (checkCoord) { if (x != rhs.x) return false; if (y != rhs.y) return false; if (z != rhs.z) return false; } return true; } /** * Set the selection item for the group/name hierarchy. * * @param item * The selection item from the group/name hierarchy. */ void GiftiLabel::setGroupNameSelectionItem(GroupAndNameHierarchyItem* item) { m_groupNameSelectionItem = item; } /** * @return The selection item for the Group/Name selection hierarchy. * May be NULL in some circumstances. */ const GroupAndNameHierarchyItem* GiftiLabel::getGroupNameSelectionItem() const { return m_groupNameSelectionItem; } /** * @return The selection item for the Group/Name selection hierarchy. * May be NULL in some circumstances. */ GroupAndNameHierarchyItem* GiftiLabel::getGroupNameSelectionItem() { return m_groupNameSelectionItem; } workbench-1.1.1/src/FilesBase/GiftiLabel.h000066400000000000000000000154661255417355300203210ustar00rootroot00000000000000#ifndef __GIFTILABEL_H__ #define __GIFTILABEL_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "TracksModificationInterface.h" #include #include #include namespace caret { class GroupAndNameHierarchyItem; /** * Represents a GIFTI Label. */ class GiftiLabel : public CaretObject, TracksModificationInterface { public: GiftiLabel( const int32_t key, const AString& name); explicit GiftiLabel( const int32_t key, const AString& name, const float red, const float green, const float blue, const float alpha); explicit GiftiLabel( const int32_t key, const AString& name, const float red, const float green, const float blue, const float alpha, const float x, const float y, const float z); explicit GiftiLabel( const int32_t key, const AString& name, const double red, const double green, const double blue, const double alpha); GiftiLabel( const int32_t key, const AString& name, const float rgba[]); explicit GiftiLabel( const int32_t key, const AString& name, const int32_t red, const int32_t green, const int32_t blue, const int32_t alpha); GiftiLabel( const int32_t key, const AString& name, const int32_t rgba[]); GiftiLabel(const int32_t key); GiftiLabel(const GiftiLabel& gl); public: GiftiLabel& operator=(const GiftiLabel& gl); virtual ~GiftiLabel(); private: static float colorClamp(const float& in); void copyHelper(const GiftiLabel& o); void initializeMembersGiftiLabel(); public: int32_t hashCode(); bool equals(const GiftiLabel&); int32_t operator<(const GiftiLabel& gl); int32_t getKey() const; void setKey(const int32_t key); AString getName() const; /** * @return True if the name of the label is detected * to be a "medial wall" name. */ inline bool isMedialWallName() const { return this->medialWallNameFlag; } void setName(const AString& name); AString getNameAndKeyForLabelEditor() const; bool isSelected() const; void setSelected(const bool selected); void getColor(float rgbaOut[4]) const; void setColor(const float rgba[4]); static void getDefaultColor(float rgbaOut[4]); float getRed() const; float getGreen() const; float getBlue() const; float getAlpha() const; float getX() const; float getY() const; float getZ() const; void getXYZ(float xyz[3]) const; void setX(const float x); void setY(const float y); void setZ(const float z); void setXYZ(const float xyz[3]); void setModified(); void clearModified(); bool isModified() const; AString toString() const; int32_t getCount() const; void setCount(const int32_t count); void incrementCount(); bool matches(const GiftiLabel& rhs, const bool checkColor = false, const bool checkCoord = false) const; void setGroupNameSelectionItem(GroupAndNameHierarchyItem* item); const GroupAndNameHierarchyItem* getGroupNameSelectionItem() const; GroupAndNameHierarchyItem* getGroupNameSelectionItem(); /** * @return The invalid label key. */ static inline int32_t getInvalidLabelKey() { return s_invalidLabelKey; } private: void setNamePrivate(const AString& name); /**tracks modification status (DO NOT CLONE) */ bool modifiedFlag; /** * Name of label * DO NOT set directly with assignment operation. * Use setName() or setNamePrivate() so that the * medial wall name flag is updated. */ AString name; int32_t key; bool selected; bool medialWallNameFlag; float red; float green; float blue; float alpha; float x; float y; float z; /**Used to count nodes/voxel using label (not saved in file) */ int32_t count; /** Selection status of this label in the map/label hierarchy */ mutable GroupAndNameHierarchyItem* m_groupNameSelectionItem; /** The invalid label key */ const static int32_t s_invalidLabelKey; }; #ifdef __GIFTI_LABEL_DECLARE__ const int32_t GiftiLabel::s_invalidLabelKey = std::numeric_limits::min(); //const int32_t GiftiLabel::s_invalidLabelKey = -2147483648; #endif // __GIFTI_LABEL_DECLARE__ } // namespace #endif // __GIFTILABEL_H__ workbench-1.1.1/src/FilesBase/GiftiLabelTable.cxx000066400000000000000000001437401255417355300216410ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include "AStringNaturalComparison.h" #include "CaretAssert.h" #include "CaretLogger.h" #include "GiftiLabel.h" #include "GiftiLabelTable.h" #include "GiftiXmlElements.h" #include "StringTableModel.h" #include "XmlWriter.h" using namespace caret; /** * Constructor. * */ GiftiLabelTable::GiftiLabelTable() : CaretObject() { this->initializeMembersGiftiLabelTable(); clear();//actually adds the 0: ??? label } /** * Destructor */ GiftiLabelTable::~GiftiLabelTable() { for (LABELS_MAP_CONST_ITERATOR iter = this->labelsMap.begin(); iter != labelsMap.end(); iter++) { delete iter->second; } this->labelsMap.clear(); } /** * Copy Constructor * @param Object that is copied. */ GiftiLabelTable::GiftiLabelTable(const GiftiLabelTable& glt) : CaretObject(glt), TracksModificationInterface() { this->initializeMembersGiftiLabelTable(); this->copyHelper(glt); } /** * Assignment operator. */ GiftiLabelTable& GiftiLabelTable::operator=(const GiftiLabelTable& glt) { if (this != &glt) { CaretObject::operator=(glt); this->copyHelper(glt); }; return *this; } /** * Helps with copy constructor and assignment operator. */ void GiftiLabelTable::copyHelper(const GiftiLabelTable& glt) { this->clear(); for (LABELS_MAP_CONST_ITERATOR iter = glt.labelsMap.begin(); iter != glt.labelsMap.end(); iter++) { GiftiLabel* myLabel = this->getLabel(iter->second->getKey()); if (myLabel != NULL) { *myLabel = *(iter->second); } else { addLabel(iter->second); } } } void GiftiLabelTable::initializeMembersGiftiLabelTable() { this->modifiedFlag = false; m_tableModelColumnCount = 0; m_tableModelColumnIndexKey = m_tableModelColumnCount++; m_tableModelColumnIndexName = m_tableModelColumnCount++; m_tableModelColumnIndexColorSwatch = m_tableModelColumnCount++; m_tableModelColumnIndexRed = m_tableModelColumnCount++; m_tableModelColumnIndexGreen = m_tableModelColumnCount++; m_tableModelColumnIndexBlue = m_tableModelColumnCount++; } /** * Clear the labelTable. * */ void GiftiLabelTable::clear() { for (LABELS_MAP_CONST_ITERATOR iter = this->labelsMap.begin(); iter != labelsMap.end(); iter++) { delete iter->second; } this->labelsMap.clear(); GiftiLabel gl(0, "???", 1.0, 1.0, 1.0, 0.0); this->addLabel(&gl); this->modifiedFlag = false; } /** * Append a label table to this label table. Since labels may be * duplicated, the map returned that converts the keys of * the appended LabelTable to keys for "this" label table. * * @param lt Label table that is to be appended. * * @return A map where the keys are the keys in the label table * that is passed as a parameter and the values are the keys * into "this" label table. * */ std::map GiftiLabelTable::append(const GiftiLabelTable& glt) { std::map keyConverterMap; for (LABELS_MAP_CONST_ITERATOR iter = glt.labelsMap.begin(); iter != glt.labelsMap.end(); iter++) { int32_t key = iter->first; int32_t newKey = this->addLabel(iter->second); keyConverterMap.insert(std::make_pair(key, newKey)); } return keyConverterMap; } /** * Add a label. If a label with the name exists, its colors * are replaced with these color components. * @param labelName Name of label. * @param red Red color component ranging 0.0 to 1.0. * @param green Green color component ranging 0.0 to 1.0. * @param blue Blue color component ranging 0.0 to 1.0. * @param alpha Alpha color component ranging 0.0 to 1.0. * @return Index of the existing label, or, if no label * exists with name, index of new label. * */ int32_t GiftiLabelTable::addLabel( const AString& labelName, const float red, const float green, const float blue, const float alpha) { const GiftiLabel gl(GiftiLabel::getInvalidLabelKey(), labelName, red, green, blue, alpha); return this->addLabel(&gl); } /** * Add a label. If a label with the name exists, its colors * are replaced with these color components. * @param labelName Name of label. * @param red Red color component ranging 0.0 to 1.0. * @param green Green color component ranging 0.0 to 1.0. * @param blue Blue color component ranging 0.0 to 1.0. * @return Index of the existing label, or, if no label * exists with name, index of new label. * */ int32_t GiftiLabelTable::addLabel( const AString& labelName, const float red, const float green, const float blue) { return this->addLabel(labelName, red, green, blue, 1.0f); } /** * Add a label. If a label with the name exists, its colors * are replaced with these color components. * @param labelName Name of label. * @param red Red color component ranging 0 to 255. * @param green Green color component ranging 0 to 255. * @param blue Blue color component ranging 0 to 255. * @param alpha Alpha color component ranging 0 to 255. * @return Index of the existing label, or, if no label * exists with name, index of new label. * */ int32_t GiftiLabelTable::addLabel( const AString& labelName, const int32_t red, const int32_t green, const int32_t blue, const int32_t alpha) { const GiftiLabel gl(GiftiLabel::getInvalidLabelKey(), labelName, red, green, blue, alpha); return this->addLabel(&gl); } /** * Add a label. If a label with the name exists, its colors * are replaced with these color components. * @param labelName Name of label. * @param red Red color component ranging 0 to 255. * @param green Green color component ranging 0 to 255. * @param blue Blue color component ranging 0 to 255. * @return Index of the existing label, or, if no label * exists with name, index of new label. * */ int32_t GiftiLabelTable::addLabel( const AString& labelName, const int32_t red, const int32_t green, const int32_t blue) { return this->addLabel(labelName, red, green, blue, 255); } /** * Add a label to the label table. If the label's key is already in * the label table, a new key is created. If a label of the same * name already exists, the key of the existing label is returned * and its color is overridden. * @param glIn - Label to add. * @return Key of the label, possibly different than its original key. * */ int32_t GiftiLabelTable::addLabel(const GiftiLabel* glIn) { /* * First see if a label with the same name already exists */ int32_t key = this->getLabelKeyFromName(glIn->getName()); /* * If no label with the name exists, get the key * (which may be invalid) from the input label, * and check that nothing uses that key */ if (key == GiftiLabel::getInvalidLabelKey()) { int32_t tempkey = glIn->getKey(); LABELS_MAP_ITERATOR iter = this->labelsMap.find(tempkey); if (iter == labelsMap.end()) { key = tempkey; } } /* * Still need a key, find an unused key */ if (key == GiftiLabel::getInvalidLabelKey()) { key = this->generateUnusedKey(); GiftiLabel* gl = new GiftiLabel(*glIn); gl->setKey(key); this->labelsMap.insert(std::make_pair(key, gl)); return key; } if (key == 0) { issueLabelKeyZeroWarning(glIn->getName()); // if (glIn->getName() != "???") { // CaretLogWarning("Label 0 overridden!"); // } } LABELS_MAP_ITERATOR iter = this->labelsMap.find(key); if (iter != this->labelsMap.end()) { /* * Update existing label */ GiftiLabel* gl = iter->second; gl->setName(glIn->getName()); float rgba[4]; glIn->getColor(rgba); gl->setColor(rgba); key = iter->first; } else { /* * Insert a new label */ this->labelsMap.insert(std::make_pair(key, new GiftiLabel(*glIn))); } return key; } /** * Generate an unused key. * @return An unused key. */ int32_t GiftiLabelTable::generateUnusedKey() const { const int32_t numKeys = labelsMap.size(); LABELS_MAP::const_reverse_iterator rbegin = labelsMap.rbegin();//reverse begin is largest key if (numKeys > 0 && rbegin->first > 0)//there is at least one positive key { if (rbegin->first < numKeys) { CaretAssert(labelsMap.find(rbegin->first + 1) == labelsMap.end()); return rbegin->first + 1;//keys are compact unless negatives exist, in which case consider it "compact enough" if positive holes equal number of negative keys } else { LABELS_MAP::const_iterator begin = labelsMap.begin(); if (begin->first == 1 && rbegin->first == numKeys) { CaretAssert(labelsMap.find(rbegin->first + 1) == labelsMap.end()); return rbegin->first + 1;//keys are compact but missing 0, do not return 0, so return next } else {//there aren't enough negatives to make up for the missing, search for a hole in the positives LABELS_MAP::const_iterator iter = labelsMap.upper_bound(0);//start with first positive int32_t curVal = 0;//if it isn't one, we can stop early while (iter != labelsMap.end() && iter->first == curVal + 1)//it should NEVER hit end(), due to above checks, but if it did, it would return rbegin->first + 1 { curVal = iter->first; ++iter; } CaretAssert(labelsMap.find(curVal + 1) == labelsMap.end()); return curVal + 1; } } } else { CaretAssert(labelsMap.find(1) == labelsMap.end()); return 1;//otherwise, no keys exist or all keys are non-positive, return 1 } /*int32_t numKeys = labelsMap.size(); LABELS_MAP_CONST_ITERATOR myend = labelsMap.end(); if (labelsMap.upper_bound(numKeys - 1) == myend) {//returns a valid iterator only if there is no strictly greater key - zero key is assumed to exist, being the ??? special palette, no negatives exist return numKeys;//keys are therefore compact, return the next one } if (labelsMap.find(0) == myend && labelsMap.upper_bound(numKeys) == myend) {//similar check, but in case label 0 doesn't exist (but don't override it) return numKeys + 1; } std::vector scratch; scratch.resize(numKeys);//guaranteed to have at least one missing spot within this range other than zero, or one of above tests would have returned for (int32_t i = 0; i < numKeys; ++i) { scratch[i] = 0; } for (LABELS_MAP_CONST_ITERATOR iter = labelsMap.begin(); iter != myend; ++iter) { if (iter->first >= 0 && iter->first < numKeys) {//dont try to mark above the range scratch[iter->first] = 1; } } for (int32_t i = 1; i < numKeys; ++i) {//NOTE: start at 1! 0 is reserved (sort of) if (scratch[i] == 0) { return i; } } CaretAssertMessage(false, "generateUnusedKey() failed for unknown reasons"); return 0;//should never happen//*/ /*std::set keys = getKeys(); int32_t newKey = 1; bool found = false; while (found == false) { if (std::find(keys.begin(), keys.end(), newKey) == keys.end()) { found = true; } else { newKey++; } } return newKey;//*/ } /** * Remove the label with the specified key. * @param key - key of label. * */ void GiftiLabelTable::deleteLabel(const int32_t key) { if (key == 0) {//key 0 is reserved (sort of) CaretLogWarning("Label 0 DELETED!"); } LABELS_MAP_ITERATOR iter = this->labelsMap.find(key); if (iter != this->labelsMap.end()) { GiftiLabel* gl = iter->second; this->labelsMap.erase(iter); delete gl; setModified(); } } /** * Remove a label from the label table. * This method WILL DELETE the label passed * in so the caller should never use the parameter * passed after this call. * @param label - label to remove. * */ void GiftiLabelTable::deleteLabel(const GiftiLabel* label) { if (label->getKey() == 0) {//key 0 is reserved (sort of) CaretLogWarning("Label 0 DELETED!"); } for (LABELS_MAP_ITERATOR iter = this->labelsMap.begin(); iter != this->labelsMap.end(); iter++) { if (iter->second == label) { this->labelsMap.erase(iter); setModified(); break; } } delete label; } /** * Remove unused labels from the label table. Note that the unassigned * label is not removed, even if it is unused. * * @param usedLabelKeys - Color keys that are in use. * */ void GiftiLabelTable::deleteUnusedLabels(const std::set& usedLabelKeys) { LABELS_MAP newMap; int32_t unassignedKey = getUnassignedLabelKey(); for (LABELS_MAP_ITERATOR iter = this->labelsMap.begin(); iter != this->labelsMap.end(); iter++) { int32_t key = iter->first; GiftiLabel* gl = iter->second; if (key == unassignedKey || usedLabelKeys.find(key) != usedLabelKeys.end()) {//unassigned key gets a free pass newMap.insert(std::make_pair(key, gl)); } else { delete gl; } } this->labelsMap = newMap; this->setModified(); } /** * Insert the label using the labels key. * @param labelIn - Label to insert (replaces an existing label * with the same key). * */ void GiftiLabelTable::insertLabel(const GiftiLabel* labelIn) { GiftiLabel* label = new GiftiLabel(*labelIn); int32_t key = label->getKey(); if (key == GiftiLabel::getInvalidLabelKey()) { key = this->generateUnusedKey(); label->setKey(key); } if (key == 0) {//key 0 is reserved (sort of) issueLabelKeyZeroWarning(label->getName()); } /* * Note: A map DOES NOT replace an existing key, so it * must be deleted and then added. */ LABELS_MAP_ITERATOR keyPos = this->labelsMap.find(label->getKey()); if (keyPos != this->labelsMap.end()) { GiftiLabel* gl = keyPos->second; this->labelsMap.erase(keyPos); delete gl; } this->labelsMap.insert(std::make_pair(label->getKey(), label)); this->setModified(); } /** * Get the key of a lable from its name. * @param name Name to search for. * @return Key of Name or GiftiLabel::getInvalidLabelKey() if not found. * */ int32_t GiftiLabelTable::getLabelKeyFromName(const AString& name) const { LABELS_MAP newMap; for (LABELS_MAP_CONST_ITERATOR iter = this->labelsMap.begin(); iter != this->labelsMap.end(); iter++) { int32_t key = iter->first; GiftiLabel* gl = iter->second; if (gl->getName() == name) { return key; } } return GiftiLabel::getInvalidLabelKey(); } /** * Get a GIFTI Label from its name. * @param labelName - Name of label that is sought. * @return Reference to label with name or null if no matching label. * */ const GiftiLabel* GiftiLabelTable::getLabel(const AString& labelName) const { LABELS_MAP newMap; for (LABELS_MAP_CONST_ITERATOR iter = this->labelsMap.begin(); iter != this->labelsMap.end(); iter++) { GiftiLabel* gl = iter->second; if (gl->getName() == labelName) { return gl; } } return NULL; } /** * Get a GIFTI Label from its name. * @param labelName - Name of label that is sought. * @return Reference to label with name or null if no matching label. * */ GiftiLabel* GiftiLabelTable::getLabel(const AString& labelName) { LABELS_MAP newMap; for (LABELS_MAP_CONST_ITERATOR iter = this->labelsMap.begin(); iter != this->labelsMap.end(); iter++) { GiftiLabel* gl = iter->second; if (gl->getName() == labelName) { return gl; } } return NULL; } /** * Get the label whose name is the longest substring of "name" beginning * at the first character. * * @param name - name for which best matching label is sought. * @return Reference to best matching label or null if not found. * */ const GiftiLabel* GiftiLabelTable::getLabelBestMatching(const AString& name) const { GiftiLabel* bestMatchingLabel = NULL; int32_t bestMatchLength = -1; LABELS_MAP newMap; for (LABELS_MAP_CONST_ITERATOR iter = this->labelsMap.begin(); iter != this->labelsMap.end(); iter++) { GiftiLabel* gl = iter->second; AString labelName = gl->getName(); if (name.startsWith(labelName)) { const int32_t len = labelName.length(); if (len > bestMatchLength) { bestMatchLength = len; bestMatchingLabel = iter->second; } } } return bestMatchingLabel; } /** * Get the GiftiLabel at the specified key. * * @param key - Key of GiftiLabel entry. * @return The GiftiLabel at the specified key or null if the * there is not a label at the specified key. * */ const GiftiLabel* GiftiLabelTable::getLabel(const int32_t key) const { LABELS_MAP_CONST_ITERATOR iter = this->labelsMap.find(key); if (iter != this->labelsMap.end()) { return iter->second; } return NULL; } /** * Get the GiftiLabel at the specified key. * * @param key - Key of GiftiLabel entry. * @return The GiftiLabel at the specified key or null if the * there is not a label at the specified key. */ GiftiLabel* GiftiLabelTable::getLabel(const int32_t key) { LABELS_MAP_ITERATOR iter = this->labelsMap.find(key); if (iter != this->labelsMap.end()) { return iter->second; } return NULL; } /** * Get the key for the unassigned label. * @return Index of key for unassigned label. * A valid key will always be returned. * */ int32_t GiftiLabelTable::getUnassignedLabelKey() const { const GiftiLabel* gl = this->getLabel("???"); if (gl != NULL) { return gl->getKey(); } /* * Remove 'constness' from this object so that the * label can be added. */ GiftiLabelTable* glt = (GiftiLabelTable*)this; const int32_t key = glt->addLabel("???", 0.0f, 0.0f, 0.0f, 0.0f); return key; } /** * Get the number of labels. This value is one greater than the last * label key. Note that not every key may have a label. If there * are no labels this returns 0. * @return Number of labels. * */ int32_t GiftiLabelTable::getNumberOfLabels() const { return this->labelsMap.size(); } /** * Get the name of the label at the key. If there is no label at the * key an empty string is returned. * @param key - key of label. * @return Name of label at inkeydex. * */ AString GiftiLabelTable::getLabelName(const int32_t key) const { LABELS_MAP_CONST_ITERATOR iter = this->labelsMap.find(key); if (iter != this->labelsMap.end()) { const AString name = iter->second->getName(); return name; } return ""; } /** * Set the name of a label. * @param key - key of label. * @param name - new name of label. * */ void GiftiLabelTable::setLabelName( const int32_t key, const AString& name) { if (key == 0) { if (name != "???") { issueLabelKeyZeroWarning(name); // CaretLogWarning("Label 0 modified!"); } } LABELS_MAP_ITERATOR iter = this->labelsMap.find(key); if (iter != this->labelsMap.end()) { iter->second->setName(name); } } /** * Set a label. If a label with the specified key exists, * it is replaced. * * @param key Key for label. * @param name Name of label. * @param red Red color component. * @param green Green color component. * @param blue Blue color component. * @param alpha Alpha color component. * */ void GiftiLabelTable::setLabel( const int32_t key, const AString& name, const float red, const float green, const float blue, const float alpha) { if (key == 0) { if (name != "???") { issueLabelKeyZeroWarning(name); // CaretLogWarning("Label 0 modified!"); } } LABELS_MAP_ITERATOR iter = this->labelsMap.find(key); if (iter != this->labelsMap.end()) { GiftiLabel* gl = iter->second; gl->setName(name); float rgba[4] = { red, green, blue, alpha }; gl->setColor(rgba); } else { GiftiLabel gl(key, name, red, green, blue, alpha); this->addLabel(&gl); } } /** * Set a label. If a label with the specified key exists, * it is replaced. * * @param key Key for label. * @param name Name of label. * @param red Red color component. * @param green Green color component. * @param blue Blue color component. * @param alpha Alpha color component. * @param x The X-coordinate. * @param y The Y-coordinate. * @param z The Z-coordinate. * */ void GiftiLabelTable::setLabel(const int32_t key, const AString& name, const float red, const float green, const float blue, const float alpha, const float x, const float y, const float z) { if (key == 0) { if (name != "???") { issueLabelKeyZeroWarning(name); //CaretLogWarning("Label 0 modified!"); } } LABELS_MAP_ITERATOR iter = this->labelsMap.find(key); if (iter != this->labelsMap.end()) { GiftiLabel* gl = iter->second; gl->setName(name); float rgba[4] = { red, green, blue, alpha }; gl->setColor(rgba); gl->setX(x); gl->setY(y); gl->setZ(z); } else { GiftiLabel gl(key, name, red, green, blue, alpha, x, y, z); this->addLabel(&gl); } } /** * Get the selection status of the label at the specified key. If there * is no label at the key, false is returned. * @param key - key of label * @return selection status of label. * */ bool GiftiLabelTable::isLabelSelected(const int32_t key) const { LABELS_MAP_CONST_ITERATOR iter = this->labelsMap.find(key); if (iter != this->labelsMap.end()) { return iter->second->isSelected(); } return false; } /** * Set the selection status of a label. * @param key - key of label. * @param sel - new selection status. * */ void GiftiLabelTable::setLabelSelected( const int32_t key, const bool sel) { LABELS_MAP_ITERATOR iter = this->labelsMap.find(key); if (iter != this->labelsMap.end()) { iter->second->setSelected(sel); } } /** * Set the selection status for all labels. * @param newStatus New selection status. * */ void GiftiLabelTable::setSelectionStatusForAllLabels(const bool newStatus) { for (LABELS_MAP_ITERATOR iter = this->labelsMap.begin(); iter != this->labelsMap.end(); iter++) { GiftiLabel* gl = iter->second; gl->setSelected(newStatus); } } /** * Get the alpha color component for a label. If the key is not a * valid label, an alpha of zero is returned. * @param key - Key of label. * @return Alpha for label or zero if invalid key. * */ float GiftiLabelTable::getLabelAlpha(const int32_t key) const { const GiftiLabel* gl = this->getLabel(key); if (gl != NULL) { return gl->getAlpha(); } return 0.0; } /** * Get the color for a label. * @param key - key of label. * @return Its color components or null if it is an invalid key. * */ void GiftiLabelTable::getLabelColor(const int32_t key, float rgbaOut[4]) const { const GiftiLabel* gl = this->getLabel(key); if (gl != NULL) { gl->getColor(rgbaOut); } } /** * Set the color of a label. * @param key - key of label. * @param color - new color of label. * */ void GiftiLabelTable::setLabelColor( const int32_t key, const float color[]) { if (key == 0) { CaretLogFiner("Label 0 color changed"); } LABELS_MAP_ITERATOR iter = this->labelsMap.find(key); if (iter != this->labelsMap.end()) { GiftiLabel* gl = iter->second; gl->setColor(color); } } /** * Get the label keys sorted by label name. * * @return Vector containing label keys sorted by name. * */ std::vector GiftiLabelTable::getLabelKeysSortedByName() const { /* * Use map to sort by name * If AStringNaturalComparison crashes, temporarily remove * as the third template parameter. */ std::map nameToKeyMap; for (LABELS_MAP_CONST_ITERATOR iter = this->labelsMap.begin(); iter != this->labelsMap.end(); iter++) { nameToKeyMap.insert(std::make_pair(iter->second->getName(), iter->first)); } std::vector keysSortedByName; for (std::map::iterator iter = nameToKeyMap.begin(); iter != nameToKeyMap.end(); iter++) { keysSortedByName.push_back(iter->second); } return keysSortedByName; } /** * Reset the label counts to zero. * */ void GiftiLabelTable::resetLabelCounts() { for (LABELS_MAP_ITERATOR iter = this->labelsMap.begin(); iter != this->labelsMap.end(); iter++) { if (iter != this->labelsMap.end()) { GiftiLabel* gl = iter->second; gl->setCount(0); } } } ///** // * @return Are there any labels that have an invalid group/name // * hierarchy settings. This can be caused by changing the name // * of a label or its color. // */ //bool //GiftiLabelTable::hasLabelsWithInvalidGroupNameHierarchy() const //{ // for (LABELS_MAP_CONST_ITERATOR iter = this->labelsMap.begin(); // iter != this->labelsMap.end(); // iter++) { // if (iter != this->labelsMap.end()) { // GiftiLabel* gl = iter->second; // if (gl->getGroupNameSelectionItem() == NULL) { // return true; // break; // } // } // } // // return false; //} /** * Remove labels that have the 'count' attribute * set to zero. * Note the ??? label is not removed. */ void GiftiLabelTable::removeLabelsWithZeroCounts() { const int32_t unknownKey = getUnassignedLabelKey(); /** * First, iterate through the map to find * labels that have the 'count' attribute * set to zero. Delete the label and save * the key since one cannot erase the map * element without confusing the iterator * and causing a crash. */ std::vector unusedkeys; for (LABELS_MAP_ITERATOR iter = this->labelsMap.begin(); iter != this->labelsMap.end(); iter++) { const GiftiLabel* gl = iter->second; if (gl->getCount() <= 0) { /* * Get key and save it. */ const int32_t key = iter->first; if (key != unknownKey) { unusedkeys.push_back(key); /* * Delete the label. */ delete gl; iter->second = NULL; } } } /* * Now, remove all of the elements in the * map for the keys that were found in the * previous loop. */ bool isLabelRemoved = false; for (std::vector::iterator iter = unusedkeys.begin(); iter != unusedkeys.end(); iter++) { const int32_t key = *iter; this->labelsMap.erase(key); isLabelRemoved = true; } if (isLabelRemoved) { this->setModified(); } } /** * Create labels for the keys with generated names and colors. * @param newKeys - Keys that need labels. * */ void GiftiLabelTable::createLabelsForKeys(const std::set& newKeys) { AString namePrefix = "Name"; int32_t nameCount = 0; int32_t colorCounter = 0; for (std::set::iterator iter = newKeys.begin(); iter != newKeys.end(); iter++) { int32_t key = *iter; if (this->getLabel(key) == NULL) { bool found = false; AString name; while (! found) { std::stringstream str; str << namePrefix.toStdString() << "_" << nameCount; nameCount++; name = AString::fromStdString(str.str()); if (this->getLabel(name) == NULL) { found = true; } float red = 0.0f; float green = 0.0f; float blue = 0.0f; float alpha = 1.0f; switch (colorCounter) { case 0: red = 1.0f; break; case 1: red = 1.0f; blue = 0.5f; break; case 2: red = 1.0f; blue = 1.0f; break; case 3: red = 1.0f; green = 0.5f; break; case 4: red = 1.0f; green = 0.5f; blue = 0.5f; break; case 5: red = 1.0f; green = 0.5f; blue = 1.0f; break; case 6: blue = 0.5f; break; case 7: blue = 1.0f; break; case 8: red = 0.5f; break; case 9: red = 0.5f; blue = 0.5f; break; case 10: red = 0.5f; blue = 1.0f; break; case 11: red = 0.5f; green = 0.5f; break; case 12: red = 0.5f; green = 0.5f; blue = 0.5f; break; case 13: red = 0.5f; green = 0.5f; blue = 1.0f; break; case 14: red = 0.5f; green = 1.0f; break; case 15: red = 0.5f; green = 1.0f; blue = 0.5f; break; case 16: red = 0.5f; green = 1.0f; blue = 1.0f; colorCounter = 0; // start over break; } colorCounter++; GiftiLabel* gl = new GiftiLabel(key, name, red, green, blue, alpha); this->addLabel(gl); } } } } /** * Write the metadata in GIFTI XML format. * * @param xmlWriter - output stream * @throws GiftiException if an error occurs while writing. * */ void GiftiLabelTable::writeAsXML(XmlWriter& xmlWriter) { try { // // Write the label tag // xmlWriter.writeStartElement(GiftiXmlElements::TAG_LABEL_TABLE); // // Write the labels // std::set keys = this->getKeys(); for (std::set::const_iterator iter = keys.begin(); iter != keys.end(); iter++) { int key = *iter; const GiftiLabel* label = this->getLabel(key); if (label != NULL) { XmlAttributes attributes; attributes.addAttribute(GiftiXmlElements::ATTRIBUTE_LABEL_KEY, key); float rgba[4]; label->getColor(rgba); attributes.addAttribute(GiftiXmlElements::ATTRIBUTE_LABEL_RED, rgba[0]); attributes.addAttribute(GiftiXmlElements::ATTRIBUTE_LABEL_GREEN, rgba[1]); attributes.addAttribute(GiftiXmlElements::ATTRIBUTE_LABEL_BLUE, rgba[2]); attributes.addAttribute(GiftiXmlElements::ATTRIBUTE_LABEL_ALPHA, rgba[3]); xmlWriter.writeElementCData(GiftiXmlElements::TAG_LABEL, attributes, label->getName()); } } // // Write the closing label tag // xmlWriter.writeEndElement(); } catch (XmlException& e) { throw GiftiException(e); } } void GiftiLabelTable::writeAsXML(QXmlStreamWriter& xmlWriter) const { try { // // Write the label tag // xmlWriter.writeStartElement(GiftiXmlElements::TAG_LABEL_TABLE); // // Write the labels // std::set keys = this->getKeys(); for (std::set::const_iterator iter = keys.begin(); iter != keys.end(); iter++) { int key = *iter; const GiftiLabel* label = this->getLabel(key); if (label != NULL) { xmlWriter.writeStartElement(GiftiXmlElements::TAG_LABEL); xmlWriter.writeAttribute(GiftiXmlElements::ATTRIBUTE_LABEL_KEY, AString::number(key)); float rgba[4]; label->getColor(rgba); xmlWriter.writeAttribute(GiftiXmlElements::ATTRIBUTE_LABEL_RED, AString::number(rgba[0])); xmlWriter.writeAttribute(GiftiXmlElements::ATTRIBUTE_LABEL_GREEN, AString::number(rgba[1])); xmlWriter.writeAttribute(GiftiXmlElements::ATTRIBUTE_LABEL_BLUE, AString::number(rgba[2])); xmlWriter.writeAttribute(GiftiXmlElements::ATTRIBUTE_LABEL_ALPHA, AString::number(rgba[3])); xmlWriter.writeCharacters(label->getName()); xmlWriter.writeEndElement(); } } // // Write the closing label tag // xmlWriter.writeEndElement(); } catch (XmlException& e) { throw GiftiException(e); } } /** * Convert to a string. * * @return String representation of labelTable. * */ AString GiftiLabelTable::toString() const { AString s = "GiftiLabelTable=["; for (LABELS_MAP_CONST_ITERATOR iter = this->labelsMap.begin(); iter != this->labelsMap.end(); iter++) { s += "key="; s += AString::number(iter->first); s += iter->second->toString(); s += ","; } s += "]"; return s; } /** * Get a nicely formatted string for printing. * * @param indentation - use as indentation. * @return String containing label information. * */ AString GiftiLabelTable::toFormattedString(const AString& indentation) const { std::set allKeys = getKeys(); const int32_t numberOfKeys = allKeys.size(); if (numberOfKeys <= 0) { return "Empty"; } int32_t columnCounter = 0; const int32_t COL_INDENT = columnCounter++; const int32_t COL_KEY = columnCounter++; const int32_t COL_NAME = columnCounter++; const int32_t COL_RED = columnCounter++; const int32_t COL_GREEN = columnCounter++; const int32_t COL_BLUE = columnCounter++; const int32_t COL_ALPHA = columnCounter++; const int32_t numberOfTableRows = numberOfKeys + 1; StringTableModel table(numberOfTableRows, columnCounter); int32_t rowIndex = 0; table.setElement(rowIndex, COL_INDENT, // accomplishes indentation indentation); table.setElement(rowIndex, COL_KEY, "KEY"); table.setElement(rowIndex, COL_NAME, "NAME"); table.setElement(rowIndex, COL_RED, "RED"); table.setElement(rowIndex, COL_GREEN, "GREEN"); table.setElement(rowIndex, COL_BLUE, "BLUE"); table.setElement(rowIndex, COL_ALPHA, "ALPHA"); rowIndex++; table.setColumnAlignment(COL_KEY, StringTableModel::ALIGN_RIGHT); table.setColumnAlignment(COL_NAME, StringTableModel::ALIGN_LEFT); table.setColumnAlignment(COL_RED, StringTableModel::ALIGN_RIGHT); table.setColumnAlignment(COL_GREEN, StringTableModel::ALIGN_RIGHT); table.setColumnAlignment(COL_BLUE, StringTableModel::ALIGN_RIGHT); table.setColumnAlignment(COL_ALPHA, StringTableModel::ALIGN_RIGHT); for (std::set::iterator iter = allKeys.begin(); iter != allKeys.end(); iter++) { const int32_t key = *iter; const GiftiLabel* label = getLabel(key); CaretAssertArrayIndex("table", numberOfTableRows, rowIndex); table.setElement(rowIndex, COL_KEY, key); if (label != NULL) { table.setElement(rowIndex, COL_NAME, label->getName()); table.setElement(rowIndex, COL_RED, label->getRed()); table.setElement(rowIndex, COL_GREEN, label->getGreen()); table.setElement(rowIndex, COL_BLUE, label->getBlue()); table.setElement(rowIndex, COL_ALPHA, label->getAlpha()); } else { table.setElement(rowIndex, COL_NAME, "Label Missing"); } rowIndex++; } return table.getInString(); } /** * Read the label table from XML DOM structures. * @param rootNode - the LabelTable node. * @throws GiftiException if an error occurs. * * void GiftiLabelTable::readFromXMLDOM(const Node* rootNode) { } */ /** * Read a LabelTable from a String. The beginning and ending tags must * be the label table tag GiftiXmlElements.TAG_LABEL_TABLE. * @param s - string containing the label table. * @throws GiftiException If there is an error processing the table. * */ void GiftiLabelTable::readFromXmlString(const AString& /*s*/) { CaretAssertMessage(0, "Not implemented yet!"); } void GiftiLabelTable::readFromQXmlStreamReader(QXmlStreamReader& xml) { clear(); bool haveUnassigned = false;//because clear() creates the default "???" label if (!xml.isStartElement() || xml.name() != GiftiXmlElements::TAG_LABEL_TABLE) {//TODO: try to recover instead of erroring? xml.raiseError("tried to read GiftiLabelTable when current element is not " + GiftiXmlElements::TAG_LABEL_TABLE); return; } while (xml.readNextStartElement() && !xml.atEnd()) { if (xml.name() != GiftiXmlElements::TAG_LABEL) { xml.raiseError("unexpected element '" + xml.name().toString() + "' encountered in " + GiftiXmlElements::TAG_LABEL_TABLE); } int key; float rgba[4]; QXmlStreamAttributes myAttrs = xml.attributes(); bool ok = false; QString temp = myAttrs.value(GiftiXmlElements::ATTRIBUTE_LABEL_KEY).toString(); key = temp.toInt(&ok); if (!ok) xml.raiseError("Key attribute of Label missing or noninteger"); temp = myAttrs.value(GiftiXmlElements::ATTRIBUTE_LABEL_RED).toString(); rgba[0] = temp.toFloat(&ok); if (!ok) xml.raiseError("Red attribute of Label missing or not a number"); temp = myAttrs.value(GiftiXmlElements::ATTRIBUTE_LABEL_GREEN).toString(); rgba[1] = temp.toFloat(&ok); if (!ok) xml.raiseError("Green attribute of Label missing or not a number"); temp = myAttrs.value(GiftiXmlElements::ATTRIBUTE_LABEL_BLUE).toString(); rgba[2] = temp.toFloat(&ok); if (!ok) xml.raiseError("Blue attribute of Label missing or not a number"); temp = myAttrs.value(GiftiXmlElements::ATTRIBUTE_LABEL_ALPHA).toString(); if (temp == "") { rgba[3] = 1.0f; } else { rgba[3] = temp.toFloat(&ok); if (!ok) xml.raiseError("Alpha attribute of Label not a number"); } temp = xml.readElementText(); if (xml.hasError()) return; if ((temp == "unknown" || temp == "Unknown") && rgba[3] == 0.0f) { if (haveUnassigned) { CaretLogWarning("found multiple label elements that should be interpreted as unlabeled"); } else { CaretLogFiner("Using '" + temp + "' label as unlabeled key"); haveUnassigned = true; temp = "???";//pretend they are actually our internal unlabeled name } } else if (temp == "???") { if (haveUnassigned) { CaretLogWarning("found multiple label elements that should be interpreted as unlabeled"); } haveUnassigned = true; } setLabel(key, temp, rgba[0], rgba[1], rgba[2], rgba[3]); } } /** * Set this object has been modified. * */ void GiftiLabelTable::setModified() { this->modifiedFlag = true; } /** * Set this object as not modified. Object should also * clear the modification status of its children. * */ void GiftiLabelTable::clearModified() { this->modifiedFlag = false; for (LABELS_MAP_CONST_ITERATOR iter = this->labelsMap.begin(); iter != this->labelsMap.end(); iter++) { iter->second->clearModified(); } } /** * Get the modification status. Returns true if this object or * any of its children have been modified. * @return - The modification status. * */ bool GiftiLabelTable::isModified() const { if (this->modifiedFlag) return true; for (LABELS_MAP_CONST_ITERATOR iter = this->labelsMap.begin(); iter != this->labelsMap.end(); iter++) { if (iter->second->isModified()) return true; } return false; } /** * Get an iterator that steps thought the label keys in * ascending order. * @return An iterator for stepping through the keys in * ascending order. * * Iterator GiftiLabelTable::getKeysIterator() const { return this-> } */ /** * Get the valid keys of the labels in ascending order. * @return A Set containing the valid keys of the label in * ascending order. * */ std::set GiftiLabelTable::getKeys() const { std::set keys; for (std::map::const_iterator iter = this->labelsMap.begin(); iter != this->labelsMap.end(); iter++) { keys.insert(iter->first); } return keys; } void GiftiLabelTable::getKeys(std::vector& keysOut) const { keysOut.reserve(labelsMap.size()); for (std::map::const_iterator iter = this->labelsMap.begin(); iter != this->labelsMap.end(); iter++) { keysOut.push_back(iter->first); } } /** * Get all keys and names. * * @parm keysAndNamesOut * Map containing the pairs of corresponding keys and names. */ void GiftiLabelTable::getKeysAndNames(std::map& keysAndNamesOut) const { keysAndNamesOut.clear(); for (std::map::const_iterator iter = this->labelsMap.begin(); iter != this->labelsMap.end(); iter++) { const GiftiLabel* gl = iter->second; keysAndNamesOut.insert(std::make_pair(iter->first, gl->getName())); } } /** * Change the key of a label from 'currentKey' to 'newKey'. * If a label exists with 'newKey', the label with 'newKey' is removed. * * @param currentKey * Key currently used by the label. * @param newKey * New key for the label. */ void GiftiLabelTable::changeLabelKey(const int32_t currentKey, const int32_t newKey) { /* * Remove a label that uses 'newKey'. */ if (this->labelsMap.find(newKey) != this->labelsMap.end()) { deleteLabel(newKey); } /* * Get the label with 'currentKey' and remove it from the map. */ LABELS_MAP_ITERATOR currentLabelIter = this->labelsMap.find(currentKey); if (currentLabelIter == this->labelsMap.end()) { CaretLogSevere("Attempting to change label key for non-existent label with key=" + AString::number(currentKey)); return; } GiftiLabel* label = currentLabelIter->second; this->labelsMap.erase(currentKey); /* * Change the lable's key from 'currentKey' to 'newKey' * and add the label into the label's map. */ label->setKey(newKey); this->labelsMap.insert(std::make_pair(newKey, label)); } bool GiftiLabelTable::matches(const GiftiLabelTable& rhs, const bool checkColors, const bool checkCoords) const { if (labelsMap.size() != rhs.labelsMap.size()) return false; for (LABELS_MAP::const_iterator iter = labelsMap.begin(); iter != labelsMap.end(); ++iter) { LABELS_MAP::const_iterator riter = rhs.labelsMap.find(iter->first); if (riter == rhs.labelsMap.end()) return false; if (!iter->second->matches(*(riter->second), checkColors, checkCoords)) return false; } return true; } /** * Called when label key zero's name is changed. * May result in a logger message is name is not a preferred name * for the label with key zero. * * @param name * New name for label with key zero. */ void GiftiLabelTable::issueLabelKeyZeroWarning(const AString& name) const { if ((name != "???") && (name.toLower() != "unknown")) { CaretLogFine("Label with key=0 overridden with name \"" + name + "\". This label is typically \"???\" or \"unknown\"."); } } /** * Export the content of the GIFTI Label Table to a Caret5 Color File. */ void GiftiLabelTable::exportToCaret5ColorFile(const AString& filename) const { if (filename.isEmpty()) { throw GiftiException("Missing filename for export of label table to caret5 color file format."); } QFile file(filename); if ( ! file.open(QFile::WriteOnly)) { const AString msg = ("Unable to open " + filename + " for export of label table to caret5 color file format.\n" + file.errorString()); throw GiftiException(msg); } QXmlStreamWriter xmlWriter(&file); xmlWriter.setAutoFormatting(true); xmlWriter.writeStartDocument("1.0"); xmlWriter.writeStartElement("Border_Color_File"); xmlWriter.writeStartElement("FileHeader"); xmlWriter.writeStartElement("Element"); xmlWriter.writeTextElement("comment", "Exported from Caret7/Workbench"); xmlWriter.writeEndElement(); xmlWriter.writeEndElement(); std::set keys = this->getKeys(); for (std::set::const_iterator iter = keys.begin(); iter != keys.end(); iter++) { int key = *iter; const GiftiLabel* label = this->getLabel(key); if (label != NULL) { xmlWriter.writeStartElement("Color"); xmlWriter.writeTextElement("name", label->getName()); const int32_t red = static_cast(label->getRed() * 255.0); const int32_t green = static_cast(label->getGreen() * 255.0); const int32_t blue = static_cast(label->getBlue() * 255.0); const int32_t alpha = static_cast(label->getAlpha() * 255.0); xmlWriter.writeTextElement("red", AString::number(red)); xmlWriter.writeTextElement("green", AString::number(green)); xmlWriter.writeTextElement("blue", AString::number(blue)); xmlWriter.writeTextElement("alpha", AString::number(alpha)); xmlWriter.writeTextElement("pointSize", "1.5"); xmlWriter.writeTextElement("lineSize", "1.0"); xmlWriter.writeTextElement("symbol", "POINT"); xmlWriter.writeTextElement("sumscolorid", ""); xmlWriter.writeEndElement(); } } xmlWriter.writeEndElement(); xmlWriter.writeEndDocument(); file.close(); } workbench-1.1.1/src/FilesBase/GiftiLabelTable.h000066400000000000000000000150571255417355300212650ustar00rootroot00000000000000#ifndef __GIFTILABELTABLE_H__ #define __GIFTILABELTABLE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AString.h" #include "CaretObject.h" #include "TracksModificationInterface.h" #include "GiftiException.h" #include #include #include #include #include #include namespace caret { class GiftiLabel; class XmlWriter; class XmlException; /** * Maintains a GIFTI Label Table using key/value pairs. */ class GiftiLabelTable : public CaretObject, TracksModificationInterface { public: GiftiLabelTable(); GiftiLabelTable(const GiftiLabelTable& glt); GiftiLabelTable& operator=(const GiftiLabelTable& glt); bool matches(const GiftiLabelTable& rhs, const bool checkColors = false, const bool checkCoords = false) const; bool operator==(const GiftiLabelTable& rhs) const { return matches(rhs, true); } bool operator!=(const GiftiLabelTable& rhs) const { return !((*this) == rhs); } virtual ~GiftiLabelTable(); private: void copyHelper(const GiftiLabelTable& glt); void initializeMembersGiftiLabelTable(); public: void clear(); std::map append(const GiftiLabelTable& glt); int32_t addLabel( const AString& labelName, const float red, const float green, const float blue, const float alpha); int32_t addLabel( const AString& labelName, const float red, const float green, const float blue); int32_t addLabel( const AString& labelName, const int32_t red, const int32_t green, const int32_t blue, const int32_t alpha); int32_t addLabel( const AString& labelName, const int32_t red, const int32_t green, const int32_t blue); int32_t addLabel(const GiftiLabel* glt); void deleteLabel(const int32_t key); void deleteLabel(const GiftiLabel* label); void deleteUnusedLabels(const std::set& usedLabelKeys); void insertLabel(const GiftiLabel* label); int32_t getLabelKeyFromName(const AString& name) const; const GiftiLabel* getLabel(const AString& labelName) const; GiftiLabel* getLabel(const AString& labelName); const GiftiLabel* getLabelBestMatching(const AString& name) const; const GiftiLabel* getLabel(const int32_t key) const; GiftiLabel* getLabel(const int32_t key); int32_t getUnassignedLabelKey() const; int32_t getNumberOfLabels() const; AString getLabelName(const int32_t key) const; void setLabelName( const int32_t key, const AString& name); void setLabel(const int32_t key, const AString& name, const float red, const float green, const float blue, const float alpha); void setLabel(const int32_t key, const AString& name, const float red, const float green, const float blue, const float alpha, const float x, const float y, const float z); bool isLabelSelected(const int32_t key) const; void setLabelSelected( const int32_t key, const bool sel); void setSelectionStatusForAllLabels(const bool newStatus); float getLabelAlpha(const int32_t key) const; void getLabelColor(const int32_t key, float rgbaOut[4]) const; void setLabelColor( const int32_t key, const float color[4]); std::vector getLabelKeysSortedByName() const; void resetLabelCounts(); void removeLabelsWithZeroCounts(); void createLabelsForKeys(const std::set& newKeys); void writeAsXML(XmlWriter& xmlWriter); void writeAsXML(QXmlStreamWriter& xmlWriter) const; AString toString() const; AString toFormattedString(const AString& indentation) const; //void readFromXMLDOM(const Node* rootNode) // ; void readFromXmlString(const AString& s); void readFromQXmlStreamReader(QXmlStreamReader& xml); void setModified(); void clearModified(); bool isModified() const; //Iterator getKeysIterator() const; std::set getKeys() const; void getKeys(std::vector& keysOut) const; void getKeysAndNames(std::map& keysAndNamesOut) const; // bool hasLabelsWithInvalidGroupNameHierarchy() const; int32_t generateUnusedKey() const; void changeLabelKey(const int32_t currentKey, const int32_t newKey); void exportToCaret5ColorFile(const AString& filename) const; private: void issueLabelKeyZeroWarning(const AString& name) const; /** The label table storage. Use a TreeMap since label keys may be sparse. */ typedef std::map LABELS_MAP; typedef std::map::iterator LABELS_MAP_ITERATOR; typedef std::map::const_iterator LABELS_MAP_CONST_ITERATOR; LABELS_MAP labelsMap; /**tracks modification status */ bool modifiedFlag; int32_t m_tableModelColumnIndexKey; int32_t m_tableModelColumnIndexName; int32_t m_tableModelColumnIndexColorSwatch; int32_t m_tableModelColumnIndexRed; int32_t m_tableModelColumnIndexGreen; int32_t m_tableModelColumnIndexBlue; int32_t m_tableModelColumnCount; }; } // namespace #endif // __GIFTILABELTABLE_H__ workbench-1.1.1/src/FilesBase/GiftiMetaData.cxx000066400000000000000000000404461255417355300213310ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include "CaretAssert.h" #include "GiftiMetaData.h" #include "GiftiMetaDataXmlElements.h" //#include "NiftiUtilities.h" #include "GiftiXmlElements.h" #include "XmlWriter.h" using namespace caret; /** * Constructor. * */ GiftiMetaData::GiftiMetaData() : CaretObject(), TracksModificationInterface() { this->initializeMembersGiftiMetaData(); } /** * Destructor */ GiftiMetaData::~GiftiMetaData() { } /** * Copy Constructor * @param Object that is copied. */ GiftiMetaData::GiftiMetaData(const GiftiMetaData& o) : CaretObject(o), TracksModificationInterface() { this->initializeMembersGiftiMetaData(); this->copyHelper(o); } /** * Assignment operator. */ GiftiMetaData& GiftiMetaData::operator=(const GiftiMetaData& o) { if (this != &o) { CaretObject::operator=(o); this->copyHelper(o); }; return *this; } bool GiftiMetaData::operator==(const GiftiMetaData& rhs) const { return (metadata == rhs.metadata); } /** * Helps with copy constructor and assignment operator. */ void GiftiMetaData::copyHelper(const GiftiMetaData& o) { /* * Preserve this instance's Unique ID, but only if it already has one. */ if (exists(GiftiMetaDataXmlElements::METADATA_NAME_UNIQUE_ID)) { const AString uid = this->getUniqueID(); this->metadata = o.metadata; this->set(GiftiMetaDataXmlElements::METADATA_NAME_UNIQUE_ID, uid); this->clearModified(); } else { this->metadata = o.metadata; } } void GiftiMetaData::initializeMembersGiftiMetaData() { this->modifiedFlag = false; } /** * Get the Unique ID. If there is not a unique ID, one is created. * @return String containing unique ID. * */ AString GiftiMetaData::getUniqueID() const { AString uid; MetaDataConstIterator iter = this->metadata.find(GiftiMetaDataXmlElements::METADATA_NAME_UNIQUE_ID); if (iter != this->metadata.end()) { uid = iter->second; } else { uid = SystemUtilities::createUniqueID(); this->metadata.insert(std::make_pair(GiftiMetaDataXmlElements::METADATA_NAME_UNIQUE_ID, uid)); } return uid; } /** * Reset the unique identifier. * Do not call this method unless you really need to such * as in the caret when multiple data arrays have the * same unique identifier. */ void GiftiMetaData::resetUniqueIdentifier() { this->set(GiftiMetaDataXmlElements::METADATA_NAME_UNIQUE_ID, SystemUtilities::createUniqueID()); } /** * Remove the UniqueID from this metadata. * * void GiftiMetaData::removeUniqueID() { this->remove(GiftiMetaDataXmlElements::METADATA_NAME_UNIQUE_ID); } */ /** * Clear the metadata. * */ void GiftiMetaData::clear(bool keepUUID) { if (keepUUID && exists(GiftiMetaDataXmlElements::METADATA_NAME_UNIQUE_ID)) { /* * Preserve this instance's Unique ID. */ const AString uid = this->getUniqueID(); this->metadata.clear(); this->set(GiftiMetaDataXmlElements::METADATA_NAME_UNIQUE_ID, uid); this->clearModified(); } else { metadata.clear(); } } /** * Append the metadata to this metadata. A comment is always appended. * Other metadata are added only if the name is not in "this" metadata. * * @param smd Metadata that is to be appended to "this". * */ void GiftiMetaData::append(const GiftiMetaData& smd) { for (MetaDataConstIterator iter = smd.metadata.begin(); iter != smd.metadata.end(); iter++) { if (iter->first != GiftiMetaDataXmlElements::METADATA_NAME_UNIQUE_ID) { this->set(iter->first, iter->second); } } this->setModified(); } /** * Clears this metadata and then copies all metadata from "smd" * with the exception of the Unique ID. * * @param smd Metadata that is to be copied to "this". * */ void GiftiMetaData::replace(const GiftiMetaData& smd) { /* * Preserve UniqueID. */ const AString uid = this->getUniqueID(); this->metadata = smd.metadata; this->set(GiftiMetaDataXmlElements::METADATA_NAME_UNIQUE_ID, uid); this->setModified(); } /** * Sets metadata. If a metadata entry named "name" already * exists, it is replaced. * * @param name Name of metadata entry. * @param value Value for metadata entry. * */ void GiftiMetaData::set(const AString& name, const AString& value) { MetaDataIterator namePos = this->metadata.find(name); if (namePos != this->metadata.end()) { if (namePos->second != value) { namePos->second = value; this->setModified(); } } else { this->metadata.insert(std::make_pair(name, value)); this->setModified(); } } /** * Set metadata with an integer value. * @param name - name of metadata. * @param value - value of metadata. * */ void GiftiMetaData::setInt( const AString& name, const int32_t value) { AString s = AString::number(value); this->set(name, s); } /** * Set metadata with an float value. * @param name - name of metadata. * @param value - value of metadata. * */ void GiftiMetaData::setFloat( const AString& name, const float value) { AString s = AString::number(value); this->set(name, s); } /** * Replace ALL of the metadata with the data in the given map. * * @param map * New metadata that replaces all existing metadata. */ void GiftiMetaData::replaceWithMap(const std::map& map) { /* * Save UniqueID if it has one and use it if no unique ID in given map. */ AString uid; if (exists(GiftiMetaDataXmlElements::METADATA_NAME_UNIQUE_ID)) { uid = this->getUniqueID(); } this->metadata = map; /* * If metadata was not in given map, restore the Unique ID if we had one. */ if (this->metadata.find(GiftiMetaDataXmlElements::METADATA_NAME_UNIQUE_ID) == this->metadata.end() && uid != "") { this->set(GiftiMetaDataXmlElements::METADATA_NAME_UNIQUE_ID, uid); } this->setModified(); } /** * @return ALL of the metadata in map. */ std::map GiftiMetaData::getAsMap() const { return this->metadata; } /** * Remove a metadata entry. * * @param name Name of metadata entry that is to be removed. * */ void GiftiMetaData::remove(const AString& name) { this->metadata.erase(name); } /** * See if a metadata entry "name" exists. * * @param name Name of metadata entry. * @return Returns true if the metadata entry "name" exists, else false. * */ bool GiftiMetaData::exists(const AString& name) const { if (this->metadata.find(name) != this->metadata.end()) { return true; } return false; } /** * Get a value for metadata entry. * * @param name Name of metadata entry. * @return The value of the metadata entry "name". If the * metadata entry "name" does not exist an empty * string is returned. * */ AString GiftiMetaData::get(const AString& name) const { if (this->metadata.empty()) { return ""; } MetaDataConstIterator iter = this->metadata.find(name); if (iter != this->metadata.end()) { return iter->second; } return ""; } /** * Get the metadata as an integer value. If the metadata does not exist * of its string representation is not a number, zero is returned. * @param name - name of metadata. * @return Integer value associated with the metadata. * */ int32_t GiftiMetaData::getInt(const AString& name) const { AString s = this->get(name); if (s.length() > 0) { int32_t i = s.toInt(); return i; } return 0; } /** * Get the metadata as an float value. If the metadata does not exist * of its string representation is not a number, zero is returned. * @param name - name of metadata. * @return Float value associated with the metadata. * */ float GiftiMetaData::getFloat(const AString& name) const { AString s = this->get(name); if (s.length() > 0) { float f = s.toFloat(); return f; } return 0.0f; } /** * Get names of all metadata. * * @return List of all metadata names. * */ std::vector GiftiMetaData::getAllMetaDataNames() const { std::vector names; for (MetaDataConstIterator iter = this->metadata.begin(); iter != this->metadata.end(); iter++) { names.push_back(iter->first); } return names; } /** * Update metanames from caret5. * */ void GiftiMetaData::updateFromCaret5Names() { } /** * Replace a metadata name. * @param oldName - old name of metadata. * @param newName - new name of metadata. * */ void GiftiMetaData::replaceName( const AString& oldName, const AString& newName) { MetaDataIterator iter = this->metadata.find(oldName); if (iter != this->metadata.end()) { AString value = iter->second; this->remove(oldName); this->set(newName, value); this->setModified(); } } /** * Convert to a string. * * @return String representation of metadata. * */ AString GiftiMetaData::toString() const { AString s = "GiftiMetaData=["; for (MetaDataConstIterator iter = this->metadata.begin(); iter != this->metadata.end(); iter++) { const AString& name = iter->first; const AString& value = iter->second; s += ("(" + name + "," + value + ")"); } s += "]"; return s; } /** * Get a nicely formatted string for printing. * * @param indentation - use as indentation. * @return String containing label information. * */ AString GiftiMetaData::toFormattedString(const AString& indentation) { return (indentation + this->toString()); } /** * Write the metadata in GIFTI XML format. * * @param xmlWriter - output stream * @throws GiftiException if an error occurs while writing. */ void GiftiMetaData::writeAsXML(XmlWriter& xmlWriter) { try { // // Write the metadata tag // xmlWriter.writeStartElement(GiftiXmlElements::TAG_METADATA); // // Write the metadata // for (MetaDataConstIterator iter = this->metadata.begin(); iter != this->metadata.end(); iter++) { const AString& key = iter->first; const AString& value = iter->second; // // MD Tag // xmlWriter.writeStartElement(GiftiXmlElements::TAG_METADATA_ENTRY); // // Name and value // xmlWriter.writeElementCData(GiftiXmlElements::TAG_METADATA_NAME, key); xmlWriter.writeElementCData(GiftiXmlElements::TAG_METADATA_VALUE, value); // // Closing tag // xmlWriter.writeEndElement(); } // // Write the closing metadata tag // xmlWriter.writeEndElement(); } catch (XmlException& e) { throw GiftiException(e); } } void GiftiMetaData::writeCiftiXML1(QXmlStreamWriter& xmlWriter) const { if (metadata.empty()) return;//don't write an empty tag if we have no metadata xmlWriter.writeStartElement(GiftiXmlElements::TAG_METADATA); for (MetaDataConstIterator iter = metadata.begin(); iter != metadata.end(); ++iter) { xmlWriter.writeStartElement(GiftiXmlElements::TAG_METADATA_ENTRY); xmlWriter.writeTextElement(GiftiXmlElements::TAG_METADATA_NAME, iter->first); xmlWriter.writeTextElement(GiftiXmlElements::TAG_METADATA_VALUE, iter->second); xmlWriter.writeEndElement(); } xmlWriter.writeEndElement(); } void GiftiMetaData::writeCiftiXML2(QXmlStreamWriter& xmlWriter) const { writeCiftiXML1(xmlWriter); } void GiftiMetaData::writeBorderFileXML3(QXmlStreamWriter& xmlWriter) const { writeCiftiXML1(xmlWriter); } void GiftiMetaData::readCiftiXML1(QXmlStreamReader& xml) { clear(false); while (!xml.atEnd())//don't check the current element's name { xml.readNext(); if (xml.isStartElement()) { QStringRef name = xml.name(); if (name == GiftiXmlElements::TAG_METADATA_ENTRY) { readEntry(xml); } else { xml.raiseError("unexpected tag name in " + GiftiXmlElements::TAG_METADATA + ": " + name.toString()); } } else if (xml.isEndElement()) { break; } } } void GiftiMetaData::readCiftiXML2(QXmlStreamReader& xml) { readCiftiXML1(xml); } void GiftiMetaData::readBorderFileXML1(QXmlStreamReader& xml) { readCiftiXML1(xml); } void GiftiMetaData::readBorderFileXML3(QXmlStreamReader& xml) { readCiftiXML1(xml); } void GiftiMetaData::readEntry(QXmlStreamReader& xml) { AString key, value; bool haveKey = false, haveValue = false; while (!xml.atEnd())//don't check the current element's name { xml.readNext(); if (xml.isStartElement()) { QStringRef name = xml.name(); if (name == GiftiXmlElements::TAG_METADATA_NAME) { if (haveKey) throw GiftiException("MD element has multiple Name elements"); key = xml.readElementText(); haveKey = true; } else if (name == GiftiXmlElements::TAG_METADATA_VALUE) { if (haveValue) throw GiftiException("MD element has multiple Value elements"); value = xml.readElementText(); haveValue = true; } else { xml.raiseError("unexpected tag name in " + GiftiXmlElements::TAG_METADATA_ENTRY + ": " + name.toString()); } } else if (xml.isEndElement()) { if (haveKey && haveValue) { if (exists(key)) { xml.raiseError("key '" + key + "' used more than once in " + GiftiXmlElements::TAG_METADATA); } else { set(key, value); } } else { if (haveKey) { xml.raiseError(GiftiXmlElements::TAG_METADATA_ENTRY + " element has no " + GiftiXmlElements::TAG_METADATA_VALUE + " element"); } else { if (haveValue) { xml.raiseError(GiftiXmlElements::TAG_METADATA_ENTRY + " element has no " + GiftiXmlElements::TAG_METADATA_NAME + " element"); } else { xml.raiseError(GiftiXmlElements::TAG_METADATA_ENTRY + " element has no " + GiftiXmlElements::TAG_METADATA_NAME + " or " + GiftiXmlElements::TAG_METADATA_VALUE + " element"); } } } break; } } CaretAssert(xml.hasError() || (xml.isEndElement() && xml.name() == "MD")); } /** * Set this object has been modified. * */ void GiftiMetaData::setModified() { this->modifiedFlag = true; } /** * Set this object as not modified. Object should also * clear the modification status of its children. * */ void GiftiMetaData::clearModified() { this->modifiedFlag = false; } /** * Get the modification status. Returns true if this object or * any of its children have been modified. * @return - The modification status. * */ bool GiftiMetaData::isModified() const { return this->modifiedFlag; } workbench-1.1.1/src/FilesBase/GiftiMetaData.h000066400000000000000000000077221255417355300207560ustar00rootroot00000000000000#ifndef __GIFTIMETADATA_H__ #define __GIFTIMETADATA_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "GiftiException.h" #include "TracksModificationInterface.h" #include #include #include #include class QXmlStreamReader; class QXmlStreamWriter; namespace caret { class XmlWriter; /** * Maintains GIFTI metadata using name/value pairs. A * metadata object may be associated with the GIFTI * data file and with each GIFTI data array. */ class GiftiMetaData : public CaretObject, TracksModificationInterface { public: GiftiMetaData(); public: GiftiMetaData(const GiftiMetaData& o); GiftiMetaData& operator=(const GiftiMetaData& o); bool operator==(const GiftiMetaData& rhs) const; bool operator!=(const GiftiMetaData& rhs) const { return !((*this) == rhs); } virtual ~GiftiMetaData(); private: void copyHelper(const GiftiMetaData& o); void initializeMembersGiftiMetaData(); public: AString getUniqueID() const; //void removeUniqueID(); void clear(bool keepUUID = true); void append(const GiftiMetaData& smd); void replace(const GiftiMetaData& smd); void set( const AString& name, const AString& value); void setInt( const AString& name, const int32_t value); void setFloat( const AString& name, const float value); void replaceWithMap(const std::map& map); std::map getAsMap() const; void remove(const AString& name); bool exists(const AString& name) const; AString get(const AString& name) const; int32_t getInt(const AString& name) const; float getFloat(const AString& name) const; std::vector getAllMetaDataNames() const; void updateFromCaret5Names(); AString toString() const; AString toFormattedString(const AString& indentation); void writeAsXML(XmlWriter& xmlWriter); void writeCiftiXML1(QXmlStreamWriter& xmlWriter) const; void writeCiftiXML2(QXmlStreamWriter& xmlWriter) const;//extra names for code style, and in case it changes in a future version void writeBorderFileXML3(QXmlStreamWriter& xmlWriter) const; void readCiftiXML1(QXmlStreamReader& xml); void readCiftiXML2(QXmlStreamReader& xml); void readBorderFileXML1(QXmlStreamReader& xml); void readBorderFileXML3(QXmlStreamReader& xml); void setModified(); void clearModified(); bool isModified() const; void resetUniqueIdentifier(); private: void readEntry(QXmlStreamReader& xml); std::map createTreeMap(); void replaceName( const AString& oldName, const AString& newName); public: private: /**the metadata storage. */ mutable std::map metadata; typedef std::map::iterator MetaDataIterator; typedef std::map::const_iterator MetaDataConstIterator; /**has the metadata been modified */ bool modifiedFlag; }; } // namespace #endif // __GIFTIMETADATA_H__ workbench-1.1.1/src/FilesBase/GiftiMetaDataXmlElements.h000066400000000000000000000206141255417355300231270ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ namespace caret { namespace GiftiMetaDataXmlElements { /** metadata name for primary anatomical structure found in NIFTI_INTENT_POINTSET data arrays. */ static const AString METADATA_NAME_ANATOMICAL_STRUCTURE_PRIMARY = "AnatomicalStructurePrimary"; /** metadata value for NIFTI_INTENT_POINTSET primary anatomical structure representing the left cerebral cortex. */ static const AString METADATA_NAME_ANATOMICAL_STRUCTURE_PRIMARY_VALUE_CORTEX_LEFT = "CortexLeft"; /** metadata value for NIFTI_INTENT_POINTSET primary anatomical structure representing the right cerebral cortex. */ static const AString METADATA_NAME_ANATOMICAL_STRUCTURE_PRIMARY_VALUE_CORTEX_RIGHT = "CortexRight"; /** metadata value for NIFTI_INTENT_POINTSET primary anatomical structure representing the both left and right cerebral cortex. */ static const AString METADATA_NAME_ANATOMICAL_STRUCTURE_PRIMARY_VALUE_CORTEX_BOTH = "CortexRightAndLeft"; /** metadata value for NIFTI_INTENT_POINTSET primary anatomical structure representing the cerebellum. */ static const AString METADATA_NAME_ANATOMICAL_STRUCTURE_PRIMARY_VALUE_CEREBELLUM = "Cerebellum"; /** metadata value for NIFTI_INTENT_POINTSET primary anatomical structure representing the head. */ static const AString METADATA_NAME_ANATOMICAL_STRUCTURE_PRIMARY_VALUE_HEAD = "Head"; /** metadata value for NIFTI_INTENT_POINTSET primary anatomical structure representing the left hippocampus. */ static const AString METADATA_NAME_ANATOMICAL_STRUCTURE_PRIMARY_VALUE_HIPPOCAMPUS_LEFT = "HippocampusLeft"; /** metadata value for NIFTI_INTENT_POINTSET primary anatomical structure representing the right hippocampus. */ static const AString METADATA_NAME_ANATOMICAL_STRUCTURE_PRIMARY_VALUE_HIPPOCAMPUS_RIGHT = "HippocampusRight"; /** metadata name for for secondary anatomical structure found in NIFTI_INTENT_POINTSET data arrays. */ static const AString METADATA_NAME_ANATOMICAL_STRUCTURE_SECONDARY = "AnatomicalStructureSecondary"; /** metadata value for NIFTI_INTENT_POINTSET secondary anatomical structure representing the gray and white boundary. */ static const AString METADATA_NAME_ANATOMICAL_STRUCTURE_SECONDARY_VALUE_GRAY_WHITE = "GrayWhite"; /** metadata value for NIFTI_INTENT_POINTSET secondary anatomical structure representing the pial (gray/CSF boundary). */ static const AString METADATA_NAME_ANATOMICAL_STRUCTURE_SECONDARY_VALUE_PIAL = "Pial"; /** metadata value for NIFTI_INTENT_POINTSET secondary anatomical structure representing the mid thickness (layer 4). */ static const AString METADATA_NAME_ANATOMICAL_STRUCTURE_SECONDARY_VALUE_ = "MidThickness"; /** metadata name found in the file's metadata and indicates the date and time the file was written. */ static const AString METADATA_NAME_DATE = "Date"; /** metadata name found in any metadata and provides a description of the entity's content. */ static const AString METADATA_NAME_DESCRIPTION = "Description"; static const AString METADATA_NAME_COMMENT = "Comment"; /** metadata name for geometric type found in NIFTI_INTENT_POINTSET data arrays. */ static const AString METADATA_NAME_GEOMETRIC_TYPE = "GeometricType"; /** metadata value for NIFTI_INTENT_POINTSET's Geometric Type Reconstruction with a "blocky" appearance. */ static const AString METADATA_NAME_GEOMETRIC_TYPE_VALUE_RECONSTRUCTION = "Reconstruction"; /** metadata value for NIFTI_INTENT_POINTSET's Geometric Type representing true anatomical structure. */ static const AString METADATA_NAME_GEOMETRIC_TYPE_VALUE_ANATOMICAL = "Anatomical"; /** metadata value for NIFTI_INTENT_POINTSET's Geometric Type for inflated surface. */ static const AString METADATA_NAME_GEOMETRIC_TYPE_VALUE_INFLATED = "Inflated"; /** metadata value for NIFTI_INTENT_POINTSET's Geometric Type for very inflated surface. */ static const AString METADATA_NAME_GEOMETRIC_TYPE_VALUE_VERY_INFLATED = "VeryInflated"; /** metadata value for NIFTI_INTENT_POINTSET's Geometric Type spherical surface. */ static const AString METADATA_NAME_GEOMETRIC_TYPE_VALUE_SPHERICAL = "Spherical"; /** metadata value for NIFTI_INTENT_POINTSET's Geometric Type semi-spherical surface with one half flattened. */ static const AString METADATA_NAME_GEOMETRIC_TYPE_VALUE_SEMI_SPHERICAL = "SemiSpherical"; /** metadata value for NIFTI_INTENT_POINTSET's Geometric Type ellipsoid surface. */ static const AString METADATA_NAME_GEOMETRIC_TYPE_VALUE_ELLIPSOID = "Ellipsoid"; /** metadata value for NIFTI_INTENT_POINTSET's Geometric Type flat surface. */ static const AString METADATA_NAME_GEOMETRIC_TYPE_VALUE_FLAT = "Flat"; /** metadata value for NIFTI_INTENT_POINTSET's Geometric Type hull surface (eg: wrapping around cortex with sulci filled but not necessarily convex). */ static const AString METADATA_NAME_GEOMETRIC_TYPE_VALUE_HULL = "Hull"; /**metadata name for NIFTI Intent label found in functional data arrays. */ static const AString METADATA_NAME_INTENT_CODE = "Intent_code"; /**metadata name for NIFTI Intent parameter one in functional data arrays.*/ static const AString METADATA_NAME_INTENT_P1 = "intent_p1"; /**metadata name for NIFTI Intent parameter two in functional data arrays.*/ static const AString METADATA_NAME_INTENT_P2 = "intent_p2"; /**metadata name for NIFTI Intent parameter three in functional data arrays.*/ static const AString METADATA_NAME_INTENT_P3 = "intent_p3"; /**metadata name for name of data, often displayed in the user-interface. */ static const AString METADATA_NAME_NAME = "Name"; /**metadata name for text that identifies a subjet. */ static const AString METADATA_NAME_SUBJECT_ID = "SubjectID"; /**metadata name for text that uniquely defines a surface. */ static const AString METADATA_NAME_SURFACE_ID = "SurfaceID"; /**metadata name for time step (TR) in NIFTI_INTENT_TIME_SERIES arrays. */ static const AString METADATA_NAME_TIME_STEP = "TimeStep"; /**metadata name for topological type in NIFTI_INTENT_TRIANGLE arrays. */ static const AString METADATA_NAME_TOPOLOGICAL_TYPE = "TopologicalType"; /** metadata value for NIFTI_INTENT_TRIANGLE's Topological Type for closed topology. */ static const AString METADATA_NAME_TOPOLOGICAL_TYPE_VALUE_CLOSED = "Closed"; /** metadata value for NIFTI_INTENT_TRIANGLE's Topological Type for open topology (perhaps medial wall removed). */ static const AString METADATA_NAME_TOPOLOGICAL_TYPE_VALUE_OPEN = "Open"; /** metadata value for NIFTI_INTENT_TRIANGLE's Topological Type for cut topology (typically used with flat surfaces with medial wall removed and cuts made to reduce distortion. */ static const AString METADATA_NAME_TOPOLOGICAL_TYPE_VALUE_CUT = "Cut"; /** metadata name for a unique identifiers for the data array. This ID is best generated using a Universal Unique Identifier function such as Java's java.util.uuid or C's uuid_generate(). @see UUID */ static const AString METADATA_NAME_UNIQUE_ID = "UniqueID"; /**metadata name for username of user that wrote the file in file metadata.*/ static const AString METADATA_NAME_USER_NAME = "UserName"; /**name of study metadata link set metadata */ static const AString METADATA_NAME_STUDY_METADATA_LINK_SET = "StudyMetaDataLinkSet"; /**name of palette color mapping stored in metadata */ static const AString METADATA_NAME_PALETTE_COLOR_MAPPING = "PaletteColorMapping"; } // namespace } // namespace workbench-1.1.1/src/FilesBase/GiftiXmlElements.cxx000066400000000000000000000023471255417355300221040ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "GiftiXmlElements.h" using namespace caret; /** * Get an attribute dimension (Dim0, Dim1, etc). * @param Index value for dimension. * @return Dim0, Dim1, etc */ AString GiftiXmlElements::getAttributeDimension(int32_t dimIndex) { std::ostringstream str; str << GiftiXmlElements::ATTRIBUTE_DATA_ARRAY_DIM_PREFIX.toStdString() << dimIndex; return AString::fromStdString(str.str()); } workbench-1.1.1/src/FilesBase/GiftiXmlElements.h000066400000000000000000000165221255417355300215310ustar00rootroot00000000000000#ifndef __GIFTIXMLELEMENTS_H__ #define __GIFTIXMLELEMENTS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include namespace caret { /** * GIFTI XML Element tags */ namespace GiftiXmlElements { /** tag for coordinate transformation matrix element */ static const AString TAG_COORDINATE_TRANSFORMATION_MATRIX = "CoordinateSystemTransformMatrix"; /** tag for data element */ static const AString TAG_DATA = "Data"; /** tag for data array element */ static const AString TAG_DATA_ARRAY = "DataArray"; /** tag for coordinate transformation data space element */ static const AString TAG_MATRIX_DATA_SPACE = "DataSpace"; /** tag for GIFTI element */ static const AString TAG_GIFTI = "GIFTI"; /** tag for label element */ static const AString TAG_LABEL = "Label"; /** tag for label table element */ static const AString TAG_LABEL_TABLE = "LabelTable"; /** tag for a metadata entry */ static const AString TAG_METADATA_ENTRY = "MD"; /** tag for matrix data */ static const AString TAG_MATRIX_DATA = "MatrixData"; /** tag for metadata */ static const AString TAG_METADATA = "MetaData"; /** tag for metadata name */ static const AString TAG_METADATA_NAME = "Name"; /** tag for coordinate transformation space element */ static const AString TAG_MATRIX_TRANSFORMED_SPACE = "TransformedSpace"; /** tag for metadata value element */ static const AString TAG_METADATA_VALUE = "Value"; /** attribute for data array indexing order */ static const AString ATTRIBUTE_DATA_ARRAY_INDEXING_ORDER = "ArrayIndexingOrder"; /** attribute for data array data type */ static const AString ATTRIBUTE_DATA_ARRAY_DATA_TYPE = "DataType"; /** attribute for data array dimensionality */ static const AString ATTRIBUTE_DATA_ARRAY_DIMENSIONALITY = "Dimensionality"; /** attribute for data array dimensionality */ static const AString ATTRIBUTE_DATA_ARRAY_DIM_PREFIX = "Dim"; /** attribute for data array encoding */ static const AString ATTRIBUTE_DATA_ARRAY_ENCODING = "Encoding"; /** attribute for data array ending */ static const AString ATTRIBUTE_DATA_ARRAY_ENDIAN = "Endian"; /** attribute for data array external file name */ static const AString ATTRIBUTE_DATA_ARRAY_EXTERNAL_FILE_NAME = "ExternalFileName"; /** attribute for data array external file offset */ static const AString ATTRIBUTE_DATA_ARRAY_EXTERNAL_FILE_OFFSET = "ExternalFileOffset"; /** attribute for data array label index REPLACED BY KEY*/ static const AString ATTRIBUTE_LABEL_INDEX_obsolete = "Index"; /** attribute for data array label key */ static const AString ATTRIBUTE_LABEL_KEY = "Key"; /** attribute for data array label red color component */ static const AString ATTRIBUTE_LABEL_RED = "Red"; /** attribute for data array label green color component */ static const AString ATTRIBUTE_LABEL_GREEN = "Green"; /** attribute for data array label blue color component */ static const AString ATTRIBUTE_LABEL_BLUE = "Blue"; /** attribute for data array label alpha color component */ static const AString ATTRIBUTE_LABEL_ALPHA = "Alpha"; /** attribute for data array label X-coordinate */ static const AString ATTRIBUTE_LABEL_X = "X"; /** attribute for data array label X-coordinate */ static const AString ATTRIBUTE_LABEL_Y = "Y"; /** attribute for data array label X-coordinate */ static const AString ATTRIBUTE_LABEL_Z = "Z"; /** attribute for data array intent */ static const AString ATTRIBUTE_DATA_ARRAY_INTENT = "Intent"; /** attribute for data array intent parameter 1 */ static const AString ATTRIBUTE_DATA_ARRAY_INTENT_P1 = "intent_p1"; /** attribute for data array intent parameter 2 */ static const AString ATTRIBUTE_DATA_ARRAY_INTENT_P2 = "intent_p2"; /** attribute for data array intent parameter 3 */ static const AString ATTRIBUTE_DATA_ARRAY_INTENT_P3 = "intent_p3"; /** attribute for GIFTI Number of Data Arrays */ static const AString ATTRIBUTE_GIFTI_NUMBER_OF_DATA_ARRAYS = "NumberOfDataArrays"; /** attribute for GIFTI Version */ static const AString ATTRIBUTE_GIFTI_VERSION = "Version"; AString getAttributeDimension(int32_t dimIndex); /** * Get an attribute dimension (Dim0, Dim1, etc). * @param Index value for dimension. * @return Dim0, Dim1, etc * static AString getAttributeDimension(int32_t dimIndex) { std::ostringstream str; str << ATTRIBUTE_DATA_ARRAY_DIM_PREFIX << dimIndex;; return str.str(); } */ /* static const AString tagGIFTI = "GIFTI"; static const AString tagMetaData = "MetaData"; static const AString tagMD = "MD"; static const AString tagName = "Name"; static const AString tagValue = "Value"; static const AString tagDataArray = "DataArray"; static const AString tagData = "Data"; static const AString tagLabelTable = "LabelTable"; static const AString tagLabel = "Labe"; static const AString tagMatrix = "CoordinateSystemTransformMatrix"; static const AString tagMatrixDataSpace = "DataSpace"; static const AString tagMatrixTransformedSpace = "TransformedSpace"; static const AString tagMatrixData = "MatrixData"; static const AString attVersion = "Version"; static const AString attNumberOfDataArrays = "NumberOfDataArrays"; static const AString attArraySubscriptingOrder = "ArrayIndexingOrder"; static const AString attKey = "Key"; static const AString attRed = "Red"; static const AString attGreen = "Green"; static const AString attBlue = "Blue"; static const AString attAlpha = "Alpha"; static const AString attIntent = "Intent"; static const AString attDataType = "DataType"; static const AString attDimensionality = "Dimensionality"; static const AString attDim = "Dim"; static const AString attEncoding = "Encoding"; static const AString attEndian = "Endian"; static const AString attExternalFileName = "ExternalFileName"; static const AString attExternalFileOffset = "ExternalFileOffset"; */ }; // namespace } // namespace #endif // __GIFTIXMLELEMENTS_H__ workbench-1.1.1/src/FilesBase/NiftiEnums.cxx000066400000000000000000001031011255417355300207330ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __NIFTI_ENUMS_DECLARE__ #include "NiftiEnums.h" #undef __NIFTI_ENUMS_DECLARE__ using namespace caret; ///Nifti Data Type Enum NiftiDataTypeEnum::NiftiDataTypeEnum() { } NiftiDataTypeEnum::NiftiDataTypeEnum(Enum e, const AString& name, const int32_t integerCode) { this->e = e; this->name = name; this->integerCode = integerCode; } NiftiDataTypeEnum::~NiftiDataTypeEnum() { } void NiftiDataTypeEnum::createDataTypes() { if (dataTypesCreatedFlag) { return; } dataTypesCreatedFlag = true; dataTypes.push_back(NiftiDataTypeEnum(NIFTI_TYPE_INVALID, "NIFTI_DATA_TYPE_NONE", 0)); dataTypes.push_back(NiftiDataTypeEnum(NIFTI_TYPE_UINT8, "NIFTI_TYPE_UINT8", 2)); dataTypes.push_back(NiftiDataTypeEnum(NIFTI_TYPE_INT16, "NIFTI_TYPE_INT16", 4)); dataTypes.push_back(NiftiDataTypeEnum(NIFTI_TYPE_INT32, "NIFTI_TYPE_INT32", 8)); dataTypes.push_back(NiftiDataTypeEnum(NIFTI_TYPE_FLOAT32, "NIFTI_TYPE_FLOAT32", 16)); dataTypes.push_back(NiftiDataTypeEnum(NIFTI_TYPE_COMPLEX64, "NIFTI_TYPE_COMPLEX64", 32)); dataTypes.push_back(NiftiDataTypeEnum(NIFTI_TYPE_FLOAT64, "NIFTI_TYPE_FLOAT64", 64)); dataTypes.push_back(NiftiDataTypeEnum(NIFTI_TYPE_RGB24, "NIFTI_TYPE_RGB24", 128)); dataTypes.push_back(NiftiDataTypeEnum(NIFTI_TYPE_INT8, "NIFTI_TYPE_INT8", 256)); dataTypes.push_back(NiftiDataTypeEnum(NIFTI_TYPE_UINT16, "NIFTI_TYPE_UINT16", 512)); dataTypes.push_back(NiftiDataTypeEnum(NIFTI_TYPE_UINT32, "NIFTI_TYPE_UINT32", 768)); dataTypes.push_back(NiftiDataTypeEnum(NIFTI_TYPE_INT64, "NIFTI_TYPE_INT64", 1024)); dataTypes.push_back(NiftiDataTypeEnum(NIFTI_TYPE_UINT64, "NIFTI_TYPE_UINT64", 1280)); dataTypes.push_back(NiftiDataTypeEnum(NIFTI_TYPE_FLOAT128, "NIFTI_TYPE_FLOAT128", 1792)); dataTypes.push_back(NiftiDataTypeEnum(NIFTI_TYPE_COMPLEX128, "NIFTI_TYPE_COMPLEX128", 1792)); dataTypes.push_back(NiftiDataTypeEnum(NIFTI_TYPE_COMPLEX256, "NIFTI_TYPE_COMPLEX256", 2048)); } /** * Get a string representition of the enumerated type. * @param e * Enumerated value. * @return * String representing enumerated value. */ AString NiftiDataTypeEnum::toName(Enum e) { createDataTypes(); const NiftiDataTypeEnum* ndt = findData(e); return ndt->name; } /** * Get an enumerated value corresponding to its name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ NiftiDataTypeEnum::Enum NiftiDataTypeEnum::fromName(const AString& s, bool* isValidOut) { createDataTypes(); bool validFlag = false; Enum e = NIFTI_TYPE_FLOAT32; for (std::vector::iterator iter = dataTypes.begin(); iter != dataTypes.end(); iter++) { const NiftiDataTypeEnum& ndt = *iter; if (ndt.name == s) { e = ndt.e; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } return e; } /** * Find the Data Type using the enum. * * @return * Object using enum or NULL if not found. */ const NiftiDataTypeEnum* NiftiDataTypeEnum::findData(Enum e) { createDataTypes(); for (std::vector::iterator iter = dataTypes.begin(); iter != dataTypes.end(); iter++) { const NiftiDataTypeEnum& ndt = *iter; if (ndt.e == e) { return &ndt; } } return NULL; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t NiftiDataTypeEnum::toIntegerCode(Enum e) { createDataTypes(); const NiftiDataTypeEnum* ndt = findData(e); return ndt->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ NiftiDataTypeEnum::Enum NiftiDataTypeEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { createDataTypes(); bool validFlag = false; Enum e = NIFTI_TYPE_INVALID; for (std::vector::iterator iter = dataTypes.begin(); iter != dataTypes.end(); iter++) { const NiftiDataTypeEnum& ndt = *iter; if (ndt.integerCode == integerCode) { e = ndt.e; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } return e; } ///Nifti Intent Enum NiftiIntentEnum::NiftiIntentEnum(Enum e, const AString& enumName, const int32_t integerCode, const AString& name, const AString& p1Name, const AString& p2Name, const AString& p3Name) { this->e = e; this->enumName = enumName; this->integerCode = integerCode; this->name = name; this->p1Name = p1Name; this->p2Name = p2Name; this->p3Name = p3Name; } //NiftiIntentEnum::NiftiIntentEnum() //{ // //} NiftiIntentEnum::~NiftiIntentEnum() { } void NiftiIntentEnum::initializeIntents() { if (intentsCreatedFlag) { return; } intentsCreatedFlag = true; //intents.push_back( // NiftiIntentEnum()); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_NONE,"NIFTI_INTENT_NONE", 0,"None","","","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_CORREL,"NIFTI_INTENT_CORREL", 2,"Correlation Statistic","DOF","","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_TTEST,"NIFTI_INTENT_TTEST", 3,"T-Statistic","DOF","","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_FTEST,"NIFTI_INTENT_FTEST", 4,"F-Statistic","Numberator DOF","Denominator DOF","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_ZSCORE,"NIFTI_INTENT_ZSCORE", 5,"Z-Score","","","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_CHISQ,"NIFTI_INTENT_CHISQ", 6,"Chi-Squared Distribution","DOF","","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_BETA,"NIFTI_INTENT_BETA", 7,"Beta Distribution","a","b","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_BINOM,"NIFTI_INTENT_BINOM", 8,"Binomial Distribution", "Number of Trials","Probability per Trial","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_GAMMA,"NIFTI_INTENT_GAMMA", 9,"Gamma Distribution","Shape","Scale","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_POISSON,"NIFTI_INTENT_POISSON", 10,"Poisson Distribution","Mean","","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_NORMAL,"NIFTI_INTENT_NORMAL", 11,"Normal Distribution","Mean","Standard Deviation","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_FTEST_NONC,"NIFTI_INTENT_FTEST_NONC", 12,"F-Statistic Non-Central", "Numerator DOF", "Denominator DOF", "Numerator Noncentrality Parameter")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_CHISQ_NONC,"NIFTI_INTENT_CHISQ_NONC", 13,"Chi-Squared Non-Central", "DOF", "Noncentrality Parameter","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_LOGISTIC,"NIFTI_INTENT_LOGISTIC", 14,"Logistic Distribution","Location","Scale","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_LAPLACE,"NIFTI_INTENT_LAPLACE", 15,"Laplace Distribution","Location","Scale","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_UNIFORM,"NIFTI_INTENT_UNIFORM", 16,"Uniform Distribution","Lower End","Upper End","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_TTEST_NONC,"NIFTI_INTENT_TTEST_NONC", 17,"T-Statistic Non-Central", "DOF", "Noncentrality Parameter","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_WEIBULL,"NIFTI_INTENT_WEIBULL", 18,"Weibull Distribution","Location","Scale","Power")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_CHI,"NIFTI_INTENT_CHI", 19,"Chi Distribution", "Half Normal Distribution", "Rayleigh Distribution", "Maxwell-Boltzmann Distribution")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_INVGAUSS,"NIFTI_INTENT_INVGAUSS", 20,"Inverse Gaussian Distribution", "MU", "Lambda","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_EXTVAL,"NIFTI_INTENT_EXTVAL", 21,"Extreme Value Distribution", "Location", "Scale","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_PVAL,"NIFTI_INTENT_PVAL", 22,"P-Value","","","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_LOGPVAL,"NIFTI_INTENT_LOGPVAL", 23,"Log P-Value","","","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_LOG10PVAL,"NIFTI_INTENT_LOG10PVAL", 24,"Logn10 P-Value","","","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_ESTIMATE,"NIFTI_INTENT_ESTIMATE", 1001,"Estimate","","","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_LABEL,"NIFTI_INTENT_LABEL", 1002,"Label Indices","","","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_NEURONAME,"NIFTI_INTENT_NEURONAME", 1003,"Neuronames Indices","","","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_GENMATRIX,"NIFTI_INTENT_GENMATRIX", 1004,"General Matrix","","","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_SYMMATRIX,"NIFTI_INTENT_SYMMATRIX", 1005,"Symmetric Matrix","","","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_DISPVECT,"NIFTI_INTENT_DISPVECT", 1006,"Displacement Vector","","","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_VECTOR,"NIFTI_INTENT_VECTOR", 1007,"Vector","","","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_POINTSET,"NIFTI_INTENT_POINTSET", 1008,"Point Set","","","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_TRIANGLE,"NIFTI_INTENT_TRIANGLE", 1009,"Triangle","","","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_QUATERNION,"NIFTI_INTENT_QUATERNION", 1010,"Quaternion","","","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_DIMLESS,"NIFTI_INTENT_DIMLESS", 1011,"Dimensionless Number","","","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_TIME_SERIES,"NIFTI_INTENT_TIME_SERIES", 2001,"Time Series","","","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_NODE_INDEX,"NIFTI_INTENT_NODE_INDEX", 2002,"Node Index","","","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_RGB_VECTOR,"NIFTI_INTENT_RGB_VECTOR", 2003,"RGB","","","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_RGBA_VECTOR,"NIFTI_INTENT_RGBA_VECTOR", 2004,"RGBA","","","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_SHAPE,"NIFTI_INTENT_SHAPE", 2005,"Shape","","","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_CARET_DEFORMATION_NODE_INDICES,"NIFTI_INTENT_CARET_DEFORMATION_NODE_INDICES", 25000,"Deformation Node Indices","","","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_CARET_DEFORMATION_NODE_AREAS,"NIFTI_INTENT_CARET_DEFORMATION_NODE_AREAS", 25001,"Deformation Node Areas","","","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_CONNECTIVITY_DENSE,"NIFTI_INTENT_CONNECTIVITY_DENSE", 3001,"Connectivity - Dense","","","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_CONNECTIVITY_DENSE_TIME,"NIFTI_INTENT_CONNECTIVITY_DENSE_TIME", 3002,"Connectivity - Dense Time Series","","","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_CONNECTIVITY_PARCELLATED,"NIFTI_INTENT_CONNECTIVITY_PARCELLATED", 3003,"Connectivity - Parcellated","","","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_CONNECTIVITY_PARCELLATED_TIME,"NIFTI_INTENT_CONNECTIVITY_PARCELLATED_TIME", 3004,"Connectivity - Parcellated Time Series","","","")); intents.push_back(NiftiIntentEnum(NIFTI_INTENT_CONNECTIVITY_TRAJECTORY,"NIFTI_INTENT_CONNECTIVITY_TRAJECTORY", 3005,"Connectivity - Trajectory","","","")); } /** * Get a string representition of the enumerated type. * @param e * Enumerated value. * @return * String representing enumerated value. */ AString NiftiIntentEnum::toName(Enum e) { initializeIntents(); const NiftiIntentEnum* ni = findData(e); return ni->enumName; } /** * Get an enumerated value corresponding to its name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ NiftiIntentEnum::Enum NiftiIntentEnum::fromName(const AString& s, bool* isValidOut) { initializeIntents(); bool validFlag = false; Enum e = NIFTI_INTENT_NONE; for (std::vector::const_iterator iter = intents.begin(); iter != intents.end(); iter++) { const NiftiIntentEnum& intent = *iter; if (intent.enumName == s) { e = intent.e; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } return e; } /** * Find the Intent object corresponding to the enum. * @param e * The enum * @return * The Intent or NULL if enum does not match an intent. */ const NiftiIntentEnum* NiftiIntentEnum::findData(Enum e) { initializeIntents(); for (std::vector::const_iterator iter = intents.begin(); iter != intents.end(); iter++) { const NiftiIntentEnum& intent = *iter; if (intent.e == e) { return &intent; } } CaretAssertMessage(0, "Intent enum failed to match."); return NULL; } /** * Get the "P1" name associated with an intent. * @param e * The enum. * @return * P1 name associated with intent (may be empty string). */ AString NiftiIntentEnum::toNameP1(Enum e) { initializeIntents(); const NiftiIntentEnum* ni = findData(e); return ni->p1Name; } /** * Get the "P2" name associated with an intent. * @param e * The enum. * @return * P2 name associated with intent (may be empty string). */ AString NiftiIntentEnum::toNameP2(Enum e) { initializeIntents(); const NiftiIntentEnum* ni = findData(e); return ni->p2Name; } /** * Get the "P3" name associated with an intent. * @param e * The enum. * @return * P3 name associated with intent (may be empty string). */ AString NiftiIntentEnum::toNameP3(Enum e) { initializeIntents(); const NiftiIntentEnum* ni = findData(e); return ni->p3Name; } /** * Get the integer code associated with an intent. * @param e * The enum. * @return * Integer code associated with intent. */ int32_t NiftiIntentEnum::toIntegerCode(Enum e) { initializeIntents(); const NiftiIntentEnum* ni = findData(e); return ni->integerCode; } /** * Find enum corresponding to integer code. * @param integerCode * The integer code. * @param isValidOut * If not NULL, on exit it indicates valid integer code. * @return * Enum corresponding to integer code. */ NiftiIntentEnum::Enum NiftiIntentEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { initializeIntents(); bool validFlag = false; Enum e = NIFTI_INTENT_NONE; for (std::vector::const_iterator iter = intents.begin(); iter != intents.end(); iter++) { const NiftiIntentEnum& intent = *iter; if (intent.integerCode == integerCode) { e = intent.e; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } return e; } ///Nifti Spacing Units Enum NiftiSpacingUnitsEnum::NiftiSpacingUnitsEnum(Enum e, const int32_t integerCode, const AString& name) { this->e = e; this->integerCode = integerCode; this->name = name; } NiftiSpacingUnitsEnum::~NiftiSpacingUnitsEnum() { } void NiftiSpacingUnitsEnum::initializeSpacingUnits() { if (initializedFlag) { return; } initializedFlag = true; spacingUnits.push_back(NiftiSpacingUnitsEnum(NIFTI_UNITS_UNKNOWN, 0, "NIFTI_UNITS_UNKNOWN")); spacingUnits.push_back(NiftiSpacingUnitsEnum(NIFTI_UNITS_METER, 1, "NIFTI_UNITS_METER")); spacingUnits.push_back(NiftiSpacingUnitsEnum(NIFTI_UNITS_MM, 2, "NIFTI_UNITS_MM")); spacingUnits.push_back(NiftiSpacingUnitsEnum(NIFTI_UNITS_MICRON, 3, "NIFTI_UNITS_MICRON")); } /** * Get a string representition of the enumerated type. * @param e * Enumerated value. * @return * String representing enumerated value. */ AString NiftiSpacingUnitsEnum::toName(Enum e) { initializeSpacingUnits(); const NiftiSpacingUnitsEnum* nsu = findData(e); return nsu->name; } /** * Get an enumerated value corresponding to its name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ NiftiSpacingUnitsEnum::Enum NiftiSpacingUnitsEnum::fromName(const AString& s, bool* isValidOut) { initializeSpacingUnits(); bool validFlag = false; Enum e = NIFTI_UNITS_UNKNOWN; for (std::vector::iterator iter = spacingUnits.begin(); iter != spacingUnits.end(); iter++) { const NiftiSpacingUnitsEnum& ndt = *iter; if (ndt.name == s) { e = ndt.e; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } return e; } /** * Find the Intent object corresponding to the enum. * @param e * The enum * @return * The Intent or NULL if enum does not match an intent. */ const NiftiSpacingUnitsEnum* NiftiSpacingUnitsEnum::findData(Enum e) { initializeSpacingUnits(); for (std::vector::const_iterator iter = spacingUnits.begin(); iter != spacingUnits.end(); iter++) { const NiftiSpacingUnitsEnum& nsu = *iter; if (nsu.e == e) { return &nsu; } return &nsu; } CaretAssertMessage(0, "Spacing Units enum failed to match."); return NULL; } /** * Get the integer code associated with an spacing units. * @param e * The enum. * @return * Integer code associated with spacing units. */ int32_t NiftiSpacingUnitsEnum::toIntegerCode(Enum e) { initializeSpacingUnits(); const NiftiSpacingUnitsEnum* nsu = findData(e); return nsu->integerCode; } /** * Find enum corresponding to integer code. * @param integerCode * The integer code. * @param isValidOut * If not NULL, on exit it indicates valid integer code. * @return * Enum corresponding to integer code. */ NiftiSpacingUnitsEnum::Enum NiftiSpacingUnitsEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { initializeSpacingUnits(); bool validFlag = false; Enum e = NIFTI_UNITS_UNKNOWN; for (std::vector::const_iterator iter = spacingUnits.begin(); iter != spacingUnits.end(); iter++) { const NiftiSpacingUnitsEnum& nsu = *iter; if (nsu.integerCode == integerCode) { e = nsu.e; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } return e; } ///Nifti Time Units Enum /** * Constructor. * * @param e * An enumerated value. * @param name * Name of enumberated value. * @param guiName * Name in GUI of enumberated value. */ NiftiTimeUnitsEnum::NiftiTimeUnitsEnum(const Enum e, const int32_t integerCode, const AString& name, const AString& guiName) { this->e = e; this->integerCode = integerCode; this->name = name; this->guiName = guiName; } /** * Destructor. */ NiftiTimeUnitsEnum::~NiftiTimeUnitsEnum() { } void NiftiTimeUnitsEnum::initializeTimeUnits() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(NiftiTimeUnitsEnum(NIFTI_UNITS_UNKNOWN, 0,"NIFTI_UNITS_UNKNOWN","Unknown")); enumData.push_back(NiftiTimeUnitsEnum(NIFTI_UNITS_SEC, 8,"NIFTI_UNITS_SEC","Seconds")); enumData.push_back(NiftiTimeUnitsEnum(NIFTI_UNITS_MSEC, 16,"NIFTI_UNITS_MSEC","Milliseconds")); enumData.push_back(NiftiTimeUnitsEnum(NIFTI_UNITS_USEC, 24,"NIFTI_UNITS_USEC","Microseconds")); enumData.push_back(NiftiTimeUnitsEnum(NIFTI_UNITS_HZ, 32,"NIFTI_UNITS_HZ","Hertz")); enumData.push_back(NiftiTimeUnitsEnum(NIFTI_UNITS_PPM, 40,"NIFTI_UNITS_PPM","Parts Per Million")); } /** * Find the data for and enumerated value. * @param e * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const NiftiTimeUnitsEnum* NiftiTimeUnitsEnum::findData(const Enum e) { initializeTimeUnits(); int64_t num = enumData.size(); for (int64_t i = 0; i < num; i++) { const NiftiTimeUnitsEnum* d = &enumData[i]; if (d->e == e) { return d; } } CaretAssertMessage(0, "Time Units enum failed to match."); return NULL; } /** * Get a string representation of the enumerated type. * @param e * Enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * label exists for the input enum value. * @return * String representing enumerated value. */ AString NiftiTimeUnitsEnum::toName(Enum e) { initializeTimeUnits(); const NiftiTimeUnitsEnum* ntu = findData(e); return ntu->name; } /** * Get an enumerated value corresponding to its name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ NiftiTimeUnitsEnum::Enum NiftiTimeUnitsEnum::fromName(const AString& s, bool* isValidOut) { initializeTimeUnits(); bool validFlag = false; Enum e = NIFTI_UNITS_UNKNOWN; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const NiftiTimeUnitsEnum& d = *iter; if (d.name == s) { e = d.e; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } return e; } /** * Get the integer code associated with an time units. * @param e * The enum. * @return * Integer code associated with time units. */ int32_t NiftiTimeUnitsEnum::toIntegerCode(Enum e) { initializeTimeUnits(); const NiftiTimeUnitsEnum* nsu = findData(e); return nsu->integerCode; } /** * Find enum corresponding to integer code. * @param integerCode * The integer code. * @param isValidOut * If not NULL, on exit it indicates valid integer code. * @return * Enum corresponding to integer code. */ NiftiTimeUnitsEnum::Enum NiftiTimeUnitsEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { initializeTimeUnits(); bool validFlag = false; Enum e = NIFTI_UNITS_UNKNOWN; for (std::vector::const_iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const NiftiTimeUnitsEnum& nsu = *iter; if (nsu.integerCode == integerCode) { e = nsu.e; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } return e; } /** * Get a string representation for GUI of the enumerated type. * @param e * Enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * label exists for the input enum value. * @return * String representing enumerated value for GUI. */ AString NiftiTimeUnitsEnum::toGuiName(Enum e) { initializeTimeUnits(); const NiftiTimeUnitsEnum* ntu = findData(e); return ntu->guiName; } /** * Constructor. * * @param e * An enumerated value. * @param name * Name of enumberated value. */ NiftiTransformEnum::NiftiTransformEnum( const Enum e, const int32_t integerCode, const AString& name) { this->e = e; this->integerCode = integerCode; this->name = name; } /** * Destructor. */ NiftiTransformEnum::~NiftiTransformEnum() { } void NiftiTransformEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(NiftiTransformEnum(NIFTI_XFORM_UNKNOWN, 0,"NIFTI_XFORM_UNKNOWN")); enumData.push_back(NiftiTransformEnum(NIFTI_XFORM_SCANNER_ANAT, 1,"NIFTI_XFORM_SCANNER_ANAT")); enumData.push_back(NiftiTransformEnum(NIFTI_XFORM_ALIGNED_ANAT, 2,"NIFTI_XFORM_ALIGNED_ANAT")); enumData.push_back(NiftiTransformEnum(NIFTI_XFORM_TALAIRACH, 3,"NIFTI_XFORM_TALAIRACH")); enumData.push_back(NiftiTransformEnum(NIFTI_XFORM_MNI_152, 4,"NIFTI_XFORM_MNI_152")); } /** * Find the data for and enumerated value. * @param e * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const NiftiTransformEnum* NiftiTransformEnum::findData(const Enum e) { initialize(); int64_t num = enumData.size(); for (int64_t i = 0; i < num; i++) { const NiftiTransformEnum* d = &enumData[i]; if (d->e == e) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param e * Enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * label exists for the input enum value. * @return * String representing enumerated value. */ AString NiftiTransformEnum::toName(Enum e) { initialize(); const NiftiTransformEnum* nt = findData(e); return nt->name; } /** * Get an enumerated value corresponding to its name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ NiftiTransformEnum::Enum NiftiTransformEnum::fromName(const AString& s, bool* isValidOut) { initialize(); bool validFlag = false; Enum e = NIFTI_XFORM_UNKNOWN; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const NiftiTransformEnum& d = *iter; if (d.name == s) { e = d.e; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } return e; } /** * Get the integer code associated with a transform. * @param e * The enum. * @return * Integer code associated with a transform. */ int32_t NiftiTransformEnum::toIntegerCode(Enum e) { initialize(); const NiftiTransformEnum* nsu = findData(e); return nsu->integerCode; } /** * Find enum corresponding to integer code. * @param integerCode * The integer code. * @param isValidOut * If not NULL, on exit it indicates valid integer code. * @return * Enum corresponding to integer code. */ NiftiTransformEnum::Enum NiftiTransformEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { initialize(); bool validFlag = false; Enum e = NIFTI_XFORM_UNKNOWN; for (std::vector::const_iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const NiftiTransformEnum& nsu = *iter; if (nsu.integerCode == integerCode) { e = nsu.e; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } return e; } /** * Constructor. * * @param e * An enumerated value. * @param name * Name of enumberated value. */ NiftiVersionEnum::NiftiVersionEnum( const Enum e, const int32_t integerCode, const AString& name) { this->e = e; this->integerCode = integerCode; this->name = name; } /** * Destructor. */ NiftiVersionEnum::~NiftiVersionEnum() { } void NiftiVersionEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(NiftiVersionEnum(NIFTI_VERSION_1, 348, "NIFTI_VERSION_1")); enumData.push_back(NiftiVersionEnum(NIFTI_VERSION_2, 540, "NIFTI_VERSION_2")); } /** * Find the data for and enumerated value. * @param e * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const NiftiVersionEnum* NiftiVersionEnum::findData(const Enum e) { initialize(); int64_t num = enumData.size(); for (int64_t i = 0; i < num; i++) { const NiftiVersionEnum* d = &enumData[i]; if (d->e == e) { return d; } } CaretAssertMessage(0, "NIFTI Version enum failed to match."); return NULL; } /** * Get a string representation of the enumerated type. * @param e * Enumerated value. * @return * String representing enumerated value. */ AString NiftiVersionEnum::toName(Enum e) { initialize(); const NiftiVersionEnum* nv = findData(e); return nv->name; } /** * Get an enumerated value corresponding to its name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ NiftiVersionEnum::Enum NiftiVersionEnum::fromName(const AString& s, bool* isValidOut) { initialize(); bool validFlag = false; Enum e = NIFTI_VERSION_1; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const NiftiVersionEnum& d = *iter; if (d.name == s) { e = d.e; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } return e; } /** * Get the integer code associated with a transform. * @param e * The enum. * @return * Integer code associated with a transform. */ int32_t NiftiVersionEnum::toIntegerCode(Enum e) { initialize(); const NiftiVersionEnum* nsu = findData(e); return nsu->integerCode; } /** * Find enum corresponding to integer code. * @param integerCode * The integer code. * @param isValidOut * If not NULL, on exit it indicates valid integer code. * @return * Enum corresponding to integer code. */ NiftiVersionEnum::Enum NiftiVersionEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { initialize(); bool validFlag = false; Enum e = NIFTI_VERSION_1; for (std::vector::const_iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const NiftiVersionEnum& nsu = *iter; if (nsu.integerCode == integerCode) { e = nsu.e; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } return e; } workbench-1.1.1/src/FilesBase/NiftiEnums.h000066400000000000000000000323051255417355300203670ustar00rootroot00000000000000#ifndef __NIFTI_ENUMS_H__ #define __NIFTI_ENUMS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include "nifti2.h" namespace caret { //we specify here whether to write in native byte order, or to honor the original //byte order, we could also get clever and try to determine whether or not we are //a big or little-endian machine, but the key concept to keep track of is whether or //not we want to honor the byte order of the original file (for in place read/write) //not the actual byte order. This should also function portably. enum NIFTI_BYTE_ORDER { NATIVE_BYTE_ORDER, SWAPPED_BYTE_ORDER }; /** * NIFTI Data Types. Note that only a small subset are used * in GIFTI data files. */ class NiftiDataTypeEnum { public: /** NIFTI Data Types. Note that only a small subset are used * in GIFTI data files. */ enum Enum { /** invalid data type. */ NIFTI_TYPE_INVALID = 0, /** unsigned byte. */ NIFTI_TYPE_UINT8 = ::NIFTI_TYPE_UINT8, /** signed short. */ NIFTI_TYPE_INT16 = ::NIFTI_TYPE_INT16, /** signed int. */ NIFTI_TYPE_INT32 = ::NIFTI_TYPE_INT32, /** 32 bit float. */ NIFTI_TYPE_FLOAT32 = ::NIFTI_TYPE_FLOAT32, /** 64 bit complex = 2 32 bit floats. */ NIFTI_TYPE_COMPLEX64 = ::NIFTI_TYPE_COMPLEX64, /** 64 bit float = double. */ NIFTI_TYPE_FLOAT64 = ::NIFTI_TYPE_FLOAT64, /** 3 8 bit bytes. */ NIFTI_TYPE_RGB24 = ::NIFTI_TYPE_RGB24, /** signed char. */ NIFTI_TYPE_INT8 = ::NIFTI_TYPE_INT8, /** unsigned short. */ NIFTI_TYPE_UINT16 = ::NIFTI_TYPE_UINT16, /** unsigned int. */ NIFTI_TYPE_UINT32 = ::NIFTI_TYPE_UINT32, /** signed long long. */ NIFTI_TYPE_INT64 = ::NIFTI_TYPE_INT64, /** unsigned long long. */ NIFTI_TYPE_UINT64 = ::NIFTI_TYPE_UINT64, /** 128 bit float = long double. */ NIFTI_TYPE_FLOAT128 = ::NIFTI_TYPE_FLOAT128, /** 128 bit complex = 2 64 bit floats. */ NIFTI_TYPE_COMPLEX128 = ::NIFTI_TYPE_COMPLEX128, /** 256 bit complex = 2 128 bit floats */ NIFTI_TYPE_COMPLEX256 = ::NIFTI_TYPE_COMPLEX256 }; ~NiftiDataTypeEnum(); private: NiftiDataTypeEnum(); NiftiDataTypeEnum(Enum e, const AString& name, const int32_t integerCode); static const NiftiDataTypeEnum* findData(Enum e); static std::vector dataTypes; static void createDataTypes(); static bool dataTypesCreatedFlag; Enum e; AString name; int32_t integerCode; public: static AString toName(Enum e); static Enum fromName(const AString& s, bool* isValidOut); static int32_t toIntegerCode(Enum e); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); }; /** * NIFTI Intent codes and related parameters. */ class NiftiIntentEnum { public: /** NIFTI Intent codes and related parameters. */ enum Enum { /** */ NIFTI_INTENT_NONE = ::NIFTI_INTENT_NONE, /** */ NIFTI_INTENT_CORREL = ::NIFTI_INTENT_CORREL, /** */ NIFTI_INTENT_TTEST = ::NIFTI_INTENT_FTEST, /** */ NIFTI_INTENT_FTEST = ::NIFTI_INTENT_FTEST, /** */ NIFTI_INTENT_ZSCORE = ::NIFTI_INTENT_ZSCORE, /** */ NIFTI_INTENT_CHISQ = ::NIFTI_INTENT_CHISQ, /** */ NIFTI_INTENT_BETA = ::NIFTI_INTENT_BETA, /** */ NIFTI_INTENT_BINOM = ::NIFTI_INTENT_BINOM, /** */ NIFTI_INTENT_GAMMA = ::NIFTI_INTENT_GAMMA, /** */ NIFTI_INTENT_POISSON = ::NIFTI_INTENT_POISSON, /** */ NIFTI_INTENT_NORMAL = ::NIFTI_INTENT_NORMAL, /** */ NIFTI_INTENT_FTEST_NONC = ::NIFTI_INTENT_FTEST_NONC, /** */ NIFTI_INTENT_CHISQ_NONC = ::NIFTI_INTENT_CHISQ_NONC, /** */ NIFTI_INTENT_LOGISTIC = ::NIFTI_INTENT_LOGISTIC, /** */ NIFTI_INTENT_LAPLACE = ::NIFTI_INTENT_LAPLACE, /** */ NIFTI_INTENT_UNIFORM = ::NIFTI_INTENT_UNIFORM, /** */ NIFTI_INTENT_TTEST_NONC = ::NIFTI_INTENT_TTEST_NONC, /** */ NIFTI_INTENT_WEIBULL = ::NIFTI_INTENT_WEIBULL, /** */ NIFTI_INTENT_CHI = ::NIFTI_INTENT_CHI, /** */ NIFTI_INTENT_INVGAUSS = ::NIFTI_INTENT_INVGAUSS, /** */ NIFTI_INTENT_EXTVAL = ::NIFTI_INTENT_EXTVAL, /** */ NIFTI_INTENT_PVAL = ::NIFTI_INTENT_PVAL, /** */ NIFTI_INTENT_LOGPVAL = ::NIFTI_INTENT_LOGPVAL, /** */ NIFTI_INTENT_LOG10PVAL = ::NIFTI_INTENT_LOG10PVAL, /** */ NIFTI_INTENT_ESTIMATE = ::NIFTI_INTENT_ESTIMATE, /** */ NIFTI_INTENT_LABEL = ::NIFTI_INTENT_LABEL, /** */ NIFTI_INTENT_NEURONAME = ::NIFTI_INTENT_NEURONAME, /** */ NIFTI_INTENT_GENMATRIX = ::NIFTI_INTENT_GENMATRIX, /** */ NIFTI_INTENT_SYMMATRIX = ::NIFTI_INTENT_SYMMATRIX, /** */ NIFTI_INTENT_DISPVECT = ::NIFTI_INTENT_DISPVECT, /** */ NIFTI_INTENT_VECTOR = ::NIFTI_INTENT_VECTOR, /** */ NIFTI_INTENT_POINTSET = ::NIFTI_INTENT_POINTSET, /** */ NIFTI_INTENT_TRIANGLE = ::NIFTI_INTENT_TRIANGLE, /** */ NIFTI_INTENT_QUATERNION = ::NIFTI_INTENT_QUATERNION, /** */ NIFTI_INTENT_DIMLESS = ::NIFTI_INTENT_DIMLESS, /** */ NIFTI_INTENT_TIME_SERIES = 2001, /** */ NIFTI_INTENT_NODE_INDEX = 2002, /** */ NIFTI_INTENT_RGB_VECTOR = 2003, /** */ NIFTI_INTENT_RGBA_VECTOR = 2004, /** */ NIFTI_INTENT_SHAPE = 2005, /** */ NIFTI_INTENT_CARET_DEFORMATION_NODE_INDICES = 25000, /** */ NIFTI_INTENT_CARET_DEFORMATION_NODE_AREAS = 25001, /** */ NIFTI_INTENT_CONNECTIVITY_DENSE = ::NIFTI_INTENT_CONNECTIVITY_DENSE, /** */ NIFTI_INTENT_CONNECTIVITY_DENSE_TIME = ::NIFTI_INTENT_CONNECTIVITY_DENSE_TIME, /** */ NIFTI_INTENT_CONNECTIVITY_PARCELLATED = ::NIFTI_INTENT_CONNECTIVITY_PARCELLATED, /** */ NIFTI_INTENT_CONNECTIVITY_PARCELLATED_TIME = ::NIFTI_INTENT_CONNECTIVITY_PARCELLATED_TIME, /** */ NIFTI_INTENT_CONNECTIVITY_TRAJECTORY = ::NIFTI_INTENT_CONNECTIVITY_DENSE_TRAJECTORY }; ~NiftiIntentEnum(); private: NiftiIntentEnum(const Enum e, const AString& enumName, const int32_t integerCode, const AString& name, const AString& p1Name, const AString& p2Name, const AString& p3Name); //NiftiIntentEnum(); Enum e; AString enumName; int32_t integerCode; AString name; AString p1Name; AString p2Name; AString p3Name; static void initializeIntents(); static const NiftiIntentEnum* findData(Enum e); static std::vector intents; static bool intentsCreatedFlag; public: static AString toName(Enum e); static AString toNameP1(Enum e); static AString toNameP2(Enum e); static AString toNameP3(Enum e); static Enum fromName(const AString& s, bool* isValidOut); static int32_t toIntegerCode(Enum e); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); }; /** * NIFTI Spacing Units */ class NiftiSpacingUnitsEnum { public: /** NIFTI Spacing Units */ enum Enum { /** */ NIFTI_UNITS_UNKNOWN = ::NIFTI_UNITS_UNKNOWN, /** */ NIFTI_UNITS_METER = ::NIFTI_UNITS_METER, /** */ NIFTI_UNITS_MM = ::NIFTI_UNITS_MM, /** */ NIFTI_UNITS_MICRON = ::NIFTI_UNITS_MICRON }; ~NiftiSpacingUnitsEnum(); private: NiftiSpacingUnitsEnum(Enum e, const int32_t integerCode, const AString& name); static const NiftiSpacingUnitsEnum* findData(Enum e); static std::vector spacingUnits; static void initializeSpacingUnits(); static bool initializedFlag; Enum e; int32_t integerCode; AString name; public: static AString toName(Enum e); static Enum fromName(const AString& s, bool* isValidOut); static int32_t toIntegerCode(Enum e); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); }; /** * NIFTI Time Units */ class NiftiTimeUnitsEnum { public: /** NIFTI Time Units */ enum Enum { /** Unknown */ NIFTI_UNITS_UNKNOWN = ::NIFTI_UNITS_UNKNOWN, /** Seconds */ NIFTI_UNITS_SEC = ::NIFTI_UNITS_SEC, /** Milliseconds */ NIFTI_UNITS_MSEC = ::NIFTI_UNITS_MSEC, /** Microseconds */ NIFTI_UNITS_USEC = ::NIFTI_UNITS_USEC, /** Hertz */ NIFTI_UNITS_HZ = ::NIFTI_UNITS_HZ, /** Parts Per Million */ NIFTI_UNITS_PPM = ::NIFTI_UNITS_PPM }; ~NiftiTimeUnitsEnum(); static AString toName(Enum e); static Enum fromName(const AString& s, bool* isValidOut); static int32_t toIntegerCode(Enum e); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static AString toGuiName(Enum e); private: NiftiTimeUnitsEnum(const Enum e, const int32_t integerCode, const AString& name, const AString& guiName); static const NiftiTimeUnitsEnum* findData(const Enum e); static std::vector enumData; static void initializeTimeUnits(); static bool initializedFlag; Enum e; int32_t integerCode; AString name; AString guiName; }; /** * NIFTI Transform. */ class NiftiTransformEnum { public: /** NIFTI Transform. */ enum Enum { /** Arbitrary Coordinates */ NIFTI_XFORM_UNKNOWN = ::NIFTI_XFORM_UNKNOWN, /** Scanner-base anatomical coordinates */ NIFTI_XFORM_SCANNER_ANAT = ::NIFTI_XFORM_SCANNER_ANAT, /** Coordinates aligned to another file's or anatomial "truth" */ NIFTI_XFORM_ALIGNED_ANAT = ::NIFTI_XFORM_ALIGNED_ANAT, /** Coordinates aligned to Talairach-Tournoux Atlas: (0,0,0) = Anterior Commissure */ NIFTI_XFORM_TALAIRACH = ::NIFTI_XFORM_TALAIRACH, /** MNI 152 Normalize Coordinates */ NIFTI_XFORM_MNI_152 = ::NIFTI_XFORM_MNI_152 }; ~NiftiTransformEnum(); static AString toName(Enum e); static Enum fromName(const AString& s, bool* isValidOut); static int32_t toIntegerCode(Enum e); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); private: NiftiTransformEnum(const Enum e, const int32_t integerCode, const AString& name); static std::vector enumData; static const NiftiTransformEnum* findData(const Enum e); static void initialize(); static bool initializedFlag; Enum e; int32_t integerCode; AString name; }; /** * The NIFTI version */ class NiftiVersionEnum { public: /** The NIFTI version */ enum Enum { /** NIFTI-1 */ NIFTI_VERSION_1, /** NIFTI-2 */ NIFTI_VERSION_2 }; ~NiftiVersionEnum(); static AString toName(Enum e); static Enum fromName(const AString& s, bool* isValidOut); static int32_t toIntegerCode(Enum e); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); private: NiftiVersionEnum(const Enum e, const int32_t integerCode, const AString& name); static std::vector enumData; static void initialize(); static bool initializedFlag; Enum e; int32_t integerCode; AString name; static const NiftiVersionEnum* findData(const Enum e); }; #ifdef __NIFTI_ENUMS_DECLARE__ std::vector NiftiDataTypeEnum::dataTypes; bool NiftiDataTypeEnum::dataTypesCreatedFlag = false; std::vector NiftiIntentEnum::intents; bool NiftiIntentEnum::intentsCreatedFlag = false; std::vector NiftiSpacingUnitsEnum::spacingUnits; bool NiftiSpacingUnitsEnum::initializedFlag = false; std::vector NiftiTimeUnitsEnum::enumData; bool NiftiTimeUnitsEnum::initializedFlag = false; std::vector NiftiTransformEnum::enumData; bool NiftiTransformEnum::initializedFlag = false; std::vector NiftiVersionEnum::enumData; bool NiftiVersionEnum::initializedFlag = false; #endif // __NIFTI_ENUMS_DECLARE__ } // namespace #endif // __NIFTI_ENUMS_H workbench-1.1.1/src/FilesBase/VolumeBase.cxx000066400000000000000000000373061255417355300207310ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "VolumeBase.h" #include "DataFileException.h" #include "FloatMatrix.h" #include "GiftiLabelTable.h" #include "GiftiMetaData.h" #include "GiftiXmlElements.h" #include "Palette.h" #include "PaletteColorMapping.h" #include "Vector3D.h" #include using namespace caret; using namespace std; AbstractHeader::~AbstractHeader() { } void VolumeBase::reinitialize(const vector& dimensionsIn, const vector >& indexToSpace, const int64_t numComponents) { CaretAssert(numComponents > 0); clear(); int numDims = (int)dimensionsIn.size(); if (numDims < 3) { throw DataFileException("volume files must have 3 or more dimensions"); } m_origDims = dimensionsIn;//save the original dimensions int64_t storeDims[5]; storeDims[3] = 1; for (int i = 0; i < numDims; ++i) { if (i > 2) { storeDims[3] *= dimensionsIn[i]; } else { storeDims[i] = dimensionsIn[i]; } } m_volSpace.setSpace(storeDims, indexToSpace); if (storeDims[0] == 1 && storeDims[1] == 1 && storeDims[2] == 1 && storeDims[3] > 10000) {//slight hack, to detect if a cifti file is loaded as a volume file, because many 1x1x1 frames could use a surprisingly large amount of memory (metadata, palette, etc) throw DataFileException("this file doesn't appear to be a volume file"); } storeDims[4] = numComponents; m_storage.reinitialize(storeDims); } void VolumeBase::addSubvolumes(const int64_t& numToAdd) { CaretAssert(numToAdd > 0); vector olddims = getDimensions();//use the already flattened dimensions to start, as the non-spatial dimensions must be flattened to add an arbitrary number of maps CaretAssert(olddims[3] > 0);//can't add volumes when we have no dimensions, stop the debugger here if (olddims[3] < 1) { throw DataFileException("cannot call addSubvolumes on an uninitialized VolumeFile");//release shouldn't allow it either } vector newdims = olddims; newdims[3] += numToAdd;//add to the flattened non-spatial dimensions VolumeStorage newStorage(newdims.data()); newdims.resize(4);//drop the number of components from the dimensions array m_origDims = newdims;//and reset our original dimensions for (int64_t c = 0; c < olddims[4]; ++c) { for (int64_t b = 0; b < olddims[3]; ++b) { newStorage.setFrame(m_storage.getFrame(b, c), b, c); } } m_storage.swap(newStorage); setModified();//NOTE: will invalidate splines, can be made more efficient for certain cases when merging Base and File } void VolumeBase::setVolumeSpace(const vector >& indexToSpace) { m_volSpace.setSpace(getDimensionsPtr(), indexToSpace); setModified(); } VolumeBase::VolumeBase() { m_origDims.push_back(0);//give original dimensions 3 elements, just because m_origDims.push_back(0); m_origDims.push_back(0); m_ModifiedFlag = false; } VolumeBase::VolumeBase(const vector& dimensionsIn, const vector >& indexToSpace, const int64_t numComponents) { reinitialize(dimensionsIn, indexToSpace, numComponents); m_ModifiedFlag = true; } void VolumeBase::getOrientAndSpacingForPlumb(VolumeSpace::OrientTypes* orientOut, float* spacingOut, float* centerOut) const { m_volSpace.getOrientAndSpacingForPlumb(orientOut, spacingOut, centerOut); } void VolumeBase::getOrientation(VolumeSpace::OrientTypes orientOut[3]) const { m_volSpace.getOrientation(orientOut); } void VolumeBase::reorient(const VolumeSpace::OrientTypes newOrient[3]) { VolumeSpace::OrientTypes curOrient[3]; getOrientation(curOrient); int curReverse[3];//for each spatial axis, which index currently goes that direction bool curReverseNeg[3]; int doSomething = false;//check whether newOrient is any different for (int i = 0; i < 3; ++i) { if (curOrient[i] != newOrient[i]) doSomething = true; curReverse[curOrient[i] & 3] = i;//int values of the enum are crafted to make this work curReverseNeg[curOrient[i] & 3] = ((curOrient[i] & 4) != 0); } if (!doSomething) return; bool flip[3]; int fetchFrom[3]; for (int i = 0; i < 3; ++i) { flip[i] = curReverseNeg[newOrient[i] & 3] != ((newOrient[i] & 4) != 0); fetchFrom[i] = curReverse[newOrient[i] & 3]; } const int64_t* dims = getDimensionsPtr(); int64_t rowSize = dims[0]; int64_t sliceSize = rowSize * dims[1]; int64_t frameSize = sliceSize * dims[2]; vector scratchFrame(frameSize); int64_t newDims[5] = {dims[fetchFrom[0]], dims[fetchFrom[1]], dims[fetchFrom[2]], dims[3], dims[4]}; VolumeStorage newStorage(newDims); for (int c = 0; c < dims[4]; ++c) { for (int b = 0; b < dims[3]; ++b) { const float* oldFrame = m_storage.getFrame(b, c); int64_t newIndices[3], oldIndices[3]; for (newIndices[2] = 0; newIndices[2] < newDims[2]; ++newIndices[2]) { if (flip[2]) { oldIndices[fetchFrom[2]] = newDims[2] - newIndices[2] - 1; } else { oldIndices[fetchFrom[2]] = newIndices[2]; } for (newIndices[1] = 0; newIndices[1] < newDims[1]; ++newIndices[1]) { if (flip[1]) { oldIndices[fetchFrom[1]] = newDims[1] - newIndices[1] - 1; } else { oldIndices[fetchFrom[1]] = newIndices[1]; } for (newIndices[0] = 0; newIndices[0] < newDims[0]; ++newIndices[0]) { if (flip[0]) { oldIndices[fetchFrom[0]] = newDims[0] - newIndices[0] - 1; } else { oldIndices[fetchFrom[0]] = newIndices[0]; } scratchFrame[newStorage.getIndex(newIndices, 0, 0)] = oldFrame[getIndex(oldIndices)]; } } } newStorage.setFrame(scratchFrame.data(), b, c); } } const vector >& oldSform = m_volSpace.getSform(); vector > indexToSpace = oldSform;//reorder and flip the sform - this might belong in VolumeSpace for (int i = 0; i < 3; ++i) { for (int j = 0; j < 3; ++j) { if (flip[j]) { indexToSpace[i][j] = -oldSform[i][fetchFrom[j]]; indexToSpace[i][3] += oldSform[i][fetchFrom[j]] * (newDims[j] - 1); } else { indexToSpace[i][j] = oldSform[i][fetchFrom[j]]; } } }//and now generate the inverse for spaceToIndex m_volSpace.setSpace(newDims, indexToSpace); m_origDims[0] = newDims[0];//update m_origDims too m_origDims[1] = newDims[1]; m_origDims[2] = newDims[2]; m_storage.swap(newStorage); setModified(); } void VolumeBase::enclosingVoxel(const float* coordIn, int64_t* indexOut) const { enclosingVoxel(coordIn[0], coordIn[1], coordIn[2], indexOut[0], indexOut[1], indexOut[2]); } void VolumeBase::enclosingVoxel(const float& coordIn1, const float& coordIn2, const float& coordIn3, int64_t* indexOut) const { enclosingVoxel(coordIn1, coordIn2, coordIn3, indexOut[0], indexOut[1], indexOut[2]); } void VolumeBase::enclosingVoxel(const float* coordIn, int64_t& indexOut1, int64_t& indexOut2, int64_t& indexOut3) const { enclosingVoxel(coordIn[0], coordIn[1], coordIn[2], indexOut1, indexOut2, indexOut3); } void VolumeBase::enclosingVoxel(const float& coordIn1, const float& coordIn2, const float& coordIn3, int64_t& indexOut1, int64_t& indexOut2, int64_t& indexOut3) const { float tempInd1, tempInd2, tempInd3; spaceToIndex(coordIn1, coordIn2, coordIn3, tempInd1, tempInd2, tempInd3); indexOut1 = (int64_t)floor(0.5f + tempInd1); indexOut2 = (int64_t)floor(0.5f + tempInd2); indexOut3 = (int64_t)floor(0.5f + tempInd3); } int64_t VolumeBase::getBrickIndexFromNonSpatialIndexes(const vector& extraInds) const { CaretAssert(extraInds.size() == m_origDims.size() - 3); int extraDims = (int)extraInds.size(); if (extraDims == 0) return 0; CaretAssert(extraInds[extraDims - 1] >= 0 && extraInds[extraDims - 1] < m_origDims[extraDims + 2]); int64_t ret = extraInds[extraDims - 1]; for (int i = extraDims - 2; i >= 0; --i)//yes, its supposed to loop starting with the second highest dimension { CaretAssert(extraInds[i] >= 0 && extraInds[i] < m_origDims[i + 3]); ret = ret * m_origDims[i + 3] + extraInds[i];//factored polynomial form } CaretAssert(ret < getDimensionsPtr()[3]);//otherwise, dimensions[3] and m_origDims don't match return ret; } vector VolumeBase::getNonSpatialIndexesFromBrickIndex(const int64_t& brickIndex) const { CaretAssert(brickIndex >= 0 && brickIndex < getDimensionsPtr()[3]); vector ret; int extraDims = (int)m_origDims.size() - 3; if (extraDims <= 0) return ret;//empty vector if there are no extra-spatial dimensions, so we don't call resize(0), even though it should be safe ret.resize(extraDims); int64_t myRemaining = brickIndex, temp; for (int i = 0; i < extraDims; ++i) { temp = myRemaining % m_origDims[i + 3];//modulus myRemaining = (myRemaining - temp) / m_origDims[i + 3];//subtract the remainder even though int divide should truncate correctly, just to make it obvious ret[i] = temp; } CaretAssert(myRemaining == 0);//otherwise, m_dimensions[3] and m_origDims don't match return ret; } void VolumeBase::indexToSpace(const int64_t* indexIn, float* coordOut) const { indexToSpace(indexIn[0], indexIn[1], indexIn[2], coordOut[0], coordOut[1], coordOut[2]); } void VolumeBase::indexToSpace(const int64_t* indexIn, float& coordOut1, float& coordOut2, float& coordOut3) const { indexToSpace(indexIn[0], indexIn[1], indexIn[2], coordOut1, coordOut2, coordOut3); } void VolumeBase::indexToSpace(const float* indexIn, float* coordOut) const { indexToSpace(indexIn[0], indexIn[1], indexIn[2], coordOut[0], coordOut[1], coordOut[2]); } void VolumeBase::indexToSpace(const float& indexIn1, const float& indexIn2, const float& indexIn3, float* coordOut) const { indexToSpace(indexIn1, indexIn2, indexIn3, coordOut[0], coordOut[1], coordOut[2]); } void VolumeBase::indexToSpace(const float* indexIn, float& coordOut1, float& coordOut2, float& coordOut3) const { indexToSpace(indexIn[0], indexIn[1], indexIn[2], coordOut1, coordOut2, coordOut3); } void VolumeBase::indexToSpace(const float& indexIn1, const float& indexIn2, const float& indexIn3, float& coordOut1, float& coordOut2, float& coordOut3) const { m_volSpace.indexToSpace(indexIn1, indexIn2, indexIn3, coordOut1, coordOut2, coordOut3); } bool VolumeBase::isPlumb() const { return m_volSpace.isPlumb(); } void VolumeBase::spaceToIndex(const float* coordIn, float* indexOut) const { spaceToIndex(coordIn[0], coordIn[1], coordIn[2], indexOut[0], indexOut[1], indexOut[2]); } void VolumeBase::spaceToIndex(const float& coordIn1, const float& coordIn2, const float& coordIn3, float* indexOut) const { spaceToIndex(coordIn1, coordIn2, coordIn3, indexOut[0], indexOut[1], indexOut[2]); } void VolumeBase::spaceToIndex(const float* coordIn, float& indexOut1, float& indexOut2, float& indexOut3) const { spaceToIndex(coordIn[0], coordIn[1], coordIn[2], indexOut1, indexOut2, indexOut3); } void VolumeBase::spaceToIndex(const float& coordIn1, const float& coordIn2, const float& coordIn3, float& indexOut1, float& indexOut2, float& indexOut3) const { m_volSpace.spaceToIndex(coordIn1, coordIn2, coordIn3, indexOut1, indexOut2, indexOut3); } void VolumeBase::clear() { m_storage.clear(); m_origDims.clear(); m_origDims.resize(3, 0);//give original dimensions 3 elements, just because m_header.grabNew(NULL); } VolumeBase::~VolumeBase() { } /** * Is the file empty (contains no data)? * * @return * true if the file is empty, else false. */ bool VolumeBase::isEmpty() const { return (getDimensionsPtr()[0] <= 0); } VolumeBase::VolumeStorage::VolumeStorage() { for (int i = 0; i < 5; ++i) { m_dimensions[i] = 0; m_mult[i] = 0; } } void VolumeBase::VolumeStorage::reinitialize(int64_t dims[5]) { for (int i = 0; i < 5; ++i) { CaretAssert(dims[i] > 0);//stop the debugger in the right place if (dims[i] < 1) throw DataFileException("VolumeStorage dimensions must be positive");//release shouldn't allow it either, though m_dimensions[i] = dims[i]; } m_mult[0] = m_dimensions[0]; for (int i = 1; i < 5; ++i) { m_mult[i] = m_mult[i - 1] * m_dimensions[i]; } m_data.resize(m_mult[4]); } VolumeBase::VolumeStorage::VolumeStorage(int64_t dims[5]) { reinitialize(dims); } const float* VolumeBase::VolumeStorage::getFrame(const int64_t brickIndex, const int64_t component) const { return m_data.data() + brickIndex * m_mult[2] + component * m_mult[3];//NOTE: do not use [4] } void VolumeBase::VolumeStorage::setFrame(const float* frameIn, const int64_t brickIndex, const int64_t component) { int64_t start = brickIndex * m_mult[2] + component * m_mult[3]; for (int64_t i = 0; i < m_mult[2]; ++i) { m_data[i + start] = frameIn[i]; } } void VolumeBase::VolumeStorage::setValueAllVoxels(const float value) { for (int64_t i = 0; i < m_mult[4]; ++i) { m_data[i] = value; } } void VolumeBase::VolumeStorage::swap(VolumeStorage& rhs) { m_data.swap(rhs.m_data); for (int i = 0; i < 5; ++i) { std::swap(m_dimensions[i], rhs.m_dimensions[i]); std::swap(m_mult[i], rhs.m_mult[i]); } } void VolumeBase::VolumeStorage::getDimensions(vector& dimOut) const { dimOut.resize(5); for (int i = 0; i < 5; ++i) { dimOut[i] = m_dimensions[i]; } } void VolumeBase::VolumeStorage::getDimensions(int64_t& dimOut1, int64_t& dimOut2, int64_t& dimOut3, int64_t& dimTimeOut, int64_t& numComponents) const { dimOut1 = m_dimensions[0]; dimOut2 = m_dimensions[1]; dimOut3 = m_dimensions[2]; dimTimeOut = m_dimensions[3]; numComponents = m_dimensions[4]; } vector VolumeBase::VolumeStorage::getDimensions() const { vector ret; getDimensions(ret); return ret; } void VolumeBase::VolumeStorage::clear() { m_data.clear(); for (int i = 0; i < 5; ++i) { m_dimensions[i] = 0; m_mult[i] = 0; } } /** * @return Is this instance modified? */ bool VolumeBase::isModifiedVolumeBase() const { if (m_ModifiedFlag) { return true; } return false; } /** * Clear this instance's modified status */ void VolumeBase::clearModifiedVolumeBase() { m_ModifiedFlag = false; } /** * Set this instance's status to modified. */ void VolumeBase::setModified() { m_ModifiedFlag = true; } workbench-1.1.1/src/FilesBase/VolumeBase.h000066400000000000000000000447101255417355300203530ustar00rootroot00000000000000 #ifndef __VOLUME_BASE_H__ #define __VOLUME_BASE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "stdint.h" #include #include "CaretAssert.h" #include "CaretPointer.h" #include "VolumeMappableInterface.h" #include "VolumeSpace.h" namespace caret { struct AbstractHeader { enum HeaderType { NIFTI }; virtual HeaderType getType() const = 0; virtual AbstractHeader* clone() const = 0; virtual ~AbstractHeader(); }; class VolumeBase : public VolumeMappableInterface { class VolumeStorage { std::vector m_data; int64_t m_dimensions[5];//store internally as 4d+component int64_t m_mult[5];//precalculated multipliers for getIndex/getValue/setValue - NOTE: [0] is for index[1], [4] is the entire size of the data VolumeStorage(const VolumeStorage& rhs);//deny copy, assignment for now VolumeStorage& operator=(const VolumeStorage& rhs); public: VolumeStorage(); VolumeStorage(int64_t dims[5]); void reinitialize(int64_t dims[5]); void clear(); void getDimensions(std::vector& dimOut) const;//NOTE: always returns a vector of 5 elements void getDimensions(int64_t& dimOut1, int64_t& dimOut2, int64_t& dimOut3, int64_t& dimTimeOut, int64_t& numComponents) const; std::vector getDimensions() const; const int64_t* getDimensionsPtr() const { return m_dimensions; } inline const int64_t& getNumberOfComponents() const { return m_dimensions[4]; } void swap(VolumeStorage& rhs); ///get a value at three indexes and optionally timepoint inline const float& getValue(const int64_t& indexIn1, const int64_t& indexIn2, const int64_t& indexIn3, const int64_t brickIndex, const int64_t component) const { CaretAssert(indexValid(indexIn1, indexIn2, indexIn3, brickIndex, component));//assert so release version isn't slowed by checking return m_data[getIndex(indexIn1, indexIn2, indexIn3, brickIndex, component)]; } inline const float& getValue(const int64_t indexIn[3], const int64_t brickIndex, const int64_t component) const { return getValue(indexIn[0], indexIn[1], indexIn[2], brickIndex, component); } ///gets index into data array for three indexes plus time index inline int64_t getIndex(const int64_t& indexIn1, const int64_t& indexIn2, const int64_t& indexIn3, const int64_t brickIndex, const int64_t component) const { CaretAssert(indexValid(indexIn1, indexIn2, indexIn3, brickIndex, component)); return indexIn1 + m_mult[0] * indexIn2 + m_mult[1] * indexIn3 + m_mult[2] * brickIndex + m_mult[3] * component; } inline int64_t getIndex(const int64_t indexIn[3], const int64_t brickIndex, const int64_t component) const { return getIndex(indexIn[0], indexIn[1], indexIn[2], brickIndex, component); } ///checks if an index is within array dimensions inline bool indexValid(const int64_t& indexIn1, const int64_t& indexIn2, const int64_t& indexIn3, const int64_t brickIndex, const int64_t component) const {//inlined so that getValue and setValue can get optimized out entirely if (indexIn1 < 0 || indexIn1 >= m_dimensions[0]) return false; if (indexIn2 < 0 || indexIn2 >= m_dimensions[1]) return false; if (indexIn3 < 0 || indexIn3 >= m_dimensions[2]) return false; if (brickIndex < 0 || brickIndex >= m_dimensions[3]) return false; if (component < 0 || component >= m_dimensions[4]) return false; return true; } inline bool indexValid(const int64_t indexIn[3], const int64_t brickIndex, const int64_t component) const { return indexValid(indexIn[0], indexIn[1], indexIn[2], brickIndex, component); } ///set a value at an index triplet and optionally timepoint inline void setValue(const float& valueIn, const int64_t& indexIn1, const int64_t& indexIn2, const int64_t& indexIn3, const int64_t brickIndex, const int64_t component) { CaretAssert(indexValid(indexIn1, indexIn2, indexIn3, brickIndex, component));//assert so release version isn't slowed by checking m_data[getIndex(indexIn1, indexIn2, indexIn3, brickIndex, component)] = valueIn; } inline void setValue(const float& valueIn, const int64_t indexIn[3], const int64_t brickIndex, const int64_t component) { setValue(valueIn, indexIn[0], indexIn[1], indexIn[2], brickIndex, component); } /// set every voxel to the given value void setValueAllVoxels(const float value); ///get a frame (const) const float* getFrame(const int64_t brickIndex = 0, const int64_t component = 0) const; ///set a frame void setFrame(const float* frameIn, const int64_t brickIndex = 0, const int64_t component = 0); }; VolumeStorage m_storage; VolumeSpace m_volSpace; std::vector m_origDims;//keep track of the original dimensions bool m_ModifiedFlag; protected: VolumeBase(); VolumeBase(const std::vector& dimensionsIn, const std::vector >& indexToSpace, const int64_t numComponents = 1); ///recreates the volume file storage with new size and spacing void reinitialize(const std::vector& dimensionsIn, const std::vector >& indexToSpace, const int64_t numComponents = 1); void addSubvolumes(const int64_t& numToAdd); public: void clear(); virtual ~VolumeBase(); ///there isn't much VolumeFile can do to restrict access to the header, so just have it public CaretPointer m_header; ///get the spacing info inline const std::vector >& getSform() const { return m_volSpace.getSform(); } void setVolumeSpace(const std::vector >& indexToSpace); ///get the originally specified dimensions vector inline const std::vector& getOriginalDimensions() const { return m_origDims; } inline const int64_t& getNumberOfComponents() const { return m_storage.getNumberOfComponents(); } ///translates extraspatial indices into a (flat) brick index int64_t getBrickIndexFromNonSpatialIndexes(const std::vector& extraInds) const; ///translates a (flat) brick index into the original extraspatial indices std::vector getNonSpatialIndexesFromBrickIndex(const int64_t& brickIndex) const; ///returns true if volume space is not skew, and each axis and index is separate bool isPlumb() const; ///returns orientation, spacing, and center (spacing/center can be negative, spacing/center is LPI rearranged to ijk (first dimension uses first element), will assert false if isOblique is true) void getOrientAndSpacingForPlumb(VolumeSpace::OrientTypes* orientOut, float* spacingOut, float* centerOut) const; ///get just orientation, even for non-plumb volumes void getOrientation(VolumeSpace::OrientTypes orientOut[3]) const; ///reorient this volume void reorient(const VolumeSpace::OrientTypes newOrient[3]); //not to worry, simple passthrough convenience functions like these get partially optimized to the main one by even -O1, and completely optimized together by -O2 or -O3 ///returns coordinate triplet of an index triplet void indexToSpace(const int64_t* indexIn, float* coordOut) const; ///returns three coordinates of an index triplet void indexToSpace(const int64_t* indexIn, float& coordOut1, float& coordOut2, float& coordOut3) const; ///returns coordinate triplet of a floating point index triplet void indexToSpace(const float* indexIn, float* coordOut) const; ///returns coordinate triplet of three floating point indexes void indexToSpace(const float& indexIn1, const float& indexIn2, const float& indexIn3, float* coordOut) const; ///returns three coordinates of a floating point index triplet void indexToSpace(const float* indexIn, float& coordOut1, float& coordOut2, float& coordOut3) const; ///returns three coordinates of three floating point indexes void indexToSpace(const float& indexIn1, const float& indexIn2, const float& indexIn3, float& coordOut1, float& coordOut2, float& coordOut3) const; ///returns floating point index triplet of a given coordinate triplet void spaceToIndex(const float* coordIn, float* indexOut) const; ///returns floating point index triplet of three given coordinates void spaceToIndex(const float& coordIn1, const float& coordIn2, const float& coordIn3, float* indexOut) const; ///returns three floating point indexes of a given coordinate triplet void spaceToIndex(const float* coordIn, float& indexOut1, float& indexOut2, float& indexOut3) const; ///returns three floating point indexes of three given coordinates void spaceToIndex(const float& coordIn1, const float& coordIn2, const float& coordIn3, float& indexOut1, float& indexOut2, float& indexOut3) const; ///returns integer index triplet of voxel whose center is closest to the coordinate triplet void enclosingVoxel(const float* coordIn, int64_t* indexOut) const; ///returns integer index triplet of voxel whose center is closest to the three coordinates void enclosingVoxel(const float& coordIn1, const float& coordIn2, const float& coordIn3, int64_t* indexOut) const; ///returns integer indexes of voxel whose center is closest to the coordinate triplet void enclosingVoxel(const float* coordIn, int64_t& indexOut1, int64_t& indexOut2, int64_t& indexOut3) const; ///returns integer indexes of voxel whose center is closest to the three coordinates void enclosingVoxel(const float& coordIn1, const float& coordIn2, const float& coordIn3, int64_t& indexOut1, int64_t& indexOut2, int64_t& indexOut3) const; inline const VolumeSpace& getVolumeSpace() const { return m_volSpace; } ///get a value at an index triplet and optionally timepoint inline const float& getValue(const int64_t* indexIn, const int64_t brickIndex = 0, const int64_t component = 0) const { return m_storage.getValue(indexIn[0], indexIn[1], indexIn[2], brickIndex, component); } ///get a value at three indexes and optionally timepoint inline const float& getValue(const int64_t& indexIn1, const int64_t& indexIn2, const int64_t& indexIn3, const int64_t brickIndex = 0, const int64_t component = 0) const { return m_storage.getValue(indexIn1, indexIn2, indexIn3, brickIndex, component); } /** * Get the value of the voxel containing the given coordinate. * * @param coordinateIn * The 3D coordinate * @param validOut * If not NULL, will indicate if the coordinate (and hence the * returned value) is valid. * @param mapIndex * Index of map. * @param component * Voxel component. * @return * Value of voxel containing the given coordinate. */ inline float getVoxelValue(const float* coordinateIn, bool* validOut = NULL, const int64_t mapIndex = 0, const int64_t component = 0) const { return getVoxelValue(coordinateIn[0], coordinateIn[1], coordinateIn[2], validOut, mapIndex, component); } /** * Get the value of the voxel containing the given coordinate. * * @param coordinateX * The X coordinate * @param coordinateY * The Y coordinate * @param coordinateZ * The Z coordinate * @param validOut * If not NULL, will indicate if the coordinate (and hence the * returned value) is valid. * @param mapIndex * Index of map. * @param component * Voxel component. * @return * Value of voxel containing the given coordinate. */ inline float getVoxelValue(const float coordinateX, const float coordinateY, const float coordinateZ, bool* validOut = NULL, const int64_t mapIndex = 0, const int64_t component = 0) const { if (validOut != NULL) { *validOut = false; } int64_t voxelI, voxelJ, voxelK; enclosingVoxel(coordinateX, coordinateY, coordinateZ, voxelI, voxelJ, voxelK); if (indexValid(voxelI, voxelJ, voxelK, mapIndex, component)) { if (validOut != NULL) { *validOut = true; } return getValue(voxelI, voxelJ, voxelK, mapIndex, component); } return 0.0; } ///get a frame (const) const float* getFrame(const int64_t brickIndex = 0, const int64_t component = 0) const { return m_storage.getFrame(brickIndex, component); } ///set a value at an index triplet and optionally timepoint inline void setValue(const float& valueIn, const int64_t* indexIn, const int64_t brickIndex = 0, const int64_t component = 0) { m_storage.setValue(valueIn, indexIn[0], indexIn[1], indexIn[2], brickIndex, component); setModified(); } ///set a value at an index triplet and optionally timepoint inline void setValue(const float& valueIn, const int64_t& indexIn1, const int64_t& indexIn2, const int64_t& indexIn3, const int64_t brickIndex = 0, const int64_t component = 0) { m_storage.setValue(valueIn, indexIn1, indexIn2, indexIn3, brickIndex, component); setModified(); } /// set every voxel to the given value void setValueAllVoxels(const float value) { m_storage.setValueAllVoxels(value); setModified(); } ///set a frame void setFrame(const float* frameIn, const int64_t brickIndex = 0, const int64_t component = 0) { m_storage.setFrame(frameIn, brickIndex, component); setModified(); } ///gets dimensions as a vector of 5 integers, 3 spatial, time, components void getDimensions(std::vector& dimOut) const { m_storage.getDimensions(dimOut); } ///gets dimensions void getDimensions(int64_t& dimOut1, int64_t& dimOut2, int64_t& dimOut3, int64_t& dimTimeOut, int64_t& numComponents) const { m_storage.getDimensions(dimOut1, dimOut2, dimOut3, dimTimeOut, numComponents); } ///gets dimensions std::vector getDimensions() const { return m_storage.getDimensions(); } const int64_t* getDimensionsPtr() const { return m_storage.getDimensionsPtr(); } ///gets index into data array for three indexes plus time index inline int64_t getIndex(const int64_t& indexIn1, const int64_t& indexIn2, const int64_t& indexIn3, const int64_t brickIndex = 0, const int64_t component = 0) const { return m_storage.getIndex(indexIn1, indexIn2, indexIn3, brickIndex, component); } inline int64_t getIndex(const int64_t* indexIn, const int64_t brickIndex = 0, const int64_t component = 0) const { return m_storage.getIndex(indexIn[0], indexIn[1], indexIn[2], brickIndex, component); } inline bool indexValid(const int64_t* indexIn, const int64_t brickIndex = 0, const int64_t component = 0) const { return m_storage.indexValid(indexIn[0], indexIn[1], indexIn[2], brickIndex, component); } ///checks if an index is within array dimensions inline bool indexValid(const int64_t& indexIn1, const int64_t& indexIn2, const int64_t& indexIn3, const int64_t brickIndex = 0, const int64_t component = 0) const {//inlined so that getValue and setValue can get optimized out entirely return m_storage.indexValid(indexIn1, indexIn2, indexIn3, brickIndex, component); } virtual void setModified();//virtual because we need the functions that change voxels in this class to call the setModified in VolumeFile if it really is a VolumeFile (which it always is) void clearModifiedVolumeBase(); bool isModifiedVolumeBase() const; bool isEmpty() const; }; } #endif //__VOLUME_BASE_H__ workbench-1.1.1/src/FilesBase/VolumeMappableInterface.cxx000066400000000000000000000066011255417355300234130ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __VOLUME_MAPPABLE_INTERFACE_DECLARE__ #include "VolumeMappableInterface.h" #undef __VOLUME_MAPPABLE_INTERFACE_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * Get the voxel spacing for each of the spatial dimensions. * * @param spacingOut1 * Spacing for the first dimension (typically X). * @param spacingOut2 * Spacing for the first dimension (typically Y). * @param spacingOut3 * Spacing for the first dimension (typically Z). */ void VolumeMappableInterface::getVoxelSpacing(float& spacingOut1, float& spacingOut2, float& spacingOut3) const { float originX, originY, originZ; float x1, y1, z1; indexToSpace(0, 0, 0, originX, originY, originZ); indexToSpace(1, 1, 1, x1, y1, z1); spacingOut1 = x1 - originX; spacingOut2 = y1 - originY; spacingOut3 = z1 - originZ; } /** * Does this volume have these spatial dimensions? * * @param dim1 * First dimension. * @param dim2 * Second dimension. * @param dim3 * Third dimension. * @return * True if this volume's spatial dimensions match the * given dimensions, else false. */ bool VolumeMappableInterface::matchesDimensions(const int64_t dim1, const int64_t dim2, const int64_t dim3) const { std::vector dims; getDimensions(dims); if (dims.size() >= 3){ if ((dims[0] == dim1) && (dims[1] == dim2) && (dims[2] == dim3)) { return true; } } return false; } /** * Adjust the given indices so that they are valid * in the range 0 to (volume dimension - 1) * * @param index1 * First index. * @param index2 * Second index. * @param index3 * Third index. */ void VolumeMappableInterface::limitIndicesToValidIndices(int64_t& index1, int64_t& index2, int64_t& index3) const { std::vector dims; getDimensions(dims); if (dims.size() >= 3) { if (index1 >= dims[0]) { index1 = dims[0] - 1; } if (index1 < 0) { index1 = 0; } if (index2 >= dims[1]) { index2 = dims[1] - 1; } if (index2 < 0) { index2 = 0; } if (index3 >= dims[2]) { index3 = dims[2] - 1; } if (index3 < 0) { index3 = 0; } } } workbench-1.1.1/src/FilesBase/VolumeMappableInterface.h000066400000000000000000000332621255417355300230430ustar00rootroot00000000000000#ifndef __VOLUME_MAPPABLE_INTERFACE_H__ #define __VOLUME_MAPPABLE_INTERFACE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "DisplayGroupEnum.h" #include "VolumeSliceViewPlaneEnum.h" namespace caret { class BoundingBox; class PaletteFile; /** * \class caret::VolumeMappableInterface * \brief Interface for data that is mapped to volumes * \ingroup FilesBase * * Defines an interface for data files that are drawn with voxel data. */ class VolumeMappableInterface { protected: VolumeMappableInterface() { } virtual ~VolumeMappableInterface() { } private: VolumeMappableInterface(const VolumeMappableInterface&); VolumeMappableInterface& operator=(const VolumeMappableInterface&); public: /** * Get the dimensions of the volume. * * @param dimOut1 * First dimension (i) out. * @param dimOut2 * Second dimension (j) out. * @param dimOut3 * Third dimension (k) out. * @param dimTimeOut * Time dimensions out (number of maps) * @param numComponents * Number of components per voxel. */ virtual void getDimensions(int64_t& dimOut1, int64_t& dimOut2, int64_t& dimOut3, int64_t& dimTimeOut, int64_t& numComponents) const = 0; /** * Get the dimensions of the volume. * * @param dimsOut * Will contain 5 elements: (0) X-dimension, (1) Y-dimension * (2) Z-dimension, (3) time, (4) components. */ virtual void getDimensions(std::vector& dimsOut) const = 0; bool matchesDimensions(const int64_t dim1, const int64_t dim2, const int64_t dim3) const; void limitIndicesToValidIndices(int64_t& index1, int64_t& index2, int64_t& index3) const; /** * @return The number of componenents per voxel. */ virtual const int64_t& getNumberOfComponents() const = 0; /** * Get the value of the voxel containing the given coordinate. * * @param coordinateIn * The 3D coordinate * @param validOut * If not NULL, will indicate if the coordinate (and hence the * returned value) is valid. * @param mapIndex * Index of map. * @param component * Voxel component. * @return * Value of voxel containing the given coordinate. */ virtual float getVoxelValue(const float* coordinateIn, bool* validOut = NULL, const int64_t mapIndex = 0, const int64_t component = 0) const = 0; /** * Get the value of the voxel containing the given coordinate. * * @param coordinateX * The X coordinate * @param coordinateY * The Y coordinate * @param coordinateZ * The Z coordinate * @param validOut * If not NULL, will indicate if the coordinate (and hence the * returned value) is valid. * @param mapIndex * Index of map. * @param component * Voxel component. * @return * Value of voxel containing the given coordinate. */ virtual float getVoxelValue(const float coordinateX, const float coordinateY, const float coordinateZ, bool* validOut = NULL, const int64_t mapIndex = 0, const int64_t component = 0) const = 0; /** * Convert an index to space (coordinates). * * @param indexIn1 * First dimension (i). * @param indexIn2 * Second dimension (j). * @param indexIn3 * Third dimension (k). * @param coordOut1 * Output first (x) coordinate. * @param coordOut2 * Output first (y) coordinate. * @param coordOut3 * Output first (z) coordinate. */ virtual void indexToSpace(const float& indexIn1, const float& indexIn2, const float& indexIn3, float& coordOut1, float& coordOut2, float& coordOut3) const = 0; /** * Convert an index to space (coordinates). * * @param indexIn1 * First dimension (i). * @param indexIn2 * Second dimension (j). * @param indexIn3 * Third dimension (k). * @param coordOut * Output XYZ coordinates. */ virtual void indexToSpace(const float& indexIn1, const float& indexIn2, const float& indexIn3, float* coordOut) const = 0; /** * Convert an index to space (coordinates). * * @param indexIn * IJK indices * @param coordOut * Output XYZ coordinates. */ virtual void indexToSpace(const int64_t* indexIn, float* coordOut) const = 0; /** * Convert a coordinate to indices. Note that output indices * MAY NOT BE WITHING THE VALID VOXEL DIMENSIONS. * * @param coordIn1 * First (x) input coordinate. * @param coordIn2 * Second (y) input coordinate. * @param coordIn3 * Third (z) input coordinate. * @param indexOut1 * First output index (i). * @param indexOut2 * First output index (j). * @param indexOut3 * First output index (k). */ virtual void enclosingVoxel(const float& coordIn1, const float& coordIn2, const float& coordIn3, int64_t& indexOut1, int64_t& indexOut2, int64_t& indexOut3) const = 0; /** * Determine in the given voxel indices are valid (within the volume). * * @param indexIn1 * First dimension (i). * @param indexIn2 * Second dimension (j). * @param indexIn3 * Third dimension (k). * @param coordOut1 * Output first (x) coordinate. * @param brickIndex * Time/map index (default 0). * @param component * Voxel component (default 0). */ virtual bool indexValid(const int64_t& indexIn1, const int64_t& indexIn2, const int64_t& indexIn3, const int64_t brickIndex = 0, const int64_t component = 0) const = 0; /** * Get a bounding box for the voxel coordinate ranges. * * @param boundingBoxOut * The output bounding box. */ virtual void getVoxelSpaceBoundingBox(BoundingBox& boundingBoxOut) const = 0; /** * Get the voxel spacing for each of the spatial dimensions. * * @param spacingOut1 * Spacing for the first dimension (typically X). * @param spacingOut2 * Spacing for the first dimension (typically Y). * @param spacingOut3 * Spacing for the first dimension (typically Z). */ void getVoxelSpacing(float& spacingOut1, float& spacingOut2, float& spacingOut3) const; /** * Get the voxel colors for a slice in the map. * * @param paletteFile * The palette file. * @param mapIndex * Index of the map. * @param slicePlane * The slice plane. * @param sliceIndex * Index of the slice. * @param displayGroup * The selected display group. * @param tabIndex * Index of selected tab. * @param rgbaOut * Output containing the rgba values (must have been allocated * by caller to sufficient count of elements in the slice). * @return * Number of voxels with alpha greater than zero */ virtual int64_t getVoxelColorsForSliceInMap(const PaletteFile* paletteFile, const int32_t mapIndex, const VolumeSliceViewPlaneEnum::Enum slicePlane, const int64_t sliceIndex, const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, uint8_t* rgbaOut) const = 0; /** * Get the voxel colors for a sub slice in the map. * * @param paletteFile * The palette file. * @param mapIndex * Index of the map. * @param slicePlane * The slice plane. * @param sliceIndex * Index of the slice. * @param firstCornerVoxelIndex * Indices of voxel for first corner of sub-slice (inclusive). * @param lastCornerVoxelIndex * Indices of voxel for last corner of sub-slice (inclusive). * @param voxelCountIJK * Voxel counts for each axis. * @param displayGroup * The selected display group. * @param tabIndex * Index of selected tab. * @param rgbaOut * Output containing the rgba values (must have been allocated * by caller to sufficient count of elements in the slice). * @return * Number of voxels with alpha greater than zero */ virtual int64_t getVoxelColorsForSubSliceInMap(const PaletteFile* paletteFile, const int32_t mapIndex, const VolumeSliceViewPlaneEnum::Enum slicePlane, const int64_t sliceIndex, const int64_t firstCornerVoxelIndex[3], const int64_t lastCornerVoxelIndex[3], const int64_t voxelCountIJK[3], const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, uint8_t* rgbaOut) const = 0; /** * Get the voxel coloring for the voxel at the given indices. * * @param paletteFile * The palette file. * @param indexIn1 * First dimension (i). * @param indexIn2 * Second dimension (j). * @param indexIn3 * Third dimension (k). * @param brickIndex * Time/map index. * @param displayGroup * The selected display group. * @param tabIndex * Index of selected tab. * @param rgbaOut * Output containing RGBA values for voxel at the given indices. */ virtual void getVoxelColorInMap(const PaletteFile* paletteFile, const int64_t indexIn1, const int64_t indexIn2, const int64_t indexIn3, const int64_t brickIndex, const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, uint8_t rgbaOut[4]) const = 0; }; #ifdef __VOLUME_MAPPABLE_INTERFACE_DECLARE__ // #endif // __VOLUME_MAPPABLE_INTERFACE_DECLARE__ } // namespace #endif //__VOLUME_MAPPABLE_INTERFACE_H__ workbench-1.1.1/src/FilesBase/VolumeSliceViewPlaneEnum.cxx000066400000000000000000000270771255417355300235620ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __VOLUME_SLICE_VIEW_AXIS_ENUM_DECLARE__ #include "VolumeSliceViewPlaneEnum.h" #undef __VOLUME_SLICE_VIEW_AXIS_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class VolumeSliceViewPlaneEnum * \brief * * */ /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. * @param guiNameAbbreviation * Abbreviated name for user-interface. */ VolumeSliceViewPlaneEnum::VolumeSliceViewPlaneEnum(const Enum enumValue, const AString& name, const AString& guiName, const AString& guiNameAbbreviation) { this->enumValue = enumValue; this->integerCode = integerCodeCounter++; this->name = name; this->guiName = guiName; this->guiNameAbbreviation = guiNameAbbreviation; } /** * Destructor. */ VolumeSliceViewPlaneEnum::~VolumeSliceViewPlaneEnum() { } /** * Initialize the enumerated metadata. */ void VolumeSliceViewPlaneEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(VolumeSliceViewPlaneEnum(ALL, "ALL", "All", "All")); enumData.push_back(VolumeSliceViewPlaneEnum(AXIAL, "AXIAL", "Axial", "A")); enumData.push_back(VolumeSliceViewPlaneEnum(CORONAL, "CORONAL", "Coronal", "C")); enumData.push_back(VolumeSliceViewPlaneEnum(PARASAGITTAL, "PARASAGITTAL", "Parasagittal", "P")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const VolumeSliceViewPlaneEnum* VolumeSliceViewPlaneEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const VolumeSliceViewPlaneEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString VolumeSliceViewPlaneEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const VolumeSliceViewPlaneEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ VolumeSliceViewPlaneEnum::Enum VolumeSliceViewPlaneEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = AXIAL; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const VolumeSliceViewPlaneEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type VolumeSliceViewPlaneEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString VolumeSliceViewPlaneEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const VolumeSliceViewPlaneEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ VolumeSliceViewPlaneEnum::Enum VolumeSliceViewPlaneEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = AXIAL; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const VolumeSliceViewPlaneEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type VolumeSliceViewPlaneEnum")); } return enumValue; } /** * Get a GUI abbreviated string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * Short name representing enumerated value. */ AString VolumeSliceViewPlaneEnum::toGuiNameAbbreviation(Enum enumValue) { if (initializedFlag == false) initialize(); const VolumeSliceViewPlaneEnum* enumInstance = findData(enumValue); return enumInstance->guiNameAbbreviation; } /** * Get an enumerated value corresponding to its abbreviated GUI name. * @param guiNameAbbreviation * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ VolumeSliceViewPlaneEnum::Enum VolumeSliceViewPlaneEnum::fromGuiNameAbbreviation(const AString& guiNameAbbreviation, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = AXIAL; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const VolumeSliceViewPlaneEnum& d = *iter; if (d.guiNameAbbreviation == guiNameAbbreviation) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiNameAbbreviation " + guiNameAbbreviation + "failed to match enumerated value for type VolumeSliceViewPlaneEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t VolumeSliceViewPlaneEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const VolumeSliceViewPlaneEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ VolumeSliceViewPlaneEnum::Enum VolumeSliceViewPlaneEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = AXIAL; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const VolumeSliceViewPlaneEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type VolumeSliceViewPlaneEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void VolumeSliceViewPlaneEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void VolumeSliceViewPlaneEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(VolumeSliceViewPlaneEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void VolumeSliceViewPlaneEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(VolumeSliceViewPlaneEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } workbench-1.1.1/src/FilesBase/VolumeSliceViewPlaneEnum.h000066400000000000000000000070631255417355300232000ustar00rootroot00000000000000#ifndef __VOLUME_SLICE_VIEW_AXIS_ENUM__H_ #define __VOLUME_SLICE_VIEW_AXIS_ENUM__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class VolumeSliceViewPlaneEnum { public: /** * Enumerated values. */ enum Enum { /** All Axis */ ALL, /** Axial (Horizontal) */ AXIAL, /** Coronal */ CORONAL, /** Parasagittal */ PARASAGITTAL }; ~VolumeSliceViewPlaneEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiNameAbbreviation(const AString& guiNameAbbreviation, bool* isValidOut); static AString toGuiNameAbbreviation(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); private: VolumeSliceViewPlaneEnum(const Enum enumValue, const AString& name, const AString& guiName, const AString& guiNameAbbreviation); static const VolumeSliceViewPlaneEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; /** A short user-friendly name that is displayed in the GUI */ AString guiNameAbbreviation; }; #ifdef __VOLUME_SLICE_VIEW_AXIS_ENUM_DECLARE__ std::vector VolumeSliceViewPlaneEnum::enumData; bool VolumeSliceViewPlaneEnum::initializedFlag = false; int32_t VolumeSliceViewPlaneEnum::integerCodeCounter = 0; #endif // __VOLUME_SLICE_VIEW_AXIS_ENUM_DECLARE__ } // namespace #endif //__VOLUME_SLICE_VIEW_AXIS_ENUM__H_ workbench-1.1.1/src/FilesBase/VolumeSpace.cxx000066400000000000000000000454151255417355300211120ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "VolumeSpace.h" #include "CaretAssert.h" #include "CaretException.h" #include "CaretLogger.h" #include "FloatMatrix.h" #include #include #include #include using namespace std; using namespace caret; VolumeSpace::VolumeSpace() { m_dims[0] = 0; m_dims[1] = 0; m_dims[2] = 0; m_sform = FloatMatrix::identity(4).getMatrix(); computeInverse(); } VolumeSpace::VolumeSpace(const int64_t dims[3], const vector >& sform) { setSpace(dims, sform); } VolumeSpace::VolumeSpace(const int64_t dims[3], const float sform[12]) { setSpace(dims, sform); } void VolumeSpace::setSpace(const int64_t dims[3], const vector >& sform) { if (sform.size() < 2 || sform.size() > 4) { CaretAssert(false); throw CaretException("VolumeSpace initialized with wrong size sform"); } for (int i = 0; i < (int)sform.size(); ++i) { if (sform[i].size() != 4) { CaretAssert(false); throw CaretException("VolumeSpace initialized with wrong size sform"); } } m_dims[0] = dims[0]; m_dims[1] = dims[1]; m_dims[2] = dims[2]; m_sform = sform; m_sform.resize(4);//make sure its 4x4 m_sform[3].resize(4); m_sform[3][0] = 0.0f;//force the fourth row to be correct m_sform[3][1] = 0.0f; m_sform[3][2] = 0.0f; m_sform[3][3] = 1.0f; computeInverse(); } void VolumeSpace::setSpace(const int64_t dims[3], const float sform[12]) { m_sform = FloatMatrix::identity(4).getMatrix(); for (int i = 0; i < 3; ++i) { for (int j = 0; j < 4; ++j) { m_sform[i][j] = sform[i * 4 + j]; } } m_dims[0] = dims[0]; m_dims[1] = dims[1]; m_dims[2] = dims[2]; computeInverse(); } void VolumeSpace::computeInverse() { m_inverse = FloatMatrix(m_sform).inverse().getMatrix(); } void VolumeSpace::spaceToIndex(const float& coordIn1, const float& coordIn2, const float& coordIn3, float& indexOut1, float& indexOut2, float& indexOut3) const { indexOut1 = coordIn1 * m_inverse[0][0] + coordIn2 * m_inverse[0][1] + coordIn3 * m_inverse[0][2] + m_inverse[0][3]; indexOut2 = coordIn1 * m_inverse[1][0] + coordIn2 * m_inverse[1][1] + coordIn3 * m_inverse[1][2] + m_inverse[1][3]; indexOut3 = coordIn1 * m_inverse[2][0] + coordIn2 * m_inverse[2][1] + coordIn3 * m_inverse[2][2] + m_inverse[2][3]; } void VolumeSpace::enclosingVoxel(const float& coordIn1, const float& coordIn2, const float& coordIn3, int64_t& indexOut1, int64_t& indexOut2, int64_t& indexOut3) const { float tempInd1, tempInd2, tempInd3; spaceToIndex(coordIn1, coordIn2, coordIn3, tempInd1, tempInd2, tempInd3); indexOut1 = (int64_t)floor(0.5f + tempInd1); indexOut2 = (int64_t)floor(0.5f + tempInd2); indexOut3 = (int64_t)floor(0.5f + tempInd3); } bool VolumeSpace::matches(const VolumeSpace& right) const { for (int i = 0; i < 3; ++i) { if (m_dims[i] != right.m_dims[i]) { return false; } } const float TOLER_RATIO = 0.999f;//ratio a spacing element can mismatch by for (int i = 0; i < 3; ++i) { for (int j = 0; j < 4; ++j) { float leftelem = m_sform[i][j]; float rightelem = right.m_sform[i][j]; if ((leftelem != rightelem) && (leftelem == 0.0f || rightelem == 0.0f || (leftelem / rightelem < TOLER_RATIO || rightelem / leftelem < TOLER_RATIO))) { return false; } } } return true; } bool VolumeSpace::operator==(const VolumeSpace& right) const { for (int i = 0; i < 3; ++i) { if (m_dims[i] != right.m_dims[i]) { return false; } } for (int i = 0; i < 3; ++i) { for (int j = 0; j < 4; ++j) { if (m_sform[i][j] != right.m_sform[i][j]) { return false; } } } return true; } void VolumeSpace::getSpacingVectors(Vector3D& iStep, Vector3D& jStep, Vector3D& kStep, Vector3D& origin) const { FloatMatrix(m_sform).getAffineVectors(iStep, jStep, kStep, origin); } float VolumeSpace::getVoxelVolume() const { Vector3D spacingVecs[4]; getSpacingVectors(spacingVecs[0], spacingVecs[1], spacingVecs[2], spacingVecs[3]); return abs(spacingVecs[0].dot(spacingVecs[1].cross(spacingVecs[2]))); } void VolumeSpace::getOrientAndSpacingForPlumb(OrientTypes* orientOut, float* spacingOut, float* originOut) const { CaretAssert(isPlumb()); if (!isPlumb()) { throw CaretException("orientation and spacing asked for on non-plumb volume space");//this will fail MISERABLY on non-plumb volumes, so throw otherwise } for (int i = 0; i < 3; ++i) { for (int j = 0; j < 3; ++j) { if (m_sform[i][j] != 0.0f) { spacingOut[j] = m_sform[i][j]; originOut[j] = m_sform[i][3]; bool negative = (m_sform[i][j] < 0.0f); switch (i) { case 0: //left/right orientOut[j] = (negative ? RIGHT_TO_LEFT : LEFT_TO_RIGHT); break; case 1: //forward/back orientOut[j] = (negative ? ANTERIOR_TO_POSTERIOR : POSTERIOR_TO_ANTERIOR); break; case 2: //up/down orientOut[j] = (negative ? SUPERIOR_TO_INFERIOR : INFERIOR_TO_SUPERIOR); break; default: //will never get called break; }; } } } } void VolumeSpace::getOrientation(OrientTypes orientOut[3]) const { Vector3D ivec, jvec, kvec, origin; getSpacingVectors(ivec, jvec, kvec, origin); int next = 1, bestarray[3] = {0, 0, 0}; float bestVal = -1.0f;//make sure at least the first test trips true, if there is a zero spacing vector it will default to report LPI for (int first = 0; first < 3; ++first)//brute force search for best fit - only 6 to try { int third = 3 - first - next; float testVal = abs(ivec[first] * jvec[next] * kvec[third]); if (testVal > bestVal) { bestVal = testVal; bestarray[0] = first; bestarray[1] = next; } testVal = abs(ivec[first] * jvec[third] * kvec[next]); if (testVal > bestVal) { bestVal = testVal; bestarray[0] = first; bestarray[1] = third; } next = 0; } bestarray[2] = 3 - bestarray[0] - bestarray[1]; Vector3D spaceHats[3];//to translate into enums without casting spaceHats[0] = ivec; spaceHats[1] = jvec; spaceHats[2] = kvec; for (int i = 0; i < 3; ++i) { bool neg = (spaceHats[i][bestarray[i]] < 0.0f); switch (bestarray[i]) { case 0: if (neg) { orientOut[i] = RIGHT_TO_LEFT; } else { orientOut[i] = LEFT_TO_RIGHT; } break; case 1: if (neg) { orientOut[i] = ANTERIOR_TO_POSTERIOR; } else { orientOut[i] = POSTERIOR_TO_ANTERIOR; } break; case 2: if (neg) { orientOut[i] = SUPERIOR_TO_INFERIOR; } else { orientOut[i] = INFERIOR_TO_SUPERIOR; } break; default: CaretAssert(0); } } } bool VolumeSpace::isPlumb() const { char axisUsed = 0; char indexUsed = 0; for (int i = 0; i < 3; ++i) { for (int j = 0; j < 3; ++j) { if (m_sform[i][j] != 0.0f) { if (axisUsed & (1< #include #include "stdint.h" #include namespace caret { class VolumeSpace { int64_t m_dims[3]; std::vector > m_sform, m_inverse; void computeInverse(); public: enum OrientTypes { LEFT_TO_RIGHT = 0, RIGHT_TO_LEFT = 4, POSTERIOR_TO_ANTERIOR = 1, ANTERIOR_TO_POSTERIOR = 5, INFERIOR_TO_SUPERIOR = 2, SUPERIOR_TO_INFERIOR = 6 }; VolumeSpace(); VolumeSpace(const int64_t dims[3], const std::vector >& sform); VolumeSpace(const int64_t dims[3], const float sform[12]); void setSpace(const int64_t dims[3], const std::vector >& sform); void setSpace(const int64_t dims[3], const float sform[12]); const int64_t* getDims() const { return m_dims; } const std::vector >& getSform() const { return m_sform; } void getSpacingVectors(Vector3D& iStep, Vector3D& jStep, Vector3D& kStep, Vector3D& origin) const; float getVoxelVolume() const; bool matches(const VolumeSpace& right) const;//allows slight mismatches bool operator==(const VolumeSpace& right) const;//requires that it be exact bool operator!=(const VolumeSpace& right) const { return !(*this == right); } ///returns true if volume space is not skew, and each axis and index is separate bool isPlumb() const; ///returns orientation, spacing, and center (spacing/center can be negative, spacing/center is LPI rearranged to ijk (first dimension uses first element), will assert false if isOblique is true) void getOrientAndSpacingForPlumb(OrientTypes* orientOut, float* spacingOut, float* originOut) const; ///get just orientation, even for non-plumb volumes void getOrientation(OrientTypes orientOut[3]) const; ///returns coordinate triplet of an index triplet template inline void indexToSpace(const T* indexIn, float* coordOut) const { indexToSpace(indexIn[0], indexIn[1], indexIn[2], coordOut[0], coordOut[1], coordOut[2]); } ///returns coordinate triplet of three indices template inline void indexToSpace(const T& indexIn1, const T& indexIn2, const T& indexIn3, float* coordOut) const { indexToSpace(indexIn1, indexIn2, indexIn3, coordOut[0], coordOut[1], coordOut[2]); } ///returns three coordinates of an index triplet template inline void indexToSpace(const T* indexIn, float& coordOut1, float& coordOut2, float& coordOut3) const { indexToSpace(indexIn[0], indexIn[1], indexIn[2], coordOut1, coordOut2, coordOut3); } ///returns three coordinates of three indices template void indexToSpace(const T& indexIn1, const T& indexIn2, const T& indexIn3, float& coordOut1, float& coordOut2, float& coordOut3) const; ///returns floating point index triplet of a given coordinate triplet inline void spaceToIndex(const float* coordIn, float* indexOut) const { spaceToIndex(coordIn[0], coordIn[1], coordIn[2], indexOut[0], indexOut[1], indexOut[2]); } ///returns floating point index triplet of three given coordinates inline void spaceToIndex(const float& coordIn1, const float& coordIn2, const float& coordIn3, float* indexOut) const { spaceToIndex(coordIn1, coordIn2, coordIn3, indexOut[0], indexOut[1], indexOut[2]); } ///returns three floating point indexes of a given coordinate triplet inline void spaceToIndex(const float* coordIn, float& indexOut1, float& indexOut2, float& indexOut3) const { spaceToIndex(coordIn[0], coordIn[1], coordIn[2], indexOut1, indexOut2, indexOut3); } ///returns three floating point indexes of three given coordinates void spaceToIndex(const float& coordIn1, const float& coordIn2, const float& coordIn3, float& indexOut1, float& indexOut2, float& indexOut3) const; ///returns integer index triplet of voxel whose center is closest to the coordinate triplet inline void enclosingVoxel(const float* coordIn, int64_t* indexOut) const { enclosingVoxel(coordIn[0], coordIn[1], coordIn[2], indexOut[0], indexOut[1], indexOut[2]); } ///returns integer index triplet of voxel whose center is closest to the three coordinates inline void enclosingVoxel(const float& coordIn1, const float& coordIn2, const float& coordIn3, int64_t* indexOut) const { enclosingVoxel(coordIn1, coordIn2, coordIn3, indexOut[0], indexOut[1], indexOut[2]); } ///returns integer indexes of voxel whose center is closest to the coordinate triplet inline void enclosingVoxel(const float* coordIn, int64_t& indexOut1, int64_t& indexOut2, int64_t& indexOut3) const { enclosingVoxel(coordIn[0], coordIn[1], coordIn[2], indexOut1, indexOut2, indexOut3); } ///returns integer indexes of voxel whose center is closest to the three coordinates void enclosingVoxel(const float& coordIn1, const float& coordIn2, const float& coordIn3, int64_t& indexOut1, int64_t& indexOut2, int64_t& indexOut3) const; template inline bool indexValid(const T* indexIn) const { return indexValid(indexIn[0], indexIn[1], indexIn[2]);//implicit cast to int64_t } ///checks if an index is within array dimensions inline bool indexValid(const int64_t& indexIn1, const int64_t& indexIn2, const int64_t& indexIn3) const { if (indexIn1 < 0 || indexIn1 >= m_dims[0]) return false; if (indexIn2 < 0 || indexIn2 >= m_dims[1]) return false; if (indexIn3 < 0 || indexIn3 >= m_dims[2]) return false; return true; } inline int64_t getIndex(const int64_t& indexIn1, const int64_t& indexIn2, const int64_t& indexIn3) const { CaretAssert(indexValid(indexIn1, indexIn2, indexIn3)); return indexIn1 + m_dims[0] * (indexIn2 + m_dims[1] * indexIn3); } template inline int64_t getIndex(const T* indexIn) const { return getIndex(indexIn[0], indexIn[1], indexIn[2]);//implicit cast to int64_t } void readCiftiXML1(QXmlStreamReader& xml);//xml functions void readCiftiXML2(QXmlStreamReader& xml); void writeCiftiXML1(QXmlStreamWriter& xml) const; void writeCiftiXML2(QXmlStreamWriter& xml) const; }; template void VolumeSpace::indexToSpace(const T& indexIn1, const T& indexIn2, const T& indexIn3, float& coordOut1, float& coordOut2, float& coordOut3) const { coordOut1 = indexIn1 * m_sform[0][0] + indexIn2 * m_sform[0][1] + indexIn3 * m_sform[0][2] + m_sform[0][3]; coordOut2 = indexIn1 * m_sform[1][0] + indexIn2 * m_sform[1][1] + indexIn3 * m_sform[1][2] + m_sform[1][3]; coordOut3 = indexIn1 * m_sform[2][0] + indexIn2 * m_sform[2][1] + indexIn3 * m_sform[2][2] + m_sform[2][3]; } } #endif //__VOLUME_SPACE_H__ workbench-1.1.1/src/FilesBase/nifti1.h000077500000000000000000002054021255417355300175030ustar00rootroot00000000000000#ifndef _NIFTI_HEADER_ #define _NIFTI_HEADER_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ /* * NOTE: our version is slightly modified, mainly turning #define into const int32_t */ /***************************************************************************** ** This file defines the "NIFTI-1" header format. ** ** It is derived from 2 meetings at the NIH (31 Mar 2003 and ** ** 02 Sep 2003) of the Data Format Working Group (DFWG), ** ** chartered by the NIfTI (Neuroimaging Informatics Technology ** ** Initiative) at the National Institutes of Health (NIH). ** **--------------------------------------------------------------** ** Neither the National Institutes of Health (NIH), the DFWG, ** ** nor any of the members or employees of these institutions ** ** imply any warranty of usefulness of this material for any ** ** purpose, and do not assume any liability for damages, ** ** incidental or otherwise, caused by any use of this document. ** ** If these conditions are not acceptable, do not use this! ** **--------------------------------------------------------------** ** Author: Robert W Cox (NIMH, Bethesda) ** ** Advisors: John Ashburner (FIL, London), ** ** Stephen Smith (FMRIB, Oxford), ** ** Mark Jenkinson (FMRIB, Oxford) ** ******************************************************************************/ /*---------------------------------------------------------------------------*/ /* Note that the ANALYZE 7.5 file header (dbh.h) is (c) Copyright 1986-1995 Biomedical Imaging Resource Mayo Foundation Incorporation of components of dbh.h are by permission of the Mayo Foundation. Changes from the ANALYZE 7.5 file header in this file are released to the public domain, including the functional comments and any amusing asides. -----------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/ /*! INTRODUCTION TO NIFTI-1: ------------------------ The twin (and somewhat conflicting) goals of this modified ANALYZE 7.5 format are: (a) To add information to the header that will be useful for functional neuroimaging data analysis and display. These additions include: - More basic data types. - Two affine transformations to specify voxel coordinates. - "Intent" codes and parameters to describe the meaning of the data. - Affine scaling of the stored data values to their "true" values. - Optional storage of the header and image data in one file (.nii). (b) To maintain compatibility with non-NIFTI-aware ANALYZE 7.5 compatible software (i.e., such a program should be able to do something useful with a NIFTI-1 dataset -- at least, with one stored in a traditional .img/.hdr file pair). Most of the unused fields in the ANALYZE 7.5 header have been taken, and some of the lesser-used fields have been co-opted for other purposes. Notably, most of the data_history substructure has been co-opted for other purposes, since the ANALYZE 7.5 format describes this substructure as "not required". NIFTI-1 FLAG (MAGIC STRINGS): ---------------------------- To flag such a struct as being conformant to the NIFTI-1 spec, the last 4 bytes of the header must be either the C String "ni1" or "n+1"; in hexadecimal, the 4 bytes 6E 69 31 00 or 6E 2B 31 00 (in any future version of this format, the '1' will be upgraded to '2', etc.). Normally, such a "magic number" or flag goes at the start of the file, but trying to avoid clobbering widely-used ANALYZE 7.5 fields led to putting this marker last. However, recall that "the last shall be first" (Matthew 20:16). If a NIFTI-aware program reads a header file that is NOT marked with a NIFTI magic string, then it should treat the header as an ANALYZE 7.5 structure. NIFTI-1 FILE STORAGE: -------------------- "ni1" means that the image data is stored in the ".img" file corresponding to the header file (starting at file offset 0). "n+1" means that the image data is stored in the same file as the header information. We recommend that the combined header+data filename suffix be ".nii". When the dataset is stored in one file, the first byte of image data is stored at byte location (int)vox_offset in this combined file. The minimum allowed value of vox_offset is 352; for compatibility with some software, vox_offset should be an integral multiple of 16. GRACE UNDER FIRE: ---------------- Most NIFTI-aware programs will only be able to handle a subset of the full range of datasets possible with this format. All NIFTI-aware programs should take care to check if an input dataset conforms to the program's needs and expectations (e.g., check datatype, intent_code, etc.). If the input dataset can't be handled by the program, the program should fail gracefully (e.g., print a useful warning; not crash). SAMPLE CODES: ------------ The associated files nifti1_io.h and nifti1_io.c provide a sample implementation in C of a set of functions to read, write, and manipulate NIFTI-1 files. The file nifti1_test.c is a sample program that uses the nifti1_io.c functions. -----------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/ /* HEADER STRUCT DECLARATION: ------------------------- In the comments below for each field, only NIFTI-1 specific requirements or changes from the ANALYZE 7.5 format are described. For convenience, the 348 byte header is described as a single struct, rather than as the ANALYZE 7.5 group of 3 substructs. Further comments about the interpretation of various elements of this header are after the data type definition itself. Fields that are marked as ++UNUSED++ have no particular interpretation in this standard. (Also see the UNUSED FIELDS comment section, far below.) The presumption below is that the various C types have particular sizes: sizeof(int) = sizeof(float) = 4 ; sizeof(short) = 2 -----------------------------------------------------------------------------*/ /*=================*/ #ifdef __cplusplus extern "C" { #endif /*=================*/ //hopefully cross-platform solution to byte padding added by some compilers #pragma pack(push) #pragma pack(1) /*! \struct nifti_1_header \brief Data structure defining the fields in the nifti1 header. This binary header should be found at the beginning of a valid NIFTI-1 header file. */ /*************************/ /************************/ struct nifti_1_header { /* NIFTI-1 usage */ /* ANALYZE 7.5 field(s) */ /*************************/ /************************/ /*--- was header_key substruct ---*/ int sizeof_hdr; /*!< MUST be 348 */ /* int sizeof_hdr; */ /* 0 */ char data_type[10]; /*!< ++UNUSED++ */ /* char data_type[10]; */ /* 4 */ char db_name[18]; /*!< ++UNUSED++ */ /* char db_name[18]; */ /* 14 */ int extents; /*!< ++UNUSED++ */ /* int extents; */ /* 32 */ short session_error; /*!< ++UNUSED++ */ /* short session_error; */ /* 36 */ char regular; /*!< ++UNUSED++ */ /* char regular; */ /* 38 */ char dim_info; /*!< MRI slice ordering. */ /* char hkey_un0; */ /* 39 */ /*--- was image_dimension substruct ---*/ short dim[8]; /*!< Data array dimensions.*/ /* short dim[8]; */ /* 40 */ float intent_p1 ; /*!< 1st intent parameter. */ /* short unused8; */ /* 56 */ /* short unused9; */ float intent_p2 ; /*!< 2nd intent parameter. */ /* short unused10; */ /* 60 */ /* short unused11; */ float intent_p3 ; /*!< 3rd intent parameter. */ /* short unused12; */ /* 64 */ /* short unused13; */ short intent_code ; /*!< NIFTI_INTENT_* code. */ /* short unused14; */ /* 68 */ short datatype; /*!< Defines data type! */ /* short datatype; */ /* 70 */ short bitpix; /*!< Number bits/voxel. */ /* short bitpix; */ /* 72 */ short slice_start; /*!< First slice index. */ /* short dim_un0; */ /* 74 */ float pixdim[8]; /*!< Grid spacings. */ /* float pixdim[8]; */ /* 76 */ float vox_offset; /*!< Offset into .nii file */ /* float vox_offset; */ /* 108 */ float scl_slope ; /*!< Data scaling: slope. */ /* float funused1; */ /* 112 */ float scl_inter ; /*!< Data scaling: offset. */ /* float funused2; */ /* 116 */ short slice_end; /*!< Last slice index. */ /* float funused3; */ /* 120 */ char slice_code ; /*!< Slice timing order. */ /* 122 */ char xyzt_units ; /*!< Units of pixdim[1..4] */ /* 123 */ float cal_max; /*!< Max display intensity */ /* float cal_max; */ /* 124 */ float cal_min; /*!< Min display intensity */ /* float cal_min; */ /* 128 */ float slice_duration;/*!< Time for 1 slice. */ /* float compressed; */ /* 132 */ float toffset; /*!< Time axis shift. */ /* float verified; */ /* 136 */ int glmax; /*!< ++UNUSED++ */ /* int glmax; */ /* 140 */ int glmin; /*!< ++UNUSED++ */ /* int glmin; */ /* 144 */ /*--- was data_history substruct ---*/ char descrip[80]; /*!< any text you like. */ /* char descrip[80]; */ /* 148 */ char aux_file[24]; /*!< auxiliary filename. */ /* char aux_file[24]; */ /* 228 */ short qform_code ; /*!< NIFTI_XFORM_* code. */ /*-- all ANALYZE 7.5 ---*/ /* 252 */ short sform_code ; /*!< NIFTI_XFORM_* code. */ /* fields below here */ /* 254 */ /* are replaced */ float quatern_b ; /*!< Quaternion b param. */ /* 256 */ float quatern_c ; /*!< Quaternion c param. */ /* 260 */ float quatern_d ; /*!< Quaternion d param. */ /* 264 */ float qoffset_x ; /*!< Quaternion x shift. */ /* 268 */ float qoffset_y ; /*!< Quaternion y shift. */ /* 272 */ float qoffset_z ; /*!< Quaternion z shift. */ /* 276 */ float srow_x[4] ; /*!< 1st row affine transform. */ /* 280 */ float srow_y[4] ; /*!< 2nd row affine transform. */ /* 296 */ float srow_z[4] ; /*!< 3rd row affine transform. */ /* 312 */ char intent_name[16];/*!< 'name' or meaning of data. */ /* 328 */ char magic[4] ; /*!< MUST be "ni1\0" or "n+1\0". */ /* 344 */ } ; /**** 348 bytes total ****/ typedef struct nifti_1_header nifti_1_header ; /*---------------------------------------------------------------------------*/ /* HEADER EXTENSIONS: ----------------- After the end of the 348 byte header (e.g., after the magic field), the next 4 bytes are a char array field named "extension". By default, all 4 bytes of this array should be set to zero. In a .nii file, these 4 bytes will always be present, since the earliest start point for the image data is byte #352. In a separate .hdr file, these bytes may or may not be present. If not present (i.e., if the length of the .hdr file is 348 bytes), then a NIfTI-1 compliant program should use the default value of extension={0,0,0,0}. The first byte (extension[0]) is the only value of this array that is specified at present. The other 3 bytes are reserved for future use. If extension[0] is nonzero, it indicates that extended header information is present in the bytes following the extension array. In a .nii file, this extended header data is before the image data (and vox_offset must be set correctly to allow for this). In a .hdr file, this extended data follows extension and proceeds (potentially) to the end of the file. The format of extended header data is weakly specified. Each extension must be an integer multiple of 16 bytes long. The first 8 bytes of each extension comprise 2 integers: int esize , ecode ; These values may need to be byte-swapped, as indicated by dim[0] for the rest of the header. * esize is the number of bytes that form the extended header data + esize must be a positive integral multiple of 16 + this length includes the 8 bytes of esize and ecode themselves * ecode is a non-negative integer that indicates the format of the extended header data that follows + different ecode values are assigned to different developer groups + at present, the "registered" values for code are = 0 = unknown private format (not recommended!) = 2 = DICOM format (i.e., attribute tags and values) = 4 = AFNI group (i.e., ASCII XML-ish elements) In the interests of interoperability (a primary rationale for NIfTI), groups developing software that uses this extension mechanism are encouraged to document and publicize the format of their extensions. To this end, the NIfTI DFWG will assign even numbered codes upon request to groups submitting at least rudimentary documentation for the format of their extension; at present, the contact is mailto:rwcox@nih.gov. The assigned codes and documentation will be posted on the NIfTI website. All odd values of ecode (and 0) will remain unassigned; at least, until the even ones are used up, when we get to 2,147,483,646. Note that the other contents of the extended header data section are totally unspecified by the NIfTI-1 standard. In particular, if binary data is stored in such a section, its byte order is not necessarily the same as that given by examining dim[0]; it is incumbent on the programs dealing with such data to determine the byte order of binary extended header data. Multiple extended header sections are allowed, each starting with an esize,ecode value pair. The first esize value, as described above, is at bytes #352-355 in the .hdr or .nii file (files start at byte #0). If this value is positive, then the second (esize2) will be found starting at byte #352+esize1 , the third (esize3) at byte #352+esize1+esize2, et cetera. Of course, in a .nii file, the value of vox_offset must be compatible with these extensions. If a malformed file indicates that an extended header data section would run past vox_offset, then the entire extended header section should be ignored. In a .hdr file, if an extended header data section would run past the end-of-file, that extended header data should also be ignored. With the above scheme, a program can successively examine the esize and ecode values, and skip over each extended header section if the program doesn't know how to interpret the data within. Of course, any program can simply ignore all extended header sections simply by jumping straight to the image data using vox_offset. -----------------------------------------------------------------------------*/ /*! \struct nifti1_extender \brief This structure represents a 4-byte string that should follow the binary nifti_1_header data in a NIFTI-1 header file. If the char values are {1,0,0,0}, the file is expected to contain extensions, values of {0,0,0,0} imply the file does not contain extensions. Other sequences of values are not currently defined. */ struct nifti1_extender { char extension[4] ; } ; typedef struct nifti1_extender nifti1_extender ; /*! \struct nifti1_extension \brief Data structure defining the fields of a header extension. */ struct nifti1_extension { int esize ; /*!< size of extension, in bytes (must be multiple of 16) */ int ecode ; /*!< extension code, one of the NIFTI_ECODE_ values */ char * edata ; /*!< raw data, with no byte swapping */ } ; typedef struct nifti1_extension nifti1_extension ; //and restore packing behavior #pragma pack(pop) /*---------------------------------------------------------------------------*/ /* DATA DIMENSIONALITY (as in ANALYZE 7.5): --------------------------------------- dim[0] = number of dimensions; - if dim[0] is outside range 1..7, then the header information needs to be byte swapped appropriately - ANALYZE supports dim[0] up to 7, but NIFTI-1 reserves dimensions 1,2,3 for space (x,y,z), 4 for time (t), and 5,6,7 for anything else needed. dim[i] = length of dimension #i, for i=1..dim[0] (must be positive) - also see the discussion of intent_code, far below pixdim[i] = voxel width along dimension #i, i=1..dim[0] (positive) - cf. ORIENTATION section below for use of pixdim[0] - the units of pixdim can be specified with the xyzt_units field (also described far below). Number of bits per voxel value is in bitpix, which MUST correspond with the datatype field. The total number of bytes in the image data is dim[1] * ... * dim[dim[0]] * bitpix / 8 In NIFTI-1 files, dimensions 1,2,3 are for space, dimension 4 is for time, and dimension 5 is for storing multiple values at each spatiotemporal voxel. Some examples: - A typical whole-brain FMRI experiment's time series: - dim[0] = 4 - dim[1] = 64 pixdim[1] = 3.75 xyzt_units = NIFTI_UNITS_MM - dim[2] = 64 pixdim[2] = 3.75 | NIFTI_UNITS_SEC - dim[3] = 20 pixdim[3] = 5.0 - dim[4] = 120 pixdim[4] = 2.0 - A typical T1-weighted anatomical volume: - dim[0] = 3 - dim[1] = 256 pixdim[1] = 1.0 xyzt_units = NIFTI_UNITS_MM - dim[2] = 256 pixdim[2] = 1.0 - dim[3] = 128 pixdim[3] = 1.1 - A single slice EPI time series: - dim[0] = 4 - dim[1] = 64 pixdim[1] = 3.75 xyzt_units = NIFTI_UNITS_MM - dim[2] = 64 pixdim[2] = 3.75 | NIFTI_UNITS_SEC - dim[3] = 1 pixdim[3] = 5.0 - dim[4] = 1200 pixdim[4] = 0.2 - A 3-vector stored at each point in a 3D volume: - dim[0] = 5 - dim[1] = 256 pixdim[1] = 1.0 xyzt_units = NIFTI_UNITS_MM - dim[2] = 256 pixdim[2] = 1.0 - dim[3] = 128 pixdim[3] = 1.1 - dim[4] = 1 pixdim[4] = 0.0 - dim[5] = 3 intent_code = NIFTI_INTENT_VECTOR - A single time series with a 3x3 matrix at each point: - dim[0] = 5 - dim[1] = 1 xyzt_units = NIFTI_UNITS_SEC - dim[2] = 1 - dim[3] = 1 - dim[4] = 1200 pixdim[4] = 0.2 - dim[5] = 9 intent_code = NIFTI_INTENT_GENMATRIX - intent_p1 = intent_p2 = 3.0 (indicates matrix dimensions) -----------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/ /* DATA STORAGE: ------------ If the magic field is "n+1", then the voxel data is stored in the same file as the header. In this case, the voxel data starts at offset (int)vox_offset into the header file. Thus, vox_offset=352.0 means that the data starts immediately after the NIFTI-1 header. If vox_offset is greater than 352, the NIFTI-1 format does not say much about the contents of the dataset file between the end of the header and the start of the data. FILES: ----- If the magic field is "ni1", then the voxel data is stored in the associated ".img" file, starting at offset 0 (i.e., vox_offset is not used in this case, and should be set to 0.0). When storing NIFTI-1 datasets in pairs of files, it is customary to name the files in the pattern "name.hdr" and "name.img", as in ANALYZE 7.5. When storing in a single file ("n+1"), the file name should be in the form "name.nii" (the ".nft" and ".nif" suffixes are already taken; cf. http://www.icdatamaster.com/n.html ). BYTE ORDERING: ------------- The byte order of the data arrays is presumed to be the same as the byte order of the header (which is determined by examining dim[0]). Floating point types are presumed to be stored in IEEE-754 format. -----------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/ /* DETAILS ABOUT vox_offset: ------------------------ In a .nii file, the vox_offset field value is interpreted as the start location of the image data bytes in that file. In a .hdr/.img file pair, the vox_offset field value is the start location of the image data bytes in the .img file. * If vox_offset is less than 352 in a .nii file, it is equivalent to 352 (i.e., image data never starts before byte #352 in a .nii file). * The default value for vox_offset in a .nii file is 352. * In a .hdr file, the default value for vox_offset is 0. * vox_offset should be an integer multiple of 16; otherwise, some programs may not work properly (e.g., SPM). This is to allow memory-mapped input to be properly byte-aligned. Note that since vox_offset is an IEEE-754 32 bit float (for compatibility with the ANALYZE-7.5 format), it effectively has a 24 bit mantissa. All integers from 0 to 2^24 can be represented exactly in this format, but not all larger integers are exactly storable as IEEE-754 32 bit floats. However, unless you plan to have vox_offset be potentially larger than 16 MB, this should not be an issue. (Actually, any integral multiple of 16 up to 2^27 can be represented exactly in this format, which allows for up to 128 MB of random information before the image data. If that isn't enough, then perhaps this format isn't right for you.) In a .img file (i.e., image data stored separately from the NIfTI-1 header), data bytes between #0 and #vox_offset-1 (inclusive) are completely undefined and unregulated by the NIfTI-1 standard. One potential use of having vox_offset > 0 in the .hdr/.img file pair storage method is to make the .img file be a copy of (or link to) a pre-existing image file in some other format, such as DICOM; then vox_offset would be set to the offset of the image data in this file. (It may not be possible to follow the "multiple-of-16 rule" with an arbitrary external file; using the NIfTI-1 format in such a case may lead to a file that is incompatible with software that relies on vox_offset being a multiple of 16.) In a .nii file, data bytes between #348 and #vox_offset-1 (inclusive) may be used to store user-defined extra information; similarly, in a .hdr file, any data bytes after byte #347 are available for user-defined extra information. The (very weak) regulation of this extra header data is described elsewhere. -----------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/ /* DATA SCALING: ------------ If the scl_slope field is nonzero, then each voxel value in the dataset should be scaled as y = scl_slope * x + scl_inter where x = voxel value stored y = "true" voxel value Normally, we would expect this scaling to be used to store "true" floating values in a smaller integer datatype, but that is not required. That is, it is legal to use scaling even if the datatype is a float type (crazy, perhaps, but legal). - However, the scaling is to be ignored if datatype is DT_RGB24. - If datatype is a complex type, then the scaling is to be applied to both the real and imaginary parts. The cal_min and cal_max fields (if nonzero) are used for mapping (possibly scaled) dataset values to display colors: - Minimum display intensity (black) corresponds to dataset value cal_min. - Maximum display intensity (white) corresponds to dataset value cal_max. - Dataset values below cal_min should display as black also, and values above cal_max as white. - Colors "black" and "white", of course, may refer to any scalar display scheme (e.g., a color lookup table specified via aux_file). - cal_min and cal_max only make sense when applied to scalar-valued datasets (i.e., dim[0] < 5 or dim[5] = 1). -----------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/ /* TYPE OF DATA (acceptable values for datatype field): --------------------------------------------------- Values of datatype smaller than 256 are ANALYZE 7.5 compatible. Larger values are NIFTI-1 additions. These are all multiples of 256, so that no bits below position 8 are set in datatype. But there is no need to use only powers-of-2, as the original ANALYZE 7.5 datatype codes do. The additional codes are intended to include a complete list of basic scalar types, including signed and unsigned integers from 8 to 64 bits, floats from 32 to 128 bits, and complex (float pairs) from 64 to 256 bits. Note that most programs will support only a few of these datatypes! A NIFTI-1 program should fail gracefully (e.g., print a warning message) when it encounters a dataset with a type it doesn't like. -----------------------------------------------------------------------------*/ #undef DT_UNKNOWN /* defined in dirent.h on some Unix systems */ #include /*! \defgroup NIFTI1_DATATYPES \brief nifti1 datatype codes @{ */ /*--- the original ANALYZE 7.5 type codes ---*/ const int32_t DT_NONE =0; const int32_t DT_UNKNOWN =0; /* what it says, dude */ const int32_t DT_BINARY =1; /* binary (1 bit/voxel) */ const int32_t DT_UNSIGNED_CHAR =2; /* unsigned char (8 bits/voxel) */ const int32_t DT_SIGNED_SHORT =4; /* signed short (16 bits/voxel) */ const int32_t DT_SIGNED_INT =8; /* signed int (32 bits/voxel) */ const int32_t DT_FLOAT =16; /* float (32 bits/voxel) */ const int32_t DT_COMPLEX =32; /* complex (64 bits/voxel) */ const int32_t DT_DOUBLE =64; /* double (64 bits/voxel) */ const int32_t DT_RGB =128; /* RGB triple (24 bits/voxel) */ const int32_t DT_ALL =255; /* not very useful (?) */ /*----- another set of names for the same ---*/ const int32_t DT_UINT8 =2; const int32_t DT_INT16 =4; const int32_t DT_INT32 =8; const int32_t DT_FLOAT32 =16; const int32_t DT_COMPLEX64 =32; const int32_t DT_FLOAT64 =64; const int32_t DT_RGB24 =128; /*------------------- new codes for NIFTI ---*/ const int32_t DT_INT8 =256; /* signed char (8 bits) */ const int32_t DT_UINT16 =512; /* unsigned short (16 bits) */ const int32_t DT_UINT32 =768; /* unsigned int (32 bits) */ const int32_t DT_INT64 =1024; /* long long (64 bits) */ const int32_t DT_UINT64 =1280; /* unsigned long long (64 bits) */ const int32_t DT_FLOAT128 =1536; /* long double (128 bits) */ const int32_t DT_COMPLEX128 =1792; /* double pair (128 bits) */ const int32_t DT_COMPLEX256 =2048; /* long double pair (256 bits) */ /* @} */ /*------- aliases for all the above codes ---*/ /*! \defgroup NIFTI1_DATATYPE_ALIASES \brief aliases for the nifti1 datatype codes @{ */ /*! unsigned char. */ const int32_t NIFTI_TYPE_UINT8 =2; /*! signed short. */ const int32_t NIFTI_TYPE_INT16 =4; /*! signed int. */ const int32_t NIFTI_TYPE_INT32 =8; /*! 32 bit float. */ const int32_t NIFTI_TYPE_FLOAT32 =16; /*! 64 bit complex = 2 32 bit floats. */ const int32_t NIFTI_TYPE_COMPLEX64 =32; /*! 64 bit float = double. */ const int32_t NIFTI_TYPE_FLOAT64 =64; /*! 3 8 bit bytes. */ const int32_t NIFTI_TYPE_RGB24 =128; /*! signed char. */ const int32_t NIFTI_TYPE_INT8 =256; /*! unsigned short. */ const int32_t NIFTI_TYPE_UINT16 =512; /*! unsigned int. */ const int32_t NIFTI_TYPE_UINT32 =768; /*! signed long long. */ const int32_t NIFTI_TYPE_INT64 =1024; /*! unsigned long long. */ const int32_t NIFTI_TYPE_UINT64 =1280; /*! 128 bit float = long double. */ const int32_t NIFTI_TYPE_FLOAT128 =1536; /*! 128 bit complex = 2 64 bit floats. */ const int32_t NIFTI_TYPE_COMPLEX128 =1792; /*! 256 bit complex = 2 128 bit floats */ const int32_t NIFTI_TYPE_COMPLEX256 =2048; /* @} */ /*-------- sample typedefs for complicated types ---*/ #if 0 typedef struct { float r,i; } complex_float ; typedef struct { double r,i; } complex_double ; typedef struct { long double r,i; } complex_longdouble ; typedef struct { unsigned char r,g,b; } rgb_byte ; #endif /*---------------------------------------------------------------------------*/ /* INTERPRETATION OF VOXEL DATA: ---------------------------- The intent_code field can be used to indicate that the voxel data has some particular meaning. In particular, a large number of codes is given to indicate that the the voxel data should be interpreted as being drawn from a given probability distribution. VECTOR-VALUED DATASETS: ---------------------- The 5th dimension of the dataset, if present (i.e., dim[0]=5 and dim[5] > 1), contains multiple values (e.g., a vector) to be stored at each spatiotemporal location. For example, the header values - dim[0] = 5 - dim[1] = 64 - dim[2] = 64 - dim[3] = 20 - dim[4] = 1 (indicates no time axis) - dim[5] = 3 - datatype = DT_FLOAT - intent_code = NIFTI_INTENT_VECTOR mean that this dataset should be interpreted as a 3D volume (64x64x20), with a 3-vector of floats defined at each point in the 3D grid. A program reading a dataset with a 5th dimension may want to reformat the image data to store each voxels' set of values together in a struct or array. This programming detail, however, is beyond the scope of the NIFTI-1 file specification! Uses of dimensions 6 and 7 are also not specified here. STATISTICAL PARAMETRIC DATASETS (i.e., SPMs): -------------------------------------------- Values of intent_code from NIFTI_FIRST_STATCODE to NIFTI_LAST_STATCODE (inclusive) indicate that the numbers in the dataset should be interpreted as being drawn from a given distribution. Most such distributions have auxiliary parameters (e.g., NIFTI_INTENT_TTEST has 1 DOF parameter). If the dataset DOES NOT have a 5th dimension, then the auxiliary parameters are the same for each voxel, and are given in header fields intent_p1, intent_p2, and intent_p3. If the dataset DOES have a 5th dimension, then the auxiliary parameters are different for each voxel. For example, the header values - dim[0] = 5 - dim[1] = 128 - dim[2] = 128 - dim[3] = 1 (indicates a single slice) - dim[4] = 1 (indicates no time axis) - dim[5] = 2 - datatype = DT_FLOAT - intent_code = NIFTI_INTENT_TTEST mean that this is a 2D dataset (128x128) of t-statistics, with the t-statistic being in the first "plane" of data and the degrees-of-freedom parameter being in the second "plane" of data. If the dataset 5th dimension is used to store the voxel-wise statistical parameters, then dim[5] must be 1 plus the number of parameters required by that distribution (e.g., intent_code=NIFTI_INTENT_TTEST implies dim[5] must be 2, as in the example just above). Note: intent_code values 2..10 are compatible with AFNI 1.5x (which is why there is no code with value=1, which is obsolescent in AFNI). OTHER INTENTIONS: ---------------- The purpose of the intent_* fields is to help interpret the values stored in the dataset. Some non-statistical values for intent_code and conventions are provided for storing other complex data types. The intent_name field provides space for a 15 character (plus 0 byte) 'name' string for the type of data stored. Examples: - intent_code = NIFTI_INTENT_ESTIMATE; intent_name = "T1"; could be used to signify that the voxel values are estimates of the NMR parameter T1. - intent_code = NIFTI_INTENT_TTEST; intent_name = "House"; could be used to signify that the voxel values are t-statistics for the significance of 'activation' response to a House stimulus. - intent_code = NIFTI_INTENT_DISPVECT; intent_name = "ToMNI152"; could be used to signify that the voxel values are a displacement vector that transforms each voxel (x,y,z) location to the corresponding location in the MNI152 standard brain. - intent_code = NIFTI_INTENT_SYMMATRIX; intent_name = "DTI"; could be used to signify that the voxel values comprise a diffusion tensor image. If no data name is implied or needed, intent_name[0] should be set to 0. -----------------------------------------------------------------------------*/ /*! default: no intention is indicated in the header. */ const int32_t NIFTI_INTENT_NONE =0; /*-------- These codes are for probability distributions ---------------*/ /* Most distributions have a number of parameters, below denoted by p1, p2, and p3, and stored in - intent_p1, intent_p2, intent_p3 if dataset doesn't have 5th dimension - image data array if dataset does have 5th dimension Functions to compute with many of the distributions below can be found in the CDF library from U Texas. Formulas for and discussions of these distributions can be found in the following books: [U] Univariate Discrete Distributions, NL Johnson, S Kotz, AW Kemp. [C1] Continuous Univariate Distributions, vol. 1, NL Johnson, S Kotz, N Balakrishnan. [C2] Continuous Univariate Distributions, vol. 2, NL Johnson, S Kotz, N Balakrishnan. */ /*----------------------------------------------------------------------*/ /*! [C2, chap 32] Correlation coefficient R (1 param): p1 = degrees of freedom R/sqrt(1-R*R) is t-distributed with p1 DOF. */ /*! \defgroup NIFTI1_INTENT_CODES \brief nifti1 intent codes, to describe intended meaning of dataset contents @{ */ const int32_t NIFTI_INTENT_CORREL =2; /*! [C2, chap 28] Student t statistic (1 param): p1 = DOF. */ const int32_t NIFTI_INTENT_TTEST =3; /*! [C2, chap 27] Fisher F statistic (2 params): p1 = numerator DOF, p2 = denominator DOF. */ const int32_t NIFTI_INTENT_FTEST =4; /*! [C1, chap 13] Standard normal (0 params): Density = N(0,1). */ const int32_t NIFTI_INTENT_ZSCORE =5; /*! [C1, chap 18] Chi-squared (1 param): p1 = DOF. Density(x) proportional to exp(-x/2) * x^(p1/2-1). */ const int32_t NIFTI_INTENT_CHISQ =6; /*! [C2, chap 25] Beta distribution (2 params): p1=a, p2=b. Density(x) proportional to x^(a-1) * (1-x)^(b-1). */ const int32_t NIFTI_INTENT_BETA =7; /*! [U, chap 3] Binomial distribution (2 params): p1 = number of trials, p2 = probability per trial. Prob(x) = (p1 choose x) * p2^x * (1-p2)^(p1-x), for x=0,1,...,p1. */ const int32_t NIFTI_INTENT_BINOM =8; /*! [C1, chap 17] Gamma distribution (2 params): p1 = shape, p2 = scale. Density(x) proportional to x^(p1-1) * exp(-p2*x). */ const int32_t NIFTI_INTENT_GAMMA =9; /*! [U, chap 4] Poisson distribution (1 param): p1 = mean. Prob(x) = exp(-p1) * p1^x / x! , for x=0,1,2,.... */ const int32_t NIFTI_INTENT_POISSON =10; /*! [C1, chap 13] Normal distribution (2 params): p1 = mean, p2 = standard deviation. */ const int32_t NIFTI_INTENT_NORMAL =11; /*! [C2, chap 30] Noncentral F statistic (3 params): p1 = numerator DOF, p2 = denominator DOF, p3 = numerator noncentrality parameter. */ const int32_t NIFTI_INTENT_FTEST_NONC=12; /*! [C2, chap 29] Noncentral chi-squared statistic (2 params): p1 = DOF, p2 = noncentrality parameter. */ const int32_t NIFTI_INTENT_CHISQ_NONC=13; /*! [C2, chap 23] Logistic distribution (2 params): p1 = location, p2 = scale. Density(x) proportional to sech^2((x-p1)/(2*p2)). */ const int32_t NIFTI_INTENT_LOGISTIC =14; /*! [C2, chap 24] Laplace distribution (2 params): p1 = location, p2 = scale. Density(x) proportional to exp(-abs(x-p1)/p2). */ const int32_t NIFTI_INTENT_LAPLACE =15; /*! [C2, chap 26] Uniform distribution: p1 = lower end, p2 = upper end. */ const int32_t NIFTI_INTENT_UNIFORM =16; /*! [C2, chap 31] Noncentral t statistic (2 params): p1 = DOF, p2 = noncentrality parameter. */ const int32_t NIFTI_INTENT_TTEST_NONC=17; /*! [C1, chap 21] Weibull distribution (3 params): p1 = location, p2 = scale, p3 = power. Density(x) proportional to ((x-p1)/p2)^(p3-1) * exp(-((x-p1)/p2)^p3) for x > p1. */ const int32_t NIFTI_INTENT_WEIBULL =18; /*! [C1, chap 18] Chi distribution (1 param): p1 = DOF. Density(x) proportional to x^(p1-1) * exp(-x^2/2) for x > 0. p1 = 1 = 'half normal' distribution p1 = 2 = Rayleigh distribution p1 = 3 = Maxwell-Boltzmann distribution. */ const int32_t NIFTI_INTENT_CHI =19; /*! [C1, chap 15] Inverse Gaussian (2 params): p1 = mu, p2 = lambda Density(x) proportional to exp(-p2*(x-p1)^2/(2*p1^2*x)) / x^3 for x > 0. */ const int32_t NIFTI_INTENT_INVGAUSS =20; /*! [C2, chap 22] Extreme value type I (2 params): p1 = location, p2 = scale cdf(x) = exp(-exp(-(x-p1)/p2)). */ const int32_t NIFTI_INTENT_EXTVAL =21; /*! Data is a 'p-value' (no params). */ const int32_t NIFTI_INTENT_PVAL =22; /*! Data is ln(p-value) (no params). To be safe, a program should compute p = exp(-abs(this_value)). The nifti_stats.c library returns this_value as positive, so that this_value = -log(p). */ const int32_t NIFTI_INTENT_LOGPVAL =23; /*! Data is log10(p-value) (no params). To be safe, a program should compute p = pow(10.,-abs(this_value)). The nifti_stats.c library returns this_value as positive, so that this_value = -log10(p). */ const int32_t NIFTI_INTENT_LOG10PVAL =24; /*! Smallest intent_code that indicates a statistic. */ const int32_t NIFTI_FIRST_STATCODE =2; /*! Largest intent_code that indicates a statistic. */ const int32_t NIFTI_LAST_STATCODE =24; /*---------- these values for intent_code aren't for statistics ----------*/ /*! To signify that the value at each voxel is an estimate of some parameter, set intent_code = NIFTI_INTENT_ESTIMATE. The name of the parameter may be stored in intent_name. */ const int32_t NIFTI_INTENT_ESTIMATE =1001; /*! To signify that the value at each voxel is an index into some set of labels, set intent_code = NIFTI_INTENT_LABEL. The filename with the labels may stored in aux_file. */ const int32_t NIFTI_INTENT_LABEL =1002; /*! To signify that the value at each voxel is an index into the NeuroNames labels set, set intent_code = NIFTI_INTENT_NEURONAME. */ const int32_t NIFTI_INTENT_NEURONAME=1003; /*! To store an M x N matrix at each voxel: - dataset must have a 5th dimension (dim[0]=5 and dim[5]>1) - intent_code must be NIFTI_INTENT_GENMATRIX - dim[5] must be M*N - intent_p1 must be M (in float format) - intent_p2 must be N (ditto) - the matrix values A[i][[j] are stored in row-order: - A[0][0] A[0][1] ... A[0][N-1] - A[1][0] A[1][1] ... A[1][N-1] - etc., until - A[M-1][0] A[M-1][1] ... A[M-1][N-1] */ const int32_t NIFTI_INTENT_GENMATRIX=1004; /*! To store an NxN symmetric matrix at each voxel: - dataset must have a 5th dimension - intent_code must be NIFTI_INTENT_SYMMATRIX - dim[5] must be N*(N+1)/2 - intent_p1 must be N (in float format) - the matrix values A[i][[j] are stored in row-order: - A[0][0] - A[1][0] A[1][1] - A[2][0] A[2][1] A[2][2] - etc.: row-by-row */ const int32_t NIFTI_INTENT_SYMMATRIX=1005; /*! To signify that the vector value at each voxel is to be taken as a displacement field or vector: - dataset must have a 5th dimension - intent_code must be NIFTI_INTENT_DISPVECT - dim[5] must be the dimensionality of the displacment vector (e.g., 3 for spatial displacement, 2 for in-plane) */ const int32_t NIFTI_INTENT_DISPVECT =1006; /* specifically for displacements */ const int32_t NIFTI_INTENT_VECTOR =1007; /* for any other type of vector */ /*! To signify that the vector value at each voxel is really a spatial coordinate (e.g., the vertices or nodes of a surface mesh): - dataset must have a 5th dimension - intent_code must be NIFTI_INTENT_POINTSET - dim[0] = 5 - dim[1] = number of points - dim[2] = dim[3] = dim[4] = 1 - dim[5] must be the dimensionality of space (e.g., 3 => 3D space). - intent_name may describe the object these points come from (e.g., "pial", "gray/white" , "EEG", "MEG"). */ const int32_t NIFTI_INTENT_POINTSET =1008; /*! To signify that the vector value at each voxel is really a triple of indexes (e.g., forming a triangle) from a pointset dataset: - dataset must have a 5th dimension - intent_code must be NIFTI_INTENT_TRIANGLE - dim[0] = 5 - dim[1] = number of triangles - dim[2] = dim[3] = dim[4] = 1 - dim[5] = 3 - datatype should be an integer type (preferably DT_INT32) - the data values are indexes (0,1,...) into a pointset dataset. */ const int32_t NIFTI_INTENT_TRIANGLE =1009; /*! To signify that the vector value at each voxel is a quaternion: - dataset must have a 5th dimension - intent_code must be NIFTI_INTENT_QUATERNION - dim[0] = 5 - dim[5] = 4 - datatype should be a floating point type */ const int32_t NIFTI_INTENT_QUATERNION=1010; /*! Dimensionless value - no params - although, as in _ESTIMATE the name of the parameter may be stored in intent_name. */ const int32_t NIFTI_INTENT_DIMLESS =1011; /* @} */ /*---------------------------------------------------------------------------*/ /* 3D IMAGE (VOLUME) ORIENTATION AND LOCATION IN SPACE: --------------------------------------------------- There are 3 different methods by which continuous coordinates can attached to voxels. The discussion below emphasizes 3D volumes, and the continuous coordinates are referred to as (x,y,z). The voxel index coordinates (i.e., the array indexes) are referred to as (i,j,k), with valid ranges: i = 0 .. dim[1]-1 j = 0 .. dim[2]-1 (if dim[0] >= 2) k = 0 .. dim[3]-1 (if dim[0] >= 3) The (x,y,z) coordinates refer to the CENTER of a voxel. In methods 2 and 3, the (x,y,z) axes refer to a subject-based coordinate system, with +x = Right +y = Anterior +z = Superior. This is a right-handed coordinate system. However, the exact direction these axes point with respect to the subject depends on qform_code (Method 2) and sform_code (Method 3). N.B.: The i index varies most rapidly, j index next, k index slowest. Thus, voxel (i,j,k) is stored starting at location (i + j*dim[1] + k*dim[1]*dim[2]) * (bitpix/8) into the dataset array. N.B.: The ANALYZE 7.5 coordinate system is +x = Left +y = Anterior +z = Superior which is a left-handed coordinate system. This backwardness is too difficult to tolerate, so this NIFTI-1 standard specifies the coordinate order which is most common in functional neuroimaging. N.B.: The 3 methods below all give the locations of the voxel centers in the (x,y,z) coordinate system. In many cases, programs will wish to display image data on some other grid. In such a case, the program will need to convert its desired (x,y,z) values into (i,j,k) values in order to extract (or interpolate) the image data. This operation would be done with the inverse transformation to those described below. N.B.: Method 2 uses a factor 'qfac' which is either -1 or 1; qfac is stored in the otherwise unused pixdim[0]. If pixdim[0]=0.0 (which should not occur), we take qfac=1. Of course, pixdim[0] is only used when reading a NIFTI-1 header, not when reading an ANALYZE 7.5 header. N.B.: The units of (x,y,z) can be specified using the xyzt_units field. METHOD 1 (the "old" way, used only when qform_code = 0): ------------------------------------------------------- The coordinate mapping from (i,j,k) to (x,y,z) is the ANALYZE 7.5 way. This is a simple scaling relationship: x = pixdim[1] * i y = pixdim[2] * j z = pixdim[3] * k No particular spatial orientation is attached to these (x,y,z) coordinates. (NIFTI-1 does not have the ANALYZE 7.5 orient field, which is not general and is often not set properly.) This method is not recommended, and is present mainly for compatibility with ANALYZE 7.5 files. METHOD 2 (used when qform_code > 0, which should be the "normal" case): --------------------------------------------------------------------- The (x,y,z) coordinates are given by the pixdim[] scales, a rotation matrix, and a shift. This method is intended to represent "scanner-anatomical" coordinates, which are often embedded in the image header (e.g., DICOM fields (0020,0032), (0020,0037), (0028,0030), and (0018,0050)), and represent the nominal orientation and location of the data. This method can also be used to represent "aligned" coordinates, which would typically result from some post-acquisition alignment of the volume to a standard orientation (e.g., the same subject on another day, or a rigid rotation to true anatomical orientation from the tilted position of the subject in the scanner). The formula for (x,y,z) in terms of header parameters and (i,j,k) is: [ x ] [ R11 R12 R13 ] [ pixdim[1] * i ] [ qoffset_x ] [ y ] = [ R21 R22 R23 ] [ pixdim[2] * j ] + [ qoffset_y ] [ z ] [ R31 R32 R33 ] [ qfac * pixdim[3] * k ] [ qoffset_z ] The qoffset_* shifts are in the NIFTI-1 header. Note that the center of the (i,j,k)=(0,0,0) voxel (first value in the dataset array) is just (x,y,z)=(qoffset_x,qoffset_y,qoffset_z). The rotation matrix R is calculated from the quatern_* parameters. This calculation is described below. The scaling factor qfac is either 1 or -1. The rotation matrix R defined by the quaternion parameters is "proper" (has determinant 1). This may not fit the needs of the data; for example, if the image grid is i increases from Left-to-Right j increases from Anterior-to-Posterior k increases from Inferior-to-Superior Then (i,j,k) is a left-handed triple. In this example, if qfac=1, the R matrix would have to be [ 1 0 0 ] [ 0 -1 0 ] which is "improper" (determinant = -1). [ 0 0 1 ] If we set qfac=-1, then the R matrix would be [ 1 0 0 ] [ 0 -1 0 ] which is proper. [ 0 0 -1 ] This R matrix is represented by quaternion [a,b,c,d] = [0,1,0,0] (which encodes a 180 degree rotation about the x-axis). METHOD 3 (used when sform_code > 0): ----------------------------------- The (x,y,z) coordinates are given by a general affine transformation of the (i,j,k) indexes: x = srow_x[0] * i + srow_x[1] * j + srow_x[2] * k + srow_x[3] y = srow_y[0] * i + srow_y[1] * j + srow_y[2] * k + srow_y[3] z = srow_z[0] * i + srow_z[1] * j + srow_z[2] * k + srow_z[3] The srow_* vectors are in the NIFTI_1 header. Note that no use is made of pixdim[] in this method. WHY 3 METHODS? -------------- Method 1 is provided only for backwards compatibility. The intention is that Method 2 (qform_code > 0) represents the nominal voxel locations as reported by the scanner, or as rotated to some fiducial orientation and location. Method 3, if present (sform_code > 0), is to be used to give the location of the voxels in some standard space. The sform_code indicates which standard space is present. Both methods 2 and 3 can be present, and be useful in different contexts (method 2 for displaying the data on its original grid; method 3 for displaying it on a standard grid). In this scheme, a dataset would originally be set up so that the Method 2 coordinates represent what the scanner reported. Later, a registration to some standard space can be computed and inserted in the header. Image display software can use either transform, depending on its purposes and needs. In Method 2, the origin of coordinates would generally be whatever the scanner origin is; for example, in MRI, (0,0,0) is the center of the gradient coil. In Method 3, the origin of coordinates would depend on the value of sform_code; for example, for the Talairach coordinate system, (0,0,0) corresponds to the Anterior Commissure. QUATERNION REPRESENTATION OF ROTATION MATRIX (METHOD 2) ------------------------------------------------------- The orientation of the (x,y,z) axes relative to the (i,j,k) axes in 3D space is specified using a unit quaternion [a,b,c,d], where a*a+b*b+c*c+d*d=1. The (b,c,d) values are all that is needed, since we require that a = sqrt(1.0-(b*b+c*c+d*d)) be nonnegative. The (b,c,d) values are stored in the (quatern_b,quatern_c,quatern_d) fields. The quaternion representation is chosen for its compactness in representing rotations. The (proper) 3x3 rotation matrix that corresponds to [a,b,c,d] is [ a*a+b*b-c*c-d*d 2*b*c-2*a*d 2*b*d+2*a*c ] R = [ 2*b*c+2*a*d a*a+c*c-b*b-d*d 2*c*d-2*a*b ] [ 2*b*d-2*a*c 2*c*d+2*a*b a*a+d*d-c*c-b*b ] [ R11 R12 R13 ] = [ R21 R22 R23 ] [ R31 R32 R33 ] If (p,q,r) is a unit 3-vector, then rotation of angle h about that direction is represented by the quaternion [a,b,c,d] = [cos(h/2), p*sin(h/2), q*sin(h/2), r*sin(h/2)]. Requiring a >= 0 is equivalent to requiring -Pi <= h <= Pi. (Note that [-a,-b,-c,-d] represents the same rotation as [a,b,c,d]; there are 2 quaternions that can be used to represent a given rotation matrix R.) To rotate a 3-vector (x,y,z) using quaternions, we compute the quaternion product [0,x',y',z'] = [a,b,c,d] * [0,x,y,z] * [a,-b,-c,-d] which is equivalent to the matrix-vector multiply [ x' ] [ x ] [ y' ] = R [ y ] (equivalence depends on a*a+b*b+c*c+d*d=1) [ z' ] [ z ] Multiplication of 2 quaternions is defined by the following: [a,b,c,d] = a*1 + b*I + c*J + d*K where I*I = J*J = K*K = -1 (I,J,K are square roots of -1) I*J = K J*K = I K*I = J J*I = -K K*J = -I I*K = -J (not commutative!) For example [a,b,0,0] * [0,0,0,1] = [0,0,-b,a] since this expands to (a+b*I)*(K) = (a*K+b*I*K) = (a*K-b*J). The above formula shows how to go from quaternion (b,c,d) to rotation matrix and direction cosines. Conversely, given R, we can compute the fields for the NIFTI-1 header by a = 0.5 * sqrt(1+R11+R22+R33) (not stored) b = 0.25 * (R32-R23) / a => quatern_b c = 0.25 * (R13-R31) / a => quatern_c d = 0.25 * (R21-R12) / a => quatern_d If a=0 (a 180 degree rotation), alternative formulas are needed. See the nifti1_io.c function mat44_to_quatern() for an implementation of the various cases in converting R to [a,b,c,d]. Note that R-transpose (= R-inverse) would lead to the quaternion [a,-b,-c,-d]. The choice to specify the qoffset_x (etc.) values in the final coordinate system is partly to make it easy to convert DICOM images to this format. The DICOM attribute "Image Position (Patient)" (0020,0032) stores the (Xd,Yd,Zd) coordinates of the center of the first voxel. Here, (Xd,Yd,Zd) refer to DICOM coordinates, and Xd=-x, Yd=-y, Zd=z, where (x,y,z) refers to the NIFTI coordinate system discussed above. (i.e., DICOM +Xd is Left, +Yd is Posterior, +Zd is Superior, whereas +x is Right, +y is Anterior , +z is Superior. ) Thus, if the (0020,0032) DICOM attribute is extracted into (px,py,pz), then qoffset_x = -px qoffset_y = -py qoffset_z = pz is a reasonable setting when qform_code=NIFTI_XFORM_SCANNER_ANAT. That is, DICOM's coordinate system is 180 degrees rotated about the z-axis from the neuroscience/NIFTI coordinate system. To transform between DICOM and NIFTI, you just have to negate the x- and y-coordinates. The DICOM attribute (0020,0037) "Image Orientation (Patient)" gives the orientation of the x- and y-axes of the image data in terms of 2 3-vectors. The first vector is a unit vector along the x-axis, and the second is along the y-axis. If the (0020,0037) attribute is extracted into the value (xa,xb,xc,ya,yb,yc), then the first two columns of the R matrix would be [ -xa -ya ] [ -xb -yb ] [ xc yc ] The negations are because DICOM's x- and y-axes are reversed relative to NIFTI's. The third column of the R matrix gives the direction of displacement (relative to the subject) along the slice-wise direction. This orientation is not encoded in the DICOM standard in a simple way; DICOM is mostly concerned with 2D images. The third column of R will be either the cross-product of the first 2 columns or its negative. It is possible to infer the sign of the 3rd column by examining the coordinates in DICOM attribute (0020,0032) "Image Position (Patient)" for successive slices. However, this method occasionally fails for reasons that I (RW Cox) do not understand. -----------------------------------------------------------------------------*/ /* [qs]form_code value: */ /* x,y,z coordinate system refers to: */ /*-----------------------*/ /*---------------------------------------*/ /*! \defgroup NIFTI1_XFORM_CODES \brief nifti1 xform codes to describe the "standard" coordinate system @{ */ /*! Arbitrary coordinates (Method 1). */ const int32_t NIFTI_XFORM_UNKNOWN =0; /*! Scanner-based anatomical coordinates */ const int32_t NIFTI_XFORM_SCANNER_ANAT=1; /*! Coordinates aligned to another file's, or to anatomical "truth". */ const int32_t NIFTI_XFORM_ALIGNED_ANAT=2; /*! Coordinates aligned to Talairach- Tournoux Atlas; (0,0,0)=AC, etc. */ const int32_t NIFTI_XFORM_TALAIRACH =3; /*! MNI 152 normalized coordinates. */ const int32_t NIFTI_XFORM_MNI_152 =4; /* @} */ /*---------------------------------------------------------------------------*/ /* UNITS OF SPATIAL AND TEMPORAL DIMENSIONS: ---------------------------------------- The codes below can be used in xyzt_units to indicate the units of pixdim. As noted earlier, dimensions 1,2,3 are for x,y,z; dimension 4 is for time (t). - If dim[4]=1 or dim[0] < 4, there is no time axis. - A single time series (no space) would be specified with - dim[0] = 4 (for scalar data) or dim[0] = 5 (for vector data) - dim[1] = dim[2] = dim[3] = 1 - dim[4] = number of time points - pixdim[4] = time step - xyzt_units indicates units of pixdim[4] - dim[5] = number of values stored at each time point Bits 0..2 of xyzt_units specify the units of pixdim[1..3] (e.g., spatial units are values 1..7). Bits 3..5 of xyzt_units specify the units of pixdim[4] (e.g., temporal units are multiples of 8). This compression of 2 distinct concepts into 1 byte is due to the limited space available in the 348 byte ANALYZE 7.5 header. The macros XYZT_TO_SPACE and XYZT_TO_TIME can be used to mask off the undesired bits from the xyzt_units fields, leaving "pure" space and time codes. Inversely, the macro SPACE_TIME_TO_XYZT can be used to assemble a space code (0,1,2,...,7) with a time code (0,8,16,32,...,56) into the combined value for xyzt_units. Note that codes are provided to indicate the "time" axis units are actually frequency in Hertz (_HZ), in part-per-million (_PPM) or in radians-per-second (_RADS). The toffset field can be used to indicate a nonzero start point for the time axis. That is, time point #m is at t=toffset+m*pixdim[4] for m=0..dim[4]-1. -----------------------------------------------------------------------------*/ /*! \defgroup NIFTI1_UNITS \brief nifti1 units codes to describe the unit of measurement for each dimension of the dataset @{ */ /*! NIFTI code for unspecified units. */ const int32_t NIFTI_UNITS_UNKNOWN=0; /** Space codes are multiples of 1. **/ /*! NIFTI code for meters. */ const int32_t NIFTI_UNITS_METER =1; /*! NIFTI code for millimeters. */ const int32_t NIFTI_UNITS_MM =2; /*! NIFTI code for micrometers. */ const int32_t NIFTI_UNITS_MICRON =3; /** Time codes are multiples of 8. **/ /*! NIFTI code for seconds. */ const int32_t NIFTI_UNITS_SEC =8; /*! NIFTI code for milliseconds. */ const int32_t NIFTI_UNITS_MSEC =16; /*! NIFTI code for microseconds. */ const int32_t NIFTI_UNITS_USEC =24; /*** These units are for spectral data: ***/ /*! NIFTI code for Hertz. */ const int32_t NIFTI_UNITS_HZ =32; /*! NIFTI code for ppm. */ const int32_t NIFTI_UNITS_PPM =40; /*! NIFTI code for radians per second. */ const int32_t NIFTI_UNITS_RADS =48; /* @} */ #undef XYZT_TO_SPACE #undef XYZT_TO_TIME #define XYZT_TO_SPACE(xyzt) ( (xyzt) & 0x07 ) #define XYZT_TO_TIME(xyzt) ( (xyzt) & 0x38 ) #undef SPACE_TIME_TO_XYZT #define SPACE_TIME_TO_XYZT(ss,tt) ( (((char)(ss)) & 0x07) \ | (((char)(tt)) & 0x38) ) /*---------------------------------------------------------------------------*/ /* MRI-SPECIFIC SPATIAL AND TEMPORAL INFORMATION: --------------------------------------------- A few fields are provided to store some extra information that is sometimes important when storing the image data from an FMRI time series experiment. (After processing such data into statistical images, these fields are not likely to be useful.) { freq_dim } = These fields encode which spatial dimension (1,2, or 3) { phase_dim } = corresponds to which acquisition dimension for MRI data. { slice_dim } = Examples: Rectangular scan multi-slice EPI: freq_dim = 1 phase_dim = 2 slice_dim = 3 (or some permutation) Spiral scan multi-slice EPI: freq_dim = phase_dim = 0 slice_dim = 3 since the concepts of frequency- and phase-encoding directions don't apply to spiral scan slice_duration = If this is positive, AND if slice_dim is nonzero, indicates the amount of time used to acquire 1 slice. slice_duration*dim[slice_dim] can be less than pixdim[4] with a clustered acquisition method, for example. slice_code = If this is nonzero, AND if slice_dim is nonzero, AND if slice_duration is positive, indicates the timing pattern of the slice acquisition. The following codes are defined: NIFTI_SLICE_SEQ_INC == sequential increasing NIFTI_SLICE_SEQ_DEC == sequential decreasing NIFTI_SLICE_ALT_INC == alternating increasing NIFTI_SLICE_ALT_DEC == alternating decreasing NIFTI_SLICE_ALT_INC2 == alternating increasing #2 NIFTI_SLICE_ALT_DEC2 == alternating decreasing #2 { slice_start } = Indicates the start and end of the slice acquisition { slice_end } = pattern, when slice_code is nonzero. These values are present to allow for the possible addition of "padded" slices at either end of the volume, which don't fit into the slice timing pattern. If there are no padding slices, then slice_start=0 and slice_end=dim[slice_dim]-1 are the correct values. For these values to be meaningful, slice_start must be non-negative and slice_end must be greater than slice_start. Otherwise, they should be ignored. The following table indicates the slice timing pattern, relative to time=0 for the first slice acquired, for some sample cases. Here, dim[slice_dim]=7 (there are 7 slices, labeled 0..6), slice_duration=0.1, and slice_start=1, slice_end=5 (1 padded slice on each end). slice index SEQ_INC SEQ_DEC ALT_INC ALT_DEC ALT_INC2 ALT_DEC2 6 : n/a n/a n/a n/a n/a n/a n/a = not applicable 5 : 0.4 0.0 0.2 0.0 0.4 0.2 (slice time offset 4 : 0.3 0.1 0.4 0.3 0.1 0.0 doesn't apply to 3 : 0.2 0.2 0.1 0.1 0.3 0.3 slices outside 2 : 0.1 0.3 0.3 0.4 0.0 0.1 the range 1 : 0.0 0.4 0.0 0.2 0.2 0.4 slice_start .. 0 : n/a n/a n/a n/a n/a n/a slice_end) The SEQ slice_codes are sequential ordering (uncommon but not unknown), either increasing in slice number or decreasing (INC or DEC), as illustrated above. The ALT slice codes are alternating ordering. The 'standard' way for these to operate (without the '2' on the end) is for the slice timing to start at the edge of the slice_start .. slice_end group (at slice_start for INC and at slice_end for DEC). For the 'ALT_*2' slice_codes, the slice timing instead starts at the first slice in from the edge (at slice_start+1 for INC2 and at slice_end-1 for DEC2). This latter acquisition scheme is found on some Siemens scanners. The fields freq_dim, phase_dim, slice_dim are all squished into the single byte field dim_info (2 bits each, since the values for each field are limited to the range 0..3). This unpleasantness is due to lack of space in the 348 byte allowance. The macros DIM_INFO_TO_FREQ_DIM, DIM_INFO_TO_PHASE_DIM, and DIM_INFO_TO_SLICE_DIM can be used to extract these values from the dim_info byte. The macro FPS_INTO_DIM_INFO can be used to put these 3 values into the dim_info byte. -----------------------------------------------------------------------------*/ #undef DIM_INFO_TO_FREQ_DIM #undef DIM_INFO_TO_PHASE_DIM #undef DIM_INFO_TO_SLICE_DIM #define DIM_INFO_TO_FREQ_DIM(di) ( ((di) ) & 0x03 ) #define DIM_INFO_TO_PHASE_DIM(di) ( ((di) >> 2) & 0x03 ) #define DIM_INFO_TO_SLICE_DIM(di) ( ((di) >> 4) & 0x03 ) #undef FPS_INTO_DIM_INFO #define FPS_INTO_DIM_INFO(fd,pd,sd) ( ( ( ((char)(fd)) & 0x03) ) | \ ( ( ((char)(pd)) & 0x03) << 2 ) | \ ( ( ((char)(sd)) & 0x03) << 4 ) ) /*! \defgroup NIFTI1_SLICE_ORDER \brief nifti1 slice order codes, describing the acquisition order of the slices @{ */ const int32_t NIFTI_SLICE_UNKNOWN =0; const int32_t NIFTI_SLICE_SEQ_INC =1; const int32_t NIFTI_SLICE_SEQ_DEC =2; const int32_t NIFTI_SLICE_ALT_INC =3; const int32_t NIFTI_SLICE_ALT_DEC =4; const int32_t NIFTI_SLICE_ALT_INC2 =5; /* 05 May 2005: RWCox */ const int32_t NIFTI_SLICE_ALT_DEC2 =6; /* 05 May 2005: RWCox */ /* @} */ /*---------------------------------------------------------------------------*/ /* UNUSED FIELDS: ------------- Some of the ANALYZE 7.5 fields marked as ++UNUSED++ may need to be set to particular values for compatibility with other programs. The issue of interoperability of ANALYZE 7.5 files is a murky one -- not all programs require exactly the same set of fields. (Unobscuring this murkiness is a principal motivation behind NIFTI-1.) Some of the fields that may need to be set for other (non-NIFTI aware) software to be happy are: extents dbh.h says this should be 16384 regular dbh.h says this should be the character 'r' glmin, } dbh.h says these values should be the min and max voxel glmax } values for the entire dataset It is best to initialize ALL fields in the NIFTI-1 header to 0 (e.g., with calloc()), then fill in what is needed. -----------------------------------------------------------------------------*/ /*---------------------------------------------------------------------------*/ /* MISCELLANEOUS C MACROS -----------------------------------------------------------------------------*/ /*.................*/ /*! Given a nifti_1_header struct, check if it has a good magic number. Returns NIFTI version number (1..9) if magic is good, 0 if it is not. */ #define NIFTI_VERSION(h) \ ( ( (h).magic[0]=='n' && (h).magic[3]=='\0' && \ ( (h).magic[1]=='i' || (h).magic[1]=='+' ) && \ ( (h).magic[2]>='1' && (h).magic[2]<='9' ) ) \ ? (h).magic[2]-'0' : 0 ) /*.................*/ /*! Check if a nifti_1_header struct says if the data is stored in the same file or in a separate file. Returns 1 if the data is in the same file as the header, 0 if it is not. */ #define NIFTI_ONEFILE(h) ( (h).magic[1] == '+' ) /*.................*/ /*! Check if a nifti_1_header struct needs to be byte swapped. Returns 1 if it needs to be swapped, 0 if it does not. */ #define NIFTI_NEEDS_SWAP(h) ( (h).dim[0] < 0 || (h).dim[0] > 7 ) /*.................*/ /*! Check if a nifti_1_header struct contains a 5th (vector) dimension. Returns size of 5th dimension if > 1, returns 0 otherwise. */ #define NIFTI_5TH_DIM(h) ( ((h).dim[0]>4 && (h).dim[5]>1) ? (h).dim[5] : 0 ) /*****************************************************************************/ /*=================*/ #ifdef __cplusplus } #endif /*=================*/ #endif /* _NIFTI_HEADER_ */ workbench-1.1.1/src/FilesBase/nifti2.h000066400000000000000000000152551255417355300175060ustar00rootroot00000000000000#ifndef __NIFTI2_HEADER #define __NIFTI2_HEADER /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "nifti1.h" /*=================*/ #ifdef __cplusplus extern "C" { #endif /*=================*/ #include /*extended nifti intent codes*/ const int32_t NIFTI_INTENT_CONNECTIVITY_UNKNOWN=3000; const int32_t NIFTI_INTENT_CONNECTIVITY_DENSE=3001; const int32_t NIFTI_INTENT_CONNECTIVITY_DENSE_TIME=3002;//CIFTI-1 name const int32_t NIFTI_INTENT_CONNECTIVITY_DENSE_SERIES=3002;//CIFTI-2 name const int32_t NIFTI_INTENT_CONNECTIVITY_PARCELLATED=3003; const int32_t NIFTI_INTENT_CONNECTIVITY_PARCELLATED_TIME=3004;//ditto const int32_t NIFTI_INTENT_CONNECTIVITY_PARCELLATED_SERIES=3004; const int32_t NIFTI_INTENT_CONNECTIVITY_CONNECTIVITY_TRAJECTORY=3005;//ditto const int32_t NIFTI_INTENT_CONNECTIVITY_DENSE_TRAJECTORY=3005; const int32_t NIFTI_INTENT_CONNECTIVITY_DENSE_SCALARS=3006; const int32_t NIFTI_INTENT_CONNECTIVITY_DENSE_LABELS=3007; const int32_t NIFTI_INTENT_CONNECTIVITY_PARCELLATED_SCALAR=3008; const int32_t NIFTI_INTENT_CONNECTIVITY_PARCELLATED_DENSE=3009; const int32_t NIFTI_INTENT_CONNECTIVITY_DENSE_PARCELLATED=3010; const int32_t NIFTI_INTENT_CONNECTIVITY_PARCELLATED_PARCELLATED_SERIES=3011; const int32_t NIFTI_INTENT_CONNECTIVITY_PARCELLATED_PARCELLATED_SCALAR=3012; const int32_t NIFTI_ECODE_CIFTI=32; #define NIFTI2_VERSION(h) \ (h).sizeof_hdr == 348 ? 1 : (\ (h).sizeof_hdr == 1543569408 ? 1 : (\ (h).sizeof_hdr == 540 ? 2 : (\ (h).sizeof_hdr == 469893120 ? 2 : 0))) #define NIFTI2_NEEDS_SWAP(h) \ (h).sizeof_hdr == 469893120 ? 1 : (\ (h).sizeof_hdr == 1543569408 ? 1 : 0) //hopefully cross-platform solution to byte padding added by some compilers #pragma pack(push) #pragma pack(1) /*! \struct nifti_2_header \brief Data structure defining the fields in the nifti2 header. This binary header should be found at the beginning of a valid NIFTI-2 header file. */ /*************************/ /************************/ /************/ struct nifti_2_header { /* NIFTI-2 usage */ /* NIFTI-1 usage */ /* offset */ /*************************/ /************************/ /************/ int32_t sizeof_hdr; /*!< MUST be 540 */ /* int32_t sizeof_hdr; (348) */ /* 0 */ char magic[8] ; /*!< MUST be valid signature. */ /* char magic[4]; */ /* 4 */ int16_t datatype; /*!< Defines data type! */ /* short datatype; */ /* 12 */ int16_t bitpix; /*!< Number bits/voxel. */ /* short bitpix; */ /* 14 */ int64_t dim[8]; /*!< Data array dimensions.*/ /* short dim[8]; */ /* 16 */ double intent_p1 ; /*!< 1st intent parameter. */ /* float intent_p1; */ /* 80 */ double intent_p2 ; /*!< 2nd intent parameter. */ /* float intent_p2; */ /* 88 */ double intent_p3 ; /*!< 3rd intent parameter. */ /* float intent_p3; */ /* 96 */ double pixdim[8]; /*!< Grid spacings. */ /* float pixdim[8]; */ /* 104 */ int64_t vox_offset; /*!< Offset into .nii file */ /* float vox_offset; */ /* 168 */ double scl_slope ; /*!< Data scaling: slope. */ /* float scl_slope; */ /* 176 */ double scl_inter ; /*!< Data scaling: offset. */ /* float scl_inter; */ /* 184 */ double cal_max; /*!< Max display intensity */ /* float cal_max; */ /* 192 */ double cal_min; /*!< Min display intensity */ /* float cal_min; */ /* 200 */ double slice_duration;/*!< Time for 1 slice. */ /* float slice_duration; */ /* 208 */ double toffset; /*!< Time axis shift. */ /* float toffset; */ /* 216 */ int64_t slice_start;/*!< First slice index. */ /* short slice_start; */ /* 224 */ int64_t slice_end; /*!< Last slice index. */ /* short slice_end; */ /* 232 */ char descrip[80]; /*!< any text you like. */ /* char descrip[80]; */ /* 240 */ char aux_file[24]; /*!< auxiliary filename. */ /* char aux_file[24]; */ /* 320 */ int32_t qform_code ; /*!< NIFTI_XFORM_* code. */ /* short qform_code; */ /* 344 */ int32_t sform_code ; /*!< NIFTI_XFORM_* code. */ /* short sform_code; */ /* 348 */ double quatern_b ; /*!< Quaternion b param. */ /* float quatern_b; */ /* 352 */ double quatern_c ; /*!< Quaternion c param. */ /* float quatern_c; */ /* 360 */ double quatern_d ; /*!< Quaternion d param. */ /* float quatern_d; */ /* 368 */ double qoffset_x ; /*!< Quaternion x shift. */ /* float qoffset_x; */ /* 376 */ double qoffset_y ; /*!< Quaternion y shift. */ /* float qoffset_y; */ /* 384 */ double qoffset_z ; /*!< Quaternion z shift. */ /* float qoffset_z; */ /* 392 */ double srow_x[4] ; /*!< 1st row affine transform. */ /* float srow_x[4]; */ /* 400 */ double srow_y[4] ; /*!< 2nd row affine transform. */ /* float srow_y[4]; */ /* 432 */ double srow_z[4] ; /*!< 3rd row affine transform. */ /* float srow_z[4]; */ /* 464 */ int32_t slice_code ; /*!< Slice timing order. */ /* char slice_code; */ /* 496 */ int32_t xyzt_units ; /*!< Units of pixdim[1..4] */ /* char xyzt_units; */ /* 500 */ int32_t intent_code ; /*!< NIFTI_INTENT_* code. */ /* short intent_code; */ /* 504 */ char intent_name[16]; /*!< 'name' or meaning of data. */ /* char intent_name[16]; */ /* 508 */ char dim_info; /*!< MRI slice ordering. */ /* char dim_info; */ /* 524 */ char unused_str[15]; /*!< unused, filled with \0 */ /* 525 */ } ; /**** 540 bytes total ****/ typedef struct nifti_2_header nifti_2_header ; //and restore packing behavior #pragma pack(pop) /*=================*/ #ifdef __cplusplus } #endif /*=================*/ #endif //__NIFTI2_HEADER workbench-1.1.1/src/FtglFont/000077500000000000000000000000001255417355300160205ustar00rootroot00000000000000workbench-1.1.1/src/FtglFont/AUTHORS000066400000000000000000000021351255417355300170710ustar00rootroot00000000000000 -*- coding: utf-8 -*- Original author: Henry Maddocks http://homepages.paradise.net.nz/henryj/ Contributors: Jed Soane (Bezier curve code) Gérard Lanois (demo, Linux port, extrusion code, gltt maintainance) Matthias Kretz (Linux port) Andrew Ellerton (Windows port) Max Rheiner (Windows port) Sébastien Barré (containers and optimisations) Marcelo E. Magallon (original autoconf, bug fixes) Robert Bell (pixmap font modifications) Sam Hocevar (build system, new maintainer) Éric Beets (C bindings) Christopher Sean Morrison (bug fixes, new maintainer) Jeff Myers (JeffM2501) (Windows fixes) Daniel Remenak (Windows fixes) Portions derived from ConvertUTF.c Copyright (C) 2001-2004 Unicode, Inc. Bug fixes: Robert Osfield Markku Rontu Mark A. Fox Patrick Rogers Kai Huettemann FTGL was inspired by gltt, Copyright (C) 1998-1999 Stephane Rehel (http://gltt.sourceforge.net) workbench-1.1.1/src/FtglFont/CMakeLists.txt000066400000000000000000000054541255417355300205700ustar00rootroot00000000000000# # Name of project # PROJECT (Ftgl) # # Need XML from Qt # SET(QT_DONT_USE_QTGUI) # # Use OpenGL from QT # #SET(QT_USE_QTOPENGL TRUE) # # Add QT for includes # INCLUDE (${QT_USE_FILE}) # # Need OpenGL # FIND_PACKAGE(OpenGL REQUIRED) IF (OPENGL_FOUND) # # Need help finding includes on Apple # IF (APPLE) # When searching for the include directory, find the location # for the OpenGL framework rather than an individual header file. FIND_PATH(OPENGL_INCLUDE_DIR OpenGL.framework /System/Library/Frameworks /Library/Frameworks ~/Library/Frameworks ) ENDIF (APPLE) # # OpenGL Include Directory # INCLUDE_DIRECTORIES(${OPENGL_INCLUDE_DIR}) ELSE (OPENGL_FOUND) MESSAGE(FATAL_ERROR "OpenGL Libraries were not found") ENDIF (OPENGL_FOUND) # # Create the brain library # ADD_LIBRARY(FtglFont FTCharToGlyphIndexMap.h FTContour.h FTFace.h FTFont/FTBitmapFontImpl.h FTFont/FTBufferFontImpl.h FTFont/FTExtrudeFontImpl.h FTFont/FTFontImpl.h FTFont/FTOutlineFontImpl.h FTFont/FTPixmapFontImpl.h FTFont/FTPolygonFontImpl.h FTFont/FTTextureFontImpl.h FTGL/FTBBox.h FTGL/FTBitmapGlyph.h FTGL/FTBuffer.h FTGL/FTBufferFont.h FTGL/FTBufferGlyph.h FTGL/FTExtrdGlyph.h FTGL/FTFont.h FTGL/ftgl.h FTGL/FTGLBitmapFont.h FTGL/FTGLExtrdFont.h FTGL/FTGLOutlineFont.h FTGL/FTGLPixmapFont.h FTGL/FTGLPolygonFont.h FTGL/FTGLTextureFont.h FTGL/FTGLGlyph.h FTGL/FTLayout.h FTGL/FTOutlineGlyph.h FTGL/FTPixmapGlyph.h FTGL/FTPoint.h FTGL/FTPolyGlyph.h FTGL/FTSimpleLayout.h FTGL/FTTextureGlyph.h FTGlyph/FTBitmapGlyphImpl.h FTGlyph/FTBufferGlyphImpl.h FTGlyph/FTExtrudeGlyphImpl.h FTGlyph/FTGlyphImpl.h FTGlyph/FTOutlineGlyphImpl.h FTGlyph/FTPixmapGlyphImpl.h FTGlyph/FTPolygonGlyphImpl.h FTGlyph/FTTextureGlyphImpl.h FTGlyphContainer.h FTInternals.h FTLayout/FTLayoutImpl.h FTLayout/FTSimpleLayoutImpl.h FTLibrary.h FTList.h FTSize.h FTUnicode.h FTVector.h FTVectoriser.h FtglConfig.h FTBuffer.cpp FTCharmap.cpp FTContour.cpp FTFace.cpp FTFont/FTBitmapFont.cpp FTFont/FTBufferFont.cpp FTFont/FTExtrudeFont.cpp FTFont/FTFont.cpp FTFont/FTFontGlue.cpp FTFont/FTOutlineFont.cpp FTFont/FTPixmapFont.cpp FTFont/FTPolygonFont.cpp FTFont/FTTextureFont.cpp FTGlyph/FTBitmapGlyph.cpp FTGlyph/FTBufferGlyph.cpp FTGlyph/FTExtrudeGlyph.cpp FTGlyph/FTGlyph.cpp FTGlyph/FTGlyphGlue.cpp FTGlyph/FTOutlineGlyph.cpp FTGlyph/FTPixmapGlyph.cpp FTGlyph/FTPolygonGlyph.cpp FTGlyph/FTTextureGlyph.cpp FTGlyphContainer.cpp FTLayout/FTLayout.cpp FTLayout/FTLayoutGlue.cpp FTLayout/FTSimpleLayout.cpp FTLibrary.cpp FTPoint.cpp FTSize.cpp FTVectoriser.cpp ) # # Find Headers # INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/Common ${FREETYPE_INCLUDE_DIR_ft2build} ${FREETYPE_INCLUDE_DIR_freetype2} ${CMAKE_SOURCE_DIR}/FtglFont ${CMAKE_SOURCE_DIR}/FtglFont/FTFont ${CMAKE_SOURCE_DIR}/FtglFont/FTGL ${CMAKE_SOURCE_DIR}/FtglFont/FTGlyph ) workbench-1.1.1/src/FtglFont/COPYING000066400000000000000000000022671255417355300170620ustar00rootroot00000000000000FTGL Herewith is a license. Basically I want you to use this software and if you think this license is preventing you from doing so let me know. Copyright (C) 2001-3 Henry Maddocks Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. workbench-1.1.1/src/FtglFont/FTBuffer.cpp000066400000000000000000000032461255417355300201740ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2008 Sam Hocevar * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "FtglConfig.h" #include "FTGL/ftgl.h" FTBuffer::FTBuffer() : width(0), height(0), pixels(0), pos(FTPoint()) { } FTBuffer::~FTBuffer() { if(pixels) { delete[] pixels; } } void FTBuffer::Size(int w, int h) { if(w == width && h == height) { return; } if(w * h != width * height) { if(pixels) { delete[] pixels; } pixels = new unsigned char[w * h]; } memset(pixels, 0, w * h); width = w; height = h; } workbench-1.1.1/src/FtglFont/FTCharToGlyphIndexMap.h000066400000000000000000000115351255417355300222420ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2014 Washington University School of Medicine * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __FTCharToGlyphIndexMap__ #define __FTCharToGlyphIndexMap__ #include #include "FTGL/ftgl.h" /** * Provides a non-STL alternative to the STL map * which maps character codes to glyph indices inside FTCharmap. * * Implementation: * - NumberOfBuckets buckets are considered. * - Each bucket has BucketSize entries. * - When the glyph index for the character code C has to be stored, the * bucket this character belongs to is found using 'C div BucketSize'. * If this bucket has not been allocated yet, do it now. * The entry in the bucked is found using 'C mod BucketSize'. * If it is set to IndexNotFound, then the glyph entry has not been set. * - Try to mimic the calls made to the STL map API. * * Caveats: * - The glyph index is now a signed long instead of unsigned long, so * the special value IndexNotFound (= -1) can be used to specify that the * glyph index has not been stored yet. */ class FTCharToGlyphIndexMap { public: typedef unsigned long CharacterCode; typedef signed long GlyphIndex; enum { NumberOfBuckets = 256, BucketSize = 256, IndexNotFound = -1 }; FTCharToGlyphIndexMap() { this->Indices = 0; } virtual ~FTCharToGlyphIndexMap() { if(this->Indices) { // Free all buckets this->clear(); // Free main structure delete [] this->Indices; this->Indices = 0; } } void clear() { if(this->Indices) { for(int i = 0; i < FTCharToGlyphIndexMap::NumberOfBuckets; i++) { if(this->Indices[i]) { delete [] this->Indices[i]; this->Indices[i] = 0; } } } } /*const */GlyphIndex find(CharacterCode c) { if(!this->Indices) { return 0; } // Find position of char code in buckets div_t pos = div(c, FTCharToGlyphIndexMap::BucketSize); if(!this->Indices[pos.quot]) { return 0; } const FTCharToGlyphIndexMap::GlyphIndex *ptr = &this->Indices[pos.quot][pos.rem]; if(*ptr == FTCharToGlyphIndexMap::IndexNotFound) { return 0; } return *ptr; } void insert(CharacterCode c, GlyphIndex g) { if(!this->Indices) { this->Indices = new GlyphIndex* [FTCharToGlyphIndexMap::NumberOfBuckets]; for(int i = 0; i < FTCharToGlyphIndexMap::NumberOfBuckets; i++) { this->Indices[i] = 0; } } // Find position of char code in buckets div_t pos = div(c, FTCharToGlyphIndexMap::BucketSize); // Allocate bucket if does not exist yet if(!this->Indices[pos.quot]) { this->Indices[pos.quot] = new GlyphIndex [FTCharToGlyphIndexMap::BucketSize]; for(int i = 0; i < FTCharToGlyphIndexMap::BucketSize; i++) { this->Indices[pos.quot][i] = FTCharToGlyphIndexMap::IndexNotFound; } } this->Indices[pos.quot][pos.rem] = g; } private: GlyphIndex** Indices; }; #endif // __FTCharToGlyphIndexMap__ workbench-1.1.1/src/FtglFont/FTCharmap.cpp000066400000000000000000000052771255417355300203440ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "FtglConfig.h" #include "FTFace.h" #include "FTCharmap.h" FTCharmap::FTCharmap(FTFace* face) : ftFace(*(face->Face())), err(0) { if(!ftFace->charmap) { if(!ftFace->num_charmaps) { // This face doesn't even have one charmap! err = 0x96; // Invalid_CharMap_Format return; } err = FT_Set_Charmap(ftFace, ftFace->charmaps[0]); } ftEncoding = ftFace->charmap->encoding; for(unsigned int i = 0; i < FTCharmap::MAX_PRECOMPUTED; i++) { charIndexCache[i] = FT_Get_Char_Index(ftFace, i); } } FTCharmap::~FTCharmap() { charMap.clear(); } bool FTCharmap::CharMap(FT_Encoding encoding) { if(ftEncoding == encoding) { err = 0; return true; } err = FT_Select_Charmap(ftFace, encoding); if(!err) { ftEncoding = encoding; charMap.clear(); } return !err; } unsigned int FTCharmap::GlyphListIndex(const unsigned int characterCode) { return charMap.find(characterCode); } unsigned int FTCharmap::FontIndex(const unsigned int characterCode) { if(characterCode < FTCharmap::MAX_PRECOMPUTED) { return charIndexCache[characterCode]; } return FT_Get_Char_Index(ftFace, characterCode); } void FTCharmap::InsertIndex(const unsigned int characterCode, const size_t containerIndex) { charMap.insert(characterCode, static_cast(containerIndex)); } workbench-1.1.1/src/FtglFont/FTCharmap.h000066400000000000000000000122031255417355300177740ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __FTCharmap__ #define __FTCharmap__ #include #include FT_FREETYPE_H #include FT_GLYPH_H #include "FTGL/ftgl.h" #include "FTCharToGlyphIndexMap.h" /** * FTCharmap takes care of specifying the encoding for a font and mapping * character codes to glyph indices. * * It doesn't preprocess all indices, only on an as needed basis. This may * seem like a performance penalty but it is quicker than using the 'raw' * freetype calls and will save significant amounts of memory when dealing * with unicode encoding * * @see "Freetype 2 Documentation" * */ class FTFace; class FTCharmap { public: /** * Constructor */ FTCharmap(FTFace* face); /** * Destructor */ virtual ~FTCharmap(); /** * Queries for the current character map code. * * @return The current character map code. */ FT_Encoding Encoding() const { return ftEncoding; } /** * Sets the character map for the face. If an error occurs the object is not modified. * Valid encodings as at Freetype 2.0.4 * ft_encoding_none * ft_encoding_symbol * ft_encoding_unicode * ft_encoding_latin_2 * ft_encoding_sjis * ft_encoding_gb2312 * ft_encoding_big5 * ft_encoding_wansung * ft_encoding_johab * ft_encoding_adobe_standard * ft_encoding_adobe_expert * ft_encoding_adobe_custom * ft_encoding_apple_roman * * @param encoding the Freetype encoding symbol. See above. * @return true if charmap was valid and set * correctly. */ bool CharMap(FT_Encoding encoding); /** * Get the FTGlyphContainer index of the input character. * * @param characterCode The character code of the requested glyph in * the current encoding eg apple roman. * @return The FTGlyphContainer index for the character or zero * if it wasn't found */ unsigned int GlyphListIndex(const unsigned int characterCode); /** * Get the font glyph index of the input character. * * @param characterCode The character code of the requested glyph in * the current encoding eg apple roman. * @return The glyph index for the character. */ unsigned int FontIndex(const unsigned int characterCode); /** * Set the FTGlyphContainer index of the character code. * * @param characterCode The character code of the requested glyph in * the current encoding eg apple roman. * @param containerIndex The index into the FTGlyphContainer of the * character code. */ void InsertIndex(const unsigned int characterCode, const size_t containerIndex); /** * Queries for errors. * * @return The current error code. Zero means no error. */ FT_Error Error() const { return err; } private: /** * Current character map code. */ FT_Encoding ftEncoding; /** * The current Freetype face. */ const FT_Face ftFace; /** * A structure that maps glyph indices to character codes * * < character code, face glyph index> */ typedef FTCharToGlyphIndexMap CharacterMap; CharacterMap charMap; /** * Precomputed font indices. */ static const unsigned int MAX_PRECOMPUTED = 128; unsigned int charIndexCache[MAX_PRECOMPUTED]; /** * Current error code. */ FT_Error err; }; #endif // __FTCharmap__ workbench-1.1.1/src/FtglFont/FTContour.cpp000066400000000000000000000163611255417355300204160ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * Copyright (c) 2008 Éric Beets * Copyright (c) 2014 Washington University School of Medicine * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "FtglConfig.h" #include "FTContour.h" #include static const unsigned int BEZIER_STEPS = 5; void FTContour::AddPoint(FTPoint point) { if(pointList.empty() || (point != pointList[pointList.size() - 1] && point != pointList[0])) { pointList.push_back(point); } } void FTContour::AddOutsetPoint(FTPoint point) { outsetPointList.push_back(point); } void FTContour::AddFrontPoint(FTPoint point) { frontPointList.push_back(point); } void FTContour::AddBackPoint(FTPoint point) { backPointList.push_back(point); } void FTContour::evaluateQuadraticCurve(FTPoint A, FTPoint B, FTPoint C) { for(unsigned int i = 1; i < BEZIER_STEPS; i++) { float t = static_cast(i) / BEZIER_STEPS; FTPoint U = (1.0f - t) * A + t * B; FTPoint V = (1.0f - t) * B + t * C; AddPoint((1.0f - t) * U + t * V); } } void FTContour::evaluateCubicCurve(FTPoint A, FTPoint B, FTPoint C, FTPoint D) { for(unsigned int i = 0; i < BEZIER_STEPS; i++) { float t = static_cast(i) / BEZIER_STEPS; FTPoint U = (1.0f - t) * A + t * B; FTPoint V = (1.0f - t) * B + t * C; FTPoint W = (1.0f - t) * C + t * D; FTPoint M = (1.0f - t) * U + t * V; FTPoint N = (1.0f - t) * V + t * W; AddPoint((1.0f - t) * M + t * N); } } // This function is a bit tricky. Given a path ABC, it returns the // coordinates of the outset point facing B on the left at a distance // of 64.0. // M // - - - - - - X // ^ / ' // | 64.0 / ' // X---->-----X ==> X--v-------X ' // A B \ A B \ .>' // \ \<' 64.0 // \ \ . // \ \ . // C X C X // FTPoint FTContour::ComputeOutsetPoint(FTPoint A, FTPoint B, FTPoint C) { /* Build the rotation matrix from 'ba' vector */ FTPoint ba = (A - B).Normalise(); FTPoint bc = C - B; /* Rotate bc to the left */ FTPoint tmp(bc.X() * -ba.X() + bc.Y() * -ba.Y(), bc.X() * ba.Y() + bc.Y() * -ba.X()); /* Compute the vector bisecting 'abc' */ FTGL_DOUBLE norm = sqrt(tmp.X() * tmp.X() + tmp.Y() * tmp.Y()); FTGL_DOUBLE dist = 64.0 * sqrt((norm - tmp.X()) / (norm + tmp.X())); tmp.X(tmp.Y() < 0.0 ? dist : -dist); tmp.Y(64.0); /* Rotate the new bc to the right */ return FTPoint(tmp.X() * -ba.X() + tmp.Y() * ba.Y(), tmp.X() * -ba.Y() + tmp.Y() * -ba.X()); } void FTContour::SetParity(int parity) { size_t size = PointCount(); FTPoint vOutset; if(((parity & 1) && clockwise) || (!(parity & 1) && !clockwise)) { // Contour orientation is wrong! We must reverse all points. // FIXME: could it be worth writing FTVector::reverse() for this? for(size_t i = 0; i < size / 2; i++) { FTPoint tmp = pointList[i]; pointList[i] = pointList[size - 1 - i]; pointList[size - 1 -i] = tmp; } clockwise = !clockwise; } for(size_t i = 0; i < size; i++) { size_t prev, cur, next; prev = (i + size - 1) % size; cur = i; next = (i + size + 1) % size; vOutset = ComputeOutsetPoint(Point(prev), Point(cur), Point(next)); AddOutsetPoint(vOutset); } } FTContour::FTContour(FT_Vector* contour, char* tags, unsigned int n) { FTPoint prev, cur(contour[(n - 1) % n]), next(contour[0]); FTPoint a;//, b = next - cur; double olddir, dir = atan2((next - cur).Y(), (next - cur).X()); double angle = 0.0; // See http://freetype.sourceforge.net/freetype2/docs/glyphs/glyphs-6.html // for a full description of FreeType tags. for(unsigned int i = 0; i < n; i++) { prev = cur; cur = next; next = FTPoint(contour[(i + 1) % n]); olddir = dir; dir = atan2((next - cur).Y(), (next - cur).X()); // Compute our path's new direction. double t = dir - olddir; if(t < -M_PI) t += 2 * M_PI; if(t > M_PI) t -= 2 * M_PI; angle += t; // Only process point tags we know. if(n < 2 || FT_CURVE_TAG(tags[i]) == FT_Curve_Tag_On) { AddPoint(cur); } else if(FT_CURVE_TAG(tags[i]) == FT_Curve_Tag_Conic) { FTPoint prev2 = prev, next2 = next; // Previous point is either the real previous point (an "on" // point), or the midpoint between the current one and the // previous "conic off" point. if(FT_CURVE_TAG(tags[(i - 1 + n) % n]) == FT_Curve_Tag_Conic) { prev2 = (cur + prev) * 0.5; AddPoint(prev2); } // Next point is either the real next point or the midpoint. if(FT_CURVE_TAG(tags[(i + 1) % n]) == FT_Curve_Tag_Conic) { next2 = (cur + next) * 0.5; } evaluateQuadraticCurve(prev2, cur, next2); } else if(FT_CURVE_TAG(tags[i]) == FT_Curve_Tag_Cubic && FT_CURVE_TAG(tags[(i + 1) % n]) == FT_Curve_Tag_Cubic) { evaluateCubicCurve(prev, cur, next, FTPoint(contour[(i + 2) % n])); } } // If final angle is positive (+2PI), it's an anti-clockwise contour, // otherwise (-2PI) it's clockwise. clockwise = (angle < 0.0); } void FTContour::buildFrontOutset(float outset) { for(size_t i = 0; i < PointCount(); ++i) { AddFrontPoint(Point(i) + Outset(i) * outset); } } void FTContour::buildBackOutset(float outset) { for(size_t i = 0; i < PointCount(); ++i) { AddBackPoint(Point(i) + Outset(i) * outset); } } workbench-1.1.1/src/FtglFont/FTContour.h000066400000000000000000000143151255417355300200600ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Éric Beets * Copyright (c) 2008 Sam Hocevar * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __FTContour__ #define __FTContour__ #include "FTGL/ftgl.h" #include "FTVector.h" /** * FTContour class is a container of points that describe a vector font * outline. It is used as a container for the output of the bezier curve * evaluator in FTVectoriser. * * @see FTOutlineGlyph * @see FTPolygonGlyph * @see FTPoint */ class FTContour { public: /** * Constructor * * @param contour * @param pointTags * @param numberOfPoints */ FTContour(FT_Vector* contour, char* pointTags, unsigned int numberOfPoints); /** * Destructor */ ~FTContour() { pointList.clear(); outsetPointList.clear(); frontPointList.clear(); backPointList.clear(); } /** * Return a point at index. * * @param index of the point in the curve. * @return const point reference */ const FTPoint& Point(size_t index) const { return pointList[index]; } /** * Return a point at index. * * @param index of the point in the outset curve. * @return const point reference */ const FTPoint& Outset(size_t index) const { return outsetPointList[index]; } /** * Return a point at index of the front outset contour. * * @param index of the point in the curve. * @return const point reference */ const FTPoint& FrontPoint(size_t index) const { if(frontPointList.size() == 0) return Point(index); return frontPointList[index]; } /** * Return a point at index of the back outset contour. * * @param index of the point in the curve. * @return const point reference */ const FTPoint& BackPoint(size_t index) const { if(backPointList.size() == 0) return Point(index); return backPointList[index]; } /** * How many points define this contour * * @return the number of points in this contour */ size_t PointCount() const { return pointList.size(); } /** * Make sure the glyph has the proper parity and create the front/back * outset contour. * * @param parity The contour's parity within the glyph. */ void SetParity(int parity); // FIXME: this should probably go away. void buildFrontOutset(float outset); void buildBackOutset(float outset); private: /** * Add a point to this contour. This function tests for duplicate * points. * * @param point The point to be added to the contour. */ inline void AddPoint(FTPoint point); /** * Add a point to this contour. This function tests for duplicate * points. * * @param point The point to be added to the contour. */ inline void AddOutsetPoint(FTPoint point); /* * Add a point to this outset contour. This function tests for duplicate * points. * * @param point The point to be added to the contour outset. */ inline void AddFrontPoint(FTPoint point); inline void AddBackPoint(FTPoint point); /** * De Casteljau (bezier) algorithm contributed by Jed Soane * Evaluates a quadratic or conic (second degree) curve */ inline void evaluateQuadraticCurve(FTPoint, FTPoint, FTPoint); /** * De Casteljau (bezier) algorithm contributed by Jed Soane * Evaluates a cubic (third degree) curve */ inline void evaluateCubicCurve(FTPoint, FTPoint, FTPoint, FTPoint); /** * Compute the vector norm */ inline FTGL_DOUBLE NormVector(const FTPoint &v); /** * Compute a rotation matrix from a vector */ inline void RotationMatrix(const FTPoint &a, const FTPoint &b, FTGL_DOUBLE *matRot, FTGL_DOUBLE *invRot); /** * Matrix and vector multiplication */ inline void MultMatrixVect(FTGL_DOUBLE *mat, FTPoint &v); /** * Compute the vector bisecting from a vector 'v' and a distance 'd' */ inline void ComputeBisec(FTPoint &v); /** * Compute the outset point coordinates */ inline FTPoint ComputeOutsetPoint(FTPoint a, FTPoint b, FTPoint c); /** * The list of points in this contour */ typedef FTVector PointVector; PointVector pointList; PointVector outsetPointList; PointVector frontPointList; PointVector backPointList; /** * Is this contour clockwise or anti-clockwise? */ bool clockwise; }; #endif // __FTContour__ workbench-1.1.1/src/FtglFont/FTFace.cpp000066400000000000000000000133761255417355300176260ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "FtglConfig.h" #include "FTFace.h" #include "FTLibrary.h" #include FT_TRUETYPE_TABLES_H FTFace::FTFace(const char* fontFilePath, bool precomputeKerning) : numGlyphs(0), fontEncodingList(0), kerningCache(0), err(0) { const FT_Long DEFAULT_FACE_INDEX = 0; ftFace = new FT_Face; err = FT_New_Face(*FTLibrary::Instance().GetLibrary(), fontFilePath, DEFAULT_FACE_INDEX, ftFace); if(err) { delete ftFace; ftFace = 0; return; } numGlyphs = (*ftFace)->num_glyphs; hasKerningTable = (FT_HAS_KERNING((*ftFace)) != 0); if(hasKerningTable && precomputeKerning) { BuildKerningCache(); } } FTFace::FTFace(const unsigned char *pBufferBytes, size_t bufferSizeInBytes, bool precomputeKerning) : numGlyphs(0), fontEncodingList(0), kerningCache(0), err(0) { const FT_Long DEFAULT_FACE_INDEX = 0; ftFace = new FT_Face; err = FT_New_Memory_Face(*FTLibrary::Instance().GetLibrary(), (FT_Byte const *)pBufferBytes, (FT_Long)bufferSizeInBytes, DEFAULT_FACE_INDEX, ftFace); if(err) { delete ftFace; ftFace = 0; return; } numGlyphs = (*ftFace)->num_glyphs; hasKerningTable = (FT_HAS_KERNING((*ftFace)) != 0); if(hasKerningTable && precomputeKerning) { BuildKerningCache(); } } FTFace::~FTFace() { if(kerningCache) { delete[] kerningCache; } if(ftFace) { FT_Done_Face(*ftFace); delete ftFace; ftFace = 0; } } bool FTFace::Attach(const char* fontFilePath) { err = FT_Attach_File(*ftFace, fontFilePath); return !err; } bool FTFace::Attach(const unsigned char *pBufferBytes, size_t bufferSizeInBytes) { FT_Open_Args open; open.flags = FT_OPEN_MEMORY; open.memory_base = (FT_Byte const *)pBufferBytes; open.memory_size = (FT_Long)bufferSizeInBytes; err = FT_Attach_Stream(*ftFace, &open); return !err; } const FTSize& FTFace::Size(const unsigned int size, const unsigned int res) { charSize.CharSize(ftFace, size, res, res); err = charSize.Error(); return charSize; } unsigned int FTFace::CharMapCount() const { return (*ftFace)->num_charmaps; } FT_Encoding* FTFace::CharMapList() { if(0 == fontEncodingList) { fontEncodingList = new FT_Encoding[CharMapCount()]; for(size_t i = 0; i < CharMapCount(); ++i) { fontEncodingList[i] = (*ftFace)->charmaps[i]->encoding; } } return fontEncodingList; } FTPoint FTFace::KernAdvance(unsigned int index1, unsigned int index2) { float x, y; if(!hasKerningTable || !index1 || !index2) { return FTPoint(0.0f, 0.0f); } if(kerningCache && index1 < FTFace::MAX_PRECOMPUTED && index2 < FTFace::MAX_PRECOMPUTED) { x = kerningCache[2 * (index2 * FTFace::MAX_PRECOMPUTED + index1)]; y = kerningCache[2 * (index2 * FTFace::MAX_PRECOMPUTED + index1) + 1]; return FTPoint(x, y); } FT_Vector kernAdvance; kernAdvance.x = kernAdvance.y = 0; err = FT_Get_Kerning(*ftFace, index1, index2, ft_kerning_unfitted, &kernAdvance); if(err) { return FTPoint(0.0f, 0.0f); } x = static_cast(kernAdvance.x) / 64.0f; y = static_cast(kernAdvance.y) / 64.0f; return FTPoint(x, y); } FT_GlyphSlot FTFace::Glyph(unsigned int index, FT_Int load_flags) { err = FT_Load_Glyph(*ftFace, index, load_flags); if(err) { return NULL; } return (*ftFace)->glyph; } void FTFace::BuildKerningCache() { FT_Vector kernAdvance; kernAdvance.x = 0; kernAdvance.y = 0; kerningCache = new float[FTFace::MAX_PRECOMPUTED * FTFace::MAX_PRECOMPUTED * 2]; for(unsigned int j = 0; j < FTFace::MAX_PRECOMPUTED; j++) { for(unsigned int i = 0; i < FTFace::MAX_PRECOMPUTED; i++) { err = FT_Get_Kerning(*ftFace, i, j, ft_kerning_unfitted, &kernAdvance); if(err) { delete[] kerningCache; kerningCache = NULL; return; } kerningCache[2 * (j * FTFace::MAX_PRECOMPUTED + i)] = static_cast(kernAdvance.x) / 64.0f; kerningCache[2 * (j * FTFace::MAX_PRECOMPUTED + i) + 1] = static_cast(kernAdvance.y) / 64.0f; } } } workbench-1.1.1/src/FtglFont/FTFace.h000066400000000000000000000122351255417355300172640ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __FTFace__ #define __FTFace__ #include #include FT_FREETYPE_H #include FT_GLYPH_H #include "FTGL/ftgl.h" #include "FTSize.h" /** * FTFace class provides an abstraction layer for the Freetype Face. * * @see "Freetype 2 Documentation" * */ class FTFace { public: /** * Opens and reads a face file. Error is set. * * @param fontFilePath font file path. */ FTFace(const char* fontFilePath, bool precomputeKerning = true); /** * Read face data from an in-memory buffer. Error is set. * * @param pBufferBytes the in-memory buffer * @param bufferSizeInBytes the length of the buffer in bytes */ FTFace(const unsigned char *pBufferBytes, size_t bufferSizeInBytes, bool precomputeKerning = true); /** * Destructor * * Disposes of the current Freetype Face. */ virtual ~FTFace(); /** * Attach auxilliary file to font (e.g., font metrics). * * @param fontFilePath auxilliary font file path. * @return true if file has opened * successfully. */ bool Attach(const char* fontFilePath); /** * Attach auxilliary data to font (e.g., font metrics) from memory * * @param pBufferBytes the in-memory buffer * @param bufferSizeInBytes the length of the buffer in bytes * @return true if file has opened * successfully. */ bool Attach(const unsigned char *pBufferBytes, size_t bufferSizeInBytes); /** * Get the freetype face object.. * * @return pointer to an FT_Face. */ FT_Face* Face() const { return ftFace; } /** * Sets the char size for the current face. * * This doesn't guarantee that the size was set correctly. Clients * should check errors. * * @param size the face size in points (1/72 inch) * @param res the resolution of the target device. * @return FTSize object */ const FTSize& Size(const unsigned int size, const unsigned int res); /** * Get the number of character maps in this face. * * @return character map count. */ unsigned int CharMapCount() const; /** * Get a list of character maps in this face. * * @return pointer to the first encoding. */ FT_Encoding* CharMapList(); /** * Gets the kerning vector between two glyphs */ FTPoint KernAdvance(unsigned int index1, unsigned int index2); /** * Loads and creates a Freetype glyph. */ FT_GlyphSlot Glyph(unsigned int index, FT_Int load_flags); /** * Gets the number of glyphs in the current face. */ unsigned int GlyphCount() const { return numGlyphs; } /** * Queries for errors. * * @return The current error code. */ FT_Error Error() const { return err; } private: /** * The Freetype face */ FT_Face* ftFace; /** * The size object associated with this face */ FTSize charSize; /** * The number of glyphs in this face */ int numGlyphs; FT_Encoding* fontEncodingList; /** * This face has kerning tables */ bool hasKerningTable; /** * If this face has kerning tables, we can cache them. */ void BuildKerningCache(); static const unsigned int MAX_PRECOMPUTED = 128; float *kerningCache; /** * Current error code. Zero means no error. */ FT_Error err; }; #endif // __FTFace__ workbench-1.1.1/src/FtglFont/FTFont/000077500000000000000000000000001255417355300171605ustar00rootroot00000000000000workbench-1.1.1/src/FtglFont/FTFont/FTBitmapFont.cpp000066400000000000000000000060031255417355300221600ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "FtglConfig.h" #include "FTGL/ftgl.h" #include "FTInternals.h" #include "FTBitmapFontImpl.h" // // FTBitmapFont // FTBitmapFont::FTBitmapFont(char const *fontFilePath) : FTFont(new FTBitmapFontImpl(this, fontFilePath)) {} FTBitmapFont::FTBitmapFont(unsigned char const *pBufferBytes, size_t bufferSizeInBytes) : FTFont(new FTBitmapFontImpl(this, pBufferBytes, bufferSizeInBytes)) {} FTBitmapFont::~FTBitmapFont() {} FTGlyph* FTBitmapFont::MakeGlyph(FT_GlyphSlot ftGlyph) { return new FTBitmapGlyph(ftGlyph); } // // FTBitmapFontImpl // template inline FTPoint FTBitmapFontImpl::RenderI(const T* string, const int len, FTPoint position, FTPoint spacing, int renderMode) { // Protect GL_BLEND glPushAttrib(GL_COLOR_BUFFER_BIT); // Protect glPixelStorei() calls (also in FTBitmapGlyphImpl::RenderImpl) glPushClientAttrib(GL_CLIENT_PIXEL_STORE_BIT); glPixelStorei(GL_UNPACK_LSB_FIRST, GL_FALSE); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glDisable(GL_BLEND); FTPoint tmp = FTFontImpl::Render(string, len, position, spacing, renderMode); glPopClientAttrib(); glPopAttrib(); return tmp; } FTPoint FTBitmapFontImpl::Render(const char * string, const int len, FTPoint position, FTPoint spacing, int renderMode) { return RenderI(string, len, position, spacing, renderMode); } FTPoint FTBitmapFontImpl::Render(const wchar_t * string, const int len, FTPoint position, FTPoint spacing, int renderMode) { return RenderI(string, len, position, spacing, renderMode); } workbench-1.1.1/src/FtglFont/FTFont/FTBitmapFontImpl.h000066400000000000000000000044761255417355300224630ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __FTBitmapFontImpl__ #define __FTBitmapFontImpl__ #include "FTFontImpl.h" class FTGlyph; class FTBitmapFontImpl : public FTFontImpl { friend class FTBitmapFont; protected: FTBitmapFontImpl(FTFont *ftFont, const char* fontFilePath) : FTFontImpl(ftFont, fontFilePath) {}; FTBitmapFontImpl(FTFont *ftFont, const unsigned char *pBufferBytes, size_t bufferSizeInBytes) : FTFontImpl(ftFont, pBufferBytes, bufferSizeInBytes) {}; virtual FTPoint Render(const char *s, const int len, FTPoint position, FTPoint spacing, int renderMode); virtual FTPoint Render(const wchar_t *s, const int len, FTPoint position, FTPoint spacing, int renderMode); private: /* Internal generic Render() implementation */ template inline FTPoint RenderI(const T *s, const int len, FTPoint position, FTPoint spacing, int mode); }; #endif // __FTBitmapFontImpl__ workbench-1.1.1/src/FtglFont/FTFont/FTBufferFont.cpp000066400000000000000000000226551255417355300221700ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2008 Sam Hocevar * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "FtglConfig.h" #include #include "FTGL/ftgl.h" #include "FTInternals.h" #include "FTBufferFontImpl.h" // // FTBufferFont // FTBufferFont::FTBufferFont(char const *fontFilePath) : FTFont(new FTBufferFontImpl(this, fontFilePath)) {} FTBufferFont::FTBufferFont(unsigned char const *pBufferBytes, size_t bufferSizeInBytes) : FTFont(new FTBufferFontImpl(this, pBufferBytes, bufferSizeInBytes)) {} FTBufferFont::~FTBufferFont() {} FTGlyph* FTBufferFont::MakeGlyph(FT_GlyphSlot ftGlyph) { FTBufferFontImpl *myimpl = dynamic_cast(impl); if(!myimpl) { return NULL; } return myimpl->MakeGlyphImpl(ftGlyph); } // // FTBufferFontImpl // FTBufferFontImpl::FTBufferFontImpl(FTFont *ftFont, const char* fontFilePath) : FTFontImpl(ftFont, fontFilePath), buffer(new FTBuffer()) { load_flags = FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP; glGenTextures(BUFFER_CACHE_SIZE, idCache); for(int i = 0; i < BUFFER_CACHE_SIZE; i++) { stringCache[i] = NULL; glBindTexture(GL_TEXTURE_2D, idCache[i]); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); } lastString = 0; } FTBufferFontImpl::FTBufferFontImpl(FTFont *ftFont, const unsigned char *pBufferBytes, size_t bufferSizeInBytes) : FTFontImpl(ftFont, pBufferBytes, bufferSizeInBytes), buffer(new FTBuffer()) { load_flags = FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP; glGenTextures(BUFFER_CACHE_SIZE, idCache); for(int i = 0; i < BUFFER_CACHE_SIZE; i++) { stringCache[i] = NULL; glBindTexture(GL_TEXTURE_2D, idCache[i]); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); } lastString = 0; } FTBufferFontImpl::~FTBufferFontImpl() { glDeleteTextures(BUFFER_CACHE_SIZE, idCache); for(int i = 0; i < BUFFER_CACHE_SIZE; i++) { if(stringCache[i]) { free(stringCache[i]); } } delete buffer; } FTGlyph* FTBufferFontImpl::MakeGlyphImpl(FT_GlyphSlot ftGlyph) { return new FTBufferGlyph(ftGlyph, buffer); } bool FTBufferFontImpl::FaceSize(const unsigned int size, const unsigned int res) { for(int i = 0; i < BUFFER_CACHE_SIZE; i++) { if(stringCache[i]) { free(stringCache[i]); stringCache[i] = NULL; } } return FTFontImpl::FaceSize(size, res); } static inline GLuint NextPowerOf2(GLuint in) { in -= 1; in |= in >> 16; in |= in >> 8; in |= in >> 4; in |= in >> 2; in |= in >> 1; return in + 1; } inline int StringCompare(void const *a, char const *b, int len) { return len < 0 ? strcmp((char const *)a, b) : strncmp((char const *)a, b, len); } inline int StringCompare(void const *a, wchar_t const *b, int len) { return len < 0 ? wcscmp((wchar_t const *)a, b) : wcsncmp((wchar_t const *)a, b, len); } inline char *StringCopy(char const *s, int len) { if(len < 0) { return strdup(s); } else { #ifdef HAVE_STRNDUP return strndup(s, len); #else char *s2 = (char*)malloc(len + 1); memcpy(s2, s, len); s2[len] = 0; return s2; #endif } } inline wchar_t *StringCopy(wchar_t const *s, int len) { if(len < 0) { #if defined HAVE_WCSDUP return wcsdup(s); #else len = (int)wcslen(s); #endif } wchar_t *s2 = (wchar_t *)malloc((len + 1) * sizeof(wchar_t)); memcpy(s2, s, len * sizeof(wchar_t)); s2[len] = 0; return s2; } template inline FTPoint FTBufferFontImpl::RenderI(const T* string, const int len, FTPoint position, FTPoint spacing, int renderMode) { const float padding = 3.0f; int width, height, texWidth, texHeight; int cacheIndex = -1; bool inCache = false; // Protect blending functions, GL_BLEND and GL_TEXTURE_2D glPushAttrib(GL_COLOR_BUFFER_BIT | GL_ENABLE_BIT); // Protect glPixelStorei() calls glPushClientAttrib(GL_CLIENT_PIXEL_STORE_BIT); glEnable(GL_BLEND); glEnable(GL_TEXTURE_2D); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // GL_ONE // Search whether the string is already in a texture we uploaded for(int n = 0; n < BUFFER_CACHE_SIZE; n++) { int i = (lastString + n + BUFFER_CACHE_SIZE) % BUFFER_CACHE_SIZE; if(stringCache[i] && !StringCompare(stringCache[i], string, len)) { cacheIndex = i; inCache = true; break; } } // If the string was not found, we need to put it in the cache and compute // its new bounding box. if(!inCache) { // FIXME: this cache is not very efficient. We should first expire // strings that are not used very often. cacheIndex = lastString; lastString = (lastString + 1) % BUFFER_CACHE_SIZE; if(stringCache[cacheIndex]) { free(stringCache[cacheIndex]); } // FIXME: only the first N bytes are copied; we want the first N chars. stringCache[cacheIndex] = StringCopy(string, len); bboxCache[cacheIndex] = BBox(string, len, FTPoint(), spacing); } FTBBox bbox = bboxCache[cacheIndex]; width = static_cast(bbox.Upper().X() - bbox.Lower().X() + padding + padding + 0.5); height = static_cast(bbox.Upper().Y() - bbox.Lower().Y() + padding + padding + 0.5); texWidth = NextPowerOf2(width); texHeight = NextPowerOf2(height); glBindTexture(GL_TEXTURE_2D, idCache[cacheIndex]); // If the string was not found, we need to render the text in a new // texture buffer, then upload it to the OpenGL layer. if(!inCache) { buffer->Size(texWidth, texHeight); buffer->Pos(FTPoint(padding, padding) - bbox.Lower()); advanceCache[cacheIndex] = FTFontImpl::Render(string, len, FTPoint(), spacing, renderMode); glBindTexture(GL_TEXTURE_2D, idCache[cacheIndex]); glPixelStorei(GL_UNPACK_LSB_FIRST, GL_FALSE); glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); /* TODO: use glTexSubImage2D later? */ glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, texWidth, texHeight, 0, GL_ALPHA, GL_UNSIGNED_BYTE, (GLvoid *)buffer->Pixels()); buffer->Size(0, 0); } FTPoint low = position + bbox.Lower(); FTPoint up = position + bbox.Upper(); glBegin(GL_QUADS); glNormal3f(0.0f, 0.0f, 1.0f); glTexCoord2f(padding / texWidth, (texHeight - height + padding) / texHeight); glVertex2f(low.Xf(), up.Yf()); glTexCoord2f(padding / texWidth, (texHeight - padding) / texHeight); glVertex2f(low.Xf(), low.Yf()); glTexCoord2f((width - padding) / texWidth, (texHeight - padding) / texHeight); glVertex2f(up.Xf(), low.Yf()); glTexCoord2f((width - padding) / texWidth, (texHeight - height + padding) / texHeight); glVertex2f(up.Xf(), up.Yf()); glEnd(); glPopClientAttrib(); glPopAttrib(); return position + advanceCache[cacheIndex]; } FTPoint FTBufferFontImpl::Render(const char * string, const int len, FTPoint position, FTPoint spacing, int renderMode) { return RenderI(string, len, position, spacing, renderMode); } FTPoint FTBufferFontImpl::Render(const wchar_t * string, const int len, FTPoint position, FTPoint spacing, int renderMode) { return RenderI(string, len, position, spacing, renderMode); } workbench-1.1.1/src/FtglFont/FTFont/FTBufferFontImpl.h000066400000000000000000000054121255417355300224470ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2008 Sam Hocevar * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __FTBufferFontImpl__ #define __FTBufferFontImpl__ #include "FTFontImpl.h" class FTGlyph; class FTBuffer; class FTBufferFontImpl : public FTFontImpl { friend class FTBufferFont; protected: FTBufferFontImpl(FTFont *ftFont, const char* fontFilePath); FTBufferFontImpl(FTFont *ftFont, const unsigned char *pBufferBytes, size_t bufferSizeInBytes); virtual ~FTBufferFontImpl(); virtual FTPoint Render(const char *s, const int len, FTPoint position, FTPoint spacing, int renderMode); virtual FTPoint Render(const wchar_t *s, const int len, FTPoint position, FTPoint spacing, int renderMode); virtual bool FaceSize(const unsigned int size, const unsigned int res); private: /** * Create an FTBufferGlyph object for the base class. */ FTGlyph* MakeGlyphImpl(FT_GlyphSlot ftGlyph); /* Internal generic Render() implementation */ template inline FTPoint RenderI(const T *s, const int len, FTPoint position, FTPoint spacing, int mode); /* Pixel buffer */ FTBuffer *buffer; static const int BUFFER_CACHE_SIZE = 16; /* Texture IDs */ GLuint idCache[BUFFER_CACHE_SIZE]; void *stringCache[BUFFER_CACHE_SIZE]; FTBBox bboxCache[BUFFER_CACHE_SIZE]; FTPoint advanceCache[BUFFER_CACHE_SIZE]; int lastString; }; #endif // __FTBufferFontImpl__ workbench-1.1.1/src/FtglFont/FTFont/FTExtrudeFont.cpp000066400000000000000000000050371255417355300223720ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "FtglConfig.h" #include "FTGL/ftgl.h" #include "FTInternals.h" #include "FTExtrudeFontImpl.h" // // FTExtrudeFont // FTExtrudeFont::FTExtrudeFont(char const *fontFilePath) : FTFont(new FTExtrudeFontImpl(this, fontFilePath)) {} FTExtrudeFont::FTExtrudeFont(const unsigned char *pBufferBytes, size_t bufferSizeInBytes) : FTFont(new FTExtrudeFontImpl(this, pBufferBytes, bufferSizeInBytes)) {} FTExtrudeFont::~FTExtrudeFont() {} FTGlyph* FTExtrudeFont::MakeGlyph(FT_GlyphSlot ftGlyph) { FTExtrudeFontImpl *myimpl = dynamic_cast(impl); if(!myimpl) { return NULL; } return new FTExtrudeGlyph(ftGlyph, myimpl->depth, myimpl->front, myimpl->back, myimpl->useDisplayLists); } // // FTExtrudeFontImpl // FTExtrudeFontImpl::FTExtrudeFontImpl(FTFont *ftFont, const char* fontFilePath) : FTFontImpl(ftFont, fontFilePath), depth(0.0f), front(0.0f), back(0.0f) { load_flags = FT_LOAD_NO_HINTING; } FTExtrudeFontImpl::FTExtrudeFontImpl(FTFont *ftFont, const unsigned char *pBufferBytes, size_t bufferSizeInBytes) : FTFontImpl(ftFont, pBufferBytes, bufferSizeInBytes), depth(0.0f), front(0.0f), back(0.0f) { load_flags = FT_LOAD_NO_HINTING; } workbench-1.1.1/src/FtglFont/FTFont/FTExtrudeFontImpl.h000066400000000000000000000051051255417355300226550ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __FTExtrudeFontImpl__ #define __FTExtrudeFontImpl__ #include "FTFontImpl.h" class FTGlyph; class FTExtrudeFontImpl : public FTFontImpl { friend class FTExtrudeFont; protected: FTExtrudeFontImpl(FTFont *ftFont, const char* fontFilePath); FTExtrudeFontImpl(FTFont *ftFont, const unsigned char *pBufferBytes, size_t bufferSizeInBytes); /** * Set the extrusion distance for the font. * * @param d The extrusion distance. */ virtual void Depth(float d) { depth = d; } /** * Set the outset distance for the font. Only implemented by * FTOutlineFont, FTPolygonFont and FTExtrudeFont * * @param o The outset distance. */ virtual void Outset(float o) { front = back = o; } /** * Set the outset distance for the font. Only implemented by * FTExtrudeFont * * @param f The front outset distance. * @param b The back outset distance. */ virtual void Outset(float f, float b) { front = f; back = b; } private: /** * The extrusion distance for the font. */ float depth; /** * The outset distance (front and back) for the font. */ float front, back; }; #endif // __FTExtrudeFontImpl__ workbench-1.1.1/src/FtglFont/FTFont/FTFont.cpp000066400000000000000000000261421255417355300210310ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * Copyright (c) 2014 Washington University School of Medicine * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "FtglConfig.h" #include "FTInternals.h" #include "FTUnicode.h" #include "FTFontImpl.h" #include "FTBitmapFontImpl.h" #include "FTExtrudeFontImpl.h" #include "FTOutlineFontImpl.h" #include "FTPixmapFontImpl.h" #include "FTPolygonFontImpl.h" #include "FTTextureFontImpl.h" #include "FTGlyphContainer.h" #include "FTFace.h" // // FTFont // FTFont::FTFont(char const *fontFilePath) { impl = new FTFontImpl(this, fontFilePath); } FTFont::FTFont(const unsigned char *pBufferBytes, size_t bufferSizeInBytes) { impl = new FTFontImpl(this, pBufferBytes, bufferSizeInBytes); } FTFont::FTFont(FTFontImpl *pImpl) { impl = pImpl; } FTFont::~FTFont() { delete impl; } bool FTFont::Attach(const char* fontFilePath) { return impl->Attach(fontFilePath); } bool FTFont::Attach(const unsigned char *pBufferBytes, size_t bufferSizeInBytes) { return impl->Attach(pBufferBytes, bufferSizeInBytes); } bool FTFont::FaceSize(const unsigned int size, const unsigned int res) { return impl->FaceSize(size, res); } unsigned int FTFont::FaceSize() const { return impl->FaceSize(); } void FTFont::Depth(float depth) { return impl->Depth(depth); } void FTFont::Outset(float outset) { return impl->Outset(outset); } void FTFont::Outset(float front, float back) { return impl->Outset(front, back); } void FTFont::GlyphLoadFlags(FT_Int flags) { return impl->GlyphLoadFlags(flags); } bool FTFont::CharMap(FT_Encoding encoding) { return impl->CharMap(encoding); } unsigned int FTFont::CharMapCount() const { return impl->CharMapCount(); } FT_Encoding* FTFont::CharMapList() { return impl->CharMapList(); } void FTFont::UseDisplayList(bool useList) { return impl->UseDisplayList(useList); } float FTFont::Ascender() const { return impl->Ascender(); } float FTFont::Descender() const { return impl->Descender(); } float FTFont::LineHeight() const { return impl->LineHeight(); } FTPoint FTFont::Render(const char * string, const int len, FTPoint position, FTPoint spacing, int renderMode) { return impl->Render(string, len, position, spacing, renderMode); } FTPoint FTFont::Render(const wchar_t * string, const int len, FTPoint position, FTPoint spacing, int renderMode) { return impl->Render(string, len, position, spacing, renderMode); } float FTFont::Advance(const char * string, const int len, FTPoint spacing) { return impl->Advance(string, len, spacing); } float FTFont::Advance(const wchar_t * string, const int len, FTPoint spacing) { return impl->Advance(string, len, spacing); } FTBBox FTFont::BBox(const char *string, const int len, FTPoint position, FTPoint spacing) { return impl->BBox(string, len, position, spacing); } FTBBox FTFont::BBox(const wchar_t *string, const int len, FTPoint position, FTPoint spacing) { return impl->BBox(string, len, position, spacing); } FT_Error FTFont::Error() const { return impl->err; } // // FTFontImpl // FTFontImpl::FTFontImpl(FTFont *ftFont, char const *fontFilePath) : face(fontFilePath), useDisplayLists(true), load_flags(FT_LOAD_DEFAULT), intf(ftFont), glyphList(0) { err = face.Error(); if(err == 0) { glyphList = new FTGlyphContainer(&face); } } FTFontImpl::FTFontImpl(FTFont *ftFont, const unsigned char *pBufferBytes, size_t bufferSizeInBytes) : face(pBufferBytes, bufferSizeInBytes), useDisplayLists(true), load_flags(FT_LOAD_DEFAULT), intf(ftFont), glyphList(0) { err = face.Error(); if(err == 0) { glyphList = new FTGlyphContainer(&face); } } FTFontImpl::~FTFontImpl() { if(glyphList) { delete glyphList; } } bool FTFontImpl::Attach(const char* fontFilePath) { if(!face.Attach(fontFilePath)) { err = face.Error(); return false; } err = 0; return true; } bool FTFontImpl::Attach(const unsigned char *pBufferBytes, size_t bufferSizeInBytes) { if(!face.Attach(pBufferBytes, bufferSizeInBytes)) { err = face.Error(); return false; } err = 0; return true; } bool FTFontImpl::FaceSize(const unsigned int size, const unsigned int res) { if(glyphList != NULL) { delete glyphList; glyphList = NULL; } charSize = face.Size(size, res); err = face.Error(); if(err != 0) { return false; } glyphList = new FTGlyphContainer(&face); return true; } unsigned int FTFontImpl::FaceSize() const { return charSize.CharSize(); } void FTFontImpl::Depth(float /*depth*/) { ; } void FTFontImpl::Outset(float /*outset*/) { ; } void FTFontImpl::Outset(float /*front*/, float /*back*/) { ; } void FTFontImpl::GlyphLoadFlags(FT_Int flags) { load_flags = flags; } bool FTFontImpl::CharMap(FT_Encoding encoding) { bool result = glyphList->CharMap(encoding); err = glyphList->Error(); return result; } unsigned int FTFontImpl::CharMapCount() const { return face.CharMapCount(); } FT_Encoding* FTFontImpl::CharMapList() { return face.CharMapList(); } void FTFontImpl::UseDisplayList(bool useList) { useDisplayLists = useList; } float FTFontImpl::Ascender() const { return charSize.Ascender(); } float FTFontImpl::Descender() const { return charSize.Descender(); } float FTFontImpl::LineHeight() const { return charSize.Height(); } template inline FTBBox FTFontImpl::BBoxI(const T* string, const int len, FTPoint position, FTPoint spacing) { FTBBox totalBBox; /* Only compute the bounds if string is non-empty. */ if(string && ('\0' != string[0])) { // for multibyte - we can't rely on sizeof(T) == character FTUnicodeStringItr ustr(string); unsigned int thisChar = *ustr++; unsigned int nextChar = *ustr; if(CheckGlyph(thisChar)) { totalBBox = glyphList->BBox(thisChar); totalBBox += position; position += FTPoint(glyphList->Advance(thisChar, nextChar), 0.0); } /* Expand totalBox by each glyph in string */ for(int i = 1; (len < 0 && *ustr) || (len >= 0 && i < len); i++) { thisChar = *ustr++; nextChar = *ustr; if(CheckGlyph(thisChar)) { position += spacing; FTBBox tempBBox = glyphList->BBox(thisChar); tempBBox += position; totalBBox |= tempBBox; position += FTPoint(glyphList->Advance(thisChar, nextChar), 0.0); } } } return totalBBox; } FTBBox FTFontImpl::BBox(const char *string, const int len, FTPoint position, FTPoint spacing) { /* The chars need to be unsigned because they are cast to int later */ return BBoxI((const unsigned char *)string, len, position, spacing); } FTBBox FTFontImpl::BBox(const wchar_t *string, const int len, FTPoint position, FTPoint spacing) { return BBoxI(string, len, position, spacing); } template inline float FTFontImpl::AdvanceI(const T* string, const int len, FTPoint spacing) { float advance = 0.0f; FTUnicodeStringItr ustr(string); for(int i = 0; (len < 0 && *ustr) || (len >= 0 && i < len); i++) { unsigned int thisChar = *ustr++; unsigned int nextChar = *ustr; if(CheckGlyph(thisChar)) { advance += glyphList->Advance(thisChar, nextChar); } if(nextChar) { advance += spacing.Xf(); } } return advance; } float FTFontImpl::Advance(const char* string, const int len, FTPoint spacing) { /* The chars need to be unsigned because they are cast to int later */ return AdvanceI((const unsigned char *)string, len, spacing); } float FTFontImpl::Advance(const wchar_t* string, const int len, FTPoint spacing) { return AdvanceI(string, len, spacing); } template inline FTPoint FTFontImpl::RenderI(const T* string, const int len, FTPoint position, FTPoint spacing, int renderMode) { // for multibyte - we can't rely on sizeof(T) == character FTUnicodeStringItr ustr(string); for(int i = 0; (len < 0 && *ustr) || (len >= 0 && i < len); i++) { unsigned int thisChar = *ustr++; unsigned int nextChar = *ustr; if(CheckGlyph(thisChar)) { position += glyphList->Render(thisChar, nextChar, position, renderMode); } if(nextChar) { position += spacing; } } return position; } FTPoint FTFontImpl::Render(const char * string, const int len, FTPoint position, FTPoint spacing, int renderMode) { return RenderI((const unsigned char *)string, len, position, spacing, renderMode); } FTPoint FTFontImpl::Render(const wchar_t * string, const int len, FTPoint position, FTPoint spacing, int renderMode) { return RenderI(string, len, position, spacing, renderMode); } bool FTFontImpl::CheckGlyph(const unsigned int characterCode) { if(glyphList->Glyph(characterCode)) { return true; } unsigned int glyphIndex = glyphList->FontIndex(characterCode); FT_GlyphSlot ftSlot = face.Glyph(glyphIndex, load_flags); if(!ftSlot) { err = face.Error(); return false; } FTGlyph* tempGlyph = intf->MakeGlyph(ftSlot); if(!tempGlyph) { if(0 == err) { err = 0x13; } return false; } glyphList->Add(tempGlyph, characterCode); return true; } workbench-1.1.1/src/FtglFont/FTFont/FTFontGlue.cpp000066400000000000000000000170141255417355300216440ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Éric Beets * Copyright (c) 2008 Sam Hocevar * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "FtglConfig.h" #include "FTInternals.h" static const FTPoint static_ftpoint; static const FTBBox static_ftbbox; FTGL_BEGIN_C_DECLS #define C_TOR(cname, cargs, cxxname, cxxarg, cxxtype) \ FTGLfont* cname cargs \ { \ cxxname *f = new cxxname cxxarg; \ if(f->Error()) \ { \ delete f; \ return NULL; \ } \ FTGLfont *ftgl = (FTGLfont *)malloc(sizeof(FTGLfont)); \ ftgl->ptr = f; \ ftgl->type = cxxtype; \ return ftgl; \ } // FTBitmapFont::FTBitmapFont(); C_TOR(ftglCreateBitmapFont, (const char *fontname), FTBitmapFont, (fontname), FONT_BITMAP); // FTBufferFont::FTBufferFont(); C_TOR(ftglCreateBufferFont, (const char *fontname), FTBufferFont, (fontname), FONT_BUFFER); // FTExtrudeFont::FTExtrudeFont(); C_TOR(ftglCreateExtrudeFont, (const char *fontname), FTExtrudeFont, (fontname), FONT_EXTRUDE); // FTOutlineFont::FTOutlineFont(); C_TOR(ftglCreateOutlineFont, (const char *fontname), FTOutlineFont, (fontname), FONT_OUTLINE); // FTPixmapFont::FTPixmapFont(); C_TOR(ftglCreatePixmapFont, (const char *fontname), FTPixmapFont, (fontname), FONT_PIXMAP); // FTPolygonFont::FTPolygonFont(); C_TOR(ftglCreatePolygonFont, (const char *fontname), FTPolygonFont, (fontname), FONT_POLYGON); // FTTextureFont::FTTextureFont(); C_TOR(ftglCreateTextureFont, (const char *fontname), FTTextureFont, (fontname), FONT_TEXTURE); // FTCustomFont::FTCustomFont(); class FTCustomFont : public FTFont { public: FTCustomFont(char const *fontFilePath, void *p, FTGLglyph * (*makeglyph) (FT_GlyphSlot, void *)) : FTFont(fontFilePath), data(p), makeglyphCallback(makeglyph) {} ~FTCustomFont() {} FTGlyph* MakeGlyph(FT_GlyphSlot slot) { FTGLglyph *g = makeglyphCallback(slot, data); FTGlyph *glyph = g->ptr; // XXX: we no longer need g, and no one will free it for us. Not // very elegant, and we need to make sure no one else will try to // use it. free(g); return glyph; } private: void *data; FTGLglyph *(*makeglyphCallback) (FT_GlyphSlot, void *); }; C_TOR(ftglCreateCustomFont, (char const *fontFilePath, void *data, FTGLglyph * (*makeglyphCallback) (FT_GlyphSlot, void *)), FTCustomFont, (fontFilePath, data, makeglyphCallback), FONT_CUSTOM); #define C_FUN(cret, cname, cargs, cxxerr, cxxname, cxxarg) \ cret cname cargs \ { \ if(!f || !f->ptr) \ { \ fprintf(stderr, "FTGL warning: NULL pointer in %s\n", #cname); \ cxxerr; \ } \ return f->ptr->cxxname cxxarg; \ } // FTFont::~FTFont(); void ftglDestroyFont(FTGLfont *f) { if(!f || !f->ptr) { fprintf(stderr, "FTGL warning: NULL pointer in %s\n", __FUNCTION__); return; } delete f->ptr; free(f); } // bool FTFont::Attach(const char* fontFilePath); C_FUN(int, ftglAttachFile, (FTGLfont *f, const char* path), return 0, Attach, (path)); // bool FTFont::Attach(const unsigned char *pBufferBytes, // size_t bufferSizeInBytes); C_FUN(int, ftglAttachData, (FTGLfont *f, const unsigned char *p, size_t s), return 0, Attach, (p, s)); // void FTFont::GlyphLoadFlags(FT_Int flags); C_FUN(void, ftglSetFontGlyphLoadFlags, (FTGLfont *f, FT_Int flags), return, GlyphLoadFlags, (flags)); // bool FTFont::CharMap(FT_Encoding encoding); C_FUN(int, ftglSetFontCharMap, (FTGLfont *f, FT_Encoding enc), return 0, CharMap, (enc)); // unsigned int FTFont::CharMapCount(); C_FUN(unsigned int, ftglGetFontCharMapCount, (FTGLfont *f), return 0, CharMapCount, ()); // FT_Encoding* FTFont::CharMapList(); C_FUN(FT_Encoding *, ftglGetFontCharMapList, (FTGLfont* f), return NULL, CharMapList, ()); // virtual bool FTFont::FaceSize(const unsigned int size, // const unsigned int res = 72); C_FUN(int, ftglSetFontFaceSize, (FTGLfont *f, unsigned int s, unsigned int r), return 0, FaceSize, (s, r > 0 ? r : 72)); // unsigned int FTFont::FaceSize() const; // XXX: need to call FaceSize() as FTFont::FaceSize() because of FTGLTexture C_FUN(unsigned int, ftglGetFontFaceSize, (FTGLfont *f), return 0, FTFont::FaceSize, ()); // virtual void FTFont::Depth(float depth); C_FUN(void, ftglSetFontDepth, (FTGLfont *f, float d), return, Depth, (d)); // virtual void FTFont::Outset(float front, float back); C_FUN(void, ftglSetFontOutset, (FTGLfont *f, float front, float back), return, FTFont::Outset, (front, back)); // void FTFont::UseDisplayList(bool useList); C_FUN(void, ftglSetFontDisplayList, (FTGLfont *f, int l), return, UseDisplayList, (l != 0)); // float FTFont::Ascender() const; C_FUN(float, ftglGetFontAscender, (FTGLfont *f), return 0.f, Ascender, ()); // float FTFont::Descender() const; C_FUN(float, ftglGetFontDescender, (FTGLfont *f), return 0.f, Descender, ()); // float FTFont::LineHeight() const; C_FUN(float, ftglGetFontLineHeight, (FTGLfont *f), return 0.f, LineHeight, ()); // void FTFont::BBox(const char* string, float& llx, float& lly, float& llz, // float& urx, float& ury, float& urz); extern "C++" { C_FUN(static FTBBox, _ftglGetFontBBox, (FTGLfont *f, char const *s, int len), return static_ftbbox, BBox, (s, len)); } void ftglGetFontBBox(FTGLfont *f, const char* s, int len, float c[6]) { FTBBox ret = _ftglGetFontBBox(f, s, len); FTPoint lower = ret.Lower(), upper = ret.Upper(); c[0] = lower.Xf(); c[1] = lower.Yf(); c[2] = lower.Zf(); c[3] = upper.Xf(); c[4] = upper.Yf(); c[5] = upper.Zf(); } // float FTFont::Advance(const char* string); C_FUN(float, ftglGetFontAdvance, (FTGLfont *f, char const *s), return 0.0, Advance, (s)); // virtual void Render(const char* string, int renderMode); extern "C++" { C_FUN(static FTPoint, _ftglRenderFont, (FTGLfont *f, char const *s, int len, FTPoint pos, FTPoint spacing, int mode), return static_ftpoint, Render, (s, len, pos, spacing, mode)); } void ftglRenderFont(FTGLfont *f, const char *s, int mode) { _ftglRenderFont(f, s, -1, FTPoint(), FTPoint(), mode); } // FT_Error FTFont::Error() const; C_FUN(FT_Error, ftglGetFontError, (FTGLfont *f), return -1, Error, ()); FTGL_END_C_DECLS workbench-1.1.1/src/FtglFont/FTFont/FTFontImpl.h000066400000000000000000000113531255417355300213160ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __FTFontImpl__ #define __FTFontImpl__ #include "FTGL/ftgl.h" #include "FTFace.h" class FTGlyphContainer; class FTGlyph; class FTFontImpl { friend class FTFont; protected: FTFontImpl(FTFont *ftFont, char const *fontFilePath); FTFontImpl(FTFont *ftFont, const unsigned char *pBufferBytes, size_t bufferSizeInBytes); virtual ~FTFontImpl(); virtual bool Attach(const char* fontFilePath); virtual bool Attach(const unsigned char *pBufferBytes, size_t bufferSizeInBytes); virtual void GlyphLoadFlags(FT_Int flags); virtual bool CharMap(FT_Encoding encoding); virtual unsigned int CharMapCount() const; virtual FT_Encoding* CharMapList(); virtual void UseDisplayList(bool useList); virtual float Ascender() const; virtual float Descender() const; virtual float LineHeight() const; virtual bool FaceSize(const unsigned int size, const unsigned int res); virtual unsigned int FaceSize() const; virtual void Depth(float depth); virtual void Outset(float outset); virtual void Outset(float front, float back); virtual FTBBox BBox(const char *s, const int len, FTPoint, FTPoint); virtual FTBBox BBox(const wchar_t *s, const int len, FTPoint, FTPoint); virtual float Advance(const char *s, const int len, FTPoint); virtual float Advance(const wchar_t *s, const int len, FTPoint); virtual FTPoint Render(const char *s, const int len, FTPoint, FTPoint, int); virtual FTPoint Render(const wchar_t *s, const int len, FTPoint, FTPoint, int); /** * Current face object */ FTFace face; /** * Current size object */ FTSize charSize; /** * Flag to enable or disable the use of Display Lists inside FTGL * true turns ON display lists. * false turns OFF display lists. */ bool useDisplayLists; /** * The default glyph loading flags. */ FT_Int load_flags; /** * Current error code. Zero means no error. */ FT_Error err; private: /** * A link back to the interface of which we are the implementation. */ FTFont *intf; /** * Check that the glyph at chr exist. If not load it. * * @param chr character index * @return true if the glyph can be created. */ bool CheckGlyph(const unsigned int chr); /** * An object that holds a list of glyphs */ FTGlyphContainer* glyphList; /** * Current pen or cursor position; */ FTPoint pen; /* Internal generic BBox() implementation */ template inline FTBBox BBoxI(const T *s, const int len, FTPoint position, FTPoint spacing); /* Internal generic Advance() implementation */ template inline float AdvanceI(const T *s, const int len, FTPoint spacing); /* Internal generic Render() implementation */ template inline FTPoint RenderI(const T *s, const int len, FTPoint position, FTPoint spacing, int mode); }; #endif // __FTFontImpl__ workbench-1.1.1/src/FtglFont/FTFont/FTOutlineFont.cpp000066400000000000000000000073361255417355300223750ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "FtglConfig.h" #include "FTGL/ftgl.h" #include "FTInternals.h" #include "FTOutlineFontImpl.h" // // FTOutlineFont // FTOutlineFont::FTOutlineFont(char const *fontFilePath) : FTFont(new FTOutlineFontImpl(this, fontFilePath)) {} FTOutlineFont::FTOutlineFont(const unsigned char *pBufferBytes, size_t bufferSizeInBytes) : FTFont(new FTOutlineFontImpl(this, pBufferBytes, bufferSizeInBytes)) {} FTOutlineFont::~FTOutlineFont() {} FTGlyph* FTOutlineFont::MakeGlyph(FT_GlyphSlot ftGlyph) { FTOutlineFontImpl *myimpl = dynamic_cast(impl); if(!myimpl) { return NULL; } return new FTOutlineGlyph(ftGlyph, myimpl->outset, myimpl->useDisplayLists); } // // FTOutlineFontImpl // FTOutlineFontImpl::FTOutlineFontImpl(FTFont *ftFont, const char* fontFilePath) : FTFontImpl(ftFont, fontFilePath), outset(0.0f) { load_flags = FT_LOAD_NO_HINTING; } FTOutlineFontImpl::FTOutlineFontImpl(FTFont *ftFont, const unsigned char *pBufferBytes, size_t bufferSizeInBytes) : FTFontImpl(ftFont, pBufferBytes, bufferSizeInBytes), outset(0.0f) { load_flags = FT_LOAD_NO_HINTING; } template inline FTPoint FTOutlineFontImpl::RenderI(const T* string, const int len, FTPoint position, FTPoint spacing, int renderMode) { // Protect GL_TEXTURE_2D, glHint(), GL_LINE_SMOOTH and blending functions glPushAttrib(GL_ENABLE_BIT | GL_HINT_BIT | GL_LINE_BIT | GL_COLOR_BUFFER_BIT); glDisable(GL_TEXTURE_2D); glEnable(GL_LINE_SMOOTH); glHint(GL_LINE_SMOOTH_HINT, GL_DONT_CARE); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // GL_ONE FTPoint tmp = FTFontImpl::Render(string, len, position, spacing, renderMode); glPopAttrib(); return tmp; } FTPoint FTOutlineFontImpl::Render(const char * string, const int len, FTPoint position, FTPoint spacing, int renderMode) { return RenderI(string, len, position, spacing, renderMode); } FTPoint FTOutlineFontImpl::Render(const wchar_t * string, const int len, FTPoint position, FTPoint spacing, int renderMode) { return RenderI(string, len, position, spacing, renderMode); } workbench-1.1.1/src/FtglFont/FTFont/FTOutlineFontImpl.h000066400000000000000000000050601255417355300226540ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __FTOutlineFontImpl__ #define __FTOutlineFontImpl__ #include "FTFontImpl.h" class FTGlyph; class FTOutlineFontImpl : public FTFontImpl { friend class FTOutlineFont; protected: FTOutlineFontImpl(FTFont *ftFont, const char* fontFilePath); FTOutlineFontImpl(FTFont *ftFont, const unsigned char *pBufferBytes, size_t bufferSizeInBytes); /** * Set the outset distance for the font. Only implemented by * FTOutlineFont, FTPolygonFont and FTExtrudeFont * * @param outset The outset distance. */ virtual void Outset(float o) { outset = o; } virtual FTPoint Render(const char *s, const int len, FTPoint position, FTPoint spacing, int renderMode); virtual FTPoint Render(const wchar_t *s, const int len, FTPoint position, FTPoint spacing, int renderMode); private: /** * The outset distance for the font. */ float outset; /* Internal generic Render() implementation */ template inline FTPoint RenderI(const T *s, const int len, FTPoint position, FTPoint spacing, int mode); }; #endif // __FTOutlineFontImpl__ workbench-1.1.1/src/FtglFont/FTFont/FTPixmapFont.cpp000066400000000000000000000075431255417355300222140ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "FtglConfig.h" #include "FTGL/ftgl.h" #include "FTInternals.h" #include "FTPixmapFontImpl.h" // // FTPixmapFont // FTPixmapFont::FTPixmapFont(char const *fontFilePath) : FTFont(new FTPixmapFontImpl(this, fontFilePath)) {} FTPixmapFont::FTPixmapFont(const unsigned char *pBufferBytes, size_t bufferSizeInBytes) : FTFont(new FTPixmapFontImpl(this, pBufferBytes, bufferSizeInBytes)) {} FTPixmapFont::~FTPixmapFont() {} FTGlyph* FTPixmapFont::MakeGlyph(FT_GlyphSlot ftGlyph) { return new FTPixmapGlyph(ftGlyph); } // // FTPixmapFontImpl // FTPixmapFontImpl::FTPixmapFontImpl(FTFont *ftFont, const char* fontFilePath) : FTFontImpl(ftFont, fontFilePath) { load_flags = FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP; } FTPixmapFontImpl::FTPixmapFontImpl(FTFont *ftFont, const unsigned char *pBufferBytes, size_t bufferSizeInBytes) : FTFontImpl(ftFont, pBufferBytes, bufferSizeInBytes) { load_flags = FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP; } template inline FTPoint FTPixmapFontImpl::RenderI(const T* string, const int len, FTPoint position, FTPoint spacing, int renderMode) { // Protect GL_TEXTURE_2D and GL_BLEND, glPixelTransferf(), and blending // functions. glPushAttrib(GL_ENABLE_BIT | GL_PIXEL_MODE_BIT | GL_COLOR_BUFFER_BIT); // Protect glPixelStorei() calls (made by FTPixmapGlyphImpl::RenderImpl). glPushClientAttrib(GL_CLIENT_PIXEL_STORE_BIT); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDisable(GL_TEXTURE_2D); GLfloat ftglColour[4]; glGetFloatv(GL_CURRENT_RASTER_COLOR, ftglColour); glPixelTransferf(GL_RED_SCALE, ftglColour[0]); glPixelTransferf(GL_GREEN_SCALE, ftglColour[1]); glPixelTransferf(GL_BLUE_SCALE, ftglColour[2]); glPixelTransferf(GL_ALPHA_SCALE, ftglColour[3]); FTPoint tmp = FTFontImpl::Render(string, len, position, spacing, renderMode); glPopClientAttrib(); glPopAttrib(); return tmp; } FTPoint FTPixmapFontImpl::Render(const char * string, const int len, FTPoint position, FTPoint spacing, int renderMode) { return RenderI(string, len, position, spacing, renderMode); } FTPoint FTPixmapFontImpl::Render(const wchar_t * string, const int len, FTPoint position, FTPoint spacing, int renderMode) { return RenderI(string, len, position, spacing, renderMode); } workbench-1.1.1/src/FtglFont/FTFont/FTPixmapFontImpl.h000066400000000000000000000043071255417355300224760ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __FTPixmapFontImpl__ #define __FTPixmapFontImpl__ #include "FTFontImpl.h" class FTGlyph; class FTPixmapFontImpl : public FTFontImpl { friend class FTPixmapFont; protected: FTPixmapFontImpl(FTFont *ftFont, const char* fontFilePath); FTPixmapFontImpl(FTFont *ftFont, const unsigned char *pBufferBytes, size_t bufferSizeInBytes); virtual FTPoint Render(const char *s, const int len, FTPoint position, FTPoint spacing, int renderMode); virtual FTPoint Render(const wchar_t *s, const int len, FTPoint position, FTPoint spacing, int renderMode); private: /* Internal generic Render() implementation */ template inline FTPoint RenderI(const T *s, const int len, FTPoint position, FTPoint spacing, int mode); }; #endif // __FTPixmapFontImpl__ workbench-1.1.1/src/FtglFont/FTFont/FTPolygonFont.cpp000066400000000000000000000047231255417355300224020ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "FtglConfig.h" #include "FTGL/ftgl.h" #include "FTInternals.h" #include "FTPolygonFontImpl.h" // // FTPolygonFont // FTPolygonFont::FTPolygonFont(char const *fontFilePath) : FTFont(new FTPolygonFontImpl(this, fontFilePath)) {} FTPolygonFont::FTPolygonFont(const unsigned char *pBufferBytes, size_t bufferSizeInBytes) : FTFont(new FTPolygonFontImpl(this, pBufferBytes, bufferSizeInBytes)) {} FTPolygonFont::~FTPolygonFont() {} FTGlyph* FTPolygonFont::MakeGlyph(FT_GlyphSlot ftGlyph) { FTPolygonFontImpl *myimpl = dynamic_cast(impl); if(!myimpl) { return NULL; } return new FTPolygonGlyph(ftGlyph, myimpl->outset, myimpl->useDisplayLists); } // // FTPolygonFontImpl // FTPolygonFontImpl::FTPolygonFontImpl(FTFont *ftFont, const char* fontFilePath) : FTFontImpl(ftFont, fontFilePath), outset(0.0f) { load_flags = FT_LOAD_NO_HINTING; } FTPolygonFontImpl::FTPolygonFontImpl(FTFont *ftFont, const unsigned char *pBufferBytes, size_t bufferSizeInBytes) : FTFontImpl(ftFont, pBufferBytes, bufferSizeInBytes), outset(0.0f) { load_flags = FT_LOAD_NO_HINTING; } workbench-1.1.1/src/FtglFont/FTFont/FTPolygonFontImpl.h000066400000000000000000000040021255417355300226570ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __FTPolygonFontImpl__ #define __FTPolygonFontImpl__ #include "FTFontImpl.h" class FTGlyph; class FTPolygonFontImpl : public FTFontImpl { friend class FTPolygonFont; protected: FTPolygonFontImpl(FTFont *ftFont, const char* fontFilePath); FTPolygonFontImpl(FTFont *ftFont, const unsigned char *pBufferBytes, size_t bufferSizeInBytes); /** * Set the outset distance for the font. Only implemented by * FTOutlineFont, FTPolygonFont and FTExtrudeFont * * @param depth The outset distance. */ virtual void Outset(float o) { outset = o; } private: /** * The outset distance (front and back) for the font. */ float outset; }; #endif // __FTPolygonFontImpl__ workbench-1.1.1/src/FtglFont/FTFont/FTTextureFont.cpp000066400000000000000000000164531255417355300224160ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "FtglConfig.h" #include #include // For memset #include "FTGL/ftgl.h" #include "FTInternals.h" #include "../FTGlyph/FTTextureGlyphImpl.h" #include "./FTTextureFontImpl.h" // // FTTextureFont // FTTextureFont::FTTextureFont(char const *fontFilePath) : FTFont(new FTTextureFontImpl(this, fontFilePath)) {} FTTextureFont::FTTextureFont(const unsigned char *pBufferBytes, size_t bufferSizeInBytes) : FTFont(new FTTextureFontImpl(this, pBufferBytes, bufferSizeInBytes)) {} FTTextureFont::~FTTextureFont() {} FTGlyph* FTTextureFont::MakeGlyph(FT_GlyphSlot ftGlyph) { FTTextureFontImpl *myimpl = dynamic_cast(impl); if(!myimpl) { return NULL; } return myimpl->MakeGlyphImpl(ftGlyph); } // // FTTextureFontImpl // static inline GLuint NextPowerOf2(GLuint in) { in -= 1; in |= in >> 16; in |= in >> 8; in |= in >> 4; in |= in >> 2; in |= in >> 1; return in + 1; } FTTextureFontImpl::FTTextureFontImpl(FTFont *ftFont, const char* fontFilePath) : FTFontImpl(ftFont, fontFilePath), maximumGLTextureSize(0), textureWidth(0), textureHeight(0), glyphHeight(0), glyphWidth(0), padding(3), xOffset(0), yOffset(0) { load_flags = FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP; remGlyphs = numGlyphs = face.GlyphCount(); } FTTextureFontImpl::FTTextureFontImpl(FTFont *ftFont, const unsigned char *pBufferBytes, size_t bufferSizeInBytes) : FTFontImpl(ftFont, pBufferBytes, bufferSizeInBytes), maximumGLTextureSize(0), textureWidth(0), textureHeight(0), glyphHeight(0), glyphWidth(0), padding(3), xOffset(0), yOffset(0) { load_flags = FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP; remGlyphs = numGlyphs = face.GlyphCount(); } FTTextureFontImpl::~FTTextureFontImpl() { if(textureIDList.size()) { glDeleteTextures((GLsizei)textureIDList.size(), (const GLuint*)&textureIDList[0]); } } FTGlyph* FTTextureFontImpl::MakeGlyphImpl(FT_GlyphSlot ftGlyph) { glyphHeight = static_cast(charSize.Height() + 0.5); glyphWidth = static_cast(charSize.Width() + 0.5); if(glyphHeight < 1) glyphHeight = 1; if(glyphWidth < 1) glyphWidth = 1; if(textureIDList.empty()) { textureIDList.push_back(CreateTexture()); xOffset = yOffset = padding; } if(xOffset > (textureWidth - glyphWidth)) { xOffset = padding; yOffset += glyphHeight; if(yOffset > (textureHeight - glyphHeight)) { textureIDList.push_back(CreateTexture()); yOffset = padding; } } FTTextureGlyph* tempGlyph = new FTTextureGlyph(ftGlyph, textureIDList[textureIDList.size() - 1], xOffset, yOffset, textureWidth, textureHeight); xOffset += static_cast(tempGlyph->BBox().Upper().X() - tempGlyph->BBox().Lower().X() + padding + 0.5); --remGlyphs; return tempGlyph; } void FTTextureFontImpl::CalculateTextureSize() { if(!maximumGLTextureSize) { maximumGLTextureSize = 1024; glGetIntegerv(GL_MAX_TEXTURE_SIZE, (GLint*)&maximumGLTextureSize); assert(maximumGLTextureSize); // If you hit this then you have an invalid OpenGL context. } textureWidth = NextPowerOf2((remGlyphs * glyphWidth) + (padding * 2)); textureWidth = textureWidth > maximumGLTextureSize ? maximumGLTextureSize : textureWidth; int h = static_cast((textureWidth - (padding * 2)) / glyphWidth + 0.5); textureHeight = NextPowerOf2(((numGlyphs / h) + 1) * glyphHeight); textureHeight = textureHeight > maximumGLTextureSize ? maximumGLTextureSize : textureHeight; } GLuint FTTextureFontImpl::CreateTexture() { CalculateTextureSize(); int totalMemory = textureWidth * textureHeight; unsigned char* textureMemory = new unsigned char[totalMemory]; memset(textureMemory, 0, totalMemory); GLuint textID; glGenTextures(1, (GLuint*)&textID); glBindTexture(GL_TEXTURE_2D, textID); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, textureWidth, textureHeight, 0, GL_ALPHA, GL_UNSIGNED_BYTE, textureMemory); delete [] textureMemory; return textID; } bool FTTextureFontImpl::FaceSize(const unsigned int size, const unsigned int res) { if(!textureIDList.empty()) { glDeleteTextures((GLsizei)textureIDList.size(), (const GLuint*)&textureIDList[0]); textureIDList.clear(); remGlyphs = numGlyphs = face.GlyphCount(); } return FTFontImpl::FaceSize(size, res); } template inline FTPoint FTTextureFontImpl::RenderI(const T* string, const int len, FTPoint position, FTPoint spacing, int renderMode) { // Protect GL_TEXTURE_2D, GL_BLEND and blending functions glPushAttrib(GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // GL_ONE glEnable(GL_TEXTURE_2D); FTTextureGlyphImpl::ResetActiveTexture(); FTPoint tmp = FTFontImpl::Render(string, len, position, spacing, renderMode); glPopAttrib(); return tmp; } FTPoint FTTextureFontImpl::Render(const char * string, const int len, FTPoint position, FTPoint spacing, int renderMode) { return RenderI(string, len, position, spacing, renderMode); } FTPoint FTTextureFontImpl::Render(const wchar_t * string, const int len, FTPoint position, FTPoint spacing, int renderMode) { return RenderI(string, len, position, spacing, renderMode); } workbench-1.1.1/src/FtglFont/FTFont/FTTextureFontImpl.h000066400000000000000000000110051255417355300226710ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __FTTextureFontImpl__ #define __FTTextureFontImpl__ #include "FTFontImpl.h" #include "FTVector.h" class FTTextureGlyph; class FTTextureFontImpl : public FTFontImpl { friend class FTTextureFont; protected: FTTextureFontImpl(FTFont *ftFont, const char* fontFilePath); FTTextureFontImpl(FTFont *ftFont, const unsigned char *pBufferBytes, size_t bufferSizeInBytes); virtual ~FTTextureFontImpl(); /** * Set the char size for the current face. * * @param size the face size in points (1/72 inch) * @param res the resolution of the target device. * @return true if size was set correctly */ virtual bool FaceSize(const unsigned int size, const unsigned int res = 72); virtual FTPoint Render(const char *s, const int len, FTPoint position, FTPoint spacing, int renderMode); virtual FTPoint Render(const wchar_t *s, const int len, FTPoint position, FTPoint spacing, int renderMode); private: /** * Create an FTTextureGlyph object for the base class. */ FTGlyph* MakeGlyphImpl(FT_GlyphSlot ftGlyph); /** * Get the size of a block of memory required to layout the glyphs * * Calculates a width and height based on the glyph sizes and the * number of glyphs. It over estimates. */ inline void CalculateTextureSize(); /** * Creates a 'blank' OpenGL texture object. * * The format is GL_ALPHA and the params are * GL_TEXTURE_WRAP_S = GL_CLAMP * GL_TEXTURE_WRAP_T = GL_CLAMP * GL_TEXTURE_MAG_FILTER = GL_LINEAR * GL_TEXTURE_MIN_FILTER = GL_LINEAR * Note that mipmapping is NOT used */ inline GLuint CreateTexture(); /** * The maximum texture dimension on this OpenGL implemetation */ GLsizei maximumGLTextureSize; /** * The minimum texture width required to hold the glyphs */ GLsizei textureWidth; /** * The minimum texture height required to hold the glyphs */ GLsizei textureHeight; /** *An array of texture ids */ FTVector textureIDList; /** * The max height for glyphs in the current font */ int glyphHeight; /** * The max width for glyphs in the current font */ int glyphWidth; /** * A value to be added to the height and width to ensure that * glyphs don't overlap in the texture */ unsigned int padding; /** * */ unsigned int numGlyphs; /** */ unsigned int remGlyphs; /** */ int xOffset; /** */ int yOffset; /* Internal generic Render() implementation */ template inline FTPoint RenderI(const T *s, const int len, FTPoint position, FTPoint spacing, int mode); }; #endif // __FTTextureFontImpl__ workbench-1.1.1/src/FtglFont/FTGL/000077500000000000000000000000001255417355300165545ustar00rootroot00000000000000workbench-1.1.1/src/FtglFont/FTGL/FTBBox.h000066400000000000000000000117771255417355300200260ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * Copyright (c) 2008 Sean Morrison * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __ftgl__ # warning This header is deprecated. Please use from now. # include #endif #ifndef __FTBBox__ #define __FTBBox__ #ifdef __cplusplus /** * FTBBox is a convenience class for handling bounding boxes. */ class FTGL_EXPORT FTBBox { public: /** * Default constructor. Bounding box is set to zero. */ FTBBox() : lower(0.0f, 0.0f, 0.0f), upper(0.0f, 0.0f, 0.0f) {} /** * Constructor. */ FTBBox(float lx, float ly, float lz, float ux, float uy, float uz) : lower(lx, ly, lz), upper(ux, uy, uz) {} /** * Constructor. */ FTBBox(FTPoint l, FTPoint u) : lower(l), upper(u) {} /** * Constructor. Extracts a bounding box from a freetype glyph. Uses * the control box for the glyph. FT_Glyph_Get_CBox() * * @param glyph A freetype glyph */ FTBBox(FT_GlyphSlot glyph) : lower(0.0f, 0.0f, 0.0f), upper(0.0f, 0.0f, 0.0f) { FT_BBox bbox; FT_Outline_Get_CBox(&(glyph->outline), &bbox); lower.X(static_cast(bbox.xMin) / 64.0f); lower.Y(static_cast(bbox.yMin) / 64.0f); lower.Z(0.0f); upper.X(static_cast(bbox.xMax) / 64.0f); upper.Y(static_cast(bbox.yMax) / 64.0f); upper.Z(0.0f); } /** * Destructor */ ~FTBBox() {} /** * Mark the bounds invalid by setting all lower dimensions greater * than the upper dimensions. */ void Invalidate() { lower = FTPoint(1.0f, 1.0f, 1.0f); upper = FTPoint(-1.0f, -1.0f, -1.0f); } /** * Determines if this bounding box is valid. * * @return True if all lower values are <= the corresponding * upper values. */ bool IsValid() { return lower.X() <= upper.X() && lower.Y() <= upper.Y() && lower.Z() <= upper.Z(); } /** * Move the Bounding Box by a vector. * * @param vector The vector to move the bbox in 3D space. */ FTBBox& operator += (const FTPoint vector) { lower += vector; upper += vector; return *this; } /** * Combine two bounding boxes. The result is the smallest bounding * box containing the two original boxes. * * @param bbox The bounding box to merge with the second one. */ FTBBox& operator |= (const FTBBox& bbox) { if(bbox.lower.X() < lower.X()) lower.X(bbox.lower.X()); if(bbox.lower.Y() < lower.Y()) lower.Y(bbox.lower.Y()); if(bbox.lower.Z() < lower.Z()) lower.Z(bbox.lower.Z()); if(bbox.upper.X() > upper.X()) upper.X(bbox.upper.X()); if(bbox.upper.Y() > upper.Y()) upper.Y(bbox.upper.Y()); if(bbox.upper.Z() > upper.Z()) upper.Z(bbox.upper.Z()); return *this; } void SetDepth(float depth) { if(depth > 0) upper.Z(lower.Z() + depth); else lower.Z(upper.Z() + depth); } inline FTPoint const Upper() const { return upper; } inline FTPoint const Lower() const { return lower; } private: /** * The bounds of the box */ FTPoint lower, upper; }; #endif //__cplusplus #endif // __FTBBox__ workbench-1.1.1/src/FtglFont/FTGL/FTBitmapGlyph.h000066400000000000000000000047251255417355300214070ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * Copyright (c) 2008 Sean Morrison * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __ftgl__ # warning This header is deprecated. Please use from now. # include #endif #ifndef __FTBitmapGlyph__ #define __FTBitmapGlyph__ #ifdef __cplusplus /** * FTBitmapGlyph is a specialisation of FTGlyph for creating bitmaps. */ class FTGL_EXPORT FTBitmapGlyph : public FTGlyph { public: /** * Constructor * * @param glyph The Freetype glyph to be processed */ FTBitmapGlyph(FT_GlyphSlot glyph); /** * Destructor */ virtual ~FTBitmapGlyph(); /** * Render this glyph at the current pen position. * * @param pen The current pen position. * @param renderMode Render mode to display * @return The advance distance for this glyph. */ virtual const FTPoint& Render(const FTPoint& pen, int renderMode); }; #endif //__cplusplus FTGL_BEGIN_C_DECLS /** * Create a specialisation of FTGLglyph for creating bitmaps. * * @param glyph The Freetype glyph to be processed * @return An FTGLglyph* object. */ FTGL_EXPORT FTGLglyph *ftglCreateBitmapGlyph(FT_GlyphSlot glyph); FTGL_END_C_DECLS #endif // __FTBitmapGlyph__ workbench-1.1.1/src/FtglFont/FTGL/FTBuffer.h000066400000000000000000000063731255417355300204010ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2008 Sam Hocevar * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __ftgl__ # warning Please use instead of . # include #endif #ifndef __FTBuffer__ #define __FTBuffer__ #ifdef __cplusplus /** * FTBuffer is a helper class for pixel buffers. * * It provides the interface between FTBufferFont and FTBufferGlyph to * optimise rendering operations. * * @see FTBufferGlyph * @see FTBufferFont */ class FTGL_EXPORT FTBuffer { public: /** * Default constructor. */ FTBuffer(); /** * Destructor */ ~FTBuffer(); /** * Get the pen's position in the buffer. * * @return The pen's position as an FTPoint object. */ inline FTPoint Pos() const { return pos; } /** * Set the pen's position in the buffer. * * @param arg An FTPoint object with the desired pen's position. */ inline void Pos(FTPoint arg) { pos = arg; } /** * Set the buffer's size. * * @param w The buffer's desired width, in pixels. * @param h The buffer's desired height, in pixels. */ void Size(int w, int h); /** * Get the buffer's width. * * @return The buffer's width, in pixels. */ inline int Width() const { return width; } /** * Get the buffer's height. * * @return The buffer's height, in pixels. */ inline int Height() const { return height; } /** * Get the buffer's direct pixel buffer. * * @return A read-write pointer to the buffer's pixels. */ inline unsigned char *Pixels() const { return pixels; } private: /** * Buffer's width and height. */ int width, height; /** * Buffer's pixel buffer. */ unsigned char *pixels; /** * Buffer's internal pen position. */ FTPoint pos; }; #endif //__cplusplus #endif // __FTBuffer__ workbench-1.1.1/src/FtglFont/FTGL/FTBufferFont.h000066400000000000000000000056341255417355300212270ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2008 Sam Hocevar * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __ftgl__ # warning Please use instead of . # include #endif #ifndef __FTBufferFont__ #define __FTBufferFont__ #ifdef __cplusplus /** * FTBufferFont is a specialisation of the FTFont class for handling * memory buffer fonts. * * @see FTFont */ class FTGL_EXPORT FTBufferFont : public FTFont { public: /** * Open and read a font file. Sets Error flag. * * @param fontFilePath font file path. */ FTBufferFont(const char* fontFilePath); /** * Open and read a font from a buffer in memory. Sets Error flag. * The buffer is owned by the client and is NOT copied by FTGL. The * pointer must be valid while using FTGL. * * @param pBufferBytes the in-memory buffer * @param bufferSizeInBytes the length of the buffer in bytes */ FTBufferFont(const unsigned char *pBufferBytes, size_t bufferSizeInBytes); /** * Destructor */ ~FTBufferFont(); protected: /** * Construct a glyph of the correct type. * * Clients must override the function and return their specialised * FTGlyph. * * @param slot A FreeType glyph slot. * @return An FT****Glyph or null on failure. */ virtual FTGlyph* MakeGlyph(FT_GlyphSlot slot); }; #endif //__cplusplus FTGL_BEGIN_C_DECLS /** * Create a specialised FTGLfont object for handling memory buffer fonts. * * @param file The font file name. * @return An FTGLfont* object. * * @see FTGLfont */ FTGL_EXPORT FTGLfont *ftglCreateBufferFont(const char *file); FTGL_END_C_DECLS #endif // __FTBufferFont__ workbench-1.1.1/src/FtglFont/FTGL/FTBufferGlyph.h000066400000000000000000000042621255417355300214000ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2008 Sam Hocevar * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __ftgl__ # warning Please use instead of . # include #endif #ifndef __FTBufferGlyph__ #define __FTBufferGlyph__ #ifdef __cplusplus /** * FTBufferGlyph is a specialisation of FTGlyph for memory buffer rendering. */ class FTGL_EXPORT FTBufferGlyph : public FTGlyph { public: /** * Constructor * * @param glyph The Freetype glyph to be processed * @param buffer An FTBuffer object in which to render the glyph. */ FTBufferGlyph(FT_GlyphSlot glyph, FTBuffer *buffer); /** * Destructor */ virtual ~FTBufferGlyph(); /** * Render this glyph at the current pen position. * * @param pen The current pen position. * @param renderMode Render mode to display * @return The advance distance for this glyph. */ virtual const FTPoint& Render(const FTPoint& pen, int renderMode); }; #endif //__cplusplus #endif // __FTBufferGlyph__ workbench-1.1.1/src/FtglFont/FTGL/FTExtrdGlyph.h000066400000000000000000000072751255417355300212640ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * Copyright (c) 2008 Sean Morrison * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __ftgl__ # warning This header is deprecated. Please use from now. # include #endif #ifndef __FTExtrudeGlyph__ #define __FTExtrudeGlyph__ #ifdef __cplusplus /** * FTExtrudeGlyph is a specialisation of FTGlyph for creating tessellated * extruded polygon glyphs. */ class FTGL_EXPORT FTExtrudeGlyph : public FTGlyph { public: /** * Constructor. Sets the Error to Invalid_Outline if the glyph isn't * an outline. * * @param glyph The Freetype glyph to be processed * @param depth The distance along the z axis to extrude the glyph * @param frontOutset outset contour size * @param backOutset outset contour size * @param useDisplayList Enable or disable the use of Display Lists * for this glyph * true turns ON display lists. * false turns OFF display lists. */ FTExtrudeGlyph(FT_GlyphSlot glyph, float depth, float frontOutset, float backOutset, bool useDisplayList); /** * Destructor */ virtual ~FTExtrudeGlyph(); /** * Render this glyph at the current pen position. * * @param pen The current pen position. * @param renderMode Render mode to display * @return The advance distance for this glyph. */ virtual const FTPoint& Render(const FTPoint& pen, int renderMode); }; #define FTExtrdGlyph FTExtrudeGlyph #endif //__cplusplus FTGL_BEGIN_C_DECLS /** * Create a specialisation of FTGLglyph for creating tessellated * extruded polygon glyphs. * * @param glyph The Freetype glyph to be processed * @param depth The distance along the z axis to extrude the glyph * @param frontOutset outset contour size * @param backOutset outset contour size * @param useDisplayList Enable or disable the use of Display Lists * for this glyph * true turns ON display lists. * false turns OFF display lists. * @return An FTGLglyph* object. */ FTGL_EXPORT FTGLglyph *ftglCreateExtrudeGlyph(FT_GlyphSlot glyph, float depth, float frontOutset, float backOutset, int useDisplayList); FTGL_END_C_DECLS #endif // __FTExtrudeGlyph__ workbench-1.1.1/src/FtglFont/FTGL/FTFont.h000066400000000000000000000473031255417355300200740ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * Copyright (c) 2008 Sean Morrison * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __ftgl__ # warning This header is deprecated. Please use from now. # include #endif #ifndef __FTFont__ #define __FTFont__ #ifdef __cplusplus class FTFontImpl; /** * FTFont is the public interface for the FTGL library. * * Specific font classes are derived from this class. It uses the helper * classes FTFace and FTSize to access the Freetype library. This class * is abstract and deriving classes must implement the protected * MakeGlyph function to create glyphs of the * appropriate type. * * It is good practice after using these functions to test the error * code returned. FT_Error Error(). Check the freetype file * fterrdef.h for error definitions. * * @see FTFace * @see FTSize */ class FTGL_EXPORT FTFont { protected: /** * Open and read a font file. Sets Error flag. * * @param fontFilePath font file path. */ FTFont(char const *fontFilePath); /** * Open and read a font from a buffer in memory. Sets Error flag. * The buffer is owned by the client and is NOT copied by FTGL. The * pointer must be valid while using FTGL. * * @param pBufferBytes the in-memory buffer * @param bufferSizeInBytes the length of the buffer in bytes */ FTFont(const unsigned char *pBufferBytes, size_t bufferSizeInBytes); private: /* Allow our internal subclasses to access the private constructor */ friend class FTBitmapFont; friend class FTBufferFont; friend class FTExtrudeFont; friend class FTOutlineFont; friend class FTPixmapFont; friend class FTPolygonFont; friend class FTTextureFont; /** * Internal FTGL FTFont constructor. For private use only. * * @param pImpl Internal implementation object. Will be destroyed * upon FTFont deletion. */ FTFont(FTFontImpl *pImpl); public: virtual ~FTFont(); /** * Attach auxilliary file to font e.g font metrics. * * Note: not all font formats implement this function. * * @param fontFilePath auxilliary font file path. * @return true if file has been attached * successfully. */ virtual bool Attach(const char* fontFilePath); /** * Attach auxilliary data to font e.g font metrics, from memory. * * Note: not all font formats implement this function. * * @param pBufferBytes the in-memory buffer. * @param bufferSizeInBytes the length of the buffer in bytes. * @return true if file has been attached * successfully. */ virtual bool Attach(const unsigned char *pBufferBytes, size_t bufferSizeInBytes); /** * Set the glyph loading flags. By default, fonts use the most * sensible flags when loading a font's glyph using FT_Load_Glyph(). * This function allows to override the default flags. * * @param flags The glyph loading flags. */ virtual void GlyphLoadFlags(FT_Int flags); /** * Set the character map for the face. * * @param encoding Freetype enumerate for char map code. * @return true if charmap was valid and * set correctly. */ virtual bool CharMap(FT_Encoding encoding); /** * Get the number of character maps in this face. * * @return character map count. */ virtual unsigned int CharMapCount() const; /** * Get a list of character maps in this face. * * @return pointer to the first encoding. */ virtual FT_Encoding* CharMapList(); /** * Set the char size for the current face. * * @param size the face size in points (1/72 inch) * @param res the resolution of the target device. * @return true if size was set correctly */ virtual bool FaceSize(const unsigned int size, const unsigned int res = 72); /** * Get the current face size in points (1/72 inch). * * @return face size */ virtual unsigned int FaceSize() const; /** * Set the extrusion distance for the font. Only implemented by * FTExtrudeFont * * @param depth The extrusion distance. */ virtual void Depth(float depth); /** * Set the outset distance for the font. Only implemented by * FTOutlineFont, FTPolygonFont and FTExtrudeFont * * @param outset The outset distance. */ virtual void Outset(float outset); /** * Set the front and back outset distances for the font. Only * implemented by FTExtrudeFont * * @param front The front outset distance. * @param back The back outset distance. */ virtual void Outset(float front, float back); /** * Enable or disable the use of Display Lists inside FTGL * * @param useList true turns ON display lists. * false turns OFF display lists. */ virtual void UseDisplayList(bool useList); /** * Get the global ascender height for the face. * * @return Ascender height */ virtual float Ascender() const; /** * Gets the global descender height for the face. * * @return Descender height */ virtual float Descender() const; /** * Gets the line spacing for the font. * * @return Line height */ virtual float LineHeight() const; /** * Get the bounding box for a string. * * @param string A char buffer. * @param len The length of the string. If < 0 then all characters * will be checked until a null character is encountered * (optional). * @param position The pen position of the first character (optional). * @param spacing A displacement vector to add after each character * has been checked (optional). * @return The corresponding bounding box. */ virtual FTBBox BBox(const char *string, const int len = -1, FTPoint position = FTPoint(), FTPoint spacing = FTPoint()); /** * Get the bounding box for a string (deprecated). * * @param string A char buffer. * @param llx Lower left near x coordinate. * @param lly Lower left near y coordinate. * @param llz Lower left near z coordinate. * @param urx Upper right far x coordinate. * @param ury Upper right far y coordinate. * @param urz Upper right far z coordinate. */ void BBox(const char* string, float& llx, float& lly, float& llz, float& urx, float& ury, float& urz) { FTBBox b = BBox(string); llx = b.Lower().Xf(); lly = b.Lower().Yf(); llz = b.Lower().Zf(); urx = b.Upper().Xf(); ury = b.Upper().Yf(); urz = b.Upper().Zf(); } /** * Get the bounding box for a string. * * @param string A wchar_t buffer. * @param len The length of the string. If < 0 then all characters * will be checked until a null character is encountered * (optional). * @param position The pen position of the first character (optional). * @param spacing A displacement vector to add after each character * has been checked (optional). * @return The corresponding bounding box. */ virtual FTBBox BBox(const wchar_t *string, const int len = -1, FTPoint position = FTPoint(), FTPoint spacing = FTPoint()); /** * Get the bounding box for a string (deprecated). * * @param string A wchar_t buffer. * @param llx Lower left near x coordinate. * @param lly Lower left near y coordinate. * @param llz Lower left near z coordinate. * @param urx Upper right far x coordinate. * @param ury Upper right far y coordinate. * @param urz Upper right far z coordinate. */ void BBox(const wchar_t* string, float& llx, float& lly, float& llz, float& urx, float& ury, float& urz) { FTBBox b = BBox(string); llx = b.Lower().Xf(); lly = b.Lower().Yf(); llz = b.Lower().Zf(); urx = b.Upper().Xf(); ury = b.Upper().Yf(); urz = b.Upper().Zf(); } /** * Get the advance for a string. * * @param string 'C' style string to be checked. * @param len The length of the string. If < 0 then all characters * will be checked until a null character is encountered * (optional). * @param spacing A displacement vector to add after each character * has been checked (optional). * @return The string's advance width. */ virtual float Advance(const char* string, const int len = -1, FTPoint spacing = FTPoint()); /** * Get the advance for a string. * * @param string A wchar_t string * @param len The length of the string. If < 0 then all characters * will be checked until a null character is encountered * (optional). * @param spacing A displacement vector to add after each character * has been checked (optional). * @return The string's advance width. */ virtual float Advance(const wchar_t* string, const int len = -1, FTPoint spacing = FTPoint()); /** * Render a string of characters. * * @param string 'C' style string to be output. * @param len The length of the string. If < 0 then all characters * will be displayed until a null character is encountered * (optional). * @param position The pen position of the first character (optional). * @param spacing A displacement vector to add after each character * has been displayed (optional). * @param renderMode Render mode to use for display (optional). * @return The new pen position after the last character was output. */ virtual FTPoint Render(const char* string, const int len = -1, FTPoint position = FTPoint(), FTPoint spacing = FTPoint(), int renderMode = FTGL::RENDER_ALL); /** * Render a string of characters * * @param string wchar_t string to be output. * @param len The length of the string. If < 0 then all characters * will be displayed until a null character is encountered * (optional). * @param position The pen position of the first character (optional). * @param spacing A displacement vector to add after each character * has been displayed (optional). * @param renderMode Render mode to use for display (optional). * @return The new pen position after the last character was output. */ virtual FTPoint Render(const wchar_t *string, const int len = -1, FTPoint position = FTPoint(), FTPoint spacing = FTPoint(), int renderMode = FTGL::RENDER_ALL); /** * Queries the Font for errors. * * @return The current error code. */ virtual FT_Error Error() const; protected: /* Allow impl to access MakeGlyph */ friend class FTFontImpl; /** * Construct a glyph of the correct type. * * Clients must override the function and return their specialised * FTGlyph. * * @param slot A FreeType glyph slot. * @return An FT****Glyph or null on failure. */ virtual FTGlyph* MakeGlyph(FT_GlyphSlot slot) = 0; private: /** * Internal FTGL FTFont implementation object. For private use only. */ FTFontImpl *impl; }; #endif //__cplusplus FTGL_BEGIN_C_DECLS /** * FTGLfont is the public interface for the FTGL library. * * It is good practice after using these functions to test the error * code returned. FT_Error Error(). Check the freetype file * fterrdef.h for error definitions. */ struct _FTGLFont; typedef struct _FTGLfont FTGLfont; /** * Create a custom FTGL font object. * * @param fontFilePath The font file name. * @param data A pointer to private data that will be passed to callbacks. * @param makeglyphCallback A glyph-making callback function. * @return An FTGLfont* object. */ FTGL_EXPORT FTGLfont *ftglCreateCustomFont(char const *fontFilePath, void *data, FTGLglyph * (*makeglyphCallback) (FT_GlyphSlot, void *)); /** * Destroy an FTGL font object. * * @param font An FTGLfont* object. */ FTGL_EXPORT void ftglDestroyFont(FTGLfont* font); /** * Attach auxilliary file to font e.g. font metrics. * * Note: not all font formats implement this function. * * @param font An FTGLfont* object. * @param path Auxilliary font file path. * @return 1 if file has been attached successfully. */ FTGL_EXPORT int ftglAttachFile(FTGLfont* font, const char* path); /** * Attach auxilliary data to font, e.g. font metrics, from memory. * * Note: not all font formats implement this function. * * @param font An FTGLfont* object. * @param data The in-memory buffer. * @param size The length of the buffer in bytes. * @return 1 if file has been attached successfully. */ FTGL_EXPORT int ftglAttachData(FTGLfont* font, const unsigned char * data, size_t size); /** * Set the character map for the face. * * @param font An FTGLfont* object. * @param encoding Freetype enumerate for char map code. * @return 1 if charmap was valid and set correctly. */ FTGL_EXPORT int ftglSetFontCharMap(FTGLfont* font, FT_Encoding encoding); /** * Get the number of character maps in this face. * * @param font An FTGLfont* object. * @return character map count. */ FTGL_EXPORT unsigned int ftglGetFontCharMapCount(FTGLfont* font); /** * Get a list of character maps in this face. * * @param font An FTGLfont* object. * @return pointer to the first encoding. */ FTGL_EXPORT FT_Encoding* ftglGetFontCharMapList(FTGLfont* font); /** * Set the char size for the current face. * * @param font An FTGLfont* object. * @param size The face size in points (1/72 inch). * @param res The resolution of the target device, or 0 to use the default * value of 72. * @return 1 if size was set correctly. */ FTGL_EXPORT int ftglSetFontFaceSize(FTGLfont* font, unsigned int size, unsigned int res); /** * Get the current face size in points (1/72 inch). * * @param font An FTGLfont* object. * @return face size */ FTGL_EXPORT unsigned int ftglGetFontFaceSize(FTGLfont* font); /** * Set the extrusion distance for the font. Only implemented by * FTExtrudeFont. * * @param font An FTGLfont* object. * @param depth The extrusion distance. */ FTGL_EXPORT void ftglSetFontDepth(FTGLfont* font, float depth); /** * Set the outset distance for the font. Only FTOutlineFont, FTPolygonFont * and FTExtrudeFont implement front outset. Only FTExtrudeFont implements * back outset. * * @param font An FTGLfont* object. * @param front The front outset distance. * @param back The back outset distance. */ FTGL_EXPORT void ftglSetFontOutset(FTGLfont* font, float front, float back); /** * Enable or disable the use of Display Lists inside FTGL. * * @param font An FTGLfont* object. * @param useList 1 turns ON display lists. * 0 turns OFF display lists. */ FTGL_EXPORT void ftglSetFontDisplayList(FTGLfont* font, int useList); /** * Get the global ascender height for the face. * * @param font An FTGLfont* object. * @return Ascender height */ FTGL_EXPORT float ftglGetFontAscender(FTGLfont* font); /** * Gets the global descender height for the face. * * @param font An FTGLfont* object. * @return Descender height */ FTGL_EXPORT float ftglGetFontDescender(FTGLfont* font); /** * Gets the line spacing for the font. * * @param font An FTGLfont* object. * @return Line height */ FTGL_EXPORT float ftglGetFontLineHeight(FTGLfont* font); /** * Get the bounding box for a string. * * @param font An FTGLfont* object. * @param string A char buffer * @param len The length of the string. If < 0 then all characters will be * checked until a null character is encountered (optional). * @param bounds An array of 6 float values where the bounding box's lower * left near and upper right far 3D coordinates will be stored. */ FTGL_EXPORT void ftglGetFontBBox(FTGLfont* font, const char *string, int len, float bounds[6]); /** * Get the advance width for a string. * * @param font An FTGLfont* object. * @param string A char string. * @return Advance width */ FTGL_EXPORT float ftglGetFontAdvance(FTGLfont* font, const char *string); /** * Render a string of characters. * * @param font An FTGLfont* object. * @param string Char string to be output. * @param mode Render mode to display. */ FTGL_EXPORT void ftglRenderFont(FTGLfont* font, const char *string, int mode); /** * Query a font for errors. * * @param font An FTGLfont* object. * @return The current error code. */ FTGL_EXPORT FT_Error ftglGetFontError(FTGLfont* font); FTGL_END_C_DECLS #endif // __FTFont__ workbench-1.1.1/src/FtglFont/FTGL/FTGLBitmapFont.h000066400000000000000000000060631255417355300214520ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * Copyright (c) 2008 Sean Morrison * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __ftgl__ # warning This header is deprecated. Please use from now. # include #endif #ifndef __FTBitmapFont__ #define __FTBitmapFont__ #ifdef __cplusplus /** * FTBitmapFont is a specialisation of the FTFont class for handling * Bitmap fonts * * @see FTFont */ class FTGL_EXPORT FTBitmapFont : public FTFont { public: /** * Open and read a font file. Sets Error flag. * * @param fontFilePath font file path. */ FTBitmapFont(const char* fontFilePath); /** * Open and read a font from a buffer in memory. Sets Error flag. * The buffer is owned by the client and is NOT copied by FTGL. The * pointer must be valid while using FTGL. * * @param pBufferBytes the in-memory buffer * @param bufferSizeInBytes the length of the buffer in bytes */ FTBitmapFont(const unsigned char *pBufferBytes, size_t bufferSizeInBytes); /** * Destructor */ ~FTBitmapFont(); protected: /** * Construct a glyph of the correct type. * * Clients must override the function and return their specialised * FTGlyph. * * @param slot A FreeType glyph slot. * @return An FT****Glyph or null on failure. */ virtual FTGlyph* MakeGlyph(FT_GlyphSlot slot); }; #define FTGLBitmapFont FTBitmapFont #endif //__cplusplus FTGL_BEGIN_C_DECLS /** * Create a specialised FTGLfont object for handling bitmap fonts. * * @param file The font file name. * @return An FTGLfont* object. * * @see FTGLfont */ FTGL_EXPORT FTGLfont *ftglCreateBitmapFont(const char *file); FTGL_END_C_DECLS #endif // __FTBitmapFont__ workbench-1.1.1/src/FtglFont/FTGL/FTGLExtrdFont.h000066400000000000000000000061771255417355300213320ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * Copyright (c) 2008 Sean Morrison * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __ftgl__ # warning This header is deprecated. Please use from now. # include #endif #ifndef __FTExtrudeFont__ #define __FTExtrudeFont__ #ifdef __cplusplus /** * FTExtrudeFont is a specialisation of the FTFont class for handling * extruded Polygon fonts * * @see FTFont * @see FTPolygonFont */ class FTGL_EXPORT FTExtrudeFont : public FTFont { public: /** * Open and read a font file. Sets Error flag. * * @param fontFilePath font file path. */ FTExtrudeFont(const char* fontFilePath); /** * Open and read a font from a buffer in memory. Sets Error flag. * The buffer is owned by the client and is NOT copied by FTGL. The * pointer must be valid while using FTGL. * * @param pBufferBytes the in-memory buffer * @param bufferSizeInBytes the length of the buffer in bytes */ FTExtrudeFont(const unsigned char *pBufferBytes, size_t bufferSizeInBytes); /** * Destructor */ ~FTExtrudeFont(); protected: /** * Construct a glyph of the correct type. * * Clients must override the function and return their specialised * FTGlyph. * * @param slot A FreeType glyph slot. * @return An FT****Glyph or null on failure. */ virtual FTGlyph* MakeGlyph(FT_GlyphSlot slot); }; #define FTGLExtrdFont FTExtrudeFont #endif //__cplusplus FTGL_BEGIN_C_DECLS /** * Create a specialised FTGLfont object for handling extruded poygon fonts. * * @param file The font file name. * @return An FTGLfont* object. * * @see FTGLfont * @see ftglCreatePolygonFont */ FTGL_EXPORT FTGLfont *ftglCreateExtrudeFont(const char *file); FTGL_END_C_DECLS #endif // __FTExtrudeFont__ workbench-1.1.1/src/FtglFont/FTGL/FTGLGlyph.h000066400000000000000000000141411255417355300204660ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * Copyright (c) 2008 Sean Morrison * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __ftgl__ # warning This header is deprecated. Please use from now. # include #endif #ifndef __FTGlyph__ #define __FTGlyph__ #ifdef __cplusplus class FTGlyphImpl; /** * FTGlyph is the base class for FTGL glyphs. * * It provides the interface between Freetype glyphs and their openGL * renderable counterparts. This is an abstract class and derived classes * must implement the Render function. * * @see FTBBox * @see FTPoint */ class FTGL_EXPORT FTGlyph { protected: /** * Create a glyph. * * @param glyph The Freetype glyph to be processed */ FTGlyph(FT_GlyphSlot glyph); private: /** * Internal FTGL FTGlyph constructor. For private use only. * * @param pImpl Internal implementation object. Will be destroyed * upon FTGlyph deletion. */ FTGlyph(FTGlyphImpl *pImpl); /* Allow our internal subclasses to access the private constructor */ friend class FTBitmapGlyph; friend class FTBufferGlyph; friend class FTExtrudeGlyph; friend class FTOutlineGlyph; friend class FTPixmapGlyph; friend class FTPolygonGlyph; friend class FTTextureGlyph; public: /** * Destructor */ virtual ~FTGlyph(); /** * Renders this glyph at the current pen position. * * @param pen The current pen position. * @param renderMode Render mode to display * @return The advance distance for this glyph. */ virtual const FTPoint& Render(const FTPoint& pen, int renderMode) = 0; /** * Return the advance width for this glyph. * * @return advance width. */ virtual float Advance() const; /** * Return the bounding box for this glyph. * * @return bounding box. */ virtual const FTBBox& BBox() const; /** * Queries for errors. * * @return The current error code. */ virtual FT_Error Error() const; private: /** * Internal FTGL FTGlyph implementation object. For private use only. */ FTGlyphImpl *impl; }; #endif //__cplusplus FTGL_BEGIN_C_DECLS /** * FTGLglyph is the base class for FTGL glyphs. * * It provides the interface between Freetype glyphs and their openGL * renderable counterparts. This is an abstract class and derived classes * must implement the ftglRenderGlyph() function. */ struct _FTGLGlyph; typedef struct _FTGLglyph FTGLglyph; /** * Create a custom FTGL glyph object. * FIXME: maybe get rid of "base" and have advanceCallback etc. functions * * @param base The base FTGLglyph* to subclass. * @param data A pointer to private data that will be passed to callbacks. * @param renderCallback A rendering callback function. * @param destroyCallback A callback function to be called upon destruction. * @return An FTGLglyph* object. */ FTGL_EXPORT FTGLglyph *ftglCreateCustomGlyph(FTGLglyph *base, void *data, void (*renderCallback) (FTGLglyph *, void *, FTGL_DOUBLE, FTGL_DOUBLE, int, FTGL_DOUBLE *, FTGL_DOUBLE *), void (*destroyCallback) (FTGLglyph *, void *)); /** * Destroy an FTGL glyph object. * * @param glyph An FTGLglyph* object. */ FTGL_EXPORT void ftglDestroyGlyph(FTGLglyph *glyph); /** * Render a glyph at the current pen position and compute the corresponding * advance. * * @param glyph An FTGLglyph* object. * @param penx The current pen's X position. * @param peny The current pen's Y position. * @param renderMode Render mode to display * @param advancex A pointer to an FTGL_DOUBLE where to write the advance's X * component. * @param advancey A pointer to an FTGL_DOUBLE where to write the advance's Y * component. */ FTGL_EXPORT void ftglRenderGlyph(FTGLglyph *glyph, FTGL_DOUBLE penx, FTGL_DOUBLE peny, int renderMode, FTGL_DOUBLE *advancex, FTGL_DOUBLE *advancey); /** * Return the advance for a glyph. * * @param glyph An FTGLglyph* object. * @return The advance's X component. */ FTGL_EXPORT float ftglGetGlyphAdvance(FTGLglyph *glyph); /** * Return the bounding box for a glyph. * * @param glyph An FTGLglyph* object. * @param bounds An array of 6 float values where the bounding box's lower * left near and upper right far 3D coordinates will be stored. */ FTGL_EXPORT void ftglGetGlyphBBox(FTGLglyph *glyph, float bounds[6]); /** * Query a glyph for errors. * * @param glyph An FTGLglyph* object. * @return The current error code. */ FTGL_EXPORT FT_Error ftglGetGlyphError(FTGLglyph* glyph); FTGL_END_C_DECLS #endif // __FTGlyph__ workbench-1.1.1/src/FtglFont/FTGL/FTGLOutlineFont.h000066400000000000000000000061151255417355300216530ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * Copyright (c) 2008 Sean Morrison * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __ftgl__ # warning This header is deprecated. Please use from now. # include #endif #ifndef __FTOutlineFont__ #define __FTOutlineFont__ #ifdef __cplusplus /** * FTOutlineFont is a specialisation of the FTFont class for handling * Vector Outline fonts * * @see FTFont */ class FTGL_EXPORT FTOutlineFont : public FTFont { public: /** * Open and read a font file. Sets Error flag. * * @param fontFilePath font file path. */ FTOutlineFont(const char* fontFilePath); /** * Open and read a font from a buffer in memory. Sets Error flag. * The buffer is owned by the client and is NOT copied by FTGL. The * pointer must be valid while using FTGL. * * @param pBufferBytes the in-memory buffer * @param bufferSizeInBytes the length of the buffer in bytes */ FTOutlineFont(const unsigned char *pBufferBytes, size_t bufferSizeInBytes); /** * Destructor */ ~FTOutlineFont(); protected: /** * Construct a glyph of the correct type. * * Clients must override the function and return their specialised * FTGlyph. * * @param slot A FreeType glyph slot. * @return An FT****Glyph or null on failure. */ virtual FTGlyph* MakeGlyph(FT_GlyphSlot slot); }; #define FTGLOutlineFont FTOutlineFont #endif //__cplusplus FTGL_BEGIN_C_DECLS /** * Create a specialised FTGLfont object for handling vector outline fonts. * * @param file The font file name. * @return An FTGLfont* object. * * @see FTGLfont */ FTGL_EXPORT FTGLfont *ftglCreateOutlineFont(const char *file); FTGL_END_C_DECLS #endif // __FTOutlineFont__ workbench-1.1.1/src/FtglFont/FTGL/FTGLPixmapFont.h000066400000000000000000000061161255417355300214730ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * Copyright (c) 2008 Sean Morrison * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __ftgl__ # warning This header is deprecated. Please use from now. # include #endif #ifndef __FTPixmapFont__ #define __FTPixmapFont__ #ifdef __cplusplus /** * FTPixmapFont is a specialisation of the FTFont class for handling * Pixmap (Grey Scale) fonts * * @see FTFont */ class FTGL_EXPORT FTPixmapFont : public FTFont { public: /** * Open and read a font file. Sets Error flag. * * @param fontFilePath font file path. */ FTPixmapFont(const char* fontFilePath); /** * Open and read a font from a buffer in memory. Sets Error flag. * The buffer is owned by the client and is NOT copied by FTGL. The * pointer must be valid while using FTGL. * * @param pBufferBytes the in-memory buffer * @param bufferSizeInBytes the length of the buffer in bytes */ FTPixmapFont(const unsigned char *pBufferBytes, size_t bufferSizeInBytes); /** * Destructor */ ~FTPixmapFont(); protected: /** * Construct a glyph of the correct type. * * Clients must override the function and return their specialised * FTGlyph. * * @param slot A FreeType glyph slot. * @return An FT****Glyph or null on failure. */ virtual FTGlyph* MakeGlyph(FT_GlyphSlot slot); }; #define FTGLPixmapFont FTPixmapFont #endif // __cplusplus FTGL_BEGIN_C_DECLS /** * Create a specialised FTGLfont object for handling pixmap (grey scale) fonts. * * @param file The font file name. * @return An FTGLfont* object. * * @see FTGLfont */ FTGL_EXPORT FTGLfont *ftglCreatePixmapFont(const char *file); FTGL_END_C_DECLS #endif // __FTPixmapFont__ workbench-1.1.1/src/FtglFont/FTGL/FTGLPolygonFont.h000066400000000000000000000061441255417355300216650ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * Copyright (c) 2008 Sean Morrison * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __ftgl__ # warning This header is deprecated. Please use from now. # include #endif #ifndef __FTPolygonFont__ #define __FTPolygonFont__ #ifdef __cplusplus /** * FTPolygonFont is a specialisation of the FTFont class for handling * tesselated Polygon Mesh fonts * * @see FTFont */ class FTGL_EXPORT FTPolygonFont : public FTFont { public: /** * Open and read a font file. Sets Error flag. * * @param fontFilePath font file path. */ FTPolygonFont(const char* fontFilePath); /** * Open and read a font from a buffer in memory. Sets Error flag. * The buffer is owned by the client and is NOT copied by FTGL. The * pointer must be valid while using FTGL. * * @param pBufferBytes the in-memory buffer * @param bufferSizeInBytes the length of the buffer in bytes */ FTPolygonFont(const unsigned char *pBufferBytes, size_t bufferSizeInBytes); /** * Destructor */ ~FTPolygonFont(); protected: /** * Construct a glyph of the correct type. * * Clients must override the function and return their specialised * FTGlyph. * * @param slot A FreeType glyph slot. * @return An FT****Glyph or null on failure. */ virtual FTGlyph* MakeGlyph(FT_GlyphSlot slot); }; #define FTGLPolygonFont FTPolygonFont #endif //__cplusplus FTGL_BEGIN_C_DECLS /** * Create a specialised FTGLfont object for handling tesselated polygon * mesh fonts. * * @param file The font file name. * @return An FTGLfont* object. * * @see FTGLfont */ FTGL_EXPORT FTGLfont *ftglCreatePolygonFont(const char *file); FTGL_END_C_DECLS #endif // __FTPolygonFont__ workbench-1.1.1/src/FtglFont/FTGL/FTGLTextureFont.h000066400000000000000000000061261255417355300216760ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * Copyright (c) 2008 Sean Morrison * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __ftgl__ # warning This header is deprecated. Please use from now. # include #endif #ifndef __FTTextureFont__ #define __FTTextureFont__ #ifdef __cplusplus /** * FTTextureFont is a specialisation of the FTFont class for handling * Texture mapped fonts * * @see FTFont */ class FTGL_EXPORT FTTextureFont : public FTFont { public: /** * Open and read a font file. Sets Error flag. * * @param fontFilePath font file path. */ FTTextureFont(const char* fontFilePath); /** * Open and read a font from a buffer in memory. Sets Error flag. * The buffer is owned by the client and is NOT copied by FTGL. The * pointer must be valid while using FTGL. * * @param pBufferBytes the in-memory buffer * @param bufferSizeInBytes the length of the buffer in bytes */ FTTextureFont(const unsigned char *pBufferBytes, size_t bufferSizeInBytes); /** * Destructor */ virtual ~FTTextureFont(); protected: /** * Construct a glyph of the correct type. * * Clients must override the function and return their specialised * FTGlyph. * * @param slot A FreeType glyph slot. * @return An FT****Glyph or null on failure. */ virtual FTGlyph* MakeGlyph(FT_GlyphSlot slot); }; #define FTGLTextureFont FTTextureFont #endif //__cplusplus FTGL_BEGIN_C_DECLS /** * Create a specialised FTGLfont object for handling texture-mapped fonts. * * @param file The font file name. * @return An FTGLfont* object. * * @see FTGLfont */ FTGL_EXPORT FTGLfont *ftglCreateTextureFont(const char *file); FTGL_END_C_DECLS #endif // __FTTextureFont__ workbench-1.1.1/src/FtglFont/FTGL/FTLayout.h000066400000000000000000000145451255417355300204450ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * Copyright (c) 2008 Sean Morrison * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __ftgl__ # warning This header is deprecated. Please use from now. # include #endif #ifndef __FTLayout__ #define __FTLayout__ #ifdef __cplusplus class FTLayoutImpl; /** * FTLayout is the interface for layout managers that render text. * * Specific layout manager classes are derived from this class. This class * is abstract and deriving classes must implement the protected * Render methods to render formatted text and * BBox methods to determine the bounding box of output text. * * @see FTFont * @see FTBBox */ class FTGL_EXPORT FTLayout { protected: FTLayout(); private: /** * Internal FTGL FTLayout constructor. For private use only. * * @param pImpl Internal implementation object. Will be destroyed * upon FTLayout deletion. */ FTLayout(FTLayoutImpl *pImpl); /* Allow our internal subclasses to access the private constructor */ friend class FTSimpleLayout; public: /** * Destructor */ virtual ~FTLayout(); /** * Get the bounding box for a formatted string. * * @param string A char string. * @param len The length of the string. If < 0 then all characters * will be checked until a null character is encountered * (optional). * @param position The pen position of the first character (optional). * @return The corresponding bounding box. */ virtual FTBBox BBox(const char* string, const int len = -1, FTPoint position = FTPoint()) = 0; /** * Get the bounding box for a formatted string. * * @param string A wchar_t string. * @param len The length of the string. If < 0 then all characters * will be checked until a null character is encountered * (optional). * @param position The pen position of the first character (optional). * @return The corresponding bounding box. */ virtual FTBBox BBox(const wchar_t* string, const int len = -1, FTPoint position = FTPoint()) = 0; /** * Render a string of characters. * * @param string 'C' style string to be output. * @param len The length of the string. If < 0 then all characters * will be displayed until a null character is encountered * (optional). * @param position The pen position of the first character (optional). * @param renderMode Render mode to display (optional) */ virtual void Render(const char *string, const int len = -1, FTPoint position = FTPoint(), int renderMode = FTGL::RENDER_ALL) = 0; /** * Render a string of characters. * * @param string wchar_t string to be output. * @param len The length of the string. If < 0 then all characters * will be displayed until a null character is encountered * (optional). * @param position The pen position of the first character (optional). * @param renderMode Render mode to display (optional) */ virtual void Render(const wchar_t *string, const int len = -1, FTPoint position = FTPoint(), int renderMode = FTGL::RENDER_ALL) = 0; /** * Queries the Layout for errors. * * @return The current error code. */ virtual FT_Error Error() const; private: /** * Internal FTGL FTLayout implementation object. For private use only. */ FTLayoutImpl *impl; }; #endif //__cplusplus FTGL_BEGIN_C_DECLS /** * FTGLlayout is the interface for layout managers that render text. */ struct _FTGLlayout; typedef struct _FTGLlayout FTGLlayout; /** * Destroy an FTGL layout object. * * @param layout An FTGLlayout* object. */ FTGL_EXPORT void ftglDestroyLayout(FTGLlayout* layout); /** * Get the bounding box for a string. * * @param layout An FTGLlayout* object. * @param string A char buffer * @param bounds An array of 6 float values where the bounding box's lower * left near and upper right far 3D coordinates will be stored. */ FTGL_EXPORT void ftglGetLayoutBBox(FTGLlayout *layout, const char* string, float bounds[6]); /** * Render a string of characters. * * @param layout An FTGLlayout* object. * @param string Char string to be output. * @param mode Render mode to display. */ FTGL_EXPORT void ftglRenderLayout(FTGLlayout *layout, const char *string, int mode); /** * Query a layout for errors. * * @param layout An FTGLlayout* object. * @return The current error code. */ FTGL_EXPORT FT_Error ftglGetLayoutError(FTGLlayout* layout); FTGL_END_C_DECLS #endif /* __FTLayout__ */ workbench-1.1.1/src/FtglFont/FTGL/FTOutlineGlyph.h000066400000000000000000000063511255417355300216070ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * Copyright (c) 2008 Sean Morrison * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __ftgl__ # warning This header is deprecated. Please use from now. # include #endif #ifndef __FTOutlineGlyph__ #define __FTOutlineGlyph__ #ifdef __cplusplus /** * FTOutlineGlyph is a specialisation of FTGlyph for creating outlines. */ class FTGL_EXPORT FTOutlineGlyph : public FTGlyph { public: /** * Constructor. Sets the Error to Invalid_Outline if the glyphs isn't * an outline. * * @param glyph The Freetype glyph to be processed * @param outset outset distance * @param useDisplayList Enable or disable the use of Display Lists * for this glyph * true turns ON display lists. * false turns OFF display lists. */ FTOutlineGlyph(FT_GlyphSlot glyph, float outset, bool useDisplayList); /** * Destructor */ virtual ~FTOutlineGlyph(); /** * Render this glyph at the current pen position. * * @param pen The current pen position. * @param renderMode Render mode to display * @return The advance distance for this glyph. */ virtual const FTPoint& Render(const FTPoint& pen, int renderMode); }; #endif //__cplusplus FTGL_BEGIN_C_DECLS /** * Create a specialisation of FTGLglyph for creating outlines. * * @param glyph The Freetype glyph to be processed * @param outset outset contour size * @param useDisplayList Enable or disable the use of Display Lists * for this glyph * true turns ON display lists. * false turns OFF display lists. * @return An FTGLglyph* object. */ FTGL_EXPORT FTGLglyph *ftglCreateOutlineGlyph(FT_GlyphSlot glyph, float outset, int useDisplayList); FTGL_END_C_DECLS #endif // __FTOutlineGlyph__ workbench-1.1.1/src/FtglFont/FTGL/FTPixmapGlyph.h000066400000000000000000000047261255417355300214320ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * Copyright (c) 2008 Sean Morrison * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __ftgl__ # warning This header is deprecated. Please use from now. # include #endif #ifndef __FTPixmapGlyph__ #define __FTPixmapGlyph__ #ifdef __cplusplus /** * FTPixmapGlyph is a specialisation of FTGlyph for creating pixmaps. */ class FTGL_EXPORT FTPixmapGlyph : public FTGlyph { public: /** * Constructor * * @param glyph The Freetype glyph to be processed */ FTPixmapGlyph(FT_GlyphSlot glyph); /** * Destructor */ virtual ~FTPixmapGlyph(); /** * Render this glyph at the current pen position. * * @param pen The current pen position. * @param renderMode Render mode to display * @return The advance distance for this glyph. */ virtual const FTPoint& Render(const FTPoint& pen, int renderMode); }; #endif //__cplusplus FTGL_BEGIN_C_DECLS /** * Create a specialisation of FTGLglyph for creating pixmaps. * * @param glyph The Freetype glyph to be processed * @return An FTGLglyph* object. */ FTGL_EXPORT FTGLglyph *ftglCreatePixmapGlyph(FT_GlyphSlot glyph); FTGL_END_C_DECLS #endif // __FTPixmapGlyph__ workbench-1.1.1/src/FtglFont/FTGL/FTPoint.h000066400000000000000000000170241255417355300202540ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * Copyright (c) 2008 Sean Morrison * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __ftgl__ # warning This header is deprecated. Please use from now. # include #endif #ifndef __FTPoint__ #define __FTPoint__ #ifdef __cplusplus /** * FTPoint class is a basic 3-dimensional point or vector. */ class FTGL_EXPORT FTPoint { public: /** * Default constructor. Point is set to zero. */ inline FTPoint() { values[0] = 0; values[1] = 0; values[2] = 0; } /** * Constructor. Z coordinate is set to zero if unspecified. * * @param x First component * @param y Second component * @param z Third component */ inline FTPoint(const FTGL_DOUBLE x, const FTGL_DOUBLE y, const FTGL_DOUBLE z = 0) { values[0] = x; values[1] = y; values[2] = z; } /** * Constructor. This converts an FT_Vector to an FTPoint * * @param ft_vector A freetype vector */ inline FTPoint(const FT_Vector& ft_vector) { values[0] = ft_vector.x; values[1] = ft_vector.y; values[2] = 0; } /** * Normalise a point's coordinates. If the coordinates are zero, * the point is left untouched. * * @return A vector of norm one. */ FTPoint Normalise(); /** * Operator += In Place Addition. * * @param point * @return this plus point. */ inline FTPoint& operator += (const FTPoint& point) { values[0] += point.values[0]; values[1] += point.values[1]; values[2] += point.values[2]; return *this; } /** * Operator + * * @param point * @return this plus point. */ inline FTPoint operator + (const FTPoint& point) const { FTPoint temp; temp.values[0] = values[0] + point.values[0]; temp.values[1] = values[1] + point.values[1]; temp.values[2] = values[2] + point.values[2]; return temp; } /** * Operator -= In Place Substraction. * * @param point * @return this minus point. */ inline FTPoint& operator -= (const FTPoint& point) { values[0] -= point.values[0]; values[1] -= point.values[1]; values[2] -= point.values[2]; return *this; } /** * Operator - * * @param point * @return this minus point. */ inline FTPoint operator - (const FTPoint& point) const { FTPoint temp; temp.values[0] = values[0] - point.values[0]; temp.values[1] = values[1] - point.values[1]; temp.values[2] = values[2] - point.values[2]; return temp; } /** * Operator * Scalar multiplication * * @param multiplier * @return this multiplied by multiplier. */ inline FTPoint operator * (double multiplier) const { FTPoint temp; temp.values[0] = values[0] * multiplier; temp.values[1] = values[1] * multiplier; temp.values[2] = values[2] * multiplier; return temp; } /** * Operator * Scalar multiplication * * @param point * @param multiplier * @return multiplier multiplied by point. */ inline friend FTPoint operator * (double multiplier, FTPoint& point) { return point * multiplier; } /** * Operator * Scalar product * * @param a First vector. * @param b Second vector. * @return a.b scalar product. */ inline friend double operator * (FTPoint &a, FTPoint& b) { return a.values[0] * b.values[0] + a.values[1] * b.values[1] + a.values[2] * b.values[2]; } /** * Operator ^ Vector product * * @param point Second point * @return this vector point. */ inline FTPoint operator ^ (const FTPoint& point) { FTPoint temp; temp.values[0] = values[1] * point.values[2] - values[2] * point.values[1]; temp.values[1] = values[2] * point.values[0] - values[0] * point.values[2]; temp.values[2] = values[0] * point.values[1] - values[1] * point.values[0]; return temp; } /** * Operator == Tests for equality * * @param a * @param b * @return true if a & b are equal */ friend bool operator == (const FTPoint &a, const FTPoint &b); /** * Operator != Tests for non equality * * @param a * @param b * @return true if a & b are not equal */ friend bool operator != (const FTPoint &a, const FTPoint &b); /** * Cast to FTGL_DOUBLE* */ inline operator const FTGL_DOUBLE*() const { return values; } /** * Setters */ inline void X(FTGL_DOUBLE x) { values[0] = x; }; inline void Y(FTGL_DOUBLE y) { values[1] = y; }; inline void Z(FTGL_DOUBLE z) { values[2] = z; }; /** * Getters */ inline FTGL_DOUBLE X() const { return values[0]; }; inline FTGL_DOUBLE Y() const { return values[1]; }; inline FTGL_DOUBLE Z() const { return values[2]; }; inline FTGL_FLOAT Xf() const { return static_cast(values[0]); }; inline FTGL_FLOAT Yf() const { return static_cast(values[1]); }; inline FTGL_FLOAT Zf() const { return static_cast(values[2]); }; private: /** * The point data */ FTGL_DOUBLE values[3]; }; #endif //__cplusplus #endif // __FTPoint__ workbench-1.1.1/src/FtglFont/FTGL/FTPolyGlyph.h000066400000000000000000000065111255417355300211110ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * Copyright (c) 2008 Sean Morrison * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __ftgl__ # warning This header is deprecated. Please use from now. # include #endif #ifndef __FTPolygonGlyph__ #define __FTPolygonGlyph__ #ifdef __cplusplus /** * FTPolygonGlyph is a specialisation of FTGlyph for creating tessellated * polygon glyphs. */ class FTGL_EXPORT FTPolygonGlyph : public FTGlyph { public: /** * Constructor. Sets the Error to Invalid_Outline if the glyphs * isn't an outline. * * @param glyph The Freetype glyph to be processed * @param outset The outset distance * @param useDisplayList Enable or disable the use of Display Lists * for this glyph * true turns ON display lists. * false turns OFF display lists. */ FTPolygonGlyph(FT_GlyphSlot glyph, float outset, bool useDisplayList); /** * Destructor */ virtual ~FTPolygonGlyph(); /** * Render this glyph at the current pen position. * * @param pen The current pen position. * @param renderMode Render mode to display * @return The advance distance for this glyph. */ virtual const FTPoint& Render(const FTPoint& pen, int renderMode); }; #define FTPolyGlyph FTPolygonGlyph #endif //__cplusplus FTGL_BEGIN_C_DECLS /** * Create a specialisation of FTGLglyph for creating tessellated * polygon glyphs. * * @param glyph The Freetype glyph to be processed * @param outset outset contour size * @param useDisplayList Enable or disable the use of Display Lists * for this glyph * true turns ON display lists. * false turns OFF display lists. * @return An FTGLglyph* object. */ FTGL_EXPORT FTGLglyph *ftglCreatePolygonGlyph(FT_GlyphSlot glyph, float outset, int useDisplayList); FTGL_END_C_DECLS #endif // __FTPolygonGlyph__ workbench-1.1.1/src/FtglFont/FTGL/FTSimpleLayout.h000066400000000000000000000147201255417355300216120ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * Copyright (c) 2008 Sean Morrison * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __ftgl__ # warning This header is deprecated. Please use from now. # include #endif #ifndef __FTSimpleLayout__ #define __FTSimpleLayout__ #ifdef __cplusplus class FTFont; /** * FTSimpleLayout is a specialisation of FTLayout for simple text boxes. * * This class has basic support for text wrapping, left, right and centered * alignment, and text justification. * * @see FTLayout */ class FTGL_EXPORT FTSimpleLayout : public FTLayout { public: /** * Initializes line spacing to 1.0, alignment to * ALIGN_LEFT and wrap to 100.0 */ FTSimpleLayout(); /** * Destructor */ ~FTSimpleLayout(); /** * Get the bounding box for a formatted string. * * @param string A char string. * @param len The length of the string. If < 0 then all characters * will be checked until a null character is encountered * (optional). * @param position The pen position of the first character (optional). * @return The corresponding bounding box. */ virtual FTBBox BBox(const char* string, const int len = -1, FTPoint position = FTPoint()); /** * Get the bounding box for a formatted string. * * @param string A wchar_t string. * @param len The length of the string. If < 0 then all characters * will be checked until a null character is encountered * (optional). * @param position The pen position of the first character (optional). * @return The corresponding bounding box. */ virtual FTBBox BBox(const wchar_t* string, const int len = -1, FTPoint position = FTPoint()); /** * Render a string of characters. * * @param string 'C' style string to be output. * @param len The length of the string. If < 0 then all characters * will be displayed until a null character is encountered * (optional). * @param position The pen position of the first character (optional). * @param renderMode Render mode to display (optional) */ virtual void Render(const char *string, const int len = -1, FTPoint position = FTPoint(), int renderMode = FTGL::RENDER_ALL); /** * Render a string of characters. * * @param string wchar_t string to be output. * @param len The length of the string. If < 0 then all characters * will be displayed until a null character is encountered * (optional). * @param position The pen position of the first character (optional). * @param renderMode Render mode to display (optional) */ virtual void Render(const wchar_t *string, const int len = -1, FTPoint position = FTPoint(), int renderMode = FTGL::RENDER_ALL); /** * Set the font to use for rendering the text. * * @param fontInit A pointer to the new font. The font is * referenced by this but will not be * disposed of when this is deleted. */ void SetFont(FTFont *fontInit); /** * @return The current font. */ FTFont *GetFont(); /** * The maximum line length for formatting text. * * @param LineLength The new line length. */ void SetLineLength(const float LineLength); /** * @return The current line length. */ float GetLineLength() const; /** * The text alignment mode used to distribute * space within a line or rendered text. * * @param Alignment The new alignment mode. */ void SetAlignment(const FTGL::TextAlignment Alignment); /** * @return The text alignment mode. */ FTGL::TextAlignment GetAlignment() const; /** * Sets the line height. * * @param LineSpacing The height of each line of text expressed as * a percentage of the current fonts line height. */ void SetLineSpacing(const float LineSpacing); /** * @return The line spacing. */ float GetLineSpacing() const; }; #endif //__cplusplus FTGL_BEGIN_C_DECLS FTGL_EXPORT FTGLlayout *ftglCreateSimpleLayout(void); FTGL_EXPORT void ftglSetLayoutFont(FTGLlayout *, FTGLfont*); FTGL_EXPORT FTGLfont *ftglGetLayoutFont(FTGLlayout *); FTGL_EXPORT void ftglSetLayoutLineLength(FTGLlayout *, const float); FTGL_EXPORT float ftglGetLayoutLineLength(FTGLlayout *); FTGL_EXPORT void ftglSetLayoutAlignment(FTGLlayout *, const int); FTGL_EXPORT int ftglGetLayoutAlignement(FTGLlayout *); FTGL_EXPORT void ftglSetLayoutLineSpacing(FTGLlayout *, const float); FTGL_EXPORT float ftglGetLayoutLineSpacing(FTGLlayout *); FTGL_END_C_DECLS #endif /* __FTSimpleLayout__ */ workbench-1.1.1/src/FtglFont/FTGL/FTTextureGlyph.h000066400000000000000000000067771255417355300216440ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * Copyright (c) 2008 Sean Morrison * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __ftgl__ # warning This header is deprecated. Please use from now. # include #endif #ifndef __FTTextureGlyph__ #define __FTTextureGlyph__ #ifdef __cplusplus /** * FTTextureGlyph is a specialisation of FTGlyph for creating texture * glyphs. */ class FTGL_EXPORT FTTextureGlyph : public FTGlyph { public: /** * Constructor * * @param glyph The Freetype glyph to be processed * @param id The id of the texture that this glyph will be * drawn in * @param xOffset The x offset into the parent texture to draw * this glyph * @param yOffset The y offset into the parent texture to draw * this glyph * @param width The width of the parent texture * @param height The height (number of rows) of the parent texture */ FTTextureGlyph(FT_GlyphSlot glyph, int id, int xOffset, int yOffset, int width, int height); /** * Destructor */ virtual ~FTTextureGlyph(); /** * Render this glyph at the current pen position. * * @param pen The current pen position. * @param renderMode Render mode to display * @return The advance distance for this glyph. */ virtual const FTPoint& Render(const FTPoint& pen, int renderMode); }; #endif //__cplusplus FTGL_BEGIN_C_DECLS /** * Create a specialisation of FTGLglyph for creating pixmaps. * * @param glyph The Freetype glyph to be processed. * @param id The id of the texture that this glyph will be drawn in. * @param xOffset The x offset into the parent texture to draw this glyph. * @param yOffset The y offset into the parent texture to draw this glyph. * @param width The width of the parent texture. * @param height The height (number of rows) of the parent texture. * @return An FTGLglyph* object. */ FTGL_EXPORT FTGLglyph *ftglCreateTextureGlyph(FT_GlyphSlot glyph, int id, int xOffset, int yOffset, int width, int height); FTGL_END_C_DECLS #endif // __FTTextureGlyph__ workbench-1.1.1/src/FtglFont/FTGL/ftgl.h000066400000000000000000000102011255417355300176530ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * Copyright (c) 2008 Sean Morrison * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __ftgl__ #define __ftgl__ /* We need the Freetype headers */ #include #include FT_FREETYPE_H #include FT_GLYPH_H #include FT_OUTLINE_H /* Floating point types used by the library */ typedef double FTGL_DOUBLE; typedef float FTGL_FLOAT; /* Macros used to declare C-linkage types and symbols */ #ifdef __cplusplus # define FTGL_BEGIN_C_DECLS extern "C" { namespace FTGL { # define FTGL_END_C_DECLS } } #else # define FTGL_BEGIN_C_DECLS # define FTGL_END_C_DECLS #endif #ifdef __cplusplus namespace FTGL { typedef enum { RENDER_FRONT = 0x0001, RENDER_BACK = 0x0002, RENDER_SIDE = 0x0004, RENDER_ALL = 0xffff } RenderMode; typedef enum { ALIGN_LEFT = 0, ALIGN_CENTER = 1, ALIGN_RIGHT = 2, ALIGN_JUSTIFY = 3 } TextAlignment; } #else # define FTGL_RENDER_FRONT 0x0001 # define FTGL_RENDER_BACK 0x0002 # define FTGL_RENDER_SIDE 0x0004 # define FTGL_RENDER_ALL 0xffff # define FTGL_ALIGN_LEFT 0 # define FTGL_ALIGN_CENTER 1 # define FTGL_ALIGN_RIGHT 2 # define FTGL_ALIGN_JUSTIFY 3 #endif // Compiler-specific conditional compilation #ifdef _MSC_VER // MS Visual C++ // Disable various warning. // 4786: template name too long #pragma warning(disable : 4251) #pragma warning(disable : 4275) #pragma warning(disable : 4786) // The following definitions control how symbols are exported. // If the target is a static library ensure that FTGL_LIBRARY_STATIC // is defined. If building a dynamic library (ie DLL) ensure the // FTGL_LIBRARY macro is defined, as it will mark symbols for // export. If compiling a project to _use_ the _dynamic_ library // version of the library, no definition is required. #ifdef FTGL_LIBRARY_STATIC // static lib - no special export required # define FTGL_EXPORT #elif FTGL_LIBRARY // dynamic lib - must export/import symbols appropriately. # define FTGL_EXPORT __declspec(dllexport) #else # define FTGL_EXPORT __declspec(dllimport) #endif #else // Compiler that is not MS Visual C++. // Ensure that the export symbol is defined (and blank) #define FTGL_EXPORT #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #endif // __ftgl__ workbench-1.1.1/src/FtglFont/FTGlyph/000077500000000000000000000000001255417355300173355ustar00rootroot00000000000000workbench-1.1.1/src/FtglFont/FTGlyph/FTBitmapGlyph.cpp000066400000000000000000000065141255417355300225210ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * Copyright (c) 2014 Washington University School of Medicine * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "FtglConfig.h" #include #include "FTGL/ftgl.h" #include "FTInternals.h" #include "FTBitmapGlyphImpl.h" // // FTGLBitmapGlyph // FTBitmapGlyph::FTBitmapGlyph(FT_GlyphSlot glyph) : FTGlyph(new FTBitmapGlyphImpl(glyph)) {} FTBitmapGlyph::~FTBitmapGlyph() {} const FTPoint& FTBitmapGlyph::Render(const FTPoint& pen, int renderMode) { FTBitmapGlyphImpl *myimpl = dynamic_cast(impl); return myimpl->RenderImpl(pen, renderMode); } // // FTGLBitmapGlyphImpl // FTBitmapGlyphImpl::FTBitmapGlyphImpl(FT_GlyphSlot glyph) : FTGlyphImpl(glyph), destWidth(0), destHeight(0), data(0) { err = FT_Render_Glyph(glyph, FT_RENDER_MODE_MONO); if(err || ft_glyph_format_bitmap != glyph->format) { return; } FT_Bitmap bitmap = glyph->bitmap; unsigned int srcWidth = bitmap.width; unsigned int srcHeight = bitmap.rows; unsigned int srcPitch = bitmap.pitch; destWidth = srcWidth; destHeight = srcHeight; destPitch = srcPitch; if(destWidth && destHeight) { data = new unsigned char[destPitch * destHeight]; unsigned char* dest = data + ((destHeight - 1) * destPitch); unsigned char* src = bitmap.buffer; for(unsigned int y = 0; y < srcHeight; ++y) { memcpy(dest, src, srcPitch); dest -= destPitch; src += srcPitch; } } pos = FTPoint(glyph->bitmap_left, static_cast(srcHeight) - glyph->bitmap_top, 0.0); } FTBitmapGlyphImpl::~FTBitmapGlyphImpl() { delete [] data; } const FTPoint& FTBitmapGlyphImpl::RenderImpl(const FTPoint& pen, int /*renderMode*/) { if(data) { float dx, dy; dx = pen.Xf() + pos.Xf(); dy = pen.Yf() - pos.Yf(); glBitmap(0, 0, 0.0f, 0.0f, dx, dy, (const GLubyte*)0); glPixelStorei(GL_UNPACK_ROW_LENGTH, destPitch * 8); glBitmap(destWidth, destHeight, 0.0f, 0.0, 0.0, 0.0, (const GLubyte*)data); glBitmap(0, 0, 0.0f, 0.0f, -dx, -dy, (const GLubyte*)0); } return advance; } workbench-1.1.1/src/FtglFont/FTGlyph/FTBitmapGlyphImpl.h000066400000000000000000000041411255417355300230020ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __FTBitmapGlyphImpl__ #define __FTBitmapGlyphImpl__ #include "FTGlyphImpl.h" class FTBitmapGlyphImpl : public FTGlyphImpl { friend class FTBitmapGlyph; protected: FTBitmapGlyphImpl(FT_GlyphSlot glyph); virtual ~FTBitmapGlyphImpl(); virtual const FTPoint& RenderImpl(const FTPoint& pen, int renderMode); private: /** * The width of the glyph 'image' */ unsigned int destWidth; /** * The height of the glyph 'image' */ unsigned int destHeight; /** * The pitch of the glyph 'image' */ unsigned int destPitch; /** * Vector from the pen position to the topleft corner of the bitmap */ FTPoint pos; /** * Pointer to the 'image' data */ unsigned char* data; }; #endif // __FTBitmapGlyphImpl__ workbench-1.1.1/src/FtglFont/FTGlyph/FTBufferGlyph.cpp000066400000000000000000000063661255417355300225230ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2008 Sam Hocevar * Copyright (c) 2014 Washington University School of Medicine * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "FtglConfig.h" #include #include "FTGL/ftgl.h" #include "FTInternals.h" #include "FTBufferGlyphImpl.h" // // FTGLBufferGlyph // FTBufferGlyph::FTBufferGlyph(FT_GlyphSlot glyph, FTBuffer *buffer) : FTGlyph(new FTBufferGlyphImpl(glyph, buffer)) {} FTBufferGlyph::~FTBufferGlyph() {} const FTPoint& FTBufferGlyph::Render(const FTPoint& pen, int renderMode) { FTBufferGlyphImpl *myimpl = dynamic_cast(impl); return myimpl->RenderImpl(pen, renderMode); } // // FTGLBufferGlyphImpl // FTBufferGlyphImpl::FTBufferGlyphImpl(FT_GlyphSlot glyph, FTBuffer *p) : FTGlyphImpl(glyph), has_bitmap(false), buffer(p) { err = FT_Render_Glyph(glyph, FT_RENDER_MODE_NORMAL); if(err || glyph->format != ft_glyph_format_bitmap) { return; } bitmap = glyph->bitmap; pixels = new unsigned char[bitmap.pitch * bitmap.rows]; memcpy(pixels, bitmap.buffer, bitmap.pitch * bitmap.rows); if(bitmap.width && bitmap.rows) { has_bitmap = true; corner = FTPoint(glyph->bitmap_left, glyph->bitmap_top); } } FTBufferGlyphImpl::~FTBufferGlyphImpl() { delete[] pixels; } const FTPoint& FTBufferGlyphImpl::RenderImpl(const FTPoint& pen, int /*renderMode*/) { if(has_bitmap) { FTPoint pos(buffer->Pos() + pen + corner); int dx = (int)(pos.Xf() + 0.5f); int dy = buffer->Height() - (int)(pos.Yf() + 0.5f); unsigned char * dest = buffer->Pixels() + dx + dy * buffer->Width(); for(int y = 0; y < bitmap.rows; y++) { // FIXME: change the loop bounds instead of doing this test if(y + dy < 0 || y + dy >= buffer->Height()) continue; for(int x = 0; x < bitmap.width; x++) { if(x + dx < 0 || x + dx >= buffer->Width()) continue; unsigned char p = pixels[y * bitmap.pitch + x]; if(p) { dest[y * buffer->Width() + x] = p; } } } } return advance; } workbench-1.1.1/src/FtglFont/FTGlyph/FTBufferGlyphImpl.h000066400000000000000000000032621255417355300230020ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2008 Sam Hocevar * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __FTBufferGlyphImpl__ #define __FTBufferGlyphImpl__ #include "FTGlyphImpl.h" class FTBufferGlyphImpl : public FTGlyphImpl { friend class FTBufferGlyph; protected: FTBufferGlyphImpl(FT_GlyphSlot glyph, FTBuffer *p); virtual ~FTBufferGlyphImpl(); virtual const FTPoint& RenderImpl(const FTPoint& pen, int renderMode); private: bool has_bitmap; FT_Bitmap bitmap; unsigned char *pixels; FTPoint corner; FTBuffer *buffer; }; #endif // __FTBufferGlyphImpl__ workbench-1.1.1/src/FtglFont/FTGlyph/FTExtrudeGlyph.cpp000066400000000000000000000164201255417355300227220ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * Copyright (c) 2014 Washington University School of Medicine * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "FtglConfig.h" #include #include "FTGL/ftgl.h" #include "FTInternals.h" #include "FTExtrudeGlyphImpl.h" #include "FTVectoriser.h" // // FTGLExtrudeGlyph // FTExtrudeGlyph::FTExtrudeGlyph(FT_GlyphSlot glyph, float depth, float frontOutset, float backOutset, bool useDisplayList) : FTGlyph(new FTExtrudeGlyphImpl(glyph, depth, frontOutset, backOutset, useDisplayList)) {} FTExtrudeGlyph::~FTExtrudeGlyph() {} const FTPoint& FTExtrudeGlyph::Render(const FTPoint& pen, int renderMode) { FTExtrudeGlyphImpl *myimpl = dynamic_cast(impl); return myimpl->RenderImpl(pen, renderMode); } // // FTGLExtrudeGlyphImpl // FTExtrudeGlyphImpl::FTExtrudeGlyphImpl(FT_GlyphSlot glyph, float _depth, float _frontOutset, float _backOutset, bool useDisplayList) : FTGlyphImpl(glyph), vectoriser(0), glList(0) { bBox.SetDepth(-_depth); if(ft_glyph_format_outline != glyph->format) { err = 0x14; // Invalid_Outline return; } vectoriser = new FTVectoriser(glyph); if((vectoriser->ContourCount() < 1) || (vectoriser->PointCount() < 3)) { delete vectoriser; vectoriser = NULL; return; } hscale = glyph->face->size->metrics.x_ppem * 64; vscale = glyph->face->size->metrics.y_ppem * 64; depth = _depth; frontOutset = _frontOutset; backOutset = _backOutset; if(useDisplayList) { glList = glGenLists(3); /* Front face */ glNewList(glList + 0, GL_COMPILE); RenderFront(); glEndList(); /* Back face */ glNewList(glList + 1, GL_COMPILE); RenderBack(); glEndList(); /* Side face */ glNewList(glList + 2, GL_COMPILE); RenderSide(); glEndList(); delete vectoriser; vectoriser = NULL; } } FTExtrudeGlyphImpl::~FTExtrudeGlyphImpl() { if(glList) { glDeleteLists(glList, 3); } else if(vectoriser) { delete vectoriser; } } const FTPoint& FTExtrudeGlyphImpl::RenderImpl(const FTPoint& pen, int renderMode) { glTranslatef(pen.Xf(), pen.Yf(), pen.Zf()); if(glList) { if(renderMode & FTGL::RENDER_FRONT) glCallList(glList + 0); if(renderMode & FTGL::RENDER_BACK) glCallList(glList + 1); if(renderMode & FTGL::RENDER_SIDE) glCallList(glList + 2); } else if(vectoriser) { if(renderMode & FTGL::RENDER_FRONT) RenderFront(); if(renderMode & FTGL::RENDER_BACK) RenderBack(); if(renderMode & FTGL::RENDER_SIDE) RenderSide(); } glTranslatef(-pen.Xf(), -pen.Yf(), -pen.Zf()); return advance; } void FTExtrudeGlyphImpl::RenderFront() { vectoriser->MakeMesh(1.0, 1, frontOutset); glNormal3d(0.0, 0.0, 1.0); const FTMesh *mesh = vectoriser->GetMesh(); for(unsigned int j = 0; j < mesh->TesselationCount(); ++j) { const FTTesselation* subMesh = mesh->Tesselation(j); unsigned int polygonType = subMesh->PolygonType(); glBegin(polygonType); for(unsigned int i = 0; i < subMesh->PointCount(); ++i) { FTPoint pt = subMesh->Point(i); glTexCoord2f(pt.Xf() / hscale, pt.Yf() / vscale); glVertex3f(pt.Xf() / 64.0f, pt.Yf() / 64.0f, 0.0f); } glEnd(); } } void FTExtrudeGlyphImpl::RenderBack() { vectoriser->MakeMesh(-1.0, 2, backOutset); glNormal3d(0.0, 0.0, -1.0); const FTMesh *mesh = vectoriser->GetMesh(); for(unsigned int j = 0; j < mesh->TesselationCount(); ++j) { const FTTesselation* subMesh = mesh->Tesselation(j); unsigned int polygonType = subMesh->PolygonType(); glBegin(polygonType); for(unsigned int i = 0; i < subMesh->PointCount(); ++i) { //FTPoint pt = subMesh->Point(i); glTexCoord2f(subMesh->Point(i).Xf() / hscale, subMesh->Point(i).Yf() / vscale); glVertex3f(subMesh->Point(i).Xf() / 64.0f, subMesh->Point(i).Yf() / 64.0f, -depth); } glEnd(); } } void FTExtrudeGlyphImpl::RenderSide() { int contourFlag = vectoriser->ContourFlag(); for(size_t c = 0; c < vectoriser->ContourCount(); ++c) { const FTContour* contour = vectoriser->Contour(c); size_t n = contour->PointCount(); if(n < 2) { continue; } glBegin(GL_QUAD_STRIP); for(size_t j = 0; j <= n; ++j) { size_t cur = (j == n) ? 0 : j; size_t next = (cur == n - 1) ? 0 : cur + 1; FTPoint frontPt = contour->FrontPoint(cur); FTPoint nextPt = contour->FrontPoint(next); FTPoint backPt = contour->BackPoint(cur); FTPoint normal = FTPoint(0.f, 0.f, 1.f) ^ (frontPt - nextPt); if(normal != FTPoint(0.0f, 0.0f, 0.0f)) { glNormal3dv(static_cast(normal.Normalise())); } glTexCoord2f(frontPt.Xf() / hscale, frontPt.Yf() / vscale); if(contourFlag & ft_outline_reverse_fill) { glVertex3f(backPt.Xf() / 64.0f, backPt.Yf() / 64.0f, 0.0f); glVertex3f(frontPt.Xf() / 64.0f, frontPt.Yf() / 64.0f, -depth); } else { glVertex3f(backPt.Xf() / 64.0f, backPt.Yf() / 64.0f, -depth); glVertex3f(frontPt.Xf() / 64.0f, frontPt.Yf() / 64.0f, 0.0f); } } glEnd(); } } workbench-1.1.1/src/FtglFont/FTGlyph/FTExtrudeGlyphImpl.h000066400000000000000000000042011255417355300232030ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __FTExtrudeGlyphImpl__ #define __FTExtrudeGlyphImpl__ #include "FTGlyphImpl.h" class FTVectoriser; class FTExtrudeGlyphImpl : public FTGlyphImpl { friend class FTExtrudeGlyph; protected: FTExtrudeGlyphImpl(FT_GlyphSlot glyph, float depth, float frontOutset, float backOutset, bool useDisplayList); virtual ~FTExtrudeGlyphImpl(); virtual const FTPoint& RenderImpl(const FTPoint& pen, int renderMode); private: /** * Private rendering methods. */ void RenderFront(); void RenderBack(); void RenderSide(); /** * Private rendering variables. */ unsigned int hscale, vscale; float depth; float frontOutset, backOutset; FTVectoriser *vectoriser; /** * OpenGL display list */ GLuint glList; }; #endif // __FTExtrudeGlyphImpl__ workbench-1.1.1/src/FtglFont/FTGlyph/FTGlyph.cpp000066400000000000000000000043431255417355300213620ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * Copyright (c) 2014 Washington University School of Medicine * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "FtglConfig.h" #include "FTGL/ftgl.h" #include "FTInternals.h" #include "FTGlyphImpl.h" // // FTGlyph // FTGlyph::FTGlyph(FT_GlyphSlot glyph) { impl = new FTGlyphImpl(glyph); } FTGlyph::FTGlyph(FTGlyphImpl *pImpl) { impl = pImpl; } FTGlyph::~FTGlyph() { delete impl; } float FTGlyph::Advance() const { return impl->Advance(); } const FTBBox& FTGlyph::BBox() const { return impl->BBox(); } FT_Error FTGlyph::Error() const { return impl->Error(); } // // FTGlyphImpl // FTGlyphImpl::FTGlyphImpl(FT_GlyphSlot glyph, bool /*useList*/) : err(0) { if(glyph) { bBox = FTBBox(glyph); advance = FTPoint(glyph->advance.x / 64.0f, glyph->advance.y / 64.0f); } } FTGlyphImpl::~FTGlyphImpl() {} float FTGlyphImpl::Advance() const { return advance.Xf(); } const FTBBox& FTGlyphImpl::BBox() const { return bBox; } FT_Error FTGlyphImpl::Error() const { return err; } workbench-1.1.1/src/FtglFont/FTGlyph/FTGlyphGlue.cpp000066400000000000000000000147401255417355300222010ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "FtglConfig.h" #include "FTGL/ftgl.h" #include "FTInternals.h" static const FTPoint static_ftpoint; static const FTBBox static_ftbbox; FTGL_BEGIN_C_DECLS #define C_TOR(cname, cargs, cxxname, cxxarg, cxxtype) \ FTGLglyph* cname cargs \ { \ cxxname *g = new cxxname cxxarg; \ if(g->Error()) \ { \ delete g; \ return NULL; \ } \ FTGLglyph *ftgl = (FTGLglyph *)malloc(sizeof(FTGLglyph)); \ ftgl->ptr = g; \ ftgl->type = cxxtype; \ return ftgl; \ } // FTBitmapGlyph::FTBitmapGlyph(); C_TOR(ftglCreateBitmapGlyph, (FT_GlyphSlot glyph), FTBitmapGlyph, (glyph), GLYPH_BITMAP); // FTBufferGlyph::FTBufferGlyph(); // FIXME: not implemented // FTExtrudeGlyph::FTExtrudeGlyph(); C_TOR(ftglCreateExtrudeGlyph, (FT_GlyphSlot glyph, float depth, float frontOutset, float backOutset, int useDisplayList), FTExtrudeGlyph, (glyph, depth, frontOutset, backOutset, (useDisplayList != 0)), GLYPH_EXTRUDE); // FTOutlineGlyph::FTOutlineGlyph(); C_TOR(ftglCreateOutlineGlyph, (FT_GlyphSlot glyph, float outset, int useDisplayList), FTOutlineGlyph, (glyph, outset, (useDisplayList != 0)), GLYPH_OUTLINE); // FTPixmapGlyph::FTPixmapGlyph(); C_TOR(ftglCreatePixmapGlyph, (FT_GlyphSlot glyph), FTPixmapGlyph, (glyph), GLYPH_PIXMAP); // FTPolygonGlyph::FTPolygonGlyph(); C_TOR(ftglCreatePolygonGlyph, (FT_GlyphSlot glyph, float outset, int useDisplayList), FTPolygonGlyph, (glyph, outset, (useDisplayList != 0)), GLYPH_POLYGON); // FTTextureGlyph::FTTextureGlyph(); C_TOR(ftglCreateTextureGlyph, (FT_GlyphSlot glyph, int id, int xOffset, int yOffset, int width, int height), FTTextureGlyph, (glyph, id, xOffset, yOffset, width, height), GLYPH_TEXTURE); // FTCustomGlyph::FTCustomGlyph(); class FTCustomGlyph : public FTGlyph { public: FTCustomGlyph(FTGLglyph *base, void *p, void (*render) (FTGLglyph *, void *, FTGL_DOUBLE, FTGL_DOUBLE, int, FTGL_DOUBLE *, FTGL_DOUBLE *), void (*destroy) (FTGLglyph *, void *)) : FTGlyph((FT_GlyphSlot)0), baseGlyph(base), data(p), renderCallback(render), destroyCallback(destroy) {} ~FTCustomGlyph() { destroyCallback(baseGlyph, data); } float Advance() const { return baseGlyph->ptr->Advance(); } const FTPoint& Render(const FTPoint& pen, int renderMode) { FTGL_DOUBLE advancex, advancey; renderCallback(baseGlyph, data, pen.X(), pen.Y(), renderMode, &advancex, &advancey); advance = FTPoint(advancex, advancey); return advance; } const FTBBox& BBox() const { return baseGlyph->ptr->BBox(); } FT_Error Error() const { return baseGlyph->ptr->Error(); } private: FTPoint advance; FTGLglyph *baseGlyph; void *data; void (*renderCallback) (FTGLglyph *, void *, FTGL_DOUBLE, FTGL_DOUBLE, int, FTGL_DOUBLE *, FTGL_DOUBLE *); void (*destroyCallback) (FTGLglyph *, void *); }; C_TOR(ftglCreateCustomGlyph, (FTGLglyph *base, void *data, void (*renderCallback) (FTGLglyph *, void *, FTGL_DOUBLE, FTGL_DOUBLE, int, FTGL_DOUBLE *, FTGL_DOUBLE *), void (*destroyCallback) (FTGLglyph *, void *)), FTCustomGlyph, (base, data, renderCallback, destroyCallback), GLYPH_CUSTOM); #define C_FUN(cret, cname, cargs, cxxerr, cxxname, cxxarg) \ cret cname cargs \ { \ if(!g || !g->ptr) \ { \ fprintf(stderr, "FTGL warning: NULL pointer in %s\n", #cname); \ cxxerr; \ } \ return g->ptr->cxxname cxxarg; \ } // FTGlyph::~FTGlyph(); void ftglDestroyGlyph(FTGLglyph *g) { if(!g || !g->ptr) { fprintf(stderr, "FTGL warning: NULL pointer in %s\n", __FUNCTION__); return; } delete g->ptr; free(g); } // const FTPoint& FTGlyph::Render(const FTPoint& pen, int renderMode); extern "C++" { C_FUN(static const FTPoint&, _ftglRenderGlyph, (FTGLglyph *g, const FTPoint& pen, int renderMode), return static_ftpoint, Render, (pen, renderMode)); } void ftglRenderGlyph(FTGLglyph *g, FTGL_DOUBLE penx, FTGL_DOUBLE peny, int renderMode, FTGL_DOUBLE *advancex, FTGL_DOUBLE *advancey) { FTPoint pen(penx, peny); FTPoint ret = _ftglRenderGlyph(g, pen, renderMode); *advancex = ret.X(); *advancey = ret.Y(); } // float FTGlyph::Advance() const; C_FUN(float, ftglGetGlyphAdvance, (FTGLglyph *g), return 0.0, Advance, ()); // const FTBBox& FTGlyph::BBox() const; extern "C++" { C_FUN(static const FTBBox&, _ftglGetGlyphBBox, (FTGLglyph *g), return static_ftbbox, BBox, ()); } void ftglGetGlyphBBox(FTGLglyph *g, float bounds[6]) { FTBBox ret = _ftglGetGlyphBBox(g); FTPoint lower = ret.Lower(), upper = ret.Upper(); bounds[0] = lower.Xf(); bounds[1] = lower.Yf(); bounds[2] = lower.Zf(); bounds[3] = upper.Xf(); bounds[4] = upper.Yf(); bounds[5] = upper.Zf(); } // FT_Error FTGlyph::Error() const; C_FUN(FT_Error, ftglGetGlyphError, (FTGLglyph *g), return -1, Error, ()); FTGL_END_C_DECLS workbench-1.1.1/src/FtglFont/FTGlyph/FTGlyphImpl.h000066400000000000000000000035401255417355300216470ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __FTGlyphImpl__ #define __FTGlyphImpl__ #include "FTGL/ftgl.h" class FTGlyphImpl { friend class FTGlyph; protected: FTGlyphImpl(FT_GlyphSlot glyph, bool useDisplayList = true); virtual ~FTGlyphImpl(); float Advance() const; const FTBBox& BBox() const; FT_Error Error() const; /** * The advance distance for this glyph */ FTPoint advance; /** * The bounding box of this glyph. */ FTBBox bBox; /** * Current error code. Zero means no error. */ FT_Error err; }; #endif // __FTGlyphImpl__ workbench-1.1.1/src/FtglFont/FTGlyph/FTOutlineGlyph.cpp000066400000000000000000000074351255417355300227270ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Éric Beets * Copyright (c) 2008 Sam Hocevar * Copyright (c) 2014 Washington University School of Medicine * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "FtglConfig.h" #include "FTGL/ftgl.h" #include "FTInternals.h" #include "FTOutlineGlyphImpl.h" #include "FTVectoriser.h" // // FTGLOutlineGlyph // FTOutlineGlyph::FTOutlineGlyph(FT_GlyphSlot glyph, float outset, bool useDisplayList) : FTGlyph(new FTOutlineGlyphImpl(glyph, outset, useDisplayList)) {} FTOutlineGlyph::~FTOutlineGlyph() {} const FTPoint& FTOutlineGlyph::Render(const FTPoint& pen, int renderMode) { FTOutlineGlyphImpl *myimpl = dynamic_cast(impl); return myimpl->RenderImpl(pen, renderMode); } // // FTGLOutlineGlyphImpl // FTOutlineGlyphImpl::FTOutlineGlyphImpl(FT_GlyphSlot glyph, float _outset, bool useDisplayList) : FTGlyphImpl(glyph), glList(0) { if(ft_glyph_format_outline != glyph->format) { err = 0x14; // Invalid_Outline return; } vectoriser = new FTVectoriser(glyph); if((vectoriser->ContourCount() < 1) || (vectoriser->PointCount() < 3)) { delete vectoriser; vectoriser = NULL; return; } outset = _outset; if(useDisplayList) { glList = glGenLists(1); glNewList(glList, GL_COMPILE); DoRender(); glEndList(); delete vectoriser; vectoriser = NULL; } } FTOutlineGlyphImpl::~FTOutlineGlyphImpl() { if(glList) { glDeleteLists(glList, 1); } else if(vectoriser) { delete vectoriser; } } const FTPoint& FTOutlineGlyphImpl::RenderImpl(const FTPoint& pen, int /*renderMode*/) { glTranslatef(pen.Xf(), pen.Yf(), pen.Zf()); if(glList) { glCallList(glList); } else if(vectoriser) { DoRender(); } glTranslatef(-pen.Xf(), -pen.Yf(), -pen.Zf()); return advance; } void FTOutlineGlyphImpl::DoRender() { for(unsigned int c = 0; c < vectoriser->ContourCount(); ++c) { const FTContour* contour = vectoriser->Contour(c); glBegin(GL_LINE_LOOP); for(unsigned int i = 0; i < contour->PointCount(); ++i) { FTPoint point = FTPoint(contour->Point(i).X() + contour->Outset(i).X() * outset, contour->Point(i).Y() + contour->Outset(i).Y() * outset, 0); glVertex2f(point.Xf() / 64.0f, point.Yf() / 64.0f); } glEnd(); } } workbench-1.1.1/src/FtglFont/FTGlyph/FTOutlineGlyphImpl.h000066400000000000000000000040301255417355300232020ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __FTOutlineGlyphImpl__ #define __FTOutlineGlyphImpl__ #include "FTGlyphImpl.h" class FTVectoriser; class FTOutlineGlyphImpl : public FTGlyphImpl { friend class FTOutlineGlyph; protected: FTOutlineGlyphImpl(FT_GlyphSlot glyph, float outset, bool useDisplayList); virtual ~FTOutlineGlyphImpl(); virtual const FTPoint& RenderImpl(const FTPoint& pen, int renderMode); private: /** * Private rendering method. */ void DoRender(); /** * Private rendering variables. */ FTVectoriser *vectoriser; /** * Private rendering variables. */ float outset; /** * OpenGL display list */ GLuint glList; }; #endif // __FTOutlineGlyphImpl__ workbench-1.1.1/src/FtglFont/FTGlyph/FTPixmapGlyph.cpp000066400000000000000000000070761255417355300225470ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * Copyright (c) 2014 Washington University School of Medicine * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "FtglConfig.h" #include #include "FTGL/ftgl.h" #include "FTInternals.h" #include "FTPixmapGlyphImpl.h" // // FTGLPixmapGlyph // FTPixmapGlyph::FTPixmapGlyph(FT_GlyphSlot glyph) : FTGlyph(new FTPixmapGlyphImpl(glyph)) {} FTPixmapGlyph::~FTPixmapGlyph() {} const FTPoint& FTPixmapGlyph::Render(const FTPoint& pen, int renderMode) { FTPixmapGlyphImpl *myimpl = dynamic_cast(impl); return myimpl->RenderImpl(pen, renderMode); } // // FTGLPixmapGlyphImpl // FTPixmapGlyphImpl::FTPixmapGlyphImpl(FT_GlyphSlot glyph) : FTGlyphImpl(glyph), destWidth(0), destHeight(0), data(0) { err = FT_Render_Glyph(glyph, FT_RENDER_MODE_NORMAL); if(err || ft_glyph_format_bitmap != glyph->format) { return; } FT_Bitmap bitmap = glyph->bitmap; //check the pixel mode //ft_pixel_mode_grays int srcWidth = bitmap.width; int srcHeight = bitmap.rows; destWidth = srcWidth; destHeight = srcHeight; if(destWidth && destHeight) { data = new unsigned char[destWidth * destHeight * 2]; unsigned char* src = bitmap.buffer; unsigned char* dest = data + ((destHeight - 1) * destWidth * 2); size_t destStep = destWidth * 2 * 2; for(int y = 0; y < srcHeight; ++y) { for(int x = 0; x < srcWidth; ++x) { *dest++ = static_cast(255); *dest++ = *src++; } dest -= destStep; } destHeight = srcHeight; } pos.X(glyph->bitmap_left); pos.Y(srcHeight - glyph->bitmap_top); } FTPixmapGlyphImpl::~FTPixmapGlyphImpl() { delete [] data; } const FTPoint& FTPixmapGlyphImpl::RenderImpl(const FTPoint& pen, int /*renderMode*/) { if(data) { float dx, dy; dx = floor(pen.Xf() + pos.Xf()); dy = floor(pen.Yf() - pos.Yf()); glBitmap(0, 0, 0.0f, 0.0f, dx, dy, (const GLubyte*)0); glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); glPixelStorei(GL_UNPACK_ALIGNMENT, 2); glDrawPixels(destWidth, destHeight, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, (const GLvoid*)data); glBitmap(0, 0, 0.0f, 0.0f, -dx, -dy, (const GLubyte*)0); } return advance; } workbench-1.1.1/src/FtglFont/FTGlyph/FTPixmapGlyphImpl.h000066400000000000000000000037551255417355300230360ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __FTPixmapGlyphImpl__ #define __FTPixmapGlyphImpl__ #include "FTGlyphImpl.h" class FTPixmapGlyphImpl : public FTGlyphImpl { friend class FTPixmapGlyph; protected: FTPixmapGlyphImpl(FT_GlyphSlot glyph); virtual ~FTPixmapGlyphImpl(); virtual const FTPoint& RenderImpl(const FTPoint& pen, int renderMode); private: /** * The width of the glyph 'image' */ int destWidth; /** * The height of the glyph 'image' */ int destHeight; /** * Vector from the pen position to the topleft corner of the pixmap */ FTPoint pos; /** * Pointer to the 'image' data */ unsigned char* data; }; #endif // __FTPixmapGlyphImpl__ workbench-1.1.1/src/FtglFont/FTGlyph/FTPolygonGlyph.cpp000066400000000000000000000076531255417355300227410ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Éric Beets * Copyright (c) 2008 Sam Hocevar * Copyright (c) 2014 Washington University School of Medicine * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "FtglConfig.h" #include "FTGL/ftgl.h" #include "FTInternals.h" #include "FTPolygonGlyphImpl.h" #include "FTVectoriser.h" // // FTGLPolyGlyph // FTPolygonGlyph::FTPolygonGlyph(FT_GlyphSlot glyph, float outset, bool useDisplayList) : FTGlyph(new FTPolygonGlyphImpl(glyph, outset, useDisplayList)) {} FTPolygonGlyph::~FTPolygonGlyph() {} const FTPoint& FTPolygonGlyph::Render(const FTPoint& pen, int renderMode) { FTPolygonGlyphImpl *myimpl = dynamic_cast(impl); return myimpl->RenderImpl(pen, renderMode); } // // FTGLPolyGlyphImpl // FTPolygonGlyphImpl::FTPolygonGlyphImpl(FT_GlyphSlot glyph, float _outset, bool useDisplayList) : FTGlyphImpl(glyph), glList(0) { if(ft_glyph_format_outline != glyph->format) { err = 0x14; // Invalid_Outline return; } vectoriser = new FTVectoriser(glyph); if((vectoriser->ContourCount() < 1) || (vectoriser->PointCount() < 3)) { delete vectoriser; vectoriser = NULL; return; } hscale = glyph->face->size->metrics.x_ppem * 64; vscale = glyph->face->size->metrics.y_ppem * 64; outset = _outset; if(useDisplayList) { glList = glGenLists(1); glNewList(glList, GL_COMPILE); DoRender(); glEndList(); delete vectoriser; vectoriser = NULL; } } FTPolygonGlyphImpl::~FTPolygonGlyphImpl() { if(glList) { glDeleteLists(glList, 1); } else if(vectoriser) { delete vectoriser; } } const FTPoint& FTPolygonGlyphImpl::RenderImpl(const FTPoint& pen, int /*renderMode*/) { glTranslatef(pen.Xf(), pen.Yf(), pen.Zf()); if(glList) { glCallList(glList); } else if(vectoriser) { DoRender(); } glTranslatef(-pen.Xf(), -pen.Yf(), -pen.Zf()); return advance; } void FTPolygonGlyphImpl::DoRender() { vectoriser->MakeMesh(1.0, 1, outset); const FTMesh *mesh = vectoriser->GetMesh(); for(unsigned int t = 0; t < mesh->TesselationCount(); ++t) { const FTTesselation* subMesh = mesh->Tesselation(t); unsigned int polygonType = subMesh->PolygonType(); glBegin(polygonType); for(unsigned int i = 0; i < subMesh->PointCount(); ++i) { FTPoint point = subMesh->Point(i); glTexCoord2f(point.Xf() / hscale, point.Yf() / vscale); glVertex3f(point.Xf() / 64.0f, point.Yf() / 64.0f, 0.0f); } glEnd(); } } workbench-1.1.1/src/FtglFont/FTGlyph/FTPolygonGlyphImpl.h000066400000000000000000000037721255417355300232260ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __FTPolygonGlyphImpl__ #define __FTPolygonGlyphImpl__ #include "FTGlyphImpl.h" class FTVectoriser; class FTPolygonGlyphImpl : public FTGlyphImpl { friend class FTPolygonGlyph; public: FTPolygonGlyphImpl(FT_GlyphSlot glyph, float outset, bool useDisplayList); virtual ~FTPolygonGlyphImpl(); virtual const FTPoint& RenderImpl(const FTPoint& pen, int renderMode); private: /** * Private rendering method. */ void DoRender(); /** * Private rendering variables. */ unsigned int hscale, vscale; FTVectoriser *vectoriser; float outset; /** * OpenGL display list */ GLuint glList; }; #endif // __FTPolygonGlyphImpl__ workbench-1.1.1/src/FtglFont/FTGlyph/FTTextureGlyph.cpp000066400000000000000000000104551255417355300227440ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * Copyright (c) 2014 Washington University School of Medicine * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "FtglConfig.h" #include #include "FTGL/ftgl.h" #include "FTInternals.h" #include "FTTextureGlyphImpl.h" // // FTGLTextureGlyph // FTTextureGlyph::FTTextureGlyph(FT_GlyphSlot glyph, int id, int xOffset, int yOffset, int width, int height) : FTGlyph(new FTTextureGlyphImpl(glyph, id, xOffset, yOffset, width, height)) {} FTTextureGlyph::~FTTextureGlyph() {} const FTPoint& FTTextureGlyph::Render(const FTPoint& pen, int renderMode) { FTTextureGlyphImpl *myimpl = dynamic_cast(impl); return myimpl->RenderImpl(pen, renderMode); } // // FTGLTextureGlyphImpl // GLint FTTextureGlyphImpl::activeTextureID = 0; FTTextureGlyphImpl::FTTextureGlyphImpl(FT_GlyphSlot glyph, int id, int xOffset, int yOffset, int width, int height) : FTGlyphImpl(glyph), destWidth(0), destHeight(0), glTextureID(id) { /* FIXME: need to propagate the render mode all the way down to * here in order to get FT_RENDER_MODE_MONO aliased fonts. */ err = FT_Render_Glyph(glyph, FT_RENDER_MODE_NORMAL); if(err || glyph->format != ft_glyph_format_bitmap) { return; } FT_Bitmap bitmap = glyph->bitmap; destWidth = bitmap.width; destHeight = bitmap.rows; if(destWidth && destHeight) { glPushClientAttrib(GL_CLIENT_PIXEL_STORE_BIT); glPixelStorei(GL_UNPACK_LSB_FIRST, GL_FALSE); glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glBindTexture(GL_TEXTURE_2D, glTextureID); glTexSubImage2D(GL_TEXTURE_2D, 0, xOffset, yOffset, destWidth, destHeight, GL_ALPHA, GL_UNSIGNED_BYTE, bitmap.buffer); glPopClientAttrib(); } // 0 // +----+ // | | // | | // | | // +----+ // 1 uv[0].X(static_cast(xOffset) / static_cast(width)); uv[0].Y(static_cast(yOffset) / static_cast(height)); uv[1].X(static_cast(xOffset + destWidth) / static_cast(width)); uv[1].Y(static_cast(yOffset + destHeight) / static_cast(height)); corner = FTPoint(glyph->bitmap_left, glyph->bitmap_top); } FTTextureGlyphImpl::~FTTextureGlyphImpl() {} const FTPoint& FTTextureGlyphImpl::RenderImpl(const FTPoint& pen, int /*renderMode*/) { float dx, dy; if(activeTextureID != glTextureID) { glBindTexture(GL_TEXTURE_2D, (GLuint)glTextureID); activeTextureID = glTextureID; } dx = floor(pen.Xf() + corner.Xf()); dy = floor(pen.Yf() + corner.Yf()); glBegin(GL_QUADS); glTexCoord2f(uv[0].Xf(), uv[0].Yf()); glVertex2f(dx, dy); glTexCoord2f(uv[0].Xf(), uv[1].Yf()); glVertex2f(dx, dy - destHeight); glTexCoord2f(uv[1].Xf(), uv[1].Yf()); glVertex2f(dx + destWidth, dy - destHeight); glTexCoord2f(uv[1].Xf(), uv[0].Yf()); glVertex2f(dx + destWidth, dy); glEnd(); return advance; } workbench-1.1.1/src/FtglFont/FTGlyph/FTTextureGlyphImpl.h000066400000000000000000000053771255417355300232420ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __FTTextureGlyphImpl__ #define __FTTextureGlyphImpl__ #include "FTGlyphImpl.h" class FTTextureGlyphImpl : public FTGlyphImpl { friend class FTTextureGlyph; friend class FTTextureFontImpl; protected: FTTextureGlyphImpl(FT_GlyphSlot glyph, int id, int xOffset, int yOffset, int width, int height); virtual ~FTTextureGlyphImpl(); virtual const FTPoint& RenderImpl(const FTPoint& pen, int renderMode); private: /** * Reset the currently active texture to zero to get into a known * state before drawing a string. This is to get round possible * threading issues. */ static void ResetActiveTexture() { activeTextureID = 0; } /** * The width of the glyph 'image' */ int destWidth; /** * The height of the glyph 'image' */ int destHeight; /** * Vector from the pen position to the topleft corner of the pixmap */ FTPoint corner; /** * The texture co-ords of this glyph within the texture. */ FTPoint uv[2]; /** * The texture index that this glyph is contained in. */ int glTextureID; /** * The texture index of the currently active texture * * We keep track of the currently active texture to try to reduce the * number of texture bind operations. */ static GLint activeTextureID; }; #endif // __FTTextureGlyphImpl__ workbench-1.1.1/src/FtglFont/FTGlyphContainer.cpp000066400000000000000000000064611255417355300217130ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * Copyright (c) 2014 Washington University School of Medicine * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "FtglConfig.h" #include "FTGL/ftgl.h" #include "FTGlyphContainer.h" #include "FTFace.h" #include "FTCharmap.h" FTGlyphContainer::FTGlyphContainer(FTFace* f) : face(f), err(0) { glyphs.push_back(NULL); charMap = new FTCharmap(face); } FTGlyphContainer::~FTGlyphContainer() { GlyphVector::iterator it; for(it = glyphs.begin(); it != glyphs.end(); ++it) { delete *it; } glyphs.clear(); delete charMap; } bool FTGlyphContainer::CharMap(FT_Encoding encoding) { bool result = charMap->CharMap(encoding); err = charMap->Error(); return result; } unsigned int FTGlyphContainer::FontIndex(const unsigned int charCode) const { return charMap->FontIndex(charCode); } void FTGlyphContainer::Add(FTGlyph* tempGlyph, const unsigned int charCode) { charMap->InsertIndex(charCode, glyphs.size()); glyphs.push_back(tempGlyph); } const FTGlyph* /*const*/ FTGlyphContainer::Glyph(const unsigned int charCode) const { unsigned int index = charMap->GlyphListIndex(charCode); return glyphs[index]; } FTBBox FTGlyphContainer::BBox(const unsigned int charCode) const { return Glyph(charCode)->BBox(); } float FTGlyphContainer::Advance(const unsigned int charCode, const unsigned int nextCharCode) { unsigned int left = charMap->FontIndex(charCode); unsigned int right = charMap->FontIndex(nextCharCode); return face->KernAdvance(left, right).Xf() + Glyph(charCode)->Advance(); } FTPoint FTGlyphContainer::Render(const unsigned int charCode, const unsigned int nextCharCode, FTPoint penPosition, int renderMode) { unsigned int left = charMap->FontIndex(charCode); unsigned int right = charMap->FontIndex(nextCharCode); FTPoint kernAdvance = face->KernAdvance(left, right); if(!face->Error()) { unsigned int index = charMap->GlyphListIndex(charCode); kernAdvance += glyphs[index]->Render(penPosition, renderMode); } return kernAdvance; } workbench-1.1.1/src/FtglFont/FTGlyphContainer.h000066400000000000000000000116201255417355300213510ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2014 Washington University School of Medicine * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __FTGlyphContainer__ #define __FTGlyphContainer__ #include #include FT_FREETYPE_H #include FT_GLYPH_H #include "FTGL/ftgl.h" #include "FTVector.h" class FTFace; class FTGlyph; class FTCharmap; /** * FTGlyphContainer holds the post processed FTGlyph objects. * * @see FTGlyph */ class FTGlyphContainer { typedef FTVector GlyphVector; public: /** * Constructor * * @param face The Freetype face */ FTGlyphContainer(FTFace* face); /** * Destructor */ ~FTGlyphContainer(); /** * Sets the character map for the face. * * @param encoding the Freetype encoding symbol. See above. * @return true if charmap was valid * and set correctly */ bool CharMap(FT_Encoding encoding); /** * Get the font index of the input character. * * @param characterCode The character code of the requested glyph in the * current encoding eg apple roman. * @return The font index for the character. */ unsigned int FontIndex(const unsigned int characterCode) const; /** * Adds a glyph to this glyph list. * * @param glyph The FTGlyph to be inserted into the container * @param characterCode The char code of the glyph NOT the glyph index. */ void Add(FTGlyph* glyph, const unsigned int characterCode); /** * Get a glyph from the glyph list * * @param characterCode The char code of the glyph NOT the glyph index * @return An FTGlyph or null is it hasn't been * loaded. */ const FTGlyph* /*const*/ Glyph(const unsigned int characterCode) const; /** * Get the bounding box for a character. * @param characterCode The char code of the glyph NOT the glyph index */ FTBBox BBox(const unsigned int characterCode) const; /** * Returns the kerned advance width for a glyph. * * @param characterCode glyph index of the character * @param nextCharacterCode the next glyph in a string * @return advance width */ float Advance(const unsigned int characterCode, const unsigned int nextCharacterCode); /** * Renders a character * @param characterCode the glyph to be Rendered * @param nextCharacterCode the next glyph in the string. Used for kerning. * @param penPosition the position to Render the glyph * @param renderMode Render mode to display * @return The distance to advance the pen position after Rendering */ FTPoint Render(const unsigned int characterCode, const unsigned int nextCharacterCode, FTPoint penPosition, int renderMode); /** * Queries the Font for errors. * * @return The current error code. */ FT_Error Error() const { return err; } private: /** * The FTGL face */ FTFace* face; /** * The Character Map object associated with the current face */ FTCharmap* charMap; /** * A structure to hold the glyphs */ GlyphVector glyphs; /** * Current error code. Zero means no error. */ FT_Error err; }; #endif // __FTGlyphContainer__ workbench-1.1.1/src/FtglFont/FTInternals.h000066400000000000000000000066101255417355300203650ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Éric Beets * Copyright (c) 2008 Sam Hocevar * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __FTINTERNALS_H__ #define __FTINTERNALS_H__ #include "FTGL/ftgl.h" #include #include // Fixes for deprecated identifiers in 2.1.5 #ifndef FT_OPEN_MEMORY #define FT_OPEN_MEMORY (FT_Open_Flags)1 #endif #ifndef FT_RENDER_MODE_MONO #define FT_RENDER_MODE_MONO ft_render_mode_mono #endif #ifndef FT_RENDER_MODE_NORMAL #define FT_RENDER_MODE_NORMAL ft_render_mode_normal #endif #ifdef WIN32 // Under windows avoid including is overrated. // Sure, it can be avoided and "name space pollution" can be // avoided, but why? It really doesn't make that much difference // these days. #define WIN32_LEAN_AND_MEAN #include #ifndef __gl_h_ #include #include #endif #else // Non windows platforms - don't require nonsense as seen above :-) #ifndef __gl_h_ #ifdef SDL_main #include "SDL_opengl.h" #elif __APPLE_CC__ #include #include #else #include #if defined (__sun__) && !defined (__sparc__) #include #else #include #endif #endif #endif // Required for compatibility with glext.h style function definitions of // OpenGL extensions, such as in src/osg/Point.cpp. #ifndef APIENTRY #define APIENTRY #endif #endif FTGL_BEGIN_C_DECLS typedef enum { GLYPH_CUSTOM, GLYPH_BITMAP, GLYPH_BUFFER, GLYPH_PIXMAP, GLYPH_OUTLINE, GLYPH_POLYGON, GLYPH_EXTRUDE, GLYPH_TEXTURE, } GlyphType; struct _FTGLglyph { FTGlyph *ptr; FTGL::GlyphType type; }; typedef enum { FONT_CUSTOM, FONT_BITMAP, FONT_BUFFER, FONT_PIXMAP, FONT_OUTLINE, FONT_POLYGON, FONT_EXTRUDE, FONT_TEXTURE, } FontType; struct _FTGLfont { FTFont *ptr; FTGL::FontType type; }; typedef enum { LAYOUT_SIMPLE, } LayoutType; struct _FTGLlayout { FTLayout *ptr; FTGLfont *font; FTGL::LayoutType type; }; FTGL_END_C_DECLS #endif //__FTINTERNALS_H__ workbench-1.1.1/src/FtglFont/FTLayout/000077500000000000000000000000001255417355300175275ustar00rootroot00000000000000workbench-1.1.1/src/FtglFont/FTLayout/FTLayout.cpp000066400000000000000000000032611255417355300217440ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "FtglConfig.h" #include "FTGL/ftgl.h" #include "../FTFont/FTFontImpl.h" #include "./FTLayoutImpl.h" // // FTLayout // FTLayout::FTLayout() { impl = new FTLayoutImpl(); } FTLayout::FTLayout(FTLayoutImpl *pImpl) { impl = pImpl; } FTLayout::~FTLayout() { delete impl; } FT_Error FTLayout::Error() const { return impl->err; } // // FTLayoutImpl // FTLayoutImpl::FTLayoutImpl() : err(0) { ; } FTLayoutImpl::~FTLayoutImpl() { ; } workbench-1.1.1/src/FtglFont/FTLayout/FTLayoutGlue.cpp000066400000000000000000000125551255417355300225670ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Éric Beets * Copyright (c) 2008 Sam Hocevar * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "FtglConfig.h" #include "FTInternals.h" static const FTBBox static_ftbbox; FTGL_BEGIN_C_DECLS #define C_TOR(cname, cargs, cxxname, cxxarg, cxxtype) \ FTGLlayout* cname cargs \ { \ cxxname *l = new cxxname cxxarg; \ if(l->Error()) \ { \ delete l; \ return NULL; \ } \ FTGLlayout *ftgl = (FTGLlayout *)malloc(sizeof(FTGLlayout)); \ ftgl->ptr = l; \ ftgl->type = cxxtype; \ return ftgl; \ } // FTSimpleLayout::FTSimpleLayout(); C_TOR(ftglCreateSimpleLayout, (), FTSimpleLayout, (), LAYOUT_SIMPLE); #define C_FUN(cret, cname, cargs, cxxerr, cxxname, cxxarg) \ cret cname cargs \ { \ if(!l || !l->ptr) \ { \ fprintf(stderr, "FTGL warning: NULL pointer in %s\n", #cname); \ cxxerr; \ } \ return l->ptr->cxxname cxxarg; \ } // FTLayout::~FTLayout(); void ftglDestroyLayout(FTGLlayout *l) { if(!l || !l->ptr) { fprintf(stderr, "FTGL warning: NULL pointer in %s\n", __FUNCTION__); return; } delete l->ptr; free(l); } // virtual FTBBox FTLayout::BBox(const char* string) extern "C++" { C_FUN(static FTBBox, _ftgGetlLayoutBBox, (FTGLlayout *l, const char *s), return static_ftbbox, BBox, (s)); } void ftgGetlLayoutBBox(FTGLlayout *l, const char * s, float c[6]) { FTBBox ret = _ftgGetlLayoutBBox(l, s); FTPoint lower = ret.Lower(), upper = ret.Upper(); c[0] = lower.Xf(); c[1] = lower.Yf(); c[2] = lower.Zf(); c[3] = upper.Xf(); c[4] = upper.Yf(); c[5] = upper.Zf(); } // virtual void FTLayout::Render(const char* string, int renderMode); C_FUN(void, ftglRenderLayout, (FTGLlayout *l, const char *s, int r), return, Render, (s, r)); // FT_Error FTLayout::Error() const; C_FUN(FT_Error, ftglGetLayoutError, (FTGLlayout *l), return -1, Error, ()); // void FTSimpleLayout::SetFont(FTFont *fontInit) void ftglSetLayoutFont(FTGLlayout *l, FTGLfont *font) { if(!l || !l->ptr) { fprintf(stderr, "FTGL warning: NULL pointer in %s\n", __FUNCTION__); return; } if(l->type != FTGL::LAYOUT_SIMPLE) { fprintf(stderr, "FTGL warning: %s not implemented for %d\n", __FUNCTION__, l->type); } l->font = font; return dynamic_cast(l->ptr)->SetFont(font->ptr); } // FTFont *FTSimpleLayout::GetFont() FTGLfont *ftglGetLayoutFont(FTGLlayout *l) { if(!l || !l->ptr) { fprintf(stderr, "FTGL warning: NULL pointer in %s\n", __FUNCTION__); return NULL; } if(l->type != FTGL::LAYOUT_SIMPLE) { fprintf(stderr, "FTGL warning: %s not implemented for %d\n", __FUNCTION__, l->type); } return l->font; } #undef C_FUN #define C_FUN(cret, cname, cargs, cxxerr, cxxname, cxxarg) \ cret cname cargs \ { \ if(!l || !l->ptr) \ { \ fprintf(stderr, "FTGL warning: NULL pointer in %s\n", #cname); \ cxxerr; \ } \ if(l->type != FTGL::LAYOUT_SIMPLE) \ { \ fprintf(stderr, "FTGL warning: %s not implemented for %d\n", \ __FUNCTION__, l->type); \ cxxerr; \ } \ return dynamic_cast(l->ptr)->cxxname cxxarg; \ } // void FTSimpleLayout::SetLineLength(const float LineLength); C_FUN(void, ftglSetLayoutLineLength, (FTGLlayout *l, const float length), return, SetLineLength, (length)); // float FTSimpleLayout::GetLineLength() const C_FUN(float, ftglGetLayoutLineLength, (FTGLlayout *l), return 0.0f, GetLineLength, ()); // void FTSimpleLayout::SetAlignment(const TextAlignment Alignment) C_FUN(void, ftglSetLayoutAlignment, (FTGLlayout *l, const int a), return, SetAlignment, ((FTGL::TextAlignment)a)); // TextAlignment FTSimpleLayout::GetAlignment() const C_FUN(int, ftglGetLayoutAlignement, (FTGLlayout *l), return FTGL::ALIGN_LEFT, GetAlignment, ()); // void FTSimpleLayout::SetLineSpacing(const float LineSpacing) C_FUN(void, ftglSetLayoutLineSpacing, (FTGLlayout *l, const float f), return, SetLineSpacing, (f)); FTGL_END_C_DECLS workbench-1.1.1/src/FtglFont/FTLayout/FTLayoutImpl.h000066400000000000000000000032411255417355300222310ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __FTLayoutImpl__ #define __FTLayoutImpl__ #include "FTSize.h" #include "FTGlyphContainer.h" class FTLayoutImpl { friend class FTLayout; protected: FTLayoutImpl(); virtual ~FTLayoutImpl(); protected: /** * Current pen or cursor position; */ FTPoint pen; /** * Current error code. Zero means no error. */ FT_Error err; }; #endif // __FTLayoutImpl__ workbench-1.1.1/src/FtglFont/FTLayout/FTSimpleLayout.cpp000066400000000000000000000342271255417355300231240ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * Copyright (c) 2014 Washington University School of Medicine * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "FtglConfig.h" #include #include #include "FTInternals.h" #include "FTUnicode.h" #include "FTGlyphContainer.h" #include "FTSimpleLayoutImpl.h" // // FTSimpleLayout // FTSimpleLayout::FTSimpleLayout() : FTLayout(new FTSimpleLayoutImpl()) {} FTSimpleLayout::~FTSimpleLayout() {} FTBBox FTSimpleLayout::BBox(const char *string, const int len, FTPoint pos) { return dynamic_cast(impl)->BBox(string, len, pos); } FTBBox FTSimpleLayout::BBox(const wchar_t *string, const int len, FTPoint pos) { return dynamic_cast(impl)->BBox(string, len, pos); } void FTSimpleLayout::Render(const char *string, const int len, FTPoint pos, int renderMode) { return dynamic_cast(impl)->Render(string, len, pos, renderMode); } void FTSimpleLayout::Render(const wchar_t* string, const int len, FTPoint pos, int renderMode) { return dynamic_cast(impl)->Render(string, len, pos, renderMode); } void FTSimpleLayout::SetFont(FTFont *fontInit) { dynamic_cast(impl)->currentFont = fontInit; } FTFont *FTSimpleLayout::GetFont() { return dynamic_cast(impl)->currentFont; } void FTSimpleLayout::SetLineLength(const float LineLength) { dynamic_cast(impl)->lineLength = LineLength; } float FTSimpleLayout::GetLineLength() const { return dynamic_cast(impl)->lineLength; } void FTSimpleLayout::SetAlignment(const FTGL::TextAlignment Alignment) { dynamic_cast(impl)->alignment = Alignment; } FTGL::TextAlignment FTSimpleLayout::GetAlignment() const { return dynamic_cast(impl)->alignment; } void FTSimpleLayout::SetLineSpacing(const float LineSpacing) { dynamic_cast(impl)->lineSpacing = LineSpacing; } float FTSimpleLayout::GetLineSpacing() const { return dynamic_cast(impl)->lineSpacing; } // // FTSimpleLayoutImpl // FTSimpleLayoutImpl::FTSimpleLayoutImpl() { currentFont = NULL; lineLength = 100.0f; alignment = FTGL::ALIGN_LEFT; lineSpacing = 1.0f; } template inline FTBBox FTSimpleLayoutImpl::BBoxI(const T* string, const int len, FTPoint position) { FTBBox tmp; WrapText(string, len, position, 0, &tmp); return tmp; } FTBBox FTSimpleLayoutImpl::BBox(const char *string, const int len, FTPoint position) { return BBoxI(string, len, position); } FTBBox FTSimpleLayoutImpl::BBox(const wchar_t *string, const int len, FTPoint position) { return BBoxI(string, len, position); } template inline void FTSimpleLayoutImpl::RenderI(const T *string, const int len, FTPoint position, int renderMode) { pen = FTPoint(0.0f, 0.0f); WrapText(string, len, position, renderMode, NULL); } void FTSimpleLayoutImpl::Render(const char *string, const int len, FTPoint position, int renderMode) { RenderI(string, len, position, renderMode); } void FTSimpleLayoutImpl::Render(const wchar_t* string, const int len, FTPoint position, int renderMode) { RenderI(string, len, position, renderMode); } template inline void FTSimpleLayoutImpl::WrapTextI(const T *buf, const int /*len*/, FTPoint position, int renderMode, FTBBox *bounds) { FTUnicodeStringItr breakItr(buf); // points to the last break character FTUnicodeStringItr lineStart(buf); // points to the line start float nextStart = 0.0; // total width of the current line float breakWidth = 0.0; // width of the line up to the last word break float currentWidth = 0.0; // width of all characters on the current line float prevWidth; // width of all characters but the current glyph float wordLength = 0.0; // length of the block since the last break char int charCount = 0; // number of characters so far on the line int breakCharCount = 0; // number of characters before the breakItr float glyphWidth, advance; FTBBox glyphBounds; // Reset the pen position pen.Y(0); // If we have bounds mark them invalid if(bounds) { bounds->Invalidate(); } // Scan the input for all characters that need output FTUnicodeStringItr prevItr(buf); for (FTUnicodeStringItr itr(buf); *itr; prevItr = itr++, charCount++) { // Find the width of the current glyph glyphBounds = currentFont->BBox(itr.getBufferFromHere(), 1); glyphWidth = glyphBounds.Upper().Xf() - glyphBounds.Lower().Xf(); advance = currentFont->Advance(itr.getBufferFromHere(), 1); prevWidth = currentWidth; // Compute the width of all glyphs up to the end of buf[i] currentWidth = nextStart + glyphWidth; // Compute the position of the next glyph nextStart += advance; // See if the current character is a space, a break or a regular character if((currentWidth > lineLength) || (*itr == '\n')) { // A non whitespace character has exceeded the line length. Or a // newline character has forced a line break. Output the last // line and start a new line after the break character. // If we have not yet found a break, break on the last character if(breakItr == lineStart || (*itr == '\n')) { // Break on the previous character breakItr = prevItr; breakCharCount = charCount - 1; breakWidth = prevWidth; // None of the previous words will be carried to the next line wordLength = 0; // If the current character is a newline discard its advance if(*itr == '\n') advance = 0; } float remainingWidth = lineLength - breakWidth; // Render the current substring FTUnicodeStringItr breakChar = breakItr; // move past the break character and don't count it on the next line either ++breakChar; --charCount; // If the break character is a newline do not render it if(*breakChar == '\n') { ++breakChar; --charCount; } OutputWrapped(lineStart.getBufferFromHere(), breakCharCount, //breakItr.getBufferFromHere() - lineStart.getBufferFromHere(), position, renderMode, remainingWidth, bounds); // Store the start of the next line lineStart = breakChar; // TODO: Is Height() the right value here? pen -= FTPoint(0, currentFont->LineHeight() * lineSpacing); // The current width is the width since the last break nextStart = wordLength + advance; wordLength += advance; currentWidth = wordLength + advance; // Reset the safe break for the next line breakItr = lineStart; charCount -= breakCharCount; } else if(iswspace(*itr)) { // This is the last word break position wordLength = 0; breakItr = itr; breakCharCount = charCount; // Check to see if this is the first whitespace character in a run if(buf == itr.getBufferFromHere() || !iswspace(*prevItr)) { // Record the width of the start of the block breakWidth = currentWidth; } } else { wordLength += advance; } } float remainingWidth = lineLength - currentWidth; // Render any remaining text on the last line // Disable justification for the last row if(alignment == FTGL::ALIGN_JUSTIFY) { alignment = FTGL::ALIGN_LEFT; OutputWrapped(lineStart.getBufferFromHere(), -1, position, renderMode, remainingWidth, bounds); alignment = FTGL::ALIGN_JUSTIFY; } else { OutputWrapped(lineStart.getBufferFromHere(), -1, position, renderMode, remainingWidth, bounds); } } void FTSimpleLayoutImpl::WrapText(const char *buf, const int len, FTPoint position, int renderMode, FTBBox *bounds) { WrapTextI(buf, len, position, renderMode, bounds); } void FTSimpleLayoutImpl::WrapText(const wchar_t* buf, const int len, FTPoint position, int renderMode, FTBBox *bounds) { WrapTextI(buf, len, position, renderMode, bounds); } template inline void FTSimpleLayoutImpl::OutputWrappedI(const T *buf, const int len, FTPoint position, int renderMode, const float remaining, FTBBox *bounds) { float distributeWidth = 0.0; // Align the text according as specified by Alignment switch (alignment) { case FTGL::ALIGN_LEFT: pen.X(0); break; case FTGL::ALIGN_CENTER: pen.X(remaining / 2); break; case FTGL::ALIGN_RIGHT: pen.X(remaining); break; case FTGL::ALIGN_JUSTIFY: pen.X(0); distributeWidth = remaining; break; } // If we have bounds expand them by the line's bounds, otherwise render // the line. if(bounds) { FTBBox temp = currentFont->BBox(buf, len); // Add the extra space to the upper x dimension temp = FTBBox(temp.Lower() + pen, temp.Upper() + pen + FTPoint(distributeWidth, 0)); // See if this is the first area to be added to the bounds if(bounds->IsValid()) { *bounds |= temp; } else { *bounds = temp; } } else { RenderSpace(buf, len, position, renderMode, distributeWidth); } } void FTSimpleLayoutImpl::OutputWrapped(const char *buf, const int len, FTPoint position, int renderMode, const float remaining, FTBBox *bounds) { OutputWrappedI(buf, len, position, renderMode, remaining, bounds); } void FTSimpleLayoutImpl::OutputWrapped(const wchar_t *buf, const int len, FTPoint position, int renderMode, const float remaining, FTBBox *bounds) { OutputWrappedI(buf, len, position, renderMode, remaining, bounds); } template inline void FTSimpleLayoutImpl::RenderSpaceI(const T *string, const int len, FTPoint /*position*/, int renderMode, const float extraSpace) { float space = 0.0; // If there is space to distribute, count the number of spaces if(extraSpace > 0.0) { int numSpaces = 0; // Count the number of space blocks in the input FTUnicodeStringItr prevItr(string), itr(string); for(int i = 0; ((len < 0) && *itr) || ((len >= 0) && (i <= len)); ++i, prevItr = itr++) { // If this is the end of a space block, increment the counter if((i > 0) && !iswspace(*itr) && iswspace(*prevItr)) { numSpaces++; } } space = extraSpace/numSpaces; } // Output all characters of the string FTUnicodeStringItr prevItr(string), itr(string); for(int i = 0; ((len < 0) && *itr) || ((len >= 0) && (i <= len)); ++i, prevItr = itr++) { // If this is the end of a space block, distribute the extra space // inside it if((i > 0) && !iswspace(*itr) && iswspace(*prevItr)) { pen += FTPoint(space, 0); } pen = currentFont->Render(itr.getBufferFromHere(), 1, pen, FTPoint(), renderMode); } } void FTSimpleLayoutImpl::RenderSpace(const char *string, const int len, FTPoint position, int renderMode, const float extraSpace) { RenderSpaceI(string, len, position, renderMode, extraSpace); } void FTSimpleLayoutImpl::RenderSpace(const wchar_t *string, const int len, FTPoint position, int renderMode, const float extraSpace) { RenderSpaceI(string, len, position, renderMode, extraSpace); } workbench-1.1.1/src/FtglFont/FTLayout/FTSimpleLayoutImpl.h000066400000000000000000000231131255417355300234030ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __FTSimpleLayoutImpl__ #define __FTSimpleLayoutImpl__ #include "FTLayoutImpl.h" class FTFont; class FTSimpleLayoutImpl : public FTLayoutImpl { friend class FTSimpleLayout; protected: FTSimpleLayoutImpl(); virtual ~FTSimpleLayoutImpl() {}; virtual FTBBox BBox(const char* string, const int len, FTPoint position); virtual FTBBox BBox(const wchar_t* string, const int len, FTPoint position); virtual void Render(const char *string, const int len, FTPoint position, int renderMode); virtual void Render(const wchar_t *string, const int len, FTPoint position, int renderMode); /** * Render a string of characters and distribute extra space amongst * the whitespace regions of the string. * * @param string A buffer of wchar_t characters to output. * @param len The length of the string. If < 0 then all characters * will be displayed until a null character is encountered. * @param position TODO * @param renderMode Render mode to display * @param extraSpace The amount of extra space to distribute amongst * the characters. */ virtual void RenderSpace(const char *string, const int len, FTPoint position, int renderMode, const float extraSpace); /** * Render a string of characters and distribute extra space amongst * the whitespace regions of the string. * * @param string A buffer of wchar_t characters to output. * @param len The length of the string. If < 0 then all characters * will be displayed until a null character is encountered. * @param position TODO * @param renderMode Render mode to display * @param extraSpace The amount of extra space to distribute amongst * the characters. */ virtual void RenderSpace(const wchar_t *string, const int len, FTPoint position, int renderMode, const float extraSpace); private: /** * Either render a string of characters and wrap lines * longer than a threshold or compute the bounds * of a string of characters when wrapped. The functionality * of this method is exposed by the BBoxWrapped and * RenderWrapped methods. * * @param buf A char string to output. * @param len The length of the string. If < 0 then all characters * will be displayed until a null character is encountered. * @param position TODO * @param renderMode Render mode to display * @param bounds A pointer to a bounds object. If non null * the bounds of the text when laid out * will be stored in bounds. If null the * text will be rendered. */ virtual void WrapText(const char *buf, const int len, FTPoint position, int renderMode, FTBBox *bounds); /** * Either render a string of characters and wrap lines * longer than a threshold or compute the bounds * of a string of characters when wrapped. The functionality * of this method is exposed by the BBoxWrapped and * RenderWrapped methods. * * @param buf A wchar_t style string to output. * @param len The length of the string. If < 0 then all characters * will be displayed until a null character is encountered. * @param position TODO * @param renderMode Render mode to display * @param bounds A pointer to a bounds object. If non null * the bounds of the text when laid out * will be stored in bounds. If null the * text will be rendered. */ virtual void WrapText(const wchar_t *buf, const int len, FTPoint position, int renderMode, FTBBox *bounds); /** * A helper method used by WrapText to either output the text or * compute it's bounds. * * @param buf A pointer to an array of character data. * @param len The length of the string. If < 0 then all characters * will be displayed until a null character is encountered. * @param position TODO * @param renderMode Render mode to display * @param RemainingWidth The amount of extra space left on the line. * @param bounds A pointer to a bounds object. If non null the * bounds will be initialized or expanded by the * bounds of the line. If null the text will be * rendered. If the bounds are invalid (lower > upper) * they will be initialized. Otherwise they * will be expanded. */ void OutputWrapped(const char *buf, const int len, FTPoint position, int renderMode, const float RemainingWidth, FTBBox *bounds); /** * A helper method used by WrapText to either output the text or * compute it's bounds. * * @param buf A pointer to an array of character data. * @param len The length of the string. If < 0 then all characters * will be displayed until a null character is encountered. * @param position TODO * @param renderMode Render mode to display * @param RemainingWidth The amount of extra space left on the line. * @param bounds A pointer to a bounds object. If non null the * bounds will be initialized or expanded by the * bounds of the line. If null the text will be * rendered. If the bounds are invalid (lower > upper) * they will be initialized. Otherwise they * will be expanded. */ void OutputWrapped(const wchar_t *buf, const int len, FTPoint position, int renderMode, const float RemainingWidth, FTBBox *bounds); /** * The font to use for rendering the text. The font is * referenced by this but will not be disposed of when this * is deleted. */ FTFont *currentFont; /** * The maximum line length for formatting text. */ float lineLength; /** * The text alignment mode used to distribute * space within a line or rendered text. */ FTGL::TextAlignment alignment; /** * The height of each line of text expressed as * a percentage of the font's line height. */ float lineSpacing; /* Internal generic BBox() implementation */ template inline FTBBox BBoxI(const T* string, const int len, FTPoint position); /* Internal generic Render() implementation */ template inline void RenderI(const T* string, const int len, FTPoint position, int renderMode); /* Internal generic RenderSpace() implementation */ template inline void RenderSpaceI(const T* string, const int len, FTPoint position, int renderMode, const float extraSpace); /* Internal generic WrapText() implementation */ template void WrapTextI(const T* buf, const int len, FTPoint position, int renderMode, FTBBox *bounds); /* Internal generic OutputWrapped() implementation */ template void OutputWrappedI(const T* buf, const int len, FTPoint position, int renderMode, const float RemainingWidth, FTBBox *bounds); }; #endif // __FTSimpleLayoutImpl__ workbench-1.1.1/src/FtglFont/FTLibrary.cpp000066400000000000000000000040501255417355300203610ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "FtglConfig.h" #include "FTLibrary.h" const FTLibrary& FTLibrary::Instance() { static FTLibrary ftlib; return ftlib; } FTLibrary::~FTLibrary() { if(library != 0) { FT_Done_FreeType(*library); delete library; library= 0; } // if(manager != 0) // { // FTC_Manager_Done(manager); // // delete manager; // manager= 0; // } } FTLibrary::FTLibrary() : library(0), err(0) { Initialise(); } bool FTLibrary::Initialise() { if(library != 0) return true; library = new FT_Library; err = FT_Init_FreeType(library); if(err) { delete library; library = 0; return false; } // FTC_Manager* manager; // // if(FTC_Manager_New(lib, 0, 0, 0, my_face_requester, 0, manager) // { // delete manager; // manager= 0; // return false; // } return true; } workbench-1.1.1/src/FtglFont/FTLibrary.h000066400000000000000000000076041255417355300200360ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2014 Washington University School of Medicine * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __FTLibrary__ #define __FTLibrary__ #include #include FT_FREETYPE_H //#include FT_CACHE_H #include "FTGL/ftgl.h" /** * FTLibrary class is the global accessor for the Freetype library. * * This class encapsulates the Freetype Library. This is a singleton class * and ensures that only one FT_Library is in existence at any one time. * All constructors are private therefore clients cannot create or * instantiate this class themselves and must access it's methods via the * static FTLibrary::Instance() function. * * Just because this class returns a valid FTLibrary object * doesn't mean that the Freetype Library has been successfully initialised. * Clients should check for errors. You can initialse the library AND check * for errors using the following code... * err = FTLibrary::Instance().Error(); * * @see "Freetype 2 Documentation" * */ class FTLibrary { public: /** * Global acces point to the single FTLibrary object. * * @return The global FTLibrary object. */ static const FTLibrary& Instance(); /** * Gets a pointer to the native Freetype library. * * @return A handle to a FreeType library instance. */ const FT_Library* /*const*/ GetLibrary() const { return library; } /** * Queries the library for errors. * * @return The current error code. */ FT_Error Error() const { return err; } /** * Destructor * * Disposes of the Freetype library */ ~FTLibrary(); private: /** * Default constructors. * * Made private to stop clients creating there own FTLibrary * objects. */ FTLibrary(); FTLibrary(const FT_Library&){} FTLibrary& operator=(const FT_Library&) { return *this; } /** * Initialises the Freetype library * * Even though this function indicates success via the return value, * clients can't see this so must check the error codes. This function * is only ever called by the default c_stor * * @return true if the Freetype library was * successfully initialised, false * otherwise. */ bool Initialise(); /** * Freetype library handle. */ FT_Library* library; // FTC_Manager* manager; /** * Current error code. Zero means no error. */ FT_Error err; }; #endif // __FTLibrary__ workbench-1.1.1/src/FtglFont/FTList.h000066400000000000000000000063021255417355300173370ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __FTList__ #define __FTList__ #include "FTGL/ftgl.h" /** * Provides a non-STL alternative to the STL list */ template class FTList { public: typedef FT_LIST_ITEM_TYPE value_type; typedef value_type& reference; typedef const value_type& const_reference; typedef size_t size_type; /** * Constructor */ FTList() : listSize(0), tail(0) { tail = NULL; head = new Node; } /** * Destructor */ ~FTList() { Node* next; for(Node *walk = head; walk; walk = next) { next = walk->next; delete walk; } } /** * Get the number of items in the list */ size_type size() const { return listSize; } /** * Add an item to the end of the list */ void push_back(const value_type& item) { Node* node = new Node(item); if(head->next == NULL) { head->next = node; } if(tail) { tail->next = node; } tail = node; ++listSize; } /** * Get the item at the front of the list */ reference front() const { return head->next->payload; } /** * Get the item at the end of the list */ reference back() const { return tail->payload; } private: struct Node { Node() : next(NULL) {} Node(const value_type& item) : next(NULL) { payload = item; } Node* next; value_type payload; }; size_type listSize; Node* head; Node* tail; }; #endif // __FTList__ workbench-1.1.1/src/FtglFont/FTPoint.cpp000066400000000000000000000036361255417355300200570ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "FtglConfig.h" #include #include "FTGL/ftgl.h" bool operator == (const FTPoint &a, const FTPoint &b) { return((a.values[0] == b.values[0]) && (a.values[1] == b.values[1]) && (a.values[2] == b.values[2])); } bool operator != (const FTPoint &a, const FTPoint &b) { return((a.values[0] != b.values[0]) || (a.values[1] != b.values[1]) || (a.values[2] != b.values[2])); } FTPoint FTPoint::Normalise() { double norm = sqrt(values[0] * values[0] + values[1] * values[1] + values[2] * values[2]); if(norm == 0.0) { return *this; } FTPoint temp(values[0] / norm, values[1] / norm, values[2] / norm); return temp; } workbench-1.1.1/src/FtglFont/FTSize.cpp000066400000000000000000000056131255417355300176750ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "FtglConfig.h" #include "FTSize.h" FTSize::FTSize() : ftFace(0), ftSize(0), size(0), xResolution(0), yResolution(0), err(0) {} FTSize::~FTSize() {} bool FTSize::CharSize(FT_Face* face, unsigned int pointSize, unsigned int xRes, unsigned int yRes) { if(size != pointSize || xResolution != xRes || yResolution != yRes) { err = FT_Set_Char_Size(*face, 0L, pointSize * 64, xResolution, yResolution); if(!err) { ftFace = face; size = pointSize; xResolution = xRes; yResolution = yRes; ftSize = (*ftFace)->size; } } return !err; } unsigned int FTSize::CharSize() const { return size; } float FTSize::Ascender() const { return ftSize == 0 ? 0.0f : static_cast(ftSize->metrics.ascender) / 64.0f; } float FTSize::Descender() const { return ftSize == 0 ? 0.0f : static_cast(ftSize->metrics.descender) / 64.0f; } float FTSize::Height() const { if(0 == ftSize) { return 0.0f; } if(FT_IS_SCALABLE((*ftFace))) { return ((*ftFace)->bbox.yMax - (*ftFace)->bbox.yMin) * ((float)ftSize->metrics.y_ppem / (float)(*ftFace)->units_per_EM); } else { return static_cast(ftSize->metrics.height) / 64.0f; } } float FTSize::Width() const { if(0 == ftSize) { return 0.0f; } if(FT_IS_SCALABLE((*ftFace))) { return ((*ftFace)->bbox.xMax - (*ftFace)->bbox.xMin) * (static_cast(ftSize->metrics.x_ppem) / static_cast((*ftFace)->units_per_EM)); } else { return static_cast(ftSize->metrics.max_advance) / 64.0f; } } float FTSize::Underline() const { return 0.0f; } workbench-1.1.1/src/FtglFont/FTSize.h000066400000000000000000000112671255417355300173440ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __FTSize__ #define __FTSize__ #include #include FT_FREETYPE_H #include "FTGL/ftgl.h" /** * FTSize class provides an abstraction layer for the Freetype Size. * * @see "Freetype 2 Documentation" * */ class FTSize { public: /** * Default Constructor */ FTSize(); /** * Destructor */ virtual ~FTSize(); /** * Sets the char size for the current face. * * This doesn't guarantee that the size was set correctly. Clients * should check errors. If an error does occur the size object isn't modified. * * @param face Parent face for this size object * @param point_size the face size in points (1/72 inch) * @param x_resolution the horizontal resolution of the target device. * @param y_resolution the vertical resolution of the target device. * @return true if the size has been set. Clients should check Error() for more information if this function returns false() */ bool CharSize(FT_Face* face, unsigned int point_size, unsigned int x_resolution, unsigned int y_resolution); /** * get the char size for the current face. * * @return The char size in points */ unsigned int CharSize() const; /** * Gets the global ascender height for the face in pixels. * * @return Ascender height */ float Ascender() const; /** * Gets the global descender height for the face in pixels. * * @return Ascender height */ float Descender() const; /** * Gets the global face height for the face. * * If the face is scalable this returns the height of the global * bounding box which ensures that any glyph will be less than or * equal to this height. If the font isn't scalable there is no * guarantee that glyphs will not be taller than this value. * * @return height in pixels. */ float Height() const; /** * Gets the global face width for the face. * * If the face is scalable this returns the width of the global * bounding box which ensures that any glyph will be less than or * equal to this width. If the font isn't scalable this value is * the max_advance for the face. * * @return width in pixels. */ float Width() const; /** * Gets the underline position for the face. * * @return underline position in pixels */ float Underline() const; /** * Queries for errors. * * @return The current error code. */ FT_Error Error() const { return err; } private: /** * The current Freetype face that this FTSize object relates to. */ FT_Face* ftFace; /** * The Freetype size. */ FT_Size ftSize; /** * The size in points. */ unsigned int size; /** * The horizontal resolution. */ unsigned int xResolution; /** * The vertical resolution. */ unsigned int yResolution; /** * Current error code. Zero means no error. */ FT_Error err; }; #endif // __FTSize__ workbench-1.1.1/src/FtglFont/FTUnicode.h000066400000000000000000000177341255417355300200250ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2008 Daniel Remenak * * Portions derived from ConvertUTF.c Copyright (C) 2001-2004 Unicode, Inc * Unicode, Inc. hereby grants the right to freely use the information * supplied in this file in the creation of products supporting the * Unicode Standard, and to make copies of this file in any form * for internal or external distribution as long as this notice * remains attached. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __FTUnicode__ #define __FTUnicode__ /** * Provides a way to easily walk multibyte unicode strings in the various * Unicode encodings (UTF-8, UTF-16, UTF-32, UCS-2, and UCS-4). Encodings * with elements larger than one byte must already be in the correct endian * order for the current architecture. */ template class FTUnicodeStringItr { public: /** * Constructor. Also reads the first character and stores it. * * @param string The buffer to iterate. No copy is made. */ FTUnicodeStringItr(const T* string) : curPos(string), nextPos(string) { (*this)++; }; /** * Pre-increment operator. Reads the next unicode character and sets * the state appropriately. * Note - not protected against overruns. */ FTUnicodeStringItr& operator++() { curPos = nextPos; // unicode handling switch (sizeof(T)) { case 1: // UTF-8 // get this character readUTF8(); break; case 2: // UTF-16 readUTF16(); break; case 4: // UTF-32 // fall through default: // error condition really, but give it a shot anyway curChar = *nextPos++; } return *this; } /** * Post-increment operator. Reads the next character and sets * the state appropriately. * Note - not protected against overruns. */ FTUnicodeStringItr operator++(int) { FTUnicodeStringItr temp = *this; ++*this; return temp; } /** * Equality operator. Two FTUnicodeStringItrs are considered equal * if they have the same current buffer and buffer position. */ bool operator==(const FTUnicodeStringItr& right) const { if (curPos == right.getBufferFromHere()) return true; return false; } /** * Dereference operator. * * @return The unicode codepoint of the character currently pointed * to by the FTUnicodeStringItr. */ unsigned int operator*() const { return curChar; } /** * Buffer-fetching getter. You can use this to retreive the buffer * starting at the currently-iterated character for functions which * require a Unicode string as input. */ const T* getBufferFromHere() const { return curPos; } private: /** * Helper function for reading a single UTF8 character from the string. * Updates internal state appropriately. */ void readUTF8(); /** * Helper function for reading a single UTF16 character from the string. * Updates internal state appropriately. */ void readUTF16(); /** * The buffer position of the first element in the current character. */ const T* curPos; /** * The character stored at the current buffer position (prefetched on * increment, so there's no penalty for dereferencing more than once). */ unsigned int curChar; /** * The buffer position of the first element in the next character. */ const T* nextPos; // unicode magic numbers static const char utf8bytes[256]; static const unsigned long offsetsFromUTF8[6]; static const unsigned long highSurrogateStart; static const unsigned long highSurrogateEnd; static const unsigned long lowSurrogateStart; static const unsigned long lowSurrogateEnd; static const unsigned long highSurrogateShift; static const unsigned long lowSurrogateBase; }; /* The first character in a UTF8 sequence indicates how many bytes * to read (among other things) */ template const char FTUnicodeStringItr::utf8bytes[256] = { 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, 4,4,4,4,4,4,4,4,5,5,5,5,6,6,6,6 }; /* Magic values subtracted from a buffer value during UTF8 conversion. * This table contains as many values as there might be trailing bytes * in a UTF-8 sequence. */ template const unsigned long FTUnicodeStringItr::offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL, 0x03C82080UL, 0xFA082080UL, 0x82082080UL }; // get a UTF8 character; leave the tracking pointer at the start of the // next character // not protected against invalid UTF8 template inline void FTUnicodeStringItr::readUTF8() { unsigned int ch = 0; unsigned int extraBytesToRead = utf8bytes[(unsigned char)(*nextPos)]; // falls through switch (extraBytesToRead) { case 6: ch += *nextPos++; ch <<= 6; /* remember, illegal UTF-8 */ case 5: ch += *nextPos++; ch <<= 6; /* remember, illegal UTF-8 */ case 4: ch += *nextPos++; ch <<= 6; case 3: ch += *nextPos++; ch <<= 6; case 2: ch += *nextPos++; ch <<= 6; case 1: ch += *nextPos++; } ch -= offsetsFromUTF8[extraBytesToRead-1]; curChar = ch; } // Magic numbers for UTF-16 conversions template const unsigned long FTUnicodeStringItr::highSurrogateStart = 0xD800; template const unsigned long FTUnicodeStringItr::highSurrogateEnd = 0xDBFF; template const unsigned long FTUnicodeStringItr::lowSurrogateStart = 0xDC00; template const unsigned long FTUnicodeStringItr::lowSurrogateEnd = 0xDFFF; template const unsigned long FTUnicodeStringItr::highSurrogateShift = 10; template const unsigned long FTUnicodeStringItr::lowSurrogateBase = 0x0010000UL; template inline void FTUnicodeStringItr::readUTF16() { unsigned int ch = *nextPos++; // if we have the first half of the surrogate pair if (ch >= highSurrogateStart && ch <= highSurrogateEnd) { unsigned int ch2 = *curPos; // complete the surrogate pair if (ch2 >= lowSurrogateStart && ch2 <= lowSurrogateEnd) { ch = ((ch - highSurrogateStart) << highSurrogateShift) + (ch2 - lowSurrogateStart) + lowSurrogateBase; ++nextPos; } } curChar = ch; } #endif workbench-1.1.1/src/FtglFont/FTVector.h000066400000000000000000000116371255417355300176750ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __FTVector__ #define __FTVector__ #include "FTGL/ftgl.h" /** * Provides a non-STL alternative to the STL vector */ template class FTVector { public: typedef FT_VECTOR_ITEM_TYPE value_type; typedef value_type& reference; typedef const value_type& const_reference; typedef value_type* iterator; typedef const value_type* const_iterator; typedef size_t size_type; FTVector() { Capacity = Size = 0; Items = 0; } virtual ~FTVector() { clear(); } FTVector& operator =(const FTVector& v) { reserve(v.capacity()); iterator ptr = begin(); const_iterator vbegin = v.begin(); const_iterator vend = v.end(); while(vbegin != vend) { *ptr++ = *vbegin++; } Size = v.size(); return *this; } size_type size() const { return Size; } size_type capacity() const { return Capacity; } iterator begin() { return Items; } const_iterator begin() const { return Items; } iterator end() { return begin() + size(); } const_iterator end() const { return begin() + size(); } bool empty() const { return size() == 0; } reference operator [](size_type pos) { return(*(begin() + pos)); } const_reference operator [](size_type pos) const { return *(begin() + pos); } void clear() { if(Capacity) { delete [] Items; Capacity = Size = 0; Items = 0; } } void reserve(size_type n) { if(capacity() < n) { expand(n); } } void push_back(const value_type& x) { if(size() == capacity()) { expand(); } (*this)[size()] = x; ++Size; } void resize(size_type n, value_type x) { if(n == size()) { return; } reserve(n); iterator ibegin, iend; if(n >= Size) { ibegin = this->end(); iend = this->begin() + n; } else { ibegin = this->begin() + n; iend = this->end(); } while(ibegin != iend) { *ibegin++ = x; } Size = n; } private: void expand(size_type capacity_hint = 0) { size_type new_capacity = (capacity() == 0) ? 256 : capacity() * 2; if(capacity_hint) { while(new_capacity < capacity_hint) { new_capacity *= 2; } } value_type *new_items = new value_type[new_capacity]; iterator ibegin = this->begin(); iterator iend = this->end(); value_type *ptr = new_items; while(ibegin != iend) { *ptr++ = *ibegin++; } if(Capacity) { delete [] Items; } Items = new_items; Capacity = new_capacity; } size_type Capacity; size_type Size; value_type* Items; }; #endif // __FTVector__ workbench-1.1.1/src/FtglFont/FTVectoriser.cpp000066400000000000000000000216261255417355300211120ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Éric Beets * Copyright (c) 2008 Sam Hocevar * Copyright (c) 2014 Washington University School of Medicine * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "FtglConfig.h" #include "FTInternals.h" #include "FTVectoriser.h" #ifndef CALLBACK #define CALLBACK #endif #define GCC_VERSION (__GNUC__ * 10000 \ + __GNUC_MINOR__ * 100 \ + __GNUC_PATCHLEVEL__) /* Test for GCC > 4.9.0 on Apple JWH */ #if defined __APPLE_CC__ && GCC_VERSION > 4900 typedef GLvoid (*GLUTesselatorFunction) (); #elif defined __APPLE_CC__ && __APPLE_CC__ < 5465 typedef GLvoid (*GLUTesselatorFunction) (...); #elif defined WIN32 && !defined __CYGWIN__ typedef GLvoid (CALLBACK *GLUTesselatorFunction) (); #else typedef GLvoid (*GLUTesselatorFunction) (); #endif void CALLBACK ftglError(GLenum errCode, FTMesh* mesh) { mesh->Error(errCode); } void CALLBACK ftglVertex(void* data, FTMesh* mesh) { FTGL_DOUBLE* vertex = static_cast(data); mesh->AddPoint(vertex[0], vertex[1], vertex[2]); } void CALLBACK ftglCombine(FTGL_DOUBLE coords[3], void** /*vertex_data[4]*/, GLfloat* /*weight[4]*/, void** outData, FTMesh* mesh) { const FTGL_DOUBLE* vertex = static_cast(coords); *outData = const_cast(mesh->Combine(vertex[0], vertex[1], vertex[2])); } void CALLBACK ftglBegin(GLenum type, FTMesh* mesh) { mesh->Begin(type); } void CALLBACK ftglEnd(FTMesh* mesh) { mesh->End(); } FTMesh::FTMesh() : currentTesselation(0), err(0) { tesselationList.reserve(16); } FTMesh::~FTMesh() { for(size_t t = 0; t < tesselationList.size(); ++t) { delete tesselationList[t]; } tesselationList.clear(); } void FTMesh::AddPoint(const FTGL_DOUBLE x, const FTGL_DOUBLE y, const FTGL_DOUBLE z) { currentTesselation->AddPoint(x, y, z); } const FTGL_DOUBLE* FTMesh::Combine(const FTGL_DOUBLE x, const FTGL_DOUBLE y, const FTGL_DOUBLE z) { tempPointList.push_back(FTPoint(x, y,z)); return static_cast(tempPointList.back()); } void FTMesh::Begin(GLenum meshType) { currentTesselation = new FTTesselation(meshType); } void FTMesh::End() { tesselationList.push_back(currentTesselation); } const FTTesselation* /*const*/ FTMesh::Tesselation(size_t index) const { return (index < tesselationList.size()) ? tesselationList[index] : NULL; } FTVectoriser::FTVectoriser(const FT_GlyphSlot glyph) : contourList(0), mesh(0), ftContourCount(0), contourFlag(0) { if(glyph) { outline = glyph->outline; ftContourCount = outline.n_contours; contourList = 0; contourFlag = outline.flags; ProcessContours(); } } FTVectoriser::~FTVectoriser() { for(size_t c = 0; c < ContourCount(); ++c) { delete contourList[c]; } delete [] contourList; delete mesh; } void FTVectoriser::ProcessContours() { short contourLength = 0; short startIndex = 0; short endIndex = 0; contourList = new FTContour*[ftContourCount]; for(int i = 0; i < ftContourCount; ++i) { FT_Vector* pointList = &outline.points[startIndex]; char* tagList = &outline.tags[startIndex]; endIndex = outline.contours[i]; contourLength = (endIndex - startIndex) + 1; FTContour* contour = new FTContour(pointList, tagList, contourLength); contourList[i] = contour; startIndex = endIndex + 1; } // Compute each contour's parity. FIXME: see if FT_Outline_Get_Orientation // can do it for us. for(int i = 0; i < ftContourCount; i++) { FTContour *c1 = contourList[i]; // 1. Find the leftmost point. FTPoint leftmost(65536.0, 0.0); for(size_t n = 0; n < c1->PointCount(); n++) { FTPoint p = c1->Point(n); if(p.X() < leftmost.X()) { leftmost = p; } } // 2. Count how many other contours we cross when going further to // the left. int parity = 0; for(int j = 0; j < ftContourCount; j++) { if(j == i) { continue; } FTContour *c2 = contourList[j]; for(size_t n = 0; n < c2->PointCount(); n++) { FTPoint p1 = c2->Point(n); FTPoint p2 = c2->Point((n + 1) % c2->PointCount()); /* FIXME: combinations of >= > <= and < do not seem stable */ if((p1.Y() < leftmost.Y() && p2.Y() < leftmost.Y()) || (p1.Y() >= leftmost.Y() && p2.Y() >= leftmost.Y()) || (p1.X() > leftmost.X() && p2.X() > leftmost.X())) { continue; } else if(p1.X() < leftmost.X() && p2.X() < leftmost.X()) { parity++; } else { FTPoint a = p1 - leftmost; FTPoint b = p2 - leftmost; if(b.X() * a.Y() > b.Y() * a.X()) { parity++; } } } } // 3. Make sure the glyph has the proper parity. c1->SetParity(parity); } } size_t FTVectoriser::PointCount() { size_t s = 0; for(size_t c = 0; c < ContourCount(); ++c) { s += contourList[c]->PointCount(); } return s; } const FTContour* /*const*/ FTVectoriser::Contour(size_t index) const { return (index < ContourCount()) ? contourList[index] : NULL; } void FTVectoriser::MakeMesh(FTGL_DOUBLE zNormal, int outsetType, float outsetSize) { if(mesh) { delete mesh; } mesh = new FTMesh; GLUtesselator* tobj = gluNewTess(); gluTessCallback(tobj, GLU_TESS_BEGIN_DATA, (GLUTesselatorFunction)ftglBegin); gluTessCallback(tobj, GLU_TESS_VERTEX_DATA, (GLUTesselatorFunction)ftglVertex); gluTessCallback(tobj, GLU_TESS_COMBINE_DATA, (GLUTesselatorFunction)ftglCombine); gluTessCallback(tobj, GLU_TESS_END_DATA, (GLUTesselatorFunction)ftglEnd); gluTessCallback(tobj, GLU_TESS_ERROR_DATA, (GLUTesselatorFunction)ftglError); if(contourFlag & ft_outline_even_odd_fill) // ft_outline_reverse_fill { gluTessProperty(tobj, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_ODD); } else { gluTessProperty(tobj, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_NONZERO); } gluTessProperty(tobj, GLU_TESS_TOLERANCE, 0); gluTessNormal(tobj, 0.0f, 0.0f, zNormal); gluTessBeginPolygon(tobj, mesh); for(size_t c = 0; c < ContourCount(); ++c) { /* Build the */ switch(outsetType) { case 1 : contourList[c]->buildFrontOutset(outsetSize); break; case 2 : contourList[c]->buildBackOutset(outsetSize); break; } const FTContour* contour = contourList[c]; gluTessBeginContour(tobj); for(size_t p = 0; p < contour->PointCount(); ++p) { const FTGL_DOUBLE* d; switch(outsetType) { case 1: d = contour->FrontPoint(p); break; case 2: d = contour->BackPoint(p); break; case 0: default: d = contour->Point(p); break; } // XXX: gluTessVertex doesn't modify the data but does not // specify "const" in its prototype, so we cannot cast to // a const type. gluTessVertex(tobj, (GLdouble *)d, (GLvoid *)d); } gluTessEndContour(tobj); } gluTessEndPolygon(tobj); gluDeleteTess(tobj); } workbench-1.1.1/src/FtglFont/FTVectoriser.h000066400000000000000000000173421255417355300205570ustar00rootroot00000000000000/* * FTGL - OpenGL font library * * Copyright (c) 2001-2004 Henry Maddocks * Copyright (c) 2008 Sam Hocevar * Copyright (c) 2014 Washington University School of Medicine * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef __FTVectoriser__ #define __FTVectoriser__ #include "FTGL/ftgl.h" #include "FTContour.h" #include "FTList.h" #include "FTVector.h" #ifndef CALLBACK #define CALLBACK #endif /** * FTTesselation captures points that are output by OpenGL's gluTesselator. */ class FTTesselation { public: /** * Default constructor */ FTTesselation(GLenum m) : meshType(m) { pointList.reserve(128); } /** * Destructor */ ~FTTesselation() { pointList.clear(); } /** * Add a point to the mesh. */ void AddPoint(const FTGL_DOUBLE x, const FTGL_DOUBLE y, const FTGL_DOUBLE z) { pointList.push_back(FTPoint(x, y, z)); } /** * The number of points in this mesh */ size_t PointCount() const { return pointList.size(); } /** * */ const FTPoint& Point(unsigned int index) const { return pointList[index]; } /** * Return the OpenGL polygon type. */ GLenum PolygonType() const { return meshType; } private: /** * Points generated by gluTesselator. */ typedef FTVector PointVector; PointVector pointList; /** * OpenGL primitive type from gluTesselator. */ GLenum meshType; }; /** * FTMesh is a container of FTTesselation's that make up a polygon glyph */ class FTMesh { typedef FTVector TesselationVector; typedef FTList PointList; public: /** * Default constructor */ FTMesh(); /** * Destructor */ ~FTMesh(); /** * Add a point to the mesh */ void AddPoint(const FTGL_DOUBLE x, const FTGL_DOUBLE y, const FTGL_DOUBLE z); /** * Create a combine point for the gluTesselator */ const FTGL_DOUBLE* Combine(const FTGL_DOUBLE x, const FTGL_DOUBLE y, const FTGL_DOUBLE z); /** * Begin a new polygon */ void Begin(GLenum meshType); /** * End a polygon */ void End(); /** * Record a gluTesselation error */ void Error(GLenum e) { err = e; } /** * The number of tesselations in the mesh */ size_t TesselationCount() const { return tesselationList.size(); } /** * Get a tesselation by index */ const FTTesselation* /*const*/ Tesselation(size_t index) const; /** * Return the temporary point list. For testing only. */ const PointList& TempPointList() const { return tempPointList; } /** * Get the GL ERROR returned by the glu tesselator */ GLenum Error() const { return err; } private: /** * The current sub mesh that we are constructing. */ FTTesselation* currentTesselation; /** * Holds each sub mesh that comprises this glyph. */ TesselationVector tesselationList; /** * Holds extra points created by gluTesselator. See ftglCombine. */ PointList tempPointList; /** * GL ERROR returned by the glu tesselator */ GLenum err; }; const FTGL_DOUBLE FTGL_FRONT_FACING = 1.0; const FTGL_DOUBLE FTGL_BACK_FACING = -1.0; /** * FTVectoriser class is a helper class that converts font outlines into * point data. * * @see FTExtrudeGlyph * @see FTOutlineGlyph * @see FTPolygonGlyph * @see FTContour * @see FTPoint * */ class FTVectoriser { public: /** * Constructor * * @param glyph The freetype glyph to be processed */ FTVectoriser(const FT_GlyphSlot glyph); /** * Destructor */ virtual ~FTVectoriser(); /** * Build an FTMesh from the vector outline data. * * @param zNormal The direction of the z axis of the normal * for this mesh * FIXME: change the following for a constant * @param outsetType Specify the outset type contour * 0 : Original * 1 : Front * 2 : Back * @param outsetSize Specify the outset size contour */ void MakeMesh(FTGL_DOUBLE zNormal = FTGL_FRONT_FACING, int outsetType = 0, float outsetSize = 0.0f); /** * Get the current mesh. */ const FTMesh* /*const*/ GetMesh() const { return mesh; } /** * Get the total count of points in this outline * * @return the number of points */ size_t PointCount(); /** * Get the count of contours in this outline * * @return the number of contours */ size_t ContourCount() const { return ftContourCount; } /** * Return a contour at index * * @return the number of contours */ const FTContour* /*const*/ Contour(size_t index) const; /** * Get the number of points in a specific contour in this outline * * @param c The contour index * @return the number of points in contour[c] */ size_t ContourSize(int c) const { return contourList[c]->PointCount(); } /** * Get the flag for the tesselation rule for this outline * * @return The contour flag */ int ContourFlag() const { return contourFlag; } private: /** * Process the freetype outline data into contours of points * * @param front front outset distance * @param back back outset distance */ void ProcessContours(); /** * The list of contours in the glyph */ FTContour** contourList; /** * A Mesh for tesselations */ FTMesh* mesh; /** * The number of contours reported by Freetype */ short ftContourCount; /** * A flag indicating the tesselation rule for the glyph */ int contourFlag; /** * A Freetype outline */ FT_Outline outline; }; #endif // __FTVectoriser__ workbench-1.1.1/src/FtglFont/FtglConfig.h000066400000000000000000000046301255417355300202160ustar00rootroot00000000000000/* config.h.in. Generated from configure.ac by autoheader. */ /* Define to the path to a TrueType font */ #define FONT_FILE /usr/X11R6/share/fonts/TTF/VeraSe.ttf /* Define to 1 if you have the header file. */ #undef HAVE_DLFCN_H /* Define to 1 if you have the header file. */ #undef HAVE_GLUT_GLUT_H /* Define to 1 if you have the header file. */ #undef HAVE_GL_GLUT_H /* Define to 1 if you have the header file. */ #define HAVE_INTTYPES_H 1 /* Define to 1 if you have the header file. */ #define HAVE_MEMORY_H 1 /* Define to 1 if you have the header file. */ #define HAVE_STDINT_H 1 /* Define to 1 if you have the header file. */ #define HAVE_STDLIB_H 1 /* Define to 1 if you have the header file. */ #define HAVE_STRINGS_H 1 /* Define to 1 if you have the header file. */ #define HAVE_STRING_H 1 /* Define to 1 if you have the `strndup' function. */ #define HAVE_STRNDUP 1 /* not on mac (hippocampus) */ #undef HAVE_STRNDUP /* Define to 1 if you have the header file. */ #undef HAVE_SYS_STAT_H /* Define to 1 if you have the header file. */ #define HAVE_SYS_TYPES_H 1 /* Define to 1 if you have the header file. */ #undef HAVE_UNISTD_H /* Define to 1 if you have the `wcsdup' function. */ #undef HAVE_WCSDUP /* Define to 1 if your C compiler doesn't accept -c and -o together. */ #undef NO_MINUS_C_MINUS_O /* Define to the address where bug reports for this package should be sent. */ #undef PACKAGE_BUGREPORT /* Define to the full name of this package. */ #undef PACKAGE_NAME /* Define to the full name and version of this package. */ #undef PACKAGE_STRING /* Define to the one symbol short name of this package. */ #undef PACKAGE_TARNAME /* Define to the version of this package. */ #undef PACKAGE_VERSION /* Define to 1 if you have the ANSI C header files. */ #define STDC_HEADERS 1 /* Define to 1 if the X Window System is missing or not being used. */ #define X_DISPLAY_MISSING 1 /* FOR WINDOWS, otherwise __declspec(dllimport) linking errors */ #define FTGL_LIBRARY_STATIC 1 workbench-1.1.1/src/FtglFont/Makefile.am000066400000000000000000000053401255417355300200560ustar00rootroot00000000000000 lib_LTLIBRARIES = libftgl.la libftgl_la_SOURCES = \ FTBuffer.cpp \ FTCharmap.cpp \ FTCharmap.h \ FTCharToGlyphIndexMap.h \ FTContour.cpp \ FTContour.h \ FTFace.cpp \ FTFace.h \ FTGlyphContainer.cpp \ FTGlyphContainer.h \ FTInternals.h \ FTLibrary.cpp \ FTLibrary.h \ FTList.h \ FTPoint.cpp \ FTSize.cpp \ FTSize.h \ FTVector.h \ FTVectoriser.cpp \ FTVectoriser.h \ FTUnicode.h \ $(ftglyph_sources) \ $(ftfont_sources) \ $(ftlayout_sources) \ $(ftgl_headers) \ $(NULL) libftgl_la_CPPFLAGS = -IFTGlyph -IFTFont -IFTLayout libftgl_la_CXXFLAGS = $(FT2_CFLAGS) $(GL_CFLAGS) libftgl_la_LDFLAGS = \ -no-undefined -version-number $(LT_VERSION) libftgl_la_LIBADD = \ $(FT2_LIBS) $(GL_LIBS) ftgldir = $(includedir)/FTGL ftgl_HEADERS = $(ftgl_headers) ftgl_headers = \ FTGL/ftgl.h \ FTGL/FTBBox.h \ FTGL/FTBuffer.h \ FTGL/FTPoint.h \ FTGL/FTGlyph.h \ FTGL/FTBitmapGlyph.h \ FTGL/FTBufferGlyph.h \ FTGL/FTExtrdGlyph.h \ FTGL/FTOutlineGlyph.h \ FTGL/FTPixmapGlyph.h \ FTGL/FTPolyGlyph.h \ FTGL/FTTextureGlyph.h \ FTGL/FTFont.h \ FTGL/FTGLBitmapFont.h \ FTGL/FTBufferFont.h \ FTGL/FTGLExtrdFont.h \ FTGL/FTGLOutlineFont.h \ FTGL/FTGLPixmapFont.h \ FTGL/FTGLPolygonFont.h \ FTGL/FTGLTextureFont.h \ FTGL/FTLayout.h \ FTGL/FTSimpleLayout.h \ ${NULL} ftglyph_sources = \ FTGlyph/FTGlyph.cpp \ FTGlyph/FTGlyphImpl.h \ FTGlyph/FTGlyphGlue.cpp \ FTGlyph/FTBitmapGlyph.cpp \ FTGlyph/FTBitmapGlyphImpl.h \ FTGlyph/FTBufferGlyph.cpp \ FTGlyph/FTBufferGlyphImpl.h \ FTGlyph/FTExtrudeGlyph.cpp \ FTGlyph/FTExtrudeGlyphImpl.h \ FTGlyph/FTOutlineGlyph.cpp \ FTGlyph/FTOutlineGlyphImpl.h \ FTGlyph/FTPixmapGlyph.cpp \ FTGlyph/FTPixmapGlyphImpl.h \ FTGlyph/FTPolygonGlyph.cpp \ FTGlyph/FTPolygonGlyphImpl.h \ FTGlyph/FTTextureGlyph.cpp \ FTGlyph/FTTextureGlyphImpl.h \ $(NULL) ftfont_sources = \ FTFont/FTFont.cpp \ FTFont/FTFontImpl.h \ FTFont/FTFontGlue.cpp \ FTFont/FTBitmapFont.cpp \ FTFont/FTBitmapFontImpl.h \ FTFont/FTBufferFont.cpp \ FTFont/FTBufferFontImpl.h \ FTFont/FTExtrudeFont.cpp \ FTFont/FTExtrudeFontImpl.h \ FTFont/FTOutlineFont.cpp \ FTFont/FTOutlineFontImpl.h \ FTFont/FTPixmapFont.cpp \ FTFont/FTPixmapFontImpl.h \ FTFont/FTPolygonFont.cpp \ FTFont/FTPolygonFontImpl.h \ FTFont/FTTextureFont.cpp \ FTFont/FTTextureFontImpl.h \ $(NULL) ftlayout_sources = \ FTLayout/FTLayout.cpp \ FTLayout/FTLayoutImpl.h \ FTLayout/FTLayoutGlue.cpp \ FTLayout/FTSimpleLayout.cpp \ FTLayout/FTSimpleLayoutImpl.h \ $(NULL) NULL = workbench-1.1.1/src/FtglFont/Makefile.in000066400000000000000000001550131255417355300200720ustar00rootroot00000000000000# Makefile.in generated by automake 1.10.1 from Makefile.am. # @configure_input@ # Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, # 2003, 2004, 2005, 2006, 2007, 2008 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY, to the extent permitted by law; without # even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. @SET_MAKE@ VPATH = @srcdir@ pkgdatadir = $(datadir)/@PACKAGE@ pkglibdir = $(libdir)/@PACKAGE@ pkgincludedir = $(includedir)/@PACKAGE@ am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd install_sh_DATA = $(install_sh) -c -m 644 install_sh_PROGRAM = $(install_sh) -c install_sh_SCRIPT = $(install_sh) -c INSTALL_HEADER = $(INSTALL_DATA) transform = $(program_transform_name) NORMAL_INSTALL = : PRE_INSTALL = : POST_INSTALL = : NORMAL_UNINSTALL = : PRE_UNINSTALL = : POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ subdir = src DIST_COMMON = $(ftgl_HEADERS) $(srcdir)/Makefile.am \ $(srcdir)/Makefile.in ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/m4/cxx.m4 $(top_srcdir)/m4/font.m4 \ $(top_srcdir)/m4/freetype2.m4 $(top_srcdir)/m4/gl.m4 \ $(top_srcdir)/m4/glut.m4 $(top_srcdir)/m4/pkg.m4 \ $(top_srcdir)/configure.ac am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) mkinstalldirs = $(install_sh) -d CONFIG_HEADER = $(top_builddir)/config.h CONFIG_CLEAN_FILES = am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; am__vpath_adj = case $$p in \ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \ *) f=$$p;; \ esac; am__strip_dir = `echo $$p | sed -e 's|^.*/||'`; am__installdirs = "$(DESTDIR)$(libdir)" "$(DESTDIR)$(ftgldir)" libLTLIBRARIES_INSTALL = $(INSTALL) LTLIBRARIES = $(lib_LTLIBRARIES) am__DEPENDENCIES_1 = libftgl_la_DEPENDENCIES = $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) am__objects_1 = am__objects_2 = libftgl_la-FTGlyph.lo libftgl_la-FTGlyphGlue.lo \ libftgl_la-FTBitmapGlyph.lo libftgl_la-FTBufferGlyph.lo \ libftgl_la-FTExtrudeGlyph.lo libftgl_la-FTOutlineGlyph.lo \ libftgl_la-FTPixmapGlyph.lo libftgl_la-FTPolygonGlyph.lo \ libftgl_la-FTTextureGlyph.lo $(am__objects_1) am__objects_3 = libftgl_la-FTFont.lo libftgl_la-FTFontGlue.lo \ libftgl_la-FTBitmapFont.lo libftgl_la-FTBufferFont.lo \ libftgl_la-FTExtrudeFont.lo libftgl_la-FTOutlineFont.lo \ libftgl_la-FTPixmapFont.lo libftgl_la-FTPolygonFont.lo \ libftgl_la-FTTextureFont.lo $(am__objects_1) am__objects_4 = libftgl_la-FTLayout.lo libftgl_la-FTLayoutGlue.lo \ libftgl_la-FTSimpleLayout.lo $(am__objects_1) am__objects_5 = $(am__objects_1) am_libftgl_la_OBJECTS = libftgl_la-FTBuffer.lo libftgl_la-FTCharmap.lo \ libftgl_la-FTContour.lo libftgl_la-FTFace.lo \ libftgl_la-FTGlyphContainer.lo libftgl_la-FTLibrary.lo \ libftgl_la-FTPoint.lo libftgl_la-FTSize.lo \ libftgl_la-FTVectoriser.lo $(am__objects_2) $(am__objects_3) \ $(am__objects_4) $(am__objects_5) $(am__objects_1) libftgl_la_OBJECTS = $(am_libftgl_la_OBJECTS) libftgl_la_LINK = $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CXXLD) $(libftgl_la_CXXFLAGS) \ $(CXXFLAGS) $(libftgl_la_LDFLAGS) $(LDFLAGS) -o $@ DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) depcomp = $(SHELL) $(top_srcdir)/.auto/depcomp am__depfiles_maybe = depfiles CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) LTCXXCOMPILE = $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) CXXLD = $(CXX) CXXLINK = $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ --mode=link $(CXXLD) $(AM_CXXFLAGS) $(CXXFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) LTCOMPILE = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) CCLD = $(CC) LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ SOURCES = $(libftgl_la_SOURCES) DIST_SOURCES = $(libftgl_la_SOURCES) ftglHEADERS_INSTALL = $(INSTALL_HEADER) HEADERS = $(ftgl_HEADERS) ETAGS = etags CTAGS = ctags DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) ACLOCAL = @ACLOCAL@ AMTAR = @AMTAR@ AR = @AR@ AUTOCONF = @AUTOCONF@ AUTOHEADER = @AUTOHEADER@ AUTOMAKE = @AUTOMAKE@ AWK = @AWK@ CC = @CC@ CCDEPMODE = @CCDEPMODE@ CFLAGS = @CFLAGS@ CONVERT = @CONVERT@ CPP = @CPP@ CPPFLAGS = @CPPFLAGS@ CPPUNIT_CFLAGS = @CPPUNIT_CFLAGS@ CPPUNIT_LIBS = @CPPUNIT_LIBS@ CXX = @CXX@ CXXCPP = @CXXCPP@ CXXDEPMODE = @CXXDEPMODE@ CXXFLAGS = @CXXFLAGS@ CYGPATH_W = @CYGPATH_W@ DEFS = @DEFS@ DEPDIR = @DEPDIR@ DOXYGEN = @DOXYGEN@ DSYMUTIL = @DSYMUTIL@ DVIPS = @DVIPS@ ECHO = @ECHO@ ECHO_C = @ECHO_C@ ECHO_N = @ECHO_N@ ECHO_T = @ECHO_T@ EGREP = @EGREP@ EPSTOPDF = @EPSTOPDF@ EXEEXT = @EXEEXT@ F77 = @F77@ FFLAGS = @FFLAGS@ FRAMEWORK_OPENGL = @FRAMEWORK_OPENGL@ FT2_CFLAGS = @FT2_CFLAGS@ FT2_CONFIG = @FT2_CONFIG@ FT2_LIBS = @FT2_LIBS@ GLUT_CFLAGS = @GLUT_CFLAGS@ GLUT_LIBS = @GLUT_LIBS@ GL_CFLAGS = @GL_CFLAGS@ GL_LIBS = @GL_LIBS@ GREP = @GREP@ INSTALL = @INSTALL@ INSTALL_DATA = @INSTALL_DATA@ INSTALL_PROGRAM = @INSTALL_PROGRAM@ INSTALL_SCRIPT = @INSTALL_SCRIPT@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ KPSEWHICH = @KPSEWHICH@ LATEX = @LATEX@ LDFLAGS = @LDFLAGS@ LIBOBJS = @LIBOBJS@ LIBS = @LIBS@ LIBTOOL = @LIBTOOL@ LN_S = @LN_S@ LTLIBOBJS = @LTLIBOBJS@ LT_MAJOR = @LT_MAJOR@ LT_MICRO = @LT_MICRO@ LT_MINOR = @LT_MINOR@ LT_VERSION = @LT_VERSION@ MAKEINFO = @MAKEINFO@ MKDIR_P = @MKDIR_P@ NMEDIT = @NMEDIT@ OBJEXT = @OBJEXT@ PACKAGE = @PACKAGE@ PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ PACKAGE_NAME = @PACKAGE_NAME@ PACKAGE_STRING = @PACKAGE_STRING@ PACKAGE_TARNAME = @PACKAGE_TARNAME@ PACKAGE_VERSION = @PACKAGE_VERSION@ PATH_SEPARATOR = @PATH_SEPARATOR@ PKG_CONFIG = @PKG_CONFIG@ RANLIB = @RANLIB@ SED = @SED@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ STRIP = @STRIP@ VERSION = @VERSION@ XMKMF = @XMKMF@ X_CFLAGS = @X_CFLAGS@ X_EXTRA_LIBS = @X_EXTRA_LIBS@ X_LIBS = @X_LIBS@ X_PRE_LIBS = @X_PRE_LIBS@ abs_builddir = @abs_builddir@ abs_srcdir = @abs_srcdir@ abs_top_builddir = @abs_top_builddir@ abs_top_srcdir = @abs_top_srcdir@ ac_ct_CC = @ac_ct_CC@ ac_ct_CXX = @ac_ct_CXX@ ac_ct_F77 = @ac_ct_F77@ am__include = @am__include@ am__leading_dot = @am__leading_dot@ am__quote = @am__quote@ am__tar = @am__tar@ am__untar = @am__untar@ bindir = @bindir@ build = @build@ build_alias = @build_alias@ build_cpu = @build_cpu@ build_os = @build_os@ build_vendor = @build_vendor@ builddir = @builddir@ datadir = @datadir@ datarootdir = @datarootdir@ docdir = @docdir@ dvidir = @dvidir@ exec_prefix = @exec_prefix@ host = @host@ host_alias = @host_alias@ host_cpu = @host_cpu@ host_os = @host_os@ host_vendor = @host_vendor@ htmldir = @htmldir@ includedir = @includedir@ infodir = @infodir@ install_sh = @install_sh@ libdir = @libdir@ libexecdir = @libexecdir@ localedir = @localedir@ localstatedir = @localstatedir@ mandir = @mandir@ mkdir_p = @mkdir_p@ oldincludedir = @oldincludedir@ pdfdir = @pdfdir@ prefix = @prefix@ program_transform_name = @program_transform_name@ psdir = @psdir@ sbindir = @sbindir@ sharedstatedir = @sharedstatedir@ srcdir = @srcdir@ sysconfdir = @sysconfdir@ target_alias = @target_alias@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ lib_LTLIBRARIES = libftgl.la libftgl_la_SOURCES = \ FTBuffer.cpp \ FTCharmap.cpp \ FTCharmap.h \ FTCharToGlyphIndexMap.h \ FTContour.cpp \ FTContour.h \ FTFace.cpp \ FTFace.h \ FTGlyphContainer.cpp \ FTGlyphContainer.h \ FTInternals.h \ FTLibrary.cpp \ FTLibrary.h \ FTList.h \ FTPoint.cpp \ FTSize.cpp \ FTSize.h \ FTVector.h \ FTVectoriser.cpp \ FTVectoriser.h \ FTUnicode.h \ $(ftglyph_sources) \ $(ftfont_sources) \ $(ftlayout_sources) \ $(ftgl_headers) \ $(NULL) libftgl_la_CPPFLAGS = -IFTGlyph -IFTFont -IFTLayout libftgl_la_CXXFLAGS = $(FT2_CFLAGS) $(GL_CFLAGS) libftgl_la_LDFLAGS = \ -no-undefined -version-number $(LT_VERSION) libftgl_la_LIBADD = \ $(FT2_LIBS) $(GL_LIBS) ftgldir = $(includedir)/FTGL ftgl_HEADERS = $(ftgl_headers) ftgl_headers = \ FTGL/ftgl.h \ FTGL/FTBBox.h \ FTGL/FTBuffer.h \ FTGL/FTPoint.h \ FTGL/FTGlyph.h \ FTGL/FTBitmapGlyph.h \ FTGL/FTBufferGlyph.h \ FTGL/FTExtrdGlyph.h \ FTGL/FTOutlineGlyph.h \ FTGL/FTPixmapGlyph.h \ FTGL/FTPolyGlyph.h \ FTGL/FTTextureGlyph.h \ FTGL/FTFont.h \ FTGL/FTGLBitmapFont.h \ FTGL/FTBufferFont.h \ FTGL/FTGLExtrdFont.h \ FTGL/FTGLOutlineFont.h \ FTGL/FTGLPixmapFont.h \ FTGL/FTGLPolygonFont.h \ FTGL/FTGLTextureFont.h \ FTGL/FTLayout.h \ FTGL/FTSimpleLayout.h \ ${NULL} ftglyph_sources = \ FTGlyph/FTGlyph.cpp \ FTGlyph/FTGlyphImpl.h \ FTGlyph/FTGlyphGlue.cpp \ FTGlyph/FTBitmapGlyph.cpp \ FTGlyph/FTBitmapGlyphImpl.h \ FTGlyph/FTBufferGlyph.cpp \ FTGlyph/FTBufferGlyphImpl.h \ FTGlyph/FTExtrudeGlyph.cpp \ FTGlyph/FTExtrudeGlyphImpl.h \ FTGlyph/FTOutlineGlyph.cpp \ FTGlyph/FTOutlineGlyphImpl.h \ FTGlyph/FTPixmapGlyph.cpp \ FTGlyph/FTPixmapGlyphImpl.h \ FTGlyph/FTPolygonGlyph.cpp \ FTGlyph/FTPolygonGlyphImpl.h \ FTGlyph/FTTextureGlyph.cpp \ FTGlyph/FTTextureGlyphImpl.h \ $(NULL) ftfont_sources = \ FTFont/FTFont.cpp \ FTFont/FTFontImpl.h \ FTFont/FTFontGlue.cpp \ FTFont/FTBitmapFont.cpp \ FTFont/FTBitmapFontImpl.h \ FTFont/FTBufferFont.cpp \ FTFont/FTBufferFontImpl.h \ FTFont/FTExtrudeFont.cpp \ FTFont/FTExtrudeFontImpl.h \ FTFont/FTOutlineFont.cpp \ FTFont/FTOutlineFontImpl.h \ FTFont/FTPixmapFont.cpp \ FTFont/FTPixmapFontImpl.h \ FTFont/FTPolygonFont.cpp \ FTFont/FTPolygonFontImpl.h \ FTFont/FTTextureFont.cpp \ FTFont/FTTextureFontImpl.h \ $(NULL) ftlayout_sources = \ FTLayout/FTLayout.cpp \ FTLayout/FTLayoutImpl.h \ FTLayout/FTLayoutGlue.cpp \ FTLayout/FTSimpleLayout.cpp \ FTLayout/FTSimpleLayoutImpl.h \ $(NULL) NULL = all: all-am .SUFFIXES: .SUFFIXES: .cpp .lo .o .obj $(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) @for dep in $?; do \ case '$(am__configure_deps)' in \ *$$dep*) \ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh \ && exit 0; \ exit 1;; \ esac; \ done; \ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu src/Makefile'; \ cd $(top_srcdir) && \ $(AUTOMAKE) --gnu src/Makefile .PRECIOUS: Makefile Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status @case '$?' in \ *config.status*) \ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ *) \ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ esac; $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(top_srcdir)/configure: $(am__configure_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh $(ACLOCAL_M4): $(am__aclocal_m4_deps) cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh install-libLTLIBRARIES: $(lib_LTLIBRARIES) @$(NORMAL_INSTALL) test -z "$(libdir)" || $(MKDIR_P) "$(DESTDIR)$(libdir)" @list='$(lib_LTLIBRARIES)'; for p in $$list; do \ if test -f $$p; then \ f=$(am__strip_dir) \ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(libLTLIBRARIES_INSTALL) $(INSTALL_STRIP_FLAG) '$$p' '$(DESTDIR)$(libdir)/$$f'"; \ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(libLTLIBRARIES_INSTALL) $(INSTALL_STRIP_FLAG) "$$p" "$(DESTDIR)$(libdir)/$$f"; \ else :; fi; \ done uninstall-libLTLIBRARIES: @$(NORMAL_UNINSTALL) @list='$(lib_LTLIBRARIES)'; for p in $$list; do \ p=$(am__strip_dir) \ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(libdir)/$$p'"; \ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(libdir)/$$p"; \ done clean-libLTLIBRARIES: -test -z "$(lib_LTLIBRARIES)" || rm -f $(lib_LTLIBRARIES) @list='$(lib_LTLIBRARIES)'; for p in $$list; do \ dir="`echo $$p | sed -e 's|/[^/]*$$||'`"; \ test "$$dir" != "$$p" || dir=.; \ echo "rm -f \"$${dir}/so_locations\""; \ rm -f "$${dir}/so_locations"; \ done libftgl.la: $(libftgl_la_OBJECTS) $(libftgl_la_DEPENDENCIES) $(libftgl_la_LINK) -rpath $(libdir) $(libftgl_la_OBJECTS) $(libftgl_la_LIBADD) $(LIBS) mostlyclean-compile: -rm -f *.$(OBJEXT) distclean-compile: -rm -f *.tab.c @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libftgl_la-FTBitmapFont.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libftgl_la-FTBitmapGlyph.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libftgl_la-FTBuffer.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libftgl_la-FTBufferFont.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libftgl_la-FTBufferGlyph.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libftgl_la-FTCharmap.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libftgl_la-FTContour.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libftgl_la-FTExtrudeFont.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libftgl_la-FTExtrudeGlyph.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libftgl_la-FTFace.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libftgl_la-FTFont.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libftgl_la-FTFontGlue.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libftgl_la-FTGlyph.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libftgl_la-FTGlyphContainer.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libftgl_la-FTGlyphGlue.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libftgl_la-FTLayout.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libftgl_la-FTLayoutGlue.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libftgl_la-FTLibrary.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libftgl_la-FTOutlineFont.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libftgl_la-FTOutlineGlyph.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libftgl_la-FTPixmapFont.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libftgl_la-FTPixmapGlyph.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libftgl_la-FTPoint.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libftgl_la-FTPolygonFont.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libftgl_la-FTPolygonGlyph.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libftgl_la-FTSimpleLayout.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libftgl_la-FTSize.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libftgl_la-FTTextureFont.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libftgl_la-FTTextureGlyph.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libftgl_la-FTVectoriser.Plo@am__quote@ .cpp.o: @am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXXCOMPILE) -c -o $@ $< .cpp.obj: @am__fastdepCXX_TRUE@ $(CXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ `$(CYGPATH_W) '$<'` @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Po @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` .cpp.lo: @am__fastdepCXX_TRUE@ $(LTCXXCOMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/$*.Tpo $(DEPDIR)/$*.Plo @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(LTCXXCOMPILE) -c -o $@ $< libftgl_la-FTBuffer.lo: FTBuffer.cpp @am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -MT libftgl_la-FTBuffer.lo -MD -MP -MF $(DEPDIR)/libftgl_la-FTBuffer.Tpo -c -o libftgl_la-FTBuffer.lo `test -f 'FTBuffer.cpp' || echo '$(srcdir)/'`FTBuffer.cpp @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libftgl_la-FTBuffer.Tpo $(DEPDIR)/libftgl_la-FTBuffer.Plo @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='FTBuffer.cpp' object='libftgl_la-FTBuffer.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -c -o libftgl_la-FTBuffer.lo `test -f 'FTBuffer.cpp' || echo '$(srcdir)/'`FTBuffer.cpp libftgl_la-FTCharmap.lo: FTCharmap.cpp @am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -MT libftgl_la-FTCharmap.lo -MD -MP -MF $(DEPDIR)/libftgl_la-FTCharmap.Tpo -c -o libftgl_la-FTCharmap.lo `test -f 'FTCharmap.cpp' || echo '$(srcdir)/'`FTCharmap.cpp @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libftgl_la-FTCharmap.Tpo $(DEPDIR)/libftgl_la-FTCharmap.Plo @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='FTCharmap.cpp' object='libftgl_la-FTCharmap.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -c -o libftgl_la-FTCharmap.lo `test -f 'FTCharmap.cpp' || echo '$(srcdir)/'`FTCharmap.cpp libftgl_la-FTContour.lo: FTContour.cpp @am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -MT libftgl_la-FTContour.lo -MD -MP -MF $(DEPDIR)/libftgl_la-FTContour.Tpo -c -o libftgl_la-FTContour.lo `test -f 'FTContour.cpp' || echo '$(srcdir)/'`FTContour.cpp @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libftgl_la-FTContour.Tpo $(DEPDIR)/libftgl_la-FTContour.Plo @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='FTContour.cpp' object='libftgl_la-FTContour.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -c -o libftgl_la-FTContour.lo `test -f 'FTContour.cpp' || echo '$(srcdir)/'`FTContour.cpp libftgl_la-FTFace.lo: FTFace.cpp @am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -MT libftgl_la-FTFace.lo -MD -MP -MF $(DEPDIR)/libftgl_la-FTFace.Tpo -c -o libftgl_la-FTFace.lo `test -f 'FTFace.cpp' || echo '$(srcdir)/'`FTFace.cpp @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libftgl_la-FTFace.Tpo $(DEPDIR)/libftgl_la-FTFace.Plo @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='FTFace.cpp' object='libftgl_la-FTFace.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -c -o libftgl_la-FTFace.lo `test -f 'FTFace.cpp' || echo '$(srcdir)/'`FTFace.cpp libftgl_la-FTGlyphContainer.lo: FTGlyphContainer.cpp @am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -MT libftgl_la-FTGlyphContainer.lo -MD -MP -MF $(DEPDIR)/libftgl_la-FTGlyphContainer.Tpo -c -o libftgl_la-FTGlyphContainer.lo `test -f 'FTGlyphContainer.cpp' || echo '$(srcdir)/'`FTGlyphContainer.cpp @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libftgl_la-FTGlyphContainer.Tpo $(DEPDIR)/libftgl_la-FTGlyphContainer.Plo @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='FTGlyphContainer.cpp' object='libftgl_la-FTGlyphContainer.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -c -o libftgl_la-FTGlyphContainer.lo `test -f 'FTGlyphContainer.cpp' || echo '$(srcdir)/'`FTGlyphContainer.cpp libftgl_la-FTLibrary.lo: FTLibrary.cpp @am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -MT libftgl_la-FTLibrary.lo -MD -MP -MF $(DEPDIR)/libftgl_la-FTLibrary.Tpo -c -o libftgl_la-FTLibrary.lo `test -f 'FTLibrary.cpp' || echo '$(srcdir)/'`FTLibrary.cpp @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libftgl_la-FTLibrary.Tpo $(DEPDIR)/libftgl_la-FTLibrary.Plo @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='FTLibrary.cpp' object='libftgl_la-FTLibrary.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -c -o libftgl_la-FTLibrary.lo `test -f 'FTLibrary.cpp' || echo '$(srcdir)/'`FTLibrary.cpp libftgl_la-FTPoint.lo: FTPoint.cpp @am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -MT libftgl_la-FTPoint.lo -MD -MP -MF $(DEPDIR)/libftgl_la-FTPoint.Tpo -c -o libftgl_la-FTPoint.lo `test -f 'FTPoint.cpp' || echo '$(srcdir)/'`FTPoint.cpp @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libftgl_la-FTPoint.Tpo $(DEPDIR)/libftgl_la-FTPoint.Plo @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='FTPoint.cpp' object='libftgl_la-FTPoint.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -c -o libftgl_la-FTPoint.lo `test -f 'FTPoint.cpp' || echo '$(srcdir)/'`FTPoint.cpp libftgl_la-FTSize.lo: FTSize.cpp @am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -MT libftgl_la-FTSize.lo -MD -MP -MF $(DEPDIR)/libftgl_la-FTSize.Tpo -c -o libftgl_la-FTSize.lo `test -f 'FTSize.cpp' || echo '$(srcdir)/'`FTSize.cpp @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libftgl_la-FTSize.Tpo $(DEPDIR)/libftgl_la-FTSize.Plo @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='FTSize.cpp' object='libftgl_la-FTSize.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -c -o libftgl_la-FTSize.lo `test -f 'FTSize.cpp' || echo '$(srcdir)/'`FTSize.cpp libftgl_la-FTVectoriser.lo: FTVectoriser.cpp @am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -MT libftgl_la-FTVectoriser.lo -MD -MP -MF $(DEPDIR)/libftgl_la-FTVectoriser.Tpo -c -o libftgl_la-FTVectoriser.lo `test -f 'FTVectoriser.cpp' || echo '$(srcdir)/'`FTVectoriser.cpp @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libftgl_la-FTVectoriser.Tpo $(DEPDIR)/libftgl_la-FTVectoriser.Plo @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='FTVectoriser.cpp' object='libftgl_la-FTVectoriser.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -c -o libftgl_la-FTVectoriser.lo `test -f 'FTVectoriser.cpp' || echo '$(srcdir)/'`FTVectoriser.cpp libftgl_la-FTGlyph.lo: FTGlyph/FTGlyph.cpp @am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -MT libftgl_la-FTGlyph.lo -MD -MP -MF $(DEPDIR)/libftgl_la-FTGlyph.Tpo -c -o libftgl_la-FTGlyph.lo `test -f 'FTGlyph/FTGlyph.cpp' || echo '$(srcdir)/'`FTGlyph/FTGlyph.cpp @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libftgl_la-FTGlyph.Tpo $(DEPDIR)/libftgl_la-FTGlyph.Plo @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='FTGlyph/FTGlyph.cpp' object='libftgl_la-FTGlyph.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -c -o libftgl_la-FTGlyph.lo `test -f 'FTGlyph/FTGlyph.cpp' || echo '$(srcdir)/'`FTGlyph/FTGlyph.cpp libftgl_la-FTGlyphGlue.lo: FTGlyph/FTGlyphGlue.cpp @am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -MT libftgl_la-FTGlyphGlue.lo -MD -MP -MF $(DEPDIR)/libftgl_la-FTGlyphGlue.Tpo -c -o libftgl_la-FTGlyphGlue.lo `test -f 'FTGlyph/FTGlyphGlue.cpp' || echo '$(srcdir)/'`FTGlyph/FTGlyphGlue.cpp @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libftgl_la-FTGlyphGlue.Tpo $(DEPDIR)/libftgl_la-FTGlyphGlue.Plo @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='FTGlyph/FTGlyphGlue.cpp' object='libftgl_la-FTGlyphGlue.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -c -o libftgl_la-FTGlyphGlue.lo `test -f 'FTGlyph/FTGlyphGlue.cpp' || echo '$(srcdir)/'`FTGlyph/FTGlyphGlue.cpp libftgl_la-FTBitmapGlyph.lo: FTGlyph/FTBitmapGlyph.cpp @am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -MT libftgl_la-FTBitmapGlyph.lo -MD -MP -MF $(DEPDIR)/libftgl_la-FTBitmapGlyph.Tpo -c -o libftgl_la-FTBitmapGlyph.lo `test -f 'FTGlyph/FTBitmapGlyph.cpp' || echo '$(srcdir)/'`FTGlyph/FTBitmapGlyph.cpp @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libftgl_la-FTBitmapGlyph.Tpo $(DEPDIR)/libftgl_la-FTBitmapGlyph.Plo @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='FTGlyph/FTBitmapGlyph.cpp' object='libftgl_la-FTBitmapGlyph.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -c -o libftgl_la-FTBitmapGlyph.lo `test -f 'FTGlyph/FTBitmapGlyph.cpp' || echo '$(srcdir)/'`FTGlyph/FTBitmapGlyph.cpp libftgl_la-FTBufferGlyph.lo: FTGlyph/FTBufferGlyph.cpp @am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -MT libftgl_la-FTBufferGlyph.lo -MD -MP -MF $(DEPDIR)/libftgl_la-FTBufferGlyph.Tpo -c -o libftgl_la-FTBufferGlyph.lo `test -f 'FTGlyph/FTBufferGlyph.cpp' || echo '$(srcdir)/'`FTGlyph/FTBufferGlyph.cpp @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libftgl_la-FTBufferGlyph.Tpo $(DEPDIR)/libftgl_la-FTBufferGlyph.Plo @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='FTGlyph/FTBufferGlyph.cpp' object='libftgl_la-FTBufferGlyph.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -c -o libftgl_la-FTBufferGlyph.lo `test -f 'FTGlyph/FTBufferGlyph.cpp' || echo '$(srcdir)/'`FTGlyph/FTBufferGlyph.cpp libftgl_la-FTExtrudeGlyph.lo: FTGlyph/FTExtrudeGlyph.cpp @am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -MT libftgl_la-FTExtrudeGlyph.lo -MD -MP -MF $(DEPDIR)/libftgl_la-FTExtrudeGlyph.Tpo -c -o libftgl_la-FTExtrudeGlyph.lo `test -f 'FTGlyph/FTExtrudeGlyph.cpp' || echo '$(srcdir)/'`FTGlyph/FTExtrudeGlyph.cpp @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libftgl_la-FTExtrudeGlyph.Tpo $(DEPDIR)/libftgl_la-FTExtrudeGlyph.Plo @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='FTGlyph/FTExtrudeGlyph.cpp' object='libftgl_la-FTExtrudeGlyph.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -c -o libftgl_la-FTExtrudeGlyph.lo `test -f 'FTGlyph/FTExtrudeGlyph.cpp' || echo '$(srcdir)/'`FTGlyph/FTExtrudeGlyph.cpp libftgl_la-FTOutlineGlyph.lo: FTGlyph/FTOutlineGlyph.cpp @am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -MT libftgl_la-FTOutlineGlyph.lo -MD -MP -MF $(DEPDIR)/libftgl_la-FTOutlineGlyph.Tpo -c -o libftgl_la-FTOutlineGlyph.lo `test -f 'FTGlyph/FTOutlineGlyph.cpp' || echo '$(srcdir)/'`FTGlyph/FTOutlineGlyph.cpp @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libftgl_la-FTOutlineGlyph.Tpo $(DEPDIR)/libftgl_la-FTOutlineGlyph.Plo @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='FTGlyph/FTOutlineGlyph.cpp' object='libftgl_la-FTOutlineGlyph.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -c -o libftgl_la-FTOutlineGlyph.lo `test -f 'FTGlyph/FTOutlineGlyph.cpp' || echo '$(srcdir)/'`FTGlyph/FTOutlineGlyph.cpp libftgl_la-FTPixmapGlyph.lo: FTGlyph/FTPixmapGlyph.cpp @am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -MT libftgl_la-FTPixmapGlyph.lo -MD -MP -MF $(DEPDIR)/libftgl_la-FTPixmapGlyph.Tpo -c -o libftgl_la-FTPixmapGlyph.lo `test -f 'FTGlyph/FTPixmapGlyph.cpp' || echo '$(srcdir)/'`FTGlyph/FTPixmapGlyph.cpp @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libftgl_la-FTPixmapGlyph.Tpo $(DEPDIR)/libftgl_la-FTPixmapGlyph.Plo @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='FTGlyph/FTPixmapGlyph.cpp' object='libftgl_la-FTPixmapGlyph.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -c -o libftgl_la-FTPixmapGlyph.lo `test -f 'FTGlyph/FTPixmapGlyph.cpp' || echo '$(srcdir)/'`FTGlyph/FTPixmapGlyph.cpp libftgl_la-FTPolygonGlyph.lo: FTGlyph/FTPolygonGlyph.cpp @am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -MT libftgl_la-FTPolygonGlyph.lo -MD -MP -MF $(DEPDIR)/libftgl_la-FTPolygonGlyph.Tpo -c -o libftgl_la-FTPolygonGlyph.lo `test -f 'FTGlyph/FTPolygonGlyph.cpp' || echo '$(srcdir)/'`FTGlyph/FTPolygonGlyph.cpp @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libftgl_la-FTPolygonGlyph.Tpo $(DEPDIR)/libftgl_la-FTPolygonGlyph.Plo @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='FTGlyph/FTPolygonGlyph.cpp' object='libftgl_la-FTPolygonGlyph.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -c -o libftgl_la-FTPolygonGlyph.lo `test -f 'FTGlyph/FTPolygonGlyph.cpp' || echo '$(srcdir)/'`FTGlyph/FTPolygonGlyph.cpp libftgl_la-FTTextureGlyph.lo: FTGlyph/FTTextureGlyph.cpp @am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -MT libftgl_la-FTTextureGlyph.lo -MD -MP -MF $(DEPDIR)/libftgl_la-FTTextureGlyph.Tpo -c -o libftgl_la-FTTextureGlyph.lo `test -f 'FTGlyph/FTTextureGlyph.cpp' || echo '$(srcdir)/'`FTGlyph/FTTextureGlyph.cpp @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libftgl_la-FTTextureGlyph.Tpo $(DEPDIR)/libftgl_la-FTTextureGlyph.Plo @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='FTGlyph/FTTextureGlyph.cpp' object='libftgl_la-FTTextureGlyph.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -c -o libftgl_la-FTTextureGlyph.lo `test -f 'FTGlyph/FTTextureGlyph.cpp' || echo '$(srcdir)/'`FTGlyph/FTTextureGlyph.cpp libftgl_la-FTFont.lo: FTFont/FTFont.cpp @am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -MT libftgl_la-FTFont.lo -MD -MP -MF $(DEPDIR)/libftgl_la-FTFont.Tpo -c -o libftgl_la-FTFont.lo `test -f 'FTFont/FTFont.cpp' || echo '$(srcdir)/'`FTFont/FTFont.cpp @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libftgl_la-FTFont.Tpo $(DEPDIR)/libftgl_la-FTFont.Plo @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='FTFont/FTFont.cpp' object='libftgl_la-FTFont.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -c -o libftgl_la-FTFont.lo `test -f 'FTFont/FTFont.cpp' || echo '$(srcdir)/'`FTFont/FTFont.cpp libftgl_la-FTFontGlue.lo: FTFont/FTFontGlue.cpp @am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -MT libftgl_la-FTFontGlue.lo -MD -MP -MF $(DEPDIR)/libftgl_la-FTFontGlue.Tpo -c -o libftgl_la-FTFontGlue.lo `test -f 'FTFont/FTFontGlue.cpp' || echo '$(srcdir)/'`FTFont/FTFontGlue.cpp @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libftgl_la-FTFontGlue.Tpo $(DEPDIR)/libftgl_la-FTFontGlue.Plo @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='FTFont/FTFontGlue.cpp' object='libftgl_la-FTFontGlue.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -c -o libftgl_la-FTFontGlue.lo `test -f 'FTFont/FTFontGlue.cpp' || echo '$(srcdir)/'`FTFont/FTFontGlue.cpp libftgl_la-FTBitmapFont.lo: FTFont/FTBitmapFont.cpp @am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -MT libftgl_la-FTBitmapFont.lo -MD -MP -MF $(DEPDIR)/libftgl_la-FTBitmapFont.Tpo -c -o libftgl_la-FTBitmapFont.lo `test -f 'FTFont/FTBitmapFont.cpp' || echo '$(srcdir)/'`FTFont/FTBitmapFont.cpp @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libftgl_la-FTBitmapFont.Tpo $(DEPDIR)/libftgl_la-FTBitmapFont.Plo @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='FTFont/FTBitmapFont.cpp' object='libftgl_la-FTBitmapFont.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -c -o libftgl_la-FTBitmapFont.lo `test -f 'FTFont/FTBitmapFont.cpp' || echo '$(srcdir)/'`FTFont/FTBitmapFont.cpp libftgl_la-FTBufferFont.lo: FTFont/FTBufferFont.cpp @am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -MT libftgl_la-FTBufferFont.lo -MD -MP -MF $(DEPDIR)/libftgl_la-FTBufferFont.Tpo -c -o libftgl_la-FTBufferFont.lo `test -f 'FTFont/FTBufferFont.cpp' || echo '$(srcdir)/'`FTFont/FTBufferFont.cpp @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libftgl_la-FTBufferFont.Tpo $(DEPDIR)/libftgl_la-FTBufferFont.Plo @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='FTFont/FTBufferFont.cpp' object='libftgl_la-FTBufferFont.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -c -o libftgl_la-FTBufferFont.lo `test -f 'FTFont/FTBufferFont.cpp' || echo '$(srcdir)/'`FTFont/FTBufferFont.cpp libftgl_la-FTExtrudeFont.lo: FTFont/FTExtrudeFont.cpp @am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -MT libftgl_la-FTExtrudeFont.lo -MD -MP -MF $(DEPDIR)/libftgl_la-FTExtrudeFont.Tpo -c -o libftgl_la-FTExtrudeFont.lo `test -f 'FTFont/FTExtrudeFont.cpp' || echo '$(srcdir)/'`FTFont/FTExtrudeFont.cpp @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libftgl_la-FTExtrudeFont.Tpo $(DEPDIR)/libftgl_la-FTExtrudeFont.Plo @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='FTFont/FTExtrudeFont.cpp' object='libftgl_la-FTExtrudeFont.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -c -o libftgl_la-FTExtrudeFont.lo `test -f 'FTFont/FTExtrudeFont.cpp' || echo '$(srcdir)/'`FTFont/FTExtrudeFont.cpp libftgl_la-FTOutlineFont.lo: FTFont/FTOutlineFont.cpp @am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -MT libftgl_la-FTOutlineFont.lo -MD -MP -MF $(DEPDIR)/libftgl_la-FTOutlineFont.Tpo -c -o libftgl_la-FTOutlineFont.lo `test -f 'FTFont/FTOutlineFont.cpp' || echo '$(srcdir)/'`FTFont/FTOutlineFont.cpp @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libftgl_la-FTOutlineFont.Tpo $(DEPDIR)/libftgl_la-FTOutlineFont.Plo @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='FTFont/FTOutlineFont.cpp' object='libftgl_la-FTOutlineFont.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -c -o libftgl_la-FTOutlineFont.lo `test -f 'FTFont/FTOutlineFont.cpp' || echo '$(srcdir)/'`FTFont/FTOutlineFont.cpp libftgl_la-FTPixmapFont.lo: FTFont/FTPixmapFont.cpp @am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -MT libftgl_la-FTPixmapFont.lo -MD -MP -MF $(DEPDIR)/libftgl_la-FTPixmapFont.Tpo -c -o libftgl_la-FTPixmapFont.lo `test -f 'FTFont/FTPixmapFont.cpp' || echo '$(srcdir)/'`FTFont/FTPixmapFont.cpp @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libftgl_la-FTPixmapFont.Tpo $(DEPDIR)/libftgl_la-FTPixmapFont.Plo @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='FTFont/FTPixmapFont.cpp' object='libftgl_la-FTPixmapFont.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -c -o libftgl_la-FTPixmapFont.lo `test -f 'FTFont/FTPixmapFont.cpp' || echo '$(srcdir)/'`FTFont/FTPixmapFont.cpp libftgl_la-FTPolygonFont.lo: FTFont/FTPolygonFont.cpp @am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -MT libftgl_la-FTPolygonFont.lo -MD -MP -MF $(DEPDIR)/libftgl_la-FTPolygonFont.Tpo -c -o libftgl_la-FTPolygonFont.lo `test -f 'FTFont/FTPolygonFont.cpp' || echo '$(srcdir)/'`FTFont/FTPolygonFont.cpp @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libftgl_la-FTPolygonFont.Tpo $(DEPDIR)/libftgl_la-FTPolygonFont.Plo @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='FTFont/FTPolygonFont.cpp' object='libftgl_la-FTPolygonFont.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -c -o libftgl_la-FTPolygonFont.lo `test -f 'FTFont/FTPolygonFont.cpp' || echo '$(srcdir)/'`FTFont/FTPolygonFont.cpp libftgl_la-FTTextureFont.lo: FTFont/FTTextureFont.cpp @am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -MT libftgl_la-FTTextureFont.lo -MD -MP -MF $(DEPDIR)/libftgl_la-FTTextureFont.Tpo -c -o libftgl_la-FTTextureFont.lo `test -f 'FTFont/FTTextureFont.cpp' || echo '$(srcdir)/'`FTFont/FTTextureFont.cpp @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libftgl_la-FTTextureFont.Tpo $(DEPDIR)/libftgl_la-FTTextureFont.Plo @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='FTFont/FTTextureFont.cpp' object='libftgl_la-FTTextureFont.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -c -o libftgl_la-FTTextureFont.lo `test -f 'FTFont/FTTextureFont.cpp' || echo '$(srcdir)/'`FTFont/FTTextureFont.cpp libftgl_la-FTLayout.lo: FTLayout/FTLayout.cpp @am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -MT libftgl_la-FTLayout.lo -MD -MP -MF $(DEPDIR)/libftgl_la-FTLayout.Tpo -c -o libftgl_la-FTLayout.lo `test -f 'FTLayout/FTLayout.cpp' || echo '$(srcdir)/'`FTLayout/FTLayout.cpp @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libftgl_la-FTLayout.Tpo $(DEPDIR)/libftgl_la-FTLayout.Plo @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='FTLayout/FTLayout.cpp' object='libftgl_la-FTLayout.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -c -o libftgl_la-FTLayout.lo `test -f 'FTLayout/FTLayout.cpp' || echo '$(srcdir)/'`FTLayout/FTLayout.cpp libftgl_la-FTLayoutGlue.lo: FTLayout/FTLayoutGlue.cpp @am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -MT libftgl_la-FTLayoutGlue.lo -MD -MP -MF $(DEPDIR)/libftgl_la-FTLayoutGlue.Tpo -c -o libftgl_la-FTLayoutGlue.lo `test -f 'FTLayout/FTLayoutGlue.cpp' || echo '$(srcdir)/'`FTLayout/FTLayoutGlue.cpp @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libftgl_la-FTLayoutGlue.Tpo $(DEPDIR)/libftgl_la-FTLayoutGlue.Plo @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='FTLayout/FTLayoutGlue.cpp' object='libftgl_la-FTLayoutGlue.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -c -o libftgl_la-FTLayoutGlue.lo `test -f 'FTLayout/FTLayoutGlue.cpp' || echo '$(srcdir)/'`FTLayout/FTLayoutGlue.cpp libftgl_la-FTSimpleLayout.lo: FTLayout/FTSimpleLayout.cpp @am__fastdepCXX_TRUE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -MT libftgl_la-FTSimpleLayout.lo -MD -MP -MF $(DEPDIR)/libftgl_la-FTSimpleLayout.Tpo -c -o libftgl_la-FTSimpleLayout.lo `test -f 'FTLayout/FTSimpleLayout.cpp' || echo '$(srcdir)/'`FTLayout/FTSimpleLayout.cpp @am__fastdepCXX_TRUE@ mv -f $(DEPDIR)/libftgl_la-FTSimpleLayout.Tpo $(DEPDIR)/libftgl_la-FTSimpleLayout.Plo @AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='FTLayout/FTSimpleLayout.cpp' object='libftgl_la-FTSimpleLayout.lo' libtool=yes @AMDEPBACKSLASH@ @AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCXX_FALSE@ $(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libftgl_la_CPPFLAGS) $(CPPFLAGS) $(libftgl_la_CXXFLAGS) $(CXXFLAGS) -c -o libftgl_la-FTSimpleLayout.lo `test -f 'FTLayout/FTSimpleLayout.cpp' || echo '$(srcdir)/'`FTLayout/FTSimpleLayout.cpp mostlyclean-libtool: -rm -f *.lo clean-libtool: -rm -rf .libs _libs install-ftglHEADERS: $(ftgl_HEADERS) @$(NORMAL_INSTALL) test -z "$(ftgldir)" || $(MKDIR_P) "$(DESTDIR)$(ftgldir)" @list='$(ftgl_HEADERS)'; for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ f=$(am__strip_dir) \ echo " $(ftglHEADERS_INSTALL) '$$d$$p' '$(DESTDIR)$(ftgldir)/$$f'"; \ $(ftglHEADERS_INSTALL) "$$d$$p" "$(DESTDIR)$(ftgldir)/$$f"; \ done uninstall-ftglHEADERS: @$(NORMAL_UNINSTALL) @list='$(ftgl_HEADERS)'; for p in $$list; do \ f=$(am__strip_dir) \ echo " rm -f '$(DESTDIR)$(ftgldir)/$$f'"; \ rm -f "$(DESTDIR)$(ftgldir)/$$f"; \ done ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ unique=`for i in $$list; do \ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ done | \ $(AWK) '{ files[$$0] = 1; nonemtpy = 1; } \ END { if (nonempty) { for (i in files) print i; }; }'`; \ mkid -fID $$unique tags: TAGS TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ $(TAGS_FILES) $(LISP) tags=; \ here=`pwd`; \ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ unique=`for i in $$list; do \ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ done | \ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ END { if (nonempty) { for (i in files) print i; }; }'`; \ if test -z "$(ETAGS_ARGS)$$tags$$unique"; then :; else \ test -n "$$unique" || unique=$$empty_fix; \ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ $$tags $$unique; \ fi ctags: CTAGS CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ $(TAGS_FILES) $(LISP) tags=; \ list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ unique=`for i in $$list; do \ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ done | \ $(AWK) '{ files[$$0] = 1; nonempty = 1; } \ END { if (nonempty) { for (i in files) print i; }; }'`; \ test -z "$(CTAGS_ARGS)$$tags$$unique" \ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ $$tags $$unique GTAGS: here=`$(am__cd) $(top_builddir) && pwd` \ && cd $(top_srcdir) \ && gtags -i $(GTAGS_ARGS) $$here distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags distdir: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ list='$(DISTFILES)'; \ dist_files=`for file in $$list; do echo $$file; done | \ sed -e "s|^$$srcdirstrip/||;t" \ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ case $$dist_files in \ */*) $(MKDIR_P) `echo "$$dist_files" | \ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ sort -u` ;; \ esac; \ for file in $$dist_files; do \ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ if test -d $$d/$$file; then \ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \ fi; \ cp -pR $$d/$$file $(distdir)$$dir || exit 1; \ else \ test -f $(distdir)/$$file \ || cp -p $$d/$$file $(distdir)/$$file \ || exit 1; \ fi; \ done check-am: all-am check: check-am all-am: Makefile $(LTLIBRARIES) $(HEADERS) installdirs: for dir in "$(DESTDIR)$(libdir)" "$(DESTDIR)$(ftgldir)"; do \ test -z "$$dir" || $(MKDIR_P) "$$dir"; \ done install: install-am install-exec: install-exec-am install-data: install-data-am uninstall: uninstall-am install-am: all-am @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am installcheck: installcheck-am install-strip: $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ `test -z '$(STRIP)' || \ echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install mostlyclean-generic: clean-generic: distclean-generic: -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) maintainer-clean-generic: @echo "This command is intended for maintainers to use" @echo "it deletes files that may require special tools to rebuild." clean: clean-am clean-am: clean-generic clean-libLTLIBRARIES clean-libtool \ mostlyclean-am distclean: distclean-am -rm -rf ./$(DEPDIR) -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-tags dvi: dvi-am dvi-am: html: html-am info: info-am info-am: install-data-am: install-ftglHEADERS install-dvi: install-dvi-am install-exec-am: install-libLTLIBRARIES install-html: install-html-am install-info: install-info-am install-man: install-pdf: install-pdf-am install-ps: install-ps-am installcheck-am: maintainer-clean: maintainer-clean-am -rm -rf ./$(DEPDIR) -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic mostlyclean: mostlyclean-am mostlyclean-am: mostlyclean-compile mostlyclean-generic \ mostlyclean-libtool pdf: pdf-am pdf-am: ps: ps-am ps-am: uninstall-am: uninstall-ftglHEADERS uninstall-libLTLIBRARIES .MAKE: install-am install-strip .PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \ clean-libLTLIBRARIES clean-libtool ctags distclean \ distclean-compile distclean-generic distclean-libtool \ distclean-tags distdir dvi dvi-am html html-am info info-am \ install install-am install-data install-data-am install-dvi \ install-dvi-am install-exec install-exec-am \ install-ftglHEADERS install-html install-html-am install-info \ install-info-am install-libLTLIBRARIES install-man install-pdf \ install-pdf-am install-ps install-ps-am install-strip \ installcheck installcheck-am installdirs maintainer-clean \ maintainer-clean-generic mostlyclean mostlyclean-compile \ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ tags uninstall uninstall-am uninstall-ftglHEADERS \ uninstall-libLTLIBRARIES # Tell versions [3.59,3.63) of GNU make to not export all variables. # Otherwise a system limit (for SysV at least) may be exceeded. .NOEXPORT: workbench-1.1.1/src/FtglFont/NEWS000066400000000000000000000462531255417355300165310ustar00rootroot00000000000000 -*- coding: utf-8 -*- FTGL ==== Included below are release notes for all versions of FTGL that have been released to date. All versions prior to and including version 2.1.2 were exclusively developed by Henry Maddocks. Subsequent versions have changes attributed per contributor. ---------------------------------------------------------------------- --- 2008-06-12 Release 2.1.3~rc5 --- ---------------------------------------------------------------------- * Stable API. Public headers are now frozen. * Fixed several memory corruption and crash bugs - Sam Hocevar * Fixed several memory leaks - Sam Hocevar * Kerning and glyph performance enhancements - Sean Morrison * The library now also exports a pure C interface - Éric Beets * Inset/outset contour support for fonts - Éric Beets * Fix the FTLayout rendering - Éric Beets * Added new FTLayout and FTSimpleLayout support for layout managers - Sam Hocevar * Fixed the paths in the XCode project - Henry Maddocks * Changed the behaviour of some objects so that if there is an error their state isn't changed - Henry Maddocks * New, fast FTBufferFont texture fonts - Sam Hocevar * UTF-8 support - Daniel Remenak ---------------------------------------------------------------------- --- 2004-12-11 Release 2.1.2 --- ---------------------------------------------------------------------- * Changed the way the colour is specified for Pixmap fonts. It can now be done per string rather than at start up as previous. * Fixed a couple of compilation errors caused by the new FTPoint stuff, mostly... * More const correctness. It's like a virus! ---------------------------------------------------------------------- --- 2004-12-05 Release 2.1.1 --- ---------------------------------------------------------------------- * Added the xCode project properly this time. ---------------------------------------------------------------------- --- 2004-12-05 Release 2.1.0 --- ---------------------------------------------------------------------- * Added texture co-ordinates to the geometry based font types. * Added the ability to turn off (or on) glDisplayList creation inside FTGL. * Removed unnecessary translates in the glyph rendering code. * Moved the Mac project to XCode. * Added a line height function to FTFont. * Got rid of the GL_TEXTURE_2D_BINDING_EXT call and replaced it with a static member. * Fixed a bug where resizing FTGLTextureFont caused a GL error. * Refactored FTPoint quite a bit. * More unit tests. Fixed a heap of bugs. ---------------------------------------------------------------------- --- 2004-08-16 Release 2.0.11 --- ---------------------------------------------------------------------- * Updated FTFont( *pBufferBytes, bufferSizeInBytes) documentation. ---------------------------------------------------------------------- --- 2004-08-16 Release 2.0.10 --- ---------------------------------------------------------------------- * Fixed tab problem in unix Makefile. * Added CYGWIN GLUTesselatorFunction define to FTVectoriser. ---------------------------------------------------------------------- --- 2004-04-21 Release 2.0.9 --- ---------------------------------------------------------------------- * Fixed includes for pre 2.1.7 versions of freetype * Changed unix build to create FTGL subdir for includes ---------------------------------------------------------------------- --- 2004-04-09 Release 2.0.8 --- ---------------------------------------------------------------------- * Fixes for deprecated identifiers in 2.1.5 * Changed the internals to use FTGlyphSlot instead of FTGlyph * Added a unit test for FTBitmapGlyph, FTCharToGlyphIndexMap. * Fixed a memory leak in FTGlyphContainer. * Added the ability to get the list of charmaps in the font. * Changed FTGLTextureFont to use FTVector for texture id list. ---------------------------------------------------------------------- --- 2003-08-31 Release 2.07 --- ---------------------------------------------------------------------- * Minor fix for unix build scripts. * Minor fix for unit tests. ---------------------------------------------------------------------- --- 2003-08-25 Release 2.06 --- ---------------------------------------------------------------------- * Updated the unix build scripts. ---------------------------------------------------------------------- --- 2003-08-25 Release 2.05 --- ---------------------------------------------------------------------- * Refactored FTGlyphContainer & FTCharmap. They now store FTGlyphs sequentially rather than by glyph index. This should save a heap of memory and a bit of time at startup. * Changed the Mac font paths in the demos. * Changed the unit tests for new hinter in Freetype 2.1.4. * Added a test for broken contour tags. ---------------------------------------------------------------------- --- 2003-04-12 Release 2.04 --- ---------------------------------------------------------------------- * Fixed resize behavior in FTGLTextureFont. ---------------------------------------------------------------------- --- 2003-04-09 Release 2.03 --- ---------------------------------------------------------------------- * Fix in FTContour to handle broken contours. ---------------------------------------------------------------------- --- 2003-04-03 Release 2.02 --- ---------------------------------------------------------------------- * Fixed memory leaks ---------------------------------------------------------------------- --- 2003-03-14 Release 2.01 --- ---------------------------------------------------------------------- * Minor changes to autoconf to detect glu ---------------------------------------------------------------------- --- 2003-03-11 Release 2.0 --- ---------------------------------------------------------------------- * Fixed some alignment bugs caused by changes to Freetype ( > 2.0.9). * Minor fixes to float declarations. * Moved FTBBox and FTPoint to their own files and added Move() and operator += to FTBBox * Replaced FT_Vector with FTPoint for kerning. * Fixed the glPushAttrib calls. * Changed gluTess callback def. * Rewriting FTGLDemo. * Minor fixes for irix. * Removed a bunch of redundant members and made them function locals. * Removed the Open() & Close() functions from FTFont because there was no way to handle Close correctly which makes Open redundant. * Removed Open() from FTface. * Improved the robustness of some of the error handling. * Removed the FTCharmap Platform/Encoding function. * Added unit tests. * Removed the precache flag. * Unvirtualised functions in FTLibrary and FTGlyphContainer. * Fixed empty string bug in FTFont::BBox. * Refactored FTContour and moved it to it's own file. * Removed unnecessary memory allocations in vector Glyphs. They now access the vector data directly. * Made vectoriser a local variable in vector glyphs. * Fixed a long standing visual bug in FTVectoriser. * Changed size calculations to use floats instead of ints. This includes FTBBox. * Refactored FTGlyph. Now calculates advance (as a float) and bbox. * Changed contourList from FTVector to an array in FTVectoriser. * Made function and member names more consistant. * Moved header files to include directory. * Mesh now uses a list for glCombine points. * Delete the display lists. * Unix AutoConf support. * Attach 'files' from memory. ---------------------------------------------------------------------- --- 2002-10-23 Release 1.4 --- ---------------------------------------------------------------------- * FTGL now requires 2.0.9 or later. See below for reason. * Merged 1.32 branch with main tree * Glyph loading has been optimised for pixel based glyphs. * Removed mmgr * Added FTFont::Attach * Updated API docs * Removed stl map and vector. Replaced by code supplied by Sebastien Barre * Removed work around for Type1 height and width bug in freetype. It seems to be fixed in 2.0.9 * Added a test target to the Mac OSX project * Inline some private functions. ---------------------------------------------------------------------- --- 2002-04-23 Release 1.32 --- ---------------------------------------------------------------------- * Fixed enable state attribute in FTGLBitmapFont * Wrapped tb.h & trackball.h in EXTERN "C" * Renamed FTGLDemo to .cpp Ellers... * New MSVC projects updated to v1.3 * Removed a lot of unnecessary Windows stuff from ftgl.h * Added functions to load font from memory. * Fixed a couple of Windows 'for' scope problems in FTExtrdGlyph * FTGLDemo. Added #define for windows font ---------------------------------------------------------------------- --- 2002-01-30 Release 1.31 --- ---------------------------------------------------------------------- * Forgot to update readme etc for 1.3 ---------------------------------------------------------------------- --- 2002-01-27 Release 1.3b5 --- ---------------------------------------------------------------------- * FTBbox now uses float rather then int * Fixed some more warnings (size_t) * Removed the contour winding function because it didn't fix the problem!! * Fixed up some state settings in fonts. ---------------------------------------------------------------------- --- 2001-12-11 Release 1.3b4 --- ---------------------------------------------------------------------- * Added MAC OSX project (Project Builder) * Added a function for extruded glyphs that calculates the winding order of the glyph contour. * Added FTGL_DEBUG to include memory debugger. * Added a couple of typedefs to FTGL.h, mainly to aid debugging * Cleaned up the includes. ---------------------------------------------------------------------- --- 2001-11-13 Release 1.3b3 --- ---------------------------------------------------------------------- * Texture fonts now behave the same as the others and can be loaded on demand. This made FTGLTextureFont MUCH simpler!!!! It has also improved the grid fitting so less texture mem is needed. * Refactored FTVectoriser... This now builds contours and meshes internally and then passes the raw point data onto the glyphs. The gluTess data is captured in an internal non static data structure fixing a memory Leak in PolyGlyph (glCombine). This has enabled... * Extruded fonts. FTGLExtrdFont & FTExtrdGlyph. * Reversed the winding for polyglyphs, extruded glyphs and texture glyphs to make them CCW * Bounding box function * Fixed the != and == operators in ftPoint * Un-virtualised some functions in FTFont * Added a demo app to dist. ---------------------------------------------------------------------- --- 2001-11-09 Release 1.21 --- ---------------------------------------------------------------------- * Visual Studio projects updated for .cpp source file extensions. * A couple of windows 'cast' warnings have been fixed. ---------------------------------------------------------------------- --- 2001-11-06 Release 1.2 --- ---------------------------------------------------------------------- * Glyphs can now be loaded on the fly instead of being pre-cached. If FTFont::Open() is called with false, FTGlyphContainer will build a list of null pointers. Then when ever a glyph needs to be access eg by FTFont::advance or FTFont::render, it will be built and slotted into the glyphlist in the correct position. * Removed glext.h from FTGL.h and replaced it with a test for GL_EXT_texture_object. * Added padding to texture size calculations. * Fixed a NASTY bug in FTGLTextureFont. Only came to light after changes to the glyph preprocessing. ---------------------------------------------------------------------- --- 2001-10-31 Release 1.1 --- ---------------------------------------------------------------------- * Renamed the source to .cpp * Removed the static activeTextureID from FTTextureGlyph and replaced it with a call to glGetIntegerv( GL_TEXTURE_2D_BINDING_EXT, &activeTextureID); * Added an include for glext.h in FTGL.h * Tidied up the glbegin/glEnd pairs in FTTextureGlyph & FTGLTextureFont * Fixed the problem with doc filenames. * Tidied up some implicit type conversions. * Fixed FTCharMap to ensure that a valid default charmap is always created by the c_stor. ---------------------------------------------------------------------- --- 2001-10-26 Release 1.01 --- ---------------------------------------------------------------------- * Removed the glEnable( GL_TEXTURE_2D) from FTGLTextureFont * Removed the redundant tempGlyph members in the FTGLXXXXFont classes * Made a change in FTGL.h to include correct headers for MAC OSX * FTGL.h now includes glu.h * Minor fixes to get rid of Project Builder warnings (MAC OSX) * Fixed some of the docs ---------------------------------------------------------------------- --- 2001-10-24 Release 1.0 --- ---------------------------------------------------------------------- * Version 1.0 release ---------------------------------------------------------------------- --- 2001-09-29 Release 1.0b7 --- ---------------------------------------------------------------------- * Tesselation winding rules * Fixed bug in FTContour Add point function * Cleaned up disposal of FTCharmap in FTFace * renamed FTVectorGlyph to FTOutlineGlyph * New distribution structure * Minor changes for windows (VC 6) * Windows and Linux ports. ---------------------------------------------------------------------- --- 2001-09-20 Release 1.0b6 --- ---------------------------------------------------------------------- * Implemented the new FTCharmap class. The performance improvement is dramatic. * Tidied up the way the freetype FT_Face object is disposed of by FTFont and FTFace. This was a potential crash. * FTVectorGlyph and FTPolyGlyph now disposes of the freetype glyph correctly after initialsation. This was a potential crash. * Preliminary support for unicode...wchar_t Tested with non european fonts. * Added function to calc the advance width of a string. * Minor tidy ups. ---------------------------------------------------------------------- --- 2001-08-29 Release 1.0b5 --- ---------------------------------------------------------------------- * Settled on integers for FTSize stuff. NOTE the FTGlyph stuff is still up in the air. * Fixed the positional stuff. * Added Java Doc comments. NOT COMPLETE * Fixes for linux, mainly to clear warnings. * changed the return type for FTFace::Glyph() from a reference to a pointer so it can return NULL on failure. * Related to above...better error handling and reporting in FTGLXXXFont::MakeGlyphList() * Fixed a bug in FTVectoriser that was ignoring non printing characters. This meant that the pen wasn't advanced for spaces etc. It affected polygon and outline font rendering. * Minor tidy ups. ---------------------------------------------------------------------- --- 2001-08-21 Release 1.0b4 --- ---------------------------------------------------------------------- * Changed the mode for FT_Load_Glyph to FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP for outline and polygon fonts & FT_LOAD_NO_HINTING for texture fonts. Seems to produce better looking glyphs. * FTGLTextureFont can now use multiple textures to render glyphs if they don't fit within one GL_MAX_TEXTURE_SIZE texture. * Changed FTSize to use bbox for global width and height. Needs more work (eg float or int?) and need to check inconsistancies in freetype. * Being more strict with types eg integer indices and sizes are now unsigned. ---------------------------------------------------------------------- --- 2001-08-08 Release 1.0b3 --- ---------------------------------------------------------------------- * I've made fundamental change to the way the glyphlist is built. This is to get round the problems I was having with charmaps. At this stage it is a temporary solution. Previously the glyphList was indexed by char code. Now it's indexed by glyph index and the conversion is done by the freetype function FT_Get_Char_Index(). If this proves to be too slow I'll make my own charmap and use it to index into the glyphlist. This has fixed all the charmap related problems/bugs. * Enabled alpha blend in Pixmap font. * Enabled LINE_SMOOTH in Outline font * Fixed bug that prevented the display of chars >127 * Moved pixel store stuff out of BitmapGlyph into BitmapFont. * Minor changes for IRIX (compiles but isn't tested) * Pixmap fonts can now be in colour. It uses the current colour when the font is CREATED. This isn't ideal but is better than the alternatives. * Tidied up the error handling. * Minor code clean ups. ---------------------------------------------------------------------- --- 2001-08-06 BETA Release 1.0b2 --- ---------------------------------------------------------------------- * Minor tidy ups for first public release. ---------------------------------------------------------------------- --- 2001-08-03 First BETA Release 1.0b1 --- ---------------------------------------------------------------------- * All font types are now working, Bitmaps, Pixmaps, Texture, Outline and Polygons. Quality of output and performance varies wildly. :) ---------------------------------------------------------------------- --- 2001-07-22 First ALPHA Release 1.0a1 --- ---------------------------------------------------------------------- * And so it begins. workbench-1.1.1/src/FtglFont/README000066400000000000000000000016551255417355300167070ustar00rootroot00000000000000FTGL 2.1 5 December 2004 DESCRIPTION: FTGL is a free open source library to enable developers to use arbitrary fonts in their OpenGL (www.opengl.org) applications. Unlike other OpenGL font libraries FTGL uses standard font file formats so doesn't need a preprocessing step to convert the high quality font data into a lesser quality, proprietary format. FTGL uses the Freetype (www.freetype.org) font library to open and 'decode' the fonts. It then takes that output and stores it in a format most efficient for OpenGL rendering. Rendering modes supported are: - Bit maps - Antialiased Pix maps - Outlines - Polygon meshes - Extruded polygon meshes - Texture maps - Buffer maps USAGE: FTGLPixmapFont font("Arial.ttf"); font.FaceSize(72); font.Render("Hello World!"); CONTACT: Please contact us if you have any suggestions, feature requests, or problems. Sam Hocevar Christopher Sean Morrison workbench-1.1.1/src/FtglFont/README_WORKBENCH.txt000066400000000000000000000015701255417355300211230ustar00rootroot00000000000000 How FtglFont was created. * Download and uncompress an FTGL library. * Copy the source only: cp -r /src ./FtglFont * cd FtglFont * Create the CMakeLists.txt file. ** find . -name '*.h' -print ** find . -name '*.cpp' -print ** Add .h and .cpp files to ADD_LIBRARY ** Add all subdirectories to INCLUDE_DIRECTORIES ** Need 'ft2build.h' from FreeType it is in /Developer/SDKs/MacOSX10.7.sdk/usr/X11/include on mac ** Change the include for "config.h" to "FtglConfig.h" so that it does not conflict with other "config.h" files ** Create the FtglConfig.h file by "cp /config.h.in ./FtglConfig.h" ** Add FtglConfig.h to CMakeLists.txt ** Edit FtglConfig.h and define these items: *** HAVE_INTTYPES_H *** HAVE_MEMORY_H *** HAVE_STDINT_H *** HAVE_STDLIB_H *** HAVE_STRINGS_H *** HAVE_STRING_H *** HAVE_STRNDUP *** HAVE_SYS_TYPES_H *** STDC_HEADERS *** X_DISPLAY_MISSING workbench-1.1.1/src/Gifti/000077500000000000000000000000001255417355300153375ustar00rootroot00000000000000workbench-1.1.1/src/Gifti/CMakeLists.txt000066400000000000000000000014211255417355300200750ustar00rootroot00000000000000 # # Name of Project # PROJECT (Gifti) # # Need XML from Qt # SET(QT_DONT_USE_QTGUI) # # Add QT for includes # INCLUDE (${QT_USE_FILE}) # # Create GIFTI Library # ADD_LIBRARY(Gifti GiftiArrayIndexingOrderEnum.h GiftiDataArray.h GiftiEncodingEnum.h GiftiEndianEnum.h GiftiFile.h GiftiFileSaxReader.h GiftiFileWriter.h GiftiLabelTableSaxReader.h GiftiMetaDataSaxReader.h GiftiArrayIndexingOrderEnum.cxx GiftiDataArray.cxx GiftiEncodingEnum.cxx GiftiEndianEnum.cxx GiftiFile.cxx GiftiFileSaxReader.cxx GiftiFileWriter.cxx GiftiLabelTableSaxReader.cxx GiftiMetaDataSaxReader.cxx ) # # Find Headers # INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/Palette ${CMAKE_SOURCE_DIR}/FilesBase ${CMAKE_SOURCE_DIR}/Gifti ${CMAKE_SOURCE_DIR}/Nifti ${CMAKE_SOURCE_DIR}/Xml ${CMAKE_SOURCE_DIR}/Common ) workbench-1.1.1/src/Gifti/GiftiArrayIndexingOrderEnum.cxx000066400000000000000000000117031255417355300234350ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __GIFTIARRAYINDEXINGORDER_DECLARE__ #include "GiftiArrayIndexingOrderEnum.h" #undef __GIFTIARRAYINDEXINGORDER_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * Constructor. * * @param e * An enumerated value. * @param name * Name of enumberated value. */ GiftiArrayIndexingOrderEnum::GiftiArrayIndexingOrderEnum( const Enum e, const AString& name, const AString& giftiName) { this->e = e; this->name = name; this->giftiName = giftiName; } /** * Destructor. */ GiftiArrayIndexingOrderEnum::~GiftiArrayIndexingOrderEnum() { } void GiftiArrayIndexingOrderEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(GiftiArrayIndexingOrderEnum(COLUMN_MAJOR_ORDER, "COLUMN_MAJOR_ORDER", "ColumnMajorOrder")); enumData.push_back(GiftiArrayIndexingOrderEnum(ROW_MAJOR_ORDER, "ROW_MAJOR_ORDER", "RowMajorOrder")); } /** * Find the data for and enumerated value. * @param e * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const GiftiArrayIndexingOrderEnum* GiftiArrayIndexingOrderEnum::findData(const Enum e) { initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const GiftiArrayIndexingOrderEnum* d = &enumData[i]; if (d->e == e) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param e * Enumerated value. * @return * String representing enumerated value. */ AString GiftiArrayIndexingOrderEnum::toName(Enum e) { initialize(); const GiftiArrayIndexingOrderEnum* gaio = findData(e); return gaio->name; } /** * Get an enumerated value corresponding to its name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ GiftiArrayIndexingOrderEnum::Enum GiftiArrayIndexingOrderEnum::fromName(const AString& s, bool* isValidOut) { initialize(); bool validFlag = false; Enum e = ROW_MAJOR_ORDER; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const GiftiArrayIndexingOrderEnum& d = *iter; if (d.name == s) { e = d.e; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("name \"" + s + " \"failed to match enumerated value for type GiftiArrayIndexingOrderEnum")); } return e; } /** * Get a string representation of the enumerated type. * @param e * Enumerated value. * @return * String representing enumerated value. */ AString GiftiArrayIndexingOrderEnum::toGiftiName(Enum e) { initialize(); const GiftiArrayIndexingOrderEnum* gaio = findData(e); return gaio->giftiName; } /** * Get an enumerated value corresponding to its name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ GiftiArrayIndexingOrderEnum::Enum GiftiArrayIndexingOrderEnum::fromGiftiName(const AString& s, bool* isValidOut) { initialize(); bool validFlag = false; Enum e = ROW_MAJOR_ORDER; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const GiftiArrayIndexingOrderEnum& d = *iter; if (d.giftiName == s) { e = d.e; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("giftiName \"" + s + " \"failed to match enumerated value for type GiftiArrayIndexingOrderEnum")); } return e; } workbench-1.1.1/src/Gifti/GiftiArrayIndexingOrderEnum.h000066400000000000000000000044601255417355300230640ustar00rootroot00000000000000#ifndef __GIFTIARRAYINDEXINGORDER_H__ #define __GIFTIARRAYINDEXINGORDER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "GiftiException.h" #include #include #include namespace caret { /** * Type for GIFTI Data Array ArrayIndexingOrder Attribute. */ class GiftiArrayIndexingOrderEnum { public: /** Type for GIFTI Data Array ArrayIndexingOrder Attribute. */ enum Enum { /** Column-Major Order (Fortran/Matlab) */ COLUMN_MAJOR_ORDER, /** Row-Major Order (C/C++/Java) */ ROW_MAJOR_ORDER }; ~GiftiArrayIndexingOrderEnum(); static AString toName(Enum e); static Enum fromName(const AString& s, bool* isValidOut); static AString toGiftiName(Enum e); static Enum fromGiftiName(const AString& s, bool* isValidOut); private: GiftiArrayIndexingOrderEnum(const Enum e, const AString& name, const AString& giftiName); static const GiftiArrayIndexingOrderEnum* findData(const Enum e); static std::vector enumData; static void initialize(); static bool initializedFlag; Enum e; AString name; AString giftiName; }; #ifdef __GIFTIARRAYINDEXINGORDER_DECLARE__ std::vector GiftiArrayIndexingOrderEnum::enumData; bool GiftiArrayIndexingOrderEnum::initializedFlag = false; #endif // __GIFTIARRAYINDEXINGORDER_DECLARE__ } // namespace #endif // __GIFTIARRAYINDEXINGORDER_H__ workbench-1.1.1/src/Gifti/GiftiDataArray.cxx000066400000000000000000001723671255417355300207360ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #include "Base64.h" #include "ByteOrderEnum.h" #include "ByteSwapping.h" #include "CaretAssert.h" #include "CaretLogger.h" #include "DataCompressZLib.h" //#include "FileUtilities.h" #include "FastStatistics.h" #include "GiftiDataArray.h" #include "GiftiFile.h" #include "GiftiMetaDataXmlElements.h" #include "GiftiXmlElements.h" #include "Histogram.h" #include "NiftiEnums.h" #include "PaletteColorMapping.h" #include "SystemUtilities.h" #include "XmlWriter.h" using namespace caret; /** * constructor. */ GiftiDataArray::GiftiDataArray(const NiftiIntentEnum::Enum intentIn, const NiftiDataTypeEnum::Enum dataTypeIn, const std::vector& dimensionsIn, const GiftiEncodingEnum::Enum encodingIn) { intent = intentIn; dataTypeSize = 0; dataPointerFloat = NULL; dataPointerInt = NULL; dataPointerUByte = NULL; this->paletteColorMapping = NULL; this->descriptiveStatistics = NULL; this->descriptiveStatisticsLimitedValues = NULL; clear(); dataType = dataTypeIn; setDimensions(dimensionsIn); encoding = encodingIn; endian = getSystemEndian(); arraySubscriptingOrder = GiftiArrayIndexingOrderEnum::ROW_MAJOR_ORDER; externalFileName = ""; externalFileOffset = 0; if (intent == NiftiIntentEnum::NIFTI_INTENT_POINTSET) { Matrix4x4 gm; matrices.push_back(gm); } } /** * constructor. */ GiftiDataArray::GiftiDataArray(const NiftiIntentEnum::Enum intentIn) { intent = intentIn; dataTypeSize = 0; dataPointerFloat = NULL; dataPointerInt = NULL; dataPointerUByte = NULL; this->paletteColorMapping = NULL; this->descriptiveStatistics = NULL; this->descriptiveStatisticsLimitedValues = NULL; clear(); dimensions.clear(); encoding = GiftiEncodingEnum::ASCII; endian = getSystemEndian(); arraySubscriptingOrder = GiftiArrayIndexingOrderEnum::ROW_MAJOR_ORDER; externalFileName = ""; externalFileOffset = 0; /*if (intent == NiftiIntentEnum::NIFTI_INTENT_POINTSET) { Matrix4x4 gm; matrices.push_back(gm); }//*///TSC: do not add a fake matrix with no data or transformed space BEFORE knowing if one already exists, instead add one in validate, after reading, if none exists dataType = NiftiDataTypeEnum::NIFTI_TYPE_FLOAT32; getDataTypeAppropriateForIntent(intent, dataType); } /** * destructor. */ GiftiDataArray::~GiftiDataArray() { clear(); } /** * copy constructor. */ GiftiDataArray::GiftiDataArray(const GiftiDataArray& nda) : CaretObject(), TracksModificationInterface() { dataTypeSize = 0; dataPointerFloat = NULL; dataPointerInt = NULL; dataPointerUByte = NULL; this->paletteColorMapping = NULL; this->descriptiveStatistics = NULL; this->descriptiveStatisticsLimitedValues = NULL; copyHelperGiftiDataArray(nda); } /** * assignment operator. */ GiftiDataArray& GiftiDataArray::operator=(const GiftiDataArray& nda) { if (this != &nda) { copyHelperGiftiDataArray(nda); } return *this; } /** * the copy helper (used by copy constructor and assignment operator). */ void GiftiDataArray::copyHelperGiftiDataArray(const GiftiDataArray& nda) { this->paletteColorMapping = NULL; if (nda.paletteColorMapping != NULL) { this->paletteColorMapping = new PaletteColorMapping(*nda.paletteColorMapping); } if (this->descriptiveStatistics != NULL) { delete this->descriptiveStatistics; this->descriptiveStatistics = NULL; } if (this->descriptiveStatisticsLimitedValues != NULL) { delete this->descriptiveStatisticsLimitedValues; this->descriptiveStatisticsLimitedValues = NULL; } intent = nda.intent; encoding = nda.encoding; arraySubscriptingOrder = nda.arraySubscriptingOrder; dataType = nda.dataType; //dataLocation = nda.dataLocation; dataTypeSize = nda.dataTypeSize; endian = nda.endian; dimensions = nda.dimensions; allocateData(); data = nda.data; metaData = nda.metaData; nonWrittenMetaData = nda.nonWrittenMetaData; externalFileName = nda.externalFileName; externalFileOffset = nda.externalFileOffset; minMaxFloatValuesValid = nda.minMaxFloatValuesValid; minValueFloat = nda.minValueFloat; maxValueFloat = nda.maxValueFloat; minMaxFloatValuesValid = nda.minMaxFloatValuesValid; minValueInt = nda.minValueInt; maxValueInt = nda.maxValueInt; minMaxIntValuesValid = nda.minMaxIntValuesValid; minMaxPercentageValuesValid = nda.minMaxPercentageValuesValid; negMaxPct = nda.negMaxPct; negMinPct = nda.negMinPct; posMinPct = nda.posMinPct; posMaxPct = nda.posMaxPct; negMaxPctValue = nda.negMaxPctValue; negMinPctValue = nda.negMinPctValue; posMinPctValue = nda.posMinPctValue; posMaxPctValue = nda.posMaxPctValue; matrices = nda.matrices; setModified(); } /** * get the data type appropriate for the intent (returns true if intent is valid). */ bool GiftiDataArray::getDataTypeAppropriateForIntent(const NiftiIntentEnum::Enum intent, NiftiDataTypeEnum::Enum& dataTypeOut) { // // Default to float // dataTypeOut = NiftiDataTypeEnum::NIFTI_TYPE_FLOAT32; if (intent == NiftiIntentEnum::NIFTI_INTENT_POINTSET) { dataTypeOut = NiftiDataTypeEnum::NIFTI_TYPE_FLOAT32; } else if (intent == NiftiIntentEnum::NIFTI_INTENT_TIME_SERIES) { dataTypeOut = NiftiDataTypeEnum::NIFTI_TYPE_FLOAT32; } else if (intent == NiftiIntentEnum::NIFTI_INTENT_NORMAL) { dataTypeOut = NiftiDataTypeEnum::NIFTI_TYPE_FLOAT32; } else if (intent == NiftiIntentEnum::NIFTI_INTENT_LABEL) { dataTypeOut = NiftiDataTypeEnum::NIFTI_TYPE_INT32; } else if ((intent == NiftiIntentEnum::NIFTI_INTENT_RGB_VECTOR) || (intent == NiftiIntentEnum::NIFTI_INTENT_RGBA_VECTOR)) { dataTypeOut = NiftiDataTypeEnum::NIFTI_TYPE_UINT8; } else if (intent == NiftiIntentEnum::NIFTI_INTENT_SHAPE) { dataTypeOut = NiftiDataTypeEnum::NIFTI_TYPE_FLOAT32; } else if (intent == NiftiIntentEnum::NIFTI_INTENT_SYMMATRIX) { dataTypeOut = NiftiDataTypeEnum::NIFTI_TYPE_FLOAT32; } else if (intent == NiftiIntentEnum::NIFTI_INTENT_TRIANGLE) { dataTypeOut = NiftiDataTypeEnum::NIFTI_TYPE_INT32; } // else { // CaretLogFine("Unrecogized NIFTI intent \"" // + NiftiIntentEnum::toName(intent) // + "\" in GiftiDataArray::getDataTypeAppropriateForIntent()."); // return false; // } return true; } /** * add nodes. */ void GiftiDataArray::addRows(const int32_t numRowsToAdd) { dimensions[0] += numRowsToAdd; allocateData(); } /** * delete rows. */ void GiftiDataArray::deleteRows(const std::vector& rowsToDeleteIn) { if (rowsToDeleteIn.empty()) { return; } // // Sort rows in reverse order // std::vector rowsToDelete = rowsToDeleteIn; std::sort(rowsToDelete.begin(), rowsToDelete.end()); std::unique(rowsToDelete.begin(), rowsToDelete.end()); std::reverse(rowsToDelete.begin(), rowsToDelete.end()); // // size of row in bytes // int64_t numBytesInRow = 1; for (uint32_t i = 1; i < dimensions.size(); i++) { numBytesInRow = dimensions[i]; } numBytesInRow *= dataTypeSize; // // Remove the unneeded rows // for (uint32_t i = 0; i < rowsToDelete.size(); i++) { const int32_t offset = rowsToDelete[i] * numBytesInRow; data.erase(data.begin() + offset, data.begin() + offset + numBytesInRow); } // // Update the dimensions // dimensions[0] -= rowsToDelete.size(); setModified(); } /** * set number of nodes which causes reallocation of data. */ void GiftiDataArray::setDimensions(const std::vector dimensionsIn) { dimensions = dimensionsIn; if (dimensions.size() == 1) { dimensions.push_back(1); } else if (dimensions.empty()) { dimensions.push_back(0); dimensions.push_back(0); } allocateData(); } /** * allocate data for this array. */ void GiftiDataArray::allocateData() { // // Determine the number of items to allocate // int64_t dataSizeInBytes = 1; for (uint32_t i = 0; i < dimensions.size(); i++) { dataSizeInBytes *= dimensions[i]; } // // Bytes required by each data type // dataTypeSize = 0; switch (dataType) { case NiftiDataTypeEnum::NIFTI_TYPE_FLOAT32: dataTypeSize = sizeof(float); break; case NiftiDataTypeEnum::NIFTI_TYPE_INT32: dataTypeSize = sizeof(int32_t); break; case NiftiDataTypeEnum::NIFTI_TYPE_UINT8: dataTypeSize = sizeof(uint8_t); break; default: CaretAssertMessage(0, "Unsupported GIFTI data type."); break; } dataSizeInBytes *= dataTypeSize; // // Does data need to be allocated // if (dataSizeInBytes > 0) { // // Allocate the needed memory // data.resize(dataSizeInBytes); } else { data.clear(); } // // Update the data pointers // updateDataPointers(); setModified(); } /** * update the data pointers. */ void GiftiDataArray::updateDataPointers() { dataPointerFloat = NULL; dataPointerInt = NULL; dataPointerUByte = NULL; if (data.empty() == false) { switch (dataType) { case NiftiDataTypeEnum::NIFTI_TYPE_FLOAT32: dataPointerFloat = (float*)&data[0]; break; case NiftiDataTypeEnum::NIFTI_TYPE_INT32: dataPointerInt = (int32_t*)&data[0]; break; case NiftiDataTypeEnum::NIFTI_TYPE_UINT8: dataPointerUByte = (uint8_t*)&data[0]; break; default: CaretAssertMessage(0, "Unsupported GIFTI Data Type"); break; } } } /** * reset column. */ void GiftiDataArray::clear() { arraySubscriptingOrder = GiftiArrayIndexingOrderEnum::ROW_MAJOR_ORDER; encoding = GiftiEncodingEnum::ASCII; dataType = NiftiDataTypeEnum::NIFTI_TYPE_FLOAT32; endian = getSystemEndian(); dataTypeSize = sizeof(float); metaData.clear(); nonWrittenMetaData.clear(); dimensions.clear(); setDimensions(dimensions); externalFileName = ""; externalFileOffset = 0; minMaxFloatValuesValid = false; minMaxPercentageValuesValid = false; if (this->paletteColorMapping != NULL) { delete this->paletteColorMapping; this->paletteColorMapping = NULL; } if (this->descriptiveStatistics != NULL) { delete this->descriptiveStatistics; this->descriptiveStatistics = NULL; } if (this->descriptiveStatisticsLimitedValues != NULL) { delete this->descriptiveStatisticsLimitedValues; this->descriptiveStatisticsLimitedValues = NULL; } // do not clear // parentGiftiDataFile; // arrayType; // setDimensions will call allocateData() which will set // dataPointer // dataPointerFloat // dataPointerInt // dataPointerUByte; } /** * get the number of nodes (1st dimension). */ int64_t GiftiDataArray::getNumberOfRows() const { if (dimensions.empty() == false) { return dimensions[0]; } return 0; } /** * get the total number of elements. */ int64_t GiftiDataArray::getTotalNumberOfElements() const { if (dimensions.empty()) { return 0; } int64_t num = 1; for (uint32_t i = 0; i < dimensions.size(); i++) { num *= dimensions[i]; } return num; } /** * get number of components per node (2nd dimension). */ int64_t GiftiDataArray::getNumberOfComponents() const { if (dimensions.size() > 1) { return dimensions[1]; } else if (dimensions.size() == 1) { return 1; } return 0; } /** * get data offset. */ /*int64_t GiftiDataArray::getDataOffset(const int64_t nodeNum, const int64_t componentNum) const { const int64_t off = nodeNum * dimensions[1] + componentNum;//TSC: this is WRONG! (assumes 2 dimensions, assumes a particular index order) fix it before uncommenting return off; }//*/ /** * get the system's endian. */ GiftiEndianEnum::Enum GiftiDataArray::getSystemEndian() { GiftiEndianEnum::Enum endian = GiftiEndianEnum::ENDIAN_BIG; if (ByteOrderEnum::isSystemLittleEndian()) { endian = GiftiEndianEnum::ENDIAN_LITTLE; } //if (QSysInfo::ByteOrder == QSysInfo::BigEndian) { // return ENDIAN_BIG; //} return endian; } /** * get external file information. */ void GiftiDataArray::getExternalFileInformation(AString& nameOut, int64_t & offsetOut) const { nameOut = externalFileName; offsetOut = externalFileOffset; } /** * set external file information. */ void GiftiDataArray::setExternalFileInformation(const AString& nameIn, const int64_t offsetIn) { externalFileName = nameIn; externalFileOffset = offsetIn; } /** * remap integer values that are indices to a table. * void GiftiDataArray::remapIntValues(const std::vector& remappingTable) { if (remappingTable.isEmpty()) { return; } if (dataType != NiftiDataType::NIFTI_TYPE_INT32) { return; } const int64_t num = getTotalNumberOfElements(); for (int64_t i = 0; i < num; i++) { dataPointerInt[i] = remappingTable[dataPointerInt[i]]; } } */ /** * Update label indices using the index converter array. Typically, * this is done when appending one label file to another. * * @param indexConverter Converts the label indices to new values. */ void GiftiDataArray::transferLabelIndices(const std::map& indexConverter) { if (this->getDataType() == NiftiDataTypeEnum::NIFTI_TYPE_INT32) { int64_t num = this->getTotalNumberOfElements(); for (int i = 0; i < num; i++) { int oldIndex = this->dataPointerInt[i]; std::map::const_iterator iter = indexConverter.find(oldIndex); if (iter != indexConverter.end()) { this->dataPointerInt[i] = iter->second; //indexConverter.get(newIndex); } else { this->dataPointerInt[i] = 0; } this->setModified(); } } } /** * read a GIFTI data array from text. * Data array should already be initialized and allocated. */ void GiftiDataArray::readFromText(const AString text, const GiftiEndianEnum::Enum dataEndianForReading, const GiftiArrayIndexingOrderEnum::Enum arraySubscriptingOrderForReading, const NiftiDataTypeEnum::Enum dataTypeForReading, const std::vector& dimensionsForReading, const GiftiEncodingEnum::Enum encodingForReading, const AString& externalFileNameForReading, const int64_t externalFileOffsetForReading, const bool isReadOnlyMetaData) { const NiftiDataTypeEnum::Enum requiredDataType = dataType; dataType = dataTypeForReading; encoding = encodingForReading; endian = dataEndianForReading; arraySubscriptingOrder = arraySubscriptingOrderForReading; setDimensions(dimensionsForReading); if (dimensionsForReading.size() == 0) { throw GiftiException("Data array has no dimensions."); } //setExternalFileInformation(externalFileNameForReading, // externalFileOffsetForReading);//TSC: don't set the external filename on the array, because that is what it uses when writing the array // // If NOT metadata only // if (isReadOnlyMetaData == false) { // // Total number of elements in Data Array // const int64_t numElements = getTotalNumberOfElements(); switch (encoding) { case GiftiEncodingEnum::ASCII: { std::istringstream stream(text.toStdString()); switch (dataType) { case NiftiDataTypeEnum::NIFTI_TYPE_FLOAT32: { float* ptr = dataPointerFloat; for (int64_t i = 0; i < numElements; i++) { stream >> *ptr; ptr++; } } break; case NiftiDataTypeEnum::NIFTI_TYPE_INT32: { int32_t* ptr = dataPointerInt; for (int64_t i = 0; i < numElements; i++) { stream >> *ptr; ptr++; } } break; case NiftiDataTypeEnum::NIFTI_TYPE_UINT8: { uint8_t* ptr = dataPointerUByte; int32_t c; for (int64_t i = 0; i < numElements; i++) { stream >> c; *ptr = static_cast(c); ptr++; } } break; default: throw GiftiException("DataType " + NiftiDataTypeEnum::toName(dataType) + " not supported in GIFTI"); } } break; case GiftiEncodingEnum::BASE64_BINARY: { // // Decode the Base64 data using VTK's algorithm // const uint64_t numDecoded = Base64::decode((const unsigned char*)(text.toStdString().c_str()), data.size(), &data[0]); if (numDecoded != data.size()) { std::ostringstream str; str << "Decoding of Base64 Binary data failed.\n" << "Decoded " << AString::number(numDecoded).toStdString() << " bytes but should be " << AString::number(static_cast(data.size())).toStdString() << " bytes."; throw GiftiException(AString::fromStdString(str.str())); } // // Is byte swapping needed ? // if (endian != getSystemEndian()) { byteSwapData(getSystemEndian()); } } break; case GiftiEncodingEnum::GZIP_BASE64_BINARY: { // // Decode the Base64 data using VTK's algorithm // unsigned char* dataBuffer = new unsigned char[data.size()]; //const char* textChars = text.toAscii().constData(); const uint64_t numDecoded = Base64::decode((unsigned char*)text.toStdString().c_str(), data.size(), dataBuffer); if (numDecoded == 0) { std::ostringstream str; str << "Decoding of GZip Base64 Binary data failed." << "Decoded " << AString::number(numDecoded).toStdString() << " bytes but should be " << AString::number(static_cast(data.size())).toStdString() << " bytes."; throw GiftiException(AString::fromStdString(str.str())); } // // Uncompress the data using VTK's algorithm // DataCompressZLib compressor; const uint64_t uncompressedDataLength = compressor.uncompressData(dataBuffer, numDecoded, (unsigned char*)&data[0], data.size()); if (uncompressedDataLength != data.size()) { std::ostringstream str; str << "Decompression of Binary data failed.\n" << "Uncompressed " << AString::number(uncompressedDataLength).toStdString() << " bytes but should be " << AString::number(static_cast(data.size())).toStdString() << " bytes."; throw GiftiException(AString::fromStdString(str.str())); } // // Free memory // delete[] dataBuffer; // // Is byte swapping needed ? // if (endian != getSystemEndian()) { byteSwapData(getSystemEndian()); } } break; case GiftiEncodingEnum::EXTERNAL_FILE_BINARY: { if (externalFileNameForReading.length() <= 0) { throw GiftiException("External file name is empty."); } std::ifstream extBinFile(externalFileNameForReading.toStdString().c_str(), std::ifstream::in); if (extBinFile.good() == false) { throw GiftiException("Error opening \"" + externalFileNameForReading + "\""); } else { // // Move to the offset of the data // extBinFile.seekg(externalFileOffsetForReading, std::ios::beg); if (extBinFile.good() == false) { throw GiftiException("Error seeking to \"" + AString::number(externalFileOffsetForReading) + "\" in \"" + externalFileNameForReading + "\""); } // // Set the number of bytes that must be read // std::streamsize numberOfBytesToRead = 0; char* pointerToForReadingData = NULL; switch (dataType) { case NiftiDataTypeEnum::NIFTI_TYPE_FLOAT32: numberOfBytesToRead = numElements * sizeof(float); pointerToForReadingData = (char*)dataPointerFloat; break; case NiftiDataTypeEnum::NIFTI_TYPE_INT32: numberOfBytesToRead = numElements * sizeof(int32_t); pointerToForReadingData = (char*)dataPointerInt; break; case NiftiDataTypeEnum::NIFTI_TYPE_UINT8: numberOfBytesToRead = numElements * sizeof(uint8_t); pointerToForReadingData = (char*)dataPointerUByte; break; default: throw GiftiException("DataType " + NiftiDataTypeEnum::toName(dataType) + " not supported in GIFTI"); } // // Read the data // extBinFile.read((char*)pointerToForReadingData, numberOfBytesToRead); if(extBinFile.good() == false) { throw GiftiException("Tried to read " + AString::number((int64_t)numberOfBytesToRead) + " from " + externalFileOffsetForReading + " but failed"); } // // Is byte swapping needed ? // if (endian != getSystemEndian()) { byteSwapData(getSystemEndian()); } } } break; } // // Check if data type needs to be converted // if (requiredDataType != dataType) { if (intent != NiftiIntentEnum::NIFTI_INTENT_POINTSET) { convertToDataType(requiredDataType); } } // // Are array indices in opposite order // if (arraySubscriptingOrderForReading == GiftiArrayIndexingOrderEnum::COLUMN_MAJOR_ORDER) { convertArrayIndexingOrder(); } } // If NOT metadata only setModified(); } /** * convert array indexing order of data. */ void GiftiDataArray::convertArrayIndexingOrder() { const int32_t numDim = static_cast(dimensions.size()); if (numDim > 2) { throw GiftiException("Row/Column Major order conversion unavailable for arrays " "with dimensions greater than two."); } // // Swap data // if (numDim == 2) { int32_t dimI = dimensions[0]; int32_t dimJ = dimensions[1]; /* * If a dimensions is "1", the data does not need to be transposed. */ if ((dimI != 1) && (dimJ != 1)) { // // Is matrix square? // if (dimI == dimJ) { switch (dataType) { case NiftiDataTypeEnum::NIFTI_TYPE_FLOAT32: { for (int64_t i = 1; i < dimI; i++) { for (int64_t j = 0; j < i; j++) { const int64_t indexLowerLeft = (i * dimJ) + j; const int64_t indexUpperRight = (j * dimI) + i; float temp = dataPointerFloat[indexLowerLeft]; dataPointerFloat[indexLowerLeft] = dataPointerFloat[indexUpperRight]; dataPointerFloat[indexUpperRight] = temp; } } } break; case NiftiDataTypeEnum::NIFTI_TYPE_INT32: { for (int64_t i = 1; i < dimI; i++) { for (int64_t j = 0; j < i; j++) { const int64_t indexLowerLeft = (i * dimJ) + j; const int64_t indexUpperRight = (j * dimI) + i; const int32_t temp = dataPointerInt[indexLowerLeft]; dataPointerInt[indexLowerLeft] = dataPointerInt[indexUpperRight]; dataPointerInt[indexUpperRight] = temp; } } } break; case NiftiDataTypeEnum::NIFTI_TYPE_UINT8: { for (int64_t i = 1; i < dimI; i++) { for (int64_t j = 0; j < i; j++) { const int64_t indexLowerLeft = (i * dimJ) + j; const int64_t indexUpperRight = (j * dimI) + i; const uint8_t temp = dataPointerUByte[indexLowerLeft]; dataPointerUByte[indexLowerLeft] = dataPointerUByte[indexUpperRight]; dataPointerUByte[indexUpperRight] = temp; } } } break; default: throw GiftiException("DataType " + NiftiDataTypeEnum::toName(dataType) + " not supported in GIFTI"); break; } } else { // // Copy the data // std::vector dataCopy = data; switch (arraySubscriptingOrder) { case GiftiArrayIndexingOrderEnum::ROW_MAJOR_ORDER: switch (dataType) { case NiftiDataTypeEnum::NIFTI_TYPE_FLOAT32: { float* ptr = (float*)&(dataCopy[0]); for (int64_t i = 0; i < dimI; i++) { for (int64_t j = 0; j < dimJ; j++) { const int64_t indx = (j * dimI) + i; const int64_t ptrIndex = (i * dimJ) + j; dataPointerFloat[indx] = ptr[ptrIndex]; } } } break; case NiftiDataTypeEnum::NIFTI_TYPE_INT32: { uint32_t* ptr = (uint32_t*)&(dataCopy[0]); for (int64_t i = 0; i < dimI; i++) { for (int64_t j = 0; j < dimJ; j++) { const int64_t indx = (j * dimI) + i; const int64_t ptrIndex = (i * dimJ) + j; dataPointerInt[indx] = ptr[ptrIndex]; } } } break; case NiftiDataTypeEnum::NIFTI_TYPE_UINT8: { uint8_t* ptr = (uint8_t*)&(dataCopy[0]); for (int64_t i = 0; i < dimI; i++) { for (int64_t j = 0; j < dimJ; j++) { const int64_t indx = (j * dimI) + i; const int64_t ptrIndex = (i * dimJ) + j; dataPointerUByte[indx] = ptr[ptrIndex]; } } } break; default: throw GiftiException("DataType " + NiftiDataTypeEnum::toName(dataType) + " not supported in GIFTI"); break; } break; case GiftiArrayIndexingOrderEnum::COLUMN_MAJOR_ORDER: switch (dataType) { case NiftiDataTypeEnum::NIFTI_TYPE_FLOAT32: { float* ptr = (float*)&(dataCopy[0]); for (int64_t i = 0; i < dimI; i++) { for (int64_t j = 0; j < dimJ; j++) { const int64_t indx = (i * dimJ) + j; const int64_t ptrIndex = (j * dimI) + i; dataPointerFloat[indx] = ptr[ptrIndex]; } } } break; case NiftiDataTypeEnum::NIFTI_TYPE_INT32: { uint32_t* ptr = (uint32_t*)&(dataCopy[0]); for (int64_t i = 0; i < dimI; i++) { for (int64_t j = 0; j < dimJ; j++) { const int64_t indx = (i * dimJ) + j; const int64_t ptrIndex = (j * dimI) + i; dataPointerInt[indx] = ptr[ptrIndex]; } } } break; case NiftiDataTypeEnum::NIFTI_TYPE_UINT8: { uint8_t* ptr = (uint8_t*)&(dataCopy[0]); for (int64_t i = 0; i < dimI; i++) { for (int64_t j = 0; j < dimJ; j++) { const int64_t indx = (i * dimJ) + j; const int64_t ptrIndex = (j * dimI) + i; dataPointerUByte[indx] = ptr[ptrIndex]; } } } break; default: throw GiftiException("DataType " + NiftiDataTypeEnum::toName(dataType) + " not supported in GIFTI"); break; } break; } } } } switch (arraySubscriptingOrder) { case GiftiArrayIndexingOrderEnum::COLUMN_MAJOR_ORDER: arraySubscriptingOrder = GiftiArrayIndexingOrderEnum::ROW_MAJOR_ORDER; break; case GiftiArrayIndexingOrderEnum::ROW_MAJOR_ORDER: arraySubscriptingOrder = GiftiArrayIndexingOrderEnum::COLUMN_MAJOR_ORDER; break; } } /** * write the data as XML. * @param stream * Stream for XML. * @param externalBinaryOutputStream * Stream for external binary file. * @param encodingForWriting * GIFTI encoding used when writing the data. */ void GiftiDataArray::writeAsXML(std::ostream& stream, std::ostream* externalBinaryOutputStream, GiftiEncodingEnum::Enum encodingForWriting) { this->encoding = encodingForWriting; // // Do not write if data array is isEmpty() // const int64_t numRows = this->dimensions[0]; if (numRows <= 0) { return; } XmlWriter xmlWriter(stream); // // Clean up the dimensions by removing any "last" dimensions that // are one with the exception of the first dimension // e.g.: dimension = [73730, 1] becomes [73730] // int64_t dimensionality = static_cast(dimensions.size()); for (int64_t i = (dimensionality - 1); i >= 1; i--) { if (dimensions[i] <= 1) { dimensions.resize(i); } } dimensionality = static_cast(dimensions.size()); /* * Push the palette color mapping into the metadata. */ if (this->paletteColorMapping != NULL) { const AString paletteXML = this->paletteColorMapping->encodeInXML(); this->getMetaData()->set(GiftiMetaDataXmlElements::METADATA_NAME_PALETTE_COLOR_MAPPING, paletteXML); } // // External file not supported // //const AString externalFileName = ""; //const AString externalFileOffset = "0"; // // Write the opening tag // XmlAttributes dataAtt; dataAtt.addAttribute(GiftiXmlElements::ATTRIBUTE_DATA_ARRAY_INTENT, NiftiIntentEnum::toName(this->intent)); dataAtt.addAttribute(GiftiXmlElements::ATTRIBUTE_DATA_ARRAY_DATA_TYPE, NiftiDataTypeEnum::toName(this->dataType)); dataAtt.addAttribute(GiftiXmlElements::ATTRIBUTE_DATA_ARRAY_INDEXING_ORDER, GiftiArrayIndexingOrderEnum::toGiftiName(this->arraySubscriptingOrder)); dataAtt.addAttribute(GiftiXmlElements::ATTRIBUTE_DATA_ARRAY_DIMENSIONALITY, dimensionality); for (int64_t i = 0; i < dimensionality; i++) { const AString dimName = GiftiXmlElements::ATTRIBUTE_DATA_ARRAY_DIM_PREFIX + AString::number(i); dataAtt.addAttribute(dimName, this->dimensions[i]); } dataAtt.addAttribute(GiftiXmlElements::ATTRIBUTE_DATA_ARRAY_ENCODING, GiftiEncodingEnum::toGiftiName(this->encoding)); dataAtt.addAttribute(GiftiXmlElements::ATTRIBUTE_DATA_ARRAY_ENDIAN, GiftiEndianEnum::toGiftiName(this->endian)); dataAtt.addAttribute(GiftiXmlElements::ATTRIBUTE_DATA_ARRAY_EXTERNAL_FILE_NAME, externalFileName); dataAtt.addAttribute(GiftiXmlElements::ATTRIBUTE_DATA_ARRAY_EXTERNAL_FILE_OFFSET, externalFileOffset); xmlWriter.writeStartElement(GiftiXmlElements::TAG_DATA_ARRAY, dataAtt); // // Write the metadata // this->metaData.writeAsXML(xmlWriter); /* * Write one identity matrix for the transformation matrix. * * We do not want to write the transformation matrices that * were read from the file as the first matrix was applied to * the coordinates when the file was read. To do so would require * applying an inverse of the matrix. However, the user may have * modified the coordinates so the matrix is no longer valid for * the file's coordinates. */ if (getIntent() == NiftiIntentEnum::NIFTI_INTENT_POINTSET) { Matrix4x4 identityMatrix; identityMatrix.identity(); identityMatrix.setDataSpaceName(NiftiTransformEnum::toName(NiftiTransformEnum::NIFTI_XFORM_TALAIRACH)); identityMatrix.setTransformedSpaceName(NiftiTransformEnum::toName(NiftiTransformEnum::NIFTI_XFORM_TALAIRACH)); identityMatrix.writeAsGiftiXML(xmlWriter, GiftiXmlElements::TAG_COORDINATE_TRANSFORMATION_MATRIX, GiftiXmlElements::TAG_MATRIX_DATA_SPACE, GiftiXmlElements::TAG_MATRIX_TRANSFORMED_SPACE, GiftiXmlElements::TAG_MATRIX_DATA); } // // NOTE: for the base64 and ZLIB-Base64 data, it is important that there are // no spaces between the and tags. // switch (encoding) { case GiftiEncodingEnum::ASCII: { // // Write the start element. // xmlWriter.writeStartElement(GiftiXmlElements::TAG_DATA); // // Newline after tag (only do this for ASCII !!!) // //stream << "\n"; // // determine the number of items per row (node) // int64_t numItemsPerRow = 1; for (uint32_t i = 1; i < dimensions.size(); i++) { numItemsPerRow *= dimensions[i]; } // // Write the rows // int32_t offset = 0; for (int32_t i = 0; i < numRows; i++) { xmlWriter.writeCharacters(" "); switch (dataType) { case NiftiDataTypeEnum::NIFTI_TYPE_FLOAT32: for (int64_t j = 0; j < numItemsPerRow; j++) { xmlWriter.writeCharacters(AString::number(this->dataPointerFloat[offset + j])); xmlWriter.writeCharacters(" "); } break; case NiftiDataTypeEnum::NIFTI_TYPE_INT32: for (int64_t j = 0; j < numItemsPerRow; j++) { xmlWriter.writeCharacters(AString::number(this->dataPointerInt[offset + j])); xmlWriter.writeCharacters(" "); } break; case NiftiDataTypeEnum::NIFTI_TYPE_UINT8: for (int64_t j = 0; j < numItemsPerRow; j++) { xmlWriter.writeCharacters(AString::number(this->dataPointerUByte[offset + j])); xmlWriter.writeCharacters(" "); } break; default: throw GiftiException("DataType " + NiftiDataTypeEnum::toName(dataType) + " not supported in GIFTI"); break; } xmlWriter.writeCharacters("\n"); offset += numItemsPerRow; } // // Write the closing Data tag // xmlWriter.writeEndElement(); } break; case GiftiEncodingEnum::BASE64_BINARY: { // // Encode the data with VTK's Base64 algorithm // const uint64_t bufferLength = static_cast(data.size() * 1.5); char* buffer = new char[bufferLength]; const uint64_t compressedLength = Base64::encode(&data[0], data.size(), (unsigned char*)buffer); if (compressedLength >= bufferLength) { throw GiftiException( "Base64 encoding buffer length (" + AString::number(bufferLength) + ") is too small but needs to be " + AString::number(compressedLength)); } buffer[compressedLength] = '\0'; // // Write the data MUST BE NO space around data // xmlWriter.writeElementNoSpace(GiftiXmlElements::TAG_DATA, buffer); // // Free memory // delete[] buffer; } break; case GiftiEncodingEnum::GZIP_BASE64_BINARY: { // // Compress the data with VTK's ZLIB algorithm // DataCompressZLib compressor; unsigned long compressedDataBufferLength = compressor.getMaximumCompressionSpace(data.size()); unsigned char* compressedDataBuffer = new unsigned char[compressedDataBufferLength]; unsigned long compressedDataLength = compressor.compressData(&data[0], data.size(), compressedDataBuffer, compressedDataBufferLength); // // Encode the data with VTK's Base64 algorithm // char* buffer = new char[static_cast(compressedDataLength * 1.5)]; const uint64_t compressedLength = Base64::encode(compressedDataBuffer, compressedDataLength, (unsigned char*)buffer); buffer[compressedLength] = '\0'; // // Write the data MUST BE NO space around data // xmlWriter.writeElementNoSpace(GiftiXmlElements::TAG_DATA, buffer); // // Free memory // delete[] buffer; delete[] compressedDataBuffer; } break; case GiftiEncodingEnum::EXTERNAL_FILE_BINARY: { const int64_t dataLength = data.size(); externalBinaryOutputStream->write((const char*)&data[0], dataLength); if (externalBinaryOutputStream->bad()) { throw GiftiException("Output stream for external file reports its status as bad."); } // // Write the empty data // xmlWriter.writeElementNoSpace(GiftiXmlElements::TAG_DATA, ""); } break; } // // write the closing data array tag // xmlWriter.writeEndElement(); } /** * convert to data type. */ void GiftiDataArray::convertToDataType(const NiftiDataTypeEnum::Enum newDataType) { if (newDataType != dataType) { // // make a copy of myself // GiftiDataArray copyOfMe(*this); // // Set my new data type and reallocate memory // const NiftiDataTypeEnum::Enum oldDataType = dataType; dataType = newDataType; allocateData(); if (data.empty() == false) { // // Get total number of elements // int64_t numElements = 1; for (uint32_t i = 0; i < dimensions.size(); i++) { numElements *= dimensions[i]; } // // copy the data // for (int64_t i = 0; i < numElements; i++) { switch (dataType) { case NiftiDataTypeEnum::NIFTI_TYPE_FLOAT32: switch (oldDataType) { case NiftiDataTypeEnum::NIFTI_TYPE_FLOAT32: dataPointerFloat[i] = static_cast(copyOfMe.dataPointerFloat[i]); break; case NiftiDataTypeEnum::NIFTI_TYPE_INT32: dataPointerFloat[i] = static_cast(copyOfMe.dataPointerInt[i]); break; case NiftiDataTypeEnum::NIFTI_TYPE_UINT8: dataPointerFloat[i] = static_cast(copyOfMe.dataPointerUByte[i]); break; default: throw GiftiException("DataType " + NiftiDataTypeEnum::toName(oldDataType) + " not supported in GIFTI"); break; } break; case NiftiDataTypeEnum::NIFTI_TYPE_INT32: switch (oldDataType) { case NiftiDataTypeEnum::NIFTI_TYPE_FLOAT32: dataPointerInt[i] = static_cast(copyOfMe.dataPointerFloat[i]); break; case NiftiDataTypeEnum::NIFTI_TYPE_INT32: dataPointerInt[i] = static_cast(copyOfMe.dataPointerInt[i]); break; case NiftiDataTypeEnum::NIFTI_TYPE_UINT8: dataPointerInt[i] = static_cast(copyOfMe.dataPointerUByte[i]); break; default: throw GiftiException("DataType " + NiftiDataTypeEnum::toName(oldDataType) + " not supported in GIFTI"); break; } break; case NiftiDataTypeEnum::NIFTI_TYPE_UINT8: switch (oldDataType) { case NiftiDataTypeEnum::NIFTI_TYPE_FLOAT32: dataPointerUByte[i] = static_cast(copyOfMe.dataPointerFloat[i]); break; case NiftiDataTypeEnum::NIFTI_TYPE_INT32: dataPointerUByte[i] = static_cast(copyOfMe.dataPointerInt[i]); break; case NiftiDataTypeEnum::NIFTI_TYPE_UINT8: dataPointerUByte[i] = static_cast(copyOfMe.dataPointerUByte[i]); break; default: throw GiftiException("DataType " + NiftiDataTypeEnum::toName(oldDataType) + " not supported in GIFTI"); break; } break; default: throw GiftiException("DataType " + NiftiDataTypeEnum::toName(dataType) + " not supported in GIFTI"); break; } } } } setModified(); } /** * byte swap the data (data read is different endian than this system). */ void GiftiDataArray::byteSwapData(const GiftiEndianEnum::Enum newEndian) { endian = newEndian; switch (dataType) { case NiftiDataTypeEnum::NIFTI_TYPE_FLOAT32: ByteSwapping::swapBytes(dataPointerFloat, getTotalNumberOfElements()); break; case NiftiDataTypeEnum::NIFTI_TYPE_INT32: ByteSwapping::swapBytes(dataPointerInt, getTotalNumberOfElements()); break; case NiftiDataTypeEnum::NIFTI_TYPE_UINT8: // should not need to swap ?? break; default: CaretAssertMessage(0, "Unsupported GIFTI data Type"); break; } } /** * Set this object has been modified. * */ void GiftiDataArray::setModified() { this->modifiedFlag = true; if (this->descriptiveStatistics != NULL) { delete this->descriptiveStatistics; this->descriptiveStatistics = NULL; } if (this->descriptiveStatisticsLimitedValues != NULL) { delete this->descriptiveStatisticsLimitedValues; this->descriptiveStatisticsLimitedValues = NULL; } } /** * Set this object as not modified. Object should also * clear the modification status of its children. * */ void GiftiDataArray::clearModified() { this->modifiedFlag = false; if (this->paletteColorMapping != NULL) { this->paletteColorMapping->clearModified(); } } /** * Get the modification status. Returns true if this object or * any of its children have been modified. * * DOES NOT include palette color mapping modification status. * * @return - The modification status. * */ bool GiftiDataArray::isModified() const { // if (this->paletteColorMapping != NULL) { // if (this->paletteColorMapping->isModified()) { // return true; // } // } return this->modifiedFlag; } /** * get minimum and maximum values (valid for int data only). */ void GiftiDataArray::getMinMaxValues(int& minValue, int& maxValue) const { if (minMaxIntValuesValid == false) { minValueInt = std::numeric_limits::max(); minValueInt = std::numeric_limits::min(); int64_t numItems = getTotalNumberOfElements(); for (int64_t i = 0; i < numItems; i++) { minValueInt = std::min(minValueInt, dataPointerInt[i]); maxValueInt = std::max(maxValueInt, dataPointerInt[i]); } minMaxIntValuesValid = true; } minValue = minValueInt; maxValue = maxValueInt; } /** * Get the minimum and maximum values for float data. * * @param minValue * Output minimum value. * @param maxValue * Output maximum value. */ void GiftiDataArray::getMinMaxValuesFloat(float& minValue, float& maxValue) const { if (minMaxFloatValuesValid == false) { minValueFloat = std::numeric_limits::max(); maxValueFloat = -std::numeric_limits::max(); int64_t numItems = getTotalNumberOfElements(); for (int64_t i = 0; i < numItems; i++) { minValueFloat = std::min(minValueFloat, dataPointerFloat[i]); maxValueFloat = std::max(maxValueFloat, dataPointerFloat[i]); } minMaxFloatValuesValid = true; } minValue = minValueFloat; maxValue = maxValueFloat; } /** * set all elements of array to zero. */ void GiftiDataArray::zeroize() { if (data.empty() == false) { std::fill(data.begin(), data.end(), 0); } metaData.clear(); nonWrittenMetaData.clear(); } /** * get an offset for indices into data (dimensionality of indices must be same as data). */ int64_t GiftiDataArray::getDataOffset(const int32_t indices[]) const { const int32_t numDim = static_cast(dimensions.size()); int64_t offset = 0; int64_t dimProduct = 1; switch (this->arraySubscriptingOrder) { case GiftiArrayIndexingOrderEnum::ROW_MAJOR_ORDER: for (int32_t d = (numDim - 1); d >= 0; d--) { offset += indices[d] * dimProduct; dimProduct *= dimensions[d]; } break; case GiftiArrayIndexingOrderEnum::COLUMN_MAJOR_ORDER: // correct??? for (int32_t d = 0; d <= (numDim - 1); d++) { offset += indices[d] * dimProduct; dimProduct *= dimensions[d]; } break; } return offset; } /** * get a float value (data type must be float and dimensionality of indices must be same as data). */ float GiftiDataArray::getDataFloat32(const int32_t indices[]) const { const int64_t offset = getDataOffset(indices); return dataPointerFloat[offset]; } /** * get a float value pointer(data type must be float and dimensionality of indices must be same as data). */ const float* GiftiDataArray::getDataFloat32Pointer(const int32_t indices[]) const { const int64_t offset = getDataOffset(indices); return &dataPointerFloat[offset]; } /** * get an int value (data type must be int and dimensionality of indices must be same as data). */ int32_t GiftiDataArray::getDataInt32(const int32_t indices[]) const { const int64_t offset = getDataOffset(indices); return dataPointerInt[offset]; } /** * get an int value pointer (data type must be int and dimensionality of indices must be same as data). */ const int32_t* GiftiDataArray::getDataInt32Pointer(const int32_t indices[]) const { const int64_t offset = getDataOffset(indices); return &dataPointerInt[offset]; } /** * get a byte value (data type must be unsigned char and dimensionality of indices must be same as data). */ uint8_t GiftiDataArray::getDataUInt8(const int32_t indices[]) const { const int64_t offset = getDataOffset(indices); return dataPointerUByte[offset]; } /** * get a byte value pointer(data type must be unsigned char and dimensionality of indices must be same as data). */ const uint8_t* GiftiDataArray::getDataUInt8Pointer(const int32_t indices[]) const { const int64_t offset = getDataOffset(indices); return &dataPointerUByte[offset]; } /** * set a float value (data type must be float and dimensionality of indices must be same as data). */ void GiftiDataArray::setDataFloat32(const int32_t indices[], const float dataValue) const { const int64_t offset = getDataOffset(indices); dataPointerFloat[offset] = dataValue; } /** * set an int value (data type must be int and dimensionality of indices must be same as data). */ void GiftiDataArray::setDataInt32(const int32_t indices[], const int32_t dataValue) const { const int64_t offset = getDataOffset(indices); dataPointerInt[offset] = dataValue; } /** * set a byte value (data type must be unsigned char and dimensionality of indices must be same as data). */ void GiftiDataArray::setDataUInt8(const int32_t indices[], const uint8_t dataValue) const { const int64_t offset = getDataOffset(indices); dataPointerUByte[offset] = dataValue; } /** * valid intent name. */ bool GiftiDataArray::intentNameValid(const AString& intentNameIn) { bool valid = false; NiftiIntentEnum::fromName(intentNameIn, &valid); return valid; } /** * remove all matrices. */ void GiftiDataArray::removeAllMatrices() { matrices.clear(); setModified(); } /** * remove a matrix. */ void GiftiDataArray::removeMatrix(const int32_t indx) { matrices.erase(matrices.begin() + indx); setModified(); } /** * Get the color palette mapping. * * @return * The palette color mapping. */; PaletteColorMapping* GiftiDataArray::getPaletteColorMapping() { if (this->paletteColorMapping == NULL) { this->paletteColorMapping = new PaletteColorMapping(); const AString paletteString = this->getMetaData()->get(GiftiMetaDataXmlElements::METADATA_NAME_PALETTE_COLOR_MAPPING); if (paletteString.isEmpty() == false) { try { this->paletteColorMapping->decodeFromStringXML(paletteString); } catch (const XmlException& e) { this->paletteColorMapping = new PaletteColorMapping(); CaretLogSevere("Failed to parse Palette XML: " + e.whatString()); } } this->paletteColorMapping->clearModified(); } return this->paletteColorMapping; } /** * Get the color palette mapping. * * @return * The palette color mapping. */; const PaletteColorMapping* GiftiDataArray::getPaletteColorMapping() const { if (this->paletteColorMapping == NULL) { this->paletteColorMapping = new PaletteColorMapping(); const AString paletteString = this->getMetaData()->get(GiftiMetaDataXmlElements::METADATA_NAME_PALETTE_COLOR_MAPPING); if (paletteString.isEmpty() == false) { try { this->paletteColorMapping->decodeFromStringXML(paletteString); } catch (const XmlException& e) { this->paletteColorMapping = new PaletteColorMapping(); CaretLogSevere("Failed to parse Palette XML: " + e.whatString()); } } this->paletteColorMapping->clearModified(); } return this->paletteColorMapping; } /** * Get the descriptive statistics for this data array. * * @return Descriptive statistics. */ const DescriptiveStatistics* GiftiDataArray::getDescriptiveStatistics() const { if (this->getDataType() == NiftiDataTypeEnum::NIFTI_TYPE_FLOAT32) { if (this->descriptiveStatistics == NULL) { this->descriptiveStatistics = new DescriptiveStatistics(); } this->descriptiveStatistics->update(this->dataPointerFloat, this->getTotalNumberOfElements()); } return this->descriptiveStatistics; } const FastStatistics* GiftiDataArray::getFastStatistics() const { if (this->getDataType() == NiftiDataTypeEnum::NIFTI_TYPE_FLOAT32) { if (m_fastStatistics == NULL) { m_fastStatistics.grabNew(new FastStatistics()); } m_fastStatistics->update(dataPointerFloat, getTotalNumberOfElements()); } return m_fastStatistics; } const Histogram* GiftiDataArray::getHistogram() const { if (this->getDataType() == NiftiDataTypeEnum::NIFTI_TYPE_FLOAT32) { if (m_histogram == NULL) { m_histogram.grabNew(new Histogram(100)); } m_histogram->update(dataPointerFloat, getTotalNumberOfElements()); } return m_histogram; } /** * Get the descriptive statistics for this data array limited * to values within the given ranges. * * @param mostPositiveValueInclusive * Values more positive than this value are excluded. * @param leastPositiveValueInclusive * Values less positive than this value are excluded. * @param leastNegativeValueInclusive * Values less negative than this value are excluded. * @param mostNegativeValueInclusive * Values more negative than this value are excluded. * @param includeZeroValues * If true zero values (very near zero) are included. * @return Descriptive statistics. */ const DescriptiveStatistics* GiftiDataArray::getDescriptiveStatistics(const float mostPositiveValueInclusive, const float leastPositiveValueInclusive, const float leastNegativeValueInclusive, const float mostNegativeValueInclusive, const bool includeZeroValues) const { if (this->getDataType() == NiftiDataTypeEnum::NIFTI_TYPE_FLOAT32) { if (this->descriptiveStatisticsLimitedValues == NULL) { this->descriptiveStatisticsLimitedValues = new DescriptiveStatistics(); } this->descriptiveStatisticsLimitedValues->update(this->dataPointerFloat, this->getTotalNumberOfElements(), mostPositiveValueInclusive, leastPositiveValueInclusive, leastNegativeValueInclusive, mostNegativeValueInclusive, includeZeroValues); } return this->descriptiveStatisticsLimitedValues; } const Histogram* GiftiDataArray::getHistogram(const float mostPositiveValueInclusive, const float leastPositiveValueInclusive, const float leastNegativeValueInclusive, const float mostNegativeValueInclusive, const bool includeZeroValues) const { if (this->getDataType() == NiftiDataTypeEnum::NIFTI_TYPE_FLOAT32) { if (m_histogramLimitedValues == NULL) { m_histogramLimitedValues.grabNew(new Histogram(100)); } m_histogramLimitedValues->update(dataPointerFloat, getTotalNumberOfElements(), mostPositiveValueInclusive, leastPositiveValueInclusive, leastNegativeValueInclusive, mostNegativeValueInclusive, includeZeroValues); } return m_histogramLimitedValues; } AString GiftiDataArray::toString() const { std::ostringstream str; str << "Data Array" << std::endl; str << " DataType=" << NiftiDataTypeEnum::toName(this->dataType).toStdString() << std::endl; str << " Intent=" << NiftiIntentEnum::toName(this->intent).toStdString() << std::endl; str << " Dimensions=" << AString::fromNumbers(this->dimensions, ",").toStdString(); str << " MetaData=" << this->metaData.toString().toStdString() << std::endl; return AString::fromStdString(str.str()); } void GiftiDataArray::validateArrayAfterReading() { //pointset arrays are mandated to have at least one tranfsormation matrix, for some unknown reason if (intent == NiftiIntentEnum::NIFTI_INTENT_POINTSET && matrices.size() == 0) { CaretLogWarning("pointset gifti array did not include a transformation matrix, adding identity transform"); Matrix4x4 gm; gm.setDataSpaceName("NIFTI_XFORM_TALAIRACH"); gm.setTransformedSpaceName("NIFTI_XFORM_TALAIRACH"); matrices.push_back(gm); } } workbench-1.1.1/src/Gifti/GiftiDataArray.h000066400000000000000000000411451255417355300203500ustar00rootroot00000000000000 #ifndef __GIFTI_DATA_ARRAY_H__ #define __GIFTI_DATA_ARRAY_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #include "CaretObject.h" #include "CaretPointer.h" #include "DescriptiveStatistics.h" #include "FastStatistics.h" #include "GiftiArrayIndexingOrderEnum.h" #include "GiftiEncodingEnum.h" #include "GiftiEndianEnum.h" #include "GiftiLabelTable.h" #include "GiftiMetaData.h" #include "Histogram.h" #include "Matrix4x4.h" #include "NiftiEnums.h" #include "TracksModificationInterface.h" namespace caret { class GiftiFile; class GiftiException; class PaletteColorMapping; /// class GiftiDataArray. class GiftiDataArray : public CaretObject, TracksModificationInterface { public: // constructor GiftiDataArray(const NiftiIntentEnum::Enum intentIn, const NiftiDataTypeEnum::Enum dataTypeIn, const std::vector& dimensionsIn, const GiftiEncodingEnum::Enum encodingIn = GiftiEncodingEnum::ASCII); // constructor used when reading data GiftiDataArray(const NiftiIntentEnum::Enum intentIn); // copy constructor GiftiDataArray(const GiftiDataArray& nda); // assignment operator GiftiDataArray& operator=(const GiftiDataArray& nda); // destructor virtual ~GiftiDataArray(); // add rows (increase 1st dimension) void addRows(const int32_t numRowsToAdd); // delete rows void deleteRows(const std::vector& rowsToDelete); // convert all data arrays to data type void convertToDataType(const NiftiDataTypeEnum::Enum newDataType) ; // set the dimensions void setDimensions(const std::vector dimensionsIn); // reset column virtual void clear(); /// get the number of dimensions int32_t getNumberOfDimensions() const { return dimensions.size(); } /// get the dimensions std::vector getDimensions() const { return dimensions; } /// current size of the data (in bytes) int64_t getDataSizeInBytes() const { return data.size(); } /// get a dimension int32_t getDimension(const int32_t dimIndex) const { return dimensions[dimIndex]; } // get the number of rows (1st dimension) int64_t getNumberOfRows() const; // get number of components per node (2nd dimension) int64_t getNumberOfComponents() const; // get the total number of elements int64_t getTotalNumberOfElements() const; // get data offset //int64_t getDataOffset(const int64_t nodeNum, const int64_t componentNum) const;//TSC: implementation was wrong, commenting out for now // read a data array from text void readFromText(const AString text, const GiftiEndianEnum::Enum dataEndianForReading, const GiftiArrayIndexingOrderEnum::Enum arraySubscriptingOrderForReading, const NiftiDataTypeEnum::Enum dataTypeForReading, const std::vector& dimensionsForReading, const GiftiEncodingEnum::Enum encodingForReading, const AString& externalFileNameForReading, const int64_t externalFileOffsetForReading, const bool isReadOnlyMetaData); // write the data as XML void writeAsXML(std::ostream& stream, std::ostream* externalBinaryOutputStream, GiftiEncodingEnum::Enum encodingForWriting); /// get endian GiftiEndianEnum::Enum getEndian() const { return endian; } // set endian void setEndian(const GiftiEndianEnum::Enum e) { endian = e; } /// get the system's endian static GiftiEndianEnum::Enum getSystemEndian(); // get external file information void getExternalFileInformation(AString& nameOut, int64_t& offsetOut) const; // set external file information void setExternalFileInformation(const AString& nameIn, const int64_t offsetIn); /// get the metadata GiftiMetaData* getMetaData() { return &metaData; } /// get the metadata (const method) const GiftiMetaData* getMetaData() const { return &metaData; } /// set the metadata void setMetaData(const GiftiMetaData* gmd) { metaData = *gmd; setModified(); } /// get the number of matrices int32_t getNumberOfMatrices() const { return matrices.size(); } /// add a matrix void addMatrix(const Matrix4x4& gm) { matrices.push_back(gm); } /// get a matrix Matrix4x4* getMatrix(const int32_t indx) { return &matrices[indx]; } /// remove all matrices void removeAllMatrices(); /// remove a matrix void removeMatrix(const int32_t indx); /// get the matrix (const method) const Matrix4x4* getMatrix(const int32_t indx) const { return &matrices[indx]; } /// get the non-written metadata for values not saved to file //GiftiMetaData* getNonWrittenMetaData() { return &nonWrittenMetaData; } /// get the non-written metadata for values not save to file (const method) //const GiftiMetaData* getNonWrittenMetaData() const { return &nonWrittenMetaData; } /// get the data type NiftiDataTypeEnum::Enum getDataType() const { return dataType; } /// set the data type void setDataType(const NiftiDataTypeEnum::Enum dt) { dataType = dt; setModified(); } /// get the encoding GiftiEncodingEnum::Enum getEncoding() const { return encoding; } /// set the encoding void setEncoding(const GiftiEncodingEnum::Enum e) { encoding = e; setModified(); } /// get the data intent NiftiIntentEnum::Enum getIntent() const { return intent; } /// set the data intent void setIntent(const NiftiIntentEnum::Enum cat) { intent = cat; setModified(); } /// valid intent name static bool intentNameValid(const AString& intentNameIn); /// get array subscripting order GiftiArrayIndexingOrderEnum::Enum getArraySubscriptingOrder() const { return arraySubscriptingOrder; } /// set array subscripting order void setArraySubscriptingOrder(const GiftiArrayIndexingOrderEnum::Enum aso) { arraySubscriptingOrder = aso; } /// get pointer for floating point data (valid only if data type is FLOAT) float* getDataPointerFloat() { return dataPointerFloat; } /// get pointer for floating point data (const method) (valid only if data type is FLOAT) const float* getDataPointerFloat() const { return dataPointerFloat; } /// get pointer for integer data (valid only if data type is INT) int32_t* getDataPointerInt() { return dataPointerInt; } /// get pointer for integer data (const method) (valid only if data type is INT) const int32_t* getDataPointerInt() const { return dataPointerInt; } /// get pointer for unsigned byte data (valid only if data type is UBYTE) uint8_t* getDataPointerUByte() { return dataPointerUByte; } /// get pointer for unsigned byte data (const method) (valid only if data type is UBYTE) const uint8_t* getDataPointerUByte() const { return dataPointerUByte; } // set all elements of array to zero void zeroize(); // get minimum and maximum values (valid for int data only) void getMinMaxValues(int& minValue, int& maxValue) const; void getMinMaxValuesFloat(float& minValue, float& maxValue) const; // remap integer values that are indices to a table //void remapIntValues(const std::vector& remappingTable); void transferLabelIndices(const std::map& indexConverter); // get the data type appropriate for the intent (returns true if valid intent) static bool getDataTypeAppropriateForIntent(const NiftiIntentEnum::Enum intentIn, NiftiDataTypeEnum::Enum& dataTypeOut); // get an offset for indices into data (dimensionality of indices must be same as data) int64_t getDataOffset(const int32_t indices[]) const; // get a float value (data type must be float and dimensionality of indices must be same as data) float getDataFloat32(const int32_t indices[]) const; // get a float value pointer (data type must be float and dimensionality of indices must be same as data) const float* getDataFloat32Pointer(const int32_t indices[]) const; // get an int value (data type must be int and dimensionality of indices must be same as data) int32_t getDataInt32(const int32_t indices[]) const; // get an int value pointer(data type must be int and dimensionality of indices must be same as data) const int32_t* getDataInt32Pointer(const int32_t indices[]) const; // get a byte value (data type must be unsigned char and dimensionality of indices must be same as data) uint8_t getDataUInt8(const int32_t indices[]) const; // get a byte value pointer (data type must be unsigned char and dimensionality of indices must be same as data) const uint8_t* getDataUInt8Pointer(const int32_t indices[]) const; // set a float value (data type must be float and dimensionality of indices must be same as data) void setDataFloat32(const int32_t indices[], const float dataValue) const; // set an int value (data type must be int and dimensionality of indices must be same as data) void setDataInt32(const int32_t indices[], const int32_t dataValue) const; // set a byte value (data type must be unsigned char and dimensionality of indices must be same as data) void setDataUInt8(const int32_t indices[], const uint8_t dataValue) const; void setModified(); void clearModified(); bool isModified() const; virtual AString toString() const; PaletteColorMapping* getPaletteColorMapping(); const PaletteColorMapping* getPaletteColorMapping() const; const DescriptiveStatistics* getDescriptiveStatistics() const; const FastStatistics* getFastStatistics() const; const Histogram* getHistogram() const; const DescriptiveStatistics* getDescriptiveStatistics(const float mostPositiveValueInclusive, const float leastPositiveValueInclusive, const float leastNegativeValueInclusive, const float mostNegativeValueInclusive, const bool includeZeroValues) const; const Histogram* getHistogram(const float mostPositiveValueInclusive, const float leastPositiveValueInclusive, const float leastNegativeValueInclusive, const float mostNegativeValueInclusive, const bool includeZeroValues) const; protected: //validate the array void validateArrayAfterReading(); // allocate data for this column virtual void allocateData(); // the copy helper (used by copy constructor and assignment operator) void copyHelperGiftiDataArray(const GiftiDataArray& nda); // update the data pointers void updateDataPointers(); // byte swap the data (data read is different endian than this system) void byteSwapData(const GiftiEndianEnum::Enum newEndian); /// convert array indexing order of data void convertArrayIndexingOrder(); /// the data std::vector data; /// size of one data type element uint32_t dataTypeSize; /// pointer for floating point data float* dataPointerFloat; /// pointer for integer data int32_t* dataPointerInt; /// pointer for unsigned byte data uint8_t* dataPointerUByte; /// the matrix (typically only used by coordinates) std::vector matrices; /// the metadata GiftiMetaData metaData; /// the metadata not written to file (mainly for file specific data array meta data) GiftiMetaData nonWrittenMetaData; /// dimensions of the data std::vector dimensions; /// data type NiftiDataTypeEnum::Enum dataType; /// encoding of data GiftiEncodingEnum::Enum encoding; // endian of data GiftiEndianEnum::Enum endian; /// intent name NiftiIntentEnum::Enum intent; /// array subscripting order GiftiArrayIndexingOrderEnum::Enum arraySubscriptingOrder; /// external file name AString externalFileName; /// external file offset int64_t externalFileOffset; /// the palette color mapping mutable PaletteColorMapping* paletteColorMapping; /// minimum float value mutable float minValueFloat; /// maximum float value mutable float maxValueFloat; /// min/max float values valid (child class must set this false when an array value is changed) mutable bool minMaxFloatValuesValid; /// minimum int value mutable int32_t minValueInt; /// maximum int value mutable int32_t maxValueInt; /// min/max int values valid (child class must set this false when an array value is changed) mutable bool minMaxIntValuesValid; mutable float negMaxPct; mutable float negMinPct; mutable float posMinPct; mutable float posMaxPct; mutable float negMaxPctValue; mutable float negMinPctValue; mutable float posMinPctValue; mutable float posMaxPctValue; /// min/max percentage values valid mutable bool minMaxPercentageValuesValid; /// statistics about data (DO NOT COPY) mutable DescriptiveStatistics* descriptiveStatistics; mutable CaretPointer m_fastStatistics; mutable CaretPointer m_histogram; /// statistics about data (DO NOT COPY) mutable DescriptiveStatistics* descriptiveStatisticsLimitedValues; mutable CaretPointer m_histogramLimitedValues; bool modifiedFlag; // DO NOT COPY // ***** BE SURE TO UPDATE copyHelper() if elements are added ****** /// allow NodeDataFile access to protected elements friend class GiftiFile; }; } // namespace #endif // __GIFTI_DATA_ARRAY_H__ workbench-1.1.1/src/Gifti/GiftiEncodingEnum.cxx000066400000000000000000000116621255417355300214270ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __GIFTIENCODING_DECLARE__ #include "GiftiEncodingEnum.h" #undef __GIFTIENCODING_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * Constructor. * * @param e * An enumerated value. * @param name * Name of enumberated value. */ GiftiEncodingEnum::GiftiEncodingEnum( const Enum e, const int32_t integerCode, const AString& name, const AString& giftiName) { this->e = e; this->integerCode = integerCode; this->name = name; this->giftiName = giftiName; } /** * Destructor. */ GiftiEncodingEnum::~GiftiEncodingEnum() { } void GiftiEncodingEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(GiftiEncodingEnum(ASCII, -1, "ASCII", "ASCII")); enumData.push_back(GiftiEncodingEnum(BASE64_BINARY, -1, "BASE64_BINARY", "Base64Binary")); enumData.push_back(GiftiEncodingEnum(GZIP_BASE64_BINARY, -1, "GZIP_BASE64_BINARY", "GZipBase64Binary")); enumData.push_back(GiftiEncodingEnum(EXTERNAL_FILE_BINARY, -1, "EXTERNAL_FILE_BINARY", "ExternalFileBinary")); } /** * Find the data for and enumerated value. * @param e * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const GiftiEncodingEnum* GiftiEncodingEnum::findData(const Enum e) { initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const GiftiEncodingEnum* d = &enumData[i]; if (d->e == e) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param e * Enumerated value. * @return * String representing enumerated value. */ AString GiftiEncodingEnum::toName(Enum e) { initialize(); const GiftiEncodingEnum* gaio = findData(e); return gaio->name; } /** * Get an enumerated value corresponding to its name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ GiftiEncodingEnum::Enum GiftiEncodingEnum::fromName(const AString& s, bool* isValidOut) { initialize(); bool validFlag = false; Enum e = ASCII; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const GiftiEncodingEnum& d = *iter; if (d.name == s) { e = d.e; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("name \"" + s + " \"failed to match enumerated value for type GiftiEncodingEnum")); } return e; } /** * Get a string representation of the enumerated type. * @param e * Enumerated value. * @return * String representing enumerated value. */ AString GiftiEncodingEnum::toGiftiName(Enum e) { initialize(); const GiftiEncodingEnum* gaio = findData(e); return gaio->giftiName; } /** * Get an enumerated value corresponding to its name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ GiftiEncodingEnum::Enum GiftiEncodingEnum::fromGiftiName(const AString& s, bool* isValidOut) { initialize(); bool validFlag = false; Enum e = ASCII; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const GiftiEncodingEnum& d = *iter; if (d.giftiName == s) { e = d.e; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("giftiName \"" + s + " \"failed to match enumerated value for type GiftiEncodingEnum")); } return e; } workbench-1.1.1/src/Gifti/GiftiEncodingEnum.h000066400000000000000000000045031255417355300210500ustar00rootroot00000000000000#ifndef __GIFTIENCODING_H__ #define __GIFTIENCODING_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include namespace caret { /** * GIFTI Encoding Types. */ class GiftiEncodingEnum { public: /** GIFTI Encoding Types. */ enum Enum { /** The data is ASCII Text */ ASCII, /** THe data is binary data that is encoded as text using the Base64 Algorithm */ BASE64_BINARY, /** The data is binary compressed using the GZIP algorithm and then encoded as Base64 */ GZIP_BASE64_BINARY, /** The data is stored in a separate, uncompressed, binary data file */ EXTERNAL_FILE_BINARY }; ~GiftiEncodingEnum(); static AString toName(Enum e); static Enum fromName(const AString& s, bool* isValidOut); static AString toGiftiName(Enum e); static Enum fromGiftiName(const AString& s, bool* isValidOut); private: GiftiEncodingEnum(const Enum e, const int32_t integerCode, const AString& name, const AString& giftiName); static const GiftiEncodingEnum* findData(const Enum e); static std::vector enumData; static void initialize(); static bool initializedFlag; Enum e; int32_t integerCode; AString name; AString giftiName; }; #ifdef __GIFTIENCODING_DECLARE__ std::vector GiftiEncodingEnum::enumData; bool GiftiEncodingEnum::initializedFlag = false; #endif // __GIFTIENCODING_DECLARE__ } // namespace #endif // __GIFTIENCODING_H__ workbench-1.1.1/src/Gifti/GiftiEndianEnum.cxx000066400000000000000000000112701255417355300210720ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __GIFTIENDIAN_DECLARE__ #include "GiftiEndianEnum.h" #undef __GIFTIENDIAN_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * Constructor. * * @param e * An enumerated value. * @param name * Name of enumberated value. */ GiftiEndianEnum::GiftiEndianEnum( const Enum e, const int32_t integerCode, const AString& name, const AString& giftiName) { this->e = e; this->integerCode = integerCode; this->name = name; this->giftiName = giftiName; } /** * Destructor. */ GiftiEndianEnum::~GiftiEndianEnum() { } void GiftiEndianEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(GiftiEndianEnum(ENDIAN_BIG, 0, "ENDIAN_BIG", "BigEndian")); enumData.push_back(GiftiEndianEnum(ENDIAN_LITTLE, 1, "ENDIAN_LITTLE", "LittleEndian")); } /** * Find the data for and enumerated value. * @param e * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const GiftiEndianEnum* GiftiEndianEnum::findData(const Enum e) { initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const GiftiEndianEnum* d = &enumData[i]; if (d->e == e) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param e * Enumerated value. * @return * String representing enumerated value. */ AString GiftiEndianEnum::toName(Enum e) { initialize(); const GiftiEndianEnum* gaio = findData(e); return gaio->name; } /** * Get an enumerated value corresponding to its name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ GiftiEndianEnum::Enum GiftiEndianEnum::fromName(const AString& s, bool* isValidOut) { initialize(); bool validFlag = false; Enum e = ENDIAN_LITTLE; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const GiftiEndianEnum& d = *iter; if (d.name == s) { e = d.e; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("name \"" + s + " \"failed to match enumerated value for type GiftiEndianEnum")); } return e; } /** * Get a string representation of the enumerated type. * @param e * Enumerated value. * @return * String representing enumerated value. */ AString GiftiEndianEnum::toGiftiName(Enum e) { initialize(); const GiftiEndianEnum* gaio = findData(e); return gaio->giftiName; } /** * Get an enumerated value corresponding to its name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ GiftiEndianEnum::Enum GiftiEndianEnum::fromGiftiName(const AString& s, bool* isValidOut) { initialize(); bool validFlag = false; Enum e = ENDIAN_LITTLE; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const GiftiEndianEnum& d = *iter; if (d.giftiName == s) { e = d.e; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("giftiName \"" + s + " \"failed to match enumerated value for type GiftiEndianEnum")); } return e; } workbench-1.1.1/src/Gifti/GiftiEndianEnum.h000066400000000000000000000040761255417355300205250ustar00rootroot00000000000000#ifndef __GIFTIENDIAN_H__ #define __GIFTIENDIAN_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "GiftiException.h" #include #include #include namespace caret { /** * GIFTI Endian Types. */ class GiftiEndianEnum { public: /** GIFTI Endian Types. */ enum Enum { /** Data is in Big Endian byte order */ ENDIAN_BIG, /** Data is in Little Endian byte order */ ENDIAN_LITTLE }; ~GiftiEndianEnum(); static AString toName(Enum e); static Enum fromName(const AString& s, bool* isValidOut); static AString toGiftiName(Enum e); static Enum fromGiftiName(const AString& s, bool* isValidOut); private: GiftiEndianEnum(const Enum e, const int32_t integerCode, const AString& name, const AString& giftiName); static const GiftiEndianEnum* findData(const Enum e); static std::vector enumData; static void initialize(); static bool initializedFlag; Enum e; int32_t integerCode; AString name; AString giftiName; }; #ifdef __GIFTIENDIAN_DECLARE__ std::vector GiftiEndianEnum::enumData; bool GiftiEndianEnum::initializedFlag = false; #endif // __GIFTIENDIAN_DECLARE__ } // namespace #endif // __GIFTIENDIAN_H__ workbench-1.1.1/src/Gifti/GiftiFile.cxx000066400000000000000000001111621255417355300177270ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include "CaretAssert.h" #include "CaretLogger.h" #include "DataFileException.h" #include "FileInformation.h" #include "GiftiEncodingEnum.h" #define __GIFTI_FILE_MAIN__ #include "GiftiFile.h" #undef __GIFTI_FILE_MAIN__ #include "GiftiFileSaxReader.h" #include "GiftiMetaDataXmlElements.h" #include "GiftiFileWriter.h" #include "GiftiXmlElements.h" #include "XmlSaxParser.h" using namespace caret; /** * Constructor */ GiftiFile::GiftiFile(const AString& descriptiveName, const NiftiIntentEnum::Enum defaultDataArrayIntentIn, const NiftiDataTypeEnum::Enum defaultDataTypeIn, const AString& defaultExtension, const bool dataAreIndicesIntoLabelTableIn) : DataFile() { this->descriptiveName = descriptiveName; defaultDataArrayIntent = defaultDataArrayIntentIn; defaultDataType = defaultDataTypeIn; dataAreIndicesIntoLabelTable = dataAreIndicesIntoLabelTableIn; this->defaultExtension = defaultExtension; numberOfNodesForSparseNodeIndexFile = 0; this->encodingForWriting = GiftiFile::defaultEncodingForWriting; } /** * Constructor for generic gifti data array file */ GiftiFile::GiftiFile() : DataFile() { this->descriptiveName = "GIFTI"; defaultDataArrayIntent = NiftiIntentEnum::NIFTI_INTENT_NONE; defaultDataType = NiftiDataTypeEnum::NIFTI_TYPE_FLOAT32; dataAreIndicesIntoLabelTable = false; numberOfNodesForSparseNodeIndexFile = 0; this->defaultExtension = ".gii"; this->encodingForWriting = GiftiFile::defaultEncodingForWriting; } /** * copy constructor. */ GiftiFile::GiftiFile(const GiftiFile& nndf) : DataFile(nndf) { copyHelperGiftiFile(nndf); } /** * assignment operator. */ GiftiFile& GiftiFile::operator=(const GiftiFile& nndf) { if (this != &nndf) { DataFile::operator=(nndf); this->copyHelperGiftiFile(nndf); } return *this; } /** * copy helper. */ void GiftiFile::copyHelperGiftiFile(const GiftiFile& nndf) { labelTable = nndf.labelTable; metaData = nndf.metaData; defaultDataType = nndf.defaultDataType; defaultDataArrayIntent = nndf.defaultDataArrayIntent; dataAreIndicesIntoLabelTable = nndf.dataAreIndicesIntoLabelTable; numberOfNodesForSparseNodeIndexFile = nndf.numberOfNodesForSparseNodeIndexFile; this->descriptiveName = nndf.descriptiveName; this->defaultExtension = nndf.defaultExtension; int32_t numArrays = this->getNumberOfDataArrays(); for (int32_t i = (numArrays - 1); i >= 0; i--) { this->removeDataArray(i); } for (std::size_t i = 0; i < nndf.dataArrays.size(); i++) { addDataArray(new GiftiDataArray(*nndf.dataArrays[i])); } this->encodingForWriting = nndf.encodingForWriting; } /** * Destructor */ GiftiFile::~GiftiFile() { for (uint64_t i = 0; i < this->dataArrays.size(); i++) { delete this->dataArrays[i]; } } /** * compare a file for unit testing (returns true if "within tolerance"). */ bool GiftiFile::compareFileForUnitTesting(const GiftiFile* gf, const float tolerance, AString& messageOut) const { messageOut = ""; if (gf == NULL) { messageOut += "ERROR: File for comparison is not a GiftiFile or subtype.\n"; return false; } const int32_t numLabels = labelTable.getNumberOfLabels(); if (numLabels != gf->labelTable.getNumberOfLabels()) { messageOut += "ERROR: The files contain a different number of labels.\n"; } else { int32_t labelCount = 0; for (int32_t k = 0; k < numLabels; k++) { if (labelTable.getLabel(k) != gf->getLabelTable()->getLabel(k)) { labelCount++; } } if (labelCount > 0) { messageOut += "ERROR: The files have " + AString::number(labelCount) + " different labels.\n"; } } const int32_t numArrays = getNumberOfDataArrays(); if (numArrays != gf->getNumberOfDataArrays()) { messageOut += "ERROR: The files contain a different number of data arrays (data columns)"; } else { for (int32_t i = 0; i < numArrays; i++) { const GiftiDataArray* gdf1 = getDataArray(i); const GiftiDataArray* gdf2 = gf->getDataArray(i); const std::vector dim1 = gdf1->getDimensions(); const std::vector dim2 = gdf2->getDimensions(); if (dim1 != dim2) { messageOut += "ERROR: Data Array " + AString::number(i) + " have a different number of dimensions.\n"; } else { if (gdf1->getDataType() != gdf2->getDataType()) { messageOut += "ERROR: Data Array " + AString::number(i) + " are different data types.\n"; } else if (gdf1->getTotalNumberOfElements() != gdf2->getTotalNumberOfElements()) { messageOut += "ERROR: Data Array " + AString::number(i) + " have a different number of total elements.\n"; } else { const int32_t numElem = gdf1->getTotalNumberOfElements(); int32_t diffCount = 0; switch (gdf1->getDataType()) { case NiftiDataTypeEnum::NIFTI_TYPE_FLOAT32: { const float* p1 = gdf1->getDataPointerFloat(); const float* p2 = gdf2->getDataPointerFloat(); for (int64_t m = 0; m < numElem; m++) { float diff = p1[m] - p2[m]; if (diff < 0.0) diff = -diff; if (diff > tolerance) { diffCount++; } } } break; case NiftiDataTypeEnum::NIFTI_TYPE_INT32: { const int32_t tol = static_cast(tolerance); const int32_t* p1 = gdf1->getDataPointerInt(); const int32_t* p2 = gdf2->getDataPointerInt(); for (int64_t m = 0; m < numElem; m++) { float diff = p1[m] - p2[m]; if (diff < 0.0) diff = -diff; if (diff > tol) { diffCount++; } } } break; case NiftiDataTypeEnum::NIFTI_TYPE_UINT8: { const uint8_t tol = static_cast(tolerance); const uint8_t* p1 = gdf1->getDataPointerUByte(); const uint8_t* p2 = gdf2->getDataPointerUByte(); for (int64_t m = 0; m < numElem; m++) { float diff = p1[m] - p2[m]; if (diff < 0.0) diff = -diff; if (diff > tol) { diffCount++; } } } break; default: CaretAssertMessage(0, "Invalid Data Type"); break; } if (diffCount > 0) { messageOut += "ERROR: There are " + AString::number(diffCount) + " elements with a difference that are greater than " + AString::number(tolerance) + " in data array " + AString::number(i) + ".\n"; } } } } } return (messageOut.isEmpty()); } /** * Set the name of a data array. */ void GiftiFile::setDataArrayName(const int32_t arrayIndex, const AString& name) { CaretAssertVectorIndex(this->dataArrays, arrayIndex); dataArrays[arrayIndex]->getMetaData()->set(GiftiXmlElements::TAG_METADATA_NAME, name); setModified(); } /** * returns true if the file is isEmpty() (contains no data). */ bool GiftiFile::isEmpty() const { return dataArrays.empty(); } /** * get the data array with the specified name. */ GiftiDataArray* GiftiFile::getDataArrayWithName(const AString& n) { for (int32_t i = 0; i < getNumberOfDataArrays(); i++) { if (getDataArrayName(i) == n) { return getDataArray(i); } } return NULL; } /** * get the data array with the specified name. */ const GiftiDataArray* GiftiFile::getDataArrayWithName(const AString& n) const { for (int32_t i = 0; i < getNumberOfDataArrays(); i++) { if (getDataArrayName(i) == n) { return getDataArray(i); } } return NULL; } /** * Get the data array index for an array with the specified name. If the * name is not found a negative number is returned. */ int GiftiFile::getDataArrayWithNameIndex(const AString& n) const { for (int32_t i = 0; i < getNumberOfDataArrays(); i++) { if (getDataArrayName(i) == n) { return i; } } return -1; } /** * get the data array of the specified intent. */ GiftiDataArray* GiftiFile::getDataArrayWithIntent(const NiftiIntentEnum::Enum intent) { for (int32_t i = 0; i < getNumberOfDataArrays(); i++) { GiftiDataArray* gda = getDataArray(i); if (gda->getIntent() == intent) { return gda; } } return NULL; } /** * get the data array of the specified intent (const method). */ const GiftiDataArray* GiftiFile::getDataArrayWithIntent(const NiftiIntentEnum::Enum intent) const { for (int32_t i = 0; i < getNumberOfDataArrays(); i++) { const GiftiDataArray* gda = getDataArray(i); if (gda->getIntent() == intent) { return gda; } } return NULL; } /** * get the index of the data array of the specified intent. */ int32_t GiftiFile::getDataArrayWithIntentIndex(const NiftiIntentEnum::Enum intent) const { for (int32_t i = 0; i < getNumberOfDataArrays(); i++) { const GiftiDataArray* gda = getDataArray(i); if (gda->getIntent() == intent) { return i; } } return -1; } /** * Get the name for a data array. */ AString GiftiFile::getDataArrayName(const int32_t arrayIndex) const { CaretAssertVectorIndex(this->dataArrays, arrayIndex); AString s = dataArrays[arrayIndex]->getMetaData()->get(GiftiXmlElements::TAG_METADATA_NAME); return s; } /** * Set the comment for a data array. */ void GiftiFile::setDataArrayComment(const int32_t arrayIndex, const AString& comm) { CaretAssertVectorIndex(this->dataArrays, arrayIndex); dataArrays[arrayIndex]->getMetaData()->set(GiftiMetaDataXmlElements::METADATA_NAME_COMMENT, comm); setModified(); } /** * Append to the comment for a data array. */ void GiftiFile::appendToDataArrayComment(const int32_t arrayIndex, const AString& comm) { CaretAssertVectorIndex(this->dataArrays, arrayIndex); if (comm.isEmpty() == false) { AString s(getDataArrayComment(arrayIndex)); s.append(comm); setDataArrayComment(arrayIndex, s); setModified(); } } /** * Prepend to the comment for a data array. */ void GiftiFile::prependToDataArrayComment(const int32_t arrayIndex, const AString& comm) { CaretAssertVectorIndex(this->dataArrays, arrayIndex); if (comm.isEmpty() == false) { AString s(comm); s.append(getDataArrayComment(arrayIndex)); setDataArrayComment(arrayIndex, s); setModified(); } } /** * Get the comment for a data array. */ AString GiftiFile::getDataArrayComment(const int32_t arrayIndex) const { CaretAssertVectorIndex(this->dataArrays, arrayIndex); AString s; (void)dataArrays[arrayIndex]->getMetaData()->get(GiftiMetaDataXmlElements::METADATA_NAME_COMMENT); return s; } /** * get the default data array intent. */ void GiftiFile::setDefaultDataArrayIntent(const NiftiIntentEnum::Enum newIntent) { defaultDataArrayIntent = newIntent; setModified(); } /** * */ void GiftiFile::clear() { DataFile::clear(); for (std::size_t i = 0; i < dataArrays.size(); i++) { if (dataArrays[i] != NULL) { delete dataArrays[i]; dataArrays[i] = NULL; } } dataArrays.clear(); labelTable.clear(); metaData.clear(); // do not clear // giftiElementName // requiredArrayTypeDataTypes } void GiftiFile::clearAndKeepMetadata() {//same as above, minus metaData.clear() DataFile::clear(); for (std::size_t i = 0; i < dataArrays.size(); i++) { if (dataArrays[i] != NULL) { delete dataArrays[i]; dataArrays[i] = NULL; } } dataArrays.clear(); labelTable.clear(); } /** * get all of the data array names. */ void GiftiFile::getAllArrayNames(std::vector& names) const { names.clear(); for (int32_t i = 0; i < getNumberOfDataArrays(); i++) { names.push_back(getDataArrayName(i)); } } /** * check for data arrays with the same name (returns true if there are any). */ bool GiftiFile::checkForDataArraysWithSameName(std::vector& multipleArrayNames) const { multipleArrayNames.clear(); const int32_t numArrays = getNumberOfDataArrays(); if (numArrays > 0) { std::set badNames; for (int32_t i = 0; i < (numArrays - 1); i++) { for (int32_t j = i + 1; j < numArrays; j++) { if (getDataArrayName(i) == getDataArrayName(j)) { badNames.insert(getDataArrayName(i)); } } } if (badNames.empty() == false) { multipleArrayNames.insert(multipleArrayNames.begin(), badNames.begin(), badNames.end()); } } return (multipleArrayNames.size() > 0); } /** * add a data array. */ void GiftiFile::addDataArray(GiftiDataArray* gda) { CaretAssert(gda); // const AString uuid = gda->getMetaData()->getUniqueID(); // const int32_t numDataArrays = this->getNumberOfDataArrays(); // for (int32_t i = 0; i < numDataArrays; i++) { // const AString daUuid = this->getDataArray(i)->getMetaData()->getUniqueID(); // if (daUuid == uuid) { // CaretLogWarning("File " // + this->getFileNameNoPath() // + " contains multiple data arrays with the same unique identifier. " // "Unique ID has been modified to ensure uniqueness within file."); // const bool modStatus = gda->isModified(); // gda->getMetaData()->resetUniqueIdentifier(); // if (modStatus == false) { // gda->clearModified(); // } // } // } dataArrays.push_back(gda); setModified(); } /** * append a gifti array data file to this one. */ void GiftiFile::append(const GiftiFile& gf) { const bool copyDataArrayFlag = true; const int32_t numArrays = gf.getNumberOfDataArrays(); if (numArrays <= 0) { return; } // // Replace filename if "this" file is isEmpty() // if (getNumberOfDataArrays() == 0) { setFileName(gf.getFileName()); } /* * Append metadata. */ this->metaData.append(*gf.getMetaData()); /* * Updates for label table. */ std::map labelIndexConverter = this->labelTable.append(*gf.getLabelTable()); // // Append the data arrays // for (int32_t i = 0; i < gf.getNumberOfDataArrays(); i++) { GiftiDataArray* gda = gf.dataArrays[i]; if (copyDataArrayFlag) { gda = new GiftiDataArray(*(gf.dataArrays[i])); } if (gda->getIntent() == NiftiIntentEnum::NIFTI_INTENT_LABEL) { gda->transferLabelIndices(labelIndexConverter); } this->addDataArray(gda); } setModified(); } /** * append array data file to this one but selectively load/overwrite arrays * indexDestination is where naf's data arrays should be (-1=new, -2=do not load). * "indexDestination" will be updated with the columns actually used. * @param gf * GIFTI file that is appended. * @param indexDestination * How arrays are added/replaced. */ void GiftiFile::append(const GiftiFile& gf, std::vector& indexDestination) { const bool copyDataArrayFlag = true; const int32_t numArrays = gf.getNumberOfDataArrays(); if (numArrays <= 0) { return; } /* * Append metadata. */ this->metaData.append(*gf.getMetaData()); /* * Updates for label table. */ std::map labelIndexConverter = this->labelTable.append(*gf.getLabelTable()); // // Replace file name if this file is isEmpty() // if (getNumberOfDataArrays() == 0) { setFileName(gf.getFileName()); } // // append the data arrays // for (int32_t i = 0; i < numArrays; i++) { const int32_t arrayIndex = indexDestination[i]; if (arrayIndex != 0) { GiftiDataArray* gda = gf.dataArrays[i]; if (copyDataArrayFlag) { gda = new GiftiDataArray(*gda); if (gda->getIntent() == NiftiIntentEnum::NIFTI_INTENT_LABEL) { gda->transferLabelIndices(labelIndexConverter); } this->addDataArray(gda); } // // Replacing existing array ? // if (arrayIndex >= 0) { delete dataArrays[arrayIndex]; dataArrays[arrayIndex] = gda; } // // create new array // else if (arrayIndex == -1) { dataArrays.push_back(gda); // // Lets others know where the array was placed // indexDestination[i] = getNumberOfDataArrays() - 1; } } // // Ignore array // else { // nothing } } this->appendToFileComment(gf.getFileComment()); setModified(); } AString GiftiFile::getFileComment() const { return this->metaData.get(GiftiMetaDataXmlElements::METADATA_NAME_COMMENT); } void GiftiFile::setFileComment(const AString& comment) { if (comment.isEmpty() == false) { AString s = this->getFileName(); s += "\n" + comment; this->setFileComment(s); } } void GiftiFile::appendToFileComment(const AString& comment) { this->metaData.set(GiftiMetaDataXmlElements::METADATA_NAME_COMMENT, comment); } /** * append helper for files where data are label indices. * The table "oldIndicesToNewIndicesTable" maps label indices from * "naf" label indices to the proper indices for "this" file. * void GiftiFile::appendLabelDataHelper(const GiftiFile& naf, const std::vector& arrayWillBeAppended, std::vector& oldIndicesToNewIndicesTable) { oldIndicesToNewIndicesTable.clear(); if ((dataAreIndicesIntoLabelTable == false) || (naf.dataAreIndicesIntoLabelTable == false)) { return; } const int32_t numArrays = naf.getNumberOfDataArrays(); if (numArrays != static_cast(arrayWillBeAppended.size())) { return; } const GiftiLabelTable* nltNew = naf.getLabelTable(); const int32_t numLabelsNew = nltNew->getNumberOfLabels(); if (numLabelsNew <= 0) { return; } oldIndicesToNewIndicesTable.resize(numLabelsNew, -1); // // Determine which labels will be appended // for (int32_t i = 0; i < numArrays; i++) { GiftiDataArray* nda = naf.dataArrays[i]; if (nda->getDataType() == NiftiDataType::NIFTI_TYPE_INT32) { const int32_t numElem = nda->getTotalNumberOfElements(); if (numElem >= 0) { int32_t* dataPtr = nda->getDataPointerInt(); for (int32_t i = 0; i < numElem; i++) { const int32_t indx = dataPtr[i]; if ((indx >= 0) && (indx < numLabelsNew)) { oldIndicesToNewIndicesTable[indx] = -2; } else { std::cout << "WARNING Invalid label index set to zero: " << indx << " of " << numLabelsNew << " labels." << std::endl; dataPtr[i] = 0; } } } } } // // remap the label indices // GiftiLabelTable* myLabelTable = getLabelTable(); for (int32_t i = 0; i < numLabelsNew; i++) { if (oldIndicesToNewIndicesTable[i] == -2) { int32_t indx = myLabelTable->addLabel(nltNew->getLabel(i)); oldIndicesToNewIndicesTable[i] = indx; nltNew->g unsigned char r, g, b, a; nltNew->getColor(i, r, g, b, a); myLabelTable->setColor(indx, r, g, b, a); } } } */ /** * add rows to this file. */ void GiftiFile::addRows(const int32_t numberOfRowsToAdd) { for (int32_t i = 0; i < getNumberOfDataArrays(); i++) { dataArrays[i]->addRows(numberOfRowsToAdd); } setModified(); } /** * reset a data array. */ void GiftiFile::resetDataArray(const int32_t arrayIndex) { CaretAssertVectorIndex(this->dataArrays, arrayIndex); dataArrays[arrayIndex]->zeroize(); } /** * remove a data array. */ void GiftiFile::removeDataArray(const int32_t arrayIndex) { CaretAssertVectorIndex(this->dataArrays, arrayIndex); int32_t numArrays = getNumberOfDataArrays(); if ((arrayIndex >= 0) && (arrayIndex < numArrays)) { delete dataArrays[arrayIndex]; for (int32_t i = arrayIndex; i < (numArrays - 1); i++) { dataArrays[i] = dataArrays[i + 1]; } dataArrays.resize(numArrays - 1); } } /** * remove a data array. */ void GiftiFile::removeDataArray(const GiftiDataArray* arrayPointer) { CaretAssert(arrayPointer); for (int32_t i = 0; i < getNumberOfDataArrays(); i++) { if (getDataArray(i) == arrayPointer) { removeDataArray(i); break; } } } /** * read the file. */ void GiftiFile::readFile(const AString& filename) { this->clear(); this->setFileName(filename); GiftiFileSaxReader saxReader(this); std::auto_ptr parser(XmlSaxParser::createXmlParser()); try { parser->parseFile(filename, &saxReader); } catch (const XmlSaxParserException& e) { clear(); this->setFileName(""); int lineNum = e.getLineNumber(); int colNum = e.getColumnNumber(); std::ostringstream str; str << "Parse error while reading"; if ((lineNum >= 0) && (colNum >= 0)) { str << ", line/col (" << e.getLineNumber() << "/" << e.getColumnNumber() << ")"; } str << ": " << e.whatString().toStdString(); throw DataFileException(filename, AString::fromStdString(str.str())); } /* * If any maps are missing names, give them default names. */ const int32_t numArrays = getNumberOfDataArrays(); for (int32_t i = 0; i < numArrays; i++) { AString arrayName = getDataArrayName(i); if (arrayName.isEmpty()) { arrayName = ("#" + AString::number(i + 1)); setDataArrayName(i, arrayName); } } } /** * write the file. */ void GiftiFile::writeFile(const AString& filename) { try { this->setFileName(filename); FileInformation fileInfo(filename); if (fileInfo.exists()) { //if (GiftiDataArrayFile.isFileOverwriteAllowed() == false) { // throw new GiftiException( // "Overwriting of existing files is currently prohibited"); //} } // // Create a GIFTI Data Array File Writer // GiftiFileWriter giftiFileWriter(filename, this->encodingForWriting); // // Start writing the file // int numberOfDataArrays = this->getNumberOfDataArrays(); giftiFileWriter.start(numberOfDataArrays, &this->metaData, &this->labelTable); // // Write the data arrays // for (int i = 0; i < numberOfDataArrays; i++) { giftiFileWriter.writeDataArray(this->getDataArray(i)); } // // Finish writing the file // giftiFileWriter.finish(); } catch (const GiftiException& e) { throw DataFileException(filename, e.whatString()); } this->clearModified(); /* // // Get how the array data should be encoded // GiftiDataArray::ENCODING encoding = GiftiDataArray::ENCODING_INTERNAL_ASCII; switch (getFileWriteType()) { case FILE_FORMAT_ASCII: break; case FILE_FORMAT_BINARY: break; case FILE_FORMAT_XML: encoding = GiftiDataArray::ENCODING_INTERNAL_ASCII; break; case FILE_FORMAT_XML_BASE64: encoding = GiftiDataArray::ENCODING_INTERNAL_BASE64_BINARY; break; case FILE_FORMAT_XML_GZIP_BASE64: encoding = GiftiDataArray::ENCODING_INTERNAL_COMPRESSED_BASE64_BINARY; break; case FILE_FORMAT_XML_EXTERNAL_BINARY: encoding = GiftiDataArray::ENCODING_EXTERNAL_FILE_BINARY; break; case FILE_FORMAT_OTHER: break; case FILE_FORMAT_COMMA_SEPARATED_VALUE_FILE: break; } AString giftiFileVersionString = AString::number(GiftiFile::getCurrentFileVersion(), 'f', 6); while (giftiFileVersionString.endsWith("00")) { giftiFileVersionString.resize(giftiFileVersionString.size() - 1); } stream << "" << "\n"; stream << "" << "\n"; stream << "<" << GiftiCommon::tagGIFTI << "\n" << " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" << " xsi:noNamespaceSchemaLocation=\"http://brainvis.wustl.edu/caret6/xml_schemas/GIFTI_Caret.xsd\"\n" << " " << GiftiCommon::attVersion << "=\"" << giftiFileVersionString << "\"\n" << " " << GiftiCommon::attNumberOfDataArrays << "=\"" << getNumberOfDataArrays() << "\"" << ">" << "\n"; int32_t indent = 0; // // External binary file. // AString externalBinaryFileName; int64_t externalBinaryFileDataOffset = 0; std::ofstream* externalBinaryOutputStream = NULL; if (encoding == GiftiDataArray::ENCODING_EXTERNAL_FILE_BINARY) { externalBinaryFileName = getFileNameNoPath() + ".data"; externalBinaryOutputStream = new std::ofstream(externalBinaryFileName.toAscii().constData(), std::ofstream::binary); if (externalBinaryOutputStream == NULL) { throw GiftiException("Unable to open " + externalBinaryFileName + " for output."); } } #ifdef CARET_FLAG // // copy the Abstract File header into this file's metadata // metaData.clear(); AbstractFileHeaderContainer::iterator iter; for (iter = header.begin(); iter != header.end(); iter++) { // // Get the tag and its value // const AString tag(iter->first); const AString value(iter->second); metaData.set(tag,value); } #endif // CARET_FLAG indent++; metaData.writeAsXML(stream, indent); indent--; // // Write the labels // indent++; labelTable.writeAsXML(stream, indent); indent--; indent++; for (unsigned int32_t i = 0; i < dataArrays.size(); i++) { #ifdef CARET_FLAG dataArrays[i]->setEncoding(encoding); #endif // CARET_FLAG if (externalBinaryOutputStream != NULL) { externalBinaryFileDataOffset = externalBinaryOutputStream->tellp(); } dataArrays[i]->setExternalFileInformation(externalBinaryFileName, externalBinaryFileDataOffset); dataArrays[i]->writeAsXML(stream, indent, externalBinaryOutputStream); } indent--; stream << "" << "\n"; if (externalBinaryOutputStream != NULL) { externalBinaryOutputStream->close(); } */ } /** * set the number of nodes for sparse node index files (NIFTI_INTENT_NODE_INDEX). */ void GiftiFile::setNumberOfNodesForSparseNodeIndexFiles(const int32_t numNodes) { numberOfNodesForSparseNodeIndexFile = numNodes; } /** * process NIFTI_INTENT_NODE_INDEX arrays. */ void GiftiFile::procesNiftiIntentNodeIndexArrays() { // // See if there is a node index array // GiftiDataArray* nodeIndexArray = getDataArrayWithIntent(NiftiIntentEnum::NIFTI_INTENT_NODE_INDEX); if (nodeIndexArray != NULL) { // // Make sure node index array is integer type and one dimensional // if (nodeIndexArray->getDataType() != NiftiDataTypeEnum::NIFTI_TYPE_INT32) { throw GiftiException("Data type other than \"int\" not supported for data intent node index."); } if (nodeIndexArray->getNumberOfDimensions() < 1) { throw GiftiException("Dimensions other than one not supported for data intent node index."); } // // Make node index array integer // nodeIndexArray->convertToDataType(NiftiDataTypeEnum::NIFTI_TYPE_INT32); // // Get the node indices // const int32_t numNodeIndices = nodeIndexArray->getDimension(0); if (numNodeIndices <= 0) { throw GiftiException("Dimension is zero for data intent: " + NiftiIntentEnum::toName(NiftiIntentEnum::NIFTI_INTENT_NODE_INDEX)); } const int32_t zeroIndex[2] = { 0, 0 }; const int32_t* indexData = nodeIndexArray->getDataInt32Pointer(zeroIndex); // // Find the true number of nodes // int32_t numNodes = numberOfNodesForSparseNodeIndexFile; if (numNodes <= 0) { int32_t minNodeIndex = 0; nodeIndexArray->getMinMaxValues(minNodeIndex, numNodes); } // // Check each data array // const int32_t numArrays = getNumberOfDataArrays(); for (int32_t i = 0; i < numArrays; i++) { GiftiDataArray* dataArray = getDataArray(i); if (dataArray->getIntent() != NiftiIntentEnum::NIFTI_INTENT_NODE_INDEX) { if (dataArray->getNumberOfDimensions() < 1) { throw GiftiException("Data Array with intent \"" + NiftiIntentEnum::toName(dataArray->getIntent()) + " is not one-dimensional in sparse node file."); } if (dataArray->getDimension(0) != numNodeIndices) { throw GiftiException("Data Array with intent \"" + NiftiIntentEnum::toName(dataArray->getIntent()) + " has a different number of nodes than the NIFTI_INTENT_NODE_INDEX array in the file."); } switch (dataArray->getDataType()) { case NiftiDataTypeEnum::NIFTI_TYPE_FLOAT32: { std::vector dataFloat(numNodes, 0.0); const float* readPtr = dataArray->getDataFloat32Pointer(zeroIndex); for (int32_t m = 0; m < numNodeIndices; m++) { dataFloat[indexData[m]] = readPtr[m]; } std::vector newDim(1, numNodes); dataArray->setDimensions(newDim); for (int32_t n = 0; n < numNodes; n++) { const int32_t indxs[2] = { n, 0 }; dataArray->setDataFloat32(indxs, dataFloat[n]); } } break; case NiftiDataTypeEnum::NIFTI_TYPE_INT32: { std::vector dataInt(numNodes, 0); const int32_t* readPtr = dataArray->getDataInt32Pointer(zeroIndex); for (int32_t m = 0; m < numNodeIndices; m++) { dataInt[indexData[m]] = readPtr[m]; } std::vector newDim(1, numNodes); dataArray->setDimensions(newDim); for (int32_t n = 0; n < numNodes; n++) { const int32_t indxs[2] = { n, 0 }; dataArray->setDataInt32(indxs, dataInt[n]); } } break; case NiftiDataTypeEnum::NIFTI_TYPE_UINT8: { std::vector dataByte(numNodes, 0); const uint8_t* readPtr = dataArray->getDataUInt8Pointer(zeroIndex); for (int32_t m = 0; m < numNodeIndices; m++) { dataByte[indexData[m]] = readPtr[m]; } std::vector newDim(1, numNodes); dataArray->setDimensions(newDim); for (int32_t n = 0; n < numNodes; n++) { const int32_t indxs[2] = { n, 0 }; dataArray->setDataUInt8(indxs, dataByte[n]); } } break; default: CaretAssertMessage(0, "Invalid Data Type"); break; } } } // // Remove the node index array // removeDataArray(nodeIndexArray); } } /** * Set this object as not modified. Object should also * clear the modification status of its children. * */ void GiftiFile::clearModified() { DataFile::clearModified(); metaData.clearModified(); labelTable.clearModified(); for (int i = 0; i < this->getNumberOfDataArrays(); i++) { this->getDataArray(i)->clearModified(); } } /** * Get the modification status. Returns true if this object or * any of its children have been modified. * @return - The modification status. * */ bool GiftiFile::isModified() const { if (DataFile::isModified()) { return true; } if (metaData.isModified()) { return true; } if (labelTable.isModified()) { return true; } for (int i = 0; i < this->getNumberOfDataArrays(); i++) { if (this->getDataArray(i)->isModified()) { return true; } } return false; } /** * Set the encoding for writing the file. * @param encoding * New encoding. */ void GiftiFile::setEncodingForWriting(const GiftiEncodingEnum::Enum encoding) { this->encodingForWriting = encoding; } /** * Validate the data arrays (optional for subclasses). */ void GiftiFile::validateDataArrays() { // nothing } AString GiftiFile::toString() const { std::ostringstream str; str << "Gifti File: " << this->getFileName().toStdString() << std::endl; str << this->metaData.toString().toStdString() << std::endl; str << this->labelTable.toString().toStdString() << std::endl; for (int32_t i = 0; i < this->getNumberOfDataArrays(); i++) { const GiftiDataArray* gda = this->getDataArray(i); str << gda->toString().toStdString() << std::endl; } return AString::fromStdString(str.str()); } workbench-1.1.1/src/Gifti/GiftiFile.h000066400000000000000000000224331255417355300173560ustar00rootroot00000000000000#ifndef __GIFTI_FILE_H__ #define __GIFTI_FILE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretAssert.h" #include "DataFile.h" #include "GiftiDataArray.h" #include "GiftiEncodingEnum.h" #include "GiftiLabelTable.h" #include "NiftiEnums.h" #include "TracksModificationInterface.h" namespace caret { /// This abstract class defines some variables and methods used for gifti data array files. /// While this class may be instantiated, it is best subclassed. class GiftiFile : public DataFile { public: /// append array index values enum APPEND_ARRAY_INDEX { APPEND_ARRAY_NEW = -1, APPEND_ARRAY_DO_NOT_LOAD = -2 }; /// constructor GiftiFile(const AString& descriptiveName, const NiftiIntentEnum::Enum defaultDataArrayIntentIn, const NiftiDataTypeEnum::Enum defaultDataTypeIn, const AString& defaultExt, const bool dataAreIndicesIntoLabelTableIn); /// constructor for generic gifti data array file GiftiFile(); // copy constructor GiftiFile(const GiftiFile& nndf); // destructor virtual ~GiftiFile(); // assignment operator GiftiFile& operator=(const GiftiFile& nndf); // add a data array virtual void addDataArray(GiftiDataArray* nda); // add rows to this file. void addRows(const int32_t numberOfRowsToAdd); // append a data array file to this one virtual void append(const GiftiFile& naf); // append a data array file to this one but selectively load/overwrite arraysumns // arrayDestination is where naf's arrays should be (-1=new, -2=do not load) virtual void append(const GiftiFile& naf, std::vector& indexDestinationg); /// compare a file for unit testing (returns true if "within tolerance") virtual bool compareFileForUnitTesting(const GiftiFile* gf, const float tolerance, AString& messageOut) const; // Clear the gifti array data file. virtual void clear(); virtual void clearAndKeepMetadata(); // returns true if the file is isEmpty() (contains no data) virtual bool isEmpty() const; AString getFileComment() const; void setFileComment(const AString& comment); void appendToFileComment(const AString& comment); /// get the number of data arrays int32_t getNumberOfDataArrays() const { return dataArrays.size() ; } /// get a data array GiftiDataArray* getDataArray(const int32_t arrayNumber) { CaretAssertVectorIndex(dataArrays, arrayNumber); return dataArrays[arrayNumber]; } /// get a data array (const method) const GiftiDataArray* getDataArray(const int32_t arrayNumber) const { CaretAssertVectorIndex(dataArrays, arrayNumber); return dataArrays[arrayNumber]; } /// reset a data array virtual void resetDataArray(const int32_t arrayIndex); /// remove a data array virtual void removeDataArray(const GiftiDataArray* arrayPointer); /// remove a data array virtual void removeDataArray(const int32_t arrayIndex); // get all of the data array names void getAllArrayNames(std::vector& names) const; // get the specified data array's name AString getDataArrayName(const int32_t arrayIndex) const; // get the index of the data array with the specified name int32_t getDataArrayWithNameIndex(const AString& n) const; // get the data array with the specified name GiftiDataArray* getDataArrayWithName(const AString& n); // get the data array with the specified name const GiftiDataArray* getDataArrayWithName(const AString& n) const; // get the index of the data array of the specified intent int32_t getDataArrayWithIntentIndex(const NiftiIntentEnum::Enum intent) const; // get the data array of the specified intent GiftiDataArray* getDataArrayWithIntent(const NiftiIntentEnum::Enum intent); // get the data array of the specified intent (const method) const GiftiDataArray* getDataArrayWithIntent(const NiftiIntentEnum::Enum intent) const; // get the comment for a data array AString getDataArrayComment(const int32_t arrayIndex) const; // set the name of a data array void setDataArrayName(const int32_t arrayIndex, const AString& name); // set the comment for a data array void setDataArrayComment(const int32_t arrayIndex, const AString& comm); // append to the comment for a data array void appendToDataArrayComment(const int32_t arrayIndex, const AString& comm); // prepend to the comment for a data array void prependToDataArrayComment(const int32_t arrayIndex, const AString& comm); // check for data arrays with the same name (returns true if there are any) bool checkForDataArraysWithSameName(std::vector& multipleDataArrayNames) const; // get the metadata GiftiMetaData* getMetaData() { return &metaData; } // get the metadata (const method) const GiftiMetaData* getMetaData() const { return &metaData; } /// get the label table GiftiLabelTable* getLabelTable() { return &labelTable; } /// get the label table const GiftiLabelTable* getLabelTable() const { return &labelTable; } /// get the current version for GiftiFiles static float getCurrentFileVersion() { return 1.0; } /// get the default data array intent NiftiIntentEnum::Enum getDefaultDataArrayIntent() const { return defaultDataArrayIntent; } /// get the default data array intent void setDefaultDataArrayIntent(const NiftiIntentEnum::Enum newIntent); /// set the number of nodes for sparse node index files (NIFTI_INTENT_NODE_INDEX) void setNumberOfNodesForSparseNodeIndexFiles(const int32_t numNodes); // read the XML file virtual void readFile(const AString& filename); // write the XML file virtual void writeFile(const AString& filename); bool getReadMetaDataOnlyFlag() const { return false; } /** @return The encoding used to write the file. */ GiftiEncodingEnum::Enum getEncodingForWriting() const { return this->encodingForWriting; } void setEncodingForWriting(const GiftiEncodingEnum::Enum encoding); virtual void clearModified(); virtual bool isModified() const; virtual AString toString() const; protected: // append helper for files where data are label indices //void appendLabelDataHelper(const GiftiFile& naf, // const std::vector& arrayWillBeAppended, // std::vector& oldIndicesToNewIndicesTable); // copy helper void copyHelperGiftiFile(const GiftiFile& nndf); // process NIFTI_INTENT_NODE_INDEX arrays void procesNiftiIntentNodeIndexArrays(); // validate the data arrays (optional for subclasses) virtual void validateDataArrays(); /// the data arrays std::vector dataArrays; /// the label table GiftiLabelTable labelTable; /// the file's metadata GiftiMetaData metaData; GiftiEncodingEnum::Enum encodingForWriting; /// the default data type NiftiDataTypeEnum::Enum defaultDataType; /// default data array intent for this file NiftiIntentEnum::Enum defaultDataArrayIntent; /// data arrays contain indices into label table bool dataAreIndicesIntoLabelTable; AString descriptiveName; AString defaultExtension; /// number of nodes in sparse node index files (NIFTI_INTENT_NODE_INDEX array) int32_t numberOfNodesForSparseNodeIndexFile; /** The default encoding for writing a GIFTI file. */ static GiftiEncodingEnum::Enum defaultEncodingForWriting; /*!!!! be sure to update copyHelperGiftiFile if new member added !!!!*/ // // friends // }; #ifdef __GIFTI_FILE_MAIN__ GiftiEncodingEnum::Enum GiftiFile::defaultEncodingForWriting = GiftiEncodingEnum::GZIP_BASE64_BINARY; #endif // __GIFTI_FILE_MAIN__ } // namespace #endif // __GIFTI_FILE_H__ workbench-1.1.1/src/Gifti/GiftiFileSaxReader.cxx000066400000000000000000000515721255417355300215360ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretLogger.h" #include "FileInformation.h" #include "GiftiEndianEnum.h" #include "GiftiLabel.h" #include "GiftiFile.h" #include "GiftiFileSaxReader.h" #include "GiftiLabelTableSaxReader.h" #include "GiftiMetaDataSaxReader.h" #include "GiftiXmlElements.h" #include "NiftiEnums.h" #include "XmlAttributes.h" #include "XmlException.h" using namespace caret; /** * constructor. */ GiftiFileSaxReader::GiftiFileSaxReader(GiftiFile* giftiFileIn) { this->giftiFile = giftiFileIn; this->state = STATE_NONE; this->stateStack.push(this->state); this->elementText = ""; this->dataArray.grabNew(NULL); this->labelTable = NULL; this->labelTableSaxReader = NULL; this->metaDataSaxReader = NULL; this->dataArrayDataHasBeenRead = false; } /** * destructor. */ GiftiFileSaxReader::~GiftiFileSaxReader() { } /** * start an element. */ void GiftiFileSaxReader::startElement(const AString& namespaceURI, const AString& localName, const AString& qName, const XmlAttributes& attributes) { const STATE previousState = this->state; switch (this->state) { case STATE_NONE: if (qName == GiftiXmlElements::TAG_GIFTI) { this->state = STATE_GIFTI; // // Check version of file being read // const float version = attributes.getValueAsFloat(GiftiXmlElements::ATTRIBUTE_GIFTI_VERSION); if (version > GiftiFile::getCurrentFileVersion()) { std::ostringstream str; str << "File version is " << version << " but this Caret" << " does not support versions newer than " << GiftiFile::getCurrentFileVersion() << ".\n" << "You may need a newer version of Caret."; throw XmlSaxParserException(AString::fromStdString(str.str())); } else if (version < 1.0) { throw XmlSaxParserException( "File version is " + AString::number(version) + " but this Caret" " does not support versions before 1.0"); } } else { std::ostringstream str; str << "Root element is \"" << qName << "\" but should be " << GiftiXmlElements::TAG_GIFTI; throw XmlSaxParserException(AString::fromStdString(str.str())); } break; case STATE_GIFTI: if (qName == GiftiXmlElements::TAG_METADATA) { this->state = STATE_METADATA; this->metaDataSaxReader = new GiftiMetaDataSaxReader(giftiFile->getMetaData()); this->metaDataSaxReader->startElement(namespaceURI, localName, qName, attributes); } else if (qName == GiftiXmlElements::TAG_DATA_ARRAY) { this->state = STATE_DATA_ARRAY; this->createDataArray(attributes); } else if (qName == GiftiXmlElements::TAG_LABEL_TABLE) { this->state = STATE_LABEL_TABLE; this->labelTableSaxReader = new GiftiLabelTableSaxReader(giftiFile->getLabelTable()); this->labelTableSaxReader->startElement(namespaceURI, localName, qName, attributes); } else { std::ostringstream str; str << "Child of " << GiftiXmlElements::TAG_GIFTI << " is \"" << qName << "\" but should be one of \n" << " " << GiftiXmlElements::TAG_METADATA << "\n" << " " << GiftiXmlElements::TAG_DATA_ARRAY << "\n" << " " << GiftiXmlElements::TAG_LABEL_TABLE; throw XmlSaxParserException(AString::fromStdString(str.str())); } break; case STATE_METADATA: this->metaDataSaxReader->startElement(namespaceURI, localName, qName, attributes); break; case STATE_LABEL_TABLE: this->labelTableSaxReader->startElement(namespaceURI, localName, qName, attributes); break; case STATE_DATA_ARRAY: if (qName == GiftiXmlElements::TAG_METADATA) { this->state = STATE_METADATA; this->metaDataSaxReader = new GiftiMetaDataSaxReader(dataArray->getMetaData()); this->metaDataSaxReader->startElement(namespaceURI, localName, qName, attributes); } else if (qName == GiftiXmlElements::TAG_DATA) { this->state = STATE_DATA_ARRAY_DATA; } else if (qName == GiftiXmlElements::TAG_COORDINATE_TRANSFORMATION_MATRIX) { this->state = STATE_DATA_ARRAY_MATRIX; this->dataArray->addMatrix(Matrix4x4()); this->matrix = dataArray->getMatrix(dataArray->getNumberOfMatrices() - 1); } else { std::ostringstream str; str << "Child of " << GiftiXmlElements::TAG_DATA_ARRAY << " is \"" << qName << "\" but should be one of \n" << " " << GiftiXmlElements::TAG_METADATA << "\n" << " " << GiftiXmlElements::TAG_COORDINATE_TRANSFORMATION_MATRIX << "\n" << " " << GiftiXmlElements::TAG_DATA; throw XmlSaxParserException(AString::fromStdString(str.str())); } break; case STATE_DATA_ARRAY_DATA: { std::ostringstream str; str << GiftiXmlElements::TAG_DATA << " has child \"" << qName << "\" but should not have any child nodes"; throw XmlSaxParserException(AString::fromStdString(str.str())); } break; case STATE_DATA_ARRAY_MATRIX: if (qName == GiftiXmlElements::TAG_MATRIX_DATA_SPACE) { this->state = STATE_DATA_ARRAY_MATRIX_DATA_SPACE; } else if (qName == GiftiXmlElements::TAG_MATRIX_TRANSFORMED_SPACE) { this->state = STATE_DATA_ARRAY_MATRIX_TRANSFORMED_SPACE; } else if (qName == GiftiXmlElements::TAG_MATRIX_DATA) { this->state = STATE_DATA_ARRAY_MATRIX_DATA; } else { std::ostringstream str; str << "Child of " << GiftiXmlElements::TAG_COORDINATE_TRANSFORMATION_MATRIX << " is \"" << qName << "\" but should be one of \n" << " " << GiftiXmlElements::TAG_MATRIX_DATA_SPACE << "\n" << " " << GiftiXmlElements::TAG_MATRIX_TRANSFORMED_SPACE << "\n" << " " << GiftiXmlElements::TAG_MATRIX_DATA; throw XmlSaxParserException(AString::fromStdString(str.str())); } break; case STATE_DATA_ARRAY_MATRIX_DATA_SPACE: { std::ostringstream str; str << GiftiXmlElements::TAG_MATRIX_DATA_SPACE << " has child \"" << qName << "\" but should not have any child nodes"; throw XmlSaxParserException(AString::fromStdString(str.str())); } break; case STATE_DATA_ARRAY_MATRIX_TRANSFORMED_SPACE: { std::ostringstream str; str << GiftiXmlElements::TAG_MATRIX_TRANSFORMED_SPACE << " has child \"" << qName << "\" but should not have any child nodes"; throw XmlSaxParserException(AString::fromStdString(str.str())); } break; case STATE_DATA_ARRAY_MATRIX_DATA: { std::ostringstream str; str << GiftiXmlElements::TAG_MATRIX_DATA << " has child \"" << qName << "\" but should not have any child nodes"; throw XmlSaxParserException(AString::fromStdString(str.str())); } break; } // // Save previous state // stateStack.push(previousState); elementText = ""; } /** * end an element. */ void GiftiFileSaxReader::endElement(const AString& namespaceURI, const AString& localName, const AString& qName) { switch (this->state) { case STATE_NONE: break; case STATE_GIFTI: break; case STATE_METADATA: this->metaDataSaxReader->endElement(namespaceURI, localName, qName); if (qName == GiftiXmlElements::TAG_METADATA) { delete this->metaDataSaxReader; this->metaDataSaxReader = NULL; } break; case STATE_LABEL_TABLE: this->labelTableSaxReader->endElement(namespaceURI, localName, qName); if (qName == GiftiXmlElements::TAG_LABEL_TABLE) { delete this->labelTableSaxReader; this->labelTableSaxReader = NULL; } break; case STATE_DATA_ARRAY: if (this->dataArray != NULL) { /* * An external binary file may not have a DATA tag * so this will handle reading in that case. */ if (this->encodingForReadingArrayData == GiftiEncodingEnum::EXTERNAL_FILE_BINARY) { if (this->dataArrayDataHasBeenRead == false) { this->processArrayData(); } } this->giftiFile->addDataArray(this->dataArray.releasePointer()); } else { } break; case STATE_DATA_ARRAY_DATA: this->processArrayData(); break; case STATE_DATA_ARRAY_MATRIX: this->matrix = NULL; break; case STATE_DATA_ARRAY_MATRIX_DATA_SPACE: this->matrix->setDataSpaceName(elementText); break; case STATE_DATA_ARRAY_MATRIX_TRANSFORMED_SPACE: this->matrix->setTransformedSpaceName(elementText); break; case STATE_DATA_ARRAY_MATRIX_DATA: { std::istringstream istr(elementText.toStdString()); double m[4][4]; for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { istr >> m[i][j]; } } matrix->setMatrix(m); /* * In GIFTI file, matrix is row-major order * but Matrix4x4 is column-major order. */ matrix->transpose(); } break; } // // Clear out for new elements // this->elementText = ""; // // Go to previous state // if (this->stateStack.empty()) { throw XmlSaxParserException("State stack is empty while reading XML NiftDataFile."); } this->state = stateStack.top(); this->stateStack.pop(); } /** * create a data array. */ void GiftiFileSaxReader::createDataArray(const XmlAttributes& attributes) { // // Intent // AString intentName = attributes.getValue(GiftiXmlElements::ATTRIBUTE_DATA_ARRAY_INTENT); if (intentName.isEmpty()) { intentName = attributes.getValue("Intent"); } if (intentName.isEmpty()) { throw XmlSaxParserException( "Required attribute " + GiftiXmlElements::ATTRIBUTE_DATA_ARRAY_INTENT + " not found for DataArray"); } bool intentValid = false; NiftiIntentEnum::Enum intent = NiftiIntentEnum::fromName(intentName, &intentValid); if (intentValid == false) { throw XmlSaxParserException("Intent name invalid: " + intentName); } // // Data type name // const AString dataTypeName = attributes.getValue(GiftiXmlElements::ATTRIBUTE_DATA_ARRAY_DATA_TYPE); if (dataTypeName.isEmpty()) { throw XmlSaxParserException("Required attribute " + GiftiXmlElements::ATTRIBUTE_DATA_ARRAY_DATA_TYPE + " not found for DataArray"); } bool dataTypeNameValid = false; this->dataTypeForReadingArrayData = NiftiDataTypeEnum::fromName(dataTypeName, &dataTypeNameValid); if (dataTypeNameValid == false) { throw XmlSaxParserException("Attribute " + GiftiXmlElements::ATTRIBUTE_DATA_ARRAY_DATA_TYPE + "is invalid: " + dataTypeName); } // // Encoding // const AString encodingName = attributes.getValue(GiftiXmlElements::ATTRIBUTE_DATA_ARRAY_ENCODING); if (encodingName.isEmpty()) { throw XmlSaxParserException("Required attribute " + GiftiXmlElements::ATTRIBUTE_DATA_ARRAY_ENCODING + " not found for DataArray"); } bool validEncoding = false; encodingForReadingArrayData = GiftiEncodingEnum::fromGiftiName(encodingName, &validEncoding); if (validEncoding == false) { throw XmlSaxParserException("Attribute " + GiftiXmlElements::ATTRIBUTE_DATA_ARRAY_ENCODING + "is invalid: " + encodingName); } // // External File Name // this->externalFileNameForReadingData = attributes.getValue(GiftiXmlElements::ATTRIBUTE_DATA_ARRAY_EXTERNAL_FILE_NAME); /* * Update external file with path to XML file */ FileInformation extBinInfo(this->externalFileNameForReadingData); if (extBinInfo.isAbsolute() == false) { FileInformation xmlInfo(this->giftiFile->getFileName()); AString path = xmlInfo.getPathName(); if (path.isEmpty() == false) { FileInformation fi(path, this->externalFileNameForReadingData); AString newName = fi.getAbsoluteFilePath(); if (newName.isEmpty() == false) { this->externalFileNameForReadingData = newName; } } } // // External File Offset // this->externalFileOffsetForReadingData = 0; if (encodingForReadingArrayData == GiftiEncodingEnum::EXTERNAL_FILE_BINARY) { const AString offsetString = attributes.getValue(GiftiXmlElements::ATTRIBUTE_DATA_ARRAY_EXTERNAL_FILE_OFFSET); //if (offsetString.isEmpty() == false) { bool validOffsetFlag = false; this->externalFileOffsetForReadingData = offsetString.toLong(&validOffsetFlag); if (validOffsetFlag == false) { XmlSaxParserException e("File Offset is not an integer (" + offsetString + ")"); CaretLogThrowing(e); throw e; } //} } // // Endian // AString endianAttributeNameForReadingArrayData = attributes.getValue(GiftiXmlElements::ATTRIBUTE_DATA_ARRAY_ENDIAN); if (endianAttributeNameForReadingArrayData.isEmpty()) { throw XmlSaxParserException("Required attribute " + GiftiXmlElements::ATTRIBUTE_DATA_ARRAY_ENDIAN + " not found for DataArray"); } bool endianValid = false; this->endianForReadingArrayData = GiftiEndianEnum::fromGiftiName(endianAttributeNameForReadingArrayData, &endianValid); if (endianValid == false) { throw XmlSaxParserException("Attribute " + GiftiXmlElements::ATTRIBUTE_DATA_ARRAY_ENDIAN + "is invalid: " + endianAttributeNameForReadingArrayData); } // // Dimensions // const AString dimString = attributes.getValue(GiftiXmlElements::ATTRIBUTE_DATA_ARRAY_DIMENSIONALITY); if (dimString.isEmpty()) { throw XmlSaxParserException("Required attribute " + GiftiXmlElements::ATTRIBUTE_DATA_ARRAY_DIMENSIONALITY + " not found for DataArray"); } this->dimensionsForReadingArrayData.clear(); const int numDimensions = dimString.toInt(); for (int i = 0; i < numDimensions; i++) { const AString dimNumString = attributes.getValue(GiftiXmlElements::getAttributeDimension(i)); if (dimNumString.isEmpty()) { throw XmlSaxParserException("Required dimension " + GiftiXmlElements::getAttributeDimension(i) + " not found for DataArray"); } const int dim = dimNumString.toInt(); this->dimensionsForReadingArrayData.push_back(dim); } // // Data Location // /* const AString dataLocationString = attributes.getValue(GiftiXmlElements::attDataLocation); if (dataLocationString.isEmpty()) { errorMessage = "Required attribute " + GiftiXmlElements::attDataLocation + " not found for DataArray"; return false; } bool validDataLocation = false; dataLocationForReadingArrayData = GiftiDataArray::getDataLocationFromName(dataLocationString, &validDataLocation); if (validDataLocation == false) { errorMessage = "Attribute " + GiftiXmlElements::attDataLocation + "is invalid: " + dataLocationString; return false; } if (dataLocationForReadingArrayData == GiftiDataArray::DATA_LOCATION_EXTERNAL) { errorMessage = "External data storage not supported."; return false; } */ // // Subscript order // const AString subscriptOrderString = attributes.getValue(GiftiXmlElements::ATTRIBUTE_DATA_ARRAY_INDEXING_ORDER); if (subscriptOrderString.isEmpty()) { throw XmlSaxParserException("Required attribute " + GiftiXmlElements::ATTRIBUTE_DATA_ARRAY_INDEXING_ORDER + " not found for DataArray"); } bool validArraySubscriptingOrder = false; this->arraySubscriptingOrderForReadingArrayData = GiftiArrayIndexingOrderEnum::fromGiftiName( subscriptOrderString, &validArraySubscriptingOrder); if (validArraySubscriptingOrder == false) { throw XmlSaxParserException("Attribute " + GiftiXmlElements::ATTRIBUTE_DATA_ARRAY_INDEXING_ORDER + "is invalid: " + subscriptOrderString); } this->dataArray.grabNew(new GiftiDataArray(intent)); /* * Indicate that data has not been read. */ dataArrayDataHasBeenRead = false; } /** * process the array data into numbers. */ void GiftiFileSaxReader::processArrayData() { this->dataArrayDataHasBeenRead = true; CaretAssert(dataArray); try { dataArray->readFromText(elementText, this->endianForReadingArrayData, arraySubscriptingOrderForReadingArrayData, dataTypeForReadingArrayData, dimensionsForReadingArrayData, encodingForReadingArrayData, externalFileNameForReadingData, externalFileOffsetForReadingData, this->giftiFile->getReadMetaDataOnlyFlag()); } catch (const GiftiException& e) { throw XmlSaxParserException(e.whatString()); } } /** * get characters in an element. */ void GiftiFileSaxReader::characters(const char* ch) { if (this->metaDataSaxReader != NULL) { this->metaDataSaxReader->characters(ch); } else if (this->labelTableSaxReader != NULL) { this->labelTableSaxReader->characters(ch); } else { elementText += ch; } } /** * a fatal error occurs. */ void GiftiFileSaxReader::fatalError(const XmlSaxParserException& e) { /* std::ostringstream str; str << "Fatal Error at line number: " << e.getLineNumber() << "\n" << "Column number: " << e.getColumnNumber() << "\n" << "Message: " << e.whatString(); if (errorMessage.isEmpty() == false) { str << "\n" << errorMessage; } errorMessage = str.str(); */ // // Stop parsing // throw e; } // a warning occurs void GiftiFileSaxReader::warning(const XmlSaxParserException& e) { CaretLogWarning("XML Parser Warning: " + e.whatString()); } // an error occurs void GiftiFileSaxReader::error(const XmlSaxParserException& e) { CaretLogSevere("XML Parser Error: " + e.whatString()); throw e; } void GiftiFileSaxReader::startDocument() { } void GiftiFileSaxReader::endDocument() { } workbench-1.1.1/src/Gifti/GiftiFileSaxReader.h000066400000000000000000000124131255417355300211520ustar00rootroot00000000000000 #ifndef __GIFTI_FILE_SAX_READER_H__ #define __GIFTI_FILE_SAX_READER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include "CaretPointer.h" #include "GiftiArrayIndexingOrderEnum.h" #include "GiftiEndianEnum.h" #include "GiftiEncodingEnum.h" #include "NiftiEnums.h" #include "XmlSaxParserException.h" #include "XmlSaxParserHandlerInterface.h" namespace caret { class GiftiDataArray; class GiftiFile; class GiftiLabelTableSaxReader; class GiftiMetaDataSaxReader; class GiftiFile; class Matrix4x4; class XmlAttributes; class XmlException; /// class for reading a GIFTI Node Data File with a SAX Parser class GiftiFileSaxReader : public CaretObject, public XmlSaxParserHandlerInterface { public: GiftiFileSaxReader(GiftiFile* giftiFileIn); virtual ~GiftiFileSaxReader(); void startElement(const AString& namespaceURI, const AString& localName, const AString& qName, const XmlAttributes& attributes); void endElement(const AString& namspaceURI, const AString& localName, const AString& qName); void characters(const char* ch); void fatalError(const XmlSaxParserException& e); void warning(const XmlSaxParserException& e); void error(const XmlSaxParserException& e); void startDocument(); void endDocument(); protected: /// file reading states enum STATE { /// no state STATE_NONE, /// processing GIFTI tag STATE_GIFTI, /// processing MetaData tag STATE_METADATA, /// processing LabelTable tag STATE_LABEL_TABLE, /// processing DataArray tag STATE_DATA_ARRAY, /// processing DataArray Data tag STATE_DATA_ARRAY_DATA, /// processing DataArray Matrix Tag STATE_DATA_ARRAY_MATRIX, /// processing DataArray Matrix Data Space Tag STATE_DATA_ARRAY_MATRIX_DATA_SPACE, /// processing DataArray Matrix Transformed Space Tag STATE_DATA_ARRAY_MATRIX_TRANSFORMED_SPACE, /// processing DataArray Matrix Data Tag STATE_DATA_ARRAY_MATRIX_DATA }; // process the array data into numbers void processArrayData(); // create a data array void createDataArray(const XmlAttributes& attributes); /// file reading state STATE state; /// the state stack used when reading a file std::stack stateStack; /// the error message AString errorMessage; /// GIFTI file that is being read GiftiFile* giftiFile; /// element text AString elementText; /// GIFTI data array being read CaretPointer dataArray; /// GIFTI label table being read GiftiLabelTable* labelTable; /// GIFTI label table sax reader GiftiLabelTableSaxReader* labelTableSaxReader; /// GIFTI meta data sax reader GiftiMetaDataSaxReader* metaDataSaxReader; /// GIFTI matrix data being read Matrix4x4* matrix; /// endian attribute data GiftiEndianEnum::Enum endianForReadingArrayData; /// array subscripting order for reading GiftiArrayIndexingOrderEnum::Enum arraySubscriptingOrderForReadingArrayData; /// data type for reading NiftiDataTypeEnum::Enum dataTypeForReadingArrayData; /// dimension for reading std::vector dimensionsForReadingArrayData; /// encoding for reading GiftiEncodingEnum::Enum encodingForReadingArrayData; /// data location for reading //GiftiDataArray::DATA_LOCATION dataLocationForReadingArrayData; /// external file name AString externalFileNameForReadingData; /// external file offset int64_t externalFileOffsetForReadingData; /// tracks if data has been read since external binary may not have DATA tag bool dataArrayDataHasBeenRead; }; } // namespace #endif // __GIFTI_FILE_SAX_READER_H__ workbench-1.1.1/src/Gifti/GiftiFileWriter.cxx000066400000000000000000000274321255417355300211320ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #define __GIFTI_FILE_WRITER_DECLARE__ #include "GiftiFileWriter.h" #undef __GIFTI_FILE_WRITER_DECLARE__ #include "FileInformation.h" #include "GiftiDataArray.h" #include "GiftiXmlElements.h" #include "XmlWriter.h" using namespace caret; /** * \class GiftiFileWriter * \brief Writes GIFTI files. * * Writes a GIFTI data array file. In most cases, using the * GiftiDataArrayFile's writeFile() method should be used for writing a file. * This class can be used to incrementally write a file. After constructing * an object of this class, call start(), call writeDataArray() for each * data array, and lastly call finish(). */ /** * Constructor. * * @param filename - name of the file. * @param encoding - encoding of the file. */ GiftiFileWriter::GiftiFileWriter(const AString& filename, const GiftiEncodingEnum::Enum encoding) : CaretObject() { this->xmlFileOutputStream = NULL; this->externalFileOutputStream = NULL; this->maximumExternalFileSize = 1024 * 1024 * 1024; this->filename = filename; this->encoding = encoding; this->xmlWriter = NULL; this->dataArraysWrittenCounter = 0; } /** * Destructor. */ GiftiFileWriter::~GiftiFileWriter() { this->closeFiles(); if (this->xmlWriter != NULL) { delete this->xmlWriter; this->xmlWriter = NULL; } } /** * Start writing the file. * * @param numberOfDataArrays - The number of data arrays that for file * that is being written. * @param metadata - The file's metadata or null if no metadata. * @param labelTable - The file's label table or null if no labels. * @throws GiftiException - If an error occurs. */ void GiftiFileWriter::start(const int numberOfDataArrays, GiftiMetaData* metadata, GiftiLabelTable* labelTable) { this->numberOfDataArrays = numberOfDataArrays; // // Does the file need to be opened? // if (this->xmlFileOutputStream == NULL) { // // Open the file // char* name = this->filename.toCharArray(); this->xmlFileOutputStream = new std::ofstream(name); delete[] name; if (! this->xmlFileOutputStream->good()) { delete this->xmlFileOutputStream; this->xmlFileOutputStream = NULL; AString msg = "Unable to open " + this->filename + " for writing."; throw GiftiException(msg); } // // Remove any existing external files. // this->removeExternalFiles(); } try { // // Format the version string so that it ends with at most one zero // const AString versionString = AString::number(GiftiFile::getCurrentFileVersion()); // // Create the xml writer // this->xmlWriter = new XmlWriter(*this->xmlFileOutputStream); // // Write header info // this->xmlWriter->writeStartDocument("1.0"); //this->xmlWriter.writeDTD(GiftiXmlElements.TAG_GIFTI, // "http://www.nitrc.org/frs/download.php/115/gifti.dtd"); // // Write GIFTI root element // XmlAttributes attributes; attributes.addAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance"); attributes.addAttribute("xsi:noNamespaceSchemaLocation", "http://brainvis.wustl.edu/caret6/xml_schemas/GIFTI_Caret.xsd"); attributes.addAttribute(GiftiXmlElements::ATTRIBUTE_GIFTI_VERSION, versionString); attributes.addAttribute(GiftiXmlElements::ATTRIBUTE_GIFTI_NUMBER_OF_DATA_ARRAYS, this->numberOfDataArrays); this->xmlWriter->writeStartElement(GiftiXmlElements::TAG_GIFTI, attributes); // // Write Metadata // if (metadata != NULL) { metadata->writeAsXML(*this->xmlWriter); } // // Write Labels // if (labelTable != NULL) { labelTable->writeAsXML(*this->xmlWriter); } } catch (const GiftiException& e) { this->closeFiles(); throw e; } } /** * Write a GIFTI Data Array. * * @param gda - The data array. * @throws GiftiException - If an error occurs. */ void GiftiFileWriter::writeDataArray(GiftiDataArray* gda) { this->verifyOpened(); if (this->dataArraysWrittenCounter >= this->numberOfDataArrays) { this->closeFiles(); throw GiftiException("PROGRAMMER ERROR: the number of data arrays " "written exceeds the number of data arrays in the file " "passed to start."); } try { // // // // Get the external file name. // // // String externalFileName = ""; // if (this.encoding == GiftiEncoding.EXTERNAL_FILE_BINARY) { // externalFileName = this.getExternalFileNameForWriting(); // } // // Writing to an external binary data file? // if (this->encoding == GiftiEncodingEnum::EXTERNAL_FILE_BINARY) { if (this->externalFileOutputStream == NULL) { char* name = this->getExternalFileNameForWriting().toCharArray(); this->externalFileOutputStream = new std::ofstream(name,std::fstream::binary); delete[] name; if (! *this->externalFileOutputStream) { this->closeFiles(); const AString msg = ("Unable to open " + this->getExternalFileNameForWriting() + " for writing."); throw GiftiException(msg); } } const int64_t fileOffset = this->externalFileOutputStream->tellp(); FileInformation myInfo(this->getExternalFileNameForWriting());//TODO: get filename only without doing a stat? gda->setExternalFileInformation(myInfo.getFileName(), fileOffset); } // // Write data array // gda->writeAsXML(*this->xmlFileOutputStream, this->externalFileOutputStream, this->encoding); // // Increment counter of data arrays written // this->dataArraysWrittenCounter++; } catch (const GiftiException& e) { this->closeFiles(); throw e; } catch (const XmlException& e) { this->closeFiles(); throw GiftiException(e); } } /** * Finish writing the file. Closes any open files. * @throws GiftiException If file error or number of data arrays written * does not match number of data arrays passed to start() method. */ void GiftiFileWriter::finish() { this->verifyOpened(); try { // // Write GIFTI root end element // this->xmlWriter->writeEndElement(); this->xmlWriter->writeEndDocument(); } catch (const XmlException& e) { this->closeFiles(); throw GiftiException(e); } // // Close the file // this->closeFiles(); } /** * Get the maximum external file size. * * @return The size. */ long GiftiFileWriter::getMaximumExternalFileSize() const { return this->maximumExternalFileSize; } /** * Set the maximum external file size. Starts a new external file when * this size is reached or exceeded. * * @param size Desired maximum size. */ void GiftiFileWriter::setMaximumExternalFileSize(const long size) { this->maximumExternalFileSize = size; } /** * Close any open files. */ void GiftiFileWriter::closeFiles() { if (this->xmlFileOutputStream != NULL) { if (this->xmlFileOutputStream->is_open()) { this->xmlFileOutputStream->close(); } delete this->xmlFileOutputStream; this->xmlFileOutputStream = NULL; } if (this->externalFileOutputStream != NULL) { if (this->externalFileOutputStream->is_open()) { this->externalFileOutputStream->close(); } delete this->externalFileOutputStream; this->externalFileOutputStream = NULL; } } /** * Verify the file is properly opened. * * @throws GiftiException If file was not opened properly. */ void GiftiFileWriter::verifyOpened() { if (this->xmlWriter == NULL) { throw GiftiException("Trying to write to file named \"" + this->filename + "\" but the file was not properly opened " "by calling start() or opening the file " "failed."); } } /** * Remove any existing external data files. * * @throws GiftiException - If unable to delete a file. */ void GiftiFileWriter::removeExternalFiles() { int maxFiles = 50; // more than enough for (int counter = -1; counter <= maxFiles; counter++) { AString name = this->getExternalFileNamePrefix() + AString::number(counter); if (counter < 0) { name = this->getExternalFileNamePrefix(); // old version of ext file } FileInformation fileInfo(name); if (fileInfo.exists()) { if (fileInfo.remove() == false) { throw GiftiException("Unable to delete an existing external " " file named \"" + name + "\"."); } } } } /** * Get the name of the external file prefix. * * @return The XML file name + ".data" */ AString GiftiFileWriter::getExternalFileNamePrefix() const { return this->filename + ".data"; } /** * Get the name of the external file that should be used for writing. * * @return Name of external file. */ AString GiftiFileWriter::getExternalFileNameForWriting() const { return this->getExternalFileNamePrefix(); // COULD use multiple files for external data when large. // boolean done = false; // int counter = 1; // String name = this.getExternalFileNamePrefix() + counter; // while (done == false) { // File file = new File(name); // if (file.exists()) { // if (file.length() > this.maximumExternalFileSize) { // counter++; // name = this.getExternalFileNamePrefix() + counter; // } // else { // done = true; // } // } // else { // done = true; // } // } // // return name; } /** * Get a description of this object's content. * @return String describing this object's content. */ AString GiftiFileWriter::toString() const { return "GiftiFileWriter"; } workbench-1.1.1/src/Gifti/GiftiFileWriter.h000066400000000000000000000061571255417355300205600ustar00rootroot00000000000000#ifndef __GIFTI_FILE_WRITER__H_ #define __GIFTI_FILE_WRITER__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretObject.h" #include "GiftiFile.h" #include "GiftiEncodingEnum.h" namespace caret { class GiftiDataArray; class GiftiMetaData; class GiftiLabelTable; class XmlWriter; class GiftiFileWriter : public CaretObject { public: GiftiFileWriter(const AString& filename, const GiftiEncodingEnum::Enum encoding); virtual ~GiftiFileWriter(); void start(const int numberOfDataArrays, GiftiMetaData* metadata, GiftiLabelTable* labelTable); void writeDataArray(GiftiDataArray* gda); void finish(); long getMaximumExternalFileSize() const; void setMaximumExternalFileSize(const long size); private: GiftiFileWriter(const GiftiFileWriter&); GiftiFileWriter& operator=(const GiftiFileWriter&); void closeFiles(); void verifyOpened(); void removeExternalFiles(); AString getExternalFileNamePrefix() const; AString getExternalFileNameForWriting() const; public: virtual AString toString() const; private: /** The file output stream for the XML file. */ std::ofstream* xmlFileOutputStream; /** The file output stream for the external data file. */ std::ofstream* externalFileOutputStream; /** The number of data arrays in the file being written. */ int numberOfDataArrays; /** Start a new external file when the external file reaches this size. */ long maximumExternalFileSize; /** name of file to write. */ AString filename; /** encoding of file. */ GiftiEncodingEnum::Enum encoding; /** The XML writer. */ XmlWriter* xmlWriter; /** Counts the number of data arrays that have been written. */ int dataArraysWrittenCounter; }; #ifdef __GIFTI_FILE_WRITER_DECLARE__ // #endif // __GIFTI_FILE_WRITER_DECLARE__ } // namespace #endif //__GIFTI_FILE_WRITER__H_ workbench-1.1.1/src/Gifti/GiftiLabelTableSaxReader.cxx000066400000000000000000000200151255417355300226320ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretLogger.h" #include "GiftiEndianEnum.h" #include "GiftiLabel.h" #include "GiftiFile.h" #include "GiftiLabelTableSaxReader.h" #include "GiftiXmlElements.h" #include "NiftiEnums.h" #include "XmlAttributes.h" #include "XmlException.h" using namespace caret; /** * constructor. */ GiftiLabelTableSaxReader::GiftiLabelTableSaxReader(GiftiLabelTable* labelTable) { this->state = STATE_NONE; this->stateStack.push(state); this->elementText = ""; this->labelTable = labelTable; m_haveUnlabeled = false; } /** * destructor. */ GiftiLabelTableSaxReader::~GiftiLabelTableSaxReader() { } /** * start an element. */ void GiftiLabelTableSaxReader::startElement(const AString& /* namespaceURI */, const AString& /* localName */, const AString& qName, const XmlAttributes& attributes) { const STATE previousState = this->state; switch (this->state) { case STATE_NONE: if (qName == GiftiXmlElements::TAG_LABEL_TABLE) { this->state = STATE_LABEL_TABLE; } else { std::ostringstream str; str << "Root element is \"" << qName.toStdString() << "\" but should be " << GiftiXmlElements::TAG_LABEL_TABLE.toStdString(); throw XmlSaxParserException(AString::fromStdString(str.str())); } break; case STATE_LABEL_TABLE: if (qName == GiftiXmlElements::TAG_LABEL) { this->state = STATE_LABEL_TABLE_LABEL; AString s = attributes.getValue(GiftiXmlElements::ATTRIBUTE_LABEL_KEY); if (s == "") { s = attributes.getValue("Index"); } if (s.isEmpty()) { std::ostringstream str; str << "Tag " << GiftiXmlElements::TAG_LABEL.toStdString() << " is missing its " << GiftiXmlElements::ATTRIBUTE_LABEL_KEY.toStdString(); throw XmlSaxParserException(AString::fromStdString(str.str())); } this->labelIndex = s.toInt(); float defaultRGBA[4]; GiftiLabel::getDefaultColor(defaultRGBA); this->labelRed = defaultRGBA[0]; this->labelGreen = defaultRGBA[1]; this->labelBlue = defaultRGBA[2]; this->labelAlpha = defaultRGBA[3]; const AString redString = attributes.getValue(GiftiXmlElements::ATTRIBUTE_LABEL_RED); if (redString.isEmpty() == false) { this->labelRed = redString.toFloat(); } const AString greenString = attributes.getValue(GiftiXmlElements::ATTRIBUTE_LABEL_GREEN); if (greenString.isEmpty() == false) { this->labelGreen = greenString.toFloat(); } const AString blueString = attributes.getValue(GiftiXmlElements::ATTRIBUTE_LABEL_BLUE); if (blueString.isEmpty() == false) { this->labelBlue = blueString.toFloat(); } const AString alphaString = attributes.getValue(GiftiXmlElements::ATTRIBUTE_LABEL_ALPHA); if (alphaString.isEmpty() == false) { this->labelAlpha = alphaString.toFloat(); } this->labelX = attributes.getValueAsFloat(GiftiXmlElements::ATTRIBUTE_LABEL_X); this->labelY = attributes.getValueAsFloat(GiftiXmlElements::ATTRIBUTE_LABEL_Y); this->labelZ = attributes.getValueAsFloat(GiftiXmlElements::ATTRIBUTE_LABEL_Z); } else { std::ostringstream str; str << "Child of " << GiftiXmlElements::TAG_LABEL_TABLE.toStdString() << " is \"" << qName.toStdString() << "\" but should be " << GiftiXmlElements::TAG_LABEL.toStdString(); throw XmlSaxParserException(AString::fromStdString(str.str())); } break; case STATE_LABEL_TABLE_LABEL: { std::ostringstream str; str << GiftiXmlElements::TAG_LABEL.toStdString() << " has child \"" << qName.toStdString() << "\" but should not have any child nodes"; throw XmlSaxParserException(AString::fromStdString(str.str())); } break; } // // Save previous state // this->stateStack.push(previousState); this->elementText = ""; } /** * end an element. */ void GiftiLabelTableSaxReader::endElement(const AString& /* namspaceURI */, const AString& /* localName */, const AString& /*qName*/) { switch (state) { case STATE_NONE: break; case STATE_LABEL_TABLE: break; case STATE_LABEL_TABLE_LABEL: if ((elementText == "unknown" || elementText == "Unknown") && labelAlpha == 0.0f) { if (!m_haveUnlabeled)//if we already have an unlabeled key, don't change things, and warn { m_haveUnlabeled = true; CaretLogFiner("Using '" + elementText + "' label as unlabeled key"); elementText = "???";//pretend these strings are actually "???" when alpha is 0, as that means it is unlabeled } else { CaretLogWarning("found multiple label elements that should be interpreted as unlabeled"); } } else if (elementText == "???") { if (m_haveUnlabeled) { CaretLogWarning("found multiple label elements that should be interpreted as unlabeled"); } m_haveUnlabeled = true;//that identifier always means unlabeled to us, don't let it slip by without setting m_haveUnlabeled } this->labelTable->setLabel(this->labelIndex, this->elementText, this->labelRed, this->labelGreen, this->labelBlue, this->labelAlpha, this->labelX, this->labelY, this->labelZ); break; } // // Clear out for new elements // this->elementText = ""; // // Go to previous state // if (this->stateStack.empty()) { throw XmlSaxParserException("State stack is empty while reading GiftiLabelTable."); } this->state = stateStack.top(); this->stateStack.pop(); } /** * get characters in an element. */ void GiftiLabelTableSaxReader::characters(const char* ch) { this->elementText += ch; } /** * a fatal error occurs. */ void GiftiLabelTableSaxReader::fatalError(const XmlSaxParserException& e) { throw e; } /** * A warning occurs */ void GiftiLabelTableSaxReader::warning(const XmlSaxParserException& e) { CaretLogWarning("XML Parser Warning: " + e.whatString()); } // an error occurs void GiftiLabelTableSaxReader::error(const XmlSaxParserException& e) { throw e; } void GiftiLabelTableSaxReader::startDocument() { } void GiftiLabelTableSaxReader::endDocument() { } workbench-1.1.1/src/Gifti/GiftiLabelTableSaxReader.h000066400000000000000000000070031255417355300222610ustar00rootroot00000000000000 #ifndef __GIFTI_LABEL_TABLE_SAX_READER_H__ #define __GIFTI_LABEL_TABLE_SAX_READER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include "XmlSaxParserException.h" #include "XmlSaxParserHandlerInterface.h" namespace caret { class GiftiLabelTable; class XmlAttributes; /** * class for reading a GiftiLabelTable with a SAX Parser */ class GiftiLabelTableSaxReader : public CaretObject, public XmlSaxParserHandlerInterface { public: GiftiLabelTableSaxReader(GiftiLabelTable* labelTable); virtual ~GiftiLabelTableSaxReader(); void startElement(const AString& namespaceURI, const AString& localName, const AString& qName, const XmlAttributes& attributes); void endElement(const AString& namspaceURI, const AString& localName, const AString& qName); void characters(const char* ch); void fatalError(const XmlSaxParserException& e); void warning(const XmlSaxParserException& e); void error(const XmlSaxParserException& e); void startDocument(); void endDocument(); protected: /// file reading states enum STATE { /// no state STATE_NONE, /// processing LabelTable tag STATE_LABEL_TABLE, /// processing LabelTable Label STATE_LABEL_TABLE_LABEL }; /// file reading state STATE state; /// the state stack used when reading a file std::stack stateStack; /// the error message AString errorMessage; /// element text AString elementText; /// GIFTI label table being read GiftiLabelTable* labelTable; /// label index int labelIndex; /// label color component float labelRed; /// label color component float labelGreen; /// label color component float labelBlue; /// label color component float labelAlpha; /// label's X-coordinate float labelX; /// label's Y-coordinate float labelY; /// label's Z-coordinate float labelZ; /// tracks whether we have read an "unused" label, because the GiftiLabelTable constructor and clear() add "???" whether we want it or not bool m_haveUnlabeled; }; } // namespace #endif // __GIFTI_LABEL_TABLE_SAX_READER_H__ workbench-1.1.1/src/Gifti/GiftiMetaDataSaxReader.cxx000066400000000000000000000144641255417355300223360ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretLogger.h" #include "GiftiMetaData.h" #include "GiftiMetaDataSaxReader.h" #include "GiftiXmlElements.h" #include "XmlAttributes.h" #include "XmlException.h" using namespace caret; /** * constructor. */ GiftiMetaDataSaxReader::GiftiMetaDataSaxReader(GiftiMetaData* metaData) { this->state = STATE_NONE; this->stateStack.push(this->state); this->metaDataName = ""; this->metaDataValue = ""; this->elementText = ""; this->metaData = metaData; } /** * destructor. */ GiftiMetaDataSaxReader::~GiftiMetaDataSaxReader() { } /** * start an element. */ void GiftiMetaDataSaxReader::startElement(const AString& /* namespaceURI */, const AString& /* localName */, const AString& qName, const XmlAttributes& /*attributes*/) { const STATE previousState = this->state; switch (this->state) { case STATE_NONE: if (qName == GiftiXmlElements::TAG_METADATA) { this->state = STATE_METADATA; } else { std::ostringstream str; str << "Root element is \"" << qName.toStdString() << "\" but should be " << GiftiXmlElements::TAG_METADATA.toStdString(); throw XmlSaxParserException(AString::fromStdString(str.str())); } break; case STATE_METADATA: if (qName == GiftiXmlElements::TAG_METADATA_ENTRY) { this->state = STATE_METADATA_MD; } else { std::ostringstream str; str << "Child of " << GiftiXmlElements::TAG_METADATA.toStdString() << " is \"" << qName.toStdString() << "\" but should be " << GiftiXmlElements::TAG_METADATA_ENTRY.toStdString(); throw XmlSaxParserException(AString::fromStdString(str.str())); } break; case STATE_METADATA_MD: if (qName == GiftiXmlElements::TAG_METADATA_NAME) { this->state = STATE_METADATA_MD_NAME; } else if (qName == GiftiXmlElements::TAG_METADATA_VALUE) { this->state = STATE_METADATA_MD_VALUE; } else { std::ostringstream str; str << "Child of " << GiftiXmlElements::TAG_METADATA_ENTRY.toStdString() << " is \"" << qName.toStdString() << "\" but should be one of \n" << " " << GiftiXmlElements::TAG_METADATA_NAME.toStdString() << "\n" << " " << GiftiXmlElements::TAG_METADATA_VALUE.toStdString(); throw XmlSaxParserException(AString::fromStdString(str.str())); } break; case STATE_METADATA_MD_NAME: { std::ostringstream str; str << GiftiXmlElements::TAG_METADATA_NAME.toStdString() << " has child \"" << qName.toStdString() << "\" but should not have any child nodes"; throw XmlSaxParserException(AString::fromStdString(str.str())); } break; case STATE_METADATA_MD_VALUE: { std::ostringstream str; str << GiftiXmlElements::TAG_METADATA_VALUE.toStdString() << " has child \"" << qName.toStdString() << "\" but should not have any child nodes"; throw XmlSaxParserException(AString::fromStdString(str.str())); } break; } // // Save previous state // this->stateStack.push(previousState); this->elementText = ""; } /** * end an element. */ void GiftiMetaDataSaxReader::endElement(const AString& /* namspaceURI */, const AString& /* localName */, const AString& /*qName*/) { switch (state) { case STATE_NONE: break; case STATE_METADATA: break; case STATE_METADATA_MD: if ((this->metaDataName.isEmpty() == false) && (this->metaDataValue.isEmpty() == false)) { if (this->metaData != NULL) { this->metaData->set(metaDataName, metaDataValue); } else { throw XmlSaxParserException("ERROR: Have metadata name/value but no MetaDeta."); } this->metaDataName = ""; this->metaDataValue = ""; } break; case STATE_METADATA_MD_NAME: this->metaDataName = elementText; break; case STATE_METADATA_MD_VALUE: this->metaDataValue = elementText; break; } // // Clear out for new elements // this->elementText = ""; // // Go to previous state // if (this->stateStack.empty()) { throw XmlSaxParserException("State stack is empty while reading XML MetaData."); } this->state = this->stateStack.top(); this->stateStack.pop(); } /** * get characters in an element. */ void GiftiMetaDataSaxReader::characters(const char* ch) { this->elementText += ch; } /** * a fatal error occurs. */ void GiftiMetaDataSaxReader::fatalError(const XmlSaxParserException& e) { // // Stop parsing // CaretLogSevere("XML Parser Fatal Error: " + e.whatString()); throw e; } // a warning occurs void GiftiMetaDataSaxReader::warning(const XmlSaxParserException& e) { CaretLogWarning("XML Parser Warning: " + e.whatString()); } // an error occurs void GiftiMetaDataSaxReader::error(const XmlSaxParserException& e) { throw e; } void GiftiMetaDataSaxReader::startDocument() { } void GiftiMetaDataSaxReader::endDocument() { } workbench-1.1.1/src/Gifti/GiftiMetaDataSaxReader.h000066400000000000000000000064031255417355300217550ustar00rootroot00000000000000 #ifndef __GIFTI_META_DATA_SAX_READER_H__ #define __GIFTI_META_DATA_SAX_READER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include "XmlSaxParserException.h" #include "XmlSaxParserHandlerInterface.h" namespace caret { class GiftiMetaData; class XmlAttributes; /** * class for reading Gifti Metadata with a SAX Parser */ class GiftiMetaDataSaxReader : public CaretObject, public XmlSaxParserHandlerInterface { public: GiftiMetaDataSaxReader(GiftiMetaData* metaData); virtual ~GiftiMetaDataSaxReader(); void startElement(const AString& namespaceURI, const AString& localName, const AString& qName, const XmlAttributes& attributes); void endElement(const AString& namspaceURI, const AString& localName, const AString& qName); void characters(const char* ch); void fatalError(const XmlSaxParserException& e); void warning(const XmlSaxParserException& e); void error(const XmlSaxParserException& e); void startDocument(); void endDocument(); private: GiftiMetaDataSaxReader(const GiftiMetaDataSaxReader&); GiftiMetaDataSaxReader& operator=(const GiftiMetaDataSaxReader&); protected: /// file reading states enum STATE { /// no state STATE_NONE, /// processing MetaData tag STATE_METADATA, /// processing MetaData MD child tag STATE_METADATA_MD, /// processing MetaData MD Name tag STATE_METADATA_MD_NAME, /// processing MetaData MD Value tag STATE_METADATA_MD_VALUE }; /// file reading state STATE state; /// the state stack used when reading a file std::stack stateStack; /// the error message AString errorMessage; /// meta data name AString metaDataName; /// meta data value AString metaDataValue; /// element text AString elementText; /// GIFTI meta data being read GiftiMetaData* metaData; }; } // namespace #endif // __GIFTI_META_DATA_SAX_READER_H__ workbench-1.1.1/src/GuiQt/000077500000000000000000000000001255417355300153265ustar00rootroot00000000000000workbench-1.1.1/src/GuiQt/AboutWorkbenchDialog.cxx000066400000000000000000000141021255417355300221050ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __ABOUT_WORKBENCH_DIALOG_DECLARE__ #include "AboutWorkbenchDialog.h" #undef __ABOUT_WORKBENCH_DIALOG_DECLARE__ #include #include #include #include #include #include #include #include "ApplicationInformation.h" #include "BrainOpenGLWidget.h" #include "CaretAssert.h" #include "WuQDataEntryDialog.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::AboutWorkbenchDialog * \brief Dialog that displays information about workbench. */ /** * Constructor. * * @param openGLParentWidget * The parent OpenGL Widget for which information is provided. */ AboutWorkbenchDialog::AboutWorkbenchDialog(BrainOpenGLWidget* openGLParentWidget) : WuQDialogModal("About Workbench", openGLParentWidget) { m_openGLParentWidget = openGLParentWidget; this->setCancelButtonText(""); ApplicationInformation appInfo; QLabel* imageLabel = NULL; QPixmap pixmap; if (WuQtUtilities::loadPixmap(":/About/hcp-logo.png", pixmap)) { imageLabel = new QLabel(); imageLabel->setPixmap(pixmap); imageLabel->setAlignment(Qt::AlignCenter); } QLabel* workbenchLabel = new QLabel(appInfo.getName()); QFont workbenchFont = workbenchLabel->font(); workbenchFont.setBold(true); workbenchFont.setPointSize(32); workbenchLabel->setFont(workbenchFont); const QString labelStyle = ("QLabel { " " font: 20px bold " "}"); QLabel* hcpWebsiteLabel = new QLabel("" "Visit the Human Connectome Project" ""); hcpWebsiteLabel->setStyleSheet(labelStyle); hcpWebsiteLabel->setAlignment(Qt::AlignCenter); QObject::connect(hcpWebsiteLabel, SIGNAL(linkActivated(const QString&)), this, SLOT(websiteLinkActivated(const QString&))); QLabel* versionLabel = new QLabel("Version " + appInfo.getVersion()); QLabel* copyrightLabel = new QLabel("Copyright " + QString::number(QDate::currentDate().year()) + " Washington University"); m_morePushButton = addUserPushButton("More...", QDialogButtonBox::NoRole); m_openGLPushButton = addUserPushButton("OpenGL...", QDialogButtonBox::NoRole); QWidget* widget = new QWidget(); QVBoxLayout* layout = new QVBoxLayout(widget); WuQtUtilities::setLayoutSpacingAndMargins(layout, 4, 2); if (imageLabel != NULL) { layout->addWidget(imageLabel, 0, Qt::AlignHCenter); layout->addSpacing(15); } layout->addWidget(workbenchLabel, 0, Qt::AlignHCenter); layout->addSpacing(15); layout->addWidget(hcpWebsiteLabel, 0, Qt::AlignHCenter); layout->addSpacing(15); layout->addWidget(versionLabel, 0, Qt::AlignHCenter); layout->addSpacing(15); layout->addWidget(copyrightLabel, 0, Qt::AlignHCenter); layout->addSpacing(15); this->setCentralWidget(widget, WuQDialog::SCROLL_AREA_NEVER); } /** * Destructor. */ AboutWorkbenchDialog::~AboutWorkbenchDialog() { } AboutWorkbenchDialog::DialogUserButtonResult AboutWorkbenchDialog::userButtonPressed(QPushButton* userPushButton) { if (userPushButton == m_openGLPushButton) { displayOpenGLInformation(); } else if (userPushButton == m_morePushButton) { displayMoreInformation(); } else { CaretAssert(0); } return AboutWorkbenchDialog::RESULT_NONE; } void AboutWorkbenchDialog::displayOpenGLInformation() { WuQDataEntryDialog ded("OpenGL Information", this, true); ded.addTextEdit("", m_openGLParentWidget->getOpenGLInformation(), true); ded.resize(600, 600); ded.setCancelButtonText(""); ded.exec(); } void AboutWorkbenchDialog::displayMoreInformation() { ApplicationInformation appInfo; std::vector informationData; appInfo.getAllInformation(informationData); QString styleName("Undefined"); QStyle* appStyle = QApplication::style(); if (appStyle != NULL) { styleName = appStyle->objectName(); } informationData.push_back(QString("Style Name: " + styleName)); WuQDataEntryDialog ded("More " + appInfo.getName() + " Information", this, true); const int32_t numInfo = static_cast(informationData.size()); for (int32_t i = 0; i < numInfo; i++) { ded.addWidget("", new QLabel(informationData[i])); } ded.setCancelButtonText(""); ded.exec(); } /** * Called when a label's hyperlink is selected. * @param link * The URL. */ void AboutWorkbenchDialog::websiteLinkActivated(const QString& link) { if (link.isEmpty() == false) { QDesktopServices::openUrl(QUrl(link)); } } workbench-1.1.1/src/GuiQt/AboutWorkbenchDialog.h000066400000000000000000000037641255417355300215460ustar00rootroot00000000000000#ifndef __ABOUT_WORKBENCH_DIALOG__H_ #define __ABOUT_WORKBENCH_DIALOG__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "WuQDialogModal.h" class QPushButton; namespace caret { class BrainOpenGLWidget; class AboutWorkbenchDialog : public WuQDialogModal { Q_OBJECT public: AboutWorkbenchDialog(BrainOpenGLWidget* openGLParentWidget); virtual ~AboutWorkbenchDialog(); protected: virtual DialogUserButtonResult userButtonPressed(QPushButton* userPushButton); private slots: void websiteLinkActivated(const QString& link); private: AboutWorkbenchDialog(const AboutWorkbenchDialog&); AboutWorkbenchDialog& operator=(const AboutWorkbenchDialog&); void displayMoreInformation(); void displayOpenGLInformation(); QPushButton* m_morePushButton; QPushButton* m_openGLPushButton; BrainOpenGLWidget* m_openGLParentWidget; }; #ifdef __ABOUT_WORKBENCH_DIALOG_DECLARE__ // #endif // __ABOUT_WORKBENCH_DIALOG_DECLARE__ } // namespace #endif //__ABOUT_WORKBENCH_DIALOG__H_ workbench-1.1.1/src/GuiQt/BorderEditingSelectionDialog.cxx000066400000000000000000000117751255417355300235740ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __BORDER_EDITING_SELECTION_DIALOG_DECLARE__ #include "BorderEditingSelectionDialog.h" #undef __BORDER_EDITING_SELECTION_DIALOG_DECLARE__ #include #include #include #include #include #include "CaretAssert.h" using namespace caret; /** * \class caret::BorderEditingSelectionDialog * \brief Dialog for selecting border when borders are editing. * \ingroup GuiQt */ /** * Constructor. * * @param modeDescription * Description of the mode. * @param borderNames * Names of the borders for selection. * @param parent * Parent of the dialog. */ BorderEditingSelectionDialog::BorderEditingSelectionDialog(const AString& modeDescription, const std::vector& borderNames, QWidget* parent) : WuQDialogModal("Border Editing", parent) { AString modeText(modeDescription + ((borderNames.size() > 1) ? " these borders (ordered by distance): " : " this border: ")); const QString toolTipText = ("ERASE AND REPLACE - Distance is the average of:\n" " * Distance from first segment point to nearest point in border.\n" " * Distance from last segment point to nearest point in border.\n" "\n" "EXTEND - Distance is from first segment point to any point\n" "in border.\n"); QLabel* modeLabel = new QLabel(modeText); QPushButton* allOnPushButton = new QPushButton("All On"); QObject::connect(allOnPushButton, SIGNAL(clicked()), this, SLOT(allOnPushButtonClicked())); QPushButton* allOffPushButton = new QPushButton("All Off"); QObject::connect(allOffPushButton, SIGNAL(clicked()), this, SLOT(allOffPushButtonClicked())); QWidget* allOnOffWidget = new QWidget(); QHBoxLayout* allOnOffLayout = new QHBoxLayout(allOnOffWidget); allOnOffLayout->addWidget(allOnPushButton); allOnOffLayout->addStrut(10); allOnOffLayout->addWidget(allOffPushButton); allOnOffLayout->addStretch(); QWidget* widget = new QWidget; QVBoxLayout* layout = new QVBoxLayout(widget); layout->addWidget(modeLabel); for (std::vector::const_iterator iter = borderNames.begin(); iter != borderNames.end(); iter++) { QCheckBox* cb = new QCheckBox(*iter); cb->setChecked(true); cb->setToolTip(toolTipText); m_borderNameCheckBoxes.push_back(cb); layout->addWidget(cb); } setTopBottomAndCentralWidgets(allOnOffWidget, widget, NULL, WuQDialog::SCROLL_AREA_AS_NEEDED); disableAutoDefaultForAllPushButtons(); } /** * Destructor. */ BorderEditingSelectionDialog::~BorderEditingSelectionDialog() { } /** * Called when ALL ON push button is clicked. */ void BorderEditingSelectionDialog::allOnPushButtonClicked() { setAllCheckBoxesChecked(true); } /** * Called when ALL OFF push button is clicked. */ void BorderEditingSelectionDialog::allOffPushButtonClicked() { setAllCheckBoxesChecked(false); } /** * Set all checkbox check status. * * @param status * New checked status. */ void BorderEditingSelectionDialog::setAllCheckBoxesChecked(const bool status) { for (std::vector::iterator iter = m_borderNameCheckBoxes.begin(); iter != m_borderNameCheckBoxes.end(); iter++) { QCheckBox* cb = *iter; cb->setChecked(status); } } /** * Is the border name with the given index selected ? * * @param borderNameIndex * Index of the border name. * @return * Selection status. */ bool BorderEditingSelectionDialog::isBorderNameSelected(const int32_t borderNameIndex) const { CaretAssertVectorIndex(m_borderNameCheckBoxes, borderNameIndex); return m_borderNameCheckBoxes[borderNameIndex]->isChecked(); } workbench-1.1.1/src/GuiQt/BorderEditingSelectionDialog.h000066400000000000000000000042151255417355300232100ustar00rootroot00000000000000#ifndef __BORDER_EDITING_SELECTION_DIALOG_H__ #define __BORDER_EDITING_SELECTION_DIALOG_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "WuQDialogModal.h" class QCheckBox; namespace caret { class BorderEditingSelectionDialog : public WuQDialogModal { Q_OBJECT public: BorderEditingSelectionDialog(const AString& modeDescription, const std::vector& borderNames, QWidget* parent); virtual ~BorderEditingSelectionDialog(); // ADD_NEW_METHODS_HERE bool isBorderNameSelected(const int32_t borderNameIndex) const; private slots: void allOnPushButtonClicked(); void allOffPushButtonClicked(); private: BorderEditingSelectionDialog(const BorderEditingSelectionDialog&); BorderEditingSelectionDialog& operator=(const BorderEditingSelectionDialog&); void setAllCheckBoxesChecked(const bool status); std::vector m_borderNameCheckBoxes; // ADD_NEW_MEMBERS_HERE }; #ifdef __BORDER_EDITING_SELECTION_DIALOG_DECLARE__ // #endif // __BORDER_EDITING_SELECTION_DIALOG_DECLARE__ } // namespace #endif //__BORDER_EDITING_SELECTION_DIALOG_H__ workbench-1.1.1/src/GuiQt/BorderFileSplitDialog.cxx000066400000000000000000000256111255417355300222300ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __BORDER_FILE_SPLIT_DIALOG_DECLARE__ #include "BorderFileSplitDialog.h" #undef __BORDER_FILE_SPLIT_DIALOG_DECLARE__ #include #include #include #include #include #include #include "BorderFile.h" #include "Brain.h" #include "BrainStructure.h" #include "CaretAssert.h" #include "CaretDataFileSelectionComboBox.h" #include "CaretDataFileSelectionModel.h" #include "CaretFileDialog.h" #include "EventDataFileAdd.h" #include "EventManager.h" #include "EventUserInterfaceUpdate.h" #include "FileInformation.h" #include "GuiManager.h" #include "WuQMessageBox.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::BorderFileSplitDialog * \brief Dialog for splitting multi-structure border file. * \ingroup GuiQt * * Some older border files contained borders for multiple structures * which will not work with wb_command operations. This dialog is * used to split up a multi-structure border file into multiple border * files each containing a single structure. */ /** * Constructor. */ BorderFileSplitDialog::BorderFileSplitDialog(QWidget* parent) : WuQDialogModal("Split Multi-Structure Border File", parent) { m_dialogIsBeingCreatedFlag = true; m_fileNameToolButtonSignalMapper = new QSignalMapper(this); QObject::connect(m_fileNameToolButtonSignalMapper, SIGNAL(mapped(int)), this, SLOT(fileNameToolButtonClicked(int))); Brain* brain = GuiManager::get()->getBrain(); m_fileSelectionModel.grabNew(CaretDataFileSelectionModel::newInstanceForMultiStructureBorderFiles(brain)); QLabel* fileSelectionLabel = new QLabel("Multi Structure Border File: "); m_fileSelectionComboBox = new CaretDataFileSelectionComboBox(this); m_fileSelectionComboBox->updateComboBox(m_fileSelectionModel); QObject::connect(m_fileSelectionComboBox, SIGNAL(fileSelected(CaretDataFile*)), this, SLOT(borderMultiStructureFileSelected(CaretDataFile*))); QHBoxLayout* multiLayout = new QHBoxLayout(); multiLayout->addWidget(fileSelectionLabel); multiLayout->addWidget(m_fileSelectionComboBox->getWidget()); multiLayout->addStretch(); m_gridLayout = new QGridLayout(); WuQtUtilities::setLayoutSpacingAndMargins(m_gridLayout, 3, 3); m_gridLayout->setColumnStretch(0, 0); m_gridLayout->setColumnStretch(1, 100); int rowIndex = 0; m_gridLayout->addWidget(new QLabel("Structures"), rowIndex, 0, Qt::AlignHCenter); m_gridLayout->addWidget(new QLabel("New Single-Structure Border Files"), rowIndex, 1, Qt::AlignHCenter); QWidget* widget = new QWidget(); QVBoxLayout* layout = new QVBoxLayout(widget); layout->addLayout(multiLayout); layout->addWidget(WuQtUtilities::createHorizontalLineWidget()); layout->addLayout(m_gridLayout); layout->addStretch(); setCentralWidget(widget, WuQDialog::SCROLL_AREA_AS_NEEDED); m_dialogIsBeingCreatedFlag = false; CaretDataFile* firstFile = m_fileSelectionModel->getSelectedFile(); if (firstFile != NULL) { borderMultiStructureFileSelected(firstFile); } } /** * Destructor. */ BorderFileSplitDialog::~BorderFileSplitDialog() { } /** * Called when a multi-structure border file is selected. */ void BorderFileSplitDialog::borderMultiStructureFileSelected(CaretDataFile* caretDataFile) { if (m_dialogIsBeingCreatedFlag) { return; } AString pathName; AString fileNameNoExt; AString fileExtension; std::vector structures; if (caretDataFile != NULL) { BorderFile* borderFile = dynamic_cast(caretDataFile); CaretAssert(borderFile); structures = borderFile->getAllBorderStructures(); FileInformation fileInfo(caretDataFile->getFileName()); fileInfo.getFileComponents(pathName, fileNameNoExt, fileExtension); } const int32_t numStructures = static_cast(structures.size()); for (int32_t i = 0; i < numStructures; i++) { QPushButton* pushButton = NULL; QLineEdit* lineEdit = NULL; if (i < static_cast(m_structureRows.size())) { CaretAssertVectorIndex(m_structureRows, i); pushButton = m_structureRows[i].m_fileNamePushButton; lineEdit = m_structureRows[i].m_fileNameLineEdit; if (m_structureRows[i].m_fileNamePushButton->isHidden()) { pushButton->setVisible(true); lineEdit->setVisible(true); } } else { const int MINIMUM_LINE_EDIT_WIDTH = 400; pushButton = new QPushButton(); QObject::connect(pushButton, SIGNAL(clicked()), m_fileNameToolButtonSignalMapper, SLOT(map())); m_fileNameToolButtonSignalMapper->setMapping(pushButton, i); lineEdit = new QLineEdit(); lineEdit->setMinimumWidth(MINIMUM_LINE_EDIT_WIDTH); const int rowIndex = m_gridLayout->rowCount(); m_gridLayout->addWidget(pushButton, rowIndex, 0); m_gridLayout->addWidget(lineEdit, rowIndex, 1); StructureRow structureRow; structureRow.m_fileNamePushButton = pushButton; structureRow.m_fileNameLineEdit = lineEdit; m_structureRows.push_back(structureRow); } CaretAssertVectorIndex(m_structureRows, i); CaretAssert(pushButton); CaretAssert(lineEdit); CaretAssertVectorIndex(structures, i); m_structureRows[i].m_structure = structures[i]; pushButton->setText(StructureEnum::toGuiName(structures[i]) + "..."); AString singleStructureFileName; if ( ! pathName.isEmpty()) { singleStructureFileName += (pathName + "/"); } singleStructureFileName += fileNameNoExt; singleStructureFileName += ("_" + StructureEnum::toGuiName(structures[i])); singleStructureFileName += ("." + fileExtension); lineEdit->setText(singleStructureFileName); lineEdit->end(false); } const int32_t numRows = static_cast(m_structureRows.size()); for (int32_t i = numStructures; i < numRows; i++) { m_structureRows[i].m_fileNamePushButton->hide(); m_structureRows[i].m_fileNameLineEdit->hide(); } } /** * Called when a file name tool button is clicked. Allows user to set * name of new border file with file selection dialog. * * @param buttonIndex * Index of button that was clicked. */ void BorderFileSplitDialog::fileNameToolButtonClicked(int buttonIndex) { CaretAssertVectorIndex(m_structureRows, buttonIndex); QLineEdit* lineEdit = m_structureRows[buttonIndex].m_fileNameLineEdit; AString newBorderFileName = CaretFileDialog::getSaveFileNameDialog(DataFileTypeEnum::BORDER, this, "Choose Border File Name", lineEdit->text().trimmed()); if ( ! newBorderFileName.isEmpty()) { lineEdit->setText(newBorderFileName); lineEdit->end(false); } } /** * Called when the OK button is clicked. */ void BorderFileSplitDialog::okButtonClicked() { const BorderFile* borderFile = m_fileSelectionComboBox->getSelectionModel()->getSelectedFileOfType(); if (borderFile == NULL) { WuQMessageBox::errorOk(this, "No border file is selected."); return; } std::map singleStructureFileNames; const int32_t numSingleStructureFiles = static_cast(m_structureRows.size()); for (int32_t i = 0; i < numSingleStructureFiles; i++) { if (m_structureRows[i].m_fileNameLineEdit->isVisible()) { singleStructureFileNames.insert(std::make_pair(m_structureRows[i].m_structure, m_structureRows[i].m_fileNameLineEdit->text().trimmed())); } } if (singleStructureFileNames.empty()) { WuQMessageBox::errorOk(this, "Border file contains no structures."); return; } std::map structureNumberOfNodes; Brain* brain = GuiManager::get()->getBrain(); const int32_t numStructures = brain->getNumberOfBrainStructures(); for (int32_t iStruct = 0; iStruct < numStructures; iStruct++) { const BrainStructure* bs = brain->getBrainStructure(iStruct); structureNumberOfNodes.insert(std::make_pair(bs->getStructure(), bs->getNumberOfNodes())); } std::vector singleStructureBorderFiles; AString errorMessage; if (borderFile->splitIntoSingleStructureFiles(singleStructureFileNames, structureNumberOfNodes, singleStructureBorderFiles, errorMessage)) { for (std::vector::iterator iter = singleStructureBorderFiles.begin(); iter != singleStructureBorderFiles.end(); iter++) { EventManager::get()->sendEvent(EventDataFileAdd(*iter).getPointer()); } EventManager::get()->sendEvent(EventUserInterfaceUpdate().getPointer()); WuQMessageBox::informationOk(this, ("New border file(s) were created but not saved. " "Use File Menu->Save/Manage Files to save these new files.")); } else { WuQMessageBox::errorOk(this, errorMessage); return; } WuQDialog::okButtonClicked(); } workbench-1.1.1/src/GuiQt/BorderFileSplitDialog.h000066400000000000000000000052301255417355300216500ustar00rootroot00000000000000#ifndef __BORDER_FILE_SPLIT_DIALOG_H__ #define __BORDER_FILE_SPLIT_DIALOG_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretPointer.h" #include "StructureEnum.h" #include "WuQDialogModal.h" class QGridLayout; class QLineEdit; class QPushButton; class QSignalMapper; namespace caret { class CaretDataFile; class CaretDataFileSelectionComboBox; class CaretDataFileSelectionModel; class BorderFileSplitDialog : public WuQDialogModal { Q_OBJECT public: BorderFileSplitDialog(QWidget* parent = 0); virtual ~BorderFileSplitDialog(); // ADD_NEW_METHODS_HERE protected: virtual void okButtonClicked(); private slots: void fileNameToolButtonClicked(int); void borderMultiStructureFileSelected(CaretDataFile* caretDataFile); private: BorderFileSplitDialog(const BorderFileSplitDialog&); BorderFileSplitDialog& operator=(const BorderFileSplitDialog&); QGridLayout* m_gridLayout; /* Use CaretPointer so that the file selection model gets destroyed */ CaretPointer m_fileSelectionModel; CaretDataFileSelectionComboBox* m_fileSelectionComboBox; struct StructureRow { QPushButton* m_fileNamePushButton; QLineEdit* m_fileNameLineEdit; StructureEnum::Enum m_structure; }; std::vector m_structureRows; QSignalMapper* m_fileNameToolButtonSignalMapper; bool m_dialogIsBeingCreatedFlag; // ADD_NEW_MEMBERS_HERE }; #ifdef __BORDER_FILE_SPLIT_DIALOG_DECLARE__ // #endif // __BORDER_FILE_SPLIT_DIALOG_DECLARE__ } // namespace #endif //__BORDER_FILE_SPLIT_DIALOG_H__ workbench-1.1.1/src/GuiQt/BorderOptimizeDialog.cxx000066400000000000000000001504261255417355300221400ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __BORDER_OPTIMIZE_DIALOG_DECLARE__ #include "BorderOptimizeDialog.h" #undef __BORDER_OPTIMIZE_DIALOG_DECLARE__ #include #include #include #include #include #include #include #include #include #include #include #include "Border.h" #include "BorderFile.h" #include "Brain.h" #include "BrainStructure.h" #include "BrowserTabContent.h" #include "CaretAssert.h" #include "CaretDataFileSelectionComboBox.h" #include "CaretDataFileSelectionModel.h" #include "CaretFileDialog.h" #include "CaretMappableDataFile.h" #include "CaretMappableDataFileAndMapSelectionModel.h" #include "CaretMappableDataFileAndMapSelectorObject.h" #include "CursorDisplayScoped.h" #include "EventBrowserTabGet.h" #include "EventDataFileAdd.h" #include "EventManager.h" #include "EventSurfaceColoringInvalidate.h" #include "EventUpdateInformationWindows.h" #include "EventUserInterfaceUpdate.h" #include "GuiManager.h" #include "MetricFile.h" #include "OverlaySet.h" #include "ProgressReportingDialog.h" #include "Surface.h" #include "SurfaceSelectionModel.h" #include "SurfaceSelectionViewController.h" #include "WuQMessageBox.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::BorderOptimizeDialog * \brief Border Optimize Dialog. * \ingroup GuiQt */ static const int STRETCH_NONE = 0; static const int STRETCH_MAX = 100; static const bool DATA_FILES_IN_SCROLL_BARS = false; /** * Constructor. * * @param parent * Parent of the dialog. */ BorderOptimizeDialog::BorderOptimizeDialog(QWidget* parent) : WuQDialogModal("Border Optimize", parent), m_surface(NULL), m_borderEnclosingROI(NULL), m_borderPairFileSelectionModel(NULL), m_surfaceSelectionStructure(StructureEnum::INVALID), m_surfaceSelectionModel(NULL), m_vertexAreasMetricFileSelectionModel(NULL), m_browserTabIndex(-1), m_upsamplingSurfaceSelectionModel(NULL), m_upsamplingSurfaceStructure(StructureEnum::INVALID) { m_optimizeDataFileTypes.push_back(DataFileTypeEnum::CONNECTIVITY_DENSE); m_optimizeDataFileTypes.push_back(DataFileTypeEnum::CONNECTIVITY_DENSE_SCALAR); m_optimizeDataFileTypes.push_back(DataFileTypeEnum::CONNECTIVITY_DENSE_TIME_SERIES); m_optimizeDataFileTypes.push_back(DataFileTypeEnum::METRIC); QWidget* dialogWidget = new QWidget(); QVBoxLayout* dialogLayout = new QVBoxLayout(dialogWidget); dialogLayout->addWidget(createBorderSelectionWidget(), STRETCH_NONE); if (DATA_FILES_IN_SCROLL_BARS) { dialogLayout->addWidget(createDataFilesWidget(), STRETCH_MAX); } else { dialogLayout->addWidget(createDataFilesWidget(), STRETCH_MAX); } dialogLayout->addWidget(createSurfaceSelectionWidget(), STRETCH_NONE); dialogLayout->addWidget(createVertexAreasMetricWidget(), STRETCH_NONE); dialogLayout->addWidget(createSphericalUpsamplingWidget(), STRETCH_NONE); dialogLayout->addWidget(createSavingWidget(), STRETCH_NONE); QWidget* optionsWidget = createOptionsWidget(); if (optionsWidget != NULL) { dialogLayout->addWidget(optionsWidget, STRETCH_NONE); } if (DATA_FILES_IN_SCROLL_BARS) { dialogLayout->addStretch(); setCentralWidget(dialogWidget, SCROLL_AREA_NEVER); } else { setCentralWidget(dialogWidget, SCROLL_AREA_AS_NEEDED_VERT_NO_HORIZ); } if (DATA_FILES_IN_SCROLL_BARS) { if (m_defaultDataFilesWidgetSize.width() > 300) { QSize defaultSize = sizeHint(); defaultSize.setWidth(m_defaultDataFilesWidgetSize.width() + 100); setSizeOfDialogWhenDisplayed(defaultSize); } } m_dialogWidget = dialogWidget; // if ( ! DATA_FILES_IN_SCROLL_BARS) { // setSizePolicy(sizePolicy().horizontalPolicy(), // QSizePolicy::Fixed); // } } /** * Destructor. */ BorderOptimizeDialog::~BorderOptimizeDialog() { if (m_surfaceSelectionModel != NULL) { delete m_surfaceSelectionModel; } if (m_vertexAreasMetricFileSelectionModel != NULL) { delete m_vertexAreasMetricFileSelectionModel; } if (m_borderPairFileSelectionModel != NULL) { delete m_borderPairFileSelectionModel; } if (m_upsamplingSurfaceSelectionModel != NULL) { delete m_upsamplingSurfaceSelectionModel; } } /** * Update the content of the dialog. * * @param browserTabIndex * Index of browser tab in which borders are drawn. * @param surface * Surface on which borders are drawn. * @param bordersInsideROI * Map where value is border and key is number of border * points inside the ROI * @param borderEnclosingROI * Border that encloses the region of interest * @param nodesInsideROI * Nodes inside the region of interest. */ void BorderOptimizeDialog::updateDialog(const int32_t browserTabIndex, Surface* surface, std::vector >& bordersInsideROI, Border* borderEnclosingROI, std::vector& nodesInsideROI) { CaretAssert(surface); CaretAssert(borderEnclosingROI); m_browserTabIndex = browserTabIndex; m_surface = surface; m_borderEnclosingROI = borderEnclosingROI; m_nodesInsideROI = nodesInsideROI; m_borderPointsInsideROICount.clear(); m_bordersInsideROI.clear(); for (std::vector >::reverse_iterator bi = bordersInsideROI.rbegin(); bi != bordersInsideROI.rend(); bi++) { m_borderPointsInsideROICount.push_back(bi->first); m_bordersInsideROI.push_back(bi->second); } /* * Update surface selection. * Will need to recreate model if the structure has changed. */ const StructureEnum::Enum structure = m_surface->getStructure(); if (m_surfaceSelectionModel != NULL) { if (structure != m_surfaceSelectionStructure) { delete m_surfaceSelectionModel; m_surfaceSelectionModel = NULL; m_surfaceSelectionStructure = StructureEnum::INVALID; } } if (m_surfaceSelectionModel == NULL) { m_surfaceSelectionStructure = structure; std::vector allSurfaceTypes; SurfaceTypeEnum::getAllEnums(allSurfaceTypes); m_surfaceSelectionModel = new SurfaceSelectionModel(m_surfaceSelectionStructure, allSurfaceTypes); BrainStructure* bs = GuiManager::get()->getBrain()->getBrainStructure(m_surfaceSelectionStructure, false); if (bs != NULL) { Surface* surface = bs->getPrimaryAnatomicalSurface(); if (surface != NULL) { m_surfaceSelectionModel->updateModel(); m_surfaceSelectionModel->setSurface(const_cast(surface)); } } } else { m_surfaceSelectionModel->updateModel(); } if (m_surfaceSelectionModel != NULL) { m_surfaceSelectionControl->updateControl(m_surfaceSelectionModel); m_surfaceSelectionControl->getWidget()->setEnabled(true); } else { m_surfaceSelectionControl->getWidget()->setEnabled(false); } Surface* defaultSphericalSurface = NULL; if (m_upsamplingSurfaceSelectionModel != NULL) { defaultSphericalSurface = m_upsamplingSurfaceSelectionModel->getSurface(); if (structure != m_upsamplingSurfaceStructure) { delete m_upsamplingSurfaceSelectionModel; m_upsamplingSurfaceSelectionModel = NULL; m_upsamplingSurfaceStructure = StructureEnum::INVALID; } } if (m_upsamplingSurfaceSelectionModel == NULL) { m_upsamplingSurfaceStructure = structure; std::vector sphericalSurfaceTypes; sphericalSurfaceTypes.push_back(SurfaceTypeEnum::SPHERICAL); m_upsamplingSurfaceSelectionModel = new SurfaceSelectionModel(m_upsamplingSurfaceStructure, sphericalSurfaceTypes); m_upsamplingResolutionSpinBox->setValue(0); } if (defaultSphericalSurface != NULL) { m_upsamplingSurfaceSelectionModel->setSurface(defaultSphericalSurface); } m_upsamplingSurfaceSelectionModel->updateModel(); m_upsamplingSurfaceSelectionControl->updateControl(m_upsamplingSurfaceSelectionModel); if (m_upsamplingResolutionSpinBox->value() <= 0) { Surface* surface = m_upsamplingSurfaceSelectionModel->getSurface(); if (surface != NULL) { m_upsamplingResolutionSpinBox->setValue(surface->getNumberOfNodes() * 4); } } /* * Update metric selection model * Will need to recreate if the structure has changed. */ if (m_vertexAreasMetricFileSelectionModel != NULL) { if (structure != m_vertexAreasMetricFileSelectionModel->getStructure()) { delete m_vertexAreasMetricFileSelectionModel; m_vertexAreasMetricFileSelectionModel = NULL; } } if (m_vertexAreasMetricFileSelectionModel == NULL) { std::vector metricDataTypes; metricDataTypes.push_back(DataFileTypeEnum::METRIC); m_vertexAreasMetricFileSelectionModel = CaretDataFileSelectionModel::newInstanceForCaretDataFileTypesInStructure(GuiManager::get()->getBrain(), m_surfaceSelectionStructure, metricDataTypes); m_vertexAreasMetricFileComboBox->updateComboBox(m_vertexAreasMetricFileSelectionModel); } if (m_vertexAreasMetricFileSelectionModel != NULL) { m_vertexAreasMetricFileComboBox->updateComboBox(m_vertexAreasMetricFileSelectionModel); } /* * Update border file selection but limit to border files that are the same structure * as the surface */ m_borderPairFileSelectionModel->setStructure(structure); m_borderPairFileSelectionComboBox->updateComboBox(m_borderPairFileSelectionModel); QString initialBaseName = ""; /* * Update borders inside ROI selections */ CaretAssert(m_bordersInsideROI.size() == m_borderPointsInsideROICount.size()); const int32_t NUM_ROWS = 2; const int32_t numberOfBorders = static_cast(m_bordersInsideROI.size()); for (int32_t i = 0; i < numberOfBorders; i++) { if (i >= static_cast(m_borderCheckBoxes.size())) { QCheckBox* cb = new QCheckBox(""); m_borderCheckBoxes.push_back(cb); /* * Use two rows and then wrap additional columns on right */ const int32_t row = (i % NUM_ROWS); const int32_t col = (i / NUM_ROWS); m_bordersInsideROIGridLayout->addWidget(cb, row, col); } CaretAssertVectorIndex(m_borderCheckBoxes, i); CaretAssertVectorIndex(m_bordersInsideROI, i); CaretAssertVectorIndex(m_borderPointsInsideROICount, i); if (m_borderCheckBoxes[i]->text() != m_bordersInsideROI[i]->getName()) { m_borderCheckBoxes[i]->blockSignals(true); m_borderCheckBoxes[i]->setChecked(true); m_borderCheckBoxes[i]->blockSignals(false); } // m_borderCheckBoxes[i]->setText(m_bordersInsideROI[i]->getName()); m_borderCheckBoxes[i]->setText(m_bordersInsideROI[i]->getName() + " (" + QString::number(m_borderPointsInsideROICount[i]) + ")"); m_borderCheckBoxes[i]->setVisible(true); if (i < 2) { if (initialBaseName != "") initialBaseName += "_"; initialBaseName += m_bordersInsideROI[i]->getName(); } } m_savingBaseNameLineEdit->setText(initialBaseName); /* * Remove border selections not needed. */ for (int32_t i = numberOfBorders; i < static_cast(m_borderCheckBoxes.size()); i++) { m_borderCheckBoxes[i]->setVisible(false); } /* * Update the data file selectors. */ for (std::vector::iterator fileIter = m_optimizeDataFileSelectors.begin(); fileIter != m_optimizeDataFileSelectors.end(); fileIter++) { BorderOptimizeDataFileSelector* selector = *fileIter; selector->updateFileData(); } m_dialogWidget->adjustSize(); } /** * Called when OK button is clicked. */ void BorderOptimizeDialog::okButtonClicked() { preserveDialogSizeAndPositionWhenReOpened(); AString errorMessage; m_selectedBorders.clear(); Surface* gradientComputationSurface = m_surfaceSelectionModel->getSurface(); if (gradientComputationSurface == NULL) { errorMessage.appendWithNewLine("Gradient Computation Surface is not valid."); } if (m_borderEnclosingROI != NULL) { if (m_borderEnclosingROI->getNumberOfPoints() < 3) { errorMessage.appendWithNewLine("Border named " + m_borderEnclosingROI->getName() + " enclosing region is invalid must contain at least three points"); } } else { errorMessage.appendWithNewLine("Border enclosing region is invalid."); } std::vector dataFileSelections; for (std::vector::iterator fileIter = m_optimizeDataFileSelectors.begin(); fileIter != m_optimizeDataFileSelectors.end(); fileIter++) { BorderOptimizeDataFileSelector* selector = *fileIter; CaretPointer fileInfo = selector->getSelections(); if (fileInfo != NULL) { dataFileSelections.push_back(*fileInfo); } } if (dataFileSelections.empty()) { errorMessage.appendWithNewLine("No optimization data files are selected."); } /* * "Pair" border file */ const BorderFile* pairBorderFile = m_borderPairFileSelectionModel->getSelectedFileOfType(); std::vector pairedBorders; /* * Get borders selected by the user. */ AString metricFileMapName; CaretAssert(m_bordersInsideROI.size() <= m_borderCheckBoxes.size()); const int32_t numBorderFileSelections = static_cast(m_bordersInsideROI.size()); for (int32_t iBorder = 0; iBorder < numBorderFileSelections; iBorder++) { if (m_borderCheckBoxes[iBorder]->isVisible()) { if (m_borderCheckBoxes[iBorder]->isChecked()) { CaretAssertVectorIndex(m_bordersInsideROI, iBorder); Border* border = m_bordersInsideROI[iBorder]; m_selectedBorders.push_back(border); metricFileMapName.append(border->getName() + " "); if (pairBorderFile != NULL) { if (pairBorderFile->containsBorder(border)) { pairedBorders.push_back(border); } } } } } const int32_t numPairedBorders = static_cast(pairedBorders.size()); if (m_selectedBorders.empty()) { errorMessage.appendWithNewLine("No optimization border files are selected."); } else if (m_borderPairCheckBox->isChecked()) { if (pairBorderFile == NULL) { errorMessage.appendWithNewLine("Border pair file selection is invalid."); } else if (numPairedBorders != 2) { errorMessage.appendWithNewLine("Two of the selected borders must be in the border pair file."); if (pairedBorders.empty()) { errorMessage.appendWithNewLine("None of the selected borders are in the border pair file."); } else { errorMessage.appendWithNewLine("There are " + AString::number(numPairedBorders) + " selected borders in the border pair file."); for (int32_t i = 0; i < numPairedBorders; i++) { CaretAssertVectorIndex(pairedBorders, i); errorMessage.appendWithNewLine(" " + pairedBorders[i]->getName()); } } } } Surface* upsamplingSphericalSurface = NULL; int32_t upsamplingResolution = 0; if (m_upsamplingGroupBox->isChecked()) { upsamplingSphericalSurface = m_upsamplingSurfaceSelectionModel->getSurface(); if (upsamplingSphericalSurface == NULL) { errorMessage.appendWithNewLine("Upsampling is selected but no spherical surface is available."); } upsamplingResolution = m_upsamplingResolutionSpinBox->value(); if (upsamplingResolution <= 0) { errorMessage.appendWithNewLine("Upsampling resolution is invalid."); } } const QString savingBaseName = m_savingBaseNameLineEdit->text().trimmed(); const QString savingDirectoryName = m_savingDirectoryLineEdit->text().trimmed(); if (m_savingGroupBox->isChecked()) { if (savingDirectoryName.isEmpty()) { errorMessage.appendWithNewLine("Save Results is selected and Saving Directory is empty."); } if (savingBaseName.isEmpty()) { errorMessage.appendWithNewLine("Save Results is selected and Saving Base Filename is empty."); } } if ( ! errorMessage.isEmpty()) { WuQMessageBox::errorOk(this, errorMessage); return; } if (m_borderPairCheckBox->isChecked()) { CaretAssertMessage((pairedBorders.size() == 2), "Must be exactly two borders in the border pair vector"); } else { pairedBorders.clear(); } ProgressReportingDialog progressDialog("Border Optimization", "", this); progressDialog.setMinimum(0); progressDialog.setValue(0); MetricFile* vertexAreasMetricFile = NULL; CaretDataFile* vertexAreaFile = m_vertexAreasMetricFileSelectionModel->getSelectedFile(); if (vertexAreaFile != NULL) { vertexAreasMetricFile = dynamic_cast(vertexAreaFile); } const float gradientFollowingStrength = m_gradientFollowingStrengthSpinBox->value(); CaretPointer resultsMetricFile; resultsMetricFile.grabNew(new MetricFile()); BorderOptimizeExecutor::InputData algInput(m_selectedBorders, pairedBorders, m_borderEnclosingROI, m_nodesInsideROI, gradientComputationSurface, dataFileSelections, vertexAreasMetricFile, gradientFollowingStrength, upsamplingSphericalSurface, upsamplingResolution, resultsMetricFile, m_savingGroupBox->isChecked(), savingDirectoryName, savingBaseName); /* * Run border optimization. */ AString statisticsInformation; const bool algSuccessFlag = BorderOptimizeExecutor::run(algInput, statisticsInformation, errorMessage); /* * The progress dialog normally closes on its own but * may fail to close if the algorithm fails due to * the 'progress value' not reaching 'progress maximum'. * This prevent the error dialog from being shown behind * the progress dialog. */ progressDialog.close(); if (algSuccessFlag) { AString infoMsg; if (m_outputGradientMapCheckBox->isChecked() && ! resultsMetricFile->isEmpty()) { resultsMetricFile->setStructure(gradientComputationSurface->getStructure()); resultsMetricFile->setMapName(0, metricFileMapName); PaletteColorMapping* pcm = resultsMetricFile->getMapPaletteColorMapping(0); pcm->setAutoScalePercentageNegativeMaximum(96.0); pcm->setAutoScalePercentageNegativeMinimum(4.0); pcm->setAutoScalePercentagePositiveMinimum(4.0); pcm->setAutoScalePercentagePositiveMaximum(96.0); pcm->setSelectedPaletteName("videen_style"); pcm->setDisplayNegativeDataFlag(false); pcm->setDisplayZeroDataFlag(false); pcm->setDisplayPositiveDataFlag(true); pcm->setInterpolatePaletteFlag(true); EventDataFileAdd addDataFile(resultsMetricFile.releasePointer()); EventManager::get()->sendEvent(addDataFile.getPointer()); infoMsg.appendWithNewLine("Border Optimization Gradient results in file " + resultsMetricFile->getFileNameNoPath() + " Map Name: " + metricFileMapName); if (m_browserTabIndex >= 0) { /* * Updating user interface will update content of overlay * so that metric file gets added. */ EventManager::get()->sendEvent(EventUserInterfaceUpdate().getPointer()); EventBrowserTabGet browserTabEvent(m_browserTabIndex); EventManager::get()->sendEvent(browserTabEvent.getPointer()); BrowserTabContent* tabContent = browserTabEvent.getBrowserTab(); if (tabContent != NULL) { /* * Create a new overlay at the top */ OverlaySet* overlaySet = tabContent->getOverlaySet(); CaretAssert(overlaySet); overlaySet->insertOverlayAbove(0); /* * Load the gradient metric file into the top-most * (should be the new) overlay. */ Overlay* overlay = overlaySet->getPrimaryOverlay(); CaretAssert(overlay); overlay->setSelectionData(resultsMetricFile, 0); overlay->setEnabled(true); overlay->setMapYokingGroup(MapYokingGroupEnum::MAP_YOKING_GROUP_OFF); overlay->setOpacity(1.0); EventManager::get()->sendEvent(EventSurfaceColoringInvalidate().getPointer()); } } } /* * Display the statistics information. */ if ( ! statisticsInformation.isEmpty()) { infoMsg.appendWithNewLine(""); infoMsg.appendWithNewLine(statisticsInformation); } infoMsg = infoMsg.replaceHtmlSpecialCharactersWithEscapeCharacters(); EventManager::get()->sendEvent(EventUpdateInformationWindows(infoMsg).getPointer()); /* * Success, allow dialog to close. */ WuQDialogModal::okButtonClicked(); return; } WuQMessageBox::errorOk(this, errorMessage); } /** * Called when cancel button clicked. */ void BorderOptimizeDialog::cancelButtonClicked() { preserveDialogSizeAndPositionWhenReOpened(); /* * Allow dialog to close. */ WuQDialogModal::cancelButtonClicked(); } /** * Make dialog appear in same place and size when repopened. */ void BorderOptimizeDialog::preserveDialogSizeAndPositionWhenReOpened() { setSaveWindowPositionForNextTime("BorderOptimizeDialog"); setSizeOfDialogWhenDisplayed(size()); } /** * Get borders that were selected by the user. * * @param selectedBordersOut * Output containing borders selected by the user. */ void BorderOptimizeDialog::getModifiedBorders(std::vector& modifiedBordersOut) const { modifiedBordersOut = m_selectedBorders; } /** * @return The border selection widget. */ QWidget* BorderOptimizeDialog::createBorderSelectionWidget() { m_borderPairCheckBox = new QCheckBox("Border Pair File"); m_borderPairCheckBox->setChecked(true); m_borderPairFileSelectionModel = CaretDataFileSelectionModel::newInstanceForCaretDataFileType(GuiManager::get()->getBrain(), DataFileTypeEnum::BORDER); m_borderPairFileSelectionComboBox = new CaretDataFileSelectionComboBox(this); QHBoxLayout* borderPairLayout = new QHBoxLayout(); borderPairLayout->addWidget(m_borderPairCheckBox, 0); borderPairLayout->addWidget(m_borderPairFileSelectionComboBox->getWidget(), 100); QObject::connect(m_borderPairCheckBox, SIGNAL(clicked(bool)), m_borderPairFileSelectionComboBox->getWidget(), SLOT(setEnabled(bool))); m_borderPairFileSelectionComboBox->getWidget()->setEnabled(m_borderPairCheckBox->isChecked()); m_bordersInsideROIGridLayout = new QGridLayout(); m_bordersInsideROIGridLayout->setMargin(2); m_bordersInsideROIGridLayout->setHorizontalSpacing(25); m_bordersInsideROIGridLayout->setColumnStretch(100, 1000); // force widgets to left QAction* diableAllAction = new QAction("Disable All", this); QObject::connect(diableAllAction, SIGNAL(triggered(bool)), this, SLOT(bordersDisableAllSelected())); QToolButton* disableAllToolButton = new QToolButton(); disableAllToolButton->setDefaultAction(diableAllAction); QAction* enableAllAction = new QAction("Enable All", this); QObject::connect(enableAllAction, SIGNAL(triggered(bool)), this, SLOT(bordersEnableAllSelected())); QToolButton* enableAllToolButton = new QToolButton(); enableAllToolButton->setDefaultAction(enableAllAction); QHBoxLayout* buttonsLayout = new QHBoxLayout(); buttonsLayout->setMargin(2); buttonsLayout->addWidget(enableAllToolButton); buttonsLayout->addWidget(disableAllToolButton); buttonsLayout->addStretch(); QGroupBox* widget = new QGroupBox("Borders"); widget->setSizePolicy(widget->sizePolicy().horizontalPolicy(), QSizePolicy::Fixed); QVBoxLayout* layout = new QVBoxLayout(widget); layout->setMargin(2); layout->addLayout(borderPairLayout); layout->addLayout(m_bordersInsideROIGridLayout, STRETCH_NONE); layout->addLayout(buttonsLayout, STRETCH_NONE); return widget; } /** * Disable all borders. */ void BorderOptimizeDialog::bordersDisableAllSelected() { setAllBorderEnabledSelections(false); } /** * Enable all borders. */ void BorderOptimizeDialog::bordersEnableAllSelected() { setAllBorderEnabledSelections(true); } /** * Set the enable status of all borders to the given value. * * @param status * New status. */ void BorderOptimizeDialog::setAllBorderEnabledSelections(const bool status) { for (std::vector::iterator iter = m_borderCheckBoxes.begin(); iter != m_borderCheckBoxes.end(); iter++) { QCheckBox* cb = *iter; cb->setChecked(status); } } /** * @return The data files widget. */ QWidget* BorderOptimizeDialog::createDataFilesWidget() { QWidget* gridWidget = new QWidget(); if (DATA_FILES_IN_SCROLL_BARS) { gridWidget->setSizePolicy(gridWidget->sizePolicy().horizontalPolicy(), QSizePolicy::Fixed); } else { // gridWidget->setSizePolicy(gridWidget->sizePolicy().horizontalPolicy(), // QSizePolicy::Fixed); } m_borderOptimizeDataFileGridLayout = new QGridLayout(gridWidget); m_borderOptimizeDataFileGridLayout->setMargin(2); std::vector optimizeMapFiles; GuiManager::get()->getBrain()->getAllMappableDataFileWithDataFileTypes(m_optimizeDataFileTypes, optimizeMapFiles); const int32_t numberOfMapFiles = static_cast(optimizeMapFiles.size()); // const int32_t minimumDataFilesToShow = 10; // const int32_t numberOfFilesToShow = std::max(minimumDataFilesToShow, // numberOfMapFiles); const int32_t numberOfFilesToShow = 3; for (int32_t i = 0; i < numberOfFilesToShow; i++) { CaretMappableDataFile* mapFile = NULL; if (i < numberOfMapFiles) { CaretAssertVectorIndex(optimizeMapFiles, i); mapFile = optimizeMapFiles[i]; } addDataFileRow(mapFile); } QWidget* widget = gridWidget; if (DATA_FILES_IN_SCROLL_BARS) { QScrollArea* scrollArea = new QScrollArea(); scrollArea->setWidget(gridWidget); scrollArea->setWidgetResizable(true); scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); scrollArea->setSizePolicy(gridWidget->sizePolicy().horizontalPolicy(), QSizePolicy::Preferred); widget = scrollArea; } else { widget->setSizePolicy(widget->sizePolicy().horizontalPolicy(), QSizePolicy::Fixed); } QAction* addRowAction = new QAction("Add New Data File Row", this); QObject::connect(addRowAction, SIGNAL(triggered(bool)), this, SLOT(addDataFileRowToolButtonClicked())); QToolButton* addRowToolButton = new QToolButton(); addRowToolButton->setDefaultAction(addRowAction); QAction* diableAllAction = new QAction("Disable All", this); QObject::connect(diableAllAction, SIGNAL(triggered(bool)), this, SLOT(dataFilesDisableAllSelected())); QToolButton* disableAllToolButton = new QToolButton(); disableAllToolButton->setDefaultAction(diableAllAction); QAction* enableAllAction = new QAction("Enable All", this); QObject::connect(enableAllAction, SIGNAL(triggered(bool)), this, SLOT(dataFilesEnableAllSelected())); QToolButton* enableAllToolButton = new QToolButton(); enableAllToolButton->setDefaultAction(enableAllAction); QHBoxLayout* buttonsLayout = new QHBoxLayout(); buttonsLayout->setMargin(2); buttonsLayout->addWidget(addRowToolButton); buttonsLayout->addWidget(enableAllToolButton); buttonsLayout->addWidget(disableAllToolButton); buttonsLayout->addStretch(); QGroupBox* groupBox = new QGroupBox("Data Files"); if ( ! DATA_FILES_IN_SCROLL_BARS) { // groupBox->setSizePolicy(groupBox->sizePolicy().horizontalPolicy(), // QSizePolicy::Minimum); } QVBoxLayout* groupBoxLayout = new QVBoxLayout(groupBox); groupBoxLayout->setMargin(2); if (DATA_FILES_IN_SCROLL_BARS) { groupBox->setMinimumHeight(300); groupBoxLayout->addWidget(widget, STRETCH_NONE); } else { groupBoxLayout->addWidget(widget, STRETCH_NONE); // STRETCH_MAX); } groupBoxLayout->addLayout(buttonsLayout, STRETCH_NONE); m_defaultDataFilesWidgetSize = gridWidget->sizeHint(); if ( ! DATA_FILES_IN_SCROLL_BARS) { groupBox->setSizePolicy(groupBox->sizePolicy().horizontalPolicy(), QSizePolicy::Fixed); //groupBoxLayout->addStretch(); } return groupBox; } /** * Add a data file row * * @param mapFile * If not NULL, the file selector is set to this file. */ void BorderOptimizeDialog::addDataFileRow(CaretMappableDataFile* mapFile) { const int32_t index = static_cast(m_optimizeDataFileSelectors.size()); BorderOptimizeDataFileSelector* selector = new BorderOptimizeDataFileSelector(index, m_optimizeDataFileTypes, mapFile, m_borderOptimizeDataFileGridLayout, this); selector->setSelected(true); m_optimizeDataFileSelectors.push_back(selector); } /** * Called when data file's Add Row button is clicked. */ void BorderOptimizeDialog::addDataFileRowToolButtonClicked() { addDataFileRow(NULL); } /** * Disable all borders. */ void BorderOptimizeDialog::dataFilesDisableAllSelected() { setAllDataFileEnabledSelections(false); } /** * Enable all borders. */ void BorderOptimizeDialog::dataFilesEnableAllSelected() { setAllDataFileEnabledSelections(true); } void BorderOptimizeDialog::saveBrowseButtonClicked() { QString path = CaretFileDialog::getExistingDirectoryDialog(); if (path != "") m_savingDirectoryLineEdit->setText(path); } /** * Set the enable status of all borders to the given value. * * @param status * New status. */ void BorderOptimizeDialog::setAllDataFileEnabledSelections(const bool status) { for (std::vector::iterator iter = m_optimizeDataFileSelectors.begin(); iter != m_optimizeDataFileSelectors.end(); iter++) { BorderOptimizeDataFileSelector* dfs = *iter; dfs->m_selectionCheckBox->setChecked(status); } } /** * @return The metric vertex areas widget. */ QWidget* BorderOptimizeDialog::createVertexAreasMetricWidget() { m_vertexAreasMetricFileComboBox = new CaretDataFileSelectionComboBox(this); QGroupBox* widget = new QGroupBox("Vertex Areas Metric File"); widget->setSizePolicy(widget->sizePolicy().horizontalPolicy(), QSizePolicy::Fixed); QVBoxLayout* layout = new QVBoxLayout(widget); layout->setMargin(2); layout->addWidget(m_vertexAreasMetricFileComboBox->getWidget()); return widget; } /** * @return The options widget. */ QWidget* BorderOptimizeDialog::createOptionsWidget() { m_keepRegionBorderCheckBox = new QCheckBox("Keep Boundary Border"); m_keepRegionBorderCheckBox->setChecked(true); m_outputGradientMapCheckBox = new QCheckBox("Output Gradient as Map"); m_outputGradientMapCheckBox->setChecked(true); QLabel* gradientLabel = new QLabel("Graident Following Strength"); m_gradientFollowingStrengthSpinBox = new QDoubleSpinBox(); m_gradientFollowingStrengthSpinBox->setRange(0.0, 1.0e6); m_gradientFollowingStrengthSpinBox->setDecimals(2); m_gradientFollowingStrengthSpinBox->setSingleStep(1.0); m_gradientFollowingStrengthSpinBox->setValue(5); QHBoxLayout* gradientLayout = new QHBoxLayout(); gradientLayout->addWidget(gradientLabel); gradientLayout->addWidget(m_gradientFollowingStrengthSpinBox); gradientLayout->addStretch(); QGroupBox* widget = new QGroupBox("Options"); widget->setSizePolicy(widget->sizePolicy().horizontalPolicy(), QSizePolicy::Fixed); QVBoxLayout* layout = new QVBoxLayout(widget); layout->setMargin(2); layout->addWidget(m_keepRegionBorderCheckBox); layout->addWidget(m_outputGradientMapCheckBox); layout->addLayout(gradientLayout); return widget; } /** * @return Return keep boundary border selection status. */ bool BorderOptimizeDialog::isKeepBoundaryBorderSelected() const { return m_keepRegionBorderCheckBox->isChecked(); } /** * @return The options widget. */ QWidget* BorderOptimizeDialog::createSurfaceSelectionWidget() { m_surfaceSelectionControl = new SurfaceSelectionViewController(this); QObject::connect(m_surfaceSelectionControl, SIGNAL(surfaceSelected(Surface*)), this, SLOT(gradientComputatonSurfaceSelected(Surface*))); QGroupBox* widget = new QGroupBox("Gradient Computation Surface"); widget->setSizePolicy(widget->sizePolicy().horizontalPolicy(), QSizePolicy::Fixed); QVBoxLayout* layout = new QVBoxLayout(widget); layout->setMargin(2); layout->addWidget(m_surfaceSelectionControl->getWidget()); return widget; } /** * @return The spherical upsampling widget. */ QWidget* BorderOptimizeDialog::createSphericalUpsamplingWidget() { QLabel* surfaceLabel = new QLabel("Current Sphere"); m_upsamplingSurfaceSelectionControl = new SurfaceSelectionViewController(this); QLabel* resolutionLabel = new QLabel("Upsampling Resolution"); m_upsamplingResolutionSpinBox = new QSpinBox(); m_upsamplingResolutionSpinBox->setRange(0, std::numeric_limits::max()); m_upsamplingResolutionSpinBox->setSingleStep(1); m_upsamplingResolutionSpinBox->setToolTip("Number of vertices to use when making the upsampling sphere"); m_upsamplingGroupBox = new QGroupBox(" Upsampling"); m_upsamplingGroupBox->setSizePolicy(m_upsamplingGroupBox->sizePolicy().horizontalPolicy(), QSizePolicy::Fixed); m_upsamplingGroupBox->setCheckable(true); m_upsamplingGroupBox->setChecked(true); QGridLayout* layout = new QGridLayout(m_upsamplingGroupBox); layout->setMargin(2); layout->setColumnStretch(0, 0); layout->setColumnStretch(1, 100); int row = 0; layout->addWidget(surfaceLabel, row, 0); layout->addWidget(m_upsamplingSurfaceSelectionControl->getWidget(), row, 1); row++; layout->addWidget(resolutionLabel, row, 0); layout->addWidget(m_upsamplingResolutionSpinBox, row, 1, Qt::AlignLeft); return m_upsamplingGroupBox; } QWidget* BorderOptimizeDialog::createSavingWidget() { m_savingGroupBox = new QGroupBox(" Save results"); m_savingGroupBox->setSizePolicy(m_savingGroupBox->sizePolicy().horizontalPolicy(), QSizePolicy::Fixed); m_savingGroupBox->setCheckable(true); m_savingGroupBox->setChecked(true); QGridLayout* layout = new QGridLayout(m_savingGroupBox); layout->setMargin(2); layout->setColumnStretch(0, 0);//label layout->setColumnStretch(1, 100);//line edit layout->setColumnStretch(2, 0);//button or extended line edit layout->addWidget(new QLabel("Base Filename"), 0, 0); m_savingBaseNameLineEdit = new QLineEdit(); layout->addWidget(m_savingBaseNameLineEdit, 0, 1, 1, -1);//extend to end of row layout->addWidget(new QLabel("Path"), 1, 0); m_savingDirectoryLineEdit = new QLineEdit(); layout->addWidget(m_savingDirectoryLineEdit, 1, 1); QPushButton* browseButton = new QPushButton("Browse..."); QObject::connect(browseButton, SIGNAL(clicked()), this, SLOT(saveBrowseButtonClicked())); layout->addWidget(browseButton, 1, 2); return m_savingGroupBox; } /** * Called when gradient computational surface is selected * * @param surface * Surface that was selected. */ void BorderOptimizeDialog::gradientComputatonSurfaceSelected(Surface* surface) { if (m_surfaceSelectionModel != NULL) { m_surfaceSelectionModel->setSurface(surface); } } /** * Constructor. * * @param optimizeDataFileTypes * File types for selection * @param defaultFile * The default file (may be NULL) * @param gridLayout * Layout for the widgets. * @param parent * Parent of the selector. */ BorderOptimizeDataFileSelector::BorderOptimizeDataFileSelector(const int32_t itemIndex, const std::vector& optimizeDataFileTypes, CaretMappableDataFile* defaultFile, QGridLayout* gridLayout, QObject* parent) : QObject(parent) { m_mapFileAndIndexSelectorObject = new CaretMappableDataFileAndMapSelectorObject(optimizeDataFileTypes, CaretMappableDataFileAndMapSelectorObject::OPTION_SHOW_MAP_INDEX_SPIN_BOX, this); QObject::connect(m_mapFileAndIndexSelectorObject, SIGNAL(selectionWasPerformed()), this, SLOT(mapFileSelectionChanged())); QWidget* mapFileComboBox; QWidget* mapIndexSpinBox; QWidget* mapNameComboBox; m_mapFileAndIndexSelectorObject->getWidgetsForAddingToLayout(mapFileComboBox, mapIndexSpinBox, mapNameComboBox); CaretAssert(mapFileComboBox); CaretAssert(mapIndexSpinBox); CaretAssert(mapNameComboBox); mapIndexSpinBox->setFixedWidth(80); m_allMapsCheckBox = new QCheckBox(""); QObject::connect(m_allMapsCheckBox, SIGNAL(toggled(bool)), this, SLOT(allMapsCheckBoxToggled(bool))); m_invertGradientCheckBox = new QCheckBox(""); m_skipGradientCheckBox = new QCheckBox(""); m_selectionCheckBox = new QCheckBox(""); m_selectionCheckBox->setChecked(true); QObject::connect(m_selectionCheckBox, SIGNAL(toggled(bool)), this, SLOT(selectionCheckBoxToggled(bool))); m_exclusionDistanceSpinBox = new QDoubleSpinBox(); m_exclusionDistanceSpinBox->setRange(0, 1.0e6); m_exclusionDistanceSpinBox->setDecimals(2); m_exclusionDistanceSpinBox->setSingleStep(1.0); m_exclusionDistanceSpinBox->setValue(2); m_smoothingSpinBox = new QDoubleSpinBox(); m_smoothingSpinBox->setRange(0.0, 1.0e6); m_smoothingSpinBox->setDecimals(2); m_smoothingSpinBox->setSingleStep(1.0); m_smoothingSpinBox->setValue(1.0); m_weightSpinBox = new QDoubleSpinBox(); m_weightSpinBox->setRange(0.0, 1.0); m_weightSpinBox->setDecimals(2); m_weightSpinBox->setSingleStep(0.01); m_weightSpinBox->setValue(0.7); int32_t columnCount = 0; const int32_t COLUMN_SELECT = columnCount++; const int32_t COLUMN_EXCLUDE = columnCount++; const int32_t COLUMN_SMOOTH = columnCount++; const int32_t COLUMN_WEIGHT = columnCount++; const int32_t COLUMN_INVERT = columnCount++; const int32_t COLUMN_SKIP = columnCount++; const int32_t COLUMN_ALL_MAP = columnCount++; const int32_t COLUMN_MAP_INDEX = columnCount++; const int32_t COLUMN_MAP_NAME = columnCount++; if (itemIndex == 0) { const int32_t row = gridLayout->rowCount(); gridLayout->addWidget(new QLabel("Enable"), row, COLUMN_SELECT, 2, 1, Qt::AlignCenter); gridLayout->addWidget(new QLabel("Exclusion"), row, COLUMN_EXCLUDE, Qt::AlignCenter); gridLayout->addWidget(new QLabel("Radius (mm)"), row+1, COLUMN_EXCLUDE, Qt::AlignCenter); gridLayout->addWidget(new QLabel("Smoothing"), row, COLUMN_SMOOTH, Qt::AlignCenter); gridLayout->addWidget(new QLabel("Sigma (mm)"), row+1, COLUMN_SMOOTH, Qt::AlignCenter); gridLayout->addWidget(new QLabel("Weight"), row, COLUMN_WEIGHT, 2, 1, Qt::AlignCenter); gridLayout->addWidget(new QLabel("Invert"), row, COLUMN_INVERT, Qt::AlignHCenter); gridLayout->addWidget(new QLabel("Gradient"), row + 1, COLUMN_INVERT, Qt::AlignHCenter); gridLayout->addWidget(new QLabel("Skip"), row, COLUMN_SKIP, Qt::AlignHCenter); gridLayout->addWidget(new QLabel("Gradient"), row + 1, COLUMN_SKIP, Qt::AlignHCenter); gridLayout->addWidget(new QLabel("All"), row, COLUMN_ALL_MAP, Qt::AlignCenter); gridLayout->addWidget(new QLabel("Maps"), row + 1, COLUMN_ALL_MAP, Qt::AlignCenter); gridLayout->addWidget(new QLabel("File/Map"), row, COLUMN_MAP_INDEX, 2, 2, Qt::AlignCenter); gridLayout->setVerticalSpacing(2); gridLayout->setColumnStretch(COLUMN_SELECT, 0); gridLayout->setColumnStretch(COLUMN_EXCLUDE, 0); gridLayout->setColumnStretch(COLUMN_SMOOTH, 0); gridLayout->setColumnStretch(COLUMN_WEIGHT, 0); gridLayout->setColumnStretch(COLUMN_INVERT, 0); gridLayout->setColumnStretch(COLUMN_SKIP, 0); gridLayout->setColumnStretch(COLUMN_ALL_MAP, 0); gridLayout->setColumnStretch(COLUMN_MAP_INDEX, 0); gridLayout->setColumnStretch(COLUMN_MAP_NAME, 100); } int row = gridLayout->rowCount(); gridLayout->addWidget(m_selectionCheckBox, row, COLUMN_SELECT, 2, 1, Qt::AlignCenter); gridLayout->addWidget(m_exclusionDistanceSpinBox, row, COLUMN_EXCLUDE, 2, 1, Qt::AlignCenter); gridLayout->addWidget(m_smoothingSpinBox, row, COLUMN_SMOOTH, 2, 1, Qt::AlignCenter); gridLayout->addWidget(m_weightSpinBox, row, COLUMN_WEIGHT, 2, 1, Qt::AlignCenter); gridLayout->addWidget(m_invertGradientCheckBox, row, COLUMN_INVERT, 2, 1, Qt::AlignCenter); gridLayout->addWidget(m_skipGradientCheckBox, row, COLUMN_SKIP, 2, 1, Qt::AlignCenter); gridLayout->addWidget(m_allMapsCheckBox, row, COLUMN_ALL_MAP, 2, 1, Qt::AlignCenter); gridLayout->addWidget(mapFileComboBox, row, COLUMN_MAP_INDEX, 1, 2); row++; gridLayout->addWidget(mapIndexSpinBox, row, COLUMN_MAP_INDEX); gridLayout->addWidget(mapNameComboBox, row, COLUMN_MAP_NAME); CaretMappableDataFileAndMapSelectionModel* model = m_mapFileAndIndexSelectorObject->getModel(); if (defaultFile != NULL) { model->setSelectedFile(defaultFile); } else { m_selectionCheckBox->blockSignals(true); m_selectionCheckBox->setChecked(false); m_selectionCheckBox->blockSignals(false); } m_mapFileAndIndexSelectorObject->updateFileAndMapSelector(model); updateFileData(); } /* * Destructor. */ BorderOptimizeDataFileSelector::~BorderOptimizeDataFileSelector() { } /** * Update the file data. */ void BorderOptimizeDataFileSelector::updateFileData() { const bool widgetsEnabled = m_selectionCheckBox->isChecked(); m_allMapsCheckBox->setEnabled(widgetsEnabled); m_exclusionDistanceSpinBox->setEnabled(widgetsEnabled); m_invertGradientCheckBox->setEnabled(widgetsEnabled); m_skipGradientCheckBox->setEnabled(widgetsEnabled); m_smoothingSpinBox->setEnabled(widgetsEnabled); m_weightSpinBox->setEnabled(widgetsEnabled); CaretMappableDataFileAndMapSelectionModel* model = m_mapFileAndIndexSelectorObject->getModel(); m_mapFileAndIndexSelectorObject->updateFileAndMapSelector(model); m_mapFileAndIndexSelectorObject->setEnabled(widgetsEnabled); if (widgetsEnabled) { const CaretMappableDataFile* mapFile = model->getSelectedFile(); const bool denseFileFlag = ((mapFile != NULL) ? (mapFile->getDataFileType() == DataFileTypeEnum::CONNECTIVITY_DENSE) : false); if (denseFileFlag) { m_allMapsCheckBox->setChecked(false); m_allMapsCheckBox->setEnabled(false); m_exclusionDistanceSpinBox->setEnabled(true); m_skipGradientCheckBox->setChecked(false); m_skipGradientCheckBox->setEnabled(false); } else { m_exclusionDistanceSpinBox->setEnabled(false); } m_allMapsCheckBox->setEnabled( ! denseFileFlag); } } /** * Set the selection status. * * @param selectedStatus * New selection status. */ void BorderOptimizeDataFileSelector::setSelected(const bool selectedStatus) { m_selectionCheckBox->setChecked(selectedStatus); } /** * Gets called when the map file selection changes. */ void BorderOptimizeDataFileSelector::mapFileSelectionChanged() { updateFileData(); } /** * @return Selections made for data file. */ CaretPointer BorderOptimizeDataFileSelector::getSelections() const { CaretMappableDataFileAndMapSelectionModel* model = m_mapFileAndIndexSelectorObject->getModel(); CaretMappableDataFile* mapFile = model->getSelectedFile(); const int32_t mapIndex = model->getSelectedMapIndex(); CaretPointer dataOut(NULL); if (mapFile != NULL) { if ((mapIndex >= 0) && (mapIndex < mapFile->getNumberOfMaps())) { if (m_selectionCheckBox->isChecked()) { dataOut.grabNew(new BorderOptimizeExecutor::DataFileInfo(mapFile, mapIndex, m_allMapsCheckBox->isChecked(), m_smoothingSpinBox->value(), m_weightSpinBox->value(), m_invertGradientCheckBox->isChecked(), m_skipGradientCheckBox->isChecked(), m_exclusionDistanceSpinBox->value())); } } } return dataOut; } /* * Called when selection checkbox changes. * @param checked * New status for selection check box. */ void BorderOptimizeDataFileSelector::selectionCheckBoxToggled(bool /*checked*/) { updateFileData(); } /* * Called when all maps check box is toggled. * * @param checked * New status for selection check box. */ void BorderOptimizeDataFileSelector::allMapsCheckBoxToggled(bool /*checked*/) { updateFileData(); } workbench-1.1.1/src/GuiQt/BorderOptimizeDialog.h000066400000000000000000000156231255417355300215640ustar00rootroot00000000000000#ifndef __BORDER_OPTIMIZE_DIALOG_H__ #define __BORDER_OPTIMIZE_DIALOG_H__ /*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "BorderOptimizeExecutor.h" #include "DataFileTypeEnum.h" #include "StructureEnum.h" #include "WuQDialogModal.h" class QCheckBox; class QDoubleSpinBox; class QGridLayout; class QGroupBox; class QLineEdit; class QSpinBox; class QVBoxLayout; namespace caret { class Border; class BorderOptimizeDataFileSelector; class CaretDataFileSelectionComboBox; class CaretDataFileSelectionModel; class CaretMappableDataFile; class CaretMappableDataFileAndMapSelectorObject; class Surface; class SurfaceSelectionModel; class SurfaceSelectionViewController; class BorderOptimizeDialog : public WuQDialogModal { Q_OBJECT public: BorderOptimizeDialog(QWidget* parent); virtual ~BorderOptimizeDialog(); void getModifiedBorders(std::vector& modifiedBordersOut) const; bool isKeepBoundaryBorderSelected() const; void updateDialog(const int32_t browserTabIndex, Surface* surface, std::vector >& bordersInsideROI, Border* borderEnclosingROI, std::vector& nodesInsideROI); protected: virtual void okButtonClicked(); virtual void cancelButtonClicked(); private slots: void addDataFileRowToolButtonClicked(); void gradientComputatonSurfaceSelected(Surface* surface); void bordersDisableAllSelected(); void bordersEnableAllSelected(); void dataFilesDisableAllSelected(); void dataFilesEnableAllSelected(); void saveBrowseButtonClicked(); private: BorderOptimizeDialog(const BorderOptimizeDialog&); BorderOptimizeDialog& operator=(const BorderOptimizeDialog&); QWidget* createBorderSelectionWidget(); QWidget* createDataFilesWidget(); QWidget* createVertexAreasMetricWidget(); QWidget* createOptionsWidget(); QWidget* createSurfaceSelectionWidget(); QWidget* createSphericalUpsamplingWidget(); QWidget* createSavingWidget(); void addDataFileRow(CaretMappableDataFile* mapFile); void setAllBorderEnabledSelections(const bool status); void setAllDataFileEnabledSelections(const bool status); void preserveDialogSizeAndPositionWhenReOpened(); QWidget* m_dialogWidget; Surface* m_surface; std::vector m_bordersInsideROI; std::vector m_borderPointsInsideROICount; std::vector m_selectedBorders; QGridLayout* m_bordersInsideROIGridLayout; Border* m_borderEnclosingROI; std::vector m_nodesInsideROI; std::vector m_optimizeDataFileSelectors; CaretDataFileSelectionComboBox* m_borderPairFileSelectionComboBox; CaretDataFileSelectionModel* m_borderPairFileSelectionModel; QCheckBox* m_borderPairCheckBox; std::vector m_borderCheckBoxes; std::vector m_optimizeDataFileTypes; QGridLayout* m_borderOptimizeDataFileGridLayout; StructureEnum::Enum m_surfaceSelectionStructure; SurfaceSelectionModel* m_surfaceSelectionModel; SurfaceSelectionViewController* m_surfaceSelectionControl; CaretDataFileSelectionComboBox* m_vertexAreasMetricFileComboBox; CaretDataFileSelectionModel* m_vertexAreasMetricFileSelectionModel; QDoubleSpinBox* m_gradientFollowingStrengthSpinBox; int32_t m_browserTabIndex; QCheckBox* m_keepRegionBorderCheckBox; QCheckBox* m_outputGradientMapCheckBox; QSize m_defaultDataFilesWidgetSize; QGroupBox* m_upsamplingGroupBox; SurfaceSelectionModel* m_upsamplingSurfaceSelectionModel; StructureEnum::Enum m_upsamplingSurfaceStructure; SurfaceSelectionViewController* m_upsamplingSurfaceSelectionControl; QSpinBox* m_upsamplingResolutionSpinBox; QGroupBox* m_savingGroupBox; QLineEdit* m_savingBaseNameLineEdit; QLineEdit* m_savingDirectoryLineEdit; }; class BorderOptimizeDataFileSelector : public QObject { Q_OBJECT public: BorderOptimizeDataFileSelector(const int32_t itemIndex, const std::vector& optimizeDataFileTypes, CaretMappableDataFile* defaultFile, QGridLayout* gridLayout, QObject* parent); ~BorderOptimizeDataFileSelector(); void updateFileData(); CaretPointer getSelections() const; void setSelected(const bool selectedStatus); private slots: void selectionCheckBoxToggled(bool checked); void allMapsCheckBoxToggled(bool checked); void mapFileSelectionChanged(); public: CaretMappableDataFileAndMapSelectorObject* m_mapFileAndIndexSelectorObject; QCheckBox* m_allMapsCheckBox; QDoubleSpinBox* m_exclusionDistanceSpinBox; QCheckBox* m_invertGradientCheckBox; QCheckBox* m_skipGradientCheckBox; QCheckBox* m_selectionCheckBox; QDoubleSpinBox* m_smoothingSpinBox; QDoubleSpinBox* m_weightSpinBox; }; #ifdef __BORDER_OPTIMIZE_DIALOG_DECLARE__ #endif // __BORDER_OPTIMIZE_DIALOG_DECLARE__ } // namespace #endif //__BORDER_OPTIMIZE_DIALOG_H__ workbench-1.1.1/src/GuiQt/BorderOptimizeExecutor.cxx000066400000000000000000001613231255417355300225350ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __BORDER_OPTIMIZE_EXECUTOR_DECLARE__ #include "BorderOptimizeExecutor.h" #undef __BORDER_OPTIMIZE_EXECUTOR_DECLARE__ #include "Border.h" #include "CaretAssert.h" #include "Surface.h" #include "AlgorithmBorderResample.h" #include "AlgorithmBorderToVertices.h" #include "AlgorithmCiftiCorrelationGradient.h" #include "AlgorithmCiftiRestrictDenseMap.h" #include "AlgorithmCiftiSeparate.h" #include "AlgorithmMetricDilate.h" #include "AlgorithmMetricGradient.h" #include "AlgorithmMetricFindClusters.h" #include "AlgorithmNodesInsideBorder.h" #include "AlgorithmSurfaceCreateSphere.h" #include "AlgorithmSurfaceResample.h" #include "BorderFile.h" #include "CaretLogger.h" #include "CaretOMP.h" #include "CiftiFile.h" #include "CiftiMappableDataFile.h" #include "EventProgressUpdate.h" #include "FileInformation.h" #include "GeodesicHelper.h" #include "MathFunctions.h" #include "MetricFile.h" #include "SurfaceFile.h" #include "SurfaceProjectedItem.h" #include "SurfaceProjectionBarycentric.h" #include "SurfaceResamplingHelper.h" #include "TextFile.h" #include "TopologyHelper.h" #include using namespace caret; using namespace std; /** * \class caret::BorderOptimizeExecutor * \brief * \ingroup GuiQt * * */ /** * Constructor. */ BorderOptimizeExecutor::BorderOptimizeExecutor() : CaretObject() { } /** * Destructor. */ BorderOptimizeExecutor::~BorderOptimizeExecutor() { } namespace { void doCombination(const MetricFile& gradient, const vector& roiNodes, const bool& invert, const float& mapStrength, vector& combinedGradData) { const float* gradVals = gradient.getValuePointerForColumn(0); float myMin = 0.0f, myMax = 0.0f; bool first = true; int numSelected = (int)roiNodes.size(); for (int i = 0; i < numSelected; ++i)//for normalizing, gather min/max { float tempVal = gradVals[roiNodes[i]]; if (MathFunctions::isNumeric(tempVal)) { if (first) { first = false; myMin = tempVal; myMax = tempVal; } else { if (tempVal < myMin) myMin = tempVal; if (tempVal > myMax) myMax = tempVal; } } } if (myMin == myMax)//if no contrast, map it all to (post-inversion) minimum - also trips if no numeric data { if (invert) { myMin -= 1.0f; } else { myMax += 1.0f; } } float myRange = myMax - myMin; for (int i = 0; i < numSelected; ++i)//for normalizing, gather min/max { float tempVal = gradVals[roiNodes[i]]; if (MathFunctions::isNumeric(tempVal)) { float toCombine; if (invert) { toCombine = (myMax - tempVal) / myRange; } else { toCombine = (tempVal - myMin) / myRange; } combinedGradData[roiNodes[i]] *= 1.0f + mapStrength * (toCombine - 1.0f);//equals toCombine * mapStrength + 1.0f - mapStrength } else { combinedGradData[roiNodes[i]] *= 1.0f - mapStrength; } } } bool extractGradientData(const CaretMappableDataFile* dataFile, const int32_t& mapIndex, SurfaceFile* surface, const MetricFile* gradRoi, const float& smoothing, const MetricFile* correctedAreasMetric, MetricFile& gradientOut, const bool& skipGradient, const float& excludeDist) { int numNodes = surface->getNumberOfNodes(); MetricFile tempData, tempRoi; const MetricFile* useData = &tempData, *useRoi = gradRoi; switch (dataFile->getDataFileType()) { case DataFileTypeEnum::METRIC: { const MetricFile* metricFile = dynamic_cast(dataFile); CaretAssert(metricFile != NULL); CaretAssert(metricFile->getNumberOfNodes() == numNodes); useData = metricFile; break; } case DataFileTypeEnum::CONNECTIVITY_DENSE_SCALAR: case DataFileTypeEnum::CONNECTIVITY_DENSE_TIME_SERIES: { tempData.setNumberOfNodesAndColumns(numNodes, 1); tempData.setStructure(surface->getStructure()); tempRoi.setNumberOfNodesAndColumns(numNodes, 1); const CiftiMappableDataFile* ciftiMappableFile = dynamic_cast(dataFile); CaretAssert(ciftiMappableFile != NULL); CaretAssert(ciftiMappableFile->getMappingSurfaceNumberOfNodes(surface->getStructure()) == numNodes); vector surfData, ciftiRoi; bool result = ciftiMappableFile->getMapDataForSurface(mapIndex, surface->getStructure(), surfData, &ciftiRoi); CaretAssert(result); if (!result) { CaretLogSevere("failed to get map data for map " + AString::number(mapIndex) + " of data file of type " + DataFileTypeEnum::toName(dataFile->getDataFileType())); return false; } tempData.setValuesForColumn(0, surfData.data()); vector maskedROI(numNodes); const float* gradRoiData = gradRoi->getValuePointerForColumn(0); for (int i = 0; i < numNodes; ++i) { if (gradRoiData[i] > 0.0f) { maskedROI[i] = ciftiRoi[i]; } else { maskedROI[i] = 0.0f; } } tempRoi.setValuesForColumn(0, maskedROI.data()); useRoi = &tempRoi; break; } case DataFileTypeEnum::CONNECTIVITY_DENSE: { const CiftiMappableDataFile* ciftiMappableFile = dynamic_cast(dataFile); CaretAssert(ciftiMappableFile != NULL); CaretAssert(ciftiMappableFile->getMappingSurfaceNumberOfNodes(surface->getStructure()) == numNodes); const CiftiFile* dataCifti = ciftiMappableFile->getCiftiFile(); const MetricFile* leftRoi = NULL, *rightRoi = NULL, *cerebRoi = NULL; const MetricFile* leftCorrAreas = NULL, *rightCorrAreas = NULL, *cerebCorrAreas = NULL; SurfaceFile* leftSurf = NULL, *rightSurf = NULL, *cerebSurf = NULL; switch (surface->getStructure()) { case StructureEnum::CORTEX_LEFT: leftRoi = gradRoi; leftCorrAreas = correctedAreasMetric; leftSurf = surface; break; case StructureEnum::CORTEX_RIGHT: rightRoi = gradRoi; rightCorrAreas = correctedAreasMetric; rightSurf = surface; break; case StructureEnum::CEREBELLUM: cerebRoi = gradRoi; cerebCorrAreas = correctedAreasMetric; cerebSurf = surface; break; default: CaretAssert(false); break; } CiftiFile restrictCifti, corrGradCifti; AlgorithmCiftiRestrictDenseMap(NULL, dataCifti, CiftiXML::ALONG_COLUMN, &restrictCifti, leftRoi, rightRoi, cerebRoi, NULL); AlgorithmCiftiCorrelationGradient(NULL, &restrictCifti, &corrGradCifti, leftSurf, rightSurf, cerebSurf, leftCorrAreas, rightCorrAreas, cerebCorrAreas, smoothing, 0.0f, false, false, excludeDist, -1.0f); AlgorithmCiftiSeparate(NULL, &corrGradCifti, CiftiXML::ALONG_COLUMN, surface->getStructure(), &gradientOut); return true; } default: CaretLogWarning("ignoring map " + AString::number(mapIndex) + " of data file of type " + DataFileTypeEnum::toName(dataFile->getDataFileType())); return false; } if (skipGradient) { gradientOut.setNumberOfNodesAndColumns(numNodes, 1); gradientOut.setStructure(surface->getStructure()); gradientOut.setValuesForColumn(0, useData->getValuePointerForColumn(0)); } else { AlgorithmMetricGradient(NULL, surface, useData, &gradientOut, NULL, smoothing, useRoi, false, 0, correctedAreasMetric); } return true; } bool getStatisticsString(const CaretMappableDataFile* dataFile, const int32_t& mapIndex, const vector nodeLists[2], const SurfaceFile& surface, const MetricFile* correctedAreasMetric, const float& excludeDist, AString& statsOut) { vector tempStatsStore, roiData; StructureEnum::Enum structure = surface.getStructure(); int numNodes = surface.getNumberOfNodes(); const float* statsData = NULL; switch (dataFile->getDataFileType()) { case DataFileTypeEnum::METRIC: { const MetricFile* metricFile = dynamic_cast(dataFile); CaretAssert(metricFile != NULL); statsData = metricFile->getValuePointerForColumn(mapIndex); break; } case DataFileTypeEnum::CONNECTIVITY_DENSE_SCALAR: case DataFileTypeEnum::CONNECTIVITY_DENSE_TIME_SERIES: { const CiftiMappableDataFile* ciftiMappableFile = dynamic_cast(dataFile); CaretAssert(ciftiMappableFile != NULL); bool result = ciftiMappableFile->getMapDataForSurface(mapIndex, structure, tempStatsStore, &roiData); CaretAssert(result); if (!result) { CaretLogSevere("failed to get map data for map " + AString::number(mapIndex) + " of data file of type " + DataFileTypeEnum::toName(dataFile->getDataFileType())); return false; } statsData = tempStatsStore.data(); break; } case DataFileTypeEnum::CONNECTIVITY_DENSE: { const CiftiMappableDataFile* ciftiMappableFile = dynamic_cast(dataFile); CaretAssert(ciftiMappableFile != NULL); const CiftiFile* dataCifti = ciftiMappableFile->getCiftiFile(); const CiftiXML& myXML = dataCifti->getCiftiXML(); const CiftiBrainModelsMap& myDenseMap = myXML.getBrainModelsMap(CiftiXML::ALONG_ROW); CaretAssert(myDenseMap.hasSurfaceData(structure));//the GUI should filter by structure, right? int64_t rowLength = myXML.getDimensionLength(CiftiXML::ALONG_ROW); vector > data[2]; for (int i = 0; i < 2; ++i) { data[i].resize(nodeLists[i].size()); for (int j = 0; j < (int)nodeLists[i].size(); ++j) { int64_t index = myDenseMap.getIndexForNode(nodeLists[i][j], structure); if (index != -1)//roi can go outside the cifti ROI, ignore such vertices { data[i][j].resize(rowLength);//only allocate the ones inside the cifti ROI dataCifti->getRow(data[i][j].data(), index); } } } vector samples[2][2]; CaretPointer myGeoBase; if (correctedAreasMetric != NULL) { myGeoBase.grabNew(new GeodesicHelperBase(&surface, correctedAreasMetric->getValuePointerForColumn(0))); } for (int posSide = 0; posSide < 2; ++posSide) { int listSize = (int)nodeLists[posSide].size(); #pragma omp CARET_PAR { CaretPointer myGeoHelp; if (correctedAreasMetric != NULL) { myGeoHelp.grabNew(new GeodesicHelper(myGeoBase)); } else { myGeoHelp = surface.getGeodesicHelper(); } #pragma omp CARET_FOR schedule(dynamic) for (int i = 0; i < listSize; ++i) { if (!data[posSide][i].empty()) { vector excludeNodes; vector excludeDists; myGeoHelp->getNodesToGeoDist(nodeLists[posSide][i], excludeDist, excludeNodes, excludeDists); vector excludeLookup(numNodes, 0); for (int j = 0; j < (int)excludeNodes.size(); ++j) { excludeLookup[excludeNodes[j]] = 1; } for (int side = 0; side < 2; ++side) { vector averagerow(rowLength, 0.0); int count = 0; for (int j = 0; j < (int)nodeLists[side].size(); ++j) { if (excludeLookup[nodeLists[side][j]] == 0 && !data[side][j].empty()) { ++count; float* dataRef = data[side][j].data(); for (int k = 0; k < rowLength; ++k) { averagerow[k] += dataRef[k]; } } } if (count != 0) { double accum1 = 0.0, accum2 = 0.0; for (int k = 0; k < rowLength; ++k) { averagerow[k] /= count; accum1 += averagerow[k]; accum2 += data[posSide][i][k]; } float mean1 = accum1 / rowLength, mean2 = accum2 / rowLength; accum1 = 0.0, accum2 = 0.0; double accum3 = 0.0; for (int k = 0; k < rowLength; ++k) { float val1 = averagerow[k] - mean1, val2 = data[posSide][i][k] - mean2; accum1 += val1 * val2; accum2 += val1 * val1; accum3 += val2 * val2; } double corrval = accum1 / sqrt(accum2 * accum3); if (corrval >= 0.999999) corrval = 0.999999;//prevent inf if (corrval <= -0.999999) corrval = -0.999999;//prevent -inf #pragma omp critical { samples[posSide][side].push_back(0.5f * log((1 + corrval)/(1 - corrval)));//fisher z transform } } } } } } } float tstats[2], cohen_ds[2], pvals[2]; for (int piece = 0; piece < 2; ++piece) { int count[2]; float mean[2], variance[2]; for (int posSide = 0; posSide < 2; ++posSide) { count[posSide] = (int)samples[posSide][piece].size(); if (count[posSide] < 2) return false; double accum = 0.0; for (int i = 0; i < count[posSide]; ++i) { accum += samples[posSide][piece][i]; } mean[posSide] = accum / count[posSide]; accum = 0.0; for (int i = 0; i < count[posSide]; ++i) { float tempf = samples[posSide][piece][i] - mean[posSide]; accum += tempf * tempf; } variance[posSide] = accum / (count[posSide] - 1); } tstats[piece] = (mean[0] - mean[1]) / sqrt(variance[0] / count[0] + variance[1] / count[1]);//welch's t-test cohen_ds[piece] = (mean[0] - mean[1]) / sqrt(((count[0] - 1) * variance[0] + (count[1] - 1) * variance[1]) / (count[0] + count[1] - 2)); pvals[piece] = 2.0f * MathFunctions::q_func(abs(tstats[piece]));//treat as 2-tailed z-stat, this isn't meant to be rigorous, and the t-test cdf is a pain } statsOut = "p1=" + AString::number(pvals[0], 'g', 3) + ", p2=" + AString::number(pvals[1], 'g', 3) + ", t1=" + AString::number(tstats[0], 'g', 3) + ", t2=" + AString::number(tstats[1], 'g', 3) + ", d1=" + AString::number(cohen_ds[0], 'g', 3) + ", d2=" + AString::number(cohen_ds[1], 'g', 3); return true; } default: CaretLogWarning("statistics: ignoring data file of type " + DataFileTypeEnum::toName(dataFile->getDataFileType())); return false; } float mean[2], variance[2]; int count[2] = {0, 0}; for (int piece = 0; piece < 2; ++piece) { double accum = 0.0; for (int i = 0; i < (int)nodeLists[piece].size(); ++i) { if (roiData.empty() || roiData[nodeLists[piece][i]] > 0.0f) { accum += statsData[nodeLists[piece][i]];//weight by vertex area? weighted welch's t-test, oh joy ++count[piece]; } } mean[piece] = accum / count[piece]; accum = 0.0; for (int i = 0; i < (int)nodeLists[piece].size(); ++i) { if (roiData.empty() || roiData[nodeLists[piece][i]] > 0.0f) { float tempf = (statsData[nodeLists[piece][i]] - mean[piece]); accum += tempf * tempf; } } variance[piece] = accum / (count[piece] - 1); } float tstat = (mean[0] - mean[1]) / sqrt(variance[0] / count[0] + variance[1] / count[1]);//welch's t-test float cohen_d = (mean[0] - mean[1]) / sqrt(((count[0] - 1) * variance[0] + (count[1] - 1) * variance[1]) / (count[0] + count[1] - 2)); float pval = 2.0f * MathFunctions::q_func(abs(tstat));//treat as 2-tailed z-stat, this isn't meant to be rigorous, and the t-test cdf is a pain statsOut = "p=" + AString::number(pval, 'g', 3) + ", t=" + AString::number(tstat, 'g', 3) + ", d=" + AString::number(cohen_d, 'g', 3); return true; } int getBorderPointNode(const Border* theBorder, const int& index) { CaretAssert(index >= 0 && index < theBorder->getNumberOfPoints()); const SurfaceProjectionBarycentric* thisBary = theBorder->getPoint(index)->getBarycentricProjection(); CaretAssert(thisBary != NULL && thisBary->isValid()); int thisNode = thisBary->getNodeWithLargestWeight(); CaretAssert(thisNode >= 0); return thisNode; } struct BorderRedrawInfo { int startpoint, endpoint; }; } /** * Run the border optimization algorithm. * * @param inputData * Input data for the algorithm. * @param statisticsInformationOut * Output containing statistics information. * @param errorMessageOut * Output containing error information when false returned. * @return * True if successful, else false. */ bool BorderOptimizeExecutor::run(const InputData& inputData, AString& statisticsInformationOut, AString& errorMessageOut) { AString stageString = "initializing"; try { statisticsInformationOut.clear(); errorMessageOut.clear(); printInputs(inputData); SurfaceFile* computeSurf = inputData.m_surface; const MetricFile* correctedAreasMetric = inputData.m_vertexAreasMetricFile; int32_t numNodes = computeSurf->getNumberOfNodes(); int numSelected = (int)inputData.m_nodesInsideROI.size(); if (numSelected < 1) { errorMessageOut = "no nodes selected"; return false; } vector roiData(numNodes, 0.0f); vector combinedGradData(numNodes, 0.0f); for (int i = 0; i < numSelected; ++i) { roiData[inputData.m_nodesInsideROI[i]] = 1.0f; combinedGradData[inputData.m_nodesInsideROI[i]] = 1.0f;//also initialize only the inside-roi parts of the gradient combining function, leaving the outside 0 } int numBorders = (int)inputData.m_borders.size(); vector myRedrawInfo(numBorders); const int PROGRESS_MAX = 100; const int SEGMENT_PROGRESS = 10; const int COMPUTE_PROGRESS = 55; const int HELPER_PROGRESS = 5; const int DRAW_PROGRESS = 10; const int STATISTICS_PROGRESS = 20; CaretAssert(SEGMENT_PROGRESS + COMPUTE_PROGRESS + HELPER_PROGRESS + DRAW_PROGRESS + STATISTICS_PROGRESS == PROGRESS_MAX); stageString = "border segment locating"; for (int i = 0; i < numBorders; ++i) {//find pieces of border to redraw, before doing gradient, so it can error early Border* thisBorder = inputData.m_borders[i]; EventProgressUpdate tempEvent(0, PROGRESS_MAX, (SEGMENT_PROGRESS * i) / numBorders, "finding in-roi segment of border '" + thisBorder->getName() + "'"); EventManager::get()->sendEvent(&tempEvent); if (tempEvent.isCancelled()) { errorMessageOut = "cancelled by user"; return false; } int numPoints = thisBorder->getNumberOfPoints(); int start, end; if (thisBorder->isClosed()) { if (numPoints < 3)//one outside, two inside will work { errorMessageOut = "Border '" + thisBorder->getName() + "' has too few points to adjust"; return false; } for (start = numPoints - 1; start >= 0; --start)//search backwards for a point outside the roi { if (!(roiData[getBorderPointNode(thisBorder, start)] > 0.0f)) break; } if (start == -1)//no points outside ROI { errorMessageOut = "Border '" + thisBorder->getName() + "' has no points outside the ROI"; return false; } int added; for (added = 1; added < numPoints; ++added)//search forward from it to find the point inside the roi - if the above loop had to loop, this will end on the first iteration { int pointIndex = (start + added) % numPoints;//closed border requires mod arithmetic if (roiData[getBorderPointNode(thisBorder, pointIndex)] > 0.0f) break; } if (added == numPoints)//no points inside ROI {//NOTE: if multipart borders are used, and passed borders don't check the parts for being in/out, then this name is not unique errorMessageOut = "Border '" + thisBorder->getName() + "' has no points inside the ROI"; return false; } start = (start + added) % numPoints; for (added = 1; added < numPoints; ++added) { int pointIndex = (start + added) % numPoints; if (!(roiData[getBorderPointNode(thisBorder, pointIndex)] > 0.0f)) break; } CaretAssert(added != numPoints);//we have points inside and outside roi, this search should have stopped end = (start + added - 1) % numPoints;//we will check this for being equal to start later, first check for multiple sections for (; added < numPoints; ++added) { int pointIndex = (start + added) % numPoints; if (roiData[getBorderPointNode(thisBorder, pointIndex)] > 0.0f) { errorMessageOut = "Border '" + thisBorder->getName() + "' has multiple sections inside the ROI"; return false; } } if (end == start) { errorMessageOut = "Border '" + thisBorder->getName() + "' has only one point inside the ROI"; return false; } if (getBorderPointNode(thisBorder, start) == getBorderPointNode(thisBorder, end)) { errorMessageOut = "Border '" + thisBorder->getName() + "' enters and exits the ROI too close to the same vertex"; return false; } } else {//open border if (numPoints < 2)//two, both inside, will work { errorMessageOut = "Border '" + thisBorder->getName() + "' has too few points to adjust"; return false; } for (start = 0; start < numPoints; ++start)//search for first point inside { if (roiData[getBorderPointNode(thisBorder, start)] > 0.0f) break; } if (start == numPoints) {//NOTE: if multipart borders are used, and passed borders don't check the parts for being in/out, then this name is not unique errorMessageOut = "Border '" + thisBorder->getName() + "' has no points inside the ROI"; return false; } for (end = start + 1; end < numPoints; ++end)//search for first point outside { if (!(roiData[getBorderPointNode(thisBorder, end)] > 0.0f)) break; } --end;//and subtract one to get inclusive range, also deals with == numPoints for (int check = end + 2; check < numPoints; ++check)//look for a second piece inside { if (roiData[getBorderPointNode(thisBorder, check)] > 0.0f) { errorMessageOut = "Border '" + thisBorder->getName() + "' has multiple sections inside the ROI"; return false; } } } myRedrawInfo[i].startpoint = start; myRedrawInfo[i].endpoint = end; } stageString = "data processing"; int numInputs = (int)inputData.m_dataFileInfo.size(); vector inputRoiData(numNodes, 0.0f); for (int i = 0; i < (int)inputData.m_nodesInsideROI.size(); ++i) { inputRoiData[inputData.m_nodesInsideROI[i]] = 1.0f; } MetricFile inputRoi, dilatedRoi; inputRoi.setNumberOfNodesAndColumns(numNodes, 1); inputRoi.setValuesForColumn(0, inputRoiData.data()); AlgorithmMetricDilate(NULL, &inputRoi, computeSurf, 0.0001f, &dilatedRoi);//dilate roi by 1 neighbor for (int i = 0; i < numInputs; ++i) { EventProgressUpdate tempEvent(0, PROGRESS_MAX, SEGMENT_PROGRESS + (COMPUTE_PROGRESS * i) / numInputs, "processing data file '" + inputData.m_dataFileInfo[i].m_mapFile->getFileNameNoPath() + "'"); EventManager::get()->sendEvent(&tempEvent); if (tempEvent.isCancelled()) { errorMessageOut = "cancelled by user"; return false; } MetricFile tempGradient; if (inputData.m_dataFileInfo[i].m_allMapsFlag) { for (int j = 0; j < inputData.m_dataFileInfo[i].m_mapFile->getNumberOfMaps(); ++j) { if (extractGradientData(inputData.m_dataFileInfo[i].m_mapFile, j, computeSurf, &dilatedRoi, inputData.m_dataFileInfo[i].m_smoothing, correctedAreasMetric, tempGradient, inputData.m_dataFileInfo[i].m_skipGradient, inputData.m_dataFileInfo[i].m_corrGradExcludeDist)) { doCombination(tempGradient, inputData.m_nodesInsideROI, inputData.m_dataFileInfo[i].m_invertGradientFlag, inputData.m_dataFileInfo[i].m_weight, combinedGradData); } } } else { if (extractGradientData(inputData.m_dataFileInfo[i].m_mapFile, inputData.m_dataFileInfo[i].m_mapIndex, computeSurf, &dilatedRoi, inputData.m_dataFileInfo[i].m_smoothing, correctedAreasMetric, tempGradient, inputData.m_dataFileInfo[i].m_skipGradient, inputData.m_dataFileInfo[i].m_corrGradExcludeDist)) { doCombination(tempGradient, inputData.m_nodesInsideROI, inputData.m_dataFileInfo[i].m_invertGradientFlag, inputData.m_dataFileInfo[i].m_weight, combinedGradData); } } } stageString = "border drawing"; if (inputData.m_combinedGradientDataOut != NULL) { inputData.m_combinedGradientDataOut->setNumberOfNodesAndColumns(numNodes, 1); inputData.m_combinedGradientDataOut->setStructure(computeSurf->getStructure()); inputData.m_combinedGradientDataOut->setValuesForColumn(0, combinedGradData.data()); } SurfaceFile* drawSurf = computeSurf, *origSphere = inputData.m_upsamplingSphericalSurface, highresSphere, highresMidthick; vector drawGrad = combinedGradData, drawRoi = roiData; vector origAreasStore, highresAreasStore; const float* origAreas = NULL; if (correctedAreasMetric != NULL) { origAreas = correctedAreasMetric->getValuePointerForColumn(0); } const float* drawAreas = origAreas; if (origSphere != NULL) { if (inputData.m_upsamplingSphericalSurface->getNumberOfNodes() >= inputData.m_upsamplingResolution) { errorMessageOut = "upsampling number of vertices must be greater than current vertex count"; return false; } AlgorithmSurfaceCreateSphere(NULL, inputData.m_upsamplingResolution, &highresSphere); int highresNumNodes = highresSphere.getNumberOfNodes(); highresSphere.setStructure(origSphere->getStructure()); AlgorithmSurfaceResample(NULL, computeSurf, origSphere, &highresSphere, SurfaceResamplingMethodEnum::BARYCENTRIC, &highresMidthick); if (origAreas == NULL) { computeSurf->computeNodeAreas(origAreasStore); origAreas = origAreasStore.data(); highresMidthick.computeNodeAreas(highresAreasStore); } else { vector origRatioStore(origAreas, origAreas + numNodes), highresRatioStore(highresNumNodes), wrongAreas, highresWrongAreas; computeSurf->computeNodeAreas(wrongAreas);//to get high res corrected areas, convert to expansion ratio, highresMidthick.computeNodeAreas(highresWrongAreas);//then resample and multiply by the high res surface areas for (int i = 0; i < numNodes; ++i) { origRatioStore[i] /= wrongAreas[i]; }//we don't have anything high res but the wrong areas yet, so use like area measures - expansion ratio should be fairly smooth anyway, so not as important SurfaceResamplingHelper initialUpsampler(SurfaceResamplingMethodEnum::ADAP_BARY_AREA, origSphere, &highresSphere, wrongAreas.data(), highresWrongAreas.data()); initialUpsampler.resampleNormal(origRatioStore.data(), highresRatioStore.data()); highresAreasStore.resize(highresNumNodes); for (int i = 0; i < highresNumNodes; ++i) { highresAreasStore[i] = highresRatioStore[i] * highresWrongAreas[i]; } } drawSurf = &highresMidthick; drawAreas = highresAreasStore.data(); drawGrad.resize(highresNumNodes); drawRoi.resize(highresNumNodes); SurfaceResamplingHelper finalUpsampler(SurfaceResamplingMethodEnum::ADAP_BARY_AREA, origSphere, &highresSphere, origAreas, highresAreasStore.data(), roiData.data()); finalUpsampler.resampleNormal(combinedGradData.data(), drawGrad.data()); finalUpsampler.getResampleValidROI(drawRoi.data()); } { EventProgressUpdate tempEvent(0, PROGRESS_MAX, SEGMENT_PROGRESS + COMPUTE_PROGRESS, "generating geodesic helper"); EventManager::get()->sendEvent(&tempEvent); if (tempEvent.isCancelled()) { errorMessageOut = "cancelled by user"; return false; } } CaretPointer myGeoHelp; CaretPointer myGeoBase; if (correctedAreasMetric != NULL) { myGeoBase.grabNew(new GeodesicHelperBase(drawSurf, drawAreas)); myGeoHelp.grabNew(new GeodesicHelper(myGeoBase)); } else { myGeoHelp = drawSurf->getGeodesicHelper(); } CaretPointer myTopoHelp = drawSurf->getTopologyHelper(); vector drawOrigBorders = inputData.m_borders; BorderFile highresOrigBorders; if (origSphere != NULL) {//first, copy all borders and resample to get endpoint positions on high res mesh, then draw new segments as borders and downsample just the segments BorderFile origBorders; for (int i = 0; i < numBorders; ++i) { origBorders.addBorder(new Border(*(inputData.m_borders[i]))); } AlgorithmBorderResample(NULL, &origBorders, origSphere, &highresSphere, &highresOrigBorders);//NOTE: this must keep each border and point intact and in the same order, just on the new sphere for (int i = 0; i < numBorders; ++i) { drawOrigBorders[i] = highresOrigBorders.getBorder(i); } } vector roiMinusTracesData = drawRoi; BorderFile redrawnSegments; for (int i = 0; i < numBorders; ++i) { EventProgressUpdate tempEvent(0, PROGRESS_MAX, SEGMENT_PROGRESS + COMPUTE_PROGRESS + HELPER_PROGRESS + (DRAW_PROGRESS * i) / numBorders, "redrawing segment of border '" + inputData.m_borders[i]->getName() + "'");//use non-resampled borders for name, just in case EventManager::get()->sendEvent(&tempEvent); if (tempEvent.isCancelled()) { errorMessageOut = "cancelled by user"; return false; } vector nodes; vector dists; myGeoHelp->getPathFollowingData(getBorderPointNode(drawOrigBorders[i], myRedrawInfo[i].startpoint), getBorderPointNode(drawOrigBorders[i], myRedrawInfo[i].endpoint), drawGrad.data(), nodes, dists, inputData.m_gradientFollowingStrength, drawRoi.data(), true, true); if (nodes.size() < 3)//require at least 1 surviving point after removing endpoints { errorMessageOut = "Unable to redraw border segment for border '" + inputData.m_borders[i]->getName() + "'"; return false; } CaretPointer redrawnSegment(new Border()); for (int j = 1; j < (int)nodes.size() - 1; ++j)//drop the closest node to the start and end points from the redrawn segment { const vector& nodeTiles = myTopoHelp->getNodeTiles(nodes[j]); CaretAssert(!nodeTiles.empty()); const int32_t* tileNodes = drawSurf->getTriangle(nodeTiles[0]); int whichNode; for (whichNode = 0; whichNode < 3; ++whichNode) { if (tileNodes[whichNode] == nodes[j]) break; } CaretAssert(whichNode < 3);//it should always find it float weights[3] = { 0.0f, 0.0f, 0.0f }; weights[whichNode] = 1.0f; SurfaceProjectedItem* myItem = new SurfaceProjectedItem(); myItem->getBarycentricProjection()->setTriangleNodes(tileNodes);//none of these should throw myItem->getBarycentricProjection()->setTriangleAreas(weights); myItem->getBarycentricProjection()->setValid(true); myItem->setStructure(computeSurf->getStructure()); redrawnSegment->addPoint(myItem); } redrawnSegments.addBorder(redrawnSegment.releasePointer()); int numOrigPoints = inputData.m_borders[i]->getNumberOfPoints(); Border fullRedrawn = *(drawOrigBorders[i]);//use the potentially upsampled version for the ROI splitting, but don't re-downsample it as the modified border on the current mesh fullRedrawn.removeAllPoints(); if (!(inputData.m_borders[i]->isClosed()))//if it is a closed border, start with the newly drawn section, for simplicity { for (int j = 0; j <= myRedrawInfo[i].startpoint; ++j)//include the original startpoint { fullRedrawn.addPoint(new SurfaceProjectedItem(*(drawOrigBorders[i]->getPoint(j)))); } } fullRedrawn.addPoints(redrawnSegment); if (inputData.m_borders[i]->isClosed()) {//mod arithmetic for closed borders int numKeep = ((numOrigPoints + myRedrawInfo[i].startpoint - myRedrawInfo[i].endpoint) % numOrigPoints) + 1;//inclusive for (int j = 0; j < numKeep; ++j) { fullRedrawn.addPoint(new SurfaceProjectedItem(*(drawOrigBorders[i]->getPoint((j + myRedrawInfo[i].endpoint) % numOrigPoints)))); } } else { for (int j = myRedrawInfo[i].endpoint; j < numOrigPoints; ++j)//include original endpoint { fullRedrawn.addPoint(new SurfaceProjectedItem(*(drawOrigBorders[i]->getPoint(j)))); } } MetricFile borderTrace; BorderFile tempBorderFile; tempBorderFile.addBorder(new Border(fullRedrawn));//because it takes ownership of a pointer AlgorithmBorderToVertices(NULL, drawSurf, &tempBorderFile, &borderTrace); const float* traceData = borderTrace.getValuePointerForColumn(0); int drawNumNodes = drawSurf->getNumberOfNodes(); for (int j = 0; j < drawNumNodes; ++j) { if (traceData[j] > 0.0f) { roiMinusTracesData[j] = 0.0f; } } } BorderFile* segmentsToUse = &redrawnSegments; BorderFile downsampledSegments; if (origSphere != NULL) { AlgorithmBorderResample(NULL, &redrawnSegments, &highresSphere, origSphere, &downsampledSegments); segmentsToUse = &downsampledSegments; } vector modifiedBorders(numBorders);//modify all without replacing so we can error before changing any for (int i = 0; i < numBorders; ++i) { Border& modifiedBorder = modifiedBorders[i]; modifiedBorder = *(inputData.m_borders[i]); int numOrigPoints = inputData.m_borders[i]->getNumberOfPoints(); modifiedBorder.removeAllPoints();//keep name, color, class, etc if (!(inputData.m_borders[i]->isClosed()))//if it is a closed border, start with the newly drawn section, for simplicity { for (int j = 0; j <= myRedrawInfo[i].startpoint; ++j)//include the original startpoint { modifiedBorder.addPoint(new SurfaceProjectedItem(*(inputData.m_borders[i]->getPoint(j)))); } } modifiedBorder.addPoints(segmentsToUse->getBorder(i)); if (inputData.m_borders[i]->isClosed()) {//mod arithmetic for closed borders int numKeep = ((numOrigPoints + myRedrawInfo[i].startpoint - myRedrawInfo[i].endpoint) % numOrigPoints) + 1;//inclusive for (int j = 0; j < numKeep; ++j) { modifiedBorder.addPoint(new SurfaceProjectedItem(*(inputData.m_borders[i]->getPoint((j + myRedrawInfo[i].endpoint) % numOrigPoints)))); } } else { for (int j = myRedrawInfo[i].endpoint; j < numOrigPoints; ++j)//include original endpoint { modifiedBorder.addPoint(new SurfaceProjectedItem(*(inputData.m_borders[i]->getPoint(j)))); } } } stageString = "roi splitting"; MetricFile roiMinusBorderTraces, roiMetric, drawAreasMetric; roiMinusBorderTraces.setNumberOfNodesAndColumns(roiMinusTracesData.size(), 1); roiMinusBorderTraces.setValuesForColumn(0, roiMinusTracesData.data()); roiMetric.setNumberOfNodesAndColumns(drawRoi.size(), 1); roiMetric.setValuesForColumn(0, drawRoi.data()); drawAreasMetric.setNumberOfNodesAndColumns(drawSurf->getNumberOfNodes(), 1); drawAreasMetric.setValuesForColumn(0, drawAreas); MetricFile clustersMetric; int endVal = 0; AlgorithmMetricFindClusters(NULL, drawSurf, &roiMinusBorderTraces, 0.5f, 10.0f, &clustersMetric, false, &roiMetric, &drawAreasMetric, 0, 1, &endVal); if (endVal != 3) { statisticsInformationOut = AString::number(endVal - 1) + " cluster(s) found after splitting the roi with the redrawn borders, skipping statistics"; } else { const float* clusterData = clustersMetric.getValuePointerForColumn(0); vector downsampledClusters; if (origSphere != NULL) { SurfaceResamplingHelper downsampler(SurfaceResamplingMethodEnum::ADAP_BARY_AREA, &highresSphere, origSphere, highresAreasStore.data(), origAreas, drawRoi.data()); vector highresData(highresSphere.getNumberOfNodes()), downsamledData(numNodes); const float* highresClusters = clustersMetric.getValuePointerForColumn(0); for (int i = 0; i < (int)highresData.size(); ++i) { highresData[i] = floor(highresClusters[i] + 0.5f); } downsampler.resamplePopular(highresData.data(), downsamledData.data()); downsampledClusters.resize(numNodes); for (int i = 0; i < (int)downsamledData.size(); ++i) { downsampledClusters[i] = downsamledData[i]; } clusterData = downsampledClusters.data(); } stageString = "statistics"; vector nodeLists[2]; for (int i = 0; i < numNodes; ++i) { int label = (int)floor(clusterData[i] + 0.5f); CaretAssert(label < 3); if (label > 0) nodeLists[label - 1].push_back(i); } vector orderedNodeLists[2]; if (nodeLists[0].size() >= nodeLists[1].size()) { orderedNodeLists[0] = nodeLists[0]; orderedNodeLists[1] = nodeLists[1]; } else { orderedNodeLists[0] = nodeLists[1]; orderedNodeLists[1] = nodeLists[0]; } if (inputData.m_borderPair.size() == 2) { vector insideBorderNodes; AlgorithmNodesInsideBorder(NULL, computeSurf, inputData.m_borderPair[0], false, insideBorderNodes); vector insideLookup(numNodes, 0); for (int i = 0; i < (int)insideBorderNodes.size(); ++i) { insideLookup[insideBorderNodes[i]] = 1; } int counts[2] = {0, 0}; for (int whichList = 0; whichList < 2; ++whichList) { for (int i = 0; i < (int)orderedNodeLists[whichList].size(); ++i) { if (insideLookup[orderedNodeLists[whichList][i]] != 0) { ++counts[whichList]; } } } if (counts[0] >= counts[1]) { statisticsInformationOut += inputData.m_borderPair[0]->getName() + " n=" + AString::number(orderedNodeLists[0].size()) + ", " + inputData.m_borderPair[1]->getName() + " n=" + AString::number(orderedNodeLists[1].size()) + "\n\n"; } else { statisticsInformationOut += inputData.m_borderPair[1]->getName() + " n=" + AString::number(orderedNodeLists[0].size()) + ", " + inputData.m_borderPair[0]->getName() + " n=" + AString::number(orderedNodeLists[1].size()) + "\n\n"; } if (orderedNodeLists[0].size() < 2 || orderedNodeLists[1].size() < 2) { statisticsInformationOut += "roi pieces are too small for statistics"; } else { for (int i = 0; i < numInputs; ++i) { EventProgressUpdate tempEvent(0, PROGRESS_MAX, SEGMENT_PROGRESS + COMPUTE_PROGRESS + HELPER_PROGRESS + DRAW_PROGRESS + (STATISTICS_PROGRESS * i) / numInputs, "computing statistics on file '" + inputData.m_dataFileInfo[i].m_mapFile->getFileNameNoPath() + "'"); EventManager::get()->sendEvent(&tempEvent); if (tempEvent.isCancelled()) { errorMessageOut = "cancelled by user"; return false; } if (inputData.m_dataFileInfo[i].m_allMapsFlag) { for (int j = 0; j < inputData.m_dataFileInfo[i].m_mapFile->getNumberOfMaps(); ++j) { AString statsOut; if(getStatisticsString(inputData.m_dataFileInfo[i].m_mapFile, j, orderedNodeLists, *computeSurf, correctedAreasMetric, inputData.m_dataFileInfo[i].m_corrGradExcludeDist, statsOut)) { statisticsInformationOut += statsOut + ": " + inputData.m_dataFileInfo[i].m_mapFile->getMapName(inputData.m_dataFileInfo[i].m_mapIndex) + ", " + inputData.m_dataFileInfo[i].m_mapFile->getFileNameNoPath() + ", " + FileInformation(inputData.m_dataFileInfo[i].m_mapFile->getFileName()).getLastDirectory() + "\n"; } } statisticsInformationOut += "\n"; } else { AString statsOut; if(getStatisticsString(inputData.m_dataFileInfo[i].m_mapFile, inputData.m_dataFileInfo[i].m_mapIndex, orderedNodeLists, *computeSurf, correctedAreasMetric, inputData.m_dataFileInfo[i].m_corrGradExcludeDist, statsOut)) { statisticsInformationOut += statsOut + ": " + inputData.m_dataFileInfo[i].m_mapFile->getMapName(inputData.m_dataFileInfo[i].m_mapIndex) + ", " + inputData.m_dataFileInfo[i].m_mapFile->getFileNameNoPath() + ", " + FileInformation(inputData.m_dataFileInfo[i].m_mapFile->getFileName()).getLastDirectory() + "\n\n"; } } } } } } stageString = "border replacing"; for (int i = 0; i < numBorders; ++i)//now replace the borders with the modified ones { inputData.m_borders[i]->replacePointsWithUndoSaving(&modifiedBorders[i]); } if (inputData.m_saveResults) { saveResults(inputData, statisticsInformationOut); } /* * Modifying a border: * * (1) Make a copy of the border * * Border* borderCopy = new Border(*border) * * (2) Modify the 'copied' border * * (3) When modification is complete, calling this method * will first make an 'undo' copy of 'border' that is stored * inside of border and then replace the points in 'border' * with those from 'borderCopy'. This will allow the * user to press the Border ToolBar's 'Undo Finish' button * if the changes are not acceptable. * * border->replacePointsWithUndoSaving(borderCopy) */ } catch (CaretException& e) {//all exceptions are errors errorMessageOut = "Caught exception during " + stageString + " stage: " + e.whatString(); return false; } return true; } void BorderOptimizeExecutor::saveResults(const BorderOptimizeExecutor::InputData& inputData, const AString& statisticsInformation) { AString completeBase = FileInformation(inputData.m_savingPath, inputData.m_savingBaseName).getAbsoluteFilePath(); BorderFile tempOutBorder; tempOutBorder.setNumberOfNodes(inputData.m_surface->getNumberOfNodes()); tempOutBorder.addBorder(new Border(*(inputData.m_borderEnclosingROI))); tempOutBorder.writeFile(completeBase + ".border"); if (inputData.m_combinedGradientDataOut != NULL) { inputData.m_combinedGradientDataOut->writeFile(completeBase + ".func.gii"); } TextFile textOut;//because QT's paths use non-native separators on windows textOut.replaceText(statisticsInformation); textOut.addLine("\n\nBorders chosen to optimize:"); for (std::vector::const_iterator bi = inputData.m_borders.begin(); bi != inputData.m_borders.end(); bi++) { textOut.addLine((*bi)->getName()); } textOut.addLine("\nBorder pair for statistics:"); for (std::vector::const_iterator bi = inputData.m_borderPair.begin(); bi != inputData.m_borderPair.end(); bi++) { textOut.addLine((*bi)->getName()); } textOut.addLine("\nOptimizing Surface: " + inputData.m_surface->getFileName()); textOut.addLine("\nData Files used:"); for (std::vector::const_iterator fi = inputData.m_dataFileInfo.begin(); fi != inputData.m_dataFileInfo.end(); fi++) { const DataFileInfo& dfi = *fi; textOut.addLine("\n" + dfi.m_mapFile->getFileName()); if (dfi.m_allMapsFlag) { textOut.addLine(" Map: All Maps"); } else { textOut.addLine(" Map: " + AString::number(dfi.m_mapIndex) + ", " + dfi.m_mapFile->getMapName(dfi.m_mapIndex)); } textOut.addLine(" Strength: " + AString::number(dfi.m_weight)); textOut.addLine(" Smoothing: " + AString::number(dfi.m_smoothing)); textOut.addLine(" Invert Gradient: " + AString::fromBool(dfi.m_invertGradientFlag)); } if (inputData.m_vertexAreasMetricFile != NULL) textOut.addLine("\nVertex Areas Metric File:\n" + inputData.m_vertexAreasMetricFile->getFileName()); if (inputData.m_upsamplingSphericalSurface != NULL) { textOut.addLine("\nUpsampling Sphere: " + inputData.m_upsamplingSphericalSurface->getFileName()); textOut.addLine("Upsampling Resolution: " + AString::number(inputData.m_upsamplingResolution)); } textOut.addLine("\nGradient Following Strength: " + AString::number(inputData.m_gradientFollowingStrength)); textOut.writeFile(completeBase + ".txt"); } /** * Run the border optimization algorithm. * * @param inputData * Input data for the algorithm. */ void BorderOptimizeExecutor::printInputs(const InputData& inputData) { std::cout << "Optimizing borders: " << std::endl; for (std::vector::const_iterator bi = inputData.m_borders.begin(); bi != inputData.m_borders.end(); bi++) { std::cout << " " << qPrintable((*bi)->getName()) << std::endl; } std::cout << "Border pair: " << std::endl; for (std::vector::const_iterator bi = inputData.m_borderPair.begin(); bi != inputData.m_borderPair.end(); bi++) { std::cout << " " << qPrintable((*bi)->getName()) << std::endl; } std::cout << "Optimizing Surface: " << qPrintable(inputData.m_surface->getFileNameNoPath()) << std::endl; std::cout << "Number of nodes in ROI: " << qPrintable(AString::number(inputData.m_nodesInsideROI.size())) << std::endl; std::cout << "Optimizing Data Files: " << std::endl; for (std::vector::const_iterator fi = inputData.m_dataFileInfo.begin(); fi != inputData.m_dataFileInfo.end(); fi++) { const DataFileInfo& dfi = *fi; std::cout << " Name: " << qPrintable(dfi.m_mapFile->getFileNameNoPath()) << std::endl; if (dfi.m_allMapsFlag) { std::cout << " Map: All Maps" << std::endl; } else { std::cout << " Map: " << qPrintable(AString::number(dfi.m_mapIndex)) << " " << qPrintable(dfi.m_mapFile->getMapName(dfi.m_mapIndex)) << std::endl; } std::cout << " Strength: " << dfi.m_weight << std::endl; std::cout << " Smoothing: " << dfi.m_smoothing << std::endl; std::cout << " Invert Gradient: " << AString::fromBool(dfi.m_invertGradientFlag) << std::endl; } std::cout << "Vertex Areas Metric File: " << ((inputData.m_vertexAreasMetricFile != NULL) ? qPrintable(inputData.m_vertexAreasMetricFile->getFileNameNoPath()) : "NULL") << std::endl; if (inputData.m_upsamplingSphericalSurface != NULL) { std::cout << "Upsampling Sphere: " << qPrintable(inputData.m_upsamplingSphericalSurface->getFileNameNoPath()) << std::endl; std::cout << "Upsampling Resolution: " << qPrintable(QString::number(inputData.m_upsamplingResolution)) << std::endl; } std::cout << "Gradient Following Strength: " << inputData.m_gradientFollowingStrength << std::endl; } workbench-1.1.1/src/GuiQt/BorderOptimizeExecutor.h000066400000000000000000000125251255417355300221610ustar00rootroot00000000000000#ifndef __BORDER_OPTIMIZE_EXECUTOR_H__ #define __BORDER_OPTIMIZE_EXECUTOR_H__ /*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "CaretPointer.h" namespace caret { class Border; class CaretMappableDataFile; class MetricFile; class Surface; class BorderOptimizeExecutor : public CaretObject { public: /** * Info about the data files */ struct DataFileInfo { DataFileInfo(const CaretMappableDataFile* mapFile, const int32_t mapIndex, const bool allMapsFlag, const float smoothing, const float weight, const bool invertGradientFlag, const bool& skipGradient, const float& corrGradExcludeDist) : m_mapFile(mapFile), m_mapIndex(mapIndex), m_allMapsFlag(allMapsFlag), m_smoothing(smoothing), m_weight(weight), m_invertGradientFlag(invertGradientFlag), m_skipGradient(skipGradient), m_corrGradExcludeDist(corrGradExcludeDist) { } const CaretMappableDataFile* m_mapFile; int32_t m_mapIndex; bool m_allMapsFlag; float m_smoothing; float m_weight; bool m_invertGradientFlag; bool m_skipGradient; float m_corrGradExcludeDist; }; /** * Update data for the algorithm */ struct InputData { InputData(std::vector borders, std::vector borderPair, const Border* borderEnclosingROI, const std::vector& nodesInsideROI, Surface* surface, const std::vector& dataFileInfo, const MetricFile* vertexAreasMetricFile, const float& gradientFollowingStrength, Surface* upsamplingSphericalSurface, const int32_t upsamplingResolution, MetricFile* combinedGradientDataOut, bool saveResults, const AString& savingPath, const AString& savingBaseName) : m_borders(borders), m_borderPair(borderPair), m_borderEnclosingROI(borderEnclosingROI), m_nodesInsideROI(nodesInsideROI), m_surface(surface), m_dataFileInfo(dataFileInfo), m_vertexAreasMetricFile(vertexAreasMetricFile), m_gradientFollowingStrength(gradientFollowingStrength), m_upsamplingSphericalSurface(upsamplingSphericalSurface), m_upsamplingResolution(upsamplingResolution), m_combinedGradientDataOut(combinedGradientDataOut), m_saveResults(saveResults), m_savingPath(savingPath), m_savingBaseName(savingBaseName) { } std::vector m_borders; std::vector m_borderPair; const Border* m_borderEnclosingROI; const std::vector& m_nodesInsideROI; Surface* m_surface; const std::vector& m_dataFileInfo; const MetricFile* m_vertexAreasMetricFile; const float m_gradientFollowingStrength; Surface* m_upsamplingSphericalSurface; const int32_t m_upsamplingResolution; MetricFile* m_combinedGradientDataOut; bool m_saveResults; AString m_savingPath, m_savingBaseName; }; BorderOptimizeExecutor(); virtual ~BorderOptimizeExecutor(); static void printInputs(const InputData& inputData); static bool run(const InputData& inputData, AString& statisticsInformationOut, AString& errorMessageOut); static void saveResults(const InputData& inputData, const AString& statisticsInformation); private: BorderOptimizeExecutor(const BorderOptimizeExecutor&); BorderOptimizeExecutor& operator=(const BorderOptimizeExecutor&); // ADD_NEW_MEMBERS_HERE }; #ifdef __BORDER_OPTIMIZE_EXECUTOR_DECLARE__ // #endif // __BORDER_OPTIMIZE_EXECUTOR_DECLARE__ } // namespace #endif //__BORDER_OPTIMIZE_EXECUTOR_H__ workbench-1.1.1/src/GuiQt/BorderPropertiesEditorDialog.cxx000066400000000000000000000565241255417355300236470ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #include #include #include #include #define __BORDER_PROPERTIES_EDITOR_DIALOG__DECLARE__ #include "BorderPropertiesEditorDialog.h" #undef __BORDER_PROPERTIES_EDITOR_DIALOG__DECLARE__ #include "Brain.h" #include "Border.h" #include "BorderFile.h" #include "CaretAssert.h" #include "CaretFileDialog.h" #include "EventDataFileAdd.h" #include "EventManager.h" #include "GroupAndNameHierarchyModel.h" #include "GiftiLabel.h" #include "GiftiLabelTable.h" #include "GiftiLabelTableEditor.h" #include "GiftiLabelTableSelectionComboBox.h" #include "GuiManager.h" #include "SurfaceFile.h" #include "WuQDataEntryDialog.h" #include "WuQMessageBox.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::BorderPropertiesEditorDialog * \brief Dialog that queries user to finish drawing of a border. * * This dialog is displayed when the user finishes drawing a * border. It allows the user to select the border file, * enter the border name, select the color, enter the class, * set the type of border (open/closed), and * possibly other attributes of the border. */ /** * Create a new instance of the border properties editor for finishing * a border using a drawing mode. * * @param border * Border that was drawn. * @param parent * Parent on which this dialog is shown. * @return * Dialog that will finish the border. * Users MUST DELETE the returned dialog. */ BorderPropertiesEditorDialog* BorderPropertiesEditorDialog::newInstanceFinishBorder(Border* border, SurfaceFile* surfaceFile, QWidget* parent) { CaretAssert(surfaceFile); BorderPropertiesEditorDialog* dialog = new BorderPropertiesEditorDialog("Finish Border", surfaceFile, BorderPropertiesEditorDialog::MODE_FINISH_DRAWING, NULL, border, parent); return dialog; } /** * Create a new instance of the border properties editor for editing * a border properties. * * @param editModeBorderFile * Border file containing the border that is being edited. * @param border * Border that is to be edited. * @param parent * Parent on which this dialog is shown. * @return * Dialog that will finish the border. * Users MUST DELETE the returned dialog. */ BorderPropertiesEditorDialog* BorderPropertiesEditorDialog::newInstanceEditBorder(BorderFile* editModeBorderFile, Border* border, QWidget* parent) { BorderPropertiesEditorDialog* dialog = new BorderPropertiesEditorDialog("Edit Border Properties", NULL, BorderPropertiesEditorDialog::MODE_EDIT, editModeBorderFile, border, parent); return dialog; } /** * Constructor. */ BorderPropertiesEditorDialog::BorderPropertiesEditorDialog(const QString& title, SurfaceFile* finishBorderSurfaceFile, Mode mode, BorderFile* editModeBorderFile, Border* border, QWidget* parent) : WuQDialogModal(title, parent) { CaretAssert(border); m_finishBorderSurfaceFile = finishBorderSurfaceFile; m_editModeBorderFile = editModeBorderFile; m_mode = mode; m_border = border; m_classComboBox = NULL; QString borderName = border->getName(); QString className = border->getClassName(); switch (m_mode) { case MODE_EDIT: break; case MODE_FINISH_DRAWING: if (s_previousDataValid) { borderName = s_previousName; className = s_previousClassName; } break; } /* * File selection combo box */ QLabel* borderFileLabel = new QLabel("Border File"); m_borderFileSelectionComboBox = new QComboBox(); WuQtUtilities::setToolTipAndStatusTip(m_borderFileSelectionComboBox, "Selects an existing border file\n" "to which new borders are added."); QObject::connect(m_borderFileSelectionComboBox, SIGNAL(activated(int)), this, SLOT(borderFileSelected())); QAction* newFileAction = WuQtUtilities::createAction("New", "Create a new border file", this, this, SLOT(newBorderFileButtonClicked())); QToolButton* newFileToolButton = new QToolButton(); newFileToolButton->setDefaultAction(newFileAction); /* * Completer for name */ m_nameCompleterStringListModel = new QStringListModel(this); /* * Name */ QLabel* nameLabel = new QLabel("Name"); m_nameComboBox = new GiftiLabelTableSelectionComboBox(this); m_nameComboBox->setUnassignedLabelTextOverride("Select Name"); // m_nameLineEdit->setText(borderName); QAction* displayNameColorEditorAction = WuQtUtilities::createAction("Add/Edit...", "Add and/or edit name colors", this, this, SLOT(displayNameEditor())); QToolButton* displayNameColorEditorToolButton = new QToolButton(); displayNameColorEditorToolButton->setDefaultAction(displayNameColorEditorAction); /* * Class */ QLabel* classLabel = new QLabel("Class"); m_classComboBox = new GiftiLabelTableSelectionComboBox(this); WuQtUtilities::setToolTipAndStatusTip(m_classComboBox->getWidget(), "The class is used to group borders with similar\n" "characteristics. Controls are available to\n" "display borders by their class attributes."); QAction* displayClassEditorAction = WuQtUtilities::createAction("Add/Edit...", "Add and/or edit classes", this, this, SLOT(displayClassEditor())); QToolButton* displayClassEditorToolButton = new QToolButton(); displayClassEditorToolButton->setDefaultAction(displayClassEditorAction); /* * Closed */ m_closedCheckBox = new QCheckBox("Closed Border"); WuQtUtilities::setToolTipAndStatusTip(m_closedCheckBox, "If checked, additional points will be added\n" "to the border so that the border forms a loop\n" "with the last point adjacent to the first point."); if (s_previousClosedSelected) { m_closedCheckBox->setChecked(true); } else { m_closedCheckBox->setChecked(false); } /* * Reverse point order */ m_reversePointOrderCheckBox = new QCheckBox("Reverse Point Order"); WuQtUtilities::setToolTipAndStatusTip(m_reversePointOrderCheckBox, "If checked, the order of the points in the \n" "border are reversed when the OK button is pressed."); /* * Layout widgets */ QWidget* widget = new QWidget(); QGridLayout* gridLayout = new QGridLayout(widget); int row = 0; gridLayout->addWidget(borderFileLabel, row, 0); gridLayout->addWidget(m_borderFileSelectionComboBox, row, 1); gridLayout->addWidget(newFileToolButton, row, 2); row++; gridLayout->addWidget(nameLabel, row, 0); gridLayout->addWidget(m_nameComboBox->getWidget(), row, 1); gridLayout->addWidget(displayNameColorEditorToolButton, row, 2); row++; gridLayout->addWidget(classLabel, row, 0); gridLayout->addWidget(m_classComboBox->getWidget(), row, 1); gridLayout->addWidget(displayClassEditorToolButton, row, 2); row++; gridLayout->addWidget(WuQtUtilities::createHorizontalLineWidget(), row, 0, 1, 3, Qt::AlignLeft); row++; gridLayout->addWidget(m_closedCheckBox, row, 0, 1, 3, Qt::AlignLeft); row++; gridLayout->addWidget(m_reversePointOrderCheckBox, row, 0, 1, 3, Qt::AlignLeft); /* * Show/Hide options based upon mode */ bool showFileOptionFlag = false; bool showClosedOptionFlag = false; bool showReverseOptionFlag = false; switch (m_mode) { case MODE_EDIT: showReverseOptionFlag = true; break; case MODE_FINISH_DRAWING: showFileOptionFlag = true; showClosedOptionFlag = true; break; } borderFileLabel->setVisible(showFileOptionFlag); m_borderFileSelectionComboBox->setVisible(showFileOptionFlag); newFileToolButton->setVisible(showFileOptionFlag); m_closedCheckBox->setVisible(showClosedOptionFlag); m_reversePointOrderCheckBox->setVisible(showReverseOptionFlag); loadBorderFileComboBox(); loadNameComboBox(borderName); loadClassComboBox(className); /* * Set the widget for the dialog. */ setCentralWidget(widget, WuQDialog::SCROLL_AREA_NEVER); } /** * Destructor. */ BorderPropertiesEditorDialog::~BorderPropertiesEditorDialog() { } /** * Get the selected border file. * @return BorderFile or NULL if no border file. */ BorderFile* BorderPropertiesEditorDialog::getSelectedBorderFile() { if (m_editModeBorderFile != NULL) { return m_editModeBorderFile; } const int fileComboBoxIndex = m_borderFileSelectionComboBox->currentIndex(); void* filePointer = m_borderFileSelectionComboBox->itemData(fileComboBoxIndex).value(); BorderFile* borderFile = (BorderFile*)filePointer; s_previousBorderFile = borderFile; return borderFile; } /** * Load the SINGLE STRUCTURE border files into the border file combo box. */ void BorderPropertiesEditorDialog::loadBorderFileComboBox() { CaretAssert(m_border); const StructureEnum::Enum borderStructure = m_border->getStructure(); Brain* brain = GuiManager::get()->getBrain(); const int32_t numBorderFiles = brain->getNumberOfBorderFiles(); m_borderFileSelectionComboBox->clear(); int defaultFileComboIndex = 0; for (int32_t i = 0; i < numBorderFiles; i++) { BorderFile* borderFile = brain->getBorderFile(i); if (borderFile->isSingleStructure()) { if (borderFile->getStructure() == borderStructure) { const AString name = borderFile->getFileNameNoPath(); m_borderFileSelectionComboBox->addItem(name, qVariantFromValue((void*)borderFile)); if (s_previousBorderFile == borderFile) { defaultFileComboIndex = m_borderFileSelectionComboBox->count() - 1; } } } } if (m_borderFileSelectionComboBox->count() > 0) { m_borderFileSelectionComboBox->setCurrentIndex(defaultFileComboIndex); const BorderFile* borderFile = getSelectedBorderFile(); if (borderFile != NULL) { m_nameCompleterStringList = borderFile->getAllBorderNamesSorted(); m_nameCompleterStringListModel->setStringList(m_nameCompleterStringList); } } } /** * Called to create a new border file. */ void BorderPropertiesEditorDialog::newBorderFileButtonClicked() { CaretAssert(m_border); const StructureEnum::Enum borderStructure = m_border->getStructure(); if (StructureEnum::isSingleStructure(borderStructure)) { CaretAssert(m_finishBorderSurfaceFile); /* * Let user choose a different path/name */ BorderFile* newBorderFile = new BorderFile(); GuiManager::get()->getBrain()->convertDataFilePathNameToAbsolutePathName(newBorderFile); AString newBorderFileName = CaretFileDialog::getSaveFileNameDialog(DataFileTypeEnum::BORDER, this, "Choose Border File Name", newBorderFile->getFileName()); /* * If user cancels, delete the new border file and return */ if (newBorderFileName.isEmpty()) { delete newBorderFile; return; } /* * Set name of new border file along with structure and number of nodes */ newBorderFile->setFileName(newBorderFileName); newBorderFile->setStructure(borderStructure); newBorderFile->setNumberOfNodes(m_finishBorderSurfaceFile->getNumberOfNodes()); EventManager::get()->sendEvent(EventDataFileAdd(newBorderFile).getPointer()); s_previousBorderFile = newBorderFile; loadBorderFileComboBox(); borderFileSelected(); } else { WuQMessageBox::errorOk(this, ("Border must be for a single structure but it is for " + StructureEnum::toGuiName(borderStructure))); } } /** * Called when a border file is selected. */ void BorderPropertiesEditorDialog::borderFileSelected() { loadNameComboBox(); if (m_classComboBox != NULL) { loadClassComboBox(); } } /** * Load the class combo box. * * @param name * If not empty, make this name the selected name. */ void BorderPropertiesEditorDialog::loadClassComboBox(const QString& name) { BorderFile* borderFile = getSelectedBorderFile(); if (borderFile != NULL) { m_classComboBox->updateContent(borderFile->getClassColorTable()); if (name.isEmpty() == false) { m_classComboBox->setSelectedLabelName(name); } } else { m_classComboBox->updateContent(NULL); } } /** * Load the name combo box. * * @param name * If not empty, make this name the selected name. */ void BorderPropertiesEditorDialog::loadNameComboBox(const QString& name) { BorderFile* borderFile = getSelectedBorderFile(); if (borderFile != NULL) { m_nameComboBox->updateContent(borderFile->getNameColorTable()); if (name.isEmpty() == false) { m_nameComboBox->setSelectedLabelName(name); } } else { m_nameComboBox->updateContent(NULL); } } /** * Called when the OK button is pressed. */ void BorderPropertiesEditorDialog::okButtonClicked() { AString errorMessage; /* * Get border file. */ BorderFile* borderFile = getSelectedBorderFile(); if (borderFile == NULL) { WuQMessageBox::errorOk(this, "Border file is not valid, use the New button to create a border file."); return; } /* * Get data entered by the user. */ const AString name = m_nameComboBox->getSelectedLabelName(); if (name.isEmpty()) { errorMessage += ("Name is invalid.\n"); } else { const int32_t unassignedNameKey = borderFile->getNameColorTable()->getUnassignedLabelKey(); const int32_t selectedNameKey = m_nameComboBox->getSelectedLabelKey(); if (selectedNameKey == unassignedNameKey) { errorMessage += "Choose or create a name for the border"; } } const QString className = m_classComboBox->getSelectedLabelName(); /* * Error? */ if (errorMessage.isEmpty() == false) { WuQMessageBox::errorOk(this, errorMessage); return; } Border* borderBeingEdited = NULL; bool finishModeFlag = false; switch (m_mode) { case MODE_EDIT: borderBeingEdited = m_border; break; case MODE_FINISH_DRAWING: borderBeingEdited = new Border(*m_border); finishModeFlag = true; break; } /* * Make a copy of the border being drawn */ borderBeingEdited->setName(name); borderBeingEdited->setClassName(className); if (finishModeFlag) { /* * Close border */ if (m_closedCheckBox->isChecked()) { borderBeingEdited->addPointsToCloseBorderWithGeodesic(m_finishBorderSurfaceFile); borderBeingEdited->setClosed(true); } else { borderBeingEdited->setClosed(false); } /* * Add border to border file */ CaretAssert(borderFile); borderFile->addBorder(borderBeingEdited); /* * Save values entered by the user and * use them to initialize the dialog next * time it is displayed. */ s_previousDataValid = true; s_previousName = name; s_previousClassName = className; s_previousClosedSelected = m_closedCheckBox->isChecked(); s_previousBorderFile = borderFile; } else { if (m_reversePointOrderCheckBox->isChecked()) { m_border->reverse(); } } if (m_nameCompleterStringList.contains(name) == false) { m_nameCompleterStringList.append(name); m_nameCompleterStringList.sort(); m_nameCompleterStringListModel->setStringList(m_nameCompleterStringList); } /* * continue with OK button processing */ WuQDialogModal::okButtonClicked(); } /** * Display the class editor */ void BorderPropertiesEditorDialog::displayClassEditor() { BorderFile* borderFile = getSelectedBorderFile(); if (borderFile == NULL) { WuQMessageBox::errorOk(this, "Border file is not valid, use the New button to create a border file."); return; } /* * Need to detect a change in the name class table. * So: * (1) Save the modified status * (2) Clear the modified status * (3) After editing, see if the modified status is on indicating * that the user has made a change to the table * (4) If user modified table, invalidate border colors so that * the borders get the new color(s). * (5) If user DID NOT modify table, restore the modification * status of the table. */ GiftiLabelTable* classTable = borderFile->getClassColorTable(); const bool modifiedStatus = classTable->isModified(); classTable->clearModified(); GiftiLabelTableEditor editor(borderFile, classTable, "Edit Class Attributes", GiftiLabelTableEditor::OPTION_NONE, this); const QString className = m_classComboBox->getSelectedLabelName(); if (className.isEmpty() == false) { editor.selectLabelWithName(className); } const int dialogResult = editor.exec(); loadClassComboBox(); if (dialogResult == GiftiLabelTableEditor::Accepted) { const QString selectedClassName = editor.getLastSelectedLabelName(); if (selectedClassName.isEmpty() == false) { m_classComboBox->setSelectedLabelName(selectedClassName); } } if (classTable->isModified()) { /* * User changed something in the table. */ borderFile->invalidateAllAssignedColors(); } else { if (modifiedStatus) { /* * User did not change anything but need to restore * modified status. */ classTable->setModified(); } } } /** * Display the name editor */ void BorderPropertiesEditorDialog::displayNameEditor() { BorderFile* borderFile = getSelectedBorderFile(); if (borderFile == NULL) { WuQMessageBox::errorOk(this, "Border file is not valid, use the New button to create a border file."); return; } /* * Need to detect a change in the name color table. * So: * (1) Save the modified status * (2) Clear the modified status * (3) After editing, see if the modified status is on indicating * that the user has made a change to the table * (4) If user modified table, invalidate border colors so that * the borders get the new color(s). * (5) If user DID NOT modify table, restore the modification * status of the table. */ GiftiLabelTable* nameTable = borderFile->getNameColorTable(); const bool modifiedStatus = nameTable->isModified(); nameTable->clearModified(); GiftiLabelTableEditor editor(borderFile, nameTable, "Edit Name Attributes", GiftiLabelTableEditor::OPTION_UNASSIGNED_LABEL_HIDDEN, this); const QString name = m_nameComboBox->getSelectedLabelName(); if (name.isEmpty() == false) { editor.selectLabelWithName(name); } const int dialogResult = editor.exec(); loadNameComboBox(); if (dialogResult == GiftiLabelTableEditor::Accepted) { const QString selectedName = editor.getLastSelectedLabelName(); if (selectedName.isEmpty() == false) { m_nameComboBox->setSelectedLabelName(selectedName); } } if (nameTable->isModified()) { /* * User changed something in the table. */ borderFile->invalidateAllAssignedColors(); } else { if (modifiedStatus) { /* * User did not change anything but need to restore * modified status. */ nameTable->setModified(); } } } workbench-1.1.1/src/GuiQt/BorderPropertiesEditorDialog.h000066400000000000000000000103131255417355300232560ustar00rootroot00000000000000#ifndef __BORDER_PROPERTIES_EDITOR_DIALOG__H_ #define __BORDER_PROPERTIES_EDITOR_DIALOG__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "WuQDialogModal.h" class QCheckBox; class QComboBox; class QDoubleSpinBox; class QLabel; class QLineEdit; class QStringListModel; namespace caret { class Border; class BorderFile; class GiftiLabelTableSelectionComboBox; class SurfaceFile; class BorderPropertiesEditorDialog : public WuQDialogModal { Q_OBJECT public: static BorderPropertiesEditorDialog* newInstanceFinishBorder(Border* border, SurfaceFile* surfaceFile, QWidget* parent = 0); static BorderPropertiesEditorDialog* newInstanceEditBorder(BorderFile* editModeBorderFile, Border* border, QWidget* parent = 0); virtual ~BorderPropertiesEditorDialog(); protected: virtual void okButtonClicked(); private slots: void displayNameEditor(); void displayClassEditor(); void borderFileSelected(); void newBorderFileButtonClicked(); private: enum Mode { MODE_EDIT, MODE_FINISH_DRAWING }; BorderPropertiesEditorDialog(const QString& title, SurfaceFile* finishBorderSurfaceFile, const Mode mode, BorderFile* editModeBorderFile, Border* border, QWidget* parent = 0); BorderPropertiesEditorDialog(const BorderPropertiesEditorDialog&); BorderPropertiesEditorDialog& operator=(const BorderPropertiesEditorDialog&); BorderFile* getSelectedBorderFile(); void loadBorderFileComboBox(); void loadNameComboBox(const QString& name = ""); void loadClassComboBox(const QString& className = ""); Mode m_mode; BorderFile* m_editModeBorderFile; Border* m_border; QComboBox* m_borderFileSelectionComboBox; GiftiLabelTableSelectionComboBox* m_nameComboBox; QStringList m_nameCompleterStringList; QStringListModel* m_nameCompleterStringListModel; QCheckBox* m_closedCheckBox; GiftiLabelTableSelectionComboBox* m_classComboBox; QCheckBox* m_reversePointOrderCheckBox; SurfaceFile* m_finishBorderSurfaceFile; static bool s_previousDataValid; static AString s_previousName; static BorderFile* s_previousBorderFile; static bool s_previousClosedSelected; static AString s_previousClassName; }; #ifdef __BORDER_PROPERTIES_EDITOR_DIALOG__DECLARE__ bool BorderPropertiesEditorDialog::s_previousDataValid = false; AString BorderPropertiesEditorDialog::s_previousName = "Name"; BorderFile* BorderPropertiesEditorDialog::s_previousBorderFile = NULL; bool BorderPropertiesEditorDialog::s_previousClosedSelected = false; AString BorderPropertiesEditorDialog::s_previousClassName = "None"; #endif // __BORDER_PROPERTIES_EDITOR_DIALOG__DECLARE__ } // namespace #endif //__BORDER_PROPERTIES_EDITOR_DIALOG__H_ workbench-1.1.1/src/GuiQt/BorderSelectionViewController.cxx000066400000000000000000000507021255417355300240400ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #include #include //#include #include #define __BORDER_SELECTION_VIEW_CONTROLLER_DECLARE__ #include "BorderSelectionViewController.h" #undef __BORDER_SELECTION_VIEW_CONTROLLER_DECLARE__ #include "BorderDrawingTypeEnum.h" #include "Brain.h" #include "BrainOpenGL.h" #include "BrowserTabContent.h" #include "CaretAssert.h" #include "GroupAndNameHierarchyViewController.h" #include "DisplayGroupEnumComboBox.h" #include "DisplayPropertiesBorders.h" #include "EnumComboBoxTemplate.h" #include "EventGraphicsUpdateAllWindows.h" #include "EventManager.h" #include "EventUserInterfaceUpdate.h" #include "GuiManager.h" #include "SceneClass.h" #include "WuQDataEntryDialog.h" #include "WuQFactory.h" #include "WuQTabWidget.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::BorderSelectionViewController * \brief Widget for controlling display of borders * * Widget for controlling the display of borders including * different display groups. */ /** * Constructor. */ BorderSelectionViewController::BorderSelectionViewController(const int32_t browserWindowIndex, QWidget* parent) : QWidget(parent) { m_browserWindowIndex = browserWindowIndex; QLabel* groupLabel = new QLabel("Group"); m_bordersDisplayGroupComboBox = new DisplayGroupEnumComboBox(this); QObject::connect(m_bordersDisplayGroupComboBox, SIGNAL(displayGroupSelected(const DisplayGroupEnum::Enum)), this, SLOT(borderDisplayGroupSelected(const DisplayGroupEnum::Enum))); QHBoxLayout* groupLayout = new QHBoxLayout(); groupLayout->addWidget(groupLabel); groupLayout->addWidget(m_bordersDisplayGroupComboBox->getWidget()); groupLayout->addStretch(); m_bordersDisplayCheckBox = new QCheckBox("Display Borders"); QObject::connect(m_bordersDisplayCheckBox, SIGNAL(clicked(bool)), this, SLOT(processAttributesChanges())); QWidget* attributesWidget = this->createAttributesWidget(); QWidget* selectionWidget = this->createSelectionWidget(); m_tabWidget = new WuQTabWidget(WuQTabWidget::TAB_ALIGN_LEFT, this); m_tabWidget->addTab(attributesWidget, "Attributes"); m_tabWidget->addTab(selectionWidget, "Selection"); m_tabWidget->setCurrentWidget(attributesWidget); QVBoxLayout* layout = new QVBoxLayout(this); WuQtUtilities::setLayoutSpacingAndMargins(layout, 2, 2); layout->addLayout(groupLayout); layout->addSpacing(10); layout->addWidget(m_bordersDisplayCheckBox); layout->addWidget(m_tabWidget->getWidget(), 0, Qt::AlignLeft); layout->addStretch(); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_USER_INTERFACE_UPDATE); BorderSelectionViewController::allBorderSelectionViewControllers.insert(this); } /** * Destructor. */ BorderSelectionViewController::~BorderSelectionViewController() { EventManager::get()->removeAllEventsFromListener(this); BorderSelectionViewController::allBorderSelectionViewControllers.erase(this); } QWidget* BorderSelectionViewController::createSelectionWidget() { m_borderClassNameHierarchyViewController = new GroupAndNameHierarchyViewController(m_browserWindowIndex); return m_borderClassNameHierarchyViewController; } /** * @return The attributes widget. */ QWidget* BorderSelectionViewController::createAttributesWidget() { m_bordersContralateralCheckBox = new QCheckBox("Contralateral"); QObject::connect(m_bordersContralateralCheckBox, SIGNAL(clicked(bool)), this, SLOT(processAttributesChanges())); std::vector drawingTypeEnums; BorderDrawingTypeEnum::getAllEnums(drawingTypeEnums); const int32_t numDrawingTypeEnums = static_cast(drawingTypeEnums.size()); QLabel* drawAsLabel = new QLabel("Draw As"); m_drawTypeComboBox = new QComboBox(); for (int32_t i = 0; i < numDrawingTypeEnums; i++) { BorderDrawingTypeEnum::Enum drawType = drawingTypeEnums[i]; m_drawTypeComboBox->addItem(BorderDrawingTypeEnum::toGuiName(drawType), (int)drawType); } m_drawTypeComboBox->setToolTip("Select the drawing style of borders"); QObject::connect(m_drawTypeComboBox, SIGNAL(activated(int)), this, SLOT(processAttributesChanges())); QLabel* coloringLabel = new QLabel("Coloring"); m_coloringTypeComboBox = new EnumComboBoxTemplate(this); m_coloringTypeComboBox->setup(); m_coloringTypeComboBox->getWidget()->setToolTip("Select the coloring assignment for borders"); QObject::connect(m_coloringTypeComboBox, SIGNAL(itemActivated()), this, SLOT(processAttributesChanges())); float minLineWidth = 0; float maxLineWidth = 1000; //BrainOpenGL::getMinMaxLineWidth(minLineWidth, // maxLineWidth); QLabel* lineWidthLabel = new QLabel("Line Diameter"); m_lineWidthSpinBox = WuQFactory::newDoubleSpinBox(); m_lineWidthSpinBox->setFixedWidth(80); m_lineWidthSpinBox->setRange(minLineWidth, maxLineWidth); m_lineWidthSpinBox->setSingleStep(1.0); m_lineWidthSpinBox->setDecimals(1); m_lineWidthSpinBox->setSuffix("mm"); m_lineWidthSpinBox->setToolTip("Adjust the width of borders drawn as lines.\n" "The maximum width is dependent upon the \n" "graphics system. There is no maximum value\n" "for this control and the drawn width of the \n" "lines will stop increasing even though the\n" "value of this control is changing"); QObject::connect(m_lineWidthSpinBox, SIGNAL(valueChanged(double)), this, SLOT(processAttributesChanges())); QLabel* pointSizeLabel = new QLabel("Symbol Diameter"); m_pointSizeSpinBox = WuQFactory::newDoubleSpinBox(); m_pointSizeSpinBox->setFixedWidth(80); m_pointSizeSpinBox->setRange(minLineWidth, maxLineWidth); m_pointSizeSpinBox->setSingleStep(1.0); m_pointSizeSpinBox->setDecimals(1); m_pointSizeSpinBox->setToolTip("Adjust the size of borders drawn as points"); m_pointSizeSpinBox->setSuffix("mm"); QObject::connect(m_pointSizeSpinBox, SIGNAL(valueChanged(double)), this, SLOT(processAttributesChanges())); m_enableUnstretchedLinesCheckBox = new QCheckBox("Unstretched Lines"); QObject::connect(m_enableUnstretchedLinesCheckBox, SIGNAL(clicked(bool)), this, SLOT(processAttributesChanges())); m_unstretchedLinesLengthSpinBox = WuQFactory::newDoubleSpinBox(); m_unstretchedLinesLengthSpinBox->setFixedWidth(80); m_unstretchedLinesLengthSpinBox->setRange(0.0, 10000000.0); m_unstretchedLinesLengthSpinBox->setSingleStep(1.0); m_unstretchedLinesLengthSpinBox->setDecimals(1); m_unstretchedLinesLengthSpinBox->setToolTip(WuQtUtilities::createWordWrappedToolTipText("Ratio = (length of border on flat surface divided by length of border of 3d (primary anatomical) surface. " "When ratio is greater than the unstretched lines value, the border segment is NOT drawn.")); m_unstretchedLinesLengthSpinBox->setSuffix("mm"); QObject::connect(m_unstretchedLinesLengthSpinBox, SIGNAL(valueChanged(double)), this, SLOT(processAttributesChanges())); QWidget* gridWidget = new QWidget(); QGridLayout* gridLayout = new QGridLayout(gridWidget); WuQtUtilities::setLayoutSpacingAndMargins(gridLayout, 8, 2); int row = gridLayout->rowCount(); gridLayout->addWidget(m_bordersContralateralCheckBox, row, 0, 1, 2, Qt::AlignLeft); row++; gridLayout->addWidget(WuQtUtilities::createHorizontalLineWidget(), row, 0, 1, 2); row++; gridLayout->addWidget(drawAsLabel, row, 0); gridLayout->addWidget(m_drawTypeComboBox, row, 1); row++; gridLayout->addWidget(coloringLabel, row, 0); gridLayout->addWidget(m_coloringTypeComboBox->getWidget(), row, 1); row++; gridLayout->addWidget(lineWidthLabel, row, 0); gridLayout->addWidget(m_lineWidthSpinBox, row, 1); row++; gridLayout->addWidget(pointSizeLabel, row, 0); gridLayout->addWidget(m_pointSizeSpinBox, row, 1); row++; gridLayout->addWidget(m_enableUnstretchedLinesCheckBox, row, 0); gridLayout->addWidget(m_unstretchedLinesLengthSpinBox, row, 1); gridWidget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); QWidget* widget = new QWidget(); QVBoxLayout* layout = new QVBoxLayout(widget); layout->addWidget(gridWidget); layout->addStretch(); return widget; } /** * Called when a widget on the attributes page has * its value changed. */ void BorderSelectionViewController::processAttributesChanges() { DisplayPropertiesBorders* dpb = GuiManager::get()->getBrain()->getDisplayPropertiesBorders(); const int selectedDrawTypeIndex = m_drawTypeComboBox->currentIndex(); const int drawTypeInteger = m_drawTypeComboBox->itemData(selectedDrawTypeIndex).toInt(); const BorderDrawingTypeEnum::Enum selectedDrawingType = static_cast(drawTypeInteger); const FeatureColoringTypeEnum::Enum selectedColoringType = m_coloringTypeComboBox->getSelectedItem(); BrowserTabContent* browserTabContent = GuiManager::get()->getBrowserTabContentForBrowserWindow(m_browserWindowIndex, true); if (browserTabContent == NULL) { return; } const int32_t browserTabIndex = browserTabContent->getTabNumber(); const DisplayGroupEnum::Enum displayGroup = dpb->getDisplayGroupForTab(browserTabIndex); dpb->setDisplayed(displayGroup, browserTabIndex, m_bordersDisplayCheckBox->isChecked()); dpb->setContralateralDisplayed(displayGroup, browserTabIndex, m_bordersContralateralCheckBox->isChecked()); dpb->setDrawingType(displayGroup, browserTabIndex, selectedDrawingType); dpb->setColoringType(displayGroup, browserTabIndex, selectedColoringType); dpb->setLineWidth(displayGroup, browserTabIndex, m_lineWidthSpinBox->value()); dpb->setPointSize(displayGroup, browserTabIndex, m_pointSizeSpinBox->value()); dpb->setUnstretchedLinesEnabled(displayGroup, browserTabIndex, m_enableUnstretchedLinesCheckBox->isChecked()); dpb->setUnstretchedLinesLength(displayGroup, browserTabIndex, m_unstretchedLinesLengthSpinBox->value()); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); updateOtherBorderViewControllers(); } /** * Called when the border display group combo box is changed. */ void BorderSelectionViewController::borderDisplayGroupSelected(const DisplayGroupEnum::Enum displayGroup) { /* * Update selected display group in model. */ BrowserTabContent* browserTabContent = GuiManager::get()->getBrowserTabContentForBrowserWindow(m_browserWindowIndex, false); if (browserTabContent == NULL) { return; } const int32_t browserTabIndex = browserTabContent->getTabNumber(); Brain* brain = GuiManager::get()->getBrain(); DisplayPropertiesBorders* dsb = brain->getDisplayPropertiesBorders(); dsb->setDisplayGroupForTab(browserTabIndex, displayGroup); /* * Since display group has changed, need to update controls */ updateBorderViewController(); /* * Apply the changes. */ processBorderSelectionChanges(); } /** * Update the border selection widget. */ void BorderSelectionViewController::updateBorderViewController() { setWindowTitle("Borders"); BrowserTabContent* browserTabContent = GuiManager::get()->getBrowserTabContentForBrowserWindow(m_browserWindowIndex, true); if (browserTabContent == NULL) { return; } const int32_t browserTabIndex = browserTabContent->getTabNumber(); Brain* brain = GuiManager::get()->getBrain(); DisplayPropertiesBorders* dpb = brain->getDisplayPropertiesBorders(); const DisplayGroupEnum::Enum displayGroup = dpb->getDisplayGroupForTab(browserTabIndex); m_bordersDisplayGroupComboBox->setSelectedDisplayGroup(dpb->getDisplayGroupForTab(browserTabIndex)); /*; * Get all of border files. */ std::vector allBorderFiles; const int32_t numberOfBorderFiles = brain->getNumberOfBorderFiles(); for (int32_t ibf= 0; ibf < numberOfBorderFiles; ibf++) { allBorderFiles.push_back(brain->getBorderFile(ibf)); } /* * Update the class/name hierarchy */ m_borderClassNameHierarchyViewController->updateContents(allBorderFiles, displayGroup); std::vector drawingTypeEnums; BorderDrawingTypeEnum::getAllEnums(drawingTypeEnums); const int32_t numDrawingTypeEnums = static_cast(drawingTypeEnums.size()); m_bordersDisplayCheckBox->setChecked(dpb->isDisplayed(displayGroup, browserTabIndex)); m_bordersContralateralCheckBox->setChecked(dpb->isContralateralDisplayed(displayGroup, browserTabIndex)); const BorderDrawingTypeEnum::Enum selectedDrawingType = dpb->getDrawingType(displayGroup, browserTabIndex); int32_t selectedDrawingTypeIndex = 0; for (int32_t i = 0; i < numDrawingTypeEnums; i++) { BorderDrawingTypeEnum::Enum drawType = drawingTypeEnums[i]; if (drawType == selectedDrawingType) { selectedDrawingTypeIndex = i; } } m_drawTypeComboBox->setCurrentIndex(selectedDrawingTypeIndex); m_coloringTypeComboBox->setSelectedItem(dpb->getColoringType(displayGroup, browserTabIndex)); m_lineWidthSpinBox->blockSignals(true); m_lineWidthSpinBox->setValue(dpb->getLineWidth(displayGroup, browserTabIndex)); m_lineWidthSpinBox->blockSignals(false); m_pointSizeSpinBox->blockSignals(true); m_pointSizeSpinBox->setValue(dpb->getPointSize(displayGroup, browserTabIndex)); m_pointSizeSpinBox->blockSignals(false); m_enableUnstretchedLinesCheckBox->setChecked(dpb->isUnstretchedLinesEnabled(displayGroup, browserTabIndex)); m_unstretchedLinesLengthSpinBox->blockSignals(true); m_unstretchedLinesLengthSpinBox->setValue(dpb->getUnstretchedLinesLength(displayGroup, browserTabIndex)); m_unstretchedLinesLengthSpinBox->blockSignals(false); } /** * Update other selection toolbox since they should all be the same. */ void BorderSelectionViewController::updateOtherBorderViewControllers() { for (std::set::iterator iter = BorderSelectionViewController::allBorderSelectionViewControllers.begin(); iter != BorderSelectionViewController::allBorderSelectionViewControllers.end(); iter++) { BorderSelectionViewController* bsw = *iter; if (bsw != this) { bsw->updateBorderViewController(); } } } /** * Gets called when border selections are changed. */ void BorderSelectionViewController::processBorderSelectionChanges() { BrowserTabContent* browserTabContent = GuiManager::get()->getBrowserTabContentForBrowserWindow(m_browserWindowIndex, false); CaretAssert(browserTabContent); const int32_t browserTabIndex = browserTabContent->getTabNumber(); Brain* brain = GuiManager::get()->getBrain(); DisplayPropertiesBorders* dsb = brain->getDisplayPropertiesBorders(); dsb->setDisplayGroupForTab(browserTabIndex, m_bordersDisplayGroupComboBox->getSelectedDisplayGroup()); processSelectionChanges(); } /** * Issue update events after selections are changed. */ void BorderSelectionViewController::processSelectionChanges() { updateOtherBorderViewControllers(); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } /** * Receive events from the event manager. * * @param event * Event sent by event manager. */ void BorderSelectionViewController::receiveEvent(Event* event) { bool doUpdate = false; if (event->getEventType() == EventTypeEnum::EVENT_USER_INTERFACE_UPDATE) { EventUserInterfaceUpdate* uiEvent = dynamic_cast(event); CaretAssert(uiEvent); if (uiEvent->isUpdateForWindow(m_browserWindowIndex)) { if (uiEvent->isBorderUpdate() || uiEvent->isToolBoxUpdate()) { doUpdate = true; uiEvent->setEventProcessed(); } } } if (doUpdate) { updateBorderViewController(); } } /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ SceneClass* BorderSelectionViewController::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "BorderSelectionViewController", 1); sceneClass->addClass(m_tabWidget->saveToScene(sceneAttributes, "m_tabWidget")); return sceneClass; } /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * SceneClass containing the state that was previously * saved and should be restored. */ void BorderSelectionViewController::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } m_tabWidget->restoreFromScene(sceneAttributes, sceneClass->getClass("m_tabWidget")); } workbench-1.1.1/src/GuiQt/BorderSelectionViewController.h000066400000000000000000000072661255417355300234740ustar00rootroot00000000000000#ifndef __BORDER_SELECTION_VIEW_CONTROLLER__H_ #define __BORDER_SELECTION_VIEW_CONTROLLER__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include "DisplayGroupEnum.h" #include "EventListenerInterface.h" #include "SceneableInterface.h" class QCheckBox; class QComboBox; class QDoubleSpinBox; namespace caret { class GroupAndNameHierarchyViewController; class DisplayGroupEnumComboBox; class EnumComboBoxTemplate; class WuQTabWidget; class BorderSelectionViewController : public QWidget, public EventListenerInterface, public SceneableInterface { Q_OBJECT public: BorderSelectionViewController(const int32_t browserWindowIndex, QWidget* parent = 0); virtual ~BorderSelectionViewController(); void receiveEvent(Event* event); virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private slots: void processBorderSelectionChanges(); void processSelectionChanges(); void borderDisplayGroupSelected(const DisplayGroupEnum::Enum); void processAttributesChanges(); private: BorderSelectionViewController(const BorderSelectionViewController&); BorderSelectionViewController& operator=(const BorderSelectionViewController&); void updateBorderViewController(); void updateOtherBorderViewControllers(); QWidget* createSelectionWidget(); QWidget* createAttributesWidget(); int32_t m_browserWindowIndex; GroupAndNameHierarchyViewController* m_borderClassNameHierarchyViewController; QCheckBox* m_bordersDisplayCheckBox; QCheckBox* m_bordersContralateralCheckBox; DisplayGroupEnumComboBox* m_bordersDisplayGroupComboBox; QComboBox* m_drawTypeComboBox; EnumComboBoxTemplate* m_coloringTypeComboBox; QDoubleSpinBox* m_lineWidthSpinBox; QDoubleSpinBox* m_pointSizeSpinBox; QCheckBox* m_enableUnstretchedLinesCheckBox; QDoubleSpinBox* m_unstretchedLinesLengthSpinBox; WuQTabWidget* m_tabWidget; static std::set allBorderSelectionViewControllers; }; #ifdef __BORDER_SELECTION_VIEW_CONTROLLER_DECLARE__ std::set BorderSelectionViewController::allBorderSelectionViewControllers; #endif // __BORDER_SELECTION_VIEW_CONTROLLER_DECLARE__ } // namespace #endif //__BORDER_SELECTION_VIEW_CONTROLLER__H_ workbench-1.1.1/src/GuiQt/BrainBrowserWindow.cxx000066400000000000000000003436121255417355300216520ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #include #include #include #include #include #include #include #define __BRAIN_BROWSER_WINDOW_DECLARE__ #include "BrainBrowserWindow.h" #undef __BRAIN_BROWSER_WINDOW_DECLARE__ #include "AboutWorkbenchDialog.h" #include "ApplicationInformation.h" #include "BorderFile.h" #include "BorderFileSplitDialog.h" #include "Brain.h" #include "BrainBrowserWindowToolBar.h" #include "BrainBrowserWindowOrientedToolBox.h" #include "BrainOpenGLWidget.h" #include "BrainStructure.h" #include "BrowserTabContent.h" #include "CaretAssert.h" #include "CaretFileDialog.h" #include "CaretFileRemoteDialog.h" #include "CaretPreferences.h" #include "CursorDisplayScoped.h" #include "DataFileException.h" #include "DeveloperFlagsEnum.h" #include "DisplayPropertiesVolume.h" #include "EventBrowserWindowNew.h" #include "CaretLogger.h" #include "ElapsedTimer.h" #include "EventBrowserWindowCreateTabs.h" #include "EventDataFileRead.h" #include "EventMacDockMenuUpdate.h" #include "EventManager.h" #include "EventModelGetAll.h" #include "EventGraphicsUpdateAllWindows.h" #include "EventGraphicsUpdateOneWindow.h" #include "EventSpecFileReadDataFiles.h" #include "EventSurfaceColoringInvalidate.h" #include "EventUserInterfaceUpdate.h" #include "FileInformation.h" #include "FociProjectionDialog.h" #include "GuiManager.h" #include "ModelSurface.h" #include "ModelSurfaceMontage.h" #include "ModelWholeBrain.h" #include "PlainTextStringBuilder.h" #include "ProgressReportingDialog.h" #include "SceneAttributes.h" #include "SceneClass.h" #include "SceneClassArray.h" #include "SceneClassAssistant.h" #include "SceneEnumeratedType.h" #include "SceneFile.h" #include "SceneWindowGeometry.h" #include "SessionManager.h" #include "SpecFile.h" #include "SpecFileManagementDialog.h" #include "StructureEnumComboBox.h" #include "Surface.h" #include "SurfaceMontageConfigurationAbstract.h" #include "SurfaceSelectionViewController.h" #include "TileTabsConfiguration.h" #include "WuQDataEntryDialog.h" #include "WuQMessageBox.h" #include "WuQtUtilities.h" #include "VtkFileExporter.h" using namespace caret; /** * Constructor. * * @param browserWindowIndex * Index for this window. * @param browserTabContent * If not NULL, this is the tab displayed in the window. * If NULL, a new tab is created. * @param parent * Parent of this object * @param flags * Flags for Qt. */ BrainBrowserWindow::BrainBrowserWindow(const int browserWindowIndex, BrowserTabContent* browserTabContent, const CreateDefaultTabsMode createDefaultTabsMode, QWidget* parent, Qt::WindowFlags flags) : QMainWindow(parent, flags) { m_developMenuAction = NULL; if (BrainBrowserWindow::s_firstWindowFlag) { BrainBrowserWindow::s_firstWindowFlag = false; } m_viewTileTabsSelected = false; m_sceneTileTabsConfigurationText = "From Scene: "; m_sceneTileTabsConfiguration = new TileTabsConfiguration(); m_sceneTileTabsConfiguration->setName(m_sceneTileTabsConfigurationText); m_defaultTileTabsConfiguration = new TileTabsConfiguration(); m_defaultTileTabsConfiguration->setDefaultConfiguration(true); m_defaultTileTabsConfiguration->setName("All Tabs (Default)"); m_selectedTileTabsConfigurationUniqueIdentifier = m_defaultTileTabsConfiguration->getUniqueIdentifier(); GuiManager* guiManager = GuiManager::get(); setAttribute(Qt::WA_DeleteOnClose); m_browserWindowIndex = browserWindowIndex; setWindowTitle(guiManager->applicationName() + " " + AString::number(m_browserWindowIndex + 1)); setObjectName(windowTitle()); m_openGLWidget = new BrainOpenGLWidget(this, browserWindowIndex); const int openGLSizeX = 500; const int openGLSizeY = (WuQtUtilities::isSmallDisplay() ? 200 : 375); m_openGLWidget->setMinimumSize(openGLSizeX, openGLSizeY); setCentralWidget(m_openGLWidget); m_overlayVerticalToolBox = new BrainBrowserWindowOrientedToolBox(m_browserWindowIndex, "Overlay ToolBox", BrainBrowserWindowOrientedToolBox::TOOL_BOX_OVERLAYS_VERTICAL, this); m_overlayVerticalToolBox->setAllowedAreas(Qt::LeftDockWidgetArea); m_overlayHorizontalToolBox = new BrainBrowserWindowOrientedToolBox(m_browserWindowIndex, "Overlay ToolBox ", BrainBrowserWindowOrientedToolBox::TOOL_BOX_OVERLAYS_HORIZONTAL, this); m_overlayHorizontalToolBox->setAllowedAreas(Qt::BottomDockWidgetArea); if (WuQtUtilities::isSmallDisplay()) { m_overlayActiveToolBox = m_overlayVerticalToolBox; addDockWidget(Qt::LeftDockWidgetArea, m_overlayVerticalToolBox); m_overlayHorizontalToolBox->setVisible(false); //m_overlayHorizontalToolBox->toggleViewAction()->trigger(); } else { m_overlayActiveToolBox = m_overlayHorizontalToolBox; addDockWidget(Qt::BottomDockWidgetArea, m_overlayHorizontalToolBox); m_overlayVerticalToolBox->setVisible(false); //m_overlayVerticalToolBox->toggleViewAction()->trigger(); } QObject::connect(m_overlayHorizontalToolBox, SIGNAL(visibilityChanged(bool)), this, SLOT(processOverlayHorizontalToolBoxVisibilityChanged(bool))); QObject::connect(m_overlayVerticalToolBox, SIGNAL(visibilityChanged(bool)), this, SLOT(processOverlayVerticalToolBoxVisibilityChanged(bool))); m_featuresToolBox = new BrainBrowserWindowOrientedToolBox(m_browserWindowIndex, "Features ToolBox", BrainBrowserWindowOrientedToolBox::TOOL_BOX_FEATURES, this); m_featuresToolBox->setAllowedAreas(Qt::RightDockWidgetArea); addDockWidget(Qt::RightDockWidgetArea, m_featuresToolBox); createActionsUsedByToolBar(); m_overlayToolBoxAction->blockSignals(true); m_overlayToolBoxAction->setChecked(true); m_overlayToolBoxAction->blockSignals(false); m_featuresToolBoxAction->blockSignals(true); m_featuresToolBoxAction->setChecked(true); m_featuresToolBoxAction->blockSignals(false); m_toolbar = new BrainBrowserWindowToolBar(m_browserWindowIndex, browserTabContent, m_overlayToolBoxAction, m_featuresToolBoxAction, this); m_showToolBarAction = m_toolbar->toolBarToolButtonAction; addToolBar(m_toolbar); createActions(); createMenus(); m_toolbar->updateToolBar(); processShowOverlayToolBox(m_overlayToolBoxAction->isChecked()); processHideFeaturesToolBox(); if (browserTabContent == NULL) { if (createDefaultTabsMode == CREATE_DEFAULT_TABS_YES) { m_toolbar->addDefaultTabsAfterLoadingSpecFile(); } } m_sceneAssistant = new SceneClassAssistant(); m_defaultWindowComponentStatus.isFeaturesToolBoxDisplayed = m_featuresToolBoxAction->isChecked(); m_defaultWindowComponentStatus.isOverlayToolBoxDisplayed = m_overlayToolBoxAction->isChecked(); m_defaultWindowComponentStatus.isToolBarDisplayed = m_showToolBarAction->isChecked(); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_BROWSER_WINDOW_MENUS_UPDATE); } /** * Destructor. */ BrainBrowserWindow::~BrainBrowserWindow() { EventManager::get()->removeAllEventsFromListener(this); delete m_defaultTileTabsConfiguration; delete m_sceneTileTabsConfiguration; delete m_sceneAssistant; } /** * Receive an event. * * @param event * The event that the receive can respond to. */ void BrainBrowserWindow::receiveEvent(Event* event) { if (event->getEventType() == EventTypeEnum::EVENT_BROWSER_WINDOW_MENUS_UPDATE) { const CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); if (m_developMenuAction != NULL) { m_developMenuAction->setVisible(prefs->isDevelopMenuEnabled()); event->setEventProcessed(); } } } /** * Reset the graphics window size. * When the window is created, the graphics is set to a reasonable size * by setting the minimum size of the graphics region. If the minimum * size is small, */ void BrainBrowserWindow::resetGraphicsWidgetMinimumSize() { m_openGLWidget->setMinimumSize(100, 100); } void BrainBrowserWindow::setGraphicsWidgetFixedSize(const int32_t width, const int32_t height) { m_openGLWidget->setFixedSize(width, height); } void BrainBrowserWindow::getGraphicsWidgetSize(int32_t& widthOut, int32_t& heightOut) const { widthOut = m_openGLWidget->width(); heightOut = m_openGLWidget->height(); } /** * @return True if tile tabs is selected, else false. */ bool BrainBrowserWindow::isTileTabsSelected() const { return m_viewTileTabsSelected; } /** * @return the index of this browser window. */ int32_t BrainBrowserWindow::getBrowserWindowIndex() const { return m_browserWindowIndex; } /** * Called when the window is requested to close. * * @param event * CloseEvent that may or may not be accepted * allowing the window to close. */ void BrainBrowserWindow::closeEvent(QCloseEvent* event) { if (m_closeWithoutConfirmationFlag) { m_closeWithoutConfirmationFlag = false; event->accept(); return; } /* * The GuiManager may warn user about closing the * window and the user may cancel closing of the window. */ GuiManager* guiManager = GuiManager::get(); if (guiManager->allowBrainBrowserWindowToClose(this, m_toolbar->tabBar->count())) { event->accept(); } else { event->ignore(); } } /** * Called when a key is pressed. * * @param event * The key event. */ void BrainBrowserWindow::keyPressEvent(QKeyEvent* event) { bool keyEventWasProcessed = false; /* * Pressing the Escape Key exits full screen mode. */ if (event->key() == Qt::Key_Escape) { if (event->modifiers() == Qt::NoModifier) { if (isFullScreen()) { processViewFullScreenSelected(); keyEventWasProcessed = true; } } } /* * According to the documentation, if the key event was not acted upon, * pass it on the base class implementation. */ if ( ! keyEventWasProcessed) { QMainWindow::keyPressEvent(event); } } /** * Create actions for this window. * NOTE: This is called BEFORE the toolbar is created. */ void BrainBrowserWindow::createActionsUsedByToolBar() { QIcon featuresToolBoxIcon; const bool featuresToolBoxIconValid = WuQtUtilities::loadIcon(":/ToolBar/features_toolbox.png", featuresToolBoxIcon); QIcon overlayToolBoxIcon; const bool overlayToolBoxIconValid = WuQtUtilities::loadIcon(":/ToolBar/overlay_toolbox.png", overlayToolBoxIcon); /* * Note: The name of a dock widget becomes its * name in the toggleViewAction(). So, use * a separate action here so that the name in * the menu is as set here. */ m_overlayToolBoxAction = WuQtUtilities::createAction("Overlay ToolBox", "Overlay ToolBox", this, this, SLOT(processShowOverlayToolBox(bool))); m_overlayToolBoxAction->setCheckable(true); if (overlayToolBoxIconValid) { m_overlayToolBoxAction->setIcon(overlayToolBoxIcon); m_overlayToolBoxAction->setIconVisibleInMenu(false); } else { m_overlayToolBoxAction->setIconText("OT"); } /* * Note: The name of a dock widget becomes its * name in the toggleViewAction(). So, use * a separate action here so that the name in * the menu is as set here. */ m_featuresToolBoxAction = m_featuresToolBox->toggleViewAction(); m_featuresToolBoxAction->setCheckable(true); QObject::connect(m_featuresToolBoxAction, SIGNAL(triggered(bool)), this, SLOT(processShowFeaturesToolBox(bool))); if (featuresToolBoxIconValid) { m_featuresToolBoxAction->setIcon(featuresToolBoxIcon); m_featuresToolBoxAction->setIconVisibleInMenu(false); } else { m_featuresToolBoxAction->setIconText("LT"); } } /** * Show the surface properties editor dialog. */ void BrainBrowserWindow::processShowSurfacePropertiesDialog() { GuiManager::get()->processShowSurfacePropertiesEditorDialog(this); } /** * Create actions for this window. * NOTE: This is called AFTER the toolbar is created. */ void BrainBrowserWindow::createActions() { CaretAssert(m_toolbar); GuiManager* guiManager = GuiManager::get(); m_aboutWorkbenchAction = WuQtUtilities::createAction("About Workbench...", "Information about Workbench", this, this, SLOT(processAboutWorkbench())); m_newWindowAction = WuQtUtilities::createAction("New Window", "Creates a new window for viewing brain models", Qt::CTRL+Qt::Key_N, this, this, SLOT(processNewWindow())); m_newTabAction = WuQtUtilities::createAction("New Tab", "Create a new tab (window pane) in the window", Qt::CTRL + Qt::Key_T, this, this, SLOT(processNewTab())); m_duplicateTabAction = WuQtUtilities::createAction("Duplicate Tab", "Create a new tab (window pane) that duplicates the selected tab in the window", Qt::CTRL + Qt::Key_D, this, this, SLOT(processDuplicateTab())); m_openFileAction = WuQtUtilities::createAction("Open File...", "Open a data file including a spec file located on the computer", Qt::CTRL+Qt::Key_O, this, this, SLOT(processDataFileOpen())); m_openLocationAction = WuQtUtilities::createAction("Open Location...", "Open a data file including a spec file located on a web server (http)", Qt::CTRL+Qt::Key_L, this, this, SLOT(processDataFileLocationOpen())); m_manageFilesAction = WuQtUtilities::createAction("Save/Manage Files...", "Save and Manage Loaded Files", Qt::CTRL + Qt::Key_S, this, this, SLOT(processManageSaveLoadedFiles())); m_closeSpecFileAction = WuQtUtilities::createAction("Close All Files", "Close all loaded files", this, this, SLOT(processCloseAllFiles())); m_closeTabAction = WuQtUtilities::createAction("Close Tab", "Close the active tab (window pane) in the window", Qt::CTRL + Qt::Key_W, this, m_toolbar, SLOT(closeSelectedTab())); m_closeWithoutConfirmationFlag = false; m_closeWindowActionConfirmTitle = "Close Window..."; m_closeWindowActionNoConfirmTitle = "Close Window"; m_closeWindowAction = WuQtUtilities::createAction(m_closeWindowActionConfirmTitle, "Close the window", Qt::CTRL + Qt::SHIFT + Qt::Key_W, this, this, SLOT(processCloseWindow())); m_captureImageAction = WuQtUtilities::createAction("Capture Image...", "Capture an Image of the windows content", this, this, SLOT(processCaptureImage())); m_recordMovieAction = WuQtUtilities::createAction("Animation Control...", "Animate Brain Surface", this, this, SLOT(processRecordMovie())); m_preferencesAction = WuQtUtilities::createAction("Preferences...", "Edit the preferences", this, this, SLOT(processEditPreferences())); m_exitProgramAction = WuQtUtilities::createAction("Exit", "Exit (quit) the program", Qt::CTRL+Qt::Key_Q, this, this, SLOT(processExitProgram())); m_dataFociProjectAction = WuQtUtilities::createAction("Project Foci...", "Project Foci to Surfaces", this, this, SLOT(processProjectFoci())); m_dataBorderFilesSplitAction = WuQtUtilities::createAction("Split Multi-Structure Border File(s)...", "Split Multi-Structure Border File(s", this, this, SLOT(processSplitBorderFiles())); m_viewFullScreenAction = WuQtUtilities::createAction("Full Screen", "View using all of screen", Qt::CTRL+Qt::Key_F, this); QObject::connect(m_viewFullScreenAction, SIGNAL(triggered()), this, SLOT(processViewFullScreenSelected())); /* * Note: If shortcut key is changed, also change the shortcut key * for the tile tabs configuration dialog menu item to match. */ m_viewTileTabsAction = WuQtUtilities::createAction("Tile Tabs", "View all tabs in a tiled layout", Qt::CTRL+Qt::Key_M, this); QObject::connect(m_viewTileTabsAction, SIGNAL(triggered()), this, SLOT(processViewTileTabs())); m_createAndEditTileTabsAction = WuQtUtilities::createAction("Create and Edit...", "Create, Delete, and Edit Tile Tabs Configurations", Qt::CTRL + Qt::SHIFT + Qt::Key_M, this); m_nextTabAction = WuQtUtilities::createAction("Next Tab", "Move to the next tab", Qt::CTRL + Qt::Key_Right, this, m_toolbar, SLOT(nextTab())); m_previousTabAction = WuQtUtilities::createAction("Previous Tab", "Move to the previous tab", Qt::CTRL + Qt::Key_Left, this, m_toolbar, SLOT(previousTab())); m_renameSelectedTabAction = WuQtUtilities::createAction("Rename Selected Tab...", "Change the name of the selected tab", this, m_toolbar, SLOT(renameTab())); m_moveTabsInWindowToNewWindowsAction = WuQtUtilities::createAction("Move All Tabs in Current Window to New Windows", "Move all but the left most tab to new windows", this, m_toolbar, SLOT(moveTabsToNewWindows())); m_moveTabsFromAllWindowsToOneWindowAction = WuQtUtilities::createAction("Move All Tabs From All Windows Into Selected Window", "Move all tabs from all windows into selected window", this, this, SLOT(processMoveAllTabsToOneWindow())); m_bringAllToFrontAction = WuQtUtilities::createAction("Bring All To Front", "Move all windows on top of other application windows", this, guiManager, SLOT(processBringAllWindowsToFront())); m_tileWindowsAction = WuQtUtilities::createAction("Tile Windows", "Arrange the windows into grid so that the fill the screen", this, guiManager, SLOT(processTileWindows())); m_informationDialogAction = WuQtUtilities::createAction("Information...", "Show the Informaiton Window", this, guiManager, SLOT(processShowInformationWindow())); m_helpHcpWebsiteAction = WuQtUtilities::createAction("Go to HCP Website...", "Load the HCP Website in your computer's web browser", this, this, SLOT(processHcpWebsiteInBrowser())); m_helpHcpFeatureRequestAction = WuQtUtilities::createAction("Submit HCP Software Feature Request...", "Go to HCP Feature Request Website in your computer's web browser", this, this, SLOT(processHcpFeatureRequestWebsiteInBrowser())); m_helpWorkbenchBugReportAction = WuQtUtilities::createAction("Report a Workbench Bug...", "Send a Workbench Bug Report", this, this, SLOT(processReportWorkbenchBug())); m_connectToAllenDatabaseAction = WuQtUtilities::createAction("Allen Brain Institute Database...", "Open a connection to the Allen Brain Institute Database", this, this, SLOT(processConnectToAllenDataBase())); m_connectToAllenDatabaseAction->setEnabled(false); m_connectToConnectomeDatabaseAction = WuQtUtilities::createAction("Human Connectome Project Database...", "Open a connection to the Human Connectome Project Database", this, this, SLOT(processConnectToConnectomeDataBase())); m_connectToConnectomeDatabaseAction->setEnabled(false); m_developerGraphicsTimingAction = WuQtUtilities::createAction("Time Graphics Update", "Show the average time for updating the windows graphics", this, this, SLOT(processDevelopGraphicsTiming())); m_developerExportVtkFileAction = WuQtUtilities::createAction("Export to VTK File", "Export model(s) to VTK File", this, this, SLOT(processDevelopExportVtkFile())); } /** * Create menus for this window. */ void BrainBrowserWindow::createMenus() { CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); /* * Create the menu bar and add menus to it. */ QMenuBar* menubar = menuBar(); menubar->addMenu(createMenuFile()); menubar->addMenu(createMenuView()); QMenu* dataMenu = createMenuData(); if (dataMenu != NULL) { menubar->addMenu(dataMenu); } menubar->addMenu(createMenuSurface()); QMenu* volumeMenu = createMenuVolume(); if (volumeMenu != NULL) { menubar->addMenu(volumeMenu); } QMenu* connectMenu = createMenuConnect(); if (connectMenu != NULL) { menubar->addMenu(connectMenu); } QMenu* developMenu = createMenuDevelop(); m_developMenuAction = menubar->addMenu(developMenu); m_developMenuAction->setVisible(prefs->isDevelopMenuEnabled()); menubar->addMenu(createMenuWindow()); menubar->addMenu(createMenuHelp()); } /** * @return Create and return the developer menu. */ QMenu* BrainBrowserWindow::createMenuDevelop() { QMenu* menu = new QMenu("Develop", this); QObject::connect(menu, SIGNAL(aboutToShow()), this, SLOT(developerMenuAboutToShow())); menu->addAction(m_developerExportVtkFileAction); /* * Hide the Export to VTK menu item */ m_developerExportVtkFileAction->setVisible(false); menu->addAction(m_developerGraphicsTimingAction); std::vector developerFlags; DeveloperFlagsEnum::getAllEnums(developerFlags); m_developerFlagsActionGroup = NULL; if ( ! developerFlags.empty()) { menu->addSeparator(); m_developerFlagsActionGroup = new QActionGroup(this); m_developerFlagsActionGroup->setExclusive(false); QObject::connect(m_developerFlagsActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(developerMenuFlagTriggered(QAction*))); for (std::vector::iterator iter = developerFlags.begin(); iter != developerFlags.end(); iter++) { const DeveloperFlagsEnum::Enum flag = *iter; QAction* action = menu->addAction(DeveloperFlagsEnum::toGuiName(flag)); action->setCheckable(true); action->setData(static_cast(DeveloperFlagsEnum::toIntegerCode(flag))); m_developerFlagsActionGroup->addAction(action); } } return menu; } /** * Called when developer menu is about to show. */ void BrainBrowserWindow::developerMenuAboutToShow() { std::vector developerFlags; DeveloperFlagsEnum::getAllEnums(developerFlags); QList actions = m_developerFlagsActionGroup->actions(); QListIterator iter(actions); while (iter.hasNext()) { QAction* action = iter.next(); const int integerCode = action->data().toInt(); bool valid = false; const DeveloperFlagsEnum::Enum enumValue = DeveloperFlagsEnum::fromIntegerCode(integerCode, &valid); if (valid) { const bool flag = DeveloperFlagsEnum::isFlag(enumValue); action->setChecked(flag); } else { CaretLogSevere("Failed to find develper flag for updating menu: " + action->text()); } } } /** * Called when developer flag is checked/unchecked. * * @param action * Action that is checked/unchecked. */ void BrainBrowserWindow::developerMenuFlagTriggered(QAction* action) { const int integerCode = action->data().toInt(); bool valid = false; const DeveloperFlagsEnum::Enum enumValue = DeveloperFlagsEnum::fromIntegerCode(integerCode, &valid); if (valid) { DeveloperFlagsEnum::setFlag(enumValue, action->isChecked()); /* * Update graphics and GUI */ EventManager::get()->sendEvent(EventSurfaceColoringInvalidate().getPointer()); EventManager::get()->sendEvent(EventUserInterfaceUpdate().getPointer()); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } else { CaretLogSevere("Failed to find develper flag for reading menu: " + action->text()); } } /** * Create the file menu. * @return the file menu. */ QMenu* BrainBrowserWindow::createMenuFile() { QMenu* menu = new QMenu("File", this); QObject::connect(menu, SIGNAL(aboutToShow()), this, SLOT(processFileMenuAboutToShow())); menu->addAction(m_aboutWorkbenchAction); menu->addAction(m_preferencesAction); #ifndef CARET_OS_MACOSX menu->addSeparator(); #endif // CARET_OS_MACOSX menu->addAction(m_newWindowAction); menu->addAction(m_newTabAction); menu->addAction(m_duplicateTabAction); menu->addSeparator(); menu->addAction(m_openFileAction); menu->addAction(m_openLocationAction); m_recentSpecFileMenuOpenConfirmTitle = "Open Recent Spec File"; m_recentSpecFileMenuLoadNoConfirmTitle = "Load All Files in Recent Spec File"; m_recentSpecFileMenu = menu->addMenu(m_recentSpecFileMenuOpenConfirmTitle); QObject::connect(m_recentSpecFileMenu, SIGNAL(aboutToShow()), this, SLOT(processRecentSpecFileMenuAboutToBeDisplayed())); QObject::connect(m_recentSpecFileMenu, SIGNAL(triggered(QAction*)), this, SLOT(processRecentSpecFileMenuSelection(QAction*))); //menu->addAction(m_openFileViaSpecFileAction); menu->addAction(m_manageFilesAction); menu->addAction(m_closeSpecFileAction); menu->addSeparator(); menu->addAction(m_recordMovieAction); menu->addAction(m_captureImageAction); menu->addSeparator(); menu->addAction(m_closeTabAction); menu->addAction(m_closeWindowAction); menu->addSeparator(); #ifndef CARET_OS_MACOSX menu->addSeparator(); #endif // CARET_OS_MACOSX menu->addAction(m_exitProgramAction); return menu; } /** * Called to display the overlay toolbox. */ void BrainBrowserWindow::processShowOverlayToolBox(bool status) { m_overlayActiveToolBox->setVisible(status); m_overlayToolBoxAction->blockSignals(true); m_overlayToolBoxAction->setChecked(status); m_overlayToolBoxAction->blockSignals(false); if (status) { m_overlayToolBoxAction->setToolTip("Hide Overlay Toolbox"); EventManager::get()->sendEvent(EventUserInterfaceUpdate().setWindowIndex(m_browserWindowIndex).addToolBox().getPointer()); } else { m_overlayToolBoxAction->setToolTip("Show Overlay Toolbox"); } } /** * Called when visibility of horizontal overlay toolbox is changed. * @param visible * New visibility status. */ void BrainBrowserWindow::processOverlayHorizontalToolBoxVisibilityChanged(bool visible) { if (visible == false) { if (m_overlayActiveToolBox == m_overlayHorizontalToolBox) { m_overlayToolBoxAction->blockSignals(true); m_overlayToolBoxAction->setChecked(false); m_overlayToolBoxAction->blockSignals(false); } } } /** * Called when visibility of vertical overlay toolbox is changed. * @param visible * New visibility status. */ void BrainBrowserWindow::processOverlayVerticalToolBoxVisibilityChanged(bool visible) { if (visible == false) { if (m_overlayActiveToolBox == m_overlayVerticalToolBox) { m_overlayToolBoxAction->blockSignals(true); m_overlayToolBoxAction->setChecked(false); m_overlayToolBoxAction->blockSignals(false); } } } /** * Called when File Menu is about to show. */ void BrainBrowserWindow::processFileMenuAboutToShow() { if (isMacOptionKeyDown()) { m_closeWindowAction->setText(m_closeWindowActionNoConfirmTitle); m_recentSpecFileMenu->setTitle(m_recentSpecFileMenuLoadNoConfirmTitle); } else { m_closeWindowAction->setText(m_closeWindowActionConfirmTitle); m_recentSpecFileMenu->setTitle(m_recentSpecFileMenuOpenConfirmTitle); } } void BrainBrowserWindow::processCloseWindow() { if (m_closeWindowAction->text() == m_closeWindowActionConfirmTitle) { /* * When confirming, just use close slot and it will result * in confirmation dialog being displayed. */ m_closeWithoutConfirmationFlag = false; close(); } else if (m_closeWindowAction->text() == m_closeWindowActionNoConfirmTitle) { m_closeWithoutConfirmationFlag = true; close(); } else { CaretAssert(0); } } /** * @return Is the Option Key down on a Mac. * Always returns false if not a Mac. */ bool BrainBrowserWindow::isMacOptionKeyDown() const { bool keyDown = false; #ifdef CARET_OS_MACOSX keyDown = (QApplication::queryKeyboardModifiers() == Qt::AltModifier); #endif // CARET_OS_MACOSX return keyDown; } /** * Called when Open Recent Spec File Menu is about to be displayed * and creates the content of the menu. */ void BrainBrowserWindow::processRecentSpecFileMenuAboutToBeDisplayed() { m_recentSpecFileMenu->clear(); const int32_t numRecentSpecFiles = BrainBrowserWindow::loadRecentSpecFileMenu(m_recentSpecFileMenu); if (numRecentSpecFiles > 0) { m_recentSpecFileMenu->addSeparator(); QAction* action = new QAction("Clear Menu", m_recentSpecFileMenu); action->setData("CLEAR_CLEAR"); m_recentSpecFileMenu->addAction(action); } } /** * Load a menu with recent spec files. This method only ADDS * items to the menu, nothing is removed or cleared. * * @param recentSpecFileMenu * Menu to which recent spec files are added. * @return * Returns the number of recent spec files added to the menu. */ int32_t BrainBrowserWindow::loadRecentSpecFileMenu(QMenu* recentSpecFileMenu) { CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); std::vector recentSpecFiles; prefs->getPreviousSpecFiles(recentSpecFiles); const int32_t numRecentSpecFiles = static_cast(recentSpecFiles.size()); for (int32_t i = 0; i < numRecentSpecFiles; i++) { AString actionName; AString actionFullPath; if (DataFile::isFileOnNetwork(recentSpecFiles[i])) { actionName = recentSpecFiles[i]; actionFullPath = recentSpecFiles[i]; } else { FileInformation fileInfo(recentSpecFiles[i]); QString path = fileInfo.getPathName(); QString name = fileInfo.getFileName(); if (path.isEmpty() == false) { name += (" (" + path + ")"); } actionName = name; actionFullPath = fileInfo.getAbsoluteFilePath(); } QAction* action = new QAction(actionName, recentSpecFileMenu); /* * If this "setData()" action changes you will need to update: * (1) BrainBrowserWindow::processRecentSpecFileMenuSelection * (2) MacDockMenu::menuActionTriggered */ action->setData(actionFullPath); recentSpecFileMenu->addAction(action); } return numRecentSpecFiles; } /** * Called when an item is selected from the recent spec file * menu. * @param itemAction * Action of the menu item that was selected. */ void BrainBrowserWindow::processRecentSpecFileMenuSelection(QAction* itemAction) { //AString errorMessages; const AString specFileName = itemAction->data().toString(); if (specFileName == "CLEAR_CLEAR") { CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); prefs->clearPreviousSpecFiles(); return; } if ( ! specFileName.isEmpty()) { SpecFile specFile; try { specFile.readFile(specFileName); if (m_recentSpecFileMenu->title() == m_recentSpecFileMenuOpenConfirmTitle) { if (GuiManager::get()->processShowOpenSpecFileDialog(&specFile, this)) { m_toolbar->addDefaultTabsAfterLoadingSpecFile(); } } else if (m_recentSpecFileMenu->title() == m_recentSpecFileMenuLoadNoConfirmTitle) { std::vector fileNamesToLoad; fileNamesToLoad.push_back(specFileName); loadFilesFromCommandLine(fileNamesToLoad, BrainBrowserWindow::LOAD_SPEC_FILE_CONTENTS_VIA_COMMAND_LINE); m_toolbar->addDefaultTabsAfterLoadingSpecFile(); } else { CaretAssert(0); } } catch (const DataFileException& e) { //errorMessages += e.whatString(); QMessageBox::critical(this, "ERROR", e.whatString()); return; } EventManager::get()->sendEvent(EventSurfaceColoringInvalidate().getPointer()); EventManager::get()->sendEvent(EventUserInterfaceUpdate().getPointer()); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } } /** * Called when view menu is about to show. */ void BrainBrowserWindow::processViewMenuAboutToShow() { m_viewMoveFeaturesToolBoxMenu->setEnabled(isFullScreen() == false); m_viewMoveOverlayToolBoxMenu->setEnabled(isFullScreen()== false); if (isFullScreen()) { m_viewFullScreenAction->setText("Exit Full Screen"); } else { m_viewFullScreenAction->setText("Enter Full Screen"); } if (isTileTabsSelected()) { m_viewTileTabsAction->setText("Exit Tile Tabs"); } else { m_viewTileTabsAction->setText("Enter Tile Tabs"); } } /** * Create the view menu. * @return the view menu. */ QMenu* BrainBrowserWindow::createMenuView() { m_tileTabsMenu = new QMenu("Tile Tabs Configuration"); QObject::connect(m_tileTabsMenu, SIGNAL(aboutToShow()), this, SLOT(processTileTabsMenuAboutToBeDisplayed())); QObject::connect(m_tileTabsMenu, SIGNAL(triggered(QAction*)), this, SLOT(processTileTabsMenuSelection(QAction*))); m_tileTabsMenu->addAction(m_createAndEditTileTabsAction); m_tileTabsMenu->addSeparator(); QMenu* menu = new QMenu("View", this); QObject::connect(menu, SIGNAL(aboutToShow()), this, SLOT(processViewMenuAboutToShow())); m_viewMoveFeaturesToolBoxMenu = createMenuViewMoveFeaturesToolBox(); m_viewMoveOverlayToolBoxMenu = createMenuViewMoveOverlayToolBox(); menu->addAction(m_showToolBarAction); menu->addMenu(m_viewMoveFeaturesToolBoxMenu); menu->addMenu(m_viewMoveOverlayToolBoxMenu); menu->addSeparator(); menu->addAction(m_viewFullScreenAction); menu->addAction(m_viewTileTabsAction); menu->addSeparator(); menu->addMenu(m_tileTabsMenu); return menu; } /** * @return Create and return the overlay toolbox menu. */ QMenu* BrainBrowserWindow::createMenuViewMoveFeaturesToolBox() { QMenu* menu = new QMenu("Features Toolbox", this); menu->addAction("Attach to Right", this, SLOT(processMoveFeaturesToolBoxToRight())); menu->addAction("Detach", this, SLOT(processMoveFeaturesToolBoxToFloat())); menu->addAction("Hide", this, SLOT(processHideFeaturesToolBox())); return menu; } /** * @return Create and return the overlay toolbox menu. */ QMenu* BrainBrowserWindow::createMenuViewMoveOverlayToolBox() { QMenu* menu = new QMenu("Overlay Toolbox", this); menu->addAction("Attach to Bottom", this, SLOT(processMoveOverlayToolBoxToBottom())); menu->addAction("Attach to Left", this, SLOT(processMoveOverlayToolBoxToLeft())); menu->addAction("Detach", this, SLOT(processMoveOverlayToolBoxToFloat())); menu->addAction("Hide", this, SLOT(processHideOverlayToolBox())); return menu; } /** * Update the Tile Tabs Configuration Menu just before it is displayed. */ void BrainBrowserWindow::processTileTabsMenuAboutToBeDisplayed() { /* * Remove all actions for user-defined tile tabs configurations */ QList menuActions = m_tileTabsMenu->actions(); QListIterator actionIterator(menuActions); while (actionIterator.hasNext()) { QAction* action = actionIterator.next(); if (action != m_createAndEditTileTabsAction) { if (action->isSeparator() == false) { m_tileTabsMenu->removeAction(action); delete action; } } } CaretPreferences* preferences = SessionManager::get()->getCaretPreferences(); preferences->readTileTabsConfigurations(); std::vector configurations = preferences->getTileTabsConfigurationsSortedByName(); const int32_t numConfigurations = static_cast(configurations.size()); /* * Add All Tabs (Default) tile tabs configuration */ QAction* defaultAction = m_tileTabsMenu->addAction(m_defaultTileTabsConfiguration->getName()); defaultAction->setCheckable(true); defaultAction->setData(QVariant(m_defaultTileTabsConfiguration->getUniqueIdentifier())); /* * Add the scene configuration */ QAction* sceneAction = m_tileTabsMenu->addAction(m_sceneTileTabsConfiguration->getName()); sceneAction->setCheckable(true); sceneAction->setData(QVariant(m_sceneTileTabsConfiguration->getUniqueIdentifier())); //sceneAction->setEnabled(false); bool haveSelectedTileTabsConfiguration = false; /* * Add the user defined Tile Tabs configurations */ if (numConfigurations > 0) { m_tileTabsMenu->addSeparator(); for (int32_t i = 0; i < numConfigurations; i++) { QAction* action = m_tileTabsMenu->addAction(configurations[i]->getName()); action->setCheckable(true); if (m_selectedTileTabsConfigurationUniqueIdentifier == configurations[i]->getUniqueIdentifier()) { action->setChecked(true); haveSelectedTileTabsConfiguration = true; } action->setData(QVariant(configurations[i]->getUniqueIdentifier())); } } if (m_selectedTileTabsConfigurationUniqueIdentifier == m_sceneTileTabsConfiguration->getUniqueIdentifier()) { haveSelectedTileTabsConfiguration = true; sceneAction->setChecked(true); } if ( ! haveSelectedTileTabsConfiguration) { defaultAction->setChecked(true); m_selectedTileTabsConfigurationUniqueIdentifier = m_defaultTileTabsConfiguration->getUniqueIdentifier(); } } /** * Process a selection from the Tile Tab Configuration Menu. * * @param action * Action that was selected. */ void BrainBrowserWindow::processTileTabsMenuSelection(QAction* action) { if (action == m_createAndEditTileTabsAction) { processViewTileTabsConfigurationDialog(); } else { //CaretPreferences* preferences = SessionManager::get()->getCaretPreferences(); m_selectedTileTabsConfigurationUniqueIdentifier = action->data().toString(); if (isTileTabsSelected()) { EventManager::get()->sendEvent(EventGraphicsUpdateOneWindow(m_browserWindowIndex).getPointer()); } } } /* * Set the selected tile tabs configuration. If requested configuration is * NULL, the default configuration will be selected. * * @param configuration * New selection for tile tabs configuration. */ void BrainBrowserWindow::setSelectedTileTabsConfiguration(TileTabsConfiguration* configuration) { if (configuration != NULL) { m_selectedTileTabsConfigurationUniqueIdentifier = configuration->getUniqueIdentifier(); } else { m_selectedTileTabsConfigurationUniqueIdentifier = m_defaultTileTabsConfiguration->getUniqueIdentifier(); } } /** * @return The selected tile tabs configuration. */ TileTabsConfiguration* BrainBrowserWindow::getSelectedTileTabsConfiguration() { CaretPreferences* preferences = SessionManager::get()->getCaretPreferences(); TileTabsConfiguration* configuration = preferences->getTileTabsConfigurationByUniqueIdentifier(m_selectedTileTabsConfigurationUniqueIdentifier); if (configuration != NULL) { /* * A user configuration is selected */ return configuration; } else if (m_selectedTileTabsConfigurationUniqueIdentifier == m_defaultTileTabsConfiguration->getUniqueIdentifier()) { /* * Default configuration is selected */ return m_defaultTileTabsConfiguration; } else if (m_selectedTileTabsConfigurationUniqueIdentifier == m_sceneTileTabsConfiguration->getUniqueIdentifier()) { /* * Scene's configuration is selected */ return m_sceneTileTabsConfiguration; } else { /* * The selected configuration has been deleted, likely in another browser window * so selecte the default configuration */ m_selectedTileTabsConfigurationUniqueIdentifier = m_defaultTileTabsConfiguration->getUniqueIdentifier(); } return m_defaultTileTabsConfiguration; } /** * Create the connect menu. * @return the connect menu. */ QMenu* BrainBrowserWindow::createMenuConnect() { QMenu* menu = new QMenu("Connect"); if (m_connectToAllenDatabaseAction->isEnabled()) { menu->addAction(m_connectToAllenDatabaseAction); } if (m_connectToConnectomeDatabaseAction->isEnabled()) { menu->addAction(m_connectToConnectomeDatabaseAction); } /* * If none of the actions are enabled, the menu will be * empty so there is no need to display the menu. */ if (menu->isEmpty()) { delete menu; menu = NULL; } return menu; } /** * Create the data menu. * @return the data menu. */ QMenu* BrainBrowserWindow::createMenuData() { QMenu* menu = new QMenu("Data", this); QObject::connect(menu, SIGNAL(aboutToShow()), this, SLOT(processDataMenuAboutToShow())); menu->addAction(m_dataFociProjectAction); menu->addAction(m_dataBorderFilesSplitAction); return menu; } /** * Called when Data Menu is about to show. */ void BrainBrowserWindow::processDataMenuAboutToShow() { Brain* brain = GuiManager::get()->getBrain(); bool haveFociFiles = (GuiManager::get()->getBrain()->getNumberOfFociFiles() > 0); m_dataFociProjectAction->setEnabled(haveFociFiles); bool haveMultiStructureBorderFiles = false; const int32_t numBorderFiles = brain->getNumberOfBorderFiles(); for (int32_t i = 0; i < numBorderFiles; i++) { if ( ! brain->getBorderFile(i)->isSingleStructure()) { haveMultiStructureBorderFiles = true; break; } } m_dataBorderFilesSplitAction->setEnabled(haveMultiStructureBorderFiles); } /** * Create the surface menu. * @return the surface menu. */ QMenu* BrainBrowserWindow::createMenuSurface() { QMenu* menu = new QMenu("Surface", this); menu->addAction("Information...", this, SLOT(processSurfaceMenuInformation())); menu->addAction("Properties...", this, SLOT(processShowSurfacePropertiesDialog())); menu->addAction("Primary Anatomical...", this, SLOT(processSurfaceMenuPrimaryAnatomical())); return menu; } /** * Called when Primary Anatomical is selected from the surface menu. */ void BrainBrowserWindow::processSurfaceMenuPrimaryAnatomical() { Brain* brain = GuiManager::get()->getBrain(); const int32_t numBrainStructures = brain->getNumberOfBrainStructures(); if (numBrainStructures <= 0) { return; } WuQDataEntryDialog ded("Primary Anatomical Surfaces", this); std::vector surfaceSelectionControls; for (int32_t i = 0; i < numBrainStructures; i++) { BrainStructure* bs = brain->getBrainStructure(i); SurfaceSelectionViewController* ssc = ded.addSurfaceSelectionViewController(StructureEnum::toGuiName(bs->getStructure()), bs); ssc->setSurface(bs->getPrimaryAnatomicalSurface()); surfaceSelectionControls.push_back(ssc); } if (ded.exec() == WuQDataEntryDialog::Accepted) { for (int32_t i = 0; i < numBrainStructures; i++) { BrainStructure* bs = brain->getBrainStructure(i); bs->setPrimaryAnatomicalSurface(surfaceSelectionControls[i]->getSurface()); } EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } } /** * Called when Information is selected from the surface menu. */ void BrainBrowserWindow::processSurfaceMenuInformation() { BrowserTabContent* btc = getBrowserTabContent(); if (btc != NULL) { AString txt = ""; Model* mdc = btc->getModelForDisplay(); ModelSurface* mdcs = dynamic_cast(mdc); if (mdcs != NULL) { txt += mdcs->getSurface()->getInformation(); } ModelWholeBrain* mdcwb = dynamic_cast(mdc); if (mdcwb != NULL) { std::vector allStructures; StructureEnum::getAllEnums(allStructures); for (std::vector::iterator iter = allStructures.begin(); iter != allStructures.end(); iter++) { const Surface* surface = mdcwb->getSelectedSurface(*iter, btc->getTabNumber()); if (surface != NULL) { txt += surface->getInformation(); txt += "\n"; } } } ModelSurfaceMontage* msm = dynamic_cast(mdc); if (msm != NULL) { std::vector surfaces; msm->getSelectedConfiguration(btc->getTabNumber())->getDisplayedSurfaces(surfaces); for (std::vector::iterator iter = surfaces.begin(); iter != surfaces.end(); iter++) { const Surface* s = *iter; txt += s->getInformation(); txt += "\n"; } } if (txt.isEmpty() == false) { WuQMessageBox::informationOk(this, txt); } } } /** * Create the volume menu. * @return the volume menu. */ QMenu* BrainBrowserWindow::createMenuVolume() { // QMenu* menu = new QMenu("Volume", this); // return menu; return NULL; } /** * Create the window menu. * @return the window menu. */ QMenu* BrainBrowserWindow::createMenuWindow() { m_moveSelectedTabToWindowMenu = new QMenu("Move Selected Tab to Window"); QObject::connect(m_moveSelectedTabToWindowMenu, SIGNAL(aboutToShow()), this, SLOT(processMoveSelectedTabToWindowMenuAboutToBeDisplayed())); QObject::connect(m_moveSelectedTabToWindowMenu, SIGNAL(triggered(QAction*)), this, SLOT(processMoveSelectedTabToWindowMenuSelection(QAction*))); QMenu* menu = new QMenu("Window", this); menu->addAction(m_nextTabAction); menu->addAction(m_previousTabAction); menu->addAction(m_renameSelectedTabAction); menu->addSeparator(); menu->addAction(m_moveTabsInWindowToNewWindowsAction); menu->addAction(m_moveTabsFromAllWindowsToOneWindowAction); menu->addMenu(m_moveSelectedTabToWindowMenu); menu->addSeparator(); /* * Cannot use the Identify action since it sets the "checkable" attribute * and this will add checkbox and checkmark to the menu. In addition, * using the identify action would also hide the help viewer if it is * displayed and we don't want that. */ QAction* identifyAction = GuiManager::get()->getIdentifyBrainordinateDialogDisplayAction(); menu->addAction(identifyAction->text(), this, SLOT(processShowIdentifyBrainordinateDialog())); menu->addAction(m_informationDialogAction); menu->addAction(GuiManager::get()->getSceneDialogDisplayAction()); menu->addSeparator(); menu->addAction(m_bringAllToFrontAction); menu->addAction(m_tileWindowsAction); return menu; } /** * Create the help menu. * @return the help menu. */ QMenu* BrainBrowserWindow::createMenuHelp() { QMenu* menu = new QMenu("Help", this); /* * Cannot use the Help action since it sets the "checkable" attribute * and this will add checkbox and checkmark to the menu. In addition, * using the help action would also hide the help viewer if it is * dispalyed and we don't want that. */ QAction* helpAction = GuiManager::get()->getHelpViewerDialogDisplayAction(); menu->addAction(helpAction->text(), this, SLOT(processShowHelpInformation())); menu->addSeparator(); menu->addAction(m_helpHcpWebsiteAction); menu->addAction(m_helpWorkbenchBugReportAction); menu->addAction(m_helpHcpFeatureRequestAction); return menu; } /** * Called to show/hide help content. */ void BrainBrowserWindow::processShowHelpInformation() { /* * Always display the help viewer when selected from the menu. * Even if the help viewer is active it may be under other windows * so triggering it will cause it to display. */ QAction* helpAction = GuiManager::get()->getHelpViewerDialogDisplayAction(); if (helpAction->isChecked()) { helpAction->blockSignals(true); helpAction->setChecked(false); helpAction->blockSignals(false); } helpAction->trigger(); } void BrainBrowserWindow::processShowIdentifyBrainordinateDialog() { /* * Always display the identify dialog when selected from the menu. * Even if the identify dialog is active it may be under other windows * so triggering it will cause it to display. */ GuiManager::get()->showHideIdentfyBrainordinateDialog(true, this); // QAction* identifyAction = GuiManager::get()->getIdentifyBrainordinateDialogDisplayAction(); // if (identifyAction->isChecked()) { // identifyAction->blockSignals(true); // identifyAction->setChecked(false); // identifyAction->blockSignals(false); // } // identifyAction->trigger(); } /** * Time the graphics drawing. */ void BrainBrowserWindow::processDevelopGraphicsTiming() { ElapsedTimer et; et.start(); const int32_t numTimes = 5; for (int32_t i = 0; i < numTimes; i++) { EventManager::get()->sendEvent(EventGraphicsUpdateOneWindow(m_browserWindowIndex).getPointer()); } const float time = et.getElapsedTimeSeconds() / numTimes; const AString timeString = AString::number(time, 'f', 5); const AString msg = ("Time to draw graphics (seconds): " + timeString); WuQMessageBox::informationOk(this, msg); } /** * Export to VTK file. */ void BrainBrowserWindow::processDevelopExportVtkFile() { static QString previousVtkFileName = ""; BrowserTabContent* btc = getBrowserTabContent(); if (btc != NULL) { const int32_t tabIndex = btc->getTabNumber(); std::vector surfaceFiles; std::vector surfaceFilesColoring; ModelSurface* modelSurface = btc->getDisplayedSurfaceModel(); ModelWholeBrain* modelWholeBrain = btc->getDisplayedWholeBrainModel(); if (modelSurface != NULL) { SurfaceFile* sf = modelSurface->getSurface(); surfaceFiles.push_back(sf); surfaceFilesColoring.push_back(sf->getSurfaceNodeColoringRgbaForBrowserTab(tabIndex)); } else if (modelWholeBrain != NULL) { std::vector wholeBrainSurfaces = modelWholeBrain->getSelectedSurfaces(tabIndex); for (std::vector::iterator iter = wholeBrainSurfaces.begin(); iter != wholeBrainSurfaces.end(); iter++) { Surface* surface = *iter; surfaceFiles.push_back(surface); surfaceFilesColoring.push_back(surface->getWholeBrainNodeColoringRgbaForBrowserTab(tabIndex)); } } if (surfaceFiles.empty() == false) { QString vtkSurfaceFileFilter = "VTK Poly Data File (*.vtp)"; CaretFileDialog cfd(this, "Export to VTK File", GuiManager::get()->getBrain()->getCurrentDirectory(), vtkSurfaceFileFilter); cfd.selectFilter(vtkSurfaceFileFilter); cfd.setAcceptMode(QFileDialog::AcceptSave); cfd.setFileMode(CaretFileDialog::AnyFile); if (previousVtkFileName.isEmpty() == false) { cfd.selectFile(previousVtkFileName); } if (cfd.exec() == CaretFileDialog::Accepted) { QStringList selectedFiles = cfd.selectedFiles(); if (selectedFiles.size() > 0) { const QString vtkFileName = selectedFiles[0]; if (vtkFileName.isEmpty() == false) { try { previousVtkFileName = vtkFileName; VtkFileExporter::writeSurfaces(surfaceFiles, surfaceFilesColoring, vtkFileName); } catch (const DataFileException& dfe) { WuQMessageBox::errorOk(this, dfe.whatString()); } } } } } else { WuQMessageBox::errorOk(this, "Displayed model does not support exporting to VTK File at this time."); } } } /** * Project foci. */ void BrainBrowserWindow::processProjectFoci() { FociProjectionDialog fpd(this); fpd.exec(); } /** * Split multi-structure border files. */ void BrainBrowserWindow::processSplitBorderFiles() { BorderFileSplitDialog dialog(this); dialog.exec(); } /** * Called when capture image is selected. */ void BrainBrowserWindow::processCaptureImage() { GuiManager::get()->processShowImageCaptureDialog(this); } /** * Called when record movie is selected. */ void BrainBrowserWindow::processRecordMovie() { GuiManager::get()->processShowMovieDialog(this); } /** * Called when capture image is selected. */ void BrainBrowserWindow::processEditPreferences() { GuiManager::get()->processShowPreferencesDialog(this); } /** * Called when information dialog is selected. */ void BrainBrowserWindow::processInformationDialog() { GuiManager::get()->processShowInformationDisplayDialog(this); } /** * Called when close spec file is selected. */ void BrainBrowserWindow::processCloseAllFiles() { if(!WuQMessageBox::warningYesNo(this, "Are you sure you want to close all files?")) return; Brain* brain = GuiManager::get()->getBrain(); brain->resetBrain(); EventManager::get()->sendEvent(EventUserInterfaceUpdate().getPointer()); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); GuiManager::get()->closeAllOtherWindows(this); EventManager::get()->sendEvent(EventUserInterfaceUpdate().getPointer()); } /** * Create a new window. */ void BrainBrowserWindow::processNewWindow() { CursorDisplayScoped cursor; cursor.showWaitCursor(); EventManager::get()->blockEvent(EventTypeEnum::EVENT_GRAPHICS_UPDATE_ONE_WINDOW, true); EventBrowserWindowNew eventNewBrowser(this, NULL); EventManager::get()->sendEvent(eventNewBrowser.getPointer()); if (eventNewBrowser.isError()) { cursor.restoreCursor(); QMessageBox::critical(this, "", eventNewBrowser.getErrorMessage()); return; } const int32_t newWindowIndex = eventNewBrowser.getBrowserWindowCreated()->getBrowserWindowIndex(); EventManager::get()->sendEvent(EventUserInterfaceUpdate().setWindowIndex(newWindowIndex).getPointer()); EventManager::get()->blockEvent(EventTypeEnum::EVENT_GRAPHICS_UPDATE_ONE_WINDOW, false); EventManager::get()->sendEvent(EventGraphicsUpdateOneWindow(newWindowIndex).getPointer()); } /** * Display about workbench dialog. */ void BrainBrowserWindow::processAboutWorkbench() { AboutWorkbenchDialog awd(this->m_openGLWidget); awd.exec(); } /** * Called when open location is selected. */ void BrainBrowserWindow::processDataFileLocationOpen() { CaretFileRemoteDialog fileRemoteDialog(this); fileRemoteDialog.exec(); } /** * Called when open data file is selected. */ void BrainBrowserWindow::processDataFileOpen() { if (s_previousOpenFileNameFilter.isEmpty()) { s_previousOpenFileNameFilter = DataFileTypeEnum::toQFileDialogFilter(DataFileTypeEnum::SPECIFICATION); } /* * Get all file filters. */ std::vector dataFileTypes; DataFileTypeEnum::getAllEnums(dataFileTypes, false); QStringList filenameFilterList; filenameFilterList.append("Any File (*)"); for (std::vector::const_iterator iter = dataFileTypes.begin(); iter != dataFileTypes.end(); iter++) { AString filterName = DataFileTypeEnum::toQFileDialogFilter(*iter); filenameFilterList.append(filterName); } /* * Setup file selection dialog. */ CaretFileDialog fd(this); fd.setAcceptMode(CaretFileDialog::AcceptOpen); fd.setNameFilters(filenameFilterList); fd.setFileMode(CaretFileDialog::ExistingFiles); fd.setViewMode(CaretFileDialog::List); fd.selectNameFilter(s_previousOpenFileNameFilter); if (s_previousOpenFileDirectory.isEmpty() == false) { FileInformation fileInfo(s_previousOpenFileDirectory); if (fileInfo.exists()) { fd.setDirectory(s_previousOpenFileDirectory); } } if ( ! s_previousOpenFileGeometry.isEmpty()) { fd.restoreGeometry(s_previousOpenFileGeometry); } AString errorMessages; if (fd.exec() == CaretFileDialog::Accepted) { QStringList selectedFiles = fd.selectedFiles(); if (selectedFiles.empty() == false) { /* * Load the files. */ std::vector filenamesVector; QStringListIterator nameIter(selectedFiles); while (nameIter.hasNext()) { const QString name = nameIter.next(); filenamesVector.push_back(name); } std::vector dataFileTypesDummyNotUsed; loadFiles(this, filenamesVector, dataFileTypesDummyNotUsed, LOAD_SPEC_FILE_WITH_DIALOG, "", ""); } s_previousOpenFileNameFilter = fd.selectedNameFilter(); s_previousOpenFileDirectory = fd.directory().absolutePath(); s_previousOpenFileGeometry = fd.saveGeometry(); } } /** * Load files that are on the network * * @param parentForDialogs, * Parent widget on which dialogs are displayed. * @param filenames * List of filenames to read. * @param dataFileTypes * Type for each data file (optional, if not available type matched from file's extension). * @param username * Username for network file reading * @param password * Password for network file reading */ bool BrainBrowserWindow::loadFilesFromNetwork(QWidget* parentForDialogs, const std::vector& filenames, const std::vector dataFileTypes, const AString& username, const AString& password) { // this->loadFilesFromCommandLine(filenames, // BrainBrowserWindow::LOAD_SPEC_FILE_WITH_DIALOG_VIA_COMMAND_LINE); const bool successFlag = loadFiles(parentForDialogs, filenames, dataFileTypes, BrainBrowserWindow::LOAD_SPEC_FILE_WITH_DIALOG_VIA_COMMAND_LINE, username, password); return successFlag; } /** * Load the files that were specified on the command line. * @param filenames * Names of files on the command line. * @param loadSpecFileMode * Specifies handling of SpecFiles */ void BrainBrowserWindow::loadFilesFromCommandLine(const std::vector& filenames, const LoadSpecFileMode loadSpecFileMode) { std::vector dataFileTypesDummyNotUsed; loadFiles(this, filenames, dataFileTypesDummyNotUsed, loadSpecFileMode, "", ""); } /** * Load the scene file and the scene with the given name or number * @param sceneFileName * Name of scene file. * @param sceneNameOrNumber * Name or number of scene. Name takes precedence over number. * Scene numbers start at one. */ void BrainBrowserWindow::loadSceneFromCommandLine(const AString& sceneFileName, const AString& sceneNameOrNumber) { std::vector filenames; filenames.push_back(sceneFileName); loadFilesFromCommandLine(filenames, LOAD_SPEC_FILE_CONTENTS_VIA_COMMAND_LINE); bool haveSceneFileError = true; bool haveSceneError = true; FileInformation fileInfo(sceneFileName); const AString nameNoExt = fileInfo.getFileName(); Brain* brain = GuiManager::get()->getBrain(); const int32_t numSceneFiles = brain->getNumberOfSceneFiles(); for (int32_t i = 0; i < numSceneFiles; i++) { SceneFile* sf = brain->getSceneFile(i); if (sf->getFileName().contains(sceneFileName)) { haveSceneFileError = false; Scene* scene = sf->getSceneWithName(sceneNameOrNumber); if (scene == NULL) { bool isValidNumber = false; int sceneNumber = sceneNameOrNumber.toInt(&isValidNumber); if (isValidNumber) { sceneNumber--; // convert to index (numbers start at one) if ((sceneNumber >= 0) && (sceneNumber < sf->getNumberOfScenes())) { scene = sf->getSceneAtIndex(sceneNumber); } } } if (scene != NULL) { GuiManager::get()->processShowSceneDialogAndScene(this, sf, scene); haveSceneError = false; break; } } } if (haveSceneFileError) { const AString msg = ("No scene file named \"" + sceneFileName + "\" was loaded."); WuQMessageBox::errorOk(this, msg); } else if (haveSceneError) { const AString msg = ("No scene with name/number \"" + sceneNameOrNumber + "\" found in scene file."); WuQMessageBox::errorOk(this, msg); } } /** * Load data files. If there are errors, an error message dialog * will be displayed. * * @param parentForDialogs * Parent widget for any dialogs (if NULL this window is used). * @param filenames * Names of files. * @param dataFileTypes * Type for each filename (optional). If the type is not present for a * filename, the type is inferred from the filename's extension. * @param loadSpecFileMode * Specifies handling of SpecFiles * @param username * Username for network file reading * @param password * Password for network file reading * @return true if there are no errors, else false. */ bool BrainBrowserWindow::loadFiles(QWidget* parentForDialogs, const std::vector& filenames, const std::vector dataFileTypes, const LoadSpecFileMode loadSpecFileMode, const AString& username, const AString& password) { QWidget* parentWidget = parentForDialogs; if (parentWidget == NULL) { parentWidget = this; } /* * Pick out specific file types. */ AString specFileName; std::vector volumeFileNames; std::vector surfaceFileNames; std::vector otherFileNames; std::vector otherDataFileTypes; AString typeErrorMessage = ""; const int32_t numFiles = static_cast(filenames.size()); const int32_t numDataTypes = static_cast(dataFileTypes.size()); for (int32_t i = 0; i < numFiles; i++) { const AString name = filenames[i]; bool isValidType = false; DataFileTypeEnum::Enum fileType = DataFileTypeEnum::UNKNOWN; if (i < numDataTypes) { fileType = dataFileTypes[i]; isValidType = true; } else { fileType = DataFileTypeEnum::fromFileExtension(name, &isValidType); if (isValidType == false) { typeErrorMessage.appendWithNewLine("Extension for " + name + " does not match a suppported file type"); } } switch (fileType) { case DataFileTypeEnum::SPECIFICATION: if (specFileName.isEmpty() == false) { QMessageBox::critical(parentWidget, "ERROR", "More than one spec file cannot be loaded"); return false; } specFileName = name; break; case DataFileTypeEnum::SURFACE: surfaceFileNames.push_back(name); break; case DataFileTypeEnum::VOLUME: volumeFileNames.push_back(name); break; default: otherFileNames.push_back(name); otherDataFileTypes.push_back(fileType); break; } } if (typeErrorMessage.isEmpty() == false) { QMessageBox::critical(parentWidget, "ERROR", typeErrorMessage); return false; } /* * Load files in this order: * (1) Spec File - Limit to one. * (2) Volume File * (3) Surface File * (4) All other files. */ std::vector > filesToLoad; const int32_t numVolumeFiles = static_cast(volumeFileNames.size()); for (int32_t i = 0; i < numVolumeFiles; i++) { filesToLoad.push_back(std::make_pair(volumeFileNames[i], DataFileTypeEnum::VOLUME)); } const int32_t numSurfaceFiles = static_cast(surfaceFileNames.size()); for (int32_t i = 0; i < numSurfaceFiles; i++) { filesToLoad.push_back(std::make_pair(surfaceFileNames[i], DataFileTypeEnum::SURFACE)); } const int32_t numOtherFiles = static_cast(otherFileNames.size()); for (int32_t i = 0; i < numOtherFiles; i++) { filesToLoad.push_back(std::make_pair(otherFileNames[i], otherDataFileTypes[i])); } bool createDefaultTabsFlag = false; /* * If there are no models loaded, will want to create default tabs. */ EventModelGetAll modelGetAllEvent; EventManager::get()->sendEvent(modelGetAllEvent.getPointer()); const int32_t numberOfModels = static_cast(modelGetAllEvent.getModels().size()); if (numberOfModels <= 0) { createDefaultTabsFlag = true; } AString errorMessages; ElapsedTimer timer; timer.start(); float specFileTimeStart = 0.0; float specFileTimeEnd = 0.0; bool sceneFileWasLoaded = false; /* * Load spec file (before data files) */ if (specFileName.isEmpty() == false) { SpecFile specFile; try { specFile.readFile(specFileName); } catch (const DataFileException& e) { errorMessages += e.whatString(); QMessageBox::critical(parentWidget, "ERROR", errorMessages); return false; } switch (loadSpecFileMode) { case LOAD_SPEC_FILE_CONTENTS_VIA_COMMAND_LINE: { timer.reset(); // resets timer specFileTimeStart = timer.getElapsedTimeSeconds(); /* * Load all files listed in spec file */ specFile.setAllFilesSelectedForLoading(true); EventSpecFileReadDataFiles readSpecFileEvent(GuiManager::get()->getBrain(), &specFile); if (username.isEmpty() == false) { readSpecFileEvent.setUsernameAndPassword(username, password); } ProgressReportingDialog::runEvent(&readSpecFileEvent, parentWidget, specFile.getFileNameNoPath()); if (readSpecFileEvent.isError()) { if (errorMessages.isEmpty() == false) { errorMessages += "\n"; } errorMessages += readSpecFileEvent.getErrorMessage(); } specFileTimeEnd = timer.getElapsedTimeSeconds(); createDefaultTabsFlag = true; } break; case LOAD_SPEC_FILE_WITH_DIALOG: case LOAD_SPEC_FILE_WITH_DIALOG_VIA_COMMAND_LINE: { if (GuiManager::get()->processShowOpenSpecFileDialog(&specFile, this)) { // Brain* brain = GuiManager::get()->getBrain(); // if (SpecFileManagementDialog::runOpenSpecFileDialog(brain, // &specFile, // this)) { m_toolbar->addDefaultTabsAfterLoadingSpecFile(); createDefaultTabsFlag = true; } } break; } sceneFileWasLoaded = specFile.areAllFilesSelectedForLoadingSceneFiles(); } /* * Prepare to load any data files */ EventDataFileRead loadFilesEvent(GuiManager::get()->getBrain()); if (username.isEmpty() == false) { loadFilesEvent.setUsernameAndPassword(username, password); } /* * Add data files to data file loading event (after loading spec file) */ const int32_t numFilesToLoad = static_cast(filesToLoad.size()); for (int32_t i = 0; i < numFilesToLoad; i++) { AString name = filesToLoad[i].first; const DataFileTypeEnum::Enum fileType = filesToLoad[i].second; loadFilesEvent.addDataFile(fileType, name); } /* * Now, load the data files */ const int32_t numberOfValidFiles = loadFilesEvent.getNumberOfDataFilesToRead(); if (numberOfValidFiles > 0) { ProgressReportingDialog::runEvent(&loadFilesEvent, parentWidget, "Loading Data Files"); errorMessages.appendWithNewLine(loadFilesEvent.getErrorMessage()); /* * Check for errors */ for (int32_t i = 0; i < numberOfValidFiles; i++) { const AString& dataFileName = loadFilesEvent.getDataFileName(i); const DataFileTypeEnum::Enum dataFileType = loadFilesEvent.getDataFileType(i); const AString shortName = FileInformation(dataFileName).getFileName(); if (loadFilesEvent.isFileErrorInvalidStructure(i)) { /* * Allow user to specify the structure */ WuQDataEntryDialog ded("Structure", parentWidget); StructureEnumComboBox* ssc = ded.addStructureEnumComboBox(""); ded.setTextAtTop(("File \"" + shortName + "\"\nhas missing or invalid structure, select it's structure." "\nAfter loading, save file with File Menu->Save Manage Files" "\nto prevent this error."), false); if (ded.exec() == WuQDataEntryDialog::Accepted) { EventDataFileRead loadFileEventStructure(GuiManager::get()->getBrain()); loadFileEventStructure.addDataFile(ssc->getSelectedStructure(), dataFileType, dataFileName); if (username.isEmpty() == false) { loadFileEventStructure.setUsernameAndPassword(username, password); } ProgressReportingDialog::runEvent(&loadFileEventStructure, parentWidget, ("Loading " + shortName)); if (loadFileEventStructure.isError()) { errorMessages.appendWithNewLine(loadFileEventStructure.getErrorMessage()); } else { if (loadFileEventStructure.getNumberOfDataFilesToRead() == 1) { CaretDataFile* cdf = loadFileEventStructure.getDataFileRead(0); if (cdf != NULL) { cdf->setModified(); } } } } else { errorMessages.appendWithNewLine("File \"" + shortName + "\" not loaded due to invalid structure."); } } else if (loadFilesEvent.isFileError(i) == false) { if (dataFileType == DataFileTypeEnum::SCENE) { sceneFileWasLoaded = true; } } } } const float specFileTime = specFileTimeEnd - specFileTimeStart; const float createTabsStartTime = timer.getElapsedTimeSeconds(); const EventBrowserWindowCreateTabs::Mode tabMode = (createDefaultTabsFlag ? EventBrowserWindowCreateTabs::MODE_LOADED_SPEC_FILE : EventBrowserWindowCreateTabs::MODE_LOADED_DATA_FILE); EventBrowserWindowCreateTabs createTabsEvent(tabMode); EventManager::get()->sendEvent(createTabsEvent.getPointer()); const float createTabsTime = timer.getElapsedTimeSeconds() - createTabsStartTime; const float guiStartTime = timer.getElapsedTimeSeconds(); EventManager::get()->sendEvent(EventSurfaceColoringInvalidate().getPointer()); EventManager::get()->sendEvent(EventUserInterfaceUpdate().getPointer()); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); const float guiTime = timer.getElapsedTimeSeconds() - guiStartTime; if (specFileName.isEmpty() == false) { CaretLogInfo("Time to read files from spec file (in GUI) \"" + FileInformation(specFileName).getFileName() + "\" was " + AString::number(timer.getElapsedTimeSeconds()) + " seconds.\n Time to read spec data files was " + AString::number(specFileTime) + " seconds.\n Time to update GUI was " + AString::number(guiTime) + " seconds.\n Time to create tabs was " + AString::number(createTabsTime)); } bool successFlag = true; if (errorMessages.isEmpty() == false) { successFlag = false; QMessageBox::critical(parentWidget, "ERROR", errorMessages); } if (sceneFileWasLoaded) { GuiManager::get()->processShowSceneDialog(this); } EventManager::get()->sendEvent(EventMacDockMenuUpdate().getPointer()); return successFlag; } /** * Called when manage/save loaded files is selected. */ void BrainBrowserWindow::processManageSaveLoadedFiles() { GuiManager::get()->processShowSaveManageFilesDialog(this); // Brain* brain = GuiManager::get()->getBrain(); // SpecFileManagementDialog::runManageFilesDialog(brain, // this); } /** * Exit the program. */ void BrainBrowserWindow::processExitProgram() { GuiManager::get()->exitProgram(this); } /** * Update full screen status. * * @param showFullScreenDisplay * If true, show as full screen, else show as normal screen * @param saveRestoreWindowStatus * If true, save/restore the window status */ void BrainBrowserWindow::processViewFullScreen(bool showFullScreenDisplay, const bool saveRestoreWindowStatus) { if (showFullScreenDisplay == false) { EventManager::get()->blockEvent(EventTypeEnum::EVENT_USER_INTERFACE_UPDATE, true); showNormal(); if (saveRestoreWindowStatus) { restoreWindowComponentStatus(m_normalWindowComponentStatus); } EventManager::get()->blockEvent(EventTypeEnum::EVENT_USER_INTERFACE_UPDATE, false); } else { if (saveRestoreWindowStatus) { saveWindowComponentStatus(m_normalWindowComponentStatus); } /* * Hide and disable Toolbar, Overlay, and Features ToolBox */ m_showToolBarAction->setChecked(true); m_showToolBarAction->trigger(); m_showToolBarAction->setEnabled(false); m_overlayToolBoxAction->setChecked(true); m_overlayToolBoxAction->trigger(); m_overlayToolBoxAction->setEnabled(false); m_featuresToolBoxAction->setChecked(true); m_featuresToolBoxAction->trigger(); m_featuresToolBoxAction->setEnabled(false); showFullScreen(); } EventManager::get()->sendEvent(EventUserInterfaceUpdate().setWindowIndex(m_browserWindowIndex).addToolBar().getPointer()); EventManager::get()->sendEvent(EventGraphicsUpdateOneWindow(m_browserWindowIndex).getPointer()); } /** * Called when view full screen is selected and toggles the status of full screen. */ void BrainBrowserWindow::processViewFullScreenSelected() { const bool toggledStatus = (! isFullScreen()); processViewFullScreen(toggledStatus, true); } /** * Called when tile tabs is selected and toggles the state of tile tabs. */ void BrainBrowserWindow::processViewTileTabs() { const bool toggledStatus = (! m_viewTileTabsSelected); setViewTileTabs(toggledStatus); } /** * Set the status of tile tabs. */ void BrainBrowserWindow::setViewTileTabs(const bool newStatus) { m_viewTileTabsSelected = newStatus; EventManager::get()->sendEvent(EventUserInterfaceUpdate().setWindowIndex(m_browserWindowIndex).addToolBar().getPointer()); EventManager::get()->sendEvent(EventGraphicsUpdateOneWindow(m_browserWindowIndex).getPointer()); } /** * View the Tile Tabs Configuration Dialog. */ void BrainBrowserWindow::processViewTileTabsConfigurationDialog() { GuiManager::get()->processShowTileTabsConfigurationDialog(this); } /** * Restore the status of window components. * @param wcs * Window component status that is restored. */ void BrainBrowserWindow::restoreWindowComponentStatus(const WindowComponentStatus& wcs) { if (wcs.windowGeometry.isEmpty() == false) { restoreGeometry(wcs.windowGeometry); } if (wcs.windowState.isEmpty() == false) { restoreState(wcs.windowState); } m_showToolBarAction->setEnabled(true); if (wcs.isToolBarDisplayed) { m_showToolBarAction->setChecked(false); m_showToolBarAction->trigger(); } else { m_showToolBarAction->setChecked(true); m_showToolBarAction->trigger(); } m_overlayToolBoxAction->setEnabled(true); if (wcs.isOverlayToolBoxDisplayed) { m_overlayToolBoxAction->blockSignals(true); m_overlayToolBoxAction->setChecked(false); m_overlayToolBoxAction->blockSignals(false); m_overlayToolBoxAction->trigger(); } else { m_overlayToolBoxAction->blockSignals(true); m_overlayToolBoxAction->setChecked(true); m_overlayToolBoxAction->blockSignals(false); m_overlayToolBoxAction->trigger(); } m_featuresToolBoxAction->setEnabled(true); if (wcs.isFeaturesToolBoxDisplayed) { m_featuresToolBoxAction->blockSignals(true); m_featuresToolBoxAction->setChecked(false); m_featuresToolBoxAction->blockSignals(false); m_featuresToolBoxAction->trigger(); } else { m_featuresToolBoxAction->blockSignals(true); m_featuresToolBoxAction->setChecked(true); m_featuresToolBoxAction->blockSignals(false); m_featuresToolBoxAction->trigger(); } if (m_featuresToolBox != NULL) { if (wcs.featuresGeometry.isEmpty() == false) { m_featuresToolBox->restoreGeometry(wcs.featuresGeometry); } } } /** * Save the status of window components. * @param wcs * Will contains status after exit. * @param hideComponents * If true, any components (toolbar/toolbox) will be hidden. */ void BrainBrowserWindow::saveWindowComponentStatus(WindowComponentStatus& wcs) { wcs.windowState = saveState(); wcs.windowGeometry = saveGeometry(); if (m_featuresToolBoxAction->isChecked()) { if (m_featuresToolBox != NULL) { wcs.featuresGeometry = m_featuresToolBox->saveGeometry(); } } wcs.isToolBarDisplayed = m_showToolBarAction->isChecked(); wcs.isOverlayToolBoxDisplayed = m_overlayToolBoxAction->isChecked(); wcs.isFeaturesToolBoxDisplayed = m_featuresToolBoxAction->isChecked(); m_showToolBarAction->setEnabled(false); m_overlayToolBoxAction->setEnabled(false); m_featuresToolBoxAction->setEnabled(false); } /** * Adds a new tab to the window. */ void BrainBrowserWindow::processNewTab() { m_toolbar->addNewTab(); } /** * Adds a new tab to the window. */ void BrainBrowserWindow::processDuplicateTab() { BrowserTabContent* previousTabContent = getBrowserTabContent(); m_toolbar->addNewTabCloneContent(previousTabContent); } /** * Called when move all tabs to one window is selected. */ void BrainBrowserWindow::processMoveAllTabsToOneWindow() { /* * Wait cursor */ CursorDisplayScoped cursor; cursor.showWaitCursor(); std::vector otherTabContent; GuiManager::get()->closeOtherWindowsAndReturnTheirTabContent(this, otherTabContent); const int32_t numOtherTabs = static_cast(otherTabContent.size()); for (int32_t i = 0; i < numOtherTabs; i++) { m_toolbar->addNewTabWithContent(otherTabContent[i]); m_toolbar->updateToolBar(); } EventManager::get()->sendEvent(EventSurfaceColoringInvalidate().getPointer()); EventManager::get()->sendEvent(EventUserInterfaceUpdate().getPointer()); EventManager::get()->sendEvent(EventGraphicsUpdateOneWindow(m_browserWindowIndex).getPointer()); } /** * Called when the move tab to window is about to be displayed. */ void BrainBrowserWindow::processMoveSelectedTabToWindowMenuAboutToBeDisplayed() { m_moveSelectedTabToWindowMenu->clear(); if (getBrowserTabContent() == NULL) { return; } /* * Allow movement of tab to new window ONLY if this window * contains more than one tab. */ if (m_toolbar->tabBar->count() > 1) { QAction* toNewWindowAction = new QAction("New Window", m_moveSelectedTabToWindowMenu); toNewWindowAction->setData(qVariantFromValue((void*)NULL)); m_moveSelectedTabToWindowMenu->addAction(toNewWindowAction); } std::vector browserWindows = GuiManager::get()->getAllOpenBrainBrowserWindows(); for (int32_t i = 0; i < static_cast(browserWindows.size()); i++) { if (browserWindows[i] != this) { QAction* action = new QAction(browserWindows[i]->windowTitle(), m_moveSelectedTabToWindowMenu); action->setData(qVariantFromValue((void*)browserWindows[i])); m_moveSelectedTabToWindowMenu->addAction(action); } } } /** * Called when move tab to window menu item is selected. * This window may close if there are no more tabs after * the tab is removed. * @param action * Action from menu item that was selected. */ void BrainBrowserWindow::processMoveSelectedTabToWindowMenuSelection(QAction* action) { if (action != NULL) { /* * Wait cursor */ CursorDisplayScoped cursor; cursor.showWaitCursor(); void* p = action->data().value(); BrainBrowserWindow* moveToBrowserWindow = (BrainBrowserWindow*)p; BrowserTabContent* btc = getBrowserTabContent(); if (moveToBrowserWindow != NULL) { m_toolbar->removeTabWithContent(btc); moveToBrowserWindow->m_toolbar->addNewTabWithContent(btc); } else { EventBrowserWindowNew newWindow(this, btc); EventManager::get()->sendEvent(newWindow.getPointer()); if (newWindow.isError()) { cursor.restoreCursor(); QMessageBox::critical(this, "", newWindow.getErrorMessage()); return; } m_toolbar->removeTabWithContent(btc); } if (m_toolbar->tabBar->count() <= 0) { close(); } EventManager::get()->sendEvent(EventUserInterfaceUpdate().getPointer()); } } /** * Called to move the overlay toolbox to the left side of the window. */ void BrainBrowserWindow::processMoveOverlayToolBoxToLeft() { moveOverlayToolBox(Qt::LeftDockWidgetArea); } /** * Called to move the overlay toolbox to the bottom side of the window. */ void BrainBrowserWindow::processMoveOverlayToolBoxToBottom() { moveOverlayToolBox(Qt::BottomDockWidgetArea); } /** * Called to move the overlay toolbox to float outside of the window. */ void BrainBrowserWindow::processMoveOverlayToolBoxToFloat() { moveOverlayToolBox(Qt::NoDockWidgetArea); } /** * Called to hide the overlay toolbox. */ void BrainBrowserWindow::processHideOverlayToolBox() { processShowOverlayToolBox(false); } /** * Called to move the layers toolbox to the right side of the window. */ void BrainBrowserWindow::processMoveFeaturesToolBoxToRight() { moveFeaturesToolBox(Qt::RightDockWidgetArea); } /** * Called to move the layers toolbox to float outside of the window. */ void BrainBrowserWindow::processMoveFeaturesToolBoxToFloat() { moveFeaturesToolBox(Qt::NoDockWidgetArea); } /** * Called to hide the layers tool box. */ void BrainBrowserWindow::processHideFeaturesToolBox() { if (m_featuresToolBoxAction->isChecked()) { m_featuresToolBoxAction->trigger(); } } /** * Called to display the layers toolbox. */ void BrainBrowserWindow::processShowFeaturesToolBox(bool status) { if (status) { m_featuresToolBoxAction->setToolTip("Hide Features Toolbox"); EventManager::get()->sendEvent(EventUserInterfaceUpdate().setWindowIndex(m_browserWindowIndex).addToolBox().getPointer()); } else { m_featuresToolBoxAction->setToolTip("Show Features Toolbox"); } } /** * Move the overlay toolbox to the desired location. * @param area * DockWidget location. */ void BrainBrowserWindow::moveFeaturesToolBox(Qt::DockWidgetArea area) { switch (area) { case Qt::LeftDockWidgetArea: CaretAssertMessage(0, "Layers toolbox not allowed on left"); break; case Qt::RightDockWidgetArea: m_featuresToolBox->setFloating(false); addDockWidget(Qt::RightDockWidgetArea, m_featuresToolBox); if (m_featuresToolBoxAction->isChecked() == false) { m_featuresToolBoxAction->trigger(); } break; case Qt::TopDockWidgetArea: CaretAssertMessage(0, "Layers toolbox not allowed on top"); break; case Qt::BottomDockWidgetArea: CaretAssertMessage(0, "Layers toolbox not allowed on bottom"); break; default: m_featuresToolBox->setFloating(true); if (m_featuresToolBoxAction->isChecked() == false) { m_featuresToolBoxAction->trigger(); } break; } } /** * Move the overlay toolbox to the desired location. * @param area * DockWidget location. */ void BrainBrowserWindow::moveOverlayToolBox(Qt::DockWidgetArea area) { bool isVisible = false; switch (area) { case Qt::LeftDockWidgetArea: m_overlayHorizontalToolBox->setVisible(false); m_overlayVerticalToolBox->setFloating(false); addDockWidget(Qt::LeftDockWidgetArea, m_overlayVerticalToolBox); m_overlayVerticalToolBox->setVisible(true); m_overlayActiveToolBox = m_overlayVerticalToolBox; isVisible = true; break; case Qt::RightDockWidgetArea: CaretAssertMessage(0, "Overlay toolbox not allowed on right"); break; case Qt::TopDockWidgetArea: CaretAssertMessage(0, "Overlay toolbox not allowed on top"); break; case Qt::BottomDockWidgetArea: m_overlayVerticalToolBox->setVisible(false); m_overlayHorizontalToolBox->setFloating(false); addDockWidget(Qt::BottomDockWidgetArea, m_overlayHorizontalToolBox); m_overlayHorizontalToolBox->setVisible(true); m_overlayActiveToolBox = m_overlayHorizontalToolBox; isVisible = true; break; default: m_overlayActiveToolBox->setVisible(true); m_overlayActiveToolBox->setFloating(true); isVisible = true; break; } processShowOverlayToolBox(isVisible); } /** * Remove and return all tabs from this toolbar. * After this the window containing this toolbar * will contain no tabs! * * @param allTabContent * Will contain the content from the tabs upon return. */ void BrainBrowserWindow::removeAndReturnAllTabs(std::vector& allTabContent) { m_toolbar->removeAndReturnAllTabs(allTabContent); } /** * @return Return the active browser tab content in * this browser window. */ BrowserTabContent* BrainBrowserWindow::getBrowserTabContent() { return m_toolbar->getTabContentFromSelectedTab(); } /** * @return Return the active browser tab content in * this browser window. */ const BrowserTabContent* BrainBrowserWindow::getBrowserTabContent() const { return m_toolbar->getTabContentFromSelectedTab(); } /** * get browser tab content for tab with specified tab Index * @param tabIndex * Desired tabIndex * @return Return the active browser tab content in * this browser window. */ BrowserTabContent* BrainBrowserWindow::getBrowserTabContent(int tabIndex) { return m_toolbar->getTabContentFromTab(tabIndex); } /** * Returns a popup menu for the main window. * Overrides that in QMainWindow and prevents the * default context menu from appearing. * * @return Context menu for display or NULL if * nothing available. */ QMenu* BrainBrowserWindow::createPopupMenu() { return NULL; } /** * Open a connection to the allen brain institute database. */ void BrainBrowserWindow::processConnectToAllenDataBase() { GuiManager::get()->processShowAllenDataBaseWebView(this); } /** * Open a connection to the human connectome project database. */ void BrainBrowserWindow::processConnectToConnectomeDataBase() { GuiManager::get()->processShowConnectomeDataBaseWebView(this); } /** * Load the HCP Website into the user's web browser. */ void BrainBrowserWindow::processHcpWebsiteInBrowser() { QUrl url("https://humanconnectome.org"); QDesktopServices::openUrl(url); } /** * Report a Workbench bug. */ void BrainBrowserWindow::processReportWorkbenchBug() { GuiManager::get()->processShowBugReportDialog(this, m_openGLWidget->getOpenGLInformation()); } /** * Load the HCP Feature Request Website into the user's web browser. */ void BrainBrowserWindow::processHcpFeatureRequestWebsiteInBrowser() { QUrl url("http://humanconnectome.org/contact/feature-request.php"); QDesktopServices::openUrl(url); } /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ SceneClass* BrainBrowserWindow::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "BrainBrowserWindow", 1); m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); /* * Save the selected tile tabs configuration as the scene configuration */ if (m_viewTileTabsSelected) { /* * Note: The number of rows and columns in the default tile tabs * configuration is updated each time the graphics region of the * window is drawn in BrainOpenGLWidget::paintGL(). */ const TileTabsConfiguration* tileTabs = getSelectedTileTabsConfiguration(); if (tileTabs != NULL) { sceneClass->addString("m_sceneTileTabsConfiguration", tileTabs->encodeInXML()); } } //sceneClass->addString("m_selectedTileTabsConfigurationUniqueIdentifier", // m_selectedTileTabsConfigurationUniqueIdentifier); /* * Save toolbar */ sceneClass->addClass(m_toolbar->saveToScene(sceneAttributes, "m_toolbar")); /* * Save overlay toolbox */ { AString orientationName = ""; if (m_overlayActiveToolBox == m_overlayHorizontalToolBox) { orientationName = "horizontal"; } else if (m_overlayActiveToolBox == m_overlayVerticalToolBox) { orientationName = "vertical"; } SceneClass* overlayToolBoxClass = new SceneClass("overlayToolBox", "OverlayToolBox", 1); overlayToolBoxClass->addString("orientation", orientationName); overlayToolBoxClass->addBoolean("floating", m_overlayActiveToolBox->isFloating()); overlayToolBoxClass->addBoolean("visible", m_overlayActiveToolBox->isVisible()); sceneClass->addClass(overlayToolBoxClass); sceneClass->addClass(m_overlayActiveToolBox->saveToScene(sceneAttributes, "m_overlayActiveToolBox")); } switch (sceneAttributes->getSceneType()) { case SceneTypeEnum::SCENE_TYPE_FULL: break; case SceneTypeEnum::SCENE_TYPE_GENERIC: break; } /* * Save features toolbox */ { SceneClass* featureToolBoxClass = new SceneClass("featureToolBox", "FeatureToolBox", 1); featureToolBoxClass->addBoolean("floating", m_featuresToolBox->isFloating()); featureToolBoxClass->addBoolean("visible", m_featuresToolBox->isVisible()); sceneClass->addClass(featureToolBoxClass); sceneClass->addClass(m_featuresToolBox->saveToScene(sceneAttributes, "m_featuresToolBox")); } /* * Position and size */ SceneWindowGeometry swg(this); sceneClass->addClass(swg.saveToScene(sceneAttributes, "geometry")); sceneClass->addBoolean("isFullScreen", isFullScreen()); sceneClass->addBoolean("isMaximized", isMaximized()); sceneClass->addBoolean("m_viewTileTabsAction", m_viewTileTabsSelected); return sceneClass; } /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * SceneClass containing the state that was previously * saved and should be restored. */ void BrainBrowserWindow::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } /* * Restore toolbar */ const SceneClass* toolbarClass = sceneClass->getClass("m_toolbar"); if (toolbarClass != NULL) { m_toolbar->restoreFromScene(sceneAttributes, toolbarClass); } m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); /* * Restore Unique ID of selected tile tabs configuration. * If not valid, use default configuration */ m_selectedTileTabsConfigurationUniqueIdentifier = sceneClass->getStringValue("m_selectedTileTabsConfigurationUniqueIdentifier", ""); CaretPreferences* caretPreferences = SessionManager::get()->getCaretPreferences(); TileTabsConfiguration* selectedConfiguration = caretPreferences->getTileTabsConfigurationByUniqueIdentifier(m_selectedTileTabsConfigurationUniqueIdentifier); if (selectedConfiguration == NULL) { m_selectedTileTabsConfigurationUniqueIdentifier = m_defaultTileTabsConfiguration->getUniqueIdentifier(); } /* * Restoration status for full screen and tab tiles * * If "m_screenMode" is found, the scene is an older scene that was * created prior to splitting Full Screen and Tile Tabs into * separate functionality. */ bool restoreToFullScreen = false; bool restoreToTabTiles = false; const SceneObject* screenModeObject = sceneClass->getObjectWithName("m_screenMode"); if (screenModeObject != NULL) { const SceneEnumeratedType* screenEnum = dynamic_cast(screenModeObject); if (screenEnum != NULL) { const AString screenModeName = screenEnum->stringValue(); if (screenModeName == "NORMAL") { } else if (screenModeName == "FULL_SCREEN") { restoreToFullScreen = true; } else if (screenModeName == "TAB_MONTAGE") { restoreToTabTiles = true; } else if (screenModeName == "TAB_MONTAGE_FULL_SCREEN") { restoreToTabTiles = true; restoreToFullScreen = true; } else { CaretLogWarning("Unrecognized obsolete screen mode: " + screenModeName); } } } else { restoreToFullScreen = sceneClass->getBooleanValue("isFullScreen", false); restoreToTabTiles = sceneClass->getBooleanValue("m_viewTileTabsAction", false); /* * If tile tabs was saved to the scene, restore it as the scenes tile tabs configuration */ if (restoreToTabTiles) { const AString tileTabsConfigString = sceneClass->getStringValue("m_sceneTileTabsConfiguration"); if ( ! tileTabsConfigString.isEmpty()) { m_sceneTileTabsConfiguration->decodeFromXML(tileTabsConfigString); m_sceneTileTabsConfiguration->setName(m_sceneTileTabsConfigurationText + " " + sceneAttributes->getSceneName()); m_selectedTileTabsConfigurationUniqueIdentifier = m_sceneTileTabsConfiguration->getUniqueIdentifier(); } } } m_normalWindowComponentStatus = m_defaultWindowComponentStatus; processViewFullScreen(restoreToFullScreen, false); setViewTileTabs(restoreToTabTiles); /* * Position and size */ SceneWindowGeometry swg(this); swg.restoreFromScene(sceneAttributes, sceneClass->getClass("geometry")); EventManager::get()->sendEvent(EventUserInterfaceUpdate().getPointer()); QApplication::processEvents(); if (restoreToFullScreen == false) { /* * Restore feature toolbox */ const SceneClass* featureToolBoxClass = sceneClass->getClass("featureToolBox"); if (featureToolBoxClass != NULL) { const bool toolBoxVisible = featureToolBoxClass->getBooleanValue("visible", true); const bool toolBoxFloating = featureToolBoxClass->getBooleanValue("floating", false); if (toolBoxVisible) { if (toolBoxFloating) { processMoveFeaturesToolBoxToFloat(); } else { processMoveFeaturesToolBoxToRight(); } } m_featuresToolBoxAction->blockSignals(true); m_featuresToolBoxAction->setChecked(! toolBoxVisible); m_featuresToolBoxAction->blockSignals(false); m_featuresToolBoxAction->trigger(); //processShowFeaturesToolBox(toolBoxVisible); m_featuresToolBox->restoreFromScene(sceneAttributes, sceneClass->getClass("m_featuresToolBox")); } /* * Restore overlay toolbox */ const SceneClass* overlayToolBoxClass = sceneClass->getClass("overlayToolBox"); if (overlayToolBoxClass != NULL) { const AString orientationName = overlayToolBoxClass->getStringValue("orientation", "horizontal"); const bool toolBoxVisible = overlayToolBoxClass->getBooleanValue("visible", true); const bool toolBoxFloating = overlayToolBoxClass->getBooleanValue("floating", false); if (orientationName == "horizontal") { processMoveOverlayToolBoxToBottom(); } else { processMoveOverlayToolBoxToLeft(); } if (toolBoxFloating) { processMoveOverlayToolBoxToFloat(); } processShowOverlayToolBox(toolBoxVisible); m_overlayActiveToolBox->restoreFromScene(sceneAttributes, sceneClass->getClass("m_overlayActiveToolBox")); } } switch (sceneAttributes->getSceneType()) { case SceneTypeEnum::SCENE_TYPE_FULL: break; case SceneTypeEnum::SCENE_TYPE_GENERIC: break; } const bool maximizedWindow = sceneClass->getBooleanValue("isMaximized", false); if (maximizedWindow) { showMaximized(); } } /** * Get the viewport size for the window. * * @param w * Output width. * @param h * Output height. */ void BrainBrowserWindow::getViewportSize(int &w, int &h) { m_openGLWidget->getViewPortSize(w,h); } /** * @return A string describing the object's content. */ AString BrainBrowserWindow::toString() const { AString msg; msg.appendWithNewLine("Window " + AString::number(getBrowserWindowIndex() + 1) + ":"); const BrowserTabContent* btc = getBrowserTabContent(); if (btc != NULL) { msg.appendWithNewLine(btc->toString()); } return msg; } /** * Get a text description of the window's content. * * @param descriptionOut * Description of the window's content. */ void BrainBrowserWindow::getDescriptionOfContent(PlainTextStringBuilder& descriptionOut) const { descriptionOut.addLine("Window " + AString::number(getBrowserWindowIndex() + 1) + ":"); std::vector tabContent; if (isTileTabsSelected()) { m_toolbar->getAllTabContent(tabContent); } else { BrowserTabContent* btc = m_toolbar->getTabContentFromSelectedTab(); if (btc != NULL) { tabContent.push_back(btc); } } descriptionOut.pushIndentation(); for (std::vector::iterator iter = tabContent.begin(); iter != tabContent.end(); iter++) { const BrowserTabContent* btc = *iter; btc->getDescriptionOfContent(descriptionOut); } descriptionOut.popIndentation(); } workbench-1.1.1/src/GuiQt/BrainBrowserWindow.h000066400000000000000000000336021255417355300212720ustar00rootroot00000000000000 #ifndef __BRAIN_BROWSER_WINDOW_H__ #define __BRAIN_BROWSER_WINDOW_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include "AString.h" #include "DataFileTypeEnum.h" #include "EventListenerInterface.h" #include "SceneableInterface.h" class QAction; class QActionGroup; class QDockWidget; class QMenu; namespace caret { class BrainBrowserWindowToolBar; class BrainBrowserWindowOrientedToolBox; class BrainOpenGLWidget; class BrowserTabContent; class PlainTextStringBuilder; class SceneClassAssistant; class TileTabsConfiguration; /** * The brain browser window is the viewer for * brain models. It may contain multiple tabs * with each tab displaying brain models. */ class BrainBrowserWindow : public QMainWindow, public EventListenerInterface, public SceneableInterface { Q_OBJECT public: virtual ~BrainBrowserWindow(); virtual void receiveEvent(Event* event); BrowserTabContent* getBrowserTabContent(); const BrowserTabContent* getBrowserTabContent() const; BrowserTabContent* getBrowserTabContent(int tabIndex); QMenu* createPopupMenu(); void removeAndReturnAllTabs(std::vector& allTabContent); int32_t getBrowserWindowIndex() const; bool isTileTabsSelected() const; /** * Mode for loading spec files */ enum LoadSpecFileMode { /** Do not show spec file dialog, just load all files listed in spec file listed on command line at program startup */ LOAD_SPEC_FILE_CONTENTS_VIA_COMMAND_LINE, /** Show spec file in spec file dialog for user selections */ LOAD_SPEC_FILE_WITH_DIALOG, /** Show spec file in spec file dialog for user selections from spec file listed on command line at program startup */ LOAD_SPEC_FILE_WITH_DIALOG_VIA_COMMAND_LINE }; void loadFilesFromCommandLine(const std::vector& filenames, const LoadSpecFileMode loadSpecFileMode); void loadSceneFromCommandLine(const AString& sceneFileName, const AString& sceneNameOrNumber); bool loadFilesFromNetwork(QWidget* parentForDialogs, const std::vector& filenames, const std::vector dataFileTypes, const AString& username, const AString& password); virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); void getViewportSize(int &w, int &h); TileTabsConfiguration* getSelectedTileTabsConfiguration(); void setSelectedTileTabsConfiguration(TileTabsConfiguration* configuration); void resetGraphicsWidgetMinimumSize(); void setGraphicsWidgetFixedSize(const int32_t width, const int32_t height); void getGraphicsWidgetSize(int32_t& widthOut, int32_t& heightOut) const; AString toString() const; virtual void getDescriptionOfContent(PlainTextStringBuilder& descriptionOut) const; static int32_t loadRecentSpecFileMenu(QMenu* recentSpecFileMenu); protected: void closeEvent(QCloseEvent* event); void keyPressEvent(QKeyEvent* event); private slots: void processAboutWorkbench(); void processInformationDialog(); void processNewWindow(); void processNewTab(); void processDuplicateTab(); void processDataFileLocationOpen(); void processDataFileOpen(); void processManageSaveLoadedFiles(); void processCaptureImage(); void processRecordMovie(); void processEditPreferences(); void processCloseAllFiles(); void processCloseWindow(); void processExitProgram(); void processMoveAllTabsToOneWindow(); void processViewFullScreenSelected(); void processViewTileTabs(); void processViewTileTabsConfigurationDialog(); void processShowHelpInformation(); void processShowIdentifyBrainordinateDialog(); void processMoveOverlayToolBoxToLeft(); void processMoveOverlayToolBoxToBottom(); void processMoveOverlayToolBoxToFloat(); void processHideOverlayToolBox(); void processMoveFeaturesToolBoxToRight(); void processMoveFeaturesToolBoxToFloat(); void processHideFeaturesToolBox(); void processMoveSelectedTabToWindowMenuAboutToBeDisplayed(); void processMoveSelectedTabToWindowMenuSelection(QAction*); void processRecentSpecFileMenuAboutToBeDisplayed(); void processRecentSpecFileMenuSelection(QAction*); void processShowOverlayToolBox(bool); void processShowFeaturesToolBox(bool); void processOverlayHorizontalToolBoxVisibilityChanged(bool); void processOverlayVerticalToolBoxVisibilityChanged(bool); void processFileMenuAboutToShow(); void processDataMenuAboutToShow(); void processViewMenuAboutToShow(); void processTileTabsMenuAboutToBeDisplayed(); void processTileTabsMenuSelection(QAction*); void processSurfaceMenuInformation(); void processSurfaceMenuPrimaryAnatomical(); void processConnectToAllenDataBase(); void processConnectToConnectomeDataBase(); void processHcpWebsiteInBrowser(); void processHcpFeatureRequestWebsiteInBrowser(); void processReportWorkbenchBug(); void processShowSurfacePropertiesDialog(); void processDevelopGraphicsTiming(); void processDevelopExportVtkFile(); void developerMenuAboutToShow(); void developerMenuFlagTriggered(QAction*); void processProjectFoci(); void processSplitBorderFiles(); private: // Contains status of components such as enter/exit full screen struct WindowComponentStatus { bool isFeaturesToolBoxDisplayed; bool isOverlayToolBoxDisplayed; bool isToolBarDisplayed; QByteArray windowState; QByteArray windowGeometry; QByteArray featuresGeometry; }; enum CreateDefaultTabsMode { CREATE_DEFAULT_TABS_YES, CREATE_DEFAULT_TABS_NO }; BrainBrowserWindow(const int browserWindowIndex, BrowserTabContent* browserTabContent, const CreateDefaultTabsMode createDefaultTabsMode, QWidget* parent = 0, Qt::WindowFlags flags = 0); BrainBrowserWindow(const BrainBrowserWindow&); BrainBrowserWindow& operator=(const BrainBrowserWindow&); bool loadFiles(QWidget* parentForDialogs, const std::vector& filenames, const std::vector dataFileTypes, const LoadSpecFileMode loadSpecFileMode, const AString& username, const AString& password); void createActions(); void createActionsUsedByToolBar(); void createMenus(); QMenu* createMenuDevelop(); QMenu* createMenuFile(); QMenu* createMenuView(); QMenu* createMenuViewMoveOverlayToolBox(); QMenu* createMenuViewMoveFeaturesToolBox(); QMenu* createMenuConnect(); QMenu* createMenuData(); QMenu* createMenuSurface(); QMenu* createMenuVolume(); QMenu* createMenuWindow(); QMenu* createMenuHelp(); void moveOverlayToolBox(Qt::DockWidgetArea area); void moveFeaturesToolBox(Qt::DockWidgetArea area); void restoreWindowComponentStatus(const WindowComponentStatus& wcs); void saveWindowComponentStatus(WindowComponentStatus& wcs); void openSpecFile(const AString& specFileName); void processViewFullScreen(bool showFullScreenDisplay, const bool saveRestoreWindowStatus); void setViewTileTabs(const bool newStatus); bool isMacOptionKeyDown() const; /** Index of this window */ int32_t m_browserWindowIndex; BrainOpenGLWidget* m_openGLWidget; BrainBrowserWindowToolBar* m_toolbar; QAction* m_aboutWorkbenchAction; QAction* m_newWindowAction; QAction* m_newTabAction; QAction* m_duplicateTabAction; QAction* m_openFileAction; QAction* m_openLocationAction; QAction* m_manageFilesAction; QAction* m_closeSpecFileAction; QAction* m_closeTabAction; QAction* m_closeWindowAction; AString m_closeWindowActionConfirmTitle; AString m_closeWindowActionNoConfirmTitle; bool m_closeWithoutConfirmationFlag; QAction* m_captureImageAction; QAction* m_recordMovieAction; QAction* m_preferencesAction; QAction* m_exitProgramAction; QAction* m_showToolBarAction; QMenu* m_viewMoveFeaturesToolBoxMenu; QMenu* m_viewMoveOverlayToolBoxMenu; QAction* m_viewFullScreenAction; QAction* m_viewTileTabsAction; bool m_viewTileTabsSelected; QMenu* m_tileTabsMenu; QAction* m_createAndEditTileTabsAction; QAction* m_nextTabAction; QAction* m_previousTabAction; QAction* m_renameSelectedTabAction; QAction* m_moveTabsInWindowToNewWindowsAction; QAction* m_moveTabsFromAllWindowsToOneWindowAction; QAction* m_bringAllToFrontAction; QAction* m_tileWindowsAction; QAction* m_informationDialogAction; QAction* m_connectToAllenDatabaseAction; QAction* m_connectToConnectomeDatabaseAction; QAction* m_helpHcpWebsiteAction; QAction* m_helpHcpFeatureRequestAction; QAction* m_helpWorkbenchBugReportAction; QAction* m_developMenuAction; QActionGroup* m_developerFlagsActionGroup; QAction* m_developerGraphicsTimingAction; QAction* m_developerExportVtkFileAction; QAction* m_overlayToolBoxAction; QAction* m_featuresToolBoxAction; QAction* m_dataFociProjectAction; QAction* m_dataBorderFilesSplitAction; QMenu* m_moveSelectedTabToWindowMenu; QMenu* m_recentSpecFileMenu; AString m_recentSpecFileMenuOpenConfirmTitle; AString m_recentSpecFileMenuLoadNoConfirmTitle; BrainBrowserWindowOrientedToolBox* m_overlayHorizontalToolBox; BrainBrowserWindowOrientedToolBox* m_overlayVerticalToolBox; BrainBrowserWindowOrientedToolBox* m_overlayActiveToolBox; BrainBrowserWindowOrientedToolBox* m_featuresToolBox; AString m_selectedTileTabsConfigurationUniqueIdentifier; TileTabsConfiguration* m_defaultTileTabsConfiguration; TileTabsConfiguration* m_sceneTileTabsConfiguration; AString m_sceneTileTabsConfigurationText; static AString s_previousOpenFileNameFilter; static AString s_previousOpenFileDirectory; static QByteArray s_previousOpenFileGeometry; WindowComponentStatus m_defaultWindowComponentStatus; WindowComponentStatus m_normalWindowComponentStatus; static bool s_firstWindowFlag; friend class BrainBrowserWindowToolBar; friend class GuiManager; SceneClassAssistant* m_sceneAssistant; /** X position from scene file for first window */ static int32_t s_sceneFileFirstWindowX; /** Y position from scene file for first window */ static int32_t s_sceneFileFirstWindowY; }; #ifdef __BRAIN_BROWSER_WINDOW_DECLARE__ AString BrainBrowserWindow::s_previousOpenFileNameFilter; AString BrainBrowserWindow::s_previousOpenFileDirectory; QByteArray BrainBrowserWindow::s_previousOpenFileGeometry; bool BrainBrowserWindow::s_firstWindowFlag = true; int32_t BrainBrowserWindow::s_sceneFileFirstWindowX = -1; int32_t BrainBrowserWindow::s_sceneFileFirstWindowY = -1; #endif // __BRAIN_BROWSER_WINDOW_DECLARE__ } #endif // __BRAIN_BROWSER_WINDOW_H__ workbench-1.1.1/src/GuiQt/BrainBrowserWindowComboBox.cxx000066400000000000000000000116411255417355300232750ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __BRAIN_BROWSER_WINDOW_COMBO_BOX_DECLARE__ #include "BrainBrowserWindowComboBox.h" #undef __BRAIN_BROWSER_WINDOW_COMBO_BOX_DECLARE__ #include #include "BrainBrowserWindow.h" #include "CaretAssert.h" #include "GuiManager.h" using namespace caret; /** * \class caret::BrainBrowserWindowComboBox * \brief Combo box for selection of browser windows * \ingroup GuiQt */ /** * Constructor. */ BrainBrowserWindowComboBox::BrainBrowserWindowComboBox(QObject* parent) : WuQWidget(parent) { m_comboBox = new QComboBox(); QObject::connect(m_comboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(comboBoxIndexSelected(int))); } /** * Destructor. */ BrainBrowserWindowComboBox::~BrainBrowserWindowComboBox() { } /** * Called when the user selects an item from the combo box. * @param indx * Index of item selected. */ void BrainBrowserWindowComboBox::comboBoxIndexSelected(int /*indx*/) { BrainBrowserWindow* bbw = getSelectedBrowserWindow(); if (bbw != NULL) { emit browserWindowIndexSelected(bbw->getBrowserWindowIndex()); emit browserWindowSelected(bbw); } } /** * Update the combo box. */ void BrainBrowserWindowComboBox::updateComboBox() { m_comboBox->blockSignals(true); std::vector browserWindows = GuiManager::get()->getAllOpenBrainBrowserWindows(); BrainBrowserWindow* selectedWindow = getSelectedBrowserWindow(); m_comboBox->clear(); int32_t defaultIndex = 0; const int32_t numWindows = static_cast(browserWindows.size()); for (int32_t i = 0; i < numWindows; i++) { BrainBrowserWindow* bbw = browserWindows[i]; const int32_t browserWindowIndex = bbw->getBrowserWindowIndex(); m_comboBox->addItem(QString::number(browserWindowIndex + 1)); m_comboBox->setItemData(i, qVariantFromValue((void*)bbw)); if (bbw == selectedWindow) { defaultIndex = i; } } if (defaultIndex < m_comboBox->count()) { m_comboBox->setCurrentIndex(defaultIndex); } m_comboBox->blockSignals(false); } /** * @return The widget inside this control. */ QWidget* BrainBrowserWindowComboBox::getWidget() { return m_comboBox; } /** * Set the selected browser window using the browser window index. * If the index is invalid selection will not change. * * @param browserWindowIndex * Index of window. */ void BrainBrowserWindowComboBox::setBrowserWindowByIndex(const int32_t browserWindowIndex) { for (int32_t i = 0; i < m_comboBox->count(); i++) { void* pointer = m_comboBox->itemData(i).value(); BrainBrowserWindow* bbw = (BrainBrowserWindow*)pointer; if (bbw->getBrowserWindowIndex() == browserWindowIndex) { m_comboBox->blockSignals(true); m_comboBox->setCurrentIndex(i); m_comboBox->blockSignals(false); return; } } } /** * Set the selected browser window. * If the window is invalid selection will not change. * * @param browserWindow * The window. */ void BrainBrowserWindowComboBox::setBrowserWindow(BrainBrowserWindow* browserWindow) { CaretAssert(browserWindow); setBrowserWindowByIndex(browserWindow->getBrowserWindowIndex()); } /** * @return Index of selected browser window. A negative value * is returned if invalid. */ int32_t BrainBrowserWindowComboBox::getSelectedBrowserWindowIndex() const { int32_t indx = -1; BrainBrowserWindow* bbw = getSelectedBrowserWindow(); if (bbw != NULL) { indx = bbw->getBrowserWindowIndex(); } return indx; } /** * @return Selected browser window. NULL is returned if invalid. */ BrainBrowserWindow* BrainBrowserWindowComboBox::getSelectedBrowserWindow() const { BrainBrowserWindow* bbw = NULL; const int32_t indx = m_comboBox->currentIndex(); if ((indx >= 0) && (indx < m_comboBox->count())) { void* pointer = m_comboBox->itemData(indx).value(); bbw = (BrainBrowserWindow*)pointer; } return bbw; } workbench-1.1.1/src/GuiQt/BrainBrowserWindowComboBox.h000066400000000000000000000045741255417355300227310ustar00rootroot00000000000000#ifndef __BRAIN_BROWSER_WINDOW_COMBO_BOX_H__ #define __BRAIN_BROWSER_WINDOW_COMBO_BOX_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "WuQWidget.h" class QComboBox; namespace caret { class BrainBrowserWindow; class BrainBrowserWindowComboBox : public WuQWidget { Q_OBJECT public: BrainBrowserWindowComboBox(QObject* parent); virtual ~BrainBrowserWindowComboBox(); void updateComboBox(); virtual QWidget* getWidget(); void setBrowserWindowByIndex(const int32_t browserWindowIndex); void setBrowserWindow(BrainBrowserWindow* browserWindow); int32_t getSelectedBrowserWindowIndex() const; BrainBrowserWindow* getSelectedBrowserWindow() const; signals: void browserWindowIndexSelected(const int32_t browserWindowIndex); void browserWindowSelected(BrainBrowserWindow* browserWindow); private slots: void comboBoxIndexSelected(int indx); private: BrainBrowserWindowComboBox(const BrainBrowserWindowComboBox&); BrainBrowserWindowComboBox& operator=(const BrainBrowserWindowComboBox&); public: // ADD_NEW_METHODS_HERE private: QComboBox* m_comboBox; // ADD_NEW_MEMBERS_HERE }; #ifdef __BRAIN_BROWSER_WINDOW_COMBO_BOX_DECLARE__ // #endif // __BRAIN_BROWSER_WINDOW_COMBO_BOX_DECLARE__ } // namespace #endif //__BRAIN_BROWSER_WINDOW_COMBO_BOX_H__ workbench-1.1.1/src/GuiQt/BrainBrowserWindowOrientedToolBox.cxx000066400000000000000000000647071255417355300246600ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #include #include #include "BorderSelectionViewController.h" #include "Brain.h" #include "BrainBrowserWindow.h" #include "BrainBrowserWindowOrientedToolBox.h" #include "BrowserTabContent.h" #include "CaretAssert.h" #include "CaretDataFile.h" #include "CaretPreferences.h" #include "ChartableLineSeriesBrainordinateInterface.h" #include "ChartableMatrixInterface.h" #include "ChartToolBoxViewController.h" #include "CiftiConnectivityMatrixViewController.h" #include "EventBrowserWindowContentGet.h" #include "EventManager.h" #include "EventUserInterfaceUpdate.h" #include "FiberOrientationSelectionViewController.h" #include "FociSelectionViewController.h" #include "GuiManager.h" #include "ImageSelectionViewController.h" #include "LabelSelectionViewController.h" #include "OverlaySetViewController.h" #include "SceneClass.h" #include "SceneWindowGeometry.h" #include "SessionManager.h" #include "VolumeFile.h" #include "VolumeSurfaceOutlineSetViewController.h" #include "WuQtUtilities.h" using namespace caret; /** * Construct the toolbox. * * @param browserWindowIndex * Index of browser window that contains this toolbox. * @param title * Title for the toolbox. * @param location * Locations allowed for this toolbox. */ BrainBrowserWindowOrientedToolBox::BrainBrowserWindowOrientedToolBox(const int32_t browserWindowIndex, const QString& title, const ToolBoxType toolBoxType, QWidget* parent) : QDockWidget(parent) { m_browserWindowIndex = browserWindowIndex; toggleViewAction()->setText("Toolbox"); m_toolBoxTitle = title; setWindowTitle(m_toolBoxTitle); bool isFeaturesToolBox = false; bool isOverlayToolBox = false; Qt::Orientation orientation = Qt::Horizontal; AString toolboxTypeName = ""; switch (toolBoxType) { case TOOL_BOX_FEATURES: orientation = Qt::Vertical; isFeaturesToolBox = true; toggleViewAction()->setText("Features Toolbox"); toolboxTypeName = "Features"; break; case TOOL_BOX_OVERLAYS_HORIZONTAL: orientation = Qt::Horizontal; isOverlayToolBox = true; toolboxTypeName = "OverlayHorizontal"; break; case TOOL_BOX_OVERLAYS_VERTICAL: orientation = Qt::Vertical; isOverlayToolBox = true; toolboxTypeName = "OverlayVertical"; break; } /* * Needed for saving and restoring window state in main window */ CaretAssert(toolboxTypeName.length() > 0); setObjectName("BrainBrowserWindowOrientedToolBox_" + toolboxTypeName + "_" + AString::number(browserWindowIndex)); m_borderSelectionViewController = NULL; m_chartToolBoxViewController = NULL; m_connectivityMatrixViewController = NULL; m_fiberOrientationViewController = NULL; m_fociSelectionViewController = NULL; m_imageSelectionViewController = NULL; m_labelSelectionViewController = NULL; m_overlaySetViewController = NULL; m_volumeSurfaceOutlineSetViewController = NULL; m_tabWidget = new QTabWidget(); m_borderTabIndex = -1; m_chartTabIndex = -1; m_connectivityTabIndex = -1; m_fiberOrientationTabIndex = -1; m_fociTabIndex = -1; m_imageTabIndex = -1; m_labelTabIndex = -1; m_overlayTabIndex = -1; m_volumeSurfaceOutlineTabIndex = -1; if (isOverlayToolBox) { m_overlaySetViewController = new OverlaySetViewController(orientation, browserWindowIndex, this); m_overlayTabIndex = addToTabWidget(m_overlaySetViewController, "Layers"); } if (isOverlayToolBox) { m_chartToolBoxViewController = new ChartToolBoxViewController(orientation, browserWindowIndex, this); m_chartTabIndex = addToTabWidget(m_chartToolBoxViewController, "Charting"); } if (isOverlayToolBox) { m_connectivityMatrixViewController = new CiftiConnectivityMatrixViewController(orientation, this); m_connectivityTabIndex = addToTabWidget(m_connectivityMatrixViewController, "Connectivity"); } if (isFeaturesToolBox) { m_borderSelectionViewController = new BorderSelectionViewController(browserWindowIndex, this); m_borderTabIndex = addToTabWidget(m_borderSelectionViewController, "Borders"); } if (isFeaturesToolBox) { m_fiberOrientationViewController = new FiberOrientationSelectionViewController(browserWindowIndex, this); m_fiberOrientationTabIndex = addToTabWidget(m_fiberOrientationViewController, "Fibers"); } if (isFeaturesToolBox) { m_fociSelectionViewController = new FociSelectionViewController(browserWindowIndex, this); m_fociTabIndex = addToTabWidget(m_fociSelectionViewController, "Foci"); } if (isFeaturesToolBox) { m_imageSelectionViewController = new ImageSelectionViewController(browserWindowIndex, this); m_imageTabIndex = addToTabWidget(m_imageSelectionViewController, "Images"); } if (isFeaturesToolBox) { m_labelSelectionViewController = new LabelSelectionViewController(browserWindowIndex, this); m_labelTabIndex = addToTabWidget(m_labelSelectionViewController, "Labels"); } if (isOverlayToolBox) { m_volumeSurfaceOutlineSetViewController = new VolumeSurfaceOutlineSetViewController(orientation, m_browserWindowIndex); m_volumeSurfaceOutlineTabIndex = addToTabWidget(m_volumeSurfaceOutlineSetViewController, "Vol/Surf Outline"); } setWidget(m_tabWidget); if (orientation == Qt::Horizontal) { setMinimumHeight(200); setMaximumHeight(800); } else { if (isOverlayToolBox) { setMinimumWidth(300); setMaximumWidth(800); } else { setMinimumWidth(200); setMaximumWidth(800); } } QObject::connect(this, SIGNAL(topLevelChanged(bool)), this, SLOT(floatingStatusChanged(bool))); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_USER_INTERFACE_UPDATE); } /** * Destructor. */ BrainBrowserWindowOrientedToolBox::~BrainBrowserWindowOrientedToolBox() { EventManager::get()->removeAllEventsFromListener(this); } /** * Place widget into a scroll area and then into the tab widget. * @param page * Widget that is added. * @param label * Name corresponding to widget's tab. */ int BrainBrowserWindowOrientedToolBox::addToTabWidget(QWidget* page, const QString& label) { QScrollArea* scrollArea = new QScrollArea(); scrollArea->setWidget(page); scrollArea->setWidgetResizable(true); scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); int indx = m_tabWidget->addTab(scrollArea, label); return indx; } /** * Called when floating status changes. * @param status * New floating status. */ void BrainBrowserWindowOrientedToolBox::floatingStatusChanged(bool /*status*/) { QString title = m_toolBoxTitle; setWindowTitle(title); } /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ SceneClass* BrainBrowserWindowOrientedToolBox::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "BrainBrowserWindowOrientedToolBox", 1); AString tabName; const int tabIndex = m_tabWidget->currentIndex(); if ((tabIndex >= 0) && tabIndex < m_tabWidget->count()) { tabName = m_tabWidget->tabText(tabIndex); } sceneClass->addString("selectedTabName", tabName); if (m_chartToolBoxViewController != NULL) { sceneClass->addClass(m_chartToolBoxViewController->saveToScene(sceneAttributes, "m_chartToolBoxViewController")); } // if (m_chartTabWidget != NULL) { // const AString chartTabName = m_chartTabWidget->tabText(m_chartTabWidget->currentIndex()); // sceneClass->addString("selectedChartTabName", // chartTabName); // } /* * Save current widget size */ QWidget* childWidget = m_tabWidget->currentWidget(); if (childWidget != NULL) { SceneWindowGeometry swg(childWidget, this); sceneClass->addClass(swg.saveToScene(sceneAttributes, "childWidget")); } /* * Save the toolbox */ SceneWindowGeometry swg(this, GuiManager::get()->getBrowserWindowByWindowIndex(this->m_browserWindowIndex)); sceneClass->addClass(swg.saveToScene(sceneAttributes, "geometry")); /** * Save the size when visible BUT NOT floating */ if (isFloating() == false) { sceneClass->addInteger("toolboxWidth", width()); sceneClass->addInteger("toolboxHeight", height()); } /* * Save controllers in the toolbox */ if (m_borderSelectionViewController != NULL) { sceneClass->addClass(m_borderSelectionViewController->saveToScene(sceneAttributes, "m_borderSelectionViewController")); } if (m_fiberOrientationViewController != NULL) { sceneClass->addClass(m_fiberOrientationViewController->saveToScene(sceneAttributes, "m_fiberOrientationViewController")); } if (m_fociSelectionViewController != NULL) { sceneClass->addClass(m_fociSelectionViewController->saveToScene(sceneAttributes, "m_fociSelectionViewController")); } if (m_imageSelectionViewController != NULL) { sceneClass->addClass(m_imageSelectionViewController->saveToScene(sceneAttributes, "m_imageSelectionViewController")); } if (m_labelSelectionViewController != NULL) { sceneClass->addClass(m_labelSelectionViewController->saveToScene(sceneAttributes, "m_labelSelectionViewController")); } return sceneClass; } /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * SceneClass containing the state that was previously * saved and should be restored. */ void BrainBrowserWindowOrientedToolBox::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } const AString tabName = sceneClass->getStringValue("selectedTabName", ""); for (int32_t i = 0; i < m_tabWidget->count(); i++) { if (m_tabWidget->tabText(i) == tabName) { m_tabWidget->setCurrentIndex(i); break; } } if (m_chartToolBoxViewController != NULL) { m_chartToolBoxViewController->restoreFromScene(sceneAttributes, sceneClass->getClass("m_chartToolBoxViewController")); } // const AString chartTabName = sceneClass->getStringValue("selectedChartTabName", // ""); // if ( ! chartTabName.isEmpty()) { // for (int32_t i = 0; i < m_chartTabWidget->count(); i++) { // if (m_chartTabWidget->tabText(i) == chartTabName) { // m_chartTabWidget->setCurrentIndex(i); // break; // } // } // } /* * Restore controllers in the toolbox */ if (m_borderSelectionViewController != NULL) { m_borderSelectionViewController->restoreFromScene(sceneAttributes, sceneClass->getClass("m_borderSelectionViewController")); } if (m_fiberOrientationViewController != NULL) { m_fiberOrientationViewController->restoreFromScene(sceneAttributes, sceneClass->getClass("m_fiberOrientationViewController")); } if (m_fociSelectionViewController != NULL) { m_fociSelectionViewController->restoreFromScene(sceneAttributes, sceneClass->getClass("m_fociSelectionViewController")); } if (m_imageSelectionViewController != NULL) { m_imageSelectionViewController->restoreFromScene(sceneAttributes, sceneClass->getClass("m_imageSelectionViewController")); } if (m_labelSelectionViewController != NULL) { m_labelSelectionViewController->restoreFromScene(sceneAttributes, sceneClass->getClass("m_labelSelectionViewController")); } /* * Restore current widget size */ QWidget* childWidget = m_tabWidget->currentWidget(); QSize childMinSize; QSize childSize; if (childWidget != NULL) { childMinSize = childWidget->minimumSize(); SceneWindowGeometry swg(childWidget, this); swg.restoreFromScene(sceneAttributes, sceneClass->getClass("childWidget")); childSize = childWidget->size(); } if (isFloating() && isVisible()) { SceneWindowGeometry swg(this, GuiManager::get()->getBrowserWindowByWindowIndex(this->m_browserWindowIndex)); swg.restoreFromScene(sceneAttributes, sceneClass->getClass("geometry")); } else { /* * From http://stackoverflow.com/questions/2722939/c-resize-a-docked-qt-qdockwidget-programmatically * * Set the minimum and maximum sizes and restore them later. * Trying to restore them immediately does not work. So, as * explained in the link above, set the minimum and maximum * sizes to that the toolbox is the correct size and then use * a timer to restore the correct values for the minimum and * maximum sizes after a little delay. */ const int w = sceneClass->getIntegerValue("toolboxWidth", -1); const int h = sceneClass->getIntegerValue("toolboxHeight", -1); if ((w > 0) && (h > 0)) { m_minimumSizeAfterSceneRestored = minimumSize(); m_maximumSizeAfterSceneRestored = maximumSize(); setMaximumWidth(w); setMaximumHeight(h); setMinimumWidth(w); setMinimumHeight(h); QTimer::singleShot(1000, // 1000 ms => 1 second this, SLOT(restoreMinimumAndMaximumSizesAfterSceneRestored())); } } } /** * This slot is called when restoring a scene */ void BrainBrowserWindowOrientedToolBox::restoreMinimumAndMaximumSizesAfterSceneRestored() { setMinimumSize(m_minimumSizeAfterSceneRestored); setMaximumSize(m_maximumSizeAfterSceneRestored); } /** * Receive events from the event manager. * * @param event * Event sent by event manager. */ void BrainBrowserWindowOrientedToolBox::receiveEvent(Event* event) { if (event->getEventType() == EventTypeEnum::EVENT_USER_INTERFACE_UPDATE) { EventUserInterfaceUpdate* uiEvent = dynamic_cast(event); CaretAssert(uiEvent); uiEvent->setEventProcessed(); Brain* brain = GuiManager::get()->getBrain(); /* * Determine types of data this is loaded */ bool haveBorders = false; bool haveConnFiles = false; bool haveFibers = false; bool haveFoci = false; bool haveImages = false; bool haveLabels = false; bool haveSurfaces = false; bool haveVolumes = false; std::vector allDataFiles; brain->getAllDataFiles(allDataFiles); for (std::vector::iterator iter = allDataFiles.begin(); iter != allDataFiles.end(); iter++) { const CaretDataFile* caretDataFile = *iter; const DataFileTypeEnum::Enum dataFileType = caretDataFile->getDataFileType(); switch (dataFileType) { case DataFileTypeEnum::BORDER: haveBorders = true; break; case DataFileTypeEnum::CONNECTIVITY_DENSE: haveConnFiles = true; break; case DataFileTypeEnum::CONNECTIVITY_DENSE_LABEL: haveLabels = true; break; case DataFileTypeEnum::CONNECTIVITY_DENSE_PARCEL: haveConnFiles = true; break; case DataFileTypeEnum::CONNECTIVITY_DENSE_SCALAR: break; case DataFileTypeEnum::CONNECTIVITY_DENSE_TIME_SERIES: break; case DataFileTypeEnum::CONNECTIVITY_FIBER_ORIENTATIONS_TEMPORARY: haveFibers = true; break; case DataFileTypeEnum::CONNECTIVITY_FIBER_TRAJECTORY_TEMPORARY: haveConnFiles = true; break; case DataFileTypeEnum::CONNECTIVITY_PARCEL: haveConnFiles = true; break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_DENSE: haveConnFiles = true; break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_LABEL: haveLabels = true; break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_SCALAR: break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_SERIES: break; case DataFileTypeEnum::CONNECTIVITY_SCALAR_DATA_SERIES: break; case DataFileTypeEnum::FOCI: haveFoci = true; break; case DataFileTypeEnum::IMAGE: haveImages = true; break; case DataFileTypeEnum::LABEL: haveLabels = true; break; case DataFileTypeEnum::METRIC: break; case DataFileTypeEnum::PALETTE: break; case DataFileTypeEnum::RGBA: break; case DataFileTypeEnum::SCENE: break; case DataFileTypeEnum::SPECIFICATION: break; case DataFileTypeEnum::SURFACE: haveSurfaces = true; break; case DataFileTypeEnum::UNKNOWN: break; case DataFileTypeEnum::VOLUME: { haveVolumes = true; const VolumeFile* vf = dynamic_cast(caretDataFile); CaretAssert(vf); if (vf->isMappedWithLabelTable()) { haveLabels = true; } } break; } } /* * Enable surface volume outline only if have both surfaces and volumes * loaded and model being viewed is allows drawing of volume surface * outline. */ int defaultTabIndex = -1; bool enableLayers = true; bool enableVolumeSurfaceOutline = false; bool enableCharts = false; EventBrowserWindowContentGet browserContentEvent(m_browserWindowIndex); EventManager::get()->sendEvent(browserContentEvent.getPointer()); BrowserTabContent* windowContent = browserContentEvent.getSelectedBrowserTabContent(); if (windowContent != NULL) { switch (windowContent->getSelectedModelType()) { case ModelTypeEnum::MODEL_TYPE_INVALID: break; case ModelTypeEnum::MODEL_TYPE_SURFACE: defaultTabIndex = m_overlayTabIndex; break; case ModelTypeEnum::MODEL_TYPE_SURFACE_MONTAGE: defaultTabIndex = m_overlayTabIndex; break; case ModelTypeEnum::MODEL_TYPE_VOLUME_SLICES: defaultTabIndex = m_overlayTabIndex; enableVolumeSurfaceOutline = (haveSurfaces & haveVolumes); break; case ModelTypeEnum::MODEL_TYPE_WHOLE_BRAIN: defaultTabIndex = m_overlayTabIndex; enableVolumeSurfaceOutline = (haveSurfaces & haveVolumes); break; case ModelTypeEnum::MODEL_TYPE_CHART: defaultTabIndex = m_chartTabIndex; enableLayers = false; enableVolumeSurfaceOutline = false; haveBorders = false; haveFibers = false; haveFoci = false; haveLabels = false; enableCharts = true; break; } } /* * Get the selected tab BEFORE enabling/disabling tabs. * Otherwise, the enabling/disabling of tabs may cause the selection * to change */ const int32_t tabIndex = m_tabWidget->currentIndex(); /* * Enable/disable Tabs based upon data that is loaded * NOTE: Order is important so that overlay tab is * automatically selected. */ if (m_chartTabIndex >= 0) m_tabWidget->setTabEnabled(m_chartTabIndex, enableCharts); if (m_connectivityTabIndex >= 0) m_tabWidget->setTabEnabled(m_connectivityTabIndex, haveConnFiles); if (m_volumeSurfaceOutlineTabIndex >= 0) m_tabWidget->setTabEnabled(m_volumeSurfaceOutlineTabIndex, enableVolumeSurfaceOutline); if (m_borderTabIndex >= 0) m_tabWidget->setTabEnabled(m_borderTabIndex, haveBorders); if (m_fiberOrientationTabIndex >= 0) m_tabWidget->setTabEnabled(m_fiberOrientationTabIndex, haveFibers); if (m_fociTabIndex >= 0) m_tabWidget->setTabEnabled(m_fociTabIndex, haveFoci); if (m_imageTabIndex >= 0) m_tabWidget->setTabEnabled(m_imageTabIndex, haveImages); if (m_labelTabIndex >= 0) m_tabWidget->setTabEnabled(m_labelTabIndex, haveLabels); if (m_overlayTabIndex >= 0) m_tabWidget->setTabEnabled(m_overlayTabIndex, enableLayers); /* * Switch selected tab if it is not valid */ bool tabIndexValid = false; if ((tabIndex >= 0) && (tabIndex < m_tabWidget->count())) { if (m_tabWidget->isTabEnabled(tabIndex)) { tabIndexValid = true; } } if ( ! tabIndexValid) { if (m_tabWidget->isTabEnabled(defaultTabIndex)) { m_tabWidget->setCurrentIndex(defaultTabIndex); } else { for (int i = 0; i < m_tabWidget->count(); ++i) { if (m_tabWidget->isTabEnabled(i)) { m_tabWidget->setCurrentIndex(i); break; } } } } } else { } } workbench-1.1.1/src/GuiQt/BrainBrowserWindowOrientedToolBox.h000066400000000000000000000104251255417355300242710ustar00rootroot00000000000000#ifndef __BRAIN_BROWSER_WINDOW_ORIENTED_TOOLBOX_H__ #define __BRAIN_BROWSER_WINDOW_ORIENTED_TOOLBOX_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "EventListenerInterface.h" #include "SceneableInterface.h" class QTabWidget; namespace caret { class BorderSelectionViewController; class ChartToolBoxViewController; class CiftiConnectivityMatrixViewController; class FiberOrientationSelectionViewController; class FociSelectionViewController; class ImageSelectionViewController; class LabelSelectionViewController; class OverlaySetViewController; class VolumeSurfaceOutlineSetViewController; class BrainBrowserWindowOrientedToolBox : public QDockWidget, public EventListenerInterface, public SceneableInterface { Q_OBJECT public: enum ToolBoxType { TOOL_BOX_FEATURES, TOOL_BOX_OVERLAYS_HORIZONTAL, TOOL_BOX_OVERLAYS_VERTICAL, }; BrainBrowserWindowOrientedToolBox(const int32_t browserWindowIndex, const QString& title, const ToolBoxType toolBoxType, QWidget* parent = 0); ~BrainBrowserWindowOrientedToolBox(); void receiveEvent(Event* event); virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private slots: void floatingStatusChanged(bool); void restoreMinimumAndMaximumSizesAfterSceneRestored(); private: BrainBrowserWindowOrientedToolBox(const BrainBrowserWindowOrientedToolBox&); BrainBrowserWindowOrientedToolBox& operator=(const BrainBrowserWindowOrientedToolBox&); int addToTabWidget(QWidget* page, const QString& label); OverlaySetViewController* m_overlaySetViewController; BorderSelectionViewController* m_borderSelectionViewController; CiftiConnectivityMatrixViewController* m_connectivityMatrixViewController; ChartToolBoxViewController* m_chartToolBoxViewController; FiberOrientationSelectionViewController* m_fiberOrientationViewController; FociSelectionViewController* m_fociSelectionViewController; ImageSelectionViewController* m_imageSelectionViewController; LabelSelectionViewController* m_labelSelectionViewController; VolumeSurfaceOutlineSetViewController* m_volumeSurfaceOutlineSetViewController; QTabWidget* m_tabWidget; QString m_toolBoxTitle; int32_t m_browserWindowIndex; int32_t m_overlayTabIndex; int32_t m_borderTabIndex; int32_t m_connectivityTabIndex; int32_t m_chartTabIndex; int32_t m_fiberOrientationTabIndex; int32_t m_fociTabIndex; int32_t m_imageTabIndex; int32_t m_labelTabIndex; int32_t m_volumeSurfaceOutlineTabIndex; QSize m_minimumSizeAfterSceneRestored; QSize m_maximumSizeAfterSceneRestored; }; } #endif // __BRAIN_BROWSER_WINDOW_ORIENTED_TOOLBOX_H__ workbench-1.1.1/src/GuiQt/BrainBrowserWindowToolBar.cxx000066400000000000000000004324161255417355300231360ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Brain.h" #include "BrainBrowserWindow.h" #include "BrainBrowserWindowToolBar.h" #include "BrainBrowserWindowToolBarChartAttributes.h" #include "BrainBrowserWindowToolBarChartAxes.h" #include "BrainBrowserWindowToolBarChartType.h" #include "BrainBrowserWindowToolBarClipping.h" #include "BrainBrowserWindowToolBarSlicePlane.h" #include "BrainBrowserWindowToolBarSliceSelection.h" #include "BrainBrowserWindowToolBarSurfaceMontage.h" #include "BrainBrowserWindowToolBarTab.h" #include "BrainBrowserWindowToolBarVolumeMontage.h" #include "BrainStructure.h" #include "BrowserTabContent.h" #include "CaretAssert.h" #include "CaretFunctionName.h" #include "CaretLogger.h" #include "CaretPreferences.h" #include "CursorDisplayScoped.h" #include "DisplayPropertiesBorders.h" #include "EventBrowserTabDelete.h" #include "EventBrowserTabGet.h" #include "EventBrowserTabGetAll.h" #include "EventBrowserTabGetAllViewed.h" #include "EventBrowserTabNew.h" #include "EventBrowserWindowContentGet.h" #include "EventBrowserWindowCreateTabs.h" #include "EventBrowserWindowNew.h" #include "EventGetOrSetUserInputModeProcessor.h" #include "EventGraphicsUpdateOneWindow.h" #include "EventGraphicsUpdateAllWindows.h" #include "EventUserInterfaceUpdate.h" #include "EventManager.h" #include "EventModelGetAll.h" #include "EventSurfaceColoringInvalidate.h" #include "EventUpdateYokedWindows.h" #include "GuiManager.h" #include "Model.h" #include "ModelChart.h" #include "ModelSurface.h" #include "ModelSurfaceMontage.h" #include "ModelSurfaceSelector.h" #include "ModelTransform.h" #include "ModelVolume.h" #include "ModelWholeBrain.h" #include "OverlaySet.h" #include "SceneAttributes.h" #include "SceneClass.h" #include "SceneIntegerArray.h" #include "ScenePrimitiveArray.h" #include "SessionManager.h" #include "Surface.h" #include "SurfaceSelectionModel.h" #include "SurfaceSelectionViewController.h" #include "StructureSurfaceSelectionControl.h" #include "UserInputModeAbstract.h" #include "VolumeFile.h" #include "VolumeSliceViewPlaneEnum.h" #include "VolumeSurfaceOutlineSetModel.h" #include "WuQDataEntryDialog.h" #include "WuQFactory.h" #include "WuQMessageBox.h" #include "WuQWidgetObjectGroup.h" #include "WuQtUtilities.h" using namespace caret; /** * Constructor. * * @param browserWindowIndex * Index of the parent browser window. * @param initialBrowserTabContent * Content of default tab (may be NULL in which cast * new content is created). * @param toolBoxToolButtonAction * Action for the Toolbox button in this toolbar. * @param parent * Parent for this toolbar. */ BrainBrowserWindowToolBar::BrainBrowserWindowToolBar(const int32_t browserWindowIndex, BrowserTabContent* initialBrowserTabContent, QAction* overlayToolBoxAction, QAction* layersToolBoxAction, BrainBrowserWindow* parentBrainBrowserWindow) : QToolBar(parentBrainBrowserWindow) { this->browserWindowIndex = browserWindowIndex; this->updateCounter = 0; m_tabIndexForTileTabsHighlighting = -1; this->isContructorFinished = false; this->isDestructionInProgress = false; this->viewOrientationLeftIcon = NULL; this->viewOrientationRightIcon = NULL; this->viewOrientationAnteriorIcon = NULL; this->viewOrientationPosteriorIcon = NULL; this->viewOrientationDorsalIcon = NULL; this->viewOrientationVentralIcon = NULL; this->viewOrientationLeftLateralIcon = NULL; this->viewOrientationLeftMedialIcon = NULL; this->viewOrientationRightLateralIcon = NULL; this->viewOrientationRightMedialIcon = NULL; /* * Needed for saving and restoring window state in main window */ setObjectName("BrainBrowserWindowToolBar_" + AString::number(browserWindowIndex)); /* * Create tab bar that displays models. */ this->tabBar = new QTabBar(); if (WuQtUtilities::isSmallDisplay()) { this->tabBar->setStyleSheet("QTabBar::tab:selected {" " font: bold;" "} " "QTabBar::tab {" " font: italic" "}"); } else { this->tabBar->setStyleSheet("QTabBar::tab:selected {" " font: bold 14px;" "} " "QTabBar::tab {" " font: italic" "}"); } this->tabBar->setShape(QTabBar::RoundedNorth); this->tabBar->setMovable(true); #ifdef Q_OS_MACX /* * Adding a parent to the style will result in it * being destroyed when this instance is destroyed. * The style must remain valid until the destruction * of this instance. It cannot be declared statically. */ QCleanlooksStyle* cleanLooksStyle = new QCleanlooksStyle(); cleanLooksStyle->setParent(this); this->tabBar->setStyle(cleanLooksStyle); #endif // Q_OS_MACX QObject::connect(this->tabBar, SIGNAL(currentChanged(int)), this, SLOT(selectedTabChanged(int))); QObject::connect(this->tabBar, SIGNAL(tabCloseRequested(int)), this, SLOT(tabClosed(int))); QObject::connect(this->tabBar, SIGNAL(tabMoved(int,int)), this, SLOT(tabMoved(int,int))); /* * Custom view action */ const QString customToolTip = ("Pressing the \"Custom\" button displays a dialog for creating and editing orientations.\n" "Note that custom orientations are stored in your Workbench's preferences and thus\n" "will be availble in any concurrent or future instances of Workbench."); this->customViewAction = WuQtUtilities::createAction("Custom", customToolTip, this, this, SLOT(customViewActionTriggered())); /* * Actions at right side of toolbar */ QToolButton* informationDialogToolButton = new QToolButton(); informationDialogToolButton->setDefaultAction(GuiManager::get()->getInformationDisplayDialogEnabledAction()); QToolButton* identifyDialogToolButton = new QToolButton(); identifyDialogToolButton->setDefaultAction(GuiManager::get()->getIdentifyBrainordinateDialogDisplayAction()); QToolButton* helpDialogToolButton = new QToolButton(); helpDialogToolButton->setDefaultAction(GuiManager::get()->getHelpViewerDialogDisplayAction()); QToolButton* sceneDialogToolButton = new QToolButton(); sceneDialogToolButton->setDefaultAction(GuiManager::get()->getSceneDialogDisplayAction()); /* * Toolbar action and tool button at right of the tab bar */ QIcon toolBarIcon; const bool toolBarIconValid = WuQtUtilities::loadIcon(":/ToolBar/toolbar.png", toolBarIcon); this->toolBarToolButtonAction = WuQtUtilities::createAction("Toolbar", "Show or hide the toolbar", this, this, SLOT(showHideToolBar(bool))); if (toolBarIconValid) { this->toolBarToolButtonAction->setIcon(toolBarIcon); this->toolBarToolButtonAction->setIconVisibleInMenu(false); } this->toolBarToolButtonAction->setIconVisibleInMenu(false); this->toolBarToolButtonAction->blockSignals(true); this->toolBarToolButtonAction->setCheckable(true); this->toolBarToolButtonAction->setChecked(true); this->showHideToolBar(this->toolBarToolButtonAction->isChecked()); this->toolBarToolButtonAction->blockSignals(false); QToolButton* toolBarToolButton = new QToolButton(); toolBarToolButton->setDefaultAction(this->toolBarToolButtonAction); /* * Toolbox control at right of the tab bar */ QToolButton* overlayToolBoxToolButton = new QToolButton(); overlayToolBoxToolButton->setDefaultAction(overlayToolBoxAction); QToolButton* layersToolBoxToolButton = new QToolButton(); layersToolBoxToolButton->setDefaultAction(layersToolBoxAction); /* * Make all tool buttons the same height */ WuQtUtilities::matchWidgetHeights(helpDialogToolButton, informationDialogToolButton, identifyDialogToolButton, sceneDialogToolButton, toolBarToolButton, overlayToolBoxToolButton, layersToolBoxToolButton); /* * Tab bar and controls at far right side of toolbar */ this->tabBarWidget = new QWidget(); QHBoxLayout* tabBarLayout = new QHBoxLayout(this->tabBarWidget); WuQtUtilities::setLayoutSpacingAndMargins(tabBarLayout, 2, 1); tabBarLayout->addWidget(this->tabBar, 100); tabBarLayout->addWidget(helpDialogToolButton); tabBarLayout->addWidget(informationDialogToolButton); tabBarLayout->addWidget(identifyDialogToolButton); tabBarLayout->addWidget(sceneDialogToolButton); tabBarLayout->addWidget(toolBarToolButton); tabBarLayout->addWidget(overlayToolBoxToolButton); tabBarLayout->addWidget(layersToolBoxToolButton); /* * Create the toolbar's widgets. */ this->viewWidget = this->createViewWidget(); this->orientationWidget = this->createOrientationWidget(); this->chartAxesWidget = createChartAxesWidget(); this->chartAttributesWidget = createChartAttributesWidget(); this->chartTypeWidget = createChartTypeWidget(); this->wholeBrainSurfaceOptionsWidget = this->createWholeBrainSurfaceOptionsWidget(); this->volumeIndicesWidget = this->createVolumeIndicesWidget(); this->modeWidget = this->createModeWidget(); this->windowWidget = this->createTabOptionsWidget(); this->singleSurfaceSelectionWidget = this->createSingleSurfaceOptionsWidget(); this->surfaceMontageSelectionWidget = this->createSurfaceMontageOptionsWidget(); m_clippingOptionsWidget = createClippingOptionsWidget(); this->volumeMontageWidget = this->createVolumeMontageWidget(); this->volumePlaneWidget = this->createVolumePlaneWidget(); /* * Layout the toolbar's widgets. */ m_toolbarWidget = new QWidget(); this->toolbarWidgetLayout = new QHBoxLayout(m_toolbarWidget); WuQtUtilities::setLayoutSpacingAndMargins(this->toolbarWidgetLayout, 2, 1); this->toolbarWidgetLayout->addWidget(this->viewWidget, 0, Qt::AlignLeft); this->toolbarWidgetLayout->addWidget(this->orientationWidget, 0, Qt::AlignLeft); this->toolbarWidgetLayout->addWidget(this->wholeBrainSurfaceOptionsWidget, 0, Qt::AlignLeft); this->toolbarWidgetLayout->addWidget(this->singleSurfaceSelectionWidget, 0, Qt::AlignLeft); this->toolbarWidgetLayout->addWidget(this->surfaceMontageSelectionWidget, 0, Qt::AlignLeft); this->toolbarWidgetLayout->addWidget(this->volumePlaneWidget, 0, Qt::AlignLeft); this->toolbarWidgetLayout->addWidget(this->volumeMontageWidget, 0, Qt::AlignLeft); this->toolbarWidgetLayout->addWidget(this->volumeIndicesWidget, 0, Qt::AlignLeft); this->toolbarWidgetLayout->addWidget(this->chartTypeWidget, 0, Qt::AlignLeft); this->toolbarWidgetLayout->addWidget(this->chartAxesWidget, 0, Qt::AlignLeft); this->toolbarWidgetLayout->addWidget(this->chartAttributesWidget, 0, Qt::AlignLeft); this->toolbarWidgetLayout->addWidget(this->modeWidget, 0, Qt::AlignLeft); this->toolbarWidgetLayout->addWidget(m_clippingOptionsWidget, 0, Qt::AlignLeft); this->toolbarWidgetLayout->addWidget(this->windowWidget, 0, Qt::AlignLeft); this->toolbarWidgetLayout->addStretch(); /* * Widget below toolbar for user input mode mouse controls */ this->userInputControlsWidgetLayout = new QHBoxLayout(); this->userInputControlsWidgetLayout->addSpacing(5); WuQtUtilities::setLayoutSpacingAndMargins(this->userInputControlsWidgetLayout, 0, 0); this->userInputControlsWidget = new QWidget(); QVBoxLayout* userInputLayout = new QVBoxLayout(this->userInputControlsWidget); WuQtUtilities::setLayoutSpacingAndMargins(userInputLayout, 2, 0); userInputLayout->addWidget(WuQtUtilities::createHorizontalLineWidget()); userInputLayout->addLayout(this->userInputControlsWidgetLayout); userInputControlsWidgetActiveInputWidget = NULL; /* * Arrange the tabbar and the toolbar vertically. */ QWidget* w = new QWidget(); QVBoxLayout* layout = new QVBoxLayout(w); WuQtUtilities::setLayoutSpacingAndMargins(layout, 1, 0); layout->addWidget(this->tabBarWidget); layout->addWidget(m_toolbarWidget); layout->addWidget(this->userInputControlsWidget); this->addWidget(w); if (initialBrowserTabContent != NULL) { this->addNewTabWithContent(initialBrowserTabContent); } else { AString errorMessage; this->createNewTab(errorMessage); } this->updateToolBar(); this->isContructorFinished = true; EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_BROWSER_TAB_GET_ALL_VIEWED); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_BROWSER_WINDOW_CONTENT_GET); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_BROWSER_WINDOW_CREATE_TABS); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_UPDATE_YOKED_WINDOWS); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_USER_INTERFACE_UPDATE); } /** * Destructor. */ BrainBrowserWindowToolBar::~BrainBrowserWindowToolBar() { this->isDestructionInProgress = true; if (this->viewOrientationLeftIcon != NULL) { delete this->viewOrientationLeftIcon; this->viewOrientationLeftIcon = NULL; } if (this->viewOrientationRightIcon != NULL) { delete this->viewOrientationRightIcon; this->viewOrientationRightIcon = NULL; } if (this->viewOrientationAnteriorIcon != NULL) { delete this->viewOrientationAnteriorIcon; this->viewOrientationAnteriorIcon = NULL; } if (this->viewOrientationPosteriorIcon != NULL) { delete this->viewOrientationPosteriorIcon; this->viewOrientationPosteriorIcon = NULL; } if (this->viewOrientationDorsalIcon != NULL) { delete this->viewOrientationDorsalIcon; this->viewOrientationDorsalIcon = NULL; } if (this->viewOrientationVentralIcon != NULL) { delete this->viewOrientationVentralIcon; this->viewOrientationVentralIcon = NULL; } if (this->viewOrientationLeftLateralIcon != NULL) { delete this->viewOrientationLeftLateralIcon; this->viewOrientationLeftLateralIcon = NULL; } if (this->viewOrientationLeftMedialIcon != NULL) { delete this->viewOrientationLeftMedialIcon; this->viewOrientationLeftMedialIcon = NULL; } if (this->viewOrientationRightLateralIcon != NULL) { delete this->viewOrientationRightLateralIcon; this->viewOrientationRightLateralIcon = NULL; } if (this->viewOrientationRightMedialIcon != NULL) { delete this->viewOrientationRightMedialIcon; this->viewOrientationRightMedialIcon = NULL; } EventManager::get()->removeAllEventsFromListener(this); this->viewWidgetGroup->clear(); this->orientationWidgetGroup->clear(); this->wholeBrainSurfaceOptionsWidgetGroup->clear(); this->modeWidgetGroup->clear(); this->singleSurfaceSelectionWidgetGroup->clear(); for (int i = (this->tabBar->count() - 1); i >= 0; i--) { this->tabClosed(i); } this->isDestructionInProgress = false; } /** * Create a new tab. * @param errorMessage * If fails to create new tab, it will contain a message * describing the error. * @return * Pointer to content of new tab or NULL if unable to * create the new tab. */ BrowserTabContent* BrainBrowserWindowToolBar::createNewTab(AString& errorMessage) { errorMessage = ""; EventBrowserTabNew newTabEvent; EventManager::get()->sendEvent(newTabEvent.getPointer()); if (newTabEvent.isError()) { errorMessage = newTabEvent.getErrorMessage(); return NULL; } BrowserTabContent* tabContent = newTabEvent.getBrowserTab(); Brain* brain = GuiManager::get()->getBrain(); tabContent->getVolumeSurfaceOutlineSet()->selectSurfacesAfterSpecFileLoaded(brain, false); this->addNewTabWithContent(tabContent); return tabContent; } /** * Add a new tab and clone the content of the given tab. * @param browserTabContentToBeCloned * Tab Content that is to be cloned into the new tab. */ void BrainBrowserWindowToolBar::addNewTabCloneContent(BrowserTabContent* browserTabContentToBeCloned) { /* * Wait cursor */ CursorDisplayScoped cursor; cursor.showWaitCursor(); AString errorMessage; BrowserTabContent* tabContent = this->createNewTab(errorMessage); if (tabContent == NULL) { cursor.restoreCursor(); QMessageBox::critical(this, "", errorMessage); return; } if (browserTabContentToBeCloned != NULL) { /* * New tab is clone of tab that was displayed when the new tab was created. */ tabContent->cloneBrowserTabContent(browserTabContentToBeCloned); } this->updateToolBar(); EventManager::get()->sendEvent(EventSurfaceColoringInvalidate().getPointer()); EventManager::get()->sendEvent(EventUserInterfaceUpdate().setWindowIndex(this->browserWindowIndex).getPointer()); EventManager::get()->sendEvent(EventGraphicsUpdateOneWindow(this->browserWindowIndex).getPointer()); } /** * Add a new tab containing the given content. * @param tabContent * Content for new tab. */ void BrainBrowserWindowToolBar::addNewTabWithContent(BrowserTabContent* tabContent) { addOrInsertNewTab(tabContent, -1); } /** * Adds a new tab. */ void BrainBrowserWindowToolBar::addNewTab() { /* * Wait cursor */ CursorDisplayScoped cursor; cursor.showWaitCursor(); AString errorMessage; BrowserTabContent* tabContent = this->createNewTab(errorMessage); if (tabContent == NULL) { cursor.restoreCursor(); QMessageBox::critical(this, "", errorMessage); return; } this->updateToolBar(); EventManager::get()->sendEvent(EventSurfaceColoringInvalidate().getPointer()); EventManager::get()->sendEvent(EventUserInterfaceUpdate().setWindowIndex(this->browserWindowIndex).getPointer()); EventManager::get()->sendEvent(EventGraphicsUpdateOneWindow(this->browserWindowIndex).getPointer()); } /** * Insert the tab content at the given index. * * @param browserTabContent * Content of the tab. * @param insertAtIndex * Insert the tab at the given index in the tab bar. Must be greater than or * equal to zero. */ void BrainBrowserWindowToolBar::insertTabAtIndex(BrowserTabContent* browserTabContent, const int32_t insertAtIndex) { CaretAssert(insertAtIndex >= 0); addOrInsertNewTab(browserTabContent, insertAtIndex); } /** * Add or Insert the tab content at the given index. * * @param browserTabContent * Content of the tab. * @param insertAtIndex * If greater than or equal to zero, insert the tab at this index. * Otherwise, use the index from the tab content or just append. */ void BrainBrowserWindowToolBar::addOrInsertNewTab(BrowserTabContent* browserTabContent, const int32_t insertAtIndex) { CaretAssert(browserTabContent); this->tabBar->blockSignals(true); int32_t newTabIndex = -1; if (insertAtIndex >= 0) { newTabIndex = this->tabBar->insertTab(insertAtIndex, "NewTab"); } else { const int32_t tabContentIndex = browserTabContent->getTabNumber(); const int32_t numTabs = this->tabBar->count(); if (numTabs <= 0) { newTabIndex = this->tabBar->addTab("NewTab"); } else { int insertIndex = 0; for (int32_t i = 0; i < numTabs; i++) { if (tabContentIndex > this->getTabContentFromTab(i)->getTabNumber()) { insertIndex = i + 1; } } if (insertIndex >= numTabs) { newTabIndex = this->tabBar->addTab("NewTab"); } else { this->tabBar->insertTab(insertIndex, "NewTab"); newTabIndex = insertIndex; } } } this->tabBar->setTabData(newTabIndex, qVariantFromValue((void*)browserTabContent)); const int32_t numOpenTabs = this->tabBar->count(); this->tabBar->setTabsClosable(numOpenTabs > 1); this->updateTabName(newTabIndex); this->tabBar->setCurrentIndex(newTabIndex); this->tabBar->blockSignals(false); } /** * Shows/hides the toolbar. */ void BrainBrowserWindowToolBar::showHideToolBar(bool showIt) { if (this->isContructorFinished) { m_toolbarWidget->setVisible(showIt); } this->toolBarToolButtonAction->blockSignals(true); if (showIt) { this->toolBarToolButtonAction->setToolTip("Hide Toolbar"); this->toolBarToolButtonAction->setChecked(true); } else { this->toolBarToolButtonAction->setToolTip("Show Toolbar"); this->toolBarToolButtonAction->setChecked(false); } this->toolBarToolButtonAction->blockSignals(false); } /** * Add the default tabs after loading a spec file. */ void BrainBrowserWindowToolBar::addDefaultTabsAfterLoadingSpecFile() { EventModelGetAll eventAllModels; EventManager::get()->sendEvent(eventAllModels.getPointer()); const std::vector allModels = eventAllModels.getModels(); ModelSurface* leftSurfaceModel = NULL; ModelSurface* leftSurfaceInflated = NULL; ModelSurface* leftSurfaceVeryInflated = NULL; int32_t leftSurfaceTypeCode = 1000000; ModelSurface* rightSurfaceModel = NULL; ModelSurface* rightSurfaceInflated = NULL; ModelSurface* rightSurfaceVeryInflated = NULL; int32_t rightSurfaceTypeCode = 1000000; ModelSurface* cerebellumSurfaceModel = NULL; ModelSurface* cerebellumSurfaceInflated = NULL; ModelSurface* cerebellumSurfaceVeryInflated = NULL; int32_t cerebellumSurfaceTypeCode = 1000000; ModelChart* chartModel = NULL; ModelSurfaceMontage* surfaceMontageModel = NULL; ModelVolume* volumeModel = NULL; ModelWholeBrain* wholeBrainModel = NULL; for (std::vector::const_iterator iter = allModels.begin(); iter != allModels.end(); iter++) { ModelSurface* surfaceModel = dynamic_cast(*iter); if (surfaceModel != NULL) { Surface* surface = surfaceModel->getSurface(); StructureEnum::Enum structure = surface->getStructure(); SurfaceTypeEnum::Enum surfaceType = surface->getSurfaceType(); const int32_t surfaceTypeCode = SurfaceTypeEnum::toIntegerCode(surfaceType); switch (structure) { case StructureEnum::CEREBELLUM: if (surfaceTypeCode < cerebellumSurfaceTypeCode) { cerebellumSurfaceModel = surfaceModel; cerebellumSurfaceTypeCode = surfaceTypeCode; } if (surfaceType == SurfaceTypeEnum::INFLATED) { cerebellumSurfaceInflated = surfaceModel; } else if (surfaceType == SurfaceTypeEnum::VERY_INFLATED) { cerebellumSurfaceVeryInflated = surfaceModel; } break; case StructureEnum::CORTEX_LEFT: if (surfaceTypeCode < leftSurfaceTypeCode) { leftSurfaceModel = surfaceModel; leftSurfaceTypeCode = surfaceTypeCode; } if (surfaceType == SurfaceTypeEnum::INFLATED) { leftSurfaceInflated = surfaceModel; } else if (surfaceType == SurfaceTypeEnum::VERY_INFLATED) { leftSurfaceVeryInflated = surfaceModel; } break; case StructureEnum::CORTEX_RIGHT: if (surfaceTypeCode < rightSurfaceTypeCode) { rightSurfaceModel = surfaceModel; rightSurfaceTypeCode = surfaceTypeCode; } if (surfaceType == SurfaceTypeEnum::INFLATED) { rightSurfaceInflated = surfaceModel; } else if (surfaceType == SurfaceTypeEnum::VERY_INFLATED) { rightSurfaceVeryInflated = surfaceModel; } break; default: break; } } else if (dynamic_cast(*iter) != NULL) { surfaceMontageModel = dynamic_cast(*iter); } else if (dynamic_cast(*iter) != NULL) { volumeModel = dynamic_cast(*iter); } else if (dynamic_cast(*iter) != NULL) { wholeBrainModel = dynamic_cast(*iter); } else if (dynamic_cast(*iter)) { chartModel = dynamic_cast(*iter); } else { CaretAssertMessage(0, AString("Unknow controller type: ") + (*iter)->getNameForGUI(true)); } } if (cerebellumSurfaceInflated != NULL) { cerebellumSurfaceModel = cerebellumSurfaceInflated; } else if (cerebellumSurfaceVeryInflated != NULL) { cerebellumSurfaceModel = cerebellumSurfaceVeryInflated; } if (leftSurfaceInflated != NULL) { leftSurfaceModel = leftSurfaceInflated; } else if (leftSurfaceVeryInflated != NULL) { leftSurfaceModel = leftSurfaceVeryInflated; } if (rightSurfaceInflated != NULL) { rightSurfaceModel = rightSurfaceInflated; } else if (rightSurfaceVeryInflated != NULL) { rightSurfaceModel = rightSurfaceVeryInflated; } int32_t numberOfTabsNeeded = 0; if (surfaceMontageModel != NULL) { numberOfTabsNeeded++; } if (volumeModel != NULL) { numberOfTabsNeeded++; } if (wholeBrainModel != NULL) { numberOfTabsNeeded++; } if (chartModel != NULL) { numberOfTabsNeeded++; } if (leftSurfaceModel != NULL) { numberOfTabsNeeded++; } if (rightSurfaceModel != NULL) { numberOfTabsNeeded++; } if (cerebellumSurfaceModel != NULL) { numberOfTabsNeeded++; } const int32_t numberOfTabsToAdd = numberOfTabsNeeded - this->tabBar->count(); for (int32_t i = 0; i < numberOfTabsToAdd; i++) { AString errorMessage; this->createNewTab(errorMessage); } int32_t tabIndex = 0; tabIndex = loadIntoTab(tabIndex, surfaceMontageModel); tabIndex = loadIntoTab(tabIndex, volumeModel); tabIndex = loadIntoTab(tabIndex, wholeBrainModel); tabIndex = loadIntoTab(tabIndex, chartModel); tabIndex = loadIntoTab(tabIndex, leftSurfaceModel); tabIndex = loadIntoTab(tabIndex, rightSurfaceModel); tabIndex = loadIntoTab(tabIndex, cerebellumSurfaceModel); const int numTabs = this->tabBar->count(); if (numTabs > 0) { this->tabBar->setCurrentIndex(0); Brain* brain = GuiManager::get()->getBrain(); for (int32_t i = 0; i < numTabs; i++) { BrowserTabContent* btc = this->getTabContentFromTab(i); if (btc != NULL) { btc->getVolumeSurfaceOutlineSet()->selectSurfacesAfterSpecFileLoaded(brain, true); } } /* * Set the default tab to whole brain, if present */ int32_t surfaceTabIndex = -1; int32_t montageTabIndex = -1; int32_t wholeBrainTabIndex = -1; int32_t volumeTabIndex = -1; for (int32_t i = 0; i < numTabs; i++) { BrowserTabContent* btc = getTabContentFromTab(i); if (btc != NULL) { switch (btc->getSelectedModelType()) { case ModelTypeEnum::MODEL_TYPE_INVALID: break; case ModelTypeEnum::MODEL_TYPE_SURFACE: if (surfaceTabIndex < 0) { surfaceTabIndex = i; } break; case ModelTypeEnum::MODEL_TYPE_SURFACE_MONTAGE: if (montageTabIndex < 0) { montageTabIndex = i; } break; case ModelTypeEnum::MODEL_TYPE_VOLUME_SLICES: if (volumeTabIndex < 0) { volumeTabIndex = i; } break; case ModelTypeEnum::MODEL_TYPE_WHOLE_BRAIN: if (wholeBrainTabIndex < 0) { wholeBrainTabIndex = i; } break; case ModelTypeEnum::MODEL_TYPE_CHART: break; } } } int32_t defaultTabIndex = 0; if (montageTabIndex >= 0) { defaultTabIndex = montageTabIndex; } else if (surfaceTabIndex >= 0) { defaultTabIndex = surfaceTabIndex; } else if (volumeTabIndex >= 0) { defaultTabIndex = volumeTabIndex; } else if (wholeBrainTabIndex >= 0) { defaultTabIndex = wholeBrainTabIndex; } this->tabBar->setCurrentIndex(defaultTabIndex); } } /** * Load a controller into the tab with the given index. * @param tabIndexIn * Index of tab into which controller is loaded. A * new tab will be created, if needed. * @param controller * Model that is to be displayed in the tab. If * NULL, this method does nothing. * @return * Index of next tab after controller is displayed. * If the input controller was NULL, the returned * value is identical to the input tab index. */ int32_t BrainBrowserWindowToolBar::loadIntoTab(const int32_t tabIndexIn, Model* controller) { if (tabIndexIn < 0) { return -1; } int32_t tabIndex = tabIndexIn; if (controller != NULL) { if (tabIndex >= this->tabBar->count()) { AString errorMessage; if (this->createNewTab(errorMessage) == NULL) { return -1; } tabIndex = this->tabBar->count() - 1; } void* p = this->tabBar->tabData(tabIndex).value(); BrowserTabContent* btc = (BrowserTabContent*)p; btc->setSelectedModelType(controller->getModelType()); ModelSurface* surfaceModel = dynamic_cast(controller); if (surfaceModel != NULL) { btc->getSurfaceModelSelector()->setSelectedStructure(surfaceModel->getSurface()->getStructure()); btc->getSurfaceModelSelector()->setSelectedSurfaceModel(surfaceModel); btc->setSelectedModelType(ModelTypeEnum::MODEL_TYPE_SURFACE); } this->updateTabName(tabIndex); tabIndex++; } return tabIndex; } /** * Move all but the current tab to new windows. */ void BrainBrowserWindowToolBar::moveTabsToNewWindows() { int32_t numTabs = this->tabBar->count(); if (numTabs > 1) { const int32_t currentIndex = this->tabBar->currentIndex(); QWidget* lastParent = this->parentWidget(); if (lastParent == NULL) { lastParent = this; } for (int32_t i = (numTabs - 1); i >= 0; i--) { if (i != currentIndex) { void* p = this->tabBar->tabData(i).value(); BrowserTabContent* btc = (BrowserTabContent*)p; EventBrowserWindowNew eventNewWindow(lastParent, btc); EventManager::get()->sendEvent(eventNewWindow.getPointer()); if (eventNewWindow.isError()) { QMessageBox::critical(this, "", eventNewWindow.getErrorMessage()); break; } else { lastParent = eventNewWindow.getBrowserWindowCreated(); this->tabBar->setTabData(i, qVariantFromValue((void*)NULL)); this->tabClosed(i); } } } } EventManager::get()->sendEvent(EventUserInterfaceUpdate().getPointer()); } /** * Remove and return all tabs from this toolbar. * After this the window containing this toolbar * will contain no tabs! * * @param allTabContent * Will contain the content from the tabs upon return. */ void BrainBrowserWindowToolBar::removeAndReturnAllTabs(std::vector& allTabContent) { allTabContent.clear(); int32_t numTabs = this->tabBar->count(); for (int32_t i = (numTabs - 1); i >= 0; i--) { void* p = this->tabBar->tabData(i).value(); BrowserTabContent* btc = (BrowserTabContent*)p; if (btc != NULL) { allTabContent.push_back(btc); } this->tabBar->setTabData(i, qVariantFromValue((void*)NULL)); this->tabClosed(i); } } /** * Get content of all tabs * * @param allTabContent * Will contain the content from the tabs upon return. */ void BrainBrowserWindowToolBar::getAllTabContent(std::vector& allTabContent) const { allTabContent.clear(); int32_t numTabs = this->tabBar->count(); for (int32_t i = 0; i < numTabs; i++) { void* p = this->tabBar->tabData(i).value(); BrowserTabContent* btc = (BrowserTabContent*)p; if (btc != NULL) { allTabContent.push_back(btc); } } } /** * Remove the tab that contains the given tab content. * Note: The tab content is NOT deleted and the caller must * either delete it or move it into a window. * After this method completes, the windowo may contain no tabs. * * @param browserTabContent */ void BrainBrowserWindowToolBar::removeTabWithContent(BrowserTabContent* browserTabContent) { int32_t numTabs = this->tabBar->count(); for (int32_t i = 0; i < numTabs; i++) { void* p = this->tabBar->tabData(i).value(); BrowserTabContent* btc = (BrowserTabContent*)p; if (btc == browserTabContent) { this->tabBar->setTabData(i, qVariantFromValue((void*)NULL)); this->tabClosed(i); if (this->tabBar->count() <= 0) { EventManager::get()->removeAllEventsFromListener(this); } break; } } } /** * Select the next tab. */ void BrainBrowserWindowToolBar::nextTab() { int32_t numTabs = this->tabBar->count(); if (numTabs > 1) { int32_t tabIndex = this->tabBar->currentIndex(); tabIndex++; if (tabIndex >= numTabs) { tabIndex = 0; } this->tabBar->setCurrentIndex(tabIndex); } } /** * Select the previous tab. */ void BrainBrowserWindowToolBar::previousTab() { int32_t numTabs = this->tabBar->count(); if (numTabs > 1) { int32_t tabIndex = this->tabBar->currentIndex(); tabIndex--; if (tabIndex < 0) { tabIndex = numTabs - 1; } this->tabBar->setCurrentIndex(tabIndex); } } /** * Rename the current tab. */ void BrainBrowserWindowToolBar::renameTab() { const int tabIndex = this->tabBar->currentIndex(); if (tabIndex >= 0) { void* p = this->tabBar->tabData(tabIndex).value(); BrowserTabContent* btc = (BrowserTabContent*)p; AString currentName = btc->getUserName(); bool ok = false; AString newName = QInputDialog::getText(this, "Set Tab Name", "New Name (empty to reset)", QLineEdit::Normal, currentName, &ok); if (ok) { btc->setUserName(newName); this->updateTabName(tabIndex); } } } /** * Update the name of the tab at the given index. The * name is obtained from the tabs browser content. * * @param tabIndex * Index of tab. */ void BrainBrowserWindowToolBar::updateTabName(const int32_t tabIndex) { int32_t tabIndexForUpdate = tabIndex; if (tabIndexForUpdate < 0) { tabIndexForUpdate = this->tabBar->currentIndex(); } void* p = this->tabBar->tabData(tabIndexForUpdate).value(); AString newName = ""; BrowserTabContent* btc = (BrowserTabContent*)p; if (btc != NULL) { newName = btc->getName(); } this->tabBar->setTabText(tabIndexForUpdate, newName); } /** * Close the selected tab. This method is typically * called by the BrowswerWindow's File Menu. */ void BrainBrowserWindowToolBar::closeSelectedTab() { const int tabIndex = this->tabBar->currentIndex(); if (this->tabBar->count() > 1) { this->tabClosed(tabIndex); } } /** * Called when the selected tab is changed. * @param index * Index of selected tab. */ void BrainBrowserWindowToolBar::selectedTabChanged(int indx) { this->updateTabName(indx); this->updateToolBar(); this->updateToolBox(); emit viewedModelChanged(); BrainBrowserWindow* browserWindow = GuiManager::get()->getBrowserWindowByWindowIndex(this->browserWindowIndex); if (browserWindow != NULL) { if (browserWindow->isTileTabsSelected()) { const BrowserTabContent* btc = getTabContentFromSelectedTab(); if (btc != NULL) { m_tabIndexForTileTabsHighlighting = btc->getTabNumber(); /* * Short timer that will reset the tab highlighting */ const int timeInMilliseconds = 750; QTimer::singleShot(timeInMilliseconds, this, SLOT(resetTabIndexForTileTabsHighlighting())); } } } this->updateGraphicsWindow(); } /** * Reset the tab index for tab tile highlighting. */ void BrainBrowserWindowToolBar::resetTabIndexForTileTabsHighlighting() { m_tabIndexForTileTabsHighlighting = -1; EventManager::get()->sendEvent(EventGraphicsUpdateOneWindow(this->browserWindowIndex).getPointer()); } /** * Called when a tab has been closed. * * @param tabIndex * Index of tab that was moved. */ void BrainBrowserWindowToolBar::tabClosed(int tabIndex) { CaretAssertArrayIndex(this-tabBar->tabData(), this->tabBar->count(), tabIndex); this->removeTab(tabIndex); if (this->isDestructionInProgress == false) { this->updateToolBar(); this->updateToolBox(); emit viewedModelChanged(); } } /** * Called when a tab has been moved. * * @param from * Previous location of tab. * @param to * New location of tab. */ void BrainBrowserWindowToolBar::tabMoved(int /*from*/, int /*to*/) { this->updateGraphicsWindow(); } /** * Remove the tab at the given index. * @param index */ void BrainBrowserWindowToolBar::removeTab(int tabIndex) { CaretAssertArrayIndex(this-tabBar->tabData(), this->tabBar->count(), tabIndex); void* p = this->tabBar->tabData(tabIndex).value(); if (p != NULL) { BrowserTabContent* btc = (BrowserTabContent*)p; EventBrowserTabDelete deleteTabEvent(btc); EventManager::get()->sendEvent(deleteTabEvent.getPointer()); } this->tabBar->blockSignals(true); this->tabBar->removeTab(tabIndex); this->tabBar->blockSignals(false); const int numOpenTabs = this->tabBar->count(); this->tabBar->setTabsClosable(numOpenTabs > 1); } /** * Update the toolbar. */ void BrainBrowserWindowToolBar::updateToolBar() { if (this->isDestructionInProgress) { return; } /* * If there are no models, close all but the first tab. */ EventModelGetAll getAllModelsEvent; EventManager::get()->sendEvent(getAllModelsEvent.getPointer()); if (getAllModelsEvent.getFirstModel() == NULL) { for (int i = (this->tabBar->count() - 1); i >= 0; i--) { this->removeTab(i); } } this->incrementUpdateCounter(__CARET_FUNCTION_NAME__); BrowserTabContent* browserTabContent = this->getTabContentFromSelectedTab(); const ModelTypeEnum::Enum viewModel = this->updateViewWidget(browserTabContent); bool showOrientationWidget = false; bool showWholeBrainSurfaceOptionsWidget = false; bool showSingleSurfaceOptionsWidget = false; bool showSurfaceMontageOptionsWidget = false; bool showClippingOptionsWidget = true; bool showVolumeIndicesWidget = false; bool showVolumePlaneWidget = false; bool showVolumeMontageWidget = false; bool showChartAxesWidget = false; bool showChartTypeWidget = false; bool showChartAttributesWidget = false; bool showModeWidget = true; bool showWindowWidget = true; switch (viewModel) { case ModelTypeEnum::MODEL_TYPE_INVALID: break; case ModelTypeEnum::MODEL_TYPE_SURFACE: showOrientationWidget = true; showSingleSurfaceOptionsWidget = true; break; case ModelTypeEnum::MODEL_TYPE_SURFACE_MONTAGE: showOrientationWidget = true; showSurfaceMontageOptionsWidget = true; break; case ModelTypeEnum::MODEL_TYPE_VOLUME_SLICES: showVolumeIndicesWidget = true; showVolumePlaneWidget = true; showVolumeMontageWidget = true; break; case ModelTypeEnum::MODEL_TYPE_WHOLE_BRAIN: showOrientationWidget = true; showWholeBrainSurfaceOptionsWidget = true; showVolumeIndicesWidget = true; break; case ModelTypeEnum::MODEL_TYPE_CHART: { showChartTypeWidget = true; showClippingOptionsWidget = false; showModeWidget = false; ModelChart* modelChart = browserTabContent->getDisplayedChartModel(); if (modelChart != NULL) { switch (modelChart->getSelectedChartDataType(browserTabContent->getTabNumber())) { case ChartDataTypeEnum::CHART_DATA_TYPE_INVALID: break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_LAYER: showChartAttributesWidget = true; break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_SERIES: showChartAttributesWidget = true; break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_TIME_SERIES: showChartAxesWidget = true; showChartAttributesWidget = true; break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_FREQUENCY_SERIES: showChartAxesWidget = true; showChartAttributesWidget = true; break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_DATA_SERIES: showChartAxesWidget = true; showChartAttributesWidget = true; break; } } } break; } /* * Need to turn off display of all widgets, * otherwise, the toolbar width may be overly * expanded with empty space as other widgets * are turned on and off. */ this->orientationWidget->setVisible(false); this->wholeBrainSurfaceOptionsWidget->setVisible(false); this->singleSurfaceSelectionWidget->setVisible(false); this->surfaceMontageSelectionWidget->setVisible(false); this->chartTypeWidget->setVisible(false); this->chartAxesWidget->setVisible(false); this->chartAttributesWidget->setVisible(false); this->volumeIndicesWidget->setVisible(false); this->volumePlaneWidget->setVisible(false); this->volumeMontageWidget->setVisible(false); this->modeWidget->setVisible(false); this->windowWidget->setVisible(false); m_clippingOptionsWidget->setVisible(false); this->orientationWidget->setVisible(showOrientationWidget); this->wholeBrainSurfaceOptionsWidget->setVisible(showWholeBrainSurfaceOptionsWidget); this->singleSurfaceSelectionWidget->setVisible(showSingleSurfaceOptionsWidget); this->surfaceMontageSelectionWidget->setVisible(showSurfaceMontageOptionsWidget); this->chartTypeWidget->setVisible(showChartTypeWidget); this->chartAxesWidget->setVisible(showChartAxesWidget); this->chartAttributesWidget->setVisible(showChartAttributesWidget); this->volumeIndicesWidget->setVisible(showVolumeIndicesWidget); this->volumePlaneWidget->setVisible(showVolumePlaneWidget); this->volumeMontageWidget->setVisible(showVolumeMontageWidget); this->modeWidget->setVisible(showModeWidget); m_clippingOptionsWidget->setVisible(showClippingOptionsWidget); this->windowWidget->setVisible(showWindowWidget); updateToolBarComponents(browserTabContent); this->decrementUpdateCounter(__CARET_FUNCTION_NAME__); if (this->updateCounter != 0) { CaretLogSevere("Update counter is non-zero at end of updateToolBar()"); } this->updateTabName(-1); BrainBrowserWindow* browserWindow = GuiManager::get()->getBrowserWindowByWindowIndex(this->browserWindowIndex); if (browserWindow != NULL) { if (browserWindow->isFullScreen()) { this->setVisible(false); } else { this->setVisible(true); } } /* * Try to avoid resizing of Toolbar widget (view, orientation, etc) * height when models are changed. Let it grow but never shrink. */ if (isVisible()) { if (m_toolbarWidget->isVisible()) { const int sizeHintHeight = m_toolbarWidget->sizeHint().height(); const int actualHeight = m_toolbarWidget->height(); if (sizeHintHeight >= actualHeight) { m_toolbarWidget->setFixedHeight(sizeHintHeight); } } } } /** * Update the components in the toolbar with the given tab content. * * @param browserTabContent * The current tab content. */ void BrainBrowserWindowToolBar::updateToolBarComponents(BrowserTabContent* browserTabContent) { if (browserTabContent != NULL) { this->updateOrientationWidget(browserTabContent); this->updateWholeBrainSurfaceOptionsWidget(browserTabContent); this->updateVolumeIndicesWidget(browserTabContent); this->updateSingleSurfaceOptionsWidget(browserTabContent); this->updateSurfaceMontageOptionsWidget(browserTabContent); this->updateChartTypeWidget(browserTabContent); this->updateChartAxesWidget(browserTabContent); this->updateChartAttributesWidget(browserTabContent); this->updateVolumeMontageWidget(browserTabContent); this->updateVolumePlaneWidget(browserTabContent); this->updateModeWidget(browserTabContent); this->updateTabOptionsWidget(browserTabContent); this->updateClippingOptionsWidget(browserTabContent); } } /** * Create the view widget. * * @return The view widget. */ QWidget* BrainBrowserWindowToolBar::createViewWidget() { this->viewModeChartRadioButton = new QRadioButton("Chart"); this->viewModeSurfaceRadioButton = new QRadioButton("Surface"); this->viewModeSurfaceMontageRadioButton = new QRadioButton("Montage"); this->viewModeVolumeRadioButton = new QRadioButton("Volume"); this->viewModeWholeBrainRadioButton = new QRadioButton("All"); // this->viewModeChartRadioButton->setVisible(false); QWidget* widget = new QWidget(); QVBoxLayout* layout = new QVBoxLayout(widget); WuQtUtilities::setLayoutSpacingAndMargins(layout, 4, 5); layout->addWidget(this->viewModeChartRadioButton); layout->addWidget(this->viewModeSurfaceMontageRadioButton); layout->addWidget(this->viewModeVolumeRadioButton); layout->addWidget(this->viewModeWholeBrainRadioButton); layout->addWidget(this->viewModeSurfaceRadioButton); layout->addStretch(); QButtonGroup* viewModeRadioButtonGroup = new QButtonGroup(this); viewModeRadioButtonGroup->addButton(this->viewModeChartRadioButton); viewModeRadioButtonGroup->addButton(this->viewModeSurfaceRadioButton); viewModeRadioButtonGroup->addButton(this->viewModeSurfaceMontageRadioButton); viewModeRadioButtonGroup->addButton(this->viewModeVolumeRadioButton); viewModeRadioButtonGroup->addButton(this->viewModeWholeBrainRadioButton); QObject::connect(viewModeRadioButtonGroup, SIGNAL(buttonClicked(QAbstractButton*)), this, SLOT(viewModeRadioButtonClicked(QAbstractButton*))); this->viewWidgetGroup = new WuQWidgetObjectGroup(this); this->viewWidgetGroup->add(this->viewModeChartRadioButton); this->viewWidgetGroup->add(this->viewModeSurfaceRadioButton); this->viewWidgetGroup->add(this->viewModeSurfaceMontageRadioButton); this->viewWidgetGroup->add(this->viewModeVolumeRadioButton); this->viewWidgetGroup->add(this->viewModeWholeBrainRadioButton); QWidget* w = this->createToolWidget("View", widget, WIDGET_PLACEMENT_NONE, WIDGET_PLACEMENT_TOP, 0); return w; } /** * Update the view widget. * * @param browserTabContent * Content in the tab. * @return * An enumerated type indicating the type of model being viewed. */ ModelTypeEnum::Enum BrainBrowserWindowToolBar::updateViewWidget(BrowserTabContent* browserTabContent) { ModelTypeEnum::Enum modelType = ModelTypeEnum::MODEL_TYPE_INVALID; if (browserTabContent != NULL) { modelType = browserTabContent->getSelectedModelType(); } this->incrementUpdateCounter(__CARET_FUNCTION_NAME__); this->viewWidgetGroup->blockAllSignals(true); /* * Enable buttons for valid types */ if (browserTabContent != NULL) { this->viewModeSurfaceRadioButton->setEnabled(browserTabContent->isSurfaceModelValid()); this->viewModeSurfaceMontageRadioButton->setEnabled(browserTabContent->isSurfaceMontageModelValid()); this->viewModeVolumeRadioButton->setEnabled(browserTabContent->isVolumeSliceModelValid()); this->viewModeWholeBrainRadioButton->setEnabled(browserTabContent->isWholeBrainModelValid()); this->viewModeChartRadioButton->setEnabled(browserTabContent->isChartModelValid()); } switch (modelType) { case ModelTypeEnum::MODEL_TYPE_INVALID: break; case ModelTypeEnum::MODEL_TYPE_SURFACE: this->viewModeSurfaceRadioButton->setChecked(true); break; case ModelTypeEnum::MODEL_TYPE_SURFACE_MONTAGE: this->viewModeSurfaceMontageRadioButton->setChecked(true); break; case ModelTypeEnum::MODEL_TYPE_VOLUME_SLICES: this->viewModeVolumeRadioButton->setChecked(true); break; case ModelTypeEnum::MODEL_TYPE_WHOLE_BRAIN: this->viewModeWholeBrainRadioButton->setChecked(true); break; case ModelTypeEnum::MODEL_TYPE_CHART: this->viewModeChartRadioButton->setChecked(true); break; } this->viewWidgetGroup->blockAllSignals(false); this->decrementUpdateCounter(__CARET_FUNCTION_NAME__); return modelType; } /** * Create the orientation widget. * * @return The orientation widget. */ QWidget* BrainBrowserWindowToolBar::createOrientationWidget() { this->viewOrientationLeftIcon = WuQtUtilities::loadIcon(":/ToolBar/view-left.png"); this->viewOrientationRightIcon = WuQtUtilities::loadIcon(":/ToolBar/view-right.png"); this->viewOrientationAnteriorIcon = WuQtUtilities::loadIcon(":/ToolBar/view-anterior.png"); this->viewOrientationPosteriorIcon = WuQtUtilities::loadIcon(":/ToolBar/view-posterior.png"); this->viewOrientationDorsalIcon = WuQtUtilities::loadIcon(":/ToolBar/view-dorsal.png"); this->viewOrientationVentralIcon = WuQtUtilities::loadIcon(":/ToolBar/view-ventral.png"); this->viewOrientationLeftLateralIcon = WuQtUtilities::loadIcon(":/ToolBar/view-left-lateral.png"); this->viewOrientationLeftMedialIcon = WuQtUtilities::loadIcon(":/ToolBar/view-left-medial.png"); this->viewOrientationRightLateralIcon = WuQtUtilities::loadIcon(":/ToolBar/view-right-lateral.png"); this->viewOrientationRightMedialIcon = WuQtUtilities::loadIcon(":/ToolBar/view-right-medial.png"); this->orientationLeftOrLateralToolButtonAction = WuQtUtilities::createAction("L", "View from a LEFT perspective", this, this, SLOT(orientationLeftOrLateralToolButtonTriggered(bool))); if (this->viewOrientationLeftIcon != NULL) { this->orientationLeftOrLateralToolButtonAction->setIcon(*this->viewOrientationLeftIcon); } else { this->orientationLeftOrLateralToolButtonAction->setIconText("L"); } this->orientationRightOrMedialToolButtonAction = WuQtUtilities::createAction("R", "View from a RIGHT perspective", this, this, SLOT(orientationRightOrMedialToolButtonTriggered(bool))); if (this->viewOrientationRightIcon != NULL) { this->orientationRightOrMedialToolButtonAction->setIcon(*this->viewOrientationRightIcon); } else { this->orientationRightOrMedialToolButtonAction->setIconText("R"); } this->orientationAnteriorToolButtonAction = WuQtUtilities::createAction("A", "View from an ANTERIOR perspective", this, this, SLOT(orientationAnteriorToolButtonTriggered(bool))); if (this->viewOrientationAnteriorIcon != NULL) { this->orientationAnteriorToolButtonAction->setIcon(*this->viewOrientationAnteriorIcon); } else { this->orientationAnteriorToolButtonAction->setIconText("A"); } this->orientationPosteriorToolButtonAction = WuQtUtilities::createAction("P", "View from a POSTERIOR perspective", this, this, SLOT(orientationPosteriorToolButtonTriggered(bool))); if (this->viewOrientationPosteriorIcon != NULL) { this->orientationPosteriorToolButtonAction->setIcon(*this->viewOrientationPosteriorIcon); } else { this->orientationPosteriorToolButtonAction->setIconText("P"); } this->orientationDorsalToolButtonAction = WuQtUtilities::createAction("D", "View from a DORSAL perspective", this, this, SLOT(orientationDorsalToolButtonTriggered(bool))); if (this->viewOrientationDorsalIcon != NULL) { this->orientationDorsalToolButtonAction->setIcon(*this->viewOrientationDorsalIcon); } else { this->orientationDorsalToolButtonAction->setIconText("D"); } this->orientationVentralToolButtonAction = WuQtUtilities::createAction("V", "View from a VENTRAL perspective", this, this, SLOT(orientationVentralToolButtonTriggered(bool))); if (this->viewOrientationVentralIcon != NULL) { this->orientationVentralToolButtonAction->setIcon(*this->viewOrientationVentralIcon); } else { this->orientationVentralToolButtonAction->setIconText("V"); } this->orientationLateralMedialToolButtonAction = WuQtUtilities::createAction("LM", "View from a Lateral/Medial perspective", this, this, SLOT(orientationLateralMedialToolButtonTriggered(bool))); this->orientationDorsalVentralToolButtonAction = WuQtUtilities::createAction("DV", "View from a Dorsal/Ventral perspective", this, this, SLOT(orientationDorsalVentralToolButtonTriggered(bool))); this->orientationAnteriorPosteriorToolButtonAction = WuQtUtilities::createAction("AP", "View from a Anterior/Posterior perspective", this, this, SLOT(orientationAnteriorPosteriorToolButtonTriggered(bool))); this->orientationResetToolButtonAction = WuQtUtilities::createAction("R\nE\nS\nE\nT", "Reset the view to dorsal and remove any panning or zooming", this, this, SLOT(orientationResetToolButtonTriggered(bool))); this->orientationLeftOrLateralToolButton = new QToolButton(); this->orientationLeftOrLateralToolButton->setDefaultAction(this->orientationLeftOrLateralToolButtonAction); this->orientationRightOrMedialToolButton = new QToolButton(); this->orientationRightOrMedialToolButton->setDefaultAction(this->orientationRightOrMedialToolButtonAction); this->orientationAnteriorToolButton = new QToolButton(); this->orientationAnteriorToolButton->setDefaultAction(this->orientationAnteriorToolButtonAction); this->orientationPosteriorToolButton = new QToolButton(); this->orientationPosteriorToolButton->setDefaultAction(this->orientationPosteriorToolButtonAction); this->orientationDorsalToolButton = new QToolButton(); this->orientationDorsalToolButton->setDefaultAction(this->orientationDorsalToolButtonAction); this->orientationVentralToolButton = new QToolButton(); this->orientationVentralToolButton->setDefaultAction(this->orientationVentralToolButtonAction); this->orientationLateralMedialToolButton = new QToolButton(); this->orientationLateralMedialToolButton->setDefaultAction(this->orientationLateralMedialToolButtonAction); this->orientationDorsalVentralToolButton = new QToolButton(); this->orientationDorsalVentralToolButton->setDefaultAction(this->orientationDorsalVentralToolButtonAction); this->orientationAnteriorPosteriorToolButton = new QToolButton(); this->orientationAnteriorPosteriorToolButton->setDefaultAction(this->orientationAnteriorPosteriorToolButtonAction); WuQtUtilities::matchWidgetWidths(this->orientationLateralMedialToolButton, this->orientationDorsalVentralToolButton, this->orientationAnteriorPosteriorToolButton); QToolButton* orientationResetToolButton = new QToolButton(); orientationResetToolButton->setDefaultAction(this->orientationResetToolButtonAction); this->orientationCustomViewSelectToolButton = new QToolButton(); this->orientationCustomViewSelectToolButton->setDefaultAction(this->customViewAction); this->orientationCustomViewSelectToolButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); QGridLayout* buttonGridLayout = new QGridLayout(); buttonGridLayout->setColumnStretch(3, 100); WuQtUtilities::setLayoutSpacingAndMargins(buttonGridLayout, 0, 0); buttonGridLayout->addWidget(this->orientationLeftOrLateralToolButton, 0, 0); buttonGridLayout->addWidget(this->orientationRightOrMedialToolButton, 0, 1); buttonGridLayout->addWidget(this->orientationDorsalToolButton, 1, 0); buttonGridLayout->addWidget(this->orientationVentralToolButton, 1, 1); buttonGridLayout->addWidget(this->orientationAnteriorToolButton, 2, 0); buttonGridLayout->addWidget(this->orientationPosteriorToolButton, 2, 1); buttonGridLayout->addWidget(this->orientationLateralMedialToolButton, 0, 2); buttonGridLayout->addWidget(this->orientationDorsalVentralToolButton, 1, 2); buttonGridLayout->addWidget(this->orientationAnteriorPosteriorToolButton, 2, 2); buttonGridLayout->addWidget(this->orientationCustomViewSelectToolButton, 3, 0, 1, 5, Qt::AlignHCenter); buttonGridLayout->addWidget(orientationResetToolButton, 0, 4, 3, 1); QWidget* w = new QWidget(); QVBoxLayout* layout = new QVBoxLayout(w); WuQtUtilities::setLayoutSpacingAndMargins(layout, 0, 0); layout->addLayout(buttonGridLayout); this->orientationWidgetGroup = new WuQWidgetObjectGroup(this); this->orientationWidgetGroup->add(this->orientationLeftOrLateralToolButtonAction); this->orientationWidgetGroup->add(this->orientationRightOrMedialToolButtonAction); this->orientationWidgetGroup->add(this->orientationAnteriorToolButtonAction); this->orientationWidgetGroup->add(this->orientationPosteriorToolButtonAction); this->orientationWidgetGroup->add(this->orientationDorsalToolButtonAction); this->orientationWidgetGroup->add(this->orientationVentralToolButtonAction); this->orientationWidgetGroup->add(this->orientationResetToolButtonAction); QWidget* orientWidget = this->createToolWidget("Orientation", w, WIDGET_PLACEMENT_LEFT, WIDGET_PLACEMENT_TOP, 0); orientWidget->setVisible(false); return orientWidget; } /** * Update the orientation widget. * * @param browserTabContent * Content of browser tab. */ void BrainBrowserWindowToolBar::updateOrientationWidget(BrowserTabContent* browserTabContent) { if (this->orientationWidget->isHidden()) { return; } this->incrementUpdateCounter(__CARET_FUNCTION_NAME__); const int32_t tabIndex = browserTabContent->getTabNumber(); this->orientationWidgetGroup->blockAllSignals(true); const Model* mdc = this->getDisplayedModel(); if (mdc != NULL) { const ModelSurface* mdcs = dynamic_cast(mdc); const ModelSurfaceMontage* mdcsm = dynamic_cast(mdc); const ModelVolume* mdcv = dynamic_cast(mdc); const ModelWholeBrain* mdcwb = dynamic_cast(mdc); bool rightFlag = false; bool leftFlag = false; bool leftRightFlag = false; bool enableDualViewOrientationButtons = false; bool showDualViewOrientationButtons = false; bool showSingleViewOrientationButtons = false; if (mdcs != NULL) { const Surface* surface = mdcs->getSurface(); const StructureEnum::Enum structure = surface->getStructure(); if (StructureEnum::isLeft(structure)) { leftFlag = true; } else if (StructureEnum::isRight(structure)) { rightFlag = true; } else { leftRightFlag = true; } showSingleViewOrientationButtons = true; } else if (mdcsm != NULL) { AString latMedLeftRightText = "LM"; AString latMedLeftRightToolTipText = "View from a Lateral/Medial perspective"; switch (mdcsm->getSelectedConfigurationType(tabIndex)) { case SurfaceMontageConfigurationTypeEnum::CEREBELLAR_CORTEX_CONFIGURATION: latMedLeftRightText = "LR"; latMedLeftRightToolTipText = "View from a Right/Left Perspective"; enableDualViewOrientationButtons = true; break; case SurfaceMontageConfigurationTypeEnum::CEREBRAL_CORTEX_CONFIGURATION: enableDualViewOrientationButtons = true; break; case SurfaceMontageConfigurationTypeEnum::FLAT_CONFIGURATION: break; } this->orientationLateralMedialToolButtonAction->setText(latMedLeftRightText); WuQtUtilities::setToolTipAndStatusTip(this->orientationLateralMedialToolButtonAction, latMedLeftRightToolTipText); showDualViewOrientationButtons = true; } else if (mdcv != NULL) { /* nothing */ } else if (mdcwb != NULL) { leftRightFlag = true; showSingleViewOrientationButtons = true; } else { CaretAssertMessage(0, "Unknown model display controller type"); } if (rightFlag || leftFlag) { if (rightFlag) { if (this->viewOrientationRightLateralIcon != NULL) { this->orientationLeftOrLateralToolButtonAction->setIcon(*this->viewOrientationRightLateralIcon); } else { this->orientationLeftOrLateralToolButtonAction->setIconText("L"); } if (this->viewOrientationRightMedialIcon != NULL) { this->orientationRightOrMedialToolButtonAction->setIcon(*this->viewOrientationRightMedialIcon); } else { this->orientationRightOrMedialToolButtonAction->setIconText("M"); } } else if (leftFlag) { if (this->viewOrientationLeftLateralIcon != NULL) { this->orientationLeftOrLateralToolButtonAction->setIcon(*this->viewOrientationLeftLateralIcon); } else { this->orientationLeftOrLateralToolButtonAction->setIconText("L"); } if (this->viewOrientationLeftMedialIcon != NULL) { this->orientationRightOrMedialToolButtonAction->setIcon(*this->viewOrientationLeftMedialIcon); } else { this->orientationRightOrMedialToolButtonAction->setIconText("M"); } } WuQtUtilities::setToolTipAndStatusTip(this->orientationLeftOrLateralToolButtonAction, "View from a LATERAL perspective"); WuQtUtilities::setToolTipAndStatusTip(this->orientationRightOrMedialToolButtonAction, "View from a MEDIAL perspective"); } else if (leftRightFlag) { if (this->viewOrientationLeftIcon != NULL) { this->orientationLeftOrLateralToolButtonAction->setIcon(*this->viewOrientationLeftIcon); } else { this->orientationLeftOrLateralToolButtonAction->setIconText("L"); } if (this->viewOrientationRightIcon != NULL) { this->orientationRightOrMedialToolButtonAction->setIcon(*this->viewOrientationRightIcon); } else { this->orientationRightOrMedialToolButtonAction->setIconText("R"); } WuQtUtilities::setToolTipAndStatusTip(this->orientationLeftOrLateralToolButtonAction, "View from a LEFT perspective"); WuQtUtilities::setToolTipAndStatusTip(this->orientationRightOrMedialToolButtonAction, "View from a RIGHT perspective"); } /* * The dual view buttons are not need for a flat map montage. * However, if they are hidden, their space is not reallocated and the * Reset button remains on the right and it looks weird. So, * display them but disable them when a flat map montage. */ this->orientationLateralMedialToolButton->setVisible(showDualViewOrientationButtons); this->orientationDorsalVentralToolButton->setVisible(showDualViewOrientationButtons); this->orientationAnteriorPosteriorToolButton->setVisible(showDualViewOrientationButtons); this->orientationLateralMedialToolButton->setEnabled(enableDualViewOrientationButtons); this->orientationDorsalVentralToolButton->setEnabled(enableDualViewOrientationButtons); this->orientationAnteriorPosteriorToolButton->setEnabled(enableDualViewOrientationButtons); this->orientationLeftOrLateralToolButton->setVisible(showSingleViewOrientationButtons); this->orientationRightOrMedialToolButton->setVisible(showSingleViewOrientationButtons); this->orientationDorsalToolButton->setVisible(showSingleViewOrientationButtons); this->orientationVentralToolButton->setVisible(showSingleViewOrientationButtons); this->orientationAnteriorToolButton->setVisible(showSingleViewOrientationButtons); this->orientationPosteriorToolButton->setVisible(showSingleViewOrientationButtons); } this->orientationWidgetGroup->blockAllSignals(false); this->decrementUpdateCounter(__CARET_FUNCTION_NAME__); } /** * Create the whole brain surface options widget. * * @return The whole brain surface options widget. */ QWidget* BrainBrowserWindowToolBar::createWholeBrainSurfaceOptionsWidget() { this->wholeBrainSurfaceTypeComboBox = WuQFactory::newComboBox(); WuQtUtilities::setToolTipAndStatusTip(this->wholeBrainSurfaceTypeComboBox, "Select the geometric type of surface for display"); QObject::connect(this->wholeBrainSurfaceTypeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(wholeBrainSurfaceTypeComboBoxIndexChanged(int))); /* * Left */ this->wholeBrainSurfaceLeftCheckBox = new QCheckBox(" "); WuQtUtilities::setToolTipAndStatusTip(this->wholeBrainSurfaceLeftCheckBox, "Enable/Disable display of the left cortical surface"); QObject::connect(this->wholeBrainSurfaceLeftCheckBox, SIGNAL(stateChanged(int)), this, SLOT(wholeBrainSurfaceLeftCheckBoxStateChanged(int))); QAction* leftSurfaceAction = WuQtUtilities::createAction("Left", "Select the whole brain left surface", this, this, SLOT(wholeBrainSurfaceLeftToolButtonTriggered(bool))); QToolButton* wholeBrainLeftSurfaceToolButton = new QToolButton(); wholeBrainLeftSurfaceToolButton->setDefaultAction(leftSurfaceAction); /* * Right */ this->wholeBrainSurfaceRightCheckBox = new QCheckBox(" "); WuQtUtilities::setToolTipAndStatusTip(this->wholeBrainSurfaceRightCheckBox, "Enable/Disable display of the right cortical surface"); QObject::connect(this->wholeBrainSurfaceRightCheckBox, SIGNAL(stateChanged(int)), this, SLOT(wholeBrainSurfaceRightCheckBoxStateChanged(int))); QAction* rightSurfaceAction = WuQtUtilities::createAction("Right", "Select the whole brain right surface", this, this, SLOT(wholeBrainSurfaceRightToolButtonTriggered(bool))); QToolButton* wholeBrainRightSurfaceToolButton = new QToolButton(); wholeBrainRightSurfaceToolButton->setDefaultAction(rightSurfaceAction); /* * Cerebellum */ this->wholeBrainSurfaceCerebellumCheckBox = new QCheckBox(" "); WuQtUtilities::setToolTipAndStatusTip(this->wholeBrainSurfaceCerebellumCheckBox, "Enable/Disable display of the cerebellum surface"); QObject::connect(this->wholeBrainSurfaceCerebellumCheckBox, SIGNAL(stateChanged(int)), this, SLOT(wholeBrainSurfaceCerebellumCheckBoxStateChanged(int))); QAction* cerebellumSurfaceAction = WuQtUtilities::createAction("Cerebellum", "Select the whole brain cerebellum surface", this, this, SLOT(wholeBrainSurfaceCerebellumToolButtonTriggered(bool))); QToolButton* wholeBrainCerebellumSurfaceToolButton = new QToolButton(); wholeBrainCerebellumSurfaceToolButton->setDefaultAction(cerebellumSurfaceAction); /* * Left/Right separation */ const int separationSpinngerWidth = 48; this->wholeBrainSurfaceSeparationLeftRightSpinBox = WuQFactory::newDoubleSpinBox(); this->wholeBrainSurfaceSeparationLeftRightSpinBox->setDecimals(0); this->wholeBrainSurfaceSeparationLeftRightSpinBox->setFixedWidth(separationSpinngerWidth); this->wholeBrainSurfaceSeparationLeftRightSpinBox->setMinimum(-100000.0); this->wholeBrainSurfaceSeparationLeftRightSpinBox->setMaximum(100000.0); WuQtUtilities::setToolTipAndStatusTip(this->wholeBrainSurfaceSeparationLeftRightSpinBox, "Adjust the separation of the left and right cortical surfaces"); QObject::connect(this->wholeBrainSurfaceSeparationLeftRightSpinBox, SIGNAL(valueChanged(double)), this, SLOT(wholeBrainSurfaceSeparationLeftRightSpinBoxValueChanged(double))); /* * Cerebellum separation */ this->wholeBrainSurfaceSeparationCerebellumSpinBox = WuQFactory::newDoubleSpinBox(); this->wholeBrainSurfaceSeparationCerebellumSpinBox->setDecimals(0); this->wholeBrainSurfaceSeparationCerebellumSpinBox->setFixedWidth(separationSpinngerWidth); this->wholeBrainSurfaceSeparationCerebellumSpinBox->setMinimum(-100000.0); this->wholeBrainSurfaceSeparationCerebellumSpinBox->setMaximum(100000.0); WuQtUtilities::setToolTipAndStatusTip(this->wholeBrainSurfaceSeparationCerebellumSpinBox, "Adjust the separation of the cerebellum from the left and right cortical surfaces"); QObject::connect(this->wholeBrainSurfaceSeparationCerebellumSpinBox, SIGNAL(valueChanged(double)), this, SLOT(wholeBrainSurfaceSeparationCerebellumSpinBoxSelected(double))); QLabel* columnTwoSpaceLabel = new QLabel(" "); wholeBrainLeftSurfaceToolButton->setText("L"); wholeBrainRightSurfaceToolButton->setText("R"); wholeBrainCerebellumSurfaceToolButton->setText("C"); bool originalFlag = false; QGridLayout* gridLayout = new QGridLayout(); if (originalFlag) { gridLayout->setVerticalSpacing(2); gridLayout->setHorizontalSpacing(2); gridLayout->addWidget(this->wholeBrainSurfaceTypeComboBox, 0, 0, 1, 6); gridLayout->addWidget(this->wholeBrainSurfaceLeftCheckBox, 1, 0); gridLayout->addWidget(wholeBrainLeftSurfaceToolButton, 1, 1); gridLayout->addWidget(columnTwoSpaceLabel, 1, 2); gridLayout->addWidget(this->wholeBrainSurfaceRightCheckBox, 1, 3); gridLayout->addWidget(wholeBrainRightSurfaceToolButton, 1, 4); gridLayout->addWidget(this->wholeBrainSurfaceSeparationLeftRightSpinBox, 1, 5); gridLayout->addWidget(this->wholeBrainSurfaceCerebellumCheckBox, 2, 0); gridLayout->addWidget(wholeBrainCerebellumSurfaceToolButton, 2, 1); gridLayout->addWidget(this->wholeBrainSurfaceSeparationCerebellumSpinBox, 2, 5); } else { gridLayout->setVerticalSpacing(2); gridLayout->setHorizontalSpacing(2); gridLayout->addWidget(this->wholeBrainSurfaceTypeComboBox, 0, 0, 1, 6); gridLayout->addWidget(this->wholeBrainSurfaceLeftCheckBox, 1, 0); gridLayout->addWidget(wholeBrainLeftSurfaceToolButton, 1, 1); gridLayout->addWidget(this->wholeBrainSurfaceRightCheckBox, 2, 0); gridLayout->addWidget(wholeBrainRightSurfaceToolButton, 2, 1); gridLayout->addWidget(this->wholeBrainSurfaceCerebellumCheckBox, 3, 0); gridLayout->addWidget(wholeBrainCerebellumSurfaceToolButton, 3, 1); gridLayout->addWidget(this->wholeBrainSurfaceSeparationLeftRightSpinBox, 1, 2, 2, 1); gridLayout->addWidget(this->wholeBrainSurfaceSeparationCerebellumSpinBox, 3, 2); } QWidget* widget = new QWidget(); QVBoxLayout* layout = new QVBoxLayout(widget); WuQtUtilities::setLayoutSpacingAndMargins(layout, 0, 0); layout->addLayout(gridLayout); this->wholeBrainSurfaceOptionsWidgetGroup = new WuQWidgetObjectGroup(this); this->wholeBrainSurfaceOptionsWidgetGroup->add(this->wholeBrainSurfaceTypeComboBox); this->wholeBrainSurfaceOptionsWidgetGroup->add(this->wholeBrainSurfaceLeftCheckBox); this->wholeBrainSurfaceOptionsWidgetGroup->add(wholeBrainLeftSurfaceToolButton); this->wholeBrainSurfaceOptionsWidgetGroup->add(this->wholeBrainSurfaceRightCheckBox); this->wholeBrainSurfaceOptionsWidgetGroup->add(wholeBrainRightSurfaceToolButton); this->wholeBrainSurfaceOptionsWidgetGroup->add(this->wholeBrainSurfaceCerebellumCheckBox); this->wholeBrainSurfaceOptionsWidgetGroup->add(wholeBrainCerebellumSurfaceToolButton); this->wholeBrainSurfaceOptionsWidgetGroup->add(this->wholeBrainSurfaceSeparationLeftRightSpinBox); this->wholeBrainSurfaceOptionsWidgetGroup->add(this->wholeBrainSurfaceSeparationCerebellumSpinBox); QWidget* w = this->createToolWidget("Surface Viewing", widget, WIDGET_PLACEMENT_LEFT, WIDGET_PLACEMENT_TOP, 0); w->setVisible(false); return w; } /** * Update the whole brain surface options widget. * * @param browserTabContent * Content of browser tab. */ void BrainBrowserWindowToolBar::updateWholeBrainSurfaceOptionsWidget(BrowserTabContent* browserTabContent) { if (this->wholeBrainSurfaceOptionsWidget->isHidden()) { return; } this->incrementUpdateCounter(__CARET_FUNCTION_NAME__); ModelWholeBrain* wholeBrainModel = browserTabContent->getDisplayedWholeBrainModel(); if (wholeBrainModel != NULL) { const int32_t tabNumber = browserTabContent->getTabNumber(); this->wholeBrainSurfaceOptionsWidgetGroup->blockAllSignals(true); std::vector availableSurfaceTypes; wholeBrainModel->getAvailableSurfaceTypes(availableSurfaceTypes); const SurfaceTypeEnum::Enum selectedSurfaceType = wholeBrainModel->getSelectedSurfaceType(tabNumber); int32_t defaultIndex = 0; this->wholeBrainSurfaceTypeComboBox->clear(); int32_t numSurfaceTypes = static_cast(availableSurfaceTypes.size()); for (int32_t i = 0; i < numSurfaceTypes; i++) { const SurfaceTypeEnum::Enum st = availableSurfaceTypes[i]; if (st == selectedSurfaceType) { defaultIndex = this->wholeBrainSurfaceTypeComboBox->count(); } const AString name = SurfaceTypeEnum::toGuiName(st); const int integerCode = SurfaceTypeEnum::toIntegerCode(st); this->wholeBrainSurfaceTypeComboBox->addItem(name, integerCode); } if (defaultIndex < this->wholeBrainSurfaceTypeComboBox->count()) { this->wholeBrainSurfaceTypeComboBox->setCurrentIndex(defaultIndex); } this->wholeBrainSurfaceLeftCheckBox->setChecked(browserTabContent->isWholeBrainLeftEnabled()); this->wholeBrainSurfaceRightCheckBox->setChecked(browserTabContent->isWholeBrainRightEnabled()); this->wholeBrainSurfaceCerebellumCheckBox->setChecked(browserTabContent->isWholeBrainCerebellumEnabled()); this->wholeBrainSurfaceSeparationLeftRightSpinBox->setValue(browserTabContent->getWholeBrainLeftRightSeparation()); this->wholeBrainSurfaceSeparationCerebellumSpinBox->setValue(browserTabContent->getWholeBrainCerebellumSeparation()); this->wholeBrainSurfaceOptionsWidgetGroup->blockAllSignals(false); } this->decrementUpdateCounter(__CARET_FUNCTION_NAME__); } /** * Create the volume indices widget. * * @return The volume indices widget. */ QWidget* BrainBrowserWindowToolBar::createVolumeIndicesWidget() { m_sliceSelectionComponent = new BrainBrowserWindowToolBarSliceSelection(this); QWidget* w = this->createToolWidget("Slice Indices/Coords", m_sliceSelectionComponent, WIDGET_PLACEMENT_LEFT, WIDGET_PLACEMENT_TOP, 0); w->setVisible(false); return w; } /** * Update the volume indices widget. * * @param browserTabContent * Content of browser tab. */ void BrainBrowserWindowToolBar::updateVolumeIndicesWidget(BrowserTabContent* browserTabContent) { if (this->volumeIndicesWidget->isHidden()) { return; } m_sliceSelectionComponent->updateContent(browserTabContent); } /** * Create the mode widget. * * @return The mode widget. */ QWidget* BrainBrowserWindowToolBar::createModeWidget() { /* * Borders */ this->modeInputModeBordersAction = WuQtUtilities::createAction("Border", "Perform border operations with mouse", this); QToolButton* inputModeBordersToolButton = new QToolButton(); this->modeInputModeBordersAction->setCheckable(true); inputModeBordersToolButton->setDefaultAction(this->modeInputModeBordersAction); /* * Foci */ this->modeInputModeFociAction = WuQtUtilities::createAction("Foci", "Perform foci operations with mouse", this); QToolButton* inputModeFociToolButton = new QToolButton(); this->modeInputModeFociAction->setCheckable(true); inputModeFociToolButton->setDefaultAction(this->modeInputModeFociAction); /* * Volume Edit */ this->modeInputVolumeEditAction = WuQtUtilities::createAction("Volume", "Edit volume voxels", this); this->modeInputVolumeEditAction->setCheckable(true); QToolButton* inputModeVolumeEditButton = new QToolButton(); inputModeVolumeEditButton->setDefaultAction(this->modeInputVolumeEditAction); /* * View Mode */ this->modeInputModeViewAction = WuQtUtilities::createAction("View", "Perform viewing operations with mouse\n" "\n" "Identify: Click Left Mouse\n" "Pan: Move mouse with left mouse button down and keyboard shift key down\n" "Rotate: Move mouse with left mouse button down\n" #ifdef CARET_OS_MACOSX "Zoom: Move mouse with left mouse button down and keyboard apple key down", #else // CARET_OS_MACOSX "Zoom: Move mouse with left mouse button down and keyboard control key down", #endif // CARET_OS_MACOSX this); this->modeInputModeViewAction->setCheckable(true); QToolButton* inputModeViewToolButton = new QToolButton(); inputModeViewToolButton->setDefaultAction(this->modeInputModeViewAction); WuQtUtilities::matchWidgetWidths(inputModeBordersToolButton, inputModeFociToolButton, inputModeViewToolButton, inputModeVolumeEditButton); /* * Layout for input modes */ QWidget* inputModeWidget = new QWidget(); QVBoxLayout* inputModeLayout = new QVBoxLayout(inputModeWidget); WuQtUtilities::setLayoutSpacingAndMargins(inputModeLayout, 2, 2); inputModeLayout->addWidget(inputModeBordersToolButton, 0, Qt::AlignHCenter); inputModeLayout->addWidget(inputModeFociToolButton, 0, Qt::AlignHCenter); inputModeLayout->addWidget(inputModeViewToolButton, 0, Qt::AlignHCenter); inputModeLayout->addWidget(inputModeVolumeEditButton, 0, Qt::AlignHCenter); this->modeInputModeActionGroup = new QActionGroup(this); this->modeInputModeActionGroup->addAction(this->modeInputModeBordersAction); this->modeInputModeActionGroup->addAction(this->modeInputModeFociAction); this->modeInputModeActionGroup->addAction(this->modeInputModeViewAction); this->modeInputModeActionGroup->addAction(this->modeInputVolumeEditAction); QObject::connect(this->modeInputModeActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(modeInputModeActionTriggered(QAction*))); this->modeInputModeActionGroup->setExclusive(true); QWidget* widget = new QWidget(); QVBoxLayout* layout = new QVBoxLayout(widget); WuQtUtilities::setLayoutSpacingAndMargins(layout, 0, 0); layout->addWidget(inputModeWidget, 0, Qt::AlignHCenter); layout->addStretch(); this->modeWidgetGroup = new WuQWidgetObjectGroup(this); this->modeWidgetGroup->add(this->modeInputModeActionGroup); QWidget* w = this->createToolWidget("Mode", widget, WIDGET_PLACEMENT_LEFT, WIDGET_PLACEMENT_NONE, 0); return w; } /** * Called when a tools input mode button is clicked. * @param action * Action of tool button that was clicked. */ void BrainBrowserWindowToolBar::modeInputModeActionTriggered(QAction* action) { BrowserTabContent* tabContent = this->getTabContentFromSelectedTab(); if (tabContent == NULL) { return; } UserInputModeAbstract::UserInputMode inputMode = UserInputModeAbstract::INVALID; if (action == this->modeInputModeBordersAction) { inputMode = UserInputModeAbstract::BORDERS; /* * If borders are not displayed, display them */ DisplayPropertiesBorders* dpb = GuiManager::get()->getBrain()->getDisplayPropertiesBorders(); const int32_t browserTabIndex = tabContent->getTabNumber(); const DisplayGroupEnum::Enum displayGroup = dpb->getDisplayGroupForTab(browserTabIndex); if (dpb->isDisplayed(displayGroup, browserTabIndex) == false) { dpb->setDisplayed(displayGroup, browserTabIndex, true); this->updateUserInterface(); this->updateGraphicsWindow(); } } else if (action == this->modeInputModeFociAction) { inputMode = UserInputModeAbstract::FOCI; } else if (action == this->modeInputVolumeEditAction) { inputMode = UserInputModeAbstract::VOLUME_EDIT; } else if (action == this->modeInputModeViewAction) { inputMode = UserInputModeAbstract::VIEW; } else { CaretAssertMessage(0, "Tools input mode action is invalid, new action added???"); } EventManager::get()->sendEvent(EventGetOrSetUserInputModeProcessor(this->browserWindowIndex, inputMode).getPointer()); this->updateModeWidget(tabContent); this->updateDisplayedModeUserInputWidget(); } /** * Update the tools widget. * * @param browserTabContent * Content of browser tab. */ void BrainBrowserWindowToolBar::updateModeWidget(BrowserTabContent* /*browserTabContent*/) { if (this->modeWidget->isHidden()) { return; } this->incrementUpdateCounter(__CARET_FUNCTION_NAME__); this->modeWidgetGroup->blockAllSignals(true); EventGetOrSetUserInputModeProcessor getInputModeEvent(this->browserWindowIndex); EventManager::get()->sendEvent(getInputModeEvent.getPointer()); switch (getInputModeEvent.getUserInputMode()) { case UserInputModeAbstract::INVALID: /* may get here when program is exiting and widgets are being destroyed */ break; case UserInputModeAbstract::BORDERS: this->modeInputModeBordersAction->setChecked(true); break; case UserInputModeAbstract::FOCI: this->modeInputModeFociAction->setChecked(true); break; case UserInputModeAbstract::VOLUME_EDIT: this->modeInputVolumeEditAction->setChecked(true); break; case UserInputModeAbstract::VIEW: this->modeInputModeViewAction->setChecked(true); break; } this->modeWidgetGroup->blockAllSignals(false); this->updateDisplayedModeUserInputWidget(); this->decrementUpdateCounter(__CARET_FUNCTION_NAME__); } void BrainBrowserWindowToolBar::updateDisplayedModeUserInputWidget() { EventGetOrSetUserInputModeProcessor getInputModeEvent(this->browserWindowIndex); EventManager::get()->sendEvent(getInputModeEvent.getPointer()); UserInputModeAbstract* userInputProcessor = getInputModeEvent.getUserInputProcessor(); QWidget* userInputWidget = userInputProcessor->getWidgetForToolBar(); /* * If a widget is display and needs to change, * remove the old widget. */ if (this->userInputControlsWidgetActiveInputWidget != NULL) { if (userInputWidget != this->userInputControlsWidgetActiveInputWidget) { /* * Remove the current input widget: * (1) Set its visibility to false * (2) Remove the widget from the toolbar's layout * (3) Set its parent to NULL. * * Why is the parent set to NULL? * * When a widget is put into a layout, the widget is put into * a QWidgetItem (subclass of QLayoutItem). * * QLayout::removeWidget() will delete the QWidgetItem but * it does not reset the parent for the widget that was in * QWidgetItem. So the user will need to delete the widget * unless it is placed into a layout. * * After removing the widget, set the widget's parent to NULL. * As a result, when the input receiver (owner of the widget) * is deleted, it can examine the parent, and, if the parent * is NULL, it can delete the widget preventing a memory link * and a possible crash. */ this->userInputControlsWidgetActiveInputWidget->setVisible(false); this->userInputControlsWidgetLayout->removeWidget(this->userInputControlsWidgetActiveInputWidget); this->userInputControlsWidgetActiveInputWidget->setParent(NULL); this->userInputControlsWidgetActiveInputWidget = NULL; } } if (this->userInputControlsWidgetActiveInputWidget == NULL) { if (userInputWidget != NULL) { this->userInputControlsWidgetActiveInputWidget = userInputWidget; this->userInputControlsWidgetActiveInputWidget->setVisible(true); this->userInputControlsWidgetLayout->addWidget(this->userInputControlsWidgetActiveInputWidget); this->userInputControlsWidget->setVisible(true); this->userInputControlsWidgetLayout->update(); } else { this->userInputControlsWidget->setVisible(false); } } } /** * Create the tab options widget. * * @return The tab options widget. */ QWidget* BrainBrowserWindowToolBar::createTabOptionsWidget() { m_tabOptionsComponent = new BrainBrowserWindowToolBarTab(this->browserWindowIndex, this); QWidget* w = this->createToolWidget("Tab", m_tabOptionsComponent, WIDGET_PLACEMENT_LEFT, WIDGET_PLACEMENT_TOP, 0); return w; } /** * Update the tab widget. * * @param browserTabContent * Content of browser tab. */ void BrainBrowserWindowToolBar::updateTabOptionsWidget(BrowserTabContent* browserTabContent) { if (this->windowWidget->isHidden()) { return; } m_tabOptionsComponent->updateContent(browserTabContent); } /** * Create the chart type widget. * * @return * Widget containing the chart options. */ QWidget* BrainBrowserWindowToolBar::createChartTypeWidget() { m_chartTypeToolBarComponent = new BrainBrowserWindowToolBarChartType(this); QWidget* w = this->createToolWidget("Chart Type", m_chartTypeToolBarComponent, WIDGET_PLACEMENT_LEFT, WIDGET_PLACEMENT_TOP, 100); w->setVisible(false); return w; } /** * Update the chart type widget. * * @param browserTabContent * The active model display (may be NULL). */ void BrainBrowserWindowToolBar::updateChartTypeWidget(BrowserTabContent* browserTabContent) { if (this->chartTypeWidget->isHidden()) { return; } m_chartTypeToolBarComponent->updateContent(browserTabContent); } /** * Create the chart axes widget. * * @return * Widget containing the chart axes. */ QWidget* BrainBrowserWindowToolBar::createChartAxesWidget() { m_chartAxisToolBarComponent = new BrainBrowserWindowToolBarChartAxes(this); QWidget* w = this->createToolWidget("Chart Axes", m_chartAxisToolBarComponent, WIDGET_PLACEMENT_LEFT, WIDGET_PLACEMENT_TOP, 100); w->setVisible(false); return w; } /** * Update the chart axes widget. * * @param browserTabContent * The active model display (may be NULL). */ void BrainBrowserWindowToolBar::updateChartAxesWidget(BrowserTabContent* browserTabContent) { if (this->chartAxesWidget->isHidden()) { return; } m_chartAxisToolBarComponent->updateContent(browserTabContent); } /** * Create the chart attributes widget. * * @return * Widget containing the chart attributes. */ QWidget* BrainBrowserWindowToolBar::createChartAttributesWidget() { m_chartAttributesToolBarComponent = new BrainBrowserWindowToolBarChartAttributes(this); QWidget* w = this->createToolWidget("Chart Attributes", m_chartAttributesToolBarComponent, WIDGET_PLACEMENT_LEFT, WIDGET_PLACEMENT_TOP, 100); w->setVisible(false); return w; } /** * Update the chart attributes widget. * * @param browserTabContent * The active model display (may be NULL). */ void BrainBrowserWindowToolBar::updateChartAttributesWidget(BrowserTabContent* browserTabContent) { if (this->chartAttributesWidget->isHidden()) { return; } m_chartAttributesToolBarComponent->updateContent(browserTabContent); } /** * Create the single surface options widget. * * @return The single surface options widget. */ QWidget* BrainBrowserWindowToolBar::createSingleSurfaceOptionsWidget() { QLabel* structureSurfaceLabel = new QLabel("Brain Structure and Surface: "); this->surfaceSurfaceSelectionControl = new StructureSurfaceSelectionControl(false); QObject::connect(this->surfaceSurfaceSelectionControl, SIGNAL(selectionChanged(const StructureEnum::Enum, ModelSurface*)), this, SLOT(surfaceSelectionControlChanged(const StructureEnum::Enum, ModelSurface*))); this->surfaceSurfaceSelectionControl->setMinimumWidth(150); this->surfaceSurfaceSelectionControl->setMaximumWidth(1200); QWidget* widget = new QWidget(); QVBoxLayout* layout = new QVBoxLayout(widget); WuQtUtilities::setLayoutSpacingAndMargins(layout, 2, 2); layout->addWidget(structureSurfaceLabel); layout->addWidget(this->surfaceSurfaceSelectionControl); layout->addStretch(); this->singleSurfaceSelectionWidgetGroup = new WuQWidgetObjectGroup(this); this->singleSurfaceSelectionWidgetGroup->add(this->surfaceSurfaceSelectionControl); QWidget* w = this->createToolWidget("Selection", widget, WIDGET_PLACEMENT_LEFT, WIDGET_PLACEMENT_TOP, 100); w->setVisible(false); return w; } /** * Update the single surface options widget. * * @param browserTabContent * The active model display controller (may be NULL). */ void BrainBrowserWindowToolBar::updateSingleSurfaceOptionsWidget(BrowserTabContent* browserTabContent) { if (this->singleSurfaceSelectionWidget->isHidden()) { return; } this->incrementUpdateCounter(__CARET_FUNCTION_NAME__); this->singleSurfaceSelectionWidgetGroup->blockAllSignals(true); this->surfaceSurfaceSelectionControl->updateControl(browserTabContent->getSurfaceModelSelector()); this->singleSurfaceSelectionWidgetGroup->blockAllSignals(false); this->decrementUpdateCounter(__CARET_FUNCTION_NAME__); } /** * @return Create and return the surface montage options widget. */ QWidget* BrainBrowserWindowToolBar::createSurfaceMontageOptionsWidget() { m_surfaceMontageToolBarComponent = new BrainBrowserWindowToolBarSurfaceMontage(this); QWidget* w = this->createToolWidget("Montage Selection", m_surfaceMontageToolBarComponent, WIDGET_PLACEMENT_LEFT, WIDGET_PLACEMENT_TOP, 100); w->setVisible(false); return w; } /** * Update the surface montage options widget. * * @param browserTabContent * The active model display controller (may be NULL). */ void BrainBrowserWindowToolBar::updateSurfaceMontageOptionsWidget(BrowserTabContent* browserTabContent) { if (this->surfaceMontageSelectionWidget->isHidden()) { return; } m_surfaceMontageToolBarComponent->updateContent(browserTabContent); } /** * @return Create and return the clipping options component. */ QWidget* BrainBrowserWindowToolBar::createClippingOptionsWidget() { m_clippingToolBarComponent = new BrainBrowserWindowToolBarClipping(this->browserWindowIndex, this); QWidget* w = this->createToolWidget("Clipping", m_clippingToolBarComponent, WIDGET_PLACEMENT_LEFT, WIDGET_PLACEMENT_TOP, 100); w->setVisible(false); return w; } /** * Update the clipping options widget. * * @param browserTabContent * The active browser tab. */ void BrainBrowserWindowToolBar::updateClippingOptionsWidget(BrowserTabContent* browserTabContent) { if (m_clippingOptionsWidget->isHidden()) { return; } m_clippingToolBarComponent->updateContent(browserTabContent); } /** * Create the volume montage widget. * * @return The volume montage widget. */ QWidget* BrainBrowserWindowToolBar::createVolumeMontageWidget() { m_volumeMontageComponent = new BrainBrowserWindowToolBarVolumeMontage(this); QWidget* w = this->createToolWidget("Montage", m_volumeMontageComponent, WIDGET_PLACEMENT_LEFT, WIDGET_PLACEMENT_TOP, 0); w->setVisible(false); return w; } /** * Update the volume montage widget. * * @param browserTabContent * Content of browser tab. */ void BrainBrowserWindowToolBar::updateVolumeMontageWidget(BrowserTabContent* browserTabContent) { if (this->volumeMontageWidget->isHidden()) { return; } this->incrementUpdateCounter(__CARET_FUNCTION_NAME__); m_volumeMontageComponent->updateContent(browserTabContent); this->decrementUpdateCounter(__CARET_FUNCTION_NAME__); } /** * Create the volume plane widget. * * @return The volume plane widget. */ QWidget* BrainBrowserWindowToolBar::createVolumePlaneWidget() { m_slicePlaneComponent = new BrainBrowserWindowToolBarSlicePlane(this); QWidget* w = this->createToolWidget("Slice Plane", m_slicePlaneComponent, WIDGET_PLACEMENT_LEFT, WIDGET_PLACEMENT_TOP, 0); w->setVisible(false); return w; } /** * Update the volume plane orientation widget. * * @param browserTabContent * Content of browser tab. */ void BrainBrowserWindowToolBar::updateVolumePlaneWidget(BrowserTabContent* browserTabContent) { if (this->volumePlaneWidget->isHidden()) { return; } m_slicePlaneComponent->updateContent(browserTabContent); } /** * Create a tool widget which is a group of widgets with * a descriptive label added. * * @param name * Name for the descriptive labe. * @param childWidget * Child widget that is in the tool widget. * @param verticalBarPlacement * Where to place a vertical bar. Values other than right or * left are ignored in which case no vertical bar is displayed. * @param contentPlacement * Where to place widget which must be top or bottom. * @return The tool widget. */ QWidget* BrainBrowserWindowToolBar::createToolWidget(const QString& name, QWidget* childWidget, const WidgetPlacement verticalBarPlacement, const WidgetPlacement contentPlacement, const int /*horizontalStretching*/) { QLabel* nameLabel = new QLabel("" + name + ""); QWidget* w = new QWidget(); QGridLayout* layout = new QGridLayout(w); layout->setColumnStretch(0, 100); layout->setColumnStretch(1, 100); WuQtUtilities::setLayoutSpacingAndMargins(layout, 2, 0); switch (contentPlacement) { case WIDGET_PLACEMENT_BOTTOM: layout->setRowStretch(0, 100); layout->setRowStretch(1, 0); layout->addWidget(childWidget, 1, 0, 1, 2); break; case WIDGET_PLACEMENT_TOP: layout->setRowStretch(1, 100); layout->setRowStretch(0, 0); layout->addWidget(childWidget, 0, 0, 1, 2); break; case WIDGET_PLACEMENT_NONE: layout->setRowStretch(0, 0); layout->addWidget(childWidget, 0, 0, 1, 2); break; default: CaretAssert(0); } layout->addWidget(nameLabel, 2, 0, 1, 2, Qt::AlignHCenter); const bool addVerticalBarOnLeftSide = (verticalBarPlacement == WIDGET_PLACEMENT_LEFT); const bool addVerticalBarOnRightSide = (verticalBarPlacement == WIDGET_PLACEMENT_RIGHT); if (addVerticalBarOnLeftSide || addVerticalBarOnRightSide) { QWidget* w2 = new QWidget(); QHBoxLayout* horizLayout = new QHBoxLayout(w2); WuQtUtilities::setLayoutSpacingAndMargins(horizLayout, 0, 0); if (addVerticalBarOnLeftSide) { horizLayout->addWidget(WuQtUtilities::createVerticalLineWidget(), 0); horizLayout->addSpacing(3); } const int widgetStretchFactor = 100; horizLayout->addWidget(w, widgetStretchFactor); if (addVerticalBarOnRightSide) { horizLayout->addSpacing(3); horizLayout->addWidget(WuQtUtilities::createVerticalLineWidget(), 0); } w = w2; } return w; } /** * Update the graphics windows for the selected tab. */ void BrainBrowserWindowToolBar::updateGraphicsWindow() { EventManager::get()->sendEvent( EventGraphicsUpdateOneWindow(this->browserWindowIndex).getPointer()); } /** * Update the user-interface. */ void BrainBrowserWindowToolBar::updateUserInterface() { EventManager::get()->sendEvent(EventUserInterfaceUpdate().setWindowIndex(this->browserWindowIndex).getPointer()); } /** * Update the toolbox for the window */ void BrainBrowserWindowToolBar::updateToolBox() { EventManager::get()->sendEvent(EventUserInterfaceUpdate().setWindowIndex(this->browserWindowIndex).addToolBox().getPointer()); } /** * Called when a view mode is selected. */ void BrainBrowserWindowToolBar::viewModeRadioButtonClicked(QAbstractButton*) { CaretLogEntering(); BrowserTabContent* btc = this->getTabContentFromSelectedTab(); if (btc == NULL) { return; } if (this->viewModeChartRadioButton->isChecked()) { btc->setSelectedModelType(ModelTypeEnum::MODEL_TYPE_CHART); } else if (this->viewModeSurfaceRadioButton->isChecked()) { btc->setSelectedModelType(ModelTypeEnum::MODEL_TYPE_SURFACE); } else if (this->viewModeSurfaceMontageRadioButton->isChecked()) { btc->setSelectedModelType(ModelTypeEnum::MODEL_TYPE_SURFACE_MONTAGE); } else if (this->viewModeVolumeRadioButton->isChecked()) { btc->setSelectedModelType(ModelTypeEnum::MODEL_TYPE_VOLUME_SLICES); } else if (this->viewModeWholeBrainRadioButton->isChecked()) { btc->setSelectedModelType(ModelTypeEnum::MODEL_TYPE_WHOLE_BRAIN); } else { btc->setSelectedModelType(ModelTypeEnum::MODEL_TYPE_INVALID); } this->checkUpdateCounter(); this->updateToolBar(); this->updateTabName(-1); this->updateToolBox(); emit viewedModelChanged(); this->updateGraphicsWindow(); } /** * Called when orientation left or lateral button is pressed. */ void BrainBrowserWindowToolBar::orientationLeftOrLateralToolButtonTriggered(bool /*checked*/) { BrowserTabContent* btc = this->getTabContentFromSelectedTab(); btc->leftView(); this->updateGraphicsWindow(); this->updateOtherYokedWindows(); this->checkUpdateCounter(); } /** * Called when orientation right or medial button is pressed. */ void BrainBrowserWindowToolBar::orientationRightOrMedialToolButtonTriggered(bool /*checked*/) { BrowserTabContent* btc = this->getTabContentFromSelectedTab(); btc->rightView(); this->updateGraphicsWindow(); this->updateOtherYokedWindows(); this->checkUpdateCounter(); } /** * Called when orientation anterior button is pressed. */ void BrainBrowserWindowToolBar::orientationAnteriorToolButtonTriggered(bool /*checked*/) { BrowserTabContent* btc = this->getTabContentFromSelectedTab(); btc->anteriorView(); this->updateGraphicsWindow(); this->updateOtherYokedWindows(); this->checkUpdateCounter(); } /** * Called when orientation posterior button is pressed. */ void BrainBrowserWindowToolBar::orientationPosteriorToolButtonTriggered(bool /*checked*/) { BrowserTabContent* btc = this->getTabContentFromSelectedTab(); btc->posteriorView(); this->updateGraphicsWindow(); this->updateOtherYokedWindows(); this->checkUpdateCounter(); } /** * Called when orientation dorsal button is pressed. */ void BrainBrowserWindowToolBar::orientationDorsalToolButtonTriggered(bool /*checked*/) { BrowserTabContent* btc = this->getTabContentFromSelectedTab(); btc->dorsalView(); this->updateGraphicsWindow(); this->updateOtherYokedWindows(); this->checkUpdateCounter(); } /** * Called when orientation ventral button is pressed. */ void BrainBrowserWindowToolBar::orientationVentralToolButtonTriggered(bool /*checked*/) { BrowserTabContent* btc = this->getTabContentFromSelectedTab(); btc->ventralView(); this->updateGraphicsWindow(); this->updateOtherYokedWindows(); this->checkUpdateCounter(); } /** * Called when orientation reset button is pressed. */ void BrainBrowserWindowToolBar::orientationResetToolButtonTriggered(bool /*checked*/) { BrowserTabContent* btc = this->getTabContentFromSelectedTab(); btc->resetView(); Model* mdc = btc->getModelForDisplay(); if (mdc != NULL) { this->updateVolumeIndicesWidget(btc); this->updateGraphicsWindow(); this->updateOtherYokedWindows(); } this->checkUpdateCounter(); } /** * Called when orientation lateral/medial button is pressed. */ void BrainBrowserWindowToolBar::orientationLateralMedialToolButtonTriggered(bool /*checked*/) { BrowserTabContent* btc = this->getTabContentFromSelectedTab(); btc->leftView(); this->updateGraphicsWindow(); this->updateOtherYokedWindows(); this->checkUpdateCounter(); } /** * Called when orientation dorsal/ventral button is pressed. */ void BrainBrowserWindowToolBar::orientationDorsalVentralToolButtonTriggered(bool /*checked*/) { BrowserTabContent* btc = this->getTabContentFromSelectedTab(); btc->dorsalView(); this->updateGraphicsWindow(); this->updateOtherYokedWindows(); this->checkUpdateCounter(); } /** * Called when orientation anterior/posterior button is pressed. */ void BrainBrowserWindowToolBar::orientationAnteriorPosteriorToolButtonTriggered(bool /*checked*/) { BrowserTabContent* btc = this->getTabContentFromSelectedTab(); btc->anteriorView(); this->updateGraphicsWindow(); this->updateOtherYokedWindows(); this->checkUpdateCounter(); } /** * Called when custom view is triggered and displays Custom View Menu. */ void BrainBrowserWindowToolBar::customViewActionTriggered() { CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); prefs->readCustomViews(); const std::vector > customViewNameAndComments = prefs->getCustomViewNamesAndComments(); QMenu menu; QAction* editAction = menu.addAction("Create and Edit..."); editAction->setToolTip("Add and delete Custom Views.\n" "Edit model transformations."); const int32_t numViews = static_cast(customViewNameAndComments.size()); if (numViews > 0) { menu.addSeparator(); } for (int32_t i = 0; i < numViews; i++) { QAction* action = menu.addAction(customViewNameAndComments[i].first); action->setToolTip(WuQtUtilities::createWordWrappedToolTipText(customViewNameAndComments[i].second)); } QAction* selectedAction = menu.exec(QCursor::pos()); if (selectedAction != NULL) { if (selectedAction == editAction) { BrainBrowserWindow* bbw = GuiManager::get()->getBrowserWindowByWindowIndex(browserWindowIndex); GuiManager::get()->processShowCustomViewDialog(bbw); } else { const AString customViewName = selectedAction->text(); ModelTransform modelTransform; if (prefs->getCustomView(customViewName, modelTransform)) { BrowserTabContent* btc = this->getTabContentFromSelectedTab(); btc->setTransformationsFromModelTransform(modelTransform); this->updateGraphicsWindow(); this->updateOtherYokedWindows(); } } } } /** * Called when the whole brain surface type combo box is changed. */ void BrainBrowserWindowToolBar::wholeBrainSurfaceTypeComboBoxIndexChanged(int /*indx*/) { CaretLogEntering(); this->checkUpdateCounter(); BrowserTabContent* btc = this->getTabContentFromSelectedTab(); const int32_t tabIndex = btc->getTabNumber(); ModelWholeBrain* wholeBrainModel = btc->getDisplayedWholeBrainModel(); if (wholeBrainModel == NULL) { return; } int32_t comboBoxIndex = this->wholeBrainSurfaceTypeComboBox->currentIndex(); if (comboBoxIndex >= 0) { const int32_t integerCode = this->wholeBrainSurfaceTypeComboBox->itemData(comboBoxIndex).toInt(); bool isValid = false; const SurfaceTypeEnum::Enum surfaceType = SurfaceTypeEnum::fromIntegerCode(integerCode, &isValid); if (isValid) { wholeBrainModel->setSelectedSurfaceType(tabIndex, surfaceType); this->updateVolumeIndicesWidget(btc); /* slices may get deselected */ this->updateGraphicsWindow(); this->updateOtherYokedWindows(); } } } /** * Called when whole brain surface left check box is toggled. */ void BrainBrowserWindowToolBar::wholeBrainSurfaceLeftCheckBoxStateChanged(int /*state*/) { CaretLogEntering(); this->checkUpdateCounter(); BrowserTabContent* btc = this->getTabContentFromSelectedTab(); ModelWholeBrain* wholeBrainModel = btc->getDisplayedWholeBrainModel(); if (wholeBrainModel == NULL) { return; } btc->setWholeBrainLeftEnabled(this->wholeBrainSurfaceLeftCheckBox->isChecked()); this->updateGraphicsWindow(); this->updateOtherYokedWindows(); } /** * Called when the left surface tool button is pressed. */ void BrainBrowserWindowToolBar::wholeBrainSurfaceLeftToolButtonTriggered(bool /*checked*/) { CaretLogEntering(); this->checkUpdateCounter(); BrowserTabContent* btc = this->getTabContentFromSelectedTab(); ModelWholeBrain* wholeBrainModel = btc->getDisplayedWholeBrainModel(); if (wholeBrainModel == NULL) { return; } const int32_t tabIndex = btc->getTabNumber(); Brain* brain = GuiManager::get()->getBrain(); BrainStructure* brainStructure = brain->getBrainStructure(StructureEnum::CORTEX_LEFT, false); if (brainStructure != NULL) { std::vector surfaces; brainStructure->getSurfacesOfType(wholeBrainModel->getSelectedSurfaceType(tabIndex), surfaces); const int32_t numSurfaces = static_cast(surfaces.size()); if (numSurfaces > 0) { Surface* selectedSurface = wholeBrainModel->getSelectedSurface(StructureEnum::CORTEX_LEFT, tabIndex); QMenu menu; QActionGroup* actionGroup = new QActionGroup(&menu); actionGroup->setExclusive(true); for (int32_t i = 0; i < numSurfaces; i++) { QString name = surfaces[i]->getFileNameNoPath(); QAction* action = actionGroup->addAction(name); action->setCheckable(true); if (surfaces[i] == selectedSurface) { action->setChecked(true); } menu.addAction(action); } QAction* result = menu.exec(QCursor::pos()); if (result != NULL) { QList actionList = actionGroup->actions(); for (int32_t i = 0; i < numSurfaces; i++) { if (result == actionList.at(i)) { wholeBrainModel->setSelectedSurface(StructureEnum::CORTEX_LEFT, tabIndex, surfaces[i]); this->updateGraphicsWindow(); this->updateOtherYokedWindows(); break; } } } } } } /** * Called when the right surface tool button is pressed. */ void BrainBrowserWindowToolBar::wholeBrainSurfaceRightToolButtonTriggered(bool /*checked*/) { CaretLogEntering(); this->checkUpdateCounter(); CaretLogEntering(); this->checkUpdateCounter(); BrowserTabContent* btc = this->getTabContentFromSelectedTab(); ModelWholeBrain* wholeBrainModel = btc->getDisplayedWholeBrainModel(); if (wholeBrainModel == NULL) { return; } const int32_t tabIndex = btc->getTabNumber(); Brain* brain = GuiManager::get()->getBrain(); BrainStructure* brainStructure = brain->getBrainStructure(StructureEnum::CORTEX_RIGHT, false); if (brainStructure != NULL) { std::vector surfaces; brainStructure->getSurfacesOfType(wholeBrainModel->getSelectedSurfaceType(tabIndex), surfaces); const int32_t numSurfaces = static_cast(surfaces.size()); if (numSurfaces > 0) { Surface* selectedSurface = wholeBrainModel->getSelectedSurface(StructureEnum::CORTEX_RIGHT, tabIndex); QMenu menu; QActionGroup* actionGroup = new QActionGroup(&menu); actionGroup->setExclusive(true); for (int32_t i = 0; i < numSurfaces; i++) { QString name = surfaces[i]->getFileNameNoPath(); QAction* action = actionGroup->addAction(name); action->setCheckable(true); if (surfaces[i] == selectedSurface) { action->setChecked(true); } menu.addAction(action); } QAction* result = menu.exec(QCursor::pos()); if (result != NULL) { QList actionList = actionGroup->actions(); for (int32_t i = 0; i < numSurfaces; i++) { if (result == actionList.at(i)) { wholeBrainModel->setSelectedSurface(StructureEnum::CORTEX_RIGHT, tabIndex, surfaces[i]); this->updateGraphicsWindow(); this->updateOtherYokedWindows(); break; } } } } } } /** * Called when the cerebellum surface tool button is pressed. */ void BrainBrowserWindowToolBar::wholeBrainSurfaceCerebellumToolButtonTriggered(bool /*checked*/) { CaretLogEntering(); this->checkUpdateCounter(); CaretLogEntering(); this->checkUpdateCounter(); BrowserTabContent* btc = this->getTabContentFromSelectedTab(); ModelWholeBrain* wholeBrainModel = btc->getDisplayedWholeBrainModel(); if (wholeBrainModel == NULL) { return; } const int32_t tabIndex = btc->getTabNumber(); Brain* brain = GuiManager::get()->getBrain(); BrainStructure* brainStructure = brain->getBrainStructure(StructureEnum::CEREBELLUM, false); if (brainStructure != NULL) { std::vector surfaces; brainStructure->getSurfacesOfType(wholeBrainModel->getSelectedSurfaceType(tabIndex), surfaces); const int32_t numSurfaces = static_cast(surfaces.size()); if (numSurfaces > 0) { Surface* selectedSurface = wholeBrainModel->getSelectedSurface(StructureEnum::CEREBELLUM, tabIndex); QMenu menu; QActionGroup* actionGroup = new QActionGroup(&menu); actionGroup->setExclusive(true); for (int32_t i = 0; i < numSurfaces; i++) { QString name = surfaces[i]->getFileNameNoPath(); QAction* action = actionGroup->addAction(name); action->setCheckable(true); if (surfaces[i] == selectedSurface) { action->setChecked(true); } menu.addAction(action); } QAction* result = menu.exec(QCursor::pos()); if (result != NULL) { QList actionList = actionGroup->actions(); for (int32_t i = 0; i < numSurfaces; i++) { if (result == actionList.at(i)) { wholeBrainModel->setSelectedSurface(StructureEnum::CEREBELLUM, tabIndex, surfaces[i]); this->updateGraphicsWindow(); this->updateOtherYokedWindows(); break; } } } } } } /** * Called when whole brain surface right checkbox is toggled. */ void BrainBrowserWindowToolBar::wholeBrainSurfaceRightCheckBoxStateChanged(int /*state*/) { CaretLogEntering(); this->checkUpdateCounter(); BrowserTabContent* btc = this->getTabContentFromSelectedTab(); ModelWholeBrain* wholeBrainModel = btc->getDisplayedWholeBrainModel(); if (wholeBrainModel == NULL) { return; } btc->setWholeBrainRightEnabled(this->wholeBrainSurfaceRightCheckBox->isChecked()); this->updateGraphicsWindow(); this->updateOtherYokedWindows(); } /** * Called when whole brain cerebellum check box is toggled. */ void BrainBrowserWindowToolBar::wholeBrainSurfaceCerebellumCheckBoxStateChanged(int /*state*/) { CaretLogEntering(); this->checkUpdateCounter(); BrowserTabContent* btc = this->getTabContentFromSelectedTab(); ModelWholeBrain* wholeBrainModel = btc->getDisplayedWholeBrainModel(); if (wholeBrainModel == NULL) { return; } btc->setWholeBrainCerebellumEnabled(this->wholeBrainSurfaceCerebellumCheckBox->isChecked()); this->updateGraphicsWindow(); this->updateOtherYokedWindows(); } /** * Called when whole brain separation left/right spin box value is changed. */ void BrainBrowserWindowToolBar::wholeBrainSurfaceSeparationLeftRightSpinBoxValueChanged(double /*d*/) { CaretLogEntering(); this->checkUpdateCounter(); BrowserTabContent* btc = this->getTabContentFromSelectedTab(); ModelWholeBrain* wholeBrainModel = btc->getDisplayedWholeBrainModel(); if (wholeBrainModel == NULL) { return; } btc->setWholeBrainLeftRightSeparation(this->wholeBrainSurfaceSeparationLeftRightSpinBox->value()); this->updateGraphicsWindow(); this->updateOtherYokedWindows(); } /** * Called when whole brain left&right/cerebellum spin box value is changed. */ void BrainBrowserWindowToolBar::wholeBrainSurfaceSeparationCerebellumSpinBoxSelected(double /*d*/) { CaretLogEntering(); this->checkUpdateCounter(); BrowserTabContent* btc = this->getTabContentFromSelectedTab(); ModelWholeBrain* wholeBrainModel = btc->getDisplayedWholeBrainModel(); if (wholeBrainModel == NULL) { return; } btc->setWholeBrainCerebellumSeparation(this->wholeBrainSurfaceSeparationCerebellumSpinBox->value()); this->updateGraphicsWindow(); this->updateOtherYokedWindows(); } /** * Called when a single surface control is changed. * @param structure * Structure that is selected. * @param surfaceModel * Model that is selected. */ void BrainBrowserWindowToolBar::surfaceSelectionControlChanged( const StructureEnum::Enum structure, ModelSurface* surfaceModel) { if (surfaceModel != NULL) { BrowserTabContent* btc = this->getTabContentFromSelectedTab(); ModelSurfaceSelector* surfaceModelSelector = btc->getSurfaceModelSelector(); surfaceModelSelector->setSelectedStructure(structure); surfaceModelSelector->setSelectedSurfaceModel(surfaceModel); EventManager::get()->sendEvent(EventSurfaceColoringInvalidate().getPointer()); this->updateUserInterface(); this->updateGraphicsWindow(); } this->updateTabName(-1); this->checkUpdateCounter(); } void BrainBrowserWindowToolBar::checkUpdateCounter() { if (this->updateCounter != 0) { CaretLogWarning(AString("Update counter is non-zero, this indicates that signal needs to be blocked during update, value=") + AString::number(updateCounter)); } } void BrainBrowserWindowToolBar::incrementUpdateCounter(const char* /*methodName*/) { this->updateCounter++; } void BrainBrowserWindowToolBar::decrementUpdateCounter(const char* /*methodName*/) { this->updateCounter--; } /** * Receive events from the event manager. * * @param event * Event sent by event manager. */ void BrainBrowserWindowToolBar::receiveEvent(Event* event) { if (event->getEventType() == EventTypeEnum::EVENT_BROWSER_WINDOW_CONTENT_GET) { EventBrowserWindowContentGet* getModelEvent = dynamic_cast(event); CaretAssert(getModelEvent); if (getModelEvent->getBrowserWindowIndex() == this->browserWindowIndex) { BrainBrowserWindow* browserWindow = GuiManager::get()->getBrowserWindowByWindowIndex(this->browserWindowIndex); if (browserWindow != NULL) { if (browserWindow->isTileTabsSelected()) { const int32_t numTabs = this->tabBar->count(); for (int32_t i = 0; i < numTabs; i++) { BrowserTabContent* btc = this->getTabContentFromTab(i); getModelEvent->addTabContentToDraw(btc); } /* * Tab that is highlighted so user knows which tab * any changes in toolbar/toolboxes apply to. */ getModelEvent->setTabIndexForTileTabsHighlighting(m_tabIndexForTileTabsHighlighting); getModelEvent->setTileTabsConfiguration(browserWindow->getSelectedTileTabsConfiguration()); } else { BrowserTabContent* btc = this->getTabContentFromSelectedTab(); getModelEvent->addTabContentToDraw(btc); } getModelEvent->setSelectedBrowserTabContent(this->getTabContentFromSelectedTab()); } getModelEvent->setEventProcessed(); } } else if (event->getEventType() == EventTypeEnum::EVENT_USER_INTERFACE_UPDATE) { EventUserInterfaceUpdate* uiEvent = dynamic_cast(event); CaretAssert(uiEvent); if (uiEvent->isToolBarUpdate() && uiEvent->isUpdateForWindow(this->browserWindowIndex)) { this->updateToolBar(); uiEvent->setEventProcessed(); } } else if (event->getEventType() == EventTypeEnum::EVENT_BROWSER_WINDOW_CREATE_TABS) { EventBrowserWindowCreateTabs* tabEvent = dynamic_cast(event); CaretAssert(tabEvent); EventModelGetAll eventAllModels; EventManager::get()->sendEvent(eventAllModels.getPointer()); const bool haveModels = (eventAllModels.getModels().empty() == false); if (haveModels) { switch (tabEvent->getMode()) { case EventBrowserWindowCreateTabs::MODE_LOADED_DATA_FILE: if (tabBar->count() == 0) { AString errorMessage; createNewTab(errorMessage); if (errorMessage.isEmpty() == false) { CaretLogSevere(errorMessage); } } break; case EventBrowserWindowCreateTabs::MODE_LOADED_SPEC_FILE: this->addDefaultTabsAfterLoadingSpecFile(); break; } } tabEvent->setEventProcessed(); } else if (event->getEventType() == EventTypeEnum::EVENT_UPDATE_YOKED_WINDOWS) { EventUpdateYokedWindows* yokeUpdateEvent = dynamic_cast(event); CaretAssert(yokeUpdateEvent); BrowserTabContent* browserTabContent = getTabContentFromSelectedTab(); if (browserTabContent != NULL) { if (this->browserWindowIndex != yokeUpdateEvent->getBrowserWindowIndexThatIssuedEvent()) { if (browserTabContent->getYokingGroup() == yokeUpdateEvent->getYokingGroup()) { this->updateToolBar(); this->updateGraphicsWindow(); } } } } else if (event->getEventType() == EventTypeEnum::EVENT_BROWSER_TAB_GET_ALL_VIEWED) { EventBrowserTabGetAllViewed* viewedTabsEvent = dynamic_cast(event); CaretAssert(viewedTabsEvent); BrainBrowserWindow* browserWindow = GuiManager::get()->getBrowserWindowByWindowIndex(this->browserWindowIndex); if (browserWindow != NULL) { if (browserWindow->isTileTabsSelected()) { const int32_t numTabs = this->tabBar->count(); for (int32_t i = 0; i < numTabs; i++) { BrowserTabContent* btc = this->getTabContentFromTab(i); viewedTabsEvent->addViewedBrowserTab(btc); } } else { BrowserTabContent* btc = getTabContentFromSelectedTab(); if (btc != NULL) { viewedTabsEvent->addViewedBrowserTab(btc); } } } viewedTabsEvent->setEventProcessed(); } else { } // CaretLogFinest("Toolbar width/height: " // + AString::number(width()) // + "/" // + AString::number(height())); } /** * If this windows is yoked, issue an event to update other * windows that are using the same yoking. */ void BrainBrowserWindowToolBar::updateOtherYokedWindows() { BrowserTabContent* browserTabContent = getTabContentFromSelectedTab(); if (browserTabContent != NULL) { if (browserTabContent->isYoked()) { EventManager::get()->sendEvent(EventUpdateYokedWindows(this->browserWindowIndex, browserTabContent->getYokingGroup()).getPointer()); } } } /** * Get the content in the browser tab. * @return * Browser tab contents in the selected tab or NULL if none. */ BrowserTabContent* BrainBrowserWindowToolBar::getTabContentFromSelectedTab() { const int tabIndex = this->tabBar->currentIndex(); BrowserTabContent* btc = this->getTabContentFromTab(tabIndex); return btc; } /** * Get the content in the given browser tab * @param tabIndex * Index of tab. * @return * Browser tab contents in the selected tab or NULL if none. */ BrowserTabContent* BrainBrowserWindowToolBar::getTabContentFromTab(const int tabIndex) { if ((tabIndex >= 0) && (tabIndex < this->tabBar->count())) { void* p = this->tabBar->tabData(tabIndex).value(); BrowserTabContent* btc = (BrowserTabContent*)p; return btc; } return NULL; } /** * Get the model displayed in the selected tab. * @return * Model in the selected tab or NULL if * no model is displayed. */ Model* BrainBrowserWindowToolBar::getDisplayedModel() { Model* mdc = NULL; BrowserTabContent* btc = this->getTabContentFromSelectedTab(); if (btc != NULL) { mdc = btc->getModelForDisplay(); } return mdc; } /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ SceneClass* BrainBrowserWindowToolBar::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "BrainBrowserWindowToolBar", 1); switch (sceneAttributes->getSceneType()) { case SceneTypeEnum::SCENE_TYPE_FULL: break; case SceneTypeEnum::SCENE_TYPE_GENERIC: break; } /* * Determine tabs in this toolbar that should be saved to scene */ const std::vector tabIndicesForScene = sceneAttributes->getIndicesOfTabsForSavingToScene(); std::vector tabIndicesToSave; const int numTabsInToolbar = this->tabBar->count(); if (numTabsInToolbar > 0) { for (int32_t i = 0; i < numTabsInToolbar; i++) { BrowserTabContent* btc = this->getTabContentFromTab(i); const int32_t tabIndex = btc->getTabNumber(); if (std::find(tabIndicesForScene.begin(), tabIndicesForScene.end(), tabIndex) != tabIndicesForScene.end()) { tabIndicesToSave.push_back(tabIndex); } } } /* * Save the tabs */ const int numTabs = static_cast(tabIndicesToSave.size()); if (numTabs > 0) { SceneIntegerArray* sceneTabIndexArray = new SceneIntegerArray("tabIndices", numTabs); for (int32_t i = 0; i < numTabs; i++) { const int32_t tabIndex = tabIndicesToSave[i]; sceneTabIndexArray->setValue(i, tabIndex); } sceneClass->addChild(sceneTabIndexArray); } /* * Add selected tab to scene */ int32_t selectedTabIndex = -1; BrowserTabContent* selectedTab = getTabContentFromSelectedTab(); if (selectedTab != NULL) { selectedTabIndex = selectedTab->getTabNumber(); } sceneClass->addInteger("selectedTabIndex", selectedTabIndex); /* * Toolbar visible */ sceneClass->addBoolean("toolBarVisible", m_toolbarWidget->isVisible()); return sceneClass; } /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * SceneClass containing the state that was previously * saved and should be restored. */ void BrainBrowserWindowToolBar::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } switch (sceneAttributes->getSceneType()) { case SceneTypeEnum::SCENE_TYPE_FULL: break; case SceneTypeEnum::SCENE_TYPE_GENERIC: break; } /* * Close any tabs */ const int32_t numberOfOpenTabs = this->tabBar->count(); for (int32_t iClose = (numberOfOpenTabs - 1); iClose >= 0; iClose--) { this->tabClosed(iClose); } /* * Index of selected browser tab (NOT the tabBar) */ const int32_t selectedTabIndex = sceneClass->getIntegerValue("selectedTabIndex", -1); /* * Create new tabs */ int32_t defaultTabBarIndex = 0; const ScenePrimitiveArray* sceneTabIndexArray = sceneClass->getPrimitiveArray("tabIndices"); if (sceneTabIndexArray != NULL) { const int32_t numValidTabs = sceneTabIndexArray->getNumberOfArrayElements(); for (int32_t iTab = 0; iTab < numValidTabs; iTab++) { const int32_t tabIndex = sceneTabIndexArray->integerValue(iTab); EventBrowserTabGet getTabContent(tabIndex); EventManager::get()->sendEvent(getTabContent.getPointer()); BrowserTabContent* tabContent = getTabContent.getBrowserTab(); if (tabContent != NULL) { if (tabContent->getTabNumber() == selectedTabIndex) { defaultTabBarIndex = iTab; } this->insertTabAtIndex(tabContent, iTab); } else { sceneAttributes->addToErrorMessage("Toolbar in window " + AString::number(this->browserWindowIndex) + " failed to restore tab " + AString::number(selectedTabIndex)); } } } /* * Select tab */ if ((defaultTabBarIndex >= 0) && (defaultTabBarIndex < tabBar->count())) { tabBar->setCurrentIndex(defaultTabBarIndex); } /* * Show hide toolbar */ const bool showToolBar = sceneClass->getBooleanValue("toolBarVisible", true); showHideToolBar(showToolBar); } workbench-1.1.1/src/GuiQt/BrainBrowserWindowToolBar.h000066400000000000000000000372361255417355300225640ustar00rootroot00000000000000#ifndef __BRAIN_BROWSER_WINDOW_TOOLBAR_H__ #define __BRAIN_BROWSER_WINDOW_TOOLBAR_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include "EnumComboBoxTemplate.h" #include "EventListenerInterface.h" #include "ModelTypeEnum.h" #include "SceneableInterface.h" #include "StructureEnum.h" class QAbstractButton; class QAction; class QActionGroup; class QButtonGroup; class QCheckBox; class QComboBox; class QDoubleSpinBox; class QHBoxLayout; class QIcon; class QLabel; class QMenu; class QRadioButton; class QSpinBox; class QTabBar; class QToolButton; namespace caret { class BrainBrowserWindowToolBarChartAxes; class BrainBrowserWindowToolBarChartAttributes; class BrainBrowserWindowToolBarChartType; class BrainBrowserWindowToolBarClipping; class BrainBrowserWindowToolBarSlicePlane; class BrainBrowserWindowToolBarSliceSelection; class BrainBrowserWindowToolBarSurfaceMontage; class BrainBrowserWindowToolBarTab; class BrainBrowserWindowToolBarVolumeMontage; class BrainBrowserWindow; class BrowserTabContent; class Model; class ModelSurface; class ModelVolumeInterface; class SceneAttributes; class SceneClass; class Surface; class SurfaceSelectionViewController; class StructureSurfaceSelectionControl; class WuQWidgetObjectGroup; class BrainBrowserWindowToolBar : public QToolBar, public EventListenerInterface, public SceneableInterface { Q_OBJECT public: BrainBrowserWindowToolBar(const int32_t browserWindowIndex, BrowserTabContent* initialBrowserTabContent, QAction* overlayToolBoxAction, QAction* layersToolBoxAction, BrainBrowserWindow* parentBrainBrowserWindow); ~BrainBrowserWindowToolBar(); void addNewTab(); void addNewTabCloneContent(BrowserTabContent* browserTabContentToBeCloned); void addNewTabWithContent(BrowserTabContent* browserTabContent); void addDefaultTabsAfterLoadingSpecFile(); void receiveEvent(Event* event); virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); signals: void viewedModelChanged(); private: enum WidgetPlacement { WIDGET_PLACEMENT_NONE, WIDGET_PLACEMENT_BOTTOM, WIDGET_PLACEMENT_LEFT, WIDGET_PLACEMENT_RIGHT, WIDGET_PLACEMENT_TOP }; BrainBrowserWindowToolBar(const BrainBrowserWindowToolBar&); BrainBrowserWindowToolBar& operator=(const BrainBrowserWindowToolBar&); BrowserTabContent* getTabContentFromSelectedTab(); BrowserTabContent* getTabContentFromTab(const int tabIndex); Model* getDisplayedModel(); int32_t loadIntoTab(const int32_t tabIndex, Model* controller); void updateGraphicsWindow(); void updateUserInterface(); void updateToolBox(); void updateTabName(const int32_t tabIndex); void updateOtherYokedWindows(); QWidget* createViewWidget(); QWidget* createOrientationWidget(); QWidget* createWholeBrainSurfaceOptionsWidget(); QWidget* createVolumeIndicesWidget(); QWidget* createModeWidget(); QWidget* createTabOptionsWidget(); QWidget* createChartAxesWidget(); QWidget* createChartAttributesWidget(); QWidget* createChartTypeWidget(); QWidget* createSingleSurfaceOptionsWidget(); QWidget* createSurfaceMontageOptionsWidget(); QWidget* createClippingOptionsWidget(); QWidget* createVolumeMontageWidget(); QWidget* createVolumePlaneWidget(); ModelTypeEnum::Enum updateViewWidget(BrowserTabContent* browserTabContent); void updateOrientationWidget(BrowserTabContent* browserTabContent); void updateWholeBrainSurfaceOptionsWidget(BrowserTabContent* browserTabContent); void updateVolumeIndicesWidget(BrowserTabContent* browserTabContent); void updateModeWidget(BrowserTabContent* browserTabContent); void updateTabOptionsWidget(BrowserTabContent* browserTabContent); void updateSingleSurfaceOptionsWidget(BrowserTabContent* browserTabContent); void updateSurfaceMontageOptionsWidget(BrowserTabContent* browserTabContent); void updateChartAxesWidget(BrowserTabContent* browserTabContent); void updateChartAttributesWidget(BrowserTabContent* browserTabContent); void updateChartTypeWidget(BrowserTabContent* browserTabContent); void updateVolumeMontageWidget(BrowserTabContent* browserTabContent); void updateVolumePlaneWidget(BrowserTabContent* browserTabContent); void updateClippingOptionsWidget(BrowserTabContent* browserTabContent); QWidget* createToolWidget(const QString& name, QWidget* childWidget, const WidgetPlacement verticalBarPlacement, const WidgetPlacement contentPlacement, const int horizontalStretching); QWidget* viewWidget; QWidget* orientationWidget; QWidget* wholeBrainSurfaceOptionsWidget; QWidget* volumeIndicesWidget; QWidget* modeWidget; QWidget* windowWidget; QWidget* singleSurfaceSelectionWidget; QWidget* surfaceMontageSelectionWidget; QWidget* m_clippingOptionsWidget; QWidget* volumeMontageWidget; QWidget* volumePlaneWidget; QWidget* chartTypeWidget; QWidget* chartAxesWidget; QWidget* chartAttributesWidget; WuQWidgetObjectGroup* viewWidgetGroup; WuQWidgetObjectGroup* orientationWidgetGroup; WuQWidgetObjectGroup* wholeBrainSurfaceOptionsWidgetGroup; WuQWidgetObjectGroup* modeWidgetGroup; WuQWidgetObjectGroup* singleSurfaceSelectionWidgetGroup; QWidget* fullToolBarWidget; QWidget* m_toolbarWidget; QHBoxLayout* toolbarWidgetLayout; QWidget* tabBarWidget; QTabBar* tabBar; /** Widget displayed at bottom of toolbar for mouse input controls */ QWidget* userInputControlsWidget; /** Layout for widget displayed at bottom of toolbar for mouse input controls */ QHBoxLayout* userInputControlsWidgetLayout; /** Is set to the user input widget provided by the user input processor */ QWidget* userInputControlsWidgetActiveInputWidget; /** * When updating, no signals should be emitted. This variable * is incremented at the beginning of an update method and * decremented at the end of the update method. If it is * non-zero in a slot method, then a signal was emitted during * the update and the widget that emitted the signal should * have its signal blocked. */ void incrementUpdateCounter(const char* methodName); void decrementUpdateCounter(const char* methodName); void checkUpdateCounter(); int updateCounter; void removeAndReturnAllTabs(std::vector& allTabContent); void getAllTabContent(std::vector& allTabContent) const; void removeTabWithContent(BrowserTabContent* browserTabContent); public slots: void closeSelectedTab(); void moveTabsToNewWindows(); void nextTab(); void previousTab(); void renameTab(); void updateToolBar(); void updateToolBarComponents(BrowserTabContent* browserTabContent); void showHideToolBar(bool showIt); private slots: void selectedTabChanged(int indx); void tabClosed(int index); void tabMoved(int, int); private: void insertTabAtIndex(BrowserTabContent* browserTabContent, const int32_t insertAtIndex); void addOrInsertNewTab(BrowserTabContent* browserTabContent, const int32_t insertAtIndex); void removeTab(int index); BrowserTabContent* createNewTab(AString& errorMessage); QRadioButton* viewModeSurfaceRadioButton; QRadioButton* viewModeSurfaceMontageRadioButton; QRadioButton* viewModeVolumeRadioButton; QRadioButton* viewModeWholeBrainRadioButton; QRadioButton* viewModeChartRadioButton; QAction* customViewAction; private slots: void viewModeRadioButtonClicked(QAbstractButton*); void customViewActionTriggered(); private: QAction* orientationLateralMedialToolButtonAction; QAction* orientationDorsalVentralToolButtonAction; QAction* orientationAnteriorPosteriorToolButtonAction; QToolButton* orientationLateralMedialToolButton; QToolButton* orientationDorsalVentralToolButton; QToolButton* orientationAnteriorPosteriorToolButton; QAction* orientationLeftOrLateralToolButtonAction; QAction* orientationRightOrMedialToolButtonAction; QAction* orientationAnteriorToolButtonAction; QAction* orientationPosteriorToolButtonAction; QAction* orientationDorsalToolButtonAction; QAction* orientationVentralToolButtonAction; QToolButton* orientationLeftOrLateralToolButton; QToolButton* orientationRightOrMedialToolButton; QToolButton* orientationAnteriorToolButton; QToolButton* orientationPosteriorToolButton; QToolButton* orientationDorsalToolButton; QToolButton* orientationVentralToolButton; QAction* orientationResetToolButtonAction; QToolButton* orientationCustomViewSelectToolButton; QIcon* viewOrientationLeftIcon; QIcon* viewOrientationRightIcon; QIcon* viewOrientationAnteriorIcon; QIcon* viewOrientationPosteriorIcon; QIcon* viewOrientationDorsalIcon; QIcon* viewOrientationVentralIcon; QIcon* viewOrientationLeftLateralIcon; QIcon* viewOrientationLeftMedialIcon; QIcon* viewOrientationRightLateralIcon; QIcon* viewOrientationRightMedialIcon; private slots: void orientationLeftOrLateralToolButtonTriggered(bool checked); void orientationRightOrMedialToolButtonTriggered(bool checked); void orientationAnteriorToolButtonTriggered(bool checked); void orientationPosteriorToolButtonTriggered(bool checked); void orientationDorsalToolButtonTriggered(bool checked); void orientationVentralToolButtonTriggered(bool checked); void orientationResetToolButtonTriggered(bool checked); void orientationLateralMedialToolButtonTriggered(bool checked); void orientationDorsalVentralToolButtonTriggered(bool checked); void orientationAnteriorPosteriorToolButtonTriggered(bool checked); private: QComboBox* wholeBrainSurfaceTypeComboBox; QCheckBox* wholeBrainSurfaceLeftCheckBox; QCheckBox* wholeBrainSurfaceRightCheckBox; QCheckBox* wholeBrainSurfaceCerebellumCheckBox; QDoubleSpinBox* wholeBrainSurfaceSeparationLeftRightSpinBox; QDoubleSpinBox* wholeBrainSurfaceSeparationCerebellumSpinBox; private slots: void wholeBrainSurfaceTypeComboBoxIndexChanged(int indx); void wholeBrainSurfaceLeftCheckBoxStateChanged(int state); void wholeBrainSurfaceRightCheckBoxStateChanged(int state); void wholeBrainSurfaceCerebellumCheckBoxStateChanged(int state); void wholeBrainSurfaceSeparationLeftRightSpinBoxValueChanged(double d); void wholeBrainSurfaceSeparationCerebellumSpinBoxSelected(double d); void wholeBrainSurfaceLeftToolButtonTriggered(bool checked); void wholeBrainSurfaceRightToolButtonTriggered(bool checked); void wholeBrainSurfaceCerebellumToolButtonTriggered(bool checked); private: StructureSurfaceSelectionControl* surfaceSurfaceSelectionControl; private slots: void surfaceSelectionControlChanged(const StructureEnum::Enum, ModelSurface*); private: BrainBrowserWindowToolBarChartAxes* m_chartAxisToolBarComponent; BrainBrowserWindowToolBarChartType* m_chartTypeToolBarComponent; BrainBrowserWindowToolBarChartAttributes* m_chartAttributesToolBarComponent; BrainBrowserWindowToolBarSurfaceMontage* m_surfaceMontageToolBarComponent; BrainBrowserWindowToolBarClipping* m_clippingToolBarComponent; BrainBrowserWindowToolBarSlicePlane* m_slicePlaneComponent; BrainBrowserWindowToolBarSliceSelection* m_sliceSelectionComponent; BrainBrowserWindowToolBarVolumeMontage* m_volumeMontageComponent; BrainBrowserWindowToolBarTab* m_tabOptionsComponent; private slots: void modeInputModeActionTriggered(QAction*); private: void updateDisplayedModeUserInputWidget(); QActionGroup* modeInputModeActionGroup; QAction* modeInputModeBordersAction; QAction* modeInputModeFociAction; QAction* modeInputModeViewAction; QAction* modeInputVolumeEditAction; private: QAction* toolBarToolButtonAction; QAction* toolBoxToolButtonAction; int32_t browserWindowIndex; private slots: void resetTabIndexForTileTabsHighlighting(); private: friend class BrainBrowserWindow; friend class BrainBrowserWindowToolBarChartAxes; friend class BrainBrowserWindowToolBarChartType; friend class BrainBrowserWindowToolBarClipping; friend class BrainBrowserWindowToolBarComponent; friend class BrainBrowserWindowToolBarSurfaceMontage; friend class BrainBrowserWindowToolBarSlicePlane; friend class BrainBrowserWindowToolBarSliceSelection; friend class BrainBrowserWindowToolBarTab; friend class BrainBrowserWindowToolBarVolumeMontage; /** * When a tab is selected in Tile Tabs viewing, * the graphics window content of the tab is * highlighted for a short time by drawing a * box around it. */ int32_t m_tabIndexForTileTabsHighlighting; bool isContructorFinished; bool isDestructionInProgress; }; } #endif // __BRAIN_BROWSER_WINDOW_TOOLBAR_H__ workbench-1.1.1/src/GuiQt/BrainBrowserWindowToolBarChartAttributes.cxx000066400000000000000000000501731255417355300261630ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #include #include #define __BRAIN_BROWSER_WINDOW_TOOL_BAR_CHART_ATTRIBUTES_DECLARE__ #include "BrainBrowserWindowToolBarChartAttributes.h" #undef __BRAIN_BROWSER_WINDOW_TOOL_BAR_CHART_ATTRIBUTES_DECLARE__ #include "BrowserTabContent.h" #include "CaretAssert.h" #include "CaretDataFile.h" #include "CaretDataFileSelectionModel.h" #include "EnumComboBoxTemplate.h" #include "CaretMappableDataFile.h" #include "CaretMappableDataFileAndMapSelectionModel.h" #include "ChartMatrixDisplayProperties.h" #include "ChartModelDataSeries.h" #include "ChartModelFrequencySeries.h" #include "ChartModelTimeSeries.h" #include "ChartableMatrixInterface.h" #include "EventBrowserWindowGraphicsRedrawn.h" #include "EventGraphicsUpdateAllWindows.h" #include "EventManager.h" #include "ModelChart.h" #include "WuQFactory.h" #include "WuQWidgetObjectGroup.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::BrainBrowserWindowToolBarChartAttributes * \brief Controls for chart attributes. * \ingroup GuiQt */ /** * Constructor. * * @param parentToolBar * The parent toolbar. */ BrainBrowserWindowToolBarChartAttributes::BrainBrowserWindowToolBarChartAttributes(BrainBrowserWindowToolBar* parentToolBar) : BrainBrowserWindowToolBarComponent(parentToolBar) { m_cartesianChartAttributesWidget = new CartesianChartAttributesWidget(this); m_matrixChartAttributesWidget = new MatrixChartAttributesWidget(this); m_stackedWidget = new QStackedWidget(); m_stackedWidget->addWidget(m_cartesianChartAttributesWidget); m_stackedWidget->addWidget(m_matrixChartAttributesWidget); QVBoxLayout* layout = new QVBoxLayout(this); WuQtUtilities::setLayoutSpacingAndMargins(layout, 0, 0); layout->addWidget(m_stackedWidget); layout->addStretch(); } /** * Destructor. */ BrainBrowserWindowToolBarChartAttributes::~BrainBrowserWindowToolBarChartAttributes() { } /** * Update content of this tool bar component. * * @param browserTabContent * Content of the browser tab. */ void BrainBrowserWindowToolBarChartAttributes::updateContent(BrowserTabContent* /*browserTabContent*/) { ChartModelCartesian* cartesianChart = getCartesianChart(); ChartMatrixDisplayProperties* matrixProperties = getChartableMatrixDisplayProperties(); if (cartesianChart != NULL) { m_stackedWidget->setCurrentWidget(m_cartesianChartAttributesWidget); m_cartesianChartAttributesWidget->updateContent(); m_stackedWidget->setEnabled(true); } else if (matrixProperties) { m_stackedWidget->setCurrentWidget(m_matrixChartAttributesWidget); m_matrixChartAttributesWidget->updateContent(); m_stackedWidget->setEnabled(true); } else { m_stackedWidget->setEnabled(false); } } /** * @return Cartesian chart in this widget. Returned value will * be NULL if the chart (or no chart) is not a Cartesian Chart. */ ChartModelCartesian* BrainBrowserWindowToolBarChartAttributes::getCartesianChart() { ChartModelCartesian* cartesianChart = NULL; BrowserTabContent* browserTabContent = getTabContentFromSelectedTab(); if (browserTabContent != NULL) { ModelChart* modelChart = browserTabContent->getDisplayedChartModel(); if (modelChart != NULL) { const int32_t tabIndex = browserTabContent->getTabNumber(); const ChartDataTypeEnum::Enum chartType = modelChart->getSelectedChartDataType(tabIndex); switch (chartType) { case ChartDataTypeEnum::CHART_DATA_TYPE_INVALID: break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_LAYER: break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_SERIES: break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_DATA_SERIES: cartesianChart = modelChart->getSelectedDataSeriesChartModel(tabIndex); //dynamic_cast(chart); break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_FREQUENCY_SERIES: cartesianChart = modelChart->getSelectedFrequencySeriesChartModel(tabIndex); break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_TIME_SERIES: cartesianChart = modelChart->getSelectedTimeSeriesChartModel(tabIndex); //dynamic_cast(chart); break; } } } return cartesianChart; } /** * @return Matrix chart interface in this widget. Returned value will * be NULL if the chart (or no chart) is not a Matrix Chart. */ ChartMatrixDisplayProperties* BrainBrowserWindowToolBarChartAttributes::getChartableMatrixDisplayProperties() { ChartMatrixDisplayProperties* matrixDisplayProperties = NULL; BrowserTabContent* browserTabContent = getTabContentFromSelectedTab(); if (browserTabContent != NULL) { ModelChart* modelChart = browserTabContent->getDisplayedChartModel(); if (modelChart != NULL) { const int32_t tabIndex = browserTabContent->getTabNumber(); const ChartDataTypeEnum::Enum chartType = modelChart->getSelectedChartDataType(tabIndex); switch (chartType) { case ChartDataTypeEnum::CHART_DATA_TYPE_INVALID: break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_LAYER: { ChartableMatrixInterface* matrixInterface = modelChart->getChartableMatrixParcelFileSelectionModel(tabIndex)->getSelectedFileOfType(); if (matrixInterface != NULL) { matrixDisplayProperties = matrixInterface->getChartMatrixDisplayProperties(tabIndex); } } break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_SERIES: { CaretDataFileSelectionModel* fileModel = modelChart->getChartableMatrixSeriesFileSelectionModel(tabIndex); CaretDataFile* caretFile = fileModel->getSelectedFile(); if (caretFile != NULL) { ChartableMatrixInterface* matrixInterface = dynamic_cast(caretFile); if (matrixInterface != NULL) { matrixDisplayProperties = matrixInterface->getChartMatrixDisplayProperties(tabIndex); } } } break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_DATA_SERIES: break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_FREQUENCY_SERIES: break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_TIME_SERIES: break; } } } return matrixDisplayProperties; } /** * Update the graphics. */ void BrainBrowserWindowToolBarChartAttributes::updateGraphics() { EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } /* ===========================================================================*/ /** * \class caret::CartesianChartAttributesWidget * \brief Controls for cartesian chart attributes. * \ingroup GuiQt */ /** * Constructor. * * @param brainBrowserWindowToolBarChartAttributes * The parent attributes widget. */ CartesianChartAttributesWidget::CartesianChartAttributesWidget(BrainBrowserWindowToolBarChartAttributes* brainBrowserWindowToolBarChartAttributes) : QWidget(brainBrowserWindowToolBarChartAttributes) { m_brainBrowserWindowToolBarChartAttributes = brainBrowserWindowToolBarChartAttributes; QLabel* cartesianLineWidthLabel = new QLabel("Line width "); m_cartesianLineWidthDoubleSpinBox = WuQFactory::newDoubleSpinBoxWithMinMaxStepDecimalsSignalDouble(0.0, 10000.0, 0.1, 1, this, SLOT(cartesianLineWidthValueChanged(double))); m_cartesianLineWidthDoubleSpinBox->setFixedWidth(65); QGridLayout* gridLayout = new QGridLayout(this); WuQtUtilities::setLayoutSpacingAndMargins(gridLayout, 0, 0); gridLayout->setColumnStretch(0, 0); gridLayout->setColumnStretch(0, 100); gridLayout->addWidget(cartesianLineWidthLabel, 0, 0); gridLayout->addWidget(m_cartesianLineWidthDoubleSpinBox, 0, 1); this->setFixedSize(this->sizeHint()); } /** * Destructor. */ CartesianChartAttributesWidget::~CartesianChartAttributesWidget() { } /** * Update the content of this widget. */ void CartesianChartAttributesWidget::updateContent() { ChartModelCartesian* chart = m_brainBrowserWindowToolBarChartAttributes->getCartesianChart(); if (chart != NULL) { m_cartesianLineWidthDoubleSpinBox->blockSignals(true); m_cartesianLineWidthDoubleSpinBox->setValue(chart->getLineWidth()); m_cartesianLineWidthDoubleSpinBox->blockSignals(false); } } /** * Called when the cartesian line width is changed. * * @param value * New value for line width. */ void CartesianChartAttributesWidget::cartesianLineWidthValueChanged(double value) { ChartModelCartesian* chart = m_brainBrowserWindowToolBarChartAttributes->getCartesianChart(); if (chart != NULL) { chart->setLineWidth(value); m_brainBrowserWindowToolBarChartAttributes->updateGraphics(); } } /* ===========================================================================*/ /** * \class caret::MatrixChartAttributesWidget * \brief Controls for matrix chart attributes. * \ingroup GuiQt */ /** * Constructor. * * @param brainBrowserWindowToolBarChartAttributes * The parent attributes widget. */ MatrixChartAttributesWidget::MatrixChartAttributesWidget(BrainBrowserWindowToolBarChartAttributes* brainBrowserWindowToolBarChartAttributes) : QWidget(brainBrowserWindowToolBarChartAttributes), EventListenerInterface() { m_brainBrowserWindowToolBarChartAttributes = brainBrowserWindowToolBarChartAttributes; QLabel* cellWidthLabel = new QLabel("Cell Width"); m_cellWidthSpinBox = WuQFactory::newDoubleSpinBoxWithMinMaxStepDecimalsSignalDouble(1.0, 1000.0, 0.1, 2, this, SLOT(cellWidthSpinBoxValueChanged(double))); m_cellWidthSpinBox->setKeyboardTracking(true); QLabel* cellHeightLabel = new QLabel("Cell Height"); m_cellHeightSpinBox = WuQFactory::newDoubleSpinBoxWithMinMaxStepDecimalsSignalDouble(1.0, 1000.0, 0.1, 2, this, SLOT(cellHeightSpinBoxValueChanged(double))); m_cellHeightSpinBox->setKeyboardTracking(true); QAction* resetButtonAction = WuQtUtilities::createAction("Reset", "Reset panning (SHIFT-mouse),zooming (CTRL-mouse), and scale matrix to fit window", this, this, SLOT(resetButtonClicked())); QToolButton* resetToolButton = new QToolButton(); resetToolButton->setDefaultAction(resetButtonAction); WuQtUtilities::matchWidgetWidths(m_cellHeightSpinBox, m_cellWidthSpinBox); m_highlightSelectionCheckBox = new QCheckBox("Highlight Selection"); QObject::connect(m_highlightSelectionCheckBox, SIGNAL(clicked(bool)), this, SLOT(highlightSelectionCheckBoxClicked(bool))); m_displayGridLinesCheckBox = new QCheckBox("Show Grid Outline"); QObject::connect(m_displayGridLinesCheckBox, SIGNAL(clicked(bool)), this, SLOT(displayGridLinesCheckBoxClicked(bool))); m_manualWidgetsGroup = new WuQWidgetObjectGroup(this); m_manualWidgetsGroup->add(m_cellWidthSpinBox); m_manualWidgetsGroup->add(m_cellHeightSpinBox); m_manualWidgetsGroup->add(m_displayGridLinesCheckBox); m_manualWidgetsGroup->add(resetToolButton); const int32_t COLUMN_LABEL = 0; const int32_t COLUMN_WIDGET = 1; QGridLayout* gridLayout = new QGridLayout(this); WuQtUtilities::setLayoutSpacingAndMargins(gridLayout, 2, 2); int32_t rowIndex = gridLayout->rowCount(); gridLayout->addWidget(cellWidthLabel, rowIndex, COLUMN_LABEL); gridLayout->addWidget(m_cellWidthSpinBox, rowIndex, COLUMN_WIDGET); rowIndex++; gridLayout->addWidget(cellHeightLabel, rowIndex, COLUMN_LABEL); gridLayout->addWidget(m_cellHeightSpinBox, rowIndex, COLUMN_WIDGET); rowIndex++; gridLayout->addWidget(resetToolButton, rowIndex, COLUMN_LABEL, 1, 2, Qt::AlignHCenter); rowIndex++; gridLayout->addWidget(m_highlightSelectionCheckBox, rowIndex, COLUMN_LABEL, 1, 2, Qt::AlignLeft); rowIndex++; gridLayout->addWidget(m_displayGridLinesCheckBox, rowIndex, COLUMN_LABEL, 1, 2, Qt::AlignLeft); rowIndex++; this->setFixedSize(this->sizeHint()); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_BROWSER_WINDOW_GRAPHICS_HAVE_BEEN_REDRAWN); } /** * Destructor. */ MatrixChartAttributesWidget::~MatrixChartAttributesWidget() { EventManager::get()->removeAllEventsFromListener(this); } /** * Receive an event. * * @param event * The event. */ void MatrixChartAttributesWidget::receiveEvent(Event* event) { if (event->getEventType() == EventTypeEnum::EVENT_BROWSER_WINDOW_GRAPHICS_HAVE_BEEN_REDRAWN) { EventBrowserWindowGraphicsRedrawn* redrawEvent = dynamic_cast(event); CaretAssert(event); /* * When the matrix view mode is auto, the OpenGL graphics update the size of * the matrix cells for use by manual mode (cell sizes are in pixels). */ ChartMatrixDisplayProperties* matrixDisplayProperties = m_brainBrowserWindowToolBarChartAttributes->getChartableMatrixDisplayProperties(); if (matrixDisplayProperties != NULL) { const BrowserTabContent* tabContent = m_brainBrowserWindowToolBarChartAttributes->getTabContentFromSelectedTab(); if (tabContent->getTabNumber() == redrawEvent->getBrowserWindowIndex()) { updateContent(); } } } } /** * Update the content of this widget. */ void MatrixChartAttributesWidget::updateContent() { ChartMatrixDisplayProperties* matrixDisplayProperties = m_brainBrowserWindowToolBarChartAttributes->getChartableMatrixDisplayProperties(); if (matrixDisplayProperties != NULL) { m_cellWidthSpinBox->blockSignals(true); m_cellWidthSpinBox->setValue(matrixDisplayProperties->getCellWidth()); m_cellWidthSpinBox->blockSignals(false); m_cellHeightSpinBox->blockSignals(true); m_cellHeightSpinBox->setValue(matrixDisplayProperties->getCellHeight()); m_cellHeightSpinBox->blockSignals(false); m_highlightSelectionCheckBox->blockSignals(true); m_highlightSelectionCheckBox->setChecked(matrixDisplayProperties->isSelectedRowColumnHighlighted()); m_highlightSelectionCheckBox->blockSignals(false); m_displayGridLinesCheckBox->blockSignals(true); m_displayGridLinesCheckBox->setChecked(matrixDisplayProperties->isGridLinesDisplayed()); m_displayGridLinesCheckBox->blockSignals(false); } } /** * Called when the cell width spin box value is changed. * * @param value * New value for cell width. */ void MatrixChartAttributesWidget::cellWidthSpinBoxValueChanged(double value) { ChartMatrixDisplayProperties* matrixDisplayProperties = m_brainBrowserWindowToolBarChartAttributes->getChartableMatrixDisplayProperties(); if (matrixDisplayProperties != NULL) { matrixDisplayProperties->setScaleMode(ChartMatrixScaleModeEnum::CHART_MATRIX_SCALE_MANUAL); matrixDisplayProperties->setCellWidth(value); m_brainBrowserWindowToolBarChartAttributes->updateGraphics(); } } /** * Called when the cell height spin box value is changed. * * @param value * New value for cell height. */ void MatrixChartAttributesWidget::cellHeightSpinBoxValueChanged(double value) { ChartMatrixDisplayProperties* matrixDisplayProperties = m_brainBrowserWindowToolBarChartAttributes->getChartableMatrixDisplayProperties(); if (matrixDisplayProperties != NULL) { matrixDisplayProperties->setScaleMode(ChartMatrixScaleModeEnum::CHART_MATRIX_SCALE_MANUAL); matrixDisplayProperties->setCellHeight(value); m_brainBrowserWindowToolBarChartAttributes->updateGraphics(); } } /** * Called when the reset button is clicked. */ void MatrixChartAttributesWidget::resetButtonClicked() { ChartMatrixDisplayProperties* matrixDisplayProperties = m_brainBrowserWindowToolBarChartAttributes->getChartableMatrixDisplayProperties(); if (matrixDisplayProperties != NULL) { matrixDisplayProperties->resetPropertiesToDefault(); updateContent(); m_brainBrowserWindowToolBarChartAttributes->updateGraphics(); } } /** * Called when the show selection check box is checked. * * @param checked * New checked status. */ void MatrixChartAttributesWidget::highlightSelectionCheckBoxClicked(bool checked) { ChartMatrixDisplayProperties* matrixDisplayProperties = m_brainBrowserWindowToolBarChartAttributes->getChartableMatrixDisplayProperties(); if (matrixDisplayProperties != NULL) { matrixDisplayProperties->setSelectedRowColumnHighlighted(checked); m_brainBrowserWindowToolBarChartAttributes->updateGraphics(); } } /** * Called when the display grid lines check box is checked. * * @param checked * New checked status. */ void MatrixChartAttributesWidget::displayGridLinesCheckBoxClicked(bool checked) { ChartMatrixDisplayProperties* matrixDisplayProperties = m_brainBrowserWindowToolBarChartAttributes->getChartableMatrixDisplayProperties(); if (matrixDisplayProperties != NULL) { matrixDisplayProperties->setGridLinesDisplayed(checked); m_brainBrowserWindowToolBarChartAttributes->updateGraphics(); } } workbench-1.1.1/src/GuiQt/BrainBrowserWindowToolBarChartAttributes.h000066400000000000000000000113561255417355300256100ustar00rootroot00000000000000#ifndef __BRAIN_BROWSER_WINDOW_TOOL_BAR_CHART_ATTRIBUTES_H__ #define __BRAIN_BROWSER_WINDOW_TOOL_BAR_CHART_ATTRIBUTES_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "BrainBrowserWindowToolBarComponent.h" #include "EventListenerInterface.h" class QCheckBox; class QDoubleSpinBox; class QStackedWidget; namespace caret { class CartesianChartAttributesWidget; class ChartMatrixDisplayProperties; class ChartModelCartesian; class EnumComboBoxTemplate; class MatrixChartAttributesWidget; class WuQWidgetObjectGroup; class BrainBrowserWindowToolBarChartAttributes : public BrainBrowserWindowToolBarComponent { Q_OBJECT public: BrainBrowserWindowToolBarChartAttributes(BrainBrowserWindowToolBar* parentToolBar); virtual ~BrainBrowserWindowToolBarChartAttributes(); virtual void updateContent(BrowserTabContent* browserTabContent); // ADD_NEW_METHODS_HERE private: BrainBrowserWindowToolBarChartAttributes(const BrainBrowserWindowToolBarChartAttributes&); BrainBrowserWindowToolBarChartAttributes& operator=(const BrainBrowserWindowToolBarChartAttributes&); ChartModelCartesian* getCartesianChart(); ChartMatrixDisplayProperties* getChartableMatrixDisplayProperties(); void updateGraphics(); CartesianChartAttributesWidget* m_cartesianChartAttributesWidget; MatrixChartAttributesWidget* m_matrixChartAttributesWidget; QStackedWidget* m_stackedWidget; // ADD_NEW_MEMBERS_HERE friend class CartesianChartAttributesWidget; friend class MatrixChartAttributesWidget; }; /* * While this should be a private class in the class above * Qt's meta-object compiler (moc) does not allow it to be. */ class CartesianChartAttributesWidget : public QWidget { Q_OBJECT public: CartesianChartAttributesWidget(BrainBrowserWindowToolBarChartAttributes* brainBrowserWindowToolBarChartAttributes); ~CartesianChartAttributesWidget(); virtual void updateContent(); private slots: void cartesianLineWidthValueChanged(double value); private: BrainBrowserWindowToolBarChartAttributes* m_brainBrowserWindowToolBarChartAttributes; QDoubleSpinBox* m_cartesianLineWidthDoubleSpinBox; }; /* * While this should be a private class in the class above * Qt's meta-object compiler (moc) does not allow it to be. */ class MatrixChartAttributesWidget : public QWidget, public EventListenerInterface { Q_OBJECT public: MatrixChartAttributesWidget(BrainBrowserWindowToolBarChartAttributes* brainBrowserWindowToolBarChartAttributes); ~MatrixChartAttributesWidget(); virtual void receiveEvent(Event* event); virtual void updateContent(); private slots: void cellWidthSpinBoxValueChanged(double value); void cellHeightSpinBoxValueChanged(double value); void highlightSelectionCheckBoxClicked(bool checked); void displayGridLinesCheckBoxClicked(bool checked); void resetButtonClicked(); private: BrainBrowserWindowToolBarChartAttributes* m_brainBrowserWindowToolBarChartAttributes; QDoubleSpinBox* m_cellWidthSpinBox; QDoubleSpinBox* m_cellHeightSpinBox; WuQWidgetObjectGroup* m_manualWidgetsGroup; QCheckBox* m_highlightSelectionCheckBox; QCheckBox* m_displayGridLinesCheckBox; }; #ifdef __BRAIN_BROWSER_WINDOW_TOOL_BAR_CHART_ATTRIBUTES_DECLARE__ // #endif // __BRAIN_BROWSER_WINDOW_TOOL_BAR_CHART_ATTRIBUTES_DECLARE__ } // namespace #endif //__BRAIN_BROWSER_WINDOW_TOOL_BAR_CHART_ATTRIBUTES_H__ workbench-1.1.1/src/GuiQt/BrainBrowserWindowToolBarChartAxes.cxx000066400000000000000000000407701255417355300247370ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __BRAIN_BROWSER_WINDOW_TOOL_BAR_CHART_AXES_DECLARE__ #include "BrainBrowserWindowToolBarChartAxes.h" #undef __BRAIN_BROWSER_WINDOW_TOOL_BAR_CHART_AXES_DECLARE__ #include #include #include #include #include "BrowserTabContent.h" #include "BrainBrowserWindowToolBar.h" #include "CaretAssert.h" #include "ChartAxis.h" #include "ChartAxisCartesian.h" #include "ChartModelDataSeries.h" #include "ChartModelFrequencySeries.h" #include "ChartModelTimeSeries.h" #include "ModelChart.h" #include "WuQWidgetObjectGroup.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::BrainBrowserWindowToolBarChartAxes * \brief Component of toolbar for chart axes. * \ingroup GuiQt */ /** * Constructor. * * @param parentToolBar * parent toolbar. */ BrainBrowserWindowToolBarChartAxes::BrainBrowserWindowToolBarChartAxes(BrainBrowserWindowToolBar* parentToolBar) : BrainBrowserWindowToolBarComponent(parentToolBar), m_parentToolBar(parentToolBar) { QGridLayout* gridLayout = new QGridLayout(this); WuQtUtilities::setLayoutSpacingAndMargins(gridLayout, 0, 0); gridLayout->addWidget(new QLabel("Axis"), 0, 0, Qt::AlignHCenter); gridLayout->addWidget(new QLabel("Auto"), 0, 1, Qt::AlignHCenter); gridLayout->addWidget(new QLabel("Min"), 0, 2, Qt::AlignHCenter); gridLayout->addWidget(new QLabel("Max"), 0, 3, Qt::AlignHCenter); createAxisWidgets(gridLayout, m_bottomAxisLabel, m_bottomAxisAutoRangeScaleCheckBox, m_bottomAxisMinimumValueSpinBox, m_bottomAxisMaximumValueSpinBox, m_bottomAxisWidgetGroup); QObject::connect(m_bottomAxisAutoRangeScaleCheckBox, SIGNAL(clicked(bool)), this, SLOT(bottomAxisAutoRangeScaleCheckBoxClicked(bool))); QObject::connect(m_bottomAxisMinimumValueSpinBox, SIGNAL(valueChanged(double)), this, SLOT(bottomAxisValueChanged(double))); QObject::connect(m_bottomAxisMaximumValueSpinBox, SIGNAL(valueChanged(double)), this, SLOT(bottomAxisValueChanged(double))); createAxisWidgets(gridLayout, m_leftAxisLabel, m_leftAxisAutoRangeScaleCheckBox, m_leftAxisMinimumValueSpinBox, m_leftAxisMaximumValueSpinBox, m_leftAxisWidgetGroup); QObject::connect(m_leftAxisAutoRangeScaleCheckBox, SIGNAL(clicked(bool)), this, SLOT(leftAxisAutoRangeScaleCheckBoxClicked(bool))); QObject::connect(m_leftAxisMinimumValueSpinBox, SIGNAL(valueChanged(double)), this, SLOT(leftAxisValueChanged(double))); QObject::connect(m_leftAxisMaximumValueSpinBox, SIGNAL(valueChanged(double)), this, SLOT(leftAxisValueChanged(double))); // createAxisWidgets(gridLayout, // m_topAxisLabel, // m_topAxisAutoRangeScaleCheckBox, // m_topAxisMinimumValueSpinBox, // m_topAxisMaximumValueSpinBox, // m_topAxisWidgetGroup); // // createAxisWidgets(gridLayout, // m_rightAxisLabel, // m_rightAxisAutoRangeScaleCheckBox, // m_rightAxisMinimumValueSpinBox, // m_rightAxisMaximumValueSpinBox, // m_rightAxisWidgetGroup); } /** * Destructor. */ BrainBrowserWindowToolBarChartAxes::~BrainBrowserWindowToolBarChartAxes() { } /** * Create the widgets for an axis. * * @param gridLayout * Layout in which axis widgets are displayed. * @param nameLabel * Label that contains name of axis. * @param autoRangeScaleCheckBox * Checkbox for enabling auto range scale. * @param minimumValueSpinBox * Spin box for minimum value. * @param maximumValueSpinBox * Spin box for maximum value. * @param widgetGroup * Widget group for the axis' widgets. */ void BrainBrowserWindowToolBarChartAxes::createAxisWidgets(QGridLayout* gridLayout, QLabel*& nameLabel, QCheckBox*& autoRangeScaleCheckBox, QDoubleSpinBox*& minimumValueSpinBox, QDoubleSpinBox*& maximumValueSpinBox, WuQWidgetObjectGroup*& widgetGroup) { const int32_t spinBoxWidth = 100; nameLabel = new QLabel(); autoRangeScaleCheckBox = new QCheckBox(" "); WuQtUtilities::setWordWrappedToolTip(autoRangeScaleCheckBox, "Axis Auto Range\n\n" "When checked, the minimum and maximum values are " "automatically adjusted to show the full range " "of values for the axis.\n\n" "When not checked, the user may adjust the minimum " "and maximum values for the axis to show a " "desired range of data for the axis.\n\n" "Changing the status from not checked to checked " "will reset the minimum and maximum values to show " "the full range of data."); minimumValueSpinBox = new QDoubleSpinBox(); minimumValueSpinBox->setFixedWidth(spinBoxWidth); minimumValueSpinBox->setMinimum(-std::numeric_limits::max()); minimumValueSpinBox->setMaximum(std::numeric_limits::max()); minimumValueSpinBox->setSingleStep(1.0); minimumValueSpinBox->setDecimals(2); WuQtUtilities::setWordWrappedToolTip(minimumValueSpinBox, "Minimum value displayed for the axis"); maximumValueSpinBox = new QDoubleSpinBox(); maximumValueSpinBox->setFixedWidth(spinBoxWidth); maximumValueSpinBox->setMinimum(-std::numeric_limits::max()); maximumValueSpinBox->setMaximum(std::numeric_limits::max()); maximumValueSpinBox->setSingleStep(1.0); maximumValueSpinBox->setDecimals(2); WuQtUtilities::setWordWrappedToolTip(maximumValueSpinBox, "Maximum value displayed for the axis"); widgetGroup = new WuQWidgetObjectGroup(this); widgetGroup->add(nameLabel); widgetGroup->add(autoRangeScaleCheckBox); widgetGroup->add(minimumValueSpinBox); widgetGroup->add(maximumValueSpinBox); const int rowIndex = gridLayout->rowCount(); gridLayout->addWidget(nameLabel, rowIndex, 0, Qt::AlignLeft); gridLayout->addWidget(autoRangeScaleCheckBox, rowIndex, 1, Qt::AlignHCenter); gridLayout->addWidget(minimumValueSpinBox, rowIndex, 2, Qt::AlignHCenter); gridLayout->addWidget(maximumValueSpinBox, rowIndex, 3, Qt::AlignHCenter); } /** * Update content of this tool bar component. * * @param browserTabContent * Content of the browser tab. */ void BrainBrowserWindowToolBarChartAxes::updateContent(BrowserTabContent* /*browserTabContent*/) { m_bottomAxisWidgetGroup->blockAllSignals(true); m_leftAxisWidgetGroup->blockAllSignals(true); // m_topAxisWidgetGroup->blockAllSignals(true); // m_rightAxisWidgetGroup->blockAllSignals(true); ChartModelCartesian* cartesianChart = getCartesianChart(); if (cartesianChart != NULL) { if (cartesianChart != NULL) { updateAxisWidgets(cartesianChart->getLeftAxis(), m_leftAxisLabel, m_leftAxisAutoRangeScaleCheckBox, m_leftAxisMinimumValueSpinBox, m_leftAxisMaximumValueSpinBox, m_leftAxisWidgetGroup); updateAxisWidgets(cartesianChart->getBottomAxis(), m_bottomAxisLabel, m_bottomAxisAutoRangeScaleCheckBox, m_bottomAxisMinimumValueSpinBox, m_bottomAxisMaximumValueSpinBox, m_bottomAxisWidgetGroup); // updateAxisWidgets(lineSeriesChart->getRightAxis(), // m_rightAxisLabel, // m_rightAxisAutoRangeScaleCheckBox, // m_rightAxisMinimumValueSpinBox, // m_rightAxisMaximumValueSpinBox, // m_rightAxisWidgetGroup); // // updateAxisWidgets(lineSeriesChart->getTopAxis(), // m_topAxisLabel, // m_topAxisAutoRangeScaleCheckBox, // m_topAxisMinimumValueSpinBox, // m_topAxisMaximumValueSpinBox, // m_topAxisWidgetGroup); } } m_bottomAxisWidgetGroup->blockAllSignals(false); m_leftAxisWidgetGroup->blockAllSignals(false); // m_topAxisWidgetGroup->blockAllSignals(false); // m_rightAxisWidgetGroup->blockAllSignals(false); } /** * @return Cartesian chart in this widget. Returned value will * be NULL if the chart (or no chart) is not a Cartesian Chart. */ ChartModelCartesian* BrainBrowserWindowToolBarChartAxes::getCartesianChart() { ChartModelCartesian* cartesianChart = NULL; BrowserTabContent* browserTabContent = getTabContentFromSelectedTab(); if (browserTabContent != NULL) { ModelChart* modelChart = browserTabContent->getDisplayedChartModel(); if (modelChart != NULL) { const int32_t tabIndex = browserTabContent->getTabNumber(); const ChartDataTypeEnum::Enum chartType = modelChart->getSelectedChartDataType(tabIndex); switch (chartType) { case ChartDataTypeEnum::CHART_DATA_TYPE_INVALID: break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_LAYER: break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_SERIES: break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_DATA_SERIES: cartesianChart = modelChart->getSelectedDataSeriesChartModel(tabIndex); //dynamic_cast(chart); break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_FREQUENCY_SERIES: cartesianChart = modelChart->getSelectedFrequencySeriesChartModel(tabIndex); break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_TIME_SERIES: cartesianChart = modelChart->getSelectedTimeSeriesChartModel(tabIndex); //dynamic_cast(chart); break; } } } return cartesianChart; } /** * Update the widgets for an axis. * * @param chartAxis * Source axis containing data. * @param nameLabel * Label that contains name of axis. * @param autoRangeScaleCheckBox * Checkbox for enabling auto range scale. * @param minimumValueSpinBox * Spin box for minimum value. * @param maximumValueSpinBox * Spin box for maximum value. * @param widgetGroup * Widget group for the axis' widgets. */ void BrainBrowserWindowToolBarChartAxes::updateAxisWidgets(const ChartAxis* chartAxis, QLabel* nameLabel, QCheckBox* autoRangeScaleCheckBox, QDoubleSpinBox* minimumValueSpinBox, QDoubleSpinBox* maximumValueSpinBox, WuQWidgetObjectGroup* widgetGroup) { if (chartAxis == NULL) { return; } const ChartAxisCartesian* chartAxisCartesian = dynamic_cast(chartAxis); CaretAssert(chartAxisCartesian); const bool visible = chartAxis->isVisible(); if (visible) { nameLabel->setText(chartAxis->getText()); autoRangeScaleCheckBox->setChecked(chartAxis->isAutoRangeScaleEnabled()); const float minValue = chartAxisCartesian->getMinimumValue(); const float maxValue = chartAxisCartesian->getMaximumValue(); minimumValueSpinBox->setValue(minValue); maximumValueSpinBox->setValue(maxValue); } widgetGroup->setVisible(visible); } /** * Called when left axis auto range scale checkbox is clicked. * * @param checked * New check status. */ void BrainBrowserWindowToolBarChartAxes::leftAxisAutoRangeScaleCheckBoxClicked(bool checked) { ChartModelCartesian* cartesianChart = getCartesianChart(); if (cartesianChart != NULL) { cartesianChart->getLeftAxis()->setAutoRangeScaleEnabled(checked); if (checked) { updateContent(getTabContentFromSelectedTab()); invalidateColoringAndUpdateGraphicsWindow(); } } } /** * Called when left axis minimum or maximum value is changed. * * @param value * New value. */ void BrainBrowserWindowToolBarChartAxes::leftAxisValueChanged(double /*value*/) { ChartModelCartesian* cartesianChart = getCartesianChart(); if (cartesianChart != NULL) { m_leftAxisAutoRangeScaleCheckBox->setChecked(false); ChartAxisCartesian* axis = dynamic_cast(cartesianChart->getLeftAxis()); CaretAssert(axis); axis->setAutoRangeScaleEnabled(false); axis->setMinimumValue(m_leftAxisMinimumValueSpinBox->value()); axis->setMaximumValue(m_leftAxisMaximumValueSpinBox->value()); invalidateColoringAndUpdateGraphicsWindow(); } } /** * Called when bottom axis auto range scale checkbox is clicked. * * @param checked * New check status. */ void BrainBrowserWindowToolBarChartAxes::bottomAxisAutoRangeScaleCheckBoxClicked(bool checked) { ChartModelCartesian* cartesianChart = getCartesianChart(); if (cartesianChart != NULL) { cartesianChart->getBottomAxis()->setAutoRangeScaleEnabled(checked); if (checked) { updateContent(getTabContentFromSelectedTab()); invalidateColoringAndUpdateGraphicsWindow(); } } } /** * Called when bottom axis minimum or maximum value is changed. * * @param value * New value. */ void BrainBrowserWindowToolBarChartAxes::bottomAxisValueChanged(double /*value*/) { ChartModelCartesian* cartesianChart = getCartesianChart(); if (cartesianChart != NULL) { m_bottomAxisAutoRangeScaleCheckBox->setChecked(false); ChartAxisCartesian* axis = dynamic_cast(cartesianChart->getBottomAxis()); CaretAssert(axis); axis->setAutoRangeScaleEnabled(false); axis->setMinimumValue(m_bottomAxisMinimumValueSpinBox->value()); axis->setMaximumValue(m_bottomAxisMaximumValueSpinBox->value()); invalidateColoringAndUpdateGraphicsWindow(); } } workbench-1.1.1/src/GuiQt/BrainBrowserWindowToolBarChartAxes.h000066400000000000000000000107301255417355300243550ustar00rootroot00000000000000#ifndef __BRAIN_BROWSER_WINDOW_TOOL_BAR_CHART_AXES_H__ #define __BRAIN_BROWSER_WINDOW_TOOL_BAR_CHART_AXES_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ class QCheckBox; class QDoubleSpinBox; class QGridLayout; class QLabel; #include "BrainBrowserWindowToolBarComponent.h" namespace caret { class ChartAxis; class ChartModelCartesian; class BrainBrowserWindowToolBar; class WuQWidgetObjectGroup; class BrainBrowserWindowToolBarChartAxes : public BrainBrowserWindowToolBarComponent { Q_OBJECT public: BrainBrowserWindowToolBarChartAxes(BrainBrowserWindowToolBar* parentToolBar); virtual ~BrainBrowserWindowToolBarChartAxes(); virtual void updateContent(BrowserTabContent* browserTabContent); private slots: void leftAxisAutoRangeScaleCheckBoxClicked(bool checked); void leftAxisValueChanged(double value); void bottomAxisAutoRangeScaleCheckBoxClicked(bool checked); void bottomAxisValueChanged(double value); private: BrainBrowserWindowToolBarChartAxes(const BrainBrowserWindowToolBarChartAxes&); BrainBrowserWindowToolBarChartAxes& operator=(const BrainBrowserWindowToolBarChartAxes&); void createAxisWidgets(QGridLayout* gridLayout, QLabel*& nameLabel, QCheckBox*& autoRangeScaleCheckBox, QDoubleSpinBox*& minimumValueSpinBox, QDoubleSpinBox*& maximumValueSpinBox, WuQWidgetObjectGroup*& widgetGroup); void updateAxisWidgets(const ChartAxis* chartAxis, QLabel* nameLabel, QCheckBox* autoRangeScaleCheckBox, QDoubleSpinBox* minimumValueSpinBox, QDoubleSpinBox* maximumValueSpinBox, WuQWidgetObjectGroup* widgetGroup); ChartModelCartesian* getCartesianChart(); public: // ADD_NEW_METHODS_HERE private: BrainBrowserWindowToolBar* m_parentToolBar; QLabel* m_bottomAxisLabel; QCheckBox* m_bottomAxisAutoRangeScaleCheckBox; QDoubleSpinBox* m_bottomAxisMinimumValueSpinBox; QDoubleSpinBox* m_bottomAxisMaximumValueSpinBox; WuQWidgetObjectGroup* m_bottomAxisWidgetGroup; QLabel* m_leftAxisLabel; QCheckBox* m_leftAxisAutoRangeScaleCheckBox; QDoubleSpinBox* m_leftAxisMinimumValueSpinBox; QDoubleSpinBox* m_leftAxisMaximumValueSpinBox; WuQWidgetObjectGroup* m_leftAxisWidgetGroup; // QLabel* m_topAxisLabel; // // QCheckBox* m_topAxisAutoRangeScaleCheckBox; // // QDoubleSpinBox* m_topAxisMinimumValueSpinBox; // // QDoubleSpinBox* m_topAxisMaximumValueSpinBox; // // WuQWidgetObjectGroup* m_topAxisWidgetGroup; // // QLabel* m_rightAxisLabel; // // QCheckBox* m_rightAxisAutoRangeScaleCheckBox; // // QDoubleSpinBox* m_rightAxisMinimumValueSpinBox; // // QDoubleSpinBox* m_rightAxisMaximumValueSpinBox; // // WuQWidgetObjectGroup* m_rightAxisWidgetGroup; // ADD_NEW_MEMBERS_HERE }; #ifdef __BRAIN_BROWSER_WINDOW_TOOL_BAR_CHART_AXES_DECLARE__ // #endif // __BRAIN_BROWSER_WINDOW_TOOL_BAR_CHART_AXES_DECLARE__ } // namespace #endif //__BRAIN_BROWSER_WINDOW_TOOL_BAR_CHART_AXES_H__ workbench-1.1.1/src/GuiQt/BrainBrowserWindowToolBarChartType.cxx000066400000000000000000000232001255417355300247450ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __BRAIN_BROWSER_WINDOW_TOOL_BAR_CHART_DECLARE__ #include "BrainBrowserWindowToolBarChartType.h" #undef __BRAIN_BROWSER_WINDOW_TOOL_BAR_CHART_DECLARE__ #include #include #include #include "BrowserTabContent.h" #include "BrainBrowserWindowToolBar.h" #include "CaretAssert.h" #include "ChartModel.h" #include "EnumComboBoxTemplate.h" #include "ModelChart.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::BrainBrowserWindowToolBarChartType * \brief Chart Component of Brain Browser Window ToolBar * \ingroup GuiQt */ /** * Constructor. * * @param parentToolBar * parent toolbar. */ BrainBrowserWindowToolBarChartType::BrainBrowserWindowToolBarChartType(BrainBrowserWindowToolBar* parentToolBar) : BrainBrowserWindowToolBarComponent(parentToolBar), m_parentToolBar(parentToolBar) { m_chartTypeButtonGroup = new QButtonGroup(this); QVBoxLayout* radioButtonLayout = new QVBoxLayout(this); std::vector allChartTypes; ChartDataTypeEnum::getAllEnums(allChartTypes); for (std::vector::iterator chartIter = allChartTypes.begin(); chartIter != allChartTypes.end(); chartIter++) { const ChartDataTypeEnum::Enum ct = *chartIter; if (ct == ChartDataTypeEnum::CHART_DATA_TYPE_INVALID) { continue; } QRadioButton* rb = new QRadioButton(ChartDataTypeEnum::toGuiName(ct)); m_chartTypeButtonGroup->addButton(rb, m_chartTypeRadioButtons.size()); radioButtonLayout->addWidget(rb); m_chartTypeRadioButtons.push_back(std::make_pair(ct, rb)); } WuQtUtilities::setLayoutSpacingAndMargins(radioButtonLayout, 4, 5); radioButtonLayout->addStretch(); // m_chartMatrixLayerTypeRadioButton = new QRadioButton(ChartDataTypeEnum::toGuiName(ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_LAYER)); // // m_chartMatrixSeriesTypeRadioButton = new QRadioButton(ChartDataTypeEnum::toGuiName(ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_SERIES)); // // m_chartDataSeriesTypeRadioButton = new QRadioButton(ChartDataTypeEnum::toGuiName(ChartDataTypeEnum::CHART_DATA_TYPE_LINE_DATA_SERIES)); // // m_chartTimeSeriesTypeRadioButton = new QRadioButton(ChartDataTypeEnum::toGuiName(ChartDataTypeEnum::CHART_DATA_TYPE_LINE_TIME_SERIES)); QObject::connect(m_chartTypeButtonGroup, SIGNAL(buttonClicked(int)), this, SLOT(chartTypeRadioButtonClicked(int))); // m_chartTypeButtonGroup->addButton(m_chartMatrixLayerTypeRadioButton); // m_chartTypeButtonGroup->addButton(m_chartMatrixSeriesTypeRadioButton); // m_chartTypeButtonGroup->addButton(m_chartDataSeriesTypeRadioButton); // m_chartTypeButtonGroup->addButton(m_chartTimeSeriesTypeRadioButton); // WuQtUtilities::setLayoutSpacingAndMargins(layout, 4, 5); // layout->addWidget(m_chartDataSeriesTypeRadioButton); // layout->addWidget(m_chartMatrixLayerTypeRadioButton); // layout->addWidget(m_chartMatrixSeriesTypeRadioButton); // layout->addWidget(m_chartTimeSeriesTypeRadioButton); // layout->addStretch(); } /** * Destructor. */ BrainBrowserWindowToolBarChartType::~BrainBrowserWindowToolBarChartType() { } /** * Called when a radio button is clicked. * * @param buttonIndex * Index of button that is clicked. */ void BrainBrowserWindowToolBarChartType::chartTypeRadioButtonClicked(int buttonIndex) { CaretAssertVectorIndex(m_chartTypeRadioButtons, buttonIndex); ChartDataTypeEnum::Enum chartDataType = m_chartTypeRadioButtons[buttonIndex].first; // ChartDataTypeEnum::Enum chartDataType = ChartDataTypeEnum::CHART_DATA_TYPE_INVALID; // // if (m_chartDataSeriesTypeRadioButton->isChecked()) { // chartDataType = ChartDataTypeEnum::CHART_DATA_TYPE_LINE_DATA_SERIES; // } // else if (m_chartMatrixLayerTypeRadioButton->isChecked()) { // chartDataType = ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_LAYER; // } // else if (m_chartTimeSeriesTypeRadioButton->isChecked()) { // chartDataType = ChartDataTypeEnum::CHART_DATA_TYPE_LINE_TIME_SERIES; // } // else if (m_chartMatrixSeriesTypeRadioButton->isChecked()) { // chartDataType = ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_SERIES; // } // else { // CaretAssertMessage(0, "Has a new chart radio button been added?"); // } m_parentToolBar->getTabContentFromSelectedTab(); BrowserTabContent* btc = m_parentToolBar->getTabContentFromSelectedTab(); ModelChart* chartModel = btc->getDisplayedChartModel(); const int32_t tabIndex = btc->getTabNumber(); chartModel->setSelectedChartDataType(tabIndex, chartDataType); //updateContent(btc); invalidateColoringAndUpdateGraphicsWindow(); m_parentToolBar->updateUserInterface(); } /** * Update content of this tool bar component. * * @param browserTabContent * Content of the browser tab. */ void BrainBrowserWindowToolBarChartType::updateContent(BrowserTabContent* browserTabContent) { m_chartTypeButtonGroup->blockSignals(true); const ModelChart* chartModel = browserTabContent->getDisplayedChartModel(); if (chartModel != NULL) { const int32_t tabIndex = browserTabContent->getTabNumber(); const ChartDataTypeEnum::Enum selectedChartType = chartModel->getSelectedChartDataType(tabIndex); std::vector validChartDataTypes; chartModel->getValidChartDataTypes(validChartDataTypes); for (std::vector >::iterator buttIter = m_chartTypeRadioButtons.begin(); buttIter != m_chartTypeRadioButtons.end(); buttIter++) { ChartDataTypeEnum::Enum chartType = buttIter->first; QRadioButton* radioButton = buttIter->second; const bool validTypeFlag = (std::find(validChartDataTypes.begin(), validChartDataTypes.end(), chartType) != validChartDataTypes.end()); radioButton->setEnabled(validTypeFlag); if (chartType == selectedChartType) { radioButton->setChecked(true); } } // bool dataSeriesValidFlag = false; // bool matrixLayerValidFlag = false; // bool matrixSeriesValidFlag = false; // bool timeSeriesValidFlag = false; // for (std::vector::iterator iter = validChartDataTypes.begin(); // iter != validChartDataTypes.end(); // iter++) { // switch (*iter) { // case ChartDataTypeEnum::CHART_DATA_TYPE_INVALID: // break; // case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_LAYER: // matrixLayerValidFlag = true; // break; // case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_SERIES: // matrixSeriesValidFlag = true; // break; // case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_DATA_SERIES: // dataSeriesValidFlag = true; // break; // case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_FREQUENCY_SERIES: // CaretAssertToDoFatal(); // break; // case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_TIME_SERIES: // timeSeriesValidFlag = true; // break; // } // } // // m_chartDataSeriesTypeRadioButton->setEnabled(dataSeriesValidFlag); // m_chartMatrixLayerTypeRadioButton->setEnabled(matrixLayerValidFlag); // m_chartTimeSeriesTypeRadioButton->setEnabled(timeSeriesValidFlag); // // switch (chartType) { // case ChartDataTypeEnum::CHART_DATA_TYPE_INVALID: // break; // case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_LAYER: // m_chartMatrixLayerTypeRadioButton->setChecked(true); // break; // case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_SERIES: // m_chartMatrixSeriesTypeRadioButton->setChecked(true); // break; // case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_DATA_SERIES: // m_chartDataSeriesTypeRadioButton->setChecked(true); // break; // case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_FREQUENCY_SERIES: // CaretAssertToDoFatal(); // break; // case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_TIME_SERIES: // m_chartTimeSeriesTypeRadioButton->setChecked(true); // break; // } } m_chartTypeButtonGroup->blockSignals(false); } workbench-1.1.1/src/GuiQt/BrainBrowserWindowToolBarChartType.h000066400000000000000000000052371255417355300244040ustar00rootroot00000000000000#ifndef __BRAIN_BROWSER_WINDOW_TOOL_BAR_CHART_H__ #define __BRAIN_BROWSER_WINDOW_TOOL_BAR_CHART_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ class QAbstractButton; class QButtonGroup; class QRadioButton; #include "BrainBrowserWindowToolBarComponent.h" #include "ChartDataTypeEnum.h" namespace caret { class EnumComboBoxTemplate; class BrainBrowserWindowToolBarChartType : public BrainBrowserWindowToolBarComponent { Q_OBJECT public: BrainBrowserWindowToolBarChartType(BrainBrowserWindowToolBar* parentToolBar); virtual ~BrainBrowserWindowToolBarChartType(); virtual void updateContent(BrowserTabContent* browserTabContent); private slots: void chartTypeRadioButtonClicked(int buttonIndex); private: BrainBrowserWindowToolBarChartType(const BrainBrowserWindowToolBarChartType&); BrainBrowserWindowToolBarChartType& operator=(const BrainBrowserWindowToolBarChartType&); public: // ADD_NEW_METHODS_HERE private: // ADD_NEW_MEMBERS_HERE BrainBrowserWindowToolBar* m_parentToolBar; std::vector > m_chartTypeRadioButtons; QButtonGroup* m_chartTypeButtonGroup; // QRadioButton* m_chartMatrixLayerTypeRadioButton; // // QRadioButton* m_chartMatrixSeriesTypeRadioButton; // // QRadioButton* m_chartDataSeriesTypeRadioButton; // // QRadioButton* m_chartFrequencySeriesTypeRadioButton; // // QRadioButton* m_chartTimeSeriesTypeRadioButton; }; #ifdef __BRAIN_BROWSER_WINDOW_TOOL_BAR_CHART_DECLARE__ // #endif // __BRAIN_BROWSER_WINDOW_TOOL_BAR_CHART_DECLARE__ } // namespace #endif //__BRAIN_BROWSER_WINDOW_TOOL_BAR_CHART_H__ workbench-1.1.1/src/GuiQt/BrainBrowserWindowToolBarClipping.cxx000066400000000000000000000152551255417355300246220ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #define __BRAIN_BROWSER_WINDOW_TOOL_BAR_CLIPPING_DECLARE__ #include "BrainBrowserWindowToolBarClipping.h" #undef __BRAIN_BROWSER_WINDOW_TOOL_BAR_CLIPPING_DECLARE__ #include "BrowserTabContent.h" #include "CaretAssert.h" #include "GuiManager.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::BrainBrowserWindowToolBarClipping * \brief Clipping group for toolbar. * \ingroup GuiQt */ /** * Constructor. */ BrainBrowserWindowToolBarClipping::BrainBrowserWindowToolBarClipping(const int32_t browserWindowIndex, BrainBrowserWindowToolBar* parentToolBar) : BrainBrowserWindowToolBarComponent(parentToolBar), m_browserWindowIndex(browserWindowIndex), m_parentToolBar(parentToolBar) { m_xClippingEnabledCheckBox = new QCheckBox("X"); QObject::connect(m_xClippingEnabledCheckBox, SIGNAL(clicked(bool)), this, SLOT(clippingCheckBoxCheckStatusChanged())); m_yClippingEnabledCheckBox = new QCheckBox("Y"); QObject::connect(m_yClippingEnabledCheckBox, SIGNAL(clicked(bool)), this, SLOT(clippingCheckBoxCheckStatusChanged())); m_zClippingEnabledCheckBox = new QCheckBox("Z"); QObject::connect(m_zClippingEnabledCheckBox, SIGNAL(clicked(bool)), this, SLOT(clippingCheckBoxCheckStatusChanged())); m_surfaceClippingEnabledCheckBox = new QCheckBox("Surface"); QObject::connect(m_surfaceClippingEnabledCheckBox, SIGNAL(clicked(bool)), this, SLOT(clippingCheckBoxCheckStatusChanged())); m_volumeClippingEnabledCheckBox = new QCheckBox("Volume"); QObject::connect(m_volumeClippingEnabledCheckBox, SIGNAL(clicked(bool)), this, SLOT(clippingCheckBoxCheckStatusChanged())); m_featuresClippingEnabledCheckBox = new QCheckBox("Features"); QObject::connect(m_featuresClippingEnabledCheckBox, SIGNAL(clicked(bool)), this, SLOT(clippingCheckBoxCheckStatusChanged())); QToolButton* setupToolButton = new QToolButton(); setupToolButton->setText("Setup"); QObject::connect(setupToolButton, SIGNAL(clicked()), this, SLOT(setupClippingPushButtonClicked())); /* * Layout: * Column 1: Clipping X, Y, Z * Column 2: Nothing but used as space * Column 3: Type of data clipped. */ QGridLayout* checkboxGridLayout = new QGridLayout(); WuQtUtilities::setLayoutSpacingAndMargins(checkboxGridLayout, 4, 0); checkboxGridLayout->setColumnMinimumWidth(1, 15); checkboxGridLayout->setColumnStretch(0, 0); checkboxGridLayout->setColumnStretch(0, 1); checkboxGridLayout->setColumnStretch(0, 2); checkboxGridLayout->addWidget(m_xClippingEnabledCheckBox, 0, 0); checkboxGridLayout->addWidget(m_yClippingEnabledCheckBox, 1, 0); checkboxGridLayout->addWidget(m_zClippingEnabledCheckBox, 2, 0); checkboxGridLayout->addWidget(m_surfaceClippingEnabledCheckBox, 0, 2); checkboxGridLayout->addWidget(m_volumeClippingEnabledCheckBox, 1, 2); checkboxGridLayout->addWidget(m_featuresClippingEnabledCheckBox, 2, 2); QVBoxLayout* layout = new QVBoxLayout(this); WuQtUtilities::setLayoutSpacingAndMargins(layout, 0, 0); layout->addLayout(checkboxGridLayout); layout->addWidget(setupToolButton, 0, Qt::AlignHCenter); layout->addStretch(); } /** * Destructor. */ BrainBrowserWindowToolBarClipping::~BrainBrowserWindowToolBarClipping() { } /** * Update the surface montage options widget. * * @param browserTabContent * The active model display controller (may be NULL). */ void BrainBrowserWindowToolBarClipping::updateContent(BrowserTabContent* browserTabContent) { bool xEnabled; bool yEnabled; bool zEnabled; bool surfaceEnabled; bool volumeEnabled; bool featuresEnabled; browserTabContent->getClippingPlaneEnabled(xEnabled, yEnabled, zEnabled, surfaceEnabled, volumeEnabled, featuresEnabled); m_xClippingEnabledCheckBox->setChecked(xEnabled); m_yClippingEnabledCheckBox->setChecked(yEnabled); m_zClippingEnabledCheckBox->setChecked(zEnabled); m_surfaceClippingEnabledCheckBox->setChecked(surfaceEnabled); m_volumeClippingEnabledCheckBox->setChecked(volumeEnabled); m_featuresClippingEnabledCheckBox->setChecked(featuresEnabled); } /** * Called when a clipping checkbox status is changed. */ void BrainBrowserWindowToolBarClipping::clippingCheckBoxCheckStatusChanged() { BrowserTabContent* browserTabContent = getTabContentFromSelectedTab(); if (browserTabContent != NULL) { browserTabContent->setClippingPlaneEnabled(m_xClippingEnabledCheckBox->isChecked(), m_yClippingEnabledCheckBox->isChecked(), m_zClippingEnabledCheckBox->isChecked(), m_surfaceClippingEnabledCheckBox->isChecked(), m_volumeClippingEnabledCheckBox->isChecked(), m_featuresClippingEnabledCheckBox->isChecked()); this->updateGraphicsWindow(); this->updateOtherYokedWindows(); } } /** * Called when the setup clipping push button is clicked. */ void BrainBrowserWindowToolBarClipping::setupClippingPushButtonClicked() { BrainBrowserWindow* browserWindow = GuiManager::get()->getBrowserWindowByWindowIndex(m_browserWindowIndex); GuiManager::get()->processShowClippingPlanesDialog(browserWindow); } workbench-1.1.1/src/GuiQt/BrainBrowserWindowToolBarClipping.h000066400000000000000000000050661255417355300242460ustar00rootroot00000000000000#ifndef __BRAIN_BROWSER_WINDOW_TOOL_BAR_CLIPPING_H__ #define __BRAIN_BROWSER_WINDOW_TOOL_BAR_CLIPPING_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "BrainBrowserWindowToolBarComponent.h" namespace caret { class BrainBrowserWindowToolBarClipping : public BrainBrowserWindowToolBarComponent { Q_OBJECT public: BrainBrowserWindowToolBarClipping(const int32_t browserWindowIndex, BrainBrowserWindowToolBar* parentToolBar); virtual ~BrainBrowserWindowToolBarClipping(); virtual void updateContent(BrowserTabContent* browserTabContent); // ADD_NEW_METHODS_HERE private slots: void clippingCheckBoxCheckStatusChanged(); void setupClippingPushButtonClicked(); private: BrainBrowserWindowToolBarClipping(const BrainBrowserWindowToolBarClipping&); BrainBrowserWindowToolBarClipping& operator=(const BrainBrowserWindowToolBarClipping&); const int32_t m_browserWindowIndex; BrainBrowserWindowToolBar* m_parentToolBar; QCheckBox* m_xClippingEnabledCheckBox; QCheckBox* m_yClippingEnabledCheckBox; QCheckBox* m_zClippingEnabledCheckBox; QCheckBox* m_surfaceClippingEnabledCheckBox; QCheckBox* m_volumeClippingEnabledCheckBox; QCheckBox* m_featuresClippingEnabledCheckBox; // ADD_NEW_MEMBERS_HERE }; #ifdef __BRAIN_BROWSER_WINDOW_TOOL_BAR_CLIPPING_DECLARE__ // #endif // __BRAIN_BROWSER_WINDOW_TOOL_BAR_CLIPPING_DECLARE__ } // namespace #endif //__BRAIN_BROWSER_WINDOW_TOOL_BAR_CLIPPING_H__ workbench-1.1.1/src/GuiQt/BrainBrowserWindowToolBarComponent.cxx000066400000000000000000000074621255417355300250200ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __BRAIN_BROWSER_WINDOW_TOOL_BAR_COMPONENT_DECLARE__ #include "BrainBrowserWindowToolBarComponent.h" #undef __BRAIN_BROWSER_WINDOW_TOOL_BAR_COMPONENT_DECLARE__ #include "BrainBrowserWindowToolBar.h" #include "CaretAssert.h" #include "EventManager.h" #include "EventSurfaceColoringInvalidate.h" #include "WuQWidgetObjectGroup.h" using namespace caret; /** * \class caret::BrainBrowserWindowToolBarComponent * \brief Abstract class for brain browser window tool bar components. * \ingroup GuiQt * */ /** * Constructor. * * @param parent * Parent widget. */ BrainBrowserWindowToolBarComponent::BrainBrowserWindowToolBarComponent(BrainBrowserWindowToolBar* parentToolBar) : QWidget(parentToolBar), m_parentToolBar(parentToolBar) { m_widgetGroup = new WuQWidgetObjectGroup(this); // EventManager::get()->addEventListener(this, EventTypeEnum::); } /** * Destructor. */ BrainBrowserWindowToolBarComponent::~BrainBrowserWindowToolBarComponent() { m_widgetGroup->clear(); EventManager::get()->removeAllEventsFromListener(this); } /** * Receive an event. * * @param event * An event for which this instance is listening. */ void BrainBrowserWindowToolBarComponent::receiveEvent(Event* /*event*/) { // if (event->getEventType() == EventTypeEnum::) { // eventName = dynamic_cast(event); // CaretAssert(eventName); // // event->setEventProcessed(); // } } /** * Add a widget/object to the widget group so that all items can be * enabled or disabled. * * @param qObject * QObject or QWidget subclass added to widget group. */ void BrainBrowserWindowToolBarComponent::addToWidgetGroup(QObject* qObject) { CaretAssert(qObject); m_widgetGroup->add(qObject); } /** * Block/unblock signals for components in the widget group. * * @param blocked * New status for blocked (true/false). */ void BrainBrowserWindowToolBarComponent::blockAllSignals(const bool blocked) { m_widgetGroup->blockAllSignals(blocked); } /** * @return The browser tab content for the selected tab. */ BrowserTabContent* BrainBrowserWindowToolBarComponent::getTabContentFromSelectedTab() { return m_parentToolBar->getTabContentFromSelectedTab(); } /** * Invalidate surface coloring and update the graphics in the * window containing this toolbar. */ void BrainBrowserWindowToolBarComponent::invalidateColoringAndUpdateGraphicsWindow() { EventManager::get()->sendEvent(EventSurfaceColoringInvalidate().getPointer()); updateGraphicsWindow(); } /** * Update the graphics window. */ void BrainBrowserWindowToolBarComponent::updateGraphicsWindow() { m_parentToolBar->updateGraphicsWindow(); } /** * Update toolbars in other yoked windows. */ void BrainBrowserWindowToolBarComponent::updateOtherYokedWindows() { m_parentToolBar->updateOtherYokedWindows(); } /** * Update the user interface. */ void BrainBrowserWindowToolBarComponent::updateUserInterface() { m_parentToolBar->updateUserInterface(); } workbench-1.1.1/src/GuiQt/BrainBrowserWindowToolBarComponent.h000066400000000000000000000051321255417355300244350ustar00rootroot00000000000000#ifndef __BRAIN_BROWSER_WINDOW_TOOL_BAR_COMPONENT_H__ #define __BRAIN_BROWSER_WINDOW_TOOL_BAR_COMPONENT_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "EventListenerInterface.h" namespace caret { class BrainBrowserWindowToolBar; class BrowserTabContent; class WuQWidgetObjectGroup; class BrainBrowserWindowToolBarComponent : public QWidget, public EventListenerInterface { Q_OBJECT protected: BrainBrowserWindowToolBarComponent(BrainBrowserWindowToolBar* parentToolBar); public: virtual ~BrainBrowserWindowToolBarComponent(); virtual void updateContent(BrowserTabContent* browserTabContent) = 0; BrowserTabContent* getTabContentFromSelectedTab(); void invalidateColoringAndUpdateGraphicsWindow(); void updateGraphicsWindow(); void updateOtherYokedWindows(); void updateUserInterface(); private: BrainBrowserWindowToolBarComponent(const BrainBrowserWindowToolBarComponent&); BrainBrowserWindowToolBarComponent& operator=(const BrainBrowserWindowToolBarComponent&); protected: void addToWidgetGroup(QObject* qObject); void blockAllSignals(const bool status); public: // ADD_NEW_METHODS_HERE virtual void receiveEvent(Event* event); private: BrainBrowserWindowToolBar* m_parentToolBar; WuQWidgetObjectGroup* m_widgetGroup; // ADD_NEW_MEMBERS_HERE }; #ifdef __BRAIN_BROWSER_WINDOW_TOOL_BAR_COMPONENT_DECLARE__ // #endif // __BRAIN_BROWSER_WINDOW_TOOL_BAR_COMPONENT_DECLARE__ } // namespace #endif //__BRAIN_BROWSER_WINDOW_TOOL_BAR_COMPONENT_H__ workbench-1.1.1/src/GuiQt/BrainBrowserWindowToolBarSlicePlane.cxx000066400000000000000000000233101255417355300250630ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __BRAIN_BROWSER_WINDOW_TOOL_BAR_SLICE_PLANE_DECLARE__ #include "BrainBrowserWindowToolBarSlicePlane.h" #undef __BRAIN_BROWSER_WINDOW_TOOL_BAR_SLICE_PLANE_DECLARE__ #include #include #include #include #include "BrainBrowserWindowToolBar.h" #include "BrowserTabContent.h" #include "CaretAssert.h" #include "CaretLogger.h" #include "WuQWidgetObjectGroup.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::BrainBrowserWindowToolBarSlicePlane * \brief Toolbar component for volume slice plane and projection selection. * \ingroup GuiQt */ /** * Constructor. */ BrainBrowserWindowToolBarSlicePlane::BrainBrowserWindowToolBarSlicePlane(BrainBrowserWindowToolBar* parentToolBar) : BrainBrowserWindowToolBarComponent(parentToolBar), m_parentToolBar(parentToolBar) { QIcon parasagittalIcon; const bool parasagittalIconValid = WuQtUtilities::loadIcon(":/ToolBar/view-plane-parasagittal.png", parasagittalIcon); QIcon coronalIcon; const bool coronalIconValid = WuQtUtilities::loadIcon(":/ToolBar/view-plane-coronal.png", coronalIcon); QIcon axialIcon; const bool axialIconValid = WuQtUtilities::loadIcon(":/ToolBar/view-plane-axial.png", axialIcon); m_volumePlaneParasagittalToolButtonAction = WuQtUtilities::createAction(VolumeSliceViewPlaneEnum::toGuiNameAbbreviation(VolumeSliceViewPlaneEnum::PARASAGITTAL), "View the PARASAGITTAL slice", this); m_volumePlaneParasagittalToolButtonAction->setCheckable(true); if (parasagittalIconValid) { m_volumePlaneParasagittalToolButtonAction->setIcon(parasagittalIcon); } m_volumePlaneCoronalToolButtonAction = WuQtUtilities::createAction(VolumeSliceViewPlaneEnum::toGuiNameAbbreviation(VolumeSliceViewPlaneEnum::CORONAL), "View the CORONAL slice", this); m_volumePlaneCoronalToolButtonAction->setCheckable(true); if (coronalIconValid) { m_volumePlaneCoronalToolButtonAction->setIcon(coronalIcon); } m_volumePlaneAxialToolButtonAction = WuQtUtilities::createAction(VolumeSliceViewPlaneEnum::toGuiNameAbbreviation(VolumeSliceViewPlaneEnum::AXIAL), "View the AXIAL slice", this); m_volumePlaneAxialToolButtonAction->setCheckable(true); if (axialIconValid) { m_volumePlaneAxialToolButtonAction->setIcon(axialIcon); } m_volumePlaneAllToolButtonAction = WuQtUtilities::createAction(VolumeSliceViewPlaneEnum::toGuiNameAbbreviation(VolumeSliceViewPlaneEnum::ALL), "View the PARASAGITTAL, CORONAL, and AXIAL slices", this); m_volumePlaneAllToolButtonAction->setCheckable(true); m_volumePlaneActionGroup = new QActionGroup(this); m_volumePlaneActionGroup->addAction(m_volumePlaneParasagittalToolButtonAction); m_volumePlaneActionGroup->addAction(m_volumePlaneCoronalToolButtonAction); m_volumePlaneActionGroup->addAction(m_volumePlaneAxialToolButtonAction); m_volumePlaneActionGroup->addAction(m_volumePlaneAllToolButtonAction); m_volumePlaneActionGroup->setExclusive(true); QObject::connect(m_volumePlaneActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(volumePlaneActionGroupTriggered(QAction*))); m_volumePlaneResetToolButtonAction = WuQtUtilities::createAction("Reset", "Reset to remove panning, zooming, and/or oblique rotation", this, this, SLOT(volumePlaneResetToolButtonTriggered(bool))); QToolButton* volumePlaneParasagittalToolButton = new QToolButton(); volumePlaneParasagittalToolButton->setDefaultAction(m_volumePlaneParasagittalToolButtonAction); QToolButton* volumePlaneCoronalToolButton = new QToolButton(); volumePlaneCoronalToolButton->setDefaultAction(m_volumePlaneCoronalToolButtonAction); QToolButton* volumePlaneAxialToolButton = new QToolButton(); volumePlaneAxialToolButton->setDefaultAction(m_volumePlaneAxialToolButtonAction); QToolButton* volumePlaneAllToolButton = new QToolButton(); volumePlaneAllToolButton->setDefaultAction(m_volumePlaneAllToolButtonAction); QToolButton* volumePlaneResetToolButton = new QToolButton(); volumePlaneResetToolButton->setDefaultAction(m_volumePlaneResetToolButtonAction); WuQtUtilities::matchWidgetHeights(volumePlaneParasagittalToolButton, volumePlaneCoronalToolButton, volumePlaneAxialToolButton, volumePlaneAllToolButton); QToolButton* slicePlaneCustomToolButton = new QToolButton(); slicePlaneCustomToolButton->setDefaultAction(m_parentToolBar->customViewAction); slicePlaneCustomToolButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); QHBoxLayout* planeLayout1 = new QHBoxLayout(); WuQtUtilities::setLayoutSpacingAndMargins(planeLayout1, 0, 0); planeLayout1->addStretch(); planeLayout1->addWidget(volumePlaneParasagittalToolButton); planeLayout1->addWidget(volumePlaneCoronalToolButton); planeLayout1->addWidget(volumePlaneAxialToolButton); planeLayout1->addWidget(volumePlaneAllToolButton); planeLayout1->addStretch(); QVBoxLayout* layout = new QVBoxLayout(this); WuQtUtilities::setLayoutSpacingAndMargins(layout, 0, 0); layout->addLayout(planeLayout1); layout->addWidget(volumePlaneResetToolButton, 0, Qt::AlignHCenter); layout->addWidget(slicePlaneCustomToolButton, 0, Qt::AlignHCenter); m_volumePlaneWidgetGroup = new WuQWidgetObjectGroup(this); m_volumePlaneWidgetGroup->add(m_volumePlaneActionGroup); m_volumePlaneWidgetGroup->add(m_volumePlaneResetToolButtonAction); } /** * Destructor. */ BrainBrowserWindowToolBarSlicePlane::~BrainBrowserWindowToolBarSlicePlane() { } /** * Update the surface montage options widget. * * @param browserTabContent * The active model display controller (may be NULL). */ void BrainBrowserWindowToolBarSlicePlane::updateContent(BrowserTabContent* browserTabContent) { m_volumePlaneWidgetGroup->blockAllSignals(true); switch (browserTabContent->getSliceViewPlane()) { case VolumeSliceViewPlaneEnum::ALL: m_volumePlaneAllToolButtonAction->setChecked(true); break; case VolumeSliceViewPlaneEnum::AXIAL: m_volumePlaneAxialToolButtonAction->setChecked(true); break; case VolumeSliceViewPlaneEnum::CORONAL: m_volumePlaneCoronalToolButtonAction->setChecked(true); break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: m_volumePlaneParasagittalToolButtonAction->setChecked(true); break; } m_volumePlaneWidgetGroup->blockAllSignals(false); } /** * Called when volume slice plane button is clicked. */ void BrainBrowserWindowToolBarSlicePlane::volumePlaneActionGroupTriggered(QAction* action) { VolumeSliceViewPlaneEnum::Enum plane = VolumeSliceViewPlaneEnum::AXIAL; if (action == m_volumePlaneAllToolButtonAction) { plane = VolumeSliceViewPlaneEnum::ALL; } else if (action == m_volumePlaneAxialToolButtonAction) { plane = VolumeSliceViewPlaneEnum::AXIAL; } else if (action == m_volumePlaneCoronalToolButtonAction) { plane = VolumeSliceViewPlaneEnum::CORONAL; } else if (action == m_volumePlaneParasagittalToolButtonAction) { plane = VolumeSliceViewPlaneEnum::PARASAGITTAL; } else { CaretLogSevere("Invalid volume plane action: " + action->text()); } BrowserTabContent* btc = getTabContentFromSelectedTab(); btc->setSliceViewPlane(plane); m_parentToolBar->updateVolumeIndicesWidget(btc); updateGraphicsWindow(); updateOtherYokedWindows(); } /** * Called when volume reset slice view button is pressed. */ void BrainBrowserWindowToolBarSlicePlane::volumePlaneResetToolButtonTriggered(bool /*checked*/) { BrowserTabContent* btc = getTabContentFromSelectedTab(); btc->resetView(); m_parentToolBar->updateVolumeIndicesWidget(btc); updateGraphicsWindow(); updateOtherYokedWindows(); } workbench-1.1.1/src/GuiQt/BrainBrowserWindowToolBarSlicePlane.h000066400000000000000000000050641255417355300245160ustar00rootroot00000000000000#ifndef __BRAIN_BROWSER_WINDOW_TOOL_BAR_SLICE_PLANE_H__ #define __BRAIN_BROWSER_WINDOW_TOOL_BAR_SLICE_PLANE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "BrainBrowserWindowToolBarComponent.h" class QActionGroup; namespace caret { class WuQWidgetObjectGroup; class BrainBrowserWindowToolBarSlicePlane : public BrainBrowserWindowToolBarComponent { Q_OBJECT public: BrainBrowserWindowToolBarSlicePlane(BrainBrowserWindowToolBar* parentToolBar); virtual ~BrainBrowserWindowToolBarSlicePlane(); virtual void updateContent(BrowserTabContent* browserTabContent); // ADD_NEW_METHODS_HERE private slots: void volumePlaneActionGroupTriggered(QAction*); void volumePlaneResetToolButtonTriggered(bool checked); private: BrainBrowserWindowToolBarSlicePlane(const BrainBrowserWindowToolBarSlicePlane&); BrainBrowserWindowToolBarSlicePlane& operator=(const BrainBrowserWindowToolBarSlicePlane&); BrainBrowserWindowToolBar* m_parentToolBar; WuQWidgetObjectGroup* m_volumePlaneWidgetGroup; QAction* m_volumePlaneParasagittalToolButtonAction; QAction* m_volumePlaneCoronalToolButtonAction; QAction* m_volumePlaneAxialToolButtonAction; QAction* m_volumePlaneAllToolButtonAction; QAction* m_volumePlaneResetToolButtonAction; QActionGroup* m_volumePlaneActionGroup; // ADD_NEW_MEMBERS_HERE }; #ifdef __BRAIN_BROWSER_WINDOW_TOOL_BAR_SLICE_PLANE_DECLARE__ // #endif // __BRAIN_BROWSER_WINDOW_TOOL_BAR_SLICE_PLANE_DECLARE__ } // namespace #endif //__BRAIN_BROWSER_WINDOW_TOOL_BAR_SLICE_PLANE_H__ workbench-1.1.1/src/GuiQt/BrainBrowserWindowToolBarSliceSelection.cxx000066400000000000000000000614651255417355300257660ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __BRAIN_BROWSER_WINDOW_TOOL_BAR_SLICE_SELECTION_DECLARE__ #include "BrainBrowserWindowToolBarSliceSelection.h" #undef __BRAIN_BROWSER_WINDOW_TOOL_BAR_SLICE_SELECTION_DECLARE__ #include #include #include #include #include #include #include #include "BrainBrowserWindowToolBar.h" #include "BrowserTabContent.h" #include "CaretAssert.h" #include "EnumComboBoxTemplate.h" #include "EventManager.h" #include "EventUpdateVolumeEditingToolBar.h" #include "GuiManager.h" #include "ModelVolume.h" #include "ModelWholeBrain.h" #include "VolumeFile.h" #include "VolumeSliceProjectionTypeEnum.h" #include "WuQFactory.h" #include "WuQWidgetObjectGroup.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::BrainBrowserWindowToolBarSliceSelection * \brief Toolbar component for selection of volume slices. * \ingroup GuiQt */ /** * Constructor. */ BrainBrowserWindowToolBarSliceSelection::BrainBrowserWindowToolBarSliceSelection(BrainBrowserWindowToolBar* parentToolBar) : BrainBrowserWindowToolBarComponent(parentToolBar), m_parentToolBar(parentToolBar) { QAction* volumeIndicesOriginToolButtonAction = WuQtUtilities::createAction("O\nR\nI\nG\nI\nN", "Set the slice indices to the origin, \n" "stereotaxic coordinate (0, 0, 0)", this, this, SLOT(volumeIndicesOriginActionTriggered())); QToolButton* volumeIndicesOriginToolButton = new QToolButton; volumeIndicesOriginToolButton->setDefaultAction(volumeIndicesOriginToolButtonAction); QLabel* parasagittalLabel = new QLabel("P:"); QLabel* coronalLabel = new QLabel("C:"); QLabel* axialLabel = new QLabel("A:"); m_volumeIndicesParasagittalCheckBox = new QCheckBox(" "); WuQtUtilities::setToolTipAndStatusTip(m_volumeIndicesParasagittalCheckBox, "Enable/Disable display of PARASAGITTAL slice"); QObject::connect(m_volumeIndicesParasagittalCheckBox, SIGNAL(stateChanged(int)), this, SLOT(volumeIndicesParasagittalCheckBoxStateChanged(int))); m_volumeIndicesCoronalCheckBox = new QCheckBox(" "); WuQtUtilities::setToolTipAndStatusTip(m_volumeIndicesCoronalCheckBox, "Enable/Disable display of CORONAL slice"); QObject::connect(m_volumeIndicesCoronalCheckBox, SIGNAL(stateChanged(int)), this, SLOT(volumeIndicesCoronalCheckBoxStateChanged(int))); m_volumeIndicesAxialCheckBox = new QCheckBox(" "); WuQtUtilities::setToolTipAndStatusTip(m_volumeIndicesAxialCheckBox, "Enable/Disable display of AXIAL slice"); QObject::connect(m_volumeIndicesAxialCheckBox, SIGNAL(stateChanged(int)), this, SLOT(volumeIndicesAxialCheckBoxStateChanged(int))); const int sliceIndexSpinBoxWidth = 55; const int sliceCoordinateSpinBoxWidth = 60; m_volumeIndicesParasagittalSpinBox = WuQFactory::newSpinBox(); m_volumeIndicesParasagittalSpinBox->setFixedWidth(sliceIndexSpinBoxWidth); WuQtUtilities::setToolTipAndStatusTip(m_volumeIndicesParasagittalSpinBox, "Change the selected PARASAGITTAL slice"); QObject::connect(m_volumeIndicesParasagittalSpinBox, SIGNAL(valueChanged(int)), this, SLOT(volumeIndicesParasagittalSpinBoxValueChanged(int))); m_volumeIndicesCoronalSpinBox = WuQFactory::newSpinBox(); m_volumeIndicesCoronalSpinBox->setFixedWidth(sliceIndexSpinBoxWidth); WuQtUtilities::setToolTipAndStatusTip(m_volumeIndicesCoronalSpinBox, "Change the selected CORONAL slice"); QObject::connect(m_volumeIndicesCoronalSpinBox, SIGNAL(valueChanged(int)), this, SLOT(volumeIndicesCoronalSpinBoxValueChanged(int))); m_volumeIndicesAxialSpinBox = WuQFactory::newSpinBox(); m_volumeIndicesAxialSpinBox->setFixedWidth(sliceIndexSpinBoxWidth); WuQtUtilities::setToolTipAndStatusTip(m_volumeIndicesAxialSpinBox, "Change the selected AXIAL slice"); QObject::connect(m_volumeIndicesAxialSpinBox, SIGNAL(valueChanged(int)), this, SLOT(volumeIndicesAxialSpinBoxValueChanged(int))); m_volumeIndicesXcoordSpinBox = WuQFactory::newDoubleSpinBox(); m_volumeIndicesXcoordSpinBox->setDecimals(1); m_volumeIndicesXcoordSpinBox->setFixedWidth(sliceCoordinateSpinBoxWidth); WuQtUtilities::setToolTipAndStatusTip(m_volumeIndicesXcoordSpinBox, "Adjust coordinate to select PARASAGITTAL slice"); QObject::connect(m_volumeIndicesXcoordSpinBox, SIGNAL(valueChanged(double)), this, SLOT(volumeIndicesXcoordSpinBoxValueChanged(double))); m_volumeIndicesYcoordSpinBox = WuQFactory::newDoubleSpinBox(); m_volumeIndicesYcoordSpinBox->setDecimals(1); m_volumeIndicesYcoordSpinBox->setFixedWidth(sliceCoordinateSpinBoxWidth); WuQtUtilities::setToolTipAndStatusTip(m_volumeIndicesYcoordSpinBox, "Adjust coordinate to select CORONAL slice"); QObject::connect(m_volumeIndicesYcoordSpinBox, SIGNAL(valueChanged(double)), this, SLOT(volumeIndicesYcoordSpinBoxValueChanged(double))); m_volumeIndicesZcoordSpinBox = WuQFactory::newDoubleSpinBox(); m_volumeIndicesZcoordSpinBox->setDecimals(1); m_volumeIndicesZcoordSpinBox->setFixedWidth(sliceCoordinateSpinBoxWidth); WuQtUtilities::setToolTipAndStatusTip(m_volumeIndicesZcoordSpinBox, "Adjust coordinate to select AXIAL slice"); QObject::connect(m_volumeIndicesZcoordSpinBox, SIGNAL(valueChanged(double)), this, SLOT(volumeIndicesZcoordSpinBoxValueChanged(double))); const AString idToolTipText = ("When selected: If there is an identification operation " "in ths tab or any other tab with the same yoking status " "(not Off), the volume slices will move to the location " "of the identified brainordinate."); m_volumeIdentificationUpdatesSlicesAction = WuQtUtilities::createAction("", WuQtUtilities::createWordWrappedToolTipText(idToolTipText), this, this, SLOT(volumeIdentificationToggled(bool))); m_volumeIdentificationUpdatesSlicesAction->setCheckable(true); QIcon volumeCrossHairIcon; const bool volumeCrossHairIconValid = WuQtUtilities::loadIcon(":/ToolBar/volume-crosshair-pointer.png", volumeCrossHairIcon); if (volumeCrossHairIconValid) { m_volumeIdentificationUpdatesSlicesAction->setIcon(volumeCrossHairIcon); } else { m_volumeIdentificationUpdatesSlicesAction->setText("ID"); } QToolButton* volumeIDToolButton = new QToolButton; volumeIDToolButton->setDefaultAction(m_volumeIdentificationUpdatesSlicesAction); m_volumeSliceProjectionTypeEnumComboBox = new EnumComboBoxTemplate(this); m_volumeSliceProjectionTypeEnumComboBox->setup(); m_volumeSliceProjectionTypeEnumComboBox->getComboBox()->setSizeAdjustPolicy(QComboBox::AdjustToContentsOnFirstShow); QObject::connect(m_volumeSliceProjectionTypeEnumComboBox, SIGNAL(itemActivated()), this, SLOT(volumeSliceProjectionTypeEnumComboBoxItemActivated())); WuQtUtilities::setToolTipAndStatusTip(m_volumeSliceProjectionTypeEnumComboBox->getWidget(), "Chooses viewing orientation (oblique or orthogonal)"); QGridLayout* gridLayout = new QGridLayout(this); WuQtUtilities::setLayoutSpacingAndMargins(gridLayout, 0, 0); gridLayout->addWidget(m_volumeIndicesParasagittalCheckBox, 0, 0); gridLayout->addWidget(parasagittalLabel, 0, 1); gridLayout->addWidget(m_volumeIndicesCoronalCheckBox, 1, 0); gridLayout->addWidget(coronalLabel, 1, 1); gridLayout->addWidget(m_volumeIndicesAxialCheckBox, 2, 0); gridLayout->addWidget(axialLabel, 2, 1); gridLayout->addWidget(m_volumeIndicesParasagittalSpinBox, 0, 2); gridLayout->addWidget(m_volumeIndicesCoronalSpinBox, 1, 2); gridLayout->addWidget(m_volumeIndicesAxialSpinBox, 2, 2); gridLayout->addWidget(m_volumeIndicesXcoordSpinBox, 0, 3); gridLayout->addWidget(m_volumeIndicesYcoordSpinBox, 1, 3); gridLayout->addWidget(m_volumeIndicesZcoordSpinBox, 2, 3); gridLayout->addWidget(volumeIDToolButton, 3, 0, 1, 2, Qt::AlignLeft); gridLayout->addWidget(m_volumeSliceProjectionTypeEnumComboBox->getWidget(), 3, 2, 1, 3, Qt::AlignRight); gridLayout->addWidget(volumeIndicesOriginToolButton, 0, 4, 3, 1); m_volumeIndicesWidgetGroup = new WuQWidgetObjectGroup(this); m_volumeIndicesWidgetGroup->add(volumeIndicesOriginToolButtonAction); m_volumeIndicesWidgetGroup->add(m_volumeIndicesParasagittalCheckBox); m_volumeIndicesWidgetGroup->add(m_volumeIndicesParasagittalSpinBox); m_volumeIndicesWidgetGroup->add(m_volumeIndicesCoronalCheckBox); m_volumeIndicesWidgetGroup->add(m_volumeIndicesCoronalSpinBox); m_volumeIndicesWidgetGroup->add(m_volumeIndicesAxialCheckBox); m_volumeIndicesWidgetGroup->add(m_volumeIndicesAxialSpinBox); m_volumeIndicesWidgetGroup->add(m_volumeIndicesXcoordSpinBox); m_volumeIndicesWidgetGroup->add(m_volumeIndicesYcoordSpinBox); m_volumeIndicesWidgetGroup->add(m_volumeIndicesZcoordSpinBox); m_volumeIndicesWidgetGroup->add(m_volumeIdentificationUpdatesSlicesAction); } /** * Destructor. */ BrainBrowserWindowToolBarSliceSelection::~BrainBrowserWindowToolBarSliceSelection() { } /** * Update the surface montage options widget. * * @param browserTabContent * The active model display controller (may be NULL). */ void BrainBrowserWindowToolBarSliceSelection::updateContent(BrowserTabContent* browserTabContent) { m_volumeIndicesWidgetGroup->blockAllSignals(true); const int32_t tabIndex = browserTabContent->getTabNumber(); VolumeMappableInterface* vf = NULL; ModelVolume* volumeModel = browserTabContent->getDisplayedVolumeModel(); if (volumeModel != NULL) { if (m_parentToolBar->getDisplayedModel() == volumeModel) { vf = volumeModel->getUnderlayVolumeFile(tabIndex); m_volumeIndicesAxialCheckBox->setVisible(false); m_volumeIndicesCoronalCheckBox->setVisible(false); m_volumeIndicesParasagittalCheckBox->setVisible(false); } } ModelWholeBrain* wholeBrainModel = browserTabContent->getDisplayedWholeBrainModel(); if (wholeBrainModel != NULL) { if (m_parentToolBar->getDisplayedModel() == wholeBrainModel) { vf = wholeBrainModel->getUnderlayVolumeFile(tabIndex); m_volumeIndicesAxialCheckBox->setVisible(true); m_volumeIndicesCoronalCheckBox->setVisible(true); m_volumeIndicesParasagittalCheckBox->setVisible(true); } } if (vf != NULL) { m_volumeIndicesAxialCheckBox->setChecked(browserTabContent->isSliceAxialEnabled()); m_volumeIndicesCoronalCheckBox->setChecked(browserTabContent->isSliceCoronalEnabled()); m_volumeIndicesParasagittalCheckBox->setChecked(browserTabContent->isSliceParasagittalEnabled()); } m_volumeSliceProjectionTypeEnumComboBox->setSelectedItem(browserTabContent->getSliceProjectionType()); m_volumeIdentificationUpdatesSlicesAction->setChecked(browserTabContent->isIdentificationUpdatesVolumeSlices()); this->updateSliceIndicesAndCoordinatesRanges(); m_volumeIndicesWidgetGroup->blockAllSignals(false); } /* * Set the values/minimums/maximums for volume slice indices and coordinate spin controls. */ void BrainBrowserWindowToolBarSliceSelection::updateSliceIndicesAndCoordinatesRanges() { const bool blockedStatus = m_volumeIndicesWidgetGroup->signalsBlocked(); m_volumeIndicesWidgetGroup->blockAllSignals(true); BrowserTabContent* btc = this->getTabContentFromSelectedTab(); const int32_t tabIndex = btc->getTabNumber(); VolumeMappableInterface* vf = NULL; ModelVolume* volumeModel = btc->getDisplayedVolumeModel(); if (volumeModel != NULL) { vf = volumeModel->getUnderlayVolumeFile(tabIndex); } ModelWholeBrain* wholeBrainModel = btc->getDisplayedWholeBrainModel(); if (wholeBrainModel != NULL) { vf = wholeBrainModel->getUnderlayVolumeFile(tabIndex); } if (vf != NULL) { m_volumeIndicesAxialSpinBox->setEnabled(true); m_volumeIndicesCoronalSpinBox->setEnabled(true); m_volumeIndicesParasagittalSpinBox->setEnabled(true); std::vector dimensions; vf->getDimensions(dimensions); /* * Setup minimum and maximum slices for each dimension. * Range is unlimited when Yoked. */ int minAxialDim = 0; int minCoronalDim = 0; int minParasagittalDim = 0; int maxAxialDim = (dimensions[2] > 0) ? (dimensions[2] - 1) : 0; int maxCoronalDim = (dimensions[1] > 0) ? (dimensions[1] - 1) : 0; int maxParasagittalDim = (dimensions[0] > 0) ? (dimensions[0] - 1) : 0; m_volumeIndicesAxialSpinBox->setRange(minAxialDim, maxAxialDim); m_volumeIndicesCoronalSpinBox->setRange(minCoronalDim, maxCoronalDim); m_volumeIndicesParasagittalSpinBox->setRange(minParasagittalDim, maxParasagittalDim); /* * Setup minimum and maximum coordinates for each dimension. * Range is unlimited when Yoked. */ int64_t slicesZero[3] = { 0, 0, 0 }; float sliceZeroCoords[3]; vf->indexToSpace(slicesZero, sliceZeroCoords); int64_t slicesMax[3] = { maxParasagittalDim, maxCoronalDim, maxAxialDim }; float sliceMaxCoords[3]; vf->indexToSpace(slicesMax, sliceMaxCoords); m_volumeIndicesXcoordSpinBox->setMinimum(std::min(sliceZeroCoords[0], sliceMaxCoords[0])); m_volumeIndicesYcoordSpinBox->setMinimum(std::min(sliceZeroCoords[1], sliceMaxCoords[1])); m_volumeIndicesZcoordSpinBox->setMinimum(std::min(sliceZeroCoords[2], sliceMaxCoords[2])); m_volumeIndicesXcoordSpinBox->setMaximum(std::max(sliceZeroCoords[0], sliceMaxCoords[0])); m_volumeIndicesYcoordSpinBox->setMaximum(std::max(sliceZeroCoords[1], sliceMaxCoords[1])); m_volumeIndicesZcoordSpinBox->setMaximum(std::max(sliceZeroCoords[2], sliceMaxCoords[2])); int64_t slicesOne[3] = { 1, 1, 1 }; float slicesOneCoords[3]; vf->indexToSpace(slicesOne, slicesOneCoords); const float dx = std::fabs(slicesOneCoords[0] - sliceZeroCoords[0]); const float dy = std::fabs(slicesOneCoords[1] - sliceZeroCoords[1]); const float dz = std::fabs(slicesOneCoords[2] - sliceZeroCoords[2]); m_volumeIndicesXcoordSpinBox->setSingleStep(dx); m_volumeIndicesYcoordSpinBox->setSingleStep(dy); m_volumeIndicesZcoordSpinBox->setSingleStep(dz); m_volumeIndicesAxialSpinBox->setValue(btc->getSliceIndexAxial(vf)); m_volumeIndicesCoronalSpinBox->setValue(btc->getSliceIndexCoronal(vf)); m_volumeIndicesParasagittalSpinBox->setValue(btc->getSliceIndexParasagittal(vf)); int64_t slices[3] = { btc->getSliceIndexParasagittal(vf), btc->getSliceIndexCoronal(vf), btc->getSliceIndexAxial(vf) }; float sliceCoords[3] = { 0.0, 0.0, 0.0 }; if (vf != NULL) { vf->indexToSpace(slices, sliceCoords); } m_volumeIndicesXcoordSpinBox->setValue(btc->getSliceCoordinateParasagittal()); m_volumeIndicesYcoordSpinBox->setValue(btc->getSliceCoordinateCoronal()); m_volumeIndicesZcoordSpinBox->setValue(btc->getSliceCoordinateAxial()); } m_volumeIndicesWidgetGroup->blockAllSignals(blockedStatus); } /** * Called when volume indices ORIGIN tool button is pressed. */ void BrainBrowserWindowToolBarSliceSelection::volumeIndicesOriginActionTriggered() { BrowserTabContent* btc = this->getTabContentFromSelectedTab(); btc->setSlicesToOrigin(); updateContent(btc); this->updateGraphicsWindow(); this->updateOtherYokedWindows(); } /** * Called when volume indices parasagittal check box is toggled. */ void BrainBrowserWindowToolBarSliceSelection::volumeIndicesParasagittalCheckBoxStateChanged(int /*state*/) { BrowserTabContent* btc = this->getTabContentFromSelectedTab(); btc->setSliceParasagittalEnabled(m_volumeIndicesParasagittalCheckBox->isChecked()); this->updateGraphicsWindow(); this->updateOtherYokedWindows(); } /** * Called when volume indices coronal check box is toggled. */ void BrainBrowserWindowToolBarSliceSelection::volumeIndicesCoronalCheckBoxStateChanged(int /*state*/) { BrowserTabContent* btc = this->getTabContentFromSelectedTab(); btc->setSliceCoronalEnabled(m_volumeIndicesCoronalCheckBox->isChecked()); this->updateGraphicsWindow(); this->updateOtherYokedWindows(); } /** * Called when volume indices axial check box is toggled. */ void BrainBrowserWindowToolBarSliceSelection::volumeIndicesAxialCheckBoxStateChanged(int /*state*/) { BrowserTabContent* btc = this->getTabContentFromSelectedTab(); btc->setSliceAxialEnabled(m_volumeIndicesAxialCheckBox->isChecked()); this->updateGraphicsWindow(); this->updateOtherYokedWindows(); } /** * Called when volume indices parasagittal spin box value is changed. */ void BrainBrowserWindowToolBarSliceSelection::volumeIndicesParasagittalSpinBoxValueChanged(int /*i*/) { this->readVolumeSliceIndicesAndUpdateSliceCoordinates(); } /** * Called when volume indices coronal spin box value is changed. */ void BrainBrowserWindowToolBarSliceSelection::volumeIndicesCoronalSpinBoxValueChanged(int /*i*/) { this->readVolumeSliceIndicesAndUpdateSliceCoordinates(); } /** * Called when volume indices axial spin box value is changed. */ void BrainBrowserWindowToolBarSliceSelection::volumeIndicesAxialSpinBoxValueChanged(int /*i*/) { this->readVolumeSliceIndicesAndUpdateSliceCoordinates(); } /** * Called when X stereotaxic coordinate is changed. * @param d * New value. */ void BrainBrowserWindowToolBarSliceSelection::volumeIndicesXcoordSpinBoxValueChanged(double /*d*/) { this->readVolumeSliceCoordinatesAndUpdateSliceIndices(); } /** * Called when Y stereotaxic coordinate is changed. * @param d * New value. */ void BrainBrowserWindowToolBarSliceSelection::volumeIndicesYcoordSpinBoxValueChanged(double /*d*/) { this->readVolumeSliceCoordinatesAndUpdateSliceIndices(); } /** * Called when Z stereotaxic coordinate is changed. * @param d * New value. */ void BrainBrowserWindowToolBarSliceSelection::volumeIndicesZcoordSpinBoxValueChanged(double /*d*/) { this->readVolumeSliceCoordinatesAndUpdateSliceIndices(); } /** * Read the slice indices and update the slice coordinates. */ void BrainBrowserWindowToolBarSliceSelection::readVolumeSliceIndicesAndUpdateSliceCoordinates() { BrowserTabContent* btc = this->getTabContentFromSelectedTab(); const int32_t tabIndex = btc->getTabNumber(); VolumeMappableInterface* underlayVolumeFile = NULL; ModelWholeBrain* wholeBrainModel = btc->getDisplayedWholeBrainModel(); if (wholeBrainModel != NULL) { if (m_parentToolBar->getDisplayedModel() == wholeBrainModel) { underlayVolumeFile = wholeBrainModel->getUnderlayVolumeFile(tabIndex); } } ModelVolume* volumeModel = btc->getDisplayedVolumeModel(); if (volumeModel != NULL) { if (m_parentToolBar->getDisplayedModel() == volumeModel) { underlayVolumeFile = volumeModel->getUnderlayVolumeFile(tabIndex); } } if (underlayVolumeFile != NULL) { const int64_t parasagittalSlice = m_volumeIndicesParasagittalSpinBox->value(); const int64_t coronalSlice = m_volumeIndicesCoronalSpinBox->value(); const int64_t axialSlice = m_volumeIndicesAxialSpinBox->value(); btc->setSliceIndexAxial(underlayVolumeFile, axialSlice); btc->setSliceIndexCoronal(underlayVolumeFile, coronalSlice); btc->setSliceIndexParasagittal(underlayVolumeFile, parasagittalSlice); } this->updateSliceIndicesAndCoordinatesRanges(); this->updateGraphicsWindow(); this->updateOtherYokedWindows(); } /** * Read the slice coordinates and convert to slices indices and then * update the displayed slices. */ void BrainBrowserWindowToolBarSliceSelection::readVolumeSliceCoordinatesAndUpdateSliceIndices() { BrowserTabContent* btc = this->getTabContentFromSelectedTab(); const int32_t tabIndex = btc->getTabNumber(); VolumeMappableInterface* underlayVolumeFile = NULL; ModelWholeBrain* wholeBrainModel = btc->getDisplayedWholeBrainModel(); if (wholeBrainModel != NULL) { if (m_parentToolBar->getDisplayedModel() == wholeBrainModel) { underlayVolumeFile = wholeBrainModel->getUnderlayVolumeFile(tabIndex); } } ModelVolume* volumeModel = btc->getDisplayedVolumeModel(); if (volumeModel != NULL) { if (m_parentToolBar->getDisplayedModel() == volumeModel) { underlayVolumeFile = volumeModel->getUnderlayVolumeFile(tabIndex); } } if (underlayVolumeFile != NULL) { float sliceCoords[3] = { m_volumeIndicesXcoordSpinBox->value(), m_volumeIndicesYcoordSpinBox->value(), m_volumeIndicesZcoordSpinBox->value() }; btc->selectSlicesAtCoordinate(sliceCoords); } this->updateSliceIndicesAndCoordinatesRanges(); this->updateGraphicsWindow(); this->updateOtherYokedWindows(); } /** * Called when projection type is changed. */ void BrainBrowserWindowToolBarSliceSelection::volumeSliceProjectionTypeEnumComboBoxItemActivated() { BrowserTabContent* btc = this->getTabContentFromSelectedTab(); const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType = m_volumeSliceProjectionTypeEnumComboBox->getSelectedItem(); btc->setSliceProjectionType(sliceProjectionType); this->updateGraphicsWindow(); this->updateOtherYokedWindows(); EventManager::get()->sendEvent(EventUpdateVolumeEditingToolBar().getPointer()); } /** * Called when volume identification action toggled. * * @param value * New value. */ void BrainBrowserWindowToolBarSliceSelection::volumeIdentificationToggled(bool value) { BrowserTabContent* browserTabContent = this->getTabContentFromSelectedTab(); if (browserTabContent == NULL) { return; } browserTabContent->setIdentificationUpdatesVolumeSlices(value); } workbench-1.1.1/src/GuiQt/BrainBrowserWindowToolBarSliceSelection.h000066400000000000000000000074041255417355300254040ustar00rootroot00000000000000#ifndef __BRAIN_BROWSER_WINDOW_TOOL_BAR_SLICE_SELECTION_H__ #define __BRAIN_BROWSER_WINDOW_TOOL_BAR_SLICE_SELECTION_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "BrainBrowserWindowToolBarComponent.h" class QAction; class QCheckBox; class QDoubleSpinBox; class QSpinBox; class WuQWidgetObjectGroup; namespace caret { class EnumComboBoxTemplate; class BrainBrowserWindowToolBarSliceSelection : public BrainBrowserWindowToolBarComponent { Q_OBJECT public: BrainBrowserWindowToolBarSliceSelection(BrainBrowserWindowToolBar* parentToolBar); virtual ~BrainBrowserWindowToolBarSliceSelection(); virtual void updateContent(BrowserTabContent* browserTabContent); // ADD_NEW_METHODS_HERE private slots: void volumeIndicesOriginActionTriggered(); void volumeIndicesParasagittalCheckBoxStateChanged(int state); void volumeIndicesCoronalCheckBoxStateChanged(int state); void volumeIndicesAxialCheckBoxStateChanged(int state); void volumeIndicesParasagittalSpinBoxValueChanged(int i); void volumeIndicesCoronalSpinBoxValueChanged(int i); void volumeIndicesAxialSpinBoxValueChanged(int i); void volumeIndicesXcoordSpinBoxValueChanged(double d); void volumeIndicesYcoordSpinBoxValueChanged(double d); void volumeIndicesZcoordSpinBoxValueChanged(double d); void volumeSliceProjectionTypeEnumComboBoxItemActivated(); void volumeIdentificationToggled(bool value); private: BrainBrowserWindowToolBarSliceSelection(const BrainBrowserWindowToolBarSliceSelection&); BrainBrowserWindowToolBarSliceSelection& operator=(const BrainBrowserWindowToolBarSliceSelection&); void readVolumeSliceCoordinatesAndUpdateSliceIndices(); void readVolumeSliceIndicesAndUpdateSliceCoordinates(); void updateSliceIndicesAndCoordinatesRanges(); BrainBrowserWindowToolBar* m_parentToolBar; WuQWidgetObjectGroup* m_volumeIndicesWidgetGroup; QAction* m_volumeIdentificationUpdatesSlicesAction; QCheckBox* m_volumeIndicesParasagittalCheckBox; QCheckBox* m_volumeIndicesCoronalCheckBox; QCheckBox* m_volumeIndicesAxialCheckBox; QSpinBox* m_volumeIndicesParasagittalSpinBox; QSpinBox* m_volumeIndicesCoronalSpinBox; QSpinBox* m_volumeIndicesAxialSpinBox; QDoubleSpinBox* m_volumeIndicesXcoordSpinBox; QDoubleSpinBox* m_volumeIndicesYcoordSpinBox; QDoubleSpinBox* m_volumeIndicesZcoordSpinBox; EnumComboBoxTemplate* m_volumeSliceProjectionTypeEnumComboBox; // ADD_NEW_MEMBERS_HERE }; #ifdef __BRAIN_BROWSER_WINDOW_TOOL_BAR_SLICE_SELECTION_DECLARE__ // #endif // __BRAIN_BROWSER_WINDOW_TOOL_BAR_SLICE_SELECTION_DECLARE__ } // namespace #endif //__BRAIN_BROWSER_WINDOW_TOOL_BAR_SLICE_SELECTION_H__ workbench-1.1.1/src/GuiQt/BrainBrowserWindowToolBarSurfaceMontage.cxx000066400000000000000000001050411255417355300257510ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #define __BRAIN_BROWSER_WINDOW_TOOL_BAR_SURFACE_MONTAGE_DECLARE__ #include "BrainBrowserWindowToolBarSurfaceMontage.h" #undef __BRAIN_BROWSER_WINDOW_TOOL_BAR_SURFACE_MONTAGE_DECLARE__ #include "BrainBrowserWindowToolBar.h" #include "BrowserTabContent.h" #include "CaretAssert.h" #include "EnumComboBoxTemplate.h" #include "EventManager.h" #include "EventSurfaceColoringInvalidate.h" #include "ModelSurfaceMontage.h" #include "SurfaceMontageConfigurationCerebellar.h" #include "SurfaceMontageConfigurationCerebral.h" #include "SurfaceMontageConfigurationFlatMaps.h" #include "SurfaceMontageLayoutOrientationEnum.h" #include "SurfaceSelectionModel.h" #include "SurfaceSelectionViewController.h" #include "WuQtUtilities.h" #include "WuQWidgetObjectGroup.h" using namespace caret; /** * \class caret::BrainBrowserWindowToolBarSurfaceMontage * \brief Surface Montage Component of Brain Browser Window ToolBar * \ingroup GuiQt */ /** * Constructor. * * @param parentToolBar * parent toolbar. */ BrainBrowserWindowToolBarSurfaceMontage::BrainBrowserWindowToolBarSurfaceMontage(BrainBrowserWindowToolBar* parentToolBar) : BrainBrowserWindowToolBarComponent(parentToolBar), m_parentToolBar(parentToolBar) { m_surfaceMontageConfigurationTypeEnumComboBox = new EnumComboBoxTemplate(this); m_surfaceMontageConfigurationTypeEnumComboBox->setup(); QObject::connect(m_surfaceMontageConfigurationTypeEnumComboBox, SIGNAL(itemActivated()), this, SLOT(surfaceMontageConfigurationTypeEnumComboBoxItemActivated())); addToWidgetGroup(m_surfaceMontageConfigurationTypeEnumComboBox->getWidget()); WuQtUtilities::setToolTipAndStatusTip(m_surfaceMontageConfigurationTypeEnumComboBox->getWidget(), ("Selects Surface Montage Configuration:\n" " Cerebellar Cortex\n" " Cerebral Cortex\n" " Flat Maps")); m_surfaceMontageLayoutOrientationEnumComboBox = new EnumComboBoxTemplate(this); m_surfaceMontageLayoutOrientationEnumComboBox->setup(); QObject::connect(m_surfaceMontageLayoutOrientationEnumComboBox, SIGNAL(itemActivated()), this, SLOT(surfaceMontageLayoutOrientationEnumComboBoxItemActivated())); addToWidgetGroup(m_surfaceMontageLayoutOrientationEnumComboBox->getWidget()); WuQtUtilities::setToolTipAndStatusTip(m_surfaceMontageLayoutOrientationEnumComboBox->getWidget(), ("Selects Surface Layout:\n" " Landscape (Layout left-to-right)\n" " Portrait (Layout top-to-bottom)")); m_cerebellarComponent = new SurfaceMontageCerebellarComponent(this); m_cerebralComponent = new SurfaceMontageCerebralComponent(this); m_flatMapsComponent = new SurfaceMontageFlatMapsComponent(this); QHBoxLayout* configOrientationLayout = new QHBoxLayout(); WuQtUtilities::setLayoutSpacingAndMargins(configOrientationLayout, 2, 0); configOrientationLayout->addStretch(); configOrientationLayout->addWidget(m_surfaceMontageConfigurationTypeEnumComboBox->getWidget()); configOrientationLayout->addStretch(); configOrientationLayout->addSpacing(10); configOrientationLayout->addWidget(m_surfaceMontageLayoutOrientationEnumComboBox->getWidget()); configOrientationLayout->addStretch(); m_stackedWidget = new QStackedWidget(); m_stackedWidget->addWidget(m_cerebellarComponent); m_stackedWidget->addWidget(m_cerebralComponent); m_stackedWidget->addWidget(m_flatMapsComponent); QVBoxLayout* layout = new QVBoxLayout(this); WuQtUtilities::setLayoutSpacingAndMargins(layout, 2, 0); layout->addLayout(configOrientationLayout); layout->addWidget(WuQtUtilities::createHorizontalLineWidget()); layout->addWidget(m_stackedWidget); // EventManager::get()->addEventListener(this, EventTypeEnum::); } /** * Destructor. */ BrainBrowserWindowToolBarSurfaceMontage::~BrainBrowserWindowToolBarSurfaceMontage() { EventManager::get()->removeAllEventsFromListener(this); } /** * Called when the montage configuration is changed. */ void BrainBrowserWindowToolBarSurfaceMontage::surfaceMontageConfigurationTypeEnumComboBoxItemActivated() { m_parentToolBar->getTabContentFromSelectedTab(); BrowserTabContent* btc = m_parentToolBar->getTabContentFromSelectedTab(); const SurfaceMontageConfigurationTypeEnum::Enum configType = m_surfaceMontageConfigurationTypeEnumComboBox->getSelectedItem(); ModelSurfaceMontage* msm = btc->getDisplayedSurfaceMontageModel(); const int32_t tabIndex = btc->getTabNumber(); msm->setSelectedConfigurationType(tabIndex, configType); updateContent(btc); invalidateColoringAndUpdateGraphicsWindow(); m_parentToolBar->updateUserInterface(); } /** * Called when the layout orientation value is changed. */ void BrainBrowserWindowToolBarSurfaceMontage::surfaceMontageLayoutOrientationEnumComboBoxItemActivated() { m_parentToolBar->getTabContentFromSelectedTab(); BrowserTabContent* btc = m_parentToolBar->getTabContentFromSelectedTab(); ModelSurfaceMontage* msm = btc->getDisplayedSurfaceMontageModel(); const int32_t tabIndex = btc->getTabNumber(); SurfaceMontageConfigurationAbstract* selectedConfiguration = msm->getSelectedConfiguration(tabIndex); selectedConfiguration->setLayoutOrientation(m_surfaceMontageLayoutOrientationEnumComboBox->getSelectedItem()); invalidateColoringAndUpdateGraphicsWindow(); } /** * Update the surface montage options widget. * * @param browserTabContent * The active model display controller (may be NULL). */ void BrainBrowserWindowToolBarSurfaceMontage::updateContent(BrowserTabContent* browserTabContent) { ModelSurfaceMontage* msm = browserTabContent->getDisplayedSurfaceMontageModel(); if (msm == NULL) { return; } const int32_t tabIndex = browserTabContent->getTabNumber(); std::vector validConfigs; if (msm->getCerebellarConfiguration(tabIndex)->isValid()) { validConfigs.push_back(SurfaceMontageConfigurationTypeEnum::CEREBELLAR_CORTEX_CONFIGURATION); } if (msm->getCerebralConfiguration(tabIndex)->isValid()) { validConfigs.push_back(SurfaceMontageConfigurationTypeEnum::CEREBRAL_CORTEX_CONFIGURATION); } if (msm->getFlatMapsConfiguration(tabIndex)->isValid()) { validConfigs.push_back(SurfaceMontageConfigurationTypeEnum::FLAT_CONFIGURATION); } m_surfaceMontageConfigurationTypeEnumComboBox->setupWithItems(validConfigs); SurfaceMontageConfigurationAbstract* selectedConfiguration = msm->getSelectedConfiguration(tabIndex); m_surfaceMontageConfigurationTypeEnumComboBox->setSelectedItem(msm->getSelectedConfigurationType(tabIndex)); m_surfaceMontageLayoutOrientationEnumComboBox->setSelectedItem(selectedConfiguration->getLayoutOrientation()); switch (msm->getSelectedConfigurationType(tabIndex)) { case SurfaceMontageConfigurationTypeEnum::CEREBELLAR_CORTEX_CONFIGURATION: m_cerebellarComponent->updateContent(browserTabContent); m_stackedWidget->setCurrentWidget(m_cerebellarComponent); break; case SurfaceMontageConfigurationTypeEnum::CEREBRAL_CORTEX_CONFIGURATION: m_cerebralComponent->updateContent(browserTabContent); m_stackedWidget->setCurrentWidget(m_cerebralComponent); break; case SurfaceMontageConfigurationTypeEnum::FLAT_CONFIGURATION: m_flatMapsComponent->updateContent(browserTabContent); m_stackedWidget->setCurrentWidget(m_flatMapsComponent); break; } } /* ******************************************************************************************** */ /** * \class caret::SurfaceMontageCerebralComponent * \brief Cerebral Surface Montage Component of Brain Browser Window ToolBar * \ingroup GuiQt */ /** * Constructor. * * @param parentToolBar * parent toolbar. */ SurfaceMontageCerebralComponent::SurfaceMontageCerebralComponent(BrainBrowserWindowToolBarSurfaceMontage* parentToolBarMontage) : QWidget(parentToolBarMontage) { m_parentToolBarMontage = parentToolBarMontage; m_leftCheckBox = new QCheckBox("Left"); QObject::connect(m_leftCheckBox, SIGNAL(toggled(bool)), this, SLOT(checkBoxSelected(bool))); m_rightCheckBox = new QCheckBox("Right"); QObject::connect(m_rightCheckBox, SIGNAL(toggled(bool)), this, SLOT(checkBoxSelected(bool))); m_lateralCheckBox = new QCheckBox("Lateral"); QObject::connect(m_lateralCheckBox, SIGNAL(toggled(bool)), this, SLOT(checkBoxSelected(bool))); m_medialCheckBox = new QCheckBox("Medial"); QObject::connect(m_medialCheckBox, SIGNAL(toggled(bool)), this, SLOT(checkBoxSelected(bool))); m_surfaceMontageFirstSurfaceCheckBox = new QCheckBox(" "); QObject::connect(m_surfaceMontageFirstSurfaceCheckBox, SIGNAL(toggled(bool)), this, SLOT(checkBoxSelected(bool))); m_surfaceMontageSecondSurfaceCheckBox = new QCheckBox(" "); QObject::connect(m_surfaceMontageSecondSurfaceCheckBox, SIGNAL(toggled(bool)), this, SLOT(checkBoxSelected(bool))); m_leftSurfaceViewController = new SurfaceSelectionViewController(this); QObject::connect(m_leftSurfaceViewController, SIGNAL(surfaceSelected(Surface*)), this, SLOT(leftSurfaceSelected(Surface*))); m_leftSecondSurfaceViewController = new SurfaceSelectionViewController(this); QObject::connect(m_leftSecondSurfaceViewController, SIGNAL(surfaceSelected(Surface*)), this, SLOT(leftSecondSurfaceSelected(Surface*))); m_rightSurfaceViewController = new SurfaceSelectionViewController(this); QObject::connect(m_rightSurfaceViewController, SIGNAL(surfaceSelected(Surface*)), this, SLOT(rightSurfaceSelected(Surface*))); m_rightSecondSurfaceViewController = new SurfaceSelectionViewController(this); QObject::connect(m_rightSecondSurfaceViewController, SIGNAL(surfaceSelected(Surface*)), this, SLOT(rightSecondSurfaceSelected(Surface*))); QHBoxLayout* checkBoxLayout = new QHBoxLayout(); WuQtUtilities::setLayoutSpacingAndMargins(checkBoxLayout, 2, 0); checkBoxLayout->addWidget(m_leftCheckBox); checkBoxLayout->addSpacing(10); checkBoxLayout->addStretch(); checkBoxLayout->addWidget(m_lateralCheckBox); checkBoxLayout->addSpacing(10); checkBoxLayout->addStretch(); checkBoxLayout->addWidget(m_medialCheckBox); checkBoxLayout->addSpacing(10); checkBoxLayout->addStretch(); checkBoxLayout->addWidget(m_rightCheckBox); int32_t columnIndex = 0; const int32_t COLUMN_ONE_TWO = columnIndex++; const int32_t COLUMN_INDEX_LEFT = columnIndex++; const int32_t COLUMN_INDEX_RIGHT = columnIndex++; QGridLayout* layout = new QGridLayout(this); layout->setColumnStretch(0, 0); layout->setColumnStretch(1, 100); layout->setColumnStretch(2, 100); WuQtUtilities::setLayoutSpacingAndMargins(layout, 4, 0); int row = layout->rowCount(); layout->addWidget(m_surfaceMontageFirstSurfaceCheckBox, row, COLUMN_ONE_TWO); layout->addWidget(m_leftSurfaceViewController->getWidget(), row, COLUMN_INDEX_LEFT); layout->addWidget(m_rightSurfaceViewController->getWidget(), row, COLUMN_INDEX_RIGHT); row = layout->rowCount(); layout->addWidget(m_surfaceMontageSecondSurfaceCheckBox, row, COLUMN_ONE_TWO); layout->addWidget(m_leftSecondSurfaceViewController->getWidget(), row, COLUMN_INDEX_LEFT); layout->addWidget(m_rightSecondSurfaceViewController->getWidget(), row, COLUMN_INDEX_RIGHT); row = layout->rowCount(); layout->addLayout(checkBoxLayout, row, COLUMN_INDEX_LEFT, 1, 2); row = layout->rowCount(); m_widgetGroup = new WuQWidgetObjectGroup(this); m_widgetGroup->add(m_leftSurfaceViewController->getWidget()); m_widgetGroup->add(m_leftSecondSurfaceViewController->getWidget()); m_widgetGroup->add(m_rightSurfaceViewController->getWidget()); m_widgetGroup->add(m_rightSecondSurfaceViewController->getWidget()); m_widgetGroup->add(m_leftCheckBox); m_widgetGroup->add(m_rightCheckBox); m_widgetGroup->add(m_surfaceMontageFirstSurfaceCheckBox); m_widgetGroup->add(m_surfaceMontageSecondSurfaceCheckBox); m_widgetGroup->add(m_medialCheckBox); m_widgetGroup->add(m_lateralCheckBox); setFixedHeight(sizeHint().height()); } /** * Destructor. */ SurfaceMontageCerebralComponent::~SurfaceMontageCerebralComponent() { } /** * Update the cerebral montage options widget. * * @param browserTabContent * The active model display controller (may be NULL). */ void SurfaceMontageCerebralComponent::updateContent(BrowserTabContent* browserTabContent) { m_widgetGroup->blockAllSignals(true); const int32_t tabIndex = browserTabContent->getTabNumber(); ModelSurfaceMontage* msm = browserTabContent->getDisplayedSurfaceMontageModel(); SurfaceMontageConfigurationCerebral* smcc = msm->getCerebralConfiguration(tabIndex); m_leftCheckBox->setChecked(smcc->isLeftEnabled()); m_rightCheckBox->setChecked(smcc->isRightEnabled()); m_surfaceMontageFirstSurfaceCheckBox->setChecked(smcc->isFirstSurfaceEnabled()); m_surfaceMontageSecondSurfaceCheckBox->setChecked(smcc->isSecondSurfaceEnabled()); m_lateralCheckBox->setChecked(smcc->isLateralEnabled()); m_medialCheckBox->setChecked(smcc->isMedialEnabled()); m_leftSurfaceViewController->updateControl(smcc->getLeftFirstSurfaceSelectionModel()); m_leftSecondSurfaceViewController->updateControl(smcc->getLeftSecondSurfaceSelectionModel()); m_rightSurfaceViewController->updateControl(smcc->getRightFirstSurfaceSelectionModel()); m_rightSecondSurfaceViewController->updateControl(smcc->getRightSecondSurfaceSelectionModel()); m_widgetGroup->blockAllSignals(false); } /** * Called when montage left first surface is selected. * @param surface * Surface that was selected. */ void SurfaceMontageCerebralComponent::leftSurfaceSelected(Surface* surface) { if (surface != NULL) { BrowserTabContent* btc = m_parentToolBarMontage->getTabContentFromSelectedTab(); const int32_t tabIndex = btc->getTabNumber(); ModelSurfaceMontage* msm = btc->getDisplayedSurfaceMontageModel(); SurfaceMontageConfigurationCerebral* smcc = msm->getCerebralConfiguration(tabIndex); smcc->getLeftFirstSurfaceSelectionModel()->setSurface(surface); m_parentToolBarMontage->invalidateColoringAndUpdateGraphicsWindow(); } } /** * Called when montage left second surface is selected. * @param surface * Surface that was selected. */ void SurfaceMontageCerebralComponent::leftSecondSurfaceSelected(Surface* surface) { if (surface != NULL) { BrowserTabContent* btc = m_parentToolBarMontage->getTabContentFromSelectedTab(); const int32_t tabIndex = btc->getTabNumber(); ModelSurfaceMontage* msm = btc->getDisplayedSurfaceMontageModel(); SurfaceMontageConfigurationCerebral* smcc = msm->getCerebralConfiguration(tabIndex); smcc->getLeftSecondSurfaceSelectionModel()->setSurface(surface); m_parentToolBarMontage->invalidateColoringAndUpdateGraphicsWindow(); } } /** * Called when montage right surface is selected. * @param surface * Surface that was selected. */ void SurfaceMontageCerebralComponent::rightSurfaceSelected(Surface* surface) { if (surface != NULL) { BrowserTabContent* btc = m_parentToolBarMontage->getTabContentFromSelectedTab(); const int32_t tabIndex = btc->getTabNumber(); ModelSurfaceMontage* msm = btc->getDisplayedSurfaceMontageModel(); SurfaceMontageConfigurationCerebral* smcc = msm->getCerebralConfiguration(tabIndex); smcc->getRightFirstSurfaceSelectionModel()->setSurface(surface); m_parentToolBarMontage->invalidateColoringAndUpdateGraphicsWindow(); } } /** * Called when montage right second surface is selected. * @param surface * Surface that was selected. */ void SurfaceMontageCerebralComponent::rightSecondSurfaceSelected(Surface* surface) { if (surface != NULL) { BrowserTabContent* btc = m_parentToolBarMontage->getTabContentFromSelectedTab(); const int32_t tabIndex = btc->getTabNumber(); ModelSurfaceMontage* msm = btc->getDisplayedSurfaceMontageModel(); SurfaceMontageConfigurationCerebral* smcc = msm->getCerebralConfiguration(tabIndex); smcc->getRightSecondSurfaceSelectionModel()->setSurface(surface); m_parentToolBarMontage->invalidateColoringAndUpdateGraphicsWindow(); } } /** * Called when surface montage checkbox is toggled. * @param status * New status of check box. */ void SurfaceMontageCerebralComponent::checkBoxSelected(bool /*status*/) { BrowserTabContent* btc = m_parentToolBarMontage->getTabContentFromSelectedTab(); const int32_t tabIndex = btc->getTabNumber(); ModelSurfaceMontage* msm = btc->getDisplayedSurfaceMontageModel(); SurfaceMontageConfigurationCerebral* smcc = msm->getCerebralConfiguration(tabIndex); smcc->setLeftEnabled(m_leftCheckBox->isChecked()); smcc->setRightEnabled(m_rightCheckBox->isChecked()); smcc->setFirstSurfaceEnabled(m_surfaceMontageFirstSurfaceCheckBox->isChecked()); smcc->setSecondSurfaceEnabled(m_surfaceMontageSecondSurfaceCheckBox->isChecked()); smcc->setLateralEnabled(m_lateralCheckBox->isChecked()); smcc->setMedialEnabled(m_medialCheckBox->isChecked()); m_parentToolBarMontage->updateUserInterface(); m_parentToolBarMontage->invalidateColoringAndUpdateGraphicsWindow(); } /* ******************************************************************************************** */ /** * \class caret::SurfaceMontageCerebellarComponent * \brief Cerebellar Surface Montage Component of Brain Browser Window ToolBar * \ingroup GuiQt */ /** * Constructor. * * @param parentToolBar * parent toolbar. */ SurfaceMontageCerebellarComponent::SurfaceMontageCerebellarComponent(BrainBrowserWindowToolBarSurfaceMontage* parentToolBarMontage) : QWidget(parentToolBarMontage) { m_parentToolBarMontage = parentToolBarMontage; m_dorsalCheckBox = new QCheckBox("Dorsal"); QObject::connect(m_dorsalCheckBox, SIGNAL(toggled(bool)), this, SLOT(checkBoxSelected(bool))); m_ventralCheckBox = new QCheckBox("Ventral"); QObject::connect(m_ventralCheckBox, SIGNAL(toggled(bool)), this, SLOT(checkBoxSelected(bool))); m_anteriorCheckBox = new QCheckBox("Anterior"); QObject::connect(m_anteriorCheckBox, SIGNAL(toggled(bool)), this, SLOT(checkBoxSelected(bool))); m_posteriorCheckBox = new QCheckBox("Posterior"); QObject::connect(m_posteriorCheckBox, SIGNAL(toggled(bool)), this, SLOT(checkBoxSelected(bool))); m_firstSurfaceCheckBox = new QCheckBox(" "); QObject::connect(m_firstSurfaceCheckBox, SIGNAL(toggled(bool)), this, SLOT(checkBoxSelected(bool))); m_secondSurfaceCheckBox = new QCheckBox(" "); QObject::connect(m_secondSurfaceCheckBox, SIGNAL(toggled(bool)), this, SLOT(checkBoxSelected(bool))); m_firstSurfaceViewController = new SurfaceSelectionViewController(this); QObject::connect(m_firstSurfaceViewController, SIGNAL(surfaceSelected(Surface*)), this, SLOT(firstSurfaceSelected(Surface*))); m_secondSurfaceViewController = new SurfaceSelectionViewController(this); QObject::connect(m_secondSurfaceViewController, SIGNAL(surfaceSelected(Surface*)), this, SLOT(secondSurfaceSelected(Surface*))); QHBoxLayout* checkBoxLayout = new QHBoxLayout(); WuQtUtilities::setLayoutSpacingAndMargins(checkBoxLayout, 2, 0); checkBoxLayout->addStretch(); checkBoxLayout->addWidget(m_dorsalCheckBox); checkBoxLayout->addSpacing(5); checkBoxLayout->addStretch(); checkBoxLayout->addWidget(m_ventralCheckBox); checkBoxLayout->addSpacing(5); checkBoxLayout->addStretch(); checkBoxLayout->addWidget(m_anteriorCheckBox); checkBoxLayout->addSpacing(5); checkBoxLayout->addStretch(); checkBoxLayout->addWidget(m_posteriorCheckBox); checkBoxLayout->addStretch(); int32_t columnIndex = 0; const int32_t COLUMN_CHECKBOX = columnIndex++; const int32_t COLUMN_SELECTION = columnIndex++; QGridLayout* layout = new QGridLayout(this); layout->setColumnStretch(COLUMN_CHECKBOX, 0); layout->setColumnStretch(COLUMN_SELECTION, 100); WuQtUtilities::setLayoutSpacingAndMargins(layout, 4, 0); int row = layout->rowCount(); layout->addWidget(m_firstSurfaceCheckBox, row, COLUMN_CHECKBOX); layout->addWidget(m_firstSurfaceViewController->getWidget(), row, COLUMN_SELECTION); row = layout->rowCount(); layout->addWidget(m_secondSurfaceCheckBox, row, COLUMN_CHECKBOX); layout->addWidget(m_secondSurfaceViewController->getWidget(), row, COLUMN_SELECTION); row = layout->rowCount(); layout->addLayout(checkBoxLayout, row, COLUMN_CHECKBOX, 1, 2); row = layout->rowCount(); m_widgetGroup = new WuQWidgetObjectGroup(this); m_widgetGroup->add(m_firstSurfaceViewController->getWidget()); m_widgetGroup->add(m_secondSurfaceViewController->getWidget()); m_widgetGroup->add(m_firstSurfaceCheckBox); m_widgetGroup->add(m_secondSurfaceCheckBox); m_widgetGroup->add(m_dorsalCheckBox); m_widgetGroup->add(m_ventralCheckBox); m_widgetGroup->add(m_anteriorCheckBox); m_widgetGroup->add(m_posteriorCheckBox); setFixedHeight(sizeHint().height()); } /** * Destructor. */ SurfaceMontageCerebellarComponent::~SurfaceMontageCerebellarComponent() { } /** * Update the cerebellar montage options widget. * * @param browserTabContent * The active model display controller (may be NULL). */ void SurfaceMontageCerebellarComponent::updateContent(BrowserTabContent* browserTabContent) { m_widgetGroup->blockAllSignals(true); const int32_t tabIndex = browserTabContent->getTabNumber(); ModelSurfaceMontage* msm = browserTabContent->getDisplayedSurfaceMontageModel(); SurfaceMontageConfigurationCerebellar* smcc = msm->getCerebellarConfiguration(tabIndex); m_firstSurfaceCheckBox->setChecked(smcc->isFirstSurfaceEnabled()); m_secondSurfaceCheckBox->setChecked(smcc->isSecondSurfaceEnabled()); m_dorsalCheckBox->setChecked(smcc->isDorsalEnabled()); m_ventralCheckBox->setChecked(smcc->isVentralEnabled()); m_anteriorCheckBox->setChecked(smcc->isAnteriorEnabled()); m_posteriorCheckBox->setChecked(smcc->isPosteriorEnabled()); m_firstSurfaceViewController->updateControl(smcc->getFirstSurfaceSelectionModel()); m_secondSurfaceViewController->updateControl(smcc->getSecondSurfaceSelectionModel()); m_widgetGroup->blockAllSignals(false); } /** * Called when first cerebellar surface is selected. * @param surface * Surface that was selected. */ void SurfaceMontageCerebellarComponent::firstSurfaceSelected(Surface* surface) { if (surface != NULL) { BrowserTabContent* btc = m_parentToolBarMontage->getTabContentFromSelectedTab(); const int32_t tabIndex = btc->getTabNumber(); ModelSurfaceMontage* msm = btc->getDisplayedSurfaceMontageModel(); SurfaceMontageConfigurationCerebellar* smcc = msm->getCerebellarConfiguration(tabIndex); smcc->getFirstSurfaceSelectionModel()->setSurface(surface); m_parentToolBarMontage->invalidateColoringAndUpdateGraphicsWindow(); } } /** * Called when second cerebellar surface is selected. * @param surface * Surface that was selected. */ void SurfaceMontageCerebellarComponent::secondSurfaceSelected(Surface* surface) { if (surface != NULL) { BrowserTabContent* btc = m_parentToolBarMontage->getTabContentFromSelectedTab(); const int32_t tabIndex = btc->getTabNumber(); ModelSurfaceMontage* msm = btc->getDisplayedSurfaceMontageModel(); SurfaceMontageConfigurationCerebellar* smcc = msm->getCerebellarConfiguration(tabIndex); smcc->getSecondSurfaceSelectionModel()->setSurface(surface); m_parentToolBarMontage->invalidateColoringAndUpdateGraphicsWindow(); } } /** * Called cerebellar surface montage checkbox is toggled. * @param status * New status of check box. */ void SurfaceMontageCerebellarComponent::checkBoxSelected(bool /*status*/) { BrowserTabContent* btc = m_parentToolBarMontage->getTabContentFromSelectedTab(); const int32_t tabIndex = btc->getTabNumber(); ModelSurfaceMontage* msm = btc->getDisplayedSurfaceMontageModel(); SurfaceMontageConfigurationCerebellar* smcc = msm->getCerebellarConfiguration(tabIndex); smcc->setDorsalEnabled(m_dorsalCheckBox->isChecked()); smcc->setVentralEnabled(m_ventralCheckBox->isChecked()); smcc->setAnteriorEnabled(m_anteriorCheckBox->isChecked()); smcc->setPosteriorEnabled(m_posteriorCheckBox->isChecked()); smcc->setFirstSurfaceEnabled(m_firstSurfaceCheckBox->isChecked()); smcc->setSecondSurfaceEnabled(m_secondSurfaceCheckBox->isChecked()); m_parentToolBarMontage->updateUserInterface(); m_parentToolBarMontage->invalidateColoringAndUpdateGraphicsWindow(); } /* ******************************************************************************************** */ /** * \class caret::SurfaceMontageFlatMapsComponent * \brief Flat Surface Montage Component of Brain Browser Window ToolBar * \ingroup GuiQt */ /** * Constructor. * * @param parentToolBar * parent toolbar. */ SurfaceMontageFlatMapsComponent::SurfaceMontageFlatMapsComponent(BrainBrowserWindowToolBarSurfaceMontage* parentToolBarMontage) : QWidget(parentToolBarMontage) { m_parentToolBarMontage = parentToolBarMontage; m_leftSurfaceCheckBox = new QCheckBox("Left"); QObject::connect(m_leftSurfaceCheckBox, SIGNAL(toggled(bool)), this, SLOT(checkBoxSelected(bool))); m_rightSurfaceCheckBox = new QCheckBox("Right"); QObject::connect(m_rightSurfaceCheckBox, SIGNAL(toggled(bool)), this, SLOT(checkBoxSelected(bool))); m_cerebellumSurfaceCheckBox = new QCheckBox("Cerebellum "); QObject::connect(m_cerebellumSurfaceCheckBox, SIGNAL(toggled(bool)), this, SLOT(checkBoxSelected(bool))); m_leftSurfaceViewController = new SurfaceSelectionViewController(this); QObject::connect(m_leftSurfaceViewController, SIGNAL(surfaceSelected(Surface*)), this, SLOT(leftSurfaceSelected(Surface*))); m_rightSurfaceViewController = new SurfaceSelectionViewController(this); QObject::connect(m_rightSurfaceViewController, SIGNAL(surfaceSelected(Surface*)), this, SLOT(rightSurfaceSelected(Surface*))); m_cerebellumSurfaceViewController = new SurfaceSelectionViewController(this); QObject::connect(m_cerebellumSurfaceViewController, SIGNAL(surfaceSelected(Surface*)), this, SLOT(cerebellumSurfaceSelected(Surface*))); // QHBoxLayout* checkBoxLayout = new QHBoxLayout(); // WuQtUtilities::setLayoutSpacingAndMargins(checkBoxLayout, 2, 0); // checkBoxLayout->addStretch(); // checkBoxLayout->addWidget(m_leftSurfaceCheckBox); // checkBoxLayout->addSpacing(5); // checkBoxLayout->addStretch(); // checkBoxLayout->addWidget(m_rightSurfaceCheckBox); // checkBoxLayout->addSpacing(5); // checkBoxLayout->addStretch(); // checkBoxLayout->addWidget(m_cerebellumSurfaceCheckBox); // checkBoxLayout->addStretch(); int32_t columnIndex = 0; const int32_t COLUMN_CHECKBOX = columnIndex++; const int32_t COLUMN_SELECTION = columnIndex++; QGridLayout* layout = new QGridLayout(this); layout->setColumnStretch(COLUMN_CHECKBOX, 0); layout->setColumnStretch(COLUMN_SELECTION, 100); WuQtUtilities::setLayoutSpacingAndMargins(layout, 4, 2); int row = layout->rowCount(); layout->addWidget(m_leftSurfaceCheckBox, row, COLUMN_CHECKBOX); layout->addWidget(m_leftSurfaceViewController->getWidget(), row, COLUMN_SELECTION); row = layout->rowCount(); layout->addWidget(m_rightSurfaceCheckBox, row, COLUMN_CHECKBOX); layout->addWidget(m_rightSurfaceViewController->getWidget(), row, COLUMN_SELECTION); row = layout->rowCount(); layout->addWidget(m_cerebellumSurfaceCheckBox, row, COLUMN_CHECKBOX); layout->addWidget(m_cerebellumSurfaceViewController->getWidget(), row, COLUMN_SELECTION); row = layout->rowCount(); // layout->addLayout(checkBoxLayout, // row, COLUMN_CHECKBOX, // 1, 2); // row = layout->rowCount(); m_widgetGroup = new WuQWidgetObjectGroup(this); m_widgetGroup->add(m_leftSurfaceViewController->getWidget()); m_widgetGroup->add(m_rightSurfaceViewController->getWidget()); m_widgetGroup->add(m_cerebellumSurfaceViewController->getWidget()); m_widgetGroup->add(m_leftSurfaceCheckBox); m_widgetGroup->add(m_rightSurfaceCheckBox); m_widgetGroup->add(m_cerebellumSurfaceCheckBox); setFixedHeight(sizeHint().height()); } /** * Destructor. */ SurfaceMontageFlatMapsComponent::~SurfaceMontageFlatMapsComponent() { } /** * Update the flat maps montage options widget. * * @param browserTabContent * The active model display controller (may be NULL). */ void SurfaceMontageFlatMapsComponent::updateContent(BrowserTabContent* browserTabContent) { m_widgetGroup->blockAllSignals(true); const int32_t tabIndex = browserTabContent->getTabNumber(); ModelSurfaceMontage* msm = browserTabContent->getDisplayedSurfaceMontageModel(); SurfaceMontageConfigurationFlatMaps* smcc = msm->getFlatMapsConfiguration(tabIndex); m_leftSurfaceCheckBox->setChecked(smcc->isLeftEnabled()); m_rightSurfaceCheckBox->setChecked(smcc->isRightEnabled()); m_cerebellumSurfaceCheckBox->setChecked(smcc->isCerebellumEnabled()); m_leftSurfaceViewController->updateControl(smcc->getLeftSurfaceSelectionModel()); m_rightSurfaceViewController->updateControl(smcc->getRightSurfaceSelectionModel()); m_cerebellumSurfaceViewController->updateControl(smcc->getCerebellumSurfaceSelectionModel()); m_widgetGroup->blockAllSignals(false); } /** * Called when flat left surface is selected. * @param surface * Surface that was selected. */ void SurfaceMontageFlatMapsComponent::leftSurfaceSelected(Surface* surface) { if (surface != NULL) { BrowserTabContent* btc = m_parentToolBarMontage->getTabContentFromSelectedTab(); const int32_t tabIndex = btc->getTabNumber(); ModelSurfaceMontage* msm = btc->getDisplayedSurfaceMontageModel(); SurfaceMontageConfigurationFlatMaps* smcc = msm->getFlatMapsConfiguration(tabIndex); smcc->getLeftSurfaceSelectionModel()->setSurface(surface); m_parentToolBarMontage->invalidateColoringAndUpdateGraphicsWindow(); } } /** * Called when flat right surface is selected. * @param surface * Surface that was selected. */ void SurfaceMontageFlatMapsComponent::rightSurfaceSelected(Surface* surface) { if (surface != NULL) { BrowserTabContent* btc = m_parentToolBarMontage->getTabContentFromSelectedTab(); const int32_t tabIndex = btc->getTabNumber(); ModelSurfaceMontage* msm = btc->getDisplayedSurfaceMontageModel(); SurfaceMontageConfigurationFlatMaps* smcc = msm->getFlatMapsConfiguration(tabIndex); smcc->getRightSurfaceSelectionModel()->setSurface(surface); m_parentToolBarMontage->invalidateColoringAndUpdateGraphicsWindow(); } } /** * Called when flat cerebellum surface is selected. * @param surface * Surface that was selected. */ void SurfaceMontageFlatMapsComponent::cerebellumSurfaceSelected(Surface* surface) { if (surface != NULL) { BrowserTabContent* btc = m_parentToolBarMontage->getTabContentFromSelectedTab(); const int32_t tabIndex = btc->getTabNumber(); ModelSurfaceMontage* msm = btc->getDisplayedSurfaceMontageModel(); SurfaceMontageConfigurationFlatMaps* smcc = msm->getFlatMapsConfiguration(tabIndex); smcc->getCerebellumSurfaceSelectionModel()->setSurface(surface); m_parentToolBarMontage->invalidateColoringAndUpdateGraphicsWindow(); } } /** * Called when flat maps surface montage checkbox is toggled. * @param status * New status of check box. */ void SurfaceMontageFlatMapsComponent::checkBoxSelected(bool /*status*/) { BrowserTabContent* btc = m_parentToolBarMontage->getTabContentFromSelectedTab(); const int32_t tabIndex = btc->getTabNumber(); ModelSurfaceMontage* msm = btc->getDisplayedSurfaceMontageModel(); SurfaceMontageConfigurationFlatMaps* smcc = msm->getFlatMapsConfiguration(tabIndex); smcc->setLeftEnabled(m_leftSurfaceCheckBox->isChecked()); smcc->setRightEnabled(m_rightSurfaceCheckBox->isChecked()); smcc->setCerebellumEnabled(m_cerebellumSurfaceCheckBox->isChecked()); m_parentToolBarMontage->updateUserInterface(); m_parentToolBarMontage->invalidateColoringAndUpdateGraphicsWindow(); } workbench-1.1.1/src/GuiQt/BrainBrowserWindowToolBarSurfaceMontage.h000066400000000000000000000147731255417355300254110ustar00rootroot00000000000000#ifndef __BRAIN_BROWSER_WINDOW_TOOL_BAR_SURFACE_MONTAGE_H__ #define __BRAIN_BROWSER_WINDOW_TOOL_BAR_SURFACE_MONTAGE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "BrainBrowserWindowToolBarComponent.h" class QCheckBox; class QStackedWidget; namespace caret { class BrowserTabContent; class EnumComboBoxTemplate; class Surface; class SurfaceMontageCerebellarComponent; class SurfaceMontageCerebralComponent; class SurfaceMontageFlatMapsComponent; class SurfaceSelectionViewController; class BrainBrowserWindowToolBarSurfaceMontage : public BrainBrowserWindowToolBarComponent { Q_OBJECT public: BrainBrowserWindowToolBarSurfaceMontage(BrainBrowserWindowToolBar* parentToolBar); virtual ~BrainBrowserWindowToolBarSurfaceMontage(); virtual void updateContent(BrowserTabContent* browserTabContent); private slots: void surfaceMontageConfigurationTypeEnumComboBoxItemActivated(); void surfaceMontageLayoutOrientationEnumComboBoxItemActivated(); private: BrainBrowserWindowToolBarSurfaceMontage(const BrainBrowserWindowToolBarSurfaceMontage&); BrainBrowserWindowToolBarSurfaceMontage& operator=(const BrainBrowserWindowToolBarSurfaceMontage&); BrainBrowserWindowToolBar* m_parentToolBar; SurfaceMontageCerebralComponent* m_cerebralComponent; SurfaceMontageCerebellarComponent* m_cerebellarComponent; SurfaceMontageFlatMapsComponent* m_flatMapsComponent; QStackedWidget* m_stackedWidget; EnumComboBoxTemplate* m_surfaceMontageConfigurationTypeEnumComboBox; EnumComboBoxTemplate* m_surfaceMontageLayoutOrientationEnumComboBox; // ADD_NEW_MEMBERS_HERE private slots: }; /* ===========================================================================*/ class SurfaceMontageCerebralComponent : public QWidget { Q_OBJECT public: SurfaceMontageCerebralComponent(BrainBrowserWindowToolBarSurfaceMontage* parentToolBarMontage); ~SurfaceMontageCerebralComponent(); void updateContent(BrowserTabContent* browserTabContent); private slots: void leftSurfaceSelected(Surface*); void leftSecondSurfaceSelected(Surface*); void rightSurfaceSelected(Surface*); void rightSecondSurfaceSelected(Surface*); void checkBoxSelected(bool); private: BrainBrowserWindowToolBarSurfaceMontage* m_parentToolBarMontage; SurfaceSelectionViewController* m_leftSurfaceViewController; SurfaceSelectionViewController* m_leftSecondSurfaceViewController; SurfaceSelectionViewController* m_rightSurfaceViewController; SurfaceSelectionViewController* m_rightSecondSurfaceViewController; QCheckBox* m_leftCheckBox; QCheckBox* m_rightCheckBox; QCheckBox* m_surfaceMontageFirstSurfaceCheckBox; QCheckBox* m_surfaceMontageSecondSurfaceCheckBox; QCheckBox* m_lateralCheckBox; QCheckBox* m_medialCheckBox; WuQWidgetObjectGroup* m_widgetGroup; }; /* ===========================================================================*/ class SurfaceMontageCerebellarComponent : public QWidget { Q_OBJECT public: SurfaceMontageCerebellarComponent(BrainBrowserWindowToolBarSurfaceMontage* parentToolBarMontage); ~SurfaceMontageCerebellarComponent(); void updateContent(BrowserTabContent* browserTabContent); private slots: void firstSurfaceSelected(Surface*); void secondSurfaceSelected(Surface*); void checkBoxSelected(bool); private: BrainBrowserWindowToolBarSurfaceMontage* m_parentToolBarMontage; SurfaceSelectionViewController* m_firstSurfaceViewController; SurfaceSelectionViewController* m_secondSurfaceViewController; QCheckBox* m_firstSurfaceCheckBox; QCheckBox* m_secondSurfaceCheckBox; QCheckBox* m_dorsalCheckBox; QCheckBox* m_ventralCheckBox; QCheckBox* m_anteriorCheckBox; QCheckBox* m_posteriorCheckBox; WuQWidgetObjectGroup* m_widgetGroup; }; /* ===========================================================================*/ class SurfaceMontageFlatMapsComponent : public QWidget { Q_OBJECT public: SurfaceMontageFlatMapsComponent(BrainBrowserWindowToolBarSurfaceMontage* parentToolBarMontage); ~SurfaceMontageFlatMapsComponent(); void updateContent(BrowserTabContent* browserTabContent); private slots: void leftSurfaceSelected(Surface*); void rightSurfaceSelected(Surface*); void cerebellumSurfaceSelected(Surface*); void checkBoxSelected(bool); private: BrainBrowserWindowToolBarSurfaceMontage* m_parentToolBarMontage; SurfaceSelectionViewController* m_leftSurfaceViewController; SurfaceSelectionViewController* m_rightSurfaceViewController; SurfaceSelectionViewController* m_cerebellumSurfaceViewController; QCheckBox* m_leftSurfaceCheckBox; QCheckBox* m_rightSurfaceCheckBox; QCheckBox* m_cerebellumSurfaceCheckBox; WuQWidgetObjectGroup* m_widgetGroup; }; #ifdef __BRAIN_BROWSER_WINDOW_TOOL_BAR_SURFACE_MONTAGE_DECLARE__ // #endif // __BRAIN_BROWSER_WINDOW_TOOL_BAR_SURFACE_MONTAGE_DECLARE__ } // namespace #endif //__BRAIN_BROWSER_WINDOW_TOOL_BAR_SURFACE_MONTAGE_H__ workbench-1.1.1/src/GuiQt/BrainBrowserWindowToolBarTab.cxx000066400000000000000000000076401255417355300235620ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __BRAIN_BROWSER_WINDOW_TOOL_BAR_TAB_DECLARE__ #include "BrainBrowserWindowToolBarTab.h" #undef __BRAIN_BROWSER_WINDOW_TOOL_BAR_TAB_DECLARE__ #include #include #include "BrainBrowserWindowToolBar.h" #include "BrowserTabContent.h" #include "CaretAssert.h" #include "EnumComboBoxTemplate.h" #include "GuiManager.h" #include "WuQtUtilities.h" #include "WuQWidgetObjectGroup.h" #include "YokingGroupEnum.h" using namespace caret; /** * \class caret::BrainBrowserWindowToolBarTab * \brief Tab section of Browser Window Toolbar. * \ingroup GuiQt */ /** * Constructor. */ BrainBrowserWindowToolBarTab::BrainBrowserWindowToolBarTab(const int32_t browserWindowIndex, BrainBrowserWindowToolBar* parentToolBar) : BrainBrowserWindowToolBarComponent(parentToolBar), m_browserWindowIndex(browserWindowIndex), m_parentToolBar(parentToolBar) { m_yokingGroupComboBox = new EnumComboBoxTemplate(this); m_yokingGroupComboBox->setup(); m_yokingGroupComboBox->getWidget()->setStatusTip("Select a yoking group (linked views)"); m_yokingGroupComboBox->getWidget()->setToolTip(("Select a yoking group (linked views).\n" "Models yoked to a group are displayed in the same view.\n" "Surface Yoking is applied to Surface, Surface Montage\n" "and Whole Brain. Volume Yoking is applied to Volumes.")); QLabel* yokeToLabel = new QLabel("Yoking:"); QObject::connect(m_yokingGroupComboBox, SIGNAL(itemActivated()), this, SLOT(yokeToGroupComboBoxIndexChanged())); QVBoxLayout* layout = new QVBoxLayout(this); WuQtUtilities::setLayoutSpacingAndMargins(layout, 4, 0); layout->addWidget(yokeToLabel); layout->addWidget(m_yokingGroupComboBox->getWidget()); addToWidgetGroup(yokeToLabel); addToWidgetGroup(m_yokingGroupComboBox->getWidget()); } /** * Destructor. */ BrainBrowserWindowToolBarTab::~BrainBrowserWindowToolBarTab() { } /** * Update the surface montage options widget. * * @param browserTabContent * The active model display controller (may be NULL). */ void BrainBrowserWindowToolBarTab::updateContent(BrowserTabContent* browserTabContent) { blockAllSignals(true); m_yokingGroupComboBox->setSelectedItem(browserTabContent->getYokingGroup()); blockAllSignals(false); } /** * Called when window yoke to tab combo box is selected. */ void BrainBrowserWindowToolBarTab::yokeToGroupComboBoxIndexChanged() { BrowserTabContent* browserTabContent = this->getTabContentFromSelectedTab(); if (browserTabContent == NULL) { return; } YokingGroupEnum::Enum yokingGroup = m_yokingGroupComboBox->getSelectedItem(); browserTabContent->setYokingGroup(yokingGroup); m_parentToolBar->updateToolBarComponents(browserTabContent); this->updateGraphicsWindow(); } workbench-1.1.1/src/GuiQt/BrainBrowserWindowToolBarTab.h000066400000000000000000000043171255417355300232050ustar00rootroot00000000000000#ifndef __BRAIN_BROWSER_WINDOW_TOOL_BAR_TAB_H__ #define __BRAIN_BROWSER_WINDOW_TOOL_BAR_TAB_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "BrainBrowserWindowToolBarComponent.h" namespace caret { class BrainBrowserWindowToolBar; class EnumComboBoxTemplate; class BrainBrowserWindowToolBarTab : public BrainBrowserWindowToolBarComponent { Q_OBJECT public: BrainBrowserWindowToolBarTab(const int32_t browserWindowIndex, BrainBrowserWindowToolBar* parentToolBar); virtual ~BrainBrowserWindowToolBarTab(); virtual void updateContent(BrowserTabContent* browserTabContent); // ADD_NEW_METHODS_HERE private slots: void yokeToGroupComboBoxIndexChanged(); private: BrainBrowserWindowToolBarTab(const BrainBrowserWindowToolBarTab&); BrainBrowserWindowToolBarTab& operator=(const BrainBrowserWindowToolBarTab&); EnumComboBoxTemplate* m_yokingGroupComboBox; const int32_t m_browserWindowIndex; BrainBrowserWindowToolBar* m_parentToolBar; // ADD_NEW_MEMBERS_HERE }; #ifdef __BRAIN_BROWSER_WINDOW_TOOL_BAR_TAB_DECLARE__ // #endif // __BRAIN_BROWSER_WINDOW_TOOL_BAR_TAB_DECLARE__ } // namespace #endif //__BRAIN_BROWSER_WINDOW_TOOL_BAR_TAB_H__ workbench-1.1.1/src/GuiQt/BrainBrowserWindowToolBarVolumeMontage.cxx000066400000000000000000000166101255417355300256330ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __BRAIN_BROWSER_WINDOW_TOOL_BAR_VOLUME_MONTAGE_DECLARE__ #include "BrainBrowserWindowToolBarVolumeMontage.h" #undef __BRAIN_BROWSER_WINDOW_TOOL_BAR_VOLUME_MONTAGE_DECLARE__ #include #include #include #include #include #include "BrowserTabContent.h" #include "CaretAssert.h" #include "WuQFactory.h" #include "WuQWidgetObjectGroup.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::BrainBrowserWindowToolBarVolumeMontage * \brief Toolbar component for volume montage slice selections * \ingroup GuiQt * * */ /** * Constructor. */ BrainBrowserWindowToolBarVolumeMontage::BrainBrowserWindowToolBarVolumeMontage(BrainBrowserWindowToolBar* parentToolBar) : BrainBrowserWindowToolBarComponent(parentToolBar), m_parentToolBar(parentToolBar) { QLabel* rowsLabel = new QLabel("Rows:"); QLabel* columnsLabel = new QLabel("Cols:"); QLabel* spacingLabel = new QLabel("Step:"); const int spinBoxWidth = 48; m_montageRowsSpinBox = WuQFactory::newSpinBox(); m_montageRowsSpinBox->setRange(1, 20); m_montageRowsSpinBox->setMaximumWidth(spinBoxWidth); WuQtUtilities::setToolTipAndStatusTip(m_montageRowsSpinBox, "Select the number of rows in montage of volume slices"); QObject::connect(m_montageRowsSpinBox, SIGNAL(valueChanged(int)), this, SLOT(montageRowsSpinBoxValueChanged(int))); m_montageColumnsSpinBox = WuQFactory::newSpinBox(); m_montageColumnsSpinBox->setRange(1, 20); m_montageColumnsSpinBox->setMaximumWidth(spinBoxWidth); WuQtUtilities::setToolTipAndStatusTip(m_montageColumnsSpinBox, "Select the number of columns in montage of volume slices"); QObject::connect(m_montageColumnsSpinBox, SIGNAL(valueChanged(int)), this, SLOT(montageColumnsSpinBoxValueChanged(int))); m_montageSpacingSpinBox = WuQFactory::newSpinBox(); m_montageSpacingSpinBox->setRange(1, 2500); m_montageSpacingSpinBox->setMaximumWidth(spinBoxWidth); WuQtUtilities::setToolTipAndStatusTip(m_montageSpacingSpinBox, "Select the number of slices stepped (incremented) between displayed montage slices"); QObject::connect(m_montageSpacingSpinBox, SIGNAL(valueChanged(int)), this, SLOT(montageSpacingSpinBoxValueChanged(int))); m_montageEnabledAction = WuQtUtilities::createAction("On", "View a montage of parallel slices", this, this, SLOT(montageEnabledActionToggled(bool))); m_montageEnabledAction->setCheckable(true); QToolButton* montageEnabledToolButton = new QToolButton(); montageEnabledToolButton->setDefaultAction(m_montageEnabledAction); QGridLayout* gridLayout = new QGridLayout(this); WuQtUtilities::setLayoutSpacingAndMargins(gridLayout, 0, 0); gridLayout->setVerticalSpacing(2); gridLayout->addWidget(rowsLabel, 0, 0); gridLayout->addWidget(m_montageRowsSpinBox, 0, 1); gridLayout->addWidget(columnsLabel, 1, 0); gridLayout->addWidget(m_montageColumnsSpinBox, 1, 1); gridLayout->addWidget(spacingLabel, 2, 0); gridLayout->addWidget(m_montageSpacingSpinBox, 2, 1); gridLayout->addWidget(montageEnabledToolButton, 3, 0, 1, 2, Qt::AlignHCenter); m_volumeMontageWidgetGroup = new WuQWidgetObjectGroup(this); m_volumeMontageWidgetGroup->add(m_montageRowsSpinBox); m_volumeMontageWidgetGroup->add(m_montageColumnsSpinBox); m_volumeMontageWidgetGroup->add(m_montageSpacingSpinBox); m_volumeMontageWidgetGroup->add(m_montageEnabledAction); } /** * Destructor. */ BrainBrowserWindowToolBarVolumeMontage::~BrainBrowserWindowToolBarVolumeMontage() { } /** * Update the surface montage options widget. * * @param browserTabContent * The active model display controller (may be NULL). */ void BrainBrowserWindowToolBarVolumeMontage::updateContent(BrowserTabContent* browserTabContent) { m_volumeMontageWidgetGroup->blockAllSignals(true); switch (browserTabContent->getSliceDrawingType()) { case VolumeSliceDrawingTypeEnum::VOLUME_SLICE_DRAW_MONTAGE: m_montageEnabledAction->setChecked(true); break; case VolumeSliceDrawingTypeEnum::VOLUME_SLICE_DRAW_SINGLE: m_montageEnabledAction->setChecked(false); break; } m_montageRowsSpinBox->setValue(browserTabContent->getMontageNumberOfRows()); m_montageColumnsSpinBox->setValue(browserTabContent->getMontageNumberOfColumns()); m_montageSpacingSpinBox->setValue(browserTabContent->getMontageSliceSpacing()); m_volumeMontageWidgetGroup->blockAllSignals(false); } /** * Called when montage enabled button toggled. */ void BrainBrowserWindowToolBarVolumeMontage::montageEnabledActionToggled(bool) { VolumeSliceDrawingTypeEnum::Enum drawingType = VolumeSliceDrawingTypeEnum::VOLUME_SLICE_DRAW_SINGLE; if (m_montageEnabledAction->isChecked()) { drawingType = VolumeSliceDrawingTypeEnum::VOLUME_SLICE_DRAW_MONTAGE; } BrowserTabContent* btc = this->getTabContentFromSelectedTab(); btc->setSliceDrawingType(drawingType); this->updateGraphicsWindow(); this->updateOtherYokedWindows(); } /** * Called when montage rows spin box value is changed. */ void BrainBrowserWindowToolBarVolumeMontage::montageRowsSpinBoxValueChanged(int /*i*/) { BrowserTabContent* btc = this->getTabContentFromSelectedTab(); btc->setMontageNumberOfRows(m_montageRowsSpinBox->value()); this->updateGraphicsWindow(); this->updateOtherYokedWindows(); } /** * Called when montage columns spin box value is changed. */ void BrainBrowserWindowToolBarVolumeMontage::montageColumnsSpinBoxValueChanged(int /*i*/) { BrowserTabContent* btc = this->getTabContentFromSelectedTab(); btc->setMontageNumberOfColumns(m_montageColumnsSpinBox->value()); this->updateGraphicsWindow(); this->updateOtherYokedWindows(); } /** * Called when montage spacing spin box value is changed. */ void BrainBrowserWindowToolBarVolumeMontage::montageSpacingSpinBoxValueChanged(int /*i*/) { BrowserTabContent* btc = this->getTabContentFromSelectedTab(); btc->setMontageSliceSpacing(m_montageSpacingSpinBox->value()); this->updateGraphicsWindow(); this->updateOtherYokedWindows(); } workbench-1.1.1/src/GuiQt/BrainBrowserWindowToolBarVolumeMontage.h000066400000000000000000000050061255417355300252550ustar00rootroot00000000000000#ifndef __BRAIN_BROWSER_WINDOW_TOOL_BAR_VOLUME_MONTAGE_H__ #define __BRAIN_BROWSER_WINDOW_TOOL_BAR_VOLUME_MONTAGE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "BrainBrowserWindowToolBarComponent.h" class QSpinBox; namespace caret { class WuQWidgetObjectGroup; class BrainBrowserWindowToolBarVolumeMontage : public BrainBrowserWindowToolBarComponent { Q_OBJECT public: BrainBrowserWindowToolBarVolumeMontage(BrainBrowserWindowToolBar* parentToolBar); virtual ~BrainBrowserWindowToolBarVolumeMontage(); virtual void updateContent(BrowserTabContent* browserTabContent); // ADD_NEW_METHODS_HERE private slots: void montageRowsSpinBoxValueChanged(int i); void montageColumnsSpinBoxValueChanged(int i); void montageSpacingSpinBoxValueChanged(int i); void montageEnabledActionToggled(bool); private: BrainBrowserWindowToolBarVolumeMontage(const BrainBrowserWindowToolBarVolumeMontage&); BrainBrowserWindowToolBarVolumeMontage& operator=(const BrainBrowserWindowToolBarVolumeMontage&); BrainBrowserWindowToolBar* m_parentToolBar; QSpinBox* m_montageRowsSpinBox; QSpinBox* m_montageColumnsSpinBox; QSpinBox* m_montageSpacingSpinBox; QAction* m_montageEnabledAction; WuQWidgetObjectGroup* m_volumeMontageWidgetGroup; // ADD_NEW_MEMBERS_HERE }; #ifdef __BRAIN_BROWSER_WINDOW_TOOL_BAR_VOLUME_MONTAGE_DECLARE__ // #endif // __BRAIN_BROWSER_WINDOW_TOOL_BAR_VOLUME_MONTAGE_DECLARE__ } // namespace #endif //__BRAIN_BROWSER_WINDOW_TOOL_BAR_VOLUME_MONTAGE_H__ workbench-1.1.1/src/GuiQt/BrainOpenGLWidget.cxx000066400000000000000000001564401255417355300213300ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #include #define __BRAIN_OPENGL_WIDGET_DEFINE__ #include "BrainOpenGLWidget.h" #undef __BRAIN_OPENGL_WIDGET_DEFINE__ #include "Border.h" #include "Brain.h" #include "BrainOpenGLFixedPipeline.h" #include "BrainOpenGLShape.h" #include "BrainOpenGLWidgetContextMenu.h" #include "BrainOpenGLWidgetTextRenderer.h" #include "BrainOpenGLViewportContent.h" #include "BrainStructure.h" #include "BrowserTabContent.h" #include "CaretAssert.h" #include "CaretLogger.h" #include "CaretPreferences.h" #include "CursorManager.h" #include "DummyFontTextRenderer.h" #include "EventBrainReset.h" #include "EventImageCapture.h" #include "EventModelGetAll.h" #include "EventManager.h" #include "EventBrowserWindowContentGet.h" #include "EventBrowserWindowGraphicsRedrawn.h" #include "EventGraphicsUpdateAllWindows.h" #include "EventGraphicsUpdateOneWindow.h" #include "EventGetOrSetUserInputModeProcessor.h" #include "EventUserInterfaceUpdate.h" #include "FtglFontTextRenderer.h" #include "GuiManager.h" #include "MathFunctions.h" #include "Matrix4x4.h" #include "Model.h" #include "MouseEvent.h" #include "SelectionManager.h" #include "SelectionItemSurfaceNode.h" #include "SelectionItemVoxelEditing.h" #include "SessionManager.h" #include "Surface.h" #include "TileTabsConfiguration.h" #include "UserInputModeBorders.h" #include "UserInputModeFoci.h" #include "UserInputModeView.h" #include "UserInputModeVolumeEdit.h" using namespace caret; /** * Constructor. * * @param * The parent widget. */ BrainOpenGLWidget::BrainOpenGLWidget(QWidget* parent, const int32_t windowIndex) : QGLWidget(parent) { this->openGL = NULL; this->borderBeingDrawn = new Border(); this->textRenderer = NULL; /* * Create a FTGL font renderer */ if (this->textRenderer == NULL){ this->textRenderer = new FtglFontTextRenderer(); if ( ! this->textRenderer->isValid()) { CaretLogWarning("Failed to create FTGL text renderer."); delete this->textRenderer; this->textRenderer = NULL; } } /* * If creating previous renderer failed, use QT for text. */ if (this->textRenderer == NULL){ this->textRenderer = new BrainOpenGLWidgetTextRenderer(this); if ( ! this->textRenderer->isValid()) { delete this->textRenderer; this->textRenderer = NULL; CaretLogWarning("Failed to create QT GL text renderer."); } } if (this->textRenderer == NULL) { CaretLogSevere("Unable to create a text renderer for OpenGL."); this->textRenderer = new DummyFontTextRenderer(); } this->windowIndex = windowIndex; this->userInputBordersModeProcessor = new UserInputModeBorders(this->borderBeingDrawn, windowIndex); this->userInputFociModeProcessor = new UserInputModeFoci(windowIndex); this->userInputVolumeEditModeProcessor = new UserInputModeVolumeEdit(windowIndex); this->userInputViewModeProcessor = new UserInputModeView(); this->selectedUserInputProcessor = this->userInputViewModeProcessor; this->selectedUserInputProcessor->initialize(); this->mousePressX = -10000; this->mousePressY = -10000; this->mouseNewDraggingStartedFlag = false; EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_BRAIN_RESET); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_GRAPHICS_UPDATE_ALL_WINDOWS); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_GRAPHICS_UPDATE_ONE_WINDOW); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_GET_OR_SET_USER_INPUT_MODE); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_IMAGE_CAPTURE); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_USER_INTERFACE_UPDATE); } /** * Destructor. */ BrainOpenGLWidget::~BrainOpenGLWidget() { makeCurrent(); this->clearDrawingViewportContents(); if (this->textRenderer != NULL) { delete this->textRenderer; this->textRenderer = NULL; } if (this->openGL != NULL) { delete this->openGL; this->openGL = NULL; } delete this->userInputViewModeProcessor; delete this->userInputBordersModeProcessor; delete this->userInputFociModeProcessor; delete this->userInputVolumeEditModeProcessor; this->selectedUserInputProcessor = NULL; // DO NOT DELETE since it does not own the object to which it points delete this->borderBeingDrawn; EventManager::get()->removeAllEventsFromListener(this); } /** * Initializes graphics. */ void BrainOpenGLWidget::initializeGL() { if (this->openGL == NULL) { this->openGL = new BrainOpenGLFixedPipeline(this->textRenderer); //GuiManager::get()->getBrainOpenGL(); } this->openGL->initializeOpenGL(); this->lastMouseX = 0; this->lastMouseY = 0; this->isMousePressedNearToolBox = false; this->setFocusPolicy(Qt::StrongFocus); QGLFormat format = this->format(); AString msg = ("OpenGL Context:" "\n Accum: " + AString::fromBool(format.accum()) + "\n Accum size: " + AString::number(format.accumBufferSize()) + "\n Alpha: " + AString::fromBool(format.alpha()) + "\n Alpha size: " + AString::number(format.alphaBufferSize()) + "\n Depth: " + AString::fromBool(format.depth()) + "\n Depth size: " + AString::number(format.depthBufferSize()) + "\n Direct Rendering: " + AString::fromBool(format.directRendering()) + "\n Red size: " + AString::number(format.redBufferSize()) + "\n Green size: " + AString::number(format.greenBufferSize()) + "\n Blue size: " + AString::number(format.blueBufferSize()) + "\n Double Buffer: " + AString::fromBool(format.doubleBuffer()) + "\n RGBA: " + AString::fromBool(format.rgba()) + "\n Samples: " + AString::fromBool(format.sampleBuffers()) + "\n Samples size: " + AString::number(format.samples()) + "\n Stencil: " + AString::fromBool(format.stencil()) + "\n Stencil size: " + AString::number(format.stencilBufferSize()) + "\n Swap Interval: " + AString::number(format.swapInterval()) + "\n Stereo: " + AString::fromBool(format.stereo()) + "\n Major Version: " + AString::number(format.majorVersion()) + "\n Minor Version: " + AString::number(format.minorVersion())); msg += ("\n\n" + this->openGL->getOpenGLInformation()); CaretLogConfig(msg); if (m_openGLVersionInformation.isEmpty()) { m_openGLVersionInformation = msg; } if (s_defaultGLFormatInitialized == false) { CaretLogSevere("PROGRAM ERROR: The default QGLFormat has not been set.\n" "Need to call BrainOpenGLWidget::initializeDefaultGLFormat() prior to " "instantiating an instance of this class."); } } /** * @return Information about OpenGL. */ QString BrainOpenGLWidget::getOpenGLInformation() { AString info = m_openGLVersionInformation; #if BRAIN_OPENGL_INFO_SUPPORTS_DISPLAY_LISTS int32_t numDisplayLists = 0; for (GLuint iList = 1; iList < 1000; iList++) { if (glIsList(iList)) { numDisplayLists++; } } info += ("\nAt least " + AString::number(numDisplayLists) + " display lists are allocated in first OpenGL context."); #endif // BRAIN_OPENGL_INFO_SUPPORTS_DISPLAY_LISTS #ifdef BRAIN_OPENGL_INFO_SUPPORTS_VERTEX_BUFFERS int32_t numVertexBuffers = 0; for (GLuint iBuff = 1; iBuff < 1000; iBuff++) { if (glIsBuffer(iBuff)) { numVertexBuffers++; } } info += ("\nAt least " + AString::number(numVertexBuffers) + " vertex buffers are allocated"); #endif // BRAIN_OPENGL_INFO_SUPPORTS_VERTEX_BUFFERS return info; } /** * Called when widget is resized. */ void BrainOpenGLWidget::resizeGL(int w, int h) { this->windowWidth[this->windowIndex] = w; this->windowHeight[this->windowIndex] = h; } void BrainOpenGLWidget::getViewPortSize(int &w, int &h) { w = this->windowWidth[this->windowIndex]; h = this->windowHeight[this->windowIndex]; } /** * @return Pointer to the border that is being drawn. */ Border* BrainOpenGLWidget::getBorderBeingDrawn() { return this->borderBeingDrawn; } /** * Clear the contents for drawing into the viewports. */ void BrainOpenGLWidget::clearDrawingViewportContents() { const int32_t num = static_cast(this->drawingViewportContents.size()); for (int32_t i = 0; i < num; i++) { delete this->drawingViewportContents[i]; } this->drawingViewportContents.clear(); } ///** // * Paints the graphics. // */ //void //BrainOpenGLWidget::paintGL() //{ // /* // * Set the cursor to that requested by the user input processor // */ // CursorEnum::Enum cursor = this->selectedUserInputProcessor->getCursor(); // // GuiManager::get()->getCursorManager()->setCursorForWidget(this, // cursor); // // this->clearDrawingViewportContents(); // // int windowViewport[4] = { // 0, // 0, // this->windowWidth[this->windowIndex], // this->windowHeight[this->windowIndex] // }; // // EventBrowserWindowContentGet getModelEvent(this->windowIndex); // EventManager::get()->sendEvent(getModelEvent.getPointer()); // // if (getModelEvent.isError()) { // return; // } // // /* // * Highlighting of border points // */ // this->openGL->setDrawHighlightedEndPoints(false); // if (this->selectedUserInputProcessor == this->userInputBordersModeProcessor) { // this->openGL->setDrawHighlightedEndPoints(this->userInputBordersModeProcessor->isHighlightBorderEndPoints()); // } // // const int32_t numToDraw = getModelEvent.getNumberOfItemsToDraw(); // if (numToDraw == 1) { // BrainOpenGLViewportContent* vc = new BrainOpenGLViewportContent(windowViewport, // windowViewport, // false, // GuiManager::get()->getBrain(), // getModelEvent.getTabContentToDraw(0)); // this->drawingViewportContents.push_back(vc); // } // else if (numToDraw > 1) { // int32_t numRows = 0; // int32_t numCols = 0; // // const int32_t windowWidth = this->windowWidth[this->windowIndex]; // const int32_t windowHeight = this->windowHeight[this->windowIndex]; // // std::vector rowHeights; // std::vector columnsWidths; // // /* // * Determine if default configuration for tiles // */ // TileTabsConfiguration* tileTabsConfiguration = getModelEvent.getTileTabsConfiguration(); // CaretAssert(tileTabsConfiguration); // // /* // * NOTE: When computing widths and heights, do not round. // * Rounding may cause the bottom most row or column to extend // * outside the graphics region. Shrinking the last row or // * column is not desired since it might cause the last model // * to be drawn slightly smaller than the others. // */ // if (tileTabsConfiguration->isDefaultConfiguration()) { // /* // * Update number of rows/columns in the default configuration // * so that if a scene is saved, the correct number of rows // * and columns are saved to the scene. // */ // tileTabsConfiguration->updateDefaultConfigurationRowsAndColumns(numToDraw); // numRows = tileTabsConfiguration->getNumberOfRows(); // numCols = tileTabsConfiguration->getNumberOfColumns(); // // for (int32_t i = 0; i < numRows; i++) { // rowHeights.push_back(windowHeight / numRows); // } // for (int32_t i = 0; i < numCols; i++) { // columnsWidths.push_back(windowWidth / numCols); // } // } // else { // /* // * Rows/columns from user configuration // */ // numRows = tileTabsConfiguration->getNumberOfRows(); // numCols = tileTabsConfiguration->getNumberOfColumns(); // // /* // * Determine height of each row // */ // float rowStretchTotal = 0.0; // for (int32_t i = 0; i < numRows; i++) { // rowStretchTotal += tileTabsConfiguration->getRowStretchFactor(i); // } // CaretAssert(rowStretchTotal > 0.0); // for (int32_t i = 0; i < numRows; i++) { // const int32_t h = static_cast((tileTabsConfiguration->getRowStretchFactor(i) / rowStretchTotal) // * windowHeight); // // rowHeights.push_back(h); // } // // /* // * Determine width of each column // */ // float columnStretchTotal = 0.0; // for (int32_t i = 0; i < numCols; i++) { // columnStretchTotal += tileTabsConfiguration->getColumnStretchFactor(i); // } // CaretAssert(columnStretchTotal > 0.0); // for (int32_t i = 0; i < numCols; i++) { // const int32_t w = static_cast((tileTabsConfiguration->getColumnStretchFactor(i) / columnStretchTotal) // * windowWidth); // columnsWidths.push_back(w); // } // } // // CaretAssert(numCols == static_cast(columnsWidths.size())); // CaretAssert(numRows == static_cast(rowHeights.size())); // // /* // * Verify all rows fit within the window // */ // int32_t rowHeightsSum = 0; // for (int32_t i = 0; i < numRows; i++) { // rowHeightsSum += rowHeights[i]; // } // if (rowHeightsSum > windowHeight) { // CaretLogSevere("PROGRAM ERROR: Tile Tabs total row heights exceed window height"); // rowHeights[numRows - 1] -= (rowHeightsSum - windowHeight); // } // // /* // * Adjust width of last column so that it does not extend beyond viewport // */ // int32_t columnWidthsSum = 0; // for (int32_t i = 0; i < numCols; i++) { // columnWidthsSum += columnsWidths[i]; // } // if (columnWidthsSum > windowWidth) { // CaretLogSevere("PROGRAM ERROR: Tile Tabs total row heights exceed window height"); // columnsWidths[numCols - 1] = columnWidthsSum - windowWidth; // } // // CaretLogFiner("Tile Tabs Row Heights: " // + AString::fromNumbers(rowHeights, ", ")); // CaretLogFiner("Tile Tabs Column Widths: " // + AString::fromNumbers(columnsWidths, ", ")); // // /* // * Arrange models left-to-right and top-to-bottom. // */ // int32_t vpX = 0; // int32_t vpY = this->windowHeight[this->windowIndex]; // // int32_t iModel = 0; // for (int32_t i = 0; i < numRows; i++) { // const int32_t vpHeight = rowHeights[i]; // vpX = 0; // vpY -= vpHeight; // for (int32_t j = 0; j < numCols; j++) { // const int32_t vpWidth = columnsWidths[j]; // if (iModel < numToDraw) { // const int modelViewport[4] = { // vpX, // vpY, // vpWidth, // vpHeight // }; // // BrowserTabContent* tabContent = getModelEvent.getTabContentToDraw(iModel); // const bool highlightTab = (getModelEvent.getTabIndexForTileTabsHighlighting() == tabContent->getTabNumber()); // BrainOpenGLViewportContent* vc = // new BrainOpenGLViewportContent(modelViewport, // modelViewport, // highlightTab, // GuiManager::get()->getBrain(), // tabContent); // this->drawingViewportContents.push_back(vc); // } // iModel++; // vpX += vpWidth; // // if (iModel >= numToDraw) { // /* // * More cells than models for drawing so set loop // * indices so that loops terminate // */ // j = numCols; // i = numRows; // } // } // } // } // // if (this->selectedUserInputProcessor == userInputBordersModeProcessor) { // this->openGL->setBorderBeingDrawn(this->borderBeingDrawn); // } // else { // this->openGL->setBorderBeingDrawn(NULL); // } // this->openGL->drawModels(this->drawingViewportContents); // // /* // * Issue browser window redrawn event // */ // BrainBrowserWindow* bbw = GuiManager::get()->getBrowserWindowByWindowIndex(this->windowIndex); // if (bbw != NULL) { // EventManager::get()->sendEvent(EventBrowserWindowGraphicsRedrawn(bbw).getPointer()); // } //} /** * Paints the graphics. */ void BrainOpenGLWidget::paintGL() { /* * Set the cursor to that requested by the user input processor */ CursorEnum::Enum cursor = this->selectedUserInputProcessor->getCursor(); GuiManager::get()->getCursorManager()->setCursorForWidget(this, cursor); this->clearDrawingViewportContents(); int windowViewport[4] = { 0, 0, this->windowWidth[this->windowIndex], this->windowHeight[this->windowIndex] }; EventBrowserWindowContentGet getModelEvent(this->windowIndex); EventManager::get()->sendEvent(getModelEvent.getPointer()); if (getModelEvent.isError()) { return; } /* * Highlighting of border points */ this->openGL->setDrawHighlightedEndPoints(false); if (this->selectedUserInputProcessor == this->userInputBordersModeProcessor) { this->openGL->setDrawHighlightedEndPoints(this->userInputBordersModeProcessor->isHighlightBorderEndPoints()); } const int32_t numToDraw = getModelEvent.getNumberOfItemsToDraw(); if (numToDraw == 1) { BrainOpenGLViewportContent* vc = new BrainOpenGLViewportContent(windowViewport, windowViewport, false, GuiManager::get()->getBrain(), getModelEvent.getTabContentToDraw(0)); this->drawingViewportContents.push_back(vc); } else if (numToDraw > 1) { //int32_t numRows = 0; //int32_t numCols = 0; const int32_t windowWidth = this->windowWidth[this->windowIndex]; const int32_t windowHeight = this->windowHeight[this->windowIndex]; std::vector rowHeights; std::vector columnsWidths; /* * Determine if default configuration for tiles */ TileTabsConfiguration* tileTabsConfiguration = getModelEvent.getTileTabsConfiguration(); CaretAssert(tileTabsConfiguration); // /* // * NOTE: When computing widths and heights, do not round. // * Rounding may cause the bottom most row or column to extend // * outside the graphics region. Shrinking the last row or // * column is not desired since it might cause the last model // * to be drawn slightly smaller than the others. // */ // if (tileTabsConfiguration->isDefaultConfiguration()) { // /* // * Update number of rows/columns in the default configuration // * so that if a scene is saved, the correct number of rows // * and columns are saved to the scene. // */ // tileTabsConfiguration->updateDefaultConfigurationRowsAndColumns(numToDraw); // numRows = tileTabsConfiguration->getNumberOfRows(); // numCols = tileTabsConfiguration->getNumberOfColumns(); // // for (int32_t i = 0; i < numRows; i++) { // rowHeights.push_back(windowHeight / numRows); // } // for (int32_t i = 0; i < numCols; i++) { // columnsWidths.push_back(windowWidth / numCols); // } // } // else { // /* // * Rows/columns from user configuration // */ // numRows = tileTabsConfiguration->getNumberOfRows(); // numCols = tileTabsConfiguration->getNumberOfColumns(); // // /* // * Determine height of each row // */ // float rowStretchTotal = 0.0; // for (int32_t i = 0; i < numRows; i++) { // rowStretchTotal += tileTabsConfiguration->getRowStretchFactor(i); // } // CaretAssert(rowStretchTotal > 0.0); // for (int32_t i = 0; i < numRows; i++) { // const int32_t h = static_cast((tileTabsConfiguration->getRowStretchFactor(i) / rowStretchTotal) // * windowHeight); // // rowHeights.push_back(h); // } // // /* // * Determine width of each column // */ // float columnStretchTotal = 0.0; // for (int32_t i = 0; i < numCols; i++) { // columnStretchTotal += tileTabsConfiguration->getColumnStretchFactor(i); // } // CaretAssert(columnStretchTotal > 0.0); // for (int32_t i = 0; i < numCols; i++) { // const int32_t w = static_cast((tileTabsConfiguration->getColumnStretchFactor(i) / columnStretchTotal) // * windowWidth); // columnsWidths.push_back(w); // } // } // // CaretAssert(numCols == static_cast(columnsWidths.size())); // CaretAssert(numRows == static_cast(rowHeights.size())); // // /* // * Verify all rows fit within the window // */ // int32_t rowHeightsSum = 0; // for (int32_t i = 0; i < numRows; i++) { // rowHeightsSum += rowHeights[i]; // } // if (rowHeightsSum > windowHeight) { // CaretLogSevere("PROGRAM ERROR: Tile Tabs total row heights exceed window height"); // rowHeights[numRows - 1] -= (rowHeightsSum - windowHeight); // } // // /* // * Adjust width of last column so that it does not extend beyond viewport // */ // int32_t columnWidthsSum = 0; // for (int32_t i = 0; i < numCols; i++) { // columnWidthsSum += columnsWidths[i]; // } // if (columnWidthsSum > windowWidth) { // CaretLogSevere("PROGRAM ERROR: Tile Tabs total row heights exceed window height"); // columnsWidths[numCols - 1] = columnWidthsSum - windowWidth; // } // // CaretLogFiner("Tile Tabs Row Heights: " // + AString::fromNumbers(rowHeights, ", ")); // CaretLogFiner("Tile Tabs Column Widths: " // + AString::fromNumbers(columnsWidths, ", ")); /* * Get the sizes of the tab tiles from the tile tabs configuration */ if ( ! tileTabsConfiguration->getRowHeightsAndColumnWidthsForWindowSize(windowWidth, windowHeight, numToDraw, rowHeights, columnsWidths)) { CaretLogSevere("Tile Tabs Row/Column sizing failed !!!"); return; } // numRows = static_cast(rowHeights.size()); // numCols = static_cast(columnsWidths.size()); // // /* // * Arrange models left-to-right and top-to-bottom. // */ // int32_t vpX = 0; // int32_t vpY = this->windowHeight[this->windowIndex]; // // int32_t iModel = 0; // for (int32_t i = 0; i < numRows; i++) { // const int32_t vpHeight = rowHeights[i]; // vpX = 0; // vpY -= vpHeight; // for (int32_t j = 0; j < numCols; j++) { // const int32_t vpWidth = columnsWidths[j]; // if (iModel < numToDraw) { // const int modelViewport[4] = { // vpX, // vpY, // vpWidth, // vpHeight // }; // // BrowserTabContent* tabContent = getModelEvent.getTabContentToDraw(iModel); // const bool highlightTab = (getModelEvent.getTabIndexForTileTabsHighlighting() == tabContent->getTabNumber()); // BrainOpenGLViewportContent* vc = // new BrainOpenGLViewportContent(modelViewport, // modelViewport, // highlightTab, // GuiManager::get()->getBrain(), // tabContent); // this->drawingViewportContents.push_back(vc); // } // iModel++; // vpX += vpWidth; // // if (iModel >= numToDraw) { // /* // * More cells than models for drawing so set loop // * indices so that loops terminate // */ // j = numCols; // i = numRows; // } // } // } /* * Create the viewport drawing contents for all tabs */ std::vector allTabs; for (int32_t i = 0; i < getModelEvent.getNumberOfItemsToDraw(); i++) { allTabs.push_back(getModelEvent.getTabContentToDraw(i)); } this->drawingViewportContents = BrainOpenGLViewportContent::createViewportContentForTileTabs(allTabs, GuiManager::get()->getBrain(), windowWidth, windowHeight, rowHeights, columnsWidths, getModelEvent.getTabIndexForTileTabsHighlighting()); } if (this->selectedUserInputProcessor == userInputBordersModeProcessor) { this->openGL->setBorderBeingDrawn(this->borderBeingDrawn); } else { this->openGL->setBorderBeingDrawn(NULL); } this->openGL->drawModels(this->drawingViewportContents); /* * Issue browser window redrawn event */ BrainBrowserWindow* bbw = GuiManager::get()->getBrowserWindowByWindowIndex(this->windowIndex); if (bbw != NULL) { EventManager::get()->sendEvent(EventBrowserWindowGraphicsRedrawn(bbw).getPointer()); } } /** * Override of event handling. */ bool BrainOpenGLWidget::event(QEvent* event) { const bool toolTipsEnabled = false; if (toolTipsEnabled) { if (event->type() == QEvent::ToolTip) { QHelpEvent* helpEvent = dynamic_cast(event); CaretAssert(helpEvent); QPoint globalXY = helpEvent->globalPos(); QPoint xy = helpEvent->pos(); static int counter = 0; std::cout << "Displaying tooltip " << counter++ << " at global (" << globalXY.x() << ", " << globalXY.y() << ") at pos (" << xy.x() << ", " << xy.y() << ")" << std::endl; QToolTip::showText(globalXY, "This is the tooltip " + AString::number(counter)); return true; } } return QGLWidget::event(event); } /** * Receive Content Menu events from Qt. * @param contextMenuEvent * The context menu event. */ void BrainOpenGLWidget::contextMenuEvent(QContextMenuEvent* contextMenuEvent) { const int x = contextMenuEvent->x(); const int y1 = contextMenuEvent->y(); const int y = this->height() - y1; BrainOpenGLViewportContent* viewportContent = this->getViewportContentAtXY(x, y); if (viewportContent == NULL) { return; } BrowserTabContent* tabContent = viewportContent->getBrowserTabContent(); if (tabContent == NULL) { return; } SelectionManager* idManager = this->performIdentification(x, y, false); BrainOpenGLWidgetContextMenu contextMenu(idManager, tabContent, this); contextMenu.exec(contextMenuEvent->globalPos()); } /** * Receive Mouse Wheel events from Qt. A wheel event is that same * as CTRL-LEFT-DRAG. The wheel's change in value is reported as * change in Y. Change in X is reported as zero. * * @param we * The wheel event. */ void BrainOpenGLWidget::wheelEvent(QWheelEvent* we) { const int wheelX = we->x(); const int wheelY = this->windowHeight[this->windowIndex] - we->y(); int delta = we->delta(); delta = MathFunctions::limitRange(delta, -2, 2); /* * Use location of mouse press so that the model * being manipulated does not change if mouse moves * out of its viewport without releasing the mouse * button. */ BrainOpenGLViewportContent* viewportContent = this->getViewportContentAtXY(wheelX, wheelY); MouseEvent mouseEvent(viewportContent, this, this->windowIndex, wheelX, wheelY, 0, delta, 0, 0, this->mouseNewDraggingStartedFlag); this->selectedUserInputProcessor->mouseLeftDragWithCtrl(mouseEvent); we->accept(); } /* * A mouse event that is the middle mouse button but with no keys pressed * is reported as a SHIFT-LEFT-DRAG and the mouse event is changed. * * @param mouseButtons * Button state when event was generated * @param button * Button that caused the event. * @param keyModifiers * Keys that are down, may be modified. * @param isMouseMoving * True if mouse is moving, else false. */ void BrainOpenGLWidget::checkForMiddleMouseButton(Qt::MouseButtons& mouseButtons, Qt::MouseButton& button, Qt::KeyboardModifiers& keyModifiers, const bool isMouseMoving) { if (isMouseMoving) { if (button == Qt::NoButton) { if (mouseButtons == Qt::MiddleButton) { if (keyModifiers == Qt::NoModifier) { mouseButtons = Qt::LeftButton; button = Qt::NoButton; keyModifiers = Qt::ShiftModifier; } } } } else { if (button == Qt::MiddleButton) { if (keyModifiers == Qt::NoModifier) { button = Qt::LeftButton; keyModifiers = Qt::ShiftModifier; } } } } /** * Receive mouse press events from Qt. * @param me * The mouse event. */ void BrainOpenGLWidget::mousePressEvent(QMouseEvent* me) { Qt::MouseButton button = me->button(); Qt::KeyboardModifiers keyModifiers = me->modifiers(); Qt::MouseButtons mouseButtons = me->buttons(); checkForMiddleMouseButton(mouseButtons, button, keyModifiers, false); /* * When the mouse is dragged, a mouse input receiver may want to * know that a new dragging has started. */ this->mouseNewDraggingStartedFlag = true; this->isMousePressedNearToolBox = false; if (button == Qt::LeftButton) { const int mouseX = me->x(); const int mouseY = this->windowHeight[this->windowIndex] - me->y(); this->mousePressX = mouseX; this->mousePressY = mouseY; this->lastMouseX = mouseX; this->lastMouseY = mouseY; this->mouseMovementMinimumX = mouseX; this->mouseMovementMaximumX = mouseX; this->mouseMovementMinimumY = mouseY; this->mouseMovementMaximumY = mouseY; /* * The user may intend to increase the size of a toolbox * but instead misses the edge of the toolbox when trying * to drag the toolbox and make it larger. So, indicate * when the user is very close to the edge of the graphics * window. */ const int nearToolBoxDistance = 5; if ((mouseX < nearToolBoxDistance) || (mouseX > (this->windowWidth[this->windowIndex] - 5)) || (mouseY < nearToolBoxDistance) || (mouseY > (this->windowHeight[this->windowIndex] - 5))) { this->isMousePressedNearToolBox = true; } } else { this->mousePressX = -10000; this->mousePressY = -10000; } me->accept(); } /** * Receive mouse button release events from Qt. * @param me * The mouse event. */ void BrainOpenGLWidget::mouseReleaseEvent(QMouseEvent* me) { Qt::MouseButton button = me->button(); Qt::KeyboardModifiers keyModifiers = me->modifiers(); Qt::MouseButtons mouseButtons = me->buttons(); checkForMiddleMouseButton(mouseButtons, button, keyModifiers, false); if (button == Qt::LeftButton) { const int mouseX = me->x(); const int mouseY = this->windowHeight[this->windowIndex] - me->y(); this->mouseMovementMinimumX = std::min(this->mouseMovementMinimumX, mouseX); this->mouseMovementMaximumX = std::max(this->mouseMovementMaximumX, mouseX); this->mouseMovementMinimumY = std::min(this->mouseMovementMinimumY, mouseY); this->mouseMovementMaximumY = std::max(this->mouseMovementMaximumY, mouseY); const int dx = this->mouseMovementMaximumX - this->mouseMovementMinimumX; const int dy = this->mouseMovementMaximumY - this->mouseMovementMinimumY; const int absDX = (dx >= 0) ? dx : -dx; const int absDY = (dy >= 0) ? dy : -dy; /* * Use location of mouse press so that the model * being manipulated does not change if mouse moves * out of its viewport without releasing the mouse * button. */ BrainOpenGLViewportContent* viewportContent = this->getViewportContentAtXY(this->mousePressX, this->mousePressY); if ((absDX <= BrainOpenGLWidget::MOUSE_MOVEMENT_TOLERANCE) && (absDY <= BrainOpenGLWidget::MOUSE_MOVEMENT_TOLERANCE)) { MouseEvent mouseEvent(viewportContent, this, this->windowIndex, mouseX, mouseY, dx, dy, this->mousePressX, this->mousePressY, this->mouseNewDraggingStartedFlag); if (keyModifiers == Qt::NoModifier) { this->selectedUserInputProcessor->mouseLeftClick(mouseEvent); } else if (keyModifiers == Qt::ShiftModifier) { this->selectedUserInputProcessor->mouseLeftClickWithShift(mouseEvent); } else if (keyModifiers == (Qt::ShiftModifier | Qt::ControlModifier)) { this->selectedUserInputProcessor->mouseLeftClickWithCtrlShift(mouseEvent); } } } this->mousePressX = -10000; this->mousePressY = -10000; this->isMousePressedNearToolBox = false; me->accept(); } /** * Get the viewport content at the given location. * @param x * X-coordinate. * @param y * Y-coordinate. */ BrainOpenGLViewportContent* BrainOpenGLWidget::getViewportContentAtXY(const int x, const int y) { BrainOpenGLViewportContent* viewportContent = NULL; const int32_t num = static_cast(this->drawingViewportContents.size()); for (int32_t i = 0; i < num; i++) { int viewport[4]; this->drawingViewportContents[i]->getModelViewport(viewport); if ((x >= viewport[0]) && (x < (viewport[0] + viewport[2])) && (y >= viewport[1]) && (y < (viewport[1] + viewport[3]))) { viewportContent = this->drawingViewportContents[i]; break; } } return viewportContent; } /** * Perform identification on all items EXCEPT voxel editing. * * @param x * X-coordinate for identification. * @param y * Y-coordinate for identification. * @param applySelectionBackgroundFiltering * If true (which is in most cases), if there are multiple items * selected, those items "behind" other items are not reported. * For example, suppose a focus is selected and there is a node * the focus. If this parameter is true, the node will NOT be * selected. If this parameter is false, the node will be * selected. * @return * SelectionManager providing identification information. */ SelectionManager* BrainOpenGLWidget::performIdentification(const int x, const int y, const bool applySelectionBackgroundFiltering) { BrainOpenGLViewportContent* idViewport = this->getViewportContentAtXY(x, y); this->makeCurrent(); CaretLogFine("Performing selection"); SelectionManager* idManager = GuiManager::get()->getBrain()->getSelectionManager(); idManager->reset(); idManager->setAllSelectionsEnabled(true); idManager->getVoxelEditingIdentification()->setEnabledForSelection(false); if (idViewport != NULL) { /* * ID coordinate needs to be relative to the viewport * int vp[4]; idViewport->getViewport(vp); const int idX = x - vp[0]; const int idY = y - vp[1]; */ this->openGL->selectModel(idViewport, x, y, applySelectionBackgroundFiltering); } return idManager; } /** * Perform identification on all items EXCEPT voxel editing. * * @param editingVolumeFile * Volume file that is being edited. * @param x * X-coordinate for identification. * @param y * Y-coordinate for identification. * @return * SelectionManager providing identification information. */ SelectionManager* BrainOpenGLWidget::performIdentificationVoxelEditing(VolumeFile* editingVolumeFile, const int x, const int y) { BrainOpenGLViewportContent* idViewport = this->getViewportContentAtXY(x, y); this->makeCurrent(); CaretLogFine("Performing selection"); SelectionManager* idManager = GuiManager::get()->getBrain()->getSelectionManager(); idManager->reset(); idManager->setAllSelectionsEnabled(false); SelectionItemVoxelEditing* idVoxelEdit = idManager->getVoxelEditingIdentification(); idVoxelEdit->setEnabledForSelection(true); idVoxelEdit->setVolumeFileForEditing(editingVolumeFile); if (idViewport != NULL) { /* * ID coordinate needs to be relative to the viewport * int vp[4]; idViewport->getViewport(vp); const int idX = x - vp[0]; const int idY = y - vp[1]; */ this->openGL->selectModel(idViewport, x, y, true); } return idManager; } void BrainOpenGLWidget::performProjection(const int x, const int y, SurfaceProjectedItem& projectionOut) { BrainOpenGLViewportContent* projectionViewport = this->getViewportContentAtXY(x, y); this->makeCurrent(); CaretLogFine("Performing projection"); if (projectionViewport != NULL) { /* * ID coordinate needs to be relative to the viewport * int vp[4]; idViewport->getViewport(vp); const int idX = x - vp[0]; const int idY = y - vp[1]; */ this->openGL->projectToModel(projectionViewport, x, y, projectionOut); } } /** * Receive mouse move events from Qt. * @param me * The mouse event. */ void BrainOpenGLWidget::mouseMoveEvent(QMouseEvent* me) { Qt::MouseButton button = me->button(); Qt::KeyboardModifiers keyModifiers = me->modifiers(); Qt::MouseButtons mouseButtons = me->buttons(); checkForMiddleMouseButton(mouseButtons, button, keyModifiers, true); if (button == Qt::NoButton) { if (mouseButtons == Qt::LeftButton) { const int mouseX = me->x(); const int mouseY = this->windowHeight[this->windowIndex] - me->y(); this->mouseMovementMinimumX = std::min(this->mouseMovementMinimumX, mouseX); this->mouseMovementMaximumX = std::max(this->mouseMovementMaximumX, mouseX); this->mouseMovementMinimumY = std::min(this->mouseMovementMinimumY, mouseY); this->mouseMovementMaximumY = std::max(this->mouseMovementMaximumY, mouseY); const int dx = mouseX - this->lastMouseX; const int dy = mouseY - this->lastMouseY; const int absDX = (dx >= 0) ? dx : -dx; const int absDY = (dy >= 0) ? dy : -dy; if ((absDX > 0) || (absDY > 0)) { /* * Use location of mouse press so that the model * being manipulated does not change if mouse moves * out of its viewport without releasing the mouse * button. */ BrainOpenGLViewportContent* viewportContent = this->getViewportContentAtXY(this->mousePressX, this->mousePressY); MouseEvent mouseEvent(viewportContent, this, this->windowIndex, mouseX, mouseY, dx, dy, this->mousePressX, this->mousePressY, this->mouseNewDraggingStartedFlag); if (keyModifiers == Qt::NoModifier) { this->selectedUserInputProcessor->mouseLeftDrag(mouseEvent); } else if (keyModifiers == Qt::ControlModifier) { this->selectedUserInputProcessor->mouseLeftDragWithCtrl(mouseEvent); } else if (keyModifiers == Qt::ShiftModifier) { this->selectedUserInputProcessor->mouseLeftDragWithShift(mouseEvent); } else if (keyModifiers == Qt::AltModifier) { this->selectedUserInputProcessor->mouseLeftDragWithAlt(mouseEvent); } else if (keyModifiers == (Qt::ShiftModifier | Qt::ControlModifier)) { this->selectedUserInputProcessor->mouseLeftDragWithCtrlShift(mouseEvent); } this->mouseNewDraggingStartedFlag = false; } this->lastMouseX = mouseX; this->lastMouseY = mouseY; } } me->accept(); } /** * Receive events from the event manager. * * @param event * Event sent by event manager. */ void BrainOpenGLWidget::receiveEvent(Event* event) { if (event->getEventType() == EventTypeEnum::EVENT_BRAIN_RESET) { EventBrainReset* brainResetEvent = dynamic_cast(event); CaretAssert(brainResetEvent); this->borderBeingDrawn->clear(); brainResetEvent->setEventProcessed(); } else if (event->getEventType() == EventTypeEnum::EVENT_GRAPHICS_UPDATE_ALL_WINDOWS) { EventGraphicsUpdateAllWindows* updateAllEvent = dynamic_cast(event); CaretAssert(updateAllEvent); updateAllEvent->setEventProcessed(); if (updateAllEvent->isRepaint()) { this->repaint(); } else { this->updateGL(); } } else if (event->getEventType() == EventTypeEnum::EVENT_GRAPHICS_UPDATE_ONE_WINDOW) { EventGraphicsUpdateOneWindow* updateOneEvent = dynamic_cast(event); CaretAssert(updateOneEvent); if (updateOneEvent->getWindowIndex() == this->windowIndex) { updateOneEvent->setEventProcessed(); this->updateGL(); } else { /* * If a window is yoked, update its graphics. */ EventBrowserWindowContentGet getModelEvent(this->windowIndex); EventManager::get()->sendEvent(getModelEvent.getPointer()); if (getModelEvent.isError()) { return; } bool needUpdate = false; if (needUpdate) { this->updateGL(); } } } else if (event->getEventType() == EventTypeEnum::EVENT_GET_OR_SET_USER_INPUT_MODE) { EventGetOrSetUserInputModeProcessor* inputModeEvent = dynamic_cast(event); CaretAssert(inputModeEvent); if (inputModeEvent->getWindowIndex() == this->windowIndex) { if (inputModeEvent->isGetUserInputMode()) { inputModeEvent->setUserInputProcessor(this->selectedUserInputProcessor); } else if (inputModeEvent->isSetUserInputMode()) { UserInputModeAbstract* newUserInputProcessor = NULL; switch (inputModeEvent->getUserInputMode()) { case UserInputModeAbstract::INVALID: CaretAssertMessage(0, "INVALID is NOT allowed for user input mode"); break; case UserInputModeAbstract::BORDERS: newUserInputProcessor = this->userInputBordersModeProcessor; break; case UserInputModeAbstract::FOCI: newUserInputProcessor = this->userInputFociModeProcessor; break; case UserInputModeAbstract::VOLUME_EDIT: newUserInputProcessor = this->userInputVolumeEditModeProcessor; break; case UserInputModeAbstract::VIEW: newUserInputProcessor = this->userInputViewModeProcessor; break; } if (newUserInputProcessor != NULL) { if (newUserInputProcessor != this->selectedUserInputProcessor) { this->selectedUserInputProcessor->finish(); this->selectedUserInputProcessor = newUserInputProcessor; this->selectedUserInputProcessor->initialize(); } } } inputModeEvent->setEventProcessed(); } } else if (event->getEventType() == EventTypeEnum::EVENT_IMAGE_CAPTURE) { EventImageCapture* imageCaptureEvent = dynamic_cast(event); CaretAssert(imageCaptureEvent); if (imageCaptureEvent->getBrowserWindowIndex() == this->windowIndex) { captureImage(imageCaptureEvent); imageCaptureEvent->setEventProcessed(); } } else if (event->getEventType() == EventTypeEnum::EVENT_USER_INTERFACE_UPDATE) { EventUserInterfaceUpdate* guiUpdateEvent = dynamic_cast(event); CaretAssert(guiUpdateEvent); guiUpdateEvent->setEventProcessed(); this->selectedUserInputProcessor->update(); } else { } } /** * Capture an image using the parameters from the event. * * @param imageCaptureEvent * The image capture event. */ void BrainOpenGLWidget::captureImage(EventImageCapture* imageCaptureEvent) { const int oldSizeX = this->windowWidth[this->windowIndex]; const int oldSizeY = this->windowHeight[this->windowIndex]; /* * Note that a size of zero indicates capture graphics in its * current size. */ const int imageSizeX = imageCaptureEvent->getImageSizeX(); const int imageSizeY = imageCaptureEvent->getImageSizeY(); /* * Force immediate mode since problems with display lists * in image capture. */ BrainOpenGLShape::setImmediateModeOverride(true); QImage image; const CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); const ImageCaptureMethodEnum::Enum imageCaptureMethod = prefs->getImageCaptureMethod(); switch (imageCaptureMethod) { case ImageCaptureMethodEnum::IMAGE_CAPTURE_WITH_GRAB_FRAME_BUFFER: { /* * Grab frame buffer seems to have a bug in that it grabs * the previous buffer on Mac so repaint to ensure frame * buffer is updated. (repaint() updates immediately, * update() is a scheduled update). */ repaint(); image = grabFrameBuffer(); /* * If image was captured successfully and the caller has * requested that the image be a specific size, scale * the image to the requested size. */ if ((image.width() > 0) && (image.height() > 0)) { if ((imageSizeX > 0) && (imageSizeY > 0)) { if ((image.width() != imageSizeX) || (image.height() != imageSizeY)) { image = image.scaled(imageSizeX, imageSizeY, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } } } } break; case ImageCaptureMethodEnum::IMAGE_CAPTURE_WITH_RENDER_PIXMAP: { QPixmap pixmap = this->renderPixmap(imageSizeX, imageSizeY); image = pixmap.toImage(); } break; } if ((image.size().width() <= 0) || (image.size().height() <= 0)) { imageCaptureEvent->setErrorMessage("Image capture appears to have failed (invalid size)."); } else { imageCaptureEvent->setImage(image); uint8_t backgroundColor[3]; this->openGL->getBackgroundColor(backgroundColor); imageCaptureEvent->setBackgroundColor(backgroundColor); } BrainOpenGLShape::setImmediateModeOverride(false); this->resizeGL(oldSizeX, oldSizeY); } /** * Initialize the OpenGL format. This must be called * prior to initializing an instance of this class so * that the OpenGL is setup properly. */ void BrainOpenGLWidget::initializeDefaultGLFormat() { QGLFormat glfmt; glfmt.setAccum(false); glfmt.setAlpha(true); glfmt.setAlphaBufferSize(8); glfmt.setDepth(true); glfmt.setDepthBufferSize(24); glfmt.setDirectRendering(true); glfmt.setDoubleBuffer(true); glfmt.setOverlay(false); glfmt.setSampleBuffers(false); glfmt.setStencil(false); glfmt.setStereo(false); glfmt.setRgba(true); glfmt.setRedBufferSize(8); glfmt.setGreenBufferSize(8); glfmt.setBlueBufferSize(8); QGLFormat::setDefaultFormat(glfmt); s_defaultGLFormatInitialized = true; } workbench-1.1.1/src/GuiQt/BrainOpenGLWidget.h000066400000000000000000000127621255417355300207530ustar00rootroot00000000000000 #ifndef __BRAIN_OPENGL_WIDGET_H__ #define __BRAIN_OPENGL_WIDGET_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretOpenGLInclude.h" #include #include #include #include "BrainConstants.h" #include "EventListenerInterface.h" class QMouseEvent; namespace caret { class Border; class BrainOpenGL; class BrainOpenGLTextRenderInterface; class BrainOpenGLViewportContent; class BrowserTabContent; class EventImageCapture; class SelectionManager; class Model; class MouseEvent; class SurfaceProjectedItem; class UserInputModeBorders; class UserInputModeFoci; class UserInputModeView; class UserInputModeVolumeEdit; class UserInputModeAbstract; class VolumeFile; class BrainOpenGLWidget : public QGLWidget, public EventListenerInterface { Q_OBJECT public: BrainOpenGLWidget(QWidget* parent, const int32_t windowIndex); ~BrainOpenGLWidget(); //Model* getDisplayedModelController(); void receiveEvent(Event* event); SelectionManager* performIdentification(const int x, const int y, const bool applySelectionBackgroundFiltering); SelectionManager* performIdentificationVoxelEditing(VolumeFile* editingVolumeFile, const int x, const int y); void performProjection(const int x, const int y, SurfaceProjectedItem& projectionOut); Border* getBorderBeingDrawn(); static void initializeDefaultGLFormat(); QString getOpenGLInformation(); void getViewPortSize(int &w, int &h); protected: virtual void initializeGL(); virtual void resizeGL(int w, int h); virtual void paintGL(); virtual void contextMenuEvent(QContextMenuEvent* contextMenuEvent); virtual bool event(QEvent* event); virtual void mouseMoveEvent(QMouseEvent* e); virtual void mousePressEvent(QMouseEvent* e); virtual void mouseReleaseEvent(QMouseEvent* e); virtual void wheelEvent(QWheelEvent* e); private: void clearDrawingViewportContents(); BrainOpenGLViewportContent* getViewportContentAtXY(const int x, const int y); void checkForMiddleMouseButton(Qt::MouseButtons& mouseButtons, Qt::MouseButton& button, Qt::KeyboardModifiers& keyModifiers, const bool isMouseMoving); void captureImage(EventImageCapture* imageCaptureEvent); BrainOpenGL* openGL; //BrowserTabContent* browserTabContent; int32_t windowIndex; //int32_t windowTabIndex; //Model* modelController; std::vector drawingViewportContents; int32_t windowWidth[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_WINDOWS]; int32_t windowHeight[BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_WINDOWS]; int32_t mouseMovementMinimumX; int32_t mouseMovementMaximumX; int32_t mouseMovementMinimumY; int32_t mouseMovementMaximumY; bool mouseNewDraggingStartedFlag; static const int32_t MOUSE_MOVEMENT_TOLERANCE; int mousePressX; int mousePressY; bool isMousePressedNearToolBox; int lastMouseX; int lastMouseY; BrainOpenGLTextRenderInterface* textRenderer; UserInputModeAbstract* selectedUserInputProcessor; UserInputModeView* userInputViewModeProcessor; UserInputModeBorders* userInputBordersModeProcessor; UserInputModeFoci* userInputFociModeProcessor; UserInputModeVolumeEdit* userInputVolumeEditModeProcessor; Border* borderBeingDrawn; static bool s_defaultGLFormatInitialized; QString m_openGLVersionInformation; }; #ifdef __BRAIN_OPENGL_WIDGET_DEFINE__ const int32_t BrainOpenGLWidget::MOUSE_MOVEMENT_TOLERANCE = 10; bool BrainOpenGLWidget::s_defaultGLFormatInitialized = false; #endif // __BRAIN_OPENGL_WIDGET_DEFINE__ } // namespace #endif // __BRAIN_OPENGL_WIDGET_H__ workbench-1.1.1/src/GuiQt/BrainOpenGLWidgetContextMenu.cxx000066400000000000000000002512621255417355300235200ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __BRAIN_OPEN_G_L_WIDGET_CONTEXT_MENU_DECLARE__ #include "BrainOpenGLWidgetContextMenu.h" #undef __BRAIN_OPEN_G_L_WIDGET_CONTEXT_MENU_DECLARE__ #include "AlgorithmException.h" #include "AlgorithmNodesInsideBorder.h" #include "Border.h" #include "Brain.h" #include "BrainOpenGLWidget.h" #include "BrainStructure.h" #include "BrowserTabContent.h" #include "ChartableLineSeriesBrainordinateInterface.h" #include "ChartingDataManager.h" #include "CiftiBrainordinateLabelFile.h" #include "CiftiConnectivityMatrixDataFileManager.h" #include "CiftiFiberTrajectoryFile.h" #include "CiftiFiberTrajectoryManager.h" #include "CiftiMappableConnectivityMatrixDataFile.h" #include "CursorDisplayScoped.h" #include "EventManager.h" #include "EventGraphicsUpdateAllWindows.h" #include "EventUpdateInformationWindows.h" #include "EventUserInterfaceUpdate.h" #include "FociPropertiesEditorDialog.h" #include "Focus.h" #include "GiftiLabel.h" #include "GiftiLabelTable.h" #include "GuiManager.h" #include "IdentifiedItemNode.h" #include "IdentificationManager.h" #include "LabelFile.h" #include "Overlay.h" #include "OverlaySet.h" #include "Model.h" #include "ProgressReportingDialog.h" #include "SelectionItemBorderSurface.h" #include "SelectionItemFocusSurface.h" #include "SelectionItemFocusVolume.h" #include "SelectionItemSurfaceNode.h" #include "SelectionItemSurfaceNodeIdentificationSymbol.h" #include "SelectionItemVoxel.h" #include "SelectionManager.h" #include "SessionManager.h" #include "Surface.h" #include "UserInputModeFociWidget.h" #include "VolumeFile.h" #include "WuQMessageBox.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::BrainOpenGLWidgetContextMenu * \brief Context (pop-up) menu for BrainOpenGLWidget * * Displays a menu in the BrainOpenGLWidget. Content of menu * is dependent upon data under the cursor. */ /** * Constructor. * @param selectionManager * The selection manager, provides data under the cursor. * @param parentOpenGLWidget * Parent OpenGL Widget on which the menu is displayed. */ BrainOpenGLWidgetContextMenu::BrainOpenGLWidgetContextMenu(SelectionManager* selectionManager, BrowserTabContent* browserTabContent, BrainOpenGLWidget* parentOpenGLWidget) : QMenu(parentOpenGLWidget) { this->parentOpenGLWidget = parentOpenGLWidget; this->selectionManager = selectionManager; this->browserTabContent = browserTabContent; /* * Add the identification actions. */ addIdentificationActions(); /* * Add the border options. */ addBorderRegionOfInterestActions(); /* * Add the foci actions. */ addFociActions(); /* * Show Label ROI operations only for surfaces */ // SelectionItemSurfaceNode* surfaceID = this->selectionManager->getSurfaceNodeIdentification(); // if (surfaceID->isValid()) { addLabelRegionOfInterestActions(); // } // /* // * Identify Node // */ // SelectionItemSurfaceNode* surfaceID = this->selectionManager->getSurfaceNodeIdentification(); // // std::vector ciftiConnectivityActions; // QActionGroup* ciftiConnectivityActionGroup = new QActionGroup(this); // QObject::connect(ciftiConnectivityActionGroup, SIGNAL(triggered(QAction*)), // this, SLOT(parcelCiftiConnectivityActionSelected(QAction*))); // // std::vector ciftiFiberTrajectoryActions; // QActionGroup* ciftiFiberTrajectoryActionGroup = new QActionGroup(this); // QObject::connect(ciftiFiberTrajectoryActionGroup, SIGNAL(triggered(QAction*)), // this, SLOT(parcelCiftiFiberTrajectoryActionSelected(QAction*))); // // std::vector dataSeriesActions; // QActionGroup* dataSeriesActionGroup = new QActionGroup(this); // QObject::connect(dataSeriesActionGroup, SIGNAL(triggered(QAction*)), // this, SLOT(parcelChartableDataActionSelected(QAction*))); // if (surfaceID->isValid()) { // /* // * Connectivity actions for labels // */ // Brain* brain = surfaceID->getBrain(); // Surface* surface = surfaceID->getSurface(); // const int32_t nodeNumber = surfaceID->getNodeNumber(); // // CiftiConnectivityMatrixDataFileManager* connMatrixMan = brain->getCiftiConnectivityMatrixDataFileManager(); // std::vector ciftiMatrixFiles; // brain->getAllCiftiConnectivityMatrixFiles(ciftiMatrixFiles); // bool hasCiftiConnectivity = (ciftiMatrixFiles.empty() == false); // // CiftiFiberTrajectoryManager* ciftiFiberTrajectoryManager = brain->getCiftiFiberTrajectoryManager(); // std::vector ciftiFiberTrajectoryFiles; // const int32_t numFiberFiles = brain->getNumberOfConnectivityFiberTrajectoryFiles(); // for (int32_t i = 0; i < numFiberFiles; i++) { // ciftiFiberTrajectoryFiles.push_back(brain->getConnectivityFiberTrajectoryFile(i)); // } // const bool haveCiftiFiberTrajectoryFiles = (ciftiFiberTrajectoryFiles.empty() == false); // // std::vector chartableFiles; // brain->getAllChartableDataFiles(chartableFiles); // const bool haveChartableFiles = (chartableFiles.empty() == false); // ChartingDataManager* chartingDataManager = brain->getChartingDataManager(); // // Model* model = this->browserTabContent->getModelForDisplay(); // if (model != NULL) { // std::vector allMappableLabelFiles; // // std::vector ciftiLabelFiles; // brain->getConnectivityDenseLabelFiles(ciftiLabelFiles); // allMappableLabelFiles.insert(allMappableLabelFiles.end(), // ciftiLabelFiles.begin(), // ciftiLabelFiles.end()); // // std::vector brainStructureLabelFiles; // surface->getBrainStructure()->getLabelFiles(brainStructureLabelFiles); // allMappableLabelFiles.insert(allMappableLabelFiles.end(), // brainStructureLabelFiles.begin(), // brainStructureLabelFiles.end()); // // const int32_t numberOfLabelFiles = static_cast(allMappableLabelFiles.size()); // for (int32_t ilf = 0; ilf < numberOfLabelFiles; ilf++) { // CaretMappableDataFile* mappableLabelFile = allMappableLabelFiles[ilf]; // const int32_t numMaps = mappableLabelFile->getNumberOfMaps(); // for (int32_t mapIndex = 0; mapIndex < numMaps; mapIndex++) { // // int32_t labelKey = -1; // AString labelName; // CiftiBrainordinateLabelFile* ciftiLabelFile = dynamic_cast(mappableLabelFile); // LabelFile* labelFile = dynamic_cast(mappableLabelFile); // // if (ciftiLabelFile != NULL) { // float nodeValue; // bool nodeValueValid = false; // AString stringValue; // if (ciftiLabelFile->getMapSurfaceNodeValue(mapIndex, // surface->getStructure(), // nodeNumber, // surface->getNumberOfNodes(), // nodeValue, // nodeValueValid, // stringValue)) { // if (nodeValueValid) { // labelKey = static_cast(nodeValue); // const GiftiLabelTable* labelTable = ciftiLabelFile->getMapLabelTable(mapIndex); // labelName = labelTable->getLabelName(labelKey); // } // } // } // else if (labelFile != NULL) { // labelKey = labelFile->getLabelKey(nodeNumber, // mapIndex); // labelName = labelFile->getLabelName(nodeNumber, // mapIndex); // } // else { // CaretAssertMessage(0, // "Should never get here, new or invalid label file type"); // } // // const AString mapName = mappableLabelFile->getMapName(mapIndex); // if (labelName.isEmpty() == false) { // ParcelConnectivity* pc = new ParcelConnectivity(mappableLabelFile, // mapIndex, // labelKey, // labelName, // surface, // nodeNumber, // chartingDataManager, // connMatrixMan, // ciftiFiberTrajectoryManager); // this->parcelConnectivities.push_back(pc); // // if (hasCiftiConnectivity) { // const AString actionName("Show Cifti Connectivity For Parcel " // + labelName // + " in map " // + mapName); // QAction* action = ciftiConnectivityActionGroup->addAction(actionName); // action->setData(qVariantFromValue((void*)pc)); // ciftiConnectivityActions.push_back(action); // } // // if (haveCiftiFiberTrajectoryFiles) { // const AString fiberTrajActionName("Show Average Fiber Trajectory for Parcel " // + labelName // + " in map " // + mapName); // QAction* fiberTrajAction = ciftiFiberTrajectoryActionGroup->addAction(fiberTrajActionName); // fiberTrajAction->setData(qVariantFromValue((void*)pc)); // ciftiFiberTrajectoryActions.push_back(fiberTrajAction); // } // // if (haveChartableFiles) { // const AString tsActionName("Show Data Series Graph For Parcel " // + labelName // + " in map " // + mapName); // QAction* tsAction = dataSeriesActionGroup->addAction(tsActionName); // tsAction->setData(qVariantFromValue((void*)pc)); // dataSeriesActions.push_back(tsAction); // } // } // } // } // } // } // if (idVoxel->isValid()) { // Brain* brain = idVoxel->getBrain(); // std::vector mappableFiles; // brain->getAllMappableDataFiles(mappableFiles); // // double voxelXYZDouble[3]; // idVoxel->getModelXYZ(voxelXYZDouble); // const float voxelXYZ[3] = { // voxelXYZDouble[0], // voxelXYZDouble[1], // voxelXYZDouble[2] // }; // const int32_t numberOfFiles = static_cast(mappableFiles.size()); // for (int32_t ilf = 0; ilf < numberOfFiles; ilf++) { // CaretMappableDataFile* mappableLabelFile = mappableFiles[ilf]; // if (mappableLabelFile->isMappedWithLabelTable() // && mappableLabelFile->isVolumeMappable()) { // const int32_t numMaps = mappableLabelFile->getNumberOfMaps(); // for (int32_t mapIndex = 0; mapIndex < numMaps; mapIndex++) { // // int32_t labelKey = -1; // AString labelName; // CiftiBrainordinateLabelFile* ciftiLabelFile = dynamic_cast(mappableLabelFile); // VolumeFile* volumeLabelFile = dynamic_cast(mappableLabelFile); // VolumeMappableInterface* volumeInterface = dynamic_cast(mappableLabelFile); // if (volumeInterface != NULL) { // float nodeValue; // bool nodeValueValid = false; // AString stringValue; // // int64_t voxelIJK[3]; // float voxelValue; // bool voxelValueValid; // AString textValue; // if (ciftiLabelFile->getMapVolumeVoxelValue(mapIndex, // voxelXYZ, // voxelIJK, // voxelValue, // voxelValueValid, // textValue)) { // if (voxelValueValid) { // labelKey = static_cast(voxelValue); // const GiftiLabelTable* labelTable = ciftiLabelFile->getMapLabelTable(mapIndex); // labelName = labelTable->getLabelName(labelKey); // } // } // } // else { // CaretAssertMessage(0, // "Should never get here, new or invalid label file type"); // } // // const AString mapName = mappableLabelFile->getMapName(mapIndex); // // //// if (labelName.isEmpty() == false) { //// ParcelConnectivity* pc = new ParcelConnectivity(mappableLabelFile, //// mapIndex, //// labelKey, //// labelName, //// surface, //// nodeNumber, //// chartingDataManager, //// connMatrixMan, //// ciftiFiberTrajectoryManager); //// this->parcelConnectivities.push_back(pc); //// //// if (hasCiftiConnectivity) { //// const AString actionName("Show Cifti Connectivity For Parcel " //// + labelName //// + " in map " //// + mapName); //// QAction* action = ciftiConnectivityActionGroup->addAction(actionName); //// action->setData(qVariantFromValue((void*)pc)); //// ciftiConnectivityActions.push_back(action); //// } //// //// if (haveCiftiFiberTrajectoryFiles) { //// const AString fiberTrajActionName("Show Average Fiber Trajectory for Parcel " //// + labelName //// + " in map " //// + mapName); //// QAction* fiberTrajAction = ciftiFiberTrajectoryActionGroup->addAction(fiberTrajActionName); //// fiberTrajAction->setData(qVariantFromValue((void*)pc)); //// ciftiFiberTrajectoryActions.push_back(fiberTrajAction); //// } //// //// if (haveChartableFiles) { //// const AString tsActionName("Show Data Series Graph For Parcel " //// + labelName //// + " in map " //// + mapName); //// QAction* tsAction = dataSeriesActionGroup->addAction(tsActionName); //// tsAction->setData(qVariantFromValue((void*)pc)); //// dataSeriesActions.push_back(tsAction); //// } //// } // // // } // } // } // } // if (ciftiConnectivityActions.empty() == false) { // this->addSeparator(); // for (std::vector::iterator ciftiConnIter = ciftiConnectivityActions.begin(); // ciftiConnIter != ciftiConnectivityActions.end(); // ciftiConnIter++) { // this->addAction(*ciftiConnIter); // } // } // // if (ciftiFiberTrajectoryActions.empty() == false) { // this->addSeparator(); // for (std::vector::iterator ciftiFiberIter = ciftiFiberTrajectoryActions.begin(); // ciftiFiberIter != ciftiFiberTrajectoryActions.end(); // ciftiFiberIter++) { // this->addAction(*ciftiFiberIter); // } // } // if(dataSeriesActions.empty() == false) { // this->addSeparator(); // for (std::vector::iterator tsIter = dataSeriesActions.begin(); // tsIter != dataSeriesActions.end(); // tsIter++) { // this->addAction(*tsIter); // } // } // std::vector createActions; // const SelectionItemSurfaceNodeIdentificationSymbol* idSymbol = selectionManager->getSurfaceNodeIdentificationSymbol(); // // /* // * Create focus at surface node or at ID symbol // */ // if (surfaceID->isValid() // && (focusID->isValid() == false)) { // const int32_t nodeIndex = surfaceID->getNodeNumber(); // const Surface* surface = surfaceID->getSurface(); // const QString text = ("Create Focus at Vertex " // + QString::number(nodeIndex) // + " (" // + AString::fromNumbers(surface->getCoordinate(nodeIndex), 3, ",") // + ")..."); // // createActions.push_back(WuQtUtilities::createAction(text, // "", // this, // this, // SLOT(createSurfaceFocusSelected()))); // } // else if (idSymbol->isValid() // && (focusID->isValid() == false)) { // const int32_t nodeIndex = idSymbol->getNodeNumber(); // const Surface* surface = idSymbol->getSurface(); // const QString text = ("Create Focus at Selected Vertex " // + QString::number(nodeIndex) // + " (" // + AString::fromNumbers(surface->getCoordinate(nodeIndex), 3, ",") // + ")..."); // // createActions.push_back(WuQtUtilities::createAction(text, // "", // this, // this, // SLOT(createSurfaceIDSymbolFocusSelected()))); // } // // /* // * Create focus at voxel as long as there is no volume focus ID // */ // if (idVoxel->isValid() // && (focusVolID->isValid() == false)) { // int64_t ijk[3]; // idVoxel->getVoxelIJK(ijk); // float xyz[3]; // const VolumeMappableInterface* vf = idVoxel->getVolumeFile(); // vf->indexToSpace(ijk, xyz); // // const AString text = ("Create Focus at Voxel IJK (" // + AString::fromNumbers(ijk, 3, ",") // + ") XYZ (" // + AString::fromNumbers(xyz, 3, ",") // + ")..."); // createActions.push_back(WuQtUtilities::createAction(text, // "", // this, // this, // SLOT(createVolumeFocusSelected()))); // } // // if (createActions.empty() == false) { // if (this->actions().count() > 0) { // this->addSeparator(); // } // for (std::vector::iterator iter = createActions.begin(); // iter != createActions.end(); // iter++) { // this->addAction(*iter); // } // } // // /* // * Actions for editing // */ // std::vector editActions; // // /* // * Edit Surface Focus // */ // if (focusID->isValid()) { // const QString text = ("Edit Surface Focus (" // + focusID->getFocus()->getName() // + ")"); // editActions.push_back(WuQtUtilities::createAction(text, // "", // this, // this, // SLOT(editSurfaceFocusSelected()))); // } // // /* // * Edit volume focus // */ // if (focusVolID->isValid()) { // const QString text = ("Edit Volume Focus (" // + focusVolID->getFocus()->getName() // + ")"); // editActions.push_back(WuQtUtilities::createAction(text, // "", // this, // this, // SLOT(editVolumeFocusSelected()))); // } // // if (editActions.empty() == false) { // if (this->actions().count() > 0) { // this->addSeparator(); // } // for (std::vector::iterator iter = editActions.begin(); // iter != editActions.end(); // iter++) { // this->addAction(*iter); // } // } if (this->actions().count() > 0) { this->addSeparator(); } this->addAction("Remove All Vertex Identification Symbols", this, SLOT(removeAllNodeIdentificationSymbolsSelected())); if (idSymbol->isValid()) { const AString text = ("Remove Identification of Vertices " + AString::number(idSymbol->getNodeNumber())); this->addAction(WuQtUtilities::createAction(text, "", this, this, SLOT(removeNodeIdentificationSymbolSelected()))); } } /** * Destructor. */ BrainOpenGLWidgetContextMenu::~BrainOpenGLWidgetContextMenu() { for (std::vector::iterator parcelIter = this->parcelConnectivities.begin(); parcelIter != this->parcelConnectivities.end(); parcelIter++) { delete *parcelIter; } } /** * Add the actions to this context menu. * * @param actionsToAdd * Actions to add to the menum. * @param addSeparatorBeforeActions * If true and there are actions presently in the menu, a separator * (horizontal bar) is added prior to adding the given actions. */ void BrainOpenGLWidgetContextMenu::addActionsToMenu(QList& actionsToAdd, const bool addSeparatorBeforeActions) { if (actionsToAdd.empty() == false) { if (addSeparatorBeforeActions) { if (actions().isEmpty() == false) { addSeparator(); } } addActions(actionsToAdd); } } /** * Add the identification actions to the menu. */ void BrainOpenGLWidgetContextMenu::addIdentificationActions() { /* * Accumlate identification actions */ QList identificationActions; /* * Identify Border */ SelectionItemBorderSurface* borderID = this->selectionManager->getSurfaceBorderIdentification(); if (borderID->isValid()) { const QString text = ("Identify Border (" + borderID->getBorder()->getName() + ") Under Mouse"); identificationActions.push_back(WuQtUtilities::createAction(text, "", this, this, SLOT(identifySurfaceBorderSelected()))); } /* * Identify Surface Focus */ SelectionItemFocusSurface* focusID = this->selectionManager->getSurfaceFocusIdentification(); if (focusID->isValid()) { const QString text = ("Identify Surface Focus (" + focusID->getFocus()->getName() + ") Under Mouse"); identificationActions.push_back(WuQtUtilities::createAction(text, "", this, this, SLOT(identifySurfaceFocusSelected()))); } /* * Identify Node */ SelectionItemSurfaceNode* surfaceID = this->selectionManager->getSurfaceNodeIdentification(); if (surfaceID->isValid()) { const int32_t nodeIndex = surfaceID->getNodeNumber(); const Surface* surface = surfaceID->getSurface(); const QString text = ("Identify Vertex " + QString::number(nodeIndex) + " (" + AString::fromNumbers(surface->getCoordinate(nodeIndex), 3, ",") + ") Under Mouse"); identificationActions.push_back(WuQtUtilities::createAction(text, "", this, this, SLOT(identifySurfaceNodeSelected()))); } /* * Identify Voxel */ SelectionItemVoxel* idVoxel = this->selectionManager->getVoxelIdentification(); if (idVoxel->isValid()) { int64_t ijk[3]; idVoxel->getVoxelIJK(ijk); const AString text = ("Identify Voxel (" + AString::fromNumbers(ijk, 3, ",") + ")"); identificationActions.push_back(WuQtUtilities::createAction(text, "", this, this, SLOT(identifyVoxelSelected()))); } /* * Identify Volume Focus */ SelectionItemFocusVolume* focusVolID = this->selectionManager->getVolumeFocusIdentification(); if (focusVolID->isValid()) { const QString text = ("Identify Volume Focus (" + focusVolID->getFocus()->getName() + ") Under Mouse"); identificationActions.push_back(WuQtUtilities::createAction(text, "", this, this, SLOT(identifyVolumeFocusSelected()))); } addActionsToMenu(identificationActions, true); } /** * Add the border region of interest actions to the menu. */ void BrainOpenGLWidgetContextMenu::addBorderRegionOfInterestActions() { SelectionItemBorderSurface* borderID = this->selectionManager->getSurfaceBorderIdentification(); QList borderActions; if (borderID->isValid()) { Brain* brain = borderID->getBrain(); std::vector ciftiMatrixFiles; brain->getAllCiftiConnectivityMatrixFiles(ciftiMatrixFiles); bool hasCiftiConnectivity = (ciftiMatrixFiles.empty() == false); /* * Connectivity actions for borders */ if (hasCiftiConnectivity) { const QString text = ("Show CIFTI Connectivity for Nodes Inside Border " + borderID->getBorder()->getName()); QAction* action = WuQtUtilities::createAction(text, "", this, this, SLOT(borderCiftiConnectivitySelected())); borderActions.push_back(action); } std::vector chartableFiles; brain->getAllChartableBrainordinateDataFiles(chartableFiles); if (chartableFiles.empty() == false) { const QString text = ("Show Charts for Nodes Inside Border " + borderID->getBorder()->getName()); QAction* action = WuQtUtilities::createAction(text, "", this, this, SLOT(borderDataSeriesSelected())); borderActions.push_back(action); } } addActionsToMenu(borderActions, true); } /** * Add all label region of interest options to the menu */ void BrainOpenGLWidgetContextMenu::addLabelRegionOfInterestActions() { Brain* brain = NULL; float voxelXYZ[3] = { 0.0, 0.0, 0.0 }; SelectionItemVoxel* idVoxel = this->selectionManager->getVoxelIdentification(); if (idVoxel->isValid()) { double voxelXYZDouble[3]; idVoxel->getModelXYZ(voxelXYZDouble); voxelXYZ[0] = voxelXYZDouble[0]; voxelXYZ[1] = voxelXYZDouble[1]; voxelXYZ[2] = voxelXYZDouble[2]; brain = idVoxel->getBrain(); } Surface* surface = NULL; int32_t surfaceNodeIndex = -1; int32_t surfaceNumberOfNodes = -1; StructureEnum::Enum surfaceStructure = StructureEnum::INVALID; SelectionItemSurfaceNode* idNode = this->selectionManager->getSurfaceNodeIdentification(); if (idNode->isValid()) { surface = idNode->getSurface(); surfaceNodeIndex = idNode->getNodeNumber(); surfaceNumberOfNodes = surface->getNumberOfNodes(); surfaceStructure = surface->getStructure(); brain = idNode->getBrain(); } /* * If Brain is invalid, then there is no identified node or voxel */ if (brain == NULL) { return; } /* * Manager for connectivity matrix files */ CiftiConnectivityMatrixDataFileManager* ciftiConnectivityMatrixManager = SessionManager::get()->getCiftiConnectivityMatrixDataFileManager(); std::vector ciftiMatrixFiles; brain->getAllCiftiConnectivityMatrixFiles(ciftiMatrixFiles); bool hasCiftiConnectivity = (ciftiMatrixFiles.empty() == false); /* * Manager for fiber trajectory */ CiftiFiberTrajectoryManager* ciftiFiberTrajectoryManager = SessionManager::get()->getCiftiFiberTrajectoryManager(); std::vector ciftiFiberTrajectoryFiles; const int32_t numFiberFiles = brain->getNumberOfConnectivityFiberTrajectoryFiles(); for (int32_t i = 0; i < numFiberFiles; i++) { ciftiFiberTrajectoryFiles.push_back(brain->getConnectivityFiberTrajectoryFile(i)); } const bool haveCiftiFiberTrajectoryFiles = (ciftiFiberTrajectoryFiles.empty() == false); /* * Manager for Chartable files */ std::vector chartableFiles; brain->getAllChartableBrainordinateDataFiles(chartableFiles); const bool haveChartableFiles = (chartableFiles.empty() == false); ChartingDataManager* chartingDataManager = brain->getChartingDataManager(); /* * Actions for each file type */ QList ciftiConnectivityActions; QActionGroup* ciftiConnectivityActionGroup = new QActionGroup(this); QObject::connect(ciftiConnectivityActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(parcelCiftiConnectivityActionSelected(QAction*))); QList ciftiFiberTrajectoryActions; QActionGroup* ciftiFiberTrajectoryActionGroup = new QActionGroup(this); QObject::connect(ciftiFiberTrajectoryActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(parcelCiftiFiberTrajectoryActionSelected(QAction*))); QList chartableDataActions; QActionGroup* chartableDataActionGroup = new QActionGroup(this); QObject::connect(chartableDataActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(parcelChartableDataActionSelected(QAction*))); /* * Get all mappable files and find files mapped with using labels */ std::vector mappableFiles; brain->getAllMappableDataFiles(mappableFiles); /* * Process each map file */ for (std::vector::iterator mapFileIterator = mappableFiles.begin(); mapFileIterator != mappableFiles.end(); mapFileIterator++) { CaretMappableDataFile* mappableLabelFile = *mapFileIterator; if (mappableLabelFile->isMappedWithLabelTable()) { const int32_t numMaps = mappableLabelFile->getNumberOfMaps(); for (int32_t mapIndex = 0; mapIndex < numMaps; mapIndex++) { Surface* labelSurface = NULL; int32_t labelNodeNumber = -1; int32_t labelKey = -1; AString labelName; int64_t volumeDimensions[3] = { -1, -1, -1 }; ParcelConnectivity::ParcelType parcelType = ParcelConnectivity::PARCEL_TYPE_INVALID; if (mappableLabelFile->isVolumeMappable()) { CiftiBrainordinateLabelFile* ciftiLabelFile = dynamic_cast(mappableLabelFile); VolumeFile* volumeLabelFile = dynamic_cast(mappableLabelFile); VolumeMappableInterface* volumeInterface = dynamic_cast(mappableLabelFile); if (volumeInterface != NULL) { int64_t voxelIJK[3]; float voxelValue; bool voxelValueValid; AString textValue; if (ciftiLabelFile != NULL) { if (ciftiLabelFile->getMapVolumeVoxelValue(mapIndex, voxelXYZ, voxelIJK, voxelValue, voxelValueValid, textValue)) { if (voxelValueValid) { labelKey = static_cast(voxelValue); const GiftiLabelTable* labelTable = ciftiLabelFile->getMapLabelTable(mapIndex); labelName = labelTable->getLabelName(labelKey); if (labelName.isEmpty() == false) { parcelType = ParcelConnectivity::PARCEL_TYPE_VOLUME_VOXELS; } } } } else if (volumeLabelFile != NULL) { int64_t voxelIJK[3]; volumeLabelFile->enclosingVoxel(voxelXYZ, voxelIJK); if (volumeLabelFile->indexValid(voxelIJK)) { const float voxelValue = volumeLabelFile->getValue(voxelIJK[0], voxelIJK[1], voxelIJK[2], mapIndex); const GiftiLabelTable* labelTable = volumeLabelFile->getMapLabelTable(mapIndex); labelKey = static_cast(voxelValue); labelName = labelTable->getLabelName(voxelValue); if (labelName.isEmpty() == false) { parcelType = ParcelConnectivity::PARCEL_TYPE_VOLUME_VOXELS; } } } else { CaretAssertMessage(0, "Should never get here, new or invalid label file type"); } std::vector dims; volumeInterface->getDimensions(dims); if (dims.size() >= 3) { volumeDimensions[0] = dims[0]; volumeDimensions[1] = dims[1]; volumeDimensions[2] = dims[2]; } } } if (mappableLabelFile->isSurfaceMappable()) { if (labelName.isEmpty()) { if (idNode->isValid()) { labelSurface = idNode->getSurface(); labelNodeNumber = idNode->getNodeNumber(); LabelFile* labelFile = dynamic_cast(mappableLabelFile); CiftiBrainordinateLabelFile* ciftiLabelFile = dynamic_cast(mappableLabelFile); if (labelFile != NULL) { labelKey = labelFile->getLabelKey(labelNodeNumber, mapIndex); const GiftiLabelTable* labelTable = labelFile->getMapLabelTable(mapIndex); labelName = labelTable->getLabelName(labelKey); if (labelName.isEmpty() == false) { parcelType = ParcelConnectivity::PARCEL_TYPE_SURFACE_NODES; } } else if (ciftiLabelFile != NULL) { float nodeValue = 0.0; bool nodeValueValid = false; AString stringValue; if (ciftiLabelFile->getMapSurfaceNodeValue(mapIndex, surfaceStructure, surfaceNodeIndex, surfaceNumberOfNodes, nodeValue, nodeValueValid, stringValue)) { if (nodeValueValid) { labelKey = nodeValue; const GiftiLabelTable* labelTable = ciftiLabelFile->getMapLabelTable(mapIndex); labelName = labelTable->getLabelName(labelKey); if (labelName.isEmpty() == false) { parcelType = ParcelConnectivity::PARCEL_TYPE_SURFACE_NODES; } } } } } } } if (parcelType != ParcelConnectivity::PARCEL_TYPE_INVALID) { const AString mapName = (AString::number(mapIndex + 1) + ": " + mappableLabelFile->getMapName(mapIndex)); ParcelConnectivity* parcelConnectivity = new ParcelConnectivity(brain, parcelType, mappableLabelFile, mapIndex, labelKey, labelName, labelSurface, labelNodeNumber, volumeDimensions, chartingDataManager, ciftiConnectivityMatrixManager, ciftiFiberTrajectoryManager); this->parcelConnectivities.push_back(parcelConnectivity); if (hasCiftiConnectivity) { bool matchFlag = false; if (parcelType == ParcelConnectivity::PARCEL_TYPE_SURFACE_NODES) { matchFlag = true; } else if (parcelType == ParcelConnectivity::PARCEL_TYPE_VOLUME_VOXELS) { for (std::vector::iterator iter = ciftiMatrixFiles.begin(); iter != ciftiMatrixFiles.end(); iter++) { const CiftiMappableConnectivityMatrixDataFile* ciftiFile = *iter; if (ciftiFile->matchesDimensions(volumeDimensions[0], volumeDimensions[1], volumeDimensions[2])) { matchFlag = true; break; } } } if (matchFlag) { const AString actionName("Show Cifti Connectivity For Parcel " + labelName + " in map " + mapName); QAction* action = ciftiConnectivityActionGroup->addAction(actionName); action->setData(qVariantFromValue((void*)parcelConnectivity)); ciftiConnectivityActions.push_back(action); } } if (haveCiftiFiberTrajectoryFiles) { const AString fiberTrajActionName("Show Average Fiber Trajectory for Parcel " + labelName + " in map " + mapName); QAction* fiberTrajAction = ciftiFiberTrajectoryActionGroup->addAction(fiberTrajActionName); fiberTrajAction->setData(qVariantFromValue((void*)parcelConnectivity)); ciftiFiberTrajectoryActions.push_back(fiberTrajAction); } if (haveChartableFiles) { bool matchFlag = false; if (parcelType == ParcelConnectivity::PARCEL_TYPE_SURFACE_NODES) { matchFlag = true; } else if (parcelType == ParcelConnectivity::PARCEL_TYPE_VOLUME_VOXELS) { for (std::vector::iterator iter = chartableFiles.begin(); iter != chartableFiles.end(); iter++) { const ChartableLineSeriesBrainordinateInterface* chartFile = *iter; const CaretMappableDataFile* mapDataFile = chartFile->getLineSeriesChartCaretMappableDataFile(); if (mapDataFile != NULL){ if (mapDataFile->isVolumeMappable()) { const VolumeMappableInterface* volMap = dynamic_cast(mapDataFile); if (volMap->matchesDimensions(volumeDimensions[0], volumeDimensions[1], volumeDimensions[2])) { matchFlag = true; break; } } } } } if (matchFlag) { const AString tsActionName("Show Data/Time Series Graph For Parcel " + labelName + " in map " + mapName); QAction* tsAction = chartableDataActionGroup->addAction(tsActionName); tsAction->setData(qVariantFromValue((void*)parcelConnectivity)); chartableDataActions.push_back(tsAction); } } } } } } addActionsToMenu(ciftiConnectivityActions, true); addActionsToMenu(ciftiFiberTrajectoryActions, true); addActionsToMenu(chartableDataActions, true); } /** * Add the foci options to the menu. */ void BrainOpenGLWidgetContextMenu::addFociActions() { QList fociCreateActions; const SelectionItemSurfaceNodeIdentificationSymbol* idSymbol = selectionManager->getSurfaceNodeIdentificationSymbol(); SelectionItemFocusSurface* focusID = this->selectionManager->getSurfaceFocusIdentification(); SelectionItemSurfaceNode* surfaceID = this->selectionManager->getSurfaceNodeIdentification(); SelectionItemVoxel* idVoxel = this->selectionManager->getVoxelIdentification(); SelectionItemFocusVolume* focusVolID = this->selectionManager->getVolumeFocusIdentification(); /* * Create focus at surface node or at ID symbol */ if (surfaceID->isValid() && (focusID->isValid() == false)) { const int32_t nodeIndex = surfaceID->getNodeNumber(); const Surface* surface = surfaceID->getSurface(); const QString text = ("Create Focus at Vertex " + QString::number(nodeIndex) + " (" + AString::fromNumbers(surface->getCoordinate(nodeIndex), 3, ",") + ")..."); fociCreateActions.push_back(WuQtUtilities::createAction(text, "", this, this, SLOT(createSurfaceFocusSelected()))); } else if (idSymbol->isValid() && (focusID->isValid() == false)) { const int32_t nodeIndex = idSymbol->getNodeNumber(); const Surface* surface = idSymbol->getSurface(); const QString text = ("Create Focus at Selected Vertex " + QString::number(nodeIndex) + " (" + AString::fromNumbers(surface->getCoordinate(nodeIndex), 3, ",") + ")..."); fociCreateActions.push_back(WuQtUtilities::createAction(text, "", this, this, SLOT(createSurfaceIDSymbolFocusSelected()))); } /* * Create focus at voxel as long as there is no volume focus ID */ if (idVoxel->isValid() && (focusVolID->isValid() == false)) { int64_t ijk[3]; idVoxel->getVoxelIJK(ijk); float xyz[3]; const VolumeMappableInterface* vf = idVoxel->getVolumeFile(); vf->indexToSpace(ijk, xyz); const AString text = ("Create Focus at Voxel IJK (" + AString::fromNumbers(ijk, 3, ",") + ") XYZ (" + AString::fromNumbers(xyz, 3, ",") + ")..."); fociCreateActions.push_back(WuQtUtilities::createAction(text, "", this, this, SLOT(createVolumeFocusSelected()))); } addActionsToMenu(fociCreateActions, true); /* * Actions for editing */ QList fociEditActions; /* * Edit Surface Focus */ if (focusID->isValid()) { const QString text = ("Edit Surface Focus (" + focusID->getFocus()->getName() + ")"); fociEditActions.push_back(WuQtUtilities::createAction(text, "", this, this, SLOT(editSurfaceFocusSelected()))); } /* * Edit volume focus */ if (focusVolID->isValid()) { const QString text = ("Edit Volume Focus (" + focusVolID->getFocus()->getName() + ")"); fociEditActions.push_back(WuQtUtilities::createAction(text, "", this, this, SLOT(editVolumeFocusSelected()))); } addActionsToMenu(fociEditActions, true); } /** * Called when a cifti connectivity action is selected. * @param action * Action that was selected. */ void BrainOpenGLWidgetContextMenu::parcelCiftiConnectivityActionSelected(QAction* action) { void* pointer = action->data().value(); ParcelConnectivity* pc = (ParcelConnectivity*)pointer; std::vector nodeIndices; std::vector voxelIndices; switch (pc->parcelType) { case ParcelConnectivity::PARCEL_TYPE_INVALID: break; case ParcelConnectivity::PARCEL_TYPE_SURFACE_NODES: pc->getNodeIndices(nodeIndices); if (nodeIndices.empty()) { WuQMessageBox::errorOk(this, "No vertices match label " + pc->labelName); return; } if (pc->ciftiConnectivityManager->hasNetworkFiles(pc->brain)) { if (warnIfNetworkBrainordinateCountIsLarge(nodeIndices.size()) == false) { return; } } break; case ParcelConnectivity::PARCEL_TYPE_VOLUME_VOXELS: pc->getVoxelIndices(voxelIndices); if (voxelIndices.empty()) { WuQMessageBox::errorOk(this, "No voxels match label " + pc->labelName); return; } if (pc->ciftiConnectivityManager->hasNetworkFiles(pc->brain)) { if (warnIfNetworkBrainordinateCountIsLarge(voxelIndices.size()) == false) { return; } } break; } CursorDisplayScoped cursor; cursor.showWaitCursor(); try { ProgressReportingDialog progressDialog("Connectivity Within Parcel", "", this); progressDialog.setValue(0); switch (pc->parcelType) { case ParcelConnectivity::PARCEL_TYPE_INVALID: break; case ParcelConnectivity::PARCEL_TYPE_SURFACE_NODES: pc->ciftiConnectivityManager->loadAverageDataForSurfaceNodes(pc->brain, pc->surface, nodeIndices); break; case ParcelConnectivity::PARCEL_TYPE_VOLUME_VOXELS: pc->ciftiConnectivityManager->loadAverageDataForVoxelIndices(pc->brain, pc->volumeDimensions, voxelIndices); break; } } catch (const DataFileException& e) { cursor.restoreCursor(); WuQMessageBox::errorOk(this, e.whatString()); } EventManager::get()->sendEvent(EventUserInterfaceUpdate().getPointer()); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } /** * Called when a cifti connectivity action is selected. * @param action * Action that was selected. */ void BrainOpenGLWidgetContextMenu::parcelCiftiFiberTrajectoryActionSelected(QAction* action) { void* pointer = action->data().value(); ParcelConnectivity* pc = (ParcelConnectivity*)pointer; std::vector nodeIndices; switch (pc->parcelType) { case ParcelConnectivity::PARCEL_TYPE_INVALID: break; case ParcelConnectivity::PARCEL_TYPE_SURFACE_NODES: pc->getNodeIndices(nodeIndices); if (nodeIndices.empty()) { WuQMessageBox::errorOk(this, "No vertices match label " + pc->labelName); return; } // if (pc->ciftiFiberTrajectoryManager->hasNetworkFiles()) { // if (warnIfBrainordinateCountIsLarge(nodeIndices.size())) { // return; // } // } break; case ParcelConnectivity::PARCEL_TYPE_VOLUME_VOXELS: break; } CursorDisplayScoped cursor; cursor.showWaitCursor(); try { ProgressReportingDialog progressDialog("Trajectory Within Parcel", "", this); progressDialog.setValue(0); switch (pc->parcelType) { case ParcelConnectivity::PARCEL_TYPE_INVALID: break; case ParcelConnectivity::PARCEL_TYPE_SURFACE_NODES: pc->ciftiFiberTrajectoryManager->loadDataAverageForSurfaceNodes(pc->brain, pc->surface, nodeIndices); break; case ParcelConnectivity::PARCEL_TYPE_VOLUME_VOXELS: std::vector voxelIndices; pc->getVoxelIndices(voxelIndices); pc->ciftiFiberTrajectoryManager->loadAverageDataForVoxelIndices(pc->brain, pc->volumeDimensions, voxelIndices); break; } } catch (const DataFileException& e) { cursor.restoreCursor(); WuQMessageBox::errorOk(this, e.whatString()); } EventManager::get()->sendEvent(EventUserInterfaceUpdate().getPointer()); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } /** * Called when border cifti connectivity is selected. */ void BrainOpenGLWidgetContextMenu::borderCiftiConnectivitySelected() { SelectionItemBorderSurface* borderID = this->selectionManager->getSurfaceBorderIdentification(); Border* border = borderID->getBorder(); Surface* surface = borderID->getSurface(); const int32_t numberOfNodes = surface->getNumberOfNodes(); LabelFile labelFile; labelFile.setNumberOfNodesAndColumns(numberOfNodes, 1); const int32_t labelKey = labelFile.getLabelTable()->addLabel("TempLabel", 1.0f, 1.0f, 1.0f, 1.0f); const int32_t mapIndex = 0; try { AlgorithmNodesInsideBorder algorithmInsideBorder(NULL, surface, border, false, mapIndex, labelKey, &labelFile); std::vector nodeIndices; nodeIndices.reserve(numberOfNodes); for (int32_t i = 0; i < numberOfNodes; i++) { if (labelFile.getLabelKey(i, mapIndex) == labelKey) { nodeIndices.push_back(i); } } if (nodeIndices.empty()) { WuQMessageBox::errorOk(this, "No vertices found inside border " + border->getName()); return; } if (borderID->getBrain()->getChartingDataManager()->hasNetworkFiles()) { if (warnIfNetworkBrainordinateCountIsLarge(nodeIndices.size()) == false) { return; } } CursorDisplayScoped cursor; cursor.showWaitCursor(); try { ProgressReportingDialog progressDialog("Connectivity Within Border", "", this); progressDialog.setValue(0); CiftiConnectivityMatrixDataFileManager* ciftiConnMann = SessionManager::get()->getCiftiConnectivityMatrixDataFileManager(); ciftiConnMann->loadAverageDataForSurfaceNodes(borderID->getBrain(), surface, nodeIndices); } catch (const DataFileException& e) { cursor.restoreCursor(); WuQMessageBox::errorOk(this, e.whatString()); } EventManager::get()->sendEvent(EventUserInterfaceUpdate().getPointer()); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } catch (const AlgorithmException& e) { WuQMessageBox::errorOk(this, e.whatString()); } } /** * Called when a connectivity action is selected. * @param action * Action that was selected. */ void BrainOpenGLWidgetContextMenu::parcelChartableDataActionSelected(QAction* action) { void* pointer = action->data().value(); ParcelConnectivity* pc = (ParcelConnectivity*)pointer; std::vector nodeIndices; std::vector voxelIndices; switch (pc->parcelType) { case ParcelConnectivity::PARCEL_TYPE_INVALID: break; case ParcelConnectivity::PARCEL_TYPE_SURFACE_NODES: pc->getNodeIndices(nodeIndices); if (nodeIndices.empty()) { WuQMessageBox::errorOk(this, "No vertices match label " + pc->labelName); return; } if (pc->chartingDataManager->hasNetworkFiles()) { if (warnIfNetworkBrainordinateCountIsLarge(nodeIndices.size()) == false) { return; } } break; case ParcelConnectivity::PARCEL_TYPE_VOLUME_VOXELS: pc->getVoxelIndices(voxelIndices); if (voxelIndices.empty()) { WuQMessageBox::errorOk(this, "No voxels match label " + pc->labelName); return; } if (pc->chartingDataManager->hasNetworkFiles()) { if (warnIfNetworkBrainordinateCountIsLarge(voxelIndices.size()) == false) { return; } } break; } CursorDisplayScoped cursor; cursor.showWaitCursor(); try { ProgressReportingDialog progressDialog("Data Series Within Parcel", "", this); progressDialog.setValue(0); switch (pc->parcelType) { case ParcelConnectivity::PARCEL_TYPE_INVALID: break; case ParcelConnectivity::PARCEL_TYPE_SURFACE_NODES: pc->chartingDataManager->loadAverageChartForSurfaceNodes(pc->surface, nodeIndices); break; case ParcelConnectivity::PARCEL_TYPE_VOLUME_VOXELS: cursor.restoreCursor(); WuQMessageBox::errorOk(this, "Charting of voxel average has not been implemented."); break; } } catch (const DataFileException& e) { cursor.restoreCursor(); WuQMessageBox::errorOk(this, e.whatString()); } EventManager::get()->sendEvent(EventUserInterfaceUpdate().getPointer()); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } /** * Called when border timeseries is selected. */ void BrainOpenGLWidgetContextMenu::borderDataSeriesSelected() { SelectionItemBorderSurface* borderID = this->selectionManager->getSurfaceBorderIdentification(); Border* border = borderID->getBorder(); Surface* surface = borderID->getSurface(); const int32_t numberOfNodes = surface->getNumberOfNodes(); LabelFile labelFile; labelFile.setNumberOfNodesAndColumns(numberOfNodes, 1); const int32_t labelKey = labelFile.getLabelTable()->addLabel("TempLabel", 1.0f, 1.0f, 1.0f, 1.0f); const int32_t mapIndex = 0; try { AlgorithmNodesInsideBorder algorithmInsideBorder(NULL, surface, border, false, mapIndex, labelKey, &labelFile); std::vector nodeIndices; nodeIndices.reserve(numberOfNodes); for (int32_t i = 0; i < numberOfNodes; i++) { if (labelFile.getLabelKey(i, mapIndex) == labelKey) { nodeIndices.push_back(i); } } if (nodeIndices.empty()) { WuQMessageBox::errorOk(this, "No vertices found inside border " + border->getName()); return; } if (borderID->getBrain()->getChartingDataManager()->hasNetworkFiles()) { if (warnIfNetworkBrainordinateCountIsLarge(nodeIndices.size()) == false) { return; } } CursorDisplayScoped cursor; cursor.showWaitCursor(); try { ProgressReportingDialog progressDialog("Data Series Within Border", "", this); progressDialog.setValue(0); ChartingDataManager* chartingDataManager = borderID->getBrain()->getChartingDataManager(); chartingDataManager->loadAverageChartForSurfaceNodes(surface, nodeIndices); } catch (const DataFileException& e) { cursor.restoreCursor(); WuQMessageBox::errorOk(this, e.whatString()); } } catch (const AlgorithmException& e) { WuQMessageBox::errorOk(this, e.whatString()); } EventManager::get()->sendEvent(EventUserInterfaceUpdate().getPointer()); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } /** * @return If no data series graphs are enabled, enable all of them and return * true. Else, return false. */ bool BrainOpenGLWidgetContextMenu::enableDataSeriesGraphsIfNoneEnabled() { Brain* brain = GuiManager::get()->getBrain(); std::vector chartFiles; brain->getAllChartableBrainordinateDataFiles(chartFiles); if (chartFiles.empty()) { return false; } const int32_t tabIndex = this->browserTabContent->getTabNumber(); /* * Exit if any data series graph is enabled. */ for (std::vector::iterator iter = chartFiles.begin(); iter != chartFiles.end(); iter++) { ChartableLineSeriesBrainordinateInterface* chartFile = *iter; if (chartFile->isLineSeriesChartingEnabled(tabIndex)) { return false; } } /* * Enable and display all data series graphs. */ for (std::vector::iterator iter = chartFiles.begin(); iter != chartFiles.end(); iter++) { ChartableLineSeriesBrainordinateInterface* chartFile = *iter; chartFile->setLineSeriesChartingEnabled(tabIndex, true); } return true; } /** * Called to display identication information on the surface border. */ void BrainOpenGLWidgetContextMenu::identifySurfaceBorderSelected() { SelectionItemBorderSurface* borderID = this->selectionManager->getSurfaceBorderIdentification(); Brain* brain = borderID->getBrain(); this->selectionManager->clearOtherSelectedItems(borderID); const AString idMessage = this->selectionManager->getIdentificationText(brain); IdentificationManager* idManager = brain->getIdentificationManager(); idManager->addIdentifiedItem(new IdentifiedItem(idMessage)); EventManager::get()->sendEvent(EventUpdateInformationWindows().getPointer()); } /** * Called to create a focus at a node location */ void BrainOpenGLWidgetContextMenu::createSurfaceFocusSelected() { SelectionItemSurfaceNode* surfaceID = this->selectionManager->getSurfaceNodeIdentification(); const Surface* surface = surfaceID->getSurface(); const int32_t nodeIndex = surfaceID->getNodeNumber(); const float* xyz = surface->getCoordinate(nodeIndex); const AString focusName = (StructureEnum::toGuiName(surface->getStructure()) + " Vertex " + AString::number(nodeIndex)); const AString comment = ("Created from " + focusName); Focus* focus = new Focus(); focus->setName(focusName); focus->getProjection(0)->setStereotaxicXYZ(xyz); focus->setComment(comment); FociPropertiesEditorDialog::createFocus(focus, this->browserTabContent, this->parentOpenGLWidget); } /** * Called to create a focus at a node location */ void BrainOpenGLWidgetContextMenu::createSurfaceIDSymbolFocusSelected() { SelectionItemSurfaceNodeIdentificationSymbol* nodeSymbolID = this->selectionManager->getSurfaceNodeIdentificationSymbol(); const Surface* surface = nodeSymbolID->getSurface(); const int32_t nodeIndex = nodeSymbolID->getNodeNumber(); const float* xyz = surface->getCoordinate(nodeIndex); const AString focusName = (StructureEnum::toGuiName(surface->getStructure()) + " Vertex " + AString::number(nodeIndex)); const AString comment = ("Created from " + focusName); Focus* focus = new Focus(); focus->setName(focusName); focus->getProjection(0)->setStereotaxicXYZ(xyz); focus->setComment(comment); FociPropertiesEditorDialog::createFocus(focus, this->browserTabContent, this->parentOpenGLWidget); } /** * Called to create a focus at a voxel location */ void BrainOpenGLWidgetContextMenu::createVolumeFocusSelected() { SelectionItemVoxel* voxelID = this->selectionManager->getVoxelIdentification(); const VolumeMappableInterface* vf = voxelID->getVolumeFile(); int64_t ijk[3]; voxelID->getVoxelIJK(ijk); float xyz[3]; vf->indexToSpace(ijk, xyz); const CaretMappableDataFile* cmdf = dynamic_cast(vf); const AString focusName = (cmdf->getFileNameNoPath() + " IJK (" + AString::fromNumbers(ijk, 3, ",") + ")"); const AString comment = ("Created from " + focusName); Focus* focus = new Focus(); focus->setName(focusName); focus->getProjection(0)->setStereotaxicXYZ(xyz); focus->setComment(comment); FociPropertiesEditorDialog::createFocus(focus, this->browserTabContent, this->parentOpenGLWidget); } /** * Called to display identication information on the surface focus. */ void BrainOpenGLWidgetContextMenu::identifySurfaceFocusSelected() { SelectionItemFocusSurface* focusID = this->selectionManager->getSurfaceFocusIdentification(); Brain* brain = focusID->getBrain(); this->selectionManager->clearOtherSelectedItems(focusID); const AString idMessage = this->selectionManager->getIdentificationText(brain); IdentificationManager* idManager = brain->getIdentificationManager(); idManager->addIdentifiedItem(new IdentifiedItem(idMessage)); EventManager::get()->sendEvent(EventUpdateInformationWindows().getPointer()); } /** * Called to display identication information on the volume focus. */ void BrainOpenGLWidgetContextMenu::identifyVolumeFocusSelected() { SelectionItemFocusVolume* focusID = this->selectionManager->getVolumeFocusIdentification(); Brain* brain = focusID->getBrain(); this->selectionManager->clearOtherSelectedItems(focusID); const AString idMessage = this->selectionManager->getIdentificationText(brain); IdentificationManager* idManager = brain->getIdentificationManager(); idManager->addIdentifiedItem(new IdentifiedItem(idMessage)); EventManager::get()->sendEvent(EventUpdateInformationWindows().getPointer()); } /** * Called to edit the surface focus. */ void BrainOpenGLWidgetContextMenu::editSurfaceFocusSelected() { SelectionItemFocusSurface* focusID = this->selectionManager->getSurfaceFocusIdentification(); Focus* focus = focusID->getFocus(); FociFile* fociFile = focusID->getFociFile(); FociPropertiesEditorDialog::editFocus(fociFile, focus, this->parentOpenGLWidget); } /** * Called to edit the volume focus. */ void BrainOpenGLWidgetContextMenu::editVolumeFocusSelected() { SelectionItemFocusVolume* focusID = this->selectionManager->getVolumeFocusIdentification(); Focus* focus = focusID->getFocus(); FociFile* fociFile = focusID->getFociFile(); FociPropertiesEditorDialog::editFocus(fociFile, focus, this->parentOpenGLWidget); } /** * Called to display identication information on the surface border. */ void BrainOpenGLWidgetContextMenu::identifySurfaceNodeSelected() { // SelectionItemSurfaceNode* surfaceID = this->selectionManager->getSurfaceNodeIdentification(); GuiManager::get()->processIdentification(this->browserTabContent->getTabNumber(), this->selectionManager, this->parentOpenGLWidget); /* * * * Brain* brain = surfaceID->getBrain(); this->selectionManager->clearOtherSelectedItems(surfaceID); const AString idMessage = this->selectionManager->getIdentificationText(brain); Surface* surface = surfaceID->getSurface(); const StructureEnum::Enum structure = surface->getStructure(); IdentificationManager* idManager = brain->getIdentificationManager(); idManager->addIdentifiedItem(new IdentifiedItemNode(idMessage, structure, surface->getNumberOfNodes(), surfaceID->getNodeNumber())); EventManager::get()->sendEvent(EventUpdateInformationWindows().getPointer()); * * */ } /** * Called to display identication information on the surface border. */ void BrainOpenGLWidgetContextMenu::identifyVoxelSelected() { SelectionItemVoxel* voxelID = this->selectionManager->getVoxelIdentification(); Brain* brain = voxelID->getBrain(); this->selectionManager->clearOtherSelectedItems(voxelID); const AString idMessage = this->selectionManager->getIdentificationText(brain); IdentificationManager* idManager = brain->getIdentificationManager(); idManager->addIdentifiedItem(new IdentifiedItem(idMessage)); EventManager::get()->sendEvent(EventUpdateInformationWindows().getPointer()); } /** * Called to remove all node identification symbols. */ void BrainOpenGLWidgetContextMenu::removeAllNodeIdentificationSymbolsSelected() { IdentificationManager* idManager = GuiManager::get()->getBrain()->getIdentificationManager(); idManager->removeAllIdentifiedItems(); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } /** * Called to remove node identification symbol from node. */ void BrainOpenGLWidgetContextMenu::removeNodeIdentificationSymbolSelected() { SelectionItemSurfaceNodeIdentificationSymbol* idSymbol = selectionManager->getSurfaceNodeIdentificationSymbol(); if (idSymbol->isValid()) { Surface* surface = idSymbol->getSurface(); IdentificationManager* idManager = GuiManager::get()->getBrain()->getIdentificationManager(); idManager->removeIdentifiedNodeItem(surface->getStructure(), surface->getNumberOfNodes(), idSymbol->getNodeNumber()); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } } /** * This is called when the data file is on the network to warn users if the * query is very large and may take a long time. * * @param numberOfBrainordinatesInROI * Number of brainordinates in the ROI. */ bool BrainOpenGLWidgetContextMenu::warnIfNetworkBrainordinateCountIsLarge(const int64_t numberOfBrainordinatesInROI) { if (numberOfBrainordinatesInROI < 200) { return true; } const QString msg = ("There are " + QString::number(numberOfBrainordinatesInROI) + " brainordinates in the selected region. Loading data from the network for " + "this quantity of brainordinates may take a very long time."); const bool result = WuQMessageBox::warningYesNo(this, "Do you want to continue?", msg); return result; } /* ------------------------------------------------------------------------- */ /** * Constructor. */ BrainOpenGLWidgetContextMenu::ParcelConnectivity::ParcelConnectivity(Brain* brain, const ParcelType parcelType, CaretMappableDataFile* mappableLabelFile, const int32_t labelFileMapIndex, const int32_t labelKey, const QString& labelName, Surface* surface, const int32_t nodeNumber, const int64_t volumeDimensions[3], ChartingDataManager* chartingDataManager, CiftiConnectivityMatrixDataFileManager* ciftiConnectivityManager, CiftiFiberTrajectoryManager* ciftiFiberTrajectoryManager) { this->brain = brain; this->parcelType = parcelType; this->mappableLabelFile = mappableLabelFile; this->labelFileMapIndex = labelFileMapIndex; this->labelKey = labelKey; this->labelName = labelName; this->surface = surface; this->nodeNumber = nodeNumber; this->volumeDimensions[0] = volumeDimensions[0]; this->volumeDimensions[1] = volumeDimensions[1]; this->volumeDimensions[2] = volumeDimensions[2]; this->chartingDataManager = chartingDataManager; this->ciftiConnectivityManager = ciftiConnectivityManager; this->ciftiFiberTrajectoryManager = ciftiFiberTrajectoryManager; } /** * Destructor. */ BrainOpenGLWidgetContextMenu::ParcelConnectivity::~ParcelConnectivity() { } /** * Get the node indices comprising the parcel. * * @param nodeIndicesOut * Contains node indices upon exit. */ void BrainOpenGLWidgetContextMenu::ParcelConnectivity::getNodeIndices(std::vector& nodeIndicesOut) const { nodeIndicesOut.clear(); if (this->parcelType != PARCEL_TYPE_SURFACE_NODES) { return; } CiftiBrainordinateLabelFile* ciftiLabelFile = dynamic_cast(mappableLabelFile); LabelFile* labelFile = dynamic_cast(mappableLabelFile); if (ciftiLabelFile != NULL) { ciftiLabelFile->getNodeIndicesWithLabelKey(surface->getStructure(), surface->getNumberOfNodes(), labelFileMapIndex, labelKey, nodeIndicesOut); } else if (labelFile != NULL) { labelFile->getNodeIndicesWithLabelKey(labelFileMapIndex, labelKey, nodeIndicesOut); } else { CaretAssertMessage(0, "Should never get here, new or invalid label file type for surface nodes"); } } /** * Get the voxel indices comprising the parcel * * @param voxelIndicesOut * Contains voxel indices upon exit. */ void BrainOpenGLWidgetContextMenu::ParcelConnectivity::getVoxelIndices(std::vector& voxelIndicesOut) const { voxelIndicesOut.clear(); if (this->parcelType != PARCEL_TYPE_VOLUME_VOXELS) { return; } CiftiBrainordinateLabelFile* ciftiLabelFile = dynamic_cast(mappableLabelFile); VolumeFile* volumeLabelFile = dynamic_cast(mappableLabelFile); if (ciftiLabelFile != NULL) { ciftiLabelFile->getVoxelIndicesWithLabelKey(labelFileMapIndex, labelKey, voxelIndicesOut); } else if (volumeLabelFile != NULL) { volumeLabelFile->getVoxelIndicesWithLabelKey(labelFileMapIndex, labelKey, voxelIndicesOut); } else { CaretAssertMessage(0, "Should never get here, new or invalid label file type for volume voxels"); } } workbench-1.1.1/src/GuiQt/BrainOpenGLWidgetContextMenu.h000066400000000000000000000132221255417355300231350ustar00rootroot00000000000000#ifndef __BRAIN_OPEN_G_L_WIDGET_CONTEXT_MENU__H_ #define __BRAIN_OPEN_G_L_WIDGET_CONTEXT_MENU__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include "VolumeSliceViewPlaneEnum.h" #include "VoxelIJK.h" class QAction; namespace caret { class Brain; class BrainOpenGLWidget; class BrowserTabContent; class CaretMappableDataFile; class CiftiConnectivityMatrixDataFileManager; class CiftiFiberTrajectoryManager; class ChartingDataManager; class SelectionManager; class LabelFile; class Surface; class BrainOpenGLWidgetContextMenu : public QMenu { Q_OBJECT public: BrainOpenGLWidgetContextMenu(SelectionManager* selectionManager, BrowserTabContent* browserTabContent, BrainOpenGLWidget* parentOpenGLWidget); virtual ~BrainOpenGLWidgetContextMenu(); private slots: void createSurfaceFocusSelected(); void createSurfaceIDSymbolFocusSelected(); void createVolumeFocusSelected(); void editSurfaceFocusSelected(); void editVolumeFocusSelected(); void removeAllNodeIdentificationSymbolsSelected(); void removeNodeIdentificationSymbolSelected(); void identifySurfaceBorderSelected(); void identifySurfaceFocusSelected(); void identifyVolumeFocusSelected(); void identifySurfaceNodeSelected(); void identifyVoxelSelected(); void parcelCiftiFiberTrajectoryActionSelected(QAction* action); void parcelCiftiConnectivityActionSelected(QAction* action); void parcelChartableDataActionSelected(QAction* action); void borderCiftiConnectivitySelected(); // void borderConnectivitySelected(); void borderDataSeriesSelected(); private: class ParcelConnectivity { public: enum ParcelType { PARCEL_TYPE_INVALID, PARCEL_TYPE_SURFACE_NODES, PARCEL_TYPE_VOLUME_VOXELS }; ParcelConnectivity(Brain* brain, const ParcelType parcelType, CaretMappableDataFile* mappableLabelFile, const int32_t labelFileMapIndex, const int32_t labelKey, const QString& labelName, Surface* surface, const int32_t nodeNumber, const int64_t volumeDimensions[3], ChartingDataManager* chartingDataManager, CiftiConnectivityMatrixDataFileManager* ciftiConnectivityManager, CiftiFiberTrajectoryManager* ciftiFiberTrajectoryManager); virtual ~ParcelConnectivity(); void getNodeIndices(std::vector& nodeIndicesOut) const; void getVoxelIndices(std::vector& voxelIndicesOut) const; Brain* brain; ParcelType parcelType; CaretMappableDataFile* mappableLabelFile; int32_t labelFileMapIndex; int32_t labelKey; QString labelName; Surface* surface; int32_t nodeNumber; int64_t volumeDimensions[3]; CiftiConnectivityMatrixDataFileManager* ciftiConnectivityManager; ChartingDataManager* chartingDataManager; CiftiFiberTrajectoryManager* ciftiFiberTrajectoryManager; }; BrainOpenGLWidgetContextMenu(const BrainOpenGLWidgetContextMenu&); BrainOpenGLWidgetContextMenu& operator=(const BrainOpenGLWidgetContextMenu&); bool warnIfNetworkBrainordinateCountIsLarge(const int64_t numberOfBrainordinatesInROI); bool enableDataSeriesGraphsIfNoneEnabled(); void addIdentificationActions(); void addBorderRegionOfInterestActions(); void addFociActions(); void addLabelRegionOfInterestActions(); void addActionsToMenu(QList& actionsToAdd, const bool addSeparatorBeforeActions); BrainOpenGLWidget* parentOpenGLWidget; std::vector parcelConnectivities; SelectionManager* selectionManager; BrowserTabContent* browserTabContent; }; #ifdef __BRAIN_OPEN_G_L_WIDGET_CONTEXT_MENU_DECLARE__ // #endif // __BRAIN_OPEN_G_L_WIDGET_CONTEXT_MENU_DECLARE__ } // namespace #endif //__BRAIN_OPEN_G_L_WIDGET_CONTEXT_MENU__H_ workbench-1.1.1/src/GuiQt/BrainOpenGLWidgetTextRenderer.cxx000066400000000000000000000176741255417355300236710ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __BRAIN_OPEN_G_L_WIDGET_TEXT_RENDERER_DECLARE__ #include "BrainOpenGLWidgetTextRenderer.h" #undef __BRAIN_OPEN_G_L_WIDGET_TEXT_RENDERER_DECLARE__ #include #include "CaretAssert.h" #include "CaretLogger.h" using namespace caret; /** * \class caret::BrainOpenGLWidgetTextRenderer * \brief Text rendering using QGLWidget. * * Draws text using methods in QGLWidget. * * @param glWidget * QGLWidget that does the text rendering. */ /** * Constructor. */ BrainOpenGLWidgetTextRenderer::BrainOpenGLWidgetTextRenderer(QGLWidget* glWidget) : BrainOpenGLTextRenderInterface() { m_glWidget = glWidget; m_normalFont.initialize("Arial", NORMAL); m_boldFont.initialize("Arial", BOLD); } /** * Destructor. */ BrainOpenGLWidgetTextRenderer::~BrainOpenGLWidgetTextRenderer() { } /** * @return The font system is valid. */ bool BrainOpenGLWidgetTextRenderer::isValid() const { return true; } /** * Draw text at the given window coordinates. * * @param viewport * The current viewport. * @param windowX * X-coordinate in the window of first text character * using the 'alignment' * @param windowY * Y-coordinate in the window at which bottom of text is placed. * @param text * Text that is to be drawn. * @param alignment * Alignment of text * @param textStyle * Style of the text. * @param fontHeight * Height of the text. * @param fontName * Name of the font. */ void BrainOpenGLWidgetTextRenderer::drawTextAtWindowCoords(const int viewport[4], const int windowX, const int windowY, const QString& text, const TextAlignmentX alignmentX, const TextAlignmentY alignmentY, const TextStyle textStyle, const int fontHeight) { int32_t width, height; getTextBoundsInPixels(width, height, text, textStyle, fontHeight); /* * Find font */ QFont* font = findFont(textStyle, fontHeight); if (font == NULL) { return; } /* * X-Coordinate of text */ int x = windowX + viewport[0]; switch (alignmentX) { case X_LEFT: break; case X_CENTER: x -= width / 2; break; case X_RIGHT: x -= width; break; } /* * Y-Coordinate of text * Note that QGLWidget has origin at top left corner */ int y = m_glWidget->height() - (windowY + viewport[1]); switch (alignmentY) { case Y_BOTTOM: break; case Y_CENTER: y += height / 2; break; case Y_TOP: y += height; break; } m_glWidget->renderText(x, y, text, *font); } /** * Draw text at the given model coordinates. * * @param modelX * X-coordinate in model space of first text character * @param modelY * Y-coordinate in model space. * @param modelZ * Z-coordinate in model space. * @param text * Text that is to be drawn. * @param textStyle * Style of the text. * @param fontHeight * Height of the text. * @param fontName * Name of the font. */ void BrainOpenGLWidgetTextRenderer::drawTextAtModelCoords(const double modelX, const double modelY, const double modelZ, const QString& text, const TextStyle textStyle, const int fontHeight) { /* * Find font */ QFont* font = findFont(textStyle, fontHeight); if (font == NULL) { return; } m_glWidget->renderText(modelX, modelY, modelZ, text, *font); } /** * Get the bounds of the text (in pixels) using the given text * attributes. * * @param widthOut * Output containing width of text characters. * @param heightOut * Output containing height of text characters. * @param text * Text that is to be drawn. * @param textStyle * Style of the text. * @param fontHeight * Height of the text. * @param fontName * Name of the font. */ void BrainOpenGLWidgetTextRenderer::getTextBoundsInPixels(int32_t& widthOut, int32_t& heightOut, const QString& text, const TextStyle textStyle, const int fontHeight) { QFont* font = findFont(textStyle, fontHeight); if (font == NULL) { return; } QFontMetricsF fontMetrics(*font); QRectF boundsRect = fontMetrics.boundingRect(text); widthOut = boundsRect.width(); heightOut = boundsRect.height(); } /** * Find a font with the given name, height, and style. * Once a font is created it is cached so that it can be * used again and avoids font creation which may be * expensive. * * @return a font */ QFont* BrainOpenGLWidgetTextRenderer::findFont(const TextStyle textStyle, const int fontHeight) { QFont* font = NULL; switch (textStyle) { case BrainOpenGLTextRenderInterface::BOLD: if (m_boldFont.m_fontValid) { font = m_boldFont.m_font; } break; case BrainOpenGLTextRenderInterface::NORMAL: if (m_normalFont.m_fontValid) { font = m_normalFont.m_font; } break; } font->setPointSize(fontHeight); return font; } /** * Constructor. */ BrainOpenGLWidgetTextRenderer::FontData::FontData() { m_font = NULL; m_fontValid = false; } /** * Destructor. */ BrainOpenGLWidgetTextRenderer::FontData::~FontData() { delete m_font; m_font = NULL; } /** * @param fontName * Name of font. * @param textStyle * Style of font. */ void BrainOpenGLWidgetTextRenderer::FontData::initialize(const AString& fontName, const TextStyle textStyle) { m_font = new QFont(fontName); switch (textStyle) { case BrainOpenGLTextRenderInterface::BOLD: m_font->setBold(true); break; case BrainOpenGLTextRenderInterface::NORMAL: break; } m_fontValid = true; } /** * @return Name of the text renderer. */ AString BrainOpenGLWidgetTextRenderer::getName() const { return "QT's QGLWidget"; } workbench-1.1.1/src/GuiQt/BrainOpenGLWidgetTextRenderer.h000066400000000000000000000065761255417355300233150ustar00rootroot00000000000000#ifndef __BRAIN_OPEN_G_L_WIDGET_TEXT_RENDERER__H_ #define __BRAIN_OPEN_G_L_WIDGET_TEXT_RENDERER__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "BrainOpenGLTextRenderInterface.h" class QGLWidget; class QFont; namespace caret { class BrainOpenGLWidgetTextRenderer : public BrainOpenGLTextRenderInterface { public: BrainOpenGLWidgetTextRenderer(QGLWidget* glWidget); virtual ~BrainOpenGLWidgetTextRenderer(); bool isValid() const; void drawTextAtWindowCoords(const int viewport[4], const int windowX, const int windowY, const QString& text, const TextAlignmentX alignmentX, const TextAlignmentY alignmentY, const TextStyle textStyle, const int fontHeight); void drawTextAtModelCoords(const double modelX, const double modelY, const double modelZ, const QString& text, const TextStyle textStyle, const int fontHeight); void getTextBoundsInPixels(int32_t& widthOut, int32_t& heightOut, const QString& text, const TextStyle textStyle, const int fontHeight); virtual AString getName() const; private: BrainOpenGLWidgetTextRenderer(const BrainOpenGLWidgetTextRenderer&); BrainOpenGLWidgetTextRenderer& operator=(const BrainOpenGLWidgetTextRenderer&); private: class FontData { public: FontData(); ~FontData(); void initialize(const AString& fontName, const TextStyle textStyle); QFont* m_font; bool m_fontValid; }; FontData m_normalFont; FontData m_boldFont; QFont* findFont(const TextStyle textStyle, const int fontHeight); QGLWidget* m_glWidget; }; #ifdef __BRAIN_OPEN_G_L_WIDGET_TEXT_RENDERER_DECLARE__ // #endif // __BRAIN_OPEN_G_L_WIDGET_TEXT_RENDERER_DECLARE__ } // namespace #endif //__BRAIN_OPEN_G_L_WIDGET_TEXT_RENDERER__H_ workbench-1.1.1/src/GuiQt/BugReportDialog.cxx000066400000000000000000000165451255417355300211160ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #include #include #include #include #define __BUG_REPORT_DIALOG_DECLARE__ #include "BugReportDialog.h" #undef __BUG_REPORT_DIALOG_DECLARE__ #include "ApplicationInformation.h" #include "CaretAssert.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::BugReportDialog * \brief Dialog for reporting bugs in Workbench. * \ingroup GuiQt */ /** * Constructor. * * @param parent * Parent on which this dialog is displayed. * @param openGLInformation * Information about OpenGL. */ BugReportDialog::BugReportDialog(QWidget* parent, const AString& openGLInformation) : WuQDialogNonModal("Report Workbench Bug", parent) { const AString emailAddress("workbench_bugs@brainvis.wustl.edu"); m_emailAddressURL = ("mailto:" + emailAddress + "?subject=Workbench Bug Report"); m_uploadWebSite = "http://brainvis.wustl.edu/cgi-bin/upload.cgi"; const AString newLines("\n\n"); AString bugInfo; bugInfo.appendWithNewLine(""); bugInfo.appendWithNewLine("(1) SUMMARY_OF_PROBLEM" + newLines); bugInfo.appendWithNewLine("(2) HOW TO REPRODUCE PROBLEM" + newLines); bugInfo.appendWithNewLine("(3) WHAT HAPPENS" + newLines); bugInfo.appendWithNewLine("(4) WHAT SHOULD HAPPEN" + newLines); bugInfo.appendWithNewLine("(5) NAME OF UPLOADED DATA ZIP FILE (Press \"Upload Data\" button to go to the upload website)" + newLines); bugInfo.appendWithNewLine("(6) SCREEN CAPTURES - After this information is copied into your email client, " "image captures (File Menu->Capture Image) that help describe the bug may be " "copied into the email message." + newLines); bugInfo.appendWithNewLine("------------------------------------------------------------------"); bugInfo.appendWithNewLine("PLEASE DO NOT CHANGE TEXT BELOW THIS LINE.\n" "It is used to help us understand the context of your reported bug.\n"); bugInfo.appendWithNewLine(ApplicationInformation().getAllInformationInString("\n\n")); bugInfo.appendWithNewLine(openGLInformation); const AString hcpURL("http://humanconnectome.org/connectome/get-connectome-workbench.html"); const AString infoMessage = ("Verify that your bug occurs in the latest version of Workbench: " "" + hcpURL + "." + "
" + "You are using version " + ApplicationInformation().getVersion() + "." + "

" + "Please address each of the numbered items in the field below." + " Use the \"Copy to Email\" button to submit your bug report to " + emailAddress + "

" + "Data files that cause the bug may be uploaded (as a ZIP file) at " + "" + m_uploadWebSite + "." + " wb_command -zip-spec-file can be used to zip a data set." + ""); QLabel* versionLabel = new QLabel(infoMessage); versionLabel->setWordWrap(true); versionLabel->setOpenExternalLinks(true); m_textEdit = new QTextEdit(); m_textEdit->setMinimumSize(600, 400); m_textEdit->setText(bugInfo); QPushButton* copyToClipboardPushButton = new QPushButton("Copy to Clipboard"); QObject::connect(copyToClipboardPushButton, SIGNAL(clicked()), this, SLOT(copyToClipboard())); QPushButton* copyToEmailPushButton = new QPushButton("Copy to Email"); QObject::connect(copyToEmailPushButton, SIGNAL(clicked()), this, SLOT(copyToEmail())); QPushButton* uploadWebSitePushButton = new QPushButton("Upload Data"); QObject::connect(uploadWebSitePushButton, SIGNAL(clicked()), this, SLOT(openUploadWebsite())); copyToClipboardPushButton->setToolTip("Copies information to the computer's clipboard"); copyToEmailPushButton->setToolTip("Sends information to the user's email client"); uploadWebSitePushButton->setToolTip("Display upload website in user's web browser"); QHBoxLayout* buttonsLayout = new QHBoxLayout(); buttonsLayout->addStretch(); buttonsLayout->addWidget(uploadWebSitePushButton); buttonsLayout->addStretch(); buttonsLayout->addWidget(copyToClipboardPushButton); buttonsLayout->addStretch(); buttonsLayout->addWidget(copyToEmailPushButton); buttonsLayout->addStretch(); QWidget* contentWidget = new QWidget; QVBoxLayout* layout = new QVBoxLayout(contentWidget); layout->addWidget(versionLabel, 0); layout->addSpacing(8); layout->addWidget(m_textEdit, 1000); layout->addLayout(buttonsLayout, 0); setCentralWidget(contentWidget, WuQDialog::SCROLL_AREA_NEVER); setApplyButtonText(""); disableAutoDefaultForAllPushButtons(); } /** * Destructor. */ BugReportDialog::~BugReportDialog() { } /** * Copy the text to the user's clipboard. */ void BugReportDialog::copyToClipboard() { QApplication::clipboard()->setText(m_textEdit->toPlainText().trimmed(), QClipboard::Clipboard); } /** * Copy the text to the user's email client. */ void BugReportDialog::copyToEmail() { const AString mailURL = (m_emailAddressURL + "&body=" + m_textEdit->toPlainText().trimmed()); QUrl url(mailURL); QDesktopServices::openUrl(url); } /** * Update the dialog. */ void BugReportDialog::updateDialog() { /* nothing to do */ } /** * Open the Upload Website in the user's web browser. */ void BugReportDialog::openUploadWebsite() { QDesktopServices::openUrl(QUrl(m_uploadWebSite)); } workbench-1.1.1/src/GuiQt/BugReportDialog.h000066400000000000000000000036431255417355300205360ustar00rootroot00000000000000#ifndef __BUG_REPORT_DIALOG_H__ #define __BUG_REPORT_DIALOG_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "WuQDialogNonModal.h" class QTextEdit; namespace caret { class BugReportDialog : public WuQDialogNonModal { Q_OBJECT public: BugReportDialog(QWidget* parent, const AString& openGLInformation); virtual ~BugReportDialog(); virtual void updateDialog(); private: BugReportDialog(const BugReportDialog&); BugReportDialog& operator=(const BugReportDialog&); public: // ADD_NEW_METHODS_HERE private slots: void copyToClipboard(); void copyToEmail(); void openUploadWebsite(); private: QTextEdit* m_textEdit; QString m_emailAddressURL; QString m_uploadWebSite; // ADD_NEW_MEMBERS_HERE }; #ifdef __BUG_REPORT_DIALOG_DECLARE__ // #endif // __BUG_REPORT_DIALOG_DECLARE__ } // namespace #endif //__BUG_REPORT_DIALOG_H__ workbench-1.1.1/src/GuiQt/CMakeLists.txt000066400000000000000000000321631255417355300200730ustar00rootroot00000000000000 # # Name of project # PROJECT (GuiQt) # # Use OpenGL from QT # SET(QT_USE_QTNETWORK TRUE) SET(QT_USE_QTOPENGL TRUE) SET(QT_USE_QTWEBKIT TRUE) # # QT include files # INCLUDE(${QT_USE_FILE}) # # Headers that must be processed with QT's "moc" # Any class that derives from a QT class and contains # the "Q_OBJECT" macro must be listed here. # # Also LIST each of these headers in the # source files section so that they show # up in development tools such as XCode. # SET(MOC_INPUT_HEADER_FILES AboutWorkbenchDialog.h BorderEditingSelectionDialog.h BorderFileSplitDialog.h BorderOptimizeDialog.h BorderPropertiesEditorDialog.h BorderSelectionViewController.h BrainBrowserWindow.h BrainBrowserWindowComboBox.h BrainBrowserWindowOrientedToolBox.h BrainBrowserWindowToolBar.h BrainBrowserWindowToolBarChartAttributes.h BrainBrowserWindowToolBarChartAxes.h BrainBrowserWindowToolBarChartType.h BrainBrowserWindowToolBarClipping.h BrainBrowserWindowToolBarComponent.h BrainBrowserWindowToolBarSlicePlane.h BrainBrowserWindowToolBarSliceSelection.h BrainBrowserWindowToolBarSurfaceMontage.h BrainBrowserWindowToolBarTab.h BrainBrowserWindowToolBarVolumeMontage.h BrainOpenGLWidget.h BrainOpenGLWidgetContextMenu.h BugReportDialog.h CaretColorEnumComboBox.h CaretDataFileSelectionComboBox.h CaretFileDialog.h CaretFileDialogExtendable.h CaretFileRemoteDialog.h CaretMappableDataFileAndMapSelector.h CaretMappableDataFileAndMapSelectorObject.h ChartHistoryViewController.h ChartLinesSelectionViewController.h ChartMatrixParcelSelectionViewController.h ChartMatrixSeriesSelectionViewController.h ChartSelectionViewController.h ChartToolBoxViewController.h CiftiConnectivityMatrixViewController.h CiftiParcelSelectionComboBox.h ClippingPlanesDialog.h ColorEditorWidget.h CustomViewDialog.h DisplayGroupEnumComboBox.h EnumComboBoxTemplate.h FiberOrientationSelectionViewController.h FiberSamplesOpenGLWidget.h FociProjectionDialog.h FociPropertiesEditorDialog.h FociSelectionViewController.h GiftiLabelTableEditor.h GiftiLabelTableSelectionComboBox.h GroupAndNameHierarchyViewController.h GuiManager.h HelpViewerDialog.h HyperLinkTextBrowser.h IdentifyBrainordinateDialog.h ImageCaptureDialog.h ImageSelectionViewController.h InformationDisplayDialog.h InformationDisplayPropertiesDialog.h LabelSelectionViewController.h MacApplication.h MacDockMenu.h MapSettingsFiberTrajectoryWidget.h MapSettingsLabelsWidget.h MapSettingsLayerWidget.h MapSettingsPaletteColorMappingWidget.h MapSettingsParcelsWidget.h MapYokingGroupComboBox.h MetaDataEditorDialog.h MetaDataEditorWidget.h MovieDialog.h OverlaySetViewController.h OverlaySettingsEditorDialog.h OverlayViewController.h PaletteColorMappingEditorDialog.h PlotMagnifier.h PlotPanner.h PreferencesDialog.h ProgressReportingDialog.h ProgressReportingWithSlots.h RegionOfInterestCreateFromBorderDialog.h SceneCreateReplaceDialog.h SceneDialog.h SpecFileManagementDialog.h SplashScreen.h StructureEnumComboBox.h StructureSurfaceSelectionControl.h SurfacePropertiesEditorDialog.h SurfaceSelectionViewController.h TileTabsConfigurationDialog.h UserInputModeBordersWidget.h UserInputModeFociWidget.h UserInputModeVolumeEditWidget.h UsernamePasswordWidget.h VolumeFileCreateDialog.h VolumeSurfaceOutlineColorOrTabViewController.h VolumeSurfaceOutlineSetViewController.h VolumeSurfaceOutlineViewController.h WuQCollapsibleWidget.h WuQDataEntryDialog.h WuQDialog.h WuQDialogModal.h WuQDialogNonModal.h WuQDoubleSlider.h WuQEventBlockingFilter.h WuQGridLayoutGroup.h WuQGroupBoxExclusiveWidget.h WuQImageLabel.h WuQListWidget.h WuQMessageBox.h WuQwtPlot.h WuQSpinBoxGroup.h WuQSpinBoxOddValue.h WuQTabWidget.h WuQTimedMessageDisplay.h WuQTreeWidget.h WuQTrueFalseComboBox.h WuQWebView.h WuQWidget.h WuQWidgetObjectGroup.h ) # # Header files # # If the header file is qt 'moc' file # also list it in the section above # named MOC_INPUT_HEADER_FILES # SET(SOURCE_FILES AboutWorkbenchDialog.h BorderEditingSelectionDialog.h BorderFileSplitDialog.h BorderOptimizeDialog.h BorderOptimizeExecutor.h BorderPropertiesEditorDialog.h BorderSelectionViewController.h BrainBrowserWindow.h BrainBrowserWindowComboBox.h BrainBrowserWindowOrientedToolBox.h BrainBrowserWindowToolBar.h BrainBrowserWindowToolBarChartAttributes.h BrainBrowserWindowToolBarChartAxes.h BrainBrowserWindowToolBarChartType.h BrainBrowserWindowToolBarClipping.h BrainBrowserWindowToolBarComponent.h BrainBrowserWindowToolBarSlicePlane.h BrainBrowserWindowToolBarSliceSelection.h BrainBrowserWindowToolBarSurfaceMontage.h BrainBrowserWindowToolBarTab.h BrainBrowserWindowToolBarVolumeMontage.h BrainOpenGLWidget.h BrainOpenGLWidgetContextMenu.h BrainOpenGLWidgetTextRenderer.h BugReportDialog.h CaretColorEnumComboBox.h CaretDataFileSelectionComboBox.h CaretFileDialog.h CaretFileDialogExtendable.h CaretFileRemoteDialog.h CaretMappableDataFileAndMapSelector.h CaretMappableDataFileAndMapSelectorObject.h ChartHistoryViewController.h ChartLinesSelectionViewController.h ChartMatrixParcelSelectionViewController.h ChartMatrixSeriesSelectionViewController.h ChartSelectionViewController.h ChartToolBoxViewController.h CiftiConnectivityMatrixViewController.h CiftiParcelSelectionComboBox.h ClippingPlanesDialog.h ColorEditorWidget.h CursorDisplayScoped.h CursorEnum.h CursorManager.h CustomViewDialog.h DisplayGroupEnumComboBox.h EnumComboBoxTemplate.h EventBrowserTabGetAllViewed.h EventBrowserWindowContentGet.h EventBrowserWindowCreateTabs.h EventBrowserWindowGraphicsRedrawn.h EventBrowserWindowNew.h EventGetOrSetUserInputModeProcessor.h EventGraphicsUpdateAllWindows.h EventGraphicsUpdateOneWindow.h EventHelpViewerDisplay.h EventImageCapture.h EventMacDockMenuUpdate.h EventOperatingSystemRequestOpenDataFile.h EventOverlaySettingsEditorDialogRequest.h EventPaletteColorMappingEditorDialogRequest.h EventUpdateInformationWindows.h EventUpdateVolumeEditingToolBar.h EventUpdateYokedWindows.h EventUserInterfaceUpdate.h FiberOrientationSelectionViewController.h FiberSamplesOpenGLWidget.h FociProjectionDialog.h FociPropertiesEditorDialog.h FociSelectionViewController.h GiftiLabelTableEditor.h GiftiLabelTableSelectionComboBox.h GroupAndNameHierarchyTreeWidgetItem.h GroupAndNameHierarchyViewController.h GuiManager.h HelpViewerDialog.h HyperLinkTextBrowser.h IdentifyBrainordinateDialog.h ImageCaptureDialog.h ImageSelectionViewController.h InformationDisplayDialog.h InformationDisplayPropertiesDialog.h LabelSelectionViewController.h MacApplication.h MacDockMenu.h MapSettingsFiberTrajectoryWidget.h MapSettingsLabelsWidget.h MapSettingsLayerWidget.h MapSettingsPaletteColorMappingWidget.h MapSettingsParcelsWidget.h MapYokingGroupComboBox.h MetaDataEditorDialog.h MetaDataEditorWidget.h MouseEvent.h MovieDialog.h OverlaySetViewController.h OverlaySettingsEditorDialog.h OverlayViewController.h PaletteColorMappingEditorDialog.h PlotMagnifier.h PlotPanner.h PreferencesDialog.h ProgressReportingDialog.h ProgressReportingWithSlots.h RegionOfInterestCreateFromBorderDialog.h SceneCreateReplaceDialog.h SceneDialog.h SceneWindowGeometry.h SpecFileManagementDialog.h SplashScreen.h StructureEnumComboBox.h StructureSurfaceSelectionControl.h SurfacePropertiesEditorDialog.h SurfaceSelectionViewController.h TileTabsConfigurationDialog.h UserInputModeAbstract.h UserInputModeBorders.h UserInputModeBordersWidget.h UserInputModeFociWidget.h UserInputModeFoci.h UserInputModeView.h UserInputModeVolumeEdit.h UserInputModeVolumeEditWidget.h UsernamePasswordWidget.h ViewModeEnum.h VolumeFileCreateDialog.h VolumeSurfaceOutlineColorOrTabViewController.h VolumeSurfaceOutlineSetViewController.h VolumeSurfaceOutlineViewController.h WuQCollapsibleWidget.h WuQDataEntryDialog.h WuQDialog.h WuQDialogModal.h WuQDialogNonModal.h WuQDoubleSlider.h WuQEventBlockingFilter.h WuQFactory.h WuQGridLayoutGroup.h WuQGroupBoxExclusiveWidget.h WuQImageLabel.h WuQListWidget.h WuQMessageBox.h WuQwtPlot.h WuQSpinBoxGroup.h WuQSpinBoxOddValue.h WuQTabWidget.h WuQTimedMessageDisplay.h WuQTreeWidget.h WuQTrueFalseComboBox.h WuQWebView.h WuQWidget.h WuQWidgetObjectGroup.h WuQtUtilities.h AboutWorkbenchDialog.cxx BorderEditingSelectionDialog.cxx BorderFileSplitDialog.cxx BorderOptimizeDialog.cxx BorderOptimizeExecutor.cxx BorderPropertiesEditorDialog.cxx BorderSelectionViewController.cxx BrainBrowserWindow.cxx BrainBrowserWindowComboBox.cxx BrainBrowserWindowOrientedToolBox.cxx BrainBrowserWindowToolBar.cxx BrainBrowserWindowToolBarChartAttributes.cxx BrainBrowserWindowToolBarChartAxes.cxx BrainBrowserWindowToolBarChartType.cxx BrainBrowserWindowToolBarClipping.cxx BrainBrowserWindowToolBarComponent.cxx BrainBrowserWindowToolBarSlicePlane.cxx BrainBrowserWindowToolBarSliceSelection.cxx BrainBrowserWindowToolBarSurfaceMontage.cxx BrainBrowserWindowToolBarTab.cxx BrainBrowserWindowToolBarVolumeMontage.cxx BrainOpenGLWidget.cxx BrainOpenGLWidgetContextMenu.cxx BrainOpenGLWidgetTextRenderer.cxx BugReportDialog.cxx CaretColorEnumComboBox.cxx CaretDataFileSelectionComboBox.cxx CaretFileDialog.cxx CaretFileDialogExtendable.cxx CaretFileRemoteDialog.cxx CaretMappableDataFileAndMapSelector.cxx CaretMappableDataFileAndMapSelectorObject.cxx ChartHistoryViewController.cxx ChartLinesSelectionViewController.cxx ChartMatrixParcelSelectionViewController.cxx ChartMatrixSeriesSelectionViewController.cxx ChartSelectionViewController.cxx ChartToolBoxViewController.cxx CiftiConnectivityMatrixViewController.cxx CiftiParcelSelectionComboBox.cxx ClippingPlanesDialog.cxx ColorEditorWidget.cxx CursorDisplayScoped.cxx CursorEnum.cxx CursorManager.cxx CustomViewDialog.cxx DisplayGroupEnumComboBox.cxx EventBrowserTabGetAllViewed.cxx EventBrowserWindowContentGet.cxx EventBrowserWindowCreateTabs.cxx EventBrowserWindowGraphicsRedrawn.cxx EventBrowserWindowNew.cxx EventGetOrSetUserInputModeProcessor.cxx EventGraphicsUpdateAllWindows.cxx EventGraphicsUpdateOneWindow.cxx EventHelpViewerDisplay.cxx EventImageCapture.cxx EventMacDockMenuUpdate.cxx EventOperatingSystemRequestOpenDataFile.cxx EventOverlaySettingsEditorDialogRequest.cxx EventPaletteColorMappingEditorDialogRequest.cxx EventUpdateInformationWindows.cxx EventUpdateVolumeEditingToolBar.cxx EventUpdateYokedWindows.cxx EventUserInterfaceUpdate.cxx FiberOrientationSelectionViewController.cxx FiberSamplesOpenGLWidget.cxx FociProjectionDialog.cxx FociPropertiesEditorDialog.cxx FociSelectionViewController.cxx GiftiLabelTableEditor.cxx GiftiLabelTableSelectionComboBox.cxx GroupAndNameHierarchyTreeWidgetItem.cxx GroupAndNameHierarchyViewController.cxx GuiManager.cxx HelpViewerDialog.cxx HyperLinkTextBrowser.cxx IdentifyBrainordinateDialog.cxx ImageCaptureDialog.cxx ImageSelectionViewController.cxx InformationDisplayDialog.cxx InformationDisplayPropertiesDialog.cxx LabelSelectionViewController.cxx MacApplication.cxx MacDockMenu.cxx MapSettingsFiberTrajectoryWidget.cxx MapSettingsLabelsWidget.cxx MapSettingsLayerWidget.cxx MapSettingsPaletteColorMappingWidget.cxx MapSettingsParcelsWidget.cxx MapYokingGroupComboBox.cxx MetaDataEditorDialog.cxx MetaDataEditorWidget.cxx MouseEvent.cxx MovieDialog.cxx OverlaySetViewController.cxx OverlayViewController.cxx OverlaySettingsEditorDialog.cxx PaletteColorMappingEditorDialog.cxx PlotMagnifier.cxx PlotPanner.cxx PreferencesDialog.cxx ProgressReportingDialog.cxx ProgressReportingWithSlots.cxx RegionOfInterestCreateFromBorderDialog.cxx SceneCreateReplaceDialog.cxx SceneDialog.cxx SceneWindowGeometry.cxx SpecFileManagementDialog.cxx SplashScreen.cxx StructureEnumComboBox.cxx StructureSurfaceSelectionControl.cxx SurfacePropertiesEditorDialog.cxx SurfaceSelectionViewController.cxx TileTabsConfigurationDialog.cxx UserInputModeAbstract.cxx UserInputModeBorders.cxx UserInputModeBordersWidget.cxx UserInputModeFoci.cxx UserInputModeFociWidget.cxx UserInputModeView.cxx UserInputModeVolumeEdit.cxx UserInputModeVolumeEditWidget.cxx UsernamePasswordWidget.cxx ViewModeEnum.cxx VolumeFileCreateDialog.cxx VolumeSurfaceOutlineColorOrTabViewController.cxx VolumeSurfaceOutlineSetViewController.cxx VolumeSurfaceOutlineViewController.cxx WuQCollapsibleWidget.cxx WuQDataEntryDialog.cxx WuQDialog.cxx WuQDialogModal.cxx WuQDialogNonModal.cxx WuQDoubleSlider.cxx WuQEventBlockingFilter.cxx WuQFactory.cxx WuQGridLayoutGroup.cxx WuQGroupBoxExclusiveWidget.cxx WuQImageLabel.cxx WuQListWidget.cxx WuQMessageBox.cxx WuQwtPlot.cxx WuQSpinBoxGroup.cxx WuQSpinBoxOddValue.cxx WuQTabWidget.cxx WuQTimedMessageDisplay.cxx WuQTreeWidget.cxx WuQTrueFalseComboBox.cxx WuQWebView.cxx WuQWidget.cxx WuQWidgetObjectGroup.cxx WuQtUtilities.cxx ) SET(FORMS MovieDialog.ui) # # Process the header files with moc producing moc_*.cpp files # INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/GuiQt) QT4_WRAP_UI(FORMS_HEADERS ${FORMS}) QT4_WRAP_CPP(MOC_SOURCE_FILES ${MOC_INPUT_HEADER_FILES}) # # Create the GUI library # ADD_LIBRARY(GuiQt ${SOURCE_FILES} ${MOC_SOURCE_FILES} ${FORMS_HEADERS} ) INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/GuiQt ${CMAKE_SOURCE_DIR}/Qwt ${CMAKE_SOURCE_DIR}/Algorithms ${CMAKE_SOURCE_DIR}/Brain ${CMAKE_SOURCE_DIR}/Commands ${CMAKE_SOURCE_DIR}/Charting ${CMAKE_SOURCE_DIR}/FilesBase ${CMAKE_SOURCE_DIR}/Files ${CMAKE_SOURCE_DIR}/Cifti ${CMAKE_SOURCE_DIR}/Gifti ${CMAKE_SOURCE_DIR}/Nifti ${CMAKE_SOURCE_DIR}/OSMesaDummy ${CMAKE_SOURCE_DIR}/Operations ${CMAKE_SOURCE_DIR}/OperationsBase ${CMAKE_SOURCE_DIR}/Palette ${CMAKE_SOURCE_DIR}/Scenes ${CMAKE_SOURCE_DIR}/Xml ${CMAKE_SOURCE_DIR}/Common ) workbench-1.1.1/src/GuiQt/CaretColorEnumComboBox.cxx000066400000000000000000000077561255417355300224040ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CARET_COLOR_ENUM_COMBOBOX_DECLARE__ #include "CaretColorEnumComboBox.h" #undef __CARET_COLOR_ENUM_COMBOBOX_DECLARE__ #include using namespace caret; /** * \class caret::CaretColorEnumComboBox * \brief Control for selection of Caret Color enumerated types. * * Control for selection of Caret Color enumerated types. */ /** * Constructor. * @param parent * Parent object. */ CaretColorEnumComboBox::CaretColorEnumComboBox(QObject* parent) : WuQWidget(parent) { this->colorComboBox = new QComboBox(); std::vector colors; CaretColorEnum::getAllEnums(colors); const int32_t numColors = static_cast(colors.size()); for (int32_t i = 0; i < numColors; i++) { const CaretColorEnum::Enum colorEnum = colors[i]; const int32_t indx = this->colorComboBox->count(); const AString name = CaretColorEnum::toGuiName(colorEnum); this->colorComboBox->addItem(name); this->colorComboBox->setItemData(indx, CaretColorEnum::toIntegerCode(colorEnum)); const float* rgb = CaretColorEnum::toRGB(colorEnum); QPixmap pm(10, 10); pm.fill(QColor::fromRgbF(rgb[0], rgb[1], rgb[2])); QIcon icon(pm); this->colorComboBox->setItemIcon(indx, icon); } setSelectedColor(CaretColorEnum::BLACK); QObject::connect(this->colorComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(colorComboBoxIndexChanged(int))); } /** * Destructor. */ CaretColorEnumComboBox::~CaretColorEnumComboBox() { } /** * @return The actual widget. */ QWidget* CaretColorEnumComboBox::getWidget() { return this->colorComboBox; } /** * @return The selected color. */ CaretColorEnum::Enum CaretColorEnumComboBox::getSelectedColor() { const int32_t indx = this->colorComboBox->currentIndex(); const int32_t integerCode = this->colorComboBox->itemData(indx).toInt(); CaretColorEnum::Enum color = CaretColorEnum::fromIntegerCode(integerCode, NULL); return color; } /** * Set the selected color. * @param color * New color for selection. */ void CaretColorEnumComboBox::setSelectedColor(const CaretColorEnum::Enum color) { const int32_t numColors = static_cast(this->colorComboBox->count()); for (int32_t i = 0; i < numColors; i++) { const int32_t integerCode = this->colorComboBox->itemData(i).toInt(); CaretColorEnum::Enum c = CaretColorEnum::fromIntegerCode(integerCode, NULL); if (c == color) { this->colorComboBox->blockSignals(true); this->colorComboBox->setCurrentIndex(i); this->colorComboBox->blockSignals(false); break; } } } /** * Called when a color is selected. * @param indx * Index of item selected. */ void CaretColorEnumComboBox::colorComboBoxIndexChanged(int indx) { const int32_t integerCode = this->colorComboBox->itemData(indx).toInt(); CaretColorEnum::Enum color = CaretColorEnum::fromIntegerCode(integerCode, NULL); emit colorSelected(color); } workbench-1.1.1/src/GuiQt/CaretColorEnumComboBox.h000066400000000000000000000036431255417355300220200ustar00rootroot00000000000000#ifndef __CARET_COLOR_ENUM_COMBOBOX__H_ #define __CARET_COLOR_ENUM_COMBOBOX__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretColorEnum.h" #include "WuQWidget.h" class QComboBox; namespace caret { class CaretColorEnumComboBox : public WuQWidget { Q_OBJECT public: CaretColorEnumComboBox(QObject* parent); virtual ~CaretColorEnumComboBox(); CaretColorEnum::Enum getSelectedColor(); void setSelectedColor(const CaretColorEnum::Enum color); QWidget* getWidget(); signals: void colorSelected(const CaretColorEnum::Enum); private slots: void colorComboBoxIndexChanged(int); private: CaretColorEnumComboBox(const CaretColorEnumComboBox&); CaretColorEnumComboBox& operator=(const CaretColorEnumComboBox&); private: QComboBox* colorComboBox; }; #ifdef __CARET_COLOR_ENUM_COMBOBOX_DECLARE__ // #endif // __CARET_COLOR_ENUM_COMBOBOX_DECLARE__ } // namespace #endif //__CARET_COLOR_ENUM_COMBOBOX__H_ workbench-1.1.1/src/GuiQt/CaretDataFileSelectionComboBox.cxx000066400000000000000000000107171255417355300240070ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __CARET_DATA_FILE_SELECTION_COMBO_BOX_DECLARE__ #include "CaretDataFileSelectionComboBox.h" #undef __CARET_DATA_FILE_SELECTION_COMBO_BOX_DECLARE__ #include "CaretAssert.h" #include "CaretDataFile.h" #include "CaretDataFileSelectionModel.h" #include "FilePathNamePrefixCompactor.h" #include "WuQEventBlockingFilter.h" using namespace caret; /** * \class caret::CaretDataFileSelectionComboBox * \brief Combo box for selection of a CaretDataFile. * \ingroup GuiQt */ /** * Constructor. * * @param parent * Parent of combo box. */ CaretDataFileSelectionComboBox::CaretDataFileSelectionComboBox(QObject* parent) : WuQWidget(parent) { m_selectionModel = NULL; m_comboBox = new QComboBox(); m_comboBox->setSizeAdjustPolicy(QComboBox::AdjustToContents); QObject::connect(m_comboBox, SIGNAL(activated(int)), this, SLOT(slotFileIndexSelected(int))); WuQEventBlockingFilter::blockMouseWheelEventInMacComboBox(m_comboBox); } /** * Destructor. */ CaretDataFileSelectionComboBox::~CaretDataFileSelectionComboBox() { } /** * @return The widget (combobox) for insertion into a layout. */ QWidget* CaretDataFileSelectionComboBox::getWidget() { return m_comboBox; } /** * Gets called when the user makes a selection. Issues the * 'fileSelected()' signal. * * @param indx * Index of the item that was selected. */ void CaretDataFileSelectionComboBox::slotFileIndexSelected(int indx) { CaretDataFile* caretDataFile = NULL; if ((indx >= 0) && (indx < m_comboBox->count())) { void* filePointer = m_comboBox->itemData(indx).value(); caretDataFile = (CaretDataFile*)filePointer; } if (m_selectionModel != NULL) { m_selectionModel->setSelectedFile(caretDataFile); } emit fileSelected(caretDataFile); } /** * Update the content of the combo box. * * @param * Selection model for the combo box. */ void CaretDataFileSelectionComboBox::updateComboBox(CaretDataFileSelectionModel* selectionModel) { m_comboBox->blockSignals(true); m_comboBox->clear(); m_selectionModel = selectionModel; if (m_selectionModel != NULL) { const CaretDataFile* selectedFile = m_selectionModel->getSelectedFile(); int defaultIndex = 0; std::vector caretDataFiles = m_selectionModel->getAvailableFiles(); std::vector displayNames; FilePathNamePrefixCompactor::removeMatchingPathPrefixFromCaretDataFiles(caretDataFiles, displayNames); CaretAssert(caretDataFiles.size() == displayNames.size()); const int32_t numFiles = static_cast(caretDataFiles.size()); for (int32_t iFile = 0; iFile < numFiles; iFile++) { CaretAssertVectorIndex(caretDataFiles, iFile); CaretDataFile* cdf = caretDataFiles[iFile]; CaretAssert(cdf); CaretAssertVectorIndex(displayNames, iFile); m_comboBox->addItem(displayNames[iFile], qVariantFromValue((void*)cdf)); if (cdf == selectedFile) { defaultIndex = m_comboBox->count() - 1; } } if ((defaultIndex >= 0) && (defaultIndex < m_comboBox->count())) { m_comboBox->setCurrentIndex(defaultIndex); } } m_comboBox->blockSignals(false); } /** * @return Selection model in this combo box. */ CaretDataFileSelectionModel* CaretDataFileSelectionComboBox::getSelectionModel() { return m_selectionModel; } workbench-1.1.1/src/GuiQt/CaretDataFileSelectionComboBox.h000066400000000000000000000043741255417355300234360ustar00rootroot00000000000000#ifndef __CARET_DATA_FILE_SELECTION_COMBO_BOX_H__ #define __CARET_DATA_FILE_SELECTION_COMBO_BOX_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "DataFileTypeEnum.h" #include "WuQWidget.h" class QComboBox; namespace caret { class CaretDataFile; class CaretDataFileSelectionModel; class CaretDataFileSelectionComboBox : public WuQWidget { Q_OBJECT public: CaretDataFileSelectionComboBox(QObject* parent); virtual ~CaretDataFileSelectionComboBox(); void updateComboBox(CaretDataFileSelectionModel* selectionModel); CaretDataFileSelectionModel* getSelectionModel(); virtual QWidget* getWidget(); // ADD_NEW_METHODS_HERE signals: void fileSelected(CaretDataFile* caretDataFile); private slots: void slotFileIndexSelected(int indx); private: CaretDataFileSelectionComboBox(const CaretDataFileSelectionComboBox&); CaretDataFileSelectionComboBox& operator=(const CaretDataFileSelectionComboBox&); QComboBox* m_comboBox; CaretDataFileSelectionModel* m_selectionModel; // ADD_NEW_MEMBERS_HERE }; #ifdef __CARET_DATA_FILE_SELECTION_COMBO_BOX_DECLARE__ // #endif // __CARET_DATA_FILE_SELECTION_COMBO_BOX_DECLARE__ } // namespace #endif //__CARET_DATA_FILE_SELECTION_COMBO_BOX_H__ workbench-1.1.1/src/GuiQt/CaretFileDialog.cxx000066400000000000000000000403251255417355300210340ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #define __CARET_FILE_DIALOG_DECLARE__ #include "CaretFileDialog.h" #undef __CARET_FILE_DIALOG_DECLARE__ #include "Brain.h" #include "GuiManager.h" using namespace caret; FilterFilesProxyModel::FilterFilesProxyModel() { m_dataFileType = DataFileTypeEnum::UNKNOWN; } FilterFilesProxyModel::~FilterFilesProxyModel() { } /** * On Macs, Qt shows files that do not match the filter as disabled. This * method looks for disabled files and prevents them from being displayed. * * @return True to display file, else false. */ bool FilterFilesProxyModel::filterAcceptsRow ( int sourceRow, const QModelIndex & sourceParent ) const { /* * See if the 'super' allows file to be displayed. */ bool showIt = QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent); if (showIt) { /* * See if item is disabled, and if so, do not show it. */ QModelIndex modelIndex = sourceModel()->index(sourceRow, 0, sourceParent); QFileSystemModel* fileModel = qobject_cast(sourceModel()); Qt::ItemFlags flags = fileModel->flags(modelIndex); if((flags & Qt::ItemIsEnabled) == 0) { showIt = false; } /* * All CIFTI files use a 'CIFTI prefix' followed by the NIFTI volume * extension. As a result, when the File Dialog's filter is set * to volume files, both volume and CIFTI files are displayed. * So, when the filter is volume files, inhibit the display CIFTI * files in the file selection dialog. */ if (showIt) { if (m_dataFileType == DataFileTypeEnum::VOLUME) { const AString name = fileModel->fileName(modelIndex); bool isValid = false; const DataFileTypeEnum::Enum fileType = DataFileTypeEnum::fromFileExtension(name, &isValid); if (isValid) { if (fileType != DataFileTypeEnum::VOLUME) { showIt = false; } } } } } return showIt; } void FilterFilesProxyModel::setDataFileTypeForFiltering(const DataFileTypeEnum::Enum dataFileType) { m_dataFileType = dataFileType; } /** * \class caret::CaretFileDialog * \brief Adds additional functionality over Qt's QFileDialog. */ /** * Constructor. */ CaretFileDialog::CaretFileDialog(QWidget* parent, Qt::WindowFlags f) : QFileDialog(parent, f) { this->initializeCaretFileDialog(); this->setDirectory(GuiManager::get()->getBrain()->getCurrentDirectory()); } /** * Constructor. */ CaretFileDialog::CaretFileDialog(QWidget* parent, const QString& caption, const QString& directory, const QString& filter) : QFileDialog(parent, caption, directory, filter) { this->initializeCaretFileDialog(); if (directory.isEmpty()) { this->setDirectory(GuiManager::get()->getBrain()->getCurrentDirectory()); } } /** * Destructor. */ CaretFileDialog::~CaretFileDialog() { } /** * Initialize the file dialog. */ void CaretFileDialog::initializeCaretFileDialog() { /* * Create a proxy model that hides files that do not match the file filter. * On Macs, Qt shows files that do not match the file filter as disabled * but we don't want them displayed. The dialog will take ownership of * the proxy model so it does not need to be deleted by this instance. */ m_filterFilesProxyModel = new FilterFilesProxyModel(); this->setProxyModel(m_filterFilesProxyModel); #ifdef Q_OS_MACX /* * On Macs, add /Volumes to the sidebar URLs * so that mounted disks can be accessed. */ QList urls = this->sidebarUrls(); urls.append(QUrl::fromLocalFile("/Volumes")); this->setSidebarUrls(urls); #endif // Q_OS_MACX QObject::connect(this, SIGNAL(filterSelected(const QString&)), this, SLOT(fileFilterWasChanged(const QString&))); } /** * Overrides parent's setVisible to ensure file filter is properly set * * @param visible * New visibility status. */ void CaretFileDialog::setVisible(bool visible) { if (visible) { fileFilterWasChanged(selectedFilter()); } QFileDialog::setVisible(visible); } /** * Gets called when the file filter is changed. * * @param filter * Newly selected file filter. */ void CaretFileDialog::fileFilterWasChanged(const QString& filter) { bool isValid = false; DataFileTypeEnum::Enum dataFileType = DataFileTypeEnum::fromQFileDialogFilter(filter, &isValid); if ( ! isValid) { dataFileType = DataFileTypeEnum::UNKNOWN; } m_filterFilesProxyModel->setDataFileTypeForFiltering(dataFileType); } /** * Like QFileDialog::getOpenFileName() except that this * NEVER uses the native file dialog thus providing * a consistent user-interface across platforms. * * @param parent * Parent on which this dialog is displayed. * @param caption * Caption for dialog (if not provided a default caption is shown) * @param dir * Directory show by dialog (Brain's current directory if empty string) * @param filter * Filter for file file selection. * @param selectedFilter * Optional selected filter. * @param options * Options (see QFileDialog). * @return * Name of file selected or empty string if user cancelled. */ QString CaretFileDialog::getOpenFileNameDialog(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, Options options ) { CaretFileDialog cfd(parent, caption, dir, filter); if (selectedFilter != 0) { cfd.selectFilter(*selectedFilter); } cfd.setOptions(options); cfd.setAcceptMode(CaretFileDialog::AcceptOpen); cfd.setFileMode(CaretFileDialog::AnyFile); if (cfd.exec() == CaretFileDialog::Accepted) { QStringList selectedFiles = cfd.selectedFiles(); if (selectedFiles.size() > 0) { return selectedFiles[0]; } } return QString(); } /** * Like QFileDialog::getSaveFileName() except that this * NEVER uses the native file dialog thus providing * a consistent user-interface across platforms. * * @param parent * Parent on which this dialog is displayed. * @param caption * Caption for dialog (if not provided a default caption is shown) * @param dir * Directory show by dialog (Brain's current directory if empty string) * @param filter * Filter for file file selection. * @param selectedFilter * Optional selected filter. * @param options * Options (see QFileDialog). * @return * Name of file selected or empty string if user cancelled. */ QString CaretFileDialog::getSaveFileNameDialog(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, Options options) { CaretFileDialog cfd(parent, caption, dir, filter); if (selectedFilter != 0) { cfd.selectFilter(*selectedFilter); } cfd.setOptions(options); cfd.setAcceptMode(QFileDialog::AcceptSave); cfd.setFileMode(CaretFileDialog::AnyFile); if (cfd.exec() == CaretFileDialog::Accepted) { QStringList selectedFiles = cfd.selectedFiles(); if (selectedFiles.size() > 0) { return selectedFiles[0]; } } return QString(); } /** * Like QFileDialog::getSaveFileName() except that this * NEVER uses the native file dialog thus providing * a consistent user-interface across platforms. * * The file filter will be from the given data file type. * The returned file will contain a valid extension from the * given data file type. * * @param dataFileType * Type of file being saved. * @param parent * Parent on which this dialog is displayed. * @param caption * Caption for dialog (if not provided a default caption is shown) * @param dir * Directory show by dialog (Brain's current directory if empty string) * @param options * Options (see QFileDialog). * @return * Name of file selected or empty string if user cancelled. If there is * no file extension or it is invalid, a valid extension will be added. */ QString CaretFileDialog::getSaveFileNameDialog(const DataFileTypeEnum::Enum dataFileType, QWidget *parent, const QString &caption, const QString &dir, Options options) { CaretFileDialog cfd(parent, caption, dir, DataFileTypeEnum::toQFileDialogFilter(dataFileType)); cfd.selectFilter(DataFileTypeEnum::toQFileDialogFilter(dataFileType)); cfd.setOptions(options); cfd.setAcceptMode(QFileDialog::AcceptSave); cfd.setFileMode(CaretFileDialog::AnyFile); if (cfd.exec() == CaretFileDialog::Accepted) { QStringList selectedFiles = cfd.selectedFiles(); if (selectedFiles.size() > 0) { AString filename = DataFileTypeEnum::addFileExtensionIfMissing(selectedFiles[0], dataFileType); return filename; } } return QString(); } /** * Like QFileDialog::getSaveFileName() except that this * NEVER uses the native file dialog thus providing * a consistent user-interface across platforms. It also will * display "Choose" for the selection button. * * The file filter will be from the given data file type. * The returned file will contain a valid extension from the * given data file type. * * @param dataFileType * Type of file being saved. * @param directoryOrFileName * Name of directory or name of file. * @param parent * Parent on which this dialog is displayed. * @param caption * Caption for dialog (if not provided a default caption is shown) * @param dir * Directory show by dialog (Brain's current directory if empty string) * @param options * Options (see QFileDialog). * @return * Name of file selected or empty string if user cancelled. If there is * no file extension or it is invalid, a valid extension will be added. */ QString CaretFileDialog::getChooseFileNameDialog(const DataFileTypeEnum::Enum dataFileType, const QString& directoryOrFileName, QWidget *parent) { CaretFileDialog fd(parent); fd.setFilter(DataFileTypeEnum::toQFileDialogFilter(dataFileType)); fd.selectFilter(DataFileTypeEnum::toQFileDialogFilter(dataFileType)); fd.setAcceptMode(CaretFileDialog::AcceptSave); fd.setFileMode(CaretFileDialog::AnyFile); fd.setViewMode(CaretFileDialog::List); if (directoryOrFileName.isEmpty() == false) { QFileInfo fileInfo(directoryOrFileName); if (fileInfo.isDir()) { fd.setDirectory(directoryOrFileName); } else { fd.selectFile(directoryOrFileName); } } else { fd.setDirectory(GuiManager::get()->getBrain()->getCurrentDirectory()); } fd.setLabelText(CaretFileDialog::Accept, "Choose"); fd.setWindowTitle("Choose File Name"); if (fd.exec() == CaretFileDialog::Accepted) { QStringList selectedFiles = fd.selectedFiles(); if (selectedFiles.size() > 0) { AString filename = DataFileTypeEnum::addFileExtensionIfMissing(selectedFiles[0], dataFileType); return filename; } } return QString(); } /** * Like QFileDialog::getExistingDirectory() except that this * NEVER uses the native file dialog thus providing * a consistent user-interface across platforms. * * @param parent * Parent on which this dialog is displayed. * @param caption * Caption for dialog (if not provided a default caption is shown) * @param dir * Directory show by dialog (Brain's current directory if empty string) * @param filter * Filter for file file selection. * @param selectedFilter * Optional selected filter. * @param options * Options (see QFileDialog). * @return * Name of directory selected or empty string if user cancelled. */ QString CaretFileDialog::getExistingDirectoryDialog(QWidget *parent, const QString &caption, const QString &dir, Options options) { CaretFileDialog cfd(parent, caption, dir, ""); cfd.setOptions(options); cfd.setAcceptMode(CaretFileDialog::AcceptOpen); cfd.setFileMode(CaretFileDialog::Directory); cfd.setOption(CaretFileDialog::ShowDirsOnly, true); if (cfd.exec() == CaretFileDialog::Accepted) { QStringList selectedFiles = cfd.selectedFiles(); if (selectedFiles.size() > 0) { return selectedFiles[0]; } } return QString(); } /** * Like QFileDialog::getOpenFileNames() except that this * NEVER uses the native file dialog thus providing * a consistent user-interface across platforms. * * @param parent * Parent on which this dialog is displayed. * @param caption * Caption for dialog (if not provided a default caption is shown) * @param dir * Directory show by dialog (Brain's current directory if empty string) * @param filter * Filter for file file selection. * @param selectedFilter * Optional selected filter. * @param options * Options (see QFileDialog). * @return * StringList of file(s) selected or empty list if user cancelled. */ QStringList CaretFileDialog::getOpenFileNamesDialog(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, Options options) { CaretFileDialog cfd(parent, caption, dir, filter); if (selectedFilter != 0) { cfd.selectFilter(*selectedFilter); } cfd.setOptions(options); cfd.setAcceptMode(CaretFileDialog::AcceptOpen); cfd.setFileMode(CaretFileDialog::ExistingFiles); if (cfd.exec() == CaretFileDialog::Accepted) { QStringList selectedFiles = cfd.selectedFiles(); if (selectedFiles.size() > 0) { return selectedFiles; } } QStringList sl; return sl; } workbench-1.1.1/src/GuiQt/CaretFileDialog.h000066400000000000000000000122171255417355300204600ustar00rootroot00000000000000#ifndef __CARET_FILE_DIALOG__H_ #define __CARET_FILE_DIALOG__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "DataFileTypeEnum.h" namespace caret { class FilterFilesProxyModel; class CaretFileDialog : public QFileDialog { Q_OBJECT public: CaretFileDialog(QWidget* parent, Qt::WindowFlags f); CaretFileDialog(QWidget* parent = 0, const QString& caption = QString(), const QString& directory = QString(), const QString& filter = QString()); virtual ~CaretFileDialog(); // modal method to get open file name static QString getOpenFileNameDialog(QWidget *parent = 0, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, Options options = 0); // modal method to get save file name static QString getSaveFileNameDialog(QWidget *parent = 0, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, Options options = 0); // modal method to get save file name static QString getSaveFileNameDialog(const DataFileTypeEnum::Enum dataFileType, QWidget *parent = 0, const QString &caption = QString(), const QString &dir = QString(), Options options = 0); // modal method to get choose file name static QString getChooseFileNameDialog(const DataFileTypeEnum::Enum dataFileType, const QString& directoryOrFileName, QWidget *parent = 0); // modal method to get directory name static QString getExistingDirectoryDialog(QWidget *parent = 0, const QString &caption = QString(), const QString &dir = QString(), Options options = ShowDirsOnly); // modal method to get open file names static QStringList getOpenFileNamesDialog(QWidget *parent = 0, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, Options options = 0); public slots: virtual void setVisible(bool visible); private slots: void fileFilterWasChanged(const QString& filter); private: CaretFileDialog(const CaretFileDialog&); CaretFileDialog& operator=(const CaretFileDialog&); void initializeCaretFileDialog(); FilterFilesProxyModel* m_filterFilesProxyModel; }; /** * May be fully implemented to provide additional filtering of files. */ class FilterFilesProxyModel : public QSortFilterProxyModel { public: FilterFilesProxyModel(); virtual ~FilterFilesProxyModel(); void setDataFileTypeForFiltering(const DataFileTypeEnum::Enum dataFileType); protected: bool filterAcceptsRow ( int sourceRow, const QModelIndex & sourceParent ) const; private: DataFileTypeEnum::Enum m_dataFileType; }; #ifdef __CARET_FILE_DIALOG_DECLARE__ // #endif // __CARET_FILE_DIALOG_DECLARE__ } // namespace #endif //__CARET_FILE_DIALOG__H_ workbench-1.1.1/src/GuiQt/CaretFileDialogExtendable.cxx000066400000000000000000000115751255417355300230350ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #define __CARET_FILE_DIALOG_EXTENDABLE_DECLARE__ #include "CaretFileDialogExtendable.h" #undef __CARET_FILE_DIALOG_EXTENDABLE_DECLARE__ #include "CaretAssert.h" #include "CaretFileDialog.h" using namespace caret; /** * \class caret::CaretFileDialogExtendable * \brief A File Dialog that can have a widget added to it. * * Embeds a QFileDialog inside a dialog so that a widget * can be inserted below the FileDialog. All public methods * from QFileDialog are duplicated and go straight to * the embedded QFileDialog. */ /** * Constructor. */ CaretFileDialogExtendable::CaretFileDialogExtendable(QWidget* parent, Qt::WindowFlags f) : QDialog(parent, f) { Qt::WindowFlags flags = 0; m_caretFileDialog = new CaretFileDialogPrivate(NULL, flags); createDialog(); } /** * Constructor. */ CaretFileDialogExtendable::CaretFileDialogExtendable(QWidget* parent, const QString& caption, const QString& directory, const QString& filter) : QDialog(parent) { m_caretFileDialog = new CaretFileDialogPrivate(NULL, caption, directory, filter); createDialog(); } /** * Destructor. */ CaretFileDialogExtendable::~CaretFileDialogExtendable() { } /** * Create the dialog. */ void CaretFileDialogExtendable::createDialog() { QObject::connect(m_caretFileDialog, SIGNAL(currentChanged(const QString&)), this, SIGNAL(currentChanged(const QString&))); QObject::connect(m_caretFileDialog, SIGNAL( directoryEntered(const QString&)), this, SIGNAL( directoryEntered(const QString&))); QObject::connect(m_caretFileDialog, SIGNAL(fileSelected(const QString&)), this, SIGNAL(fileSelected(const QString&))); QObject::connect(m_caretFileDialog, SIGNAL(filesSelected(const QStringList&)), this, SIGNAL(filesSelected(const QStringList&))); QObject::connect(m_caretFileDialog, SIGNAL(filterSelected(const QString&)), this, SIGNAL(filterSelected(const QString&))); QObject::connect(m_caretFileDialog, SIGNAL(finished(int)), this, SLOT(fileDialogFinished(int))); m_caretFileDialog->setAttribute(Qt::WA_DeleteOnClose, false); m_caretFileDialog->setSizeGripEnabled(false); m_dialogLayout = new QVBoxLayout(this); m_dialogLayout->addWidget(m_caretFileDialog); } /** * Gets called when child file dialog issues its finished signal. * @param result * Result of dialog. */ void CaretFileDialogExtendable::fileDialogFinished(int result) { this->setResult(result); this->done(result); } /** * Add a widget at the bottom to this file dialog. * @param widget * Widget that is added. */ void CaretFileDialogExtendable::addWidget(QWidget* widget) { CaretAssert(widget); m_dialogLayout->addWidget(widget); } //================================================================================== CaretFileDialogPrivate::CaretFileDialogPrivate(QWidget* parent, Qt::WindowFlags /*f*/) : CaretFileDialog(parent, Qt::Widget) { } CaretFileDialogPrivate::CaretFileDialogPrivate(QWidget* parent, const QString& caption, const QString& directory, const QString& filter) : CaretFileDialog(parent, caption, directory, filter) { } CaretFileDialogPrivate::~CaretFileDialogPrivate() { } void CaretFileDialogPrivate::closeEvent(QCloseEvent* event) { event->ignore(); } void CaretFileDialogPrivate::done(int result) { CaretFileDialog::done(result); } workbench-1.1.1/src/GuiQt/CaretFileDialogExtendable.h000066400000000000000000000174751255417355300224670ustar00rootroot00000000000000#ifndef __CARET_FILE_DIALOG_EXTENDABLE__H_ #define __CARET_FILE_DIALOG_EXTENDABLE__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "CaretFileDialog.h" class QVBoxLayout; namespace caret { class CaretFileDialogPrivate : public CaretFileDialog { Q_OBJECT public: CaretFileDialogPrivate(QWidget* parent, Qt::WindowFlags f); CaretFileDialogPrivate(QWidget* parent = 0, const QString& caption = QString(), const QString& directory = QString(), const QString& filter = QString()); virtual ~CaretFileDialogPrivate(); protected: virtual void done(int result); virtual void closeEvent(QCloseEvent* event); }; class CaretFileDialogExtendable : public QDialog { Q_OBJECT public: CaretFileDialogExtendable(QWidget* parent, Qt::WindowFlags f); CaretFileDialogExtendable(QWidget* parent = 0, const QString& caption = QString(), const QString& directory = QString(), const QString& filter = QString()); virtual ~CaretFileDialogExtendable(); void addWidget(QWidget* widget); QFileDialog::AcceptMode acceptMode() const { return m_caretFileDialog->acceptMode(); } bool confirmOverwrite() const { return m_caretFileDialog->confirmOverwrite(); } QString defaultSuffix() const { return m_caretFileDialog->defaultSuffix(); } QDir directory() const { return m_caretFileDialog->directory(); } QFileDialog::FileMode fileMode() const { return m_caretFileDialog->fileMode(); } QStringList filters() const { return m_caretFileDialog->filters(); } QStringList history() const { return m_caretFileDialog->history(); } QFileIconProvider* iconProvider() const { return m_caretFileDialog->iconProvider(); } bool isReadOnly() const { return m_caretFileDialog->isReadOnly(); } QAbstractItemDelegate* itemDelegate() const { return m_caretFileDialog->itemDelegate(); } QString labelText(const QFileDialog::DialogLabel label) const { return m_caretFileDialog->labelText(label); } QStringList nameFilters() const { return m_caretFileDialog->nameFilters(); } void open(QObject* receiver, const char* member) { m_caretFileDialog->open(receiver, member); } QFileDialog::Options options() const { return m_caretFileDialog->options(); } QAbstractProxyModel* proxyModel() const { return m_caretFileDialog->proxyModel(); } bool resolveSymlinks() const { return m_caretFileDialog->resolveSymlinks(); } bool restoreState(const QByteArray& state) { return m_caretFileDialog->restoreState(state); } QByteArray saveState() const { return m_caretFileDialog->saveState(); } void selectFile(const QString& name) { m_caretFileDialog->selectFile(name); } void selectNameFilter(const QString& filter) { m_caretFileDialog->selectNameFilter(filter); } QStringList selectedFiles() const { return m_caretFileDialog->selectedFiles(); } QString selectedNameFilter() const { return m_caretFileDialog->selectedNameFilter(); } void setAcceptMode(const QFileDialog::AcceptMode mode) { m_caretFileDialog->setAcceptMode(mode); } void setConfirmOverwrite(const bool enabled) { m_caretFileDialog->setConfirmOverwrite(enabled); } void setDefaultSuffix(const QString& suffix) { m_caretFileDialog->setDefaultSuffix(suffix); } void setDirectory(const QString& dir) { m_caretFileDialog->setDirectory(dir); } void setDirectory(const QDir& dir) { m_caretFileDialog->setDirectory(dir); } void setFileMode(const QFileDialog::FileMode mode) { m_caretFileDialog->setFileMode(mode); } void setFilter(const QString& filter) { m_caretFileDialog->setFilter(filter); } void setHistory(const QStringList& paths) { m_caretFileDialog->setHistory(paths); } void setIconProvider(QFileIconProvider* provider) { m_caretFileDialog->setIconProvider(provider); } void setItemDelegate(QAbstractItemDelegate* delegate) { m_caretFileDialog->setItemDelegate(delegate); } void setLabelText(const QFileDialog::DialogLabel label, const QString& text) { m_caretFileDialog->setLabelText(label, text); } void setNameFilter(const QString& filter) { m_caretFileDialog->setNameFilter(filter); } void setNameFilterDetailsVisible(bool enabled) { m_caretFileDialog->setNameFilterDetailsVisible(enabled); } void setNameFilters(const QStringList& filters) { m_caretFileDialog->setNameFilters(filters); } void setOption(QFileDialog::Option option, bool on = true) { m_caretFileDialog->setOption(option, on); } void setOptions(QFileDialog::Options options) { m_caretFileDialog->setOptions(options); } void setProxyModel(QAbstractProxyModel* proxyModel) { m_caretFileDialog->setProxyModel(proxyModel); } void setReadOnly(const bool enabled) { m_caretFileDialog->setReadOnly(enabled); } void setResolveSymlinks(bool enabled) { m_caretFileDialog->setResolveSymlinks(enabled); } void setSidebarUrls(const QList& urls) { m_caretFileDialog->setSidebarUrls(urls); } void setViewMode(const QFileDialog::ViewMode viewMode) { m_caretFileDialog->setViewMode(viewMode); } QList sidebarUrls() const { return m_caretFileDialog->sidebarUrls(); } bool testOption(QFileDialog::Option option) const { return m_caretFileDialog->testOption(option); } QFileDialog::ViewMode viewMode() const { return m_caretFileDialog->viewMode(); } signals: void currentChanged(const QString& path); void directoryEntered(const QString& directory); void fileSelected(const QString& file); void filesSelected(const QStringList& selected); void filterSelected(const QString& filter); private slots: void fileDialogFinished(int); private: CaretFileDialogExtendable(const CaretFileDialogExtendable&); CaretFileDialogExtendable& operator=(const CaretFileDialogExtendable&); void createDialog(); QVBoxLayout* m_dialogLayout; CaretFileDialogPrivate* m_caretFileDialog; }; #ifdef __CARET_FILE_DIALOG_EXTENDABLE_DECLARE__ // #endif // __CARET_FILE_DIALOG_EXTENDABLE_DECLARE__ } // namespace #endif //__CARET_FILE_DIALOG_EXTENDABLE__H_ workbench-1.1.1/src/GuiQt/CaretFileRemoteDialog.cxx000066400000000000000000000267341255417355300222200ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #include #include #include #include #include #include #define __CARET_FILE_REMOTE_DIALOG_DECLARE__ #include "CaretFileRemoteDialog.h" #undef __CARET_FILE_REMOTE_DIALOG_DECLARE__ #include "Brain.h" #include "BrainBrowserWindow.h" #include "CaretAssert.h" #include "CaretPreferences.h" #include "DataFileTypeEnum.h" #include "EnumComboBoxTemplate.h" #include "EventDataFileRead.h" #include "EventGraphicsUpdateAllWindows.h" #include "EventManager.h" #include "EventSurfaceColoringInvalidate.h" #include "EventUserInterfaceUpdate.h" #include "GuiManager.h" #include "ProgressReportingDialog.h" #include "SessionManager.h" #include "SpecFile.h" #include "UsernamePasswordWidget.h" #include "WuQMessageBox.h" #include "WuQWidgetObjectGroup.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::CaretFileRemoteDialog * \brief Open a remote data file * */ /** * Constructor. */ CaretFileRemoteDialog::CaretFileRemoteDialog(QWidget* parent) : WuQDialogModal("Open Location", parent) { QWidget* locationWidget = createLocationWidget(); m_usernamePasswordWidget = new UsernamePasswordWidget(); createAndLoadStandardData(); QWidget* controlsWidget = new QWidget(); QVBoxLayout* controlsLayout = new QVBoxLayout(controlsWidget); controlsLayout->addWidget(locationWidget); controlsLayout->addWidget(m_usernamePasswordWidget, 0, Qt::AlignCenter); WuQtUtilities::setLayoutSpacingAndMargins(controlsLayout, 4, 2); setCentralWidget(controlsWidget, WuQDialog::SCROLL_AREA_NEVER); /* * Restore previous selections */ QRadioButton* defaultRadioButton = NULL; if (s_previousSelections.m_firstTime) { s_previousSelections.m_firstTime = false; } m_customUrlLineEdit->setText(s_previousSelections.m_customURL); m_customUrlFileTypeComboBox->setSelectedItem(s_previousSelections.m_customDataFileType); m_standardFileComboBox->setCurrentIndex(s_previousSelections.m_standardFileComboBoxIndex); if (m_locationCustomRadioButton->text() == s_previousSelections.m_radioButtonText) { defaultRadioButton = m_locationCustomRadioButton; } else if (m_locationStandardRadioButton->text() == s_previousSelections.m_radioButtonText) { defaultRadioButton = m_locationStandardRadioButton; } if (defaultRadioButton != NULL) { defaultRadioButton->setChecked(true); } locationSourceRadioButtonClicked(defaultRadioButton); } /** * Destructor. */ CaretFileRemoteDialog::~CaretFileRemoteDialog() { } /** * @return The location widget. */ QWidget* CaretFileRemoteDialog::createLocationWidget() { QStringList filenameFilterList; std::vector dataFileTypes; DataFileTypeEnum::getAllEnums(dataFileTypes, false); m_locationCustomRadioButton = new QRadioButton("Custom"); m_locationStandardRadioButton = new QRadioButton("Standard"); QButtonGroup* locationButtonGroup = new QButtonGroup(this); locationButtonGroup->addButton(m_locationCustomRadioButton); locationButtonGroup->addButton(m_locationStandardRadioButton); QObject::connect(locationButtonGroup, SIGNAL(buttonClicked(QAbstractButton*)), this, SLOT(locationSourceRadioButtonClicked(QAbstractButton*))); QLabel* customUrlLabel = new QLabel("URL: "); QLabel* customUrlTypeLabel = new QLabel("Type: "); m_customUrlLineEdit = new QLineEdit(); m_customUrlLineEdit->setMinimumWidth(400); m_customUrlFileTypeComboBox = new EnumComboBoxTemplate(this); m_customUrlFileTypeComboBox->setup(); m_customUrlFileTypeComboBox->setSelectedItem(DataFileTypeEnum::CONNECTIVITY_DENSE_SCALAR); m_standardFileComboBox = new QComboBox(); QGroupBox* locationGroupBox = new QGroupBox("Location"); QGridLayout* locationGridLayout = new QGridLayout(locationGroupBox); locationGridLayout->setColumnStretch(0, 0); locationGridLayout->setColumnStretch(1, 0); locationGridLayout->setColumnStretch(2, 100); int row = locationGridLayout->rowCount(); locationGridLayout->addWidget(m_locationCustomRadioButton, row, 0, 2, 1); locationGridLayout->addWidget(customUrlLabel, row, 1); locationGridLayout->addWidget(m_customUrlLineEdit, row, 2); row++; locationGridLayout->addWidget(customUrlTypeLabel, row, 1); locationGridLayout->addWidget(m_customUrlFileTypeComboBox->getWidget(), row, 2); row++; locationGridLayout->addWidget(WuQtUtilities::createHorizontalLineWidget(), row, 0, 1, 3); row++; locationGridLayout->addWidget(m_locationStandardRadioButton, row, 0, 1, 2); locationGridLayout->addWidget(m_standardFileComboBox, row, 2); m_customWidgetGroup = new WuQWidgetObjectGroup(this); m_customWidgetGroup->add(customUrlLabel); m_customWidgetGroup->add(customUrlTypeLabel); m_customWidgetGroup->add(m_customUrlLineEdit); m_customWidgetGroup->add(m_customUrlFileTypeComboBox); m_standardWidgetGroup = new WuQWidgetObjectGroup(this); m_standardWidgetGroup->add(m_standardFileComboBox); QObject::connect(m_customUrlLineEdit, SIGNAL(textEdited(const QString&)), this, SLOT(selectCustomRadioButton())); QObject::connect(m_customUrlFileTypeComboBox, SIGNAL(itemActivated()), this, SLOT(selectCustomRadioButton())); QObject::connect(m_standardFileComboBox, SIGNAL(activated(int)), this, SLOT(selectStandardRadioButton())); return locationGroupBox; } /** * To select standard radio button. */ void CaretFileRemoteDialog::selectStandardRadioButton() { m_locationStandardRadioButton->setChecked(true); locationSourceRadioButtonClicked(m_locationStandardRadioButton); } /** * To select custom radio button. */ void CaretFileRemoteDialog::selectCustomRadioButton() { m_locationCustomRadioButton->setChecked(true); locationSourceRadioButtonClicked(m_locationCustomRadioButton); } /** * Called when a location source radio button is clicked. */ void CaretFileRemoteDialog::locationSourceRadioButtonClicked(QAbstractButton* button) { bool okButtonEnabled = true; if (button == m_locationCustomRadioButton) { /* nothing */ } else if (button == m_locationStandardRadioButton) { /* nothing */ } else { okButtonEnabled = false; } getDialogButtonBox()->button(QDialogButtonBox::Ok)->setEnabled(okButtonEnabled); } /** * Create and load the standard data. */ void CaretFileRemoteDialog::createAndLoadStandardData() { m_standardFileComboBox->blockSignals(true); m_standardData.push_back(StandardData("HCP-Q1 Correlation with Mean Gray Regression (Avg 20)", "https://db.humanconnectome.org/spring/cifti-average?resource=HCP_Q1:Q1:Demo_HCP_unrelated20_FunctionalConnectivity_mgt-regression", DataFileTypeEnum::CONNECTIVITY_DENSE)); m_standardData.push_back(StandardData("HCP-Q1 Full Correlation (Avg 20)", "https://db.humanconnectome.org/spring/cifti-average?resource=HCP_Q1:Q1:Demo_HCP_unrelated20_FunctionalConnectivity_FullCorrel", DataFileTypeEnum::CONNECTIVITY_DENSE)); m_standardData.push_back(StandardData("Pilot1 Average Dense Connectome", "https://db.humanconnectome.org/data/services/cifti-average?searchID=PILOT1_AVG_xnat:subjectData", DataFileTypeEnum::CONNECTIVITY_DENSE)); const int numStandardData = static_cast(m_standardData.size()); for (int i = 0; i < numStandardData; i++) { m_standardFileComboBox->addItem(m_standardData[i].m_userFriendlyName, qVariantFromValue(i)); } m_standardFileComboBox->blockSignals(false); } /** * Called when OK button is pressed. */ void CaretFileRemoteDialog::okButtonClicked() { AString username; AString password; m_usernamePasswordWidget->getUsernameAndPassword(username, password); bool customSelected = false; if (m_locationCustomRadioButton->isChecked()) { customSelected = true; } else if (m_locationStandardRadioButton->isChecked()) { customSelected = false; } else { CaretAssert(0); // OK button only enabled if radio button selected } AString dataFileURL; DataFileTypeEnum::Enum dataFileType = DataFileTypeEnum::UNKNOWN; if (customSelected) { dataFileURL = m_customUrlLineEdit->text().trimmed(); dataFileType = m_customUrlFileTypeComboBox->getSelectedItem(); s_previousSelections.m_customURL = dataFileURL; s_previousSelections.m_customDataFileType = dataFileType; s_previousSelections.m_radioButtonText = m_locationCustomRadioButton->text(); } else { const int indx = m_standardFileComboBox->currentIndex(); if (indx >= 0) { dataFileURL = m_standardData[indx].m_locationUrl; dataFileType = m_standardData[indx].m_dataFileType; } s_previousSelections.m_standardFileComboBoxIndex = indx; s_previousSelections.m_radioButtonText = m_locationStandardRadioButton->text(); } BrainBrowserWindow* browserWindow = GuiManager::get()->getActiveBrowserWindow(); std::vector files; files.push_back(dataFileURL); std::vector dataFileTypes; dataFileTypes.push_back(dataFileType); const bool successFlag = browserWindow->loadFilesFromNetwork(this, files, dataFileTypes, username, password); if (successFlag) { WuQDialogModal::okButtonClicked(); } } workbench-1.1.1/src/GuiQt/CaretFileRemoteDialog.h000066400000000000000000000073541255417355300216420ustar00rootroot00000000000000#ifndef __CARET_FILE_REMOTE_DIALOG__H_ #define __CARET_FILE_REMOTE_DIALOG__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "DataFileTypeEnum.h" #include "WuQDialogModal.h" class QCheckBox; class QComboBox; class QLineEdit; class QRadioButton; namespace caret { class EnumComboBoxTemplate; class UsernamePasswordWidget; class WuQWidgetObjectGroup; class CaretFileRemoteDialog : public WuQDialogModal { Q_OBJECT public: CaretFileRemoteDialog(QWidget* parent = 0); virtual ~CaretFileRemoteDialog(); protected: void okButtonClicked(); private slots: void locationSourceRadioButtonClicked(QAbstractButton* button); void selectCustomRadioButton(); void selectStandardRadioButton(); private: CaretFileRemoteDialog(const CaretFileRemoteDialog&); CaretFileRemoteDialog& operator=(const CaretFileRemoteDialog&); class StandardData { public: StandardData(const AString& userFriendlyName, const AString locationUrl, const DataFileTypeEnum::Enum dataFileType) : m_userFriendlyName(userFriendlyName), m_locationUrl(locationUrl), m_dataFileType(dataFileType) { } AString m_userFriendlyName; AString m_locationUrl; DataFileTypeEnum::Enum m_dataFileType; }; class PreviousSelections { public: PreviousSelections() { m_customURL = "http://"; m_customDataFileType = DataFileTypeEnum::CONNECTIVITY_DENSE; m_standardFileComboBoxIndex = 0; m_firstTime = true; } AString m_customURL; DataFileTypeEnum::Enum m_customDataFileType; int m_standardFileComboBoxIndex; AString m_radioButtonText; bool m_firstTime; }; QWidget* createLocationWidget(); void createAndLoadStandardData(); UsernamePasswordWidget* m_usernamePasswordWidget; QRadioButton* m_locationCustomRadioButton; QRadioButton* m_locationStandardRadioButton; QComboBox* m_standardFileComboBox; EnumComboBoxTemplate* m_customUrlFileTypeComboBox; QLineEdit* m_customUrlLineEdit; QCheckBox* m_savePasswordToPreferencesCheckBox; WuQWidgetObjectGroup* m_customWidgetGroup; WuQWidgetObjectGroup* m_standardWidgetGroup; std::vector m_standardData; static PreviousSelections s_previousSelections; }; #ifdef __CARET_FILE_REMOTE_DIALOG_DECLARE__ CaretFileRemoteDialog::PreviousSelections CaretFileRemoteDialog::s_previousSelections; #endif // __CARET_FILE_REMOTE_DIALOG_DECLARE__ } // namespace #endif //__CARET_FILE_REMOTE_DIALOG__H_ workbench-1.1.1/src/GuiQt/CaretMappableDataFileAndMapSelector.cxx000066400000000000000000001142421255417355300247320ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #include #include #include #include #include #include "WuQDataEntryDialog.h" #include "WuQFactory.h" #include "WuQMessageBox.h" #include "WuQWidgetObjectGroup.h" #include "WuQtUtilities.h" #define __CARET_MAPPABLE_DATA_FILE_AND_MAP_SELECTOR_DECLARE__ #include "CaretMappableDataFileAndMapSelector.h" #undef __CARET_MAPPABLE_DATA_FILE_AND_MAP_SELECTOR_DECLARE__ #include "Brain.h" #include "BrainStructure.h" #include "CaretAssert.h" #include "CiftiBrainordinateLabelFile.h" #include "CiftiBrainordinateScalarFile.h" #include "DataFileException.h" #include "EventDataFileAdd.h" #include "EventManager.h" #include "GiftiLabel.h" #include "GiftiLabelTableEditor.h" #include "GiftiLabelTable.h" #include "GiftiMetaData.h" #include "GuiManager.h" #include "LabelFile.h" #include "MetricFile.h" #include "SpecFile.h" #include "StructureEnumComboBox.h" #include "WuQMessageBox.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::CaretMappableDataFileAndMapSelector * \brief Widget for selecting a map file and map. * * Dialog that allows the user to select a map file and * a map within the map file. The dialog also allows * the user to create a new map file and/or map. */ /** * Constructor. * * @param defaultName * Default name for file. * @param brain * Brain structure that contains map files. * @param structure * Type of map file. * @param parent * The parent. */ CaretMappableDataFileAndMapSelector::CaretMappableDataFileAndMapSelector(const AString defaultName, Brain* brain, const std::vector& supportedMapFileTypes, const std::vector& supportedStructures, QObject* parent) : WuQWidget(parent) { m_brain = brain; m_defaultName = defaultName; m_newMapAction = NULL; m_supportedMapFileTypes = supportedMapFileTypes; m_supportedStructures = supportedStructures; m_mapFileTypesThatAllowAddingMaps.push_back(DataFileTypeEnum::LABEL); m_mapFileTypesThatAllowAddingMaps.push_back(DataFileTypeEnum::METRIC); /* * File Type */ QLabel* mapFileTypeLabel = new QLabel("File Type: "); this->mapFileTypeComboBox = new QComboBox(); for (std::vector::iterator dataFileTypeIterator = m_supportedMapFileTypes.begin(); dataFileTypeIterator != m_supportedMapFileTypes.end(); dataFileTypeIterator++) { const DataFileTypeEnum::Enum dataFileType = *dataFileTypeIterator; const AString name = DataFileTypeEnum::toGuiName(dataFileType); this->mapFileTypeComboBox->addItem(name, (int)DataFileTypeEnum::toIntegerCode(dataFileType)); } QObject::connect(this->mapFileTypeComboBox, SIGNAL(activated(int)), this, SLOT(mapFileTypeComboBoxSelected(int))); /* * Structure */ QLabel* mapFileStructureLabel = new QLabel("Structure: "); m_mapFileStructureComboBox = new StructureEnumComboBox(this); m_mapFileStructureComboBox->listOnlyTheseStructures(supportedStructures); if ( ! supportedStructures.empty()) { m_mapFileStructureComboBox->setSelectedStructure(supportedStructures[0]); } QObject::connect(m_mapFileStructureComboBox, SIGNAL(structureSelected(const StructureEnum::Enum)), this, SLOT(mapFileStructureComboBoxSelected(const StructureEnum::Enum))); /* * File Selection */ QLabel* mapFileLabel = new QLabel("File: "); this->mapFileComboBox = new QComboBox(); QObject::connect(this->mapFileComboBox, SIGNAL(activated(int)), this, SLOT(mapFileComboBoxSelected(int))); QAction* newMapFileAction = WuQtUtilities::createAction("New...", "", this, this, SLOT(newMapFileToolButtonSelected())); QToolButton* newMapFileToolButton = new QToolButton(); newMapFileToolButton->setDefaultAction(newMapFileAction); /* * Map Name Selection */ QLabel* mapNameLabel = new QLabel("Map: "); this->mapNameComboBox = new QComboBox(); QObject::connect(this->mapNameComboBox, SIGNAL(activated(int)), this, SLOT(mapNameComboBoxSelected(int))); m_newMapAction = WuQtUtilities::createAction("New...", "", this, this, SLOT(newMapToolButtonSelected())); QToolButton* newMapToolButton = new QToolButton(); newMapToolButton->setDefaultAction(m_newMapAction); /* * Label selection */ QLabel* labelNameLabel = new QLabel("Label Name: "); this->labelSelectionComboBox = new QComboBox(); QObject::connect(this->labelSelectionComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(labelNameComboBoxSelected(int))); QAction* editLabelsAction = WuQtUtilities::createAction("Edit...", "Add/Edit/Delete Labels\nand edit their colors", this, this, SLOT(showLabelsEditor())); QToolButton* editLabelsToolButton = new QToolButton(); editLabelsToolButton->setDefaultAction(editLabelsAction); this->labelValueControlsGroup = new WuQWidgetObjectGroup(this); this->labelValueControlsGroup->add(labelNameLabel); this->labelValueControlsGroup->add(this->labelSelectionComboBox); this->labelValueControlsGroup->add(editLabelsToolButton); /* * Float Value Entry */ QLabel* floatValueLabel = new QLabel("Value: "); m_floatValueSpinBox = WuQFactory::newDoubleSpinBox(); m_floatValueSpinBox->setMaximumWidth(100); m_floatValueSpinBox->setRange(-std::numeric_limits::max(), std::numeric_limits::max()); m_floatValueSpinBox->setValue(1.0); QObject::connect(m_floatValueSpinBox, SIGNAL(valueChanged(double)), this, SLOT(floatValueChanged(double))); m_floatValueControlsGroup = new WuQWidgetObjectGroup(this); m_floatValueControlsGroup->add(floatValueLabel); m_floatValueControlsGroup->add(m_floatValueSpinBox); /* * Layout widgets */ this->widget = new QWidget(); QGridLayout* gridLayout = new QGridLayout(this->widget); WuQtUtilities::setLayoutSpacingAndMargins(gridLayout, 4, 2); gridLayout->setColumnStretch(0, 0); gridLayout->setColumnStretch(1, 100); gridLayout->setColumnStretch(2, 0); int32_t rowIndex = gridLayout->rowCount(); gridLayout->addWidget(mapFileTypeLabel, rowIndex, 0); gridLayout->addWidget(this->mapFileTypeComboBox, rowIndex, 1); rowIndex++; gridLayout->addWidget(mapFileStructureLabel, rowIndex, 0); gridLayout->addWidget(m_mapFileStructureComboBox->getWidget(), rowIndex, 1); rowIndex++; gridLayout->addWidget(mapFileLabel, rowIndex, 0); gridLayout->addWidget(this->mapFileComboBox, rowIndex, 1); gridLayout->addWidget(newMapFileToolButton, rowIndex, 2); rowIndex++; gridLayout->addWidget(mapNameLabel, rowIndex, 0); gridLayout->addWidget(this->mapNameComboBox, rowIndex, 1); gridLayout->addWidget(newMapToolButton, rowIndex, 2); rowIndex++; gridLayout->addWidget(labelNameLabel, rowIndex, 0); gridLayout->addWidget(this->labelSelectionComboBox, rowIndex, 1); gridLayout->addWidget(editLabelsToolButton, rowIndex, 2); rowIndex++; gridLayout->addWidget(floatValueLabel, rowIndex, 0, Qt::AlignLeft); gridLayout->addWidget(m_floatValueSpinBox, rowIndex, 1); rowIndex++; this->setMapFileTypeComboBoxCurrentIndex(0); loadLastSelectionsForFileType(DataFileTypeEnum::UNKNOWN); } /** * Destructor. */ CaretMappableDataFileAndMapSelector::~CaretMappableDataFileAndMapSelector() { } /** * Load the map file combo box. * * @param selectedFileIndex * Index of selected file. */ void CaretMappableDataFileAndMapSelector::loadMapFileComboBox(const int32_t selectedFileIndex) { /* * Fill widgets */ std::vector matchingMapFiles; const DataFileTypeEnum::Enum selectedDataFileType = this->getSelectedMapFileType(); const StructureEnum::Enum selectedStructure = this->getSelectedMapFileStructure(); std::vector caretMappableDataFiles; m_brain->getAllMappableDataFiles(caretMappableDataFiles); for (std::vector::iterator iter = caretMappableDataFiles.begin(); iter != caretMappableDataFiles.end(); iter++) { CaretMappableDataFile* caretMapFile = *iter; const DataFileTypeEnum::Enum fileType = caretMapFile->getDataFileType(); if (selectedDataFileType == fileType) { if (caretMapFile->isMappableToSurfaceStructure(selectedStructure)) { matchingMapFiles.push_back(caretMapFile); } } } this->mapFileComboBox->clear(); for (std::vector::iterator iter = matchingMapFiles.begin(); iter != matchingMapFiles.end(); iter++) { CaretMappableDataFile* cmdf = *iter; this->mapFileComboBox->addItem(cmdf->getFileNameNoPath(), qVariantFromValue((void*)cmdf)); } if ((selectedFileIndex >= 0) && (selectedFileIndex < this->mapFileComboBox->count())) { this->setMapFileComboBoxCurrentIndex(selectedFileIndex); } this->loadMapNameComboBox(0); } /** * Called when the user makes a selection from the map file type combo box. * A signal indicating that the selections have changed will be emitted. * * @param indx * Index of item selected. */ void CaretMappableDataFileAndMapSelector::mapFileTypeComboBoxSelected(int indx) { this->setMapFileTypeComboBoxCurrentIndex(indx); loadLastSelectionsForFileType(this->getSelectedMapFileType()); enableDisableNewMapAction(); emit selectionChanged(this); } /** * Enable/disable the new map action. */ void CaretMappableDataFileAndMapSelector::enableDisableNewMapAction() { if (m_newMapAction != NULL) { const DataFileTypeEnum::Enum dataFileType = this->getSelectedMapFileType(); if (std::find(m_mapFileTypesThatAllowAddingMaps.begin(), m_mapFileTypesThatAllowAddingMaps.end(), dataFileType) != m_mapFileTypesThatAllowAddingMaps.end()) { m_newMapAction->setEnabled(true); } else { m_newMapAction->setEnabled(false); } } } /** * Called when the structure is changed. * * @param structure * Selected structure. */ void CaretMappableDataFileAndMapSelector::mapFileStructureComboBoxSelected(const StructureEnum::Enum /*structure*/) { loadLastSelectionsForFileType(getSelectedMapFileType()); emit selectionChanged(this); } /** * Set the map file type combo box to the given index. * @param indx * Index for selection. */ void CaretMappableDataFileAndMapSelector::setMapFileTypeComboBoxCurrentIndex(int indx) { this->mapFileTypeComboBox->setCurrentIndex(indx); const int32_t integerCode = this->mapFileTypeComboBox->itemData(indx).toInt(); const DataFileTypeEnum::Enum dataFileType = DataFileTypeEnum::fromIntegerCode(integerCode, NULL); this->updateFileTypeSelections(dataFileType); this->loadMapFileComboBox(0); } /** * Update the selections for specific file types. */ void CaretMappableDataFileAndMapSelector::updateFileTypeSelections(const DataFileTypeEnum::Enum dataFileType) { bool showLabelSelectionWidgets = false; bool showScalarSelectionWidgets = false; switch (dataFileType) { case DataFileTypeEnum::CONNECTIVITY_DENSE_LABEL: showLabelSelectionWidgets = true; break; case DataFileTypeEnum::CONNECTIVITY_DENSE_SCALAR: showScalarSelectionWidgets = true; break; case DataFileTypeEnum::LABEL: showLabelSelectionWidgets = true; break; case DataFileTypeEnum::METRIC: showScalarSelectionWidgets = true; break; default: break; } labelValueControlsGroup->setVisible(showLabelSelectionWidgets); m_floatValueControlsGroup->setVisible(showScalarSelectionWidgets); } /** * Called when the user makes a selection from the map file combo box. * A signal indicating that the selections have changed will be emitted. * * @param indx * Index of item selected. */ void CaretMappableDataFileAndMapSelector::mapFileComboBoxSelected(int indx) { this->setMapFileComboBoxCurrentIndex(indx); emit selectionChanged(this); } /** * Set the map file combo box to the given index. * @param indx * Index for selection. */ void CaretMappableDataFileAndMapSelector::setMapFileComboBoxCurrentIndex(int indx) { this->mapFileComboBox->setCurrentIndex(indx); this->loadMapNameComboBox(0); this->loadLabelNameComboBox(); } /** * Set the map name combo box to the given index. * @param indx * Index for selection. */ void CaretMappableDataFileAndMapSelector::setMapNameComboBoxCurrentIndex(int indx) { this->mapNameComboBox->setCurrentIndex(indx); } /** * Load the contents of the map name combo box. * @param selectedMapIndex * Index of the map that is selected. */ void CaretMappableDataFileAndMapSelector::loadMapNameComboBox(const int32_t selectedMapIndex) { this->mapNameComboBox->clear(); CaretMappableDataFile* cmdf = this->getSelectedMapFile(); if (cmdf != NULL) { const int32_t numMaps = cmdf->getNumberOfMaps(); for (int32_t i = 0; i < numMaps; i++) { this->mapNameComboBox->addItem(cmdf->getMapName(i)); } if ((selectedMapIndex >= 0) && (selectedMapIndex < numMaps)) { this->mapNameComboBox->setCurrentIndex(selectedMapIndex); } } } /** * Called when the user makes a selection from the map name combo box. * * @param indx * Index of item selected. */ void CaretMappableDataFileAndMapSelector::mapNameComboBoxSelected(int /*indx*/) { emit selectionChanged(this); } /** * Called when the new map file tool button is pressed. */ void CaretMappableDataFileAndMapSelector::newMapFileToolButtonSelected() { const DataFileTypeEnum::Enum dataFileType = this->getSelectedMapFileType(); QString fileExtension = DataFileTypeEnum::toFileExtension(dataFileType); QString newFileName = "NewFile"; QString newMapName = "Map Name"; if (m_defaultName.isEmpty() == false) { newFileName = m_defaultName; newMapName = m_defaultName; } newFileName += ("." + fileExtension); WuQDataEntryDialog newFileMapDialog("New Map File and Map", this->widget); QLineEdit* mapFileNameLineEdit = newFileMapDialog.addLineEditWidget("New Map File Name", newFileName); QLineEdit* mapNameLineEdit = newFileMapDialog.addLineEditWidget("New Map Name", newMapName); if (newFileMapDialog.exec() == WuQDataEntryDialog::Accepted) { QString mapFileName = mapFileNameLineEdit->text(); const QString mapName = mapNameLineEdit->text(); try { if (mapFileName.endsWith(fileExtension) == false) { mapFileName += ("." + fileExtension); } const StructureEnum::Enum structure = getSelectedMapFileStructure(); BrainStructure* brainStructure = GuiManager::get()->getBrain()->getBrainStructure(structure, false); if (brainStructure == NULL) { throw DataFileException(newFileName, "Invalid brain structure (not loaded): " + StructureEnum::toGuiName(structure)); } const int32_t numberOfNodes = brainStructure->getNumberOfNodes(); if (numberOfNodes <= 0) { throw DataFileException(newFileName, "Invalid number of nodes (0) in brain structure: " + StructureEnum::toGuiName(structure)); } switch (dataFileType) { case DataFileTypeEnum::CONNECTIVITY_DENSE_LABEL: { AString errorMessage; CiftiMappableDataFile* ciftiMappableDataFile = CiftiMappableDataFile::newInstanceForCiftiFileTypeAndSurface(DataFileTypeEnum::CONNECTIVITY_DENSE_LABEL, structure, numberOfNodes, errorMessage); if (ciftiMappableDataFile == NULL) { throw DataFileException(mapFileName, errorMessage); } CaretAssert(dynamic_cast(ciftiMappableDataFile) != NULL); if (ciftiMappableDataFile != NULL) { ciftiMappableDataFile->setMapName(0, mapName); ciftiMappableDataFile->setFileName(mapFileName); EventManager::get()->sendEvent(EventDataFileAdd(ciftiMappableDataFile).getPointer()); } else { throw DataFileException(mapFileName, errorMessage); } } break; case DataFileTypeEnum::CONNECTIVITY_DENSE_SCALAR: { AString errorMessage; CiftiMappableDataFile* ciftiMappableDataFile = CiftiMappableDataFile::newInstanceForCiftiFileTypeAndSurface(DataFileTypeEnum::CONNECTIVITY_DENSE_SCALAR, structure, numberOfNodes, errorMessage); if (ciftiMappableDataFile == NULL) { throw DataFileException(mapFileName, errorMessage); } CaretAssert(dynamic_cast(ciftiMappableDataFile) != NULL); if (ciftiMappableDataFile != NULL) { ciftiMappableDataFile->setMapName(0, mapName); ciftiMappableDataFile->setFileName(mapFileName); EventManager::get()->sendEvent(EventDataFileAdd(ciftiMappableDataFile).getPointer()); } else { throw DataFileException(mapFileName, errorMessage); } } break; case DataFileTypeEnum::LABEL: { LabelFile* labelFile = new LabelFile(); labelFile->setNumberOfNodesAndColumns(numberOfNodes, 1); labelFile->setMapName(0, mapName); labelFile->setStructure(structure); labelFile->setFileName(mapFileName); EventManager::get()->sendEvent(EventDataFileAdd(labelFile).getPointer()); } break; case DataFileTypeEnum::METRIC: { MetricFile* metricFile = new MetricFile(); metricFile->setNumberOfNodesAndColumns(numberOfNodes, 1); metricFile->setMapName(0, mapName); metricFile->setStructure((structure)); metricFile->setFileName(mapFileName); EventManager::get()->sendEvent(EventDataFileAdd(metricFile).getPointer()); } break; default: CaretAssertMessage(0, ("File Type " + DataFileTypeEnum::toName(dataFileType) + " not allowed.")); break; } this->loadMapFileComboBox(0); const int numMapFiles = this->mapFileComboBox->count(); if (numMapFiles > 0) { this->setMapFileComboBoxCurrentIndex(numMapFiles - 1); } this->setMapNameComboBoxCurrentIndex(0); emit selectionChanged(this); } catch (const DataFileException& dfe) { WuQMessageBox::errorOk(this->widget, dfe.whatString()); } } } /** * @return The widget is for displaying this selector. */ QWidget* CaretMappableDataFileAndMapSelector::getWidget() { return this->widget; } /** * Called when the new map tool button is pressed. */ void CaretMappableDataFileAndMapSelector::newMapToolButtonSelected() { CaretMappableDataFile* mapFile = this->getSelectedMapFile(); if (mapFile != NULL) { const StructureEnum::Enum structure = getSelectedMapFileStructure(); if ( ! mapFile->isMappableToSurfaceStructure(structure)) { WuQMessageBox::errorOk(this->getWidget(), "The selected file cannot be mapped to the given structure, use the New button to create a new file."); return; } QString presetMapName = "Map Name"; if (m_defaultName.isEmpty() == false) { presetMapName = m_defaultName; } bool valid = false; const QString newMapName = QInputDialog::getText(this->mapFileComboBox, "Map Name", "Map Name", QLineEdit::Normal, presetMapName, &valid); if (valid) { try { GiftiTypeFile* gtf = dynamic_cast(mapFile); int32_t mapIndex = 0; if (gtf != NULL ) { gtf->addMaps(gtf->getNumberOfNodes(), 1); mapIndex = gtf->getNumberOfMaps() - 1; gtf->setMapName(mapIndex, newMapName); this->loadMapNameComboBox(mapIndex); } else { CaretAssertMessage(0, "Add support for non-GIFTI files !!!!"); } this->loadLabelNameComboBox(); } catch (const DataFileException& e) { WuQMessageBox::errorOk(this->getWidget(), e.whatString()); } } } else { WuQMessageBox::errorOk(this->getWidget(), "The selected file is invalid, use the New button to create a new file."); } } /** * @return The selected map file. Value will be NULL * if no map file is selected. */ CaretMappableDataFile* CaretMappableDataFileAndMapSelector::getSelectedMapFile() { const int indx = this->mapFileComboBox->currentIndex(); if ((indx >= 0) && (indx < this->mapFileComboBox->count())) { void* pointer = this->mapFileComboBox->itemData(indx).value(); CaretMappableDataFile* cmdf = (CaretMappableDataFile*)pointer; return cmdf; } return NULL; } /** * @return The index of the selected map. Will be negative * value if there is no map file or the map file contains * no maps. */ int32_t CaretMappableDataFileAndMapSelector::getSelectedMapIndex() { const int indx = this->mapNameComboBox->currentIndex(); CaretMappableDataFile* cmdf = this->getSelectedMapFile(); if (cmdf != NULL) { if ((indx >= 0) && (indx < cmdf->getNumberOfMaps())) { return indx; } } return -1; } /** * @return The name of the selected map file type. */ AString CaretMappableDataFileAndMapSelector::getNameOfSelectedMapFileType() { const DataFileTypeEnum::Enum dataFileType = this->getSelectedMapFileType(); const AString name = DataFileTypeEnum::toGuiName(dataFileType); return name; } /** * @return The selected map file type. */ DataFileTypeEnum::Enum CaretMappableDataFileAndMapSelector::getSelectedMapFileType() const { int32_t indx = this->mapFileTypeComboBox->currentIndex(); if (indx < 0) { indx = 0; } if ((indx >= 0) && (indx < this->mapFileTypeComboBox->count())) { const int32_t integerCode = this->mapFileTypeComboBox->itemData(indx).toInt(); DataFileTypeEnum::Enum dataFileType = DataFileTypeEnum::fromIntegerCode(integerCode, NULL); return dataFileType; } CaretAssertMessage(0, "Invalid data file type"); return DataFileTypeEnum::UNKNOWN; } /** * @return The selected structure. */ StructureEnum::Enum CaretMappableDataFileAndMapSelector::getSelectedMapFileStructure() const { return m_mapFileStructureComboBox->getSelectedStructure(); } /** * Are the selections valid? * @param errorMessageOut * Contains error messages on output if selections * are not valid. * @return true if the selections are valid, else false. */ bool CaretMappableDataFileAndMapSelector::isValidSelections(AString& errorMessageOut) { errorMessageOut = ""; if (this->getSelectedMapFileType() == DataFileTypeEnum::UNKNOWN) { errorMessageOut = "Selected file type is invalid."; } else if (this->getSelectedMapFile() == NULL) { errorMessageOut = "No map file is selected, create a file with New button."; } else if (this->getSelectedMapIndex() < 0) { errorMessageOut = "No map is selected, create a map with New button."; } const bool valid = errorMessageOut.isEmpty(); return valid; } /** * Called when a selection is made from the label combo box. * @param indx * Index of selection. */ void CaretMappableDataFileAndMapSelector::labelNameComboBoxSelected(int /*indx*/) { emit selectionChanged(this); } /** * Called when a selection is made from the label combo box. * @param value * New value. */ void CaretMappableDataFileAndMapSelector::floatValueChanged(double /*value*/) { emit selectionChanged(this); } /** * Load the label names into the label selection combo box. */ void CaretMappableDataFileAndMapSelector::loadLabelNameComboBox() { const int32_t selectedKey = this->getSelectedLabelKey(); this->labelSelectionComboBox->clear(); CaretMappableDataFile* cmdf = this->getSelectedMapFile(); if (cmdf != NULL) { if (cmdf->isMappedWithLabelTable()) { const int32_t mapIndex = getSelectedMapIndex(); if (mapIndex >= 0) { GiftiLabelTable* labelTable = cmdf->getMapLabelTable(mapIndex); int32_t selectedIndex = 0; std::vector labelKeys = labelTable->getLabelKeysSortedByName(); const int numKeys = static_cast(labelKeys.size()); for (int32_t i = 0; i < numKeys; i++) { const int32_t key = labelKeys[i]; if (selectedKey == key) { selectedIndex = i; } const AString name = labelTable->getLabelName(key); this->labelSelectionComboBox->addItem(name, (int)key); } if ((selectedIndex >= 0) && (selectedIndex < this->labelSelectionComboBox->count())) { this->labelSelectionComboBox->setCurrentIndex(selectedIndex); } } } } } /** * Display the label editor. */ void CaretMappableDataFileAndMapSelector::showLabelsEditor() { CaretMappableDataFile* cmdf = this->getSelectedMapFile(); if (cmdf != NULL) { if (cmdf->isMappedWithLabelTable()) { const int32_t mapIndex = getSelectedMapIndex(); if (mapIndex >= 0) { GiftiLabelTable* labelTable = cmdf->getMapLabelTable(mapIndex); GiftiLabelTableEditor labelsEditor(cmdf, mapIndex, "Edit Labels", GiftiLabelTableEditor::OPTION_NONE, this->getWidget()); const int dialogResult = labelsEditor.exec(); this->loadLabelNameComboBox(); if (dialogResult == GiftiLabelTableEditor::Accepted) { const AString labelName = labelsEditor.getLastSelectedLabelName(); const int32_t labelKey = labelTable->getLabelKeyFromName(labelName); const int labelIndex = this->labelSelectionComboBox->findData((int)labelKey); if (labelIndex >= 0) { if (labelKey != labelTable->getUnassignedLabelKey()) { this->labelSelectionComboBox->setCurrentIndex(labelIndex); } } } } } } } /** * @return The metric value from the metric value spin box. */ float CaretMappableDataFileAndMapSelector::getSelectedMetricValue() const { const float value = m_floatValueSpinBox->value(); return value; } /** * @return Key of the selected label. */ int32_t CaretMappableDataFileAndMapSelector::getSelectedLabelKey() const { int32_t key = 0; const int indx = this->labelSelectionComboBox->currentIndex(); if ((indx >= 0) && (indx < this->labelSelectionComboBox->count())) { key = this->labelSelectionComboBox->itemData(indx).toInt(); } return key; } /** * @return Name of the selected label, empty string if no selection. */ AString CaretMappableDataFileAndMapSelector::getSelectedLabelName() const { AString name = ""; const int indx = this->labelSelectionComboBox->currentIndex(); if ((indx >= 0) && (indx < this->labelSelectionComboBox->count())) { name = this->labelSelectionComboBox->currentText(); } return name; } /** * Load the last selection that matches the given data file type. If the file type is unknown, * then just find the last selection of any kind. * * @param dataFileTypeIn * Last selected data file type. */ void CaretMappableDataFileAndMapSelector::loadLastSelectionsForFileType(const DataFileTypeEnum::Enum dataFileTypeIn) { DataFileTypeEnum::Enum dataFileType = dataFileTypeIn; StructureEnum::Enum structure = StructureEnum::INVALID; if (dataFileType != DataFileTypeEnum::UNKNOWN) { structure = getSelectedMapFileStructure(); } PreviousSelection* ps = getPreviousSelection(dataFileType, structure); // if (dataFileType == DataFileTypeEnum::UNKNOWN) { // dataFileType = DataFileTypeEnum::LABEL; // //// if (this->brainStructure->getNumberOfLabelFiles() > 0) { //// dataFileType = DataFileTypeEnum::LABEL; //// } //// else if (this->brainStructure->getNumberOfMetricFiles() > 0) { //// dataFileType = DataFileTypeEnum::METRIC; //// } // } // const int mapTypeIndex = this->mapFileTypeComboBox->findData((int)dataFileType); // if (mapTypeIndex >= 0) { // this->setMapFileTypeComboBoxCurrentIndex(mapTypeIndex); // } if (ps != NULL) { const int dataFileTypeInt = DataFileTypeEnum::toIntegerCode(ps->m_dataFileType); const int mapFileTypeIndex = mapFileTypeComboBox->findData(dataFileTypeInt); if (mapFileTypeIndex >= 0) { this->setMapFileTypeComboBoxCurrentIndex(mapFileTypeIndex); } m_mapFileStructureComboBox->setSelectedStructure(ps->m_structure); const int fileIndex = this->mapFileComboBox->findData(qVariantFromValue((void*)ps->m_mapFile)); if (fileIndex >= 0) { this->setMapFileComboBoxCurrentIndex(fileIndex); const int mapIndex = ps->m_mapFile->getMapIndexFromName(ps->m_mapName); if (mapIndex >= 0) { this->setMapNameComboBoxCurrentIndex(mapIndex); } if ( ! ps->m_labelName.isEmpty()) { this->labelSelectionComboBox->clear(); loadLabelNameComboBox(); const int labelIndex = labelSelectionComboBox->findText(ps->m_labelName); if (labelIndex >= 0) { this->labelSelectionComboBox->setCurrentIndex(labelIndex); } } m_floatValueSpinBox->blockSignals(true); m_floatValueSpinBox->setValue(ps->m_scalarValue); m_floatValueSpinBox->blockSignals(false); } } enableDisableNewMapAction(); } /** * Save the current selections. User of this selector * MUST call this to save the selections so that they * can be initialized the next time a selector for the * brain structure is used. */ void CaretMappableDataFileAndMapSelector::saveCurrentSelections() { DataFileTypeEnum::Enum dataFileType = DataFileTypeEnum::UNKNOWN; StructureEnum::Enum structure = StructureEnum::INVALID; CaretMappableDataFile* cmdf = getSelectedMapFile(); if (cmdf != NULL) { dataFileType = cmdf->getDataFileType(); structure = m_mapFileStructureComboBox->getSelectedStructure(); AString mapName = cmdf->getMapName(getSelectedMapIndex()); PreviousSelection* ps = new PreviousSelection(structure, dataFileType, cmdf, mapName, labelSelectionComboBox->currentText(), m_floatValueSpinBox->value()); previousSelections.push_back(ps); } } /** * Get the previous selections for a data file type and structure. * * @param dataFileType * Data file type. If UNKNOWN, match to any file type. * @param structure * Structure for which previous selections are desired. * @return * Pointer to matching previous selection or NULL if no match * with a valid data file. */ CaretMappableDataFileAndMapSelector::PreviousSelection* CaretMappableDataFileAndMapSelector::getPreviousSelection(const DataFileTypeEnum::Enum dataFileType, const StructureEnum::Enum structure) { std::vector allMapFiles; GuiManager::get()->getBrain()->getAllMappableDataFiles(allMapFiles); for (std::vector::reverse_iterator iter = previousSelections.rbegin(); iter != previousSelections.rend(); iter++) { PreviousSelection* ps = *iter; bool matchFlag = false; if (dataFileType == DataFileTypeEnum::UNKNOWN) { matchFlag = true; } else if ((ps->m_dataFileType == dataFileType) && (ps->m_structure == structure)) { matchFlag = true; } if (matchFlag) { if (std::find(allMapFiles.begin(), allMapFiles.end(), ps->m_mapFile) != allMapFiles.end()) { return ps; } } } return NULL; } workbench-1.1.1/src/GuiQt/CaretMappableDataFileAndMapSelector.h000066400000000000000000000155271255417355300243650ustar00rootroot00000000000000#ifndef __CARET_MAPPABLE_DATA_FILE_AND_MAP_SELECTOR__H_ #define __CARET_MAPPABLE_DATA_FILE_AND_MAP_SELECTOR__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" #include "CaretMappableDataFile.h" #include "DataFileTypeEnum.h" #include "StructureEnum.h" #include "WuQWidget.h" class QAction; class QComboBox; class QDoubleSpinBox; class QLineEdit; class QStackedWidget; namespace caret { class Brain; class BrainStructure; class CiftiBrainordinateScalarFile; class GiftiTypeFile; class LabelFile; class MetricFile; class StructureEnumComboBox; class WuQWidgetObjectGroup; class CaretMappableDataFileAndMapSelector : public WuQWidget { Q_OBJECT public: CaretMappableDataFileAndMapSelector(const AString defaultName, Brain* brain, const std::vector& supportedMapFileTypes, const std::vector& supportedStructures, QObject* parent); virtual ~CaretMappableDataFileAndMapSelector(); QWidget* getWidget(); CaretMappableDataFile* getSelectedMapFile(); DataFileTypeEnum::Enum getSelectedMapFileType() const; StructureEnum::Enum getSelectedMapFileStructure() const; int32_t getSelectedMapIndex(); AString getNameOfSelectedMapFileType(); bool isValidSelections(AString& errorMessageOut); float getSelectedMetricValue() const; int32_t getSelectedLabelKey() const; AString getSelectedLabelName() const; void saveCurrentSelections(); signals: void selectionChanged(CaretMappableDataFileAndMapSelector*); private: CaretMappableDataFileAndMapSelector(const CaretMappableDataFileAndMapSelector&); CaretMappableDataFileAndMapSelector& operator=(const CaretMappableDataFileAndMapSelector&); private slots: void mapFileTypeComboBoxSelected(int); void mapFileStructureComboBoxSelected(const StructureEnum::Enum); void mapFileComboBoxSelected(int); void mapNameComboBoxSelected(int); void labelNameComboBoxSelected(int); void floatValueChanged(double); void newMapFileToolButtonSelected(); void newMapToolButtonSelected(); void showLabelsEditor(); private: void setMapFileTypeComboBoxCurrentIndex(int indx); void setMapFileComboBoxCurrentIndex(int indx); void setMapNameComboBoxCurrentIndex(int indx); void loadMapFileComboBox(const int32_t selectedFileIndex); void loadMapNameComboBox(const int32_t selectedMapIndex); void loadLabelNameComboBox(); void updateFileTypeSelections(const DataFileTypeEnum::Enum dataFileType); void enableDisableNewMapAction(); QWidget* widget; QComboBox* mapFileTypeComboBox; StructureEnumComboBox* m_mapFileStructureComboBox; QComboBox* mapFileComboBox; QComboBox* mapNameComboBox; QAction* m_newMapAction; Brain* m_brain; QWidget* m_valueWidgetFloat; WuQWidgetObjectGroup* m_floatValueControlsGroup; QDoubleSpinBox* m_floatValueSpinBox; QWidget* valueWidgetLabel; WuQWidgetObjectGroup* labelValueControlsGroup; QComboBox* labelSelectionComboBox; QStackedWidget* valueEntryStackedWidget; std::vector m_supportedMapFileTypes; std::vector m_supportedStructures; std::vector m_mapFileTypesThatAllowAddingMaps; AString m_defaultName; class PreviousSelection { public: StructureEnum::Enum m_structure; DataFileTypeEnum::Enum m_dataFileType; CaretMappableDataFile* m_mapFile; AString m_mapName; AString m_labelName; float m_scalarValue; PreviousSelection(const StructureEnum::Enum structure, const DataFileTypeEnum::Enum dataFileType, CaretMappableDataFile* mapFile, const AString& mapName, const AString& labelName, const float scalarValue) { m_structure = structure; m_dataFileType = dataFileType; m_mapFile = mapFile; m_mapName = mapName; m_labelName = labelName; m_scalarValue = scalarValue; } bool operator=(const PreviousSelection& ps) const { if ((m_structure == ps.m_structure) && (m_dataFileType == ps.m_dataFileType)) { return true; } return false; } }; PreviousSelection* getPreviousSelection(const DataFileTypeEnum::Enum dataFileType, const StructureEnum::Enum structure); void loadLastSelectionsForFileType(const DataFileTypeEnum::Enum dataFileType); static std::vector previousSelections; }; #ifdef __CARET_MAPPABLE_DATA_FILE_AND_MAP_SELECTOR_DECLARE__ std::vector CaretMappableDataFileAndMapSelector::previousSelections; #endif // __CARET_MAPPABLE_DATA_FILE_AND_MAP_SELECTOR_DECLARE__ } // namespace #endif //__CARET_MAPPABLE_DATA_FILE_AND_MAP_SELECTOR__H_ workbench-1.1.1/src/GuiQt/CaretMappableDataFileAndMapSelectorObject.cxx000066400000000000000000000237031255417355300260620ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CARET_MAPPABLE_DATA_FILE_AND_MAP_SELECTOR_OBJECT_DECLARE__ #include "CaretMappableDataFileAndMapSelectorObject.h" #undef __CARET_MAPPABLE_DATA_FILE_AND_MAP_SELECTOR_OBJECT_DECLARE__ #include #include #include "CaretAssert.h" #include "CaretDataFileSelectionComboBox.h" #include "CaretMappableDataFile.h" #include "CaretMappableDataFileAndMapSelectionModel.h" #include "GuiManager.h" #include "WuQEventBlockingFilter.h" using namespace caret; /** * \class caret::CaretMappableDataFileAndMapSelectorObject * \brief Widgets for selecting a map file and map index. * \ingroup GuiQt */ /** * Constructor that creates a selection object. User will need to * insert a model into an instance created with this constructor. * This constructor is used when the selection model is stored * in another object. * * @param options * Options for this instance. * @param parent * Parent of this instance. */ CaretMappableDataFileAndMapSelectorObject::CaretMappableDataFileAndMapSelectorObject(const Options options, QObject* parent) : QObject(parent) { m_model = NULL; initializeConstruction(options); m_needToDestroyModelFlag = false; } /** * Constructor that creates a selection object with a model for the selected * data file type. * * @param dataFileType * File type for selection * @param options * Options for this instance. * @param parent * Parent of this instance. */ CaretMappableDataFileAndMapSelectorObject::CaretMappableDataFileAndMapSelectorObject(const DataFileTypeEnum::Enum dataFileType, const Options options, QObject* parent) : QObject(parent) { m_model = new CaretMappableDataFileAndMapSelectionModel(GuiManager::get()->getBrain(), dataFileType); initializeConstruction(options); m_needToDestroyModelFlag = true; } /** * Constructor that creates a selection object with a model for the selected * data file types. * * @param dataFileTypes * File types for selection * @param options * Options for this instance. * @param parent * Parent of this instance. */ CaretMappableDataFileAndMapSelectorObject::CaretMappableDataFileAndMapSelectorObject(const std::vector& dataFileTypes, const Options options, QObject* parent) : QObject(parent) { m_model = new CaretMappableDataFileAndMapSelectionModel(GuiManager::get()->getBrain(), dataFileTypes); initializeConstruction(options); m_needToDestroyModelFlag = true; } /** * Destructor. */ CaretMappableDataFileAndMapSelectorObject::~CaretMappableDataFileAndMapSelectorObject() { if (m_needToDestroyModelFlag) { delete m_model; m_model = NULL; } } /** * Assist with construction. * * @param options * Options for this instance. */ void CaretMappableDataFileAndMapSelectorObject::initializeConstruction(const Options options) { m_enabled = true; m_mapFileComboBox = new CaretDataFileSelectionComboBox(this); QObject::connect(m_mapFileComboBox, SIGNAL(fileSelected(CaretDataFile*)), this, SLOT(mapFileComboBoxFileSelected(CaretDataFile*))); m_mapIndexSpinBox = NULL; if (options & OPTION_SHOW_MAP_INDEX_SPIN_BOX) { m_mapIndexSpinBox = new QSpinBox(); m_mapIndexSpinBox->setMinimum(1); m_mapIndexSpinBox->setSingleStep(1); QObject::connect(m_mapIndexSpinBox, SIGNAL(valueChanged(int)), this, SLOT(mapIndexSpinBoxValuesChanged(int))); } m_mapNameComboBox = new QComboBox(); QObject::connect(m_mapNameComboBox, SIGNAL(activated(int)), this, SLOT(mapNameComboBoxActivated(int))); WuQEventBlockingFilter::blockMouseWheelEventInMacComboBox(m_mapNameComboBox); } /** * Update the model in this file and map selector. * * @param model * The model. */ void CaretMappableDataFileAndMapSelectorObject::updateFileAndMapSelector(CaretMappableDataFileAndMapSelectionModel* model) { CaretAssert(model); m_model = model; m_mapFileComboBox->updateComboBox(model->getCaretDataFileSelectionModel()); updateContent(); } /** * @return Model in this file and map selector. */ CaretMappableDataFileAndMapSelectionModel* CaretMappableDataFileAndMapSelectorObject::getModel() { return m_model; } /** * Get the widgets for this file and map selector. * * @param mapFileComboBox * Combo box for file selection. * @param mapIndexSpinBox * Spin box for map index selection. * @param mapNameComboBox * Combo box for map name selection. */ void CaretMappableDataFileAndMapSelectorObject::getWidgetsForAddingToLayout(QWidget* &mapFileComboBox, QWidget* &mapIndexSpinBox, QWidget* &mapNameComboBox) { mapFileComboBox = m_mapFileComboBox->getWidget(); mapIndexSpinBox = m_mapIndexSpinBox; mapNameComboBox = m_mapNameComboBox; } /** * Update the content of this object. */ void CaretMappableDataFileAndMapSelectorObject::updateContent() { bool validFlag = false; bool validMapsFlag = false; if (m_model != NULL) { CaretMappableDataFile* mapFile = m_model->getSelectedFile(); if (mapFile != NULL) { validFlag = true; const int32_t numMaps = mapFile->getNumberOfMaps(); const int32_t mapIndex = m_model->getSelectedMapIndex(); if ((mapIndex >= 0) && (mapIndex < numMaps)) { validMapsFlag = true; if (m_mapIndexSpinBox != NULL) { m_mapIndexSpinBox->blockSignals(true); m_mapIndexSpinBox->setMaximum(numMaps); m_mapIndexSpinBox->blockSignals(false); } m_mapNameComboBox->clear(); for (int32_t i = 0; i < numMaps; i++) { m_mapNameComboBox->addItem(mapFile->getMapName(i)); } if (m_mapIndexSpinBox != NULL) { /* * Note: Indices are zero to num-maps minus 1 * but show 1 to num-maps */ m_mapIndexSpinBox->blockSignals(true); m_mapIndexSpinBox->setValue(mapIndex + 1); m_mapIndexSpinBox->blockSignals(false); } m_mapNameComboBox->setCurrentIndex(mapIndex); } /* * Dense connectivity does not allow map selection. */ if (mapFile->getDataFileType() == DataFileTypeEnum::CONNECTIVITY_DENSE) { validMapsFlag = false; } } } const bool fileEnabledFlag = (validFlag & m_enabled); m_mapFileComboBox->getWidget()->setEnabled(fileEnabledFlag); const bool mapEnabledFlag = (validMapsFlag && m_enabled); if (m_mapIndexSpinBox != NULL) { m_mapIndexSpinBox->setEnabled(mapEnabledFlag); } m_mapNameComboBox->setEnabled(mapEnabledFlag); } /** * Gets called when a file is selected by the user. * * @param caretDataFile * File that is selected. */ void CaretMappableDataFileAndMapSelectorObject::mapFileComboBoxFileSelected(CaretDataFile* /*caretDataFile*/) { updateContent(); emit selectionWasPerformed(); } /** * Gets called when a file is selected by the user. * * @param mapIndex * Index of the map. */ void CaretMappableDataFileAndMapSelectorObject::mapIndexSpinBoxValuesChanged(int mapIndex) { if (m_model != NULL) { /* * Note: Indices are zero to num-maps minus 1 * but show 1 to num-maps */ int32_t zeroToOneMapIndex = mapIndex - 1; m_model->setSelectedMapIndex(zeroToOneMapIndex); updateContent(); emit selectionWasPerformed(); } } /** * Gets called when a file is selected by the user. * * @param mapIndex * Index of the map. */ void CaretMappableDataFileAndMapSelectorObject::mapNameComboBoxActivated(int mapIndex) { if (m_model != NULL) { m_model->setSelectedMapIndex(mapIndex); updateContent(); emit selectionWasPerformed(); } } /** * @return Is the selector's widgets enabled. */ bool CaretMappableDataFileAndMapSelectorObject::isEnabled() const { return m_enabled; } /** * Set the selector's widgets enabled. * * @enabled * New enabled status. */ void CaretMappableDataFileAndMapSelectorObject::setEnabled(const bool enabled) { m_enabled = enabled; updateContent(); } workbench-1.1.1/src/GuiQt/CaretMappableDataFileAndMapSelectorObject.h000066400000000000000000000075601255417355300255120ustar00rootroot00000000000000#ifndef __CARET_MAPPABLE_DATA_FILE_AND_MAP_SELECTOR_OBJECT_H__ #define __CARET_MAPPABLE_DATA_FILE_AND_MAP_SELECTOR_OBJECT_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "DataFileTypeEnum.h" class QComboBox; class QSpinBox; namespace caret { class CaretDataFile; class CaretDataFileSelectionComboBox; class CaretMappableDataFileAndMapSelectionModel; class CaretMappableDataFileAndMapSelectorObject : public QObject { Q_OBJECT public: /** Options for selector */ enum Options { /** Create the map index spin box */ OPTION_SHOW_MAP_INDEX_SPIN_BOX = 1 }; CaretMappableDataFileAndMapSelectorObject(const Options options, QObject* parent); CaretMappableDataFileAndMapSelectorObject(const DataFileTypeEnum::Enum dataFileType, const Options options, QObject* parent); CaretMappableDataFileAndMapSelectorObject(const std::vector& dataFileTypes, const Options options, QObject* parent); void updateFileAndMapSelector(CaretMappableDataFileAndMapSelectionModel* model); CaretMappableDataFileAndMapSelectionModel* getModel(); virtual ~CaretMappableDataFileAndMapSelectorObject(); void getWidgetsForAddingToLayout(QWidget* &mapFileComboBox, QWidget* &mapIndexSpinBox, QWidget* &mapNameComboBox); bool isEnabled() const; void setEnabled(const bool enabled); // ADD_NEW_METHODS_HERE signals: void selectionWasPerformed(); private slots: void mapFileComboBoxFileSelected(CaretDataFile* caretDataFile); void mapIndexSpinBoxValuesChanged(int); void mapNameComboBoxActivated(int); private: CaretMappableDataFileAndMapSelectorObject(const CaretMappableDataFileAndMapSelectorObject&); CaretMappableDataFileAndMapSelectorObject& operator=(const CaretMappableDataFileAndMapSelectorObject&); void initializeConstruction(const Options options); void updateContent(); CaretMappableDataFileAndMapSelectionModel* m_model; bool m_needToDestroyModelFlag; CaretDataFileSelectionComboBox* m_mapFileComboBox; QSpinBox* m_mapIndexSpinBox; QComboBox* m_mapNameComboBox; bool m_enabled; // ADD_NEW_MEMBERS_HERE }; #ifdef __CARET_MAPPABLE_DATA_FILE_AND_MAP_SELECTOR_OBJECT_DECLARE__ // #endif // __CARET_MAPPABLE_DATA_FILE_AND_MAP_SELECTOR_OBJECT_DECLARE__ } // namespace #endif //__CARET_MAPPABLE_DATA_FILE_AND_MAP_SELECTOR_OBJECT_H__ workbench-1.1.1/src/GuiQt/ChartHistoryViewController.cxx000066400000000000000000000522541255417355300234040ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #include #include #include #include #define __CHART_HISTORY_VIEW_CONTROLLER_DECLARE__ #include "ChartHistoryViewController.h" #undef __CHART_HISTORY_VIEW_CONTROLLER_DECLARE__ #include "Brain.h" #include "BrowserTabContent.h" #include "CaretAssert.h" #include "CaretColorEnumComboBox.h" #include "ChartDataCartesian.h" #include "ChartDataSource.h" #include "ChartModelDataSeries.h" #include "ChartModelFrequencySeries.h" #include "ChartModelTimeSeries.h" #include "EventManager.h" #include "EventUserInterfaceUpdate.h" #include "EventGraphicsUpdateOneWindow.h" #include "GuiManager.h" #include "ModelChart.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::ChartHistoryViewController * \brief Shows history of loaded charts in the selected tab. * \ingroup GuiQt */ /** * Constructor. */ ChartHistoryViewController::ChartHistoryViewController(const Qt::Orientation orientation, const int32_t browserWindowIndex, QWidget* parent) : QWidget(parent), m_orientation(orientation), m_browserWindowIndex(browserWindowIndex) { m_averageCheckBox = new QCheckBox("Show Average"); WuQtUtilities::setWordWrappedToolTip(m_averageCheckBox, "Display an average of the displayed chart data. " "NOTE: If the charts contain a different number of points " "the average will be that of those charts that contain " "the same number of points as the most recently displayed " "chart."); QObject::connect(m_averageCheckBox, SIGNAL(clicked(bool)), this, SLOT(averageCheckBoxClicked(bool))); QPushButton* clearPushButton = new QPushButton("Clear"); clearPushButton->setFixedWidth(clearPushButton->sizeHint().width() + 20); QObject::connect(clearPushButton, SIGNAL(clicked()), this, SLOT(clearPushButtonClicked())); WuQtUtilities::setWordWrappedToolTip(clearPushButton, "Remove all charts of the selected type in this tab"); QLabel* maximumDisplayedLabel = new QLabel("Show last "); m_maximumDisplayedSpinBox = new QSpinBox(); m_maximumDisplayedSpinBox->setMinimum(1); m_maximumDisplayedSpinBox->setMaximum(1000); QObject::connect(m_maximumDisplayedSpinBox, SIGNAL(valueChanged(int)), this, SLOT(maximumDisplayedSpinBoxValueChanged(int))); QHBoxLayout* maxDisplayedLayout = new QHBoxLayout(); maxDisplayedLayout->addWidget(maximumDisplayedLabel); maxDisplayedLayout->addWidget(m_maximumDisplayedSpinBox); maxDisplayedLayout->addStretch(); WuQtUtilities::setWordWrappedToolTip(m_maximumDisplayedSpinBox, "Maximum number of charts of the selected type " "displayed in this tab"); m_chartDataCheckBoxesSignalMapper = new QSignalMapper(this); QObject::connect(m_chartDataCheckBoxesSignalMapper, SIGNAL(mapped(int)), this, SLOT(chartDataCheckBoxSignalMapped(int))); m_chartDataColorComboBoxesSignalMapper = new QSignalMapper(this); QObject::connect(m_chartDataColorComboBoxesSignalMapper, SIGNAL(mapped(int)), this, SLOT(chartDataColorComboBoxSignalMapped(int))); m_chartDataColorConstructionButtonSignalMapper = new QSignalMapper(this); QObject::connect(m_chartDataColorConstructionButtonSignalMapper, SIGNAL(mapped(int)), this, SLOT(chartDataConstructionToolButtonSignalMapped(int))); QWidget* chartDataWidget = new QWidget(); m_chartDataGridLayout = new QGridLayout(chartDataWidget); m_chartDataGridLayout->setHorizontalSpacing(6); m_chartDataGridLayout->setVerticalSpacing(2); m_chartDataGridLayout->setContentsMargins(0, 0, 0, 0); m_chartDataGridLayout->setColumnStretch(COLUMN_CHART_DATA_CHECKBOX, 0); m_chartDataGridLayout->setColumnStretch(COLUMN_CHART_DATA_CONSTRUCTION, 0); m_chartDataGridLayout->setColumnStretch(COLUMN_CHART_DATA_COLOR, 0); m_chartDataGridLayout->setColumnStretch(COLUMN_CHART_DATA_NAME, 100); m_chartDataGridLayout->addWidget(new QLabel("On"), 0, COLUMN_CHART_DATA_CHECKBOX, Qt::AlignHCenter); m_chartDataGridLayout->addWidget(new QLabel("Move"), 0, COLUMN_CHART_DATA_CONSTRUCTION, Qt::AlignHCenter); m_chartDataGridLayout->addWidget(new QLabel("Color"), 0, COLUMN_CHART_DATA_COLOR, Qt::AlignHCenter); QVBoxLayout* leftOrTopLayout = new QVBoxLayout(); leftOrTopLayout->addWidget(m_averageCheckBox); leftOrTopLayout->addLayout(maxDisplayedLayout); leftOrTopLayout->addWidget(clearPushButton); leftOrTopLayout->addStretch(); QVBoxLayout* rightOrBottomLayout = new QVBoxLayout(); rightOrBottomLayout->addWidget(chartDataWidget); rightOrBottomLayout->addStretch(); switch (m_orientation) { case Qt::Horizontal: { m_chartDataGridLayout->addWidget(new QLabel("Name"), 0, COLUMN_CHART_DATA_NAME, Qt::AlignHCenter); QHBoxLayout* layout = new QHBoxLayout(this); layout->addLayout(leftOrTopLayout, 0); layout->addWidget(WuQtUtilities::createVerticalLineWidget()); layout->addLayout(rightOrBottomLayout, 100); layout->addStretch(); } break; case Qt::Vertical: { QVBoxLayout* layout = new QVBoxLayout(this); layout->addLayout(leftOrTopLayout, 0); layout->addLayout(rightOrBottomLayout, 100); layout->addStretch(); } break; } EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_USER_INTERFACE_UPDATE); } /** * Destructor. */ ChartHistoryViewController::~ChartHistoryViewController() { EventManager::get()->removeAllEventsFromListener(this); } /** * Gets called when the average checkbox is clicked. * * @param clicked * New status. */ void ChartHistoryViewController::averageCheckBoxClicked(bool clicked) { ChartModel* chartModel = NULL; int32_t tabIndex = -1; getSelectedChartModelAndTab(chartModel, tabIndex); if (chartModel == NULL) { return; } chartModel->setAverageChartDisplaySelected(clicked); updateAfterSelectionsChanged(); } /** * Called when clear push button is clicked. */ void ChartHistoryViewController::clearPushButtonClicked() { ChartModel* chartModel = NULL; int32_t tabIndex = -1; getSelectedChartModelAndTab(chartModel, tabIndex); if (chartModel == NULL) { return; } chartModel->removeChartData(); updateAfterSelectionsChanged(); } /** * Called when maximum number of displayed charts is changed. * * @param value * New value. */ void ChartHistoryViewController::maximumDisplayedSpinBoxValueChanged(int value) { ChartModel* chartModel = NULL; int32_t tabIndex = -1; getSelectedChartModelAndTab(chartModel, tabIndex); if (chartModel == NULL) { return; } switch (chartModel->getChartSelectionMode()) { case ChartSelectionModeEnum::CHART_SELECTION_MODE_SINGLE: break; case ChartSelectionModeEnum::CHART_SELECTION_MODE_ANY: chartModel->setMaximumNumberOfChartDatasToDisplay(value); break; } updateAfterSelectionsChanged(); } /** * Update after selections in this view controller are changed. */ void ChartHistoryViewController::updateAfterSelectionsChanged() { EventManager::get()->sendEvent(EventGraphicsUpdateOneWindow(m_browserWindowIndex).getPointer()); updateHistoryViewController(); } /** * Update this view controller. */ void ChartHistoryViewController::updateHistoryViewController() { ChartModel* chartModel = NULL; int32_t tabIndex = -1; getSelectedChartModelAndTab(chartModel, tabIndex); if (chartModel == NULL) { return; } switch (chartModel->getChartSelectionMode()) { case ChartSelectionModeEnum::CHART_SELECTION_MODE_ANY: m_maximumDisplayedSpinBox->setEnabled(true); m_maximumDisplayedSpinBox->setValue(chartModel->getMaximumNumberOfChartDatasToDisplay()); break; case ChartSelectionModeEnum::CHART_SELECTION_MODE_SINGLE: m_maximumDisplayedSpinBox->setValue(1); m_maximumDisplayedSpinBox->setEnabled(false); break; } const std::vector chartDataVector = chartModel->getAllChartDatas(); const int32_t numData = static_cast(chartDataVector.size()); const int32_t numWidgetRows = static_cast(m_chartDataCheckBoxes.size()); const int32_t maxItems = std::max(numData, numWidgetRows); for (int32_t i = 0; i < maxItems; i++) { if (i >= static_cast(m_chartDataCheckBoxes.size())) { /* * Checkbox */ QCheckBox* checkBox = new QCheckBox(" "); QObject::connect(checkBox, SIGNAL(clicked(bool)), m_chartDataCheckBoxesSignalMapper, SLOT(map())); m_chartDataCheckBoxesSignalMapper->setMapping(checkBox, i); m_chartDataCheckBoxes.push_back(checkBox); /* * Construction Tool Button */ QIcon constructionIcon; const bool constructionIconValid = WuQtUtilities::loadIcon(":/LayersPanel/construction.png", constructionIcon); QToolButton* constructionToolButton = new QToolButton(); if (constructionIconValid) { constructionToolButton->setIcon(constructionIcon); } else { constructionToolButton->setText("M"); } m_chartDataContructionToolButtons.push_back(constructionToolButton); QObject::connect(constructionToolButton, SIGNAL(clicked()), m_chartDataColorConstructionButtonSignalMapper, SLOT(map())); m_chartDataColorConstructionButtonSignalMapper->setMapping(constructionToolButton, i); /* * Color */ CaretColorEnumComboBox* colorComboBox = new CaretColorEnumComboBox(this); QObject::connect(colorComboBox, SIGNAL(colorSelected(const CaretColorEnum::Enum)), m_chartDataColorComboBoxesSignalMapper, SLOT(map())); m_chartDataColorComboBoxesSignalMapper->setMapping(colorComboBox, i); m_chartDataColorComboBoxes.push_back(colorComboBox); /* * Label */ m_chartDataNameLabels.push_back(new QLabel()); /* * Layout */ const int widgetIndex = static_cast(m_chartDataCheckBoxes.size()) - 1; CaretAssertVectorIndex(m_chartDataCheckBoxes, widgetIndex); const int row = m_chartDataGridLayout->rowCount(); m_chartDataGridLayout->addWidget(m_chartDataCheckBoxes[widgetIndex], row, COLUMN_CHART_DATA_CHECKBOX); m_chartDataGridLayout->addWidget(constructionToolButton, row, COLUMN_CHART_DATA_CONSTRUCTION); m_chartDataGridLayout->addWidget(m_chartDataColorComboBoxes[widgetIndex]->getWidget(), row, COLUMN_CHART_DATA_COLOR); switch (m_orientation) { case Qt::Horizontal: { m_chartDataGridLayout->addWidget(m_chartDataNameLabels[widgetIndex], row, COLUMN_CHART_DATA_NAME); } break; case Qt::Vertical: { const int nextRow = m_chartDataGridLayout->rowCount(); m_chartDataGridLayout->addWidget(m_chartDataNameLabels[widgetIndex], nextRow, COLUMN_CHART_DATA_CHECKBOX, 1, COLUMN_COUNT, Qt::AlignLeft); } break; } } CaretAssertVectorIndex(m_chartDataCheckBoxes, i); CaretAssertVectorIndex(m_chartDataContructionToolButtons, i); CaretAssertVectorIndex(m_chartDataColorComboBoxes, i); CaretAssertVectorIndex(m_chartDataNameLabels, i); QCheckBox* checkBox = m_chartDataCheckBoxes[i]; QToolButton* constructToolButton = m_chartDataContructionToolButtons[i]; CaretColorEnumComboBox* colorComboBox = m_chartDataColorComboBoxes[i]; QLabel* nameLabel = m_chartDataNameLabels[i]; bool showRowFlag = false; if (i < numData) { showRowFlag = true; const ChartData* chartData = chartDataVector[i]; const ChartDataCartesian* chartDataCartesian = dynamic_cast(chartData); const ChartDataSource* dataSource = chartData->getChartDataSource(); checkBox->setChecked(chartData->isSelected(tabIndex)); if (chartDataCartesian != NULL) { colorComboBox->setSelectedColor(chartDataCartesian->getColor()); colorComboBox->getWidget()->setEnabled(true); } else { colorComboBox->getWidget()->setEnabled(false); } nameLabel->setText(dataSource->getDescription()); } checkBox->setVisible(showRowFlag); constructToolButton->setVisible(showRowFlag); colorComboBox->getWidget()->setVisible(showRowFlag); nameLabel->setVisible(showRowFlag); } /* * Update averaging. */ bool enableAvergeWidgetsFlag = false; if (chartModel != NULL) { if (chartModel->isAverageChartDisplaySupported()) { enableAvergeWidgetsFlag = true; m_averageCheckBox->setChecked(chartModel->isAverageChartDisplaySelected()); } } m_averageCheckBox->setEnabled(enableAvergeWidgetsFlag); } /** * Get the chart model and the selected tab. * * @param chartModelOut * Output containing chart model (may be NULL). * @param tabIndexOut * Output containing tab index (negative if invalid). */ void ChartHistoryViewController::getSelectedChartModelAndTab(ChartModel* &chartModelOut, int32_t& tabIndexOut) { chartModelOut = NULL; tabIndexOut = -1; Brain* brain = GuiManager::get()->getBrain(); BrowserTabContent* browserTabContent = GuiManager::get()->getBrowserTabContentForBrowserWindow(m_browserWindowIndex, true); if (browserTabContent == NULL) { return; } tabIndexOut = browserTabContent->getTabNumber(); ModelChart* modelChart = brain->getChartModel(); if (modelChart != NULL) { switch (modelChart->getSelectedChartDataType(tabIndexOut)) { case ChartDataTypeEnum::CHART_DATA_TYPE_INVALID: break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_LAYER: break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_SERIES: break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_DATA_SERIES: chartModelOut = modelChart->getSelectedDataSeriesChartModel(tabIndexOut); break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_FREQUENCY_SERIES: chartModelOut = modelChart->getSelectedFrequencySeriesChartModel(tabIndexOut); break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_TIME_SERIES: chartModelOut = modelChart->getSelectedTimeSeriesChartModel(tabIndexOut); break; } } } /** * Called when a check box is changed. * * @param indx * Index of checkbox. */ void ChartHistoryViewController::chartDataCheckBoxSignalMapped(int indx) { CaretAssertVectorIndex(m_chartDataCheckBoxes, indx); ChartModel* chartModel = NULL; int32_t tabIndex = -1; getSelectedChartModelAndTab(chartModel, tabIndex); if (chartModel == NULL) { return; } std::vector chartDataVector = chartModel->getAllChartDatas(); CaretAssertVectorIndex(chartDataVector, indx); chartDataVector[indx]->setSelected(tabIndex, m_chartDataCheckBoxes[indx]->isChecked()); updateAfterSelectionsChanged(); } /** * Called when a check box is changed. * * @param indx * Index of checkbox. */ void ChartHistoryViewController::chartDataColorComboBoxSignalMapped(int indx) { CaretAssertVectorIndex(m_chartDataColorComboBoxes, indx); ChartModel* chartModel = NULL; int32_t tabIndex = -1; getSelectedChartModelAndTab(chartModel, tabIndex); if (chartModel == NULL) { return; } std::vector chartDataVector = chartModel->getAllChartDatas(); CaretAssertVectorIndex(chartDataVector, indx); ChartDataCartesian* chartDataCartesian = dynamic_cast(chartDataVector[indx]); if (chartDataCartesian != NULL) { chartDataCartesian->setColor(m_chartDataColorComboBoxes[indx]->getSelectedColor()); } updateAfterSelectionsChanged(); } /** * Called when construction tool button is clicked * * @param indx * Index of tool button. */ void ChartHistoryViewController::chartDataConstructionToolButtonSignalMapped(int indx) { CaretAssertVectorIndex(m_chartDataContructionToolButtons, indx); ChartModel* chartModel = NULL; int32_t tabIndex = -1; getSelectedChartModelAndTab(chartModel, tabIndex); if (chartModel == NULL) { return; } std::vector chartDataVector = chartModel->getAllChartDatas(); const int32_t numCharts = static_cast(chartDataVector.size()); QMenu menu(m_chartDataContructionToolButtons[indx]); QAction* moveUpAction = menu.addAction("Move Chart Up"); if (indx <= 0) { moveUpAction->setEnabled(false); } QAction* moveDownAction = menu.addAction("Move Chart Down"); if (indx >= (numCharts - 1)) { moveDownAction->setEnabled(false); } QAction* removeAction = menu.addAction("Remove Chart"); QAction* selectedAction = menu.exec(m_chartDataContructionToolButtons[indx]->mapToGlobal(QPoint(0,0))); if (selectedAction != NULL) { if (selectedAction == moveUpAction) { chartModel->moveChartDataAtIndexToOneLowerIndex(indx); } else if (selectedAction == moveDownAction) { chartModel->moveChartDataAtIndexToOneHigherIndex(indx); } else if (selectedAction == removeAction) { chartModel->removeChartAtIndex(indx); } else { CaretAssertMessage(0, "Has a new action been added but not processed?"); } this->updateAfterSelectionsChanged(); } } /** * Receive an event. * * @param event * An event for which this instance is listening. */ void ChartHistoryViewController::receiveEvent(Event* event) { if (event->getEventType() == EventTypeEnum::EVENT_USER_INTERFACE_UPDATE) { EventUserInterfaceUpdate* uiEvent = dynamic_cast(event); CaretAssert(uiEvent); if (uiEvent->isUpdateForWindow(m_browserWindowIndex) || uiEvent->isToolBoxUpdate()) { this->updateHistoryViewController(); uiEvent->setEventProcessed(); } } } workbench-1.1.1/src/GuiQt/ChartHistoryViewController.h000066400000000000000000000100111255417355300230120ustar00rootroot00000000000000#ifndef __CHART_HISTORY_VIEW_CONTROLLER_H__ #define __CHART_HISTORY_VIEW_CONTROLLER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "EventListenerInterface.h" class QCheckBox; class QGridLayout; class QLabel; class QMenu; class QSignalMapper; class QSpinBox; class QToolButton; namespace caret { class CaretColorEnumComboBox; class ChartModel; class ChartHistoryViewController : public QWidget, public EventListenerInterface { Q_OBJECT public: ChartHistoryViewController(const Qt::Orientation orientation, const int32_t browserWindowIndex, QWidget* parent); virtual ~ChartHistoryViewController(); private: ChartHistoryViewController(const ChartHistoryViewController&); ChartHistoryViewController& operator=(const ChartHistoryViewController&); public: // ADD_NEW_METHODS_HERE virtual void receiveEvent(Event* event); private slots: void averageCheckBoxClicked(bool); void clearPushButtonClicked(); void maximumDisplayedSpinBoxValueChanged(int); void chartDataCheckBoxSignalMapped(int); void chartDataColorComboBoxSignalMapped(int); void chartDataConstructionToolButtonSignalMapped(int); private: // ADD_NEW_MEMBERS_HERE void updateAfterSelectionsChanged(); void updateHistoryViewController(); void getSelectedChartModelAndTab(ChartModel* &chartModelOut, int32_t& tabIndexOut); const Qt::Orientation m_orientation; const int32_t m_browserWindowIndex; QCheckBox* m_averageCheckBox; QSpinBox* m_maximumDisplayedSpinBox; QGridLayout* m_chartDataGridLayout; std::vector m_chartDataCheckBoxes; std::vector m_chartDataContructionToolButtons; std::vector m_chartDataColorComboBoxes; std::vector m_chartDataNameLabels; QSignalMapper* m_chartDataCheckBoxesSignalMapper; QSignalMapper* m_chartDataColorComboBoxesSignalMapper; QSignalMapper* m_chartDataColorConstructionButtonSignalMapper; static const int32_t COLUMN_CHART_DATA_CHECKBOX; static const int32_t COLUMN_CHART_DATA_CONSTRUCTION; static const int32_t COLUMN_CHART_DATA_COLOR; static const int32_t COLUMN_CHART_DATA_NAME; static const int32_t COLUMN_COUNT; }; #ifdef __CHART_HISTORY_VIEW_CONTROLLER_DECLARE__ const int32_t ChartHistoryViewController::COLUMN_CHART_DATA_CHECKBOX = 0; const int32_t ChartHistoryViewController::COLUMN_CHART_DATA_CONSTRUCTION = 1; const int32_t ChartHistoryViewController::COLUMN_CHART_DATA_COLOR = 2; const int32_t ChartHistoryViewController::COLUMN_CHART_DATA_NAME = 3; const int32_t ChartHistoryViewController::COLUMN_COUNT = 4; #endif // __CHART_HISTORY_VIEW_CONTROLLER_DECLARE__ } // namespace #endif //__CHART_HISTORY_VIEW_CONTROLLER_H__ workbench-1.1.1/src/GuiQt/ChartLinesSelectionViewController.cxx000066400000000000000000000341501255417355300246560ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CHART_LINES_SELECTION_VIEW_CONTROLLER_DECLARE__ #include "ChartLinesSelectionViewController.h" #undef __CHART_LINES_SELECTION_VIEW_CONTROLLER_DECLARE__ #include #include #include #include #include #include #include #include #include "Brain.h" #include "BrowserTabContent.h" #include "CaretAssert.h" #include "CaretLogger.h" #include "CaretMappableDataFile.h" #include "ChartModel.h" #include "ChartableLineSeriesBrainordinateInterface.h" #include "ChartableMatrixSeriesInterface.h" #include "CiftiMappableDataFile.h" #include "CiftiParcelLabelFile.h" #include "EventManager.h" #include "EventGraphicsUpdateAllWindows.h" #include "EventGraphicsUpdateOneWindow.h" #include "EventPaletteColorMappingEditorDialogRequest.h" #include "EventSurfaceColoringInvalidate.h" #include "EventUserInterfaceUpdate.h" #include "GuiManager.h" #include "MapYokingGroupComboBox.h" #include "ModelChart.h" #include "WuQFactory.h" #include "WuQMessageBox.h" #include "WuQtUtilities.h" using namespace caret; static const char* BRAINORDINATE_FILE_POINTER_PROPERTY_NAME = "brainordinateFilePointer"; /** * \class caret::ChartLinesSelectionViewController * \brief Handles selection of charts displayed in chart model. * \ingroup GuiQt */ /** * Constructor. */ ChartLinesSelectionViewController::ChartLinesSelectionViewController(const Qt::Orientation /*orientation */, const int32_t browserWindowIndex, QWidget* parent) : QWidget(parent), m_browserWindowIndex(browserWindowIndex) { /* * In the grid layout, there are columns for the checkboxes (used * for brainordinate charts) and radio buttons (used for matrix * charts). Display of checkboxes and radiobuttons is mutually * exclusive. The "Select" column title is over both the checkbox * and radio button columns. */ m_brainordinateGridLayout = new QGridLayout(); WuQtUtilities::setLayoutSpacingAndMargins(m_brainordinateGridLayout, 4, 2); m_brainordinateGridLayout->setColumnStretch(BRAINORDINATE_COLUMN_CHECKBOX, 0); m_brainordinateGridLayout->setColumnStretch(BRAINORDINATE_COLUMN_YOKING_COMBO_BOX, 0); m_brainordinateGridLayout->setColumnStretch(BRAINORDINATE_COLUMN_LINE_EDIT, 100); const int titleRow = m_brainordinateGridLayout->rowCount(); m_brainordinateGridLayout->addWidget(new QLabel("Select"), titleRow, BRAINORDINATE_COLUMN_CHECKBOX, Qt::AlignHCenter); m_brainordinateGridLayout->addWidget(new QLabel("Yoke"), titleRow, BRAINORDINATE_COLUMN_YOKING_COMBO_BOX, Qt::AlignHCenter); m_brainordinateGridLayout->addWidget(new QLabel("Charting File"), titleRow, BRAINORDINATE_COLUMN_LINE_EDIT, Qt::AlignHCenter); m_signalMapperBrainordinateFileEnableCheckBox = new QSignalMapper(this); QObject::connect(m_signalMapperBrainordinateFileEnableCheckBox, SIGNAL(mapped(int)), this, SLOT(brainordinateSelectionCheckBoxClicked(int))); m_signalMapperBrainordinateYokingComboBox = new QSignalMapper(this); QObject::connect(m_signalMapperBrainordinateYokingComboBox, SIGNAL(mapped(int)), this, SLOT(brainordinateYokingComboBoxActivated(int))); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_USER_INTERFACE_UPDATE); QVBoxLayout* layout = new QVBoxLayout(this); WuQtUtilities::setLayoutSpacingAndMargins(layout, 0, 0); layout->addLayout(m_brainordinateGridLayout); layout->addStretch(); } /** * Destructor. */ ChartLinesSelectionViewController::~ChartLinesSelectionViewController() { EventManager::get()->removeAllEventsFromListener(this); } /** * Update the view controller. */ void ChartLinesSelectionViewController::updateSelectionViewController() { Brain* brain = GuiManager::get()->getBrain(); BrowserTabContent* browserTabContent = GuiManager::get()->getBrowserTabContentForBrowserWindow(m_browserWindowIndex, true); if (browserTabContent == NULL) { return; } const int32_t browserTabIndex = browserTabContent->getTabNumber(); ChartDataTypeEnum::Enum chartDataType = ChartDataTypeEnum::CHART_DATA_TYPE_INVALID; ModelChart* modelChart = brain->getChartModel(); if (modelChart != NULL) { chartDataType = modelChart->getSelectedChartDataType(browserTabIndex); } bool validFlag = false; switch (chartDataType) { case ChartDataTypeEnum::CHART_DATA_TYPE_INVALID: break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_LAYER: break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_SERIES: break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_DATA_SERIES: validFlag = true; break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_FREQUENCY_SERIES: validFlag = true; break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_TIME_SERIES: validFlag = true; break; } if (validFlag) { std::vector chartableLineSeriesFilesVector; const ChartDataTypeEnum::Enum chartDataType = modelChart->getSelectedChartDataType(browserTabIndex); brain->getAllChartableLineSeriesDataFilesForChartDataType(chartDataType, chartableLineSeriesFilesVector); const int32_t numChartableFiles = static_cast(chartableLineSeriesFilesVector.size()); for (int32_t i = 0; i < numChartableFiles; i++) { QCheckBox* checkBox = NULL; QLineEdit* lineEdit = NULL; MapYokingGroupComboBox* yokeComboBox = NULL; if (i < static_cast(m_fileInfoRows.size())) { checkBox = m_fileInfoRows[i].m_fileEnableCheckBox;// m_brainordinateFileEnableCheckBoxes[i]; lineEdit = m_fileInfoRows[i].m_fileNameLineEdit; // m_brainordinateFileNameLineEdits[i]; yokeComboBox = m_fileInfoRows[i].m_fileYokingComboBox; // m_brainordinateSeriesYokingComboBoxes[i]; } else { checkBox = new QCheckBox(""); QObject::connect(checkBox, SIGNAL(clicked(bool)), m_signalMapperBrainordinateFileEnableCheckBox, SLOT(map())); m_signalMapperBrainordinateFileEnableCheckBox->setMapping(checkBox, i); yokeComboBox = new MapYokingGroupComboBox(this); yokeComboBox->getWidget()->setStatusTip("Synchronize enabled status and map indices)"); yokeComboBox->getWidget()->setToolTip("Yoke to Overlay Mapped Files"); #ifdef CARET_OS_MACOSX yokeComboBox->getWidget()->setFixedWidth(yokeComboBox->getWidget()->sizeHint().width() - 20); #endif // CARET_OS_MACOSX QObject::connect(yokeComboBox, SIGNAL(itemActivated()), m_signalMapperBrainordinateYokingComboBox, SLOT(map())); m_signalMapperBrainordinateYokingComboBox->setMapping(yokeComboBox, i); lineEdit = new QLineEdit(); lineEdit->setReadOnly(true); FileInfoRow rowInfo; rowInfo.m_fileEnableCheckBox = checkBox; rowInfo.m_fileNameLineEdit = lineEdit; rowInfo.m_fileYokingComboBox = yokeComboBox; rowInfo.m_lineSeriesFile = NULL; m_fileInfoRows.push_back(rowInfo); const int row = m_brainordinateGridLayout->rowCount(); m_brainordinateGridLayout->addWidget(checkBox, row, BRAINORDINATE_COLUMN_CHECKBOX, Qt::AlignHCenter); m_brainordinateGridLayout->addWidget(yokeComboBox->getWidget(), row, BRAINORDINATE_COLUMN_YOKING_COMBO_BOX, Qt::AlignHCenter); m_brainordinateGridLayout->addWidget(lineEdit, row, BRAINORDINATE_COLUMN_LINE_EDIT); } CaretAssertVectorIndex(chartableLineSeriesFilesVector, i); ChartableLineSeriesInterface* chartBrainFile = chartableLineSeriesFilesVector[i]; CaretAssert(chartBrainFile); m_fileInfoRows[i].m_lineSeriesFile = chartBrainFile; const bool checkBoxStatus = chartBrainFile->isLineSeriesChartingEnabled(browserTabIndex); QVariant brainordinateFilePointerVariant = qVariantFromValue((void*)chartBrainFile); CaretMappableDataFile* caretMappableDataFile = chartBrainFile->getLineSeriesChartCaretMappableDataFile(); checkBox->blockSignals(true); checkBox->setChecked(checkBoxStatus); checkBox->blockSignals(false); checkBox->setProperty(BRAINORDINATE_FILE_POINTER_PROPERTY_NAME, brainordinateFilePointerVariant); /* * Could be line chart for matrix series file */ ChartableMatrixSeriesInterface* matrixFile = dynamic_cast(caretMappableDataFile); if (matrixFile != NULL) { yokeComboBox->getWidget()->setEnabled(true); yokeComboBox->setMapYokingGroup(matrixFile->getMatrixRowColumnMapYokingGroup(browserTabIndex)); } else { yokeComboBox->getWidget()->setEnabled(false); } CaretAssert(caretMappableDataFile); lineEdit->setText(caretMappableDataFile->getFileName()); } const int32_t numItems = static_cast(m_fileInfoRows.size()); for (int32_t i = 0; i < numItems; i++) { bool showCheckBox = false; bool showYokeComboBox = false; bool showLineEdit = false; if (i < numChartableFiles) { showLineEdit = true; showYokeComboBox = true; showCheckBox = true; } else { m_fileInfoRows[i].m_lineSeriesFile = NULL; } m_fileInfoRows[i].m_fileEnableCheckBox->setVisible(showCheckBox); m_fileInfoRows[i].m_fileNameLineEdit->setVisible(showLineEdit); m_fileInfoRows[i].m_fileYokingComboBox->getWidget()->setVisible(showYokeComboBox); } } } /** * Called when a brainordinate yoking combo box changes. * * @param indx * Index of yoking combo box that was clicked. */ void ChartLinesSelectionViewController::brainordinateYokingComboBoxActivated(int indx) { CaretAssertVectorIndex(m_fileInfoRows, indx); ChartableMatrixSeriesInterface* matrixFile = dynamic_cast(m_fileInfoRows[indx].m_lineSeriesFile); if (matrixFile != NULL) { BrowserTabContent* browserTabContent = GuiManager::get()->getBrowserTabContentForBrowserWindow(m_browserWindowIndex, true); if (browserTabContent == NULL) { return; } const int32_t browserTabIndex = browserTabContent->getTabNumber(); matrixFile->setMatrixRowColumnMapYokingGroup(browserTabIndex, m_fileInfoRows[indx].m_fileYokingComboBox->getMapYokingGroup()); } } /** * Called when a brainordinate enabled check box changes state. * * @param indx * Index of checkbox that was clicked. */ void ChartLinesSelectionViewController::brainordinateSelectionCheckBoxClicked(int indx) { CaretAssertVectorIndex(m_fileInfoRows, indx); BrowserTabContent* browserTabContent = GuiManager::get()->getBrowserTabContentForBrowserWindow(m_browserWindowIndex, true); if (browserTabContent == NULL) { return; } const int32_t browserTabIndex = browserTabContent->getTabNumber(); const bool newStatus = m_fileInfoRows[indx].m_fileEnableCheckBox->isChecked(); if (m_fileInfoRows[indx].m_lineSeriesFile != NULL) { m_fileInfoRows[indx].m_lineSeriesFile->setLineSeriesChartingEnabled(browserTabIndex, newStatus); } } /** * Receive an event. * * @param event * An event for which this instance is listening. */ void ChartLinesSelectionViewController::receiveEvent(Event* event) { if (event->getEventType() == EventTypeEnum::EVENT_USER_INTERFACE_UPDATE) { EventUserInterfaceUpdate* uiEvent = dynamic_cast(event); CaretAssert(uiEvent); if (uiEvent->isUpdateForWindow(m_browserWindowIndex) || uiEvent->isToolBoxUpdate()) { this->updateSelectionViewController(); uiEvent->setEventProcessed(); } } } workbench-1.1.1/src/GuiQt/ChartLinesSelectionViewController.h000066400000000000000000000066711255417355300243120ustar00rootroot00000000000000#ifndef __CHART_LINES_SELECTION_VIEW_CONTROLLER_H__ #define __CHART_LINES_SELECTION_VIEW_CONTROLLER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "EventListenerInterface.h" class QCheckBox; class QGridLayout; class QLineEdit; class QSignalMapper; namespace caret { class Brain; class ChartableLineSeriesInterface; class ChartableLineSeriesBrainordinateInterface; class MapYokingGroupComboBox; class ModelChart; class ChartLinesSelectionViewController : public QWidget, public EventListenerInterface { Q_OBJECT public: ChartLinesSelectionViewController(const Qt::Orientation orientation, const int32_t browserWindowIndex, QWidget* parent); virtual ~ChartLinesSelectionViewController(); private slots: void brainordinateSelectionCheckBoxClicked(int); void brainordinateYokingComboBoxActivated(int); private: ChartLinesSelectionViewController(const ChartLinesSelectionViewController&); ChartLinesSelectionViewController& operator=(const ChartLinesSelectionViewController&); public: // ADD_NEW_METHODS_HERE virtual void receiveEvent(Event* event); private: // ADD_NEW_MEMBERS_HERE void updateSelectionViewController(); const int32_t m_browserWindowIndex; struct FileInfoRow { QLineEdit* m_fileNameLineEdit; QCheckBox* m_fileEnableCheckBox; MapYokingGroupComboBox* m_fileYokingComboBox; ChartableLineSeriesInterface* m_lineSeriesFile; }; std::vector m_fileInfoRows; QGridLayout* m_brainordinateGridLayout; QSignalMapper* m_signalMapperBrainordinateFileEnableCheckBox; QSignalMapper* m_signalMapperBrainordinateYokingComboBox; static const int BRAINORDINATE_COLUMN_CHECKBOX; static const int BRAINORDINATE_COLUMN_YOKING_COMBO_BOX; static const int BRAINORDINATE_COLUMN_LINE_EDIT; }; #ifdef __CHART_LINES_SELECTION_VIEW_CONTROLLER_DECLARE__ const int ChartLinesSelectionViewController::BRAINORDINATE_COLUMN_CHECKBOX = 0; const int ChartLinesSelectionViewController::BRAINORDINATE_COLUMN_YOKING_COMBO_BOX = 1; const int ChartLinesSelectionViewController::BRAINORDINATE_COLUMN_LINE_EDIT = 2; #endif // __CHART_LINES_SELECTION_VIEW_CONTROLLER_DECLARE__ } // namespace #endif //__CHART_LINES_SELECTION_VIEW_CONTROLLER_H__ workbench-1.1.1/src/GuiQt/ChartMatrixParcelSelectionViewController.cxx000066400000000000000000001363301255417355300262020ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CHART_MATRIX_PARCEL_SELECTION_VIEW_CONTROLLER_DECLARE__ #include "ChartMatrixParcelSelectionViewController.h" #undef __CHART_MATRIX_PARCEL_SELECTION_VIEW_CONTROLLER_DECLARE__ #include #include #include #include #include #include #include #include "Brain.h" #include "BrowserTabContent.h" #include "CaretAssert.h" #include "CaretDataFileSelectionComboBox.h" #include "CaretDataFileSelectionModel.h" #include "CaretLogger.h" #include "CaretMappableDataFile.h" #include "CaretMappableDataFileAndMapSelectionModel.h" #include "CaretMappableDataFileAndMapSelectorObject.h" #include "ChartableMatrixInterface.h" #include "ChartMatrixDisplayProperties.h" #include "ChartMatrixLoadingDimensionEnum.h" #include "ChartableMatrixSeriesInterface.h" #include "ChartModel.h" #include "CiftiMappableConnectivityMatrixDataFile.h" #include "CiftiMappableDataFile.h" #include "CiftiParcelLabelFile.h" #include "DeveloperFlagsEnum.h" #include "EnumComboBoxTemplate.h" #include "EventChartMatrixParcelYokingValidation.h" #include "EventManager.h" #include "EventGraphicsUpdateAllWindows.h" #include "EventGraphicsUpdateOneWindow.h" #include "EventPaletteColorMappingEditorDialogRequest.h" #include "EventSurfaceColoringInvalidate.h" #include "EventUserInterfaceUpdate.h" #include "GuiManager.h" #include "ModelChart.h" #include "WuQFactory.h" #include "WuQMessageBox.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::ChartMatrixParcelSelectionViewController * \brief Handles selection of charts displayed in chart model. * \ingroup GuiQt */ /** * Constructor. */ ChartMatrixParcelSelectionViewController::ChartMatrixParcelSelectionViewController(const Qt::Orientation orientation, const int32_t browserWindowIndex, QWidget* parent) : QWidget(parent), m_browserWindowIndex(browserWindowIndex) { m_matrixParcelChartWidget = createMatrixParcelChartWidget(orientation); m_parcelRemappingGroupBox = createParcelRemappingWidget(orientation); QVBoxLayout* layout = new QVBoxLayout(this); WuQtUtilities::setLayoutSpacingAndMargins(layout, 1, 0); layout->addWidget(m_matrixParcelChartWidget); layout->addWidget(m_parcelRemappingGroupBox); //layout->addStretch(); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_USER_INTERFACE_UPDATE); // /* // * ColorBar Tool Button // */ // QIcon colorBarIcon; // const bool colorBarIconValid = WuQtUtilities::loadIcon(":/LayersPanel/colorbar.png", // colorBarIcon); // m_matrixParcelColorBarAction = WuQtUtilities::createAction("CB", // "Display color bar for this overlay", // this, // this, // SLOT(matrixParcelColorBarActionTriggered(bool))); // m_matrixParcelColorBarAction->setCheckable(true); // if (colorBarIconValid) { // m_matrixParcelColorBarAction->setIcon(colorBarIcon); // } // QToolButton* colorBarToolButton = new QToolButton(); // colorBarToolButton->setDefaultAction(m_matrixParcelColorBarAction); // // /* // * Settings Tool Button // */ // QLabel* settingsLabel = new QLabel("Settings"); // QIcon settingsIcon; // const bool settingsIconValid = WuQtUtilities::loadIcon(":/LayersPanel/wrench.png", // settingsIcon); // // m_matrixParcelSettingsAction = WuQtUtilities::createAction("S", // "Edit settings for this map and overlay", // this, // this, // SLOT(matrixParcelSettingsActionTriggered())); // if (settingsIconValid) { // m_matrixParcelSettingsAction->setIcon(settingsIcon); // } // QToolButton* settingsToolButton = new QToolButton(); // settingsToolButton->setDefaultAction(m_matrixParcelSettingsAction); // // // QLabel* fileLabel = new QLabel("Matrix File"); // m_matrixParcelFileSelectionComboBox = new CaretDataFileSelectionComboBox(this); // QObject::connect(m_matrixParcelFileSelectionComboBox, SIGNAL(fileSelected(CaretDataFile*)), // this, SLOT(matrixParcelFileSelected(CaretDataFile*))); // // QLabel* loadDimensionLabel = new QLabel("Load By"); // m_matrixParcelLoadByColumnRowComboBox = new EnumComboBoxTemplate(this); // m_matrixParcelLoadByColumnRowComboBox->setup(); // QObject::connect(m_matrixParcelLoadByColumnRowComboBox, SIGNAL(itemActivated()), // this, SLOT(matrixParcelFileLoadingComboBoxActivated())); // // // QLabel* yokeLabel = new QLabel("Yoke "); // m_matrixParcelYokingGroupComboBox = new EnumComboBoxTemplate(this); // m_matrixParcelYokingGroupComboBox->setup(); // QObject::connect(m_matrixParcelYokingGroupComboBox, SIGNAL(itemActivated()), // this, SLOT(matrixParcelYokingGroupEnumComboBoxActivated())); // // m_matrixParcelChartWidget = new QGroupBox("Matrix Loading"); // QGridLayout* matrixLayout = new QGridLayout(m_matrixParcelChartWidget); // // switch (orientation) { // case Qt::Horizontal: // { // WuQtUtilities::setLayoutSpacingAndMargins(matrixLayout, 2, 0); // matrixLayout->setColumnStretch(0, 0); // matrixLayout->setColumnStretch(1, 0); // matrixLayout->setColumnStretch(2, 0); // matrixLayout->setColumnStretch(3, 0); // matrixLayout->setColumnStretch(4, 100); // // matrixLayout->addWidget(loadDimensionLabel, // 0, 0, // Qt::AlignHCenter); // matrixLayout->addWidget(settingsLabel, // 0, 1, // 1, 2, // Qt::AlignHCenter); // matrixLayout->addWidget(yokeLabel, // 0, 3, // Qt::AlignHCenter); // matrixLayout->addWidget(fileLabel, // 0, 4, // Qt::AlignHCenter); // matrixLayout->addWidget(m_matrixParcelLoadByColumnRowComboBox->getWidget(), // 1, 0); // matrixLayout->addWidget(settingsToolButton, // 1, 1); // matrixLayout->addWidget(colorBarToolButton, // 1, 2); // matrixLayout->addWidget(m_matrixParcelYokingGroupComboBox->getWidget(), // 1, 3); // matrixLayout->addWidget(m_matrixParcelFileSelectionComboBox->getWidget(), // 1, 4); // } // break; // case Qt::Vertical: // { // WuQtUtilities::setLayoutSpacingAndMargins(matrixLayout, 2, 0); // matrixLayout->setColumnStretch(0, 0); // matrixLayout->setColumnStretch(1, 0); // matrixLayout->setColumnStretch(2, 0); // matrixLayout->setColumnStretch(3, 0); // matrixLayout->setColumnStretch(4, 100); // // matrixLayout->addWidget(loadDimensionLabel, // 0, 0, // Qt::AlignHCenter); // matrixLayout->addWidget(settingsLabel, // 0, 1, // 1, 2, // Qt::AlignHCenter); // matrixLayout->addWidget(yokeLabel, // 0, 3, // Qt::AlignHCenter); // matrixLayout->addWidget(m_matrixParcelLoadByColumnRowComboBox->getWidget(), // 1, 0); // matrixLayout->addWidget(settingsToolButton, // 1, 1); // matrixLayout->addWidget(colorBarToolButton, // 1, 2); // matrixLayout->addWidget(m_matrixParcelYokingGroupComboBox->getWidget(), // 1, 3); // matrixLayout->addWidget(fileLabel, // 2, 0, 1, 4, // Qt::AlignHCenter); // matrixLayout->addWidget(m_matrixParcelFileSelectionComboBox->getWidget(), // 3, 0, 1, 4); // } // break; // default: // CaretAssert(0); // break; // } // // m_parcelReorderingEnabledCheckBox = new QCheckBox(""); // QObject::connect(m_parcelReorderingEnabledCheckBox, SIGNAL(clicked(bool)), // this, SLOT(parcelLabelFileRemappingFileSelectorChanged())); // // m_parcelLabelFileRemappingFileSelector = new CaretMappableDataFileAndMapSelectorObject(DataFileTypeEnum::CONNECTIVITY_PARCEL_LABEL, // CaretMappableDataFileAndMapSelectorObject::OPTION_SHOW_MAP_INDEX_SPIN_BOX, // this); // QObject::connect(m_parcelLabelFileRemappingFileSelector, SIGNAL(selectionWasPerformed()), // this, SLOT(parcelLabelFileRemappingFileSelectorChanged())); // // QLabel* parcelCheckBoxLabel = new QLabel("On"); // QLabel* parcelFileLabel = new QLabel("Parcel Label File"); // QLabel* parcelFileMapLabel = new QLabel("Map"); // QLabel* parcelFileMapIndexLabel = new QLabel("Index"); // QWidget* mapFileComboBox = NULL; // QWidget* mapIndexSpinBox = NULL; // QWidget* mapNameComboBox = NULL; // m_parcelLabelFileRemappingFileSelector->getWidgetsForAddingToLayout(mapFileComboBox, // mapIndexSpinBox, // mapNameComboBox); // m_parcelRemappingGroupBox = new QGroupBox("Parcel Reordering"); // m_parcelRemappingGroupBox->setFlat(true); // m_parcelRemappingGroupBox->setAlignment(Qt::AlignHCenter); // QGridLayout* parcelMapFileLayout = new QGridLayout(m_parcelRemappingGroupBox); // switch (orientation) { // case Qt::Horizontal: // { // WuQtUtilities::setLayoutSpacingAndMargins(parcelMapFileLayout, 2, 0); // parcelMapFileLayout->setColumnStretch(0, 0); // parcelMapFileLayout->setColumnStretch(1, 100); // parcelMapFileLayout->setColumnStretch(2, 0); // parcelMapFileLayout->setColumnStretch(3, 100); // parcelMapFileLayout->addWidget(parcelCheckBoxLabel, 0, 0, Qt::AlignHCenter); // parcelMapFileLayout->addWidget(parcelFileLabel, 0, 1, Qt::AlignHCenter); // parcelMapFileLayout->addWidget(parcelFileMapLabel, 0, 2, 1, 2, Qt::AlignHCenter); // parcelMapFileLayout->addWidget(m_parcelReorderingEnabledCheckBox, 1,0); // parcelMapFileLayout->addWidget(mapFileComboBox, 1, 1); // parcelMapFileLayout->addWidget(mapIndexSpinBox, 1, 2); // parcelMapFileLayout->addWidget(mapNameComboBox, 1, 3); // } // break; // case Qt::Vertical: // { // WuQtUtilities::setLayoutSpacingAndMargins(parcelMapFileLayout, 2, 0); // parcelMapFileLayout->setColumnStretch(0, 0); // parcelMapFileLayout->setColumnStretch(1, 100); // parcelMapFileLayout->addWidget(parcelCheckBoxLabel, 0, 0, Qt::AlignHCenter); // parcelMapFileLayout->addWidget(parcelFileLabel, 0, 1, Qt::AlignHCenter); // parcelMapFileLayout->addWidget(m_parcelReorderingEnabledCheckBox, 1,0, Qt::AlignHCenter); // parcelMapFileLayout->addWidget(mapFileComboBox, 1, 1); // parcelMapFileLayout->addWidget(parcelFileMapIndexLabel, 2, 0, Qt::AlignHCenter); // parcelMapFileLayout->addWidget(parcelFileMapLabel, 2, 1, Qt::AlignHCenter); // parcelMapFileLayout->addWidget(mapIndexSpinBox, 3, 0); // parcelMapFileLayout->addWidget(mapNameComboBox, 3, 1); // } // break; // default: // CaretAssert(0); // } // // // QWidget* widget = new QWidget(this); // QVBoxLayout* layout = new QVBoxLayout(widget); // WuQtUtilities::setLayoutSpacingAndMargins(layout, 1, 0); // layout->addWidget(m_matrixParcelChartWidget); // layout->addWidget(m_parcelRemappingGroupBox); // //layout->addStretch(); // // /* // * TEMP TODO // * FINISH IMPLEMENTATION OF LOADING AND YOKING // */ // const bool hideLoadControls = false; // const bool hideYokeControls = false; // if (hideLoadControls) { // loadDimensionLabel->hide(); // m_matrixParcelLoadByColumnRowComboBox->getWidget()->hide(); // } // if (hideYokeControls) { // yokeLabel->hide(); // m_matrixParcelYokingGroupComboBox->getWidget()->hide(); // } // // EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_USER_INTERFACE_UPDATE); } /** * Destructor. */ ChartMatrixParcelSelectionViewController::~ChartMatrixParcelSelectionViewController() { EventManager::get()->removeAllEventsFromListener(this); } /** * Update the view controller. */ void ChartMatrixParcelSelectionViewController::updateSelectionViewController() { Brain* brain = GuiManager::get()->getBrain(); BrowserTabContent* browserTabContent = GuiManager::get()->getBrowserTabContentForBrowserWindow(m_browserWindowIndex, true); if (browserTabContent == NULL) { return; } const int32_t browserTabIndex = browserTabContent->getTabNumber(); ChartDataTypeEnum::Enum chartDataType = ChartDataTypeEnum::CHART_DATA_TYPE_INVALID; ModelChart* modelChart = brain->getChartModel(); if (modelChart != NULL) { chartDataType = modelChart->getSelectedChartDataType(browserTabIndex); } if (chartDataType == ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_LAYER) { updateMatrixParcelChartWidget(brain, modelChart, browserTabIndex); } } /** * Receive an event. * * @param event * An event for which this instance is listening. */ void ChartMatrixParcelSelectionViewController::receiveEvent(Event* event) { if (event->getEventType() == EventTypeEnum::EVENT_USER_INTERFACE_UPDATE) { EventUserInterfaceUpdate* uiEvent = dynamic_cast(event); CaretAssert(uiEvent); if (uiEvent->isUpdateForWindow(m_browserWindowIndex) || uiEvent->isToolBoxUpdate()) { this->updateSelectionViewController(); uiEvent->setEventProcessed(); } } } /** * Called when a matrix file is selected. * * @param caretDataFile * Caret data file that was selected. */ void ChartMatrixParcelSelectionViewController::matrixParcelFileSelected(CaretDataFile* /*caretDataFile*/) { updateSelectionViewController(); EventManager::get()->sendEvent(EventGraphicsUpdateOneWindow(m_browserWindowIndex).getPointer()); } /** * Gets called when matrix loading combo box is changed. */ void ChartMatrixParcelSelectionViewController::matrixParcelFileLoadingComboBoxActivated() { CaretMappableDataFile* caretMappableDataFile = NULL; ChartableMatrixInterface* chartableMatrixInterface = NULL; ChartMatrixDisplayProperties* chartMatrixDisplayProperties = NULL; ChartableMatrixParcelInterface* chartableMatrixParcelInterface = NULL; ChartableMatrixSeriesInterface* chartableMatrixSeriesInterface = NULL; int32_t browserTabIndex = -1; if ( ! getChartMatrixAndProperties(caretMappableDataFile, chartableMatrixInterface, chartableMatrixParcelInterface, chartableMatrixSeriesInterface, chartMatrixDisplayProperties, browserTabIndex)) { return; } CaretAssert(chartableMatrixParcelInterface); chartableMatrixParcelInterface->setMatrixLoadingDimension(m_matrixParcelLoadByColumnRowComboBox->getSelectedItem()); EventManager::get()->sendEvent(EventSurfaceColoringInvalidate().getPointer()); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } /** * Gets called when yoking gruup is changed. */ void ChartMatrixParcelSelectionViewController::matrixParcelYokingGroupEnumComboBoxActivated() { CaretMappableDataFile* caretMappableDataFile = NULL; ChartableMatrixInterface* chartableMatrixInterface = NULL; ChartMatrixDisplayProperties* chartMatrixDisplayProperties = NULL; ChartableMatrixParcelInterface* chartableMatrixParcelInterface = NULL; ChartableMatrixSeriesInterface* chartableMatrixSeriesInterface = NULL; int32_t browserTabIndex = -1; if ( ! getChartMatrixAndProperties(caretMappableDataFile, chartableMatrixInterface, chartableMatrixParcelInterface, chartableMatrixSeriesInterface, chartMatrixDisplayProperties, browserTabIndex)) { return; } CaretAssert(chartableMatrixParcelInterface); YokingGroupEnum::Enum newYokingGroup = m_matrixParcelYokingGroupComboBox->getSelectedItem(); int32_t selectedRowColumnIndex = -1; if (newYokingGroup != YokingGroupEnum::YOKING_GROUP_OFF) { const YokingGroupEnum::Enum previousYokingGroup = chartableMatrixParcelInterface->getYokingGroup(); EventChartMatrixParcelYokingValidation yokeEvent(chartableMatrixParcelInterface, newYokingGroup); EventManager::get()->sendEvent(yokeEvent.getPointer()); AString message; if ( ! yokeEvent.isValidateYokingCompatible(message, selectedRowColumnIndex)) { message = WuQtUtilities::createWordWrappedToolTipText(message); WuQMessageBox::YesNoCancelResult result = WuQMessageBox::warningYesNoCancel(m_matrixParcelYokingGroupComboBox->getWidget(), message, ""); switch (result) { case WuQMessageBox::RESULT_YES: break; case WuQMessageBox::RESULT_NO: newYokingGroup = YokingGroupEnum::YOKING_GROUP_OFF; selectedRowColumnIndex = -1; break; case WuQMessageBox::RESULT_CANCEL: newYokingGroup = previousYokingGroup; selectedRowColumnIndex = -1; break; } } } /* * Need to update combo box since user may have changed mind and * the combo box status needs to change */ m_matrixParcelYokingGroupComboBox->setSelectedItem(newYokingGroup); chartableMatrixParcelInterface->setYokingGroup(newYokingGroup); /* * If yoking changed update the file's selected row or column */ if (newYokingGroup != YokingGroupEnum::YOKING_GROUP_OFF) { if (selectedRowColumnIndex >= 0) { CiftiMappableConnectivityMatrixDataFile* matrixFile = dynamic_cast(chartableMatrixInterface); if (matrixFile != NULL) { switch (chartableMatrixParcelInterface->getMatrixLoadingDimension()) { case ChartMatrixLoadingDimensionEnum::CHART_MATRIX_LOADING_BY_COLUMN: matrixFile->loadDataForColumnIndex(selectedRowColumnIndex); break; case ChartMatrixLoadingDimensionEnum::CHART_MATRIX_LOADING_BY_ROW: matrixFile->loadDataForRowIndex(selectedRowColumnIndex); break; } } } } EventManager::get()->sendEvent(EventSurfaceColoringInvalidate().getPointer()); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } /** * Called when colorbar icon button is clicked. */ void ChartMatrixParcelSelectionViewController::matrixParcelColorBarActionTriggered(bool status) { CaretMappableDataFile* caretMappableDataFile = NULL; ChartableMatrixInterface* chartableMatrixInterface = NULL; ChartMatrixDisplayProperties* chartMatrixDisplayProperties = NULL; ChartableMatrixParcelInterface* chartableMatrixParcelInterface = NULL; ChartableMatrixSeriesInterface* chartableMatrixSeriesInterface = NULL; int32_t browserTabIndex = -1; if ( ! getChartMatrixAndProperties(caretMappableDataFile, chartableMatrixInterface, chartableMatrixParcelInterface, chartableMatrixSeriesInterface, chartMatrixDisplayProperties, browserTabIndex)) { return; } chartMatrixDisplayProperties->setColorBarDisplayed(status); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } /** * Called when settings icon button is clicked to display palette editor. */ void ChartMatrixParcelSelectionViewController::matrixParcelSettingsActionTriggered() { CaretMappableDataFile* caretMappableDataFile = NULL; ChartableMatrixInterface* chartableMatrixInterface = NULL; ChartMatrixDisplayProperties* chartMatrixDisplayProperties = NULL; ChartableMatrixParcelInterface* chartableMatrixParcelInterface = NULL; ChartableMatrixSeriesInterface* chartableMatrixSeriesInterface = NULL; int32_t browserTabIndex = -1; if ( ! getChartMatrixAndProperties(caretMappableDataFile, chartableMatrixInterface, chartableMatrixParcelInterface, chartableMatrixSeriesInterface, chartMatrixDisplayProperties, browserTabIndex)) { return; } const int32_t mapIndex = 0; EventPaletteColorMappingEditorDialogRequest dialogEvent(m_browserWindowIndex, caretMappableDataFile, mapIndex); EventManager::get()->sendEvent(dialogEvent.getPointer()); } /** * @param orientation * Orientation for the widget. * @return * The matrix chart widget. */ QGroupBox* ChartMatrixParcelSelectionViewController::createMatrixParcelChartWidget(const Qt::Orientation orientation) { /* * ColorBar Tool Button */ QIcon colorBarIcon; const bool colorBarIconValid = WuQtUtilities::loadIcon(":/LayersPanel/colorbar.png", colorBarIcon); m_matrixParcelColorBarAction = WuQtUtilities::createAction("CB", "Display color bar for this overlay", this, this, SLOT(matrixParcelColorBarActionTriggered(bool))); m_matrixParcelColorBarAction->setCheckable(true); if (colorBarIconValid) { m_matrixParcelColorBarAction->setIcon(colorBarIcon); } QToolButton* colorBarToolButton = new QToolButton(); colorBarToolButton->setDefaultAction(m_matrixParcelColorBarAction); /* * Settings Tool Button */ QLabel* settingsLabel = new QLabel("Settings"); QIcon settingsIcon; const bool settingsIconValid = WuQtUtilities::loadIcon(":/LayersPanel/wrench.png", settingsIcon); m_matrixParcelSettingsAction = WuQtUtilities::createAction("S", "Edit settings for this map and overlay", this, this, SLOT(matrixParcelSettingsActionTriggered())); if (settingsIconValid) { m_matrixParcelSettingsAction->setIcon(settingsIcon); } QToolButton* settingsToolButton = new QToolButton(); settingsToolButton->setDefaultAction(m_matrixParcelSettingsAction); QLabel* fileLabel = new QLabel("Matrix File"); m_matrixParcelFileSelectionComboBox = new CaretDataFileSelectionComboBox(this); QObject::connect(m_matrixParcelFileSelectionComboBox, SIGNAL(fileSelected(CaretDataFile*)), this, SLOT(matrixParcelFileSelected(CaretDataFile*))); QLabel* loadDimensionLabel = new QLabel("Load By"); m_matrixParcelLoadByColumnRowComboBox = new EnumComboBoxTemplate(this); m_matrixParcelLoadByColumnRowComboBox->setup(); QObject::connect(m_matrixParcelLoadByColumnRowComboBox, SIGNAL(itemActivated()), this, SLOT(matrixParcelFileLoadingComboBoxActivated())); QLabel* yokeLabel = new QLabel("Yoke "); m_matrixParcelYokingGroupComboBox = new EnumComboBoxTemplate(this); m_matrixParcelYokingGroupComboBox->setup(); QObject::connect(m_matrixParcelYokingGroupComboBox, SIGNAL(itemActivated()), this, SLOT(matrixParcelYokingGroupEnumComboBoxActivated())); QGroupBox* fileYokeGroupBox = new QGroupBox("Matrix Loading"); fileYokeGroupBox->setFlat(true); fileYokeGroupBox->setAlignment(Qt::AlignHCenter); QGridLayout* fileYokeLayout = new QGridLayout(fileYokeGroupBox); switch (orientation) { case Qt::Horizontal: { WuQtUtilities::setLayoutSpacingAndMargins(fileYokeLayout, 2, 0); fileYokeLayout->setColumnStretch(0, 0); fileYokeLayout->setColumnStretch(1, 0); fileYokeLayout->setColumnStretch(2, 0); fileYokeLayout->setColumnStretch(3, 0); fileYokeLayout->setColumnStretch(4, 100); fileYokeLayout->addWidget(loadDimensionLabel, 0, 0, Qt::AlignHCenter); fileYokeLayout->addWidget(settingsLabel, 0, 1, 1, 2, Qt::AlignHCenter); fileYokeLayout->addWidget(yokeLabel, 0, 3, Qt::AlignHCenter); fileYokeLayout->addWidget(fileLabel, 0, 4, Qt::AlignHCenter); fileYokeLayout->addWidget(m_matrixParcelLoadByColumnRowComboBox->getWidget(), 1, 0); fileYokeLayout->addWidget(settingsToolButton, 1, 1); fileYokeLayout->addWidget(colorBarToolButton, 1, 2); fileYokeLayout->addWidget(m_matrixParcelYokingGroupComboBox->getWidget(), 1, 3); fileYokeLayout->addWidget(m_matrixParcelFileSelectionComboBox->getWidget(), 1, 4); } break; case Qt::Vertical: { WuQtUtilities::setLayoutSpacingAndMargins(fileYokeLayout, 2, 0); fileYokeLayout->setColumnStretch(0, 0); fileYokeLayout->setColumnStretch(1, 0); fileYokeLayout->setColumnStretch(2, 0); fileYokeLayout->setColumnStretch(3, 0); fileYokeLayout->setColumnStretch(4, 100); fileYokeLayout->addWidget(loadDimensionLabel, 0, 0, Qt::AlignHCenter); fileYokeLayout->addWidget(settingsLabel, 0, 1, 1, 2, Qt::AlignHCenter); fileYokeLayout->addWidget(yokeLabel, 0, 3, Qt::AlignHCenter); fileYokeLayout->addWidget(m_matrixParcelLoadByColumnRowComboBox->getWidget(), 1, 0); fileYokeLayout->addWidget(settingsToolButton, 1, 1); fileYokeLayout->addWidget(colorBarToolButton, 1, 2); fileYokeLayout->addWidget(m_matrixParcelYokingGroupComboBox->getWidget(), 1, 3); fileYokeLayout->addWidget(fileLabel, 2, 0, 1, 4, Qt::AlignHCenter); fileYokeLayout->addWidget(m_matrixParcelFileSelectionComboBox->getWidget(), 3, 0, 1, 4); } break; default: CaretAssert(0); break; } return fileYokeGroupBox; } /** * @param orientation * Orientation for the widget. * @return * The parcel remapping widget. */ QGroupBox* ChartMatrixParcelSelectionViewController::createParcelRemappingWidget(const Qt::Orientation orientation) { m_parcelReorderingEnabledCheckBox = new QCheckBox(""); QObject::connect(m_parcelReorderingEnabledCheckBox, SIGNAL(clicked(bool)), this, SLOT(parcelLabelFileRemappingFileSelectorChanged())); m_parcelLabelFileRemappingFileSelector = new CaretMappableDataFileAndMapSelectorObject(DataFileTypeEnum::CONNECTIVITY_PARCEL_LABEL, CaretMappableDataFileAndMapSelectorObject::OPTION_SHOW_MAP_INDEX_SPIN_BOX, this); QObject::connect(m_parcelLabelFileRemappingFileSelector, SIGNAL(selectionWasPerformed()), this, SLOT(parcelLabelFileRemappingFileSelectorChanged())); QLabel* parcelCheckBoxLabel = new QLabel("On"); QLabel* parcelFileLabel = new QLabel("Parcel Label File"); QLabel* parcelFileMapLabel = new QLabel("Map"); QLabel* parcelFileMapIndexLabel = new QLabel("Index"); QWidget* mapFileComboBox = NULL; QWidget* mapIndexSpinBox = NULL; QWidget* mapNameComboBox = NULL; m_parcelLabelFileRemappingFileSelector->getWidgetsForAddingToLayout(mapFileComboBox, mapIndexSpinBox, mapNameComboBox); QGroupBox* groupBox = new QGroupBox("Parcel Reordering"); groupBox->setFlat(true); groupBox->setAlignment(Qt::AlignHCenter); QGridLayout* parcelMapFileLayout = new QGridLayout(groupBox); switch (orientation) { case Qt::Horizontal: { WuQtUtilities::setLayoutSpacingAndMargins(parcelMapFileLayout, 2, 0); parcelMapFileLayout->setColumnStretch(0, 0); parcelMapFileLayout->setColumnStretch(1, 100); parcelMapFileLayout->setColumnStretch(2, 0); parcelMapFileLayout->setColumnStretch(3, 100); parcelMapFileLayout->addWidget(parcelCheckBoxLabel, 0, 0, Qt::AlignHCenter); parcelMapFileLayout->addWidget(parcelFileLabel, 0, 1, Qt::AlignHCenter); parcelMapFileLayout->addWidget(parcelFileMapLabel, 0, 2, 1, 2, Qt::AlignHCenter); parcelMapFileLayout->addWidget(m_parcelReorderingEnabledCheckBox, 1,0); parcelMapFileLayout->addWidget(mapFileComboBox, 1, 1); parcelMapFileLayout->addWidget(mapIndexSpinBox, 1, 2); parcelMapFileLayout->addWidget(mapNameComboBox, 1, 3); } break; case Qt::Vertical: { WuQtUtilities::setLayoutSpacingAndMargins(parcelMapFileLayout, 2, 0); parcelMapFileLayout->setColumnStretch(0, 0); parcelMapFileLayout->setColumnStretch(1, 100); parcelMapFileLayout->addWidget(parcelCheckBoxLabel, 0, 0, Qt::AlignHCenter); parcelMapFileLayout->addWidget(parcelFileLabel, 0, 1, Qt::AlignHCenter); parcelMapFileLayout->addWidget(m_parcelReorderingEnabledCheckBox, 1,0, Qt::AlignHCenter); parcelMapFileLayout->addWidget(mapFileComboBox, 1, 1); parcelMapFileLayout->addWidget(parcelFileMapIndexLabel, 2, 0, Qt::AlignHCenter); parcelMapFileLayout->addWidget(parcelFileMapLabel, 2, 1, Qt::AlignHCenter); parcelMapFileLayout->addWidget(mapIndexSpinBox, 3, 0); parcelMapFileLayout->addWidget(mapNameComboBox, 3, 1); } break; default: CaretAssert(0); } return groupBox; } /** * Get the matrix related files and properties in this view controller. * * @param caretMappableDataFileOut * Output with selected caret mappable data file. * @param chartableMatrixInterfaceOut * Output with ChartableMatrixInterface implemented by the caret * mappable data file. * @param chartableMatrixParcelInterfaceOut * Output with ChartableMatrixParcelInterfaceOut implemented by the * caret mappable data file (may be NULL). * @param chartableMatrixSeriesInterfaceOut * Output with ChartableMatrixSeriesInterfaceOut implemented by the * caret mappable data file (may be NULL). * @param browserTabIndexOut * Index selected tab. * @param chartMatrixDisplayPropertiesOut * Matrix display properties from the ChartableMatrixInterface. * @return True if all output values are valid, else false. */ bool ChartMatrixParcelSelectionViewController::getChartMatrixAndProperties(CaretMappableDataFile* &caretMappableDataFileOut, ChartableMatrixInterface* & chartableMatrixInterfaceOut, ChartableMatrixParcelInterface* &chartableMatrixParcelInterfaceOut, ChartableMatrixSeriesInterface* &chartableMatrixSeriesInterfaceOut, ChartMatrixDisplayProperties* &chartMatrixDisplayPropertiesOut, int32_t& browserTabIndexOut) { caretMappableDataFileOut = NULL; chartableMatrixInterfaceOut = NULL; chartableMatrixParcelInterfaceOut = NULL; chartableMatrixSeriesInterfaceOut = NULL; chartMatrixDisplayPropertiesOut = NULL; browserTabIndexOut = -1; Brain* brain = GuiManager::get()->getBrain(); BrowserTabContent* browserTabContent = GuiManager::get()->getBrowserTabContentForBrowserWindow(m_browserWindowIndex, true); if (browserTabContent == NULL) { return false; } browserTabIndexOut = browserTabContent->getTabNumber(); if (browserTabIndexOut < 0) { return false; } ModelChart* modelChart = brain->getChartModel(); if (modelChart != NULL) { switch (modelChart->getSelectedChartDataType(browserTabIndexOut)) { case ChartDataTypeEnum::CHART_DATA_TYPE_INVALID: break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_LAYER: { CaretDataFileSelectionModel* parcelFileSelectionModel = modelChart->getChartableMatrixParcelFileSelectionModel(browserTabIndexOut); //m_matrixParcelFileSelectionComboBox->updateComboBox(parcelFileSelectionModel); CaretDataFile* caretParcelFile = parcelFileSelectionModel->getSelectedFile(); if (caretParcelFile != NULL) { chartableMatrixInterfaceOut = dynamic_cast(caretParcelFile); if (chartableMatrixInterfaceOut != NULL) { chartableMatrixParcelInterfaceOut = dynamic_cast(caretParcelFile); chartMatrixDisplayPropertiesOut = chartableMatrixInterfaceOut->getChartMatrixDisplayProperties(browserTabIndexOut); caretMappableDataFileOut = chartableMatrixInterfaceOut->getMatrixChartCaretMappableDataFile(); return true; } } } break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_SERIES: { CaretDataFileSelectionModel* seriesFileSelectionModel = modelChart->getChartableMatrixSeriesFileSelectionModel(browserTabIndexOut); CaretDataFile* caretSeriesFile = seriesFileSelectionModel->getSelectedFile(); if (caretSeriesFile != NULL) { chartableMatrixInterfaceOut = dynamic_cast(caretSeriesFile); if (chartableMatrixInterfaceOut != NULL) { chartableMatrixSeriesInterfaceOut = dynamic_cast(caretSeriesFile); chartMatrixDisplayPropertiesOut = chartableMatrixInterfaceOut->getChartMatrixDisplayProperties(browserTabIndexOut); caretMappableDataFileOut = chartableMatrixInterfaceOut->getMatrixChartCaretMappableDataFile(); return true; } } } break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_DATA_SERIES: break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_FREQUENCY_SERIES: break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_TIME_SERIES: break; } // CaretDataFileSelectionModel* parcelFileSelectionModel = modelChart->getChartableMatrixParcelFileSelectionModel(browserTabIndexOut); // //m_matrixParcelFileSelectionComboBox->updateComboBox(parcelFileSelectionModel); // CaretDataFile* caretParcelFile = parcelFileSelectionModel->getSelectedFile(); // // CaretDataFileSelectionModel* seriesFileSelectionModel = modelChart->getChartableMatrixSeriesFileSelectionModel(browserTabIndexOut); // CaretDataFile* caretSeriesFile = seriesFileSelectionModel->getSelectedFile(); // // if (caretParcelFile != NULL) { // chartableMatrixInterfaceOut = dynamic_cast(caretParcelFile); // if (chartableMatrixInterfaceOut != NULL) { // chartableMatrixParcelInterfaceOut = dynamic_cast(caretParcelFile); // chartableMatrixSeriesInterfaceOut = dynamic_cast(caretParcelFile); // chartMatrixDisplayPropertiesOut = chartableMatrixInterfaceOut->getChartMatrixDisplayProperties(browserTabIndexOut); // caretMappableDataFileOut = chartableMatrixInterfaceOut->getMatrixChartCaretMappableDataFile(); // return true; // } // } } return false; } /** * Gets called when a change is made in the parcel label file remapping * selections. */ void ChartMatrixParcelSelectionViewController::parcelLabelFileRemappingFileSelectorChanged() { CaretMappableDataFile* caretMappableDataFile = NULL; ChartableMatrixInterface* chartableMatrixInterface = NULL; ChartMatrixDisplayProperties* chartMatrixDisplayProperties = NULL; ChartableMatrixParcelInterface* chartableMatrixParcelInterface = NULL; ChartableMatrixSeriesInterface* chartableMatrixSeriesInterface = NULL; int32_t browserTabIndex = -1; if ( ! getChartMatrixAndProperties(caretMappableDataFile, chartableMatrixInterface, chartableMatrixParcelInterface, chartableMatrixSeriesInterface, chartMatrixDisplayProperties, browserTabIndex)) { return; } CaretAssert(chartableMatrixParcelInterface); const bool remappingEnabled = m_parcelReorderingEnabledCheckBox->isChecked(); CaretMappableDataFileAndMapSelectionModel* model = m_parcelLabelFileRemappingFileSelector->getModel(); CiftiParcelLabelFile* parcelLabelFile = model->getSelectedFileOfType(); int32_t parcelLabelFileMapIndex = model->getSelectedMapIndex(); chartableMatrixParcelInterface->setSelectedParcelLabelFileAndMapForReordering(parcelLabelFile, parcelLabelFileMapIndex, remappingEnabled); if (remappingEnabled) { AString errorMessage; if ( ! chartableMatrixParcelInterface->createParcelReordering(parcelLabelFile, parcelLabelFileMapIndex, errorMessage)) { WuQMessageBox::errorOk(this, errorMessage); } } } /** * Update the matrix chart widget. * * @param brain * The Brain. * @param modelChart * The Model for charts. * @param browserTabIndex * Index of the browser tab. */ void ChartMatrixParcelSelectionViewController::updateMatrixParcelChartWidget(Brain* /* brain */, ModelChart* modelChart, const int32_t /*browserTabIndex*/) { CaretMappableDataFile* caretMappableDataFile = NULL; ChartableMatrixInterface* chartableMatrixInterface = NULL; ChartMatrixDisplayProperties* chartMatrixDisplayProperties = NULL; ChartableMatrixParcelInterface* chartableMatrixParcelInterface = NULL; ChartableMatrixSeriesInterface* chartableMatrixSeriesInterface = NULL; int32_t browserTabIndex = -1; if ( ! getChartMatrixAndProperties(caretMappableDataFile, chartableMatrixInterface, chartableMatrixParcelInterface, chartableMatrixSeriesInterface, chartMatrixDisplayProperties, browserTabIndex)) { return; } if (chartableMatrixParcelInterface != NULL) { CaretDataFileSelectionModel* fileSelectionModel = modelChart->getChartableMatrixParcelFileSelectionModel(browserTabIndex); m_matrixParcelFileSelectionComboBox->updateComboBox(fileSelectionModel); const ChartMatrixLoadingDimensionEnum::Enum loadType = chartableMatrixParcelInterface->getMatrixLoadingDimension(); m_matrixParcelLoadByColumnRowComboBox->setSelectedItem(loadType); const YokingGroupEnum::Enum yokingGroup = chartableMatrixParcelInterface->getYokingGroup(); m_matrixParcelYokingGroupComboBox->setSelectedItem(yokingGroup); m_matrixParcelColorBarAction->blockSignals(true); m_matrixParcelColorBarAction->setChecked(chartMatrixDisplayProperties->isColorBarDisplayed()); m_matrixParcelColorBarAction->blockSignals(false); m_matrixParcelYokingGroupComboBox->getWidget()->setEnabled(chartableMatrixParcelInterface->isSupportsLoadingAttributes()); m_matrixParcelLoadByColumnRowComboBox->getWidget()->setEnabled(chartableMatrixParcelInterface->isSupportsLoadingAttributes()); /* * Update palette reordering. */ std::vector parcelLabelFiles; CiftiParcelLabelFile* parcelLabelFile = NULL; int32_t parcelLabelFileMapIndex = -1; bool remappingEnabled = false; chartableMatrixParcelInterface->getSelectedParcelLabelFileAndMapForReordering(parcelLabelFiles, parcelLabelFile, parcelLabelFileMapIndex, remappingEnabled); std::vector caretMapDataFiles; if ( ! parcelLabelFiles.empty()) { caretMapDataFiles.insert(caretMapDataFiles.end(), parcelLabelFiles.begin(), parcelLabelFiles.end()); } m_parcelReorderingEnabledCheckBox->setChecked(remappingEnabled); CaretMappableDataFileAndMapSelectionModel* model = m_parcelLabelFileRemappingFileSelector->getModel(); model->overrideAvailableDataFiles(caretMapDataFiles); model->setSelectedFile(parcelLabelFile); model->setSelectedMapIndex(parcelLabelFileMapIndex); m_parcelLabelFileRemappingFileSelector->updateFileAndMapSelector(model); m_matrixParcelColorBarAction->setEnabled(caretMappableDataFile->isMappedWithPalette()); m_matrixParcelSettingsAction->setEnabled(caretMappableDataFile->isMappedWithPalette()); m_parcelRemappingGroupBox->setVisible(true); } m_parcelRemappingGroupBox->setEnabled(chartableMatrixParcelInterface != NULL); } workbench-1.1.1/src/GuiQt/ChartMatrixParcelSelectionViewController.h000066400000000000000000000110361255417355300256220ustar00rootroot00000000000000#ifndef __CHART_MATRIX_PARCEL_SELECTION_VIEW_CONTROLLER_H__ #define __CHART_MATRIX_PARCEL_SELECTION_VIEW_CONTROLLER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "EventListenerInterface.h" class QCheckBox; class QGroupBox; namespace caret { class Brain; class CaretDataFile; class CaretDataFileSelectionComboBox; class CaretMappableDataFile; class CaretMappableDataFileAndMapSelectorObject; class ChartMatrixDisplayProperties; class ChartableMatrixInterface; class ChartableMatrixParcelInterface; class ChartableMatrixSeriesInterface; class EnumComboBoxTemplate; class ModelChart; class ChartMatrixParcelSelectionViewController : public QWidget, public EventListenerInterface { Q_OBJECT public: ChartMatrixParcelSelectionViewController(const Qt::Orientation orientation, const int32_t browserWindowIndex, QWidget* parent); virtual ~ChartMatrixParcelSelectionViewController(); private slots: void matrixParcelFileSelected(CaretDataFile* caretDataFile); void matrixParcelFileLoadingComboBoxActivated(); void matrixParcelYokingGroupEnumComboBoxActivated(); void matrixParcelColorBarActionTriggered(bool status); void matrixParcelSettingsActionTriggered(); void parcelLabelFileRemappingFileSelectorChanged(); private: ChartMatrixParcelSelectionViewController(const ChartMatrixParcelSelectionViewController&); ChartMatrixParcelSelectionViewController& operator=(const ChartMatrixParcelSelectionViewController&); public: // ADD_NEW_METHODS_HERE virtual void receiveEvent(Event* event); private: // ADD_NEW_MEMBERS_HERE QGroupBox* createMatrixParcelChartWidget(const Qt::Orientation orientation); QGroupBox* createParcelRemappingWidget(const Qt::Orientation orientation); void updateSelectionViewController(); void updateMatrixParcelChartWidget(Brain* brain, ModelChart* modelChart, const int32_t browserTabIndex); bool getChartMatrixAndProperties(CaretMappableDataFile* &caretMappableDataFileOut, ChartableMatrixInterface* & chartableMatrixInterfaceOut, ChartableMatrixParcelInterface* &chartableMatrixParcelInterfaceOut, ChartableMatrixSeriesInterface* &chartableMatrixSeriesInterfaceOut, ChartMatrixDisplayProperties* &chartMatrixDisplayPropertiesOut, int32_t& browserTabIndexOut); QGroupBox* m_matrixParcelChartWidget; const int32_t m_browserWindowIndex; CaretDataFileSelectionComboBox* m_matrixParcelFileSelectionComboBox; EnumComboBoxTemplate* m_matrixParcelLoadByColumnRowComboBox; EnumComboBoxTemplate* m_matrixParcelYokingGroupComboBox; QAction* m_matrixParcelColorBarAction; QAction* m_matrixParcelSettingsAction; QGroupBox* m_parcelRemappingGroupBox; QCheckBox* m_parcelReorderingEnabledCheckBox; CaretMappableDataFileAndMapSelectorObject* m_parcelLabelFileRemappingFileSelector; }; #ifdef __CHART_MATRIX_PARCEL_SELECTION_VIEW_CONTROLLER_DECLARE__ #endif // __CHART_MATRIX_PARCEL_SELECTION_VIEW_CONTROLLER_DECLARE__ } // namespace #endif //__CHART_MATRIX_PARCEL_SELECTION_VIEW_CONTROLLER_H__ workbench-1.1.1/src/GuiQt/ChartMatrixSeriesSelectionViewController.cxx000066400000000000000000000523461255417355300262320ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CHART_MATRIX_SERIES_SELECTION_VIEW_CONTROLLER_DECLARE__ #include "ChartMatrixSeriesSelectionViewController.h" #undef __CHART_MATRIX_SERIES_SELECTION_VIEW_CONTROLLER_DECLARE__ #include #include #include #include #include #include #include #include #include #include #include #include #include "Brain.h" #include "BrowserTabContent.h" #include "CaretAssert.h" #include "CaretDataFileSelectionComboBox.h" #include "CaretDataFileSelectionModel.h" #include "CaretLogger.h" #include "CaretMappableDataFile.h" #include "CaretMappableDataFileAndMapSelectionModel.h" #include "CaretMappableDataFileAndMapSelectorObject.h" #include "ChartableMatrixInterface.h" #include "ChartMatrixDisplayProperties.h" #include "ChartMatrixLoadingDimensionEnum.h" #include "ChartModel.h" #include "CiftiMappableConnectivityMatrixDataFile.h" #include "CiftiMappableDataFile.h" #include "CiftiParcelLabelFile.h" #include "CiftiScalarDataSeriesFile.h" #include "DeveloperFlagsEnum.h" #include "EnumComboBoxTemplate.h" #include "EventChartMatrixParcelYokingValidation.h" #include "EventManager.h" #include "EventGraphicsUpdateAllWindows.h" #include "EventGraphicsUpdateOneWindow.h" #include "EventPaletteColorMappingEditorDialogRequest.h" #include "EventSurfaceColoringInvalidate.h" #include "EventUserInterfaceUpdate.h" #include "GuiManager.h" #include "MapYokingGroupComboBox.h" #include "ModelChart.h" #include "WuQFactory.h" #include "WuQMessageBox.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::ChartMatrixSeriesSelectionViewController * \brief Handles selection of charts displayed in chart model. * \ingroup GuiQt */ /** * Constructor. */ ChartMatrixSeriesSelectionViewController::ChartMatrixSeriesSelectionViewController(const Qt::Orientation orientation, const int32_t browserWindowIndex, QWidget* parent) : QWidget(parent), m_browserWindowIndex(browserWindowIndex) { /* * ColorBar Tool Button */ QIcon colorBarIcon; const bool colorBarIconValid = WuQtUtilities::loadIcon(":/LayersPanel/colorbar.png", colorBarIcon); m_matrixSeriesColorBarAction = WuQtUtilities::createAction("CB", "Display color bar for this overlay", this, this, SLOT(matrixSeriesColorBarActionTriggered(bool))); m_matrixSeriesColorBarAction->setCheckable(true); if (colorBarIconValid) { m_matrixSeriesColorBarAction->setIcon(colorBarIcon); } QToolButton* colorBarToolButton = new QToolButton(); colorBarToolButton->setDefaultAction(m_matrixSeriesColorBarAction); /* * Settings Tool Button */ QLabel* settingsLabel = new QLabel("Settings"); QIcon settingsIcon; const bool settingsIconValid = WuQtUtilities::loadIcon(":/LayersPanel/wrench.png", settingsIcon); m_matrixSeriesSettingsAction = WuQtUtilities::createAction("S", "Edit settings for this map and overlay", this, this, SLOT(matrixSeriesSettingsActionTriggered())); if (settingsIconValid) { m_matrixSeriesSettingsAction->setIcon(settingsIcon); } QToolButton* settingsToolButton = new QToolButton(); settingsToolButton->setDefaultAction(m_matrixSeriesSettingsAction); QLabel* fileLabel = new QLabel("Matrix File"); m_matrixSeriesFileSelectionComboBox = new CaretDataFileSelectionComboBox(this); QObject::connect(m_matrixSeriesFileSelectionComboBox, SIGNAL(fileSelected(CaretDataFile*)), this, SLOT(matrixSeriesFileSelected(CaretDataFile*))); /* * Yoking Group */ QLabel* yokeLabel = new QLabel("Yoke "); m_matrixSeriesYokingComboBox = new MapYokingGroupComboBox(this); m_matrixSeriesYokingComboBox->getWidget()->setStatusTip("Synchronize enabled status and map indices)"); m_matrixSeriesYokingComboBox->getWidget()->setToolTip("Yoke to Overlay Mapped Files"); #ifdef CARET_OS_MACOSX m_matrixSeriesYokingComboBox->getWidget()->setFixedWidth(m_matrixSeriesYokingComboBox->getWidget()->sizeHint().width() - 20); #endif // CARET_OS_MACOSX QObject::connect(m_matrixSeriesYokingComboBox, SIGNAL(itemActivated()), this, SLOT(matrixSeriesYokingGroupActivated())); QWidget* gridWidget = new QWidget(); QGridLayout* gridLayout = new QGridLayout(gridWidget); switch (orientation) { case Qt::Horizontal: { WuQtUtilities::setLayoutSpacingAndMargins(gridLayout, 2, 0); gridLayout->setColumnStretch(0, 0); gridLayout->setColumnStretch(1, 0); gridLayout->setColumnStretch(2, 0); gridLayout->setColumnStretch(3, 100); gridLayout->addWidget(settingsLabel, 0, 0, 1, 2, Qt::AlignHCenter); gridLayout->addWidget(yokeLabel, 0, 2, Qt::AlignHCenter); gridLayout->addWidget(fileLabel, 0, 3, Qt::AlignHCenter); gridLayout->addWidget(settingsToolButton, 1, 0); gridLayout->addWidget(colorBarToolButton, 1, 1); gridLayout->addWidget(m_matrixSeriesYokingComboBox->getWidget(), 1, 2); gridLayout->addWidget(m_matrixSeriesFileSelectionComboBox->getWidget(), 1, 3); } break; case Qt::Vertical: { WuQtUtilities::setLayoutSpacingAndMargins(gridLayout, 2, 0); gridLayout->setColumnStretch(0, 0); gridLayout->setColumnStretch(1, 0); gridLayout->setColumnStretch(2, 0); gridLayout->setColumnStretch(3, 100); gridLayout->addWidget(settingsLabel, 0, 0, 1, 2, Qt::AlignHCenter); gridLayout->addWidget(yokeLabel, 0, 2, Qt::AlignHCenter); gridLayout->addWidget(settingsToolButton, 1, 0); gridLayout->addWidget(colorBarToolButton, 1, 1); gridLayout->addWidget(m_matrixSeriesYokingComboBox->getWidget(), 1, 2); gridLayout->addWidget(fileLabel, 2, 0, 1, 4, Qt::AlignHCenter); gridLayout->addWidget(m_matrixSeriesFileSelectionComboBox->getWidget(), 3, 0, 1, 4); } break; default: CaretAssert(0); break; } gridWidget->setSizePolicy(gridWidget->sizePolicy().horizontalPolicy(), QSizePolicy::Fixed); QVBoxLayout* layout = new QVBoxLayout(this); WuQtUtilities::setLayoutSpacingAndMargins(layout, 1, 0); layout->addWidget(gridWidget); layout->addStretch(); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_USER_INTERFACE_UPDATE); } /** * Destructor. */ ChartMatrixSeriesSelectionViewController::~ChartMatrixSeriesSelectionViewController() { EventManager::get()->removeAllEventsFromListener(this); } /** * Update the view controller. */ void ChartMatrixSeriesSelectionViewController::updateSelectionViewController() { Brain* brain = GuiManager::get()->getBrain(); BrowserTabContent* browserTabContent = GuiManager::get()->getBrowserTabContentForBrowserWindow(m_browserWindowIndex, true); if (browserTabContent == NULL) { return; } const int32_t browserTabIndex = browserTabContent->getTabNumber(); ChartDataTypeEnum::Enum chartDataType = ChartDataTypeEnum::CHART_DATA_TYPE_INVALID; ModelChart* modelChart = brain->getChartModel(); if (modelChart != NULL) { chartDataType = modelChart->getSelectedChartDataType(browserTabIndex); } if (chartDataType == ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_SERIES) { CaretMappableDataFile* caretMappableDataFile = NULL; ChartableMatrixInterface* chartableMatrixInterface = NULL; ChartMatrixDisplayProperties* chartMatrixDisplayProperties = NULL; ChartableMatrixParcelInterface* chartableMatrixParcelInterface = NULL; ChartableMatrixSeriesInterface* chartableMatrixSeriesInterface = NULL; int32_t browserTabIndex = -1; if ( ! getChartMatrixAndProperties(caretMappableDataFile, chartableMatrixInterface, chartableMatrixParcelInterface, chartableMatrixSeriesInterface, chartMatrixDisplayProperties, browserTabIndex)) { return; } if (chartableMatrixSeriesInterface != NULL) { CaretDataFileSelectionModel* fileSelectionModel = modelChart->getChartableMatrixSeriesFileSelectionModel(browserTabIndex); m_matrixSeriesFileSelectionComboBox->updateComboBox(fileSelectionModel); const MapYokingGroupEnum::Enum yokingGroup = chartableMatrixSeriesInterface->getMatrixRowColumnMapYokingGroup(browserTabIndex); m_matrixSeriesYokingComboBox->setMapYokingGroup(yokingGroup); m_matrixSeriesColorBarAction->blockSignals(true); m_matrixSeriesColorBarAction->setChecked(chartMatrixDisplayProperties->isColorBarDisplayed()); m_matrixSeriesColorBarAction->blockSignals(false); m_matrixSeriesColorBarAction->setEnabled(caretMappableDataFile->isMappedWithPalette()); m_matrixSeriesSettingsAction->setEnabled(caretMappableDataFile->isMappedWithPalette()); } } } /** * Receive an event. * * @param event * An event for which this instance is listening. */ void ChartMatrixSeriesSelectionViewController::receiveEvent(Event* event) { if (event->getEventType() == EventTypeEnum::EVENT_USER_INTERFACE_UPDATE) { EventUserInterfaceUpdate* uiEvent = dynamic_cast(event); CaretAssert(uiEvent); if (uiEvent->isUpdateForWindow(m_browserWindowIndex) || uiEvent->isToolBoxUpdate()) { this->updateSelectionViewController(); uiEvent->setEventProcessed(); } } } /** * Get the matrix related files and properties in this view controller. * * @param caretMappableDataFileOut * Output with selected caret mappable data file. * @param chartableMatrixInterfaceOut * Output with ChartableMatrixInterface implemented by the caret * mappable data file. * @param chartableMatrixParcelInterfaceOut * Output with ChartableMatrixParcelInterfaceOut implemented by the * caret mappable data file (may be NULL). * @param chartableMatrixSeriesInterfaceOut * Output with ChartableMatrixSeriesInterfaceOut implemented by the * caret mappable data file (may be NULL). * @param browserTabIndexOut * Index selected tab. * @param chartMatrixDisplayPropertiesOut * Matrix display properties from the ChartableMatrixInterface. * @return True if all output values are valid, else false. */ bool ChartMatrixSeriesSelectionViewController::getChartMatrixAndProperties(CaretMappableDataFile* &caretMappableDataFileOut, ChartableMatrixInterface* & chartableMatrixInterfaceOut, ChartableMatrixParcelInterface* &chartableMatrixParcelInterfaceOut, ChartableMatrixSeriesInterface* &chartableMatrixSeriesInterfaceOut, ChartMatrixDisplayProperties* &chartMatrixDisplayPropertiesOut, int32_t& browserTabIndexOut) { caretMappableDataFileOut = NULL; chartableMatrixInterfaceOut = NULL; chartableMatrixParcelInterfaceOut = NULL; chartableMatrixSeriesInterfaceOut = NULL; chartMatrixDisplayPropertiesOut = NULL; browserTabIndexOut = -1; Brain* brain = GuiManager::get()->getBrain(); BrowserTabContent* browserTabContent = GuiManager::get()->getBrowserTabContentForBrowserWindow(m_browserWindowIndex, true); if (browserTabContent == NULL) { return false; } browserTabIndexOut = browserTabContent->getTabNumber(); if (browserTabIndexOut < 0) { return false; } ModelChart* modelChart = brain->getChartModel(); if (modelChart != NULL) { switch (modelChart->getSelectedChartDataType(browserTabIndexOut)) { case ChartDataTypeEnum::CHART_DATA_TYPE_INVALID: break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_LAYER: { CaretDataFileSelectionModel* parcelFileSelectionModel = modelChart->getChartableMatrixParcelFileSelectionModel(browserTabIndexOut); //m_matrixParcelFileSelectionComboBox->updateComboBox(parcelFileSelectionModel); CaretDataFile* caretParcelFile = parcelFileSelectionModel->getSelectedFile(); if (caretParcelFile != NULL) { chartableMatrixInterfaceOut = dynamic_cast(caretParcelFile); if (chartableMatrixInterfaceOut != NULL) { chartableMatrixParcelInterfaceOut = dynamic_cast(caretParcelFile); chartMatrixDisplayPropertiesOut = chartableMatrixInterfaceOut->getChartMatrixDisplayProperties(browserTabIndexOut); caretMappableDataFileOut = chartableMatrixInterfaceOut->getMatrixChartCaretMappableDataFile(); return true; } } } break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_SERIES: { CaretDataFileSelectionModel* seriesFileSelectionModel = modelChart->getChartableMatrixSeriesFileSelectionModel(browserTabIndexOut); CaretDataFile* caretSeriesFile = seriesFileSelectionModel->getSelectedFile(); if (caretSeriesFile != NULL) { chartableMatrixInterfaceOut = dynamic_cast(caretSeriesFile); if (chartableMatrixInterfaceOut != NULL) { chartableMatrixSeriesInterfaceOut = dynamic_cast(caretSeriesFile); chartMatrixDisplayPropertiesOut = chartableMatrixInterfaceOut->getChartMatrixDisplayProperties(browserTabIndexOut); caretMappableDataFileOut = chartableMatrixInterfaceOut->getMatrixChartCaretMappableDataFile(); return true; } } } break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_DATA_SERIES: break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_FREQUENCY_SERIES: break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_TIME_SERIES: break; } } return false; } /** * Called when a matrix series file is selected. * * @param caretDataFile * Caret data file that was selected. */ void ChartMatrixSeriesSelectionViewController::matrixSeriesFileSelected(CaretDataFile* /*caretDataFile*/) { updateSelectionViewController(); EventManager::get()->sendEvent(EventGraphicsUpdateOneWindow(m_browserWindowIndex).getPointer()); } /** * Called when colorbar icon button is clicked for matrix series file. */ void ChartMatrixSeriesSelectionViewController::matrixSeriesColorBarActionTriggered(bool status) { CaretMappableDataFile* caretMappableDataFile = NULL; ChartableMatrixInterface* chartableMatrixInterface = NULL; ChartMatrixDisplayProperties* chartMatrixDisplayProperties = NULL; ChartableMatrixParcelInterface* chartableMatrixParcelInterface = NULL; ChartableMatrixSeriesInterface* chartableMatrixSeriesInterface = NULL; int32_t browserTabIndex = -1; if ( ! getChartMatrixAndProperties(caretMappableDataFile, chartableMatrixInterface, chartableMatrixParcelInterface, chartableMatrixSeriesInterface, chartMatrixDisplayProperties, browserTabIndex)) { return; } if (chartableMatrixSeriesInterface != NULL) { chartMatrixDisplayProperties->setColorBarDisplayed(status); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } } /** * Called when settings icon button is clicked to display palette editor * for matrix series file. */ void ChartMatrixSeriesSelectionViewController::matrixSeriesSettingsActionTriggered() { CaretMappableDataFile* caretMappableDataFile = NULL; ChartableMatrixInterface* chartableMatrixInterface = NULL; ChartMatrixDisplayProperties* chartMatrixDisplayProperties = NULL; ChartableMatrixParcelInterface* chartableMatrixParcelInterface = NULL; ChartableMatrixSeriesInterface* chartableMatrixSeriesInterface = NULL; int32_t browserTabIndex = -1; if ( ! getChartMatrixAndProperties(caretMappableDataFile, chartableMatrixInterface, chartableMatrixParcelInterface, chartableMatrixSeriesInterface, chartMatrixDisplayProperties, browserTabIndex)) { return; } if (chartableMatrixSeriesInterface != NULL) { const int32_t mapIndex = 0; EventPaletteColorMappingEditorDialogRequest dialogEvent(m_browserWindowIndex, caretMappableDataFile, mapIndex); EventManager::get()->sendEvent(dialogEvent.getPointer()); } } /** * Called when matrix series yoking group is changed. */ void ChartMatrixSeriesSelectionViewController::matrixSeriesYokingGroupActivated() { CaretMappableDataFile* caretMappableDataFile = NULL; ChartableMatrixInterface* chartableMatrixInterface = NULL; ChartMatrixDisplayProperties* chartMatrixDisplayProperties = NULL; ChartableMatrixParcelInterface* chartableMatrixParcelInterface = NULL; ChartableMatrixSeriesInterface* chartableMatrixSeriesInterface = NULL; int32_t browserTabIndex = -1; if ( ! getChartMatrixAndProperties(caretMappableDataFile, chartableMatrixInterface, chartableMatrixParcelInterface, chartableMatrixSeriesInterface, chartMatrixDisplayProperties, browserTabIndex)) { return; } m_matrixSeriesYokingComboBox->validateYokingChange(chartableMatrixSeriesInterface, browserTabIndex); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } workbench-1.1.1/src/GuiQt/ChartMatrixSeriesSelectionViewController.h000066400000000000000000000070551255417355300256540ustar00rootroot00000000000000#ifndef __CHART_MATRIX_SERIES_SELECTION_VIEW_CONTROLLER_H__ #define __CHART_MATRIX_SERIES_SELECTION_VIEW_CONTROLLER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "EventListenerInterface.h" namespace caret { class CaretDataFile; class CaretDataFileSelectionComboBox; class CaretMappableDataFile; class ChartMatrixDisplayProperties; class ChartableMatrixInterface; class ChartableMatrixParcelInterface; class ChartableMatrixSeriesInterface; class MapYokingGroupComboBox; class ChartMatrixSeriesSelectionViewController : public QWidget, public EventListenerInterface { Q_OBJECT public: ChartMatrixSeriesSelectionViewController(const Qt::Orientation orientation, const int32_t browserWindowIndex, QWidget* parent); virtual ~ChartMatrixSeriesSelectionViewController(); private slots: void matrixSeriesFileSelected(CaretDataFile* caretDataFile); void matrixSeriesColorBarActionTriggered(bool status); void matrixSeriesSettingsActionTriggered(); void matrixSeriesYokingGroupActivated(); private: ChartMatrixSeriesSelectionViewController(const ChartMatrixSeriesSelectionViewController&); ChartMatrixSeriesSelectionViewController& operator=(const ChartMatrixSeriesSelectionViewController&); public: // ADD_NEW_METHODS_HERE virtual void receiveEvent(Event* event); private: // ADD_NEW_MEMBERS_HERE void updateSelectionViewController(); bool getChartMatrixAndProperties(CaretMappableDataFile* &caretMappableDataFileOut, ChartableMatrixInterface* & chartableMatrixInterfaceOut, ChartableMatrixParcelInterface* &chartableMatrixParcelInterfaceOut, ChartableMatrixSeriesInterface* &chartableMatrixSeriesInterfaceOut, ChartMatrixDisplayProperties* &chartMatrixDisplayPropertiesOut, int32_t& browserTabIndexOut); const int32_t m_browserWindowIndex; QAction* m_matrixSeriesColorBarAction; QAction* m_matrixSeriesSettingsAction; CaretDataFileSelectionComboBox* m_matrixSeriesFileSelectionComboBox; MapYokingGroupComboBox* m_matrixSeriesYokingComboBox; }; #ifdef __CHART_MATRIX_SERIES_SELECTION_VIEW_CONTROLLER_DECLARE__ #endif // __CHART_MATRIX_SERIES_SELECTION_VIEW_CONTROLLER_DECLARE__ } // namespace #endif //__CHART_MATRIX_SERIES_SELECTION_VIEW_CONTROLLER_H__ workbench-1.1.1/src/GuiQt/ChartSelectionViewController.cxx000066400000000000000000000140221255417355300236570ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CHART_SELECTION_VIEW_CONTROLLER_DECLARE__ #include "ChartSelectionViewController.h" #undef __CHART_SELECTION_VIEW_CONTROLLER_DECLARE__ #include #include #include #include "Brain.h" #include "BrowserTabContent.h" #include "CaretAssert.h" #include "ChartLinesSelectionViewController.h" #include "ChartMatrixParcelSelectionViewController.h" #include "ChartMatrixSeriesSelectionViewController.h" #include "EventManager.h" #include "EventUserInterfaceUpdate.h" #include "GuiManager.h" #include "ModelChart.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::ChartSelectionViewController * \brief Handles selection of charts displayed in chart model. * \ingroup GuiQt */ /** * Constructor. */ ChartSelectionViewController::ChartSelectionViewController(const Qt::Orientation orientation, const int32_t browserWindowIndex, QWidget* parent) : QWidget(parent), m_browserWindowIndex(browserWindowIndex) { m_mode = MODE_INVALID; m_brainordinateChartWidget = new ChartLinesSelectionViewController(orientation, browserWindowIndex, parent); m_matrixParcelChartWidget = new ChartMatrixParcelSelectionViewController(orientation, browserWindowIndex, parent); m_matrixSeriesChartWidget = new ChartMatrixSeriesSelectionViewController(orientation, browserWindowIndex, parent); m_stackedWidget = new QStackedWidget(); m_stackedWidget->addWidget(m_brainordinateChartWidget); m_stackedWidget->addWidget(m_matrixParcelChartWidget); m_stackedWidget->addWidget(m_matrixSeriesChartWidget); QVBoxLayout* layout = new QVBoxLayout(this); WuQtUtilities::setLayoutSpacingAndMargins(layout, 0, 0); layout->addWidget(m_stackedWidget); layout->addStretch(); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_USER_INTERFACE_UPDATE); } /** * Destructor. */ ChartSelectionViewController::~ChartSelectionViewController() { EventManager::get()->removeAllEventsFromListener(this); } /** * Update the view controller. */ void ChartSelectionViewController::updateSelectionViewController() { m_mode = MODE_INVALID; Brain* brain = GuiManager::get()->getBrain(); BrowserTabContent* browserTabContent = GuiManager::get()->getBrowserTabContentForBrowserWindow(m_browserWindowIndex, true); if (browserTabContent == NULL) { return; } const int32_t browserTabIndex = browserTabContent->getTabNumber(); ChartDataTypeEnum::Enum chartDataType = ChartDataTypeEnum::CHART_DATA_TYPE_INVALID; ModelChart* modelChart = brain->getChartModel(); if (modelChart != NULL) { chartDataType = modelChart->getSelectedChartDataType(browserTabIndex); } switch (chartDataType) { case ChartDataTypeEnum::CHART_DATA_TYPE_INVALID: break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_LAYER: m_mode = MODE_MATRIX_LAYER; break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_SERIES: m_mode = MODE_MATRIX_SERIES; break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_DATA_SERIES: m_mode = MODE_BRAINORDINATE; break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_FREQUENCY_SERIES: m_mode = MODE_BRAINORDINATE; break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_TIME_SERIES: m_mode = MODE_BRAINORDINATE; break; } switch (m_mode) { case MODE_INVALID: break; case MODE_BRAINORDINATE: m_stackedWidget->setCurrentWidget(m_brainordinateChartWidget); //m_brainordinateChartWidget->updateSelectionViewController(); break; case MODE_MATRIX_LAYER: m_stackedWidget->setCurrentWidget(m_matrixParcelChartWidget); //m_matrixParcelChartWidget->updateSelectionViewController(); break; case MODE_MATRIX_SERIES: m_stackedWidget->setCurrentWidget(m_matrixSeriesChartWidget); //m_matrixSeriesChartWidget->updateSelectionViewController(); break; } } /** * Receive an event. * * @param event * An event for which this instance is listening. */ void ChartSelectionViewController::receiveEvent(Event* event) { if (event->getEventType() == EventTypeEnum::EVENT_USER_INTERFACE_UPDATE) { EventUserInterfaceUpdate* uiEvent = dynamic_cast(event); CaretAssert(uiEvent); if (uiEvent->isUpdateForWindow(m_browserWindowIndex) || uiEvent->isToolBoxUpdate()) { this->updateSelectionViewController(); uiEvent->setEventProcessed(); } } } workbench-1.1.1/src/GuiQt/ChartSelectionViewController.h000066400000000000000000000052141255417355300233070ustar00rootroot00000000000000#ifndef __CHART_SELECTION_VIEW_CONTROLLER_H__ #define __CHART_SELECTION_VIEW_CONTROLLER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "EventListenerInterface.h" class QStackedWidget; namespace caret { class ChartLinesSelectionViewController; class ChartMatrixParcelSelectionViewController; class ChartMatrixSeriesSelectionViewController; class ChartSelectionViewController : public QWidget, public EventListenerInterface { Q_OBJECT public: ChartSelectionViewController(const Qt::Orientation orientation, const int32_t browserWindowIndex, QWidget* parent); virtual ~ChartSelectionViewController(); private: ChartSelectionViewController(const ChartSelectionViewController&); ChartSelectionViewController& operator=(const ChartSelectionViewController&); public: // ADD_NEW_METHODS_HERE virtual void receiveEvent(Event* event); private: enum Mode { MODE_INVALID, MODE_BRAINORDINATE, MODE_MATRIX_LAYER, MODE_MATRIX_SERIES }; // ADD_NEW_MEMBERS_HERE void updateSelectionViewController(); QStackedWidget* m_stackedWidget; ChartLinesSelectionViewController* m_brainordinateChartWidget; ChartMatrixParcelSelectionViewController* m_matrixParcelChartWidget; ChartMatrixSeriesSelectionViewController* m_matrixSeriesChartWidget; Mode m_mode; const int32_t m_browserWindowIndex; }; #ifdef __CHART_SELECTION_VIEW_CONTROLLER_DECLARE__ #endif // __CHART_SELECTION_VIEW_CONTROLLER_DECLARE__ } // namespace #endif //__CHART_SELECTION_VIEW_CONTROLLER_H__ workbench-1.1.1/src/GuiQt/ChartToolBoxViewController.cxx000066400000000000000000000221011255417355300233150ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CHART_TOOL_BOX_VIEW_CONTROLLER_DECLARE__ #include "ChartToolBoxViewController.h" #undef __CHART_TOOL_BOX_VIEW_CONTROLLER_DECLARE__ #include #include #include #include "Brain.h" #include "BrowserTabContent.h" #include "CaretAssert.h" #include "ChartHistoryViewController.h" #include "ChartModel.h" #include "ChartModelDataSeries.h" #include "ChartModelFrequencySeries.h" #include "ChartModelTimeSeries.h" #include "ChartSelectionViewController.h" #include "EventManager.h" #include "EventUserInterfaceUpdate.h" #include "GuiManager.h" #include "ModelChart.h" #include "SceneClass.h" #include "SceneClassAssistant.h" using namespace caret; /** * \class caret::ChartToolBoxViewController * \brief View controller for the "Charting" tab of the overlay toolbox * \ingroup GuiQt */ /** * Constructor. */ ChartToolBoxViewController::ChartToolBoxViewController(const Qt::Orientation orientation, const int32_t browserWindowIndex, QWidget* parent) : QWidget(parent), EventListenerInterface(), m_browserWindowIndex(browserWindowIndex) { /* * Data series widgets */ m_chartSelectionViewController = new ChartSelectionViewController(orientation, browserWindowIndex, NULL); m_chartHistoryViewController = new ChartHistoryViewController(orientation, browserWindowIndex, NULL); m_tabWidget = new QTabWidget(); m_tabWidget->addTab(m_chartSelectionViewController, "Loading"); m_tabWidget->addTab(m_chartHistoryViewController, "History"); QVBoxLayout* layout = new QVBoxLayout(this); layout->addWidget(m_tabWidget); layout->addStretch(); m_sceneAssistant = new SceneClassAssistant(); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_USER_INTERFACE_UPDATE); } /** * Destructor. */ ChartToolBoxViewController::~ChartToolBoxViewController() { EventManager::get()->removeAllEventsFromListener(this); delete m_sceneAssistant; } /** * Receive an event. * * @param event * An event for which this instance is listening. */ void ChartToolBoxViewController::receiveEvent(Event* event) { if (event->getEventType() == EventTypeEnum::EVENT_USER_INTERFACE_UPDATE) { EventUserInterfaceUpdate* uiEvent = dynamic_cast(event); CaretAssert(uiEvent); if (uiEvent->isUpdateForWindow(m_browserWindowIndex) || uiEvent->isToolBoxUpdate()) { uiEvent->setEventProcessed(); ChartModel* chartModel = getSelectedChartModel(); bool historyWidgetValid = false; if (chartModel != NULL) { switch (chartModel->getChartDataType()) { case ChartDataTypeEnum::CHART_DATA_TYPE_INVALID: break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_LAYER: break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_SERIES: break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_DATA_SERIES: historyWidgetValid = true; break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_FREQUENCY_SERIES: historyWidgetValid = true; break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_TIME_SERIES: historyWidgetValid = true; break; } } const int32_t historyTabIndex = m_tabWidget->indexOf(m_chartHistoryViewController); if (historyTabIndex >= 0) { m_tabWidget->setTabEnabled(historyTabIndex, historyWidgetValid); } if ( ! historyWidgetValid) { m_tabWidget->setCurrentWidget(m_chartSelectionViewController); } } } } /** * @return Chart model selected in the selected tab (NULL if not valid) */ ChartModel* ChartToolBoxViewController::getSelectedChartModel() { Brain* brain = GuiManager::get()->getBrain(); BrowserTabContent* browserTabContent = GuiManager::get()->getBrowserTabContentForBrowserWindow(m_browserWindowIndex, true); if (browserTabContent == NULL) { return NULL; } ChartModel* chartModel = NULL; ModelChart* modelChart = brain->getChartModel(); if (modelChart != NULL) { const int32_t browserTabIndex = browserTabContent->getTabNumber(); switch (modelChart->getSelectedChartDataType(browserTabIndex)) { case ChartDataTypeEnum::CHART_DATA_TYPE_INVALID: break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_LAYER: break; case ChartDataTypeEnum::CHART_DATA_TYPE_MATRIX_SERIES: break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_DATA_SERIES: chartModel = modelChart->getSelectedDataSeriesChartModel(browserTabIndex); break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_FREQUENCY_SERIES: chartModel = modelChart->getSelectedFrequencySeriesChartModel(browserTabIndex); break; case ChartDataTypeEnum::CHART_DATA_TYPE_LINE_TIME_SERIES: chartModel = modelChart->getSelectedTimeSeriesChartModel(browserTabIndex); break; } } return chartModel; } /** * Save information specific to this type of model to the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @param instanceName * Name of instance in the scene. */ SceneClass* ChartToolBoxViewController::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "ChartToolBoxViewController", 1); AString tabName; const int tabIndex = m_tabWidget->currentIndex(); if ((tabIndex >= 0) && tabIndex < m_tabWidget->count()) { tabName = m_tabWidget->tabText(tabIndex); } sceneClass->addString("selectedChartTabName", tabName); m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); // Uncomment if sub-classes must save to scene //saveSubClassDataToScene(sceneAttributes, // sceneClass); return sceneClass; } /** * Restore information specific to the type of model from the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass from which model specific information is obtained. */ void ChartToolBoxViewController::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } const AString tabName = sceneClass->getStringValue("selectedChartTabName", ""); for (int32_t i = 0; i < m_tabWidget->count(); i++) { if (m_tabWidget->tabText(i) == tabName) { m_tabWidget->setCurrentIndex(i); break; } } m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); //Uncomment if sub-classes must restore from scene //restoreSubClassDataFromScene(sceneAttributes, // sceneClass); } workbench-1.1.1/src/GuiQt/ChartToolBoxViewController.h000066400000000000000000000065121255417355300227520ustar00rootroot00000000000000#ifndef __CHART_TOOL_BOX_VIEW_CONTROLLER_H__ #define __CHART_TOOL_BOX_VIEW_CONTROLLER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "EventListenerInterface.h" #include "SceneableInterface.h" class QStackedWidget; class QTabWidget; namespace caret { class ChartHistoryViewController; class ChartModel; class ChartSelectionViewController; class SceneClassAssistant; class ChartToolBoxViewController : public QWidget, public EventListenerInterface, public SceneableInterface { Q_OBJECT public: ChartToolBoxViewController(const Qt::Orientation orientation, const int32_t browserWindowIndex, QWidget* parent); virtual ~ChartToolBoxViewController(); // ADD_NEW_METHODS_HERE virtual void receiveEvent(Event* event); virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); // If there will be sub-classes of this class that need to save // and restore data from scenes, these pure virtual methods can // be uncommented to force their implemetation by sub-classes. // protected: // virtual void saveSubClassDataToScene(const SceneAttributes* sceneAttributes, // SceneClass* sceneClass) = 0; // // virtual void restoreSubClassDataFromScene(const SceneAttributes* sceneAttributes, // const SceneClass* sceneClass) = 0; private: ChartToolBoxViewController(const ChartToolBoxViewController&); ChartToolBoxViewController& operator=(const ChartToolBoxViewController&); ChartModel* getSelectedChartModel(); SceneClassAssistant* m_sceneAssistant; const int32_t m_browserWindowIndex; QTabWidget* m_tabWidget; ChartSelectionViewController* m_chartSelectionViewController; ChartHistoryViewController* m_chartHistoryViewController; // ADD_NEW_MEMBERS_HERE }; #ifdef __CHART_TOOL_BOX_VIEW_CONTROLLER_DECLARE__ // #endif // __CHART_TOOL_BOX_VIEW_CONTROLLER_DECLARE__ } // namespace #endif //__CHART_TOOL_BOX_VIEW_CONTROLLER_H__ workbench-1.1.1/src/GuiQt/CiftiConnectivityMatrixViewController.cxx000066400000000000000000000465241255417355300256060ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __CIFTI_CONNECTIVITY_MATRIX_VIEW_CONTROLLER_DECLARE__ #include "CiftiConnectivityMatrixViewController.h" #undef __CIFTI_CONNECTIVITY_MATRIX_VIEW_CONTROLLER_DECLARE__ #include #include #include #include #include #include #include #include #include "Brain.h" #include "CiftiBrainordinateScalarFile.h" #include "CiftiFiberOrientationFile.h" #include "CiftiFiberTrajectoryFile.h" #include "CiftiMappableConnectivityMatrixDataFile.h" #include "CursorDisplayScoped.h" #include "EventDataFileAdd.h" #include "EventManager.h" #include "EventGraphicsUpdateAllWindows.h" #include "EventSurfaceColoringInvalidate.h" #include "EventUserInterfaceUpdate.h" #include "FiberTrajectoryMapProperties.h" #include "GuiManager.h" #include "WuQMessageBox.h" #include "WuQtUtilities.h" using namespace caret; static const char* FILE_POINTER_PROPERTY_NAME = "filePointer"; /** * \class caret::CiftiConnectivityMatrixViewController * \brief View-Controller connectivity files * \ingroup GuiQt */ /** * Constructor. */ CiftiConnectivityMatrixViewController::CiftiConnectivityMatrixViewController(const Qt::Orientation /*orientation*/, QWidget* parent) : QWidget(parent) { m_gridLayout = new QGridLayout(); WuQtUtilities::setLayoutSpacingAndMargins(m_gridLayout, 2, 2); m_gridLayout->setColumnStretch(COLUMN_ENABLE_CHECKBOX, 0); m_gridLayout->setColumnStretch(COLUMN_COPY_BUTTON, 0); m_gridLayout->setColumnStretch(COLUMN_NAME_LINE_EDIT, 100); m_gridLayout->setColumnStretch(COLUMN_ORIENTATION_FILE_COMBO_BOX, 100); const int titleRow = m_gridLayout->rowCount(); m_gridLayout->addWidget(new QLabel("On"), titleRow, COLUMN_ENABLE_CHECKBOX); m_gridLayout->addWidget(new QLabel("Copy"), titleRow, COLUMN_COPY_BUTTON); m_gridLayout->addWidget(new QLabel("Connectivity File"), titleRow, COLUMN_NAME_LINE_EDIT); m_gridLayout->addWidget(new QLabel("Fiber Orientation File"), titleRow, COLUMN_ORIENTATION_FILE_COMBO_BOX); m_signalMapperFileEnableCheckBox = new QSignalMapper(this); QObject::connect(m_signalMapperFileEnableCheckBox, SIGNAL(mapped(int)), this, SLOT(enabledCheckBoxClicked(int))); m_signalMapperFileCopyToolButton = new QSignalMapper(this); QObject::connect(m_signalMapperFileCopyToolButton, SIGNAL(mapped(int)), this, SLOT(copyToolButtonClicked(int))); m_signalMapperFiberOrientationFileComboBox = new QSignalMapper(this); QObject::connect(m_signalMapperFiberOrientationFileComboBox, SIGNAL(mapped(int)), this, SLOT(fiberOrientationFileComboBoxActivated(int))); QVBoxLayout* layout = new QVBoxLayout(this); layout->addLayout(m_gridLayout); layout->addStretch(); s_allCiftiConnectivityMatrixViewControllers.insert(this); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_USER_INTERFACE_UPDATE); } /** * Destructor. */ CiftiConnectivityMatrixViewController::~CiftiConnectivityMatrixViewController() { s_allCiftiConnectivityMatrixViewControllers.erase(this); EventManager::get()->removeAllEventsFromListener(this); } /** * Update this view controller. */ void CiftiConnectivityMatrixViewController::updateViewController() { Brain* brain = GuiManager::get()->getBrain(); std::vector files; std::vector trajFiles; brain->getConnectivityFiberTrajectoryFiles(trajFiles); for (std::vector::iterator trajIter = trajFiles.begin(); trajIter != trajFiles.end(); trajIter++) { files.push_back(*trajIter); } std::vector matrixFiles; brain->getAllCiftiConnectivityMatrixFiles(matrixFiles); for (std::vector::iterator matrixIter = matrixFiles.begin(); matrixIter != matrixFiles.end(); matrixIter++) { files.push_back(*matrixIter); } const int32_t numFiles = static_cast(files.size()); for (int32_t i = 0; i < numFiles; i++) { QCheckBox* checkBox = NULL; QLineEdit* lineEdit = NULL; QToolButton* copyToolButton = NULL; QComboBox* comboBox = NULL; if (i < static_cast(m_fileEnableCheckBoxes.size())) { checkBox = m_fileEnableCheckBoxes[i]; lineEdit = m_fileNameLineEdits[i]; copyToolButton = m_fileCopyToolButtons[i]; comboBox = m_fiberOrientationFileComboBoxes[i]; } else { checkBox = new QCheckBox(""); m_fileEnableCheckBoxes.push_back(checkBox); lineEdit = new QLineEdit(); lineEdit->setReadOnly(true); m_fileNameLineEdits.push_back(lineEdit); copyToolButton = new QToolButton(); copyToolButton->setText("Copy"); copyToolButton->setToolTip("Copy loaded row data to a new CIFTI Scalar File"); m_fileCopyToolButtons.push_back(copyToolButton); comboBox = new QComboBox(); m_fiberOrientationFileComboBoxes.push_back(comboBox); QObject::connect(copyToolButton, SIGNAL(clicked()), m_signalMapperFileCopyToolButton, SLOT(map())); m_signalMapperFileCopyToolButton->setMapping(copyToolButton, i); QObject::connect(checkBox, SIGNAL(clicked(bool)), m_signalMapperFileEnableCheckBox, SLOT(map())); m_signalMapperFileEnableCheckBox->setMapping(checkBox, i); QObject::connect(comboBox, SIGNAL(activated(int)), m_signalMapperFiberOrientationFileComboBox, SLOT(map())); m_signalMapperFiberOrientationFileComboBox->setMapping(comboBox, i); const int row = m_gridLayout->rowCount(); m_gridLayout->addWidget(checkBox, row, COLUMN_ENABLE_CHECKBOX); m_gridLayout->addWidget(copyToolButton, row, COLUMN_COPY_BUTTON); m_gridLayout->addWidget(lineEdit, row, COLUMN_NAME_LINE_EDIT); m_gridLayout->addWidget(comboBox, row, COLUMN_ORIENTATION_FILE_COMBO_BOX); } const CiftiMappableConnectivityMatrixDataFile* matrixFile = dynamic_cast(files[i]); const CiftiFiberTrajectoryFile* trajFile = dynamic_cast(files[i]); bool checkStatus = false; if (matrixFile != NULL) { checkStatus = matrixFile->isMapDataLoadingEnabled(0); } else if (trajFile != NULL) { checkStatus = trajFile->isDataLoadingEnabled(); } else { CaretAssertMessage(0, "Has a new file type been added?"); } checkBox->setChecked(checkStatus); checkBox->setProperty(FILE_POINTER_PROPERTY_NAME, qVariantFromValue((void*)files[i])); lineEdit->setText(files[i]->getFileName()); } const int32_t numItems = static_cast(m_fileEnableCheckBoxes.size()); for (int32_t i = 0; i < numItems; i++) { bool showRow = false; bool showOrientationComboBox = false; if (i < numFiles) { showRow = true; if (dynamic_cast(files[i]) != NULL) { showOrientationComboBox = true; } } m_fileEnableCheckBoxes[i]->setVisible(showRow); m_fileCopyToolButtons[i]->setVisible(showRow); m_fileNameLineEdits[i]->setVisible(showRow); m_fiberOrientationFileComboBoxes[i]->setVisible(showOrientationComboBox); m_fiberOrientationFileComboBoxes[i]->setEnabled(showOrientationComboBox); } updateFiberOrientationComboBoxes(); } /** * Update the fiber orientation combo boxes. */ void CiftiConnectivityMatrixViewController::updateFiberOrientationComboBoxes() { std::vector orientationFiles; GuiManager::get()->getBrain()->getConnectivityFiberOrientationFiles(orientationFiles); const int32_t numOrientationFiles = static_cast(orientationFiles.size()); const int32_t numItems = static_cast(m_fiberOrientationFileComboBoxes.size()); for (int32_t i = 0; i < numItems; i++) { QComboBox* comboBox = m_fiberOrientationFileComboBoxes[i]; CiftiMappableConnectivityMatrixDataFile* matrixFile = NULL; CiftiFiberTrajectoryFile* trajFile = NULL; if (comboBox->isEnabled()) { getFileAtIndex(i, matrixFile, trajFile); } if (trajFile != NULL) { int32_t selectedIndex = 0; CiftiFiberOrientationFile* selectedOrientationFile = trajFile->getMatchingFiberOrientationFile(); comboBox->clear(); for (int32_t iOrient = 0; iOrient < numOrientationFiles; iOrient++) { CiftiFiberOrientationFile* orientFile = orientationFiles[iOrient]; if (trajFile->isFiberOrientationFileCombatible(orientFile)) { if (orientFile == selectedOrientationFile) { selectedIndex = iOrient; } comboBox->addItem(orientFile->getFileNameNoPath(), qVariantFromValue((void*)orientFile)); } } if ((selectedIndex >= 0) && (selectedIndex < comboBox->count())) { comboBox->setCurrentIndex(selectedIndex); } } else { comboBox->clear(); } // const bool showComboBox = (trajFile != NULL); // m_fiberOrientationFileComboBoxes[i]->setVisible(showComboBox); // m_fiberOrientationFileComboBoxes[i]->setEnabled(showComboBox); // std::cout << "Show Orientation File Combo Box: " // << i // << ": " // << qPrintable(AString::fromBool(showComboBox)) // << std::endl; } } /** * Called when an enabled check box changes state. * * @param indx * Index of checkbox that was clicked. */ void CiftiConnectivityMatrixViewController::enabledCheckBoxClicked(int indx) { CaretAssertVectorIndex(m_fileEnableCheckBoxes, indx); const bool newStatus = m_fileEnableCheckBoxes[indx]->isChecked(); CiftiMappableConnectivityMatrixDataFile* matrixFile = NULL; CiftiFiberTrajectoryFile* trajFile = NULL; getFileAtIndex(indx, matrixFile, trajFile); if (matrixFile != NULL) { matrixFile->setMapDataLoadingEnabled(0, newStatus); } else if (trajFile != NULL) { trajFile->setDataLoadingEnabled(newStatus); } else { CaretAssertMessage(0, "Has a new file type been added?"); } updateOtherCiftiConnectivityMatrixViewControllers(); } /** * Get the file associated with the given index. One of the output files * will be NULL and the other will be non-NULL. * * @param indx * The index. * @param ciftiMatrixFileOut * If there is a CIFTI matrix file at the given index, this will be non-NULL. * @param ciftiTrajFileOut * If there is a CIFTI trajectory file at the given index, this will be non-NULL. */ void CiftiConnectivityMatrixViewController::getFileAtIndex(const int32_t indx, CiftiMappableConnectivityMatrixDataFile* &ciftiMatrixFileOut, CiftiFiberTrajectoryFile* &ciftiTrajFileOut) { CaretAssertVectorIndex(m_fileEnableCheckBoxes, indx); void* ptr = m_fileEnableCheckBoxes[indx]->property(FILE_POINTER_PROPERTY_NAME).value(); CaretMappableDataFile* mapFilePointer = (CaretMappableDataFile*)ptr; ciftiMatrixFileOut = dynamic_cast(mapFilePointer); ciftiTrajFileOut = dynamic_cast(mapFilePointer); AString name = ""; if (mapFilePointer != NULL) { name = mapFilePointer->getFileNameNoPath(); } // std::cout << "File at index: " // << indx // << " name: " // << qPrintable(name) // << " cifti-matrix-ptr: " // << (long)ciftiMatrixFileOut // << " cifti-traj-ptr: " // << (long)ciftiTrajFileOut // << std::endl; if (ciftiMatrixFileOut != NULL) { /* OK */ } else if (ciftiTrajFileOut != NULL) { /* OK */ } else { CaretAssertMessage(0, "Has a new file type been added?"); } } /** * Called when fiber orientation file combo box changed. * * @param indx * Index of combo box that was changed. */ void CiftiConnectivityMatrixViewController::fiberOrientationFileComboBoxActivated(int indx) { CaretAssertVectorIndex(m_fiberOrientationFileComboBoxes, indx); CiftiMappableConnectivityMatrixDataFile* matrixFile = NULL; CiftiFiberTrajectoryFile* trajFile = NULL; getFileAtIndex(indx, matrixFile, trajFile); CaretAssertMessage(trajFile, "Selected orientation file but trajectory file is invalid."); QComboBox* cb = m_fiberOrientationFileComboBoxes[indx]; void* ptr = cb->itemData(indx, Qt::UserRole).value(); CaretAssert(ptr); CiftiFiberOrientationFile* orientFile = (CiftiFiberOrientationFile*)ptr; std::vector orientationFiles; GuiManager::get()->getBrain()->getConnectivityFiberOrientationFiles(orientationFiles); if (std::find(orientationFiles.begin(), orientationFiles.end(), orientFile) == orientationFiles.end()) { CaretAssertMessage(0, "Selected fiber orientation file is no longer valid."); return; } trajFile->setMatchingFiberOrientationFile(orientFile); updateOtherCiftiConnectivityMatrixViewControllers(); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } /** * Called when copy tool button is clicked. * * @param indx * Index of copy tool button that was clicked. */ void CiftiConnectivityMatrixViewController::copyToolButtonClicked(int indx) { CursorDisplayScoped cursor; cursor.showWaitCursor(); CiftiMappableConnectivityMatrixDataFile* matrixFile = NULL; CiftiFiberTrajectoryFile* trajFile = NULL; getFileAtIndex(indx, matrixFile, trajFile); bool errorFlag = false; AString errorMessage; const AString directoryName = GuiManager::get()->getBrain()->getCurrentDirectory(); if (matrixFile != NULL) { CiftiBrainordinateScalarFile* scalarFile = CiftiBrainordinateScalarFile::newInstanceFromRowInCiftiConnectivityMatrixFile(matrixFile, directoryName, errorMessage); if (scalarFile != NULL) { EventDataFileAdd dataFileAdd(scalarFile); EventManager::get()->sendEvent(dataFileAdd.getPointer()); if (dataFileAdd.isError()) { errorMessage = dataFileAdd.getErrorMessage(); errorFlag = true; } } else { errorFlag = true; } } else if (trajFile != NULL) { CiftiFiberTrajectoryFile* newTrajFile = trajFile->newFiberTrajectoryFileFromLoadedRowData(directoryName, errorMessage); if (newTrajFile != NULL) { EventDataFileAdd dataFileAdd(newTrajFile); EventManager::get()->sendEvent(dataFileAdd.getPointer()); if (dataFileAdd.isError()) { errorMessage = dataFileAdd.getErrorMessage(); errorFlag = true; } } else { errorFlag = true; } } else { CaretAssertMessage(0, "Has a new file type been added?"); } EventManager::get()->sendEvent(EventUserInterfaceUpdate().getPointer()); cursor.restoreCursor(); if (errorFlag) { WuQMessageBox::errorOk(m_fileCopyToolButtons[indx], errorMessage); } } ///** // * Update graphics and GUI after // */ //void //CiftiConnectivityMatrixViewController::updateUserInterfaceAndGraphicsWindow() //{ // updateOtherCiftiConnectivityMatrixViewControllers(); // // EventManager::get()->sendEvent(EventSurfaceColoringInvalidate().getPointer()); // EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); //} /** * Update other connectivity view controllers other than 'this' instance * that contain the same connectivity file. */ void CiftiConnectivityMatrixViewController::updateOtherCiftiConnectivityMatrixViewControllers() { for (std::set::iterator iter = s_allCiftiConnectivityMatrixViewControllers.begin(); iter != s_allCiftiConnectivityMatrixViewControllers.end(); iter++) { CiftiConnectivityMatrixViewController* clvc = *iter; if (clvc != this) { clvc->updateViewController(); } } } /** * Receive events from the event manager. * * @param event * Event sent by event manager. */ void CiftiConnectivityMatrixViewController::receiveEvent(Event* event) { if (event->getEventType() == EventTypeEnum::EVENT_USER_INTERFACE_UPDATE) { EventUserInterfaceUpdate* uiEvent = dynamic_cast(event); CaretAssert(uiEvent); // if (uiEvent->isUpdateForWindow(this->browserWindowIndex)) { if (uiEvent->isConnectivityUpdate() || uiEvent->isToolBoxUpdate()) { this->updateViewController(); uiEvent->setEventProcessed(); } // } } } workbench-1.1.1/src/GuiQt/CiftiConnectivityMatrixViewController.h000066400000000000000000000076551255417355300252350ustar00rootroot00000000000000#ifndef __CIFTI_CONNECTIVITY_MATRIX_VIEW_CONTROLLER__H_ #define __CIFTI_CONNECTIVITY_MATRIX_VIEW_CONTROLLER__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include "EventListenerInterface.h" class QCheckBox; class QComboBox; class QGridLayout; class QLineEdit; class QSignalMapper; class QToolButton; namespace caret { class CiftiMappableConnectivityMatrixDataFile; class CiftiFiberTrajectoryFile; class CiftiConnectivityMatrixViewController : public QWidget, EventListenerInterface { Q_OBJECT public: CiftiConnectivityMatrixViewController(const Qt::Orientation orientation, QWidget* parent); virtual ~CiftiConnectivityMatrixViewController(); void receiveEvent(Event* event); private slots: void enabledCheckBoxClicked(int); void copyToolButtonClicked(int); void fiberOrientationFileComboBoxActivated(int); private: CiftiConnectivityMatrixViewController(const CiftiConnectivityMatrixViewController&); CiftiConnectivityMatrixViewController& operator=(const CiftiConnectivityMatrixViewController&); // void updateUserInterfaceAndGraphicsWindow(); void updateViewController(); void updateOtherCiftiConnectivityMatrixViewControllers(); void updateFiberOrientationComboBoxes(); void getFileAtIndex(const int32_t indx, CiftiMappableConnectivityMatrixDataFile* &ciftiMatrixFileOut, CiftiFiberTrajectoryFile* &ciftiTrajFileOut); std::vector m_fileEnableCheckBoxes; std::vector m_fileNameLineEdits; std::vector m_fileCopyToolButtons; std::vector m_fiberOrientationFileComboBoxes; QGridLayout* m_gridLayout; QSignalMapper* m_signalMapperFileEnableCheckBox; QSignalMapper* m_signalMapperFileCopyToolButton; QSignalMapper* m_signalMapperFiberOrientationFileComboBox; static std::set s_allCiftiConnectivityMatrixViewControllers; static int COLUMN_ENABLE_CHECKBOX; static int COLUMN_COPY_BUTTON; static int COLUMN_NAME_LINE_EDIT; static int COLUMN_ORIENTATION_FILE_COMBO_BOX; }; #ifdef __CIFTI_CONNECTIVITY_MATRIX_VIEW_CONTROLLER_DECLARE__ std::set CiftiConnectivityMatrixViewController::s_allCiftiConnectivityMatrixViewControllers; int CiftiConnectivityMatrixViewController::COLUMN_ENABLE_CHECKBOX = 0; int CiftiConnectivityMatrixViewController::COLUMN_COPY_BUTTON = 1; int CiftiConnectivityMatrixViewController::COLUMN_NAME_LINE_EDIT = 2; int CiftiConnectivityMatrixViewController::COLUMN_ORIENTATION_FILE_COMBO_BOX = 3; #endif // __CIFTI_CONNECTIVITY_MATRIX_VIEW_CONTROLLER_DECLARE__ } // namespace #endif //__CIFTI_CONNECTIVITY_MATRIX_VIEW_CONTROLLER__H_ workbench-1.1.1/src/GuiQt/CiftiParcelSelectionComboBox.cxx000066400000000000000000000100351255417355300235350ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __CIFTI_PARCEL_SELECTION_COMBO_BOX_DECLARE__ #include "CiftiParcelSelectionComboBox.h" #undef __CIFTI_PARCEL_SELECTION_COMBO_BOX_DECLARE__ #include #include "AStringNaturalComparison.h" #include "CaretAssert.h" #include "CaretLogger.h" #include "CiftiParcelsMap.h" using namespace caret; /** * \class caret::CiftiParcelSelectionComboBox * \brief ComboBox for selection of a Parcel from a CiftiParcelsMap. * \ingroup GuiQt */ /** * Constructor. */ CiftiParcelSelectionComboBox::CiftiParcelSelectionComboBox(QObject* parent) : WuQWidget(parent) { m_comboBox = new QComboBox(); QObject::connect(m_comboBox, SIGNAL(activated(const QString&)), this, SIGNAL(parcelNameSelected(const QString&))); } /** * Destructor. */ CiftiParcelSelectionComboBox::~CiftiParcelSelectionComboBox() { } /** * @return The QComboBox for adding to a layout, enalbling, etc. */ QWidget* CiftiParcelSelectionComboBox::getWidget() { return m_comboBox; } /** * Update the combo box with the given parcels map. If NULL, * combo box will be empty. * * @param parcelsMap * Parcels map inserted into combo box. */ void CiftiParcelSelectionComboBox::updateComboBox(const CiftiParcelsMap* parcelsMap) { QString selectedParcelName = m_comboBox->currentText(); m_comboBox->clear(); if (parcelsMap != NULL) { std::set sortedNames; const std::vector& allParcels = parcelsMap->getParcels(); for (std::vector::const_iterator parcelIter = allParcels.begin(); parcelIter != allParcels.end(); parcelIter++) { sortedNames.insert(parcelIter->m_name); } for (std::set::iterator iter = sortedNames.begin(); iter != sortedNames.end(); iter++) { m_comboBox->addItem(*iter); } // QStringList parcelNamesList = QStringList::fromSet(sortedNames); // parcelNamesList.append(parcelIter->m_name); // // m_comboBox->addItems(parcelNamesList); } if ( ! selectedParcelName.isEmpty()) { if (m_comboBox->findText(selectedParcelName) < 0) { selectedParcelName = ""; } } if (selectedParcelName.isEmpty()) { if (m_comboBox->count() > 0) { selectedParcelName = m_comboBox->itemText(0); } } if ( ! selectedParcelName.isEmpty()) { setSelectedParcelName(selectedParcelName); } } /** * @return Name of selected parcel. */ AString CiftiParcelSelectionComboBox::getSelectedParcelName() { return m_comboBox->currentText(); } /** * Set the selected parcel to the parcel with the given name. */ void CiftiParcelSelectionComboBox::setSelectedParcelName(const QString& parcelName) { const int32_t parcelNameIndex = m_comboBox->findText(parcelName); if (parcelNameIndex >= 0) { m_comboBox->setCurrentIndex(parcelNameIndex); } else { CaretLogWarning("Parcel named \"" + parcelName + "\" is not valid name in CiftiParcelSelectionComboBox"); } } workbench-1.1.1/src/GuiQt/CiftiParcelSelectionComboBox.h000066400000000000000000000041061255417355300231640ustar00rootroot00000000000000#ifndef __CIFTI_PARCEL_SELECTION_COMBO_BOX_H__ #define __CIFTI_PARCEL_SELECTION_COMBO_BOX_H__ /*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AString.h" #include "WuQWidget.h" class QComboBox; namespace caret { class CiftiParcelsMap; class CiftiParcelSelectionComboBox : public WuQWidget { Q_OBJECT public: CiftiParcelSelectionComboBox(QObject* parent); virtual ~CiftiParcelSelectionComboBox(); virtual QWidget* getWidget(); void updateComboBox(const CiftiParcelsMap* parcelsMap); AString getSelectedParcelName(); public slots: void setSelectedParcelName(const QString& parcelName); signals: void parcelNameSelected(const QString& parcelName); // ADD_NEW_METHODS_HERE private: CiftiParcelSelectionComboBox(const CiftiParcelSelectionComboBox&); CiftiParcelSelectionComboBox& operator=(const CiftiParcelSelectionComboBox&); QComboBox* m_comboBox; // ADD_NEW_MEMBERS_HERE }; #ifdef __CIFTI_PARCEL_SELECTION_COMBO_BOX_DECLARE__ // #endif // __CIFTI_PARCEL_SELECTION_COMBO_BOX_DECLARE__ } // namespace #endif //__CIFTI_PARCEL_SELECTION_COMBO_BOX_H__ workbench-1.1.1/src/GuiQt/ClippingPlanesDialog.cxx000066400000000000000000000416611255417355300221120ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CLIPPING_PLANES_DIALOG_DECLARE__ #include "ClippingPlanesDialog.h" #undef __CLIPPING_PLANES_DIALOG_DECLARE__ #include #include #include #include #include #include "BrainBrowserWindow.h" #include "BrainBrowserWindowComboBox.h" #include "BrowserTabContent.h" #include "CaretAssert.h" #include "EventGraphicsUpdateAllWindows.h" #include "EventManager.h" #include "EventUserInterfaceUpdate.h" #include "GuiManager.h" #include "WuQtUtilities.h" #include "WuQWidgetObjectGroup.h" using namespace caret; /** * \class caret::ClippingPlanesDialog * \brief Dialog for adjusting clipping planes transformation. * \ingroup GuiQt */ /** * Constructor. */ ClippingPlanesDialog::ClippingPlanesDialog(QWidget* parent) : WuQDialogNonModal("Clipping Planes", parent) { m_blockDialogUpdate = true; /*------------------------------------------------------------------------*/ /* * Create widgets */ /* * Window number */ QLabel* windowLabel = new QLabel("Workbench Window: "); m_browserWindowComboBox = new BrainBrowserWindowComboBox(this); m_browserWindowComboBox->getWidget()->setFixedWidth(50); QObject::connect(m_browserWindowComboBox, SIGNAL(browserWindowSelected(BrainBrowserWindow*)), this, SLOT(browserWindowComboBoxValueChanged(BrainBrowserWindow*))); QHBoxLayout* windowLayout = new QHBoxLayout(); windowLayout->addWidget(windowLabel); windowLayout->addWidget(m_browserWindowComboBox->getWidget()); windowLayout->addStretch(); /* * X, Y, Z column labels */ QLabel* xColumnLabel = new QLabel("X"); QLabel* yColumnLabel = new QLabel("Y"); QLabel* zColumnLabel = new QLabel("Z"); const int spinBoxWidth = 90; /* * Panning */ const double panStep = 1.0; QLabel* panLabel = new QLabel("Pan:"); m_xPanDoubleSpinBox = new QDoubleSpinBox; m_xPanDoubleSpinBox->setMinimum(-100000.0); m_xPanDoubleSpinBox->setMaximum( 100000.0); m_xPanDoubleSpinBox->setSingleStep(panStep); m_xPanDoubleSpinBox->setDecimals(2); m_xPanDoubleSpinBox->setFixedWidth(spinBoxWidth); QObject::connect(m_xPanDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(clippingValueChanged())); m_yPanDoubleSpinBox = new QDoubleSpinBox; m_yPanDoubleSpinBox->setMinimum(-100000.0); m_yPanDoubleSpinBox->setMaximum( 100000.0); m_yPanDoubleSpinBox->setSingleStep(panStep); m_yPanDoubleSpinBox->setDecimals(2); m_yPanDoubleSpinBox->setFixedWidth(spinBoxWidth); QObject::connect(m_yPanDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(clippingValueChanged())); m_zPanDoubleSpinBox = new QDoubleSpinBox; m_zPanDoubleSpinBox->setMinimum(-100000.0); m_zPanDoubleSpinBox->setMaximum( 100000.0); m_zPanDoubleSpinBox->setSingleStep(panStep); m_zPanDoubleSpinBox->setDecimals(2); m_zPanDoubleSpinBox->setFixedWidth(spinBoxWidth); QObject::connect(m_zPanDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(clippingValueChanged())); /* * Rotation */ const double rotationMinimum = -360.0; const double rotationMaximum = 360.0; const double rotateStep = 1.0; QLabel* rotateLabel = new QLabel("Rotate: "); m_xRotateDoubleSpinBox = new QDoubleSpinBox; m_xRotateDoubleSpinBox->setWrapping(true); m_xRotateDoubleSpinBox->setMinimum(rotationMinimum); m_xRotateDoubleSpinBox->setMaximum(rotationMaximum); m_xRotateDoubleSpinBox->setSingleStep(rotateStep); m_xRotateDoubleSpinBox->setDecimals(2); m_xRotateDoubleSpinBox->setFixedWidth(spinBoxWidth); QObject::connect(m_xRotateDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(clippingValueChanged())); m_yRotateDoubleSpinBox = new QDoubleSpinBox; m_yRotateDoubleSpinBox->setWrapping(true); m_yRotateDoubleSpinBox->setMinimum(rotationMinimum); m_yRotateDoubleSpinBox->setMaximum(rotationMaximum); m_yRotateDoubleSpinBox->setSingleStep(rotateStep); m_yRotateDoubleSpinBox->setDecimals(2); m_yRotateDoubleSpinBox->setFixedWidth(spinBoxWidth); QObject::connect(m_yRotateDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(clippingValueChanged())); m_zRotateDoubleSpinBox = new QDoubleSpinBox; m_zRotateDoubleSpinBox->setWrapping(true); m_zRotateDoubleSpinBox->setMinimum(rotationMinimum); m_zRotateDoubleSpinBox->setMaximum(rotationMaximum); m_zRotateDoubleSpinBox->setSingleStep(rotateStep); m_zRotateDoubleSpinBox->setDecimals(2); m_zRotateDoubleSpinBox->setFixedWidth(spinBoxWidth); QObject::connect(m_zRotateDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(clippingValueChanged())); /* * Thickness */ const double thicknessMinimum = 0.0; const double thicknessMaximum = 1000000.0; const double thicknessStep = 1.0; QLabel* thicknessLabel = new QLabel("Thickness (mm)"); m_xThicknessDoubleSpinBox = new QDoubleSpinBox(); m_xThicknessDoubleSpinBox->setMinimum(thicknessMinimum); m_xThicknessDoubleSpinBox->setMaximum(thicknessMaximum); m_xThicknessDoubleSpinBox->setSingleStep(thicknessStep); m_xThicknessDoubleSpinBox->setDecimals(2); m_xThicknessDoubleSpinBox->setFixedWidth(spinBoxWidth); QObject::connect(m_xThicknessDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(clippingValueChanged())); m_yThicknessDoubleSpinBox = new QDoubleSpinBox(); m_yThicknessDoubleSpinBox->setMinimum(thicknessMinimum); m_yThicknessDoubleSpinBox->setMaximum(thicknessMaximum); m_yThicknessDoubleSpinBox->setSingleStep(thicknessStep); m_yThicknessDoubleSpinBox->setDecimals(2); m_yThicknessDoubleSpinBox->setFixedWidth(spinBoxWidth); QObject::connect(m_yThicknessDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(clippingValueChanged())); m_zThicknessDoubleSpinBox = new QDoubleSpinBox(); m_zThicknessDoubleSpinBox->setMinimum(thicknessMinimum); m_zThicknessDoubleSpinBox->setMaximum(thicknessMaximum); m_zThicknessDoubleSpinBox->setSingleStep(thicknessStep); m_zThicknessDoubleSpinBox->setDecimals(2); m_zThicknessDoubleSpinBox->setFixedWidth(spinBoxWidth); QObject::connect(m_zThicknessDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(clippingValueChanged())); m_clippingWidgetGroup = new WuQWidgetObjectGroup(this); m_clippingWidgetGroup->add(m_xPanDoubleSpinBox); m_clippingWidgetGroup->add(m_yPanDoubleSpinBox); m_clippingWidgetGroup->add(m_zPanDoubleSpinBox); m_clippingWidgetGroup->add(m_xRotateDoubleSpinBox); m_clippingWidgetGroup->add(m_yRotateDoubleSpinBox); m_clippingWidgetGroup->add(m_zRotateDoubleSpinBox); m_clippingWidgetGroup->add(m_xThicknessDoubleSpinBox); m_clippingWidgetGroup->add(m_yThicknessDoubleSpinBox); m_clippingWidgetGroup->add(m_zThicknessDoubleSpinBox); /* * Show clipping box checkbox */ m_displayClippingBoxCheckBox = new QCheckBox("Show Clipping Box Outline"); QObject::connect(m_displayClippingBoxCheckBox, SIGNAL(clicked(bool)), this, SLOT(clippingValueChanged())); /*------------------------------------------------------------------------*/ /* * Layout widgets */ /* * Columns for grid layout */ int column = 0; const int COLUMN_LABEL = column++; const int COLUMN_X = column++; const int COLUMN_Y = column++; const int COLUMN_Z = column++; QWidget* gridWidget = new QWidget(); QGridLayout* gridLayout = new QGridLayout(gridWidget); WuQtUtilities::setLayoutSpacingAndMargins(gridLayout, 4, 4); int row = 0; gridLayout->addWidget(xColumnLabel, row, COLUMN_X, Qt::AlignHCenter); gridLayout->addWidget(yColumnLabel, row, COLUMN_Y, Qt::AlignHCenter); gridLayout->addWidget(zColumnLabel, row, COLUMN_Z, Qt::AlignHCenter); row++; gridLayout->addWidget(panLabel, row, COLUMN_LABEL); gridLayout->addWidget(m_xPanDoubleSpinBox, row, COLUMN_X); gridLayout->addWidget(m_yPanDoubleSpinBox, row, COLUMN_Y); gridLayout->addWidget(m_zPanDoubleSpinBox, row, COLUMN_Z); row++; gridLayout->addWidget(rotateLabel, row, COLUMN_LABEL); gridLayout->addWidget(m_xRotateDoubleSpinBox, row, COLUMN_X); gridLayout->addWidget(m_yRotateDoubleSpinBox, row, COLUMN_Y); gridLayout->addWidget(m_zRotateDoubleSpinBox, row, COLUMN_Z); row++; gridLayout->addWidget(thicknessLabel, row, COLUMN_LABEL); gridLayout->addWidget(m_xThicknessDoubleSpinBox, row, COLUMN_X); gridLayout->addWidget(m_yThicknessDoubleSpinBox, row, COLUMN_Y); gridLayout->addWidget(m_zThicknessDoubleSpinBox, row, COLUMN_Z); /*------------------------------------------------------------------------*/ /* * Finish up */ QWidget* widget = new QWidget(); QVBoxLayout* layout = new QVBoxLayout(widget); WuQtUtilities::setLayoutSpacingAndMargins(layout, 4, 4); layout->addLayout(windowLayout); layout->addWidget(m_displayClippingBoxCheckBox); layout->addWidget(WuQtUtilities::createHorizontalLineWidget()); layout->addWidget(gridWidget); widget->setFixedSize(widget->sizeHint()); /* * Remove apply button by using an empty name */ setApplyButtonText(""); m_resetPushButton = addUserPushButton("Reset", QDialogButtonBox::NoRole); setCentralWidget(widget, WuQDialog::SCROLL_AREA_NEVER); /* * No auto default button processing (Qt highlights button) */ disableAutoDefaultForAllPushButtons(); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_USER_INTERFACE_UPDATE); setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); m_blockDialogUpdate = false; } /** * Destructor. */ ClippingPlanesDialog::~ClippingPlanesDialog() { EventManager::get()->removeAllEventsFromListener(this); } /** * Called when a user (added) push button is clicked. * * @param userPushButton * Button that was clicked. */ WuQDialogNonModal::DialogUserButtonResult ClippingPlanesDialog::userButtonPressed(QPushButton* userPushButton) { if (userPushButton == m_resetPushButton) { BrainBrowserWindow* bbw = m_browserWindowComboBox->getSelectedBrowserWindow(); if (bbw != NULL) { BrowserTabContent* btc = bbw->getBrowserTabContent(); if (btc != NULL) { btc->resetClippingPlaneTransformation(); updateContent(btc->getTabNumber()); updateGraphicsWindow(); } } } else { CaretAssert(0); } return WuQDialogNonModal::RESULT_NONE; } /** * Called when window number combo box value changed. */ void ClippingPlanesDialog::browserWindowComboBoxValueChanged(BrainBrowserWindow* browserWindow) { int32_t windowIndex = -1; if (browserWindow != NULL) { windowIndex = browserWindow->getBrowserWindowIndex(); } updateContent(windowIndex); } /** * Called when a clipping value is changed. */ void ClippingPlanesDialog::clippingValueChanged() { const float panning[3] = { m_xPanDoubleSpinBox->value(), m_yPanDoubleSpinBox->value(), m_zPanDoubleSpinBox->value() }; const float rotation[3] = { m_xRotateDoubleSpinBox->value(), m_yRotateDoubleSpinBox->value(), m_zRotateDoubleSpinBox->value() }; const float thickness[3] ={ m_xThicknessDoubleSpinBox->value(), m_yThicknessDoubleSpinBox->value(), m_zThicknessDoubleSpinBox->value() }; /* * Update, set, and validate selected browser window */ BrainBrowserWindow* bbw = m_browserWindowComboBox->getSelectedBrowserWindow(); if (bbw != NULL) { BrowserTabContent* btc = bbw->getBrowserTabContent(); if (btc != NULL) { btc->setClippingPlaneTransformation(panning, rotation, thickness, m_displayClippingBoxCheckBox->isChecked()); updateGraphicsWindow(); } } } /** * Update the selected graphics window. */ void ClippingPlanesDialog::updateGraphicsWindow() { EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); EventManager::get()->sendEvent(EventUserInterfaceUpdate().getPointer()); } /** * Gets called when the dialog gains focus. */ void ClippingPlanesDialog::focusGained() { updateDialog(); } /** * Update the dialog. */ void ClippingPlanesDialog::updateDialog() { m_browserWindowComboBox->updateComboBox(); updateContent(m_browserWindowComboBox->getSelectedBrowserWindowIndex()); } /** * Update the content in the dialog * @param browserWindowIndexIn * Index of the browser window. */ void ClippingPlanesDialog::updateContent(const int32_t browserWindowIndexIn) { /* * May get updates when graphics are redrawn by this dialog * and not doing this could result in infinite loop */ if (m_blockDialogUpdate) { return; } /* * Update, set, and validate selected browser window */ m_browserWindowComboBox->updateComboBox(); m_browserWindowComboBox->setBrowserWindowByIndex(browserWindowIndexIn); const int32_t browserWindowIndex = m_browserWindowComboBox->getSelectedBrowserWindowIndex(); BrainBrowserWindow* bbw = GuiManager::get()->getBrowserWindowByWindowIndex(browserWindowIndex); if (bbw != NULL) { BrowserTabContent* btc = bbw->getBrowserTabContent(); if (btc != NULL) { m_clippingWidgetGroup->blockAllSignals(true); float panning[3]; float rotation[3]; float thickness[3]; bool displayClippingBox; btc->getClippingPlaneTransformation(panning, rotation, thickness, displayClippingBox); m_xPanDoubleSpinBox->setValue(panning[0]); m_yPanDoubleSpinBox->setValue(panning[1]); m_zPanDoubleSpinBox->setValue(panning[2]); m_xRotateDoubleSpinBox->setValue(rotation[0]); m_yRotateDoubleSpinBox->setValue(rotation[1]); m_zRotateDoubleSpinBox->setValue(rotation[2]); m_xThicknessDoubleSpinBox->setValue(thickness[0]); m_yThicknessDoubleSpinBox->setValue(thickness[1]); m_zThicknessDoubleSpinBox->setValue(thickness[2]); m_displayClippingBoxCheckBox->setChecked(displayClippingBox); m_clippingWidgetGroup->blockAllSignals(false); } } } /** * Receive events from the event manager. * * @param event * Event sent by event manager. */ void ClippingPlanesDialog::receiveEvent(Event* event) { if (event->getEventType() == EventTypeEnum::EVENT_USER_INTERFACE_UPDATE) { EventUserInterfaceUpdate* updateEvent = dynamic_cast(event); CaretAssert(updateEvent); updateEvent->setEventProcessed(); updateDialog(); } } workbench-1.1.1/src/GuiQt/ClippingPlanesDialog.h000066400000000000000000000060711255417355300215330ustar00rootroot00000000000000#ifndef __CLIPPING_PLANES_DIALOG_H__ #define __CLIPPING_PLANES_DIALOG_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "WuQDialogNonModal.h" #include "EventListenerInterface.h" class QCheckBox; class QDoubleSpinBox; class QPushButton; namespace caret { class BrainBrowserWindow; class BrainBrowserWindowComboBox; class WuQWidgetObjectGroup; class ClippingPlanesDialog : public WuQDialogNonModal, public EventListenerInterface { Q_OBJECT public: ClippingPlanesDialog(QWidget* parent); virtual ~ClippingPlanesDialog(); void updateDialog(); void updateContent(const int32_t browserWindowIndex); // ADD_NEW_METHODS_HERE virtual void receiveEvent(Event* event); private slots: void browserWindowComboBoxValueChanged(BrainBrowserWindow* browserWindow); void clippingValueChanged(); protected: void focusGained(); virtual DialogUserButtonResult userButtonPressed(QPushButton* userPushButton); private: void updateGraphicsWindow(); ClippingPlanesDialog(const ClippingPlanesDialog&); ClippingPlanesDialog& operator=(const ClippingPlanesDialog&); QPushButton* m_resetPushButton; BrainBrowserWindowComboBox* m_browserWindowComboBox; WuQWidgetObjectGroup* m_clippingWidgetGroup; QDoubleSpinBox* m_xPanDoubleSpinBox; QDoubleSpinBox* m_yPanDoubleSpinBox; QDoubleSpinBox* m_zPanDoubleSpinBox; QDoubleSpinBox* m_xRotateDoubleSpinBox; QDoubleSpinBox* m_yRotateDoubleSpinBox; QDoubleSpinBox* m_zRotateDoubleSpinBox; QDoubleSpinBox* m_xThicknessDoubleSpinBox; QDoubleSpinBox* m_yThicknessDoubleSpinBox; QDoubleSpinBox* m_zThicknessDoubleSpinBox; QCheckBox* m_displayClippingBoxCheckBox; bool m_blockDialogUpdate; // ADD_NEW_MEMBERS_HERE }; #ifdef __CLIPPING_PLANES_DIALOG_DECLARE__ // #endif // __CLIPPING_PLANES_DIALOG_DECLARE__ } // namespace #endif //__CLIPPING_PLANES_DIALOG_H__ workbench-1.1.1/src/GuiQt/ColorEditorWidget.cxx000066400000000000000000000233601255417355300214470ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #include #define __COLOR_EDITOR_WIDGET_DECLARE__ #include "ColorEditorWidget.h" #undef __COLOR_EDITOR_WIDGET_DECLARE__ #include "WuQFactory.h" #include "WuQWidgetObjectGroup.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::ColorEditorWidget * \brief Widget for editing RGBA colors. * \ingroup GuiQt */ /** * Constructor. */ ColorEditorWidget::ColorEditorWidget(const bool alphaControlEnabled, QWidget* parent) : QWidget(parent) { this->colorSwatchWidget = new QWidget(); this->colorSwatchWidget->setFixedHeight(25); QLabel* redLabel = new QLabel("Red"); this->redSpinBox = WuQFactory::newSpinBox(); this->redSpinBox->setRange(0, 255); this->redSpinBox->setSingleStep(1); QObject::connect(this->redSpinBox, SIGNAL(valueChanged(int)), this, SLOT(redValueChanged(int))); this->redSlider = new QSlider(); this->redSlider->setRange(0, 255); this->redSlider->setOrientation(Qt::Horizontal); QObject::connect(this->redSlider, SIGNAL(valueChanged(int)), this, SLOT(redValueChanged(int))); QLabel* greenLabel = new QLabel("Green"); this->greenSpinBox = WuQFactory::newSpinBox(); this->greenSpinBox->setRange(0, 255); this->greenSpinBox->setSingleStep(1); QObject::connect(this->greenSpinBox, SIGNAL(valueChanged(int)), this, SLOT(greenValueChanged(int))); this->greenSlider = new QSlider(); this->greenSlider->setRange(0, 255); this->greenSlider->setOrientation(Qt::Horizontal); QObject::connect(this->greenSlider, SIGNAL(valueChanged(int)), this, SLOT(greenValueChanged(int))); QLabel* blueLabel = new QLabel("Blue"); this->blueSpinBox = WuQFactory::newSpinBox(); this->blueSpinBox->setRange(0, 255); this->blueSpinBox->setSingleStep(1); QObject::connect(this->blueSpinBox, SIGNAL(valueChanged(int)), this, SLOT(blueValueChanged(int))); this->blueSlider = new QSlider(); this->blueSlider->setRange(0, 255); this->blueSlider->setOrientation(Qt::Horizontal); QObject::connect(this->blueSlider, SIGNAL(valueChanged(int)), this, SLOT(blueValueChanged(int))); QLabel* alphaLabel = NULL; this->alphaSpinBox = NULL; if (alphaControlEnabled) { alphaLabel = new QLabel("Alpha"); this->alphaSpinBox = WuQFactory::newSpinBox(); this->alphaSpinBox->setRange(0, 255); this->alphaSpinBox->setSingleStep(1); QObject::connect(this->alphaSpinBox, SIGNAL(valueChanged(int)), this, SLOT(alphaValueChanged(int))); this->alphaSlider = new QSlider(); this->alphaSlider->setRange(0, 255); this->alphaSlider->setOrientation(Qt::Horizontal); QObject::connect(this->alphaSlider, SIGNAL(valueChanged(int)), this, SLOT(alphaValueChanged(int))); } int columnCount = 0; const int COLUMN_NAME = columnCount++; const int COLUMN_SLIDER = columnCount++; const int COLUMN_SPINBOX = columnCount++; const int COLUMN_LAST = columnCount; int row = 0; QGridLayout* gridLayout = new QGridLayout(this); WuQtUtilities::setLayoutSpacingAndMargins(gridLayout, 2, 2); gridLayout->addWidget(this->colorSwatchWidget, row, 0, 1, COLUMN_LAST); row++; gridLayout->addWidget(redLabel, row, COLUMN_NAME); gridLayout->addWidget(this->redSlider, row, COLUMN_SLIDER); gridLayout->addWidget(this->redSpinBox, row, COLUMN_SPINBOX); row++; gridLayout->addWidget(greenLabel, row, COLUMN_NAME); gridLayout->addWidget(this->greenSlider, row, COLUMN_SLIDER); gridLayout->addWidget(this->greenSpinBox, row, COLUMN_SPINBOX); row++; gridLayout->addWidget(blueLabel, row, COLUMN_NAME); gridLayout->addWidget(this->blueSlider, row, COLUMN_SLIDER); gridLayout->addWidget(this->blueSpinBox, row, COLUMN_SPINBOX); row++; if (this->alphaSpinBox != NULL) { gridLayout->addWidget(alphaLabel, row, COLUMN_NAME); gridLayout->addWidget(this->alphaSlider, row, COLUMN_SLIDER); gridLayout->addWidget(this->alphaSpinBox, row, COLUMN_SPINBOX); row++; } this->controlsWidgetGroup = new WuQWidgetObjectGroup(this); this->controlsWidgetGroup->add(this->redSpinBox); this->controlsWidgetGroup->add(this->redSlider); this->controlsWidgetGroup->add(this->greenSpinBox); this->controlsWidgetGroup->add(this->greenSlider); this->controlsWidgetGroup->add(this->blueSpinBox); this->controlsWidgetGroup->add(this->blueSlider); if (this->alphaSpinBox != NULL) { this->controlsWidgetGroup->add(this->alphaSpinBox); this->controlsWidgetGroup->add(this->alphaSlider); } } /** * Destructor. */ ColorEditorWidget::~ColorEditorWidget() { } /** * Set the color in the control with RGBA * values ranging 0.0 to 1.0. * @param rgba * Input RGBA values. */ void ColorEditorWidget::setColor(const float rgba[4]) { const int32_t intRGBA[4] = { std::min(static_cast(rgba[0] * 255.0), 255), std::min(static_cast(rgba[1] * 255.0), 255), std::min(static_cast(rgba[2] * 255.0), 255), std::min(static_cast(rgba[3] * 255.0), 255) }; this->setColor(intRGBA); } /** * Get the color in the control with RGBA * values ranging 0.0 to 1.0. * @param rgba * Output RGBA values. */ void ColorEditorWidget::getColor(float rgba[4]) const { int intRGBA[4]; this->getColor(intRGBA); rgba[0] = intRGBA[0] / 255.0; rgba[1] = intRGBA[1] / 255.0; rgba[2] = intRGBA[2] / 255.0; rgba[3] = intRGBA[3] / 255.0; } /** * Set the color in the control with RGBA * values ranging 0 to 255. * @param rgba * Input RGBA values. */ void ColorEditorWidget::setColor(const int rgba[4]) { this->controlsWidgetGroup->blockAllSignals(true); this->redSpinBox->setValue(rgba[0]); this->redSlider->setValue(rgba[0]); this->greenSpinBox->setValue(rgba[1]); this->greenSlider->setValue(rgba[1]); this->blueSpinBox->setValue(rgba[2]); this->blueSlider->setValue(rgba[2]); if (this->alphaSpinBox != NULL) { this->alphaSpinBox->setValue(rgba[3]); this->alphaSlider->setValue(rgba[3]); } this->updateColorSwatch(); this->controlsWidgetGroup->blockAllSignals(false); } /** * Get the color in the control with RGBA * values ranging 0 to 255 * @param rgba * Output RGBA values. */ void ColorEditorWidget::getColor(int rgba[4]) const { rgba[0] = this->redSpinBox->value(); rgba[1] = this->greenSpinBox->value(); rgba[2] = this->blueSpinBox->value(); if (this->alphaSpinBox != NULL) { rgba[3] = this->alphaSpinBox->value(); } else { rgba[3] = 255; } } /** * Called when a red component value is changed. */ void ColorEditorWidget::redValueChanged(int value) { this->controlsWidgetGroup->blockAllSignals(true); this->redSlider->setValue(value); this->redSpinBox->setValue(value); this->emitColorChangedSignal(); this->controlsWidgetGroup->blockAllSignals(false); } /** * Called when a green component value is changed. */ void ColorEditorWidget::greenValueChanged(int value) { this->controlsWidgetGroup->blockAllSignals(true); this->greenSlider->setValue(value); this->greenSpinBox->setValue(value); this->emitColorChangedSignal(); this->controlsWidgetGroup->blockAllSignals(false); } /** * Called when a blue component value is changed. */ void ColorEditorWidget::blueValueChanged(int value) { this->controlsWidgetGroup->blockAllSignals(true); this->blueSlider->setValue(value); this->blueSpinBox->setValue(value); this->emitColorChangedSignal(); this->controlsWidgetGroup->blockAllSignals(false); } /** * Called when a alpha component value is changed. */ void ColorEditorWidget::alphaValueChanged(int value) { this->controlsWidgetGroup->blockAllSignals(true); this->alphaSlider->setValue(value); this->alphaSpinBox->setValue(value); this->emitColorChangedSignal(); this->controlsWidgetGroup->blockAllSignals(false); } void ColorEditorWidget::updateColorSwatch() { int rgb[4]; this->getColor(rgb); QPalette pal; pal.setColor(QPalette::Window, QColor(rgb[0], rgb[1], rgb[2])); this->colorSwatchWidget->setAutoFillBackground(true); this->colorSwatchWidget->setBackgroundRole(QPalette::Window); this->colorSwatchWidget->setPalette(pal); } /** * Call to emit the color changed signals. */ void ColorEditorWidget::emitColorChangedSignal() { this->updateColorSwatch(); float rgba[4]; this->getColor(rgba); emit colorChanged(rgba); int intRgba[4]; this->getColor(intRgba); emit colorChanged(intRgba); } workbench-1.1.1/src/GuiQt/ColorEditorWidget.h000066400000000000000000000050031255417355300210660ustar00rootroot00000000000000#ifndef __COLOR_EDITOR_WIDGET__H_ #define __COLOR_EDITOR_WIDGET__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include class QSlider; class QSpinBox; namespace caret { class WuQWidgetObjectGroup; class ColorEditorWidget : public QWidget { Q_OBJECT public: ColorEditorWidget(const bool alphaControlEnabled = false, QWidget* parent = 0); virtual ~ColorEditorWidget(); void setColor(const float rgba[4]); void getColor(float rgba[4]) const; void setColor(const int rgba[4]); void getColor(int rgba[4]) const; signals: void colorChanged(const float*); void colorChanged(const int*); public slots: void redValueChanged(int); void blueValueChanged(int); void greenValueChanged(int); void alphaValueChanged(int); void emitColorChangedSignal(); private: ColorEditorWidget(const ColorEditorWidget&); ColorEditorWidget& operator=(const ColorEditorWidget&); private: void updateColorSwatch(); WuQWidgetObjectGroup* controlsWidgetGroup; QWidget* colorSwatchWidget; QSpinBox* redSpinBox; QSpinBox* greenSpinBox; QSpinBox* blueSpinBox; QSpinBox* alphaSpinBox; QSlider* redSlider; QSlider* greenSlider; QSlider* blueSlider; QSlider* alphaSlider; }; #ifdef __COLOR_EDITOR_WIDGET_DECLARE__ // #endif // __COLOR_EDITOR_WIDGET_DECLARE__ } // namespace #endif //__COLOR_EDITOR_WIDGET__H_ workbench-1.1.1/src/GuiQt/CursorDisplayScoped.cxx000066400000000000000000000043411255417355300220150ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __CURSOR_DISPLAY_SCOPED_DECLARE__ #include "CursorDisplayScoped.h" #undef __CURSOR_DISPLAY_SCOPED_DECLARE__ #include "CursorManager.h" #include "GuiManager.h" using namespace caret; /** * \class caret::CursorDisplayScoped * \brief Displays a override cursor * \ingroup GuiQt * * When one of the 'show' methods is called, * displays a cursor that overrides all other cursors in the * application until an instance of this goes out of scope or the * method restoreCursor() is called. */ /** * Constructor that displays a wait cursor. * */ CursorDisplayScoped::CursorDisplayScoped() : CaretObject() { this->isCursorActive = false; } /** * Destructor. */ CursorDisplayScoped::~CursorDisplayScoped() { this->restoreCursor(); } /** * Display the given cursor. * * @param cursor * Cursor that is displayed. */ void CursorDisplayScoped::showCursor(const QCursor& cursor) { this->isCursorActive = true; QApplication::setOverrideCursor(cursor); QApplication::processEvents(); } /** * Display the wait cursor. */ void CursorDisplayScoped::showWaitCursor() { this->showCursor(Qt::WaitCursor); } /** * Restore the default cursor. */ void CursorDisplayScoped::restoreCursor() { if (this->isCursorActive) { QApplication::restoreOverrideCursor(); QApplication::processEvents(); this->isCursorActive = false; } } workbench-1.1.1/src/GuiQt/CursorDisplayScoped.h000066400000000000000000000032061255417355300214410ustar00rootroot00000000000000#ifndef __CURSOR_DISPLAY_SCOPED_H_ #define __CURSOR_DISPLAY_SCOPED_H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" class QCursor; namespace caret { class CursorDisplayScoped : public CaretObject { public: CursorDisplayScoped(); void showWaitCursor(); void showCursor(const QCursor& cursor); void restoreCursor(); virtual ~CursorDisplayScoped(); private: CursorDisplayScoped(const CursorDisplayScoped&); CursorDisplayScoped& operator=(const CursorDisplayScoped&); bool isCursorActive; private: }; #ifdef __CURSOR_DISPLAY_SCOPED_DECLARE__ // #endif // __CURSOR_DISPLAY_SCOPED_DECLARE__ } // namespace #endif //__CURSOR_DISPLAY_SCOPED__H_ workbench-1.1.1/src/GuiQt/CursorEnum.cxx000066400000000000000000000230131255417355300201530ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __CURSOR_ENUM_DECLARE__ #include "CursorEnum.h" #undef __CURSOR_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::CursorEnum * \brief Enumerated type for Cursors (both Qt and custom) * \ingroup GuiQt * * An enumerated type for cursors. Some cursors are * from Qt and others are created using bitmaps. */ /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ CursorEnum::CursorEnum(const Enum enumValue, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCodeCounter++; this->name = name; this->guiName = guiName; } /** * Destructor. */ CursorEnum::~CursorEnum() { } /** * Initialize the enumerated metadata. */ void CursorEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(CursorEnum(CURSOR_DEFAULT, "CURSOR_DEFAULT", "Default Cursor")); enumData.push_back(CursorEnum(CURSOR_ARROW, "CURSOR_ARROW", "Arrow Cursor")); enumData.push_back(CursorEnum(CURSOR_CROSS, "CURSOR_CROSS", "Cross Cursor")); enumData.push_back(CursorEnum(CURSOR_DRAWING_PEN, "CURSOR_DRAWING_PEN", "Drawing Pen Cursor")); enumData.push_back(CursorEnum(CURSOR_POINTING_HAND, "CURSOR_POINTING_HAND", "Pointing Hand Cursor")); enumData.push_back(CursorEnum(CURSOR_WAIT, "CURSOR_WAIT", "Wait Cursor")); enumData.push_back(CursorEnum(CURSOR_WHATS_THIS, "CURSOR_WHATS_THIS", "What's this Cursor")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const CursorEnum* CursorEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const CursorEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString CursorEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const CursorEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ CursorEnum::Enum CursorEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = CURSOR_DEFAULT; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const CursorEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type CursorEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString CursorEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const CursorEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ CursorEnum::Enum CursorEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = CURSOR_DEFAULT; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const CursorEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type CursorEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t CursorEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const CursorEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ CursorEnum::Enum CursorEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = CURSOR_DEFAULT; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const CursorEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type CursorEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void CursorEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void CursorEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(CursorEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void CursorEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(CursorEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } workbench-1.1.1/src/GuiQt/CursorEnum.h000066400000000000000000000066211255417355300176060ustar00rootroot00000000000000#ifndef __CURSOR_ENUM__H_ #define __CURSOR_ENUM__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class CursorEnum { public: /** * Enumerated values. */ enum Enum { /** The default cursor which is usually the parent widget's cursor */ CURSOR_DEFAULT, /** Arrow (typically same as CURSOR_DEFAULT but overrides cursor provided by parent widget, Qt::ArrowCursor) */ CURSOR_ARROW, /** Cross (plus symbol) */ CURSOR_CROSS, /** Drawing Pen */ CURSOR_DRAWING_PEN, /** Pointing hand, Qt::PointingHandCursor*/ CURSOR_POINTING_HAND, /** Wait, Qt::WaitCursor*/ CURSOR_WAIT, /** What's this? Arrow with question mark */ CURSOR_WHATS_THIS }; ~CursorEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); private: CursorEnum(const Enum enumValue, const AString& name, const AString& guiName); static const CursorEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; #ifdef __CURSOR_ENUM_DECLARE__ std::vector CursorEnum::enumData; bool CursorEnum::initializedFlag = false; int32_t CursorEnum::integerCodeCounter = 0; #endif // __CURSOR_ENUM_DECLARE__ } // namespace #endif //__CURSOR_ENUM__H_ workbench-1.1.1/src/GuiQt/CursorManager.cxx000066400000000000000000000074611255417355300206320ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #define __CURSOR_MANAGER_DECLARE__ #include "CursorManager.h" #undef __CURSOR_MANAGER_DECLARE__ #include "WuQtUtilities.h" using namespace caret; /** * \class caret::CursorManager * \brief Manages cursors * \ingroup GuiQt * Provides cursors, some predefined by Qt and * others unique to workbench. */ /** * Constructor. */ CursorManager::CursorManager() : CaretObject() { this->defaultCursor = QCursor(); this->arrowCursor = QCursor(Qt::ArrowCursor); this->crossCursor = QCursor(Qt::CrossCursor); this->penCursor = this->loadCursor(":Cursor/pen_eraser.png", 6, 32 - 7, Qt::UpArrowCursor); this->pointingHandCursor = QCursor(Qt::PointingHandCursor); this->waitCursor = QCursor(Qt::WaitCursor); this->whatsThisCursor = QCursor(Qt::WhatsThisCursor); } /** * Destructor. */ CursorManager::~CursorManager() { } /** * Set the given cursor for the given widget. * @param widget * Widget that has its cursor set. * @param cursor * Cursor for the widget. */ void CursorManager::setCursorForWidget(QWidget* widget, const CursorEnum::Enum cursor) const { switch (cursor) { case CursorEnum::CURSOR_DEFAULT: widget->unsetCursor(); break; case CursorEnum::CURSOR_ARROW: widget->setCursor(this->arrowCursor); break; case CursorEnum::CURSOR_CROSS: widget->setCursor(this->crossCursor); break; case CursorEnum::CURSOR_DRAWING_PEN: widget->setCursor(this->penCursor); break; case CursorEnum::CURSOR_POINTING_HAND: widget->setCursor(this->pointingHandCursor); break; case CursorEnum::CURSOR_WAIT: widget->setCursor(this->waitCursor); break; case CursorEnum::CURSOR_WHATS_THIS: widget->setCursor(this->whatsThisCursor); break; } } /** * Load an image and create a cursor using the image. * * @param filename * Name of file containing the image. * @param hotSpotX * Hot spot (location in cursor reported to GUI) * @param hotSpotY * Hot spot (location in cursor reported to GUI) * @param cursorShapeIfImageLoadingFails * Cursor shape used if loading the image fails. * @return * Cursor that was created. */ QCursor CursorManager::loadCursor(const QString& filename, const int hotSpotX, const int hotSpotY, const Qt::CursorShape& cursorShapeIfImageLoadingFails) const { QPixmap cursorPixmap; if (WuQtUtilities::loadPixmap(filename, cursorPixmap)) { QCursor cursor(cursorPixmap, hotSpotX, hotSpotY); return cursor; } return QCursor(cursorShapeIfImageLoadingFails); } workbench-1.1.1/src/GuiQt/CursorManager.h000066400000000000000000000037761255417355300202640ustar00rootroot00000000000000#ifndef __CURSOR_MANAGER__H_ #define __CURSOR_MANAGER__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretObject.h" #include "CursorEnum.h" class QWidget; namespace caret { class CursorManager : public CaretObject { public: CursorManager(); virtual ~CursorManager(); void setCursorForWidget(QWidget* widget, const CursorEnum::Enum cursor) const; private: CursorManager(const CursorManager&); CursorManager& operator=(const CursorManager&); QCursor loadCursor(const QString& filename, const int hotSpotX, const int hotSpotY, const Qt::CursorShape& cursorShapeIfImageLoadingFails) const; QCursor arrowCursor; QCursor crossCursor; QCursor defaultCursor; QCursor penCursor; QCursor pointingHandCursor; QCursor waitCursor; QCursor whatsThisCursor; }; #ifdef __CURSOR_MANAGER_DECLARE__ // #endif // __CURSOR_MANAGER_DECLARE__ } // namespace #endif //__CURSOR_MANAGER__H_ workbench-1.1.1/src/GuiQt/CustomViewDialog.cxx000066400000000000000000001025101255417355300212760ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CUSTOM_VIEW_DIALOG_DECLARE__ #include "CustomViewDialog.h" #undef __CUSTOM_VIEW_DIALOG_DECLARE__ #include #include #include #include #include #include #include #include #include #include #include "BrainBrowserWindow.h" #include "BrainBrowserWindowComboBox.h" #include "BrowserTabContent.h" #include "CaretAssert.h" #include "CaretPreferences.h" #include "EventBrowserWindowGraphicsRedrawn.h" #include "EventGraphicsUpdateOneWindow.h" #include "EventManager.h" #include "GuiManager.h" #include "Matrix4x4.h" #include "Model.h" #include "ModelTransform.h" #include "SessionManager.h" #include "WuQDataEntryDialog.h" #include "WuQListWidget.h" #include "WuQMessageBox.h" #include "WuQtUtilities.h" #include "WuQWidgetObjectGroup.h" using namespace caret; /** * \class caret::CustomViewDialog * \brief Dialog for creation of custom views (orientations). * \ingroup GuiQt */ /** * Constructor. */ CustomViewDialog::CustomViewDialog(QWidget* parent) : WuQDialogNonModal("Custom Orientation", parent) { m_blockDialogUpdate = true; /* * Remove apply button by using an empty name */ setApplyButtonText(""); /* * Create the controls */ QWidget* customViewWidget = createCustomViewWidget(); QWidget* transformWidget = createTransformsWidget(); m_copyWidget = createCopyWidget(); /* * Layout for dialog */ QWidget* widget = new QWidget(); QHBoxLayout* layout = new QHBoxLayout(widget); layout->setSpacing(layout->spacing() / 2); layout->addWidget(transformWidget, 0, Qt::AlignVCenter); layout->addWidget(m_copyWidget, 0, Qt::AlignVCenter); layout->addWidget(customViewWidget, 0, Qt::AlignVCenter); setCentralWidget(widget, WuQDialog::SCROLL_AREA_NEVER); /* * No auto default button processing (Qt highlights button) */ disableAutoDefaultForAllPushButtons(); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_BROWSER_WINDOW_GRAPHICS_HAVE_BEEN_REDRAWN); setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); m_blockDialogUpdate = false; } /** * Destructor. */ CustomViewDialog::~CustomViewDialog() { EventManager::get()->removeAllEventsFromListener(this); } /** * @return The Custom View widget. */ QWidget* CustomViewDialog::createCustomViewWidget() { /* * View selection list widget */ m_customViewListWidget = new WuQListWidget(); m_customViewListWidget->setFixedHeight(100); m_customViewListWidget->setFixedWidth(140); m_customViewListWidget->setSelectionMode(QListWidget::SingleSelection); QObject::connect(m_customViewListWidget, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(customViewSelected())); QObject::connect(m_customViewListWidget, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(customViewSelectedAndApplied())); m_newCustomViewPushButton = new QPushButton("New..."); WuQtUtilities::setWordWrappedToolTip(m_newCustomViewPushButton, "Create a new Custom View by entering its name. The view will use the current transformation values."); QObject::connect(m_newCustomViewPushButton, SIGNAL(clicked()), this, SLOT(newCustomViewPushButtonClicked())); m_deleteCustomViewPushButton = new QPushButton("Delete..."); WuQtUtilities::setWordWrappedToolTip(m_deleteCustomViewPushButton, "Delete the selected Custom View."); QObject::connect(m_deleteCustomViewPushButton, SIGNAL(clicked()), this, SLOT(deleteCustomViewPushButtonClicked())); WuQtUtilities::matchWidgetWidths(m_newCustomViewPushButton, m_deleteCustomViewPushButton); QGroupBox* groupBox = new QGroupBox("Custom Orientation"); QVBoxLayout* layout = new QVBoxLayout(groupBox); WuQtUtilities::setLayoutSpacingAndMargins(layout, 4, 2); layout->addWidget(m_customViewListWidget, 100, Qt::AlignHCenter); layout->addWidget(m_newCustomViewPushButton, 0, Qt::AlignHCenter); layout->addWidget(m_deleteCustomViewPushButton, 0, Qt::AlignHCenter); layout->addStretch(); groupBox->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); return groupBox; } /** * Called when a custom view is selected. */ void CustomViewDialog::customViewSelected() { } /** * Called when a custom view name is double-clicked. */ void CustomViewDialog::customViewSelectedAndApplied() { copyToTransformPushButtonClicked(); } /** * @return Name of selected custom view.. */ AString CustomViewDialog::getSelectedCustomViewName() { AString viewName; QListWidgetItem* lwi = m_customViewListWidget->currentItem(); if (lwi != NULL) { viewName = lwi->text(); } return viewName; } /** * Load content of custom view list widget. * * @param selectedName * If not empty, this name will be selected. */ void CustomViewDialog::loadCustomViewListWidget(const AString& selectedName) { /* * Cannot use getSelectedUserView() since the item returned may be invalid * if items have been removed. */ QString previousViewName = getSelectedCustomViewName(); if (selectedName.isEmpty() == false) { previousViewName = selectedName; } int defaultSelectedIndex = m_customViewListWidget->currentRow(); m_customViewListWidget->clear(); CaretPreferences* prefs = getCaretPreferences(); prefs->readCustomViews(); const std::vector > customViewNamesAndComments = prefs->getCustomViewNamesAndComments(); const int32_t numViews = static_cast(customViewNamesAndComments.size()); for (int32_t i = 0; i < numViews; i++) { const AString viewName = customViewNamesAndComments[i].first; const AString comment = customViewNamesAndComments[i].second; if (viewName == previousViewName) { defaultSelectedIndex = i; } QListWidgetItem* lwi = new QListWidgetItem(viewName); if (comment.isEmpty() == false) { lwi->setToolTip(WuQtUtilities::createWordWrappedToolTipText(comment)); } m_customViewListWidget->addItem(lwi); } if (defaultSelectedIndex >= numViews) { defaultSelectedIndex = numViews - 1; } if (defaultSelectedIndex < 0) { defaultSelectedIndex = 0; } if ((defaultSelectedIndex >= 0) && (defaultSelectedIndex < m_customViewListWidget->count())) { m_customViewListWidget->setCurrentRow(defaultSelectedIndex); customViewSelected(); m_customViewListWidget->scrollToItem(m_customViewListWidget->currentItem()); } const bool haveViews = (numViews > 0); m_copyWidget->setEnabled(haveViews); const bool haveSelectedView = (getSelectedCustomViewName().isEmpty() == false); m_deleteCustomViewPushButton->setEnabled(haveSelectedView); } /** * @return Create and return the copy buttons widget. */ QWidget* CustomViewDialog::createCopyWidget() { QPushButton* copyToCustomViewPushButton = new QPushButton("Copy -->"); WuQtUtilities::setWordWrappedToolTip(copyToCustomViewPushButton, "Copy the Transform's values into the selected Custom Orientation."); QObject::connect(copyToCustomViewPushButton, SIGNAL(clicked()), this, SLOT(copyToCustomViewPushButtonClicked())); QPushButton* copyToTransformPushButton = new QPushButton("<-- Load"); WuQtUtilities::setWordWrappedToolTip(copyToTransformPushButton, "Load the Custom Orientation's transform values into the Transform."); QObject::connect(copyToTransformPushButton, SIGNAL(clicked()), this, SLOT(copyToTransformPushButtonClicked())); QWidget* widget = new QWidget(); QVBoxLayout* layout = new QVBoxLayout(widget); layout->addWidget(copyToCustomViewPushButton); layout->addWidget(copyToTransformPushButton); widget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); return widget; } /** * Called when Copy To Custom View push buttton is clicked. */ void CustomViewDialog::copyToCustomViewPushButtonClicked() { CaretPreferences* prefs = getCaretPreferences(); ModelTransform modelTransform; if (prefs->getCustomView(getSelectedCustomViewName(), modelTransform)) { moveTransformToCustomView(modelTransform); prefs->addOrReplaceCustomView(modelTransform); } } /** * Move the transform values to the given user view. * * @param userView * User View into which transform values are moved. */ void CustomViewDialog::moveTransformToCustomView(ModelTransform& modelTransform) { double panX, panY, panZ, rotX, rotY, rotZ, obRotX, obRotY, obRotZ, zoom; getTransformationControlValues(panX, panY, panZ, rotX, rotY, rotZ, obRotX, obRotY, obRotZ, zoom); Matrix4x4 rotationMatrix; rotationMatrix.setRotation(rotX, rotY, rotZ); float rotationMatrixArray[4][4]; rotationMatrix.getMatrix(rotationMatrixArray); Matrix4x4 obliqueRotationMatrix; obliqueRotationMatrix.setRotation(obRotX, obRotY, obRotZ); float obliqueRotationMatrixArray[4][4]; obliqueRotationMatrix.getMatrix(obliqueRotationMatrixArray); modelTransform.setPanningRotationMatrixAndZoom(panX, panY, panZ, rotationMatrixArray, obliqueRotationMatrixArray, zoom); } /** * Called when Move To Transform push buttton is clicked. */ void CustomViewDialog::copyToTransformPushButtonClicked() { CaretPreferences* prefs = getCaretPreferences(); const AString customViewName = getSelectedCustomViewName(); ModelTransform modelTransform; if (prefs->getCustomView(customViewName, modelTransform)) { float panX, panY, panZ, rotationMatrixArray[4][4], obliqueRotationMatrixArray[4][4], zoom; modelTransform.getPanningRotationMatrixAndZoom(panX, panY, panZ, rotationMatrixArray, obliqueRotationMatrixArray, zoom); Matrix4x4 rotationMatrix; rotationMatrix.setMatrix(rotationMatrixArray); double rotX, rotY, rotZ; rotationMatrix.getRotation(rotX, rotY, rotZ); Matrix4x4 obliqueRotationMatrix; obliqueRotationMatrix.setMatrix(obliqueRotationMatrixArray); double obRotX, obRotY, obRotZ; obliqueRotationMatrix.getRotation(obRotX, obRotY, obRotZ); setTransformationControlValues(panX, panY, panZ, rotX, rotY, rotZ, obRotX, obRotY, obRotZ, zoom); transformValueChanged(); } } /** * @return The Transform widget. */ QWidget* CustomViewDialog::createTransformsWidget() { const int spinBoxWidth = 90; /* * Window number */ QLabel* windowLabel = new QLabel("Workbench Window: "); m_browserWindowComboBox = new BrainBrowserWindowComboBox(this); m_browserWindowComboBox->getWidget()->setFixedWidth(spinBoxWidth); QObject::connect(m_browserWindowComboBox, SIGNAL(browserWindowSelected(BrainBrowserWindow*)), this, SLOT(browserWindowComboBoxValueChanged(BrainBrowserWindow*))); /* * Panning */ const double panStep = 1.0; QLabel* panLabel = new QLabel("Pan (X,Y):"); m_xPanDoubleSpinBox = new QDoubleSpinBox; m_xPanDoubleSpinBox->setMinimum(-100000.0); m_xPanDoubleSpinBox->setMaximum( 100000.0); m_xPanDoubleSpinBox->setSingleStep(panStep); m_xPanDoubleSpinBox->setDecimals(2); m_xPanDoubleSpinBox->setFixedWidth(spinBoxWidth); QObject::connect(m_xPanDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(transformValueChanged())); m_yPanDoubleSpinBox = new QDoubleSpinBox; m_yPanDoubleSpinBox->setMinimum(-100000.0); m_yPanDoubleSpinBox->setMaximum( 100000.0); m_yPanDoubleSpinBox->setSingleStep(panStep); m_yPanDoubleSpinBox->setDecimals(2); m_yPanDoubleSpinBox->setFixedWidth(spinBoxWidth); QObject::connect(m_yPanDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(transformValueChanged())); m_zPanDoubleSpinBox = new QDoubleSpinBox; m_zPanDoubleSpinBox->setMinimum(-100000.0); m_zPanDoubleSpinBox->setMaximum( 100000.0); m_zPanDoubleSpinBox->setSingleStep(panStep); m_zPanDoubleSpinBox->setDecimals(2); m_zPanDoubleSpinBox->setFixedWidth(spinBoxWidth); QObject::connect(m_zPanDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(transformValueChanged())); /* * Rotation */ const double rotationMinimum = -360.0; const double rotationMaximum = 360.0; const double rotateStep = 1.0; QLabel* rotateLabel = new QLabel("Rotate (X,Y,Z): "); m_xRotateDoubleSpinBox = new QDoubleSpinBox; m_xRotateDoubleSpinBox->setWrapping(true); m_xRotateDoubleSpinBox->setMinimum(rotationMinimum); m_xRotateDoubleSpinBox->setMaximum(rotationMaximum); m_xRotateDoubleSpinBox->setSingleStep(rotateStep); m_xRotateDoubleSpinBox->setDecimals(2); m_xRotateDoubleSpinBox->setFixedWidth(spinBoxWidth); QObject::connect(m_xRotateDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(transformValueChanged())); m_yRotateDoubleSpinBox = new QDoubleSpinBox; m_yRotateDoubleSpinBox->setWrapping(true); m_yRotateDoubleSpinBox->setMinimum(rotationMinimum); m_yRotateDoubleSpinBox->setMaximum(rotationMaximum); m_yRotateDoubleSpinBox->setSingleStep(rotateStep); m_yRotateDoubleSpinBox->setDecimals(2); m_yRotateDoubleSpinBox->setFixedWidth(spinBoxWidth); QObject::connect(m_yRotateDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(transformValueChanged())); m_zRotateDoubleSpinBox = new QDoubleSpinBox; m_zRotateDoubleSpinBox->setWrapping(true); m_zRotateDoubleSpinBox->setMinimum(rotationMinimum); m_zRotateDoubleSpinBox->setMaximum(rotationMaximum); m_zRotateDoubleSpinBox->setSingleStep(rotateStep); m_zRotateDoubleSpinBox->setDecimals(2); m_zRotateDoubleSpinBox->setFixedWidth(spinBoxWidth); QObject::connect(m_zRotateDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(transformValueChanged())); /* * Rotation */ QLabel* obliqueRotateLabel = new QLabel("Oblique Rotate (X,Y,Z): "); m_xObliqueRotateDoubleSpinBox = new QDoubleSpinBox; m_xObliqueRotateDoubleSpinBox->setWrapping(true); m_xObliqueRotateDoubleSpinBox->setMinimum(rotationMinimum); m_xObliqueRotateDoubleSpinBox->setMaximum(rotationMaximum); m_xObliqueRotateDoubleSpinBox->setSingleStep(rotateStep); m_xObliqueRotateDoubleSpinBox->setDecimals(2); m_xObliqueRotateDoubleSpinBox->setFixedWidth(spinBoxWidth); QObject::connect(m_xObliqueRotateDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(transformValueChanged())); m_yObliqueRotateDoubleSpinBox = new QDoubleSpinBox; m_yObliqueRotateDoubleSpinBox->setWrapping(true); m_yObliqueRotateDoubleSpinBox->setMinimum(rotationMinimum); m_yObliqueRotateDoubleSpinBox->setMaximum(rotationMaximum); m_yObliqueRotateDoubleSpinBox->setSingleStep(rotateStep); m_yObliqueRotateDoubleSpinBox->setDecimals(2); m_yObliqueRotateDoubleSpinBox->setFixedWidth(spinBoxWidth); QObject::connect(m_yObliqueRotateDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(transformValueChanged())); m_zObliqueRotateDoubleSpinBox = new QDoubleSpinBox; m_zObliqueRotateDoubleSpinBox->setWrapping(true); m_zObliqueRotateDoubleSpinBox->setMinimum(rotationMinimum); m_zObliqueRotateDoubleSpinBox->setMaximum(rotationMaximum); m_zObliqueRotateDoubleSpinBox->setSingleStep(rotateStep); m_zObliqueRotateDoubleSpinBox->setDecimals(2); m_zObliqueRotateDoubleSpinBox->setFixedWidth(spinBoxWidth); QObject::connect(m_zObliqueRotateDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(transformValueChanged())); /* * Zoom */ const double zoomStep = 0.1; QLabel* zoomLabel = new QLabel("Zoom: "); m_zoomDoubleSpinBox = new QDoubleSpinBox; m_zoomDoubleSpinBox->setMinimum(0.001); m_zoomDoubleSpinBox->setMaximum(10000.0); m_zoomDoubleSpinBox->setSingleStep(zoomStep); m_zoomDoubleSpinBox->setDecimals(3); m_zoomDoubleSpinBox->setFixedWidth(spinBoxWidth); QObject::connect(m_zoomDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(transformValueChanged())); m_transformWidgetGroup = new WuQWidgetObjectGroup(this); m_transformWidgetGroup->add(m_xPanDoubleSpinBox); m_transformWidgetGroup->add(m_yPanDoubleSpinBox); m_transformWidgetGroup->add(m_zPanDoubleSpinBox); m_transformWidgetGroup->add(m_xRotateDoubleSpinBox); m_transformWidgetGroup->add(m_yRotateDoubleSpinBox); m_transformWidgetGroup->add(m_zRotateDoubleSpinBox); m_transformWidgetGroup->add(m_xObliqueRotateDoubleSpinBox); m_transformWidgetGroup->add(m_yObliqueRotateDoubleSpinBox); m_transformWidgetGroup->add(m_zObliqueRotateDoubleSpinBox); m_transformWidgetGroup->add(m_zoomDoubleSpinBox); /*------------------------------------------------------------------------*/ /* * Layout widgets */ /* * Columns for grid layout */ int column = 0; const int COLUMN_LABEL = column++; const int COLUMN_X = column++; const int COLUMN_Y = column++; const int COLUMN_Z = column++; const int COLUMN_COUNT = column++; QGroupBox* groupBox = new QGroupBox("Transform"); QGridLayout* gridLayout = new QGridLayout(groupBox); WuQtUtilities::setLayoutSpacingAndMargins(gridLayout, 4, 4); int row = 0; gridLayout->addWidget(windowLabel, row, COLUMN_LABEL); gridLayout->addWidget(m_browserWindowComboBox->getWidget(), row, COLUMN_X); row++; gridLayout->addWidget(WuQtUtilities::createHorizontalLineWidget(), row, COLUMN_LABEL, 1, COLUMN_COUNT); gridLayout->setRowMinimumHeight(row, 10.0); row++; gridLayout->addWidget(panLabel, row, COLUMN_LABEL); gridLayout->addWidget(m_xPanDoubleSpinBox, row, COLUMN_X); gridLayout->addWidget(m_yPanDoubleSpinBox, row, COLUMN_Y); gridLayout->addWidget(m_zPanDoubleSpinBox, row, COLUMN_Z); row++; gridLayout->addWidget(rotateLabel, row, COLUMN_LABEL); gridLayout->addWidget(m_xRotateDoubleSpinBox, row, COLUMN_X); gridLayout->addWidget(m_yRotateDoubleSpinBox, row, COLUMN_Y); gridLayout->addWidget(m_zRotateDoubleSpinBox, row, COLUMN_Z); row++; gridLayout->addWidget(obliqueRotateLabel, row, COLUMN_LABEL); gridLayout->addWidget(m_xObliqueRotateDoubleSpinBox, row, COLUMN_X); gridLayout->addWidget(m_yObliqueRotateDoubleSpinBox, row, COLUMN_Y); gridLayout->addWidget(m_zObliqueRotateDoubleSpinBox, row, COLUMN_Z); row++; gridLayout->addWidget(zoomLabel, row, COLUMN_LABEL); gridLayout->addWidget(m_zoomDoubleSpinBox, row, COLUMN_X); row++; groupBox->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); return groupBox; } /** * Called when window number combo box value changed. */ void CustomViewDialog::browserWindowComboBoxValueChanged(BrainBrowserWindow* browserWindow) { int32_t windowIndex = -1; if (browserWindow != NULL) { windowIndex = browserWindow->getBrowserWindowIndex(); } updateContent(windowIndex); } /** * Called when a transform value is changed. */ void CustomViewDialog::transformValueChanged() { double panX, panY, panZ, rotX, rotY, rotZ, obRotX, obRotY, obRotZ, zoom; getTransformationControlValues(panX, panY, panZ, rotX, rotY, rotZ, obRotX, obRotY, obRotZ, zoom); BrainBrowserWindow* bbw = m_browserWindowComboBox->getSelectedBrowserWindow(); if (bbw != NULL) { BrowserTabContent* btc = bbw->getBrowserTabContent(); if (btc != NULL) { Model* model = btc->getModelForDisplay(); if (model != NULL) { Matrix4x4 rotationMatrix; rotationMatrix.setRotation(rotX, rotY, rotZ); float rotationMatrixArray[4][4]; rotationMatrix.getMatrix(rotationMatrixArray); Matrix4x4 obliqueRotationMatrix; obliqueRotationMatrix.setRotation(obRotX, obRotY, obRotZ); float obliqueRotationMatrixArray[4][4]; obliqueRotationMatrix.getMatrix(obliqueRotationMatrixArray); ModelTransform modelTransform; modelTransform.setPanningRotationMatrixAndZoom(panX, panY, panZ, rotationMatrixArray, obliqueRotationMatrixArray, zoom); btc->setTransformationsFromModelTransform(modelTransform); updateGraphicsWindow(); } } } } /** * Update the selected graphics window. */ void CustomViewDialog::updateGraphicsWindow() { BrainBrowserWindow* bbw = m_browserWindowComboBox->getSelectedBrowserWindow(); if (bbw != NULL) { const int32_t windowIndex = bbw->getBrowserWindowIndex(); m_blockDialogUpdate = true; EventManager::get()->sendEvent(EventGraphicsUpdateOneWindow(windowIndex).getPointer()); m_blockDialogUpdate = false; } } /** * Gets called when the dialog gains focus. */ void CustomViewDialog::focusGained() { updateDialog(); } /** * Update the dialog. */ void CustomViewDialog::updateDialog() { m_browserWindowComboBox->updateComboBox(); updateContent(m_browserWindowComboBox->getSelectedBrowserWindowIndex()); } /** * Update the dialog. */ void CustomViewDialog::updateContent(const int32_t browserWindowIndexIn) { /* * May get updates when graphics are redrawn by this dialog * and not doing this could result in infinite loop */ if (m_blockDialogUpdate) { return; } /* * Update, set, and validate selected browser window */ m_browserWindowComboBox->updateComboBox(); m_browserWindowComboBox->setBrowserWindowByIndex(browserWindowIndexIn); const int32_t browserWindowIndex = m_browserWindowComboBox->getSelectedBrowserWindowIndex(); BrainBrowserWindow* bbw = GuiManager::get()->getBrowserWindowByWindowIndex(browserWindowIndex); if (bbw != NULL) { BrowserTabContent* btc = bbw->getBrowserTabContent(); if (btc != NULL) { Model* model = btc->getModelForDisplay(); if (model != NULL) { const float* panning = btc->getTranslation(); const Matrix4x4 rotationMatrix = btc->getRotationMatrix(); const float zooming = btc->getScaling(); const Matrix4x4 obliqueRotationMatrix = btc->getObliqueVolumeRotationMatrix(); double rotX, rotY, rotZ; rotationMatrix.getRotation(rotX, rotY, rotZ); double obRotX, obRotY, obRotZ; obliqueRotationMatrix.getRotation(obRotX, obRotY, obRotZ); setTransformationControlValues(panning[0], panning[1], panning[2], rotX, rotY, rotZ, obRotX, obRotY, obRotZ, zooming); } } m_transformWidgetGroup->setEnabled(true); } else { m_transformWidgetGroup->setEnabled(false); } loadCustomViewListWidget(); } /** * Get the transformation values. * * @param panX * X pannning * @param panX * X pannning * @param rotX * X rotation * @param rotY * Y rotation * @param rotZ * Z rotation * @param zoom * Zooming */ void CustomViewDialog::getTransformationControlValues(double& panX, double& panY, double& panZ, double& rotX, double& rotY, double& rotZ, double& obRotX, double& obRotY, double& obRotZ, double& zoom) const { panX = m_xPanDoubleSpinBox->value(); panY = m_yPanDoubleSpinBox->value(); panZ = m_zPanDoubleSpinBox->value(); rotX = m_xRotateDoubleSpinBox->value(); rotY = m_yRotateDoubleSpinBox->value(); rotZ = m_zRotateDoubleSpinBox->value(); obRotX = m_xObliqueRotateDoubleSpinBox->value(); obRotY = m_yObliqueRotateDoubleSpinBox->value(); obRotZ = m_zObliqueRotateDoubleSpinBox->value(); zoom = m_zoomDoubleSpinBox->value(); } /** * Set the transformation values. * * @param panX * X pannning * @param panX * X pannning * @param rotX * X rotation * @param rotY * Y rotation * @param rotZ * Z rotation * @param zoom * Zooming */ void CustomViewDialog::setTransformationControlValues(const double panX, const double panY, const double panZ, const double rotX, const double rotY, const double rotZ, const double obRotX, const double obRotY, const double obRotZ, const double zoom) const { m_transformWidgetGroup->blockAllSignals(true); m_xPanDoubleSpinBox->setValue(panX); m_yPanDoubleSpinBox->setValue(panY); m_zPanDoubleSpinBox->setValue(panZ); m_xRotateDoubleSpinBox->setValue(rotX); m_yRotateDoubleSpinBox->setValue(rotY); m_zRotateDoubleSpinBox->setValue(rotZ); m_xObliqueRotateDoubleSpinBox->setValue(obRotX); m_yObliqueRotateDoubleSpinBox->setValue(obRotY); m_zObliqueRotateDoubleSpinBox->setValue(obRotZ); m_zoomDoubleSpinBox->setValue(zoom); m_transformWidgetGroup->blockAllSignals(false); } /** * @return The caret preferences. */ CaretPreferences* CustomViewDialog::getCaretPreferences() { CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); return prefs; } /** * Called when new custom view push button clicked. */ void CustomViewDialog::newCustomViewPushButtonClicked() { CaretPreferences* prefs = getCaretPreferences(); const std::vector existingCustomViewNames = prefs->getCustomViewNames(); bool createViewFlag = false; AString newViewName; AString newViewComment; bool exitLoop = false; while (exitLoop == false) { WuQDataEntryDialog ded("New Custom View", m_newCustomViewPushButton); QLineEdit* nameLineEdit = ded.addLineEditWidget("View Name"); QTextEdit* commentTextEdit = ded.addTextEdit("Comment", "", false); nameLineEdit->setFocus(); if (ded.exec() == WuQDataEntryDialog::Accepted) { newViewName = nameLineEdit->text().trimmed(); if (newViewName.isEmpty() == false) { newViewComment = commentTextEdit->toPlainText().trimmed(); /* * If custom view exists with name entered by user, * then warn the user. */ if (std::find(existingCustomViewNames.begin(), existingCustomViewNames.end(), newViewName) != existingCustomViewNames.end()) { const QString msg = ("View named \"" + newViewName + "\" already exits. Replace?"); if (WuQMessageBox::warningYesNo(m_newCustomViewPushButton, msg)) { exitLoop = true; createViewFlag = true; } } else { exitLoop = true; createViewFlag = true; } } } else { exitLoop = true; } } if (createViewFlag && (newViewName.isEmpty() == false)) { ModelTransform mt; mt.setName(newViewName); mt.setComment(newViewComment); moveTransformToCustomView(mt); prefs->addOrReplaceCustomView(mt); loadCustomViewListWidget(newViewName); } } /** * Called when save custom view push button clicked. */ void CustomViewDialog::deleteCustomViewPushButtonClicked() { const AString viewName = getSelectedCustomViewName(); if (viewName.isEmpty() == false) { const QString msg = ("Delete view named \"" + viewName + "\" ?"); if (WuQMessageBox::warningYesNo(m_newCustomViewPushButton, msg)) { CaretPreferences* prefs = getCaretPreferences(); prefs->removeCustomView(viewName); loadCustomViewListWidget(); } } } /** * Receive events from the event manager. * * @param event * Event sent by event manager. */ void CustomViewDialog::receiveEvent(Event* event) { if (event->getEventType() == EventTypeEnum::EVENT_BROWSER_WINDOW_GRAPHICS_HAVE_BEEN_REDRAWN) { EventBrowserWindowGraphicsRedrawn* redrawnEvent = dynamic_cast(event); CaretAssert(redrawnEvent); redrawnEvent->setEventProcessed(); const int32_t selectedBrowserWindowIndex = m_browserWindowComboBox->getSelectedBrowserWindowIndex(); if (redrawnEvent->getBrowserWindowIndex() == selectedBrowserWindowIndex) { updateContent(selectedBrowserWindowIndex); } } } workbench-1.1.1/src/GuiQt/CustomViewDialog.h000066400000000000000000000121761255417355300207330ustar00rootroot00000000000000#ifndef __CUSTOM_VIEW_DIALOG_H__ #define __CUSTOM_VIEW_DIALOG_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "EventListenerInterface.h" #include "WuQDialogNonModal.h" class QDoubleSpinBox; class QPushButton; namespace caret { class BrainBrowserWindow; class BrainBrowserWindowComboBox; class CaretPreferences; class ModelTransform; class WuQListWidget; class WuQWidgetObjectGroup; class CustomViewDialog : public WuQDialogNonModal, public EventListenerInterface { Q_OBJECT public: CustomViewDialog(QWidget* parent); virtual ~CustomViewDialog(); void updateDialog(); void updateContent(const int32_t browserWindowIndex); void receiveEvent(Event* event); private: CustomViewDialog(const CustomViewDialog&); CustomViewDialog& operator=(const CustomViewDialog&); private slots: void transformValueChanged(); void newCustomViewPushButtonClicked(); void deleteCustomViewPushButtonClicked(); void browserWindowComboBoxValueChanged(BrainBrowserWindow* browserWindow); void customViewSelected(); void customViewSelectedAndApplied(); void copyToCustomViewPushButtonClicked(); void copyToTransformPushButtonClicked(); public: // ADD_NEW_METHODS_HERE protected: void focusGained(); private: void loadCustomViewListWidget(const AString& selectedName = ""); void updateGraphicsWindow(); void getTransformationControlValues(double& panX, double& panY, double& panZ, double& rotX, double& rotY, double& rotZ, double& obRotX, double& obRotY, double& obRotZ, double& zoom) const; void setTransformationControlValues(const double panX, const double panY, const double panZ, const double rotX, const double rotY, const double rotZ, const double obRotX, const double obRotY, const double obRotZ, const double zoom) const; // ADD_NEW_MEMBERS_HERE CaretPreferences* getCaretPreferences(); //std::vector getAllCustomViewNames(); QWidget* createCustomViewWidget(); QWidget* createCopyWidget(); QWidget* createTransformsWidget(); //UserView* getSelectedUserView(); AString getSelectedCustomViewName(); void moveTransformToCustomView(ModelTransform& modelTransform); QWidget* m_copyWidget; QDoubleSpinBox* m_xPanDoubleSpinBox; QDoubleSpinBox* m_yPanDoubleSpinBox; QDoubleSpinBox* m_zPanDoubleSpinBox; QDoubleSpinBox* m_xRotateDoubleSpinBox; QDoubleSpinBox* m_yRotateDoubleSpinBox; QDoubleSpinBox* m_zRotateDoubleSpinBox; QDoubleSpinBox* m_xObliqueRotateDoubleSpinBox; QDoubleSpinBox* m_yObliqueRotateDoubleSpinBox; QDoubleSpinBox* m_zObliqueRotateDoubleSpinBox; QDoubleSpinBox* m_zoomDoubleSpinBox; WuQWidgetObjectGroup* m_transformWidgetGroup; BrainBrowserWindowComboBox* m_browserWindowComboBox; QPushButton* m_newCustomViewPushButton; QPushButton* m_deleteCustomViewPushButton; WuQListWidget* m_customViewListWidget; bool m_blockDialogUpdate; }; #ifdef __CUSTOM_VIEW_DIALOG_DECLARE__ // #endif // __CUSTOM_VIEW_DIALOG_DECLARE__ } // namespace #endif //__CUSTOM_VIEW_DIALOG_H__ workbench-1.1.1/src/GuiQt/DisplayGroupEnumComboBox.cxx000066400000000000000000000102131255417355300227470ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __DISPLAY_GROUP_ENUM_COMBO_BOX_DECLARE__ #include "DisplayGroupEnumComboBox.h" #undef __DISPLAY_GROUP_ENUM_COMBO_BOX_DECLARE__ using namespace caret; /** * \class caret::DisplayGroupEnumComboBox * \brief Combo box for selection of a display group. * \ingroup GuiQt * * Encapsulates a QComboBox for the selection of a * DisplayGroupEnum value. QComboBox is not extended * to prevent access to its methods that could cause * the selection to get messed up. */ /** * Constructor. * @param Parent * Parent object. */ DisplayGroupEnumComboBox::DisplayGroupEnumComboBox(QObject* parent) : WuQWidget(parent) { std::vector allDisplayGroups; DisplayGroupEnum::getAllEnums(allDisplayGroups); const int32_t numStructures = static_cast(allDisplayGroups.size()); this->displayGroupComboBox = new QComboBox(); for (int32_t i = 0; i < numStructures; i++) { this->displayGroupComboBox->addItem(DisplayGroupEnum::toGuiName(allDisplayGroups[i])); this->displayGroupComboBox->setItemData(i, DisplayGroupEnum::toIntegerCode(allDisplayGroups[i])); } QObject::connect(this->displayGroupComboBox, SIGNAL(activated(int)), this, SLOT(displayGroupComboBoxSelection(int))); } /** * Destructor. */ DisplayGroupEnumComboBox::~DisplayGroupEnumComboBox() { } /** * @return The selected display group. */ DisplayGroupEnum::Enum DisplayGroupEnumComboBox::getSelectedDisplayGroup() const { DisplayGroupEnum::Enum displayGroup = DisplayGroupEnum::getDefaultValue(); const int32_t indx = this->displayGroupComboBox->currentIndex(); if (indx >= 0) { const int32_t integerCode = this->displayGroupComboBox->itemData(indx).toInt(); displayGroup = DisplayGroupEnum::fromIntegerCode(integerCode, NULL); } return displayGroup; } /** * @return The widget (combo box) for adding to layout. */ QWidget* DisplayGroupEnumComboBox::getWidget() { return this->displayGroupComboBox; } /** * Set the display group. * @param displayGroup * New value for display group. */ void DisplayGroupEnumComboBox::setSelectedDisplayGroup(const DisplayGroupEnum::Enum displayGroup) { const int32_t displayGroupIntegerCode = DisplayGroupEnum::toIntegerCode(displayGroup); const int numStructures = this->displayGroupComboBox->count(); for (int32_t i = 0; i < numStructures; i++) { if (displayGroupIntegerCode == this->displayGroupComboBox->itemData(i).toInt()) { if (this->signalsBlocked()) { this->displayGroupComboBox->blockSignals(true); } this->displayGroupComboBox->setCurrentIndex(i); if (this->signalsBlocked()) { this->displayGroupComboBox->blockSignals(false); } break; } } } /** * Called when a display group is selected (receives signal) * @param indx * Index of selected item. */ void DisplayGroupEnumComboBox::displayGroupComboBoxSelection(int indx) { if (this->signalsBlocked() == false) { const int32_t integerCode = this->displayGroupComboBox->itemData(indx).toInt(); DisplayGroupEnum::Enum displayGroup = DisplayGroupEnum::fromIntegerCode(integerCode, NULL); emit displayGroupSelected(displayGroup); } } workbench-1.1.1/src/GuiQt/DisplayGroupEnumComboBox.h000066400000000000000000000037601255417355300224050ustar00rootroot00000000000000#ifndef __DISPLAY_GROUP_ENUM_COMBO_BOX__H_ #define __DISPLAY_GROUP_ENUM_COMBO_BOX__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "DisplayGroupEnum.h" #include "WuQWidget.h" class QComboBox; namespace caret { class DisplayGroupEnumComboBox : public WuQWidget { Q_OBJECT public: DisplayGroupEnumComboBox(QObject* parent); virtual ~DisplayGroupEnumComboBox(); DisplayGroupEnum::Enum getSelectedDisplayGroup() const; QWidget* getWidget(); public slots: void setSelectedDisplayGroup(const DisplayGroupEnum::Enum displayGroup); signals: void displayGroupSelected(const DisplayGroupEnum::Enum); private slots: void displayGroupComboBoxSelection(int); private: DisplayGroupEnumComboBox(const DisplayGroupEnumComboBox&); DisplayGroupEnumComboBox& operator=(const DisplayGroupEnumComboBox&); QComboBox* displayGroupComboBox; }; #ifdef __DISPLAY_GROUP_ENUM_COMBO_BOX_DECLARE__ // #endif // __DISPLAY_GROUP_ENUM_COMBO_BOX_DECLARE__ } // namespace #endif //__DISPLAY_GROUP_ENUM_COMBO_BOX__H_ workbench-1.1.1/src/GuiQt/EnumComboBoxTemplate.h000066400000000000000000000167201255417355300215360ustar00rootroot00000000000000#ifndef __ENUM_COMBOBOX_TEMPLATE__H_ #define __ENUM_COMBOBOX_TEMPLATE__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "AString.h" #include "WuQFactory.h" #include "WuQWidget.h" /** * \class caret::EnumComboBoxTemplate * \brief Create a combo box for a workbench enumerated type. * \ingroup GuiQt * * Create a combo box for a workbench enumerated type. Typically, * the enumerated types are created with wb_command: * wb_command.app/Contents/MacOS/wb_command -class-create-enum LabelDrawingTypeEnum 2 true * * This class is not a template class because QObject subclasses may * not be templates. See http://doc.trolltech.com/qq/qq15-academic.html * While the class cannot be a template, methods in the class be templates. * *

* How to Use This Class *

* Declare:
* EnumComboBoxTemplate* m_someTypeComboBox; * *

* Construct:
* m_someTypeComboBox = new EnumComboBoxTemplate(this);
* m_someTypeComboBox->setup();
* *

* Read:
* const SomeTypeEnum::Enum enumValue = m_someTypeComboBox->getSelectedItem(); * *

* Set:
* m_someTypeComboBox->setSelectedItem(enumValue); * *

* Get notified when user makes selection:
* See itemActivated() and itemChanged(). * */ namespace caret { class EnumComboBoxTemplate : public WuQWidget { Q_OBJECT public: /** * Constructor. * @param parent * Parent object. */ EnumComboBoxTemplate(QObject* parent) : WuQWidget(parent) { m_itemComboBox = WuQFactory::newComboBox(); QObject::connect(m_itemComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(itemComboBoxIndexChanged(int))); QObject::connect(m_itemComboBox, SIGNAL(activated(int)), this, SLOT(itemComboBoxActivated(int))); } /** * Destructor. */ virtual ~EnumComboBoxTemplate() { } /** * Setup the combo box. */ template void setup() { std::vector allEnums; CT::getAllEnums(allEnums); m_itemComboBox->blockSignals(true); m_itemComboBox->clear(); const int32_t numColors = static_cast(allEnums.size()); for (int32_t i = 0; i < numColors; i++) { const ET enumValue = allEnums[i]; const int32_t indx = m_itemComboBox->count(); const AString name = CT::toGuiName(enumValue); m_itemComboBox->addItem(name); m_itemComboBox->setItemData(indx, CT::toIntegerCode(enumValue)); } m_itemComboBox->blockSignals(false); } /** * Setup the combo box. */ template void setupWithItems(const std::vector& comboBoxItems) { m_itemComboBox->blockSignals(true); m_itemComboBox->clear(); const int32_t numColors = static_cast(comboBoxItems.size()); for (int32_t i = 0; i < numColors; i++) { const ET enumValue = comboBoxItems[i]; const int32_t indx = m_itemComboBox->count(); const AString name = CT::toGuiName(enumValue); m_itemComboBox->addItem(name); m_itemComboBox->setItemData(indx, CT::toIntegerCode(enumValue)); } m_itemComboBox->blockSignals(false); } /** * @return The selected item. */ template ET getSelectedItem() const { const int32_t indx = m_itemComboBox->currentIndex(); const int32_t integerCode = m_itemComboBox->itemData(indx).toInt(); ET item = CT::fromIntegerCode(integerCode, NULL); return item; } /** * Set the selected item. * @param item * New item for selection. */ template void setSelectedItem(const ET item) { const int32_t numItems = static_cast(m_itemComboBox->count()); for (int32_t i = 0; i < numItems; i++) { const int32_t integerCode = m_itemComboBox->itemData(i).toInt(); ET enumValue = CT::fromIntegerCode(integerCode, NULL); if (enumValue == item) { m_itemComboBox->blockSignals(true); m_itemComboBox->setCurrentIndex(i); m_itemComboBox->blockSignals(false); break; } } } /** * @return The actual widget. */ virtual QWidget* getWidget() { return m_itemComboBox; } /** * @return The actual combo box. */ QComboBox* getComboBox() { return m_itemComboBox; } signals: /** * This signal is sent when the user chooses an item in the combobox. * The item's index is passed. Note that this signal is sent even * when the choice is not changed. If you need to know when the * choice actually changes, use signal itemChanged(). */ void itemActivated(); /** * This signal is sent whenever the currentIndex in the * combobox changes either through user interaction or * programmatically. */ void itemChanged(); private slots: /** * Called when the user selects an item * @param indx * Index of item selected. */ void itemComboBoxIndexChanged(int) { emit itemChanged(); } /** * Called when the user selects an item * @param indx * Index of item selected. */ void itemComboBoxActivated(int) { emit itemActivated(); } private: EnumComboBoxTemplate(const EnumComboBoxTemplate&); EnumComboBoxTemplate& operator=(const EnumComboBoxTemplate&); private: QComboBox* m_itemComboBox; }; #ifdef __ENUM_COMBOBOX_TEMPLATE_DECLARE__ // #endif // __ENUM_COMBOBOX_TEMPLATE_DECLARE__ } // namespace #endif //__ENUM_COMBOBOX_TEMPLATE__H_ workbench-1.1.1/src/GuiQt/EventBrowserTabGetAllViewed.cxx000066400000000000000000000047631255417355300233750ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __EVENT_BROWSER_TAB_GET_ALL_VIEWED_DECLARE__ #include "EventBrowserTabGetAllViewed.h" #undef __EVENT_BROWSER_TAB_GET_ALL_VIEWED_DECLARE__ #include "BrowserTabContent.h" #include "CaretAssert.h" #include "EventTypeEnum.h" using namespace caret; /** * \class caret::EventBrowserTabGetAllViewed * \brief Event that gets all viewed browser tabs * \ingroup GuiQt * * For each window, it will add its selected browser tab. If * the window is in 'Tile Tabs' mode, all of its browser tabs * are added. */ /** * Constructor. */ EventBrowserTabGetAllViewed::EventBrowserTabGetAllViewed() : Event(EventTypeEnum::EVENT_BROWSER_TAB_GET_ALL_VIEWED) { } /** * Destructor. */ EventBrowserTabGetAllViewed::~EventBrowserTabGetAllViewed() { } /** * Add a viewed browser tab. * * @param browserTabContent * Browser tab that is added. */ void EventBrowserTabGetAllViewed::addViewedBrowserTab(BrowserTabContent* browserTabContent) { m_viewedBrowserTabs.push_back(browserTabContent); } /** * @return A vector containing the viewed browser tabs */ std::vector EventBrowserTabGetAllViewed::getViewedBrowserTabs() const { return m_viewedBrowserTabs; } /** * @return A vector containing the indices of the viewed browser tabs. */ std::vector EventBrowserTabGetAllViewed::getViewdedBrowserTabIndices() const { std::vector tabIndices; for (std::vector::const_iterator iter = m_viewedBrowserTabs.begin(); iter != m_viewedBrowserTabs.end(); iter++) { const BrowserTabContent* btc = *iter; tabIndices.push_back(btc->getTabNumber()); } return tabIndices; } workbench-1.1.1/src/GuiQt/EventBrowserTabGetAllViewed.h000066400000000000000000000037101255417355300230110ustar00rootroot00000000000000#ifndef __EVENT_BROWSER_TAB_GET_ALL_VIEWED_H__ #define __EVENT_BROWSER_TAB_GET_ALL_VIEWED_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "Event.h" namespace caret { class BrowserTabContent; class EventBrowserTabGetAllViewed : public Event { public: EventBrowserTabGetAllViewed(); virtual ~EventBrowserTabGetAllViewed(); void addViewedBrowserTab(BrowserTabContent* browserTabContent); std::vector getViewedBrowserTabs() const; std::vector getViewdedBrowserTabIndices() const; private: EventBrowserTabGetAllViewed(const EventBrowserTabGetAllViewed&); EventBrowserTabGetAllViewed& operator=(const EventBrowserTabGetAllViewed&); public: // ADD_NEW_METHODS_HERE private: // ADD_NEW_MEMBERS_HERE std::vector m_viewedBrowserTabs; }; #ifdef __EVENT_BROWSER_TAB_GET_ALL_VIEWED_DECLARE__ // #endif // __EVENT_BROWSER_TAB_GET_ALL_VIEWED_DECLARE__ } // namespace #endif //__EVENT_BROWSER_TAB_GET_ALL_VIEWED_H__ workbench-1.1.1/src/GuiQt/EventBrowserWindowContentGet.cxx000066400000000000000000000075461255417355300236760ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "EventBrowserWindowContentGet.h" using namespace caret; #include "CaretAssert.h" /** * \class caret::DisplayGroupEnumComboBox * \brief Combo box for selection of a display group. * \ingroup GuiQt */ /** * Constructor. * @param browserWindowIndex * Index of browser window. */ EventBrowserWindowContentGet::EventBrowserWindowContentGet(const int32_t browserWindowIndex) : Event(EventTypeEnum::EVENT_BROWSER_WINDOW_CONTENT_GET) { m_browserWindowIndex = browserWindowIndex; m_tabIndexForTileTabsHighlighting = -1; m_tileTabsConfiguration = NULL; m_selectedBrowserTabContent = NULL; } /* * Destructor. */ EventBrowserWindowContentGet::~EventBrowserWindowContentGet() { } /** * @return Get the browser window index. */ int32_t EventBrowserWindowContentGet::getBrowserWindowIndex() const { return m_browserWindowIndex; } /** * @return The number of items to draw. */ int32_t EventBrowserWindowContentGet::getNumberOfItemsToDraw() const { return this->browserTabContents.size(); } /** * Add tab content for drawing in a window. */ void EventBrowserWindowContentGet::addTabContentToDraw(BrowserTabContent* browserTabContent) { this->browserTabContents.push_back(browserTabContent); } /** * Get the tab content for drawing in a window. * * @param itemIndex * Index of the item to draw. * @return * Pointer to tab contents for the item index. */ BrowserTabContent* EventBrowserWindowContentGet::getTabContentToDraw(const int32_t itemIndex) { CaretAssertVectorIndex(this->browserTabContents, itemIndex); return this->browserTabContents[itemIndex]; } /** * Set index of tab for highlighting in tile tabs mode. * * @param tabIndex * Index of tab for highlighting. */ void EventBrowserWindowContentGet::setTabIndexForTileTabsHighlighting(const int32_t tabIndex) { m_tabIndexForTileTabsHighlighting = tabIndex; } /** * @return Index of tab for highlighting in Tile Tabs mode. */ int32_t EventBrowserWindowContentGet::getTabIndexForTileTabsHighlighting() const { return m_tabIndexForTileTabsHighlighting; } /** * @return The tile tabs configuration when more than one tab to draw. * May be NULL. */ TileTabsConfiguration* EventBrowserWindowContentGet::getTileTabsConfiguration() const { return m_tileTabsConfiguration; } /** * Set the tile tabs configuration. * * @param tileTabsConfiguration * New selected tile tabs configuration. */ void EventBrowserWindowContentGet::setTileTabsConfiguration(TileTabsConfiguration* tileTabsConfiguration) { m_tileTabsConfiguration = tileTabsConfiguration; } /** * @return The selected browser tab content. May be NULL. */ BrowserTabContent* EventBrowserWindowContentGet::getSelectedBrowserTabContent() { return m_selectedBrowserTabContent; } /** * Set the selected browser tab content. * * @param browserTabContent * The selected browser tab content. */ void EventBrowserWindowContentGet::setSelectedBrowserTabContent(BrowserTabContent* browserTabContent) { m_selectedBrowserTabContent = browserTabContent; } workbench-1.1.1/src/GuiQt/EventBrowserWindowContentGet.h000066400000000000000000000055001255417355300233070ustar00rootroot00000000000000#ifndef __EVENT_BROWSER_WINDOW_CONTENT_GET_H__ #define __EVENT_BROWSER_WINDOW_CONTENT_GET_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "Event.h" namespace caret { class BrowserTabContent; class Model; class TileTabsConfiguration; /// Get the content of a browser window class EventBrowserWindowContentGet : public Event { public: EventBrowserWindowContentGet(const int32_t browserWindowIndex); virtual ~EventBrowserWindowContentGet(); int32_t getBrowserWindowIndex() const; int32_t getNumberOfItemsToDraw() const; void addTabContentToDraw(BrowserTabContent* browserTabContent); BrowserTabContent* getTabContentToDraw(const int32_t itemIndex); void setTabIndexForTileTabsHighlighting(const int32_t tabIndex); int32_t getTabIndexForTileTabsHighlighting() const; TileTabsConfiguration* getTileTabsConfiguration() const; void setTileTabsConfiguration(TileTabsConfiguration* tileTabsConfiguration); BrowserTabContent* getSelectedBrowserTabContent(); void setSelectedBrowserTabContent(BrowserTabContent* browserTabContent); private: EventBrowserWindowContentGet(const EventBrowserWindowContentGet&); EventBrowserWindowContentGet& operator=(const EventBrowserWindowContentGet&); BrowserTabContent* m_selectedBrowserTabContent; /** index of browswer window */ int32_t m_browserWindowIndex; /** Tab content that are to be drawn in the window */ std::vector browserTabContents; /** Index of tab that is highlighted in Tile Tabs mode */ int32_t m_tabIndexForTileTabsHighlighting; /** Selected tile tabs configuration when more than one item to draw */ TileTabsConfiguration* m_tileTabsConfiguration; }; } // namespace #endif // __EVENT_BROWSER_WINDOW_CONTENT_GET_H__ workbench-1.1.1/src/GuiQt/EventBrowserWindowCreateTabs.cxx000066400000000000000000000031321255417355300236240ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __EVENT_BROWSER_WINDOW_CREATE_TABS_DECLARE__ #include "EventBrowserWindowCreateTabs.h" #undef __EVENT_BROWSER_WINDOW_CREATE_TABS_DECLARE__ using namespace caret; /** * \class caret::EventBrowserWindowCreateTabs * \brief If needed, create browser tabs after loading a spec or data files * \ingroup GuiQt */ /** * Constructor. * @param mode * Mode for tab creation. */ EventBrowserWindowCreateTabs::EventBrowserWindowCreateTabs(const Mode mode) : Event(EventTypeEnum::EVENT_BROWSER_WINDOW_CREATE_TABS), m_mode(mode) { } /** * Destructor. */ EventBrowserWindowCreateTabs::~EventBrowserWindowCreateTabs() { } /** * @return The mode. */ EventBrowserWindowCreateTabs::Mode EventBrowserWindowCreateTabs::getMode() const { return m_mode; } workbench-1.1.1/src/GuiQt/EventBrowserWindowCreateTabs.h000066400000000000000000000035031255417355300232530ustar00rootroot00000000000000#ifndef __EVENT_BROWSER_WINDOW_CREATE_TABS__H_ #define __EVENT_BROWSER_WINDOW_CREATE_TABS__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "Event.h" namespace caret { class EventBrowserWindowCreateTabs : public Event { public: enum Mode { MODE_LOADED_DATA_FILE, MODE_LOADED_SPEC_FILE }; EventBrowserWindowCreateTabs(const Mode mode); virtual ~EventBrowserWindowCreateTabs(); Mode getMode() const; private: EventBrowserWindowCreateTabs(const EventBrowserWindowCreateTabs&); EventBrowserWindowCreateTabs& operator=(const EventBrowserWindowCreateTabs&); public: // ADD_NEW_METHODS_HERE private: // ADD_NEW_MEMBERS_HERE const Mode m_mode; }; #ifdef __EVENT_BROWSER_WINDOW_CREATE_TABS_DECLARE__ // #endif // __EVENT_BROWSER_WINDOW_CREATE_TABS_DECLARE__ } // namespace #endif //__EVENT_BROWSER_WINDOW_CREATE_TABS__H_ workbench-1.1.1/src/GuiQt/EventBrowserWindowGraphicsRedrawn.cxx000066400000000000000000000037531255417355300247030ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __EVENT_BROWSER_WINDOW_GRAPHICS_REDRAWN_DECLARE__ #include "EventBrowserWindowGraphicsRedrawn.h" #undef __EVENT_BROWSER_WINDOW_GRAPHICS_REDRAWN_DECLARE__ #include "BrainBrowserWindow.h" #include "EventTypeEnum.h" using namespace caret; /** * \class caret::EventBrowserWindowGraphicsRedrawn * \brief Event issued when a browser windows graphics are redrawn. * \ingroup GuiQt */ /** * Constructor. */ EventBrowserWindowGraphicsRedrawn::EventBrowserWindowGraphicsRedrawn(BrainBrowserWindow* brainBrowserWindow) : Event(EventTypeEnum::EVENT_BROWSER_WINDOW_GRAPHICS_HAVE_BEEN_REDRAWN) { m_brainBrowserWindow = brainBrowserWindow; m_brainBrowserWindowIndex = m_brainBrowserWindow->getBrowserWindowIndex(); } /** * Destructor. */ EventBrowserWindowGraphicsRedrawn::~EventBrowserWindowGraphicsRedrawn() { } /** * @return Browser window that was redrawn. */ BrainBrowserWindow* EventBrowserWindowGraphicsRedrawn::getBrowserWindow() const { return m_brainBrowserWindow; } /** * @return Index of browser window that was redrawn. */ int32_t EventBrowserWindowGraphicsRedrawn::getBrowserWindowIndex() const { return m_brainBrowserWindowIndex; } workbench-1.1.1/src/GuiQt/EventBrowserWindowGraphicsRedrawn.h000066400000000000000000000037601255417355300243260ustar00rootroot00000000000000#ifndef __EVENT_BROWSER_WINDOW_GRAPHICS_REDRAWN_H__ #define __EVENT_BROWSER_WINDOW_GRAPHICS_REDRAWN_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "Event.h" namespace caret { class BrainBrowserWindow; class EventBrowserWindowGraphicsRedrawn : public Event { public: EventBrowserWindowGraphicsRedrawn(BrainBrowserWindow* brainBrowserWindow); virtual ~EventBrowserWindowGraphicsRedrawn(); BrainBrowserWindow* getBrowserWindow() const; int32_t getBrowserWindowIndex() const; private: EventBrowserWindowGraphicsRedrawn(const EventBrowserWindowGraphicsRedrawn&); EventBrowserWindowGraphicsRedrawn& operator=(const EventBrowserWindowGraphicsRedrawn&); public: // ADD_NEW_METHODS_HERE private: BrainBrowserWindow* m_brainBrowserWindow; int32_t m_brainBrowserWindowIndex; // ADD_NEW_MEMBERS_HERE }; #ifdef __EVENT_BROWSER_WINDOW_GRAPHICS_REDRAWN_DECLARE__ // #endif // __EVENT_BROWSER_WINDOW_GRAPHICS_REDRAWN_DECLARE__ } // namespace #endif //__EVENT_BROWSER_WINDOW_GRAPHICS_REDRAWN_H__ workbench-1.1.1/src/GuiQt/EventBrowserWindowNew.cxx000066400000000000000000000050711255417355300223440ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "EventBrowserWindowNew.h" using namespace caret; /** * \class caret::EventBrowserWindowNew * \brief Event Issued to create a new browser window * \ingroup GuiQt */ /** * Constructor. * @param parent * Widget used for window placement (may be NULL). * @param browserTabContent * Initial content for window (may be NULL). */ EventBrowserWindowNew::EventBrowserWindowNew(QWidget* parent, BrowserTabContent* browserTabContent) : Event(EventTypeEnum::EVENT_BROWSER_WINDOW_NEW) { this->parent = parent; this->browserTabContent = browserTabContent; this->browserWindowCreated = NULL; } /** * Constructor. */ EventBrowserWindowNew::EventBrowserWindowNew() : Event(EventTypeEnum::EVENT_BROWSER_WINDOW_NEW) { this->browserTabContent = NULL; } /* * Destructor. */ EventBrowserWindowNew::~EventBrowserWindowNew() { } /** * @return Parent for placement of new window (my be NULL). */ QWidget* EventBrowserWindowNew::getParent() { return this->parent; } /** * Returns the tab content for the window. If the * returned value is NULL, then a new tab should be * created. * * @return Returns the browser tab content. */ BrowserTabContent* EventBrowserWindowNew::getBrowserTabContent() const { return this->browserTabContent; } /** * @return The browser window that was created. */ BrainBrowserWindow* EventBrowserWindowNew::getBrowserWindowCreated() const { return this->browserWindowCreated; } /** * Set the browser window that was created. * param browserWindowCreated * Browser window that was created. */ void EventBrowserWindowNew::setBrowserWindowCreated(BrainBrowserWindow* browserWindowCreated) { this->browserWindowCreated = browserWindowCreated; } workbench-1.1.1/src/GuiQt/EventBrowserWindowNew.h000066400000000000000000000041271255417355300217720ustar00rootroot00000000000000#ifndef __EVENT_BROWSER_WINDOW_NEW_H__ #define __EVENT_BROWSER_WINDOW_NEW_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "Event.h" class QWidget; namespace caret { class BrainBrowserWindow; class BrowserTabContent; /// Create a new browser window class EventBrowserWindowNew : public Event { public: EventBrowserWindowNew(); EventBrowserWindowNew(QWidget* parent, BrowserTabContent* browserTabContent); virtual ~EventBrowserWindowNew(); QWidget* getParent(); BrowserTabContent* getBrowserTabContent() const; BrainBrowserWindow* getBrowserWindowCreated() const; void setBrowserWindowCreated(BrainBrowserWindow* browserWindowCreated); private: EventBrowserWindowNew(const EventBrowserWindowNew&); EventBrowserWindowNew& operator=(const EventBrowserWindowNew&); /** Widget used for placement of new window */ QWidget* parent; /** If not NULL, contains tab for the new window */ BrowserTabContent* browserTabContent; /** Window that was created. */ BrainBrowserWindow* browserWindowCreated; }; } // namespace #endif // __EVENT_BROWSER_WINDOW_NEW_H__ workbench-1.1.1/src/GuiQt/EventGetOrSetUserInputModeProcessor.cxx000066400000000000000000000067031255417355300251420ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "EventGetOrSetUserInputModeProcessor.h" using namespace caret; /** * \class caret::EventGetOrSetUserInputModeProcessor * \brief Event that acts as a getter/setting for user input mode. * \ingroup GuiQt */ /** * Constructor for SETTING the user input mode. * * @param windowIndex * Index of the window for the user input mode. * @param userInputMode * The requested input mode. */ EventGetOrSetUserInputModeProcessor::EventGetOrSetUserInputModeProcessor(const int32_t windowIndex, const UserInputModeAbstract::UserInputMode userInputMode) : Event(EventTypeEnum::EVENT_GET_OR_SET_USER_INPUT_MODE) { this->userInputProcessor = NULL; this->userInputMode = userInputMode; this->windowIndex = windowIndex; this->modeGetOrSet = SET; } /** * Constructor for GETTING the user input mode. * * @param windowIndex * Index of the window for the user input mode. */ EventGetOrSetUserInputModeProcessor::EventGetOrSetUserInputModeProcessor(const int32_t windowIndex) : Event(EventTypeEnum::EVENT_GET_OR_SET_USER_INPUT_MODE) { this->userInputProcessor = NULL; this->userInputMode = UserInputModeAbstract::INVALID; this->windowIndex = windowIndex; this->modeGetOrSet = GET; } /* * Destructor. */ EventGetOrSetUserInputModeProcessor::~EventGetOrSetUserInputModeProcessor() { } /** * @return The window index. */ int32_t EventGetOrSetUserInputModeProcessor::getWindowIndex() const { return this->windowIndex; } /** * @return The requested input mode. */ UserInputModeAbstract::UserInputMode EventGetOrSetUserInputModeProcessor::getUserInputMode() const { return this->userInputMode; } /** * Set the user input processor which is called when GETTING. * @param userInputProcessor * Value of current input processor. */ void EventGetOrSetUserInputModeProcessor::setUserInputProcessor(UserInputModeAbstract* userInputProcessor) { this->userInputProcessor = userInputProcessor; this->userInputMode = this->userInputProcessor->getUserInputMode(); } /** * @return The user input processor valid only when GETTING. */ UserInputModeAbstract* EventGetOrSetUserInputModeProcessor::getUserInputProcessor() { return this->userInputProcessor; } /** * @return true if this event is GETTING the user input mode. */ bool EventGetOrSetUserInputModeProcessor::isGetUserInputMode() const { return (this->modeGetOrSet == GET); } /** * @return true if this event is SETTING the user input mode. */ bool EventGetOrSetUserInputModeProcessor::isSetUserInputMode() const { return (this->modeGetOrSet == SET); } workbench-1.1.1/src/GuiQt/EventGetOrSetUserInputModeProcessor.h000066400000000000000000000051401255417355300245610ustar00rootroot00000000000000#ifndef __EVENT_GET_OR_SET_USER_INPUT_MODE_PROCESSOR_H__ #define __EVENT_GET_OR_SET_USER_INPUT_MODE_PROCESSOR_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "Event.h" #include "UserInputModeAbstract.h" namespace caret { /// Event for getting or setting the user input mode processor class EventGetOrSetUserInputModeProcessor : public Event { public: EventGetOrSetUserInputModeProcessor(const int32_t windowIndex, const UserInputModeAbstract::UserInputMode userInputMode); EventGetOrSetUserInputModeProcessor(const int32_t windowIndex); virtual ~EventGetOrSetUserInputModeProcessor(); bool isGetUserInputMode() const; bool isSetUserInputMode() const; int32_t getWindowIndex() const; UserInputModeAbstract::UserInputMode getUserInputMode() const; void setUserInputProcessor(UserInputModeAbstract* userInputProcessor); UserInputModeAbstract* getUserInputProcessor(); private: enum MODE_GET_OR_SET { GET, SET }; EventGetOrSetUserInputModeProcessor(const EventGetOrSetUserInputModeProcessor&); EventGetOrSetUserInputModeProcessor& operator=(const EventGetOrSetUserInputModeProcessor&); /** Is set when GETTING input mode */ UserInputModeAbstract* userInputProcessor; /** Requested input mode for SETTING and set when GETTING*/ UserInputModeAbstract::UserInputMode userInputMode; /** index of window for update */ int32_t windowIndex; /** getting or setting */ MODE_GET_OR_SET modeGetOrSet; }; } // namespace #endif // __EVENT_GET_OR_SET_USER_INPUT_MODE_PROCESSOR_H__ workbench-1.1.1/src/GuiQt/EventGraphicsUpdateAllWindows.cxx000066400000000000000000000033341255417355300237660ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "EventGraphicsUpdateAllWindows.h" using namespace caret; /** * \class caret::EventGraphicsUpdateAllWindows * \brief Event for updating all Window gui elements. * \ingroup GuiQt */ /** * Constructor. * @param doRepaint * If true, a repaint is performed and this event does not * return until painting is complete. If false, an update * is performed which schedules a repaint but does not dictate * when the repaint is performed. */ EventGraphicsUpdateAllWindows::EventGraphicsUpdateAllWindows(const bool doRepaint) : Event(EventTypeEnum::EVENT_GRAPHICS_UPDATE_ALL_WINDOWS) { this->doRepaint = doRepaint; } /* * Destructor. */ EventGraphicsUpdateAllWindows::~EventGraphicsUpdateAllWindows() { } /** * @return Indicates a repaint (instead of updates) is * to be performed. */ bool EventGraphicsUpdateAllWindows::isRepaint() const { return this->doRepaint; } workbench-1.1.1/src/GuiQt/EventGraphicsUpdateAllWindows.h000066400000000000000000000030461255417355300234130ustar00rootroot00000000000000#ifndef __EVENT_GRAPHICS_UPDATE_ALL_WINDOWS_H__ #define __EVENT_GRAPHICS_UPDATE_ALL_WINDOWS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "Event.h" namespace caret { /// Event for updating all graphics windows. class EventGraphicsUpdateAllWindows : public Event { public: EventGraphicsUpdateAllWindows(const bool doRepaint = false); virtual ~EventGraphicsUpdateAllWindows(); bool isRepaint() const; private: EventGraphicsUpdateAllWindows(const EventGraphicsUpdateAllWindows&); EventGraphicsUpdateAllWindows& operator=(const EventGraphicsUpdateAllWindows&); bool doRepaint; }; } // namespace #endif // __EVENT_GRAPHICS_UPDATE_ALL_WINDOWS_H__ workbench-1.1.1/src/GuiQt/EventGraphicsUpdateOneWindow.cxx000066400000000000000000000024571255417355300236210ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "EventGraphicsUpdateOneWindow.h" using namespace caret; /** * \class caret::EventGraphicsUpdateOneWindow * \brief Event for updating a single window * \ingroup GuiQt */ /** * Constructor. */ EventGraphicsUpdateOneWindow::EventGraphicsUpdateOneWindow(const int32_t windowIndex) : Event(EventTypeEnum::EVENT_GRAPHICS_UPDATE_ONE_WINDOW) { this->windowIndex = windowIndex; } /* * Destructor. */ EventGraphicsUpdateOneWindow::~EventGraphicsUpdateOneWindow() { } workbench-1.1.1/src/GuiQt/EventGraphicsUpdateOneWindow.h000066400000000000000000000032561255417355300232440ustar00rootroot00000000000000#ifndef __EVENT_GRAPHICS_UPDATE_ONE_WINDOW_H__ #define __EVENT_GRAPHICS_UPDATE_ONE_WINDOW_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "Event.h" namespace caret { /// Event for updating graphics in one window. class EventGraphicsUpdateOneWindow : public Event { public: EventGraphicsUpdateOneWindow(const int32_t windowIndex); virtual ~EventGraphicsUpdateOneWindow(); /// get the index of the window that is to be updated. int32_t getWindowIndex() const { return this->windowIndex; } private: EventGraphicsUpdateOneWindow(const EventGraphicsUpdateOneWindow&); EventGraphicsUpdateOneWindow& operator=(const EventGraphicsUpdateOneWindow&); /** index of window for update */ int32_t windowIndex; }; } // namespace #endif // __EVENT_GRAPHICS_UPDATE_ONE_WINDOW_H__ workbench-1.1.1/src/GuiQt/EventHelpViewerDisplay.cxx000066400000000000000000000036431255417355300224620ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __EVENT_HELP_VIEWER_DISPLAY_DECLARE__ #include "EventHelpViewerDisplay.h" #undef __EVENT_HELP_VIEWER_DISPLAY_DECLARE__ #include "CaretAssert.h" #include "EventTypeEnum.h" using namespace caret; /** * \class caret::EventHelpViewerDisplay * \brief Display the topic in the help viewer. * \ingroup GuiQt */ /** * Constructor displays help dialog with default topic. */ EventHelpViewerDisplay::EventHelpViewerDisplay(BrainBrowserWindow* brainBrowserWindow) : Event(EventTypeEnum::EVENT_HELP_VIEWER_DISPLAY) { m_brainBrowserWindow = brainBrowserWindow; m_helpPageName = ""; } /** * Destructor. */ EventHelpViewerDisplay::~EventHelpViewerDisplay() { } /** * @return Brain browser window on which a new help viewer dialog is displayed * May be NULL in which case any browser window should be used as parent. */ BrainBrowserWindow* EventHelpViewerDisplay::getBrainBrowserWindow() const { return m_brainBrowserWindow; } /** * @return Name of page for display (may be empty string). */ AString EventHelpViewerDisplay::getHelpPageName() const { return m_helpPageName; } workbench-1.1.1/src/GuiQt/EventHelpViewerDisplay.h000066400000000000000000000034721255417355300221070ustar00rootroot00000000000000#ifndef __EVENT_HELP_VIEWER_DISPLAY_H__ #define __EVENT_HELP_VIEWER_DISPLAY_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "Event.h" namespace caret { class BrainBrowserWindow; class EventHelpViewerDisplay : public Event { public: EventHelpViewerDisplay(BrainBrowserWindow* brainBrowserWindow); virtual ~EventHelpViewerDisplay(); BrainBrowserWindow* getBrainBrowserWindow() const; AString getHelpPageName() const; // ADD_NEW_METHODS_HERE private: EventHelpViewerDisplay(const EventHelpViewerDisplay&); EventHelpViewerDisplay& operator=(const EventHelpViewerDisplay&); BrainBrowserWindow* m_brainBrowserWindow; AString m_helpPageName; // ADD_NEW_MEMBERS_HERE }; #ifdef __EVENT_HELP_VIEWER_DISPLAY_DECLARE__ // #endif // __EVENT_HELP_VIEWER_DISPLAY_DECLARE__ } // namespace #endif //__EVENT_HELP_VIEWER_DISPLAY_H__ workbench-1.1.1/src/GuiQt/EventImageCapture.cxx000066400000000000000000000070211255417355300214220ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __EVENT_IMAGE_CAPTURE_DECLARE__ #include "EventImageCapture.h" #undef __EVENT_IMAGE_CAPTURE_DECLARE__ #include "CaretAssert.h" #include "EventTypeEnum.h" using namespace caret; /** * \class caret::EventImageCapture * \brief Event for capturing images. * \ingroup GuiQt */ /** * Constructor for capturing image of window without any resizing. * * @param browserWindowIndex * The browser window index. */ EventImageCapture::EventImageCapture(const int32_t browserWindowIndex) : Event(EventTypeEnum::EVENT_IMAGE_CAPTURE), m_browserWindowIndex(browserWindowIndex), m_imageSizeX(0), m_imageSizeY(0) { } /** * Constructor for capturing image of window with the given sizing. If * the X & Y sizes are both zero, the no image resizing is performed. * * @param browserWindowIndex * The browser window index. * @param imageSizeX * X-Size for the captured image. * @param imageSizeY * Y-Size for the captured image. */ EventImageCapture::EventImageCapture(const int32_t browserWindowIndex, const int32_t imageSizeX, const int32_t imageSizeY) : Event(EventTypeEnum::EVENT_IMAGE_CAPTURE), m_browserWindowIndex(browserWindowIndex), m_imageSizeX(imageSizeX), m_imageSizeY(imageSizeY) { m_backgroundColor[0] = 0; m_backgroundColor[1] = 0; m_backgroundColor[2] = 0; } /** * Destructor. */ EventImageCapture::~EventImageCapture() { } /** * @return The browser window index. */ int32_t EventImageCapture::getBrowserWindowIndex() const { return m_browserWindowIndex; } /** * @return The image X size. */ int32_t EventImageCapture::getImageSizeX() const { return m_imageSizeX; } /** * @return The image Y size. */ int32_t EventImageCapture::getImageSizeY() const { return m_imageSizeY; } /** * Get the graphics area's background color. * * @param backgroundColor * RGB components of background color [0, 255] */ void EventImageCapture::getBackgroundColor(uint8_t backgroundColor[3]) const { backgroundColor[0] = m_backgroundColor[0]; backgroundColor[1] = m_backgroundColor[1]; backgroundColor[2] = m_backgroundColor[2]; } /** * Set the graphics area's background color. * * @param backgroundColor * RGB components of background color [0, 255] */ void EventImageCapture::setBackgroundColor(const uint8_t backgroundColor[3]) { m_backgroundColor[0] = backgroundColor[0]; m_backgroundColor[1] = backgroundColor[1]; m_backgroundColor[2] = backgroundColor[2]; } /** * @return The captured image. */ QImage EventImageCapture::getImage() const { return m_image; } /** * Set the captured image. */ void EventImageCapture::setImage(const QImage& image) { m_image = image; } workbench-1.1.1/src/GuiQt/EventImageCapture.h000066400000000000000000000045031255417355300210510ustar00rootroot00000000000000#ifndef __EVENT_IMAGE_CAPTURE_H__ #define __EVENT_IMAGE_CAPTURE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "Event.h" namespace caret { class EventImageCapture : public Event { public: EventImageCapture(const int32_t browserWindowIndex, const int32_t imageSizeX, const int32_t imageSizeY); EventImageCapture(const int32_t browserWindowIndex); virtual ~EventImageCapture(); int32_t getBrowserWindowIndex() const; int32_t getImageSizeX() const; int32_t getImageSizeY() const; QImage getImage() const; void setImage(const QImage& image); void getBackgroundColor(uint8_t backgroundColor[3]) const; void setBackgroundColor(const uint8_t backgroundColor[3]); private: EventImageCapture(const EventImageCapture&); EventImageCapture& operator=(const EventImageCapture&); public: // ADD_NEW_METHODS_HERE private: // ADD_NEW_MEMBERS_HERE const int32_t m_browserWindowIndex; const int32_t m_imageSizeX; const int32_t m_imageSizeY; uint8_t m_backgroundColor[3]; QImage m_image; }; #ifdef __EVENT_IMAGE_CAPTURE_DECLARE__ // #endif // __EVENT_IMAGE_CAPTURE_DECLARE__ } // namespace #endif //__EVENT_IMAGE_CAPTURE_H__ workbench-1.1.1/src/GuiQt/EventMacDockMenuUpdate.cxx000066400000000000000000000026131255417355300223470ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __EVENT_MAC_DOCK_MENU_UPDATE_DECLARE__ #include "EventMacDockMenuUpdate.h" #undef __EVENT_MAC_DOCK_MENU_UPDATE_DECLARE__ #include "CaretAssert.h" #include "EventTypeEnum.h" using namespace caret; /** * \class caret::EventMacDockMenuUpdate * \brief * \ingroup GuiQt * * */ /** * Constructor. */ EventMacDockMenuUpdate::EventMacDockMenuUpdate() : Event(EventTypeEnum::EVENT_MAC_DOCK_MENU_UPDATE) { } /** * Destructor. */ EventMacDockMenuUpdate::~EventMacDockMenuUpdate() { } workbench-1.1.1/src/GuiQt/EventMacDockMenuUpdate.h000066400000000000000000000030441255417355300217730ustar00rootroot00000000000000#ifndef __EVENT_MAC_DOCK_MENU_UPDATE_H__ #define __EVENT_MAC_DOCK_MENU_UPDATE_H__ /*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "Event.h" namespace caret { class EventMacDockMenuUpdate : public Event { public: EventMacDockMenuUpdate(); virtual ~EventMacDockMenuUpdate(); // ADD_NEW_METHODS_HERE private: EventMacDockMenuUpdate(const EventMacDockMenuUpdate&); EventMacDockMenuUpdate& operator=(const EventMacDockMenuUpdate&); // ADD_NEW_MEMBERS_HERE }; #ifdef __EVENT_MAC_DOCK_MENU_UPDATE_DECLARE__ // #endif // __EVENT_MAC_DOCK_MENU_UPDATE_DECLARE__ } // namespace #endif //__EVENT_MAC_DOCK_MENU_UPDATE_H__ workbench-1.1.1/src/GuiQt/EventOperatingSystemRequestOpenDataFile.cxx000066400000000000000000000032561255417355300260040ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "EventOperatingSystemRequestOpenDataFile.h" using namespace caret; /** * \class caret::EventOperatingSystemRequestOpenDataFile * \brief Event for responding to an open file request from the operating system * \ingroup GuiQt * * On Macs, the QApplication instance may receive an open data file request * from the operating system. This event is used to by the QApplication * instance (actually MacApplication derived from QApplication) to route * the request for opening the file to the GUI. */ /** * Constructor. */ EventOperatingSystemRequestOpenDataFile::EventOperatingSystemRequestOpenDataFile(const AString& dataFileName) : Event(EventTypeEnum::EVENT_OPERATING_SYSTEM_REQUEST_OPEN_DATA_FILE), m_dataFileName(dataFileName) { } /* * Destructor. */ EventOperatingSystemRequestOpenDataFile::~EventOperatingSystemRequestOpenDataFile() { } workbench-1.1.1/src/GuiQt/EventOperatingSystemRequestOpenDataFile.h000066400000000000000000000032071255417355300254250ustar00rootroot00000000000000#ifndef __EVENT_OPERATING_SYSTEM_REQUEST_OPEN_DATA_FILE_H__ #define __EVENT_OPERATING_SYSTEM_REQUEST_OPEN_DATA_FILE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "Event.h" namespace caret { class EventOperatingSystemRequestOpenDataFile : public Event { public: EventOperatingSystemRequestOpenDataFile(const AString& dataFileName); virtual ~EventOperatingSystemRequestOpenDataFile(); AString getDataFileName() const { return m_dataFileName; } private: EventOperatingSystemRequestOpenDataFile(const EventOperatingSystemRequestOpenDataFile&); EventOperatingSystemRequestOpenDataFile& operator=(const EventOperatingSystemRequestOpenDataFile&); const AString m_dataFileName; }; } // namespace #endif // __EVENT_OPERATING_SYSTEM_REQUEST_OPEN_DATA_FILE_H__ workbench-1.1.1/src/GuiQt/EventOverlaySettingsEditorDialogRequest.cxx000066400000000000000000000041311255417355300260550ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "EventOverlaySettingsEditorDialogRequest.h" using namespace caret; /** * \class caret::EventOverlaySettingsEditorDialogRequest * \brief Event for creating overlay settings editor dialog * \ingroup GuiQt */ /** * Constructor. * @param browserWindowIndex * Index of browser window. * @param overlay * Overlay for map editor. * @param mapFile * Caret Mappable Data File. * @param mapIndex * Map index in mapFile. */ EventOverlaySettingsEditorDialogRequest::EventOverlaySettingsEditorDialogRequest(const Mode mode, const int32_t browserWindowIndex, Overlay* overlay, CaretMappableDataFile* mapFile, const int32_t mapIndex) : Event(EventTypeEnum::EVENT_OVERLAY_SETTINGS_EDITOR_SHOW), m_mode(mode) { m_browserWindowIndex = browserWindowIndex; m_overlay = overlay; m_mapFile = mapFile; m_mapIndex = mapIndex; } /* * Destructor. */ EventOverlaySettingsEditorDialogRequest::~EventOverlaySettingsEditorDialogRequest() { } workbench-1.1.1/src/GuiQt/EventOverlaySettingsEditorDialogRequest.h000066400000000000000000000065231255417355300255110ustar00rootroot00000000000000#ifndef __EVENT_MAP_SETTINGS_EDITOR_DIALOG_REQUEST_H__ #define __EVENT_MAP_SETTINGS_EDITOR_DIALOG_REQUEST_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "Event.h" namespace caret { class CaretMappableDataFile; class Overlay; /// Event for showing edit map scalar color mapping editor class EventOverlaySettingsEditorDialogRequest : public Event { public: enum Mode { MODE_SHOW_EDITOR, MODE_OVERLAY_MAP_CHANGED }; EventOverlaySettingsEditorDialogRequest(const Mode mode, const int32_t browserWindowIndex, Overlay* overlay, CaretMappableDataFile* mapFile, const int32_t mapIndex); virtual ~EventOverlaySettingsEditorDialogRequest(); /** * @return The mode (show or update) */ Mode getMode() const { return m_mode; } /** * @return Get the index of the browser window for palette being edited. */ int32_t getBrowserWindowIndex() const { return m_browserWindowIndex; } /** * @return Map file containing map whose color palette is edited */ CaretMappableDataFile* getCaretMappableDataFile() const { return m_mapFile; } /** * @return Index of map in the map file */ int32_t getMapIndex() const { return m_mapIndex; } /** * @return The overlay for the editor. */ Overlay* getOverlay() { return m_overlay; } /** * @return The overlay for the editor. */ const Overlay* getOverlay() const { return m_overlay; } private: EventOverlaySettingsEditorDialogRequest(const EventOverlaySettingsEditorDialogRequest&); EventOverlaySettingsEditorDialogRequest& operator=(const EventOverlaySettingsEditorDialogRequest&); /** The mode show/update */ const Mode m_mode; /** index of browser window for palette editing */ int32_t m_browserWindowIndex; /** Overlay for editor. */ Overlay* m_overlay; /** Map file containing map whose color palette is edited */ CaretMappableDataFile* m_mapFile; /** Index of map in the map file */ int32_t m_mapIndex; }; } // namespace #endif // __EVENT_MAP_SETTINGS_EDITOR_DIALOG_REQUEST_H__ workbench-1.1.1/src/GuiQt/EventPaletteColorMappingEditorDialogRequest.cxx000066400000000000000000000037021255417355300266270ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __EVENT_PALETTE_COLOR_MAPPING_EDITOR_DIALOG_REQUEST_DECLARE__ #include "EventPaletteColorMappingEditorDialogRequest.h" #undef __EVENT_PALETTE_COLOR_MAPPING_EDITOR_DIALOG_REQUEST_DECLARE__ #include "CaretAssert.h" #include "EventTypeEnum.h" using namespace caret; /** * \class caret::EventPaletteColorMappingEditorDialogRequest * \brief Event for editing palette color mapping in a dialog * \ingroup GuiQt */ /** * Constructor. */ EventPaletteColorMappingEditorDialogRequest::EventPaletteColorMappingEditorDialogRequest(const int32_t browserWindowIndex, CaretMappableDataFile* mapFile, const int32_t mapIndex) : Event(EventTypeEnum::EVENT_PALETTE_COLOR_MAPPING_EDITOR_SHOW), m_browserWindowIndex(browserWindowIndex), m_mapFile(mapFile), m_mapIndex(mapIndex) { CaretAssert(mapFile); CaretAssert(mapIndex >= 0); } /** * Destructor. */ EventPaletteColorMappingEditorDialogRequest::~EventPaletteColorMappingEditorDialogRequest() { } workbench-1.1.1/src/GuiQt/EventPaletteColorMappingEditorDialogRequest.h000066400000000000000000000055101255417355300262530ustar00rootroot00000000000000#ifndef __EVENT_PALETTE_COLOR_MAPPING_EDITOR_DIALOG_REQUEST_H__ #define __EVENT_PALETTE_COLOR_MAPPING_EDITOR_DIALOG_REQUEST_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "Event.h" namespace caret { class CaretMappableDataFile; class EventPaletteColorMappingEditorDialogRequest : public Event { public: EventPaletteColorMappingEditorDialogRequest(const int32_t browserWindowIndex, CaretMappableDataFile* mapFile, const int32_t mapIndex); virtual ~EventPaletteColorMappingEditorDialogRequest(); /** * @return Get the index of the browser window for palette being edited. */ int32_t getBrowserWindowIndex() const { return m_browserWindowIndex; } /** * @return Map file containing map whose color palette is edited */ CaretMappableDataFile* getCaretMappableDataFile() const { return m_mapFile; } /** * @return Index of map in the map file */ int32_t getMapIndex() const { return m_mapIndex; } // ADD_NEW_METHODS_HERE private: EventPaletteColorMappingEditorDialogRequest(const EventPaletteColorMappingEditorDialogRequest&); EventPaletteColorMappingEditorDialogRequest& operator=(const EventPaletteColorMappingEditorDialogRequest&); /** index of browser window for palette editing */ int32_t m_browserWindowIndex; /** Map file containing map whose color palette is edited */ CaretMappableDataFile* m_mapFile; /** Index of map in the map file */ int32_t m_mapIndex; // ADD_NEW_MEMBERS_HERE }; #ifdef __EVENT_PALETTE_COLOR_MAPPING_EDITOR_DIALOG_REQUEST_DECLARE__ // #endif // __EVENT_PALETTE_COLOR_MAPPING_EDITOR_DIALOG_REQUEST_DECLARE__ } // namespace #endif //__EVENT_PALETTE_COLOR_MAPPING_EDITOR_DIALOG_REQUEST_H__ workbench-1.1.1/src/GuiQt/EventUpdateInformationWindows.cxx000066400000000000000000000051601255417355300240610ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "Brain.h" #include "EventUpdateInformationWindows.h" #include "GuiManager.h" #include "IdentificationManager.h" #include "IdentifiedItem.h" using namespace caret; /** * \class caret::EventUpdateInformationWindows * \brief Event for updating Information Windows * \ingroup GuiQt */ /** * Construct an event for update of information window with * content from the identification manager. The given * text will be added to the information manager prior to * update of information window. * * @param informationText * Next text that will be added to the information manager. */ EventUpdateInformationWindows::EventUpdateInformationWindows(const AString& informationText) : Event(EventTypeEnum::EVENT_UPDATE_INFORMATION_WINDOWS) { if ( ! informationText.isEmpty()) { IdentificationManager* idManager = GuiManager::get()->getBrain()->getIdentificationManager(); CaretAssert(idManager); idManager->addIdentifiedItem(new IdentifiedItem(informationText)); } m_important = true; } /** * Construct an event for update of information window with * content from the identification manager. */ EventUpdateInformationWindows::EventUpdateInformationWindows() : Event(EventTypeEnum::EVENT_UPDATE_INFORMATION_WINDOWS) { m_important = true; } /** * Destructor. */ EventUpdateInformationWindows::~EventUpdateInformationWindows() { } /** * Set the message as not important so that the toolbox * does NOT switch to the information panel. */ EventUpdateInformationWindows* EventUpdateInformationWindows::setNotImportant() { m_important = false; return this; } /** * @return Is this message important. If so, * the Toolbox will switch to the information * display. */ bool EventUpdateInformationWindows::isImportant() const { return m_important; } workbench-1.1.1/src/GuiQt/EventUpdateInformationWindows.h000066400000000000000000000032711255417355300235070ustar00rootroot00000000000000#ifndef __EVENT_UPDATE_INFORMATION_WINDOWS_H__ #define __EVENT_UPDATE_INFORMATION_WINDOWS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "Event.h" namespace caret { class BrainStructure; /// Request display of text in the information window(s). class EventUpdateInformationWindows : public Event { public: EventUpdateInformationWindows(); EventUpdateInformationWindows(const AString& informationText); virtual ~EventUpdateInformationWindows(); EventUpdateInformationWindows* setNotImportant(); bool isImportant() const; private: EventUpdateInformationWindows(const EventUpdateInformationWindows&); EventUpdateInformationWindows& operator=(const EventUpdateInformationWindows&); bool m_important; }; } // namespace #endif // __EVENT_UPDATE_INFORMATION_WINDOWS_H__ workbench-1.1.1/src/GuiQt/EventUpdateVolumeEditingToolBar.cxx000066400000000000000000000027361255417355300242650ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __EVENT_UPDATE_VOLUME_EDITING_TOOL_BAR_DECLARE__ #include "EventUpdateVolumeEditingToolBar.h" #undef __EVENT_UPDATE_VOLUME_EDITING_TOOL_BAR_DECLARE__ #include "CaretAssert.h" #include "EventTypeEnum.h" using namespace caret; /** * \class caret::EventUpdateVolumeEditingToolBar * \brief * \ingroup GuiQt * * */ /** * Constructor. */ EventUpdateVolumeEditingToolBar::EventUpdateVolumeEditingToolBar() : Event(EventTypeEnum::EVENT_UPDATE_VOLUME_EDITING_TOOLBAR) { } /** * Destructor. */ EventUpdateVolumeEditingToolBar::~EventUpdateVolumeEditingToolBar() { } workbench-1.1.1/src/GuiQt/EventUpdateVolumeEditingToolBar.h000066400000000000000000000032251255417355300237040ustar00rootroot00000000000000#ifndef __EVENT_UPDATE_VOLUME_EDITING_TOOL_BAR_H__ #define __EVENT_UPDATE_VOLUME_EDITING_TOOL_BAR_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "Event.h" namespace caret { class EventUpdateVolumeEditingToolBar : public Event { public: EventUpdateVolumeEditingToolBar(); virtual ~EventUpdateVolumeEditingToolBar(); // ADD_NEW_METHODS_HERE private: EventUpdateVolumeEditingToolBar(const EventUpdateVolumeEditingToolBar&); EventUpdateVolumeEditingToolBar& operator=(const EventUpdateVolumeEditingToolBar&); // ADD_NEW_MEMBERS_HERE }; #ifdef __EVENT_UPDATE_VOLUME_EDITING_TOOL_BAR_DECLARE__ // #endif // __EVENT_UPDATE_VOLUME_EDITING_TOOL_BAR_DECLARE__ } // namespace #endif //__EVENT_UPDATE_VOLUME_EDITING_TOOL_BAR_H__ workbench-1.1.1/src/GuiQt/EventUpdateYokedWindows.cxx000066400000000000000000000050241255417355300226460ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __EVENT_UPDATE_YOKED_WINDOWS_DECLARE__ #include "EventUpdateYokedWindows.h" #undef __EVENT_UPDATE_YOKED_WINDOWS_DECLARE__ #include "EventTypeEnum.h" using namespace caret; /** * \class caret::EventUpdateYokedWindows * \brief Updates windows that are yoked. * \ingroup GuiQt */ /** * Constructor for a window. This given window will NOT be updated. * * @param browserWindowIndexThatIssuedEvent * Index of browser window that issued event. * @param yokingGroup * Yoking group that is selected. */ EventUpdateYokedWindows::EventUpdateYokedWindows(const int32_t browserWindowIndexThatIssuedEvent, const YokingGroupEnum::Enum yokingGroup) : Event(EventTypeEnum::EVENT_UPDATE_YOKED_WINDOWS), m_browserWindowIndexThatIssuedEvent(browserWindowIndexThatIssuedEvent), m_yokingGroup(yokingGroup) { } /** * Constructor for updating all windows. * * @param browserWindowIndexThatIssuedEvent * Index of browser window that issued event. * @param yokingGroup * Yoking group that is selected. */ EventUpdateYokedWindows::EventUpdateYokedWindows(const YokingGroupEnum::Enum yokingGroup) : Event(EventTypeEnum::EVENT_UPDATE_YOKED_WINDOWS), m_browserWindowIndexThatIssuedEvent(-1), m_yokingGroup(yokingGroup) { } /** * Destructor. */ EventUpdateYokedWindows::~EventUpdateYokedWindows() { } /** * @return Index of browser window that issued the event. */ int32_t EventUpdateYokedWindows::getBrowserWindowIndexThatIssuedEvent() const { return m_browserWindowIndexThatIssuedEvent; } /** * @return The yoking group that should be updated. */ YokingGroupEnum::Enum EventUpdateYokedWindows::getYokingGroup() const { return m_yokingGroup; } workbench-1.1.1/src/GuiQt/EventUpdateYokedWindows.h000066400000000000000000000040731255417355300222760ustar00rootroot00000000000000#ifndef __EVENT_UPDATE_YOKED_WINDOWS_H__ #define __EVENT_UPDATE_YOKED_WINDOWS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "Event.h" #include "YokingGroupEnum.h" namespace caret { class EventUpdateYokedWindows : public Event { public: EventUpdateYokedWindows(const int32_t browserWindowIndexThatIssuedEvent, const YokingGroupEnum::Enum yokingGroup); EventUpdateYokedWindows(const YokingGroupEnum::Enum yokingGroup); virtual ~EventUpdateYokedWindows(); int32_t getBrowserWindowIndexThatIssuedEvent() const; YokingGroupEnum::Enum getYokingGroup() const; private: EventUpdateYokedWindows(const EventUpdateYokedWindows&); EventUpdateYokedWindows& operator=(const EventUpdateYokedWindows&); public: // ADD_NEW_METHODS_HERE private: const int32_t m_browserWindowIndexThatIssuedEvent; const YokingGroupEnum::Enum m_yokingGroup; // ADD_NEW_MEMBERS_HERE }; #ifdef __EVENT_UPDATE_YOKED_WINDOWS_DECLARE__ // #endif // __EVENT_UPDATE_YOKED_WINDOWS_DECLARE__ } // namespace #endif //__EVENT_UPDATE_YOKED_WINDOWS_H__ workbench-1.1.1/src/GuiQt/EventUserInterfaceUpdate.cxx000066400000000000000000000153061255417355300227630ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "EventUserInterfaceUpdate.h" using namespace caret; /** * \class caret::EventUserInterfaceUpdate * \brief Event for updating the user-interface. * \ingroup GuiQt * * After the user executes an action, there is often the * need to update the user-interface. Options are available * to limit the update for specific types of data. However, * some receivers will update regardless of any specific * update type requests. In addition, the update can * be targeted to a specific window. */ /** * Constructor. By default, everything is updated. * The 'add...()' methods can be used to limit the * types of data that get updated. */ EventUserInterfaceUpdate::EventUserInterfaceUpdate() : Event(EventTypeEnum::EVENT_USER_INTERFACE_UPDATE) { m_windowIndex = -1; this->setAll(true); } /* * Destructor. */ EventUserInterfaceUpdate::~EventUserInterfaceUpdate() { } /** * @return Is the update for the given window? * * @param windowIndex * Index of window. */ bool EventUserInterfaceUpdate::isUpdateForWindow(const int32_t windowIndex) const { if (m_windowIndex < 0) { return true; } else if (m_windowIndex == windowIndex) { return true; } return false; } /** * Set the update so that it only updates a specific window. * * @return A reference to the instance so that the * request update calls can be chained. */ EventUserInterfaceUpdate& EventUserInterfaceUpdate::setWindowIndex(const int32_t windowIndex) { m_windowIndex = windowIndex; return *this; } /** * Set the status of all update types. * @param new selection status. */ void EventUserInterfaceUpdate::setAll(bool selected) { m_borderUpdate = selected; m_connectivityUpdate = selected; m_fociUpdate = selected; m_surfaceUpdate = selected; m_tabUpdate = selected; m_toolBarUpdate = selected; m_toolBoxUpdate = selected; } /** * Add borders for update (borders have changed). * * Note the first call to an 'add...()' method will * set all other updates to off. * * @return A reference to the instance so that the * request update calls can be chained. */ EventUserInterfaceUpdate& EventUserInterfaceUpdate::addBorder() { addInitialization(); m_borderUpdate = true; return *this; } /** * Add connectivity for update (connectivity has changed). * * Note the first call to an 'add...()' method will * set all other updates to off. * * @return A reference to the instance so that the * request update calls can be chained. */ EventUserInterfaceUpdate& EventUserInterfaceUpdate::addConnectivity() { addInitialization(); m_connectivityUpdate = true; return *this; } /** * Add foci for update (foci have changed). * * Note the first call to an 'add...()' method will * set all other updates to off. * * @return A reference to the instance so that the * request update calls can be chained. */ EventUserInterfaceUpdate& EventUserInterfaceUpdate::addFoci() { addInitialization(); m_fociUpdate = true; return *this; } /** * Add surface for update (surfaces have changed). * * Note the first call to an 'add...()' method will * set all other updates to off. * * @return A reference to the instance so that the * request update calls can be chained. */ EventUserInterfaceUpdate& EventUserInterfaceUpdate::addSurface() { addInitialization(); m_surfaceUpdate = true; return *this; } /** * Add browser tabs for update (browser tabs have changed). * * Note the first call to an 'add...()' method will * set all other updates to off. * * @return A reference to the instance so that the * request update calls can be chained. */ EventUserInterfaceUpdate& EventUserInterfaceUpdate::addTab() { addInitialization(); m_tabUpdate = true; return *this; } /** * Add toolbar for update (toolbar data has changed). * * Note the first call to an 'add...()' method will * set all other updates to off. * * @return A reference to the instance so that the * request update calls can be chained. */ EventUserInterfaceUpdate& EventUserInterfaceUpdate::addToolBar() { addInitialization(); m_toolBarUpdate = true; return *this; } /** * Add toolbox for update (toolbox data has changed). * * Note the first call to an 'add...()' method will * set all other updates to off. * * @return A reference to the instance so that the * request update calls can be chained. */ EventUserInterfaceUpdate& EventUserInterfaceUpdate::addToolBox() { addInitialization(); m_toolBoxUpdate = true; return *this; } /** * This is called by any of the 'add...() methods * and, if this is the first time called after * creation of an instance, it will turn off all * update types so that the 'add...()' only has * to turn on its update type. */ void EventUserInterfaceUpdate::addInitialization() { if (m_isFirstUpdateType) { setAll(false); m_isFirstUpdateType = false; } } /** * @return Is border update requested. */ bool EventUserInterfaceUpdate::isBorderUpdate() const { return m_borderUpdate; } /** * @return Is connectivity update requested. */ bool EventUserInterfaceUpdate::isConnectivityUpdate() const { return m_connectivityUpdate; } /** * @return Is foci update requested. */ bool EventUserInterfaceUpdate::isFociUpdate() const { return m_fociUpdate; } /** * @return Is surface update requested. */ bool EventUserInterfaceUpdate::isSurfaceUpdate() const { return m_surfaceUpdate; } /** * @return Is tab update requested (browser tabs have changed) */ bool EventUserInterfaceUpdate::isTabUpdate() const { return m_tabUpdate; } /** * @return Is toolbar update requested. */ bool EventUserInterfaceUpdate::isToolBarUpdate() const { return m_toolBarUpdate; } /** * @return Is toolbox update requested. */ bool EventUserInterfaceUpdate::isToolBoxUpdate() const { return m_toolBoxUpdate; } workbench-1.1.1/src/GuiQt/EventUserInterfaceUpdate.h000066400000000000000000000052531255417355300224100ustar00rootroot00000000000000#ifndef __EVENT_USER_INTERFACE_UPDATE_H__ #define __EVENT_USER_INTERFACE_UPDATE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "Event.h" namespace caret { /// Event for updating the user-interface class EventUserInterfaceUpdate : public Event { public: EventUserInterfaceUpdate(); virtual ~EventUserInterfaceUpdate(); EventUserInterfaceUpdate& addBorder(); EventUserInterfaceUpdate& addConnectivity(); EventUserInterfaceUpdate& addFoci(); EventUserInterfaceUpdate& addSurface(); EventUserInterfaceUpdate& addTab(); EventUserInterfaceUpdate& addToolBar(); EventUserInterfaceUpdate& addToolBox(); bool isBorderUpdate() const; bool isConnectivityUpdate() const; bool isFociUpdate() const; bool isSurfaceUpdate() const; bool isTabUpdate() const; bool isToolBarUpdate() const; bool isToolBoxUpdate() const; bool isUpdateForWindow(const int32_t windowIndex) const; EventUserInterfaceUpdate& setWindowIndex(const int32_t windowIndex); private: EventUserInterfaceUpdate(const EventUserInterfaceUpdate&); EventUserInterfaceUpdate& operator=(const EventUserInterfaceUpdate&); void setAll(bool selected); void addInitialization(); bool m_borderUpdate; bool m_connectivityUpdate; bool m_fociUpdate; bool m_surfaceUpdate; bool m_tabUpdate; bool m_toolBarUpdate; bool m_toolBoxUpdate; bool m_isFirstUpdateType; int32_t m_windowIndex; }; } // namespace #endif // __EVENT_USER_INTERFACE_UPDATE_H__ workbench-1.1.1/src/GuiQt/FiberOrientationSelectionViewController.cxx000066400000000000000000000603351255417355300260710ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __FIBER_ORIENTATION_SELECTION_VIEW_CONTROLLER_DECLARE__ #include "FiberOrientationSelectionViewController.h" #undef __FIBER_ORIENTATION_SELECTION_VIEW_CONTROLLER_DECLARE__ #include #include #include #include #include #include #include #include "Brain.h" #include "BrowserTabContent.h" #include "DisplayGroupEnumComboBox.h" #include "DisplayPropertiesFiberOrientation.h" #include "EventManager.h" #include "EventGraphicsUpdateAllWindows.h" #include "EventUserInterfaceUpdate.h" #include "CiftiFiberOrientationFile.h" #include "FiberOrientationColoringTypeEnum.h" #include "FiberOrientationSelectionViewController.h" #include "FiberSamplesOpenGLWidget.h" #include "GuiManager.h" #include "SceneClass.h" #include "WuQFactory.h" #include "WuQTabWidget.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::FiberOrientationSelectionViewController * \brief View/Controller for fiber orientations * \ingroup GuiQt * */ /** * Constructor. */ FiberOrientationSelectionViewController::FiberOrientationSelectionViewController(const int32_t browserWindowIndex, QWidget* parent) : QWidget(parent), m_browserWindowIndex(browserWindowIndex) { QLabel* groupLabel = new QLabel("Group"); m_displayGroupComboBox = new DisplayGroupEnumComboBox(this); QObject::connect(m_displayGroupComboBox, SIGNAL(displayGroupSelected(const DisplayGroupEnum::Enum)), this, SLOT(displayGroupSelected(const DisplayGroupEnum::Enum))); QHBoxLayout* groupLayout = new QHBoxLayout(); WuQtUtilities::setLayoutSpacingAndMargins(groupLayout, 2, 2); groupLayout->addWidget(groupLabel); groupLayout->addWidget(m_displayGroupComboBox->getWidget()); groupLayout->addStretch(); QWidget* attributesWidget = this->createAttributesWidget(); QWidget* selectionWidget = this->createSelectionWidget(); QWidget* samplesWidget = this->createSamplesWidget(); m_displayFibersCheckBox = new QCheckBox("Display Fibers"); m_displayFibersCheckBox->setToolTip("Display Fibers"); QObject::connect(m_displayFibersCheckBox, SIGNAL(clicked(bool)), this, SLOT(processSelectionChanges())); m_tabWidget = new WuQTabWidget(WuQTabWidget::TAB_ALIGN_LEFT, this); m_tabWidget->addTab(attributesWidget, "Attributes"); m_tabWidget->addTab(selectionWidget, "Selection"); m_tabWidget->addTab(samplesWidget, "Samples"); m_tabWidget->setCurrentWidget(attributesWidget); QVBoxLayout* layout = new QVBoxLayout(this); WuQtUtilities::setLayoutSpacingAndMargins(layout, 2, 2); layout->addLayout(groupLayout); layout->addSpacing(10); layout->addWidget(m_displayFibersCheckBox); layout->addWidget(m_tabWidget->getWidget(), 0, Qt::AlignLeft); layout->addStretch(); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_USER_INTERFACE_UPDATE); s_allViewControllers.insert(this); } /** * Destructor. */ FiberOrientationSelectionViewController::~FiberOrientationSelectionViewController() { EventManager::get()->removeAllEventsFromListener(this); s_allViewControllers.erase(this); } /** * @return The selection widget. */ QWidget* FiberOrientationSelectionViewController::createSelectionWidget() { m_selectionWidgetLayout = new QVBoxLayout(); QWidget* widget = new QWidget(); QVBoxLayout* layout = new QVBoxLayout(widget); layout->addLayout(m_selectionWidgetLayout); layout->addStretch(); return widget; } /** * @return The attributes widget. */ QWidget* FiberOrientationSelectionViewController::createAttributesWidget() { m_updateInProgress = true; QLabel* aboveLimitLabel = new QLabel("Slice Above Limit"); m_aboveLimitSpinBox = WuQFactory::newDoubleSpinBox(); m_aboveLimitSpinBox->setRange(0.0, std::numeric_limits::max()); m_aboveLimitSpinBox->setDecimals(3); m_aboveLimitSpinBox->setSingleStep(0.1); m_aboveLimitSpinBox->setToolTip("Fibers within this distance above the volume slice will be displayed"); QObject::connect(m_aboveLimitSpinBox, SIGNAL(valueChanged(double)), this, SLOT(processAttributesChanges())); QLabel* belowLimitLabel = new QLabel("Slice Below Limit"); m_belowLimitSpinBox = WuQFactory::newDoubleSpinBox(); m_belowLimitSpinBox->setRange(-std::numeric_limits::max(), 0.0); m_belowLimitSpinBox->setDecimals(3); m_belowLimitSpinBox->setSingleStep(0.1); m_belowLimitSpinBox->setToolTip("Fibers within this distance below the volume slice will be displayed"); QObject::connect(m_belowLimitSpinBox, SIGNAL(valueChanged(double)), this, SLOT(processAttributesChanges())); QLabel* minimumMagnitudeLabel = new QLabel("Minimum Magnitude"); m_minimumMagnitudeSpinBox = WuQFactory::newDoubleSpinBox(); m_minimumMagnitudeSpinBox->setRange(0.0, std::numeric_limits::max()); m_minimumMagnitudeSpinBox->setDecimals(2); m_minimumMagnitudeSpinBox->setSingleStep(0.05); m_minimumMagnitudeSpinBox->setToolTip("Minimum magnitude for displaying fibers"); QObject::connect(m_minimumMagnitudeSpinBox, SIGNAL(valueChanged(double)), this, SLOT(processAttributesChanges())); QLabel* lengthMultiplierLabel = new QLabel("Length Multiplier"); m_lengthMultiplierSpinBox = WuQFactory::newDoubleSpinBox(); m_lengthMultiplierSpinBox->setRange(0.0, std::numeric_limits::max()); m_lengthMultiplierSpinBox->setDecimals(2); m_lengthMultiplierSpinBox->setSingleStep(1.0); m_lengthMultiplierSpinBox->setToolTip("Fiber lengths are scaled by this value"); QObject::connect(m_lengthMultiplierSpinBox, SIGNAL(valueChanged(double)), this, SLOT(processAttributesChanges())); QLabel* fanMultiplierLabel = new QLabel("Fan Multiplier"); m_fanMultiplierSpinBox = WuQFactory::newDoubleSpinBox(); m_fanMultiplierSpinBox->setRange(0.0, std::numeric_limits::max()); m_fanMultiplierSpinBox->setDecimals(2); m_fanMultiplierSpinBox->setSingleStep(0.05); m_fanMultiplierSpinBox->setToolTip("Fan angles are scaled by this value"); QObject::connect(m_fanMultiplierSpinBox, SIGNAL(valueChanged(double)), this, SLOT(processAttributesChanges())); m_drawWithMagnitudeCheckBox = new QCheckBox("Draw With Magnitude"); m_drawWithMagnitudeCheckBox->setToolTip("When drawing fibers, the magnitude is reflected in the length of the fiber"); QObject::connect(m_drawWithMagnitudeCheckBox, SIGNAL(clicked(bool)), this, SLOT(processAttributesChanges())); QLabel* coloringTypeLabel = new QLabel("Coloring"); m_coloringTypeComboBox = new EnumComboBoxTemplate(this); m_coloringTypeComboBox->getWidget()->setToolTip("Selects method for assigning the red, green, and blue\n" "color components when drawing fibers"); QObject::connect(m_coloringTypeComboBox, SIGNAL(itemActivated()), this, SLOT(processAttributesChanges())); m_coloringTypeComboBox->setup(); QLabel* symbolTypeLabel = new QLabel("Symbol"); m_symbolTypeComboBox = new EnumComboBoxTemplate(this); m_symbolTypeComboBox->getWidget()->setToolTip("Selects type of symbol for drawing fibers"); QObject::connect(m_symbolTypeComboBox, SIGNAL(itemActivated()), this, SLOT(processAttributesChanges())); m_symbolTypeComboBox->setup(); QWidget* gridWidget = new QWidget(); QGridLayout* gridLayout = new QGridLayout(gridWidget); gridLayout->setColumnStretch(0, 0); gridLayout->setColumnStretch(1, 100); WuQtUtilities::setLayoutSpacingAndMargins(gridLayout, 8, 2); int row = gridLayout->rowCount(); gridLayout->addWidget(coloringTypeLabel, row, 0); gridLayout->addWidget(m_coloringTypeComboBox->getWidget() , row, 1); row++; gridLayout->addWidget(symbolTypeLabel, row, 0); gridLayout->addWidget(m_symbolTypeComboBox->getWidget() , row, 1); row++; gridLayout->addWidget(WuQtUtilities::createHorizontalLineWidget(), row, 0, 1, 2); row++; gridLayout->addWidget(m_drawWithMagnitudeCheckBox, row, 0, 1, 2, Qt::AlignLeft); row++; gridLayout->addWidget(minimumMagnitudeLabel, row, 0); gridLayout->addWidget(m_minimumMagnitudeSpinBox , row, 1); row++; gridLayout->addWidget(lengthMultiplierLabel, row, 0); gridLayout->addWidget(m_lengthMultiplierSpinBox , row, 1); row++; gridLayout->addWidget(fanMultiplierLabel, row, 0); gridLayout->addWidget(m_fanMultiplierSpinBox , row, 1); row++; gridLayout->addWidget(WuQtUtilities::createHorizontalLineWidget(), row, 0, 1, 2); row++; gridLayout->addWidget(aboveLimitLabel, row, 0); gridLayout->addWidget(m_aboveLimitSpinBox, row, 1); row++; gridLayout->addWidget(belowLimitLabel, row, 0); gridLayout->addWidget(m_belowLimitSpinBox, row, 1); row++; gridWidget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); QWidget* widget = new QWidget(); QVBoxLayout* layout = new QVBoxLayout(widget); WuQtUtilities::setLayoutSpacingAndMargins(layout, 2, 2); layout->addWidget(gridWidget); layout->addStretch(); m_updateInProgress = false; return widget; } /** * Called when a widget on the attributes page has * its value changed. */ void FiberOrientationSelectionViewController::processAttributesChanges() { if (m_updateInProgress) { return; } DisplayPropertiesFiberOrientation* dpfo = GuiManager::get()->getBrain()->getDisplayPropertiesFiberOrientation(); BrowserTabContent* browserTabContent = GuiManager::get()->getBrowserTabContentForBrowserWindow(m_browserWindowIndex, true); if (browserTabContent == NULL) { return; } const int32_t browserTabIndex = browserTabContent->getTabNumber(); const DisplayGroupEnum::Enum displayGroup = dpfo->getDisplayGroupForTab(browserTabIndex); dpfo->setAboveLimit(displayGroup, browserTabIndex, m_aboveLimitSpinBox->value()); dpfo->setBelowLimit(displayGroup, browserTabIndex, m_belowLimitSpinBox->value()); dpfo->setMinimumMagnitude(displayGroup, browserTabIndex, m_minimumMagnitudeSpinBox->value()); dpfo->setLengthMultiplier(displayGroup, browserTabIndex, m_lengthMultiplierSpinBox->value()); dpfo->setFanMultiplier(displayGroup, browserTabIndex, m_fanMultiplierSpinBox->value()); dpfo->setDrawWithMagnitude(displayGroup, browserTabIndex, m_drawWithMagnitudeCheckBox->isChecked()); const FiberOrientationColoringTypeEnum::Enum coloringType = m_coloringTypeComboBox->getSelectedItem(); dpfo->setColoringType(displayGroup, browserTabIndex, coloringType); const FiberOrientationSymbolTypeEnum::Enum symbolType = m_symbolTypeComboBox->getSelectedItem(); dpfo->setSymbolType(displayGroup, browserTabIndex, symbolType); dpfo->setSphereOrientationsDisplayed(displayGroup, browserTabIndex, m_displaySphereOrientationsCheckBox->isChecked()); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); updateOtherViewControllers(); } /** * Called when the fiber orientation display group combo box is changed. */ void FiberOrientationSelectionViewController::displayGroupSelected(const DisplayGroupEnum::Enum displayGroup) { if (m_updateInProgress) { return; } /* * Update selected display group in model. */ BrowserTabContent* browserTabContent = GuiManager::get()->getBrowserTabContentForBrowserWindow(m_browserWindowIndex, false); if (browserTabContent == NULL) { return; } const int32_t browserTabIndex = browserTabContent->getTabNumber(); Brain* brain = GuiManager::get()->getBrain(); DisplayPropertiesFiberOrientation* dpfo = brain->getDisplayPropertiesFiberOrientation(); dpfo->setDisplayGroupForTab(browserTabIndex, displayGroup); /* * Since display group has changed, need to update controls */ updateViewController(); /* * Apply the changes. */ processSelectionChanges(); } /** * Update the fiber orientation widget. */ void FiberOrientationSelectionViewController::updateViewController() { m_updateInProgress = true; BrowserTabContent* browserTabContent = GuiManager::get()->getBrowserTabContentForBrowserWindow(m_browserWindowIndex, true); if (browserTabContent == NULL) { return; } const int32_t browserTabIndex = browserTabContent->getTabNumber(); Brain* brain = GuiManager::get()->getBrain(); DisplayPropertiesFiberOrientation* dpfo = brain->getDisplayPropertiesFiberOrientation(); const DisplayGroupEnum::Enum displayGroup = dpfo->getDisplayGroupForTab(browserTabIndex); m_displayGroupComboBox->setSelectedDisplayGroup(displayGroup); //dpfo->isDisplayed(displayGroup, browserTabIndex) /* * Update file selection checkboxes */ const int32_t numberOfFileCheckBoxes = static_cast(m_fileSelectionCheckBoxes.size()); const int32_t numberOfFiberOrientFiles = brain->getNumberOfConnectivityFiberOrientationFiles(); for (int32_t iff = 0; iff < numberOfFiberOrientFiles; iff++) { CiftiFiberOrientationFile* cfof = brain->getConnectivityFiberOrientationFile(iff); QCheckBox* cb = NULL; if (iff < numberOfFileCheckBoxes) { cb = m_fileSelectionCheckBoxes[iff]; } else { cb = new QCheckBox(""); QObject::connect(cb, SIGNAL(clicked(bool)), this, SLOT(processSelectionChanges())); m_fileSelectionCheckBoxes.push_back(cb); m_selectionWidgetLayout->addWidget(cb); } cb->setText(cfof->getFileNameNoPath()); cb->setChecked(cfof->isDisplayed(displayGroup, browserTabIndex)); cb->setVisible(true); } /* * Hide unused file selection checkboxes */ for (int32_t iff = numberOfFiberOrientFiles; iff < numberOfFileCheckBoxes; iff++) { m_fileSelectionCheckBoxes[iff]->setVisible(false); } /* * Update the attributes */ m_displayFibersCheckBox->setChecked(dpfo->isDisplayed(displayGroup, browserTabIndex)); m_aboveLimitSpinBox->setValue(dpfo->getAboveLimit(displayGroup, browserTabIndex)); m_belowLimitSpinBox->setValue(dpfo->getBelowLimit(displayGroup, browserTabIndex)); m_lengthMultiplierSpinBox->setValue(dpfo->getLengthMultiplier(displayGroup, browserTabIndex)); m_fanMultiplierSpinBox->setValue(dpfo->getFanMultiplier(displayGroup, browserTabIndex)); m_minimumMagnitudeSpinBox->setValue(dpfo->getMinimumMagnitude(displayGroup, browserTabIndex)); m_drawWithMagnitudeCheckBox->setChecked(dpfo->isDrawWithMagnitude(displayGroup, browserTabIndex)); m_coloringTypeComboBox->setSelectedItem(dpfo->getColoringType(displayGroup, browserTabIndex)); m_symbolTypeComboBox->setSelectedItem(dpfo->getSymbolType(displayGroup, browserTabIndex)); m_displaySphereOrientationsCheckBox->setChecked(dpfo->isSphereOrientationsDisplayed(displayGroup, browserTabIndex)); /* * Update the samples */ m_samplesOpenGLWidget->update(); m_updateInProgress = false; } /** * Issue update events after selections are changed. */ void FiberOrientationSelectionViewController::processSelectionChanges() { if (m_updateInProgress) { return; } BrowserTabContent* browserTabContent = GuiManager::get()->getBrowserTabContentForBrowserWindow(m_browserWindowIndex, true); if (browserTabContent == NULL) { return; } Brain* brain = GuiManager::get()->getBrain(); const int32_t browserTabIndex = browserTabContent->getTabNumber(); DisplayPropertiesFiberOrientation* dpfo = brain->getDisplayPropertiesFiberOrientation(); const DisplayGroupEnum::Enum displayGroup = dpfo->getDisplayGroupForTab(browserTabIndex); dpfo->setDisplayed(displayGroup, browserTabIndex, m_displayFibersCheckBox->isChecked()); const int32_t numberOfFiberOrientFiles = brain->getNumberOfConnectivityFiberOrientationFiles(); CaretAssert(numberOfFiberOrientFiles <= static_cast(m_fileSelectionCheckBoxes.size())); for (int32_t iff = 0; iff < numberOfFiberOrientFiles; iff++) { CiftiFiberOrientationFile* cfof = brain->getConnectivityFiberOrientationFile(iff); cfof->setDisplayed(displayGroup, browserTabIndex, m_fileSelectionCheckBoxes[iff]->isChecked()); } updateOtherViewControllers(); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } /** * @return Create and return the spherical samples widget. */ QWidget* FiberOrientationSelectionViewController::createSamplesWidget() { m_displaySphereOrientationsCheckBox = new QCheckBox("Show Orientations"); QObject::connect(m_displaySphereOrientationsCheckBox, SIGNAL(clicked(bool)), this, SLOT(processAttributesChanges())); QGridLayout* fiberLabelsGridLayout = new QGridLayout(); fiberLabelsGridLayout->setColumnStretch(0, 0); fiberLabelsGridLayout->setColumnStretch(1, 50); fiberLabelsGridLayout->setColumnStretch(2, 50); int row = 0; fiberLabelsGridLayout->addWidget(new QLabel("Fiber "), row, 0); fiberLabelsGridLayout->addWidget(new QLabel("Mean "), row, 1); fiberLabelsGridLayout->addWidget(new QLabel("Variance"), row, 2); row++; QLabel* fiberMeanLabels[3]; QLabel* fiberVarianceLabels[3]; for (int32_t i = 0; i < 3; i++) { fiberMeanLabels[i] = new QLabel(""); fiberVarianceLabels[i] = new QLabel(""); fiberLabelsGridLayout->addWidget(new QLabel(QString::number(i+1)), row, 0); fiberLabelsGridLayout->addWidget(fiberMeanLabels[i], row, 1); fiberLabelsGridLayout->addWidget(fiberVarianceLabels[i], row, 2); row++; } m_samplesOpenGLWidget = new FiberSamplesOpenGLWidget(this->m_browserWindowIndex, m_displaySphereOrientationsCheckBox, fiberMeanLabels, fiberVarianceLabels); m_samplesOpenGLWidget->setMinimumSize(200, 200); QWidget* widget = new QWidget(); QVBoxLayout* layout = new QVBoxLayout(widget); WuQtUtilities::setLayoutSpacingAndMargins(layout, 2, 2); layout->addWidget(m_displaySphereOrientationsCheckBox); layout->addWidget(m_samplesOpenGLWidget, 100); // stretch factor layout->addLayout(fiberLabelsGridLayout); // layout->addStretch(); return widget; } /** * Update other fiber orientation view controllers. */ void FiberOrientationSelectionViewController::updateOtherViewControllers() { for (std::set::iterator iter = s_allViewControllers.begin(); iter != s_allViewControllers.end(); iter++) { FiberOrientationSelectionViewController* fosvc = *iter; if (fosvc != this) { fosvc->updateViewController(); } } } /** * Receive events from the event manager. * * @param event * Event sent by event manager. */ void FiberOrientationSelectionViewController::receiveEvent(Event* event) { bool doUpdate = false; if (event->getEventType() == EventTypeEnum::EVENT_USER_INTERFACE_UPDATE) { EventUserInterfaceUpdate* uiEvent = dynamic_cast(event); CaretAssert(uiEvent); if (uiEvent->isUpdateForWindow(m_browserWindowIndex)) { if (uiEvent->isToolBoxUpdate()) { doUpdate = true; uiEvent->setEventProcessed(); } } } if (doUpdate) { updateViewController(); } } /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ SceneClass* FiberOrientationSelectionViewController::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "FiberOrientationSelectionViewController", 1); sceneClass->addClass(m_tabWidget->saveToScene(sceneAttributes, "m_tabWidget")); return sceneClass; } /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * SceneClass containing the state that was previously * saved and should be restored. */ void FiberOrientationSelectionViewController::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } m_tabWidget->restoreFromScene(sceneAttributes, sceneClass->getClass("m_tabWidget")); } workbench-1.1.1/src/GuiQt/FiberOrientationSelectionViewController.h000066400000000000000000000102261255417355300255100ustar00rootroot00000000000000#ifndef __FIBER_ORIENTATION_SELECTION_VIEW_CONTROLLER__H_ #define __FIBER_ORIENTATION_SELECTION_VIEW_CONTROLLER__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "DisplayGroupEnum.h" #include "EnumComboBoxTemplate.h" #include "EventListenerInterface.h" #include "SceneableInterface.h" class QCheckBox; class QDoubleSpinBox; class QVBoxLayout; namespace caret { class DisplayGroupEnumComboBox; class FiberSamplesOpenGLWidget; class WuQTabWidget; class WuQTrueFalseComboBox; class FiberOrientationSelectionViewController : public QWidget, public EventListenerInterface, public SceneableInterface { Q_OBJECT public: FiberOrientationSelectionViewController(const int32_t browserWindowIndex, QWidget* parent = 0); virtual ~FiberOrientationSelectionViewController(); void receiveEvent(Event* event); virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); // ADD_NEW_METHODS_HERE private slots: void processSelectionChanges(); void displayGroupSelected(const DisplayGroupEnum::Enum); void processAttributesChanges(); private: FiberOrientationSelectionViewController(const FiberOrientationSelectionViewController&); FiberOrientationSelectionViewController& operator=(const FiberOrientationSelectionViewController&); void updateViewController(); void updateOtherViewControllers(); QWidget* createSelectionWidget(); QWidget* createAttributesWidget(); QWidget* createSamplesWidget(); const int32_t m_browserWindowIndex; DisplayGroupEnumComboBox* m_displayGroupComboBox; QCheckBox* m_displayFibersCheckBox; std::vector m_fileSelectionCheckBoxes; QDoubleSpinBox* m_aboveLimitSpinBox; QDoubleSpinBox* m_belowLimitSpinBox; QDoubleSpinBox* m_minimumMagnitudeSpinBox; QDoubleSpinBox* m_lengthMultiplierSpinBox; QDoubleSpinBox* m_fanMultiplierSpinBox; EnumComboBoxTemplate* m_coloringTypeComboBox; EnumComboBoxTemplate* m_symbolTypeComboBox; QCheckBox* m_drawWithMagnitudeCheckBox; QVBoxLayout* m_selectionWidgetLayout; FiberSamplesOpenGLWidget* m_samplesOpenGLWidget; QCheckBox* m_displaySphereOrientationsCheckBox; WuQTabWidget* m_tabWidget; bool m_updateInProgress; // ADD_NEW_MEMBERS_HERE static std::set s_allViewControllers; }; #ifdef __FIBER_ORIENTATION_SELECTION_VIEW_CONTROLLER_DECLARE__ std::set FiberOrientationSelectionViewController::s_allViewControllers; #endif // __FIBER_ORIENTATION_SELECTION_VIEW_CONTROLLER_DECLARE__ } // namespace #endif //__FIBER_ORIENTATION_SELECTION_VIEW_CONTROLLER__H_ workbench-1.1.1/src/GuiQt/FiberSamplesOpenGLWidget.cxx000066400000000000000000000516111255417355300226430ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #define __FIBER_SAMPLES_OPEN_G_L_WIDGET_DECLARE__ #include "FiberSamplesOpenGLWidget.h" #undef __FIBER_SAMPLES_OPEN_G_L_WIDGET_DECLARE__ using namespace caret; #include "Brain.h" #include "BrainOpenGL.h" #include "BrainOpenGLShapeRing.h" #include "BrainOpenGLShapeSphere.h" #include "DisplayPropertiesFiberOrientation.h" #include "Fiber.h" #include "FiberOrientation.h" #include "GuiManager.h" #include "WuQMessageBox.h" /** * \class caret::FiberSamplesOpenGLWidget * \brief OpenGL Widget for drawing fiber samples on a sphere * \defgroup GuiQt * \ingroup GuiQt */ /** * Constructor. */ FiberSamplesOpenGLWidget::FiberSamplesOpenGLWidget(const int32_t browserWindowIndex, QCheckBox* enabledCheckBox, QLabel* fiberMeanLabels[3], QLabel* fiberVarianceLabels[3], QWidget* parent) : QGLWidget(parent) { m_browserWindowIndex = browserWindowIndex; m_enabledCheckBox = enabledCheckBox; for (int32_t i = 0; i < 3; i++) { m_fiberMeanLabels[i] = fiberMeanLabels[i]; m_fiberVarianceLabels[i] = fiberVarianceLabels[i]; } m_ring = NULL; m_sphereBig = NULL; m_sphereSmall = NULL; setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); } /** * Destructor. */ FiberSamplesOpenGLWidget::~FiberSamplesOpenGLWidget() { makeCurrent(); if (m_ring != NULL) { delete m_ring; } if (m_sphereBig != NULL) { delete m_sphereBig; } if (m_sphereSmall != NULL) { delete m_sphereSmall; } } /** * Called once prior to resizeGL() and paintGL() to * make any necessary initializations. */ void FiberSamplesOpenGLWidget::initializeGL() { glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); glClearDepth(1.0); glFrontFace(GL_CCW); glEnable(GL_NORMALIZE); // // Avoid drawing backfacing polygons // glCullFace(GL_BACK); glEnable(GL_CULL_FACE); glShadeModel(GL_SMOOTH); createShapes(); } /** * Setup lighting parameters. */ void FiberSamplesOpenGLWidget::setupLighting() { glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE); glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_FALSE); float lightColor[] = { 0.9f, 0.9f, 0.9f, 1.0f }; glLightfv(GL_LIGHT0, GL_DIFFUSE, lightColor); glLightfv(GL_LIGHT1, GL_DIFFUSE, lightColor); float materialColor[] = { 0.8f, 0.8f, 0.8f, 1.0f }; glMaterialfv(GL_FRONT, GL_DIFFUSE, materialColor); glColorMaterial(GL_FRONT, GL_DIFFUSE); float ambient[] = { 0.8f, 0.8f, 0.8f, 1.0f }; glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient); float lightPosition[] = { 0.0f, 0.0f, 100.0f, 0.0f }; glPushMatrix(); glLoadIdentity(); glLightfv(GL_LIGHT0, GL_POSITION, lightPosition); glEnable(GL_LIGHT0); // // Light 1 position is opposite of light 0 // lightPosition[0] = -lightPosition[0]; lightPosition[1] = -lightPosition[1]; lightPosition[2] = -lightPosition[2]; glLightfv(GL_LIGHT1, GL_POSITION, lightPosition); glEnable(GL_LIGHT1); glPopMatrix(); glEnable(GL_LIGHTING); glEnable(GL_COLOR_MATERIAL); glColorMaterial(GL_FRONT, GL_DIFFUSE); } /** * Gets called whenever the widget is resized. * @param width * New width of widget. * @param height * New height of widget. */ void FiberSamplesOpenGLWidget::resizeGL(int width, int height) { setOrthographicProjection(width, height); } /** * Set the orthographic projection. * @param width * New width. * @param height * New height. */ void FiberSamplesOpenGLWidget::setOrthographicProjection(const int width, const int height) { m_widgetWidth = width; m_widgetHeight = height; glViewport(0, 0, width, height); const double orthoSize = 125.0; const double aspectRatio = (static_cast(width) / static_cast(height)); double orthoWidth = orthoSize; double orthoHeight = orthoSize; if (aspectRatio > 1.0) { orthoWidth *= aspectRatio; } else { const float inverseAspectRatio = 1.0 / aspectRatio; orthoHeight *= inverseAspectRatio; } const double orthoNearFar = 5000.0; // allows zooming double orthographicRight = orthoWidth; double orthographicLeft = -orthoWidth; double orthographicTop = orthoHeight; double orthographicBottom = -orthoHeight; double orthographicNear = -orthoNearFar; double orthographicFar = orthoNearFar; glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(orthographicLeft, orthographicRight, orthographicBottom, orthographicTop, orthographicNear, orthographicFar); glMatrixMode(GL_MODELVIEW); } /** * Called when the widget needs to be repainted. */ void FiberSamplesOpenGLWidget::paintGL() { setOrthographicProjection(width(), height()); glClearColor(0.0, 0.0, 0.0, 1.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable(GL_DEPTH_TEST); glMatrixMode(GL_MODELVIEW); setupLighting(); glPushMatrix(); glLoadIdentity(); double rotationMatrixElements[16]; m_rotationMatrix.getMatrixForOpenGL(rotationMatrixElements); glMultMatrixd(rotationMatrixElements); const GLfloat lineStart = -s_sphereBigRadius * 5.0; const GLfloat lineEnd = s_sphereBigRadius * 5.0; glDisable(GL_LIGHTING); glDisable(GL_COLOR_MATERIAL); /* * Axis lines */ glLineWidth(3.0); glBegin(GL_LINES); glColor3f(0.5, 0.0, 0.0); glVertex3f(lineStart, 0.0, 0.0); glVertex3f(0.0, 0.0, 0.0); glColor3f(1.0,0.0, 0.0); glVertex3f(0.0, 0.0, 0.0); glVertex3f(lineEnd, 0.0, 0.0); glColor3f(0.0, 0.5, 0.0); glVertex3f(0.0, lineStart, 0.0); glVertex3f(0.0, 0.0, 0.0); glColor3f(0.0, 1.0, 0.0); glVertex3f(0.0, 0.0, 0.0); glVertex3f(0.0, lineEnd, 0.0); glColor3f(0.0, 0.0, 0.5); glVertex3f(0.0, 0.0, lineStart); glVertex3f(0.0, 0.0, 0.0); glColor3f(0.0,0.0, 1.0); glVertex3f(0.0, 0.0, 0.0); glVertex3f(0.0, 0.0, lineEnd); glEnd(); glEnable(GL_LIGHTING); glEnable(GL_COLOR_MATERIAL); /* * Sphere */ // glColor3f(0.7, // 0.7, // 0.7); const float rgba[4] = { 0.7, 0.7, 0.7, 1.0 }; m_sphereBig->draw(rgba); glDisable(GL_LIGHTING); glDisable(GL_COLOR_MATERIAL); if (m_enabledCheckBox->isChecked()) { drawOrientations(); } glPopMatrix(); } /** * Draw the fibers on the sphere */ void FiberSamplesOpenGLWidget::drawOrientations() { glDisable(GL_CULL_FACE); DisplayPropertiesFiberOrientation* dpfo = GuiManager::get()->getBrain()->getDisplayPropertiesFiberOrientation(); std::vector xVectors; std::vector yVectors; std::vector zVectors; FiberOrientation* fiberOrientation; AString errorMessage; Brain* brain = GuiManager::get()->getBrain(); if (brain->getFiberOrientationSphericalSamplesVectors(xVectors, yVectors, zVectors, fiberOrientation, errorMessage)) { const DisplayGroupEnum::Enum displayGroup = dpfo->getDisplayGroupForTab(this->m_browserWindowIndex); const FiberOrientationColoringTypeEnum::Enum coloringType = dpfo->getColoringType(displayGroup, m_browserWindowIndex); const float minimumMagnitude = dpfo->getMinimumMagnitude(displayGroup, m_browserWindowIndex); /* * First orientations */ const int32_t numVectorsX = static_cast(xVectors.size()); for (int32_t i = 0; i < numVectorsX; i++) { if (xVectors[i].magnitude < minimumMagnitude) { continue; } float xyz[3] = { xVectors[i].direction[0] * s_sphereBigRadius, xVectors[i].direction[1] * s_sphereBigRadius, xVectors[i].direction[2] * s_sphereBigRadius }; float rgba[4] = { 0.0, 0.0, 0.0, 255.0 }; switch (coloringType) { case FiberOrientationColoringTypeEnum::FIBER_COLORING_FIBER_INDEX_AS_RGB: //glColor3f(1.0, 0.0, 0.0); rgba[0] = 1.0; break; case FiberOrientationColoringTypeEnum::FIBER_COLORING_XYZ_AS_RGB: //glColor3fv(xVectors[i].rgb); rgba[0] = xVectors[i].rgb[0]; rgba[1] = xVectors[i].rgb[1]; rgba[2] = xVectors[i].rgb[2]; break; } glPushMatrix(); glTranslatef(xyz[0], xyz[1], xyz[2]); m_sphereSmall->draw(rgba); glPopMatrix(); glPushMatrix(); glTranslatef(-xyz[0], -xyz[1], -xyz[2]); m_sphereSmall->draw(rgba); glPopMatrix(); } /* * Second orientations */ const int32_t numVectorsY = static_cast(yVectors.size()); for (int32_t i = 0; i < numVectorsY; i++) { if (yVectors[i].magnitude < minimumMagnitude) { continue; } float xyz[3] = { yVectors[i].direction[0] * s_sphereBigRadius, yVectors[i].direction[1] * s_sphereBigRadius, yVectors[i].direction[2] * s_sphereBigRadius }; float rgba[4] = { 0.0, 0.0, 0.0, 255.0 }; switch (coloringType) { case FiberOrientationColoringTypeEnum::FIBER_COLORING_FIBER_INDEX_AS_RGB: //glColor3f(0.0, 0.0, 1.0); // BLUE (RBG!) rgba[2] = 1.0; break; case FiberOrientationColoringTypeEnum::FIBER_COLORING_XYZ_AS_RGB: //glColor3fv(yVectors[i].rgb); rgba[0] = yVectors[i].rgb[0]; rgba[1] = yVectors[i].rgb[1]; rgba[2] = yVectors[i].rgb[2]; break; } glPushMatrix(); glTranslatef(xyz[0], xyz[1], xyz[2]); m_sphereSmall->draw(rgba); glPopMatrix(); glPushMatrix(); glTranslatef(-xyz[0], -xyz[1], -xyz[2]); m_sphereSmall->draw(rgba); glPopMatrix(); } /* * Third orientations */ const int32_t numVectorsZ = static_cast(zVectors.size()); for (int32_t i = 0; i < numVectorsZ; i++) { if (zVectors[i].magnitude < minimumMagnitude) { continue; } float xyz[3] = { zVectors[i].direction[0] * s_sphereBigRadius, zVectors[i].direction[1] * s_sphereBigRadius, zVectors[i].direction[2] * s_sphereBigRadius }; float rgba[4] = { 0.0, 0.0, 0.0, 255.0 }; switch (coloringType) { case FiberOrientationColoringTypeEnum::FIBER_COLORING_FIBER_INDEX_AS_RGB: //glColor3f(0.0, 1.0, 0.0); // GREEN (RBG!) rgba[1] = 1.0; break; case FiberOrientationColoringTypeEnum::FIBER_COLORING_XYZ_AS_RGB: //glColor3fv(zVectors[i].rgb); rgba[0] = zVectors[i].rgb[0]; rgba[1] = zVectors[i].rgb[1]; rgba[2] = zVectors[i].rgb[2]; break; } glPushMatrix(); glTranslatef(xyz[0], xyz[1], xyz[2]); m_sphereSmall->draw(rgba); glPopMatrix(); glPushMatrix(); glTranslatef(-xyz[0], -xyz[1], -xyz[2]); m_sphereSmall->draw(rgba); glPopMatrix(); } double viewingMatrixArray[16]; glGetDoublev(GL_MODELVIEW_MATRIX, viewingMatrixArray); Matrix4x4 viewingMatrix; viewingMatrix.setMatrixFromOpenGL(viewingMatrixArray); Matrix4x4 inverseViewingMatrix(viewingMatrix); inverseViewingMatrix.invert(); inverseViewingMatrix.setTranslation(0.0, 0.0, 0.0); double inverseViewingMatrixArray[16]; inverseViewingMatrix.getMatrixForOpenGL(inverseViewingMatrixArray); /* * Orientation Ellipse */ const float radiansToDegrees = 180.0 / M_PI; if (fiberOrientation != NULL) { const int32_t numFibers = fiberOrientation->m_numberOfFibers; for (int32_t j = 0; j < numFibers; j++) { const Fiber* fiber = fiberOrientation->m_fibers[j]; if (fiber->m_valid) { if (j < 3) { m_fiberMeanLabels[j]->setText(AString::number(fiber->m_meanF)); m_fiberVarianceLabels[j]->setText(AString::number(fiber->m_varF)); } /* * Only draw if magnitude exceeds minimum magnitude */ if (fiber->m_meanF < minimumMagnitude) { continue; } float rgba[4] = { 0.0, 0.0, 0.0, 1.0 }; switch (coloringType) { case FiberOrientationColoringTypeEnum::FIBER_COLORING_FIBER_INDEX_AS_RGB: { const int32_t colorIndex = j % 3; switch (colorIndex) { case 0: rgba[0] = 1.0; //glColor3f(1.0, 0.0, 0.0); break; case 1: rgba[2] = 1.0; //glColor3f(0.0, 0.0, 1.0); break; case 2: rgba[1] = 1.0; //glColor3f(0.0, 1.0, 0.0); break; } } break; case FiberOrientationColoringTypeEnum::FIBER_COLORING_XYZ_AS_RGB: //glColor3fv(fiber->m_directionUnitVectorRGB); rgba[0] = fiber->m_directionUnitVectorRGB[0]; rgba[1] = fiber->m_directionUnitVectorRGB[1]; rgba[2] = fiber->m_directionUnitVectorRGB[2]; break; } const float maxAngle = M_PI_2 * 0.95; float baseMajorAngle = fiber->m_fanningMajorAxisAngle; if (baseMajorAngle > maxAngle) { baseMajorAngle = maxAngle; } float baseMinorAngle = fiber->m_fanningMinorAxisAngle; if (baseMinorAngle > maxAngle) { baseMinorAngle = maxAngle; } const float z = s_sphereBigRadius; const float baseRadiusScaling = 1.0; const float maxWidth = z; const float majorAxis = std::min(z * std::tan(baseMajorAngle) * baseRadiusScaling, maxWidth); const float minorAxis = std::min(z * std::tan(baseMinorAngle) * baseRadiusScaling, maxWidth); glPushMatrix(); glRotatef(-fiber->m_phi * radiansToDegrees, 0.0, 0.0, 1.0); glRotatef(-fiber->m_theta * radiansToDegrees, 0.0, 1.0, 0.0); glRotatef(-fiber->m_psi * radiansToDegrees, 0.0, 0.0, 1.0); glPushMatrix(); glTranslatef(0.0, 0.0, s_sphereBigRadius); glScalef(majorAxis * 4.0, minorAxis * 4.0, 1.0); m_ring->draw(rgba); glPopMatrix(); glPushMatrix(); glTranslatef(0.0, 0.0, -s_sphereBigRadius); glScalef(majorAxis * 4.0, minorAxis * 4.0, 1.0); m_ring->draw(rgba); glPopMatrix(); glPopMatrix(); } } } } else { if (errorMessage.isEmpty() == false) { WuQMessageBox::errorOk(this, errorMessage); } } glEnable(GL_CULL_FACE); } /** * Create the shapes on which fibers are viewed. */ void FiberSamplesOpenGLWidget::createShapes() { if (m_sphereBig == NULL) { m_sphereBig = new BrainOpenGLShapeSphere(25, s_sphereBigRadius); } if (m_sphereSmall == NULL) { m_sphereSmall = new BrainOpenGLShapeSphere(5, s_sphereSmallRadius); } if (m_ring == NULL) { m_ring = new BrainOpenGLShapeRing(10, 0.75, 1.0); } } /** * Receive mouse press events from Qt. * @param me * The mouse event. */ void FiberSamplesOpenGLWidget::mousePressEvent(QMouseEvent* me) { const Qt::MouseButton button = me->button(); // const Qt::KeyboardModifiers keyModifiers = me->modifiers(); if (button == Qt::LeftButton) { const int mouseX = me->x(); const int mouseY = m_widgetHeight - me->y(); m_mousePressX = mouseX; m_mousePressY = mouseY; m_lastMouseX = mouseX; m_lastMouseY = mouseY; } else { m_mousePressX = -10000; m_mousePressY = -10000; } me->accept(); } /** * Receive mouse button release events from Qt. * @param me * The mouse event. */ void FiberSamplesOpenGLWidget::mouseReleaseEvent(QMouseEvent* me) { m_mousePressX = -10000; m_mousePressY = -10000; me->accept(); } /** * Receive mouse move events from Qt. * @param me * The mouse event. */ void FiberSamplesOpenGLWidget::mouseMoveEvent(QMouseEvent* me) { const Qt::MouseButton button = me->button(); // Qt::KeyboardModifiers keyModifiers = me->modifiers(); if (button == Qt::NoButton) { if (me->buttons() == Qt::LeftButton) { const int mouseX = me->x(); const int mouseY = m_widgetHeight - me->y(); const int dx = mouseX - m_lastMouseX; const int dy = mouseY - m_lastMouseY; const int absDX = (dx >= 0) ? dx : -dx; const int absDY = (dy >= 0) ? dy : -dy; if ((absDX > 0) || (absDY > 0)) { m_rotationMatrix.rotateX(-dy); m_rotationMatrix.rotateY(dx); } m_lastMouseX = mouseX; m_lastMouseY = mouseY; } } me->accept(); updateGL(); } workbench-1.1.1/src/GuiQt/FiberSamplesOpenGLWidget.h000066400000000000000000000064611255417355300222730ustar00rootroot00000000000000#ifndef __FIBER_SAMPLES_OPEN_G_L_WIDGET__H_ #define __FIBER_SAMPLES_OPEN_G_L_WIDGET__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "Matrix4x4.h" class QLabel; class QCheckBox; namespace caret { class BrainOpenGLShapeRing; class BrainOpenGLShapeSphere; class FiberSamplesOpenGLWidget : public QGLWidget { Q_OBJECT public: FiberSamplesOpenGLWidget(const int32_t browserWindowIndex, QCheckBox* enabledCheckBox, QLabel* fiberMeanLabels[3], QLabel* fiberVarianceLabels[3], QWidget* parent = 0); virtual ~FiberSamplesOpenGLWidget(); // ADD_NEW_METHODS_HERE protected: void initializeGL(); void resizeGL(int width, int height); void paintGL(); void mouseMoveEvent(QMouseEvent* e); void mousePressEvent(QMouseEvent* e); void mouseReleaseEvent(QMouseEvent* e); private: FiberSamplesOpenGLWidget(const FiberSamplesOpenGLWidget&); FiberSamplesOpenGLWidget& operator=(const FiberSamplesOpenGLWidget&); void createShapes(); void setupLighting(); void setOrthographicProjection(const int width, const int height); void drawOrientations(); // ADD_NEW_MEMBERS_HERE int32_t m_browserWindowIndex; BrainOpenGLShapeSphere* m_sphereBig; BrainOpenGLShapeSphere* m_sphereSmall; BrainOpenGLShapeRing* m_ring; GLint m_widgetWidth; GLint m_widgetHeight; int m_mousePressX; int m_mousePressY; int m_lastMouseX; int m_lastMouseY; Matrix4x4 m_rotationMatrix; QCheckBox* m_enabledCheckBox; QLabel* m_fiberMeanLabels[3]; QLabel* m_fiberVarianceLabels[3]; static const float s_sphereBigRadius; static const float s_sphereSmallRadius; }; #ifdef __FIBER_SAMPLES_OPEN_G_L_WIDGET_DECLARE__ const float FiberSamplesOpenGLWidget::s_sphereBigRadius = 100.0; const float FiberSamplesOpenGLWidget::s_sphereSmallRadius = 1.0; #endif // __FIBER_SAMPLES_OPEN_G_L_WIDGET_DECLARE__ } // namespace #endif //__FIBER_SAMPLES_OPEN_G_L_WIDGET__H_ workbench-1.1.1/src/GuiQt/FociProjectionDialog.cxx000066400000000000000000000237601255417355300221170ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #include #define __FOCI_PROJECTION_DIALOG_DECLARE__ #include "FociProjectionDialog.h" #undef __FOCI_PROJECTION_DIALOG_DECLARE__ #include "Brain.h" #include "CursorDisplayScoped.h" #include "EventGraphicsUpdateAllWindows.h" #include "EventManager.h" #include "FociFile.h" #include "GuiManager.h" #include "Surface.h" #include "SurfaceProjector.h" #include "SurfaceSelectionViewController.h" #include "WuQFactory.h" #include "WuQMessageBox.h" using namespace caret; /** * \class caret::FociProjectionDialog * \brief Dialog for projection of foci. * \ingroup GuiQt */ /** * Constructor. * @param parent * The parent widget. */ FociProjectionDialog::FociProjectionDialog(QWidget* parent) : WuQDialogModal("Project Foci", parent) { QWidget* surfaceWidget = NULL; //createSurfaceSelectionWidget(); QWidget* fociFileWidget = createFociFileSelectionWidget(); QWidget* optionsWidget = createOptionsWidget(); QWidget* widget = new QWidget(); QVBoxLayout* layout = new QVBoxLayout(widget); if (surfaceWidget != NULL) { layout->addWidget(surfaceWidget); } layout->addWidget(fociFileWidget); layout->addWidget(optionsWidget); setCentralWidget(widget, WuQDialog::SCROLL_AREA_NEVER); } /** * Destructor. */ FociProjectionDialog::~FociProjectionDialog() { } /** * Called when the OK button is clicked. */ void FociProjectionDialog::okButtonClicked() { CursorDisplayScoped cursor; cursor.showWaitCursor(); Brain* brain = GuiManager::get()->getBrain(); std::vector surfaceFiles = brain->getPrimaryAnatomicalSurfaceFiles(); SurfaceFile* leftSurfaceFile = NULL; SurfaceFile* rightSurfaceFile = NULL; SurfaceFile* cerebellumSurfaceFile = NULL; for (std::vector::iterator iter = surfaceFiles.begin(); iter != surfaceFiles.end(); iter++) { const SurfaceFile* sf = *iter; switch (sf->getStructure()) { case StructureEnum::CORTEX_LEFT: leftSurfaceFile = const_cast(sf); break; case StructureEnum::CORTEX_RIGHT: rightSurfaceFile = const_cast(sf); break; case StructureEnum::CEREBELLUM: cerebellumSurfaceFile = const_cast(sf); break; default: break; } } // if (m_leftSurfaceCheckBox != NULL) { // if (m_leftSurfaceCheckBox->isChecked()) { // const Surface* sf = m_leftSurfaceViewController->getSurface(); // if (sf != NULL) { // surfaceFiles.push_back(sf); // } // } // } // if (m_rightSurfaceCheckBox != NULL) { // if (m_rightSurfaceCheckBox->isChecked()) { // const Surface* sf = m_rightSurfaceViewController->getSurface(); // if (sf != NULL) { // surfaceFiles.push_back(sf); // } // } // } // if (m_cerebellumSurfaceCheckBox != NULL) { // if (m_cerebellumSurfaceCheckBox->isChecked()) { // const Surface* sf = m_cerebellumSurfaceViewController->getSurface(); // if (sf != NULL) { // surfaceFiles.push_back(sf); // } // } // } AString errorMessages = ""; const int32_t numberOfFociFiles = static_cast(m_fociFiles.size()); for (int32_t i = 0; i < numberOfFociFiles; i++) { if (m_fociFileCheckBoxes[i]->isChecked()) { SurfaceProjector projector(leftSurfaceFile, rightSurfaceFile, cerebellumSurfaceFile); if (m_projectAboveSurfaceCheckBox->isChecked()) { projector.setSurfaceOffset(m_projectAboveSurfaceSpinBox->value()); } FociFile* ff = brain->getFociFile(i); try { projector.projectFociFile(ff); } catch (const SurfaceProjectorException& spe) { errorMessages += (spe.whatString() + "\n"); } } } EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); cursor.restoreCursor(); if (errorMessages.isEmpty() == false) { WuQMessageBox::errorOk(this, errorMessages); } WuQDialogModal::okButtonClicked(); } /** * @return The controls for surface selection. */ QWidget* FociProjectionDialog::createSurfaceSelectionWidget() { Brain* brain = GuiManager::get()->getBrain(); BrainStructure* leftBrainStructure = brain->getBrainStructure(StructureEnum::CORTEX_LEFT, false); BrainStructure* rightBrainStructure = brain->getBrainStructure(StructureEnum::CORTEX_RIGHT, false); BrainStructure* cerebellumBrainStructure = brain->getBrainStructure(StructureEnum::CEREBELLUM, false); m_leftSurfaceCheckBox = NULL; m_leftSurfaceViewController = NULL; if (leftBrainStructure != NULL) { m_leftSurfaceCheckBox = new QCheckBox("Left: "); m_leftSurfaceCheckBox->setChecked(true); m_leftSurfaceViewController = new SurfaceSelectionViewController(this, leftBrainStructure); m_leftSurfaceViewController->updateControl(); } m_rightSurfaceCheckBox = NULL; m_rightSurfaceViewController = NULL; if (rightBrainStructure != NULL) { m_rightSurfaceCheckBox = new QCheckBox("Right: "); m_rightSurfaceCheckBox->setChecked(true); m_rightSurfaceViewController = new SurfaceSelectionViewController(this, rightBrainStructure); m_rightSurfaceViewController->updateControl(); } m_cerebellumSurfaceCheckBox = NULL; m_cerebellumSurfaceViewController = NULL; if (cerebellumBrainStructure != NULL) { m_cerebellumSurfaceCheckBox = new QCheckBox("Cerebellum: "); m_cerebellumSurfaceCheckBox->setChecked(true); m_cerebellumSurfaceViewController = new SurfaceSelectionViewController(this, cerebellumBrainStructure); m_cerebellumSurfaceViewController->updateControl(); } QGroupBox* groupBox = new QGroupBox("Projection Surfaces"); QGridLayout* layout = new QGridLayout(groupBox); layout->setColumnStretch(0, 0); layout->setColumnStretch(1, 100); int rowIndex = layout->rowCount(); if (m_leftSurfaceCheckBox != NULL) { layout->addWidget(m_leftSurfaceCheckBox, rowIndex, 0); layout->addWidget(m_leftSurfaceViewController->getWidget(), rowIndex, 1); rowIndex++; } if (m_rightSurfaceCheckBox != NULL) { layout->addWidget(m_rightSurfaceCheckBox, rowIndex, 0); layout->addWidget(m_rightSurfaceViewController->getWidget(), rowIndex, 1); rowIndex++; } if (m_cerebellumSurfaceCheckBox != NULL) { layout->addWidget(m_cerebellumSurfaceCheckBox, rowIndex, 0); layout->addWidget(m_cerebellumSurfaceViewController->getWidget(), rowIndex, 1); rowIndex++; } return groupBox; } /** * @return The controls for foci file selection. */ QWidget* FociProjectionDialog::createFociFileSelectionWidget() { QGroupBox* groupBox = new QGroupBox("Foci Files for Projection"); QVBoxLayout* layout = new QVBoxLayout(groupBox); Brain* brain = GuiManager::get()->getBrain(); const int32_t numberOfFociFiles = brain->getNumberOfFociFiles(); for (int32_t i = 0; i < numberOfFociFiles; i++) { FociFile* ff = brain->getFociFile(i); m_fociFiles.push_back(ff); QCheckBox* checkBox = new QCheckBox(ff->getFileNameNoPath()); checkBox->setChecked(true); m_fociFileCheckBoxes.push_back(checkBox); layout->addWidget(checkBox); } return groupBox; } /** * @return The options controls. */ QWidget* FociProjectionDialog::createOptionsWidget() { m_projectAboveSurfaceCheckBox = new QCheckBox("Project fixed distance above surface(s)"); m_projectAboveSurfaceSpinBox = WuQFactory::newDoubleSpinBox(); m_projectAboveSurfaceSpinBox->setRange(-100000.0, 100000.0); m_projectAboveSurfaceSpinBox->setDecimals(2); m_projectAboveSurfaceSpinBox->setSingleStep(1.0); QObject::connect(m_projectAboveSurfaceCheckBox, SIGNAL(toggled(bool)), m_projectAboveSurfaceSpinBox, SLOT(setEnabled(bool))); m_projectAboveSurfaceCheckBox->setChecked(false); QGroupBox* groupBox = new QGroupBox("Projection Options"); QGridLayout* layout = new QGridLayout(groupBox); layout->setColumnStretch(0, 0); layout->setColumnStretch(1, 100); int rowIndex = layout->rowCount(); layout->addWidget(m_projectAboveSurfaceCheckBox, rowIndex, 0); layout->addWidget(m_projectAboveSurfaceSpinBox, rowIndex, 1); rowIndex++; return groupBox; } workbench-1.1.1/src/GuiQt/FociProjectionDialog.h000066400000000000000000000050131255417355300215330ustar00rootroot00000000000000#ifndef __FOCI_PROJECTION_DIALOG__H_ #define __FOCI_PROJECTION_DIALOG__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "WuQDialogModal.h" class QCheckBox; class QDoubleSpinBox; namespace caret { class FociFile; class SurfaceSelectionViewController; class FociProjectionDialog : public WuQDialogModal { Q_OBJECT public: FociProjectionDialog(QWidget* parent); virtual ~FociProjectionDialog(); // ADD_NEW_METHODS_HERE protected: virtual void okButtonClicked(); private: FociProjectionDialog(const FociProjectionDialog&); FociProjectionDialog& operator=(const FociProjectionDialog&); QWidget* createSurfaceSelectionWidget(); QWidget* createFociFileSelectionWidget(); QWidget* createOptionsWidget(); QCheckBox* m_leftSurfaceCheckBox; SurfaceSelectionViewController* m_leftSurfaceViewController; QCheckBox* m_rightSurfaceCheckBox; SurfaceSelectionViewController* m_rightSurfaceViewController; QCheckBox* m_cerebellumSurfaceCheckBox; SurfaceSelectionViewController* m_cerebellumSurfaceViewController; QCheckBox* m_projectAboveSurfaceCheckBox; QDoubleSpinBox* m_projectAboveSurfaceSpinBox; std::vector m_fociFiles; std::vector m_fociFileCheckBoxes; // ADD_NEW_MEMBERS_HERE }; #ifdef __FOCI_PROJECTION_DIALOG_DECLARE__ // #endif // __FOCI_PROJECTION_DIALOG_DECLARE__ } // namespace #endif //__FOCI_PROJECTION_DIALOG__H_ workbench-1.1.1/src/GuiQt/FociPropertiesEditorDialog.cxx000066400000000000000000000606551255417355300233120ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #include #include #include #include #include #define __FOCI_PROPERTIES_EDITOR_DIALOG__DECLARE__ #include "FociPropertiesEditorDialog.h" #undef __FOCI_PROPERTIES_EDITOR_DIALOG__DECLARE__ #include "Brain.h" #include "BrowserTabContent.h" #include "Focus.h" #include "FociFile.h" #include "CaretAssert.h" #include "CaretFileDialog.h" #include "DisplayPropertiesFoci.h" #include "EventGraphicsUpdateAllWindows.h" #include "EventDataFileAdd.h" #include "EventManager.h" #include "EventUserInterfaceUpdate.h" #include "GiftiLabel.h" #include "GiftiLabelTable.h" #include "GiftiLabelTableEditor.h" #include "GiftiLabelTableSelectionComboBox.h" #include "CaretLogger.h" #include "GuiManager.h" #include "SurfaceProjector.h" #include "WuQDataEntryDialog.h" #include "WuQFactory.h" #include "WuQMessageBox.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::FociPropertiesEditorDialog * \brief dialog for Foci Properties Editor * \ingroup GuiQt */ /** * Create (edit and add) a new focus. * * @param focus * The focus that will be displayed in the focus editor. * If the user accepts (presses OK) to add the focus * it will be added to the selected foci file. If the * user cancels, the focus will be destroyed. Thus, * the caller MUST NEVER access the focus after calling * this static method. * @param browserTabContent * Tab content on which focis are created. * @param parent * Parent widget on which dialog is displayed. * @return * true if user pressed OK, else false. */ bool FociPropertiesEditorDialog::createFocus(Focus* focus, BrowserTabContent* browserTabContent, QWidget* parent) { CaretAssert(focus); if (s_previousCreateFocus != NULL) { focus->setName(s_previousCreateFocus->getName()); focus->setArea(s_previousCreateFocus->getArea()); focus->setClassName(s_previousCreateFocus->getClassName()); focus->setComment(s_previousCreateFocus->getComment()); focus->setExtent(s_previousCreateFocus->getExtent()); focus->setGeography(s_previousCreateFocus->getGeography()); focus->setRegionOfInterest(s_previousCreateFocus->getRegionOfInterest()); focus->setStatistic(s_previousCreateFocus->getStatistic()); } FociPropertiesEditorDialog focusCreateDialog("Create Focus", s_previousCreateFociFile, focus, true, parent); if (focusCreateDialog.exec() == FociPropertiesEditorDialog::Accepted) { s_previousCreateFociFile = focusCreateDialog.getSelectedFociFile(); if (s_previousCreateFocus == NULL) { s_previousCreateFocus = new Focus(); } focusCreateDialog.loadFromDialogIntoFocusData(s_previousCreateFocus); s_previousCreateFociFile->addFocus(focus); if (browserTabContent != NULL) { const int32_t tabIndex = browserTabContent->getTabNumber(); DisplayPropertiesFoci* dsf = GuiManager::get()->getBrain()->getDisplayPropertiesFoci(); DisplayGroupEnum::Enum displayGroup = dsf->getDisplayGroupForTab(tabIndex); dsf->setDisplayed(displayGroup, tabIndex, true); } focusCreateDialog.updateGraphicsAndUserInterface(); return true; } delete focus; return false; } /** * Edit an existing focus. * * @param focus * The focus that will be displayed in the focus editor. * If the user presses the OK button the focus will be * updated. Otherwise, no further action is taken. * @param parent * Parent widget on which dialog is displayed. * @return * true if user pressed OK, else false. */ bool FociPropertiesEditorDialog::editFocus(FociFile* fociFile, Focus* focus, QWidget* parent) { FociPropertiesEditorDialog fped("Edit Focus", fociFile, focus, false, parent); if (fped.exec() == FociPropertiesEditorDialog::Accepted) { return true; } return false; } /** * Delete all static members to eliminate reported memory leaks. */ void FociPropertiesEditorDialog::deleteStaticMembers() { if (s_previousCreateFocus != NULL) { delete s_previousCreateFocus; s_previousCreateFocus = NULL; } } /** * Constructor. */ FociPropertiesEditorDialog::FociPropertiesEditorDialog(const QString& title, FociFile* fociFile, Focus* focus, const bool allowFociFileSelection, QWidget* parent) : WuQDialogModal(title, parent) { CaretAssert(focus); m_focus = focus; m_classComboBox = NULL; /* * File selection combo box */ QLabel* fociFileLabel = new QLabel("File"); m_fociFileSelectionComboBox = new QComboBox(); WuQtUtilities::setToolTipAndStatusTip(m_fociFileSelectionComboBox, "Selects an existing focus file\n" "to which new focus are added."); QObject::connect(m_fociFileSelectionComboBox, SIGNAL(activated(int)), this, SLOT(fociFileSelected())); QAction* newFileAction = WuQtUtilities::createAction("New", "Create a new focus file", this, this, SLOT(newFociFileButtonClicked())); QToolButton* newFileToolButton = new QToolButton(); newFileToolButton->setDefaultAction(newFileAction); /* * Completer for name */ m_nameCompleterStringListModel = new QStringListModel(this); /* * Name */ QLabel* nameLabel = new QLabel("Name"); m_nameComboBox = new GiftiLabelTableSelectionComboBox(this); m_nameComboBox->setUnassignedLabelTextOverride("Select Name"); QAction* displayNameColorEditorAction = WuQtUtilities::createAction("Add/Edit...", "Add and/or edit name colors", this, this, SLOT(displayNameEditor())); QToolButton* displayNameColorEditorToolButton = new QToolButton(); displayNameColorEditorToolButton->setDefaultAction(displayNameColorEditorAction); /* * Class */ QLabel* classLabel = new QLabel("Class"); m_classComboBox = new GiftiLabelTableSelectionComboBox(this); WuQtUtilities::setToolTipAndStatusTip(m_classComboBox->getWidget(), "The class is used to group focuss with similar\n" "characteristics. Controls are available to\n" "display focuss by their class attributes."); QAction* displayClassEditorAction = WuQtUtilities::createAction("Add/Edit...", "Add and/or edit classes", this, this, SLOT(displayClassEditor())); QToolButton* displayClassEditorToolButton = new QToolButton(); displayClassEditorToolButton->setDefaultAction(displayClassEditorAction); /* * Coordinates */ QLabel* coordLabel = new QLabel("XYZ (mm)"); m_xCoordSpinBox = WuQFactory::newDoubleSpinBox(); m_xCoordSpinBox->setDecimals(2); m_xCoordSpinBox->setSingleStep(1.0); m_xCoordSpinBox->setRange(-10000000.0, 10000000.0); m_yCoordSpinBox = WuQFactory::newDoubleSpinBox(); m_yCoordSpinBox->setDecimals(2); m_yCoordSpinBox->setSingleStep(1.0); m_yCoordSpinBox->setRange(-10000000.0, 10000000.0); m_zCoordSpinBox = WuQFactory::newDoubleSpinBox(); m_zCoordSpinBox->setDecimals(2); m_zCoordSpinBox->setSingleStep(1.0); m_zCoordSpinBox->setRange(-10000000.0, 10000000.0); /* * Comment */ QLabel* commentLabel = new QLabel("Comment"); m_commentTextEdit = new QTextEdit(); m_commentTextEdit->setFixedHeight(60); QLabel* areaLabel = new QLabel("Area"); m_areaLineEdit = new QLineEdit(); QLabel* geographyLabel = new QLabel("Geography"); m_geographyLineEdit = new QLineEdit(); QLabel* extentLabel = new QLabel("Extent"); m_extentSpinBox = WuQFactory::newDoubleSpinBox(); m_extentSpinBox->setDecimals(2); m_extentSpinBox->setSingleStep(1.0); m_extentSpinBox->setRange(-10000000.0, 10000000.0); QLabel* regionOfInterestLabel = new QLabel("ROI"); m_regionOfInterestLineEdit = new QLineEdit(); QLabel* statisticLabel = new QLabel("Statistic"); m_statisticLineEdit = new QLineEdit(); m_projectCheckBox = new QCheckBox("Project to Surface"); m_projectCheckBox->setChecked(s_previousFociProjectSelected); /* * Layout widgets */ QWidget* widget = new QWidget(); QGridLayout* gridLayout = new QGridLayout(widget); WuQtUtilities::setLayoutSpacingAndMargins(gridLayout, 4, 4); gridLayout->setColumnStretch(0, 0); gridLayout->setColumnStretch(1, 100); gridLayout->setColumnStretch(2, 100); gridLayout->setColumnStretch(3, 100); gridLayout->setColumnStretch(4, 0); int row = 0; gridLayout->addWidget(fociFileLabel, row, 0); gridLayout->addWidget(m_fociFileSelectionComboBox, row, 1, 1, 3); gridLayout->addWidget(newFileToolButton, row, 4); row++; gridLayout->addWidget(WuQtUtilities::createHorizontalLineWidget(), row, 0, 1, 5); row++; gridLayout->addWidget(nameLabel, row, 0); gridLayout->addWidget(m_nameComboBox->getWidget(), row, 1, 1, 3); gridLayout->addWidget(displayNameColorEditorToolButton, row, 4); row++; gridLayout->addWidget(classLabel, row, 0); gridLayout->addWidget(m_classComboBox->getWidget(), row, 1, 1, 3); gridLayout->addWidget(displayClassEditorToolButton, row, 4); row++; gridLayout->addWidget(coordLabel, row, 0); gridLayout->addWidget(m_xCoordSpinBox, row, 1); gridLayout->addWidget(m_yCoordSpinBox, row, 2); gridLayout->addWidget(m_zCoordSpinBox, row, 3); row++; gridLayout->addWidget(commentLabel, row, 0); gridLayout->addWidget(m_commentTextEdit, row, 1, 1, 3); row++; gridLayout->addWidget(areaLabel, row, 0); gridLayout->addWidget(m_areaLineEdit, row, 1, 1, 3); row++; gridLayout->addWidget(geographyLabel, row, 0); gridLayout->addWidget(m_geographyLineEdit, row, 1, 1, 3); row++; gridLayout->addWidget(extentLabel, row, 0); gridLayout->addWidget(m_extentSpinBox, row, 1, 1, 1); row++; gridLayout->addWidget(regionOfInterestLabel, row, 0); gridLayout->addWidget(m_regionOfInterestLineEdit, row, 1, 1, 3); row++; gridLayout->addWidget(statisticLabel, row, 0); gridLayout->addWidget(m_statisticLineEdit, row, 1, 1, 3); row++; gridLayout->addWidget(WuQtUtilities::createHorizontalLineWidget(), row, 0, 1, 4); row++; gridLayout->addWidget(m_projectCheckBox, row, 0, 1, 4); row++; /* * Allow/Disallow file selection */ fociFileLabel->setEnabled(allowFociFileSelection); m_fociFileSelectionComboBox->setEnabled(allowFociFileSelection); newFileToolButton->setEnabled(allowFociFileSelection); /* * Set the widget for the dialog. */ setCentralWidget(widget, WuQDialog::SCROLL_AREA_NEVER); loadFociFileComboBox(fociFile); loadFromFocusDataIntoDialog(m_focus); } /** * Destructor. */ FociPropertiesEditorDialog::~FociPropertiesEditorDialog() { } /** * Get the selected focus file. * @return FociFile or NULL if no focus file. */ FociFile* FociPropertiesEditorDialog::getSelectedFociFile() { FociFile* fociFile = NULL; const int fileComboBoxIndex = m_fociFileSelectionComboBox->currentIndex(); if (fileComboBoxIndex >= 0) { void* filePointer = m_fociFileSelectionComboBox->itemData(fileComboBoxIndex).value(); fociFile = (FociFile*)filePointer; } return fociFile; } /** * Load the focus files into the focus file combo box. * @param fociFile * Selected foci file, may be NULL. */ void FociPropertiesEditorDialog::loadFociFileComboBox(const FociFile* selectedFociFile) { Brain* brain = GuiManager::get()->getBrain(); const int32_t numFociFiles = brain->getNumberOfFociFiles(); m_fociFileSelectionComboBox->clear(); int defaultFileComboIndex = 0; for (int32_t i = 0; i < numFociFiles; i++) { FociFile* fociFile = brain->getFociFile(i); const AString name = fociFile->getFileNameNoPath(); m_fociFileSelectionComboBox->addItem(name, qVariantFromValue((void*)fociFile)); if (selectedFociFile == fociFile) { defaultFileComboIndex = m_fociFileSelectionComboBox->count() - 1; } } if (numFociFiles > 0) { m_fociFileSelectionComboBox->setCurrentIndex(defaultFileComboIndex); const FociFile* fociFile = getSelectedFociFile(); if (fociFile != NULL) { m_nameCompleterStringList = fociFile->getAllFociNamesSorted(); m_nameCompleterStringListModel->setStringList(m_nameCompleterStringList); } } } /** * Called to create a new focus file. */ void FociPropertiesEditorDialog::newFociFileButtonClicked() { /* * Let user choose a different path/name */ FociFile* newFociFile = new FociFile(); GuiManager::get()->getBrain()->convertDataFilePathNameToAbsolutePathName(newFociFile); AString newFociFileName = CaretFileDialog::getSaveFileNameDialog(DataFileTypeEnum::FOCI, this, "Choose Foci File Name", newFociFile->getFileName()); /* * If user cancels, delete the new focus file and return */ if (newFociFileName.isEmpty()) { delete newFociFile; return; } /* * Set name of new scene file */ newFociFile->setFileName(newFociFileName); EventManager::get()->sendEvent(EventDataFileAdd(newFociFile).getPointer()); loadFociFileComboBox(newFociFile); fociFileSelected(); } /** * Called when a focus file is selected. */ void FociPropertiesEditorDialog::fociFileSelected() { loadNameComboBox(); if (m_classComboBox != NULL) { loadClassComboBox(); } } /** * Load the class combo box. * * @param name * If not empty, make this name the selected name. */ void FociPropertiesEditorDialog::loadClassComboBox(const QString& name) { FociFile* fociFile = getSelectedFociFile(); if (fociFile != NULL) { m_classComboBox->updateContent(fociFile->getClassColorTable()); if (name.isEmpty() == false) { m_classComboBox->setSelectedLabelName(name); } } else { m_classComboBox->updateContent(NULL); } } /** * Load the name combo box. * * @param name * If not empty, make this name the selected name. */ void FociPropertiesEditorDialog::loadNameComboBox(const QString& name) { FociFile* fociFile = getSelectedFociFile(); if (fociFile != NULL) { m_nameComboBox->updateContent(fociFile->getNameColorTable()); if (name.isEmpty() == false) { m_nameComboBox->setSelectedLabelName(name); } } else { m_nameComboBox->updateContent(NULL); } } /** * Called when the OK button is pressed. */ void FociPropertiesEditorDialog::okButtonClicked() { AString errorMessage; /* * Get focus file. */ FociFile* fociFile = getSelectedFociFile(); if (fociFile == NULL) { WuQMessageBox::errorOk(this, "Foci file is not valid, use the New button to create a foci file."); return; } /* * Get data entered by the user. */ const AString name = m_nameComboBox->getSelectedLabelName(); if (name.isEmpty()) { errorMessage += ("Name is invalid.\n"); } else { const int32_t unassignedNameKey = fociFile->getNameColorTable()->getUnassignedLabelKey(); const int32_t selectedNameKey = m_nameComboBox->getSelectedLabelKey(); if (selectedNameKey == unassignedNameKey) { errorMessage += "Choose or create a name for the border"; } } const QString className = m_classComboBox->getSelectedLabelName(); if ((m_xCoordSpinBox->value() == 0.0) && (m_yCoordSpinBox->value() == 0.0) && (m_zCoordSpinBox->value() == 0.0)) { errorMessage += "Coordinates are invalid (all zeros)\n"; } /* * Error? */ if (errorMessage.isEmpty() == false) { WuQMessageBox::errorOk(this, errorMessage); return; } /* * Copy data to the focus */ loadFromDialogIntoFocusData(m_focus); /* * Project the focus */ if (m_projectCheckBox->isChecked()) { Brain* brain = GuiManager::get()->getBrain(); std::vector surfaceFiles = brain->getPrimaryAnatomicalSurfaceFiles(); try { SurfaceProjector projector(surfaceFiles); projector.projectFocus(0, m_focus); } catch (SurfaceProjectorException& spe) { CaretLogSevere(spe.whatString()); } } /* * Save project status */ s_previousFociProjectSelected = m_projectCheckBox->isChecked(); updateGraphicsAndUserInterface(); if (m_nameCompleterStringList.contains(name) == false) { m_nameCompleterStringList.append(name); m_nameCompleterStringList.sort(); m_nameCompleterStringListModel->setStringList(m_nameCompleterStringList); } /* * continue with OK button processing */ WuQDialogModal::okButtonClicked(); } /** * Update the graphics and user interface. */ void FociPropertiesEditorDialog::updateGraphicsAndUserInterface() { EventManager::get()->sendEvent(EventUserInterfaceUpdate().getPointer()); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } /** * Load data from the given focus into the dialog. * @param focus * Focus from which data is obtained. */ void FociPropertiesEditorDialog::loadFromFocusDataIntoDialog(const Focus* focus) { loadNameComboBox(focus->getName()); loadClassComboBox(focus->getClassName()); float xyz[3] = { 0.0, 0.0, 0.0 }; if (focus->getNumberOfProjections() > 0) { focus->getProjection(0)->getStereotaxicXYZ(xyz); } m_xCoordSpinBox->setValue(xyz[0]); m_yCoordSpinBox->setValue(xyz[1]); m_zCoordSpinBox->setValue(xyz[2]); m_commentTextEdit->setText(focus->getComment()); m_areaLineEdit->setText(focus->getArea()); m_geographyLineEdit->setText(focus->getGeography()); m_extentSpinBox->setValue(focus->getExtent()); m_regionOfInterestLineEdit->setText(focus->getRegionOfInterest()); m_statisticLineEdit->setText(focus->getStatistic()); } /** * Load data from dialog into the focus. * @param focus * Focus into which data is placed. */ void FociPropertiesEditorDialog::loadFromDialogIntoFocusData(Focus* focus) const { focus->setName(m_nameComboBox->getSelectedLabelName()); focus->setClassName(m_classComboBox->getSelectedLabelName()); const float xyz[3] = { m_xCoordSpinBox->value(), m_yCoordSpinBox->value(), m_zCoordSpinBox->value() }; CaretAssert(focus->getNumberOfProjections() > 0); focus->getProjection(0)->setStereotaxicXYZ(xyz); focus->setComment(m_commentTextEdit->toPlainText().trimmed()); focus->setArea(m_areaLineEdit->text().trimmed()); focus->setGeography(m_geographyLineEdit->text().trimmed()); focus->setExtent(m_extentSpinBox->value()); focus->setRegionOfInterest(m_regionOfInterestLineEdit->text().trimmed()); focus->setStatistic(m_statisticLineEdit->text().trimmed()); } /** * Display the class editor */ void FociPropertiesEditorDialog::displayClassEditor() { FociFile* fociFile = getSelectedFociFile(); if (fociFile == NULL) { WuQMessageBox::errorOk(this, "Focus file is not valid, use the New button to create a focus file."); return; } GiftiLabelTableEditor editor(fociFile, fociFile->getClassColorTable(), "Edit Class Attributes", GiftiLabelTableEditor::OPTION_NONE, this); const QString className = m_classComboBox->getSelectedLabelName(); if (className.isEmpty() == false) { editor.selectLabelWithName(className); } const int dialogResult = editor.exec(); loadClassComboBox(); if (dialogResult == GiftiLabelTableEditor::Accepted) { const QString selectedClassName = editor.getLastSelectedLabelName(); if (selectedClassName.isEmpty() == false) { m_classComboBox->setSelectedLabelName(selectedClassName); } } } /** * Display the name editor */ void FociPropertiesEditorDialog::displayNameEditor() { FociFile* fociFile = getSelectedFociFile(); if (fociFile == NULL) { WuQMessageBox::errorOk(this, "Focus file is not valid, use the New button to create a focus file."); return; } GiftiLabelTableEditor editor(fociFile, fociFile->getNameColorTable(), "Edit Name Attributes", GiftiLabelTableEditor::OPTION_UNASSIGNED_LABEL_HIDDEN, this); const QString name = this->m_nameComboBox->getSelectedLabelName(); if (name.isEmpty() == false) { editor.selectLabelWithName(name); } const int dialogResult = editor.exec(); this->loadNameComboBox(); if (dialogResult == GiftiLabelTableEditor::Accepted) { const QString selectedName = editor.getLastSelectedLabelName(); if (selectedName.isEmpty() == false) { m_nameComboBox->setSelectedLabelName(selectedName); } } } /** * @return Is the project checkbox selected? */ bool FociPropertiesEditorDialog::isProjectSelected() { return m_projectCheckBox->isChecked(); } /** * set the status of the project checkbox. * @param selected * New status. */ void FociPropertiesEditorDialog::setProjectSelected(const bool selected) { m_projectCheckBox->setChecked(selected); } workbench-1.1.1/src/GuiQt/FociPropertiesEditorDialog.h000066400000000000000000000110711255417355300227230ustar00rootroot00000000000000#ifndef __FOCI_PROPERTIES_EDITOR_DIALOG__H_ #define __FOCI_PROPERTIES_EDITOR_DIALOG__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretColorEnum.h" #include "WuQDialogModal.h" class QCheckBox; class QComboBox; class QDoubleSpinBox; class QLabel; class QLineEdit; class QStringListModel; class QTextEdit; namespace caret { class BrowserTabContent; class Focus; class FociFile; class GiftiLabelTableSelectionComboBox; class FociPropertiesEditorDialog : public WuQDialogModal { Q_OBJECT public: static bool createFocus(Focus* focus, BrowserTabContent* browserTabContent, QWidget* parent); static bool editFocus(FociFile* fociFile, Focus* focus, QWidget* parent); FociPropertiesEditorDialog(const QString& title, FociFile* fociFile, Focus* focus, const bool allowFociFileSelection, QWidget* parent = 0); virtual ~FociPropertiesEditorDialog(); FociFile* getSelectedFociFile(); void loadFromFocusDataIntoDialog(const Focus* focus); void loadFromDialogIntoFocusData(Focus* focus) const; bool isProjectSelected(); void setProjectSelected(const bool selected); static void deleteStaticMembers(); protected: virtual void okButtonClicked(); private slots: void displayClassEditor(); void displayNameEditor(); void fociFileSelected(); void newFociFileButtonClicked(); private: FociPropertiesEditorDialog(const FociPropertiesEditorDialog&); FociPropertiesEditorDialog& operator=(const FociPropertiesEditorDialog&); void loadFociFileComboBox(const FociFile* fociFile); void loadClassComboBox(const QString& name = ""); void loadNameComboBox(const QString& name = ""); void updateGraphicsAndUserInterface(); Focus* m_focus; QComboBox* m_fociFileSelectionComboBox; GiftiLabelTableSelectionComboBox* m_nameComboBox; GiftiLabelTableSelectionComboBox* m_classComboBox; QDoubleSpinBox* m_xCoordSpinBox; QDoubleSpinBox* m_yCoordSpinBox; QDoubleSpinBox* m_zCoordSpinBox; QTextEdit* m_commentTextEdit; QLineEdit* m_areaLineEdit; QLineEdit* m_geographyLineEdit; QDoubleSpinBox* m_extentSpinBox; QLineEdit* m_regionOfInterestLineEdit; QLineEdit* m_statisticLineEdit; QCheckBox* m_projectCheckBox; QStringList m_nameCompleterStringList; QStringListModel* m_nameCompleterStringListModel; static bool s_previousFociProjectSelected; /** * Previous selected foci file for creation of foci * NOTE: DO NOT DELETE THIS SINCE IT POINTS TO * AN EXISTING FOCI FILE */ static FociFile* s_previousCreateFociFile; /** Copy of previously created focus */ static Focus* s_previousCreateFocus; }; #ifdef __FOCI_PROPERTIES_EDITOR_DIALOG__DECLARE__ bool FociPropertiesEditorDialog::s_previousFociProjectSelected = true; FociFile* FociPropertiesEditorDialog::s_previousCreateFociFile = NULL; Focus* FociPropertiesEditorDialog::s_previousCreateFocus = NULL; #endif // __FOCI_PROPERTIES_EDITOR_DIALOG__DECLARE__ } // namespace #endif //__FOCI_PROPERTIES_EDITOR_DIALOG__H_ workbench-1.1.1/src/GuiQt/FociSelectionViewController.cxx000066400000000000000000000422351255417355300235050ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #include #include //#include #include #define __FOCI_SELECTION_VIEW_CONTROLLER_DECLARE__ #include "FociSelectionViewController.h" #undef __FOCI_SELECTION_VIEW_CONTROLLER_DECLARE__ #include "Brain.h" #include "BrainOpenGL.h" #include "BrowserTabContent.h" #include "CaretAssert.h" #include "GroupAndNameHierarchyViewController.h" #include "DisplayGroupEnumComboBox.h" #include "DisplayPropertiesFoci.h" #include "EnumComboBoxTemplate.h" #include "FeatureColoringTypeEnum.h" #include "EventGraphicsUpdateAllWindows.h" #include "EventManager.h" #include "EventUserInterfaceUpdate.h" #include "GuiManager.h" #include "SceneClass.h" #include "WuQDataEntryDialog.h" #include "WuQFactory.h" #include "WuQTabWidget.h" #include "WuQTrueFalseComboBox.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::FociSelectionViewController * \brief Widget for controlling display of foci * \ingroup GuiQt * * Widget for controlling the display of foci including * different display groups. */ /** * Constructor. */ FociSelectionViewController::FociSelectionViewController(const int32_t browserWindowIndex, QWidget* parent) : QWidget(parent) { m_browserWindowIndex = browserWindowIndex; QLabel* groupLabel = new QLabel("Group"); m_fociDisplayGroupComboBox = new DisplayGroupEnumComboBox(this); QObject::connect(m_fociDisplayGroupComboBox, SIGNAL(displayGroupSelected(const DisplayGroupEnum::Enum)), this, SLOT(fociDisplayGroupSelected(const DisplayGroupEnum::Enum))); QHBoxLayout* groupLayout = new QHBoxLayout(); groupLayout->addWidget(groupLabel); groupLayout->addWidget(m_fociDisplayGroupComboBox->getWidget()); groupLayout->addStretch(); m_fociDisplayCheckBox = new QCheckBox("Display Foci"); m_fociDisplayCheckBox->setToolTip("Enable the display of foci"); QObject::connect(m_fociDisplayCheckBox, SIGNAL(clicked(bool)), this, SLOT(processAttributesChanges())); QWidget* attributesWidget = this->createAttributesWidget(); QWidget* selectionWidget = this->createSelectionWidget(); m_tabWidget = new WuQTabWidget(WuQTabWidget::TAB_ALIGN_LEFT, this); m_tabWidget->addTab(attributesWidget, "Attributes"); m_tabWidget->addTab(selectionWidget, "Selection"); m_tabWidget->setCurrentWidget(attributesWidget); QVBoxLayout* layout = new QVBoxLayout(this); WuQtUtilities::setLayoutSpacingAndMargins(layout, 2, 2); layout->addLayout(groupLayout); layout->addSpacing(10); layout->addWidget(m_fociDisplayCheckBox); layout->addWidget(m_tabWidget->getWidget(), 0, Qt::AlignLeft); layout->addStretch(); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_USER_INTERFACE_UPDATE); FociSelectionViewController::allFociSelectionViewControllers.insert(this); } /** * Destructor. */ FociSelectionViewController::~FociSelectionViewController() { EventManager::get()->removeAllEventsFromListener(this); FociSelectionViewController::allFociSelectionViewControllers.erase(this); } QWidget* FociSelectionViewController::createSelectionWidget() { m_fociClassNameHierarchyViewController = new GroupAndNameHierarchyViewController(m_browserWindowIndex); return m_fociClassNameHierarchyViewController; } /** * @return The attributes widget. */ QWidget* FociSelectionViewController::createAttributesWidget() { m_fociContralateralCheckBox = new QCheckBox("Contralateral"); m_fociContralateralCheckBox->setToolTip("Enable display of foci from contralateral brain structure"); QObject::connect(m_fociContralateralCheckBox, SIGNAL(clicked(bool)), this, SLOT(processAttributesChanges())); m_pasteOntoSurfaceCheckBox = new QCheckBox("Paste Onto Surface"); m_pasteOntoSurfaceCheckBox->setToolTip("Place the foci onto the surface"); QObject::connect(m_pasteOntoSurfaceCheckBox, SIGNAL(clicked(bool)), this, SLOT(processAttributesChanges())); QLabel* coloringLabel = new QLabel("Coloring"); m_coloringTypeComboBox = new EnumComboBoxTemplate(this); m_coloringTypeComboBox->setup(); m_coloringTypeComboBox->getWidget()->setToolTip("Select the coloring assignment for foci"); QObject::connect(m_coloringTypeComboBox, SIGNAL(itemActivated()), this, SLOT(processAttributesChanges())); std::vector drawingTypeEnums; FociDrawingTypeEnum::getAllEnums(drawingTypeEnums); const int32_t numDrawingTypeEnums = static_cast(drawingTypeEnums.size()); QLabel* drawAsLabel = new QLabel("Draw As"); m_drawTypeComboBox = new QComboBox(); for (int32_t i = 0; i < numDrawingTypeEnums; i++) { FociDrawingTypeEnum::Enum drawType = drawingTypeEnums[i]; m_drawTypeComboBox->addItem(FociDrawingTypeEnum::toGuiName(drawType), (int)drawType); } m_drawTypeComboBox->setToolTip("Select the drawing style of foci"); QObject::connect(m_drawTypeComboBox, SIGNAL(activated(int)), this, SLOT(processAttributesChanges())); float minLineWidth = 0; float maxLineWidth = 1000; //BrainOpenGL::getMinMaxLineWidth(minLineWidth, // maxLineWidth); QLabel* pointSizeLabel = new QLabel("Symbol Diameter"); m_sizeSpinBox = WuQFactory::newDoubleSpinBox(); m_sizeSpinBox->setFixedWidth(80); m_sizeSpinBox->setRange(minLineWidth, maxLineWidth); m_sizeSpinBox->setSingleStep(1.0); m_sizeSpinBox->setDecimals(1); m_sizeSpinBox->setToolTip("Adjust the diameter of foci"); m_sizeSpinBox->setSuffix("mm"); QObject::connect(m_sizeSpinBox, SIGNAL(valueChanged(double)), this, SLOT(processAttributesChanges())); QWidget* gridWidget = new QWidget(); QGridLayout* gridLayout = new QGridLayout(gridWidget); WuQtUtilities::setLayoutSpacingAndMargins(gridLayout, 8, 2); int row = gridLayout->rowCount(); gridLayout->addWidget(m_fociContralateralCheckBox, row, 0, 1, 2); row++; gridLayout->addWidget(m_pasteOntoSurfaceCheckBox, row, 0, 1, 2); row++; gridLayout->addWidget(WuQtUtilities::createHorizontalLineWidget(), row, 0, 1, 2); row++; gridLayout->addWidget(coloringLabel, row, 0); gridLayout->addWidget(m_coloringTypeComboBox->getWidget(), row, 1); row++; gridLayout->addWidget(drawAsLabel, row, 0); gridLayout->addWidget(m_drawTypeComboBox , row, 1); row++; gridLayout->addWidget(pointSizeLabel, row, 0); gridLayout->addWidget(m_sizeSpinBox, row, 1); gridWidget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); QWidget* widget = new QWidget(); QVBoxLayout* layout = new QVBoxLayout(widget); layout->addWidget(gridWidget); layout->addStretch(); return widget; } /** * Called when a widget on the attributes page has * its value changed. */ void FociSelectionViewController::processAttributesChanges() { DisplayPropertiesFoci* dpf = GuiManager::get()->getBrain()->getDisplayPropertiesFoci(); const FeatureColoringTypeEnum::Enum selectedColoringType = m_coloringTypeComboBox->getSelectedItem(); const int selectedDrawTypeIndex = m_drawTypeComboBox->currentIndex(); const int drawTypeInteger = m_drawTypeComboBox->itemData(selectedDrawTypeIndex).toInt(); const FociDrawingTypeEnum::Enum selectedDrawingType = static_cast(drawTypeInteger); BrowserTabContent* browserTabContent = GuiManager::get()->getBrowserTabContentForBrowserWindow(m_browserWindowIndex, true); if (browserTabContent == NULL) { return; } const int32_t browserTabIndex = browserTabContent->getTabNumber(); const DisplayGroupEnum::Enum displayGroup = dpf->getDisplayGroupForTab(browserTabIndex); dpf->setDisplayed(displayGroup, browserTabIndex, m_fociDisplayCheckBox->isChecked()); dpf->setContralateralDisplayed(displayGroup, browserTabIndex, m_fociContralateralCheckBox->isChecked()); dpf->setPasteOntoSurface(displayGroup, browserTabIndex, m_pasteOntoSurfaceCheckBox->isChecked()); dpf->setColoringType(displayGroup, browserTabIndex, selectedColoringType); dpf->setFociSize(displayGroup, browserTabIndex, m_sizeSpinBox->value()); dpf->setDrawingType(displayGroup, browserTabIndex, selectedDrawingType); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); updateOtherFociViewControllers(); } /** * Called when the foci display group combo box is changed. */ void FociSelectionViewController::fociDisplayGroupSelected(const DisplayGroupEnum::Enum displayGroup) { /* * Update selected display group in model. */ BrowserTabContent* browserTabContent = GuiManager::get()->getBrowserTabContentForBrowserWindow(m_browserWindowIndex, false); if (browserTabContent == NULL) { return; } const int32_t browserTabIndex = browserTabContent->getTabNumber(); Brain* brain = GuiManager::get()->getBrain(); DisplayPropertiesFoci* dpf = brain->getDisplayPropertiesFoci(); dpf->setDisplayGroupForTab(browserTabIndex, displayGroup); /* * Since display group has changed, need to update controls */ updateFociViewController(); /* * Apply the changes. */ processFociSelectionChanges(); } /** * Update the foci widget. */ void FociSelectionViewController::updateFociViewController() { BrowserTabContent* browserTabContent = GuiManager::get()->getBrowserTabContentForBrowserWindow(m_browserWindowIndex, true); if (browserTabContent == NULL) { return; } const int32_t browserTabIndex = browserTabContent->getTabNumber(); Brain* brain = GuiManager::get()->getBrain(); DisplayPropertiesFoci* dpf = brain->getDisplayPropertiesFoci(); const DisplayGroupEnum::Enum displayGroup = dpf->getDisplayGroupForTab(browserTabIndex); // dpf->setDisplayGroupForTab(browserTabIndex, // m_fociDisplayGroupComboBox->getSelectedDisplayGroup()); setWindowTitle("Foci"); m_fociDisplayGroupComboBox->setSelectedDisplayGroup(dpf->getDisplayGroupForTab(browserTabIndex)); /*; * Get all of foci files. */ std::vector allFociFiles; const int32_t numberOfFociFiles = brain->getNumberOfFociFiles(); for (int32_t iff = 0; iff < numberOfFociFiles; iff++) { allFociFiles.push_back(brain->getFociFile(iff)); } /* * Update the class/name hierarchy */ m_fociClassNameHierarchyViewController->updateContents(allFociFiles, displayGroup); std::vector drawingTypeEnums; FociDrawingTypeEnum::getAllEnums(drawingTypeEnums); const int32_t numDrawingTypeEnums = static_cast(drawingTypeEnums.size()); m_fociDisplayCheckBox->setChecked(dpf->isDisplayed(displayGroup, browserTabIndex)); m_fociContralateralCheckBox->setChecked(dpf->isContralateralDisplayed(displayGroup, browserTabIndex)); m_pasteOntoSurfaceCheckBox->setChecked(dpf->isPasteOntoSurface(displayGroup, browserTabIndex)); m_coloringTypeComboBox->setSelectedItem(dpf->getColoringType(displayGroup, browserTabIndex)); const FociDrawingTypeEnum::Enum selectedDrawingType = dpf->getDrawingType(displayGroup, browserTabIndex); int32_t selectedDrawingTypeIndex = 0; for (int32_t i = 0; i < numDrawingTypeEnums; i++) { FociDrawingTypeEnum::Enum drawType = drawingTypeEnums[i]; if (drawType == selectedDrawingType) { selectedDrawingTypeIndex = i; } } m_drawTypeComboBox->setCurrentIndex(selectedDrawingTypeIndex); m_sizeSpinBox->blockSignals(true); m_sizeSpinBox->setValue(dpf->getFociSize(displayGroup, browserTabIndex)); m_sizeSpinBox->blockSignals(false); } /** * Update other foci view controllers. */ void FociSelectionViewController::updateOtherFociViewControllers() { for (std::set::iterator iter = FociSelectionViewController::allFociSelectionViewControllers.begin(); iter != FociSelectionViewController::allFociSelectionViewControllers.end(); iter++) { FociSelectionViewController* bsw = *iter; if (bsw != this) { bsw->updateFociViewController(); } } } /** * Gets called when foci selections are changed. */ void FociSelectionViewController::processFociSelectionChanges() { processSelectionChanges(); } /** * Issue update events after selections are changed. */ void FociSelectionViewController::processSelectionChanges() { updateOtherFociViewControllers(); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } /** * Receive events from the event manager. * * @param event * Event sent by event manager. */ void FociSelectionViewController::receiveEvent(Event* event) { bool doUpdate = false; if (event->getEventType() == EventTypeEnum::EVENT_USER_INTERFACE_UPDATE) { EventUserInterfaceUpdate* uiEvent = dynamic_cast(event); CaretAssert(uiEvent); if (uiEvent->isUpdateForWindow(m_browserWindowIndex)) { if (uiEvent->isFociUpdate() || uiEvent->isToolBoxUpdate()) { doUpdate = true; uiEvent->setEventProcessed(); } } } if (doUpdate) { updateFociViewController(); } } /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ SceneClass* FociSelectionViewController::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "FociSelectionViewController", 1); sceneClass->addClass(m_tabWidget->saveToScene(sceneAttributes, "m_tabWidget")); return sceneClass; } /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * SceneClass containing the state that was previously * saved and should be restored. */ void FociSelectionViewController::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } m_tabWidget->restoreFromScene(sceneAttributes, sceneClass->getClass("m_tabWidget")); } workbench-1.1.1/src/GuiQt/FociSelectionViewController.h000066400000000000000000000070631255417355300231320ustar00rootroot00000000000000#ifndef __FOCI_SELECTION_VIEW_CONTROLLER__H_ #define __FOCI_SELECTION_VIEW_CONTROLLER__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include "DisplayGroupEnum.h" #include "EventListenerInterface.h" #include "SceneableInterface.h" class QCheckBox; class QComboBox; class QDoubleSpinBox; namespace caret { class GroupAndNameHierarchyViewController; class DisplayGroupEnumComboBox; class EnumComboBoxTemplate; class WuQTabWidget; class FociSelectionViewController : public QWidget, public EventListenerInterface, public SceneableInterface { Q_OBJECT public: FociSelectionViewController(const int32_t browserWindowIndex, QWidget* parent = 0); virtual ~FociSelectionViewController(); void receiveEvent(Event* event); virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private slots: void processFociSelectionChanges(); void processSelectionChanges(); void fociDisplayGroupSelected(const DisplayGroupEnum::Enum); void processAttributesChanges(); private: FociSelectionViewController(const FociSelectionViewController&); FociSelectionViewController& operator=(const FociSelectionViewController&); void updateFociViewController(); void updateOtherFociViewControllers(); QWidget* createSelectionWidget(); QWidget* createAttributesWidget(); int32_t m_browserWindowIndex; GroupAndNameHierarchyViewController* m_fociClassNameHierarchyViewController; QCheckBox* m_fociDisplayCheckBox; QCheckBox* m_fociContralateralCheckBox; QCheckBox* m_pasteOntoSurfaceCheckBox; DisplayGroupEnumComboBox* m_fociDisplayGroupComboBox; EnumComboBoxTemplate* m_coloringTypeComboBox; QDoubleSpinBox* m_lineWidthSpinBox; QDoubleSpinBox* m_sizeSpinBox; QComboBox* m_drawTypeComboBox; WuQTabWidget* m_tabWidget; static std::set allFociSelectionViewControllers; }; #ifdef __FOCI_SELECTION_VIEW_CONTROLLER_DECLARE__ std::set FociSelectionViewController::allFociSelectionViewControllers; #endif // __FOCI_SELECTION_VIEW_CONTROLLER_DECLARE__ } // namespace #endif //__FOCI_SELECTION_VIEW_CONTROLLER__H_ workbench-1.1.1/src/GuiQt/GiftiLabelTableEditor.cxx000066400000000000000000001014661255417355300222030ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __GIFTI_LABEL_TABLE_EDITOR_DECLARE__ #include "GiftiLabelTableEditor.h" #undef __GIFTI_LABEL_TABLE_EDITOR_DECLARE__ #include #include #include #include #include #include #include #include #include #include #include #include "ApplicationInformation.h" #include "BorderFile.h" #include "Brain.h" #include "CaretAssert.h" #include "CaretMappableDataFile.h" #include "ColorEditorWidget.h" #include "EventGraphicsUpdateAllWindows.h" #include "EventManager.h" #include "EventSurfaceColoringInvalidate.h" #include "EventUserInterfaceUpdate.h" #include "FociFile.h" #include "GiftiLabel.h" #include "GiftiLabelTable.h" #include "GroupAndNameHierarchyItem.h" #include "GuiManager.h" #include "WuQDataEntryDialog.h" #include "WuQMessageBox.h" #include "WuQtUtilities.h" #include "WuQWidgetObjectGroup.h" using namespace caret; /** * \class caret::GiftiLabelTableEditor * \brief Dialog for editing a GIFTI lable table. * \ingroup GuiQt */ /** * Constructor for editing label table in a mappable data file. * * @param fociFile * Foci file whose color table being edited. As colors are edited, * the assigned foci will have their color validity invalidated. * @param giftiLabelTable * Label table being edited. * @param dialogTitle * Title for the dialog. * @param options * Bitwise OR'ed Options values. * @param parent * Parent on which this dialog is displayed. */ GiftiLabelTableEditor::GiftiLabelTableEditor(CaretMappableDataFile* caretMappableDataFile, const int32_t mapIndex, const AString& dialogTitle, const uint32_t options, QWidget* parent) : WuQDialogModal(dialogTitle, parent) { CaretAssert(caretMappableDataFile); CaretAssert(mapIndex >= 0); CaretAssert(mapIndex < caretMappableDataFile->getNumberOfMaps()); initializeDialog(caretMappableDataFile->getMapLabelTable(mapIndex), options); m_caretMappableDataFile = caretMappableDataFile; m_caretMappableDataFileMapIndex = mapIndex; } /** * Constructor for a label table in a foci file. * * @param fociFile * Foci file whose color table being edited. As colors are edited, * the assigned foci will have their color validity invalidated. * @param giftiLabelTable * Label table being edited. * @param dialogTitle * Title for the dialog. * @param options * Bitwise OR'ed Options values. * @param parent * Parent on which this dialog is displayed. */ GiftiLabelTableEditor::GiftiLabelTableEditor(FociFile* fociFile, GiftiLabelTable* giftiLableTable, const AString& dialogTitle, const uint32_t options, QWidget* parent) : WuQDialogModal(dialogTitle, parent) { CaretAssert(fociFile); initializeDialog(giftiLableTable, options); m_fociFile = fociFile; } /** * Constructor for a label table in a border file. * * @param borderFile * Border file whose color table being edited. As colors are edited, * the assigned borders will have their color validity invalidated. * @param giftiLabelTable * Label table being edited. * @param dialogTitle * Title for the dialog. * @param options * Bitwise OR'ed Options values. * @param parent * Parent on which this dialog is displayed. */ GiftiLabelTableEditor::GiftiLabelTableEditor(BorderFile* borderFile, GiftiLabelTable* giftiLableTable, const AString& dialogTitle, const uint32_t options, QWidget* parent) : WuQDialogModal(dialogTitle, parent) { CaretAssert(borderFile); initializeDialog(giftiLableTable, options); m_borderFile = borderFile; } /** * Destructor. */ GiftiLabelTableEditor::~GiftiLabelTableEditor() { if (m_undoGiftiLabel != NULL) { delete m_undoGiftiLabel; m_undoGiftiLabel = NULL; } } /** * Initialize the dialog. * * @param giftiLabelTable * Label table being edited. * @param options * Bitwise OR'ed Options values. */ void GiftiLabelTableEditor::initializeDialog(GiftiLabelTable* giftiLabelTable, const uint32_t options) { m_borderFile = NULL; m_caretMappableDataFile = NULL; m_caretMappableDataFileMapIndex = -1; m_fociFile = NULL; m_showUnassignedLabelInEditor = true; if (options & OPTION_UNASSIGNED_LABEL_HIDDEN) { m_showUnassignedLabelInEditor = false; } CaretAssert(giftiLabelTable); m_giftiLableTable = giftiLabelTable; m_undoGiftiLabel = NULL; /* * Sorting */ QLabel* sortByLabel = new QLabel("Sort by "); m_sortLabelsByComboBox = new QComboBox(); m_sortLabelsByComboBox->addItem(s_SORT_COMBO_BOX_NAME_BY_KEY); m_sortLabelsByComboBox->addItem(s_SORT_COMBO_BOX_NAME_BY_NAME); m_sortLabelsByComboBox->setCurrentIndex(1); QObject::connect(m_sortLabelsByComboBox, SIGNAL(activated(int)), this, SLOT(sortingLabelsActivated())); /* * Sorting layout */ QHBoxLayout* sortingLayout = new QHBoxLayout(); sortingLayout->addWidget(sortByLabel); sortingLayout->addWidget(m_sortLabelsByComboBox); sortingLayout->addStretch(); /* * List widget for editing labels. */ m_labelSelectionListWidget = new QListWidget(); m_labelSelectionListWidget->setSelectionMode(QListWidget::SingleSelection); // QObject::connect(m_labelSelectionListWidget, SIGNAL(currentRowChanged(int)), // this, SLOT(listWidgetLabelSelected())); QObject::connect(m_labelSelectionListWidget, SIGNAL(itemClicked(QListWidgetItem*)), //SIGNAL(currentRowChanged(int)), this, SLOT(listWidgetLabelSelected())); /* * New color button. */ QPushButton* newPushButton = WuQtUtilities::createPushButton("New", "Create a new entry", this, SLOT(newButtonClicked())); /* * Undo Edit button. */ QPushButton* undoPushButton = WuQtUtilities::createPushButton("Undo Edit", "Create a new entry", this, SLOT(undoButtonClicked())); /* * Delete button. */ QPushButton* deletePushButton = WuQtUtilities::createPushButton("Delete", "Delete the selected entry", this, SLOT(deleteButtonClicked())); /* * Color editor widget */ m_colorEditorWidget = new ColorEditorWidget(); QObject::connect(m_colorEditorWidget, SIGNAL(colorChanged(const float*)), this, SLOT(colorEditorColorChanged(const float*))); /* * Label name line edit */ QLabel* nameLabel = new QLabel("Name "); m_labelNameLineEdit = new QLineEdit(); QObject::connect(m_labelNameLineEdit, SIGNAL(textEdited(const QString&)), this, SLOT(labelNameLineEditTextEdited(const QString&))); WuQtUtilities::setToolTipAndStatusTip(m_labelNameLineEdit, "Edit the name"); /* * Key value editing */ QLabel* keyLabel = NULL; m_keyValueLineEdit = NULL; m_changeKeyValueToolButton = NULL; if (options & OPTION_ADD_KEY_EDITING) { keyLabel = new QLabel("Key "); m_keyValueLineEdit = new QLineEdit(); m_keyValueLineEdit->setFixedWidth(100); m_keyValueLineEdit->setAlignment(Qt::AlignRight); m_keyValueLineEdit->setReadOnly(true); m_changeKeyValueToolButton = new QToolButton(); m_changeKeyValueToolButton->setText("Change..."); m_changeKeyValueToolButton->setToolTip("Change a label's key"); QObject::connect(m_changeKeyValueToolButton, SIGNAL(clicked(bool)), this, SLOT(changeLabelKeyLockButtonClicked())); } /* * Layout for name and key */ QGridLayout* nameKeyLayout = new QGridLayout(); nameKeyLayout->setColumnStretch(0, 0); nameKeyLayout->setColumnStretch(1, 0); nameKeyLayout->setColumnStretch(2, 100); nameKeyLayout->addWidget(nameLabel, 0, 0); nameKeyLayout->addWidget(m_labelNameLineEdit, 0, 1, 1, 2); if (options & OPTION_ADD_KEY_EDITING) { CaretAssert(keyLabel); CaretAssert(m_keyValueLineEdit); CaretAssert(m_changeKeyValueToolButton); nameKeyLayout->addWidget(keyLabel, 1, 0); nameKeyLayout->addWidget(m_keyValueLineEdit, 1, 1); nameKeyLayout->addWidget(m_changeKeyValueToolButton, 1, 2, Qt::AlignLeft); } /* * Layout for buttons */ QHBoxLayout* buttonsLayout = new QHBoxLayout(); buttonsLayout->addWidget(newPushButton); buttonsLayout->addStretch(); buttonsLayout->addWidget(undoPushButton); buttonsLayout->addStretch(); buttonsLayout->addWidget(deletePushButton); /* * Layout items in dialog */ QWidget* widget = new QWidget(); QVBoxLayout* layout = new QVBoxLayout(widget); WuQtUtilities::setLayoutSpacingAndMargins(layout, 4, 2); layout->addLayout(sortingLayout); layout->addWidget(m_labelSelectionListWidget); layout->addLayout(buttonsLayout); layout->addWidget(WuQtUtilities::createHorizontalLineWidget()); layout->addLayout(nameKeyLayout); layout->addWidget(m_colorEditorWidget); setCentralWidget(widget, WuQDialog::SCROLL_AREA_NEVER); m_editingGroup = new WuQWidgetObjectGroup(this); m_editingGroup->add(undoPushButton); m_editingGroup->add(deletePushButton); m_editingGroup->add(nameLabel); m_editingGroup->add(m_labelNameLineEdit); if (options & OPTION_ADD_KEY_EDITING) { CaretAssert(keyLabel); CaretAssert(m_keyValueLineEdit); CaretAssert(m_changeKeyValueToolButton); m_editingGroup->add(keyLabel); m_editingGroup->add(m_keyValueLineEdit); m_editingGroup->add(m_changeKeyValueToolButton); } m_editingGroup->add(m_colorEditorWidget); loadLabels("", false); m_applyPushButton = NULL; if (options & OPTION_ADD_APPLY_BUTTON) { m_applyPushButton = addUserPushButton("Apply", QDialogButtonBox::ApplyRole); } //setOkButtonText("Close"); //setCancelButtonText(""); /* * No auto default button processing (Qt highlights button) */ disableAutoDefaultForAllPushButtons(); std::map::iterator lastEditIter = s_previousSelections.find(m_giftiLableTable); if (lastEditIter != s_previousSelections.end()) { const PreviousSelections& ps = lastEditIter->second; const int sortIndex = m_sortLabelsByComboBox->findText(ps.m_sortingName); if (sortIndex >= 0) { m_sortLabelsByComboBox->setCurrentIndex(sortIndex); } sortingLabelsActivated(); selectLabelWithName(ps.m_selectedLabelName); } } /** * Called when name line edit text is changed. * @param text * Text currently in the line edit. */ void GiftiLabelTableEditor::labelNameLineEditTextEdited(const QString& text) { GiftiLabel* gl = getSelectedLabel(); if (gl != NULL) { gl->setName(text); } QListWidgetItem* selectedItem = m_labelSelectionListWidget->currentItem(); if (selectedItem != NULL) { selectedItem->setText(gl->getNameAndKeyForLabelEditor()); } m_lastSelectedLabelName = text; } /** * @return * The last name that was selected. */ AString GiftiLabelTableEditor::getLastSelectedLabelName() const { return m_lastSelectedLabelName; } /** * Select the label with the given name. * @param labelName * Name of label that is to be selected. */ void GiftiLabelTableEditor::selectLabelWithName(const AString& labelName) { const GiftiLabel* gl = m_giftiLableTable->getLabel(labelName); if (gl != NULL) { const AString keyLabelName = gl->getNameAndKeyForLabelEditor(); QList itemsWithLabelName = m_labelSelectionListWidget->findItems(keyLabelName, Qt::MatchExactly); if ( ! itemsWithLabelName.empty()) { QListWidgetItem* item = itemsWithLabelName.at(0); m_labelSelectionListWidget->setCurrentItem(item); listWidgetLabelSelected(); } } } ///** // * Called when a label in the list widget is selected. // * @param row // * Row of label selected. // */ //void //GiftiLabelTableEditor::listWidgetLabelSelected(int /*row*/) //{ // if (m_undoGiftiLabel != NULL) { // delete m_undoGiftiLabel; // m_undoGiftiLabel = NULL; // } // // bool isEditingAllowed = false; // GiftiLabel* gl = getSelectedLabel(); // if (gl != NULL) { // const bool isUnassignedLabel = (gl->getKey() == m_giftiLableTable->getUnassignedLabelKey()); // float rgba[4]; // gl->getColor(rgba); // m_colorEditorWidget->setColor(rgba); // m_labelNameLineEdit->setText(gl->getName()); // if (m_keyValueLabel != NULL) { // m_keyValueLabel->setNum(gl->getKey()); // } // // m_lastSelectedLabelName = gl->getName(); // // if (isUnassignedLabel) { // m_undoGiftiLabel = NULL; // } // else { // m_undoGiftiLabel = new GiftiLabel(*gl); // isEditingAllowed = true; // } // } // else { // m_lastSelectedLabelName = ""; // if (m_keyValueLabel != NULL) { // m_keyValueLabel->setText(""); // } // // } // // m_editingGroup->setEnabled(isEditingAllowed); //} /** * Called when a label in the list widget is selected. */ void GiftiLabelTableEditor::listWidgetLabelSelected() { if (m_undoGiftiLabel != NULL) { delete m_undoGiftiLabel; m_undoGiftiLabel = NULL; } bool isEditingAllowed = false; GiftiLabel* gl = getSelectedLabel(); if (gl != NULL) { const bool isUnassignedLabel = (gl->getKey() == m_giftiLableTable->getUnassignedLabelKey()); float rgba[4]; gl->getColor(rgba); m_colorEditorWidget->setColor(rgba); m_labelNameLineEdit->setText(gl->getName()); if (m_keyValueLineEdit != NULL) { m_keyValueLineEdit->setText(AString::number(gl->getKey())); } m_lastSelectedLabelName = gl->getName(); if (isUnassignedLabel) { m_undoGiftiLabel = NULL; } else { m_undoGiftiLabel = new GiftiLabel(*gl); isEditingAllowed = true; } } else { m_lastSelectedLabelName = ""; if (m_keyValueLineEdit != NULL) { m_keyValueLineEdit->setText(""); } } //m_editingGroup->setEnabled(isEditingAllowed); allowLabelDataEditing(isEditingAllowed); } /** * Called when a change is made in the color editor. * @param rgba * New RGBA values. */ void GiftiLabelTableEditor::colorEditorColorChanged(const float* rgba) { QListWidgetItem* selectedItem = m_labelSelectionListWidget->currentItem(); if (selectedItem != NULL) { setWidgetItemIconColor(selectedItem, rgba); } GiftiLabel* gl = getSelectedLabel(); if (gl != NULL) { gl->setColor(rgba); if (m_fociFile != NULL) { m_fociFile->invalidateAllAssignedColors(); } else if (m_borderFile != NULL) { m_borderFile->invalidateAllAssignedColors(); } else if (m_caretMappableDataFile != NULL) { VolumeFile* volumeFile = dynamic_cast(m_caretMappableDataFile); if (volumeFile != NULL) { volumeFile->clearVoxelColoringForMap(m_caretMappableDataFileMapIndex); } GroupAndNameHierarchyItem* item = gl->getGroupNameSelectionItem(); if (item != NULL) { item->setIconColorRGBA(rgba); } } } } /** * Called when label sorting is changed. */ void GiftiLabelTableEditor::sortingLabelsActivated() { AString selectedLabelName; const GiftiLabel* gl = getSelectedLabel(); if (gl != NULL) { selectedLabelName = gl->getName(); } loadLabels(selectedLabelName, false); } /** * Load labels into the list widget. * * @param selectedName * If not empty, select the label with this name * @param usePreviouslySelectedIndex * If true, use selected index prior to reloading list widget. */ void GiftiLabelTableEditor::loadLabels(const AString& selectedNameIn, const bool usePreviouslySelectedIndex) { m_labelSelectionListWidget->blockSignals(true); int32_t previousSelectedIndex = -1; if (usePreviouslySelectedIndex) { previousSelectedIndex = m_labelSelectionListWidget->currentRow(); } // const int32_t invalidLabelKey = GiftiLabel::getInvalidLabelKey(); // const GiftiLabel* invalidLabel = m_giftiLableTable->getLabel(invalidLabelKey); const GiftiLabel* selectedLabel = getSelectedLabel(); AString selectedName; if (selectedLabel != NULL) { selectedName = selectedLabel->getName(); } if ( ! selectedNameIn.isEmpty()) { const GiftiLabel* selectedNameInLabel = m_giftiLableTable->getLabel(selectedNameIn); if (selectedNameInLabel != NULL) { selectedName = selectedNameInLabel->getName(); } } m_labelSelectionListWidget->clear(); int defaultIndex = -1; const int32_t unassignedLabelKey = m_giftiLableTable->getUnassignedLabelKey(); std::vector keys; const QString sortName = m_sortLabelsByComboBox->currentText(); if (sortName == s_SORT_COMBO_BOX_NAME_BY_KEY) { m_giftiLableTable->getKeys(keys); } else if (sortName == s_SORT_COMBO_BOX_NAME_BY_NAME) { keys = m_giftiLableTable->getLabelKeysSortedByName(); } else { CaretAssertMessage(0, "Invalid sort by name"); } for (std::vector::iterator keyIterator = keys.begin(); keyIterator != keys.end(); keyIterator++) { const int32_t key = *keyIterator; if ( ! m_showUnassignedLabelInEditor) { if (key == unassignedLabelKey) { continue; } } const GiftiLabel* gl = m_giftiLableTable->getLabel(key); float rgba[4]; gl->getColor(rgba); QString keyAndNameText(QString::number(gl->getKey()).rightJustified(4, ' ', false) + ": " + (gl->getName())); QListWidgetItem* colorItem = new QListWidgetItem(keyAndNameText); setWidgetItemIconColor(colorItem, rgba); colorItem->setData(Qt::UserRole, qVariantFromValue((void*)gl)); m_labelSelectionListWidget->addItem(colorItem); if (selectedName == gl->getName()) { defaultIndex = m_labelSelectionListWidget->count() - 1; } } if (usePreviouslySelectedIndex) { defaultIndex = previousSelectedIndex; if (defaultIndex >= m_labelSelectionListWidget->count()) { defaultIndex--; } } if (usePreviouslySelectedIndex) { if (defaultIndex < 0) { if (m_labelSelectionListWidget->count() > 0) { defaultIndex = 0; } } } m_labelSelectionListWidget->blockSignals(false); if (defaultIndex >= 0) { m_labelSelectionListWidget->setCurrentRow(defaultIndex); } else { //m_editingGroup->setEnabled(false); allowLabelDataEditing(false); } } /** * Allow editing of label data. * * @param allowEditingFlag * If true allow editing of label data. */ void GiftiLabelTableEditor::allowLabelDataEditing(const bool allowEditingFlag) { m_editingGroup->setEnabled(allowEditingFlag); } /** * Set the Icon color for the item. * @param item * The list widget item. * @param rgba * RGBA values. */ void GiftiLabelTableEditor::setWidgetItemIconColor(QListWidgetItem* item, const float rgba[4]) { QColor color; color.setRedF(rgba[0]); color.setGreenF(rgba[1]); color.setBlueF(rgba[2]); color.setAlphaF(1.0); QPixmap pixmap(14, 14); pixmap.fill(color); QIcon colorIcon(pixmap); item->setIcon(colorIcon); } /** * @return The selected label or NULL if * no label is selected. */ GiftiLabel* GiftiLabelTableEditor::getSelectedLabel() { GiftiLabel* gl = NULL; QListWidgetItem* selectedItem = m_labelSelectionListWidget->currentItem(); if (selectedItem != NULL) { void* pointer = selectedItem->data(Qt::UserRole).value(); gl = (GiftiLabel*)pointer; } return gl; } /** * Called to create a new label. */ void GiftiLabelTableEditor::newButtonClicked() { /* * Make sure default name does not already exist */ AString name = "NewName_"; for (int i = 1; i < 10000; i++) { const AString testName = name + QString::number(i); if (m_giftiLableTable->getLabel(testName) == NULL) { name = testName; break; } } float rgba[4] = { 0.0, 0.0, 0.0, 1.0 }; m_giftiLableTable->addLabel(name, rgba[0], rgba[1], rgba[2], rgba[3]); loadLabels(name, false); // m_labelNameLineEdit->grabKeyboard(); // m_labelNameLineEdit->grabMouse(); listWidgetLabelSelected(); m_labelNameLineEdit->setFocus(); m_labelNameLineEdit->selectAll(); colorEditorColorChanged(rgba); } /** * Called when change label key lock button is clicked. * * @param checked * Checked status of button. */ void GiftiLabelTableEditor::changeLabelKeyLockButtonClicked() { if (m_changeKeyValueToolButton == NULL) { return; } const GiftiLabel* selectedLabel = getSelectedLabel(); if (selectedLabel == NULL) { return; } if (s_displayKeyEditingWarningFlag) { s_displayKeyEditingWarningFlag = false; const AString text("Are you sure that you want to edit label keys?"); const AString infoText("Brainordinate values are not changed in the label type file\n" "and changing label keys may cause label data to display\n" "incorrectly.\n" "\n" "This warning will not be displayed again until " + ApplicationInformation().getName() + " is restarted."); if ( ! WuQMessageBox::warningOkCancel(m_changeKeyValueToolButton, text, infoText)) { return; } } const AString labelName = selectedLabel->getName(); ChangeLabelKeyDialog changeLabelDialog(m_giftiLableTable, selectedLabel, m_changeKeyValueToolButton); if (changeLabelDialog.exec() == ChangeLabelKeyDialog::Accepted) { loadLabels(labelName, false); processApplyButton(); } } /** * Called to undo changes to selected label. */ void GiftiLabelTableEditor::undoButtonClicked() { if (m_undoGiftiLabel != NULL) { labelNameLineEditTextEdited(m_undoGiftiLabel->getName()); float rgba[4]; m_undoGiftiLabel->getColor(rgba); colorEditorColorChanged(rgba); listWidgetLabelSelected(); } } /** * Called to delete the label. */ void GiftiLabelTableEditor::deleteButtonClicked() { GiftiLabel* gl = getSelectedLabel(); if (gl != NULL) { if (WuQMessageBox::warningOkCancel(this, "Delete " + gl->getName())) { m_giftiLableTable->deleteLabel(gl); loadLabels("", true); listWidgetLabelSelected(); } } } /** * Gets called when a button this dialog added is clicked. * * @param userPushButton * Button that was clicked. */ WuQDialogModal::DialogUserButtonResult GiftiLabelTableEditor::userButtonPressed(QPushButton* userPushButton) { DialogUserButtonResult result = RESULT_NONE; if (userPushButton == m_applyPushButton) { processApplyButton(); result = RESULT_NONE; } else { result = WuQDialogModal::userButtonPressed(userPushButton); } return result; } /** * Process as if the apply button was pressed. * Apply is like OK, except that the dialog remains open. */ void GiftiLabelTableEditor::processApplyButton() { if (m_caretMappableDataFile != NULL) { const PaletteFile* paletteFile = GuiManager::get()->getBrain()->getPaletteFile(); m_caretMappableDataFile->updateScalarColoringForMap(m_caretMappableDataFileMapIndex, paletteFile); } EventManager::get()->sendEvent(EventSurfaceColoringInvalidate().getPointer()); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); EventManager::get()->sendEvent(EventUserInterfaceUpdate().getPointer()); } /** * Called when the OK button is clicked. */ void GiftiLabelTableEditor::okButtonClicked() { processApplyButton(); const GiftiLabel* gl = getSelectedLabel(); const AString labelName = (gl != NULL) ? gl->getName() : ""; const AString sortingName = m_sortLabelsByComboBox->currentText(); std::map::iterator lastEditIter = s_previousSelections.find(m_giftiLableTable); if (lastEditIter != s_previousSelections.end()) { PreviousSelections& ps = lastEditIter->second; ps.m_selectedLabelName = labelName; ps.m_sortingName = sortingName; } else { PreviousSelections ps; ps.m_selectedLabelName = labelName; ps.m_sortingName = sortingName; s_previousSelections.insert(std::make_pair(m_giftiLableTable, ps)); } WuQDialogModal::okButtonClicked(); } /* ==================================================================================================================== */ /** * Dialog for changing a label's key. * * @param giftiLabelTable * Label table that is being edited. * @param giftiLabel * Label that may have its key changed. * @param parent * Parent on which this dialog is displayed. */ ChangeLabelKeyDialog::ChangeLabelKeyDialog(GiftiLabelTable* giftiLabelTable, const GiftiLabel* giftiLabel, QWidget* parent) : WuQDialogModal("Change Label Key", parent), m_giftiLabelTable(giftiLabelTable), m_giftiLabel(giftiLabel) { QLabel* nameLabel = new QLabel("Name: "); QLabel* nameTextLabel = new QLabel(giftiLabel->getName()); QLabel* keyLabel = new QLabel("Key: "); QLabel* keyValueLabel = new QLabel(AString::number(giftiLabel->getKey())); QLabel* newKeyLabel = new QLabel("New Key: "); m_labelKeyLineEdit = new QLineEdit(); m_labelKeyLineEdit->setFixedWidth(100); m_labelKeyLineEdit->setValidator(new QIntValidator(0, 999999, m_labelKeyLineEdit)); m_labelKeyLineEdit->setText(keyValueLabel->text()); QWidget* widget = new QWidget(); QGridLayout* gridLayout = new QGridLayout(widget); gridLayout->addWidget(nameLabel, 0, 0); gridLayout->addWidget(nameTextLabel, 0, 1, Qt::AlignLeft); gridLayout->addWidget(keyLabel, 1, 0); gridLayout->addWidget(keyValueLabel, 1, 1, Qt::AlignLeft); gridLayout->addWidget(newKeyLabel, 2, 0); gridLayout->addWidget(m_labelKeyLineEdit, 2, 1, Qt::AlignLeft); setCentralWidget(widget, WuQDialog::SCROLL_AREA_NEVER); } /** * Destructor. */ ChangeLabelKeyDialog::~ChangeLabelKeyDialog() { } /** * Called when ok button is clicked. */ void ChangeLabelKeyDialog::okButtonClicked() { const AString keyText = m_labelKeyLineEdit->text().trimmed(); if ( ! keyText.isEmpty()) { const int32_t newKey = keyText.toInt(); const int32_t oldKey = m_giftiLabel->getKey(); if (newKey != oldKey) { const AString oldKeyMsg("Any brainordinates assigned to \"" + m_giftiLabel->getName() + "\" will no long receive any coloring since there is no longer a label matching the brainordinates' value."); const GiftiLabel* newKeyLabel = m_giftiLabelTable->getLabel(newKey); if (newKeyLabel != NULL) { const AString msg("WARNING: Each label must have a unique key.\n" "\n" "\n" "\n" "In addition:\n" " (1) A label named \"" + newKeyLabel->getName() + "\" is assigned the key " + AString::number(newKey) + " and this label will be removed.\n\n" " (2) Any brainordinates assigned to \"" + newKeyLabel->getName() + "\" will be assigned to \"" + m_giftiLabel->getName() + "\".\n\n" + " (3) " + oldKeyMsg); if (WuQMessageBox::warningOkCancel(this, msg)) { m_giftiLabelTable->changeLabelKey(oldKey, newKey); } else { return; } } else { if (WuQMessageBox::warningOkCancel(this, ("WARNING: " + oldKeyMsg))) { m_giftiLabelTable->changeLabelKey(oldKey, newKey); } else { return; } } } } WuQDialogModal::okButtonClicked(); } ///** // * // */ //int32_t //ChangeLabelKeyDialog::getNewKeyValue() const //{ // //} // workbench-1.1.1/src/GuiQt/GiftiLabelTableEditor.h000066400000000000000000000157561255417355300216360ustar00rootroot00000000000000#ifndef __GIFTI_LABEL_TABLE_EDITOR__H_ #define __GIFTI_LABEL_TABLE_EDITOR__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretPointer.h" #include "WuQDialogModal.h" class QAction; class QComboBox; class QLabel; class QLineEdit; class QListWidget; class QListWidgetItem; class QPushButton; class QToolButton; namespace caret { class BorderFile; class CaretMappableDataFile; class ColorEditorWidget; class FociFile; class GiftiLabel; class GiftiLabelTable; class WuQWidgetObjectGroup; class GiftiLabelTableEditor : public WuQDialogModal { Q_OBJECT public: enum Options { /** * No options */ OPTION_NONE = 0, /** * Hide the unassigned label so that it is not shown in editor. * May be bitwise OR'ed with other options. */ OPTION_UNASSIGNED_LABEL_HIDDEN = 1, /** * Add an apply button so that the graphics windows can be * updated without having to close the dialog. */ OPTION_ADD_APPLY_BUTTON = 2, /** * Add GUI components that allow the user to edit the * key assigned to the selected label. */ OPTION_ADD_KEY_EDITING = 4 }; // GiftiLabelTableEditor(GiftiLabelTable* giftiLableTable, // const AString& dialogTitle, // const uint32_t options, // QWidget* parent); GiftiLabelTableEditor(CaretMappableDataFile* caretMappableDataFile, const int32_t mapIndex, const AString& dialogTitle, const uint32_t options, QWidget* parent); GiftiLabelTableEditor(FociFile* fociFile, GiftiLabelTable* giftiLableTable, const AString& dialogTitle, const uint32_t options, QWidget* parent); GiftiLabelTableEditor(BorderFile* borderFile, GiftiLabelTable* giftiLableTable, const AString& dialogTitle, const uint32_t options, QWidget* parent); virtual ~GiftiLabelTableEditor(); AString getLastSelectedLabelName() const; void selectLabelWithName(const AString& labelName); private: GiftiLabelTableEditor(const GiftiLabelTableEditor&); GiftiLabelTableEditor& operator=(const GiftiLabelTableEditor&); private slots: void newButtonClicked(); void deleteButtonClicked(); void undoButtonClicked(); void changeLabelKeyLockButtonClicked(); // void listWidgetLabelSelected(int row); void listWidgetLabelSelected(); void colorEditorColorChanged(const float*); void labelNameLineEditTextEdited(const QString&); void sortingLabelsActivated(); protected: virtual void okButtonClicked(); DialogUserButtonResult userButtonPressed(QPushButton* userPushButton); private: void initializeDialog(GiftiLabelTable* giftiLabelTable, const uint32_t options); void loadLabels(const AString& selectedName, const bool usePreviouslySelectedIndex); GiftiLabel* getSelectedLabel(); void setWidgetItemIconColor(QListWidgetItem* item, const float rgba[4]); void processApplyButton(); void allowLabelDataEditing(const bool allowEditingFlag); QListWidget* m_labelSelectionListWidget; BorderFile* m_borderFile; FociFile* m_fociFile; CaretMappableDataFile* m_caretMappableDataFile; int32_t m_caretMappableDataFileMapIndex; GiftiLabelTable* m_giftiLableTable; ColorEditorWidget* m_colorEditorWidget; QLineEdit* m_labelNameLineEdit; QLineEdit* m_keyValueLineEdit; QToolButton* m_changeKeyValueToolButton; AString m_lastSelectedLabelName; GiftiLabel* m_undoGiftiLabel; bool m_showUnassignedLabelInEditor; WuQWidgetObjectGroup* m_editingGroup; QPushButton* m_applyPushButton; QComboBox* m_sortLabelsByComboBox; struct PreviousSelections { AString m_sortingName; AString m_selectedLabelName; }; static std::map s_previousSelections; static const AString s_SORT_COMBO_BOX_NAME_BY_KEY; static const AString s_SORT_COMBO_BOX_NAME_BY_NAME; static bool s_displayKeyEditingWarningFlag; }; class ChangeLabelKeyDialog : public WuQDialogModal { Q_OBJECT public: ChangeLabelKeyDialog(GiftiLabelTable* giftiLabelTable, const GiftiLabel* giftiLabel, QWidget* parent); ~ChangeLabelKeyDialog(); // int32_t getNewKeyValue() const; protected: virtual void okButtonClicked(); private: GiftiLabelTable* m_giftiLabelTable; const GiftiLabel* m_giftiLabel; QLineEdit* m_labelKeyLineEdit; }; #ifdef __GIFTI_LABEL_TABLE_EDITOR_DECLARE__ std::map GiftiLabelTableEditor::s_previousSelections; const AString GiftiLabelTableEditor::s_SORT_COMBO_BOX_NAME_BY_KEY = "Key"; const AString GiftiLabelTableEditor::s_SORT_COMBO_BOX_NAME_BY_NAME = "Name"; bool GiftiLabelTableEditor::s_displayKeyEditingWarningFlag = true; #endif // __GIFTI_LABEL_TABLE_EDITOR_DECLARE__ } // namespace #endif //__GIFTI_LABEL_TABLE_EDITOR__H_ workbench-1.1.1/src/GuiQt/GiftiLabelTableSelectionComboBox.cxx000066400000000000000000000262561255417355300243360ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __GIFTI_LABEL_TABLE_SELECTION_COMBO_BOX_DECLARE__ #include "GiftiLabelTableSelectionComboBox.h" #undef __GIFTI_LABEL_TABLE_SELECTION_COMBO_BOX_DECLARE__ #include #include #include #include "CaretAssert.h" #include "CaretLogger.h" #include "GiftiLabel.h" #include "GiftiLabelTable.h" using namespace caret; /** * \class caret::GiftiLabelTableSelectionComboBox * \brief Combo box for selection of a gifti label. * \ingroup GuiQt */ /** * Constructor. * * @param parent * Parent of this object. */ GiftiLabelTableSelectionComboBox::GiftiLabelTableSelectionComboBox(QObject* parent) : WuQWidget(parent) { m_ignoreInsertedRowsFlag = true; m_giftiLabelTable = NULL; m_unassignedLabelTextOverride = ""; m_comboBox = new QComboBox(); m_comboBox->setMaxVisibleItems(20); QObject::connect(m_comboBox, SIGNAL(activated(int)), this, SLOT(itemActivated(int))); QObject::connect(m_comboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(currentIndexChanged(int))); #ifdef CARET_OS_MACOSX /* * On Mac, use a windows combo box so that * the pop-up is not a list the covers the * full vertical dimension of the monitor. */ QStyle* style = QStyleFactory::create("Windows"); if (style != NULL) { m_comboBox->setStyle(style); //QPalette macPalette(QPalette(m_comboBox->palette())); //m_comboBox->setPalette(macPalette); } // QStringList styleNames; // //styleNames << "Windows" << "Plastique"; // QStringListIterator styleNameIterator(styleNames); // while (styleNameIterator.hasNext()) { // style = QStyleFactory::create(styleNameIterator.next()); // if (style != NULL) { // break; // } // } #endif // CARET_OS_MACOSX QStyle::SH_ComboBox_Popup const bool allowEditingFlag = false; if (allowEditingFlag) { m_comboBox->setEditable(true); m_comboBox->setDuplicatesEnabled(false); m_comboBox->setInsertPolicy(QComboBox::InsertAlphabetically); QAbstractItemModel* model = m_comboBox->model(); QObject::connect(model, SIGNAL(rowsInserted(const QModelIndex&, int, int)), this, SLOT(rowsWereInserted(const QModelIndex, int, int))); } } /** * Destructor. */ GiftiLabelTableSelectionComboBox::~GiftiLabelTableSelectionComboBox() { } /** * Override the text of the unassigned label. * * @param text * Text that overrides the text of the unassigned label. */ void GiftiLabelTableSelectionComboBox::setUnassignedLabelTextOverride(const AString& text) { m_unassignedLabelTextOverride = text; } /** * If the combo box is editable, this can be used to create the new * label. * * @param parent * The parent model index. * @param start * First new row index. * @param end * Last new row index. */ void GiftiLabelTableSelectionComboBox::rowsWereInserted(const QModelIndex& /*parent*/, int start, int end) { if (m_ignoreInsertedRowsFlag) { return; } for (int i = start; i <= end; i++) { const QString text = m_comboBox->itemText(i); QVariant userData = m_comboBox->itemData(i); void* pointer = userData.value(); if (pointer == NULL) { const int32_t key = m_giftiLabelTable->addLabel(text, 0.0f, 0.0f, 0.0f, 1.0f); GiftiLabel* label = m_giftiLabelTable->getLabel(key); QPixmap pm(10, 10); pm.fill(QColor::fromRgbF(label->getRed(), label->getGreen(), label->getBlue())); QIcon icon(pm); QVariant userData = qVariantFromValue((void*)label); m_comboBox->setItemData(i, userData); m_comboBox->setItemIcon(i, icon); } } } /** * Called when the add/edit button is clicked. */ void GiftiLabelTableSelectionComboBox::addEditButtonClicked() { } /** * Gets called when the index is changed by user or program code. * * @parma index * Index of item. */ void GiftiLabelTableSelectionComboBox::currentIndexChanged(int /*indx*/) { GiftiLabel* gl = getSelectedLabel(); emit labelChanged(gl); if (gl != NULL) { emit labelKeyChanged(gl->getKey()); } else { emit labelKeyChanged(GiftiLabel::getInvalidLabelKey()); } } /** * Gets called when the user selects an item even if the selection * does not change. * * @parma index * Index of item. */ void GiftiLabelTableSelectionComboBox::itemActivated(int /*indx*/) { GiftiLabel* gl = getSelectedLabel(); emit labelActivated(gl); if (gl != NULL) { emit labelKeyActivated(gl->getKey()); } else { emit labelKeyActivated(GiftiLabel::getInvalidLabelKey()); } } /** * Update the content of this control with the given lable table. * * @param giftiLabelTable * The label table. */ void GiftiLabelTableSelectionComboBox::updateContent(GiftiLabelTable* giftiLabelTable) { m_ignoreInsertedRowsFlag = true; m_giftiLabelTable = giftiLabelTable; const AString selectedLabelName = m_comboBox->currentText(); m_comboBox->clear(); int32_t defaultIndex = -1; if (m_giftiLabelTable != NULL) { const int32_t unassignedKey = m_giftiLabelTable->getUnassignedLabelKey(); const std::vector keySet = m_giftiLabelTable->getLabelKeysSortedByName(); for (std::vector::const_iterator iter = keySet.begin(); iter != keySet.end(); iter++) { const int32_t key = *iter; GiftiLabel* label = giftiLabelTable->getLabel(key); AString labelName = label->getName(); if (key == unassignedKey) { if (m_unassignedLabelTextOverride.isEmpty() == false) { labelName = m_unassignedLabelTextOverride; } } if (labelName == selectedLabelName) { defaultIndex = m_comboBox->count(); } QPixmap pm(10, 10); pm.fill(QColor::fromRgbF(label->getRed(), label->getGreen(), label->getBlue())); QIcon icon(pm); QVariant userData = qVariantFromValue((void*)label); m_comboBox->addItem(icon, labelName, userData); } m_comboBox->setEnabled(true); if (defaultIndex >= 0) { m_comboBox->blockSignals(true); m_comboBox->setCurrentIndex(defaultIndex); m_comboBox->blockSignals(false); } } else { m_comboBox->setEnabled(false); } m_ignoreInsertedRowsFlag = false; } /** * Return the widget in this object. */ QWidget* GiftiLabelTableSelectionComboBox::getWidget() { return m_comboBox; } /** * @return The selected label (NULL if no selection). */ const GiftiLabel* GiftiLabelTableSelectionComboBox::getSelectedLabel() const { const int indx = m_comboBox->currentIndex(); if (indx >= 0) { QVariant userData = m_comboBox->itemData(indx); void* pointer = userData.value(); GiftiLabel* giftiLabel = (GiftiLabel*)pointer; return giftiLabel; } return NULL; } /** * @return The selected label (NULL if no selection). */ GiftiLabel* GiftiLabelTableSelectionComboBox::getSelectedLabel() { const int indx = m_comboBox->currentIndex(); if (indx >= 0) { QVariant userData = m_comboBox->itemData(indx); void* pointer = userData.value(); GiftiLabel* giftiLabel = (GiftiLabel*)pointer; return giftiLabel; } return NULL; } /** * Set the selected label to the given label. * * @param label * The label that is to be selected. */ void GiftiLabelTableSelectionComboBox::setSelectedLabel(const GiftiLabel* label) { if (label != NULL) { QVariant userData = qVariantFromValue((void*)label); int indx = m_comboBox->findData(userData); if (indx >= 0) { m_comboBox->setCurrentIndex(indx); } else { CaretLogSevere("Label not found: " + label->getName()); } } } /** * @return Key of the selected label or GiftiLabel::getInvalidLabelKey() * if no label selected. */ int32_t GiftiLabelTableSelectionComboBox::getSelectedLabelKey() const { const GiftiLabel* gl = getSelectedLabel(); if (gl != NULL) { return gl->getKey(); } return GiftiLabel::getInvalidLabelKey(); } /** * Set the selected label to the label with the given key. * * @param key * Key of label that is to be selected. */ void GiftiLabelTableSelectionComboBox::setSelectedLabelKey(const int32_t key) { const GiftiLabel* label = m_giftiLabelTable->getLabel(key); if (label != NULL) { setSelectedLabel(label); } else { CaretLogSevere("No label with key found: " + QString::number(key)); } } /** * @return Name of selected label or empty string if no label selected. */ QString GiftiLabelTableSelectionComboBox::getSelectedLabelName() const { const GiftiLabel* gl = getSelectedLabel(); if (gl != NULL) { return gl->getName(); } return ""; } /** * Set the selected label to the label with the given name. * * @param labelName * Name of label that is to be selected. */ void GiftiLabelTableSelectionComboBox::setSelectedLabelName(const QString& labelName) { const GiftiLabel* label = m_giftiLabelTable->getLabel(labelName); if (label != NULL) { setSelectedLabel(label); } else { // CaretLogSevere("No label with name found: " + // labelName); } } workbench-1.1.1/src/GuiQt/GiftiLabelTableSelectionComboBox.h000066400000000000000000000105571255417355300237600ustar00rootroot00000000000000#ifndef __GIFTI_LABEL_TABLE_SELECTION_COMBO_BOX_H__ #define __GIFTI_LABEL_TABLE_SELECTION_COMBO_BOX_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "AString.h" #include "WuQWidget.h" class QComboBox; class QModelIndex; namespace caret { class GiftiLabel; class GiftiLabelTable; class GiftiLabelTableSelectionComboBox : public WuQWidget { Q_OBJECT public: GiftiLabelTableSelectionComboBox(QObject* parent); virtual ~GiftiLabelTableSelectionComboBox(); void updateContent(GiftiLabelTable* giftiLabelTable); virtual QWidget* getWidget(); const GiftiLabel* getSelectedLabel() const; GiftiLabel* getSelectedLabel(); void setSelectedLabel(const GiftiLabel* label); int32_t getSelectedLabelKey() const; void setSelectedLabelKey(const int32_t key); QString getSelectedLabelName() const; void setSelectedLabelName(const QString& labelName); void setUnassignedLabelTextOverride(const AString& text); signals: /** * This signal is sent when the user chooses a label in the combobox. * The item's label is passed. Note that this signal is sent even when * the choice is not changed. If you need to know when the choice * actually changes, use signal labelChanged(). If the selected * label is invalid, NULL is passed. */ void labelActivated(GiftiLabel*); /** * This signal is sent when the user chooses a label in the combobox. * The item's key is passed. Note that this signal is sent even when * the choice is not changed. If you need to know when the choice * actually changes, use signal labelKeyChanged(). If the selected * label is invalid, GiftiLabel::getInvalidLabelKey() is passed. */ void labelKeyActivated(const int32_t key); /** * This signal is sent whenever the label in the combobox * changes either through user interaction or programmatically. * If the selected label is invalid, NULL is passed. */ void labelChanged(GiftiLabel*); /** * This signal is sent whenever the label in the combobox * changes either through user interaction or programmatically. * If the selected label is invalid, GiftiLabel::getInvalidLabelKey() is passed. */ void labelKeyChanged(const int32_t key); // ADD_NEW_METHODS_HERE private slots: void currentIndexChanged(int indx); void itemActivated(int indx); void addEditButtonClicked(); void rowsWereInserted(const QModelIndex& parent, int start, int end); private: GiftiLabelTableSelectionComboBox(const GiftiLabelTableSelectionComboBox&); GiftiLabelTableSelectionComboBox& operator=(const GiftiLabelTableSelectionComboBox&); GiftiLabelTable* m_giftiLabelTable; QComboBox* m_comboBox; bool m_ignoreInsertedRowsFlag; AString m_unassignedLabelTextOverride; // ADD_NEW_MEMBERS_HERE }; #ifdef __GIFTI_LABEL_TABLE_SELECTION_COMBO_BOX_DECLARE__ // #endif // __GIFTI_LABEL_TABLE_SELECTION_COMBO_BOX_DECLARE__ } // namespace #endif //__GIFTI_LABEL_TABLE_SELECTION_COMBO_BOX_H__ workbench-1.1.1/src/GuiQt/GroupAndNameHierarchyTreeWidgetItem.cxx000066400000000000000000000422261255417355300250420ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CLASS_AND_NAME_HIERARCHY_TREE_WIDGET_ITEM_DECLARE__ #include "GroupAndNameHierarchyTreeWidgetItem.h" #undef __CLASS_AND_NAME_HIERARCHY_TREE_WIDGET_ITEM_DECLARE__ #include "CaretAssert.h" #include "GroupAndNameHierarchyGroup.h" #include "GroupAndNameHierarchyModel.h" #include "GroupAndNameHierarchyName.h" using namespace caret; /** * \class caret::ClassAndNameHierarchySelectionInfo * \brief Tree Widget Item for Class and Name Hierarchy * \ingroup GuiQt */ /** * Constructor for ClassAndNameHierarchyModel * @param classAndNameHierarchyModel * The class name hierarchy model. */ GroupAndNameHierarchyTreeWidgetItem::GroupAndNameHierarchyTreeWidgetItem(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, GroupAndNameHierarchyModel* classAndNameHierarchyModel) : QTreeWidgetItem() { CaretAssert(classAndNameHierarchyModel); initialize(classAndNameHierarchyModel, displayGroup, tabIndex, ITEM_TYPE_HIERARCHY_MODEL, classAndNameHierarchyModel->getName(), NULL); m_classAndNameHierarchyModel = classAndNameHierarchyModel; /* * Loop through each class */ std::vector classChildren = m_classAndNameHierarchyModel->getChildren(); for (std::vector::iterator classIter = classChildren.begin(); classIter != classChildren.end(); classIter++) { GroupAndNameHierarchyItem* classItem = *classIter; CaretAssert(classItem); GroupAndNameHierarchyGroup* group = dynamic_cast(classItem); CaretAssert(group); GroupAndNameHierarchyTreeWidgetItem* groupItem = new GroupAndNameHierarchyTreeWidgetItem(displayGroup, tabIndex, group); addChildItem(groupItem); } } /** * Constructor for ClassDisplayGroupSelector * @param classDisplayGroupSelector * The class display group selector. */ GroupAndNameHierarchyTreeWidgetItem::GroupAndNameHierarchyTreeWidgetItem(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, GroupAndNameHierarchyGroup* classDisplayGroupSelector) : QTreeWidgetItem() { CaretAssert(classDisplayGroupSelector); initialize(classDisplayGroupSelector, displayGroup, tabIndex, ITEM_TYPE_CLASS, classDisplayGroupSelector->getName(), classDisplayGroupSelector->getIconColorRGBA()); m_classDisplayGroupSelector = classDisplayGroupSelector; std::vector nameChildren = m_classDisplayGroupSelector->getChildren(); for (std::vector::iterator nameIter = nameChildren.begin(); nameIter != nameChildren.end(); nameIter++) { GroupAndNameHierarchyItem* nameItem = *nameIter; CaretAssert(nameItem); GroupAndNameHierarchyName* name = dynamic_cast(nameItem); CaretAssert(name); GroupAndNameHierarchyTreeWidgetItem* nameTreeItem = new GroupAndNameHierarchyTreeWidgetItem(displayGroup, tabIndex, name); addChildItem(nameTreeItem); } } /** * Constructor for NameDisplayGroupSelector * @param nameDisplayGroupSelector * The name display group selector. */ GroupAndNameHierarchyTreeWidgetItem::GroupAndNameHierarchyTreeWidgetItem(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, GroupAndNameHierarchyName* nameDisplayGroupSelector) : QTreeWidgetItem() { CaretAssert(nameDisplayGroupSelector); initialize(nameDisplayGroupSelector, displayGroup, tabIndex, ITEM_TYPE_NAME, nameDisplayGroupSelector->getName(), nameDisplayGroupSelector->getIconColorRGBA()); m_nameDisplayGroupSelector = nameDisplayGroupSelector; } /** * Destructor. */ GroupAndNameHierarchyTreeWidgetItem::~GroupAndNameHierarchyTreeWidgetItem() { /* * Note: Do not need to delete children since they are added to * Qt layouts which will delete them. */ } /** * Initialize this instance. * @param itemType * Type of item contained in this instance. */ void GroupAndNameHierarchyTreeWidgetItem::initialize(GroupAndNameHierarchyItem* groupAndNameHierarchyItem, const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const ItemType itemType, const QString text, const float* /*iconColorRGBA*/) { m_groupAndNameHierarchyItem = groupAndNameHierarchyItem; m_classAndNameHierarchyModel = dynamic_cast(groupAndNameHierarchyItem); m_classDisplayGroupSelector = dynamic_cast(groupAndNameHierarchyItem); m_nameDisplayGroupSelector = dynamic_cast(groupAndNameHierarchyItem); const int32_t count = (((m_classAndNameHierarchyModel != NULL) ? 1 : 0) + ((m_classDisplayGroupSelector != NULL) ? 1 : 0) + ((m_nameDisplayGroupSelector != NULL) ? 1 : 0)); if (count != 1) { CaretAssertMessage(0, "Invalid item added to group/name hierarchy tree."); } m_iconColorRGBA[0] = -1.0; m_iconColorRGBA[1] = -1.0; m_iconColorRGBA[2] = -1.0; m_iconColorRGBA[3] = -1.0; m_displayGroup = displayGroup; m_tabIndex = tabIndex; m_itemType = itemType; // m_classAndNameHierarchyModel = NULL; // m_classDisplayGroupSelector = NULL; // m_nameDisplayGroupSelector = NULL; m_hasChildren = false; switch (m_itemType) { case ITEM_TYPE_CLASS: m_hasChildren = true; break; case ITEM_TYPE_HIERARCHY_MODEL: m_hasChildren = true; break; case ITEM_TYPE_NAME: m_hasChildren = false; break; } setText(TREE_COLUMN, text); /*Qt::ItemFlags itemFlags = (Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);//*/ if (m_hasChildren) { // itemFlags |= Qt::ItemIsTristate; } // setFlags(itemFlags); // NEW 11/14/12 // if (iconColorRGBA != NULL) { // if (iconColorRGBA[3] > 0.0) { // QPixmap pm(10, 10); // pm.fill(QColor::fromRgbF(iconColorRGBA[0], // iconColorRGBA[1], // iconColorRGBA[2])); // QIcon icon(pm); // setIcon(TREE_COLUMN, icon); // } // } updateIconColorIncludingChildren(); } /** * Update the selections in this and its children. * @param displayGroup * Display group that is active. * @param tabIndex * Index of tab that is displayed. */ void GroupAndNameHierarchyTreeWidgetItem::updateSelections(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex) { m_displayGroup = displayGroup; m_tabIndex = tabIndex; GroupAndNameCheckStateEnum::Enum checkState = GroupAndNameCheckStateEnum::UNCHECKED; bool expandedStatus = false; switch (m_itemType) { case ITEM_TYPE_CLASS: CaretAssert(m_classDisplayGroupSelector); checkState = m_classDisplayGroupSelector->getCheckState(m_displayGroup, m_tabIndex); expandedStatus = m_classDisplayGroupSelector->isExpandedToDisplayChildren(m_displayGroup, m_tabIndex); break; case ITEM_TYPE_HIERARCHY_MODEL: CaretAssert(m_classAndNameHierarchyModel); checkState = m_classAndNameHierarchyModel->getCheckState(m_displayGroup, m_tabIndex); expandedStatus = m_classAndNameHierarchyModel->isExpandedToDisplayChildren(m_displayGroup, m_tabIndex); break; case ITEM_TYPE_NAME: CaretAssert(m_nameDisplayGroupSelector); checkState = m_nameDisplayGroupSelector->getCheckState(m_displayGroup, m_tabIndex); expandedStatus = false; break; } Qt::CheckState qtCheckState = toQCheckState(checkState); setCheckState(TREE_COLUMN, qtCheckState); if (m_hasChildren) { setExpanded(expandedStatus); for (std::vector::iterator iter = m_children.begin(); iter != m_children.end(); iter++) { GroupAndNameHierarchyTreeWidgetItem* item = *iter; item->updateSelections(m_displayGroup, tabIndex); } } } /** * If this item's color has changed, update its icon. * Process all of its children. */ void GroupAndNameHierarchyTreeWidgetItem::updateIconColorIncludingChildren() { if (m_groupAndNameHierarchyItem != NULL) { const float* rgba = m_groupAndNameHierarchyItem->getIconColorRGBA(); if (rgba != NULL) { if (rgba[3] > 0.0) { bool colorChanged = false; for (int32_t i = 0; i < 4; i++) { if (m_iconColorRGBA[i] != rgba[i]) { colorChanged = true; break; } } if (colorChanged) { m_iconColorRGBA[0] = rgba[0]; m_iconColorRGBA[1] = rgba[1]; m_iconColorRGBA[2] = rgba[2]; m_iconColorRGBA[3] = rgba[3]; QPixmap pm(10, 10); pm.fill(QColor::fromRgbF(m_iconColorRGBA[0], m_iconColorRGBA[1], m_iconColorRGBA[2])); QIcon icon(pm); setIcon(TREE_COLUMN, icon); } } } } for (std::vector::iterator iter = m_children.begin(); iter != m_children.end(); iter++) { GroupAndNameHierarchyTreeWidgetItem* item = *iter; item->updateIconColorIncludingChildren(); } } /** * Add a child. * @param child. * The child. */ void GroupAndNameHierarchyTreeWidgetItem::addChildItem(GroupAndNameHierarchyTreeWidgetItem* child) { CaretAssert(child); m_children.push_back(child); addChild(child); } /** * @return ItemType of the selected item. */ GroupAndNameHierarchyTreeWidgetItem::ItemType GroupAndNameHierarchyTreeWidgetItem::getItemType() const { return m_itemType; } /** * @return The class name and hierarchy model. NULL * if this instance contains another type of data. */ GroupAndNameHierarchyModel* GroupAndNameHierarchyTreeWidgetItem::getClassAndNameHierarchyModel() { CaretAssert(m_itemType == ITEM_TYPE_HIERARCHY_MODEL); return m_classAndNameHierarchyModel; } /** * @return The class name and hierarchy model. NULL * if this instance contains another type of data. */ GroupAndNameHierarchyGroup* GroupAndNameHierarchyTreeWidgetItem::getClassDisplayGroupSelector() { CaretAssert(m_itemType == ITEM_TYPE_CLASS); return m_classDisplayGroupSelector; } /** * @return The class name and hierarchy model. NULL * if this instance contains another type of data. */ GroupAndNameHierarchyName* GroupAndNameHierarchyTreeWidgetItem::getNameDisplayGroupSelector() { CaretAssert(m_itemType == ITEM_TYPE_NAME); return m_nameDisplayGroupSelector; } /** * Convert QCheckState to GroupAndNameCheckStateEnum * @param checkState * The QCheckState * @return GroupAndNameCheckStateEnum converted from QCheckState */ GroupAndNameCheckStateEnum::Enum GroupAndNameHierarchyTreeWidgetItem::fromQCheckState(const Qt::CheckState checkState) { switch (checkState) { case Qt::Unchecked: return GroupAndNameCheckStateEnum::UNCHECKED; break; case Qt::PartiallyChecked: return GroupAndNameCheckStateEnum::PARTIALLY_CHECKED; break; case Qt::Checked: return GroupAndNameCheckStateEnum::CHECKED; break; } return GroupAndNameCheckStateEnum::UNCHECKED; } /** * Convert GroupAndNameCheckStateEnum to QCheckState * @param checkState * The GroupAndNameCheckStateEnum * @return QCheckState converted from GroupAndNameCheckStateEnum converted. */ Qt::CheckState GroupAndNameHierarchyTreeWidgetItem::toQCheckState(const GroupAndNameCheckStateEnum::Enum checkState) { switch (checkState) { case GroupAndNameCheckStateEnum::CHECKED: return Qt::Checked; break; case GroupAndNameCheckStateEnum::PARTIALLY_CHECKED: return Qt::PartiallyChecked; break; case GroupAndNameCheckStateEnum::UNCHECKED: return Qt::Unchecked; break; } return Qt::Unchecked; } /** * Set the expanded status of the data in the model that is presented * by this item. * @param expanded * Status of expansion. */ void GroupAndNameHierarchyTreeWidgetItem::setModelDataExpanded(const bool expanded) { GroupAndNameHierarchyItem* item = NULL; switch (m_itemType) { case ITEM_TYPE_CLASS: item = m_classDisplayGroupSelector; break; case ITEM_TYPE_HIERARCHY_MODEL: item = m_classAndNameHierarchyModel; break; case ITEM_TYPE_NAME: item = m_nameDisplayGroupSelector; break; } if (item != NULL) { item->setExpandedToDisplayChildren(m_displayGroup, m_tabIndex, expanded); } } /** * Set the selected status of the data in the model that is presented * by this item. * @param selected * Status of selection. */ void GroupAndNameHierarchyTreeWidgetItem::setModelDataSelected(const bool selected) { GroupAndNameHierarchyItem* item = NULL; switch (m_itemType) { case ITEM_TYPE_CLASS: item = m_classDisplayGroupSelector; break; case ITEM_TYPE_HIERARCHY_MODEL: item = m_classAndNameHierarchyModel; break; case ITEM_TYPE_NAME: item = m_nameDisplayGroupSelector; break; } if (item != NULL) { const GroupAndNameCheckStateEnum::Enum existingCheckState = item->getCheckState(m_displayGroup, m_tabIndex); switch (existingCheckState) { case GroupAndNameCheckStateEnum::CHECKED: break; case GroupAndNameCheckStateEnum::PARTIALLY_CHECKED: break; case GroupAndNameCheckStateEnum::UNCHECKED: break; } if (selected) { item->setSelected(m_displayGroup, m_tabIndex, true); item->setAncestorsSelected(m_displayGroup, m_tabIndex, true); item->setDescendantsSelected(m_displayGroup, m_tabIndex, true); } else { item->setSelected(m_displayGroup, m_tabIndex, false); item->setDescendantsSelected(m_displayGroup, m_tabIndex, false); } } } workbench-1.1.1/src/GuiQt/GroupAndNameHierarchyTreeWidgetItem.h000066400000000000000000000116541255417355300244700ustar00rootroot00000000000000#ifndef __CLASS_AND_NAME_HIERARCHY_TREE_WIDGET_ITEM__H_ #define __CLASS_AND_NAME_HIERARCHY_TREE_WIDGET_ITEM__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "DisplayGroupEnum.h" #include "GroupAndNameCheckStateEnum.h" class QAction; class QCheckBox; class QVBoxLayout; namespace caret { class GroupAndNameHierarchyGroup; class GroupAndNameHierarchyItem; class GroupAndNameHierarchyModel; class GroupAndNameHierarchyName; class GroupAndNameHierarchyTreeWidgetItem : public QTreeWidgetItem { public: /** Type of item within the hierarchy */ enum ItemType { /** The class/name hierarchy model */ ITEM_TYPE_HIERARCHY_MODEL, /** Class in the class/name hierarchy */ ITEM_TYPE_CLASS, /** Name in the class/name hieracrchy */ ITEM_TYPE_NAME }; GroupAndNameHierarchyTreeWidgetItem(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, GroupAndNameHierarchyModel* classAndNameHierarchyModel); GroupAndNameHierarchyTreeWidgetItem(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, GroupAndNameHierarchyGroup* classDisplayGroupSelector); GroupAndNameHierarchyTreeWidgetItem(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, GroupAndNameHierarchyName* nameDisplayGroupSelector); ~GroupAndNameHierarchyTreeWidgetItem(); ItemType getItemType() const; GroupAndNameHierarchyModel* getClassAndNameHierarchyModel(); GroupAndNameHierarchyGroup* getClassDisplayGroupSelector(); GroupAndNameHierarchyName* getNameDisplayGroupSelector(); void addChildItem(GroupAndNameHierarchyTreeWidgetItem* child); void updateSelections(const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex); void setModelDataExpanded(const bool expanded); void setModelDataSelected(const bool selected); void updateIconColorIncludingChildren(); private: GroupAndNameHierarchyTreeWidgetItem(const GroupAndNameHierarchyTreeWidgetItem&); GroupAndNameHierarchyTreeWidgetItem& operator=(const GroupAndNameHierarchyTreeWidgetItem&); void initialize(GroupAndNameHierarchyItem* groupAndNameHierarchyItem, const DisplayGroupEnum::Enum displayGroup, const int32_t tabIndex, const ItemType itemType, const QString text, const float* iconColorRGBA); static GroupAndNameCheckStateEnum::Enum fromQCheckState(const Qt::CheckState checkState); static Qt::CheckState toQCheckState(const GroupAndNameCheckStateEnum::Enum checkState); ItemType m_itemType; DisplayGroupEnum::Enum m_displayGroup; int32_t m_tabIndex; GroupAndNameHierarchyItem* m_groupAndNameHierarchyItem; GroupAndNameHierarchyModel* m_classAndNameHierarchyModel; GroupAndNameHierarchyGroup* m_classDisplayGroupSelector; GroupAndNameHierarchyName* m_nameDisplayGroupSelector; std::vector m_children; bool m_displayNamesWithZeroCount; bool m_hasChildren; float m_iconColorRGBA[4]; static const int TREE_COLUMN; friend class GroupAndNameHierarchyViewController; }; #ifdef __CLASS_AND_NAME_HIERARCHY_TREE_WIDGET_ITEM_DECLARE__ const int GroupAndNameHierarchyTreeWidgetItem::TREE_COLUMN = 0; #endif // __CLASS_AND_NAME_HIERARCHY_TREE_WIDGET_ITEM_DECLARE__ } // namespace #endif //__CLASS_AND_NAME_HIERARCHY_TREE_WIDGET_ITEM__H_ workbench-1.1.1/src/GuiQt/GroupAndNameHierarchyViewController.cxx000066400000000000000000000436051255417355300251400ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __CLASS_AND_NAME_HIERARCHY_VIEW_CONTROLLER_DECLARE__ #include "GroupAndNameHierarchyViewController.h" #undef __CLASS_AND_NAME_HIERARCHY_VIEW_CONTROLLER_DECLARE__ #include #include #include #include #include #include #include "Brain.h" #include "BorderFile.h" #include "BrowserTabContent.h" #include "CaretAssert.h" #include "CiftiBrainordinateLabelFile.h" #include "EventGraphicsUpdateAllWindows.h" #include "EventManager.h" #include "EventSurfaceColoringInvalidate.h" #include "EventUserInterfaceUpdate.h" #include "GroupAndNameHierarchyGroup.h" #include "GroupAndNameHierarchyModel.h" #include "GroupAndNameHierarchyName.h" #include "GroupAndNameHierarchyTreeWidgetItem.h" #include "FociFile.h" #include "GuiManager.h" #include "GiftiLabel.h" #include "GiftiLabelTable.h" #include "LabelFile.h" #include "VolumeFile.h" #include "WuQTreeWidget.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::GroupAndNameHierarchyViewController * \brief View controller for ClassAndNameHierarchyModels * \ingroup GuiQt * * A view controller for one or more ClassAndNameHierarchyModel * instances. */ /** * Constructor. * @param parent * Parent widget. */ GroupAndNameHierarchyViewController::GroupAndNameHierarchyViewController(const int32_t browserWindowIndex, QWidget* parent) : QWidget(parent) { m_dataFileType = DataFileTypeEnum::UNKNOWN; m_displayGroup = DisplayGroupEnum::getDefaultValue(); m_previousDisplayGroup = DisplayGroupEnum::getDefaultValue(); m_previousBrowserTabIndex = -1; m_browserWindowIndex = browserWindowIndex; QWidget* allOnOffWidget = createAllOnOffControls(); m_modelTreeWidgetLayout = new QVBoxLayout(); WuQtUtilities::setLayoutSpacingAndMargins(m_modelTreeWidgetLayout, 0, 0); m_modelTreeWidget = NULL; createTreeWidget(); QVBoxLayout* layout = new QVBoxLayout(this); WuQtUtilities::setLayoutSpacingAndMargins(layout, 0, 0); layout->addWidget(allOnOffWidget); layout->addSpacing(5); layout->addLayout(m_modelTreeWidgetLayout); s_allViewControllers.insert(this); } /** * Destructor. */ GroupAndNameHierarchyViewController::~GroupAndNameHierarchyViewController() { s_allViewControllers.erase(this); } /** * Gets called when an item is collapsed so that its children are not visible. * * @param item * The QTreeWidgetItem that was collapsed. */ void GroupAndNameHierarchyViewController::itemWasCollapsed(QTreeWidgetItem* item) { GroupAndNameHierarchyTreeWidgetItem* treeItem = dynamic_cast(item); CaretAssert(treeItem); treeItem->setModelDataExpanded(false); updateSelectedAndExpandedCheckboxes(); updateSelectedAndExpandedCheckboxesInOtherViewControllers(); } /** * Gets called when an item is expaned so that its children are visible. * * @param item * The QTreeWidgetItem that was expanded. */ void GroupAndNameHierarchyViewController::itemWasExpanded(QTreeWidgetItem* item) { GroupAndNameHierarchyTreeWidgetItem* treeItem = dynamic_cast(item); CaretAssert(treeItem); treeItem->setModelDataExpanded(true); updateSelectedAndExpandedCheckboxes(); updateSelectedAndExpandedCheckboxesInOtherViewControllers(); } /** * Called when an item is changed (checkbox selected/deselected). * * @param item * The QTreeWidgetItem that was collapsed. * @param column * Ignored. */ void GroupAndNameHierarchyViewController::itemWasChanged(QTreeWidgetItem* item, int /*column*/) { GroupAndNameHierarchyTreeWidgetItem* treeItem = dynamic_cast(item); CaretAssert(treeItem); const Qt::CheckState checkState = item->checkState(GroupAndNameHierarchyTreeWidgetItem::TREE_COLUMN); const GroupAndNameCheckStateEnum::Enum itemCheckState = GroupAndNameHierarchyTreeWidgetItem::fromQCheckState(checkState); const bool newStatus = (itemCheckState != GroupAndNameCheckStateEnum::UNCHECKED); treeItem->setModelDataSelected(newStatus); updateSelectedAndExpandedCheckboxes(); updateSelectedAndExpandedCheckboxesInOtherViewControllers(); updateGraphics(); } /** * Update graphics and, in some circumstances, surface node coloring. */ void GroupAndNameHierarchyViewController::updateGraphics() { if (m_selectionInvalidatesSurfaceNodeColoring) { EventManager::get()->sendEvent(EventSurfaceColoringInvalidate().getPointer()); } EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } /** * Create buttons for all on and off */ QWidget* GroupAndNameHierarchyViewController::createAllOnOffControls() { QLabel* allLabel = new QLabel("All: "); QPushButton* onPushButton = new QPushButton("On"); QObject::connect(onPushButton, SIGNAL(clicked()), this, SLOT(allOnPushButtonClicked())); QPushButton* offPushButton = new QPushButton("Off"); QObject::connect(offPushButton, SIGNAL(clicked()), this, SLOT(allOffPushButtonClicked())); QWidget* w = new QWidget(); QHBoxLayout* layout = new QHBoxLayout(w); layout->addWidget(allLabel); layout->addWidget(onPushButton); layout->addWidget(offPushButton); layout->addStretch(); return w; } /** * Called when all on push button clicked. */ void GroupAndNameHierarchyViewController::allOnPushButtonClicked() { setAllSelected(true); } /** * Called when all off push button clicked. */ void GroupAndNameHierarchyViewController::allOffPushButtonClicked() { setAllSelected(false); } /** * Set selection status of all items. * @param selected * New selection status for all items. */ void GroupAndNameHierarchyViewController::setAllSelected(bool selected) { BrowserTabContent* browserTabContent = GuiManager::get()->getBrowserTabContentForBrowserWindow(m_browserWindowIndex, false); if (browserTabContent != NULL) { const int32_t browserTabIndex = browserTabContent->getTabNumber(); std::vector allModels = getAllModels(); const int32_t numModels = static_cast(allModels.size()); for (int32_t i = 0; i < numModels; i++) { GroupAndNameHierarchyModel* model = allModels[i]; model->setAllSelected(m_displayGroup, browserTabIndex, selected); } updateSelectedAndExpandedCheckboxesInOtherViewControllers(); updateSelectedAndExpandedCheckboxes(); updateGraphics(); } } /** * @return All models in this view controller. */ std::vector GroupAndNameHierarchyViewController::getAllModels() const { std::vector allModels; const int32_t numItems = static_cast(m_treeWidgetItems.size()); for (int32_t i = 0; i < numItems; i++) { GroupAndNameHierarchyTreeWidgetItem* treeItem = m_treeWidgetItems[i]; GroupAndNameHierarchyModel* model = treeItem->getClassAndNameHierarchyModel(); allModels.push_back(model); } return allModels; } /** * Update with border files. * @param borderFiles * The border files. * @param displayGroup * The selected display group. */ void GroupAndNameHierarchyViewController::updateContents(std::vector& borderFiles, const DisplayGroupEnum::Enum displayGroup) { std::vector models; m_displayGroup = displayGroup; std::vector classAndNameHierarchyModels; for (std::vector::iterator iter = borderFiles.begin(); iter != borderFiles.end(); iter++) { BorderFile* bf = *iter; CaretAssert(bf); models.push_back(bf->getGroupAndNameHierarchyModel()); } updateContents(models, DataFileTypeEnum::BORDER, false); } /** * Update with border files. * @param borderFiles * The border files. * @param displayGroup * The selected display group. */ void GroupAndNameHierarchyViewController::updateContents(std::vector& fociFiles, const DisplayGroupEnum::Enum displayGroup) { std::vector models; m_displayGroup = displayGroup; std::vector classAndNameHierarchyModels; for (std::vector::iterator iter = fociFiles.begin(); iter != fociFiles.end(); iter++) { FociFile* ff = *iter; CaretAssert(ff); models.push_back(ff->getGroupAndNameHierarchyModel()); } updateContents(models, DataFileTypeEnum::FOCI, false); } /** * Update with label files. * @param labelFiles * The label files. * @param ciftiLabelFiles * The CIFTI label files. * @param volumeLabelFiles * The volume label files. * @param displayGroup * The selected display group. */ void GroupAndNameHierarchyViewController::updateContents(std::vector& labelFiles, std::vector& ciftiLabelFiles, std::vector& volumeLabelFiles, const DisplayGroupEnum::Enum displayGroup) { std::vector models; m_displayGroup = displayGroup; std::vector classAndNameHierarchyModels; for (std::vector::iterator iter = labelFiles.begin(); iter != labelFiles.end(); iter++) { LabelFile* lf = *iter; CaretAssert(lf); models.push_back(lf->getGroupAndNameHierarchyModel()); } for (std::vector::iterator iter = ciftiLabelFiles.begin(); iter != ciftiLabelFiles.end(); iter++) { CiftiBrainordinateLabelFile* clf = *iter; CaretAssert(clf); models.push_back(clf->getGroupAndNameHierarchyModel()); } for (std::vector::iterator iter = volumeLabelFiles.begin(); iter != volumeLabelFiles.end(); iter++) { VolumeFile* vf = *iter; CaretAssert(vf); models.push_back(vf->getGroupAndNameHierarchyModel()); } updateContents(models, DataFileTypeEnum::LABEL, true); } /** * Create/recreate the tree widget. */ void GroupAndNameHierarchyViewController::createTreeWidget() { /* * Delete and recreate the tree widget * Seems that adding and removing items from tree widget eventually * causes a crash. */ m_treeWidgetItems.clear(); if (m_modelTreeWidget != NULL) { m_modelTreeWidget->blockSignals(true); m_modelTreeWidget->clear(); m_modelTreeWidgetLayout->removeWidget(m_modelTreeWidget); delete m_modelTreeWidget; } m_modelTreeWidget = new WuQTreeWidget(); QObject::connect(m_modelTreeWidget, SIGNAL(itemCollapsed(QTreeWidgetItem*)), this, SLOT(itemWasCollapsed(QTreeWidgetItem*))); QObject::connect(m_modelTreeWidget, SIGNAL(itemExpanded(QTreeWidgetItem*)), this, SLOT(itemWasExpanded(QTreeWidgetItem*))); QObject::connect(m_modelTreeWidget, SIGNAL(itemChanged(QTreeWidgetItem*, int)), this, SLOT(itemWasChanged(QTreeWidgetItem*, int))); m_modelTreeWidgetLayout->addWidget(m_modelTreeWidget); m_modelTreeWidget->blockSignals(false); } /** * Update the content of the view controller. * @param classAndNameHierarchyModels * ClassAndNameHierarchyModels instances for display. * @param allowNamesWithZeroCounts * If true, display names even if the usage count is zero. */ void GroupAndNameHierarchyViewController::updateContents(std::vector& classAndNameHierarchyModels, const DataFileTypeEnum::Enum dataFileType, const bool selectionInvalidatesSurfaceNodeColoring) { m_dataFileType= dataFileType; m_selectionInvalidatesSurfaceNodeColoring = selectionInvalidatesSurfaceNodeColoring; BrowserTabContent* browserTabContent = GuiManager::get()->getBrowserTabContentForBrowserWindow(m_browserWindowIndex, false); CaretAssert(browserTabContent); const int32_t browserTabIndex = browserTabContent->getTabNumber(); /* * May need an update */ bool needUpdate = false; int32_t numberOfModels = static_cast(classAndNameHierarchyModels.size()); /* * Has the number of models changed? */ if (numberOfModels != static_cast(this->m_treeWidgetItems.size())) { needUpdate = true; } // else if (m_displayGroup != m_previousDisplayGroup) { // needUpdate = true; // } // else if (browserTabIndex != m_previousBrowserTabIndex) { // needUpdate = true; // } else { /* * Have the displayed models changed? */ for (int32_t iModel = 0; iModel < numberOfModels; iModel++) { if (classAndNameHierarchyModels[iModel] != this->m_treeWidgetItems[iModel]->getClassAndNameHierarchyModel()) { needUpdate = true; break; } else if (classAndNameHierarchyModels[iModel]->getChildren().size() != this->m_treeWidgetItems[iModel]->getClassAndNameHierarchyModel()->getChildren().size()) { needUpdate = true; break; } } /* * Has the model's content been altered? */ for (int32_t iModel = 0; iModel < numberOfModels; iModel++) { if (classAndNameHierarchyModels[iModel]->needsUserInterfaceUpdate(m_displayGroup, browserTabIndex)) { needUpdate = true; break; } } } m_modelTreeWidget->blockSignals(true); if (needUpdate) { createTreeWidget(); m_modelTreeWidget->blockSignals(true); // gets reset /* * Copy the models */ for (int32_t iModel = 0; iModel < numberOfModels; iModel++) { GroupAndNameHierarchyTreeWidgetItem* modelItem = new GroupAndNameHierarchyTreeWidgetItem(m_displayGroup, browserTabIndex, classAndNameHierarchyModels[iModel]); this->m_treeWidgetItems.push_back(modelItem); m_modelTreeWidget->addTopLevelItem(modelItem); } } else { for (int32_t iModel = 0; iModel < numberOfModels; iModel++) { this->m_treeWidgetItems[iModel]->updateIconColorIncludingChildren(); } } updateSelectedAndExpandedCheckboxes(); m_previousBrowserTabIndex = browserTabIndex; m_previousDisplayGroup = m_displayGroup; m_modelTreeWidget->blockSignals(false); if (needUpdate) { m_modelTreeWidget->resizeToFitContent(); } } /** * Update the selection and expansion controls. */ void GroupAndNameHierarchyViewController::updateSelectedAndExpandedCheckboxes() { if (m_modelTreeWidget == NULL) { return; } m_modelTreeWidget->blockSignals(true); BrowserTabContent* browserTabContent = GuiManager::get()->getBrowserTabContentForBrowserWindow(m_browserWindowIndex, false); CaretAssert(browserTabContent); const int32_t browserTabIndex = browserTabContent->getTabNumber(); const int32_t numberOfModels = static_cast(this->m_treeWidgetItems.size()); for (int32_t iModel = 0; iModel < numberOfModels; iModel++) { m_treeWidgetItems[iModel]->updateSelections(m_displayGroup, browserTabIndex); } m_modelTreeWidget->blockSignals(false); } /** * Update the selection and expansion controls in other view controllers * that are set to the same display group (not tab) and contain the * same type of data. */ void GroupAndNameHierarchyViewController::updateSelectedAndExpandedCheckboxesInOtherViewControllers() { if (m_displayGroup == DisplayGroupEnum::DISPLAY_GROUP_TAB) { return; } for (std::set::iterator iter = s_allViewControllers.begin(); iter != s_allViewControllers.end(); iter++) { GroupAndNameHierarchyViewController* vc = *iter; if (vc != this) { if (vc->m_displayGroup == m_displayGroup) { if (vc->m_dataFileType == m_dataFileType) { vc->updateSelectedAndExpandedCheckboxes(); } } } } } workbench-1.1.1/src/GuiQt/GroupAndNameHierarchyViewController.h000066400000000000000000000106321255417355300245570ustar00rootroot00000000000000#ifndef __CLASS_AND_NAME_HIERARCHY_VIEW_CONTROLLER__H_ #define __CLASS_AND_NAME_HIERARCHY_VIEW_CONTROLLER__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include "DataFileTypeEnum.h" #include "DisplayGroupEnum.h" class QTreeWidgetItem; class QVBoxLayout; namespace caret { class BorderFile; class CiftiBrainordinateLabelFile; class FociFile; class LabelFile; class GroupAndNameHierarchyModel; class GroupAndNameHierarchyTreeWidgetItem; class VolumeFile; class WuQTreeWidget; class GroupAndNameHierarchyViewController : public QWidget { Q_OBJECT public: GroupAndNameHierarchyViewController(const int32_t browserWindowIndex, QWidget* parent = 0); virtual ~GroupAndNameHierarchyViewController(); void updateContents(std::vector& borderFiles, const DisplayGroupEnum::Enum displayGroup); void updateContents(std::vector& fociFiles, const DisplayGroupEnum::Enum displayGroup); void updateContents(std::vector& labelFiles, std::vector& ciftiLabelFiles, std::vector& volumeLabelFiles, const DisplayGroupEnum::Enum displayGroup); private slots: void allOnPushButtonClicked(); void allOffPushButtonClicked(); void itemWasCollapsed(QTreeWidgetItem* item); void itemWasExpanded(QTreeWidgetItem* item); void itemWasChanged(QTreeWidgetItem* item, int column); private: GroupAndNameHierarchyViewController(const GroupAndNameHierarchyViewController&); GroupAndNameHierarchyViewController& operator=(const GroupAndNameHierarchyViewController&); void updateContents(std::vector& modelItems, const DataFileTypeEnum::Enum dataFileType, const bool selectionInvalidatesSurfaceNodeColoring); std::vector getAllModels() const; void updateGraphics(); void updateSelectedAndExpandedCheckboxes(); void updateSelectedAndExpandedCheckboxesInOtherViewControllers(); void createTreeWidget(); QWidget* createAllOnOffControls(); void setAllSelected(bool selected); DataFileTypeEnum::Enum m_dataFileType; /** Contains pointers to items managed by Qt, so do not delete content */ std::vector m_treeWidgetItems; QVBoxLayout* m_modelTreeWidgetLayout; WuQTreeWidget* m_modelTreeWidget; int32_t m_browserWindowIndex; DisplayGroupEnum::Enum m_displayGroup; DisplayGroupEnum::Enum m_previousDisplayGroup; int32_t m_previousBrowserTabIndex; bool m_selectionInvalidatesSurfaceNodeColoring; static std::set s_allViewControllers; }; #ifdef __CLASS_AND_NAME_HIERARCHY_VIEW_CONTROLLER_DECLARE__ std::set GroupAndNameHierarchyViewController::s_allViewControllers; #endif // __CLASS_AND_NAME_HIERARCHY_VIEW_CONTROLLER_DECLARE__ } // namespace #endif //__CLASS_AND_NAME_HIERARCHY_VIEW_CONTROLLER__H_ workbench-1.1.1/src/GuiQt/GuiManager.cxx000066400000000000000000002741531255417355300201050ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #include #include #include #define __GUI_MANAGER_DEFINE__ #include "GuiManager.h" #undef __GUI_MANAGER_DEFINE__ #include "Brain.h" #include "BrainBrowserWindow.h" #include "BrainOpenGL.h" #include "BrainStructure.h" #include "BrowserTabContent.h" #include "BugReportDialog.h" #include "CaretAssert.h" #include "CaretLogger.h" #include "CaretMappableDataFile.h" #include "ChartingDataManager.h" #include "CiftiConnectivityMatrixDataFileManager.h" #include "CiftiFiberTrajectoryManager.h" #include "CiftiConnectivityMatrixParcelFile.h" #include "CiftiScalarDataSeriesFile.h" #include "ClippingPlanesDialog.h" #include "CursorDisplayScoped.h" #include "CursorManager.h" #include "CustomViewDialog.h" #include "DataFileException.h" #include "ElapsedTimer.h" #include "EventAlertUser.h" #include "EventBrowserTabGetAll.h" #include "EventBrowserWindowNew.h" #include "EventGraphicsUpdateAllWindows.h" #include "EventGraphicsUpdateOneWindow.h" #include "EventHelpViewerDisplay.h" #include "EventIdentificationHighlightLocation.h" #include "EventMacDockMenuUpdate.h" #include "EventManager.h" #include "EventMapYokingSelectMap.h" #include "EventModelGetAll.h" #include "EventOperatingSystemRequestOpenDataFile.h" #include "EventOverlaySettingsEditorDialogRequest.h" #include "EventPaletteColorMappingEditorDialogRequest.h" #include "EventProgressUpdate.h" #include "EventSurfaceColoringInvalidate.h" #include "EventUpdateInformationWindows.h" #include "EventUserInterfaceUpdate.h" #include "FociPropertiesEditorDialog.h" #include "HelpViewerDialog.h" #include "IdentifiedItemNode.h" #include "IdentifiedItemVoxel.h" #include "IdentificationManager.h" #include "IdentificationStringBuilder.h" #include "IdentifyBrainordinateDialog.h" #include "ImageFile.h" #include "ImageCaptureDialog.h" #include "InformationDisplayDialog.h" #include "OverlaySettingsEditorDialog.h" #include "MacDockMenu.h" #include "MovieDialog.h" #include "PaletteColorMappingEditorDialog.h" #include "PreferencesDialog.h" #include "SceneAttributes.h" #include "SceneClass.h" #include "SceneClassArray.h" #include "SceneDialog.h" #include "SceneWindowGeometry.h" #include "SelectionManager.h" #include "SelectionItemChartMatrix.h" #include "SelectionItemCiftiConnectivityMatrixRowColumn.h" #include "SelectionItemSurfaceNode.h" #include "SelectionItemSurfaceNodeIdentificationSymbol.h" #include "SelectionItemVoxel.h" #include "SelectionItemVoxelIdentificationSymbol.h" #include "SessionManager.h" #include "SpecFile.h" #include "SpecFileManagementDialog.h" #include "SurfacePropertiesEditorDialog.h" #include "Surface.h" #include "TileTabsConfigurationDialog.h" #include "VolumeMappableInterface.h" #include "WuQMessageBox.h" #include "WuQtUtilities.h" #include "CaretAssert.h" using namespace caret; /** * \defgroup GuiQt */ /** * \class caret::GuiManager * \brief Top level class for passing events to Gui widgets belonging to workbench window * \ingroup GuiQt */ /** * Constructor. * @param parent * Parent of this object. */ GuiManager::GuiManager(QObject* parent) : QObject(parent) { this->nameOfApplication = "Connectome Workbench"; //this->brainOpenGL = NULL; this->allowBrowserWindowsToCloseWithoutConfirmation = false; m_bugReportDialog = NULL; m_clippingPlanesDialog = NULL; m_customViewDialog = NULL; this->imageCaptureDialog = NULL; this->movieDialog = NULL; m_informationDisplayDialog = NULL; m_identifyBrainordinateDialog = NULL; this->preferencesDialog = NULL; this->connectomeDatabaseWebView = NULL; m_helpViewerDialog = NULL; m_paletteColorMappingEditor = NULL; this->sceneDialog = NULL; m_surfacePropertiesEditorDialog = NULL; m_tileTabsConfigurationDialog = NULL; this->cursorManager = new CursorManager(); /* * Information window. */ QIcon infoDisplayIcon; const bool infoDisplayIconValid = WuQtUtilities::loadIcon(":/ToolBar/info.png", infoDisplayIcon); m_informationDisplayDialogEnabledAction = WuQtUtilities::createAction("Information...", "Enables display of the Information Window\n" "when new information is available", this, this, SLOT(showHideInfoWindowSelected(bool))); if (infoDisplayIconValid) { m_informationDisplayDialogEnabledAction->setIcon(infoDisplayIcon); m_informationDisplayDialogEnabledAction->setIconVisibleInMenu(false); } else { m_informationDisplayDialogEnabledAction->setIconText("Info"); } m_informationDisplayDialogEnabledAction->blockSignals(true); m_informationDisplayDialogEnabledAction->setCheckable(true); m_informationDisplayDialogEnabledAction->setChecked(true); this->showHideInfoWindowSelected(m_informationDisplayDialogEnabledAction->isChecked()); m_informationDisplayDialogEnabledAction->setIconText("Info"); m_informationDisplayDialogEnabledAction->blockSignals(false); /* * Identify brainordinate window */ QIcon identifyDisplayIcon; const bool identifyDisplayIconValid = WuQtUtilities::loadIcon(":/ToolBar/identify.png", identifyDisplayIcon); m_identifyBrainordinateDialogEnabledAction = WuQtUtilities::createAction("Identify...", "Enables display of the Identify Brainordinate Window", this, this, SLOT(showIdentifyBrainordinateDialogActionToggled(bool))); if (identifyDisplayIconValid) { m_identifyBrainordinateDialogEnabledAction->setIcon(identifyDisplayIcon); m_identifyBrainordinateDialogEnabledAction->setIconVisibleInMenu(false); } else { m_identifyBrainordinateDialogEnabledAction->setIconText("ID"); } m_identifyBrainordinateDialogEnabledAction->blockSignals(true); m_identifyBrainordinateDialogEnabledAction->setCheckable(true); m_identifyBrainordinateDialogEnabledAction->setChecked(false); m_identifyBrainordinateDialogEnabledAction->blockSignals(false); /* * Scene dialog action */ m_sceneDialogDisplayAction = WuQtUtilities::createAction("Scenes...", "Show/Hide the Scenes Window", this, this, SLOT(sceneDialogDisplayActionToggled(bool))); QIcon clapBoardIcon; const bool clapBoardIconValid = WuQtUtilities::loadIcon(":/ToolBar/clapboard.png", clapBoardIcon); if (clapBoardIconValid) { m_sceneDialogDisplayAction->setIcon(clapBoardIcon); m_sceneDialogDisplayAction->setIconVisibleInMenu(false); } else { m_sceneDialogDisplayAction->setIconText("Scenes"); } m_sceneDialogDisplayAction->blockSignals(true); m_sceneDialogDisplayAction->setCheckable(true); m_sceneDialogDisplayAction->setChecked(false); m_sceneDialogDisplayAction->blockSignals(false); /* * Help dialog action */ m_helpViewerDialogDisplayAction = WuQtUtilities::createAction("Workbench Help...", "Show/Hide the Help Window", QKeySequence::HelpContents, this, this, SLOT(showHelpDialogActionToggled(bool))); QIcon helpIcon; const bool helpIconValid = WuQtUtilities::loadIcon(":/ToolBar/help.png", helpIcon); if (helpIconValid) { m_helpViewerDialogDisplayAction->setIcon(helpIcon); m_helpViewerDialogDisplayAction->setIconVisibleInMenu(false); } else { m_helpViewerDialogDisplayAction->setIconText("?"); } m_helpViewerDialogDisplayAction->blockSignals(true); m_helpViewerDialogDisplayAction->setCheckable(true); m_helpViewerDialogDisplayAction->setChecked(false); m_helpViewerDialogDisplayAction->blockSignals(false); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_ALERT_USER); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_BROWSER_WINDOW_NEW); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_HELP_VIEWER_DISPLAY); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_MAC_DOCK_MENU_UPDATE); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_OVERLAY_SETTINGS_EDITOR_SHOW); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_OPERATING_SYSTEM_REQUEST_OPEN_DATA_FILE); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_PALETTE_COLOR_MAPPING_EDITOR_SHOW); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_UPDATE_INFORMATION_WINDOWS); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_USER_INTERFACE_UPDATE); } /** * Destructor. */ GuiManager::~GuiManager() { EventManager::get()->removeAllEventsFromListener(this); delete this->cursorManager; if (this->connectomeDatabaseWebView != NULL) { delete this->connectomeDatabaseWebView; } FociPropertiesEditorDialog::deleteStaticMembers(); for (std::set::iterator iter = m_parentlessNonModalDialogs.begin(); iter != m_parentlessNonModalDialogs.end(); iter++) { QWidget* w = *iter; delete w; } m_parentlessNonModalDialogs.clear(); } /** * Get the GUI Manager. */ GuiManager* GuiManager::get() { if (GuiManager::singletonGuiManager == NULL) { GuiManager::singletonGuiManager = new GuiManager(); WuQtUtilities::sendListOfResourcesToCaretLogger(); } return GuiManager::singletonGuiManager; } /* * Create the singleton GUI Manager. */ void GuiManager::createGuiManager() { CaretAssertMessage((GuiManager::singletonGuiManager == NULL), "GUI manager has already been created."); GuiManager::singletonGuiManager = new GuiManager(); } /* * Delete the singleton GUI Manager. */ void GuiManager::deleteGuiManager() { CaretAssertMessage((GuiManager::singletonGuiManager != NULL), "GUI manager does not exist, cannot delete it."); delete GuiManager::singletonGuiManager; GuiManager::singletonGuiManager = NULL; } /** * Beep to alert the user. */ void GuiManager::beep(const int32_t numTimesToBeep) { for (int32_t i = 0; i < numTimesToBeep; i++) { SystemUtilities::sleepSeconds(0.25); QApplication::beep(); } } /** * @return The brain. */ Brain* GuiManager::getBrain() { return SessionManager::get()->getBrain(0); } /** * Get the Brain OpenGL for drawing with OpenGL. * * @return * Point to the brain. */ //BrainOpenGL* //GuiManager::getBrainOpenGL() //{ // if (this->brainOpenGL == NULL) { // this->brainOpenGL = BrainOpenGL::getBrainOpenGL(); // } // // return this->brainOpenGL; //} /** * See if a brain browser window can be closed. If there is only * one brain browser window, the user will be warned that any * changes to files will be lost and the application will exit. * If there is more than one brain browser window open and the * window being closed contains more than one tab, the user will * be warned. * * @param brainBrowserWindow * Brain browser window that will be closed. * @param numberOfOpenTabs * Number of tabs in window. * @return * True if window should be closed, else false. */ bool GuiManager::allowBrainBrowserWindowToClose(BrainBrowserWindow* brainBrowserWindow, const int32_t numberOfOpenTabs) { bool isBrowserWindowAllowedToClose = false; if (this->allowBrowserWindowsToCloseWithoutConfirmation) { isBrowserWindowAllowedToClose = true; } else { if (this->getNumberOfOpenBrainBrowserWindows() > 1) { /* * Warn if multiple tabs in window */ if (numberOfOpenTabs > 1) { QString tabMessage = QString::number(numberOfOpenTabs) + " tabs are open."; isBrowserWindowAllowedToClose = WuQMessageBox::warningCloseCancel(brainBrowserWindow, "Are you sure you want to close this window?", tabMessage); } else { isBrowserWindowAllowedToClose = true; } } else { isBrowserWindowAllowedToClose = this->exitProgram(brainBrowserWindow); } } if (isBrowserWindowAllowedToClose) { for (int32_t i = 0; i < static_cast(m_brainBrowserWindows.size()); i++) { if (m_brainBrowserWindows[i] == brainBrowserWindow) { m_brainBrowserWindows[i] = NULL; // must set to NULL BEFORE reparenting non-modal dialogs so they dont' see it this->reparentNonModalDialogs(brainBrowserWindow); } } EventManager::get()->sendEvent(EventMacDockMenuUpdate().getPointer()); } return isBrowserWindowAllowedToClose; } /** * Get the number of brain browser windows. * * @return Number of brain browser windows that are valid. */ int32_t GuiManager::getNumberOfOpenBrainBrowserWindows() const { int32_t numberOfWindows = 0; for (int32_t i = 0; i < static_cast(m_brainBrowserWindows.size()); i++) { if (m_brainBrowserWindows[i] != NULL) { numberOfWindows++; } } return numberOfWindows; } /** * Get all of the brain browser windows. * * @return * Vector containing all open brain browser windows. */ std::vector GuiManager::getAllOpenBrainBrowserWindows() const { std::vector windows; int32_t numWindows = static_cast(m_brainBrowserWindows.size()); for (int32_t i = 0; i < numWindows; i++) { if (m_brainBrowserWindows[i] != NULL) { windows.push_back(m_brainBrowserWindows[i]); } } return windows; } /** * @return Return the active browser window. If no browser window is active, * the browser window with the lowest index is returned. If no browser * window is open (which likely should never occur), NULL is returned. * * To verify that the returned window was the active window, call its * "isActiveWindow()" method. */ BrainBrowserWindow* GuiManager::getActiveBrowserWindow() const { BrainBrowserWindow* firstWindowFound = NULL; int32_t numWindows = static_cast(m_brainBrowserWindows.size()); for (int32_t i = 0; i < numWindows; i++) { BrainBrowserWindow* bbw = m_brainBrowserWindows[i]; if (bbw != NULL) { if (firstWindowFound == NULL) { firstWindowFound = bbw; } if (bbw->isActiveWindow()) { return bbw; } } } return firstWindowFound; } /** * Get the brain browser window with the given window index. * Note that as browser windows are opened or closed, a window's * index NEVER changes. Thus, a NULL value may be returned for * a window index referring to a window that was closed. * * @param browserWindowIndex * Index of the window. * @return * Pointer to window at given index or NULL in cases where * the window was closed. */ BrainBrowserWindow* GuiManager::getBrowserWindowByWindowIndex(const int32_t browserWindowIndex) { if (browserWindowIndex < static_cast(m_brainBrowserWindows.size())) { return m_brainBrowserWindows[browserWindowIndex]; } return NULL; } /** * Create a new BrainBrowser Window. * @param parent * Optional parent that is used only for window placement. * @param browserTabContent * Optional tab for initial windwo tab. * @param createDefaultTabs * If true, create the default tabs in the new window. */ BrainBrowserWindow* GuiManager::newBrainBrowserWindow(QWidget* parent, BrowserTabContent* browserTabContent, const bool createDefaultTabs) { /* * If no tabs can be created, do not create a new window. */ EventBrowserTabGetAll getAllTabs; EventManager::get()->sendEvent(getAllTabs.getPointer()); if (getAllTabs.getNumberOfBrowserTabs() == BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS) { return NULL; } int32_t windowIndex = -1; int32_t numWindows = static_cast(m_brainBrowserWindows.size()); for (int32_t i = 0; i < numWindows; i++) { if (m_brainBrowserWindows[i] == NULL) { windowIndex = i; break; } } BrainBrowserWindow* bbw = NULL; BrainBrowserWindow::CreateDefaultTabsMode tabsMode = (createDefaultTabs ? BrainBrowserWindow::CREATE_DEFAULT_TABS_YES : BrainBrowserWindow::CREATE_DEFAULT_TABS_NO); if (windowIndex < 0) { windowIndex = m_brainBrowserWindows.size(); bbw = new BrainBrowserWindow(windowIndex, browserTabContent, tabsMode); m_brainBrowserWindows.push_back(bbw); } else { bbw = new BrainBrowserWindow(windowIndex, browserTabContent, tabsMode); m_brainBrowserWindows[windowIndex] = bbw; } if (parent != NULL) { WuQtUtilities::moveWindowToOffset(parent, bbw, 20, 20); } bbw->show(); bbw->resetGraphicsWidgetMinimumSize(); return bbw; } /** * Exit the program. * @param * Parent over which dialogs are displayed for saving/verifying. * return * true if application should exit, else false. */ bool GuiManager::exitProgram(QWidget* parent) { /* * Exclude all * Connectivity Files */ std::vector dataFileTypesToExclude; dataFileTypesToExclude.push_back(DataFileTypeEnum::CONNECTIVITY_DENSE); dataFileTypesToExclude.push_back(DataFileTypeEnum::CONNECTIVITY_FIBER_ORIENTATIONS_TEMPORARY); dataFileTypesToExclude.push_back(DataFileTypeEnum::CONNECTIVITY_FIBER_TRAJECTORY_TEMPORARY); bool okToExit = false; /* * Are files modified? */ std::vector modifiedDataFiles; getBrain()->getAllModifiedFiles(dataFileTypesToExclude, modifiedDataFiles); const int32_t modFileCount = static_cast(modifiedDataFiles.size()); if (modFileCount > 0) { /* * Display dialog allowing user to save files (goes to Save/Manage * Files dialog), exit without saving, or cancel. */ const AString textMsg("Do you want to save changes you made to these files?"); AString infoTextMsg("Changes to these files will be lost if you don't save them:\n"); for (std::vector::iterator iter = modifiedDataFiles.begin(); iter != modifiedDataFiles.end(); iter++) { const CaretDataFile* cdf = *iter; infoTextMsg.appendWithNewLine(" " + cdf->getFileNameNoPath()); } infoTextMsg.appendWithNewLine(""); QMessageBox quitDialog(QMessageBox::Warning, "Exit Workbench", textMsg, QMessageBox::NoButton, parent); quitDialog.setInformativeText(infoTextMsg); QPushButton* saveButton = quitDialog.addButton("Save...", QMessageBox::AcceptRole); saveButton->setToolTip("Display manage files window to save files"); QPushButton* dontSaveButton = quitDialog.addButton("Don't Save", QMessageBox::DestructiveRole); dontSaveButton->setToolTip("Do not save changes and exit."); QPushButton* cancelButton = quitDialog.addButton("Cancel", QMessageBox::RejectRole); quitDialog.setDefaultButton(saveButton); quitDialog.setEscapeButton(cancelButton); quitDialog.exec(); const QAbstractButton* clickedButton = quitDialog.clickedButton(); if (clickedButton == saveButton) { if (SpecFileManagementDialog::runSaveFilesDialogWhileQuittingWorkbench(this->getBrain(), parent)) { okToExit = true; } } else if (clickedButton == dontSaveButton) { okToExit = true; } else if (clickedButton == cancelButton) { /* nothing */ } else { CaretAssert(0); } } else { const AString textMsg("Exiting Workbench"); const AString infoTextMsg("Would you like to save your Workbench windows in scene file " "so you can easily pick up where you left off?" "

" "Click the Show Details button for " "more information."); const AString detailTextMsg("Scenes allow one to regenerate exactly what is displayed in " "Workbench. This can be useful in these and other situations:" "\n\n" " * During manuscript preparation to restore Workbench to match " "a previously generated figure (image capture)." "\n\n" " * When returning to this dataset for further analysis." "\n\n" " * When sharing data sets with others to provide a particular " "view of a surface/volume with desired data (overlay and feature) " "selections."); QMessageBox quitDialog(QMessageBox::Warning, "Exit Workbench", textMsg, QMessageBox::NoButton, parent); quitDialog.setInformativeText(infoTextMsg); quitDialog.setDetailedText(detailTextMsg); QPushButton* exitButton = quitDialog.addButton("Exit", QMessageBox::AcceptRole); QPushButton* cancelButton = quitDialog.addButton("Cancel", QMessageBox::RejectRole); quitDialog.setDefaultButton(exitButton); quitDialog.setEscapeButton(cancelButton); quitDialog.exec(); const QAbstractButton* clickedButton = quitDialog.clickedButton(); if (clickedButton == exitButton) { okToExit = true; } else if (clickedButton == cancelButton) { /* Nothing */ } else { CaretAssert(0); } } if (okToExit) { std::vector bws = this->getAllOpenBrainBrowserWindows(); for (int i = 0; i < static_cast(bws.size()); i++) { bws[i]->deleteLater(); } QCoreApplication::instance()->quit(); } return okToExit; } /** * Show the Open Spec File Dialog with the given spec file. * * @param specFile * SpecFile displayed in the dialog. * @param browserWindow * Window on which dialog is displayed. * @return * True if user opened spec file, else false. */ bool GuiManager::processShowOpenSpecFileDialog(SpecFile* specFile, BrainBrowserWindow* browserWindow) { return SpecFileManagementDialog::runOpenSpecFileDialog(getBrain(), specFile, browserWindow); } /** * Show the Save/Manage Files Dialog. * * @param browserWindow * Window on which dialog is displayed. */ void GuiManager::processShowSaveManageFilesDialog(BrainBrowserWindow* browserWindow) { SpecFileManagementDialog::runManageFilesDialog(getBrain(), browserWindow); } /** * Get the model displayed in the given browser window. * @param browserWindowIndex * Index of browser window. * @return * Model in the browser window. May be NULL if no data loaded. */ Model* GuiManager::getModelInBrowserWindow(const int32_t browserWindowIndex) { BrowserTabContent* browserTabContent = getBrowserTabContentForBrowserWindow(browserWindowIndex, true); Model* model = NULL; if (browserTabContent != NULL) { model = browserTabContent->getModelForDisplay(); } return model; } /** * Get the browser tab content in a browser window. * @param browserWindowIndex * Index of browser window. * @param allowInvalidBrowserWindowIndex * In some instance, such as GUI construction or destruction, the window is not * fully created or deleted, thus "this->brainBrowserWindows" is invalid for * the given index. If this parameter is true, NULL will be * returned in this case. * @return * Browser tab content in the browser window. Value may be NULL * is allowInvalidBrowserWindowIndex is true */ BrowserTabContent* GuiManager::getBrowserTabContentForBrowserWindow(const int32_t browserWindowIndex, const bool allowInvalidBrowserWindowIndex) { if (allowInvalidBrowserWindowIndex) { if (browserWindowIndex >= static_cast(m_brainBrowserWindows.size())) { return NULL; } } CaretAssertVectorIndex(m_brainBrowserWindows, browserWindowIndex); BrainBrowserWindow* browserWindow = m_brainBrowserWindows[browserWindowIndex]; if (allowInvalidBrowserWindowIndex) { if (browserWindow == NULL) { return NULL; } } CaretAssert(browserWindow); BrowserTabContent* tabContent = browserWindow->getBrowserTabContent(); return tabContent; } /** * Called when bring all windows to front is selected. */ void GuiManager::processBringAllWindowsToFront() { for (int32_t i = 0; i < static_cast(m_brainBrowserWindows.size()); i++) { if (m_brainBrowserWindows[i] != NULL) { m_brainBrowserWindows[i]->show(); m_brainBrowserWindows[i]->activateWindow(); } } for (std::set::iterator iter = this->nonModalDialogs.begin(); iter != this->nonModalDialogs.end(); iter++) { QWidget* w = *iter; if (w->isVisible()) { w->raise(); } } for (std::set::iterator iter = m_parentlessNonModalDialogs.begin(); iter != m_parentlessNonModalDialogs.end(); iter++) { QWidget* w = *iter; if (w->isVisible()) { w->raise(); } } } /** * Called to tile the windows (arrange browser windows in a grid). */ void GuiManager::processTileWindows() { std::vector windows = getAllOpenBrainBrowserWindows(); const int32_t numWindows = static_cast(windows.size()); if (numWindows <= 1) { return; } QDesktopWidget* dw = QApplication::desktop(); const int32_t numScreens = dw->screenCount(); const int32_t windowsPerScreen = std::max(numWindows / numScreens, 1); /** * Determine the number of rows and columns for the montage. * Since screen width typically exceeds height, always have * columns greater than or equal to rows. */ int32_t numRows = (int)std::sqrt((double)windowsPerScreen); int32_t numCols = numRows; int32_t row2 = numRows * numRows; if (row2 < numWindows) { numCols++; } if ((numRows * numCols) < numWindows) { numRows++; } AString windowInfo("Tiled Windows"); /* * Arrange models left-to-right and top-to-bottom. * Note that origin is top-left corner. */ int32_t windowIndex = 0; for (int32_t iScreen = 0; iScreen < numScreens; iScreen++) { const QRect rect = dw->availableGeometry(iScreen); const int screenX = rect.x(); const int screenY = rect.y(); const int screenWidth = rect.width(); const int screenHeight = rect.height(); const int32_t windowWidth = screenWidth / numCols; const int32_t windowHeight = screenHeight / numRows; int32_t windowX = 0; int32_t windowY = screenY; for (int32_t iRow = 0; iRow < numRows; iRow++) { windowX = screenX; for (int32_t iCol = 0; iCol < numCols; iCol++) { windows[windowIndex]->setGeometry(windowX, windowY, windowWidth, windowHeight); windowX += windowWidth; QString info = (" Window: " + windows[windowIndex]->windowTitle() + " screen/x/y/w/h (" + QString::number(iScreen) + ", " + QString::number(windowX) + ", " + QString::number(windowY) + ", " + QString::number(windowWidth) + ", " + QString::number(windowHeight) + ")"); windowInfo.appendWithNewLine(info); windowIndex++; if (windowIndex >= numWindows) { /* * No more windows to place. * Containing loops would cause a crash. */ CaretLogFine(windowInfo); return; } } windowY += windowHeight; } } CaretLogFine(windowInfo); } /** * @return Name of the application. */ QString GuiManager::applicationName() const { return this->nameOfApplication; } /** * Receive events from the event manager. * * @param event * Event sent by event manager. */ void GuiManager::receiveEvent(Event* event) { if (event->getEventType() == EventTypeEnum::EVENT_ALERT_USER) { EventAlertUser* alertUserEvent = dynamic_cast(event); const AString message = alertUserEvent->getMessage(); BrainBrowserWindow* bbw = getActiveBrowserWindow(); CaretAssert(bbw); WuQMessageBox::errorOk(bbw, message); } else if (event->getEventType() == EventTypeEnum::EVENT_BROWSER_WINDOW_NEW) { EventBrowserWindowNew* eventNewBrowser = dynamic_cast(event); CaretAssert(eventNewBrowser); BrainBrowserWindow* bbw = this->newBrainBrowserWindow(eventNewBrowser->getParent(), eventNewBrowser->getBrowserTabContent(), true); if (bbw == NULL) { eventNewBrowser->setErrorMessage("Workench is exhausted. It cannot create any more windows."); eventNewBrowser->setEventProcessed(); return; } eventNewBrowser->setBrowserWindowCreated(bbw); eventNewBrowser->setEventProcessed(); /* * Initialize the size of the window */ const int w = bbw->width(); const int preferredMaxHeight = (WuQtUtilities::isSmallDisplay() ? 550 : 850); const int h = std::min(bbw->height(), preferredMaxHeight); bbw->resize(w, h); EventManager::get()->sendEvent(EventMacDockMenuUpdate().getPointer()); } else if (event->getEventType() == EventTypeEnum::EVENT_MAC_DOCK_MENU_UPDATE) { EventMacDockMenuUpdate* macDockMenuEvent = dynamic_cast(event); CaretAssert(event); MacDockMenu::createUpdateMacDockMenu(); macDockMenuEvent->setEventProcessed(); } else if (event->getEventType() == EventTypeEnum::EVENT_UPDATE_INFORMATION_WINDOWS) { EventUpdateInformationWindows* infoEvent = dynamic_cast(event); CaretAssert(infoEvent); bool showInfoDialog = infoEvent->isImportant(); if (showInfoDialog) { this->processShowInformationDisplayDialog(false); } infoEvent->setEventProcessed(); } else if (event->getEventType() == EventTypeEnum::EVENT_OVERLAY_SETTINGS_EDITOR_SHOW) { EventOverlaySettingsEditorDialogRequest* mapEditEvent = dynamic_cast(event); CaretAssert(mapEditEvent); const EventOverlaySettingsEditorDialogRequest::Mode mode = mapEditEvent->getMode(); const int browserWindowIndex = mapEditEvent->getBrowserWindowIndex(); CaretAssertVectorIndex(m_brainBrowserWindows, browserWindowIndex); BrainBrowserWindow* browserWindow = m_brainBrowserWindows[browserWindowIndex]; CaretAssert(browserWindow); Overlay* overlay = mapEditEvent->getOverlay(); switch (mode) { case EventOverlaySettingsEditorDialogRequest::MODE_OVERLAY_MAP_CHANGED: { for (std::set::iterator overlayEditorIter = m_overlaySettingsEditors.begin(); overlayEditorIter != m_overlaySettingsEditors.end(); overlayEditorIter++) { OverlaySettingsEditorDialog* med = *overlayEditorIter; med->updateIfThisOverlayIsInDialog(overlay); } } break; case EventOverlaySettingsEditorDialogRequest::MODE_SHOW_EDITOR: { OverlaySettingsEditorDialog* overlayEditor = NULL; for (std::set::iterator overlayEditorIter = m_overlaySettingsEditors.begin(); overlayEditorIter != m_overlaySettingsEditors.end(); overlayEditorIter++) { OverlaySettingsEditorDialog* med = *overlayEditorIter; if (med->isDoNotReplaceSelected() == false) { overlayEditor = med; break; } } bool placeInDefaultLocation = false; if (overlayEditor == NULL) { overlayEditor = new OverlaySettingsEditorDialog(browserWindow); m_overlaySettingsEditors.insert(overlayEditor); this->addNonModalDialog(overlayEditor); placeInDefaultLocation = true; } else { if (overlayEditor->isHidden()) { placeInDefaultLocation = true; } /* * Is the overlay editor requested for a window * that is not the parent of this overlay editor? */ if (browserWindow != overlayEditor->parent()) { /* * Switch the parent of the overlay editor. * On Linux, this will cause the overlay editor * to be "brought to the front" only when its * parent is "brought to the front". * * The position must be preserved. */ const QPoint globalPos = overlayEditor->pos(); overlayEditor->setParent(browserWindow, overlayEditor->windowFlags()); overlayEditor->move(globalPos); } } overlayEditor->updateDialogContent(overlay); overlayEditor->show(); overlayEditor->raise(); overlayEditor->activateWindow(); if (placeInDefaultLocation) { WuQtUtilities::moveWindowToSideOfParent(browserWindow, overlayEditor); } } break; } mapEditEvent->setEventProcessed(); } else if (event->getEventType() == EventTypeEnum::EVENT_OPERATING_SYSTEM_REQUEST_OPEN_DATA_FILE) { EventOperatingSystemRequestOpenDataFile* openFileEvent = dynamic_cast(event); CaretAssert(openFileEvent); BrainBrowserWindow* bbw = getActiveBrowserWindow(); if (bbw != NULL) { std::vector filenamesVector; std::vector dataFileTypeVectorNotUsed; filenamesVector.push_back(openFileEvent->getDataFileName()); bbw->loadFiles(bbw, filenamesVector, dataFileTypeVectorNotUsed, BrainBrowserWindow::LOAD_SPEC_FILE_WITH_DIALOG, "", ""); } else { /* * Browser window has not yet been created. * After it is created, the file will be opened. */ m_nameOfDataFileToOpenAfterStartup = openFileEvent->getDataFileName(); } } else if (event->getEventType() == EventTypeEnum::EVENT_PALETTE_COLOR_MAPPING_EDITOR_SHOW) { EventPaletteColorMappingEditorDialogRequest* paletteEditEvent = dynamic_cast(event); CaretAssert(paletteEditEvent); BrainBrowserWindow* browserWindow = m_brainBrowserWindows[paletteEditEvent->getBrowserWindowIndex()]; CaretAssert(browserWindow); bool placeInDefaultLocation = false; if (m_paletteColorMappingEditor == NULL) { m_paletteColorMappingEditor = new PaletteColorMappingEditorDialog(browserWindow); addNonModalDialog(m_paletteColorMappingEditor); placeInDefaultLocation = true; } else if (m_paletteColorMappingEditor->isHidden()) { placeInDefaultLocation = true; } m_paletteColorMappingEditor->updateDialogContent(paletteEditEvent->getCaretMappableDataFile(), paletteEditEvent->getMapIndex()); m_paletteColorMappingEditor->show(); m_paletteColorMappingEditor->raise(); m_paletteColorMappingEditor->activateWindow(); if (placeInDefaultLocation) { WuQtUtilities::moveWindowToSideOfParent(browserWindow, m_paletteColorMappingEditor); } paletteEditEvent->setEventProcessed(); } else if (event->getEventType() == EventTypeEnum::EVENT_HELP_VIEWER_DISPLAY) { EventHelpViewerDisplay* helpEvent = dynamic_cast(event); CaretAssert(helpEvent); showHideHelpDialog(true, helpEvent->getBrainBrowserWindow()); m_helpViewerDialog->showHelpPageWithName(helpEvent->getHelpPageName()); } } /** * Remove the tab content from all browser windows except for the given * browser windows, close the other browser windows, and then return * the tab content. * * @param browserWindow * Browser window that gets tab content from all other windows. * @param tabContents * Tab content from all other windows. */ void GuiManager::closeOtherWindowsAndReturnTheirTabContent(BrainBrowserWindow* browserWindow, std::vector& tabContents) { tabContents.clear(); const int32_t numWindows = m_brainBrowserWindows.size(); for (int32_t i = 0; i < numWindows; i++) { BrainBrowserWindow* bbw = m_brainBrowserWindows[i]; if (bbw != NULL) { if (bbw != browserWindow) { std::vector tabs; bbw->removeAndReturnAllTabs(tabs); tabContents.insert(tabContents.end(), tabs.begin(), tabs.end()); this->allowBrowserWindowsToCloseWithoutConfirmation = true; bbw->close(); /* * Should delete the windows that were closed! * When a window is closed, Qt uses 'deleteLater' * but we need them deleted now so that event listeners * are shut down since the closed windows no longer * have any content. */ QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); this->allowBrowserWindowsToCloseWithoutConfirmation = false; } } } } /** * Close all but the given window. * @param browserWindow * Window that is NOT closed. */ void GuiManager::closeAllOtherWindows(BrainBrowserWindow* browserWindow) { const int32_t numWindows = m_brainBrowserWindows.size(); for (int32_t i = 0; i < numWindows; i++) { BrainBrowserWindow* bbw = m_brainBrowserWindows[i]; if (bbw != NULL) { if (bbw != browserWindow) { this->allowBrowserWindowsToCloseWithoutConfirmation = true; bbw->close(); /* * Should delete the windows that were closed! * When a window is closed, Qt uses 'deleteLater' * but we need them deleted now so that event listeners * are shut down since the closed windows no longer * have any content. */ QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); this->allowBrowserWindowsToCloseWithoutConfirmation = false; } } } } /** * Reparent non-modal dialogs that may need to be reparented if the * original parent, a BrainBrowserWindow is closed in which case the * dialog is reparented to a different BrainBrowserWindow. * * @param closingBrainBrowserWindow * Browser window that is closing. */ void GuiManager::reparentNonModalDialogs(BrainBrowserWindow* closingBrainBrowserWindow) { BrainBrowserWindow* firstBrainBrowserWindow = NULL; for (int32_t i = 0; i < static_cast(m_brainBrowserWindows.size()); i++) { if (m_brainBrowserWindows[i] != NULL) { if (m_brainBrowserWindows[i] != closingBrainBrowserWindow) { firstBrainBrowserWindow = m_brainBrowserWindows[i]; break; } } } if (firstBrainBrowserWindow != NULL) { for (std::set::iterator iter = this->nonModalDialogs.begin(); iter != this->nonModalDialogs.end(); iter++) { QWidget* d = *iter; if (d->parent() == closingBrainBrowserWindow) { const bool wasVisible = d->isVisible(); const QPoint globalPos = d->pos(); d->setParent(firstBrainBrowserWindow, d->windowFlags()); d->move(globalPos); if (wasVisible) { d->show(); } else { d->hide(); } } /* * Update any dialogs that are WuQ non modal dialogs. */ WuQDialogNonModal* wuqNonModalDialog = dynamic_cast(d); if (wuqNonModalDialog != NULL) { wuqNonModalDialog->updateDialog(); } } } } /** * Show the surface properties editor dialog. * @param browserWindow * Browser window on which dialog is displayed. */ void GuiManager::processShowSurfacePropertiesEditorDialog(BrainBrowserWindow* browserWindow) { bool wasCreatedFlag = false; if (this->m_surfacePropertiesEditorDialog == NULL) { m_surfacePropertiesEditorDialog = new SurfacePropertiesEditorDialog(browserWindow); this->addNonModalDialog(m_surfacePropertiesEditorDialog); m_surfacePropertiesEditorDialog->setSaveWindowPositionForNextTime(true); wasCreatedFlag = true; } m_surfacePropertiesEditorDialog->setVisible(true); m_surfacePropertiesEditorDialog->show(); m_surfacePropertiesEditorDialog->activateWindow(); if (wasCreatedFlag) { WuQtUtilities::moveWindowToSideOfParent(browserWindow, m_surfacePropertiesEditorDialog); } } /** * @return The action for showing/hiding the scene dialog. */ QAction* GuiManager::getSceneDialogDisplayAction() { return m_sceneDialogDisplayAction; } /** * Gets called when the scene dialog action is toggled. * * @param status * New status (true display dialog, false hide it). */ void GuiManager::sceneDialogDisplayActionToggled(bool status) { showHideSceneDialog(status, NULL); } /** * Gets called by the scene dialog when the scene dialog is closed. * Ensures that the scene dialog display action status remains * synchronized with the displayed status of the scene dialog. */ void GuiManager::sceneDialogWasClosed() { m_sceneDialogDisplayAction->blockSignals(true); m_sceneDialogDisplayAction->setChecked(false); m_sceneDialogDisplayAction->blockSignals(false); } /** * Show or hide the scene dialog. * * @param status * True means show, false means hide. * @param parentBrainBrowserWindow * If this is not NULL, and the scene dialog needs to be created, * use this window as the parent and place the dialog next to this * window. */ void GuiManager::showHideSceneDialog(const bool status, BrainBrowserWindow* parentBrainBrowserWindow) { bool dialogWasCreated = false; QWidget* moveWindowParent = parentBrainBrowserWindow; if (status) { if (this->sceneDialog == NULL) { BrainBrowserWindow* sceneDialogParent = parentBrainBrowserWindow; if (sceneDialogParent == NULL) { sceneDialogParent = getActiveBrowserWindow(); } this->sceneDialog = new SceneDialog(sceneDialogParent); this->addNonModalDialog(this->sceneDialog); QObject::connect(this->sceneDialog, SIGNAL(dialogWasClosed()), this, SLOT(sceneDialogWasClosed())); dialogWasCreated = true; /* * If there was no parent dialog for placement of the scene * dialog and there is only one browser window, use the browser * for placement of the scene dialog. */ if (moveWindowParent == NULL) { if (getAllOpenBrainBrowserWindows().size() == 1) { moveWindowParent = sceneDialogParent; } } } this->sceneDialog->show(); this->sceneDialog->activateWindow(); } else { this->sceneDialog->close(); } if (dialogWasCreated) { if (moveWindowParent != NULL) { WuQtUtilities::moveWindowToSideOfParent(moveWindowParent, this->sceneDialog); } } m_sceneDialogDisplayAction->blockSignals(true); m_sceneDialogDisplayAction->setChecked(status); m_sceneDialogDisplayAction->blockSignals(false); } /** * Show the scene dialog. If dialog needs to be created, use the * given window as the parent. * @param browserWindowIn * Parent of scene dialog if it needs to be created. */ void GuiManager::processShowSceneDialog(BrainBrowserWindow* browserWindowIn) { showHideSceneDialog(true, browserWindowIn); } /** * Show the scene dialog and load the given scene from the given scene file. * If displaying the scene had an error, the scene dialog will remain open. * Otherwise, the scene dialog is closed. * * @param browserWindow * Parent of scene dialog if it needs to be created. * @param sceneFile * Scene File that contains the scene. * @param scene * Scene that is displayed. */ void GuiManager::processShowSceneDialogAndScene(BrainBrowserWindow* browserWindow, SceneFile* sceneFile, Scene* scene) { showHideSceneDialog(true, browserWindow); const bool sceneWasDisplayed = this->sceneDialog->displayScene(sceneFile, scene); if (sceneWasDisplayed) { showHideSceneDialog(false, NULL); } } /** * Show the Workbench Bug Report Dialog. * * @param browserWindow * Parent of dialog if it needs to be created. * @param openGLInformation * Information about OpenGL. */ void GuiManager::processShowBugReportDialog(BrainBrowserWindow* browserWindow, const AString& openGLInformation) { if (m_bugReportDialog == NULL) { m_bugReportDialog = new BugReportDialog(browserWindow, openGLInformation); this->addNonModalDialog(m_bugReportDialog); } m_bugReportDialog->setVisible(true); m_bugReportDialog->show(); m_bugReportDialog->activateWindow(); } /** * @return Action for display of help viewer. */ QAction* GuiManager::getHelpViewerDialogDisplayAction() { return m_helpViewerDialogDisplayAction; } /** * Show or hide the help dialog. * * @param status * True means show, false means hide. * @param parentBrainBrowserWindow * If this is not NULL, and the help dialog needs to be created, * use this window as the parent and place the dialog next to this * window. */ void GuiManager::showHideHelpDialog(const bool status, BrainBrowserWindow* parentBrainBrowserWindow) { bool dialogWasCreated = false; QWidget* moveWindowParent = parentBrainBrowserWindow; if (status) { if (m_helpViewerDialog == NULL) { BrainBrowserWindow* helpDialogParent = parentBrainBrowserWindow; if (helpDialogParent == NULL) { helpDialogParent = getActiveBrowserWindow(); } m_helpViewerDialog = new HelpViewerDialog(helpDialogParent); this->addNonModalDialog(m_helpViewerDialog); QObject::connect(m_helpViewerDialog, SIGNAL(dialogWasClosed()), this, SLOT(helpDialogWasClosed())); dialogWasCreated = true; /* * If there was no parent dialog for placement of the help * dialog and there is only one browser window, use the browser * for placement of the help dialog. */ if (moveWindowParent == NULL) { if (getAllOpenBrainBrowserWindows().size() == 1) { moveWindowParent = helpDialogParent; } } } m_helpViewerDialog->show(); m_helpViewerDialog->activateWindow(); } else { m_helpViewerDialog->close(); } if (dialogWasCreated) { if (moveWindowParent != NULL) { WuQtUtilities::moveWindowToSideOfParent(moveWindowParent, m_helpViewerDialog); } } m_helpViewerDialogDisplayAction->blockSignals(true); m_helpViewerDialogDisplayAction->setChecked(status); m_helpViewerDialogDisplayAction->blockSignals(false); } /** * Gets called by the help dialog when the help dialog is closed. * Ensures that the help dialog display action status remains * synchronized with the displayed status of the help dialog. */ void GuiManager::helpDialogWasClosed() { m_helpViewerDialogDisplayAction->blockSignals(true); m_helpViewerDialogDisplayAction->setChecked(false); m_helpViewerDialogDisplayAction->blockSignals(false); } /** * Called when show help action is triggered */ void GuiManager::showHelpDialogActionToggled(bool status) { showHideHelpDialog(status, NULL); } /** * @return The action that indicates the enabled status * for display of the information window. */ QAction* GuiManager::getInformationDisplayDialogEnabledAction() { return m_informationDisplayDialogEnabledAction; } /** * @return The action that indicates the enabled status * for display of the identify brainordinate dialog. */ QAction* GuiManager::getIdentifyBrainordinateDialogDisplayAction() { return m_identifyBrainordinateDialogEnabledAction; } /** * Show the information window. */ void GuiManager::processShowInformationWindow() { this->processShowInformationDisplayDialog(true); } void GuiManager::showHideInfoWindowSelected(bool status) { QString text("Show Information Window"); if (status) { text = "Hide Information Window"; if (m_informationDisplayDialogEnabledAction->signalsBlocked() == false) { this->processShowInformationDisplayDialog(true); } } text += ("\n\n" "When this button is 'on', the information window\n" "is automatically displayed when an identification\n" "operation (mouse click over surface or volume slice)\n" "is performed. "); m_informationDisplayDialogEnabledAction->setToolTip(text); } /** * Show the information display window. * @param forceDisplayOfDialog * If true, the window will be displayed even if its display * enabled status is off. */ void GuiManager::processShowInformationDisplayDialog(const bool forceDisplayOfDialog) { if (m_informationDisplayDialog == NULL) { std::vector bbws = this->getAllOpenBrainBrowserWindows(); if (bbws.empty() == false) { BrainBrowserWindow* parentWindow = bbws[0]; #ifdef CARET_OS_MACOSX m_informationDisplayDialog = new InformationDisplayDialog(parentWindow); this->addNonModalDialog(m_informationDisplayDialog); #else // CARET_OS_MACOSX m_informationDisplayDialog = new InformationDisplayDialog(NULL); addParentLessNonModalDialog(m_informationDisplayDialog); #endif // CARET_OS_MACOSX m_informationDisplayDialog->resize(600, 200); m_informationDisplayDialog->setSaveWindowPositionForNextTime(true); WuQtUtilities::moveWindowToSideOfParent(parentWindow, m_informationDisplayDialog); } } if (forceDisplayOfDialog || m_informationDisplayDialogEnabledAction->isChecked()) { if (m_informationDisplayDialog != NULL) { m_informationDisplayDialog->setVisible(true); m_informationDisplayDialog->show(); m_informationDisplayDialog->activateWindow(); } } } /** * Show/hide the identify dialog. * * @param status * Status (true/false) to display dialog. */ void GuiManager::showIdentifyBrainordinateDialogActionToggled(bool status) { showHideIdentfyBrainordinateDialog(status, NULL); } /** * Show or hide the identify brainordinate dialog. * * @param status * True means show, false means hide. * @param parentBrainBrowserWindow * If this is not NULL, and the help dialog needs to be created, * use this window as the parent and place the dialog next to this * window. */ void GuiManager::showHideIdentfyBrainordinateDialog(const bool status, BrainBrowserWindow* parentBrainBrowserWindow) { bool dialogWasCreated = false; QWidget* moveWindowParent = parentBrainBrowserWindow; if (status) { if (m_identifyBrainordinateDialog == NULL) { BrainBrowserWindow* idDialogParent = parentBrainBrowserWindow; if (idDialogParent == NULL) { idDialogParent = getActiveBrowserWindow(); } m_identifyBrainordinateDialog = new IdentifyBrainordinateDialog(idDialogParent); //m_identifyBrainordinateDialog->setSaveWindowPositionForNextTime(true); this->addNonModalDialog(m_identifyBrainordinateDialog); QObject::connect(m_identifyBrainordinateDialog, SIGNAL(dialogWasClosed()), this, SLOT(identifyBrainordinateDialogWasClosed())); dialogWasCreated = true; /* * If there was no parent dialog for placement of the help * dialog and there is only one browser window, use the browser * for placement of the help dialog. */ if (moveWindowParent == NULL) { if (getAllOpenBrainBrowserWindows().size() == 1) { moveWindowParent = idDialogParent; } } } m_identifyBrainordinateDialog->show(); m_identifyBrainordinateDialog->activateWindow(); } else { m_identifyBrainordinateDialog->close(); } if (dialogWasCreated) { if (moveWindowParent != NULL) { WuQtUtilities::moveWindowToSideOfParent(moveWindowParent, m_identifyBrainordinateDialog); } } m_identifyBrainordinateDialogEnabledAction->blockSignals(true); m_identifyBrainordinateDialogEnabledAction->setChecked(status); m_identifyBrainordinateDialogEnabledAction->blockSignals(false); } /** * Gets called when the identify dialog is closed. */ void GuiManager::identifyBrainordinateDialogWasClosed() { m_identifyBrainordinateDialogEnabledAction->blockSignals(true); m_identifyBrainordinateDialogEnabledAction->setChecked(false); m_identifyBrainordinateDialogEnabledAction->blockSignals(false); } /** * Add a non-modal dialog so that it may be reparented. * * @param dialog * The dialog. */ void GuiManager::addNonModalDialog(QWidget* dialog) { CaretAssert(dialog); this->nonModalDialogs.insert(dialog); } /** * Add a parent-less dialog so that it may be deleted when * the application is exited. * * @param dialog * The dialog. */ void GuiManager::addParentLessNonModalDialog(QWidget* dialog) { CaretAssert(dialog); m_parentlessNonModalDialogs.insert(dialog); } /** * Show the clipping planes dialog. * @param browserWindow * Window on which dialog was requested. */ void GuiManager::processShowClippingPlanesDialog(BrainBrowserWindow* browserWindow) { if (m_clippingPlanesDialog == NULL) { m_clippingPlanesDialog = new ClippingPlanesDialog(browserWindow); this->addNonModalDialog(m_clippingPlanesDialog); } const int32_t browserWindowIndex = browserWindow->getBrowserWindowIndex(); m_clippingPlanesDialog->updateContent(browserWindowIndex); m_clippingPlanesDialog->setVisible(true); m_clippingPlanesDialog->show(); m_clippingPlanesDialog->activateWindow(); } /** * Show the custom view dialog. * @param browserWindow * Window on which dialog was requested. */ void GuiManager::processShowCustomViewDialog(BrainBrowserWindow* browserWindow) { if (m_customViewDialog == NULL) { m_customViewDialog = new CustomViewDialog(browserWindow); this->addNonModalDialog(m_customViewDialog); } const int32_t browserWindowIndex = browserWindow->getBrowserWindowIndex(); m_customViewDialog->updateContent(browserWindowIndex); m_customViewDialog->setVisible(true); m_customViewDialog->show(); m_customViewDialog->activateWindow(); } /** * Show the tile tabs configuration dialog. * @param browserWindow * Window on which dialog was requested. */ void GuiManager::processShowTileTabsConfigurationDialog(caret::BrainBrowserWindow *browserWindow) { if (m_tileTabsConfigurationDialog == NULL) { m_tileTabsConfigurationDialog = new TileTabsConfigurationDialog(browserWindow); this->addNonModalDialog(m_tileTabsConfigurationDialog); } m_tileTabsConfigurationDialog->updateDialogWithSelectedTileTabsFromWindow(browserWindow); m_tileTabsConfigurationDialog->setVisible(true); m_tileTabsConfigurationDialog->show(); m_tileTabsConfigurationDialog->activateWindow(); } /** * Show the image capture window. * @param browserWindow * Window on which dialog was requested. */ void GuiManager::processShowImageCaptureDialog(BrainBrowserWindow* browserWindow) { if (this->imageCaptureDialog == NULL) { this->imageCaptureDialog = new ImageCaptureDialog(browserWindow); this->addNonModalDialog(this->imageCaptureDialog); } this->imageCaptureDialog->updateDialog(); this->imageCaptureDialog->setBrowserWindowIndex(browserWindow->getBrowserWindowIndex()); this->imageCaptureDialog->setVisible(true); this->imageCaptureDialog->show(); this->imageCaptureDialog->activateWindow(); } /** * Show the record movie window. * @param browserWindow * Window on which dialog was requested. */ void GuiManager::processShowMovieDialog(BrainBrowserWindow* browserWindow) { if (this->movieDialog == NULL) { this->movieDialog = new MovieDialog(browserWindow); this->addNonModalDialog(this->movieDialog); } //this->movieDialog->updateDialog(); //this->movieDialog->setBrowserWindowIndex(browserWindow->getBrowserWindowIndex()); this->movieDialog->setVisible(true); this->movieDialog->show(); this->movieDialog->activateWindow(); } /** * Show the preferences window. * @param browserWindow * Window on which dialog was requested. */ void GuiManager::processShowPreferencesDialog(BrainBrowserWindow* browserWindow) { if (this->preferencesDialog == NULL) { this->preferencesDialog = new PreferencesDialog(browserWindow); this->addNonModalDialog(this->preferencesDialog); } this->preferencesDialog->updateDialog(); this->preferencesDialog->setVisible(true); this->preferencesDialog->show(); this->preferencesDialog->activateWindow(); } /** * Show the allen database web view. * @param browserWindow * If the web view needs to be created, use this as parent. */ void GuiManager::processShowAllenDataBaseWebView(BrainBrowserWindow* browserWindow) { WuQMessageBox::informationOk(browserWindow, "Allen Database connection not yet implemented"); } /** * Show the connectome database web view. * @param browserWindow * If the web view needs to be created, use this as parent. */ void GuiManager::processShowConnectomeDataBaseWebView(BrainBrowserWindow* /*browserWindow*/) { if (this->connectomeDatabaseWebView == NULL) { this->connectomeDatabaseWebView = new WuQWebView(); this->connectomeDatabaseWebView->load(QUrl("https://db.humanconnectome.org/")); this->addNonModalDialog(this->connectomeDatabaseWebView); } this->connectomeDatabaseWebView->show(); } /** * sets animation start time for Time Course Dialogs */ void GuiManager::updateAnimationStartTime(double /*value*/) { } /** * @return The cursor manager. */ const CursorManager* GuiManager::getCursorManager() const { return this->cursorManager; } /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ SceneClass* GuiManager::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "GuiManager", 1); /* * Save session manager (brain, etc) */ sceneClass->addClass(SessionManager::get()->saveToScene(sceneAttributes, "m_sessionManager")); /* * Save windows */ std::vector browserWindowClasses; const int32_t numBrowserWindows = static_cast(m_brainBrowserWindows.size()); for (int32_t i = 0; i < numBrowserWindows; i++) { BrainBrowserWindow* bbw = m_brainBrowserWindows[i]; if (bbw != NULL) { browserWindowClasses.push_back(bbw->saveToScene(sceneAttributes, "m_brainBrowserWindows")); } } SceneClassArray* browserWindowArray = new SceneClassArray("m_brainBrowserWindows", browserWindowClasses); sceneClass->addChild(browserWindowArray); /* * Save information window */ if (m_informationDisplayDialog != NULL) { sceneClass->addClass(m_informationDisplayDialog->saveToScene(sceneAttributes, "m_informationDisplayDialog")); } /* * Save surface properties window */ if (m_surfacePropertiesEditorDialog != NULL) { sceneClass->addClass(m_surfacePropertiesEditorDialog->saveToScene(sceneAttributes, "m_surfacePropertiesEditorDialog")); } switch (sceneAttributes->getSceneType()) { case SceneTypeEnum::SCENE_TYPE_FULL: break; case SceneTypeEnum::SCENE_TYPE_GENERIC: break; } return sceneClass; } /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * SceneClass containing the state that was previously * saved and should be restored. */ void GuiManager::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { const int64_t eventCountAtStart = EventManager::get()->getEventIssuedCounter(); ElapsedTimer sceneRestoreTimer; sceneRestoreTimer.start(); if (sceneClass == NULL) { return; } switch (sceneAttributes->getSceneType()) { case SceneTypeEnum::SCENE_TYPE_FULL: break; case SceneTypeEnum::SCENE_TYPE_GENERIC: break; } /* * Invalid first window position used when * positioning other windows */ SceneWindowGeometry::setFirstBrowserWindowCoordinatesInvalid(); /* * Close all but one window and remove its tabs */ BrainBrowserWindow* firstBrowserWindow = getActiveBrowserWindow();; if (firstBrowserWindow != NULL) { closeAllOtherWindows(firstBrowserWindow); /* * Remove all tabs from window. * The tab content will be deleted by the session manager. */ std::vector windowTabContent; firstBrowserWindow->removeAndReturnAllTabs(windowTabContent); } /* * Close Overlay and palette editor windows since the files * displayed in them may become invalid */ for (std::set::iterator overlayEditorIter = m_overlaySettingsEditors.begin(); overlayEditorIter != m_overlaySettingsEditors.end(); overlayEditorIter++) { OverlaySettingsEditorDialog* med = *overlayEditorIter; med->close(); } if (m_paletteColorMappingEditor != NULL) { m_paletteColorMappingEditor->close(); } /* * Update the windows */ EventManager::get()->sendEvent(EventUserInterfaceUpdate().getPointer()); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); /* * Blocking user-interface and graphics event will speed up * restoration of the user interface. */ bool blockUserInteraceUpdateEvents = true; if (blockUserInteraceUpdateEvents) { EventManager::get()->blockEvent(EventTypeEnum::EVENT_USER_INTERFACE_UPDATE, true); } EventManager::get()->blockEvent(EventTypeEnum::EVENT_GRAPHICS_UPDATE_ALL_WINDOWS, true); EventManager::get()->blockEvent(EventTypeEnum::EVENT_GRAPHICS_UPDATE_ONE_WINDOW, true); /* * Restore session manager */ SessionManager::get()->restoreFromScene(sceneAttributes, sceneClass->getClass("m_sessionManager")); EventProgressUpdate progressEvent(""); /* * See if models available (user may have cancelled scene loading. */ EventModelGetAll getAllModelsEvent; EventManager::get()->sendEvent(getAllModelsEvent.getPointer()); const bool haveModels = (getAllModelsEvent.getModels().empty() == false); ElapsedTimer timer; timer.start(); if (haveModels) { /* * Get open windows */ std::list availableWindows; const int32_t numBrowserWindows = static_cast(m_brainBrowserWindows.size()); for (int32_t i = 0; i < numBrowserWindows; i++) { if (m_brainBrowserWindows[i] != NULL) { availableWindows.push_back(m_brainBrowserWindows[i]); } } /* * Restore windows */ progressEvent.setProgressMessage("Restoring browser windows"); EventManager::get()->sendEvent(progressEvent.getPointer()); const SceneClassArray* browserWindowArray = sceneClass->getClassArray("m_brainBrowserWindows"); if (browserWindowArray != NULL) { const int32_t numBrowserClasses = browserWindowArray->getNumberOfArrayElements(); for (int32_t i = 0; i < numBrowserClasses; i++) { const SceneClass* browserClass = browserWindowArray->getClassAtIndex(i); BrainBrowserWindow* bbw = NULL; if (availableWindows.empty() == false) { bbw = availableWindows.front(); availableWindows.pop_front(); } else { bbw = newBrainBrowserWindow(NULL, NULL, false); } if (bbw != NULL) { bbw->restoreFromScene(sceneAttributes, browserClass); } } } CaretLogFine("Time to restore browser windows was " + QString::number(timer.getElapsedTimeSeconds(), 'f', 3) + " seconds"); timer.reset(); /* * Restore information window */ progressEvent.setProgressMessage("Restoring Information Window"); EventManager::get()->sendEvent(progressEvent.getPointer()); const SceneClass* infoWindowClass = sceneClass->getClass("m_informationDisplayDialog"); if (infoWindowClass != NULL) { if (m_informationDisplayDialog == NULL) { processShowInformationWindow(); } else if (m_informationDisplayDialog->isVisible() == false) { processShowInformationWindow(); } m_informationDisplayDialog->restoreFromScene(sceneAttributes, infoWindowClass); } else { if (m_informationDisplayDialog != NULL) { /* * Will clear text */ m_informationDisplayDialog->restoreFromScene(sceneAttributes, NULL); } } /* * Restore surface properties */ progressEvent.setProgressMessage("Restoring Surface Properties Window"); EventManager::get()->sendEvent(progressEvent.getPointer()); const SceneClass* surfPropClass = sceneClass->getClass("m_surfacePropertiesEditorDialog"); if (surfPropClass != NULL) { if (m_surfacePropertiesEditorDialog == NULL) { processShowSurfacePropertiesEditorDialog(firstBrowserWindow); } else if (m_surfacePropertiesEditorDialog->isVisible() == false) { processShowSurfacePropertiesEditorDialog(firstBrowserWindow); } m_surfacePropertiesEditorDialog->restoreFromScene(sceneAttributes, surfPropClass); } CaretLogFine("Time to restore information/property windows was " + QString::number(timer.getElapsedTimeSeconds(), 'f', 3) + " seconds"); timer.reset(); } progressEvent.setProgressMessage("Invalidating coloring and updating user interface"); EventManager::get()->sendEvent(progressEvent.getPointer()); EventManager::get()->sendEvent(EventSurfaceColoringInvalidate().getPointer()); if (blockUserInteraceUpdateEvents) { EventManager::get()->blockEvent(EventTypeEnum::EVENT_USER_INTERFACE_UPDATE, false); } EventManager::get()->sendEvent(EventUserInterfaceUpdate().getPointer()); /* * Unblock graphics updates */ EventManager::get()->blockEvent(EventTypeEnum::EVENT_GRAPHICS_UPDATE_ALL_WINDOWS, false); EventManager::get()->blockEvent(EventTypeEnum::EVENT_GRAPHICS_UPDATE_ONE_WINDOW, false); progressEvent.setProgressMessage("Updating graphics in all windows"); EventManager::get()->sendEvent(progressEvent.getPointer()); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); CaretLogFine("Time to update graphics in all windows was " + QString::number(timer.getElapsedTimeSeconds(), 'f', 3) + " seconds"); timer.reset(); const int64_t totalNumberOfEvents = (EventManager::get()->getEventIssuedCounter() - eventCountAtStart); CaretLogFine("Time to restore scene was " + QString::number(sceneRestoreTimer.getElapsedTimeSeconds(), 'f', 3) + " seconds with " + AString::number(totalNumberOfEvents) + " events"); } /** * Get the name of the data file that may have been set * if Workbench was started by double-clicking a file * in the Mac OSX finder. */ AString GuiManager::getNameOfDataFileToOpenAfterStartup() const { return m_nameOfDataFileToOpenAfterStartup; } /** * Process identification after item(s) selected using a selection manager. * * @parma tabIndex * Index of tab in which identification took place. This value may * be negative indicating that the identification request is not * from a browser tab. One source for this is the Select Brainordinate * option on the Information Window. * @param selectionManager * The selection manager. * @param parentWidget * Widget on which any error message windows are displayed. */ void GuiManager::processIdentification(const int32_t tabIndex, SelectionManager* selectionManager, QWidget* parentWidget) { CursorDisplayScoped cursor; cursor.showWaitCursor(); Brain* brain = GuiManager::get()->getBrain(); CiftiConnectivityMatrixDataFileManager* ciftiConnectivityManager = SessionManager::get()->getCiftiConnectivityMatrixDataFileManager(); CiftiFiberTrajectoryManager* ciftiFiberTrajectoryManager = SessionManager::get()->getCiftiFiberTrajectoryManager(); ChartingDataManager* chartingDataManager = brain->getChartingDataManager(); IdentificationManager* identificationManager = brain->getIdentificationManager(); bool updateGraphicsFlag = false; bool updateInformationFlag = false; std::vector ciftiLoadingInfo; const QString breakAndIndent("
    "); SelectionItemSurfaceNodeIdentificationSymbol* nodeIdSymbol = selectionManager->getSurfaceNodeIdentificationSymbol(); SelectionItemVoxelIdentificationSymbol* voxelIdSymbol = selectionManager->getVoxelIdentificationSymbol(); if ((nodeIdSymbol->getSurface() != NULL) && (nodeIdSymbol->getNodeNumber() >= 0)) { const Surface* surface = nodeIdSymbol->getSurface(); const int32_t surfaceNumberOfNodes = surface->getNumberOfNodes(); const int32_t nodeIndex = nodeIdSymbol->getNodeNumber(); const StructureEnum::Enum structure = surface->getStructure(); identificationManager->removeIdentifiedNodeItem(structure, surfaceNumberOfNodes, nodeIndex); updateGraphicsFlag = true; updateInformationFlag = true; } else if (voxelIdSymbol->isValid()) { float voxelXYZ[3]; voxelIdSymbol->getVoxelXYZ(voxelXYZ); identificationManager->removeIdentifiedVoxelItem(voxelXYZ); updateGraphicsFlag = true; updateInformationFlag = true; } else { IdentifiedItem* identifiedItem = NULL; SelectionItemSurfaceNode* idNode = selectionManager->getSurfaceNodeIdentification(); SelectionItemVoxel* idVoxel = selectionManager->getVoxelIdentification(); /* * If there is a voxel ID but no node ID, identify a * node near the voxel coordinate, if it is close by. */ bool nodeIdentificationCreatedFromVoxelIdentificationFlag = false; if (idNode->isValid() == false) { if (idVoxel->isValid()) { double doubleXYZ[3]; idVoxel->getModelXYZ(doubleXYZ); const float voxelXYZ[3] = { doubleXYZ[0], doubleXYZ[1], doubleXYZ[2] }; Surface* surface = brain->getPrimaryAnatomicalSurfaceNearestCoordinate(voxelXYZ, 3.0); if (surface != NULL) { const int nodeIndex = surface->closestNode(voxelXYZ); if (nodeIndex >= 0) { idNode->reset(); idNode->setBrain(brain); idNode->setSurface(surface); idNode->setNodeNumber(nodeIndex); nodeIdentificationCreatedFromVoxelIdentificationFlag = true; } } } } /* * Load CIFTI NODE data prior to ID Message so that CIFTI * data shown in identification text is correct for the * node that was loaded. */ bool triedToLoadSurfaceODemandData = false; if (idNode->isValid()) { /* * NOT: node id was NOT created from voxel ID */ if (nodeIdentificationCreatedFromVoxelIdentificationFlag == false) { Surface* surface = idNode->getSurface(); const int32_t nodeIndex = idNode->getNodeNumber(); try { triedToLoadSurfaceODemandData = true; ciftiConnectivityManager->loadDataForSurfaceNode(brain, surface, nodeIndex, ciftiLoadingInfo); ciftiFiberTrajectoryManager->loadDataForSurfaceNode(brain, surface, nodeIndex, ciftiLoadingInfo); chartingDataManager->loadChartForSurfaceNode(surface, nodeIndex); updateGraphicsFlag = true; } catch (const DataFileException& e) { cursor.restoreCursor(); QMessageBox::critical(parentWidget, "", e.whatString()); cursor.showWaitCursor(); } } } /* * Load CIFTI VOXEL data prior to ID Message so that CIFTI * data shown in identification text is correct for the * voxel that was loaded. * * Note: If there was an attempt to load data for surface, * do not try to load voxel data. Otherwise, if the voxel * data loading fails, it will clear the coloring for * the connetivity data and make it appear as if loading data * failed. */ if (idVoxel->isValid() && ( ! triedToLoadSurfaceODemandData)) { const VolumeMappableInterface* volumeFile = idVoxel->getVolumeFile(); int64_t voxelIJK[3]; idVoxel->getVoxelIJK(voxelIJK); if (volumeFile != NULL) { float xyz[3]; volumeFile->indexToSpace(voxelIJK, xyz); updateGraphicsFlag = true; try { ciftiConnectivityManager->loadDataForVoxelAtCoordinate(brain, xyz, ciftiLoadingInfo); ciftiFiberTrajectoryManager->loadDataForVoxelAtCoordinate(brain, xyz, ciftiLoadingInfo); } catch (const DataFileException& e) { cursor.restoreCursor(); QMessageBox::critical(parentWidget, "", e.whatString()); cursor.showWaitCursor(); } try { chartingDataManager->loadChartForVoxelAtCoordinate(xyz); } catch (const DataFileException& e) { cursor.restoreCursor(); QMessageBox::critical(parentWidget, "", e.whatString()); cursor.showWaitCursor(); } } } SelectionItemChartMatrix* idChartMatrix = selectionManager->getChartMatrixIdentification(); if (idChartMatrix->isValid()) { ChartableMatrixInterface* chartMatrixInterface = idChartMatrix->getChartableMatrixInterface(); if (chartMatrixInterface != NULL) { CiftiConnectivityMatrixParcelFile* ciftiParcelFile = dynamic_cast(chartMatrixInterface); if (ciftiParcelFile != NULL) { if (ciftiParcelFile->isMapDataLoadingEnabled(0)) { const int32_t rowIndex = idChartMatrix->getMatrixRowIndex(); const int32_t columnIndex = idChartMatrix->getMatrixColumnIndex(); if ((rowIndex >= 0) && (columnIndex >= 0)) { try { ciftiConnectivityManager->loadRowOrColumnFromParcelFile(brain, ciftiParcelFile, rowIndex, columnIndex, ciftiLoadingInfo); } catch (const DataFileException& e) { cursor.restoreCursor(); QMessageBox::critical(parentWidget, "", e.whatString()); cursor.showWaitCursor(); } updateGraphicsFlag = true; } } } CiftiScalarDataSeriesFile* scalarDataSeriesFile = dynamic_cast(chartMatrixInterface); if (scalarDataSeriesFile != NULL) { const int32_t rowIndex = idChartMatrix->getMatrixRowIndex(); if (rowIndex >= 0) { scalarDataSeriesFile->setSelectedMapIndex(tabIndex, rowIndex); const MapYokingGroupEnum::Enum mapYoking = scalarDataSeriesFile->getMatrixRowColumnMapYokingGroup(tabIndex); if (mapYoking != MapYokingGroupEnum::MAP_YOKING_GROUP_OFF) { EventMapYokingSelectMap selectMapEvent(mapYoking, scalarDataSeriesFile, rowIndex, true); EventManager::get()->sendEvent(selectMapEvent.getPointer()); } else { chartingDataManager->loadChartForCiftiMappableFileRow(scalarDataSeriesFile, rowIndex); } updateGraphicsFlag = true; } } } } SelectionItemCiftiConnectivityMatrixRowColumn* idCiftiConnMatrix = selectionManager->getCiftiConnectivityMatrixRowColumnIdentification(); if (idCiftiConnMatrix != NULL) { CiftiMappableConnectivityMatrixDataFile* matrixFile = idCiftiConnMatrix->getCiftiConnectivityMatrixFile(); if (matrixFile != NULL) { const int32_t rowIndex = idCiftiConnMatrix->getMatrixRowIndex(); const int32_t columnIndex = idCiftiConnMatrix->getMatrixColumnIndex(); if ((rowIndex >= 0) || (columnIndex >= 0)) { ciftiConnectivityManager->loadRowOrColumnFromConnectivityMatrixFile(brain, matrixFile, rowIndex, columnIndex, ciftiLoadingInfo); updateGraphicsFlag = true; } } } /* * Generate identification manager */ const AString identificationMessage = selectionManager->getIdentificationText(brain); bool issuedIdentificationLocationEvent = false; if (idNode->isValid()) { Surface* surface = idNode->getSurface(); const int32_t nodeIndex = idNode->getNodeNumber(); /* * Save last selected node which may get used for foci creation. */ selectionManager->setLastSelectedItem(idNode); BrainStructure* brainStructure = surface->getBrainStructure(); CaretAssert(brainStructure); float xyz[3]; const Surface* primaryAnatomicalSurface = brainStructure->getPrimaryAnatomicalSurface(); if (primaryAnatomicalSurface != NULL) { primaryAnatomicalSurface->getCoordinate(nodeIndex, xyz); } else { CaretLogWarning("No surface/primary anatomical surface for " + StructureEnum::toGuiName(brainStructure->getStructure())); xyz[0] = -10000000.0; xyz[1] = -10000000.0; xyz[2] = -10000000.0; } identifiedItem = new IdentifiedItemNode(identificationMessage, surface->getStructure(), surface->getNumberOfNodes(), nodeIndex); /* * Only issue identification event for node * if it WAS NOT created from the voxel identification * location. */ if (nodeIdentificationCreatedFromVoxelIdentificationFlag == false) { if (issuedIdentificationLocationEvent == false) { EventIdentificationHighlightLocation idLocation(tabIndex, xyz, EventIdentificationHighlightLocation::LOAD_FIBER_ORIENTATION_SAMPLES_MODE_YES); EventManager::get()->sendEvent(idLocation.getPointer()); issuedIdentificationLocationEvent = true; } } } if (idVoxel->isValid()) { const VolumeMappableInterface* volumeFile = idVoxel->getVolumeFile(); int64_t voxelIJK[3]; idVoxel->getVoxelIJK(voxelIJK); if (volumeFile != NULL) { float xyz[3]; volumeFile->indexToSpace(voxelIJK, xyz); if (identifiedItem == NULL) { identifiedItem = new IdentifiedItemVoxel(identificationMessage, xyz); } if (issuedIdentificationLocationEvent == false) { EventIdentificationHighlightLocation idLocation(tabIndex, xyz, EventIdentificationHighlightLocation::LOAD_FIBER_ORIENTATION_SAMPLES_MODE_YES); EventManager::get()->sendEvent(idLocation.getPointer()); issuedIdentificationLocationEvent = true; } /* * Save last selected node which may get used for foci creation. */ selectionManager->setLastSelectedItem(idVoxel); } } if (identifiedItem == NULL) { if (identificationMessage.isEmpty() == false) { identifiedItem = new IdentifiedItem(identificationMessage); } } AString ciftiInfo; if (ciftiLoadingInfo.empty() == false) { IdentificationStringBuilder ciftiIdStringBuilder; ciftiIdStringBuilder.addLine(false, "CIFTI data loaded", " "); for (std::vector::iterator iter = ciftiLoadingInfo.begin(); iter != ciftiLoadingInfo.end(); iter++) { ciftiIdStringBuilder.addLine(true, *iter); } ciftiInfo = ciftiIdStringBuilder.toString(); } if (ciftiInfo.isEmpty() == false) { if (identifiedItem != NULL) { identifiedItem->appendText(ciftiInfo); } else { identifiedItem = new IdentifiedItem(ciftiInfo); } } if (identifiedItem != NULL) { identificationManager->addIdentifiedItem(identifiedItem); updateInformationFlag = true; } } if (updateInformationFlag) { EventManager::get()->sendEvent(EventUpdateInformationWindows().getPointer()); } if (updateGraphicsFlag) { EventManager::get()->sendEvent(EventSurfaceColoringInvalidate().getPointer()); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); EventManager::get()->sendEvent(EventUserInterfaceUpdate().addToolBar().addToolBox().getPointer()); } } // tabIndex workbench-1.1.1/src/GuiQt/GuiManager.h000066400000000000000000000266571255417355300175360ustar00rootroot00000000000000 #ifndef __GUI_MANAGER_H__ #define __GUI_MANAGER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include "EventListenerInterface.h" #include "SceneableInterface.h" #include "WuQWebView.h" class QAction; class QDialog; class QMenu; class QWidget; class MovieDialog; namespace caret { class Brain; class BrainBrowserWindow; class BrowserTabContent; class BugReportDialog; class ClippingPlanesDialog; class CursorManager; class CustomViewDialog; class HelpViewerDialog; class IdentifyBrainordinateDialog; class ImageFile; class ImageCaptureDialog; class InformationDisplayDialog; class OverlaySettingsEditorDialog; class Model; class PaletteColorMappingEditorDialog; class PreferencesDialog; class Scene; class SceneDialog; class SceneFile; class SelectionManager; class SpecFile; class SurfacePropertiesEditorDialog; class TileTabsConfigurationDialog; /** * Manages the graphical user-interface. */ class GuiManager : public QObject, public EventListenerInterface, public SceneableInterface { Q_OBJECT public: static GuiManager* get(); static void createGuiManager(); static void deleteGuiManager(); void beep(const int32_t numTimesToBeep = 1); Brain* getBrain(); int32_t getNumberOfOpenBrainBrowserWindows() const; BrainBrowserWindow* getActiveBrowserWindow() const; std::vector getAllOpenBrainBrowserWindows() const; BrainBrowserWindow* getBrowserWindowByWindowIndex(const int32_t browserWindowIndex); bool allowBrainBrowserWindowToClose(BrainBrowserWindow* bbw, const int32_t numberOfOpenTabs); bool exitProgram(QWidget* parent); bool processShowOpenSpecFileDialog(SpecFile* specFile, BrainBrowserWindow* browserWindow); void processShowSaveManageFilesDialog(BrainBrowserWindow* browserWindow); QString applicationName() const; //BrainOpenGL* getBrainOpenGL(); BrowserTabContent* getBrowserTabContentForBrowserWindow(const int32_t browserWindowIndex, const bool allowInvalidBrowserWindowIndex); Model* getModelInBrowserWindow(const int32_t browserWindowIndex); void receiveEvent(Event* event); const CursorManager* getCursorManager() const; QAction* getInformationDisplayDialogEnabledAction(); QAction* getIdentifyBrainordinateDialogDisplayAction(); QAction* getSceneDialogDisplayAction(); QAction* getHelpViewerDialogDisplayAction(); void closeAllOtherWindows(BrainBrowserWindow* browserWindow); void closeOtherWindowsAndReturnTheirTabContent(BrainBrowserWindow* browserWindow, std::vector& tabContents); void processShowBugReportDialog(BrainBrowserWindow* browserWindow, const AString& openGLInformation); void processShowClippingPlanesDialog(BrainBrowserWindow* browserWindow); void processShowCustomViewDialog(BrainBrowserWindow* browserWindow); void processShowImageCaptureDialog(BrainBrowserWindow* browserWindow); void processShowMovieDialog(BrainBrowserWindow* browserWindow); void processShowPreferencesDialog(BrainBrowserWindow* browserWindow); void processShowInformationDisplayDialog(const bool forceDisplayOfDialog); void processShowTileTabsConfigurationDialog(BrainBrowserWindow* browserWindow); void showHideIdentfyBrainordinateDialog(const bool status, BrainBrowserWindow* parentBrainBrowserWindow); void processShowSceneDialog(BrainBrowserWindow* browserWindow); void processShowSurfacePropertiesEditorDialog(BrainBrowserWindow* browserWindow); void processShowSceneDialogAndScene(BrainBrowserWindow* browserWindow, SceneFile* sceneFile, Scene* scene); void processShowAllenDataBaseWebView(BrainBrowserWindow* browserWindow); void processShowConnectomeDataBaseWebView(BrainBrowserWindow* browserWindow); void updateAnimationStartTime(double value); virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); AString getNameOfDataFileToOpenAfterStartup() const; void processIdentification(const int32_t tabIndex, SelectionManager* selectionManager, QWidget* parentWidget); public slots: void processBringAllWindowsToFront(); void processShowInformationWindow(); void processTileWindows(); void showHideInfoWindowSelected(bool); void showIdentifyBrainordinateDialogActionToggled(bool); void sceneDialogDisplayActionToggled(bool); void showHelpDialogActionToggled(bool); private slots: void helpDialogWasClosed(); void sceneDialogWasClosed(); void identifyBrainordinateDialogWasClosed(); private: GuiManager(QObject* parent = 0); virtual ~GuiManager(); GuiManager(const GuiManager&); GuiManager& operator=(const GuiManager&); BrainBrowserWindow* newBrainBrowserWindow(QWidget* parent, BrowserTabContent* browserTabContent, const bool createDefaultTabs); void reparentNonModalDialogs(BrainBrowserWindow* closingBrainBrowserWindow); void showHideSceneDialog(const bool status, BrainBrowserWindow* parentBrainBrowserWindow); void showHideHelpDialog(const bool status, BrainBrowserWindow* parentBrainBrowserWindow); // void processShowHelpViewerDialog(BrainBrowserWindow* browserWindow, // const AString& helpPageName); void addNonModalDialog(QWidget* dialog); void addParentLessNonModalDialog(QWidget* dialog); /** One instance of the GuiManager */ static GuiManager* singletonGuiManager; /** * Contains pointers to Brain Browser windows * As BrainBrowser windows are closed, some of * the elements may be NULL. */ std::vector m_brainBrowserWindows; /** Name of application */ QString nameOfApplication; /** Skips confirmation of browser window closing when all tabs are moved to one window */ bool allowBrowserWindowsToCloseWithoutConfirmation; /* Performs OpenGL drawing commands */ //BrainOpenGL* brainOpenGL; /* Editor for overlay settings. */ std::set m_overlaySettingsEditors; /** Editor for palette color mapping editing */ PaletteColorMappingEditorDialog* m_paletteColorMappingEditor; TileTabsConfigurationDialog* m_tileTabsConfigurationDialog; ClippingPlanesDialog* m_clippingPlanesDialog; CustomViewDialog* m_customViewDialog; ImageCaptureDialog* imageCaptureDialog; MovieDialog* movieDialog; PreferencesDialog* preferencesDialog; InformationDisplayDialog* m_informationDisplayDialog; IdentifyBrainordinateDialog* m_identifyBrainordinateDialog; SceneDialog* sceneDialog; QAction* m_sceneDialogDisplayAction; SurfacePropertiesEditorDialog* m_surfacePropertiesEditorDialog; WuQWebView* connectomeDatabaseWebView; CursorManager* cursorManager; QAction* m_informationDisplayDialogEnabledAction; QAction* m_identifyBrainordinateDialogEnabledAction; BugReportDialog* m_bugReportDialog; QAction* m_helpViewerDialogDisplayAction; HelpViewerDialog* m_helpViewerDialog; /** * Tracks non-modal dialogs that are created only one time * and may need to be reparented if the original parent, a * BrainBrowserWindow is closed in which case the dialog * is reparented to a different BrainBrowserWindow. * * On Linux and Windows, behavior is that when a dialog becomes active, * its parent window is brought forward to be directly behind the * dialog. When there are multiple browser windows, this may cause * the parent browser window to be brought forward and obscure * a browser window (not a parent of the dialog) that the user * was viewing. In this case, it may be desirable to make * the dialog parent-less. */ std::set nonModalDialogs; /** * Tracks non-modal dialogs WITHOUT parents. Dialogs without parents * are undesirable on Mac because when the parent-less dialog is * the active window, the menu bar will disappear. See the * comment for 'nonModalDialogs' for more information. */ std::set m_parentlessNonModalDialogs; /** * If Workbench is started by double-clicking a data file in * the Mac OSX Finder, this will contain the name of the data * file. When the event is received, Workbench has not yet * created windows. After creating the first Browser Window, * the values of this string is requested, and if valid, * the data file is opened. */ AString m_nameOfDataFileToOpenAfterStartup; }; #ifdef __GUI_MANAGER_DEFINE__ GuiManager* GuiManager::singletonGuiManager = NULL; #endif // __GUI_MANAGER_DEFINE__ } #endif // __GUI_MANAGER_H__ workbench-1.1.1/src/GuiQt/HelpViewerDialog.cxx000066400000000000000000001331641255417355300212540ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __HELP_VIEWER_DIALOG_DECLARE__ #include "HelpViewerDialog.h" #undef __HELP_VIEWER_DIALOG_DECLARE__ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "CaretAssert.h" #include "CaretLogger.h" #include "CommandOperation.h" #include "CommandOperationManager.h" #include "WuQMessageBox.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::HelpViewerDialog * \brief Dialog that displays the applications help information. * \ingroup GuiQt */ /** * Constructor. * * @param parent * Parent of this dialog. * @param f * Qt's window flags. */ HelpViewerDialog::HelpViewerDialog(QWidget* parent, Qt::WindowFlags f) : WuQDialogNonModal("Help", parent, f) { m_helpBrowser = NULL; m_topicSearchLineEditFirstMouseClick = true; setApplyButtonText(""); QTabWidget* indexSearchTabWidget = new QTabWidget(); indexSearchTabWidget->addTab(createTableOfContentsWidget(), "Table of Contents"); indexSearchTabWidget->addTab(createIndexSearchWidget(), "Search"); /* * Need some space above the tab widget */ QWidget* indexSearchWidget = new QWidget(); QVBoxLayout* indexSearchLayout = new QVBoxLayout(indexSearchWidget); indexSearchLayout->addWidget(indexSearchTabWidget); /* * Create the splitter and add the widgets to the splitter */ m_splitter = new QSplitter; m_splitter->setOrientation(Qt::Horizontal); m_splitter->addWidget(indexSearchWidget); m_splitter->addWidget(createHelpViewerWidget()); QList sizeList; sizeList << 225 << 375; m_splitter->setSizes(sizeList); setCentralWidget(m_splitter, WuQDialog::SCROLL_AREA_NEVER); loadHelpTopicsIntoIndexTree(); loadSearchListWidget(); } /** * Destructor. */ HelpViewerDialog::~HelpViewerDialog() { } /** * Update the content of the dialog. */ void HelpViewerDialog::updateDialog() { } /** * @return Create and return the table of contents widget. */ QWidget* HelpViewerDialog::createTableOfContentsWidget() { /* * Create the tree widget for the help topics */ m_topicIndexTreeWidget = new QTreeWidget; m_topicIndexTreeWidget->setColumnCount(1); m_topicIndexTreeWidget->setColumnHidden(0, false); m_topicIndexTreeWidget->headerItem()->setHidden(true); QObject::connect(m_topicIndexTreeWidget, SIGNAL(currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)), this, SLOT(topicIndexTreeItemChanged(QTreeWidgetItem*, QTreeWidgetItem*))); /* * Collapse All button */ QAction* collapseAllAction = WuQtUtilities::createAction("Collapse All", "", this, this, SLOT(topicCollapseAllTriggered())); QToolButton* collapseAllToolButton = new QToolButton; collapseAllToolButton->setDefaultAction(collapseAllAction); /* * Expand All button */ QAction* expandAllAction = WuQtUtilities::createAction("Expand All", "", this, this, SLOT(topicExpandAllTriggered())); QToolButton* expandAllToolButton = new QToolButton; expandAllToolButton->setDefaultAction(expandAllAction); /* * Layout for collapse/expand all buttons */ QHBoxLayout* collapseExpandLayout = new QHBoxLayout; collapseExpandLayout->addStretch(); collapseExpandLayout->addWidget(collapseAllToolButton); collapseExpandLayout->addWidget(expandAllToolButton); collapseExpandLayout->addStretch(); /* * Layout for search line edit and topics */ QWidget* topicWidgets = new QWidget(); QVBoxLayout* topicLayout = new QVBoxLayout(topicWidgets); WuQtUtilities::setLayoutMargins(topicLayout, 0); topicLayout->addLayout(collapseExpandLayout); topicLayout->addWidget(m_topicIndexTreeWidget); return topicWidgets; } /** * @return Create and return the search widget. */ QWidget* HelpViewerDialog::createIndexSearchWidget() { /* * Search line edit and list widget */ const AString searchText = ("All searches are case insensitive.\n" "\n" "You may use wildcard characters:\n" " * - Matches any characters.\n" " ? - Matches a single character.\n"); const AString topicSearchToolTipText = ("Enter text to search content of ALL help pages.\n" + searchText); m_topicSearchLineEdit = new QLineEdit; m_topicSearchLineEdit->setToolTip(topicSearchToolTipText.convertToHtmlPage()); QObject::connect(m_topicSearchLineEdit, SIGNAL(returnPressed()), this, SLOT(topicSearchLineEditStartSearch())); QObject::connect(m_topicSearchLineEdit, SIGNAL(textEdited(const QString&)), this, SLOT(topicSearchLineEditStartSearch())); QObject::connect(m_topicSearchLineEdit, SIGNAL(cursorPositionChanged(int,int)), this, SLOT(topicSearchLineEditCursorPositionChanged(int,int))); /* * List widget containing matched topics */ m_topicSearchListWidget = new QListWidget(); m_topicSearchListWidget->setSortingEnabled(false); QObject::connect(m_topicSearchListWidget, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(topicSearchListWidgetItemClicked(QListWidgetItem*))); /* * Layout for search line edit and topics */ QWidget* searchWidgets = new QWidget(); QVBoxLayout* searchLayout = new QVBoxLayout(searchWidgets); WuQtUtilities::setLayoutSpacingAndMargins(searchLayout, 4, 2); searchLayout->addWidget(m_topicSearchLineEdit); searchLayout->addWidget(m_topicSearchListWidget); return searchWidgets; } /** * @return Create and return the help viewer widget. */ QWidget* HelpViewerDialog::createHelpViewerWidget() { /* DISPLAY HELP SECTION */ /* * create the back toolbar button */ QToolButton* backwardButton = new QToolButton; backwardButton->setArrowType(Qt::LeftArrow); backwardButton->setToolTip("Show the previous page"); /* * Create the forward toolbar button */ QToolButton* forwardButton = new QToolButton; forwardButton->setArrowType(Qt::RightArrow); forwardButton->setToolTip("Show the next page"); /* * Create the print toolbar button */ QToolButton* printButton = new QToolButton; connect(printButton, SIGNAL(clicked()), this, SLOT(helpPagePrintButtonClicked())); printButton->setText("Print"); printButton->hide(); /** * Copy button */ QToolButton* copyButton = new QToolButton; copyButton->setText("Copy"); copyButton->setToolTip("Copies selected help text to clipboard."); copyButton->setEnabled(false); /* * create the help browser */ m_helpBrowser = new HelpTextBrowser(this); m_helpBrowser->setMinimumWidth(400); m_helpBrowser->setMinimumHeight(200); m_helpBrowser->setOpenExternalLinks(false); m_helpBrowser->setOpenLinks(true); QObject::connect(forwardButton, SIGNAL(clicked()), m_helpBrowser, SLOT(forward())); QObject::connect(backwardButton, SIGNAL(clicked()), m_helpBrowser, SLOT(backward())); /* * Hook up copy button to help browser */ QObject::connect(m_helpBrowser, SIGNAL(copyAvailable(bool)), copyButton, SLOT(setEnabled(bool))); QObject::connect(copyButton, SIGNAL(clicked()), m_helpBrowser, SLOT(copy())); /* * Layout for toolbuttons */ QHBoxLayout* toolButtonLayout = new QHBoxLayout; toolButtonLayout->addWidget(new QLabel("Navigate:")); toolButtonLayout->addWidget(backwardButton); toolButtonLayout->addWidget(forwardButton); toolButtonLayout->addStretch(); toolButtonLayout->addWidget(copyButton); toolButtonLayout->addWidget(printButton); /* * Layout for help browser and buttons */ QWidget* helpBrowserWidgets = new QWidget; QVBoxLayout* helpBrowserLayout = new QVBoxLayout(helpBrowserWidgets); helpBrowserLayout->addLayout(toolButtonLayout); helpBrowserLayout->addWidget(m_helpBrowser); return helpBrowserWidgets; } /** * Show the help page with the given name. * * @param helpPageName * Name of help page. */ void HelpViewerDialog::showHelpPageWithName(const AString& helpPageName) { CaretAssertMessage(0, "Not implmented yet."); const AString pageName = QString(helpPageName).replace('_', ' '); if (pageName.isEmpty()) { return; } // for (int i = 0; i < m_topicTreeWidget->count(); i++) { // QListWidgetItem* lwi = m_workbenchIndexListWidget->item(i); // if (lwi->text() == pageName) { // m_workbenchIndexListWidget->setCurrentItem(lwi); // workbenchIndexListWidgetItemClicked(lwi); // return; // } // } CaretLogSevere("Could not find help page \"" + helpPageName + "\" for loading."); } ///** // * load the index tree with the help topics. // */ //void //HelpViewerDialog::loadHelpTopicsIntoIndexTree() //{ // m_topicIndexTreeWidget->blockSignals(true); // // // QTreeWidgetItem* workbenchItem = new QTreeWidgetItem(m_topicIndexTreeWidget, // TREE_ITEM_NONE); // workbenchItem->setText(0, "Workbench"); // // // QTreeWidgetItem* menuItem = new QTreeWidgetItem(workbenchItem, // TREE_ITEM_NONE); // menuItem->setText(0, "Menus"); // // QTreeWidgetItem* glossaryItem = new QTreeWidgetItem(workbenchItem, // TREE_ITEM_NONE); // glossaryItem->setText(0, "Glossary"); // // QTreeWidgetItem* otherItem = new QTreeWidgetItem(workbenchItem, // TREE_ITEM_NONE); // otherItem->setText(0, "Other"); // // // QTreeWidgetItem* menuWbViewItem = NULL; // QTreeWidgetItem* menuFileItem = NULL; // QTreeWidgetItem* menuViewItem = NULL; // QTreeWidgetItem* menuDataItem = NULL; // QTreeWidgetItem* menuSurfaceItem = NULL; // QTreeWidgetItem* menuConnectItem = NULL; // QTreeWidgetItem* menuDevelopItem = NULL; // QTreeWidgetItem* menuWindowItem = NULL; // QTreeWidgetItem* menuHelpItem = NULL; // std::vector unknownMenuItems; // // // QDir resourceHelpDirectory(":/HelpFiles"); // // // CAN BE SET TO FIND FILES WITHOUT FULL PATH // //m_helpBrowser->setSearchPaths(QStringList(":/HelpFiles/Menus/File_Menu")); // // if (resourceHelpDirectory.exists()) { // QStringList htmlFileFilter; // htmlFileFilter << "*"; // //htmlFileFilter << "*.htm" << "*.html"; // QDirIterator dirIter(":/HelpFiles", // htmlFileFilter, // QDir::NoFilter, // QDirIterator::Subdirectories); // // while (dirIter.hasNext()) { // dirIter.next(); // const QFileInfo fileInfo = dirIter.fileInfo(); // const QString name = fileInfo.baseName(); // const QString filePath = fileInfo.filePath(); // // if (filePath.endsWith(".htm") // || filePath.endsWith(".html")) { // if (name.contains("Menu", // Qt::CaseInsensitive)) { // QTreeWidgetItem* item = createHelpTreeWidgetItemForHelpPage(NULL, // name, // filePath); // if (name.contains("wb_view")) { // menuWbViewItem = item; // } // else if (name.startsWith("File", // Qt::CaseInsensitive)) { // menuFileItem = item; // } // else if (name.startsWith("View", // Qt::CaseInsensitive)) { // menuViewItem = item; // } // else if (name.startsWith("Data", // Qt::CaseInsensitive)) { // menuDataItem = item; // } // else if (name.startsWith("Surface", // Qt::CaseInsensitive)) { // menuSurfaceItem = item; // } // else if (name.startsWith("Connect", // Qt::CaseInsensitive)) { // menuConnectItem = item; // } // else if (name.startsWith("Develop", // Qt::CaseInsensitive)) { // menuDevelopItem = item; // } // else if (name.startsWith("Window", // Qt::CaseInsensitive)) { // menuWindowItem = item; // } // else if (name.startsWith("Help", // Qt::CaseInsensitive)) { // menuHelpItem = item; // } // else { // CaretLogSevere("Unrecognized menu name, has a new menu been added? \"" // + name); // unknownMenuItems.push_back(item); // } // } // else if (filePath.contains("Glossary")) { // createHelpTreeWidgetItemForHelpPage(glossaryItem, // name, // filePath); // } // else { // createHelpTreeWidgetItemForHelpPage(otherItem, // name, // filePath); // } // } // } // // glossaryItem->sortChildren(0, // Qt::AscendingOrder); // otherItem->sortChildren(0, // Qt::AscendingOrder); // // } // else { // CaretLogSevere("Resource directory " // + resourceHelpDirectory.absolutePath() // + " not found."); // } // // addItemToParentMenu(menuItem, // menuWbViewItem, // ""); // addItemToParentMenu(menuItem, // menuFileItem, // ""); // addItemToParentMenu(menuItem, // menuViewItem, // ""); // addItemToParentMenu(menuItem, // menuDataItem, // ""); // addItemToParentMenu(menuItem, // menuSurfaceItem, // ""); // addItemToParentMenu(menuItem, // menuConnectItem, // ""); // addItemToParentMenu(menuItem, // menuDevelopItem, // ""); // addItemToParentMenu(menuItem, // menuWindowItem, // ""); // addItemToParentMenu(menuItem, // menuHelpItem, // ""); // for (std::vector::iterator unknownIter = unknownMenuItems.begin(); // unknownIter != unknownMenuItems.end(); // unknownIter++) { // addItemToParentMenu(menuItem, // *unknownIter, // ""); // } // // m_topicIndexTreeWidget->setItemExpanded(menuItem, // true); // m_topicIndexTreeWidget->setItemExpanded(workbenchItem, // true); // // /* // * Load commands // */ // CommandOperationManager* commandOperationManager = CommandOperationManager::getCommandOperationManager(); // std::vector commandOperations = commandOperationManager->getCommandOperations(); // // if ( ! commandOperations.empty()) { // /* // * Use map to sort commands by short description // */ // std::map sortCommandsMap; // for (std::vector::iterator vecIter = commandOperations.begin(); // vecIter != commandOperations.end(); // vecIter++) { // CommandOperation* op = *vecIter; // sortCommandsMap.insert(std::make_pair(op->getCommandLineSwitch(), // op)); // } // // QTreeWidgetItem* wbCommandItem = new QTreeWidgetItem(m_topicIndexTreeWidget, // TREE_ITEM_NONE); // wbCommandItem->setText(0, "wb_command"); // // QFont commandFont = wbCommandItem->font(0); // commandFont.setPointSize(10); // // for (std::map::iterator mapIter = sortCommandsMap.begin(); // mapIter != sortCommandsMap.end(); // mapIter++) { // CommandOperation* op = mapIter->second; // // HelpTreeWidgetItem* item = HelpTreeWidgetItem::newInstanceForCommandOperation(wbCommandItem, // op); // item->setFont(0, commandFont); // m_allHelpWidgetItems.push_back(item); // } // // m_topicIndexTreeWidget->setItemExpanded(wbCommandItem, // true); // } // // m_topicIndexTreeWidget->blockSignals(false); //} /** * Load Workbench help from the given directory and add it to the * the given parent. Also process any subdirectories * * @param parent * Parent tree widget item * @param dirInfo * The directory examined for HTML pages and subdirectories * @return * Tree widget item that was created. */ HelpTreeWidgetItem* HelpViewerDialog::loadWorkbenchHelpInfoFromDirectory(QTreeWidgetItem* parent, const QFileInfo& dirInfo) { QDir directory(dirInfo.absoluteFilePath()); /* * Get all HTML pages in the directory * and file an HTML page that is the same * name as the directory. */ QStringList htmlNameFilter; htmlNameFilter << "*.htm" << "*.html"; QFileInfoList htmlFileList = directory.entryInfoList(htmlNameFilter, QDir::Files, QDir::Name); QString dirHtmlPageName; QFileInfoList otherHtmlPagesList; QListIterator htmlFileIter(htmlFileList); while (htmlFileIter.hasNext()) { const QFileInfo htmlFileInfo = htmlFileIter.next(); if (htmlFileInfo.baseName() == dirInfo.baseName()) { dirHtmlPageName = htmlFileInfo.absoluteFilePath(); } else { otherHtmlPagesList.append(htmlFileInfo.absoluteFilePath()); } } /* * Create a tree widget item for this directory * that may have a help page. */ HelpTreeWidgetItem* treeItem = NULL; if ( ! dirHtmlPageName.isEmpty()) { treeItem = createHelpTreeWidgetItemForHelpPage(parent, dirInfo.baseName(), dirHtmlPageName); } else { AString text = dirInfo.baseName(); text = text.replace('_', ' '); treeItem = HelpTreeWidgetItem::newInstanceEmptyItem(parent, text); // new HelpTreeWidgetItem(parent); // treeItem->setText(0, text); } /* * Add items for any other HTML pages found in the directory */ QListIterator otherHtmlPageIter(otherHtmlPagesList); while (otherHtmlPageIter.hasNext()) { const QFileInfo pageInfo = otherHtmlPageIter.next(); createHelpTreeWidgetItemForHelpPage(treeItem, pageInfo.baseName(), pageInfo.absoluteFilePath()); } /* * Add any subdirectories as children */ QFileInfoList subDirList = directory.entryInfoList((QDir::AllDirs | QDir::NoDotAndDotDot), QDir::Name); QListIterator subDirIter(subDirList); while (subDirIter.hasNext()) { const QFileInfo subDirInfo = subDirIter.next(); loadWorkbenchHelpInfoFromDirectory(treeItem, subDirInfo); } return treeItem; } /** * load the index tree with the help topics. */ void HelpViewerDialog::loadHelpTopicsIntoIndexTree() { m_topicIndexTreeWidget->blockSignals(true); QTreeWidgetItem* workbenchItem = new QTreeWidgetItem(m_topicIndexTreeWidget, TREE_ITEM_NONE); workbenchItem->setText(0, "wb_view"); QDir resourceHelpDirectory(":/HelpFiles"); QTreeWidgetItem* glossaryItem = NULL; // CAN BE SET TO FIND FILES WITHOUT FULL PATH //m_helpBrowser->setSearchPaths(QStringList(":/HelpFiles/Menus/File_Menu")); QFileInfoList subDirList = resourceHelpDirectory.entryInfoList((QDir::AllDirs | QDir::NoDotAndDotDot), QDir::Name); QListIterator subDirIter(subDirList); while (subDirIter.hasNext()) { const QFileInfo subDirInfo = subDirIter.next(); HelpTreeWidgetItem* item = loadWorkbenchHelpInfoFromDirectory(workbenchItem, subDirInfo); /* * Is this the GLOSSARY? * If so, move it so that it is a top level item. */ if (subDirInfo.baseName().toLower() == "glossary") { if (glossaryItem != NULL) { CaretAssertMessage(0, "There should be only one glossary subdirectory !!!!"); } glossaryItem = item; workbenchItem->removeChild(glossaryItem); m_topicIndexTreeWidget->addTopLevelItem(glossaryItem); } } /* * Load commands */ CommandOperationManager* commandOperationManager = CommandOperationManager::getCommandOperationManager(); std::vector commandOperations = commandOperationManager->getCommandOperations(); QTreeWidgetItem* wbCommandItem = NULL; if ( ! commandOperations.empty()) { /* * Use map to sort commands by short description */ std::map sortCommandsMap; for (std::vector::iterator vecIter = commandOperations.begin(); vecIter != commandOperations.end(); vecIter++) { CommandOperation* op = *vecIter; sortCommandsMap.insert(std::make_pair(op->getCommandLineSwitch(), op)); } wbCommandItem = new QTreeWidgetItem(m_topicIndexTreeWidget, TREE_ITEM_NONE); wbCommandItem->setText(0, "wb_command"); QFont commandFont = wbCommandItem->font(0); commandFont.setPointSize(10); for (std::map::iterator mapIter = sortCommandsMap.begin(); mapIter != sortCommandsMap.end(); mapIter++) { CommandOperation* op = mapIter->second; HelpTreeWidgetItem* item = HelpTreeWidgetItem::newInstanceForCommandOperation(wbCommandItem, op); item->setFont(0, commandFont); addToAllItems(item); // m_allHelpWidgetItems.push_back(item); // // HelpSearchListItem* searchItem = new HelpSearchListItem(item); // m_topicSearchListWidget->addItem(searchItem); } } /* * Using setExpanded on a QTreeWidgetItem only expands its immediate children. * So, expand everything and then collapse Glossary and wb_command items so * that only wb_view items are expanded. */ m_topicIndexTreeWidget->expandAll(); if (glossaryItem != NULL) { glossaryItem->setExpanded(false); } if (wbCommandItem != NULL) { wbCommandItem->setExpanded(false); } m_topicIndexTreeWidget->sortItems(0, Qt::AscendingOrder); m_topicIndexTreeWidget->blockSignals(false); } /** * Add an item to the menu's item. * If the given item is NULL, it was not found an an error message will * be logged. * * @param parentMenu * The parent menu item. * @param item * The item that is added to the parent menu. * @param itemName * Name for item. */ void HelpViewerDialog::addItemToParentMenu(QTreeWidgetItem* parentMenu, QTreeWidgetItem* item, const AString& itemName) { CaretAssert(parentMenu); if (item != NULL) { if ( ! itemName.isEmpty()) { item->setText(0, itemName); } parentMenu->addChild(item); } else { CaretLogSevere("Did not find help for menu: " + itemName); } } /** * Create a help tree widget item for a help page URL. * * @param parent * Parent for item in index. * @param itemText * Text for the item shown in the topic index. * @param helpPageURL * URL for the help page. */ HelpTreeWidgetItem* HelpViewerDialog::createHelpTreeWidgetItemForHelpPage(QTreeWidgetItem* parent, const AString& itemText, const AString& helpPageURL) { HelpTreeWidgetItem* helpItem = HelpTreeWidgetItem::newInstanceForHtmlHelpPage(parent, itemText, helpPageURL); addToAllItems(helpItem); return helpItem; } /** * Called when selected index tree item changes. * * @param currentItem * The selected item * @param previousItem * The previously selected item */ void HelpViewerDialog::topicIndexTreeItemChanged(QTreeWidgetItem* currentItem, QTreeWidgetItem* /*previousItem*/) { if (currentItem != NULL) { /* * Note not all items are castable to HelpTreeWidgetItem. * Items not castable are category items that have an arrow to * expand/collapse its children. */ HelpTreeWidgetItem* helpItem = dynamic_cast(currentItem); if (helpItem != NULL) { displayHelpTextForHelpTreeWidgetItem(helpItem); m_topicIndexTreeWidget->scrollToItem(helpItem, QTreeWidget::EnsureVisible); } else { const AString html = AString(currentItem->text(0)).convertToHtmlPage(); m_helpBrowser->setHtml(html); } } } /** * Add the help item to all items and to the search list widget. * * @helpItem * Help item to add. */ void HelpViewerDialog::addToAllItems(HelpTreeWidgetItem* helpItem) { m_allHelpTreeWidgetItems.push_back(helpItem); } /** * Load the search list widget. List all Workbench items in alphabetical * order followed by all commands in alphabetical order. */ void HelpViewerDialog::loadSearchListWidget() { std::vector commandItems; std::vector workbenchItems; for (std::vector::iterator allIter = m_allHelpTreeWidgetItems.begin(); allIter != m_allHelpTreeWidgetItems.end(); allIter++) { HelpTreeWidgetItem* treeItem = *allIter; switch (treeItem->m_treeItemType) { case HelpTreeWidgetItem::TREE_ITEM_NONE: /* * Do not allow in search */ continue; break; case HelpTreeWidgetItem::TREE_ITEM_HELP_PAGE_URL: break; case HelpTreeWidgetItem::TREE_ITEM_HELP_TEXT: break; } HelpSearchListItem* searchItem = new HelpSearchListItem(treeItem); m_allHelpSearchListWidgetItems.push_back(searchItem); if (treeItem->text(0).startsWith("-")) { commandItems.push_back(searchItem); } else { workbenchItems.push_back(searchItem); } } std::sort(commandItems.begin(), commandItems.end(), HelpSearchListItem::sortAlphabetically); std::sort(workbenchItems.begin(), workbenchItems.end(), HelpSearchListItem::sortAlphabetically); for (std::vector::iterator wbIter = workbenchItems.begin(); wbIter != workbenchItems.end(); wbIter++) { m_topicSearchListWidget->addItem(*wbIter); } for (std::vector::iterator cmdIter = commandItems.begin(); cmdIter != commandItems.end(); cmdIter++) { m_topicSearchListWidget->addItem(*cmdIter); } } /** * Called when an item in the search list widget is clicked. * * @param item * The selected item */ void HelpViewerDialog::topicSearchListWidgetItemClicked(QListWidgetItem* item) { HelpSearchListItem* searchItem = dynamic_cast(item); if (searchItem != NULL) { HelpTreeWidgetItem* helpItem = searchItem->m_matchingTreeWidgetItem; if (helpItem != NULL) { displayHelpTextForHelpTreeWidgetItem(helpItem); } } } /** * Display the help information for the given help item. * * @param helpItem * Item for which help text is loaded. */ void HelpViewerDialog::displayHelpTextForHelpTreeWidgetItem(HelpTreeWidgetItem* helpItem) { CaretAssert(helpItem); m_helpBrowser->setSource(helpItem->m_helpPageURL); } /** * Called when search text is changed or return pressed to start * searching the help topics */ void HelpViewerDialog::topicSearchLineEditStartSearch() { const QString searchText = m_topicSearchLineEdit->text().trimmed(); const bool haveSearchTextFlag = ( ! searchText.isEmpty()); QRegExp regEx; bool haveWildcardSearchFlag = false; if (haveSearchTextFlag) { if (searchText.contains('*') || searchText.contains('?')) { haveWildcardSearchFlag = true; regEx.setPatternSyntax(QRegExp::Wildcard); regEx.setPattern(searchText); regEx.setCaseSensitivity(Qt::CaseInsensitive); } } for (std::vector::iterator iter = m_allHelpSearchListWidgetItems.begin(); iter != m_allHelpSearchListWidgetItems.end(); iter++) { HelpSearchListItem* helpListItem = *iter; CaretAssert(helpListItem); HelpTreeWidgetItem* helpTreeItem = helpListItem->m_matchingTreeWidgetItem; CaretAssert(helpTreeItem); bool showItemFlag = true; if (haveSearchTextFlag) { showItemFlag = false; if (haveWildcardSearchFlag) { if (regEx.exactMatch(helpTreeItem->m_helpText)) { showItemFlag = true; } } else if (helpTreeItem->m_helpText.contains(searchText, Qt::CaseInsensitive)) { showItemFlag = true; } } helpListItem->setHidden( ! showItemFlag); } // for (std::vector::iterator iter = m_allHelpTreeWidgetItems.begin(); // iter != m_allHelpTreeWidgetItems.end(); // iter++) { // HelpTreeWidgetItem* helpItem = *iter; // // bool showItemFlag = true; // if (haveSearchTextFlag) { // showItemFlag = false; // // if (haveWildcardSearchFlag) { // if (regEx.exactMatch(helpItem->m_helpText)) { // showItemFlag = true; // } // } // else if (helpItem->m_helpText.contains(searchText, // Qt::CaseInsensitive)) { // showItemFlag = true; // } // } // helpItem->setHidden( ! showItemFlag); // } } /** * called to print currently displayed page. */ void HelpViewerDialog::helpPagePrintButtonClicked() { QPrinter printer; QPrintDialog* printDialog = new QPrintDialog(&printer, this); if (printDialog->exec() == QPrintDialog::Accepted) { m_helpBrowser->document()->print(&printer); } } /** * Called when the cursor position is changed */ void HelpViewerDialog::topicSearchLineEditCursorPositionChanged(int,int) { if (m_topicSearchLineEditFirstMouseClick) { m_topicSearchLineEditFirstMouseClick = false; m_topicSearchLineEdit->clear(); topicSearchLineEditStartSearch(); } } /** * Expand all help topics */ void HelpViewerDialog::topicExpandAllTriggered() { m_topicIndexTreeWidget->expandAll(); } /** * Collapse all help topics */ void HelpViewerDialog::topicCollapseAllTriggered() { m_topicIndexTreeWidget->collapseAll(); } // ========================================================================= // /** * Create a help viewer widget. * * @param parentHelpViewerDialog * The parent help viewer dialog. */ HelpTextBrowser::HelpTextBrowser(HelpViewerDialog* parentHelpViewerDialog) : QTextBrowser(parentHelpViewerDialog), m_parentHelpViewerDialog(parentHelpViewerDialog) { CaretAssert(parentHelpViewerDialog); } /** * Destructor. */ HelpTextBrowser::~HelpTextBrowser() { } /** * Overrides superclass version so that images get loaded properly. * Setting search paths may eliminate need for this method. * * @param type * Type of resource. * @param url * URL of resource. * @return * QVariant containing content for display in the help viewer. */ QVariant HelpTextBrowser::loadResource(int type, const QUrl& url) { const QString urlText = url.toString(); // std::cout << " Current Source: " << qPrintable(source().toString()) << std::endl; // std::cout << "Loading resource: " << qPrintable(url.toString()) << std::endl; QVariant result; for (std::vector::iterator iter = m_parentHelpViewerDialog->m_allHelpTreeWidgetItems.begin(); iter != m_parentHelpViewerDialog->m_allHelpTreeWidgetItems.end(); iter++) { HelpTreeWidgetItem* treeItem = *iter; if (treeItem->m_helpPageURL == urlText) { // std::cout << "Found tree item " << qPrintable(treeItem->m_helpPageURL) << std::endl; result = treeItem->m_helpText; break; } } if ( ! result.isValid()) { result = QTextBrowser::loadResource(type, url); if (result.isValid()) { // std::cout << " WAS LOADED BY QTextBrowser::loadResource()" << std::endl; } else { QString typeName("Unknown"); if ( ! result.isValid()) { switch (type) { case QTextDocument::HtmlResource: typeName = "Html Resource"; break; case QTextDocument::ImageResource: typeName = "Image Resource"; break; case QTextDocument::StyleSheetResource: typeName = "Style Sheet Resource"; break; } } CaretLogSevere("Failed to load type: " + typeName + " file: " + url.toString()); } } return result; } /** * Set the source of the help browser. * * @param url * URL directing to content. */ void HelpTextBrowser::setSource(const QUrl& url) { const AString urlText = url.toString(); if (urlText.startsWith("http:")) { if (WuQMessageBox::warningOkCancel(this, "The link clicked will be displayed in your web browser.")) { if ( ! QDesktopServices::openUrl(urlText)) { WuQMessageBox::errorOk(this, ("Failed to load " + urlText)); } } } else if (urlText.startsWith("mailto")) { if (WuQMessageBox::warningOkCancel(this, "This link will open your mail client.")) { if ( ! QDesktopServices::openUrl(urlText)) { WuQMessageBox::errorOk(this, ("Failed to load " + urlText)); } } } else { QTextBrowser::setSource(url); } } // ========================================================================= // /** * Create a new help tree widget item for a wb_command item. * * @param parent * Parent for item in index. * @param commandOperation * The command. */ HelpTreeWidgetItem* HelpTreeWidgetItem::newInstanceForCommandOperation(QTreeWidgetItem* parent, CommandOperation* commandOperation) { const AString itemText = commandOperation->getCommandLineSwitch(); const AString helpInfoCopy = commandOperation->getHelpInformation("wb_command"); const AString helpText = helpInfoCopy.convertToHtmlPageWithFontSize(2); const AString helpPageURL("command:/" + commandOperation->getOperationShortDescription().replace(' ', '_')); HelpTreeWidgetItem* instance = new HelpTreeWidgetItem(parent, TREE_ITEM_HELP_TEXT, itemText, helpPageURL, helpText); return instance; } /** * Create a new help tree widget item for a help page URL. * * @param parent * Parent for item in index. * @param itemText * Text for the item shown in the topic index. * @param helpPageURL * URL for the help page. */ HelpTreeWidgetItem* HelpTreeWidgetItem::newInstanceForHtmlHelpPage(QTreeWidgetItem* parent, const AString& itemText, const AString& helpPageURL) { CaretAssertMessage( ( ! itemText.startsWith(":/")), "All help pages must be resources (page name starts with \":/\")"); QString helpText; QFile file(helpPageURL); if (file.exists()) { if (file.open(QFile::ReadOnly)) { QTextStream stream(&file); helpText = stream.readAll(); file.close(); } else { AString msg = ("Help file exists but unable to open for reading: " + helpPageURL); CaretLogSevere(msg); helpText = msg.convertToHtmlPage(); } } else { AString msg = ("HTML Help file missing: " + helpPageURL); CaretLogSevere(msg); helpText = msg.convertToHtmlPage(); } HelpTreeWidgetItem* instance = NULL; AString text = itemText; text = text.replace('_', ' '); if (parent != NULL) { instance = new HelpTreeWidgetItem(parent, TREE_ITEM_HELP_TEXT, text, "qrc" + helpPageURL, helpText); } else { instance = new HelpTreeWidgetItem(TREE_ITEM_HELP_TEXT, text, "qrc" + helpPageURL, helpText); } return instance; } /** * Constructor for item with parent but only a name. * * @param parent * Parent for item in index. * @param itemText * Text for the item shown in the topic index. */ HelpTreeWidgetItem* HelpTreeWidgetItem::newInstanceEmptyItem(QTreeWidgetItem* parent, const AString& itemText) { HelpTreeWidgetItem* item = new HelpTreeWidgetItem(parent, TREE_ITEM_NONE, itemText, "", ""); return item; } /** * Constructor for item with parent * * @param parent * Parent for item in index. * @param treeItemType * Type of tree item. * @param itemText * Text for the item shown in the topic index. * @param helpPageURL * URL for external help pages * @param helpText * Text displayed in help browser. */ HelpTreeWidgetItem::HelpTreeWidgetItem(QTreeWidgetItem* parent, const TreeItemType treeItemType, const AString& itemText, const AString& helpPageURL, const AString& helpText) : QTreeWidgetItem(parent), m_treeItemType(treeItemType), m_helpPageURL(helpPageURL), m_helpText(helpText) { setText(0, itemText); } /** * Constructor for item WITHOUT parent * * @param treeItemType * Type of tree item. * @param itemText * Text for the item shown in the topic index. * @param helpPageURL * URL for external help pages * @param helpText * Text displayed in help browser. */ HelpTreeWidgetItem::HelpTreeWidgetItem(const TreeItemType treeItemType, const AString& itemText, const AString& helpPageURL, const AString& helpText) : QTreeWidgetItem(), m_treeItemType(treeItemType), m_helpPageURL(helpPageURL), m_helpText(helpText) { setText(0, itemText); } /** * Destructor. */ HelpTreeWidgetItem::~HelpTreeWidgetItem() { } // ========================================================================= // /** * Constructs a list widget item for use during search operation * * @param matchingTreeWidgetItem * The matching tree widget item that contains the help information. */ HelpSearchListItem::HelpSearchListItem(HelpTreeWidgetItem* matchingTreeWidgetItem) { CaretAssert(matchingTreeWidgetItem); m_matchingTreeWidgetItem = matchingTreeWidgetItem; setText(m_matchingTreeWidgetItem->text(0)); } /** * Destructor. */ HelpSearchListItem::~HelpSearchListItem() { } /** * Sort the two "pointed to" items alphabetically. * * @param h1 * First item. * @param h2 * Second item. * @return * True if h1 is alphabetically before h2. */ bool HelpSearchListItem::sortAlphabetically(const HelpSearchListItem* h1, const HelpSearchListItem* h2) { CaretAssert(h1); CaretAssert(h2); return (h1->m_matchingTreeWidgetItem->text(0) < h2->m_matchingTreeWidgetItem->text(0)); } workbench-1.1.1/src/GuiQt/HelpViewerDialog.h000066400000000000000000000156311255417355300206770ustar00rootroot00000000000000#ifndef __HELP_VIEWER_DIALOG_H__ #define __HELP_VIEWER_DIALOG_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include "WuQDialogNonModal.h" class QFileInfo; class QLineEdit; class QListWidget; class QSplitter; class QToolButton; class QTreeWidget; class QUrl; namespace caret { class CommandOperation; class HelpSearchListItem; class HelpTreeWidgetItem; class HelpViewerDialog : public WuQDialogNonModal { Q_OBJECT public: HelpViewerDialog(QWidget* parent, Qt::WindowFlags f = 0); virtual ~HelpViewerDialog(); virtual void updateDialog(); void showHelpPageWithName(const AString& helpPageName); // ADD_NEW_METHODS_HERE private slots: void topicIndexTreeItemChanged(QTreeWidgetItem* currentItem, QTreeWidgetItem* previousItem); void helpPagePrintButtonClicked(); void topicSearchLineEditStartSearch(); void topicSearchLineEditCursorPositionChanged(int,int); void topicExpandAllTriggered(); void topicCollapseAllTriggered(); void topicSearchListWidgetItemClicked(QListWidgetItem*); private: enum TreeItemType { TREE_ITEM_NONE, TREE_ITEM_HELP_PAGE, TREE_ITEM_WB_COMMAND }; HelpViewerDialog(const HelpViewerDialog&); HelpViewerDialog& operator=(const HelpViewerDialog&); QWidget* createTableOfContentsWidget(); QWidget* createIndexSearchWidget(); QWidget* createHelpViewerWidget(); void loadHelpTopicsIntoIndexTree(); HelpTreeWidgetItem* createHelpTreeWidgetItemForHelpPage(QTreeWidgetItem* parent, const AString& itemText, const AString& helpPageURL); void displayHelpTextForHelpTreeWidgetItem(HelpTreeWidgetItem* helpItem); void addItemToParentMenu(QTreeWidgetItem* parentMenu, QTreeWidgetItem* item, const AString& itemName); HelpTreeWidgetItem* loadWorkbenchHelpInfoFromDirectory(QTreeWidgetItem* parent, const QFileInfo& dirInfo); void addToAllItems(HelpTreeWidgetItem* helpItem); void loadSearchListWidget(); /// the help browser QTextBrowser* m_helpBrowser; /// the splitter QSplitter* m_splitter; /// the topic index tree widget QTreeWidget* m_topicIndexTreeWidget; /// line edit for searching topics QLineEdit* m_topicSearchLineEdit; QListWidget* m_topicSearchListWidget; /// tracks first mouse click in search topic line edit bool m_topicSearchLineEditFirstMouseClick; /// All help pages in tree widget std::vector m_allHelpTreeWidgetItems; /// All items in search list widget std::vector m_allHelpSearchListWidgetItems; // ADD_NEW_MEMBERS_HERE friend class HelpTextBrowser; }; /** * The help text browser. */ class HelpTextBrowser : public QTextBrowser { Q_OBJECT public: HelpTextBrowser(HelpViewerDialog* parentHelpViewerDialog); virtual ~HelpTextBrowser(); virtual QVariant loadResource(int type, const QUrl& url); virtual void setSource(const QUrl& url); private: HelpViewerDialog* m_parentHelpViewerDialog; }; /** * Base class for items in the tree widget. */ class HelpTreeWidgetItem : public QTreeWidgetItem { public: enum TreeItemType { TREE_ITEM_NONE, TREE_ITEM_HELP_PAGE_URL, TREE_ITEM_HELP_TEXT }; static HelpTreeWidgetItem* newInstanceForCommandOperation(QTreeWidgetItem* parent, CommandOperation* commandOperation); static HelpTreeWidgetItem* newInstanceForHtmlHelpPage(QTreeWidgetItem* parent, const AString& itemText, const AString& helpPageURL); static HelpTreeWidgetItem* newInstanceEmptyItem(QTreeWidgetItem* parent, const AString& itemText); private: HelpTreeWidgetItem(QTreeWidgetItem* parent, const TreeItemType treeItemType, const AString& itemText, const AString& helpPageURL, const AString& helpText); HelpTreeWidgetItem(const TreeItemType treeItemType, const AString& itemText, const AString& helpPageURL, const AString& helpText); public: virtual ~HelpTreeWidgetItem(); const TreeItemType m_treeItemType; const AString m_helpPageURL; const AString m_helpText; }; class HelpSearchListItem : public QListWidgetItem { public: HelpSearchListItem(HelpTreeWidgetItem* matchingTreeWidgetItem); ~HelpSearchListItem(); static bool sortAlphabetically(const HelpSearchListItem* h1, const HelpSearchListItem* h2); HelpTreeWidgetItem* m_matchingTreeWidgetItem; }; #ifdef __HELP_VIEWER_DIALOG_DECLARE__ // #endif // __HELP_VIEWER_DIALOG_DECLARE__ } // namespace #endif //__HELP_VIEWER_DIALOG_H__ workbench-1.1.1/src/GuiQt/HyperLinkTextBrowser.cxx000066400000000000000000000107601255417355300221740ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" #include "HyperLinkTextBrowser.h" using namespace caret; /** * \class caret::HyperLinkTextBrowser * \brief Displays HTML text. * \ingroup GuiQt * * Displays HTML text. Input text may contain URLs * which are converted into hyperlinks. */ /** * Constructor */ HyperLinkTextBrowser::HyperLinkTextBrowser(QWidget* parent) : QTextBrowser(parent) { //QT4setTextFormat(QTextBrowser::RichText); } /** * Destructor */ HyperLinkTextBrowser::~HyperLinkTextBrowser() { } /** * Override of QT's QTextBrowser method. * Called when the user clicks a link in this browser. * @param url * User for display. */ void HyperLinkTextBrowser::setSource(const QUrl& url) { const AString s(url.toString()); if (s.startsWith("vocabulary://")) { this->appendHtml("Vocabulary links not supported yet."); // const AString name = s.mid(13); // BrainModelIdentification* bmi = theMainWindow->getBrainSet()->getBrainModelIdentification(); // const AString idString(bmi->getIdentificationTextForVocabulary(true, name)); // if (idString.isEmpty() == false) { // appendHtml(idString); // } } else { this->appendHtml("Displaying of web pages not supported yet."); // theMainWindow->displayWebPage(s); } } /** * Append text (Override of QT's QTextBrowser method). * Any URLs in text are converted to hyperlinks. */ void HyperLinkTextBrowser::append(const AString& textIn) { // // See if string contains a URL // AString text; if (textIn.indexOf("http://") >= 0) { // // Insert the string with hyperlinks into text browser // text = textIn.convertURLsToHyperlinks(); } else { text = textIn; } text.replace("\n", "
"); AString displayText = toHtml(); displayText.append("
"); displayText.append(text); setHtml(displayText); // // Scroll to newest text (at end of scroll bar) // QScrollBar* vsb = verticalScrollBar(); vsb->setValue(vsb->maximum()); } /** * Append html. * @param html * HTML that is appended. */ void HyperLinkTextBrowser::appendHtml(const AString& html) { AString displayText = toHtml(); displayText.append("
"); displayText.append(html); setHtml(displayText); // // Scroll to newest text (at end of scroll bar) // QScrollBar* vsb = verticalScrollBar(); vsb->setValue(vsb->maximum()); } /** * Set the content of the browser to the given html and then * scroll to the bottom. * * @param html * Html for browser. */ void HyperLinkTextBrowser::setContentToHtml(const AString& html) { this->setHtml(html); // // Scroll to newest text (at end of scroll bar) // QScrollBar* vsb = verticalScrollBar(); vsb->setValue(vsb->maximum()); } /** * Set the content of the browser to the given text and then * scroll to the bottom. * * @param textIn * Text for browser. */ void HyperLinkTextBrowser::setContentToText(const AString& textIn) { // // See if string contains a URL // AString displayText; if (textIn.indexOf("http://") != -1) { // // Insert the string with hyperlinks into text browser // displayText = textIn.convertURLsToHyperlinks(); } else { displayText = textIn; } displayText.replace("\n", "
"); setHtml(displayText); // // Scroll to newest text (at end of scroll bar) // QScrollBar* vsb = verticalScrollBar(); vsb->setValue(vsb->maximum()); } /** * called if a key is pressed over the text browser. * @param key * Key that is pressed. */ void HyperLinkTextBrowser::keyPressEvent(QKeyEvent* /*e*/) { emit keyPressed(); } workbench-1.1.1/src/GuiQt/HyperLinkTextBrowser.h000066400000000000000000000032201255417355300216120ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #ifndef __HYPER_LINK_TEXT_BROWSER_H__ #define __HYPER_LINK_TEXT_BROWSER_H__ #include class QKeyEvent; namespace caret { class AString; class HyperLinkTextBrowser : public QTextBrowser { Q_OBJECT public: HyperLinkTextBrowser(QWidget* parent = 0); ~HyperLinkTextBrowser(); void setContentToHtml(const AString& html); void setContentToText(const AString& text); signals: void keyPressed(); public slots: void append(const AString& text); void appendHtml(const AString& html); private slots: void setSource(const QUrl& url); private: void keyPressEvent(QKeyEvent* e); }; } #endif // __HYPER_LINK_TEXT_BROWSER_H__ workbench-1.1.1/src/GuiQt/IdentifyBrainordinateDialog.cxx000066400000000000000000001066651255417355300234650ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __IDENTIFY_BRAINORDINATE_DIALOG_DECLARE__ #include "IdentifyBrainordinateDialog.h" #undef __IDENTIFY_BRAINORDINATE_DIALOG_DECLARE__ using namespace caret; #include #include #include #include #include #include #include "Brain.h" #include "BrainordinateRegionOfInterest.h" #include "BrainStructure.h" #include "CaretAssert.h" #include "CaretDataFileSelectionComboBox.h" #include "CaretDataFileSelectionModel.h" #include "CaretLogger.h" #include "CiftiFiberTrajectoryFile.h" #include "CaretMappableDataFile.h" #include "CiftiConnectivityMatrixDataFileManager.h" #include "CiftiFiberTrajectoryManager.h" #include "CiftiMappableConnectivityMatrixDataFile.h" #include "CaretMappableDataFileAndMapSelectionModel.h" #include "CaretMappableDataFileAndMapSelectorObject.h" #include "CiftiParcelSelectionComboBox.h" #include "EventGraphicsUpdateAllWindows.h" #include "EventIdentificationHighlightLocation.h" #include "EventManager.h" #include "EventSurfaceColoringInvalidate.h" #include "EventUpdateInformationWindows.h" #include "EventUserInterfaceUpdate.h" #include "GiftiLabelTableSelectionComboBox.h" #include "GuiManager.h" #include "IdentifiedItemNode.h" #include "IdentificationManager.h" #include "SelectionItemCiftiConnectivityMatrixRowColumn.h" #include "SelectionItemSurfaceNode.h" #include "SelectionItemVoxel.h" #include "SelectionManager.h" #include "SessionManager.h" #include "StructureEnumComboBox.h" #include "Surface.h" #include "SystemUtilities.h" #include "WuQFactory.h" #include "WuQGroupBoxExclusiveWidget.h" #include "WuQMessageBox.h" #include "WuQtUtilities.h" #include static const int INDEX_SPIN_BOX_WIDTH = 100; /** * Constructor. */ IdentifyBrainordinateDialog::IdentifyBrainordinateDialog(QWidget* parent) : WuQDialogNonModal("Identify Brainordinate", parent) { /* * Surface Vertex widgets */ m_surfaceVertexWidget = createSurfaceVertexlWidget(); /* * Filter file types for CIFTI type files */ std::vector allDataFileTypes; DataFileTypeEnum::getAllEnums(allDataFileTypes); std::vector supportedCiftiRowFileTypes; std::vector supportedLabelFileTypes; for (std::vector::iterator dtIter = allDataFileTypes.begin(); dtIter != allDataFileTypes.end(); dtIter++) { const DataFileTypeEnum::Enum dataFileType = *dtIter; bool ciftiRowFlag = false; bool labelFileFlag = false; ParcelSourceDimension parcelSourceDimension = PARCEL_SOURCE_INVALID_DIMENSION; switch (dataFileType) { case DataFileTypeEnum::BORDER: break; case DataFileTypeEnum::CONNECTIVITY_DENSE: ciftiRowFlag = true; break; case DataFileTypeEnum::CONNECTIVITY_DENSE_LABEL: labelFileFlag = true; break; case DataFileTypeEnum::CONNECTIVITY_DENSE_PARCEL: ciftiRowFlag = true; break; case DataFileTypeEnum::CONNECTIVITY_PARCEL: parcelSourceDimension = PARCEL_SOURCE_LOADING_DIMENSION; break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_DENSE: parcelSourceDimension = PARCEL_SOURCE_LOADING_DIMENSION; break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_LABEL: parcelSourceDimension = PARCEL_SOURCE_MAPPING_DIMENSION; labelFileFlag = true; break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_SCALAR: parcelSourceDimension = PARCEL_SOURCE_MAPPING_DIMENSION; break; case DataFileTypeEnum::CONNECTIVITY_PARCEL_SERIES: parcelSourceDimension = PARCEL_SOURCE_MAPPING_DIMENSION; break; case DataFileTypeEnum::CONNECTIVITY_DENSE_SCALAR: ciftiRowFlag = true; break; case DataFileTypeEnum::CONNECTIVITY_DENSE_TIME_SERIES: ciftiRowFlag = true; break; case DataFileTypeEnum::CONNECTIVITY_FIBER_ORIENTATIONS_TEMPORARY: break; case DataFileTypeEnum::CONNECTIVITY_FIBER_TRAJECTORY_TEMPORARY: ciftiRowFlag = true; break; case DataFileTypeEnum::CONNECTIVITY_SCALAR_DATA_SERIES: break; case DataFileTypeEnum::FOCI: break; case DataFileTypeEnum::LABEL: labelFileFlag = true; break; case DataFileTypeEnum::IMAGE: break; case DataFileTypeEnum::METRIC: break; case DataFileTypeEnum::PALETTE: break; case DataFileTypeEnum::RGBA: break; case DataFileTypeEnum::SCENE: break; case DataFileTypeEnum::SPECIFICATION: break; case DataFileTypeEnum::SURFACE: break; case DataFileTypeEnum::UNKNOWN: break; case DataFileTypeEnum::VOLUME: break; } if (parcelSourceDimension != PARCEL_SOURCE_INVALID_DIMENSION) { CaretAssert(parcelSourceDimension != PARCEL_SOURCE_INVALID_DIMENSION); m_parcelSourceDimensionMap.insert(std::make_pair(dataFileType, parcelSourceDimension)); } if (ciftiRowFlag) { supportedCiftiRowFileTypes.push_back(dataFileType); } if (labelFileFlag) { supportedLabelFileTypes.push_back(dataFileType); } } /* * CIFTI row widgets */ m_ciftiRowWidget = createCiftiRowWidget(supportedCiftiRowFileTypes); /* * Label files widget */ m_labelFileWidgets.m_widget = createLabelFilesWidget(supportedLabelFileTypes); /* * CIFTI Parcel Widgets */ m_ciftiParcelWidget = createCiftiParcelWidget(); m_widgetBox = new WuQGroupBoxExclusiveWidget(); m_widgetBox->addWidget(m_ciftiRowWidget, "Identify Brainordinate from CIFTI File Row"); m_widgetBox->addWidget(m_ciftiParcelWidget, "Identify CIFTI File Parcel"); m_widgetBox->addWidget(m_labelFileWidgets.m_widget, "Identify Label"); m_widgetBox->addWidget(m_surfaceVertexWidget, "Identify Surface Vertex"); QObject::connect(m_widgetBox, SIGNAL(currentChanged(int32_t)), this, SLOT(selectedWidgetChanged())); setCentralWidget(m_widgetBox, WuQDialog::SCROLL_AREA_NEVER); updateDialog(); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_USER_INTERFACE_UPDATE); } /** * Destructor. */ IdentifyBrainordinateDialog::~IdentifyBrainordinateDialog() { EventManager::get()->removeAllEventsFromListener(this); delete m_ciftiRowFileSelectionModel; m_ciftiRowFileSelectionModel = NULL; } /** * Receive an event. * * @param event * The event that the receive can respond to. */ void IdentifyBrainordinateDialog::receiveEvent(Event* event) { if (event->getEventType() == EventTypeEnum::EVENT_USER_INTERFACE_UPDATE) { EventUserInterfaceUpdate* uiEvent = dynamic_cast(event); CaretAssert(uiEvent); uiEvent->setEventProcessed(); updateDialog(); } } /** * Create and return the CIFTI Parcel Widget * * @param supportedFileTypes * The supported file types for this widget. * @return * The CIFTI Parcel Widget */ QWidget* IdentifyBrainordinateDialog::createCiftiParcelWidget() { int columnCount = 0; const int COLUMN_LABEL = columnCount++; const int COLUMN_MAP_LEFT = columnCount++; const int COLUMN_MAP_RIGHT = columnCount++; m_ciftiParcelFileLabel = new QLabel("File"); m_ciftiParcelFileMapLabel = new QLabel("Map"); std::vector supportedParcelFileTypes; for (std::map::iterator iter = m_parcelSourceDimensionMap.begin(); iter != m_parcelSourceDimensionMap.end(); iter++) { const DataFileTypeEnum::Enum dataFileType = iter->first; CaretAssert(dataFileType != DataFileTypeEnum::UNKNOWN); supportedParcelFileTypes.push_back(dataFileType); } m_ciftiParcelFileSelector = new CaretMappableDataFileAndMapSelectorObject(supportedParcelFileTypes, CaretMappableDataFileAndMapSelectorObject::OPTION_SHOW_MAP_INDEX_SPIN_BOX, this); QObject::connect(m_ciftiParcelFileSelector, SIGNAL(selectionWasPerformed()), this, SLOT(slotParcelFileOrMapSelectionChanged())); m_ciftiParcelFileSelector->getWidgetsForAddingToLayout(m_ciftiParcelFileComboBox, m_ciftiParcelFileMapSpinBox, m_ciftiParcelFileMapComboBox); m_ciftiParcelFileMapSpinBox->setFixedWidth(INDEX_SPIN_BOX_WIDTH); m_ciftiParcelFileMapSpinBox->setToolTip("Map indices start at one."); m_ciftiParcelFileParcelLabel = new QLabel("Parcel"); m_ciftiParcelFileParcelNameComboBox = new CiftiParcelSelectionComboBox(this); /* * Widget and layout */ QWidget* widget = new QWidget(); QGridLayout* ciftiParcelLayout = new QGridLayout(widget); ciftiParcelLayout->addWidget(m_ciftiParcelFileLabel, 0, COLUMN_LABEL); ciftiParcelLayout->addWidget(m_ciftiParcelFileComboBox, 0, COLUMN_MAP_LEFT, 1, 2); ciftiParcelLayout->addWidget(m_ciftiParcelFileMapLabel, 1, COLUMN_LABEL); ciftiParcelLayout->addWidget(m_ciftiParcelFileMapSpinBox, 1, COLUMN_MAP_LEFT); //, Qt::AlignLeft); ciftiParcelLayout->addWidget(m_ciftiParcelFileMapComboBox, 1, COLUMN_MAP_RIGHT); //, Qt::AlignLeft); ciftiParcelLayout->addWidget(m_ciftiParcelFileParcelLabel, 2, COLUMN_LABEL); ciftiParcelLayout->addWidget(m_ciftiParcelFileParcelNameComboBox->getWidget(), 2, COLUMN_MAP_LEFT, 1, 2); return widget; } /** * Create and return the CIFTI Row Widget * * @param supportedFileTypes * The supported file types for this widget. * @return * The CIFTI Row Widget */ QWidget* IdentifyBrainordinateDialog::createCiftiRowWidget(const std::vector& supportedFileTypes) { int columnCount = 0; const int COLUMN_LABEL = columnCount++; const int COLUMN_MAP_LEFT = columnCount++; /* * CIFTI Row selection */ m_ciftiRowFileLabel = new QLabel("File"); m_ciftiRowFileSelectionModel = CaretDataFileSelectionModel::newInstanceForCaretDataFileTypes(GuiManager::get()->getBrain(), supportedFileTypes); m_ciftiRowFileComboBox = new CaretDataFileSelectionComboBox(this); m_ciftiRowFileIndexLabel = new QLabel("Row Index"); m_ciftiRowFileIndexSpinBox = WuQFactory::newSpinBoxWithMinMaxStep(0, std::numeric_limits::max(), 1); m_ciftiRowFileIndexSpinBox->setFixedWidth(INDEX_SPIN_BOX_WIDTH); m_ciftiRowFileIndexSpinBox->setToolTip("Row indices start at zero."); QWidget* widget = new QWidget(); QGridLayout* ciftiRowLayout = new QGridLayout(widget); ciftiRowLayout->addWidget(m_ciftiRowFileLabel, 0, COLUMN_LABEL); ciftiRowLayout->addWidget(m_ciftiRowFileComboBox->getWidget(), 0, COLUMN_MAP_LEFT, 1, 2); ciftiRowLayout->addWidget(m_ciftiRowFileIndexLabel, 1, COLUMN_LABEL); ciftiRowLayout->addWidget(m_ciftiRowFileIndexSpinBox, 1, COLUMN_MAP_LEFT); return widget; } /** * Create and return the Label Files Widget * * @param supportedFileTypes * The supported file types for this widget. * @return * The Label Files Row Widget */ QWidget* IdentifyBrainordinateDialog::createLabelFilesWidget(const std::vector& supportedFileTypes) { int columnCount = 0; const int COLUMN_LABEL = columnCount++; const int COLUMN_MAP_LEFT = columnCount++; const int COLUMN_MAP_RIGHT = columnCount++; m_labelFileWidgets.m_fileLabel = new QLabel("File"); m_labelFileWidgets.m_fileMapLabel = new QLabel("Map"); m_labelFileWidgets.m_fileSelector = new CaretMappableDataFileAndMapSelectorObject(supportedFileTypes, CaretMappableDataFileAndMapSelectorObject::OPTION_SHOW_MAP_INDEX_SPIN_BOX, this); QObject::connect(m_labelFileWidgets.m_fileSelector, SIGNAL(selectionWasPerformed()), this, SLOT(slotLabelFileOrMapSelectionChanged())); m_labelFileWidgets.m_fileSelector->getWidgetsForAddingToLayout(m_labelFileWidgets.m_fileComboBox, m_labelFileWidgets.m_fileMapSpinBox, m_labelFileWidgets.m_fileMapComboBox); m_labelFileWidgets.m_fileMapSpinBox->setFixedWidth(INDEX_SPIN_BOX_WIDTH); m_labelFileWidgets.m_fileMapSpinBox->setToolTip("Map indices start at one."); m_labelFileWidgets.m_fileLabellLabel = new QLabel("Label"); m_labelFileWidgets.m_fileLabelComboBox = new GiftiLabelTableSelectionComboBox(this); /* * Widget and layout */ QWidget* widget = new QWidget(); QGridLayout* labelWidgetLayout = new QGridLayout(widget); labelWidgetLayout->addWidget(m_labelFileWidgets.m_fileLabel, 0, COLUMN_LABEL); labelWidgetLayout->addWidget(m_labelFileWidgets.m_fileComboBox, 0, COLUMN_MAP_LEFT, 1, 2); labelWidgetLayout->addWidget(m_labelFileWidgets.m_fileMapLabel, 1, COLUMN_LABEL); labelWidgetLayout->addWidget(m_labelFileWidgets.m_fileMapSpinBox, 1, COLUMN_MAP_LEFT); //, Qt::AlignLeft); labelWidgetLayout->addWidget(m_labelFileWidgets.m_fileMapComboBox, 1, COLUMN_MAP_RIGHT); //, Qt::AlignLeft); labelWidgetLayout->addWidget(m_labelFileWidgets.m_fileLabellLabel, 2, COLUMN_LABEL); labelWidgetLayout->addWidget(m_labelFileWidgets.m_fileLabelComboBox->getWidget(), 2, COLUMN_MAP_LEFT, 1, 2); return widget; } /** * Called when the user changes the label file or map. */ void IdentifyBrainordinateDialog::slotLabelFileOrMapSelectionChanged() { updateDialog(); } /** * @return Create a Surface Vertex Widget */ QWidget* IdentifyBrainordinateDialog::createSurfaceVertexlWidget() { int columnCount = 0; const int COLUMN_LABEL = columnCount++; const int COLUMN_MAP_LEFT = columnCount++; m_vertexStructureLabel = new QLabel("Structure"); m_vertexStructureComboBox = new StructureEnumComboBox(this); m_vertexStructureComboBox->listOnlyValidStructures(); m_vertexIndexLabel = new QLabel("Vertex Index"); m_vertexIndexSpinBox = WuQFactory::newSpinBoxWithMinMaxStep(0, std::numeric_limits::max(), 1); m_vertexIndexSpinBox->setFixedWidth(INDEX_SPIN_BOX_WIDTH); m_vertexIndexSpinBox->setToolTip("Vertex indices start at zero."); QWidget* widget = new QWidget(); QGridLayout* surfaceVertexLayout = new QGridLayout(widget); surfaceVertexLayout->addWidget(m_vertexStructureLabel, 0, COLUMN_LABEL); surfaceVertexLayout->addWidget(m_vertexStructureComboBox->getWidget(), 0, COLUMN_MAP_LEFT, 1, 2); surfaceVertexLayout->addWidget(m_vertexIndexLabel, 1, COLUMN_LABEL); surfaceVertexLayout->addWidget(m_vertexIndexSpinBox, 1, COLUMN_MAP_LEFT); return widget; } /** * Called when the user changes the parcel file or map. */ void IdentifyBrainordinateDialog::slotParcelFileOrMapSelectionChanged() { updateDialog(); } /** * Called when the selected widget changes. */ void IdentifyBrainordinateDialog::selectedWidgetChanged() { updateDialog(); } /** * Update the dialog. */ void IdentifyBrainordinateDialog::updateDialog() { CaretMappableDataFileAndMapSelectionModel* parcelFileModel = m_ciftiParcelFileSelector->getModel(); m_ciftiParcelFileSelector->updateFileAndMapSelector(parcelFileModel); CaretMappableDataFile* parcelFile = parcelFileModel->getSelectedFile(); bool parcelsMapValidFlag = false; if (parcelFile != NULL) { const int32_t mapIndex = parcelFileModel->getSelectedMapIndex(); if ((mapIndex >= 0) && (mapIndex < parcelFile->getNumberOfMaps())) { CiftiMappableDataFile* ciftiMapFile = dynamic_cast(parcelFile); if (ciftiMapFile != NULL) { const ParcelSourceDimension parcelSourceDimension = getParcelSourceDimensionFromFile(parcelFile); switch (parcelSourceDimension) { case PARCEL_SOURCE_INVALID_DIMENSION: CaretAssertMessage(0, "Should never be invalid."); break; case PARCEL_SOURCE_LOADING_DIMENSION: m_ciftiParcelFileParcelNameComboBox->updateComboBox(ciftiMapFile->getCiftiParcelsMapForLoading()); parcelsMapValidFlag = true; break; case PARCEL_SOURCE_MAPPING_DIMENSION: m_ciftiParcelFileParcelNameComboBox->updateComboBox(ciftiMapFile->getCiftiParcelsMapForBrainordinateMapping()); parcelsMapValidFlag = true; break; } } } } if ( ! parcelsMapValidFlag) { m_ciftiParcelFileParcelNameComboBox->updateComboBox(NULL); } CaretMappableDataFileAndMapSelectionModel* labelFileModel = m_labelFileWidgets.m_fileSelector->getModel(); m_labelFileWidgets.m_fileSelector->updateFileAndMapSelector(labelFileModel); CaretMappableDataFile* labelFile = labelFileModel->getSelectedFile(); bool labelTableComboBoxValid = false; if (labelFile != NULL) { const int32_t mapIndex = labelFileModel->getSelectedMapIndex(); if ((mapIndex >= 0) && (mapIndex < labelFile->getNumberOfMaps())) { GiftiLabelTable* labelTable = labelFile->getMapLabelTable(mapIndex); m_labelFileWidgets.m_fileLabelComboBox->updateContent(labelTable); labelTableComboBoxValid = true; } } if ( ! labelTableComboBoxValid) { m_labelFileWidgets.m_fileLabelComboBox->updateContent(NULL); } m_ciftiRowFileComboBox->updateComboBox(m_ciftiRowFileSelectionModel); m_vertexStructureComboBox->listOnlyValidStructures(); } /** * Get the source of parcels for a caret data file. * * @param mapFile * The caret data file. * @return * The source for parcels (loading or mapping dimension). */ IdentifyBrainordinateDialog::ParcelSourceDimension IdentifyBrainordinateDialog::getParcelSourceDimensionFromFile(const CaretMappableDataFile* mapFile) { ParcelSourceDimension parcelSourceDim = PARCEL_SOURCE_INVALID_DIMENSION; std::map::iterator typeIter = m_parcelSourceDimensionMap.find(mapFile->getDataFileType()); if (typeIter != m_parcelSourceDimensionMap.end()) { parcelSourceDim = typeIter->second; } CaretAssert(parcelSourceDim != PARCEL_SOURCE_INVALID_DIMENSION); return parcelSourceDim; } /** * Gets called when Apply button is clicked. */ void IdentifyBrainordinateDialog::applyButtonClicked() { AString errorMessage; QWidget* selectedWidget = m_widgetBox->currentWidget(); if (m_surfaceVertexWidget == selectedWidget) { processSurfaceVertexWidget(errorMessage); } else if (m_ciftiParcelWidget == selectedWidget) { processCiftiParcelWidget(errorMessage); } else if (m_ciftiRowWidget == selectedWidget) { processCiftiRowWidget(errorMessage); } else if (m_labelFileWidgets.m_widget == selectedWidget) { processLabelFileWidget(errorMessage); } else { const QString msg("Choose one of the methods for identifying a brainordinate."); errorMessage = (WuQtUtilities::createWordWrappedToolTipText(msg)); } if ( ! errorMessage.isEmpty()) { WuQMessageBox::errorOk(this, errorMessage); return; } WuQDialogNonModal::applyButtonClicked(); } /** * Update coloring and redraw all windows. */ void IdentifyBrainordinateDialog::updateColoringAndDrawAllWindows() { EventManager::get()->sendEvent(EventSurfaceColoringInvalidate().getPointer()); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } /** * Flash the brainordinate highlighting region of interest. * * @param brainROI * brainordinate highlighting region of interest that is flashed. */ void IdentifyBrainordinateDialog::flashBrainordinateHighlightingRegionOfInterest(BrainordinateRegionOfInterest* brainROI) { const float flashDelayTime = 0.25; const int32_t flashCount = 4; for (int32_t iFlash = 0; iFlash < flashCount; iFlash++) { brainROI->setBrainordinateHighlightingEnabled(true); updateColoringAndDrawAllWindows(); SystemUtilities::sleepSeconds(flashDelayTime); brainROI->setBrainordinateHighlightingEnabled(false); updateColoringAndDrawAllWindows(); SystemUtilities::sleepSeconds(flashDelayTime); } } /** * Process user's selectons in the Label File Widget * * @param errorMessageOut * Output containing error message. */ void IdentifyBrainordinateDialog::processLabelFileWidget(AString& errorMessageOut) { Brain* brain = GuiManager::get()->getBrain(); CaretMappableDataFile* mapFile = m_labelFileWidgets.m_fileSelector->getModel()->getSelectedFile(); const int32_t mapIndex = m_labelFileWidgets.m_fileSelector->getModel()->getSelectedMapIndex(); if (mapFile != NULL) { const AString labelName = m_labelFileWidgets.m_fileLabelComboBox->getSelectedLabelName(); BrainordinateRegionOfInterest* brainROI = brain->getBrainordinateHighlightRegionOfInterest(); if (brainROI->setWithLabelFileLabel(mapFile, mapIndex, labelName, errorMessageOut)) { flashBrainordinateHighlightingRegionOfInterest(brainROI); } } } /** * Process user's selectons in the CIFTI Parcel Widget * * @param errorMessageOut * Output containing error message. */ void IdentifyBrainordinateDialog::processCiftiParcelWidget(AString& errorMessageOut) { Brain* brain = GuiManager::get()->getBrain(); CaretMappableDataFile* mapFile = m_ciftiParcelFileSelector->getModel()->getSelectedFile(); const int32_t mapIndex = m_ciftiParcelFileSelector->getModel()->getSelectedMapIndex(); if (mapFile != NULL) { CiftiMappableDataFile* ciftiFile = dynamic_cast(mapFile); const QString parcelName = m_ciftiParcelFileParcelNameComboBox->getSelectedParcelName(); const ParcelSourceDimension parcelSourceDimension = getParcelSourceDimensionFromFile(ciftiFile); BrainordinateRegionOfInterest* brainROI = brain->getBrainordinateHighlightRegionOfInterest(); switch (parcelSourceDimension) { case PARCEL_SOURCE_INVALID_DIMENSION: CaretAssertMessage(0, "Should never be invalid."); break; case PARCEL_SOURCE_LOADING_DIMENSION: brainROI->setWithCiftiParcelLoadingBrainordinates(ciftiFile, mapIndex, parcelName, errorMessageOut); break; case PARCEL_SOURCE_MAPPING_DIMENSION: brainROI->setWithCiftiParcelMappingBrainordinates(ciftiFile, mapIndex, parcelName, errorMessageOut); break; } if (brainROI->hasSurfaceNodes() || brainROI->hasVolumeVoxels()) { /* * Need to load data? */ if (parcelSourceDimension == PARCEL_SOURCE_LOADING_DIMENSION) { CiftiMappableConnectivityMatrixDataFile* connMatrixFile = dynamic_cast(ciftiFile); if (connMatrixFile != NULL) { SelectionManager* selectionManager = brain->getSelectionManager(); selectionManager->reset(); selectionManager->setAllSelectionsEnabled(true); SelectionItemCiftiConnectivityMatrixRowColumn* selectionCiftiRowColumn = selectionManager->getCiftiConnectivityMatrixRowColumnIdentification(); const CiftiParcelsMap* ciftiParcelsMap = connMatrixFile->getCiftiParcelsMapForLoading(); const int64_t parcelIndex = ciftiParcelsMap->getIndexFromNumberOrName(parcelName); if (parcelIndex >= 0) { switch (connMatrixFile->getChartMatrixLoadingDimension()) { case ChartMatrixLoadingDimensionEnum::CHART_MATRIX_LOADING_BY_COLUMN: selectionCiftiRowColumn->setFileColumn(connMatrixFile, parcelIndex); break; case ChartMatrixLoadingDimensionEnum::CHART_MATRIX_LOADING_BY_ROW: selectionCiftiRowColumn->setFileRow(connMatrixFile, parcelIndex); break; } GuiManager::get()->processIdentification(-1, // invalid tab index selectionManager, this); } else { errorMessageOut = ("Parcel name=" + parcelName + " not found in parcels map for file " + connMatrixFile->getFileNameNoPath()); } } } /* * Highlight the selected parcel */ flashBrainordinateHighlightingRegionOfInterest(brainROI); } else { brainROI->setBrainordinateHighlightingEnabled(false); updateColoringAndDrawAllWindows(); } } else { errorMessageOut = ("No parcel-type file is selected."); } } /** * Process user's selectons in the * * @param errorMessageOut * Output containing error message. */ void IdentifyBrainordinateDialog::processCiftiRowWidget(AString& errorMessageOut) { Brain* brain = GuiManager::get()->getBrain(); SelectionManager* selectionManager = brain->getSelectionManager(); selectionManager->reset(); selectionManager->setAllSelectionsEnabled(true); CaretDataFile* dataFile = m_ciftiRowFileSelectionModel->getSelectedFile(); if (dataFile != NULL) { CiftiMappableDataFile* ciftiMapFile = dynamic_cast(dataFile); CiftiFiberTrajectoryFile* ciftiTrajFile = dynamic_cast(dataFile); const int32_t selectedCiftiRowIndex = m_ciftiRowFileIndexSpinBox->value(); try { StructureEnum::Enum surfaceStructure; int32_t surfaceNodeIndex; int32_t surfaceNumberOfNodes; bool surfaceNodeValid = false; int64_t voxelIJK[3]; float voxelXYZ[3]; bool voxelValid = false; if (ciftiMapFile != NULL) { ciftiMapFile->getBrainordinateFromRowIndex(selectedCiftiRowIndex, surfaceStructure, surfaceNodeIndex, surfaceNumberOfNodes, surfaceNodeValid, voxelIJK, voxelXYZ, voxelValid); } else if (ciftiTrajFile != NULL) { ciftiTrajFile->getBrainordinateFromRowIndex(selectedCiftiRowIndex, surfaceStructure, surfaceNodeIndex, surfaceNumberOfNodes, surfaceNodeValid, voxelIJK, voxelXYZ, voxelValid); } else { errorMessageOut = "Neither CIFTI Mappable nor CIFTI Trajectory file. Has new file type been added?"; } if (surfaceNodeValid) { SelectionItemSurfaceNode* surfaceID = selectionManager->getSurfaceNodeIdentification(); const Surface* surface = brain->getPrimaryAnatomicalSurfaceForStructure(surfaceStructure); if (surface != NULL) { if ((surfaceNodeIndex >= 0) && (surfaceNodeIndex < surface->getNumberOfNodes())) { surfaceID->setSurface(const_cast(surface)); surfaceID->setBrain(brain); const float* xyz = surface->getCoordinate(surfaceNodeIndex); const double doubleXYZ[3] = { xyz[0], xyz[1], xyz[2] }; surfaceID->setModelXYZ(doubleXYZ); surfaceID->setNodeNumber(surfaceNodeIndex); GuiManager::get()->processIdentification(-1, // invalid tab index selectionManager, this); } else { errorMessageOut = ("Surface vertex index " + AString::number(surfaceNodeIndex) + " is not valid for surface " + surface->getFileNameNoPath()); } } else{ errorMessageOut = ("No surfaces are loaded for structure " + StructureEnum::toGuiName(surfaceStructure)); } } else if (voxelValid) { SelectionItemVoxel* voxelID = selectionManager->getVoxelIdentification(); voxelID->setBrain(brain); voxelID->setEnabledForSelection(true); voxelID->setVoxelIdentification(brain, ciftiMapFile, voxelIJK, 0.0); const double doubleXYZ[3] = { voxelXYZ[0], voxelXYZ[1], voxelXYZ[2] }; voxelID->setModelXYZ(doubleXYZ); GuiManager::get()->processIdentification(-1, // invalid tab index selectionManager, this); } } catch (const DataFileException& dfe) { errorMessageOut = dfe.whatString(); } } } /** * Process user's selectons in the * * @param errorMessageOut * Output containing error message. */ void IdentifyBrainordinateDialog::processSurfaceVertexWidget(AString& errorMessageOut) { Brain* brain = GuiManager::get()->getBrain(); SelectionManager* selectionManager = brain->getSelectionManager(); selectionManager->reset(); const StructureEnum::Enum selectedStructure = m_vertexStructureComboBox->getSelectedStructure(); const int32_t selectedVertexIndex = m_vertexIndexSpinBox->value(); BrainStructure* bs = brain->getBrainStructure(selectedStructure, false); if (bs != NULL) { if (selectedVertexIndex < bs->getNumberOfNodes()) { Surface* surface = bs->getPrimaryAnatomicalSurface(); if (surface != NULL) { SelectionItemSurfaceNode* nodeID = selectionManager->getSurfaceNodeIdentification(); nodeID->setBrain(brain); nodeID->setNodeNumber(selectedVertexIndex); nodeID->setSurface(surface); const float* fxyz = surface->getCoordinate(selectedVertexIndex); const double xyz[3] = { fxyz[0], fxyz[1], fxyz[2] }; nodeID->setModelXYZ(xyz); GuiManager::get()->processIdentification(-1, selectionManager, this); } else { errorMessageOut = ("PROGRAM ERROR: Primary Anatomical Surface not found for structure " + StructureEnum::toName(selectedStructure)); } } else { errorMessageOut = ("Vertex Index " + AString::number(selectedVertexIndex) + " is out of range [0, " + AString::number(bs->getNumberOfNodes() - 1) + "] for " + StructureEnum::toGuiName(selectedStructure)); } } else { errorMessageOut = ("Structure " + StructureEnum::toName(selectedStructure) + " not found."); } } workbench-1.1.1/src/GuiQt/IdentifyBrainordinateDialog.h000066400000000000000000000131021255417355300230710ustar00rootroot00000000000000#ifndef __IDENTIFY_BRAINORDINATE_DIALOG_H__ #define __IDENTIFY_BRAINORDINATE_DIALOG_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "DataFileTypeEnum.h" #include "EventListenerInterface.h" #include "WuQDialogNonModal.h" class QLabel; class QRadioButton; class QSpinBox; namespace caret { class BrainordinateRegionOfInterest; class CaretDataFileSelectionComboBox; class CaretDataFileSelectionModel; class CaretMappableDataFileAndMapSelectorObject; class CaretMappableDataFile; class CiftiParcelSelectionComboBox; class GiftiLabelTableSelectionComboBox; class StructureEnumComboBox; class WuQGroupBoxExclusiveWidget; class IdentifyBrainordinateDialog : public WuQDialogNonModal, public EventListenerInterface { Q_OBJECT public: IdentifyBrainordinateDialog(QWidget* parent); virtual ~IdentifyBrainordinateDialog(); virtual void updateDialog(); virtual void receiveEvent(Event* event); protected: virtual void applyButtonClicked(); private: IdentifyBrainordinateDialog(const IdentifyBrainordinateDialog&); IdentifyBrainordinateDialog& operator=(const IdentifyBrainordinateDialog&); public: // ADD_NEW_METHODS_HERE private slots: void slotParcelFileOrMapSelectionChanged(); void slotLabelFileOrMapSelectionChanged(); void selectedWidgetChanged(); private: enum Mode { MODE_NONE, MODE_CIFTI_PARCEL, MODE_CIFTI_ROW, MODE_SURFACE_VERTEX }; enum ParcelSourceDimension { PARCEL_SOURCE_INVALID_DIMENSION, PARCEL_SOURCE_LOADING_DIMENSION, PARCEL_SOURCE_MAPPING_DIMENSION }; // ADD_NEW_MEMBERS_HERE QWidget* createCiftiParcelWidget(); QWidget* createCiftiRowWidget(const std::vector& supportedFileTypes); QWidget* createLabelFilesWidget(const std::vector& supportedFileTypes); QWidget* createSurfaceVertexlWidget(); void processCiftiParcelWidget(AString& errorMessageOut); void processCiftiRowWidget(AString& errorMessageOut); void processLabelFileWidget(AString& errorMessageOut); void processSurfaceVertexWidget(AString& errorMessageOut); void flashBrainordinateHighlightingRegionOfInterest(BrainordinateRegionOfInterest* brainROI); void updateColoringAndDrawAllWindows(); ParcelSourceDimension getParcelSourceDimensionFromFile(const CaretMappableDataFile* mapFile); StructureEnumComboBox* m_vertexStructureComboBox; QWidget* m_surfaceVertexWidget; QLabel* m_vertexStructureLabel; QSpinBox* m_vertexIndexSpinBox; QLabel* m_vertexIndexLabel; QWidget* m_ciftiRowWidget; struct LabelFileWidgets { QWidget* m_widget; CaretMappableDataFileAndMapSelectorObject* m_fileSelector; QLabel* m_fileLabel; QLabel* m_fileMapLabel; QWidget* m_fileComboBox; QWidget* m_fileMapSpinBox; QWidget* m_fileMapComboBox; QLabel* m_fileLabellLabel; GiftiLabelTableSelectionComboBox* m_fileLabelComboBox; }; LabelFileWidgets m_labelFileWidgets; QLabel* m_ciftiRowFileLabel; CaretDataFileSelectionComboBox* m_ciftiRowFileComboBox; CaretDataFileSelectionModel* m_ciftiRowFileSelectionModel; QLabel* m_ciftiRowFileIndexLabel; QSpinBox* m_ciftiRowFileIndexSpinBox; QWidget* m_ciftiParcelWidget; QLabel* m_ciftiParcelFileLabel; QLabel* m_ciftiParcelFileMapLabel; QWidget* m_ciftiParcelFileComboBox; QWidget* m_ciftiParcelFileMapSpinBox; QWidget* m_ciftiParcelFileMapComboBox; QLabel* m_ciftiParcelFileParcelLabel; CiftiParcelSelectionComboBox* m_ciftiParcelFileParcelNameComboBox; CaretMappableDataFileAndMapSelectorObject* m_ciftiParcelFileSelector; WuQGroupBoxExclusiveWidget* m_widgetBox; std::map m_parcelSourceDimensionMap; }; #ifdef __IDENTIFY_BRAINORDINATE_DIALOG_DECLARE__ #endif // __IDENTIFY_BRAINORDINATE_DIALOG_DECLARE__ } // namespace #endif //__IDENTIFY_BRAINORDINATE_DIALOG_H__ workbench-1.1.1/src/GuiQt/ImageCaptureDialog.cxx000066400000000000000000000756701255417355300215570ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define __IMAGE_CAPTURE_DIALOG__H__DECLARE__ #include "ImageCaptureDialog.h" #undef __IMAGE_CAPTURE_DIALOG__H__DECLARE__ #include "Brain.h" #include "BrainBrowserWindow.h" #include "CaretAssert.h" #include "CaretPreferences.h" #include "DataFileException.h" #include "EnumComboBoxTemplate.h" #include "EventBrowserWindowGraphicsRedrawn.h" #include "EventGraphicsUpdateOneWindow.h" #include "EventImageCapture.h" #include "EventManager.h" #include "FileInformation.h" #include "GuiManager.h" #include "ImageFile.h" #include "ImageDimensionsModel.h" #include "SessionManager.h" #include "CaretFileDialog.h" #include "WuQFactory.h" #include "WuQMessageBox.h" #include "WuQTimedMessageDisplay.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::ImageCaptureDialog * \brief Dialog for capturing images. * \ingroup GuiQt * */ /** * Constructor for editing a palette selection. * * @param parent * Parent widget on which this dialog is displayed. */ ImageCaptureDialog::ImageCaptureDialog(BrainBrowserWindow* parent) : WuQDialogNonModal("Image Capture", parent) { m_imageDimensionsModel = new ImageDimensionsModel(); m_imageDimensionsWidget = NULL; setDeleteWhenClosed(false); /* * Use Apply button for image capture */ setApplyButtonText("Capture"); /* * Image Source */ QWidget* imageSourceWidget = createImageSourceSection(); /* * Image Size * Note: Label is updated when window size is updated */ m_imageDimensionsWidget = createImageDimensionsSection(); /* * Image Options */ QWidget* imageOptionsWidget = createImageOptionsSection(); /* * Image Destination */ QWidget* imageDestinationWidget = createImageDestinationSection(); QWidget* w = new QWidget(); QVBoxLayout* layout = new QVBoxLayout(w); layout->addWidget(imageSourceWidget); layout->addWidget(m_imageDimensionsWidget); layout->addWidget(imageOptionsWidget); layout->addWidget(imageDestinationWidget); setCentralWidget(w, WuQDialog::SCROLL_AREA_NEVER); /* * Make apply button the default button. */ QPushButton* applyButton = getDialogButtonBox()->button(QDialogButtonBox::Apply); CaretAssert(applyButton); applyButton->setAutoDefault(true); applyButton->setDefault(true); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_BROWSER_WINDOW_GRAPHICS_HAVE_BEEN_REDRAWN); updateBrowserWindowWidthAndHeightLabel(); /* * Initialize the custom image size */ m_imageDimensionsModel->setPixelWidthAndHeight(512, 512); updateDialogWithImageDimensionsModel(); m_scaleProportionallyCheckBox->setChecked(true); scaleProportionallyCheckBoxClicked(m_scaleProportionallyCheckBox->isChecked()); WuQtUtilities::setWordWrappedToolTip(m_scaleProportionallyCheckBox, ("If checked, the heights of the pixel and image dimensions " "are automatically adjusted so that their proportions " "match the proportion of the selected window's " "graphics region.")); setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); } /** * Destructor. */ ImageCaptureDialog::~ImageCaptureDialog() { EventManager::get()->removeAllEventsFromListener(this); delete m_imageDimensionsModel; } /** * @return Create and return the image source section. */ QWidget* ImageCaptureDialog::createImageSourceSection() { QLabel* windowLabel = new QLabel("Workbench Window: "); m_windowSelectionSpinBox = new QSpinBox(); m_windowSelectionSpinBox->setRange(1, BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_WINDOWS); m_windowSelectionSpinBox->setSingleStep(1); m_windowSelectionSpinBox->setFixedWidth(60); QObject::connect(m_windowSelectionSpinBox, SIGNAL(valueChanged(int)), this, SLOT(updateBrowserWindowWidthAndHeightLabel())); QGroupBox* groupBox = new QGroupBox("Source"); QGridLayout* gridLayout = new QGridLayout(groupBox); gridLayout->addWidget(windowLabel, 0, 0); gridLayout->addWidget(m_windowSelectionSpinBox, 0, 1); gridLayout->setColumnStretch(0, 0); gridLayout->setColumnStretch(1, 0); gridLayout->setColumnStretch(1000, 100); return groupBox; } /** * @return Create and return the image options section. */ QWidget* ImageCaptureDialog::createImageOptionsSection() { m_imageAutoCropCheckBox = new QCheckBox("Automatically Crop Image"); QLabel* imageAutoCropMarginLabel = new QLabel(" Margin"); m_imageAutoCropMarginSpinBox = new QSpinBox(); m_imageAutoCropMarginSpinBox->setMinimum(0); m_imageAutoCropMarginSpinBox->setMaximum(100000); m_imageAutoCropMarginSpinBox->setSingleStep(1); m_imageAutoCropMarginSpinBox->setValue(10); m_imageAutoCropMarginSpinBox->setMaximumWidth(100); QHBoxLayout* cropMarginLayout = new QHBoxLayout(); cropMarginLayout->addWidget(imageAutoCropMarginLabel); cropMarginLayout->addWidget(m_imageAutoCropMarginSpinBox); cropMarginLayout->addStretch(); QGroupBox* groupBox = new QGroupBox("Image Options"); QVBoxLayout* layout = new QVBoxLayout(groupBox); layout->addWidget(m_imageAutoCropCheckBox); layout->addLayout(cropMarginLayout); return groupBox; } /** * @return Create and return the image dimensions section. */ QWidget* ImageCaptureDialog::createImageDimensionsSection() { m_imageSizeWindowRadioButton = new QRadioButton("Size of Window"); m_imageSizeCustomRadioButton = new QRadioButton("Custom"); QButtonGroup* sizeButtonGroup = new QButtonGroup(this); sizeButtonGroup->addButton(m_imageSizeWindowRadioButton); sizeButtonGroup->addButton(m_imageSizeCustomRadioButton); QObject::connect(sizeButtonGroup, SIGNAL(buttonClicked(QAbstractButton*)), this, SLOT(sizeRadioButtonClicked(QAbstractButton*))); QLabel* customPixelsWidthLabel = new QLabel("Width:"); QLabel* customPixelsHeightLabel = new QLabel("Height:"); QLabel* customUnitsWidthLabel = new QLabel("Width:"); QLabel* customUnitsHeightLabel = new QLabel("Height:"); QLabel* customResolutionLabel = new QLabel("Resolution:"); const int pixelSpinBoxWidth = 80; QLabel* pixelDimensionsLabel = new QLabel("Pixel Dimensions"); m_pixelWidthSpinBox = new QSpinBox(); m_pixelWidthSpinBox->setFixedWidth(pixelSpinBoxWidth); m_pixelWidthSpinBox->setRange(100, 10000000); m_pixelWidthSpinBox->setSingleStep(1); m_pixelWidthSpinBox->setValue(2560); QObject::connect(m_pixelWidthSpinBox, SIGNAL(valueChanged(int)), this, SLOT(pixelWidthValueChanged(int))); m_pixelHeightSpinBox = new QSpinBox(); m_pixelHeightSpinBox->setFixedWidth(pixelSpinBoxWidth); m_pixelHeightSpinBox->setRange(100, 10000000); m_pixelHeightSpinBox->setSingleStep(1); m_pixelHeightSpinBox->setValue(2048); QObject::connect(m_pixelHeightSpinBox, SIGNAL(valueChanged(int)), this, SLOT(pixelHeightValueChanged(int))); const int imageSpinBoxWidth = 100; QLabel* imageDimensionsLabel = new QLabel("Image Dimensions"); m_imageWidthSpinBox = new QDoubleSpinBox(); m_imageWidthSpinBox->setFixedWidth(imageSpinBoxWidth); m_imageWidthSpinBox->setRange(0.01, 100000000.0); m_imageWidthSpinBox->setSingleStep(0.1); m_imageWidthSpinBox->setValue(2048); QObject::connect(m_imageWidthSpinBox, SIGNAL(valueChanged(double)), this, SLOT(imageWidthValueChanged(double))); m_imageHeightSpinBox = new QDoubleSpinBox(); m_imageHeightSpinBox->setFixedWidth(imageSpinBoxWidth); m_imageHeightSpinBox->setRange(0.01, 100000000.0); m_imageHeightSpinBox->setSingleStep(0.1); m_imageHeightSpinBox->setValue(2048); QObject::connect(m_imageHeightSpinBox, SIGNAL(valueChanged(double)), this, SLOT(imageHeightValueChanged(double))); m_imageResolutionSpinBox = new QDoubleSpinBox(); m_imageResolutionSpinBox->setFixedWidth(imageSpinBoxWidth); m_imageResolutionSpinBox->setRange(0.01, 1000000); m_imageResolutionSpinBox->setSingleStep(1); m_imageResolutionSpinBox->setValue(72); QObject::connect(m_imageResolutionSpinBox, SIGNAL(valueChanged(double)), this, SLOT(imageResolutionValueChanged(double))); m_imageSpatialUnitsEnumComboBox = new EnumComboBoxTemplate(this); m_imageSpatialUnitsEnumComboBox->setup(); QObject::connect(m_imageSpatialUnitsEnumComboBox, SIGNAL(itemActivated()), this, SLOT(imageSizeUnitsEnumComboBoxItemActivated())); m_imagePixelsPerSpatialUnitsEnumComboBox = new EnumComboBoxTemplate(this); m_imagePixelsPerSpatialUnitsEnumComboBox->setup(); QObject::connect(m_imagePixelsPerSpatialUnitsEnumComboBox, SIGNAL(itemActivated()), this, SLOT(imageResolutionUnitsEnumComboBoxItemActivated())); m_scaleProportionallyCheckBox = new QCheckBox("Scale Proportionally"); QObject::connect(m_scaleProportionallyCheckBox, SIGNAL(clicked(bool)), this, SLOT(scaleProportionallyCheckBoxClicked(bool))); QWidget* pixelsSizeWidget = new QWidget(); QGridLayout* pixelsSizeLayout = new QGridLayout(pixelsSizeWidget); WuQtUtilities::setLayoutSpacingAndMargins(pixelsSizeLayout, 4, 0); int pixelsRow = 0; pixelsSizeLayout->addWidget(pixelDimensionsLabel, pixelsRow, 0, 1, 2, Qt::AlignHCenter); pixelsRow++; pixelsSizeLayout->addWidget(customPixelsWidthLabel, pixelsRow, 0); pixelsSizeLayout->addWidget(m_pixelWidthSpinBox, pixelsRow, 1); pixelsRow++; pixelsSizeLayout->addWidget(customPixelsHeightLabel, pixelsRow, 0); pixelsSizeLayout->addWidget(m_pixelHeightSpinBox, pixelsRow, 1); pixelsRow++; pixelsSizeLayout->addWidget(WuQtUtilities::createHorizontalLineWidget(), pixelsRow, 0, 1, 2); pixelsRow++; pixelsSizeLayout->addWidget(m_scaleProportionallyCheckBox, pixelsRow, 0, 1, 2, Qt::AlignLeft); pixelsRow++; pixelsSizeWidget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); QWidget* imageUnitsWidget = new QWidget(); QGridLayout* imageUnitsLayout = new QGridLayout(imageUnitsWidget); WuQtUtilities::setLayoutSpacingAndMargins(imageUnitsLayout, 4, 0); int unitsRow = 0; imageUnitsLayout->addWidget(imageDimensionsLabel, unitsRow, 0, 1, 3, Qt::AlignHCenter); unitsRow++; imageUnitsLayout->addWidget(customUnitsWidthLabel, unitsRow, 0); imageUnitsLayout->addWidget(m_imageWidthSpinBox, unitsRow, 1); imageUnitsLayout->addWidget(m_imageSpatialUnitsEnumComboBox->getWidget(), unitsRow, 2, 2, 1); unitsRow++; imageUnitsLayout->addWidget(customUnitsHeightLabel, unitsRow, 0); imageUnitsLayout->addWidget(m_imageHeightSpinBox, unitsRow, 1); unitsRow++; imageUnitsLayout->addWidget(customResolutionLabel, unitsRow, 0); imageUnitsLayout->addWidget(m_imageResolutionSpinBox, unitsRow, 1); imageUnitsLayout->addWidget(m_imagePixelsPerSpatialUnitsEnumComboBox->getWidget(), unitsRow, 2); unitsRow++; imageUnitsWidget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); m_customDimensionsWidget = new QWidget(); QHBoxLayout* customDimensionsLayout = new QHBoxLayout(m_customDimensionsWidget); customDimensionsLayout->addSpacing(20); customDimensionsLayout->addWidget(pixelsSizeWidget, 0, Qt::AlignTop); customDimensionsLayout->addWidget(WuQtUtilities::createVerticalLineWidget()); customDimensionsLayout->addWidget(imageUnitsWidget, 0, Qt::AlignTop); QLabel* imageBytesLabel = new QLabel("Uncompressed Image Memory Size: "); m_imageNumberOfBytesLabel = new QLabel(" "); QWidget* imageBytesWidget = new QWidget(); QHBoxLayout* imageBytesLayout = new QHBoxLayout(imageBytesWidget); WuQtUtilities::setLayoutSpacingAndMargins(imageBytesLayout, 4, 0); imageBytesLayout->addWidget(imageBytesLabel); imageBytesLayout->addWidget(m_imageNumberOfBytesLabel); imageBytesLayout->addStretch(); QGroupBox* groupBox = new QGroupBox("Dimensions"); QVBoxLayout* layout = new QVBoxLayout(groupBox); layout->addWidget(m_imageSizeWindowRadioButton, 0, Qt::AlignLeft); layout->addWidget(m_imageSizeCustomRadioButton, 0, Qt::AlignLeft); layout->addWidget(m_customDimensionsWidget, 0, Qt::AlignLeft); layout->addWidget(WuQtUtilities::createHorizontalLineWidget()); layout->addWidget(imageBytesWidget); m_imageSizeWindowRadioButton->setChecked(true); sizeRadioButtonClicked(sizeButtonGroup->checkedButton()); return groupBox; } /** * Gets called when the Window Size or Custom radio button is clicked. * * @param button * Button that was clicked. */ void ImageCaptureDialog::sizeRadioButtonClicked(QAbstractButton* button) { if (button == m_imageSizeWindowRadioButton) { m_customDimensionsWidget->setEnabled(false); } else if (button == m_imageSizeCustomRadioButton) { m_customDimensionsWidget->setEnabled(true); } updateImageNumberOfBytesLabel(); } /** * Gets called when the image resolution units are changed. */ void ImageCaptureDialog::imageResolutionUnitsEnumComboBoxItemActivated() { updateDialogWithImageDimensionsModel(); } /** * Gets called when the image size units are changed. */ void ImageCaptureDialog::imageSizeUnitsEnumComboBoxItemActivated() { updateDialogWithImageDimensionsModel(); } /** * Update the dialog with data from the image dimensions model. */ void ImageCaptureDialog::updateDialogWithImageDimensionsModel() { m_pixelWidthSpinBox->blockSignals(true); m_pixelWidthSpinBox->setValue(m_imageDimensionsModel->getPixelWidth()); m_pixelWidthSpinBox->blockSignals(false); m_pixelHeightSpinBox->blockSignals(true); m_pixelHeightSpinBox->setValue(m_imageDimensionsModel->getPixelHeight()); m_pixelHeightSpinBox->blockSignals(false); const ImageSpatialUnitsEnum::Enum spatialUnits = m_imageSpatialUnitsEnumComboBox->getSelectedItem(); const ImagePixelsPerSpatialUnitsEnum::Enum pixelsPerSpatialUnit = m_imagePixelsPerSpatialUnitsEnumComboBox->getSelectedItem(); m_imageWidthSpinBox->blockSignals(true); m_imageWidthSpinBox->setValue(m_imageDimensionsModel->getSpatialWidth(spatialUnits)); m_imageWidthSpinBox->blockSignals(false); m_imageHeightSpinBox->blockSignals(true); m_imageHeightSpinBox->setValue(m_imageDimensionsModel->getSpatialHeight(spatialUnits)); m_imageHeightSpinBox->blockSignals(false); m_imageResolutionSpinBox->blockSignals(true); m_imageResolutionSpinBox->setValue(m_imageDimensionsModel->getNumberOfPixelsPerSpatialUnit(pixelsPerSpatialUnit)); m_imageResolutionSpinBox->blockSignals(false); updateImageNumberOfBytesLabel(); } /** * Update the image number of bytes label. */ void ImageCaptureDialog::updateImageNumberOfBytesLabel() { int32_t imageWidth = 0; int32_t imageHeight = 0; if (m_imageSizeCustomRadioButton->isChecked()) { imageWidth = m_pixelWidthSpinBox->value(); imageHeight = m_pixelHeightSpinBox->value(); } else if (m_imageSizeWindowRadioButton->isChecked()) { float aspectRatio = 0.0; if ( ! getSelectedWindowWidthAndHeight(imageWidth, imageHeight, aspectRatio)) { imageWidth = 0; imageHeight = 0; } } const int64_t bytesPerPixel = 3; // RGB const int64_t numberOfBytes = (static_cast(imageWidth) * static_cast(imageHeight) * bytesPerPixel); if (numberOfBytes > 0) { const AString sizeString = FileInformation::fileSizeToStandardUnits(numberOfBytes); m_imageNumberOfBytesLabel->setText(sizeString); } else { m_imageNumberOfBytesLabel->setText("Invalid/Unknown"); } } /** * Called when pixel width value is changed. * * @param value * New value. */ void ImageCaptureDialog::pixelWidthValueChanged(int value) { m_imageDimensionsModel->setPixelWidth(value, m_scaleProportionallyCheckBox->isChecked()); updateDialogWithImageDimensionsModel(); } /** * Called when pixel height value is changed. * * @param value * New value. */ void ImageCaptureDialog::pixelHeightValueChanged(int value) { m_imageDimensionsModel->setPixelHeight(value, m_scaleProportionallyCheckBox->isChecked()); updateDialogWithImageDimensionsModel(); } /** * Called when image width value is changed. * * @param value * New value. */ void ImageCaptureDialog::imageWidthValueChanged(double value) { const ImageSpatialUnitsEnum::Enum spatialUnits = m_imageSpatialUnitsEnumComboBox->getSelectedItem(); m_imageDimensionsModel->setSpatialWidth(value, spatialUnits, m_scaleProportionallyCheckBox->isChecked()); updateDialogWithImageDimensionsModel(); } /** * Called when image height value is changed. * * @param value * New value. */ void ImageCaptureDialog::imageHeightValueChanged(double value) { const ImageSpatialUnitsEnum::Enum spatialUnits = m_imageSpatialUnitsEnumComboBox->getSelectedItem(); m_imageDimensionsModel->setSpatialHeight(value, spatialUnits, m_scaleProportionallyCheckBox->isChecked()); updateDialogWithImageDimensionsModel(); } /** * Called when image resolution value changed. * * @param value * New value. */ void ImageCaptureDialog::imageResolutionValueChanged(double value) { const ImagePixelsPerSpatialUnitsEnum::Enum pixelsPerSpatialUnit = m_imagePixelsPerSpatialUnitsEnumComboBox->getSelectedItem(); m_imageDimensionsModel->setNumberOfPixelsPerSpatialUnit(value, pixelsPerSpatialUnit); updateDialogWithImageDimensionsModel(); } /** * Called when scale proportionately check box clicked. * * @parm checked * New checked status. */ void ImageCaptureDialog::scaleProportionallyCheckBoxClicked(bool checked) { if (checked) { /* * Will cause pixel height to change appropriately */ int32_t windowWidth; int32_t windowHeight; float aspectRatio; if (getSelectedWindowWidthAndHeight(windowWidth, windowHeight, aspectRatio)) { m_imageDimensionsModel->updateForAspectRatio(windowWidth, windowHeight); updateDialogWithImageDimensionsModel(); } } } /** * @return Create and return the image destination section. */ QWidget* ImageCaptureDialog::createImageDestinationSection() { m_copyImageToClipboardCheckBox = new QCheckBox("Copy to Clipboard"); m_copyImageToClipboardCheckBox->setChecked(true); m_saveImageToFileCheckBox = new QCheckBox("Save to File: " ); m_imageFileNameLineEdit = new QLineEdit(); m_imageFileNameLineEdit->setText("untitled.png"); QPushButton* fileNameSelectionPushButton = new QPushButton("Choose File..."); QObject::connect(fileNameSelectionPushButton, SIGNAL(clicked()), this, SLOT(selectImagePushButtonPressed())); QGroupBox* groupBox = new QGroupBox("Destination"); QGridLayout* gridLayout = new QGridLayout(groupBox); gridLayout->addWidget(m_copyImageToClipboardCheckBox, 0, 0, 1, 3); gridLayout->addWidget(m_saveImageToFileCheckBox, 1, 0); gridLayout->addWidget(m_imageFileNameLineEdit, 1, 1); gridLayout->addWidget(fileNameSelectionPushButton, 1, 2); return groupBox; } /** * Receive events from the event manager. * * @param event * Event sent by event manager. */ void ImageCaptureDialog::receiveEvent(Event* event) { AString windowSizeText = ""; if (event->getEventType() == EventTypeEnum::EVENT_BROWSER_WINDOW_GRAPHICS_HAVE_BEEN_REDRAWN) { EventBrowserWindowGraphicsRedrawn* graphicsRedrawnEvent = dynamic_cast(event); CaretAssert(graphicsRedrawnEvent); graphicsRedrawnEvent->setEventProcessed(); updateBrowserWindowWidthAndHeightLabel(); } } /** * Update the radio containing the size of the window. */ void ImageCaptureDialog::updateBrowserWindowWidthAndHeightLabel() { AString windowSizeText = "Size of Window"; int32_t width; int32_t height; float aspectRatio; if (getSelectedWindowWidthAndHeight(width, height, aspectRatio)) { windowSizeText += (" (" + AString::number(width) + " x " + AString::number(height) + " pixels)"); if (m_scaleProportionallyCheckBox->isChecked()) { m_imageDimensionsModel->updateForAspectRatio(width, height); } updateDialogWithImageDimensionsModel(); CaretAssert(m_imageDimensionsWidget); m_imageDimensionsWidget->setEnabled(true); } else { windowSizeText += (" (Invalid Window Number)"); CaretAssert(m_imageDimensionsWidget); m_imageDimensionsWidget->setEnabled(false); } m_imageSizeWindowRadioButton->setText(windowSizeText); } /** * Get the width and height of the selected window. * * @param widthOut * Width of window. * @param heightOut * Height of window. * @param aspectRatioOut * Aspect ratio of window (height / width) * @return * True if selected window is valid and both height and width * are greater than zero, else false. */ bool ImageCaptureDialog::getSelectedWindowWidthAndHeight(int32_t& widthOut, int32_t& heightOut, float& aspectRatioOut) const { const int selectedBrowserWindowIndex = m_windowSelectionSpinBox->value() - 1; BrainBrowserWindow* browserWindow = GuiManager::get()->getBrowserWindowByWindowIndex(selectedBrowserWindowIndex); if (browserWindow != NULL) { browserWindow->getGraphicsWidgetSize(widthOut, heightOut); if ((widthOut > 0) && (heightOut > 0)) { aspectRatioOut = (static_cast(heightOut) / static_cast(widthOut)); return true; } } return false; } /** * May be called to update the dialog's content. */ void ImageCaptureDialog::updateDialog() { updateBrowserWindowWidthAndHeightLabel(); } /** * Set the selected browser window to the browser window with the * given index. * @param browserWindowIndex * Index of browser window. */ void ImageCaptureDialog::setBrowserWindowIndex(const int32_t browserWindowIndex) { m_windowSelectionSpinBox->setValue(browserWindowIndex + 1); } /** * Called when choose file pushbutton is pressed. */ void ImageCaptureDialog::selectImagePushButtonPressed() { QString defaultFileName = m_imageFileNameLineEdit->text().trimmed(); if (defaultFileName.isEmpty()) { defaultFileName = "untitled.png"; } FileInformation fileInfo(m_imageFileNameLineEdit->text().trimmed()); if (fileInfo.isRelative()) { FileInformation absFileInfo(GuiManager::get()->getBrain()->getCurrentDirectory(), m_imageFileNameLineEdit->text().trimmed()); defaultFileName = absFileInfo.getAbsoluteFilePath(); } std::vector imageFileFilters; AString defaultFileFilter; ImageFile::getImageFileFilters(imageFileFilters, defaultFileFilter); QString filters; for (std::vector::iterator filterIterator = imageFileFilters.begin(); filterIterator != imageFileFilters.end(); filterIterator++) { if (filters.isEmpty() == false) { filters += ";;"; } filters += *filterIterator; } AString name = CaretFileDialog::getSaveFileNameDialog(this, "Choose File Name", defaultFileName, //GuiManager::get()->getBrain()->getCurrentDirectory(), filters, &defaultFileFilter); if (name.isEmpty() == false) { m_imageFileNameLineEdit->setText(name.trimmed()); } } /** * Called when the apply button is pressed. */ void ImageCaptureDialog::applyButtonClicked() { const int browserWindowIndex = m_windowSelectionSpinBox->value() - 1; /* * Zero for width/height means capture image in size of window */ int32_t imageX = 0; int32_t imageY = 0; if (m_imageSizeCustomRadioButton->isChecked()) { imageX = m_pixelWidthSpinBox->value(); imageY = m_pixelHeightSpinBox->value(); } EventImageCapture imageCaptureEvent(browserWindowIndex, imageX, imageY); EventManager::get()->sendEvent(imageCaptureEvent.getPointer()); bool errorFlag = false; if (imageCaptureEvent.getEventProcessCount() <= 0) { WuQMessageBox::errorOk(this, "Invalid window selected"); errorFlag = true; } else if (imageCaptureEvent.isError()) { WuQMessageBox::errorOk(this, imageCaptureEvent.getErrorMessage()); errorFlag = true; } if ( ! errorFlag) { uint8_t backgroundColor[3]; imageCaptureEvent.getBackgroundColor(backgroundColor); ImageFile imageFile; imageFile.setFromQImage(imageCaptureEvent.getImage()); const float pixelsPerCentimeter = m_imageDimensionsModel->getNumberOfPixelsPerSpatialUnit(ImagePixelsPerSpatialUnitsEnum::PIXEL_PER_CENTIMETER); const float pixelsPerMeter = pixelsPerCentimeter * 100; imageFile.setDotsPerMeter(pixelsPerMeter, pixelsPerMeter); if (m_imageAutoCropCheckBox->isChecked()) { const int marginSize = m_imageAutoCropMarginSpinBox->value(); imageFile.cropImageRemoveBackground(marginSize, backgroundColor); } if (m_copyImageToClipboardCheckBox->isChecked()) { QApplication::clipboard()->setImage(*imageFile.getAsQImage(), QClipboard::Clipboard); } if (m_saveImageToFileCheckBox->isChecked()) { std::vector imageFileExtensions; AString defaultFileExtension; ImageFile::getImageFileExtensions(imageFileExtensions, defaultFileExtension); AString filename = m_imageFileNameLineEdit->text().trimmed(); bool validExtension = false; for (std::vector::iterator extensionIterator = imageFileExtensions.begin(); extensionIterator != imageFileExtensions.end(); extensionIterator++) { if (filename.endsWith(*extensionIterator)) { validExtension = true; } } if (validExtension == false) { if (defaultFileExtension.isEmpty() == false) { filename += ("." + defaultFileExtension); } } try { imageFile.writeFile(filename); } catch (const DataFileException& /*e*/) { QString msg("Unable to save: " + filename); WuQMessageBox::errorOk(this, msg); errorFlag = true; } } } if (errorFlag == false) { /* * Display over "Capture" (the renamed Apply) button. */ QWidget* parent = getDialogButtonBox()->button(QDialogButtonBox::Apply); CaretAssert(parent); WuQTimedMessageDisplay::show(parent, 2.0, "Image captured"); } EventManager::get()->sendEvent(EventGraphicsUpdateOneWindow(browserWindowIndex).getPointer()); } workbench-1.1.1/src/GuiQt/ImageCaptureDialog.h000066400000000000000000000102371255417355300211700ustar00rootroot00000000000000#ifndef __IMAGE_CAPTURE_DIALOG__H_ #define __IMAGE_CAPTURE_DIALOG__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "EventListenerInterface.h" #include "WuQDialogNonModal.h" class QCheckBox; class QDoubleSpinBox; class QLabel; class QLineEdit; class QRadioButton; class QSpinBox; namespace caret { class BrainBrowserWindow; class EnumComboBoxTemplate; class ImageDimensionsModel; class WuQWidgetObjectGroup; class ImageCaptureDialog : public WuQDialogNonModal, public EventListenerInterface { Q_OBJECT public: ImageCaptureDialog(BrainBrowserWindow* parent); virtual ~ImageCaptureDialog(); void setBrowserWindowIndex(const int32_t browserWindowIndex); void updateDialog(); void receiveEvent(Event* event); protected: virtual void applyButtonClicked(); private slots: void selectImagePushButtonPressed(); void updateBrowserWindowWidthAndHeightLabel(); void imageResolutionUnitsEnumComboBoxItemActivated(); void imageSizeUnitsEnumComboBoxItemActivated(); void pixelWidthValueChanged(int); void pixelHeightValueChanged(int); void imageWidthValueChanged(double); void imageHeightValueChanged(double); void imageResolutionValueChanged(double); void scaleProportionallyCheckBoxClicked(bool); void sizeRadioButtonClicked(QAbstractButton* button); private: ImageCaptureDialog(const ImageCaptureDialog&); ImageCaptureDialog& operator=(const ImageCaptureDialog&); QWidget* createImageSourceSection(); QWidget* createImageOptionsSection(); QWidget* createImageDimensionsSection(); QWidget* createImageDestinationSection(); bool getSelectedWindowWidthAndHeight(int32_t& widthOut, int32_t& heightOut, float& aspectRatioOut) const; void updateDialogWithImageDimensionsModel(); void updateImageNumberOfBytesLabel(); QCheckBox* m_saveImageToFileCheckBox; QCheckBox* m_copyImageToClipboardCheckBox; QLineEdit* m_imageFileNameLineEdit; QRadioButton* m_imageSizeWindowRadioButton; QRadioButton* m_imageSizeCustomRadioButton; QSpinBox* m_pixelWidthSpinBox; QSpinBox* m_pixelHeightSpinBox; QDoubleSpinBox* m_imageWidthSpinBox; QDoubleSpinBox* m_imageHeightSpinBox; QDoubleSpinBox* m_imageResolutionSpinBox; QCheckBox* m_scaleProportionallyCheckBox; EnumComboBoxTemplate* m_imagePixelsPerSpatialUnitsEnumComboBox; EnumComboBoxTemplate* m_imageSpatialUnitsEnumComboBox; QLabel* m_imageNumberOfBytesLabel; QCheckBox* m_imageAutoCropCheckBox; QSpinBox* m_imageAutoCropMarginSpinBox; QSpinBox* m_windowSelectionSpinBox; QWidget* m_customDimensionsWidget; QWidget* m_imageDimensionsWidget; ImageDimensionsModel* m_imageDimensionsModel; }; #ifdef __IMAGE_CAPTURE_DIALOG__H__DECLARE__ #endif // __IMAGE_CAPTURE_DIALOG__H__DECLARE__ } // namespace #endif //__IMAGE_CAPTURE_DIALOG__H_ workbench-1.1.1/src/GuiQt/ImageSelectionViewController.cxx000066400000000000000000000276661255417355300236620ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #include #include #define __IMAGE_SELECTION_VIEW_CONTROLLER_DECLARE__ #include "ImageSelectionViewController.h" #undef __IMAGE_SELECTION_VIEW_CONTROLLER_DECLARE__ #include "Brain.h" #include "BrowserTabContent.h" #include "CaretAssert.h" #include "DisplayGroupEnumComboBox.h" #include "DisplayPropertiesImages.h" #include "EventGraphicsUpdateAllWindows.h" #include "EventManager.h" #include "EventUserInterfaceUpdate.h" #include "GuiManager.h" #include "ImageFile.h" #include "SceneClass.h" #include "SceneClassAssistant.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::ImageSelectionViewController * \brief View controller for image selection * \ingroup GuiQt */ /** * Constructor. */ ImageSelectionViewController::ImageSelectionViewController(const int32_t browserWindowIndex, QWidget* parent) : QWidget(parent), m_browserWindowIndex(browserWindowIndex) { setWindowTitle("Images"); m_sceneAssistant = new SceneClassAssistant(); QLabel* groupLabel = new QLabel("Group"); m_imagesDisplayGroupComboBox = new DisplayGroupEnumComboBox(this); QObject::connect(m_imagesDisplayGroupComboBox, SIGNAL(displayGroupSelected(const DisplayGroupEnum::Enum)), this, SLOT(imageDisplayGroupSelected(const DisplayGroupEnum::Enum))); QHBoxLayout* groupLayout = new QHBoxLayout(); groupLayout->addWidget(groupLabel); groupLayout->addWidget(m_imagesDisplayGroupComboBox->getWidget()); groupLayout->addStretch(); m_imageDisplayCheckBox = new QCheckBox("Display Image"); QObject::connect(m_imageDisplayCheckBox, SIGNAL(clicked(bool)), this, SLOT(processSelectionChanges())); m_imageRadioButtonGroup = new QButtonGroup(this); QObject::connect(m_imageRadioButtonGroup, SIGNAL(buttonClicked(int)), this, SLOT(imageRadioButtonClicked(int))); QWidget* imageRadioButtonWidget = new QWidget(); imageRadioButtonWidget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); m_imageRadioButtonLayout = new QVBoxLayout(imageRadioButtonWidget); QScrollArea* imageRadioButtonScrollArea = new QScrollArea(); imageRadioButtonScrollArea->setWidget(imageRadioButtonWidget); imageRadioButtonScrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); imageRadioButtonScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); imageRadioButtonScrollArea->setWidgetResizable(true); QVBoxLayout* layout = new QVBoxLayout(this); // WuQtUtilities::setLayoutSpacingAndMargins(layout, 2, 2); layout->addLayout(groupLayout, 0); layout->addWidget(m_imageDisplayCheckBox, 0); layout->addSpacing(10); layout->addWidget(imageRadioButtonScrollArea, 100); layout->addStretch(); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_USER_INTERFACE_UPDATE); s_allImageSelectionViewControllers.insert(this); } /** * Destructor. */ ImageSelectionViewController::~ImageSelectionViewController() { EventManager::get()->removeAllEventsFromListener(this); s_allImageSelectionViewControllers.erase(this); delete m_sceneAssistant; } /** * Receive an event. * * @param event * An event for which this instance is listening. */ void ImageSelectionViewController::receiveEvent(Event* event) { bool doUpdate = false; if (event->getEventType() == EventTypeEnum::EVENT_USER_INTERFACE_UPDATE) { EventUserInterfaceUpdate* uiEvent = dynamic_cast(event); CaretAssert(uiEvent); if (uiEvent->isUpdateForWindow(m_browserWindowIndex)) { if (uiEvent->isToolBoxUpdate()) { doUpdate = true; uiEvent->setEventProcessed(); } } } if (doUpdate) { updateImageViewController(); } } /** * Called when a selection changes. */ void ImageSelectionViewController::processSelectionChanges() { DisplayPropertiesImages* dpi = GuiManager::get()->getBrain()->getDisplayPropertiesImages(); BrowserTabContent* browserTabContent = GuiManager::get()->getBrowserTabContentForBrowserWindow(m_browserWindowIndex, true); if (browserTabContent == NULL) { return; } const int32_t browserTabIndex = browserTabContent->getTabNumber(); const DisplayGroupEnum::Enum displayGroup = dpi->getDisplayGroupForTab(browserTabIndex); dpi->setDisplayed(displayGroup, browserTabIndex, m_imageDisplayCheckBox->isChecked()); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); updateOtherImageViewControllers(); } /** * Called when the display group is changed. * * @param displayGroup * Display group selected. */ void ImageSelectionViewController::imageDisplayGroupSelected(const DisplayGroupEnum::Enum displayGroup) { /* * Update selected display group in model. */ BrowserTabContent* browserTabContent = GuiManager::get()->getBrowserTabContentForBrowserWindow(m_browserWindowIndex, false); if (browserTabContent == NULL) { return; } const int32_t browserTabIndex = browserTabContent->getTabNumber(); Brain* brain = GuiManager::get()->getBrain(); DisplayPropertiesImages* dsb = brain->getDisplayPropertiesImages(); dsb->setDisplayGroupForTab(browserTabIndex, displayGroup); /* * Since display group has changed, need to update controls */ updateImageViewController(); } /** * Called when an image radio button is clicked. * * @param buttonID * ID of button that was clicked. */ void ImageSelectionViewController::imageRadioButtonClicked(int buttonID) { BrowserTabContent* browserTabContent = GuiManager::get()->getBrowserTabContentForBrowserWindow(m_browserWindowIndex, true); if (browserTabContent == NULL) { return; } const int32_t browserTabIndex = browserTabContent->getTabNumber(); Brain* brain = GuiManager::get()->getBrain(); DisplayPropertiesImages* dpi = brain->getDisplayPropertiesImages(); const DisplayGroupEnum::Enum displayGroup = dpi->getDisplayGroupForTab(browserTabIndex); std::vector allImageFiles = brain->getAllImagesFiles(); CaretAssertVectorIndex(allImageFiles, buttonID); dpi->setSelectedImageFile(displayGroup, browserTabIndex, allImageFiles[buttonID]); updateOtherImageViewControllers(); } /** * Update the image selection widget. */ void ImageSelectionViewController::updateImageViewController() { BrowserTabContent* browserTabContent = GuiManager::get()->getBrowserTabContentForBrowserWindow(m_browserWindowIndex, true); if (browserTabContent == NULL) { return; } const int32_t browserTabIndex = browserTabContent->getTabNumber(); Brain* brain = GuiManager::get()->getBrain(); DisplayPropertiesImages* dpi = brain->getDisplayPropertiesImages(); const DisplayGroupEnum::Enum displayGroup = dpi->getDisplayGroupForTab(browserTabIndex); m_imagesDisplayGroupComboBox->setSelectedDisplayGroup(dpi->getDisplayGroupForTab(browserTabIndex)); std::vector allImageFiles = brain->getAllImagesFiles(); m_imageDisplayCheckBox->setChecked(dpi->isDisplayed(displayGroup, browserTabIndex)); const int32_t numImageFiles = static_cast(allImageFiles.size()); int32_t numRadioButtons = static_cast(m_imageRadioButtons.size()); if (numImageFiles > numRadioButtons) { const int32_t numToAdd = numImageFiles - numRadioButtons; for (int32_t i = 0; i < numToAdd; i++) { const int buttonID = static_cast(m_imageRadioButtons.size()); QRadioButton* rb = new QRadioButton(""); m_imageRadioButtons.push_back(rb); m_imageRadioButtonLayout->addWidget(rb); m_imageRadioButtonGroup->addButton(rb, buttonID); } numRadioButtons = static_cast(m_imageRadioButtons.size()); } const ImageFile* selectedImageFile = dpi->getSelectedImageFile(displayGroup, browserTabIndex); for (int32_t i = 0; i < numRadioButtons; i++) { QRadioButton* rb = m_imageRadioButtons[i]; if (i < numImageFiles) { rb->setText(allImageFiles[i]->getFileNameNoPath()); if (allImageFiles[i] == selectedImageFile) { rb->setChecked(true); } rb->setVisible(true); } else { rb->setVisible(false); } } } /** * Update other selection toolbox since they should all be the same. */ void ImageSelectionViewController::updateOtherImageViewControllers() { for (std::set::iterator iter = s_allImageSelectionViewControllers.begin(); iter != s_allImageSelectionViewControllers.end(); iter++) { ImageSelectionViewController* ivc = *iter; if (ivc != this) { ivc->updateImageViewController(); } } } /** * Save information specific to this type of model to the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @param instanceName * Name of instance in the scene. */ SceneClass* ImageSelectionViewController::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "ImageSelectionViewController", 1); m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); // Uncomment if sub-classes must save to scene //saveSubClassDataToScene(sceneAttributes, // sceneClass); return sceneClass; } /** * Restore information specific to the type of model from the scene. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass from which model specific information is obtained. */ void ImageSelectionViewController::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); //Uncomment if sub-classes must restore from scene //restoreSubClassDataFromScene(sceneAttributes, // sceneClass); } workbench-1.1.1/src/GuiQt/ImageSelectionViewController.h000066400000000000000000000074251255417355300232760ustar00rootroot00000000000000#ifndef __IMAGE_SELECTION_VIEW_CONTROLLER_H__ #define __IMAGE_SELECTION_VIEW_CONTROLLER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include "DisplayGroupEnum.h" #include "EventListenerInterface.h" #include "SceneableInterface.h" class QButtonGroup; class QCheckBox; class QRadioButton; class QVBoxLayout; namespace caret { class DisplayGroupEnumComboBox; class SceneClassAssistant; class ImageSelectionViewController : public QWidget, public EventListenerInterface, public SceneableInterface { Q_OBJECT public: ImageSelectionViewController(const int32_t browserWindowIndex, QWidget* parent = 0); virtual ~ImageSelectionViewController(); // ADD_NEW_METHODS_HERE virtual void receiveEvent(Event* event); virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private slots: void processSelectionChanges(); void imageDisplayGroupSelected(const DisplayGroupEnum::Enum); void imageRadioButtonClicked(int); // If there will be sub-classes of this class that need to save // and restore data from scenes, these pure virtual methods can // be uncommented to force their implemetation by sub-classes. // protected: // virtual void saveSubClassDataToScene(const SceneAttributes* sceneAttributes, // SceneClass* sceneClass) = 0; // // virtual void restoreSubClassDataFromScene(const SceneAttributes* sceneAttributes, // const SceneClass* sceneClass) = 0; private: ImageSelectionViewController(const ImageSelectionViewController&); ImageSelectionViewController& operator=(const ImageSelectionViewController&); void updateOtherImageViewControllers(); void updateImageViewController(); const int32_t m_browserWindowIndex; SceneClassAssistant* m_sceneAssistant; DisplayGroupEnumComboBox* m_imagesDisplayGroupComboBox; QCheckBox* m_imageDisplayCheckBox; std::vector m_imageRadioButtons; QButtonGroup* m_imageRadioButtonGroup; QVBoxLayout* m_imageRadioButtonLayout; static std::set s_allImageSelectionViewControllers; // ADD_NEW_MEMBERS_HERE }; #ifdef __IMAGE_SELECTION_VIEW_CONTROLLER_DECLARE__ std::set ImageSelectionViewController::s_allImageSelectionViewControllers; #endif // __IMAGE_SELECTION_VIEW_CONTROLLER_DECLARE__ } // namespace #endif //__IMAGE_SELECTION_VIEW_CONTROLLER_H__ workbench-1.1.1/src/GuiQt/InformationDisplayDialog.cxx000066400000000000000000000254161255417355300230150ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #define __INFORMATION_DISPLAY_DIALOG_DECLARE__ #include "InformationDisplayDialog.h" #undef __INFORMATION_DISPLAY_DIALOG_DECLARE__ #include "Brain.h" #include "BrainBrowserWindow.h" #include "EventGraphicsUpdateAllWindows.h" #include "EventUserInterfaceUpdate.h" #include "EventUpdateInformationWindows.h" #include "EventManager.h" #include "GuiManager.h" #include "HyperLinkTextBrowser.h" #include "IdentificationManager.h" #include "InformationDisplayPropertiesDialog.h" #include "SceneClass.h" #include "SceneWindowGeometry.h" #include "SelectionItemSurfaceNode.h" #include "SelectionManager.h" #include "StructureEnumComboBox.h" #include "Surface.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::InformationDisplayDialog * \brief Dialog for display of information. * \ingroup GuiQt * */ /** * Constructor. */ InformationDisplayDialog::InformationDisplayDialog(BrainBrowserWindow* parent) : WuQDialogNonModal("Information", parent) { this->setDeleteWhenClosed(false); /* * No apply button */ this->setApplyButtonText(""); m_propertiesDialog = NULL; m_informationTextBrowser = new HyperLinkTextBrowser(); m_informationTextBrowser->setLineWrapMode(QTextEdit::NoWrap); m_informationTextBrowser->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum)); QAction* clearAction = WuQtUtilities::createAction("Clear", "Clear contents of information display", this, this, SLOT(clearInformationText())); m_contralateralIdentificationAction = WuQtUtilities::createAction("Contra ID", "Enable contralateral identification", this, this, SLOT(contralateralIdentificationToggled(bool))); m_contralateralIdentificationAction->setCheckable(true); QAction* copyAction = WuQtUtilities::createAction("Copy", "Copy selection from information display", this, m_informationTextBrowser, SLOT(copy())); QAction* removeIdSymbolAction = WuQtUtilities::createAction("RID", "Remove All ID symbols", this, this, SLOT(removeIdSymbols())); QAction* settingsAction = WuQtUtilities::createAction("Properties", "Displays dialog for changing ID symbol colors and size", this, this, SLOT(showPropertiesDialog())); QObject::connect(m_informationTextBrowser, SIGNAL(copyAvailable(bool)), copyAction, SLOT(setEnabled(bool))); copyAction->setEnabled(false); QToolBar* idToolBarLeft = new QToolBar(); idToolBarLeft->setOrientation(Qt::Vertical); idToolBarLeft->setFloatable(false); idToolBarLeft->setMovable(false); idToolBarLeft->addAction(clearAction); idToolBarLeft->addSeparator(); idToolBarLeft->addAction(copyAction); idToolBarLeft->addSeparator(); QToolBar* idToolBarRight = new QToolBar(); idToolBarRight->setOrientation(Qt::Vertical); idToolBarRight->setFloatable(false); idToolBarRight->setMovable(false); idToolBarRight->addAction(removeIdSymbolAction); idToolBarRight->addSeparator(); idToolBarRight->addAction(m_contralateralIdentificationAction); idToolBarRight->addSeparator(); idToolBarRight->addAction(settingsAction); idToolBarRight->addSeparator(); QWidget* widget = new QWidget(); QHBoxLayout* layout = new QHBoxLayout(widget); WuQtUtilities::setLayoutSpacingAndMargins(layout, 0, 0); layout->addWidget(idToolBarLeft); layout->addWidget(m_informationTextBrowser); layout->addWidget(idToolBarRight); layout->setStretchFactor(idToolBarLeft, 0); layout->setStretchFactor(m_informationTextBrowser, 100); layout->setStretchFactor(idToolBarRight, 0); /* * Use processed event listener since the text event * is first processed by GuiManager which will create * this dialog, if needed, and then display it. */ EventManager::get()->addProcessedEventListener(this, EventTypeEnum::EVENT_UPDATE_INFORMATION_WINDOWS); this->setCentralWidget(widget, WuQDialog::SCROLL_AREA_NEVER); /* * There may already be identification text, so try to display it. */ updateDialog(); } /** * Destructor. */ InformationDisplayDialog::~InformationDisplayDialog() { EventManager::get()->removeAllEventsFromListener(this); } /** * Update the dialog's content. */ void InformationDisplayDialog::updateDialog() { Brain* brain = GuiManager::get()->getBrain(); IdentificationManager* idManager = brain->getIdentificationManager(); const AString text = idManager->getIdentificationText(); m_informationTextBrowser->setContentToHtml(text); m_contralateralIdentificationAction->blockSignals(true); m_contralateralIdentificationAction->setChecked(idManager->isContralateralIdentificationEnabled()); m_contralateralIdentificationAction->blockSignals(false); } /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ SceneClass* InformationDisplayDialog::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "InformationDisplayDialog", 1); /* * Position and size */ SceneWindowGeometry swg(this); sceneClass->addClass(swg.saveToScene(sceneAttributes, "geometry")); return sceneClass; } /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * SceneClass containing the state that was previously * saved and should be restored. */ void InformationDisplayDialog::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } /* * Position and size */ SceneWindowGeometry swg(this); swg.restoreFromScene(sceneAttributes, sceneClass->getClass("geometry")); } /** * Called when contralateral toolbutton is toggled. */ void InformationDisplayDialog::contralateralIdentificationToggled(bool) { Brain* brain = GuiManager::get()->getBrain(); IdentificationManager* idManager = brain->getIdentificationManager(); idManager->setContralateralIdentificationEnabled(m_contralateralIdentificationAction->isChecked()); } /** * Clear the information text. */ void InformationDisplayDialog::clearInformationText() { Brain* brain = GuiManager::get()->getBrain(); IdentificationManager* idManager = brain->getIdentificationManager(); idManager->removeIdentificationText(); updateDialog(); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } /** * Remove ID symbols from all surfaces. */ void InformationDisplayDialog::removeIdSymbols() { Brain* brain = GuiManager::get()->getBrain(); IdentificationManager* idManager = brain->getIdentificationManager(); idManager->removeAllIdentifiedSymbols(); updateDialog(); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } /** * Receive events from the event manager. * * @param event * Event sent by event manager. */ void InformationDisplayDialog::receiveEvent(Event* event) { bool doUpdate = false; if (event->getEventType() == EventTypeEnum::EVENT_UPDATE_INFORMATION_WINDOWS) { EventUpdateInformationWindows* textEvent = dynamic_cast(event); CaretAssert(textEvent); textEvent->setEventProcessed(); doUpdate = true; } else if (event->getEventType() == EventTypeEnum::EVENT_USER_INTERFACE_UPDATE) { EventUserInterfaceUpdate* uiUpdateEvent = dynamic_cast(event); CaretAssert(uiUpdateEvent); uiUpdateEvent->setEventProcessed(); doUpdate = true; } if (doUpdate) { updateDialog(); } } /** * Show the symbol properties dialog */ void InformationDisplayDialog::showPropertiesDialog() { if (m_propertiesDialog == NULL) { m_propertiesDialog = new InformationDisplayPropertiesDialog(this); } m_propertiesDialog->show(); } workbench-1.1.1/src/GuiQt/InformationDisplayDialog.h000066400000000000000000000052661255417355300224430ustar00rootroot00000000000000#ifndef __INFORMATION_DISPLAY_DIALOG__H_ #define __INFORMATION_DISPLAY_DIALOG__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "EventListenerInterface.h" #include "SceneableInterface.h" #include "WuQDialogNonModal.h" namespace caret { class BrainBrowserWindow; class HyperLinkTextBrowser; class InformationDisplayPropertiesDialog; class InformationDisplayDialog : public WuQDialogNonModal, public EventListenerInterface, public SceneableInterface { Q_OBJECT public: InformationDisplayDialog(BrainBrowserWindow* parent); virtual ~InformationDisplayDialog(); void receiveEvent(Event* event); virtual void updateDialog(); virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private slots: void clearInformationText(); void removeIdSymbols(); void contralateralIdentificationToggled(bool); //void volumeSliceIdentificationToggled(bool); void showPropertiesDialog(); private: InformationDisplayDialog(const InformationDisplayDialog&); InformationDisplayDialog& operator=(const InformationDisplayDialog&); HyperLinkTextBrowser* m_informationTextBrowser; QAction* m_contralateralIdentificationAction; QString m_informationText; InformationDisplayPropertiesDialog* m_propertiesDialog; }; #ifdef __INFORMATION_DISPLAY_DIALOG_DECLARE__ // #endif // __INFORMATION_DISPLAY_DIALOG_DECLARE__ } // namespace #endif //__INFORMATION_DISPLAY_DIALOG__H_ workbench-1.1.1/src/GuiQt/InformationDisplayPropertiesDialog.cxx000066400000000000000000000160331255417355300250650ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __INFORMATION_DISPLAY_OPTIONS_DIALOG_DECLARE__ #include "InformationDisplayPropertiesDialog.h" #undef __INFORMATION_DISPLAY_OPTIONS_DIALOG_DECLARE__ #include #include #include #include "Brain.h" #include "CaretAssert.h" #include "CaretColorEnumComboBox.h" #include "EventGraphicsUpdateAllWindows.h" #include "EventManager.h" #include "GuiManager.h" #include "IdentificationManager.h" #include "WuQFactory.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::InformationDisplayPropertiesDialog * \brief Options for information dialog display. * \ingroup GuiQt */ /** * Constructor. */ InformationDisplayPropertiesDialog::InformationDisplayPropertiesDialog(QWidget* parent, Qt::WindowFlags f) : WuQDialogNonModal("Information Properties", parent, f) { const int WIDGET_WIDTH = 100; QLabel* idColorLabel = new QLabel("ID Symbol Color: "); m_idColorComboBox = new CaretColorEnumComboBox(this); m_idColorComboBox->getWidget()->setFixedWidth(WIDGET_WIDTH); QObject::connect(m_idColorComboBox, SIGNAL(colorSelected(const CaretColorEnum::Enum)), this, SLOT(informationPropertyChanged())); QLabel* idContralateralLabel = new QLabel("ID Contralateral Symbol Color: "); m_idContralateralColorComboBox = new CaretColorEnumComboBox(this); m_idContralateralColorComboBox->getWidget()->setFixedWidth(WIDGET_WIDTH); QObject::connect(m_idContralateralColorComboBox, SIGNAL(colorSelected(const CaretColorEnum::Enum)), this, SLOT(informationPropertyChanged())); QLabel* symbolSizeLabel = new QLabel("Symbol Diameter: "); m_symbolSizeSpinBox = WuQFactory::newDoubleSpinBoxWithMinMaxStepDecimalsSignalDouble(0.1, 10000.0, 0.1, 1, this, SLOT(informationPropertyChanged())); m_symbolSizeSpinBox->setFixedWidth(WIDGET_WIDTH); m_symbolSizeSpinBox->setSuffix("mm"); QObject::connect(m_symbolSizeSpinBox, SIGNAL(valueChanged(double)), this, SLOT(informationPropertyChanged())); QLabel* mostRecentSymbolSizeLabel = new QLabel("Most Recent ID Symbol Diameter: "); m_mostRecentSymbolSizeSpinBox = WuQFactory::newDoubleSpinBoxWithMinMaxStepDecimalsSignalDouble(0.1, 10000.0, 0.1, 1, this, SLOT(informationPropertyChanged())); m_mostRecentSymbolSizeSpinBox->setFixedWidth(WIDGET_WIDTH); m_mostRecentSymbolSizeSpinBox->setSuffix("mm"); QObject::connect(m_mostRecentSymbolSizeSpinBox, SIGNAL(valueChanged(double)), this, SLOT(informationPropertyChanged())); QLabel* prefInfoLabel = new QLabel("Display of ID symbols is enabled/disabled on the Preference Dialog"); prefInfoLabel->setWordWrap(true); const int COLUMN_LABEL = 0; const int COLUMN_WIDGET = 1; QWidget* widget = new QWidget(); QGridLayout* gridLayout = new QGridLayout(widget); int row = 0; gridLayout->addWidget(idColorLabel, row, COLUMN_LABEL); gridLayout->addWidget(m_idColorComboBox->getWidget(), row, COLUMN_WIDGET); row++; gridLayout->addWidget(idContralateralLabel, row, COLUMN_LABEL); gridLayout->addWidget(m_idContralateralColorComboBox->getWidget(), row, COLUMN_WIDGET); row++; gridLayout->addWidget(symbolSizeLabel, row, COLUMN_LABEL); gridLayout->addWidget(m_symbolSizeSpinBox, row, COLUMN_WIDGET); row++; gridLayout->addWidget(mostRecentSymbolSizeLabel, row, COLUMN_LABEL); gridLayout->addWidget(m_mostRecentSymbolSizeSpinBox, row, COLUMN_WIDGET); row++; gridLayout->addWidget(WuQtUtilities::createHorizontalLineWidget(), row, COLUMN_LABEL, 1, 2); row++; gridLayout->addWidget(prefInfoLabel, row, COLUMN_LABEL, 1, 2); row++; setCentralWidget(widget, WuQDialog::SCROLL_AREA_NEVER); setApplyButtonText(""); setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); updateDialog(); setSaveWindowPositionForNextTime(true); } /** * Destructor. */ InformationDisplayPropertiesDialog::~InformationDisplayPropertiesDialog() { } /** * update its content */ void InformationDisplayPropertiesDialog::updateDialog() { Brain* brain = GuiManager::get()->getBrain(); IdentificationManager* info = brain->getIdentificationManager(); m_idColorComboBox->setSelectedColor(info->getIdentificationSymbolColor()); m_idContralateralColorComboBox->setSelectedColor(info->getIdentificationContralateralSymbolColor()); m_symbolSizeSpinBox->blockSignals(true); m_symbolSizeSpinBox->setValue(info->getIdentificationSymbolSize()); m_symbolSizeSpinBox->blockSignals(false); m_mostRecentSymbolSizeSpinBox->blockSignals(true); m_mostRecentSymbolSizeSpinBox->setValue(info->getMostRecentIdentificationSymbolSize()); m_mostRecentSymbolSizeSpinBox->blockSignals(false); } /** * Gets called when a property changes. */ void InformationDisplayPropertiesDialog::informationPropertyChanged() { Brain* brain = GuiManager::get()->getBrain(); IdentificationManager* info = brain->getIdentificationManager(); info->setIdentificationSymbolColor(m_idColorComboBox->getSelectedColor()); info->setIdentificationContralateralSymbolColor(m_idContralateralColorComboBox->getSelectedColor()); info->setIdentificationSymbolSize(m_symbolSizeSpinBox->value()); info->setMostRecentIdentificationSymbolSize(m_mostRecentSymbolSizeSpinBox->value()); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } workbench-1.1.1/src/GuiQt/InformationDisplayPropertiesDialog.h000066400000000000000000000042731255417355300245150ustar00rootroot00000000000000#ifndef __INFORMATION_DISPLAY_OPTIONS_DIALOG_H__ #define __INFORMATION_DISPLAY_OPTIONS_DIALOG_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "WuQDialogNonModal.h" #include namespace caret { class CaretColorEnumComboBox; class InformationDisplayPropertiesDialog : public WuQDialogNonModal { Q_OBJECT public: InformationDisplayPropertiesDialog(QWidget* parent, Qt::WindowFlags f = 0); virtual ~InformationDisplayPropertiesDialog(); void updateDialog(); // ADD_NEW_METHODS_HERE private slots: void informationPropertyChanged(); private: InformationDisplayPropertiesDialog(const InformationDisplayPropertiesDialog&); InformationDisplayPropertiesDialog& operator=(const InformationDisplayPropertiesDialog&); CaretColorEnumComboBox* m_idColorComboBox; CaretColorEnumComboBox* m_idContralateralColorComboBox; QDoubleSpinBox* m_symbolSizeSpinBox; QDoubleSpinBox* m_mostRecentSymbolSizeSpinBox; // ADD_NEW_MEMBERS_HERE }; #ifdef __INFORMATION_DISPLAY_OPTIONS_DIALOG_DECLARE__ // #endif // __INFORMATION_DISPLAY_OPTIONS_DIALOG_DECLARE__ } // namespace #endif //__INFORMATION_DISPLAY_OPTIONS_DIALOG_H__ workbench-1.1.1/src/GuiQt/LabelDrawingTypeComboBox.cxx000066400000000000000000000025001255417355300226750ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __LABEL_DRAWING_TYPE_COMBO_BOX_DECLARE__ #include "LabelDrawingTypeComboBox.h" #undef __LABEL_DRAWING_TYPE_COMBO_BOX_DECLARE__ using namespace caret; /** * \class caret::LabelDrawingTypeComboBox * \brief * * */ /** * Constructor. */ LabelDrawingTypeComboBox::LabelDrawingTypeComboBox() : EnumComboBoxTemplate() { } /** * Destructor. */ LabelDrawingTypeComboBox::~LabelDrawingTypeComboBox() { } workbench-1.1.1/src/GuiQt/LabelDrawingTypeComboBox.h000066400000000000000000000031631255417355300223300ustar00rootroot00000000000000#ifndef __LABEL_DRAWING_TYPE_COMBO_BOX__H_ #define __LABEL_DRAWING_TYPE_COMBO_BOX__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "EnumComboBoxTemplate.h" namespace caret { class LabelDrawingTypeComboBox : public EnumComboBoxTemplate { public: LabelDrawingTypeComboBox(); virtual ~LabelDrawingTypeComboBox(); private: LabelDrawingTypeComboBox(const LabelDrawingTypeComboBox&); LabelDrawingTypeComboBox& operator=(const LabelDrawingTypeComboBox&); public: // ADD_NEW_METHODS_HERE private: // ADD_NEW_MEMBERS_HERE }; #ifdef __LABEL_DRAWING_TYPE_COMBO_BOX_DECLARE__ // #endif // __LABEL_DRAWING_TYPE_COMBO_BOX_DECLARE__ } // namespace #endif //__LABEL_DRAWING_TYPE_COMBO_BOX__H_ workbench-1.1.1/src/GuiQt/LabelSelectionViewController.cxx000066400000000000000000000246301255417355300236430ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #include #include //#include #include #define __LABEL_SELECTION_VIEW_CONTROLLER_DECLARE__ #include "LabelSelectionViewController.h" #undef __LABEL_SELECTION_VIEW_CONTROLLER_DECLARE__ #include "Brain.h" #include "BrainOpenGL.h" #include "BrainStructure.h" #include "BrowserTabContent.h" #include "CaretAssert.h" #include "GroupAndNameHierarchyViewController.h" #include "DisplayGroupEnumComboBox.h" #include "DisplayPropertiesLabels.h" #include "EventGraphicsUpdateAllWindows.h" #include "EventManager.h" #include "EventSurfaceColoringInvalidate.h" #include "EventUserInterfaceUpdate.h" #include "GuiManager.h" #include "SceneClass.h" #include "VolumeFile.h" #include "WuQDataEntryDialog.h" #include "WuQTabWidget.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::LabelSelectionViewController * \brief Widget for controlling display of labels * \ingroup GuiQt * * Widget for controlling the display of labels including * different display groups. */ /** * Constructor. */ LabelSelectionViewController::LabelSelectionViewController(const int32_t browserWindowIndex, QWidget* parent) : QWidget(parent) { m_browserWindowIndex = browserWindowIndex; QLabel* groupLabel = new QLabel("Group"); m_labelsDisplayGroupComboBox = new DisplayGroupEnumComboBox(this); QObject::connect(m_labelsDisplayGroupComboBox, SIGNAL(displayGroupSelected(const DisplayGroupEnum::Enum)), this, SLOT(labelDisplayGroupSelected(const DisplayGroupEnum::Enum))); QHBoxLayout* groupLayout = new QHBoxLayout(); groupLayout->addWidget(groupLabel); groupLayout->addWidget(m_labelsDisplayGroupComboBox->getWidget()); groupLayout->addStretch(); QWidget* selectionWidget = this->createSelectionWidget(); QVBoxLayout* layout = new QVBoxLayout(this); layout->addLayout(groupLayout); layout->addWidget(selectionWidget, 0, Qt::AlignLeft); layout->addStretch(); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_USER_INTERFACE_UPDATE); LabelSelectionViewController::allLabelSelectionViewControllers.insert(this); } /** * Destructor. */ LabelSelectionViewController::~LabelSelectionViewController() { EventManager::get()->removeAllEventsFromListener(this); LabelSelectionViewController::allLabelSelectionViewControllers.erase(this); } QWidget* LabelSelectionViewController::createSelectionWidget() { m_labelClassNameHierarchyViewController = new GroupAndNameHierarchyViewController(m_browserWindowIndex); return m_labelClassNameHierarchyViewController; } /** * Called when the label display group combo box is changed. */ void LabelSelectionViewController::labelDisplayGroupSelected(const DisplayGroupEnum::Enum displayGroup) { /* * Update selected display group in model. */ BrowserTabContent* browserTabContent = GuiManager::get()->getBrowserTabContentForBrowserWindow(m_browserWindowIndex, false); if (browserTabContent == NULL) { return; } const int32_t browserTabIndex = browserTabContent->getTabNumber(); Brain* brain = GuiManager::get()->getBrain(); DisplayPropertiesLabels* dsb = brain->getDisplayPropertiesLabels(); dsb->setDisplayGroupForTab(browserTabIndex, displayGroup); /* * Since display group has changed, need to update controls */ updateLabelViewController(); /* * Apply the changes. */ processLabelSelectionChanges(); } /** * Update the label selection widget. */ void LabelSelectionViewController::updateLabelViewController() { setWindowTitle("Labels"); BrowserTabContent* browserTabContent = GuiManager::get()->getBrowserTabContentForBrowserWindow(m_browserWindowIndex, true); if (browserTabContent == NULL) { return; } const int32_t browserTabIndex = browserTabContent->getTabNumber(); Brain* brain = GuiManager::get()->getBrain(); DisplayPropertiesLabels* dpb = brain->getDisplayPropertiesLabels(); const DisplayGroupEnum::Enum displayGroup = dpb->getDisplayGroupForTab(browserTabIndex); m_labelsDisplayGroupComboBox->setSelectedDisplayGroup(dpb->getDisplayGroupForTab(browserTabIndex)); /* * Get all of label files. */ std::vector allLabelFiles; const int numBrainStructures = brain->getNumberOfBrainStructures(); for (int32_t ibs = 0; ibs < numBrainStructures; ibs++) { BrainStructure* brainStructure = brain->getBrainStructure(ibs); const int32_t numLabelFiles = brainStructure->getNumberOfLabelFiles(); for (int32_t ilf = 0; ilf < numLabelFiles; ilf++) { allLabelFiles.push_back(brainStructure->getLabelFile(ilf)); } } /* * Get all CIFTI label files */ std::vector allCiftiLabelFiles; const int32_t numCiftiLabelFiles = brain->getNumberOfConnectivityDenseLabelFiles(); for (int32_t iclf = 0; iclf < numCiftiLabelFiles; iclf++) { allCiftiLabelFiles.push_back(brain->getConnectivityDenseLabelFile(iclf)); } /* * Get all Volume Files that are mapped with label tables */ std::vector allVolumeLabelFiles; const int32_t numVolumeFiles = brain->getNumberOfVolumeFiles(); for (int32_t iVol = 0; iVol < numVolumeFiles; iVol++) { VolumeFile* vf = brain->getVolumeFile(iVol); if (vf->isMappedWithLabelTable()) { allVolumeLabelFiles.push_back(vf); } } /* * Update the class/name hierarchy */ m_labelClassNameHierarchyViewController->updateContents(allLabelFiles, allCiftiLabelFiles, allVolumeLabelFiles, displayGroup); } /** * Update other selection toolbox since they should all be the same. */ void LabelSelectionViewController::updateOtherLabelViewControllers() { for (std::set::iterator iter = LabelSelectionViewController::allLabelSelectionViewControllers.begin(); iter != LabelSelectionViewController::allLabelSelectionViewControllers.end(); iter++) { LabelSelectionViewController* bsw = *iter; if (bsw != this) { bsw->updateLabelViewController(); } } } /** * Gets called when label selections are changed. */ void LabelSelectionViewController::processLabelSelectionChanges() { BrowserTabContent* browserTabContent = GuiManager::get()->getBrowserTabContentForBrowserWindow(m_browserWindowIndex, false); CaretAssert(browserTabContent); const int32_t browserTabIndex = browserTabContent->getTabNumber(); Brain* brain = GuiManager::get()->getBrain(); DisplayPropertiesLabels* dsb = brain->getDisplayPropertiesLabels(); dsb->setDisplayGroupForTab(browserTabIndex, m_labelsDisplayGroupComboBox->getSelectedDisplayGroup()); processSelectionChanges(); } /** * Issue update events after selections are changed. */ void LabelSelectionViewController::processSelectionChanges() { updateOtherLabelViewControllers(); EventManager::get()->sendEvent(EventSurfaceColoringInvalidate().getPointer()); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } /** * Receive events from the event manager. * * @param event * Event sent by event manager. */ void LabelSelectionViewController::receiveEvent(Event* event) { bool doUpdate = false; if (event->getEventType() == EventTypeEnum::EVENT_USER_INTERFACE_UPDATE) { EventUserInterfaceUpdate* uiEvent = dynamic_cast(event); CaretAssert(uiEvent); if (uiEvent->isUpdateForWindow(m_browserWindowIndex)) { if (uiEvent->isToolBoxUpdate()) { doUpdate = true; uiEvent->setEventProcessed(); } } } if (doUpdate) { updateLabelViewController(); } } /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ SceneClass* LabelSelectionViewController::saveToScene(const SceneAttributes* /*sceneAttributes*/, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "LabelSelectionViewController", 1); return sceneClass; } /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * SceneClass containing the state that was previously * saved and should be restored. */ void LabelSelectionViewController::restoreFromScene(const SceneAttributes* /*sceneAttributes*/, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } } workbench-1.1.1/src/GuiQt/LabelSelectionViewController.h000066400000000000000000000061321255417355300232650ustar00rootroot00000000000000#ifndef __LABEL_SELECTION_VIEW_CONTROLLER__H_ #define __LABEL_SELECTION_VIEW_CONTROLLER__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include "DisplayGroupEnum.h" #include "EventListenerInterface.h" #include "SceneableInterface.h" class QCheckBox; namespace caret { class GroupAndNameHierarchyViewController; class DisplayGroupEnumComboBox; class LabelSelectionViewController : public QWidget, public EventListenerInterface, public SceneableInterface { Q_OBJECT public: LabelSelectionViewController(const int32_t browserWindowIndex, QWidget* parent = 0); virtual ~LabelSelectionViewController(); void receiveEvent(Event* event); virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private slots: void processLabelSelectionChanges(); void processSelectionChanges(); void labelDisplayGroupSelected(const DisplayGroupEnum::Enum); private: LabelSelectionViewController(const LabelSelectionViewController&); LabelSelectionViewController& operator=(const LabelSelectionViewController&); void updateLabelViewController(); void updateOtherLabelViewControllers(); QWidget* createSelectionWidget(); int32_t m_browserWindowIndex; GroupAndNameHierarchyViewController* m_labelClassNameHierarchyViewController; QCheckBox* m_labelsDisplayCheckBox; QCheckBox* m_labelsContralateralCheckBox; DisplayGroupEnumComboBox* m_labelsDisplayGroupComboBox; static std::set allLabelSelectionViewControllers; }; #ifdef __LABEL_SELECTION_VIEW_CONTROLLER_DECLARE__ std::set LabelSelectionViewController::allLabelSelectionViewControllers; #endif // __LABEL_SELECTION_VIEW_CONTROLLER_DECLARE__ } // namespace #endif //__LABEL_SELECTION_VIEW_CONTROLLER__H_ workbench-1.1.1/src/GuiQt/MacApplication.cxx000066400000000000000000000046521255417355300207450ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __MAC_APPLICATION_DECLARE__ #include "MacApplication.h" #undef __MAC_APPLICATION_DECLARE__ #include #include #include #include "CaretAssert.h" #include "EventManager.h" #include "EventOperatingSystemRequestOpenDataFile.h" using namespace caret; /** * \class caret::MacApplication * \brief Subclass of QApplication so that Macs can receive file open events * \ingroup GuiQt * * Allows user to open a spec file via Mac Finder. * * Based upon: http://www.qtcentre.org/wiki/index.php?title=Opening_documents_in_the_Mac_OS_X_Finder&printable=yes&useskin=vector */ /** * Constructor. */ MacApplication::MacApplication(int& argc, char** argv) : QApplication(argc, argv) { } /** * Destructor. */ MacApplication::~MacApplication() { } /** * Receive events and processes them. * * @param event * The event. * @return true if the event was processed, else false. */ bool MacApplication::event(QEvent *event) { bool eventWasProcessed = false; switch (event->type()) { case QEvent::FileOpen: { QFileOpenEvent* openFileEvent = dynamic_cast(event); CaretAssert(openFileEvent); const QString filename = openFileEvent->file(); EventManager::get()->sendEvent(EventOperatingSystemRequestOpenDataFile(filename).getPointer()); eventWasProcessed = true; } break; default: eventWasProcessed = QApplication::event(event); break; } return eventWasProcessed; } workbench-1.1.1/src/GuiQt/MacApplication.h000066400000000000000000000033161255417355300203660ustar00rootroot00000000000000#ifndef __MAC_APPLICATION_H__ #define __MAC_APPLICATION_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ /* * Based upon: http://www.qtcentre.org/wiki/index.php?title=Opening_documents_in_the_Mac_OS_X_Finder&printable=yes&useskin=vector */ #include namespace caret { class MacApplication : public QApplication { Q_OBJECT public: MacApplication(int& argc, char** argv); virtual ~MacApplication(); private: MacApplication(const MacApplication&); MacApplication& operator=(const MacApplication&); public: // ADD_NEW_METHODS_HERE protected: virtual bool event(QEvent *event); private: // ADD_NEW_MEMBERS_HERE }; #ifdef __MAC_APPLICATION_DECLARE__ // #endif // __MAC_APPLICATION_DECLARE__ } // namespace #endif //__MAC_APPLICATION_H__ workbench-1.1.1/src/GuiQt/MacDockMenu.cxx000066400000000000000000000173161255417355300202100ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __MAC_DOCK_MENU_DECLARE__ #include "MacDockMenu.h" #undef __MAC_DOCK_MENU_DECLARE__ #include "Brain.h" #include "BrainBrowserWindow.h" #include "CaretAssert.h" #include "EventBrowserWindowNew.h" #include "EventGraphicsUpdateOneWindow.h" #include "EventManager.h" #include "EventUserInterfaceUpdate.h" #include "GuiManager.h" #include "SpecFile.h" using namespace caret; #ifdef CARET_OS_MACOSX /* * Not in Qt Header Files. * See http://qt-project.org/doc/qt-4.8/exportedfunctions.html */ extern void qt_mac_set_dock_menu(QMenu *menu); #endif // CARET_OS_MACOSX /** * \class caret::MacDockMenu * \brief Menu that is displayed Mac's Dock Menu. * \ingroup GuiQt * * The Mac Dock Menu is displayed when the user either holds the mouse button * down or Control-Clicks the icon for a running wb_view instance in the * Mac's Dock. */ /** * Constructor. */ MacDockMenu::MacDockMenu(QWidget* parent) : QMenu(parent) { /* * Recent Spec Files */ const int32_t firstRecentSpecFileIndex = actions().size(); const int32_t numRecentSpecFiles = BrainBrowserWindow::loadRecentSpecFileMenu(this); QList menuActions = actions(); const int32_t numberOfRecentSpecFileActions = menuActions.size() - firstRecentSpecFileIndex; for (int32_t i = firstRecentSpecFileIndex; i < numberOfRecentSpecFileActions; i++) { m_recentSpecFileActions.push_back(menuActions.at(i)); } if (numRecentSpecFiles > 0) { addSeparator(); } /* * Name of spec file */ const AString specFileName = GuiManager::get()->getBrain()->getSpecFile()->getFileNameNoPath(); /* * Open Windows */ const std::vector browserWindows = GuiManager::get()->getAllOpenBrainBrowserWindows(); if ( ! browserWindows.empty()) { for (std::vector::const_iterator bwIter = browserWindows.begin(); bwIter != browserWindows.end(); bwIter++) { const BrainBrowserWindow* bbw = *bwIter; const AString title = (bbw->windowTitle() + " " + specFileName); QAction* action = addAction(title); action->setData(bbw->getBrowserWindowIndex()); m_browserWindowActions.push_back(action); } addSeparator(); } /* * Create New Window */ m_newBrowserWindowAction = addAction("New Window"); /* * Connect the menu to a slot for processing menu selections */ QObject::connect(this, SIGNAL(triggered(QAction*)), this, SLOT(menuActionTriggered(QAction*))); } /** * Destructor. */ MacDockMenu::~MacDockMenu() { std::cout << "Deleting Mac Dock Menu" << std::endl; } /** * Called when an item in the menu is selected. * * @param action * Menu's action that was triggered. */ void MacDockMenu::menuActionTriggered(QAction* action) { if (action == m_newBrowserWindowAction) { createNewBrowserWindow(); } else if (std::find(m_recentSpecFileActions.begin(), m_recentSpecFileActions.end(), action) != m_recentSpecFileActions.end()) { const AString specFileName = action->data().toString(); std::vector fileNamesToLoad; fileNamesToLoad.push_back(specFileName); BrainBrowserWindow* browserWindow = GuiManager::get()->getActiveBrowserWindow(); browserWindow->loadFilesFromCommandLine(fileNamesToLoad, BrainBrowserWindow::LOAD_SPEC_FILE_WITH_DIALOG); } else if (std::find(m_browserWindowActions.begin(), m_browserWindowActions.end(), action) != m_browserWindowActions.end()) { const int32_t browserWindowIndex = action->data().toInt(); BrainBrowserWindow* bbw = GuiManager::get()->getBrowserWindowByWindowIndex(browserWindowIndex); if (bbw != NULL) { bbw->activateWindow(); bbw->raise(); } } else if (action != NULL) { CaretAssertMessage(0, ("Mac Dock menu action not processed: " + action->text())); } } /** * Factory method to create/update the Mac Dock Menu. * On non-Mac platforms, this method does nothing. * * With Qt Menus, the "aboutToShow()" signal is used to * update the content of the menu so that the menu * can be updated just before it is displayed. However, * when a menu is added to the Dock, it is converted * to a platform native menu so the "aboutToShow()" signal * does not get issued. So, in Workbench, a Workbench event * is issued when something happens that requires an * update to the Dock Menu. This results in a new menu * being created and replacing the current Dock menu. */ void MacDockMenu::createUpdateMacDockMenu() { const bool DISABLE_DOCK_MENU = true; if (DISABLE_DOCK_MENU) { return; } #ifdef CARET_OS_MACOSX BrainBrowserWindow* browserWindow = GuiManager::get()->getActiveBrowserWindow(); if (browserWindow == NULL) { return; } MacDockMenu* menu = new MacDockMenu(); qt_mac_set_dock_menu(menu); /* * Previously created menus probably should be deleted but * it is not clear if QT takes ownership of the menu * which could result in a "double delete". In Qt 4.8.x, * the code does not show the menu being deleted at * any time but if that changes a crash would result. * * Another problem is that selection from the Dock * menu may cause the Dock menu to get recreated. */ // if (s_previousMacDockMenu != NULL) { // delete s_previousMacDockMenu; // } // s_previousMacDockMenu = menu; #endif // CARET_OS_MACOSX } /** * Gets called to create a new browser window. */ void MacDockMenu::createNewBrowserWindow() { EventManager::get()->blockEvent(EventTypeEnum::EVENT_GRAPHICS_UPDATE_ONE_WINDOW, true); BrainBrowserWindow* browserWindow = GuiManager::get()->getActiveBrowserWindow(); EventBrowserWindowNew eventNewBrowser(browserWindow, NULL); EventManager::get()->sendEvent(eventNewBrowser.getPointer()); if (eventNewBrowser.isError()) { QMessageBox::critical(browserWindow, "", eventNewBrowser.getErrorMessage()); return; } const int32_t newWindowIndex = eventNewBrowser.getBrowserWindowCreated()->getBrowserWindowIndex(); EventManager::get()->sendEvent(EventUserInterfaceUpdate().setWindowIndex(newWindowIndex).getPointer()); EventManager::get()->blockEvent(EventTypeEnum::EVENT_GRAPHICS_UPDATE_ONE_WINDOW, false); EventManager::get()->sendEvent(EventGraphicsUpdateOneWindow(newWindowIndex).getPointer()); } workbench-1.1.1/src/GuiQt/MacDockMenu.h000066400000000000000000000035461255417355300176350ustar00rootroot00000000000000#ifndef __MAC_DOCK_MENU_H__ #define __MAC_DOCK_MENU_H__ /*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include namespace caret { class MacDockMenu : public QMenu { Q_OBJECT public: static void createUpdateMacDockMenu(); virtual ~MacDockMenu(); // ADD_NEW_METHODS_HERE private slots: void menuActionTriggered(QAction*); private: MacDockMenu(QWidget* parent = 0); MacDockMenu(const MacDockMenu&); MacDockMenu& operator=(const MacDockMenu&); void createNewBrowserWindow(); QAction* m_newBrowserWindowAction; std::vector m_recentSpecFileActions; std::vector m_browserWindowActions; static MacDockMenu* s_previousMacDockMenu; // ADD_NEW_MEMBERS_HERE }; #ifdef __MAC_DOCK_MENU_DECLARE__ MacDockMenu* MacDockMenu::s_previousMacDockMenu = NULL; #endif // __MAC_DOCK_MENU_DECLARE__ } // namespace #endif //__MAC_DOCK_MENU_H__ workbench-1.1.1/src/GuiQt/MapSettingsFiberTrajectoryWidget.cxx000066400000000000000000000416071255417355300245030ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __MAP_SETTINGS_FIBER_TRAJECTORY_WIDGET_DECLARE__ #include "MapSettingsFiberTrajectoryWidget.h" #undef __MAP_SETTINGS_FIBER_TRAJECTORY_WIDGET_DECLARE__ #include #include #include #include #include #include #include #include #include #include "CiftiFiberTrajectoryFile.h" #include "EventManager.h" #include "EventGraphicsUpdateAllWindows.h" #include "EventUserInterfaceUpdate.h" #include "FiberTrajectoryColorModel.h" #include "FiberTrajectoryMapProperties.h" #include "GuiManager.h" #include "WuQFactory.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::MapSettingsFiberTrajectoryWidget * \brief View/Controller for fiber trajectories * \ingroup GuiQt */ /** * Constructor. * @param browserWindowIndex * Index of browser window in which this view controller is displayed. * @param parent * Parent of this object. */ MapSettingsFiberTrajectoryWidget::MapSettingsFiberTrajectoryWidget(QWidget* parent) : QWidget(parent) { m_updateInProgress = true; QWidget* attributesWidget = createAttributesWidget(); QWidget* displayModeWidget = createDisplayModeWidget(); QWidget* dataMappingWidget = createDataMappingWidget(); QVBoxLayout* layout = new QVBoxLayout(this); layout->addWidget(attributesWidget, 0, Qt::AlignLeft); layout->addWidget(displayModeWidget, 0, Qt::AlignLeft); layout->addWidget(dataMappingWidget, 0, Qt::AlignLeft); layout->addStretch(); setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); } /** * Destructor. */ MapSettingsFiberTrajectoryWidget::~MapSettingsFiberTrajectoryWidget() { } QWidget* MapSettingsFiberTrajectoryWidget::createAttributesWidget() { QLabel* colorLabel = new QLabel("Coloring: "); m_colorSelectionComboBox = WuQFactory::newComboBoxSignalInt(this, SLOT(processAttributesChanges())); QGroupBox* attributesGroupBox = new QGroupBox("Attribues"); QGridLayout* attributesGridLayout = new QGridLayout(attributesGroupBox); int row = 0; attributesGridLayout->addWidget(colorLabel, row, 0); attributesGridLayout->addWidget(m_colorSelectionComboBox, row, 1); return attributesGroupBox; } /** * @return Create and return the display mode widget. */ QWidget* MapSettingsFiberTrajectoryWidget::createDisplayModeWidget() { m_displayModeButtonGroup = new QButtonGroup(this); QObject::connect(m_displayModeButtonGroup, SIGNAL(buttonClicked(int)), this, SLOT(processAttributesChanges())); std::vector displayModes; FiberTrajectoryDisplayModeEnum::getAllEnums(displayModes); const int32_t numDisplayModes = static_cast(displayModes.size()); for (int32_t i = 0; i < numDisplayModes; i++) { const FiberTrajectoryDisplayModeEnum::Enum mode = displayModes[i]; QRadioButton* radioButton = new QRadioButton(FiberTrajectoryDisplayModeEnum::toGuiName(mode)); m_displayModeButtonGroup->addButton(radioButton, i); m_displayModeRadioButtons.push_back(radioButton); m_displayModeRadioButtonData.push_back(mode); } QGroupBox* modeGroupBox = new QGroupBox("Display Mode"); QVBoxLayout* modeGroupLayout = new QVBoxLayout(modeGroupBox); for (int32_t i = 0; i < numDisplayModes; i++) { modeGroupLayout->addWidget(m_displayModeRadioButtons[i]); } return modeGroupBox; } /** * @return Create and return the data mapping widget. */ QWidget* MapSettingsFiberTrajectoryWidget::createDataMappingWidget() { const int spinBoxWidth = 85; m_proportionStreamlineSpinBox = WuQFactory::newDoubleSpinBox(); m_proportionStreamlineSpinBox->setRange(0, std::numeric_limits::max()); m_proportionStreamlineSpinBox->setSingleStep(5); m_proportionStreamlineSpinBox->setFixedWidth(spinBoxWidth); m_proportionStreamlineSpinBox->setDecimals(3); m_proportionStreamlineSpinBox->setToolTip("A fiber is displayed only if the total number of its\n" "streamlines is greater than or equal to this value"); QObject::connect(m_proportionStreamlineSpinBox, SIGNAL(valueChanged(double)), this, SLOT(processAttributesChanges())); m_proportionMinimumSpinBox = WuQFactory::newDoubleSpinBox(); m_proportionMinimumSpinBox->setRange(0.0, 1.0); m_proportionMinimumSpinBox->setDecimals(3); m_proportionMinimumSpinBox->setSingleStep(0.05); m_proportionMinimumSpinBox->setFixedWidth(spinBoxWidth); m_proportionMinimumSpinBox->setToolTip("If the proportion for an axis is less than or equal\n" "to this value, the opacity will be zero (clear)"); QObject::connect(m_proportionMinimumSpinBox, SIGNAL(valueChanged(double)), this, SLOT(processAttributesChanges())); m_proportionMaximumSpinBox = WuQFactory::newDoubleSpinBox(); m_proportionMaximumSpinBox->setRange(0.0, 1.0); m_proportionMaximumSpinBox->setDecimals(3); m_proportionMaximumSpinBox->setSingleStep(0.05); m_proportionMaximumSpinBox->setFixedWidth(spinBoxWidth); m_proportionMaximumSpinBox->setToolTip("If the proportion for an axis is greater than or equal\n" "to this value, the opacity will be one (opaque)"); QObject::connect(m_proportionMaximumSpinBox, SIGNAL(valueChanged(double)), this, SLOT(processAttributesChanges())); m_countStreamlineSpinBox = WuQFactory::newDoubleSpinBox(); m_countStreamlineSpinBox->setRange(0, std::numeric_limits::max()); m_countStreamlineSpinBox->setDecimals(3); m_countStreamlineSpinBox->setSingleStep(5); m_countStreamlineSpinBox->setFixedWidth(spinBoxWidth); m_countStreamlineSpinBox->setToolTip("A fiber is displayed only if the total number of its\n" "streamlines is greater than or equal to this value"); QObject::connect(m_countStreamlineSpinBox, SIGNAL(valueChanged(double)), this, SLOT(processAttributesChanges())); m_countMinimumSpinBox = WuQFactory::newDoubleSpinBox(); m_countMinimumSpinBox->setRange(0, std::numeric_limits::max()); m_countMinimumSpinBox->setDecimals(3); m_countMinimumSpinBox->setSingleStep(5); m_countMinimumSpinBox->setFixedWidth(spinBoxWidth); m_countMinimumSpinBox->setToolTip("If the number of fibers for an axis is less than or equal\n" "to this value, the opacity will be zero (clear)"); QObject::connect(m_countMinimumSpinBox, SIGNAL(valueChanged(double)), this, SLOT(processAttributesChanges())); m_countMaximumSpinBox = WuQFactory::newDoubleSpinBox(); m_countMaximumSpinBox->setRange(0, std::numeric_limits::max()); m_countMaximumSpinBox->setDecimals(3); m_countMaximumSpinBox->setSingleStep(5); m_countMaximumSpinBox->setFixedWidth(spinBoxWidth); m_countMaximumSpinBox->setToolTip("If the number of fibers for an axis is greater than or equal\n" "to this value, the opacity will be one (opaque)"); QObject::connect(m_countMaximumSpinBox, SIGNAL(valueChanged(double)), this, SLOT(processAttributesChanges())); m_distanceStreamlineSpinBox = WuQFactory::newDoubleSpinBox(); m_distanceStreamlineSpinBox->setRange(0, std::numeric_limits::max()); m_distanceStreamlineSpinBox->setDecimals(3); m_distanceStreamlineSpinBox->setSingleStep(5); m_distanceStreamlineSpinBox->setFixedWidth(spinBoxWidth); m_distanceStreamlineSpinBox->setToolTip("A fiber is displayed only if the total number of its\n" "streamlines is greater than or equal to this value"); QObject::connect(m_distanceStreamlineSpinBox, SIGNAL(valueChanged(double)), this, SLOT(processAttributesChanges())); m_distanceMinimumSpinBox = WuQFactory::newDoubleSpinBox(); m_distanceMinimumSpinBox->setRange(0, std::numeric_limits::max()); m_distanceMinimumSpinBox->setDecimals(3); m_distanceMinimumSpinBox->setSingleStep(5); m_distanceMinimumSpinBox->setFixedWidth(spinBoxWidth); m_distanceMinimumSpinBox->setToolTip("If count times distance for an axis is less than or equal\n" "to this value, the opacity will be zero (clear)"); QObject::connect(m_distanceMinimumSpinBox, SIGNAL(valueChanged(double)), this, SLOT(processAttributesChanges())); m_distanceMaximumSpinBox = WuQFactory::newDoubleSpinBox(); m_distanceMaximumSpinBox->setRange(0, std::numeric_limits::max()); m_distanceMaximumSpinBox->setDecimals(3); m_distanceMaximumSpinBox->setSingleStep(5); m_distanceMaximumSpinBox->setFixedWidth(spinBoxWidth); m_distanceMaximumSpinBox->setToolTip("If the count times distance for an axis is greater than or equal\n" "to this value, the opacity will be one (opaque)"); QObject::connect(m_distanceMaximumSpinBox, SIGNAL(valueChanged(double)), this, SLOT(processAttributesChanges())); QGroupBox* dataMappingGroupBox = new QGroupBox("Data Mapping"); QGridLayout* dataMappingLayout = new QGridLayout(dataMappingGroupBox); WuQtUtilities::setLayoutSpacingAndMargins(dataMappingLayout, 4, 2); int row = dataMappingLayout->rowCount(); int columnCounter = 0; const int COLUMN_LABELS = columnCounter++; const int COLUMN_THRESHOLD = columnCounter++; const int COLUMN_MINIMUM = columnCounter++; const int COLUMN_MAXIMUM = columnCounter++; dataMappingLayout->addWidget(new QLabel("Display
Mode"), row, COLUMN_LABELS, Qt::AlignLeft); dataMappingLayout->addWidget(new QLabel("Streamline
Threshold"), row, COLUMN_THRESHOLD, Qt::AlignLeft); dataMappingLayout->addWidget(new QLabel("Map to
Clear"), row, COLUMN_MINIMUM, Qt::AlignLeft); dataMappingLayout->addWidget(new QLabel("Map to
Opaque"), row, COLUMN_MAXIMUM, Qt::AlignLeft); row++; dataMappingLayout->addWidget(new QLabel("Absolute"), row, COLUMN_LABELS, Qt::AlignHCenter); dataMappingLayout->addWidget(m_countStreamlineSpinBox, row, COLUMN_THRESHOLD, Qt::AlignHCenter); dataMappingLayout->addWidget(m_countMinimumSpinBox, row, COLUMN_MINIMUM, Qt::AlignHCenter); dataMappingLayout->addWidget(m_countMaximumSpinBox, row, COLUMN_MAXIMUM, Qt::AlignHCenter); row++; dataMappingLayout->addWidget(new QLabel("Distance"), row, COLUMN_LABELS, Qt::AlignHCenter); dataMappingLayout->addWidget(m_distanceStreamlineSpinBox, row, COLUMN_THRESHOLD, Qt::AlignHCenter); dataMappingLayout->addWidget(m_distanceMinimumSpinBox, row, COLUMN_MINIMUM, Qt::AlignHCenter); dataMappingLayout->addWidget(m_distanceMaximumSpinBox, row, COLUMN_MAXIMUM, Qt::AlignHCenter); row++; dataMappingLayout->addWidget(new QLabel("Proportion"), row, COLUMN_LABELS, Qt::AlignHCenter); dataMappingLayout->addWidget(m_proportionStreamlineSpinBox, row, COLUMN_THRESHOLD, Qt::AlignHCenter); dataMappingLayout->addWidget(m_proportionMinimumSpinBox, row, COLUMN_MINIMUM, Qt::AlignHCenter); dataMappingLayout->addWidget(m_proportionMaximumSpinBox, row, COLUMN_MAXIMUM, Qt::AlignHCenter); row++; return dataMappingGroupBox; } /** * Called when a widget on the attributes page has * its value changed. */ void MapSettingsFiberTrajectoryWidget::processAttributesChanges() { if (m_updateInProgress) { return; } if (m_fiberTrajectoryFile == NULL) { return; } FiberTrajectoryMapProperties* ftmp = m_fiberTrajectoryFile->getFiberTrajectoryMapProperties(); const int32_t selectedColorIndex = m_colorSelectionComboBox->currentIndex(); if (selectedColorIndex >= 0) { void* ptr = m_colorSelectionComboBox->itemData(selectedColorIndex, Qt::UserRole).value(); const FiberTrajectoryColorModel::Item* item = (FiberTrajectoryColorModel::Item*)ptr; ftmp->getFiberTrajectoryColorModel()->setSelectedItem(item); } const int32_t selectedModeRadioButtonIndex = m_displayModeButtonGroup->checkedId(); const FiberTrajectoryDisplayModeEnum::Enum displayMode = m_displayModeRadioButtonData[selectedModeRadioButtonIndex]; ftmp->setDisplayMode(displayMode); ftmp->setProportionStreamline(m_proportionStreamlineSpinBox->value());; ftmp->setProportionMinimumOpacity(m_proportionMinimumSpinBox->value()); ftmp->setProportionMaximumOpacity(m_proportionMaximumSpinBox->value()); ftmp->setCountStreamline(m_countStreamlineSpinBox->value()); ftmp->setCountMaximumOpacity(m_countMaximumSpinBox->value()); ftmp->setCountMinimumOpacity(m_countMinimumSpinBox->value()); ftmp->setDistanceStreamline(m_distanceStreamlineSpinBox->value()); ftmp->setDistanceMaximumOpacity(m_distanceMaximumSpinBox->value()); ftmp->setDistanceMinimumOpacity(m_distanceMinimumSpinBox->value()); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } /** * Update with the given fiber trajectory file. */ void MapSettingsFiberTrajectoryWidget::updateEditor(CiftiFiberTrajectoryFile* fiberTrajectoryFile) { m_fiberTrajectoryFile = fiberTrajectoryFile; if (m_fiberTrajectoryFile == NULL) { return; } m_updateInProgress = true; FiberTrajectoryMapProperties* ftmp = m_fiberTrajectoryFile->getFiberTrajectoryMapProperties(); FiberTrajectoryColorModel* colorModel = ftmp->getFiberTrajectoryColorModel(); std::vector colorItems = colorModel->getValidItems(); const FiberTrajectoryColorModel::Item* selectedItem = colorModel->getSelectedItem(); const int32_t numColorItems = static_cast(colorItems.size()); m_colorSelectionComboBox->blockSignals(true); m_colorSelectionComboBox->clear(); int32_t defaultIndex = 0; for (int32_t i = 0; i < numColorItems; i++) { FiberTrajectoryColorModel::Item* item = colorItems[i]; if (item == selectedItem) { defaultIndex = i; } m_colorSelectionComboBox->addItem(item->getName(), qVariantFromValue((void*)item)); } if ((defaultIndex >= 0) && (defaultIndex < m_colorSelectionComboBox->count())) { m_colorSelectionComboBox->setCurrentIndex(defaultIndex); } m_colorSelectionComboBox->blockSignals(false); const FiberTrajectoryDisplayModeEnum::Enum selectedDisplayMode = ftmp->getDisplayMode(); const int32_t numDisplayModeRadioButtons = m_displayModeButtonGroup->buttons().size(); for (int32_t i = 0; i < numDisplayModeRadioButtons; i++) { if (m_displayModeRadioButtonData[i] == selectedDisplayMode) { m_displayModeRadioButtons[i]->setChecked(true); break; } } /* * Update the attributes */ m_proportionStreamlineSpinBox->setValue(ftmp->getProportionStreamline()); m_proportionMaximumSpinBox->setValue(ftmp->getProportionMaximumOpacity()); m_proportionMinimumSpinBox->setValue(ftmp->getProportionMinimumOpacity()); m_countStreamlineSpinBox->setValue(ftmp->getCountStreamline()); m_countMaximumSpinBox->setValue(ftmp->getCountMaximumOpacity()); m_countMinimumSpinBox->setValue(ftmp->getCountMinimumOpacity()); m_distanceStreamlineSpinBox->setValue(ftmp->getDistanceStreamline()); m_distanceMaximumSpinBox->setValue(ftmp->getDistanceMaximumOpacity()); m_distanceMinimumSpinBox->setValue(ftmp->getDistanceMinimumOpacity()); m_updateInProgress = false; } /** * Update the fiber trajectory widget. */ void MapSettingsFiberTrajectoryWidget::updateWidget() { updateEditor(m_fiberTrajectoryFile); } workbench-1.1.1/src/GuiQt/MapSettingsFiberTrajectoryWidget.h000066400000000000000000000061141255417355300241220ustar00rootroot00000000000000#ifndef __MAP_SETTINGS_FIBER_TRAJECTORY_WIDGET__H_ #define __MAP_SETTINGS_FIBER_TRAJECTORY_WIDGET__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "FiberTrajectoryDisplayModeEnum.h" class QButtonGroup; class QComboBox; class QDoubleSpinBox; class QRadioButton; namespace caret { class CaretMappableDataFile; class CiftiFiberTrajectoryFile; class MapSettingsFiberTrajectoryWidget : public QWidget { Q_OBJECT public: MapSettingsFiberTrajectoryWidget(QWidget* parent = 0); virtual ~MapSettingsFiberTrajectoryWidget(); void updateEditor(CiftiFiberTrajectoryFile* fiberTrajectoryFile); void updateWidget(); // ADD_NEW_METHODS_HERE private slots: void processAttributesChanges(); private: MapSettingsFiberTrajectoryWidget(const MapSettingsFiberTrajectoryWidget&); MapSettingsFiberTrajectoryWidget& operator=(const MapSettingsFiberTrajectoryWidget&); QWidget* createAttributesWidget(); QWidget* createDisplayModeWidget(); QWidget* createDataMappingWidget(); CiftiFiberTrajectoryFile* m_fiberTrajectoryFile; QComboBox* m_colorSelectionComboBox; std::vector m_displayModeRadioButtons; std::vector m_displayModeRadioButtonData; QButtonGroup* m_displayModeButtonGroup; QDoubleSpinBox* m_proportionStreamlineSpinBox; QDoubleSpinBox* m_proportionMinimumSpinBox; QDoubleSpinBox* m_proportionMaximumSpinBox; QDoubleSpinBox* m_countStreamlineSpinBox; QDoubleSpinBox* m_countMinimumSpinBox; QDoubleSpinBox* m_countMaximumSpinBox; QDoubleSpinBox* m_distanceStreamlineSpinBox; QDoubleSpinBox* m_distanceMinimumSpinBox; QDoubleSpinBox* m_distanceMaximumSpinBox; bool m_updateInProgress; // ADD_NEW_MEMBERS_HERE }; #ifdef __MAP_SETTINGS_FIBER_TRAJECTORY_WIDGET_DECLARE__ #endif // __MAP_SETTINGS_FIBER_TRAJECTORY_WIDGET_DECLARE__ } // namespace #endif //__MAP_SETTINGS_FIBER_TRAJECTORY_WIDGET__H_ workbench-1.1.1/src/GuiQt/MapSettingsLabelsWidget.cxx000066400000000000000000000154761255417355300226140ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __MAP_SETTINGS_LABELS_WIDGET_DECLARE__ #include "MapSettingsLabelsWidget.h" #undef __MAP_SETTINGS_LABELS_WIDGET_DECLARE__ #include #include #include #include #include #include "CaretAssert.h" #include "CaretColorEnumComboBox.h" #include "CaretMappableDataFile.h" #include "EnumComboBoxTemplate.h" #include "EventGraphicsUpdateAllWindows.h" #include "EventManager.h" #include "EventSurfaceColoringInvalidate.h" #include "GiftiLabelTableEditor.h" #include "LabelDrawingTypeEnum.h" #include "LabelDrawingProperties.h" #include "Overlay.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::MapSettingsLabelsWidget * \brief Labels page for overlay and map settings. * \ingroup GuiQt */ /** * Constructor. */ MapSettingsLabelsWidget::MapSettingsLabelsWidget(QWidget* parent) : QWidget(parent) { QLabel* drawingTypeLabel = new QLabel("Drawing Type"); m_drawingTypeComboBox = new EnumComboBoxTemplate(this); m_drawingTypeComboBox->setup(); QObject::connect(m_drawingTypeComboBox, SIGNAL(itemActivated()), this, SLOT(applySelections())); QLabel* outlineColorLabel = new QLabel("Outline Color"); m_outlineColorComboBox = new CaretColorEnumComboBox(this); QObject::connect(m_outlineColorComboBox, SIGNAL(colorSelected(const CaretColorEnum::Enum)), this, SLOT(applySelections())); m_drawMedialWallFilledCheckBox = new QCheckBox("Draw Surface Medial Wall Filled"); QObject::connect(m_drawMedialWallFilledCheckBox, SIGNAL(clicked(bool)), this, SLOT(applySelections())); QPushButton* editLabelsPushButton = new QPushButton("Edit Map's Labels"); QObject::connect(editLabelsPushButton, SIGNAL(clicked()), this, SLOT(editLabelTablePushButtonClicked())); editLabelsPushButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); QWidget* gridWidget = new QWidget(); QGridLayout* gridLayout = new QGridLayout(gridWidget); gridLayout->addWidget(drawingTypeLabel, 0, 0); gridLayout->addWidget(m_drawingTypeComboBox->getWidget(), 0, 1); gridLayout->addWidget(outlineColorLabel, 1, 0); gridLayout->addWidget(m_outlineColorComboBox->getWidget(), 1, 1); gridLayout->addWidget(m_drawMedialWallFilledCheckBox, 2, 0, 1, 2, Qt::AlignLeft); gridLayout->addWidget(editLabelsPushButton, 3, 0, 1, 2, Qt::AlignLeft); gridWidget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); QVBoxLayout* layout = new QVBoxLayout(this); layout->addWidget(gridWidget); layout->addStretch(); setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); } /** * Destructor. */ MapSettingsLabelsWidget::~MapSettingsLabelsWidget() { } /** * Update the content of widget. * * @param overlay * Overlay for display in this widget. */ void MapSettingsLabelsWidget::updateContent(Overlay* overlay) { m_overlay = overlay; bool enableWidget = false; if (m_overlay != NULL) { CaretMappableDataFile* mapFile = NULL; int32_t mapIndex = -1; m_overlay->getSelectionData(mapFile, mapIndex); if (mapFile != NULL) { if (mapFile->isMappedWithLabelTable()) { const LabelDrawingProperties* labelProps = mapFile->getLabelDrawingProperties(); m_drawingTypeComboBox->setSelectedItem(labelProps->getDrawingType()); m_outlineColorComboBox->setSelectedColor(labelProps->getOutlineColor()); m_drawMedialWallFilledCheckBox->setChecked(labelProps->isDrawMedialWallFilled()); enableWidget = true; } } } setEnabled(enableWidget); } /** * Called when a control is changed. */ void MapSettingsLabelsWidget::applySelections() { if (m_overlay != NULL) { CaretMappableDataFile* mapFile = NULL; int32_t mapIndex = -1; m_overlay->getSelectionData(mapFile, mapIndex); if (mapFile != NULL) { if (mapFile->isMappedWithLabelTable()) { const LabelDrawingTypeEnum::Enum drawType = m_drawingTypeComboBox->getSelectedItem(); const CaretColorEnum::Enum outlineColor = m_outlineColorComboBox->getSelectedColor(); LabelDrawingProperties* labelProps = mapFile->getLabelDrawingProperties(); labelProps->setDrawingType(drawType); labelProps->setOutlineColor(outlineColor); labelProps->setDrawMedialWallFilled(m_drawMedialWallFilledCheckBox->isChecked()); EventManager::get()->sendEvent(EventSurfaceColoringInvalidate().getPointer()); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } } } } /** * Called when the edit label table button is clicked. */ void MapSettingsLabelsWidget::editLabelTablePushButtonClicked() { if (m_overlay != NULL) { CaretMappableDataFile* mapFile = NULL; int32_t mapIndex = -1; m_overlay->getSelectionData(mapFile, mapIndex); if (mapFile != NULL) { if (mapFile->isMappedWithLabelTable()) { if (mapIndex >= 0) { GiftiLabelTableEditor labelTableEditor(mapFile, mapIndex, "Edit Labels", GiftiLabelTableEditor::OPTION_ADD_APPLY_BUTTON, this); labelTableEditor.exec(); } } } } } workbench-1.1.1/src/GuiQt/MapSettingsLabelsWidget.h000066400000000000000000000041061255417355300222250ustar00rootroot00000000000000#ifndef __MAP_SETTINGS_LABELS_WIDGET_H__ #define __MAP_SETTINGS_LABELS_WIDGET_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include class QCheckBox; namespace caret { class CaretColorEnumComboBox; class EnumComboBoxTemplate; class Overlay; class MapSettingsLabelsWidget : public QWidget { Q_OBJECT public: MapSettingsLabelsWidget(QWidget* parent = 0); virtual ~MapSettingsLabelsWidget(); void updateContent(Overlay* overlay); private slots: void applySelections(); void editLabelTablePushButtonClicked(); // ADD_NEW_METHODS_HERE private: MapSettingsLabelsWidget(const MapSettingsLabelsWidget&); MapSettingsLabelsWidget& operator=(const MapSettingsLabelsWidget&); // ADD_NEW_MEMBERS_HERE Overlay* m_overlay; CaretColorEnumComboBox* m_outlineColorComboBox; EnumComboBoxTemplate* m_drawingTypeComboBox; QCheckBox* m_drawMedialWallFilledCheckBox; }; #ifdef __MAP_SETTINGS_LABELS_WIDGET_DECLARE__ // #endif // __MAP_SETTINGS_LABELS_WIDGET_DECLARE__ } // namespace #endif //__MAP_SETTINGS_LABELS_WIDGET_H__ workbench-1.1.1/src/GuiQt/MapSettingsLayerWidget.cxx000066400000000000000000000111631255417355300224530ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __MAP_SETTINGS_LAYER_WIDGET_DECLARE__ #include "MapSettingsLayerWidget.h" #undef __MAP_SETTINGS_LAYER_WIDGET_DECLARE__ #include #include #include #include #include #include "CaretMappableDataFile.h" #include "EnumComboBoxTemplate.h" #include "EventGraphicsUpdateAllWindows.h" #include "EventManager.h" #include "EventSurfaceColoringInvalidate.h" #include "Overlay.h" #include "VolumeMappableInterface.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::MapSettingsLayerWidget * \brief Contains user-interface components for overlay parameters. * \ingroup GuiQt */ /** * Constructor. */ MapSettingsLayerWidget::MapSettingsLayerWidget(QWidget* parent) : QWidget(parent) { QLabel* wholeBrainVoxelDrawingModeLabel = new QLabel("Voxel Drawing Mode"); m_wholeBrainVoxelDrawingModeComboBox = new EnumComboBoxTemplate(this); m_wholeBrainVoxelDrawingModeComboBox->setup(); QObject::connect(m_wholeBrainVoxelDrawingModeComboBox, SIGNAL(itemActivated()), this, SLOT(applySelections())); const AString warningText("Drawing voxels in Whole Brain view with 3D cubes can be very slow " "and should only be used with a volume that displays a limited number " "of voxels. One example is a label volume that identifies subcortical " "structures. Another example is a functional volume that is thresholded " "so that a small number of voxels are displayed. 3D cubes should never " "be used with anatomical volumes."); // QLabel* voxelWarningLabel = new QLabel(WuQtUtilities::createWordWrappedToolTipText(warningText)); QLabel* voxelWarningLabel = new QLabel(warningText); voxelWarningLabel->setWordWrap(true); QGroupBox* wholeBraingroupBox = new QGroupBox("Whole Brain"); wholeBraingroupBox->setFlat(true); QGridLayout* wholeBrainGridLayout = new QGridLayout(wholeBraingroupBox); wholeBrainGridLayout->addWidget(wholeBrainVoxelDrawingModeLabel, 0, 0); wholeBrainGridLayout->addWidget(m_wholeBrainVoxelDrawingModeComboBox->getWidget(), 0, 1); wholeBrainGridLayout->addWidget(voxelWarningLabel, 1, 0, 1, 2); wholeBraingroupBox->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); QVBoxLayout* layout = new QVBoxLayout(this); layout->addWidget(wholeBraingroupBox); layout->addStretch(); setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); } /** * Destructor. */ MapSettingsLayerWidget::~MapSettingsLayerWidget() { } /** * Update the content of widget. * * @param overlay * Overlay for display in this widget. */ void MapSettingsLayerWidget::updateContent(Overlay* overlay) { m_overlay = overlay; bool enableWidget = false; if (m_overlay != NULL) { m_wholeBrainVoxelDrawingModeComboBox->setSelectedItem(m_overlay->getWholeBrainVoxelDrawingMode()); enableWidget = true; } setEnabled(enableWidget); } /** * Called when a control is changed. */ void MapSettingsLayerWidget::applySelections() { const WholeBrainVoxelDrawingMode::Enum wholeBrainVoxelDrawingMode = m_wholeBrainVoxelDrawingModeComboBox->getSelectedItem(); if (m_overlay != NULL) { m_overlay->setWholeBrainVoxelDrawingMode(wholeBrainVoxelDrawingMode); } EventManager::get()->sendEvent(EventSurfaceColoringInvalidate().getPointer()); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } workbench-1.1.1/src/GuiQt/MapSettingsLayerWidget.h000066400000000000000000000036071255417355300221040ustar00rootroot00000000000000#ifndef __MAP_SETTINGS_LAYER_WIDGET_H__ #define __MAP_SETTINGS_LAYER_WIDGET_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include class QPushButton; namespace caret { class EnumComboBoxTemplate; class Overlay; class MapSettingsLayerWidget : public QWidget { Q_OBJECT public: MapSettingsLayerWidget(QWidget* parent = 0); virtual ~MapSettingsLayerWidget(); void updateContent(Overlay* overlay); private slots: void applySelections(); private: MapSettingsLayerWidget(const MapSettingsLayerWidget&); MapSettingsLayerWidget& operator=(const MapSettingsLayerWidget&); public: // ADD_NEW_METHODS_HERE private: // ADD_NEW_MEMBERS_HERE Overlay* m_overlay; EnumComboBoxTemplate* m_wholeBrainVoxelDrawingModeComboBox; }; #ifdef __MAP_SETTINGS_LAYER_WIDGET_DECLARE__ // #endif // __MAP_SETTINGS_LAYER_WIDGET_DECLARE__ } // namespace #endif //__MAP_SETTINGS_LAYER_WIDGET_H__ workbench-1.1.1/src/GuiQt/MapSettingsPaletteColorMappingWidget.cxx000066400000000000000000003054751255417355300253240ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define __MAP_SETTINGS_PALETTE_COLOR_MAPPING_WIDGET_DECLARE__ #include "MapSettingsPaletteColorMappingWidget.h" #undef __MAP_SETTINGS_PALETTE_COLOR_MAPPING_WIDGET_DECLARE__ #include "Brain.h" #include "CaretMappableDataFile.h" #include "CursorDisplayScoped.h" #include "EnumComboBoxTemplate.h" #include "EventCaretMappableDataFilesGet.h" #include "EventGraphicsUpdateAllWindows.h" #include "EventSurfaceColoringInvalidate.h" #include "EventManager.h" #include "FastStatistics.h" #include "GuiManager.h" #include "Histogram.h" #include "MathFunctions.h" #include "NodeAndVoxelColoring.h" #include "NumericTextFormatting.h" #include "Palette.h" #include "PaletteColorMapping.h" #include "PaletteFile.h" #include "VolumeFile.h" #include "WuQDataEntryDialog.h" #include "WuQWidgetObjectGroup.h" #include "WuQDoubleSlider.h" #include "WuQFactory.h" #include "WuQMessageBox.h" #include "WuQtUtilities.h" #include "WuQwtPlot.h" #include "qwt_plot_curve.h" #include "qwt_plot_histogram.h" #include "qwt_plot_intervalcurve.h" #include "qwt_plot_layout.h" #include "PlotMagnifier.h" #include "PlotPanner.h" using namespace caret; /* The QSlider uses integer for min/max so use max-int / 4 (approximately) */ static const int32_t BIG_NUMBER = 500000000; /** * \class caret::MapSettingsScalarDataEditorDialog * \brief Dialog for editing scalar data map settings * \ingroup GuiQt * * Presents controls for setting palettes, and thresholding used to color * scalar data. */ /** * Constructor for editing a palette selection. * * @param parent * Parent widget on which this dialog is displayed. */ MapSettingsPaletteColorMappingWidget::MapSettingsPaletteColorMappingWidget(QWidget* parent) : QWidget(parent) { m_previousCaretMappableDataFile = NULL; /* * No context menu, it screws things up * but one is used on the histogram plot */ this->setContextMenuPolicy(Qt::NoContextMenu); this->isHistogramColored = true; this->caretMappableDataFile = NULL; this->mapFileIndex = -1; this->paletteWidgetGroup = new WuQWidgetObjectGroup(this); this->thresholdWidgetGroup = new WuQWidgetObjectGroup(this); this->paletteColorMapping = NULL; QWidget* histogramWidget = this->createHistogramSection(); QWidget* histogramControlWidget = this->createHistogramControlSection(); QWidget* dataOptionsWidget = this->createDataOptionsSection(); QWidget* normalizationWidget = this->createNormalizationControlSection(); QWidget* paletteWidget = this->createPaletteSection(); QWidget* thresholdWidget = this->createThresholdSection(); QWidget* leftWidget = new QWidget(); QVBoxLayout* leftLayout = new QVBoxLayout(leftWidget); this->setLayoutSpacingAndMargins(leftLayout); leftLayout->addWidget(thresholdWidget); leftLayout->addWidget(paletteWidget); leftLayout->addStretch(); QVBoxLayout* optionsLayout = new QVBoxLayout(); optionsLayout->addWidget(normalizationWidget); optionsLayout->addWidget(dataOptionsWidget); optionsLayout->addStretch(100); QWidget* bottomRightWidget = new QWidget(); QHBoxLayout* bottomRightLayout = new QHBoxLayout(bottomRightWidget); this->setLayoutSpacingAndMargins(bottomRightLayout); bottomRightLayout->addWidget(histogramControlWidget); bottomRightLayout->addLayout(optionsLayout); bottomRightWidget->setFixedSize(bottomRightWidget->sizeHint()); QWidget* rightWidget = new QWidget(); QVBoxLayout* rightLayout = new QVBoxLayout(rightWidget); this->setLayoutSpacingAndMargins(rightLayout); rightLayout->addWidget(histogramWidget, 100); rightLayout->addWidget(bottomRightWidget, 0); rightLayout->addStretch(); QHBoxLayout* layout = new QHBoxLayout(this); this->setLayoutSpacingAndMargins(layout); layout->addWidget(leftWidget, 0); layout->addWidget(rightWidget, 100); setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); } /** * Destructor. */ MapSettingsPaletteColorMappingWidget::~MapSettingsPaletteColorMappingWidget() { } /** * May be called to update the widget's content. */ void MapSettingsPaletteColorMappingWidget::updateWidget() { this->updateEditorInternal(this->caretMappableDataFile, this->mapFileIndex); } /** * Called when the threshold type is changed. */ void MapSettingsPaletteColorMappingWidget::thresholdTypeChanged(int indx) { PaletteThresholdTypeEnum::Enum paletteThresholdType = static_cast(this->thresholdTypeComboBox->itemData(indx).toInt()); this->paletteColorMapping->setThresholdType(paletteThresholdType); this->updateEditorInternal(this->caretMappableDataFile, this->mapFileIndex); this->applySelections(); } /** * Update the minimum and maximum values for the thresholding controls. */ void MapSettingsPaletteColorMappingWidget::updateThresholdControlsMinimumMaximumRangeValues() { if (paletteColorMapping != NULL) { if (this->caretMappableDataFile != NULL) { if ((this->mapFileIndex >= 0) && (this->mapFileIndex < this->caretMappableDataFile->getNumberOfMaps())) { const PaletteThresholdRangeModeEnum::Enum thresholdRangeMode = paletteColorMapping->getThresholdRangeMode(); this->thresholdRangeModeComboBox->setSelectedItem(thresholdRangeMode); float maxValue = BIG_NUMBER; float minValue = -maxValue; float stepMax = maxValue; float stepMin = minValue; switch (thresholdRangeMode) { case PaletteThresholdRangeModeEnum::PALETTE_THRESHOLD_RANGE_MODE_FILE: this->caretMappableDataFile->getDataRangeFromAllMaps(minValue, maxValue); stepMin = minValue; stepMax = maxValue; break; case PaletteThresholdRangeModeEnum::PALETTE_THRESHOLD_RANGE_MODE_MAP: { FastStatistics* statistics = NULL; switch (this->caretMappableDataFile->getPaletteNormalizationMode()) { case PaletteNormalizationModeEnum::NORMALIZATION_ALL_MAP_DATA: statistics = const_cast(this->caretMappableDataFile->getFileFastStatistics()); break; case PaletteNormalizationModeEnum::NORMALIZATION_SELECTED_MAP_DATA: statistics = const_cast(this->caretMappableDataFile->getMapFastStatistics(this->mapFileIndex)); break; } if (statistics != NULL) { minValue = statistics->getMin(); maxValue = statistics->getMax(); stepMin = minValue; stepMax = maxValue; } } break; case PaletteThresholdRangeModeEnum::PALETTE_THRESHOLD_RANGE_MODE_UNLIMITED: { /* * For unlimited range, use twice the maximum value in the file * Using very large values can cause problems with some * Qt widgets. */ float allMapMinValue = 0.0; float allMapMaxValue = 0.0; this->caretMappableDataFile->getDataRangeFromAllMaps(allMapMinValue, allMapMaxValue); if (allMapMaxValue > allMapMinValue) { const float absMax = std::max(std::fabs(allMapMaxValue), std::fabs(allMapMinValue)); minValue = -absMax * 2.0; maxValue = absMax * 2.0; } stepMin = minValue; stepMax = maxValue; } break; } /* * Set the spin box step value to one percent of * the data's range. */ float stepValue = 1.0; const float diff = stepMax - stepMin; if (diff > 0.0) { stepValue = diff / 100.0; } float lowMin = minValue; float lowMax = maxValue; float highMin = minValue; float highMax = maxValue; if (this->paletteColorMapping->isThresholdNegMinPosMaxLinked()) { const float absMax = std::max(std::fabs(minValue), std::fabs(maxValue)); lowMin = -absMax; lowMax = 0.0; highMin = 0.0; highMax = absMax; } this->thresholdLowSlider->setRange(lowMin, lowMax); this->thresholdHighSlider->setRange(highMin, highMax); /* * Since there are multiple ways for the user to adjust * a threshold (slider or spin box) these controls must * dispaly the same values. In addition, linking the * thresholds requires updating the spin boxes and sliders * for both low and high thresholding. * * The spin box allows the user to hold down one of the * arrow keys to continuously update the data. However, * if any of the spin box's "set" methods are called * while the user holds down the arrow, holding down of * the arrow key will not work. So, when the signal is * emitted for the spin box value being changed, we need * to avoid updating that spin box during that time. */ if (allowUpdateOfThresholdLowSpinBox) { this->thresholdLowSpinBox->setRange(lowMin, lowMax); this->thresholdLowSpinBox->setSingleStep(stepValue); } if (allowUpdateOfThresholdHighSpinBox) { this->thresholdHighSpinBox->setRange(highMin, highMax); this->thresholdHighSpinBox->setSingleStep(stepValue); } } } } } /** * Should be called when a control is changed and * the change requires and update to other controls. */ void MapSettingsPaletteColorMappingWidget::applyAndUpdate() { this->applySelections(); this->updateEditorInternal(this->caretMappableDataFile, this->mapFileIndex); } /** * Update after a threshold value is changed. * * @param lowThreshold * New value for low threshold. * @param highThreshold * New value for high threshold. */ void MapSettingsPaletteColorMappingWidget::updateAfterThresholdValuesChanged(const float lowThreshold, const float highThreshold) { const PaletteThresholdTypeEnum::Enum threshType = this->paletteColorMapping->getThresholdType(); this->paletteColorMapping->setThresholdMinimum(threshType, lowThreshold); this->paletteColorMapping->setThresholdMaximum(threshType, highThreshold); updateThresholdSection(); updateHistogramPlot(); updateColoringAndGraphics(); } /** * Called when low value spin box changed. * @param thresholdLow * New value. */ void MapSettingsPaletteColorMappingWidget::thresholdLowSpinBoxValueChanged(double thresholdLow) { const PaletteThresholdTypeEnum::Enum threshType = this->paletteColorMapping->getThresholdType(); float thresholdHigh = this->paletteColorMapping->getThresholdMaximum(threshType); if (this->paletteColorMapping->isThresholdNegMinPosMaxLinked()) { thresholdHigh = -thresholdLow; } else { if (thresholdLow > thresholdHigh) { thresholdHigh = thresholdLow; } } allowUpdateOfThresholdLowSpinBox = false; updateAfterThresholdValuesChanged(thresholdLow, thresholdHigh); allowUpdateOfThresholdLowSpinBox = true; } /** * Called when high value spin box changed. * @param thresholdHigh * New value. */ void MapSettingsPaletteColorMappingWidget::thresholdHighSpinBoxValueChanged(double thresholdHigh) { const PaletteThresholdTypeEnum::Enum threshType = this->paletteColorMapping->getThresholdType(); float thresholdLow = this->paletteColorMapping->getThresholdMinimum(threshType); if (this->paletteColorMapping->isThresholdNegMinPosMaxLinked()) { thresholdLow = -thresholdHigh; } else { if (thresholdHigh < thresholdLow) { thresholdLow = thresholdHigh; } } allowUpdateOfThresholdHighSpinBox = false; updateAfterThresholdValuesChanged(thresholdLow, thresholdHigh); allowUpdateOfThresholdHighSpinBox = true; } /** * Called when low value slider changed. * @param thresholdLow * New value. */ void MapSettingsPaletteColorMappingWidget::thresholdLowSliderValueChanged(double thresholdLow) { const PaletteThresholdTypeEnum::Enum threshType = this->paletteColorMapping->getThresholdType(); float thresholdHigh = this->paletteColorMapping->getThresholdMaximum(threshType); if (this->paletteColorMapping->isThresholdNegMinPosMaxLinked()) { thresholdHigh = -thresholdLow; } else { if (thresholdLow > thresholdHigh) { thresholdHigh = thresholdLow; } } updateAfterThresholdValuesChanged(thresholdLow, thresholdHigh); } /** * Called when high value slider changed. * @param thresholdHigh * New value. */ void MapSettingsPaletteColorMappingWidget::thresholdHighSliderValueChanged(double thresholdHigh) { const PaletteThresholdTypeEnum::Enum threshType = this->paletteColorMapping->getThresholdType(); float thresholdLow = this->paletteColorMapping->getThresholdMinimum(threshType); if (this->paletteColorMapping->isThresholdNegMinPosMaxLinked()) { thresholdLow = -thresholdHigh; } else { if (thresholdHigh < thresholdLow) { thresholdLow = thresholdHigh; } } updateAfterThresholdValuesChanged(thresholdLow, thresholdHigh); } /** * Called when the threshold link check box is toggled. * * @param checked * Checked status of the checkbox. */ void MapSettingsPaletteColorMappingWidget::thresholdLinkCheckBoxToggled(bool checked) { if (this->paletteColorMapping != NULL) { this->paletteColorMapping->setThresholdNegMinPosMaxLinked(checked); const PaletteThresholdTypeEnum::Enum threshType = this->paletteColorMapping->getThresholdType(); float lowValue = this->paletteColorMapping->getThresholdMinimum(threshType); float highValue = this->paletteColorMapping->getThresholdMaximum(threshType); if (checked) { if (highValue > 0.0) { lowValue = -highValue; } else if (lowValue < 0.0) { highValue = -lowValue; } else { highValue = 1.0; lowValue = -1.0; } } updateAfterThresholdValuesChanged(lowValue, highValue); } } /** * Update coloring and graphics */ void MapSettingsPaletteColorMappingWidget::updateColoringAndGraphics() { PaletteFile* paletteFile = GuiManager::get()->getBrain()->getPaletteFile(); if (this->applyAllMapsCheckBox->isChecked()) { this->caretMappableDataFile->updateScalarColoringForAllMaps(paletteFile); } else { this->caretMappableDataFile->updateScalarColoringForMap(this->mapFileIndex, paletteFile); } EventManager::get()->sendEvent(EventSurfaceColoringInvalidate().getPointer()); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } /** * Create the threshold section of the dialog. * @return * The threshold section. */ QWidget* MapSettingsPaletteColorMappingWidget::createThresholdSection() { allowUpdateOfThresholdLowSpinBox = true; allowUpdateOfThresholdHighSpinBox = true; /* * Threshold types on/off */ QLabel* thresholdTypeLabel = new QLabel("Type"); std::vector thresholdTypes; PaletteThresholdTypeEnum::getAllEnums(thresholdTypes); const int32_t numThresholdTypes = static_cast(thresholdTypes.size()); this->thresholdTypeComboBox = new QComboBox(); WuQtUtilities::setToolTipAndStatusTip(this->thresholdTypeComboBox, "Select thresholding off/on"); for (int32_t i = 0; i < numThresholdTypes; i++) { this->thresholdTypeComboBox->addItem(PaletteThresholdTypeEnum::toGuiName(thresholdTypes[i])); this->thresholdTypeComboBox->setItemData(i, static_cast(thresholdTypes[i])); } QObject::connect(this->thresholdTypeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(thresholdTypeChanged(int))); QLabel* thresholdRangeLabel = new QLabel("Range"); this->thresholdRangeModeComboBox = new EnumComboBoxTemplate(this); QObject::connect(this->thresholdRangeModeComboBox, SIGNAL(itemActivated()), this, SLOT(thresholdRangeModeChanged())); this->thresholdRangeModeComboBox->setup(); const AString rangeModeToolTip = ("Controls range of threshold controls\n" " File: Range is from all values in file.\n" " Map: Range is from all values in selected map.\n" " Unlimited: Range is +/- infinity."); this->thresholdRangeModeComboBox->getWidget()->setToolTip(WuQtUtilities::createWordWrappedToolTipText(rangeModeToolTip)); /* * Linking of low/high thresholds */ QPixmap chainLinkPixmap; const bool chainLinkPixmapValid = WuQtUtilities::loadPixmap(":/PaletteSettings/chain_link_icon.png", chainLinkPixmap); const AString linkToolTipText("When linked, both low and high are the same\n" "ABSOLUTE value with low always being negative and\n" "high always being positive.\n" " low range: [- maximum-absolute value, 0]\n" " high range: [0, + maximum-absolute-value]\n" "\n" "When NOT linked, the low and high range controls\n" "operate independently.\n" " low and high range: [minimum-value, maximum-value]\n" "\n" "NOTE: When 'Link' is unchecked, the thresholds may \n" "change due to a difference in the allowable range of \n" "values while linked and unlinked."); this->thresholdLinkCheckBox = new QCheckBox(""); QObject::connect(this->thresholdLinkCheckBox, SIGNAL(toggled(bool)), this, SLOT(thresholdLinkCheckBoxToggled(bool))); this->thresholdLinkCheckBox->setToolTip(linkToolTipText); this->thresholdWidgetGroup->add(this->thresholdLinkCheckBox); QLabel* linkLabel = new QLabel(); if (chainLinkPixmapValid) { linkLabel->setPixmap(chainLinkPixmap); } else { linkLabel->setText("Link"); } linkLabel->setToolTip(linkToolTipText); QVBoxLayout* linkLayout = new QVBoxLayout(); linkLayout->addWidget(this->thresholdLinkCheckBox); linkLayout->addWidget(linkLabel); /* * Sliders and Spin Boxes for adjustment */ QLabel* thresholdLowLabel = new QLabel("Low"); QLabel* thresholdHighLabel = new QLabel("High"); const float thresholdMinimum = -BIG_NUMBER; const float thresholdMaximum = BIG_NUMBER; this->thresholdLowSlider = new WuQDoubleSlider(Qt::Horizontal, this); this->thresholdLowSlider->setRange(thresholdMinimum, thresholdMaximum); WuQtUtilities::setToolTipAndStatusTip(this->thresholdLowSlider->getWidget(), "Adjust the low threshold value"); this->thresholdWidgetGroup->add(this->thresholdLowSlider); QObject::connect(this->thresholdLowSlider, SIGNAL(valueChanged(double)), this, SLOT(thresholdLowSliderValueChanged(double))); this->thresholdHighSlider = new WuQDoubleSlider(Qt::Horizontal, this); this->thresholdHighSlider->setRange(thresholdMinimum, thresholdMaximum); WuQtUtilities::setToolTipAndStatusTip(this->thresholdHighSlider->getWidget(), "Adjust the high threshold value"); this->thresholdWidgetGroup->add(this->thresholdHighSlider); QObject::connect(this->thresholdHighSlider, SIGNAL(valueChanged(double)), this, SLOT(thresholdHighSliderValueChanged(double))); const int spinBoxWidth = 80.0; this->thresholdLowSpinBox = WuQFactory::newDoubleSpinBoxWithMinMaxStepDecimalsSignalDouble(thresholdMinimum, thresholdMaximum, 1.0, 3, this, SLOT(thresholdLowSpinBoxValueChanged(double))); this->thresholdLowSpinBox->setAccelerated(true); WuQtUtilities::setToolTipAndStatusTip(this->thresholdLowSpinBox, "Adjust the low threshold value"); this->thresholdWidgetGroup->add(this->thresholdLowSpinBox); this->thresholdLowSpinBox->setFixedWidth(spinBoxWidth); this->thresholdHighSpinBox = WuQFactory::newDoubleSpinBoxWithMinMaxStepDecimalsSignalDouble(thresholdMinimum, thresholdMaximum, 1.0, 3, this, SLOT(thresholdHighSpinBoxValueChanged(double))); WuQtUtilities::setToolTipAndStatusTip(this->thresholdHighSpinBox, "Adjust the high threshold value"); this->thresholdWidgetGroup->add(this->thresholdHighSpinBox); this->thresholdHighSpinBox->setFixedWidth(spinBoxWidth); this->thresholdShowInsideRadioButton = new QRadioButton("Show Data Inside Thresholds"); WuQtUtilities::setWordWrappedToolTip(this->thresholdShowInsideRadioButton, "Displays data that is greater than or equal to " "the minimum threshold value and less than or " "equal to the maximum threshold value."); this->thresholdShowOutsideRadioButton = new QRadioButton("Show Data Outside Thresholds"); WuQtUtilities::setWordWrappedToolTip(this->thresholdShowOutsideRadioButton, "Displays data that is less than the minimum " "threshold value or greater than the maximum " "threshold value."); QButtonGroup* thresholdShowButtonGroup = new QButtonGroup(this); this->thresholdWidgetGroup->add(thresholdShowButtonGroup); thresholdShowButtonGroup->addButton(this->thresholdShowInsideRadioButton); thresholdShowButtonGroup->addButton(this->thresholdShowOutsideRadioButton); QObject::connect(thresholdShowButtonGroup, SIGNAL(buttonClicked(int)), this, SLOT(applyAndUpdate())); QWidget* thresholdAdjustmentWidget = new QWidget(); QGridLayout* thresholdAdjustmentLayout = new QGridLayout(thresholdAdjustmentWidget); this->setLayoutSpacingAndMargins(thresholdAdjustmentLayout); thresholdAdjustmentLayout->setColumnStretch(0, 0); thresholdAdjustmentLayout->setColumnStretch(1, 0); thresholdAdjustmentLayout->setColumnStretch(2, 100); thresholdAdjustmentLayout->setColumnStretch(3, 0); thresholdAdjustmentLayout->addLayout(linkLayout, 0, 0, 2, 1, Qt::AlignCenter); thresholdAdjustmentLayout->addWidget(thresholdHighLabel, 0, 1); thresholdAdjustmentLayout->addWidget(this->thresholdHighSlider->getWidget(), 0, 2); thresholdAdjustmentLayout->addWidget(this->thresholdHighSpinBox, 0, 3); thresholdAdjustmentLayout->addWidget(thresholdLowLabel, 1, 1); thresholdAdjustmentLayout->addWidget(this->thresholdLowSlider->getWidget(), 1, 2); thresholdAdjustmentLayout->addWidget(this->thresholdLowSpinBox, 1, 3); thresholdAdjustmentLayout->addWidget(this->thresholdShowInsideRadioButton, 2, 0, 1, 4, Qt::AlignLeft); thresholdAdjustmentLayout->addWidget(this->thresholdShowOutsideRadioButton, 3, 0, 1, 4, Qt::AlignLeft); thresholdAdjustmentWidget->setFixedHeight(thresholdAdjustmentWidget->sizeHint().height()); QWidget* topWidget = new QWidget(); QHBoxLayout* topLayout = new QHBoxLayout(topWidget); this->setLayoutSpacingAndMargins(topLayout); topLayout->addWidget(thresholdTypeLabel); topLayout->addWidget(this->thresholdTypeComboBox); topLayout->addWidget(thresholdRangeLabel); topLayout->addWidget(this->thresholdRangeModeComboBox->getWidget()); topLayout->addStretch(); QGroupBox* thresholdGroupBox = new QGroupBox("Threshold"); QVBoxLayout* layout = new QVBoxLayout(thresholdGroupBox); this->setLayoutSpacingAndMargins(layout); layout->addWidget(topWidget, 0, Qt::AlignLeft); layout->addWidget(WuQtUtilities::createHorizontalLineWidget()); layout->addWidget(thresholdAdjustmentWidget); thresholdGroupBox->setFixedHeight(thresholdGroupBox->sizeHint().height()); this->thresholdAdjustmentWidgetGroup = new WuQWidgetObjectGroup(this); this->thresholdAdjustmentWidgetGroup->add(this->thresholdLinkCheckBox); this->thresholdAdjustmentWidgetGroup->add(thresholdLowLabel); this->thresholdAdjustmentWidgetGroup->add(thresholdHighLabel); this->thresholdAdjustmentWidgetGroup->add(this->thresholdLowSlider); this->thresholdAdjustmentWidgetGroup->add(this->thresholdHighSlider); this->thresholdAdjustmentWidgetGroup->add(this->thresholdLowSpinBox); this->thresholdAdjustmentWidgetGroup->add(this->thresholdHighSpinBox); this->thresholdAdjustmentWidgetGroup->add(this->thresholdShowInsideRadioButton); this->thresholdAdjustmentWidgetGroup->add(this->thresholdShowOutsideRadioButton); this->thresholdAdjustmentWidgetGroup->add(thresholdRangeLabel); this->thresholdAdjustmentWidgetGroup->add(this->thresholdRangeModeComboBox->getWidget()); return thresholdGroupBox; } /** * Called when threshold range mode is changed. */ void MapSettingsPaletteColorMappingWidget::thresholdRangeModeChanged() { const PaletteThresholdRangeModeEnum::Enum thresholdRange = this->thresholdRangeModeComboBox->getSelectedItem(); this->paletteColorMapping->setThresholdRangeMode(thresholdRange); updateThresholdControlsMinimumMaximumRangeValues(); applySelections(); } /** * Called when a histogram control is changed. */ void MapSettingsPaletteColorMappingWidget::histogramControlChanged() { this->updateWidget(); } /** * Create the statistics section * @return the statistics section widget. */ QWidget* MapSettingsPaletteColorMappingWidget::createHistogramControlSection() { /* * Control section */ this->histogramAllRadioButton = new QRadioButton("All"); WuQtUtilities::setToolTipAndStatusTip(this->histogramAllRadioButton, "Displays all map data in the histogram"); this->histogramMatchPaletteRadioButton = new QRadioButton("Match\nPalette"); WuQtUtilities::setToolTipAndStatusTip(this->histogramMatchPaletteRadioButton, "Use the palette mapping selections"); this->histogramAllRadioButton->setChecked(true); QButtonGroup* buttGroup = new QButtonGroup(this); buttGroup->addButton(this->histogramAllRadioButton); buttGroup->addButton(this->histogramMatchPaletteRadioButton); QObject::connect(buttGroup, SIGNAL(buttonClicked(int)), this, SLOT(histogramControlChanged())); this->histogramUsePaletteColors = new QCheckBox("Colorize"); WuQtUtilities::setToolTipAndStatusTip(this->histogramUsePaletteColors, "If checked, histogram colors match colors mapped to data"); this->histogramUsePaletteColors->setChecked(true); QObject::connect(this->histogramUsePaletteColors, SIGNAL(toggled(bool)), this, SLOT(histogramControlChanged())); QWidget* controlWidget = new QWidget(); QGridLayout* controlLayout = new QGridLayout(controlWidget); this->setLayoutSpacingAndMargins(controlLayout); controlLayout->addWidget(this->histogramAllRadioButton); controlLayout->addWidget(this->histogramMatchPaletteRadioButton); controlLayout->addWidget(WuQtUtilities::createHorizontalLineWidget()); controlLayout->addWidget(this->histogramUsePaletteColors); controlWidget->setFixedSize(controlWidget->sizeHint()); /* * Statistics */ const AString blankText(" "); this->statisticsMinimumValueLabel = new QLabel(blankText); this->statisticsMaximumValueLabel = new QLabel(blankText); this->statisticsMeanValueLabel = new QLabel(blankText); this->statisticsStandardDeviationLabel = new QLabel(blankText); QWidget* statisticsWidget = new QWidget(); QGridLayout* statisticsLayout = new QGridLayout(statisticsWidget); this->setLayoutSpacingAndMargins(statisticsLayout); statisticsLayout->addWidget(new QLabel("Mean"), 0, 0); statisticsLayout->addWidget(this->statisticsMeanValueLabel, 0, 1); statisticsLayout->addWidget(new QLabel("Std Dev"), 1, 0); statisticsLayout->addWidget(this->statisticsStandardDeviationLabel, 1, 1); statisticsLayout->addWidget(new QLabel("Max"), 2, 0); statisticsLayout->addWidget(this->statisticsMaximumValueLabel, 2, 1); statisticsLayout->addWidget(new QLabel("Min"), 3, 0); statisticsLayout->addWidget(this->statisticsMinimumValueLabel, 3, 1); statisticsWidget->setFixedHeight(statisticsWidget->sizeHint().height()); QGroupBox* groupBox = new QGroupBox("Histogram"); QHBoxLayout* layout = new QHBoxLayout(groupBox); this->setLayoutSpacingAndMargins(layout); layout->addWidget(controlWidget); layout->addWidget(WuQtUtilities::createVerticalLineWidget()); layout->addWidget(statisticsWidget); groupBox->setFixedHeight(groupBox->sizeHint().height()); return groupBox; } /** * Create the histogram section of the dialog. * @return * The histogram section. */ QWidget* MapSettingsPaletteColorMappingWidget::createHistogramSection() { //this->thresholdPlot = new QwtPlot(); this->thresholdPlot = new WuQwtPlot(); QObject::connect(this->thresholdPlot, SIGNAL(contextMenuDisplay(QContextMenuEvent*, float, float)), this, SLOT(contextMenuDisplayRequested(QContextMenuEvent*, float, float))); this->thresholdPlot->plotLayout()->setAlignCanvasToScales(true); /* * Allow zooming */ PlotMagnifier* magnifier = new PlotMagnifier(this->thresholdPlot->canvas()); magnifier->setAxisEnabled(QwtPlot::yLeft, true); magnifier->setAxisEnabled(QwtPlot::yRight, true); /* * Allow panning */ (void)new PlotPanner(this->thresholdPlot->canvas()); /* * Auto scaling */ this->thresholdPlot->setAxisAutoScale(QwtPlot::xBottom); this->thresholdPlot->setAxisAutoScale(QwtPlot::yLeft); /* * Reset View tool button */ QAction* resetViewAction = WuQtUtilities::createAction("Reset View", "Remove any zooming/panning from histogram chart", this, this, SLOT(histogramResetViewButtonClicked())); QToolButton* resetViewToolButton = new QToolButton(); resetViewToolButton->setDefaultAction(resetViewAction); QWidget* widget = new QWidget(); QVBoxLayout* layout = new QVBoxLayout(widget); layout->addWidget(this->thresholdPlot); layout->addWidget(resetViewToolButton, 0, Qt::AlignHCenter); WuQtUtilities::setLayoutSpacingAndMargins(layout, 2, 0); return widget; } /** * Called when the context menu is to be displayed. * * @param event * The context menu event. * @param graphX * X-coordinate on plot. * @param graphY * Y-coordinate on plot. */ void MapSettingsPaletteColorMappingWidget::contextMenuDisplayRequested(QContextMenuEvent* event, float graphX, float /*graphY*/) { if (this->paletteColorMapping != NULL) { const PaletteThresholdTypeEnum::Enum threshType = this->paletteColorMapping->getThresholdType(); if (threshType != PaletteThresholdTypeEnum::THRESHOLD_TYPE_OFF) { const float absValue = std::fabs(graphX); AString textValue = NumericTextFormatting::formatValue(absValue); CursorDisplayScoped cursor; cursor.showCursor(Qt::ArrowCursor); QMenu menu(this); QAction* linkedAction = NULL; QAction* minThreshAction = NULL; QAction* maxThreshAction = NULL; if (this->paletteColorMapping->isThresholdNegMinPosMaxLinked()) { linkedAction = menu.addAction("Set Thresholds to -" + textValue + " and " + textValue); } else { minThreshAction = menu.addAction("Set Minimum Threshold to " + textValue); maxThreshAction = menu.addAction("Set Maximum Threshold to " + textValue); } QAction* selectedAction = menu.exec(event->globalPos()); if (selectedAction != NULL) { float minThresh = this->paletteColorMapping->getThresholdMinimum(threshType); float maxThresh = this->paletteColorMapping->getThresholdMaximum(threshType); if (selectedAction == linkedAction) { minThresh = -absValue; maxThresh = absValue; } else if (selectedAction == minThreshAction) { minThresh = graphX; } else if (selectedAction == maxThreshAction) { maxThresh = graphX; } updateAfterThresholdValuesChanged(minThresh, maxThresh); } } } } /** * Called to reset the view of the histogram chart. */ void MapSettingsPaletteColorMappingWidget::histogramResetViewButtonClicked() { this->thresholdPlot->setAxisAutoScale(QwtPlot::xBottom,true); this->thresholdPlot->setAxisAutoScale(QwtPlot::yLeft,true); this->updateHistogramPlot(); this->thresholdPlot->setAxisAutoScale(QwtPlot::xBottom,false); this->thresholdPlot->setAxisAutoScale(QwtPlot::yLeft,false); } /** * Create the palette section of the dialog. * @return * The palette section. */ QWidget* MapSettingsPaletteColorMappingWidget::createPaletteSection() { /* * Selection */ this->paletteNameComboBox = new QComboBox(); WuQtUtilities::setToolTipAndStatusTip(this->paletteNameComboBox, "Select palette for coloring map data"); this->paletteWidgetGroup->add(this->paletteNameComboBox); QObject::connect(this->paletteNameComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(applySelections())); /* * Interpolate Colors */ this->interpolateColorsCheckBox = new QCheckBox("Interpolate Colors"); WuQtUtilities::setToolTipAndStatusTip(this->interpolateColorsCheckBox, "Smooth colors for data between palette colors"); this->paletteWidgetGroup->add(this->interpolateColorsCheckBox); QObject::connect(this->interpolateColorsCheckBox, SIGNAL(toggled(bool)), this, SLOT(applySelections())); QWidget* paletteSelectionWidget = new QWidget(); QVBoxLayout* paletteSelectionLayout = new QVBoxLayout(paletteSelectionWidget); this->setLayoutSpacingAndMargins(paletteSelectionLayout); paletteSelectionLayout->addWidget(this->paletteNameComboBox); paletteSelectionLayout->addWidget(this->interpolateColorsCheckBox); paletteSelectionWidget->setFixedHeight(paletteSelectionWidget->sizeHint().height()); /* * Color Mapping */ this->scaleAutoRadioButton = new QRadioButton("Full"); //Auto Scale"); this->scaleAutoAbsolutePercentageRadioButton = new QRadioButton("Abs Pct"); this->scaleAutoPercentageRadioButton = new QRadioButton("Percent"); //"Auto Scale Percentage"); this->scaleFixedRadioButton = new QRadioButton("Fixed"); //"Fixed Scale"); WuQtUtilities::setToolTipAndStatusTip(this->scaleAutoRadioButton, "Map (most negative, zero, most positive) data values to (-1, 0, 0, 1) in palette"); WuQtUtilities::setToolTipAndStatusTip(this->scaleAutoAbsolutePercentageRadioButton, "Map (most absolute percentiles (NOT percentages) data values to (-1, 0, 0, 1) in palette"); WuQtUtilities::setToolTipAndStatusTip(this->scaleAutoPercentageRadioButton, "Map percentiles (NOT percentages) of (most neg, least neg, least pos, most pos) data values to (-1, 0, 0, 1) in palette"); WuQtUtilities::setToolTipAndStatusTip(this->scaleFixedRadioButton, "Map specified values (most neg, least neg, least pos, most pos) to (-1, 0, 0, 1) in palette"); QButtonGroup* scaleButtonGroup = new QButtonGroup(this); this->paletteWidgetGroup->add(scaleButtonGroup); scaleButtonGroup->addButton(this->scaleAutoRadioButton); scaleButtonGroup->addButton(this->scaleAutoAbsolutePercentageRadioButton); scaleButtonGroup->addButton(this->scaleAutoPercentageRadioButton); scaleButtonGroup->addButton(this->scaleFixedRadioButton); QObject::connect(scaleButtonGroup, SIGNAL(buttonClicked(int)), this, SLOT(applyAndUpdate())); /* * Spin box width */ const int percentSpinBoxWidth = 75; const int fixedSpinBoxWidth = 100; // fixed may have much larger data values /* * Percentage mapping */ this->scaleAutoPercentageNegativeMaximumSpinBox = WuQFactory::newDoubleSpinBoxWithMinMaxStepDecimalsSignalDouble(0.0, 100.0, 1.0, 2, this, SLOT(applySelections())); WuQtUtilities::setToolTipAndStatusTip(this->scaleAutoPercentageNegativeMaximumSpinBox, "Map percentile (NOT percentage) most negative value to -1.0 in palette"); this->paletteWidgetGroup->add(this->scaleAutoPercentageNegativeMaximumSpinBox); this->scaleAutoPercentageNegativeMaximumSpinBox->setFixedWidth(percentSpinBoxWidth); this->scaleAutoPercentageNegativeMinimumSpinBox = WuQFactory::newDoubleSpinBoxWithMinMaxStepDecimalsSignalDouble(0.0, 100.0, 1.0, 2, this, SLOT(applySelections())); WuQtUtilities::setToolTipAndStatusTip(this->scaleAutoPercentageNegativeMinimumSpinBox, "Map percentile (NOT percentage) least negative value to 0.0 in palette"); this->paletteWidgetGroup->add(this->scaleAutoPercentageNegativeMinimumSpinBox); this->scaleAutoPercentageNegativeMinimumSpinBox->setFixedWidth(percentSpinBoxWidth); this->scaleAutoPercentagePositiveMinimumSpinBox = WuQFactory::newDoubleSpinBoxWithMinMaxStepDecimalsSignalDouble(0.0, 100.0, 1.0, 2, this, SLOT(applySelections())); WuQtUtilities::setToolTipAndStatusTip(this->scaleAutoPercentagePositiveMinimumSpinBox, "Map percentile (NOT percentage) least positive value to 0.0 in palette"); this->paletteWidgetGroup->add(this->scaleAutoPercentagePositiveMinimumSpinBox); this->scaleAutoPercentagePositiveMinimumSpinBox->setFixedWidth(percentSpinBoxWidth); this->scaleAutoPercentagePositiveMaximumSpinBox = WuQFactory::newDoubleSpinBoxWithMinMaxStepDecimalsSignalDouble(0.0, 100.0, 1.0, 2, this, SLOT(applySelections())); WuQtUtilities::setToolTipAndStatusTip(this->scaleAutoPercentagePositiveMaximumSpinBox, "Map percentile (NOT percentage) most positive value to 1.0 in palette"); this->paletteWidgetGroup->add(this->scaleAutoPercentagePositiveMaximumSpinBox); this->scaleAutoPercentagePositiveMaximumSpinBox->setFixedWidth(percentSpinBoxWidth); /* * Absolute percentage mapping */ this->scaleAutoAbsolutePercentageMinimumSpinBox = WuQFactory::newDoubleSpinBoxWithMinMaxStepDecimalsSignalDouble(0.0, 100.0, 1.0, 2, this, SLOT(applySelections())); WuQtUtilities::setToolTipAndStatusTip(this->scaleAutoAbsolutePercentageMinimumSpinBox, "Map percentile (NOT percentage) least absolute value to 0.0 in palette"); this->paletteWidgetGroup->add(this->scaleAutoAbsolutePercentageMinimumSpinBox); this->scaleAutoAbsolutePercentageMinimumSpinBox->setFixedWidth(percentSpinBoxWidth); this->scaleAutoAbsolutePercentageMaximumSpinBox = WuQFactory::newDoubleSpinBoxWithMinMaxStepDecimalsSignalDouble(0.0, 100.0, 1.0, 2, this, SLOT(applySelections())); WuQtUtilities::setToolTipAndStatusTip(this->scaleAutoAbsolutePercentageMaximumSpinBox, "Map percentile (NOT percentage) most absolute value to 1.0 in palette"); this->paletteWidgetGroup->add(this->scaleAutoAbsolutePercentageMaximumSpinBox); this->scaleAutoAbsolutePercentageMaximumSpinBox->setFixedWidth(percentSpinBoxWidth); /* * Fixed mapping */ this->scaleFixedNegativeMaximumSpinBox = WuQFactory::newDoubleSpinBoxWithMinMaxStepDecimalsSignalDouble(-BIG_NUMBER, 0.0, 1.0, 3, this, SLOT(applySelections())); WuQtUtilities::setToolTipAndStatusTip(this->scaleFixedNegativeMaximumSpinBox, "Map this value to -1.0 in palette"); this->paletteWidgetGroup->add(this->scaleFixedNegativeMaximumSpinBox); this->scaleFixedNegativeMaximumSpinBox->setFixedWidth(fixedSpinBoxWidth); this->scaleFixedNegativeMinimumSpinBox = WuQFactory::newDoubleSpinBoxWithMinMaxStepDecimalsSignalDouble(-BIG_NUMBER, 0.0, 1.0, 3, this, SLOT(applySelections())); WuQtUtilities::setToolTipAndStatusTip(this->scaleFixedNegativeMinimumSpinBox, "Map this value to 0.0 in palette"); this->paletteWidgetGroup->add(this->scaleFixedNegativeMinimumSpinBox); this->scaleFixedNegativeMinimumSpinBox->setFixedWidth(fixedSpinBoxWidth); this->scaleFixedPositiveMinimumSpinBox = WuQFactory::newDoubleSpinBoxWithMinMaxStepDecimalsSignalDouble(0.0, BIG_NUMBER, 1.0, 3, this, SLOT(applySelections())); WuQtUtilities::setToolTipAndStatusTip(this->scaleFixedPositiveMinimumSpinBox, "Map this value to 0.0 in palette"); this->paletteWidgetGroup->add(this->scaleFixedPositiveMinimumSpinBox); this->scaleFixedPositiveMinimumSpinBox->setFixedWidth(fixedSpinBoxWidth); this->scaleFixedPositiveMaximumSpinBox = WuQFactory::newDoubleSpinBoxWithMinMaxStepDecimalsSignalDouble(0.0, BIG_NUMBER, 1.0, 3, this, SLOT(applySelections())); WuQtUtilities::setToolTipAndStatusTip(this->scaleFixedPositiveMaximumSpinBox, "Map this value to 1.0 in palette"); this->paletteWidgetGroup->add(this->scaleFixedPositiveMaximumSpinBox); this->scaleFixedPositiveMaximumSpinBox->setFixedWidth(fixedSpinBoxWidth); QWidget* colorMappingWidget = new QWidget(); QGridLayout* colorMappingLayout = new QGridLayout(colorMappingWidget); colorMappingLayout->setColumnStretch(0, 0); colorMappingLayout->setColumnStretch(1, 100); colorMappingLayout->setColumnStretch(2, 100); this->setLayoutSpacingAndMargins(colorMappingLayout); colorMappingLayout->addWidget(this->scaleAutoRadioButton, 0, 0, Qt::AlignHCenter); colorMappingLayout->addWidget(this->scaleAutoAbsolutePercentageRadioButton, 0, 1, Qt::AlignHCenter); colorMappingLayout->addWidget(this->scaleAutoPercentageRadioButton, 0, 2, Qt::AlignHCenter); colorMappingLayout->addWidget(this->scaleFixedRadioButton, 0, 3, Qt::AlignHCenter); colorMappingLayout->addWidget(new QLabel("Pos Max"), 1, 0, Qt::AlignRight); colorMappingLayout->addWidget(new QLabel("Pos Min"), 2, 0, Qt::AlignRight); colorMappingLayout->addWidget(new QLabel("Neg Min"), 3, 0, Qt::AlignRight); colorMappingLayout->addWidget(new QLabel("Neg Max"), 4, 0, Qt::AlignRight); colorMappingLayout->addWidget(this->scaleAutoAbsolutePercentageMaximumSpinBox, 1, 1); colorMappingLayout->addWidget(this->scaleAutoAbsolutePercentageMinimumSpinBox, 2, 1); colorMappingLayout->addWidget(this->scaleAutoPercentagePositiveMaximumSpinBox, 1, 2); colorMappingLayout->addWidget(this->scaleAutoPercentagePositiveMinimumSpinBox, 2, 2); colorMappingLayout->addWidget(this->scaleAutoPercentageNegativeMinimumSpinBox, 3, 2); colorMappingLayout->addWidget(this->scaleAutoPercentageNegativeMaximumSpinBox, 4, 2); colorMappingLayout->addWidget(this->scaleFixedPositiveMaximumSpinBox, 1, 3); colorMappingLayout->addWidget(this->scaleFixedPositiveMinimumSpinBox, 2, 3); colorMappingLayout->addWidget(this->scaleFixedNegativeMinimumSpinBox, 3, 3); colorMappingLayout->addWidget(this->scaleFixedNegativeMaximumSpinBox, 4, 3); /* * Display Mode */ this->displayModePositiveCheckBox = new QCheckBox("Positive"); this->paletteWidgetGroup->add(this->displayModePositiveCheckBox); QObject::connect(this->displayModePositiveCheckBox, SIGNAL(toggled(bool)), this, SLOT(applySelections())); this->displayModeZeroCheckBox = new QCheckBox("Zero"); this->paletteWidgetGroup->add(this->displayModeZeroCheckBox); QObject::connect(this->displayModeZeroCheckBox, SIGNAL(toggled(bool)), this, SLOT(applySelections())); this->displayModeNegativeCheckBox = new QCheckBox("Negative"); this->paletteWidgetGroup->add(this->displayModeNegativeCheckBox); QObject::connect(this->displayModeNegativeCheckBox , SIGNAL(toggled(bool)), this, SLOT(applySelections())); WuQtUtilities::setToolTipAndStatusTip(this->displayModePositiveCheckBox, "Enable/Disable the display of positive data"); WuQtUtilities::setToolTipAndStatusTip(this->displayModeZeroCheckBox, "Enable/Disable the display of zero data.\n" "A value in the range [" // JWH 24 April 2015+ AString::number(NodeAndVoxelColoring::SMALL_NEGATIVE, 'f', 6) + AString::number(PaletteColorMapping::SMALL_NEGATIVE, 'f', 6) + ", " // JWH 24 April 2015+ AString::number(NodeAndVoxelColoring::SMALL_POSITIVE, 'f', 6) + AString::number(PaletteColorMapping::SMALL_POSITIVE, 'f', 6) + "]\n" "is considered to be zero."); WuQtUtilities::setToolTipAndStatusTip(this->displayModeNegativeCheckBox, "Enable/Disable the display of negative data"); QWidget* displayModeWidget = new QWidget(); QHBoxLayout* displayModeLayout = new QHBoxLayout(displayModeWidget); WuQtUtilities::setLayoutSpacingAndMargins(displayModeLayout, 10, 3); displayModeLayout->addWidget(this->displayModeNegativeCheckBox); displayModeLayout->addStretch(); displayModeLayout->addWidget(this->displayModeZeroCheckBox); displayModeLayout->addStretch(); displayModeLayout->addWidget(this->displayModePositiveCheckBox); /* * Layout widgets */ QGroupBox* paletteGroupBox = new QGroupBox("Palette"); QVBoxLayout* paletteLayout = new QVBoxLayout(paletteGroupBox); this->setLayoutSpacingAndMargins(paletteLayout); paletteLayout->addWidget(paletteSelectionWidget); paletteLayout->addWidget(WuQtUtilities::createHorizontalLineWidget()); paletteLayout->addWidget(colorMappingWidget); paletteLayout->addWidget(WuQtUtilities::createHorizontalLineWidget()); paletteLayout->addWidget(displayModeWidget); paletteGroupBox->setFixedHeight(paletteGroupBox->sizeHint().height()); return paletteGroupBox; } /** * Update contents for editing a map settings for data in a caret * mappable data file. * * @param caretMappableDataFile * Data file containing palette that is edited. * @param mapIndex * Index of map for palette that is edited. */ void MapSettingsPaletteColorMappingWidget::updateEditor(CaretMappableDataFile* caretMappableDataFile, const int32_t mapIndex) { const bool palettesEqualFlag = caretMappableDataFile->isPaletteColorMappingEqualForAllMaps(); updateEditorInternal(caretMappableDataFile, mapIndex); if (m_previousCaretMappableDataFile != caretMappableDataFile) { this->applyAllMapsCheckBox->blockSignals(true); this->applyAllMapsCheckBox->setChecked(palettesEqualFlag); this->applyAllMapsCheckBox->blockSignals(false); } m_previousCaretMappableDataFile = caretMappableDataFile; } /** * Update the threshold section. */ void MapSettingsPaletteColorMappingWidget::updateThresholdSection() { this->thresholdWidgetGroup->blockAllSignals(true); const int32_t numTypes = this->thresholdTypeComboBox->count(); for (int32_t i = 0; i < numTypes; i++) { const int value = this->thresholdTypeComboBox->itemData(i).toInt(); if (value == static_cast(this->paletteColorMapping->getThresholdType())) { this->thresholdTypeComboBox->setCurrentIndex(i); break; } } const bool enableThresholdControls = (this->paletteColorMapping->getThresholdType() != PaletteThresholdTypeEnum::THRESHOLD_TYPE_OFF); this->thresholdAdjustmentWidgetGroup->setEnabled(enableThresholdControls); const float lowValue = this->paletteColorMapping->getThresholdMinimum(this->paletteColorMapping->getThresholdType()); const float highValue = this->paletteColorMapping->getThresholdMaximum(this->paletteColorMapping->getThresholdType()); switch (this->paletteColorMapping->getThresholdTest()) { case PaletteThresholdTestEnum::THRESHOLD_TEST_SHOW_OUTSIDE: this->thresholdShowOutsideRadioButton->setChecked(true); break; case PaletteThresholdTestEnum::THRESHOLD_TEST_SHOW_INSIDE: this->thresholdShowInsideRadioButton->setChecked(true); break; } const PaletteThresholdRangeModeEnum::Enum thresholdRangeMode = paletteColorMapping->getThresholdRangeMode(); this->thresholdRangeModeComboBox->blockSignals(true); this->thresholdRangeModeComboBox->setSelectedItem(thresholdRangeMode); this->thresholdRangeModeComboBox->blockSignals(false); updateThresholdControlsMinimumMaximumRangeValues(); this->thresholdLowSlider->setValue(lowValue); this->thresholdHighSlider->setValue(highValue); if (allowUpdateOfThresholdLowSpinBox) { this->thresholdLowSpinBox->setValue(lowValue); } if (allowUpdateOfThresholdHighSpinBox) { this->thresholdHighSpinBox->setValue(highValue); } this->thresholdLinkCheckBox->setChecked(this->paletteColorMapping->isThresholdNegMinPosMaxLinked()); this->thresholdWidgetGroup->blockAllSignals(false); } /** * This PRIVATE method updates the editor content and MUST always be used * when something within this class requires updating the displayed data. * * Update contents for editing a map settings for data in a caret * mappable data file. * * @param caretMappableDataFile * Data file containing palette that is edited. * @param mapIndexIn * Index of map for palette that is edited. */ void MapSettingsPaletteColorMappingWidget::updateEditorInternal(CaretMappableDataFile* caretMappableDataFile, const int32_t mapIndexIn) { this->caretMappableDataFile = caretMappableDataFile; this->mapFileIndex = mapIndexIn; if (this->caretMappableDataFile == NULL) { return; } else if (this->mapFileIndex < 0) { return; } this->paletteWidgetGroup->blockAllSignals(true); const AString title = this->caretMappableDataFile->getFileNameNoPath() + ": " + this->caretMappableDataFile->getMapName(this->mapFileIndex); this->setWindowTitle(title); this->paletteNameComboBox->clear(); this->paletteColorMapping = this->caretMappableDataFile->getMapPaletteColorMapping(this->mapFileIndex); if (this->paletteColorMapping != NULL) { PaletteFile* paletteFile = GuiManager::get()->getBrain()->getPaletteFile(); int defaultIndex = 0; const int32_t numPalettes = paletteFile->getNumberOfPalettes(); for (int32_t i = 0; i < numPalettes; i++) { Palette* palette = paletteFile->getPalette(i); const AString name = palette->getName(); if (name == this->paletteColorMapping->getSelectedPaletteName()) { defaultIndex = i; } this->paletteNameComboBox->addItem(name, name); } if (defaultIndex < this->paletteNameComboBox->count()) { this->paletteNameComboBox->setCurrentIndex(defaultIndex); } bool isPercentageSpinBoxesEnabled = false; bool isAbsolutePercentageSpinBoxesEnabled = false; bool isFixedSpinBoxesEnabled = false; switch (this->paletteColorMapping->getScaleMode()) { case PaletteScaleModeEnum::MODE_AUTO_SCALE: this->scaleAutoRadioButton->setChecked(true); break; case PaletteScaleModeEnum::MODE_AUTO_SCALE_ABSOLUTE_PERCENTAGE: this->scaleAutoAbsolutePercentageRadioButton->setChecked(true); isAbsolutePercentageSpinBoxesEnabled = true; break; case PaletteScaleModeEnum::MODE_AUTO_SCALE_PERCENTAGE: this->scaleAutoPercentageRadioButton->setChecked(true); isPercentageSpinBoxesEnabled = true; break; case PaletteScaleModeEnum::MODE_USER_SCALE: this->scaleFixedRadioButton->setChecked(true); isFixedSpinBoxesEnabled = true; break; } this->scaleAutoPercentageNegativeMaximumSpinBox->setValue(this->paletteColorMapping->getAutoScalePercentageNegativeMaximum()); this->scaleAutoPercentageNegativeMinimumSpinBox->setValue(this->paletteColorMapping->getAutoScalePercentageNegativeMinimum()); this->scaleAutoPercentagePositiveMinimumSpinBox->setValue(this->paletteColorMapping->getAutoScalePercentagePositiveMinimum()); this->scaleAutoPercentagePositiveMaximumSpinBox->setValue(this->paletteColorMapping->getAutoScalePercentagePositiveMaximum()); this->scaleAutoPercentageNegativeMaximumSpinBox->setEnabled(isPercentageSpinBoxesEnabled); this->scaleAutoPercentageNegativeMinimumSpinBox->setEnabled(isPercentageSpinBoxesEnabled); this->scaleAutoPercentagePositiveMinimumSpinBox->setEnabled(isPercentageSpinBoxesEnabled); this->scaleAutoPercentagePositiveMaximumSpinBox->setEnabled(isPercentageSpinBoxesEnabled); this->scaleAutoAbsolutePercentageMaximumSpinBox->setValue(this->paletteColorMapping->getAutoScaleAbsolutePercentageMaximum()); this->scaleAutoAbsolutePercentageMinimumSpinBox->setValue(this->paletteColorMapping->getAutoScaleAbsolutePercentageMinimum()); this->scaleAutoAbsolutePercentageMaximumSpinBox->setEnabled(isAbsolutePercentageSpinBoxesEnabled); this->scaleAutoAbsolutePercentageMinimumSpinBox->setEnabled(isAbsolutePercentageSpinBoxesEnabled); this->scaleFixedNegativeMaximumSpinBox->setValue(this->paletteColorMapping->getUserScaleNegativeMaximum()); this->scaleFixedNegativeMinimumSpinBox->setValue(this->paletteColorMapping->getUserScaleNegativeMinimum()); this->scaleFixedPositiveMinimumSpinBox->setValue(this->paletteColorMapping->getUserScalePositiveMinimum()); this->scaleFixedPositiveMaximumSpinBox->setValue(this->paletteColorMapping->getUserScalePositiveMaximum()); this->scaleFixedNegativeMaximumSpinBox->setEnabled(isFixedSpinBoxesEnabled); this->scaleFixedNegativeMinimumSpinBox->setEnabled(isFixedSpinBoxesEnabled); this->scaleFixedPositiveMinimumSpinBox->setEnabled(isFixedSpinBoxesEnabled); this->scaleFixedPositiveMaximumSpinBox->setEnabled(isFixedSpinBoxesEnabled); this->displayModePositiveCheckBox->setChecked(this->paletteColorMapping->isDisplayPositiveDataFlag()); this->displayModeZeroCheckBox->setChecked(this->paletteColorMapping->isDisplayZeroDataFlag()); this->displayModeNegativeCheckBox->setChecked(this->paletteColorMapping->isDisplayNegativeDataFlag()); this->interpolateColorsCheckBox->setChecked(this->paletteColorMapping->isInterpolatePaletteFlag()); updateThresholdSection(); float minValue = 0.0; float maxValue = 0.0; FastStatistics* statistics = NULL; switch (this->caretMappableDataFile->getPaletteNormalizationMode()) { case PaletteNormalizationModeEnum::NORMALIZATION_ALL_MAP_DATA: statistics = const_cast(this->caretMappableDataFile->getFileFastStatistics()); break; case PaletteNormalizationModeEnum::NORMALIZATION_SELECTED_MAP_DATA: statistics = const_cast(this->caretMappableDataFile->getMapFastStatistics(this->mapFileIndex)); break; } if (statistics != NULL) { minValue = statistics->getMin(); maxValue = statistics->getMax(); } /* * Set fixed spin boxes so that they increment by 1% of data. */ float stepValue = 1.0; const float diff = maxValue - minValue; if (diff > 0.0) { stepValue = diff / 100.0; } this->scaleFixedNegativeMaximumSpinBox->setSingleStep(stepValue); this->scaleFixedNegativeMinimumSpinBox->setSingleStep(stepValue); this->scaleFixedPositiveMinimumSpinBox->setSingleStep(stepValue); this->scaleFixedPositiveMaximumSpinBox->setSingleStep(stepValue); } this->updateHistogramPlot(); this->updateNormalizationControlSection(); this->paletteWidgetGroup->blockAllSignals(false); } /** * Get histogram for displaying data * @param statisticsForAll * Statistics for all data. * @param * Histogram. */ const Histogram* MapSettingsPaletteColorMappingWidget::getHistogram(const FastStatistics* statisticsForAll) const { float mostPos = 0.0; float leastPos = 0.0; float leastNeg = 0.0; float mostNeg = 0.0; bool matchFlag = false; if (this->histogramAllRadioButton->isChecked()) { float dummy; statisticsForAll->getNonzeroRanges(mostNeg, dummy, dummy, mostPos); } else if (this->histogramMatchPaletteRadioButton->isChecked()) { matchFlag = true; switch (this->paletteColorMapping->getScaleMode()) { case PaletteScaleModeEnum::MODE_AUTO_SCALE: mostPos = statisticsForAll->getMax(); leastPos = 0.0; leastNeg = 0.0; mostNeg = statisticsForAll->getMin(); break; case PaletteScaleModeEnum::MODE_AUTO_SCALE_ABSOLUTE_PERCENTAGE: mostPos = -statisticsForAll->getApproxAbsolutePercentile(this->scaleAutoAbsolutePercentageMaximumSpinBox->value()); leastPos = -statisticsForAll->getApproxAbsolutePercentile(this->scaleAutoAbsolutePercentageMinimumSpinBox->value()); leastNeg = statisticsForAll->getApproxAbsolutePercentile(this->scaleAutoAbsolutePercentageMinimumSpinBox->value()); mostNeg = statisticsForAll->getApproxAbsolutePercentile(this->scaleAutoAbsolutePercentageMaximumSpinBox->value()); break; case PaletteScaleModeEnum::MODE_AUTO_SCALE_PERCENTAGE: mostPos = statisticsForAll->getApproxPositivePercentile(this->scaleAutoPercentagePositiveMaximumSpinBox->value()); leastPos = statisticsForAll->getApproxPositivePercentile(this->scaleAutoPercentagePositiveMinimumSpinBox->value()); leastNeg = statisticsForAll->getApproxNegativePercentile(this->scaleAutoPercentageNegativeMinimumSpinBox->value()); mostNeg = statisticsForAll->getApproxNegativePercentile(this->scaleAutoPercentageNegativeMaximumSpinBox->value()); break; case PaletteScaleModeEnum::MODE_USER_SCALE: mostPos = this->scaleFixedPositiveMaximumSpinBox->value(); leastPos = this->scaleFixedPositiveMinimumSpinBox->value(); leastNeg = this->scaleFixedNegativeMinimumSpinBox->value(); mostNeg = this->scaleFixedNegativeMaximumSpinBox->value(); break; } } else { CaretAssert(0); } const PaletteNormalizationModeEnum::Enum normMode = this->caretMappableDataFile->getPaletteNormalizationMode(); /* * Remove data that is not displayed */ bool isZeroIncluded = true; const Histogram* ret = NULL; if (matchFlag) { isZeroIncluded = this->displayModeZeroCheckBox->isChecked(); if (this->displayModeNegativeCheckBox->isChecked() == false) { mostNeg = 0.0; leastNeg = 0.0; } if (this->displayModePositiveCheckBox->isChecked() == false) { mostPos = 0.0; leastPos = 0.0; } switch (normMode) { case PaletteNormalizationModeEnum::NORMALIZATION_ALL_MAP_DATA: ret = this->caretMappableDataFile->getFileHistogram(mostPos, leastPos, leastNeg, mostNeg, isZeroIncluded); break; case PaletteNormalizationModeEnum::NORMALIZATION_SELECTED_MAP_DATA: ret = this->caretMappableDataFile->getMapHistogram(this->mapFileIndex, mostPos, leastPos, leastNeg, mostNeg, isZeroIncluded); break; } } else { switch (normMode) { case PaletteNormalizationModeEnum::NORMALIZATION_ALL_MAP_DATA: ret = caretMappableDataFile->getFileHistogram(); break; case PaletteNormalizationModeEnum::NORMALIZATION_SELECTED_MAP_DATA: ret = caretMappableDataFile->getMapHistogram(this->mapFileIndex); break; } } CaretAssert(ret); return ret; } /** * Update the histogram plot. */ void MapSettingsPaletteColorMappingWidget::updateHistogramPlot() { /* * Remove all previously attached items from the histogram plot. * The items are automatically deleted by the plot. */ this->thresholdPlot->detachItems(); FastStatistics* statistics = NULL; switch (this->caretMappableDataFile->getPaletteNormalizationMode()) { case PaletteNormalizationModeEnum::NORMALIZATION_ALL_MAP_DATA: statistics = const_cast(this->caretMappableDataFile->getFileFastStatistics()); break; case PaletteNormalizationModeEnum::NORMALIZATION_SELECTED_MAP_DATA: statistics = const_cast(this->caretMappableDataFile->getMapFastStatistics(this->mapFileIndex)); break; } if ((this->paletteColorMapping != NULL) && (statistics != NULL)) { PaletteFile* paletteFile = GuiManager::get()->getBrain()->getPaletteFile(); /* * Data values table */ const float statsMean = statistics->getMean(); const float statsStdDev = statistics->getSampleStdDev(); const float statsMin = statistics->getMin(); const float statsMax = statistics->getMax(); this->statisticsMeanValueLabel->setText(QString::number(statsMean, 'f', 4)); this->statisticsStandardDeviationLabel->setText(QString::number(statsStdDev, 'f', 4)); this->statisticsMaximumValueLabel->setText(QString::number(statsMax, 'f', 4)); this->statisticsMinimumValueLabel->setText(QString::number(statsMin, 'f', 4)); /* * Get data for this histogram. */ const Histogram* myHist = getHistogram(statistics); //const int64_t* histogram = const_cast(statistics->getHistogram()); float minValue, maxValue; myHist->getRange(minValue, maxValue); const std::vector& displayData = myHist->getHistogramDisplay(); const int64_t numHistogramValues = (int64_t)(displayData.size()); /* * Width of each 'bar' in the histogram */ float step = 1.0; if (numHistogramValues > 1) { step = ((maxValue - minValue) / numHistogramValues); } float* dataValues = NULL; float* dataRGBA = NULL; if (numHistogramValues > 0) { /* * Compute color for 'bar' in the histogram. * Note that number of data values is one more * than the number of histogram values due to that * a data value is needed on the right of the last * histogram bar. Otherwise, if there is an outlier * value, the histogram will not be drawn * correctly. */ const int64_t numDataValues = numHistogramValues + 1; dataValues = new float[numDataValues]; dataRGBA = new float[numDataValues * 4]; for (int64_t ix = 0; ix < numDataValues; ix++) { const float value = (minValue + (ix * step)); dataValues[ix] = value; } dataValues[0] = minValue; dataValues[numDataValues - 1] = maxValue; const Palette* palette = paletteFile->getPaletteByName(this->paletteColorMapping->getSelectedPaletteName()); if (this->histogramUsePaletteColors->isChecked() && (palette != NULL)) { NodeAndVoxelColoring::colorScalarsWithPalette(statistics, paletteColorMapping, palette, dataValues, dataValues, numDataValues, dataRGBA, true); // ignore thresholding } else { for (int64_t i = 0; i < numDataValues; i++) { const int64_t i4 = i * 4; dataRGBA[i4] = 1.0; dataRGBA[i4+1] = 0.0; dataRGBA[i4+2] = 0.0; dataRGBA[i4+3] = 1.0; } } } const bool displayZeros = paletteColorMapping->isDisplayZeroDataFlag(); float z = 0.0; float maxDataFrequency = 0.0; for (int64_t ix = 0; ix < numHistogramValues; ix++) { QColor color; const int64_t ix4 = ix * 4; color.setRedF(dataRGBA[ix4]); color.setGreenF(dataRGBA[ix4+1]); color.setBlueF(dataRGBA[ix4+2]); color.setAlphaF(1.0); const float startValue = dataValues[ix]; const float stopValue = dataValues[ix + 1]; float dataFrequency = displayData[ix]; bool displayIt = true; if (displayZeros == false) { if ((startValue <= 0.0) && (stopValue >= 0.0)) { dataFrequency = 0.0; displayIt = false; } } if (dataFrequency > maxDataFrequency) { maxDataFrequency = dataFrequency; } /* * If color is not displayed ('none' or thresholded), * set its frequncey value to a small value so that the plot * retains its shape and color is still slightly visible */ //Qt::BrushStyle brushStyle = Qt::SolidPattern; if (dataRGBA[ix4+3] <= 0.0) { displayIt = false; } if (displayIt == false) { color.setAlpha(0); } QVector samples; samples.push_back(QPointF(startValue, dataFrequency)); samples.push_back(QPointF(stopValue, dataFrequency)); QwtPlotCurve* curve = new QwtPlotCurve(); curve->setRenderHint(QwtPlotItem::RenderAntialiased); curve->setVisible(true); curve->setStyle(QwtPlotCurve::Steps); curve->setBrush(QBrush(color)); //, brushStyle)); curve->setPen(QPen(color)); curve->setSamples(samples); curve->attach(this->thresholdPlot); if (ix == 0) { z = curve->z(); } } z = z - 1; if ((numHistogramValues > 2) && (this->paletteColorMapping->getThresholdType() != PaletteThresholdTypeEnum::THRESHOLD_TYPE_OFF)) { float threshMinValue = this->paletteColorMapping->getThresholdNormalMinimum(); float threshMaxValue = this->paletteColorMapping->getThresholdNormalMaximum(); maxDataFrequency *= 1.05; switch (this->paletteColorMapping->getThresholdTest()) { case PaletteThresholdTestEnum::THRESHOLD_TEST_SHOW_INSIDE: { const float plotMinValue = this->thresholdPlot->axisScaleDiv(QwtPlot::xBottom)->lowerBound(); const float plotMaxValue = this->thresholdPlot->axisScaleDiv(QwtPlot::xBottom)->upperBound(); /* * Draw shaded region to left of minimum threshold */ QVector minSamples; minSamples.push_back(QPointF(plotMinValue, maxDataFrequency)); minSamples.push_back(QPointF(threshMinValue, maxDataFrequency)); QwtPlotCurve* minBox = new QwtPlotCurve(); minBox->setRenderHint(QwtPlotItem::RenderAntialiased); minBox->setVisible(true); minBox->setStyle(QwtPlotCurve::Dots); QColor minColor(100, 100, 255, 160); minBox->setBrush(QBrush(minColor, Qt::Dense4Pattern)); minBox->setPen(QPen(minColor)); minBox->setSamples(minSamples); minBox->setZ(z); minBox->attach(this->thresholdPlot); /* * Draw shaded region to right of maximum threshold */ QVector maxSamples; maxSamples.push_back(QPointF(threshMaxValue, maxDataFrequency)); maxSamples.push_back(QPointF(plotMaxValue, maxDataFrequency)); QwtPlotCurve* maxBox = new QwtPlotCurve(); maxBox->setRenderHint(QwtPlotItem::RenderAntialiased); maxBox->setVisible(true); maxBox->setStyle(QwtPlotCurve::Dots); QColor maxColor(100, 100, 255, 160); maxBox->setBrush(QBrush(maxColor, Qt::Dense4Pattern)); maxBox->setPen(QPen(maxColor)); maxBox->setSamples(maxSamples); maxBox->setZ(z); maxBox->attach(this->thresholdPlot); } break; case PaletteThresholdTestEnum::THRESHOLD_TEST_SHOW_OUTSIDE: { /* * Draw shaded region between minimum and maximum threshold */ QVector minSamples; minSamples.push_back(QPointF(threshMinValue, maxDataFrequency)); minSamples.push_back(QPointF(threshMaxValue, maxDataFrequency)); QwtPlotCurve* minBox = new QwtPlotCurve(); minBox->setRenderHint(QwtPlotItem::RenderAntialiased); minBox->setVisible(true); QColor minColor(100, 100, 255, 160); minBox->setBrush(QBrush(minColor)); //, Qt::Dense4Pattern)); minBox->setPen(QPen(minColor)); minBox->setSamples(minSamples); minBox->setZ(z); minBox->attach(this->thresholdPlot); } break; } const bool showLinesFlag = false; if (showLinesFlag) { /* * Line for high threshold */ QVector minSamples; minSamples.push_back(QPointF(threshMinValue, 0)); minSamples.push_back(QPointF(threshMinValue, maxDataFrequency)); QwtPlotCurve* minLine = new QwtPlotCurve(); minLine->setRenderHint(QwtPlotItem::RenderAntialiased); minLine->setVisible(true); minLine->setStyle(QwtPlotCurve::Lines); QColor minColor(0.0, 0.0, 1.0); minLine->setPen(QPen(minColor)); minLine->setSamples(minSamples); minLine->setZ(1.0); minLine->attach(this->thresholdPlot); /* * Line for high threshold */ QVector maxSamples; maxSamples.push_back(QPointF(threshMaxValue, 0)); maxSamples.push_back(QPointF(threshMaxValue, maxDataFrequency)); QwtPlotCurve* maxLine = new QwtPlotCurve(); maxLine->setRenderHint(QwtPlotItem::RenderAntialiased); maxLine->setVisible(true); maxLine->setStyle(QwtPlotCurve::Lines); QColor maxColor(1.0, 0.0, 0.0); maxLine->setPen(QPen(maxColor)); maxLine->setSamples(maxSamples); maxLine->setZ(1.0); maxLine->attach(this->thresholdPlot); } } if (dataValues != NULL) { delete[] dataValues; } if (dataRGBA != NULL) { delete[] dataRGBA; } /* * Causes updates of plots. */ this->thresholdPlot->replot(); } } /** * Called to apply selections. */ void MapSettingsPaletteColorMappingWidget::applySelections() { if (this->caretMappableDataFile == NULL) { return; } else if (this->mapFileIndex < 0) { return; } const int itemIndex = this->paletteNameComboBox->currentIndex(); if (itemIndex >= 0) { const AString name = this->paletteNameComboBox->itemData(itemIndex).toString(); if (this->paletteColorMapping != NULL) { this->paletteColorMapping->setSelectedPaletteName(name); } } if (this->scaleAutoRadioButton->isChecked()) { this->paletteColorMapping->setScaleMode(PaletteScaleModeEnum::MODE_AUTO_SCALE); } else if (this->scaleAutoAbsolutePercentageRadioButton->isChecked()) { this->paletteColorMapping->setScaleMode(PaletteScaleModeEnum::MODE_AUTO_SCALE_ABSOLUTE_PERCENTAGE); } else if (this->scaleAutoPercentageRadioButton->isChecked()) { this->paletteColorMapping->setScaleMode(PaletteScaleModeEnum::MODE_AUTO_SCALE_PERCENTAGE); } else if (this->scaleFixedRadioButton->isChecked()) { this->paletteColorMapping->setScaleMode(PaletteScaleModeEnum::MODE_USER_SCALE); } this->paletteColorMapping->setUserScaleNegativeMaximum(this->scaleFixedNegativeMaximumSpinBox->value()); this->paletteColorMapping->setUserScaleNegativeMinimum(this->scaleFixedNegativeMinimumSpinBox->value()); this->paletteColorMapping->setUserScalePositiveMinimum(this->scaleFixedPositiveMinimumSpinBox->value()); this->paletteColorMapping->setUserScalePositiveMaximum(this->scaleFixedPositiveMaximumSpinBox->value()); this->paletteColorMapping->setAutoScalePercentageNegativeMaximum(this->scaleAutoPercentageNegativeMaximumSpinBox->value()); this->paletteColorMapping->setAutoScalePercentageNegativeMinimum(this->scaleAutoPercentageNegativeMinimumSpinBox->value()); this->paletteColorMapping->setAutoScalePercentagePositiveMinimum(this->scaleAutoPercentagePositiveMinimumSpinBox->value()); this->paletteColorMapping->setAutoScalePercentagePositiveMaximum(this->scaleAutoPercentagePositiveMaximumSpinBox->value()); this->paletteColorMapping->setAutoScaleAbsolutePercentageMaximum(this->scaleAutoAbsolutePercentageMaximumSpinBox->value()); this->paletteColorMapping->setAutoScaleAbsolutePercentageMinimum(this->scaleAutoAbsolutePercentageMinimumSpinBox->value()); this->paletteColorMapping->setDisplayPositiveDataFlag(this->displayModePositiveCheckBox->isChecked()); this->paletteColorMapping->setDisplayNegativeDataFlag(this->displayModeNegativeCheckBox->isChecked()); this->paletteColorMapping->setDisplayZeroDataFlag(this->displayModeZeroCheckBox->isChecked()); this->paletteColorMapping->setInterpolatePaletteFlag(this->interpolateColorsCheckBox->isChecked()); float lowValue = this->thresholdLowSpinBox->value(); float highValue = this->thresholdHighSpinBox->value(); const int thresholdTypeIndex = this->thresholdTypeComboBox->currentIndex(); PaletteThresholdTypeEnum::Enum paletteThresholdType = static_cast(this->thresholdTypeComboBox->itemData(thresholdTypeIndex).toInt()); this->paletteColorMapping->setThresholdType(paletteThresholdType); this->paletteColorMapping->setThresholdMinimum(paletteThresholdType, lowValue); this->paletteColorMapping->setThresholdMaximum(paletteThresholdType, highValue); if (this->thresholdShowInsideRadioButton->isChecked()) { this->paletteColorMapping->setThresholdTest(PaletteThresholdTestEnum::THRESHOLD_TEST_SHOW_INSIDE); } else if (this->thresholdShowOutsideRadioButton->isChecked()) { this->paletteColorMapping->setThresholdTest(PaletteThresholdTestEnum::THRESHOLD_TEST_SHOW_OUTSIDE); } bool assignToAllMaps = false; if (this->applyAllMapsCheckBox->checkState() == Qt::Checked) { assignToAllMaps = true; const int numMaps = this->caretMappableDataFile->getNumberOfMaps(); for (int32_t i = 0; i < numMaps; i++) { PaletteColorMapping* pcm = this->caretMappableDataFile->getMapPaletteColorMapping(i); if (pcm != this->paletteColorMapping) { pcm->copy(*this->paletteColorMapping); } } } /* * If no map attributes (one palette applies to all maps), * update coloring for all maps since all of the maps will * need their coloring updated. */ if (this->caretMappableDataFile->hasMapAttributes() == false) { assignToAllMaps = true; } this->updateHistogramPlot(); updateColoringAndGraphics(); } /** * Set the layout margins. * @param layout * Layout for which margins are set. */ void MapSettingsPaletteColorMappingWidget::setLayoutSpacingAndMargins(QLayout* layout) { WuQtUtilities::setLayoutSpacingAndMargins(layout, 5, 3); } /** * @return The normalization control section. */ QWidget* MapSettingsPaletteColorMappingWidget::createNormalizationControlSection() { m_normalizationModeComboBox = new QComboBox(); QObject::connect(m_normalizationModeComboBox, SIGNAL(activated(int)), this, SLOT(normalizationModeComboBoxActivated(int))); /* * Initially load with all modes so that the combo box can * be set to a fixed size that allows display of all text */ std::vector allModes; PaletteNormalizationModeEnum::getAllEnums(allModes); for (std::vector::iterator allModesIter = allModes.begin(); allModesIter != allModes.end(); allModesIter++) { const PaletteNormalizationModeEnum::Enum normalMode = *allModesIter; m_normalizationModeComboBox->addItem(PaletteNormalizationModeEnum::toGuiName(normalMode), (int)PaletteNormalizationModeEnum::toIntegerCode(normalMode)); } m_normalizationModeComboBox->setToolTip(PaletteNormalizationModeEnum::getEnumToolTopInHTML()); QGroupBox* groupBox = new QGroupBox("Data Normalization"); QVBoxLayout* layout = new QVBoxLayout(groupBox); layout->addWidget(m_normalizationModeComboBox); groupBox->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); return groupBox; } /** * Update the normalization control section. */ void MapSettingsPaletteColorMappingWidget::updateNormalizationControlSection() { m_normalizationModeComboBox->clear(); std::vector validModes; if (this->caretMappableDataFile != NULL) { this->caretMappableDataFile->getPaletteNormalizationModesSupported(validModes); const int32_t numValidModes = static_cast(validModes.size()); if (numValidModes > 0) { const PaletteNormalizationModeEnum::Enum selectedMode = this->caretMappableDataFile->getPaletteNormalizationMode(); int selectedModeIndex = -1; /* * Loop through ALL modes so that the selections are always * in a consistent order. */ std::vector allModes; PaletteNormalizationModeEnum::getAllEnums(allModes); for (std::vector::iterator allModesIter = allModes.begin(); allModesIter != allModes.end(); allModesIter++) { const PaletteNormalizationModeEnum::Enum normalMode = *allModesIter; if (std::find(validModes.begin(), validModes.end(), normalMode) != validModes.end()) { if (selectedMode == normalMode) { selectedModeIndex = m_normalizationModeComboBox->count(); } m_normalizationModeComboBox->addItem(PaletteNormalizationModeEnum::toGuiName(normalMode), (int)PaletteNormalizationModeEnum::toIntegerCode(normalMode)); } } if (selectedModeIndex >= 0) { m_normalizationModeComboBox->setCurrentIndex(selectedModeIndex); } } } } /** * Called when normalization combo box is changed by user. */ void MapSettingsPaletteColorMappingWidget::normalizationModeComboBoxActivated(int) { if (this->caretMappableDataFile != NULL) { const int selectedIndex = m_normalizationModeComboBox->currentIndex(); if ((selectedIndex >= 0) && (selectedIndex < m_normalizationModeComboBox->count())) { const int32_t enumIntegerCode = m_normalizationModeComboBox->itemData(selectedIndex).toInt(); bool validFlag = false; const PaletteNormalizationModeEnum::Enum mode = PaletteNormalizationModeEnum::fromIntegerCode(enumIntegerCode, &validFlag); if (validFlag) { if (mode != this->caretMappableDataFile->getPaletteNormalizationMode()) { bool doItFlag = true; if (mode == PaletteNormalizationModeEnum::NORMALIZATION_ALL_MAP_DATA) { /* * When files are "large", using all file data may take * a very long time so allow the user to cancel. */ const int64_t megabyte = 1000000; // 10e6 const int64_t warningDataSize = 100 * megabyte; const int64_t dataSize = this->caretMappableDataFile->getDataSizeUncompressedInBytes(); if (dataSize > warningDataSize) { const int64_t gigabyte = 1000000000; // 10e9 const int64_t numReallys = std::min(dataSize / gigabyte, (int64_t)10); AString veryString; if (numReallys > 0) { veryString = ("very" + QString(", very").repeated(numReallys - 1) + " "); } const AString message("File size is " + veryString + "large (" + FileInformation::fileSizeToStandardUnits(dataSize) + "). This operation may take a " + veryString + "long time."); if (WuQMessageBox::warningOkCancel(m_normalizationModeComboBox, message)) { doItFlag = true; } else { doItFlag = false; } } } if (doItFlag) { CursorDisplayScoped cursor; cursor.showCursor(Qt::WaitCursor); this->caretMappableDataFile->setPaletteNormalizationMode(mode); this->updateEditorInternal(this->caretMappableDataFile, this->mapFileIndex); this->updateColoringAndGraphics(); } else { this->updateNormalizationControlSection(); } } } } } } /** * Called when the state of the apply all maps checkbox is changed. * @param checked * New status of checkbox. */ void MapSettingsPaletteColorMappingWidget::applyAllMapsCheckBoxStateChanged(bool checked) { if (checked) { this->applySelections(); } } /** * @return A widget containing the data options. */ QWidget* MapSettingsPaletteColorMappingWidget::createDataOptionsSection() { this->applyAllMapsCheckBox = new QCheckBox("Apply to All Maps"); this->applyAllMapsCheckBox->setCheckState(Qt::Checked); QObject::connect(this->applyAllMapsCheckBox, SIGNAL(clicked(bool)), this, SLOT(applyAllMapsCheckBoxStateChanged(bool))); this->applyAllMapsCheckBox->setToolTip("If checked, settings are applied to all maps\n" "in the file containing the selected map"); this->applyToMultipleFilesPushButton = new QPushButton("Apply to Files..."); const QString tt("Displays a dialog that allows selection of data files to which the " "palette settings are applied."); this->applyToMultipleFilesPushButton->setToolTip(WuQtUtilities::createWordWrappedToolTipText(tt)); QObject::connect(this->applyToMultipleFilesPushButton, SIGNAL(clicked()), this, SLOT(applyToMultipleFilesPushbuttonClicked())); QGroupBox* optionsGroupBox = new QGroupBox("Data Options"); QVBoxLayout* optionsLayout = new QVBoxLayout(optionsGroupBox); this->setLayoutSpacingAndMargins(optionsLayout); optionsLayout->addWidget(this->applyAllMapsCheckBox); optionsLayout->addWidget(this->applyToMultipleFilesPushButton); optionsGroupBox->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed)); return optionsGroupBox; } /** * Allows user to select files to which palette settings are applied. */ void MapSettingsPaletteColorMappingWidget::applyToMultipleFilesPushbuttonClicked() { EventCaretMappableDataFilesGet mapFilesGet; EventManager::get()->sendEvent(mapFilesGet.getPointer()); std::vector mappableFiles; mapFilesGet.getAllFiles(mappableFiles); const QString filePointerPropertyName("filePointer"); WuQDataEntryDialog ded("Apply Palettes Settings", this->applyToMultipleFilesPushButton, true); ded.setTextAtTop("Palette settings will be applied to all maps in the selected files.", true); std::vector mapFileCheckBoxes; for (std::vector::iterator iter = mappableFiles.begin(); iter != mappableFiles.end(); iter++) { CaretMappableDataFile* cmdf = *iter; if (cmdf->isMappedWithPalette()) { QCheckBox* cb = ded.addCheckBox(cmdf->getFileNameNoPath()); cb->setProperty(filePointerPropertyName.toAscii().constData(), qVariantFromValue((void*)cmdf)); mapFileCheckBoxes.push_back(cb); if (previousApplyPaletteToMapFilesSelected.find(cmdf) != previousApplyPaletteToMapFilesSelected.end()) { cb->setChecked(true); } } } previousApplyPaletteToMapFilesSelected.clear(); if (ded.exec() == WuQDataEntryDialog::Accepted) { PaletteFile* paletteFile = GuiManager::get()->getBrain()->getPaletteFile(); for (std::vector::iterator iter = mapFileCheckBoxes.begin(); iter != mapFileCheckBoxes.end(); iter++) { QCheckBox* cb = *iter; if (cb->isChecked()) { void* pointer = cb->property(filePointerPropertyName.toAscii().constData()).value(); CaretMappableDataFile* cmdf = (CaretMappableDataFile*)pointer; const int32_t numMaps = cmdf->getNumberOfMaps(); for (int32_t iMap = 0; iMap < numMaps; iMap++) { PaletteColorMapping* pcm = cmdf->getMapPaletteColorMapping(iMap); if (pcm != this->paletteColorMapping) { pcm->copy(*this->paletteColorMapping); } } cmdf->updateScalarColoringForAllMaps(paletteFile); previousApplyPaletteToMapFilesSelected.insert(cmdf); } } updateColoringAndGraphics(); } } workbench-1.1.1/src/GuiQt/MapSettingsPaletteColorMappingWidget.h000066400000000000000000000156661255417355300247510ustar00rootroot00000000000000#ifndef __MAP_SETTINGS_PALETTE_COLOR_MAPPING_WIDGET_H_ #define __MAP_SETTINGS_PALETTE_COLOR_MAPPING_WIDGET_H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include // needed by windows #include #include class QAbstractButton; class QCheckBox; class QDoubleSpinBox; class QComboBox; class QLabel; class QLayout; class QPushButton; class QRadioButton; class QwtPlot; namespace caret { class CaretMappableDataFile; class EnumComboBoxTemplate; class FastStatistics; class Histogram; class PaletteColorMapping; class WuQDoubleSlider; class WuQWidgetObjectGroup; class WuQwtPlot; class MapSettingsPaletteColorMappingWidget : public QWidget { Q_OBJECT public: MapSettingsPaletteColorMappingWidget(QWidget* parent = 0); void updateEditor(CaretMappableDataFile* caretMappableDataFile, const int32_t mapIndex); virtual ~MapSettingsPaletteColorMappingWidget(); void updateWidget(); private: MapSettingsPaletteColorMappingWidget(const MapSettingsPaletteColorMappingWidget&); MapSettingsPaletteColorMappingWidget& operator=(const MapSettingsPaletteColorMappingWidget&); private slots: void thresholdLowSpinBoxValueChanged(double); void thresholdHighSpinBoxValueChanged(double); void thresholdLowSliderValueChanged(double); void thresholdHighSliderValueChanged(double); void thresholdTypeChanged(int); void thresholdRangeModeChanged(); void thresholdLinkCheckBoxToggled(bool); void histogramControlChanged(); void histogramResetViewButtonClicked(); void applyAndUpdate(); void applySelections(); void applyAllMapsCheckBoxStateChanged(bool); void applyToMultipleFilesPushbuttonClicked(); void contextMenuDisplayRequested(QContextMenuEvent* event, float graphX, float graphY); void normalizationModeComboBoxActivated(int); private: void updateEditorInternal(CaretMappableDataFile* caretMappableDataFile, const int32_t mapIndex); QWidget* createPaletteSection(); QWidget* createThresholdSection(); void updateThresholdSection(); QWidget* createHistogramSection(); QWidget* createHistogramControlSection(); QWidget* createNormalizationControlSection(); QWidget* createDataOptionsSection(); void updateNormalizationControlSection(); void updateAfterThresholdValuesChanged(const float lowThreshold, const float highThreshold); void updateHistogramPlot(); void updateColoringAndGraphics(); void updateThresholdControlsMinimumMaximumRangeValues(); void setLayoutSpacingAndMargins(QLayout* layout); const Histogram* getHistogram(const FastStatistics* statisticsForAll) const; PaletteColorMapping* paletteColorMapping; QComboBox* paletteNameComboBox; QCheckBox* applyAllMapsCheckBox; QPushButton* applyToMultipleFilesPushButton; QRadioButton* scaleAutoRadioButton; QRadioButton* scaleAutoAbsolutePercentageRadioButton; QRadioButton* scaleAutoPercentageRadioButton; QRadioButton* scaleFixedRadioButton; QDoubleSpinBox* scaleAutoPercentageNegativeMaximumSpinBox; QDoubleSpinBox* scaleAutoPercentageNegativeMinimumSpinBox; QDoubleSpinBox* scaleAutoPercentagePositiveMinimumSpinBox; QDoubleSpinBox* scaleAutoPercentagePositiveMaximumSpinBox; QDoubleSpinBox* scaleAutoAbsolutePercentageMinimumSpinBox; QDoubleSpinBox* scaleAutoAbsolutePercentageMaximumSpinBox; QDoubleSpinBox* scaleFixedNegativeMaximumSpinBox; QDoubleSpinBox* scaleFixedNegativeMinimumSpinBox; QDoubleSpinBox* scaleFixedPositiveMinimumSpinBox; QDoubleSpinBox* scaleFixedPositiveMaximumSpinBox; QCheckBox* displayModePositiveCheckBox; QCheckBox* displayModeZeroCheckBox; QCheckBox* displayModeNegativeCheckBox; QCheckBox* interpolateColorsCheckBox; QComboBox* thresholdTypeComboBox; WuQDoubleSlider* thresholdLowSlider; WuQDoubleSlider* thresholdHighSlider; WuQWidgetObjectGroup* thresholdAdjustmentWidgetGroup; QDoubleSpinBox* thresholdLowSpinBox; QDoubleSpinBox* thresholdHighSpinBox; bool allowUpdateOfThresholdLowSpinBox; bool allowUpdateOfThresholdHighSpinBox; QRadioButton* thresholdShowInsideRadioButton; QRadioButton* thresholdShowOutsideRadioButton; EnumComboBoxTemplate* thresholdRangeModeComboBox; QCheckBox* thresholdLinkCheckBox; WuQwtPlot* thresholdPlot; QLabel* statisticsMinimumValueLabel; QLabel* statisticsMaximumValueLabel; QLabel* statisticsMeanValueLabel; QLabel* statisticsStandardDeviationLabel; QComboBox* m_normalizationModeComboBox; QRadioButton* histogramAllRadioButton; QRadioButton* histogramMatchPaletteRadioButton; QCheckBox* histogramUsePaletteColors; bool isHistogramColored; CaretMappableDataFile* caretMappableDataFile; CaretMappableDataFile* m_previousCaretMappableDataFile; int32_t mapFileIndex; std::set previousApplyPaletteToMapFilesSelected; WuQWidgetObjectGroup* paletteWidgetGroup; WuQWidgetObjectGroup* thresholdWidgetGroup; }; #ifdef __MAP_SETTINGS_PALETTE_COLOR_MAPPING_WIDGET_DECLARE__ // #endif // __MAP_SETTINGS_PALETTE_COLOR_MAPPING_WIDGET_DECLARE__ } // namespace #endif //__MAP_SETTINGS_PALETTE_COLOR_MAPPING_WIDGET_H_ workbench-1.1.1/src/GuiQt/MapSettingsParcelsWidget.cxx000066400000000000000000000110461255417355300227700ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __MAP_SETTINGS_PARCELS_WIDGET_DECLARE__ #include "MapSettingsParcelsWidget.h" #undef __MAP_SETTINGS_PARCELS_WIDGET_DECLARE__ #include #include #include "CaretAssert.h" #include "CaretColorEnumComboBox.h" #include "CiftiConnectivityMatrixParcelFile.h" #include "CiftiParcelColoringModeEnum.h" #include "EnumComboBoxTemplate.h" #include "EventGraphicsUpdateAllWindows.h" #include "EventManager.h" #include "EventSurfaceColoringInvalidate.h" using namespace caret; /** * \class caret::MapSettingsParcelsWidget * \brief Widget for setting parcel coloring properties. * \ingroup GuiQt */ /** * Constructor. */ MapSettingsParcelsWidget::MapSettingsParcelsWidget(QWidget* parent) : QWidget(parent) { m_ciftiParcelFile = NULL; QLabel* colorModeLabel = new QLabel("Color Mode"); m_parcelColoringModeEnumComboBox = new EnumComboBoxTemplate(this); m_parcelColoringModeEnumComboBox->setup(); QObject::connect(m_parcelColoringModeEnumComboBox, SIGNAL(itemActivated()), this, SLOT(ciftiParcelColoringModeEnumComboBoxItemActivated())); QLabel* colorLabel = new QLabel("Color"); m_parcelColorEnumComboBox = new CaretColorEnumComboBox(this); QObject::connect(m_parcelColorEnumComboBox, SIGNAL(colorSelected(const CaretColorEnum::Enum)), this, SLOT(parcelColorSelected(const CaretColorEnum::Enum))); QWidget* gridWidget = new QWidget(); QGridLayout* gridLayout = new QGridLayout(gridWidget); gridLayout->addWidget(colorModeLabel, 0, 0); gridLayout->addWidget(m_parcelColoringModeEnumComboBox->getWidget(), 0, 1); gridLayout->addWidget(colorLabel, 1, 0); gridLayout->addWidget(m_parcelColorEnumComboBox->getWidget(), 1, 1); gridWidget->setFixedSize(gridWidget->sizeHint()); QVBoxLayout* layout = new QVBoxLayout(this); layout->addWidget(gridWidget, 0, Qt::AlignLeft); layout->addStretch(); setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); } /** * Destructor. */ MapSettingsParcelsWidget::~MapSettingsParcelsWidget() { } /** * Update the editor with the given CIFTI matrix file. */ void MapSettingsParcelsWidget::updateEditor(CiftiConnectivityMatrixParcelFile* ciftiParcelFile) { CaretAssert(ciftiParcelFile); m_ciftiParcelFile = ciftiParcelFile; m_parcelColoringModeEnumComboBox->setSelectedItem(m_ciftiParcelFile->getSelectedParcelColoringMode()); m_parcelColorEnumComboBox->setSelectedColor(m_ciftiParcelFile->getSelectedParcelColor()); } /** * Update the widget. */ void MapSettingsParcelsWidget::updateWidget() { updateEditor(m_ciftiParcelFile); } /** * Called when parcel color combo box is changed. */ void MapSettingsParcelsWidget::parcelColorSelected(const CaretColorEnum::Enum color) { m_ciftiParcelFile->setSelectedParcelColor(color); EventManager::get()->sendEvent(EventSurfaceColoringInvalidate().getPointer()); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } /** * Called when parcel coloring mode combo box is changed. */ void MapSettingsParcelsWidget::ciftiParcelColoringModeEnumComboBoxItemActivated() { const CiftiParcelColoringModeEnum::Enum colorMode = m_parcelColoringModeEnumComboBox->getSelectedItem(); m_ciftiParcelFile->setSelectedParcelColoringMode(colorMode); EventManager::get()->sendEvent(EventSurfaceColoringInvalidate().getPointer()); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } workbench-1.1.1/src/GuiQt/MapSettingsParcelsWidget.h000066400000000000000000000043331255417355300224160ustar00rootroot00000000000000#ifndef __MAP_SETTINGS_PARCELS_WIDGET_H__ #define __MAP_SETTINGS_PARCELS_WIDGET_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretColorEnum.h" namespace caret { class CaretColorEnumComboBox; class CiftiConnectivityMatrixParcelFile; class EnumComboBoxTemplate; class MapSettingsParcelsWidget : public QWidget { Q_OBJECT public: MapSettingsParcelsWidget(QWidget* parent = 0); virtual ~MapSettingsParcelsWidget(); void updateEditor(CiftiConnectivityMatrixParcelFile* ciftiMatrixFile); void updateWidget(); private slots: void ciftiParcelColoringModeEnumComboBoxItemActivated(); void parcelColorSelected(const CaretColorEnum::Enum); // ADD_NEW_METHODS_HERE private: MapSettingsParcelsWidget(const MapSettingsParcelsWidget&); MapSettingsParcelsWidget& operator=(const MapSettingsParcelsWidget&); EnumComboBoxTemplate* m_parcelColoringModeEnumComboBox; CaretColorEnumComboBox* m_parcelColorEnumComboBox; CiftiConnectivityMatrixParcelFile* m_ciftiParcelFile; // ADD_NEW_MEMBERS_HERE }; #ifdef __MAP_SETTINGS_PARCELS_WIDGET_DECLARE__ // #endif // __MAP_SETTINGS_PARCELS_WIDGET_DECLARE__ } // namespace #endif //__MAP_SETTINGS_PARCELS_WIDGET_H__ workbench-1.1.1/src/GuiQt/MapYokingGroupComboBox.cxx000066400000000000000000000335331255417355300224250ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __MAP_YOKING_GROUP_COMBO_BOX_DECLARE__ #include "MapYokingGroupComboBox.h" #undef __MAP_YOKING_GROUP_COMBO_BOX_DECLARE__ #include "CaretAssert.h" #include "CaretMappableDataFile.h" #include "ChartableMatrixSeriesInterface.h" #include "CiftiScalarDataSeriesFile.h" #include "EnumComboBoxTemplate.h" #include "EventManager.h" #include "EventMapYokingValidation.h" #include "Overlay.h" #include "WuQMessageBox.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::MapYokingGroupComboBox * \brief Combo box for selection of a map yoking group. * \ingroup GuiQt */ /** * Constructor. */ MapYokingGroupComboBox::MapYokingGroupComboBox(QObject* parent) : WuQWidget(parent) { m_comboBox = new EnumComboBoxTemplate(this); m_comboBox->setup(); m_comboBox->getWidget()->setStatusTip("Synchronize selected map indices (and selection status for overlays)"); m_comboBox->getWidget()->setToolTip("Synchronize selected map indices (and selection status for overlays)"); #ifdef CARET_OS_MACOSX m_comboBox->getComboBox()->setFixedWidth(m_comboBox->getComboBox()->sizeHint().width() - 20); #endif // CARET_OS_MACOSX QObject::connect(m_comboBox, SIGNAL(itemActivated()), this, SLOT(comboBoxActivated())); } /** * Destructor. */ MapYokingGroupComboBox::~MapYokingGroupComboBox() { } /** * Called when the user selects a yoking group. * Verify compatibility before accepting the selection. */ void MapYokingGroupComboBox::comboBoxActivated() { emit itemActivated(); // MapYokingGroupEnum::Enum mapYokingGroup = getMapYokingGroup(); // EventMapYokingValidation validateEvent(mapYokingGroup); } /** * @return The widget. */ QWidget* MapYokingGroupComboBox::getWidget() { return m_comboBox->getWidget(); } /** * @return The selected map yoking group. */ MapYokingGroupEnum::Enum MapYokingGroupComboBox::getMapYokingGroup() const { return m_comboBox->getSelectedItem(); } /** * Set the map yoking group. * * @param mapYokingGroup * The map yoking group. */ void MapYokingGroupComboBox::setMapYokingGroup(const MapYokingGroupEnum::Enum mapYokingGroup) { m_comboBox->setSelectedItem(mapYokingGroup); } /** * Validate a change in yoking for a matrix series file. * * @param chartableMatrixSeriesInterface * Matrix series file that has yoking changed. * @param tabIndex * Index of tab for the file. */ void MapYokingGroupComboBox::validateYokingChange(ChartableMatrixSeriesInterface* chartableMatrixSeriesInterface, const int32_t tabIndex) { if (chartableMatrixSeriesInterface != NULL) { int32_t mapIndex = chartableMatrixSeriesInterface->getSelectedMapIndex(tabIndex); const MapYokingGroupEnum::Enum previousMapYokingGroup = chartableMatrixSeriesInterface->getMatrixRowColumnMapYokingGroup(tabIndex); const MapYokingGroupEnum::Enum newYokingGroup = getMapYokingGroup(); CaretMappableDataFile* mapFile = dynamic_cast(chartableMatrixSeriesInterface); CaretAssert(mapFile); bool selectionStatus = true; if ((mapFile != NULL) && (mapIndex >= 0)) { const YokeValidationResult result = validateYoking(mapFile, mapIndex, selectionStatus); switch (result) { case YOKE_VALIDATE_RESULT_ACCEPT: chartableMatrixSeriesInterface->setMatrixRowColumnMapYokingGroup(tabIndex, newYokingGroup); chartableMatrixSeriesInterface->setSelectedMapIndex(tabIndex, mapIndex); break; case YOKE_VALIDATE_RESULT_OFF: chartableMatrixSeriesInterface->setMatrixRowColumnMapYokingGroup(tabIndex, MapYokingGroupEnum::MAP_YOKING_GROUP_OFF); break; case YOKE_VALIDATE_RESULT_PREVIOUS: chartableMatrixSeriesInterface->setMatrixRowColumnMapYokingGroup(tabIndex, previousMapYokingGroup); break; } setMapYokingGroup(chartableMatrixSeriesInterface->getMatrixRowColumnMapYokingGroup(tabIndex)); } } } /** * Validate a change in yoking for an overlay. * * @param overlay * Overlay whose yoking changes. */ void MapYokingGroupComboBox::validateYokingChange(Overlay* overlay) { const MapYokingGroupEnum::Enum previousMapYokingGroup = overlay->getMapYokingGroup(); const MapYokingGroupEnum::Enum newYokingGroup = getMapYokingGroup(); CaretMappableDataFile* mapFile = NULL; int32_t mapIndex = -1; overlay->getSelectionData(mapFile, mapIndex); bool selectionStatus = overlay->isEnabled(); if ((mapFile != NULL) && (mapIndex >= 0)) { const YokeValidationResult result = validateYoking(mapFile, mapIndex, selectionStatus); switch (result) { case YOKE_VALIDATE_RESULT_ACCEPT: overlay->setEnabled(selectionStatus); overlay->setSelectionData(mapFile, mapIndex); overlay->setMapYokingGroup(newYokingGroup); break; case YOKE_VALIDATE_RESULT_OFF: overlay->setMapYokingGroup(MapYokingGroupEnum::MAP_YOKING_GROUP_OFF); break; case YOKE_VALIDATE_RESULT_PREVIOUS: overlay->setMapYokingGroup(previousMapYokingGroup); break; } setMapYokingGroup(overlay->getMapYokingGroup()); } } /** * Validate yoking when a new file is added to a yoking group. * * @param previousMapYokingGroup * The previous yoking group. * @param selectedFile * The file that the user would like to yoke. * @param selectedMapIndexInOut * The current map selected for the file. Its value will be updated * if yoking is selected (turned on or changed). * @param selectionStatusInOut * The selection status for an overlay. Its value will be updated * if yoking is selected (turned on or changed). */ MapYokingGroupComboBox::YokeValidationResult MapYokingGroupComboBox::validateYoking(CaretMappableDataFile* selectedFile, int32_t& selectedMapIndexInOut, bool& /* selectionStatusInOut */) { YokeValidationResult yokeResult = YOKE_VALIDATE_RESULT_OFF; //YOKE_VALIDATE_RESULT_PREVIOUS; MapYokingGroupEnum::Enum newYokingGroup = getMapYokingGroup(); if (newYokingGroup != MapYokingGroupEnum::MAP_YOKING_GROUP_OFF) { if ((selectedFile != NULL) && (selectedMapIndexInOut >= 0)) { /* * Get info on yoking selections */ EventMapYokingValidation validateEvent(newYokingGroup); EventManager::get()->sendEvent(validateEvent.getPointer()); /* * Check compatibility based (number of maps in yoked files) * and warn use if there is an incompatibility. */ int32_t numberOfYokedFiles = 0; AString message; if (validateEvent.validateCompatibility(selectedFile, numberOfYokedFiles, message)) { yokeResult = YOKE_VALIDATE_RESULT_ACCEPT; } else { message.appendWithNewLine(""); message.appendWithNewLine("Allow yoking?"); message = WuQtUtilities::createWordWrappedToolTipText(message); WuQMessageBox::YesNoCancelResult result = WuQMessageBox::warningYesNoCancel(m_comboBox->getWidget(), message, ""); switch (result) { case WuQMessageBox::RESULT_YES: yokeResult = YOKE_VALIDATE_RESULT_ACCEPT; break; case WuQMessageBox::RESULT_NO: yokeResult = YOKE_VALIDATE_RESULT_OFF; break; case WuQMessageBox::RESULT_CANCEL: yokeResult = YOKE_VALIDATE_RESULT_PREVIOUS; break; } } if (yokeResult == YOKE_VALIDATE_RESULT_ACCEPT) { if (numberOfYokedFiles > 0) { /* * Already have files yoked to this group so use * the map index and status from the yoking group. */ selectedMapIndexInOut = MapYokingGroupEnum::getSelectedMapIndex(newYokingGroup); //selectionStatusInOut = MapYokingGroupEnum::isEnabled(newYokingGroup); } else { /* * This is the first file added to the yoking group * so set the map index and status in the yoking group * to the file's selections. */ MapYokingGroupEnum::setSelectedMapIndex(newYokingGroup, selectedMapIndexInOut); //MapYokingGroupEnum::setEnabled(newYokingGroup, // selectionStatusInOut); } } } } return yokeResult; } //void //MapYokingGroupComboBox::validateYokingChange(const MapYokingGroupEnum::Enum previousMapYokingGroup, // CaretMappableDataFile* selectedFile, // const int32_t selectedMapIndex, // ChartableMatrixSeriesInterface* chartableMatrixSeriesInterface, // Overlay* overlay) //{ // MapYokingGroupEnum::Enum yokingGroup = getMapYokingGroup(); // if (yokingGroup != MapYokingGroupEnum::MAP_YOKING_GROUP_OFF) { // CaretMappableDataFile* selectedFile = NULL; // if ((selectedFile != NULL) // && (selectedMapIndex >= 0)) { // /* // * Get info on yoking selections // */ // EventMapYokingValidation validateEvent(yokingGroup); // EventManager::get()->sendEvent(validateEvent.getPointer()); // // const int32_t numOverlaysYoked = yokedOverlaysEvent.getNumberOfYokedOverlays(); // // /* // * Check compatibility based (number of maps in yoked files) // * and warn use if there is an incompatibility. // */ // AString message; // if ( ! validateEvent.validateCompatibility(selectedFile, // message)) { // message.appendWithNewLine(""); // message.appendWithNewLine("Allow yoking?"); // // message = WuQtUtilities::createWordWrappedToolTipText(message); // // WuQMessageBox::YesNoCancelResult result = // WuQMessageBox::warningYesNoCancel(m_comboBox->getWidget(), // message, // ""); // switch (result) { // case WuQMessageBox::RESULT_YES: // break; // case WuQMessageBox::RESULT_NO: // yokingGroup = MapYokingGroupEnum::MAP_YOKING_GROUP_OFF; // break; // case WuQMessageBox::RESULT_CANCEL: // yokingGroup = previousMapYokingGroup; // break; // } // } // // if (overlay != NULL) { // // } // overlay->setYokingGroup(yokingGroup); // if (yokingGroup != MapYokingGroupEnum::MAP_YOKING_GROUP_OFF) { // if (numOverlaysYoked <= 0) { // OverlayYokingGroupEnum::setSelectedMapIndex(yokingGroup, // selectedMapIndex); // const bool enabledStatus = overlay->isEnabled(); // OverlayYokingGroupEnum::setEnabled(yokingGroup, // enabledStatus); // } // } // } // } // else { // overlay->setYokingGroup(yokingGroup); // } // // updateViewController(overlay); // // this->updateUserInterfaceAndGraphicsWindow(); // //} // workbench-1.1.1/src/GuiQt/MapYokingGroupComboBox.h000066400000000000000000000053171255417355300220510ustar00rootroot00000000000000#ifndef __MAP_YOKING_GROUP_COMBO_BOX_H__ #define __MAP_YOKING_GROUP_COMBO_BOX_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "MapYokingGroupEnum.h" #include "WuQWidget.h" namespace caret { class CaretMappableDataFile; class ChartableMatrixSeriesInterface; class EnumComboBoxTemplate; class Overlay; class MapYokingGroupComboBox : public WuQWidget { Q_OBJECT public: MapYokingGroupComboBox(QObject* parent); virtual ~MapYokingGroupComboBox(); virtual QWidget* getWidget(); MapYokingGroupEnum::Enum getMapYokingGroup() const; void setMapYokingGroup(const MapYokingGroupEnum::Enum mapYokingGroup); void validateYokingChange(ChartableMatrixSeriesInterface* chartableMatrixSeriesInterface, const int32_t tabIndex); void validateYokingChange(Overlay* overlay); // ADD_NEW_METHODS_HERE signals: void itemActivated(); private slots: void comboBoxActivated(); private: enum YokeValidationResult { YOKE_VALIDATE_RESULT_ACCEPT, YOKE_VALIDATE_RESULT_OFF, YOKE_VALIDATE_RESULT_PREVIOUS }; MapYokingGroupComboBox(const MapYokingGroupComboBox&); MapYokingGroupComboBox& operator=(const MapYokingGroupComboBox&); YokeValidationResult validateYoking(CaretMappableDataFile* selectedFile, int32_t& selectedMapIndexInOut, bool& selectionStatusInOut); EnumComboBoxTemplate* m_comboBox; // ADD_NEW_MEMBERS_HERE }; #ifdef __MAP_YOKING_GROUP_COMBO_BOX_DECLARE__ // #endif // __MAP_YOKING_GROUP_COMBO_BOX_DECLARE__ } // namespace #endif //__MAP_YOKING_GROUP_COMBO_BOX_H__ workbench-1.1.1/src/GuiQt/MetaDataEditorDialog.cxx000066400000000000000000000077311255417355300220310ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __META_DATA_EDITOR_DIALOG_DECLARE__ #include "MetaDataEditorDialog.h" #undef __META_DATA_EDITOR_DIALOG_DECLARE__ #include #include #include #include #include "CaretAssert.h" #include "CaretDataFile.h" #include "CaretMappableDataFile.h" #include "GiftiMetaData.h" #include "MetaDataEditorWidget.h" #include "WuQMessageBox.h" using namespace caret; /** * \class caret::MetaDataEditorDialog * \brief Dialog for editing metadata. * \ingroup GuiQt */ /** * Constructor for editing a file's metadata. * * @param caretDataFile * Caret Data File that will have its metadata edited. * @param parent * Widget on which this dialog is displayed. */ MetaDataEditorDialog::MetaDataEditorDialog(CaretDataFile* caretDataFile, QWidget* parent) : WuQDialogModal("", parent) { CaretAssert(caretDataFile); initializeDialog(("Edit File Metadata: " + caretDataFile->getFileNameNoPath()), caretDataFile->getFileMetaData()); } /** * Constructor for editing a map's metadata. * * @param caretMappableDataFile * Caret Data File that will have its map's metadata edited. * @param mapIndex * Index of map that will have its metadata edited. * @param parent * Widget on which this dialog is displayed. */ MetaDataEditorDialog::MetaDataEditorDialog(CaretMappableDataFile* caretMappableDataFile, const int32_t mapIndex, QWidget* parent) : WuQDialogModal("", parent) { CaretAssert(caretMappableDataFile); const AString mapName = caretMappableDataFile->getMapName(mapIndex); initializeDialog(("Edit Map Metadata: " + mapName), caretMappableDataFile->getMapMetaData(mapIndex)); } /** * Destructor. */ MetaDataEditorDialog::~MetaDataEditorDialog() { } void MetaDataEditorDialog::initializeDialog(const AString& dialogTitle, GiftiMetaData* metaData) { CaretAssert(metaData); setWindowTitle(dialogTitle); m_metaDataEditorWidget = new MetaDataEditorWidget(this); setCentralWidget(m_metaDataEditorWidget, WuQDialog::SCROLL_AREA_NEVER); m_metaDataEditorWidget->loadMetaData(metaData); } /** * Called when OK button clicked. */ void MetaDataEditorDialog::okButtonClicked() { const AString errorMessage = m_metaDataEditorWidget->saveMetaData(); if (errorMessage.isEmpty() == false) { WuQMessageBox::errorOk(this, errorMessage); return; } WuQDialogModal::okButtonClicked(); } /** * Called when Cancel button clicked. */ void MetaDataEditorDialog::cancelButtonClicked() { if (m_metaDataEditorWidget->isMetaDataModified()) { const AString errorMessage = ("The metadata has been modified. Discard changes?"); if (WuQMessageBox::warningOkCancel(this, errorMessage)) { WuQDialogModal::cancelButtonClicked(); } } else { WuQDialogModal::cancelButtonClicked(); } } workbench-1.1.1/src/GuiQt/MetaDataEditorDialog.h000066400000000000000000000042421255417355300214500ustar00rootroot00000000000000#ifndef __META_DATA_EDITOR_DIALOG_H__ #define __META_DATA_EDITOR_DIALOG_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "WuQDialogModal.h" namespace caret { class CaretDataFile; class CaretMappableDataFile; class GiftiMetaData; class MetaDataEditorWidget; class MetaDataEditorDialog : public WuQDialogModal { Q_OBJECT public: MetaDataEditorDialog(CaretDataFile* caretDataFile, QWidget* parent); MetaDataEditorDialog(CaretMappableDataFile* caretMappableDataFile, const int32_t mapIndex, QWidget* parent); virtual ~MetaDataEditorDialog(); virtual void okButtonClicked(); virtual void cancelButtonClicked(); private: MetaDataEditorDialog(const MetaDataEditorDialog&); MetaDataEditorDialog& operator=(const MetaDataEditorDialog&); void initializeDialog(const AString& dialogTitle, GiftiMetaData* metaData); MetaDataEditorWidget* m_metaDataEditorWidget; // ADD_NEW_MEMBERS_HERE }; #ifdef __META_DATA_EDITOR_DIALOG_DECLARE__ // #endif // __META_DATA_EDITOR_DIALOG_DECLARE__ } // namespace #endif //__META_DATA_EDITOR_DIALOG_H__ workbench-1.1.1/src/GuiQt/MetaDataEditorWidget.cxx000066400000000000000000000352431255417355300220540ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __META_DATA_EDITOR_WIDGET_DECLARE__ #include "MetaDataEditorWidget.h" #undef __META_DATA_EDITOR_WIDGET_DECLARE__ #include #include #include #include #include #include #include #include #include #include #include #include #include "CaretAssert.h" #include "GiftiMetaData.h" #include "WuQDataEntryDialog.h" #include "WuQWidgetObjectGroup.h" using namespace caret; /** * \class caret::MetaDataEditorWidget * \brief Widget for editing GIFTI MetaData. * \ingroup GuiQt */ /** * Constructor. * @param parent * Parent widget. */ MetaDataEditorWidget::MetaDataEditorWidget(QWidget* parent) : QWidget(parent) { m_metaDataBeingEdited = NULL; m_deleteActionSignalMapper = new QSignalMapper(); QObject::connect(m_deleteActionSignalMapper, SIGNAL(mapped(int)), this, SLOT(deleteActionTriggered(int))); m_newPushButton = new QPushButton("New..."); QObject::connect(m_newPushButton, SIGNAL(clicked()), this, SLOT(newPushButtonClicked())); QVBoxLayout* buttonsLayout = new QVBoxLayout(); buttonsLayout->addStretch(); buttonsLayout->addWidget(m_newPushButton); buttonsLayout->addSpacing(10); m_metaGridLayout = new QGridLayout(); m_metaGridLayout->addWidget(new QLabel("Delete"), 0, COLUMN_DELETE, Qt::AlignCenter); m_metaGridLayout->addWidget(new QLabel("Name"), 0, COLUMN_NAME, Qt::AlignCenter); m_metaGridLayout->addWidget(new QLabel("Value"), 0, COLUMN_VALUE, Qt::AlignCenter); QWidget* metaDataWidget = new QWidget(); QVBoxLayout* metaDataWidgetLayout = new QVBoxLayout(metaDataWidget); metaDataWidgetLayout->addLayout(m_metaGridLayout); metaDataWidgetLayout->addStretch(); m_metaDataNameValueScrollArea = new QScrollArea(); m_metaDataNameValueScrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); m_metaDataNameValueScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); m_metaDataNameValueScrollArea->setWidget(metaDataWidget); m_metaDataNameValueScrollArea->setWidgetResizable(true); m_metaDataNameValueScrollArea->setFrameShape(QFrame::NoFrame); QHBoxLayout* dialogLayout = new QHBoxLayout(this); dialogLayout->addWidget(m_metaDataNameValueScrollArea, 100); dialogLayout->addLayout(buttonsLayout, 0); setMinimumWidth(600); setMinimumHeight(260); } /** * Destructor. */ MetaDataEditorWidget::~MetaDataEditorWidget() { } /** * Called when Add push button is clicked. */ void MetaDataEditorWidget::newPushButtonClicked() { WuQDataEntryDialog ded("New Metadata Name", m_newPushButton); m_newNameDialogLineEdit = ded.addLineEditWidget("New MetaData Name"); QObject::connect(&ded, SIGNAL(validateData(WuQDataEntryDialog*)), this, SLOT(validateNewName(WuQDataEntryDialog*))); if (ded.exec() == WuQDataEntryDialog::Accepted) { readNamesAndValues(); m_namesAndValues.push_back(std::make_pair(m_newNameDialogLineEdit->text().trimmed(), "")); displayNamesAndValues(); const int32_t numNames = static_cast(m_namesAndValues.size()); m_metaDataWidgetRows[numNames - 1]->m_valueLineEdit->setFocus(); m_metaDataNameValueScrollArea->ensureWidgetVisible(m_metaDataWidgetRows[numNames-1]->m_nameLineEdit); } } /** * Validate the new name from new name dialog. * @param dataEntryDialog * The dialog used for entry of new name. */ void MetaDataEditorWidget::validateNewName(WuQDataEntryDialog* dataEntryDialog) { const AString newName = m_newNameDialogLineEdit->text().trimmed(); if (newName.isEmpty()) { dataEntryDialog->setDataValid(false, "Name may not be empty."); return; } std::set allNames; getNamesInDialog(allNames, NULL, NULL); if (std::find(allNames.begin(), allNames.end(), newName) != allNames.end()) { const AString msg = ("Name \"" + newName + "\" already exists. Duplicate names are not allowed."); dataEntryDialog->setDataValid(false, Qt::convertFromPlainText(msg)); return; } dataEntryDialog->setDataValid(true, ""); } /** * Load the given metadata in this widget. * * @param metaData * Metadata that displayed in widget. */ void MetaDataEditorWidget::loadMetaData(GiftiMetaData* metaData) { CaretAssert(metaData); m_metaDataBeingEdited = metaData; std::vector metaDataNames = m_metaDataBeingEdited->getAllMetaDataNames(); const int32_t numMetaData = static_cast(metaDataNames.size()); /* * Get names and values */ m_unmodifiedNamesAndValues.clear(); m_namesAndValues.clear(); for (int32_t iRow = 0; iRow < numMetaData; iRow++) { const AString name = metaDataNames[iRow].trimmed(); const AString value = m_metaDataBeingEdited->get(name).trimmed(); m_namesAndValues.push_back(std::make_pair(name, value)); m_unmodifiedNamesAndValues.insert(std::make_pair(name, value)); } displayNamesAndValues(); } /** * Display the names and values. */ void MetaDataEditorWidget::displayNamesAndValues() { const int32_t numNamesAndValues = static_cast(m_namesAndValues.size()); int32_t numWidgetRows = static_cast(m_metaDataWidgetRows.size()); /* * Update existing rows and add new rows as needed. */ for (int32_t iRow = 0; iRow < numNamesAndValues; iRow++) { const AString name = m_namesAndValues[iRow].first; const AString value = m_namesAndValues[iRow].second; MetaDataWidgetRow* widgetsRow = NULL; if (iRow < numWidgetRows) { widgetsRow = m_metaDataWidgetRows[iRow]; } else { widgetsRow = new MetaDataWidgetRow(this, m_deleteActionSignalMapper, iRow); const int layoutRow = m_metaGridLayout->rowCount(); m_metaGridLayout->addWidget(widgetsRow->m_deleteToolButton, layoutRow, COLUMN_DELETE, Qt::AlignCenter); m_metaGridLayout->addWidget(widgetsRow->m_nameLineEdit, layoutRow, COLUMN_NAME); m_metaGridLayout->addWidget(widgetsRow->m_valueLineEdit, layoutRow, COLUMN_VALUE); m_metaDataWidgetRows.push_back(widgetsRow); } widgetsRow->m_nameLineEdit->setText(name); widgetsRow->m_valueLineEdit->setText(value); widgetsRow->m_widgetGroup->setVisible(true); } /* * Hide rows that are no longer used. */ numWidgetRows = static_cast(m_metaDataWidgetRows.size()); for (int32_t iRow = numNamesAndValues; iRow < numWidgetRows; iRow++) { m_metaDataWidgetRows[iRow]->m_widgetGroup->setVisible(false); } } /** * Read the names and values from the GUI. */ void MetaDataEditorWidget::readNamesAndValues() { m_namesAndValues.clear(); /* * Read from the rows. */ int32_t numWidgetRows = static_cast(m_metaDataWidgetRows.size()); for (int32_t iRow = 0; iRow < numWidgetRows; iRow++) { MetaDataWidgetRow* widgetRow = m_metaDataWidgetRows[iRow]; if (widgetRow->m_widgetGroup->isVisible()) { m_namesAndValues.push_back(std::make_pair(widgetRow->m_nameLineEdit->text().trimmed(), widgetRow->m_valueLineEdit->text().trimmed())); } } } /** * Get the names that are listed in the editor. * * @param namesOut * Output will contain all unique names. * @param duplicateNamesOut * If not NULL, any duplicate names will be placed here. * @param haveEmptyNamesOut * If not NULL, will be true if any empty names were found. * @return true if all names valid, else false. */ bool MetaDataEditorWidget::getNamesInDialog(std::set& namesOut, std::set* duplicateNamesOut, bool* haveEmptyNamesOut) { readNamesAndValues(); namesOut.clear(); if (duplicateNamesOut != NULL) { duplicateNamesOut->clear(); } if (haveEmptyNamesOut != NULL) { *haveEmptyNamesOut = false; } bool allValidNames = true; const int32_t numItems = static_cast(m_namesAndValues.size()); for (int32_t i = 0; i < numItems; i++) { const AString name = m_namesAndValues[i].first; if (name.isEmpty()) { if (haveEmptyNamesOut != NULL) { *haveEmptyNamesOut = true; } allValidNames = false; } else if (std::find(namesOut.begin(), namesOut.end(), name) != namesOut.end()) { if (duplicateNamesOut != NULL) { duplicateNamesOut->insert(name); } allValidNames = false; } else { namesOut.insert(name); } } return allValidNames; } /** * Transfer values in dialog into metadata. * * @return Empty string if no errors, otherwise error message. */ AString MetaDataEditorWidget::saveMetaData() { if (isMetaDataModified() == false) { return ""; } readNamesAndValues(); std::set allNames; std::set duplicateNames; bool haveEmptyNames; const bool valid = getNamesInDialog(allNames, &duplicateNames, &haveEmptyNames); if (valid) { m_metaDataBeingEdited->clear(); const int32_t numItems = static_cast(m_namesAndValues.size()); for (int32_t i = 0; i < numItems; i++) { const AString name = m_namesAndValues[i].first; const AString value = m_namesAndValues[i].second; m_metaDataBeingEdited->set(name, value); } return ""; } AString errorMessage = ""; if (haveEmptyNames) { errorMessage.appendWithNewLine("Empty names are not allowed."); } if (duplicateNames.empty() == false) { errorMessage.appendWithNewLine("Duplicate names are not allowed:"); for (std::set::iterator iter = duplicateNames.begin(); iter != duplicateNames.end(); iter++) { errorMessage.appendWithNewLine(" " + *iter); } } return errorMessage; } /** * @return true if the names and values been modified, else false. */ bool MetaDataEditorWidget::isMetaDataModified() { readNamesAndValues(); const int32_t numItems = static_cast(m_namesAndValues.size()); if (numItems != static_cast(m_unmodifiedNamesAndValues.size())) { return true; } std::map nameValueMap; for (int32_t i = 0; i < numItems; i++) { nameValueMap.insert(std::make_pair(m_namesAndValues[i].first, m_namesAndValues[i].second)); } const bool theSame = std::equal(m_unmodifiedNamesAndValues.begin(), m_unmodifiedNamesAndValues.end(), nameValueMap.begin()); return (theSame == false); } /** * Called when a delete tool button is clicked. */ void MetaDataEditorWidget::deleteActionTriggered(int indx) { readNamesAndValues(); CaretAssertVectorIndex(m_namesAndValues, indx); m_namesAndValues.erase(m_namesAndValues.begin() + indx); displayNamesAndValues(); } /** * Constructor. * @param parent * The parent widget. * @param deleteActionSignalMapper * The signal mapper for delete action. * @param rowIndex * Row index of widgets and used by signal mapper. */ MetaDataEditorWidget::MetaDataWidgetRow::MetaDataWidgetRow(QWidget* parent, QSignalMapper* deleteActionSignalMapper, const int32_t rowIndex) { QAction* deleteAction = new QAction("X", parent); QObject::connect(deleteAction, SIGNAL(triggered(bool)), deleteActionSignalMapper, SLOT(map())); deleteActionSignalMapper->setMapping(deleteAction, rowIndex); m_deleteToolButton = new QToolButton(); m_deleteToolButton->setDefaultAction(deleteAction); const int minWidth = 150; m_nameLineEdit = new QLineEdit(); m_nameLineEdit->setMinimumWidth(minWidth); m_valueLineEdit = new QLineEdit(); m_valueLineEdit->setMinimumWidth(minWidth); m_widgetGroup = new WuQWidgetObjectGroup(parent); m_widgetGroup->add(deleteAction); m_widgetGroup->add(m_deleteToolButton); m_widgetGroup->add(m_nameLineEdit); m_widgetGroup->add(m_valueLineEdit); } MetaDataEditorWidget::MetaDataWidgetRow::~MetaDataWidgetRow() { } workbench-1.1.1/src/GuiQt/MetaDataEditorWidget.h000066400000000000000000000071111255417355300214720ustar00rootroot00000000000000#ifndef __META_DATA_EDITOR_WIDGET_H__ #define __META_DATA_EDITOR_WIDGET_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include "AString.h" class QAction; class QGridLayout; class QLineEdit; class QPushButton; class QScrollArea; class QSignalMapper; class QToolButton; namespace caret { class GiftiMetaData; class WuQDataEntryDialog; class WuQWidgetObjectGroup; class MetaDataEditorWidget : public QWidget { Q_OBJECT public: MetaDataEditorWidget(QWidget* parent); virtual ~MetaDataEditorWidget(); void loadMetaData(GiftiMetaData* metaData); bool isMetaDataModified(); AString saveMetaData(); private slots: void newPushButtonClicked(); void deleteActionTriggered(int indx); void validateNewName(WuQDataEntryDialog* dataEntryDialog); private: MetaDataEditorWidget(const MetaDataEditorWidget&); MetaDataEditorWidget& operator=(const MetaDataEditorWidget&); enum { COLUMN_DELETE = 0, COLUMN_NAME = 1, COLUMN_VALUE = 2 }; class MetaDataWidgetRow { public: MetaDataWidgetRow(QWidget* parent, QSignalMapper* deleteActionSignalMapper, const int32_t indx); ~MetaDataWidgetRow(); QToolButton* m_deleteToolButton; QLineEdit* m_nameLineEdit; QLineEdit* m_valueLineEdit; WuQWidgetObjectGroup* m_widgetGroup; }; void displayNamesAndValues(); void readNamesAndValues(); bool getNamesInDialog(std::set& namesOut, std::set* duplicateNamesOut, bool* haveEmptyNamesOut); std::vector > m_namesAndValues; std::map m_unmodifiedNamesAndValues; /** Metadata that is being edited */ GiftiMetaData* m_metaDataBeingEdited; QGridLayout* m_metaGridLayout; std::vector m_metaDataWidgetRows; QSignalMapper* m_deleteActionSignalMapper; QPushButton* m_newPushButton; QLineEdit* m_newNameDialogLineEdit; QScrollArea* m_metaDataNameValueScrollArea; // ADD_NEW_MEMBERS_HERE }; #ifdef __META_DATA_EDITOR_WIDGET_DECLARE__ // #endif // __META_DATA_EDITOR_WIDGET_DECLARE__ } // namespace #endif //__META_DATA_EDITOR_WIDGET_H__ workbench-1.1.1/src/GuiQt/MouseEvent.cxx000066400000000000000000000127761255417355300201610ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "MouseEvent.h" using namespace caret; /** * \class caret::MouseEvent * \brief Event issued when mouse is moved or buttons are pressed. * \ingroup GuiQt */ /** * Constructor. * @param viewportContent * Content of viewport. * @param openGLWidget * OpenGL Widget in which mouse activity took place. * @param browserWindowIndex * Index of the browser winddow in which mouse activity took place. * @param x * Current mouse X-coordinate (left == 0) * @param y * Current mouse Y-coordinate (bottom == 0) * @param dx * Change in mouse X-coordinate since last mouse event * @param dy * Change in mouse Y-coordinate since last mouse event * @param mousePressX * X-coordinate of mouse when button was pressed * @param mousePressY * Y-coordinate of mouse when button was pressed * @param firstDraggingFlag * Should be true the first time in in a mouse dragging operation. */ MouseEvent::MouseEvent(BrainOpenGLViewportContent* viewportContent, BrainOpenGLWidget* openGLWidget, const int32_t browserWindowIndex, const int32_t x, const int32_t y, const int32_t dx, const int32_t dy, const int32_t mousePressX, const int32_t mousePressY, const bool firstDraggingFlag) : CaretObject() { initializeMembersMouseEvent(); m_viewportContent = viewportContent; m_openGLWidget = openGLWidget; m_browserWindowIndex = browserWindowIndex; m_x = x; m_y = y; m_dx = dx; m_dy = dy; m_pressX = mousePressX; m_pressY = mousePressY; m_firstDraggingFlag = firstDraggingFlag; } /** * Destructor */ MouseEvent::~MouseEvent() { } /** * Initialize all members. */ void MouseEvent::initializeMembersMouseEvent() { m_viewportContent = NULL; m_openGLWidget = NULL; m_browserWindowIndex = -1; m_x = 0; m_y = 0; m_dx = 0; m_dy = 0; m_pressX = 0; m_pressY = 0; m_wheelRotation = 0; } /** * @return The viewport content in which the mouse was pressed. */ BrainOpenGLViewportContent* MouseEvent::getViewportContent() const { return m_viewportContent; } /** * @return The OpenGL Widget in which the mouse event occurred. */ BrainOpenGLWidget* MouseEvent::getOpenGLWidget() const { return m_openGLWidget; } /** * Get a string showing the contents of this mouse event. * @return String describing the mouse status. */ AString MouseEvent::toString() const { const AString msg = ", x=" + AString::number(m_x) + ", y=" + AString::number(m_y) + ", dx=" + AString::number(m_dx) + ", dy=" + AString::number(m_dy); + ", pressX=" + AString::number(m_pressX) + ", pressY=" + AString::number(m_pressY); + ", wheelRotation=" + AString::number(m_wheelRotation); return msg; } /** * @return Index of the browser window in which the * event took place. */ int32_t MouseEvent::getBrowserWindowIndex() const { return m_browserWindowIndex; } /** * Get the change in the X-coordinate. * @return change in the X-coordinate. * */ int32_t MouseEvent::getDx() const { return m_dx; } /** * Get the change in the Y-coordinate. * @return change in the Y-coordinate. * */ int32_t MouseEvent::getDy() const { return m_dy; } /** * Get the X-coordinate of the mouse. * @return The X-coordinate. * */ int32_t MouseEvent::getX() const { return m_x; } /** * Get the Y-coordinate of the mouse. * Origin is at the BOTTOM of the widget !!! * @return The Y-coordinate. * */ int32_t MouseEvent::getY() const { return m_y; } /** * Get the X-coordinate of where the mouse was pressed. * @return The X-coordinate. * */ int32_t MouseEvent::getPressedX() const { return m_pressX; } /** * Get the Y-coordinate of where the mouse was pressed. * Origin is at the BOTTOM of the widget !!! * @return The Y-coordinate. * */ int32_t MouseEvent::getPressedY() const { return m_pressY; } /** * Get the amount of rotation in the mouse wheel. * @return Amount mouse wheel rotated. * */ int32_t MouseEvent::getWheelRotation() const { return m_wheelRotation; } /** * @return Is this the first in a sequence of mouse dragging? * * A mouse drag is the sequence: * (1) User presses the mouse * (2) One or more calls are made to the input receivers mouseLeftDrag() method * (3) User releases the mouse ending the dragging. * * This method returns true for the first call made to mouseLeftDrag() in * step 2 and false in all other calls to mouseLeftDrag(). */ bool MouseEvent::isFirstDragging() const { return m_firstDraggingFlag; } workbench-1.1.1/src/GuiQt/MouseEvent.h000066400000000000000000000056211255417355300175750ustar00rootroot00000000000000#ifndef __MouseEvent_H__ #define __MouseEvent_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include class QMouseEvent; namespace caret { class BrainOpenGLViewportContent; class BrainOpenGLWidget; /** * Contains information about a mouse event in the OpenGL region. */ class MouseEvent : public CaretObject { public: MouseEvent(BrainOpenGLViewportContent* viewportContent, BrainOpenGLWidget* openGLWidget, const int32_t browserWindowIndex, const int32_t x, const int32_t y, const int32_t dx, const int32_t dy, const int32_t mousePressX, const int32_t mousePressY, const bool firstDraggingFlag); virtual ~MouseEvent(); private: void initializeMembersMouseEvent(); MouseEvent(const MouseEvent& o); MouseEvent& operator=(const MouseEvent& o); public: AString toString() const; BrainOpenGLViewportContent* getViewportContent() const; BrainOpenGLWidget* getOpenGLWidget() const; int32_t getBrowserWindowIndex() const; int32_t getDx() const; int32_t getDy() const; int32_t getX() const; int32_t getY() const; int32_t getPressedX() const; int32_t getPressedY() const; int32_t getWheelRotation() const; bool isFirstDragging() const; private: BrainOpenGLViewportContent* m_viewportContent; BrainOpenGLWidget* m_openGLWidget; int32_t m_browserWindowIndex; int32_t m_x; int32_t m_y; int32_t m_dx; int32_t m_dy; int32_t m_pressX; int32_t m_pressY; int32_t m_wheelRotation; bool m_firstDraggingFlag; }; } // namespace #endif // __MouseEvent_H__ workbench-1.1.1/src/GuiQt/MovieDialog.cxx000066400000000000000000000716721255417355300202660ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include "MovieDialog.h" #include "ui_MovieDialog.h" #include "BrowserTabContent.h" #include "BrainBrowserWindow.h" #include "CaretAssert.h" #include "CaretLogger.h" #include "CaretPreferences.h" #include "DataFileException.h" #include "EventManager.h" #include "EventGraphicsUpdateAllWindows.h" #include "EventGraphicsUpdateOneWindow.h" #include "EventImageCapture.h" #include "EventUserInterfaceUpdate.h" #include "GuiManager.h" #include "ImageFile.h" #include #include "Model.h" #include "ModelSurface.h" #include "ModelSurfaceSelector.h" #include "SessionManager.h" #include "Surface.h" #include "WuQMessageBox.h" #include #include #include using namespace caret; /** * \class caret::MovieDialog * \brief Dialog used for setting up and rendering Movies. * \ingroup GuiQt */ MovieDialog::MovieDialog(QWidget *parent) : QDialog(parent), ui(new Ui::MovieDialog) { ui->setupUi(this); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_GRAPHICS_UPDATE_ONE_WINDOW); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_GRAPHICS_UPDATE_ALL_WINDOWS); m_browserWindowIndex = 0; frame_number = 0; rotate_frame_number = 0; imageX = 0; imageY = 0; m_animationStarted = false; m_volumeSliceIncrement = 0; m_reverseVolumeSliceDirection = false; m_PStart = 0; m_CStart = 0; m_AStart = 0; m_PEnd = 0; m_CEnd = 0; m_AEnd = 0; m_interpolationIndex = 0; m_surface = NULL; m_surface1 = NULL; m_surface2 = NULL; } MovieDialog::~MovieDialog() { ui->animateButton->setChecked(false); delete ui; EventManager::get()->removeAllEventsFromListener(this); } void MovieDialog::on_closeButton_clicked() { ui->animateButton->setChecked(false); this->close(); } void MovieDialog::on_interpolateSurfaceCheckbox_toggled(bool checked) { if ( ! checked) { return; } BrainBrowserWindow *bw = GuiManager::get()->getBrowserWindowByWindowIndex(m_browserWindowIndex); if(!bw) { WuQMessageBox::errorOk(this, "Invalid browser window index, " + AString::number(m_browserWindowIndex)); return; } BrowserTabContent *btc1 = bw->getBrowserTabContent(0); BrowserTabContent *btc2 = bw->getBrowserTabContent(1); if(!btc1 || !btc2) { this->ui->interpolateSurfaceCheckbox->blockSignals(true); this->ui->interpolateSurfaceCheckbox->setChecked(false); this->ui->interpolateSurfaceCheckbox->blockSignals(false); WuQMessageBox::errorOk(this, "There must be two browser tabs."); return; } // int32_t tabIndex1 = btc1->getTabNumber(); if ((btc1->getSelectedModelType() != ModelTypeEnum::MODEL_TYPE_SURFACE) || (btc2->getSelectedModelType() != ModelTypeEnum::MODEL_TYPE_SURFACE)) { this->ui->interpolateSurfaceCheckbox->blockSignals(true); this->ui->interpolateSurfaceCheckbox->setChecked(false); this->ui->interpolateSurfaceCheckbox->blockSignals(false); WuQMessageBox::errorOk(this, "Both Tab 1 and Tab 2 must contain surface models."); return; } ModelSurface *ms1 = btc1->getDisplayedSurfaceModel(); // int32_t tabIndex2 = btc2->getTabNumber(); ModelSurface *ms2 = btc2->getDisplayedSurfaceModel(); if(!(ms1&&ms2)) return; if (ms1->getSurface()->getStructure() != ms2->getSurface()->getStructure()) { this->ui->interpolateSurfaceCheckbox->blockSignals(true); this->ui->interpolateSurfaceCheckbox->setChecked(false); this->ui->interpolateSurfaceCheckbox->blockSignals(false); WuQMessageBox::errorOk(this, "Surfaces in Tab 1 and Tab 2 must be the same structure."); return; } } void MovieDialog::on_animateButton_toggled(bool checked) { // this->renderMovieButton->setChecked(status); dx = this->ui->rotateXSpinBox->value(); dy = this->ui->rotateYSpinBox->value(); dz = this->ui->rotateZSpinBox->value(); frameCount = this->ui->rotateFrameCountSpinBox->value(); reverseDirection = this->ui->reverseDirectionCheckBox->isChecked(); frameCountEnabled = this->ui->rotateFrameCountSpinBox->value() ? true : false; m_interpolationEnabled = this->ui->interpolateSurfaceCheckbox->isChecked(); m_interpolationSteps = this->ui->interpolationStepsSpinBox->value(); if(checked) { //this->renderMovieButton->setText("Stop"); this->m_animationStarted = true; this->m_isInterpolating = true; while(this->ui->animateButton->isChecked()) { EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows(true).getPointer()); QCoreApplication::instance()->processEvents(); m_animationStarted = false; } this->m_animationStarted = false; } else { //this->renderMovieButton->setText("Play"); this->CleanupInterpolation(); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows(true).getPointer()); } rotate_frame_number = 0; } void MovieDialog::on_recordButton_toggled(bool checked) { if(checked) { m_useCustomSize = ui->customSizeRadioButton->isChecked(); imageX = 0; imageY = 0; if(m_useCustomSize) { imageX = ui->windowWidthSpinBox->value(); imageY = ui->windowHeightSpinBox->value(); } } if(!checked&&(frame_number > 0)) { //render frames.... QString formatString("Movie Files (*.mpg *.mp4)"); AString fileName = QFileDialog::getSaveFileName( this, tr("Save File"),QString::null, formatString ); AString tempDir = QDir::tempPath(); if ( !fileName.isEmpty() ) { // int crop[4]; // crop[0] = imageX; // crop[1] = imageY; // crop[2] = 0; // crop[3] = 0; // if(!(crop[0]&&crop[1])) GuiManager::get()->getBrowserWindowByWindowIndex(m_browserWindowIndex)->getViewportSize(crop[0],crop[1]); unlink(fileName); CaretLogInfo("Rendering movie to:" + fileName); AString ffmpeg = SystemUtilities::getWorkbenchHome() + AString("/ffmpeg "); // JWH ffmpeg = "/mnt/myelin/distribution/caret7_distribution/workbench//macosx64_apps/ffmpeg "; double frame_rate = 30.0/double(1 + this->ui->repeatFramesSpinBox->value()); // if(ui->cropImageCheckBox->isChecked()) // { // this->getImageCrop(tempDir + "/movie0" + AString(".png"),crop); // } // // crop[0] = (crop[0]/2)*2; // crop[1] = (crop[1]/2)*2; // CaretLogInfo("Resizing image from " + AString::number(imageX) + AString(":") + AString::number(imageY) + AString(" to ") + // AString::number(crop[0]) + AString(":") + AString::number(crop[1])); // // AString command = ffmpeg + AString("-threads 4 -r " + AString::number(frame_rate) + " -i "+ tempDir + "/movie%d.png -r 30 -q:v 1 -vf crop=" + // AString::number(crop[0]) + ":" + AString::number(crop[1]) + ":" + // AString::number(crop[2]) + ":" + AString::number(crop[3]) + " " + fileName); AString command = ffmpeg + AString("-threads 4 -r " + AString::number(frame_rate) + " -i "+ tempDir + "/movie%d.png -r 30 -q:v 1 " + fileName); CaretLogFine("running " + command); system(command.toAscii().data()); CaretLogFine("Finished rendering " + fileName); } for(int i = 0;iui->marginSpinBox->value(); // CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); // uint8_t backgroundColor[3]; // prefs->getColorBackground(backgroundColor); // // ImageFile file; // file.readFile(fileName); // // int leftTopRightBottom[4]; // file.findImageObject(backgroundColor,leftTopRightBottom); // // const int width = leftTopRightBottom[2] - leftTopRightBottom[0] + 1; // const int height = leftTopRightBottom[3] - leftTopRightBottom[1] + 1; // cropOut[0] = width + 2*marginSize; // cropOut[1] = height + 2*marginSize; // cropOut[2] = leftTopRightBottom[0]+marginSize; // cropOut[3] = leftTopRightBottom[1]+marginSize; //} void MovieDialog::processRotateTransformation(const double dx, const double dy, const double dz) { BrowserTabContent* browserTabContent = GuiManager::get()->getBrowserTabContentForBrowserWindow(m_browserWindowIndex, true); if (browserTabContent == NULL) { return; } Model* modelController = browserTabContent->getModelForDisplay(); if (modelController != NULL) { // const int32_t tabIndex = browserTabContent->getTabNumber(); { Matrix4x4 rotationMatrix = browserTabContent->getRotationMatrix(); rotationMatrix.rotateX(dx); rotationMatrix.rotateY(dy); rotationMatrix.rotateZ(dz); browserTabContent->setRotationMatrix(rotationMatrix); // /* // * There are several rotation matrix. The 'NORMAL' matrix is used // * in most cases and others are used in special viewing modes // * such as surface montage and right/left lateral medial yoking // */ // if (browserTabContent->isDisplayedModelSurfaceRightLateralMedialYoked()) { // Matrix4x4* rotationMatrix = modelController->getViewingRotationMatrix(tabIndex, // Model::VIEWING_TRANSFORM_NORMAL); // rotationMatrix->rotateX(dx); // rotationMatrix->rotateY(dy); // rotationMatrix->rotateZ(dz); // // /* // * Matrix for a right medial/lateral yoked surface // */ // Matrix4x4* rotationMatrixRightLatMedYoked = modelController->getViewingRotationMatrix(tabIndex, // Model::VIEWING_TRANSFORM_RIGHT_LATERAL_MEDIAL_YOKED); // rotationMatrixRightLatMedYoked->rotateX(-dx); // rotationMatrixRightLatMedYoked->rotateY(-dy); // rotationMatrixRightLatMedYoked->rotateZ(-dz); // } // else { // // // Matrix4x4* rotationMatrix = modelController->getViewingRotationMatrix(tabIndex, // Model::VIEWING_TRANSFORM_NORMAL); // rotationMatrix->rotateX(-dx); // rotationMatrix->rotateY(dy); // rotationMatrix->rotateZ(dz); // // /* // * Matrix for a left surface opposite view in surface montage // */ // Matrix4x4* rotationMatrixSurfMontLeftOpp = modelController->getViewingRotationMatrix(tabIndex, // Model::VIEWING_TRANSFORM_SURFACE_MONTAGE_LEFT_OPPOSITE); // rotationMatrixSurfMontLeftOpp->rotateX(-dx); // rotationMatrixSurfMontLeftOpp->rotateY(dy); // rotationMatrixSurfMontLeftOpp->rotateZ(dz); // // /* // * Matrix for a right surface view in surface montage // */ // Matrix4x4* rotationMatrixSurfMontRight = modelController->getViewingRotationMatrix(tabIndex, // Model::VIEWING_TRANSFORM_SURFACE_MONTAGE_RIGHT); // rotationMatrixSurfMontRight->rotateX(dx); // rotationMatrixSurfMontRight->rotateY(-dy); // rotationMatrixSurfMontRight->rotateZ(dz); // // /* // * Matrix for a right surface opposite view in surface montage // */ // Matrix4x4* rotationMatrixSurfMontRightOpp = modelController->getViewingRotationMatrix(tabIndex, // Model::VIEWING_TRANSFORM_SURFACE_MONTAGE_RIGHT_OPPOSITE); // rotationMatrixSurfMontRightOpp->rotateX(dx); // rotationMatrixSurfMontRightOpp->rotateY(-dy); // rotationMatrixSurfMontRightOpp->rotateZ(dz); // // /* // * Matrix for a right medial/lateral yoked surface // */ // Matrix4x4* rotationMatrixRightLatMedYoked = modelController->getViewingRotationMatrix(tabIndex, // Model::VIEWING_TRANSFORM_RIGHT_LATERAL_MEDIAL_YOKED); // rotationMatrixRightLatMedYoked->rotateX(dx); // rotationMatrixRightLatMedYoked->rotateY(-dy); // rotationMatrixRightLatMedYoked->rotateZ(dz); // } } } } /** * Receive events from the event manager. * * @param event * Event sent by event manager. */ void MovieDialog::receiveEvent(Event* event) { if(event->getEventType() == EventTypeEnum::EVENT_GRAPHICS_UPDATE_ALL_WINDOWS || event->getEventType() == EventTypeEnum::EVENT_GRAPHICS_UPDATE_ONE_WINDOW ) { AString tempPath = QDir::tempPath(); if(this->ui->recordButton->isChecked()) { this->captureFrame(tempPath + AString("/movie") + AString::number(frame_number) + AString(".png")); AString temp = tempPath + AString("/movie") + AString::number(frame_number) + AString(".png"); CaretLogFine(temp); CaretLogFine("frame number:" + QString::number(frame_number)); frame_number++; } if(this->ui->animateButton->isChecked()) { this->processUpdateVolumeSlice(); this->processUpdateSurfaceInterpolation(); if(frameCountEnabled && frameCount) { if(!reverseDirection) { if(frameCount <= rotate_frame_number) { //this->ui->animateButton->setChecked(false); dx = dy = dz = 0.0; return; } } else { if(!(rotate_frame_number %frameCount) && (rotate_frame_number > 0)) { dx *= -1.0; dy *= -1.0; dz *= -1.0; } } } if(dx || dy || dz) { this->processRotateTransformation(dx, dy, dz); } rotate_frame_number++; } } } int32_t MovieDialog::getSliceDelta(const std::vector &dim, const caret::VolumeSliceViewPlaneEnum::Enum &vpe, const int32_t &sliceIndex) { if(m_animationStarted) { m_volumeSliceIncrement = ui->sliceIncrementCountSpinBox->value(); m_reverseVolumeSliceDirection = ui->reverseSliceDirectionCheckBox->isChecked(); m_sliceIncrementIsNegative = (m_volumeSliceIncrement != abs(m_volumeSliceIncrement)); switch(vpe) { case VolumeSliceViewPlaneEnum::AXIAL: if(m_sliceIncrementIsNegative) { m_AEnd = sliceIndex; m_AStart = 0>(m_AEnd+m_volumeSliceIncrement)?0:m_AEnd+m_volumeSliceIncrement; dA=-1; } else { m_AStart = sliceIndex; m_AEnd = (dim[2]-1)<(m_AStart+m_volumeSliceIncrement)?(dim[2]-1):m_AStart+m_volumeSliceIncrement; dA=1; } break; case VolumeSliceViewPlaneEnum::CORONAL: if(m_sliceIncrementIsNegative) { m_CEnd = sliceIndex; m_CStart = 0>(m_CEnd+m_volumeSliceIncrement)?0:m_CEnd+m_volumeSliceIncrement; dC=-1; } else { m_CStart = sliceIndex; m_CEnd = (dim[1]-1)<(m_CStart+m_volumeSliceIncrement)?(dim[1]-1):m_CStart+m_volumeSliceIncrement; dC=1; } break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: if(m_sliceIncrementIsNegative) { m_PEnd = sliceIndex; m_PStart = 0>(m_PEnd+m_volumeSliceIncrement)?0:m_PEnd+m_volumeSliceIncrement; dP=-1; } else { m_PStart = sliceIndex; m_PEnd = (dim[0]-1)<(m_PStart+m_volumeSliceIncrement)?(dim[0]-1):m_PStart+m_volumeSliceIncrement; dP=1; } break; default: break; } } switch(vpe) { case VolumeSliceViewPlaneEnum::AXIAL: if(dA > 0 ) //the current increment is positive { if((m_AEnd-sliceIndex) > 0) //there is still room to increment by one { return dA; } else if(m_reverseVolumeSliceDirection) { return dA = -dA; } else { return 0; } } else if(dA < 0) { if((sliceIndex-m_AStart) > 0) { return dA; } else if(m_reverseVolumeSliceDirection) { return dA = -dA; } else { return 0; } } return dA; break; case VolumeSliceViewPlaneEnum::CORONAL: if(dC > 0 ) //the current increment is positive { if((m_CEnd-sliceIndex) > 0) //there is still room to increment by one { return dC; } else if(m_reverseVolumeSliceDirection) { return dC = -dC; } else { return 0; } } else if(dC < 0) { if((sliceIndex-m_CStart) > 0) { return dC; } else if(m_reverseVolumeSliceDirection) { return dC = -dC; } else { return 0; } } return dC; break; case VolumeSliceViewPlaneEnum::PARASAGITTAL: if(dP > 0 ) //the current increment is positive { if((m_PEnd-sliceIndex) > 0) //there is still room to increment by one { return dP; } else if(m_reverseVolumeSliceDirection) { return dP = -dP; } else { return 0; } } else if(dP < 0) { if((sliceIndex-m_PStart) > 0) { return dP; } else if(m_reverseVolumeSliceDirection) { return dP = -dP; } else { return 0; } } return dP; break; default: break; } return 0; } void MovieDialog::processUpdateVolumeSlice() { BrainBrowserWindow *bw = GuiManager::get()->getBrowserWindowByWindowIndex(m_browserWindowIndex); if(!bw) { CaretLogInfo("Invalid browser window index, " + AString::number(m_browserWindowIndex)); ui->animateButton->setChecked(false); return; } BrowserTabContent *btc = bw->getBrowserTabContent(); int32_t tabIndex = btc->getTabNumber(); ModelVolume* mv = btc->getDisplayedVolumeModel(); if(mv == NULL) { return; } switch (btc->getSliceProjectionType()) { case caret::VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_OBLIQUE: return; break; case caret::VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_ORTHOGONAL: break; } // VolumeSliceCoordinateSelection* vscs = btc->getSelectedVolumeSlices(); VolumeMappableInterface* vf = mv->getUnderlayVolumeFile(tabIndex); std::vector dim; vf->getDimensions(dim); VolumeSliceViewPlaneEnum::Enum vpe = btc->getSliceViewPlane(); if(vpe == VolumeSliceViewPlaneEnum::ALL || vpe == VolumeSliceViewPlaneEnum::AXIAL) { int64_t sliceIndex = btc->getSliceIndexAxial(vf); sliceIndex += this->getSliceDelta(dim,VolumeSliceViewPlaneEnum::AXIAL,sliceIndex); btc->setSliceIndexAxial(vf,sliceIndex); } if(vpe == VolumeSliceViewPlaneEnum::ALL || vpe == VolumeSliceViewPlaneEnum::CORONAL) { int64_t sliceIndex = btc->getSliceIndexCoronal(vf); sliceIndex += this->getSliceDelta(dim,VolumeSliceViewPlaneEnum::CORONAL,sliceIndex); btc->setSliceIndexCoronal(vf,sliceIndex); } if(vpe == VolumeSliceViewPlaneEnum::ALL || vpe == VolumeSliceViewPlaneEnum::PARASAGITTAL) { int64_t sliceIndex = btc->getSliceIndexParasagittal(vf); sliceIndex += this->getSliceDelta(dim,VolumeSliceViewPlaneEnum::PARASAGITTAL,sliceIndex); btc->setSliceIndexParasagittal(vf,sliceIndex); } } void MovieDialog::captureFrame(AString filename) { ImageFile imageFile; QApplication::setOverrideCursor(QCursor(Qt::BlankCursor)); // bool valid = GuiManager::get()->captureImageOfBrowserWindowGraphicsArea(m_browserWindowIndex, // imageX, // imageY, // imageFile, // false); EventImageCapture imageCaptureEvent(m_browserWindowIndex, imageX, imageY); EventManager::get()->sendEvent(imageCaptureEvent.getPointer()); bool valid = true; AString errorMessage; if (imageCaptureEvent.getEventProcessCount() <= 0) { errorMessage = "Invalid window selected"; valid = false; } else if (imageCaptureEvent.isError()) { errorMessage = imageCaptureEvent.getErrorMessage(); valid = false; } imageFile.setFromQImage(imageCaptureEvent.getImage()); QApplication::restoreOverrideCursor(); if (valid == false) { WuQMessageBox::errorOk(this, errorMessage); // "Invalid window selected"); ui->recordButton->setChecked(false); return; } /*if (ui->cropImageCheckBox->isChecked()) { const int marginSize = ui->marginSpinBox->value(); CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); uint8_t backgroundColor[3]; prefs->getColorBackground(backgroundColor); imageFile.cropImageRemoveBackground(marginSize, backgroundColor); croppedImageX = imageFile.getAsQImage()->size().height(); croppedImageY = imageFile.getAsQImage()->size().width(); }*/ std::vector imageFileExtensions; AString defaultFileExtension; ImageFile::getImageFileExtensions(imageFileExtensions, defaultFileExtension); bool validExtension = false; for (std::vector::iterator extensionIterator = imageFileExtensions.begin(); extensionIterator != imageFileExtensions.end(); extensionIterator++) { if (filename.endsWith(*extensionIterator)) { validExtension = true; } } if (validExtension == false) { if (defaultFileExtension.isEmpty() == false) { filename += ("." + defaultFileExtension); } } uint8_t backgroundColor[3]; imageCaptureEvent.getBackgroundColor(backgroundColor); try { const int marginSize = this->ui->marginSpinBox->value(); if (marginSize > 0) { imageFile.addMargin(marginSize, backgroundColor); } imageFile.writeFile(filename); } catch (const DataFileException& /*e*/) { QString msg("Unable to save: " + filename); WuQMessageBox::errorOk(this, msg); } } void MovieDialog::on_workbenchWindowSpinBox_valueChanged(int arg1) { m_browserWindowIndex = arg1-1; } void MovieDialog::processUpdateSurfaceInterpolation() { if(!m_interpolationEnabled||!m_isInterpolating) return; BrainBrowserWindow *bw = GuiManager::get()->getBrowserWindowByWindowIndex(m_browserWindowIndex); if(!bw) { CaretLogInfo("Invalid browser window index, " + AString::number(m_browserWindowIndex)); ui->animateButton->setChecked(false); return; } BrowserTabContent *btc1 = bw->getBrowserTabContent(0); BrowserTabContent *btc2 = bw->getBrowserTabContent(1); if(!btc1) return; if(!btc2) return; // int32_t tabIndex1 = btc1->getTabNumber(); if ((btc1->getSelectedModelType() != ModelTypeEnum::MODEL_TYPE_SURFACE) || (btc2->getSelectedModelType() != ModelTypeEnum::MODEL_TYPE_SURFACE)) { CaretLogInfo("Both Tab 1 and Tab 2 must contain surface models."); return; } ModelSurface *ms1 = btc1->getDisplayedSurfaceModel(); // int32_t tabIndex2 = btc2->getTabNumber(); ModelSurface *ms2 = btc2->getDisplayedSurfaceModel(); if(!(ms1&&ms2)) return; if (ms1->getSurface()->getStructure() != ms2->getSurface()->getStructure()) { CaretLogInfo("Surfaces in Tab 1 and Tab 2 must be the same structure."); return; } if(m_interpolationIndex == 0) { m_surface1 = ms1->getSurface(); m_surface2 = ms2->getSurface(); int32_t coordCount1 = m_surface1->getNumberOfNodes(); int32_t coordCount2 = m_surface2->getNumberOfNodes(); if(coordCount1 != coordCount2) return; const float* coords1 = m_surface1->getCoordinateData(); const float* coords2 = m_surface2->getCoordinateData(); float center1[3]; float center2[3]; float centerdelta[3]; m_surface1->getBoundingBox()->getCenter(center1); m_surface2->getBoundingBox()->getCenter(center2); for(int i = 0;i<3;i++) { centerdelta[i] = center2[i]-center1[i]; } m_surfaceCoords2Back.clear(); m_delta.clear(); for(int32_t i = 0;isetCoordinates(coords); m_surface2->invalidateNormals(); m_surface2->computeNormals(); btc1->getSurfaceModelSelector()->setSelectedSurfaceModel(btc2->getSurfaceModelSelector()->getSelectedSurfaceModel()); btc1->getSurfaceModelSelector()->setSelectedStructure(btc2->getSurfaceModelSelector()->getSelectedStructure()); EventManager::get()->sendEvent(EventUserInterfaceUpdate().getPointer()); } if(m_interpolationIndex setCoordinates(coords); m_surface2->invalidateNormals(); m_surface2->computeNormals(); m_interpolationIndex++; } else { CleanupInterpolation(); } } void MovieDialog::CleanupInterpolation() { if(!m_interpolationEnabled||!m_isInterpolating) return; if (m_surface2 == NULL) { return; } for(int64_t i = 0;isetCoordinates(coords); m_surface2->invalidateNormals(); m_surface2->computeNormals(); m_isInterpolating = false; m_interpolationIndex = 0; if(!coords) { delete coords; coords = NULL; coordsCount = 0; } m_surface1 = NULL; m_surface2 = NULL; } workbench-1.1.1/src/GuiQt/MovieDialog.h000066400000000000000000000061301255417355300176760ustar00rootroot00000000000000#ifndef MOVIE_DIALOG_H #define MOVIE_DIALOG_H /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "Event.h" #include "EventListenerInterface.h" #include namespace Ui { class MovieDialog; } using namespace caret; namespace caret { class Surface; } class MovieDialog : public QDialog, public EventListenerInterface { Q_OBJECT public: explicit MovieDialog(QWidget *parent = 0); ~MovieDialog(); void receiveEvent(Event* event); void processUpdateVolumeSlice(); void processUpdateSurfaceInterpolation(); int32_t getSliceDelta(const std::vector &dim, const caret::VolumeSliceViewPlaneEnum::Enum &vpe, const int32_t &sliceIndex); //void getImageCrop(AString fileName, int *cropout); private slots: void on_closeButton_clicked(); void on_animateButton_toggled(bool checked); void on_interpolateSurfaceCheckbox_toggled(bool checked); void on_recordButton_toggled(bool checked); void on_workbenchWindowSpinBox_valueChanged(int arg1); private: Ui::MovieDialog *ui; void captureFrame(AString filename); void processRotateTransformation(const double dx, const double dy, const double dz); void CleanupInterpolation(); int32_t m_browserWindowIndex; int frame_number; int rotate_frame_number; double dx; double dy; double dz; bool frameCountEnabled; int frameCount; bool reverseDirection; int32_t dP;//change in Parasagital slice int32_t dC;//change in Coronal slice int32_t dA;//change in Axial slice bool m_useCustomSize; int32_t imageX; int32_t imageY; int32_t croppedImageX; int32_t croppedImageY; bool m_animationStarted; int32_t m_volumeSliceIncrement; bool m_sliceIncrementIsNegative; bool m_reverseVolumeSliceDirection; int64_t m_AStart; int64_t m_AEnd; int64_t m_CStart; int64_t m_CEnd; int64_t m_PStart; int64_t m_PEnd; bool m_interpolationEnabled; int64_t m_interpolationSteps; int64_t m_interpolationIndex; bool m_isInterpolating; std::vector m_delta; std::vector m_surfaceCoords2Back; float *coords; int64_t coordsCount; Surface *m_surface1; Surface *m_surface2; Surface *m_surface; }; #endif // MOVIE_DIALOG_H workbench-1.1.1/src/GuiQt/MovieDialog.ui000077500000000000000000000355471255417355300201050ustar00rootroot00000000000000 MovieDialog 0 0 459 567 0 0 Animation Control true Image Source Workbench Window: 1 10 Qt::Horizontal 40 20 Recording Control true Repeat Frames: true Qt::Horizontal 40 20 true Record true Rotation Control 0 0 Y: 0 0 X: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter -180.000000000000000 180.000000000000000 -180.000000000000000 180.000000000000000 999999999 Rotate Frame Count: Reverse Direction -180.000000000000000 180.000000000000000 Z: Qt::Horizontal 40 20 true Slice Control Slice Increment Count -999 999 Reverse Direction Qt::Horizontal 40 20 true Interpolate Surfaces true Interpolate Surface in Tab 1 to Surface in Tab 2 Qt::Horizontal 40 20 Interpolation Steps true 1 99999 20 Qt::Horizontal 40 20 true Image Size Size of Window true Custom 1 10000 2560 1 10000 1600 Qt::Horizontal 40 20 true Image Options true Margin true 1000 10 Qt::Horizontal 40 20 Qt::Horizontal 40 20 Animate true Close true workbench-1.1.1/src/GuiQt/OverlaySetViewController.cxx000066400000000000000000000244011255417355300230470ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #include #define __OVERLAY_SET_VIEW_CONTROLLER_DECLARE__ #include "OverlaySetViewController.h" #undef __OVERLAY_SET_VIEW_CONTROLLER_DECLARE__ #include "BrainConstants.h" #include "BrowserTabContent.h" #include "CaretAssert.h" #include "EventGraphicsUpdateOneWindow.h" #include "EventManager.h" #include "EventSurfaceColoringInvalidate.h" #include "EventUserInterfaceUpdate.h" #include "GuiManager.h" #include "OverlaySet.h" #include "OverlayViewController.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::OverlaySetViewController * \brief View Controller for an overlay set. * \ingroup GuiQt */ /** * Constructor. * * @param orientation * Orientation for layout * @param browserWindowIndex * Index of browser window that contains this view controller. * @param parent * Parent widget. */ OverlaySetViewController::OverlaySetViewController(const Qt::Orientation orientation, const int32_t browserWindowIndex, QWidget* parent) : QWidget(parent) { this->browserWindowIndex = browserWindowIndex; QWidget* gridWidget = new QWidget(); QGridLayout* gridLayout = new QGridLayout(gridWidget); WuQtUtilities::setLayoutSpacingAndMargins(gridLayout, 4, 2); if (orientation == Qt::Horizontal) { gridLayout->setColumnStretch(0, 0); gridLayout->setColumnStretch(1, 0); gridLayout->setColumnStretch(2, 0); gridLayout->setColumnStretch(3, 0); gridLayout->setColumnStretch(4, 0); gridLayout->setColumnStretch(5, 100); gridLayout->setColumnStretch(6, 0); gridLayout->setColumnStretch(8, 100); QLabel* onLabel = new QLabel("On"); QLabel* settingsLabel = new QLabel("Settings"); QLabel* opacityLabel = new QLabel("Opacity"); QLabel* fileLabel = new QLabel("File"); QLabel* yokeLabel = new QLabel("Yoke"); QLabel* mapLabel = new QLabel("Map"); const int row = gridLayout->rowCount(); gridLayout->addWidget(onLabel, row, 0, Qt::AlignHCenter); gridLayout->addWidget(settingsLabel, row, 1, 1, 3, Qt::AlignHCenter); gridLayout->addWidget(opacityLabel, row, 4, Qt::AlignHCenter); gridLayout->addWidget(fileLabel, row, 5, Qt::AlignHCenter); gridLayout->addWidget(yokeLabel, row, 6, Qt::AlignHCenter); gridLayout->addWidget(mapLabel, row, 8, 1, 2, Qt::AlignHCenter); } else { gridLayout->setColumnStretch(0, 0); gridLayout->setColumnStretch(1, 0); gridLayout->setColumnStretch(2, 0); gridLayout->setColumnStretch(3, 0); gridLayout->setColumnStretch(4, 0); gridLayout->setColumnStretch(5, 0); gridLayout->setColumnStretch(6, 100); } for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_OVERLAYS; i++) { OverlayViewController* ovc = new OverlayViewController(orientation, gridLayout, browserWindowIndex, i, this); this->overlayViewControllers.push_back(ovc); QObject::connect(ovc, SIGNAL(requestAddOverlayAbove(const int32_t)), this, SLOT(processAddOverlayAbove(const int32_t))); QObject::connect(ovc, SIGNAL(requestAddOverlayBelow(const int32_t)), this, SLOT(processAddOverlayBelow(const int32_t))); QObject::connect(ovc, SIGNAL(requestRemoveOverlay(const int32_t)), this, SLOT(processRemoveOverlay(const int32_t))); QObject::connect(ovc, SIGNAL(requestMoveOverlayUp(const int32_t)), this, SLOT(processMoveOverlayUp(const int32_t))); QObject::connect(ovc, SIGNAL(requestMoveOverlayDown(const int32_t)), this, SLOT(processMoveOverlayDown(const int32_t))); } if (orientation == Qt::Horizontal) { QVBoxLayout* verticalLayout = new QVBoxLayout(this); WuQtUtilities::setLayoutSpacingAndMargins(verticalLayout, 2, 2); verticalLayout->addWidget(gridWidget); verticalLayout->addStretch(); } else { QVBoxLayout* verticalLayout = new QVBoxLayout(); WuQtUtilities::setLayoutSpacingAndMargins(verticalLayout, 1, 1); verticalLayout->addWidget(gridWidget); verticalLayout->addStretch(); QHBoxLayout* horizontalLayout = new QHBoxLayout(this); WuQtUtilities::setLayoutSpacingAndMargins(horizontalLayout, 1, 1); horizontalLayout->addLayout(verticalLayout); horizontalLayout->addStretch(); } EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_USER_INTERFACE_UPDATE); } /** * Destructor. */ OverlaySetViewController::~OverlaySetViewController() { EventManager::get()->removeAllEventsFromListener(this); } /** * @return The overlay set in this view controller. */ OverlaySet* OverlaySetViewController::getOverlaySet() { OverlaySet* overlaySet = NULL; BrowserTabContent* browserTabContent = GuiManager::get()->getBrowserTabContentForBrowserWindow(this->browserWindowIndex, true); if (browserTabContent != NULL) { overlaySet = browserTabContent->getOverlaySet(); } return overlaySet; } /** * Update this overlay set view controller using the given overlay set. */ void OverlaySetViewController::updateViewController() { OverlaySet* overlaySet = this->getOverlaySet(); if (overlaySet == NULL) { return; } const int32_t numberOfOverlays = static_cast(this->overlayViewControllers.size()); const int32_t numberOfDisplayedOverlays = overlaySet->getNumberOfDisplayedOverlays(); for (int32_t i = 0; i < numberOfOverlays; i++) { Overlay* overlay = NULL; if (overlaySet != NULL) { overlay = overlaySet->getOverlay(i); } this->overlayViewControllers[i]->updateViewController(overlay); bool displayOverlay = (overlay != NULL); if (i >= numberOfDisplayedOverlays) { displayOverlay = false; } this->overlayViewControllers[i]->setVisible(displayOverlay); } } /** * Receive events from the event manager. * * @param event * Event sent by event manager. */ void OverlaySetViewController::receiveEvent(Event* event) { if (event->getEventType() == EventTypeEnum::EVENT_USER_INTERFACE_UPDATE) { EventUserInterfaceUpdate* uiEvent = dynamic_cast(event); CaretAssert(uiEvent); if (uiEvent->isUpdateForWindow(this->browserWindowIndex)) { if (uiEvent->isToolBoxUpdate()) { this->updateViewController(); uiEvent->setEventProcessed(); } } } } /** * Add an overlay above the overlay with the given index. * @param overlayIndex * Index of overlay that will have an overlay added above it. */ void OverlaySetViewController::processAddOverlayAbove(const int32_t overlayIndex) { OverlaySet* overlaySet = getOverlaySet(); if (overlaySet != NULL) { overlaySet->insertOverlayAbove(overlayIndex); this->updateColoringAndGraphics(); } } /** * Add an overlay below the overlay with the given index. * @param overlayIndex * Index of overlay that will have an overlay added below it. */ void OverlaySetViewController::processAddOverlayBelow(const int32_t overlayIndex) { OverlaySet* overlaySet = getOverlaySet(); if (overlaySet != NULL) { overlaySet->insertOverlayBelow(overlayIndex); this->updateColoringAndGraphics(); } } /** * Remove an overlay above the overlay with the given index. * @param overlayIndex * Index of overlay that will be removed */ void OverlaySetViewController::processRemoveOverlay(const int32_t overlayIndex) { OverlaySet* overlaySet = getOverlaySet(); if (overlaySet != NULL) { overlaySet->removeDisplayedOverlay(overlayIndex); this->updateColoringAndGraphics(); } } /** * Remove an overlay above the overlay with the given index. * @param overlayIndex * Index of overlay that will be removed */ void OverlaySetViewController::processMoveOverlayDown(const int32_t overlayIndex) { OverlaySet* overlaySet = getOverlaySet(); if (overlaySet != NULL) { overlaySet->moveDisplayedOverlayDown(overlayIndex); this->updateColoringAndGraphics(); } } /** * Remove an overlay above the overlay with the given index. * @param overlayIndex * Index of overlay that will be removed */ void OverlaySetViewController::processMoveOverlayUp(const int32_t overlayIndex) { OverlaySet* overlaySet = getOverlaySet(); if (overlaySet != NULL) { overlaySet->moveDisplayedOverlayUp(overlayIndex); this->updateColoringAndGraphics(); } } /** * Update surface coloring and graphics after overlay changes. */ void OverlaySetViewController::updateColoringAndGraphics() { this->updateViewController(); EventManager::get()->sendEvent(EventSurfaceColoringInvalidate().getPointer()); EventGraphicsUpdateOneWindow graphicsUpdate(this->browserWindowIndex); EventManager::get()->sendEvent(graphicsUpdate.getPointer()); } workbench-1.1.1/src/GuiQt/OverlaySetViewController.h000066400000000000000000000051011255417355300224700ustar00rootroot00000000000000#ifndef __OVERLAY_SET_VIEW_CONTROLLER__H_ #define __OVERLAY_SET_VIEW_CONTROLLER__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include "EventListenerInterface.h" class QScrollArea; class QSpinBox; namespace caret { class OverlaySet; class OverlayViewController; class OverlaySetViewController : public QWidget, public EventListenerInterface { Q_OBJECT public: OverlaySetViewController(const Qt::Orientation orientation, const int32_t browserWindowIndex, QWidget* parent = 0); virtual ~OverlaySetViewController(); void receiveEvent(Event* event); private slots: void processAddOverlayAbove(const int32_t overlayIndex); void processAddOverlayBelow(const int32_t overlayIndex); void processRemoveOverlay(const int32_t overlayIndex); void processMoveOverlayDown(const int32_t overlayIndex); void processMoveOverlayUp(const int32_t overlayIndex); private: OverlaySetViewController(const OverlaySetViewController&); OverlaySetViewController& operator=(const OverlaySetViewController&); OverlaySet* getOverlaySet(); void updateViewController(); void updateColoringAndGraphics(); std::vector overlayViewControllers; int32_t browserWindowIndex; QScrollArea* scrollArea; }; #ifdef __OVERLAY_SET_VIEW_CONTROLLER_DECLARE__ // #endif // __OVERLAY_SET_VIEW_CONTROLLER_DECLARE__ } // namespace #endif //__OVERLAY_SET_VIEW_CONTROLLER__H_ workbench-1.1.1/src/GuiQt/OverlaySettingsEditorDialog.cxx000066400000000000000000000406011255417355300235040ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #include #include #define __OVERLAY_SETTINGS_EDITOR_DIALOG_DECLARE__ #include "OverlaySettingsEditorDialog.h" #undef __OVERLAY_SETTINGS_EDITOR_DIALOG_DECLARE__ #include "CaretMappableDataFile.h" #include "CiftiFiberTrajectoryFile.h" #include "CiftiConnectivityMatrixParcelFile.h" #include "EventDataFileDelete.h" #include "EventGraphicsUpdateAllWindows.h" #include "EventManager.h" #include "EventOverlayValidate.h" #include "EventSurfaceColoringInvalidate.h" #include "EventUserInterfaceUpdate.h" #include "GiftiLabelTableEditor.h" #include "MapSettingsFiberTrajectoryWidget.h" #include "MapSettingsLabelsWidget.h" #include "MapSettingsLayerWidget.h" #include "MapSettingsPaletteColorMappingWidget.h" #include "MapSettingsParcelsWidget.h" #include "Overlay.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::__OVERLAY_SETTINGS_EDITOR_DIALOG_DECLARE__ * \brief Dialog for editing an overlay's settings * \ingroup GuiQt */ /** * Constructor for editing a palette selection. * * @param parent * Parent widget on which this dialog is displayed. */ OverlaySettingsEditorDialog::OverlaySettingsEditorDialog(QWidget* parent) : WuQDialogNonModal("Overlay and Map Settings", parent), EventListenerInterface() { /* * No context menu, it screws things up */ this->setContextMenuPolicy(Qt::NoContextMenu); this->setDeleteWhenClosed(false); m_caretMappableDataFile = NULL; m_mapIndex = -1; /* * No apply button */ this->setApplyButtonText(""); QWidget* mapNameWidget = createMapFileAndNameSection(); m_fiberTrajectoryWidget = new MapSettingsFiberTrajectoryWidget(); m_layerWidget = new MapSettingsLayerWidget(); m_paletteColorMappingWidget = new MapSettingsPaletteColorMappingWidget(); m_parcelsWidget = new MapSettingsParcelsWidget(); QWidget* windowOptionsWidget = this->createWindowOptionsSection(); m_labelsWidget = new MapSettingsLabelsWidget(); m_tabWidget = new QTabWidget(); m_labelsWidgetTabIndex = m_tabWidget->addTab(m_labelsWidget, "Labels"); m_tabWidget->setTabEnabled(m_tabWidget->count() - 1, false); m_layersWidgetTabIndex = m_tabWidget->addTab(m_layerWidget, "Layer"); m_metadataWidgetTabIndex = m_tabWidget->addTab(new QWidget(), "Metadata"); m_tabWidget->setTabEnabled(m_tabWidget->count() - 1, false); m_paletteWidgetTabIndex = m_tabWidget->addTab(m_paletteColorMappingWidget, "Palette"); m_parcelsWidgetTabIndex = m_tabWidget->addTab(m_parcelsWidget, "Parcels"); m_trajectoryWidgetTabIndex = m_tabWidget->addTab(m_fiberTrajectoryWidget, "Trajectory"); m_tabWidget->setCurrentIndex(m_tabWidget->count() - 1); QWidget* w = new QWidget(); QVBoxLayout* layout = new QVBoxLayout(w); this->setLayoutSpacingAndMargins(layout); layout->addWidget(mapNameWidget); layout->addWidget(m_tabWidget); //layout->addWidget(windowOptionsWidget); this->setCentralWidget(w, WuQDialog::SCROLL_AREA_NEVER); this->addWidgetToLeftOfButtons(windowOptionsWidget); disableAutoDefaultForAllPushButtons(); EventManager::get()->addProcessedEventListener(this, EventTypeEnum::EVENT_DATA_FILE_DELETE); } /** * Destructor. */ OverlaySettingsEditorDialog::~OverlaySettingsEditorDialog() { EventManager::get()->removeAllEventsFromListener(this); } /** * Receive an event. * * @param event * An event for which this instance is listening. */ void OverlaySettingsEditorDialog::receiveEvent(Event* event) { if (event->getEventType() == EventTypeEnum::EVENT_DATA_FILE_DELETE) { updateDialog(); } } /** * @return Create and return map file and name section of the dialog. * */ QWidget* OverlaySettingsEditorDialog::createMapFileAndNameSection() { QLabel* mapFileNameLabel = new QLabel("Map File: "); m_selectedMapFileNameLabel = new QLabel(""); QLabel* mapNameLabel = new QLabel("Map Name: "); m_selectedMapNameLabel = new QLabel(""); QGroupBox* groupBox = new QGroupBox(); QGridLayout* gridLayout = new QGridLayout(groupBox); WuQtUtilities::setLayoutSpacingAndMargins(gridLayout, 2, 2); gridLayout->setColumnStretch(0, 0); gridLayout->setColumnStretch(1, 100); gridLayout->addWidget(mapFileNameLabel, 0, 0); gridLayout->addWidget(m_selectedMapFileNameLabel, 0, 1, Qt::AlignLeft); gridLayout->addWidget(mapNameLabel, 1, 0); gridLayout->addWidget(m_selectedMapNameLabel, 1, 1, Qt::AlignLeft); return groupBox; } /** * Called for focus events. Since this dialog stores a pointer * to the overlay, we need to be aware that the overlay's parameters * may change or the overlay may even be deleted. So, when * this dialog gains focus, validate the overlay and then update * the dialog. * * @param event * The focus event. */ void OverlaySettingsEditorDialog::focusInEvent(QFocusEvent* /*event*/) { updateDialog(); } /** * Update if the given overlay is displayed in the dialog. * * @param overlay * The overlay. */ void OverlaySettingsEditorDialog::updateIfThisOverlayIsInDialog(Overlay* overlay) { if (m_overlay == overlay) { updateDialogContent(m_overlay); } } /** * May be called to update the dialog's content. * * @param overlay * Overlay for the dialog. */ void OverlaySettingsEditorDialog::updateDialogContent(Overlay* overlay) { const int32_t selectedTabIndex = m_tabWidget->currentIndex(); m_overlay = overlay; // m_caretMappableDataFile = caretMappableDataFile; // m_mapFileIndex = mapFileIndex; m_caretMappableDataFile = NULL; m_mapIndex = -1; if (m_overlay != NULL) { overlay->getSelectionData(m_caretMappableDataFile, m_mapIndex); } bool isLabelsValid = false; bool isMetadataValid = false; GiftiMetaData* metadata = NULL; bool isPaletteValid = false; bool isParcelsValid = false; bool isFiberTrajectoryValid = false; bool isVolumeLayer = false; QString mapFileName = ""; QString mapName = ""; if (m_caretMappableDataFile != NULL) { if ((m_mapIndex >= 0) && (m_mapIndex < m_caretMappableDataFile->getNumberOfMaps())) { /* * Get name of file and map */ mapFileName = m_caretMappableDataFile->getFileNameNoPath(); if (m_mapIndex >= 0) { mapName = m_caretMappableDataFile->getMapName(m_mapIndex); } /* * Update layer widget */ m_layerWidget->updateContent(m_overlay); if (m_caretMappableDataFile->isMappedWithLabelTable()) { if (m_caretMappableDataFile->getMapLabelTable(m_mapIndex) != NULL) { /* * Update label editor */ isLabelsValid = true; m_labelsWidget->updateContent(overlay); } } metadata = m_caretMappableDataFile->getMapMetaData(m_mapIndex); if (metadata != NULL) { /* * TODO: Update metadata widget */ //isMetadataValid = true; } if (m_caretMappableDataFile->isMappedWithPalette()) { if (m_caretMappableDataFile->getMapPaletteColorMapping(m_mapIndex) != NULL) { /* * Update palette settings */ isPaletteValid = true; m_paletteColorMappingWidget->updateEditor(m_caretMappableDataFile, m_mapIndex); } } CiftiFiberTrajectoryFile* trajFile = dynamic_cast(m_caretMappableDataFile); if (trajFile != NULL) { /* * Update trajectory */ isFiberTrajectoryValid = true; m_fiberTrajectoryWidget->updateEditor(trajFile); } CiftiConnectivityMatrixParcelFile* parcelsFile = dynamic_cast(m_caretMappableDataFile); if (parcelsFile != NULL) { /* * Update parcels */ isParcelsValid = true; m_parcelsWidget->updateEditor(parcelsFile); } /* * Is volume mappable */ if (m_caretMappableDataFile->isVolumeMappable()) { if (isFiberTrajectoryValid) { /* nothing */ } else { isVolumeLayer = true; } } } } /* * Set enabled status of tabs */ m_tabWidget->setTabEnabled(m_labelsWidgetTabIndex, isLabelsValid); m_tabWidget->setTabEnabled(m_layersWidgetTabIndex, isVolumeLayer); m_tabWidget->setTabEnabled(m_metadataWidgetTabIndex, isMetadataValid); m_tabWidget->setTabEnabled(m_paletteWidgetTabIndex, isPaletteValid); m_tabWidget->setTabEnabled(m_parcelsWidgetTabIndex, isParcelsValid); m_tabWidget->setTabEnabled(m_trajectoryWidgetTabIndex, isFiberTrajectoryValid); /* * When the selected tab is invalid, we want to select the * "best" tab that is enabled. The order in this vector * is the priority of the tabs. */ std::vector priorityTabIndices; priorityTabIndices.push_back(m_paletteWidgetTabIndex); priorityTabIndices.push_back(m_labelsWidgetTabIndex); priorityTabIndices.push_back(m_parcelsWidgetTabIndex); priorityTabIndices.push_back(m_trajectoryWidgetTabIndex); priorityTabIndices.push_back(m_layersWidgetTabIndex); priorityTabIndices.push_back(m_metadataWidgetTabIndex); CaretAssertMessage((static_cast(priorityTabIndices.size()) == m_tabWidget->count()), "Number of elements in priorityTabIndices is different " "than number of tab indices. Was a new tab added?"); /* * Make sure an enabled tab is selected using the tabs * in the priority order */ if (selectedTabIndex >= 0) { if (m_tabWidget->isTabEnabled(selectedTabIndex) == false) { const int32_t numPriorityTabs = static_cast(priorityTabIndices.size()); for (int32_t i = 0; i < numPriorityTabs; i++) { const int32_t tabIndex = priorityTabIndices[i]; if (m_tabWidget->isTabEnabled(tabIndex)) { m_tabWidget->setCurrentIndex(tabIndex); break; } } } } m_selectedMapFileNameLabel->setText(mapFileName); m_selectedMapNameLabel->setText(mapName); } /** * May be called to update the dialog. */ void OverlaySettingsEditorDialog::updateDialog() { /* * Validate overlay to prevent crash */ EventOverlayValidate validateOverlayEvent(m_overlay); EventManager::get()->sendEvent(validateOverlayEvent.getPointer()); if (validateOverlayEvent.isValidOverlay() == false) { m_overlay = NULL; } updateDialogContent(m_overlay); if (m_overlay == NULL) { close(); } } /** * Called when close button pressed. */ void OverlaySettingsEditorDialog::closeButtonPressed() { /* * Clear the content since it could be tied to an overlay * and we don't want the dialog updating if it is not * visible. */ updateDialogContent(NULL); /* * Allow this dialog to be reused (checked means DO NOT reuse) */ m_doNotReplaceCheckBox->setCheckState(Qt::Unchecked); WuQDialogNonModal::closeButtonClicked(); } /** * Set the layout margins. * @param layout * Layout for which margins are set. */ void OverlaySettingsEditorDialog::setLayoutSpacingAndMargins(QLayout* layout) { WuQtUtilities::setLayoutSpacingAndMargins(layout, 5, 3); } /** * @return Is the Do Not Replace selected. If so, this instance of the * dialog should not be replaced. */ bool OverlaySettingsEditorDialog::isDoNotReplaceSelected() const { const bool checked = (m_doNotReplaceCheckBox->checkState() == Qt::Checked); return checked; } /** * Called when the state of the do not reply checkbox is changed. * @param state * New state of checkbox. */ void OverlaySettingsEditorDialog::doNotReplaceCheckBoxStateChanged(int /*state*/) { // const bool checked = (state == Qt::Checked); } /** * @return A widget containing the window options. */ QWidget* OverlaySettingsEditorDialog::createWindowOptionsSection() { m_doNotReplaceCheckBox = new QCheckBox("Do Not Replace"); m_doNotReplaceCheckBox->setToolTip("If checked: \n" " (1) this window remains displayed until it is\n" " closed.\n" " (2) if the user selects editing of another map's\n" " palette, it will not replace the content of\n" " this window.\n" "If NOT checked:\n" " If the user selects editing of another map's \n" " palette, it will replace the content of this\n" " window."); QWidget* optionsWidget = new QWidget(); QVBoxLayout* optionsLayout = new QVBoxLayout(optionsWidget); this->setLayoutSpacingAndMargins(optionsLayout); optionsLayout->addWidget(m_doNotReplaceCheckBox); optionsWidget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); return optionsWidget; } /** * Called when the edit label table button is clicked. */ void OverlaySettingsEditorDialog::editLabelTablePushButtonClicked() { if (m_caretMappableDataFile != NULL) { if (m_caretMappableDataFile->isMappedWithLabelTable()) { if (m_mapIndex >= 0) { GiftiLabelTableEditor labelTableEditor(m_caretMappableDataFile, m_mapIndex, "Edit Labels", GiftiLabelTableEditor::OPTION_ADD_APPLY_BUTTON, m_editLabelTablePushButton); labelTableEditor.exec(); } } } } /** * Create and return widget for editing label tables. */ QWidget* OverlaySettingsEditorDialog::createLabelsSection() { m_editLabelTablePushButton = new QPushButton("Edit"); QObject::connect(m_editLabelTablePushButton, SIGNAL(clicked()), this, SLOT(editLabelTablePushButtonClicked())); QWidget* widget = new QWidget(); QVBoxLayout* layout = new QVBoxLayout(widget); layout->addWidget(m_editLabelTablePushButton, 0, Qt::AlignLeft); layout->addStretch(); return widget; } workbench-1.1.1/src/GuiQt/OverlaySettingsEditorDialog.h000066400000000000000000000074641255417355300231430ustar00rootroot00000000000000#ifndef __OVERLAY_SETTINGS_EDITOR_DIALOG__H_ #define __OVERLAY_SETTINGS_EDITOR_DIALOG__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "EventListenerInterface.h" #include "WuQDialogNonModal.h" class QCheckBox; class QLabel; class QLayout; class QPushButton; class QTabWidget; namespace caret { class CaretMappableDataFile; class LabelTableEditorWidget; class MapSettingsFiberTrajectoryWidget; class MapSettingsLabelsWidget; class MapSettingsLayerWidget; class MapSettingsPaletteColorMappingWidget; class MapSettingsParcelsWidget; class Overlay; class OverlaySettingsEditorDialog : public WuQDialogNonModal, public EventListenerInterface { Q_OBJECT public: OverlaySettingsEditorDialog(QWidget* parent); void updateDialogContent(Overlay* overlay); void updateIfThisOverlayIsInDialog(Overlay* overlay); void updateDialog(); virtual ~OverlaySettingsEditorDialog(); bool isDoNotReplaceSelected() const; virtual void receiveEvent(Event* event); protected: virtual void closeButtonPressed(); virtual void focusInEvent(QFocusEvent* event); private: OverlaySettingsEditorDialog(const OverlaySettingsEditorDialog&); OverlaySettingsEditorDialog& operator=(const OverlaySettingsEditorDialog&); private slots: void doNotReplaceCheckBoxStateChanged(int state); void editLabelTablePushButtonClicked(); private: QWidget* createWindowOptionsSection(); QWidget* createMapFileAndNameSection(); QWidget* createLabelsSection(); void setLayoutSpacingAndMargins(QLayout* layout); QTabWidget* m_tabWidget; QCheckBox* m_doNotReplaceCheckBox; CaretMappableDataFile* m_caretMappableDataFile; Overlay* m_overlay; int32_t m_mapIndex; MapSettingsPaletteColorMappingWidget* m_paletteColorMappingWidget; MapSettingsParcelsWidget* m_parcelsWidget; LabelTableEditorWidget* m_labelTableEditorWidget; MapSettingsFiberTrajectoryWidget* m_fiberTrajectoryWidget; MapSettingsLayerWidget* m_layerWidget; MapSettingsLabelsWidget* m_labelsWidget; QPushButton* m_editLabelTablePushButton; QLabel* m_selectedMapFileNameLabel; QLabel* m_selectedMapNameLabel; int32_t m_labelsWidgetTabIndex; int32_t m_layersWidgetTabIndex; int32_t m_metadataWidgetTabIndex; int32_t m_paletteWidgetTabIndex; int32_t m_parcelsWidgetTabIndex; int32_t m_trajectoryWidgetTabIndex; }; #ifdef __OVERLAY_SETTINGS_EDITOR_DIALOG_DECLARE__ #endif // __OVERLAY_SETTINGS_EDITOR_DIALOG_DECLARE__ } // namespace #endif //__OVERLAY_SETTINGS_EDITOR_DIALOG__H_ workbench-1.1.1/src/GuiQt/OverlayViewController.cxx000066400000000000000000001013231255417355300223720ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #include #include #include #include #include #include #include #include #define __OVERLAY_VIEW_CONTROLLER_DECLARE__ #include "OverlayViewController.h" #undef __OVERLAY_VIEW_CONTROLLER_DECLARE__ #include "CaretMappableDataFile.h" #include "EventDataFileReload.h" #include "EventGraphicsUpdateAllWindows.h" #include "EventGraphicsUpdateOneWindow.h" #include "EventManager.h" #include "EventMapYokingSelectMap.h" #include "EventOverlaySettingsEditorDialogRequest.h" #include "EventSurfaceColoringInvalidate.h" #include "EventUserInterfaceUpdate.h" #include "FileInformation.h" #include "FilePathNamePrefixCompactor.h" #include "GuiManager.h" #include "MapYokingGroupComboBox.h" #include "Overlay.h" #include "UsernamePasswordWidget.h" #include "WuQFactory.h" #include "WuQMessageBox.h" #include "WuQtUtilities.h" #include "WuQGridLayoutGroup.h" #include "WuQWidgetObjectGroup.h" using namespace caret; /** * \class caret::OverlayViewController * \brief View Controller for an overlay. * \ingroup GuiQt */ /** * Constructor. * * @param browserWindowIndex * Index of browser window in which this view controller resides. * @param showTopHorizontalLine * If true, display a horizontal line above the controls. * @param parent * The parent widget. */ OverlayViewController::OverlayViewController(const Qt::Orientation orientation, QGridLayout* gridLayout, const int32_t browserWindowIndex, const int32_t overlayIndex, QObject* parent) : QObject(parent), browserWindowIndex(browserWindowIndex), m_overlayIndex(overlayIndex) { this->overlay = NULL; m_constructionReloadFileAction = NULL; int minComboBoxWidth = 200; int maxComboBoxWidth = 100000; //400; if (orientation == Qt::Horizontal) { minComboBoxWidth = 50; maxComboBoxWidth = 100000; } /* * Enabled Check Box */ const QString checkboxText = ((orientation == Qt::Horizontal) ? " " : " "); this->enabledCheckBox = new QCheckBox(checkboxText); QObject::connect(this->enabledCheckBox, SIGNAL(clicked(bool)), this, SLOT(enabledCheckBoxClicked(bool))); this->enabledCheckBox->setToolTip("Enables display of this overlay"); /* * File Selection Check Box */ this->fileComboBox = WuQFactory::newComboBox(); this->fileComboBox->setMinimumWidth(minComboBoxWidth); this->fileComboBox->setMaximumWidth(maxComboBoxWidth); QObject::connect(this->fileComboBox, SIGNAL(activated(int)), this, SLOT(fileComboBoxSelected(int))); this->fileComboBox->setToolTip("Selects file for this overlay"); /* * Map Index Spin Box */ m_mapIndexSpinBox = WuQFactory::newSpinBox(); QObject::connect(m_mapIndexSpinBox, SIGNAL(valueChanged(int)), this, SLOT(mapIndexSpinBoxValueChanged(int))); m_mapIndexSpinBox->setToolTip("Select map by its index"); /* * Map Name Combo Box */ this->mapNameComboBox = WuQFactory::newComboBox(); this->mapNameComboBox->setMinimumWidth(minComboBoxWidth); this->mapNameComboBox->setMaximumWidth(maxComboBoxWidth); QObject::connect(this->mapNameComboBox, SIGNAL(activated(int)), this, SLOT(mapNameComboBoxSelected(int))); this->mapNameComboBox->setToolTip("Select map by its name"); /* * Opacity double spin box */ this->opacityDoubleSpinBox = WuQFactory::newDoubleSpinBox(); this->opacityDoubleSpinBox->setMinimum(0.0); this->opacityDoubleSpinBox->setMaximum(1.0); this->opacityDoubleSpinBox->setSingleStep(0.10); this->opacityDoubleSpinBox->setDecimals(1); this->opacityDoubleSpinBox->setFixedWidth(50); QObject::connect(this->opacityDoubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(opacityDoubleSpinBoxValueChanged(double))); this->opacityDoubleSpinBox->setToolTip("Opacity (0.0=transparent, 1.0=opaque)"); /* * ColorBar Tool Button */ QIcon colorBarIcon; const bool colorBarIconValid = WuQtUtilities::loadIcon(":/LayersPanel/colorbar.png", colorBarIcon); this->colorBarAction = WuQtUtilities::createAction("CB", "Display color bar for this overlay", this, this, SLOT(colorBarActionTriggered(bool))); this->colorBarAction->setCheckable(true); if (colorBarIconValid) { this->colorBarAction->setIcon(colorBarIcon); } QToolButton* colorBarToolButton = new QToolButton(); colorBarToolButton->setDefaultAction(this->colorBarAction); /* * Settings Tool Button */ QIcon settingsIcon; const bool settingsIconValid = WuQtUtilities::loadIcon(":/LayersPanel/wrench.png", settingsIcon); this->settingsAction = WuQtUtilities::createAction("S", "Edit settings for this map and overlay", this, this, SLOT(settingsActionTriggered())); if (settingsIconValid) { this->settingsAction->setIcon(settingsIcon); } QToolButton* settingsToolButton = new QToolButton(); settingsToolButton->setDefaultAction(this->settingsAction); /* * Construction Tool Button */ QIcon constructionIcon; const bool constructionIconValid = WuQtUtilities::loadIcon(":/LayersPanel/construction.png", constructionIcon); this->constructionAction = WuQtUtilities::createAction("M", "Add/Move/Remove Overlays", this); if (constructionIconValid) { this->constructionAction->setIcon(constructionIcon); } m_constructionToolButton = new QToolButton(); QMenu* constructionMenu = createConstructionMenu(m_constructionToolButton); this->constructionAction->setMenu(constructionMenu); m_constructionToolButton->setDefaultAction(this->constructionAction); m_constructionToolButton->setPopupMode(QToolButton::InstantPopup); const AString yokeToolTip = ("Select a yoking group.\n" "\n" "When files with more than one map are yoked,\n" "the seleted maps are synchronized by map index.\n" "\n" "If the SAME FILE is in yoked in multiple overlays,\n" "the overlay enabled statuses are synchronized.\n"); /* * Yoking Group */ m_mapYokingGroupComboBox = new MapYokingGroupComboBox(this); m_mapYokingGroupComboBox->getWidget()->setStatusTip("Synchronize enabled status and map indices)"); m_mapYokingGroupComboBox->getWidget()->setToolTip("Yoke to Overlay Mapped Files"); #ifdef CARET_OS_MACOSX m_mapYokingGroupComboBox->getWidget()->setFixedWidth(m_mapYokingGroupComboBox->getWidget()->sizeHint().width() - 20); #endif // CARET_OS_MACOSX QObject::connect(m_mapYokingGroupComboBox, SIGNAL(itemActivated()), this, SLOT(yokingGroupActivated())); /* * Use layout group so that items can be shown/hidden */ this->gridLayoutGroup = new WuQGridLayoutGroup(gridLayout, this); if (orientation == Qt::Horizontal) { int row = this->gridLayoutGroup->rowCount(); this->gridLayoutGroup->addWidget(this->enabledCheckBox, row, 0, Qt::AlignHCenter); this->gridLayoutGroup->addWidget(settingsToolButton, row, 1, Qt::AlignHCenter); this->gridLayoutGroup->addWidget(colorBarToolButton, row, 2); this->gridLayoutGroup->addWidget(m_constructionToolButton, row, 3); this->gridLayoutGroup->addWidget(this->opacityDoubleSpinBox, row, 4); this->gridLayoutGroup->addWidget(this->fileComboBox, row, 5); this->gridLayoutGroup->addWidget(this->m_mapYokingGroupComboBox->getWidget(), row, 6, Qt::AlignHCenter); this->gridLayoutGroup->addWidget(m_mapIndexSpinBox, row, 7); this->gridLayoutGroup->addWidget(this->mapNameComboBox, row, 8); } else { QFrame* bottomHorizontalLineWidget = new QFrame(); bottomHorizontalLineWidget->setLineWidth(0); bottomHorizontalLineWidget->setMidLineWidth(1); bottomHorizontalLineWidget->setFrameStyle(QFrame::HLine | QFrame::Raised); QLabel* fileLabel = new QLabel("File"); QLabel* mapLabel = new QLabel("Map"); int row = this->gridLayoutGroup->rowCount(); this->gridLayoutGroup->addWidget(this->enabledCheckBox, row, 0); this->gridLayoutGroup->addWidget(settingsToolButton, row, 1); this->gridLayoutGroup->addWidget(colorBarToolButton, row, 2); this->gridLayoutGroup->addWidget(m_constructionToolButton, row, 3); this->gridLayoutGroup->addWidget(fileLabel, row, 4); this->gridLayoutGroup->addWidget(this->fileComboBox, row, 5, 1, 2); row++; this->gridLayoutGroup->addWidget(this->opacityDoubleSpinBox, row, 0, 1, 2, Qt::AlignCenter); this->gridLayoutGroup->addWidget(this->m_mapYokingGroupComboBox->getWidget(), row, 2, 1, 2); this->gridLayoutGroup->addWidget(mapLabel, row, 4); this->gridLayoutGroup->addWidget(m_mapIndexSpinBox, row, 5); this->gridLayoutGroup->addWidget(this->mapNameComboBox, row, 6); row++; this->gridLayoutGroup->addWidget(bottomHorizontalLineWidget, row, 0, 1, -1); } } /** * Destructor. */ OverlayViewController::~OverlayViewController() { } /** * Set the visiblity of this overlay view controller. */ void OverlayViewController::setVisible(bool visible) { this->gridLayoutGroup->setVisible(visible); } /* * If this overlay ins an overlay settings editor, update its content */ void OverlayViewController::updateOverlaySettingsEditor() { if (overlay == NULL) { return; } CaretMappableDataFile* mapFile = NULL; int32_t mapIndex = -1; overlay->getSelectionData(mapFile, mapIndex); if ((mapFile != NULL) && (mapIndex >= 0)) { EventOverlaySettingsEditorDialogRequest pcme(EventOverlaySettingsEditorDialogRequest::MODE_OVERLAY_MAP_CHANGED, this->browserWindowIndex, this->overlay, mapFile, mapIndex); EventManager::get()->sendEvent(pcme.getPointer()); } } /** * Called when a selection is made from the file combo box. * @parm indx * Index of selection. */ void OverlayViewController::fileComboBoxSelected(int indx) { if (overlay == NULL) { return; } void* pointer = this->fileComboBox->itemData(indx).value(); CaretMappableDataFile* file = (CaretMappableDataFile*)pointer; overlay->setSelectionData(file, 0); validateYokingSelection(); //validateYokingSelection(overlay->getYokingGroup()); // not needed with call to validateYokingSelection: this->updateViewController(this->overlay); // called inside validateYokingSelection(); this->updateUserInterfaceAndGraphicsWindow(); updateOverlaySettingsEditor(); updateViewController(this->overlay); updateGraphicsWindow(); } /** * Called when a selection is made from the map index spin box. * @parm indx * Index of selection. */ void OverlayViewController::mapIndexSpinBoxValueChanged(int indx) { /* * Get the file that is selected from the file combo box */ const int32_t fileIndex = this->fileComboBox->currentIndex(); void* pointer = this->fileComboBox->itemData(fileIndex).value(); CaretMappableDataFile* file = (CaretMappableDataFile*)pointer; /* * Overlay indices range [0, N-1] but spin box shows [1, N]. */ const int overlayIndex = indx - 1; overlay->setSelectionData(file, overlayIndex); const MapYokingGroupEnum::Enum mapYoking = overlay->getMapYokingGroup(); if (mapYoking != MapYokingGroupEnum::MAP_YOKING_GROUP_OFF) { EventMapYokingSelectMap selectMapEvent(mapYoking, file, overlayIndex, overlay->isEnabled()); EventManager::get()->sendEvent(selectMapEvent.getPointer()); } /* * Need to update map name combo box. */ mapNameComboBox->blockSignals(true); if ((overlayIndex >= 0) && (overlayIndex < mapNameComboBox->count())) { mapNameComboBox->setCurrentIndex(overlayIndex); } mapNameComboBox->blockSignals(false); this->updateUserInterface(); this->updateGraphicsWindow(); updateOverlaySettingsEditor(); } /** * Called when a selection is made from the map name combo box. * @parm indx * Index of selection. */ void OverlayViewController::mapNameComboBoxSelected(int indx) { if (overlay == NULL) { return; } /* * Get the file that is selected from the file combo box */ const int32_t fileIndex = this->fileComboBox->currentIndex(); void* pointer = this->fileComboBox->itemData(fileIndex).value(); CaretMappableDataFile* file = (CaretMappableDataFile*)pointer; overlay->setSelectionData(file, indx); const MapYokingGroupEnum::Enum mapYoking = overlay->getMapYokingGroup(); if (mapYoking != MapYokingGroupEnum::MAP_YOKING_GROUP_OFF) { EventMapYokingSelectMap selectMapEvent(mapYoking, file, indx, overlay->isEnabled()); EventManager::get()->sendEvent(selectMapEvent.getPointer()); } /* * Need to update map index spin box. * Note that the map index spin box ranges [1, N]. */ m_mapIndexSpinBox->blockSignals(true); m_mapIndexSpinBox->setValue(indx + 1); m_mapIndexSpinBox->blockSignals(false); this->updateUserInterface(); this->updateGraphicsWindow(); updateOverlaySettingsEditor(); } /** * Called when enabled checkbox state is changed * @parm checked * Checked status */ void OverlayViewController::enabledCheckBoxClicked(bool checked) { if (overlay == NULL) { return; } overlay->setEnabled(checked); const MapYokingGroupEnum::Enum mapYoking = overlay->getMapYokingGroup(); if (mapYoking != MapYokingGroupEnum::MAP_YOKING_GROUP_OFF) { CaretMappableDataFile* myFile = NULL; int32_t myIndex = -1; this->overlay->getSelectionData(myFile, myIndex); EventMapYokingSelectMap selectMapEvent(mapYoking, myFile, myIndex, overlay->isEnabled()); EventManager::get()->sendEvent(selectMapEvent.getPointer()); } this->updateUserInterface(); this->updateGraphicsWindow(); } /** * Called when colorbar toolbutton is toggled. * @param status * New status. */ void OverlayViewController::colorBarActionTriggered(bool status) { if (overlay == NULL) { return; } this->overlay->setPaletteDisplayEnabled(status); this->updateGraphicsWindow(); } /** * Called when opacity value is changed. * @param value * New value. */ void OverlayViewController::opacityDoubleSpinBoxValueChanged(double value) { if (overlay == NULL) { return; } this->overlay->setOpacity(value); this->updateGraphicsWindow(); } /** * Validate yoking when there are changes made to the overlay. */ void OverlayViewController::validateYokingSelection() { m_mapYokingGroupComboBox->validateYokingChange(this->overlay); updateViewController(this->overlay); updateGraphicsWindow(); //EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } /** * Called when the yoking group is changed. */ void OverlayViewController::yokingGroupActivated() { MapYokingGroupEnum::Enum yokingGroup = m_mapYokingGroupComboBox->getMapYokingGroup(); /* * Has yoking group changed? */ if (yokingGroup != overlay->getMapYokingGroup()) { validateYokingSelection(); } } /** * Called when the settings action is selected. */ void OverlayViewController::settingsActionTriggered() { if (overlay == NULL) { return; } CaretMappableDataFile* mapFile; int32_t mapIndex = -1; this->overlay->getSelectionData(mapFile, mapIndex); if (mapFile != NULL) { EventOverlaySettingsEditorDialogRequest pcme(EventOverlaySettingsEditorDialogRequest::MODE_SHOW_EDITOR, this->browserWindowIndex, this->overlay, mapFile, mapIndex); EventManager::get()->sendEvent(pcme.getPointer()); } } /** * Update this view controller using the given overlay. * @param overlay * Overlay that is used in this view controller. */ void OverlayViewController::updateViewController(Overlay* overlay) { this->overlay = overlay; this->fileComboBox->clear(); /* * Get the selection information for the overlay. */ std::vector dataFiles; CaretMappableDataFile* selectedFile = NULL; //AString selectedMapUniqueID = ""; int32_t selectedMapIndex = -1; if (this->overlay != NULL) { this->overlay->getSelectionData(dataFiles, selectedFile, //selectedMapUniqueID, selectedMapIndex); } std::vector displayNames; FilePathNamePrefixCompactor::removeMatchingPathPrefixFromCaretDataFiles(dataFiles, displayNames); CaretAssert(dataFiles.size() == displayNames.size()); /* * Load the file selection combo box. */ int32_t selectedFileIndex = -1; const int32_t numFiles = static_cast(dataFiles.size()); for (int32_t i = 0; i < numFiles; i++) { CaretMappableDataFile* dataFile = dataFiles[i]; AString dataTypeName = DataFileTypeEnum::toOverlayTypeName(dataFile->getDataFileType()); CaretAssertVectorIndex(displayNames, i); this->fileComboBox->addItem(displayNames[i], qVariantFromValue((void*)dataFile)); if (dataFile == selectedFile) { selectedFileIndex = i; } } if (selectedFileIndex >= 0) { this->fileComboBox->setCurrentIndex(selectedFileIndex); } /* * Load the map selection combo box */ int32_t numberOfMaps = 0; this->mapNameComboBox->blockSignals(true); this->mapNameComboBox->clear(); if (selectedFile != NULL) { numberOfMaps = selectedFile->getNumberOfMaps(); for (int32_t i = 0; i < numberOfMaps; i++) { this->mapNameComboBox->addItem(selectedFile->getMapName(i)); } this->mapNameComboBox->setCurrentIndex(selectedMapIndex); } this->mapNameComboBox->blockSignals(false); /* * Load the map index spin box that ranges [1, N]. */ m_mapIndexSpinBox->blockSignals(true); m_mapIndexSpinBox->setRange(1, numberOfMaps); if (selectedFile != NULL) { m_mapIndexSpinBox->setValue(selectedMapIndex + 1); } m_mapIndexSpinBox->blockSignals(false); /* * Update enable check box */ Qt::CheckState checkState = Qt::Unchecked; if (this->overlay != NULL) { if (this->overlay->isEnabled()) { checkState = Qt::Checked; } } this->enabledCheckBox->setCheckState(checkState); m_mapYokingGroupComboBox->setMapYokingGroup(overlay->getMapYokingGroup()); this->colorBarAction->blockSignals(true); this->colorBarAction->setChecked(overlay->isPaletteDisplayEnabled()); this->colorBarAction->blockSignals(false); this->opacityDoubleSpinBox->blockSignals(true); this->opacityDoubleSpinBox->setValue(overlay->getOpacity()); this->opacityDoubleSpinBox->blockSignals(false); const bool haveFile = (selectedFile != NULL); bool haveMultipleMaps = false; bool dataIsMappedWithPalette = false; bool dataIsMappedWithLabelTable = false; bool haveOpacity = false; if (haveFile) { dataIsMappedWithPalette = selectedFile->isMappedWithPalette(); dataIsMappedWithLabelTable = selectedFile->isMappedWithLabelTable(); haveMultipleMaps = (selectedFile->getNumberOfMaps() > 1); haveOpacity = (dataIsMappedWithLabelTable || dataIsMappedWithPalette); } /** * Yoking is enabled when either: * (1) The file maps to both surface and volumes * (2) The file has multiple maps. */ bool haveYoking = false; if (haveFile) { if (selectedFile->isSurfaceMappable() && selectedFile->isVolumeMappable()) { haveYoking = true; } if (haveMultipleMaps) { haveYoking = true; } } /* * Update tooltips with full path to file and name of map * as names may be too long to fit into combo boxes */ AString fileComboBoxToolTip("Select file for this overlay"); AString nameComboBoxToolTip("Select map by its name"); if (selectedFile != NULL) { FileInformation fileInfo(selectedFile->getFileName()); fileComboBoxToolTip.append(":\n" + fileInfo.getFileName() + "\n" + fileInfo.getPathName()); nameComboBoxToolTip.append(":\n" + this->mapNameComboBox->currentText()); } this->fileComboBox->setToolTip(fileComboBoxToolTip); this->mapNameComboBox->setToolTip(nameComboBoxToolTip); /* * Make sure items are enabled at the appropriate time */ this->fileComboBox->setEnabled(haveFile); this->mapNameComboBox->setEnabled(haveFile); this->m_mapIndexSpinBox->setEnabled(haveMultipleMaps); this->enabledCheckBox->setEnabled(haveFile); this->constructionAction->setEnabled(true); this->opacityDoubleSpinBox->setEnabled(haveOpacity); this->m_mapYokingGroupComboBox->getWidget()->setEnabled(haveYoking); this->colorBarAction->setEnabled(dataIsMappedWithPalette); this->settingsAction->setEnabled(true); } /** * Update graphics and GUI after selections made */ void OverlayViewController::updateUserInterfaceAndGraphicsWindow() { updateUserInterface(); updateGraphicsWindow(); } /** * Update graphics and GUI after selections made */ void OverlayViewController::updateUserInterface() { if (this->overlay->getMapYokingGroup() != MapYokingGroupEnum::MAP_YOKING_GROUP_OFF) { EventManager::get()->sendEvent(EventUserInterfaceUpdate().getPointer()); } else { EventManager::get()->sendEvent(EventUserInterfaceUpdate().setWindowIndex(this->browserWindowIndex).getPointer()); } } /** * Update graphics after selections made */ void OverlayViewController::updateGraphicsWindow() { EventManager::get()->sendEvent(EventSurfaceColoringInvalidate().getPointer()); if (this->overlay->getMapYokingGroup() != MapYokingGroupEnum::MAP_YOKING_GROUP_OFF) { EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } else { EventManager::get()->sendEvent(EventGraphicsUpdateOneWindow(this->browserWindowIndex).getPointer()); } } /** * Create the construction menu. * @param parent * Parent widget. */ QMenu* OverlayViewController::createConstructionMenu(QWidget* parent) { QMenu* menu = new QMenu(parent); QObject::connect(menu, SIGNAL(aboutToShow()), this, SLOT(menuConstructionAboutToShow())); menu->addAction("Add Overlay Above", this, SLOT(menuAddOverlayAboveTriggered())); menu->addAction("Add Overlay Below", this, SLOT(menuAddOverlayBelowTriggered())); menu->addSeparator(); menu->addAction("Move Overlay Up", this, SLOT(menuMoveOverlayUpTriggered())); menu->addAction("Move Overlay Down", this, SLOT(menuMoveOverlayDownTriggered())); menu->addSeparator(); menu->addAction("Remove This Overlay", this, SLOT(menuRemoveOverlayTriggered())); menu->addSeparator(); m_constructionReloadFileAction = menu->addAction("Reload Selected File", this, SLOT(menuReloadFileTriggered())); menu->addSeparator(); menu->addAction("Copy Path and File Name to Clipboard", this, SLOT(menuCopyFileNameToClipBoard())); menu->addAction("Copy Map Name to Clipboard", this, SLOT(menuCopyMapNameToClipBoard())); return menu; } /** * Called when construction menu is about to be displayed. */ void OverlayViewController::menuConstructionAboutToShow() { if (this->overlay != NULL) { CaretMappableDataFile* caretDataFile = NULL; int32_t mapIndex = -1; this->overlay->getSelectionData(caretDataFile, mapIndex); QString menuText = "Reload Selected File"; if (caretDataFile != NULL) { if (caretDataFile->isModified()) { QString suffix = " (MODIFIED)"; if (caretDataFile->isModifiedPaletteColorMapping()) { if ( ! caretDataFile->isModifiedExcludingPaletteColorMapping()) { suffix = " (MODIFIED PALETTE)"; } } menuText += suffix; } } m_constructionReloadFileAction->setText(menuText); } } /** * Add an overlay above this overlay. */ void OverlayViewController::menuAddOverlayAboveTriggered() { emit requestAddOverlayAbove(m_overlayIndex); } /** * Add an overlay below this overlay. */ void OverlayViewController::menuAddOverlayBelowTriggered() { emit requestAddOverlayBelow(m_overlayIndex); } /** * Remove this overlay. */ void OverlayViewController::menuRemoveOverlayTriggered() { emit requestRemoveOverlay(m_overlayIndex); } /** * Move this overlay down. */ void OverlayViewController::menuMoveOverlayDownTriggered() { emit requestMoveOverlayDown(m_overlayIndex); } /** * Move this overlay down. */ void OverlayViewController::menuMoveOverlayUpTriggered() { emit requestMoveOverlayUp(m_overlayIndex); } /** * Copy the file name to the clip board. */ void OverlayViewController::menuCopyFileNameToClipBoard() { if (this->overlay != NULL) { CaretMappableDataFile* caretDataFile = NULL; int32_t mapIndex = -1; this->overlay->getSelectionData(caretDataFile, mapIndex); if (caretDataFile != NULL) { QApplication::clipboard()->setText(caretDataFile->getFileName().trimmed(), QClipboard::Clipboard); } } } /** * Copy the map name to the clip board. */ void OverlayViewController::menuCopyMapNameToClipBoard() { if (this->overlay != NULL) { CaretMappableDataFile* caretDataFile = NULL; int32_t mapIndex = -1; this->overlay->getSelectionData(caretDataFile, mapIndex); if (caretDataFile != NULL) { if ((mapIndex >= 0) && (mapIndex < caretDataFile->getNumberOfMaps())) { QApplication::clipboard()->setText(caretDataFile->getMapName(mapIndex).trimmed(), QClipboard::Clipboard); } } } } /** * Reload the file in the overlay. */ void OverlayViewController::menuReloadFileTriggered() { if (this->overlay != NULL) { CaretMappableDataFile* caretDataFile = NULL; int32_t mapIndex = -1; this->overlay->getSelectionData(caretDataFile, mapIndex); if (caretDataFile != NULL) { AString username; AString password; if (DataFile::isFileOnNetwork(caretDataFile->getFileName())) { const QString msg("This file is on the network. " "If accessing the file requires a username and " "password, enter it here. Otherwise, remove any " "text from the username and password fields."); if (UsernamePasswordWidget::getUserNameAndPasswordInDialog(m_constructionToolButton, "Username and Password", msg, username, password)) { /* nothing */ } else { return; } } EventDataFileReload reloadEvent(GuiManager::get()->getBrain(), caretDataFile); reloadEvent.setUsernameAndPassword(username, password); EventManager::get()->sendEvent(reloadEvent.getPointer()); if (reloadEvent.isError()) { WuQMessageBox::errorOk(m_constructionToolButton, reloadEvent.getErrorMessage()); } updateOverlaySettingsEditor(); updateUserInterfaceAndGraphicsWindow(); } } } workbench-1.1.1/src/GuiQt/OverlayViewController.h000066400000000000000000000106451255417355300220250ustar00rootroot00000000000000#ifndef __OVERLAY_VIEW_CONTROLLER__H_ #define __OVERLAY_VIEW_CONTROLLER__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include class QAction; class QCheckBox; class QComboBox; class QDoubleSpinBox; class QGridLayout; class QMenu; class QSpinBox; class QToolButton; namespace caret { class MapYokingGroupComboBox; class Overlay; class WuQGridLayoutGroup; class OverlayViewController : public QObject { Q_OBJECT public: OverlayViewController(const Qt::Orientation orientation, QGridLayout* gridLayout, const int32_t browserWindowIndex, const int32_t overlayIndex, QObject* parent); virtual ~OverlayViewController(); void setVisible(bool visible); void updateViewController(Overlay* overlay); signals: void requestAddOverlayAbove(const int32_t overlayIndex); void requestAddOverlayBelow(const int32_t overlayIndex); void requestRemoveOverlay(const int32_t overlayIndex); void requestMoveOverlayUp(const int32_t overlayIndex); void requestMoveOverlayDown(const int32_t overlayIndex); private slots: void fileComboBoxSelected(int); void mapNameComboBoxSelected(int); void mapIndexSpinBoxValueChanged(int); void enabledCheckBoxClicked(bool); void colorBarActionTriggered(bool); void settingsActionTriggered(); void opacityDoubleSpinBoxValueChanged(double value); void yokingGroupActivated(); void menuAddOverlayAboveTriggered(); void menuAddOverlayBelowTriggered(); void menuRemoveOverlayTriggered(); void menuMoveOverlayDownTriggered(); void menuMoveOverlayUpTriggered(); void menuReloadFileTriggered(); void menuCopyFileNameToClipBoard(); void menuCopyMapNameToClipBoard(); void menuConstructionAboutToShow(); private: OverlayViewController(const OverlayViewController&); OverlayViewController& operator=(const OverlayViewController&); void updateUserInterfaceAndGraphicsWindow(); void updateUserInterface(); void updateGraphicsWindow(); QMenu* createConstructionMenu(QWidget* parent); void validateYokingSelection(); void updateOverlaySettingsEditor(); const int32_t browserWindowIndex; const int32_t m_overlayIndex; Overlay* overlay; QCheckBox* enabledCheckBox; QComboBox* fileComboBox; QComboBox* mapNameComboBox; QSpinBox* m_mapIndexSpinBox; QDoubleSpinBox* opacityDoubleSpinBox; QToolButton* m_constructionToolButton; QAction* constructionAction; QAction* colorBarAction; QAction* settingsAction; MapYokingGroupComboBox* m_mapYokingGroupComboBox; QAction* m_constructionReloadFileAction; WuQGridLayoutGroup* gridLayoutGroup; friend class OverlaySetViewController; }; #ifdef __OVERLAY_VIEW_CONTROLLER_DECLARE__ // #endif // __OVERLAY_VIEW_CONTROLLER_DECLARE__ } // namespace #endif //__OVERLAY_VIEW_CONTROLLER__H_ workbench-1.1.1/src/GuiQt/PaletteColorMappingEditorDialog.cxx000066400000000000000000000130201255417355300242460ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #define __PALETTE_COLOR_MAPPING_EDITOR_DIALOG_DECLARE__ #include "PaletteColorMappingEditorDialog.h" #undef __PALETTE_COLOR_MAPPING_EDITOR_DIALOG_DECLARE__ #include "CaretAssert.h" #include "CaretMappableDataFile.h" #include "EventCaretMappableDataFilesGet.h" #include "EventManager.h" #include "MapSettingsPaletteColorMappingWidget.h" using namespace caret; /** * \class caret::PaletteColorMappingEditorDialog * \brief Dialog for editing color palettes * \ingroup GuiQt */ /** * Constructor. */ PaletteColorMappingEditorDialog::PaletteColorMappingEditorDialog(QWidget* parent) : WuQDialogNonModal("Palette Color Mapping Editor", parent) { QLabel* fileLabel = new QLabel("Map File: "); m_fileNameValueLabel = new QLabel(""); QLabel* mapLabel = new QLabel("Map Name: "); m_mapNameValueLabel = new QLabel(""); m_paletteColorMappingEditor = new MapSettingsPaletteColorMappingWidget(this); QGridLayout* namesLayout = new QGridLayout(); namesLayout->setColumnStretch(0, 0); namesLayout->setColumnStretch(1, 100); int namesRow = 0; namesLayout->addWidget(fileLabel, namesRow, 0); namesLayout->addWidget(m_fileNameValueLabel, namesRow, 1, Qt::AlignLeft); namesRow++; namesLayout->addWidget(mapLabel, namesRow, 0); namesLayout->addWidget(m_mapNameValueLabel, namesRow, 1, Qt::AlignLeft); /* * For now, hide map name labels */ mapLabel->hide(); m_mapNameValueLabel->hide(); QWidget* w = new QWidget(); QVBoxLayout* layout = new QVBoxLayout(w); layout->addLayout(namesLayout); layout->addWidget(m_paletteColorMappingEditor); setCentralWidget(w, WuQDialog::SCROLL_AREA_NEVER); disableAutoDefaultForAllPushButtons(); EventManager::get()->addProcessedEventListener(this, EventTypeEnum::EVENT_DATA_FILE_DELETE); } /** * Destructor. */ PaletteColorMappingEditorDialog::~PaletteColorMappingEditorDialog() { EventManager::get()->removeAllEventsFromListener(this); } /** * Receive an event. * * @param event * An event for which this instance is listening. */ void PaletteColorMappingEditorDialog::receiveEvent(Event* event) { if (event->getEventType() == EventTypeEnum::EVENT_DATA_FILE_DELETE) { updateDialog(); } } /** * May be called to update the dialog's content. * * @param mapFile * Map file whose map is having its palette color mapping edited * @param mapIndex * Index of the map whose color mapping is being edited. */ void PaletteColorMappingEditorDialog::updateDialogContent(CaretMappableDataFile* mapFile, const int32_t mapIndex) { m_mapFile = mapFile; m_mapIndex = mapIndex; bool enableEditor = false; if (m_mapFile != NULL) { if ((m_mapIndex >= 0) && (m_mapIndex < m_mapFile->getNumberOfMaps())) { enableEditor = true; } } if (enableEditor) { m_fileNameValueLabel->setText(m_mapFile->getFileNameNoPath()); m_mapNameValueLabel->setText(m_mapFile->getMapName(m_mapIndex)); m_paletteColorMappingEditor->setEnabled(true); m_paletteColorMappingEditor->updateEditor(m_mapFile, m_mapIndex); } else { m_fileNameValueLabel->setText(""); m_mapNameValueLabel->setText(""); m_paletteColorMappingEditor->setEnabled(false); } } /** * May be called to update the dialog. */ void PaletteColorMappingEditorDialog::updateDialog() { if (m_mapFile != NULL) { /* * Validate map file to prevent crash */ EventCaretMappableDataFilesGet getMapFilesEvent; EventManager::get()->sendEvent(getMapFilesEvent.getPointer()); std::vector allMapFiles; getMapFilesEvent.getAllFiles(allMapFiles); if (std::find(allMapFiles.begin(), allMapFiles.end(), m_mapFile) != allMapFiles.end()) { if (m_mapIndex < 0) { m_mapIndex = 0; } if (m_mapFile->getNumberOfMaps() > 0) { if (m_mapIndex >= m_mapFile->getNumberOfMaps()) { m_mapIndex = m_mapFile->getNumberOfMaps() - 1; } } else { m_mapFile = NULL; m_mapIndex = -1; } } else { m_mapFile = NULL; m_mapIndex = -1; } updateDialogContent(m_mapFile, m_mapIndex); } if (m_mapFile == NULL) { close(); } } workbench-1.1.1/src/GuiQt/PaletteColorMappingEditorDialog.h000066400000000000000000000046721255417355300237100ustar00rootroot00000000000000#ifndef __PALETTE_COLOR_MAPPING_EDITOR_DIALOG_H__ #define __PALETTE_COLOR_MAPPING_EDITOR_DIALOG_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "EventListenerInterface.h" #include "WuQDialogNonModal.h" class QLabel; namespace caret { class CaretMappableDataFile; class MapSettingsPaletteColorMappingWidget; class PaletteColorMappingEditorDialog : public WuQDialogNonModal, public EventListenerInterface { Q_OBJECT public: PaletteColorMappingEditorDialog(QWidget* parent = 0); virtual ~PaletteColorMappingEditorDialog(); virtual void receiveEvent(Event* event); /** May be called requesting the dialog to update its content */ virtual void updateDialog(); void updateDialogContent(CaretMappableDataFile* mapFile, const int32_t mapIndex); protected: // ADD_NEW_METHODS_HERE private: PaletteColorMappingEditorDialog(const PaletteColorMappingEditorDialog&); PaletteColorMappingEditorDialog& operator=(const PaletteColorMappingEditorDialog&); CaretMappableDataFile* m_mapFile; int32_t m_mapIndex; MapSettingsPaletteColorMappingWidget* m_paletteColorMappingEditor; QLabel* m_fileNameValueLabel; QLabel* m_mapNameValueLabel; // ADD_NEW_MEMBERS_HERE }; #ifdef __PALETTE_COLOR_MAPPING_EDITOR_DIALOG_DECLARE__ // #endif // __PALETTE_COLOR_MAPPING_EDITOR_DIALOG_DECLARE__ } // namespace #endif //__PALETTE_COLOR_MAPPING_EDITOR_DIALOG_H__ workbench-1.1.1/src/GuiQt/PlotMagnifier.cxx000066400000000000000000000111741255417355300206160ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ /** * \class caret::PlotMagnifier * \brief Helper class for magnifying Qwt plots * \ingroup GuiQt */ #include "PlotMagnifier.h" using namespace caret; PlotMagnifier::PlotMagnifier(QwtPlotCanvas * canvas) : QwtPlotMagnifier(canvas) { this->setEnabled(true); //The three function calls below reverse the default behavior of zooming so that it is consistent with //brainview's zooming this->setMouseFactor(1.05);//inverse of default value, 0.95 this->setWheelFactor(1.11);//inverse of default value, 0.9 this->setMouseButton(Qt::LeftButton); } bool PlotMagnifier::eventFilter(QObject * object,QEvent *event) { if ( object && (object == parent() || object == parent()->parent()) ) { switch ( event->type() ) { case QEvent::MouseButtonPress: { widgetMousePressEvent( ( QMouseEvent * )event ); break; } case QEvent::MouseMove: { widgetMouseMoveEvent( ( QMouseEvent * )event ); break; } case QEvent::MouseButtonRelease: { widgetMouseReleaseEvent( ( QMouseEvent * )event ); break; } case QEvent::Wheel: { widgetWheelEvent( ( QWheelEvent * )event ); break; } case QEvent::KeyPress: { widgetKeyPressEvent( ( QKeyEvent * )event ); break; } case QEvent::KeyRelease: { widgetKeyReleaseEvent( ( QKeyEvent * )event ); break; } default:; } } return QObject::eventFilter( object, event ); } /*! Handle a mouse press event for the observed widget. \param mouseEvent Mouse event \sa eventFilter(), widgetMouseReleaseEvent(), widgetMouseMoveEvent() */ void PlotMagnifier::widgetMousePressEvent( QMouseEvent *mouseEvent ) { if(mouseEvent->modifiers() != Qt::ControlModifier || mouseEvent->modifiers() == Qt::ShiftModifier) { return; } mouseEvent->setModifiers(Qt::NoModifier); // remove modifier so base class accepts event, we ignore events when shift key is pressed so panning works QwtPlotMagnifier::widgetMousePressEvent( mouseEvent); } /*! Handle a mouse release event for the observed widget. \param mouseEvent Mouse event \sa eventFilter(), widgetMousePressEvent(), widgetMouseMoveEvent(), */ void PlotMagnifier::widgetMouseReleaseEvent( QMouseEvent *mouseEvent ) { /*if(mouseEvent->modifiers() != Qt::ControlModifier || mouseEvent->modifiers() == Qt::ShiftModifier) { return; }*/ mouseEvent->setModifiers(Qt::NoModifier); // remove modifier so base class accepts event, we ignore events when shift key is pressed so panning works QwtPlotMagnifier::widgetMouseReleaseEvent( mouseEvent); } /*! Handle a mouse move event for the observed widget. \param mouseEvent Mouse event \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent(), */ void PlotMagnifier::widgetMouseMoveEvent( QMouseEvent *mouseEvent ) { if(mouseEvent->modifiers() != Qt::ControlModifier || mouseEvent->modifiers() == Qt::ShiftModifier) { return; } mouseEvent->setModifiers(Qt::NoModifier);// remove modifier so base class accepts event, we ignore events when shift key is pressed so panning works QwtPlotMagnifier::widgetMouseMoveEvent( mouseEvent); } /*! Handle a wheel event for the observed widget. \param wheelEvent Wheel event \sa eventFilter() */ void PlotMagnifier::widgetWheelEvent( QWheelEvent *wheelEvent ) { // remove modifier so base class accepts event wheelEvent->setModifiers(Qt::NoModifier); QwtPlotMagnifier::widgetWheelEvent( wheelEvent); }workbench-1.1.1/src/GuiQt/PlotMagnifier.h000066400000000000000000000034341255417355300202430ustar00rootroot00000000000000#ifndef __PLOT_MAGNIFIER__ #define __PLOT_MAGNIFIER__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" //qwt includes #include #include "qlayout.h" #include "qwt_plot.h" #include "qwt_plot_marker.h" #include "qwt_plot_curve.h" #include "qwt_legend.h" #include "qwt_series_data.h" #include "qwt_plot_canvas.h" #include "qwt_plot_panner.h" #include "qwt_plot_magnifier.h" #include "qwt_text.h" #include "qwt_math.h" #include namespace caret { class PlotMagnifier : public QwtPlotMagnifier { Q_OBJECT public: PlotMagnifier(QwtPlotCanvas * canvas = NULL); protected: virtual bool eventFilter(QObject * object,QEvent *event); virtual void widgetWheelEvent( QWheelEvent *wheelEvent ); virtual void widgetMouseMoveEvent( QMouseEvent *mouseEvent ); virtual void widgetMousePressEvent( QMouseEvent *mouseEvent ); virtual void widgetMouseReleaseEvent( QMouseEvent *mouseEvent ); private: }; } #endif //__PLOT_MAGNIFIER__ workbench-1.1.1/src/GuiQt/PlotPanner.cxx000066400000000000000000000075711255417355300201460ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ /** * \class caret::PlotPanner * \brief Helper class for panning Qwt plots * \ingroup GuiQt */ #include "PlotPanner.h" using namespace caret; PlotPanner::PlotPanner(QwtPlotCanvas * canvas) : QwtPlotPanner(canvas) { this->setEnabled(true); this->setMouseButton(Qt::LeftButton); } bool PlotPanner::eventFilter(QObject * object,QEvent *event) { if ( object && (object == parent() || object == parent()->parent()) ) { switch ( event->type() ) { case QEvent::MouseButtonPress: { widgetMousePressEvent( ( QMouseEvent * )event ); break; } case QEvent::MouseMove: { widgetMouseMoveEvent( ( QMouseEvent * )event ); break; } case QEvent::MouseButtonRelease: { widgetMouseReleaseEvent( ( QMouseEvent * )event ); break; } case QEvent::KeyPress: { widgetKeyPressEvent( ( QKeyEvent * )event ); break; } case QEvent::KeyRelease: { widgetKeyReleaseEvent( ( QKeyEvent * )event ); break; } default:; } } return QObject::eventFilter( object, event ); } /*! Handle a mouse press event for the observed widget. \param mouseEvent Mouse event \sa eventFilter(), widgetMouseReleaseEvent(), widgetMouseMoveEvent() */ void PlotPanner::widgetMousePressEvent( QMouseEvent *mouseEvent ) { if(mouseEvent->modifiers() != Qt::ShiftModifier || mouseEvent->modifiers() == Qt::ControlModifier) { return; } mouseEvent->setModifiers(Qt::NoModifier); // remove modifier so base class accepts event, we ignore events when shift key is pressed so panning works QwtPlotPanner::widgetMousePressEvent( mouseEvent); } /*! Handle a mouse release event for the observed widget. \param mouseEvent Mouse event \sa eventFilter(), widgetMousePressEvent(), widgetMouseMoveEvent(), */ void PlotPanner::widgetMouseReleaseEvent( QMouseEvent *mouseEvent ) { /*if(mouseEvent->modifiers() != Qt::ShiftModifier || mouseEvent->modifiers() == Qt::ControlModifier) { return; }*/ mouseEvent->setModifiers(Qt::NoModifier); // remove modifier so base class accepts event, we ignore events when shift key is pressed so panning works QwtPlotPanner::widgetMouseReleaseEvent( mouseEvent); } /*! Handle a mouse move event for the observed widget. \param mouseEvent Mouse event \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent(), */ void PlotPanner::widgetMouseMoveEvent( QMouseEvent *mouseEvent ) { if(mouseEvent->modifiers() != Qt::ShiftModifier || mouseEvent->modifiers() == Qt::ControlModifier) { return; } mouseEvent->setModifiers(Qt::NoModifier);// remove modifier so base class accepts event, we ignore events when shift key is pressed so panning works QwtPlotPanner::widgetMouseMoveEvent( mouseEvent); } workbench-1.1.1/src/GuiQt/PlotPanner.h000066400000000000000000000033141255417355300175620ustar00rootroot00000000000000#ifndef __PLOT_PANNER__ #define __PLOT_PANNER__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" //qwt includes #include #include "qlayout.h" #include "qwt_plot.h" #include "qwt_plot_marker.h" #include "qwt_plot_curve.h" #include "qwt_legend.h" #include "qwt_series_data.h" #include "qwt_plot_canvas.h" #include "qwt_plot_panner.h" #include "qwt_plot_magnifier.h" #include "qwt_text.h" #include "qwt_math.h" #include namespace caret { class PlotPanner : public QwtPlotPanner { Q_OBJECT public: PlotPanner(QwtPlotCanvas * canvas = NULL); protected: virtual bool eventFilter(QObject * object,QEvent *event); virtual void widgetMouseMoveEvent( QMouseEvent *mouseEvent ); virtual void widgetMousePressEvent( QMouseEvent *mouseEvent ); virtual void widgetMouseReleaseEvent( QMouseEvent *mouseEvent ); private: }; } #endif //__PLOT_PANNER__ workbench-1.1.1/src/GuiQt/PreferencesDialog.cxx000066400000000000000000001056701255417355300214440ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #include #include #include #include #include #include #define __PREFERENCES_DIALOG__H__DECLARE__ #include "PreferencesDialog.h" #undef __PREFERENCES_DIALOG__H__DECLARE__ #include "Brain.h" #include "CaretAssert.h" #include "CaretLogger.h" #include "CaretPreferences.h" #include "EnumComboBoxTemplate.h" #include "EventGraphicsUpdateAllWindows.h" #include "EventManager.h" #include "GuiManager.h" #include "ImageCaptureMethodEnum.h" #include "OpenGLDrawingMethodEnum.h" #include "SessionManager.h" #include "WuQtUtilities.h" #include "WuQFactory.h" #include "WuQMessageBox.h" #include "WuQTrueFalseComboBox.h" #include "WuQWidgetObjectGroup.h" using namespace caret; /** * \class caret::PreferencesDialog * \brief Dialog for display/editing of prefernces. * \ingroup GuiQt * * Presents controls for editing palettes used to color * scalar data. */ /** * Constructor for editing a palette selection. * * @param parent * Parent widget on which this dialog is displayed. */ PreferencesDialog::PreferencesDialog(QWidget* parent) : WuQDialogNonModal("Preferences", parent) { setDeleteWhenClosed(false); /* * No apply button */ setApplyButtonText(""); /* * Used to block signals in all widgets */ m_allWidgets = new WuQWidgetObjectGroup(this); /* * Create the tab widget and all tab content */ QTabWidget* tabWidget = new QTabWidget(); tabWidget->addTab(createColorsWidget(), "Colors"); tabWidget->addTab(createIdentificationSymbolWidget(), "ID"); tabWidget->addTab(createMiscellaneousWidget(), "Misc"); tabWidget->addTab(createOpenGLWidget(), "OpenGL"); tabWidget->addTab(createVolumeWidget(), "Volume"); setCentralWidget(tabWidget, WuQDialog::SCROLL_AREA_NEVER); disableAutoDefaultForAllPushButtons(); } /** * Destructor. */ PreferencesDialog::~PreferencesDialog() { } /** * Add a color button and swatch. * * @param gridLayout * Grid layout for widgets. * @param prefColor * Enumerated value for color. * @param colorSignalMapper * Signal mapper for buttons. */ void PreferencesDialog::addColorButtonAndSwatch(QGridLayout* gridLayout, const PREF_COLOR prefColor, QSignalMapper* colorSignalMapper) { QString buttonText; QWidget* colorSwatchWidget = new QWidget(); switch (prefColor) { case PREF_COLOR_BACKGROUND_ALL: buttonText = "All Background"; m_backgroundColorAllWidget = colorSwatchWidget; break; case PREF_COLOR_BACKGROUND_CHART: buttonText = "Chart Background"; m_backgroundColorChartWidget = colorSwatchWidget; break; case PREF_COLOR_BACKGROUND_SURFACE: buttonText = "Surface Background"; m_backgroundColorSurfaceWidget = colorSwatchWidget; break; case PREF_COLOR_BACKGROUND_VOLUME: buttonText = "Volume Background"; m_backgroundColorVolumeWidget = colorSwatchWidget; break; case PREF_COLOR_FOREGROUND_ALL: buttonText = "All Foreground"; m_foregroundColorAllWidget = colorSwatchWidget; break; case PREF_COLOR_FOREGROUND_CHART: buttonText = "Chart Foreground"; m_foregroundColorChartWidget = colorSwatchWidget; break; case PREF_COLOR_FOREGROUND_SURFACE: buttonText = "Surface Foreground"; m_foregroundColorSurfaceWidget = colorSwatchWidget; break; case PREF_COLOR_FOREGROUND_VOLUME: buttonText = "Volume Foreground"; m_foregroundColorVolumeWidget = colorSwatchWidget; break; case PREF_COLOR_CHART_MATRIX_GRID_LINES: buttonText = "Chart Grid Lines"; m_chartMatrixGridLinesColorWidget = colorSwatchWidget; break; case NUMBER_OF_PREF_COLORS: CaretAssert(0); break; } buttonText.append("..."); CaretAssert( ! buttonText.isEmpty()); QPushButton* colorPushButton = new QPushButton(buttonText); QObject::connect(colorPushButton, SIGNAL(clicked()), colorSignalMapper, SLOT(map())); colorSignalMapper->setMapping(colorPushButton, (int)prefColor); addWidgetsToLayout(gridLayout, colorPushButton, colorSwatchWidget); } /** * @return The colors widget. */ QWidget* PreferencesDialog::createColorsWidget() { QSignalMapper* colorSignalMapper = new QSignalMapper(this); QGridLayout* gridLayout = new QGridLayout(); addColorButtonAndSwatch(gridLayout, PREF_COLOR_FOREGROUND_ALL, colorSignalMapper); addColorButtonAndSwatch(gridLayout, PREF_COLOR_BACKGROUND_ALL, colorSignalMapper); addColorButtonAndSwatch(gridLayout, PREF_COLOR_FOREGROUND_CHART, colorSignalMapper); addColorButtonAndSwatch(gridLayout, PREF_COLOR_BACKGROUND_CHART, colorSignalMapper); addColorButtonAndSwatch(gridLayout, PREF_COLOR_CHART_MATRIX_GRID_LINES, colorSignalMapper); addColorButtonAndSwatch(gridLayout, PREF_COLOR_FOREGROUND_SURFACE, colorSignalMapper); addColorButtonAndSwatch(gridLayout, PREF_COLOR_BACKGROUND_SURFACE, colorSignalMapper); addColorButtonAndSwatch(gridLayout, PREF_COLOR_FOREGROUND_VOLUME, colorSignalMapper); addColorButtonAndSwatch(gridLayout, PREF_COLOR_BACKGROUND_VOLUME, colorSignalMapper); QObject::connect(colorSignalMapper, SIGNAL(mapped(int)), this, SLOT(colorPushButtonClicked(int))); m_allWidgets->add(colorSignalMapper); QWidget* widget = new QWidget(); QVBoxLayout* layout = new QVBoxLayout(widget); layout->addLayout(gridLayout); layout->addStretch(); return widget; } /** * Update the color widget's items. * * @param prefs * The Caret preferences. */ void PreferencesDialog::updateColorWidget(CaretPreferences* prefs) { for (int32_t i = 0; i < NUMBER_OF_PREF_COLORS; i++) { const PREF_COLOR prefColor = (PREF_COLOR)i; uint8_t rgb[3] = { 0, 0, 0 }; QWidget* colorSwatchWidget = NULL; switch (prefColor) { case PREF_COLOR_BACKGROUND_ALL: prefs->getColorBackgroundAllView(rgb); colorSwatchWidget = m_backgroundColorAllWidget; break; case PREF_COLOR_BACKGROUND_CHART: prefs->getColorBackgroundChartView(rgb); colorSwatchWidget = m_backgroundColorChartWidget; break; case PREF_COLOR_BACKGROUND_SURFACE: prefs->getColorBackgroundSurfaceView(rgb); colorSwatchWidget = m_backgroundColorSurfaceWidget; break; case PREF_COLOR_BACKGROUND_VOLUME: prefs->getColorBackgroundVolumeView(rgb); colorSwatchWidget = m_backgroundColorVolumeWidget; break; case PREF_COLOR_FOREGROUND_ALL: prefs->getColorForegroundAllView(rgb); colorSwatchWidget = m_foregroundColorAllWidget; break; case PREF_COLOR_FOREGROUND_CHART: prefs->getColorForegroundChartView(rgb); colorSwatchWidget = m_foregroundColorChartWidget; break; case PREF_COLOR_FOREGROUND_SURFACE: prefs->getColorForegroundSurfaceView(rgb); colorSwatchWidget = m_foregroundColorSurfaceWidget; break; case PREF_COLOR_FOREGROUND_VOLUME: prefs->getColorForegroundVolumeView(rgb); colorSwatchWidget = m_foregroundColorVolumeWidget; break; case PREF_COLOR_CHART_MATRIX_GRID_LINES: prefs->getColorChartMatrixGridLines(rgb); colorSwatchWidget = m_chartMatrixGridLinesColorWidget; break; case NUMBER_OF_PREF_COLORS: CaretAssert(0); break; } CaretAssert(colorSwatchWidget); colorSwatchWidget->setStyleSheet("background-color: rgb(" + AString::number(rgb[0]) + ", " + AString::number(rgb[1]) + ", " + AString::number(rgb[2]) + ");"); } } /** * @return The miscellaneous widget. */ QWidget* PreferencesDialog::createMiscellaneousWidget() { /* * Logging Level */ m_miscLoggingLevelComboBox = new QComboBox(); std::vector loggingLevels; LogLevelEnum::getAllEnums(loggingLevels); const int32_t numLogLevels = static_cast(loggingLevels.size()); for (int32_t i = 0; i < numLogLevels; i++) { const LogLevelEnum::Enum logLevel = loggingLevels[i]; m_miscLoggingLevelComboBox->addItem(LogLevelEnum::toGuiName(logLevel)); m_miscLoggingLevelComboBox->setItemData(i, LogLevelEnum::toIntegerCode(logLevel)); } QObject::connect(m_miscLoggingLevelComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(miscLoggingLevelComboBoxChanged(int))); m_allWidgets->add(m_miscLoggingLevelComboBox); /* * Splash Screen */ m_miscSplashScreenShowAtStartupComboBox = new WuQTrueFalseComboBox("On", "Off", this); QObject::connect(m_miscSplashScreenShowAtStartupComboBox, SIGNAL(statusChanged(bool)), this, SLOT(miscSplashScreenShowAtStartupComboBoxChanged(bool))); m_allWidgets->add(m_miscSplashScreenShowAtStartupComboBox); /* * Yoking */ m_yokingDefaultComboBox = new WuQTrueFalseComboBox("On", "Off", this); QObject::connect(m_yokingDefaultComboBox, SIGNAL(statusChanged(bool)), this, SLOT(yokingComboBoxToggled(bool))); m_allWidgets->add(m_yokingDefaultComboBox); /* * Developer Menu */ m_miscDevelopMenuEnabledComboBox = new WuQTrueFalseComboBox("On", "Off", this); QObject::connect(m_miscDevelopMenuEnabledComboBox, SIGNAL(statusChanged(bool)), this, SLOT(miscDevelopMenuEnabledComboBoxChanged(bool))); m_allWidgets->add(m_miscDevelopMenuEnabledComboBox); /* * Manage Files View Files Type */ m_miscSpecFileDialogViewFilesTypeEnumComboBox = new EnumComboBoxTemplate(this); m_miscSpecFileDialogViewFilesTypeEnumComboBox->setup(); QObject::connect(m_miscSpecFileDialogViewFilesTypeEnumComboBox, SIGNAL(itemActivated()), this, SLOT(miscSpecFileDialogViewFilesTypeEnumComboBoxItemActivated())); m_allWidgets->add(m_miscSpecFileDialogViewFilesTypeEnumComboBox->getWidget()); QGridLayout* gridLayout = new QGridLayout(); addWidgetToLayout(gridLayout, "Logging Level: ", m_miscLoggingLevelComboBox); addWidgetToLayout(gridLayout, "Save/Manage View Files: ", m_miscSpecFileDialogViewFilesTypeEnumComboBox->getWidget()); addWidgetToLayout(gridLayout, "New Tabs Yoked to Group A: ", m_yokingDefaultComboBox->getWidget()); addWidgetToLayout(gridLayout, "Show Develop Menu in Menu Bar: ", m_miscDevelopMenuEnabledComboBox->getWidget()); addWidgetToLayout(gridLayout, "Show Splash Screen at Startup: ", m_miscSplashScreenShowAtStartupComboBox->getWidget()); QWidget* widget = new QWidget(); QVBoxLayout* layout = new QVBoxLayout(widget); layout->addLayout(gridLayout); layout->addStretch(); return widget; } /** * Update the miscellaneous widget's items. * * @param prefs * The Caret preferences. */ void PreferencesDialog::updateMiscellaneousWidget(CaretPreferences* prefs) { const LogLevelEnum::Enum loggingLevel = prefs->getLoggingLevel(); int indx = m_miscLoggingLevelComboBox->findData(LogLevelEnum::toIntegerCode(loggingLevel)); if (indx >= 0) { m_miscLoggingLevelComboBox->setCurrentIndex(indx); } m_miscDevelopMenuEnabledComboBox->setStatus(prefs->isDevelopMenuEnabled()); m_miscSplashScreenShowAtStartupComboBox->setStatus(prefs->isSplashScreenEnabled()); m_yokingDefaultComboBox->setStatus(prefs->isYokingDefaultedOn()); m_miscSpecFileDialogViewFilesTypeEnumComboBox->setSelectedItem(prefs->getManageFilesViewFileType()); } /** * @return The identification symbol widget. */ QWidget* PreferencesDialog::createIdentificationSymbolWidget() { QLabel* surfaceLabel = new QLabel("Show Surface ID Symbols"); m_surfaceIdentificationSymbolComboBox = new WuQTrueFalseComboBox("On", "Off", this); QObject::connect(m_surfaceIdentificationSymbolComboBox, SIGNAL(statusChanged(bool)), this, SLOT(identificationSymbolToggled())); QLabel* volumeLabel = new QLabel("Show Volume ID Symbols"); m_volumeIdentificationSymbolComboBox = new WuQTrueFalseComboBox("On", "Off", this); QObject::connect(m_volumeIdentificationSymbolComboBox, SIGNAL(statusChanged(bool)), this, SLOT(identificationSymbolToggled())); QGridLayout* gridLayout = new QGridLayout(); gridLayout->addWidget(surfaceLabel, 0, 0); gridLayout->addWidget(m_surfaceIdentificationSymbolComboBox->getWidget(), 0, 1); gridLayout->addWidget(volumeLabel, 1, 0); gridLayout->addWidget(m_volumeIdentificationSymbolComboBox->getWidget(), 1, 1); QWidget* widget = new QWidget(); QVBoxLayout* layout = new QVBoxLayout(widget); layout->addLayout(gridLayout); layout->addStretch(); return widget; } /** * Update the identification widget. * * @param prefs * The Caret preferences. */ void PreferencesDialog::updateIdentificationWidget(CaretPreferences* prefs) { m_surfaceIdentificationSymbolComboBox->setStatus(prefs->isShowSurfaceIdentificationSymbols()); m_volumeIdentificationSymbolComboBox->setStatus(prefs->isShowVolumeIdentificationSymbols()); } /** * Gets called when an identification symbol check box is toggled. */ void PreferencesDialog::identificationSymbolToggled() { CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); prefs->setShowSurfaceIdentificationSymbols(m_surfaceIdentificationSymbolComboBox->isTrue()); prefs->setShowVolumeIdentificationSymbols(m_volumeIdentificationSymbolComboBox->isTrue()); } /** * @return The OpenGL widget. */ QWidget* PreferencesDialog::createOpenGLWidget() { /* * Image Capture Method */ m_openGLImageCaptureMethodEnumComboBox = new EnumComboBoxTemplate(this); m_openGLImageCaptureMethodEnumComboBox->setup(); QObject::connect(m_openGLImageCaptureMethodEnumComboBox, SIGNAL(itemActivated()), this, SLOT(openGLImageCaptureMethodEnumComboBoxItemActivated())); const AString captureMethodToolTip = ("Sometimes, the default image capture method fails to " "function correctly and the captured image does not match " "the content of the graphics window. If this occurs, " "try changing the Capture Method to Grab Frame Buffer."); WuQtUtilities::setWordWrappedToolTip(m_openGLImageCaptureMethodEnumComboBox->getComboBox(), captureMethodToolTip); m_allWidgets->add(m_openGLImageCaptureMethodEnumComboBox->getWidget()); /* * OpenGL Drawing Method */ m_openGLDrawingMethodEnumComboBox = new EnumComboBoxTemplate(this); m_openGLDrawingMethodEnumComboBox->setup(); QObject::connect(m_openGLDrawingMethodEnumComboBox, SIGNAL(itemActivated()), this, SLOT(openGLDrawingMethodEnumComboBoxItemActivated())); m_allWidgets->add(m_openGLDrawingMethodEnumComboBox->getWidget()); QGridLayout* gridLayout = new QGridLayout(); addWidgetToLayout(gridLayout, "Image Capture Method: ", m_openGLImageCaptureMethodEnumComboBox->getWidget()); QLabel* vertexBuffersLabel = addWidgetToLayout(gridLayout, "OpenGL Vertex Buffers: ", m_openGLDrawingMethodEnumComboBox->getWidget()); /* * HIDE THE VERTEX BUFFERS OPTION */ vertexBuffersLabel->setHidden(true); m_openGLDrawingMethodEnumComboBox->getWidget()->setHidden(true); QWidget* widget = new QWidget(); QVBoxLayout* layout = new QVBoxLayout(widget); layout->addLayout(gridLayout); layout->addStretch(); return widget; } /** * Update the OpenGL widget's items. * * @param prefs * The Caret preferences. */ void PreferencesDialog::updateOpenGLWidget(CaretPreferences* prefs) { const ImageCaptureMethodEnum::Enum captureMethod = prefs->getImageCaptureMethod(); m_openGLImageCaptureMethodEnumComboBox->setSelectedItem(captureMethod); const OpenGLDrawingMethodEnum::Enum drawingMethod = prefs->getOpenDrawingMethod(); m_openGLDrawingMethodEnumComboBox->setSelectedItem(drawingMethod); } /** * @return The volume widget. */ QWidget* PreferencesDialog::createVolumeWidget() { /* * Crosshairs On/Off */ m_volumeAxesCrosshairsComboBox = new WuQTrueFalseComboBox("On", "Off", this); QObject::connect(m_volumeAxesCrosshairsComboBox, SIGNAL(statusChanged(bool)), this, SLOT(volumeAxesCrosshairsComboBoxToggled(bool))); m_allWidgets->add(m_volumeAxesCrosshairsComboBox); /* * Axes Labels On/Off */ m_volumeAxesLabelsComboBox = new WuQTrueFalseComboBox("On", "Off", this); QObject::connect(m_volumeAxesLabelsComboBox, SIGNAL(statusChanged(bool)), this, SLOT(volumeAxesLabelsComboBoxToggled(bool))); m_allWidgets->add(m_volumeAxesLabelsComboBox); /* * Identification On/Off */ m_volumeIdentificationComboBox = new WuQTrueFalseComboBox("On", "Off", this); QObject::connect(m_volumeIdentificationComboBox, SIGNAL(statusChanged(bool)), this, SLOT(volumeIdentificationComboBoxToggled(bool))); m_allWidgets->add(m_volumeIdentificationComboBox); /* * Montage Coordinates On/Off */ m_volumeAxesMontageCoordinatesComboBox = new WuQTrueFalseComboBox("On", "Off", this); QObject::connect(m_volumeAxesMontageCoordinatesComboBox, SIGNAL(statusChanged(bool)), this, SLOT(volumeAxesMontageCoordinatesComboBoxToggled(bool))); m_allWidgets->add(m_volumeAxesMontageCoordinatesComboBox); /* * Montage Slice Gap */ m_volumeMontageGapSpinBox = WuQFactory::newSpinBoxWithMinMaxStepSignalInt(0, 100000, 1, this, SLOT(volumeMontageGapValueChanged(int))); m_allWidgets->add(m_volumeMontageGapSpinBox); /* * Montage Slice Coordinate Precision */ m_volumeMontageCoordinatePrecisionSpinBox = WuQFactory::newSpinBoxWithMinMaxStepSignalInt(0, 5, 1, this, SLOT(volumeMontageCoordinatePrecisionChanged(int))); m_allWidgets->add(m_volumeMontageCoordinatePrecisionSpinBox); m_allWidgets->add(m_volumeAxesCrosshairsComboBox); m_allWidgets->add(m_volumeAxesLabelsComboBox); m_allWidgets->add(m_volumeAxesMontageCoordinatesComboBox); m_allWidgets->add(m_volumeMontageGapSpinBox); m_allWidgets->add(m_volumeMontageCoordinatePrecisionSpinBox); QGridLayout* gridLayout = new QGridLayout(); addWidgetToLayout(gridLayout, "Volume Axes Crosshairs: ", m_volumeAxesCrosshairsComboBox->getWidget()); addWidgetToLayout(gridLayout, "Volume Axes Labels: ", m_volumeAxesLabelsComboBox->getWidget()); addWidgetToLayout(gridLayout, "Volume Identification For New Tabs: ", m_volumeIdentificationComboBox->getWidget()); addWidgetToLayout(gridLayout, "Volume Montage Slice Coord: ", m_volumeAxesMontageCoordinatesComboBox->getWidget()); addWidgetToLayout(gridLayout, "Volume Montage Gap: ", m_volumeMontageGapSpinBox); addWidgetToLayout(gridLayout, "Volume Montage Precision: ", m_volumeMontageCoordinatePrecisionSpinBox); QWidget* widget = new QWidget(); QVBoxLayout* layout = new QVBoxLayout(widget); layout->addLayout(gridLayout); layout->addStretch(); return widget; } /** * Update the Volume widget's items. * * @param prefs * The Caret preferences. */ void PreferencesDialog::updateVolumeWidget(CaretPreferences* prefs) { m_volumeAxesCrosshairsComboBox->setStatus(prefs->isVolumeAxesCrosshairsDisplayed()); m_volumeAxesLabelsComboBox->setStatus(prefs->isVolumeAxesLabelsDisplayed()); m_volumeAxesMontageCoordinatesComboBox->setStatus(prefs->isVolumeMontageAxesCoordinatesDisplayed()); m_volumeIdentificationComboBox->setStatus(prefs->isVolumeIdentificationDefaultedOn()); m_volumeMontageGapSpinBox->setValue(prefs->getVolumeMontageGap()); m_volumeMontageCoordinatePrecisionSpinBox->setValue(prefs->getVolumeMontageCoordinatePrecision()); } /** * Add a label in the left column and the widget in the right column. * * @param gridLayout * The grid layout to which the widgets are added. * @param labelText * Text for label. * @param widget * Widget for right column. * @return * The label that corresponds to the widget. */ QLabel* PreferencesDialog::addWidgetToLayout(QGridLayout* gridLayout, const QString& labelText, QWidget* widget) { QLabel* label = new QLabel(labelText); label->setAlignment(Qt::AlignRight); addWidgetsToLayout(gridLayout, label, widget); return label; } /** * Add widgets to the layout. If rightWidget is NULL, * leftItem spans both columns. * * @param leftWidget * Widget for left column. * @param rightWidget * Widget for right column. */ void PreferencesDialog::addWidgetsToLayout(QGridLayout* gridLayout, QWidget* leftWidget, QWidget* rightWidget) { int row = gridLayout->rowCount(); if (rightWidget != NULL) { gridLayout->addWidget(leftWidget, row, 0); gridLayout->addWidget(rightWidget, row, 1); } else { gridLayout->addWidget(leftWidget, row, 0, 1, 2, Qt::AlignLeft); } } /** * May be called to update the dialog's content. */ void PreferencesDialog::updateDialog() { m_allWidgets->blockAllSignals(true); CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); updateColorWidget(prefs); updateMiscellaneousWidget(prefs); updateIdentificationWidget(prefs); updateOpenGLWidget(prefs); updateVolumeWidget(prefs); m_allWidgets->blockAllSignals(false); } void PreferencesDialog::updateColorWithDialog(const PREF_COLOR prefColor) { CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); uint8_t rgb[3]; AString prefColorName; switch (prefColor) { case PREF_COLOR_BACKGROUND_ALL: prefs->getColorBackgroundAllView(rgb); prefColorName = "Background - All"; break; case PREF_COLOR_BACKGROUND_CHART: prefs->getColorBackgroundChartView(rgb); prefColorName = "Background - Chart"; break; case PREF_COLOR_BACKGROUND_SURFACE: prefs->getColorBackgroundSurfaceView(rgb); prefColorName = "Background - Surface"; break; case PREF_COLOR_BACKGROUND_VOLUME: prefs->getColorBackgroundVolumeView(rgb); prefColorName = "Background - Volume"; break; case PREF_COLOR_FOREGROUND_ALL: prefs->getColorForegroundAllView(rgb); prefColorName = "Foreground - All"; break; case PREF_COLOR_FOREGROUND_CHART: prefs->getColorForegroundChartView(rgb); prefColorName = "Foreground - Chart"; break; case PREF_COLOR_FOREGROUND_SURFACE: prefs->getColorForegroundSurfaceView(rgb); prefColorName = "Foreground - Surface"; break; case PREF_COLOR_FOREGROUND_VOLUME: prefs->getColorForegroundVolumeView(rgb); prefColorName = "Foreground - Volume"; break; case PREF_COLOR_CHART_MATRIX_GRID_LINES: prefs->getColorChartMatrixGridLines(rgb); prefColorName = "Chart Matrix Grid Lines"; break; case NUMBER_OF_PREF_COLORS: CaretAssert(0); break; } const QColor initialColor(rgb[0], rgb[1], rgb[2]); QColorDialog colorDialog(this); colorDialog.setCurrentColor(initialColor); colorDialog.setOption(QColorDialog::DontUseNativeDialog); colorDialog.setWindowTitle(prefColorName); if (colorDialog.exec() == QColorDialog::Accepted) { const QColor newColor = colorDialog.currentColor(); rgb[0] = newColor.red(); rgb[1] = newColor.green(); rgb[2] = newColor.blue(); switch (prefColor) { case PREF_COLOR_BACKGROUND_ALL: prefs->setColorBackgroundAllView(rgb); break; case PREF_COLOR_BACKGROUND_CHART: prefs->setColorBackgroundChartView(rgb); break; case PREF_COLOR_BACKGROUND_SURFACE: prefs->setColorBackgroundSurfaceView(rgb); break; case PREF_COLOR_BACKGROUND_VOLUME: prefs->setColorBackgroundVolumeView(rgb); break; case PREF_COLOR_FOREGROUND_ALL: prefs->setColorForegroundAllView(rgb); break; case PREF_COLOR_FOREGROUND_CHART: prefs->setColorForegroundChartView(rgb); break; case PREF_COLOR_FOREGROUND_SURFACE: prefs->setColorForegroundSurfaceView(rgb); break; case PREF_COLOR_FOREGROUND_VOLUME: prefs->setColorForegroundVolumeView(rgb); break; case PREF_COLOR_CHART_MATRIX_GRID_LINES: prefs->setColorChartMatrixGridLines(rgb); break; case NUMBER_OF_PREF_COLORS: CaretAssert(0); break; } updateColorWidget(prefs); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } } /** * Called when a color button is clicked. * * @param enumIndex * color enum integer value indicating button that was clicked. */ void PreferencesDialog::colorPushButtonClicked(int enumIndex) { const PREF_COLOR prefColor = (PREF_COLOR)enumIndex; updateColorWithDialog(prefColor); } /** * Called when the logging level is changed. * @param int indx * New index of logging level combo box. */ void PreferencesDialog::miscLoggingLevelComboBoxChanged(int indx) { CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); const int32_t logLevelIntegerCode = m_miscLoggingLevelComboBox->itemData(indx).toInt(); prefs->setLoggingLevel(LogLevelEnum::fromIntegerCode(logLevelIntegerCode, NULL)); } /** * Called when the apply button is pressed. */ void PreferencesDialog::applyButtonClicked() { EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } /** * Called when the OpenGL drawing method is changed. */ void PreferencesDialog::openGLDrawingMethodEnumComboBoxItemActivated() { const OpenGLDrawingMethodEnum::Enum drawingMethod = m_openGLDrawingMethodEnumComboBox->getSelectedItem(); CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); prefs->setOpenGLDrawingMethod(drawingMethod); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } /** * Called when the image capture method is changed. */ void PreferencesDialog::openGLImageCaptureMethodEnumComboBoxItemActivated() { const ImageCaptureMethodEnum::Enum imageCaptureMethod = m_openGLImageCaptureMethodEnumComboBox->getSelectedItem(); CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); prefs->setImageCaptureMethod(imageCaptureMethod); } /** * Called when volume crosshairs is toggled. * @param value * New value. */ void PreferencesDialog::volumeAxesCrosshairsComboBoxToggled(bool value) { CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); prefs->setVolumeAxesCrosshairsDisplayed(value); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } /** * Called when volume labels is toggled. * @param value * New value. */ void PreferencesDialog::volumeAxesLabelsComboBoxToggled(bool value) { CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); prefs->setVolumeAxesLabelsDisplayed(value); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } /** * Called when volume labels is toggled. * @param value * New value. */ void PreferencesDialog::volumeAxesMontageCoordinatesComboBoxToggled(bool value) { CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); prefs->setVolumeMontageAxesCoordinatesDisplayed(value); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } /** * Called when volume montage gap value is changed. */ void PreferencesDialog::volumeMontageGapValueChanged(int value) { CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); prefs->setVolumeMontageGap(value); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } /** * Called when volume montage coordinate precision value is changed. */ void PreferencesDialog::volumeMontageCoordinatePrecisionChanged(int value) { CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); prefs->setVolumeMontageCoordinatePrecision(value); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } /** * Called when volume identification value is changed. */ void PreferencesDialog::volumeIdentificationComboBoxToggled(bool value) { CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); prefs->setVolumeIdentificationDefaultedOn(value); } /** * Called when yoking default value is changed. */ void PreferencesDialog::yokingComboBoxToggled(bool value) { CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); prefs->setYokingDefaultedOn(value); } /** * Called when show splash screen option changed. * @param value * New value. */ void PreferencesDialog::miscSplashScreenShowAtStartupComboBoxChanged(bool value) { CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); prefs->setSplashScreenEnabled(value); } /** * Called when show develop menu option changed. * @param value * New value. */ void PreferencesDialog::miscDevelopMenuEnabledComboBoxChanged(bool value) { CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); prefs->setDevelopMenuEnabled(value); EventManager::get()->sendSimpleEvent(EventTypeEnum::EVENT_BROWSER_WINDOW_MENUS_UPDATE); } /** * Gets called when view files type is changed. */ void PreferencesDialog::miscSpecFileDialogViewFilesTypeEnumComboBoxItemActivated() { const SpecFileDialogViewFilesTypeEnum::Enum viewFilesType = m_miscSpecFileDialogViewFilesTypeEnumComboBox->getSelectedItem(); CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); prefs->setManageFilesViewFileType(viewFilesType); } workbench-1.1.1/src/GuiQt/PreferencesDialog.h000066400000000000000000000135071255417355300210660ustar00rootroot00000000000000#ifndef __PREFERENCES_DIALOG__H_ #define __PREFERENCES_DIALOG__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "WuQDialogNonModal.h" class QCheckBox; class QComboBox; class QDoubleSpinBox; class QGridLayout; class QLabel; class QSignalMapper; class QSpinBox; namespace caret { class CaretPreferences; class EnumComboBoxTemplate; class WuQTrueFalseComboBox; class WuQWidgetObjectGroup; class PreferencesDialog : public WuQDialogNonModal { Q_OBJECT public: PreferencesDialog(QWidget* parent); virtual ~PreferencesDialog(); void updateDialog(); protected: virtual void applyButtonClicked(); private slots: void colorPushButtonClicked(int); void miscDevelopMenuEnabledComboBoxChanged(bool value); void miscLoggingLevelComboBoxChanged(int); void miscSplashScreenShowAtStartupComboBoxChanged(bool value); void miscSpecFileDialogViewFilesTypeEnumComboBoxItemActivated(); void openGLDrawingMethodEnumComboBoxItemActivated(); void openGLImageCaptureMethodEnumComboBoxItemActivated(); void volumeAxesCrosshairsComboBoxToggled(bool value); void volumeAxesLabelsComboBoxToggled(bool value); void volumeAxesMontageCoordinatesComboBoxToggled(bool value); void volumeMontageGapValueChanged(int value); void volumeMontageCoordinatePrecisionChanged(int value); void volumeIdentificationComboBoxToggled(bool value); void yokingComboBoxToggled(bool value); void identificationSymbolToggled(); private: enum PREF_COLOR { PREF_COLOR_BACKGROUND_ALL = 0, PREF_COLOR_BACKGROUND_CHART = 1, PREF_COLOR_BACKGROUND_SURFACE = 2, PREF_COLOR_BACKGROUND_VOLUME = 3, PREF_COLOR_FOREGROUND_ALL = 4, PREF_COLOR_FOREGROUND_CHART = 5, PREF_COLOR_FOREGROUND_SURFACE = 6, PREF_COLOR_FOREGROUND_VOLUME = 7, PREF_COLOR_CHART_MATRIX_GRID_LINES = 8, NUMBER_OF_PREF_COLORS = 9 }; QWidget* createColorsWidget(); QWidget* createIdentificationSymbolWidget(); QWidget* createMiscellaneousWidget(); QWidget* createOpenGLWidget(); QWidget* createVolumeWidget(); void updateColorWidget(CaretPreferences* prefs); void updateIdentificationWidget(CaretPreferences* prefs); void updateMiscellaneousWidget(CaretPreferences* prefs); void updateOpenGLWidget(CaretPreferences* prefs); void updateVolumeWidget(CaretPreferences* prefs); void updateColorWithDialog(const PREF_COLOR prefColor); QLabel* addWidgetToLayout(QGridLayout* gridLayout, const QString& labelText, QWidget* widget); void addWidgetsToLayout(QGridLayout* gridLayout, QWidget* leftWidget, QWidget* rightWidget); void addColorButtonAndSwatch(QGridLayout* gridLayout, const PREF_COLOR prefColor, QSignalMapper* colorSignalMapper); PreferencesDialog(const PreferencesDialog&); PreferencesDialog& operator=(const PreferencesDialog&); QWidget* m_foregroundColorAllWidget; QWidget* m_foregroundColorChartWidget; QWidget* m_foregroundColorSurfaceWidget; QWidget* m_foregroundColorVolumeWidget; QWidget* m_backgroundColorAllWidget; QWidget* m_backgroundColorChartWidget; QWidget* m_backgroundColorSurfaceWidget; QWidget* m_backgroundColorVolumeWidget; QWidget* m_chartMatrixGridLinesColorWidget; WuQTrueFalseComboBox* m_miscDevelopMenuEnabledComboBox; QComboBox* m_miscLoggingLevelComboBox; WuQTrueFalseComboBox* m_miscSplashScreenShowAtStartupComboBox; EnumComboBoxTemplate* m_miscSpecFileDialogViewFilesTypeEnumComboBox; EnumComboBoxTemplate* m_openGLDrawingMethodEnumComboBox; EnumComboBoxTemplate* m_openGLImageCaptureMethodEnumComboBox; WuQTrueFalseComboBox* m_volumeAxesCrosshairsComboBox; WuQTrueFalseComboBox* m_volumeAxesLabelsComboBox; WuQTrueFalseComboBox* m_volumeAxesMontageCoordinatesComboBox; QSpinBox* m_volumeMontageGapSpinBox; QSpinBox* m_volumeMontageCoordinatePrecisionSpinBox; WuQTrueFalseComboBox* m_volumeIdentificationComboBox; WuQTrueFalseComboBox* m_yokingDefaultComboBox; WuQTrueFalseComboBox* m_surfaceIdentificationSymbolComboBox; WuQTrueFalseComboBox* m_volumeIdentificationSymbolComboBox; WuQWidgetObjectGroup* m_allWidgets; }; #ifdef __PREFERENCES_DIALOG__H__DECLARE__ #endif // __PREFERENCES_DIALOG__H__DECLARE__ } // namespace #endif //__PREFERENCES_DIALOG__H_ workbench-1.1.1/src/GuiQt/ProgressReportingDialog.cxx000066400000000000000000000163051255417355300226750ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __PROGRESS_REPORTING_DIALOG_DECLARE__ #include "ProgressReportingDialog.h" #undef __PROGRESS_REPORTING_DIALOG_DECLARE__ #include #include #include "CaretAssert.h" #include "EventManager.h" #include "EventProgressUpdate.h" #include "ProgressReportingWithSlots.h" using namespace caret; /** * \class caret::ProgressReportingDialog * \brief Progress Report Dialog * \ingroup GuiQt */ /** * \class caret::ProgressReportingDialog * \brief Dialog that displays "progress" as an event is running. * * This dialog will the display the "progress" on a task as the * task executes. As the task is running, it should send * EventProgressUpdate events indicating the progress of the processing. * If the user chooses to cancel the event, this dialog will indicate so * on the first EventProgressUpdate received after the Cancel button is * clicked. The task should query the ProgressReportingDialog::isCancelled() * after each progress update is sent. * * Tasks may send the EventProgressUpdate event from other threads. * When the event is received signals and slots are used to update * the contents of the dialog instead of directly updating the dialog's * content. Qt's signals and slots mechanism supports signals connecting * to slots that are in a different thread. * * Use the static method "runEvent" to run an event with a progress dialog. * * For other cases, use the public constructor. Create an instance of the * progress dialog and then start the desired task. The progress dialog * will automatically close when the progress value equals the maximum * progress value or when the progress dialog goes out of scope. */ /** * Constructor. * * User will need to send EventProgressUpdate events to update * this progress dialog. * * @param title * Title for dialog. * @param initialMessage * Message that is first displayed. * @param parent * Parent on which this progress dialog is displayed. * @param f * Window flags. */ ProgressReportingDialog::ProgressReportingDialog(const AString& title, const AString& initialMessage, QWidget* parent, Qt::WindowFlags f) : QProgressDialog(initialMessage, "Cancel", 0, 100, parent, f) { ProgressReportingFromEvent* progressFromEvent = new ProgressReportingFromEvent(this); m_progressReporter = progressFromEvent; if (title.isEmpty() == false) { setWindowTitle(title); } const int minimumTimeInMillisecondsBeforeDialogDisplayed = 0; setMinimumDuration(minimumTimeInMillisecondsBeforeDialogDisplayed); QObject::connect(progressFromEvent, SIGNAL(reportProgressRange(const int, const int)), this, SLOT(setRange(int, int))); QObject::connect(progressFromEvent, SIGNAL(reportProgressValue(const int)), this, SLOT(setValue(int))); QObject::connect(progressFromEvent, SIGNAL(reportProgressMessage(const QString&)), this, SLOT(setLabelText(const QString&))); QObject::connect(this, SIGNAL(canceled()), progressFromEvent, SLOT(requestCancel())); } /** * Destructor. */ ProgressReportingDialog::~ProgressReportingDialog() { } /** * Run the event in a progress dialog. Dialog will close after the * event completes. Progress is updated each time a * EventProgressUpdate is received. * * @param event * Event that is executed. * @param parent * Widget on which the dialog is displayed. * @param title * If not empty, title is in window's title bar */ void ProgressReportingDialog::runEvent(Event* event, QWidget* parent, const AString& title) { ProgressReportingDialog prd(title, "", parent); prd.setValue(0); EventManager::get()->sendEvent(event); prd.setValue(prd.maximum()); } //============================================================================= /** * \class caret::ProgressReportingWithSlots * \brief Interfaces between the ProgressReportingDialog and the Workbench event system * * Listens for EventProgressUpdate events and then updates the progress * dialog using signals and slots. Using signals and slots should allow * the progress events to be sent from a thread that is not the GUI thread. */ /** * Constructor. */ ProgressReportingFromEvent::ProgressReportingFromEvent(QObject* parent) : ProgressReportingWithSlots(parent), EventListenerInterface() { EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_PROGRESS_UPDATE); } /** * Constructor. */ ProgressReportingFromEvent::~ProgressReportingFromEvent() { EventManager::get()->removeAllEventsFromListener(this); } void ProgressReportingFromEvent::receiveEvent(Event* event) { if (event->getEventType() == EventTypeEnum::EVENT_PROGRESS_UPDATE) { EventProgressUpdate* progressEvent = dynamic_cast(event); CaretAssert(progressEvent); const int minProg = progressEvent->getMinimumProgressValue(); const int maxProg = progressEvent->getMaximumProgressValue(); const int progValue = progressEvent->getProgressValue(); const AString progMessage = progressEvent->getProgressMessage(); /* * Note: It appears that the content of the progress dialog * only changes when the progress value is changed. So, set * the message prior to the progress value so that if the message * changes, the message in the progress dialog will be updated * when the progress value changes. */ if (progMessage.isEmpty() == false) { setProgressMessage(progMessage); } if ((minProg >= 0) && (maxProg >= minProg)) { setProgressRange(minProg, maxProg); } if (progValue >= 0) { setProgressValue(progValue); } if (isCancelRequested()) { progressEvent->setCancelled(); } QApplication::processEvents(); progressEvent->setEventProcessed(); } } workbench-1.1.1/src/GuiQt/ProgressReportingDialog.h000066400000000000000000000046531255417355300223250ustar00rootroot00000000000000#ifndef __PROGRESS_REPORTING_DIALOG_H__ #define __PROGRESS_REPORTING_DIALOG_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "EventListenerInterface.h" #include "ProgressReportingWithSlots.h" namespace caret { class ProgressReportingFromEvent; class ProgressReportingDialog : public QProgressDialog { Q_OBJECT public: ProgressReportingDialog(const AString& title, const AString& initialMessage, QWidget* parent, Qt::WindowFlags f = 0); static void runEvent(Event* event, QWidget* parent, const AString& title); public: virtual ~ProgressReportingDialog(); private: ProgressReportingDialog(const ProgressReportingDialog&); ProgressReportingDialog& operator=(const ProgressReportingDialog&); public: // ADD_NEW_METHODS_HERE private: // ADD_NEW_MEMBERS_HERE ProgressReportingInterface* m_progressReporter; }; class ProgressReportingFromEvent : public ProgressReportingWithSlots, public EventListenerInterface { Q_OBJECT public: ProgressReportingFromEvent(QObject* parent); virtual ~ProgressReportingFromEvent(); void receiveEvent(Event* event); }; #ifdef __PROGRESS_REPORTING_DIALOG_DECLARE__ // #endif // __PROGRESS_REPORTING_DIALOG_DECLARE__ } // namespace #endif //__PROGRESS_REPORTING_DIALOG_H__ workbench-1.1.1/src/GuiQt/ProgressReportingWithSlots.cxx000066400000000000000000000104031255417355300234270ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __PROGRESS_REPORTING_WITH_SLOTS_DECLARE__ #include "ProgressReportingWithSlots.h" #undef __PROGRESS_REPORTING_WITH_SLOTS_DECLARE__ using namespace caret; /** * \class caret::ProgressReportingWithSlots * \brief Interfaces between the ProgressReportingDialog and the Workbench event system * \ingroup GuiQt * * Listens for EventProgressUpdate events and then updates the progress * dialog using signals and slots. Using signals and slots should allow * the progress events to be sent from a thread that is not the GUI thread. */ /** * Constructor. */ ProgressReportingWithSlots::ProgressReportingWithSlots(QObject* parent) : QObject(parent), ProgressReportingInterface() { m_synchronizeMutex.unlock(); m_cancelled = false; } /** * Constructor. */ ProgressReportingWithSlots::~ProgressReportingWithSlots() { } /** * Used by task to set the range of progress reporting. * * @param minimumProgress * The minimum amount reported (typically zero). * @param maximumProgress * The maximum amount of progress. */ void ProgressReportingWithSlots::setProgressRange(const int minimumProgress, const int maximumProgress) { /* * Don't let any other method in an instance of this * class execute until this method completes. */ QMutexLocker locker(&m_synchronizeMutex); if ((minimumProgress >= 0) && (maximumProgress >= minimumProgress)) { emit reportProgressRange(minimumProgress, maximumProgress); } } /** * Used by task to set the current progress. * * @param progressValue * The current progress within range of the minimum and maximum. */ void ProgressReportingWithSlots::setProgressValue(const int progressValue) { /* * Don't let any other method in an instance of this * class execute until this method completes. */ QMutexLocker locker(&m_synchronizeMutex); if (progressValue >= 0) { emit reportProgressValue(progressValue); } } /** * Used by task to set the message describing the task's activity. * * @param message * Message that is displayed. */ void ProgressReportingWithSlots::setProgressMessage(const AString& progressMessage) { /* * Don't let any other method in an instance of this * class execute until this method completes. */ QMutexLocker locker(&m_synchronizeMutex); if (progressMessage.isEmpty() == false) { emit reportProgressMessage(progressMessage); } } /** * (SLOT) Used by the user-interface to request that the task end as * soon as possible. */ void ProgressReportingWithSlots::requestCancel() { setCancelRequested(); } /** * @return "true" if a request been made to cancel the task, else false. * * Used by the task to see if the task should end as soon as * possible. If so, the task should clean up after itself * (release resources); */ bool ProgressReportingWithSlots::isCancelRequested() const { /* * Don't let any other method in an instance of this * class execute until this method completes. */ QMutexLocker locker(&m_synchronizeMutex); return m_cancelled; } /** * Set the cancel request. */ void ProgressReportingWithSlots::setCancelRequested() { /* * Don't let any other method in an instance of this * class execute until this method completes. */ QMutexLocker locker(&m_synchronizeMutex); m_cancelled = true; } workbench-1.1.1/src/GuiQt/ProgressReportingWithSlots.h000066400000000000000000000061431255417355300230620ustar00rootroot00000000000000#ifndef __PROGRESS_REPORTING_WITH_SLOTS_H__ #define __PROGRESS_REPORTING_WITH_SLOTS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "ProgressReportingInterface.h" namespace caret { class ProgressReportingWithSlots : public QObject, public ProgressReportingInterface { Q_OBJECT public: ProgressReportingWithSlots(QObject* parent); virtual ~ProgressReportingWithSlots(); virtual void setProgressRange(const int minimumProgress, const int maximumProgress); virtual void setProgressValue(const int progressValue); virtual void setProgressMessage(const AString& message); virtual bool isCancelRequested() const; virtual void setCancelRequested(); public slots: void requestCancel(); signals: /* * Emitted when the range of progress is updated. * @param minimumProgress * New value for minimum. * @param maximumProgress * New value for maximum. */ void reportProgressRange(const int minimumProgress, const int maximumProgress); /** * Emitted when the progress value is updated. * @param progressValue * New value for progress. */ void reportProgressValue(const int progressValue); /** * Emitted when the progress message is updated. * @param progressMessage * New value for progress message. * * NOTE: This must use a QString (not AString) since * it connects to a Qt slot expecting a QString */ void reportProgressMessage(const QString& progressMessage); private: /** * Ensures each method is synchronized meaning that each of * the methods for updating complete blocks if any other * methods are in progress. */ mutable QMutex m_synchronizeMutex; bool m_cancelled; }; #ifdef __PROGRESS_REPORTING_WITH_SLOTS_DECLARE__ // #endif // __PROGRESS_REPORTING_WITH_SLOTS_DECLARE__ } // namespace #endif //__PROGRESS_REPORTING_WITH_SLOTS_H__ workbench-1.1.1/src/GuiQt/RegionOfInterestCreateFromBorderDialog.cxx000066400000000000000000000534611255417355300255370ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #define __REGION_OF_INTEREST_CREATE_FROM_BORDER_DIALOG_DECLARE__ #include "RegionOfInterestCreateFromBorderDialog.h" #undef __REGION_OF_INTEREST_CREATE_FROM_BORDER_DIALOG_DECLARE__ #include #include #include #include #include #include #include #include #include #include #include using namespace caret; #include "AlgorithmException.h" #include "AlgorithmNodesInsideBorder.h" #include "Border.h" #include "BorderFile.h" #include "Brain.h" #include "BrainStructure.h" #include "CaretAssert.h" #include "CaretMappableDataFileAndMapSelector.h" #include "CiftiBrainordinateLabelFile.h" #include "CiftiBrainordinateScalarFile.h" #include "CursorDisplayScoped.h" #include "EventDataFileAdd.h" #include "EventManager.h" #include "EventGraphicsUpdateAllWindows.h" #include "EventSurfaceColoringInvalidate.h" #include "EventUserInterfaceUpdate.h" #include "GuiManager.h" #include "LabelFile.h" #include "MetricFile.h" #include "Surface.h" #include "WuQMessageBox.h" #include "WuQtUtilities.h" /** * \class caret::RegionOfInterestCreateFromBorderDialog * \brief Dailog for creating regions of interest from border(s). * \ingroup GuiQt * * Dialog that creates regions of interest from nodes inside * borders. */ /** * Constructor. * * @param border * Border used to create region of interest. * @param surface * Surface on which border is drawn and whose nodes are selected. * @param parent * Parent on which dialog is displayed. * */ RegionOfInterestCreateFromBorderDialog::RegionOfInterestCreateFromBorderDialog(Border* border, Surface* surface, QWidget* parent) : WuQDialogModal("Create Region of Interest", parent) { std::vector borders; borders.push_back(border); std::vector surfaces; surfaces.push_back(surface); this->createDialog(borders, surfaces); } /** * Destructor. */ RegionOfInterestCreateFromBorderDialog::~RegionOfInterestCreateFromBorderDialog() { } /** * Create the dialog. * * @param borders * Borders used to create regions of interest. * @param surfaceFiles * Surface files used for node selections. */ void RegionOfInterestCreateFromBorderDialog::createDialog(const std::vector& borders, std::vector& surfaces) { this->borders = borders; this->surfaces = surfaces; std::set requiredStructures; const int32_t numberOfBorders = static_cast(borders.size()); for (int32_t i = 0; i < numberOfBorders; i++) { const StructureEnum::Enum structure = borders[i]->getStructure(); requiredStructures.insert(structure); } QWidget* selectorWidget = this->createSelectors(requiredStructures, this->surfaces, this->mapFileTypeSelectors); this->inverseCheckBox = new QCheckBox("Invert Selected Vertices"); QWidget* widget = new QWidget(); QVBoxLayout* dialogLayout = new QVBoxLayout(widget); WuQtUtilities::setLayoutSpacingAndMargins(dialogLayout, 2, 2); dialogLayout->addWidget(selectorWidget); dialogLayout->addWidget(this->inverseCheckBox, 0, Qt::AlignLeft); this->setCentralWidget(widget, WuQDialog::SCROLL_AREA_NEVER); } /** * Create map files selectors for a map file type. * * @param mapFileType * Type of map file for selector. * @param requiredStructures * Structures needed. * @param surfaces * Surfaces available. * @param mapFileSelectors * Output containing the selectors. * @return Widget containing the map selector controls. */ QWidget* RegionOfInterestCreateFromBorderDialog::createSelectors(std::set& requiredStructures, std::vector& surfaces, STRUCTURE_MAP_FILE_SELECTOR_MAP& mapFileSelectors) { AString borderName; if (this->borders.empty() == false) { borderName = this->borders[0]->getName(); } QWidget* widget = new QWidget(); QVBoxLayout* mapSelectionLayout = new QVBoxLayout(widget); std::vector allowedMapFileTypes; allowedMapFileTypes.push_back(DataFileTypeEnum::CONNECTIVITY_DENSE_LABEL); allowedMapFileTypes.push_back(DataFileTypeEnum::CONNECTIVITY_DENSE_SCALAR); allowedMapFileTypes.push_back(DataFileTypeEnum::LABEL); allowedMapFileTypes.push_back(DataFileTypeEnum::METRIC); for (std::set::iterator structureIter = requiredStructures.begin(); structureIter != requiredStructures.end(); structureIter++) { const StructureEnum::Enum structure = *structureIter; for (std::vector::iterator surfaceIter = this->surfaces.begin(); surfaceIter != surfaces.end(); surfaceIter++) { Surface* surface = *surfaceIter; if (surface->getStructure() == structure) { std::vector allowedStructures; allowedStructures.push_back(surface->getStructure()); CaretMappableDataFileAndMapSelector* mapSelector = new CaretMappableDataFileAndMapSelector(borderName, GuiManager::get()->getBrain(), allowedMapFileTypes, allowedStructures, this); QObject::connect(mapSelector, SIGNAL(selectionChanged(CaretMappableDataFileAndMapSelector*)), this, SLOT(fileSelectionWasChanged(CaretMappableDataFileAndMapSelector*))); mapSelectionLayout->addWidget(mapSelector->getWidget()); mapFileSelectors.insert(std::make_pair(structure, mapSelector)); } } } return widget; } /** * Called when a map type/file/name is selected. * @param selector * Selector in which selection was made. */ void RegionOfInterestCreateFromBorderDialog::fileSelectionWasChanged(CaretMappableDataFileAndMapSelector* /*selector*/) { // std::cout << "Selection changed. " << std::endl; } /** * Called when the user presses the OK button. */ void RegionOfInterestCreateFromBorderDialog::okButtonClicked() { AString errorMessage; for (STRUCTURE_MAP_FILE_SELECTOR_ITERATOR iter = this->mapFileTypeSelectors.begin(); iter != this->mapFileTypeSelectors.end(); iter++) { //const StructureEnum::Enum structure = iter->first; CaretMappableDataFileAndMapSelector* mapSelector = iter->second; AString msg; if (mapSelector->isValidSelections(msg) == false) { if (errorMessage.isEmpty() == false) { errorMessage += "\n"; } errorMessage += msg; } } BorderFile* debugBorderFile = NULL; const bool isInverseSelection = this->inverseCheckBox->isChecked(); bool allowDialogToClose = true; if (errorMessage.isEmpty() == false) { allowDialogToClose = false; } else { /* * Show the wait cursor. */ CursorDisplayScoped cursor; cursor.showWaitCursor(); for (std::vector::iterator borderIterator = this->borders.begin(); borderIterator != this->borders.end(); borderIterator++) { Border* border = *borderIterator; const StructureEnum::Enum structure = border->getStructure(); STRUCTURE_MAP_FILE_SELECTOR_ITERATOR structureMapIterator = this->mapFileTypeSelectors.find(structure); if (structureMapIterator != this->mapFileTypeSelectors.end()) { const StructureEnum::Enum structure = structureMapIterator->first; CaretMappableDataFileAndMapSelector* mapSelector = structureMapIterator->second; mapSelector->saveCurrentSelections(); // save for next time switch (mapSelector->getSelectedMapFileType()) { case DataFileTypeEnum::CONNECTIVITY_DENSE_LABEL: { AString errorMessage; CiftiBrainordinateLabelFile* ciftiLabelFile = dynamic_cast(mapSelector->getSelectedMapFile()); CaretAssert(ciftiLabelFile); const int32_t mapIndex = mapSelector->getSelectedMapIndex(); const int32_t labelKey = mapSelector->getSelectedLabelKey(); Surface* surface = NULL; for (std::vector::iterator surfaceIterator = this->surfaces.begin(); surfaceIterator != this->surfaces.end(); surfaceIterator++) { Surface* s = *surfaceIterator; if (s->getStructure() == structure) { surface = s; break; } } CaretAssert(surface); if (surface != NULL) { try { AlgorithmNodesInsideBorder algorithmInsideBorder(NULL, surface, border, isInverseSelection, mapIndex, labelKey, ciftiLabelFile); const BorderFile* dbf = algorithmInsideBorder.getDebugBorderFile(); if (dbf != NULL) { debugBorderFile = new BorderFile(*dbf); } } catch (const AlgorithmException& e) { if (errorMessage.isEmpty() == false) { errorMessage += "\n"; } errorMessage += e.whatString(); } } else { if (errorMessage.isEmpty() == false) { errorMessage += "\n"; } errorMessage += ("Surface for structure " + StructureEnum::toGuiName(structure) + " was not found"); } } break; case DataFileTypeEnum::CONNECTIVITY_DENSE_SCALAR: { AString errorMessage; CiftiBrainordinateScalarFile* ciftiScalarFile = dynamic_cast(mapSelector->getSelectedMapFile()); CaretAssert(ciftiScalarFile); const int32_t mapIndex = mapSelector->getSelectedMapIndex(); const float value = mapSelector->getSelectedMetricValue(); Surface* surface = NULL; for (std::vector::iterator surfaceIterator = this->surfaces.begin(); surfaceIterator != this->surfaces.end(); surfaceIterator++) { Surface* s = *surfaceIterator; if (s->getStructure() == structure) { surface = s; break; } } CaretAssert(surface); if (surface != NULL) { try { AlgorithmNodesInsideBorder algorithmInsideBorder(NULL, surface, border, isInverseSelection, mapIndex, value, ciftiScalarFile); const BorderFile* dbf = algorithmInsideBorder.getDebugBorderFile(); if (dbf != NULL) { debugBorderFile = new BorderFile(*dbf); } } catch (const AlgorithmException& e) { if (errorMessage.isEmpty() == false) { errorMessage += "\n"; } errorMessage += e.whatString(); } } else { if (errorMessage.isEmpty() == false) { errorMessage += "\n"; } errorMessage += ("Surface for structure " + StructureEnum::toGuiName(structure) + " was not found"); } } break; case DataFileTypeEnum::LABEL: { LabelFile* labelFile = dynamic_cast(mapSelector->getSelectedMapFile()); const int32_t mapIndex = mapSelector->getSelectedMapIndex(); const int32_t labelKey = mapSelector->getSelectedLabelKey(); Surface* surface = NULL; for (std::vector::iterator surfaceIterator = this->surfaces.begin(); surfaceIterator != this->surfaces.end(); surfaceIterator++) { Surface* s = *surfaceIterator; if (s->getStructure() == structure) { surface = s; break; } } CaretAssert(surface); if (surface != NULL) { try { AlgorithmNodesInsideBorder algorithmInsideBorder(NULL, surface, border, isInverseSelection, mapIndex, labelKey, labelFile); const BorderFile* dbf = algorithmInsideBorder.getDebugBorderFile(); if (dbf != NULL) { debugBorderFile = new BorderFile(*dbf); } } catch (const AlgorithmException& e) { if (errorMessage.isEmpty() == false) { errorMessage += "\n"; } errorMessage += e.whatString(); } } else { if (errorMessage.isEmpty() == false) { errorMessage += "\n"; } errorMessage += ("Surface for structure " + StructureEnum::toGuiName(structure) + " was not found"); } } break; case DataFileTypeEnum::METRIC: { MetricFile* metricFile = dynamic_cast(mapSelector->getSelectedMapFile()); const int32_t mapIndex = mapSelector->getSelectedMapIndex(); const float value = mapSelector->getSelectedMetricValue(); Surface* surface = NULL; for (std::vector::iterator surfaceIterator = this->surfaces.begin(); surfaceIterator != this->surfaces.end(); surfaceIterator++) { Surface* s = *surfaceIterator; if (s->getStructure() == structure) { surface = s; break; } } CaretAssert(surface); if (surface != NULL) { try { AlgorithmNodesInsideBorder algorithmInsideBorder(NULL, surface, border, isInverseSelection, mapIndex, value, metricFile); const BorderFile* dbf = algorithmInsideBorder.getDebugBorderFile(); if (dbf != NULL) { debugBorderFile = new BorderFile(*dbf); } } catch (const AlgorithmException& e) { if (errorMessage.isEmpty() == false) { errorMessage += "\n"; } errorMessage += e.whatString(); } } else { if (errorMessage.isEmpty() == false) { errorMessage += "\n"; } errorMessage += ("Surface for structure " + StructureEnum::toGuiName(structure) + " was not found"); } } break; default: CaretAssertMessage(0, "Unsupported file type."); break; } } else { } } } if (debugBorderFile != NULL) { EventManager::get()->sendEvent(EventDataFileAdd(debugBorderFile).getPointer()); } EventManager::get()->sendEvent(EventSurfaceColoringInvalidate().getPointer()); EventManager::get()->sendEvent(EventUserInterfaceUpdate().getPointer()); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); if (errorMessage.isEmpty() == false) { WuQMessageBox::errorOk(this, errorMessage); } if (allowDialogToClose) { WuQDialogModal::okButtonClicked(); } } workbench-1.1.1/src/GuiQt/RegionOfInterestCreateFromBorderDialog.h000066400000000000000000000061101255417355300251510ustar00rootroot00000000000000#ifndef __REGION_OF_INTEREST_CREATE_FROM_BORDER_DIALOG__H_ #define __REGION_OF_INTEREST_CREATE_FROM_BORDER_DIALOG__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "CaretMappableDataFileAndMapSelector.h" #include "StructureEnum.h" #include "WuQDialogModal.h" class QButtonGroup; class QCheckBox; class QDoubleSpinBox; class QLineEdit; class QStackedWidget; namespace caret { class Border; class Surface; class RegionOfInterestCreateFromBorderDialog : public WuQDialogModal { Q_OBJECT public: RegionOfInterestCreateFromBorderDialog(Border* border, Surface* surface, QWidget* parent); virtual ~RegionOfInterestCreateFromBorderDialog(); private slots: void fileSelectionWasChanged(CaretMappableDataFileAndMapSelector*); protected: virtual void okButtonClicked(); private: RegionOfInterestCreateFromBorderDialog(const RegionOfInterestCreateFromBorderDialog&); RegionOfInterestCreateFromBorderDialog& operator=(const RegionOfInterestCreateFromBorderDialog&); void createDialog(const std::vector& borders, std::vector& surfaces); typedef std::map STRUCTURE_MAP_FILE_SELECTOR_MAP; typedef STRUCTURE_MAP_FILE_SELECTOR_MAP::iterator STRUCTURE_MAP_FILE_SELECTOR_ITERATOR; QWidget* createSelectors(std::set& requiredStructures, std::vector& surfaces, STRUCTURE_MAP_FILE_SELECTOR_MAP& mapFileSelectors); STRUCTURE_MAP_FILE_SELECTOR_MAP mapFileTypeSelectors; std::vector surfaces; std::vector borders; QCheckBox* inverseCheckBox; }; #ifdef __REGION_OF_INTEREST_CREATE_FROM_BORDER_DIALOG_DECLARE__ // #endif // __REGION_OF_INTEREST_CREATE_FROM_BORDER_DIALOG_DECLARE__ } // namespace #endif //__REGION_OF_INTEREST_CREATE_FROM_BORDER_DIALOG__H_ workbench-1.1.1/src/GuiQt/SceneCreateReplaceDialog.cxx000066400000000000000000000473171255417355300226630ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __SCENE_CREATE_REPLACE_DIALOG_DECLARE__ #include "SceneCreateReplaceDialog.h" #undef __SCENE_CREATE_REPLACE_DIALOG_DECLARE__ #include #include #include #include #include #include #include #include "Brain.h" #include "BrainBrowserWindow.h" #include "CaretAssert.h" #include "CaretPreferences.h" #include "DataFileException.h" #include "EventBrowserTabGetAll.h" #include "EventBrowserTabGetAllViewed.h" #include "EventImageCapture.h" #include "EventManager.h" #include "GuiManager.h" #include "ImageFile.h" #include "PlainTextStringBuilder.h" #include "Scene.h" #include "SceneAttributes.h" #include "SceneFile.h" #include "SceneInfo.h" #include "SessionManager.h" #include "WuQMessageBox.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::SceneCreateReplaceDialog * \brief Dialog for creating or replacing a scene. * \ingroup GuiQt * */ #include "Scene.h" /** * Constructor. * * @param dialogTitle * Title of the dialog. * @param parent * Parent on which dialog is displayed. * @param sceneFile * Scene file to which scene is added or replaced. * @param mode * Scene add/insert/replace mode * @param sceneToReplace * If non-NULL, this scene will be replaced. */ SceneCreateReplaceDialog::SceneCreateReplaceDialog(const AString& dialogTitle, QWidget* parent, SceneFile* sceneFile, const Mode mode, Scene* sceneToInsertOrReplace) : WuQDialogModal(dialogTitle, parent) { CaretAssert(sceneFile); switch (m_mode) { case MODE_ADD_NEW_SCENE: break; case MODE_INSERT_NEW_SCENE: CaretAssert(sceneToInsertOrReplace); break; case MODE_REPLACE_SCENE: CaretAssert(sceneToInsertOrReplace); break; } if ( ! s_previousSelectionsValid) { s_previousSelectionsValid = true; s_previousSelections.m_addAllLoadedFiles = true; s_previousSelections.m_addAllTabs = true; s_previousSelections.m_addModifiedPaletteSettings = true; s_previousSelections.m_addSpecFileNameToScene = true; } m_sceneFile = sceneFile; m_mode = mode; m_sceneToInsertOrReplace = sceneToInsertOrReplace; m_sceneThatWasCreated = NULL; /* * Options widget */ QLabel* optionsLabel = new QLabel("Options"); QWidget* optionsWidget = createSceneOptionsWidget(); /* * Create scene information widgets */ QLabel* nameLabel = new QLabel("Name"); m_nameLineEdit = new QLineEdit(); QLabel* descriptionLabel = new QLabel("Description"); m_descriptionTextEdit = new QPlainTextEdit(); const Qt::Alignment labelAlignment = (Qt::AlignLeft | Qt::AlignTop); /* * Layout for widgets */ int32_t columnCounter = 0; const int32_t labelColumn = columnCounter++; const int32_t widgetColumn = columnCounter++; QGridLayout* infoGridLayout = new QGridLayout(); infoGridLayout->setColumnStretch(labelColumn, 0); infoGridLayout->setColumnStretch(widgetColumn, 100); int32_t rowCounter = 0; infoGridLayout->setRowStretch(rowCounter, 0); infoGridLayout->addWidget(nameLabel, rowCounter, labelColumn, labelAlignment); infoGridLayout->addWidget(m_nameLineEdit, rowCounter, widgetColumn); rowCounter++; infoGridLayout->setRowStretch(rowCounter, 100); infoGridLayout->addWidget(descriptionLabel, rowCounter, labelColumn, labelAlignment); infoGridLayout->addWidget(m_descriptionTextEdit, rowCounter, widgetColumn); rowCounter++; infoGridLayout->setRowStretch(rowCounter, 0); infoGridLayout->addWidget(optionsLabel, rowCounter, labelColumn, labelAlignment); infoGridLayout->addWidget(optionsWidget, rowCounter, widgetColumn); rowCounter++; /* * Add the layout to a widget and return the widget. */ QWidget* infoWidget = new QWidget(); infoWidget->setLayout(infoGridLayout); QWidget* dialogWidget = new QWidget(); QVBoxLayout* dialogLayout = new QVBoxLayout(dialogWidget); dialogLayout->addWidget(infoWidget, 100); setCentralWidget(dialogWidget, WuQDialog::SCROLL_AREA_NEVER); PlainTextStringBuilder description; std::vector windows = GuiManager::get()->getAllOpenBrainBrowserWindows(); for (std::vector::iterator iter = windows.begin(); iter != windows.end(); iter++) { BrainBrowserWindow* window = *iter; window->getDescriptionOfContent(description); description.addLine(""); } switch (m_mode) { case MODE_ADD_NEW_SCENE: m_descriptionTextEdit->setPlainText(description.getText()); break; case MODE_INSERT_NEW_SCENE: m_descriptionTextEdit->setPlainText(description.getText()); break; case MODE_REPLACE_SCENE: m_nameLineEdit->setText(sceneToInsertOrReplace->getName()); m_descriptionTextEdit->setPlainText(sceneToInsertOrReplace->getDescription()); break; } setMinimumWidth(600); setMinimumHeight(300); setSaveWindowPositionForNextTime("SceneCreateDialog"); } /** * Destructor. */ SceneCreateReplaceDialog::~SceneCreateReplaceDialog() { } /** * Static method that creates a dialog for creating a new scene and * returns the scene that was created or NULL if scene was not created. * * @param parent * Parent widget on which dialog is displayed. * @param sceneFile * Scene file to which new scene is added. * @return * Scene that was created or NULL if user cancelled or there was an error. */ Scene* SceneCreateReplaceDialog::createNewScene(QWidget* parent, SceneFile* sceneFile) { SceneCreateReplaceDialog dialog("Create New Scene", parent, sceneFile, MODE_ADD_NEW_SCENE, NULL); dialog.exec(); Scene* scene = dialog.m_sceneThatWasCreated; return scene; } /** * Static method that creates a dialog for creating a new scene and * returns the scene that was created or NULL if scene was not created. * * @param parent * Parent widget on which dialog is displayed. * @param sceneFile * Scene file to which new scene is added. * @param insertBeforeScene * Insert the newly created scene BEFORE this scene. * @return * Scene that was created or NULL if user cancelled or there was an error. */ Scene* SceneCreateReplaceDialog::createNewSceneInsertBeforeScene(QWidget* parent, SceneFile* sceneFile, const Scene* insertBeforeScene) { SceneCreateReplaceDialog dialog("Insert New Scene", parent, sceneFile, MODE_INSERT_NEW_SCENE, const_cast(insertBeforeScene)); dialog.exec(); Scene* scene = dialog.m_sceneThatWasCreated; return scene; } /** * Static method that creates a dialog for replacing and existing scene and * returns the scene that was created or NULL if scene was not created. * * @param parent * Parent widget on which dialog is displayed. * @param sceneFile * File in which the given scene exists and will be replaced. * @param sceneToReplace * Scene that is being replaced. If the user presses the OK button * to replace this scene it will be destroyed so the pointer must not * be deferenced at any time after calling this method. * @return * Scene that was created or NULL if user cancelled or there was an error. */ Scene* SceneCreateReplaceDialog::replaceExistingScene(QWidget* parent, SceneFile* sceneFile, Scene* sceneToReplace) { const AString title = ("Replace Scene: " + sceneToReplace->getName()); SceneCreateReplaceDialog dialog(title, parent, sceneFile, MODE_REPLACE_SCENE, sceneToReplace); /* * Error checking will not allow two scenes with the same name * so temporarily modify name of scene being replaced and restore * it if the user does not hit OK. */ const AString savedSceneName = sceneToReplace->getName(); sceneToReplace->setName("slkkjdlkfjaslfjdljfdkljdfjsdfj"); if (dialog.exec() == SceneCreateReplaceDialog::Rejected) { sceneToReplace->setName(savedSceneName); } Scene* scene = dialog.m_sceneThatWasCreated; return scene; } /** * Add an image to the scene. * * @param scene * Scene to which image is added. */ void SceneCreateReplaceDialog::addImageToScene(Scene* scene) { AString errorMessage; CaretAssert(scene); uint8_t backgroundColor[3] = { 0, 0, 0 }; bool backgroundColorValid = false; /* * Capture an image of each window */ std::vector imageFiles; std::vector windows = GuiManager::get()->getAllOpenBrainBrowserWindows(); for (std::vector::iterator iter = windows.begin(); iter != windows.end(); iter++) { BrainBrowserWindow* bbw = *iter; const int32_t browserWindowIndex = bbw->getBrowserWindowIndex(); EventImageCapture imageCaptureEvent(browserWindowIndex); EventManager::get()->sendEvent(imageCaptureEvent.getPointer()); if (imageCaptureEvent.getEventProcessCount() > 0) { if (imageCaptureEvent.isError()) { errorMessage.appendWithNewLine(imageCaptureEvent.getErrorMessage()); } else { imageFiles.push_back(new ImageFile(imageCaptureEvent.getImage())); if ( ! backgroundColorValid) { imageCaptureEvent.getBackgroundColor(backgroundColor); backgroundColorValid = true; } } } } /* * Assemble images of each window into a single image * and add it to the scene. Use one image per row * since the images are limited in horizontal space * when shown in the listing of scenes. */ if ( ! imageFiles.empty()) { try { const int32_t numImagesPerRow = 1; ImageFile compositeImageFile; compositeImageFile.combinePreservingAspectAndFillIfNeeded(imageFiles, numImagesPerRow, backgroundColor); compositeImageFile.resizeToMaximumWidth(512); const AString PREFERRED_IMAGE_FORMAT = "png"; QByteArray byteArray; compositeImageFile.getImageInByteArray(byteArray, PREFERRED_IMAGE_FORMAT); scene->getSceneInfo()->setImageBytes(byteArray, PREFERRED_IMAGE_FORMAT); } catch (const DataFileException& dfe) { WuQMessageBox::errorOk(this, (dfe.whatString() + "\n\nEven though image failed, scene was created.")); } } /* * Free memory from the image files. */ for (std::vector::iterator iter = imageFiles.begin(); iter != imageFiles.end(); iter++) { delete *iter; } } /** * @return Widget containing the scene options widgets. */ QWidget* SceneCreateReplaceDialog::createSceneOptionsWidget() { /* * Create scene options widgets */ m_addSpecFileNameToSceneCheckBox = new QCheckBox("Add name of spec file to scene"); m_addSpecFileNameToSceneCheckBox->setChecked(s_previousSelections.m_addSpecFileNameToScene); WuQtUtilities::setWordWrappedToolTip(m_addSpecFileNameToSceneCheckBox, "Include name of spec file in the scene"); m_addAllTabsCheckBox = new QCheckBox("Add all tabs to scene"); m_addAllTabsCheckBox->setChecked(s_previousSelections.m_addAllTabs); WuQtUtilities::setWordWrappedToolTip(m_addAllTabsCheckBox, "Add all tabs to the scene. When this option is selected, " "the scene will be larger and require additional time to " "load. If NOT selected, only the selected tab in each " "window is saved to the scene."); m_addAllLoadedFilesCheckBox = new QCheckBox("Add all loaded files to scene"); m_addAllLoadedFilesCheckBox->setChecked(s_previousSelections.m_addAllLoadedFiles); WuQtUtilities::setWordWrappedToolTip(m_addAllLoadedFilesCheckBox, "Add all loaded files to scene. When this option is selected, " "the scene may require additional time to load as file that " "play no role in reproducing the scene will be loaded. If NOT " "selected, the scene may load more quickly."); m_addModifiedPaletteSettingsCheckBox = new QCheckBox("Add modified palette color mapping to scene"); m_addModifiedPaletteSettingsCheckBox->setChecked(s_previousSelections.m_addModifiedPaletteSettings); WuQtUtilities::setWordWrappedToolTip(m_addModifiedPaletteSettingsCheckBox, "The palette color mapping is saved within each data files that maps " "its data to brainordinates. However, there are instances in which " "the user wants the scene to display the data with palette color mapping " "that is different from that in the file. If this option is " "selected, modified palettes color mapping will be saved to the scene " "and the data files with modified palette color mapping do not need " "to be saved."); /* * Layout for scene options widgets */ QVBoxLayout* optionsLayout = new QVBoxLayout(); optionsLayout->addWidget(m_addSpecFileNameToSceneCheckBox); optionsLayout->addWidget(m_addAllTabsCheckBox); optionsLayout->addWidget(m_addAllLoadedFilesCheckBox); optionsLayout->addWidget(m_addModifiedPaletteSettingsCheckBox); /* * Add the layout to a widget and return the widget. */ QFrame* optionsWidget = new QFrame(); optionsWidget->setFrameStyle(QFrame::Box | QFrame::Plain); optionsWidget->setLineWidth(1); // QWidget* optionsWidget = new QWidget(); optionsWidget->setLayout(optionsLayout); return optionsWidget; } /** * Gets called if the user presses the OK button. */ void SceneCreateReplaceDialog::okButtonClicked() { s_previousSelections.m_addAllLoadedFiles = m_addAllLoadedFilesCheckBox->isChecked(); s_previousSelections.m_addAllTabs = m_addAllTabsCheckBox->isChecked(); s_previousSelections.m_addModifiedPaletteSettings = m_addModifiedPaletteSettingsCheckBox->isChecked(); s_previousSelections.m_addSpecFileNameToScene = m_addSpecFileNameToSceneCheckBox->isChecked(); const AString newSceneName = m_nameLineEdit->text(); AString errorMessage; if (newSceneName.isEmpty()) { errorMessage = "Scene Name is empty."; } else if (m_sceneFile->getSceneWithName(newSceneName) != NULL) { errorMessage = ("An existing scene uses the name \"" + newSceneName + "\". Scene names must be unique."); } if ( ! errorMessage.isEmpty()) { WuQMessageBox::errorOk(this, errorMessage); return; } Scene* newScene = new Scene(SceneTypeEnum::SCENE_TYPE_FULL); Scene::setSceneBeingCreated(newScene); newScene->setName(newSceneName); newScene->setDescription(m_descriptionTextEdit->toPlainText()); /* * Get all browser tabs and only save transformations for tabs * that are valid. */ std::vector tabIndices; if (m_addAllTabsCheckBox->isChecked()) { EventBrowserTabGetAll getAllTabs; EventManager::get()->sendEvent(getAllTabs.getPointer()); tabIndices = getAllTabs.getBrowserTabIndices(); } else { EventBrowserTabGetAllViewed getViewedTabs; EventManager::get()->sendEvent(getViewedTabs.getPointer()); tabIndices = getViewedTabs.getViewdedBrowserTabIndices(); } std::sort(tabIndices.begin(), tabIndices.end()); SceneAttributes* sceneAttributes = newScene->getAttributes(); sceneAttributes->setSceneFileName(m_sceneFile->getFileName()); sceneAttributes->setSceneName(newSceneName); sceneAttributes->setIndicesOfTabsForSavingToScene(tabIndices); sceneAttributes->setSpecFileNameSavedToScene(m_addSpecFileNameToSceneCheckBox->isChecked()); sceneAttributes->setAllLoadedFilesSavedToScene(m_addAllLoadedFilesCheckBox->isChecked()); sceneAttributes->setModifiedPaletteSettingsSavedToScene(m_addModifiedPaletteSettingsCheckBox->isChecked()); newScene->addClass(GuiManager::get()->saveToScene(sceneAttributes, "guiManager")); addImageToScene(newScene); switch (m_mode) { case MODE_ADD_NEW_SCENE: m_sceneFile->addScene(newScene); break; case MODE_INSERT_NEW_SCENE: m_sceneFile->insertScene(newScene, m_sceneToInsertOrReplace); break; case MODE_REPLACE_SCENE: m_sceneFile->replaceScene(newScene, m_sceneToInsertOrReplace); break; } m_sceneThatWasCreated = newScene; Scene::setSceneBeingCreated(NULL); WuQDialogModal::okButtonClicked(); } workbench-1.1.1/src/GuiQt/SceneCreateReplaceDialog.h000066400000000000000000000074401255417355300223010ustar00rootroot00000000000000#ifndef __SCENE_CREATE_REPLACE_DIALOG_H__ #define __SCENE_CREATE_REPLACE_DIALOG_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "WuQDialogModal.h" class QCheckBox; class QLineEdit; class QPlainTextEdit; namespace caret { class Scene; class SceneFile; class SceneCreateReplaceDialog : public WuQDialogModal { Q_OBJECT public: static Scene* createNewScene(QWidget* parent, SceneFile* sceneFile); static Scene* createNewSceneInsertBeforeScene(QWidget* parent, SceneFile* sceneFile, const Scene* insertBeforeScene); static Scene* replaceExistingScene(QWidget* parent, SceneFile* sceneFile, Scene* sceneToReplace); virtual ~SceneCreateReplaceDialog(); private: enum Mode { MODE_ADD_NEW_SCENE, MODE_INSERT_NEW_SCENE, MODE_REPLACE_SCENE }; SceneCreateReplaceDialog(const AString& dialogTitle, QWidget* parent, SceneFile* sceneFile, const Mode mode, Scene* sceneToInsertOrReplace); SceneCreateReplaceDialog(const SceneCreateReplaceDialog&); SceneCreateReplaceDialog& operator=(const SceneCreateReplaceDialog&); public: // ADD_NEW_METHODS_HERE protected: virtual void okButtonClicked(); private: // ADD_NEW_MEMBERS_HERE void addImageToScene(Scene* scene); SceneFile* m_sceneFile; Mode m_mode; Scene* m_sceneToInsertOrReplace; /** Scene that was created DO NOT DESTROY SINCE RETURNED TO CALLER */ Scene* m_sceneThatWasCreated; QWidget* createSceneOptionsWidget(); QLineEdit* m_nameLineEdit; QPlainTextEdit* m_descriptionTextEdit; QCheckBox* m_addSpecFileNameToSceneCheckBox; QCheckBox* m_addAllTabsCheckBox; QCheckBox* m_addAllLoadedFilesCheckBox; QCheckBox* m_addModifiedPaletteSettingsCheckBox; struct PreviousSelections { bool m_addSpecFileNameToScene; bool m_addAllTabs; bool m_addAllLoadedFiles; bool m_addModifiedPaletteSettings; }; static PreviousSelections s_previousSelections; static bool s_previousSelectionsValid; }; #ifdef __SCENE_CREATE_REPLACE_DIALOG_DECLARE__ SceneCreateReplaceDialog::PreviousSelections SceneCreateReplaceDialog::s_previousSelections; bool SceneCreateReplaceDialog::s_previousSelectionsValid = false; #endif // __SCENE_CREATE_REPLACE_DIALOG_DECLARE__ } // namespace #endif //__SCENE_CREATE_REPLACE_DIALOG_H__ workbench-1.1.1/src/GuiQt/SceneDialog.cxx000066400000000000000000001264171255417355300202420ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define __SCENE_DIALOG_DECLARE__ #include "SceneDialog.h" #undef __SCENE_DIALOG_DECLARE__ #include "Brain.h" #include "BrainBrowserWindow.h" #include "BrainConstants.h" #include "BrowserTabContent.h" #include "CaretAssert.h" #include "CaretFileDialog.h" #include "CaretLogger.h" #include "CaretMappableDataFile.h" #include "CursorDisplayScoped.h" #include "CaretPreferences.h" #include "DataFileException.h" #include "ElapsedTimer.h" #include "EventBrowserTabGetAll.h" #include "EventDataFileAdd.h" #include "EventImageCapture.h" #include "EventManager.h" #include "EventUserInterfaceUpdate.h" #include "GuiManager.h" #include "ImageFile.h" #include "ProgressReportingDialog.h" #include "SceneAttributes.h" #include "SceneClass.h" #include "SceneCreateReplaceDialog.h" #include "SceneFile.h" #include "SceneInfo.h" #include "Scene.h" #include "SessionManager.h" #include "UsernamePasswordWidget.h" #include "WuQDataEntryDialog.h" #include "WuQMessageBox.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::SceneDialog * \brief Dialog for manipulation of scenes. * \ingroup GuiQt */ /** * Constructor. * * @param parent * The parent widget. */ SceneDialog::SceneDialog(QWidget* parent) : WuQDialogNonModal("Scenes", parent) { m_selectedSceneClassInfoIndex = -1; /* * No apply buton */ setApplyButtonText(""); /* * Set the dialog's widget */ this->setCentralWidget(createMainPage(), WuQDialog::SCROLL_AREA_NEVER); /* * No auto default button processing (Qt highlights button) */ disableAutoDefaultForAllPushButtons(); // setDialogSizeHint(650, // 500); setSaveWindowPositionForNextTime(true); /* * Update the dialog. */ updateDialog(); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_USER_INTERFACE_UPDATE); resize(650, 500); } /** * Destructor. */ SceneDialog::~SceneDialog() { EventManager::get()->removeAllEventsFromListener(this); } /** * Update the scene dialog. */ void SceneDialog::updateDialog() { loadSceneFileComboBox(NULL); loadScenesIntoDialog(NULL); } /** * Get the selected scene file. * @return SceneFile or NULL if no scene file. */ SceneFile* SceneDialog::getSelectedSceneFile() { SceneFile* sceneFile = NULL; const int fileComboBoxIndex = m_sceneFileSelectionComboBox->currentIndex(); if (fileComboBoxIndex >= 0) { void* filePointer = m_sceneFileSelectionComboBox->itemData(fileComboBoxIndex).value(); sceneFile = (SceneFile*)filePointer; } return sceneFile; } /** * @return The selected scene. */ Scene* SceneDialog::getSelectedScene() { Scene* selectedScene = NULL; SceneFile* sceneFile = getSelectedSceneFile(); if (sceneFile != NULL) { int32_t numValidScenes = sceneFile->getNumberOfScenes(); bool selectedSceneChanged = false; if (m_selectedSceneClassInfoIndex >= numValidScenes) { m_selectedSceneClassInfoIndex = numValidScenes - 1; selectedSceneChanged = true; } if (m_selectedSceneClassInfoIndex < 0) { if (numValidScenes > 0) { m_selectedSceneClassInfoIndex = 0; selectedSceneChanged = true; } } if ((m_selectedSceneClassInfoIndex >= 0) && (m_selectedSceneClassInfoIndex < numValidScenes)) { selectedScene = sceneFile->getSceneAtIndex(m_selectedSceneClassInfoIndex); } if (selectedSceneChanged) { highlightSceneAtIndex(m_selectedSceneClassInfoIndex); } } return selectedScene; } /** * Load the scene files into the scene file combo box. * * @param selectedSceneFileIn * Scene file that is added. */ void SceneDialog::loadSceneFileComboBox(SceneFile* selectedSceneFileIn) { SceneFile* selectedSceneFile = selectedSceneFileIn; if (selectedSceneFile == NULL) { selectedSceneFile = getSelectedSceneFile(); } Brain* brain = GuiManager::get()->getBrain(); const int32_t numSceneFiles = brain->getNumberOfSceneFiles(); m_sceneFileSelectionComboBox->clear(); int defaultFileComboIndex = 0; for (int32_t i = 0; i < numSceneFiles; i++) { SceneFile* sceneFile = brain->getSceneFile(i); if (sceneFile == selectedSceneFile) { defaultFileComboIndex = i; } const AString name = sceneFile->getFileNameNoPath(); m_sceneFileSelectionComboBox->addItem(name, qVariantFromValue((void*)sceneFile)); } if (numSceneFiles > 0) { m_sceneFileSelectionComboBox->setCurrentIndex(defaultFileComboIndex); } } /** * Load the scene into the dialog. * * @param selectedSceneIn * Scene to add. */ void SceneDialog::loadScenesIntoDialog(Scene* selectedSceneIn) { SceneFile* sceneFile = getSelectedSceneFile(); Scene* selectedScene = selectedSceneIn; if (selectedScene == NULL) { selectedScene = getSelectedScene(); } for (std::vector::iterator iter = m_sceneClassInfoWidgets.begin(); iter != m_sceneClassInfoWidgets.end(); iter++) { SceneClassInfoWidget* sciw = *iter; sciw->blockSignals(true); sciw->updateContent(NULL, -1); } int32_t numberOfValidSceneInfoWidgets = 0; int32_t defaultIndex = -1; if (sceneFile != NULL) { const int32_t numScenes = sceneFile->getNumberOfScenes(); for (int32_t i = 0; i < numScenes; i++) { Scene* scene = sceneFile->getSceneAtIndex(i); QByteArray imageByteArray; AString imageBytesFormat; scene->getSceneInfo()->getImageBytes(imageByteArray, imageBytesFormat); SceneClassInfoWidget* sciw = NULL; if (i >= static_cast(m_sceneClassInfoWidgets.size())) { sciw = new SceneClassInfoWidget(); QObject::connect(sciw, SIGNAL(activated(int32_t)), this, SLOT(sceneActivated(int32_t))); QObject::connect(sciw, SIGNAL(highlighted(int32_t)), this, SLOT(sceneHighlighted(int32_t))); m_sceneClassInfoWidgets.push_back(sciw); m_sceneSelectionLayout->addWidget(sciw); } else { sciw = m_sceneClassInfoWidgets[i]; } sciw->updateContent(scene, i); sciw->setBackgroundForSelected(i == 1); if (scene == selectedScene) { defaultIndex = i; } else if (defaultIndex < 0) { defaultIndex = i; } } numberOfValidSceneInfoWidgets = numScenes; } const int32_t numberOfSceneInfoWidgets = static_cast(m_sceneClassInfoWidgets.size()); for (int32_t i = 0; i < numberOfSceneInfoWidgets; i++) { bool visibilityStatus = false; if (i < numberOfValidSceneInfoWidgets) { visibilityStatus = true; } m_sceneClassInfoWidgets[i]->setVisible(visibilityStatus); m_sceneClassInfoWidgets[i]->blockSignals(false); } if (defaultIndex >= 0) { highlightSceneAtIndex(defaultIndex); } const bool validFile = (getSelectedSceneFile() != NULL); bool validScene = false; if (validFile) { const Scene* scene = getSelectedScene(); if (scene != NULL) { validScene = true; } } m_addNewScenePushButton->setEnabled(validFile); m_deleteScenePushButton->setEnabled(validScene); m_insertNewScenePushButton->setEnabled(validScene); m_replaceScenePushButton->setEnabled(validScene); m_showScenePushButton->setEnabled(validScene); m_showSceneImagePreviewPushButton->setEnabled(validScene); } /** * Highlight the scene at the given index. * * @param sceneIndex * Index of scene to highlight. */ void SceneDialog::highlightSceneAtIndex(const int32_t sceneIndex) { bool sceneIndexValid = false; const int32_t numberOfSceneInfoWidgets = static_cast(m_sceneClassInfoWidgets.size()); for (int32_t i = 0; i < numberOfSceneInfoWidgets; i++) { SceneClassInfoWidget* sciw = m_sceneClassInfoWidgets[i]; if (sciw->isValid()) { if (sciw->getSceneIndex() == sceneIndex) { sciw->setBackgroundForSelected(true); sceneIndexValid = true; m_selectedSceneClassInfoIndex = i; } else { sciw->setBackgroundForSelected(false); } } } if ( ! sceneIndexValid) { m_selectedSceneClassInfoIndex = -1; } } /** * Highlight the given scene. * * @param scene * Scene to highlight. */ void SceneDialog::highlightScene(const Scene* scene) { const int32_t numberOfSceneInfoWidgets = static_cast(m_sceneClassInfoWidgets.size()); for (int32_t i = 0; i < numberOfSceneInfoWidgets; i++) { SceneClassInfoWidget* sciw = m_sceneClassInfoWidgets[i]; if (sciw->isValid()) { if (sciw->getScene() == scene) { highlightSceneAtIndex(sciw->getSceneIndex()); return; } } } } /** * Called to create a new scene file. */ void SceneDialog::newSceneFileButtonClicked() { /* * Let user choose a different path/name */ SceneFile* newSceneFile = new SceneFile(); GuiManager::get()->getBrain()->convertDataFilePathNameToAbsolutePathName(newSceneFile); AString newSceneFileName = CaretFileDialog::getSaveFileNameDialog(DataFileTypeEnum::SCENE, this, "Choose Scene File Name", newSceneFile->getFileName()); /* * If user cancels, delete the new scene file and return */ if (newSceneFileName.isEmpty()) { delete newSceneFile; return; } /* * Set name of new scene file and add to brain */ newSceneFile->setFileName(newSceneFileName); EventManager::get()->sendEvent(EventDataFileAdd(newSceneFile).getPointer()); this->loadSceneFileComboBox(newSceneFile); this->sceneFileSelected(); } /** * Called when a scene file is selected. */ void SceneDialog::sceneFileSelected() { loadScenesIntoDialog(NULL); } /** * Called when add new scene button clicked. */ void SceneDialog::addNewSceneButtonClicked() { if (checkForModifiedFiles() == false) { return; } SceneFile* sceneFile = getSelectedSceneFile(); if (sceneFile != NULL) { Scene* newScene = SceneCreateReplaceDialog::createNewScene(m_addNewScenePushButton, sceneFile); loadScenesIntoDialog(newScene); } } /** * Called when insert new scene button clicked. */ void SceneDialog::insertSceneButtonClicked() { if (checkForModifiedFiles() == false) { return; } SceneFile* sceneFile = getSelectedSceneFile(); if (sceneFile != NULL) { Scene* scene = getSelectedScene(); if (scene != NULL) { Scene* newScene = SceneCreateReplaceDialog::createNewSceneInsertBeforeScene(m_insertNewScenePushButton, sceneFile, scene); loadScenesIntoDialog(newScene); } } } /** * Called when replace scene button clicked. */ void SceneDialog::replaceSceneButtonClicked() { if (checkForModifiedFiles() == false) { return; } SceneFile* sceneFile = getSelectedSceneFile(); if (sceneFile != NULL) { Scene* scene = getSelectedScene(); if (scene != NULL) { Scene* newScene = SceneCreateReplaceDialog::replaceExistingScene(m_addNewScenePushButton, sceneFile, scene); loadScenesIntoDialog(newScene); } } } /** * Add an image to the scene. * * @param scene * Scene to which image is added. */ void SceneDialog::addImageToScene(Scene* scene) { AString errorMessage; CaretAssert(scene); uint8_t backgroundColor[3] = { 0, 0, 0 }; bool backgroundColorValid = false; /* * Capture an image of each window */ std::vector imageFiles; std::vector windows = GuiManager::get()->getAllOpenBrainBrowserWindows(); for (std::vector::iterator iter = windows.begin(); iter != windows.end(); iter++) { BrainBrowserWindow* bbw = *iter; const int32_t browserWindowIndex = bbw->getBrowserWindowIndex(); EventImageCapture imageCaptureEvent(browserWindowIndex); EventManager::get()->sendEvent(imageCaptureEvent.getPointer()); if (imageCaptureEvent.getEventProcessCount() > 0) { if (imageCaptureEvent.isError()) { errorMessage.appendWithNewLine(imageCaptureEvent.getErrorMessage()); } else { imageFiles.push_back(new ImageFile(imageCaptureEvent.getImage())); if ( ! backgroundColorValid) { imageCaptureEvent.getBackgroundColor(backgroundColor); backgroundColorValid = true; } } } } /* * Assemble images of each window into a single image * and add it to the scene. Use one image per row * since the images are limited in horizontal space * when shown in the listing of scenes. */ if ( ! imageFiles.empty()) { try { const int32_t numImagesPerRow = 1; ImageFile compositeImageFile; compositeImageFile.combinePreservingAspectAndFillIfNeeded(imageFiles, numImagesPerRow, backgroundColor); compositeImageFile.resizeToMaximumWidth(512); QByteArray byteArray; compositeImageFile.getImageInByteArray(byteArray, SceneDialog::PREFERRED_IMAGE_FORMAT); scene->getSceneInfo()->setImageBytes(byteArray, SceneDialog::PREFERRED_IMAGE_FORMAT); } catch (const DataFileException& dfe) { WuQMessageBox::errorOk(m_addNewScenePushButton, dfe.whatString()); } } /* * Free memory from the image files. */ for (std::vector::iterator iter = imageFiles.begin(); iter != imageFiles.end(); iter++) { delete *iter; } } /** * Check to see if there are modified files. If there are * allow the user to continue or cancel creation of the scene. * * @return * true if the scene should be created, otherwise false. */ bool SceneDialog::checkForModifiedFiles() { /* * Exclude all * Scene Files * Spec Files */ std::vector dataFileTypesToExclude; dataFileTypesToExclude.push_back(DataFileTypeEnum::SCENE); dataFileTypesToExclude.push_back(DataFileTypeEnum::SPECIFICATION); std::vector allDataFiles; GuiManager::get()->getBrain()->getAllDataFiles(allDataFiles); AString modifiedDataFilesMessage; AString modifiedPaletteFilesMessage; for (std::vector::iterator iter = allDataFiles.begin(); iter != allDataFiles.end(); iter++) { CaretDataFile* caretDataFile = *iter; const DataFileTypeEnum::Enum dataFileType = caretDataFile->getDataFileType(); if (std::find(dataFileTypesToExclude.begin(), dataFileTypesToExclude.end(), dataFileType) != dataFileTypesToExclude.end()) { continue; } CaretMappableDataFile* mappableDataFile = dynamic_cast(caretDataFile); if (caretDataFile->isModified()) { AString fileMsg = caretDataFile->getFileNameNoPath(); /* * Is modification just the palette color mapping? */ bool paletteOnlyModFlag = false; if (mappableDataFile != NULL) { if (mappableDataFile->isModifiedPaletteColorMapping()) { if ( ! mappableDataFile->isModifiedExcludingPaletteColorMapping()) { paletteOnlyModFlag = true; } } } if (paletteOnlyModFlag) { modifiedPaletteFilesMessage.appendWithNewLine(" " + fileMsg); } else { modifiedDataFilesMessage.appendWithNewLine(" " + fileMsg); } } } if ( ! modifiedDataFilesMessage.isEmpty()) { modifiedDataFilesMessage.insert(0, "These file(s) contain modified data:\n"); modifiedDataFilesMessage.append("\n"); } if ( ! modifiedPaletteFilesMessage.isEmpty()) { modifiedPaletteFilesMessage.insert(0, "These file(s) contain modified palette color mapping. It is not " "necessary to save these file(s) if the save palette color mapping " "option is selected on the scene creation dialog:\n"); modifiedPaletteFilesMessage.append("\n"); } bool result = true; if (( ! modifiedDataFilesMessage.isEmpty()) || ( ! modifiedPaletteFilesMessage.isEmpty())) { const AString msg = (modifiedDataFilesMessage + modifiedPaletteFilesMessage + "\nContinue creating the scene?"); result = WuQMessageBox::warningYesNo(this, WuQtUtilities::createWordWrappedToolTipText(msg)); } return result; } /** * Create the main page. * @return the main page. */ QWidget* SceneDialog::createMainPage() { /* * Scene File Controls */ QLabel* sceneFileLabel = new QLabel("Scene File"); m_sceneFileSelectionComboBox = new QComboBox(); m_sceneFileSelectionComboBox->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength); m_sceneFileSelectionComboBox->setSizePolicy(QSizePolicy::MinimumExpanding, m_sceneFileSelectionComboBox->sizePolicy().verticalPolicy()); WuQtUtilities::setToolTipAndStatusTip(m_sceneFileSelectionComboBox, "Selects an existing scene file\n" "to which new scenes are added."); QObject::connect(m_sceneFileSelectionComboBox, SIGNAL(activated(int)), this, SLOT(sceneFileSelected())); /* * New File button */ QPushButton* newSceneFilePushButton = new QPushButton("New..."); QObject::connect(newSceneFilePushButton, SIGNAL(clicked()), this, SLOT(newSceneFileButtonClicked())); /* * Scene controls */ QLabel* sceneLabel = new QLabel("Scenes"); /* * Add new scene button */ m_addNewScenePushButton = new QPushButton("Add..."); m_addNewScenePushButton->setToolTip("Add a new scene to the END of the list"); QObject::connect(m_addNewScenePushButton, SIGNAL(clicked()), this, SLOT(addNewSceneButtonClicked())); /* * Delete new scene button */ m_deleteScenePushButton = new QPushButton("Delete..."); m_deleteScenePushButton->setToolTip("Delete the selected scene"); QObject::connect(m_deleteScenePushButton, SIGNAL(clicked()), this, SLOT(deleteSceneButtonClicked())); /* * Insert scene button */ m_insertNewScenePushButton = new QPushButton("Insert..."); m_insertNewScenePushButton->setToolTip("Insert a new scene ABOVE the selected scene in the list"); QObject::connect(m_insertNewScenePushButton, SIGNAL(clicked()), this, SLOT(insertSceneButtonClicked())); /* * Replace scene button */ m_replaceScenePushButton = new QPushButton("Replace..."); m_replaceScenePushButton->setToolTip("Replace the selected scene"); QObject::connect(m_replaceScenePushButton, SIGNAL(clicked()), this, SLOT(replaceSceneButtonClicked())); /* * Show new scene button */ m_showScenePushButton = new QPushButton("Show"); m_showScenePushButton->setToolTip("Show the selected scene"); QObject::connect(m_showScenePushButton, SIGNAL(clicked()), this, SLOT(showSceneButtonClicked())); /* * Show scene image button */ m_showSceneImagePreviewPushButton = new QPushButton("Preview..."); m_showSceneImagePreviewPushButton->setToolTip("Show larger image and full description\n" "for the selected scene"); QObject::connect(m_showSceneImagePreviewPushButton, SIGNAL(clicked()), this, SLOT(showImagePreviewButtonClicked())); /* * Layout for scene buttons */ QVBoxLayout* sceneButtonLayout = new QVBoxLayout(); sceneButtonLayout->addWidget(m_showScenePushButton); sceneButtonLayout->addWidget(m_showSceneImagePreviewPushButton); sceneButtonLayout->addSpacing(20); sceneButtonLayout->addStretch(); sceneButtonLayout->addWidget(m_addNewScenePushButton); sceneButtonLayout->addWidget(m_insertNewScenePushButton); sceneButtonLayout->addWidget(m_replaceScenePushButton); sceneButtonLayout->addWidget(m_deleteScenePushButton); /* * Widget and layout containing the scene class info. */ m_sceneSelectionLayout = new QVBoxLayout(); m_sceneSelectionWidget = new QWidget(); m_sceneSelectionWidget->setSizePolicy(m_sceneSelectionWidget->sizePolicy().horizontalPolicy(), QSizePolicy::Fixed); QVBoxLayout* sceneSelectionWidgetLayout = new QVBoxLayout(m_sceneSelectionWidget); sceneSelectionWidgetLayout->addLayout(m_sceneSelectionLayout); sceneSelectionWidgetLayout->addStretch(); QScrollArea* sceneSelectionScrollArea = new QScrollArea(); sceneSelectionScrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); sceneSelectionScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); sceneSelectionScrollArea->setWidget(m_sceneSelectionWidget); sceneSelectionScrollArea->setWidgetResizable(true); /* * Layout widgets */ QWidget* widget = new QWidget(); QGridLayout* gridLayout = new QGridLayout(widget); int row = 0; gridLayout->addWidget(sceneFileLabel, row, 0); gridLayout->addWidget(m_sceneFileSelectionComboBox, row, 1); gridLayout->addWidget(newSceneFilePushButton, row, 2); row++; gridLayout->addWidget(WuQtUtilities::createHorizontalLineWidget(), row, 0, 1, 3); row++; gridLayout->addWidget(sceneLabel, row, 0, (Qt::AlignTop | Qt::AlignRight)); gridLayout->addWidget(sceneSelectionScrollArea, row, 1); gridLayout->addLayout(sceneButtonLayout, row, 2); row++; return widget; } /** * Called when a scene is dropped by the user dragging a scene in the list box */ void SceneDialog::sceneWasDropped() { // std::vector newlyOrderedScenes; // // /* // * Get the scenes from this list widget to obtain the new scene ordering. // */ // const int32_t numItems = m_sceneSelectionListWidget->count(); // for (int32_t i = 0; i < numItems; i++) { // QListWidgetItem* lwi = m_sceneSelectionListWidget->item(i); // if (lwi != NULL) { // if (lwi != NULL) { // Scene* scene = reinterpret_cast(qVariantValue(lwi->data(Qt::UserRole))); // newlyOrderedScenes.push_back(scene); // } // } // } // // if (newlyOrderedScenes.empty() == false) { // /* // * Update the order of the scenes in the scene file. // */ // SceneFile* sceneFile = getSelectedSceneFile(); // if (sceneFile != NULL) { // sceneFile->reorderScenes(newlyOrderedScenes); // sceneFileSelected(); // } // } } /** * Gets called to verify the content of the scene creation dialog. * @param sceneCreateDialog * Scene creation dialog. */ void SceneDialog::validateContentOfCreateSceneDialog(WuQDataEntryDialog* sceneCreateDialog) { CaretAssert(sceneCreateDialog); bool dataValid = true; QString errorMessage = ""; QWidget* sceneNameWidget = sceneCreateDialog->getDataWidgetWithName("sceneNameLineEdit"); if (sceneNameWidget != NULL) { QLineEdit* sceneNameLineEdit = dynamic_cast(sceneNameWidget); const AString sceneName = sceneNameLineEdit->text().trimmed(); if (sceneName.isEmpty()) { dataValid = false; if (errorMessage.isEmpty() == false) { errorMessage += "\n"; } errorMessage += ("Scene Name is empty."); } else { SceneFile* sceneFile = getSelectedSceneFile(); if (sceneFile != NULL) { const int32_t numScenes = sceneFile->getNumberOfScenes(); for (int32_t i = 0; i < numScenes; i++) { Scene* scene = sceneFile->getSceneAtIndex(i); if (scene->getName() == sceneName) { dataValid = false; if (errorMessage.isEmpty() == false) { errorMessage += "\n"; } errorMessage += ("An existing scene uses the name \"" + sceneName + "\". Scene names must be unique."); break; } } } } } sceneCreateDialog->setDataValid(dataValid, errorMessage); } /** * Slot that get called when a scene is highlighted (clicked). * * @param sceneIndex * Index of the scene. */ void SceneDialog::sceneHighlighted(const int32_t sceneIndex) { highlightSceneAtIndex(sceneIndex); } /** * Slot that get called when a scene is activated (double clicked). * * @param sceneIndex * Index of the scene. */ void SceneDialog::sceneActivated(const int32_t sceneIndex) { highlightSceneAtIndex(sceneIndex); showSceneButtonClicked(); } /** * Called when delete scene button clicked. */ void SceneDialog::deleteSceneButtonClicked() { Scene* scene = getSelectedScene(); if (scene != NULL) { const AString sceneName = scene->getName(); const AString msg = ("Are you sure you want to delete scene named: " + sceneName); if (WuQMessageBox::warningYesNo(m_deleteScenePushButton, msg)) { SceneFile* sceneFile = getSelectedSceneFile(); sceneFile->removeScene(scene); updateDialog(); } } } /** * Called when show scene button clicked. */ void SceneDialog::showSceneButtonClicked() { AString sceneFileName; SceneFile* sceneFile = getSelectedSceneFile(); if (sceneFile != NULL) { sceneFileName = sceneFile->getFileName(); } Scene* scene = getSelectedScene(); if (scene != NULL) { if (scene->hasFilesWithRemotePaths()) { const QString msg("This scene contains files that are on the network. " "If accessing the files requires a username and " "password, enter it here. Otherwise, remove any " "text from the username and password fields."); AString username; AString password; if (UsernamePasswordWidget::getUserNameAndPasswordInDialog(this, "Username and Password", msg, username, password)) { CaretDataFile::setFileReadingUsernameAndPassword(username, password); } else { return; } } ProgressReportingDialog progressDialog(("Restoring Scene " + scene->getName()), "", this); progressDialog.setValue(0); // ElapsedTimer timer; // timer.start(); displayScenePrivate(sceneFile, scene, false); // const AString msg = ("Time to load scene: " // + AString::number(timer.getElapsedTimeSeconds(), 'f', 3) // + " seconds."); // WuQMessageBox::informationOk(m_showScenePushButton, // msg); } } /** * Called when show image preview button is clicked. */ void SceneDialog::showImagePreviewButtonClicked() { const Scene* scene = getSelectedScene(); if (scene != NULL) { QByteArray imageByteArray; AString imageBytesFormat; scene->getSceneInfo()->getImageBytes(imageByteArray, imageBytesFormat); WuQDataEntryDialog ded(scene->getName(), m_showSceneImagePreviewPushButton, WuQDialog::SCROLL_AREA_AS_NEEDED); ded.setCancelButtonText(""); ded.setOkButtonText("Close"); try { if (imageByteArray.length() > 0) { ImageFile imageFile; imageFile.setImageFromByteArray(imageByteArray, imageBytesFormat); const QImage* image = imageFile.getAsQImage(); if (image != NULL) { if (image->isNull()) { CaretLogSevere("Preview image is invalid (isNull)"); } else { QLabel* imageLabel = new QLabel(); imageLabel->setPixmap(QPixmap::fromImage(*image)); ded.addWidget("", imageLabel); } } } } catch (const DataFileException& dfe) { CaretLogSevere("Converting preview to image: " + dfe.whatString()); } AString nameText; AString descriptionText; SceneClassInfoWidget::getFormattedTextForSceneNameAndDescription(scene->getSceneInfo(), nameText, descriptionText); QLabel* nameLabel = new QLabel(nameText); ded.addWidget("", nameLabel); if (! descriptionText.isEmpty()) { QLabel* descriptionLabel = new QLabel(descriptionText); descriptionLabel->setWordWrap(true); ded.addWidget("", descriptionLabel); } ded.exec(); } } /** * Display the given scene from the given scene file. * @param sceneFile * Scene file. * @param scene * Scene that is displayed. * @return * true if scene was displayed without error, else false. */ bool SceneDialog::displayScene(SceneFile* sceneFile, Scene* scene) { const bool isSuccessful = displayScenePrivate(sceneFile, scene, true); loadSceneFileComboBox(sceneFile); loadScenesIntoDialog(scene); return isSuccessful; } /** * Display the given scene from the given scene file. * @param sceneFile * Scene file. * @param scene * Scene that is displayed. * @return * true if scene was displayed without error, else false. */ bool SceneDialog::displayScenePrivate(SceneFile* sceneFile, Scene* scene, const bool showWaitCursor) { CaretAssert(sceneFile); CaretAssert(scene); const AString sceneFileName = sceneFile->getFileName(); const SceneClass* guiManagerClass = scene->getClassWithName("guiManager"); if (guiManagerClass->getName() != "guiManager") { WuQMessageBox::errorOk(this,"Top level scene class should be guiManager but it is: " + guiManagerClass->getName()); return false; } /* * Show the wait cursor */ CursorDisplayScoped cursor; if (showWaitCursor) { cursor.showWaitCursor(); } /* * Setup scene attributes */ SceneAttributes* sceneAttributes = scene->getAttributes(); sceneAttributes->clearErrorMessage(); sceneAttributes->setSceneFileName(sceneFileName); sceneAttributes->setSceneName(scene->getName()); sceneAttributes->setWindowRestoreBehaviorInSceneDisplay(SceneAttributes::RESTORE_WINDOW_POSITION_RELATIVE_TO_FIRST_AND_USE_SIZES); GuiManager::get()->restoreFromScene(sceneAttributes, guiManagerClass); cursor.restoreCursor(); const AString sceneErrorMessage = sceneAttributes->getErrorMessage(); if (sceneErrorMessage.isEmpty() == false) { WuQMessageBox::errorOk(this, sceneErrorMessage); return false; } return true; } /** * Receive events from the event manager. * * @param event * Event sent by event manager. */ void SceneDialog::receiveEvent(Event* event) { if (event->getEventType() == EventTypeEnum::EVENT_USER_INTERFACE_UPDATE) { EventUserInterfaceUpdate* uiEvent = dynamic_cast(event); CaretAssert(uiEvent); updateDialog(); uiEvent->setEventProcessed(); } } /* ======================================================================== */ /** * \class caret::SceneClassWidget * \brief Dialog for manipulation of scenes. * \ingroup GuiQt */ /** * Constructor. */ SceneClassInfoWidget::SceneClassInfoWidget() : QGroupBox(0) { m_scene = NULL; m_sceneIndex = -1; m_defaultBackgroundRole = backgroundRole(); m_defaultAutoFillBackgroundStatus = autoFillBackground(); m_nameLabel = new QLabel(); m_descriptionLabel = new QLabel(); m_descriptionLabel->setWordWrap(true); m_previewImageLabel = new QLabel(); m_previewImageLabel->setContentsMargins(0, 0, 0, 0); m_rightSideWidget = new QWidget(); QVBoxLayout* rightLayout = new QVBoxLayout(m_rightSideWidget); rightLayout->setContentsMargins(0, 0, 0, 0); rightLayout->addWidget(m_nameLabel); rightLayout->addSpacing(5); rightLayout->addWidget(m_descriptionLabel); rightLayout->addStretch(); m_leftSideWidget = new QWidget(); QVBoxLayout* leftLayout = new QVBoxLayout(m_leftSideWidget); leftLayout->setContentsMargins(0, 0, 0, 0); leftLayout->setSpacing(0); leftLayout->addWidget(m_previewImageLabel); leftLayout->addStretch(); WuQtUtilities::matchWidgetHeights(m_leftSideWidget, m_rightSideWidget); QHBoxLayout* layout = new QHBoxLayout(this); layout->setContentsMargins(0, 3, 0, 0); layout->setSpacing(3); layout->addWidget(m_leftSideWidget); layout->addWidget(m_rightSideWidget, 100); setSizePolicy(sizePolicy().horizontalPolicy(), QSizePolicy::Fixed); } /** * Destructor. */ SceneClassInfoWidget::~SceneClassInfoWidget() { } /** * Set/reset the background so that the widget appears to be * selected/unselected. * * @param selected * Selection status. */ void SceneClassInfoWidget::setBackgroundForSelected(const bool selected) { if (selected) { setAutoFillBackground(true); setBackgroundRole(QPalette::Highlight); } else { setAutoFillBackground(m_defaultAutoFillBackgroundStatus); setBackgroundRole(m_defaultBackgroundRole); } } /** * Update the content. * * @param scene * Scene for display. * @param sceneIndex * Index of the scene. */ void SceneClassInfoWidget::updateContent(Scene* scene, const int32_t sceneIndex) { m_scene = scene; m_sceneIndex = sceneIndex; if ((m_scene != NULL) && (m_sceneIndex >= 0)) { AString nameText; AString descriptionText; SceneClassInfoWidget::getFormattedTextForSceneNameAndDescription(scene->getSceneInfo(), nameText, descriptionText); m_nameLabel->setText(nameText); m_descriptionLabel->setText(descriptionText); QByteArray imageByteArray; AString imageBytesFormat; scene->getSceneInfo()->getImageBytes(imageByteArray, imageBytesFormat); const int previewImageWidth = 128; QImage previewImage; bool previewImageValid = false; if (imageByteArray.length() > 0) { try { ImageFile imageFile; imageFile.setImageFromByteArray(imageByteArray, imageBytesFormat); imageFile.resizeToWidth(previewImageWidth); previewImage = *imageFile.getAsQImage(); previewImageValid = true; } catch (const DataFileException& dfe) { CaretLogSevere(dfe.whatString()); } } m_previewImageLabel->clear(); m_previewImageLabel->setAlignment(Qt::AlignHCenter | Qt::AlignTop); if (previewImageValid) { m_previewImageLabel->setPixmap(QPixmap::fromImage(previewImage)); m_leftSideWidget->setMaximumHeight(1000); m_rightSideWidget->setMaximumHeight(m_leftSideWidget->sizeHint().height()); } else { m_previewImageLabel->setText("No preview
image"); int32_t maxHeight = std::max(m_leftSideWidget->sizeHint().height(), m_rightSideWidget->sizeHint().height()); maxHeight = std::min(maxHeight, previewImageWidth); m_leftSideWidget->setMaximumHeight(maxHeight); m_rightSideWidget->setMaximumHeight(maxHeight); } // const int maximumLabelSize = maximumPreviewImageSize + 8; // m_previewImageLabel->setFixedWidth(maximumLabelSize); } } /** * Get formatted text for display of scene name and description. * * @param sceneInfo * Info for the scene. * @param nameTextOut * Text for name. * @param desciptionTextOut * Text for description. */ void SceneClassInfoWidget::getFormattedTextForSceneNameAndDescription(const SceneInfo* sceneInfo, AString& nameTextOut, AString& descriptionTextOut) { CaretAssert(sceneInfo); AString name = sceneInfo->getName(); if (name.isEmpty()) { name = "NAME IS MISSING !!!"; } nameTextOut = ("NAME: " + name + ""); AString description = sceneInfo->getDescription(); if ( ! description.isEmpty()) { /* * HTML formatting is needed so text is properly displayed. * Want to put "Description" at beginning in bold but any * HTML tags are converted to text. So, after conversion to * HTML, perform a replace to insert "Description" in bold. */ const AString replaceWithDescriptionBoldText = "REPLACE_WITH_DESCRIPTION"; description = WuQtUtilities::createWordWrappedToolTipText(replaceWithDescriptionBoldText + description); description.replace(replaceWithDescriptionBoldText, "DESCRIPTION: "); } descriptionTextOut = description; } /** * Called by Qt when the mouse is pressed. * * @param event * The mouse event information. */ void SceneClassInfoWidget::mousePressEvent(QMouseEvent* event) { emit highlighted(m_sceneIndex); event->setAccepted(true); } /** * Called when the mouse is double clicked. * * @param event * The mouse event information. */ void SceneClassInfoWidget::mouseDoubleClickEvent(QMouseEvent* event) { emit activated(m_sceneIndex); event->setAccepted(true); } /** * @return The scene. */ Scene* SceneClassInfoWidget::getScene() { return m_scene; } /** * @return The index of the scene. */ int32_t SceneClassInfoWidget::getSceneIndex() const { return m_sceneIndex; } /** * @return True if this scene class info widget is valid (has * a scene with a valid index). */ bool SceneClassInfoWidget::isValid() const { if ((m_scene != NULL) && (m_sceneIndex >= 0)) { return true; } return false; } workbench-1.1.1/src/GuiQt/SceneDialog.h000066400000000000000000000131431255417355300176560ustar00rootroot00000000000000#ifndef __SCENE_DIALOG__H_ #define __SCENE_DIALOG__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "EventListenerInterface.h" #include "WuQDialogNonModal.h" class QCheckBox; class QComboBox; class QLabel; class QPushButton; class QVBoxLayout; namespace caret { class Scene; class SceneClassInfoWidget; class SceneFile; class SceneInfo; class WuQDataEntryDialog; class SceneDialog : public WuQDialogNonModal, public EventListenerInterface { Q_OBJECT public: SceneDialog(QWidget* parent = 0); virtual ~SceneDialog(); void updateDialog(); void receiveEvent(Event* event); bool displayScene(SceneFile* sceneFile, Scene* scene); private: SceneDialog(const SceneDialog&); SceneDialog& operator=(const SceneDialog&); private slots: void sceneFileSelected(); void newSceneFileButtonClicked(); void addNewSceneButtonClicked(); void deleteSceneButtonClicked(); void insertSceneButtonClicked(); void replaceSceneButtonClicked(); void showSceneButtonClicked(); void showImagePreviewButtonClicked(); void validateContentOfCreateSceneDialog(WuQDataEntryDialog*); void sceneWasDropped(); void sceneHighlighted(const int32_t sceneIndex); void sceneActivated(const int32_t sceneIndex); public: // ADD_NEW_METHODS_HERE private: SceneFile* getSelectedSceneFile(); Scene* getSelectedScene(); void loadSceneFileComboBox(SceneFile* selectedSceneFileIn); void loadScenesIntoDialog(Scene* selectedSceneIn); void addImageToScene(Scene* scene); void highlightSceneAtIndex(const int32_t sceneIndex); void highlightScene(const Scene* scene); QWidget* createMainPage(); bool displayScenePrivate(SceneFile* sceneFile, Scene* scene, const bool showWaitCursor); bool checkForModifiedFiles(); // ADD_NEW_MEMBERS_HERE QComboBox* m_sceneFileSelectionComboBox; QPushButton* m_addNewScenePushButton; QPushButton* m_insertNewScenePushButton; QPushButton* m_deleteScenePushButton; QPushButton* m_replaceScenePushButton; QPushButton* m_showScenePushButton; QPushButton* m_showSceneImagePreviewPushButton; QWidget* m_sceneSelectionWidget; QVBoxLayout* m_sceneSelectionLayout; std::vector m_sceneClassInfoWidgets; int32_t m_selectedSceneClassInfoIndex; static const AString PREFERRED_IMAGE_FORMAT; }; class SceneClassInfoWidget : public QGroupBox { Q_OBJECT public: SceneClassInfoWidget(); ~SceneClassInfoWidget(); void updateContent(Scene* scene, const int32_t sceneIndex); void setBackgroundForSelected(const bool selected); Scene* getScene(); int32_t getSceneIndex() const; bool isValid() const; static void getFormattedTextForSceneNameAndDescription(const SceneInfo* sceneInfo, AString& nameTextOut, AString& descriptionTextOut); signals: /** * Emited when user activates (double clicks) this widget. */ void activated(const int32_t sceneIndex); /** * Emited when user highlights (clicks) this widget. */ void highlighted(const int32_t sceneIndex); protected: virtual void mousePressEvent(QMouseEvent* event); virtual void mouseDoubleClickEvent(QMouseEvent* event); private: QWidget* m_leftSideWidget; QWidget* m_rightSideWidget; QLabel* m_previewImageLabel; QLabel* m_nameLabel; QLabel* m_descriptionLabel; Scene* m_scene; int32_t m_sceneIndex; bool m_previewImageValid; bool m_defaultAutoFillBackgroundStatus; QPalette::ColorRole m_defaultBackgroundRole; }; #ifdef __SCENE_DIALOG_DECLARE__ const AString SceneDialog::PREFERRED_IMAGE_FORMAT = "jpg"; #endif // __SCENE_DIALOG_DECLARE__ } // namespace #endif //__SCENE_DIALOG__H_ workbench-1.1.1/src/GuiQt/SceneWindowGeometry.cxx000066400000000000000000000256241255417355300220240ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #define __SCENE_WINDOW_GEOMETRY_DECLARE__ #include "SceneWindowGeometry.h" #undef __SCENE_WINDOW_GEOMETRY_DECLARE__ #include "BrainBrowserWindow.h" #include "SceneAttributes.h" #include "SceneClass.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::SceneWindowGeometry * \brief Assists with saving and restoring a window's geometry * \ingroup GuiQt */ /** * Constructor for window that is child of the first browser window. * @param window * The window for which geometry is restored/saved */ SceneWindowGeometry::SceneWindowGeometry(QWidget* window) : CaretObject() { m_window = window; m_parentWindow = NULL; } /** * Constructor for window that is child of the given parent window. * The window is ALWAYS positioned relative to the parent. * @param window * The window for which geometry is restored/saved * @param parentWindow * The parent window of 'window'. */ SceneWindowGeometry::SceneWindowGeometry(QWidget* window, QWidget* parentWindow) : CaretObject() { m_window = window; m_parentWindow = parentWindow; } /** * Destructor. */ SceneWindowGeometry::~SceneWindowGeometry() { } /** * Set the coordinates of the first browser window invalid. * Normally called at beginning of scene restoration prior * to restoration of windows. */ void SceneWindowGeometry::setFirstBrowserWindowCoordinatesInvalid() { s_firstBrowserWindowCoordinatesValid = false; s_firstBrowserWindow = NULL; } /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ SceneClass* SceneWindowGeometry::saveToScene(const SceneAttributes* /*sceneAttributes*/, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "SceneWindowGeometry", 1); /* * Position and size */ sceneClass->addInteger("geometryX", m_window->x()); sceneClass->addInteger("geometryY", m_window->y()); sceneClass->addInteger("geometryWidth", m_window->width()); sceneClass->addInteger("geometryHeight", m_window->height()); int32_t geometryOffsetX = 0; int32_t geometryOffsetY = 0; if (m_parentWindow != NULL) { geometryOffsetX = m_window->x() - m_parentWindow->x(); geometryOffsetY = m_window->y() - m_parentWindow->y(); } sceneClass->addInteger("geometryOffsetX", geometryOffsetX); sceneClass->addInteger("geometryOffsetY", geometryOffsetY); sceneClass->addBoolean("visibility", m_window->isVisible()); return sceneClass; } /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * SceneClass containing the state that was previously * saved and should be restored. */ void SceneWindowGeometry::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } /* * Get window geometry from the scene */ const int32_t sceneX = sceneClass->getIntegerValue("geometryX", 100); const int32_t sceneY = sceneClass->getIntegerValue("geometryY", 100); const int32_t sceneWidth = sceneClass->getIntegerValue("geometryWidth", -1); const int32_t sceneHeight = sceneClass->getIntegerValue("geometryHeight", -1); const bool isDialog = (dynamic_cast(m_window) != NULL); QDockWidget* dockWidget = dynamic_cast(m_window); const bool isDockWidget = (dockWidget != NULL); const bool isWindow = (dynamic_cast(m_window) != NULL); bool isWidget = true; if (isDialog || isDockWidget || isWindow) { isWidget = false; } /* * If it is a widget */ if (isWidget) { if ((sceneWidth > 0) && (sceneHeight > 0)) { m_window->resize(sceneWidth, sceneHeight); return; } } /* * If dock widget and not floating * just set size and exit */ if (isDockWidget && (dockWidget->isFloating() == false)) { if ((sceneWidth > 0) && (sceneHeight > 0)) { m_window->resize(sceneWidth, sceneHeight); return; } } /* * Is this window always positions relative to parent window? */ if (m_parentWindow != NULL) { /* * Get offset from parent */ const int32_t offsetX = sceneClass->getIntegerValue("geometryOffsetX", 100); const int32_t offsetY = sceneClass->getIntegerValue("geometryOffsetY", 100); const int32_t windowX = m_parentWindow->x() + offsetX; const int32_t windowY = m_parentWindow->y() + offsetY; /* * Move the window to its desired position and set its width/height * but limit to available screen space */ int32_t xywh[4]; WuQtUtilities::moveAndSizeWindow(m_window, windowX, windowY, sceneWidth, sceneHeight, xywh); return; } /* * Visibility */ const bool windowVisible = sceneClass->getBooleanValue("visibility", true); m_window->setVisible(windowVisible); /* * Is this a browser window? */ bool isFirstBrowserWindow = false; BrainBrowserWindow* browserWindow = dynamic_cast(m_window); if (browserWindow != NULL) { /* * Is this the first browser window being restored? * If so, save its positions from scene file and * its actual position. */ if (s_firstBrowserWindowCoordinatesValid == false) { isFirstBrowserWindow = true; } } /* * Determine how windows are positions and sized */ bool isResizeWindow = false; bool isMoveWindow = false; bool isMoveWindowRelative = false; switch (sceneAttributes->getRestoreWindowBehaviorInSceneDisplay()) { case SceneAttributes::RESTORE_WINDOW_USE_ALL_POSITIONS_AND_SIZES: isResizeWindow = true; isMoveWindow = true; break; case SceneAttributes::RESTORE_WINDOW_IGNORE_ALL_POSITIONS_AND_SIZES: break; case SceneAttributes::RESTORE_WINDOW_POSITION_RELATIVE_TO_FIRST_AND_USE_SIZES: if (isFirstBrowserWindow == false) { isMoveWindow = true; isMoveWindowRelative = true; } isResizeWindow = true; break; } // const QDesktopWidget* desktopWidget = QApplication::desktop(); // const QSize screenSize = WuQtUtilities::getMinimumScreenSize(); // const int maxX = screenSize.width() - 100; // const int maxY = screenSize.height() - 100; /* * Position of window defaults to window's current position */ int32_t windowX = m_window->x(); int32_t windowY = m_window->y(); if (isMoveWindow) { if ((sceneX > 0) & (sceneY > 0)) { /* * Use position in scene */ windowX = sceneX; windowY = sceneY; /* * Move position relative to first window? */ if (isMoveWindowRelative) { if ((s_firstBrowserWindowRestoredX > 0) && (s_firstBrowserWindowRestoredY > 0)) { const int32_t dx = windowX - s_firstBrowserWindowSceneX; const int32_t dy = windowY - s_firstBrowserWindowSceneY; windowX = s_firstBrowserWindowRestoredX + dx; windowY = s_firstBrowserWindowRestoredY + dy; } else { } } } } /* * NOT Resize window? */ int32_t windowWidth = sceneWidth; int32_t windowHeight = sceneHeight; if (isResizeWindow == false) { windowWidth = -1; windowHeight = -1; } /* * Move the window to its desired position and set its width/height * but limit to available screen space */ int32_t xywh[4]; WuQtUtilities::moveAndSizeWindow(m_window, windowX, windowY, windowWidth, windowHeight, xywh); /* * Is this a browser window? */ if (isFirstBrowserWindow) { s_firstBrowserWindowCoordinatesValid = true; s_firstBrowserWindow = browserWindow; s_firstBrowserWindowSceneX = sceneX; s_firstBrowserWindowSceneY = sceneY; s_firstBrowserWindowRestoredX = xywh[0]; s_firstBrowserWindowRestoredY = xywh[1]; } } workbench-1.1.1/src/GuiQt/SceneWindowGeometry.h000066400000000000000000000066371255417355300214540ustar00rootroot00000000000000#ifndef __SCENE_WINDOW_GEOMETRY__H_ #define __SCENE_WINDOW_GEOMETRY__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "SceneableInterface.h" class QWidget; namespace caret { class BrainBrowserWindow; class SceneAttributes; class SceneClass; class SceneWindowGeometry : public CaretObject, public SceneableInterface { public: SceneWindowGeometry(QWidget* window); SceneWindowGeometry(QWidget* window, QWidget* parentWindow); virtual ~SceneWindowGeometry(); virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); static void setFirstBrowserWindowCoordinatesInvalid(); private: SceneWindowGeometry(const SceneWindowGeometry&); SceneWindowGeometry& operator=(const SceneWindowGeometry&); /** The window whose geometry is restored/saved */ QWidget* m_window; /** Optional parent window (may be NULL) */ QWidget* m_parentWindow; /** X-coordinate of first browser window from the scene file */ static int32_t s_firstBrowserWindowSceneX; /** Y-coordinate of first browser window from the scene file */ static int32_t s_firstBrowserWindowSceneY; /** X-coordinate of first browser window after restoration from scene */ static int32_t s_firstBrowserWindowRestoredX; /** Y-coordinate of first browser window after restoration from scene */ static int32_t s_firstBrowserWindowRestoredY; /** First browser window */ static BrainBrowserWindow* s_firstBrowserWindow; /** Are first browser window coordinates valid */ static bool s_firstBrowserWindowCoordinatesValid; // ADD_NEW_MEMBERS_HERE }; #ifdef __SCENE_WINDOW_GEOMETRY_DECLARE__ BrainBrowserWindow* SceneWindowGeometry::s_firstBrowserWindow = NULL; int32_t SceneWindowGeometry::s_firstBrowserWindowSceneX = 0; int32_t SceneWindowGeometry::s_firstBrowserWindowSceneY = 0; int32_t SceneWindowGeometry::s_firstBrowserWindowRestoredX = 0; int32_t SceneWindowGeometry::s_firstBrowserWindowRestoredY = 0; bool SceneWindowGeometry::s_firstBrowserWindowCoordinatesValid = false; #endif // __SCENE_WINDOW_GEOMETRY_DECLARE__ } // namespace #endif //__SCENE_WINDOW_GEOMETRY__H_ workbench-1.1.1/src/GuiQt/SpecFileManagementDialog.cxx000066400000000000000000003034121255417355300226640ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SPEC_FILE_MANAGEMENT_DIALOG_DECLARE__ #include "SpecFileManagementDialog.h" #undef __SPEC_FILE_MANAGEMENT_DIALOG_DECLARE__ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Brain.h" #include "CaretAssert.h" #include "CaretDataFile.h" #include "CaretFileDialog.h" #include "CaretMappableDataFile.h" #include "CaretPreferences.h" #include "CursorDisplayScoped.h" #include "DataFileException.h" #include "EventDataFileRead.h" #include "EventDataFileReload.h" #include "EventGetDisplayedDataFiles.h" #include "EventGraphicsUpdateAllWindows.h" #include "EventManager.h" #include "EventSpecFileReadDataFiles.h" #include "EventSurfaceColoringInvalidate.h" #include "EventUserInterfaceUpdate.h" #include "FileInformation.h" #include "GuiManager.h" #include "MetaDataEditorDialog.h" #include "ProgressReportingDialog.h" #include "SessionManager.h" #include "SpecFile.h" #include "SpecFileDataFile.h" #include "SpecFileDataFileTypeGroup.h" #include "UsernamePasswordWidget.h" #include "WuQImageLabel.h" #include "WuQMessageBox.h" #include "WuQWidgetObjectGroup.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::SpecFileManagementDialog * \brief Dialog for operations with Spec Files. * \ingroup GuiQt */ /** * Run a dialog for opening a spec file. * * @param brain * Brain into which spec file is read. * @param specFile * Spec File. * @param parent * Parent of dialog. * @return * True if user loaded the spec file, else false. */ bool SpecFileManagementDialog::runOpenSpecFileDialog(Brain* brain, SpecFile* specFile, QWidget* parent) { CaretAssert(brain); CaretAssert(specFile); const AString title = ("Open Spec File: " + specFile->getFileNameNoPath()); SpecFileManagementDialog dialog(MODE_OPEN_SPEC_FILE, brain, specFile, title, parent); if (dialog.exec() == SpecFileManagementDialog::Accepted) { return true; } return false; } /** * Run a dialog for managing files in a brain. * * DO NOT delete the returned dialog as it will delete itself when closed. * * @param brain * Brain for which files are managed. * @param parent * Parent of dialog. */ void SpecFileManagementDialog::runManageFilesDialog(Brain* brain, QWidget* parent) { CaretAssert(brain); const AString title = ("Manage Data Files"); SpecFileManagementDialog dialog(MODE_MANAGE_FILES, brain, brain->getSpecFile(), title, parent); /* * Override view files type the first time the dialog is displayed * using the value from the user's preferences */ static bool firstTime = true; if (firstTime) { const CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); s_manageFilesViewFilesType = prefs->getManageFilesViewFileType(); firstTime = false; } dialog.setFilterSelections(s_manageFilesViewFilesType, s_manageFilesFilteredDataFileType, s_manageFilesFilteredStructureType); if ( ! s_manageFilesGeometry.isEmpty()) { dialog.restoreGeometry(s_manageFilesGeometry); } dialog.loadSpecFileContentIntoDialog(); dialog.exec(); dialog.getFilterSelections(s_manageFilesViewFilesType, s_manageFilesFilteredDataFileType, s_manageFilesFilteredStructureType); s_manageFilesGeometry = dialog.saveGeometry(); } /** * Run a dialog for saving files in a brain while exiting workbench. * * DO NOT delete the returned dialog as it will delete itself when closed. * * @param brain * Brain for which files are managed. * @param parent * Parent of dialog. * @return * true if workbench is allowed to exit, else false. */ bool SpecFileManagementDialog::runSaveFilesDialogWhileQuittingWorkbench(Brain* brain, QWidget* parent) { CaretAssert(brain); const AString title = ("Save Data Files"); SpecFileManagementDialog dialog(MODE_SAVE_FILES_WHILE_QUITTING, brain, brain->getSpecFile(), title, parent); if (dialog.exec()) { return true; } return false; } /** * Constructor. * * @param dialogMode * Mode of dialog. * @param brain * Brain * @param specFile * Spec File. * @param dialogTitle * Title for dialog. * @param parent * Parent of dialog. */ SpecFileManagementDialog::SpecFileManagementDialog(const Mode dialogMode, Brain* brain, SpecFile* specFile, const AString& dialogTitle, QWidget* parent) : WuQDialogModal(dialogTitle, parent), m_dialogMode(dialogMode), m_brain(brain), m_specFile(specFile) { /* * Initialize members */ m_filesTableWidget = NULL; m_fileSelectionActionGroup = NULL; m_manageFilesLoadedNotLoadedActionGroup = NULL; m_specFileDataFileCounter = 0; m_specFileTableRowIndex = -1; m_fileSorting = SpecFileManagementDialogRowContent::SORTING_TYPE_STRUCTURE_NAME; /* * Load icons. */ m_iconOpenFile = WuQtUtilities::loadIcon(":/SpecFileDialog/load_icon.png"); m_iconOptions = WuQtUtilities::loadIcon(":/SpecFileDialog/options_icon.png"); m_iconReloadFile = WuQtUtilities::loadIcon(":/SpecFileDialog/reload_icon.png"); m_iconCloseFile = WuQtUtilities::loadIcon(":/SpecFileDialog/delete_icon.png"); /* * Open Spec File or Manage Files? */ bool enableManageItems = false; bool enableOpenItems = false; switch (m_dialogMode) { case SpecFileManagementDialog::MODE_MANAGE_FILES: case SpecFileManagementDialog::MODE_SAVE_FILES_WHILE_QUITTING: m_specFile->setModifiedFilesSelectedForSaving(); enableManageItems = true; break; case SpecFileManagementDialog::MODE_OPEN_SPEC_FILE: enableOpenItems = true; break; } /* * Signal mappers for buttons */ m_fileReloadOrOpenFileActionSignalMapper = new QSignalMapper(this); QObject::connect(m_fileReloadOrOpenFileActionSignalMapper, SIGNAL(mapped(int)), this, SLOT(fileReloadOrOpenFileActionSelected(int))); m_fileOptionsActionSignalMapper = new QSignalMapper(this); QObject::connect(m_fileOptionsActionSignalMapper, SIGNAL(mapped(int)), this, SLOT(fileOptionsActionSelected(int))); m_fileCloseFileActionSignalMapper = new QSignalMapper(this); QObject::connect(m_fileCloseFileActionSignalMapper, SIGNAL(mapped(int)), this, SLOT(fileRemoveActionSelected(int))); int tableRowCounter = 0; /* * Is there a spec file? If so, set its table row index */ if (enableManageItems) { m_specFileTableRowIndex = tableRowCounter++; } bool testForDisplayedDataFiles = false; m_loadScenesPushButton = NULL; switch (m_dialogMode) { case SpecFileManagementDialog::MODE_MANAGE_FILES: setOkButtonText("Save Checked Files"); setCancelButtonText("Close"); testForDisplayedDataFiles = true; break; case SpecFileManagementDialog::MODE_OPEN_SPEC_FILE: setOkButtonText("Load"); setCancelButtonText("Cancel"); m_loadScenesPushButton = addUserPushButton("Load Scenes", QDialogButtonBox::AcceptRole); break; case SpecFileManagementDialog::MODE_SAVE_FILES_WHILE_QUITTING: setOkButtonText("Save Selected Files and Exit"); setCancelButtonText("Cancel"); testForDisplayedDataFiles = true; break; } if (testForDisplayedDataFiles) { EventGetDisplayedDataFiles displayedFilesEvent; EventManager::get()->sendEvent(displayedFilesEvent.getPointer()); m_displayedDataFiles = displayedFilesEvent.getDisplayedDataFiles(); } /* * Set column indices for table's members */ int columnCounter = 0; m_COLUMN_LOAD_CHECKBOX = -1; m_COLUMN_SAVE_CHECKBOX = -1; m_COLUMN_STATUS_LABEL = -1; m_COLUMN_DISPLAYED_LABEL = -1; m_COLUMN_IN_SPEC_FILE_CHECKBOX = -1; m_COLUMN_READ_BUTTON = -1; m_COLUMN_CLOSE_BUTTON = -1; m_COLUMN_OPTIONS_TOOLBUTTON = -1; m_COLUMN_DATA_FILE_TYPE_LABEL = -1; m_COLUMN_STRUCTURE = -1; m_COLUMN_FILE_NAME_LABEL = -1; m_COLUMN_COUNT = -1; if (enableOpenItems) { m_COLUMN_LOAD_CHECKBOX = columnCounter++; } if (enableManageItems) { m_COLUMN_SAVE_CHECKBOX = columnCounter++; m_COLUMN_STATUS_LABEL = columnCounter++; m_COLUMN_DISPLAYED_LABEL = columnCounter++; m_COLUMN_IN_SPEC_FILE_CHECKBOX = columnCounter++; m_COLUMN_READ_BUTTON = columnCounter++; m_COLUMN_CLOSE_BUTTON = columnCounter++; } m_COLUMN_OPTIONS_TOOLBUTTON = columnCounter++; m_COLUMN_DATA_FILE_TYPE_LABEL = columnCounter++; m_COLUMN_STRUCTURE = columnCounter++; m_COLUMN_FILE_NAME_LABEL = columnCounter++; m_COLUMN_COUNT = columnCounter++; /* * Get the content from the spec file. * It is examined when creating some of the toolbars. */ getDataFileContentFromSpecFile(); /* * Create the table */ m_filesTableWidget = new QTableWidget(); m_filesTableWidget->setAlternatingRowColors(true); m_filesTableWidget->setSelectionBehavior(QTableWidget::SelectItems); m_filesTableWidget->setSelectionMode(QTableWidget::SingleSelection); QObject::connect(m_filesTableWidget, SIGNAL(cellChanged(int,int)), this, SLOT(filesTableWidgetCellChanged(int,int))); QObject::connect(m_filesTableWidget->horizontalHeader(), SIGNAL(sectionClicked(int)), this, SLOT(horizontalHeaderSelectedForSorting(int))); const QString headerToolTip = ("Click on the column names (of those columns that contain text) to sort."); m_filesTableWidget->horizontalHeader()->setToolTip(WuQtUtilities::createWordWrappedToolTipText(headerToolTip)); /* * Widget and layout for files. * * Two layouts used so widget is pushed to the top left (and not * spread out) when groups of files are hidden. */ QWidget* centralWidget = NULL; centralWidget = m_filesTableWidget; m_filesTableWidget->resizeColumnsToContents(); m_filesTableWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); QWidget* toolbarWidget = new QWidget(); QVBoxLayout* toolbarLayout = new QVBoxLayout(toolbarWidget); WuQtUtilities::setLayoutSpacingAndMargins(toolbarLayout, 0, 0); QLabel* fileTypesToolBarLabel = NULL; toolbarLayout->addWidget(createFilesTypesToolBar(fileTypesToolBarLabel)); QLabel* fileStructureToolBarLabel = NULL; toolbarLayout->addWidget(createStructureToolBar(fileStructureToolBarLabel)); QLabel* fileSelectionToolBarLabel = NULL; QLabel* fileLoadedNotLoadedToolBarLabel = NULL; if (enableOpenItems) { toolbarLayout->addWidget(createFilesSelectionToolBar(fileSelectionToolBarLabel)); } else if (enableManageItems) { toolbarLayout->addWidget(createManageFilesLoadedNotLoadedToolBar(fileLoadedNotLoadedToolBarLabel)); } toolbarWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); /* * Make all of the toolbar labels have the same width to that * they line up nicely. Note that the match widgets widgets * method will ignore NULL values. */ WuQtUtilities::matchWidgetWidths(fileTypesToolBarLabel, fileStructureToolBarLabel, fileSelectionToolBarLabel, fileLoadedNotLoadedToolBarLabel); /* * No scrollbars in dialog since the table widget will have scroll bars */ // const bool enableScrollBars = false; setTopBottomAndCentralWidgets(toolbarWidget, centralWidget, NULL, WuQDialog::SCROLL_AREA_NEVER); /* * Display the data files. */ updateTableDimensionsToFitFiles(); loadSpecFileContentIntoDialog(); disableAutoDefaultForAllPushButtons(); // /* // * Table widget has a default size of 640 x 480. // * So estimate the size of the dialog with the table fully // * expanded. // */ // QHeaderView* columnHeader = m_filesTableWidget->horizontalHeader(); // const int columnHeaderHeight = columnHeader->sizeHint().height(); // // int dialogWidth = 10; // start out with a little extra space // int dialogHeight = 0; // // const int numRows = m_filesTableWidget->rowCount(); // const int numCols = m_filesTableWidget->columnCount(); // const int cellGap = 1; // if ((numRows > 0) // && (numCols > 0)) { // for (int i = 0; i < numCols; i++) { // dialogWidth += (m_filesTableWidget->columnWidth(i) + cellGap); // std::cout << "column width " << i << ": " << m_filesTableWidget->columnWidth(i) << std::endl; // } // // for (int i = 0; i < numRows; i++) { // dialogHeight += (m_filesTableWidget->rowHeight(i) + cellGap); // } // } const QSize tableSize = WuQtUtilities::estimateTableWidgetSize(m_filesTableWidget); int dialogWidth = std::max(tableSize.width(), toolbarWidget->sizeHint().width()); int dialogHeight = (toolbarWidget->sizeHint().height() + getDialogButtonBox()->sizeHint().height() + tableSize.height()); WuQtUtilities::resizeWindow(this, dialogWidth, dialogHeight); } /** * Destructor. */ SpecFileManagementDialog::~SpecFileManagementDialog() { clearSpecFileManagementDialogRowContent(); if (m_iconOpenFile != NULL) { delete m_iconOpenFile; } if (m_iconOptions != NULL) { delete m_iconOptions; } if (m_iconReloadFile != NULL) { delete m_iconReloadFile; } if (m_iconCloseFile != NULL) { delete m_iconCloseFile; } } /** * Clear content of all of the table row. */ void SpecFileManagementDialog::clearSpecFileManagementDialogRowContent() { const int32_t numRows = static_cast(m_tableRowDataFileContent.size()); for (int32_t i = 0; i < numRows; i++) { delete m_tableRowDataFileContent[i]; } m_tableRowDataFileContent.clear(); } /** * Get the info about the data files from the spec file. */ void SpecFileManagementDialog::getDataFileContentFromSpecFile() { clearSpecFileManagementDialogRowContent(); bool haveSceneFiles = false; /* * Display each type of data file */ const int32_t numGroups = m_specFile->getNumberOfDataFileTypeGroups(); for (int32_t ig = 0 ; ig < numGroups; ig++) { /* * File type of group */ SpecFileDataFileTypeGroup* group = m_specFile->getDataFileTypeGroupByIndex(ig); const DataFileTypeEnum::Enum dataFileType = group->getDataFileType(); const int32_t numFiles = group->getNumberOfFiles(); for (int iFile = 0; iFile < numFiles; iFile++) { SpecFileDataFile* sfdf = group->getFileInformation(iFile); /* * If the spec file entry is not a member of the spec file, * AND if it does not match a loaded file * AND the file does not exist * THEN the file likely was a duplicate (more than one copy * of a specific file was loaded with the same name) and * the user had removed it from Workbench. * * So, hide the file from view. */ if ( ! sfdf->isSpecFileMember()) { if (sfdf->getCaretDataFile() == NULL) { if (! sfdf->exists()) { continue; } } } SpecFileManagementDialogRowContent* rowContent = new SpecFileManagementDialogRowContent(group, sfdf); m_tableRowDataFileContent.push_back(rowContent); m_specFileDataFileCounter++; } if (dataFileType == DataFileTypeEnum::SCENE) { if (numFiles > 0) { haveSceneFiles = true; } } } if (m_loadScenesPushButton != NULL) { m_loadScenesPushButton->setEnabled(haveSceneFiles); } } /** * Called when the content of a cell changes. * Update corresponding item in the spec file. * * @param rowIndex * The row of the cell that was clicked. * @param columnIndex * The columnof the cell that was clicked. */ void SpecFileManagementDialog::filesTableWidgetCellChanged(int rowIndex, int columnIndex) { QTableWidgetItem* item = getTableWidgetItem(rowIndex, columnIndex); if (item != NULL) { /* * Is this the row containing the spec file? */ if (rowIndex == m_specFileTableRowIndex) { } else { SpecFileManagementDialogRowContent* rowContent = getFileContentInRow(rowIndex); CaretAssert(rowContent); SpecFileDataFile* sfdf = rowContent->m_specFileDataFile; const bool isSelected = WuQtUtilities::checkStateToBool(item->checkState()); if (columnIndex == m_COLUMN_SAVE_CHECKBOX) { sfdf->setSavingSelected(isSelected); } else if (columnIndex == m_COLUMN_LOAD_CHECKBOX) { sfdf->setLoadingSelected(isSelected); } else if (columnIndex == m_COLUMN_IN_SPEC_FILE_CHECKBOX) { sfdf->setSpecFileMember(isSelected); updateSpecFileRowInTable(); /* * Check the Spec File SAVE checkbox */ if (m_specFileTableRowIndex >= 0) { QTableWidgetItem* saveItem = getTableWidgetItem(m_specFileTableRowIndex, m_COLUMN_SAVE_CHECKBOX); saveItem->setCheckState(Qt::Checked); } } } } enableLoadOrSaveButton(); } void SpecFileManagementDialog::enableLoadOrSaveButton() { bool isAnyFileSelected = false; if (m_specFile != NULL) { switch (m_dialogMode) { case MODE_MANAGE_FILES: case MODE_SAVE_FILES_WHILE_QUITTING: { if (m_specFile->getNumberOfFilesSelectedForSaving() > 0) { isAnyFileSelected = true; } QTableWidgetItem* specCheckItem = getTableWidgetItem(m_specFileTableRowIndex, m_COLUMN_SAVE_CHECKBOX); if (WuQtUtilities::checkStateToBool(specCheckItem->checkState())) { isAnyFileSelected = true; } } break; case MODE_OPEN_SPEC_FILE: if (m_specFile->getNumberOfFilesSelectedForLoading() > 0) { isAnyFileSelected = true; } break; } } QAbstractButton* okButton = getDialogButtonBox()->button(QDialogButtonBox::Ok); CaretAssert(okButton); okButton->setEnabled(isAnyFileSelected); } /** * Get the table widget item at the given row and column. If compiled * debug the assertions will fail if the row or column is invalid. * * @param rowIndex * The row of the desired cell. * @param columnIndex * The column of the desired cell. * @return * item at row and column. */ QTableWidgetItem* SpecFileManagementDialog::getTableWidgetItem(const int rowIndex, const int columnIndex) { CaretAssert((rowIndex >= 0) && (rowIndex < m_filesTableWidget->rowCount())); CaretAssert((columnIndex >= 0) && (columnIndex < m_filesTableWidget->columnCount())); return m_filesTableWidget->item(rowIndex, columnIndex); } /** * Set the table widget item at the given row and column. If compiled * debug the assertions will fail if the row or column is invalid. * * @param rowIndex * The row of the desired cell. * @param columnIndex * The column of the desired cell. * @param item * The item to add. */ void SpecFileManagementDialog::setTableWidgetItem(const int rowIndex, const int columnIndex, QTableWidgetItem* item) { CaretAssert(item); CaretAssert((rowIndex >= 0) && (rowIndex < m_filesTableWidget->rowCount())); CaretAssert((columnIndex >= 0) && (columnIndex < m_filesTableWidget->columnCount())); m_filesTableWidget->blockSignals(true); m_filesTableWidget->setItem(rowIndex, columnIndex, item); m_filesTableWidget->blockSignals(false); } /** * Set the labels for the column names in the table. */ void SpecFileManagementDialog::setTableColumnLabels() { /* * Set names of table's columns */ if (m_COLUMN_LOAD_CHECKBOX >= 0){ m_filesTableWidget->setHorizontalHeaderItem(m_COLUMN_LOAD_CHECKBOX, createHeaderTextItem("Load")); } if (m_COLUMN_SAVE_CHECKBOX >= 0){ m_filesTableWidget->setHorizontalHeaderItem(m_COLUMN_SAVE_CHECKBOX, createHeaderTextItem("Save")); } if (m_COLUMN_STATUS_LABEL >= 0){ m_filesTableWidget->setHorizontalHeaderItem(m_COLUMN_STATUS_LABEL, createHeaderTextItem("Modified")); } if (m_COLUMN_DISPLAYED_LABEL >= 0) { m_filesTableWidget->setHorizontalHeaderItem(m_COLUMN_DISPLAYED_LABEL, createHeaderTextItem("Displayed")); } if (m_COLUMN_IN_SPEC_FILE_CHECKBOX >= 0){ m_filesTableWidget->setHorizontalHeaderItem(m_COLUMN_IN_SPEC_FILE_CHECKBOX, createHeaderTextItem("In Spec")); } if (m_COLUMN_READ_BUTTON >= 0){ m_filesTableWidget->setHorizontalHeaderItem(m_COLUMN_READ_BUTTON, createHeaderTextItem("Read")); } if (m_COLUMN_CLOSE_BUTTON >= 0) { m_filesTableWidget->setHorizontalHeaderItem(m_COLUMN_CLOSE_BUTTON, createHeaderTextItem("Close")); } if (m_COLUMN_OPTIONS_TOOLBUTTON >= 0){ m_filesTableWidget->setHorizontalHeaderItem(m_COLUMN_OPTIONS_TOOLBUTTON, createHeaderTextItem("More")); } if (m_COLUMN_DATA_FILE_TYPE_LABEL >= 0){ m_filesTableWidget->setHorizontalHeaderItem(m_COLUMN_DATA_FILE_TYPE_LABEL, createHeaderTextItem("Data Type")); } if (m_COLUMN_STRUCTURE >= 0){ m_filesTableWidget->setHorizontalHeaderItem(m_COLUMN_STRUCTURE, createHeaderTextItem("Structure")); } if (m_COLUMN_FILE_NAME_LABEL >= 0){ m_filesTableWidget->setHorizontalHeaderItem(m_COLUMN_FILE_NAME_LABEL, createHeaderTextItem("Data File Name")); } } /** * @return A table widget item used in the column header. * * @param text * Text for the item. * @return Table widget item containing the given text. */ QTableWidgetItem* SpecFileManagementDialog::createHeaderTextItem(const QString& text) { QList sizesList = QFontDatabase::standardSizes(); std::sort(sizesList.begin(), sizesList.end()); QTableWidgetItem* item = createTextItem(); item->setText(text); QFont font = item->font(); QListIterator sizeIter(sizesList); while (sizeIter.hasNext()) { const int nextSize = sizeIter.next(); if (nextSize > font.pointSize()) { font.setPointSize(nextSize); break; } } font.setBold(true); item->setFont(font); return item; } /** * Load items into the table widget adding rows as needed. */ void SpecFileManagementDialog::updateTableDimensionsToFitFiles() { /* * If needed, add a row for the spec file */ m_specFileTableRowIndex = -1; int numberOfRows = 0; switch (m_dialogMode) { case MODE_MANAGE_FILES: case MODE_SAVE_FILES_WHILE_QUITTING: m_specFileTableRowIndex = numberOfRows; numberOfRows++; break; case MODE_OPEN_SPEC_FILE: break; } /* * Update rows indices for data files */ const int32_t numFiles = static_cast(m_tableRowDataFileContent.size()); for (int32_t i = 0; i < numFiles; i++) { m_tableRowDataFileContent[i]->m_tableRowIndex = numberOfRows; numberOfRows++; } /* * If the number of rows has not changed, no need to update table dimensions */ bool needToCreateColumnHeaders = false; if (numberOfRows == m_filesTableWidget->rowCount()) { return; } else if (m_filesTableWidget->rowCount() == 0) { needToCreateColumnHeaders = true; } /* * Update the dimensions of the table */ const int32_t firstNewRowIndex = m_filesTableWidget->rowCount(); m_filesTableWidget->setRowCount(numberOfRows); m_filesTableWidget->setColumnCount(m_COLUMN_COUNT); m_filesTableWidget->verticalHeader()->hide(); m_filesTableWidget->setGridStyle(Qt::NoPen); m_filesTableWidget->setSortingEnabled(false); if (needToCreateColumnHeaders) { setTableColumnLabels(); } const int32_t lastNewRowIndex = m_filesTableWidget->rowCount(); // value changed by setRowCount() /* * Add new cells to the table widget */ for (int32_t iRow = firstNewRowIndex; iRow < lastNewRowIndex; iRow++) { const bool isDataFileRow = (m_specFileTableRowIndex != iRow); if (isDataFileRow) { if (m_COLUMN_LOAD_CHECKBOX >= 0) { QTableWidgetItem* item = createCheckableItem(); item->setTextAlignment(Qt::AlignHCenter); setTableWidgetItem(iRow, m_COLUMN_LOAD_CHECKBOX, item); } } if (m_COLUMN_SAVE_CHECKBOX >= 0) { QTableWidgetItem* item = createCheckableItem(); item->setTextAlignment(Qt::AlignHCenter); setTableWidgetItem(iRow, m_COLUMN_SAVE_CHECKBOX, item); } if (m_COLUMN_STATUS_LABEL >= 0) { QTableWidgetItem* item = createTextItem(); setTableWidgetItem(iRow, m_COLUMN_STATUS_LABEL, item); /* * Text for modified cell is always red and centered */ item->setTextAlignment(Qt::AlignCenter); QBrush modifiedBrush = item->foreground(); modifiedBrush.setColor(Qt::red); item->setForeground(modifiedBrush); } if (m_COLUMN_DISPLAYED_LABEL >= 0) { QTableWidgetItem* item = createTextItem(); setTableWidgetItem(iRow, m_COLUMN_DISPLAYED_LABEL, item); item->setTextAlignment(Qt::AlignCenter); } if (isDataFileRow) { if (m_COLUMN_IN_SPEC_FILE_CHECKBOX >= 0) { QTableWidgetItem* item = createCheckableItem(); item->setTextAlignment(Qt::AlignHCenter); setTableWidgetItem(iRow, m_COLUMN_IN_SPEC_FILE_CHECKBOX, item); } if (m_COLUMN_READ_BUTTON >= 0) { WuQImageLabel* loadImageLabel = new WuQImageLabel(m_iconReloadFile, "Reload"); QObject::connect(loadImageLabel, SIGNAL(clicked()), m_fileReloadOrOpenFileActionSignalMapper, SLOT(map())); m_fileReloadOrOpenFileActionSignalMapper->setMapping(loadImageLabel, iRow); m_filesTableWidget->setCellWidget(iRow, m_COLUMN_READ_BUTTON, loadImageLabel); } if (m_COLUMN_CLOSE_BUTTON >= 0) { WuQImageLabel* closeImageLabel = new WuQImageLabel(m_iconCloseFile, "Close"); QObject::connect(closeImageLabel, SIGNAL(clicked()), m_fileCloseFileActionSignalMapper, SLOT(map())); m_fileCloseFileActionSignalMapper->setMapping(closeImageLabel, iRow); m_filesTableWidget->setCellWidget(iRow, m_COLUMN_CLOSE_BUTTON, closeImageLabel); } } if (m_COLUMN_OPTIONS_TOOLBUTTON >= 0) { WuQImageLabel* optionsImageLabel = new WuQImageLabel(m_iconOptions, "Options"); QObject::connect(optionsImageLabel, SIGNAL(clicked()), m_fileOptionsActionSignalMapper, SLOT(map())); m_fileOptionsActionSignalMapper->setMapping(optionsImageLabel, iRow); m_filesTableWidget->setCellWidget(iRow, m_COLUMN_OPTIONS_TOOLBUTTON, optionsImageLabel); } if (m_COLUMN_DATA_FILE_TYPE_LABEL >= 0) { setTableWidgetItem(iRow, m_COLUMN_DATA_FILE_TYPE_LABEL, createTextItem()); } if (m_COLUMN_STRUCTURE >= 0) { setTableWidgetItem(iRow, m_COLUMN_STRUCTURE, createTextItem()); } if (m_COLUMN_FILE_NAME_LABEL >= 0) { setTableWidgetItem(iRow, m_COLUMN_FILE_NAME_LABEL, createTextItem()); } } } /** * Update the table row containing the spec file. */ void SpecFileManagementDialog::updateSpecFileRowInTable() { bool isUpdateRow = false; switch (m_dialogMode) { case MODE_MANAGE_FILES: case MODE_SAVE_FILES_WHILE_QUITTING: isUpdateRow = true; break; case MODE_OPEN_SPEC_FILE: break; } /* * Update spec file data */ if (isUpdateRow) { if (m_specFileTableRowIndex >= 0) { CaretAssert(m_COLUMN_DATA_FILE_TYPE_LABEL >= 0); QTableWidgetItem* dataTypeItem = getTableWidgetItem(m_specFileTableRowIndex, m_COLUMN_DATA_FILE_TYPE_LABEL); dataTypeItem->setText(getEditedDataFileTypeName(DataFileTypeEnum::SPECIFICATION)); // CaretAssert(m_COLUMN_SAVE_CHECKBOX >= 0); // QTableWidgetItem* saveItem = getTableWidgetItem(m_specFileTableRowIndex, // m_COLUMN_SAVE_CHECKBOX); // CaretAssert(saveItem); // // saveItem->setCheckState(WuQtUtilities::boolToCheckState(m_specFile->is())); CaretAssert(m_COLUMN_FILE_NAME_LABEL >= 0); QTableWidgetItem* nameItem = getTableWidgetItem(m_specFileTableRowIndex, m_COLUMN_FILE_NAME_LABEL); CaretAssert(nameItem); const AString specFileName = m_specFile->getFileName(); AString path; AString name; if (specFileName.isEmpty() == false) { FileInformation fileInfo(specFileName); path = fileInfo.getAbsolutePath(); name = fileInfo.getFileName(); } nameItem->setText(name); nameItem->setToolTip(path); CaretAssert(m_COLUMN_STATUS_LABEL >= 0); QTableWidgetItem* statusItem = getTableWidgetItem(m_specFileTableRowIndex, m_COLUMN_STATUS_LABEL); CaretAssert(statusItem); if (m_specFile->isModified()) { statusItem->setText("YES"); } else { statusItem->setText(""); } // /* // * Hide in Spec Check Box // */ // CaretAssert(m_COLUMN_IN_SPEC_FILE_CHECKBOX); // QTableWidgetItem* inSpecItem = getTableWidgetItem(m_specFileTableRowIndex, // m_COLUMN_IN_SPEC_FILE_CHECKBOX); // inSpecItem->setFlags(inSpecItem->flags() // & (~Qt::ItemIsEnabled)); // inSpecItem->setFlags(inSpecItem->flags() // & (~Qt::ItemIsUserCheckable)); /* * Get filtering selections */ SpecFileDialogViewFilesTypeEnum::Enum viewFilesType; DataFileTypeEnum::Enum filteredDataFileType; StructureEnum::Enum filteredStructureType; getFilterSelections(viewFilesType, filteredDataFileType, filteredStructureType); bool hideSpecFileRow = false; if ((filteredStructureType != StructureEnum::ALL) && (filteredStructureType != StructureEnum::OTHER)) { hideSpecFileRow = true; } switch (viewFilesType) { case SpecFileDialogViewFilesTypeEnum::VIEW_FILES_ALL: break; case SpecFileDialogViewFilesTypeEnum::VIEW_FILES_LOADED: break; case SpecFileDialogViewFilesTypeEnum::VIEW_FILES_LOADED_MODIFIED: if (! m_specFile->isModified()) { hideSpecFileRow = true; } break; case SpecFileDialogViewFilesTypeEnum::VIEW_FILES_LOADED_NOT_MODIFIED: if (m_specFile->isModified()) { hideSpecFileRow = true; } break; case SpecFileDialogViewFilesTypeEnum::VIEW_FILES_NOT_LOADED: hideSpecFileRow = true; break; } if (filteredDataFileType != DataFileTypeEnum::UNKNOWN) { hideSpecFileRow = true; } m_filesTableWidget->setRowHidden(m_specFileTableRowIndex, hideSpecFileRow); } } } /** * Gets called when a column header is selected. * @param logicalIndex * Index of item that was selected. */ void SpecFileManagementDialog::horizontalHeaderSelectedForSorting(int logicalIndex) { if (logicalIndex == m_COLUMN_DATA_FILE_TYPE_LABEL) { m_fileSorting = SpecFileManagementDialogRowContent::SORTING_TYPE_STRUCTURE_NAME; loadSpecFileContentIntoDialog(); } else if (logicalIndex == m_COLUMN_FILE_NAME_LABEL) { m_fileSorting = SpecFileManagementDialogRowContent::SORTING_NAME; loadSpecFileContentIntoDialog(); } else if (logicalIndex == m_COLUMN_STRUCTURE) { m_fileSorting = SpecFileManagementDialogRowContent::SORTING_STRUCTURE_TYPE_NAME; loadSpecFileContentIntoDialog(); } } /** * Get the file filtering selections * * @param viewFilesTypeOut * view files types * @param filteredDataFileTypeOut * Data file type * @param filteredStructureTypeOut * Structure */ void SpecFileManagementDialog::getFilterSelections(SpecFileDialogViewFilesTypeEnum::Enum& viewFilesTypeOut, DataFileTypeEnum::Enum& filteredDataFileTypeOut, StructureEnum::Enum& filteredStructureTypeOut) const { /* * All/Loaded/Not-Loaded */ viewFilesTypeOut = SpecFileDialogViewFilesTypeEnum::VIEW_FILES_ALL; if (m_manageFilesLoadedNotLoadedActionGroup != NULL) { const QAction* manageFileAction = m_manageFilesLoadedNotLoadedActionGroup->checkedAction(); if (manageFileAction != NULL) { viewFilesTypeOut = SpecFileDialogViewFilesTypeEnum::fromIntegerCode(manageFileAction->data().toInt(), NULL); } } /* * Filter for data file type selection */ filteredDataFileTypeOut = DataFileTypeEnum::UNKNOWN; const QAction* dataFileTypeAction = m_fileTypesActionGroup->checkedAction(); if (dataFileTypeAction != NULL) { const int dataFileTypeInt = dataFileTypeAction->data().toInt(); filteredDataFileTypeOut = DataFileTypeEnum::fromIntegerCode(dataFileTypeInt, NULL); } /* * Filter for structure */ filteredStructureTypeOut = StructureEnum::ALL; const QAction* filteredStructureAction = m_structureActionGroup->checkedAction(); if (filteredStructureAction != NULL) { const int structureTypeInt = filteredStructureAction->data().toInt(); filteredStructureTypeOut = StructureEnum::fromIntegerCode(structureTypeInt, NULL); } } /** * Set the file filtering selections * * @param viewFilesType * View files type * @param filteredDataFileType * Data file type * @param filteredStructureType * Structure */ void SpecFileManagementDialog::setFilterSelections(const SpecFileDialogViewFilesTypeEnum::Enum viewFilesType, const DataFileTypeEnum::Enum filteredDataFileType, const StructureEnum::Enum filteredStructureType) { const int32_t viewFilesTypeInt = SpecFileDialogViewFilesTypeEnum::toIntegerCode(viewFilesType); QList manageActions = m_manageFilesLoadedNotLoadedActionGroup->actions(); QListIterator manageIterator(manageActions); while (manageIterator.hasNext()) { QAction* action = manageIterator.next(); if (action->data().toInt() == viewFilesTypeInt) { action->setChecked(true); break; } } const int32_t dataFileTypeInt = DataFileTypeEnum::toIntegerCode(filteredDataFileType); QList fileTypeActions = m_fileTypesActionGroup->actions(); QListIterator fileTypeIterator(fileTypeActions); while (fileTypeIterator.hasNext()) { QAction* action = fileTypeIterator.next(); if (action->data() == dataFileTypeInt) { action->setChecked(true); break; } } const int32_t structureTypeInt = StructureEnum::toIntegerCode(filteredStructureType); QList structureTypeActions = m_structureActionGroup->actions(); QListIterator structureTypeIterator(structureTypeActions); while (structureTypeIterator.hasNext()) { QAction* action = structureTypeIterator.next(); if (action->data() == structureTypeInt) { action->setChecked(true); break; } } } ///** // * Less than method for sorting using the sorting key. // * // * @param item1 // * Tested for less than the item2 // * @param item2 // * Tested for greater than the item1 // * @return // * True if item1 is less than item2, else false. // */ //bool //lessThanForSorting(const SpecFileManagementDialogRowContent *item1, // const SpecFileManagementDialogRowContent* item2) //{ // return (item1->m_sortingKey < item2->m_sortingKey); //} /** * Sort the file content. */ void SpecFileManagementDialog::sortFileContent() { /* * Update key used for sorting */ const int32_t numDataFiles = static_cast(m_tableRowDataFileContent.size()); for (int32_t i = 0; i < numDataFiles; i++) { m_tableRowDataFileContent[i]->setSortingKey(m_fileSorting); } /* * Sort */ std::sort(m_tableRowDataFileContent.begin(), m_tableRowDataFileContent.end(), SpecFileManagementDialogRowContent::lessThanForSorting); /* * Update row indices in table */ int rowCounter = 0; if (m_specFileTableRowIndex >= 0) { rowCounter = 1; } for (int32_t i = 0; i < numDataFiles; i++) { m_tableRowDataFileContent[i]->m_tableRowIndex= rowCounter; rowCounter++; } } /** * Load the spec file data into the dialog. */ void SpecFileManagementDialog::loadSpecFileContentIntoDialog() { /* * Disable signals so cell changed signal not emitted while * modifying table content. */ m_filesTableWidget->blockSignals(true); /* * Sort the rows */ sortFileContent(); SpecFileDialogViewFilesTypeEnum::Enum filteredViewFilesType; DataFileTypeEnum::Enum filteredDataFileType; StructureEnum::Enum filteredStructureType; getFilterSelections(filteredViewFilesType, filteredDataFileType, filteredStructureType); updateSpecFileRowInTable(); /* * Load all of the data file content. */ const int32_t numDataFiles = static_cast(m_tableRowDataFileContent.size()); for (int32_t i = 0; i < numDataFiles; i++) { const int rowIndex = m_tableRowDataFileContent[i]->m_tableRowIndex; CaretAssert((rowIndex >= 0) && (rowIndex < m_filesTableWidget->rowCount())); SpecFileDataFile* specFileDataFile = m_tableRowDataFileContent[i]->m_specFileDataFile; CaretDataFile* caretDataFile = specFileDataFile->getCaretDataFile(); const StructureEnum::Enum structure = specFileDataFile->getStructure(); bool isFileSavable = false; if (caretDataFile != NULL) { if (caretDataFile->supportsWriting()) { isFileSavable = true; } } const DataFileTypeEnum::Enum dataFileType = specFileDataFile->getDataFileType(); switch (m_dialogMode) { case MODE_MANAGE_FILES: case MODE_SAVE_FILES_WHILE_QUITTING: { /* * Save checkbox */ CaretAssert(m_COLUMN_SAVE_CHECKBOX >= 0); QTableWidgetItem* saveItem = getTableWidgetItem(rowIndex, m_COLUMN_SAVE_CHECKBOX); CaretAssert(saveItem); if (isFileSavable) { saveItem->setFlags(saveItem->flags() | Qt::ItemIsEnabled); saveItem->setCheckState(WuQtUtilities::boolToCheckState(specFileDataFile->isSavingSelected())); } else { saveItem->setFlags(saveItem->flags() & (~ Qt::ItemIsEnabled)); saveItem->setCheckState(Qt::Unchecked); } /* * Status label. */ CaretAssert(m_COLUMN_STATUS_LABEL >= 0); QTableWidgetItem* statusItem = getTableWidgetItem(rowIndex, m_COLUMN_STATUS_LABEL); CaretAssert(statusItem); statusItem->setText(""); if (caretDataFile != NULL) { if (caretDataFile->isModified()) { if (isFileSavable) { statusItem->setText("YES"); /* * Is this a Caret Mappable Data file and is the modification * only in the palette color mapping? */ CaretMappableDataFile* mapFile = dynamic_cast(caretDataFile); if (mapFile != NULL) { /* * Is modification just the palette color mapping? */ if (mapFile->isModifiedPaletteColorMapping()) { if ( ! mapFile->isModifiedExcludingPaletteColorMapping()) { statusItem->setText("PALETTE"); } } } } } } /* * Displayed label. */ CaretAssert(m_COLUMN_DISPLAYED_LABEL >= 0); QTableWidgetItem* displayedItem = getTableWidgetItem(rowIndex, m_COLUMN_DISPLAYED_LABEL); CaretAssert(displayedItem); displayedItem->setText(""); if (caretDataFile != NULL) { if (m_displayedDataFiles.find(caretDataFile) != m_displayedDataFiles.end()) { displayedItem->setText("YES"); } } /* * In-spec checkbox */ CaretAssert(m_COLUMN_IN_SPEC_FILE_CHECKBOX >= 0); QTableWidgetItem* inSpecItem = getTableWidgetItem(rowIndex, m_COLUMN_IN_SPEC_FILE_CHECKBOX); CaretAssert(inSpecItem); inSpecItem->setCheckState(WuQtUtilities::boolToCheckState(specFileDataFile->isSpecFileMember())); /* * Read button */ CaretAssert(m_COLUMN_READ_BUTTON >= 0); QWidget* readCellWidget = m_filesTableWidget->cellWidget(rowIndex, m_COLUMN_READ_BUTTON); CaretAssert(readCellWidget); WuQImageLabel* readImageLabel = dynamic_cast(readCellWidget); CaretAssert(readImageLabel); if (caretDataFile != NULL) { readImageLabel->updateIconText(m_iconReloadFile, "Reload"); readImageLabel->setToolTip("Reload this file"); } else { readImageLabel->updateIconText(m_iconOpenFile, "Load"); readImageLabel->setToolTip("Load this file"); } } break; case MODE_OPEN_SPEC_FILE: { /* * Load checkbox */ CaretAssert(m_COLUMN_LOAD_CHECKBOX >= 0); QTableWidgetItem* loadItem = getTableWidgetItem(rowIndex, m_COLUMN_LOAD_CHECKBOX); CaretAssert(loadItem); loadItem->setCheckState(WuQtUtilities::boolToCheckState(specFileDataFile->isLoadingSelected())); } break; } /* * Data file type label */ CaretAssert(m_COLUMN_DATA_FILE_TYPE_LABEL >= 0); QTableWidgetItem* dataTypeItem = getTableWidgetItem(rowIndex, m_COLUMN_DATA_FILE_TYPE_LABEL); CaretAssert(dataTypeItem); dataTypeItem->setText(SpecFileManagementDialog::getEditedDataFileTypeName(dataFileType)); /* * Structure label */ CaretAssert(m_COLUMN_STRUCTURE >= 0); QTableWidgetItem* structureItem = getTableWidgetItem(rowIndex, m_COLUMN_STRUCTURE); CaretAssert(structureItem); structureItem->setText(""); if (DataFileTypeEnum::isFileUsedWithOneStructure(dataFileType)) { structureItem->setText(StructureEnum::toGuiName(specFileDataFile->getStructure())); } /* * File name and path */ CaretAssert(m_COLUMN_FILE_NAME_LABEL >= 0); QTableWidgetItem* nameItem = getTableWidgetItem(rowIndex, m_COLUMN_FILE_NAME_LABEL); CaretAssert(nameItem); FileInformation fileInfo(specFileDataFile->getFileName()); const AString path = fileInfo.getAbsolutePath(); const AString name = fileInfo.getFileName(); nameItem->setText(name); nameItem->setToolTip(path); /* * Should file (row) be hidden? */ bool isFileHidden = false; if (filteredDataFileType != DataFileTypeEnum::UNKNOWN) { if (dataFileType != filteredDataFileType) { isFileHidden = true; } } if (filteredStructureType != StructureEnum::ALL) { switch (filteredStructureType) { case StructureEnum::CORTEX_LEFT: if (structure != StructureEnum::CORTEX_LEFT) { isFileHidden = true; } break; case StructureEnum::CORTEX_RIGHT: if (structure != StructureEnum::CORTEX_RIGHT) { isFileHidden = true; } break; case StructureEnum::CEREBELLUM: case StructureEnum::CEREBELLUM_LEFT: case StructureEnum::CEREBELLUM_RIGHT: if (structure != StructureEnum::CEREBELLUM) { isFileHidden = true; } break; case StructureEnum::OTHER: { switch (structure) { case StructureEnum::CORTEX_LEFT: case StructureEnum::CORTEX_RIGHT: case StructureEnum::CEREBELLUM: case StructureEnum::CEREBELLUM_LEFT: case StructureEnum::CEREBELLUM_RIGHT: isFileHidden = true; break; default: break; } } break; default: break; } } switch (filteredViewFilesType) { case SpecFileDialogViewFilesTypeEnum::VIEW_FILES_ALL: break; case SpecFileDialogViewFilesTypeEnum::VIEW_FILES_LOADED: if (caretDataFile == NULL) { isFileHidden = true; } break; case SpecFileDialogViewFilesTypeEnum::VIEW_FILES_LOADED_MODIFIED: if (caretDataFile != NULL) { if ( ! caretDataFile->isModified()) { isFileHidden = true; } } else { isFileHidden = true; } break; case SpecFileDialogViewFilesTypeEnum::VIEW_FILES_LOADED_NOT_MODIFIED: if (caretDataFile != NULL) { if (caretDataFile->isModified()) { isFileHidden = true; } } else { isFileHidden = true; } break; case SpecFileDialogViewFilesTypeEnum::VIEW_FILES_NOT_LOADED: if (caretDataFile != NULL) { isFileHidden = true; } break; } m_filesTableWidget->setRowHidden(rowIndex, isFileHidden); } /* * Enable cell changed signal now that table has been updated. */ m_filesTableWidget->blockSignals(false); /* * Fix table geometry. */ m_filesTableWidget->horizontalHeader()->setStretchLastSection(true); m_filesTableWidget->resizeColumnsToContents(); m_filesTableWidget->resizeRowsToContents(); enableLoadOrSaveButton(); } /** * @return Create and return a text item for the table. */ QTableWidgetItem* SpecFileManagementDialog::createTextItem() { QTableWidgetItem* item = new QTableWidgetItem(); Qt::ItemFlags flags(Qt::ItemIsEnabled); item->setFlags(flags); return item; } /** * @return Create and return a checkable item for the table. */ QTableWidgetItem* SpecFileManagementDialog::createCheckableItem() { QTableWidgetItem* item = new QTableWidgetItem(); Qt::ItemFlags flags(Qt::ItemIsEnabled | Qt::ItemIsUserCheckable); item->setFlags(flags); item->setCheckState(Qt::Unchecked); return item; } #include "EventDataFileDelete.h" /** * Called when a push button was added using addUserPushButton(). * Subclasses MUST override this if user push buttons were * added using addUserPushButton(). * * @param userPushButton * User push button that was pressed. * @return * The result that indicates action that should be taken * as a result of the button being pressed. */ WuQDialogModal::DialogUserButtonResult SpecFileManagementDialog::userButtonPressed(QPushButton* userPushButton) { if (userPushButton == m_loadScenesPushButton) { /* * Load all of the scene files but nothing else */ m_specFile->setAllSceneFilesSelectedForLoadingAndAllOtherFilesNotSelected(); okButtonClickedOpenSpecFile(); GuiManager::get()->processShowSceneDialog(GuiManager::get()->getActiveBrowserWindow()); return RESULT_MODAL_ACCEPT; } else { CaretAssert(0); } return RESULT_NONE; } /** * Gets called when the OK button is pressed. */ void SpecFileManagementDialog::okButtonClicked () { bool allowDialogToClose = false; switch (m_dialogMode) { case MODE_MANAGE_FILES: allowDialogToClose = okButtonClickedManageAndSaveFiles(); break; case MODE_OPEN_SPEC_FILE: okButtonClickedOpenSpecFile(); allowDialogToClose = true; break; case MODE_SAVE_FILES_WHILE_QUITTING: allowDialogToClose = okButtonClickedManageAndSaveFiles(); break; } if (allowDialogToClose) { WuQDialogModal::okButtonClicked(); } } /** * Perform processing when the Open button is pressed for Open Spec File mode. */ void SpecFileManagementDialog::okButtonClickedOpenSpecFile() { AString username; AString password; if (m_specFile->hasFilesWithRemotePathSelectedForLoading()) { const QString msg("This spec file contains files that are on the network. " "If accessing the files requires a username and " "password, enter it here. Otherwise, remove any " "text from the username and password fields."); if (UsernamePasswordWidget::getUserNameAndPasswordInDialog(this, "Username and Password", msg, username, password)) { /* nothing */ } else { return; } } AString specFileErrorMessage = writeSpecFile(true); AString errorMessages; errorMessages.appendWithNewLine(specFileErrorMessage); EventSpecFileReadDataFiles readSpecFileEvent(m_brain, m_specFile); readSpecFileEvent.setUsernameAndPassword(username, password); ProgressReportingDialog::runEvent(&readSpecFileEvent, this, m_specFile->getFileNameNoPath()); errorMessages.appendWithNewLine(readSpecFileEvent.getErrorMessage()); updateGraphicWindowsAndUserInterface(); if (errorMessages.isEmpty() == false) { WuQMessageBox::errorOk(this, errorMessages); } } /** * Perform processing when the Open button is pressed for Manage Files * or Save Files mode. */ bool SpecFileManagementDialog::okButtonClickedManageAndSaveFiles() { /* * Wait cursor */ CursorDisplayScoped cursor; cursor.showWaitCursor(); AString errorMessages; const int32_t numDataFiles = static_cast(m_tableRowDataFileContent.size()); for (int32_t i = 0; i < numDataFiles; i++) { SpecFileDataFile* specFileDataFile = m_tableRowDataFileContent[i]->m_specFileDataFile; CaretAssert(specFileDataFile); if (specFileDataFile->isSavingSelected()) { CaretDataFile* caretDataFile = specFileDataFile->getCaretDataFile(); if (caretDataFile != NULL) { if (caretDataFile->supportsWriting()) { try { m_brain->writeDataFile(caretDataFile); specFileDataFile->setSavingSelected(false); } catch (const DataFileException& e) { errorMessages.appendWithNewLine(e.whatString()); } } } } } CaretAssert(m_COLUMN_SAVE_CHECKBOX >= 0); QTableWidgetItem* saveItem = getTableWidgetItem(m_specFileTableRowIndex, m_COLUMN_SAVE_CHECKBOX); if (saveItem->checkState() == Qt::Checked) { AString specFileName = m_specFile->getFileName(); if (m_specFile->getFileName().isEmpty()) { errorMessages.appendWithNewLine("Spec File name is empty."); } else { m_specFile->removeAnyFileInformationIfNotInSpecAndNoCaretDataFile(); AString specFileErrorMessage = writeSpecFile(false); if (specFileErrorMessage.isEmpty() == false) { errorMessages.appendWithNewLine(specFileErrorMessage); } else { } saveItem->setCheckState(Qt::Unchecked); } } /* * Spec file may have changed by SpecFile::removeAnyFileInformationIfNotInSpecAndNoCaretDataFile(). */ getDataFileContentFromSpecFile(); updateTableDimensionsToFitFiles(); loadSpecFileContentIntoDialog(); cursor.restoreCursor(); if (errorMessages.isEmpty() == false) { WuQMessageBox::errorOk(this, errorMessages); return false; } bool allowDialogToClose = false; switch (m_dialogMode) { case MODE_MANAGE_FILES: break; case MODE_OPEN_SPEC_FILE: break; case MODE_SAVE_FILES_WHILE_QUITTING: allowDialogToClose = true; break; } EventManager::get()->sendEvent(EventUserInterfaceUpdate().getPointer()); return allowDialogToClose; } /** * Write the spec file if it is modified. * * @param writeOnlyIfModified * Write only if the spec file is modified. * @return Non-empty string if there was an error writing the spec file. */ AString SpecFileManagementDialog::writeSpecFile(const bool writeOnlyIfModified) { if (writeOnlyIfModified) { if (m_specFile->isModified() == false) { return ""; } } AString errorMessage; try { m_specFile->writeFile(m_specFile->getFileName()); CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); prefs->addToPreviousSpecFiles(m_specFile->getFileName()); } catch (const DataFileException& e) { errorMessage = e.whatString(); } return errorMessage; } /** * Called when a file remove button is clicked. * * @param rowIndex * Row index of the item selected. */ void SpecFileManagementDialog::fileRemoveActionSelected(int rowIndex) { SpecFileManagementDialogRowContent* rowContent = getFileContentInRow(rowIndex); SpecFileDataFile* specFileDataFile = rowContent->m_specFileDataFile; QWidget* removeButtonWidget = m_filesTableWidget->cellWidget(rowIndex, m_COLUMN_CLOSE_BUTTON); CaretAssert(removeButtonWidget); CaretDataFile* caretDataFile = specFileDataFile->getCaretDataFile(); if (caretDataFile != NULL) { if (caretDataFile->isModified()) { const QString msg = (caretDataFile->getFileNameNoPath() + " is modified. Close without saving changes?"); if (WuQMessageBox::warningOkCancel(removeButtonWidget, msg) == false) { return; } } EventManager::get()->sendEvent(EventDataFileDelete(caretDataFile).getPointer()); getDataFileContentFromSpecFile(); loadSpecFileContentIntoDialog(); updateGraphicWindowsAndUserInterface(); } } /** * Get the content for the given row. * * @param rowIndex * Index of the row. * @return * Content for the given row. */ SpecFileManagementDialogRowContent* SpecFileManagementDialog::getFileContentInRow(const int rowIndex) { const int numDataFiles = static_cast(m_tableRowDataFileContent.size()); for (int32_t i = 0; i < numDataFiles; i++) { if (m_tableRowDataFileContent[i]->m_tableRowIndex == rowIndex) { return m_tableRowDataFileContent[i]; } } CaretAssertMessage(0, ("Invalid data file rowIndex (0 is spec file!!!): " + AString::number(rowIndex))); return NULL; } /** * Called when a file reload or open button is clicked. * * @param rowIndex * Index of the SpecFileDataFile item. */ void SpecFileManagementDialog::fileReloadOrOpenFileActionSelected(int rowIndex) { SpecFileManagementDialogRowContent* rowContent = getFileContentInRow(rowIndex); SpecFileDataFile* specFileDataFile = rowContent->m_specFileDataFile; QWidget* toolButtonWidget = m_filesTableWidget->cellWidget(rowIndex, m_COLUMN_READ_BUTTON); CaretAssert(toolButtonWidget); AString errorMessage; CaretDataFile* caretDataFile = specFileDataFile->getCaretDataFile(); if (caretDataFile != NULL) { if (caretDataFile->isModified()) { const QString msg = (caretDataFile->getFileNameNoPath() + " is modified. Reopen without saving changes?"); if (WuQMessageBox::warningOkCancel(toolButtonWidget, msg) == false) { return; } } AString username; AString password; if (DataFile::isFileOnNetwork(caretDataFile->getFileName())) { const QString msg("This file is on the network. " "If accessing the file requires a username and " "password, enter it here. Otherwise, remove any " "text from the username and password fields."); if (UsernamePasswordWidget::getUserNameAndPasswordInDialog(this, "Username and Password", msg, username, password)) { /* nothing */ } else { return; } } specFileDataFile->setSavingSelected(false); EventDataFileReload reloadEvent(m_brain, caretDataFile); reloadEvent.setUsernameAndPassword(username, password); EventManager::get()->sendEvent(reloadEvent.getPointer()); if (reloadEvent.isError()) { errorMessage.appendWithNewLine(reloadEvent.getErrorMessage()); } } else { AString username; AString password; if (DataFile::isFileOnNetwork(specFileDataFile->getFileName())) { const QString msg("This file is on the network. " "If accessing the file requires a username and " "password, enter it here. Otherwise, remove any " "text from the username and password fields."); if (UsernamePasswordWidget::getUserNameAndPasswordInDialog(this, "Username and Password", msg, username, password)) { /* nothing */ } else { return; } } EventDataFileRead readEvent(m_brain); readEvent.setUsernameAndPassword(username, password); readEvent.addDataFile(specFileDataFile->getStructure(), specFileDataFile->getDataFileType(), specFileDataFile->getFileName()); EventManager::get()->sendEvent(readEvent.getPointer()); if (readEvent.isError()) { errorMessage.appendWithNewLine(readEvent.getErrorMessage()); } } updateGraphicWindowsAndUserInterface(); loadSpecFileContentIntoDialog(); if (errorMessage.isEmpty() == false) { WuQMessageBox::errorOk(toolButtonWidget, errorMessage); } } /** * Updates graphics windows and user interface */ void SpecFileManagementDialog::updateGraphicWindowsAndUserInterface() { EventManager::get()->sendEvent(EventSurfaceColoringInvalidate().getPointer()); EventManager::get()->sendEvent(EventUserInterfaceUpdate().getPointer()); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } /** * Called when a file options button is clicked. * * @param rowIndex * Index of the SpecFileDataFile item. */ void SpecFileManagementDialog::fileOptionsActionSelected(int rowIndex) { m_filesTableWidget->setRangeSelected(QTableWidgetSelectionRange(rowIndex, m_COLUMN_OPTIONS_TOOLBUTTON, rowIndex, m_COLUMN_OPTIONS_TOOLBUTTON), false); const AString copyPathText("Copy Path and File Name to Clipboard"); if (rowIndex == m_specFileTableRowIndex) { QMenu menu; QAction* copyFilePathToClipboardAction = NULL; if (m_specFile != NULL) { if ( ! m_specFile->getFileName().trimmed().isEmpty()) { copyFilePathToClipboardAction = menu.addAction(copyPathText); } } QAction* editMetaDataAction = menu.addAction("Edit Metadata...");; QAction* setFileNameAction = menu.addAction("Set File Name..."); QAction* selectedAction = menu.exec(QCursor::pos()); if (selectedAction == NULL) { /* * If the selected action is NULL, it indicates that the user did * not make a selection. This test is needed as some of the actions * (such as setFileNameAction) may be NULL and with out this test, * those NULL actions would match. */ } else if (selectedAction == setFileNameAction) { changeFileName(&menu, NULL, m_specFile); } else if (selectedAction == editMetaDataAction) { MetaDataEditorDialog mded(m_specFile, &menu); mded.exec(); loadSpecFileContentIntoDialog(); } else if (selectedAction == copyFilePathToClipboardAction) { QApplication::clipboard()->setText(m_specFile->getFileName().trimmed(), QClipboard::Clipboard); } else if (selectedAction != NULL) { CaretAssertMessage(0, ("Unhandled Menu Action: " + selectedAction->text())); } } else { SpecFileManagementDialogRowContent* rowContent = getFileContentInRow(rowIndex); SpecFileDataFile* specFileDataFile = rowContent->m_specFileDataFile; CaretDataFile* caretDataFile = specFileDataFile->getCaretDataFile(); CaretMappableDataFile* caretMappableDataFile = NULL; if (caretDataFile != NULL) { caretMappableDataFile = dynamic_cast(caretDataFile); } QAction* copyFilePathToClipboardAction = NULL; QAction* editMetaDataAction = NULL; QAction* setFileNameAction = NULL; QAction* setStructureAction = NULL; //QAction* unloadFileAction = NULL; QAction* unloadFileMapsAction = NULL; QAction* viewMetaDataAction = NULL; QMenu menu; switch (m_dialogMode) { case MODE_MANAGE_FILES: case MODE_SAVE_FILES_WHILE_QUITTING: if (caretDataFile != NULL) { copyFilePathToClipboardAction = menu.addAction(copyPathText); editMetaDataAction = menu.addAction("Edit Metadata..."); setFileNameAction = menu.addAction("Set File Name..."); // unloadFileAction = menu.addAction("Unload File"); //if (caretMappableDataFile != NULL) { // unloadFileMapsAction = menu.addAction("Unload Map(s) from File"); // unloadFileMapsAction->setEnabled(false); //} } else { copyFilePathToClipboardAction = menu.addAction(copyPathText); //viewMetaDataAction = menu.addAction("View Metadata..."); //viewMetaDataAction->setEnabled(false); } break; case MODE_OPEN_SPEC_FILE: copyFilePathToClipboardAction = menu.addAction(copyPathText); //setStructureAction = menu.addAction("Set Structure..."); //setStructureAction->setEnabled(false); //viewMetaDataAction = menu.addAction("View Metadata..."); //viewMetaDataAction->setEnabled(false); break; } QAction* selectedAction = menu.exec(QCursor::pos()); if (selectedAction == NULL) { /* * If the selected action is NULL, it indicates that the user did * not make a selection. This test is needed as some of the actions * (such as setFileNameAction) may be NULL and with out this test, * those NULL actions would match. */ } else if (selectedAction == copyFilePathToClipboardAction) { copyFilePathToClipboard(specFileDataFile, caretDataFile); } else if (selectedAction == setFileNameAction) { changeFileName(&menu, specFileDataFile, caretDataFile); } else if (selectedAction == setStructureAction) { CaretAssert(0); } // else if (selectedAction == unloadFileAction) { // CaretDataFile* cdf = specFileDataFile->getCaretDataFile(); // GuiManager::get()->getBrain()->removeDataFile(cdf); // loadSpecFileContentIntoDialog(); // updateGraphicWindowsAndUserInterface(); // } else if (selectedAction == unloadFileMapsAction) { // if (caretDataFile->isModified()) { // specFileDataFile->setSavingSelected(true); // } } else if (selectedAction == editMetaDataAction) { if (caretDataFile != NULL) { MetaDataEditorDialog mded(caretDataFile, &menu); mded.exec(); if (caretDataFile->isModified()) { specFileDataFile->setSavingSelected(true); } loadSpecFileContentIntoDialog(); } } else if (selectedAction == viewMetaDataAction) { } else if (selectedAction != NULL) { CaretAssertMessage(0, ("Unhandled Menu Action: " + selectedAction->text())); } } } /** * Copy the loaded file's path to the clipboard. * * @param caretDataFile * The data file. */ void SpecFileManagementDialog::copyFilePathToClipboard(const SpecFileDataFile* specFileDataFile, const CaretDataFile* caretDataFile) { if (caretDataFile != NULL) { QApplication::clipboard()->setText(caretDataFile->getFileName().trimmed(), QClipboard::Clipboard); } else if (specFileDataFile != NULL) { QApplication::clipboard()->setText(specFileDataFile->getFileName().trimmed(), QClipboard::Clipboard); } else { CaretAssert(0); } } /** * Change the name of a file. * * @param parent * Widget on which file dialog is displayed. * @param specFileDataFile * Entry in spec file (NULL if caretDataFile is spec file). * @param caretDataFileIn * Caret Data File that is having name changed. */ void SpecFileManagementDialog::changeFileName(QWidget* parent, SpecFileDataFile* specFileDataFile, CaretDataFile* caretDataFileIn) { CaretDataFile* caretDataFile = caretDataFileIn; CaretAssert(caretDataFile); if (caretDataFile != m_specFile) { CaretAssert(m_specFile); } const AString newFileName = CaretFileDialog::getChooseFileNameDialog(caretDataFile->getDataFileType(), caretDataFile->getFileName(), parent); if (newFileName.isEmpty()) { return; } // QStringList filenameFilterList; // filenameFilterList.append(DataFileTypeEnum::toQFileDialogFilter(caretDataFile->getDataFileType())); // CaretFileDialog fd(parent); // fd.setAcceptMode(CaretFileDialog::AcceptSave); // fd.setNameFilters(filenameFilterList); // fd.setFileMode(CaretFileDialog::AnyFile); // fd.setViewMode(CaretFileDialog::List); // fd.selectFile(caretDataFile->getFileName()); // fd.setLabelText(CaretFileDialog::Accept, "Choose"); // fd.setWindowTitle("Choose File Name"); // if (fd.exec() == CaretFileDialog::Accepted) { // QStringList files = fd.selectedFiles(); // if (files.isEmpty() == false) { // AString newFileName = files.at(0); if (newFileName != caretDataFile->getFileName()) { /* * Clone current item, remove file from it, * and create new item. * * Note: specFileDataFile is NULL when changing the name * of the spec file. */ if (specFileDataFile != NULL) { SpecFileDataFile* sfdf = m_specFile->changeFileName(specFileDataFile, newFileName); caretDataFile = sfdf->getCaretDataFile(); CaretAssert(caretDataFile); if (caretDataFile->isModified()) { sfdf->setSavingSelected(true); } } caretDataFile->setFileName(newFileName); /* * Spec file has changed */ getDataFileContentFromSpecFile(); /* * Table may need to add/remove rows */ updateTableDimensionsToFitFiles(); /* * Update the table rows with data */ loadSpecFileContentIntoDialog(); /* * Files have changed */ updateGraphicWindowsAndUserInterface(); } // } // } } ///** // * Called when spec file options tool button is triggered. // */ //void //SpecFileManagementDialog::specFileOptionsActionTriggered() //{ // QAction* setFileNameAction = NULL; // // QMenu menu; // QAction* metadataAction = menu.addAction("Edit Metadata..."); // metadataAction->setEnabled(false); // switch (m_dialogMode) { // case MODE_MANAGE_FILES: // case MODE_SAVE_FILES_WHILE_QUITTING: // setFileNameAction = menu.addAction("Set File Name..."); // break; // case MODE_OPEN_SPEC_FILE: // break; // } // // QAction* selectedAction = menu.exec(QCursor::pos()); // // if (selectedAction == setFileNameAction) { // QStringList filenameFilterList; // filenameFilterList.append(DataFileTypeEnum::toQFileDialogFilter(DataFileTypeEnum::SPECIFICATION)); // CaretFileDialog fd(&menu); // fd.setAcceptMode(CaretFileDialog::AcceptSave); // fd.setNameFilters(filenameFilterList); // fd.setFileMode(CaretFileDialog::AnyFile); // fd.setViewMode(CaretFileDialog::List); // fd.selectFile(m_specFile->getFileName()); // fd.setLabelText(CaretFileDialog::Accept, "Choose"); // fd.setWindowTitle("Choose Spec File Name"); // if (fd.exec() == CaretFileDialog::Accepted) { // QStringList files = fd.selectedFiles(); // if (files.isEmpty() == false) { // AString newFileName = files.at(0); // m_specFile->setFileName(newFileName); // loadSpecFileContentIntoDialog(); // } // } // } // else if (selectedAction == metadataAction) { // // } // else if (selectedAction != NULL) { // CaretAssertMessage(0, // ("Unhandled Menu Action: " + selectedAction->text())); // } //} ///** // * Called to choose the name of the spec file. // */ //void //SpecFileManagementDialog::chooseSpecFileNameActionTriggered() //{ // QWidget* toolButtonWidget = m_filesTableWidget->cellWidget(m_specFileTableRowIndex, // m_COLUMN_OPTIONS_TOOLBUTTON); // CaretAssert(toolButtonWidget); // // QStringList filenameFilterList; // filenameFilterList.append(DataFileTypeEnum::toQFileDialogFilter(DataFileTypeEnum::SPECIFICATION)); // CaretFileDialog fd(toolButtonWidget); // fd.setAcceptMode(CaretFileDialog::AcceptSave); // fd.setNameFilters(filenameFilterList); // fd.setFileMode(CaretFileDialog::AnyFile); // fd.setViewMode(CaretFileDialog::List); // fd.selectFile(m_specFile->getFileName()); // fd.setLabelText(CaretFileDialog::Accept, "Choose"); // fd.setWindowTitle("Choose Spec File Name"); // if (fd.exec() == CaretFileDialog::Accepted) { // QStringList files = fd.selectedFiles(); // if (files.isEmpty() == false) { // AString newFileName = files.at(0); // m_specFile->setFileName(newFileName); // loadSpecFileContentIntoDialog(); // } // } //} /** * @return Create and return a toolbar for viewing files by type of file. * @param labelOut * If text was not empty, this parameter will be set to the QLabel that * contains the text. Otherwise, it will be NULL upon exit. */ QToolBar* SpecFileManagementDialog::createFilesTypesToolBar(QLabel* &labelOut) { m_fileTypesActionGroup = new QActionGroup(this); m_fileTypesActionGroup->setExclusive(true); QObject::connect(m_fileTypesActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(toolBarFileTypeActionTriggered(QAction*))); QAction* fileTypeAllAction = m_fileTypesActionGroup->addAction("All"); fileTypeAllAction->setCheckable(true); fileTypeAllAction->setData(qVariantFromValue(DataFileTypeEnum::toIntegerCode(DataFileTypeEnum::UNKNOWN))); /* * All types of files */ std::vector allDataFileTypes; DataFileTypeEnum::getAllEnums(allDataFileTypes); /* * Get data types of files that are listed in the dialog */ std::set loadedDataFileTypes; const int32_t numFiles = static_cast(m_tableRowDataFileContent.size()); for (int32_t i = 0; i < numFiles; i++) { SpecFileDataFile* sfdf = m_tableRowDataFileContent[i]->m_specFileDataFile; loadedDataFileTypes.insert(sfdf->getDataFileType()); } for (std::vector::iterator iter = allDataFileTypes.begin(); iter != allDataFileTypes.end(); iter++) { DataFileTypeEnum::Enum dataFileType = *iter; /* * Only list file types if listed in dialog */ if (dataFileType == DataFileTypeEnum::SPECIFICATION) { continue; } if (std::find(loadedDataFileTypes.begin(), loadedDataFileTypes.end(), dataFileType) == loadedDataFileTypes.end()) { continue; } const AString text = getEditedDataFileTypeName(dataFileType); QAction* action = m_fileTypesActionGroup->addAction(text); action->setCheckable(true); action->setData(qVariantFromValue(DataFileTypeEnum::toIntegerCode(dataFileType))); } if (m_fileTypesActionGroup->actions().isEmpty() == false) { m_fileTypesActionGroup->blockSignals(true); m_fileTypesActionGroup->actions().at(0)->setChecked(true); m_fileTypesActionGroup->blockSignals(false); } QToolBar* toolbar = createToolBarWithActionGroup("View File Types: ", labelOut, m_fileTypesActionGroup); return toolbar; } /** * @return Create and return a toolbar for selecting all or no files. * @param labelOut * If text was not empty, this parameter will be set to the QLabel that * contains the text. Otherwise, it will be NULL upon exit. */ QToolBar* SpecFileManagementDialog::createFilesSelectionToolBar(QLabel* &labelOut) { // * When loading, ALL or NONE but only ones that are visibleRegion() m_fileSelectionActionGroup = new QActionGroup(this); QObject::connect(m_fileSelectionActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(toolBarSelectFilesActionTriggered(QAction*))); QAction* allFilesAction = m_fileSelectionActionGroup->addAction("All"); allFilesAction->setData(qVariantFromValue(SHOW_FILES_ALL)); QAction* noneFilesAction = m_fileSelectionActionGroup->addAction("None"); noneFilesAction->setData(qVariantFromValue(SHOW_FILES_NONE)); QToolBar* toolbar = createToolBarWithActionGroup("Select Files: ", labelOut, m_fileSelectionActionGroup); return toolbar; } /** * @return Create and return a toolbar for selecting loaded or not loaded files. * @param labelOut * If text was not empty, this parameter will be set to the QLabel that * contains the text. Otherwise, it will be NULL upon exit. */ QToolBar* SpecFileManagementDialog::createManageFilesLoadedNotLoadedToolBar(QLabel* &labelOut) { m_manageFilesLoadedNotLoadedActionGroup = new QActionGroup(this); QObject::connect(m_manageFilesLoadedNotLoadedActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(toolBarManageFilesLoadedNotLoadedActionTriggered(QAction*))); QAction* allFilesAction = m_manageFilesLoadedNotLoadedActionGroup->addAction("All"); allFilesAction->setData(qVariantFromValue((int)MANAGE_FILES_ALL)); allFilesAction->setCheckable(true); QAction* loadedFilesAction = m_manageFilesLoadedNotLoadedActionGroup->addAction("Loaded"); loadedFilesAction->setData(qVariantFromValue((int)MANAGE_FILES_LOADED)); loadedFilesAction->setCheckable(true); QAction* loadedFilesModifiedAction = m_manageFilesLoadedNotLoadedActionGroup->addAction("Loaded:Modified"); loadedFilesModifiedAction->setData(qVariantFromValue((int)MANAGE_FILES_LOADED_MODIFIED)); loadedFilesModifiedAction->setCheckable(true); QAction* loadedFilesNotModifiedAction = m_manageFilesLoadedNotLoadedActionGroup->addAction("Loaded:Not Modified"); loadedFilesNotModifiedAction->setData(qVariantFromValue((int)MANAGE_FILES_LOADED_NOT_MODIFIED)); loadedFilesNotModifiedAction->setCheckable(true); QAction* notLoadedFilesAction = m_manageFilesLoadedNotLoadedActionGroup->addAction("Not Loaded"); notLoadedFilesAction->setData(qVariantFromValue((int)MANAGE_FILES_NOT_LOADED)); notLoadedFilesAction->setCheckable(true); m_manageFilesLoadedNotLoadedActionGroup->blockSignals(true); switch (m_dialogMode) { case MODE_MANAGE_FILES: allFilesAction->setChecked(true); break; case MODE_OPEN_SPEC_FILE: allFilesAction->setChecked(true); break; case MODE_SAVE_FILES_WHILE_QUITTING: loadedFilesModifiedAction->setChecked(true); break; } m_manageFilesLoadedNotLoadedActionGroup->blockSignals(false); QToolBar* toolbar = createToolBarWithActionGroup("View Files: ", labelOut, m_manageFilesLoadedNotLoadedActionGroup); return toolbar; } /** * @return Edit and return the text for a data file type. */ AString SpecFileManagementDialog::getEditedDataFileTypeName(const DataFileTypeEnum::Enum dataFileType) { const AString typeName = DataFileTypeEnum::toGuiName(dataFileType); const AString connectivityPrefix("Connectivity - "); const int connectivityPrefixLength = connectivityPrefix.length(); const AString temporarySuffix(" TEMPORARY"); const int temporarySuffixLength = temporarySuffix.length(); AString text = typeName; if (text.startsWith(connectivityPrefix)) { text = text.mid(connectivityPrefixLength); } if (text.endsWith(temporarySuffix)) { text = text.left(text.length() - temporarySuffixLength); } return text; } /** * @return Create and return a toolbar for viewing files by structure. */ QToolBar* SpecFileManagementDialog::createStructureToolBar(QLabel* &labelOut) { std::vector structureTypes; structureTypes.push_back(StructureEnum::ALL); structureTypes.push_back(StructureEnum::CORTEX_LEFT); structureTypes.push_back(StructureEnum::CORTEX_RIGHT); structureTypes.push_back(StructureEnum::CEREBELLUM); structureTypes.push_back(StructureEnum::OTHER); m_structureActionGroup = new QActionGroup(this); m_structureActionGroup->setExclusive(true); QObject::connect(m_structureActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(toolBarStructuresActionTriggered(QAction*))); for (std::vector::iterator iter = structureTypes.begin(); iter != structureTypes.end(); iter++) { StructureEnum::Enum structure = *iter; QAction* action = m_structureActionGroup->addAction(StructureEnum::toGuiName(structure)); action->setCheckable(true); action->setData(qVariantFromValue(StructureEnum::toIntegerCode(structure))); } if (m_structureActionGroup->actions().isEmpty() == false) { m_structureActionGroup->blockSignals(true); m_structureActionGroup->actions().at(0)->setChecked(true); m_structureActionGroup->blockSignals(false); } QToolBar* toolbar = createToolBarWithActionGroup("View Structures: ", labelOut, m_structureActionGroup); return toolbar; } /** * Create a toolbar with the given label containing all items * in the given action group. * * @param text * If not empty, this text is inserted into the left side of the toolbar. * @param labelOut * If text was not empty, this parameter will be set to the QLabel that * contains the text. Otherwise, it will be NULL upon exit. * @param actionGroup * All actions from this action group are added to the toolbar. * @return * The toolbar. */ QToolBar* SpecFileManagementDialog::createToolBarWithActionGroup(const QString& text, QLabel* &labelOut, QActionGroup* actionGroup) { QToolBar* toolbar = new QToolBar(); if (text.isEmpty() == false) { labelOut = new QLabel(text); labelOut->setAlignment(Qt::AlignLeft); toolbar->addWidget(labelOut); } else { labelOut = NULL; } QList actions = actionGroup->actions(); QListIterator iterator(actions); while (iterator.hasNext()) { toolbar->addAction(iterator.next()); } return toolbar; } /** * Called when a tool bar's file type button is selected. * * @param action * QAction of item selected. */ void SpecFileManagementDialog::toolBarFileTypeActionTriggered(QAction* /*action*/) { // if (action != NULL) { // const int dataValue = action->data().toInt(); // bool isValid = false; // const DataFileTypeEnum::Enum dataFileType = DataFileTypeEnum::fromIntegerCode(dataValue, // &isValid); // } loadSpecFileContentIntoDialog(); } /** * Called when tool bar's structure button is selected. * * @param action * QAction of item selected. */ void SpecFileManagementDialog::toolBarStructuresActionTriggered(QAction* /*action*/) { // if (action != NULL) { // const int dataValue = action->data().toInt(); // bool isValid = false; // const StructureEnum::Enum structure = StructureEnum::fromIntegerCode(dataValue, // &isValid); // } loadSpecFileContentIntoDialog(); } /** * Show loaded/not loaded files when manage files mode. * * @param action * QAction of item selected. */ void SpecFileManagementDialog::toolBarManageFilesLoadedNotLoadedActionTriggered(QAction* /*action*/) { loadSpecFileContentIntoDialog(); } /** * Set all files as selected. * * @param action * QAction of item selected. */ void SpecFileManagementDialog::toolBarSelectFilesActionTriggered(QAction* action) { // m_filesTableWidget->blockSignals(true); if (action != NULL) { const int dataValue = action->data().toInt(); bool newStatus = false; if (dataValue == SHOW_FILES_ALL) { newStatus = true; } else if (dataValue == SHOW_FILES_NONE) { newStatus = false; } const int32_t numFiles = static_cast(m_tableRowDataFileContent.size()); for (int32_t i = 0; i < numFiles; i++) { const int32_t rowIndex = m_tableRowDataFileContent[i]->m_tableRowIndex; if (m_filesTableWidget->isRowHidden(rowIndex) == false) { if (m_COLUMN_LOAD_CHECKBOX >= 0) { QTableWidgetItem* loadItem = getTableWidgetItem(rowIndex, m_COLUMN_LOAD_CHECKBOX); CaretAssert(loadItem); loadItem->setCheckState(WuQtUtilities::boolToCheckState(newStatus)); } if (m_COLUMN_SAVE_CHECKBOX >= 0) { QTableWidgetItem* saveItem = getTableWidgetItem(rowIndex, m_COLUMN_SAVE_CHECKBOX); CaretAssert(saveItem); saveItem->setCheckState(WuQtUtilities::boolToCheckState(newStatus)); } } } } // m_filesTableWidget->blockSignals(false); } /* ======================================================================= */ /** * \class caret::SpecFileManagementDialogRowContent * \brief Content of a row in the SpecFileManagementDialog. * \ingroup GuiQt */ SpecFileManagementDialogRowContent::SpecFileManagementDialogRowContent(SpecFileDataFileTypeGroup* specFileDataFileTypeGroup, SpecFileDataFile* specFileDataFile) { m_tableRowIndex = -1; m_specFileDataFileTypeGroup = specFileDataFileTypeGroup; m_specFileDataFile = specFileDataFile; } SpecFileManagementDialogRowContent::~SpecFileManagementDialogRowContent() { } /** * Less than method for sorting using the sorting key. * * @param item1 * Tested for less than the item2 * @param item2 * Tested for greater than the item1 * @return * True if item1 is less than item2, else false. */ bool SpecFileManagementDialogRowContent::lessThanForSorting(const SpecFileManagementDialogRowContent* item1, const SpecFileManagementDialogRowContent* item2) { return (item1->m_sortingKey < item2->m_sortingKey); } /** * Set the sorting key for the given sorting type prior to sorting. * Creates a text string that is used for sorting that is used by * the static sorting method. * * @param sorting * Type of sorting. */ void SpecFileManagementDialogRowContent::setSortingKey(const Sorting sorting) { FileInformation fileInfo(m_specFileDataFile->getFileName()); const QString filename = fileInfo.getFileName().toUpper(); const DataFileTypeEnum::Enum dataFileType = m_specFileDataFile->getDataFileType(); QString typeName = SpecFileManagementDialog::getEditedDataFileTypeName(dataFileType); /* * Push surface files to the top??? */ // if (dataFileType == DataFileTypeEnum::SURFACE) { // typeName = "AAAAAA"; // } /* * Push non-specific structures to the bottom */ const StructureEnum::Enum structure = m_specFileDataFile->getStructure(); QString structureName = StructureEnum::toGuiName(structure); switch (structure) { case StructureEnum::ALL: case StructureEnum::ALL_GREY_MATTER: case StructureEnum::ALL_WHITE_MATTER: case StructureEnum::INVALID: case StructureEnum::OTHER: structureName = "zzzzzz"; break; default: break; } m_sortingKey = ""; switch (sorting) { case SORTING_TYPE_STRUCTURE_NAME: m_sortingKey = (typeName + structureName + filename); break; case SORTING_NAME: m_sortingKey = filename; break; case SORTING_STRUCTURE_TYPE_NAME: m_sortingKey = (structureName + typeName + filename); break; } } workbench-1.1.1/src/GuiQt/SpecFileManagementDialog.h000066400000000000000000000243221255417355300223110ustar00rootroot00000000000000#ifndef __SPEC_FILE_MANAGEMENT_DIALOG_H__ #define __SPEC_FILE_MANAGEMENT_DIALOG_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "CaretObject.h" #include "DataFileTypeEnum.h" #include "SpecFileDialogViewFilesTypeEnum.h" #include "StructureEnum.h" #include "WuQDialogModal.h" class QActionGroup; class QIcon; class QLabel; class QSignalMapper; class QTableWidget; class QTableWidgetItem; class QToolBar; namespace caret { class Brain; class CaretDataFile; class SpecFile; class SpecFileDataFile; class SpecFileDataFileTypeGroup; class SpecFileManagementDialogRowContent; /** * Data in a row of the table widget */ class SpecFileManagementDialogRowContent : public CaretObject { public: /** * Type of sorting */ enum Sorting { SORTING_TYPE_STRUCTURE_NAME, SORTING_STRUCTURE_TYPE_NAME, SORTING_NAME }; SpecFileManagementDialogRowContent(SpecFileDataFileTypeGroup* specFileDataFileTypeGroup, SpecFileDataFile* specFileDataFile); ~SpecFileManagementDialogRowContent(); void setSortingKey(const Sorting sorting); int m_tableRowIndex; SpecFileDataFileTypeGroup* m_specFileDataFileTypeGroup; SpecFileDataFile* m_specFileDataFile; QString m_sortingKey; static bool lessThanForSorting(const SpecFileManagementDialogRowContent* item1, const SpecFileManagementDialogRowContent* item2); }; class SpecFileManagementDialog : public WuQDialogModal { Q_OBJECT public: static bool runOpenSpecFileDialog(Brain* brain, SpecFile* specFile, QWidget* parent); static void runManageFilesDialog(Brain* brain, QWidget* parent); static bool runSaveFilesDialogWhileQuittingWorkbench(Brain* brain, QWidget* parent); virtual ~SpecFileManagementDialog(); protected: WuQDialogModal::DialogUserButtonResult userButtonPressed(QPushButton* userPushButton); private slots: void toolBarFileTypeActionTriggered(QAction* action); void toolBarStructuresActionTriggered(QAction* action); void toolBarSelectFilesActionTriggered(QAction* action); void toolBarManageFilesLoadedNotLoadedActionTriggered(QAction* action); // void chooseSpecFileNameActionTriggered(); // void specFileOptionsActionTriggered(); void fileReloadOrOpenFileActionSelected(int indx); void fileRemoveActionSelected(int indx); void fileOptionsActionSelected(int indx); void filesTableWidgetCellChanged(int row, int column); void horizontalHeaderSelectedForSorting(int logicalIndex); private: enum Mode { MODE_MANAGE_FILES, MODE_OPEN_SPEC_FILE, MODE_SAVE_FILES_WHILE_QUITTING }; enum ManageFilesDisplay { MANAGE_FILES_ALL, MANAGE_FILES_LOADED, MANAGE_FILES_LOADED_MODIFIED, MANAGE_FILES_LOADED_NOT_MODIFIED, MANAGE_FILES_NOT_LOADED }; SpecFileManagementDialog(const Mode dialogMode, Brain* brain, SpecFile* specFile, const AString& dialogTitle, QWidget* parent); SpecFileManagementDialog(const SpecFileManagementDialog&); SpecFileManagementDialog& operator=(const SpecFileManagementDialog&); QToolBar* createFilesTypesToolBar(QLabel* &labelOut); QToolBar* createFilesSelectionToolBar(QLabel* &labelOut); QToolBar* createManageFilesLoadedNotLoadedToolBar(QLabel* &labelOut); QToolBar* createStructureToolBar(QLabel* &labelOut); QToolBar* createToolBarWithActionGroup(const QString& text, QLabel* &labelOut, QActionGroup* actionGroup); static AString getEditedDataFileTypeName(const DataFileTypeEnum::Enum dataFileType); SpecFileManagementDialogRowContent* getFileContentInRow(const int rowIndex); void updateGraphicWindowsAndUserInterface(); void getDataFileContentFromSpecFile(); void loadSpecFileContentIntoDialog(); void loadDialogContentIntoSpecFile(); void updateSpecFileRowInTable(); void sortFileContent(); void setTableColumnLabels(); virtual void okButtonClicked(); void okButtonClickedOpenSpecFile(); bool okButtonClickedManageAndSaveFiles(); void changeFileName(QWidget* parent, SpecFileDataFile* specFileDataFile, CaretDataFile* caretDataFile); void updateTableDimensionsToFitFiles(); QTableWidgetItem* createHeaderTextItem(const QString& text); QTableWidgetItem* createTextItem(); QTableWidgetItem* createCheckableItem(); AString writeSpecFile(const bool writeOnlyIfModified); QTableWidgetItem* getTableWidgetItem(const int rowIndex, const int columnIndex); void setTableWidgetItem(const int rowIndex, const int columnIndex, QTableWidgetItem* item); void getFilterSelections(SpecFileDialogViewFilesTypeEnum::Enum& viewFilesTypeOut, DataFileTypeEnum::Enum& filteredDataFileTypeOut, StructureEnum::Enum& filteredStructureTypeOut) const; void setFilterSelections(const SpecFileDialogViewFilesTypeEnum::Enum viewFilesType, const DataFileTypeEnum::Enum filteredDataFileType, const StructureEnum::Enum filteredStructureType); void clearSpecFileManagementDialogRowContent(); void enableLoadOrSaveButton(); void copyFilePathToClipboard(const SpecFileDataFile* specFileDataFile, const CaretDataFile* caretDataFile); // ADD_NEW_MEMBERS_HERE const Mode m_dialogMode; Brain* m_brain; SpecFile* m_specFile; QPushButton* m_loadScenesPushButton; QActionGroup* m_fileTypesActionGroup; QActionGroup* m_fileSelectionActionGroup; QActionGroup* m_manageFilesLoadedNotLoadedActionGroup; QActionGroup* m_structureActionGroup; std::vector m_tableRowDataFileContent; int m_specFileTableRowIndex; QSignalMapper* m_fileReloadOrOpenFileActionSignalMapper; QSignalMapper* m_fileCloseFileActionSignalMapper; QSignalMapper* m_fileOptionsActionSignalMapper; int m_specFileDataFileCounter; QTableWidget* m_filesTableWidget; SpecFileManagementDialogRowContent::Sorting m_fileSorting; QIcon* m_iconOptions; QIcon* m_iconOpenFile; QIcon* m_iconReloadFile; QIcon* m_iconCloseFile; std::set m_displayedDataFiles; static QByteArray s_manageFilesGeometry; static SpecFileDialogViewFilesTypeEnum::Enum s_manageFilesViewFilesType; static DataFileTypeEnum::Enum s_manageFilesFilteredDataFileType; static StructureEnum::Enum s_manageFilesFilteredStructureType; static const int SHOW_FILES_ALL; static const int SHOW_FILES_NONE; int m_COLUMN_LOAD_CHECKBOX; int m_COLUMN_SAVE_CHECKBOX; int m_COLUMN_STATUS_LABEL; int m_COLUMN_DISPLAYED_LABEL; int m_COLUMN_IN_SPEC_FILE_CHECKBOX; int m_COLUMN_READ_BUTTON; int m_COLUMN_CLOSE_BUTTON; int m_COLUMN_OPTIONS_TOOLBUTTON; int m_COLUMN_DATA_FILE_TYPE_LABEL; int m_COLUMN_STRUCTURE; int m_COLUMN_FILE_NAME_LABEL; int m_COLUMN_COUNT; friend class SpecFileManagementDialogRowContent; }; #ifdef __SPEC_FILE_MANAGEMENT_DIALOG_DECLARE__ QByteArray SpecFileManagementDialog::s_manageFilesGeometry; SpecFileDialogViewFilesTypeEnum::Enum SpecFileManagementDialog::s_manageFilesViewFilesType = SpecFileDialogViewFilesTypeEnum::VIEW_FILES_ALL; DataFileTypeEnum::Enum SpecFileManagementDialog::s_manageFilesFilteredDataFileType = DataFileTypeEnum::UNKNOWN; StructureEnum::Enum SpecFileManagementDialog::s_manageFilesFilteredStructureType = StructureEnum::ALL; const int SpecFileManagementDialog::SHOW_FILES_ALL = -1; const int SpecFileManagementDialog::SHOW_FILES_NONE = -2; #endif // __SPEC_FILE_MANAGEMENT_DIALOG_DECLARE__ } // namespace #endif //__SPEC_FILE_MANAGEMENT_DIALOG_H__ workbench-1.1.1/src/GuiQt/SplashScreen.cxx000066400000000000000000000335531255417355300204550ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #include #include #include #include #include #include #include #define __SPLASH_SCREEN_DECLARE__ #include "SplashScreen.h" #undef __SPLASH_SCREEN_DECLARE__ #include "ApplicationInformation.h" #include "Brain.h" #include "CaretAssert.h" #include "CaretFileDialog.h" #include "CaretPreferences.h" #include "DataFile.h" #include "DataFileTypeEnum.h" #include "FileInformation.h" #include "GuiManager.h" #include "SessionManager.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::SplashScreen * \brief Splash Screen display when Workbench is started. * \ingroup GuiQt */ /** * Constructor. */ SplashScreen::SplashScreen(QWidget* parent) : WuQDialogModal("", parent) { /* * Removes title bar */ // this->setWindowFlags(Qt::SplashScreen); QLabel* imageLabel = NULL; QPixmap pixmap; if (WuQtUtilities::loadPixmap(":/Splash/startup_image.png", pixmap)) { imageLabel = new QLabel(); imageLabel->setPixmap(pixmap); imageLabel->setAlignment(Qt::AlignCenter); } const QString labelStyle = ("QLabel { " " font: 20px bold " "}"); QLabel* workbenchLabel = new QLabel("" "Connectome
" "Workbench" ""); workbenchLabel->setStyleSheet(labelStyle); workbenchLabel->setAlignment(Qt::AlignCenter); ApplicationInformation appInfo; QLabel* versionLabel = new QLabel("Version: " + appInfo.getVersion()); versionLabel->setAlignment(Qt::AlignCenter); QLabel* hcpWebsiteLabel = new QLabel("" "Visit
" "Human Connectome Project
" "Website" ""); hcpWebsiteLabel->setStyleSheet(labelStyle); hcpWebsiteLabel->setAlignment(Qt::AlignCenter); QObject::connect(hcpWebsiteLabel, SIGNAL(linkActivated(const QString&)), this, SLOT(websiteLinkActivated(const QString&))); QToolButton* twitterToolButton = NULL; QIcon twitterIcon; if (WuQtUtilities::loadIcon(":/Splash/twitter.png", twitterIcon)) { QAction* twitterAction = WuQtUtilities::createAction("Twitter", "Follow HCP on Twitter", this, this, SLOT(twitterActionTriggered())); twitterAction->setIcon(twitterIcon); twitterToolButton = new QToolButton(); twitterToolButton->setDefaultAction(twitterAction); } QStringList headerText; headerText.append("Spec File"); headerText.append("Path"); m_specFileTreeWidget = new QTreeWidget(); m_specFileTreeWidget->setHeaderLabels(headerText); QObject::connect(m_specFileTreeWidget, SIGNAL(itemClicked(QTreeWidgetItem*,int)), this, SLOT(specFileTreeWidgetItemClicked(QTreeWidgetItem*))); QObject::connect(m_specFileTreeWidget, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), this, SLOT(specFileTreeWidgetItemDoubleClicked(QTreeWidgetItem*))); QScrollArea* treeScrollArea = new QScrollArea(); treeScrollArea->setWidget(m_specFileTreeWidget); treeScrollArea->setWidgetResizable(true); QWidget* leftWidget = new QWidget(); QVBoxLayout* leftColumnLayout = new QVBoxLayout(leftWidget); if (imageLabel != NULL) { leftColumnLayout->addWidget(imageLabel); leftColumnLayout->addSpacing(15); } leftColumnLayout->addWidget(workbenchLabel); leftColumnLayout->addWidget(versionLabel); leftColumnLayout->addSpacing(10); leftColumnLayout->addWidget(hcpWebsiteLabel); if (twitterToolButton != NULL) { leftColumnLayout->addSpacing(3); leftColumnLayout->addWidget(twitterToolButton, 0, Qt::AlignHCenter); } leftColumnLayout->addStretch(); QWidget* widget = new QWidget(); QHBoxLayout* horizLayout = new QHBoxLayout(widget); horizLayout->addWidget(leftWidget); horizLayout->addWidget(treeScrollArea); const int32_t treeDesiredWidth = loadSpecFileTreeWidget(); m_openOtherSpecFilePushButton = addUserPushButton("Open Other...", QDialogButtonBox::AcceptRole); setCancelButtonText("Skip"); setOkButtonText("Open"); setCentralWidget(widget, WuQDialog::SCROLL_AREA_NEVER); /* * Set a minimum width so spec files are visible */ int totalWidth = (leftWidget->sizeHint().width() + treeDesiredWidth); const int maxWidth = std::max(WuQtUtilities::getMinimumScreenSize().width() - 300, 600); if (totalWidth > maxWidth) { totalWidth = maxWidth; } widget->setMinimumWidth(totalWidth); } /** * Destructor. */ SplashScreen::~SplashScreen() { } /** * @return The selected spec file name. */ AString SplashScreen::getSelectedSpecFileName() const { return m_selectedSpecFileName; } /** * Called when a label's hyperlink is selected. * @param link * The URL. */ void SplashScreen::websiteLinkActivated(const QString& link) { if (link.isEmpty() == false) { QDesktopServices::openUrl(QUrl(link)); } } /** * Display twitter page in web browser. */ void SplashScreen::twitterActionTriggered() { websiteLinkActivated("http://twitter.com/#!/HumanConnectome"); } /** * Called when spec file tree widget item is clicked. * @param item * Item clicked. */ void SplashScreen::specFileTreeWidgetItemClicked(QTreeWidgetItem* item) { m_selectedSpecFileName = ""; if (item != NULL) { m_selectedSpecFileName = item->data(0, Qt::UserRole).toString(); } } /** * Called when spec file tree widget item is double-clicked. * @param item * Item clicked. */ void SplashScreen::specFileTreeWidgetItemDoubleClicked(QTreeWidgetItem* item) { m_selectedSpecFileName = ""; if (item != NULL) { m_selectedSpecFileName = item->data(0, Qt::UserRole).toString(); /* * Accept is like hitting OK button */ this->accept(); } } /** * Called when a push button was added using addUserPushButton(). * * @param userPushButton * User push button that was pressed. */ WuQDialogModal::DialogUserButtonResult SplashScreen::userButtonPressed(QPushButton* userPushButton) { if (userPushButton == m_openOtherSpecFilePushButton) { chooseSpecFileViaOpenFileDialog(); } else { CaretAssertMessage(0, "Unrecognized user pushbutton clicked \"" + userPushButton->text() + "\""); } return WuQDialogModal::RESULT_NONE; } /** * Choose a spec file with the Open File Dialog. */ void SplashScreen::chooseSpecFileViaOpenFileDialog() { QStringList filenameFilterList; filenameFilterList.append(DataFileTypeEnum::toQFileDialogFilter(DataFileTypeEnum::SPECIFICATION)); CaretFileDialog fd(this); fd.setAcceptMode(CaretFileDialog::AcceptOpen); fd.setNameFilters(filenameFilterList); fd.setFileMode(CaretFileDialog::ExistingFile); fd.setViewMode(CaretFileDialog::List); AString errorMessages; if (fd.exec()) { QStringList selectedFiles = fd.selectedFiles(); if (selectedFiles.empty() == false) { m_selectedSpecFileName = selectedFiles.at(0); /* * Accept indicates user has 'accepted' the * dialog so dialog is closed (like OK clicked) */ accept(); } } } /** * Load Spec Files into the tree widget. */ int32_t SplashScreen::loadSpecFileTreeWidget() { m_specFileTreeWidget->clear(); QTreeWidgetItem* selectedItem = addDirectorySpecFiles(); QTreeWidgetItem* specItem = addRecentSpecFiles(); if (specItem != NULL) { selectedItem = specItem; } int nameColWidth = 0; int pathColWidth = 0; if (selectedItem != NULL) { m_specFileTreeWidget->setCurrentItem(selectedItem); nameColWidth = m_specFileTreeWidget->QAbstractItemView::sizeHintForColumn(0) + 25; pathColWidth = m_specFileTreeWidget->QAbstractItemView::sizeHintForColumn(1); m_specFileTreeWidget->setColumnWidth(0, nameColWidth); this->specFileTreeWidgetItemClicked(selectedItem); } int treeWidgetWidth = (nameColWidth + pathColWidth); if (treeWidgetWidth < 250) { treeWidgetWidth = 250; } m_specFileTreeWidget->setMinimumWidth(treeWidgetWidth); return treeWidgetWidth; } /** * Add recent spec files. */ QTreeWidgetItem* SplashScreen::addRecentSpecFiles() { CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); std::vector recentSpecFiles; prefs->getPreviousSpecFiles(recentSpecFiles); QTreeWidgetItem* firstItem = NULL; const int32_t numRecentSpecFiles = static_cast(recentSpecFiles.size()); for (int32_t i = 0; i < numRecentSpecFiles; i++) { QString path; QString name; QString fullPath; if (firstItem == NULL) { QStringList itemText; itemText.append("Recent Spec Files"); itemText.append("-------------------------------"); QTreeWidgetItem* titleItem = new QTreeWidgetItem(itemText); titleItem->setDisabled(true); m_specFileTreeWidget->addTopLevelItem(titleItem); } const QString specFileName = recentSpecFiles[i]; if (DataFile::isFileOnNetwork(specFileName)) { const int lastSlash = specFileName.lastIndexOf('/'); name = specFileName.mid(lastSlash + 1); path = specFileName.left(lastSlash); fullPath = specFileName; } else { FileInformation fileInfo(specFileName); path = fileInfo.getPathName().trimmed(); name = fileInfo.getFileName().trimmed(); fullPath = fileInfo.getAbsoluteFilePath(); } if (name.isEmpty() == false) { QStringList treeText; treeText.append(" " + name); treeText.append(path); QTreeWidgetItem* lwi = new QTreeWidgetItem(treeText); lwi->setData(0, Qt::UserRole, fullPath); lwi->setData(1, Qt::UserRole, fullPath); m_specFileTreeWidget->addTopLevelItem(lwi); if (firstItem == NULL) { firstItem = lwi; } } } return firstItem; } /** * Add spec files in current directory */ QTreeWidgetItem* SplashScreen::addDirectorySpecFiles() { Brain* brain = GuiManager::get()->getBrain(); const QString dirName = brain->getCurrentDirectory(); QTreeWidgetItem* firstItem = NULL; QStringList nameFilters; nameFilters.append("*." + DataFileTypeEnum::toFileExtension(DataFileTypeEnum::SPECIFICATION)); QDir dir(dirName); QStringList specFileList = dir.entryList(nameFilters, QDir::Files, QDir::Name); const int32_t numFiles = specFileList.count(); for (int32_t i = 0; i < numFiles; i++) { if (firstItem == NULL) { QStringList itemText; itemText.append("Current Directory"); itemText.append(dirName); QTreeWidgetItem* titleItem = new QTreeWidgetItem(itemText); titleItem->setDisabled(true); m_specFileTreeWidget->addTopLevelItem(titleItem); } FileInformation fileInfo(specFileList.at(i)); const QString name = fileInfo.getFileName().trimmed(); const QString fullPath = fileInfo.getAbsoluteFilePath(); QStringList treeText; treeText.append(" " + name); treeText.append(" . "); // Use . for current directory QTreeWidgetItem* lwi = new QTreeWidgetItem(treeText); lwi->setData(0, Qt::UserRole, fullPath); lwi->setData(1, Qt::UserRole, fullPath); m_specFileTreeWidget->addTopLevelItem(lwi); if (firstItem == NULL) { firstItem = lwi; } } return firstItem; } workbench-1.1.1/src/GuiQt/SplashScreen.h000066400000000000000000000044301255417355300200720ustar00rootroot00000000000000#ifndef __SPLASH_SCREEN__H_ #define __SPLASH_SCREEN__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "WuQDialogModal.h" class QPushButton; class QTreeWidget; class QTreeWidgetItem; namespace caret { class SplashScreen : public WuQDialogModal { Q_OBJECT public: SplashScreen(QWidget* parent = 0); virtual ~SplashScreen(); AString getSelectedSpecFileName() const; private slots: void websiteLinkActivated(const QString& link); void specFileTreeWidgetItemClicked(QTreeWidgetItem* item); void specFileTreeWidgetItemDoubleClicked(QTreeWidgetItem* item); void chooseSpecFileViaOpenFileDialog(); void twitterActionTriggered(); protected: WuQDialogModal::DialogUserButtonResult userButtonPressed(QPushButton* userPushButton); private: SplashScreen(const SplashScreen&); SplashScreen& operator=(const SplashScreen&); QTreeWidgetItem* addRecentSpecFiles(); QTreeWidgetItem* addDirectorySpecFiles(); int32_t loadSpecFileTreeWidget(); QTreeWidget* m_specFileTreeWidget; AString m_selectedSpecFileName; QPushButton* m_openOtherSpecFilePushButton; }; #ifdef __SPLASH_SCREEN_DECLARE__ // #endif // __SPLASH_SCREEN_DECLARE__ } // namespace #endif //__SPLASH_SCREEN__H_ workbench-1.1.1/src/GuiQt/StructureEnumComboBox.cxx000066400000000000000000000127521255417355300223370ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __STRUCTURE_ENUM_COMBOBOX_DECLARE__ #include "StructureEnumComboBox.h" #undef __STRUCTURE_ENUM_COMBOBOX_DECLARE__ #include "Brain.h" #include "BrainStructure.h" #include "GuiManager.h" using namespace caret; /** * \class caret::StructureEnumComboBox * \brief Control for selection of a structure. * \ingroup GuiQt */ /** * Constructor. * @param parent * The parent. */ StructureEnumComboBox::StructureEnumComboBox(QObject* parent) : WuQWidget(parent) { std::vector allStructures; StructureEnum::getAllEnums(allStructures); const int32_t numStructures = static_cast(allStructures.size()); this->structureComboBox = new QComboBox(); for (int32_t i = 0; i < numStructures; i++) { this->structureComboBox->addItem(StructureEnum::toGuiName(allStructures[i])); this->structureComboBox->setItemData(i, StructureEnum::toIntegerCode(allStructures[i])); } QObject::connect(this->structureComboBox, SIGNAL(activated(int)), this, SLOT(structureComboBoxSelection(int))); } /** * Destructor. */ StructureEnumComboBox::~StructureEnumComboBox() { } /** * @return Number of items in combo box. */ int StructureEnumComboBox::count() const { return structureComboBox->count(); } /** * Limit the available structure to the given structures * * @param structures * Structures for display in combo box. */ void StructureEnumComboBox::listOnlyTheseStructures(const std::vector& structures) { this->structureComboBox->clear(); this->structureComboBox->blockSignals(true); const int32_t numStructures = static_cast(structures.size()); for (int32_t i = 0; i < numStructures; i++) { const StructureEnum::Enum structure = structures[i]; this->structureComboBox->addItem(StructureEnum::toGuiName(structure)); this->structureComboBox->setItemData(i, StructureEnum::toIntegerCode(structure)); } this->structureComboBox->blockSignals(false); } /** * Limit selections to those structures that are loaded. */ void StructureEnumComboBox::listOnlyValidStructures() { this->structureComboBox->clear(); this->structureComboBox->blockSignals(true); const Brain* brain = GuiManager::get()->getBrain(); const int32_t numStructures = brain->getNumberOfBrainStructures(); for (int32_t i = 0; i < numStructures; i++) { const BrainStructure* bs = brain->getBrainStructure(i); const StructureEnum::Enum structure = bs->getStructure(); this->structureComboBox->addItem(StructureEnum::toGuiName(structure)); this->structureComboBox->setItemData(i, StructureEnum::toIntegerCode(structure)); } this->structureComboBox->blockSignals(false); } /** * Called to set the selected structure. * @param structure * New value for structure. */ void StructureEnumComboBox::setSelectedStructure(const StructureEnum::Enum structure) { const int32_t structureIntegerCode = StructureEnum::toIntegerCode(structure); const int numStructures = this->structureComboBox->count(); for (int32_t i = 0; i < numStructures; i++) { if (structureIntegerCode == this->structureComboBox->itemData(i).toInt()) { if (this->signalsBlocked()) { this->structureComboBox->blockSignals(true); } this->structureComboBox->setCurrentIndex(i); if (this->signalsBlocked()) { this->structureComboBox->blockSignals(false); } break; } } } /** * @return The selected structure. */ StructureEnum::Enum StructureEnumComboBox::getSelectedStructure() const { StructureEnum::Enum structure = StructureEnum::INVALID; const int32_t indx = this->structureComboBox->currentIndex(); if (indx >= 0) { const int32_t integerCode = this->structureComboBox->itemData(indx).toInt(); structure = StructureEnum::fromIntegerCode(integerCode, NULL); } return structure; } /** * @return The widget for this control. */ QWidget* StructureEnumComboBox::getWidget() { return this->structureComboBox; } /** * Called when a structure is selected * @param indx * Index of selection. */ void StructureEnumComboBox::structureComboBoxSelection(int indx) { if (this->signalsBlocked() == false) { if ((indx >= 0) && (indx < this->structureComboBox->count())) { const int32_t integerCode = this->structureComboBox->itemData(indx).toInt(); StructureEnum::Enum structure = StructureEnum::fromIntegerCode(integerCode, NULL); emit structureSelected(structure); } } } workbench-1.1.1/src/GuiQt/StructureEnumComboBox.h000066400000000000000000000042231255417355300217560ustar00rootroot00000000000000#ifndef __STRUCTURE_ENUM_COMBOBOX__H_ #define __STRUCTURE_ENUM_COMBOBOX__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "StructureEnum.h" #include "WuQWidget.h" namespace caret { class Surface; class StructureEnumComboBox : public WuQWidget { Q_OBJECT public: StructureEnumComboBox(QObject* parent); virtual ~StructureEnumComboBox(); StructureEnum::Enum getSelectedStructure() const; int count() const; void listOnlyTheseStructures(const std::vector& structures); void listOnlyValidStructures(); QWidget* getWidget(); public slots: void setSelectedStructure(const StructureEnum::Enum structure); signals: void structureSelected(const StructureEnum::Enum); private slots: void structureComboBoxSelection(int); private: StructureEnumComboBox(const StructureEnumComboBox&); StructureEnumComboBox& operator=(const StructureEnumComboBox&); QComboBox* structureComboBox; public: private: }; #ifdef __STRUCTURE_ENUM_COMBOBOX_DECLARE__ // #endif // __STRUCTURE_ENUM_COMBOBOX_DECLARE__ } // namespace #endif //__STRUCTURE_ENUM_COMBOBOX__H_ workbench-1.1.1/src/GuiQt/StructureSurfaceSelectionControl.cxx000066400000000000000000000230541255417355300245760ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include "Brain.h" #include "BrainStructure.h" #include "CaretAssert.h" #include "EventManager.h" #include "EventModelGetAll.h" #include "WuQFactory.h" #include "GuiManager.h" #include "WuQtUtilities.h" #define __STRUCTURE_SURFACE_SELECTION_CONTROL_DECLARE__ #include "StructureSurfaceSelectionControl.h" #undef __STRUCTURE_SURFACE_SELECTION_CONTROL_DECLARE__ #include "ModelSurface.h" #include "ModelSurfaceSelector.h" #include "Surface.h" using namespace caret; /** * \class caret::StructureSufaceSelectionControl * \brief * \ingroup GuiQt */ /** * Constructor. */ StructureSurfaceSelectionControl::StructureSurfaceSelectionControl(const bool showLabels) : QWidget() { this->surfaceControllerSelector = NULL; this->structureSelectionComboBox = WuQFactory::newComboBox(); this->structureSelectionComboBox->setSizeAdjustPolicy(QComboBox::AdjustToContents); QObject::connect(this->structureSelectionComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(structureSelected(int))); this->surfaceControllerSelectionComboBox = WuQFactory::newComboBox(); this->surfaceControllerSelectionComboBox->setSizeAdjustPolicy(QComboBox::AdjustToContents); QObject::connect(this->surfaceControllerSelectionComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(surfaceControllerSelected(int))); QGridLayout* layout = new QGridLayout(this); layout->setColumnStretch(0, 0); layout->setColumnStretch(1, 100); WuQtUtilities::setLayoutSpacingAndMargins(layout, 3, 2); if (showLabels) { QLabel* structureLabel = new QLabel("Structure"); QLabel* surfaceLabel = new QLabel("Surface"); layout->addWidget(structureLabel, 0, 0); layout->addWidget(surfaceLabel, 1, 0); } layout->addWidget(this->structureSelectionComboBox, 0, 1); layout->addWidget(this->surfaceControllerSelectionComboBox, 1, 1); } /** * Destructor. */ StructureSurfaceSelectionControl::~StructureSurfaceSelectionControl() { } void StructureSurfaceSelectionControl::structureSelected(int currentIndex) { CaretAssert((currentIndex >= 0) && (currentIndex < this->structureSelectionComboBox->count())); int32_t structID = this->structureSelectionComboBox->itemData(currentIndex).toInt(); StructureEnum::Enum selectedStructure = StructureEnum::fromIntegerCode(structID, NULL); this->surfaceControllerSelector->setSelectedStructure(selectedStructure); emitSelectionChangedSignal(); // emit selectionChanged(this->surfaceControllerSelector->getSelectedStructure(), // this->surfaceControllerSelector->getSelectedSurfaceModel()); this->updateControlAfterSelection(); } void StructureSurfaceSelectionControl::surfaceControllerSelected(int currentIndex) { CaretAssert((currentIndex >= 0) && (currentIndex < this->surfaceControllerSelectionComboBox->count())); void* pointer = this->surfaceControllerSelectionComboBox->itemData(currentIndex).value(); ModelSurface* surfaceController = (ModelSurface*)pointer; this->surfaceControllerSelector->setSelectedSurfaceModel(surfaceController); emitSelectionChangedSignal(); // emit selectionChanged(this->surfaceControllerSelector->getSelectedStructure(), // this->surfaceControllerSelector->getSelectedSurfaceModel()); } /** * Emit the selection changed signal. * Also preserves mouse focus that can be disrupted by user-interface updates. */ void StructureSurfaceSelectionControl::emitSelectionChangedSignal() { const bool structureHasFocus = this->structureSelectionComboBox->hasFocus(); const bool surfaceHasFocus = this->surfaceControllerSelectionComboBox->hasFocus(); emit selectionChanged(this->surfaceControllerSelector->getSelectedStructure(), this->surfaceControllerSelector->getSelectedSurfaceModel()); if (structureHasFocus) { this->structureSelectionComboBox->setFocus(); } else if (surfaceHasFocus) { this->surfaceControllerSelectionComboBox->setFocus(); } } /** * @return The selected mode surface. */ ModelSurface* StructureSurfaceSelectionControl::getSelectedSurfaceModel() { return this->surfaceControllerSelector->getSelectedSurfaceModel(); } /** * @return The selected structure. */ StructureEnum::Enum StructureSurfaceSelectionControl::getSelectedStructure() { return this->surfaceControllerSelector->getSelectedStructure(); } /* void StructureSurfaceSelectionControl::setSelectedSurfaceModel( ModelSurface* surfaceController) { this->surfaceControllerSelector->setSelectedSurfaceModel(surfaceController); this->updateControl(); } void StructureSurfaceSelectionControl::setSelectedStructure(const StructureEnum::Enum selectedStructure) { this->surfaceControllerSelector->setSelectedStructure(selectedStructure); this->updateControl(); } */ void StructureSurfaceSelectionControl::updateControl(ModelSurfaceSelector* surfaceControllerSelector) { this->surfaceControllerSelector = surfaceControllerSelector; this->updateControlAfterSelection(); } void StructureSurfaceSelectionControl::updateControlAfterSelection() { /* * Don't let any signals get sent by updating. */ this->structureSelectionComboBox->blockSignals(true); this->surfaceControllerSelectionComboBox->blockSignals(true); this->structureSelectionComboBox->clear(); this->surfaceControllerSelectionComboBox->clear(); if (this->surfaceControllerSelector == NULL) { return; } std::vector availableSurfaceModels; std::vector availableStructures; this->surfaceControllerSelector->getSelectableStructures(availableStructures); this->surfaceControllerSelector->getSelectableSurfaceModels(availableSurfaceModels); Surface* primaryAnatomicalSurface = NULL; /* * Update the structure selection. */ StructureEnum::Enum selectedStructure = this->surfaceControllerSelector->getSelectedStructure(); int32_t defaultStructureIndex = 0; for (int32_t i = 0; i < static_cast(availableStructures.size()); i++) { StructureEnum::Enum structType = availableStructures[i]; if (structType == selectedStructure) { defaultStructureIndex = i; } this->structureSelectionComboBox->addItem(StructureEnum::toGuiName(structType), StructureEnum::toIntegerCode(structType)); } if (defaultStructureIndex < this->structureSelectionComboBox->count()) { this->structureSelectionComboBox->setCurrentIndex(defaultStructureIndex); BrainStructure* brainStructure = GuiManager::get()->getBrain()->getBrainStructure(availableStructures[defaultStructureIndex], false); if (brainStructure != NULL) { primaryAnatomicalSurface = brainStructure->getPrimaryAnatomicalSurface(); } } const bool allSelected (selectedStructure == StructureEnum::ALL); /* * Update the surface selection. */ ModelSurface* selectedSurfaceController = this->surfaceControllerSelector->getSelectedSurfaceModel(); int32_t defaultSurfaceIndex = -1; int32_t primaryAnatomicalSurfaceIndex = -1; for (std::vector::const_iterator iter = availableSurfaceModels.begin(); iter != availableSurfaceModels.end(); iter++) { ModelSurface* surfaceController = *iter; this->surfaceControllerSelectionComboBox->addItem(surfaceController->getNameForGUI(allSelected), qVariantFromValue((void*)surfaceController)); if (selectedSurfaceController == surfaceController) { defaultSurfaceIndex = this->surfaceControllerSelectionComboBox->count() - 1; } if (surfaceController->getSurface() == primaryAnatomicalSurface) { primaryAnatomicalSurfaceIndex = this->surfaceControllerSelectionComboBox->count() - 1; } } if (defaultSurfaceIndex < 0) { if (primaryAnatomicalSurfaceIndex >= 0) { defaultSurfaceIndex = primaryAnatomicalSurfaceIndex; } else if (this->surfaceControllerSelectionComboBox->count() > 0) { defaultSurfaceIndex = 0; } } if (defaultSurfaceIndex < this->surfaceControllerSelectionComboBox->count()) { this->surfaceControllerSelectionComboBox->setCurrentIndex(defaultSurfaceIndex); } this->structureSelectionComboBox->blockSignals(false); this->surfaceControllerSelectionComboBox->blockSignals(false); } workbench-1.1.1/src/GuiQt/StructureSurfaceSelectionControl.h000066400000000000000000000053171255417355300242250ustar00rootroot00000000000000#ifndef __STRUCTURE_SURFACE_SELECTION_CONTROL__H_ #define __STRUCTURE_SURFACE_SELECTION_CONTROL__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "StructureEnum.h" class QComboBox; namespace caret { class ModelSurfaceSelector; class ModelSurface; class Structure; class StructureSurfaceSelectionControl : public QWidget { Q_OBJECT public: StructureSurfaceSelectionControl(const bool showLabels); virtual ~StructureSurfaceSelectionControl(); ModelSurface* getSelectedSurfaceModel(); StructureEnum::Enum getSelectedStructure(); //void setSelectedSurfaceModel(ModelSurface* surfaceController); //void setSelectedStructure(const StructureEnum::Enum selectedStructure); void updateControl(ModelSurfaceSelector* surfaceControllerSelector); signals: void selectionChanged(const StructureEnum::Enum selectedStructure, ModelSurface* surfaceController); private slots: void structureSelected(int currentIndex); void surfaceControllerSelected(int currentIndex); private: void updateControlAfterSelection(); void emitSelectionChangedSignal(); StructureSurfaceSelectionControl(const StructureSurfaceSelectionControl&); StructureSurfaceSelectionControl& operator=(const StructureSurfaceSelectionControl&); QComboBox* structureSelectionComboBox; QComboBox* surfaceControllerSelectionComboBox; ModelSurfaceSelector* surfaceControllerSelector; public: private: }; #ifdef __STRUCTURE_SURFACE_SELECTION_CONTROL_DECLARE__ // #endif // __STRUCTURE_SURFACE_SELECTION_CONTROL_DECLARE__ } // namespace #endif //__STRUCTURE_SURFACE_SELECTION_CONTROL__H_ workbench-1.1.1/src/GuiQt/SurfacePropertiesEditorDialog.cxx000066400000000000000000000212711255417355300240110ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SURFACE_PROPERTIES_EDITOR_DIALOG_DECLARE__ #include "SurfacePropertiesEditorDialog.h" #undef __SURFACE_PROPERTIES_EDITOR_DIALOG_DECLARE__ #include #include #include #include using namespace caret; #include "Brain.h" #include "CaretAssert.h" #include "DisplayPropertiesSurface.h" #include "GuiManager.h" #include "EnumComboBoxTemplate.h" #include "EventGraphicsUpdateAllWindows.h" #include "EventSurfaceColoringInvalidate.h" #include "EventManager.h" #include "EventUserInterfaceUpdate.h" #include "SceneClass.h" #include "SceneWindowGeometry.h" #include "WuQFactory.h" #include "WuQTrueFalseComboBox.h" #include "WuQtUtilities.h" /** * \class caret::SurfacePropertiesEditorDialog * \brief Dialog for adjusting surface display properties. * \ingroup GuitQt */ /** * Constructor. */ SurfacePropertiesEditorDialog::SurfacePropertiesEditorDialog(QWidget* parent) : WuQDialogNonModal("Surface Properties", parent) { m_updateInProgress = true; QLabel* surfaceDrawingTypeLabel = new QLabel("Drawing Type: "); m_surfaceDrawingTypeComboBox = new EnumComboBoxTemplate(this); QObject::connect(m_surfaceDrawingTypeComboBox, SIGNAL(itemActivated()), this, SLOT(surfaceDisplayPropertyChanged())); m_surfaceDrawingTypeComboBox->setup(); QLabel* linkSizeLabel = new QLabel("Link Diameter: "); m_linkSizeSpinBox = WuQFactory::newDoubleSpinBox(); m_linkSizeSpinBox->setRange(0.0, std::numeric_limits::max()); m_linkSizeSpinBox->setSingleStep(1.0); m_linkSizeSpinBox->setDecimals(1); m_linkSizeSpinBox->setSuffix("mm"); QObject::connect(m_linkSizeSpinBox, SIGNAL(valueChanged(double)), this, SLOT(surfaceDisplayPropertyChanged())); QLabel* nodeSizeLabel = new QLabel("Vertex Diameter: "); m_nodeSizeSpinBox = WuQFactory::newDoubleSpinBox(); m_nodeSizeSpinBox->setRange(0.0, std::numeric_limits::max()); m_nodeSizeSpinBox->setSingleStep(1.0); m_nodeSizeSpinBox->setDecimals(1); m_nodeSizeSpinBox->setSuffix("mm"); QObject::connect(m_nodeSizeSpinBox, SIGNAL(valueChanged(double)), this, SLOT(surfaceDisplayPropertyChanged())); QLabel* displayNormalVectorsLabel = new QLabel("Display Normal Vectors: "); m_displayNormalVectorsComboBox = new WuQTrueFalseComboBox(this); QObject::connect(m_displayNormalVectorsComboBox, SIGNAL(statusChanged(bool)), this, SLOT(surfaceDisplayPropertyChanged())); QLabel* opacityLabel = new QLabel("Opacity: "); m_opacitySpinBox = WuQFactory::newDoubleSpinBox(); m_opacitySpinBox->setRange(0.0, 1.0); m_opacitySpinBox->setSingleStep(0.1); m_opacitySpinBox->setDecimals(2); QObject::connect(m_opacitySpinBox, SIGNAL(valueChanged(double)), this, SLOT(surfaceDisplayPropertyChanged())); QWidget* w = new QWidget(); QGridLayout* gridLayout = new QGridLayout(w); WuQtUtilities::setLayoutSpacingAndMargins(gridLayout, 2, 2); int row = gridLayout->rowCount(); gridLayout->addWidget(displayNormalVectorsLabel, row, 0); gridLayout->addWidget(m_displayNormalVectorsComboBox->getWidget(), row, 1); row++; gridLayout->addWidget(surfaceDrawingTypeLabel, row, 0); gridLayout->addWidget(m_surfaceDrawingTypeComboBox->getWidget(), row, 1); row++; gridLayout->addWidget(linkSizeLabel, row, 0); gridLayout->addWidget(m_linkSizeSpinBox, row, 1); row++; gridLayout->addWidget(nodeSizeLabel, row, 0); gridLayout->addWidget(m_nodeSizeSpinBox, row, 1); row++; gridLayout->addWidget(opacityLabel, row, 0); gridLayout->addWidget(m_opacitySpinBox, row, 1); row++; setCentralWidget(w, WuQDialog::SCROLL_AREA_NEVER); updateDialog(); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_USER_INTERFACE_UPDATE); /* * No apply button */ setApplyButtonText(""); } /** * Destructor. */ SurfacePropertiesEditorDialog::~SurfacePropertiesEditorDialog() { EventManager::get()->removeAllEventsFromListener(this); } /** * Called when a surface display property is changed. */ void SurfacePropertiesEditorDialog::surfaceDisplayPropertyChanged() { /* * Updating some widgets causes signals to be emitted */ if (m_updateInProgress) { return; } const SurfaceDrawingTypeEnum::Enum surfaceDrawingType = m_surfaceDrawingTypeComboBox->getSelectedItem(); DisplayPropertiesSurface* dps = GuiManager::get()->getBrain()->getDisplayPropertiesSurface(); dps->setSurfaceDrawingType(surfaceDrawingType); dps->setDisplayNormalVectors(m_displayNormalVectorsComboBox->isTrue()); dps->setLinkSize(m_linkSizeSpinBox->value()); dps->setNodeSize(m_nodeSizeSpinBox->value()); dps->setOpacity(m_opacitySpinBox->value()); EventManager::get()->sendEvent(EventSurfaceColoringInvalidate().getPointer()); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } /** * Update the properties editor. */ void SurfacePropertiesEditorDialog::updateDialog() { m_updateInProgress = true; const DisplayPropertiesSurface* dps = GuiManager::get()->getBrain()->getDisplayPropertiesSurface(); m_surfaceDrawingTypeComboBox->setSelectedItem(dps->getSurfaceDrawingType()); m_displayNormalVectorsComboBox->setStatus(dps->isDisplayNormalVectors()); m_linkSizeSpinBox->setValue(dps->getLinkSize()); m_nodeSizeSpinBox->setValue(dps->getNodeSize()); m_opacitySpinBox->setValue(dps->getOpacity()); m_updateInProgress = false; } /** * Receive events from the event manager. * * @param event * Event sent by event manager. */ void SurfacePropertiesEditorDialog::receiveEvent(Event* event) { if (event->getEventType() == EventTypeEnum::EVENT_USER_INTERFACE_UPDATE) { CaretAssert(dynamic_cast(event) != NULL); updateDialog(); } } /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ SceneClass* SurfacePropertiesEditorDialog::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "SurfacePropertiesEditorDialog", 1); /* * Position and size */ SceneWindowGeometry swg(this); sceneClass->addClass(swg.saveToScene(sceneAttributes, "geometry")); return sceneClass; } /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * SceneClass containing the state that was previously * saved and should be restored. */ void SurfacePropertiesEditorDialog::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } /* * Position and size */ SceneWindowGeometry swg(this); swg.restoreFromScene(sceneAttributes, sceneClass->getClass("geometry")); } workbench-1.1.1/src/GuiQt/SurfacePropertiesEditorDialog.h000066400000000000000000000052161255417355300234370ustar00rootroot00000000000000#ifndef __SURFACE_PROPERTIES_EDITOR_DIALOG__H_ #define __SURFACE_PROPERTIES_EDITOR_DIALOG__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "EventListenerInterface.h" #include "SceneableInterface.h" #include "WuQDialogNonModal.h" class QDoubleSpinBox; namespace caret { class EnumComboBoxTemplate; class WuQTrueFalseComboBox; class SurfacePropertiesEditorDialog : public WuQDialogNonModal, public EventListenerInterface, public SceneableInterface { Q_OBJECT public: SurfacePropertiesEditorDialog(QWidget* parent = 0); virtual ~SurfacePropertiesEditorDialog(); void receiveEvent(Event* event); void updateDialog(); virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); private slots: void surfaceDisplayPropertyChanged(); private: SurfacePropertiesEditorDialog(const SurfacePropertiesEditorDialog&); SurfacePropertiesEditorDialog& operator=(const SurfacePropertiesEditorDialog&); WuQTrueFalseComboBox* m_displayNormalVectorsComboBox; QDoubleSpinBox* m_linkSizeSpinBox; QDoubleSpinBox* m_nodeSizeSpinBox; EnumComboBoxTemplate* m_surfaceDrawingTypeComboBox; QDoubleSpinBox* m_opacitySpinBox; bool m_updateInProgress; // ADD_NEW_MEMBERS_HERE }; #ifdef __SURFACE_PROPERTIES_EDITOR_DIALOG_DECLARE__ // #endif // __SURFACE_PROPERTIES_EDITOR_DIALOG_DECLARE__ } // namespace #endif //__SURFACE_PROPERTIES_EDITOR_DIALOG__H_ workbench-1.1.1/src/GuiQt/SurfaceSelectionViewController.cxx000066400000000000000000000200061255417355300242050ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SURFACE_SELECTION_CONTROL_DECLARE__ #include "SurfaceSelectionViewController.h" #undef __SURFACE_SELECTION_CONTROL_DECLARE__ #include #include "BrainStructure.h" #include "Surface.h" #include "SurfaceSelectionModel.h" #include "WuQEventBlockingFilter.h" #include "WuQFactory.h" using namespace caret; /** * \class caret::SurfaceSelectionViewController * \brief Control for selecting surfaces. * \ingroup GuiQt * * Control for selecting surfaces. */ /** * Constructor. * @param parent * The parent. * @param surfaceSelection * Surface selection that is controlled through this control. */ SurfaceSelectionViewController::SurfaceSelectionViewController(QObject* parent, SurfaceSelectionModel* surfaceSelectionModel) : WuQWidget(parent) { this->initializeControl(MODE_SELECTION_MODEL_STATIC, surfaceSelectionModel); } /** * Constructor. * @param parent * The parent. * @param brainStructure * Allows selection of any surface with the specified brain structure. */ SurfaceSelectionViewController::SurfaceSelectionViewController(QObject* parent, BrainStructure* brainStructure) : WuQWidget(parent) { std::vector allSurfaceTypes; SurfaceTypeEnum::getAllEnums(allSurfaceTypes); SurfaceSelectionModel* ss = new SurfaceSelectionModel(brainStructure->getStructure(), allSurfaceTypes); this->initializeControl(MODE_BRAIN_STRUCTURE, ss); } /** * Destructor. */ SurfaceSelectionViewController::~SurfaceSelectionViewController() { bool isDeleteSelectionModel = false; switch (this->mode) { case MODE_BRAIN_STRUCTURE: isDeleteSelectionModel = true; break; case MODE_SELECTION_MODEL_DYNAMIC: break; case MODE_SELECTION_MODEL_STATIC: break; } if (isDeleteSelectionModel) { delete this->surfaceSelectionModel; } } /** * Constructor. Creates a selection control. User MUST call updateControl(SurfaceSelectionModel*) * so that surfaces get loaded. A instance created this way will NEVER use the selection * model this is passed to updateControl() anywhere outside of updateControl(). Thus, * slots in the model are NEVER called. * * @param parent * The parent. */ SurfaceSelectionViewController::SurfaceSelectionViewController(QObject* parent) : WuQWidget(parent) { this->initializeControl(MODE_SELECTION_MODEL_DYNAMIC, NULL); } /** * Help initialize an instance. */ void SurfaceSelectionViewController::initializeControl(const Mode mode, SurfaceSelectionModel* surfaceSelectionModel) { this->mode = mode; this->surfaceComboBox = WuQFactory::newComboBox(); this->surfaceComboBox->setSizeAdjustPolicy(QComboBox::AdjustToContents); QObject::connect(this->surfaceComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(comboBoxCurrentIndexChanged(int))); //#ifdef CARET_OS_MACOSX // /* // * Block the wheel event on Mac since it get issued when a mouse // * is moved over the combo box. // */ // WuQEventBlockingFilter* comboBoxWheelEventBlockingFilter = new WuQEventBlockingFilter(this); // comboBoxWheelEventBlockingFilter->setEventBlocked(QEvent::Wheel, // true); // surfaceComboBox->installEventFilter(comboBoxWheelEventBlockingFilter); //#endif // CARET_OS_MACOSX this->surfaceSelectionModel = surfaceSelectionModel; } /** * Update the control using the given selection model. The model is NEVER * used outside this method so slots in the model are NEVER called. * * @param selectionModel * Selection model used to update this control. If this parameter is NULL * all selections are removed from the control. */ void SurfaceSelectionViewController::updateControl(SurfaceSelectionModel* selectionModel) { std::vector surfaces; Surface* selectedSurface = NULL; if (selectionModel != NULL) { surfaces = selectionModel->getAvailableSurfaces(); selectedSurface = selectionModel->getSurface(); } this->surfaceComboBox->blockSignals(true); int32_t selectedIndex = 0; this->surfaceComboBox->clear(); const int32_t numSurfaces = static_cast(surfaces.size()); for (int32_t i = 0; i < numSurfaces; i++) { const int32_t indx = this->surfaceComboBox->count(); this->surfaceComboBox->addItem(surfaces[i]->getFileNameNoPath()); this->surfaceComboBox->setItemData(indx, qVariantFromValue((void*)surfaces[i])); if (surfaces[i] == selectedSurface) { selectedIndex = indx; } } if (numSurfaces > 0) { this->surfaceComboBox->setCurrentIndex(selectedIndex); } this->surfaceComboBox->blockSignals(false); } /** * Update the control. */ void SurfaceSelectionViewController::updateControl() { CaretAssertMessage(this->surfaceSelectionModel, "The surface selection model is NULL, you should have called " "updateControl(SurfaceSelectionModel*)"); this->updateControl(this->surfaceSelectionModel); } /** * @return the actual widget. */ QWidget* SurfaceSelectionViewController::getWidget() { return this->surfaceComboBox; } /** * @return the selected surface. NULL if no surface selected. */ Surface* SurfaceSelectionViewController::getSurface() { //return this->surfaceSelectionModel->getSurface(); Surface* s = NULL; const int indx = this->surfaceComboBox->currentIndex(); if ((indx >= 0) && (indx < this->surfaceComboBox->count())) { void* pointer = this->surfaceComboBox->itemData(indx).value(); s = (Surface*)pointer; } return s; } /** * Set the selected surface. * @param surface * The selected surface. */ void SurfaceSelectionViewController::setSurface(Surface* surface) { if (this->surfaceSelectionModel != NULL) { this->surfaceSelectionModel->setSurface(surface); this->updateControl(); } else { const int32_t numItems = this->surfaceComboBox->count(); for (int32_t i = 0; i < numItems; i++) { void* pointer = this->surfaceComboBox->itemData(i).value(); Surface* s = (Surface*)pointer; if (surface == s) { this->surfaceComboBox->blockSignals(true); this->surfaceComboBox->setCurrentIndex(i); this->surfaceComboBox->blockSignals(false); break; } } } } /** * Called when the item in the combo box is changed. * @param indx * Index of item that was selected. */ void SurfaceSelectionViewController::comboBoxCurrentIndexChanged(int indx) { void* pointer = this->surfaceComboBox->itemData(indx).value(); Surface* s = (Surface*)pointer; if (this->surfaceSelectionModel != NULL) { this->surfaceSelectionModel->setSurface(s); } emit surfaceSelected(s); } workbench-1.1.1/src/GuiQt/SurfaceSelectionViewController.h000066400000000000000000000057351255417355300236460ustar00rootroot00000000000000#ifndef __SURFACE_SELECTION_CONTROL__H_ #define __SURFACE_SELECTION_CONTROL__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "WuQWidget.h" class QComboBox; namespace caret { class BrainStructure; class Surface; class SurfaceSelectionModel; class SurfaceSelectionViewController : public WuQWidget { Q_OBJECT public: SurfaceSelectionViewController(QObject* parent, SurfaceSelectionModel* surfaceSelectionModel); SurfaceSelectionViewController(QObject* parent, BrainStructure* brainStructure); SurfaceSelectionViewController(QObject* parent); virtual ~SurfaceSelectionViewController(); QWidget* getWidget(); Surface* getSurface(); void updateControl(); void updateControl(SurfaceSelectionModel* surfaceSelectionModel); signals: void surfaceSelected(Surface*); public slots: void setSurface(Surface*); private slots: void comboBoxCurrentIndexChanged(int); private: SurfaceSelectionViewController(const SurfaceSelectionViewController&); SurfaceSelectionViewController& operator=(const SurfaceSelectionViewController&); private: enum Mode { /** Use all surfaces from a brain structure */ MODE_BRAIN_STRUCTURE, /** Selection mode NOT passed to constructor, user must use updateControl(SurfaceSelectionModel*) */ MODE_SELECTION_MODEL_DYNAMIC, /** Selection model passed to constructor, and use it */ MODE_SELECTION_MODEL_STATIC }; void initializeControl(const Mode mode, SurfaceSelectionModel* surfaceSelectionModel); Mode mode; SurfaceSelectionModel* surfaceSelectionModel; QComboBox* surfaceComboBox; }; #ifdef __SURFACE_SELECTION_CONTROL_DECLARE__ // #endif // __SURFACE_SELECTION_CONTROL_DECLARE__ } // namespace #endif //__SURFACE_SELECTION_CONTROL__H_ workbench-1.1.1/src/GuiQt/TileTabsConfigurationDialog.cxx000066400000000000000000000655031255417355300234420ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #include #include #include #include #include #include #include #define __TILE_TABS_CONFIGURATION_DIALOG_DECLARE__ #include "TileTabsConfigurationDialog.h" #undef __TILE_TABS_CONFIGURATION_DIALOG_DECLARE__ #include "BrainBrowserWindow.h" #include "CaretAssert.h" #include "CaretPreferences.h" #include "EventGraphicsUpdateOneWindow.h" #include "EventManager.h" #include "GuiManager.h" #include "SessionManager.h" #include "TileTabsConfiguration.h" #include "WuQDataEntryDialog.h" #include "WuQFactory.h" #include "WuQMessageBox.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::TileTabsConfigurationDialog * \brief Edit and create configurations for tile tabs viewing. * \ingroup GuiQt */ /** * Constructor. */ TileTabsConfigurationDialog::TileTabsConfigurationDialog(QWidget* parent) : WuQDialogNonModal("Tile Tabs Configuration", parent) { m_blockReadConfigurationsFromPreferences = false; m_caretPreferences = SessionManager::get()->getCaretPreferences(); QWidget* dialogWidget = new QWidget(); QVBoxLayout* dialogLayout = new QVBoxLayout(dialogWidget); dialogLayout->setSpacing(0); dialogLayout->addWidget(createConfigurationSelectionWidget(), 0, Qt::AlignHCenter); dialogLayout->addWidget(createEditConfigurationWidget(), 100, Qt::AlignHCenter); setCentralWidget(dialogWidget, WuQDialog::SCROLL_AREA_NEVER); disableAutoDefaultForAllPushButtons(); setApplyButtonText(""); // EventManager::get()->addEventListener(this, EventTypeEnum::); updateDialog(); /* * The content region of a scroll area is often too large vertically * so adjust the size of the dialog which will cause the scroll area * to approximately fit its content. */ WuQDialog::adjustSizeOfDialogWithScrollArea(this, m_stretchFactorScrollArea); } /** * Destructor. */ TileTabsConfigurationDialog::~TileTabsConfigurationDialog() { } /** * Gets called when the dialog gains focus. */ void TileTabsConfigurationDialog::focusGained() { updateDialog(); } /** * @return The configuration selection widget. */ QWidget* TileTabsConfigurationDialog::createConfigurationSelectionWidget() { QLabel* configurationLabel = new QLabel("Configuration"); m_configurationSelectionComboBox = WuQFactory::newComboBoxSignalInt(this, SLOT(configurationComboBoxItemSelected(int))); QHBoxLayout* selectionLayout = new QHBoxLayout(); WuQtUtilities::setLayoutMargins(selectionLayout, 0); selectionLayout->addWidget(configurationLabel, 0); selectionLayout->addWidget(m_configurationSelectionComboBox, 100); m_newConfigurationPushButton = new QPushButton("New..."); QObject::connect(m_newConfigurationPushButton, SIGNAL(clicked()), this, SLOT(newConfigurationButtonClicked())); m_renameConfigurationPushButton = new QPushButton("Rename..."); QObject::connect(m_renameConfigurationPushButton, SIGNAL(clicked()), this, SLOT(renameConfigurationButtonClicked())); m_deleteConfigurationPushButton = new QPushButton("Delete..."); QObject::connect(m_deleteConfigurationPushButton, SIGNAL(clicked()), this, SLOT(deleteConfigurationButtonClicked())); QHBoxLayout* buttonsLayout = new QHBoxLayout(); buttonsLayout->addWidget(m_newConfigurationPushButton); buttonsLayout->addStretch(); buttonsLayout->addWidget(m_renameConfigurationPushButton); buttonsLayout->addStretch(); buttonsLayout->addWidget(m_deleteConfigurationPushButton); QGroupBox* configurationWidget = new QGroupBox("Configuration Selection"); QVBoxLayout* configurationLayout = new QVBoxLayout(configurationWidget); configurationLayout->addLayout(selectionLayout); configurationLayout->addLayout(buttonsLayout); return configurationWidget; } /** * @return The edit configuration widget. */ QWidget* TileTabsConfigurationDialog::createEditConfigurationWidget() { const int32_t maximuNumberOfRows = TileTabsConfiguration::getMaximumNumberOfRows(); const int32_t maximumNumberOfColumns = TileTabsConfiguration::getMaximumNumberOfColumns(); QLabel* rowsLabel = new QLabel("Number of Rows"); QLabel* columnsLabel = new QLabel("Number of Columns"); m_numberOfRowsSpinBox = WuQFactory::newSpinBoxWithMinMaxStepSignalInt(1, maximuNumberOfRows, 1, this, SLOT(numberOfRowsOrColumnsChanged())); m_numberOfColumnsSpinBox = WuQFactory::newSpinBoxWithMinMaxStepSignalInt(1, maximumNumberOfColumns, 1, this, SLOT(numberOfRowsOrColumnsChanged())); QWidget* numberOfWidget = new QWidget(); QGridLayout* numberOfGridLayout = new QGridLayout(numberOfWidget); WuQtUtilities::setLayoutMargins(numberOfGridLayout, 0); numberOfGridLayout->addWidget(rowsLabel, 0, 0); numberOfGridLayout->addWidget(m_numberOfRowsSpinBox, 0, 1); numberOfGridLayout->addWidget(columnsLabel, 1, 0); numberOfGridLayout->addWidget(m_numberOfColumnsSpinBox, 1, 1); numberOfWidget->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); QLabel* stretchFactorLabel = new QLabel("Stretch Factors"); QLabel* indexLabel = new QLabel("Index"); QLabel* rowLabel = new QLabel("Row"); QLabel* columnLabel = new QLabel("Column"); m_stretchFactorWidget = new QWidget(); QGridLayout* stretchFactorGridLayout = new QGridLayout(m_stretchFactorWidget); WuQtUtilities::setLayoutMargins(stretchFactorGridLayout, 2); stretchFactorGridLayout->setSizeConstraint(QLayout::SetFixedSize); int row = 0; stretchFactorGridLayout->addWidget(indexLabel, row, GRID_LAYOUT_COLUMN_INDEX_FOR_LABELS, Qt::AlignHCenter); stretchFactorGridLayout->addWidget(rowLabel, row, GRID_LAYOUT_COLUMN_INDEX_FOR_ROW_CONTROLS, Qt::AlignHCenter); stretchFactorGridLayout->addWidget(columnLabel, row, GRID_LAYOUT_COLUMN_INDEX_FOR_COLUMN_CONTROLS, Qt::AlignHCenter); row++; const float stretchMinimumValue = 1.0; const float stretchMaximumValue = 10000000.0; const float stretchStep = 0.1; const float stretchDigitsRightOfDecimal = 2; const int32_t spinBoxWidth = 80; const int32_t maxItems = std::max(maximuNumberOfRows, maximumNumberOfColumns); for (int i = 0; i < maxItems; i++) { QLabel* indexLabel = new QLabel(AString::number(i + 1)); m_stretchFactorIndexLabels.push_back(indexLabel); stretchFactorGridLayout->addWidget(indexLabel, row, GRID_LAYOUT_COLUMN_INDEX_FOR_LABELS, Qt::AlignHCenter); if (i < maximuNumberOfRows) { QDoubleSpinBox* rowSpinBox = WuQFactory::newDoubleSpinBoxWithMinMaxStepDecimalsSignalDouble(stretchMinimumValue, stretchMaximumValue, stretchStep, stretchDigitsRightOfDecimal, this, SLOT(configurationStretchFactorWasChanged())); rowSpinBox->setFixedWidth(spinBoxWidth); m_rowStretchFactorSpinBoxes.push_back(rowSpinBox); stretchFactorGridLayout->addWidget(rowSpinBox, row, GRID_LAYOUT_COLUMN_INDEX_FOR_ROW_CONTROLS); } if (i < maximumNumberOfColumns) { QDoubleSpinBox* colSpinBox = WuQFactory::newDoubleSpinBoxWithMinMaxStepDecimalsSignalDouble(stretchMinimumValue, stretchMaximumValue, stretchStep, stretchDigitsRightOfDecimal, this, SLOT(configurationStretchFactorWasChanged())); colSpinBox->setFixedWidth(spinBoxWidth); m_columnStretchFactorSpinBoxes.push_back(colSpinBox); stretchFactorGridLayout->addWidget(colSpinBox, row, GRID_LAYOUT_COLUMN_INDEX_FOR_COLUMN_CONTROLS); } row++; } m_stretchFactorScrollArea = new QScrollArea(); m_stretchFactorScrollArea->setWidget(m_stretchFactorWidget); m_stretchFactorScrollArea->setWidgetResizable(true); QGroupBox* widget = new QGroupBox("Edit Configuration"); QVBoxLayout* widgetLayout = new QVBoxLayout(widget); widgetLayout->addWidget(numberOfWidget, 0); widgetLayout->addWidget(WuQtUtilities::createHorizontalLineWidget(), 0); widgetLayout->addWidget(stretchFactorLabel, 0, Qt::AlignHCenter); widgetLayout->addWidget(m_stretchFactorScrollArea, 100); return widget; } /** * Update the content of the dialog. If tile tabs is selected in the given * window, the dialog will be initialized with the tile tabs configuration * selected in the window. * * @param brainBrowserWindow * Browser window from which dialog was selected. */ void TileTabsConfigurationDialog::updateDialogWithSelectedTileTabsFromWindow(BrainBrowserWindow* brainBrowserWindow) { CaretAssert(brainBrowserWindow); m_brainBrowserWindow = brainBrowserWindow; readConfigurationsFromPreferences(); if (m_brainBrowserWindow->isTileTabsSelected()) { TileTabsConfiguration* configuration = m_brainBrowserWindow->getSelectedTileTabsConfiguration(); if (configuration != NULL) { selectTileTabConfigurationByUniqueID(configuration->getUniqueIdentifier()); } } updateDialog(); } /** * Update the tile tabs configuration in the brain browser window if * the browser window has tile tabs enabled. */ void TileTabsConfigurationDialog::updateBrowserWindowsTileTabsConfigurationSelection() { if (m_brainBrowserWindow != NULL) { if (m_brainBrowserWindow->isTileTabsSelected()) { m_brainBrowserWindow->setSelectedTileTabsConfiguration(getSelectedTileTabsConfiguration()); updateGraphicsWindows(); } } } /** * Read the configurations from the preferences. */ void TileTabsConfigurationDialog::readConfigurationsFromPreferences() { if (m_blockReadConfigurationsFromPreferences) { return; } m_caretPreferences->readTileTabsConfigurations(); } /** * Update the content of the dialog. */ void TileTabsConfigurationDialog::updateDialog() { readConfigurationsFromPreferences(); const AString selectedUniqueID = getSelectedTileTabsConfigurationUniqueID(); int defaultIndex = m_configurationSelectionComboBox->currentIndex(); m_configurationSelectionComboBox->blockSignals(true); m_configurationSelectionComboBox->clear(); std::vector configurations = m_caretPreferences->getTileTabsConfigurationsSortedByName(); const int32_t numConfig = static_cast(configurations.size()); for (int32_t i = 0; i < numConfig; i++) { const TileTabsConfiguration* configuration = configurations[i]; /* * Second element is user data which contains the Unique ID */ m_configurationSelectionComboBox->addItem(configuration->getName(), QVariant(configuration->getUniqueIdentifier())); if (configuration->getUniqueIdentifier() == selectedUniqueID) { defaultIndex = i; } } const int32_t numItemsInComboBox = m_configurationSelectionComboBox->count(); if (defaultIndex >= numItemsInComboBox) { defaultIndex = numItemsInComboBox - 1; } if (defaultIndex < 0) { defaultIndex = 0; } if (defaultIndex < m_configurationSelectionComboBox->count()) { m_configurationSelectionComboBox->setCurrentIndex(defaultIndex); selectConfigurationFromComboBoxIndex(defaultIndex); } m_configurationSelectionComboBox->blockSignals(false); updateStretchFactors(); } /** * Update the stretch factors. */ void TileTabsConfigurationDialog::updateStretchFactors() { int32_t numValidRows = 0; int32_t numValidColumns = 0; const TileTabsConfiguration* configuration = getSelectedTileTabsConfiguration(); if (configuration != NULL) { numValidRows = configuration->getNumberOfRows(); numValidColumns = configuration->getNumberOfColumns(); } const int32_t numColSpinBoxes = static_cast(m_columnStretchFactorSpinBoxes.size()); for (int32_t i = 0; i < numColSpinBoxes; i++) { QDoubleSpinBox* sb = m_columnStretchFactorSpinBoxes[i]; if (i < numValidColumns) { sb->setVisible(true); sb->blockSignals(true); sb->setValue(configuration->getColumnStretchFactor(i)); sb->blockSignals(false); } else { sb->setVisible(false); } } const int32_t numRowSpinBoxes = static_cast(m_rowStretchFactorSpinBoxes.size()); for (int32_t i = 0; i < numRowSpinBoxes; i++) { QDoubleSpinBox* sb = m_rowStretchFactorSpinBoxes[i]; if (i < numValidRows) { sb->setVisible(true); sb->blockSignals(true); sb->setValue(configuration->getRowStretchFactor(i)); sb->blockSignals(false); } else { sb->setVisible(false); } } const int32_t numIndexLabels = static_cast(m_stretchFactorIndexLabels.size()); const int32_t numValidLabels = std::max(numValidRows, numValidColumns); for (int32_t i = 0; i < numIndexLabels; i++) { if (i < numValidLabels) { m_stretchFactorIndexLabels[i]->setVisible(true); } else { m_stretchFactorIndexLabels[i]->setVisible(false); } } m_stretchFactorWidget->setFixedSize(m_stretchFactorWidget->sizeHint()); } /** * Select the tile tabs configuration with the given name. */ void TileTabsConfigurationDialog::selectTileTabConfigurationByUniqueID(const AString& uniqueID) { const int32_t numItems = m_configurationSelectionComboBox->count(); for (int32_t i = 0; i < numItems; i++) { const AString itemID = m_configurationSelectionComboBox->itemData(i, Qt::UserRole).toString(); if (itemID == uniqueID) { m_configurationSelectionComboBox->setCurrentIndex(i); selectConfigurationFromComboBoxIndex(i); break; } } } /** * Called when a configuration is selected from the combo box. * * @param indx * Index of item selected. */ void TileTabsConfigurationDialog::configurationComboBoxItemSelected(int indx) { selectConfigurationFromComboBoxIndex(indx); updateBrowserWindowsTileTabsConfigurationSelection(); } /** * Select the configuration at the given index from the configuration combo box. * * @param indx * Index of item for selection. */ void TileTabsConfigurationDialog::selectConfigurationFromComboBoxIndex(int indx) { if ((indx >= 0) && (indx < m_configurationSelectionComboBox->count())) { const AString itemID = m_configurationSelectionComboBox->itemData(indx, Qt::UserRole).toString(); TileTabsConfiguration* configuration = m_caretPreferences->getTileTabsConfigurationByUniqueIdentifier(itemID); if (configuration != NULL) { m_numberOfRowsSpinBox->blockSignals(true); m_numberOfRowsSpinBox->setValue(configuration->getNumberOfRows()); m_numberOfRowsSpinBox->blockSignals(false); m_numberOfColumnsSpinBox->blockSignals(true); m_numberOfColumnsSpinBox->setValue(configuration->getNumberOfColumns()); m_numberOfColumnsSpinBox->blockSignals(false); } } updateStretchFactors(); } /** * Called when new configuration button is clicked. */ void TileTabsConfigurationDialog::newConfigurationButtonClicked() { AString newTileTabsName; AString configurationUniqueID; bool exitLoop = false; while (exitLoop == false) { /* * Popup dialog to get name for new configuration */ WuQDataEntryDialog ded("New Tile Tabs Configuration", m_newConfigurationPushButton); QLineEdit* nameLineEdit = ded.addLineEditWidget("View Name"); nameLineEdit->setText(newTileTabsName); if (ded.exec() == WuQDataEntryDialog::Accepted) { /* * Make sure name is not empty */ newTileTabsName = nameLineEdit->text().trimmed(); if (newTileTabsName.isEmpty()) { WuQMessageBox::errorOk(m_newConfigurationPushButton, "Enter a name"); } else { /* * See if a configuration with the user entered name already exists */ TileTabsConfiguration* configuration = m_caretPreferences->getTileTabsConfigurationByName(newTileTabsName); if (configuration != NULL) { const QString msg = ("Configuration named \"" + newTileTabsName + "\" already exits. Rename it?"); if (WuQMessageBox::warningYesNo(m_newConfigurationPushButton, msg)) { configuration->setName(newTileTabsName); configurationUniqueID = configuration->getUniqueIdentifier(); exitLoop = true; } } else { /* * Create a new configuration with the name * entered by the user. */ TileTabsConfiguration* configuration = new TileTabsConfiguration(); configuration->setName(newTileTabsName); configurationUniqueID = configuration->getUniqueIdentifier(); m_caretPreferences->addTileTabsConfiguration(configuration); exitLoop = true; } } } else { /* * User pressed cancel button. */ exitLoop = true; } } if (configurationUniqueID.isEmpty() == false) { updateDialog(); selectTileTabConfigurationByUniqueID(configurationUniqueID); updateBrowserWindowsTileTabsConfigurationSelection(); } } /** * Called when delete configuration button is clicked. */ void TileTabsConfigurationDialog::deleteConfigurationButtonClicked() { TileTabsConfiguration* configuration = getSelectedTileTabsConfiguration(); if (configuration != NULL) { const AString uniqueID = configuration->getUniqueIdentifier(); const QString msg = ("Delete configuration named \"" + configuration->getName() + "\" ?"); if (WuQMessageBox::warningYesNo(m_newConfigurationPushButton, msg)) { m_caretPreferences->removeTileTabsConfigurationByUniqueIdentifier(uniqueID); updateDialog(); updateBrowserWindowsTileTabsConfigurationSelection(); } } } /** * Called when rename configuration button is clicked. */ void TileTabsConfigurationDialog::renameConfigurationButtonClicked() { TileTabsConfiguration* configuration = getSelectedTileTabsConfiguration(); if (configuration != NULL) { m_blockReadConfigurationsFromPreferences = true; bool ok = false; const AString oldName = configuration->getName(); const AString newName = QInputDialog::getText(m_deleteConfigurationPushButton, "Rename Configuration", "Name", QLineEdit::Normal, oldName, &ok); if (ok && (newName.isEmpty() == false)) { configuration->setName(newName); m_caretPreferences->writeTileTabsConfigurations(); m_blockReadConfigurationsFromPreferences = false; updateDialog(); } else { m_blockReadConfigurationsFromPreferences = false; } } } /** * @return A pointer to the selected tile tabs configuration of NULL if * no configuration is available. */ TileTabsConfiguration* TileTabsConfigurationDialog::getSelectedTileTabsConfiguration() { const AString uniqueID = getSelectedTileTabsConfigurationUniqueID(); TileTabsConfiguration* configuration = m_caretPreferences->getTileTabsConfigurationByUniqueIdentifier(uniqueID); return configuration; } /** * @return The unique identifier of the selected tile tabs configuration an * empty string if no configuration is available. */ AString TileTabsConfigurationDialog::getSelectedTileTabsConfigurationUniqueID() { AString uniqueID; const int32_t indx = m_configurationSelectionComboBox->currentIndex(); if ((indx >= 0) && (indx < m_configurationSelectionComboBox->count())) { uniqueID = m_configurationSelectionComboBox->itemData(indx, Qt::UserRole).toString(); } return uniqueID; } /** * Called when the number of rows or columns changes. */ void TileTabsConfigurationDialog::numberOfRowsOrColumnsChanged() { TileTabsConfiguration* configuration = getSelectedTileTabsConfiguration(); if (configuration != NULL) { configuration->setNumberOfRows(m_numberOfRowsSpinBox->value()); configuration->setNumberOfColumns(m_numberOfColumnsSpinBox->value()); m_caretPreferences->writeTileTabsConfigurations(); updateStretchFactors(); updateGraphicsWindows(); } } /** * Called when a configuration stretch factor value is changed. */ void TileTabsConfigurationDialog::configurationStretchFactorWasChanged() { TileTabsConfiguration* configuration = getSelectedTileTabsConfiguration(); if (configuration == NULL) { return; } const int32_t numColSpinBoxes = static_cast(m_columnStretchFactorSpinBoxes.size()); for (int32_t i = 0; i < numColSpinBoxes; i++) { if (m_columnStretchFactorSpinBoxes[i]->isEnabled()) { configuration->setColumnStretchFactor(i, m_columnStretchFactorSpinBoxes[i]->value()); } } const int32_t numRowSpinBoxes = static_cast(m_rowStretchFactorSpinBoxes.size()); for (int32_t i = 0; i < numRowSpinBoxes; i++) { if (m_rowStretchFactorSpinBoxes[i]->isEnabled()) { configuration->setRowStretchFactor(i, m_rowStretchFactorSpinBoxes[i]->value()); } } m_caretPreferences->writeTileTabsConfigurations(); updateGraphicsWindows(); } /** * Update the graphics in any windows that have tile tabs enabled to the * selected tile tabs configuration in this dialog. */ void TileTabsConfigurationDialog::updateGraphicsWindows() { std::vector allBrowserWindows = GuiManager::get()->getAllOpenBrainBrowserWindows(); for (std::vector::iterator iter = allBrowserWindows.begin(); iter != allBrowserWindows.end(); iter++) { BrainBrowserWindow* bbw = *iter; if (bbw->isTileTabsSelected()) { // if (bbw->getSelectedTileTabsConfiguration() == getSelectedTileTabsConfiguration()) { EventManager::get()->sendEvent(EventGraphicsUpdateOneWindow(bbw->getBrowserWindowIndex()).getPointer()); // } } } } workbench-1.1.1/src/GuiQt/TileTabsConfigurationDialog.h000066400000000000000000000107231255417355300230610ustar00rootroot00000000000000#ifndef __TILE_TABS_CONFIGURATION_DIALOG_H__ #define __TILE_TABS_CONFIGURATION_DIALOG_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "WuQDialogNonModal.h" class QComboBox; class QDoubleSpinBox; class QLabel; class QLineEdit; class QPushButton; class QScrollArea; class QSpinBox; namespace caret { class BrainBrowserWindow; class CaretPreferences; class TileTabsConfiguration; class TileTabsConfigurationDialog : public WuQDialogNonModal { Q_OBJECT public: TileTabsConfigurationDialog(QWidget* parent); virtual ~TileTabsConfigurationDialog(); void updateDialogWithSelectedTileTabsFromWindow(BrainBrowserWindow* brainBrowserWindow); void updateDialog(); private: TileTabsConfigurationDialog(const TileTabsConfigurationDialog&); TileTabsConfigurationDialog& operator=(const TileTabsConfigurationDialog&); public: // ADD_NEW_METHODS_HERE private slots: void newConfigurationButtonClicked(); void deleteConfigurationButtonClicked(); void renameConfigurationButtonClicked(); void configurationComboBoxItemSelected(int); void numberOfRowsOrColumnsChanged(); void configurationStretchFactorWasChanged(); protected: void focusGained(); private: // ADD_NEW_MEMBERS_HERE enum { GRID_LAYOUT_COLUMN_INDEX_FOR_LABELS = 0, GRID_LAYOUT_COLUMN_INDEX_FOR_ROW_CONTROLS = 1, GRID_LAYOUT_COLUMN_INDEX_FOR_COLUMN_CONTROLS = 2 }; void selectTileTabConfigurationByUniqueID(const AString& uniqueID); AString getSelectedTileTabsConfigurationUniqueID(); TileTabsConfiguration* getSelectedTileTabsConfiguration(); QWidget* createConfigurationSelectionWidget(); QWidget* createEditConfigurationWidget(); void updateBrowserWindowsTileTabsConfigurationSelection(); void updateStretchFactors(); void updateGraphicsWindows(); void selectConfigurationFromComboBoxIndex(int indx); void readConfigurationsFromPreferences(); QPushButton* m_newConfigurationPushButton; QPushButton* m_deleteConfigurationPushButton; QPushButton* m_renameConfigurationPushButton; QComboBox* m_configurationSelectionComboBox; QLineEdit* m_nameLineEdit; QSpinBox* m_numberOfRowsSpinBox; QSpinBox* m_numberOfColumnsSpinBox; QScrollArea* m_stretchFactorScrollArea; QWidget* m_stretchFactorWidget; std::vector m_stretchFactorIndexLabels; std::vector m_rowStretchFactorSpinBoxes; std::vector m_columnStretchFactorSpinBoxes; /** Blocks reading of preferences since that may invalidate data pointers */ bool m_blockReadConfigurationsFromPreferences; /** browser window from which this dialog was last displayed */ BrainBrowserWindow* m_brainBrowserWindow; /** * Keep a pointer to preferences but DO NOT delete it * since the preferences are managed by the session * manager. */ CaretPreferences* m_caretPreferences; }; #ifdef __TILE_TABS_CONFIGURATION_DIALOG_DECLARE__ // #endif // __TILE_TABS_CONFIGURATION_DIALOG_DECLARE__ } // namespace #endif //__TILE_TABS_CONFIGURATION_DIALOG_H__ workbench-1.1.1/src/GuiQt/UserInputModeAbstract.cxx000066400000000000000000000052541255417355300223070ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __USER_INPUT_MODE_ABSTRACT_DECLARE__ #include "UserInputModeAbstract.h" #undef __USER_INPUT_MODE_ABSTRACT_DECLARE__ #include #include #include "CaretAssert.h" using namespace caret; /** * \class caret::UserInputModeAbstract * \brief Abstract class for processing user input events * \ingroup GuiQt * * Classes implementing this interface receive * user input events from the OpenGL graphics * region of a BrowserWindow containing brain * models. */ /** * Constructor. */ UserInputModeAbstract::UserInputModeAbstract(const UserInputMode inputMode) : CaretObject(), m_userInputMode(inputMode), m_widgetForToolBar(NULL) { } /** * Destructor. */ UserInputModeAbstract::~UserInputModeAbstract() { /* * If the widget does not have a parent, then it is not * displayed (owned by another QWidget class) and must * be destroyed to avoid a memory leak. */ if (m_widgetForToolBar != NULL) { if (m_widgetForToolBar->parent() == 0) { delete m_widgetForToolBar; } m_widgetForToolBar = NULL; } } /** * @return The input mode enumerated type. */ UserInputModeAbstract::UserInputMode UserInputModeAbstract::getUserInputMode() const { return m_userInputMode; } /** * @return A widget for display at the bottom of the * Browser Window Toolbar when this mode is active. * If no user-interface controls are needed, this * method will return NULL. */ QWidget* UserInputModeAbstract::getWidgetForToolBar() { return m_widgetForToolBar; } /** * Set the widget that is displayed in the toolbar when * the user input mode is active. * * @param widgetForToolBar * Widget that is displayed in toolbar, may be NULL indicating * no widget. */ void UserInputModeAbstract::setWidgetForToolBar(QWidget* widgetForToolBar) { m_widgetForToolBar = widgetForToolBar; } workbench-1.1.1/src/GuiQt/UserInputModeAbstract.h000066400000000000000000000117771255417355300217430ustar00rootroot00000000000000#ifndef __USER_INPUT_MODE_ABSTRACT_H__ #define __USER_INPUT_MODE_ABSTRACT_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "CursorEnum.h" class QWidget; namespace caret { class BrainOpenGLViewportContent; class BrainOpenGLWidget; class MouseEvent; class UserInputModeAbstract : public CaretObject { public: /** Enumerated type for input modes */ enum UserInputMode { /** Invalid */ INVALID, /** Border Operations */ BORDERS, /** Foci Operations */ FOCI, /** Viewing Operations */ VIEW, /** Volume Edit Operations */ VOLUME_EDIT }; UserInputModeAbstract(const UserInputMode inputMode); virtual ~UserInputModeAbstract(); /** * @return The input mode enumerated type. */ UserInputMode getUserInputMode() const; /** * Called when 'this' user input receiver is set * to receive events. */ virtual void initialize() = 0; /** * Called when 'this' user input receiver is no * longer set to receive events. */ virtual void finish() = 0; /** * Called to update the input receiver for various events. */ virtual void update() = 0; QWidget* getWidgetForToolBar(); /** * @return The cursor for display in the OpenGL widget. */ virtual CursorEnum::Enum getCursor() const = 0; /** * Process a mouse left click event. * * @param mouseEvent * Mouse event information. */ virtual void mouseLeftClick(const MouseEvent& /*mouseEvent*/) { } /** * Process a mouse left click with shift key down event. * * @param mouseEvent * Mouse event information. */ virtual void mouseLeftClickWithShift(const MouseEvent& /*mouseEvent*/) { } /** * Process a mouse left click with ctrl and shift keys down event. * * @param mouseEvent * Mouse event information. */ virtual void mouseLeftClickWithCtrlShift(const MouseEvent& /*mouseEvent*/) { } /** * Process a mouse left drag with no keys down event. * * @param mouseEvent * Mouse event information. */ virtual void mouseLeftDrag(const MouseEvent& /*mouseEvent*/) { } /** * Process a mouse left drag with only the alt key down event. * * @param mouseEvent * Mouse event information. */ virtual void mouseLeftDragWithAlt(const MouseEvent& /*mouseEvent*/) { } /** * Process a mouse left drag with ctrl key down event. * * @param mouseEvent * Mouse event information. */ virtual void mouseLeftDragWithCtrl(const MouseEvent& /*mouseEvent*/) { } /** * Process a mouse left drag with ctrl and shift keys down event. * * @param mouseEvent * Mouse event information. */ virtual void mouseLeftDragWithCtrlShift(const MouseEvent& /*mouseEvent*/) { } /** * Process a mouse left drag with shift key down event. * * @param mouseEvent * Mouse event information. */ virtual void mouseLeftDragWithShift(const MouseEvent& /*mouseEvent*/) { } protected: void setWidgetForToolBar(QWidget* widgetForToolBar); private: UserInputModeAbstract(const UserInputModeAbstract&); UserInputModeAbstract& operator=(const UserInputModeAbstract&); const UserInputMode m_userInputMode; QWidget* m_widgetForToolBar; // ADD_NEW_MEMBERS_HERE }; #ifdef __USER_INPUT_MODE_ABSTRACT_DECLARE__ // #endif // __USER_INPUT_MODE_ABSTRACT_DECLARE__ } // namespace #endif //__USER_INPUT_MODE_ABSTRACT_H__ workbench-1.1.1/src/GuiQt/UserInputModeBorders.cxx000066400000000000000000000362451255417355300221500ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __USER_INPUT_MODE_BORDERS_DECLARE__ #include "UserInputModeBorders.h" #undef __USER_INPUT_MODE_BORDERS_DECLARE__ #include "Border.h" #include "BorderFile.h" #include "BorderPropertiesEditorDialog.h" #include "Brain.h" #include "BrainBrowserWindow.h" #include "BrainOpenGLWidget.h" #include "BrainOpenGLViewportContent.h" #include "BrowserTabContent.h" #include "CaretLogger.h" #include "CursorManager.h" #include "EventGraphicsUpdateAllWindows.h" #include "EventGraphicsUpdateOneWindow.h" #include "EventUserInterfaceUpdate.h" #include "EventManager.h" #include "GuiManager.h" #include "SelectionItemBorderSurface.h" #include "SelectionManager.h" #include "Model.h" #include "ModelSurface.h" #include "MouseEvent.h" #include "Surface.h" #include "SurfaceProjectedItem.h" #include "SurfaceProjectionBarycentric.h" #include "UserInputModeBordersWidget.h" #include "UserInputModeView.h" #include "WuQMessageBox.h" using namespace caret; /** * \class caret::UserInputModeBorders * \brief Processing user input for Borders mode. * * Processes user input in Border mode which includes * drawing and editing borders. */ /** * Constructor. * * @param borderBeingDrawnByOpenGL * Border that is displayed in OpenGL area when a border is being drawn * @param windowIndex * Index of the browser window using this border processing. */ UserInputModeBorders::UserInputModeBorders(Border* borderBeingDrawnByOpenGL, const int32_t windowIndex) : UserInputModeView(UserInputModeAbstract::BORDERS) { this->borderBeingDrawnByOpenGL = borderBeingDrawnByOpenGL; this->windowIndex = windowIndex; this->mode = MODE_DRAW; this->drawOperation = DRAW_OPERATION_CREATE; this->editOperation = EDIT_OPERATION_PROPERTIES; this->borderToolsWidget = new UserInputModeBordersWidget(this); setWidgetForToolBar(this->borderToolsWidget); } /** * Destructor. */ UserInputModeBorders::~UserInputModeBorders() { } /** * Draw a border point at the mouse coordinates. */ void UserInputModeBorders::drawPointAtMouseXY(BrainOpenGLWidget* openGLWidget, const int32_t mouseX, const int32_t mouseY) { SurfaceProjectedItem projectedItem; openGLWidget->performProjection(mouseX, mouseY, projectedItem); SurfaceProjectedItem* spi = new SurfaceProjectedItem(); AString txt; // if (projectedItem.isStereotaxicXYZValid()) { // spi->setStereotaxicXYZ(projectedItem.getStereotaxicXYZ()); // // txt += ("Projected Position: " // + AString::fromNumbers(projectedItem.getStereotaxicXYZ(), 3, ",")); // } if (projectedItem.getBarycentricProjection()->isValid()) { SurfaceProjectionBarycentric* bp = projectedItem.getBarycentricProjection(); txt += ("\nBarycentric Position: " + AString::fromNumbers(bp->getTriangleAreas(), 3, ",") + " " + AString::fromNumbers(bp->getTriangleNodes(), 3, ",")); SurfaceProjectionBarycentric* spb = spi->getBarycentricProjection(); spb->setProjectionSurfaceNumberOfNodes(bp->getProjectionSurfaceNumberOfNodes()); spb->setTriangleAreas(bp->getTriangleAreas()); spb->setTriangleNodes(bp->getTriangleNodes()); spb->setSignedDistanceAboveSurface(0.0); spb->setValid(true); } if (spi->isStereotaxicXYZValid() || spi->getBarycentricProjection()->isValid()) { if (borderBeingDrawnByOpenGL->getNumberOfPoints() == 0 || borderBeingDrawnByOpenGL->getStructure() == projectedItem.getStructure()) { spi->setStructure(projectedItem.getStructure()); this->borderBeingDrawnByOpenGL->addPoint(spi); } else { const AString prevName(StructureEnum::toGuiName(borderBeingDrawnByOpenGL->getStructure())); const AString newName(StructureEnum::toGuiName(projectedItem.getStructure())); WuQMessageBox::errorOk(borderToolsWidget, ("The last point added is on " + newName + " but all previous point(s) are on " + prevName + ". Either resume drawing on " + prevName + " or press the Reset button to remove all previous point(s) " "from " + prevName + " and draw on " + newName)); delete spi; } } else { delete spi; } CaretLogFiner(txt); } /** * Called when 'this' user input receiver is set * to receive events. */ void UserInputModeBorders::initialize() { this->borderToolsWidget->updateWidget(); } /** * Called when 'this' user input receiver is no * longer set to receive events. */ void UserInputModeBorders::finish() { } /** * Called to update the input receiver for various events. */ void UserInputModeBorders::update() { } /** * Update after borders changed. */ void UserInputModeBorders::updateAfterBordersChanged() { /* * Need to update all graphics windows and all border controllers. */ EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); EventManager::get()->sendEvent(EventUserInterfaceUpdate().addBorder().getPointer()); } /** * Get a description of this object's content. * @return String describing this object's content. */ AString UserInputModeBorders::toString() const { return "UserInputModeBorders"; } /** * @return the mode. */ UserInputModeBorders::Mode UserInputModeBorders::getMode() const { return this->mode; } /** * Set the mode. * @param mode * New value for mode. */ void UserInputModeBorders::setMode(const Mode mode) { if (this->mode != mode) { this->mode = mode; this->borderBeingDrawnByOpenGL->clear(); EventManager::get()->sendEvent(EventGraphicsUpdateOneWindow(this->windowIndex).getPointer()); } this->borderToolsWidget->updateWidget(); } /** * @return the create operation. */ UserInputModeBorders::DrawOperation UserInputModeBorders::getDrawOperation() const { return this->drawOperation; } /** * Set the create operation. * @param createOperation * New value for create operation. */ void UserInputModeBorders::setDrawOperation(const DrawOperation drawOperation) { this->drawOperation = drawOperation; this->borderToolsWidget->updateWidget(); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } /** * @return True if the border endpoints should be highlighted, else false. */ bool UserInputModeBorders::isHighlightBorderEndPoints() const { if (getMode() == MODE_DRAW) { if (getDrawOperation() != DRAW_OPERATION_CREATE) { return true; } } return false; } /** * Finish the border that the user was drawing. */ void UserInputModeBorders::drawOperationFinish() { this->borderBeingDrawnByOpenGL->clear(); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); EventManager::get()->sendEvent(EventUserInterfaceUpdate().addBorder().getPointer()); } /** * Undo (remove last point) from border being drawn. */ void UserInputModeBorders::drawOperationUndo() { this->borderBeingDrawnByOpenGL->removeLastPoint(); EventManager::get()->sendEvent(EventGraphicsUpdateOneWindow(this->windowIndex).getPointer()); } /** * Reset the border being drawn. */ void UserInputModeBorders::drawOperationReset() { this->borderBeingDrawnByOpenGL->clear(); EventManager::get()->sendEvent(EventGraphicsUpdateOneWindow(this->windowIndex).getPointer()); } /** * @return The edit operation. */ UserInputModeBorders::EditOperation UserInputModeBorders::getEditOperation() const { return this->editOperation; } /** * Set the edit operation. * @param editOperation * New edit operation. */ void UserInputModeBorders::setEditOperation(const EditOperation editOperation) { this->editOperation = editOperation; } /** * @return The cursor for display in the OpenGL widget. */ CursorEnum::Enum UserInputModeBorders::getCursor() const { CursorEnum::Enum cursor = CursorEnum::CURSOR_DEFAULT; switch (this->mode) { case MODE_DRAW: cursor = CursorEnum::CURSOR_DRAWING_PEN; break; case MODE_EDIT: cursor = CursorEnum::CURSOR_POINTING_HAND; break; case MODE_ROI: cursor = CursorEnum::CURSOR_POINTING_HAND; break; } return cursor; } /** * Process a mouse left click event. * * @param mouseEvent * Mouse event information. */ void UserInputModeBorders::mouseLeftClick(const MouseEvent& mouseEvent) { BrainOpenGLWidget* openGLWidget = mouseEvent.getOpenGLWidget(); const int mouseX = mouseEvent.getX(); const int mouseY = mouseEvent.getY(); switch (this->mode) { case MODE_DRAW: this->drawPointAtMouseXY(openGLWidget, mouseX, mouseY); EventManager::get()->sendEvent(EventGraphicsUpdateOneWindow(this->windowIndex).getPointer()); break; case MODE_EDIT: { SelectionManager* idManager = openGLWidget->performIdentification(mouseX, mouseY, true); SelectionItemBorderSurface* idBorder = idManager->getSurfaceBorderIdentification(); if (idBorder->isValid()) { BorderFile* borderFile = idBorder->getBorderFile(); if (borderFile->isSingleStructure()) { switch (this->editOperation) { case EDIT_OPERATION_DELETE: { Border* border = idBorder->getBorder(); borderFile->removeBorder(border); this->updateAfterBordersChanged(); } break; case EDIT_OPERATION_PROPERTIES: { Border* border = idBorder->getBorder(); std::auto_ptr editBorderDialog( BorderPropertiesEditorDialog::newInstanceEditBorder(borderFile, border, openGLWidget)); if (editBorderDialog->exec() == BorderPropertiesEditorDialog::Accepted) { this->updateAfterBordersChanged(); } } break; } } else { WuQMessageBox::errorOk(this->borderToolsWidget, borderFile->getObsoleteMultiStructureFormatMessage()); } } } break; case MODE_ROI: { SelectionManager* idManager = openGLWidget->performIdentification(mouseX, mouseY, true); SelectionItemBorderSurface* idBorder = idManager->getSurfaceBorderIdentification(); if (idBorder->isValid()) { Brain* brain = idBorder->getBrain(); Surface* surface = idBorder->getSurface(); //BorderFile* borderFile = idBorder->getBorderFile(); Border* border = idBorder->getBorder(); this->borderToolsWidget->executeRoiInsideSelectedBorderOperation(brain, surface, border); } } break; } } /** * Process a mouse left click with Shift key event. * * @param mouseEvent * Mouse event information. */ void UserInputModeBorders::mouseLeftClickWithShift(const MouseEvent& /*mouseEvent*/) { switch (this->mode) { case MODE_DRAW: this->borderToolsWidget->executeFinishOperation(); break; case MODE_EDIT: break; case MODE_ROI: break; } } /** * Process a mouse left click with ctrl and shift keys down event. * * @param mouseEvent * Mouse event information. */ void UserInputModeBorders::mouseLeftClickWithCtrlShift(const MouseEvent& mouseEvent) { BrainOpenGLWidget* openGLWidget = mouseEvent.getOpenGLWidget(); const int mouseX = mouseEvent.getX(); const int mouseY = mouseEvent.getY(); switch (this->mode) { case MODE_DRAW: this->drawPointAtMouseXY(openGLWidget, mouseX, mouseY); EventManager::get()->sendEvent(EventGraphicsUpdateOneWindow(this->windowIndex).getPointer()); break; case MODE_EDIT: break; case MODE_ROI: break; } } /** * Process a mouse left drag with ctrl and shift keys down event. * * @param mouseEvent * Mouse event information. */ void UserInputModeBorders::mouseLeftDragWithCtrlShift(const MouseEvent& mouseEvent) { BrainOpenGLWidget* openGLWidget = mouseEvent.getOpenGLWidget(); const int mouseX = mouseEvent.getX(); const int mouseY = mouseEvent.getY(); switch (this->mode) { case MODE_DRAW: this->drawPointAtMouseXY(openGLWidget, mouseX, mouseY); EventManager::get()->sendEvent(EventGraphicsUpdateOneWindow(this->windowIndex).getPointer()); break; case MODE_EDIT: break; case MODE_ROI: break; } } workbench-1.1.1/src/GuiQt/UserInputModeBorders.h000066400000000000000000000076001255417355300215660ustar00rootroot00000000000000#ifndef __USER_INPUT_MODE_BORDERS__H_ #define __USER_INPUT_MODE_BORDERS__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "UserInputModeView.h" namespace caret { class Border; class UserInputModeBordersWidget; class UserInputModeBorders : public UserInputModeView { public: enum Mode { MODE_DRAW, MODE_EDIT, MODE_ROI }; enum DrawOperation { DRAW_OPERATION_CREATE, DRAW_OPERATION_ERASE, DRAW_OPERATION_EXTEND, DRAW_OPERATION_OPTIMIZE, DRAW_OPERATION_REPLACE }; enum EditOperation { EDIT_OPERATION_DELETE, EDIT_OPERATION_PROPERTIES }; UserInputModeBorders(Border* borderBeingDrawnByOpenGL, const int32_t windowIndex); virtual ~UserInputModeBorders(); virtual void initialize(); virtual void finish(); virtual void update(); Mode getMode() const; virtual void mouseLeftClick(const MouseEvent& mouseEvent); virtual void mouseLeftClickWithShift(const MouseEvent& mouseEvent); virtual void mouseLeftClickWithCtrlShift(const MouseEvent& mouseEvent); virtual void mouseLeftDragWithCtrlShift(const MouseEvent& mouseEvent); virtual CursorEnum::Enum getCursor() const; virtual AString toString() const; bool isHighlightBorderEndPoints() const; private: /* * Some private methods are accessed by this friend class */ friend class UserInputModeBordersWidget; UserInputModeBorders(const UserInputModeBorders&); UserInputModeBorders& operator=(const UserInputModeBorders&); void setMode(const Mode mode); DrawOperation getDrawOperation() const; void setDrawOperation(const DrawOperation drawOperation); EditOperation getEditOperation() const; void setEditOperation(const EditOperation editOperation); void drawPointAtMouseXY(BrainOpenGLWidget* openGLWidget, const int32_t mouseX, const int32_t mouseY); void drawOperationFinish(); void drawOperationUndo(); void drawOperationReset(); void updateAfterBordersChanged(); UserInputModeBordersWidget* borderToolsWidget; Mode mode; DrawOperation drawOperation; EditOperation editOperation; /** * Pointer to border drawn by OpenGL. Since owned * by OpenGL, DO NOT delete this!!! */ Border* borderBeingDrawnByOpenGL; int32_t windowIndex; }; #ifdef __USER_INPUT_MODE_BORDERS_DECLARE__ // #endif // __USER_INPUT_MODE_BORDERS_DECLARE__ } // namespace #endif //__USER_INPUT_MODE_BORDERS__H_ workbench-1.1.1/src/GuiQt/UserInputModeBordersWidget.cxx000066400000000000000000001342261255417355300233120ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #include #include #include #include #include #include #define __USER_INPUT_MODE_BORDERS_WIDGET_DECLARE__ #include "UserInputModeBordersWidget.h" #undef __USER_INPUT_MODE_BORDERS_WIDGET_DECLARE__ #include "AlgorithmException.h" #include "AlgorithmNodesInsideBorder.h" #include "Border.h" #include "BorderEditingSelectionDialog.h" #include "BorderFile.h" #include "BorderOptimizeDialog.h" #include "BorderPointFromSearch.h" #include "BorderPropertiesEditorDialog.h" #include "Brain.h" #include "BrainBrowserWindow.h" #include "BrainStructure.h" #include "BrowserTabContent.h" #include "CaretAssert.h" #include "CaretLogger.h" #include "DisplayPropertiesBorders.h" #include "EventBrainReset.h" #include "EventGraphicsUpdateAllWindows.h" #include "EventManager.h" #include "EventUserInterfaceUpdate.h" #include "GuiManager.h" #include "LabelFile.h" #include "MetricFile.h" #include "ModelSurface.h" #include "ModelSurfaceMontage.h" #include "ModelWholeBrain.h" #include "RegionOfInterestCreateFromBorderDialog.h" #include "Surface.h" #include "UserInputModeBorders.h" #include "WuQDataEntryDialog.h" #include "WuQMessageBox.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::UserInputModeBordersWidget * \brief Controls for when the user input mode is border operations */ /** * Constructor. * * @param inputModeBorders * Mouse processing for border operations. * @param parent * Optional parent widget. */ UserInputModeBordersWidget::UserInputModeBordersWidget(UserInputModeBorders* inputModeBorders, QWidget* parent) : QWidget(parent) { m_transformToolTipText = ("\n\n" "At any time, the view of the surface may be changed by\n" " PAN: Move the mouse with the left mouse button down while " "holding down the Shift key.\n" " ROTATE: Move the mouse with the left mouse button down.\n" " ZOOM: Move the mouse with the left mouse button down while " "holding down the Ctrl key (Apple key on Macs)." ); m_borderOptimizeDialog = NULL; this->inputModeBorders = inputModeBorders; QLabel* nameLabel = new QLabel("Borders "); this->widgetMode = this->createModeWidget(); resetLastEditedBorder(); this->widgetDrawOperation = this->createDrawOperationWidget(); this->widgetEditOperation = this->createEditOperationWidget(); this->widgetRoiOperation = this->createRoiOperationWidget(); this->operationStackedWidget = new QStackedWidget(); this->operationStackedWidget->addWidget(this->widgetDrawOperation); this->operationStackedWidget->addWidget(this->widgetEditOperation); this->operationStackedWidget->addWidget(this->widgetRoiOperation); QHBoxLayout* layout = new QHBoxLayout(this); WuQtUtilities::setLayoutSpacingAndMargins(layout, 0, 0); layout->addWidget(nameLabel); layout->addWidget(this->widgetMode); layout->addSpacing(10); layout->addWidget(this->operationStackedWidget); layout->addStretch(); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_BRAIN_RESET); } /** * Destructor. */ UserInputModeBordersWidget::~UserInputModeBordersWidget() { EventManager::get()->removeAllEventsFromListener(this); } /** * Receive an event. * * @param event * The event that the receive can respond to. */ void UserInputModeBordersWidget::receiveEvent(Event* event) { if (event->getEventType() == EventTypeEnum::EVENT_BRAIN_RESET) { EventBrainReset* brainEvent = dynamic_cast(event); CaretAssert(brainEvent); if (m_borderOptimizeDialog != NULL) { delete m_borderOptimizeDialog; m_borderOptimizeDialog = NULL; } brainEvent->setEventProcessed(); } } /** * Update the contents of the widget. */ void UserInputModeBordersWidget::updateWidget() { /* * Show the proper widget */ switch (this->inputModeBorders->getMode()) { case UserInputModeBorders::MODE_DRAW: this->operationStackedWidget->setCurrentWidget(this->widgetDrawOperation); this->setActionGroupByActionData(this->drawOperationActionGroup, inputModeBorders->getDrawOperation()); resetLastEditedBorder(); break; case UserInputModeBorders::MODE_EDIT: this->operationStackedWidget->setCurrentWidget(this->widgetEditOperation); this->setActionGroupByActionData(this->editOperationActionGroup, inputModeBorders->getEditOperation()); break; case UserInputModeBorders::MODE_ROI: this->operationStackedWidget->setCurrentWidget(this->widgetRoiOperation); resetLastEditedBorder(); break; } const int selectedModeInteger = (int)this->inputModeBorders->getMode(); const int modeComboBoxIndex = this->modeComboBox->findData(selectedModeInteger); CaretAssert(modeComboBoxIndex >= 0); this->modeComboBox->blockSignals(true); this->modeComboBox->setCurrentIndex(modeComboBoxIndex); this->modeComboBox->blockSignals(false); } /** * Set the action with its data value of the given integer * as the active action. * @param actionGroup * Action group for which action is selected. * @param dataInteger * Integer value for data attribute. */ void UserInputModeBordersWidget::setActionGroupByActionData(QActionGroup* actionGroup, const int dataInteger) { actionGroup->blockSignals(true); const QList actionList = actionGroup->actions(); QListIterator iter(actionList); while (iter.hasNext()) { QAction* action = iter.next(); const int actionDataInteger = action->data().toInt(); if (dataInteger == actionDataInteger) { action->setChecked(true); break; } } actionGroup->blockSignals(false); } /** * @return The mode widget. */ QWidget* UserInputModeBordersWidget::createModeWidget() { this->modeComboBox = new QComboBox(); this->modeComboBox->addItem("Draw", (int)UserInputModeBorders::MODE_DRAW); this->modeComboBox->addItem("Edit", (int)UserInputModeBorders::MODE_EDIT); this->modeComboBox->addItem("ROI", (int)UserInputModeBorders::MODE_ROI); QObject::connect(this->modeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(modeComboBoxSelection(int))); QWidget* widget = new QWidget(); QHBoxLayout* layout = new QHBoxLayout(widget); WuQtUtilities::setLayoutSpacingAndMargins(layout, 2, 0); layout->addWidget(this->modeComboBox); widget->setFixedWidth(widget->sizeHint().width()); return widget; } /** * Called when a mode is selected from the mode combo box. * @param indx * Index of item selected. */ void UserInputModeBordersWidget::modeComboBoxSelection(int indx) { const int modeInteger = this->modeComboBox->itemData(indx).toInt(); const UserInputModeBorders::Mode mode = (UserInputModeBorders::Mode)modeInteger; this->inputModeBorders->setMode(mode); resetLastEditedBorder(); } /** * @return The draw operation widget. */ QWidget* UserInputModeBordersWidget::createDrawOperationWidget() { /* * Draw */ const AString drawToolTipText = ("To draw a new border segment either click the mouse " "to discretely add border points or hold down the CTRL (Apple key on Mac) and SHIFT keys " "and move the mouse with the left mouse button down to continuously " "add points. " "When the border is complete, either press the Finish button " "or hold down the Shift key and click the mouse to display a " "dialog for assigning the borders attributes (name and color). " + m_transformToolTipText); QAction* drawAction = WuQtUtilities::createAction("New", WuQtUtilities::createWordWrappedToolTipText(drawToolTipText), this); drawAction->setCheckable(true); drawAction->setData(static_cast(UserInputModeBorders::DRAW_OPERATION_CREATE)); QToolButton* drawToolButton = new QToolButton(); drawToolButton->setDefaultAction(drawAction); /* * Erase */ const AString eraseToolTipText = ("To erase a section of a border, click the mouse twice, once " "at the beginning of the section that is to be removed and a " "second time at the end of the section. " "Press the Finish button or hold down the Shift key and click the " "mouse remove the border section." + m_transformToolTipText); QAction* eraseAction = WuQtUtilities::createAction("Erase", WuQtUtilities::createWordWrappedToolTipText(eraseToolTipText), this); eraseAction->setCheckable(true); eraseAction->setData(static_cast(UserInputModeBorders::DRAW_OPERATION_ERASE)); QToolButton* eraseToolButton = new QToolButton(); eraseToolButton->setDefaultAction(eraseAction); /* * Extend */ const AString extendToolTipText = ("To extend a border, move the mouse ANY point in the border and " "either click the mouse to discretely add points or hold down the CTRL (Apple key on Mac) and SHIFT keys " "and move the mouse with the left mouse button down to continuously add points. " "Press the Finish button or hold down the Shift key and click the " "mouse add the extension to the border." "\n\n" "If the segment starts at a point within the border (not an end point), points will be removed " "from that point to the nearest end point in the border and then the extension " "will be added." + m_transformToolTipText); QAction* extendAction = WuQtUtilities::createAction("Extend", WuQtUtilities::createWordWrappedToolTipText(extendToolTipText), this); extendAction->setCheckable(true); extendAction->setData(static_cast(UserInputModeBorders::DRAW_OPERATION_EXTEND)); QToolButton* extendToolButton = new QToolButton(); extendToolButton->setDefaultAction(extendAction); /* * Replace */ const AString replaceToolTipText = ("To replace a section of a border, move the mouse to the " "start of the section that is being replaced. Either click " "the mouse to discretely add points in the new section or hold down the CTRL (Apple key on Mac) and SHIFT keys " "and move the mouse with the left mouse button down to continuously add points. " "Press the Finish button or hold down the Shift key and click the " "mouse to conclude replacing the section in the border." "\n\n" "Both the first point and the last point in the segment must " "overlap points in the border." + m_transformToolTipText); QAction* replaceAction = WuQtUtilities::createAction("Replace", WuQtUtilities::createWordWrappedToolTipText(replaceToolTipText), this); replaceAction->setCheckable(true); replaceAction->setData(static_cast(UserInputModeBorders::DRAW_OPERATION_REPLACE)); QToolButton* replaceToolButton = new QToolButton(); replaceToolButton->setDefaultAction(replaceAction); /* * Optimize */ const AString optimizeToolTipText("It's very complicated."); QAction* optimizeAction = WuQtUtilities::createAction("Optimize", WuQtUtilities::createWordWrappedToolTipText(optimizeToolTipText), this); optimizeAction->setCheckable(true); optimizeAction->setData(static_cast(UserInputModeBorders::DRAW_OPERATION_OPTIMIZE)); QToolButton* optimizeToolButton = new QToolButton(); optimizeToolButton->setDefaultAction(optimizeAction); /* * Finish */ const AString finishToolTipText = ("The finish button must be pressed (holding down the Shift key " "and clicking the mouse is a shortcut to clicking the Finish " "button) to complete any of the border drawing operations."); QAction* finishAction = WuQtUtilities::createAction("Finish", WuQtUtilities::createWordWrappedToolTipText(finishToolTipText), this, this, SLOT(drawFinishButtonClicked())); QToolButton* finishToolButton = new QToolButton(); finishToolButton->setDefaultAction(finishAction); /* * Undo */ QAction* undoAction = WuQtUtilities::createAction("Undo", "Remove (undo) the last point in the\n" "drawn border segment. If the button\n" "is held down, it will repeat removal\n" "of points until the button is released.", this, this, SLOT(drawUndoButtonClicked())); QToolButton* undoToolButton = new QToolButton(); undoToolButton->setDefaultAction(undoAction); undoToolButton->setAutoRepeat(true); undoToolButton->setAutoRepeatDelay(500); // 500ms = 1/2 second undoToolButton->setAutoRepeatInterval(100); // 100ms = 1/10 second QAction* undoFinishAction = WuQtUtilities::createAction("Undo Finish", "Undo the last Erase/Extend/Replace\n" "performed on a border.", this, this, SLOT(drawUndoLastEditButtonClicked())); m_undoFinishToolButton = new QToolButton(); m_undoFinishToolButton->setDefaultAction(undoFinishAction); /* * Reset */ QAction* resetAction = WuQtUtilities::createAction("Reset", "Remove all points in the unfinished border", this, this, SLOT(drawResetButtonClicked())); QToolButton* resetToolButton = new QToolButton(); resetToolButton->setDefaultAction(resetAction); this->drawOperationActionGroup = new QActionGroup(this); this->drawOperationActionGroup->addAction(drawAction); this->drawOperationActionGroup->addAction(eraseAction); this->drawOperationActionGroup->addAction(extendAction); this->drawOperationActionGroup->addAction(optimizeAction); this->drawOperationActionGroup->addAction(replaceAction); this->drawOperationActionGroup->setExclusive(true); QObject::connect(this->drawOperationActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(drawOperationActionTriggered(QAction*))); QWidget* widget = new QWidget(); QHBoxLayout* layout = new QHBoxLayout(widget); WuQtUtilities::setLayoutSpacingAndMargins(layout, 2, 0); layout->addWidget(drawToolButton); layout->addWidget(eraseToolButton); layout->addWidget(extendToolButton); layout->addWidget(optimizeToolButton); layout->addWidget(replaceToolButton); layout->addSpacing(10); layout->addWidget(finishToolButton); layout->addWidget(m_undoFinishToolButton); layout->addSpacing(10); layout->addWidget(undoToolButton); layout->addWidget(resetToolButton); widget->setFixedWidth(widget->sizeHint().width()); return widget; } /** * Called when draw border reset button clicked. */ void UserInputModeBordersWidget::drawResetButtonClicked() { this->inputModeBorders->drawOperationReset(); } /** * Called when draw border undo button clicked. */ void UserInputModeBordersWidget::drawUndoButtonClicked() { this->inputModeBorders->drawOperationUndo(); } /** * Undo editing (erase/extend/replace) of last border. */ void UserInputModeBordersWidget::drawUndoLastEditButtonClicked() { for (std::vector::iterator iter = m_undoFinishBorders.begin(); iter != m_undoFinishBorders.end(); iter++) { BorderFile* undoBorderFile = iter->m_borderFile; Border* undoBorder = iter->m_border; bool foundBorderFlag = false; Brain* brain = GuiManager::get()->getBrain(); const int32_t numBorderFiles = brain->getNumberOfBorderFiles(); for (int32_t i = 0; i < numBorderFiles; i++) { BorderFile* bf = brain->getBorderFile(i); if (bf == undoBorderFile) { foundBorderFlag = true; break; } } if (foundBorderFlag) { foundBorderFlag = false; const int32_t numBorders = undoBorderFile->getNumberOfBorders(); for (int32_t i = 0; i < numBorders; i++) { if (undoBorderFile->getBorder(i) == undoBorder) { foundBorderFlag = true; break; } } } if (foundBorderFlag) { if (undoBorder->isUndoBorderValid()) { if (WuQMessageBox::warningOkCancel(m_undoFinishToolButton, ("Undo changes to " + undoBorder->getName()))) { undoBorder->undoLastBorderEditing(); } } else { WuQMessageBox::errorOk(m_undoFinishToolButton, ("Cannot undo border " + undoBorder->getName())); } } else { WuQMessageBox::errorOk(m_undoFinishToolButton, "Cannot undo last edited border. " "Did not find border for undoing."); } } resetLastEditedBorder(); } /** * Publicly accessible method for initiating * an operation as if the Finish button was * pressed. */ void UserInputModeBordersWidget::executeFinishOperation() { this->drawFinishButtonClicked(); } /** * Called when draw border finish button clicked. */ void UserInputModeBordersWidget::drawFinishButtonClicked() { BrainBrowserWindow* browserWindow = GuiManager::get()->getBrowserWindowByWindowIndex(this->inputModeBorders->windowIndex); if (browserWindow == NULL) { return; } BrowserTabContent* btc = browserWindow->getBrowserTabContent(); if (btc == NULL) { return; } const int32_t browserTabIndex = btc->getTabNumber(); if (this->inputModeBorders->borderBeingDrawnByOpenGL->verifyAllPointsOnSameStructure() == false) { WuQMessageBox::errorOk(this, "Error: Border points are on more than one structure."); return; } AString modeText; int32_t minimumNumberOfBorderPoints = 2; switch (this->inputModeBorders->getDrawOperation()) { case UserInputModeBorders::DRAW_OPERATION_CREATE: modeText = "creating a border."; minimumNumberOfBorderPoints = 2; break; case UserInputModeBorders::DRAW_OPERATION_OPTIMIZE: modeText = "optimizing borders."; minimumNumberOfBorderPoints = 3; break; case UserInputModeBorders::DRAW_OPERATION_ERASE: modeText = "erasing a segment in a border."; minimumNumberOfBorderPoints = 2; break; case UserInputModeBorders::DRAW_OPERATION_EXTEND: modeText = "extending a border."; minimumNumberOfBorderPoints = 2; break; case UserInputModeBorders::DRAW_OPERATION_REPLACE: modeText = "replacing points in a border."; minimumNumberOfBorderPoints = 2; break; } if (this->inputModeBorders->borderBeingDrawnByOpenGL->getNumberOfPoints() < minimumNumberOfBorderPoints) { WuQMessageBox::errorOk(this, ("There must be at least " + AString::number(minimumNumberOfBorderPoints) + " points when " + modeText)); return; } ModelSurface* surfaceController = btc->getDisplayedSurfaceModel(); ModelWholeBrain* wholeBrainController = btc->getDisplayedWholeBrainModel(); ModelSurfaceMontage* surfaceMontageController = btc->getDisplayedSurfaceMontageModel(); Brain* brain = NULL; Surface* surface = NULL; if (surfaceController != NULL) { brain = surfaceController->getBrain(); surface = surfaceController->getSurface(); } else if (wholeBrainController != NULL) { brain = wholeBrainController->getBrain(); const StructureEnum::Enum structure = this->inputModeBorders->borderBeingDrawnByOpenGL->getStructure(); surface = wholeBrainController->getSelectedSurface(structure, btc->getTabNumber()); } else if (surfaceMontageController != NULL) { brain = surfaceMontageController->getBrain(); const StructureEnum::Enum structure = this->inputModeBorders->borderBeingDrawnByOpenGL->getStructure(); surface = surfaceMontageController->getSelectedSurface(structure, btc->getTabNumber()); } if (surface == NULL) { CaretLogSevere("PROGRAM ERROR: Cannot find surface for border drawing."); return; } if (brain == NULL) { CaretLogSevere("PROGRAM ERROR: Cannot find surface for border drawing."); return; } DisplayPropertiesBorders* dpb = GuiManager::get()->getBrain()->getDisplayPropertiesBorders(); const DisplayGroupEnum::Enum displayGroup = dpb->getDisplayGroupForTab(btc->getTabNumber()); dpb->setDisplayed(displayGroup, browserTabIndex, true); switch (this->inputModeBorders->getDrawOperation()) { case UserInputModeBorders::DRAW_OPERATION_CREATE: { std::auto_ptr finishBorderDialog( BorderPropertiesEditorDialog::newInstanceFinishBorder(this->inputModeBorders->borderBeingDrawnByOpenGL, surface, this)); if (finishBorderDialog->exec() == BorderPropertiesEditorDialog::Accepted) { this->inputModeBorders->drawOperationFinish(); } } break; case UserInputModeBorders::DRAW_OPERATION_OPTIMIZE: processBorderOptimization(displayGroup, browserTabIndex, surface, this->inputModeBorders->borderBeingDrawnByOpenGL); break; case UserInputModeBorders::DRAW_OPERATION_ERASE: case UserInputModeBorders::DRAW_OPERATION_EXTEND: case UserInputModeBorders::DRAW_OPERATION_REPLACE: { const float nearestTolerance = 10; std::vector allNearbyBorders; const int32_t numBorderFiles = brain->getNumberOfBorderFiles(); for (int32_t ibf = 0; ibf < numBorderFiles; ibf++) { const BorderFile* borderFile = brain->getBorderFile(ibf); std::vector bordersFoundFromFile; switch (this->inputModeBorders->getDrawOperation()) { case UserInputModeBorders::DRAW_OPERATION_CREATE: CaretAssert(0); break; case UserInputModeBorders::DRAW_OPERATION_ERASE: borderFile->findAllBordersWithPointsNearBothSegmentEndPoints(displayGroup, browserTabIndex, surface, this->inputModeBorders->borderBeingDrawnByOpenGL, nearestTolerance, bordersFoundFromFile); break; case UserInputModeBorders::DRAW_OPERATION_EXTEND: borderFile->findAllBordersWithAnyPointNearSegmentFirstPoint(displayGroup, browserTabIndex, surface, this->inputModeBorders->borderBeingDrawnByOpenGL, nearestTolerance, bordersFoundFromFile); // borderFile->findAllBordersWithEndPointNearSegmentFirstPoint(displayGroup, // browserTabIndex, // surface, // this->inputModeBorders->borderBeingDrawnByOpenGL, // nearestTolerance, // bordersFoundFromFile); break; case UserInputModeBorders::DRAW_OPERATION_OPTIMIZE: CaretAssertToDoFatal(); break; case UserInputModeBorders::DRAW_OPERATION_REPLACE: borderFile->findAllBordersWithPointsNearBothSegmentEndPoints(displayGroup, browserTabIndex, surface, this->inputModeBorders->borderBeingDrawnByOpenGL, nearestTolerance, bordersFoundFromFile); break; } allNearbyBorders.insert(allNearbyBorders.end(), bordersFoundFromFile.begin(), bordersFoundFromFile.end()); } if (allNearbyBorders.empty()) { WuQMessageBox::errorOk(this, "No borders were found near the border editing points"); return; } else { std::sort(allNearbyBorders.begin(), allNearbyBorders.end()); const int32_t numBorders = static_cast(allNearbyBorders.size()); AString modeMessage; switch (this->inputModeBorders->getDrawOperation()) { case UserInputModeBorders::DRAW_OPERATION_CREATE: CaretAssert(0); break; case UserInputModeBorders::DRAW_OPERATION_ERASE: modeMessage = "Erase segement in"; break; case UserInputModeBorders::DRAW_OPERATION_EXTEND: modeMessage = "Extend"; break; case UserInputModeBorders::DRAW_OPERATION_OPTIMIZE: CaretAssert(0); break; case UserInputModeBorders::DRAW_OPERATION_REPLACE: modeMessage = "Replace segment in"; break; } std::vector borderNames; for (int32_t i = 0; i < numBorders; i++) { BorderPointFromSearch& bpfs = allNearbyBorders[i]; borderNames.push_back(bpfs.border()->getName() + " (" + AString::number(bpfs.distance(), 'f', 6) + " mm)"); } BorderEditingSelectionDialog selDialog(modeMessage, borderNames, this); if (selDialog.exec() == BorderEditingSelectionDialog::Accepted) { AString errorMessage; std::vector undoBorders; for (int32_t i = 0; i < numBorders; i++) { if (selDialog.isBorderNameSelected(i)) { BorderPointFromSearch& bpfs = allNearbyBorders[i]; BorderFile* borderFile = bpfs.borderFile(); Border* border = bpfs.border(); int32_t borderPointIndex = bpfs.borderPointIndex(); CaretAssert(borderFile); CaretAssert(border); try { switch (this->inputModeBorders->getDrawOperation()) { case UserInputModeBorders::DRAW_OPERATION_CREATE: CaretAssert(0); break; case UserInputModeBorders::DRAW_OPERATION_ERASE: border->reviseEraseFromEnd(surface, this->inputModeBorders->borderBeingDrawnByOpenGL); break; case UserInputModeBorders::DRAW_OPERATION_EXTEND: border->reviseExtendFromPointIndex(surface, borderPointIndex, this->inputModeBorders->borderBeingDrawnByOpenGL); //border->reviseExtendFromEnd(surface, // this->inputModeBorders->borderBeingDrawnByOpenGL); break; case UserInputModeBorders::DRAW_OPERATION_OPTIMIZE: CaretAssert(0); break; case UserInputModeBorders::DRAW_OPERATION_REPLACE: border->reviseReplaceSegment(surface, this->inputModeBorders->borderBeingDrawnByOpenGL); break; } undoBorders.push_back(BorderFileAndBorderMemento(borderFile, border)); } catch (BorderException& e) { errorMessage.appendWithNewLine(e.whatString()); } } } if ( ! errorMessage.isEmpty()) { WuQMessageBox::errorOk(this, errorMessage); } else { setLastEditedBorder(undoBorders); this->inputModeBorders->borderBeingDrawnByOpenGL->clear(); } EventManager::get()->sendEvent(EventUserInterfaceUpdate().getPointer()); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } } } break; } } /** * Process the border optimization operation. * * @param surface * Surface on which border is drawn. * @param borderDrawnByUser * Border drawn by the user. */ void UserInputModeBordersWidget::processBorderOptimization(const DisplayGroupEnum::Enum displayGroup, const int32_t browserTabIndex, Surface* surface, Border* borderDrawnByUser) { std::vector nodesInsideBorder; try { ProgressObject* myProgObj = NULL; const bool inverseSelectionFlag = false; /* * Find nodes inside border */ AlgorithmNodesInsideBorder nib(myProgObj, surface, borderDrawnByUser, inverseSelectionFlag, nodesInsideBorder); } catch (const AlgorithmException& e) { WuQMessageBox::errorOk(this, e.whatString()); return; } if (nodesInsideBorder.empty()) { WuQMessageBox::errorOk(this, ("No nodes were found inside the drawn border " + borderDrawnByUser->getName())); return; } Brain* brain = GuiManager::get()->getBrain(); const StructureEnum::Enum surfaceStructure = surface->getStructure(); const BrainStructure* brainStructure = brain->getBrainStructure(surfaceStructure, false); if (brainStructure == NULL) { WuQMessageBox::errorOk(this, "No files for surface structure " + StructureEnum::toGuiName(surfaceStructure)); return; } std::vector optimizeDataFileTypes; optimizeDataFileTypes.push_back(DataFileTypeEnum::CONNECTIVITY_DENSE); optimizeDataFileTypes.push_back(DataFileTypeEnum::CONNECTIVITY_DENSE_SCALAR); optimizeDataFileTypes.push_back(DataFileTypeEnum::CONNECTIVITY_DENSE_TIME_SERIES); optimizeDataFileTypes.push_back(DataFileTypeEnum::METRIC); std::vector optimizeDataFiles; brain->getAllMappableDataFileWithDataFileTypes(optimizeDataFileTypes, optimizeDataFiles); /* * Create a bool vector that indicates nodes inside border * from the node index. */ const int32_t numberOfSurfaceNodes = surface->getNumberOfNodes(); std::vector nodeInROI(numberOfSurfaceNodes, false); for (std::vector::const_iterator iter = nodesInsideBorder.begin(); iter != nodesInsideBorder.end(); iter++) { const int32_t nodeIndex = *iter; CaretAssertVectorIndex(nodeInROI, nodeIndex); nodeInROI[nodeIndex] = true; } /* * Find borders inside region of interest */ std::map borderToBorderFileMap; std::vector > bordersInsideRegionOfInterest; const int32_t numberOfBorderFiles = brain->getNumberOfBorderFiles(); for (int32_t iFile = 0; iFile < numberOfBorderFiles; iFile++) { BorderFile* borderFile = brain->getBorderFile(iFile); std::vector > nodeCountAndBordersFromFileInROI; borderFile->findBordersInsideRegionOfInterest(displayGroup, browserTabIndex, surface, nodeInROI, nodeCountAndBordersFromFileInROI); for (std::vector > ::iterator bi = nodeCountAndBordersFromFileInROI.begin(); bi != nodeCountAndBordersFromFileInROI.end(); bi++) { Border* border = bi->second; borderToBorderFileMap.insert(std::make_pair(border, borderFile)); bordersInsideRegionOfInterest.push_back(*bi); } } if (bordersInsideRegionOfInterest.empty()) { WuQMessageBox::errorOk(this, "No borders were found inside the drawn border."); return; } std::sort(bordersInsideRegionOfInterest.begin(), bordersInsideRegionOfInterest.end()); if (m_borderOptimizeDialog == NULL) { m_borderOptimizeDialog = new BorderOptimizeDialog(this); } m_borderOptimizeDialog->updateDialog(browserTabIndex, surface, bordersInsideRegionOfInterest, const_cast(borderDrawnByUser), nodesInsideBorder); if (m_borderOptimizeDialog->exec() == BorderOptimizeDialog::Accepted) { std::vector modifiedBorders; m_borderOptimizeDialog->getModifiedBorders(modifiedBorders); /* * Track modified borders so that changes can be 'undone' by * the user. */ std::vector undoBorders; for (std::vector::iterator mbi = modifiedBorders.begin(); mbi != modifiedBorders.end(); mbi++) { Border* border = *mbi; std::map::iterator mapIter = borderToBorderFileMap.find(border); if (mapIter != borderToBorderFileMap.end()) { BorderFile* borderFile = mapIter->second; undoBorders.push_back(BorderFileAndBorderMemento(borderFile, border)); } else { CaretAssertMessage(0, "PROGRAM ERROR: border file not found for border."); } } setLastEditedBorder(undoBorders); if ( ! m_borderOptimizeDialog->isKeepBoundaryBorderSelected()) { this->inputModeBorders->borderBeingDrawnByOpenGL->clear(); } } /* * Update all graphics windows to displayed changed borders */ EventManager::get()->sendEvent(EventUserInterfaceUpdate().getPointer()); adjustViewActionTriggered(); } /** * Called when Adjust View button is pressed. */ void UserInputModeBordersWidget::adjustViewActionTriggered() { EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } /** * Called when a draw mode button is clicked. * @param action * Action that was triggered. */ void UserInputModeBordersWidget::drawOperationActionTriggered(QAction* action) { const int drawModeInteger = action->data().toInt(); const UserInputModeBorders::DrawOperation drawOperation = static_cast(drawModeInteger); this->inputModeBorders->setDrawOperation(drawOperation); } /** * @return The edit widget. */ QWidget* UserInputModeBordersWidget::createEditOperationWidget() { const AString deleteToolTipText = ("Delete a border by clicking the mouse over " "any point in the border." + m_transformToolTipText); QAction* deleteAction = WuQtUtilities::createAction("Delete", WuQtUtilities::createWordWrappedToolTipText(deleteToolTipText), this); deleteAction->setCheckable(true); deleteAction->setData(static_cast(UserInputModeBorders::EDIT_OPERATION_DELETE)); QToolButton* deleteToolButton = new QToolButton(); deleteToolButton->setDefaultAction(deleteAction); const AString propertiesToolTipText = ("A dialog for editing a border's properties is displayed by " "clicking any point in a border." + m_transformToolTipText); QAction* propertiesAction = WuQtUtilities::createAction("Properties", WuQtUtilities::createWordWrappedToolTipText(propertiesToolTipText), this); propertiesAction->setCheckable(true); propertiesAction->setData(static_cast(UserInputModeBorders::EDIT_OPERATION_PROPERTIES)); QToolButton* propertiesToolButton = new QToolButton(); propertiesToolButton->setDefaultAction(propertiesAction); this->editOperationActionGroup = new QActionGroup(this); this->editOperationActionGroup->addAction(deleteAction); this->editOperationActionGroup->addAction(propertiesAction); this->editOperationActionGroup->setExclusive(true); QObject::connect(this->editOperationActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(editOperationActionTriggered(QAction*))); QWidget* widget = new QWidget(); QHBoxLayout* layout = new QHBoxLayout(widget); WuQtUtilities::setLayoutSpacingAndMargins(layout, 2, 0); layout->addWidget(deleteToolButton); layout->addWidget(propertiesToolButton); widget->setFixedWidth(widget->sizeHint().width()); return widget; } /** * Called when a edit button is clicked. * @param action * Action that was triggered. */ void UserInputModeBordersWidget::editOperationActionTriggered(QAction* action) { const int editModeInteger = action->data().toInt(); const UserInputModeBorders::EditOperation editOperation = static_cast(editModeInteger); this->inputModeBorders->setEditOperation(editOperation); } /** * @return The ROI widget. */ QWidget* UserInputModeBordersWidget::createRoiOperationWidget() { QWidget* widget = new QWidget(); // QHBoxLayout* layout = new QHBoxLayout(widget); // WuQtUtilities::setLayoutSpacingAndMargins(layout, 2, 0); // // widget->setFixedWidth(widget->sizeHint().width()); return widget; } /** * Called when the user selects a border in ROI opeation. * * @param brain * Brain on which identification occurred. * @param surfaceFile * Surface on which border is located. * @param border * Border for which nodes are found inside. */ void UserInputModeBordersWidget::executeRoiInsideSelectedBorderOperation(Brain* /*brain*/, Surface* surface, Border* border) { if (border->verifyAllPointsOnSameStructure() == false) { WuQMessageBox::errorOk(this, "Error: Border points are on more than one structure."); return; } RegionOfInterestCreateFromBorderDialog createRoiDialog(border, surface, this); createRoiDialog.exec(); } /** * Reset the last edited border. */ void UserInputModeBordersWidget::resetLastEditedBorder() { m_undoFinishBorders.clear(); } /** * Set the last edited border. * * @param undoFinishBorders * Borders that were changed by the last border edit operation. */ void UserInputModeBordersWidget::setLastEditedBorder(std::vector& undoFinishBorders) { m_undoFinishBorders = undoFinishBorders; } workbench-1.1.1/src/GuiQt/UserInputModeBordersWidget.h000066400000000000000000000107731255417355300227370ustar00rootroot00000000000000#ifndef __USER_INPUT_MODE_BORDERS_WIDGET__H_ #define __USER_INPUT_MODE_BORDERS_WIDGET__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "DisplayGroupEnum.h" #include "EventListenerInterface.h" class QAction; class QActionGroup; class QComboBox; class QStackedWidget; class QToolButton; namespace caret { class BorderFile; class BorderOptimizeDialog; class Border; class Brain; class Surface; class UserInputModeBorders; class UserInputModeBordersWidget : public QWidget, public EventListenerInterface { Q_OBJECT public: UserInputModeBordersWidget(UserInputModeBorders* inputModeBorders, QWidget* parent = 0); virtual ~UserInputModeBordersWidget(); virtual void receiveEvent(Event* event); void updateWidget(); void executeFinishOperation(); void executeRoiInsideSelectedBorderOperation(Brain* brain, Surface* surface, Border* border); private slots: void adjustViewActionTriggered(); void drawOperationActionTriggered(QAction*); void editOperationActionTriggered(QAction*); void modeComboBoxSelection(int); void drawResetButtonClicked(); void drawUndoButtonClicked(); void drawUndoLastEditButtonClicked(); void drawFinishButtonClicked(); private: class BorderFileAndBorderMemento { public: BorderFileAndBorderMemento(BorderFile* borderFile, Border* border) { m_borderFile = borderFile; m_border = border; } BorderFile* m_borderFile; Border* m_border; }; UserInputModeBordersWidget(const UserInputModeBordersWidget&); UserInputModeBordersWidget& operator=(const UserInputModeBordersWidget&); void setActionGroupByActionData(QActionGroup* actionGroup, const int dataInteger); QActionGroup* drawOperationActionGroup; QActionGroup* editOperationActionGroup; QWidget* createModeWidget(); QWidget* createDrawOperationWidget(); QWidget* createEditOperationWidget(); QWidget* createRoiOperationWidget(); void setLastEditedBorder(std::vector& undoFinishBorders); void resetLastEditedBorder(); void processBorderOptimization(const DisplayGroupEnum::Enum displayGroup, const int32_t browserTabIndex, Surface* surface, Border* borderDrawnByUser); QComboBox* modeComboBox; QWidget* widgetMode; QWidget* widgetDrawOperation; QWidget* widgetEditOperation; QWidget* widgetRoiOperation; QStackedWidget* operationStackedWidget; UserInputModeBorders* inputModeBorders; QString m_transformToolTipText; QToolButton* m_undoFinishToolButton; BorderOptimizeDialog* m_borderOptimizeDialog; std::vector m_undoFinishBorders; }; #ifdef __USER_INPUT_MODE_BORDERS_WIDGET_DECLARE__ // #endif // __USER_INPUT_MODE_BORDERS_WIDGET_DECLARE__ } // namespace #endif //__USER_INPUT_MODE_BORDERS_WIDGET__H_ workbench-1.1.1/src/GuiQt/UserInputModeFoci.cxx000066400000000000000000000237701255417355300214270ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __USER_INPUT_MODE_FOCI_DECLARE__ #include "UserInputModeFoci.h" #undef __USER_INPUT_MODE_FOCI_DECLARE__ #include "Brain.h" #include "BrainOpenGLViewportContent.h" #include "BrainOpenGLWidget.h" #include "BrainStructure.h" #include "BrowserTabContent.h" #include "CaretAssert.h" #include "EventGraphicsUpdateAllWindows.h" #include "EventGraphicsUpdateOneWindow.h" #include "EventManager.h" #include "EventUserInterfaceUpdate.h" #include "FociFile.h" #include "FociPropertiesEditorDialog.h" #include "Focus.h" #include "GuiManager.h" #include "SelectionItemFocusSurface.h" #include "SelectionItemFocusVolume.h" #include "SelectionItemSurfaceNode.h" #include "SelectionItemVoxel.h" #include "SelectionManager.h" #include "MouseEvent.h" #include "Surface.h" #include "UserInputModeFociWidget.h" #include "UserInputModeView.h" #include "VolumeFile.h" using namespace caret; /** * \class caret::UserInputModeFoci * \brief Processes user input for foci. */ /** * Constructor. */ UserInputModeFoci::UserInputModeFoci(const int32_t windowIndex) : UserInputModeView(UserInputModeAbstract::FOCI), m_windowIndex(windowIndex) { m_inputModeFociWidget = new UserInputModeFociWidget(this, windowIndex); m_mode = MODE_CREATE; m_editOperation = EDIT_OPERATION_PROPERTIES; setWidgetForToolBar(m_inputModeFociWidget); } /** * Destructor. */ UserInputModeFoci::~UserInputModeFoci() { } /** * @return the mode. */ UserInputModeFoci::Mode UserInputModeFoci::getMode() const { return m_mode; } /** * Set the mode. * @param mode * New value for mode. */ void UserInputModeFoci::setMode(const Mode mode) { if (m_mode != mode) { m_mode = mode; EventManager::get()->sendEvent(EventGraphicsUpdateOneWindow(m_windowIndex).getPointer()); } this->m_inputModeFociWidget->updateWidget(); } /** * @return The edit operation. */ UserInputModeFoci::EditOperation UserInputModeFoci::getEditOperation() const { return m_editOperation; } /** * Set the edit operation. * @param editOperation * New edit operation. */ void UserInputModeFoci::setEditOperation(const EditOperation editOperation) { m_editOperation = editOperation; } /** * Called when 'this' user input receiver is set * to receive events. */ void UserInputModeFoci::initialize() { m_inputModeFociWidget->updateWidget(); } /** * Called when 'this' user input receiver is no * longer set to receive events. */ void UserInputModeFoci::finish() { } /** * Called to update the input receiver for various events. */ void UserInputModeFoci::update() { } /** * @return The cursor for display in the OpenGL widget. */ CursorEnum::Enum UserInputModeFoci::getCursor() const { CursorEnum::Enum cursor = CursorEnum::CURSOR_DEFAULT; switch (m_mode) { case MODE_CREATE: break; case MODE_EDIT: cursor = CursorEnum::CURSOR_POINTING_HAND; switch (m_editOperation) { case EDIT_OPERATION_DELETE: cursor = CursorEnum::CURSOR_CROSS; break; case EDIT_OPERATION_PROPERTIES: cursor = CursorEnum::CURSOR_WHATS_THIS; break; } break; case MODE_OPERATIONS: cursor = CursorEnum::CURSOR_POINTING_HAND; break; } return cursor; } void UserInputModeFoci::updateAfterFociChanged() { /* * Need to update all graphics windows and all border controllers. */ EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); EventManager::get()->sendEvent(EventUserInterfaceUpdate().addFoci().getPointer()); } /** * Determine the structure for the given surface. Find the primary anatomical * surface for the structure and return it. If no primary anatomical surface * is found, the return the surface that was passed in. */ Surface* UserInputModeFoci::getAnatomicalSurfaceForSurface(Surface* surface) { Brain* brain = GuiManager::get()->getBrain(); const StructureEnum::Enum structure = surface->getStructure(); BrainStructure* bs = brain->getBrainStructure(structure, false); Surface* anatSurf = bs->getPrimaryAnatomicalSurface(); if (anatSurf != NULL) { return anatSurf; } return surface; } /** * Process a mouse left click event. * * @param mouseEvent * Mouse event information. */ void UserInputModeFoci::mouseLeftClick(const MouseEvent& mouseEvent) { BrainOpenGLViewportContent* viewportContent = mouseEvent.getViewportContent(); if (viewportContent == NULL) { return; } BrainOpenGLWidget* openGLWidget = mouseEvent.getOpenGLWidget(); BrowserTabContent* browserTabContent = viewportContent->getBrowserTabContent(); SelectionManager* idManager = openGLWidget->performIdentification(mouseEvent.getX(), mouseEvent.getY(), true); switch (m_mode) { case MODE_CREATE: { SelectionItemSurfaceNode* idNode = idManager->getSurfaceNodeIdentification(); SelectionItemVoxel* idVoxel = idManager->getVoxelIdentification(); if (idNode->isValid()) { Surface* surfaceViewed = idNode->getSurface(); CaretAssert(surfaceViewed); const Surface* anatSurface = getAnatomicalSurfaceForSurface(surfaceViewed); const StructureEnum::Enum anatStructure = anatSurface->getStructure(); const int32_t nodeIndex = idNode->getNodeNumber(); const AString focusName = (StructureEnum::toGuiName(anatStructure) + " Vertex " + AString::number(nodeIndex)); const float* xyz = anatSurface->getCoordinate(nodeIndex); const AString comment = ("Created from " + focusName); Focus* focus = new Focus(); focus->setName(focusName); focus->getProjection(0)->setStereotaxicXYZ(xyz); focus->setComment(comment); FociPropertiesEditorDialog::createFocus(focus, browserTabContent, m_inputModeFociWidget); } else if (idVoxel->isValid()) { const VolumeMappableInterface* vf = idVoxel->getVolumeFile(); const CaretMappableDataFile* cmdf = dynamic_cast(vf); int64_t ijk[3]; idVoxel->getVoxelIJK(ijk); float xyz[3]; vf->indexToSpace(ijk, xyz); const AString focusName = (cmdf->getFileNameNoPath() + " IJK (" + AString::fromNumbers(ijk, 3, ",") + ")"); const AString comment = ("Created from " + focusName); Focus* focus = new Focus(); focus->setName(focusName); focus->getProjection(0)->setStereotaxicXYZ(xyz); focus->setComment(comment); FociPropertiesEditorDialog::createFocus(focus, browserTabContent, m_inputModeFociWidget); } } break; case MODE_EDIT: { FociFile* fociFile = NULL; Focus* focus = NULL; SelectionItemFocusVolume* idVolFocus = idManager->getVolumeFocusIdentification(); if (idVolFocus->isValid()) { fociFile = idVolFocus->getFociFile(); CaretAssert(fociFile); focus = idVolFocus->getFocus(); CaretAssert(focus); } SelectionItemFocusSurface* idFocus = idManager->getSurfaceFocusIdentification(); if (idFocus->isValid()) { fociFile = idFocus->getFociFile(); CaretAssert(fociFile); focus = idFocus->getFocus(); CaretAssert(focus); } if ((fociFile != NULL) && (focus != NULL)) { switch (m_editOperation) { case EDIT_OPERATION_DELETE: fociFile->removeFocus(focus); updateAfterFociChanged(); break; case EDIT_OPERATION_PROPERTIES: { FociPropertiesEditorDialog::editFocus(fociFile, focus, openGLWidget); } } } } break; case MODE_OPERATIONS: break; } } workbench-1.1.1/src/GuiQt/UserInputModeFoci.h000066400000000000000000000055301255417355300210460ustar00rootroot00000000000000#ifndef __USER_INPUT_MODE_FOCI__H_ #define __USER_INPUT_MODE_FOCI__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "UserInputModeView.h" namespace caret { class BrainOpenGLViewportContent; class BrainOpenGLWidget; class MouseEvent; class Surface; class UserInputModeFociWidget; class UserInputModeFoci : public UserInputModeView { public: enum Mode { MODE_CREATE, MODE_EDIT, MODE_OPERATIONS }; enum EditOperation { EDIT_OPERATION_DELETE, EDIT_OPERATION_PROPERTIES }; UserInputModeFoci(const int32_t windowIndex); virtual ~UserInputModeFoci(); virtual void initialize(); virtual void finish(); virtual void update(); virtual CursorEnum::Enum getCursor() const; virtual void mouseLeftClick(const MouseEvent& mouseEvent); private: /* * Note some private methods are accessed by the * friend UserInputModeFociWidget. */ friend class UserInputModeFociWidget; UserInputModeFoci(const UserInputModeFoci&); UserInputModeFoci& operator=(const UserInputModeFoci&); Mode getMode() const; void setMode(const Mode mode); EditOperation getEditOperation() const; void setEditOperation(const EditOperation editOperation); void updateAfterFociChanged(); Surface* getAnatomicalSurfaceForSurface(Surface* surface); // ADD_NEW_MEMBERS_HERE const int32_t m_windowIndex; UserInputModeFociWidget* m_inputModeFociWidget; Mode m_mode; EditOperation m_editOperation; }; #ifdef __USER_INPUT_MODE_FOCI_DECLARE__ // #endif // __USER_INPUT_MODE_FOCI_DECLARE__ } // namespace #endif //__USER_INPUT_MODE_FOCI__H_ workbench-1.1.1/src/GuiQt/UserInputModeFociWidget.cxx000066400000000000000000000427131255417355300225710ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #include #include #define __USER_INPUT_MODE_FOCI_WIDGET_DECLARE__ #include "UserInputModeFociWidget.h" #undef __USER_INPUT_MODE_FOCI_WIDGET_DECLARE__ #include "Brain.h" #include "BrainBrowserWindow.h" #include "BrowserTabContent.h" #include "CaretAssert.h" #include "DisplayPropertiesFoci.h" #include "EventGraphicsUpdateAllWindows.h" #include "EventManager.h" #include "EventUserInterfaceUpdate.h" #include "FociFile.h" #include "FociPropertiesEditorDialog.h" #include "Focus.h" #include "GuiManager.h" #include "SelectionManager.h" #include "SelectionItemSurfaceNode.h" #include "SelectionItemVoxel.h" #include "ModelSurface.h" #include "ModelWholeBrain.h" #include "Surface.h" #include "UserInputModeFoci.h" #include "VolumeFile.h" #include "WuQMessageBox.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::UserInputModeFociWidget * \brief Foci controls shown at bottom of toolbar */ /** * Constructor. * @param inputModeFoci * Process of mouse input for foci * @param windowIndex * Index of browser window * @param parent * Parent widget */ UserInputModeFociWidget::UserInputModeFociWidget(UserInputModeFoci* inputModeFoci, const int32_t windowIndex, QWidget* parent) : QWidget(parent), m_windowIndex(windowIndex) { m_transformToolTipText = ("\n\n" "At any time, the view of the surface may be changed by\n" " PAN: Move the mouse with the left mouse button down while " "holding down the Shift key.\n" " ROTATE: Move the mouse with the left mouse button down.\n" " ZOOM: Move the mouse with the left mouse button down while " "holding down the Ctrl key (Apple key on Macs)." ); m_inputModeFoci = inputModeFoci; QLabel* nameLabel = new QLabel("Foci "); QWidget* modeWidget = createModeWidget(); m_createOperationWidget = createCreateOperationWidget(); m_editOperationWidget = createEditOperationWidget(); m_taskOperationWidget = createTaskOperationWidget(); m_operationStackedWidget = new QStackedWidget(); m_operationStackedWidget->addWidget(m_createOperationWidget); m_operationStackedWidget->addWidget(m_editOperationWidget); //m_operationStackedWidget->addWidget(m_taskOperationWidget); QHBoxLayout* layout = new QHBoxLayout(this); WuQtUtilities::setLayoutSpacingAndMargins(layout, 0, 0); layout->addWidget(nameLabel); layout->addWidget(modeWidget); layout->addSpacing(10); layout->addWidget(m_operationStackedWidget); layout->addStretch(); } /** * Destructor. */ UserInputModeFociWidget::~UserInputModeFociWidget() { } /** * Update the contents of the widget. */ void UserInputModeFociWidget::updateWidget() { /* * Show the proper widget */ switch (m_inputModeFoci->getMode()) { case UserInputModeFoci::MODE_CREATE: m_operationStackedWidget->setCurrentWidget(m_createOperationWidget); // setActionGroupByActionData(m_createOperationActionGroup, // m_inputModeFoci->getCreateOperation()); break; case UserInputModeFoci::MODE_EDIT: m_operationStackedWidget->setCurrentWidget(m_editOperationWidget); setActionGroupByActionData(m_editOperationActionGroup, m_inputModeFoci->getEditOperation()); break; case UserInputModeFoci::MODE_OPERATIONS: m_operationStackedWidget->setCurrentWidget(m_taskOperationWidget); break; } const int selectedModeInteger = (int)m_inputModeFoci->getMode(); const int modeComboBoxIndex = m_modeComboBox->findData(selectedModeInteger); CaretAssert(modeComboBoxIndex >= 0); m_modeComboBox->blockSignals(true); m_modeComboBox->setCurrentIndex(modeComboBoxIndex); m_modeComboBox->blockSignals(false); } /** * Set the action with its data value of the given integer * as the active action. * @param actionGroup * Action group for which action is selected. * @param dataInteger * Integer value for data attribute. */ void UserInputModeFociWidget::setActionGroupByActionData(QActionGroup* actionGroup, const int dataInteger) { actionGroup->blockSignals(true); const QList actionList = actionGroup->actions(); QListIterator iter(actionList); while (iter.hasNext()) { QAction* action = iter.next(); const int actionDataInteger = action->data().toInt(); if (dataInteger == actionDataInteger) { action->setChecked(true); break; } } actionGroup->blockSignals(false); } /** * @return The mode widget. */ QWidget* UserInputModeFociWidget::createModeWidget() { m_modeComboBox = new QComboBox(); m_modeComboBox->addItem("Create", (int)UserInputModeFoci::MODE_CREATE); m_modeComboBox->addItem("Edit", (int)UserInputModeFoci::MODE_EDIT); // m_modeComboBox->addItem("Tasks", (int)UserInputModeFoci::MODE_OPERATIONS); QObject::connect(m_modeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(modeComboBoxSelection(int))); QWidget* widget = new QWidget(); QHBoxLayout* layout = new QHBoxLayout(widget); WuQtUtilities::setLayoutSpacingAndMargins(layout, 2, 0); layout->addWidget(m_modeComboBox); widget->setFixedWidth(widget->sizeHint().width()); return widget; } /** * Called when a mode is selected from the mode combo box. * @param indx * Index of item selected. */ void UserInputModeFociWidget::modeComboBoxSelection(int indx) { const int modeInteger = m_modeComboBox->itemData(indx).toInt(); const UserInputModeFoci::Mode mode = (UserInputModeFoci::Mode)modeInteger; m_inputModeFoci->setMode(mode); } /** * @return The draw operation widget. */ QWidget* UserInputModeFociWidget::createCreateOperationWidget() { const AString newToolTipText = ("Press this button to display a dialog for creating a new focus. " "If the mouse is clicked over a model, the dialog for creating a focus is " "displayed with the focus' coordinates set to the stereotaxic coordinates at " "the location of the mouse click." + m_transformToolTipText); QAction* newFocusAction = WuQtUtilities::createAction("New...", WuQtUtilities::createWordWrappedToolTipText(newToolTipText), this, this, SLOT(createNewFocusActionTriggered())); QToolButton* newFocusToolButton = new QToolButton(); newFocusToolButton->setDefaultAction(newFocusAction); const AString lastIDToolTipText = ("Press this button to display a dialog for creating a new focus " "with the focus' coordinates set to the stereotaxic location of the " "last identification operation. While in focus mode, an identification " "is performed by holding down the Shift key and clicking the mouse." + m_transformToolTipText); QAction* lastIdFocusAction = WuQtUtilities::createAction("Last ID", WuQtUtilities::createWordWrappedToolTipText(lastIDToolTipText), this, this, SLOT(createLastIdentificationFocusActionTriggered())); QToolButton* lastIdFocusToolButton = new QToolButton(); lastIdFocusToolButton->setDefaultAction(lastIdFocusAction); QWidget* widget = new QWidget(); QHBoxLayout* layout = new QHBoxLayout(widget); WuQtUtilities::setLayoutSpacingAndMargins(layout, 2, 0); layout->addWidget(newFocusToolButton); layout->addSpacing(5); layout->addWidget(lastIdFocusToolButton); widget->setFixedWidth(widget->sizeHint().width()); return widget; } /** * Called when new focus button is triggered * @param action * Action that was selected. */ void UserInputModeFociWidget::createNewFocusActionTriggered() { BrainBrowserWindow* browserWindow = GuiManager::get()->getBrowserWindowByWindowIndex(m_windowIndex); if (browserWindow == NULL) { return; } BrowserTabContent* btc = browserWindow->getBrowserTabContent(); if (btc == NULL) { return; } const int32_t browserTabIndex = btc->getTabNumber(); DisplayPropertiesFoci* dpf = GuiManager::get()->getBrain()->getDisplayPropertiesFoci(); const DisplayGroupEnum::Enum displayGroup = dpf->getDisplayGroupForTab(btc->getTabNumber()); dpf->setDisplayed(displayGroup, browserTabIndex, true); FociPropertiesEditorDialog::createFocus(new Focus(), btc, this); } /** * Called when last ID focus button is triggered * @param action * Action that was selected. */ void UserInputModeFociWidget::createLastIdentificationFocusActionTriggered() { Brain* brain = GuiManager::get()->getBrain(); const SelectionManager* idManager = brain->getSelectionManager(); const SelectionItem* idItem = idManager->getLastSelectedItem(); if (idItem != NULL) { const SelectionItemSurfaceNode* nodeID = dynamic_cast(idItem); const SelectionItemVoxel* voxelID = dynamic_cast(idItem); BrainBrowserWindow* browserWindow = GuiManager::get()->getBrowserWindowByWindowIndex(m_windowIndex); BrowserTabContent* browserTabContent = NULL; if (browserWindow != NULL) { browserTabContent = browserWindow->getBrowserTabContent(); } if (nodeID != NULL) { if (nodeID->isValid()) { const Surface* idSurface = nodeID->getSurface(); if (brain->isFileValid(idSurface)) { CaretAssert(idSurface); const StructureEnum::Enum structure = idSurface->getStructure(); const Surface* surface = brain->getPrimaryAnatomicalSurfaceForStructure(structure); if (surface != NULL) { const int32_t nodeIndex = nodeID->getNodeNumber(); const AString focusName = ("Last ID " + StructureEnum::toGuiName(structure) + " Node " + AString::number(nodeIndex)); const float* xyz = surface->getCoordinate(nodeIndex); const AString comment = ("Created from " + focusName); Focus* focus = new Focus(); focus->setName(focusName); focus->getProjection(0)->setStereotaxicXYZ(xyz); focus->setComment(comment); FociPropertiesEditorDialog::createFocus(focus, browserTabContent, this); } else { WuQMessageBox::errorOk(this, ("No anatomical surface found for " + StructureEnum::toGuiName(structure))); } } } } else if (voxelID != NULL) { if (voxelID->isValid()) { const VolumeMappableInterface* volumeFile = voxelID->getVolumeFile(); const CaretMappableDataFile* cmdf = dynamic_cast(volumeFile); if (brain->isFileValid(cmdf)) { CaretAssert(volumeFile); int64_t ijk[3]; voxelID->getVoxelIJK(ijk); float xyz[3]; volumeFile->indexToSpace(ijk, xyz); const AString focusName = ("Last ID " + cmdf->getFileNameNoPath() + " IJK (" + AString::fromNumbers(ijk, 3, ",") + ")"); const AString comment = ("Created from " + focusName); Focus* focus = new Focus(); focus->setName(focusName); focus->getProjection(0)->setStereotaxicXYZ(xyz); focus->setComment(comment); FociPropertiesEditorDialog::createFocus(focus, browserTabContent, this); } } } } } /** * @return The edit widget. */ QWidget* UserInputModeFociWidget::createEditOperationWidget() { const AString deleteToolTipText = ("Delete a focus by clicking the mouse over the focus." + m_transformToolTipText); QAction* deleteAction = WuQtUtilities::createAction("Delete", WuQtUtilities::createWordWrappedToolTipText(deleteToolTipText), this); deleteAction->setCheckable(true); deleteAction->setData(static_cast(UserInputModeFoci::EDIT_OPERATION_DELETE)); QToolButton* deleteToolButton = new QToolButton(); deleteToolButton->setDefaultAction(deleteAction); const AString propertiesToolTipText = ("Click the mouse over a focus to display a dialog " "for editing the focus' properties." + m_transformToolTipText); QAction* propertiesAction = WuQtUtilities::createAction("Properties", WuQtUtilities::createWordWrappedToolTipText(propertiesToolTipText), this); propertiesAction->setCheckable(true); propertiesAction->setData(static_cast(UserInputModeFoci::EDIT_OPERATION_PROPERTIES)); QToolButton* propertiesToolButton = new QToolButton(); propertiesToolButton->setDefaultAction(propertiesAction); m_editOperationActionGroup = new QActionGroup(this); m_editOperationActionGroup->addAction(deleteAction); m_editOperationActionGroup->addAction(propertiesAction); m_editOperationActionGroup->setExclusive(true); QObject::connect(m_editOperationActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(editOperationActionTriggered(QAction*))); QWidget* widget = new QWidget(); QHBoxLayout* layout = new QHBoxLayout(widget); WuQtUtilities::setLayoutSpacingAndMargins(layout, 2, 0); layout->addWidget(deleteToolButton); layout->addWidget(propertiesToolButton); widget->setFixedWidth(widget->sizeHint().width()); return widget; } /** * @return The task operation widget. */ QWidget* UserInputModeFociWidget::createTaskOperationWidget() { QWidget* widget = new QWidget(); QHBoxLayout* layout = new QHBoxLayout(widget); WuQtUtilities::setLayoutSpacingAndMargins(layout, 2, 0); widget->setFixedWidth(widget->sizeHint().width()); return widget; } /** * Called when an edit operation button is selected. * @param action * Action that was selected. */ void UserInputModeFociWidget::editOperationActionTriggered(QAction* action) { const int editModeInteger = action->data().toInt(); const UserInputModeFoci::EditOperation editOperation = static_cast(editModeInteger); m_inputModeFoci->setEditOperation(editOperation); } workbench-1.1.1/src/GuiQt/UserInputModeFociWidget.h000066400000000000000000000057341255417355300222200ustar00rootroot00000000000000#ifndef __USER_INPUT_MODE_FOCI_WIDGET__H_ #define __USER_INPUT_MODE_FOCI_WIDGET__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" class QAction; class QActionGroup; class QComboBox; class QStackedWidget; namespace caret { class Focus; class FociFile; class UserInputModeFoci; class UserInputModeFociWidget : public QWidget { Q_OBJECT public: UserInputModeFociWidget(UserInputModeFoci* inputModeFoci, const int32_t windowIndex, QWidget* parent = 0); virtual ~UserInputModeFociWidget(); void updateWidget(); // ADD_NEW_METHODS_HERE private slots: void createNewFocusActionTriggered(); void createLastIdentificationFocusActionTriggered(); void editOperationActionTriggered(QAction*); void modeComboBoxSelection(int); private: UserInputModeFociWidget(const UserInputModeFociWidget&); UserInputModeFociWidget& operator=(const UserInputModeFociWidget&); QWidget* createModeWidget(); QWidget* createCreateOperationWidget(); QWidget* createEditOperationWidget(); QWidget* createTaskOperationWidget(); void setActionGroupByActionData(QActionGroup* actionGroup, const int dataInteger); // ADD_NEW_MEMBERS_HERE UserInputModeFoci* m_inputModeFoci; const int32_t m_windowIndex; QComboBox* m_modeComboBox; QActionGroup* m_editOperationActionGroup; QWidget* m_createOperationWidget; QWidget* m_editOperationWidget; QWidget* m_taskOperationWidget; QStackedWidget* m_operationStackedWidget; QString m_transformToolTipText; friend class UserInputModeFoci; }; #ifdef __USER_INPUT_MODE_FOCI_WIDGET_DECLARE__ #endif // __USER_INPUT_MODE_FOCI_WIDGET_DECLARE__ } // namespace #endif //__USER_INPUT_MODE_FOCI_WIDGET__H_ workbench-1.1.1/src/GuiQt/UserInputModeView.cxx000066400000000000000000000203771255417355300214610ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __USER_INPUT_MODE_VIEW_DECLARE__ #include "UserInputModeView.h" #undef __USER_INPUT_MODE_VIEW_DECLARE__ #include "Brain.h" #include "BrainOpenGLViewportContent.h" #include "BrainOpenGLWidget.h" #include "BrowserTabContent.h" #include "EventGraphicsUpdateOneWindow.h" #include "EventUpdateYokedWindows.h" #include "EventManager.h" #include "GuiManager.h" #include "MouseEvent.h" using namespace caret; /** * \class caret::UserInputModeView * \brief Processing user input for VIEW mode. * * Processes user input in VIEW mode which includes * viewing transformation of brain models and * identification operations. */ /** * Constructor. */ UserInputModeView::UserInputModeView() : UserInputModeAbstract(UserInputModeAbstract::VIEW) { } /** * Constructor for subclasses. * * @param inputMode * Subclass' input mode. */ UserInputModeView::UserInputModeView(const UserInputMode inputMode) : UserInputModeAbstract(inputMode) { } /** * Destructor. */ UserInputModeView::~UserInputModeView() { } /** * Process identification.. * * @param mouseEvent * The mouse event. * @param browserTabContent * Content of the browser window's tab. * @param openGLWidget * OpenGL Widget in which mouse event occurred. * @param mouseClickX * Location of where mouse was clicked. * @param mouseClickY * Location of where mouse was clicked. */ void UserInputModeView::processModelViewIdentification(BrainOpenGLViewportContent* viewportContent, BrainOpenGLWidget* openGLWidget, const int32_t mouseClickX, const int32_t mouseClickY) { SelectionManager* selectionManager = openGLWidget->performIdentification(mouseClickX, mouseClickY, false); BrowserTabContent* btc = viewportContent->getBrowserTabContent(); if (btc != NULL) { const int32_t tabIndex = btc->getTabNumber(); GuiManager::get()->processIdentification(tabIndex, selectionManager, openGLWidget); } } /** * Called when 'this' user input receiver is set * to receive events. */ void UserInputModeView::initialize() { } /** * Called when 'this' user input receiver is no * longer set to receive events. */ void UserInputModeView::finish() { } /** * Called to update the input receiver for various events. */ void UserInputModeView::update() { } /** * @return The cursor for display in the OpenGL widget. */ CursorEnum::Enum UserInputModeView::getCursor() const { return CursorEnum::CURSOR_DEFAULT; } /** * Get a description of this object's content. * @return String describing this object's content. */ AString UserInputModeView::toString() const { return "UserInputModeView"; } /** * Process a mouse left click event. * * @param mouseEvent * Mouse event information. */ void UserInputModeView::mouseLeftClick(const MouseEvent& mouseEvent) { if (mouseEvent.getViewportContent() == NULL) { return; } processModelViewIdentification(mouseEvent.getViewportContent(), mouseEvent.getOpenGLWidget(), mouseEvent.getX(), mouseEvent.getY()); } /** * Process a mouse left click with shift key down event. * * @param mouseEvent * Mouse event information. */ void UserInputModeView::mouseLeftClickWithShift(const MouseEvent& mouseEvent) { if (mouseEvent.getViewportContent() == NULL) { return; } } /** * Process a mouse left drag with no keys down event. * * @param mouseEvent * Mouse event information. */ void UserInputModeView::mouseLeftDrag(const MouseEvent& mouseEvent) { BrainOpenGLViewportContent* viewportContent = mouseEvent.getViewportContent(); if (viewportContent == NULL) { return; } BrowserTabContent* browserTabContent = viewportContent->getBrowserTabContent(); if (browserTabContent == NULL) { return; } browserTabContent->applyMouseRotation(viewportContent, mouseEvent.getPressedX(), mouseEvent.getPressedY(), mouseEvent.getX(), mouseEvent.getY(), mouseEvent.getDx(), mouseEvent.getDy()); /* * Update graphics. */ updateGraphics(mouseEvent); } /** * Process a mouse left drag with only the alt key down event. * * @param mouseEvent * Mouse event information. */ void UserInputModeView::mouseLeftDragWithAlt(const MouseEvent& mouseEvent) { if (mouseEvent.getViewportContent() == NULL) { return; } } /** * Process a mouse left drag with ctrl key down event. * * @param mouseEvent * Mouse event information. */ void UserInputModeView::mouseLeftDragWithCtrl(const MouseEvent& mouseEvent) { BrainOpenGLViewportContent* viewportContent = mouseEvent.getViewportContent(); if (viewportContent == NULL) { return; } BrowserTabContent* browserTabContent = viewportContent->getBrowserTabContent(); if (browserTabContent == NULL) { return; } browserTabContent->applyMouseScaling(mouseEvent.getDx(), mouseEvent.getDy()); updateGraphics(mouseEvent); } /** * Process a mouse left drag with shift key down event. * * @param mouseEvent * Mouse event information. */ void UserInputModeView::mouseLeftDragWithShift(const MouseEvent& mouseEvent) { BrainOpenGLViewportContent* viewportContent = mouseEvent.getViewportContent(); if (viewportContent == NULL) { return; } BrowserTabContent* browserTabContent = viewportContent->getBrowserTabContent(); if (browserTabContent == NULL) { return; } browserTabContent->applyMouseTranslation(viewportContent, mouseEvent.getPressedX(), mouseEvent.getPressedY(), mouseEvent.getDx(), mouseEvent.getDy()); updateGraphics(mouseEvent); } /** * If this windows is yoked, issue an event to update other * windows that are using the same yoking. */ void UserInputModeView::updateGraphics(const MouseEvent& mouseEvent) { bool issuedYokeEvent = false; if (mouseEvent.getViewportContent() != NULL) { BrowserTabContent* browserTabContent = mouseEvent.getViewportContent()->getBrowserTabContent(); const int32_t browserWindowIndex = mouseEvent.getBrowserWindowIndex(); EventManager::get()->sendEvent(EventGraphicsUpdateOneWindow(browserWindowIndex).getPointer()); if (browserTabContent != NULL) { if (browserTabContent->isYoked()) { issuedYokeEvent = true; EventManager::get()->sendEvent(EventUpdateYokedWindows(browserTabContent->getYokingGroup()).getPointer()); } } } /* * If not yoked, just need to update graphics. */ if (issuedYokeEvent == false) { EventManager::get()->sendEvent(EventGraphicsUpdateOneWindow(mouseEvent.getBrowserWindowIndex()).getPointer()); } } workbench-1.1.1/src/GuiQt/UserInputModeView.h000066400000000000000000000051611255417355300211000ustar00rootroot00000000000000#ifndef __USER_INPUT_MODE_VIEW__H_ #define __USER_INPUT_MODE_VIEW__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "UserInputModeAbstract.h" namespace caret { class UserInputModeView : public UserInputModeAbstract { public: UserInputModeView(); virtual ~UserInputModeView(); virtual void initialize(); virtual void finish(); virtual void update(); virtual CursorEnum::Enum getCursor() const; virtual void mouseLeftClick(const MouseEvent& mouseEvent); virtual void mouseLeftClickWithShift(const MouseEvent& mouseEvent); virtual void mouseLeftDrag(const MouseEvent& mouseEvent); virtual void mouseLeftDragWithAlt(const MouseEvent& mouseEvent); virtual void mouseLeftDragWithCtrl(const MouseEvent& mouseEvent); virtual void mouseLeftDragWithShift(const MouseEvent& mouseEvent); protected: UserInputModeView(const UserInputMode inputMode); private: UserInputModeView(const UserInputModeView&); UserInputModeView& operator=(const UserInputModeView&); void updateGraphics(const MouseEvent& mouseEvent); void processModelViewIdentification(BrainOpenGLViewportContent* viewportContent, BrainOpenGLWidget* openGLWidget, const int32_t mouseClickX, const int32_t mouseClickY); public: virtual AString toString() const; }; #ifdef __USER_INPUT_MODE_VIEW_DECLARE__ // #endif // __USER_INPUT_MODE_VIEW_DECLARE__ } // namespace #endif //__USER_INPUT_MODE_VIEW__H_ workbench-1.1.1/src/GuiQt/UserInputModeVolumeEdit.cxx000066400000000000000000000301111255417355300226070ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __USER_INPUT_MODE_VOLUME_EDIT_DECLARE__ #include "UserInputModeVolumeEdit.h" #undef __USER_INPUT_MODE_VOLUME_EDIT_DECLARE__ #include "Brain.h" #include "BrainOpenGLWidget.h" #include "BrainOpenGLViewportContent.h" #include "BrowserTabContent.h" #include "CaretAssert.h" #include "EventBrowserWindowContentGet.h" #include "EventGraphicsUpdateAllWindows.h" #include "EventManager.h" #include "EventUserInterfaceUpdate.h" #include "GiftiLabel.h" #include "GiftiLabelTable.h" #include "GuiManager.h" #include "MouseEvent.h" #include "Overlay.h" #include "OverlaySet.h" #include "SelectionItemVoxelEditing.h" #include "SelectionManager.h" #include "UserInputModeVolumeEditWidget.h" #include "VolumeFile.h" #include "VolumeFileEditorDelegate.h" #include "VolumeSliceViewPlaneEnum.h" #include "WuQMessageBox.h" using namespace caret; /** * \class caret::UserInputModeVolumeEdit * \brief User input processor for editing volume voxels * \ingroup GuiQt */ /** * Constructor. * * Index of window using this volume editor input handler. */ UserInputModeVolumeEdit::UserInputModeVolumeEdit(const int32_t windowIndex) : UserInputModeView(UserInputModeAbstract::VOLUME_EDIT), m_windowIndex(windowIndex) { m_inputModeVolumeEditWidget = new UserInputModeVolumeEditWidget(this, windowIndex); setWidgetForToolBar(m_inputModeVolumeEditWidget); } /** * Destructor. */ UserInputModeVolumeEdit::~UserInputModeVolumeEdit() { } /** * Called when 'this' user input receiver is set * to receive events. */ void UserInputModeVolumeEdit::initialize() { m_inputModeVolumeEditWidget->updateWidget(); } /** * Called when 'this' user input receiver is no * longer set to receive events. */ void UserInputModeVolumeEdit::finish() { } /** * Called to update the input receiver for various events. */ void UserInputModeVolumeEdit::update() { m_inputModeVolumeEditWidget->updateWidget(); } /** * Process a mouse event for editing the voxels. * * @param mouseEvent * Mouse event information. */ void UserInputModeVolumeEdit::processEditCommandFromMouse(const MouseEvent& mouseEvent) { if (mouseEvent.getViewportContent() == NULL) { return; } VolumeEditInfo volumeEditInfo; if ( ! getVolumeEditInfo(volumeEditInfo)) { return; } BrainOpenGLWidget* openGLWidget = mouseEvent.getOpenGLWidget(); const int mouseX = mouseEvent.getX(); const int mouseY = mouseEvent.getY(); SelectionManager* idManager = GuiManager::get()->getBrain()->getSelectionManager(); SelectionItemVoxelEditing* idEditVoxel = idManager->getVoxelEditingIdentification(); idEditVoxel->setVolumeFileForEditing(volumeEditInfo.m_volumeFile); idManager = openGLWidget->performIdentificationVoxelEditing(volumeEditInfo.m_volumeFile, mouseX, mouseY); if ((volumeEditInfo.m_volumeFile == idEditVoxel->getVolumeFile()) && idEditVoxel->isValid()) { int64_t ijk[3]; idEditVoxel->getVoxelIJK(ijk); VolumeEditingModeEnum::Enum editMode = VolumeEditingModeEnum::VOLUME_EDITING_MODE_ON; int32_t brushSizes[3] = { 0, 0, 0 }; float paletteMappedValue = 0; AString labelMappedName; m_inputModeVolumeEditWidget->getEditingParameters(editMode, brushSizes, paletteMappedValue, labelMappedName); const int64_t brushSizesInt64[3] = { brushSizes[0], brushSizes[1], brushSizes[2] }; VolumeFileEditorDelegate* editor = volumeEditInfo.m_volumeFileEditorDelegate; CaretAssert(editor); const VolumeSliceViewPlaneEnum::Enum slicePlane = volumeEditInfo.m_sliceViewPlane; const VolumeSliceProjectionTypeEnum::Enum sliceProjectionType = volumeEditInfo.m_sliceProjectionType; const Matrix4x4 obliqueRotationMatrix = volumeEditInfo.m_obliqueRotationMatrix; bool successFlag = true; AString errorMessage; float voxelValueOn = paletteMappedValue; float voxelValueOff = 0.0; float voxelDiffXYZ[3]; idEditVoxel->getVoxelDiffXYZ(voxelDiffXYZ); if (volumeEditInfo.m_volumeFile->isMappedWithLabelTable()) { const GiftiLabelTable* labelTable = volumeEditInfo.m_volumeFile->getMapLabelTable(volumeEditInfo.m_mapIndex); const GiftiLabel* label = labelTable->getLabel(labelMappedName); if (label != NULL) { voxelValueOn = label->getKey(); } else { errorMessage = ("Label name " + labelMappedName + " is not in label table."); successFlag = false; } const GiftiLabel* unassignedLabel = labelTable->getLabel(labelTable->getUnassignedLabelKey()); if (unassignedLabel != NULL) { voxelValueOff = unassignedLabel->getKey(); } } if (successFlag) { successFlag = editor->performEditingOperation(volumeEditInfo.m_mapIndex, editMode, slicePlane, sliceProjectionType, obliqueRotationMatrix, voxelDiffXYZ, ijk, brushSizesInt64, voxelValueOn, voxelValueOff, errorMessage); } if ( ! successFlag) { WuQMessageBox::errorOk(m_inputModeVolumeEditWidget, errorMessage); } updateGraphicsAfterEditing(volumeEditInfo.m_volumeFile, volumeEditInfo.m_mapIndex); } } /** * Process a mouse left click event. * * @param mouseEvent * Mouse event information. */ void UserInputModeVolumeEdit::mouseLeftClick(const MouseEvent& mouseEvent) { processEditCommandFromMouse(mouseEvent); } /** * Process a mouse left drag with ctrl and shift keys down event. * * @param mouseEvent * Mouse event information. */ void UserInputModeVolumeEdit::mouseLeftDragWithCtrlShift(const MouseEvent& mouseEvent) { processEditCommandFromMouse(mouseEvent); } /** * Update the graphics after editing. * * @param volumeFile * Volume file that needs coloring update. * @param mapIndex * Index of the map. */ void UserInputModeVolumeEdit::updateGraphicsAfterEditing(VolumeFile* volumeFile, const int32_t mapIndex) { CaretAssert(volumeFile); CaretAssert((mapIndex >= 0) && (mapIndex < volumeFile->getNumberOfMaps())); volumeFile->clearVoxelColoringForMap(mapIndex); PaletteFile* paletteFile = GuiManager::get()->getBrain()->getPaletteFile(); volumeFile->updateScalarColoringForMap(mapIndex, paletteFile); EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } /** * @return The cursor for display in the OpenGL widget. */ CursorEnum::Enum UserInputModeVolumeEdit::getCursor() const { CursorEnum::Enum cursor = CursorEnum::CURSOR_DEFAULT; // switch (m_mode) { // case MODE_CREATE: // break; // case MODE_EDIT: // cursor = CursorEnum::CURSOR_POINTING_HAND; // switch (m_editOperation) { // case EDIT_OPERATION_DELETE: // cursor = CursorEnum::CURSOR_CROSS; // break; // case EDIT_OPERATION_PROPERTIES: // cursor = CursorEnum::CURSOR_WHATS_THIS; // break; // } // break; // case MODE_OPERATIONS: // cursor = CursorEnum::CURSOR_POINTING_HAND; // break; // } return cursor; } /** * Get information about volume data being edited. * * @param volumeEditInfo * Loaded with editing information upon sucess. * @return * True if all ofvolumeEditInfo is valid, else false. Even * if false the overlay and model volume MAY be valid (non-NULL). */ bool UserInputModeVolumeEdit::getVolumeEditInfo(VolumeEditInfo& volumeEditInfo) { volumeEditInfo.m_topOverlay = NULL; volumeEditInfo.m_volumeOverlay = NULL; volumeEditInfo.m_volumeFile = NULL; volumeEditInfo.m_mapIndex = -1; volumeEditInfo.m_sliceViewPlane = VolumeSliceViewPlaneEnum::ALL; volumeEditInfo.m_sliceProjectionType = VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_ORTHOGONAL; volumeEditInfo.m_obliqueRotationMatrix.identity(); // volumeEditInfo.m_modelVolume = NULL; EventBrowserWindowContentGet windowEvent(m_windowIndex); EventManager::get()->sendEvent(windowEvent.getPointer()); BrowserTabContent* tabContent = windowEvent.getSelectedBrowserTabContent(); if (tabContent != NULL) { ModelVolume* modelVolume = tabContent->getDisplayedVolumeModel(); ModelWholeBrain* modelWholeBrain = tabContent->getDisplayedWholeBrainModel(); if ((modelVolume != NULL) || (modelWholeBrain != NULL)) { OverlaySet* overlaySet = tabContent->getOverlaySet(); const int32_t numOverlays = overlaySet->getNumberOfDisplayedOverlays(); for (int32_t i = 0; i < numOverlays; i++) { Overlay* overlay = overlaySet->getOverlay(i); if (i == 0) { volumeEditInfo.m_topOverlay = overlay; } if (overlay->isEnabled()) { CaretMappableDataFile* mapFile = NULL; int32_t mapIndex; overlay->getSelectionData(mapFile, mapIndex); if (mapFile != NULL) { VolumeFile* vf = dynamic_cast(mapFile); if (vf != NULL) { volumeEditInfo.m_volumeOverlay = overlay; volumeEditInfo.m_volumeFile = vf; volumeEditInfo.m_mapIndex = mapIndex; volumeEditInfo.m_sliceViewPlane = tabContent->getSliceViewPlane(); volumeEditInfo.m_sliceProjectionType = tabContent->getSliceProjectionType(); volumeEditInfo.m_volumeFileEditorDelegate = vf->getVolumeFileEditorDelegate(); volumeEditInfo.m_obliqueRotationMatrix = tabContent->getObliqueVolumeRotationMatrix(); return true; } } } } } } return false; } workbench-1.1.1/src/GuiQt/UserInputModeVolumeEdit.h000066400000000000000000000074761255417355300222560ustar00rootroot00000000000000#ifndef __USER_INPUT_MODE_VOLUME_EDIT_H__ #define __USER_INPUT_MODE_VOLUME_EDIT_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "Matrix4x4.h" #include "UserInputModeView.h" #include "VolumeSliceViewPlaneEnum.h" #include "VolumeSliceProjectionTypeEnum.h" namespace caret { class ModelVolume; class Overlay; class VolumeFile; class VolumeFileEditorDelegate; class UserInputModeVolumeEditWidget; class UserInputModeVolumeEdit : public UserInputModeView { public: /** * Contains information regarding the volume file that is * being edited. */ struct VolumeEditInfo { /** The top-most overlay in the tab */ Overlay* m_topOverlay; /** The overlay containing the volume file */ Overlay* m_volumeOverlay; /** Model volume containing the volume file */ //ModelVolume* m_modelVolume; /** The volume file being edited */ VolumeFile* m_volumeFile; /** Index of the map in the volume file being edited */ int32_t m_mapIndex; /** The current slice view plane */ VolumeSliceViewPlaneEnum::Enum m_sliceViewPlane; /** Slice projection type (orthogonal/oblique) */ VolumeSliceProjectionTypeEnum::Enum m_sliceProjectionType; /** The volume's editor delegate */ VolumeFileEditorDelegate* m_volumeFileEditorDelegate; /** The oblique projection rotation matrix */ Matrix4x4 m_obliqueRotationMatrix; }; UserInputModeVolumeEdit(const int32_t windowIndex); virtual ~UserInputModeVolumeEdit(); virtual void initialize(); virtual void finish(); virtual void update(); virtual CursorEnum::Enum getCursor() const; virtual void mouseLeftClick(const MouseEvent& mouseEvent); virtual void mouseLeftDragWithCtrlShift(const MouseEvent& mouseEvent); bool getVolumeEditInfo(VolumeEditInfo& volumeEditInfo); void updateGraphicsAfterEditing(VolumeFile* volumeFile, const int32_t mapIndex); // ADD_NEW_METHODS_HERE private: UserInputModeVolumeEdit(const UserInputModeVolumeEdit&); UserInputModeVolumeEdit& operator=(const UserInputModeVolumeEdit&); void processEditCommandFromMouse(const MouseEvent& mouseEvent); const int32_t m_windowIndex; UserInputModeVolumeEditWidget* m_inputModeVolumeEditWidget; friend class UserInputModeVolumeEditWidget; // ADD_NEW_MEMBERS_HERE }; #ifdef __USER_INPUT_MODE_VOLUME_EDIT_DECLARE__ // #endif // __USER_INPUT_MODE_VOLUME_EDIT_DECLARE__ } // namespace #endif //__USER_INPUT_MODE_VOLUME_EDIT_H__ workbench-1.1.1/src/GuiQt/UserInputModeVolumeEditWidget.cxx000066400000000000000000000601201255417355300237560ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __USER_INPUT_MODE_VOLUME_EDIT_WIDGET_DECLARE__ #include "UserInputModeVolumeEditWidget.h" #undef __USER_INPUT_MODE_VOLUME_EDIT_WIDGET_DECLARE__ #include #include #include #include #include #include #include #include #include "Brain.h" #include "CaretAssert.h" #include "CaretLogger.h" #include "EventDataFileAdd.h" #include "EventManager.h" #include "EventUpdateVolumeEditingToolBar.h" #include "EventUserInterfaceUpdate.h" #include "GiftiLabel.h" #include "GiftiLabelTableEditor.h" #include "GuiManager.h" #include "Overlay.h" #include "VolumeFile.h" #include "VolumeFileEditorDelegate.h" #include "VolumeFileCreateDialog.h" #include "WuQDataEntryDialog.h" #include "WuQFactory.h" #include "WuQMessageBox.h" #include "WuQSpinBoxOddValue.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::UserInputModeVolumeEditWidget * \brief User input widget for volume editing. * \ingroup GuiQt */ /** * Constructor. * @param inputModeVolumeEdit * Process of mouse input for volume editing * @param windowIndex * Index of browser window * @param parent * Parent widget */ UserInputModeVolumeEditWidget::UserInputModeVolumeEditWidget(UserInputModeVolumeEdit* inputModeVolumeEdit, const int32_t windowIndex, QWidget* parent) : QWidget(parent), EventListenerInterface(), m_inputModeVolumeEdit(inputModeVolumeEdit), m_windowIndex(windowIndex) { CaretAssert(inputModeVolumeEdit); QVBoxLayout* layout = new QVBoxLayout(this); WuQtUtilities::setLayoutSpacingAndMargins(layout, 2, 0); layout->addWidget(createSelectionToolBar()); layout->addWidget(createModeToolBar()); setSizePolicy(sizePolicy().horizontalPolicy(), QSizePolicy::Fixed); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_UPDATE_VOLUME_EDITING_TOOLBAR); } /** * Destructor. */ UserInputModeVolumeEditWidget::~UserInputModeVolumeEditWidget() { EventManager::get()->removeAllEventsFromListener(this); } /** * Receive an event * * @param event * The event. */ void UserInputModeVolumeEditWidget::receiveEvent(Event* event) { if (event->getEventType() == EventTypeEnum::EVENT_UPDATE_VOLUME_EDITING_TOOLBAR) { EventUpdateVolumeEditingToolBar* editVolEvent = dynamic_cast(event); CaretAssert(editVolEvent); editVolEvent->setEventProcessed(); updateWidget(); } } /** * Update the widget. */ void UserInputModeVolumeEditWidget::updateWidget() { bool isValid = false; UserInputModeVolumeEdit::VolumeEditInfo volumeEditInfo; if (m_inputModeVolumeEdit->getVolumeEditInfo(volumeEditInfo)) { m_lockAction->setChecked(volumeEditInfo.m_volumeFileEditorDelegate->isLocked(volumeEditInfo.m_mapIndex)); if (volumeEditInfo.m_volumeFile != NULL) { if (volumeEditInfo.m_volumeFile->isMappedWithLabelTable()) { m_voxelLabelValueToolButton->setVisible(true); m_voxelFloatValueSpinBox->setVisible(false); AString buttonText = m_voxelLabelValueAction->text(); GiftiLabelTable* labelTable = volumeEditInfo.m_volumeFile->getMapLabelTable(volumeEditInfo.m_mapIndex); if (labelTable != NULL) { GiftiLabel* label = labelTable->getLabel(buttonText); /* * Is there a label in the label table whose name * matches the name in the button's actoin? */ if (label == NULL) { /* * Default to the unassigned label */ const int32_t unassignedKey = labelTable->getUnassignedLabelKey(); GiftiLabel* unassignedLabel = labelTable->getLabel(unassignedKey); if (unassignedLabel != NULL) { buttonText = unassignedLabel->getName(); } /* * Find the first label that is not the unassigned * label */ const std::set keys = labelTable->getKeys(); for (std::set::iterator iter = keys.begin(); iter != keys.end(); iter++) { const int32_t key = *iter; if (key != unassignedKey) { GiftiLabel* keyLabel = labelTable->getLabel(key); if (keyLabel != NULL) { buttonText = keyLabel->getName(); break; } } } } } m_voxelLabelValueAction->setText(buttonText); isValid = true; } else if (volumeEditInfo.m_volumeFile->isMappedWithPalette()) { m_voxelLabelValueToolButton->setVisible(false); m_voxelFloatValueSpinBox->setVisible(true); isValid = true; } else { CaretAssert(0); } m_addMapsToolButton->defaultAction()->setEnabled(isValid); const bool orthogonalFlag = (volumeEditInfo.m_sliceProjectionType == VolumeSliceProjectionTypeEnum::VOLUME_SLICE_PROJECTION_ORTHOGONAL); QList modeActions = m_volumeEditModeActionGroup->actions(); const int32_t numModeActions = modeActions.size(); for (int32_t i = 0; i < numModeActions; i++) { QAction* action = modeActions.at(i); const int modeInt = action->data().toInt(); bool validFlag = false; VolumeEditingModeEnum::Enum mode = VolumeEditingModeEnum::fromIntegerCode(modeInt, &validFlag); CaretAssert(validFlag); bool modeEnabledFlag = false; if (orthogonalFlag) { modeEnabledFlag = true; } else { modeEnabledFlag = VolumeEditingModeEnum::isObliqueEditingAllowed(mode); } action->setEnabled(modeEnabledFlag); } } } this->setEnabled(isValid); } /** * @return Create and return the selection toolbar. */ QWidget* UserInputModeVolumeEditWidget::createSelectionToolBar() { QLabel* volumeLabel = new QLabel("Volume:"); m_newFileToolButton = new QToolButton(); m_newFileToolButton->setDefaultAction(WuQtUtilities::createAction("New", "Create a new volume file that will become the top-most overlay", this, this, SLOT(newFileActionTriggered()))); m_addMapsToolButton = new QToolButton(); m_addMapsToolButton->setDefaultAction(WuQtUtilities::createAction("Add", ("Add maps to the selected volume file.\n" "First new map will become the top-most overlay."), this, this, SLOT(addMapsActionTriggered()))); m_lockAction = WuQtUtilities::createAction("Lock", "Lock/unlock volume file to disallow/allow editing", this, this, SLOT(lockFileActionTriggered())); m_lockAction->setCheckable(true); QToolButton* lockFileToolButton = new QToolButton(); lockFileToolButton->setDefaultAction(m_lockAction); QLabel* editLabel = new QLabel("Edit:"); QToolButton* undoToolButton = new QToolButton(); undoToolButton->setDefaultAction(WuQtUtilities::createAction("Undo", "Undo the last volume edit", this, this, SLOT(undoActionTriggered()))); QToolButton* redoToolButton = new QToolButton(); redoToolButton->setDefaultAction(WuQtUtilities::createAction("Redo", "Redo (or is it undo) the last undo", this, this, SLOT(redoActionTriggered()))); QToolButton* resetToolButton = new QToolButton(); resetToolButton->setDefaultAction(WuQtUtilities::createAction("Reset", "Reset all edting of the volume", this, this, SLOT(resetActionTriggered()))); QLabel* brushSizeLabel = new QLabel("Brush Size:"); const int MIN_BRUSH_SIZE = 1; const int MAX_BRUSH_SIZE = 999; m_xBrushSizeSpinBox = new WuQSpinBoxOddValue(this); m_xBrushSizeSpinBox->setRange(MIN_BRUSH_SIZE, MAX_BRUSH_SIZE); QObject::connect(m_xBrushSizeSpinBox, SIGNAL(valueChanged(int)), this, SLOT(xBrushSizeValueChanged(int))); m_xBrushSizeSpinBox->getWidget()->setToolTip("Parasagittal brush size (voxels).\n" "Must be an odd value."); m_yBrushSizeSpinBox = new WuQSpinBoxOddValue(this); m_yBrushSizeSpinBox->setRange(MIN_BRUSH_SIZE, MAX_BRUSH_SIZE); QObject::connect(m_yBrushSizeSpinBox, SIGNAL(valueChanged(int)), this, SLOT(yBrushSizeValueChanged(int))); m_yBrushSizeSpinBox->getWidget()->setToolTip("Coronal brush size (voxels).\n" "Must be an odd value."); m_zBrushSizeSpinBox = new WuQSpinBoxOddValue(this); m_zBrushSizeSpinBox->setRange(MIN_BRUSH_SIZE, MAX_BRUSH_SIZE); QObject::connect(m_zBrushSizeSpinBox, SIGNAL(valueChanged(int)), this, SLOT(zBrushSizeValueChanged(int))); m_zBrushSizeSpinBox->getWidget()->setToolTip("Axial brush size (voxels).\n" "Must be an odd value."); m_voxelValueLabel = new QLabel("Value:"); m_voxelFloatValueSpinBox = WuQFactory::newDoubleSpinBoxWithMinMaxStepDecimalsSignalDouble(-1000.0, 1000.0, 1.0, 1, this, SLOT(voxelValueChanged(double))); m_voxelFloatValueSpinBox->setValue(1.0); m_voxelLabelValueAction = WuQtUtilities::createAction("XXX", "Choose Label for Voxels", this, this, SLOT(labelValueActionTriggered())); m_voxelLabelValueToolButton = new QToolButton(); m_voxelLabelValueToolButton->setDefaultAction(m_voxelLabelValueAction); const int SPACE = 10; QWidget* widget = new QWidget(); QHBoxLayout* layout = new QHBoxLayout(widget); WuQtUtilities::setLayoutSpacingAndMargins(layout, 2, 0); layout->addWidget(volumeLabel); layout->addWidget(m_newFileToolButton); layout->addWidget(m_addMapsToolButton); layout->addWidget(lockFileToolButton); layout->addSpacing(SPACE); layout->addWidget(editLabel); layout->addWidget(undoToolButton); layout->addWidget(redoToolButton); layout->addWidget(resetToolButton); layout->addSpacing(SPACE); layout->addWidget(brushSizeLabel); layout->addWidget(m_xBrushSizeSpinBox->getWidget()); layout->addWidget(m_yBrushSizeSpinBox->getWidget()); layout->addWidget(m_zBrushSizeSpinBox->getWidget()); layout->addSpacing(SPACE); layout->addWidget(m_voxelValueLabel); layout->addWidget(m_voxelFloatValueSpinBox); layout->addWidget(m_voxelLabelValueToolButton); layout->addStretch(); return widget; } /** * @return Create and return the mode toolbar. */ QWidget* UserInputModeVolumeEditWidget::createModeToolBar() { QWidget* widget = new QWidget(); QHBoxLayout* layout = new QHBoxLayout(widget); WuQtUtilities::setLayoutSpacingAndMargins(layout, 2, 0); QLabel* modeLabel = new QLabel("Mode:"); layout->addWidget(modeLabel); m_volumeEditModeActionGroup = new QActionGroup(this); m_volumeEditModeActionGroup->setExclusive(true); QObject::connect(m_volumeEditModeActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(editingModeActionTriggered(QAction*))); std::vector editModes; VolumeEditingModeEnum::getAllEnums(editModes); bool firstActionFlag = true; for (std::vector::iterator iter = editModes.begin(); iter != editModes.end(); iter++) { const VolumeEditingModeEnum::Enum mode = *iter; const int modeInt = VolumeEditingModeEnum::toIntegerCode(mode); const AString modeName = VolumeEditingModeEnum::toGuiName(mode); const AString toolTip = WuQtUtilities::createWordWrappedToolTipText(VolumeEditingModeEnum::toToolTip(mode)); QAction* action = new QAction(modeName, this); action->setData(modeInt); action->setToolTip(toolTip); action->setCheckable(true); if (firstActionFlag) { firstActionFlag = false; action->setChecked(true); } m_volumeEditModeActionGroup->addAction(action); QToolButton* toolButton = new QToolButton(); toolButton->setDefaultAction(action); layout->addWidget(toolButton); } layout->addStretch(); return widget; } /** * Get the editing parameters from the toolbar. * * @param editingModeOut * Output containing the selected editing mode. * @param brushSizeOut * Brush IJK/XYZ sizes. * @param floatValueOut * Float value for palette mapped volume files. * @param labelNameOut * Label name for label mapped volume files. */ void UserInputModeVolumeEditWidget::getEditingParameters(VolumeEditingModeEnum::Enum& editingModeOut, int32_t brushSizesOut[3], float& floatValueOut, AString& labelNameOut) const { editingModeOut = getEditingMode(); brushSizesOut[0] = m_xBrushSizeSpinBox->value(); brushSizesOut[1] = m_yBrushSizeSpinBox->value(); brushSizesOut[2] = m_zBrushSizeSpinBox->value(); floatValueOut = m_voxelFloatValueSpinBox->value(); labelNameOut = m_voxelLabelValueAction->text(); } /** * @return The volume editing mode. */ VolumeEditingModeEnum::Enum UserInputModeVolumeEditWidget::getEditingMode() const { QAction* action = m_volumeEditModeActionGroup->checkedAction(); CaretAssert(action); const int modeInt = action->data().toInt(); bool validFlag = false; const VolumeEditingModeEnum::Enum editMode = VolumeEditingModeEnum::fromIntegerCode(modeInt, &validFlag); CaretAssert(validFlag); return editMode; } /** * Called when new volume file button is clicked. */ void UserInputModeVolumeEditWidget::newFileActionTriggered() { VolumeFileCreateDialog newVolumeDialog(m_newFileToolButton); if (newVolumeDialog.exec() == VolumeFileCreateDialog::Accepted) { VolumeFile* vf = newVolumeDialog.getVolumeFile(); if (vf != NULL) { for (int32_t i = 0; i < vf->getNumberOfMaps(); i++) { vf->getVolumeFileEditorDelegate()->setLocked(i, false); } EventDataFileAdd addFileEvent(vf); EventManager::get()->sendEvent(addFileEvent.getPointer()); const int32_t mapIndex = 0; UserInputModeVolumeEdit::VolumeEditInfo volumeEditInfo; m_inputModeVolumeEdit->getVolumeEditInfo(volumeEditInfo); if (volumeEditInfo.m_topOverlay != NULL) { volumeEditInfo.m_topOverlay->setSelectionData(vf, mapIndex); volumeEditInfo.m_topOverlay->setEnabled(true); volumeEditInfo.m_topOverlay->setMapYokingGroup(MapYokingGroupEnum::MAP_YOKING_GROUP_OFF); m_inputModeVolumeEdit->updateGraphicsAfterEditing(vf, mapIndex); } EventManager::get()->sendEvent(EventUserInterfaceUpdate().getPointer()); } } } /** * Called when the add maps action is triggered. */ void UserInputModeVolumeEditWidget::addMapsActionTriggered() { UserInputModeVolumeEdit::VolumeEditInfo volumeEditInfo; if (m_inputModeVolumeEdit->getVolumeEditInfo(volumeEditInfo)) { VolumeFile* vf = volumeEditInfo.m_volumeFile; WuQDataEntryDialog ded("Add Map to Volume File", m_addMapsToolButton); const int32_t newMapIndex = vf->getNumberOfMaps(); QLineEdit* nameLineEdit = ded.addLineEditWidget("Map Name"); nameLineEdit->setText("Editing (" + AString::number(newMapIndex + 1) + ")"); if (ded.exec() == WuQDataEntryDialog::Accepted) { /* * Add map, set the map name, * set the selected map to the new map, * update graphics, update user interface. */ vf->addSubvolumes(1); vf->setMapName(newMapIndex, nameLineEdit->text().trimmed()); vf->getVolumeFileEditorDelegate()->setLocked(newMapIndex, false); volumeEditInfo.m_volumeOverlay->setSelectionData(vf, newMapIndex); m_inputModeVolumeEdit->updateGraphicsAfterEditing(vf, newMapIndex); EventManager::get()->sendEvent(EventUserInterfaceUpdate().getPointer()); updateWidget(); } } } /** * Called when lock button is clicked. */ void UserInputModeVolumeEditWidget::lockFileActionTriggered() { UserInputModeVolumeEdit::VolumeEditInfo volumeEditInfo; if (m_inputModeVolumeEdit->getVolumeEditInfo(volumeEditInfo)) { volumeEditInfo.m_volumeFileEditorDelegate->setLocked(volumeEditInfo.m_mapIndex, m_lockAction->isChecked()); } } /** * Called when undo button is clicked. */ void UserInputModeVolumeEditWidget::undoActionTriggered() { UserInputModeVolumeEdit::VolumeEditInfo volumeEditInfo; if (m_inputModeVolumeEdit->getVolumeEditInfo(volumeEditInfo)) { volumeEditInfo.m_volumeFileEditorDelegate->undo(volumeEditInfo.m_mapIndex); m_inputModeVolumeEdit->updateGraphicsAfterEditing(volumeEditInfo.m_volumeFile, volumeEditInfo.m_mapIndex); } } /** * Called when redo button is clicked. */ void UserInputModeVolumeEditWidget::redoActionTriggered() { UserInputModeVolumeEdit::VolumeEditInfo volumeEditInfo; if (m_inputModeVolumeEdit->getVolumeEditInfo(volumeEditInfo)) { volumeEditInfo.m_volumeFileEditorDelegate->redo(volumeEditInfo.m_mapIndex); m_inputModeVolumeEdit->updateGraphicsAfterEditing(volumeEditInfo.m_volumeFile, volumeEditInfo.m_mapIndex); } } /** * Called when reset button is clicked. */ void UserInputModeVolumeEditWidget::resetActionTriggered() { UserInputModeVolumeEdit::VolumeEditInfo volumeEditInfo; if (m_inputModeVolumeEdit->getVolumeEditInfo(volumeEditInfo)) { volumeEditInfo.m_volumeFileEditorDelegate->reset(volumeEditInfo.m_mapIndex); m_inputModeVolumeEdit->updateGraphicsAfterEditing(volumeEditInfo.m_volumeFile, volumeEditInfo.m_mapIndex); } } /** * Called when X-size brush value is changed. */ void UserInputModeVolumeEditWidget::xBrushSizeValueChanged(int) { } /** * Called when X-size brush value is changed. */ void UserInputModeVolumeEditWidget::yBrushSizeValueChanged(int) { } /** * Called when X-size brush value is changed. */ void UserInputModeVolumeEditWidget::zBrushSizeValueChanged(int) { } /** * Called when voxel value is changed. */ void UserInputModeVolumeEditWidget::voxelValueChanged(double) { } /** * Called when voxel label value action is triggered. */ void UserInputModeVolumeEditWidget::labelValueActionTriggered() { UserInputModeVolumeEdit::VolumeEditInfo volumeEditInfo; if (m_inputModeVolumeEdit->getVolumeEditInfo(volumeEditInfo)) { if (volumeEditInfo.m_volumeFile != NULL) { GiftiLabelTableEditor lte(volumeEditInfo.m_volumeFile, volumeEditInfo.m_mapIndex, "Select Label", GiftiLabelTableEditor::OPTION_NONE, m_voxelLabelValueToolButton); const AString defaultLabelName = m_voxelLabelValueAction->text(); if ( ! defaultLabelName.isEmpty()) { lte.selectLabelWithName(defaultLabelName); } if (lte.exec() == GiftiLabelTableEditor::Accepted) { const AString selectedName = lte.getLastSelectedLabelName(); m_voxelLabelValueAction->setText(selectedName); } } } } /** * Called when an editing mode is selected. * * @param action * Editing action that was selected. */ void UserInputModeVolumeEditWidget::editingModeActionTriggered(QAction* action) { CaretAssert(action); const int modeInt = action->data().toInt(); bool validFlag = false; (void)VolumeEditingModeEnum::fromIntegerCode(modeInt, &validFlag); CaretAssert(validFlag); } workbench-1.1.1/src/GuiQt/UserInputModeVolumeEditWidget.h000066400000000000000000000100741255417355300234060ustar00rootroot00000000000000#ifndef __USER_INPUT_MODE_VOLUME_EDIT_WIDGET_H__ #define __USER_INPUT_MODE_VOLUME_EDIT_WIDGET_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "EventListenerInterface.h" #include "UserInputModeVolumeEdit.h" #include "VolumeEditingModeEnum.h" class QAction; class QActionGroup; class QDoubleSpinBox; class QLabel; class QSpinBox; class QToolButton; namespace caret { class UserInputModeVolumeEdit; class WuQSpinBoxOddValue; class UserInputModeVolumeEditWidget : public QWidget, public EventListenerInterface { Q_OBJECT public: UserInputModeVolumeEditWidget(UserInputModeVolumeEdit* inputModeVolumeEdit, const int32_t windowIndex, QWidget* parent = 0); virtual ~UserInputModeVolumeEditWidget(); void receiveEvent(Event* event); void updateWidget(); void getEditingParameters(VolumeEditingModeEnum::Enum& editingModeOut, int32_t brushSizesOut[3], float& floatValueOut, AString& labelNameOut) const; VolumeEditingModeEnum::Enum getEditingMode() const; // ADD_NEW_METHODS_HERE private slots: void newFileActionTriggered(); void lockFileActionTriggered(); void undoActionTriggered(); void redoActionTriggered(); void resetActionTriggered(); void xBrushSizeValueChanged(int); void yBrushSizeValueChanged(int); void zBrushSizeValueChanged(int); void voxelValueChanged(double); void labelValueActionTriggered(); void editingModeActionTriggered(QAction*); void addMapsActionTriggered(); private: UserInputModeVolumeEditWidget(const UserInputModeVolumeEditWidget&); UserInputModeVolumeEditWidget& operator=(const UserInputModeVolumeEditWidget&); QWidget* createSelectionToolBar(); QWidget* createModeToolBar(); QAction* m_lockAction; UserInputModeVolumeEdit* m_inputModeVolumeEdit; const int32_t m_windowIndex; QActionGroup* m_volumeEditModeActionGroup; QToolButton* m_newFileToolButton; WuQSpinBoxOddValue* m_xBrushSizeSpinBox; WuQSpinBoxOddValue* m_yBrushSizeSpinBox; WuQSpinBoxOddValue* m_zBrushSizeSpinBox; QLabel* m_voxelValueLabel; QAction* m_voxelLabelValueAction; QToolButton* m_voxelLabelValueToolButton; QDoubleSpinBox* m_voxelFloatValueSpinBox; QToolButton* m_addMapsToolButton; // ADD_NEW_MEMBERS_HERE }; #ifdef __USER_INPUT_MODE_VOLUME_EDIT_WIDGET_DECLARE__ // #endif // __USER_INPUT_MODE_VOLUME_EDIT_WIDGET_DECLARE__ } // namespace #endif //__USER_INPUT_MODE_VOLUME_EDIT_WIDGET_H__ workbench-1.1.1/src/GuiQt/UsernamePasswordWidget.cxx000066400000000000000000000173321255417355300225260ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __USERNAME_PASSWORD_WIDGET_DECLARE__ #include "UsernamePasswordWidget.h" #undef __USERNAME_PASSWORD_WIDGET_DECLARE__ #include #include #include #include #include "CaretAssert.h" #include "CaretPreferences.h" #include "SessionManager.h" #include "WuQDialogModal.h" #include "WuQMessageBox.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::UsernamePasswordWidget * \brief Widget for username and password with saving to preferences. * \ingroup GuiQt */ /** * Constructor. * * @param parent * Parent widget. */ UsernamePasswordWidget::UsernamePasswordWidget(QWidget* parent) : QWidget(parent) { QLabel* usernameLabel = new QLabel("User Name: "); m_usernameLineEdit = new QLineEdit(); m_usernameLineEdit->setFixedWidth(200); QLabel* passwordLabel = new QLabel("Password: "); m_passwordLineEdit = new QLineEdit(); m_passwordLineEdit->setFixedWidth(200); m_passwordLineEdit->setEchoMode(QLineEdit::Password); m_savePasswordToPreferencesCheckBox = new QCheckBox("Save Password to Preferences"); QObject::connect(m_savePasswordToPreferencesCheckBox, SIGNAL(clicked(bool)), this, SLOT(savePasswordToPreferencesClicked(bool))); int row = 0; QGridLayout* loginGridLayout = new QGridLayout(this); loginGridLayout->setColumnStretch(0, 0); loginGridLayout->setColumnStretch(1, 100); loginGridLayout->addWidget(usernameLabel, row, 0); loginGridLayout->addWidget(m_usernameLineEdit, row, 1); row++; loginGridLayout->addWidget(passwordLabel, row, 0); loginGridLayout->addWidget(m_passwordLineEdit, row, 1); row++; loginGridLayout->addWidget(WuQtUtilities::createHorizontalLineWidget(), row, 0, 1, 2); row++; loginGridLayout->addWidget(m_savePasswordToPreferencesCheckBox, row, 0, 1, 2, Qt::AlignLeft); row++; CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); if (s_previousUsernamePassword.m_firstTime) { s_previousUsernamePassword.m_firstTime = false; AString userName; AString password; prefs->getRemoteFileUserNameAndPassword(userName, password); s_previousUsernamePassword.m_username = userName; if (prefs->isRemoteFilePasswordSaved()) { s_previousUsernamePassword.m_password = password; } else { s_previousUsernamePassword.m_password = ""; } } m_usernameLineEdit->setText(s_previousUsernamePassword.m_username); m_passwordLineEdit->setText(s_previousUsernamePassword.m_password); m_savePasswordToPreferencesCheckBox->setChecked(prefs->isRemoteFilePasswordSaved()); } /** * Destructor. */ UsernamePasswordWidget::~UsernamePasswordWidget() { } /** * Popup a modal dialog for the username and password. * * @param parent * Parent on which dialog is displayed. * @param title * Title of dialog. * @param message * Optional message shown in dialog. * @param usernameOut * The username. * @param passwordOut * The password. * @return * True if the user pressed the OK button, else false. */ bool UsernamePasswordWidget::getUserNameAndPasswordInDialog(QWidget* parent, const AString& title, const AString& message, AString& usernameOut, AString& passwordOut) { WuQDialogModal dialog(title, parent); dialog.setWindowTitle(title); QWidget* dialogWidget = new QWidget(); QVBoxLayout* dialogLayout = new QVBoxLayout(dialogWidget); if (message.isEmpty() == false) { QLabel* messageLabel = new QLabel(message); messageLabel->setWordWrap(true); dialogLayout->addWidget(messageLabel); } UsernamePasswordWidget* userNameAndPasswordWidget = new UsernamePasswordWidget(); dialogLayout->addWidget(userNameAndPasswordWidget); dialog.setCentralWidget(dialogWidget, WuQDialog::SCROLL_AREA_NEVER); if (dialog.exec() == WuQDialogModal::Accepted) { userNameAndPasswordWidget->getUsernameAndPassword(usernameOut, passwordOut); return true; } return false; } /** * Get the username and password. If the both the username and password * are valid (returns true) and "Save Password to Preferences" is checked, * the username and password are written to the user's preferences. If it is * not checked, only user username is written to preferences. * * @param usernameOut * The username. * @param passwordOut * The password. * @return * True if both the username and password are non-empty, else false. */ bool UsernamePasswordWidget::getUsernameAndPassword(AString& usernameOut, AString& passwordOut) { usernameOut = m_usernameLineEdit->text().trimmed(); passwordOut = m_passwordLineEdit->text().trimmed(); if (usernameOut.isEmpty()) { return false; } else if (passwordOut.isEmpty()) { return false; } const bool savePassword = m_savePasswordToPreferencesCheckBox->isChecked(); CaretPreferences* prefs = SessionManager::get()->getCaretPreferences(); prefs->setRemoteFilePasswordSaved(savePassword); if (savePassword) { prefs->setRemoteFileUserNameAndPassword(usernameOut, passwordOut); } else { prefs->setRemoteFileUserNameAndPassword(usernameOut, ""); } s_previousUsernamePassword.m_username = usernameOut; s_previousUsernamePassword.m_password = passwordOut; return true; } /** * Called when save password to preferences checkbox value is changed * by the user. * * @param status * New status of save password to preferences checkbox. */ void UsernamePasswordWidget::savePasswordToPreferencesClicked(bool status) { if (status) { const QString msg = ("The Workbench preferences are stored in a file somewhere in your " "home directory and the location depends upon your operating " "system. This is not a secure file and it may be possible " "other users to access this file and find your " "open location password. Unchceck the box if you do not want " "your password saved within your preferences."); WuQMessageBox::informationOk(m_savePasswordToPreferencesCheckBox, WuQtUtilities::createWordWrappedToolTipText(msg)); } } workbench-1.1.1/src/GuiQt/UsernamePasswordWidget.h000066400000000000000000000055001255417355300221450ustar00rootroot00000000000000#ifndef __USERNAME_PASSWORD_WIDGET_H__ #define __USERNAME_PASSWORD_WIDGET_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "AString.h" class QLineEdit; class QCheckBox; namespace caret { class UsernamePasswordWidget : public QWidget { Q_OBJECT public: UsernamePasswordWidget(QWidget* parent = 0); virtual ~UsernamePasswordWidget(); bool getUsernameAndPassword(AString& usernameOut, AString& passwordOut); static bool getUserNameAndPasswordInDialog(QWidget* parent, const AString& title, const AString& message, AString& usernameOut, AString& passwordOut); private: UsernamePasswordWidget(const UsernamePasswordWidget&); UsernamePasswordWidget& operator=(const UsernamePasswordWidget&); private slots: void savePasswordToPreferencesClicked(bool); public: // ADD_NEW_METHODS_HERE private: class PreviousUsernamePassword { public: PreviousUsernamePassword() { m_username = ""; m_password = ""; m_firstTime = true; } AString m_username; AString m_password; bool m_firstTime; }; // ADD_NEW_MEMBERS_HERE QLineEdit* m_usernameLineEdit; QLineEdit* m_passwordLineEdit; QCheckBox* m_savePasswordToPreferencesCheckBox; static PreviousUsernamePassword s_previousUsernamePassword; }; #ifdef __USERNAME_PASSWORD_WIDGET_DECLARE__ UsernamePasswordWidget::PreviousUsernamePassword UsernamePasswordWidget::s_previousUsernamePassword; #endif // __USERNAME_PASSWORD_WIDGET_DECLARE__ } // namespace #endif //__USERNAME_PASSWORD_WIDGET_H__ workbench-1.1.1/src/GuiQt/ViewModeEnum.cxx000066400000000000000000000140461255417355300204230ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __VIEW_MODE_DECLARE__ #include "ViewModeEnum.h" #undef __VIEW_MODE_DECLARE__ using namespace caret; /** * Constructor. * * @param e * An enumerated value. * @param name * Name of enumberated value. */ ViewModeEnum::ViewModeEnum(const Enum e, const int32_t integerCode, const QString& name, const QString& guiName) { this->e = e; this->integerCode = integerCode; this->name = name; this->guiName = guiName; } /** * Destructor. */ ViewModeEnum::~ViewModeEnum() { } /** * Initialize the enumerated metadata. */ void ViewModeEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(ViewModeEnum(VIEW_MODE_INVALID, 0, "VIEW_MODE_INVALID", "Invalid")); enumData.push_back(ViewModeEnum(VIEW_MODE_SURFACE, 1, "VIEW_MODE_SURFACE", "Surface")); enumData.push_back(ViewModeEnum(VIEW_MODE_VOLUME_SLICES, 2, "VIEW_MODE_VOLUME_SLICES", "Volume")); enumData.push_back(ViewModeEnum(VIEW_MODE_WHOLE_BRAIN, 3, "VIEW_MODE_WHOLE_BRAIN", "Whole Brain")); } /** * Find the data for and enumerated value. * @param e * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const ViewModeEnum* ViewModeEnum::findData(const Enum e) { initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const ViewModeEnum* d = &enumData[i]; if (d->e == e) { return d; } } return &enumData[0]; } /** * Get a string representation of the enumerated type. * @param e * Enumerated value. * @return * String representing enumerated value. */ QString ViewModeEnum::toName(Enum e) { initialize(); const ViewModeEnum* gaio = findData(e); return gaio->name; } /** * Get an enumerated value corresponding to its name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ ViewModeEnum::Enum ViewModeEnum::fromName(const QString& s, bool* isValidOut) { initialize(); bool validFlag = false; Enum e = VIEW_MODE_INVALID; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ViewModeEnum& d = *iter; if (d.name == s) { e = d.e; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } return e; } /** * Get a GUI string representation of the enumerated type. * @param e * Enumerated value. * @return * String representing enumerated value. */ QString ViewModeEnum::toGuiName(Enum e) { initialize(); const ViewModeEnum* gaio = findData(e); return gaio->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ ViewModeEnum::Enum ViewModeEnum::fromGuiName(const QString& s, bool* isValidOut) { initialize(); bool validFlag = false; Enum e = VIEW_MODE_INVALID; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ViewModeEnum& d = *iter; if (d.guiName == s) { e = d.e; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } return e; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t ViewModeEnum::toIntegerCode(Enum e) { initialize(); const ViewModeEnum* ndt = findData(e); return ndt->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ ViewModeEnum::Enum ViewModeEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { initialize(); bool validFlag = false; Enum e = VIEW_MODE_INVALID; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const ViewModeEnum& ndt = *iter; if (ndt.integerCode == integerCode) { e = ndt.e; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } return e; } workbench-1.1.1/src/GuiQt/ViewModeEnum.h000066400000000000000000000044151255417355300200470ustar00rootroot00000000000000#ifndef __VIEW_MODE_ENUM_H__ #define __VIEW_MODE_ENUM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include namespace caret { /** * View Mode Types. */ class ViewModeEnum { public: /** View Mode Types. */ enum Enum { /** Invalid */ VIEW_MODE_INVALID, /** View Surface */ VIEW_MODE_SURFACE, /** View Volume Slices */ VIEW_MODE_VOLUME_SLICES, /** View Whole Brain */ VIEW_MODE_WHOLE_BRAIN }; ~ViewModeEnum(); static QString toName(Enum e); static Enum fromName(const QString& s, bool* isValidOut); static QString toGuiName(Enum e); static Enum fromGuiName(const QString& s, bool* isValidOut); static int32_t toIntegerCode(Enum e); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); private: ViewModeEnum(const Enum e, const int32_t integerCode, const QString& name, const QString& guiName); static const ViewModeEnum* findData(const Enum e); static std::vector enumData; static void initialize(); static bool initializedFlag; Enum e; int32_t integerCode; QString name; QString guiName; }; #ifdef __VIEW_MODE_DECLARE__ std::vector ViewModeEnum::enumData; bool ViewModeEnum::initializedFlag = false; #endif // __VIEW_MODE_DECLARE__ } // namespace #endif // __VIEW_MODE_ENUM_H__ workbench-1.1.1/src/GuiQt/VolumeFileCreateDialog.cxx000066400000000000000000000572031255417355300223740ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __VOLUME_FILE_CREATE_DIALOG_DECLARE__ #include "VolumeFileCreateDialog.h" #undef __VOLUME_FILE_CREATE_DIALOG_DECLARE__ #include #include #include #include #include #include #include #include #include #include #include #include "Brain.h" #include "CaretAssert.h" #include "CaretFileDialog.h" #include "CiftiMappableDataFile.h" #include "CaretVolumeExtension.h" #include "GuiManager.h" #include "VolumeFile.h" #include "WuQDataEntryDialog.h" #include "WuQFactory.h" #include "WuQMessageBox.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::VolumeFileCreateDialog * \brief Dialog for creating volume file or adding map to volume file * \ingroup GuiQt */ /** * Constructor. */ VolumeFileCreateDialog::VolumeFileCreateDialog(QWidget* parent) : WuQDialogModal("Create Volume", parent) { if ( ! s_previousVolumeSettingsValid) { s_previousVolumeSettings.m_dimensions.clear(); s_previousVolumeSettings.m_dimensions.push_back(182); s_previousVolumeSettings.m_dimensions.push_back(218); s_previousVolumeSettings.m_dimensions.push_back(182); s_previousVolumeSettings.m_dimensions.push_back(1); std::vector row1; row1.push_back(-1); row1.push_back(0); row1.push_back(0); row1.push_back(90); std::vector row2; row2.push_back(0); row2.push_back(1); row2.push_back(0); row2.push_back(-126); std::vector row3; row3.push_back(0); row3.push_back(0); row3.push_back(1); row3.push_back(-72); std::vector row4; row4.push_back(0); row4.push_back(0); row4.push_back(0); row4.push_back(1); s_previousVolumeSettings.m_indexToSpace.clear(); s_previousVolumeSettings.m_indexToSpace.push_back(row1); s_previousVolumeSettings.m_indexToSpace.push_back(row2); s_previousVolumeSettings.m_indexToSpace.push_back(row3); s_previousVolumeSettings.m_indexToSpace.push_back(row4); s_previousVolumeSettings.m_volumeType = SubvolumeAttributes::FUNCTIONAL; s_previousVolumeSettingsValid = true; } m_volumeFile = NULL; QWidget* widget = new QWidget(); QVBoxLayout* layout = new QVBoxLayout(widget); layout->addWidget(createNewVolumeFileWidget()); setCentralWidget(widget, WuQDialog::SCROLL_AREA_NEVER); } /** * Destructor. */ VolumeFileCreateDialog::~VolumeFileCreateDialog() { } /** * @return Create and return the add map to volume file widget */ QWidget* VolumeFileCreateDialog::addMapToVolumeFileWidget() { QWidget* widget = new QWidget(); return widget; } /** * @return Create and return the create new volume file widget */ QWidget* VolumeFileCreateDialog::createNewVolumeFileWidget() { const AString defaultFileName = ("File_" + AString::number(s_fileNameCounter) + ".nii.gz"); s_fileNameCounter++; QLabel* newFileNameLabel = new QLabel("Name:"); m_newFileNameLineEdit = new QLineEdit(); m_newFileNameLineEdit->setText(defaultFileName); QPushButton* newFileNamePushButton = new QPushButton("Select..."); QObject::connect(newFileNamePushButton, SIGNAL(clicked()), this, SLOT(newFileNamePushButtonClicked())); QLabel* newFileTypeLabel = new QLabel("Type:"); m_newFileTypeComboBox = new QComboBox(); m_newFileTypeComboBox->addItem("Functional (Scalar)", SubvolumeAttributes::FUNCTIONAL); m_newFileTypeComboBox->addItem("Label", SubvolumeAttributes::LABEL); QLabel* newFileNumberOfMapsLabel = new QLabel("Maps:"); m_newFileNumberOfMapsSpinBox = WuQFactory::newSpinBoxWithMinMaxStep(1, s_maximumNumberOfMaps, 1); m_newFileNumberOfMapsSpinBox->setFixedWidth(50); QObject::connect(m_newFileNumberOfMapsSpinBox, SIGNAL(valueChanged(int)), this, SLOT(numberOfMapsSpinBoxValueChanged(int))); for (int32_t iMap = 0; iMap < s_maximumNumberOfMaps; iMap++) { const AString mapNumText("Map " + AString::number(iMap + 1) + " "); QLabel* label = new QLabel(mapNumText + "Name"); m_mapNameLabels.push_back(label); QLineEdit* lineEdit = new QLineEdit(); lineEdit->setText(mapNumText); m_mapNameLineEdits.push_back(lineEdit); } QGroupBox* fileGroupBox = new QGroupBox("File"); QGridLayout* nameTypeLayout = new QGridLayout(fileGroupBox); int nameTypeRow = 0; nameTypeLayout->setColumnStretch(0, 0); nameTypeLayout->setColumnStretch(1, 0); nameTypeLayout->setColumnStretch(2, 100); nameTypeLayout->setColumnStretch(3, 0); nameTypeLayout->addWidget(newFileNameLabel, nameTypeRow, 0); nameTypeLayout->addWidget(m_newFileNameLineEdit, nameTypeRow, 1, 1, 2); nameTypeLayout->addWidget(newFileNamePushButton, nameTypeRow, 4); nameTypeRow++; nameTypeLayout->addWidget(newFileTypeLabel, nameTypeRow, 0); nameTypeLayout->addWidget(m_newFileTypeComboBox, nameTypeRow, 1); nameTypeRow++; nameTypeLayout->addWidget(newFileNumberOfMapsLabel, nameTypeRow, 0); nameTypeLayout->addWidget(m_newFileNumberOfMapsSpinBox, nameTypeRow, 1, Qt::AlignLeft); nameTypeRow++; for (int32_t iMap = 0; iMap < s_maximumNumberOfMaps; iMap++) { CaretAssertVectorIndex(m_mapNameLabels, iMap); CaretAssertVectorIndex(m_mapNameLineEdits, iMap); nameTypeLayout->addWidget(m_mapNameLabels[iMap], nameTypeRow, 0); nameTypeLayout->addWidget(m_mapNameLineEdits[iMap], nameTypeRow, 1, 1, 2); nameTypeRow++; } const int SPIN_BOX_WIDTH = 80; QLabel* dimensionsLabel = new QLabel("Dimensions:"); m_newDimXSpinBox = WuQFactory::newSpinBoxWithMinMaxStep(1, 100000, 1); m_newDimYSpinBox = WuQFactory::newSpinBoxWithMinMaxStep(1, 100000, 1); m_newDimZSpinBox = WuQFactory::newSpinBoxWithMinMaxStep(1, 100000, 1); m_newDimXSpinBox->setFixedWidth(SPIN_BOX_WIDTH); m_newDimYSpinBox->setFixedWidth(SPIN_BOX_WIDTH); m_newDimZSpinBox->setFixedWidth(SPIN_BOX_WIDTH); const double bigDouble = 100000000.0; QLabel* originLabel = new QLabel("Origin:"); m_newOriginXSpinBox = WuQFactory::newDoubleSpinBoxWithMinMaxStepDecimals(-bigDouble, bigDouble, 1.0, 1); m_newOriginYSpinBox = WuQFactory::newDoubleSpinBoxWithMinMaxStepDecimals(-bigDouble, bigDouble, 1.0, 1); m_newOriginZSpinBox = WuQFactory::newDoubleSpinBoxWithMinMaxStepDecimals(-bigDouble, bigDouble, 1.0, 1); m_newOriginXSpinBox->setFixedWidth(SPIN_BOX_WIDTH); m_newOriginYSpinBox->setFixedWidth(SPIN_BOX_WIDTH); m_newOriginZSpinBox->setFixedWidth(SPIN_BOX_WIDTH); QLabel* spacingLabel = new QLabel("Spacing:"); m_newSpacingXSpinBox = WuQFactory::newDoubleSpinBoxWithMinMaxStepDecimals(-bigDouble, bigDouble, 1.0, 1); m_newSpacingYSpinBox = WuQFactory::newDoubleSpinBoxWithMinMaxStepDecimals(-bigDouble, bigDouble, 1.0, 1); m_newSpacingZSpinBox = WuQFactory::newDoubleSpinBoxWithMinMaxStepDecimals(-bigDouble, bigDouble, 1.0, 1); m_newSpacingXSpinBox->setValue(1.0); m_newSpacingYSpinBox->setValue(1.0); m_newSpacingZSpinBox->setValue(1.0); m_newSpacingXSpinBox->setFixedWidth(SPIN_BOX_WIDTH); m_newSpacingYSpinBox->setFixedWidth(SPIN_BOX_WIDTH); m_newSpacingZSpinBox->setFixedWidth(SPIN_BOX_WIDTH); QLabel* xLabel = new QLabel("X"); QLabel* yLabel = new QLabel("Y"); QLabel* zLabel = new QLabel("Z"); QLabel* loadFromLabel = new QLabel("Load From"); m_paramFromFilePushButton = new QPushButton("File..."); QObject::connect(m_paramFromFilePushButton, SIGNAL(clicked()), this, SLOT(loadVolumeParametersFromFilePushButtonClicked())); const int COL_LABEL = 0; const int COL_X = COL_LABEL + 1; const int COL_Y = COL_X + 1; const int COL_Z = COL_Y + 1; const int COL_LOAD = COL_Z + 1; QGroupBox* paramGroupBox = new QGroupBox("Voxels"); QGridLayout* paramsLayout = new QGridLayout(paramGroupBox); int paramsRow = 0; paramsLayout->addWidget(xLabel, paramsRow, COL_X, Qt::AlignHCenter); paramsLayout->addWidget(yLabel, paramsRow, COL_Y, Qt::AlignHCenter); paramsLayout->addWidget(zLabel, paramsRow, COL_Z, Qt::AlignHCenter); paramsLayout->addWidget(loadFromLabel, paramsRow, COL_LOAD, Qt::AlignHCenter); paramsRow++; paramsLayout->addWidget(dimensionsLabel, paramsRow, COL_LABEL); paramsLayout->addWidget(m_newDimXSpinBox, paramsRow, COL_X); paramsLayout->addWidget(m_newDimYSpinBox, paramsRow, COL_Y); paramsLayout->addWidget(m_newDimZSpinBox, paramsRow, COL_Z); paramsLayout->addWidget(m_paramFromFilePushButton, paramsRow, COL_LOAD); paramsRow++; paramsLayout->addWidget(originLabel, paramsRow, COL_LABEL); paramsLayout->addWidget(m_newOriginXSpinBox, paramsRow, COL_X); paramsLayout->addWidget(m_newOriginYSpinBox, paramsRow, COL_Y); paramsLayout->addWidget(m_newOriginZSpinBox, paramsRow, COL_Z); paramsRow++; paramsLayout->addWidget(spacingLabel, paramsRow, COL_LABEL); paramsLayout->addWidget(m_newSpacingXSpinBox, paramsRow, COL_X); paramsLayout->addWidget(m_newSpacingYSpinBox, paramsRow, COL_Y); paramsLayout->addWidget(m_newSpacingZSpinBox, paramsRow, COL_Z); paramsRow++; QWidget* widget = new QWidget(); QVBoxLayout* layout = new QVBoxLayout(widget); layout->addWidget(fileGroupBox); layout->addWidget(paramGroupBox); if (s_previousVolumeSettingsValid) { m_newDimXSpinBox->setValue(s_previousVolumeSettings.m_dimensions[0]); m_newDimYSpinBox->setValue(s_previousVolumeSettings.m_dimensions[1]); m_newDimZSpinBox->setValue(s_previousVolumeSettings.m_dimensions[2]); m_newFileNumberOfMapsSpinBox->setValue(s_previousVolumeSettings.m_dimensions[3]); const int typeIndex = m_newFileTypeComboBox->findData(static_cast(s_previousVolumeSettings.m_volumeType)); if (typeIndex >= 0) { m_newFileTypeComboBox->setCurrentIndex(typeIndex); } std::vector > m = s_previousVolumeSettings.m_indexToSpace; m_newSpacingXSpinBox->setValue(m[0][0]); m_newSpacingYSpinBox->setValue(m[1][1]); m_newSpacingZSpinBox->setValue(m[2][2]); m_newOriginXSpinBox->setValue(m[0][3]); m_newOriginYSpinBox->setValue(m[1][3]); m_newOriginZSpinBox->setValue(m[2][3]); } m_newFileNameLineEdit->selectAll(); numberOfMapsSpinBoxValueChanged(m_newFileNumberOfMapsSpinBox->value()); return widget; } /** * @return Volume file that was created (NULL if error). */ VolumeFile* VolumeFileCreateDialog::getVolumeFile() { return m_volumeFile; } /** * Called to create a new file name via the file selection dialog. */ void VolumeFileCreateDialog::newFileNamePushButtonClicked() { const AString filename = CaretFileDialog::getChooseFileNameDialog(DataFileTypeEnum::VOLUME, ""); if ( ! filename.isEmpty()) { m_newFileNameLineEdit->setText(filename); } } /** * Called when the number of maps is changed. * * @param value * New value for number of maps. */ void VolumeFileCreateDialog::numberOfMapsSpinBoxValueChanged(int value) { for (int32_t iMap = 0; iMap < s_maximumNumberOfMaps; iMap++) { CaretAssertVectorIndex(m_mapNameLabels, iMap); CaretAssertVectorIndex(m_mapNameLineEdits, iMap); const bool enabledFlag = (iMap < value); m_mapNameLabels[iMap]->setEnabled(enabledFlag); m_mapNameLineEdits[iMap]->setEnabled(enabledFlag); } } /** * Gets called when the ok button is clicked. */ void VolumeFileCreateDialog::okButtonClicked() { AString filename = m_newFileNameLineEdit->text().trimmed(); if (filename.isEmpty()) { WuQMessageBox::errorOk(this, "Filename is empty."); return; } if (! DataFileTypeEnum::isValidFileExtension(filename, DataFileTypeEnum::VOLUME)) { AString validExtensions; const std::vector allExts = DataFileTypeEnum::getAllFileExtensions(DataFileTypeEnum::VOLUME); const int32_t numExts = static_cast(allExts.size()); WuQDataEntryDialog ded("Invalid Volume File Extension", m_newFileNameLineEdit); ded.setTextAtTop("Filename extension is invalid. Choose one " "of the extensions below and press OK. Otherwise, " "press Cancel to change the name of the file.", true); std::vector extButtons; for (int32_t i = 0; i < numExts; i++) { QRadioButton* rb = ded.addRadioButton("." + allExts[i]); if (allExts[i] == "nii.gz") { rb->setChecked(true); } extButtons.push_back(rb); } bool extValid = false; if (ded.exec() == WuQDataEntryDialog::Accepted) { for (int32_t i = 0; i < numExts; i++) { QRadioButton* rb = extButtons[i]; if (rb->isChecked()) { filename += rb->text(); extValid = true; break; } } } if ( ! extValid) { return; } } const int typeIndex = m_newFileTypeComboBox->currentIndex(); const SubvolumeAttributes::VolumeType volumeType = static_cast(m_newFileTypeComboBox->itemData(typeIndex).toInt()); const int32_t numMaps = m_newFileNumberOfMapsSpinBox->value(); std::vector dimensions; dimensions.push_back(m_newDimXSpinBox->value()); dimensions.push_back(m_newDimYSpinBox->value()); dimensions.push_back(m_newDimZSpinBox->value()); dimensions.push_back(numMaps); const float xOrigin = m_newOriginXSpinBox->value(); const float yOrigin = m_newOriginYSpinBox->value(); const float zOrigin = m_newOriginZSpinBox->value(); const float xSpacing = m_newSpacingXSpinBox->value(); const float ySpacing = m_newSpacingYSpinBox->value(); const float zSpacing = m_newSpacingZSpinBox->value(); if ((xSpacing == 0.0) || (ySpacing == 0.0) || (zSpacing == 0.0)) { WuQMessageBox::errorOk(this, "Spacing values must be non-zero."); return; } std::vector rowOne(4, 0.0); rowOne[0] = xSpacing; rowOne[3] = xOrigin; std::vector rowTwo(4, 0.0); rowTwo[1] = ySpacing; rowTwo[3] = yOrigin; std::vector rowThree(4, 0.0); rowThree[2] = zSpacing; rowThree[3] = zOrigin; std::vector rowFour(4, 0.0); rowFour[3] = 1.0; std::vector > indexToSpace; indexToSpace.push_back(rowOne); indexToSpace.push_back(rowTwo); indexToSpace.push_back(rowThree); indexToSpace.push_back(rowFour); m_volumeFile = new VolumeFile(dimensions, indexToSpace, 1, volumeType); m_volumeFile->setFileName(filename); m_volumeFile->setType(volumeType); s_previousVolumeSettings.m_dimensions = dimensions; s_previousVolumeSettings.m_indexToSpace = indexToSpace; s_previousVolumeSettings.m_volumeType = volumeType; s_previousVolumeSettingsValid = true; float defaultValue = 0.0; if (numMaps > 0) { switch (volumeType) { case SubvolumeAttributes::ANATOMY: break; case SubvolumeAttributes::FUNCTIONAL: break; case SubvolumeAttributes::LABEL: defaultValue = m_volumeFile->getMapLabelTable(0)->getUnassignedLabelKey(); break; case SubvolumeAttributes::RGB: CaretAssert(0); break; case SubvolumeAttributes::SEGMENTATION: break; case SubvolumeAttributes::UNKNOWN: CaretAssert(0); break; case SubvolumeAttributes::VECTOR: CaretAssert(0); break; } } m_volumeFile->setValueAllVoxels(defaultValue); m_volumeFile->updateScalarColoringForAllMaps(GuiManager::get()->getBrain()->getPaletteFile()); for (int32_t iMap = 0; iMap < numMaps; iMap++) { CaretAssertVectorIndex(m_mapNameLineEdits, iMap); m_volumeFile->setMapName(iMap, m_mapNameLineEdits[iMap]->text().trimmed()); } WuQDialog::okButtonClicked(); } /** * Set the volume parameters from a loaded file. */ void VolumeFileCreateDialog::loadVolumeParametersFromFilePushButtonClicked() { Brain* brain = GuiManager::get()->getBrain(); std::vector allMappableFiles; brain->getAllMappableDataFiles(allMappableFiles); std::vector volumeMappableFiles; for (std::vector::iterator allIter = allMappableFiles.begin(); allIter != allMappableFiles.end(); allIter++) { CaretMappableDataFile* mapFile = *allIter; if (mapFile->isVolumeMappable()) { volumeMappableFiles.push_back(mapFile); } } const int32_t numberOfVolumeMappableFiles = static_cast(volumeMappableFiles.size()); if (numberOfVolumeMappableFiles <= 0) { WuQMessageBox::errorOk(m_paramFromFilePushButton, "No volume mappable files are loaded."); } std::vector volumeRadioButtons; WuQDataEntryDialog dialog("Select File For Volume Parameters", m_paramFromFilePushButton); for (int32_t i = 0; i < numberOfVolumeMappableFiles; i++) { CaretAssertVectorIndex(volumeMappableFiles, i); QRadioButton* rb = dialog.addRadioButton(volumeMappableFiles[i]->getFileNameNoPath()); volumeRadioButtons.push_back(rb); if (i == 0) { rb->setChecked(true); } } if (dialog.exec() == WuQDataEntryDialog::Accepted) { for (int32_t i = 0; i < numberOfVolumeMappableFiles; i++) { CaretAssertVectorIndex(volumeRadioButtons, i); if (volumeRadioButtons[i]->isChecked()) { CaretAssertVectorIndex(volumeMappableFiles, i); CaretMappableDataFile* volMapFile = volumeMappableFiles[i]; CiftiMappableDataFile* ciftiFile = dynamic_cast(volMapFile); VolumeFile* volumeFile = dynamic_cast(volMapFile); int64_t dimensions[5] = { 0, 0, 0, 0, 0 }; float origin[3] = { 0.0, 0.0, 0.0 }; float spacing[3] = { 0.0, 0.0, 0.0 }; int32_t numberOfMaps = 1; SubvolumeAttributes::VolumeType volumeType = SubvolumeAttributes::FUNCTIONAL; if (ciftiFile != NULL) { int64_t dimI, dimJ, dimK, dimTime, dimComponents; ciftiFile->getDimensions(dimI, dimJ, dimK, dimTime, dimComponents); dimensions[0] = dimI; dimensions[1] = dimJ; dimensions[2] = dimK; ciftiFile->indexToSpace(0, 0, 0, origin); float oneOneOneVoxelXYZ[3]; ciftiFile->indexToSpace(1, 1, 1, oneOneOneVoxelXYZ); spacing[0] = oneOneOneVoxelXYZ[0] - origin[0]; spacing[1] = oneOneOneVoxelXYZ[1] - origin[1]; spacing[2] = oneOneOneVoxelXYZ[2] - origin[2]; numberOfMaps = volMapFile->getNumberOfMaps(); if (volMapFile->isMappedWithLabelTable()) { volumeType = SubvolumeAttributes::LABEL; } else if (volMapFile->isMappedWithPalette()) { volumeType = SubvolumeAttributes::FUNCTIONAL; } } else if (volumeFile != NULL) { const VolumeSpace volumeSpace = volumeFile->getVolumeSpace(); const int64_t* dimArray = volumeSpace.getDims(); dimensions[0] = dimArray[0]; dimensions[1] = dimArray[1]; dimensions[2] = dimArray[2]; VolumeSpace::OrientTypes orientation[3]; volumeSpace.getOrientAndSpacingForPlumb(orientation, spacing, origin); numberOfMaps = volumeFile->getNumberOfMaps(); volumeType = volumeFile->getType(); } else { AString msg = ("Program Error: " + volMapFile->getFileNameNoPath() + " is volume mappable but neither a volume nor a CIFTI file."); WuQMessageBox::errorOk(m_paramFromFilePushButton, msg); return; } const bool includeTypeAndNumberOfMapsFlag = false; if (includeTypeAndNumberOfMapsFlag) { const int typeIndex = m_newFileTypeComboBox->findData(static_cast(volumeType)); if (typeIndex >= 0) { m_newFileTypeComboBox->setCurrentIndex(typeIndex); } m_newFileNumberOfMapsSpinBox->setValue(numberOfMaps); } m_newDimXSpinBox->setValue(dimensions[0]); m_newDimYSpinBox->setValue(dimensions[1]); m_newDimZSpinBox->setValue(dimensions[2]); m_newOriginXSpinBox->setValue(origin[0]); m_newOriginYSpinBox->setValue(origin[1]); m_newOriginZSpinBox->setValue(origin[2]); m_newSpacingXSpinBox->setValue(spacing[0]); m_newSpacingYSpinBox->setValue(spacing[1]); m_newSpacingZSpinBox->setValue(spacing[2]); break; } } } } workbench-1.1.1/src/GuiQt/VolumeFileCreateDialog.h000066400000000000000000000071521255417355300220170ustar00rootroot00000000000000#ifndef __VOLUME_FILE_CREATE_DIALOG_H__ #define __VOLUME_FILE_CREATE_DIALOG_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "VolumeFile.h" #include "WuQDialogModal.h" class QComboBox; class QDoubleSpinBox; class QLabel; class QLineEdit; class QPushButton; class QSpinBox; namespace caret { class VolumeFileCreateDialog : public WuQDialogModal { Q_OBJECT public: VolumeFileCreateDialog(QWidget* parent); virtual ~VolumeFileCreateDialog(); VolumeFile* getVolumeFile(); // ADD_NEW_METHODS_HERE private slots: void newFileNamePushButtonClicked(); void loadVolumeParametersFromFilePushButtonClicked(); void numberOfMapsSpinBoxValueChanged(int); protected: virtual void okButtonClicked(); private: VolumeFileCreateDialog(const VolumeFileCreateDialog&); VolumeFileCreateDialog& operator=(const VolumeFileCreateDialog&); struct PreviousVolumeSettings { std::vector m_dimensions; std::vector > m_indexToSpace; SubvolumeAttributes::VolumeType m_volumeType; }; QWidget* createNewVolumeFileWidget(); QWidget* addMapToVolumeFileWidget(); QLineEdit* m_newFileNameLineEdit; QComboBox* m_newFileTypeComboBox; QSpinBox* m_newFileNumberOfMapsSpinBox; std::vector m_mapNameLabels; std::vector m_mapNameLineEdits; QSpinBox* m_newDimXSpinBox; QSpinBox* m_newDimYSpinBox; QSpinBox* m_newDimZSpinBox; QDoubleSpinBox* m_newSpacingXSpinBox; QDoubleSpinBox* m_newSpacingYSpinBox; QDoubleSpinBox* m_newSpacingZSpinBox; QDoubleSpinBox* m_newOriginXSpinBox; QDoubleSpinBox* m_newOriginYSpinBox; QDoubleSpinBox* m_newOriginZSpinBox; QPushButton* m_paramFromFilePushButton; VolumeFile* m_volumeFile; static int32_t s_maximumNumberOfMaps; static int32_t s_fileNameCounter; static PreviousVolumeSettings s_previousVolumeSettings; static bool s_previousVolumeSettingsValid; // ADD_NEW_MEMBERS_HERE }; #ifdef __VOLUME_FILE_CREATE_DIALOG_DECLARE__ int32_t VolumeFileCreateDialog::s_maximumNumberOfMaps = 5; int32_t VolumeFileCreateDialog::s_fileNameCounter = 1; VolumeFileCreateDialog::PreviousVolumeSettings VolumeFileCreateDialog::s_previousVolumeSettings; bool VolumeFileCreateDialog::s_previousVolumeSettingsValid = false; #endif // __VOLUME_FILE_CREATE_DIALOG_DECLARE__ } // namespace #endif //__VOLUME_FILE_CREATE_DIALOG_H__ workbench-1.1.1/src/GuiQt/VolumeSurfaceOutlineColorOrTabViewController.cxx000066400000000000000000000070711255417355300270250ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __VOLUME_SURFACE_OUTLINE_COLOR_OR_TAB_VIEW_CONTROLLER_DECLARE__ #include "VolumeSurfaceOutlineColorOrTabViewController.h" #undef __VOLUME_SURFACE_OUTLINE_COLOR_OR_TAB_VIEW_CONTROLLER_DECLARE__ #include #include "WuQFactory.h" using namespace caret; /** * \class caret::VolumeSurfaceOutlineColorOrTabViewController * \brief View Controller for VolumeSurfaceOutlineColorOrTabModel * */ /** * Constructor. */ VolumeSurfaceOutlineColorOrTabViewController::VolumeSurfaceOutlineColorOrTabViewController(QObject* parent) : WuQWidget(parent) { this->colorOrTabModel = NULL; this->modelComboBox = WuQFactory::newComboBox(); QObject::connect(this->modelComboBox, SIGNAL(activated(int)), this, SLOT(itemActivated(int))); } /** * Destructor. */ VolumeSurfaceOutlineColorOrTabViewController::~VolumeSurfaceOutlineColorOrTabViewController() { } /** * Update this view controller. */ void VolumeSurfaceOutlineColorOrTabViewController::updateViewController(VolumeSurfaceOutlineColorOrTabModel* model) { this->colorOrTabModel = model; this->modelComboBox->blockSignals(true); this->modelComboBox->clear(); VolumeSurfaceOutlineColorOrTabModel::Item* selectedItem = this->colorOrTabModel->getSelectedItem(); int32_t selectedItemIndex = -1; std::vector validItems = this->colorOrTabModel->getValidItems(); const int32_t numItems = static_cast(validItems.size()); for (int32_t i = 0; i < numItems; i++) { VolumeSurfaceOutlineColorOrTabModel::Item* item = validItems[i]; if (selectedItem == item) { selectedItemIndex = i; } this->modelComboBox->addItem(item->getName(), qVariantFromValue((void*)item)); } if (selectedItemIndex >= 0) { this->modelComboBox->setCurrentIndex(selectedItemIndex); } this->modelComboBox->blockSignals(false); } /** * @return The widget for this view controller. */ QWidget* VolumeSurfaceOutlineColorOrTabViewController::getWidget() { return this->modelComboBox; } /** * Called when the user selects an item. * @param indx * Index of item selected by the user. */ void VolumeSurfaceOutlineColorOrTabViewController::itemActivated(int indx) { if (this->colorOrTabModel == NULL) { return; } if (indx >= 0) { void* pointer = this->modelComboBox->itemData(indx).value(); VolumeSurfaceOutlineColorOrTabModel::Item* item = (VolumeSurfaceOutlineColorOrTabModel::Item*)pointer; this->colorOrTabModel->setSelectedItem(item); emit modelSelected(item); } } workbench-1.1.1/src/GuiQt/VolumeSurfaceOutlineColorOrTabViewController.h000066400000000000000000000046261255417355300264550ustar00rootroot00000000000000#ifndef __VOLUME_SURFACE_OUTLINE_COLOR_OR_TAB_VIEW_CONTROLLER__H_ #define __VOLUME_SURFACE_OUTLINE_COLOR_OR_TAB_VIEW_CONTROLLER__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "VolumeSurfaceOutlineColorOrTabModel.h" #include "WuQWidget.h" class QComboBox; namespace caret { class VolumeSurfaceOutlineColorOrTabModel; class VolumeSurfaceOutlineColorOrTabViewController : public WuQWidget { Q_OBJECT public: VolumeSurfaceOutlineColorOrTabViewController(QObject* parent); virtual ~VolumeSurfaceOutlineColorOrTabViewController(); QWidget* getWidget(); void updateViewController(VolumeSurfaceOutlineColorOrTabModel* model); signals: /** * Emitted when the user selects a model. * @param pointer to model selected. */ void modelSelected(VolumeSurfaceOutlineColorOrTabModel::Item*); private slots: void itemActivated(int indx); private: VolumeSurfaceOutlineColorOrTabViewController(const VolumeSurfaceOutlineColorOrTabViewController&); VolumeSurfaceOutlineColorOrTabViewController& operator=(const VolumeSurfaceOutlineColorOrTabViewController&); QComboBox* modelComboBox; VolumeSurfaceOutlineColorOrTabModel* colorOrTabModel; }; #ifdef __VOLUME_SURFACE_OUTLINE_COLOR_OR_TAB_VIEW_CONTROLLER_DECLARE__ // #endif // __VOLUME_SURFACE_OUTLINE_COLOR_OR_TAB_VIEW_CONTROLLER_DECLARE__ } // namespace #endif //__VOLUME_SURFACE_OUTLINE_COLOR_OR_TAB_VIEW_CONTROLLER__H_ workbench-1.1.1/src/GuiQt/VolumeSurfaceOutlineSetViewController.cxx000066400000000000000000000170121255417355300255460ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #define __VOLUME_SURFACE_OUTLINE_SET_VIEW_CONTROLLER_DECLARE__ #include "VolumeSurfaceOutlineSetViewController.h" #undef __VOLUME_SURFACE_OUTLINE_SET_VIEW_CONTROLLER_DECLARE__ #include "BrowserTabContent.h" #include "CaretAssert.h" #include "EventManager.h" #include "EventUserInterfaceUpdate.h" #include "GuiManager.h" #include "VolumeSurfaceOutlineSetModel.h" #include "VolumeSurfaceOutlineViewController.h" #include "WuQFactory.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::VolumeSurfaceOutlineSetViewController * \brief View Controller for VolumeSurfaceOutlineSetModel */ /** * Constructor. * * @param orientation * Orientation for layout * @param browserWindowIndex * Index of browser window that contains this view controller. * @param parent * Parent widget. */ VolumeSurfaceOutlineSetViewController::VolumeSurfaceOutlineSetViewController(const Qt::Orientation orientation, const int32_t browserWindowIndex, QWidget* parent) : QWidget(parent) { this->browserWindowIndex = browserWindowIndex; QWidget* gridWidget = new QWidget(); QGridLayout* gridLayout = new QGridLayout(gridWidget); WuQtUtilities::setLayoutSpacingAndMargins(gridLayout, 4, 2); if (orientation == Qt::Horizontal) { gridLayout->setColumnStretch(0, 0); gridLayout->setColumnStretch(1, 0); gridLayout->setColumnStretch(2, 0); gridLayout->setColumnStretch(3, 100); QLabel* onLabel = new QLabel("On"); QLabel* colorLabel = new QLabel("Color Source"); QLabel* thicknessLabel = new QLabel("Thickness"); QLabel* fileLabel = new QLabel("File"); const int row = gridLayout->rowCount(); gridLayout->addWidget(onLabel, row, 0, Qt::AlignHCenter); gridLayout->addWidget(colorLabel, row, 1, Qt::AlignHCenter); gridLayout->addWidget(thicknessLabel, row, 2, Qt::AlignHCenter); gridLayout->addWidget(fileLabel, row, 3, Qt::AlignHCenter); } else { gridLayout->setColumnStretch(0, 0); gridLayout->setColumnStretch(1, 0); gridLayout->setColumnStretch(2, 100); } for (int32_t i = 0; i < BrainConstants::MAXIMUM_NUMBER_OF_VOLUME_SURFACE_OUTLINES; i++) { VolumeSurfaceOutlineViewController* ovc = new VolumeSurfaceOutlineViewController(orientation, gridLayout); this->outlineViewControllers.push_back(ovc); } QLabel* outlineCountLabel = new QLabel("Number of Outlines: "); this->outlineCountSpinBox = WuQFactory::newSpinBox(); this->outlineCountSpinBox->setRange(BrainConstants::MINIMUM_NUMBER_OF_VOLUME_SURFACE_OUTLINES, BrainConstants::MAXIMUM_NUMBER_OF_VOLUME_SURFACE_OUTLINES); this->outlineCountSpinBox->setSingleStep(1); QObject::connect(this->outlineCountSpinBox, SIGNAL(valueChanged(int)), this, SLOT(outlineCountSpinBoxValueChanged(int))); QHBoxLayout* overlayCountLayout = new QHBoxLayout(); overlayCountLayout->addWidget(outlineCountLabel); overlayCountLayout->addWidget(this->outlineCountSpinBox); overlayCountLayout->addStretch(); QVBoxLayout* layout = new QVBoxLayout(this); WuQtUtilities::setLayoutSpacingAndMargins(layout, 2, 2); layout->addWidget(gridWidget); layout->addLayout(overlayCountLayout); layout->addStretch(); EventManager::get()->addEventListener(this, EventTypeEnum::EVENT_USER_INTERFACE_UPDATE); } /** * Destructor. */ VolumeSurfaceOutlineSetViewController::~VolumeSurfaceOutlineSetViewController() { EventManager::get()->removeAllEventsFromListener(this); for (size_t i = 0; i < outlineViewControllers.size(); ++i) { delete outlineViewControllers[i]; } } /** * Called when outline count spin box value changed. * @param value * New value. */ void VolumeSurfaceOutlineSetViewController::outlineCountSpinBoxValueChanged(int value) { VolumeSurfaceOutlineSetModel* outlineSet = this->getOutlineSet(); if (outlineSet != NULL) { outlineSet->setNumberOfDisplayedVolumeSurfaceOutlines(value); this->updateViewController(); } } /** * @return The outline set in this view controller. */ VolumeSurfaceOutlineSetModel* VolumeSurfaceOutlineSetViewController::getOutlineSet() { VolumeSurfaceOutlineSetModel* outlineSet = NULL; BrowserTabContent* browserTabContent = GuiManager::get()->getBrowserTabContentForBrowserWindow(this->browserWindowIndex, true); if (browserTabContent != NULL) { outlineSet = browserTabContent->getVolumeSurfaceOutlineSet(); } return outlineSet; } /** * Update this overlay set view controller using the given overlay set. */ void VolumeSurfaceOutlineSetViewController::updateViewController() { VolumeSurfaceOutlineSetModel* outlineSet = this->getOutlineSet(); if (outlineSet == NULL) { return; } const int32_t numberOfOutlines = static_cast(this->outlineViewControllers.size()); const int32_t numberOfDisplayedOutline = outlineSet->getNumberOfDislayedVolumeSurfaceOutlines(); this->outlineCountSpinBox->blockSignals(true); this->outlineCountSpinBox->setValue(numberOfDisplayedOutline); this->outlineCountSpinBox->blockSignals(false); for (int32_t i = 0; i < numberOfOutlines; i++) { VolumeSurfaceOutlineModel* outlineModel = NULL; if (outlineSet != NULL) { outlineModel = outlineSet->getVolumeSurfaceOutlineModel(i); } this->outlineViewControllers[i]->updateViewController(outlineModel); bool displayOutline = (outlineModel != NULL); if (i >= numberOfDisplayedOutline) { displayOutline = false; } this->outlineViewControllers[i]->setVisible(displayOutline); } } /** * Receive events from the event manager. * * @param event * Event sent by event manager. */ void VolumeSurfaceOutlineSetViewController::receiveEvent(Event* event) { if (event->getEventType() == EventTypeEnum::EVENT_USER_INTERFACE_UPDATE) { EventUserInterfaceUpdate* uiEvent = dynamic_cast(event); CaretAssert(uiEvent); if (uiEvent->isUpdateForWindow(this->browserWindowIndex)) { if (uiEvent->isSurfaceUpdate() || uiEvent->isToolBoxUpdate()) { this->updateViewController(); uiEvent->setEventProcessed(); } } } } workbench-1.1.1/src/GuiQt/VolumeSurfaceOutlineSetViewController.h000066400000000000000000000047051255417355300252000ustar00rootroot00000000000000#ifndef __VOLUME_SURFACE_OUTLINE_SET_VIEW_CONTROLLER__H_ #define __VOLUME_SURFACE_OUTLINE_SET_VIEW_CONTROLLER__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include "EventListenerInterface.h" class QSpinBox; namespace caret { class VolumeSurfaceOutlineSetModel; class VolumeSurfaceOutlineViewController; class VolumeSurfaceOutlineSetViewController : public QWidget, public EventListenerInterface { Q_OBJECT public: VolumeSurfaceOutlineSetViewController(const Qt::Orientation orientation, const int32_t browserWindowIndex, QWidget* parent = 0); virtual ~VolumeSurfaceOutlineSetViewController(); void receiveEvent(Event* event); private slots: void outlineCountSpinBoxValueChanged(int); private: VolumeSurfaceOutlineSetViewController(const VolumeSurfaceOutlineSetViewController&); VolumeSurfaceOutlineSetViewController& operator=(const VolumeSurfaceOutlineSetViewController&); VolumeSurfaceOutlineSetModel* getOutlineSet(); void updateViewController(); QSpinBox* outlineCountSpinBox; int32_t browserWindowIndex; std::vector outlineViewControllers; }; #ifdef __VOLUME_SURFACE_OUTLINE_SET_VIEW_CONTROLLER_DECLARE__ // #endif // __VOLUME_SURFACE_OUTLINE_SET_VIEW_CONTROLLER_DECLARE__ } // namespace #endif //__VOLUME_SURFACE_OUTLINE_SET_VIEW_CONTROLLER__H_ workbench-1.1.1/src/GuiQt/VolumeSurfaceOutlineViewController.cxx000066400000000000000000000177101255417355300250770ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #include #include #define __VOLUME_SURFACE_OUTLINE_VIEW_CONTROLLER_DECLARE__ #include "VolumeSurfaceOutlineViewController.h" #undef __VOLUME_SURFACE_OUTLINE_VIEW_CONTROLLER_DECLARE__ #include "EventGraphicsUpdateAllWindows.h" #include "EventManager.h" #include "SurfaceSelectionModel.h" #include "SurfaceSelectionViewController.h" #include "VolumeSurfaceOutlineColorOrTabViewController.h" #include "VolumeSurfaceOutlineModel.h" #include "WuQFactory.h" #include "WuQGridLayoutGroup.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::VolumeSurfaceOutlineViewController * \brief View controller for volume surface outline * */ /** * Constructor. */ VolumeSurfaceOutlineViewController::VolumeSurfaceOutlineViewController(const Qt::Orientation orientation, QGridLayout* gridLayout, QObject* parent) : QObject(parent) { this->outlineModel = NULL; this->enabledCheckBox = new QCheckBox(" "); QObject::connect(this->enabledCheckBox, SIGNAL(stateChanged(int)), this, SLOT(enabledCheckBoxStateChanged(int))); this->enabledCheckBox->setToolTip("Enables display of this volume surface outline"); this->surfaceSelectionViewController = new SurfaceSelectionViewController(this); QObject::connect(this->surfaceSelectionViewController, SIGNAL(surfaceSelected(Surface*)), this, SLOT(surfaceSelected(Surface*))); this->surfaceSelectionViewController->getWidget()->setToolTip("Select surface drawn as outline over volume slices"); this->colorOrTabSelectionControl = new VolumeSurfaceOutlineColorOrTabViewController(this); QObject::connect(this->colorOrTabSelectionControl, SIGNAL(modelSelected(VolumeSurfaceOutlineColorOrTabModel::Item*)), this, SLOT(colorTabSelected(VolumeSurfaceOutlineColorOrTabModel::Item*))); this->colorOrTabSelectionControl->getWidget()->setToolTip("Select coloring for surface outline.\n" "If tab, coloring assigned to selected surface\n" "in the selected tab is used.\n"); const float minLineWidth = 0.1; const float maxLineWidth = 100.0; const float stepSize = 0.5; this->thicknessSpinBox = WuQFactory::newDoubleSpinBox(); this->thicknessSpinBox->setRange(minLineWidth, maxLineWidth); this->thicknessSpinBox->setSingleStep(stepSize); this->thicknessSpinBox->setFixedWidth(100); QObject::connect(this->thicknessSpinBox, SIGNAL(valueChanged(double)), this, SLOT(thicknessSpinBoxValueChanged(double))); this->thicknessSpinBox->setToolTip("Thickness of surface outline"); if (orientation == Qt::Horizontal) { this->gridLayoutGroup = new WuQGridLayoutGroup(gridLayout, this); int row = this->gridLayoutGroup->rowCount(); this->gridLayoutGroup->addWidget(this->enabledCheckBox, row, 0); this->gridLayoutGroup->addWidget(this->colorOrTabSelectionControl->getWidget(), row, 1); this->gridLayoutGroup->addWidget(this->thicknessSpinBox, row, 2); this->gridLayoutGroup->addWidget(this->surfaceSelectionViewController->getWidget(), row, 3); } else { QFrame* bottomHorizontalLineWidget = new QFrame(); bottomHorizontalLineWidget->setLineWidth(0); bottomHorizontalLineWidget->setMidLineWidth(1); bottomHorizontalLineWidget->setFrameStyle(QFrame::HLine | QFrame::Raised); this->gridLayoutGroup = new WuQGridLayoutGroup(gridLayout, this); int row = this->gridLayoutGroup->rowCount(); this->gridLayoutGroup->addWidget(this->enabledCheckBox, row, 0, 2, 1, Qt::AlignCenter); this->gridLayoutGroup->addWidget(this->surfaceSelectionViewController->getWidget(), row, 1, 1, 2); row++; this->gridLayoutGroup->addWidget(this->colorOrTabSelectionControl->getWidget(), row, 1); this->gridLayoutGroup->addWidget(this->thicknessSpinBox, row, 2, Qt::AlignLeft); row++; this->gridLayoutGroup->addWidget(bottomHorizontalLineWidget, row, 0, 1, -1); } } /** * Destructor. */ VolumeSurfaceOutlineViewController::~VolumeSurfaceOutlineViewController() { } /** * Set the visibility of widgets in this view controller. */ void VolumeSurfaceOutlineViewController::setVisible(bool visible) { this->gridLayoutGroup->setVisible(visible); } /** * Called when a surface is selected. * @param surface * Surface that was selected. */ void VolumeSurfaceOutlineViewController::surfaceSelected(Surface* surface) { if (this->outlineModel != NULL) { this->outlineModel->getSurfaceSelectionModel()->setSurface(surface); } this->updateGraphics(); } /** * Called when a color/tab is selected. * @param colorTab * Value that was selected. */ void VolumeSurfaceOutlineViewController::colorTabSelected(VolumeSurfaceOutlineColorOrTabModel::Item* /*colorTab*/) { this->updateGraphics(); } /** * Called when enabled checkbox is selected. * @param state * New state of checkbox. */ void VolumeSurfaceOutlineViewController::enabledCheckBoxStateChanged(int state) { if (this->outlineModel != NULL) { const bool selected = (state == Qt::Checked); this->outlineModel->setDisplayed(selected); } this->updateGraphics(); } /** * Called when thickness value is changed. * @param value * Value that was selected. */ void VolumeSurfaceOutlineViewController::thicknessSpinBoxValueChanged(double value) { if (this->outlineModel != NULL) { this->outlineModel->setThickness(value); } this->updateGraphics(); } /** * Update this view controller. * @param outlineModel * Outline model for use in this view controller. */ void VolumeSurfaceOutlineViewController::updateViewController(VolumeSurfaceOutlineModel* outlineModel) { this->outlineModel = outlineModel; if (this->outlineModel != NULL) { Qt::CheckState state = Qt::Unchecked; if (this->outlineModel->isDisplayed()) { state = Qt::Checked; } this->enabledCheckBox->setCheckState(state); this->thicknessSpinBox->blockSignals(true); this->thicknessSpinBox->setValue(outlineModel->getThickness()); this->thicknessSpinBox->blockSignals(false); this->surfaceSelectionViewController->updateControl(outlineModel->getSurfaceSelectionModel()); //this->surfaceSelectionViewController->setSurface(outlineModel->getSurface()); this->colorOrTabSelectionControl->updateViewController(outlineModel->getColorOrTabModel()); } } /** * Update the graphics. */ void VolumeSurfaceOutlineViewController::updateGraphics() { EventManager::get()->sendEvent(EventGraphicsUpdateAllWindows().getPointer()); } workbench-1.1.1/src/GuiQt/VolumeSurfaceOutlineViewController.h000066400000000000000000000055651255417355300245310ustar00rootroot00000000000000#ifndef __VOLUME_SURFACE_OUTLINE_VIEW_CONTROLLER__H_ #define __VOLUME_SURFACE_OUTLINE_VIEW_CONTROLLER__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "VolumeSurfaceOutlineColorOrTabModel.h" class QCheckBox; class QDoubleSpinBox; class QGridLayout; namespace caret { class Surface; class SurfaceSelectionViewController; class VolumeSurfaceOutlineModel; class VolumeSurfaceOutlineColorOrTabViewController; class WuQGridLayoutGroup; class VolumeSurfaceOutlineViewController : public QObject { Q_OBJECT public: VolumeSurfaceOutlineViewController(const Qt::Orientation orientation, QGridLayout* gridLayout, QObject* parent = 0); virtual ~VolumeSurfaceOutlineViewController(); void setVisible(bool visible); void updateViewController(VolumeSurfaceOutlineModel* outlineModel); private slots: void enabledCheckBoxStateChanged(int); void thicknessSpinBoxValueChanged(double); void surfaceSelected(Surface*); void colorTabSelected(VolumeSurfaceOutlineColorOrTabModel::Item*); private: VolumeSurfaceOutlineViewController(const VolumeSurfaceOutlineViewController&); VolumeSurfaceOutlineViewController& operator=(const VolumeSurfaceOutlineViewController&); void updateGraphics(); VolumeSurfaceOutlineModel* outlineModel; WuQGridLayoutGroup* gridLayoutGroup; QCheckBox* enabledCheckBox; VolumeSurfaceOutlineColorOrTabViewController* colorOrTabSelectionControl; QDoubleSpinBox* thicknessSpinBox; SurfaceSelectionViewController* surfaceSelectionViewController; }; #ifdef __VOLUME_SURFACE_OUTLINE_VIEW_CONTROLLER_DECLARE__ // #endif // __VOLUME_SURFACE_OUTLINE_VIEW_CONTROLLER_DECLARE__ } // namespace #endif //__VOLUME_SURFACE_OUTLINE_VIEW_CONTROLLER__H_ workbench-1.1.1/src/GuiQt/WuQCollapsibleWidget.cxx000066400000000000000000000116031255417355300221050ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #include #include #include #define __WU_Q_COLLAPSIBLE_WIDGET_DECLARE__ #include "WuQCollapsibleWidget.h" #undef __WU_Q_COLLAPSIBLE_WIDGET_DECLARE__ #include "CaretAssert.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::WuQCollapsibleWidget * \brief A container widget with multiple collapsible sections * * A container widget with multiple collapsible sections in * a vertical orientation. Each section is independent of * the other sections so that multiple sections may be open * at one time. */ /** * Constructor. */ WuQCollapsibleWidget::WuQCollapsibleWidget(QWidget* parent) : QWidget(parent) { this->showHideActionGroup = new QActionGroup(this); QObject::connect(this->showHideActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(showHideActionGroupTriggered(QAction*))); this->collapsibleLayout = new QVBoxLayout(); WuQtUtilities::setLayoutSpacingAndMargins(this->collapsibleLayout, 2, 2); QWidget* widget = new QWidget(); QVBoxLayout* widgetLayout = new QVBoxLayout(widget); widgetLayout->addLayout(this->collapsibleLayout); widgetLayout->addStretch(); QScrollArea* scrollArea = new QScrollArea(); scrollArea->setWidgetResizable(true); scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); scrollArea->setWidget(widget); QVBoxLayout* layout = new QVBoxLayout(this); layout->addWidget(scrollArea); //layout->addStretch(); Using this will result in the widget too small vertically } /** * Destructor. */ WuQCollapsibleWidget::~WuQCollapsibleWidget() { } /** * @return The recommended size for this widget. */ QSize WuQCollapsibleWidget::sizeHint() const { //QSize minSize = this->minimumSize(); //std::cout << "Collapse Min Size: " //<< minSize.width() << ", " << minSize.width() << std::endl; QSize sz = QWidget::sizeHint(); sz.setHeight(500); return sz; } /** * Add a widget to this collapsible widget with the * given label in the titlebar. * @param widget * Widget that is added. * @param text * Text displayed for collapsing/expanding view * of widget. */ void WuQCollapsibleWidget::addItem(QWidget* widget, const QString& text) { CaretAssert(widget); QLabel* label = new QLabel(text); QAction* showHideAction = this->showHideActionGroup->addAction("-"); showHideAction->setData(qVariantFromValue((void*)widget)); QToolButton* showHideToolButton = new QToolButton(); showHideToolButton->setDefaultAction(showHideAction); QHBoxLayout* controlLayout = new QHBoxLayout(); WuQtUtilities::setLayoutSpacingAndMargins(controlLayout, 2, 2); controlLayout->addWidget(showHideToolButton); controlLayout->addSpacing(15); controlLayout->addWidget(label); controlLayout->addStretch(); QFrame* controlFrame = new QFrame(); controlFrame->setLineWidth(1); controlFrame->setMidLineWidth(2); controlFrame->setFrameStyle(QFrame::Box | QFrame::Plain); controlFrame->setLayout(controlLayout); controlFrame->setFixedHeight(controlFrame->sizeHint().height()); this->collapsibleLayout->addWidget(controlFrame); this->collapsibleLayout->addWidget(widget); this->widgets.push_back(widget); } /** * Called when a show/hide tool button is triggered */ void WuQCollapsibleWidget::showHideActionGroupTriggered(QAction* action) { if (action != NULL) { void* pointer = action->data().value(); CaretAssert(pointer); QWidget* widget = (QWidget*)pointer; QString collapseExpandText = ""; if (widget->isVisible()) { widget->setVisible(false); collapseExpandText = "+"; } else { widget->setVisible(true); collapseExpandText = "-"; } action->setText(collapseExpandText); } } workbench-1.1.1/src/GuiQt/WuQCollapsibleWidget.h000066400000000000000000000036301255417355300215330ustar00rootroot00000000000000#ifndef __WU_Q_COLLAPSIBLE_WIDGET__H_ #define __WU_Q_COLLAPSIBLE_WIDGET__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include class QAction; class QActionGroup; class QVBoxLayout; namespace caret { class WuQCollapsibleWidget : public QWidget { Q_OBJECT public: WuQCollapsibleWidget(QWidget* parent = 0); virtual ~WuQCollapsibleWidget(); void addItem(QWidget* widget, const QString& text); virtual QSize sizeHint() const; private slots: void showHideActionGroupTriggered(QAction*); private: WuQCollapsibleWidget(const WuQCollapsibleWidget&); WuQCollapsibleWidget& operator=(const WuQCollapsibleWidget&); QVBoxLayout* collapsibleLayout; QActionGroup* showHideActionGroup; QVector widgets; }; #ifdef __WU_Q_COLLAPSIBLE_WIDGET_DECLARE__ // #endif // __WU_Q_COLLAPSIBLE_WIDGET_DECLARE__ } // namespace #endif //__WU_Q_COLLAPSIBLE_WIDGET__H_ workbench-1.1.1/src/GuiQt/WuQDataEntryDialog.cxx000066400000000000000000000332021255417355300215220ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "CaretColorEnumComboBox.h" #include "StructureEnumComboBox.h" #include "SurfaceSelectionViewController.h" #include "WuQDataEntryDialog.h" #include "WuQFactory.h" #include "WuQMessageBox.h" #include "WuQtUtilities.h" using namespace caret; /** * constructor. * * @param title * Title for dialog. * @param parent * Parent of the dialog. * @param addScrollBarsFlag * If true, add scrollbars to the dialog (typically true when the dialog * will contain many items. * @param f * Qt's Window flags. * */ WuQDataEntryDialog::WuQDataEntryDialog(const QString& title, QWidget* parent, const bool addScrollBarsFlag, Qt::WindowFlags f) : WuQDialogModal(title, parent, f) { WuQDialog::ScrollAreaStatus scrollBarStatus = WuQDialog::SCROLL_AREA_NEVER; if (addScrollBarsFlag) { scrollBarStatus = WuQDialog::SCROLL_AREA_ALWAYS; } constructDialog(scrollBarStatus); } /** * constructor. * * @param title * Title for dialog. * @param parent * Parent of the dialog. * @param scrollBarStatus * Add scrollbars (never, as needed, always). * @param f * Qt's Window flags. * */ WuQDataEntryDialog::WuQDataEntryDialog(const QString& title, QWidget* parent, const WuQDialog::ScrollAreaStatus scrollBarStatus, Qt::WindowFlags f) : WuQDialogModal(title, parent, f) { constructDialog(scrollBarStatus); } /** * destructor. */ WuQDataEntryDialog::~WuQDataEntryDialog() { } /** * Finish construction of the dialog. * * @param scrollBarStatus * Status for scroll bars. */ void WuQDataEntryDialog::constructDialog(const WuQDialog::ScrollAreaStatus scrollBarStatus) { // // Widget and Layout for user's widgets // QWidget* widgetForGridLayout = new QWidget; widgetGridLayout = new QGridLayout(widgetForGridLayout); // // Labels for text at top, hidden until set by user // textAtTopLabel = new QLabel; textAtTopLabel->setHidden(true); // // ButtonGroup for radio buttons // radioButtonGroup = new QButtonGroup(this); // // Layout for dialog // QWidget* widget = new QWidget(); QVBoxLayout* dialogLayout = new QVBoxLayout(widget); dialogLayout->addWidget(textAtTopLabel); dialogLayout->addWidget(widgetForGridLayout); this->setCentralWidget(widget, scrollBarStatus); } /** * Called when the OK button is pressed. * If needed should override this to process * data when the OK button is pressed and then * call this to issue the accept signal. */ void WuQDataEntryDialog::okButtonClicked() { m_isDataValid = true; m_dataInvalidErrorMessage = ""; emit validateData(this); if (m_isDataValid) { // if (dataEnteredIsValid()) { // accept(); // } WuQDialogModal::okButtonClicked(); } else { if (m_dataInvalidErrorMessage.isEmpty()) { m_dataInvalidErrorMessage = "Data is not valid."; } WuQMessageBox::errorOk(this, m_dataInvalidErrorMessage); } } /** * This method is used to set the validity of the data * widgets that were added by the user. To use this, * connect to the validateData() signal and then call * this to indicate the validity of the data widgets. * getDataWidgetWithName() can be used to get widgets * but only will work if the user sets the name of * the widgets. * @param isValid * True if the data is valid, else false. * @param dataInvalidErrorMessage * If data is invalid, message describing why data is invalid. */ void WuQDataEntryDialog::setDataValid(const bool isValid, const QString& dataInvalidErrorMessage) { m_isDataValid = isValid; m_dataInvalidErrorMessage = dataInvalidErrorMessage; } /** * Get the data widget that was added by user with the * given name. For this method to return the correct * widget, the user must call setObjectName() on a * widget after it is added to the dialog. * * @param name * Name of the widget. * @return * Pointer to widget with the given name or NULL * if the widget is not found. */ QWidget* WuQDataEntryDialog::getDataWidgetWithName(const QString& name) { const int numWidgets = widgets.size(); for (int i = 0; i < numWidgets; i++) { if (widgets[i]->objectName() == name) { return widgets[i]; } } return NULL; } ///** // * override to verify data after OK button pressed. // */ //bool //WuQDataEntryDialog::dataEnteredIsValid() //{ // return true; //} /** * set text at top of dialog (text is automatically wrapped). */ void WuQDataEntryDialog::setTextAtTop(const QString& s, const bool wrapTheText) { textAtTopLabel->setText(s); textAtTopLabel->setWordWrap(wrapTheText); if (s.isEmpty()) { textAtTopLabel->setHidden(true); } else { textAtTopLabel->setHidden(false); } } /** * add widgets to the next available row in the dialog. */ void WuQDataEntryDialog::addWidgetsToNextRow(QWidget* leftColumnWidget, QWidget* rightColumnWidget) { // // add widgets to the next row // const int rowNumber = widgetGridLayout->rowCount(); if (leftColumnWidget != NULL) { widgetGridLayout->addWidget(leftColumnWidget, rowNumber, 0); } if (rightColumnWidget != NULL) { widgetGridLayout->addWidget(rightColumnWidget, rowNumber, 1); } } /** * add widget to next available row in the dialog. */ QWidget* WuQDataEntryDialog::addWidget(const QString& labelText, QWidget* widget) { // // Create the label // QLabel* label = new QLabel(labelText); // // Keep pointer to widget // widgets.push_back(widget); // // add widget to layout // addWidgetsToNextRow(label, widget); return widget; } /** * add a check box. */ QCheckBox* WuQDataEntryDialog::addCheckBox(const QString& text, const bool defaultValue) { // // Create check box // QCheckBox* cb = new QCheckBox(text); cb->setChecked(defaultValue); // // Keep pointer to widget // widgets.push_back(cb); // // add widget to both columns of layout // const int rowNumber = widgetGridLayout->rowCount(); widgetGridLayout->addWidget(cb, rowNumber, 0, 1, 2, Qt::AlignLeft); return cb; } /** * get radio button selected (-1 if none, value is sequence added). */ int WuQDataEntryDialog::getRadioButtonSelected() const { return radioButtonGroup->checkedId(); } /** * add a radio button (all radio buttons will be mutually exclusive). */ QRadioButton* WuQDataEntryDialog::addRadioButton(const QString& text, const bool defaultValue) { // // Create radio button // QRadioButton* rb = new QRadioButton(text); rb->setChecked(defaultValue); // // Add to radio button group // const int buttNum = radioButtonGroup->buttons().count(); radioButtonGroup->addButton(rb, buttNum); // // Keep pointer to widget // widgets.push_back(rb); // // add widget to both columns of layout // const int rowNumber = widgetGridLayout->rowCount(); widgetGridLayout->addWidget(rb, rowNumber, 0, 1, 2, Qt::AlignLeft); return rb; } /** * add a combo box. */ QComboBox* WuQDataEntryDialog::addComboBox(const QString& labelText, const QStringList& comboBoxItems, const QList* comboBoxItemsUserData) { // // Create combo box // QComboBox* comboBox = WuQFactory::newComboBox(); for (int i = 0; i < comboBoxItems.size(); i++) { QVariant userData; if (comboBoxItemsUserData != NULL) { if (i < comboBoxItemsUserData->size()) { userData = (*comboBoxItemsUserData).at(i); } } comboBox->addItem(comboBoxItems.at(i), userData); } // // Add to dialog // addWidget(labelText, comboBox); return comboBox; } /** * add line edit. */ QLineEdit* WuQDataEntryDialog::addLineEditWidget(const QString& labelText, const QString& defaultText) { // // Create line edit // QLineEdit* le = new QLineEdit; le->setText(defaultText); // // Add to dialog // addWidget(labelText, le); return le; } /* * add list box. */ QListWidget* WuQDataEntryDialog::addListWidget(const QString& labelText, const QStringList& listBoxItems) { // // Create and populate list widget // QListWidget* lw = new QListWidget; lw->addItems(listBoxItems); if (listBoxItems.count() > 0) { lw->setCurrentRow(0); } // // Add to dialog // addWidget(labelText, lw); return lw; } /** * add spin box. */ QSpinBox* WuQDataEntryDialog::addSpinBox(const QString& labelText, const int defaultValue, const int minimumValue, const int maximumValue, const int singleStep) { // // Create spin box // QSpinBox* sb = WuQFactory::newSpinBox(); sb->setMinimum(minimumValue); sb->setMaximum(maximumValue); sb->setSingleStep(singleStep); sb->setValue(defaultValue); // // Add to dialog // addWidget(labelText, sb); return sb; } /** * add double spin box. */ QDoubleSpinBox* WuQDataEntryDialog::addDoubleSpinBox(const QString& labelText, const float defaultValue, const float minimumValue, const float maximumValue, const float singleStep, const int numberOfDecimals) { // // Create spin box // QDoubleSpinBox* sb = WuQFactory::newDoubleSpinBox(); sb->setMinimum(minimumValue); sb->setMaximum(maximumValue); sb->setSingleStep(singleStep); sb->setValue(defaultValue); sb->setDecimals(numberOfDecimals); // // Add to dialog // addWidget(labelText, sb); return sb; } /** * add a text edit. */ QTextEdit* WuQDataEntryDialog::addTextEdit(const QString& labelText, const QString& defaultText, const bool readOnlyFlag) { // // Create text edit // QTextEdit* te = new QTextEdit; te->setReadOnly(readOnlyFlag); te->setPlainText(defaultText); // // add to dialog // addWidget(labelText, te); return te; } /** * Add a caret color selection control. */ CaretColorEnumComboBox* WuQDataEntryDialog::addCaretColorEnumComboBox(const QString& labelText, const CaretColorEnum::Enum defaultColor) { CaretColorEnumComboBox* caretColorComboBox = new CaretColorEnumComboBox(this); caretColorComboBox->setSelectedColor(defaultColor); this->addWidget(labelText, caretColorComboBox->getWidget()); return caretColorComboBox; } /** * Add a structure selection control. */ StructureEnumComboBox* WuQDataEntryDialog::addStructureEnumComboBox(const QString& labelText, const StructureEnum::Enum defaultStructure) { StructureEnumComboBox* structureEnumComboBox = new StructureEnumComboBox(this); structureEnumComboBox->setSelectedStructure(defaultStructure); this->addWidget(labelText, structureEnumComboBox->getWidget()); return structureEnumComboBox; } /** * Add a surface selection control */ SurfaceSelectionViewController* WuQDataEntryDialog::addSurfaceSelectionViewController(const QString& labelText, BrainStructure* brainStructure) { SurfaceSelectionViewController* surfaceSelectionViewController = new SurfaceSelectionViewController(this, brainStructure); this->addWidget(labelText, surfaceSelectionViewController->getWidget()); return surfaceSelectionViewController; } workbench-1.1.1/src/GuiQt/WuQDataEntryDialog.h000066400000000000000000000153471255417355300211610ustar00rootroot00000000000000 #ifndef __WU_Q_DATA_ENTRY_DIALOG_H__ #define __WU_Q_DATA_ENTRY_DIALOG_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "CaretColorEnum.h" #include "StructureEnum.h" #include "WuQDialogModal.h" class QButtonGroup; class QCheckBox; class QComboBox; class QDialogButtonBox; class QDoubleSpinBox; class QGridLayout; class QLabel; class QLineEdit; class QListWidget; class QRadioButton; class QSpinBox; class QTextEdit; namespace caret { class BrainStructure; class CaretColorEnumComboBox; class StructureEnumComboBox; class SurfaceSelectionViewController; /// class for a modal data entry dialog class WuQDataEntryDialog : public WuQDialogModal { Q_OBJECT public: // constructor WuQDataEntryDialog(const QString& title, QWidget* parent, const bool addScrollBarsFlag = false, Qt::WindowFlags f = 0); WuQDataEntryDialog(const QString& title, QWidget* parent, const WuQDialog::ScrollAreaStatus scrollBarStatus, Qt::WindowFlags f = 0); // destructor ~WuQDataEntryDialog(); // add widget to next available row in the dialog QWidget* addWidget(const QString& labelText, QWidget* widget); // add widgets to the next available row in the dialog void addWidgetsToNextRow(QWidget* leftColumnWidget, QWidget* rightColumnWidget); // add a check box QCheckBox* addCheckBox(const QString& text, const bool defaultValue = false); // add a combo box QComboBox* addComboBox(const QString& labelText, const QStringList& comboBoxItems, const QList* comboBoxItemsUserData = NULL); // add line edit QLineEdit* addLineEditWidget(const QString& labelText, const QString& defaultText = ""); // add list box QListWidget* addListWidget(const QString& labelText, const QStringList& listBoxItems); // add a radio button (all radio buttons will be mutually exclusive) QRadioButton* addRadioButton(const QString& text, const bool defaultValue = false); // get radio button selected (-1 if none, value is sequence added) int getRadioButtonSelected() const; // add spin box QSpinBox* addSpinBox(const QString& labelText, const int defaultValue, const int minimumValue = -10000, const int maximumValue = 10000, const int singleStep = 1); // add double spin box QDoubleSpinBox* addDoubleSpinBox(const QString& labelText, const float defaultValue, const float minimumValue = -10000000.0, const float maximumValue = 10000000.0, const float singleStep = 1.0, const int numberOfDecimals = 3); // add a text edit QTextEdit* addTextEdit(const QString& labelText, const QString& defaultText, const bool readOnlyFlag); // add a structure selection control StructureEnumComboBox* addStructureEnumComboBox(const QString& labelText, const StructureEnum::Enum defaultStructure = StructureEnum::INVALID); // add a caret color selection control CaretColorEnumComboBox* addCaretColorEnumComboBox(const QString& labelText, const CaretColorEnum::Enum defaultColor = CaretColorEnum::BLACK); SurfaceSelectionViewController* addSurfaceSelectionViewController(const QString& labelText, BrainStructure* brainStructure); // set text at top of dialog void setTextAtTop(const QString& s, const bool wrapTheText); void setDataValid(const bool isValid, const QString& dataInvalidErrorMessage); QWidget* getDataWidgetWithName(const QString& name); signals: /** * This signal is emitted when the user presses the OK button. * The user of the dialog can then examine the widgets that were * added to the dialog for validity and then pass the validity * status back to this dialog via setDataValid(). */ void validateData(WuQDataEntryDialog* dataEntryDialog); protected: virtual void okButtonClicked(); // // override to verify data after OK button pressed if subclassing this dialog // virtual bool dataEnteredIsValid(); private: void constructDialog(const WuQDialog::ScrollAreaStatus scrollBarStatus); /// widgets in dialog QVector widgets; /// layout for widgets in dialog QGridLayout* widgetGridLayout; /// label for text at dialog top QLabel* textAtTopLabel; /// button group for radio buttons QButtonGroup* radioButtonGroup; /** Indicates validity of data */ bool m_isDataValid; /** Error message displayed when data is invalid */ QString m_dataInvalidErrorMessage; }; } // namespace #endif // __WU_Q_DATA_ENTRY_DIALOG_H__ workbench-1.1.1/src/GuiQt/WuQDialog.cxx000066400000000000000000000637361255417355300177250ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ /* * Some code in keyPressEvent copied from QT4, original license follows */ /**************************************************************************** ** ** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the QtGui module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Digia. For licensing terms and ** conditions see http://qt.digia.com/licensing. For further information ** use the contact form at http://qt.digia.com/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Digia gives you certain additional ** rights. These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "CaretAssert.h" #include "CaretLogger.h" #define __WU_QDIALOG_DECLARE__ #include "WuQDialog.h" #undef __WU_QDIALOG_DECLARE__ #include "WuQtUtilities.h" using namespace caret; /** * \class caret::WuQDialog * \brief Base class for dialogs. * * A base class for dialogs. */ /** * constructor. * * @param dialogTitle * Title for dialog. * @param parent * Parent widget on which this dialog is displayed. * @param f * optional Qt::WindowFlags */ WuQDialog::WuQDialog(const AString& dialogTitle, QWidget* parent, Qt::WindowFlags f) : QDialog(parent, f) { m_placeCentralWidgetInScrollAreaStatus = SCROLL_AREA_NEVER; m_topWidget = NULL; m_centralWidget = NULL; m_bottomWidget = NULL; m_firstTimeInShowMethodFlag = true; m_centralWidgetLayoutIndex = -1; m_sizeHintWidth = -1; m_sizeHintHeight = -1; this->autoDefaultProcessingEnabledFlag = true; this->setAttribute(Qt::WA_DeleteOnClose, false); this->setWindowTitle(dialogTitle); this->setFocusPolicy(Qt::ClickFocus); this->userWidgetLayout = new QVBoxLayout(); WuQtUtilities::setLayoutSpacingAndMargins(this->userWidgetLayout, 0, 0); m_layoutLeftOfButtonBox = new QHBoxLayout(); m_layoutLeftOfButtonBox->setContentsMargins(0, 0, 0, 0); this->buttonBox = new QDialogButtonBox(Qt::Horizontal, this); QObject::connect(this->buttonBox, SIGNAL(clicked(QAbstractButton*)), this, SLOT(clicked(QAbstractButton*))); QHBoxLayout* bottomLayout = new QHBoxLayout(); bottomLayout->addLayout(m_layoutLeftOfButtonBox, 0); bottomLayout->addWidget(this->buttonBox, 1000); QVBoxLayout* dialogLayout = new QVBoxLayout(this); WuQtUtilities::setLayoutSpacingAndMargins(dialogLayout, 0, 0); dialogLayout->addLayout(this->userWidgetLayout); dialogLayout->addLayout(bottomLayout); } /** * destructor. */ WuQDialog::~WuQDialog() { } ///** // * Set the size hint for the dialog to the given width and height. // * // * NOTE: If there are scrollbars added to the dialog, this size hint // * may be ignored. // */ //void //WuQDialog::setDialogSizeHint(const int32_t width, // const int32_t height) //{ // m_sizeHintWidth = width; // m_sizeHintHeight = height; //} /** * Add a widget to the left of the buttons at the bottom of the dialog. * More than one widget can be added and the first widget will be on * the left-most side. * * @param widget * Widget to add. */ void WuQDialog::addWidgetToLeftOfButtons(QWidget* widget) { m_layoutLeftOfButtonBox->addWidget(widget); } /** * Allows deletion of the dialog by the windowing * system when the dialog is closed. This should only * be true if the dialog is dynamically allocated (with * new) and not explicitly delete'ed by the user's code. * * @param deleteFlag * If true, dialog will be deleted by the windowing system. */ void WuQDialog::setDeleteWhenClosed(bool deleteFlag) { this->setAttribute(Qt::WA_DeleteOnClose, deleteFlag); } /** * Set the text of a standard button. If the * text is an empty string, the button is removed. * * @param button * Standard button enum identifying button. * @param text * Text for the button. */ void WuQDialog::setStandardButtonText(QDialogButtonBox::StandardButton button, const AString& text) { QPushButton* pushButton = this->buttonBox->button(button); if (text.isEmpty()) { if (pushButton != NULL) { this->buttonBox->removeButton(pushButton); } } else { if (pushButton == NULL) { pushButton = this->buttonBox->addButton(button); } pushButton->setText(text); } } /** * called to capture image after timeout so nothing obscures window. */ void WuQDialog::slotCaptureImageAfterTimeOut() { QImage image = QPixmap::grabWindow(this->winId()).toImage(); if (image.isNull() == false) { QClipboard* clipboard = QApplication::clipboard(); clipboard->setImage(image); QMessageBox::information(this, "Information", "An image of this dialog has been placed onto the computer's clipboard."); } } /** * called to capture image of window and place it on the clipboard */ void WuQDialog::slotMenuCaptureImageOfWindowToClipboard() { // // Need to delay capture so that the context sensistive // menu closes or else the menu will be in the captured image. // QApplication::processEvents(); QTimer::singleShot(1000, this, SLOT(slotCaptureImageAfterTimeOut())); } /** * add a capture image of window menu item to the menu. */ void WuQDialog::addImageCaptureToMenu(QMenu* menu) { menu->addAction("Capture Image to Clipboard", this, SLOT(slotMenuCaptureImageOfWindowToClipboard())); } /** * called by parent when context menu event occurs. */ void WuQDialog::contextMenuEvent(QContextMenuEvent* cme) { const bool enableCaptureMenu = false; if (enableCaptureMenu) { // // Popup menu for selection of pages // QMenu menu(this); // // Add menu item for image capture // addImageCaptureToMenu(&menu); // // Popup the menu // menu.exec(cme->globalPos()); } } /** * Called by parent when a key press event occurs * * This code is taken from QDialog::keyPressEvent. * so it may need to be updated with new version of * Qt. * * It is used to disable the AutoDault button * property which triggers a button click when * the return key is pressed on a dialog. */ void WuQDialog::keyPressEvent(QKeyEvent* e) { if (autoDefaultProcessingEnabledFlag) { QDialog::keyPressEvent(e); return; } bool acceptedEvent = false; // Calls reject() if Escape is pressed. Simulates a button // click for the default button if Enter is pressed. Move focus // for the arrow keys. Ignore the rest. #ifdef Q_WS_MAC if(e->modifiers() == Qt::ControlModifier && e->key() == Qt::Key_Period) { } else #endif if (!e->modifiers() || (e->modifiers() & Qt::KeypadModifier && e->key() == Qt::Key_Enter)) { switch (e->key()) { case Qt::Key_Enter: case Qt::Key_Return: e->accept(); acceptedEvent = true; break; case Qt::Key_Escape: break; default: return; } } if (acceptedEvent == false) { QDialog::keyPressEvent(e); } } /** * ring the bell. */ void WuQDialog::beep() { QApplication::beep(); } /** * show the watch cursor. */ void WuQDialog::showWaitCursor() { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); } /** * normal cursor. */ void WuQDialog::showNormalCursor() { QApplication::restoreOverrideCursor(); } /** * called to close. */ bool WuQDialog::close() { return QDialog::close(); } /** * @return The dialog button box for adding buttons. */ QDialogButtonBox* WuQDialog::getDialogButtonBox() { return this->buttonBox; } /** * Sets the given widgets to be the window's top and central widget. * Note: WuQDialog takes ownership of the widget pointer * and deletes it at the appropriate time. * * This should be called ONE and ONLY one time. * * Unless prohibited by the second parameter, the central widget * will be automatically placed into a scroll area if the * widget makes the dialog too big for the screen. * * @param topWidget * The optional widget displayed at top (may be NULL). * @param centralWidget * The central widget. * @param bottomWidget * The optional widget displayed at bottom (may be NULL). * @param placeCentralWidgetInScrollAreaStatus * Status of using a scroll area for the central widget */ void WuQDialog::setTopBottomAndCentralWidgets(QWidget* topWidget, QWidget* centralWidget, QWidget* bottomWidget, const ScrollAreaStatus placeCentralWidgetInScrollAreaStatus) { CaretAssert(centralWidget); this->setTopBottomAndCentralWidgetsInternal(topWidget, centralWidget, bottomWidget, placeCentralWidgetInScrollAreaStatus); } /** * Sets the give widget to be the window's central widget. * Note: WuQDialog takes ownership of the widget pointer * and deletes it at the appropriate time. * * This should be called ONE and ONLY one time. * * Unless prohibited by the second parameter, the central widget * will be automatically placed into a scroll area if the * widget makes the dialog too big for the screen. * * @param centralWidget * The central widget. * @param placeCentralWidgetInScrollAreaStatus * Status of using a scroll area for the central widget */ void WuQDialog::setCentralWidget(QWidget* centralWidget, const ScrollAreaStatus placeCentralWidgetInScrollAreaStatus) { CaretAssert(centralWidget); this->setTopBottomAndCentralWidgetsInternal(NULL, centralWidget, NULL, placeCentralWidgetInScrollAreaStatus); } /** * Sets the given widgets to be the window's top and central widget. * Note: WuQDialog takes ownership of the widget pointer * and deletes it at the appropriate time. * * This should be called ONE and ONLY one time. * * Unless prohibited by the second parameter, the central widget * will be automatically placed into a scroll area if the * widget makes the dialog too big for the screen. * * @param topWidget * The widget display at top. * @param centralWidget * The central widget. * @param topWidget * The widget display at top. * @param placeCentralWidgetInScrollAreaStatus * Status of using a scroll area for the central widget */ void WuQDialog::setTopBottomAndCentralWidgetsInternal(QWidget* topWidget, QWidget* centralWidget, QWidget* bottomWidget, const ScrollAreaStatus placeCentralWidgetInScrollAreaStatus) { m_topWidget = topWidget; m_centralWidget = centralWidget; m_bottomWidget = bottomWidget; m_placeCentralWidgetInScrollAreaStatus = placeCentralWidgetInScrollAreaStatus; if (topWidget != NULL) { this->userWidgetLayout->addWidget(topWidget); } m_centralWidgetLayoutIndex = userWidgetLayout->count(); this->userWidgetLayout->addWidget(centralWidget); if (bottomWidget != NULL) { this->userWidgetLayout->addWidget(bottomWidget); } } /** * Enable auto default button processing. * Often it is very annoying so use this method * withe enable == false, to disable it. * * @param enabled * New status for auto default processing. */ void WuQDialog::setAutoDefaultButtonProcessing(bool enabled) { this->autoDefaultProcessingEnabledFlag = enabled; } /** * Disable the auto default property for ANY buttons * in this dialog. This method must be called after * the subclass has added its widget(s) to the dialog. */ void WuQDialog::disableAutoDefaultForAllPushButtons() { /* * Disable auto default for all push buttons */ QList allChildPushButtons = findChildren(QRegExp(".*")); QListIterator allChildPushButtonsIterator(allChildPushButtons); while (allChildPushButtonsIterator.hasNext()) { QPushButton* pushButton = allChildPushButtonsIterator.next(); pushButton->setAutoDefault(false); pushButton->setDefault(false); } } /** * Called when a help button has been added to the dialog. * User must override this method when adding a help button. */ void WuQDialog::helpButtonClicked() { CaretAssertMessage(0, "Help button was added to dialog but WuQDialog::helpButtonClicked() was not overriden"); } /** * Called for focus events. Since this dialog stores a pointer * to the overlay, we need to be aware that the overlay's parameters * may change or the overlay may even be deleted. So, when * this dialog gains focus, validate the overlay and then update * the dialog. * * @param event * The focus event. */ void WuQDialog::focusInEvent(QFocusEvent* /*event*/) { focusGained(); } /** * Will be called when dialog gains focus. User may override this * method to receive focus in events. */ void WuQDialog::focusGained() { /* nothing - intended to be overridden by users */ } /** * Gets called when the dialog is to be displayed. */ void WuQDialog::showEvent(QShowEvent* event) { int32_t resizedDialogWidth = -1; int32_t resizedDialogHeight = -1; if (m_firstTimeInShowMethodFlag) { const int32_t dialogWidth = sizeHint().width(); const int32_t dialogHeight = sizeHint().height(); /* * Maximum size is the size of the smallest screen. */ const QSize maxSize = WuQtUtilities::getMinimumScreenSize(); const int32_t margin = 100; const int32_t maximumDialogWidth = maxSize.width() - margin; const int32_t maximumDialogHeight = maxSize.height() - (margin * 2); // allow space top/bottom //const int32_t maximumDialogHeight = (maxSize.height() / 2) - margin; bool putCentralWidgetIntoScrollAreaFlag = false; bool testCentralWidgetForTooBig = false; switch (m_placeCentralWidgetInScrollAreaStatus) { case SCROLL_AREA_ALWAYS: putCentralWidgetIntoScrollAreaFlag = true; break; case SCROLL_AREA_AS_NEEDED_VERT_NO_HORIZ: putCentralWidgetIntoScrollAreaFlag = true; break; case SCROLL_AREA_AS_NEEDED: testCentralWidgetForTooBig = true; break; case SCROLL_AREA_NEVER: break; } if (testCentralWidgetForTooBig) { /* * Are contents too big for window? */ if ((dialogWidth > maximumDialogWidth) || (dialogHeight > maximumDialogHeight)) { putCentralWidgetIntoScrollAreaFlag = true; resizedDialogWidth = std::min(dialogWidth, maximumDialogWidth); resizedDialogHeight = std::min(dialogHeight, maximumDialogHeight); } } if (putCentralWidgetIntoScrollAreaFlag) { /* * Remove the central widget, * place it into a scroll area, * and insert the scroll area into the layout */ userWidgetLayout->removeWidget(m_centralWidget); QScrollArea* scrollArea = new QScrollArea(); scrollArea->setWidgetResizable(true); scrollArea->setWidget(m_centralWidget); if (m_placeCentralWidgetInScrollAreaStatus == SCROLL_AREA_AS_NEEDED_VERT_NO_HORIZ) { scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); } userWidgetLayout->insertWidget(m_centralWidgetLayoutIndex, scrollArea); /* * Resize the dialog. */ if ((resizedDialogWidth > 0) && (resizedDialogHeight > 0)) { m_sizeHintWidth = resizedDialogWidth; m_sizeHintHeight = resizedDialogHeight; adjustSize(); } } } QDialog::showEvent(event); m_firstTimeInShowMethodFlag = false; } /** * @return The size hint. * * Overriding the size hint is the best way to set the size of a dialog. */ QSize WuQDialog::sizeHint () const { QSize sh = QDialog::sizeHint(); if ((m_sizeHintWidth > 0) && (m_sizeHintHeight > 0)) { sh.setWidth(m_sizeHintWidth); sh.setHeight(m_sizeHintHeight); /* * Reset the size hint so that the user can resize the dialog * without it reverting to this size hint. */ m_sizeHintWidth = -1; m_sizeHintHeight = -1; } return sh; } /** * Set the size of the dialog for next time it is displayed. * This must be called BEFORE displayng the dialog. */ void WuQDialog::setSizeOfDialogWhenDisplayed(const QSize& size) { const int32_t w = size.width(); const int32_t h = size.height(); if ((w > 0) && (h > 0)) { m_sizeHintWidth = w; m_sizeHintHeight = h; } } /** * A scroll area's size hint is often larger than its content widget and in * this case the dialog will be too large with blank space in the scroll area. * This method will adjust the size of the dialog which in turn will shrink * the size of the scroll area to better fit its content. This should only * be called once for a dialog so that the user can adjust the size of the * dialog, if desired. * * @param dialog * Dialog whose size is adjusted. * @param scrollArea * The scroll area in the dialog. */ void WuQDialog::adjustSizeOfDialogWithScrollArea(QDialog* dialog, QScrollArea* scrollArea) { /* * The content region of a scroll area is often too large vertically * so adjust the size of the dialog which will cause the scroll area * to approximately fit its content. */ QWidget* scrollAreaContentWidget = scrollArea->widget(); if (scrollAreaContentWidget != NULL) { int32_t contentHeight = scrollAreaContentWidget->sizeHint().height(); int32_t scrollHeight = scrollArea->sizeHint().height(); int32_t scrollBarHeight = scrollArea->horizontalScrollBar()->sizeHint().height(); int32_t diff = (scrollHeight - (contentHeight + scrollBarHeight + 10)); if (diff > 0) { QSize dialogSizeHint = dialog->sizeHint(); dialogSizeHint.setHeight(dialogSizeHint.height() - diff); dialog->resize(dialogSizeHint); } } } /** * Adds a button to the dialog. When the button is * pressed, userButtonPressed(QPushButton*) will be * called with the button that was created and returned * by this method. The subclass of the dialog MUST * override userButtonPressed(QPushButton*). * * @param text * Text for the pushbutton. * @param buttonRole * Role of button. NOTE: This is used for placement of buttons in * the appropriate location for the operating system. Any action, * such as closing the dialog will not occur because of this button * push. * @return * QPushButton that was created. */ QPushButton* WuQDialog::addUserPushButton(const AString& text, const QDialogButtonBox::ButtonRole buttonRole) { QPushButton* pushButton = getDialogButtonBox()->addButton(text, buttonRole); return pushButton; } /** * Called when a push button was added using addUserPushButton(). * Subclasses MUST override this if user push buttons were * added using addUserPushButton(). * * @param userPushButton * User push button that was pressed. * @return * The result that indicates action that should be taken * as a result of the button being pressed. */ WuQDialog::DialogUserButtonResult WuQDialog::userButtonPressed(QPushButton* userPushButton) { const AString msg = ("Subclass of WuQDialog added a user pushbutton but failed to override userButtonPressed for button labeled \"" + userPushButton->text() + "\""); CaretAssertMessage(0, msg); CaretLogSevere(msg); return RESULT_NONE; } /** * Called when the OK button is clicked. * If needed should override this to process * data when the OK button is clicked and then * call this to issue the accept signal. */ void WuQDialog::okButtonClicked() { if (! isModal()) { CaretAssertMessage(0, "WuQDialog::okButtonClicked() should never be called for a NON-modal dialog."); } accept(); } /** * Called when the Cancel button is clicked. * If needed should override this to process * data when the Cancel button is clicked. * Call this to issue the reject signal. */ void WuQDialog::cancelButtonClicked() { if (! isModal()) { CaretAssertMessage(0, "WuQDialog::cancelButtonClicked() should never be called for a NON-modal dialog."); } reject(); } /** * Called when the Apply button is pressed. * If needed should override this to process * data when the Apply button is pressed. */ void WuQDialog::applyButtonClicked() { if (isModal()) { CaretAssertMessage(0, "WuQDialog::applyButtonClicked() should never be called for a MODAL dialog."); } } /** * Called when the Close button is pressed. * If needed should override this to process * data when the Close button is pressed. */ void WuQDialog::closeButtonClicked() { if (isModal()) { CaretAssertMessage(0, "WuQDialog::closeButtonClicked() should never be called for a MODAL dialog."); } close(); } /** * Called when a button is pressed. */ void WuQDialog::clicked(QAbstractButton* button) { QDialogButtonBox::StandardButton standardButton = this->getDialogButtonBox()->standardButton(button); if (standardButton == QDialogButtonBox::Ok) { okButtonClicked(); } else if (standardButton == QDialogButtonBox::Cancel) { cancelButtonClicked(); } else if (standardButton == QDialogButtonBox::Apply) { applyButtonClicked(); } else if (standardButton == QDialogButtonBox::Close) { closeButtonClicked(); } else if (standardButton == QDialogButtonBox::Help) { this->helpButtonClicked(); } else { QPushButton* pushButton = dynamic_cast(button); CaretAssert(pushButton); const DialogUserButtonResult result = this->userButtonPressed(pushButton); switch (result) { case RESULT_MODAL_ACCEPT: accept(); break; case RESULT_MODAL_REJECT: reject(); break; case RESULT_NON_MODAL_CLOSE: close(); break; case RESULT_NONE: break; }; } } workbench-1.1.1/src/GuiQt/WuQDialog.h000066400000000000000000000131741255417355300173410ustar00rootroot00000000000000#ifndef __WU_QDIALOG_H__ #define __WU_QDIALOG_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include "AString.h" class QFocusEvent; class QHBoxLayout; class QKeyEvent; class QMenu; class QScrollArea; class QVBoxLayout; namespace caret { class WuQDialog : public QDialog { Q_OBJECT protected: WuQDialog(const AString& dialogTitle, QWidget* parent, Qt::WindowFlags f = 0); public: /** * Status of scroll area displayed */ enum ScrollAreaStatus { SCROLL_AREA_ALWAYS, SCROLL_AREA_AS_NEEDED, SCROLL_AREA_AS_NEEDED_VERT_NO_HORIZ, SCROLL_AREA_NEVER }; /** * Result of user button pressed. */ enum DialogUserButtonResult { /** MODAL accept which means OK pressed and dialog closes */ RESULT_MODAL_ACCEPT, /** MODAL reject which means Cancel pressed and dialog closes */ RESULT_MODAL_REJECT, /** NON-MODAL close dialog. */ RESULT_NON_MODAL_CLOSE, /** none which means no action is taken and dialog remains open */ RESULT_NONE }; virtual ~WuQDialog(); void setCentralWidget(QWidget* centralWidget, const ScrollAreaStatus placeCentralWidgetInScrollAreaStatus); void setTopBottomAndCentralWidgets(QWidget* topWidget, QWidget* centralWidget, QWidget* bottomWidget, const ScrollAreaStatus placeCentralWidgetInScrollAreaStatus); void setStandardButtonText(QDialogButtonBox::StandardButton button, const AString& text); QPushButton* addUserPushButton(const AString& text, const QDialogButtonBox::ButtonRole buttonRole); void setDeleteWhenClosed(bool deleteFlag); void setAutoDefaultButtonProcessing(bool enabled); void disableAutoDefaultForAllPushButtons(); void addWidgetToLeftOfButtons(QWidget* widget); static void beep(); static void showWaitCursor(); static void showNormalCursor(); static void adjustSizeOfDialogWithScrollArea(QDialog* dialog, QScrollArea* scrollArea); virtual QSize sizeHint() const; void setSizeOfDialogWhenDisplayed(const QSize& size); public slots: virtual bool close(); protected slots: void slotMenuCaptureImageOfWindowToClipboard(); void slotCaptureImageAfterTimeOut(); protected: QDialogButtonBox* getDialogButtonBox(); virtual DialogUserButtonResult userButtonPressed(QPushButton* userPushButton); void addImageCaptureToMenu(QMenu* menu); virtual void okButtonClicked(); virtual void cancelButtonClicked(); virtual void applyButtonClicked(); virtual void closeButtonClicked(); virtual void keyPressEvent(QKeyEvent* e); virtual void contextMenuEvent(QContextMenuEvent*); virtual void focusInEvent(QFocusEvent* event); virtual void helpButtonClicked(); virtual void focusGained(); virtual void showEvent(QShowEvent* event); // void setDialogSizeHint(const int32_t width, // const int32_t height); private slots: void clicked(QAbstractButton* button); private: void setTopBottomAndCentralWidgetsInternal(QWidget* topWidget, QWidget* centralWidget, QWidget* bottomWidget, const ScrollAreaStatus placeCentralWidgetInScrollAreaStatus); QVBoxLayout* userWidgetLayout; QDialogButtonBox* buttonBox; QHBoxLayout* m_layoutLeftOfButtonBox; bool autoDefaultProcessingEnabledFlag; bool m_firstTimeInShowMethodFlag; ScrollAreaStatus m_placeCentralWidgetInScrollAreaStatus; int32_t m_centralWidgetLayoutIndex; QWidget* m_topWidget; QWidget* m_centralWidget; QWidget* m_bottomWidget; mutable int32_t m_sizeHintWidth; mutable int32_t m_sizeHintHeight; }; #ifdef __WU_QDIALOG_DECLARE__ #endif // __WU_QDIALOG_DECLARE__ } #endif // __WU_QDIALOG_H__ workbench-1.1.1/src/GuiQt/WuQDialogModal.cxx000066400000000000000000000153641255417355300206740ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __WU_Q_DIALOG_MODAL_DECLARE__ #include "WuQDialogModal.h" #undef __WU_Q_DIALOG_MODAL_DECLARE__ #include "CaretAssert.h" #include "CaretLogger.h" using namespace caret; /** * \class caret::WuQDialogModal * \brief Base class for modal dialogs. * * A base class for modal dialogs. */ /** * Constructs a modal dialog. After construction, * use exec() to display the dialog. * * @param dialogTitle * Title for dialog. * @param parent * Parent widget on which this dialog is displayed. * @param f * optional Qt::WindowFlags */ WuQDialogModal::WuQDialogModal(const AString& dialogTitle, QWidget* parent, Qt::WindowFlags f) : WuQDialog(dialogTitle, parent, f) { m_isSaveDialogPosition = false; this->getDialogButtonBox()->addButton(QDialogButtonBox::Ok); this->getDialogButtonBox()->addButton(QDialogButtonBox::Cancel); // QObject::connect(this->getDialogButtonBox(), SIGNAL(clicked(QAbstractButton*)), // this, SLOT(clicked(QAbstractButton*))); this->getDialogButtonBox()->button(QDialogButtonBox::Ok)->setDefault(true); //this->getDialogButtonBox()->button(QDialogButtonBox::Ok)->setAutoDefault(true); } /** * Constructs a modal dialog. After construction, * use exec() to display the dialog. * * @param dialogTitle * Title for dialog. * @param centralWidget, * Central widget that is displayed in the dialog. * @param parent * Parent widget on which this dialog is displayed. * @param f * optional Qt::WindowFlags */ WuQDialogModal::WuQDialogModal(const AString& dialogTitle, QWidget* centralWidget, QWidget* parent, Qt::WindowFlags f) : WuQDialog(dialogTitle, parent, f) { this->getDialogButtonBox()->addButton(QDialogButtonBox::Ok); this->getDialogButtonBox()->addButton(QDialogButtonBox::Cancel); QObject::connect(this->getDialogButtonBox(), SIGNAL(clicked(QAbstractButton*)), this, SLOT(clicked(QAbstractButton*))); this->setCentralWidget(centralWidget, WuQDialog::SCROLL_AREA_NEVER); } /** * Destructor. */ WuQDialogModal::~WuQDialogModal() { } /** * Set the OK button to the given text. If the text * is zero length, the OK button is removed. * * @text * Text for OK button. */ void WuQDialogModal::setOkButtonText(const AString& text) { this->setStandardButtonText(QDialogButtonBox::Ok, text); /* if (text.isEmpty()) { this->getDialogButtonBox()->button(QDialogButtonBox::Cancel)->setDefault(true); this->getDialogButtonBox()->button(QDialogButtonBox::Cancel)->setAutoDefault(true); } else { this->getDialogButtonBox()->button(QDialogButtonBox::Ok)->setDefault(true); this->getDialogButtonBox()->button(QDialogButtonBox::Ok)->setAutoDefault(true); } */ } /** * Set the Cancel button to the given text. If the text * is zero length, the Cancel button is removed. * * @text * Text for OK button. */ void WuQDialogModal::setCancelButtonText(const AString& text) { this->setStandardButtonText(QDialogButtonBox::Cancel, text); } /** * If this method is called, each time the dialog is closed, it will * save the position of the dialog and restore the dialog to that * position when the dialog is reopened. * * @param savePositionName * Name used for saving the dialog's position. If the default value * (an empty string), the dialog's title is used for saving the * dialog's position. Note that is there is more than one dialog * with the same name positions may not be correctly restored. */ void WuQDialogModal::setSaveWindowPositionForNextTime(const AString& savePositionName) { m_isSaveDialogPosition = true; m_saveDialogPositionName = savePositionName; if (m_saveDialogPositionName.isEmpty()) { m_saveDialogPositionName = windowTitle(); if (m_saveDialogPositionName.isEmpty()) { m_isSaveDialogPosition = false; } } } /** * Shows/hides a widget. * Override to optionally place dialog via values passed to setDisplayedXY. */ void WuQDialogModal::setVisible(bool visible) { WuQDialog::setVisible(visible); if (m_isSaveDialogPosition) { /* * Find previous position of dialog. */ std::map::iterator iter = s_savedDialogPositions.find(m_saveDialogPositionName); if (visible) { if (iter != s_savedDialogPositions.end()) { /* * Restore dialog position */ SavedPosition savedPosition = iter->second; if ((savedPosition.x > 0) && (savedPosition.y > 0)) { move(savedPosition.x, savedPosition.y); } if ((savedPosition.w > 0) && (savedPosition.h > 0)) { resize(savedPosition.w, savedPosition.h); } } } else { /* * Save position of dialog. */ SavedPosition savedPosition; savedPosition.x = x(); savedPosition.y = y(); savedPosition.w = width(); savedPosition.h = height(); if (iter != s_savedDialogPositions.end()) { /* * Replace dialog position */ iter->second = savedPosition; } else { /* * Insert dialog position */ s_savedDialogPositions.insert(std::make_pair(m_saveDialogPositionName, savedPosition)); } } } } workbench-1.1.1/src/GuiQt/WuQDialogModal.h000066400000000000000000000046641255417355300203220ustar00rootroot00000000000000#ifndef __WU_Q_DIALOG_MODAL__H_ #define __WU_Q_DIALOG_MODAL__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "WuQDialog.h" namespace caret { class WuQDialogModal : public WuQDialog { Q_OBJECT public: WuQDialogModal(const AString& dialogTitle, QWidget* parent, Qt::WindowFlags f = 0); WuQDialogModal(const AString& dialogTitle, QWidget* centralWidget, QWidget* parent, Qt::WindowFlags f = 0); virtual ~WuQDialogModal(); void setOkButtonText(const AString& text); void setCancelButtonText(const AString& text); void setSaveWindowPositionForNextTime(const AString& savePositionName = ""); public slots: virtual void setVisible(bool); private: WuQDialogModal(const WuQDialogModal&); WuQDialogModal& operator=(const WuQDialogModal&); private: bool m_isSaveDialogPosition; AString m_saveDialogPositionName; struct SavedPosition { SavedPosition() { x = -1; y = -1; w = -1; h = -1; } int x; int y; int w; int h; }; static std::map s_savedDialogPositions; }; #ifdef __WU_Q_DIALOG_MODAL_DECLARE__ std::map WuQDialogModal::s_savedDialogPositions; #endif // __WU_Q_DIALOG_MODAL_DECLARE__ } // namespace #endif //__WU_Q_DIALOG_MODAL__H_ workbench-1.1.1/src/GuiQt/WuQDialogNonModal.cxx000066400000000000000000000121341255417355300213370ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __WU_Q_DIALOG_NON_MODAL_DECLARE__ #include "WuQDialogNonModal.h" #undef __WU_Q_DIALOG_NON_MODAL_DECLARE__ #include "CaretAssert.h" #include "CaretLogger.h" using namespace caret; /** * \class caret::WuQDialogNonModal * \brief Base class for non-modal dialogs. * * A base class for non-modal dialogs. */ /** * Constructs a modal dialog. After construction, * use exec() to display the dialog. * * @param dialogTitle * Title for dialog. * @param parent * Parent widget on which this dialog is displayed. * @param f * optional Qt::WindowFlags */ WuQDialogNonModal::WuQDialogNonModal(const AString& dialogTitle, QWidget* parent, Qt::WindowFlags f) : WuQDialog(dialogTitle, parent, f) { this->getDialogButtonBox()->addButton(QDialogButtonBox::Apply); this->getDialogButtonBox()->addButton(QDialogButtonBox::Close); // QObject::connect(this->getDialogButtonBox(), SIGNAL(clicked(QAbstractButton*)), // this, SLOT(clicked(QAbstractButton*))); this->getDialogButtonBox()->button(QDialogButtonBox::Apply)->setDefault(false); this->getDialogButtonBox()->button(QDialogButtonBox::Apply)->setAutoDefault(false); this->getDialogButtonBox()->button(QDialogButtonBox::Close)->setDefault(false); this->getDialogButtonBox()->button(QDialogButtonBox::Close)->setAutoDefault(false); m_isPositionRestoredWhenReopened = false; m_positionWhenClosedValid = false; } /** * Destructor. */ WuQDialogNonModal::~WuQDialogNonModal() { } /** * Gets called when the dialog is closing. * Overriden so that position of dialog * can be saved. */ void WuQDialogNonModal::closeEvent(QCloseEvent* /*event*/) { // if (m_isPositionRestoredWhenReopened) { // /* // * Save position and size of dialog so that // * when it is shown next time, it will be // * in the position and size as when it was // * closed. // */ // m_positionWhenClosedValid = true; // m_positionWhenClosed = this->pos(); // m_sizeWhenClosed = this->size(); // } // // WuQDialog::closeEvent(event); emit dialogWasClosed(); } /** * Gets called when the dialog is to be displayed. */ void WuQDialogNonModal::showEvent(QShowEvent* event) { // if (m_isPositionRestoredWhenReopened) { // if (m_positionWhenClosedValid) { // /* // * Restore the dialog in the position and size that it // * was in when closed. Use move() for position and // * the size hint for the size. // */ // this->move(m_positionWhenClosed); // this->resize(m_sizeWhenClosed); //// const int32_t w = m_sizeWhenClosed.width(); //// const int32_t h = m_sizeWhenClosed.height(); //// if ((w > 0) //// && (h > 0)) { //// this->setDialogSizeHint(w, //// h); //// adjustSize(); //// } // } // } WuQDialog::showEvent(event); } /** * This slot can be called and it simply calls * applyButtonClicked. This slot can be connected * to GUI components. */ void WuQDialogNonModal::apply() { this->applyButtonClicked(); } /** * Set the Apply button to the given text. If the text * is zero length, the Apply button is removed. * * @text * Text for OK button. */ void WuQDialogNonModal::setApplyButtonText(const AString& text) { this->setStandardButtonText(QDialogButtonBox::Apply, text); } /** * Set the Close button to the given text. If the text * is zero length, the Close button is removed. * * @text * Text for OK button. */ void WuQDialogNonModal::setCloseButtonText(const AString& text) { this->setStandardButtonText(QDialogButtonBox::Close, text); } /** * If the given parameter is true, save the position of this * dialog when it is closed. Next time window is displayed * in the current session, use the position at the time the * dialog was closed. * @param saveIt * If true save the position for next time. */ void WuQDialogNonModal::setSaveWindowPositionForNextTime(const bool saveIt) { m_isPositionRestoredWhenReopened = saveIt; } workbench-1.1.1/src/GuiQt/WuQDialogNonModal.h000066400000000000000000000045511255417355300207700ustar00rootroot00000000000000#ifndef __WU_Q_DIALOG_NON_MODAL__H_ #define __WU_Q_DIALOG_NON_MODAL__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "WuQDialog.h" namespace caret { class WuQDialogNonModal : public WuQDialog { Q_OBJECT public: WuQDialogNonModal(const AString& dialogTitle, QWidget* parent = 0, Qt::WindowFlags f = 0); virtual ~WuQDialogNonModal(); void setApplyButtonText(const AString& text); void setCloseButtonText(const AString& text); /** May be called requesting the dialog to update its content */ virtual void updateDialog() = 0; void setSaveWindowPositionForNextTime(const bool saveIt); signals: /** * This signal is emitted when the dialog is closed (hidden). */ void dialogWasClosed(); protected slots: void apply(); protected: virtual void closeEvent(QCloseEvent* event); virtual void showEvent(QShowEvent* event); private: WuQDialogNonModal(const WuQDialogNonModal&); WuQDialogNonModal& operator=(const WuQDialogNonModal&); QPoint m_positionWhenClosed; QSize m_sizeWhenClosed; bool m_positionWhenClosedValid; bool m_isPositionRestoredWhenReopened; }; #ifdef __WU_Q_DIALOG_NON_MODAL_DECLARE__ // #endif // __WU_Q_DIALOG_NON_MODAL_DECLARE__ } // namespace #endif //__WU_Q_DIALOG_NON_MODAL__H_ workbench-1.1.1/src/GuiQt/WuQDoubleSlider.cxx000066400000000000000000000075371255417355300211000ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __WU_Q_DOUBLE_SLIDER_DECLARE__ #include "WuQDoubleSlider.h" #undef __WU_Q_DOUBLE_SLIDER_DECLARE__ #include using namespace caret; /** * \class caret::WuQDoubleSlider * \brief A slider for real values. * * Creates a slider for real values by encapsulating * a QSlider that operates only on integer values. */ /** * Constructor. * * @param orientation * horizontal or vertical * @param parent * optional parent */ WuQDoubleSlider::WuQDoubleSlider(Qt::Orientation orientation, QObject* parent) : WuQWidget(parent) { this->slider = new QSlider(orientation); this->slider->setRange(0, 1000); QObject::connect(this->slider, SIGNAL(valueChanged(int)), this, SLOT(qSliderValueChanged(int))); this->setRange(-100.0, 100.0); this->setValue(0); } /** * Destructor. */ WuQDoubleSlider::~WuQDoubleSlider() { } /** * Called when the encapsulated QSlider * value is changed. * @param value * New value. */ void WuQDoubleSlider::qSliderValueChanged(int value) { const double dSlider = this->slider->maximum() - this->slider->minimum(); const double parametricValue = (static_cast(value) - this->slider->minimum()) / dSlider; double dRange = this->maximum - this->minimum; if (dRange == 0.0) { dRange = 1.0; } this->sliderValue = dRange * parametricValue + this->minimum; emit valueChanged(this->sliderValue); } /** * @return The widget that is enapsulated. */ QWidget* WuQDoubleSlider::getWidget() { return this->slider; } /** * Set range of values. * @param minValue * New value for minimum. * @param maxValue * New value for maximum. */ void WuQDoubleSlider::setRange(double minValue, double maxValue) { this->minimum = minValue; this->maximum = maxValue; if (this->minimum > this->maximum) { this->maximum = this->minimum; } this->updateSlider(); } /** * @return The value of the slider. */ double WuQDoubleSlider::value() const { return this->sliderValue; } /** * Set the value for the slider. * @param d * New value. */ void WuQDoubleSlider::setValue(double valueIn) { this->sliderValue = valueIn; this->updateSlider(); emit valueChanged(this->sliderValue); } /** * Update the encapsulated slider. */ void WuQDoubleSlider::updateSlider() { double dRange = this->maximum - this->minimum; if (dRange == 0.0) { dRange = 1.0; } if (this->sliderValue > this->maximum) { this->sliderValue = this->maximum; } if (this->sliderValue < this->minimum) { this->sliderValue = this->minimum; } const double parametricValue = (this->sliderValue - this->minimum) / dRange; const int dSlider = this->slider->maximum() - this->slider->minimum(); const int qSliderValue = static_cast(dSlider * parametricValue) + this->minimum; this->slider->blockSignals(true); this->slider->setValue(qSliderValue); this->slider->blockSignals(false); } workbench-1.1.1/src/GuiQt/WuQDoubleSlider.h000066400000000000000000000037301255417355300205140ustar00rootroot00000000000000#ifndef __WU_Q_DOUBLE_SLIDER__H_ #define __WU_Q_DOUBLE_SLIDER__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "WuQWidget.h" class QSlider; namespace caret { class WuQDoubleSlider : public WuQWidget { Q_OBJECT public: WuQDoubleSlider(Qt::Orientation orientation, QObject* parent); virtual ~WuQDoubleSlider(); QWidget* getWidget(); void setRange(double minValue, double maxValue); double value() const; signals: void valueChanged(double); public slots: void setValue(double); private slots: void qSliderValueChanged(int); private: WuQDoubleSlider(const WuQDoubleSlider&); WuQDoubleSlider& operator=(const WuQDoubleSlider&); void updateSlider(); QSlider* slider; double minimum; double maximum; double sliderValue; }; #ifdef __WU_Q_DOUBLE_SLIDER_DECLARE__ // #endif // __WU_Q_DOUBLE_SLIDER_DECLARE__ } // namespace #endif //__WU_Q_DOUBLE_SLIDER__H_ workbench-1.1.1/src/GuiQt/WuQEventBlockingFilter.cxx000066400000000000000000000107351255417355300224150ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __WU_Q_EVENT_BLOCKING_FILTER_DECLARE__ #include "WuQEventBlockingFilter.h" #undef __WU_Q_EVENT_BLOCKING_FILTER_DECLARE__ #include #include using namespace caret; /** * \class caret::WuQEventBlockingFilter * \brief A QEvent Filter * * A QEvent Filter. There are cases in which one wants to * block a specific event from being processed by widgets. * For example, on Mac, dragging the mouse over a combo box * causes a wheel event that unintentionally changes the * value of the combo box. * * To use an instance of this blocking filter, (1) Create * an instance of this class, (2) call setEventBlocked() which * the event enumerated type that is to be blocked, (3) pass * the instance of this class to the widget's * installEventFilter() method. */ /** * Constructor. * @param object * Parent that will own this event filter. The parent * will handle destruction of this event filter. */ WuQEventBlockingFilter::WuQEventBlockingFilter(QObject* parent) : QObject(parent) { } /** * Add a wheel blocking filter to a combo box (on mac only). On a Mac, * if the mouse pointer is moved across a combo box (particularly when * the user is using a Track Pad), Qt will get this event and apply it * as a wheel event to the combo box changing the selection in the * combo box. * * @param comboBox * Combo box that has its wheel event blocked. */ void WuQEventBlockingFilter::blockMouseWheelEventInMacComboBox(QComboBox* comboBox) { #ifdef CARET_OS_MACOSX /* * Attach an event filter that blocks wheel events in the combo box if Mac */ WuQEventBlockingFilter* comboBoxWheelEventBlockingFilter = new WuQEventBlockingFilter(comboBox); comboBoxWheelEventBlockingFilter->setEventBlocked(QEvent::Wheel, true); comboBox->installEventFilter(comboBoxWheelEventBlockingFilter); #endif // CARET_OS_MACOSX } /** * Destructor. */ WuQEventBlockingFilter::~WuQEventBlockingFilter() { } /** * Set the blocking status for the given event type. * @param eventType * Type of event to block. * @param blockedStatus * New status for blocking. */ void WuQEventBlockingFilter::setEventBlocked(const QEvent::Type eventType, const bool blockedStatus) { const int eventAsInt = static_cast(eventType); this->blockedEventsHashTable.insert(eventAsInt, blockedStatus); } /** * Is the given event blocked? * @param eventType * Type of event. * @return * True if event is being blocked, else false. */ bool WuQEventBlockingFilter::isEventBlocked(const QEvent::Type eventType) const { const int eventAsInt = static_cast(eventType); return this->blockedEventsHashTable.value(eventAsInt, false); } /** * Filters events for the given object. * @param object * Object for which events are being filtered. * @param event * Event that is examined for blocking. * @ @return * True, meaning that this event will not receive * any furthere processing. Or false, meaning * that the processing has been passed to the * parent class of the object. */ bool WuQEventBlockingFilter::eventFilter(QObject* object, QEvent* event) { const int eventAsInt = static_cast(event->type()); if (this->blockedEventsHashTable.value(eventAsInt, false)) { return true; } /* * Let parent do the filtering */ return QObject::eventFilter(object, event); } workbench-1.1.1/src/GuiQt/WuQEventBlockingFilter.h000066400000000000000000000042511255417355300220360ustar00rootroot00000000000000#ifndef __WU_Q_EVENT_BLOCKING_FILTER__H_ #define __WU_Q_EVENT_BLOCKING_FILTER__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include class QComboBox; namespace caret { class WuQEventBlockingFilter : public QObject { Q_OBJECT public: static void blockMouseWheelEventInMacComboBox(QComboBox* comboBox); WuQEventBlockingFilter(QObject* parent); virtual ~WuQEventBlockingFilter(); void setEventBlocked(const QEvent::Type eventType, const bool blockedStatus); bool isEventBlocked(const QEvent::Type eventType) const; protected: bool eventFilter(QObject* object, QEvent* event); private: WuQEventBlockingFilter(const WuQEventBlockingFilter&); WuQEventBlockingFilter& operator=(const WuQEventBlockingFilter&); /** * Hash Table is probably fastest way to track multiple events * that can be blocked. Key is int (QEvent::Type) and value * is blocked status. */ QHash blockedEventsHashTable; }; #ifdef __WU_Q_EVENT_BLOCKING_FILTER_DECLARE__ // #endif // __WU_Q_EVENT_BLOCKING_FILTER_DECLARE__ } // namespace #endif //__WU_Q_EVENT_BLOCKING_FILTER__H_ workbench-1.1.1/src/GuiQt/WuQFactory.cxx000066400000000000000000000424701255417355300201250ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __WU_Q_FACTORY_DECLARE__ #include "WuQFactory.h" #undef __WU_Q_FACTORY_DECLARE__ #include "CaretLogger.h" using namespace caret; /** * \class caret::WuQFactory * \brief A factory for creating Qt Widgets with desired attributes. * \ingroup GuiQt * * This factory creates Qt Widgets with some attributes set to desired * values. For QSpinBox, by default, issues value changed events as * each character is entered into the spin box. So, if the user enters * five numbers, five events are issued, which at times, can cause * undesired updates to the user-interface. In addition, the methods in * this factory can simplify the creation of new widgets. *

* Qt3 contained overloaded constructors that contained parameters for * widget attributes but these were removed in Qt4 most likely because * there was no way to clearly indicate the purpose of the parameters other * than the documentation. Thus, in Qt4 a widget must be created and then * methods called to set the widget's attributes. Using the a naming * convention in these factory methods removes the ambiguity about any * parameters. *

* Rules for the method naming:
*

  1. use the pattern "new[OptionalParameterNames][WithSignal[Type]]
    *
      *
    • A method name always begins with new *
    • WidgetType is the name of the Qt widget without the Q *
    • OptionalParameterNames describe any additional parameters *
    • WithSignal is at the end of the method name for those methods * that connect a value changed signal to a receiving class and slot. If a parameter * is passed, the type should also be indicated (WithSignalInt, WithSignalString, etc.). * Keep in mind that the receiving slot does not need to contain any parameters even * if the signal does contain parameters. In most cases, the signal is only issued * if the user changes the value. However, some widgets issue a signal when either the * user changes the value or the value is changed programmatically and this should be * noted in the documentation. *
    *
  2. Do not use default parameters. *
  3. Do not overload method names (identically named methods containing different parameters). *
  4. This naming convention may result in long names. However, the names will make the usage fairly obvious. *
  5. These naming patterns is based off that for Objective-C. *
*/ #include #include #include #include #include #include #include "WuQEventBlockingFilter.h" /** * Constructor. */ WuQFactory::WuQFactory() { } /** * Destructor. */ WuQFactory::~WuQFactory() { } /** * Create a combo box. * * On Apple computers, the mouse wheel can cause unintended changes * to a combo box even if the combo box does not have focus. So, * block the wheel event on Apple computers. In addition, on Apple * computers, every item that is in the combo box is displayed in * the pop-up menu (as large as the vertical size of the screen) * so use a Windows Style combo box to improve the usability. * * @return * A combo box. */ QComboBox* WuQFactory::newComboBox() { QComboBox* cb = new QComboBox(); #ifdef CARET_OS_MACOSX /* * Attach an event filter that blocks wheel events in the combo box if Mac */ WuQEventBlockingFilter* comboBoxWheelEventBlockingFilter = new WuQEventBlockingFilter(cb); comboBoxWheelEventBlockingFilter->setEventBlocked(QEvent::Wheel, true); cb->installEventFilter(comboBoxWheelEventBlockingFilter); //setWindowsStyleForApple(cb); #endif // CARET_OS_MACOSX return cb; } /** * Create a combo box. * * On Apple computers, the mouse wheel can cause unintended changes * to a combo box even if the combo box does not have focus. So, * block the wheel event on Apple computers. In addition, on Apple * computers, every item that is in the combo box is displayed in * the pop-up menu (as large as the vertical size of the screen) * so use a Windows Style combo box to improve the usability. * * @param receiver * Object that received the signal when the value is changed by the user. * @param method * Method that is connected to the spin box's valueChanged(int) * signal. * @return * A combo box. */ QComboBox* WuQFactory::newComboBoxSignalInt(QObject* receiver, const char* method) { QComboBox* cb = newComboBox(); QObject::connect(cb, SIGNAL(activated(int)), receiver, method); return cb; } /** * Create a spin box. * * The minimum value is the most negative 32-bit integer, the maximum * values is the most positive 32-bit integer, step size is one, and * the default value is zero. * Keyboard tracking is disabled so that signal are NOT issued when * the user changes the text contained in the spin box. * * @return * A QSpinBox initialized with the default parameters. */ QSpinBox* WuQFactory::newSpinBox() { QSpinBox* sb = newSpinBoxWithMinMaxStep(std::numeric_limits::min(), std::numeric_limits::max(), 1); return sb; } /** * Create a spin box. * * The minimum value is the most negative 32-bit integer, the maximum * values is the most positive 32-bit integer, step size is one, and * the default value is zero. * Keyboard tracking is disabled so that signal are NOT issued when * the user changes the text contained in the spin box. *

* NOTE: The signal contains an integer parameters that is the new value * contained in the spin box. The signal is emitted when the user * changes the value AND when the value is changed programatically. * * @param receiver * Object that received the signal when the value is changed. * @param method * Method that is connected to the spin box's valueChanged(int) * signal. * @return * A QSpinBox initialized with the default parameters. */ QSpinBox* WuQFactory::newSpinBoxWithSignalInt(QObject* receiver, const char* method) { QSpinBox* sb = newSpinBoxWithMinMaxStepSignalInt(std::numeric_limits::min(), std::numeric_limits::max(), 1, receiver, method); return sb; } /** * Create a spin box with the given minimum, maximum, and step values. * Keyboard tracking is disabled so that signal are NOT issued when * the user changes the text contained in the spin box. *

* The default value is zero. If zero is not within the given * minimum and maximum values, the default value is the minimum value. * * @param minimumValue * Minimum value for spin box. * @param maximumValue * Maximum value for spin box. * @param stepSize * Step (change in value) when the user increments the spin box. * @return * A QSpinBox initialized with the given parameters. */ QSpinBox* WuQFactory::newSpinBoxWithMinMaxStep(const int minimumValue, const int maximumValue, const int stepSize) { QSpinBox* sb = new QSpinBox(); sb->setMinimum(minimumValue); sb->setMaximum(maximumValue); sb->setSingleStep(stepSize); sb->setKeyboardTracking(false); if ((0 >= minimumValue) && (0 <= maximumValue)) { sb->setValue(0); } else { sb->setValue(minimumValue); } return sb; } /** * Create a spin box with the given minimum, maximum, and step values. * Keyboard tracking is disabled so that signal are NOT issued when * the user changes the text contained in the spin box. *

* The default value is zero. If zero is not within the given * minimum and maximum values, the default value is the minimum value. *

* NOTE: The signal contains an integer parameters that is the new value * contained in the spin box. The signal is emitted when the user * changes the value AND when the value is changed programatically. * * @param minimumValue * Minimum value for spin box. * @param maximumValue * Maximum value for spin box. * @param stepSize * Step (change in value) when the user increments the spin box. * @param receiver * Object that received the signal when the value is changed. * @param method * Method that is connected to the spin box's valueChanged(int) * signal. * @return * A QSpinBox initialized with the given parameters. */ QSpinBox* WuQFactory::newSpinBoxWithMinMaxStepSignalInt(const int minimumValue, const int maximumValue, const int stepSize, QObject* receiver, const char* method) { QSpinBox* sb = newSpinBoxWithMinMaxStep(minimumValue, maximumValue, stepSize); QObject::connect(sb, SIGNAL(valueChanged(int)), receiver, method); return sb; } /** * Create a double spin box with the minimum value set to the most negative * float (not double) value, the maximum value set to the most positive * float (not double) value, the step size set to one and the number of * digits right of the decimal set to two. The default value is zero. * Keyboard tracking is disabled so that signal are NOT issued when * the user changes the text contained in the spin box. *

* Since workbench stores most data as 32-bit floats, the default minimum and * maximum values are those for float, not double. * * @return * A QDoubleSpinBox initialized with the default parameters. */ QDoubleSpinBox* WuQFactory::newDoubleSpinBox() { QDoubleSpinBox* sb = newDoubleSpinBoxWithMinMaxStepDecimals(-std::numeric_limits::max(), std::numeric_limits::max(), 1.0, 2); return sb; } /** * Create a double spin box with the minimum value set to the most negative * float (not double) value, the maximum value set to the most positive * float (not double) value, the step size set to one and the number of * digits right of the decimal set to two. The default value is zero. * Keyboard tracking is disabled so that signal are NOT issued when * the user changes the text contained in the spin box. *

* Since workbench stores most data as 32-bit floats, the default minimum and * maximum values are those for float, not double. * *

* NOTE: The signal contains an double parameter that is the new value * contained in the spin box. The signal is emitted when the user * changes the value AND when the value is changed programatically. * * @param receiver * Object that received the signal when the value is changed. * @param method * Method that is connected to the spin box's valueChanged(double) * signal. * @return * A QDoubleSpinBox initialized with the default parameters. */ QDoubleSpinBox* WuQFactory::newDoubleSpinBoxWithSignalDouble(QObject* receiver, const char* method) { QDoubleSpinBox* sb = newDoubleSpinBoxWithMinMaxStepDecimalsSignalDouble(-std::numeric_limits::max(), std::numeric_limits::max(), 1.0, 2, receiver, method); return sb; } /** * Create a double spin box with the given minimum, maximum, step, and * digits right of decimal values. * Keyboard tracking is disabled so that signal are NOT issued when * the user changes the text contained in the spin box. *

* The default value is zero. If zero is not within the given * minimum and maximum values, the default value is the minimum value. * * @param minimumValue * Minimum value for spin box. * @param maximumValue * Maximum value for spin box. * @param stepSize * Step (change in value) when the user increments the spin box. * @param digitsRightOfDecimal * Number of digits to right of decimal shown in the spin box. * @return * A QDoubleSpinBox initialized with the given parameters. */ QDoubleSpinBox* WuQFactory::newDoubleSpinBoxWithMinMaxStepDecimals(const double minimumValue, const double maximumValue, const double stepSize, const int digitsRightOfDecimal) { QDoubleSpinBox* sb = new QDoubleSpinBox(); sb->setMinimum(minimumValue); sb->setMaximum(maximumValue); sb->setSingleStep(stepSize); sb->setDecimals(digitsRightOfDecimal); sb->setKeyboardTracking(false); if ((0 >= minimumValue) && (0 <= maximumValue)) { sb->setValue(0); } else { sb->setValue(minimumValue); } return sb; } /** * Create a double spin box with the given minimum, maximum, step, and * digits right of decimal values. * Keyboard tracking is disabled so that signal are NOT issued when * the user changes the text contained in the spin box. *

* The default value is zero. If zero is not within the given * minimum and maximum values, the default value is the minimum value. *

* NOTE: The signal contains an double parameter that is the new value * contained in the spin box. The signal is emitted when the user * changes the value AND when the value is changed programatically. * * @param minimumValue * Minimum value for spin box. * @param maximumValue * Maximum value for spin box. * @param stepSize * Step (change in value) when the user increments the spin box. * @param digitsRightOfDecimal * Number of digits to right of decimal shown in the spin box. * @param receiver * Object that received the signal when the value is changed. * @param method * Method that is connected to the spin box's valueChanged(double) * signal. * @return * A QDoubleSpinBox initialized with the given parameters. */ QDoubleSpinBox* WuQFactory::newDoubleSpinBoxWithMinMaxStepDecimalsSignalDouble(const double minimumValue, const double maximumValue, const double stepSize, const int digitsRightOfDecimal, QObject* receiver, const char* method) { QDoubleSpinBox* sb = newDoubleSpinBoxWithMinMaxStepDecimals(minimumValue, maximumValue, stepSize, digitsRightOfDecimal); QObject::connect(sb, SIGNAL(valueChanged(double)), receiver, method); return sb; } /** * Sets the style of the given windows to windows. */ void WuQFactory::setWindowsStyleForApple(QWidget* w) { // w->setStyle(new WorkbenchMacStyle()); // return; /* * Only try creating once */ if (s_windowsStyleForAppleWasCreated == false) { s_windowsStyleForAppleWasCreated = true; s_windowsStyleForApple = QStyleFactory::create("Windows"); if (s_windowsStyleForApple == NULL) { CaretLogSevere("Failed to create Windows Style"); } } if (s_windowsStyleForApple != NULL) { w->setStyle(s_windowsStyleForApple); } } //int //WorkbenchMacStyle::styleHint ( StyleHint sh, const QStyleOption * opt, const QWidget * w, QStyleHintReturn * hret) const //{ // int value = QMacStyle::styleHint(sh, opt, w, hret); // // if (sh == QStyle::SH_ComboBox_Popup) { // value = 0; // } // // return value; //} workbench-1.1.1/src/GuiQt/WuQFactory.h000066400000000000000000000104011255417355300175370ustar00rootroot00000000000000#ifndef __WU_Q_FACTORY_H__ #define __WU_Q_FACTORY_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ class QComboBox; class QDoubleSpinBox; class QObject; class QSpinBox; class QStyle; class QWidget; namespace caret { class WuQFactory { public: static QComboBox* newComboBox(); static QComboBox* newComboBoxSignalInt(QObject* receiver, const char* method); static QSpinBox* newSpinBox(); static QSpinBox* newSpinBoxWithSignalInt(QObject* receiver, const char* method); static QSpinBox* newSpinBoxWithMinMaxStep(const int minimumValue, const int maximumValue, const int stepSize); static QSpinBox* newSpinBoxWithMinMaxStepSignalInt(const int minimumValue, const int maximumValue, const int stepSize, QObject* receiver, const char* method); static QDoubleSpinBox* newDoubleSpinBox(); static QDoubleSpinBox* newDoubleSpinBoxWithSignalDouble(QObject* receiver, const char* method); static QDoubleSpinBox* newDoubleSpinBoxWithMinMaxStepDecimals(const double minimumValue, const double maximumValue, const double stepSize, const int digitsRightOfDecimal); static QDoubleSpinBox* newDoubleSpinBoxWithMinMaxStepDecimalsSignalDouble(const double minimumValue, const double maximumValue, const double stepSize, const int digitsRightOfDecimal, QObject* receiver, const char* method); private: WuQFactory(); virtual ~WuQFactory(); WuQFactory(const WuQFactory&); WuQFactory& operator=(const WuQFactory&); static void setWindowsStyleForApple(QWidget* w); static QStyle* s_windowsStyleForApple; static bool s_windowsStyleForAppleWasCreated; public: // ADD_NEW_METHODS_HERE private: // ADD_NEW_MEMBERS_HERE }; // class WorkbenchMacStyle : public QMacStyle { // public: // WorkbenchMacStyle() {} // // virtual ~WorkbenchMacStyle() {} // virtual int styleHint ( StyleHint sh, const QStyleOption * opt = 0, const QWidget * w = 0, QStyleHintReturn * hret = 0 ) const; // }; #ifdef __WU_Q_FACTORY_DECLARE__ QStyle* WuQFactory::s_windowsStyleForApple = NULL; bool WuQFactory::s_windowsStyleForAppleWasCreated; #endif // __WU_Q_FACTORY_DECLARE__ } // namespace #endif //__WU_Q_FACTORY_H__ workbench-1.1.1/src/GuiQt/WuQGridLayoutGroup.cxx000066400000000000000000000141341255417355300216120ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #define __WU_Q_GRID_LAYOUT_GROUP_DECLARE__ #include "WuQGridLayoutGroup.h" #undef __WU_Q_GRID_LAYOUT_GROUP_DECLARE__ using namespace caret; /** * \class caret::WuQGridLayoutGroup * \brief Controls display of a group of widgets in a QGridLayout * * When displaying QWidgets in a QGridLayout one may want to * hide the widgets in a row. However, even though the widgets * are hidden and QGridLayout removes the space occupied by * the widgets, it seems to keep the spacing around the widgets * and the layout does not full shrink. * * This group will remove and add the widgets as the group * is set visible or hidden. This seems to remove the extra * spacing and allows the QGridLayout to fully shrink. */ /** * Constructor. * @param gridLayout * Grid layout of the widgets. * @param parent * Parent object. */ WuQGridLayoutGroup::WuQGridLayoutGroup(QGridLayout* gridLayout, QObject* parent) : QObject(parent) { this->gridLayout = gridLayout; this->areWidgetsInLayout = true; } /** * Destructor. */ WuQGridLayoutGroup::~WuQGridLayoutGroup() { const int numItems = this->layoutItems.size(); for (int i = 0; i < numItems; i++) { delete this->layoutItems[i]; } this->layoutItems.clear(); } /** * Add a widget to the group. * @param widget * Widget added to the layout * @param row * Row for widget. * @param column * Column for widget. * @param alignment * Alignment of widget. */ void WuQGridLayoutGroup::addWidget(QWidget* widget, int row, int column, Qt::Alignment alignment) { this->addWidget(widget, row, column, 1, 1, alignment); } /** * Add a widget to the group. * @param widget * Widget added to the layout * @param fromRow * Row for widget. * @param fromColumn * Column for widget. * @param rowSpan * Rows for widget. * @param columnSpan * Columns for widget. * @param alignment * Alignment of widget. */ void WuQGridLayoutGroup::addWidget(QWidget* widget, int fromRow, int fromColumn, int rowSpan, int columnSpan, Qt::Alignment alignment) { ItemRowCol* irc = new ItemRowCol(widget, fromRow, fromColumn, rowSpan, columnSpan, alignment); this->layoutItems.push_back(irc); this->gridLayout->addWidget(irc->widget, irc->fromRow, irc->fromColumn, irc->rowSpan, irc->columnSpan, irc->alignment); } /** * Set the visibility of the widgets in the group. */ void WuQGridLayoutGroup::setVisible(bool visible) { if (visible) { if (this->areWidgetsInLayout) { return; } const int numItems = this->layoutItems.size(); for (int i = 0; i < numItems; i++) { ItemRowCol* irc = this->layoutItems[i]; this->gridLayout->addWidget(irc->widget, irc->fromRow, irc->fromColumn, irc->rowSpan, irc->columnSpan, irc->alignment); irc->widget->setVisible(true); } this->areWidgetsInLayout = true; } else { if (this->areWidgetsInLayout) { const int numItems = this->layoutItems.size(); for (int i = 0; i < numItems; i++) { ItemRowCol* irc = this->layoutItems[i]; irc->widget->setVisible(false); this->gridLayout->removeWidget(irc->widget); } this->areWidgetsInLayout = false; } } } /** * Constructor for widget. * * @param widget * Widget added to the layout * @param fromRow * Row for widget. * @param fromColumn * Column for widget. * @param rowSpan * Rows for widget. * @param columnSpan * Columns for widget. * @param alignment * Alignment of widget. */ WuQGridLayoutGroup::ItemRowCol::ItemRowCol(QWidget *widget, int fromRow, int fromColumn, int rowSpan, int columnSpan, Qt::Alignment alignment) { this->widget = widget; this->fromRow = fromRow; this->fromColumn = fromColumn; this->rowSpan = rowSpan; this->columnSpan = columnSpan; this->alignment = alignment; } /** * @return Number of rows in grid layout. */ int WuQGridLayoutGroup::rowCount() const { return this->gridLayout->rowCount(); } /** * @return Number of columns in grid layout. */ int WuQGridLayoutGroup::columnCount() const { return this->gridLayout->columnCount(); } workbench-1.1.1/src/GuiQt/WuQGridLayoutGroup.h000066400000000000000000000052741255417355300212440ustar00rootroot00000000000000#ifndef __WU_Q_GRID_LAYOUT_GROUP__H_ #define __WU_Q_GRID_LAYOUT_GROUP__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include class QGridLayout; namespace caret { class WuQGridLayoutGroup : public QObject { Q_OBJECT public: WuQGridLayoutGroup(QGridLayout* gridLayout, QObject* parent = 0); virtual ~WuQGridLayoutGroup(); void addWidget(QWidget* widget, int row, int column, Qt::Alignment alignment = 0); void addWidget(QWidget* widget, int fromRow, int fromColumn, int rowSpan, int columnSpan, Qt::Alignment alignment = 0); int rowCount() const; int columnCount() const; void setVisible(bool visible); private: WuQGridLayoutGroup(const WuQGridLayoutGroup&); WuQGridLayoutGroup& operator=(const WuQGridLayoutGroup&); class ItemRowCol { public: ItemRowCol(QWidget *widget, int fromRow, int fromColumn, int rowSpan, int columnSpan, Qt::Alignment alignment); QWidget* widget; int fromRow; int fromColumn; int rowSpan; int columnSpan; Qt::Alignment alignment; }; QGridLayout* gridLayout; bool areWidgetsInLayout; QVector layoutItems; }; #ifdef __WU_Q_GRID_LAYOUT_GROUP_DECLARE__ // #endif // __WU_Q_GRID_LAYOUT_GROUP_DECLARE__ } // namespace #endif //__WU_Q_GRID_LAYOUT_GROUP__H_ workbench-1.1.1/src/GuiQt/WuQGroupBoxExclusiveWidget.cxx000066400000000000000000000234551255417355300233210ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __WU_Q_GROUP_BOX_EXCLUSIVE_WIDGET_DECLARE__ #include "WuQGroupBoxExclusiveWidget.h" #undef __WU_Q_GROUP_BOX_EXCLUSIVE_WIDGET_DECLARE__ #include #include #include #include #include #include #include "CaretAssert.h" #include "CaretLogger.h" #include "WuQtUtilities.h" using namespace caret; /** * \class caret::WuQGroupBoxExclusiveWidget * \brief A vertical layout with only one active item. * \ingroup GuiQt * * The items are layout out vertically inside an outline box * with each item containing a name an radio button for * mutual selection. Sort of a list of group boxes where * only one item is enabled at a time. */ /** * Constructor. * * @param parent * The parent widget. */ WuQGroupBoxExclusiveWidget::WuQGroupBoxExclusiveWidget(QWidget* parent) : QWidget(parent) { m_currentWidgetIndex = -1; m_widgetsGridLayout = new QGridLayout(this); m_widgetsGridLayout->setVerticalSpacing(10); m_widgetsGridLayout->setColumnMinimumWidth(0, 10); m_widgetsGridLayout->setColumnStretch(0, 0); m_widgetsGridLayout->setColumnStretch(1, 100); m_radioButtonGroup = new QButtonGroup(this); m_radioButtonGroup->setExclusive(true); QObject::connect(m_radioButtonGroup, SIGNAL(buttonClicked(QAbstractButton*)), this, SLOT(radioButtonClicked(QAbstractButton*))); } /** * Destructor. */ WuQGroupBoxExclusiveWidget::~WuQGroupBoxExclusiveWidget() { } /** * Called when a group's radio button is clicked. Will emit the * currentChanged() signal. * * @param button * Button that was clicked. */ void WuQGroupBoxExclusiveWidget::radioButtonClicked(QAbstractButton* button) { int32_t itemIndex = -1; QRadioButton* selectedRadioButton = qobject_cast(button); const int32_t num = count(); for (int32_t i = 0; i < num; i++) { CaretAssertVectorIndex(m_widgetDatas, i); if (m_widgetDatas[i].m_radioButton == selectedRadioButton) { itemIndex = i; m_widgetDatas[i].m_widget->setEnabled(true); } else { m_widgetDatas[i].m_widget->setEnabled(false); } } if (itemIndex >= 0) { m_currentWidgetIndex = itemIndex; updateSelectionWithValidWidget(); emit currentChanged(itemIndex); } else { m_currentWidgetIndex = -1; } } /** * Appends the given widget and returns its index. * * @param widget * Widget that is added. * @param textLabel * Text label displayed next to radio button. * @return * Index of the widget. */ int32_t WuQGroupBoxExclusiveWidget::addWidget(QWidget* widget, const QString& textLabel) { QRadioButton* radioButton = new QRadioButton(textLabel); m_radioButtonGroup->addButton(radioButton); QFrame* frameBox = new QFrame(); frameBox->setFrameStyle(QFrame::StyledPanel | QFrame::Plain); frameBox->setLineWidth(1); frameBox->setMidLineWidth(1); QHBoxLayout* frameBoxLayout = new QHBoxLayout(frameBox); frameBoxLayout->setContentsMargins(2, 2, 2, 2); frameBoxLayout->addWidget(widget, 100, Qt::AlignLeft); int row = m_widgetsGridLayout->rowCount(); m_widgetsGridLayout->addWidget(radioButton, row, 0, 1, 2, Qt::AlignLeft); row++; m_widgetsGridLayout->addWidget(frameBox, row, 1); WidgetData widgetData; widgetData.m_frameBox = frameBox; widgetData.m_radioButton = radioButton; widgetData.m_widget = widget; m_widgetDatas.push_back(widgetData); updateSelectionWithValidWidget(); return (m_widgetDatas.size() - 1); } /** * @return Number of widgets in container. */ int32_t WuQGroupBoxExclusiveWidget::count() const { return m_widgetDatas.size(); } /** * @return Index of widget that is selected. -1 if no widgets have been added. */ int32_t WuQGroupBoxExclusiveWidget::currentIndex() const { if (m_widgetDatas.empty()) { return -1; } CaretAssertVectorIndex(m_widgetDatas, m_currentWidgetIndex); return m_currentWidgetIndex; } void WuQGroupBoxExclusiveWidget::updateSelectionWithValidWidget() { if (m_widgetDatas.empty()) { m_currentWidgetIndex = -1; return; } if (m_currentWidgetIndex < 0) { m_currentWidgetIndex = 0; } else if (m_currentWidgetIndex >= count()) { m_currentWidgetIndex = count() - 1; } CaretAssertVectorIndex(m_widgetDatas, m_currentWidgetIndex); if ( ! isWidgetEnabled(m_currentWidgetIndex)) { m_currentWidgetIndex = -1; const int32_t num = count(); for (int32_t i = 0; i < num; i++) { if (isWidgetEnabled(i)) { m_currentWidgetIndex = i; break; } } } if (m_currentWidgetIndex >= 0) { CaretAssertVectorIndex(m_widgetDatas, m_currentWidgetIndex); m_radioButtonGroup->blockSignals(true); m_widgetDatas[m_currentWidgetIndex].m_radioButton->setChecked(true); m_radioButtonGroup->blockSignals(false); } const int32_t num = count(); for (int32_t i = 0; i < num; i++) { m_widgetDatas[i].m_widget->setEnabled(m_widgetDatas[i].m_radioButton->isChecked()); } } /** * @return The current widget or NULL is no widgets have been added. */ QWidget* WuQGroupBoxExclusiveWidget::currentWidget() const { const int32_t index = currentIndex(); if (index >= 0) { CaretAssertVectorIndex(m_widgetDatas, index); return m_widgetDatas[index].m_widget; } return NULL; } /** * @param widget * Widget for its index. * @return * Index of the given widget or -1 if the widget was never added. */ int32_t WuQGroupBoxExclusiveWidget::indexOf(QWidget* widget) const { const int32_t numWidgets = count(); for (int32_t i = 0; i < numWidgets; i++) { CaretAssertVectorIndex(m_widgetDatas, i); if (m_widgetDatas[i].m_widget == widget) { return i; } } return -1; } /** * @param index * Index of widget that is returned. * * @return * The widget at the given index or NULL if index is invalid. */ QWidget* WuQGroupBoxExclusiveWidget::widget(int32_t index) const { if ((index < 0) || (index >= count())) { const QString msg("Request with invalid widget index=" + QString::number(index)); CaretLogWarning(msg); CaretAssertMessage(0, msg); return NULL; } CaretAssertVectorIndex(m_widgetDatas, index); return m_widgetDatas[index].m_widget; } /** * Sets the current widget to the widget at the given index. * * @param index * Index of the widget. */ void WuQGroupBoxExclusiveWidget::setCurrentIndex(int32_t index) { if ((index < 0) || (index >= count())) { const QString msg("Attemp to set invalid current widget index=" + QString::number(index)); CaretLogWarning(msg); CaretAssertMessage(0, msg); return; } m_currentWidgetIndex = index; updateSelectionWithValidWidget(); } /** * Sets the current widget to the widget at the given widget. * * @param widget * Widget that is selected. */ void WuQGroupBoxExclusiveWidget::setCurrentWidget(QWidget* widget) { if (m_widgetDatas.empty()) { return; } const int32_t index = indexOf(widget); if (index >= 0) { m_currentWidgetIndex = index; } else { const QString msg("Widget was not found"); CaretLogWarning(msg); CaretAssertMessage(0, msg); } updateSelectionWithValidWidget(); } /** * Set the widget at given index enabled. * * @param index * Index of the widget. * @param enabled * New enabled status. */ void WuQGroupBoxExclusiveWidget::setWidgetEnabled(const int32_t index, const bool enabled) { if ((index < 0) || (index >= count())) { const QString msg("Attemp to set invalid widget enabled index=" + QString::number(index)); CaretLogWarning(msg); CaretAssertMessage(0, msg); return; } CaretAssertVectorIndex(m_widgetDatas, index); m_widgetDatas[index].m_radioButton->setEnabled(enabled); updateSelectionWithValidWidget(); } /** * @return Is the widget at the given index enabled? * * @param index * Index of the widget. */ bool WuQGroupBoxExclusiveWidget::isWidgetEnabled(const int32_t index) const { if ((index < 0) || (index >= count())) { const QString msg("Attemp to query invalid widget enabled index=" + QString::number(index)); CaretLogWarning(msg); CaretAssertMessage(0, msg); return false; } CaretAssertVectorIndex(m_widgetDatas, index); return m_widgetDatas[index].m_radioButton->isEnabled(); } workbench-1.1.1/src/GuiQt/WuQGroupBoxExclusiveWidget.h000066400000000000000000000061711255417355300227420ustar00rootroot00000000000000#ifndef __WU_Q_GROUP_BOX_EXCLUSIVE_WIDGET_H__ #define __WU_Q_GROUP_BOX_EXCLUSIVE_WIDGET_H__ /*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include class QAbstractButton; class QButtonGroup; class QFrame; class QGridLayout; class QRadioButton; namespace caret { class WuQGroupBoxExclusiveWidget : public QWidget { Q_OBJECT public: WuQGroupBoxExclusiveWidget(QWidget* parent = 0); virtual ~WuQGroupBoxExclusiveWidget(); int32_t addWidget(QWidget* widget, const QString& textLabel); int32_t count() const; int32_t currentIndex() const; QWidget* currentWidget() const; int32_t indexOf(QWidget* widget) const; QWidget* widget(int32_t index) const; void setWidgetEnabled(int32_t index, const bool enabled); bool isWidgetEnabled(const int32_t index) const; // ADD_NEW_METHODS_HERE signals: /** * Emitted when the user changes the selected widget. * * @param * Index of the selected widget. */ void currentChanged(int32_t index); public slots: void setCurrentIndex(int32_t index); void setCurrentWidget(QWidget* widget); private slots: void radioButtonClicked(QAbstractButton* button); private: WuQGroupBoxExclusiveWidget(const WuQGroupBoxExclusiveWidget&); WuQGroupBoxExclusiveWidget& operator=(const WuQGroupBoxExclusiveWidget&); void updateSelectionWithValidWidget(); // ADD_NEW_MEMBERS_HERE struct WidgetData { QWidget* m_widget; QFrame* m_frameBox; QRadioButton* m_radioButton; }; std::vector m_widgetDatas; QButtonGroup* m_radioButtonGroup; QGridLayout* m_widgetsGridLayout; mutable int32_t m_currentWidgetIndex; }; #ifdef __WU_Q_GROUP_BOX_EXCLUSIVE_WIDGET_DECLARE__ // #endif // __WU_Q_GROUP_BOX_EXCLUSIVE_WIDGET_DECLARE__ } // namespace #endif //__WU_Q_GROUP_BOX_EXCLUSIVE_WIDGET_H__ workbench-1.1.1/src/GuiQt/WuQImageLabel.cxx000066400000000000000000000125771255417355300205050ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __WU_Q_IMAGE_LABEL_DECLARE__ #include "WuQImageLabel.h" #undef __WU_Q_IMAGE_LABEL_DECLARE__ using namespace caret; /** * \class caret::WuQImageLabel * \brief Displays an icon in a pixmap and issues clicked signal. * \ingroup GuiQt * * Displays an icon in a QLabel. If the icon is clicked by the user * the clicked signal is emitted. This ImageLabel is intended use is * within a QTableWidget cell as a QButton'ish class would have a * background. * */ #include #include #include #include /** * Default constructor creates a label with no image nor text. */ WuQImageLabel::WuQImageLabel() : QLabel() { setAlignment(Qt::AlignCenter); } /** * Constructor constructs an image label with either the given image if the * image is valid (not NULL). If the image is invalid (NULL), the text * will be displayed. * * @param image * Image that is displayed. * @param text * Text that is displayed if image is not valid (NULL). */ WuQImageLabel::WuQImageLabel(const QImage* image, const QString& text) : QLabel() { updateImageText(image, text); setAlignment(Qt::AlignCenter); } /** * Constructor constructs an image label with either the given icon if the * icon is valid (not NULL). If the icon is invalid (NULL), the text * will be displayed. * * @param icon * Icon that is displayed. * @param text * Text that is displayed if icon is not valid (NULL). */ WuQImageLabel::WuQImageLabel(const QIcon* icon, const QString& text) : QLabel() { updateIconText(icon, text); setAlignment(Qt::AlignCenter); } /** * Constructor constructs an image label with either the given icon if the * icon is valid (not NULL). If the icon is invalid (NULL), the text * will be displayed. * * @param icon * Icon that is displayed. * @param text * Text that is displayed if icon is not valid (NULL). */ WuQImageLabel::WuQImageLabel(const QIcon& icon, const QString& text) : QLabel() { updateIconText(&icon, text); setAlignment(Qt::AlignCenter); } /** * Destructor. */ WuQImageLabel::~WuQImageLabel() { } /* * Update image label with either the given icon if the * icon is valid (not NULL). If the icon is invalid (NULL), the text * will be displayed. * * @param icon * Icon that is displayed. * @param text * Text that is displayed if icon is not valid (NULL). */ void WuQImageLabel::updateIconText(const QIcon* icon, const QString& text) { if (icon != NULL) { setPixmap(icon->pixmap(16)); } else { setText(text); } } /* * Update image label with either the given image if the * image is valid (not NULL). If the image is invalid (NULL), the text * will be displayed. * * @param image * Image that is displayed. * @param text * Text that is displayed if icon is not valid (NULL). */ void WuQImageLabel::updateImageText(const QImage* image, const QString& text) { if (image != NULL) { setPixmap(QPixmap::fromImage(*image)); } else { setPixmap(QPixmap()); } setText(text); } /** * Called when the mouse is moved. * * @param ev * The mouse event. */ void WuQImageLabel::mouseMoveEvent(QMouseEvent* ev) { if (ev->button() == Qt::NoButton) { if (ev->buttons() == Qt::LeftButton) { const int x = ev->x(); const int y = ev->y(); if (x < m_mouseMinX) m_mouseMinX = x; if (x > m_mouseMaxX) m_mouseMaxX = x; if (y < m_mouseMinY) m_mouseMinY = y; if (y > m_mouseMaxY) m_mouseMaxY = y; } } } /** * Called when the mouse button is pressed. * * @param ev * The mouse event. */ void WuQImageLabel::mousePressEvent(QMouseEvent* ev) { if (ev->button() == Qt::LeftButton) { m_mouseMinX = ev->x(); m_mouseMaxX = ev->x(); m_mouseMinY = ev->y(); m_mouseMaxY = ev->y(); } } /** * Called when the mouse button is released. * * @param ev * The mouse event. */ void WuQImageLabel::mouseReleaseEvent(QMouseEvent* ev) { if (ev->button() == Qt::LeftButton) { const int dx = std::abs(m_mouseMaxX - m_mouseMinX); const int dy = std::abs(m_mouseMaxY - m_mouseMinY); const int tolerance = 5; if ((dx < tolerance) && (dy < tolerance)) { emit clicked(); } } } workbench-1.1.1/src/GuiQt/WuQImageLabel.h000066400000000000000000000046601255417355300201240ustar00rootroot00000000000000#ifndef __WU_Q_IMAGE_LABEL_H__ #define __WU_Q_IMAGE_LABEL_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include class QIcon; class QImage; namespace caret { class WuQImageLabel : public QLabel { Q_OBJECT public: WuQImageLabel(); WuQImageLabel(const QImage* image, const QString& text); WuQImageLabel(const QIcon* icon, const QString& text); WuQImageLabel(const QIcon& icon, const QString& text); virtual ~WuQImageLabel(); void updateImageText(const QImage* icon, const QString& text); void updateIconText(const QIcon* icon, const QString& text); virtual void mouseMoveEvent(QMouseEvent* ev); virtual void mousePressEvent(QMouseEvent* ev); virtual void mouseReleaseEvent(QMouseEvent* ev); // ADD_NEW_METHODS_HERE signals: /** * Emitted if the mouse button is clicked over * this widget. */ void clicked(); private: WuQImageLabel(const WuQImageLabel&); WuQImageLabel& operator=(const WuQImageLabel&); int m_mouseMinX; int m_mouseMaxX; int m_mouseMinY; int m_mouseMaxY; // ADD_NEW_MEMBERS_HERE }; #ifdef __WU_Q_IMAGE_LABEL_DECLARE__ // #endif // __WU_Q_IMAGE_LABEL_DECLARE__ } // namespace #endif //__WU_Q_IMAGE_LABEL_H__ workbench-1.1.1/src/GuiQt/WuQListWidget.cxx000066400000000000000000000040771255417355300205760ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __WU_Q_LIST_WIDGET_DECLARE__ #include "WuQListWidget.h" #undef __WU_Q_LIST_WIDGET_DECLARE__ using namespace caret; /** * \class caret::WuQListWidget * \brief List widget that allows drag and drop to itself. * * QListWidget supports drag and drop but also requires * that one subclass QListWidget to receive the drop event. * This derivation of QListWidget receives the drop event * and emits a signal to indicate an item has been dropped. * * This class can be used instead of QListWidget so that * one can be notified, via a signal, that an item has been * dropped without having to subclass QListWidget. */ /** * Constructor. * @param parent * The optional parent widget. */ WuQListWidget::WuQListWidget(QWidget* parent) : QListWidget(parent) { /* * Enable dragging and dropping within this list widget. */ setSelectionMode(QListWidget::SingleSelection); setDragEnabled(true); setDragDropMode(QListWidget::InternalMove); } /** * Destructor. */ WuQListWidget::~WuQListWidget() { } /** * Receives drop events then emits the itemWasDropped signal. * @param e * The drop event. */ void WuQListWidget::dropEvent(QDropEvent* e) { QListWidget::dropEvent(e); emit itemWasDropped(); } workbench-1.1.1/src/GuiQt/WuQListWidget.h000066400000000000000000000032561255417355300202210ustar00rootroot00000000000000#ifndef __WU_Q_LIST_WIDGET__H_ #define __WU_Q_LIST_WIDGET__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include namespace caret { class WuQListWidget : public QListWidget { Q_OBJECT public: WuQListWidget(QWidget* parent = 0); virtual ~WuQListWidget(); signals: /** Is emitted when an item has been dropped. */ void itemWasDropped(); protected: virtual void dropEvent(QDropEvent*); private: WuQListWidget(const WuQListWidget&); WuQListWidget& operator=(const WuQListWidget&); public: // ADD_NEW_METHODS_HERE private: // ADD_NEW_MEMBERS_HERE }; #ifdef __WU_Q_LIST_WIDGET_DECLARE__ // #endif // __WU_Q_LIST_WIDGET_DECLARE__ } // namespace #endif //__WU_Q_LIST_WIDGET__H_ workbench-1.1.1/src/GuiQt/WuQMessageBox.cxx000066400000000000000000000247531255417355300205570ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretAssert.h" #include "WuQMessageBox.h" using namespace caret; /** * Constructor. * * @param parent * Parent on which message box is displayed. */ WuQMessageBox::WuQMessageBox(QWidget* parent) : QMessageBox(parent) { } /** * Destructor. */ WuQMessageBox::~WuQMessageBox() { } /** * Display a message box with the buttons Save, Discard, * and Cancel. Pressing the enter key is the equivalent of * pressing the Save button. * * @param parent * Parent on which message box is displayed. * @param text * Message that is displayed. * @param informativeText * Displayed below 'text' if this is not empty. * @return * QMessageBox::Save, QMessageBox::Discard, or QMessageBox::Cancel. */ QMessageBox::StandardButton WuQMessageBox::saveDiscardCancel(QWidget* parent, const QString& text, const QString& informativeText) { QMessageBox msgBox(parent); msgBox.setIcon(QMessageBox::Warning); msgBox.setWindowTitle(""); msgBox.setText(text); if (informativeText.isEmpty() == false) { msgBox.setInformativeText(informativeText); } msgBox.addButton(QMessageBox::Save); msgBox.addButton(QMessageBox::Discard); msgBox.addButton(QMessageBox::Cancel); msgBox.setDefaultButton(QMessageBox::Save); msgBox.setEscapeButton(QMessageBox::Cancel); QMessageBox::StandardButton buttonPressed = static_cast(msgBox.exec()); switch (buttonPressed) { case QMessageBox::Save: case QMessageBox::Discard: case QMessageBox::Cancel: break; default: CaretAssert(0); } return buttonPressed; } /** * Display a warning message box with Close and Cancel * buttons. Pressing the enter key is the equivalent * of pressing the Close button. * * @param parent * Parent on which message box is displayed. * @param text * Message that is displayed. * @param informativeText * Displayed below 'text' if this is not empty. * @return * true if the Close button was pressed else false * if the cancel button was pressed. */ bool WuQMessageBox::warningCloseCancel(QWidget* parent, const QString& text, const QString& informativeText) { QMessageBox msgBox(parent); msgBox.setIcon(QMessageBox::Warning); msgBox.setWindowTitle(""); msgBox.setText(text); if (informativeText.isEmpty() == false) { msgBox.setInformativeText(informativeText); } msgBox.addButton(QMessageBox::Close); msgBox.addButton(QMessageBox::Cancel); msgBox.setDefaultButton(QMessageBox::Close); msgBox.setEscapeButton(QMessageBox::Cancel); QMessageBox::StandardButton buttonPressed = static_cast(msgBox.exec()); bool closePressed = false; switch (buttonPressed) { case QMessageBox::Close: closePressed = true; break; case QMessageBox::Cancel: break; default: CaretAssert(0); break; } return closePressed; } /** * Display a warning message box with Ok and Cancel * buttons. Pressing the enter key is the equivalent * of pressing the Ok button. * * @param parent * Parent on which message box is displayed. * @param text * Message that is displayed. * @param informativeText * Displayed below 'text' if this is not empty. * @return * true if the Ok button was pressed else false * if the cancel button was pressed. */ bool WuQMessageBox::warningOkCancel(QWidget* parent, const QString& text, const QString& informativeText) { QMessageBox msgBox(parent); msgBox.setIcon(QMessageBox::Warning); msgBox.setWindowTitle(""); msgBox.setText(text); if (informativeText.isEmpty() == false) { msgBox.setInformativeText(informativeText); } msgBox.addButton(QMessageBox::Ok); msgBox.addButton(QMessageBox::Cancel); msgBox.setDefaultButton(QMessageBox::Ok); msgBox.setEscapeButton(QMessageBox::Cancel); QMessageBox::StandardButton buttonPressed = static_cast(msgBox.exec()); bool okPressed = false; switch (buttonPressed) { case QMessageBox::Ok: okPressed = true; break; case QMessageBox::Cancel: break; default: CaretAssert(0); break; } return okPressed; } /** * Display a warning message box with Yes and No * buttons. Pressing the enter key is the equivalent * of pressing the Yes button. * * @param parent * Parent on which message box is displayed. * @param text * Message that is displayed. * @param informativeText * Displayed below 'text' if this is not empty. * @return * true if the Yes button was pressed else false * if the No button was pressed. */ bool WuQMessageBox::warningYesNo(QWidget* parent, const QString& text, const QString& informativeText) { QMessageBox msgBox(parent); msgBox.setIcon(QMessageBox::Warning); msgBox.setWindowTitle(""); msgBox.setText(text); if (informativeText.isEmpty() == false) { msgBox.setInformativeText(informativeText); } msgBox.addButton(QMessageBox::Yes); msgBox.addButton(QMessageBox::No); msgBox.setDefaultButton(QMessageBox::Yes); msgBox.setEscapeButton(QMessageBox::No); QMessageBox::StandardButton buttonPressed = static_cast(msgBox.exec()); bool yesPressed = false; switch (buttonPressed) { case QMessageBox::Yes: yesPressed = true; break; case QMessageBox::No: break; default: CaretAssert(0); break; } return yesPressed; } /** * Display a warning message box with Ok and Cancel * buttons. Pressing the enter key is the equivalent * of pressing the Ok button. * * @param parent * Parent on which message box is displayed. * @param text * Message that is displayed. * @return * true if the Ok button was pressed else false * if the cancel button was pressed. */ bool WuQMessageBox::warningOkCancel(QWidget* parent, const QString& text) { return WuQMessageBox::warningOkCancel(parent, text, ""); } /** * Display a warning message box with Yes and No * buttons. Pressing the enter key is the equivalent * of pressing the Yes button. * * @param parent * Parent on which message box is displayed. * @param text * Message that is displayed. * @return * true if the Yes button was pressed else false * if the No button was pressed. */ bool WuQMessageBox::warningYesNo(QWidget* parent, const QString& text) { return WuQMessageBox::warningYesNo(parent, text, ""); } /** * Display a warning message box with Yes, No, and Cancel * buttons. Pressing the enter key is the equivalent * of pressing the Yes button. * * @param parent * Parent on which message box is displayed. * @param text * Message that is displayed. * @return * true if the Yes button was pressed else false * if the No button was pressed. */ WuQMessageBox::YesNoCancelResult WuQMessageBox::warningYesNoCancel(QWidget* parent, const QString& text, const QString& informativeText) { QMessageBox msgBox(parent); msgBox.setIcon(QMessageBox::Warning); msgBox.setWindowTitle(""); msgBox.setText(text); if (informativeText.isEmpty() == false) { msgBox.setInformativeText(informativeText); } msgBox.addButton(QMessageBox::Yes); msgBox.addButton(QMessageBox::No); msgBox.addButton(QMessageBox::Cancel); msgBox.setDefaultButton(QMessageBox::Yes); msgBox.setEscapeButton(QMessageBox::No); QMessageBox::StandardButton buttonPressed = static_cast(msgBox.exec()); YesNoCancelResult result = RESULT_CANCEL; switch (buttonPressed) { case QMessageBox::Yes: result = RESULT_YES; break; case QMessageBox::No: result = RESULT_NO; break; case QMessageBox::Cancel: result = RESULT_CANCEL; break; default: CaretAssert(0); break; } return result; } /** * Display an information message box with the * given text and an OK button. * * @param parent * Parent on which message box is displayed. * @param text * Message that is displayed. */ void WuQMessageBox::informationOk(QWidget* parent, const QString& text) { QMessageBox msgBox(parent); msgBox.setIcon(QMessageBox::Information); msgBox.setWindowTitle(""); msgBox.setText(text); msgBox.addButton(QMessageBox::Ok); msgBox.setDefaultButton(QMessageBox::Ok); msgBox.setEscapeButton(QMessageBox::Ok); msgBox.exec(); } /** * Display an error message box with the * given text and an OK button. * * @param parent * Parent on which message box is displayed. * @param text * Message that is displayed. */ void WuQMessageBox::errorOk(QWidget* parent, const QString& text) { QMessageBox msgBox(parent); msgBox.setIcon(QMessageBox::Critical); msgBox.setWindowTitle(""); msgBox.setText(text); msgBox.addButton(QMessageBox::Ok); msgBox.setDefaultButton(QMessageBox::Ok); msgBox.setEscapeButton(QMessageBox::Ok); msgBox.exec(); } workbench-1.1.1/src/GuiQt/WuQMessageBox.h000066400000000000000000000055751255417355300202050ustar00rootroot00000000000000 #ifndef __WU_QMESSAGE_BOX_H__ #define __WU_QMESSAGE_BOX_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include namespace caret { class WuQMessageBox : public QMessageBox { Q_OBJECT public: enum YesNoCancelResult { RESULT_YES, RESULT_NO, RESULT_CANCEL }; static void errorOk(QWidget* parent, const QString& text); static void informationOk(QWidget* parent, const QString& text); static bool warningOkCancel(QWidget* parent, const QString& text); static bool warningYesNo(QWidget* parent, const QString& text); static bool warningOkCancel(QWidget* parent, const QString& text, const QString& informativeText); static bool warningYesNo(QWidget* parent, const QString& text, const QString& informativeText); static YesNoCancelResult warningYesNoCancel(QWidget* parent, const QString& text, const QString& informativeText); static bool warningCloseCancel(QWidget* parent, const QString& text, const QString& informativeText); static QMessageBox::StandardButton saveDiscardCancel(QWidget* parent, const QString& text, const QString& informativeText); private: WuQMessageBox(QWidget* parent = 0); ~WuQMessageBox(); WuQMessageBox(const WuQMessageBox&); WuQMessageBox& operator=(const WuQMessageBox&); }; } #endif // __WU_QMESSAGE_DIALOG_H__ workbench-1.1.1/src/GuiQt/WuQSpinBoxGroup.cxx000066400000000000000000000075021255417355300211120ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __WU_Q_SPIN_BOX_GROUP_DECLARE__ #include "WuQSpinBoxGroup.h" #undef __WU_Q_SPIN_BOX_GROUP_DECLARE__ #include #include using namespace caret; /** * \class caret::WuQSpinBoxGroup * \brief Container that organizes a group of spin boxes. * * Container that organizes a group of spin boxes. */ /** * Constructor. * @param parent * Parent of this container. */ WuQSpinBoxGroup::WuQSpinBoxGroup(QObject* parent) : QObject(parent) { } /** * Destructor. */ WuQSpinBoxGroup::~WuQSpinBoxGroup() { for (std::vector::iterator iter = this->spinBoxReceivers.begin(); iter != this->spinBoxReceivers.end(); iter++) { delete *iter; } this->spinBoxReceivers.clear(); } /** * Add a spin box to this spin box group. * @param abstractSpinBox * Spin box that is added. */ void WuQSpinBoxGroup::addSpinBox(QAbstractSpinBox* abstractSpinBox) { SpinBoxReceiver* sbr = new SpinBoxReceiver(this, abstractSpinBox, this->spinBoxReceivers.size()); this->spinBoxReceivers.push_back(sbr); } /** * Called when double spin box value is changed. * @param doubleSpinBox * Double spin box that had its value changed. * @param d * New value. */ void WuQSpinBoxGroup::doubleSpinBoxChangeReceiver(QDoubleSpinBox* doubleSpinBox, const double d) { emit doubleSpinBoxValueChanged(doubleSpinBox, d); } /** * Called when spin box value is changed. * @param spinBox * Spin box that had its value changed. * @param i * New value. */ void WuQSpinBoxGroup::spinBoxChangeReceiver(QSpinBox* spinBox, const int i) { emit spinBoxValueChanged(spinBox, i); } //------------------------------------------------------------ SpinBoxReceiver::SpinBoxReceiver(WuQSpinBoxGroup* spinBoxGroup, QAbstractSpinBox* abstractSpinBox, const int spinBoxIndex) { this->spinBoxGroup = spinBoxGroup; this->spinBoxIndex = spinBoxIndex; this->spinBox = dynamic_cast(abstractSpinBox); if (this->spinBox != NULL) { QObject::connect(this->spinBox, SIGNAL(valueChanged(int)), this, SLOT(valueChangedSlot(int))); } this->doubleSpinBox = dynamic_cast(abstractSpinBox); if (this->doubleSpinBox != NULL) { QObject::connect(this->doubleSpinBox, SIGNAL(valueChanged(double)), this, SLOT(valueChangedSlot(double))); } } SpinBoxReceiver::~SpinBoxReceiver() { } void SpinBoxReceiver::valueChangedSlot(int i) { this->spinBoxGroup->spinBoxChangeReceiver(this->spinBox, i); } void SpinBoxReceiver::valueChangedSlot(double d) { this->spinBoxGroup->doubleSpinBoxChangeReceiver(this->doubleSpinBox, d); } workbench-1.1.1/src/GuiQt/WuQSpinBoxGroup.h000066400000000000000000000067031255417355300205410ustar00rootroot00000000000000#ifndef __WU_Q_SPIN_BOX_GROUP__H_ #define __WU_Q_SPIN_BOX_GROUP__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include class QAbstractSpinBox; class QDoubleSpinBox; class QSpinBox; namespace caret { class AString; class SpinBoxReceiver; class WuQSpinBoxGroup : public QObject { Q_OBJECT public: WuQSpinBoxGroup(QObject* parent); virtual ~WuQSpinBoxGroup(); void addSpinBox(QAbstractSpinBox* abstractSpinBox); signals: /** * Emitted when a QSpinBox has its value changed. * @param spinBox * Spin box that had its value changed. * @param i * New value from spin box. */ void spinBoxValueChanged(QSpinBox* spinBox, const int i); /** * Emitted when a QDoubleSpinBox has its value changed. * @param doubleSpinBox * Double spin box that had its value changed. * @param d * New value from spin box. */ void doubleSpinBoxValueChanged(QDoubleSpinBox* doubleSpinBox, const double d); //void valueChanged(const AString& text); private: WuQSpinBoxGroup(const WuQSpinBoxGroup&); WuQSpinBoxGroup& operator=(const WuQSpinBoxGroup&); std::vector spinBoxReceivers; void doubleSpinBoxChangeReceiver(QDoubleSpinBox* doubleSpinBox, const double d); void spinBoxChangeReceiver(QSpinBox* spinBox, const int i); friend class SpinBoxReceiver; }; class SpinBoxReceiver : public QObject { Q_OBJECT public: SpinBoxReceiver(WuQSpinBoxGroup* spinBoxGroup, QAbstractSpinBox* abstractSpinBox, const int indx); ~SpinBoxReceiver(); private slots: void valueChangedSlot(int i); void valueChangedSlot(double d); private: SpinBoxReceiver(const SpinBoxReceiver&); SpinBoxReceiver& operator=(const SpinBoxReceiver&); WuQSpinBoxGroup* spinBoxGroup; QSpinBox* spinBox; QDoubleSpinBox* doubleSpinBox; int spinBoxIndex; }; #ifdef __WU_Q_SPIN_BOX_GROUP_DECLARE__ // #endif // __WU_Q_SPIN_BOX_GROUP_DECLARE__ } // namespace #endif //__WU_Q_SPIN_BOX_GROUP__H_ workbench-1.1.1/src/GuiQt/WuQSpinBoxOddValue.cxx000066400000000000000000000175351255417355300215300ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ /* Qt License is included since the API is copied from QSpinBox */ /**************************************************************************** ** ** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the QtGui module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Digia. For licensing terms and ** conditions see http://qt.digia.com/licensing. For further information ** use the contact form at http://qt.digia.com/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Digia gives you certain additional ** rights. These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #define __WU_Q_SPIN_BOX_ODD_VALUE_DECLARE__ #include "WuQSpinBoxOddValue.h" #undef __WU_Q_SPIN_BOX_ODD_VALUE_DECLARE__ #include #include #include "CaretAssert.h" #include "CaretLogger.h" #include "MathFunctions.h" using namespace caret; /** * \class caret::WuQSpinBoxOddValue * \brief Spin box that allows only odd values * \ingroup GuiQt * * Creates a spin box that allows only odd values. * Any attempts to set the value to an event value * or any attempts to set other parameters that * would cause an even value are ignored. To * ensure only odd values, enter of text is disabled. */ /** * Constructor. */ WuQSpinBoxOddValue::WuQSpinBoxOddValue(QObject* parent) : WuQWidget(parent) { m_spinBox = new WuQSpinBoxOddValueSpinBox(); m_spinBox->setMinimum(-99999999); m_spinBox->setMaximum( 99999999); m_spinBox->setSingleStep(2); } /** * Destructor. */ WuQSpinBoxOddValue::~WuQSpinBoxOddValue() { } /** * Return the enapsulated widget so that it can be added * to a layout */ QWidget* WuQSpinBoxOddValue::getWidget() { return m_spinBox; } /** * @return The minimum value allowed in the spin box. */ int WuQSpinBoxOddValue::minimum() const { return m_spinBox->minimum(); } /** * @return The maximum value allowed in the spin box. */ int WuQSpinBoxOddValue::maximum() const { return m_spinBox->maximum(); } /** * Set the minimum value. If the value is even, no action is taken. * * @param min * New minimum value. */ void WuQSpinBoxOddValue::setMinimum(int min) { if (MathFunctions::isEvenNumber(min)) { const AString msg = "Value passed to WuQSpinBoxOddValue::setMinimum MUST BE AN ODD NUMBER"; CaretAssertMessage(0, msg); CaretLogSevere(msg); return; } m_spinBox->setMinimum(min); } /** * Set the maximum value. If the value is even, no action is taken. * * @param min * New maximum value. */ void WuQSpinBoxOddValue::setMaximum(int max) { if (MathFunctions::isEvenNumber(max)) { const AString msg = "Value passed to WuQSpinBoxOddValue::setMaximum MUST BE AN ODD NUMBER"; CaretAssertMessage(0, msg); CaretLogSevere(msg); return; } m_spinBox->setMaximum(max); } /** * Set the range of values. * * @param minimum * New minimum value. If the value is even, no action is taken. * @param maximum * New maximum value. If the value is even, no action is taken. */ void WuQSpinBoxOddValue::setRange(int minimum, int maximum) { setMinimum(minimum); setMaximum(maximum); } /** * @return The step value when the user presses the up or down arrow. */ int WuQSpinBoxOddValue::singleStep() const { return m_spinBox->singleStep(); } /** * Set the step value for when the up or down arrow is pressed. * If the value is odd, no action is taken since the spin box * must change value by 2 so that the value remains odd. * * @param val * New step value. */ void WuQSpinBoxOddValue::setSingleStep(int val) { if (MathFunctions::isOddNumber(val)) { const AString msg = ("Value passed to WuQSpinBoxOddValue::setMaximum MUST BE AN EVEN NUMBER " "so that incrementing and decrementing preserves an odd value in the " "spin box."); CaretAssertMessage(0, msg); CaretLogSevere(msg); return; } m_spinBox->setSingleStep(val); } /** * @return The value in the spin box. */ int WuQSpinBoxOddValue::value() const { return m_spinBox->value(); } /** * Set the value. If the value is even, no action is taken. * * @param val * New value. */ void WuQSpinBoxOddValue::setValue(int val) { if (MathFunctions::isEvenNumber(val)) { const AString msg = "Value passed to WuQSpinBoxOddValue::setValue MUST BE AN ODD NUMBER"; CaretAssertMessage(0, msg); CaretLogSevere(msg); return; } m_spinBox->setValue(val); } /* -----------------------------------------------------------------------*/ /** * \class caret::WuQSpinBoxOddValueSpinBox * \brief Spin Box used by WuQSpinBoxOddValue * \ingroup GuiQt * * Spin Box used by WuQSpinBoxOddValue. * This is intended only for use by WuQSpinBoxOddValue * and it is simply a QSpinBox with its line edit * disabled. Disabling the line edit is a protected * functions in QSpinBox and that is the only reason * this class exists. */ /** * Constructor. * * @param parent * Optional parent widget. */ WuQSpinBoxOddValueSpinBox::WuQSpinBoxOddValueSpinBox(QWidget* parent) : QSpinBox(parent) { lineEdit()->setEnabled(false); } /** * Destructor. */ WuQSpinBoxOddValueSpinBox::~WuQSpinBoxOddValueSpinBox() { } int WuQSpinBoxOddValueSpinBox::valueFromText(const QString& text) const { bool validFlag = false; int value = text.toInt(&validFlag); if (MathFunctions::isEvenNumber(value)) { if (value > 0) { value = value - 1; } else if (value < 0) { value = value + 1; } } return value; } workbench-1.1.1/src/GuiQt/WuQSpinBoxOddValue.h000066400000000000000000000106031255417355300211420ustar00rootroot00000000000000#ifndef __WU_Q_SPIN_BOX_ODD_VALUE_H__ #define __WU_Q_SPIN_BOX_ODD_VALUE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ /* Qt License is included since the API is copied from QSpinBox */ /**************************************************************************** ** ** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the QtGui module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Digia. For licensing terms and ** conditions see http://qt.digia.com/licensing. For further information ** use the contact form at http://qt.digia.com/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Digia gives you certain additional ** rights. These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "WuQWidget.h" #include namespace caret { class WuQSpinBoxOddValueSpinBox : public QSpinBox { public: WuQSpinBoxOddValueSpinBox(QWidget* parent = 0); virtual ~WuQSpinBoxOddValueSpinBox(); protected: virtual int valueFromText(const QString& text) const; }; class WuQSpinBoxOddValue : public WuQWidget { Q_OBJECT public: WuQSpinBoxOddValue(QObject* parent); virtual ~WuQSpinBoxOddValue(); virtual QWidget* getWidget(); int minimum() const; int maximum() const; void setMinimum(int min); void setMaximum(int max); void setRange(int minimum, int maximum); int singleStep() const; void setSingleStep(int val); int value() const; // ADD_NEW_METHODS_HERE signals: void valueChanged(int i); public slots: void setValue(int val); private: WuQSpinBoxOddValue(const WuQSpinBoxOddValue&); WuQSpinBoxOddValue& operator=(const WuQSpinBoxOddValue&); QSpinBox* m_spinBox; // ADD_NEW_MEMBERS_HERE }; #ifdef __WU_Q_SPIN_BOX_ODD_VALUE_DECLARE__ // #endif // __WU_Q_SPIN_BOX_ODD_VALUE_DECLARE__ } // namespace #endif //__WU_Q_SPIN_BOX_ODD_VALUE_H__ workbench-1.1.1/src/GuiQt/WuQTabWidget.cxx000066400000000000000000000162631255417355300203710ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #define __WU_Q_TAB_WIDGET_DECLARE__ #include "WuQTabWidget.h" #undef __WU_Q_TAB_WIDGET_DECLARE__ #include "SceneClass.h" using namespace caret; /** * \class caret::WuQTabWidget * \brief Replaces QTabWidget and allows the tab to be aligned. * * On some platforms, the tabs are centered, and when the tab widget * is wide an in a scrollable area it is sometimes difficult to find the * tabs. So, this tab widget allows the tab bar placed at a defined * alignment. */ /** * Constructor. * @param alignment * Aligment of the tab. * @param parent * Parent of this instance. */ WuQTabWidget::WuQTabWidget(const TabAlignment alignment, QObject* parent) : WuQWidget(parent) { m_tabBar = new QTabBar(); QObject::connect(m_tabBar, SIGNAL(currentChanged(int)), this, SLOT(tabBarCurrentIndexChanged(int))); m_stackedWidget = new QStackedWidget(); QHBoxLayout* tabBarLayout = new QHBoxLayout(); Qt::Alignment tabBarAlignment = Qt::AlignLeft; switch (alignment) { case TAB_ALIGN_CENTER: tabBarAlignment = Qt::AlignHCenter; break; case TAB_ALIGN_LEFT: tabBarAlignment = Qt::AlignLeft; break; case TAB_ALIGN_RIGHT: tabBarAlignment = Qt::AlignRight; break; } tabBarLayout->addWidget(m_tabBar, 100, tabBarAlignment); tabBarLayout->setMargin(0); QGroupBox* stackedWidgetGroupBox = new QGroupBox(); QVBoxLayout* groupBoxLayout = new QVBoxLayout(stackedWidgetGroupBox); groupBoxLayout->addWidget(m_stackedWidget); m_widget = new QWidget(); QVBoxLayout* verticalLayout = new QVBoxLayout(m_widget); QMargins margins = verticalLayout->contentsMargins(); margins.setLeft(0); margins.setRight(0); verticalLayout->setContentsMargins(margins); verticalLayout->setSpacing(3); verticalLayout->addLayout(tabBarLayout, 0); verticalLayout->addWidget(stackedWidgetGroupBox, 0, tabBarAlignment); verticalLayout->addStretch(); } /** * Destructor. */ WuQTabWidget::~WuQTabWidget() { } /** * @return The embedded widget. */ QWidget* WuQTabWidget::getWidget() { return m_widget; } /** * Adds a tab with the given page and label to the tab widget, and returns * the index of the tab in the tab bar. If the tab's label contains an * ampersand, the letter following the ampersand is used as a shortcut * for the tab, e.g. if the label is "Bro&wse" then Alt+W becomes a * shortcut which will move the focus to this tab. * * @param page * New page that is added (must not be NULL). * @param label * Label displayed in the page's tab. */ void WuQTabWidget::addTab(QWidget* page, const QString& label) { m_tabBar->addTab(label); m_stackedWidget->addWidget(page); } /** * Called when the tab bar changes the current widget. * @param index * Index of selected widget. */ void WuQTabWidget::tabBarCurrentIndexChanged(int index) { setCurrentIndex(index); emit currentChanged(index); } /** * @return Returns the index position of the current tab page. * The current index is -1 if there is no current widget. */ int WuQTabWidget::currentIndex() const { return m_tabBar->currentIndex(); } /** * @return Returns a pointer to the page currently being displayed by the * tab dialog. The tab dialog does its best to make sure that this value * is never 0 (but if you try hard enough, it can be). */ QWidget* WuQTabWidget::currentWidget() const { return m_stackedWidget->currentWidget(); } //signals: //void currentChanged(int index); /** * Makes widget at the given index the current widget. The widget used must * be a page in this tab widget. */ void WuQTabWidget::setCurrentIndex(int index) { m_tabBar->blockSignals(true); m_tabBar->setCurrentIndex(index); m_tabBar->blockSignals(false); m_stackedWidget->setCurrentIndex(index); } /** * Makes widget the current widget. The widget used must be a page in * this tab widget. */ void WuQTabWidget::setCurrentWidget(QWidget* widget) { const int indx = m_stackedWidget->indexOf(widget); if (indx > 0) { m_tabBar->blockSignals(true); m_tabBar->setCurrentIndex(indx); m_tabBar->blockSignals(false); } } /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ SceneClass* WuQTabWidget::saveToScene(const SceneAttributes* /*sceneAttributes*/, const AString& instanceName) { SceneClass* sceneClass = new SceneClass(instanceName, "WuQTabWidget", 1); AString tabName; const int32_t selectedTabIndex = currentIndex(); if (selectedTabIndex >= 0) { tabName = m_tabBar->tabText(selectedTabIndex); } sceneClass->addString("selectedTabName", tabName); return sceneClass; } /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * SceneClass containing the state that was previously * saved and should be restored. */ void WuQTabWidget::restoreFromScene(const SceneAttributes* /*sceneAttributes*/, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } const AString tabName = sceneClass->getStringValue("selectedTabName"); const int32_t numTabs = m_tabBar->count(); for (int32_t i = 0; i < numTabs; i++) { if (m_tabBar->tabText(i) == tabName) { setCurrentIndex(i); break; } } } workbench-1.1.1/src/GuiQt/WuQTabWidget.h000066400000000000000000000051401255417355300200060ustar00rootroot00000000000000#ifndef __WU_Q_TAB_WIDGET__H_ #define __WU_Q_TAB_WIDGET__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "WuQWidget.h" #include "SceneableInterface.h" class QTabBar; class QStackedWidget; namespace caret { class WuQTabWidget : public WuQWidget, public SceneableInterface { Q_OBJECT public: enum TabAlignment { TAB_ALIGN_LEFT, TAB_ALIGN_CENTER, TAB_ALIGN_RIGHT }; WuQTabWidget(const TabAlignment alignment, QObject* parent); virtual ~WuQTabWidget(); QWidget* getWidget(); void addTab(QWidget* page, const QString& label); int currentIndex() const; QWidget* currentWidget() const; virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName); virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); signals: void currentChanged(int index); public slots: void setCurrentIndex(int index); void setCurrentWidget(QWidget* widget); private slots: void tabBarCurrentIndexChanged(int index); private: WuQTabWidget(const WuQTabWidget&); WuQTabWidget& operator=(const WuQTabWidget&); QTabBar* m_tabBar; QStackedWidget* m_stackedWidget; QWidget* m_widget; // ADD_NEW_MEMBERS_HERE }; #ifdef __WU_Q_TAB_WIDGET_DECLARE__ // #endif // __WU_Q_TAB_WIDGET_DECLARE__ } // namespace #endif //__WU_Q_TAB_WIDGET__H_ workbench-1.1.1/src/GuiQt/WuQTimedMessageDisplay.cxx000066400000000000000000000074071255417355300224140ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #define __WU_Q_TIMED_MESSAGE_DISPLAY_DECLARE__ #include "WuQTimedMessageDisplay.h" #undef __WU_Q_TIMED_MESSAGE_DISPLAY_DECLARE__ #include "AString.h" #include "CaretAssert.h" using namespace caret; /** * \class caret::WuQTimedMessageDisplay * \brief Timed message display. * * Displays a buttonless dialog for a period of time. * * Use the static show() method to display a timed message display. */ /** * Constructor. * * Display a message containing the given message for the given amount * of time. * * @param parent * Parent on which message is displayed. * @param displayForSeconds * Message is displayed for this amount of time, in milliseconds. * @param message * Message that is displayed. */ WuQTimedMessageDisplay::WuQTimedMessageDisplay(QWidget* parent, const float displayForSeconds, const QString& message) : QDialog(parent, Qt::FramelessWindowHint) { CaretAssertMessage(displayForSeconds > 0.0, "Display time must be greater than zero."); /* * Modal so it blocks until done. */ setModal(true); /* * Delete self when done. */ this->setAttribute(Qt::WA_DeleteOnClose, true); /* * Put message in window. */ QLabel* label = new QLabel(message); label->setFrameStyle(QFrame::Panel | QFrame::Plain); label->setLineWidth(2); QVBoxLayout* layout = new QVBoxLayout(this); layout->setContentsMargins(3, 3, 3, 3); layout->addWidget(label); /* * Setup a timer to call the accept(() slot when done. * accept() will close the dialog. */ QTimer* timer = new QTimer(this); timer->setSingleShot(true); QObject::connect(timer, SIGNAL(timeout()), this, SLOT(accept())); timer->start(displayForSeconds * 1000.0); /* * Display directly over the parent */ QPoint pos = parent->mapToGlobal(parent->pos()); move(pos); } /** * Display a message containing the given message for the given amount * of time. This method will not return until the message window closes. * * @param parent * Parent on which message is displayed. * @param displayForSeconds * Message is displayed for this amount of time, in milliseconds. * @param message * Message that is displayed. */ void WuQTimedMessageDisplay::show(QWidget* parent, const float displayForSeconds, const QString& message) { WuQTimedMessageDisplay* md = new WuQTimedMessageDisplay(parent, displayForSeconds, message); md->exec(); } /** * Destructor. */ WuQTimedMessageDisplay::~WuQTimedMessageDisplay() { } workbench-1.1.1/src/GuiQt/WuQTimedMessageDisplay.h000066400000000000000000000035771255417355300220450ustar00rootroot00000000000000#ifndef __WU_Q_TIMED_MESSAGE_DISPLAY_H__ #define __WU_Q_TIMED_MESSAGE_DISPLAY_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include namespace caret { class WuQTimedMessageDisplay : public QDialog { Q_OBJECT private: WuQTimedMessageDisplay(QWidget* parent, const float displayForSeconds, const QString& message); public: static void show(QWidget* parent, const float displayForSeconds, const QString& message); virtual ~WuQTimedMessageDisplay(); private: WuQTimedMessageDisplay(const WuQTimedMessageDisplay&); WuQTimedMessageDisplay& operator=(const WuQTimedMessageDisplay&); public: // ADD_NEW_METHODS_HERE private: // ADD_NEW_MEMBERS_HERE }; #ifdef __WU_Q_TIMED_MESSAGE_DISPLAY_DECLARE__ // #endif // __WU_Q_TIMED_MESSAGE_DISPLAY_DECLARE__ } // namespace #endif //__WU_Q_TIMED_MESSAGE_DISPLAY_H__ workbench-1.1.1/src/GuiQt/WuQTreeWidget.cxx000066400000000000000000000076631255417355300205660ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #define __WU_Q_TREE_WIDGET_DECLARE__ #include "WuQTreeWidget.h" #undef __WU_Q_TREE_WIDGET_DECLARE__ using namespace caret; /** * \class caret::WuQTreeWidget * \brief Tree Widget that can size to its content's size. * * A QTreeWidget normally gets a size hint of (256, 256) and * does not increase in size when the scroll bars are turned * off. If fitToContentSizeWithoutScrollBars() is called * this tree widget will resize to the size of its content. */ /** * Constructor. */ WuQTreeWidget::WuQTreeWidget(QWidget* parent) : QTreeWidget(parent) { this->setHeaderHidden(true); this->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); this->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); this->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); QObject::connect(this, SIGNAL(itemCollapsed(QTreeWidgetItem*)), this, SLOT(itemExpandedOrCollapsed(QTreeWidgetItem*))); QObject::connect(this, SIGNAL(itemExpanded(QTreeWidgetItem*)), this, SLOT(itemExpandedOrCollapsed(QTreeWidgetItem*))); } /** * Destructor. */ WuQTreeWidget::~WuQTreeWidget() { } /** * Called when an item is expanded or collapsed to * update the fixed size. */ void WuQTreeWidget::itemExpandedOrCollapsed(QTreeWidgetItem*) { this->resizeToFitContent(); } /** * Size the widget so that it is the size of * its content's without any scroll bars. */ void WuQTreeWidget::resizeToFitContent() { const int height = this->calculateHeight() + 6; this->setFixedHeight(height); int totalColumnWidths = 20; // space for arrows const int numCols = this->columnCount(); for (int i = 0; i < numCols; i++) { totalColumnWidths += this->sizeHintForColumn(i); } if (totalColumnWidths < 256) { totalColumnWidths = 256; } this->setFixedWidth(totalColumnWidths); } /* * From http://qt-project.org/forums/viewthread/2533 */ int WuQTreeWidget::calculateHeight() const { int h = 0; int topLevelCount = this->topLevelItemCount(); for (int i = 0;i < topLevelCount;i++) { QTreeWidgetItem * item = topLevelItem(i); h += this->calculateHeightRec(item); } if (h != 0) { h += header()->sizeHint().height(); } return h; } /* * */ int WuQTreeWidget::calculateHeightRec(QTreeWidgetItem * item) const { if (item == NULL) return 0; QModelIndex index = indexFromItem(item); if (item->isExpanded() == false) { int h = rowHeight(index); return h; } /* int h = 0; for (int j = 0; j < item->columnCount(); j++) { const int itemHeight = item->sizeHint(j).height() + 2; if (itemHeight > h) { h = itemHeight; } } */ //int h = item->sizeHint(0).height() + 2; //std::cout << "EXPANDED h=" << h << " rowSizeHint=" << indexRowSizeHint(index); int h = indexRowSizeHint(index); int childCount = item->childCount(); for (int i = 0; i < childCount;i++) { h += this->calculateHeightRec(item->child(i)); } return h; } workbench-1.1.1/src/GuiQt/WuQTreeWidget.h000066400000000000000000000032051255417355300201770ustar00rootroot00000000000000#ifndef __WU_Q_TREE_WIDGET__H_ #define __WU_Q_TREE_WIDGET__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include namespace caret { class WuQTreeWidget : public QTreeWidget { Q_OBJECT public: WuQTreeWidget(QWidget* parent = 0); virtual ~WuQTreeWidget(); void resizeToFitContent(); private slots: void itemExpandedOrCollapsed(QTreeWidgetItem*); private: WuQTreeWidget(const WuQTreeWidget&); WuQTreeWidget& operator=(const WuQTreeWidget&); int calculateHeight() const; int calculateHeightRec(QTreeWidgetItem* treeItem) const; }; #ifdef __WU_Q_TREE_WIDGET_DECLARE__ // #endif // __WU_Q_TREE_WIDGET_DECLARE__ } // namespace #endif //__WU_Q_TREE_WIDGET__H_ workbench-1.1.1/src/GuiQt/WuQTrueFalseComboBox.cxx000066400000000000000000000060661255417355300220420ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __WU_Q_TRUE_FALSE_COMBO_BOX_DECLARE__ #include "WuQTrueFalseComboBox.h" #undef __WU_Q_TRUE_FALSE_COMBO_BOX_DECLARE__ using namespace caret; /** * \class caret::WuQTrueFalseComboBox * \brief Combo box for true/false type values. */ /** * Constructor. */ WuQTrueFalseComboBox::WuQTrueFalseComboBox(const QString& trueText, const QString& falseText, QObject* parent) : WuQWidget(parent) { this->createComboBox(trueText, falseText); } /** * Constructor. */ WuQTrueFalseComboBox::WuQTrueFalseComboBox(QObject* parent) : WuQWidget(parent) { this->createComboBox("true", "false"); } /** * Destructor. */ WuQTrueFalseComboBox::~WuQTrueFalseComboBox() { } /** * Create the combo box. * @param trueText * Text for 'true' value. * @param falseText * Text for 'false' value. */ void WuQTrueFalseComboBox::createComboBox(const QString& trueText, const QString& falseText) { this->comboBox = new QComboBox(); this->comboBox->addItem(trueText); this->comboBox->addItem(falseText); QObject::connect(this->comboBox, SIGNAL(activated(int)), this, SLOT(comboBoxValueChanged(int))); } /** * Called when value is changed. */ void WuQTrueFalseComboBox::comboBoxValueChanged(int indx) { bool boolValue = (indx == 0); emit statusChanged(boolValue); } /** * @return The embedded widget. */ QWidget* WuQTrueFalseComboBox::getWidget() { return this->comboBox; } /** * @return If true is selected. */ bool WuQTrueFalseComboBox::isTrue() { const int indx = this->comboBox->currentIndex(); const bool boolValue = (indx == 0); return boolValue; } /** * @return If false is selected. */ bool WuQTrueFalseComboBox::isFalse() { const int indx = this->comboBox->currentIndex(); const bool boolValue = (indx == 1); return boolValue; } /** * Set the new true/false status. * @parma status * New status. */ void WuQTrueFalseComboBox::setStatus(const bool status) { if (status) { this->comboBox->setCurrentIndex(0); } else { this->comboBox->setCurrentIndex(1); } } workbench-1.1.1/src/GuiQt/WuQTrueFalseComboBox.h000066400000000000000000000041761255417355300214670ustar00rootroot00000000000000#ifndef __WU_Q_TRUE_FALSE_COMBO_BOX__H_ #define __WU_Q_TRUE_FALSE_COMBO_BOX__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "WuQWidget.h" namespace caret { class WuQTrueFalseComboBox : public WuQWidget { Q_OBJECT public: WuQTrueFalseComboBox(const QString& trueText, const QString& falseText, QObject* parent); WuQTrueFalseComboBox(QObject* parent); virtual ~WuQTrueFalseComboBox(); QWidget* getWidget(); bool isTrue(); bool isFalse(); void setStatus(const bool status); signals: /** Emitted when user makes a selection */ void statusChanged(bool); private slots: void comboBoxValueChanged(int indx); private: WuQTrueFalseComboBox(const WuQTrueFalseComboBox&); WuQTrueFalseComboBox& operator=(const WuQTrueFalseComboBox&); void createComboBox(const QString& trueText, const QString& falseText); QComboBox* comboBox; }; #ifdef __WU_Q_TRUE_FALSE_COMBO_BOX_DECLARE__ // #endif // __WU_Q_TRUE_FALSE_COMBO_BOX_DECLARE__ } // namespace #endif //__WU_Q_TRUE_FALSE_COMBO_BOX__H_ workbench-1.1.1/src/GuiQt/WuQWebView.cxx000066400000000000000000000026721255417355300200660ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "WuQWebView.h" #include #include #include WuQWebView::WuQWebView(QWidget *parent) : QWebView(parent) { connect(page()->networkAccessManager(), SIGNAL(sslErrors(QNetworkReply*, const QList & )), this, SLOT(handleSslErrors(QNetworkReply*, const QList & ))); } void WuQWebView::handleSslErrors(QNetworkReply* reply, const QList &/*errors*/) { /*qDebug() << "handleSslErrors: "; foreach (QSslError e, errors) { qDebug() << "ssl error: " << e; }*/ reply->ignoreSslErrors(); } workbench-1.1.1/src/GuiQt/WuQWebView.h000066400000000000000000000021641255417355300175070ustar00rootroot00000000000000#ifndef WUQWEBVIEW_H #define WUQWEBVIEW_H /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include class WuQWebView : public QWebView { Q_OBJECT public: WuQWebView(QWidget *parent = 0); private slots: void handleSslErrors(QNetworkReply* reply, const QList &/*errors*/); }; #endif // WUQWEBVIEW_H workbench-1.1.1/src/GuiQt/WuQWidget.cxx000066400000000000000000000056441255417355300177430ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __WU_Q_WIDGET_DECLARE__ #include "WuQWidget.h" #undef __WU_Q_WIDGET_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::WuQWidget * \brief Class for extending Qt GUI Widgets through encapsulation * * It is often desirable to extend a Qt GUI widget but one does * not want to directly subclass a QWidget. For example, a * QComboBox is a useful control for selection of an enumerated * type. It is desirable to allow the user of the class to only * access the combo box by setting and getting the enumerated type. * However, if one directly subclasses QComboBox, it allows access * to many methods including those that get and set via an index * or name which could allow the insertion of invalid values. So, * by encapsualting, one can provide accessor methods using the * enumerated types. Protected inheritance is not a viable solution * because it prevents the connection of signals and slots. * * This class is not derived from QWidget since that would require. * the additional of a layout to hold the actual widget. Instead, * this class is derived from QObject so that there are no 'widget' * methods available to the user and so that the signal and slot * mechanism is available. Subclasses can define, signals and slots * that are appropriate, such as those that use an enumerated type * as a parameter. * * The parent of derived classes must be passed to the contructor. * The parent is typically some deriviative of QWidget such as * QDialog. By using a parent, Qt will destroy an instance of * this class when the parent is destroyed. * * An instance of this class is never added to a layout. Instead, * deriving classes implement the getWidget() method to provide * the enapsulated widget for insertion into a layout. * * Since the encapsulated QWidget is added to a layout, never * delete the encapsulated widget since it will have a Qt parent * which will destroy it. */ /** * Constructor. */ WuQWidget::WuQWidget(QObject* parent) : QObject(parent) { CaretAssert(parent); } /** * Destructor. */ WuQWidget::~WuQWidget() { } workbench-1.1.1/src/GuiQt/WuQWidget.h000066400000000000000000000026711255417355300173650ustar00rootroot00000000000000#ifndef __WU_Q_WIDGET__H_ #define __WU_Q_WIDGET__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include class QWidget; namespace caret { class WuQWidget : public QObject { Q_OBJECT public: WuQWidget(QObject* parent); virtual ~WuQWidget(); virtual QWidget* getWidget() = 0; private: WuQWidget(const WuQWidget&); WuQWidget& operator=(const WuQWidget&); public: private: }; #ifdef __WU_Q_WIDGET_DECLARE__ // #endif // __WU_Q_WIDGET_DECLARE__ } // namespace #endif //__WU_Q_WIDGET__H_ workbench-1.1.1/src/GuiQt/WuQWidgetObjectGroup.cxx000066400000000000000000000107441255417355300221040ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include "WuQWidgetObjectGroup.h" using namespace caret; /** * constructor. * WuQWidgetObjectGroup::WuQWidgetObjectGroup(QWidget* parent) : QObject(parent) { } */ /** * constructor. */ WuQWidgetObjectGroup::WuQWidgetObjectGroup(QObject* parent) : QObject(parent) { } /** * destructor. */ WuQWidgetObjectGroup::~WuQWidgetObjectGroup() { this->clear(); } /** * Remove all objects from this group. The objects are NOT deleted * since widgets are typically 'owned' by their parents. */ void WuQWidgetObjectGroup::clear() { this->objects.clear(); } /** * add a QObject (QWidget is descendent of QObject) to the group. */ void WuQWidgetObjectGroup::add(QObject* o) { this->objects.push_back(o); } /** * enable the group's widgets. */ void WuQWidgetObjectGroup::setEnabled(bool enable) { for (int i = 0; i < this->objects.size(); i++) { QWidget* widget = qobject_cast(this->objects[i]); if (widget != NULL) { widget->setEnabled(enable); } } } /** * disable the group's widgets. */ void WuQWidgetObjectGroup::setDisabled(bool disable) { for (int i = 0; i < this->objects.size(); i++) { QWidget* widget = qobject_cast(this->objects[i]); if (widget != NULL) { widget->setDisabled(disable); } } } /** * @return true if any of the widgets are visible. */ bool WuQWidgetObjectGroup::isVisible() const { for (int i = 0; i < this->objects.size(); i++) { QWidget* widget = qobject_cast(this->objects[i]); if (widget != NULL) { if (widget->isVisible()) { return true; } } } return false; } /** * make the group's widgets visible. */ void WuQWidgetObjectGroup::setVisible(bool makeVisible) { for (int i = 0; i < this->objects.size(); i++) { QWidget* widget = qobject_cast(this->objects[i]); if (widget != NULL) { widget->setVisible(makeVisible); } } } /** * make the group's widgets hidden. */ void WuQWidgetObjectGroup::setHidden(bool hidden) { setVisible(! hidden); } /** * block signals. */ void WuQWidgetObjectGroup::blockAllSignals(bool blockTheSignals) { for (int i = 0; i < this->objects.size(); i++) { this->objects.at(i)->blockSignals(blockTheSignals); } } /** * set status of all checkboxes. */ void WuQWidgetObjectGroup::setAllCheckBoxesChecked(const bool b) { for (int i = 0; i < this->objects.size(); i++) { QCheckBox* cb = qobject_cast(this->objects.at(i)); if (cb != NULL) { cb->setChecked(b); } } } /** * make all of the widgets in the group the same size as size hint * of largest widget. */ void WuQWidgetObjectGroup::resizeAllToLargestSizeHint() { int largestWidth = -1; int largestHeight = -1; for (int i = 0; i < this->objects.size(); i++) { QWidget* widget = qobject_cast(this->objects[i]); if (widget == NULL) { continue; } const QSize size = widget->sizeHint(); if (size.width() > largestWidth) { largestWidth = size.width(); } if (size.height() > largestHeight) { largestHeight = size.height(); } } if ((largestWidth > 0) && (largestHeight > 0)) { QSize newSize(largestWidth, largestHeight); for (int i = 0; i < this->objects.size(); i++) { QWidget* widget = qobject_cast(this->objects[i]); if (widget != NULL) { widget->setFixedSize(newSize); } } } } workbench-1.1.1/src/GuiQt/WuQWidgetObjectGroup.h000066400000000000000000000042171255417355300215270ustar00rootroot00000000000000 #ifndef __QT_WIDGET_OBJECT_GROUP_H__ #define __QT_WIDGET_OBJECT_GROUP_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include class QLayout; class QObject; namespace caret { /** * Groups QWidget and/or QObjects for applying operations to * all such as blocking signals and setting visibility. */ class WuQWidgetObjectGroup : public QObject { Q_OBJECT public: //WuQWidgetObjectGroup(QWidget* parent); WuQWidgetObjectGroup(QObject* parent); ~WuQWidgetObjectGroup(); void add(QObject* w); void clear(); QObject* getObject() { return dynamic_cast(this); } bool isVisible() const; public slots: void blockAllSignals(bool blockTheSignals); void setEnabled(bool enable); void setDisabled(bool disable); void setVisible(bool makeVisible); void setHidden(bool hidden); void resizeAllToLargestSizeHint(); void setAllCheckBoxesChecked(const bool b); protected: QVector objects; private: // prevent access to QObject's blockSignals() method bool blockSignals(bool); }; } // namespace #endif // __QT_WIDGET_OBJECT_GROUP_H__ workbench-1.1.1/src/GuiQt/WuQtUtilities.cxx000066400000000000000000000736031255417355300206570ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "CaretAssert.h" #include "CaretLogger.h" #include "WuQtUtilities.h" using namespace caret; /** * Create an action with the specified text. * * @param text * Text for the action. * @param toolAndStatusTipText * Text for both tool and status tips. * @param parent * Owner of the created action. * @return * Action that was created. */ QAction* WuQtUtilities::createAction(const QString& text, const QString& toolAndStatusTipText, QObject* parent) { QAction* action = new QAction(parent); action->setText(text); if (toolAndStatusTipText.isEmpty() == false) { action->setStatusTip(toolAndStatusTipText); action->setToolTip(toolAndStatusTipText); } return action; } /** * Create an action with the specified text. * * @param text * Text for the action. * @param toolAndStatusTipText * Text for both tool and status tips. * @param shortcut * Keyboard shortcut. * @param parent * Owner of the created action. * @return * Action that was created. */ QAction* WuQtUtilities::createAction(const QString& text, const QString& toolAndStatusTipText, const QKeySequence& shortcut, QObject* parent) { QAction* action = new QAction(parent); action->setText(text); if (toolAndStatusTipText.isEmpty() == false) { action->setStatusTip(toolAndStatusTipText); action->setToolTip(toolAndStatusTipText); } action->setShortcut(shortcut); return action; } /** * Create an action with the specified text, shortcut, * and calls the specified slot. * * @param text * Text for the action. * @param toolAndStatusTipText * Text for both tool and status tips. * @param shortcut * Keyboard shortcut. * @param parent * Owner of the created action. * @param receiver * Owner of method that is called when action is triggered. * @param method * method in receiver that is called when action is triggered. * @return * Action that was created. */ QAction* WuQtUtilities::createAction(const QString& text, const QString& toolAndStatusTipText, const QKeySequence& shortcut, QObject* parent, QObject* receiver, const char* method) { QAction* action = WuQtUtilities::createAction(text, toolAndStatusTipText, parent, receiver, method); action->setShortcut(shortcut); return action; } /** * Create an action with the specified text and calls * the specified slot. * * @param text * Text for the action. * @param toolAndStatusTipText * Text for both tool and status tips. * @param parent * Owner of the created action. * @param receiver * Owner of method that is called when action is triggered. * @param method * method in receiver that is called when action is triggered. * @return * Action that was created. */ QAction* WuQtUtilities::createAction(const QString& text, const QString& toolAndStatusTipText, QObject* parent, QObject* receiver, const char* method) { QAction* action = WuQtUtilities::createAction(text, toolAndStatusTipText, parent); // QAction* action = new QAction(parent); // action->setText(text); // if (toolAndStatusTipText.isEmpty() == false) { // action->setStatusTip(toolAndStatusTipText); // action->setToolTip(toolAndStatusTipText); // } QObject::connect(action, SIGNAL(triggered(bool)), receiver, method); return action; } /** * Create a pushbutton. * * @param text * Text for the pushbutton. * @param toolAndStatusTipText * Text for both tool and status tips. * @param receiver * Owner of method that is called when button is clicked. * @param method * method in receiver that is called when button is clicked. * @return * Pushbutton that was created. */ QPushButton* WuQtUtilities::createPushButton(const QString& text, const QString& toolAndStatusTipText, QObject* receiver, const char* method) { QPushButton* pb = new QPushButton(text); if (toolAndStatusTipText.isEmpty() == false) { pb->setStatusTip(toolAndStatusTipText); pb->setToolTip(toolAndStatusTipText); } QObject::connect(pb, SIGNAL(clicked()), receiver, method); return pb; } /** * Create a horizontal line widget used as a separator. * * @return A horizontal line widget used as a separator. */ QWidget* WuQtUtilities::createHorizontalLineWidget() { QFrame* frame = new QFrame(); frame->setMidLineWidth(1); frame->setLineWidth(1); frame->setFrameStyle(QFrame::HLine | QFrame::Sunken); return frame; } /** * Create a vertical line widget used as a separator. * * @return A vertical line widget used as a separator. */ QWidget* WuQtUtilities::createVerticalLineWidget() { QFrame* frame = new QFrame(); frame->setMidLineWidth(0); frame->setLineWidth(2); frame->setFrameStyle(QFrame::VLine | QFrame::Sunken); return frame; } /** * Move a window relative to its parent window * but do not let the window move off the screen. * X is left to right, Y is top to bottom. * * @param parentWindow * The parent window of the window being moved. * @param window * The window. * @param xOffset * Offset widget from parent by this X amount. * @param yOffset * Offset widget from parent by this Y amount. */ void WuQtUtilities::moveWindowToOffset(QWidget* parentWindow, QWidget* window, const int xOffset, const int yOffset) { int x = parentWindow->x() + xOffset; int y = parentWindow->y() + yOffset; QDesktopWidget* dw = QApplication::desktop(); const QRect geometry = dw->availableGeometry(parentWindow); const int margin = 20; const int maxX = geometry.width() - margin; const int maxY = geometry.height() - margin; if (x > maxX) x = maxX; if (x < margin) x = margin; if (y > maxY) y = maxY; if (y < margin) y = margin; window->move(x, y); } /** * Place a dialog next to its parent. May not work correctly with * multi-screen systems. * * MUST BE CALLED after a window is displayed since the given window * may not have its geometry (size) set until AFTER it is displayed. * * It will stop after the first one of these actions that is successful: * 1) Put window on right of parent if all of window will be visible. * 2) Put window on left of parent if all of window will be visible. * 3) Put window on right of parent if more space to right of window. * 4) Put window on left of parent. * @param parent * The parent. * @param window * The window. */ void WuQtUtilities::moveWindowToSideOfParent(QWidget* parent, QWidget* window) { const QRect parentGeometry = parent->geometry(); const int px = parentGeometry.x(); const int py = parentGeometry.y(); const int pw = parentGeometry.width(); const int ph = parentGeometry.height(); const int parentMaxX = px + pw; //int x = px + pw + 1; int y = py + ph - window->height() - 20; // int y = py; const int windowWidth = window->width(); QDesktopWidget* dw = QApplication::desktop(); const QRect geometry = dw->availableGeometry(parent); const int screenMinX = geometry.x(); const int screenWidth = geometry.width(); const int screenMaxX = screenMinX + screenWidth; const int screenMaxY = geometry.x() + geometry.height(); const int spaceOnLeft = px -screenMinX; const int spaceOnRight = screenMaxX - parentMaxX; int x = screenMinX; if (spaceOnRight > windowWidth) { x = parentMaxX; } else if (spaceOnLeft > windowWidth) { x = px - windowWidth; } else if (spaceOnRight > spaceOnLeft) { x = screenMaxX - windowWidth; } // else { // x = screenMinX; // } if ((x + windowWidth) > screenMaxX) { x = screenMaxX - windowWidth; } if (x < screenMinX) { x = screenMinX; } const int maxY = screenMaxY - window->height() - 50; if (y > maxY) { y = maxY; } if (y < 50) { y = 50; } window->move(x, y); } /** * Move and size a window limiting window so that * it fits within the screen. * @param x * X-coordinate of window. * @param y * Y-coordinate of window. * @param w * Width of window. * @param h * Height of window. * @param xywhOut * On exit contains 4 values that are the actual * x, y, width, and height of the window after * any needed adjustments for screen sizes. */ void WuQtUtilities::moveAndSizeWindow(QWidget* window, const int32_t x, const int32_t y, const int32_t w, const int32_t h, int32_t* xywhOut) { QDesktopWidget* dw = QApplication::desktop(); /* * Get available geometry where window is to be placed * This geometry is all screens together as one large screen */ QPoint pXY(x, y); const QRect availableRect = dw->screen()->geometry(); const int32_t screenSizeX = availableRect.width(); const int32_t screenSizeY = availableRect.height(); /* * Limit width/height in desktop */ int32_t width = std::min(w, screenSizeX); int32_t height = std::min(h, screenSizeY); /* * Limit window position in desktop */ int32_t xPos = x; if (xPos < availableRect.x()) { xPos = availableRect.x(); } const int32_t maxX = screenSizeX - 200; if (xPos >= maxX) { xPos = maxX; } int32_t yPos = y; if (yPos < availableRect.y()) { yPos = availableRect.y(); } const int32_t maxY = screenSizeY - 200; if (yPos >= maxY) { yPos = maxY; } /* * Make sure visible in closest screen */ pXY.setX(xPos); pXY.setY(yPos); const int32_t nearestScreen = dw->screenNumber(pXY); if (nearestScreen >= 0) { const QRect screenRect = dw->availableGeometry(nearestScreen); if (xPos < screenRect.x()) { xPos = screenRect.x(); } const int32_t maxX = screenRect.right() - 200; if (xPos > maxX) { xPos = maxX; } if (yPos < screenRect.y()) { yPos = screenRect.y(); } const int32_t maxY = screenRect.bottom() - 200; if (yPos > maxY) { yPos = maxY; } } /* * Move and size window */ // std::cout << "Moving to " << xPos << ", " << yPos << std::endl; // std::cout << "Width " << width << ", " << height << std::endl; window->move(xPos, yPos); window->resize(width, height); if (xywhOut != NULL) { xywhOut[0] = window->x(); xywhOut[1] = window->y(); xywhOut[2] = window->width(); xywhOut[3] = window->height(); } } /** * Resize a window but limit maximum size of window * to an area less than the full size of the display. * Has no effect on position of the window so part * of window may end up being outside the display. * * @param window * Window that is resized. * @param width * Desired width of the window. * @param height * Desired width of the window. */ void WuQtUtilities::resizeWindow(QWidget* window, const int32_t width, const int32_t height) { QDesktopWidget* dw = QApplication::desktop(); QPoint pXY(window->x(), window->y()); const int32_t nearestScreen = dw->screenNumber(pXY); if (nearestScreen >= 0) { const QRect screenRect = dw->availableGeometry(nearestScreen); const int32_t screenWidth = screenRect.width() - 100; const int32_t screenHeight = screenRect.height() - 100; const int windowWidth = std::min(width, screenWidth); const int windowHeight = std::min(height, screenHeight); window->resize(windowWidth, windowHeight); } } /** * Table widget has a default size of 640 x 480. * Estimate the size of the dialog with the table fully expanded. * * @param tableWidget * Table widget whose size is estimated. */ QSize WuQtUtilities::estimateTableWidgetSize(QTableWidget* tableWidget) { QSize tableSize(0, 0); /* * Table widget has a default size of 640 x 480. * So estimate the size of the dialog with the table fully * expanded. */ const int numRows = tableWidget->rowCount(); const int numCols = tableWidget->columnCount(); const int cellGap = (tableWidget->showGrid() ? 3 : 0); if ((numRows > 0) && (numCols > 0)) { int tableWidth = 10; // start out with a little extra space int tableHeight = 0; if (tableWidget->horizontalHeader()->isHidden() == false) { QHeaderView* columnHeader = tableWidget->horizontalHeader(); const int columnHeaderHeight = columnHeader->sizeHint().height(); tableHeight += columnHeaderHeight; } if (tableWidget->verticalHeader()->isHidden() == false) { QHeaderView* rowHeader = tableWidget->verticalHeader(); const int rowHeaderHeight = rowHeader->sizeHint().width(); tableHeight += rowHeaderHeight; } std::vector columnWidths(numCols, 0); std::vector rowHeights(numRows, 0); for (int iCol = 0; iCol < numCols; iCol++) { columnWidths[iCol] = tableWidget->columnWidth(iCol) + cellGap; } for (int jRow = 0; jRow < numRows; jRow++) { rowHeights[jRow]= (tableWidget->rowHeight(jRow) + cellGap); } for (int iCol = 0; iCol < numCols; iCol++) { for (int jRow = 0; jRow < numRows; jRow++) { QWidget* widget = tableWidget->cellWidget(jRow, iCol); if (widget != NULL) { const QSize widgetSizeHint = widget->sizeHint(); columnWidths[iCol] = std::max(columnWidths[iCol], widgetSizeHint.width()); rowHeights[jRow] = std::max(rowHeights[jRow], widgetSizeHint.height()); } QTableWidgetItem* item = tableWidget->item(jRow, iCol); if (item != NULL) { int itemWidth = 0; int itemHeight = 0; if (item->flags() && Qt::ItemIsUserCheckable) { itemWidth += 12; } QFont font = item->font(); const QString text = item->text(); if (text.isEmpty() == false) { QFont font = item->font(); QFontMetrics fontMetrics(font); const int textWidth = fontMetrics.width(text); const int textHeight = fontMetrics.height(); itemWidth += textWidth; itemHeight = std::max(itemHeight, textHeight); } columnWidths[iCol] = std::max(columnWidths[iCol], itemWidth); rowHeights[jRow] = std::max(rowHeights[jRow], itemHeight); } } } for (int iCol = 0; iCol < numCols; iCol++) { tableWidth += columnWidths[iCol]; } for (int jRow = 0; jRow < numRows; jRow++) { tableHeight += (rowHeights[jRow] - 2); } tableSize.setWidth(tableWidth); tableSize.setHeight(tableHeight); } return tableSize; } /** * Set the tool tip and status tip for a widget. * * @param widget * Widget that has its tool and status tip set. * @param text * Text for the tool and status tip. */ void WuQtUtilities::setToolTipAndStatusTip(QWidget* widget, const QString& text) { widget->setToolTip(text); widget->setStatusTip(text); } /** * Set the tool tip and status tip for an action. * * @param action * Action that has its tool and status tip set. * @param text * Text for the tool and status tip. */ void WuQtUtilities::setToolTipAndStatusTip(QAction* action, const QString& text) { action->setToolTip(text); action->setStatusTip(text); } /** * Print a list of resources to the Caret Logger. */ void WuQtUtilities::sendListOfResourcesToCaretLogger() { QString msg = "Resources loaded:\n"; QDir dir(":/"); QFileInfoList infoList = dir.entryInfoList(); for (int i = 0; i < infoList.count(); i++) { msg += " "; msg += infoList.at(i).filePath(); } CaretLogInfo(msg); } /** * Load an icon. * * @param filename * Name of file (or resource) containing the icon. * @param iconOut * Output that will contain the desired icon. * @return * True if the icon is valid, else false. */ bool WuQtUtilities::loadIcon(const QString& filename, QIcon& iconOut) { QPixmap pixmap; const bool valid = WuQtUtilities::loadPixmap(filename, pixmap); if (valid) { iconOut.addPixmap(pixmap); } return valid; } /** * Load an icon. * @param filename * Name of file containing the icon. * @return Pointer to icon (call must delete it) or NULL * if there was a failure to load the icon. */ QIcon* WuQtUtilities::loadIcon(const QString& filename) { QPixmap pixmap; const bool valid = WuQtUtilities::loadPixmap(filename, pixmap); QIcon* icon = NULL; if (valid) { icon = new QIcon(pixmap); } return icon; } /** * Load an pixmap. * * @param filename * Name of file (or resource) containing the pixmap. * @param pixmapOut * Output that will contain the desired pixmap. * @return * True if the pixmap is valid, else false. */ bool WuQtUtilities::loadPixmap(const QString& filename, QPixmap& pixmapOut) { bool valid = pixmapOut.load(filename); if (valid == false) { QString msg = "Failed to load Pixmap \"" + filename + "\"."; CaretLogSevere(msg); } else if ((pixmapOut.width() <= 0) || (pixmapOut.height() <= 0)) { QString msg = "Pixmap \"" + filename + "\" has invalid size."; CaretLogSevere(msg); valid = false; } return valid; } /** * Find the widget with the maximum height in its * size hint. Apply this height to all of the widgets. * * @param w1 Required widget. * @param w2 Required widget. * @param w3 Optional widget. * @param w4 Optional widget. * @param w5 Optional widget. * @param w6 Optional widget. * @param w7 Optional widget. * @param w8 Optional widget. * @param w9 Optional widget. * @param w10 Optional widget. */ void WuQtUtilities::matchWidgetHeights(QWidget* w1, QWidget* w2, QWidget* w3, QWidget* w4, QWidget* w5, QWidget* w6, QWidget* w7, QWidget* w8, QWidget* w9, QWidget* w10) { QVector widgets; if (w1 != NULL) widgets.push_back(w1); if (w2 != NULL) widgets.push_back(w2); if (w3 != NULL) widgets.push_back(w3); if (w4 != NULL) widgets.push_back(w4); if (w5 != NULL) widgets.push_back(w5); if (w6 != NULL) widgets.push_back(w6); if (w7 != NULL) widgets.push_back(w7); if (w8 != NULL) widgets.push_back(w8); if (w9 != NULL) widgets.push_back(w9); if (w10 != NULL) widgets.push_back(w10); int maxHeight = 0; const int num = widgets.size(); for (int i = 0; i < num; i++) { const int h = widgets[i]->sizeHint().height(); if (h > maxHeight) { maxHeight = h; } } if (maxHeight > 0) { for (int i = 0; i < num; i++) { widgets[i]->setFixedHeight(maxHeight); } } } /** * Find the widget with the maximum width in its * size hint. Apply this width to all of the widgets. * * @param w1 Required widget. * @param w2 Required widget. * @param w3 Optional widget. * @param w4 Optional widget. * @param w5 Optional widget. * @param w6 Optional widget. * @param w7 Optional widget. * @param w8 Optional widget. * @param w9 Optional widget. * @param w10 Optional widget. */ void WuQtUtilities::matchWidgetWidths(QWidget* w1, QWidget* w2, QWidget* w3, QWidget* w4, QWidget* w5, QWidget* w6, QWidget* w7, QWidget* w8, QWidget* w9, QWidget* w10) { QVector widgets; if (w1 != NULL) widgets.push_back(w1); if (w2 != NULL) widgets.push_back(w2); if (w3 != NULL) widgets.push_back(w3); if (w4 != NULL) widgets.push_back(w4); if (w5 != NULL) widgets.push_back(w5); if (w6 != NULL) widgets.push_back(w6); if (w7 != NULL) widgets.push_back(w7); if (w8 != NULL) widgets.push_back(w8); if (w9 != NULL) widgets.push_back(w9); if (w10 != NULL) widgets.push_back(w10); int maxWidth = 0; const int num = widgets.size(); for (int i = 0; i < num; i++) { const int w = widgets[i]->sizeHint().width(); if (w > maxWidth) { maxWidth = w; } } if (maxWidth > 0) { for (int i = 0; i < num; i++) { widgets[i]->setFixedWidth(maxWidth); } } } /** * Set the margins and spacing for a layout. * @param layout * Layout that has margins and spacings set. * @param spacing * Spacing between widgets within layout. * @param contentsMargin * Margin around the layout. */ void WuQtUtilities::setLayoutSpacingAndMargins(QLayout* layout, const int spacing, const int contentsMargin) { layout->setSpacing(spacing); layout->setContentsMargins(contentsMargin, contentsMargin, contentsMargin, contentsMargin); } /** * Set the content margins around a layout. * * @param contentsMargin * Margin around the layout. */ void WuQtUtilities::setLayoutMargins(QLayout* layout, const int contentsMargin) { layout->setContentsMargins(contentsMargin, contentsMargin, contentsMargin, contentsMargin); } /** * @return The minimum size (width/height) of all screens. */ QSize WuQtUtilities::getMinimumScreenSize() { int minWidth = std::numeric_limits::max(); int minHeight = std::numeric_limits::max(); QDesktopWidget* dw = QApplication::desktop(); const int numScreens = dw->screenCount(); for (int i = 0; i < numScreens; i++) { const QRect rect = dw->availableGeometry(i); const int w = rect.width(); const int h = rect.height(); minWidth = std::min(minWidth, w); minHeight = std::min(minHeight, h); } const QSize size(minWidth, minHeight); return size; } /** * Is the user's display small? This is loosely * defined as a vertical resolution of 800 or less. * @return true if resolution is 800 or less, * else false. */ bool WuQtUtilities::isSmallDisplay() { QDesktopWidget* dw = QApplication::desktop(); QRect screenRect = dw->screenGeometry(); const int verticalSize = screenRect.height(); if (verticalSize <= 800) { return true; } return false; } /** * Get a String containing information about a layout' content. * @param layout * The layout * @return * String with info. */ QString WuQtUtilities::getLayoutContentDescription(QLayout* layout) { QString s; s.reserve(25000); s += ("Layout type : " + QString(typeid(*layout).name()) + "\n"); const int itemCount = layout->count(); for (int32_t i = 0; i < itemCount; i++) { s += " "; QLayoutItem* layoutItem = layout->itemAt(i); QLayout* layout = layoutItem->layout(); if (layout != NULL) { s += QString(typeid(*layout).name()); } QWidget* widget = layoutItem->widget(); if (widget != NULL) { s += QString(typeid(*widget).name()); } QSpacerItem* spacerItem = layoutItem->spacerItem(); if (spacerItem != NULL) { s += "QSpacerItem"; } } return s; } /** * Play a sound file. The sound file MUST be in the distribution's * "resources/sounds" directory. * * Note that sound files, as of Qt 4.8, do not support Qt's resource * system. * * @param soundFileName * Name of sound file (with no path, just the filename). */ void WuQtUtilities::playSound(const QString& soundFileName) { const QString workbenchDir = SystemUtilities::getWorkbenchHome(); const QString soundFilePath = (workbenchDir + "/../resources/sounds/" + soundFileName); if (QFile::exists(soundFilePath)) { QSound::play(soundFilePath); } else { CaretLogSevere("Sound file \"" + soundFilePath + "\" does not exist."); } } /** * Create the text for a tooltip so that long lines are * wrapped and the tooltip is not one giant line * that is the width of the display. * * This is accomplished by placing the text into a * QTextDocument and then retrieving the text with * HTML formatting. * * @param tooltipText * Text for the tooltip. * @return * Text reformatted for display in a tool tip. */ QString WuQtUtilities::createWordWrappedToolTipText(const QString& tooltipText) { if (tooltipText.isEmpty()) { return ""; } QTextDocument textDocument(tooltipText); QString html = textDocument.toHtml(); return html; } /** * Set the text for a tooltip so that long lines are * wrapped and the tooltip is not one giant line * that is the width of the display. * * This is accomplished by placing the text into a * QTextDocument and then retrieving the text with * HTML formatting. * * @param widget * Widget on which tooltip is set. * @param tooltipText * Text for the widget's tooltip. */ void WuQtUtilities::setWordWrappedToolTip(QWidget* widget, const QString& tooltipText) { widget->setToolTip(createWordWrappedToolTipText(tooltipText)); } /** * Convert a Qt::CheckState to a boolean value. * * @param checkState * The check state value. * @return * true if checked or partially checked, else false. */ bool WuQtUtilities::checkStateToBool(const Qt::CheckState checkState) { if (checkState == Qt::Unchecked) { return false; } return true; } /** * Convert a boolean value to a Qt::CheckState * * @param value * The boolean value. * @return * Check state indicating checked or not checked. */ Qt::CheckState WuQtUtilities::boolToCheckState(const bool value) { if (value) { return Qt::Checked; } return Qt::Unchecked; } workbench-1.1.1/src/GuiQt/WuQtUtilities.h000066400000000000000000000151211255417355300202730ustar00rootroot00000000000000 #ifndef __WU_QT_UTILITIES_H__ #define __WU_QT_UTILITIES_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include class QAction; class QBoxLayout; class QDialog; class QIcon; class QKeySequence; class QLayout; class QObject; class QPixmap; class QPushButton; class QString; class QTableWidget; class QWidget; namespace caret { /** * Utilities for use with Qt. */ class WuQtUtilities { public: static QAction* createAction(const QString& text, const QString& toolAndStatusTipText, const QKeySequence& shortcut, QObject* parent, QObject* receiver, const char* method); static QAction* createAction(const QString& text, const QString& toolAndStatusTipText, QObject* parent, QObject* receiver, const char* method); static QAction* createAction(const QString& text, const QString& toolAndStatusTipText, QObject* parent); static QAction* createAction(const QString& text, const QString& toolAndStatusTipText, const QKeySequence& shortcut, QObject* parent); static QPushButton* createPushButton(const QString& text, const QString& toolAndStatusTipText, QObject* receiver, const char* method); static QWidget* createVerticalLineWidget(); static QWidget* createHorizontalLineWidget(); static void moveWindowToOffset(QWidget* parentWidget, QWidget* window, const int xOffset, const int yOffset); static void setToolTipAndStatusTip(QWidget* widget, const QString& text); static void setToolTipAndStatusTip(QAction* action, const QString& text); static void sendListOfResourcesToCaretLogger(); static bool loadIcon(const QString& filename, QIcon& iconOut); static QIcon* loadIcon(const QString& filename); static bool loadPixmap(const QString& filename, QPixmap& pixmapOut); static void moveWindowToSideOfParent(QWidget* parent, QWidget* window); static void moveAndSizeWindow(QWidget* window, const int32_t x, const int32_t y, const int32_t w, const int32_t h, int32_t* xywhOut); static void resizeWindow(QWidget* window, const int32_t width, const int32_t height); static void matchWidgetHeights(QWidget* w1, QWidget* w2, QWidget* w3 = 0, QWidget* w4 = 0, QWidget* w5 = 0, QWidget* w6 = 0, QWidget* w7 = 0, QWidget* w8 = 0, QWidget* w9 = 0, QWidget* w10 = 0); static void matchWidgetWidths(QWidget* w1, QWidget* w2, QWidget* w3 = 0, QWidget* w4 = 0, QWidget* w5 = 0, QWidget* w6 = 0, QWidget* w7 = 0, QWidget* w8 = 0, QWidget* w9 = 0, QWidget* w10 = 0); static void setLayoutSpacingAndMargins(QLayout* layout, const int spacing, const int contentsMargin); static void setLayoutMargins(QLayout* layout, const int contentsMargin); static QSize estimateTableWidgetSize(QTableWidget* tableWidget); static QSize getMinimumScreenSize(); static bool isSmallDisplay(); static QString getLayoutContentDescription(QLayout* layout); static void playSound(const QString& soundFileName); static QString createWordWrappedToolTipText(const QString& tooltipText); static void setWordWrappedToolTip(QWidget* widget, const QString& tooltipText); static bool checkStateToBool(const Qt::CheckState checkState); static Qt::CheckState boolToCheckState(const bool value); private: WuQtUtilities(); ~WuQtUtilities(); WuQtUtilities(const WuQtUtilities&); WuQtUtilities& operator=(const WuQtUtilities&); }; } #endif // __WU_QT_UTILITIES_H__ workbench-1.1.1/src/GuiQt/WuQwtPlot.cxx000066400000000000000000000042041255417355300200000ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __WU_QWT_PLOT_DECLARE__ #include "WuQwtPlot.h" #undef __WU_QWT_PLOT_DECLARE__ #include #include #include "qwt_plot_canvas.h" #include "CaretAssert.h" using namespace caret; /** * \class caret::WuQwtPlot * \brief Extends QwtPlot * \ingroup GuiQt * * Extends QwtPlot by adding a context menu with graph coordinates */ /** * Constructor. */ WuQwtPlot::WuQwtPlot(QWidget* w) : QwtPlot(w) { } /** * Destructor. */ WuQwtPlot::~WuQwtPlot() { } /** * Receives the context menu event. * * @param event * The context menu event. */ void WuQwtPlot::contextMenuEvent(QContextMenuEvent* event) { QPoint canvasPos = canvas()->mapFromGlobal(event->globalPos()); const QPointF plotPos = inverseTransform(canvasPos); emit contextMenuDisplay(event, plotPos.x(), plotPos.y()); } /*! Translate a point from pixel into plot coordinates \return Point in plot coordinates \sa transform() */ QPointF WuQwtPlot::inverseTransform( const QPoint &pos ) const { QwtScaleMap xMap = canvasMap( QwtPlot::xBottom ); QwtScaleMap yMap = canvasMap( QwtPlot::yLeft ); return QPointF( xMap.invTransform( pos.x() ), yMap.invTransform( pos.y() ) ); } workbench-1.1.1/src/GuiQt/WuQwtPlot.h000066400000000000000000000033251255417355300174300ustar00rootroot00000000000000#ifndef __WU_QWT_PLOT_H__ #define __WU_QWT_PLOT_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "qwt_plot.h" namespace caret { class WuQwtPlot : public QwtPlot { Q_OBJECT public: WuQwtPlot(QWidget* w = 0); virtual ~WuQwtPlot(); virtual void contextMenuEvent(QContextMenuEvent* event); // ADD_NEW_METHODS_HERE QPointF inverseTransform( const QPoint &pos ) const; signals: void contextMenuDisplay(QContextMenuEvent* event, float graphX, float graphY); private: WuQwtPlot(const WuQwtPlot&); WuQwtPlot& operator=(const WuQwtPlot&); // ADD_NEW_MEMBERS_HERE }; #ifdef __WU_QWT_PLOT_DECLARE__ // #endif // __WU_QWT_PLOT_DECLARE__ } // namespace #endif //__WU_QWT_PLOT_H__ workbench-1.1.1/src/Nifti/000077500000000000000000000000001255417355300153465ustar00rootroot00000000000000workbench-1.1.1/src/Nifti/CMakeLists.txt000066400000000000000000000007331255417355300201110ustar00rootroot00000000000000# # The NIFTI Project # project (Nifti) # # Need XML from Qt # SET(QT_DONT_USE_QTGUI) # # Add QT for includes # INCLUDE (${QT_USE_FILE}) # # Create the NIFTI library # ADD_LIBRARY(Nifti Matrix4x4.h NiftiHeader.h NiftiIO.h Matrix4x4.cxx NiftiHeader.cxx NiftiIO.cxx ) # # Find Headers # INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/Nifti ${CMAKE_SOURCE_DIR}/FilesBase ${CMAKE_SOURCE_DIR}/Common ${CMAKE_SOURCE_DIR}/Palette ${CMAKE_SOURCE_DIR}/Cifti ${CMAKE_SOURCE_DIR}/Xml ) workbench-1.1.1/src/Nifti/Matrix4x4.cxx000066400000000000000000001412211255417355300176770ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ /*========================================================================= Program: Visualization Toolkit Module: $RCSfile: vtkBase64Utilities.h,v $ Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen All rights reserved. See Copyright.txt or http://www.kitware.com/Copyright.htm for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notice for more information. Program: Visualization Toolkit Module: $RCSfile: vtkMatrix4x4.cxx,v $ Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen All rights reserved. See Copyright.txt or http://www.kitware.com/Copyright.htm for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notice for more information. =========================================================================*/ #include #include "CaretAssert.h" #include "CaretLogger.h" #include "MathFunctions.h" #include "Matrix4x4.h" #include "NiftiEnums.h" #include "XmlWriter.h" using namespace caret; static const bool INFO = false; static const bool DEBUG = false; static const float iDF = 1.0f; static const double SMALL_POSITIVE_NUMBER = 0.000001; static const double SMALL_NEGATIVE_NUMBER = -0.000001; /** * * constructor that creates an identity matrix. * */ Matrix4x4::Matrix4x4() : CaretObject() { this->initializeMembersMatrix4x4(); } /** * Destructor */ Matrix4x4::~Matrix4x4() { } /** * Copy Constructor * @param Object that is copied. */ Matrix4x4::Matrix4x4(const Matrix4x4& o) : CaretObject(o) { this->initializeMembersMatrix4x4(); this->copyHelper(o); } /** * Assignment operator. */ Matrix4x4& Matrix4x4::operator=(const Matrix4x4& o) { if (this != &o) { CaretObject::operator=(o); this->copyHelper(o); }; return *this; } /** * Helps with copy constructor and assignment operator. */ void Matrix4x4::copyHelper(const Matrix4x4& o) { this->setMatrix(o); this->dataSpaceName = o.dataSpaceName; this->transformedSpaceName = o.transformedSpaceName; } void Matrix4x4::initializeMembersMatrix4x4() { this->identity(); this->dataSpaceName = NiftiTransformEnum::toName(NiftiTransformEnum::NIFTI_XFORM_TALAIRACH); this->transformedSpaceName = NiftiTransformEnum::toName(NiftiTransformEnum::NIFTI_XFORM_TALAIRACH); this->clearModified(); } /** * Set the matrix to the identity matrix. * */ void Matrix4x4::identity() { matrix[0][0] = 1.0; matrix[0][1] = 0.0; matrix[0][2] = 0.0; matrix[0][3] = 0.0; matrix[1][0] = 0.0; matrix[1][1] = 1.0; matrix[1][2] = 0.0; matrix[1][3] = 0.0; matrix[2][0] = 0.0; matrix[2][1] = 0.0; matrix[2][2] = 1.0; matrix[2][3] = 0.0; matrix[3][0] = 0.0; matrix[3][1] = 0.0; matrix[3][2] = 0.0; matrix[3][3] = 1.0; this->setModified(); } /** * Get the translation from the matrix. * * param The translation as an array of three floats. * */ void Matrix4x4::getTranslation(float translatationOut[3]) const { translatationOut[0] = matrix[0][3]; translatationOut[1] = matrix[1][3]; translatationOut[2] = matrix[2][3]; } /** * Set (replace) the matrix's translation. * * @param t An array of three float containing the translation. * */ void Matrix4x4::setTranslation(const float t[3]) { matrix[0][3] = t[0]; matrix[1][3] = t[1]; matrix[2][3] = t[2]; this->setModified(); } /** * Set (replace) the matrix's translation. * * @param tx The translation along the X-Axis. * @param ty The translation along the Y-Axis. * @param tz The translation along the Z-Axis. * */ void Matrix4x4::setTranslation( const double tx, const double ty, const double tz) { matrix[0][3] = tx; matrix[1][3] = ty; matrix[2][3] = tz; this->setModified(); } /** * * Apply a translation by multiplying the matrix by a matrix * containing the specified translation. Translates in the * screen' coordinate system. * * @param tx The translation along the X-Axis. * @param ty The translation along the Y-Axis. * @param tz The translation along the Z-Axis. * */ void Matrix4x4::translate( const double tx, const double ty, const double tz) { Matrix4x4 cm; cm.setTranslation(tx, ty, tz); postmultiply(cm); this->setModified(); } /** * Apply scaling by multiplying the matrix by a matrix * containing the specified scaling. Translates in the * screen' coordinate system. * * @param sx The scaling along the X-Axis. * @param sy The scaling along the Y-Axis. * @param sz The scaling along the Z-Axis. * */ void Matrix4x4::scale( const double sx, const double sy, const double sz) { Matrix4x4 cm; cm.matrix[0][0] = sx; cm.matrix[1][1] = sy; cm.matrix[2][2] = sz; postmultiply(cm); this->setModified(); } /** * Get the scaling from the matrix. * @param scaleOutX * X scaling output. * @param scaleOutY * Y scaling output. * @param scaleOutZ * Z scaling output. */ void Matrix4x4::getScale(double& scaleOutX, double& scaleOutY, double& scaleOutZ) const { double U[3][3], VT[3][3]; for (int i = 0; i < 3; i++) { U[0][i] = matrix[0][i]; U[1][i] = matrix[1][i]; U[2][i] = matrix[2][i]; } double scale[3]; Matrix4x4::SingularValueDecomposition3x3(U, U, scale, VT); scaleOutX = scale[0]; scaleOutY = scale[1]; scaleOutZ = scale[2]; } //---------------------------------------------------------------------------- // Perform singular value decomposition on the matrix A: // A = U * W * VT // where U and VT are orthogonal W is diagonal (the diagonal elements // are returned in vector w). // The matrices U and VT will both have positive determinants. // The scale factors w are ordered according to how well the // corresponding eigenvectors (in VT) match the x, y and z axes // respectively. // // The singular value decomposition is used to decompose a linear // transformation into a rotation, followed by a scale, followed // by a second rotation. The scale factors w will be negative if // the determinant of matrix A is negative. // // Contributed by David Gobbi (dgobbi@irus.rri.on.ca) void Matrix4x4::SingularValueDecomposition3x3(const double A[3][3], double U[3][3], double w[3], double VT[3][3]) { int i; double B[3][3]; // copy so that A can be used for U or VT without risk for (i = 0; i < 3; i++) { B[0][i] = A[0][i]; B[1][i] = A[1][i]; B[2][i] = A[2][i]; } // temporarily flip if determinant is negative double d = Determinant3x3(B); if (d < 0) { for (i = 0; i < 3; i++) { B[0][i] = -B[0][i]; B[1][i] = -B[1][i]; B[2][i] = -B[2][i]; } } // orthogonalize, diagonalize, etc. Orthogonalize3x3(B, U); Transpose3x3(B, B); Multiply3x3(B, U, VT); Diagonalize3x3(VT, w, VT); Multiply3x3(U, VT, U); Transpose3x3(VT, VT); // re-create the flip if (d < 0) { w[0] = -w[0]; w[1] = -w[1]; w[2] = -w[2]; } /* paranoia check: recombine to ensure that the SVD is correct vtkMath::Transpose3x3(B, B); if (d < 0) { for (i = 0; i < 3; i++) { B[0][i] = -B[0][i]; B[1][i] = -B[1][i]; B[2][i] = -B[2][i]; } } int j; T2 maxerr = 0; T2 tmp; T2 M[3][3]; T2 W[3][3]; vtkMath::Identity3x3(W); W[0][0] = w[0]; W[1][1] = w[1]; W[2][2] = w[2]; vtkMath::Identity3x3(M); vtkMath::Multiply3x3(M, U, M); vtkMath::Multiply3x3(M, W, M); vtkMath::Multiply3x3(M, VT, M); for (i = 0; i < 3; i++) { for (j = 0; j < 3; j++) { if ((tmp = fabs(B[i][j] - M[i][j])) > maxerr) { maxerr = tmp; } } } vtkGenericWarningMacro("SingularValueDecomposition max error = " << maxerr); */ } void Matrix4x4::Diagonalize3x3(const double A[3][3], double w[3], double V[3][3]) { int i,j,k,maxI; double tmp, maxVal; // do the matrix[3][3] to **matrix conversion for Jacobi double C[3][3]; double *ATemp[3],*VTemp[3]; for (i = 0; i < 3; i++) { C[i][0] = A[i][0]; C[i][1] = A[i][1]; C[i][2] = A[i][2]; ATemp[i] = C[i]; VTemp[i] = V[i]; } // diagonalize using Jacobi Matrix4x4::JacobiN(ATemp,3,w,VTemp); // if all the eigenvalues are the same, return identity matrix if (w[0] == w[1] && w[0] == w[2]) { for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { V[i][j] = 0.0; } } return; } // transpose temporarily, it makes it easier to sort the eigenvectors Transpose3x3(V,V); // if two eigenvalues are the same, re-orthogonalize to optimally line // up the eigenvectors with the x, y, and z axes for (i = 0; i < 3; i++) { if (w[(i+1)%3] == w[(i+2)%3]) // two eigenvalues are the same { // find maximum element of the independant eigenvector maxVal = fabs(V[i][0]); maxI = 0; for (j = 1; j < 3; j++) { if (maxVal < (tmp = fabs(V[i][j]))) { maxVal = tmp; maxI = j; } } // swap the eigenvector into its proper position if (maxI != i) { tmp = w[maxI]; w[maxI] = w[i]; w[i] = tmp; SwapVectors3(V[i],V[maxI]); } // maximum element of eigenvector should be positive if (V[maxI][maxI] < 0) { V[maxI][0] = -V[maxI][0]; V[maxI][1] = -V[maxI][1]; V[maxI][2] = -V[maxI][2]; } // re-orthogonalize the other two eigenvectors j = (maxI+1)%3; k = (maxI+2)%3; V[j][0] = 0.0; V[j][1] = 0.0; V[j][2] = 0.0; V[j][j] = 1.0; MathFunctions::crossProduct(V[maxI],V[j],V[k]); MathFunctions::normalizeVector(V[k]); MathFunctions::crossProduct(V[k],V[maxI],V[j]); // transpose vectors back to columns Transpose3x3(V,V); return; } } // the three eigenvalues are different, just sort the eigenvectors // to align them with the x, y, and z axes // find the vector with the largest x element, make that vector // the first vector maxVal = fabs(V[0][0]); maxI = 0; for (i = 1; i < 3; i++) { if (maxVal < (tmp = fabs(V[i][0]))) { maxVal = tmp; maxI = i; } } // swap eigenvalue and eigenvector if (maxI != 0) { tmp = w[maxI]; w[maxI] = w[0]; w[0] = tmp; SwapVectors3(V[maxI],V[0]); } // do the same for the y element if (fabs(V[1][1]) < fabs(V[2][1])) { tmp = w[2]; w[2] = w[1]; w[1] = tmp; SwapVectors3(V[2],V[1]); } // ensure that the sign of the eigenvectors is correct for (i = 0; i < 2; i++) { if (V[i][i] < 0) { V[i][0] = -V[i][0]; V[i][1] = -V[i][1]; V[i][2] = -V[i][2]; } } // set sign of final eigenvector to ensure that determinant is positive if (Determinant3x3(V) < 0) { V[2][0] = -V[2][0]; V[2][1] = -V[2][1]; V[2][2] = -V[2][2]; } // transpose the eigenvectors back again Transpose3x3(V,V); } #define VTK_ROTATE(a,i,j,k,l) g=a[i][j];h=a[k][l];a[i][j]=g-s*(h+g*tau);\ a[k][l]=h+s*(g-h*tau) // Jacobi iteration for the solution of eigenvectors/eigenvalues of a nxn // real symmetric matrix. Square nxn matrix a; size of matrix in n; // output eigenvalues in w; and output eigenvectors in v. Resulting // eigenvalues/vectors are sorted in decreasing order; eigenvectors are // normalized. int Matrix4x4::JacobiN(double **a, int n, double *w, double **v) { const int VTK_MAX_ROTATIONS = 20; int i, j, k, iq, ip, numPos; double tresh, theta, tau, t, sm, s, h, g, c, tmp; double bspace[4], zspace[4]; double *b = bspace; double *z = zspace; // only allocate memory if the matrix is large if (n > 4) { b = new double[n]; z = new double[n]; } // initialize for (ip=0; ip 3 && (fabs(w[ip])+g) == fabs(w[ip]) && (fabs(w[iq])+g) == fabs(w[iq])) { a[ip][iq] = 0.0; } else if (fabs(a[ip][iq]) > tresh) { h = w[iq] - w[ip]; if ( (fabs(h)+g) == fabs(h)) { t = (a[ip][iq]) / h; } else { theta = 0.5*h / (a[ip][iq]); t = 1.0 / (fabs(theta)+sqrt(1.0+theta*theta)); if (theta < 0.0) { t = -t; } } c = 1.0 / sqrt(1+t*t); s = t*c; tau = s/(1.0+c); h = t*a[ip][iq]; z[ip] -= h; z[iq] += h; w[ip] -= h; w[iq] += h; a[ip][iq]=0.0; // ip already shifted left by 1 unit for (j = 0;j <= ip-1;j++) { VTK_ROTATE(a,j,ip,j,iq); } // ip and iq already shifted left by 1 unit for (j = ip+1;j <= iq-1;j++) { VTK_ROTATE(a,ip,j,j,iq); } // iq already shifted left by 1 unit for (j=iq+1; j= VTK_MAX_ROTATIONS ) { CaretLogWarning("Matrix4x4::Jacobi: Error extracting eigenfunctions"); return 0; } // sort eigenfunctions these changes do not affect accuracy for (j=0; j= tmp) // why exchage if same? { k = i; tmp = w[k]; } } if (k != j) { w[k] = w[j]; w[j] = tmp; for (i=0; i> 1) + (n & 1); for (j=0; j= 0.0 ) { numPos++; } } // if ( numPos < ceil(double(n)/double(2.0)) ) if ( numPos < ceil_half_n) { for(i=0; i 4) { delete [] b; delete [] z; } return 1; } #undef VTK_ROTATE void Matrix4x4::Multiply3x3(const double A[3][3], const double B[3][3], double C[3][3]) { double D[3][3]; for (int i = 0; i < 3; i++) { D[0][i] = A[0][0]*B[0][i] + A[0][1]*B[1][i] + A[0][2]*B[2][i]; D[1][i] = A[1][0]*B[0][i] + A[1][1]*B[1][i] + A[1][2]*B[2][i]; D[2][i] = A[2][0]*B[0][i] + A[2][1]*B[1][i] + A[2][2]*B[2][i]; } for (int j = 0; j < 3; j++) { C[j][0] = D[j][0]; C[j][1] = D[j][1]; C[j][2] = D[j][2]; } } //---------------------------------------------------------------------------- void Matrix4x4::Transpose3x3(const double A[3][3], double AT[3][3]) { double tmp; tmp = A[1][0]; AT[1][0] = A[0][1]; AT[0][1] = tmp; tmp = A[2][0]; AT[2][0] = A[0][2]; AT[0][2] = tmp; tmp = A[2][1]; AT[2][1] = A[1][2]; AT[1][2] = tmp; AT[0][0] = A[0][0]; AT[1][1] = A[1][1]; AT[2][2] = A[2][2]; } /** * Apply a rotation about the X-axis. Rotates in the * screen's coordinate system. * * @param degrees Amount to rotate in degrees. * */ void Matrix4x4::rotateX(const double degrees) { rotate(degrees, 1.0, 0.0, 0.0); } /** * Apply a rotation about the Y-axis. Rotates in the * screen's coordinate system. * * @param degrees Amount to rotate in degrees. * */ void Matrix4x4::rotateY(const double degrees) { rotate(degrees, 0.0, 1.0, 0.0); } /** * Apply a rotation about the Z-axis. Rotates in the * screen's coordinate system. * * @param degrees Amount to rotate in degrees. * */ void Matrix4x4::rotateZ(const double degrees) { rotate(degrees, 0.0, 0.0, 1.0); } /** * Rotate angle degrees about the vector. * @param angle - Angle of rotation. * @param vector - Vector about which rotation occurs. * */ void Matrix4x4::rotate( const double angle, const double vector[4]) { this->rotate(angle, vector[0], vector[1], vector[2]); this->setModified(); } /** * Rotate angle degrees about the vector (x,y,z). * * @param angle Amount to rotate in degrees. * @param xin X-component of the vector. * @param yin Y-component of the vector. * @param zin Z-component of the vector. * */ void Matrix4x4::rotate( const double angleIn, const double xin, const double yin, const double zin) { /// from vtkTransformConcatenation::Rotate() float angle = angleIn; if (angle == 0.0 || (xin == 0.0 && yin == 0.0 && zin == 0.0)) { return; } // convert to radians angle = MathFunctions::toRadians(angle); // make a normalized quaternion double w = std::cos(0.5*angle); double f = std::sin(0.5*angle) / std::sqrt(xin*xin+yin*yin+zin*zin); double x = xin * f; double y = yin * f; double z = zin * f; double ww = w*w; double wx = w*x; double wy = w*y; double wz = w*z; double xx = x*x; double yy = y*y; double zz = z*z; double xy = x*y; double xz = x*z; double yz = y*z; double s = ww - xx - yy - zz; // convert the quaternion to a matrix Matrix4x4 m; m.matrix[0][0] = xx*2.0 + s; m.matrix[1][0] = (xy + wz)*2; m.matrix[2][0] = (xz - wy)*2; m.matrix[0][1] = (xy - wz)*2; m.matrix[1][1] = yy*2.0 + s; m.matrix[2][1] = (yz + wx)*2; m.matrix[0][2] = (xz + wy)*2; m.matrix[1][2] = (yz - wx)*2; m.matrix[2][2] = zz*2.0 + s; m.fixNumericalError(); postmultiply(m); this->fixNumericalError(); this->setModified(); } /* * Set the rotation matrix using the given angles. * WARNING: Any scaling or translation is will be removed!!! * * @rotationX * The X-rotation angle. * @rotationY * The Y-rotation angle. * @rotationZ * The Z-rotation angle. */ void Matrix4x4::setRotation(const double rotationX, const double rotationY, const double rotationZ) { identity(); rotateY(rotationY); rotateX(rotationX); rotateZ(rotationZ); } /* * Get the rotation angles from the matrix. * * From vktTransform::GetOrientation() * * @rotationOutX * Output containing X-rotation from matrix. * @rotationOutY * Output containing X-rotation from matrix. * @rotationOutZ * Output containing X-rotation from matrix. */ void Matrix4x4::getRotation(double& rotationOutX, double& rotationOutY, double& rotationOutZ) const { #define VTK_AXIS_EPSILON 0.001 int i; // convenient access to matrix // double (*matrix)[4] = amatrix->Element; double ortho[3][3]; for (i = 0; i < 3; i++) { ortho[0][i] = matrix[0][i]; ortho[1][i] = matrix[1][i]; ortho[2][i] = matrix[2][i]; } if (Determinant3x3(ortho) < 0) { ortho[0][2] = -ortho[0][2]; ortho[1][2] = -ortho[1][2]; ortho[2][2] = -ortho[2][2]; } Matrix4x4::Orthogonalize3x3(ortho, ortho); // first rotate about y axis double x2 = ortho[2][0]; double y2 = ortho[2][1]; double z2 = ortho[2][2]; double x3 = ortho[1][0]; double y3 = ortho[1][1]; double z3 = ortho[1][2]; double d1 = sqrt(x2*x2 + z2*z2); double cosTheta, sinTheta; if (d1 < VTK_AXIS_EPSILON) { cosTheta = 1.0; sinTheta = 0.0; } else { cosTheta = z2/d1; sinTheta = x2/d1; } double theta = std::atan2(sinTheta, cosTheta); rotationOutY = - MathFunctions::toDegrees(theta ); // now rotate about x axis double d = std::sqrt(x2*x2 + y2*y2 + z2*z2); double sinPhi, cosPhi; if (d < VTK_AXIS_EPSILON) { sinPhi = 0.0; cosPhi = 1.0; } else if (d1 < VTK_AXIS_EPSILON) { sinPhi = y2/d; cosPhi = z2/d; } else { sinPhi = y2/d; cosPhi = (x2*x2 + z2*z2)/(d1*d); } double phi = std::atan2(sinPhi, cosPhi); rotationOutX = MathFunctions::toDegrees(phi); // finally, rotate about z double x3p = x3*cosTheta - z3*sinTheta; double y3p = - sinPhi*sinTheta*x3 + cosPhi*y3 - sinPhi*cosTheta*z3; double d2 = std::sqrt(x3p*x3p + y3p*y3p); double cosAlpha, sinAlpha; if (d2 < VTK_AXIS_EPSILON) { cosAlpha = 1.0; sinAlpha = 0.0; } else { cosAlpha = y3p/d2; sinAlpha = x3p/d2; } double alpha = std::atan2(sinAlpha, cosAlpha); rotationOutZ = MathFunctions::toDegrees(alpha); } void Matrix4x4::Orthogonalize3x3(const double A[3][3], double B[3][3]) { int i; // copy the matrix for (i = 0; i < 3; i++) { B[0][i] = A[0][i]; B[1][i] = A[1][i]; B[2][i] = A[2][i]; } // Pivot the matrix to improve accuracy double scale[3]; int index[3]; double tmp, largest; // Loop over rows to get implicit scaling information for (i = 0; i < 3; i++) { largest = fabs(B[i][0]); if ((tmp = fabs(B[i][1])) > largest) { largest = tmp; } if ((tmp = fabs(B[i][2])) > largest) { largest = tmp; } scale[i] = 1.0; if (largest != 0) { scale[i] = double(1.0)/largest; } } // first column index[0] = 0; largest = scale[0]*fabs(B[0][0]); if ((tmp = scale[1]*fabs(B[1][0])) >= largest) { largest = tmp; index[0] = 1; } if ((tmp = scale[2]*fabs(B[2][0])) >= largest) { index[0] = 2; } if (index[0] != 0) { SwapVectors3(B[index[0]],B[0]); scale[index[0]] = scale[0]; } // second column index[1] = 1; largest = scale[1]*fabs(B[1][1]); if ((tmp = scale[2]*fabs(B[2][1])) >= largest) { index[1] = 2; SwapVectors3(B[2],B[1]); } // third column index[2] = 2; // A quaternian can only describe a pure rotation, not // a rotation with a flip, therefore the flip must be // removed before the matrix is converted to a quaternion. double d = Matrix4x4::Determinant3x3(B); if (d < 0) { for (i = 0; i < 3; i++) { B[0][i] = -B[0][i]; B[1][i] = -B[1][i]; B[2][i] = -B[2][i]; } } // Do orthogonalization using a quaternion intermediate // (this, essentially, does the orthogonalization via // diagonalization of an appropriately constructed symmetric // 4x4 matrix rather than by doing SVD of the 3x3 matrix) double quat[4]; MathFunctions::matrixToQuatern(B,quat); MathFunctions::quaternToMatrix(quat,B); // Put the flip back into the orthogonalized matrix. if (d < 0) { for (i = 0; i < 3; i++) { B[0][i] = -B[0][i]; B[1][i] = -B[1][i]; B[2][i] = -B[2][i]; } } // Undo the pivoting if (index[1] != 1) { SwapVectors3(B[index[1]],B[1]); } if (index[0] != 0) { SwapVectors3(B[index[0]],B[0]); } } /** * Swap elements in vectors. * @param v1 * First vector. * @param v2 * Second vector. */ void Matrix4x4::SwapVectors3(double v1[3], double v2[3]) { double x[3] = { v1[0], v1[1], v1[2] }; v1[0] = v2[0]; v1[1] = v2[1]; v1[2] = v2[2]; v2[0] = x[0]; v2[1] = x[1]; v2[2] = x[2]; } /** * Premultiply by a matrix. * * @param tm Matrix that is used for pre-multiplication. * */ void Matrix4x4::premultiply(const Matrix4x4& tm) { double matrixOut[4][4]; for (int row = 0; row < 4; row++) { matrixOut[row][0] = matrix[row][0] * tm.matrix[0][0] + matrix[row][1] * tm.matrix[1][0] + matrix[row][2] * tm.matrix[2][0] + matrix[row][3] * tm.matrix[3][0]; matrixOut[row][1] = matrix[row][0] * tm.matrix[0][1] + matrix[row][1] * tm.matrix[1][1] + matrix[row][2] * tm.matrix[2][1] + matrix[row][3] * tm.matrix[3][1]; matrixOut[row][2] = matrix[row][0] * tm.matrix[0][2] + matrix[row][1] * tm.matrix[1][2] + matrix[row][2] * tm.matrix[2][2] + matrix[row][3] * tm.matrix[3][2]; matrixOut[row][3] = matrix[row][0] * tm.matrix[0][3] + matrix[row][1] * tm.matrix[1][3] + matrix[row][2] * tm.matrix[2][3] + matrix[row][3] * tm.matrix[3][3]; } setMatrix(matrixOut); this->fixNumericalError(); this->setModified(); } /** * Postmultiply by a matrix. * * @param tm Matrix that is used for post-multiplication. * */ void Matrix4x4::postmultiply(const Matrix4x4& tm) { double matrixOut[4][4]; for (int row = 0; row < 4; row++) { matrixOut[row][0] = tm.matrix[row][0] * matrix[0][0] + tm.matrix[row][1] * matrix[1][0] + tm.matrix[row][2] * matrix[2][0] + tm.matrix[row][3] * matrix[3][0]; matrixOut[row][1] = tm.matrix[row][0] * matrix[0][1] + tm.matrix[row][1] * matrix[1][1] + tm.matrix[row][2] * matrix[2][1] + tm.matrix[row][3] * matrix[3][1]; matrixOut[row][2] = tm.matrix[row][0] * matrix[0][2] + tm.matrix[row][1] * matrix[1][2] + tm.matrix[row][2] * matrix[2][2] + tm.matrix[row][3] * matrix[3][2]; matrixOut[row][3] = tm.matrix[row][0] * matrix[0][3] + tm.matrix[row][1] * matrix[1][3] + tm.matrix[row][2] * matrix[2][3] + tm.matrix[row][3] * matrix[3][3]; } setMatrix(matrixOut); this->fixNumericalError(); this->setModified(); } /** * Get the matrix as 16 element one-dimensional array for use by OpenGL. * * @param m A 16-element array of double. * */ void Matrix4x4::getMatrixForOpenGL(double m[16]) const { m[0] = this->matrix[0][0]; m[1] = this->matrix[1][0]; m[2] = this->matrix[2][0]; m[3] = this->matrix[3][0]; m[4] = this->matrix[0][1]; m[5] = this->matrix[1][1]; m[6] = this->matrix[2][1]; m[7] = this->matrix[3][1]; m[8] = this->matrix[0][2]; m[9] = this->matrix[1][2]; m[10] = this->matrix[2][2]; m[11] = this->matrix[3][2]; m[12] = this->matrix[0][3]; m[13] = this->matrix[1][3]; m[14] = this->matrix[2][3]; m[15] = this->matrix[3][3]; } /** * Set the matrix from a one-dimensional OpenGL Matrix as 16 elements. * * @param m A 16-element array of double containing OpenGL Matrix. * */ void Matrix4x4::setMatrixFromOpenGL(const double m[16]) { this->matrix[0][0] = m[0]; this->matrix[1][0] = m[1]; this->matrix[2][0] = m[2]; this->matrix[3][0] = m[3]; this->matrix[0][1] = m[4]; this->matrix[1][1] = m[5]; this->matrix[2][1] = m[6]; this->matrix[3][1] = m[7]; this->matrix[0][2] = m[8]; this->matrix[1][2] = m[9]; this->matrix[2][2] = m[10]; this->matrix[3][2] = m[11]; this->matrix[0][3] = m[12]; this->matrix[1][3] = m[13]; this->matrix[2][3] = m[14]; this->matrix[3][3] = m[15]; this->setModified(); } /** * Get the matrix as 16 element one-dimensional array for use by OpenGL. * * @param m A 16-element array of double. * */ void Matrix4x4::getMatrixForOpenGL(float m[16]) const { m[0] = this->matrix[0][0]; m[1] = this->matrix[1][0]; m[2] = this->matrix[2][0]; m[3] = this->matrix[3][0]; m[4] = this->matrix[0][1]; m[5] = this->matrix[1][1]; m[6] = this->matrix[2][1]; m[7] = this->matrix[3][1]; m[8] = this->matrix[0][2]; m[9] = this->matrix[1][2]; m[10] = this->matrix[2][2]; m[11] = this->matrix[3][2]; m[12] = this->matrix[0][3]; m[13] = this->matrix[1][3]; m[14] = this->matrix[2][3]; m[15] = this->matrix[3][3]; } /** * Set the matrix from a one-dimensional OpenGL Matrix as 16 elements. * * @param m A 16-element array of double containing OpenGL Matrix. * */ void Matrix4x4::setMatrixFromOpenGL(const float m[16]) { this->matrix[0][0] = m[0]; this->matrix[1][0] = m[1]; this->matrix[2][0] = m[2]; this->matrix[3][0] = m[3]; this->matrix[0][1] = m[4]; this->matrix[1][1] = m[5]; this->matrix[2][1] = m[6]; this->matrix[3][1] = m[7]; this->matrix[0][2] = m[8]; this->matrix[1][2] = m[9]; this->matrix[2][2] = m[10]; this->matrix[3][2] = m[11]; this->matrix[0][3] = m[12]; this->matrix[1][3] = m[13]; this->matrix[2][3] = m[14]; this->matrix[3][3] = m[15]; this->setModified(); } /** * Convert the given vector to an OpenGL rotation matrix. * "This" matrix is set to the identity matrix before createing * the rotation matrix. Use Matrix4x4::getMatrixForOpenGL() * to get the matrix after calling this method and then pass * array to glMultMatrixd(). * * http://lifeofaprogrammergeek.blogspot.com/2008/07/rendering-cylinder-between-two-points.html * * @param vector * The vector. MUST be a unit vector. */ void Matrix4x4::setMatrixToOpenGLRotationFromVector(const float vector[3]) { float vx = vector[0]; float vy = vector[1]; float vz = vector[2]; float z = (float)std::sqrt( vx*vx + vy*vy + vz*vz ); double ax = 0.0f; double zero = 1.0e-3; if (std::abs(vz) < zero) { ax = 57.2957795*std::acos( vx/z ); // rotation angle in x-y plane if ( vx <= 0.0f ) ax = -ax; } else { ax = 57.2957795*std::acos( vz/z ); // rotation angle if ( vz <= 0.0f ) ax = -ax; } float rx = -vy*vz; float ry = vx*vz; if ((std::abs(vx) < zero) && (std::fabs(vz) < zero)) { if (vy > 0) { ax = 90; } } identity(); if (std::abs(vz) < zero) { rotateY(90.0); // Rotate & align with x axis rotateX(-ax); // Rotate to point 2 in x-y plane } else { rotate(ax, rx, ry, 0.0); // Rotate about rotation vector } } /** * Transpose the matrix. * */ void Matrix4x4::transpose() { double m[4][4]; m[0][0] = matrix[0][0]; m[0][1] = matrix[1][0]; m[0][2] = matrix[2][0]; m[0][3] = matrix[3][0]; m[1][0] = matrix[0][1]; m[1][1] = matrix[1][1]; m[1][2] = matrix[2][1]; m[1][3] = matrix[3][1]; m[2][0] = matrix[0][2]; m[2][1] = matrix[1][2]; m[2][2] = matrix[2][2]; m[2][3] = matrix[3][2]; m[3][0] = matrix[0][3]; m[3][1] = matrix[1][3]; m[3][2] = matrix[2][3]; m[3][3] = matrix[3][3]; setMatrix(m); this->setModified(); } /** * Set the matrix. * * @param m A 4x4 array of doubles. * */ void Matrix4x4::setMatrix(const double m[4][4]) { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { matrix[i][j] = m[i][j]; } } this->setModified(); } /** * Get the matrix. * * @param m A 4x4 array of doubles. * */ void Matrix4x4::getMatrix(double m[4][4]) const { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { m[i][j] = matrix[i][j]; } } } /** * Set the matrix. * * @param m A 4x4 array of float. * */ void Matrix4x4::setMatrix(const float m[4][4]) { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { matrix[i][j] = m[i][j]; } } this->setModified(); } /** * Get the matrix. * * @param m A 4x4 array of floats. * */ void Matrix4x4::getMatrix(float m[4][4]) const { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { m[i][j] = matrix[i][j]; } } } /** * Set the matrix. * * @param cm A Matrix. * */ void Matrix4x4::setMatrix(const Matrix4x4& cm) { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { matrix[i][j] = cm.matrix[i][j]; } } this->setModified(); } void Matrix4x4::multiplyPoint3(float p[3]) const { float pout[3] = { 0.0f, 0.0f, 0.0f }; for (int row = 0; row < 3; row++) { pout[row] = (float)(this->matrix[row][0] * p[0] + this->matrix[row][1] * p[1] + this->matrix[row][2] * p[2] + this->matrix[row][3]); } p[0] = pout[0]; p[1] = pout[1]; p[2] = pout[2]; } void Matrix4x4::multiplyPoint4(float p[4]) const { float pout[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; for (int row = 0; row < 4; row++) { pout[row] = (float)(this->matrix[row][0] * p[0] + this->matrix[row][1] * p[1] + this->matrix[row][2] * p[2] + this->matrix[row][3] * p[3]); } p[0] = pout[0]; p[1] = pout[1]; p[2] = pout[2]; p[3] = pout[3]; } void Matrix4x4::multiplyPoint3X3(float p[3]) const { float pout[3] = { 0.0f, 0.0f, 0.0f }; for (int row = 0; row < 3; row++) { pout[row] = (float)(this->matrix[row][0] * p[0] + this->matrix[row][1] * p[1] + this->matrix[row][2] * p[2]); } p[0] = pout[0]; p[1] = pout[1]; p[2] = pout[2]; } /** * Get the data space name (used by GIFTI). * * @return Name of data space. * */ AString Matrix4x4::getDataSpaceName() const { return dataSpaceName; } /** * Set the data space name (used by GIFTI). * * @param name Name of data space. * */ void Matrix4x4::setDataSpaceName(const AString& name) { dataSpaceName = name; this->setModified(); } /** * Get the transformed space name (used by GIFTI). * * @return Name of transformed space. * */ AString Matrix4x4::getTransformedSpaceName() const { return transformedSpaceName; } /** * Set the transformed space name (used by GIFTI). * * @param name Name of transformed space. * */ void Matrix4x4::setTransformedSpaceName(const AString& name) { transformedSpaceName = name; this->setModified(); } /** * Set a matrix element. * @param i Row * @param j Column * @return value at [i][j] * */ double Matrix4x4::getMatrixElement( const int32_t i, const int32_t j) const { return this->matrix[i][j]; } /** * Set a matrix element. * @param i Row * @param j Column * @param e New Value * */ void Matrix4x4::setMatrixElement( const int32_t i, const int32_t j, const double e) { this->matrix[i][j] = e; this->setModified(); } /** * Determine if two matrices are approximately equal. * @param m - matrix to compare. * @param error - Maximum difference between a matrix element. * @return true if all corresponding elements differ by less than "error", * else false. * */ bool Matrix4x4::compare( const Matrix4x4& m, const float error) { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { float diff = (float)std::abs(this->matrix[i][j] - m.matrix[i][j]); if (diff > error) { return false; } } } return true; } double Matrix4x4::fixZero(const double f) { if (f > SMALL_POSITIVE_NUMBER) { return f; } else if (f < SMALL_NEGATIVE_NUMBER) { return f; } return 0.0; } /** * Fix numerical error such as very tiny numbers and "negative zeros". * */ void Matrix4x4::fixNumericalError() { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { double f = fixZero(this->matrix[i][j]); this->matrix[i][j] = f; } } this->setModified(); } /** * Invert a matrix. * @return true if inversion of matrix is successful, * else false. * */ bool Matrix4x4::invert() { double m[4][4]; const bool valid = Matrix4x4::Inverse(this->matrix, m); if (valid) { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { this->matrix[i][j] = m[i][j]; } } this->setModified(); return true; } else { CaretLogWarning("Matrix inversion failed for " + this->toString()); return false; } } /** * Inverse from VTK */ bool Matrix4x4::Inverse(const double a[4][4], double matrixOut[4][4]) const { /////SqMatPtr outElem = (SqMatPtr)outElements; // inverse( original_matrix, inverse_matrix ) // calculate the inverse of a 4x4 matrix // // -1 // A = ___1__ adjoint A // det A // // calculate the 4x4 determinent // if the determinent is zero, // then the inverse matrix is not unique. const double det = Determinant4x4(a); if ( det == 0.0 ) { return false; } // calculate the adjoint matrix Adjoint(a, matrixOut); //vtkMatrix4x4::Adjoint(inElements, outElements ); // scale the adjoint matrix to get the inverse for (int i=0; i<4; i++) { for(int j=0; j<4; j++) { matrixOut[i][j] = matrixOut[i][j] / det; } } return true; } /** * Adjoint from vtkMatrix4x4::Adjoint */ void Matrix4x4::Adjoint(const double inputMatrix[4][4], double outputMatrix[4][4]) const { // // adjoint( original_matrix, inverse_matrix ) // // calculate the adjoint of a 4x4 matrix // // Let a denote the minor determinant of matrix A obtained by // ij // // deleting the ith row and jth column from A. // // i+j // Let b = (-1) a // ij ji // // The matrix B = (b ) is the adjoint of A // ij // double a1, a2, a3, a4, b1, b2, b3, b4; double c1, c2, c3, c4, d1, d2, d3, d4; // assign to individual variable names to aid // selecting correct values a1 = inputMatrix[0][0]; b1 = inputMatrix[0][1]; c1 = inputMatrix[0][2]; d1 = inputMatrix[0][3]; a2 = inputMatrix[1][0]; b2 = inputMatrix[1][1]; c2 = inputMatrix[1][2]; d2 = inputMatrix[1][3]; a3 = inputMatrix[2][0]; b3 = inputMatrix[2][1]; c3 = inputMatrix[2][2]; d3 = inputMatrix[2][3]; a4 = inputMatrix[3][0]; b4 = inputMatrix[3][1]; c4 = inputMatrix[3][2]; d4 = inputMatrix[3][3]; // row column labeling reversed since we transpose rows & columns outputMatrix[0][0] = Matrix4x4::Determinant3x3( b2, b3, b4, c2, c3, c4, d2, d3, d4); outputMatrix[1][0] = - Matrix4x4::Determinant3x3( a2, a3, a4, c2, c3, c4, d2, d3, d4); outputMatrix[2][0] = Matrix4x4::Determinant3x3( a2, a3, a4, b2, b3, b4, d2, d3, d4); outputMatrix[3][0] = - Matrix4x4::Determinant3x3( a2, a3, a4, b2, b3, b4, c2, c3, c4); outputMatrix[0][1] = - Matrix4x4::Determinant3x3( b1, b3, b4, c1, c3, c4, d1, d3, d4); outputMatrix[1][1] = Matrix4x4::Determinant3x3( a1, a3, a4, c1, c3, c4, d1, d3, d4); outputMatrix[2][1] = - Matrix4x4::Determinant3x3( a1, a3, a4, b1, b3, b4, d1, d3, d4); outputMatrix[3][1] = Matrix4x4::Determinant3x3( a1, a3, a4, b1, b3, b4, c1, c3, c4); outputMatrix[0][2] = Matrix4x4::Determinant3x3( b1, b2, b4, c1, c2, c4, d1, d2, d4); outputMatrix[1][2] = - Matrix4x4::Determinant3x3( a1, a2, a4, c1, c2, c4, d1, d2, d4); outputMatrix[2][2] = Matrix4x4::Determinant3x3( a1, a2, a4, b1, b2, b4, d1, d2, d4); outputMatrix[3][2] = - Matrix4x4::Determinant3x3( a1, a2, a4, b1, b2, b4, c1, c2, c4); outputMatrix[0][3] = - Matrix4x4::Determinant3x3( b1, b2, b3, c1, c2, c3, d1, d2, d3); outputMatrix[1][3] = Matrix4x4::Determinant3x3( a1, a2, a3, c1, c2, c3, d1, d2, d3); outputMatrix[2][3] = - Matrix4x4::Determinant3x3( a1, a2, a3, b1, b2, b3, d1, d2, d3); outputMatrix[3][3] = Matrix4x4::Determinant3x3( a1, a2, a3, b1, b2, b3, c1, c2, c3); } /** * From vtkMatrix4x4::Determinant */ double Matrix4x4::Determinant4x4(const double matrixIn[4][4]) const { double a1, a2, a3, a4, b1, b2, b3, b4, c1, c2, c3, c4, d1, d2, d3, d4; // assign to individual variable names to aid selecting // correct elements a1 = matrixIn[0][0]; b1 = matrixIn[0][1]; c1 = matrixIn[0][2]; d1 = matrixIn[0][3]; a2 = matrixIn[1][0]; b2 = matrixIn[1][1]; c2 = matrixIn[1][2]; d2 = matrixIn[1][3]; a3 = matrixIn[2][0]; b3 = matrixIn[2][1]; c3 = matrixIn[2][2]; d3 = matrixIn[2][3]; a4 = matrixIn[3][0]; b4 = matrixIn[3][1]; c4 = matrixIn[3][2]; d4 = matrixIn[3][3]; const double result = a1 * Matrix4x4::Determinant3x3( b2, b3, b4, c2, c3, c4, d2, d3, d4) - b1 * Matrix4x4::Determinant3x3( a2, a3, a4, c2, c3, c4, d2, d3, d4) + c1 * Matrix4x4::Determinant3x3( a2, a3, a4, b2, b3, b4, d2, d3, d4) - d1 * Matrix4x4::Determinant3x3( a2, a3, a4, b2, b3, b4, c2, c3, c4); return result; } double Matrix4x4::Determinant3x3(double a1, double a2, double a3, double b1, double b2, double b3, double c1, double c2, double c3) { return ( a1 * Matrix4x4::Determinant2x2( b2, b3, c2, c3 ) - b1 * Matrix4x4::Determinant2x2( a2, a3, c2, c3 ) + c1 * Matrix4x4::Determinant2x2( a2, a3, b2, b3 ) ); } double Matrix4x4::Determinant2x2(double a, double b, double c, double d) { return (a * d - b * c); }; double Matrix4x4::Determinant3x3(double A[3][3]) { return A[0][0] * A[1][1] * A[2][2] + A[1][0] * A[2][1] * A[0][2] + A[2][0] * A[0][1] * A[1][2] - A[0][0] * A[2][1] * A[1][2] - A[1][0] * A[0][1] * A[2][2] - A[2][0] * A[1][1] * A[0][2]; } /** * Write the matrix as a GIFTI matrix using the given XML tags. * * @param xmlWriter * The XML writer * @param xmlMatrixTag * XML tag for for the matrix and its components. * @param xmlDataSpaceTag * XML tag for for the data space name. * @param xmlTransformedSpaceTag * XML tag for for the transformed space name. * @param xmlMatrixDataTag * XML tag for the matrix data. * * @throws XmlException * If an error occurs while writing. */ void Matrix4x4::writeAsGiftiXML(XmlWriter& xmlWriter, const AString& xmlMatrixTag, const AString& xmlDataSpaceTag, const AString& xmlTransformedSpaceTag, const AString& xmlMatrixDataTag) { xmlWriter.writeStartElement(xmlMatrixTag); xmlWriter.writeElementCData(xmlDataSpaceTag, this->dataSpaceName); xmlWriter.writeElementCData(xmlTransformedSpaceTag, this->transformedSpaceName); /* * Note: Matrix4x4 is column major order but GIFTI uses * row major order so transpose matrix as values are * written. */ xmlWriter.writeStartElement(xmlMatrixDataTag); for (int32_t iRow = 0; iRow < 4; iRow++) { for (int32_t jCol = 0; jCol < 4; jCol++) { /* * Transpose indices */ const int32_t i = jCol; const int32_t j = iRow; const AString txt = (AString::number(this->matrix[i][j]) + " "); if (jCol == 0) { xmlWriter.writeCharactersWithIndent(txt); } else { xmlWriter.writeCharacters(txt); } if (jCol == 3) { xmlWriter.writeCharacters("\n"); } } } xmlWriter.writeEndElement(); xmlWriter.writeEndElement(); } /** * Convert the matrix into a string representation. * * @return String representation of the matrix. * */ AString Matrix4x4::toString() const { return toFormattedString(" "); } /** * Get a nicely formatted string for printing. * * @param indentation - use as indentation. * @return String containing label information. * */ AString Matrix4x4::toFormattedString(const AString& indentation) const { float translation[3]; getTranslation(translation); double rotation[3]; getRotation(rotation[0], rotation[1], rotation[2]); double scale[3]; getScale(scale[0], scale[1], scale[2]); const AString s("Matrix4x4: \n" + indentation + "Translation: " + AString::fromNumbers(translation, 3, ", ") + "\n" + indentation + "Rotation: " + AString::fromNumbers(rotation, 3, ", ") + "\n" + indentation + "Scale: " + AString::fromNumbers(scale, 3, ", ")); return s; } /** * Set this object has been modified. * */ void Matrix4x4::setModified() { this->modifiedFlag = true; } /** * Set this object as not modified. Object should also * clear the modification status of its children. * */ void Matrix4x4::clearModified() { this->modifiedFlag = false; } /** * Get the modification status. Returns true if this object or * any of its children have been modified. * @return - The modification status. * */ bool Matrix4x4::isModified() const { return this->modifiedFlag; } workbench-1.1.1/src/Nifti/Matrix4x4.h000066400000000000000000000143061255417355300173270ustar00rootroot00000000000000#ifndef __MATRIX4X4_H__ #define __MATRIX4X4_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "XmlException.h" #include #include namespace caret { class XmlWriter; /** * A 4x4 homogeneous transformation matrix. */ class Matrix4x4 : public CaretObject { public: Matrix4x4(); public: Matrix4x4(const Matrix4x4& o); Matrix4x4& operator=(const Matrix4x4& o); virtual ~Matrix4x4(); private: void copyHelper(const Matrix4x4& o); void initializeMembersMatrix4x4(); public: void identity(); void getTranslation(float translatationOut[3]) const; void setTranslation(const float t[]); void setTranslation( const double tx, const double ty, const double tz); void translate( const double tx, const double ty, const double tz); void scale( const double sx, const double sy, const double sz); void getScale(double& scaleOutX, double& scaleOutY, double& scaleOutZ) const; void rotateX(const double degrees); void rotateY(const double degrees); void rotateZ(const double degrees); void rotate( const double angle, const double vector[]); void rotate( const double angle, const double x, const double y, const double z); void getRotation(double& rotationOutX, double& rotationOutY, double& rotationOutZ) const; void setRotation(const double rotationX, const double rotationY, const double rotationZ); void premultiply(const Matrix4x4& tm); void postmultiply(const Matrix4x4& tm); void getMatrixForOpenGL(double m[16]) const; void setMatrixFromOpenGL(const double m[16]); void getMatrixForOpenGL(float m[16]) const; void setMatrixFromOpenGL(const float m[16]); void setMatrixToOpenGLRotationFromVector(const float vector[3]); void transpose(); void setMatrix(const double m[4][4]); void getMatrix(double m[4][4]) const; void setMatrix(const float m[4][4]); void getMatrix(float m[4][4]) const; void setMatrix(const Matrix4x4& cm); void multiplyPoint4(float p[4]) const; void multiplyPoint3(float p[3]) const; void multiplyPoint3X3(float p[3]) const; AString getDataSpaceName() const; void setDataSpaceName(const AString& name); AString getTransformedSpaceName() const; void setTransformedSpaceName(const AString& name); double getMatrixElement( const int32_t i, const int32_t j) const; void setMatrixElement( const int32_t i, const int32_t j, const double e); bool compare( const Matrix4x4& m, const float error); bool invert(); AString toString() const; AString toFormattedString(const AString& indentation) const; void setModified(); void clearModified(); bool isModified() const; void writeAsGiftiXML(XmlWriter& xmlWriter, const AString& xmlMatrixTag, const AString& xmlDataSpaceTag, const AString& xmlTransformedSpaceTag, const AString& xmlMatrixDataTag); private: double fixZero(const double f); void fixNumericalError(); void Transpose(const double input[4][4], double output[4][4]) const; bool Inverse(const double inputMatrix[4][4], double outputMatrix[4][4]) const; void Adjoint(const double inputMatrix[4][4], double outputMatrix[4][4]) const; void UpperTriangle4(const double inputMatrix[4][4], double outputMatrix[4][4]) const; void UpperTriangle3(const double inputMatrix[3][3], double outputMatrix[3][3]) const; double Determinant4x4(const double matrix[4][4]) const; static double Determinant3x3(double A[3][3]); double Determinant3(const double matrix[3][3]) const; static double Determinant3x3(double a1, double a2, double a3, double b1, double b2, double b3, double c1, double c2, double c3); static double Determinant2x2(double a, double b, double c, double d); static void Orthogonalize3x3(const double A[3][3], double B[3][3]); static void SwapVectors3(double v1[3], double v2[3]); static void SingularValueDecomposition3x3(const double A[3][3], double U[3][3], double w[3], double VT[3][3]); static void Transpose3x3(const double A[3][3], double AT[3][3]); static void Multiply3x3(const double A[3][3], const double B[3][3], double C[3][3]); static void Diagonalize3x3(const double A[3][3], double w[3], double V[3][3]); static int JacobiN(double **a, int n, double *w, double **v); protected: /**the 4x4 matrix */ double matrix[4][4]; /**data space name (used by GIFTI) */ AString dataSpaceName; /**transformed space name (used by GIFTI) */ AString transformedSpaceName; private: /**data modification status (DO NOT CLONE) */ bool modifiedFlag; }; } // namespace #endif // __MATRIX4X4_H__ workbench-1.1.1/src/Nifti/NiftiHeader.cxx000066400000000000000000001043121255417355300202550ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "NiftiHeader.h" #include "ByteSwapping.h" #include "CaretAssert.h" #include "CaretLogger.h" #include "DataFileException.h" #include "FloatMatrix.h" #include "MathFunctions.h" #include #include #include using namespace std; using namespace caret; NiftiHeader::NiftiHeader() { if (sizeof(nifti_1_header) != 348 || sizeof(nifti_2_header) != 540)//these should be made into static asserts when we move to c++11 or decide to use boost { throw DataFileException("internal error: nifti header structs are the wrong size");//this is not a runtime assert, because we want this checked in release builds too } memset(&m_header, 0, sizeof(m_header)); for (int i = 0; i < 8; ++i) m_header.dim[i] = 1;//we don't support 0-dimensional nifti, and we maintain 1s on unused dimensions to make some stupid nifti readers happy m_header.xyzt_units = SPACE_TIME_TO_XYZT(NIFTI_UNITS_MM, NIFTI_UNITS_SEC); m_header.scl_slope = 1.0;//default to identity scaling m_header.scl_inter = 0.0; m_header.datatype = NIFTI_TYPE_FLOAT32; m_header.bitpix = typeToNumBits(m_header.datatype); m_version = 0; m_isSwapped = false; } AbstractHeader* NiftiHeader::clone() const { return new NiftiHeader(*this); } NiftiHeader::NiftiHeader(const NiftiHeader& rhs) { m_header = rhs.m_header; m_isSwapped = rhs.m_isSwapped; m_version = rhs.m_version; m_extensions.reserve(rhs.m_extensions.size()); for (size_t i = 0; i < rhs.m_extensions.size(); ++i) { m_extensions.push_back(CaretPointer(new NiftiExtension(*(rhs.m_extensions[i])))); } } NiftiHeader& NiftiHeader::operator=(const NiftiHeader& rhs) { if (this == &rhs) return *this; m_header = rhs.m_header; m_isSwapped = rhs.m_isSwapped; m_version = rhs.m_version; m_extensions.clear(); m_extensions.reserve(rhs.m_extensions.size()); for (size_t i = 0; i < rhs.m_extensions.size(); ++i) { m_extensions.push_back(CaretPointer(new NiftiExtension(*(rhs.m_extensions[i])))); } return *this; } bool NiftiHeader::canWriteVersion(const int& version) const { if (computeVoxOffset(version) < 0) return false;//error condition, can happen if an extension is longer than 2^31 if (version == 2) return true;//our internal state is nifti-2, return early if (version != 1) return false;//we can only write 1 and 2 vector dims = getDimensions(); for (int i = 0; i < (int)dims.size(); ++i) { if (dims[i] > numeric_limits::max()) return false; } if (m_header.intent_code > numeric_limits::max() || m_header.intent_code < numeric_limits::min()) return false; if (m_header.slice_code > numeric_limits::max() || m_header.slice_code < numeric_limits::min()) return false; if (m_header.xyzt_units > numeric_limits::max() || m_header.xyzt_units < numeric_limits::min()) return false; if (m_header.qform_code > numeric_limits::max() || m_header.qform_code < numeric_limits::min()) return false; if (m_header.sform_code > numeric_limits::max() || m_header.sform_code < numeric_limits::min()) return false; return true; } int64_t NiftiHeader::computeVoxOffset(const int& version) const { int64_t ret; switch (version) { case 1: ret = 4 + sizeof(nifti_1_header);//the 4 is the extender bytes break; case 2: ret = 4 + sizeof(nifti_2_header); break; default: return -1; } int numExtensions = (int)m_extensions.size(); for (int i = 0; i < numExtensions; ++i) { CaretAssert(m_extensions[i] != NULL); int64_t thisSize = 8 + m_extensions[i]->m_bytes.size();//8 is for the int32_t size and ecode for nifti-1 style extensions if (thisSize % 16 != 0)//round up to nearest multiple of 16 { int paddingBytes = 16 - (thisSize % 16); thisSize += paddingBytes; } if (thisSize > numeric_limits::max()) return -1;//since we don't have nifti-2 style extensions yet, always fail ret += thisSize; } if (version == 1)//need to put it into a float exactly (yes, really) { float temp = ret; if (ret != (int64_t)temp) return -1;//for now, just fail, until it actually becomes a problem } return ret; } bool NiftiHeader::getDataScaling(double& mult, double& offset) const { if (m_header.datatype == NIFTI_TYPE_RGB24 || m_header.scl_slope == 0.0 || (m_header.scl_slope == 1.0 && m_header.scl_inter == 0.0))//the "if slope is zero" case is in the nifti spec { mult = 1.0;//in case someone ignores the boolean offset = 0.0; return false; } mult = m_header.scl_slope; offset = m_header.scl_inter; return true; } vector NiftiHeader::getDimensions() const { CaretAssert(m_header.dim[0] >= 0 && m_header.dim[0] <= 7);//because storage is private and initialized to zero, so it should never be invalid vector ret(m_header.dim[0]); for (int i = 0; i < m_header.dim[0]; ++i) { ret[i] = m_header.dim[i + 1]; } return ret; } vector > NiftiHeader::getFSLSpace() const {//don't look at me, blame analyze and flirt vector dimensions = getDimensions(); if (dimensions.size() < 3) throw DataFileException("NiftiHeaderIO has less than 3 dimensions, can't generate the FSL space for it"); FloatMatrix ret; vector > sform = getSForm(); float determinant = sform[0][0] * sform[1][1] * sform[2][2] + sform[0][1] * sform[1][2] * sform[2][0] + sform[0][2] * sform[1][0] * sform[2][1] - sform[0][2] * sform[1][1] * sform[2][0] - sform[0][0] * sform[1][2] * sform[2][1] - sform[0][1] * sform[1][0] * sform[2][2];//just write out the 3x3 determinant rather than packing it into a FloatMatrix first - and I haven't put a determinant function in yet ret = FloatMatrix::identity(4);//generate a 4x4 with 0 0 0 1 last row via FloatMatrix for convenience if (determinant > 0.0f) { ret[0][0] = -m_header.pixdim[1];//yes, they really use pixdim, despite checking the SForm/QForm for flipping - ask them, not me ret[0][3] = (dimensions[0] - 1) * m_header.pixdim[1];//note - pixdim[1] is for i, pixdim[0] is qfac } else { ret[0][0] = m_header.pixdim[1]; } ret[1][1] = m_header.pixdim[2]; ret[2][2] = m_header.pixdim[3]; int32_t spaceUnit = XYZT_TO_SPACE(m_header.xyzt_units); switch (spaceUnit) { case NIFTI_UNITS_METER: ret *= 1000.0f; ret[3][3] = 1.0f; break; case NIFTI_UNITS_MICRON: ret *= 0.001f; ret[3][3] = 1.0f; break; case 0://will already have warned in getSForm() case NIFTI_UNITS_MM: break; default: break;//will already have warned in getSForm() } return ret.getMatrix(); } bool NiftiHeader::operator==(const NiftiHeader& rhs) const { if (m_version != rhs.m_version) return false;//this is to test for consistency, not to test if two headers mean the same thing if (m_isSwapped != rhs.m_isSwapped) return false; return memcmp(&m_header, &(rhs.m_header), sizeof(m_header)) == 0; } vector > NiftiHeader::getSForm() const { FloatMatrix ret = FloatMatrix::zeros(4, 4); ret[3][3] = 1.0f;//force 0 0 0 1 last row if (m_header.sform_code != 0)//prefer sform { for(int i = 0; i < 4; i++) { ret[0][i] = m_header.srow_x[i]; ret[1][i] = m_header.srow_y[i]; ret[2][i] = m_header.srow_z[i]; } } else if (m_header.qform_code != 0) {//fall back to qform float rotmat[3][3], quat[4]; quat[1] = m_header.quatern_b; quat[2] = m_header.quatern_c; quat[3] = m_header.quatern_d; float checkquat = quat[1] * quat[1] + quat[2] * quat[2] + quat[3] * quat[3]; if (checkquat <= 1.01f)//make sure qform is sane { if (checkquat > 1.0f) { quat[0] = 0.0f; } else { quat[0] = sqrt(1.0f - checkquat); } MathFunctions::quaternToMatrix(quat, rotmat); for (int i = 0; i < 3; ++i) { for (int j = 0; j < 3; ++j) { rotmat[i][j] *= m_header.pixdim[i + 1]; } } if (m_header.pixdim[0] < 0.0f)//left handed coordinate system, flip the kvec { rotmat[0][2] = -rotmat[0][2]; rotmat[1][2] = -rotmat[1][2]; rotmat[2][2] = -rotmat[2][2]; } for (int i = 0; i < 3; ++i) { for (int j = 0; j < 3; ++j) { ret[i][j] = rotmat[i][j]; } } ret[0][3] = m_header.qoffset_x; ret[1][3] = m_header.qoffset_y; ret[2][3] = m_header.qoffset_z; } else { CaretLogWarning("found quaternion with length greater than 1 in nifti header"); ret[0][0] = m_header.pixdim[1]; ret[1][1] = m_header.pixdim[2]; ret[2][2] = m_header.pixdim[3]; } } else {//fall back to analyze and complain CaretLogWarning("no sform or qform code found, using ANALYZE coordinates!"); ret[0][0] = m_header.pixdim[1]; ret[1][1] = m_header.pixdim[2]; ret[2][2] = m_header.pixdim[3]; } int32_t spaceUnit = XYZT_TO_SPACE(m_header.xyzt_units); switch (spaceUnit) { case NIFTI_UNITS_METER: ret *= 1000.0f; ret[3][3] = 1.0f; break; case NIFTI_UNITS_MICRON: ret *= 0.001f; ret[3][3] = 1.0f; break; case 0: CaretLogFine("found spatial unit of '0' in nifti header, assuming millimeters"); case NIFTI_UNITS_MM: break; default: CaretLogWarning("unrecognized spatial unit in nifti header"); } return ret.getMatrix(); } QString NiftiHeader::toString() const { QString ret; if (isSwapped()) { ret += "native endian: false\n"; } else { ret += "native endian: true\n"; } ret += "sizeof_hdr: " + QString::number(m_header.sizeof_hdr) + "\n";//skip the fields that aren't important, like intent_p1, cal_max, etc ret += "magic: " + QByteArray(m_header.magic, 8);//quirk: QByteArray supports embedded nulls, so adding "\n" here doesn't result in a newline in the string ret += "\ndatatype: " + QString::number(m_header.datatype) + "\n"; ret += "bitpix: " + QString::number(m_header.bitpix) + "\n"; CaretAssert(m_header.dim[0] < 8); for (int i = 0; i <= m_header.dim[0]; ++i) { ret += "dim[" + QString::number(i) + "]: " + QString::number(m_header.dim[i]) + "\n"; } for (int i = 0; i <= m_header.dim[0]; ++i) { ret += "pixdim[" + QString::number(i) + "]: " + QString::number(m_header.pixdim[i]) + "\n"; } ret += "vox_offset: " + QString::number(m_header.vox_offset) + "\n"; ret += "scl_slope: " + QString::number(m_header.scl_slope) + "\n"; ret += "scl_inter: " + QString::number(m_header.scl_inter) + "\n"; ret += "sform_code: " + QString::number(m_header.sform_code) + "\n"; if (m_header.sform_code != NIFTI_XFORM_UNKNOWN) { ret += "srow_x:"; for (int i = 0; i < 4; ++i) { ret += " " + QString::number(m_header.srow_x[i]); } ret += "\nsrow_y:"; for (int i = 0; i < 4; ++i) { ret += " " + QString::number(m_header.srow_y[i]); } ret += "\nsrow_z:"; for (int i = 0; i < 4; ++i) { ret += " " + QString::number(m_header.srow_z[i]); } ret += "\n"; } ret += "qform_code: " + QString::number(m_header.qform_code) + "\n"; if (m_header.qform_code != NIFTI_XFORM_UNKNOWN) { ret += "quatern_b: " + QString::number(m_header.quatern_b) + "\n"; ret += "quatern_c: " + QString::number(m_header.quatern_c) + "\n"; ret += "quatern_d: " + QString::number(m_header.quatern_d) + "\n"; ret += "qoffset_x: " + QString::number(m_header.qoffset_x) + "\n"; ret += "qoffset_y: " + QString::number(m_header.qoffset_y) + "\n"; ret += "qoffset_z: " + QString::number(m_header.qoffset_z) + "\n"; } ret += "xyzt_units: " + QString::number(m_header.xyzt_units) + "\n"; ret += "intent_code: " + QString::number(m_header.intent_code) + "\n"; ret += "intent_name: " + QByteArray(m_header.intent_name, 16);//same quirk ret += "\n"; int numExts = (int)m_extensions.size(); ret += QString::number(numExts) + " extension"; if (numExts != 1) ret += "s"; if (numExts == 0) { ret += "\n"; } else { ret += ":\n"; for (int i = 0; i < numExts; ++i) { CaretAssert(m_extensions[i] != NULL); ret += "\n"; ret += "code: " + QString::number(m_extensions[i]->m_ecode) + "\n"; ret += "length: " + QString::number(m_extensions[i]->m_bytes.size()) + "\n"; } } return ret; } void NiftiHeader::setDataType(const int16_t& type) { m_header.bitpix = typeToNumBits(m_header.datatype);//to check for errors m_header.datatype = type; } void NiftiHeader::setDimensions(const vector& dimsIn) { if (dimsIn.size() > 7 || dimsIn.empty()) throw DataFileException("Number of dimensions must be between 1 and 7, inclusive."); m_header.dim[0] = dimsIn.size(); int i = 0; for(; i < (int)dimsIn.size(); i++) { if (dimsIn[i] < 1) throw DataFileException("all dimension lengths must be positive");//maybe these should be asserts? m_header.dim[i + 1] = dimsIn[i]; } for (; i < 7; ++i) { m_header.dim[i + 1] = 1;//we maintain 1s on unused dimensions to make some stupid nifti readers happy } } void NiftiHeader::setIntent(const int32_t& code, const char name[16]) { m_header.intent_code = code; int i;//custom strncpy-like code to fill nulls to the end for (i = 0; i < 16 && name[i] != '\0'; ++i) m_header.intent_name[i] = name[i]; for (; i < 16; ++i) m_header.intent_name[i] = '\0'; } void NiftiHeader::setSForm(const vector >& sForm) { CaretAssert(sForm.size() >= 3);//programmer error to pass badly sized matrix if (sForm.size() < 3) throw DataFileException("internal error: setSForm matrix badly sized");//but make release also throw for (int i = 0; i < (int)sForm.size(); i++) { CaretAssert(sForm[i].size() >= 4);//ditto if (sForm[i].size() < 4) throw DataFileException("internal error: setSForm matrix badly sized"); } m_header.xyzt_units = SPACE_TIME_TO_XYZT(NIFTI_UNITS_MM, NIFTI_UNITS_SEC);//overwrite whatever units we read in for (int i = 0; i < 4; i++) { m_header.srow_x[i] = sForm[0][i]; m_header.srow_y[i] = sForm[1][i]; m_header.srow_z[i] = sForm[2][i]; } m_header.sform_code = NIFTI_XFORM_MNI_152; Vector3D ivec, jvec, kvec; ivec[0] = sForm[0][0]; ivec[1] = sForm[1][0]; ivec[2] = sForm[2][0]; jvec[0] = sForm[0][1]; jvec[1] = sForm[1][1]; jvec[2] = sForm[2][1]; kvec[0] = sForm[0][2]; kvec[1] = sForm[1][2]; kvec[2] = sForm[2][2]; m_header.pixdim[0] = 1.0f; m_header.pixdim[1] = ivec.length(); m_header.pixdim[2] = jvec.length(); m_header.pixdim[3] = kvec.length(); ivec = ivec.normal(); jvec = jvec.normal(); kvec = kvec.normal(); if (kvec.dot(ivec.cross(jvec)) < 0.0f)//left handed sform! { m_header.pixdim[0] = -1.0f; kvec = -kvec;//because to nifti, "left handed" apparently means "up is down", not "left is right" } float rotmat[3][3]; rotmat[0][0] = ivec[0]; rotmat[1][0] = jvec[0]; rotmat[2][0] = kvec[0]; rotmat[0][1] = ivec[1]; rotmat[1][1] = jvec[1]; rotmat[2][1] = kvec[1]; rotmat[0][2] = ivec[2]; rotmat[1][2] = jvec[2]; rotmat[2][2] = kvec[2]; float quat[4]; if (!MathFunctions::matrixToQuatern(rotmat, quat)) { m_header.qform_code = NIFTI_XFORM_UNKNOWN;//0, implies that there is no qform m_header.quatern_b = 0.0;//set dummy values anyway m_header.quatern_c = 0.0; m_header.quatern_d = 0.0; m_header.qoffset_x = sForm[0][3]; m_header.qoffset_y = sForm[1][3]; m_header.qoffset_z = sForm[2][3]; } else { m_header.qform_code = NIFTI_XFORM_MNI_152; m_header.quatern_b = quat[1]; m_header.quatern_c = quat[2]; m_header.quatern_d = quat[3]; m_header.qoffset_x = sForm[0][3]; m_header.qoffset_y = sForm[1][3]; m_header.qoffset_z = sForm[2][3]; } } void NiftiHeader::clearDataScaling() { m_header.scl_slope = 1.0; m_header.scl_inter = 0.0; } void NiftiHeader::setDataScaling(const double& mult, const double& offset) { m_header.scl_slope = mult; m_header.scl_inter = offset; } void NiftiHeader::read(CaretBinaryFile& inFile) { nifti_1_header buffer1; nifti_2_header buffer2; inFile.read(&buffer1, sizeof(nifti_1_header)); int version = NIFTI2_VERSION(buffer1); bool swapped = false; try { if (version == 2) { memcpy(&buffer2, &buffer1, sizeof(nifti_1_header)); inFile.read(((char*)&buffer2) + sizeof(nifti_1_header), sizeof(nifti_2_header) - sizeof(nifti_1_header)); if (NIFTI2_NEEDS_SWAP(buffer2)) { swapped = true; swapHeaderBytes(buffer2); } setupFrom(buffer2); } else if (version == 1) { if (NIFTI2_NEEDS_SWAP(buffer1))//yes, this works on nifti-1 also { swapped = true; swapHeaderBytes(buffer1); } setupFrom(buffer1); } else { throw DataFileException(inFile.getFilename() + " is not a valid NIfTI file"); } } catch (DataFileException& e) {//catch and throw in order to add filename info throw DataFileException("error reading NIfTI file " + inFile.getFilename() + ": " + e.whatString()); } m_extensions.clear(); char extender[4]; inFile.read(extender, 4); int extensions = 0;//if it has extensions in a format we don't know about, don't try to read them if (version == 1 && extender[0] != 0) extensions = 1;//sadly, this is the only thing nifti-1 says about the extender bytes if (version == 2 && extender[0] == 1 && extender[1] == 0 && extender[2] == 0 && extender[3] == 0) extensions = 1;//from http://nifti.nimh.nih.gov/nifti-2 as of 4/4/2014: if (extensions == 1)//"extentions match those of NIfTI-1.1 when the extender bytes are 1 0 0 0" { int64_t extStart; if (version == 1) { extStart = 352; } else { CaretAssert(version == 2); extStart = 544; } CaretAssert(inFile.pos() == extStart); while(extStart + 2 * sizeof(int32_t) <= (size_t)m_header.vox_offset) { int32_t esize, ecode; inFile.read(&esize, sizeof(int32_t)); if (swapped) ByteSwapping::swap(esize); inFile.read(&ecode, sizeof(int32_t)); if (swapped) ByteSwapping::swap(ecode); if (esize < 8 || esize + extStart > m_header.vox_offset) break; CaretPointer tempExtension(new NiftiExtension()); if ((size_t)esize > 2 * sizeof(int32_t))//don't try to read 0 bytes { tempExtension->m_bytes.resize(esize - 2 * sizeof(int32_t)); inFile.read(tempExtension->m_bytes.data(), esize - 2 * sizeof(int32_t)); } tempExtension->m_ecode = ecode; m_extensions.push_back(tempExtension); extStart += esize;//esize includes the two int32_ts } } m_isSwapped = swapped;//now that we know there were no errors (because they throw), complete the internal state m_version = version; } void NiftiHeader::setupFrom(const nifti_1_header& header) { if (header.sizeof_hdr != sizeof(nifti_1_header)) throw DataFileException("incorrect sizeof_hdr"); const char magic[] = "n+1\0";//only support single-file nifti if (strncmp(header.magic, magic, 4) != 0) throw DataFileException("incorrect magic"); if (header.dim[0] < 1 || header.dim[0] > 7) throw DataFileException("incorrect dim[0]"); for (int i = 0; i < header.dim[0]; ++i) { if (header.dim[i + 1] < 1) throw DataFileException("dim[" + QString::number(i + 1) + "] < 1"); } if (header.vox_offset < 352) throw DataFileException("incorrect vox_offset"); int numBits = typeToNumBits(header.datatype); if (header.bitpix != numBits) CaretLogWarning("datatype disagrees with bitpix"); m_header.sizeof_hdr = header.sizeof_hdr;//copy in everything, so we don't have to fake anything to print the header as read for (int i = 0; i < 4; ++i)//mostly using nifti-2 field order to make it easier to find if things are missed { m_header.magic[i] = header.magic[i]; m_header.srow_x[i] = header.srow_x[i];//slight hack - nifti-1 magic and srows both happen to be 4 long m_header.srow_y[i] = header.srow_y[i]; m_header.srow_z[i] = header.srow_z[i]; } m_header.datatype = header.datatype; m_header.bitpix = header.bitpix; for (int i = 0; i < 8; ++i) { m_header.dim[i] = header.dim[i]; m_header.pixdim[i] = header.pixdim[i]; } m_header.intent_p1 = header.intent_p1; m_header.intent_p2 = header.intent_p2; m_header.intent_p3 = header.intent_p3; m_header.vox_offset = header.vox_offset;//technically, this could result in integer overflow, if the header extensions total exabytes in size m_header.scl_slope = header.scl_slope; m_header.scl_inter = header.scl_inter; m_header.cal_max = header.cal_max; m_header.cal_min = header.cal_min; m_header.slice_duration = header.slice_duration; m_header.toffset = header.toffset; m_header.slice_start = header.slice_start; m_header.slice_end = header.slice_end; for (int i = 0; i < 80; ++i) m_header.descrip[i] = header.descrip[i]; for (int i = 0; i < 24; ++i) m_header.aux_file[i] = header.aux_file[i]; m_header.qform_code = header.qform_code; m_header.sform_code = header.sform_code; m_header.quatern_b = header.quatern_b; m_header.quatern_c = header.quatern_c; m_header.quatern_d = header.quatern_d; m_header.qoffset_x = header.qoffset_x; m_header.qoffset_y = header.qoffset_y; m_header.qoffset_z = header.qoffset_z; m_header.slice_code = header.slice_code; m_header.xyzt_units = header.xyzt_units; m_header.intent_code = header.intent_code; for (int i = 0; i < 16; ++i) m_header.intent_name[i] = header.intent_name[i]; m_header.dim_info = header.dim_info; } void NiftiHeader::setupFrom(const nifti_2_header& header) { if (header.sizeof_hdr != sizeof(nifti_2_header)) throw DataFileException("incorrect sizeof_hdr"); const char magic[] = "n+2\0\r\n\032\n";//only support single-file nifti for (int i = 0; i < 8; ++i) { if (header.magic[i] != magic[i]) throw DataFileException("incorrect magic"); } if (header.dim[0] < 1 || header.dim[0] > 7) throw DataFileException("incorrect dim[0]"); for (int i = 0; i < header.dim[0]; ++i) { if (header.dim[i + 1] < 1) throw DataFileException("dim[" + QString::number(i + 1) + "] < 1"); } if (header.vox_offset < 352) throw DataFileException("incorrect vox_offset"); if (header.bitpix != typeToNumBits(header.datatype)) CaretLogWarning("datatype disagrees with bitpix"); memcpy(&m_header, &header, sizeof(nifti_2_header)); } int NiftiHeader::typeToNumBits(const int64_t& type) { switch (type) { case DT_BINARY: return 1; break; case NIFTI_TYPE_INT8: case NIFTI_TYPE_UINT8: return 8; break; case NIFTI_TYPE_INT16: case NIFTI_TYPE_UINT16: return 16; break; case NIFTI_TYPE_RGB24: return 24; break; case NIFTI_TYPE_INT32: case NIFTI_TYPE_UINT32: case NIFTI_TYPE_FLOAT32: return 32; break; case NIFTI_TYPE_INT64: case NIFTI_TYPE_UINT64: case NIFTI_TYPE_FLOAT64: case NIFTI_TYPE_COMPLEX64: return 64; break; case NIFTI_TYPE_FLOAT128: case NIFTI_TYPE_COMPLEX128: return 128; break; case NIFTI_TYPE_COMPLEX256: return 256; break; default: throw DataFileException("incorrect datatype code"); } } void NiftiHeader::swapHeaderBytes(nifti_1_header& header) { ByteSwapping::swap(header.sizeof_hdr);//by order of fields in nifti-1 header, skip unused because we don't store their data ByteSwapping::swapArray(header.dim, 8); ByteSwapping::swap(header.intent_p1); ByteSwapping::swap(header.intent_p2); ByteSwapping::swap(header.intent_p3); ByteSwapping::swap(header.intent_code); ByteSwapping::swap(header.datatype); ByteSwapping::swap(header.bitpix); ByteSwapping::swap(header.slice_start); ByteSwapping::swapArray(header.pixdim, 8); ByteSwapping::swap(header.vox_offset); ByteSwapping::swap(header.scl_slope); ByteSwapping::swap(header.scl_inter); ByteSwapping::swap(header.slice_end); ByteSwapping::swap(header.cal_max); ByteSwapping::swap(header.cal_min); ByteSwapping::swap(header.slice_duration); ByteSwapping::swap(header.toffset); ByteSwapping::swap(header.qform_code); ByteSwapping::swap(header.sform_code); ByteSwapping::swap(header.quatern_b); ByteSwapping::swap(header.quatern_c); ByteSwapping::swap(header.quatern_d); ByteSwapping::swap(header.qoffset_x); ByteSwapping::swap(header.qoffset_y); ByteSwapping::swap(header.qoffset_z); ByteSwapping::swapArray(header.srow_x, 4); ByteSwapping::swapArray(header.srow_y, 4); ByteSwapping::swapArray(header.srow_z, 4); } void NiftiHeader::swapHeaderBytes(nifti_2_header& header) { ByteSwapping::swap(header.sizeof_hdr);//by order of fields in nifti-2 header ByteSwapping::swap(header.datatype); ByteSwapping::swap(header.bitpix); ByteSwapping::swapArray(header.dim, 8); ByteSwapping::swap(header.intent_p1); ByteSwapping::swap(header.intent_p2); ByteSwapping::swap(header.intent_p3); ByteSwapping::swapArray(header.pixdim, 8); ByteSwapping::swap(header.vox_offset); ByteSwapping::swap(header.scl_slope); ByteSwapping::swap(header.scl_inter); ByteSwapping::swap(header.cal_max); ByteSwapping::swap(header.cal_min); ByteSwapping::swap(header.slice_duration); ByteSwapping::swap(header.toffset); ByteSwapping::swap(header.slice_start); ByteSwapping::swap(header.slice_end); ByteSwapping::swap(header.qform_code); ByteSwapping::swap(header.sform_code); ByteSwapping::swap(header.quatern_b); ByteSwapping::swap(header.quatern_c); ByteSwapping::swap(header.quatern_d); ByteSwapping::swap(header.qoffset_x); ByteSwapping::swap(header.qoffset_y); ByteSwapping::swap(header.qoffset_z); ByteSwapping::swapArray(header.srow_x, 4); ByteSwapping::swapArray(header.srow_y, 4); ByteSwapping::swapArray(header.srow_z, 4); ByteSwapping::swap(header.slice_code); ByteSwapping::swap(header.xyzt_units); ByteSwapping::swap(header.intent_code); } void NiftiHeader::write(CaretBinaryFile& outFile, const int& version, const bool& swapEndian) {//always write in native byte order, until there is a real reason to do otherwise if (!canWriteVersion(version)) throw DataFileException("unable to write NIfTI version " + QString::number(version) + " for file " + outFile.getFilename()); const char padding[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; int64_t voxOffset; if (version == 2) { nifti_2_header outHeader; prepareHeader(outHeader); voxOffset = outHeader.vox_offset; if (swapEndian) swapHeaderBytes(outHeader); outFile.write(&outHeader, sizeof(nifti_2_header)); } else if (version == 1) { nifti_1_header outHeader; prepareHeader(outHeader); voxOffset = outHeader.vox_offset; if (swapEndian) swapHeaderBytes(outHeader); outFile.write(&outHeader, sizeof(nifti_1_header)); } else { CaretAssert(0);//canWriteVersion should have said no throw DataFileException("internal error: NiftiHeader::canWriteVersion() returned true for unimplemented writing version"); } char extender[] = { 0, 0, 0, 0 };//at least until nifti-2 gets a new extension format, use the same code for both int numExtensions = (int)m_extensions.size(); if (numExtensions != 0) extender[0] = 1; outFile.write(extender, 4); for (int i = 0; i < numExtensions; ++i) { CaretAssert(m_extensions[i] != NULL); int64_t thisSize = 8 + m_extensions[i]->m_bytes.size();//8 is for the int32_t size and ecode for nifti-1 style extensions int paddingBytes = 0; if (thisSize % 16 != 0)//round up to nearest multiple of 16 { paddingBytes = 16 - (thisSize % 16); thisSize += paddingBytes; } CaretAssert(thisSize <= numeric_limits::max()); CaretAssert(thisSize + outFile.pos() <= voxOffset); int32_t outSize = thisSize; int32_t outEcode = m_extensions[i]->m_ecode; if (swapEndian) { ByteSwapping::swap(outSize); ByteSwapping::swap(outEcode); } outFile.write(&outSize, sizeof(int32_t)); outFile.write(&outEcode, sizeof(int32_t)); outFile.write(m_extensions[i]->m_bytes.data(), m_extensions[i]->m_bytes.size()); if (paddingBytes != 0) outFile.write(padding, paddingBytes); } CaretAssert(outFile.pos() == voxOffset); m_header.vox_offset = voxOffset;//update internal state to reflect the state that was written to the file m_version = version; m_isSwapped = swapEndian; } void NiftiHeader::prepareHeader(nifti_1_header& header) const { CaretAssert(canWriteVersion(1));//programmer error to call this if it isn't possible header.sizeof_hdr = sizeof(nifti_1_header);//do static things first const char magic[] = "n+1\0";//only support single-file nifti for (int i = 0; i < 4; ++i) header.magic[i] = magic[i]; for (int i = 0; i < 10; ++i) header.data_type[i] = 0;//then zero unused things for (int i = 0; i < 18; ++i) header.db_name[i] = 0; header.extents = 0; header.session_error = 0; header.regular = 0; header.glmax = 0; header.glmin = 0; header.dim_info = m_header.dim_info;//by order of fields in nifti-1 header, skipping unused and static for (int i = 0; i < 8; ++i) header.dim[i] = m_header.dim[i];//canWriteVersion should have already checked that this is okay, first in write(), then asserted above header.intent_p1 = m_header.intent_p1;//theoretically, this could be a problem wih large exponents, or if extremely high precision is required header.intent_p2 = m_header.intent_p2;//but we don't use them at all currently, so we don't care header.intent_p3 = m_header.intent_p3; header.intent_code = m_header.intent_code; header.datatype = m_header.datatype; header.bitpix = typeToNumBits(m_header.datatype);//in case we ever accept wrong bitpix with a warning, NEVER write wrong bitpix header.slice_start = m_header.slice_start; for (int i = 0; i < 8; ++i) header.pixdim[i] = m_header.pixdim[i];//more double to float conversion header.vox_offset = computeVoxOffset(1);//again, canWriteVersion should have checked that this, and later conversions, are okay CaretAssert(header.vox_offset >= 352); header.scl_slope = m_header.scl_slope; header.scl_inter = m_header.scl_inter; header.slice_end = m_header.slice_end; header.slice_code = m_header.slice_code; header.xyzt_units = m_header.xyzt_units; header.cal_min = m_header.cal_min; header.cal_max = m_header.cal_max; header.slice_duration = m_header.slice_duration; header.toffset = m_header.toffset; for (int i = 0; i < 80; ++i) header.descrip[i] = m_header.descrip[i]; for (int i = 0; i < 24; ++i) header.aux_file[i] = m_header.aux_file[i]; header.qform_code = m_header.qform_code; header.sform_code = m_header.sform_code; header.quatern_b = m_header.quatern_b; header.quatern_c = m_header.quatern_c; header.quatern_d = m_header.quatern_d; header.qoffset_x = m_header.qoffset_x; header.qoffset_y = m_header.qoffset_y; header.qoffset_z = m_header.qoffset_z; for (int i = 0; i < 4; ++i) { header.srow_x[i] = m_header.srow_x[i]; header.srow_y[i] = m_header.srow_y[i]; header.srow_z[i] = m_header.srow_z[i]; } for (int i = 0; i < 16; ++i) header.intent_name[i] = m_header.intent_name[i]; } void NiftiHeader::prepareHeader(nifti_2_header& header) const { CaretAssert(canWriteVersion(2)); memcpy(&header, &m_header, sizeof(nifti_2_header));//first copy everything, then fix static and computed fields header.sizeof_hdr = sizeof(nifti_2_header); const char magic[] = "n+2\0\r\n\032\n"; for (int i = 0; i < 8; ++i) header.magic[i] = magic[i]; header.bitpix = typeToNumBits(header.datatype); header.vox_offset = computeVoxOffset(2); for (int i = 0; i < 15; ++i) header.unused_str[i] = 0;//in case we read in a header where these bytes weren't zero } workbench-1.1.1/src/Nifti/NiftiHeader.h000066400000000000000000000074641255417355300177140ustar00rootroot00000000000000#ifndef __NIFTI_HEADER_H__ #define __NIFTI_HEADER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretBinaryFile.h" #include "VolumeBase.h" //for AbstractHeader, AbstravtVolumeExtension #include "nifti1.h" #include "nifti2.h" #include namespace caret { struct NiftiExtension { int32_t m_ecode; std::vector m_bytes; }; struct NiftiHeader : public AbstractHeader { std::vector > m_extensions;//allow direct access to the extensions NiftiHeader(); NiftiHeader(const NiftiHeader& rhs); NiftiHeader& operator=(const NiftiHeader& rhs); void read(CaretBinaryFile& inFile); void write(CaretBinaryFile& outFile, const int& version = 1, const bool& swapEndian = false);//returns new vox_offset, doesn't set it internally bool canWriteVersion(const int& version) const; bool isSwapped() const { return m_isSwapped; } int version() const { return m_version; } HeaderType getType() const { return NIFTI; } AbstractHeader* clone() const; std::vector getDimensions() const; std::vector > getSForm() const; int64_t getDataOffset() const { return m_header.vox_offset; } int16_t getDataType() const { return m_header.datatype; } int32_t getIntentCode() const { return m_header.intent_code; } const char* getIntentName() const { return m_header.intent_name; }//NOTE: MAY NOT HAVE A NULL TERMINATOR bool getDataScaling(double& mult, double& offset) const;//returns false if scaling not needed QString toString() const; void setDimensions(const std::vector& dimsIn); void setSForm(const std::vector > &sForm); void setIntent(const int32_t& code, const char name[16]); void setDataType(const int16_t& type); void clearDataScaling(); void setDataScaling(const double& mult, const double& offset); ///get the FSL "scale" space std::vector > getFSLSpace() const; bool operator==(const NiftiHeader& rhs) const;//for testing purposes bool operator!=(const NiftiHeader& rhs) const { return !((*this) == rhs); } private: nifti_2_header m_header;//storage for header values regardless of version int m_version; bool m_isSwapped; static void swapHeaderBytes(nifti_1_header &header); static void swapHeaderBytes(nifti_2_header &header); void prepareHeader(nifti_1_header& header) const;//transform internal state into ready to write header struct void prepareHeader(nifti_2_header& header) const; void setupFrom(const nifti_1_header& header);//error check provided header, and populate members from it void setupFrom(const nifti_2_header& header); static int typeToNumBits(const int64_t& type); int64_t computeVoxOffset(const int& version) const; }; } #endif //__NIFTI_HEADER_H__ workbench-1.1.1/src/Nifti/NiftiIO.cxx000066400000000000000000000072011255417355300173730ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "NiftiIO.h" #include "DataFileException.h" using namespace std; using namespace caret; void NiftiIO::openRead(const QString& filename) { m_file.open(filename); m_header.read(m_file); if (m_header.getDataType() == DT_BINARY) { throw DataFileException("file uses the binary datatype, which is unsupported: " + filename); } m_dims = m_header.getDimensions(); } void NiftiIO::writeNew(const QString& filename, const NiftiHeader& header, const int& version, const bool& withRead, const bool& swapEndian) { if (header.getDataType() == DT_BINARY) { throw DataFileException("writing NIFTI with binary datatype is unsupported"); } if (withRead) { m_file.open(filename, CaretBinaryFile::READ_WRITE_TRUNCATE);//for cifti on-disk writing, replace structure with along row needs to RMW } else { m_file.open(filename, CaretBinaryFile::WRITE_TRUNCATE); } m_header = header; m_header.write(m_file, version, swapEndian); m_dims = m_header.getDimensions(); } void NiftiIO::close() { m_file.close(); m_dims.clear(); } int NiftiIO::getNumComponents() const { switch (m_header.getDataType()) { case NIFTI_TYPE_RGB24: return 3; break; case NIFTI_TYPE_COMPLEX64: case NIFTI_TYPE_COMPLEX128: case NIFTI_TYPE_COMPLEX256: return 2; break; case NIFTI_TYPE_INT8: case NIFTI_TYPE_UINT8: case NIFTI_TYPE_INT16: case NIFTI_TYPE_UINT16: case NIFTI_TYPE_INT32: case NIFTI_TYPE_UINT32: case NIFTI_TYPE_FLOAT32: case NIFTI_TYPE_INT64: case NIFTI_TYPE_UINT64: case NIFTI_TYPE_FLOAT64: case NIFTI_TYPE_FLOAT128: return 1; break; default: CaretAssert(0); throw DataFileException("internal error, report what you did to the developers"); } } int NiftiIO::numBytesPerElem() { switch (m_header.getDataType()) { case NIFTI_TYPE_INT8: case NIFTI_TYPE_UINT8: case NIFTI_TYPE_RGB24: return 1; break; case NIFTI_TYPE_INT16: case NIFTI_TYPE_UINT16: return 2; break; case NIFTI_TYPE_INT32: case NIFTI_TYPE_UINT32: case NIFTI_TYPE_FLOAT32: case NIFTI_TYPE_COMPLEX64: return 4; break; case NIFTI_TYPE_INT64: case NIFTI_TYPE_UINT64: case NIFTI_TYPE_FLOAT64: case NIFTI_TYPE_COMPLEX128: return 8; break; case NIFTI_TYPE_FLOAT128: case NIFTI_TYPE_COMPLEX256: return 16; break; default: CaretAssert(0); throw DataFileException("internal error, report what you did to the developers"); } } workbench-1.1.1/src/Nifti/NiftiIO.h000066400000000000000000000276711255417355300170350ustar00rootroot00000000000000#ifndef __NIFTI_IO_H__ #define __NIFTI_IO_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "ByteSwapping.h" #include "CaretAssert.h" #include "CaretBinaryFile.h" #include "DataFileException.h" #include "NiftiHeader.h" #include #include #include #include namespace caret { class NiftiIO { CaretBinaryFile m_file; NiftiHeader m_header; std::vector m_dims; std::vector m_scratch;//scratch memory for byteswapping, type conversion, etc int numBytesPerElem();//for resizing scratch template void convertRead(TO* out, FROM* in, const int64_t& count);//for reading from file template void convertWrite(TO* out, const FROM* in, const int64_t& count);//for writing to file public: void openRead(const QString& filename); void writeNew(const QString& filename, const NiftiHeader& header, const int& version = 1, const bool& withRead = false, const bool& swapEndian = false); QString getFilename() const { return m_file.getFilename(); } void overrideDimensions(const std::vector& newDims) { m_dims = newDims; }//HACK: deal with reading/writing CIFTI-1's broken headers void close(); const NiftiHeader& getHeader() const { return m_header; } const std::vector& getDimensions() const { return m_dims; } int getNumComponents() const; //to read/write 1 frame of a standard volume file, call with fullDims = 3, indexSelect containing indexes for any of dims 4-7 that exist //NOTE: you need to provide storage for all components within the range, if getNumComponents() == 3 and fullDims == 0, you need 3 elements allocated template void readData(T* dataOut, const int& fullDims, const std::vector& indexSelect, const bool& tolerateShortRead = false); template void writeData(const T* dataIn, const int& fullDims, const std::vector& indexSelect); }; template void NiftiIO::readData(T* dataOut, const int& fullDims, const std::vector& indexSelect, const bool& tolerateShortRead) { CaretAssert(fullDims >= 0 && fullDims <= (int)m_dims.size()); CaretAssert((size_t)fullDims + indexSelect.size() == m_dims.size());//could be >=, but should catch more stupid mistakes as == int64_t numElems = getNumComponents();//for now, calculate read size on the fly, as the read call will be the slowest part int curDim; for (curDim = 0; curDim < fullDims; ++curDim) { numElems *= m_dims[curDim]; } int64_t numDimSkip = numElems, numSkip = 0; for (; curDim < (int)m_dims.size(); ++curDim) { CaretAssert(indexSelect[curDim - fullDims] >= 0 && indexSelect[curDim - fullDims] < m_dims[curDim]); numSkip += indexSelect[curDim - fullDims] * numDimSkip; numDimSkip *= m_dims[curDim]; } m_scratch.resize(numElems * numBytesPerElem()); m_file.seek(numSkip * numBytesPerElem() + m_header.getDataOffset()); int64_t numRead = 0; m_file.read(m_scratch.data(), m_scratch.size(), &numRead); if ((numRead != (int64_t)m_scratch.size() && !tolerateShortRead) || numRead < 0)//for now, assume read giving -1 is always a problem { throw DataFileException("error while reading from file '" + m_file.getFilename() + "'"); } switch (m_header.getDataType()) { case NIFTI_TYPE_UINT8: case NIFTI_TYPE_RGB24://handled by components convertRead(dataOut, (uint8_t*)m_scratch.data(), numElems); break; case NIFTI_TYPE_INT8: convertRead(dataOut, (int8_t*)m_scratch.data(), numElems); break; case NIFTI_TYPE_UINT16: convertRead(dataOut, (uint16_t*)m_scratch.data(), numElems); break; case NIFTI_TYPE_INT16: convertRead(dataOut, (int16_t*)m_scratch.data(), numElems); break; case NIFTI_TYPE_UINT32: convertRead(dataOut, (uint32_t*)m_scratch.data(), numElems); break; case NIFTI_TYPE_INT32: convertRead(dataOut, (int32_t*)m_scratch.data(), numElems); break; case NIFTI_TYPE_UINT64: convertRead(dataOut, (uint64_t*)m_scratch.data(), numElems); break; case NIFTI_TYPE_INT64: convertRead(dataOut, (int64_t*)m_scratch.data(), numElems); break; case NIFTI_TYPE_FLOAT32: case NIFTI_TYPE_COMPLEX64://components convertRead(dataOut, (float*)m_scratch.data(), numElems); break; case NIFTI_TYPE_FLOAT64: case NIFTI_TYPE_COMPLEX128: convertRead(dataOut, (double*)m_scratch.data(), numElems); break; case NIFTI_TYPE_FLOAT128: case NIFTI_TYPE_COMPLEX256: convertRead(dataOut, (long double*)m_scratch.data(), numElems); break; default: CaretAssert(0); throw DataFileException("internal error, tell the developers what you just tried to do"); } } template void NiftiIO::writeData(const T* dataIn, const int& fullDims, const std::vector& indexSelect) { CaretAssert(fullDims >= 0 && fullDims <= (int)m_dims.size()); CaretAssert((size_t)fullDims + indexSelect.size() == m_dims.size());//could be >=, but should catch more stupid mistakes as == int64_t numElems = getNumComponents();//for now, calculate read size on the fly, as the read call will be the slowest part int curDim; for (curDim = 0; curDim < fullDims; ++curDim) { numElems *= m_dims[curDim]; } int64_t numDimSkip = numElems, numSkip = 0; for (; curDim < (int)m_dims.size(); ++curDim) { CaretAssert(indexSelect[curDim - fullDims] >= 0 && indexSelect[curDim - fullDims] < m_dims[curDim]); numSkip += indexSelect[curDim - fullDims] * numDimSkip; numDimSkip *= m_dims[curDim]; } m_scratch.resize(numElems * numBytesPerElem()); m_file.seek(numSkip * numBytesPerElem() + m_header.getDataOffset()); switch (m_header.getDataType()) { case NIFTI_TYPE_UINT8: case NIFTI_TYPE_RGB24://handled by components convertWrite((uint8_t*)m_scratch.data(), dataIn, numElems); break; case NIFTI_TYPE_INT8: convertWrite((int8_t*)m_scratch.data(), dataIn, numElems); break; case NIFTI_TYPE_UINT16: convertWrite((uint16_t*)m_scratch.data(), dataIn, numElems); break; case NIFTI_TYPE_INT16: convertWrite((int16_t*)m_scratch.data(), dataIn, numElems); break; case NIFTI_TYPE_UINT32: convertWrite((uint32_t*)m_scratch.data(), dataIn, numElems); break; case NIFTI_TYPE_INT32: convertWrite((int32_t*)m_scratch.data(), dataIn, numElems); break; case NIFTI_TYPE_UINT64: convertWrite((uint64_t*)m_scratch.data(), dataIn, numElems); break; case NIFTI_TYPE_INT64: convertWrite((int64_t*)m_scratch.data(), dataIn, numElems); break; case NIFTI_TYPE_FLOAT32: case NIFTI_TYPE_COMPLEX64://components convertWrite((float*)m_scratch.data(), dataIn, numElems); break; case NIFTI_TYPE_FLOAT64: case NIFTI_TYPE_COMPLEX128: convertWrite((double*)m_scratch.data(), dataIn, numElems); break; case NIFTI_TYPE_FLOAT128: case NIFTI_TYPE_COMPLEX256: convertWrite((long double*)m_scratch.data(), dataIn, numElems); break; default: CaretAssert(0); throw DataFileException("internal error, tell the developers what you just tried to do"); } m_file.write(m_scratch.data(), m_scratch.size()); } template void NiftiIO::convertRead(TO* out, FROM* in, const int64_t& count) { if (m_header.isSwapped()) { ByteSwapping::swapArray(in, count); } double mult, offset; bool doScale = m_header.getDataScaling(mult, offset); if (std::numeric_limits::is_integer)//do round to nearest when integer output type { if (doScale) { for (int64_t i = 0; i < count; ++i) { out[i] = (TO)floor(0.5 + offset + mult * (long double)in[i]);//we don't always need that much precision, but it will still be faster than hard drives } } else { for (int64_t i = 0; i < count; ++i) { out[i] = (TO)floor(0.5 + in[i]); } } } else { if (doScale) { for (int64_t i = 0; i < count; ++i) { out[i] = (TO)(offset + mult * (long double)in[i]);//we don't always need that much precision, but it will still be faster than hard drives } } else { for (int64_t i = 0; i < count; ++i) { out[i] = (TO)in[i];//explicit cast to make sure the compiler doesn't squawk } } } } template void NiftiIO::convertWrite(TO* out, const FROM* in, const int64_t& count) { double mult, offset; bool doScale = m_header.getDataScaling(mult, offset); if (std::numeric_limits::is_integer)//do round to nearest when integer output type { if (doScale) { for (int64_t i = 0; i < count; ++i) { out[i] = (TO)floor(0.5 + ((long double)in[i] - offset) / mult);//we don't always need that much precision, but it will still be faster than hard drives } } else { for (int64_t i = 0; i < count; ++i) { out[i] = (TO)floor(0.5 + in[i]); } } } else { if (doScale) { for (int64_t i = 0; i < count; ++i) { out[i] = (TO)(((long double)in[i] - offset) / mult);//we don't always need that much precision, but it will still be faster than hard drives } } else { for (int64_t i = 0; i < count; ++i) { out[i] = (TO)in[i];//explicit cast to make sure the compiler doesn't squawk } } } if (m_header.isSwapped()) ByteSwapping::swapArray(out, count); } } #endif //__NIFTI_IO_H__ workbench-1.1.1/src/OSMesaDummy/000077500000000000000000000000001255417355300164405ustar00rootroot00000000000000workbench-1.1.1/src/OSMesaDummy/CMakeLists.txt000066400000000000000000000004551255417355300212040ustar00rootroot00000000000000 # # Name of Project # PROJECT(OSMesaDummy) # # Need Qt for reading from resource file. # INCLUDE(${QT_USE_FILE}) # # Create a library # ADD_LIBRARY(OSMesaDummy OSMesaDummy.h OSMesaDummy.c ) # # Include directories # INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/Brain ${CMAKE_SOURCE_DIR}/OSMesaDummy ) workbench-1.1.1/src/OSMesaDummy/OSMesaDummy.c000066400000000000000000000045121255417355300207510ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "assert.h" #define __O_S_MESA_DUMMY_DECLARE__ #include "OSMesaDummy.h" #undef __O_S_MESA_DUMMY_DECLARE__ /* * THESE ARE DUMMY METHODS. * * THE COMMAND LINE USES OSMESA FOR RENDERING IMAGES USING OPENG * WITHOUT A DISPLAY. HOWEVER, WITH THE GUI, IF THE OSMESA * LIBRARY IS LINKED, IT SOMEHOW RESULTS IN THE MESA GL (OPENGL) * LIBRARY BEING LINKED INSTEAD OF THE SYSTEM'S HARDWARE OPENGL * LIBRARY AND EVERYTHING IS SCREWED UP. * * IT IS THE DISPLAY OF HELP INFORMATION FOR COMMANDS THAT INCLUDES * THE SHOW SCENE COMMAND THAT REQUIRES OSMESA. */ /*GLAPI*/ OSMesaContext /*GLAPIENTRY*/ OSMesaCreateContextExt(GLenum format, GLint depthBits, GLint stencilBits, GLint accumBits, OSMesaContext sharelist) { /* * NOTE: These are "C" functions (NOT "C++") and so the names * of the parameters cannot be removed to avoid a compilation * warning as missing parameter names is a compiler error in "C". */ if (format || depthBits || stencilBits || accumBits || sharelist) { } assert(0); return 0; } /*GLAPI*/ void /*GLAPIENTRY*/ OSMesaDestroyContext( OSMesaContext ctx ) { if (ctx) { } assert(0); } /*GLAPI*/ GLboolean /*GLAPIENTRY*/ OSMesaMakeCurrent( OSMesaContext ctx, void * buffer, GLenum type, GLsizei width, GLsizei height ) { if (ctx || buffer || type || width || height) { } assert(0); return GL_FALSE; } workbench-1.1.1/src/OSMesaDummy/OSMesaDummy.h000066400000000000000000000027231255417355300207600ustar00rootroot00000000000000#ifndef __O_S_MESA_DUMMY_H__ #define __O_S_MESA_DUMMY_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretOpenGLInclude.h" typedef struct osmesa_context *OSMesaContext; /* * THESE ARE DUMMY METHODS. * * See the ".c" file for more info. */ /*GLAPI*/ OSMesaContext /*GLAPIENTRY*/ OSMesaCreateContextExt(GLenum format, GLint depthBits, GLint stencilBits, GLint accumBits, OSMesaContext sharelist); /*GLAPI*/ void /*GLAPIENTRY*/ OSMesaDestroyContext( OSMesaContext ctx ); /*GLAPI*/ GLboolean /*GLAPIENTRY*/ OSMesaMakeCurrent( OSMesaContext ctx, void *buffer, GLenum type, GLsizei width, GLsizei height ); #endif //__O_S_MESA_DUMMY_H__ workbench-1.1.1/src/Operations/000077500000000000000000000000001255417355300164205ustar00rootroot00000000000000workbench-1.1.1/src/Operations/CMakeLists.txt000066400000000000000000000126631255417355300211700ustar00rootroot00000000000000# # Name of project # PROJECT (Operations) # # Need XML from Qt # SET(QT_DONT_USE_QTGUI) # # Add QT for includes # INCLUDE (${QT_USE_FILE}) # # Create the helper library # ADD_LIBRARY(Operations OperationAddToSpecFile.h OperationBackendAverageDenseROI.h OperationBackendAverageROICorrelation.h OperationBorderExportColorTable.h OperationBorderFileExportToCaret5.h OperationBorderMerge.h OperationCiftiChangeTimestep.h OperationCiftiConvert.h OperationCiftiConvertToScalar.h OperationCiftiCopyMapping.h OperationCiftiCreateDenseFromTemplate.h OperationCiftiCreateScalarSeries.h OperationCiftiEstimateFWHM.h OperationCiftiExportDenseMapping.h OperationCiftiLabelExportTable.h OperationCiftiLabelImport.h OperationCiftiMath.h OperationCiftiMerge.h OperationCiftiPalette.h OperationCiftiResampleDconnMemory.h OperationCiftiROIAverage.h OperationCiftiSeparateAll.h OperationCiftiStats.h OperationCiftiWeightedStats.h OperationConvertAffine.h OperationConvertFiberOrientations.h OperationConvertMatrix4ToMatrix2.h OperationConvertMatrix4ToWorkbenchSparse.h OperationConvertWarpfield.h OperationEstimateFiberBinghams.h OperationException.h OperationFileConvert.h OperationFileInformation.h OperationFociGetProjectionVertex.h OperationFociListCoords.h OperationLabelExportTable.h OperationLabelMask.h OperationLabelMerge.h OperationMetadataRemoveProvenance.h OperationMetadataStringReplace.h OperationMetricConvert.h OperationMetricLabelImport.h OperationMetricMask.h OperationMetricMath.h OperationMetricMerge.h OperationMetricPalette.h OperationMetricStats.h OperationMetricVertexSum.h OperationMetricWeightedStats.h OperationNiftiInformation.h OperationProbtrackXDotConvert.h OperationSetMapName.h OperationSetMapNames.h OperationSetStructure.h OperationShowScene.h OperationSpecFileMerge.h OperationSurfaceClosestVertex.h OperationSurfaceCoordinatesToMetric.h OperationSurfaceCutResample.h OperationSurfaceFlipNormals.h OperationSurfaceGeodesicDistance.h OperationSurfaceGeodesicROIs.h OperationSurfaceInformation.h OperationSurfaceNormals.h OperationSurfaceVertexAreas.h OperationVolumeCapturePlane.h OperationVolumeCopyExtensions.h OperationVolumeCreate.h OperationVolumeLabelExportTable.h OperationVolumeLabelImport.h OperationVolumeMath.h OperationVolumeMerge.h OperationVolumePalette.h OperationVolumeReorient.h OperationVolumeSetSpace.h OperationVolumeStats.h OperationVolumeWeightedStats.h OperationWbsparseMergeDense.h OperationZipSceneFile.h OperationZipSpecFile.h OperationAddToSpecFile.cxx OperationBackendAverageDenseROI.cxx OperationBackendAverageROICorrelation.cxx OperationBorderExportColorTable.cxx OperationBorderFileExportToCaret5.cxx OperationBorderMerge.cxx OperationCiftiChangeTimestep.cxx OperationCiftiConvert.cxx OperationCiftiConvertToScalar.cxx OperationCiftiCopyMapping.cxx OperationCiftiCreateDenseFromTemplate.cxx OperationCiftiCreateScalarSeries.cxx OperationCiftiEstimateFWHM.cxx OperationCiftiExportDenseMapping.cxx OperationCiftiLabelExportTable.cxx OperationCiftiLabelImport.cxx OperationCiftiMath.cxx OperationCiftiMerge.cxx OperationCiftiPalette.cxx OperationCiftiResampleDconnMemory.cxx OperationCiftiROIAverage.cxx OperationCiftiSeparateAll.cxx OperationCiftiStats.cxx OperationCiftiWeightedStats.cxx OperationConvertAffine.cxx OperationConvertFiberOrientations.cxx OperationConvertMatrix4ToMatrix2.cxx OperationConvertMatrix4ToWorkbenchSparse.cxx OperationConvertWarpfield.cxx OperationException.cxx OperationEstimateFiberBinghams.cxx OperationFileConvert.cxx OperationFileInformation.cxx OperationFociGetProjectionVertex.cxx OperationFociListCoords.cxx OperationLabelExportTable.cxx OperationLabelMask.cxx OperationLabelMerge.cxx OperationMetadataRemoveProvenance.cxx OperationMetadataStringReplace.cxx OperationMetricConvert.cxx OperationMetricLabelImport.cxx OperationMetricMask.cxx OperationMetricMath.cxx OperationMetricMerge.cxx OperationMetricPalette.cxx OperationMetricStats.cxx OperationMetricVertexSum.cxx OperationMetricWeightedStats.cxx OperationNiftiInformation.cxx OperationProbtrackXDotConvert.cxx OperationSetMapName.cxx OperationSetMapNames.cxx OperationSetStructure.cxx OperationShowScene.cxx OperationSpecFileMerge.cxx OperationSurfaceClosestVertex.cxx OperationSurfaceCoordinatesToMetric.cxx OperationSurfaceCutResample.cxx OperationSurfaceFlipNormals.cxx OperationSurfaceGeodesicDistance.cxx OperationSurfaceGeodesicROIs.cxx OperationSurfaceInformation.cxx OperationSurfaceNormals.cxx OperationSurfaceVertexAreas.cxx OperationVolumeCapturePlane.cxx OperationVolumeCopyExtensions.cxx OperationVolumeCreate.cxx OperationVolumeLabelExportTable.cxx OperationVolumeLabelImport.cxx OperationVolumeMath.cxx OperationVolumeMerge.cxx OperationVolumePalette.cxx OperationVolumeReorient.cxx OperationVolumeSetSpace.cxx OperationVolumeStats.cxx OperationVolumeWeightedStats.cxx OperationWbsparseMergeDense.cxx OperationZipSceneFile.cxx OperationZipSpecFile.cxx ) # # Find Headers # INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/Algorithms ${CMAKE_SOURCE_DIR}/Operations ${CMAKE_SOURCE_DIR}/OperationsBase ${CMAKE_SOURCE_DIR}/Brain ${CMAKE_SOURCE_DIR}/Charting ${CMAKE_SOURCE_DIR}/Palette ${CMAKE_SOURCE_DIR}/FilesBase ${CMAKE_SOURCE_DIR}/Files ${CMAKE_SOURCE_DIR}/Gifti ${CMAKE_SOURCE_DIR}/Cifti ${CMAKE_SOURCE_DIR}/Nifti ${CMAKE_SOURCE_DIR}/Quazip ${CMAKE_SOURCE_DIR}/Scenes ${CMAKE_SOURCE_DIR}/Xml ${CMAKE_SOURCE_DIR}/Common ) # # Mesa Library used by show scene command # IF (OSMESA_FOUND) ADD_DEFINITIONS(${OSMESA_DEFINITION}) INCLUDE_DIRECTORIES(${OSMESA_INCLUDE_DIRECTORY}) ENDIF (OSMESA_FOUND) workbench-1.1.1/src/Operations/OperationAddToSpecFile.cxx000066400000000000000000000063061255417355300234400ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationAddToSpecFile.h" #include "OperationException.h" #include "SpecFile.h" #include "FileInformation.h" #include using namespace caret; using namespace std; AString OperationAddToSpecFile::getCommandSwitch() { return "-add-to-spec-file"; } AString OperationAddToSpecFile::getShortDescription() { return "ADD A FILE TO A SPECIFICATION FILE"; } OperationParameters* OperationAddToSpecFile::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addStringParameter(1, "specfile", "the specification file to add to"); ret->addStringParameter(2, "structure", "the structure of the data file"); ret->addStringParameter(3, "filename", "the path to the file"); AString myText = AString("The resulting spec file overwrites the existing spec file. If the spec file doesn't exist, ") + "it is created with default metadata. The structure argument must be one of the following:\n\n"; vector myStructureEnums; StructureEnum::getAllEnums(myStructureEnums); for (int i = 0; i < (int)myStructureEnums.size(); ++i) { myText += StructureEnum::toName(myStructureEnums[i]) + "\n"; } ret->setHelpText(myText); return ret; } void OperationAddToSpecFile::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); bool ok = false; AString mySpecName = myParams->getString(1);//spec file AString myStructureName = myParams->getString(2);//file structure StructureEnum::Enum myStrucure = StructureEnum::fromName(myStructureName, &ok); if (!ok) { throw OperationException("unrecognized structure type"); } AString myDataFileName = myParams->getString(3);//file path FileInformation myDataFileInfo(myDataFileName); if (!myDataFileInfo.exists()) { throw OperationException("data file not found"); } DataFileTypeEnum::Enum myType = DataFileTypeEnum::fromFileExtension(myDataFileName, &ok); if (!ok) { throw OperationException("unrecognized data file type"); } SpecFile mySpec; FileInformation mySpecInfo(mySpecName); if (mySpecInfo.exists()) { mySpec.readFile(mySpecName); } else { mySpec.setFileName(mySpecName); } mySpec.addDataFile(myType, myStrucure, myDataFileName, true, false, true); mySpec.writeFile(mySpecName); } workbench-1.1.1/src/Operations/OperationAddToSpecFile.h000066400000000000000000000026251255417355300230650ustar00rootroot00000000000000#ifndef __OPERATION_ADD_TO_SPEC_FILE_H__ #define __OPERATION_ADD_TO_SPEC_FILE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationAddToSpecFile : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationAddToSpecFile; } #endif //__OPERATION_ADD_TO_SPEC_FILE_H__ workbench-1.1.1/src/Operations/OperationBackendAverageDenseROI.cxx000066400000000000000000000132241255417355300252020ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AString.h" #include "ByteOrderEnum.h" #include "ByteSwapping.h" #include "CaretPointer.h" #include "CiftiFile.h" #include "FileInformation.h" #include "OperationBackendAverageDenseROI.h" #include "OperationException.h" #include #include #include using namespace caret; using namespace std; AString OperationBackendAverageDenseROI::getCommandSwitch() { return "-backend-average-dense-roi"; } AString OperationBackendAverageDenseROI::getShortDescription() { return "CONNECTOME DB BACKEND COMMAND FOR CIFTI AVERAGE DENSE ROI"; } OperationParameters* OperationBackendAverageDenseROI::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addStringParameter(1, "index-list", "comma separated list of cifti indexes to average"); ret->addStringParameter(2, "out-file", "file to write the average row to"); ret->setHelpText( AString("This command is probably not the one you are looking for, try -cifti-average-dense-roi. ") + "It takes the list of cifti files to average from standard input, and writes its output as little endian, " + "32-bit integer of row size followed by the row as 32-bit floats." ); return ret; } void OperationBackendAverageDenseROI::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); AString indexListString, outfileName; indexListString = myParams->getString(1); outfileName = myParams->getString(2); CiftiXML baseXML; bool ok = false; vector indexList; QStringList indexStrings = indexListString.split(","); int numStrings = (int)indexStrings.size(); indexList.resize(numStrings); for (int i = 0; i < numStrings; ++i) { indexList[i] = indexStrings[i].toInt(&ok); if (!ok) { throw OperationException("failed to parse '" + indexStrings[i] + "' as integer"); } if (indexList[i] < 0) { throw OperationException("negative integers are not valid cifti indexes"); } } vector > ciftiList; string myLine; while (cin.good()) { if (!getline(cin, myLine)) { break; } if (myLine == "") { continue;//skip blank lines } FileInformation ciftiFileInfo(myLine.c_str()); if (!ciftiFileInfo.exists()) { throw OperationException(AString("file does not exist: ") + myLine.c_str()); } CaretPointer tempCifti(new CiftiFile(myLine.c_str()));//can't skip parsing XML, as different arguments could be different cifti versions, which results in different dimension order ciftiList.push_back(tempCifti); } int numCifti = (int)ciftiList.size(); if (numCifti > 0) { baseXML = ciftiList[0]->getCiftiXML(); if (baseXML.getNumberOfDimensions() != 2) throw OperationException("this command currently only supports 2D cifti"); int numRows = baseXML.getDimensionLength(CiftiXML::ALONG_COLUMN); int rowSize = baseXML.getDimensionLength(CiftiXML::ALONG_ROW); vector accum(rowSize, 0.0); vector rowScratch(rowSize); for (int i = 0; i < numCifti; ++i) { if (baseXML != ciftiList[i]->getCiftiXML())//equality testing is smart, compares mapping equivalence, despite multiple ways to specify some mappings { throw OperationException("error, cifti header of file #" + AString::number(i + 1) + " doesn't match"); } for (int j = 0; j < numStrings; ++j) { if (indexList[j] >= numRows) { throw OperationException("error, cifti index outside number of rows"); } ciftiList[i]->getRow(rowScratch.data(), indexList[j]); for (int k = 0; k < rowSize; ++k) { accum[k] += rowScratch[k]; } } } for (int k = 0; k < rowSize; ++k) { rowScratch[k] = accum[k] / numCifti / numStrings; } int32_t outSize = rowSize; if (ByteOrderEnum::isSystemBigEndian()) { ByteSwapping::swapBytes(rowScratch.data(), rowSize);//beware, we are doing the byteswapping in place ByteSwapping::swapBytes(&outSize, 1); } ofstream outfile(outfileName.toLocal8Bit().constData(), ios_base::out | ios_base::binary | ios_base::trunc); if (!outfile.write((char*)&outSize, 4)) { throw OperationException("error writing output"); } if (!outfile.write((char*)rowScratch.data(), rowSize * sizeof(float))) { throw OperationException("error writing output"); } outfile.close(); } } workbench-1.1.1/src/Operations/OperationBackendAverageDenseROI.h000066400000000000000000000027131255417355300246300ustar00rootroot00000000000000#ifndef __OPERATION_BACKEND_AVERAGE_DENSE_ROI_H__ #define __OPERATION_BACKEND_AVERAGE_DENSE_ROI_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationBackendAverageDenseROI : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationBackendAverageDenseROI; } #endif //__OPERATION_BACKEND_AVERAGE_DENSE_ROI_H__ workbench-1.1.1/src/Operations/OperationBackendAverageROICorrelation.cxx000066400000000000000000000173761255417355300264410ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AString.h" #include "ByteOrderEnum.h" #include "ByteSwapping.h" #include "CaretOMP.h" #include "CaretPointer.h" #include "CiftiFile.h" #include "FileInformation.h" #include "OperationBackendAverageROICorrelation.h" #include "OperationException.h" #include #include #include using namespace caret; using namespace std; AString OperationBackendAverageROICorrelation::getCommandSwitch() { return "-backend-average-roi-correlation"; } AString OperationBackendAverageROICorrelation::getShortDescription() { return "CONNECTOME DB BACKEND COMMAND FOR CIFTI AVERAGE ROI CORRELATION"; } OperationParameters* OperationBackendAverageROICorrelation::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addStringParameter(1, "index-list", "comma separated list of cifti indexes to average and then correlate"); ret->addStringParameter(2, "out-file", "file to write the average row to"); ret->setHelpText( AString("This command is probably not the one you are looking for, try -cifti-average-roi-correlation. ") + "It takes the list of cifti files to average from standard input, and writes its output as little endian, " + "32-bit integer of row size followed by the row as 32-bit floats." ); return ret; } void OperationBackendAverageROICorrelation::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); AString indexListString, outfileName; indexListString = myParams->getString(1); outfileName = myParams->getString(2); CiftiXML baseXML;//TODO: remove when switching to raw reading bool ok = false; vector indexList; QStringList indexStrings = indexListString.split(","); int numStrings = (int)indexStrings.size(); indexList.resize(numStrings); for (int i = 0; i < numStrings; ++i) { indexList[i] = indexStrings[i].toInt(&ok); if (!ok) { throw OperationException("failed to parse '" + indexStrings[i] + "' as integer"); } if (indexList[i] < 0) { throw OperationException("negative integers are not valid cifti indexes"); } } vector > ciftiList; string myLine; while (cin.good()) { if (!getline(cin, myLine)) { break; } if (myLine == "") { continue;//skip blank lines } FileInformation ciftiFileInfo(myLine.c_str()); if (!ciftiFileInfo.exists()) { throw OperationException(AString("file does not exist: ") + myLine.c_str()); } CaretPointer tempCifti(new CiftiFile(myLine.c_str()));//can't skip parsing XML, as different arguments could be different cifti versions, which results in different dimension order ciftiList.push_back(tempCifti); } int numCifti = (int)ciftiList.size(); if (numCifti > 0) { baseXML = ciftiList[0]->getCiftiXML(); if (baseXML.getNumberOfDimensions() != 2) throw OperationException("operation only supports 2D cifti files"); int rowSize = baseXML.getDimensionLength(CiftiXML::ALONG_ROW); vector accum(rowSize, 0.0); vector rowScratch(rowSize); for (int i = 0; i < numCifti; ++i) { if (!baseXML.approximateMatch(ciftiList[i]->getCiftiXML()))//equality testing is smart, compares mapping equivalence, despite multiple ways to specify some mappings { throw OperationException("error, cifti header of file #" + AString::number(i + 1) + " doesn't match"); } processCifti(ciftiList[i], indexList, rowScratch); for (int k = 0; k < rowSize; ++k) { accum[k] += rowScratch[k]; } } for (int k = 0; k < rowSize; ++k) { rowScratch[k] = accum[k] / numCifti / numStrings; } int32_t outSize = rowSize; if (ByteOrderEnum::isSystemBigEndian()) { ByteSwapping::swapBytes(rowScratch.data(), rowSize);//beware, we are doing the byteswapping in place ByteSwapping::swapBytes(&outSize, 1); } ofstream outfile(outfileName.toLocal8Bit().constData(), ios_base::out | ios_base::binary | ios_base::trunc); if (!outfile.write((char*)&outSize, 4)) { throw OperationException("error writing output"); } if (!outfile.write((char*)rowScratch.data(), rowSize * sizeof(float))) { throw OperationException("error writing output"); } outfile.close(); } } void OperationBackendAverageROICorrelation::processCifti(const CiftiFile* myCifti, const vector& indexList, vector& output) { int rowSize = myCifti->getNumberOfColumns(); int colSize = myCifti->getNumberOfRows(); int listSize = (int)indexList.size(); vector accumarray(rowSize, 0.0); vector average(rowSize); for (int i = 0; i < listSize; ++i) { if (indexList[i] >= colSize)//we already checked for negatives { throw OperationException("cifti index too large"); } myCifti->getRow(average.data(), indexList[i]); for (int j = 0; j < rowSize; ++j) { accumarray[j] += average[j]; } } double accum = 0.0; for (int i = 0; i < rowSize; ++i) { average[i] = accumarray[i] / listSize; accum += average[i]; } float mean = accum / rowSize; accum = 0.0; for (int i = 0; i < rowSize; ++i) { average[i] -= mean; accum += average[i] * average[i]; } float rrs = sqrt(accum); int curRow = 0; #pragma omp CARET_PAR { vector rowscratch(rowSize); #pragma omp CARET_FOR schedule(dynamic) for (int i = 0; i < colSize; ++i) { int myRow; #pragma omp critical { myRow = curRow;//force sequential reading ++curRow; myCifti->getRow(rowscratch.data(), myRow);//but never read multiple rows at once from the same file } double tempaccum = 0.0;//compute mean of new row for (int j = 0; j < rowSize; ++j) { tempaccum += rowscratch[j]; } float thismean = tempaccum / rowSize; tempaccum = 0.0; double corraccum = 0.0;//correlate for (int j = 0; j < rowSize; ++j) { float tempf = rowscratch[j] - thismean; tempaccum += tempf * tempf;//compute rrs on the fly corraccum += tempf * average[j];//gather the correlation } corraccum /= rrs * sqrt(tempaccum); if (corraccum > 0.999999) corraccum = 0.999999; if (corraccum < -0.999999) corraccum = -0.999999; output[i] = 0.5 * log((1 + corraccum) / (1 - corraccum)); } } } workbench-1.1.1/src/Operations/OperationBackendAverageROICorrelation.h000066400000000000000000000031751255417355300260560ustar00rootroot00000000000000#ifndef __OPERATION_BACKEND_AVERAGE_ROI_CORRELATION_H__ #define __OPERATION_BACKEND_AVERAGE_ROI_CORRELATION_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" #include namespace caret { class OperationBackendAverageROICorrelation : public AbstractOperation { static void processCifti(const CiftiFile* myCifti, const std::vector& indexList, std::vector& output); public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationBackendAverageROICorrelation; } #endif //__OPERATION_BACKEND_AVERAGE_ROI_CORRELATION_H__ workbench-1.1.1/src/Operations/OperationBorderExportColorTable.cxx000066400000000000000000000076001255417355300254160ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationBorderExportColorTable.h" #include "OperationException.h" #include "Border.h" #include "BorderFile.h" #include "GiftiLabel.h" #include "GiftiLabelTable.h" #include #include using namespace caret; using namespace std; AString OperationBorderExportColorTable::getCommandSwitch() { return "-border-export-color-table"; } AString OperationBorderExportColorTable::getShortDescription() { return "WRITE BORDER NAMES AND COLORS AS TEXT"; } OperationParameters* OperationBorderExportColorTable::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addBorderParameter(1, "border-file", "the input border file"); ret->addStringParameter(2, "table-out", "output - the output text file");//fake output formatting ret->createOptionalParameter(3, "-class-colors", "use class colors instead of the name colors"); ret->setHelpText( AString("Takes the names and colors of each border, and writes it to the same format as -metric-label-import expects. ") + "By default, the borders are colored by border name, specify -class-colors to color them by class instead. " + "The key values start at 1 and follow the order of the borders in the file." ); return ret; } int OperationBorderExportColorTable::floatTo255(const float& in) { return (int)floor(in * 255.0f + 0.5f); } void OperationBorderExportColorTable::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); BorderFile* myFile = myParams->getBorder(1); AString outfileName = myParams->getString(2); bool useClassColors = myParams->getOptionalParameter(3)->m_present; ofstream outFile(outfileName.toLocal8Bit().constData()); if (!outFile) throw OperationException("failed to open output text file"); const GiftiLabelTable* tempTable = NULL; if (useClassColors) { tempTable = myFile->getClassColorTable(); } else { tempTable = myFile->getNameColorTable(); } int numBorders = myFile->getNumberOfBorders(); for (int i = 0; i < numBorders; ++i) { const Border* thisBorder = myFile->getBorder(i); outFile << thisBorder->getName() << endl; outFile << i + 1 << " ";//NOTE: NEVER use the label key const GiftiLabel* tempLabel = NULL; if (useClassColors) { tempLabel = tempTable->getLabelBestMatching(thisBorder->getClassName());//see BrainOpenGLFixedPipeline::drawSurfaceBorders(Surface* surface) } else { tempLabel = tempTable->getLabelBestMatching(thisBorder->getName()); } if (tempLabel == NULL) { outFile << "0 0 0 255" << endl; } else { outFile << floatTo255(tempLabel->getRed()) << " " << floatTo255(tempLabel->getGreen()) << " " << floatTo255(tempLabel->getBlue()) << " " << floatTo255(tempLabel->getAlpha()) << endl; } if (!outFile) throw OperationException("error writing to output text file"); } } workbench-1.1.1/src/Operations/OperationBorderExportColorTable.h000066400000000000000000000027731255417355300250510ustar00rootroot00000000000000#ifndef __OPERATION_BORDER_EXPORT_COLOR_TABLE_H__ #define __OPERATION_BORDER_EXPORT_COLOR_TABLE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationBorderExportColorTable : public AbstractOperation { static int floatTo255(const float& in); public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationBorderExportColorTable; } #endif //__OPERATION_BORDER_EXPORT_COLOR_TABLE_H__ workbench-1.1.1/src/Operations/OperationBorderFileExportToCaret5.cxx000066400000000000000000000125131255417355300256150ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretAssert.h" #include "CaretLogger.h" #include "BorderFile.h" #include "DataFileException.h" #include "FileInformation.h" #include "OperationBorderFileExportToCaret5.h" #include "OperationException.h" #include "SurfaceFile.h" using namespace caret; /** * \class caret::OperationBorderFileExportToCaret5 * \brief Export border file to Caret5 file format */ /** * @return Command line switch */ AString OperationBorderFileExportToCaret5::getCommandSwitch() { return "-border-file-export-to-caret5"; } /** * @return Short description of operation */ AString OperationBorderFileExportToCaret5::getShortDescription() { return "EXPORT BORDER FILE TO CARET5 FILE FORMAT"; } /** * @return Parameters for operation */ OperationParameters* OperationBorderFileExportToCaret5::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addStringParameter(1, "border-file", "workbench border file"); ret->addStringParameter(2, "output-file-prefix", "prefix for name of output caret5 border/borderproj/bordercolor files"); ParameterComponent* surfaceOpt = ret->createRepeatableParameter(3, "-surface", "specify an input surface"); surfaceOpt->addSurfaceParameter(1, "surface-in", "a surface file for unprojection of borders"); AString helpText("A Workbench border file may contain borders for multiple " "structures and borders that are both projected and " "unprojected. It also contains a color table for the borders. " "\n" "\n" "Caret5 has both border (unprojected) and border projection " "(projected) files. In addition, each Caret5 border or border " "projection file typically contains data for a single structure. " "Caret5 also uses a border color file that associates colors with " "the names of the borders. " "\n" "\n" "This command will try to output both Caret5 border and " "border projection files. Each output border/border projection " "file will contains data for one structure so there may be " "many files created. The structure name is included in the name of " "each border or border projection file that is created. " "\n" "\n" "One Caret5 border color file will also be produced by this " "command. " "\n" "\n" "Providing surface(s) as input parameters is optional, but recommended. " "Surfaces may be needed to create both projected and/or unprojected coordinates " "of borders. If there is a failure to produce an output border or " "border projection due to a missing surface with the matching structure, " "an error message will be displayed and some " "output files will not be created. " "\n" "\n" "When writing new files, this command will overwrite a file " "with the same name. " ""); ret->setHelpText(helpText); return ret; } /** * Use Parameters and perform operation */ void OperationBorderFileExportToCaret5::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); AString borderFileName = FileInformation(myParams->getString(1)).getAbsoluteFilePath(); AString outputCaret5FilePrefix = FileInformation(myParams->getString(2)).getAbsoluteFilePath(); std::vector allSurfaces; const std::vector& surfaceInputs = *(myParams->getRepeatableParameterInstances(3)); const int32_t numSurfaceInputs = static_cast(surfaceInputs.size()); for (int32_t iSurf = 0; iSurf < numSurfaceInputs; iSurf++) { SurfaceFile* sf = surfaceInputs[iSurf]->getSurface(1); allSurfaces.push_back(sf); } try { BorderFile borderFile; borderFile.readFile(borderFileName); borderFile.exportToCaret5Format(allSurfaces, outputCaret5FilePrefix); } catch (const DataFileException& dfe) { throw OperationException(dfe); } } workbench-1.1.1/src/Operations/OperationBorderFileExportToCaret5.h000066400000000000000000000030111255417355300252330ustar00rootroot00000000000000#ifndef __OPERATION_BORDER_FILE_EXPORT_TO_CARET5_H__ #define __OPERATION_BORDER_FILE_EXPORT_TO_CARET5_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationBorderFileExportToCaret5 : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationBorderFileExportToCaret5; } // namespace #endif //__OPERATION_BORDER_FILE_EXPORT_TO_CARET5_H__ workbench-1.1.1/src/Operations/OperationBorderMerge.cxx000066400000000000000000000141741255417355300232310ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationBorderMerge.h" #include "OperationException.h" #include "Border.h" #include "BorderFile.h" #include #include #include using namespace caret; using namespace std; AString OperationBorderMerge::getCommandSwitch() { return "-border-merge"; } AString OperationBorderMerge::getShortDescription() { return "MERGE BORDER FILES INTO A NEW FILE"; } OperationParameters* OperationBorderMerge::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addBorderOutputParameter(1, "border-file-out", "the output border file"); ParameterComponent* borderOpt = ret->createRepeatableParameter(2, "-border", "specify an input border file"); borderOpt->addBorderParameter(1, "border-file-in", "a border file to use borders from"); ParameterComponent* selectOpt = borderOpt->createRepeatableParameter(2, "-select", "select a single border to use"); selectOpt->addStringParameter(1, "border", "the border number or name"); OptionalParameter* upToOpt = selectOpt->createOptionalParameter(2, "-up-to", "use an inclusive range of borders"); upToOpt->addStringParameter(1, "last-border", "the number or name of the last column to include"); upToOpt->createOptionalParameter(2, "-reverse", "use the range in reverse order"); ret->setHelpText( AString("Takes one or more border files and makes a new border file from the borders in them.\n\n") + "Example: wb_command -border-merge out.border -border first.border -select 1 -border second.border\n\n" + "This example would take the first border from first.border, followed by all borders from second.border, " + "and write these to out.border." ); return ret; } void OperationBorderMerge::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); BorderFile* outFile = myParams->getOutputBorder(1); const vector& borderInst = *(myParams->getRepeatableParameterInstances(2)); int numInputs = (int)borderInst.size(); if (numInputs < 1) throw OperationException("no inputs specified"); for (int i = 0; i < numInputs; ++i) { BorderFile* input = borderInst[i]->getBorder(1); if (i != 0)//structure automatically gets set on empty file by added borders { if (outFile->getStructure() != input->getStructure()) throw OperationException("input files have different structures"); } if (input->getNumberOfNodes() != -1) { int outNodes = outFile->getNumberOfNodes(); if (outNodes != -1) { if (outNodes != input->getNumberOfNodes()) throw OperationException("input files have different number of surface vertices"); } else { outFile->setNumberOfNodes(input->getNumberOfNodes()); } } int numBorderParts = input->getNumberOfBorders(); const vector& selectOpts = *(borderInst[i]->getRepeatableParameterInstances(2)); int numSelectOpts = (int)selectOpts.size(); if (numSelectOpts > 0) { BorderMultiPartHelper myHelp(input); for (int j = 0; j < numSelectOpts; ++j) { int initialBorder = myHelp.fromNumberOrName(selectOpts[j]->getString(1)); if (initialBorder < 0) throw OperationException("border '" + selectOpts[j]->getString(1) + "' not found in file '" + input->getFileName() + "'"); OptionalParameter* upToOpt = selectOpts[j]->getOptionalParameter(2); if (upToOpt->m_present) { int finalBorder = myHelp.fromNumberOrName(upToOpt->getString(1)); if (finalBorder < 0) throw OperationException("ending border '" + selectOpts[j]->getString(1) + "' not found in file '" + input->getFileName() + "'"); bool reverse = upToOpt->getOptionalParameter(2)->m_present; if (reverse) { for (int b = finalBorder; b >= initialBorder; --b) { for (int k = 0; k < (int)myHelp.borderPieceList[b].size(); ++k) { outFile->addBorder(new Border(*(input->getBorder(myHelp.borderPieceList[b][k])))); } } } else { for (int b = initialBorder; b <= finalBorder; ++b) { for (int k = 0; k < (int)myHelp.borderPieceList[b].size(); ++k) { outFile->addBorder(new Border(*(input->getBorder(myHelp.borderPieceList[b][k])))); } } } } else { for (int k = 0; k < (int)myHelp.borderPieceList[initialBorder].size(); ++k) { outFile->addBorder(new Border(*(input->getBorder(myHelp.borderPieceList[initialBorder][k])))); } } } } else { for (int j = 0; j < numBorderParts; ++j) { outFile->addBorder(new Border(*(input->getBorder(j)))); } } } } workbench-1.1.1/src/Operations/OperationBorderMerge.h000066400000000000000000000026031255417355300226500ustar00rootroot00000000000000#ifndef __OPERATION_BORDER_MERGE_H__ #define __OPERATION_BORDER_MERGE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationBorderMerge : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationBorderMerge; } #endif //__OPERATION_BORDER_MERGE_H__ workbench-1.1.1/src/Operations/OperationCiftiChangeTimestep.cxx000066400000000000000000000104611255417355300247060ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationCiftiChangeTimestep.h" #include "OperationException.h" #include "CiftiFile.h" #include "CaretLogger.h" #include "MultiDimIterator.h" using namespace caret; using namespace std; AString OperationCiftiChangeTimestep::getCommandSwitch() { return "-cifti-change-timestep"; } AString OperationCiftiChangeTimestep::getShortDescription() { return "CHANGE THE TIMESTEP OF A CIFTI FILE"; } OperationParameters* OperationCiftiChangeTimestep::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addStringParameter(1, "cifti", "the cifti file to modify"); OptionalParameter* rowTimestep = ret->createOptionalParameter(2, "-row-timestep", "set the timestep along rows"); rowTimestep->addDoubleParameter(1, "seconds", "seconds per timestep"); OptionalParameter* columnTimestep = ret->createOptionalParameter(3, "-column-timestep", "set the timestep along columns"); columnTimestep->addDoubleParameter(1, "seconds", "seconds per timestep"); ret->setHelpText( AString("Warns if a dimension specified is not timepoints, otherwise modifies the timestep, and finally writes the result to ") + "the same filename if any dimensions were modified.\nNOTE: you probably want -row-timestep, as that matches the .dtseries.nii specification. " + "The other option is available just for completeness." ); return ret; } void OperationCiftiChangeTimestep::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); AString ciftiName = myParams->getString(1); OptionalParameter* rowTimestep = myParams->getOptionalParameter(2); OptionalParameter* columnTimestep = myParams->getOptionalParameter(3); if (!columnTimestep->m_present && !rowTimestep->m_present) { return; } CiftiFile myCifti; myCifti.openFile(ciftiName); CiftiXML tempXML = myCifti.getCiftiXML(); bool modified = false; if (rowTimestep->m_present) { float step = (float)rowTimestep->getDouble(1); if (tempXML.getMappingType(CiftiXML::ALONG_ROW) == CiftiMappingType::SERIES) { CiftiSeriesMap& myMap = tempXML.getSeriesMap(CiftiXML::ALONG_ROW); myMap.setStep(step);//TSC: leave units as-is, I guess modified = true; } else { CaretLogWarning("could not set row timestep"); } } if (columnTimestep->m_present) { float step = (float)columnTimestep->getDouble(1); if (tempXML.getMappingType(CiftiXML::ALONG_COLUMN) == CiftiMappingType::SERIES) { CiftiSeriesMap& myMap = tempXML.getSeriesMap(CiftiXML::ALONG_COLUMN); myMap.setStep(step);//TSC: leave units as-is, I guess modified = true; } else { CaretLogWarning("could not set row timestep"); } } if (modified) { myCifti.convertToInMemory(); CiftiFile outCifti; outCifti.setWritingFile(ciftiName);//starts on-disk writing outCifti.setCiftiXML(tempXML); const vector& dims = myCifti.getDimensions(); vector extraDims(dims.begin() + 1, dims.end()); vector scratchrow(dims[0]); for (MultiDimIterator iter(extraDims); !iter.atEnd(); ++iter) { myCifti.getRow(scratchrow.data(), *iter); outCifti.setRow(scratchrow.data(), *iter); } outCifti.writeFile(ciftiName);//superfluous, unless we aren't writing on-disk } } workbench-1.1.1/src/Operations/OperationCiftiChangeTimestep.h000066400000000000000000000026661255417355300243430ustar00rootroot00000000000000#ifndef __OPERATION_CIFTI_CHANGE_TIMESTEP_H__ #define __OPERATION_CIFTI_CHANGE_TIMESTEP_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationCiftiChangeTimestep : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationCiftiChangeTimestep; } #endif //__OPERATION_CIFTI_CHANGE_TIMESTEP_H__ workbench-1.1.1/src/Operations/OperationCiftiConvert.cxx000066400000000000000000000637361255417355300234430ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationCiftiConvert.h" #include "OperationException.h" #include "CaretAssert.h" #include "CaretPointer.h" #include "CiftiFile.h" #include "CiftiXML.h" #include "FloatMatrix.h" #include "GiftiFile.h" #include "VolumeFile.h" #include #include #include #include #include #include using namespace caret; using namespace std; AString OperationCiftiConvert::getCommandSwitch() { return "-cifti-convert"; } AString OperationCiftiConvert::getShortDescription() { return "DUMP CIFTI MATRIX INTO OTHER FORMATS"; } OperationParameters* OperationCiftiConvert::getParameters() { OperationParameters* ret = new OperationParameters(); OptionalParameter* toGiftiExt = ret->createOptionalParameter(1, "-to-gifti-ext", "convert to GIFTI external binary"); toGiftiExt->addCiftiParameter(1, "cifti-in", "the input cifti file"); toGiftiExt->addStringParameter(2, "gifti-out", "output - the output gifti file"); OptionalParameter* fromGiftiExt = ret->createOptionalParameter(2, "-from-gifti-ext", "convert a GIFTI made with this command back into a CIFTI"); fromGiftiExt->addStringParameter(1, "gifti-in", "the input gifti file"); fromGiftiExt->addCiftiOutputParameter(2, "cifti-out", "the output cifti file"); OptionalParameter* fgresetTimeOpt = fromGiftiExt->createOptionalParameter(3, "-reset-timepoints", "reset the mapping along rows to timepoints, taking length from the gifti file"); fgresetTimeOpt->addDoubleParameter(1, "timestep", "the desired time between frames"); fgresetTimeOpt->addDoubleParameter(2, "timestart", "the desired time offset of the initial frame"); OptionalParameter* fgresetTimeunitsOpt = fgresetTimeOpt->createOptionalParameter(3, "-unit", "use a unit other than time"); fgresetTimeunitsOpt->addStringParameter(1, "unit", "unit identifier (default SECOND)"); fromGiftiExt->createOptionalParameter(4, "-reset-scalars", "reset mapping along rows to scalars, taking length from the gifti file"); OptionalParameter* fromGiftiReplace = fromGiftiExt->createOptionalParameter(5, "-replace-binary", "replace data with a binary file"); fromGiftiReplace->addStringParameter(1, "binary-in", "the binary file that contains replacement data"); fromGiftiReplace->createOptionalParameter(2, "-flip-endian", "byteswap the binary file"); fromGiftiReplace->createOptionalParameter(3, "-transpose", "transpose the binary file"); OptionalParameter* toNifti = ret->createOptionalParameter(3, "-to-nifti", "convert to NIFTI1"); toNifti->addCiftiParameter(1, "cifti-in", "the input cifti file"); toNifti->addVolumeOutputParameter(2, "nifti-out", "the output nifti file"); OptionalParameter* fromNifti = ret->createOptionalParameter(4, "-from-nifti", "convert a NIFTI (1 or 2) file made with this command back into CIFTI"); fromNifti->addVolumeParameter(1, "nifti-in", "the input nifti file"); fromNifti->addCiftiParameter(2, "cifti-template", "a cifti file with the dimension(s) and mapping(s) that should be used"); fromNifti->addCiftiOutputParameter(3, "cifti-out", "the output cifti file"); OptionalParameter* fnresetTimeOpt = fromNifti->createOptionalParameter(4, "-reset-timepoints", "reset the mapping along rows to timepoints, taking length from the nifti file"); fnresetTimeOpt->addDoubleParameter(1, "timestep", "the desired time between frames"); fnresetTimeOpt->addDoubleParameter(2, "timestart", "the desired time offset of the initial frame"); OptionalParameter* fnresetTimeunitsOpt = fnresetTimeOpt->createOptionalParameter(3, "-unit", "use a unit other than time"); fnresetTimeunitsOpt->addStringParameter(1, "unit", "unit identifier (default SECOND)"); fromNifti->createOptionalParameter(5, "-reset-scalars", "reset mapping along rows to scalars, taking length from the nifti file"); OptionalParameter* toText = ret->createOptionalParameter(5, "-to-text", "convert to a plain text file"); toText->addCiftiParameter(1, "cifti-in", "the input cifti file"); toText->addStringParameter(2, "text-out", "output - the output text file"); OptionalParameter* toTextColDelimOpt = toText->createOptionalParameter(3, "-col-delim", "choose string to put between elements in a row"); toTextColDelimOpt->addStringParameter(1, "delim-string", "the string to use (default is a tab character)"); OptionalParameter* fromText = ret->createOptionalParameter(6, "-from-text", "convert from plain text to cifti"); fromText->addStringParameter(1, "text-in", "the input text file"); fromText->addCiftiParameter(2, "cifti-template", "a cifti file with the dimension(s) and mapping(s) that should be used"); fromText->addCiftiOutputParameter(3, "cifti-out", "the output cifti file"); OptionalParameter* fromTextColDelimOpt = fromText->createOptionalParameter(4, "-col-delim", "specify string that is between elements in a row"); fromTextColDelimOpt->addStringParameter(1, "delim-string", "the string to use (default is any whitespace)"); OptionalParameter* ftresetTimeOpt = fromText->createOptionalParameter(5, "-reset-timepoints", "reset the mapping along rows to timepoints, taking length from the text file"); ftresetTimeOpt->addDoubleParameter(1, "timestep", "the desired time between frames"); ftresetTimeOpt->addDoubleParameter(2, "timestart", "the desired time offset of the initial frame"); OptionalParameter* ftresetTimeunitsOpt = ftresetTimeOpt->createOptionalParameter(3, "-unit", "use a unit other than time"); ftresetTimeunitsOpt->addStringParameter(1, "unit", "unit identifier (default SECOND)"); fromText->createOptionalParameter(6, "-reset-scalars", "reset mapping along rows to scalars, taking length from the text file"); AString myText = AString("This command is used to convert a full CIFTI matrix to/from formats that can be used by programs that don't understand CIFTI. ") + "If you want to write an existing CIFTI file with a different CIFTI version, see -file-convert, and its -cifti-version-convert option. " + "If you want part of the CIFTI file as a metric, label, or volume file, see -cifti-separate. " + "If you want to create a CIFTI file from metric and/or volume files, see the -cifti-create-* commands. " + "You must specify exactly one of -to-gifti-ext, -from-gifti-ext, -to-nifti, -from-nifti, -to-text, or -from-text. " + "The -transpose option to -from-gifti-ext is needed if the replacement binary file is in column-major order. " + "The -unit options accept these values:\n"; vector units = CiftiSeriesMap::getAllUnits(); for (int i = 0; i < (int)units.size(); ++i) { myText += "\n" + CiftiSeriesMap::unitToString(units[i]); } ret->setHelpText(myText); return ret; } void OperationCiftiConvert::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); int modes = 0; OptionalParameter* toGiftiExt = myParams->getOptionalParameter(1); OptionalParameter* fromGiftiExt = myParams->getOptionalParameter(2); OptionalParameter* toNifti = myParams->getOptionalParameter(3); OptionalParameter* fromNifti = myParams->getOptionalParameter(4); OptionalParameter* toText = myParams->getOptionalParameter(5); OptionalParameter* fromText = myParams->getOptionalParameter(6); if (toGiftiExt->m_present) ++modes; if (fromGiftiExt->m_present) ++modes; if (toNifti->m_present) ++modes; if (fromNifti->m_present) ++modes; if (toText->m_present) ++modes; if (fromText->m_present) ++modes; if (modes != 1) { throw OperationException("you must specify exactly one conversion mode"); } if (toGiftiExt->m_present) { CiftiFile* myInFile = toGiftiExt->getCifti(1); AString myGiftiName = toGiftiExt->getString(2); vector myDims; myDims.push_back(myInFile->getNumberOfRows()); myDims.push_back(myInFile->getNumberOfColumns()); const CiftiXML& myXML = myInFile->getCiftiXML();//soft of hack - metric files use "normal" when they really mean none, using the same thing as metric files means it should just work if (myXML.getNumberOfDimensions() != 2) throw OperationException("conversion only supported for 2D cifti"); GiftiDataArray* myArray = new GiftiDataArray(NiftiIntentEnum::NIFTI_INTENT_NORMAL, NiftiDataTypeEnum::NIFTI_TYPE_FLOAT32, myDims, GiftiEncodingEnum::EXTERNAL_FILE_BINARY); float* myOutData = myArray->getDataPointerFloat(); for (int i = 0; i < myInFile->getNumberOfRows(); ++i) { myInFile->getRow(myOutData + i * myInFile->getNumberOfColumns(), i); } AString myCiftiXML = myXML.writeXMLToString(); myArray->getMetaData()->set("CiftiXML", myCiftiXML); GiftiFile myOutFile; myOutFile.setEncodingForWriting(GiftiEncodingEnum::EXTERNAL_FILE_BINARY); myOutFile.addDataArray(myArray); myOutFile.writeFile(myGiftiName); } if (fromGiftiExt->m_present) { AString myGiftiName = fromGiftiExt->getString(1); GiftiFile myInFile; myInFile.readFile(myGiftiName); if (myInFile.getNumberOfDataArrays() != 1) { throw OperationException("gifti file was not created by -cifti-convert, please use a -cifti-create-* command"); } GiftiDataArray* dataArrayRef = myInFile.getDataArray(0); if (!dataArrayRef->getMetaData()->exists("CiftiXML")) throw OperationException("gifti file was not created by -cifti-convert, please use a -cifti-create-* command"); if (dataArrayRef->getDataType() != NiftiDataTypeEnum::NIFTI_TYPE_FLOAT32)//this may not be needed { throw OperationException("input gifti has the wrong data type"); } CiftiFile* myOutFile = fromGiftiExt->getOutputCifti(2); CiftiXML myXML; myXML.readXML(dataArrayRef->getMetaData()->get("CiftiXML")); if (myXML.getNumberOfDimensions() != 2) throw OperationException("conversion only supported for 2D cifti"); int64_t numCols = dataArrayRef->getNumberOfComponents(); int64_t numRows = dataArrayRef->getNumberOfRows(); OptionalParameter* fgresetTimeOpt = fromGiftiExt->getOptionalParameter(3); if (fgresetTimeOpt->m_present) { CiftiSeriesMap::Unit myUnit = CiftiSeriesMap::SECOND; OptionalParameter* fgresetTimeunitsOpt = fgresetTimeOpt->getOptionalParameter(3); if (fgresetTimeunitsOpt->m_present) { bool ok = false; myUnit = CiftiSeriesMap::stringToUnit(fgresetTimeunitsOpt->getString(1), ok); if (!ok) throw OperationException("unrecognized unit name: '" + fgresetTimeunitsOpt->getString(1) + "'"); } myXML.setMap(CiftiXML::ALONG_ROW, CiftiSeriesMap(numCols, fgresetTimeOpt->getDouble(2), fgresetTimeOpt->getDouble(1), myUnit)); } else { if (myXML.getMappingType(CiftiXML::ALONG_ROW) == CiftiMappingType::SERIES) { myXML.getSeriesMap(CiftiXML::ALONG_ROW).setLength(numCols); } } if (myXML.getMappingType(CiftiXML::ALONG_COLUMN) == CiftiMappingType::SERIES) { myXML.getSeriesMap(CiftiXML::ALONG_COLUMN).setLength(numRows); } if (fromGiftiExt->getOptionalParameter(4)->m_present) { if (fgresetTimeOpt->m_present) throw OperationException("only one of -reset-timepoints and -reset-scalars may be specified"); CiftiScalarsMap newMap; newMap.setLength(numCols); myXML.setMap(CiftiXML::ALONG_ROW, newMap); } if (myXML.getDimensionLength(CiftiXML::ALONG_ROW) != numCols || myXML.getDimensionLength(CiftiXML::ALONG_COLUMN) != numRows) { throw OperationException("dimensions of input gifti array (" + AString::number(numRows) + " rows, " + AString::number(numCols) + " columns)" + " do not match dimensions of the Cifti XML (" + AString::number(myXML.getDimensionLength(CiftiXML::ALONG_COLUMN)) + " rows, " + AString::number(myXML.getDimensionLength(CiftiXML::ALONG_ROW)) + " columns)"); } myOutFile->setCiftiXML(myXML); OptionalParameter* fromGiftiReplace = fromGiftiExt->getOptionalParameter(5); if (fromGiftiReplace->m_present) { AString replaceFileName = fromGiftiReplace->getString(1); QFile replaceFile(replaceFileName); if (replaceFile.size() != (int64_t)(sizeof(float) * numCols * numRows)) { throw OperationException("replacement file is the wrong size, size is " + AString::number(replaceFile.size()) + ", needed " + AString::number(sizeof(float) * numCols * numRows)); } if (!replaceFile.open(QIODevice::ReadOnly)) { throw OperationException("unable to open replacement file for reading"); } OptionalParameter* swapBytes = fromGiftiReplace->getOptionalParameter(2); OptionalParameter* transpose = fromGiftiReplace->getOptionalParameter(3); int64_t readSize = numCols, numReads = numRows; if (transpose->m_present) { readSize = numRows; numReads = numCols; } CaretArray myScratch(readSize); for (int i = 0; i < numReads; ++i) { if (replaceFile.read((char*)(myScratch.getArray()), sizeof(float) * readSize) != (int64_t)(sizeof(float) * readSize)) { throw OperationException("short read from replacement file, aborting"); } float tempVal; char* tempValPointer = (char*)&tempVal;//copy method isn't as fast, but it is clean for (int j = 0; j < readSize; ++j) { if (swapBytes->m_present) { char* elemPointer = (char*)(myScratch.getArray() + j); for (int k = 0; k < (int)sizeof(float); ++k) { tempValPointer[k] = elemPointer[sizeof(float) - 1 - k]; } } else { tempVal = myScratch[j]; } if (transpose->m_present) { int32_t indices[] = {j, i}; dataArrayRef->setDataFloat32(indices, tempVal); } else { int32_t indices[] = {i, j}; dataArrayRef->setDataFloat32(indices, tempVal); } } } } vector scratchRow(numCols); for (int i = 0; i < numRows; ++i) { for (int j = 0; j < numCols; ++j) { int32_t indices[] = {i, j}; scratchRow[j] = dataArrayRef->getDataFloat32(indices); } myOutFile->setRow(scratchRow.data(), i); } } if (toNifti->m_present) { CiftiFile* myCiftiIn = toNifti->getCifti(1); if (myCiftiIn->getCiftiXML().getNumberOfDimensions() != 2) throw OperationException("conversion only supported for 2D cifti"); VolumeFile* myNiftiOut = toNifti->getOutputVolume(2); vector outDims(4, 1); outDims[3] = myCiftiIn->getNumberOfColumns(); if (outDims[3] > numeric_limits::max()) throw OperationException("cifti rows are too long for nifti1, failing"); int64_t numRows = myCiftiIn->getNumberOfRows(); int64_t temp = numRows; int index = 0; while (temp > numeric_limits::max()) { if (index > 1) throw OperationException("too many cifti rows for nifti1 spatial dimensions, failing"); outDims[index] = numeric_limits::max(); temp = (temp - 1) / numeric_limits::max() + 1;//round up ++index; } outDims[index] = temp; myNiftiOut->reinitialize(outDims, FloatMatrix::identity(4).getMatrix()); myNiftiOut->setValueAllVoxels(0.0f); int64_t ijk[3] = { 0, 0, 0 }; vector rowscratch(outDims[3]); for (int64_t i = 0; i < numRows; ++i) { myCiftiIn->getRow(rowscratch.data(), i); for (int64_t j = 0; j < outDims[3]; ++j) { myNiftiOut->setValue(rowscratch[j], ijk, j); } ++ijk[0]; if (ijk[0] >= outDims[0]) { ijk[0] = 0; ++ijk[1]; if (ijk[1] >= outDims[1]) { ijk[1] = 0; ++ijk[2]; CaretAssert(i == numRows - 1 || ijk[2] < outDims[2]);//in case it divided exactly } } } } if (fromNifti->m_present) { VolumeFile* myNiftiIn = fromNifti->getVolume(1); CiftiFile* myTemplate = fromNifti->getCifti(2); CiftiFile* myCiftiOut = fromNifti->getOutputCifti(3); vector myDims; myNiftiIn->getDimensions(myDims); if (myDims[4] != 1) throw OperationException("input nifti has multiple components, aborting"); CiftiXML outXML = myTemplate->getCiftiXML(); if (outXML.getNumberOfDimensions() != 2) throw OperationException("conversion only supported for 2D cifti"); OptionalParameter* fnresetTimeOpt = fromNifti->getOptionalParameter(4); if (fnresetTimeOpt->m_present) { CiftiSeriesMap::Unit myUnit = CiftiSeriesMap::SECOND; OptionalParameter* fnresetTimeunitsOpt = fnresetTimeOpt->getOptionalParameter(3); if (fnresetTimeunitsOpt->m_present) { bool ok = false; myUnit = CiftiSeriesMap::stringToUnit(fnresetTimeunitsOpt->getString(1), ok); if (!ok) throw OperationException("unrecognized unit name: '" + fnresetTimeunitsOpt->getString(1) + "'"); } outXML.setMap(CiftiXML::ALONG_ROW, CiftiSeriesMap(myDims[3], fnresetTimeOpt->getDouble(2), fnresetTimeOpt->getDouble(1), myUnit)); } if (fromNifti->getOptionalParameter(5)->m_present) { if (fnresetTimeOpt->m_present) throw OperationException("only one of -reset-timepoints and -reset-scalars may be specified"); CiftiScalarsMap newMap; newMap.setLength(myDims[3]); outXML.setMap(CiftiXML::ALONG_ROW, newMap); } int64_t numRows = outXML.getDimensionLength(CiftiXML::ALONG_COLUMN), numCols = outXML.getDimensionLength(CiftiXML::ALONG_ROW); if (myDims[3] != numCols) { throw OperationException("input nifti has the different size than cifti template for row length (nifti says " + AString::number(myDims[3]) + ", cifti XML says " + AString::number(numCols) + ")"); } if (numRows > myDims[0] * myDims[1] * myDims[2]) { throw OperationException("input nifti is too small for column length (need at least " + AString::number(numRows) + ", product of first three nifti dimensions is " + AString::number(myDims[0] * myDims[1] * myDims[2]) + ")"); } myCiftiOut->setCiftiXML(outXML); vector rowscratch(numCols); for (int64_t i = 0; i < numRows; ++i) { for (int64_t j = 0; j < numCols; ++j) { rowscratch[j] = myNiftiIn->getFrame(j)[i]; } myCiftiOut->setRow(rowscratch.data(), i); } } if (toText->m_present) { CiftiFile* ciftiIn = toText->getCifti(1); AString textOutName = toText->getString(2); AString delim = "\t"; OptionalParameter* colDelimOpt = toText->getOptionalParameter(3); if (colDelimOpt->m_present) { delim = colDelimOpt->getString(1); if (delim.isEmpty()) throw OperationException("delimiter string must not be empty");//catch some possible stupid mistakes } const CiftiXML& myXML = ciftiIn->getCiftiXML(); if (myXML.getNumberOfDimensions() != 2) throw OperationException("conversion only supported for 2D cifti"); if (myXML.getDimensionLength(0) < 1) throw OperationException("input cifti has zero-length rows"); vector dims = myXML.getDimensions(); vector scratchRow(dims[0]); fstream textOut(textOutName.toLocal8Bit().constData(), fstream::out | fstream::trunc | fstream::binary);//write the same file, no newline translation, regardless of OS for (int64_t i = 0; i < dims[1]; ++i) { ciftiIn->getRow(scratchRow.data(), i); textOut << AString::number(scratchRow[0]); for (int64_t j = 1; j < dims[0]; ++j) { textOut << delim << AString::number(scratchRow[j]); } textOut << "\n"; } } if (fromText->m_present) { AString textInName = fromText->getString(1); CiftiFile* ciftiTemplate = fromText->getCifti(2); CiftiFile* ciftiOut = fromText->getOutputCifti(3); AString delim = "";//empty is special value for "any whitespace" OptionalParameter* colDelimOpt = fromText->getOptionalParameter(4); if (colDelimOpt->m_present) { delim = colDelimOpt->getString(1); if (delim.isEmpty()) throw OperationException("delimiter string must not be empty"); } CiftiXML outXML = ciftiTemplate->getCiftiXML(); if (outXML.getNumberOfDimensions() != 2) throw OperationException("conversion only supported for 2D cifti"); int64_t numRows = outXML.getDimensionLength(CiftiXML::ALONG_COLUMN); fstream textIn(textInName.toLocal8Bit().constData(), fstream::in);//do newline translation on input string templine;//need to extract a row from the file first to set the row length if (!getline(textIn, templine)) throw OperationException("failed to read from input text file"); QStringList entries; if (delim.isEmpty()) { entries = QString(templine.c_str()).split(QRegExp("\\s+"), QString::SkipEmptyParts); } else { entries = QString(templine.c_str()).split(delim, QString::SkipEmptyParts); } if (numRows < 1) throw OperationException("template cifti file has no data");//this probably throws an exception in CiftiFile, but double check int textRowLength = entries.size(); OptionalParameter* ftresetTimeOpt = fromText->getOptionalParameter(5); if (ftresetTimeOpt->m_present) { CiftiSeriesMap::Unit myUnit = CiftiSeriesMap::SECOND; OptionalParameter* ftresetTimeunitsOpt = ftresetTimeOpt->getOptionalParameter(3); if (ftresetTimeunitsOpt->m_present) { bool ok = false; myUnit = CiftiSeriesMap::stringToUnit(ftresetTimeunitsOpt->getString(1), ok); if (!ok) throw OperationException("unrecognized unit name: '" + ftresetTimeunitsOpt->getString(1) + "'"); } outXML.setMap(CiftiXML::ALONG_ROW, CiftiSeriesMap(textRowLength, ftresetTimeOpt->getDouble(2), ftresetTimeOpt->getDouble(1), myUnit)); } if (fromText->getOptionalParameter(6)->m_present) { if (ftresetTimeOpt->m_present) throw OperationException("only one of -reset-timepoints and -reset-scalars may be specified"); CiftiScalarsMap newMap; newMap.setLength(textRowLength); outXML.setMap(CiftiXML::ALONG_ROW, newMap); } if (textRowLength != outXML.getDimensionLength(CiftiXML::ALONG_ROW)) { throw OperationException("text file has different row length than cifti template (text file says " + AString::number(textRowLength) + ", cifti XML says " + AString::number(outXML.getDimensionLength(CiftiXML::ALONG_ROW)) + ")"); } ciftiOut->setCiftiXML(outXML); vector temprow(textRowLength); bool ok = false; for (int i = 0; i < textRowLength; ++i) { temprow[i] = entries[i].toFloat(&ok); if (!ok) throw OperationException("failed to convert text to number: '" + entries[i] + "'"); } ciftiOut->setRow(temprow.data(), 0); for (int64_t j = 1; j < numRows; ++j) { if (!getline(textIn, templine)) throw OperationException("failed to read from input text file (not enough rows)"); if (delim.isEmpty()) { entries = QString(templine.c_str()).split(QRegExp("\\s+"), QString::SkipEmptyParts); } else { entries = QString(templine.c_str()).split(delim, QString::SkipEmptyParts); } if (entries.size() != textRowLength) throw OperationException("text file has inconsistent line length"); for (int i = 0; i < textRowLength; ++i) { temprow[i] = entries[i].toFloat(&ok); if (!ok) throw OperationException("failed to convert text to number: '" + entries[i] + "'"); } ciftiOut->setRow(temprow.data(), j); } } } workbench-1.1.1/src/Operations/OperationCiftiConvert.h000066400000000000000000000026111255417355300230510ustar00rootroot00000000000000#ifndef __OPERATION_CIFTI_CONVERT_H__ #define __OPERATION_CIFTI_CONVERT_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationCiftiConvert : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationCiftiConvert; } #endif //__OPERATION_CIFTI_CONVERT_H__ workbench-1.1.1/src/Operations/OperationCiftiConvertToScalar.cxx000066400000000000000000000123301255417355300250540ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretLogger.h" #include "CiftiFile.h" #include "FileInformation.h" #include "OperationCiftiConvertToScalar.h" #include "OperationException.h" #include #include using namespace caret; using namespace std; AString OperationCiftiConvertToScalar::getCommandSwitch() { return "-cifti-convert-to-scalar"; } AString OperationCiftiConvertToScalar::getShortDescription() { return "CHANGE A CIFTI DIMENSION TO NAMED SCALAR MAPS"; } OperationParameters* OperationCiftiConvertToScalar::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiParameter(1, "cifti-in", "input cifti file"); ret->addStringParameter(2, "direction", "which mapping to change to scalar maps, ROW or COLUMN"); ret->addCiftiOutputParameter(3, "cifti-out", "output cifti file"); OptionalParameter* nameFileOpt = ret->createOptionalParameter(4, "-name-file", "specify names for the maps"); nameFileOpt->addStringParameter(1, "file", "text file containing map names, one per line"); ret->setHelpText( AString("Creates a new cifti file with the same data as the input, but with one of the dimensions set to contain strings identifying each map. ") + "Specifying ROW will convert a dtseries file to a dscalar file." ); return ret; } void OperationCiftiConvertToScalar::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); CiftiFile* ciftiIn = myParams->getCifti(1); AString direction = myParams->getString(2); int mydir = -1; if (direction == "ROW") mydir = 0; if (direction == "COLUMN") mydir = 1; if (mydir == -1) { throw OperationException(" must be 'ROW' or 'COLUMN'"); } CiftiFile* ciftiOut = myParams->getOutputCifti(3); OptionalParameter* nameFileOpt = myParams->getOptionalParameter(4); CiftiXMLOld myXML = ciftiIn->getCiftiXMLOld(); int rowSize = myXML.getNumberOfColumns(), colSize = myXML.getNumberOfRows(); if (mydir == 0) { myXML.resetRowsToScalars(rowSize); if (nameFileOpt->m_present) { AString listfileName = nameFileOpt->getString(1); FileInformation textFileInfo(listfileName); if (!textFileInfo.exists()) { throw OperationException("name list file doesn't exist"); } fstream nameListFile(listfileName.toLocal8Bit().constData(), fstream::in); if (!nameListFile.good()) { throw OperationException("error reading name list file"); } string mapName; for (int i = 0; i < rowSize; ++i) { getline(nameListFile, mapName); if (!nameListFile)//no, seriously, that is how you check if your input was good { CaretLogWarning("name file contained " + AString::number(i) + " names, expected " + AString::number(rowSize)); break; } myXML.setMapNameForRowIndex(i, mapName.c_str()); } } } else { myXML.resetColumnsToScalars(colSize); if (nameFileOpt->m_present) { AString listfileName = nameFileOpt->getString(1); FileInformation textFileInfo(listfileName); if (!textFileInfo.exists()) { throw OperationException("name list file doesn't exist"); } fstream nameListFile(listfileName.toLocal8Bit().constData(), fstream::in); if (!nameListFile.good()) { throw OperationException("error reading name list file"); } string mapName; for (int i = 0; i < colSize; ++i) { getline(nameListFile, mapName); if (!nameListFile)//no, seriously, that is how you check if your input was good { CaretLogWarning("warning, name file contained " + AString::number(i) + " names, expected " + AString::number(colSize)); break; } myXML.setMapNameForColumnIndex(i, mapName.c_str()); } } } ciftiOut->setCiftiXML(myXML); vector myrow(rowSize); for (int i = 0; i < colSize; ++i) { ciftiIn->getRow(myrow.data(), i); ciftiOut->setRow(myrow.data(), i); } } workbench-1.1.1/src/Operations/OperationCiftiConvertToScalar.h000066400000000000000000000026771255417355300245160ustar00rootroot00000000000000#ifndef __OPERATION_CIFTI_CONVERT_TO_SCALAR_H__ #define __OPERATION_CIFTI_CONVERT_TO_SCALAR_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationCiftiConvertToScalar : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationCiftiConvertToScalar; } #endif //__OPERATION_CIFTI_CONVERT_TO_SCALAR_H__ workbench-1.1.1/src/Operations/OperationCiftiCopyMapping.cxx000066400000000000000000000074571255417355300242470ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationCiftiCopyMapping.h" #include "OperationException.h" #include "CiftiFile.h" #include "MultiDimIterator.h" using namespace caret; using namespace std; AString OperationCiftiCopyMapping::getCommandSwitch() { return "-cifti-copy-mapping"; } AString OperationCiftiCopyMapping::getShortDescription() { return "REPLACE MAPPING ON A CIFTI FILE"; } OperationParameters* OperationCiftiCopyMapping::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiParameter(1, "data-cifti", "the cifti file to use the data from"); ret->addStringParameter(2, "replace-dir", "which direction on to replace the mapping"); ret->addCiftiParameter(3, "template-cifti", "a cifti file containing the desired mapping"); ret->addStringParameter(4, "template-dir", "which direction on to use the mapping from"); ret->addCiftiOutputParameter(5, "cifti-out", "the output cifti file"); ret->setHelpText( AString(" must have the same length along the replace direction as has along the template direction. ") + "Each direction argument must be either ROW or COLUMN." ); return ret; } void OperationCiftiCopyMapping::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); const CiftiFile* dataCifti = myParams->getCifti(1); AString dataDirStr = myParams->getString(2); int dataDir = -1; if (dataDirStr == "ROW") { dataDir = CiftiXML::ALONG_ROW; } else if (dataDirStr == "COLUMN") { dataDir = CiftiXML::ALONG_COLUMN; } else { throw OperationException("incorrect string for replace-dir, use ROW or COLUMN"); } const CiftiFile* templateCifti = myParams->getCifti(3); AString templateDirStr = myParams->getString(4); int templateDir = -1; if (templateDirStr == "ROW") { templateDir = CiftiXML::ALONG_ROW; } else if (templateDirStr == "COLUMN") { templateDir = CiftiXML::ALONG_COLUMN; } else { throw OperationException("incorrect string for template-dir, use ROW or COLUMN"); } CiftiFile* ciftiOut = myParams->getOutputCifti(5); CiftiXML outXML = dataCifti->getCiftiXML();//we need a copy of it so we can modify if (outXML.getDimensionLength(dataDir) != templateCifti->getCiftiXML().getDimensionLength(templateDir)) { throw OperationException("selected directions on files have different length"); } outXML.setMap(dataDir, *(templateCifti->getCiftiXML().getMap(templateDir))); ciftiOut->setCiftiXML(outXML); vector outDims = outXML.getDimensions(); vector scratchrow(outDims[0]); for (MultiDimIterator iter(vector(outDims.begin() + 1, outDims.end())); !iter.atEnd(); ++iter) {//drop the first dimension, row length dataCifti->getRow(scratchrow.data(), *iter); ciftiOut->setRow(scratchrow.data(), *iter); } } workbench-1.1.1/src/Operations/OperationCiftiCopyMapping.h000066400000000000000000000026441255417355300236650ustar00rootroot00000000000000#ifndef __OPERATION_CIFTI_COPY_MAPPING_H__ #define __OPERATION_CIFTI_COPY_MAPPING_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationCiftiCopyMapping : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationCiftiCopyMapping; } #endif //__OPERATION_CIFTI_COPY_MAPPING_H__ workbench-1.1.1/src/Operations/OperationCiftiCreateDenseFromTemplate.cxx000066400000000000000000001002421255417355300265050ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationCiftiCreateDenseFromTemplate.h" #include "OperationException.h" #include "AlgorithmCiftiSeparate.h" #include "AlgorithmCiftiReplaceStructure.h" #include "CaretAssert.h" #include "CiftiFile.h" #include "GiftiLabelTable.h" #include "LabelFile.h" #include "MetricFile.h" #include "VolumeFile.h" #include using namespace caret; using namespace std; AString OperationCiftiCreateDenseFromTemplate::getCommandSwitch() { return "-cifti-create-dense-from-template"; } AString OperationCiftiCreateDenseFromTemplate::getShortDescription() { return "CREATE CIFTI WITH MATCHING DENSE MAP"; } OperationParameters* OperationCiftiCreateDenseFromTemplate::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiParameter(1, "template-cifti", "file to match brainordinates of"); ret->addCiftiOutputParameter(2, "cifti-out", "the output cifti file"); OptionalParameter* seriesOpt = ret->createOptionalParameter(3, "-series", "make a dtseries file instead of a dscalar"); seriesOpt->addDoubleParameter(1, "step", "increment between series points"); seriesOpt->addDoubleParameter(2, "start", "start value of the series"); OptionalParameter* sUnitOpt = seriesOpt->createOptionalParameter(3, "-unit", "select unit for series (default SECOND)"); sUnitOpt->addStringParameter(1, "unit", "unit identifier"); OptionalParameter* volAllOpt = ret->createOptionalParameter(4, "-volume-all", "specify an input volume file for all voxel data"); volAllOpt->addVolumeParameter(1, "volume-in", "the input volume file"); volAllOpt->createOptionalParameter(2, "-from-cropped", "the input is cropped to the size of the voxel data in the template file"); ParameterComponent* ciftiOpt = ret->createRepeatableParameter(5, "-cifti", "use input data from a cifti file"); ciftiOpt->addCiftiParameter(1, "cifti-in", "cifti file containing input data"); ParameterComponent* metricOpt = ret->createRepeatableParameter(6, "-metric", "use input data from a metric file"); metricOpt->addStringParameter(1, "structure", "which structure to put the metric file into"); metricOpt->addMetricParameter(2, "metric-in", "input metric file"); ParameterComponent* labelOpt = ret->createRepeatableParameter(7, "-label", "use input data from surface label files"); labelOpt->addStringParameter(1, "structure", "which structure to put the label file into"); labelOpt->addLabelParameter(2, "label-in", "input label file"); ParameterComponent* volOpt = ret->createRepeatableParameter(8, "-volume", "use a volume file for a single volume structure's data"); volOpt->addStringParameter(1, "structure", "which structure to put the volume file into"); volOpt->addVolumeParameter(2, "volume-in", "the input volume file"); volOpt->createOptionalParameter(3, "-from-cropped", "the input is cropped to the size of the volume structure"); AString helpText = AString("This command helps you make a new dscalar, dtseries, or dlabel cifti file that matches the brainordinate space used in another cifti file. ") + "The template file must have the desired brainordinate space in the mapping along the column direction (for dtseries, dscalar, dlabel, and symmetric dconn this is always the case). " + "All input cifti files must have a brain models mapping along column and use the same volume space and/or surface vertex count as the template for structures that they contain. " + "If any input files contain label data, then input files with non-label data are not allowed, and the -series option may not be used.\n\n" + "Any structure that isn't covered by an input is filled with zeros or the unlabeled key.\n\n" + "The argument of -metric, -label or -volume must be one of the following:\n"; vector myStructureEnums; StructureEnum::getAllEnums(myStructureEnums); for (int i = 0; i < (int)myStructureEnums.size(); ++i) { helpText += "\n" + StructureEnum::toName(myStructureEnums[i]); } helpText += "\n\nThe argument to -unit must be one of the following:\n"; vector unitList = CiftiSeriesMap::getAllUnits(); for (int i = 0; i < (int)unitList.size(); ++i) { helpText += "\n" + CiftiSeriesMap::unitToString(unitList[i]); } ret->setHelpText(helpText); return ret; } namespace { enum DataSourceType { NONE,//fill with zeros CIFTI, LABEL, METRIC, VOLUME, VOLUME_ALL }; struct DataSourceInfo { DataSourceType type; int64_t index;//for repeatable options DataSourceInfo() { type = NONE; index = -1; }; }; } void OperationCiftiCreateDenseFromTemplate::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); const CiftiFile* templateCifti = myParams->getCifti(1); CiftiFile* ciftiOut = myParams->getOutputCifti(2); const CiftiXML& templateXML = templateCifti->getCiftiXML(); if (templateXML.getNumberOfDimensions() != 2) { throw OperationException("template cifti file must have exactly 2 dimensions"); } if (templateXML.getMappingType(CiftiXML::ALONG_COLUMN) != CiftiMappingType::BRAIN_MODELS) { throw OperationException("template cifti file does not have brain models along column"); } CiftiXML outXML = templateXML; int labelMode = -1;//-1 not set, 0 set to false, 1 set to true int64_t numMaps = -1; const CaretMappableDataFile* nameFile = NULL; const CiftiFile* ciftiNameFile = NULL;//cifti doesn't inherit from CaretMappableDataFile, it is too different const CiftiBrainModelsMap& templateMap = templateXML.getBrainModelsMap(CiftiXML::ALONG_COLUMN); vector surfStructures = templateMap.getSurfaceStructureList(), volStructures = templateMap.getVolumeStructureList(); vector surfInfo(surfStructures.size()), volInfo(volStructures.size()); OptionalParameter* volAllOpt = myParams->getOptionalParameter(4); if (volAllOpt->m_present) { if (!templateMap.hasVolumeData()) { throw OperationException("-volume-all specified, but template cifti file does use any voxels"); } VolumeFile* allVolume = volAllOpt->getVolume(1); bool fromCropped = volAllOpt->getOptionalParameter(2)->m_present; const VolumeSpace& volAllSpace = allVolume->getVolumeSpace(); VolumeSpace templateSpace; if (fromCropped) { int64_t tempDims[3], tempOffset[3]; vector > tempSform; AlgorithmCiftiSeparate::getCroppedVolSpaceAll(templateCifti, CiftiXML::ALONG_COLUMN, tempDims, tempSform, tempOffset); templateSpace.setSpace(tempDims, tempSform); } else { templateSpace = templateMap.getVolumeSpace(); } if (!templateSpace.matches(volAllSpace)) { throw OperationException("-volume-all specifies a volume file that doesn't match the volume space of the template cifti file"); } numMaps = allVolume->getNumberOfMaps(); nameFile = allVolume; if (allVolume->getType() == SubvolumeAttributes::LABEL) { labelMode = 1; } else { labelMode = 0; } for (int i = 0; i < (int)volInfo.size(); ++i) { volInfo[i].type = VOLUME_ALL; } } const vector& ciftiInstances = *(myParams->getRepeatableParameterInstances(5)); for (int instance = 0; instance < (int)ciftiInstances.size(); ++instance) { const CiftiFile* thisCifti = ciftiInstances[instance]->getCifti(1); const CiftiXML& thisXML = thisCifti->getCiftiXML(); if (thisXML.getNumberOfDimensions() != 2) { throw OperationException("input cifti files must have exactly 2 dimensions"); } if (thisXML.getMappingType(CiftiXML::ALONG_COLUMN) != CiftiMappingType::BRAIN_MODELS) { throw OperationException("input cifti files must have a brain models mapping along column"); } const CiftiBrainModelsMap& thisDenseMap = thisXML.getBrainModelsMap(CiftiXML::ALONG_COLUMN); bool isLabel = (thisXML.getMappingType(CiftiXML::ALONG_ROW) == CiftiMappingType::LABELS); if (labelMode == -1) { labelMode = isLabel ? 1 : 0; } else { if (isLabel) { if (labelMode == 0) { throw OperationException("cifti file '" + thisCifti->getFileName() + "' contains label data, but -volume-all contains non-label data"); } } else { if (labelMode == 1) { throw OperationException("cifti file '" + thisCifti->getFileName() + "' contains non-label data, but -volume-all contains label data"); } } } int64_t thisNumMaps = thisXML.getDimensionLength(CiftiXML::ALONG_ROW); if (numMaps == -1) { numMaps = thisNumMaps; } else { if (numMaps != thisNumMaps) { throw OperationException("cifti file '" + thisCifti->getFileName() + "' contains " + AString::number(thisNumMaps) + " maps, but -volume-all contains " + AString::number(numMaps) + " maps"); } } if (thisDenseMap.hasVolumeData() && templateMap.hasVolumeData() && !templateMap.getVolumeSpace().matches(thisDenseMap.getVolumeSpace())) { throw OperationException("cifti file '" + thisCifti->getFileName() + "' uses a different volume space than the template file"); } vector thisSurfStructs = thisDenseMap.getSurfaceStructureList(), thisVolStructs = thisDenseMap.getVolumeStructureList(); for (int whichStruct = 0; whichStruct < (int)thisSurfStructs.size(); ++whichStruct) { vector::const_iterator iter = std::find(surfStructures.begin(), surfStructures.end(), thisSurfStructs[whichStruct]); if (iter == surfStructures.end()) { cout << ("discarding surface structure " + StructureEnum::toName(thisSurfStructs[whichStruct]) + " from file '" + thisCifti->getFileName() + "'") << endl; } else { int outIndex = (int)(iter - surfStructures.begin()); int64_t templateNumNodes = templateMap.getSurfaceNumberOfNodes(thisSurfStructs[whichStruct]); int64_t thisNumNodes = thisDenseMap.getSurfaceNumberOfNodes(thisSurfStructs[whichStruct]); if (thisNumNodes != templateNumNodes) { throw OperationException("cifti file '" + thisCifti->getFileName() + "' contains surface data for " + StructureEnum::toName(thisSurfStructs[whichStruct]) + ", but uses a surface with " + AString::number(thisNumNodes) + " vertices, while template needs " + AString::number(templateNumNodes)); } if (surfInfo[outIndex].type != NONE) { throw OperationException("cifti file '" + thisCifti->getFileName() + "' contains data for structure " + StructureEnum::toName(thisSurfStructs[whichStruct]) + ", but data for this structure also exists in an another -cifti option's file"); } surfInfo[outIndex].type = CIFTI; surfInfo[outIndex].index = instance; } } if (templateMap.hasVolumeData()) { for (int whichStruct = 0; whichStruct < (int)thisVolStructs.size(); ++whichStruct) { vector::const_iterator iter = std::find(volStructures.begin(), volStructures.end(), thisSurfStructs[whichStruct]); if (iter == volStructures.end()) { cout << ("discarding volume structure " + StructureEnum::toName(thisSurfStructs[whichStruct]) + " from file '" + thisCifti->getFileName() + "'") << endl; } else { int outIndex = (int)(iter - volStructures.begin()); if (volInfo[outIndex].type != NONE) { throw OperationException("cifti file '" + thisCifti->getFileName() + "' contains data for structure " + StructureEnum::toName(thisSurfStructs[whichStruct]) + ", but data for this structure also exists in an another option's file"); } volInfo[outIndex].type = CIFTI; volInfo[outIndex].index = instance; } } } else { if (thisDenseMap.hasVolumeData()) { cout << ("discarding volume structures from file '" + thisCifti->getFileName() + "'") << endl; } } if (ciftiNameFile == NULL && (isLabel || thisXML.getMappingType(CiftiXML::ALONG_ROW) == CiftiMappingType::SCALARS)) { ciftiNameFile = thisCifti;//cifti trumps everything, if it has scalar or label } } const vector& metricInstances = *(myParams->getRepeatableParameterInstances(6)); for (int instance = 0; instance < (int)metricInstances.size(); ++instance) { if (labelMode == 1) throw OperationException("-metric option specified when other inputs are label-type files"); labelMode = 0; AString structString = metricInstances[instance]->getString(1); const MetricFile* thisMetric = metricInstances[instance]->getMetric(2); bool ok = false; StructureEnum::Enum thisStruct = StructureEnum::fromName(structString, &ok); if (!ok) throw OperationException("unrecognized structure string in -metric option: " + structString); vector::const_iterator iter = std::find(surfStructures.begin(), surfStructures.end(), thisStruct); if (iter == surfStructures.end()) { throw OperationException("-metric option specified for structure " + structString + ", but template cifti file does not contain this surface structure"); } int outIndex = (int)(iter - surfStructures.begin()); if (thisMetric->getNumberOfNodes() != templateMap.getSurfaceNumberOfNodes(thisStruct)) { throw OperationException("metric file '" + thisMetric->getFileName() + "' has " + AString::number(thisMetric->getNumberOfNodes()) + " vertices, but template cifti requires " + AString::number(templateMap.getSurfaceNumberOfNodes(thisStruct)) + " for structure " + structString); } if (surfInfo[outIndex].type != NONE) { throw OperationException("-metric specified with structure " + structString + ", but data for this structure also exists in an another option"); } int64_t thisNumMaps = thisMetric->getNumberOfMaps(); if (numMaps == -1) { numMaps = thisNumMaps; } else { if (numMaps != thisNumMaps) { throw OperationException("metric file '" + thisMetric->getFileName() + "' contains " + AString::number(thisNumMaps) + " maps, but other file(s) contain " + AString::number(numMaps) + " maps"); } } checkStructureMatch(thisMetric, thisStruct, "metric file '" + thisMetric->getFileName() + "'", "the -metric option specified"); surfInfo[outIndex].type = METRIC; surfInfo[outIndex].index = instance; if (instance == 0) nameFile = thisMetric;//-metric trumps -volume-all for names, but use the first -metric } const vector& labelInstances = *(myParams->getRepeatableParameterInstances(7)); for (int instance = 0; instance < (int)labelInstances.size(); ++instance) { if (labelMode == 0) throw OperationException("-label option specified when other inputs are real-valued files"); labelMode = 1; AString structString = labelInstances[instance]->getString(1); const LabelFile* thisLabel = labelInstances[instance]->getLabel(2); bool ok = false; StructureEnum::Enum thisStruct = StructureEnum::fromName(structString, &ok); if (!ok) throw OperationException("unrecognized structure string in -label option: " + structString); vector::const_iterator iter = std::find(surfStructures.begin(), surfStructures.end(), thisStruct); if (iter == surfStructures.end()) { throw OperationException("-label option specified for structure " + structString + ", but template cifti file does not contain this surface structure"); } int outIndex = (int)(iter - surfStructures.begin()); if (thisLabel->getNumberOfNodes() != templateMap.getSurfaceNumberOfNodes(thisStruct)) { throw OperationException("label file '" + thisLabel->getFileName() + "' has " + AString::number(thisLabel->getNumberOfNodes()) + " vertices, but template cifti requires " + AString::number(templateMap.getSurfaceNumberOfNodes(thisStruct)) + " for structure " + structString); } if (surfInfo[outIndex].type != NONE) { throw OperationException("-label specified with structure " + structString + ", but data for this structure also exists in an another option"); } int64_t thisNumMaps = thisLabel->getNumberOfMaps(); if (numMaps == -1) { numMaps = thisNumMaps; } else { if (numMaps != thisNumMaps) { throw OperationException("label file '" + thisLabel->getFileName() + "' contains " + AString::number(thisNumMaps) + " maps, but other file(s) contain " + AString::number(numMaps) + " maps"); } } checkStructureMatch(thisLabel, thisStruct, "label file '" + thisLabel->getFileName() + "'", "the -label option specified"); surfInfo[outIndex].type = LABEL; surfInfo[outIndex].index = instance; if (instance == 0) nameFile = thisLabel;//-label trumps -volume-all for names, but use the first -label } const vector& volumeInstances = *(myParams->getRepeatableParameterInstances(8)); for (int instance = 0; instance < (int)volumeInstances.size(); ++instance) { AString structString = volumeInstances[instance]->getString(1); const VolumeFile* thisVol = volumeInstances[instance]->getVolume(2); bool fromCropped = volumeInstances[instance]->getOptionalParameter(3)->m_present; bool ok = false; StructureEnum::Enum thisStruct = StructureEnum::fromName(structString, &ok); if (!ok) throw OperationException("unrecognized structure string in -volume option: " + structString); vector::const_iterator iter = std::find(volStructures.begin(), volStructures.end(), thisStruct); if (iter == volStructures.end()) { throw OperationException("-volume option specified for structure " + structString + ", but template cifti file does not contain this volume structure"); } int outIndex = (int)(iter - surfStructures.begin()); bool isLabel = (thisVol->getType() == SubvolumeAttributes::LABEL); if (labelMode == -1) { labelMode = isLabel ? 1 : 0; } else { if (isLabel) { if (labelMode == 0) { throw OperationException("volume file '" + thisVol->getFileName() + "' contains label data, but other file(s) contain non-label data"); } } else { if (labelMode == 1) { throw OperationException("volume file '" + thisVol->getFileName() + "' contains non-label data, but other file(s) contain label data"); } } } VolumeSpace templateSpace; if (fromCropped) { int64_t tempDims[3], tempOffset[3]; vector > tempSform; AlgorithmCiftiSeparate::getCroppedVolSpace(templateCifti, CiftiXML::ALONG_COLUMN, thisStruct, tempDims, tempSform, tempOffset); templateSpace.setSpace(tempDims, tempSform); } else { templateSpace = templateMap.getVolumeSpace(); } if (!thisVol->matchesVolumeSpace(templateSpace)) { throw OperationException("volume file '" + thisVol->getFileName() + "' does not match volume space of template cifti file"); } if (volInfo[outIndex].type != NONE) { throw OperationException("-volume specified with structure " + structString + ", but data for this structure also exists in an another option"); } int64_t thisNumMaps = thisVol->getNumberOfMaps(); if (numMaps == -1) { numMaps = thisNumMaps; } else { if (numMaps != thisNumMaps) { throw OperationException("volume file '" + thisVol->getFileName() + "' contains " + AString::number(thisNumMaps) + " maps, but other file(s) contain " + AString::number(numMaps) + " maps"); } } volInfo[outIndex].type = VOLUME; volInfo[outIndex].index = instance; if (nameFile == NULL) nameFile = thisVol;//-volume is the lowest priority for names } if (numMaps == -1) { throw OperationException("you must specify at least one input option"); } OptionalParameter* seriesOpt = myParams->getOptionalParameter(3); if (seriesOpt->m_present) { if (labelMode == 1) { throw OperationException("-series option cannot be used when input is label data"); } CiftiSeriesMap outMap; outMap.setLength(numMaps); outMap.setStep(seriesOpt->getDouble(1)); outMap.setStart(seriesOpt->getDouble(2)); OptionalParameter* unitOpt = seriesOpt->getOptionalParameter(3); if (unitOpt->m_present) { AString unitString = unitOpt->getString(1); bool ok = false; CiftiSeriesMap::Unit myUnit = CiftiSeriesMap::stringToUnit(unitString, ok); if (!ok) throw OperationException("unrecognized unit string '" + unitString + "'"); outMap.setUnit(myUnit); } outXML.setMap(CiftiXML::ALONG_ROW, outMap); } else { if (labelMode == 1) { CiftiLabelsMap outMap; outMap.setLength(numMaps); if (ciftiNameFile != NULL) { const CiftiMappingType& nameMap = *(ciftiNameFile->getCiftiXML().getMap(CiftiXML::ALONG_ROW)); for (int64_t i = 0; i < numMaps; ++i) { outMap.setMapName(i, nameMap.getIndexName(i)); } } else { CaretAssert(nameFile != NULL); for (int64_t i = 0; i < numMaps; ++i) { outMap.setMapName(i, nameFile->getMapName(i)); } } outXML.setMap(CiftiXML::ALONG_ROW, outMap); } else { CiftiScalarsMap outMap; outMap.setLength(numMaps); if (ciftiNameFile != NULL) { const CiftiMappingType& nameMap = *(ciftiNameFile->getCiftiXML().getMap(CiftiXML::ALONG_ROW)); for (int64_t i = 0; i < numMaps; ++i) { outMap.setMapName(i, nameMap.getIndexName(i)); } } else { CaretAssert(nameFile != NULL); for (int64_t i = 0; i < numMaps; ++i) { outMap.setMapName(i, nameFile->getMapName(i)); } } outXML.setMap(CiftiXML::ALONG_ROW, outMap); } } ciftiOut->setCiftiXML(outXML); for (int whichStruct = 0; whichStruct < (int)surfStructures.size(); ++whichStruct) { switch (surfInfo[whichStruct].type) { case CIFTI: { const CiftiFile* toUse = ciftiInstances[surfInfo[whichStruct].index]->getCifti(1); if (labelMode == 1) { LabelFile tempLabel; AlgorithmCiftiSeparate(NULL, toUse, CiftiXML::ALONG_COLUMN, surfStructures[whichStruct], &tempLabel); AlgorithmCiftiReplaceStructure(NULL, ciftiOut, CiftiXML::ALONG_COLUMN, surfStructures[whichStruct], &tempLabel); } else { MetricFile tempMetric; AlgorithmCiftiSeparate(NULL, toUse, CiftiXML::ALONG_COLUMN, surfStructures[whichStruct], &tempMetric); AlgorithmCiftiReplaceStructure(NULL, ciftiOut, CiftiXML::ALONG_COLUMN, surfStructures[whichStruct], &tempMetric); } break; } case METRIC: { const MetricFile* toUse = metricInstances[surfInfo[whichStruct].index]->getMetric(2); AlgorithmCiftiReplaceStructure(NULL, ciftiOut, CiftiXML::ALONG_COLUMN, surfStructures[whichStruct], toUse); break; } case LABEL: { const LabelFile* toUse = labelInstances[surfInfo[whichStruct].index]->getLabel(2); AlgorithmCiftiReplaceStructure(NULL, ciftiOut, CiftiXML::ALONG_COLUMN, surfStructures[whichStruct], toUse); break; } case NONE: { int numNodes = templateMap.getSurfaceNumberOfNodes(surfStructures[whichStruct]); if (labelMode == 1) { LabelFile tempLabel; tempLabel.setNumberOfNodesAndColumns(numNodes, numMaps); int32_t unlabeledKey = tempLabel.getLabelTable()->getUnassignedLabelKey(); vector scratchCol(numNodes, unlabeledKey); for (int64_t i = 0; i < numMaps; ++i) { tempLabel.setLabelKeysForColumn(i, scratchCol.data()); } AlgorithmCiftiReplaceStructure(NULL, ciftiOut, CiftiXML::ALONG_COLUMN, surfStructures[whichStruct], &tempLabel); } else { MetricFile tempMetric; tempMetric.setNumberOfNodesAndColumns(numNodes, numMaps); vector scratchCol(numNodes, 0.0f); for (int64_t i = 0; i < numMaps; ++i) { tempMetric.setValuesForColumn(i, scratchCol.data()); } AlgorithmCiftiReplaceStructure(NULL, ciftiOut, CiftiXML::ALONG_COLUMN, surfStructures[whichStruct], &tempMetric); } break; } default: CaretAssert(false); throw OperationException("internal error, invalid source type for surface data, tell the developers what you did"); } } if (volStructures.size() > 0 && volInfo[0].type == VOLUME_ALL)//NOTE: if one structure is VOLUME_ALL, all are, and the cropped space is different than per-structure, so DO NOT enter the structure loop { const VolumeFile* toUse = volAllOpt->getVolume(1); bool fromCropped = volAllOpt->getOptionalParameter(2)->m_present; AlgorithmCiftiReplaceStructure(NULL, ciftiOut, CiftiXML::ALONG_COLUMN, toUse, fromCropped); } else { for (int whichStruct = 0; whichStruct < (int)volStructures.size(); ++whichStruct) { switch (volInfo[whichStruct].type) { case CIFTI: { const CiftiFile* toUse = ciftiInstances[volInfo[whichStruct].index]->getCifti(1); VolumeFile tempVol; int64_t dims1[3], dims2[3], off1[3], off2[3];//check if the cropped space matches, if so we can save memory easily by using the crop argument (and this is also the common case) vector > sform1, sform2; AlgorithmCiftiSeparate::getCroppedVolSpace(toUse, CiftiXML::ALONG_COLUMN, volStructures[whichStruct], dims1, sform1, off1); AlgorithmCiftiSeparate::getCroppedVolSpace(templateCifti, CiftiXML::ALONG_COLUMN, volStructures[whichStruct], dims2, sform2, off2); VolumeSpace space1(dims1, sform1), space2(dims2, sform2); if (space1.matches(space2)) { AlgorithmCiftiSeparate(NULL, toUse, CiftiXML::ALONG_COLUMN, volStructures[whichStruct], &tempVol, off1, NULL, true); AlgorithmCiftiReplaceStructure(NULL, ciftiOut, CiftiXML::ALONG_COLUMN, volStructures[whichStruct], &tempVol, true); } else { AlgorithmCiftiSeparate(NULL, toUse, CiftiXML::ALONG_COLUMN, volStructures[whichStruct], &tempVol, off1, NULL, false); AlgorithmCiftiReplaceStructure(NULL, ciftiOut, CiftiXML::ALONG_COLUMN, volStructures[whichStruct], &tempVol, false); } break; } case VOLUME: { const VolumeFile* toUse = volumeInstances[volInfo[whichStruct].index]->getVolume(2); bool fromCropped = volumeInstances[volInfo[whichStruct].index]->getOptionalParameter(3)->m_present; AlgorithmCiftiReplaceStructure(NULL, ciftiOut, CiftiXML::ALONG_COLUMN, volStructures[whichStruct], toUse, fromCropped); break; } case NONE: { VolumeFile tempVol; int64_t offset[3]; vector dims(3); vector > sform; AlgorithmCiftiSeparate::getCroppedVolSpace(templateCifti, CiftiXML::ALONG_COLUMN, volStructures[whichStruct], dims.data(), sform, offset); dims.push_back(numMaps); if (labelMode == 1) { int64_t frameSize = dims[0] * dims[1] * dims[2]; tempVol.reinitialize(dims, sform, 1, SubvolumeAttributes::LABEL); for (int64_t i = 0; i < numMaps; ++i) { int32_t unlabeledKey = tempVol.getMapLabelTable(i)->getUnassignedLabelKey(); vector scratchFrame(frameSize, unlabeledKey); tempVol.setFrame(scratchFrame.data(), i); } } else { tempVol.reinitialize(dims, sform); tempVol.setValueAllVoxels(0.0f); } AlgorithmCiftiReplaceStructure(NULL, ciftiOut, CiftiXML::ALONG_COLUMN, volStructures[whichStruct], &tempVol, true); break; } default: CaretAssert(false); throw OperationException("internal error, invalid source type for volume structure data, tell the developers what you did"); } } } } workbench-1.1.1/src/Operations/OperationCiftiCreateDenseFromTemplate.h000066400000000000000000000027621255417355300261420ustar00rootroot00000000000000#ifndef __OPERATION_CIFTI_CREATE_DENSE_FROM_TEMPLATE_H__ #define __OPERATION_CIFTI_CREATE_DENSE_FROM_TEMPLATE_H__ /*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationCiftiCreateDenseFromTemplate : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationCiftiCreateDenseFromTemplate; } #endif //__OPERATION_CIFTI_CREATE_DENSE_FROM_TEMPLATE_H__ workbench-1.1.1/src/Operations/OperationCiftiCreateScalarSeries.cxx000066400000000000000000000140311255417355300255070ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationCiftiCreateScalarSeries.h" #include "OperationException.h" #include "CaretLogger.h" #include "CiftiFile.h" #include "FloatMatrix.h" #include #include #include #include using namespace caret; using namespace std; AString OperationCiftiCreateScalarSeries::getCommandSwitch() { return "-cifti-create-scalar-series"; } AString OperationCiftiCreateScalarSeries::getShortDescription() { return "IMPORT SERIES DATA INTO CIFTI"; } OperationParameters* OperationCiftiCreateScalarSeries::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addStringParameter(1, "input", "input file"); ret->addCiftiOutputParameter(2, "cifti-out", "output cifti file"); ret->createOptionalParameter(3, "-transpose", "use if the rows of the text file are along the scalar dimension"); OptionalParameter* nameFileOpt = ret->createOptionalParameter(4, "-name-file", "use a text file to set names on scalar dimension"); nameFileOpt->addStringParameter(1, "file", "text file containing names, one per line"); OptionalParameter* seriesOpt = ret->createOptionalParameter(5, "-series", "set the units and values of the series"); seriesOpt->addStringParameter(1, "unit", "the unit to use"); seriesOpt->addDoubleParameter(2, "start", "the value at the first series point"); seriesOpt->addDoubleParameter(3, "step", "the interval between series points"); AString myHelp = AString("Convert a text file containing series of equal length into a cifti file. ") + "The text file should have lines made up of numbers separated by whitespace, with no extra newlines between lines.\n\n" + "The argument must be one of the following:\n"; vector units = CiftiSeriesMap::getAllUnits(); for (int i = 0; i < (int)units.size(); ++i) { myHelp += "\n" + CiftiSeriesMap::unitToString(units[i]); } ret->setHelpText(myHelp); return ret; } void OperationCiftiCreateScalarSeries::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); AString inFileName = myParams->getString(1); CiftiFile* outFile = myParams->getOutputCifti(2); bool transpose = myParams->getOptionalParameter(3)->m_present; ifstream nameFile; OptionalParameter* nameFileOpt = myParams->getOptionalParameter(4); if (nameFileOpt->m_present) { nameFile.open(nameFileOpt->getString(1).toLocal8Bit().constData()); if (!nameFile) throw OperationException("failed to open name file"); } ifstream inputFile(inFileName.toLocal8Bit().constData()); vector > inFileData; if (!inputFile.good()) throw OperationException("failed to open input file '" + inFileName + "'"); string inputLine; while (inputFile) { getline(inputFile, inputLine); QStringList tokens = QString(inputLine.c_str()).split(QRegExp("\\s+"), QString::SkipEmptyParts); if (tokens.empty()) break;//in case there are extra newlines on the end if (!inFileData.empty() && (int)inFileData.back().size() != tokens.size()) throw OperationException("input file is not a rectangular matrix, starting at line " + AString::number(inFileData.size() + 2));//1 for 0-indexing, 1 for line not added to matrix yet inFileData.push_back(vector()); for (int i = 0; i < tokens.size(); ++i) { bool ok = false; inFileData.back().push_back(tokens[i].toFloat(&ok)); if (!ok) throw OperationException("input file contains non-number '" + tokens[i] + "'"); } } if (inFileData.empty() || inFileData[0].empty()) throw OperationException("input file contains no data"); if (transpose) { inFileData = FloatMatrix(inFileData).transpose().getMatrix(); } CiftiScalarsMap colMap; colMap.setLength(inFileData.size()); if (nameFileOpt->m_present) { for (int i = 0; i < (int)inFileData.size(); ++i) { getline(nameFile, inputLine); if (!nameFile) { CaretLogWarning("name file contained " + AString::number(i) + " names, expected " + AString::number(inFileData.size())); break; } colMap.setMapName(i, inputLine.c_str()); } } CiftiSeriesMap rowMap; rowMap.setLength(inFileData[0].size()); OptionalParameter* seriesOpt = myParams->getOptionalParameter(5); if (seriesOpt->m_present) { AString unitName = seriesOpt->getString(1); bool ok = false; CiftiSeriesMap::Unit myUnit = CiftiSeriesMap::stringToUnit(unitName, ok); if (!ok) { throw OperationException("unrecognized unit name: '" + unitName + "'"); } rowMap.setUnit(myUnit); rowMap.setStart(seriesOpt->getDouble(2)); rowMap.setStep(seriesOpt->getDouble(3)); } CiftiXML outXML; outXML.setNumberOfDimensions(2); outXML.setMap(CiftiXML::ALONG_ROW, rowMap); outXML.setMap(CiftiXML::ALONG_COLUMN, colMap); outFile->setCiftiXML(outXML); for (int i = 0; i < (int)inFileData.size(); ++i) { outFile->setRow(inFileData[i].data(), i); } } workbench-1.1.1/src/Operations/OperationCiftiCreateScalarSeries.h000066400000000000000000000027211255417355300251370ustar00rootroot00000000000000#ifndef __OPERATION_CIFTI_CREATE_SCALAR_SERIES_H__ #define __OPERATION_CIFTI_CREATE_SCALAR_SERIES_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationCiftiCreateScalarSeries : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationCiftiCreateScalarSeries; } #endif //__OPERATION_CIFTI_CREATE_SCALAR_SERIES_H__ workbench-1.1.1/src/Operations/OperationCiftiEstimateFWHM.cxx000066400000000000000000000200431255417355300242400ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationCiftiEstimateFWHM.h" #include "OperationException.h" #include "AlgorithmCiftiSeparate.h" #include "AlgorithmMetricEstimateFWHM.h" #include "AlgorithmVolumeEstimateFWHM.h" #include "CaretPointer.h" #include "CiftiFile.h" #include "MetricFile.h" #include "SurfaceFile.h" #include "VolumeFile.h" using namespace caret; using namespace std; AString OperationCiftiEstimateFWHM::getCommandSwitch() { return "-cifti-estimate-fwhm"; } AString OperationCiftiEstimateFWHM::getShortDescription() { return "ESTIMATE FWHM SMOOTHNESS OF A CIFTI FILE"; } OperationParameters* OperationCiftiEstimateFWHM::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiParameter(1, "cifti", "the input cifti file"); ret->createOptionalParameter(2, "-merged-volume", "treat volume components as if they were a single component"); OptionalParameter* columnOpt = ret->createOptionalParameter(3, "-column", "only output estimates for one column"); columnOpt->addIntegerParameter(1, "column", "the column number"); ParameterComponent* surfOpt = ret->createRepeatableParameter(4, "-surface", "specify an input surface"); surfOpt->addStringParameter(1, "structure", "what structure to use this surface for"); surfOpt->addSurfaceParameter(2, "surface", "the surface file"); AString myText = AString("Estimate the smoothness of the components of the cifti file, printing the estimates to standard output. ") + "If -merged-volume is used, all voxels are used as a single component, rather than separated by structure.\n\n" + " must be one of the following:\n"; vector myStructureEnums; StructureEnum::getAllEnums(myStructureEnums); for (int i = 0; i < (int)myStructureEnums.size(); ++i) { myText += "\n" + StructureEnum::toName(myStructureEnums[i]); } ret->setHelpText(myText); return ret; } struct VolParams { AString name; CaretPointer data, roi;//prevent copy in vector expansion }; struct SurfParams { AString name; SurfaceFile* surf; CaretPointer data, roi; }; void OperationCiftiEstimateFWHM::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); CiftiFile* myCifti = myParams->getCifti(1); bool mergedVol = myParams->getOptionalParameter(2)->m_present; int column = -1; OptionalParameter* columnOpt = myParams->getOptionalParameter(3); if (columnOpt->m_present) { column = columnOpt->getInteger(1) - 1;//compensate for 1-based UI indices if (column < 0 || column >= myCifti->getNumberOfColumns()) throw OperationException("invalid column index"); } const vector& surfInstances = *(myParams->getRepeatableParameterInstances(4)); int numInstances = (int)surfInstances.size(); for (int i = 0; i < numInstances; ++i) { bool ok = false; StructureEnum::fromName(surfInstances[i]->getString(1), &ok); if (!ok) throw OperationException("unrecognized structure name: " + surfInstances[i]->getString(1)); } vector surfUsed(surfInstances.size(), false); vector volProcess; vector surfProcess; const CiftiXMLOld& myXML = myCifti->getCiftiXMLOld(); if (myXML.getMappingType(CiftiXMLOld::ALONG_COLUMN) != CIFTI_INDEX_TYPE_BRAIN_MODELS) { throw OperationException("mapping type along column must be brain models"); } vector surfStructs, volStructs; myXML.getStructureLists(CiftiXMLOld::ALONG_COLUMN, surfStructs, volStructs); int numSurf = (int)surfStructs.size(); surfProcess.resize(numSurf); for (int i = 0; i < numSurf; ++i) { surfProcess[i].surf = NULL; for (int j = 0; j < numInstances; ++j) { StructureEnum::Enum myStruct = StructureEnum::fromName(surfInstances[j]->getString(1), NULL);//we already checked that this is a structure name if (myStruct == surfStructs[i]) { surfProcess[i].surf = surfInstances[j]->getSurface(2); break; } } if (surfProcess[i].surf == NULL) throw OperationException("missing surface for structure '" + StructureEnum::toName(surfStructs[i]) + "'"); surfProcess[i].name = StructureEnum::toName(surfStructs[i]); surfProcess[i].data.grabNew(new MetricFile()); surfProcess[i].roi.grabNew(new MetricFile()); AlgorithmCiftiSeparate(NULL, myCifti, CiftiXMLOld::ALONG_COLUMN, surfStructs[i], surfProcess[i].data, surfProcess[i].roi); if (surfProcess[i].surf->getNumberOfNodes() != surfProcess[i].data->getNumberOfNodes()) { throw OperationException("input surface for structure '" + StructureEnum::toName(surfStructs[i]) + "' has different number of nodes than the cifti file"); } } if (mergedVol) { volProcess.resize(1); volProcess[0].name = "Voxels"; volProcess[0].data.grabNew(new VolumeFile()); volProcess[0].roi.grabNew(new VolumeFile()); int64_t offset[3]; AlgorithmCiftiSeparate(NULL, myCifti, CiftiXMLOld::ALONG_COLUMN, volProcess[0].data, offset, volProcess[0].roi, true); } else { int numVol = (int)volStructs.size(); volProcess.resize(numVol); for (int i = 0; i < numVol; ++i) { volProcess[i].name = StructureEnum::toName(volStructs[i]); volProcess[i].data.grabNew(new VolumeFile()); volProcess[i].roi.grabNew(new VolumeFile()); int64_t offset[3]; AlgorithmCiftiSeparate(NULL, myCifti, CiftiXMLOld::ALONG_COLUMN, volStructs[i], volProcess[i].data, offset, volProcess[i].roi, true); } } if (column == -1) { int rowLength = (int)myCifti->getNumberOfColumns(); for (int i = 0; i < rowLength; ++i) { if (rowLength > 1) cout << "Column " << i + 1 << ":" << endl; for (int j = 0; j < (int)surfProcess.size(); ++j) { float fwhm = AlgorithmMetricEstimateFWHM::estimateFWHM(surfProcess[j].surf, surfProcess[j].data, surfProcess[j].roi, i); cout << surfProcess[j].name << " FWHM: " << fwhm << endl; } for (int j = 0; j < (int)volProcess.size(); ++j) { Vector3D fwhm = AlgorithmVolumeEstimateFWHM::estimateFWHM(volProcess[j].data, volProcess[j].roi, i); cout << volProcess[j].name << " FWHM: " << fwhm[0] << ", " << fwhm[1] << ", " << fwhm[2] << endl; } } } else { cout << "Column " << column + 1 << ":" << endl; for (int j = 0; j < (int)surfProcess.size(); ++j) { float fwhm = AlgorithmMetricEstimateFWHM::estimateFWHM(surfProcess[j].surf, surfProcess[j].data, surfProcess[j].roi, column); cout << surfProcess[j].name << " FWHM: " << fwhm << endl; } for (int j = 0; j < (int)volProcess.size(); ++j) { Vector3D fwhm = AlgorithmVolumeEstimateFWHM::estimateFWHM(volProcess[j].data, volProcess[j].roi, column); cout << volProcess[j].name << " FWHM: " << fwhm[0] << ", " << fwhm[1] << ", " << fwhm[2] << endl; } } } workbench-1.1.1/src/Operations/OperationCiftiEstimateFWHM.h000066400000000000000000000026521255417355300236730ustar00rootroot00000000000000#ifndef __OPERATION_CIFTI_ESTIMATE_FWHM_H__ #define __OPERATION_CIFTI_ESTIMATE_FWHM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationCiftiEstimateFWHM : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationCiftiEstimateFWHM; } #endif //__OPERATION_CIFTI_ESTIMATE_FWHM_H__ workbench-1.1.1/src/Operations/OperationCiftiExportDenseMapping.cxx000066400000000000000000000203151255417355300255610ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationCiftiExportDenseMapping.h" #include "OperationException.h" #include "CiftiFile.h" #include "CiftiXML.h" #include using namespace caret; using namespace std; AString OperationCiftiExportDenseMapping::getCommandSwitch() { return "-cifti-export-dense-mapping"; } AString OperationCiftiExportDenseMapping::getShortDescription() { return "WRITE INDEX TO ELEMENT MAPPING AS TEXT"; } OperationParameters* OperationCiftiExportDenseMapping::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiParameter(1, "cifti", "the cifti file"); ret->addStringParameter(2, "direction", "which direction to export the mapping from, ROW or COLUMN");//TODO: 3rd+ dimension ParameterComponent* surfOpt = ret->createRepeatableParameter(3, "-surface", "export the the mapping of one surface structure"); surfOpt->addStringParameter(1, "structure", "the structure to output"); surfOpt->addStringParameter(2, "text-out", "output - the output text file");//fake the output formatting surfOpt->createOptionalParameter(3, "-no-cifti-index", "don't write the cifti index in the output file"); ParameterComponent* volOpt = ret->createRepeatableParameter(4, "-volume", "export the the mapping of one volume structure"); volOpt->addStringParameter(1, "structure", "the structure to output"); volOpt->addStringParameter(2, "text-out", "output - the output text file");//fake the output formatting volOpt->createOptionalParameter(3, "-no-cifti-index", "don't write the cifti index in the output file"); OptionalParameter* volAllOpt = ret->createOptionalParameter(5, "-volume-all", "export the the mapping of all voxels");//repeatable, for different options? volAllOpt->addStringParameter(1, "text-out", "output - the output text file");//fake the output formatting volAllOpt->createOptionalParameter(2, "-no-cifti-index", "don't write the cifti index in the output file"); volAllOpt->createOptionalParameter(3, "-structure", "write the structure each voxel belongs to in the output file"); //-surface-all? -all? ret->setHelpText( AString("This command produces text files that describe the mapping from cifti indices to surface vertices or voxels. ") + "All indices are zero-based. " + "The default format for -surface is lines of the form:\n\n" + " \n\n" + "The default format for -volume and -volume-all is lines of the form:\n\n" + " " ); return ret; } void OperationCiftiExportDenseMapping::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); CiftiFile* myCifti = myParams->getCifti(1); AString dirString = myParams->getString(2); int myDir; if (dirString == "ROW") { myDir = CiftiXML::ALONG_ROW; } else if (dirString == "COLUMN") { myDir = CiftiXML::ALONG_COLUMN; } else { throw OperationException("incorrect string for direction, use ROW or COLUMN"); } const vector& surfOpts = *(myParams->getRepeatableParameterInstances(3)); const vector& volOpts = *(myParams->getRepeatableParameterInstances(4)); OptionalParameter* volAllOpt = myParams->getOptionalParameter(5); const CiftiXML& myXML = myCifti->getCiftiXML(); if (myXML.getMappingType(myDir) != CiftiMappingType::BRAIN_MODELS) throw OperationException("specified direction in cifti file does not have BRAIN_MODELS mapping"); const CiftiBrainModelsMap& myDenseMap = myXML.getBrainModelsMap(myDir); for (int i = 0; i < (int)surfOpts.size(); ++i) { AString structName = surfOpts[i]->getString(1); bool ok = false; StructureEnum::Enum myStruct = StructureEnum::fromName(structName, &ok); if (!ok) throw OperationException("invalid structure name: '" + structName + "'"); if (!myDenseMap.hasSurfaceData(myStruct)) throw OperationException("no surface mapping found in specified direction for structure '" + structName + "'"); AString outName = surfOpts[i]->getString(2); bool writeCiftiIndex = !(surfOpts[i]->getOptionalParameter(3)->m_present); ofstream outFile(outName.toLocal8Bit().constData()); if (!outFile) throw OperationException("failed to open output text file"); vector myMap = myDenseMap.getSurfaceMap(myStruct); for (int j = 0; j < (int)myMap.size(); ++j) { if (writeCiftiIndex) outFile << myMap[j].m_ciftiIndex << " "; outFile << myMap[j].m_surfaceNode << "\n";//avoid endl so it doesn't constantly flush } outFile.flush(); if (!outFile) throw OperationException("failed to write to output text file"); } for (int i = 0; i < (int)volOpts.size(); ++i) { AString structName = volOpts[i]->getString(1); bool ok = false; StructureEnum::Enum myStruct = StructureEnum::fromName(structName, &ok); if (!ok) throw OperationException("invalid structure name: '" + structName + "'"); if (!myDenseMap.hasVolumeData(myStruct)) throw OperationException("no volume mapping found in specified direction for structure '" + structName + "'"); AString outName = volOpts[i]->getString(2); bool writeCiftiIndex = !(volOpts[i]->getOptionalParameter(3)->m_present); ofstream outFile(outName.toLocal8Bit().constData()); if (!outFile) throw OperationException("failed to open output text file"); vector myMap = myDenseMap.getVolumeStructureMap(myStruct); for (int j = 0; j < (int)myMap.size(); ++j) { if (writeCiftiIndex) outFile << myMap[j].m_ciftiIndex << " "; outFile << myMap[j].m_ijk[0] << " " << myMap[j].m_ijk[1] << " " << myMap[j].m_ijk[2] << "\n";//avoid endl so it doesn't constantly flush } outFile.flush(); if (!outFile) throw OperationException("failed to write to output text file"); } if (volAllOpt->m_present) { if (!myDenseMap.hasVolumeData()) throw OperationException("no volume data found in specified direction"); AString outName = volAllOpt->getString(1); bool writeCiftiIndex = !(volAllOpt->getOptionalParameter(2)->m_present); bool writeStructure = volAllOpt->getOptionalParameter(3)->m_present; ofstream outFile(outName.toLocal8Bit().constData()); if (!outFile) throw OperationException("failed to open output text file"); vector volStructs = myDenseMap.getVolumeStructureList();//NOTE: CiftiBrainModelsMap guarantees this is in cifti index order for (int j = 0; j < (int)volStructs.size(); ++j) { vector myMap = myDenseMap.getVolumeStructureMap(volStructs[j]); AString structName = StructureEnum::toName(volStructs[j]); for (int k = 0; k < (int)myMap.size(); ++k) { if (writeCiftiIndex) outFile << myMap[k].m_ciftiIndex << " "; if (writeStructure) outFile << structName << " "; outFile << myMap[k].m_ijk[0] << " " << myMap[k].m_ijk[1] << " " << myMap[k].m_ijk[2] << "\n";//avoid endl so it doesn't constantly flush } } outFile.flush(); if (!outFile) throw OperationException("failed to write to output text file"); } } workbench-1.1.1/src/Operations/OperationCiftiExportDenseMapping.h000066400000000000000000000027211255417355300252070ustar00rootroot00000000000000#ifndef __OPERATION_CIFTI_EXPORT_DENSE_MAPPING_H__ #define __OPERATION_CIFTI_EXPORT_DENSE_MAPPING_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationCiftiExportDenseMapping : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationCiftiExportDenseMapping; } #endif //__OPERATION_CIFTI_EXPORT_DENSE_MAPPING_H__ workbench-1.1.1/src/Operations/OperationCiftiLabelExportTable.cxx000066400000000000000000000074131255417355300252020ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationCiftiLabelExportTable.h" #include "OperationException.h" #include "CiftiFile.h" #include "GiftiLabel.h" //we should rename these to not imply that they are gifti-specific #include "GiftiLabelTable.h" #include #include using namespace caret; using namespace std; AString OperationCiftiLabelExportTable::getCommandSwitch() { return "-cifti-label-export-table"; } AString OperationCiftiLabelExportTable::getShortDescription() { return "EXPORT LABEL TABLE FROM CIFTI AS TEXT"; } OperationParameters* OperationCiftiLabelExportTable::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiParameter(1, "label-in", "the input cifti label file"); ret->addStringParameter(2, "map", "the number or name of the label map to use"); ret->addStringParameter(3, "table-out", "output - the output text file");//fake output formatting ret->setHelpText( AString("Takes the label table from the cifti label map, and writes it to a text format matching what is expected by -cifti-label-import.") ); return ret; } int OperationCiftiLabelExportTable::floatTo255(const float& in) { return (int)floor(in * 255.0f + 0.5f); } void OperationCiftiLabelExportTable::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); CiftiFile* myCifti = myParams->getCifti(1); AString mapString = myParams->getString(2); AString outfileName = myParams->getString(3); const CiftiXML& myXML = myCifti->getCiftiXML(); if (myXML.getMappingType(CiftiXML::ALONG_ROW) != CiftiMappingType::LABELS) throw OperationException("cifti file must have LABELS mapping along row"); const CiftiLabelsMap& myLabelsMap = myXML.getLabelsMap(CiftiXML::ALONG_ROW); int64_t mapIndex = myLabelsMap.getIndexFromNumberOrName(mapString); if (mapIndex == -1) throw OperationException("map '" + mapString + "' not found in label map"); ofstream outFile(outfileName.toLocal8Bit().constData()); if (!outFile) throw OperationException("failed to open output text file"); const GiftiLabelTable* myTable = myLabelsMap.getMapLabelTable(mapIndex); set allKeys = myTable->getKeys(); int32_t unassignedKey = myTable->getUnassignedLabelKey(); for (set::iterator iter = allKeys.begin(); iter != allKeys.end(); ++iter) { if (*iter == unassignedKey) continue;//don't output the unused key, because import doesn't want it in the text file const GiftiLabel* thisLabel = myTable->getLabel(*iter); outFile << thisLabel->getName() << endl; outFile << thisLabel->getKey() << " " << floatTo255(thisLabel->getRed()) << " " << floatTo255(thisLabel->getGreen()) << " " << floatTo255(thisLabel->getBlue()) << " " << floatTo255(thisLabel->getAlpha()) << endl; if (!outFile) throw OperationException("error writing to output text file"); } } workbench-1.1.1/src/Operations/OperationCiftiLabelExportTable.h000066400000000000000000000027651255417355300246340ustar00rootroot00000000000000#ifndef __OPERATION_CIFTI_LABEL_EXPORT_TABLE_H__ #define __OPERATION_CIFTI_LABEL_EXPORT_TABLE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationCiftiLabelExportTable : public AbstractOperation { static int floatTo255(const float& in); public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationCiftiLabelExportTable; } #endif //__OPERATION_CIFTI_LABEL_EXPORT_TABLE_H__ workbench-1.1.1/src/Operations/OperationCiftiLabelImport.cxx000066400000000000000000000315611255417355300242240ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationCiftiLabelImport.h" #include "OperationException.h" #include "CaretLogger.h" #include "CiftiFile.h" #include "FileInformation.h" #include "GiftiLabel.h" #include "GiftiLabelTable.h" #include #include #include #include #include #include #include #include using namespace caret; using namespace std; AString OperationCiftiLabelImport::getCommandSwitch() { return "-cifti-label-import"; } AString OperationCiftiLabelImport::getShortDescription() { return "MAKE A CIFTI LABEL FILE FROM A CIFTI FILE"; } OperationParameters* OperationCiftiLabelImport::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiParameter(1, "input", "the input cifti file"); ret->addStringParameter(2, "label-list-file", "text file containing the values and names for labels"); ret->addCiftiOutputParameter(3, "output", "the output cifti label file"); ret->createOptionalParameter(4, "-discard-others", "set any values not mentioned in the label list to the ??? label"); OptionalParameter* unlabeledOption = ret->createOptionalParameter(5, "-unlabeled-value", "set the value that will be interpreted as unlabeled"); unlabeledOption->addIntegerParameter(1, "value", "the numeric value for unlabeled (default 0)"); ret->createOptionalParameter(6, "-drop-unused-labels", "remove any unused label values from the label table"); ret->setHelpText( AString("Creates a cifti label file from a cifti file with label-like values. ") + "You may specify the empty string ('' will work on linux/mac) for , which will be treated as if it is an empty file. " + "The label list file must have lines of the following format:\n\n" + "\n \n\n" + "Do not specify the \"unlabeled\" key in the file, it is assumed that 0 means not labeled unless -unlabeled-value is specified. " + "Label names must be on a separate line, but may contain spaces or other unusual characters (but not newline). " + "Whitespace is trimmed from both ends of the label name, but is kept if it is in the middle of a label. " + "The values of red, green, blue and alpha must be integers from 0 to 255, and will specify the color the label is drawn as " + "(alpha of 255 means opaque, which is probably what you want). " + "By default, it will set new label names with names of LABEL_# for any values encountered that are not mentioned in the " + "list file, specify -discard-others to instead set these to the \"unlabeled\" key." ); return ret; } void OperationCiftiLabelImport::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); CiftiFile* ciftiIn = myParams->getCifti(1); AString listfileName = myParams->getString(2); CiftiFile* ciftiOut = myParams->getOutputCifti(3); bool discardOthers = myParams->getOptionalParameter(4)->m_present; int32_t unlabeledValue = 0; OptionalParameter* unlabeledOption = myParams->getOptionalParameter(5); if (unlabeledOption->m_present) { unlabeledValue = (int32_t)unlabeledOption->getInteger(1); } bool dropUnused = myParams->getOptionalParameter(6)->m_present; map translate; GiftiLabelTable myTable; if (listfileName != "") { AString temp; FileInformation textFileInfo(listfileName); if (!textFileInfo.exists()) { throw OperationException("label list file doesn't exist"); } fstream labelListFile(listfileName.toLocal8Bit().constData(), fstream::in); if (!labelListFile.good()) { throw OperationException("error reading label list file"); } string labelName; int32_t value, red, green, blue, alpha; int labelCount = 0; translate[unlabeledValue] = 0;//placeholder, we don't know the correct translated value yet while (labelListFile.good()) { ++labelCount;//just for error messages, so start at 1 getline(labelListFile, labelName); labelListFile >> value; if (labelListFile.eof() && labelName == "") break;//if end of file trying to read an int, and label name is empty, its really just end of file labelListFile >> red; labelListFile >> green; labelListFile >> blue; if (!(labelListFile >> alpha))//yes, that is seriously the correct way to check if input was successfully extracted...so much fail { throw OperationException("label list file is malformed for entry #" + AString::number(labelCount) + ": " + AString(labelName.c_str())); } if (red < 0 || red > 255) { throw OperationException("bad value for red for entry #" + AString::number(labelCount) + ", " + AString(labelName.c_str()) + ": " + AString::number(red)); } if (green < 0 || green > 255) { throw OperationException("bad value for green for entry #" + AString::number(labelCount) + ", " + AString(labelName.c_str()) + ": " + AString::number(green)); } if (blue < 0 || blue > 255) { throw OperationException("bad value for blue for entry #" + AString::number(labelCount) + ", " + AString(labelName.c_str()) + ": " + AString::number(blue)); } if (alpha < 0 || alpha > 255) { throw OperationException("bad value for alpha for entry #" + AString::number(labelCount) + ", " + AString(labelName.c_str()) + ": " + AString::number(alpha)); } if (value == GiftiLabel::getInvalidLabelKey()) { throw OperationException("entry #" + AString::number(labelCount) + ", " + AString(labelName.c_str()) + " specifies unusable key value: " + value); } while (isspace(labelListFile.peek())) { labelListFile.ignore();//drop the newline, possible carriage return or other whitespace so that getline doesn't get nothing, and cause int extraction to fail } temp = AString(labelName.c_str()).trimmed();//drop errant CR or other whitespace from beginning and end of lines if (translate.find(value) != translate.end()) { if (value == unlabeledValue) { throw OperationException("the unlabeled value must not be specified in label list file"); } else { throw OperationException(AString("label key ") + AString::number(value) + " specified more than once"); } } GiftiLabel myLabel(value, temp, red, green, blue, alpha); if (myTable.getLabelKeyFromName(temp) != GiftiLabel::getInvalidLabelKey()) { AString nameBase = temp, newName;//resolve collision by generating a name with an additional number on it bool success = false; for (int extra = 1; extra < 100; ++extra)//but stop at 100, because really... { newName = nameBase + "_" + AString::number(extra); if (myTable.getLabelKeyFromName(newName) == GiftiLabel::getInvalidLabelKey()) { success = true; break; } } if (success) { CaretLogWarning("name collision in input name '" + nameBase + "', changing one to '" + newName + "'"); } else { throw OperationException("giving up on resolving name collision for input name '" + nameBase + "'"); } myLabel.setName(newName); } int32_t newValue; if (value == 0)//because label 0 exists in the default constructed table { myTable.insertLabel(&myLabel);//but we do want to be able to overwrite the default 0 label newValue = 0;//if value 0 is specified twice, or once without specifying a different unlabeled value, the check versus the translate map will catch it } else { newValue = myTable.addLabel(&myLabel);//we don't want to overwrite relocated labels } translate[value] = newValue; } } int32_t unusedLabel = myTable.getUnassignedLabelKey(); translate[unlabeledValue] = unusedLabel; const CiftiXMLOld& xmlIn = ciftiIn->getCiftiXMLOld(); int rowSize = xmlIn.getNumberOfColumns(), colSize = xmlIn.getNumberOfRows(); CiftiXMLOld xmlOut = xmlIn; xmlOut.resetRowsToLabels(rowSize); vector > usedArray(rowSize); vector rowScratch(rowSize); vector > matrixOut(colSize, vector(rowSize));//because the label table gets modified as we scan values, store the translated values instead of going back for a second pass for (int row = 0; row < colSize; ++row) { ciftiIn->getRow(rowScratch.data(), row); for (int col = 0; col < rowSize; ++col) { int32_t labelval = (int32_t)floor(rowScratch[col] + 0.5f);//just in case it somehow got poorly encoded, round to nearest if (dropUnused) { usedArray[col].insert(labelval); } map::iterator myiter = translate.find(labelval); if (myiter == translate.end()) { if (discardOthers) { matrixOut[row][col] = unusedLabel; } else {//use a random color, but fully opaque for the label GiftiLabel myLabel(labelval, AString("LABEL_") + AString::number(labelval), rand() & 255, rand() & 255, rand() & 255, 255); if (myTable.getLabelKeyFromName(myLabel.getName()) != GiftiLabel::getInvalidLabelKey()) { AString nameBase = myLabel.getName(), newName;//resolve collision by generating a name with an additional number on it bool success = false; for (int extra = 1; extra < 100; ++extra)//but stop at 100, because really... { newName = nameBase + "_" + AString::number(extra); if (myTable.getLabelKeyFromName(newName) == GiftiLabel::getInvalidLabelKey()) { success = true; break; } } if (success) { CaretLogWarning("name collision in auto-generated name '" + nameBase + "', changed to '" + newName + "'"); } else { throw OperationException("giving up on resolving name collision for auto-generated name '" + nameBase + "'"); } myLabel.setName(newName); } int32_t newValue = myTable.addLabel(&myLabel);//don't overwrite any values in the table translate[labelval] = newValue; matrixOut[row][col] = newValue; } } else { matrixOut[row][col] = myiter->second; } } } for (int i = 0; i < rowSize; ++i) { xmlOut.setMapNameForIndex(CiftiXMLOld::ALONG_ROW, i, xmlIn.getMapName(CiftiXMLOld::ALONG_ROW, i)); if (discardOthers) { GiftiLabelTable colTable = myTable; colTable.deleteUnusedLabels(usedArray[i]); *(xmlOut.getMapLabelTable(CiftiXMLOld::ALONG_ROW, i)) = colTable; } else { *(xmlOut.getMapLabelTable(CiftiXMLOld::ALONG_ROW, i)) = myTable; } } ciftiOut->setCiftiXML(xmlOut); for (int row = 0; row < colSize; ++row) { ciftiOut->setRow(matrixOut[row].data(), row); } } workbench-1.1.1/src/Operations/OperationCiftiLabelImport.h000066400000000000000000000026441255417355300236510ustar00rootroot00000000000000#ifndef __OPERATION_CIFTI_LABEL_IMPORT_H__ #define __OPERATION_CIFTI_LABEL_IMPORT_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationCiftiLabelImport : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationCiftiLabelImport; } #endif //__OPERATION_CIFTI_LABEL_IMPORT_H__ workbench-1.1.1/src/Operations/OperationCiftiMath.cxx000066400000000000000000000352461255417355300227070ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationCiftiMath.h" #include "OperationException.h" #include "CaretAssert.h" #include "CaretLogger.h" #include "CaretMathExpression.h" #include "CiftiFile.h" #include "CiftiXML.h" #include "MultiDimIterator.h" #include using namespace caret; using namespace std; AString OperationCiftiMath::getCommandSwitch() { return "-cifti-math"; } AString OperationCiftiMath::getShortDescription() { return "EVALUATE EXPRESSION ON CIFTI FILES"; } OperationParameters* OperationCiftiMath::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addStringParameter(1, "expression", "the expression to evaluate, in quotes"); ret->addCiftiOutputParameter(2, "cifti-out", "the output cifti file"); ParameterComponent* varOpt = ret->createRepeatableParameter(3, "-var", "a cifti file to use as a variable"); varOpt->addStringParameter(1, "name", "the name of the variable, as used in the expression"); varOpt->addCiftiParameter(2, "cifti", "the cifti file to use as this variable"); ParameterComponent* selectOpt = varOpt->createRepeatableParameter(3, "-select", "select a single index from a dimension");//repeatable option to repeatable option selectOpt->addIntegerParameter(1, "dim", "the dimension to select from (1-based)"); selectOpt->addIntegerParameter(2, "index", "the index to use (1-based)"); selectOpt->createOptionalParameter(3, "-repeat", "repeat the selected values for each index of output in this dimension");//with a repeat option OptionalParameter* fixNanOpt = ret->createOptionalParameter(4, "-fixnan", "replace NaN results with a value"); fixNanOpt->addDoubleParameter(1, "replace", "value to replace NaN with"); ret->createOptionalParameter(5, "-override-mapping-check", "don't check the mappings for compatibility, only check length"); AString myText = AString("This command evaluates at each matrix element independently. ") + "There must be at least one -var option (to get the output layout from), even if the specified in it isn't used in .\n\n" + "To select a single column from a 2D file (most cifti files are 2D), use -select 1 , where is 1-based. " + "To select a single row from a 2D file, use -select 2 . " + "Where -select is not used, the cifti files must have compatible mappings (e.g., brain models and parcels mappings must match exactly except for parcel names). " + "Use -override-mapping-check to skip this checking.\n\n" + "Filenames are not valid in , use a variable name and a -var option with matching to specify an input file. " + "The format of is as follows:\n\n"; myText += CaretMathExpression::getExpressionHelpInfo(); ret->setHelpText(myText); return ret; } void OperationCiftiMath::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); AString expression = myParams->getString(1); CaretMathExpression myExpr(expression); cout << "parsed '" + expression + "' as '" + myExpr.toString() + "'" << endl; vector myVarNames = myExpr.getVarNames(); CiftiFile* myCiftiOut = myParams->getOutputCifti(2); const vector& myVarOpts = *(myParams->getRepeatableParameterInstances(3)); OptionalParameter* fixNanOpt = myParams->getOptionalParameter(4); bool nanfix = false; float nanfixval = 0; if (fixNanOpt->m_present) { nanfix = true; nanfixval = (float)fixNanOpt->getDouble(1); } bool overrideMapCheck = myParams->getOptionalParameter(5)->m_present; int numInputs = myVarOpts.size(); int numVars = myVarNames.size(); vector varCiftiFiles(numVars, (CiftiFile*)NULL); if (numInputs == 0 && numVars == 0) throw OperationException("you must specify at least one input file (-var), even if the expression doesn't use a variable"); CiftiXML outXML; QString xmlText; vector outDims;//don't even assume 2 dimensions, in case someone makes a 1-d cifti vector > selectInfo(numVars); for (int i = 0; i < numInputs; ++i) { AString varName = myVarOpts[i]->getString(1); double constVal; if (CaretMathExpression::getNamedConstant(varName, constVal)) { throw OperationException("'" + varName + "' is a named constant equal to " + AString::number(constVal, 'g', 15) + ", please use a different variable name"); } const CiftiXML& tempXML = myVarOpts[i]->getCifti(2)->getCiftiXML(); int thisNumDims = tempXML.getNumberOfDimensions(); vector thisSelectInfo(thisNumDims, -1); vector thisRepeat(thisNumDims, false); const vector& thisSelectOpts = *(myVarOpts[i]->getRepeatableParameterInstances(3)); for (int j = 0; j < (int)thisSelectOpts.size(); ++j) { int dim = (int)thisSelectOpts[j]->getInteger(1) - 1; int64_t thisIndex = thisSelectOpts[j]->getInteger(2) - 1; if (dim >= (int)thisSelectInfo.size()) { if (thisIndex != 0) throw OperationException("-select used for variable '" + varName + "' with index other than 1 on nonexistent dimension"); thisSelectInfo.resize(dim + 1, -1); thisRepeat.resize(dim + 1, false); } thisSelectInfo[dim] = thisIndex; thisRepeat[dim] = thisSelectOpts[j]->getOptionalParameter(3)->m_present; } bool found = false; for (int j = 0; j < numVars; ++j) { if (varName == myVarNames[j]) { if (varCiftiFiles[j] != NULL) throw OperationException("variable '" + varName + "' specified more than once"); found = true; varCiftiFiles[j] = myVarOpts[i]->getCifti(2); selectInfo[j] = thisSelectInfo;//copy selection info break; } } if (!found && (numVars != 0 || numInputs != 1))//supress warning when a single -var is used with a constant expression, as required per help { CaretLogWarning("variable '" + varName + "' not used in expression"); } int newNumDims = (int)max(thisSelectInfo.size(), outDims.size());//now, to figure out the output dimensions with -select and -repeat for (int j = 0; j < newNumDims; ++j) { if (j >= (int)outDims.size())//need to expand output dimensions { outXML.setNumberOfDimensions(j + 1);//does not clear existing mappings outDims.push_back(-1);//unknown length } if (j >= (int)thisSelectInfo.size())//need to expand input info { thisSelectInfo.push_back(-1);//use "all" indices, but there is only 1 (virtual) index, pushing 0 should have same effect thisRepeat.push_back(false);//repeat not specified } if (outDims[j] == -1)//if we don't know the output length yet, put it in if we have it (-repeat not specified) { if (thisSelectInfo[j] == -1)//no -select for this dimension, use all maps { if (j < thisNumDims) { outDims[j] = tempXML.getDimensionLength(j); outXML.setMap(j, *(tempXML.getMap(j)));//copy the mapping type, since this input defines this dimension } else {//if higher dimension than the file has, transparently say it is of length 1, and don't use the mapping outDims[j] = 1; } } else {//-select was used if (!thisRepeat[j])//if -repeat wasn't used, output length is 1 { outDims[j] = 1; } } } else { if (thisSelectInfo[j] == -1)//-select was not used { if (j < thisNumDims) { if (outDims[j] != tempXML.getDimensionLength(j)) { throw OperationException("variable '" + varName + "' has length " + AString::number(tempXML.getDimensionLength(j)) + " for dimension " + AString::number(j + 1) + " while previous -var options require a length of " + AString::number(outDims[j])); } if (outXML.getMap(j) == NULL)//dimension was set to 1 by -select, but didn't set a mapping, so borrow from here { outXML.setMap(j, *(tempXML.getMap(j))); } else {//test mapping types for compatibility since -select wasn't used AString explanation; if (!overrideMapCheck && !outXML.getMap(j)->approximateMatch(*(tempXML.getMap(j)), &explanation)) { throw OperationException("variable " + varName + "'s " + CiftiMappingType::mappingTypeToName(tempXML.getMap(j)->getType()) + " mapping on dimension " + AString::number(j + 1) + " doesn't match mappings from earlier -var options: '" + explanation + "'"); } } } else { if (outDims[j] != 1) { throw OperationException(AString("variable '" + varName + "' is of lower dimensionality than output, ") + "and the length of output dimension " + AString::number(j + 1) + " is " + AString::number(outDims[j]) + ", you might want to use -select with -repeat"); } } } else { if (!thisRepeat[j]) { if (outDims[j] != 1) { throw OperationException("variable '" + varName + "' uses -select for dimension " + AString::number(j + 1) + ", but previous -var options require a length of " + AString::number(outDims[j])); } } } } } } for (int i = 0; i < numVars; ++i) { if (varCiftiFiles[i] == NULL) throw OperationException("no -var option specified for variable '" + myVarNames[i] + "'"); } CiftiScalarsMap dummyMap;//make an empty length-1 scalar map for dimensions we don't have a mapping for dummyMap.setLength(1); for (int i = 0; i < outXML.getNumberOfDimensions(); ++i) { if (outDims[i] == -1) throw OperationException("all -var options used -select and -repeat for dimension " + AString::number(i + 1) + ", there is no file to get the dimension length from"); if (outXML.getMap(i) == NULL)//-select was used in all variables for this dimension, so we don't have a mapping { outXML.setMap(i, dummyMap);//so, make it a length-1 scalar with no name and empty metadata } CaretAssert(outDims[i] == outXML.getDimensionLength(i)); } if (outXML.getNumberOfDimensions() < 1) throw OperationException("output must have at least 1 dimension"); myCiftiOut->setCiftiXML(outXML); vector values(numVars), scratchRow(outDims[0]); vector > inputRows(numVars); vector > loadedRow(numVars);//to detect and prevent rereading the same row for (int v = 0; v < numVars; ++v) { inputRows[v].resize(varCiftiFiles[v]->getCiftiXML().getDimensionLength(CiftiXML::ALONG_ROW)); loadedRow[v].resize(varCiftiFiles[v]->getCiftiXML().getNumberOfDimensions() - 1, -1);//we always load a full row, so ignore first dim } for (MultiDimIterator iter(vector(outDims.begin() + 1, outDims.end())); !iter.atEnd(); ++iter) { for (int v = 0; v < numVars; ++v)//first, retrieve whichever rows are needed { bool needToLoad = false; for (int dim = 0; dim < (int)loadedRow[v].size(); ++dim) { int64_t indexNeeded = -1; if (selectInfo[v][dim + 1] == -1) { CaretAssert(dim + 1 < (int)outDims.size());//"match to output index" can't work past output dimensionality indexNeeded = (*iter)[dim];//NOTE: iter also doesn't include the first dim } else { indexNeeded = selectInfo[v][dim + 1]; } if (indexNeeded != loadedRow[v][dim]) { needToLoad = true; loadedRow[v][dim] = indexNeeded; } } if (needToLoad) { varCiftiFiles[v]->getRow(inputRows[v].data(), loadedRow[v]); } } for (int j = 0; j < outDims[0]; ++j) { for (int v = 0; v < numVars; ++v)//now we check for select along row { if (selectInfo[v][0] == -1) { values[v] = inputRows[v][j]; } else { values[v] = inputRows[v][selectInfo[v][0]]; } } scratchRow[j] = (float)myExpr.evaluate(values); if (nanfix && scratchRow[j] != scratchRow[j]) { scratchRow[j] = nanfixval; } } myCiftiOut->setRow(scratchRow.data(), *iter); } } workbench-1.1.1/src/Operations/OperationCiftiMath.h000066400000000000000000000025671255417355300223340ustar00rootroot00000000000000#ifndef __OPERATION_CIFTI_MATH_H__ #define __OPERATION_CIFTI_MATH_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationCiftiMath : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationCiftiMath; } #endif //__OPERATION_CIFTI_MATH_H__ workbench-1.1.1/src/Operations/OperationCiftiMerge.cxx000066400000000000000000000370401255417355300230470ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationCiftiMerge.h" #include "OperationException.h" #include "CaretAssert.h" #include "CaretPointer.h" #include "CiftiFile.h" #include using namespace caret; using namespace std; AString OperationCiftiMerge::getCommandSwitch() { return "-cifti-merge"; } AString OperationCiftiMerge::getShortDescription() { return "MERGE CIFTI TIMESERIES, SCALAR, OR LABEL FILES"; } OperationParameters* OperationCiftiMerge::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiOutputParameter(1, "cifti-out", "output cifti file"); ParameterComponent* ciftiOpt = ret->createRepeatableParameter(2, "-cifti", "specify an input cifti file"); ciftiOpt->addCiftiParameter(1, "cifti-in", "a cifti file to use columns from"); ParameterComponent* columnOpt = ciftiOpt->createRepeatableParameter(2, "-column", "select a single column to use"); columnOpt->addIntegerParameter(1, "column", "the column index (starting from 1)"); OptionalParameter* upToOpt = columnOpt->createOptionalParameter(2, "-up-to", "use an inclusive range of columns"); upToOpt->addIntegerParameter(1, "last-column", "the index of the last column to include"); upToOpt->createOptionalParameter(2, "-reverse", "use the range in reverse order"); ret->setHelpText( AString("Given input CIFTI files which have matching mappings along columns, and for which mappings along rows ") + "are the same type, all either series, scalars, or labels, this command concatenates the specified columns horizontally (rows become longer).\n\n" + "Example: wb_command -cifti-merge out.dtseries.nii -cifti first.dtseries.nii -column 1 -cifti second.dtseries.nii\n\n" + "This example would take the first column from first.dtseries.nii, followed by all columns from second.dtseries.nii, " + "and write these columns to out.dtseries.nii." ); return ret; } void OperationCiftiMerge::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); CiftiFile* ciftiOut = myParams->getOutputCifti(1); const vector& myInputs = *(myParams->getRepeatableParameterInstances(2)); vector ciftiList; int numInputs = (int)myInputs.size(); if (numInputs == 0) throw OperationException("no inputs specified"); const CiftiFile* firstCifti = myInputs[0]->getCifti(1); const CiftiXML& baseXML = firstCifti->getCiftiXML(); if (baseXML.getNumberOfDimensions() != 2) throw OperationException("only 2D cifti are supported"); const CiftiMappingType& baseColMapping = *(baseXML.getMap(CiftiXML::ALONG_COLUMN)), &baseRowMapping = *(baseXML.getMap(CiftiXML::ALONG_ROW)); switch (baseRowMapping.getType()) { case CiftiMappingType::SCALARS: case CiftiMappingType::LABELS: case CiftiMappingType::SERIES: break; default: throw OperationException("row mapping type must be series, scalars, or labels"); } int64_t numOutColumns = 0;//output row length for (int i = 0; i < numInputs; ++i) { const CiftiFile* ciftiIn = myInputs[i]->getCifti(1); vector thisDims = ciftiIn->getDimensions(); const CiftiXML& thisXML = ciftiIn->getCiftiXML(); if (thisXML.getNumberOfDimensions() != 2) throw OperationException("only 2D cifti are supported"); if (!thisXML.getMap(CiftiXML::ALONG_COLUMN)->approximateMatch(baseColMapping)) throw OperationException("file '" + ciftiIn->getFileName() + "' has non-matching mapping along columns"); if (thisXML.getMappingType(CiftiXML::ALONG_ROW) != baseRowMapping.getType()) throw OperationException("file '" + ciftiIn->getFileName() + "' has different mapping type along rows"); const vector& columnOpts = *(myInputs[i]->getRepeatableParameterInstances(2)); int numColumnOpts = (int)columnOpts.size(); if (numColumnOpts > 0) { for (int j = 0; j < numColumnOpts; ++j) { int64_t initialColumn = columnOpts[j]->getInteger(1) - 1;//1-based indexing convention if (initialColumn < 0 || initialColumn >= thisDims[0]) throw OperationException("column '" + AString::number(initialColumn + 1) + "' not valid in file '" + ciftiIn->getFileName() + "'"); OptionalParameter* upToOpt = columnOpts[j]->getOptionalParameter(2); if (upToOpt->m_present) { int finalColumn = upToOpt->getInteger(1) - 1;//ditto if (finalColumn < 0 || finalColumn >= thisDims[0]) throw OperationException("ending column '" + AString::number(finalColumn + 1) + "' not valid in file '" + ciftiIn->getFileName() + "'"); if (finalColumn < initialColumn) throw OperationException("ending column occurs before starting column in file '" + ciftiIn->getFileName() + "'"); numOutColumns += finalColumn - initialColumn + 1;//inclusive - we don't need to worry about reversing for counting, though } else { numOutColumns += 1; } } } else { numOutColumns += thisDims[0]; } } CiftiScalarsMap outScalarMap;//we only use one of these CiftiLabelsMap outLabelMap; CiftiSeriesMap outSeriesMap; bool isLabel = false, doLoop = true;//whether we need to set attributes on each column switch (baseRowMapping.getType()) { case CiftiMappingType::SCALARS: outScalarMap.setLength(numOutColumns); break; case CiftiMappingType::LABELS: outLabelMap.setLength(numOutColumns); isLabel = true; break; case CiftiMappingType::SERIES: outSeriesMap.setLength(numOutColumns); outSeriesMap.setUnit(((const CiftiSeriesMap&)baseRowMapping).getUnit()); outSeriesMap.setStart(((const CiftiSeriesMap&)baseRowMapping).getStart()); outSeriesMap.setStep(((const CiftiSeriesMap&)baseRowMapping).getStep()); doLoop = false; break; default: CaretAssert(false); } int64_t curCol = 0, scratchRowLength = 0; for (int i = 0; i < numInputs; ++i) { const CiftiFile* ciftiIn = myInputs[i]->getCifti(1); vector thisDims = ciftiIn->getDimensions(); const CiftiXML& thisXML = ciftiIn->getCiftiXML(); const vector& columnOpts = *(myInputs[i]->getRepeatableParameterInstances(2)); int numColumnOpts = (int)columnOpts.size(); if (numColumnOpts > 0) { scratchRowLength = max(scratchRowLength, thisXML.getDimensionLength(CiftiXML::ALONG_ROW));//if we use the entire row, we don't need a separate scratch row for it if (doLoop) { for (int j = 0; j < numColumnOpts; ++j) { int64_t initialColumn = columnOpts[j]->getInteger(1) - 1;//1-based indexing convention OptionalParameter* upToOpt = columnOpts[j]->getOptionalParameter(2); if (upToOpt->m_present) { int finalColumn = upToOpt->getInteger(1) - 1;//ditto bool reverse = upToOpt->getOptionalParameter(2)->m_present; if (reverse) { for (int c = finalColumn; c >= initialColumn; --c) { if (isLabel) { const CiftiLabelsMap& thisLabelMap = thisXML.getLabelsMap(CiftiXML::ALONG_ROW); outLabelMap.setMapName(curCol, thisLabelMap.getMapName(c)); *(outLabelMap.getMapLabelTable(curCol)) = *(thisLabelMap.getMapLabelTable(c)); *(outLabelMap.getMapMetadata(curCol)) = *(thisLabelMap.getMapMetadata(c)); } else { const CiftiScalarsMap& thisScalarMap = thisXML.getScalarsMap(CiftiXML::ALONG_ROW); outScalarMap.setMapName(curCol, thisScalarMap.getMapName(c)); *(outScalarMap.getMapPalette(curCol)) = *(thisScalarMap.getMapPalette(c)); *(outScalarMap.getMapMetadata(curCol)) = *(thisScalarMap.getMapMetadata(c)); } ++curCol; } } else { for (int c = initialColumn; c <= finalColumn; ++c) { if (isLabel) { const CiftiLabelsMap& thisLabelMap = thisXML.getLabelsMap(CiftiXML::ALONG_ROW); outLabelMap.setMapName(curCol, thisLabelMap.getMapName(c)); *(outLabelMap.getMapLabelTable(curCol)) = *(thisLabelMap.getMapLabelTable(c)); *(outLabelMap.getMapMetadata(curCol)) = *(thisLabelMap.getMapMetadata(c)); } else { const CiftiScalarsMap& thisScalarMap = thisXML.getScalarsMap(CiftiXML::ALONG_ROW); outScalarMap.setMapName(curCol, thisScalarMap.getMapName(c)); *(outScalarMap.getMapPalette(curCol)) = *(thisScalarMap.getMapPalette(c)); *(outScalarMap.getMapMetadata(curCol)) = *(thisScalarMap.getMapMetadata(c)); } ++curCol; } } } else { if (isLabel) { const CiftiLabelsMap& thisLabelMap = thisXML.getLabelsMap(CiftiXML::ALONG_ROW); outLabelMap.setMapName(curCol, thisLabelMap.getMapName(initialColumn)); *(outLabelMap.getMapLabelTable(curCol)) = *(thisLabelMap.getMapLabelTable(initialColumn)); *(outLabelMap.getMapMetadata(curCol)) = *(thisLabelMap.getMapMetadata(initialColumn)); } else { const CiftiScalarsMap& thisScalarMap = thisXML.getScalarsMap(CiftiXML::ALONG_ROW); outScalarMap.setMapName(curCol, thisScalarMap.getMapName(initialColumn)); *(outScalarMap.getMapPalette(curCol)) = *(thisScalarMap.getMapPalette(initialColumn)); *(outScalarMap.getMapMetadata(curCol)) = *(thisScalarMap.getMapMetadata(initialColumn)); } ++curCol; } } } } else { if (doLoop) { for (int64_t j = 0; j < thisDims[0]; ++j) { if (isLabel) { const CiftiLabelsMap& thisLabelMap = thisXML.getLabelsMap(CiftiXML::ALONG_ROW); outLabelMap.setMapName(curCol, thisLabelMap.getMapName(j)); *(outLabelMap.getMapLabelTable(curCol)) = *(thisLabelMap.getMapLabelTable(j)); *(outLabelMap.getMapMetadata(curCol)) = *(thisLabelMap.getMapMetadata(j)); } else { const CiftiScalarsMap& thisScalarMap = thisXML.getScalarsMap(CiftiXML::ALONG_ROW); outScalarMap.setMapName(curCol, thisScalarMap.getMapName(j)); *(outScalarMap.getMapPalette(curCol)) = *(thisScalarMap.getMapPalette(j)); *(outScalarMap.getMapMetadata(curCol)) = *(thisScalarMap.getMapMetadata(j)); } ++curCol; } } } } CaretAssert(!doLoop || curCol == numOutColumns); CiftiXML outXML; outXML.setNumberOfDimensions(2); outXML.setMap(CiftiXML::ALONG_COLUMN, baseColMapping); switch (baseRowMapping.getType()) { case CiftiMappingType::LABELS: outXML.setMap(CiftiXML::ALONG_ROW, outLabelMap); break; case CiftiMappingType::SCALARS: outXML.setMap(CiftiXML::ALONG_ROW, outScalarMap); break; case CiftiMappingType::SERIES: outXML.setMap(CiftiXML::ALONG_ROW, outSeriesMap); break; default: CaretAssert(false); } ciftiOut->setCiftiXML(outXML); int64_t numRows = baseColMapping.getLength(); vector outRow(numOutColumns), scratchRow(scratchRowLength); for (int64_t row = 0; row < numRows; ++row) { curCol = 0; for (int i = 0; i < numInputs; ++i) { const CiftiFile* ciftiIn = myInputs[i]->getCifti(1); vector thisDims = ciftiIn->getDimensions(); const vector& columnOpts = *(myInputs[i]->getRepeatableParameterInstances(2)); int numColumnOpts = (int)columnOpts.size(); if (numColumnOpts > 0) { ciftiIn->getRow(scratchRow.data(), row); for (int j = 0; j < numColumnOpts; ++j) { int64_t initialColumn = columnOpts[j]->getInteger(1) - 1;//1-based indexing convention OptionalParameter* upToOpt = columnOpts[j]->getOptionalParameter(2); if (upToOpt->m_present) { int finalColumn = upToOpt->getInteger(1) - 1;//ditto bool reverse = upToOpt->getOptionalParameter(2)->m_present; if (reverse) { for (int c = finalColumn; c >= initialColumn; --c) { outRow[curCol] = scratchRow[c]; ++curCol; } } else { for (int c = initialColumn; c <= finalColumn; ++c) { outRow[curCol] = scratchRow[c]; ++curCol; } } } else { outRow[curCol] = scratchRow[initialColumn]; ++curCol; } } } else { ciftiIn->getRow(outRow.data() + curCol, row); curCol += thisDims[0]; } } CaretAssert(curCol == numOutColumns); ciftiOut->setRow(outRow.data(), row); } } workbench-1.1.1/src/Operations/OperationCiftiMerge.h000066400000000000000000000025751255417355300225010ustar00rootroot00000000000000#ifndef __OPERATION_CIFTI_MERGE_H__ #define __OPERATION_CIFTI_MERGE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationCiftiMerge : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationCiftiMerge; } #endif //__OPERATION_CIFTI_MERGE_H__ workbench-1.1.1/src/Operations/OperationCiftiPalette.cxx000066400000000000000000000274311255417355300234110ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationCiftiPalette.h" #include "CiftiFile.h" #include "OperationException.h" #include "Palette.h" #include "PaletteColorMapping.h" #include "PaletteFile.h" #include using namespace caret; using namespace std; AString OperationCiftiPalette::getCommandSwitch() { return "-cifti-palette"; } AString OperationCiftiPalette::getShortDescription() { return "SET PALETTE ON A CIFTI FILE"; } OperationParameters* OperationCiftiPalette::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiParameter(1, "cifti-in", "the cifti input"); ret->addStringParameter(2, "mode", "the mapping mode"); ret->addCiftiOutputParameter(3, "cifti-out", "the output cifti file"); OptionalParameter* columnSelect = ret->createOptionalParameter(4, "-column", "select a single column for scalar maps"); columnSelect->addStringParameter(1, "column", "the column number or name"); OptionalParameter* posMinMaxPercent = ret->createOptionalParameter(5, "-pos-percent", "percentage min/max for positive data coloring"); posMinMaxPercent->addDoubleParameter(1, "pos-min-%", "the percentile for the least positive data"); posMinMaxPercent->addDoubleParameter(2, "pos-max-%", "the percentile for the most positive data"); OptionalParameter* negMinMaxPercent = ret->createOptionalParameter(6, "-neg-percent", "percentage min/max for negative data coloring"); negMinMaxPercent->addDoubleParameter(1, "neg-min-%", "the percentile for the least negative data"); negMinMaxPercent->addDoubleParameter(2, "neg-max-%", "the percentile for the most negative data"); OptionalParameter* posMinMaxValue = ret->createOptionalParameter(7, "-pos-user", "user min/max values for positive data coloring"); posMinMaxValue->addDoubleParameter(1, "pos-min-user", "the value for the least positive data"); posMinMaxValue->addDoubleParameter(2, "pos-max-user", "the value for the most positive data"); OptionalParameter* negMinMaxValue = ret->createOptionalParameter(8, "-neg-user", "user min/max values for negative data coloring"); negMinMaxValue->addDoubleParameter(1, "neg-min-user", "the value for the least negative data"); negMinMaxValue->addDoubleParameter(2, "neg-max-user", "the value for the most negative data"); OptionalParameter* interpolate = ret->createOptionalParameter(9, "-interpolate", "interpolate colors"); interpolate->addBooleanParameter(1, "interpolate", "boolean, whether to interpolate"); OptionalParameter* displayPositive = ret->createOptionalParameter(10, "-disp-pos", "display positive data"); displayPositive->addBooleanParameter(1, "display", "boolean, whether to display"); OptionalParameter* displayNegative = ret->createOptionalParameter(11, "-disp-neg", "display positive data"); displayNegative->addBooleanParameter(1, "display", "boolean, whether to display"); OptionalParameter* displayZero = ret->createOptionalParameter(12, "-disp-zero", "display data closer to zero than the min cutoff"); displayZero->addBooleanParameter(1, "display", "boolean, whether to display"); OptionalParameter* paletteName = ret->createOptionalParameter(13, "-palette-name", "set the palette used"); paletteName->addStringParameter(1, "name", "the name of the palette"); OptionalParameter* thresholdOpt = ret->createOptionalParameter(14, "-thresholding", "set the thresholding"); thresholdOpt->addStringParameter(1, "type", "thresholding setting"); thresholdOpt->addStringParameter(2, "test", "show values inside or outside thresholds"); thresholdOpt->addDoubleParameter(3, "min", "lower threshold"); thresholdOpt->addDoubleParameter(4, "max", "upper threshold"); AString myText = AString("NOTE: The output file must be a different file than the input file.\n\n") + "For scalar maps, by default the palette is changed for every map, specify -column to change only one map. Palette settings not specified will be taken from the first column " + "for scalar maps, and from the existing file palette for other mapping types. " + "The argument must be one of the following:\n\n"; vector myEnums; PaletteScaleModeEnum::getAllEnums(myEnums); for (int i = 0; i < (int)myEnums.size(); ++i) { myText += PaletteScaleModeEnum::toName(myEnums[i]) + "\n"; } myText += "\nThe argument to -palette-name must be one of the following:\n\n"; PaletteFile myPF; int32_t numPalettes = myPF.getNumberOfPalettes(); for (int i = 0; i < numPalettes; ++i) { myText += myPF.getPalette(i)->getName() + "\n"; } myText += "\nThe argument to -thresholding must be one of the following:\n\n"; vector myEnums2; PaletteThresholdTypeEnum::getAllEnums(myEnums2); for (int i = 0; i < (int)myEnums2.size(); ++i) { myText += PaletteThresholdTypeEnum::toName(myEnums2[i]) + "\n"; } myText += "\nThe argument to -thresholding must be one of the following:\n\n"; vector myEnums3; PaletteThresholdTestEnum::getAllEnums(myEnums3); for (int i = 0; i < (int)myEnums3.size(); ++i) { myText += PaletteThresholdTestEnum::toName(myEnums3[i]) + "\n"; } ret->setHelpText(myText); return ret; } void OperationCiftiPalette::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); CiftiFile* ciftiIn = myParams->getCifti(1); CiftiXMLOld myOutXML = ciftiIn->getCiftiXMLOld(); int64_t numRows = myOutXML.getNumberOfRows(), numCols = myOutXML.getNumberOfColumns(); if (numRows < 1 || numCols < 1) { throw OperationException("cifti file has invalid dimensions"); } PaletteColorMapping myMapping; if (myOutXML.getRowMappingType() == CIFTI_INDEX_TYPE_SCALARS) { myMapping = *(myOutXML.getMapPalette(CiftiXMLOld::ALONG_ROW, 0)); } else { myMapping = *(myOutXML.getFilePalette()); } AString myModeName = myParams->getString(2); bool ok = false; PaletteScaleModeEnum::Enum myMode = PaletteScaleModeEnum::fromName(myModeName, &ok); if (!ok) { throw OperationException("unknown mapping mode"); } CiftiFile* ciftiOut = myParams->getOutputCifti(3); int myColumn = -1; OptionalParameter* columnOpt = myParams->getOptionalParameter(4); if (columnOpt->m_present) { if (myOutXML.getRowMappingType() != CIFTI_INDEX_TYPE_SCALARS) { throw OperationException("-column option can only be used on scalar maps"); } AString columnIdentifier = columnOpt->getString(1); bool ok = false; myColumn = columnIdentifier.toInt(&ok) - 1; if (ok) { if (myColumn < 0 || myColumn >= numCols) { throw OperationException("invalid column specified"); } } else { int i = 0; for (; i < numCols; ++i) { if (myOutXML.getMapNameForRowIndex(i) == columnIdentifier)//index along a row - we need to fix these function names { myColumn = i; break; } } if (i >= numCols) { throw OperationException("invalid column specified"); } } } myMapping.setScaleMode(myMode); OptionalParameter* posMinMaxPercent = myParams->getOptionalParameter(5); if (posMinMaxPercent->m_present) { myMapping.setAutoScalePercentagePositiveMinimum(posMinMaxPercent->getDouble(1)); myMapping.setAutoScalePercentagePositiveMaximum(posMinMaxPercent->getDouble(2)); } OptionalParameter* negMinMaxPercent = myParams->getOptionalParameter(6); if (negMinMaxPercent->m_present) { myMapping.setAutoScalePercentageNegativeMinimum(negMinMaxPercent->getDouble(1)); myMapping.setAutoScalePercentageNegativeMaximum(negMinMaxPercent->getDouble(2)); } OptionalParameter* posMinMaxValue = myParams->getOptionalParameter(7); if (posMinMaxValue->m_present) { myMapping.setUserScalePositiveMinimum(posMinMaxValue->getDouble(1)); myMapping.setUserScalePositiveMaximum(posMinMaxValue->getDouble(2)); } OptionalParameter* negMinMaxValue = myParams->getOptionalParameter(8); if (negMinMaxValue->m_present) { myMapping.setUserScaleNegativeMinimum(negMinMaxValue->getDouble(1)); myMapping.setUserScaleNegativeMaximum(negMinMaxValue->getDouble(2)); } OptionalParameter* interpolate = myParams->getOptionalParameter(9); if (interpolate->m_present) { myMapping.setInterpolatePaletteFlag(interpolate->getBoolean(1)); } OptionalParameter* displayPositive = myParams->getOptionalParameter(10); if (displayPositive->m_present) { myMapping.setDisplayPositiveDataFlag(displayPositive->getBoolean(1)); } OptionalParameter* displayNegative = myParams->getOptionalParameter(11); if (displayNegative->m_present) { myMapping.setDisplayNegativeDataFlag(displayNegative->getBoolean(1)); } OptionalParameter* displayZero = myParams->getOptionalParameter(12); if (displayZero->m_present) { myMapping.setDisplayZeroDataFlag(displayZero->getBoolean(1)); } OptionalParameter* paletteName = myParams->getOptionalParameter(13); if (paletteName->m_present) { myMapping.setSelectedPaletteName(paletteName->getString(1)); } OptionalParameter* thresholdOpt = myParams->getOptionalParameter(14); if (thresholdOpt->m_present) { bool ok = false; PaletteThresholdTypeEnum::Enum mytype = PaletteThresholdTypeEnum::fromName(thresholdOpt->getString(1), &ok); if (!ok) throw OperationException("unrecognized threshold type string: " + thresholdOpt->getString(1)); PaletteThresholdTestEnum::Enum mytest = PaletteThresholdTestEnum::fromName(thresholdOpt->getString(2), &ok); if (!ok) throw OperationException("unrecognized threshold test string: " + thresholdOpt->getString(2)); myMapping.setThresholdType(mytype); myMapping.setThresholdTest(mytest); myMapping.setThresholdMinimum(mytype, thresholdOpt->getDouble(3)); myMapping.setThresholdMaximum(mytype, thresholdOpt->getDouble(4)); } if (myOutXML.getRowMappingType() == CIFTI_INDEX_TYPE_SCALARS) { if (myColumn == -1) { for (int i = 0; i < numCols; ++i) { *(myOutXML.getMapPalette(CiftiXMLOld::ALONG_ROW, i)) = myMapping; } } else { *(myOutXML.getMapPalette(CiftiXMLOld::ALONG_ROW, myColumn)) = myMapping; } } else { *(myOutXML.getFilePalette()) = myMapping; } ciftiOut->setCiftiXML(myOutXML); vector scratchRow(numCols); for (int64_t i = 0; i < numRows; ++i) { ciftiIn->getRow(scratchRow.data(), i); ciftiOut->setRow(scratchRow.data(), i); } } workbench-1.1.1/src/Operations/OperationCiftiPalette.h000066400000000000000000000026111255417355300230270ustar00rootroot00000000000000#ifndef __OPERATION_CIFTI_PALETTE_H__ #define __OPERATION_CIFTI_PALETTE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationCiftiPalette : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationCiftiPalette; } #endif //__OPERATION_CIFTI_PALETTE_H__ workbench-1.1.1/src/Operations/OperationCiftiROIAverage.cxx000066400000000000000000000214701255417355300237340ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationCiftiROIAverage.h" #include "AlgorithmCiftiSeparate.h" #include "CiftiFile.h" #include "MetricFile.h" #include "OperationException.h" #include "VolumeFile.h" #include using namespace caret; using namespace std; AString OperationCiftiROIAverage::getCommandSwitch() { return "-cifti-roi-average"; } AString OperationCiftiROIAverage::getShortDescription() { return "AVERAGE ROWS IN A SINGLE CIFTI FILE"; } OperationParameters* OperationCiftiROIAverage::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiParameter(1, "cifti-in", "the cifti file to average rows from"); ret->addStringParameter(2, "text-out", "output text file of the average values"); OptionalParameter* ciftiRoiOpt = ret->createOptionalParameter(3, "-cifti-roi", "cifti file containing combined rois"); ciftiRoiOpt->addCiftiParameter(1, "roi-cifti", "the rois as a cifti file"); OptionalParameter* leftRoiOpt = ret->createOptionalParameter(4, "-left-roi", "vertices to use from left hemisphere"); leftRoiOpt->addMetricParameter(1, "roi-metric", "the left roi as a metric file"); OptionalParameter* rightRoiOpt = ret->createOptionalParameter(5, "-right-roi", "vertices to use from right hemisphere"); rightRoiOpt->addMetricParameter(1, "roi-metric", "the right roi as a metric file"); OptionalParameter* cerebRoiOpt = ret->createOptionalParameter(6, "-cerebellum-roi", "vertices to use from cerebellum"); cerebRoiOpt->addMetricParameter(1, "roi-metric", "the cerebellum roi as a metric file"); OptionalParameter* volRoiOpt = ret->createOptionalParameter(7, "-vol-roi", "voxels to use"); volRoiOpt->addVolumeParameter(1, "roi-vol", "the roi volume file"); ret->setHelpText( AString("Average the rows that are within the specified ROIs, and write the resulting average row to a text file, separated by newlines. ") + "If -cifti-roi is specified, -left-roi, -right-roi, -cerebellum-roi, and -vol-roi must not be specified." ); return ret; } void OperationCiftiROIAverage::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); CiftiFile* myCifti = myParams->getCifti(1); if (myCifti->getCiftiXML().getMappingType(CiftiXML::ALONG_COLUMN) != CiftiMappingType::BRAIN_MODELS) { throw OperationException("input cifti file does not have a brain models mapping along column"); } AString textFileName = myParams->getString(2); fstream textFile(textFileName.toLocal8Bit().constData(), fstream::out | fstream::trunc); if (!textFile.good()) { throw OperationException("error opening output file for writing"); } int numCols = myCifti->getNumberOfColumns(); vector accum(numCols, 0.0); int accumCount = 0; CiftiFile* ciftiROI = NULL; OptionalParameter* ciftiRoiOpt = myParams->getOptionalParameter(3); if (ciftiRoiOpt->m_present) { ciftiROI = ciftiRoiOpt->getCifti(1); } OptionalParameter* leftRoiOpt = myParams->getOptionalParameter(4); if (leftRoiOpt->m_present) { if (ciftiROI != NULL) throw OperationException("-cifti-roi cannot be used with any other ROI option"); MetricFile* tempMetric = leftRoiOpt->getMetric(1); processSurfaceComponent(myCifti, StructureEnum::CORTEX_LEFT, tempMetric, accum, accumCount); } OptionalParameter* rightRoiOpt = myParams->getOptionalParameter(5); if (rightRoiOpt->m_present) { if (ciftiROI != NULL) throw OperationException("-cifti-roi cannot be used with any other ROI option"); MetricFile* tempMetric = rightRoiOpt->getMetric(1); processSurfaceComponent(myCifti, StructureEnum::CORTEX_RIGHT, tempMetric, accum, accumCount); } OptionalParameter* cerebRoiOpt = myParams->getOptionalParameter(6); if (cerebRoiOpt->m_present) { if (ciftiROI != NULL) throw OperationException("-cifti-roi cannot be used with any other ROI option"); MetricFile* tempMetric = cerebRoiOpt->getMetric(1); processSurfaceComponent(myCifti, StructureEnum::CEREBELLUM, tempMetric, accum, accumCount); } OptionalParameter* volRoiOpt = myParams->getOptionalParameter(7); if (volRoiOpt->m_present) { if (ciftiROI != NULL) throw OperationException("-cifti-roi cannot be used with any other ROI option"); VolumeFile* tempVol = volRoiOpt->getVolume(1); processVolume(myCifti, tempVol, accum, accumCount); } if (ciftiROI != NULL) { const CiftiXML& roiXML = ciftiROI->getCiftiXML(); const CiftiXML& inputXML = myCifti->getCiftiXML(); if (!(roiXML.getMap(CiftiXML::ALONG_COLUMN)->approximateMatch(*inputXML.getMap(CiftiXML::ALONG_COLUMN)))) { throw OperationException("dense mappings of input and roi cifti files don't match"); } const CiftiBrainModelsMap& roiDense = roiXML.getBrainModelsMap(CiftiXML::ALONG_COLUMN); if (roiDense.hasVolumeData()) { VolumeFile roiVol; int64_t offset[3]; AlgorithmCiftiSeparate(NULL, ciftiROI, CiftiXML::ALONG_COLUMN, &roiVol, offset, NULL, false);//don't crop because there should be only one map, and processVolume doesn't currently accept cropped processVolume(myCifti, &roiVol, accum, accumCount); } vector surfStructs = roiDense.getSurfaceStructureList(); for (int i = 0; i < (int)surfStructs.size(); ++i) { MetricFile roiMetric; AlgorithmCiftiSeparate(NULL, ciftiROI, CiftiXML::ALONG_COLUMN, surfStructs[i], &roiMetric); processSurfaceComponent(myCifti, surfStructs[i], &roiMetric, accum, accumCount); } } if (accumCount == 0) { throw OperationException("ROI(s) don't match any data"); } for (int i = 0; i < numCols; ++i) { textFile << accum[i] / accumCount << endl; } } void OperationCiftiROIAverage::processSurfaceComponent(const CiftiFile* myCifti, const StructureEnum::Enum& myStruct, const MetricFile* myRoi, vector& accum, int& accumCount) { int numCols = myCifti->getNumberOfColumns(); int numNodes = myRoi->getNumberOfNodes(); const CiftiBrainModelsMap& myDenseMap = myCifti->getCiftiXML().getBrainModelsMap(CiftiXML::ALONG_COLUMN); if (myDenseMap.getSurfaceNumberOfNodes(myStruct) != numNodes) { throw OperationException("roi number of vertices doesn't match for structure " + StructureEnum::toName(myStruct)); } vector scratch(numCols); vector myMap = myDenseMap.getSurfaceMap(myStruct); int mapSize = myMap.size(); for (int i = 0; i < mapSize; ++i) { if (myRoi->getValue(myMap[i].m_surfaceNode, 0) > 0.0f) { ++accumCount; myCifti->getRow(scratch.data(), myMap[i].m_ciftiIndex); for (int j = 0; j < numCols; ++j) { accum[j] += scratch[j]; } } } } void OperationCiftiROIAverage::processVolume(const CiftiFile* myCifti, const VolumeFile* myRoi, vector& accum, int& accumCount) { int numCols = myCifti->getNumberOfColumns(); const CiftiXMLOld& myXml = myCifti->getCiftiXMLOld(); int64_t dims[3]; vector > sform; if (!myXml.getVolumeDimsAndSForm(dims, sform)) { throw OperationException("no volume data in cifti file"); } if (!myRoi->matchesVolumeSpace(dims, sform)) { throw OperationException("volume roi doesn't match cifti volume space"); } vector scratch(numCols); vector myMap; myXml.getVolumeMapForColumns(myMap); int mapSize = myMap.size(); for (int i = 0; i < mapSize; ++i) { if (myRoi->getValue(myMap[i].m_ijk) > 0.0f) { ++accumCount; myCifti->getRow(scratch.data(), myMap[i].m_ciftiIndex); for (int j = 0; j < numCols; ++j) { accum[j] += scratch[j]; } } } } workbench-1.1.1/src/Operations/OperationCiftiROIAverage.h000066400000000000000000000035061255417355300233610ustar00rootroot00000000000000#ifndef __OPERATION_CIFTI_ROI_AVERAGE_H__ #define __OPERATION_CIFTI_ROI_AVERAGE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" #include "StructureEnum.h" #include namespace caret { class CiftiFile; class MetricFile; class VolumeFile; class OperationCiftiROIAverage : public AbstractOperation { static void processSurfaceComponent(const CiftiFile* myCifti, const StructureEnum::Enum& myStruct, const MetricFile* myRoi, std::vector& accum, int& accumCount); static void processVolume(const CiftiFile* myCifti, const VolumeFile* myRoi, std::vector& accum, int& accumCount); public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationCiftiROIAverage; } #endif //__OPERATION_CIFTI_ROI_AVERAGE_H__ workbench-1.1.1/src/Operations/OperationCiftiResampleDconnMemory.cxx000066400000000000000000000502011255417355300257250ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationCiftiResampleDconnMemory.h" #include "OperationException.h" #include "AffineFile.h" #include "AlgorithmCiftiResample.h" #include "CiftiFile.h" #include "MetricFile.h" #include "SurfaceFile.h" #include "WarpfieldFile.h" using namespace caret; using namespace std; AString OperationCiftiResampleDconnMemory::getCommandSwitch() { return "-cifti-resample-dconn-memory"; } AString OperationCiftiResampleDconnMemory::getShortDescription() { return "USE LOTS OF MEMORY TO RESAMPLE DCONN"; } OperationParameters* OperationCiftiResampleDconnMemory::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiParameter(1, "cifti-in", "the cifti file to resample"); ret->addCiftiParameter(3, "cifti-template", "a cifti file containing the cifti space to resample to"); ret->addStringParameter(4, "template-direction", "the direction of the template to use as the resampling space"); ret->addStringParameter(5, "surface-method", "specify a surface resampling method"); ret->addStringParameter(6, "volume-method", "specify a volume interpolation method"); ret->addCiftiOutputParameter(7, "cifti-out", "the output cifti file"); ret->createOptionalParameter(8, "-surface-largest", "use largest weight instead of weighted average when doing surface resampling"); OptionalParameter* volDilateOpt = ret->createOptionalParameter(9, "-volume-predilate", "dilate the volume components before resampling"); volDilateOpt->addDoubleParameter(1, "dilate-mm", "distance, in mm, to dilate"); OptionalParameter* surfDilateOpt = ret->createOptionalParameter(10, "-surface-postdilate", "dilate the surface components after resampling"); surfDilateOpt->addDoubleParameter(1, "dilate-mm", "distance, in mm, to dilate"); OptionalParameter* affineOpt = ret->createOptionalParameter(11, "-affine", "use an affine transformation on the volume components"); affineOpt->addStringParameter(1, "affine-file", "the affine file to use"); OptionalParameter* flirtOpt = affineOpt->createOptionalParameter(2, "-flirt", "MUST be used if affine is a flirt affine"); flirtOpt->addStringParameter(1, "source-volume", "the source volume used when generating the affine"); flirtOpt->addStringParameter(2, "target-volume", "the target volume used when generating the affine"); OptionalParameter* warpfieldOpt = ret->createOptionalParameter(12, "-warpfield", "use a warpfield on the volume components"); warpfieldOpt->addStringParameter(1, "warpfield", "the warpfield to use"); OptionalParameter* fnirtOpt = warpfieldOpt->createOptionalParameter(2, "-fnirt", "MUST be used if using a fnirt warpfield"); fnirtOpt->addStringParameter(1, "source-volume", "the source volume used when generating the warpfield"); OptionalParameter* leftSpheresOpt = ret->createOptionalParameter(13, "-left-spheres", "specify spheres for left surface resampling"); leftSpheresOpt->addSurfaceParameter(1, "current-sphere", "a sphere with the same mesh as the current left surface"); leftSpheresOpt->addSurfaceParameter(2, "new-sphere", "a sphere with the new left mesh that is in register with the current sphere"); OptionalParameter* leftAreaSurfsOpt = leftSpheresOpt->createOptionalParameter(3, "-left-area-surfs", "specify left surfaces to do vertex area correction based on"); leftAreaSurfsOpt->addSurfaceParameter(1, "current-area", "a relevant left anatomical surface with current mesh"); leftAreaSurfsOpt->addSurfaceParameter(2, "new-area", "a relevant left anatomical surface with new mesh"); OptionalParameter* leftAreaMetricsOpt = leftSpheresOpt->createOptionalParameter(4, "-left-area-metrics", "specify left vertex area metrics to do area correction based on"); leftAreaMetricsOpt->addMetricParameter(1, "current-area", "a metric file with vertex areas for the current mesh"); leftAreaMetricsOpt->addMetricParameter(2, "new-area", "a metric file with vertex areas for the new mesh"); OptionalParameter* rightSpheresOpt = ret->createOptionalParameter(14, "-right-spheres", "specify spheres for right surface resampling"); rightSpheresOpt->addSurfaceParameter(1, "current-sphere", "a sphere with the same mesh as the current right surface"); rightSpheresOpt->addSurfaceParameter(2, "new-sphere", "a sphere with the new right mesh that is in register with the current sphere"); OptionalParameter* rightAreaSurfsOpt = rightSpheresOpt->createOptionalParameter(3, "-right-area-surfs", "specify right surfaces to do vertex area correction based on"); rightAreaSurfsOpt->addSurfaceParameter(1, "current-area", "a relevant right anatomical surface with current mesh"); rightAreaSurfsOpt->addSurfaceParameter(2, "new-area", "a relevant right anatomical surface with new mesh"); OptionalParameter* rightAreaMetricsOpt = rightSpheresOpt->createOptionalParameter(4, "-right-area-metrics", "specify right vertex area metrics to do area correction based on"); rightAreaMetricsOpt->addMetricParameter(1, "current-area", "a metric file with vertex areas for the current mesh"); rightAreaMetricsOpt->addMetricParameter(2, "new-area", "a metric file with vertex areas for the new mesh"); OptionalParameter* cerebSpheresOpt = ret->createOptionalParameter(15, "-cerebellum-spheres", "specify spheres for cerebellum surface resampling"); cerebSpheresOpt->addSurfaceParameter(1, "current-sphere", "a sphere with the same mesh as the current cerebellum surface"); cerebSpheresOpt->addSurfaceParameter(2, "new-sphere", "a sphere with the new cerebellum mesh that is in register with the current sphere"); OptionalParameter* cerebAreaSurfsOpt = cerebSpheresOpt->createOptionalParameter(3, "-cerebellum-area-surfs", "specify cerebellum surfaces to do vertex area correction based on"); cerebAreaSurfsOpt->addSurfaceParameter(1, "current-area", "a relevant cerebellum anatomical surface with current mesh"); cerebAreaSurfsOpt->addSurfaceParameter(2, "new-area", "a relevant cerebellum anatomical surface with new mesh"); OptionalParameter* cerebAreaMetricsOpt = cerebSpheresOpt->createOptionalParameter(4, "-cerebellum-area-metrics", "specify cerebellum vertex area metrics to do area correction based on"); cerebAreaMetricsOpt->addMetricParameter(1, "current-area", "a metric file with vertex areas for the current mesh"); cerebAreaMetricsOpt->addMetricParameter(2, "new-area", "a metric file with vertex areas for the new mesh"); AString myHelpText = AString("This command does the same thing as running -cifti-resample twice, but uses memory up to approximately 2x the size that the intermediate file would be. ") + "This is because the intermediate dconn is kept in memory, rather than written to disk, " + "and the components before and after resampling/dilation have to be in memory at the same time during the relevant computation. " + "If spheres are not specified for a surface structure which exists in the cifti files, its data is copied without resampling or dilation. " + "Dilation is done with the 'nearest' method, and is done on for surface data. " + "Volume components are padded before dilation so that dilation doesn't run into the edge of the component bounding box.\n\n" + "The argument must be one of the following:\n\n" + "CUBIC\nENCLOSING_VOXEL\nTRILINEAR\n\n" + "The argument must be one of the following:\n\n"; vector allEnums; SurfaceResamplingMethodEnum::getAllEnums(allEnums); for (int i = 0; i < (int)allEnums.size(); ++i) { myHelpText += SurfaceResamplingMethodEnum::toName(allEnums[i]) + "\n"; } ret->setHelpText(myHelpText); return ret; } void OperationCiftiResampleDconnMemory::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); CiftiFile* myCiftiIn = myParams->getCifti(1); CiftiFile* myTemplate = myParams->getCifti(3); AString myTemplDirString = myParams->getString(4); int templateDir = -1; if (myTemplDirString == "ROW") { templateDir = CiftiXML::ALONG_ROW; } else { if (myTemplDirString == "COLUMN") { templateDir = CiftiXML::ALONG_COLUMN; } else { throw OperationException("unrecognized template direction string, use ROW or COLUMN"); } } bool ok = false; SurfaceResamplingMethodEnum::Enum mySurfMethod = SurfaceResamplingMethodEnum::fromName(myParams->getString(5), &ok); if (!ok) { throw OperationException("invalid surface resampling method name"); } AString myVolMethodString = myParams->getString(6); VolumeFile::InterpType myVolMethod = VolumeFile::CUBIC; if (myVolMethodString == "CUBIC") { myVolMethod = VolumeFile::CUBIC; } else if (myVolMethodString == "TRILINEAR") { myVolMethod = VolumeFile::TRILINEAR; } else if (myVolMethodString == "ENCLOSING_VOXEL") { myVolMethod = VolumeFile::ENCLOSING_VOXEL; } else { throw OperationException("unrecognized volume interpolation method"); } CiftiFile* myCiftiOut = myParams->getOutputCifti(7); bool surfLargest = myParams->getOptionalParameter(8)->m_present; float voldilatemm = -1.0f, surfdilatemm = -1.0f; OptionalParameter* volDilateOpt = myParams->getOptionalParameter(9); if (volDilateOpt->m_present) { voldilatemm = (float)volDilateOpt->getDouble(1); if (voldilatemm <= 0.0f) throw OperationException("dilation amount must be positive"); } OptionalParameter* surfDilateOpt = myParams->getOptionalParameter(10); if (surfDilateOpt->m_present) { surfdilatemm = (float)surfDilateOpt->getDouble(1); if (surfdilatemm <= 0.0f) throw OperationException("dilation amount must be positive"); } OptionalParameter* affineOpt = myParams->getOptionalParameter(11); OptionalParameter* warpfieldOpt = myParams->getOptionalParameter(12); if (affineOpt->m_present && warpfieldOpt->m_present) throw OperationException("you cannot specify both -affine and -warpfield"); AffineFile myAffine; WarpfieldFile myWarpfield; if (affineOpt->m_present) { OptionalParameter* flirtOpt = affineOpt->getOptionalParameter(2); if (flirtOpt->m_present) { myAffine.readFlirt(affineOpt->getString(1), flirtOpt->getString(1), flirtOpt->getString(2)); } else { myAffine.readWorld(affineOpt->getString(1)); } } if (warpfieldOpt->m_present) { OptionalParameter* fnirtOpt = warpfieldOpt->getOptionalParameter(2); if (fnirtOpt->m_present) { myWarpfield.readFnirt(warpfieldOpt->getString(1), fnirtOpt->getString(1)); } else { myWarpfield.readWorld(warpfieldOpt->getString(1)); } } SurfaceFile* curLeftSphere = NULL, *newLeftSphere = NULL; MetricFile* curLeftAreas = NULL, *newLeftAreas = NULL; MetricFile curLeftAreasTemp, newLeftAreasTemp; OptionalParameter* leftSpheresOpt = myParams->getOptionalParameter(13); if (leftSpheresOpt->m_present) { curLeftSphere = leftSpheresOpt->getSurface(1); newLeftSphere = leftSpheresOpt->getSurface(2); OptionalParameter* leftAreaSurfsOpt = leftSpheresOpt->getOptionalParameter(3); if (leftAreaSurfsOpt->m_present) { SurfaceFile* curAreaSurf = leftAreaSurfsOpt->getSurface(1); SurfaceFile* newAreaSurf = leftAreaSurfsOpt->getSurface(2); vector nodeAreasTemp; curAreaSurf->computeNodeAreas(nodeAreasTemp); curLeftAreasTemp.setNumberOfNodesAndColumns(curAreaSurf->getNumberOfNodes(), 1); curLeftAreasTemp.setValuesForColumn(0, nodeAreasTemp.data()); curLeftAreas = &curLeftAreasTemp; newAreaSurf->computeNodeAreas(nodeAreasTemp); newLeftAreasTemp.setNumberOfNodesAndColumns(newAreaSurf->getNumberOfNodes(), 1); newLeftAreasTemp.setValuesForColumn(0, nodeAreasTemp.data()); newLeftAreas = &newLeftAreasTemp; } OptionalParameter* leftAreaMetricsOpt = leftSpheresOpt->getOptionalParameter(4); if (leftAreaMetricsOpt->m_present) { if (leftAreaSurfsOpt->m_present) { throw OperationException("only one of -left-area-surfs and -left-area-metrics can be specified"); } curLeftAreas = leftAreaMetricsOpt->getMetric(1); newLeftAreas = leftAreaMetricsOpt->getMetric(2); } } SurfaceFile* curRightSphere = NULL, *newRightSphere = NULL; MetricFile* curRightAreas = NULL, *newRightAreas = NULL; MetricFile curRightAreasTemp, newRightAreasTemp; OptionalParameter* rightSpheresOpt = myParams->getOptionalParameter(14); if (rightSpheresOpt->m_present) { curRightSphere = rightSpheresOpt->getSurface(1); newRightSphere = rightSpheresOpt->getSurface(2); OptionalParameter* rightAreaSurfsOpt = rightSpheresOpt->getOptionalParameter(3); if (rightAreaSurfsOpt->m_present) { SurfaceFile* curAreaSurf = rightAreaSurfsOpt->getSurface(1); SurfaceFile* newAreaSurf = rightAreaSurfsOpt->getSurface(2); vector nodeAreasTemp; curAreaSurf->computeNodeAreas(nodeAreasTemp); curRightAreasTemp.setNumberOfNodesAndColumns(curAreaSurf->getNumberOfNodes(), 1); curRightAreasTemp.setValuesForColumn(0, nodeAreasTemp.data()); curRightAreas = &curRightAreasTemp; newAreaSurf->computeNodeAreas(nodeAreasTemp); newRightAreasTemp.setNumberOfNodesAndColumns(newAreaSurf->getNumberOfNodes(), 1); newRightAreasTemp.setValuesForColumn(0, nodeAreasTemp.data()); newRightAreas = &newRightAreasTemp; } OptionalParameter* rightAreaMetricsOpt = rightSpheresOpt->getOptionalParameter(4); if (rightAreaMetricsOpt->m_present) { if (rightAreaSurfsOpt->m_present) { throw OperationException("only one of -right-area-surfs and -right-area-metrics can be specified"); } curRightAreas = rightAreaMetricsOpt->getMetric(1); newRightAreas = rightAreaMetricsOpt->getMetric(2); } } SurfaceFile* curCerebSphere = NULL, *newCerebSphere = NULL; MetricFile* curCerebAreas = NULL, *newCerebAreas = NULL; MetricFile curCerebAreasTemp, newCerebAreasTemp; OptionalParameter* cerebSpheresOpt = myParams->getOptionalParameter(15); if (cerebSpheresOpt->m_present) { curCerebSphere = cerebSpheresOpt->getSurface(1); newCerebSphere = cerebSpheresOpt->getSurface(2); OptionalParameter* cerebAreaSurfsOpt = cerebSpheresOpt->getOptionalParameter(3); if (cerebAreaSurfsOpt->m_present) { SurfaceFile* curAreaSurf = cerebAreaSurfsOpt->getSurface(1); SurfaceFile* newAreaSurf = cerebAreaSurfsOpt->getSurface(2); vector nodeAreasTemp; curAreaSurf->computeNodeAreas(nodeAreasTemp); curCerebAreasTemp.setNumberOfNodesAndColumns(curAreaSurf->getNumberOfNodes(), 1); curCerebAreasTemp.setValuesForColumn(0, nodeAreasTemp.data()); curCerebAreas = &curCerebAreasTemp; newAreaSurf->computeNodeAreas(nodeAreasTemp); newCerebAreasTemp.setNumberOfNodesAndColumns(newAreaSurf->getNumberOfNodes(), 1); newCerebAreasTemp.setValuesForColumn(0, nodeAreasTemp.data()); newCerebAreas = &newCerebAreasTemp; } OptionalParameter* cerebAreaMetricsOpt = cerebSpheresOpt->getOptionalParameter(4); if (cerebAreaMetricsOpt->m_present) { if (cerebAreaSurfsOpt->m_present) { throw OperationException("only one of -cerebellum-area-surfs and -cerebellum-area-metrics can be specified"); } curCerebAreas = cerebAreaMetricsOpt->getMetric(1); newCerebAreas = cerebAreaMetricsOpt->getMetric(2); } } pair colErrors = AlgorithmCiftiResample::checkForErrors(myCiftiIn, CiftiXML::ALONG_COLUMN, myTemplate, templateDir, mySurfMethod, curLeftSphere, newLeftSphere, curLeftAreas, newLeftAreas, curRightSphere, newRightSphere, curRightAreas, newRightAreas, curCerebSphere, newCerebSphere, curCerebAreas, newCerebAreas); pair rowErrors = AlgorithmCiftiResample::checkForErrors(myCiftiIn, CiftiXML::ALONG_ROW, myTemplate, templateDir, mySurfMethod, curLeftSphere, newLeftSphere, curLeftAreas, newLeftAreas, curRightSphere, newRightSphere, curRightAreas, newRightAreas, curCerebSphere, newCerebSphere, curCerebAreas, newCerebAreas); ok = true; AString message; if (rowErrors.first) { message += "Error in resampling along row: " + rowErrors.second; ok = false; } if (colErrors.first) { if (!ok) message += "\n"; message = "Error in resampling along column: " + colErrors.second; ok = false; } if (!ok) { throw OperationException(message); } CiftiFile tempCifti; //TSC: resampling along column first causes it to hit peak memory usage earlier if (warpfieldOpt->m_present) { AlgorithmCiftiResample(myProgObj, myCiftiIn, CiftiXML::ALONG_COLUMN, myTemplate, templateDir, mySurfMethod, myVolMethod, &tempCifti, surfLargest, voldilatemm, surfdilatemm, myWarpfield.getWarpfield(), curLeftSphere, newLeftSphere, curLeftAreas, newLeftAreas, curRightSphere, newRightSphere, curRightAreas, newRightAreas, curCerebSphere, newCerebSphere, curCerebAreas, newCerebAreas); AlgorithmCiftiResample(myProgObj, &tempCifti, CiftiXML::ALONG_ROW, myTemplate, templateDir, mySurfMethod, myVolMethod, myCiftiOut, surfLargest, voldilatemm, surfdilatemm, myWarpfield.getWarpfield(), curLeftSphere, newLeftSphere, curLeftAreas, newLeftAreas, curRightSphere, newRightSphere, curRightAreas, newRightAreas, curCerebSphere, newCerebSphere, curCerebAreas, newCerebAreas); } else {//rely on AffineFile() being the identity transform for if neither option is specified AlgorithmCiftiResample(myProgObj, myCiftiIn, CiftiXML::ALONG_COLUMN, myTemplate, templateDir, mySurfMethod, myVolMethod, &tempCifti, surfLargest, voldilatemm, surfdilatemm, myAffine.getMatrix(), curLeftSphere, newLeftSphere, curLeftAreas, newLeftAreas, curRightSphere, newRightSphere, curRightAreas, newRightAreas, curCerebSphere, newCerebSphere, curCerebAreas, newCerebAreas); AlgorithmCiftiResample(myProgObj, &tempCifti, CiftiXML::ALONG_ROW, myTemplate, templateDir, mySurfMethod, myVolMethod, myCiftiOut, surfLargest, voldilatemm, surfdilatemm, myAffine.getMatrix(), curLeftSphere, newLeftSphere, curLeftAreas, newLeftAreas, curRightSphere, newRightSphere, curRightAreas, newRightAreas, curCerebSphere, newCerebSphere, curCerebAreas, newCerebAreas); } } workbench-1.1.1/src/Operations/OperationCiftiResampleDconnMemory.h000066400000000000000000000027271255417355300253640ustar00rootroot00000000000000#ifndef __OPERATION_CIFTI_RESAMPLE_DCONN_MEMORY_H__ #define __OPERATION_CIFTI_RESAMPLE_DCONN_MEMORY_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationCiftiResampleDconnMemory : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationCiftiResampleDconnMemory; } #endif //__OPERATION_CIFTI_RESAMPLE_DCONN_MEMORY_H__ workbench-1.1.1/src/Operations/OperationCiftiSeparateAll.cxx000066400000000000000000000156741255417355300242160ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AlgorithmCiftiSeparate.h" #include "CiftiFile.h" #include "MetricFile.h" #include "OperationCiftiSeparateAll.h" #include "OperationException.h" #include "VolumeFile.h" using namespace caret; using namespace std; AString OperationCiftiSeparateAll::getCommandSwitch() { return "-cifti-separate-all"; } AString OperationCiftiSeparateAll::getShortDescription() { return "DEPRECATED: use -cifti-separate"; } OperationParameters* OperationCiftiSeparateAll::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiParameter(1, "cifti-in", "the cifti to split"); OptionalParameter* leftOpt = ret->createOptionalParameter(2, "-left", "output the left surface data"); leftOpt->addMetricOutputParameter(1, "left-metric", "the output metric for the left surface"); OptionalParameter* leftRoiOpt = leftOpt->createOptionalParameter(2, "-left-roi", "output the ROI for the left surface data"); leftRoiOpt->addMetricOutputParameter(1, "left-roi-metric", "output metric for the left ROI"); OptionalParameter* rightOpt = ret->createOptionalParameter(3, "-right", "output the right surface data"); rightOpt->addMetricOutputParameter(1, "right-metric", "the output metric for the right surface"); OptionalParameter* rightRoiOpt = rightOpt->createOptionalParameter(2, "-right-roi", "output the ROI for the right surface data"); rightRoiOpt->addMetricOutputParameter(1, "right-roi-metric", "output metric for the right ROI"); OptionalParameter* cerebOpt = ret->createOptionalParameter(4, "-cerebellum", "output the cerebellum surface data"); cerebOpt->addMetricOutputParameter(1, "cerebellum-metric", "the output metric for the cerebellum surface"); OptionalParameter* cerebRoiOpt = cerebOpt->createOptionalParameter(2, "-cerebellum-roi", "output the ROI for the cerebellum surface data"); cerebRoiOpt->addMetricOutputParameter(1, "cerebellum-roi-metric", "output metric for the cerebellum ROI"); OptionalParameter* volOpt = ret->createOptionalParameter(5, "-volume", "output the voxel data"); volOpt->addVolumeOutputParameter(1, "volume-out", "output volume file"); OptionalParameter* volRoiOpt = volOpt->createOptionalParameter(2, "-volume-roi", "output the combined ROI for the volume data"); volRoiOpt->addVolumeOutputParameter(1, "volume-roi-out", "output volume for ROI"); OptionalParameter* dirOpt = ret->createOptionalParameter(6, "-direction", "choose the direction to separate (default COLUMN)"); dirOpt->addStringParameter(1, "direction", "which direction to separate into components, ROW or COLUMN"); ret->setHelpText( AString("DEPRECATED: this command may be removed in a future release, use -cifti-separate.\n\n") + "All volume components are put together into one volume, the boundaries between volume components are not output by this command. " + "The COLUMN direction (default) is usually what you want, ROW will only work for dconn. " + "Using this command with -volume will usually take (much) more memory than the cifti file, since it must create the whole volume, rather than just the included voxels." ); return ret; } void OperationCiftiSeparateAll::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); CiftiFile* myCifti = myParams->getCifti(1); int myDir = CiftiXMLOld::ALONG_COLUMN; OptionalParameter* dirOpt = myParams->getOptionalParameter(6); if (dirOpt->m_present) { AString dirName = dirOpt->getString(1); if (dirName == "ROW") { myDir = CiftiXMLOld::ALONG_ROW; } else { if (dirName != "COLUMN") { throw OperationException("direction must be 'ROW' or 'COLUMN'"); } } } OptionalParameter* leftOpt = myParams->getOptionalParameter(2); if (leftOpt->m_present) { MetricFile* outData = leftOpt->getOutputMetric(1); MetricFile* outRoi = NULL; OptionalParameter* roiOpt = leftOpt->getOptionalParameter(2); if (roiOpt->m_present) { outRoi = roiOpt->getOutputMetric(1); } processSurfaceComponent(myCifti, StructureEnum::CORTEX_LEFT, myDir, outData, outRoi); } OptionalParameter* rightOpt = myParams->getOptionalParameter(3); if (rightOpt->m_present) { MetricFile* outData = rightOpt->getOutputMetric(1); MetricFile* outRoi = NULL; OptionalParameter* roiOpt = rightOpt->getOptionalParameter(2); if (roiOpt->m_present) { outRoi = roiOpt->getOutputMetric(1); } processSurfaceComponent(myCifti, StructureEnum::CORTEX_RIGHT, myDir, outData, outRoi); } OptionalParameter* cerebOpt = myParams->getOptionalParameter(4); if (cerebOpt->m_present) { MetricFile* outData = cerebOpt->getOutputMetric(1); MetricFile* outRoi = NULL; OptionalParameter* roiOpt = cerebOpt->getOptionalParameter(2); if (roiOpt->m_present) { outRoi = roiOpt->getOutputMetric(1); } processSurfaceComponent(myCifti, StructureEnum::CEREBELLUM, myDir, outData, outRoi); } OptionalParameter* volOpt = myParams->getOptionalParameter(5); if (volOpt->m_present) { VolumeFile* outData = volOpt->getOutputVolume(1); VolumeFile* outRoi = NULL; OptionalParameter* roiOpt = volOpt->getOptionalParameter(2); if (roiOpt->m_present) { outRoi = roiOpt->getOutputVolume(1); } processVolume(myCifti, myDir, outData, outRoi); } } void OperationCiftiSeparateAll::processSurfaceComponent(const CiftiFile* myCifti, const StructureEnum::Enum& myStruct, const int& myDir, MetricFile* outData, MetricFile* outROI) { AlgorithmCiftiSeparate(NULL, myCifti, myDir, myStruct, outData, outROI); } void OperationCiftiSeparateAll::processVolume(const CiftiFile* myCifti, const int& myDir, VolumeFile* outData, VolumeFile* outROI) { int64_t offset[3]; AlgorithmCiftiSeparate(NULL, myCifti, myDir, outData, offset, outROI, false); } workbench-1.1.1/src/Operations/OperationCiftiSeparateAll.h000066400000000000000000000033541255417355300236330ustar00rootroot00000000000000#ifndef __OPERATION_CIFTI_SEPARATE_ALL_H__ #define __OPERATION_CIFTI_SEPARATE_ALL_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" #include "StructureEnum.h" namespace caret { class OperationCiftiSeparateAll : public AbstractOperation { static void processSurfaceComponent(const CiftiFile* myCifti, const StructureEnum::Enum& myStruct, const int& myDir, MetricFile* outData, MetricFile* outROI = NULL); static void processVolume(const CiftiFile* myCifti, const int& myDir, VolumeFile* outData, VolumeFile* outROI = NULL); public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationCiftiSeparateAll; } #endif //__OPERATION_CIFTI_SEPARATE_ALL_H__ workbench-1.1.1/src/Operations/OperationCiftiStats.cxx000066400000000000000000000226431255417355300231110ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationCiftiStats.h" #include "OperationException.h" #include "CiftiFile.h" #include "ReductionOperation.h" #include #include #include #include #include #include using namespace caret; using namespace std; AString OperationCiftiStats::getCommandSwitch() { return "-cifti-stats"; } AString OperationCiftiStats::getShortDescription() { return "STATISTICS ALONG CIFTI COLUMNS"; } OperationParameters* OperationCiftiStats::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiParameter(1, "cifti-in", "the input cifti"); OptionalParameter* reduceOpt = ret->createOptionalParameter(2, "-reduce", "use a reduction operation"); reduceOpt->addStringParameter(1, "operation", "the reduction operation"); OptionalParameter* percentileOpt = ret->createOptionalParameter(3, "-percentile", "give the value at a percentile"); percentileOpt->addDoubleParameter(1, "percent", "the percentile to find"); OptionalParameter* columnOpt = ret->createOptionalParameter(4, "-column", "only display output for one column"); columnOpt->addIntegerParameter(1, "column", "the column index (starting from 1)"); OptionalParameter* roiOpt = ret->createOptionalParameter(5, "-roi", "only consider data inside an roi"); roiOpt->addCiftiParameter(1, "roi-cifti", "the roi, as a cifti file"); roiOpt->createOptionalParameter(2, "-match-maps", "each column of input uses the corresponding column from the roi file"); ret->createOptionalParameter(6, "-show-map-name", "print column index and name before each output"); ret->setHelpText( AString("For each column of the input, a single number is printed, resulting from the specified reduction or percentile operation. ") + "Use -column to only give output for a single column. " + "Use -roi to consider only the data within a region. " + "Exactly one of -reduce or -percentile must be specified.\n\n" + "The argument to the -reduce option must be one of the following:\n\n" + ReductionOperation::getHelpInfo()); return ret; } namespace { float reduce(const vector& data, const ReductionEnum::Enum& myop, const vector& roiData) { if (roiData.empty()) { return ReductionOperation::reduce(data.data(), data.size(), myop); } else { int64_t numElems = (int64_t)data.size(); CaretAssert(numElems == (int64_t)roiData.size()); vector toUse; toUse.reserve(numElems); for (int64_t i = 0; i < numElems; ++i) { if (roiData[i] > 0.0f) { toUse.push_back(data[i]); } } if (toUse.empty()) throw OperationException("roi column is empty"); return ReductionOperation::reduce(toUse.data(), toUse.size(), myop); } } float percentile(const vector& data, const float& percent, const vector& roiData) { CaretAssert(percent >= 0.0f && percent <= 100.0f); vector toUse; if (roiData.empty()) { toUse = data; } else { int64_t numElems = (int64_t)data.size(); CaretAssert(numElems == (int64_t)roiData.size()); toUse.reserve(numElems); for (int i = 0; i < numElems; ++i) { if (roiData[i] > 0.0f) { toUse.push_back(data[i]); } } } if (toUse.empty()) throw OperationException("roi is empty"); sort(toUse.begin(), toUse.end()); const float index = percent / 100.0f * (toUse.size() - 1); if (index <= 0) return toUse[0]; if (index >= toUse.size() - 1) return toUse.back(); float ipart, fpart; fpart = modf(index, &ipart); return (1.0f - fpart) * toUse[(int)ipart] + fpart * toUse[((int)ipart) + 1]; } } void OperationCiftiStats::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); CiftiFile* myInput = myParams->getCifti(1); const CiftiXML& myXML = myInput->getCiftiXML(); if (myXML.getNumberOfDimensions() != 2) throw OperationException("only 2D cifti are supported in this command"); int64_t numCols = myXML.getDimensionLength(CiftiXML::ALONG_ROW); int64_t colLength = myXML.getDimensionLength(CiftiXML::ALONG_COLUMN); OptionalParameter* reduceOpt = myParams->getOptionalParameter(2); OptionalParameter* percentileOpt = myParams->getOptionalParameter(3); if (reduceOpt->m_present == percentileOpt->m_present)//use == as logical xor { throw OperationException("you must use exactly one of -reduce or -percentile"); } ReductionEnum::Enum myop = ReductionEnum::INVALID; if (reduceOpt->m_present) { bool ok = false; myop = ReductionEnum::fromName(reduceOpt->getString(1), &ok); if (!ok) throw OperationException("unrecognized reduction operation: " + reduceOpt->getString(1)); } float percent = 0.0f; if (percentileOpt->m_present) { percent = (float)percentileOpt->getDouble(1);//use not within range to trap NaNs, just in case if (!(percent >= 0.0f && percent <= 100.0f)) throw OperationException("percentile must be between 0 and 100"); } int useColumn = -1; OptionalParameter* columnOpt = myParams->getOptionalParameter(4); if (columnOpt->m_present) { useColumn = columnOpt->getInteger(1) - 1; if (useColumn < 0 || useColumn >= numCols) throw OperationException("invalid column specified"); } vector roiData; bool matchColumnMode = false; CiftiFile* roiCifti = NULL; OptionalParameter* roiOpt = myParams->getOptionalParameter(5); if (roiOpt->m_present) { roiCifti = roiOpt->getCifti(1); if (!roiCifti->getCiftiXML().getMap(CiftiXML::ALONG_COLUMN)->approximateMatch(*(myXML.getMap(CiftiXML::ALONG_COLUMN)))) { throw OperationException("roi cifti does not match input cifti along columns"); } roiData.resize(colLength); if (roiOpt->getOptionalParameter(2)->m_present) { if (myXML.getMap(CiftiXML::ALONG_ROW)->getLength() != roiCifti->getCiftiXML().getMap(CiftiXML::ALONG_ROW)->getLength()) { throw OperationException("-match-maps specified, but roi has different number of columns than input"); } matchColumnMode = true; } else { roiCifti->getColumn(roiData.data(), 0);//we are only getting one column, so go ahead and do it in on-disk mode } } bool showMapName = myParams->getOptionalParameter(6)->m_present; const CiftiMappingType* rowMap = myXML.getMap(CiftiXML::ALONG_ROW); vector colScratch(colLength); if (useColumn == -1) { myInput->convertToInMemory();//we will be getting all columns, so read it all in first if (matchColumnMode) { roiCifti->convertToInMemory();//ditto } for (int i = 0; i < numCols; ++i) { myInput->getColumn(colScratch.data(), i); if (matchColumnMode) { roiCifti->getColumn(roiData.data(), i); } float result; if (reduceOpt->m_present) { result = reduce(colScratch, myop, roiData); } else { CaretAssert(percentileOpt->m_present); result = percentile(colScratch, percent, roiData); } if (showMapName) { cout << AString::number(i + 1) << ": " << rowMap->getIndexName(i) << ": "; } stringstream resultsstr; resultsstr << setprecision(7) << result; cout << resultsstr.str() << endl; } } else { myInput->getColumn(colScratch.data(), useColumn); if (matchColumnMode) { roiCifti->getColumn(roiData.data(), useColumn); } float result; if (reduceOpt->m_present) { result = reduce(colScratch, myop, roiData); } else { CaretAssert(percentileOpt->m_present); result = percentile(colScratch, percent, roiData); } if (showMapName) { cout << AString::number(useColumn + 1) << ": " << rowMap->getIndexName(useColumn) << ": "; } stringstream resultsstr; resultsstr << setprecision(7) << result; cout << resultsstr.str() << endl; } } workbench-1.1.1/src/Operations/OperationCiftiStats.h000066400000000000000000000025751255417355300225400ustar00rootroot00000000000000#ifndef __OPERATION_CIFTI_STATS_H__ #define __OPERATION_CIFTI_STATS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationCiftiStats : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationCiftiStats; } #endif //__OPERATION_CIFTI_STATS_H__ workbench-1.1.1/src/Operations/OperationCiftiWeightedStats.cxx000066400000000000000000000664251255417355300246000ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationCiftiWeightedStats.h" #include "OperationException.h" #include "CaretHeap.h" #include "CiftiFile.h" #include "MetricFile.h" #include "SurfaceFile.h" #include #include #include #include #include using namespace caret; using namespace std; AString OperationCiftiWeightedStats::getCommandSwitch() { return "-cifti-weighted-stats"; } AString OperationCiftiWeightedStats::getShortDescription() { return "WEIGHTED STATISTICS ALONG CIFTI COLUMNS"; } OperationParameters* OperationCiftiWeightedStats::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addCiftiParameter(1, "cifti-in", "the input cifti"); OptionalParameter* spatialWeightOpt = ret->createOptionalParameter(2, "-spatial-weights", "use vertex area and voxel volume as weights"); OptionalParameter* leftAreaSurfOpt = spatialWeightOpt->createOptionalParameter(1, "-left-area-surf", "use a surface for left vertex areas"); leftAreaSurfOpt->addSurfaceParameter(1, "left-surf", "the left surface to use"); OptionalParameter* rightAreaSurfOpt = spatialWeightOpt->createOptionalParameter(2, "-right-area-surf", "use a surface for right vertex areas"); rightAreaSurfOpt->addSurfaceParameter(1, "right-surf", "the right surface to use"); OptionalParameter* cerebAreaSurfOpt = spatialWeightOpt->createOptionalParameter(3, "-cerebellum-area-surf", "use a surface for cerebellum vertex areas"); cerebAreaSurfOpt->addSurfaceParameter(1, "cerebellum-surf", "the cerebellum surface to use"); OptionalParameter* leftAreaMetricOpt = spatialWeightOpt->createOptionalParameter(4, "-left-area-metric", "use a metric file for left vertex areas"); leftAreaMetricOpt->addMetricParameter(1, "left-metric", "metric file containing left vertex areas"); OptionalParameter* rightAreaMetricOpt = spatialWeightOpt->createOptionalParameter(5, "-right-area-metric", "use a metric file for right vertex areas"); rightAreaMetricOpt->addMetricParameter(1, "right-metric", "metric file containing right vertex areas"); OptionalParameter* cerebAreaMetricOpt = spatialWeightOpt->createOptionalParameter(6, "-cerebellum-area-metric", "use a metric file for cerebellum vertex areas"); cerebAreaMetricOpt->addMetricParameter(1, "cerebellum-metric", "metric file containing cerebellum vertex areas"); OptionalParameter* ciftiWeightOpt = ret->createOptionalParameter(3, "-cifti-weights", "use a cifti file containing weights"); ciftiWeightOpt->addCiftiParameter(1, "weight-cifti", "the weights to use, as a cifti file"); OptionalParameter* columnOpt = ret->createOptionalParameter(4, "-column", "only display output for one column"); columnOpt->addIntegerParameter(1, "column", "the column to use (1-based)"); OptionalParameter* roiOpt = ret->createOptionalParameter(5, "-roi", "only consider data inside an roi"); roiOpt->addCiftiParameter(1, "roi-cifti", "the roi, as a cifti file"); roiOpt->createOptionalParameter(2, "-match-maps", "each column of input uses the corresponding column from the roi file"); ret->createOptionalParameter(6, "-mean", "compute weighted mean"); OptionalParameter* stdevOpt = ret->createOptionalParameter(7, "-stdev", "compute weighted standard deviation"); stdevOpt->createOptionalParameter(1, "-sample", "estimate population stdev from the sample"); OptionalParameter* percentileOpt = ret->createOptionalParameter(8, "-percentile", "compute weighted percentile"); percentileOpt->addDoubleParameter(1, "percent", "the percentile to find"); ret->createOptionalParameter(9, "-sum", "compute weighted sum"); ret->createOptionalParameter(10, "-show-map-name", "print map index and name before each output"); ret->setHelpText( AString("If the mapping along column is brain models, for each column of the input, the specified operation is done on each surface and across all voxels, and the results are printed separately. ") + "For other mapping types, the operation is done on each column, and one number per map is printed. " + "Exactly one of -spatial-weights or -cifti-weights must be specified. " + "Use -column to only give output for a single column. " + "Use -roi to consider only the data within a region. " + "Exactly one of -mean, -stdev, -percentile or -sum must be specified.\n\n" + "Using -sum with -spatial-weights (or with -cifti-weights and a cifti containing weights of similar meaning) is equivalent to integrating with respect to area and volume." ); return ret; } namespace { enum OperationType { MEAN, STDEV, SAMPSTDEV, PERCENTILE, SUM }; float doOperation(const float* data, const float* weights, const int64_t& numElements, const OperationType& myop, const float* roiData, const float& argument) {//argument is only used for percentile currently if (roiData != NULL) { bool haveData = false; for (int64_t i = 0; i < numElements; ++i) { if (weights[i] > 0.0f) { haveData = true; break; } } if (!haveData) throw OperationException("roi column is empty"); } switch(myop) { case SUM: case MEAN: case STDEV: case SAMPSTDEV://these all start the same way { double accum = 0.0, weightsum = 0.0; for (int64_t i = 0; i < numElements; ++i) { if (roiData == NULL || roiData[i] > 0.0f) { accum += data[i] * weights[i]; weightsum += weights[i]; } } if (myop == SUM) return accum; const float mean = accum / weightsum; if (myop == MEAN) return mean; accum = 0.0; double weightsum2 = 0.0;//for weighted sample stdev for (int64_t i = 0; i < numElements; ++i) { if (roiData == NULL || roiData[i] > 0.0f) { float tempf = data[i] - mean; accum += weights[i] * tempf * tempf; weightsum2 += weights[i] * weights[i]; } } if (myop == STDEV) return sqrt(accum / weightsum); CaretAssert(myop == SAMPSTDEV); return sqrt(accum / (weightsum - weightsum2 / weightsum));//http://en.wikipedia.org/wiki/Weighted_arithmetic_mean#Weighted_sample_variance } case PERCENTILE: { CaretAssert(argument >= 0.0f && argument <= 100.0f); CaretSimpleMinHeap sorter; double weightaccum = 0.0;//double will usually prevent adding weights in a different order from getting a different answer for (int64_t i = 0; i < numElements; ++i) { if (roiData == NULL || roiData[i] > 0.0f) { if (weights[i] < 0.0f) throw OperationException("negative weights not allowed in weighted percentile"); weightaccum += weights[i]; sorter.push(weights[i], data[i]);//sort by value, so the key is the data } } int64_t numUse = sorter.size(); if (numUse == 1)//would need special handling anyway, so get it early { float ret; sorter.top(&ret); return ret; } float targetWeight = argument / 100.0f * weightaccum; float lastData, nextData; float lastWeight = sorter.pop(&lastData); weightaccum = lastWeight; float nextWeight = sorter.top(&nextData); int64_t position = 1;//because the first and last sections get special treatment to not have flat ends on the function while (weightaccum + nextWeight * 0.5f < targetWeight && sorter.size() > 1) { ++position; sorter.pop(); weightaccum += nextWeight; lastWeight = nextWeight; lastData = nextData; nextWeight = sorter.top(&nextData); } if (targetWeight < weightaccum) { if (position == 1) {//stretch interpolation at first position to the edge return lastData + (nextData - lastData) * 0.5f * ((targetWeight - weightaccum) / lastWeight + 1.0f); } else { return lastData + (nextData - lastData) * 0.5f * ((targetWeight - weightaccum) / (lastWeight * 0.5f) + 1.0f); } } else { if (position == numUse - 1) {//ditto return (lastData + nextData) * 0.5f + (nextData - lastData) * 0.5f * (targetWeight - weightaccum) / nextWeight; } else { return (lastData + nextData) * 0.5f + (nextData - lastData) * 0.5f * (targetWeight - weightaccum) / (nextWeight * 0.5f); } } } } CaretAssert(false);//make sure execution never actually reaches end of function throw OperationException("internal error in weighted stats"); } } void OperationCiftiWeightedStats::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); CiftiFile* myInput = myParams->getCifti(1); const CiftiXML& myXML = myInput->getCiftiXML(); if (myXML.getNumberOfDimensions() != 2) throw OperationException("only 2D cifti are supported in this command"); int64_t numCols = myXML.getDimensionLength(CiftiXML::ALONG_ROW); int64_t colLength = myXML.getDimensionLength(CiftiXML::ALONG_COLUMN); OptionalParameter* spatialWeightOpt = myParams->getOptionalParameter(2); OptionalParameter* ciftiWeightOpt = myParams->getOptionalParameter(3); if (spatialWeightOpt->m_present == ciftiWeightOpt->m_present)//use == as logical xnor { throw OperationException("you must use exactly one of -spatial-weights or -cifti-weights"); } vector combinedWeights(colLength); if (spatialWeightOpt->m_present) { if (myXML.getMappingType(CiftiXML::ALONG_COLUMN) != CiftiMappingType::BRAIN_MODELS) throw OperationException("spatial weights require a brain models mapping"); CiftiBrainModelsMap myDenseMap = myXML.getBrainModelsMap(CiftiXML::ALONG_COLUMN); OptionalParameter* leftSurfOpt = spatialWeightOpt->getOptionalParameter(1); OptionalParameter* rightSurfOpt = spatialWeightOpt->getOptionalParameter(2); OptionalParameter* cerebSurfOpt = spatialWeightOpt->getOptionalParameter(3); OptionalParameter* leftMetricOpt = spatialWeightOpt->getOptionalParameter(4); OptionalParameter* rightMetricOpt = spatialWeightOpt->getOptionalParameter(5); OptionalParameter* cerebMetricOpt = spatialWeightOpt->getOptionalParameter(6); if (leftSurfOpt->m_present && leftMetricOpt->m_present) throw OperationException("only one of -left-area-surf and -left-area-metric may be specified"); if (rightSurfOpt->m_present && rightMetricOpt->m_present) throw OperationException("only one of -right-area-surf and -right-area-metric may be specified"); if (cerebSurfOpt->m_present && cerebMetricOpt->m_present) throw OperationException("only one of -cerebellum-area-surf and -cerebellum-area-metric may be specified"); vector surfStructs = myDenseMap.getSurfaceStructureList(); int numSurf = (int)surfStructs.size(); for (int i = 0; i < numSurf; ++i) { OptionalParameter* surfOpt = NULL, *metricOpt = NULL; AString structName; switch (surfStructs[i]) { case StructureEnum::CORTEX_LEFT: surfOpt = leftSurfOpt; metricOpt = leftMetricOpt; structName = "left"; break; case StructureEnum::CORTEX_RIGHT: surfOpt = rightSurfOpt; metricOpt = rightMetricOpt; structName = "right"; break; case StructureEnum::CEREBELLUM: surfOpt = cerebSurfOpt; metricOpt = cerebMetricOpt; structName = "cerebellum"; break; default: throw OperationException("unsupported surface structure: " + StructureEnum::toName(surfStructs[i])); } vector myMap = myDenseMap.getSurfaceMap(surfStructs[i]); int mapSize = (int)myMap.size(); if (surfOpt->m_present) { SurfaceFile* mySurf = surfOpt->getSurface(1); if (mySurf->getNumberOfNodes() != myDenseMap.getSurfaceNumberOfNodes(surfStructs[i])) { throw OperationException(structName + " area surface has different number of vertices than cifti file"); } vector surfAreas; mySurf->computeNodeAreas(surfAreas); for (int i = 0; i < mapSize; ++i) { combinedWeights[myMap[i].m_ciftiIndex] = surfAreas[myMap[i].m_surfaceNode]; } } else if (metricOpt->m_present) { MetricFile* myMetric = metricOpt->getMetric(1); if (myMetric->getNumberOfNodes() != myDenseMap.getSurfaceNumberOfNodes(surfStructs[i])) { throw OperationException(structName + " area metric has different number of vertices than cifti file"); } for (int i = 0; i < mapSize; ++i) { combinedWeights[myMap[i].m_ciftiIndex] = myMetric->getValue(myMap[i].m_surfaceNode, 0); } } else { throw OperationException("no area data specified for " + structName + " surface"); } } if (myDenseMap.hasVolumeData()) { float voxelVolume = myDenseMap.getVolumeSpace().getVoxelVolume(); vector volMap = myDenseMap.getFullVolumeMap(); int64_t volMapSize = (int64_t)volMap.size(); for (int64_t i = 0; i < volMapSize; ++i) { combinedWeights[volMap[i].m_ciftiIndex] = voxelVolume; } } } if (ciftiWeightOpt->m_present) { CiftiFile* weightCifti = ciftiWeightOpt->getCifti(1); if (!myXML.getMap(CiftiXML::ALONG_COLUMN)->approximateMatch(*(weightCifti->getCiftiXML().getMap(CiftiXML::ALONG_COLUMN)))) { throw OperationException("weighting cifti has incompatible mapping along column"); } weightCifti->getColumn(combinedWeights.data(), 0);//since we are only using one column, go ahead and call getColumn while it is on disk } int64_t useColumn = -1; OptionalParameter* columnOpt = myParams->getOptionalParameter(4); if (columnOpt->m_present) { useColumn = columnOpt->getInteger(1) - 1;//1-based indexing convention if (useColumn < 0 || useColumn >= numCols) throw OperationException("invalid column specified"); } vector roiData; bool matchColumnMode = false; CiftiFile* myRoi = NULL; OptionalParameter* roiOpt = myParams->getOptionalParameter(5); if (roiOpt->m_present) { myRoi = roiOpt->getCifti(1); if (!myXML.getMap(CiftiXML::ALONG_COLUMN)->approximateMatch(*(myRoi->getCiftiXML().getMap(CiftiXML::ALONG_COLUMN)))) { throw OperationException("roi cifti has incompatible mapping along column"); } roiData.resize(colLength); if (roiOpt->getOptionalParameter(2)->m_present) { if (myXML.getMap(CiftiXML::ALONG_ROW)->getLength() != myRoi->getCiftiXML().getMap(CiftiXML::ALONG_ROW)->getLength()) { throw OperationException("-match-maps specified, but roi has different number of columns than input"); } matchColumnMode = true; } else { myRoi->getColumn(roiData.data(), 0);//again, while on disk if we are using only one column } } bool haveOp = false; OperationType myop; if (myParams->getOptionalParameter(6)->m_present) { haveOp = true; myop = MEAN; } OptionalParameter* stdevOpt = myParams->getOptionalParameter(7); if (stdevOpt->m_present) { if (haveOp) throw OperationException("you may only specify one operation"); haveOp = true; if (stdevOpt->getOptionalParameter(1)->m_present) { myop = SAMPSTDEV; } else { myop = STDEV; } } float argument = -1.0f; OptionalParameter* percentileOpt = myParams->getOptionalParameter(8); if (percentileOpt->m_present) { if (haveOp) throw OperationException("you may only specify one operation"); haveOp = true; myop = PERCENTILE; argument = percentileOpt->getDouble(1); if (!(argument >= 0.0f && argument <= 100.0f)) throw OperationException("percentile must be between 0 and 100"); } if (myParams->getOptionalParameter(9)->m_present) { if (haveOp) throw OperationException("you may only specify one operation"); haveOp = true; myop = SUM; } if (!haveOp) throw OperationException("you must specify an operation"); bool showMapName = myParams->getOptionalParameter(10)->m_present; const CiftiMappingType* rowMap = myXML.getMap(CiftiXML::ALONG_ROW); vector inColumn(colLength); if (useColumn == -1) { myInput->convertToInMemory();//we will be getting all columns, so read it all in first if (matchColumnMode) { myRoi->convertToInMemory();//ditto } if (myXML.getMappingType(CiftiXML::ALONG_COLUMN) == CiftiMappingType::BRAIN_MODELS) { const CiftiBrainModelsMap& myDenseMap = myXML.getBrainModelsMap(CiftiXML::ALONG_COLUMN); vector myModels = myDenseMap.getModelInfo(); int numModels = (int)myModels.size(); for (int64_t i = 0; i < numCols; ++i) { myInput->getColumn(inColumn.data(), i); if (matchColumnMode) { myRoi->getColumn(roiData.data(), i); } if (showMapName) { cout << AString::number(i + 1) << ": " << rowMap->getIndexName(i) << ":" << endl; } for (int j = 0; j < numModels; ++j) { if (myModels[j].m_type == CiftiBrainModelsMap::SURFACE) { float result; if (roiData.empty()) { result = doOperation(inColumn.data() + myModels[j].m_indexStart, combinedWeights.data() + myModels[j].m_indexStart, myModels[j].m_indexCount, myop, NULL, argument); } else { result = doOperation(inColumn.data() + myModels[j].m_indexStart, combinedWeights.data() + myModels[j].m_indexStart, myModels[j].m_indexCount, myop, roiData.data() + myModels[j].m_indexStart, argument); } stringstream resultsstr; resultsstr << setprecision(7) << result; cout << StructureEnum::toName(myModels[j].m_structure) << ": " << resultsstr.str() << endl; } } vector volMap = myDenseMap.getFullVolumeMap(); int64_t mapSize = (int64_t)volMap.size(); if (mapSize > 0) { vector volData(mapSize), weightVolData(mapSize), roiVolData(mapSize); for (int64_t j = 0; j < mapSize; ++j) { volData[j] = inColumn[volMap[j].m_ciftiIndex]; weightVolData[j] = combinedWeights[volMap[j].m_ciftiIndex]; if (!roiData.empty()) { roiVolData[j] = roiData[volMap[j].m_ciftiIndex]; } } float result; if (roiData.empty()) { result = doOperation(volData.data(), weightVolData.data(), mapSize, myop, NULL, argument); } else { result = doOperation(volData.data(), weightVolData.data(), mapSize, myop, roiVolData.data(), argument); } stringstream resultsstr; resultsstr << setprecision(7) << result; cout << "VOLUME: " << resultsstr.str() << endl; } } } else { for (int64_t i = 0; i < numCols; ++i) { myInput->getColumn(inColumn.data(), i); float result; if (roiData.empty()) { result = doOperation(inColumn.data(), combinedWeights.data(), colLength, myop, NULL, argument); } else { result = doOperation(inColumn.data(), combinedWeights.data(), colLength, myop, roiData.data(), argument); } if (showMapName) { cout << AString::number(i + 1) << ": " << rowMap->getIndexName(i) << ": "; } stringstream resultsstr; resultsstr << setprecision(7) << result; cout << resultsstr.str() << endl; } } } else { myInput->getColumn(inColumn.data(), useColumn); if (matchColumnMode) { myRoi->getColumn(roiData.data(), useColumn); } if (myXML.getMappingType(CiftiXML::ALONG_COLUMN) == CiftiMappingType::BRAIN_MODELS) { const CiftiBrainModelsMap& myDenseMap = myXML.getBrainModelsMap(CiftiXML::ALONG_COLUMN); vector myModels = myDenseMap.getModelInfo(); int numModels = (int)myModels.size(); if (showMapName) { cout << AString::number(useColumn + 1) << ": " << rowMap->getIndexName(useColumn) << ":" << endl; } for (int j = 0; j < numModels; ++j) { if (myModels[j].m_type == CiftiBrainModelsMap::SURFACE) { float result; if (roiData.empty()) { result = doOperation(inColumn.data() + myModels[j].m_indexStart, combinedWeights.data() + myModels[j].m_indexStart, myModels[j].m_indexCount, myop, NULL, argument); } else { result = doOperation(inColumn.data() + myModels[j].m_indexStart, combinedWeights.data() + myModels[j].m_indexStart, myModels[j].m_indexCount, myop, roiData.data() + myModels[j].m_indexStart, argument); } stringstream resultsstr; resultsstr << setprecision(7) << result; cout << StructureEnum::toName(myModels[j].m_structure) << ": " << resultsstr.str() << endl; } } vector volMap = myDenseMap.getFullVolumeMap(); int64_t mapSize = (int64_t)volMap.size(); if (mapSize > 0) { vector volData(mapSize), weightVolData(mapSize), roiVolData(mapSize); for (int64_t j = 0; j < mapSize; ++j) { volData[j] = inColumn[volMap[j].m_ciftiIndex]; weightVolData[j] = combinedWeights[volMap[j].m_ciftiIndex]; if (!roiData.empty()) { roiVolData[j] = roiData[volMap[j].m_ciftiIndex]; } } float result; if (roiData.empty()) { result = doOperation(volData.data(), weightVolData.data(), mapSize, myop, NULL, argument); } else { result = doOperation(volData.data(), weightVolData.data(), mapSize, myop, roiVolData.data(), argument); } stringstream resultsstr; resultsstr << setprecision(7) << result; cout << "VOLUME: " << resultsstr.str() << endl; } } else { float result; if (roiData.empty()) { result = doOperation(inColumn.data(), combinedWeights.data(), colLength, myop, NULL, argument); } else { result = doOperation(inColumn.data(), combinedWeights.data(), colLength, myop, roiData.data(), argument); } if (showMapName) { cout << AString::number(useColumn + 1) << ": " << rowMap->getIndexName(useColumn) << ": "; } stringstream resultsstr; resultsstr << setprecision(7) << result; cout << resultsstr.str() << endl; } } } workbench-1.1.1/src/Operations/OperationCiftiWeightedStats.h000066400000000000000000000026601255417355300242140ustar00rootroot00000000000000#ifndef __OPERATION_CIFTI_WEIGHTED_STATS_H__ #define __OPERATION_CIFTI_WEIGHTED_STATS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationCiftiWeightedStats : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationCiftiWeightedStats; } #endif //__OPERATION_CIFTI_WEIGHTED_STATS_H__ workbench-1.1.1/src/Operations/OperationConvertAffine.cxx000066400000000000000000000106711255417355300235630ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AffineFile.h" #include "OperationConvertAffine.h" #include "OperationException.h" using namespace caret; using namespace std; AString OperationConvertAffine::getCommandSwitch() { return "-convert-affine"; } AString OperationConvertAffine::getShortDescription() { return "CONVERT AN AFFINE FILE BETWEEN CONVENTIONS"; } OperationParameters* OperationConvertAffine::getParameters() { OperationParameters* ret = new OperationParameters(); OptionalParameter* fromWorld = ret->createOptionalParameter(1, "-from-world", "input is a NIFTI 'world' affine"); fromWorld->addStringParameter(1, "input", "the input affine"); OptionalParameter* fromFlirt = ret->createOptionalParameter(2, "-from-flirt", "input is a flirt matrix"); fromFlirt->addStringParameter(1, "input", "the input affine"); fromFlirt->addStringParameter(2, "source-volume", "the source volume used when generating the input affine"); fromFlirt->addStringParameter(3, "target-volume", "the target volume used when generating the input affine"); OptionalParameter* toWorld = ret->createOptionalParameter(3, "-to-world", "write output as a NIFTI 'world' affine"); toWorld->addStringParameter(1, "output", "output - the output affine");//HACK: fake the output formatting, since we don't have a parameter for affine file (hard to do due to multiple on-disk formats) ParameterComponent* toFlirt = ret->createRepeatableParameter(4, "-to-flirt", "write output as a flirt matrix"); toFlirt->addStringParameter(1, "output", "output - the output affine"); toFlirt->addStringParameter(2, "source-volume", "the volume you want to apply the transform to"); toFlirt->addStringParameter(3, "target-volume", "the target space you want the transformed volume to match"); ret->setHelpText( AString("NIFTI world matrices can be used directly on mm coordinates via matrix multiplication, they use the NIFTI coordinate system, that is, ") + "positive X is right, positive Y is anterior, and positive Z is superior.\n\n" + "You must specify exactly one -from option, but you may specify multiple -to options, and any -to option that takes volumes may be specified more than once." ); return ret; } void OperationConvertAffine::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); AffineFile myAffine; bool haveInput = false; OptionalParameter* fromWorld = myParams->getOptionalParameter(1); if (fromWorld->m_present) { haveInput = true; myAffine.readWorld(fromWorld->getString(1)); } OptionalParameter* fromFlirt = myParams->getOptionalParameter(2); if (fromFlirt->m_present) { if (haveInput) throw OperationException("only one -from option may be specified"); haveInput = true; myAffine.readFlirt(fromFlirt->getString(1), fromFlirt->getString(2), fromFlirt->getString(3)); } if (!haveInput) throw OperationException("you must specify a -from option"); OptionalParameter* toWorld = myParams->getOptionalParameter(3); if (toWorld->m_present) { myAffine.writeWorld(toWorld->getString(1)); } const vector& toFlirt = *(myParams->getRepeatableParameterInstances(4));//the return of this is a pointer so that it can return NULL if the key is wrong, after asserting int numFlirt = (int)toFlirt.size();//so, dereference immediately since it should be caught in debug via assert for (int i = 0; i < numFlirt; ++i) { myAffine.writeFlirt(toFlirt[i]->getString(1), toFlirt[i]->getString(2), toFlirt[i]->getString(3)); } } workbench-1.1.1/src/Operations/OperationConvertAffine.h000066400000000000000000000026171255417355300232110ustar00rootroot00000000000000#ifndef __OPERATION_CONVERT_AFFINE_H__ #define __OPERATION_CONVERT_AFFINE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationConvertAffine : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationConvertAffine; } #endif //__OPERATION_CONVERT_AFFINE_H__ workbench-1.1.1/src/Operations/OperationConvertFiberOrientations.cxx000066400000000000000000000223621255417355300260210ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationConvertFiberOrientations.h" #include "OperationException.h" #include "CiftiFile.h" #include "VolumeFile.h" using namespace caret; using namespace std; AString OperationConvertFiberOrientations::getCommandSwitch() { return "-convert-fiber-orientations"; } AString OperationConvertFiberOrientations::getShortDescription() { return "CONVERT BINGHAM PARAMETER VOLUMES TO FIBER ORIENTATION FILE"; } OperationParameters* OperationConvertFiberOrientations::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addVolumeParameter(1, "label-volume", "volume of cifti structure labels"); ret->addCiftiOutputParameter(2, "fiber-out", "the output fiber orientation file"); ParameterComponent* fiberOpt = ret->createRepeatableParameter(3, "-fiber", "specify the parameter volumes for a fiber"); fiberOpt->addVolumeParameter(1, "mean-f", "mean fiber strength"); fiberOpt->addVolumeParameter(2, "stdev-f", "standard deviation of fiber strength"); fiberOpt->addVolumeParameter(3, "theta", "theta angle"); fiberOpt->addVolumeParameter(4, "phi", "phi angle"); fiberOpt->addVolumeParameter(5, "psi", "psi angle"); fiberOpt->addVolumeParameter(6, "ka", "ka bingham parameter"); fiberOpt->addVolumeParameter(7, "kb", "kb bingham parameter"); AString myText = AString("Takes precomputed bingham parameters from volume files and converts them to the format workbench uses for display. ") + "The argument must be a label volume, where the labels use these strings:\n\n"; vector myStructureEnums; StructureEnum::getAllEnums(myStructureEnums); for (int i = 0; i < (int)myStructureEnums.size(); ++i) { myText += "\n" + StructureEnum::toName(myStructureEnums[i]); } ret->setHelpText(myText); return ret; } void OperationConvertFiberOrientations::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); VolumeFile* labelVol = myParams->getVolume(1); if (labelVol->getType() != SubvolumeAttributes::LABEL) { throw OperationException(" must have a label table, see -volume-label-import"); } CiftiFile* ciftiOut = myParams->getOutputCifti(2); const vector& myInstances = *(myParams->getRepeatableParameterInstances(3)); int numFibers = (int)myInstances.size(); if (numFibers < 1) throw OperationException("must specify -fiber at least once"); if (numFibers > 3) throw OperationException("only three fibers are supported at this time"); for (int i = 0; i < numFibers; ++i) { VolumeFile* meanfvol = myInstances[i]->getVolume(1); VolumeFile* stdevfvol = myInstances[i]->getVolume(2); VolumeFile* thetavol = myInstances[i]->getVolume(3); VolumeFile* phivol = myInstances[i]->getVolume(4); VolumeFile* psivol = myInstances[i]->getVolume(5); VolumeFile* kavol = myInstances[i]->getVolume(6); VolumeFile* kbvol = myInstances[i]->getVolume(7); if (!labelVol->matchesVolumeSpace(meanfvol) || !labelVol->matchesVolumeSpace(stdevfvol) || !labelVol->matchesVolumeSpace(thetavol) || !labelVol->matchesVolumeSpace(phivol) || !labelVol->matchesVolumeSpace(psivol) || !labelVol->matchesVolumeSpace(kavol) || !labelVol->matchesVolumeSpace(kbvol)) { throw OperationException("all inputs must be in the same volume space"); } } map labelMap;//maps label values to structures vector > voxelLists;//voxel lists for each volume component map componentMap;//maps structures to indexes in voxelLists const GiftiLabelTable* myLabelTable = labelVol->getMapLabelTable(0); vector labelKeys; myLabelTable->getKeys(labelKeys); int count = 0; for (int i = 0; i < (int)labelKeys.size(); ++i) { bool ok = false; StructureEnum::Enum thisStructure = StructureEnum::fromName(myLabelTable->getLabelName(labelKeys[i]), &ok); if (ok) { if (componentMap.find(thisStructure) == componentMap.end())//make sure we don't already have this structure from another label { labelMap[labelKeys[i]] = thisStructure; componentMap[thisStructure] = count; ++count; } } } voxelLists.resize(count); vector mydims; labelVol->getDimensions(mydims); for (int64_t k = 0; k < mydims[2]; ++k) { for (int64_t j = 0; j < mydims[1]; ++j) { for (int64_t i = 0; i < mydims[0]; ++i) { int myval = (int)floor(labelVol->getValue(i, j, k) + 0.5f); map::iterator myiter = labelMap.find(myval); if (myiter != labelMap.end()) { int whichList = componentMap.find(myiter->second)->second;//this should always find one, so we don't need to check for being end voxelLists[whichList].push_back(i); voxelLists[whichList].push_back(j); voxelLists[whichList].push_back(k); } } } } int64_t ciftiVolDims[3]; ciftiVolDims[0] = mydims[0]; ciftiVolDims[1] = mydims[1]; ciftiVolDims[2] = mydims[2]; CiftiXMLOld myXML; myXML.resetColumnsToBrainModels(); myXML.setVolumeDimsAndSForm(ciftiVolDims, labelVol->getSform()); for (map::iterator myiter = componentMap.begin(); myiter != componentMap.end(); ++myiter) { myXML.addVolumeModelToColumns(voxelLists[myiter->second], myiter->first); } myXML.resetRowsToScalars(24); myXML.setMapNameForRowIndex(0, "x coord"); myXML.setMapNameForRowIndex(1, "y coord"); myXML.setMapNameForRowIndex(2, "z coord"); myXML.setMapNameForRowIndex(3, "mean f1"); myXML.setMapNameForRowIndex(4, "stdev f1"); myXML.setMapNameForRowIndex(5, "theta1"); myXML.setMapNameForRowIndex(6, "phi1"); myXML.setMapNameForRowIndex(7, "ka1"); myXML.setMapNameForRowIndex(8, "kb1"); myXML.setMapNameForRowIndex(9, "psi1"); myXML.setMapNameForRowIndex(10, "mean f2"); myXML.setMapNameForRowIndex(11, "stdev f2"); myXML.setMapNameForRowIndex(12, "theta2"); myXML.setMapNameForRowIndex(13, "phi2"); myXML.setMapNameForRowIndex(14, "ka2"); myXML.setMapNameForRowIndex(15, "kb2"); myXML.setMapNameForRowIndex(16, "psi2"); myXML.setMapNameForRowIndex(17, "mean f3"); myXML.setMapNameForRowIndex(18, "stdev f3"); myXML.setMapNameForRowIndex(19, "theta3"); myXML.setMapNameForRowIndex(20, "phi3"); myXML.setMapNameForRowIndex(21, "ka3"); myXML.setMapNameForRowIndex(22, "kb3"); myXML.setMapNameForRowIndex(23, "psi3"); ciftiOut->setCiftiXML(myXML); vector volMap; CaretArray temprow(24, 0.0f); temprow[14] = 1.0f;//do not put zeros in ka and kb, ever temprow[15] = 1.0f; temprow[21] = 1.0f; temprow[22] = 1.0f; myXML.getVolumeMapForColumns(volMap);//we don't need to know which voxel is from which parcel int64_t end = (int64_t)volMap.size(); for (int64_t i = 0; i < end; ++i) { labelVol->indexToSpace(volMap[i].m_ijk, temprow);//first three elements are the coordinates for (int j = 0; j < numFibers; ++j) { int base = 7 * j + 3; VolumeFile* meanfvol = myInstances[j]->getVolume(1); VolumeFile* stdevfvol = myInstances[j]->getVolume(2); VolumeFile* thetavol = myInstances[j]->getVolume(3); VolumeFile* phivol = myInstances[j]->getVolume(4); VolumeFile* psivol = myInstances[j]->getVolume(5); VolumeFile* kavol = myInstances[j]->getVolume(6); VolumeFile* kbvol = myInstances[j]->getVolume(7); temprow[base] = meanfvol->getValue(volMap[i].m_ijk); temprow[base + 1] = stdevfvol->getValue(volMap[i].m_ijk); temprow[base + 2] = thetavol->getValue(volMap[i].m_ijk); temprow[base + 3] = phivol->getValue(volMap[i].m_ijk); temprow[base + 4] = kavol->getValue(volMap[i].m_ijk); temprow[base + 5] = kbvol->getValue(volMap[i].m_ijk); temprow[base + 6] = psivol->getValue(volMap[i].m_ijk); } ciftiOut->setRow(temprow, volMap[i].m_ciftiIndex); } } workbench-1.1.1/src/Operations/OperationConvertFiberOrientations.h000066400000000000000000000027241255417355300254460ustar00rootroot00000000000000#ifndef __OPERATION_CONVERT_FIBER_ORIENTATIONS_H__ #define __OPERATION_CONVERT_FIBER_ORIENTATIONS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationConvertFiberOrientations : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationConvertFiberOrientations; } #endif //__OPERATION_CONVERT_FIBER_ORIENTATIONS_H__ workbench-1.1.1/src/Operations/OperationConvertMatrix4ToMatrix2.cxx000066400000000000000000000067461255417355300255050ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationConvertMatrix4ToMatrix2.h" #include "OperationException.h" #include "CiftiFile.h" #include "CaretSparseFile.h" using namespace caret; using namespace std; AString OperationConvertMatrix4ToMatrix2::getCommandSwitch() { return "-convert-matrix4-to-matrix2"; } AString OperationConvertMatrix4ToMatrix2::getShortDescription() { return "GENERATES A MATRIX2 CIFTI FROM MATRIX4 WBSPARSE"; } OperationParameters* OperationConvertMatrix4ToMatrix2::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addStringParameter(1, "matrix4-wbsparse", "a wbsparse matrix4 file"); ret->addCiftiOutputParameter(2, "counts-out", "the total fiber counts, as a cifti file"); OptionalParameter* distanceOpt = ret->createOptionalParameter(3, "-distances", "output average trajectory distance"); distanceOpt->addCiftiOutputParameter(1, "distance-out", "the distances, as a cifti file"); ret->setHelpText( AString("This command makes a cifti file from the fiber counts in a matrix4 wbsparse file, and optionally a second cifti file from the distances.") ); return ret; } void OperationConvertMatrix4ToMatrix2::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); AString matrix4Name = myParams->getString(1); CiftiFile* myCountsOut = myParams->getOutputCifti(2); CiftiFile* myDistOut = NULL; OptionalParameter* distanceOpt = myParams->getOptionalParameter(3); if (distanceOpt->m_present) { myDistOut = distanceOpt->getOutputCifti(1); } CaretSparseFile matrix4(matrix4Name); const CiftiXML& myXML = matrix4.getCiftiXML(); myCountsOut->setCiftiXML(myXML); if (myDistOut != NULL) myDistOut->setCiftiXML(myXML); int rowSize = myXML.getDimensionLength(CiftiXML::ALONG_ROW), numRows = myXML.getDimensionLength(CiftiXML::ALONG_COLUMN); vector scratchRow(rowSize, 0.0f); vector indices; vector fibers; for (int i = 0; i < numRows; ++i) { matrix4.getFibersRowSparse(i, indices, fibers); int numIndices = (int)indices.size(); for (int j = 0; j < numIndices; ++j) { scratchRow[indices[j]] = fibers[j].totalCount; } myCountsOut->setRow(scratchRow.data(), i); if (myDistOut != NULL) { for (int j = 0; j < numIndices; ++j) { scratchRow[indices[j]] = fibers[j].distance; } myDistOut->setRow(scratchRow.data(), i); } for (int j = 0; j < numIndices; ++j) { scratchRow[indices[j]] = 0.0f; } } } workbench-1.1.1/src/Operations/OperationConvertMatrix4ToMatrix2.h000066400000000000000000000027211255417355300251170ustar00rootroot00000000000000#ifndef __OPERATION_CONVERT_MATRIX4_TO_MATRIX2_H__ #define __OPERATION_CONVERT_MATRIX4_TO_MATRIX2_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationConvertMatrix4ToMatrix2 : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationConvertMatrix4ToMatrix2; } #endif //__OPERATION_CONVERT_MATRIX4_TO_MATRIX2_H__ workbench-1.1.1/src/Operations/OperationConvertMatrix4ToWorkbenchSparse.cxx000066400000000000000000000236571255417355300272570ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationConvertMatrix4ToWorkbenchSparse.h" #include "OperationException.h" #include "CaretHeap.h" #include "CaretSparseFile.h" #include "CiftiFile.h" #include "OxfordSparseThreeFile.h" #include "MetricFile.h" #include "VolumeFile.h" #include #include #include #include #include using namespace caret; using namespace std; AString OperationConvertMatrix4ToWorkbenchSparse::getCommandSwitch() { return "-convert-matrix4-to-workbench-sparse"; } AString OperationConvertMatrix4ToWorkbenchSparse::getShortDescription() { return "CONVERT A 3-FILE MATRIX4 TO A WORKBENCH SPARSE FILE"; } OperationParameters* OperationConvertMatrix4ToWorkbenchSparse::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addStringParameter(1, "matrix4_1", "the first matrix4 file"); ret->addStringParameter(2, "matrix4_2", "the second matrix4 file"); ret->addStringParameter(3, "matrix4_3", "the third matrix4 file"); ret->addCiftiParameter(4, "orientation-file", "the .fiberTEMP.nii file this trajectory file applies to"); ret->addStringParameter(5, "voxel-list", "list of white matter voxel index triplets as used in the trajectory matrix"); ret->addStringParameter(6, "wb-sparse-out", "output - the output workbench sparse file"); OptionalParameter* surfaceOpt = ret->createOptionalParameter(7, "-surface-seeds", "specify the surface seed space"); surfaceOpt->addMetricParameter(1, "seed-roi", "metric roi file of all nodes used in the seed space"); OptionalParameter* volumeOpt = ret->createOptionalParameter(8, "-volume-seeds", "specify the volume seed space"); volumeOpt->addCiftiParameter(1, "cifti-template", "cifti file to use the volume mappings from"); volumeOpt->addStringParameter(2, "direction", "dimension along the cifti file to take the mapping from, ROW or COLUMN"); ret->setHelpText( AString("Converts the matrix 4 output of probtrackx to workbench sparse file format. ") + "Exactly one of -surface-seeds and -volume-seeds must be specified." ); return ret; } void OperationConvertMatrix4ToWorkbenchSparse::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); AString inFile1 = myParams->getString(1); AString inFile2 = myParams->getString(2); AString inFile3 = myParams->getString(3); AString voxelFileName = myParams->getString(5); CiftiFile* orientationFile = myParams->getCifti(4); AString outFileName = myParams->getString(6); OxfordSparseThreeFile inFile(inFile1, inFile2, inFile3); const int64_t* sparseDims = inFile.getDimensions(); OptionalParameter* surfaceOpt = myParams->getOptionalParameter(7); OptionalParameter* volumeOpt = myParams->getOptionalParameter(8); if (surfaceOpt->m_present == volumeOpt->m_present) throw OperationException("you must specify exactly one of -surface-seeds and -volume-seeds");//use == on booleans as xnor const CiftiXML& orientXML = orientationFile->getCiftiXML(); if (orientXML.getMappingType(CiftiXML::ALONG_COLUMN) != CiftiMappingType::BRAIN_MODELS) throw OperationException("orientation file must have brain models mapping along column"); CiftiXML myXML; myXML.setNumberOfDimensions(2); myXML.setMap(CiftiXML::ALONG_ROW, *(orientXML.getMap(CiftiXML::ALONG_COLUMN))); if (surfaceOpt->m_present) { MetricFile* myROI = surfaceOpt->getMetric(1); const float* roiData = myROI->getValuePointerForColumn(0); CiftiBrainModelsMap tempMap; tempMap.addSurfaceModel(myROI->getNumberOfNodes(), myROI->getStructure(), roiData); if (tempMap.getLength() != sparseDims[1]) { throw OperationException("roi has a different number of selected vertices than the input matrix"); } myXML.setMap(CiftiXML::ALONG_COLUMN, tempMap); } if (volumeOpt->m_present) { CiftiFile* myTemplate = volumeOpt->getCifti(1); AString directionName = volumeOpt->getString(2); int myDir = -1; if (directionName == "ROW") { myDir = CiftiXML::ALONG_ROW; } else if (directionName == "COLUMN") { myDir = CiftiXML::ALONG_COLUMN; } else { throw OperationException("direction must be ROW or COLUMN"); } const CiftiXML& templateXML = myTemplate->getCiftiXML(); if (templateXML.getMappingType(myDir) != CiftiMappingType::BRAIN_MODELS) throw OperationException("template cifti file must have brain models along specified direction"); const CiftiBrainModelsMap& templateMap = templateXML.getBrainModelsMap(myDir); if (!templateMap.hasVolumeData()) throw OperationException("template cifti file has no volume data"); vector surfList = templateMap.getSurfaceStructureList(), volList = templateMap.getVolumeStructureList();//since we only need the volume models, we don't need to loop through all models CiftiBrainModelsMap tempMap; tempMap.setVolumeSpace(templateMap.getVolumeSpace()); for (int i = 0; i < (int)volList.size(); ++i) { vector myMap = templateMap.getVolumeStructureMap(volList[i]); int64_t mapSize = (int64_t)myMap.size(); vector ijkList; for (int64_t j = 0; j < mapSize; ++j) { ijkList.push_back(myMap[j].m_ijk[0]); ijkList.push_back(myMap[j].m_ijk[1]); ijkList.push_back(myMap[j].m_ijk[2]); } tempMap.addVolumeModel(volList[i], ijkList); } if (tempMap.getLength() != sparseDims[1]) throw OperationException("volume models in template cifti file do not match the dimension of the input file"); myXML.setMap(CiftiXML::ALONG_COLUMN, tempMap); } CaretAssert(myXML.getDimensionLength(CiftiXML::ALONG_COLUMN) == sparseDims[1]); fstream voxelFile(voxelFileName.toLocal8Bit().constData(), fstream::in); if (!voxelFile.good()) { throw OperationException("failed to open voxel list file for reading"); } const CiftiBrainModelsMap& rowMap = myXML.getBrainModelsMap(CiftiXML::ALONG_ROW);//tested above, as orientationXML const int64_t* volDims = rowMap.getVolumeSpace().getDims(); vector voxelIndices; int64_t ind1, ind2, ind3; while (voxelFile >> ind1 >> ind2 >> ind3) { if (min(min(ind1, ind2), ind3) < 0) throw OperationException("negative voxel index found in voxel list"); if (ind1 >= volDims[0] || ind2 >= volDims[1] || ind3 >= volDims[2]) throw OperationException("found voxel index that exceeds dimension in voxel list"); voxelIndices.push_back(ind1); voxelIndices.push_back(ind2); voxelIndices.push_back(ind3); } if (!voxelFile.eof()) { voxelFile.clear(); string mystring; while (getline(voxelFile, mystring)) { if (AString(mystring.c_str()).trimmed() != "") { throw OperationException("found non-digit, non-whitespace characters: " + AString(mystring.c_str())); } } } if ((int64_t)voxelIndices.size() != sparseDims[0] * 3) throw OperationException("voxel list file contains the wrong number of voxels, expected " + AString::number(sparseDims[0] * 3) + " integers, read " + AString::number(voxelIndices.size())); vector rowReorder(sparseDims[0], -1); for (int64_t i = 0; i < (int64_t)voxelIndices.size(); i += 3) { int64_t tempInd = rowMap.getIndexForVoxel(voxelIndices.data() + i); if (tempInd != -1) { rowReorder[i / 3] = tempInd; } } CaretSparseFileWriter mywriter(outFileName, myXML);//NOTE: CaretSparseFile has a different encoding of fibers, ALWAYS use getFibersRow, etc vector indicesIn, indicesOut;//this method knows about sparseness, does sorting of indexes in order to avoid scanning full rows vector fibersIn, fibersOut;//can be slower if matrix isn't very sparse, but that is a problem for other reasons anyway CaretMinHeap myHeap;//use our heap to do heapsort, rather than coding a struct for stl sort for (int64_t i = 0; i < sparseDims[1]; ++i) { inFile.getFibersRowSparse(i, indicesIn, fibersIn); size_t numNonzero = indicesIn.size(); myHeap.reserve(numNonzero); for (size_t j = 0; j < numNonzero; ++j) { int64_t newIndex = rowReorder[indicesIn[j]];//reorder if (newIndex != -1) { myHeap.push(fibersIn[j], newIndex);//heapify } } indicesOut.resize(myHeap.size()); fibersOut.resize(myHeap.size()); int64_t curIndex = 0; while (!myHeap.isEmpty()) { int64_t newIndex; fibersOut[curIndex] = myHeap.pop(&newIndex); indicesOut[curIndex] = newIndex; ++curIndex; } mywriter.writeFibersRowSparse(i, indicesOut, fibersOut); } mywriter.finish(); } workbench-1.1.1/src/Operations/OperationConvertMatrix4ToWorkbenchSparse.h000066400000000000000000000030041255417355300266640ustar00rootroot00000000000000#ifndef __OPERATION_CONVERT_MATRIX4_TO_WORKBENCH_SPARSE_H__ #define __OPERATION_CONVERT_MATRIX4_TO_WORKBENCH_SPARSE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationConvertMatrix4ToWorkbenchSparse : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationConvertMatrix4ToWorkbenchSparse; } #endif //__OPERATION_CONVERT_MATRIX4_TO_WORKBENCH_SPARSE_H__ workbench-1.1.1/src/Operations/OperationConvertWarpfield.cxx000066400000000000000000000110461255417355300243050ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationConvertWarpfield.h" #include "OperationException.h" #include "WarpfieldFile.h" using namespace caret; using namespace std; AString OperationConvertWarpfield::getCommandSwitch() { return "-convert-warpfield"; } AString OperationConvertWarpfield::getShortDescription() { return "CONVERT A WARPFIELD BETWEEN CONVENTIONS"; } OperationParameters* OperationConvertWarpfield::getParameters() { OperationParameters* ret = new OperationParameters(); OptionalParameter* fromWorld = ret->createOptionalParameter(1, "-from-world", "input is a NIFTI 'world' warpfield"); fromWorld->addStringParameter(1, "input", "the input warpfield"); OptionalParameter* fromFnirt = ret->createOptionalParameter(2, "-from-fnirt", "input is a fnirt warpfield"); fromFnirt->addStringParameter(1, "input", "the input warpfield"); fromFnirt->addStringParameter(2, "source-volume", "the source volume used when generating the input warpfield"); OptionalParameter* toWorld = ret->createOptionalParameter(3, "-to-world", "write output as a NIFTI 'world' warpfield"); toWorld->addStringParameter(1, "output", "output - the output warpfield");//HACK: fake the output formatting, since we don't have a parameter for affine file (hard to do due to multiple on-disk formats) ParameterComponent* toFnirt = ret->createRepeatableParameter(4, "-to-fnirt", "write output as a flirt warpfield"); toFnirt->addStringParameter(1, "output", "output - the output warpfield"); toFnirt->addStringParameter(2, "source-volume", "the volume you want to apply the warpfield to"); ret->setHelpText( AString("NIFTI world warpfields can be used directly on mm coordinates via sampling the three subvolumes at the coordinate ") + "and adding the sampled values to the coordinate vector, they use the NIFTI coordinate system, that is, " + "X is left to right, Y is posterior to anterior, and Z is inferior to superior.\n\n" + "NOTE: this command does not invert the warpfield, and to warp a surface, you must use the inverse of the warpfield that warps the corresponding volume.\n\n" + "You must specify exactly one -from option, but you may specify multiple -to options, and any -to option that takes volumes may be specified more than once." ); return ret; } void OperationConvertWarpfield::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); WarpfieldFile myWarp; bool haveInput = false; OptionalParameter* fromWorld = myParams->getOptionalParameter(1); OptionalParameter* fromFnirt = myParams->getOptionalParameter(2); if (fromWorld->m_present) { haveInput = true; } if (fromFnirt->m_present) { if (haveInput) throw OperationException("only one -from option may be specified"); haveInput = true; } if (!haveInput) throw OperationException("you must specify a -from option"); if (fromWorld->m_present) { myWarp.readWorld(fromWorld->getString(1)); } if (fromFnirt->m_present) { myWarp.readFnirt(fromFnirt->getString(1), fromFnirt->getString(2)); } OptionalParameter* toWorld = myParams->getOptionalParameter(3); if (toWorld->m_present) { myWarp.writeWorld(toWorld->getString(1)); } const vector& toFnirt = *(myParams->getRepeatableParameterInstances(4));//the return of this is a pointer so that it can return NULL if the key is wrong, after asserting int numFnirt = (int)toFnirt.size();//so, dereference immediately since it should be caught in debug via assert for (int i = 0; i < numFnirt; ++i) { myWarp.writeFnirt(toFnirt[i]->getString(1), toFnirt[i]->getString(2)); } } workbench-1.1.1/src/Operations/OperationConvertWarpfield.h000066400000000000000000000026411255417355300237330ustar00rootroot00000000000000#ifndef __OPERATION_CONVERT_WARPFIELD_H__ #define __OPERATION_CONVERT_WARPFIELD_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationConvertWarpfield : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationConvertWarpfield; } #endif //__OPERATION_CONVERT_WARPFIELD_H__ workbench-1.1.1/src/Operations/OperationEstimateFiberBinghams.cxx000066400000000000000000000343211255417355300252240ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationEstimateFiberBinghams.h" #include "OperationException.h" #include "CiftiFile.h" #include "MathFunctions.h" #include "StructureEnum.h" #include "Vector3D.h" #include "VolumeFile.h" #include #include using namespace caret; using namespace std; AString OperationEstimateFiberBinghams::getCommandSwitch() { return "-estimate-fiber-binghams"; } AString OperationEstimateFiberBinghams::getShortDescription() { return "ESTIMATE FIBER ORIENTATION DISTRIBUTIONS FROM BEDPOSTX SAMPLES"; } OperationParameters* OperationEstimateFiberBinghams::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addVolumeParameter(1, "merged_f1samples", "fiber 1 strength samples"); ret->addVolumeParameter(2, "merged_th1samples", "fiber 1 theta samples"); ret->addVolumeParameter(3, "merged_ph1samples", "fiber 1 phi samples"); ret->addVolumeParameter(4, "merged_f2samples", "fiber 2 strength samples"); ret->addVolumeParameter(5, "merged_th2samples", "fiber 2 theta samples"); ret->addVolumeParameter(6, "merged_ph2samples", "fiber 2 phi samples"); ret->addVolumeParameter(7, "merged_f3samples", "fiber 3 strength samples"); ret->addVolumeParameter(8, "merged_th3samples", "fiber 3 theta samples"); ret->addVolumeParameter(9, "merged_ph3samples", "fiber 3 phi samples"); ret->addVolumeParameter(10, "label-volume", "volume of cifti structure labels"); ret->addCiftiOutputParameter(11, "cifti-out", "output cifti fiber distributons file"); AString myText = AString("This command does an estimation of a bingham distribution for each fiber orientation in each voxel which is ") + "labeled a structure identifier. These labelings come from the argument, which must have labels that match the following strings:\n"; vector myStructureEnums; StructureEnum::getAllEnums(myStructureEnums); for (int i = 0; i < (int)myStructureEnums.size(); ++i) { myText += "\n" + StructureEnum::toName(myStructureEnums[i]); } ret->setHelpText(myText); return ret; } void OperationEstimateFiberBinghams::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); const VolumeFile* f1_samples = myParams->getVolume(1); const VolumeFile* th1_samples = myParams->getVolume(2); const VolumeFile* ph1_samples = myParams->getVolume(3); const VolumeFile* f2_samples = myParams->getVolume(4); const VolumeFile* th2_samples = myParams->getVolume(5); const VolumeFile* ph2_samples = myParams->getVolume(6); const VolumeFile* f3_samples = myParams->getVolume(7); const VolumeFile* th3_samples = myParams->getVolume(8); const VolumeFile* ph3_samples = myParams->getVolume(9); const VolumeFile* myVolLabel = myParams->getVolume(10); if (myVolLabel->getType() != SubvolumeAttributes::LABEL) { throw OperationException(" must have a label table, see -volume-label-import"); } if (!(myVolLabel->matchesVolumeSpace(f1_samples) && myVolLabel->matchesVolumeSpace(th1_samples) && myVolLabel->matchesVolumeSpace(ph1_samples) && myVolLabel->matchesVolumeSpace(f2_samples) && myVolLabel->matchesVolumeSpace(th2_samples) && myVolLabel->matchesVolumeSpace(ph2_samples) && myVolLabel->matchesVolumeSpace(f3_samples) && myVolLabel->matchesVolumeSpace(th3_samples) && myVolLabel->matchesVolumeSpace(ph3_samples))) { throw OperationException("all inputs must be in the same volume space"); } int64_t junk, numfsamp, numthsamp, numphsamp; f1_samples->getDimensions(junk, junk, junk, numfsamp, junk); th1_samples->getDimensions(junk, junk, junk, numthsamp, junk); ph1_samples->getDimensions(junk, junk, junk, numphsamp, junk); if (numfsamp != numthsamp || numfsamp != numphsamp) { throw OperationException("fiber 1 volumes have different numbers of samples"); } f2_samples->getDimensions(junk, junk, junk, numfsamp, junk); th2_samples->getDimensions(junk, junk, junk, numthsamp, junk); ph2_samples->getDimensions(junk, junk, junk, numphsamp, junk); if (numfsamp != numthsamp || numfsamp != numphsamp) { throw OperationException("fiber 2 volumes have different numbers of samples"); } f3_samples->getDimensions(junk, junk, junk, numfsamp, junk); th3_samples->getDimensions(junk, junk, junk, numthsamp, junk); ph3_samples->getDimensions(junk, junk, junk, numphsamp, junk); if (numfsamp != numthsamp || numfsamp != numphsamp) { throw OperationException("fiber 3 volumes have different numbers of samples"); } map labelMap;//maps label values to structures vector > voxelLists;//voxel lists for each volume component map componentMap;//maps structures to indexes in voxelLists const GiftiLabelTable* myLabelTable = myVolLabel->getMapLabelTable(0); vector labelKeys; myLabelTable->getKeys(labelKeys); int count = 0; for (int i = 0; i < (int)labelKeys.size(); ++i) { bool ok = false; StructureEnum::Enum thisStructure = StructureEnum::fromName(myLabelTable->getLabelName(labelKeys[i]), &ok); if (ok) { if (componentMap.find(thisStructure) == componentMap.end())//make sure we don't already have this structure from another label { labelMap[labelKeys[i]] = thisStructure; componentMap[thisStructure] = count; ++count; } } } voxelLists.resize(count); vector mydims; myVolLabel->getDimensions(mydims); for (int64_t k = 0; k < mydims[2]; ++k) { for (int64_t j = 0; j < mydims[1]; ++j) { for (int64_t i = 0; i < mydims[0]; ++i) { int myval = (int)floor(myVolLabel->getValue(i, j, k) + 0.5f); map::iterator myiter = labelMap.find(myval); if (myiter != labelMap.end()) { int whichList = componentMap.find(myiter->second)->second;//this should always find one, so we don't need to check for being end voxelLists[whichList].push_back(i); voxelLists[whichList].push_back(j); voxelLists[whichList].push_back(k); } } } } int64_t ciftiVolDims[3]; ciftiVolDims[0] = mydims[0]; ciftiVolDims[1] = mydims[1]; ciftiVolDims[2] = mydims[2]; CiftiXMLOld myXML; myXML.resetColumnsToBrainModels(); myXML.setVolumeDimsAndSForm(ciftiVolDims, myVolLabel->getSform()); for (map::iterator myiter = componentMap.begin(); myiter != componentMap.end(); ++myiter) { myXML.addVolumeModelToColumns(voxelLists[myiter->second], myiter->first); } myXML.resetRowsToScalars(24); myXML.setMapNameForRowIndex(0, "x coord"); myXML.setMapNameForRowIndex(1, "y coord"); myXML.setMapNameForRowIndex(2, "z coord"); myXML.setMapNameForRowIndex(3, "mean f1"); myXML.setMapNameForRowIndex(4, "stdev f1"); myXML.setMapNameForRowIndex(5, "theta1"); myXML.setMapNameForRowIndex(6, "phi1"); myXML.setMapNameForRowIndex(7, "ka1"); myXML.setMapNameForRowIndex(8, "kb1"); myXML.setMapNameForRowIndex(9, "psi1"); myXML.setMapNameForRowIndex(10, "mean f2"); myXML.setMapNameForRowIndex(11, "stdev f2"); myXML.setMapNameForRowIndex(12, "theta2"); myXML.setMapNameForRowIndex(13, "phi2"); myXML.setMapNameForRowIndex(14, "ka2"); myXML.setMapNameForRowIndex(15, "kb2"); myXML.setMapNameForRowIndex(16, "psi2"); myXML.setMapNameForRowIndex(17, "mean f3"); myXML.setMapNameForRowIndex(18, "stdev f3"); myXML.setMapNameForRowIndex(19, "theta3"); myXML.setMapNameForRowIndex(20, "phi3"); myXML.setMapNameForRowIndex(21, "ka3"); myXML.setMapNameForRowIndex(22, "kb3"); myXML.setMapNameForRowIndex(23, "psi3"); CiftiFile* myCifti = myParams->getOutputCifti(11); myCifti->setCiftiXML(myXML); vector volMap; CaretArray temprow(24); myXML.getVolumeMapForColumns(volMap);//we don't need to know which voxel is from which parcel int64_t end = (int64_t)volMap.size(); for (int64_t i = 0; i < end; ++i) { myVolLabel->indexToSpace(volMap[i].m_ijk, temprow);//first three elements are the coordinates estimateBingham(temprow.getArray() + 3, volMap[i].m_ijk, f1_samples, th1_samples, ph1_samples); estimateBingham(temprow.getArray() + 10, volMap[i].m_ijk, f2_samples, th2_samples, ph2_samples); estimateBingham(temprow.getArray() + 17, volMap[i].m_ijk, f3_samples, th3_samples, ph3_samples); myCifti->setRow(temprow, volMap[i].m_ciftiIndex); } } void OperationEstimateFiberBinghams::estimateBingham(float* binghamOut, const int64_t ijk[3], const VolumeFile* f_samples, const VolumeFile* theta_samples, const VolumeFile* phi_samples) { vector fdims; f_samples->getDimensions(fdims); vector samples(fdims[3]); vector fsamplearray(fdims[3]);//to do two-pass mean/stdev without using the VolumeFile function twice per sample double accum = 0.0; Vector3D accumvec;//initializes to the zero vector for (int i = 0; i < fdims[3]; ++i) { fsamplearray[i] = f_samples->getValue(ijk, i); accum += fsamplearray[i]; float theta = theta_samples->getValue(ijk, i);//these have already been checked to have the same number of bricks float phi = phi_samples->getValue(ijk, i); samples[i][0] = -sin(theta) * cos(phi);//NOTE: theta, phi are polar coordinates for a RADIOLOGICAL volume, so flip x so that +x = right samples[i][1] = sin(theta) * sin(phi);//ignore the f values for directionality testing samples[i][2] = cos(theta);//beware, samples may be the negative of other samples, BAS model doesn't care, and they may have restricted the samples to +z or something if (i != 0) { if (accumvec.dot(samples[i]) < 0.0f)//invert the ones that have a negative dot product with the current accumulated vector, and hope the first samples don't have a spread of more than 90 degrees { samples[i] = -samples[i]; } } accumvec += samples[i]; } accumvec = accumvec.normal();//normalize the average direction, trust this to make all components in [-1, 1] binghamOut[0] = accum / fdims[3];//the mean value of f accum = 0.0; for (int i = 0; i < fdims[3]; ++i) { float temp = fsamplearray[i] - binghamOut[0]; accum += temp * temp; } binghamOut[1] = sqrt(accum / (fdims[3] - 1));//sample standard deviation float theta = acos(accumvec[2]);//[0, pi] float phi = atan2(accumvec[1], -accumvec[0]);//[-pi, pi] - NOTE: radiological polar strikes again if (phi < 0.0f) phi += 2 * 3.14159265358979;//[0, 2pi] binghamOut[2] = theta; binghamOut[3] = phi; Vector3D xhat, yhat;//vectors to project to the normal plane of the average direction, assuming psi=0 xhat[0] = cos(theta) * cos(phi);//more radiological polar to neurological euclidean stuff xhat[1] = -cos(theta) * sin(phi); xhat[2] = sin(theta); yhat[0] = sin(phi); yhat[1] = cos(phi); yhat[2] = 0.0f; vector x_samples(fdims[3]), y_samples(fdims[3]); float dist = -1.0f; int end1 = -1, end2 = -1; for (int i = 0; i < fdims[3]; ++i)//first endpoint is farthest from mean direction { x_samples[i] = samples[i].dot(xhat); y_samples[i] = samples[i].dot(yhat); float tempf = x_samples[i] * x_samples[i] + y_samples[i] * y_samples[i]; if (tempf > dist) { end1 = i; dist = tempf; } } dist = -1.0f; for (int i = 0; i < fdims[3]; ++i)//second endpoint is farthest from first endpoint { float xdiff = x_samples[i] - x_samples[end1]; float ydiff = y_samples[i] - y_samples[end1]; float tempf = xdiff * xdiff + ydiff * ydiff; if (tempf > dist) { end2 = i; dist = tempf; } }//NOTE: the MAJOR axis of fanning is along y when psi = 0! ka > kb means kb is in the direction of more fanning float psi = atan((x_samples[end2] - x_samples[end1]) / (y_samples[end2] - y_samples[end1]));//[-pi/2, pi/2] - TODO: check radiological psi orientation if (!MathFunctions::isNumeric(psi)) { const float LARGE_K = 1800.0f;//stdev of 1/60, typical maximum ka in a voxel is around 200 binghamOut[4] = LARGE_K; binghamOut[5] = LARGE_K; binghamOut[6] = 0; return; } if (psi < 0) psi += 3.14159265358979;//[0, pi] binghamOut[6] = psi; double accumx = 0.0, accumy = 0.0; for (int i = 0; i < fdims[3]; ++i) {//rotate the samples through -psi on the plane to orient them to the axes float newx = x_samples[i] * cos(psi) + y_samples[i] * -sin(psi);//NOTE: again, radiological psi? float newy = x_samples[i] * sin(psi) + y_samples[i] * cos(psi); accumx += newx * newx;//assume mean of zero, because we already chose the center of the distribution accumy += newy * newy; } float ka = (fdims[3] - 1) / (2 * accumx);//convert variance to concentrations float kb = (fdims[3] - 1) / (2 * accumy);//but they aren't negative binghamOut[4] = ka; binghamOut[5] = kb; } workbench-1.1.1/src/Operations/OperationEstimateFiberBinghams.h000066400000000000000000000032021255417355300246430ustar00rootroot00000000000000#ifndef __OPERATION_ESTIMATE_FIBER_BINGHAMS_H__ #define __OPERATION_ESTIMATE_FIBER_BINGHAMS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationEstimateFiberBinghams : public AbstractOperation { static void estimateBingham(float* binghamOut, const int64_t ijk[3], const caret::VolumeFile* f_samples, const caret::VolumeFile* theta_samples, const caret::VolumeFile* phi_samples); public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationEstimateFiberBinghams; } #endif //__OPERATION_ESTIMATE_FIBER_BINGHAMS_H__ workbench-1.1.1/src/Operations/OperationException.cxx000066400000000000000000000042431255417355300227660ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationException.h" #include using namespace caret; /** * Constructor. * */ OperationException::OperationException() : CaretException() { this->initializeMembersOperationException(); } /** * Constructor that uses stack trace from the exception * passed in as a parameter. * * @param e Any exception whose stack trace becomes * this exception's stack trace. * */ OperationException::OperationException( const CaretException& e) : CaretException(e) { this->initializeMembersOperationException(); } /** * Constructor. * * @param s Description of the exception. * */ OperationException::OperationException(const AString& s) : CaretException(s) { this->initializeMembersOperationException(); } /** * Copy Constructor. * @param e * Exception that is copied. */ OperationException::OperationException(const OperationException& e) : CaretException(e) { } /** * Assignment operator. * @param e * Exception that is copied. * @return * Copy of the exception. */ OperationException& OperationException::operator=(const OperationException& e) { if (this != &e) { CaretException::operator=(e); } return *this; } /** * Destructor */ OperationException::~OperationException() throw() { } void OperationException::initializeMembersOperationException() { } workbench-1.1.1/src/Operations/OperationException.h000066400000000000000000000027421255417355300224150ustar00rootroot00000000000000#ifndef __OPERATION_EXCEPTION_H__ #define __OPERATION_EXCEPTION_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretException.h" namespace caret { /// An exception thrown during command processing. class OperationException : public CaretException { public: OperationException(); OperationException(const CaretException& e); OperationException(const AString& s); OperationException(const OperationException& e); OperationException& operator=(const OperationException& e); virtual ~OperationException() throw(); private: void initializeMembersOperationException(); }; } // namespace #endif // __OPERATION_EXCEPTION_H__ workbench-1.1.1/src/Operations/OperationFileConvert.cxx000066400000000000000000000276231255417355300232570ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationFileConvert.h" #include "OperationException.h" #include "BorderFile.h" #include "Border.h" #include "CaretLogger.h" #include "CiftiFile.h" #include "FileInformation.h" #include "MultiDimIterator.h" #include "NiftiIO.h" #include "SurfaceFile.h" using namespace caret; using namespace std; AString OperationFileConvert::getCommandSwitch() { return "-file-convert"; } AString OperationFileConvert::getShortDescription() { return "CHANGE VERSION OF FILE FORMAT"; } OperationParameters* OperationFileConvert::getParameters() { OperationParameters* ret = new OperationParameters(); OptionalParameter* borderConv = ret->createOptionalParameter(1, "-border-version-convert", "write a border file with a different version"); borderConv->addBorderParameter(1, "border-in", "the input border file"); borderConv->addIntegerParameter(2, "out-version", "the format version to write as, 1 or 3 (2 doesn't exist)"); borderConv->addStringParameter(3, "border-out", "output - the output border file");//fake the output formatting, the auto-output code will use whatever version it wants OptionalParameter* borderSurfOpt = borderConv->createOptionalParameter(4, "-surface", "must be specified if the input is version 1"); borderSurfOpt->addSurfaceParameter(1, "surface", "use this surface file for structure and number of vertices, ignore borders on other structures"); OptionalParameter* niftiConv = ret->createOptionalParameter(2, "-nifti-version-convert", "write a nifti file with a different version"); niftiConv->addStringParameter(1, "input", "the input nifti file");//VolumeFile isn't "generic nifti", so we use NiftiIO directly niftiConv->addIntegerParameter(2, "version", "the nifti version to write as"); niftiConv->addStringParameter(3, "output", "output - the output nifti file");//fake the output formatting, we don't have a "generic nifti" type OptionalParameter* ciftiConv = ret->createOptionalParameter(3, "-cifti-version-convert", "write a cifti file with a different version"); ciftiConv->addCiftiParameter(1, "cifti-in", "the input cifti file"); ciftiConv->addStringParameter(2, "version", "the cifti version to write as"); ciftiConv->addStringParameter(3, "cifti-out", "output - the output cifti file");//fake the output formatting so we can just call writeFile and be done with it (and also not add a layer of provenance) ret->setHelpText( AString("You may only specify one top-level option.") ); return ret; } namespace //hide helpers in a file-specific namespace { //hidden templated function to do generic nifti reading and writing template void niftiConvertHelper(NiftiIO& inputIO, const AString& outFileName, const int64_t& maxMem, const int& outVer, const bool& collision) { const vector dims = inputIO.getDimensions();//DO NOT make this a reference if (collision) { int64_t totalBytes = (int)sizeof(T) * inputIO.getNumComponents(), totalElems = inputIO.getNumComponents();//compute memory usage to check whether to warn for (int fullDims = 0; fullDims < (int)dims.size(); ++fullDims) { totalBytes *= dims[fullDims]; totalElems *= dims[fullDims]; } if (totalBytes > maxMem) { CaretLogInfo("collision between input and output filenames, reading input file into memory"); } else { CaretLogFine("collision between input and output filenames, reading input file into memory"); } vector scratchmem(totalElems); inputIO.readData(scratchmem.data(), dims.size(), vector()); inputIO.close();//don't have it try to close after being overwritten NiftiIO outputIO;//now we can open the output file outputIO.writeNew(outFileName, inputIO.getHeader(), outVer);//NOTE: this keeps data scaling fields, data type, extensions, header fields we ignore, etc outputIO.writeData(scratchmem.data(), dims.size(), vector()); } else { NiftiIO outputIO; outputIO.writeNew(outFileName, inputIO.getHeader(), outVer);//NOTE: this keeps data scaling fields, data type, extensions, header fields we ignore, etc int64_t totalBytes = (int)sizeof(T) * inputIO.getNumComponents(), totalElems = inputIO.getNumComponents(); int fullDims = 0; for (; fullDims < (int)dims.size() && totalBytes * dims[fullDims] < maxMem; ++fullDims) { totalBytes *= dims[fullDims]; totalElems *= dims[fullDims]; } vector remainDims(dims.begin() + fullDims, dims.end()); vector scratchmem(totalElems);//this is the main purpose of the template... for (MultiDimIterator myiter(remainDims); !myiter.atEnd(); ++myiter) { inputIO.readData(scratchmem.data(), fullDims, *myiter);//...which results in these templating over the desired type outputIO.writeData(scratchmem.data(), fullDims, *myiter); } } } } void OperationFileConvert::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); OptionalParameter* borderConv = myParams->getOptionalParameter(1); OptionalParameter* niftiConv = myParams->getOptionalParameter(2); OptionalParameter* ciftiConv = myParams->getOptionalParameter(3); int numChosen = 0; if (borderConv->m_present) ++numChosen; if (niftiConv->m_present) ++numChosen; if (ciftiConv->m_present) ++numChosen; if (numChosen != 1) throw OperationException("you must choose exactly one top level option"); if (borderConv->m_present) { const BorderFile* borderIn = borderConv->getBorder(1); int outVersion = (int)borderConv->getInteger(2); AString outFilename = borderConv->getString(3); BorderFile borderOut; int numBorders = borderIn->getNumberOfBorders(); if (borderIn->getNumberOfNodes() < 1)//version 1 border file { OptionalParameter* surfOpt = borderConv->getOptionalParameter(4); if (!surfOpt->m_present) throw OperationException("version 1 border files require the -surface suboption for conversion"); SurfaceFile* convSurf = surfOpt->getSurface(1); StructureEnum::Enum myStructure = convSurf->getStructure(); borderOut.setStructure(myStructure); borderOut.setNumberOfNodes(convSurf->getNumberOfNodes()); for (int i = 0; i < numBorders; ++i) { const Border* thisBorder = borderIn->getBorder(i); if (thisBorder->getStructure() == myStructure) { borderOut.addBorder(new Border(*thisBorder));//takes ownership of RAW POINTER } } } else { borderOut.setStructure(borderIn->getStructure()); borderOut.setNumberOfNodes(borderIn->getNumberOfNodes()); for (int i = 0; i < numBorders; ++i) { borderOut.addBorder(new Border(*borderIn->getBorder(i)));//ditto } } borderOut.writeFile(outFilename, outVersion); } if (niftiConv->m_present) { AString inFileName = niftiConv->getString(1); int outVer = (int)niftiConv->getInteger(2); AString outFileName = niftiConv->getString(3); const int64_t maxMem = 1<<29;//half gigabyte - will usually be much less, could be an option NiftiIO inputIO; inputIO.openRead(inFileName); const NiftiHeader& inHeader = inputIO.getHeader(); FileInformation outInfo(outFileName), inInfo(inFileName); bool collision = false; if (outInfo.getCanonicalFilePath() != "" && outInfo.getCanonicalFilePath() == inInfo.getCanonicalFilePath()) {//collision! can't do on-disk reading during writing collision = true; } double mult, offset;//if the offset is large compared to the scale, it is nontrivial to deduce a suitable type to preserve sufficient precision to round back to the unscaled input values if (inHeader.getDataScaling(mult, offset))//we can't use the on-disk type because we don't provide access to the unscaled (wrong) values {//alternatively, we could give read/write access to the unscaled values, but that is distasteful niftiConvertHelper(inputIO, outFileName, maxMem, outVer, collision);//so, always use long double to reduce rounding problems, even if it is often massive overkill } else { switch (inHeader.getDataType()) { case NIFTI_TYPE_UINT8: case NIFTI_TYPE_RGB24: niftiConvertHelper(inputIO, outFileName, maxMem, outVer, collision); break; case NIFTI_TYPE_INT8: niftiConvertHelper(inputIO, outFileName, maxMem, outVer, collision); break; case NIFTI_TYPE_UINT16: niftiConvertHelper(inputIO, outFileName, maxMem, outVer, collision); break; case NIFTI_TYPE_INT16: niftiConvertHelper(inputIO, outFileName, maxMem, outVer, collision); break; case NIFTI_TYPE_UINT32: niftiConvertHelper(inputIO, outFileName, maxMem, outVer, collision); break; case NIFTI_TYPE_INT32: niftiConvertHelper(inputIO, outFileName, maxMem, outVer, collision); break; case NIFTI_TYPE_UINT64: niftiConvertHelper(inputIO, outFileName, maxMem, outVer, collision); break; case NIFTI_TYPE_INT64: niftiConvertHelper(inputIO, outFileName, maxMem, outVer, collision); break; case NIFTI_TYPE_FLOAT32: case NIFTI_TYPE_COMPLEX64: niftiConvertHelper(inputIO, outFileName, maxMem, outVer, collision); break; case NIFTI_TYPE_FLOAT64: case NIFTI_TYPE_COMPLEX128: niftiConvertHelper(inputIO, outFileName, maxMem, outVer, collision); break; case NIFTI_TYPE_FLOAT128: case NIFTI_TYPE_COMPLEX256: niftiConvertHelper(inputIO, outFileName, maxMem, outVer, collision); break; default: throw OperationException("unsupported nifti datatype"); } } } if (ciftiConv->m_present) { CiftiFile* ciftiIn = ciftiConv->getCifti(1); AString versionString = ciftiConv->getString(2); AString outFileName = ciftiConv->getString(3); ciftiIn->writeFile(outFileName, CiftiVersion(versionString));//also handles complications like writing to the same file as it is set to read on-disk from } } workbench-1.1.1/src/Operations/OperationFileConvert.h000066400000000000000000000026031255417355300226730ustar00rootroot00000000000000#ifndef __OPERATION_FILE_CONVERT_H__ #define __OPERATION_FILE_CONVERT_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationFileConvert : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationFileConvert; } #endif //__OPERATION_FILE_CONVERT_H__ workbench-1.1.1/src/Operations/OperationFileInformation.cxx000066400000000000000000000153231255417355300241160ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretAssert.h" #include "CaretLogger.h" #include "CaretDataFile.h" #include "CaretDataFileHelper.h" #include "CaretMappableDataFile.h" #include "CaretPointer.h" #include "CiftiMappableDataFile.h" #include "DataFileContentInformation.h" #include "DataFileException.h" #include "OperationFileInformation.h" #include "OperationException.h" using namespace caret; using namespace std; /** * \class caret::OperationFileInformation * \brief List information about a file's content */ /** * @return Command line switch */ AString OperationFileInformation::getCommandSwitch() { return "-file-information"; } /** * @return Short description of operation */ AString OperationFileInformation::getShortDescription() { return "LIST INFORMATION ABOUT A FILE'S CONTENT"; } /** * @return Parameters for operation */ OperationParameters* OperationFileInformation::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addStringParameter(1, "data-file", "data file"); ret->createOptionalParameter(2, "-no-map-info", "do not show map information for files that support maps"); ret->createOptionalParameter(3, "-only-step-interval", "suppress normal output, print the interval between maps"); ret->createOptionalParameter(4, "-only-number-of-maps", "suppress normal output, print the number of maps"); ret->createOptionalParameter(5, "-only-map-names", "suppress normal output, print the names of all maps"); AString helpText("List information about the content of a data file. " "Only one -only option may be specified. " "The information listed when no -only option is present is dependent upon the type of data file."); ret->setHelpText(helpText); return ret; } /** * Use Parameters and perform operation */ void OperationFileInformation::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); const AString dataFileName = myParams->getString(1); const bool showMapInformationFlag = ! (myParams->getOptionalParameter(2)->m_present); int countOnlys = 0; bool onlyTimestep = myParams->getOptionalParameter(3)->m_present; if (onlyTimestep) ++countOnlys; bool onlyNumMaps = myParams->getOptionalParameter(4)->m_present; if (onlyNumMaps) ++countOnlys; bool onlyMapNames = myParams->getOptionalParameter(5)->m_present; if (onlyMapNames) ++countOnlys; if (countOnlys > 1) throw OperationException("only one -only-* option may be specified"); bool preferOnDisk = (!showMapInformationFlag || countOnlys != 0); CaretPointer caretDataFile; try { caretDataFile.grabNew(CaretDataFileHelper::readAnyCaretDataFile(dataFileName, preferOnDisk)); } catch (const DataFileException& dfe) { /* * If the name ends with ".nii" but was rejected by VolumeFile, it could be * an unsupported CIFTI file type. */ if (dataFileName.endsWith(".nii")) { try { DataFileContentInformation dataFileContentInformation; CiftiMappableDataFile::getDataFileContentInformationForGenericCiftiFile(dataFileName, dataFileContentInformation); cout << qPrintable(dataFileContentInformation.getInformationInString()) << endl; } catch (const DataFileException& dfe2) { throw DataFileException(dfe.whatString() + "\n" + "Also unsuccessful trying to read as generic CIFTI file."); } return; } else { throw dfe; } } if (onlyTimestep) { CaretMappableDataFile* mappableFile = dynamic_cast(caretDataFile.getPointer()); if (mappableFile == NULL) throw OperationException("file does not support maps");//TODO: also give error on things that it doesn't make sense on if (mappableFile->getMapIntervalUnits() == NiftiTimeUnitsEnum::NIFTI_UNITS_UNKNOWN) throw OperationException("file does not support series data"); float start, step; mappableFile->getMapIntervalStartAndStep(start, step); cout << step << endl; } if (onlyNumMaps) { CaretMappableDataFile* mappableFile = dynamic_cast(caretDataFile.getPointer()); if (mappableFile == NULL) throw OperationException("file does not support maps");//TODO: also give error on things that it doesn't make sense on int numMaps = mappableFile->getNumberOfMaps(); if (numMaps < 1) throw OperationException("file does not support maps"); cout << numMaps << endl; } if (onlyMapNames) { CaretMappableDataFile* mappableFile = dynamic_cast(caretDataFile.getPointer()); if (mappableFile == NULL) throw OperationException("file does not support maps");//TODO: also give error on things that it doesn't make sense on int numMaps = mappableFile->getNumberOfMaps(); if (numMaps < 1) throw OperationException("file does not support maps"); for (int i = 0; i < numMaps; ++i) { cout << mappableFile->getMapName(i) << endl; } } if (countOnlys == 0) { DataFileContentInformation dataFileContentInformation; dataFileContentInformation.setOptionFlag(DataFileContentInformation::OPTION_SHOW_MAP_INFORMATION, showMapInformationFlag); caretDataFile->addToDataFileContentInformation(dataFileContentInformation); cout << qPrintable(dataFileContentInformation.getInformationInString()) << endl; } } workbench-1.1.1/src/Operations/OperationFileInformation.h000066400000000000000000000027131255417355300235420ustar00rootroot00000000000000#ifndef __OPERATION_FILE_INFORMATION_H__ #define __OPERATION_FILE_INFORMATION_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationFileInformation : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationFileInformation; } // namespace #endif //__OPERATION_FILE_INFORMATION_H__ workbench-1.1.1/src/Operations/OperationFociGetProjectionVertex.cxx000066400000000000000000000136321255417355300256050ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationFociGetProjectionVertex.h" #include "OperationException.h" #include "FociFile.h" #include "Focus.h" #include "MetricFile.h" #include "SurfaceFile.h" #include "SurfaceProjectedItem.h" #include "SurfaceProjectionBarycentric.h" #include "SurfaceProjectionVanEssen.h" using namespace caret; using namespace std; AString OperationFociGetProjectionVertex::getCommandSwitch() { return "-foci-get-projection-vertex"; } AString OperationFociGetProjectionVertex::getShortDescription() { return "GET PROJECTION VERTEX FOR FOCI"; } OperationParameters* OperationFociGetProjectionVertex::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addFociParameter(1, "foci", "the foci file"); ret->addSurfaceParameter(2, "surface", "the surface related to the foci file"); ret->addMetricOutputParameter(3, "metric-out", "the output metric file"); OptionalParameter* nameOpt = ret->createOptionalParameter(4, "-name", "select a focus by name"); nameOpt->addStringParameter(1, "name", "the name of the focus"); ret->setHelpText( AString("For each focus, a column is created in , and the vertex with the most influence on its projection ") + "is assigned a value of 1 in that column, with all other vertices 0. " + "If -name is used, only one focus will be used." ); return ret; } void OperationFociGetProjectionVertex::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); const FociFile* myFoci = myParams->getFoci(1); const SurfaceFile* mySurf = myParams->getSurface(2); MetricFile* myMetricOut = myParams->getOutputMetric(3); OptionalParameter* nameOpt = myParams->getOptionalParameter(4); int numFoci = myFoci->getNumberOfFoci(); if (numFoci < 1) throw OperationException("Foci file has no foci"); int numNodes = mySurf->getNumberOfNodes(); if (nameOpt->m_present) { AString name = nameOpt->getString(1); myMetricOut->setNumberOfNodesAndColumns(numNodes, 1); myMetricOut->setStructure(mySurf->getStructure()); bool found = false; for (int i = 0; i < numFoci; ++i) { const Focus* myFocus = myFoci->getFocus(i); if (name == myFocus->getName()) { found = true; if (myFocus->getNumberOfProjections() < 1) throw OperationException("matching focus has no projections"); const SurfaceProjectedItem* firstItem = myFocus->getProjection(0); int whichNode = -1; if (firstItem->getBarycentricProjection()->isValid()) { const float* areas = firstItem->getBarycentricProjection()->getTriangleAreas(); const int32_t* nodes = firstItem->getBarycentricProjection()->getTriangleNodes(); int pos = 0; for (int i = 1; i < 3; ++i) { if (areas[i] > areas[pos]) pos = i; } whichNode = nodes[pos]; } else { throw OperationException("barycentric projection not valid");//someone else can worry about van essen projection, if it is needed } if (whichNode >= numNodes) throw OperationException("foci file has larger node numbers than surface"); myMetricOut->initializeColumn(0, 0.0f); myMetricOut->setColumnName(0, myFocus->getName()); myMetricOut->setValue(whichNode, 0, 1.0f); break; } } if (!found) throw OperationException("specified name did not match any foci"); } else { myMetricOut->setNumberOfNodesAndColumns(numNodes, numFoci); myMetricOut->setStructure(mySurf->getStructure()); for (int i = 0; i < numFoci; ++i) { const Focus* myFocus = myFoci->getFocus(i); if (myFocus->getNumberOfProjections() < 1) throw OperationException("matching focus has no projections"); const SurfaceProjectedItem* firstItem = myFocus->getProjection(0); int whichNode = -1; if (firstItem->getBarycentricProjection()->isValid()) { const float* areas = firstItem->getBarycentricProjection()->getTriangleAreas(); const int32_t* nodes = firstItem->getBarycentricProjection()->getTriangleNodes(); int pos = 0; for (int i = 1; i < 3; ++i) { if (areas[i] > areas[pos]) pos = i; } whichNode = nodes[pos]; } else { throw OperationException("barycentric projection not valid");//someone else can worry about van essen projection, if it is needed } if (whichNode >= numNodes) throw OperationException("foci file has larger node numbers than surface"); myMetricOut->initializeColumn(i, 0.0f); myMetricOut->setColumnName(i, myFocus->getName()); myMetricOut->setValue(whichNode, i, 1.0f); } } } workbench-1.1.1/src/Operations/OperationFociGetProjectionVertex.h000066400000000000000000000027211255417355300252270ustar00rootroot00000000000000#ifndef __OPERATION_FOCI_GET_PROJECTION_VERTEX_H__ #define __OPERATION_FOCI_GET_PROJECTION_VERTEX_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationFociGetProjectionVertex : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationFociGetProjectionVertex; } #endif //__OPERATION_FOCI_GET_PROJECTION_VERTEX_H__ workbench-1.1.1/src/Operations/OperationFociListCoords.cxx000066400000000000000000000062341255417355300237200ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationFociListCoords.h" #include "OperationException.h" #include "FociFile.h" #include "Focus.h" #include using namespace caret; using namespace std; AString OperationFociListCoords::getCommandSwitch() { return "-foci-list-coords"; } AString OperationFociListCoords::getShortDescription() { return "OUTPUT FOCI COORDINATES IN A TEXT FILE"; } OperationParameters* OperationFociListCoords::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addFociParameter(1, "foci-file", "input foci file"); ret->addStringParameter(2, "coord-file-out", "output - the output coordinate text file"); OptionalParameter* namesOpt = ret->createOptionalParameter(3, "-names-out", "output the foci names"); namesOpt->addStringParameter(1, "names-file-out", "output - text file to put foci names in"); ret->setHelpText( AString("Output the coordinates for every focus in the foci file, and optionally the focus names in a second text file.") ); return ret; } void OperationFociListCoords::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); FociFile* myFoci = myParams->getFoci(1);//gets the surface with key 1 AString coordFileName = myParams->getString(2); OptionalParameter* namesOpt = myParams->getOptionalParameter(3); bool outputNames = false; AString nameFileName; if (namesOpt->m_present) { outputNames = true; nameFileName = namesOpt->getString(1); } ofstream coordFile(coordFileName.toLocal8Bit().constData()); if (!coordFile.good()) { throw OperationException("failed to open coordinate output file for writing"); } fstream nameFile; if (outputNames) { nameFile.open(nameFileName.toLocal8Bit().constData(), fstream::out); if (!nameFile.good()) { throw OperationException("failed to open name output file for writing"); } } int numFoci = myFoci->getNumberOfFoci(); for (int i = 0; i < numFoci; ++i) { const Focus* myFocus = myFoci->getFocus(i); const float* coords = myFocus->getSearchXYZ(); coordFile << coords[0] << " " << coords[1] << " " << coords[2] << endl; if (outputNames) { nameFile << myFocus->getName() << endl; } } } workbench-1.1.1/src/Operations/OperationFociListCoords.h000066400000000000000000000026301255417355300233410ustar00rootroot00000000000000#ifndef __OPERATION_FOCI_LIST_COORDS_H__ #define __OPERATION_FOCI_LIST_COORDS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationFociListCoords : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationFociListCoords; } #endif //__OPERATION_FOCI_LIST_COORDS_H__ workbench-1.1.1/src/Operations/OperationLabelExportTable.cxx000066400000000000000000000060721255417355300242230ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationLabelExportTable.h" #include "OperationException.h" #include "GiftiLabel.h" #include "GiftiLabelTable.h" #include "LabelFile.h" #include #include using namespace caret; using namespace std; AString OperationLabelExportTable::getCommandSwitch() { return "-label-export-table"; } AString OperationLabelExportTable::getShortDescription() { return "EXPORT LABEL TABLE FROM GIFTI AS TEXT"; } OperationParameters* OperationLabelExportTable::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addLabelParameter(1, "label-in", "the input label file"); ret->addStringParameter(2, "table-out", "output - the output text file");//fake output formatting ret->setHelpText( AString("Takes the label table from the gifti label file, and writes it to a text format matching what is expected by -metric-label-import.") ); return ret; } int OperationLabelExportTable::floatTo255(const float& in) { return (int)floor(in * 255.0f + 0.5f); } void OperationLabelExportTable::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); LabelFile* myLabel = myParams->getLabel(1); AString outName = myParams->getString(2); ofstream outFile(outName.toLocal8Bit().constData()); if (!outFile) throw OperationException("failed to open output text file"); const GiftiLabelTable* myTable = myLabel->getLabelTable(); set allKeys = myTable->getKeys(); int32_t unassignedKey = myTable->getUnassignedLabelKey(); for (set::iterator iter = allKeys.begin(); iter != allKeys.end(); ++iter) { if (*iter == unassignedKey) continue;//don't output the unused key, because import doesn't want it in the text file const GiftiLabel* thisLabel = myTable->getLabel(*iter); outFile << thisLabel->getName() << endl; outFile << thisLabel->getKey() << " " << floatTo255(thisLabel->getRed()) << " " << floatTo255(thisLabel->getGreen()) << " " << floatTo255(thisLabel->getBlue()) << " " << floatTo255(thisLabel->getAlpha()) << endl; if (!outFile) throw OperationException("error writing to output text file"); } } workbench-1.1.1/src/Operations/OperationLabelExportTable.h000066400000000000000000000027241255417355300236500ustar00rootroot00000000000000#ifndef __OPERATION_LABEL_EXPORT_TABLE_H__ #define __OPERATION_LABEL_EXPORT_TABLE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationLabelExportTable : public AbstractOperation { static int floatTo255(const float& in); public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationLabelExportTable; } #endif //__OPERATION_LABEL_EXPORT_TABLE_H__ workbench-1.1.1/src/Operations/OperationLabelMask.cxx000066400000000000000000000112221255417355300226560ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationLabelMask.h" #include "OperationException.h" #include "GiftiLabelTable.h" #include "LabelFile.h" #include "MetricFile.h" using namespace caret; using namespace std; AString OperationLabelMask::getCommandSwitch() { return "-label-mask"; } AString OperationLabelMask::getShortDescription() { return "MASK A LABEL FILE"; } OperationParameters* OperationLabelMask::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addLabelParameter(1, "label", "the label file to mask"); ret->addMetricParameter(2, "mask", "the mask metric"); ret->addLabelOutputParameter(3, "label-out", "the output label file"); OptionalParameter* columnSelect = ret->createOptionalParameter(4, "-column", "select a single column"); columnSelect->addStringParameter(1, "column", "the column number or name"); ret->setHelpText( AString("By default, the output label is a copy of the input label, but with the 'unused' label wherever the mask metric is not positive. ") + "if -column is specified, the output contains only one column, the masked version of the specified input column." ); return ret; } void OperationLabelMask::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); LabelFile* myLabel = myParams->getLabel(1); MetricFile* myMask = myParams->getMetric(2); int32_t numNodes = myLabel->getNumberOfNodes(), numCols = myLabel->getNumberOfColumns(); if (myMask->getNumberOfNodes() != numNodes) { throw OperationException("mask metric must have the same number of vertices"); } LabelFile* myLabelOut = myParams->getOutputLabel(3);//gets the output metric with key 2 OptionalParameter* columnSelect = myParams->getOptionalParameter(4);//gets optional parameter with key 3 int columnNum = -1; if (columnSelect->m_present) {//set up to use the single column columnNum = (int)myLabel->getMapIndexFromNameOrNumber(columnSelect->getString(1)); if (columnNum < 0 || columnNum >= numCols) { throw OperationException("invalid column specified"); } } vector columnScratch(numNodes); int32_t unusedKey = myLabel->getLabelTable()->getUnassignedLabelKey(); if (columnNum == -1) { myLabelOut->setNumberOfNodesAndColumns(numNodes, numCols); myLabelOut->setStructure(myLabel->getStructure()); *(myLabelOut->getLabelTable()) = *(myLabel->getLabelTable()); const float* roiColumn = myMask->getValuePointerForColumn(0); for (int i = 0; i < numCols; ++i) { myLabelOut->setColumnName(i, myLabel->getColumnName(i)); const int32_t* keyColumn = myLabel->getLabelKeyPointerForColumn(i); for (int j = 0; j < numNodes; ++j) { if (roiColumn[j] > 0.0f) { columnScratch[j] = keyColumn[j]; } else { columnScratch[j] = unusedKey; } } myLabelOut->setLabelKeysForColumn(i, columnScratch.data()); } } else { myLabelOut->setNumberOfNodesAndColumns(numNodes, 1); myLabelOut->setStructure(myLabel->getStructure()); *(myLabelOut->getLabelTable()) = *(myLabel->getLabelTable()); myLabelOut->setColumnName(0, myLabel->getColumnName(columnNum)); const float* roiColumn = myMask->getValuePointerForColumn(0); const int32_t* keyColumn = myLabel->getLabelKeyPointerForColumn(columnNum); for (int j = 0; j < numNodes; ++j) { if (roiColumn[j] > 0.0f) { columnScratch[j] = keyColumn[j]; } else { columnScratch[j] = unusedKey; } } myLabelOut->setLabelKeysForColumn(0, columnScratch.data()); } } workbench-1.1.1/src/Operations/OperationLabelMask.h000066400000000000000000000025671255417355300223170ustar00rootroot00000000000000#ifndef __OPERATION_LABEL_MASK_H__ #define __OPERATION_LABEL_MASK_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationLabelMask : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationLabelMask; } #endif //__OPERATION_LABEL_MASK_H__ workbench-1.1.1/src/Operations/OperationLabelMerge.cxx000066400000000000000000000221301255417355300230220ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationLabelMerge.h" #include "OperationException.h" #include "GiftiLabelTable.h" #include "LabelFile.h" #include #include using namespace caret; using namespace std; AString OperationLabelMerge::getCommandSwitch() { return "-label-merge"; } AString OperationLabelMerge::getShortDescription() { return "MERGE LABEL FILES INTO A NEW FILE"; } OperationParameters* OperationLabelMerge::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addLabelOutputParameter(1, "label-out", "the output label"); ParameterComponent* labelOpt = ret->createRepeatableParameter(2, "-label", "specify an input label"); labelOpt->addLabelParameter(1, "label-in", "a label file to use columns from"); ParameterComponent* columnOpt = labelOpt->createRepeatableParameter(2, "-column", "select a single column to use"); columnOpt->addStringParameter(1, "column", "the column number or name"); OptionalParameter* upToOpt = columnOpt->createOptionalParameter(2, "-up-to", "use an inclusive range of columns"); upToOpt->addStringParameter(1, "last-column", "the number or name of the last column to include"); upToOpt->createOptionalParameter(2, "-reverse", "use the range in reverse order"); ret->setHelpText( AString("Takes one or more label files and constructs a new label file by concatenating columns from them. ") + "The input files must have the same number of vertices and the same structure.\n\n" + "Example: wb_command -label-merge out.label.gii -label first.label.gii -column 1 -label second.label.gii\n\n" + "This example would take the first column from first.label.gii and all subvolumes from second.label.gii, " + "and write these to out.label.gii." ); return ret; } namespace {//private namespace for helper functions void doRemap(const int32_t* input, const map& remap, const int numElems, const int32_t& unlabeledValue, int32_t* output) { for (int i = 0; i < numElems; ++i) { map::const_iterator iter = remap.find(input[i]); if (iter != remap.end()) { output[i] = iter->second; } else {//values that have no key output[i] = unlabeledValue; } } } } void OperationLabelMerge::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); LabelFile* myLabelOut = myParams->getOutputLabel(1); const vector& myInputs = *(myParams->getRepeatableParameterInstances(2)); int numInputs = (int)myInputs.size(); if (numInputs == 0) throw OperationException("no inputs specified"); const LabelFile* firstLabel = myInputs[0]->getLabel(1); int numOutColumns = 0; int numNodes = firstLabel->getNumberOfNodes(); StructureEnum::Enum myStruct = firstLabel->getStructure(); GiftiLabelTable outTable; vector > fileRemap(numInputs); for (int i = 0; i < numInputs; ++i) { LabelFile* inputLabel = myInputs[i]->getLabel(1); fileRemap[i] = outTable.append(*(inputLabel->getLabelTable()));//NOTE: does (and must) include identity mappings - anything that doesn't match was invalid in the original file if (numNodes != inputLabel->getNumberOfNodes()) throw OperationException("file '" + inputLabel->getFileName() + "' has a different number of nodes than the first"); if (myStruct != inputLabel->getStructure()) throw OperationException("file '" + inputLabel->getFileName() + "' has a different structure than the first"); const vector& columnOpts = *(myInputs[i]->getRepeatableParameterInstances(2)); int numColumnOpts = (int)columnOpts.size(); if (numColumnOpts > 0) { for (int j = 0; j < numColumnOpts; ++j) { int initialColumn = inputLabel->getMapIndexFromNameOrNumber(columnOpts[j]->getString(1)); if (initialColumn < 0) throw OperationException("column '" + columnOpts[j]->getString(1) + "' not found in file '" + inputLabel->getFileName() + "'"); OptionalParameter* upToOpt = columnOpts[j]->getOptionalParameter(2); if (upToOpt->m_present) { int finalColumn = inputLabel->getMapIndexFromNameOrNumber(upToOpt->getString(1)); if (finalColumn < 0) throw OperationException("ending column '" + upToOpt->getString(1) + "' not found in file '" + inputLabel->getFileName() + "'"); if (finalColumn < initialColumn) throw OperationException("ending column '" + upToOpt->getString(1) + "' occurs before starting column '" + columnOpts[j]->getString(1) + "' in file '" + inputLabel->getFileName() + "'"); numOutColumns += finalColumn - initialColumn + 1;//inclusive - we don't need to worry about reversing for counting, though } else { numOutColumns += 1; } } } else { numOutColumns += inputLabel->getNumberOfColumns(); } } myLabelOut->setNumberOfNodesAndColumns(numNodes, numOutColumns); myLabelOut->setStructure(myStruct); *(myLabelOut->getLabelTable()) = outTable; vector scratchCol(numNodes); int curColumn = 0; for (int i = 0; i < numInputs; ++i) { const LabelFile* inputLabel = myInputs[i]->getLabel(1); const vector& columnOpts = *(myInputs[i]->getRepeatableParameterInstances(2)); int numColumnOpts = (int)columnOpts.size(); if (numColumnOpts > 0) { for (int j = 0; j < numColumnOpts; ++j) { int initialColumn = inputLabel->getMapIndexFromNameOrNumber(columnOpts[j]->getString(1)); OptionalParameter* upToOpt = columnOpts[j]->getOptionalParameter(2); if (upToOpt->m_present) { int finalColumn = inputLabel->getMapIndexFromNameOrNumber(upToOpt->getString(1)); bool reverse = upToOpt->getOptionalParameter(2)->m_present; if (reverse) { for (int c = finalColumn; c >= initialColumn; --c) { doRemap(inputLabel->getLabelKeyPointerForColumn(c), fileRemap[i], numNodes, outTable.getUnassignedLabelKey(), scratchCol.data()); myLabelOut->setLabelKeysForColumn(curColumn, scratchCol.data()); myLabelOut->setColumnName(curColumn, inputLabel->getColumnName(c)); ++curColumn; } } else { for (int c = initialColumn; c <= finalColumn; ++c) { doRemap(inputLabel->getLabelKeyPointerForColumn(c), fileRemap[i], numNodes, outTable.getUnassignedLabelKey(), scratchCol.data()); myLabelOut->setLabelKeysForColumn(curColumn, scratchCol.data()); myLabelOut->setColumnName(curColumn, inputLabel->getColumnName(c)); ++curColumn; } } } else { doRemap(inputLabel->getLabelKeyPointerForColumn(initialColumn), fileRemap[i], numNodes, outTable.getUnassignedLabelKey(), scratchCol.data()); myLabelOut->setLabelKeysForColumn(curColumn, scratchCol.data()); myLabelOut->setColumnName(curColumn, inputLabel->getColumnName(initialColumn)); ++curColumn; } } } else { int numColumns = inputLabel->getNumberOfColumns(); for (int j = 0; j < numColumns; ++j) { doRemap(inputLabel->getLabelKeyPointerForColumn(j), fileRemap[i], numNodes, outTable.getUnassignedLabelKey(), scratchCol.data()); myLabelOut->setLabelKeysForColumn(curColumn, scratchCol.data()); myLabelOut->setColumnName(curColumn, inputLabel->getColumnName(j)); ++curColumn; } } } CaretAssert(curColumn == numOutColumns); } workbench-1.1.1/src/Operations/OperationLabelMerge.h000066400000000000000000000025751255417355300224620ustar00rootroot00000000000000#ifndef __OPERATION_LABEL_MERGE_H__ #define __OPERATION_LABEL_MERGE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationLabelMerge : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationLabelMerge; } #endif //__OPERATION_LABEL_MERGE_H__ workbench-1.1.1/src/Operations/OperationMetadataRemoveProvenance.cxx000066400000000000000000000127171255417355300257540ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationMetadataRemoveProvenance.h" #include "OperationException.h" #include "CiftiFile.h" #include "DataFileTypeEnum.h" #include "LabelFile.h" #include "MetricFile.h" #include "VolumeFile.h" #include "FileInformation.h" using namespace caret; using namespace std; AString OperationMetadataRemoveProvenance::getCommandSwitch() { return "-metadata-remove-provenance"; } AString OperationMetadataRemoveProvenance::getShortDescription() { return "REMOVE PROVENANCE INFORMATION FROM FILE METADATA"; } OperationParameters* OperationMetadataRemoveProvenance::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addStringParameter(1, "input-file", "the file to remove provenance information from"); ret->addStringParameter(2, "output-file", "output - the name to save the modified file as");//HACK: fake the output format, since it doesn't know what kind of file it is ret->setHelpText( AString("Removes the provenance metadata fields added by workbench during processing.") ); return ret; } void OperationMetadataRemoveProvenance::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); AString inFileName = myParams->getString(1); AString outFileName = myParams->getString(2); bool ok = false; DataFileTypeEnum::Enum myType = DataFileTypeEnum::fromFileExtension(inFileName, &ok); if (!ok) { throw OperationException("unrecognized data file type"); } switch (myType) { case DataFileTypeEnum::METRIC: { MetricFile myMetric; myMetric.readFile(inFileName); removeProvenance(myMetric.getFileMetaData()); myMetric.writeFile(outFileName); break; } case DataFileTypeEnum::LABEL: { LabelFile myLabel; myLabel.readFile(inFileName); removeProvenance(myLabel.getFileMetaData()); myLabel.writeFile(outFileName); break; } case DataFileTypeEnum::VOLUME: { VolumeFile myVol; myVol.readFile(inFileName); removeProvenance(myVol.getFileMetaData()); myVol.writeFile(outFileName); break; } case DataFileTypeEnum::CONNECTIVITY_DENSE: case DataFileTypeEnum::CONNECTIVITY_DENSE_LABEL: case DataFileTypeEnum::CONNECTIVITY_DENSE_PARCEL: case DataFileTypeEnum::CONNECTIVITY_DENSE_SCALAR: case DataFileTypeEnum::CONNECTIVITY_DENSE_TIME_SERIES: case DataFileTypeEnum::CONNECTIVITY_PARCEL: case DataFileTypeEnum::CONNECTIVITY_PARCEL_DENSE: { CiftiFile myCifti; FileInformation myInfo1(inFileName), myInfo2(outFileName); myCifti.openFile(inFileName); if (myInfo1.getCanonicalFilePath() == myInfo2.getCanonicalFilePath()) { myCifti.convertToInMemory(); } CiftiXMLOld myXML = myCifti.getCiftiXMLOld(); CiftiFile myOutCifti; myOutCifti.setWritingFile(outFileName); int numRows = myXML.getNumberOfRows(), rowSize = myXML.getNumberOfColumns(); removeProvenance(myXML.getFileMetaData()); myOutCifti.setCiftiXML(myXML, false); vector scratchRow(rowSize); for (int i = 0; i < numRows; ++i) { myCifti.getRow(scratchRow.data(), i); myOutCifti.setRow(scratchRow.data(), i); } myOutCifti.writeFile(outFileName); break; } default: throw OperationException("file type not supported in metadata remove provenance"); } } void OperationMetadataRemoveProvenance::removeProvenance(GiftiMetaData* toModify) { if (toModify == NULL) return; set provenanceNames = getProvenanceKeys(); for (set::iterator iter = provenanceNames.begin(); iter != provenanceNames.end(); ++iter) { toModify->remove(*iter); } } void OperationMetadataRemoveProvenance::removeProvenance(map* toModify) { if (toModify == NULL) return; set provenanceNames = getProvenanceKeys(); for (set::iterator iter = provenanceNames.begin(); iter != provenanceNames.end(); ++iter) { toModify->erase(*iter); } } set OperationMetadataRemoveProvenance::getProvenanceKeys() { set ret; ret.insert("Provenance");//these should really be in a common location somewhere ret.insert("ParentProvenance"); ret.insert("ProgramProvenance"); ret.insert("WorkingDirectory"); return ret; } workbench-1.1.1/src/Operations/OperationMetadataRemoveProvenance.h000066400000000000000000000033221255417355300253710ustar00rootroot00000000000000#ifndef __OPERATION_METADATA_REMOVE_PROVENANCE_H__ #define __OPERATION_METADATA_REMOVE_PROVENANCE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" #include #include namespace caret { class GiftiMetaData; class OperationMetadataRemoveProvenance : public AbstractOperation { static void removeProvenance(GiftiMetaData* toModify); static void removeProvenance(std::map* toModify); static std::set getProvenanceKeys(); public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationMetadataRemoveProvenance; } #endif //__OPERATION_METADATA_REMOVE_PROVENANCE_H__ workbench-1.1.1/src/Operations/OperationMetadataStringReplace.cxx000066400000000000000000000205541255417355300252360ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationMetadataStringReplace.h" #include "OperationException.h" #include "CiftiFile.h" #include "DataFileTypeEnum.h" #include "LabelFile.h" #include "MetricFile.h" #include "VolumeFile.h" #include "FileInformation.h" using namespace caret; using namespace std; AString OperationMetadataStringReplace::getCommandSwitch() { return "-metadata-string-replace"; } AString OperationMetadataStringReplace::getShortDescription() { return "REPLACE A STRING IN ALL METADATA OF A FILE"; } OperationParameters* OperationMetadataStringReplace::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addStringParameter(1, "input-file", "the file to replace metadata in"); ret->addStringParameter(2, "find-string", "the string to find"); ret->addStringParameter(3, "replace-string", "the string to replace with"); ret->addStringParameter(4, "output-file", "output - the name to save the modified file as");//HACK: fake the output format, since it doesn't know what kind of file it is ret->createOptionalParameter(5, "-case-insensitive", "match with case variation also"); ret->setHelpText( AString("Replaces all occurrences of in the metadata and map names of with .") ); return ret; } void OperationMetadataStringReplace::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); AString inFileName = myParams->getString(1); AString findString = myParams->getString(2); AString replString = myParams->getString(3); AString outFileName = myParams->getString(4); Qt::CaseSensitivity myCS = Qt::CaseSensitive; if (myParams->getOptionalParameter(5)->m_present) { myCS = Qt::CaseInsensitive; } bool ok = false; DataFileTypeEnum::Enum myType = DataFileTypeEnum::fromFileExtension(inFileName, &ok); if (!ok) { throw OperationException("unrecognized data file type"); } switch (myType) { case DataFileTypeEnum::METRIC: { MetricFile myMetric; myMetric.readFile(inFileName); replaceInMetaData(myMetric.getFileMetaData(), findString, replString, myCS); int32_t numMaps = myMetric.getNumberOfMaps(); for (int32_t map = 0; map < numMaps; ++map) { replaceInMetaData(myMetric.getMapMetaData(map), findString, replString, myCS); AString mapName = myMetric.getMapName(map); myMetric.setMapName(map, mapName.replace(findString, replString, myCS)); } myMetric.writeFile(outFileName); break; } case DataFileTypeEnum::LABEL: { LabelFile myLabel; myLabel.readFile(inFileName); replaceInMetaData(myLabel.getFileMetaData(), findString, replString, myCS); int32_t numMaps = myLabel.getNumberOfMaps(); for (int32_t map = 0; map < numMaps; ++map) { replaceInMetaData(myLabel.getMapMetaData(map), findString, replString, myCS); AString mapName = myLabel.getMapName(map); myLabel.setMapName(map, mapName.replace(findString, replString, myCS)); } myLabel.writeFile(outFileName); break; } case DataFileTypeEnum::VOLUME: { VolumeFile myVol; myVol.readFile(inFileName); replaceInMetaData(myVol.getFileMetaData(), findString, replString, myCS); int32_t numMaps = myVol.getNumberOfMaps(); for (int32_t map = 0; map < numMaps; ++map) { replaceInMetaData(myVol.getMapMetaData(map), findString, replString, myCS); AString mapName = myVol.getMapName(map); myVol.setMapName(map, mapName.replace(findString, replString, myCS)); } myVol.writeFile(outFileName); break; } case DataFileTypeEnum::CONNECTIVITY_DENSE: case DataFileTypeEnum::CONNECTIVITY_DENSE_LABEL: case DataFileTypeEnum::CONNECTIVITY_DENSE_PARCEL: case DataFileTypeEnum::CONNECTIVITY_DENSE_SCALAR: case DataFileTypeEnum::CONNECTIVITY_DENSE_TIME_SERIES: case DataFileTypeEnum::CONNECTIVITY_PARCEL: case DataFileTypeEnum::CONNECTIVITY_PARCEL_DENSE: { CiftiFile myCifti; FileInformation myInfo1(inFileName), myInfo2(outFileName); myCifti.openFile(inFileName); if (myInfo1.getCanonicalFilePath() == myInfo2.getCanonicalFilePath()) { myCifti.convertToInMemory(); } CiftiXMLOld myXML = myCifti.getCiftiXMLOld(); CiftiFile myOutCifti; myOutCifti.setWritingFile(outFileName); int numRows = myXML.getNumberOfRows(), rowSize = myXML.getNumberOfColumns(); replaceInMetaData(myXML.getFileMetaData(), findString, replString, myCS); if (myXML.getMappingType(CiftiXMLOld::ALONG_ROW) == CIFTI_INDEX_TYPE_SCALARS || myXML.getMappingType(CiftiXMLOld::ALONG_ROW) == CIFTI_INDEX_TYPE_LABELS) { for (int row = 0; row < numRows; ++row) { AString mapName = myXML.getMapName(CiftiXMLOld::ALONG_ROW, row); myXML.setMapNameForIndex(CiftiXMLOld::ALONG_ROW, row, mapName.replace(findString, replString, myCS)); replaceInMetaData(myXML.getMapMetadata(CiftiXMLOld::ALONG_ROW, row), findString, replString, myCS); } } if (myXML.getMappingType(CiftiXMLOld::ALONG_COLUMN) == CIFTI_INDEX_TYPE_SCALARS || myXML.getMappingType(CiftiXMLOld::ALONG_COLUMN) == CIFTI_INDEX_TYPE_LABELS) { for (int col = 0; col < rowSize; ++col) { AString mapName = myXML.getMapName(CiftiXMLOld::ALONG_COLUMN, col); myXML.setMapNameForIndex(CiftiXMLOld::ALONG_COLUMN, col, mapName.replace(findString, replString, myCS)); replaceInMetaData(myXML.getMapMetadata(CiftiXMLOld::ALONG_COLUMN, col), findString, replString, myCS); } } myOutCifti.setCiftiXML(myXML, false); vector scratchRow(rowSize); for (int i = 0; i < numRows; ++i) { myCifti.getRow(scratchRow.data(), i); myOutCifti.setRow(scratchRow.data(), i); } myOutCifti.writeFile(outFileName); break; } default: throw OperationException("file type not supported in metadata string replace"); } } void OperationMetadataStringReplace::replaceInMetaData(GiftiMetaData* toModify, const AString& findStr, const AString& replStr, const Qt::CaseSensitivity& myCS) { if (toModify == NULL) return; vector metaNames = toModify->getAllMetaDataNames(); int64_t numNames = (int64_t)metaNames.size(); for (int64_t i = 0; i < numNames; ++i) { AString value = toModify->get(metaNames[i]); toModify->set(metaNames[i], value.replace(findStr, replStr, myCS)); } } void OperationMetadataStringReplace::replaceInMetaData(map* toModify, const AString& findStr, const AString& replStr, const Qt::CaseSensitivity& myCS) { if (toModify == NULL) return; for (map::iterator iter = toModify->begin(); iter != toModify->end(); ++iter) { iter->second.replace(findStr, replStr, myCS);//replace actually modifies the string itself } } workbench-1.1.1/src/Operations/OperationMetadataStringReplace.h000066400000000000000000000034371255417355300246640ustar00rootroot00000000000000#ifndef __OPERATION_METADATA_STRING_REPLACE_H__ #define __OPERATION_METADATA_STRING_REPLACE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" #include namespace caret { class GiftiMetaData; class OperationMetadataStringReplace : public AbstractOperation { static void replaceInMetaData(GiftiMetaData* toModify, const AString& findStr, const AString& replStr, const Qt::CaseSensitivity& myCS); static void replaceInMetaData(std::map* toModify, const AString& findStr, const AString& replStr, const Qt::CaseSensitivity& myCS); public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationMetadataStringReplace; } #endif //__OPERATION_METADATA_STRING_REPLACE_H__ workbench-1.1.1/src/Operations/OperationMetricConvert.cxx000066400000000000000000000126561255417355300236230ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationMetricConvert.h" #include "OperationException.h" #include "CaretLogger.h" #include "FloatMatrix.h" #include "MetricFile.h" #include "SurfaceFile.h" #include "VolumeFile.h" #include using namespace caret; using namespace std; AString OperationMetricConvert::getCommandSwitch() { return "-metric-convert"; } AString OperationMetricConvert::getShortDescription() { return "CONVERT METRIC FILE TO FAKE NIFTI"; } OperationParameters* OperationMetricConvert::getParameters() { OperationParameters* ret = new OperationParameters(); OptionalParameter* toNifti = ret->createOptionalParameter(1, "-to-nifti", "convert metric to nifti"); toNifti->addMetricParameter(1, "metric-in", "the metric to convert"); toNifti->addVolumeOutputParameter(2, "nifti-out", "the output nifti file");//we can use VolumeFile because it is 4D, though some spatial dimensions may be singular OptionalParameter* fromNifti = ret->createOptionalParameter(2, "-from-nifti", "convert nifti to metric"); fromNifti->addVolumeParameter(1, "nifti-in", "the nifti file to convert"); fromNifti->addSurfaceParameter(2, "surface-in", "surface file to use number of nodes and structure from"); fromNifti->addMetricOutputParameter(3, "metric-out", "the output metric file"); ret->setHelpText( AString("The purpose of this command is to convert between metric files and nifti1 so that gifti-unaware programs can operate on the data. ") + "You must specify exactly one of the options." ); return ret; } void OperationMetricConvert::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); int modes = 0; OptionalParameter* toNifti = myParams->getOptionalParameter(1); if (toNifti->m_present) ++modes; OptionalParameter* fromNifti = myParams->getOptionalParameter(2); if (fromNifti->m_present) ++modes; if (modes != 1) { throw OperationException("you must specify exactly one conversion mode"); } if (toNifti->m_present) { MetricFile* myMetric = toNifti->getMetric(1); VolumeFile* outVolume = toNifti->getOutputVolume(2); int numNodes = myMetric->getNumberOfNodes(), numCols = myMetric->getNumberOfColumns(); if (numCols > 32767)//max of short { throw OperationException("number of metric columns exceeds nifti1 limit, failing"); } vector myDims(4, 1); myDims[3] = numCols; if (numNodes > 32767) { myDims[0] = 32767; int temp = (numNodes - 1) / 32767 + 1;//round up instead of down if (temp > 32767)//should be rare to the point of nonexistence, but technically possible { myDims[1] = 32767; myDims[2] = (temp - 1) / 32767 + 1; if (myDims[2] > 32767) throw OperationException("number of vertices too large for nifti1 spatial dimensions, failing"); } else { myDims[1] = temp; } } else { myDims[0] = numNodes; } int frameSize = myDims[0] * myDims[1] * myDims[2]; vector scratchFrame(frameSize, 0.0f); FloatMatrix mySpace = FloatMatrix::identity(4); mySpace[0][0] = -1;//use radiological sform so FSL doesn't do stupid stuff outVolume->reinitialize(myDims, mySpace.getMatrix()); for (int i = 0; i < numCols; ++i) { const float* myCol = myMetric->getValuePointerForColumn(i); for (int j = 0; j < numNodes; ++j) { scratchFrame[j] = myCol[j]; } outVolume->setFrame(scratchFrame.data(), i); } } if (fromNifti->m_present) { VolumeFile* myNifti = fromNifti->getVolume(1); SurfaceFile* mySurf = fromNifti->getSurface(2); MetricFile* outMetric = fromNifti->getOutputMetric(3); vector myDims = myNifti->getDimensions(); int numNodes = mySurf->getNumberOfNodes(); int64_t frameSize = myDims[0] * myDims[1] * myDims[2]; if (frameSize < numNodes) { throw OperationException("nifti file does not have dimensions large enough to satisfy specified number of nodes"); } int numCols = (int)myDims[3];//if someone manages over 2 billion timepoints, reward them with truncation outMetric->setNumberOfNodesAndColumns(numNodes, numCols); outMetric->setStructure(mySurf->getStructure()); for (int i = 0; i < numCols; ++i) { outMetric->setValuesForColumn(i, myNifti->getFrame(i)); } } } workbench-1.1.1/src/Operations/OperationMetricConvert.h000066400000000000000000000026171255417355300232440ustar00rootroot00000000000000#ifndef __OPERATION_METRIC_CONVERT_H__ #define __OPERATION_METRIC_CONVERT_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationMetricConvert : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationMetricConvert; } #endif //__OPERATION_METRIC_CONVERT_H__ workbench-1.1.1/src/Operations/OperationMetricLabelImport.cxx000066400000000000000000000334521255417355300244120ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationMetricLabelImport.h" #include "OperationException.h" #include "CaretLogger.h" #include "FileInformation.h" #include "GiftiLabel.h" #include "GiftiLabelTable.h" #include "LabelFile.h" #include "MetricFile.h" #include #include #include #include #include #include using namespace caret; using namespace std; AString OperationMetricLabelImport::getCommandSwitch() { return "-metric-label-import"; } AString OperationMetricLabelImport::getShortDescription() { return "IMPORT A GIFTI LABEL FILE FROM A METRIC FILE"; } OperationParameters* OperationMetricLabelImport::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addMetricParameter(1, "input", "the input metric file"); ret->addStringParameter(2, "label-list-file", "text file containing the values and names for labels"); ret->addLabelOutputParameter(3, "output", "the output gifti label file"); ret->createOptionalParameter(4, "-discard-others", "set any values not mentioned in the label list to the ??? label"); OptionalParameter* unlabeledOption = ret->createOptionalParameter(5, "-unlabeled-value", "set the value that will be interpreted as unlabeled"); unlabeledOption->addIntegerParameter(1, "value", "the numeric value for unlabeled (default 0)"); OptionalParameter* columnSelect = ret->createOptionalParameter(6, "-column", "select a single column to import"); columnSelect->addStringParameter(1, "column", "the column number or name"); ret->createOptionalParameter(7, "-drop-unused-labels", "remove any unused label values from the label table"); ret->setHelpText( AString("Creates a new gifti label file from a metric file with label-like values. ") + "You may specify the empty string ('' will work on linux/mac) for , which will be treated as if it is an empty file. " + "The label list file must have lines of the following format:\n\n" + "\n \n\n" + "Do not specify the \"unlabeled\" key in the file, it is assumed that 0 means not labeled unless -unlabeled-value is specified. " + "Label names must be on a separate line, but may contain spaces or other unusual characters (but not newline). " + "Whitespace is trimmed from both ends of the label name, but is kept if it is in the middle of a label. " + "The values of red, green, blue and alpha must be integers from 0 to 255, and will specify the color the label is drawn as " + "(alpha of 255 means opaque, which is probably what you want). " + "By default, it will set new label names with names of LABEL_# for any values encountered that are not mentioned in the " + "list file, specify -discard-others to instead set these voxels to the \"unlabeled\" key." ); return ret; } void OperationMetricLabelImport::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); MetricFile* myMetric = myParams->getMetric(1); AString listfileName = myParams->getString(2); LabelFile* myLabelOut = myParams->getOutputLabel(3); bool discardOthers = myParams->getOptionalParameter(4)->m_present; int32_t unlabeledValue = 0; OptionalParameter* unlabeledOption = myParams->getOptionalParameter(5); if (unlabeledOption->m_present) { unlabeledValue = (int32_t)unlabeledOption->getInteger(1); } OptionalParameter* columnSelect = myParams->getOptionalParameter(6); int columnNum = -1; if (columnSelect->m_present) { columnNum = (int)myMetric->getMapIndexFromNameOrNumber(columnSelect->getString(1)); if (columnNum < 0 || columnNum >= myMetric->getNumberOfMaps()) { throw OperationException("invalid column specified"); } } bool dropUnused = myParams->getOptionalParameter(7)->m_present; map translate; GiftiLabelTable myTable; if (listfileName != "")//maybe this should be a function of GiftiLabelTable { AString temp; FileInformation textFileInfo(listfileName); if (!textFileInfo.exists()) { throw OperationException("label list file doesn't exist"); } fstream labelListFile(listfileName.toLocal8Bit().constData(), fstream::in); if (!labelListFile.good()) { throw OperationException("error reading label list file"); } string labelName; int32_t value, red, green, blue, alpha; int labelCount = 0; translate[unlabeledValue] = 0;//placeholder, we don't know the correct translated value yet while (labelListFile.good()) { ++labelCount;//just for error messages, so start at 1 getline(labelListFile, labelName); labelListFile >> value; if (labelListFile.eof() && labelName == "") break;//if end of file trying to read an int, and label name is empty, its really just end of file labelListFile >> red; labelListFile >> green; labelListFile >> blue; if (!(labelListFile >> alpha))//yes, that is seriously the correct way to check if input was successfully extracted...so much fail { throw OperationException("label list file is malformed for entry #" + AString::number(labelCount) + ": " + AString(labelName.c_str())); } if (red < 0 || red > 255) { throw OperationException("bad value for red for entry #" + AString::number(labelCount) + ", " + AString(labelName.c_str()) + ": " + AString::number(red)); } if (green < 0 || green > 255) { throw OperationException("bad value for green for entry #" + AString::number(labelCount) + ", " + AString(labelName.c_str()) + ": " + AString::number(green)); } if (blue < 0 || blue > 255) { throw OperationException("bad value for blue for entry #" + AString::number(labelCount) + ", " + AString(labelName.c_str()) + ": " + AString::number(blue)); } if (alpha < 0 || alpha > 255) { throw OperationException("bad value for alpha for entry #" + AString::number(labelCount) + ", " + AString(labelName.c_str()) + ": " + AString::number(alpha)); } if (value == GiftiLabel::getInvalidLabelKey()) { throw OperationException("entry #" + AString::number(labelCount) + ", " + AString(labelName.c_str()) + " specifies unusable key value: " + value); } while (isspace(labelListFile.peek())) { labelListFile.ignore();//drop the newline, possible carriage return or other whitespace so that getline doesn't get nothing, and cause int extraction to fail } temp = AString(labelName.c_str()).trimmed();//drop errant CR or other whitespace from beginning and end of lines if (translate.find(value) != translate.end()) { if (value == unlabeledValue) { throw OperationException("the unlabeled value must not be specified in label list file"); } else { throw OperationException(AString("label key ") + AString::number(value) + " specified more than once"); } } GiftiLabel myLabel(value, temp, red, green, blue, alpha); if (myTable.getLabelKeyFromName(temp) != GiftiLabel::getInvalidLabelKey()) { AString nameBase = temp, newName;//resolve collision by generating a name with an additional number on it bool success = false; for (int extra = 1; extra < 100; ++extra)//but stop at 100, because really... { newName = nameBase + "_" + AString::number(extra); if (myTable.getLabelKeyFromName(newName) == GiftiLabel::getInvalidLabelKey()) { success = true; break; } } if (success) { CaretLogWarning("name collision in input name '" + nameBase + "', changing one to '" + newName + "'"); } else { throw OperationException("giving up on resolving name collision for input name '" + nameBase + "'"); } myLabel.setName(newName); } int32_t newValue; if (value == 0)//because label 0 exists in the default constructed table { myTable.insertLabel(&myLabel);//but we do want to be able to overwrite the default 0 label newValue = 0;//if value 0 is specified twice, or once without specifying a different unlabeled value, the check versus the translate map will catch it } else { newValue = myTable.addLabel(&myLabel);//we don't want to overwrite relocated labels } translate[value] = newValue; } } int32_t unusedLabel = myTable.getUnassignedLabelKey(); translate[unlabeledValue] = unusedLabel; const int numNodes = myMetric->getNumberOfNodes(); vector colScratch(numNodes); if (columnNum == -1) { const int numCols = myMetric->getNumberOfColumns(); myLabelOut->setNumberOfNodesAndColumns(numNodes, numCols); myLabelOut->setStructure(myMetric->getStructure()); set usedValues; for (int col = 0; col < numCols; ++col) { translateLabels(myMetric->getValuePointerForColumn(col), colScratch.data(), numNodes, myTable, translate, usedValues, dropUnused, discardOthers, unusedLabel); myLabelOut->setLabelKeysForColumn(col, colScratch.data()); } if (dropUnused) { myTable.deleteUnusedLabels(usedValues); } *(myLabelOut->getLabelTable()) = myTable; } else { myLabelOut->setNumberOfNodesAndColumns(numNodes, 1); myLabelOut->setStructure(myMetric->getStructure()); set usedValues; translateLabels(myMetric->getValuePointerForColumn(columnNum), colScratch.data(), numNodes, myTable, translate, usedValues, dropUnused, discardOthers, unusedLabel); myLabelOut->setLabelKeysForColumn(0, colScratch.data()); if (dropUnused) { myTable.deleteUnusedLabels(usedValues); } *(myLabelOut->getLabelTable()) = myTable; } } void OperationMetricLabelImport::translateLabels(const float* valuesIn, int32_t* labelsOut, const int& numNodes, GiftiLabelTable& myTable, map& translate, set& usedValues, const bool& dropUnused, const bool& discardOthers, const int32_t& unusedLabel) { for (int node = 0; node < numNodes; ++node) { int32_t labelval = (int32_t)floor(valuesIn[node] + 0.5f);//just in case it somehow got poorly encoded, round to nearest if (dropUnused) { usedValues.insert(labelval); } map::iterator myiter = translate.find(labelval); if (myiter == translate.end()) { if (discardOthers) { labelsOut[node] = unusedLabel; } else {//use a random color, but fully opaque for the label GiftiLabel myLabel(labelval, AString("LABEL_") + AString::number(labelval), rand() & 255, rand() & 255, rand() & 255, 255); if (myTable.getLabelKeyFromName(myLabel.getName()) != GiftiLabel::getInvalidLabelKey()) { AString nameBase = myLabel.getName(), newName;//resolve collision by generating a name with an additional number on it bool success = false; for (int extra = 1; extra < 100; ++extra)//but stop at 100, because really... { newName = nameBase + "_" + AString::number(extra); if (myTable.getLabelKeyFromName(newName) == GiftiLabel::getInvalidLabelKey()) { success = true; break; } } if (success) { CaretLogWarning("name collision in auto-generated name '" + nameBase + "', changed to '" + newName + "'"); } else { throw OperationException("giving up on resolving name collision for auto-generated name '" + nameBase + "'"); } myLabel.setName(newName); } int32_t newValue = myTable.addLabel(&myLabel);//don't overwrite any values in the table translate[labelval] = newValue; labelsOut[node] = newValue; } } else { labelsOut[node] = myiter->second; } } } workbench-1.1.1/src/Operations/OperationMetricLabelImport.h000066400000000000000000000034321255417355300240320ustar00rootroot00000000000000#ifndef __OPERATION_METRIC_LABEL_IMPORT_H__ #define __OPERATION_METRIC_LABEL_IMPORT_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" #include #include namespace caret { class GiftiLabelTable; class OperationMetricLabelImport : public AbstractOperation { static void translateLabels(const float* valuesIn, int32_t* labelsOut, const int& numNodes, GiftiLabelTable& myTable, std::map& translate, std::set& usedValues, const bool& dropUnused, const bool& discardOthers, const int32_t& unusedLabel); public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationMetricLabelImport; } #endif //__OPERATION_METRIC_LABEL_IMPORT_H__ workbench-1.1.1/src/Operations/OperationMetricMask.cxx000066400000000000000000000114001255417355300230600ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationMetricMask.h" #include "OperationException.h" #include "MetricFile.h" #include "PaletteColorMapping.h" using namespace caret; using namespace std; AString OperationMetricMask::getCommandSwitch() { return "-metric-mask"; } AString OperationMetricMask::getShortDescription() { return "MASK A METRIC FILE"; } OperationParameters* OperationMetricMask::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addMetricParameter(1, "metric", "the input metric"); ret->addMetricParameter(2, "mask", "the mask metric"); ret->addMetricOutputParameter(3, "metric-out", "the output metric"); OptionalParameter* columnSelect = ret->createOptionalParameter(4, "-column", "select a single column"); columnSelect->addStringParameter(1, "column", "the column number or name"); ret->setHelpText( AString("By default, the output metric is a copy of the input metric, but with zeros wherever the mask metric is not positive. ") + "if -column is specified, the output contains only one column, the masked version of the specified input column." ); return ret; } void OperationMetricMask::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); MetricFile* myMetric = myParams->getMetric(1); MetricFile* myMask = myParams->getMetric(2); int32_t numNodes = myMetric->getNumberOfNodes(); if (myMask->getNumberOfNodes() != numNodes) { throw OperationException("mask metric must have the same number of vertices"); } MetricFile* myMetricOut = myParams->getOutputMetric(3);//gets the output metric with key 2 OptionalParameter* columnSelect = myParams->getOptionalParameter(4);//gets optional parameter with key 3 int columnNum = -1; if (columnSelect->m_present) {//set up to use the single column columnNum = (int)myMetric->getMapIndexFromNameOrNumber(columnSelect->getString(1)); if (columnNum < 0 || columnNum >= myMetric->getNumberOfColumns()) { throw OperationException("invalid column specified"); } } CaretArray myColumnOut(numNodes); if (columnNum == -1) { int32_t numCols = myMetric->getNumberOfColumns(); myMetricOut->setNumberOfNodesAndColumns(numNodes, numCols); myMetricOut->setStructure(myMetric->getStructure()); const float* maskVals = myMask->getValuePointerForColumn(0); for (int32_t col = 0; col < numCols; ++col) { const float* metricVals = myMetric->getValuePointerForColumn(col); *(myMetricOut->getPaletteColorMapping(col)) = *(myMetric->getPaletteColorMapping(col));//copy the palette settings for (int32_t node = 0; node < numNodes; ++node) { if (maskVals[node] > 0.0f) { myColumnOut[node] = metricVals[node]; } else { myColumnOut[node] = 0.0f; } } myMetricOut->setValuesForColumn(col, myColumnOut.getArray()); myMetricOut->setColumnName(col, myMetric->getColumnName(col)); } } else { myMetricOut->setNumberOfNodesAndColumns(numNodes, 1); myMetricOut->setStructure(myMetric->getStructure()); const float* maskVals = myMask->getValuePointerForColumn(0); const float* metricVals = myMetric->getValuePointerForColumn(columnNum); *(myMetricOut->getPaletteColorMapping(0)) = *(myMetric->getPaletteColorMapping(columnNum));//copy the palette settings for (int32_t node = 0; node < numNodes; ++node) { if (maskVals[node] > 0.0f) { myColumnOut[node] = metricVals[node]; } else { myColumnOut[node] = 0.0f; } } myMetricOut->setValuesForColumn(0, myColumnOut.getArray()); myMetricOut->setColumnName(0, myMetric->getColumnName(columnNum)); } } workbench-1.1.1/src/Operations/OperationMetricMask.h000066400000000000000000000025371255417355300225200ustar00rootroot00000000000000#ifndef __METRIC_MASK_H__ #define __METRIC_MASK_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationMetricMask : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationMetricMask; } #endif //__METRIC_MASK_H__ workbench-1.1.1/src/Operations/OperationMetricMath.cxx000066400000000000000000000226451255417355300230730ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationMetricMath.h" #include "OperationException.h" #include "CaretAssert.h" #include "CaretLogger.h" #include "CaretMathExpression.h" #include "MetricFile.h" #include using namespace caret; using namespace std; AString OperationMetricMath::getCommandSwitch() { return "-metric-math"; } AString OperationMetricMath::getShortDescription() { return "EVALUATE EXPRESSION ON METRIC FILES"; } OperationParameters* OperationMetricMath::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addStringParameter(1, "expression", "the expression to evaluate, in quotes"); ret->addMetricOutputParameter(2, "metric-out", "the output metric"); ParameterComponent* varOpt = ret->createRepeatableParameter(3, "-var", "a metric to use as a variable"); varOpt->addStringParameter(1, "name", "the name of the variable, as used in the expression"); varOpt->addMetricParameter(2, "metric", "the metric file to use as this variable"); OptionalParameter* columnSelect = varOpt->createOptionalParameter(3, "-column", "select a single column"); columnSelect->addStringParameter(1, "column", "the column number or name"); varOpt->createOptionalParameter(4, "-repeat", "reuse a single column for each column of calculation"); OptionalParameter* fixNanOpt = ret->createOptionalParameter(4, "-fixnan", "replace NaN results with a value"); fixNanOpt->addDoubleParameter(1, "replace", "value to replace NaN with"); AString myText = AString("This command evaluates at each surface vertex independently. ") + "There must be at least one -var option (to get the structure, number of vertices, and number of columns from), even if the specified in it isn't used in . " + "All metrics must have the same number of vertices. " + "Filenames are not valid in , use a variable name and a -var option with matching to specify an input file. " + "If the -column option is given to any -var option, only one column is used from that file. " + "If -repeat is specified, the file must either have only one column, or have the -column option specified. " + "All files that don't use -repeat must have the same number of columns requested to be used. " + "The format of is as follows:\n\n"; myText += CaretMathExpression::getExpressionHelpInfo(); ret->setHelpText(myText); return ret; } void OperationMetricMath::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); AString expression = myParams->getString(1); CaretMathExpression myExpr(expression); cout << "parsed '" + expression + "' as '" + myExpr.toString() + "'" << endl; vector myVarNames = myExpr.getVarNames(); MetricFile* myMetricOut = myParams->getOutputMetric(2); const vector& myVarOpts = *(myParams->getRepeatableParameterInstances(3)); OptionalParameter* fixNanOpt = myParams->getOptionalParameter(4); bool nanfix = false; float nanfixval = 0; if (fixNanOpt->m_present) { nanfix = true; nanfixval = (float)fixNanOpt->getDouble(1); } int numInputs = myVarOpts.size(); int numVars = myVarNames.size(); vector varMetrics(numVars, (MetricFile*)NULL); vector metricColumns(numVars, -1); if (numInputs == 0 && numVars == 0) throw OperationException("you must specify at least one input metric (-var), even if the expression doesn't use a variable"); int numNodes; StructureEnum::Enum myStructure; int numColumns = -1; for (int i = 0; i < numInputs; ++i) { if (i == 0) { numNodes = myVarOpts[0]->getMetric(2)->getNumberOfNodes(); myStructure = myVarOpts[0]->getMetric(2)->getStructure(); } AString varName = myVarOpts[i]->getString(1); double constVal; if (CaretMathExpression::getNamedConstant(varName, constVal)) { throw OperationException("'" + varName + "' is a named constant equal to " + AString::number(constVal, 'g', 15) + ", please use a different variable name"); } MetricFile* thisMetric = myVarOpts[i]->getMetric(2); int thisColumns = thisMetric->getNumberOfColumns(); OptionalParameter* columnSelect = myVarOpts[i]->getOptionalParameter(3); int useColumn = -1; if (columnSelect->m_present) { thisColumns = 1; useColumn = thisMetric->getMapIndexFromNameOrNumber(columnSelect->getString(1)); if (useColumn == -1) throw OperationException("could not find map '" + columnSelect->getString(1) + "' in metric file for '" + varName + "'"); } bool repeat = myVarOpts[i]->getOptionalParameter(4)->m_present; if (thisMetric->getNumberOfNodes() != numNodes) { throw OperationException("metric file for variable '" + varName + "' has different number of vertices than the first metric file"); } if (repeat) { if (thisColumns != 1) { throw OperationException("-repeat specified without -column for variable '" + varName + "', but metric file has " + AString::number(thisColumns) + " columns"); } if (useColumn == -1) useColumn = 0;//-1 means use same input column as current output column, so we need to fix the special case of -repeat on single column file without -column } else { if (numColumns == -1)//then this is the first one that doesn't use -repeat { numColumns = thisColumns; } else { if (numColumns != thisColumns) { if (useColumn == -1) { throw OperationException("metric file for variable '" + varName + "' has " + AString::number(thisColumns) + " column(s), but previous metric files have " + AString::number(numColumns) + " column(s) requested to be used"); } else { throw OperationException("-column specified without -repeat for variable '" + varName + "', but previous metric files have have " + AString::number(numColumns) + " columns requested to be used"); } } } } bool found = false; for (int j = 0; j < numVars; ++j) { if (varName == myVarNames[j]) { if (varMetrics[j] != NULL) throw OperationException("variable '" + varName + "' specified more than once"); varMetrics[j] = thisMetric; metricColumns[j] = useColumn; found = true; break; } } if (!found && (numVars != 0 || numInputs != 1))//supress warning when a single -var is used with a constant expression, as required per help { CaretLogWarning("variable '" + varName + "' not used in expression"); } } for (int i = 0; i < numVars; ++i) { if (varMetrics[i] == NULL) throw OperationException("no -var option specified for variable '" + myVarNames[i] + "'"); } if (numColumns == -1) { throw OperationException("all -var options used -repeat, there is no file to get number of desired output columns from"); } vector values(numVars), colScratch(numNodes); vector columnPointers(numVars); myMetricOut->setNumberOfNodesAndColumns(numNodes, numColumns); myMetricOut->setStructure(myStructure); for (int j = 0; j < numColumns; ++j) { for (int v = 0; v < numVars; ++v) { if (metricColumns[v] == -1) { columnPointers[v] = varMetrics[v]->getValuePointerForColumn(j); } else { columnPointers[v] = varMetrics[v]->getValuePointerForColumn(metricColumns[v]); } } for (int i = 0; i < numNodes; ++i) { for (int v = 0; v < numVars; ++v) { values[v] = columnPointers[v][i]; } colScratch[i] = (float)myExpr.evaluate(values); if (nanfix && colScratch[i] != colScratch[i]) { colScratch[i] = nanfixval; } } myMetricOut->setValuesForColumn(j, colScratch.data()); } } workbench-1.1.1/src/Operations/OperationMetricMath.h000066400000000000000000000025751255417355300225200ustar00rootroot00000000000000#ifndef __OPERATION_METRIC_MATH_H__ #define __OPERATION_METRIC_MATH_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationMetricMath : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationMetricMath; } #endif //__OPERATION_METRIC_MATH_H__ workbench-1.1.1/src/Operations/OperationMetricMerge.cxx000066400000000000000000000204231255417355300232310ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationMetricMerge.h" #include "OperationException.h" #include "CaretLogger.h" #include "MetricFile.h" #include "PaletteColorMapping.h" #include using namespace caret; using namespace std; AString OperationMetricMerge::getCommandSwitch() { return "-metric-merge"; } AString OperationMetricMerge::getShortDescription() { return "MERGE METRIC FILES INTO A NEW FILE"; } OperationParameters* OperationMetricMerge::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addMetricOutputParameter(1, "metric-out", "the output metric"); ParameterComponent* metricOpt = ret->createRepeatableParameter(2, "-metric", "specify an input metric"); metricOpt->addMetricParameter(1, "metric-in", "a metric file to use columns from"); ParameterComponent* columnOpt = metricOpt->createRepeatableParameter(2, "-column", "select a single column to use"); columnOpt->addStringParameter(1, "column", "the column number or name"); OptionalParameter* upToOpt = columnOpt->createOptionalParameter(2, "-up-to", "use an inclusive range of columns"); upToOpt->addStringParameter(1, "last-column", "the number or name of the last column to include"); upToOpt->createOptionalParameter(2, "-reverse", "use the range in reverse order"); ret->setHelpText( AString("Takes one or more metric files and constructs a new metric file by concatenating columns from them. ") + "The input metric files must have the same number of vertices and same structure.\n\n" + "Example: wb_command -metric-merge out.func.gii -metric first.func.gii -column 1 -metric second.func.gii\n\n" + "This example would take the first column from first.func.gii, followed by all columns from second.func.gii, " + "and write these columns to out.func.gii." ); return ret; } void OperationMetricMerge::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); MetricFile* myMetricOut = myParams->getOutputMetric(1); const vector& myInputs = *(myParams->getRepeatableParameterInstances(2)); int numInputs = (int)myInputs.size(); if (numInputs == 0) throw OperationException("no inputs specified"); const MetricFile* firstMetric = myInputs[0]->getMetric(1); int numOutColumns = 0; int numNodes = firstMetric->getNumberOfNodes(); StructureEnum::Enum myStruct = firstMetric->getStructure(); for (int i = 0; i < numInputs; ++i) { const MetricFile* inputMetric = myInputs[i]->getMetric(1); if (numNodes != inputMetric->getNumberOfNodes()) throw OperationException("file '" + inputMetric->getFileName() + "' has a different number of nodes than the first"); if (myStruct != inputMetric->getStructure()) throw OperationException("file '" + inputMetric->getFileName() + "' has a different structure than the first"); const vector& columnOpts = *(myInputs[i]->getRepeatableParameterInstances(2)); int numColumnOpts = (int)columnOpts.size(); if (numColumnOpts > 0) { for (int j = 0; j < numColumnOpts; ++j) { int initialColumn = inputMetric->getMapIndexFromNameOrNumber(columnOpts[j]->getString(1)); if (initialColumn < 0) throw OperationException("column '" + columnOpts[j]->getString(1) + "' not found in file '" + inputMetric->getFileName() + "'"); OptionalParameter* upToOpt = columnOpts[j]->getOptionalParameter(2); if (upToOpt->m_present) { int finalColumn = inputMetric->getMapIndexFromNameOrNumber(upToOpt->getString(1)); if (finalColumn < 0) throw OperationException("ending column '" + upToOpt->getString(1) + "' not found in file '" + inputMetric->getFileName() + "'"); if (finalColumn < initialColumn) throw OperationException("ending column '" + upToOpt->getString(1) + "' occurs before starting column '" + columnOpts[j]->getString(1) + "' in file '" + inputMetric->getFileName() + "'"); numOutColumns += finalColumn - initialColumn + 1;//inclusive - we don't need to worry about reversing for counting, though } else { numOutColumns += 1; } } } else { numOutColumns += inputMetric->getNumberOfColumns(); } } myMetricOut->setNumberOfNodesAndColumns(numNodes, numOutColumns); myMetricOut->setStructure(myStruct); int curColumn = 0; for (int i = 0; i < numInputs; ++i) { const MetricFile* inputMetric = myInputs[i]->getMetric(1); const vector& columnOpts = *(myInputs[i]->getRepeatableParameterInstances(2)); int numColumnOpts = (int)columnOpts.size(); if (numColumnOpts > 0) { for (int j = 0; j < numColumnOpts; ++j) { int initialColumn = inputMetric->getMapIndexFromNameOrNumber(columnOpts[j]->getString(1)); OptionalParameter* upToOpt = columnOpts[j]->getOptionalParameter(2); if (upToOpt->m_present) { int finalColumn = inputMetric->getMapIndexFromNameOrNumber(upToOpt->getString(1)); bool reverse = upToOpt->getOptionalParameter(2)->m_present; if (reverse) { for (int c = finalColumn; c >= initialColumn; --c) { myMetricOut->setValuesForColumn(curColumn, inputMetric->getValuePointerForColumn(c)); myMetricOut->setColumnName(curColumn, inputMetric->getColumnName(c)); *(myMetricOut->getMapPaletteColorMapping(curColumn)) = *(inputMetric->getMapPaletteColorMapping(c)); ++curColumn; } } else { for (int c = initialColumn; c <= finalColumn; ++c) { myMetricOut->setValuesForColumn(curColumn, inputMetric->getValuePointerForColumn(c)); myMetricOut->setColumnName(curColumn, inputMetric->getColumnName(c)); *(myMetricOut->getMapPaletteColorMapping(curColumn)) = *(inputMetric->getMapPaletteColorMapping(c)); ++curColumn; } } } else { myMetricOut->setValuesForColumn(curColumn, inputMetric->getValuePointerForColumn(initialColumn)); myMetricOut->setColumnName(curColumn, inputMetric->getColumnName(initialColumn)); *(myMetricOut->getMapPaletteColorMapping(curColumn)) = *(inputMetric->getMapPaletteColorMapping(initialColumn)); ++curColumn; } } } else { int numColumns = inputMetric->getNumberOfColumns(); for (int j = 0; j < numColumns; ++j) { myMetricOut->setValuesForColumn(curColumn, inputMetric->getValuePointerForColumn(j)); myMetricOut->setColumnName(curColumn, inputMetric->getColumnName(j)); *(myMetricOut->getMapPaletteColorMapping(curColumn)) = *(inputMetric->getMapPaletteColorMapping(j)); ++curColumn; } } } CaretAssert(curColumn == numOutColumns); } workbench-1.1.1/src/Operations/OperationMetricMerge.h000066400000000000000000000026031255417355300226560ustar00rootroot00000000000000#ifndef __OPERATION_METRIC_MERGE_H__ #define __OPERATION_METRIC_MERGE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationMetricMerge : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationMetricMerge; } #endif //__OPERATION_METRIC_MERGE_H__ workbench-1.1.1/src/Operations/OperationMetricPalette.cxx000066400000000000000000000244311255417355300235730ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationMetricPalette.h" #include "OperationException.h" #include "MetricFile.h" #include "Palette.h" #include "PaletteColorMapping.h" #include "PaletteFile.h" #include using namespace caret; using namespace std; AString OperationMetricPalette::getCommandSwitch() { return "-metric-palette"; } AString OperationMetricPalette::getShortDescription() { return "SET THE PALETTE OF A METRIC FILE"; } OperationParameters* OperationMetricPalette::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addStringParameter(1, "metric", "the metric to modify"); ret->addStringParameter(2, "mode", "the mapping mode"); OptionalParameter* columnSelect = ret->createOptionalParameter(3, "-column", "select a single column"); columnSelect->addStringParameter(1, "column", "the column number or name"); OptionalParameter* posMinMaxPercent = ret->createOptionalParameter(4, "-pos-percent", "percentage min/max for positive data coloring"); posMinMaxPercent->addDoubleParameter(1, "pos-min-%", "the percentile for the least positive data"); posMinMaxPercent->addDoubleParameter(2, "pos-max-%", "the percentile for the most positive data"); OptionalParameter* negMinMaxPercent = ret->createOptionalParameter(5, "-neg-percent", "percentage min/max for negative data coloring"); negMinMaxPercent->addDoubleParameter(1, "neg-min-%", "the percentile for the least negative data"); negMinMaxPercent->addDoubleParameter(2, "neg-max-%", "the percentile for the most negative data"); OptionalParameter* posMinMaxValue = ret->createOptionalParameter(11, "-pos-user", "user min/max values for positive data coloring"); posMinMaxValue->addDoubleParameter(1, "pos-min-user", "the value for the least positive data"); posMinMaxValue->addDoubleParameter(2, "pos-max-user", "the value for the most positive data"); OptionalParameter* negMinMaxValue = ret->createOptionalParameter(12, "-neg-user", "user min/max values for negative data coloring"); negMinMaxValue->addDoubleParameter(1, "neg-min-user", "the value for the least negative data"); negMinMaxValue->addDoubleParameter(2, "neg-max-user", "the value for the most negative data"); OptionalParameter* interpolate = ret->createOptionalParameter(9, "-interpolate", "interpolate colors"); interpolate->addBooleanParameter(1, "interpolate", "boolean, whether to interpolate"); OptionalParameter* displayPositive = ret->createOptionalParameter(6, "-disp-pos", "display positive data"); displayPositive->addBooleanParameter(1, "display", "boolean, whether to display"); OptionalParameter* displayNegative = ret->createOptionalParameter(7, "-disp-neg", "display positive data"); displayNegative->addBooleanParameter(1, "display", "boolean, whether to display"); OptionalParameter* displayZero = ret->createOptionalParameter(8, "-disp-zero", "display data closer to zero than the min cutoff"); displayZero->addBooleanParameter(1, "display", "boolean, whether to display"); OptionalParameter* paletteName = ret->createOptionalParameter(10, "-palette-name", "set the palette used"); paletteName->addStringParameter(1, "name", "the name of the palette"); OptionalParameter* thresholdOpt = ret->createOptionalParameter(13, "-thresholding", "set the thresholding"); thresholdOpt->addStringParameter(1, "type", "thresholding setting"); thresholdOpt->addStringParameter(2, "test", "show values inside or outside thresholds"); thresholdOpt->addDoubleParameter(3, "min", "lower threshold"); thresholdOpt->addDoubleParameter(4, "max", "upper threshold"); AString myText = AString("The original metric file is overwritten with the modified version. By default, all columns of the metric file are adjusted ") + "to the new settings, use the -column option to change only one column. Mapping settings not specified in options will be taken from the first column. " + "The argument must be one of the following:\n\n"; vector myEnums; PaletteScaleModeEnum::getAllEnums(myEnums); for (int i = 0; i < (int)myEnums.size(); ++i) { myText += PaletteScaleModeEnum::toName(myEnums[i]) + "\n"; } myText += "\nThe argument to -palette-name must be one of the following:\n\n"; PaletteFile myPF; int32_t numPalettes = myPF.getNumberOfPalettes(); for (int i = 0; i < numPalettes; ++i) { myText += myPF.getPalette(i)->getName() + "\n"; } myText += "\nThe argument to -thresholding must be one of the following:\n\n"; vector myEnums2; PaletteThresholdTypeEnum::getAllEnums(myEnums2); for (int i = 0; i < (int)myEnums2.size(); ++i) { myText += PaletteThresholdTypeEnum::toName(myEnums2[i]) + "\n"; } myText += "\nThe argument to -thresholding must be one of the following:\n\n"; vector myEnums3; PaletteThresholdTestEnum::getAllEnums(myEnums3); for (int i = 0; i < (int)myEnums3.size(); ++i) { myText += PaletteThresholdTestEnum::toName(myEnums3[i]) + "\n"; } ret->setHelpText(myText); return ret; } void OperationMetricPalette::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); AString myMetricName = myParams->getString(1); AString myModeName = myParams->getString(2); bool ok = false; PaletteScaleModeEnum::Enum myMode = PaletteScaleModeEnum::fromName(myModeName, &ok); if (!ok) { throw OperationException("unknown mapping mode"); } MetricFile myMetric; myMetric.readFile(myMetricName); int myColumn = -1; OptionalParameter* columnSelect = myParams->getOptionalParameter(3); if (columnSelect->m_present) { myColumn = (int)myMetric.getMapIndexFromNameOrNumber(columnSelect->getString(1)); if (myColumn < 0 || myColumn >= myMetric.getNumberOfColumns()) { throw OperationException("invalid column specified"); } } PaletteColorMapping myMapping = *(myMetric.getMapPaletteColorMapping(0));//create the mapping, then use operator= to set for all requested columns, take defaults from first map myMapping.setScaleMode(myMode); OptionalParameter* posMinMaxPercent = myParams->getOptionalParameter(4); if (posMinMaxPercent->m_present) { myMapping.setAutoScalePercentagePositiveMinimum(posMinMaxPercent->getDouble(1)); myMapping.setAutoScalePercentagePositiveMaximum(posMinMaxPercent->getDouble(2)); } OptionalParameter* negMinMaxPercent = myParams->getOptionalParameter(5); if (negMinMaxPercent->m_present) { myMapping.setAutoScalePercentageNegativeMinimum(negMinMaxPercent->getDouble(1)); myMapping.setAutoScalePercentageNegativeMaximum(negMinMaxPercent->getDouble(2)); } OptionalParameter* posMinMaxValue = myParams->getOptionalParameter(11); if (posMinMaxValue->m_present) { myMapping.setUserScalePositiveMinimum(posMinMaxValue->getDouble(1)); myMapping.setUserScalePositiveMaximum(posMinMaxValue->getDouble(2)); } OptionalParameter* negMinMaxValue = myParams->getOptionalParameter(12); if (negMinMaxValue->m_present) { myMapping.setUserScaleNegativeMinimum(negMinMaxValue->getDouble(1)); myMapping.setUserScaleNegativeMaximum(negMinMaxValue->getDouble(2)); } OptionalParameter* displayPositive = myParams->getOptionalParameter(6); if (displayPositive->m_present) { myMapping.setDisplayPositiveDataFlag(displayPositive->getBoolean(1)); } OptionalParameter* displayNegative = myParams->getOptionalParameter(7); if (displayNegative->m_present) { myMapping.setDisplayNegativeDataFlag(displayNegative->getBoolean(1)); } OptionalParameter* displayZero = myParams->getOptionalParameter(8); if (displayZero->m_present) { myMapping.setDisplayZeroDataFlag(displayZero->getBoolean(1)); } OptionalParameter* interpolate = myParams->getOptionalParameter(9); if (interpolate->m_present) { myMapping.setInterpolatePaletteFlag(interpolate->getBoolean(1)); } OptionalParameter* paletteName = myParams->getOptionalParameter(10); if (paletteName->m_present) { myMapping.setSelectedPaletteName(paletteName->getString(1)); } OptionalParameter* thresholdOpt = myParams->getOptionalParameter(13); if (thresholdOpt->m_present) { bool ok = false; PaletteThresholdTypeEnum::Enum mytype = PaletteThresholdTypeEnum::fromName(thresholdOpt->getString(1), &ok); if (!ok) throw OperationException("unrecognized threshold type string: " + thresholdOpt->getString(1)); PaletteThresholdTestEnum::Enum mytest = PaletteThresholdTestEnum::fromName(thresholdOpt->getString(2), &ok); if (!ok) throw OperationException("unrecognized threshold test string: " + thresholdOpt->getString(2)); myMapping.setThresholdType(mytype); myMapping.setThresholdTest(mytest); myMapping.setThresholdMinimum(mytype, thresholdOpt->getDouble(3)); myMapping.setThresholdMaximum(mytype, thresholdOpt->getDouble(4)); } if (myColumn == -1) { for (int i = 0; i < myMetric.getNumberOfMaps(); ++i) { *(myMetric.getMapPaletteColorMapping(i)) = myMapping; } } else { *(myMetric.getMapPaletteColorMapping(myColumn)) = myMapping; } myMetric.writeFile(myMetricName); } workbench-1.1.1/src/Operations/OperationMetricPalette.h000066400000000000000000000026171255417355300232220ustar00rootroot00000000000000#ifndef __OPERATION_METRIC_PALETTE_H__ #define __OPERATION_METRIC_PALETTE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationMetricPalette : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationMetricPalette; } #endif //__OPERATION_METRIC_PALETTE_H__ workbench-1.1.1/src/Operations/OperationMetricStats.cxx000066400000000000000000000225121255417355300232710ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationMetricStats.h" #include "OperationException.h" #include "MetricFile.h" #include "ReductionOperation.h" #include #include #include #include #include #include using namespace caret; using namespace std; AString OperationMetricStats::getCommandSwitch() { return "-metric-stats"; } AString OperationMetricStats::getShortDescription() { return "SPATIAL STATISTICS ON A METRIC FILE"; } OperationParameters* OperationMetricStats::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addMetricParameter(1, "metric-in", "the input metric"); OptionalParameter* reduceOpt = ret->createOptionalParameter(2, "-reduce", "use a reduction operation"); reduceOpt->addStringParameter(1, "operation", "the reduction operation"); OptionalParameter* percentileOpt = ret->createOptionalParameter(3, "-percentile", "give the value at a percentile"); percentileOpt->addDoubleParameter(1, "percent", "the percentile to find"); OptionalParameter* columnOpt = ret->createOptionalParameter(4, "-column", "only display output for one column"); columnOpt->addStringParameter(1, "column", "the column number or name"); OptionalParameter* roiOpt = ret->createOptionalParameter(5, "-roi", "only consider data inside an roi"); roiOpt->addMetricParameter(1, "roi-metric", "the roi, as a metric file"); roiOpt->createOptionalParameter(2, "-match-maps", "each column of input uses the corresponding column from the roi file"); ret->createOptionalParameter(6, "-show-map-name", "print map index and name before each output"); ret->setHelpText( AString("For each column of the input, a single number is printed, resulting from the specified reduction or percentile operation. ") + "Use -column to only give output for a single column. " + "Use -roi to consider only the data within a region. " + "Exactly one of -reduce or -percentile must be specified.\n\n" + "The argument to the -reduce option must be one of the following:\n\n" + ReductionOperation::getHelpInfo()); return ret; } namespace { float reduce(const float* data, const int& numNodes, const ReductionEnum::Enum& myop, const float* roiData) { if (roiData == NULL) { return ReductionOperation::reduce(data, numNodes, myop); } else { vector toUse; toUse.reserve(numNodes); for (int i = 0; i < numNodes; ++i) { if (roiData[i] > 0.0f) { toUse.push_back(data[i]); } } if (toUse.empty()) throw OperationException("roi contains no vertices"); return ReductionOperation::reduce(toUse.data(), toUse.size(), myop); } } float percentile(const float* data, const int& numNodes, const float& percent, const float* roiData) { CaretAssert(percent >= 0.0f && percent <= 100.0f); vector toUse; if (roiData == NULL) { toUse = vector(data, data + numNodes); } else { toUse.reserve(numNodes); for (int i = 0; i < numNodes; ++i) { if (roiData[i] > 0.0f) { toUse.push_back(data[i]); } } } if (toUse.empty()) throw OperationException("roi contains no vertices"); sort(toUse.begin(), toUse.end()); const float index = percent / 100.0f * (toUse.size() - 1); if (index <= 0) return toUse[0]; if (index >= toUse.size() - 1) return toUse.back(); float ipart, fpart; fpart = modf(index, &ipart); return (1.0f - fpart) * toUse[(int)ipart] + fpart * toUse[((int)ipart) + 1]; } } void OperationMetricStats::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); MetricFile* input = myParams->getMetric(1); int numNodes = input->getNumberOfNodes(); int numCols = input->getNumberOfColumns(); OptionalParameter* reduceOpt = myParams->getOptionalParameter(2); OptionalParameter* percentileOpt = myParams->getOptionalParameter(3); if (reduceOpt->m_present == percentileOpt->m_present)//use == as logical xor { throw OperationException("you must use exactly one of -reduce or -percentile"); } ReductionEnum::Enum myop = ReductionEnum::INVALID; if (reduceOpt->m_present) { bool ok = false; myop = ReductionEnum::fromName(reduceOpt->getString(1), &ok); if (!ok) throw OperationException("unrecognized reduction operation: " + reduceOpt->getString(1)); } float percent = 0.0f; if (percentileOpt->m_present) { percent = (float)percentileOpt->getDouble(1);//use not within range to trap NaNs, just in case if (!(percent >= 0.0f && percent <= 100.0f)) throw OperationException("percentile must be between 0 and 100"); } int column = -1; OptionalParameter* columnOpt = myParams->getOptionalParameter(4); if (columnOpt->m_present) { column = input->getMapIndexFromNameOrNumber(columnOpt->getString(1)); if (column < 0) throw OperationException("invalid column specified"); } bool matchColumnMode = false; MetricFile* myRoi = NULL; const float* roiData = NULL; OptionalParameter* roiOpt = myParams->getOptionalParameter(5); if (roiOpt->m_present) { myRoi = roiOpt->getMetric(1); if (myRoi->getNumberOfNodes() != numNodes) throw OperationException("roi doesn't match input in number of vertices"); if (roiOpt->getOptionalParameter(2)->m_present) { if (myRoi->getNumberOfColumns() != numCols) { throw OperationException("-match-maps specified, but roi has different number of columns than input"); } matchColumnMode = true; } else { roiData = myRoi->getValuePointerForColumn(0); } } bool showMapName = myParams->getOptionalParameter(6)->m_present; if (column == -1) { if (reduceOpt->m_present) { for (int i = 0; i < numCols; ++i) {//store result before printing anything, in case it throws while computing if (matchColumnMode) { roiData = myRoi->getValuePointerForColumn(i); } const float result = reduce(input->getValuePointerForColumn(i), numNodes, myop, roiData); if (showMapName) cout << AString::number(i + 1) << ": " << input->getMapName(i) << ": "; stringstream resultsstr; resultsstr << setprecision(7) << result; cout << resultsstr.str() << endl; } } else { CaretAssert(percentileOpt->m_present); for (int i = 0; i < numCols; ++i) {//store result before printing anything, in case it throws while computing if (matchColumnMode) { roiData = myRoi->getValuePointerForColumn(i); } const float result = percentile(input->getValuePointerForColumn(i), numNodes, percent, roiData); if (showMapName) cout << AString::number(i + 1) << ": " << input->getMapName(i) << ": "; stringstream resultsstr; resultsstr << setprecision(7) << result; cout << resultsstr.str() << endl; } } } else { CaretAssert(column >= 0 && column < numCols); if (matchColumnMode) { roiData = myRoi->getValuePointerForColumn(column); } if (reduceOpt->m_present) { const float result = reduce(input->getValuePointerForColumn(column), numNodes, myop, roiData); if (showMapName) cout << AString::number(column + 1) << ": " << input->getMapName(column) << ": "; stringstream resultsstr; resultsstr << setprecision(7) << result; cout << resultsstr.str() << endl; } else { CaretAssert(percentileOpt->m_present); const float result = percentile(input->getValuePointerForColumn(column), numNodes, percent, roiData); if (showMapName) cout << AString::number(column + 1) << ": " << input->getMapName(column) << ": "; stringstream resultsstr; resultsstr << setprecision(7) << result; cout << resultsstr.str() << endl; } } } workbench-1.1.1/src/Operations/OperationMetricStats.h000066400000000000000000000026031255417355300227150ustar00rootroot00000000000000#ifndef __OPERATION_METRIC_STATS_H__ #define __OPERATION_METRIC_STATS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationMetricStats : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationMetricStats; } #endif //__OPERATION_METRIC_STATS_H__ workbench-1.1.1/src/Operations/OperationMetricVertexSum.cxx000066400000000000000000000162611255417355300241410ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationMetricVertexSum.h" #include "OperationException.h" #include "MetricFile.h" #include "SurfaceFile.h" #include #include #include using namespace caret; using namespace std; AString OperationMetricVertexSum::getCommandSwitch() { return "-metric-vertex-sum"; } AString OperationMetricVertexSum::getShortDescription() { return "DEPRECATED: use -metric-stats or -metric-weighted-stats"; } OperationParameters* OperationMetricVertexSum::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addMetricParameter(1, "metric-in", "the metric to sum"); OptionalParameter* integrateOpt = ret->createOptionalParameter(2, "-integrate", "integrate across a surface, rather than summing"); integrateOpt->addSurfaceParameter(1, "surface", "the surface to integrate on"); OptionalParameter* integrateMetricOpt = ret->createOptionalParameter(3, "-integrate-metric", "integrate using vertex areas from a metric file"); integrateMetricOpt->addMetricParameter(1, "area-metric", "metric file containing vertex areas"); OptionalParameter* roiOpt = ret->createOptionalParameter(4, "-roi", "only use data inside an roi"); roiOpt->addMetricParameter(1, "roi-metric", "the roi, as a metric file"); OptionalParameter* columnOpt = ret->createOptionalParameter(5, "-column", "select a single column"); columnOpt->addStringParameter(1, "column", "the column number or name"); ret->setHelpText( AString("DEPRECATED: this command may be removed in a future release, use -metric-stats or -metric-weighted-stats.\n\n") + "For each column in , sum the values across all vertices, then print the sum on standard output. " + "-integrate and -integrate-metric multiply each vertex value by the vertex area before doing the sum. " + "Only one of -integrate and -integrate-metric may be specified. " + "Use -roi to only sum within a specific area. " + "Use -column to only report for one column. " ); return ret; } void OperationMetricVertexSum::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); MetricFile* myMetric = myParams->getMetric(1); int numNodes = myMetric->getNumberOfNodes(); const float* integrateData = NULL; vector surfNodeAreas; OptionalParameter* integrateOpt = myParams->getOptionalParameter(2); if (integrateOpt->m_present) { SurfaceFile* integrateSurf = integrateOpt->getSurface(1); if (integrateSurf->getNumberOfNodes() != numNodes) throw OperationException("integration surface has the wrong number of vertices"); integrateSurf->computeNodeAreas(surfNodeAreas); integrateData = surfNodeAreas.data(); } OptionalParameter* integrateMetricOpt = myParams->getOptionalParameter(3); if (integrateMetricOpt->m_present) { if (integrateOpt->m_present) throw OperationException("only one of -integrate and -integrate-metric can be specified"); MetricFile* integrateMetric = integrateMetricOpt->getMetric(1);//we don't need to keep a pointer to it if (integrateMetric->getNumberOfNodes() != numNodes) throw OperationException("integration metric has the wrong number of vertices"); integrateData = integrateMetric->getValuePointerForColumn(0);//just its data } MetricFile* myRoi = NULL; OptionalParameter* roiOpt = myParams->getOptionalParameter(4); if (roiOpt->m_present) { myRoi = roiOpt->getMetric(1); if (myRoi->getNumberOfNodes() != numNodes) throw OperationException("roi-metric has the wrong number of vertices"); } int myColumn = -1; OptionalParameter* columnOpt = myParams->getOptionalParameter(5); if (columnOpt->m_present) { myColumn = myMetric->getMapIndexFromNameOrNumber(columnOpt->getString(1)); if (myColumn < 0 || myColumn >= myMetric->getNumberOfMaps()) { throw OperationException("invalid column specified"); } } const float* roiData = NULL; if (myRoi != NULL) roiData = myRoi->getValuePointerForColumn(0); if (integrateData != NULL) { if (myColumn == -1) { int numCols = myMetric->getNumberOfColumns(); for (int j = 0; j < numCols; ++j) { double accum = 0.0; const float* data = myMetric->getValuePointerForColumn(j); for (int i = 0; i < numNodes; ++i) { if (roiData == NULL || roiData[i] > 0.0f) { accum += data[i] * integrateData[i]; } } stringstream s; s << std::setprecision(7) << accum; cout << s.str() << endl; } } else { double accum = 0.0; const float* data = myMetric->getValuePointerForColumn(myColumn); for (int i = 0; i < numNodes; ++i) { if (roiData == NULL || roiData[i] > 0.0f) { accum += data[i] * integrateData[i]; } } stringstream s; s << std::setprecision(7) << accum; cout << s.str() << endl; } } else { if (myColumn == -1) { int numCols = myMetric->getNumberOfColumns(); for (int j = 0; j < numCols; ++j) { double accum = 0.0; const float* data = myMetric->getValuePointerForColumn(j); for (int i = 0; i < numNodes; ++i) { if (roiData == NULL || roiData[i] > 0.0f) { accum += data[i]; } } stringstream s; s << std::setprecision(7) << accum; cout << s.str() << endl; } } else { double accum = 0.0; const float* data = myMetric->getValuePointerForColumn(myColumn); for (int i = 0; i < numNodes; ++i) { if (roiData == NULL || roiData[i] > 0.0f) { accum += data[i]; } } stringstream s; s << std::setprecision(7) << accum; cout << s.str() << endl; } } } workbench-1.1.1/src/Operations/OperationMetricVertexSum.h000066400000000000000000000026361255417355300235670ustar00rootroot00000000000000#ifndef __OPERATION_METRIC_VERTEX_SUM_H__ #define __OPERATION_METRIC_VERTEX_SUM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationMetricVertexSum : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationMetricVertexSum; } #endif //__OPERATION_METRIC_VERTEX_SUM_H__ workbench-1.1.1/src/Operations/OperationMetricWeightedStats.cxx000066400000000000000000000333111255417355300247510ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationMetricWeightedStats.h" #include "OperationException.h" #include "CaretHeap.h" #include "MetricFile.h" #include "SurfaceFile.h" #include #include #include #include #include using namespace caret; using namespace std; AString OperationMetricWeightedStats::getCommandSwitch() { return "-metric-weighted-stats"; } AString OperationMetricWeightedStats::getShortDescription() { return "WEIGHTED SPATIAL STATISTICS ON A METRIC FILE"; } OperationParameters* OperationMetricWeightedStats::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addMetricParameter(1, "metric-in", "the input metric"); OptionalParameter* areaSurfOpt = ret->createOptionalParameter(2, "-area-surface", "use vertex areas as weights"); areaSurfOpt->addSurfaceParameter(1, "area-surface", "the surface to use for vertex areas"); OptionalParameter* weightMetricOpt = ret->createOptionalParameter(3, "-weight-metric", "use weights from a metric file"); weightMetricOpt->addMetricParameter(1, "weight-metric", "metric file containing the weights"); OptionalParameter* columnOpt = ret->createOptionalParameter(4, "-column", "only display output for one column"); columnOpt->addStringParameter(1, "column", "the column number or name"); OptionalParameter* roiOpt = ret->createOptionalParameter(5, "-roi", "only consider data inside an roi"); roiOpt->addMetricParameter(1, "roi-metric", "the roi, as a metric file"); roiOpt->createOptionalParameter(2, "-match-maps", "each column of input uses the corresponding column from the roi file"); ret->createOptionalParameter(6, "-mean", "compute weighted mean"); OptionalParameter* stdevOpt = ret->createOptionalParameter(7, "-stdev", "compute weighted standard deviation"); stdevOpt->createOptionalParameter(1, "-sample", "estimate population stdev from the sample"); OptionalParameter* percentileOpt = ret->createOptionalParameter(8, "-percentile", "compute weighted percentile"); percentileOpt->addDoubleParameter(1, "percent", "the percentile to find"); ret->createOptionalParameter(9, "-sum", "compute weighted sum"); ret->createOptionalParameter(10, "-show-map-name", "print map index and name before each output"); ret->setHelpText( AString("For each column of the input, a single number is printed, resulting from the specified operation. ") + "Use -column to only give output for a single column. " + "Use -roi to consider only the data within a region. " + "Exactly one of -reduce or -percentile must be specified, and exactly one of -mean, -stdev, -percentile or -sum must be specified.\n\n" + "Using -sum with -area-surface (or -weight-metric with a metric containing similar data) is equivalent to integrating with respect to surface area. " + "For example, if you want to find the surface area within an roi, do this:\n\n" + "$ wb_command -metric-weighted-stats roi.func.gii -sum -area-surface midthickness.surf.gii" ); return ret; } namespace { enum OperationType { MEAN, STDEV, SAMPSTDEV, PERCENTILE, SUM }; float doOperation(const float* data, const float* weights, const int& numNodes, const OperationType& myop, const float* roiData, const float& argument) {//argument is only used for percentile currently const float* useData = data, *useWeights = weights; int numUse = numNodes; vector dataScratch, weightScratch;//for when we have an ROI if (roiData != NULL) { dataScratch.reserve(numNodes); weightScratch.reserve(numNodes); for (int i = 0; i < numNodes; ++i) { if (roiData[i] > 0.0f) { dataScratch.push_back(data[i]); weightScratch.push_back(weights[i]); } } if (dataScratch.size() < 1) throw OperationException("roi contains no vertices"); useData = dataScratch.data(); useWeights = weightScratch.data(); numUse = (int)dataScratch.size(); } switch(myop) { case SUM: case MEAN: case STDEV: case SAMPSTDEV://these all start the same way { double accum = 0.0, weightsum = 0.0; for (int i = 0; i < numUse; ++i) { accum += useData[i] * useWeights[i]; weightsum += useWeights[i]; } if (myop == SUM) return accum; const float mean = accum / weightsum; if (myop == MEAN) return mean; accum = 0.0; double weightsum2 = 0.0;//for weighted sample stdev for (int i = 0; i < numUse; ++i) { float tempf = useData[i] - mean; accum += useWeights[i] * tempf * tempf; weightsum2 += useWeights[i] * useWeights[i]; } if (myop == STDEV) return sqrt(accum / weightsum); CaretAssert(myop == SAMPSTDEV); if (numUse < 2) throw OperationException("sample standard deviation requires at least 2 elements in the roi"); return sqrt(accum / (weightsum - weightsum2 / weightsum));//http://en.wikipedia.org/wiki/Weighted_arithmetic_mean#Weighted_sample_variance } case PERCENTILE: { CaretAssert(argument >= 0.0f && argument <= 100.0f); if (numUse == 1) return useData[0];//would need special handling anyway, so get it early CaretSimpleMinHeap sorter; double weightaccum = 0.0;//double will usually prevent adding weights in a different order from getting a different answer for (int i = 0; i < numUse; ++i) { if (useWeights[i] < 0.0f) throw OperationException("negative weights not allowed in weighted percentile"); weightaccum += useWeights[i]; sorter.push(useWeights[i], useData[i]);//sort by value } float targetWeight = argument / 100.0f * weightaccum; float lastData, nextData; float lastWeight = sorter.pop(&lastData); weightaccum = lastWeight; float nextWeight = sorter.top(&nextData); int position = 1;//because the first and last sections get special treatment to not have flat ends on the function while (weightaccum + nextWeight * 0.5f < targetWeight && sorter.size() > 1) { ++position; sorter.pop(); weightaccum += nextWeight; lastWeight = nextWeight; lastData = nextData; nextWeight = sorter.top(&nextData); } if (targetWeight < weightaccum) { if (position == 1) {//stretch interpolation at first position to the edge return lastData + (nextData - lastData) * 0.5f * ((targetWeight - weightaccum) / lastWeight + 1.0f); } else { return lastData + (nextData - lastData) * 0.5f * ((targetWeight - weightaccum) / (lastWeight * 0.5f) + 1.0f); } } else { if (position == numUse - 1) {//ditto return (lastData + nextData) * 0.5f + (nextData - lastData) * 0.5f * (targetWeight - weightaccum) / nextWeight; } else { return (lastData + nextData) * 0.5f + (nextData - lastData) * 0.5f * (targetWeight - weightaccum) / (nextWeight * 0.5f); } } } } CaretAssert(false);//make sure execution never actually reaches end of function throw OperationException("internal error in weighted stats"); } } void OperationMetricWeightedStats::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); MetricFile* input = myParams->getMetric(1); int numNodes = input->getNumberOfNodes(); int numCols = input->getNumberOfColumns(); vector areaData; const float* useWeights = NULL; OptionalParameter* areaSurfOpt = myParams->getOptionalParameter(2); if (areaSurfOpt->m_present) { SurfaceFile* mySurf = areaSurfOpt->getSurface(1); if (mySurf->getNumberOfNodes() != numNodes) throw OperationException("area surface has different number of vertices than input metric"); mySurf->computeNodeAreas(areaData); useWeights = areaData.data(); } OptionalParameter* weightMetricOpt = myParams->getOptionalParameter(3); if (weightMetricOpt->m_present) { if (useWeights != NULL) throw OperationException("you may not specify both -area-surface and -weight-metric"); MetricFile* myWeights = weightMetricOpt->getMetric(1); if (myWeights->getNumberOfNodes() != numNodes) throw OperationException("weight metric has different number of vertices than input metric"); useWeights = myWeights->getValuePointerForColumn(0); } if (useWeights == NULL) throw OperationException("you must specify either -area-surface or -weight-metric"); int column = -1; OptionalParameter* columnOpt = myParams->getOptionalParameter(4); if (columnOpt->m_present) { column = input->getMapIndexFromNameOrNumber(columnOpt->getString(1)); if (column < 0) throw OperationException("invalid column specified"); } bool matchColumnMode = false; MetricFile* myRoi = NULL; const float* roiData = NULL; OptionalParameter* roiOpt = myParams->getOptionalParameter(5); if (roiOpt->m_present) { myRoi = roiOpt->getMetric(1); if (myRoi->getNumberOfNodes() != numNodes) throw OperationException("roi doesn't match input in number of vertices"); if (roiOpt->getOptionalParameter(2)->m_present) { if (myRoi->getNumberOfColumns() != numCols) { throw OperationException("-match-maps specified, but roi has different number of columns than input"); } matchColumnMode = true; } else { roiData = myRoi->getValuePointerForColumn(0); } } bool haveOp = false; OperationType myop; if (myParams->getOptionalParameter(6)->m_present) { haveOp = true; myop = MEAN; } OptionalParameter* stdevOpt = myParams->getOptionalParameter(7); if (stdevOpt->m_present) { if (haveOp) throw OperationException("you may only specify one operation"); haveOp = true; if (stdevOpt->getOptionalParameter(1)->m_present) { myop = SAMPSTDEV; } else { myop = STDEV; } } float argument = -1.0f; OptionalParameter* percentileOpt = myParams->getOptionalParameter(8); if (percentileOpt->m_present) { if (haveOp) throw OperationException("you may only specify one operation"); haveOp = true; myop = PERCENTILE; argument = percentileOpt->getDouble(1); if (!(argument >= 0.0f && argument <= 100.0f)) throw OperationException("percentile must be between 0 and 100"); } if (myParams->getOptionalParameter(9)->m_present) { if (haveOp) throw OperationException("you may only specify one operation"); haveOp = true; myop = SUM; } if (!haveOp) throw OperationException("you must specify an operation"); bool showMapName = myParams->getOptionalParameter(10)->m_present; if (column == -1) { for (int i = 0; i < numCols; ++i) {//store result before printing anything, in case it throws while computing if (matchColumnMode) { roiData = myRoi->getValuePointerForColumn(i); } const float result = doOperation(input->getValuePointerForColumn(i), useWeights, numNodes, myop, roiData, argument); if (showMapName) cout << AString::number(i + 1) << ": " << input->getMapName(i) << ": "; stringstream resultsstr; resultsstr << setprecision(7) << result; cout << resultsstr.str() << endl; } } else { if (matchColumnMode) { roiData = myRoi->getValuePointerForColumn(column); } const float result = doOperation(input->getValuePointerForColumn(column), useWeights, numNodes, myop, roiData, argument); if (showMapName) cout << AString::number(column + 1) << ": " << input->getMapName(column) << ": "; stringstream resultsstr; resultsstr << setprecision(7) << result; cout << resultsstr.str() << endl; } } workbench-1.1.1/src/Operations/OperationMetricWeightedStats.h000066400000000000000000000026661255417355300244070ustar00rootroot00000000000000#ifndef __OPERATION_METRIC_WEIGHTED_STATS_H__ #define __OPERATION_METRIC_WEIGHTED_STATS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationMetricWeightedStats : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationMetricWeightedStats; } #endif //__OPERATION_METRIC_WEIGHTED_STATS_H__ workbench-1.1.1/src/Operations/OperationNiftiInformation.cxx000066400000000000000000000133321255417355300243060ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationNiftiInformation.h" #include "OperationException.h" #include "CiftiFile.h" #include "NiftiIO.h" #include "CiftiXML.h" #include "DataFileTypeEnum.h" #include using namespace caret; using namespace std; AString OperationNiftiInformation::getCommandSwitch() { return "-nifti-information"; } AString OperationNiftiInformation::getShortDescription() { return "DISPLAY INFORMATION ABOUT A NIFTI/CIFTI FILE"; } OperationParameters* OperationNiftiInformation::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addStringParameter(1, "nifti-file", "the nifti/cifti file to examine"); ret->createOptionalParameter(2, "-print-header", "display the header contents"); ret->createOptionalParameter(3, "-print-matrix", "output the values in the matrix (cifti only)"); OptionalParameter* printXmlOpt = ret->createOptionalParameter(4, "-print-xml", "print the cifti XML (cifti only)"); OptionalParameter* pxVersionOpt = printXmlOpt->createOptionalParameter(1, "-version", "convert the XML to a specific CIFTI version (default is the file's cifti version)"); pxVersionOpt->addStringParameter(1, "version", "the CIFTI version to use"); ret->setHelpText( AString("You must specify at least one -print-* option.") ); return ret; } void OperationNiftiInformation::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); const AString fileName = myParams->getString(1); bool printHeader = myParams->getOptionalParameter(2)->m_present; bool printMatrix = myParams->getOptionalParameter(3)->m_present; OptionalParameter* printXmlOpt = myParams->getOptionalParameter(4); bool printXml = printXmlOpt->m_present; if (!printHeader && !printMatrix && !printXml) throw OperationException("you must specify a -print-* option"); if(!QFile::exists(fileName)) throw OperationException("File '" + fileName + "' does not exist."); if(printHeader) { NiftiIO myIO; myIO.openRead(fileName); cout << myIO.getHeader().toString() << endl; cout << getMemorySizeAsString(myIO.getDimensions()) << endl; } if(printXml) { CiftiFile cf(fileName); const CiftiXML& xml = cf.getCiftiXML(); CiftiVersion printVersion = xml.getParsedVersion();//by default, rewrite with the same version that it was read with OptionalParameter* pxVersionOpt = printXmlOpt->getOptionalParameter(1); if (pxVersionOpt->m_present) { printVersion = CiftiVersion(pxVersionOpt->getString(1)); } AString xmlString = xml.writeXMLToString(printVersion); cout << xmlString << endl; } if(printMatrix) { CiftiFile cf(fileName); if (cf.getCiftiXML().getNumberOfDimensions() != 2) throw OperationException("-print-matrix only supports 2D cifti"); int64_t dim0 = cf.getNumberOfRows(); int64_t dim1 = cf.getNumberOfColumns(); vector row(dim1); AString rowString; for(int64_t i = 0;i& dimensions) { AString str; int64_t numVoxelComponents = 0; const int64_t numDims = static_cast(dimensions.size()); if (numDims > 0) { numVoxelComponents = 1; for (int64_t i = 0; i < numDims; i++) { CaretAssertVectorIndex(dimensions, i); numVoxelComponents *= dimensions[i]; } } const int64_t numberOfBytes = sizeof(float) * numVoxelComponents; str += ("Data Size in (float) memory, Bytes: " + AString::number(numberOfBytes)); const double oneKilobyte = 1024.0; const double kilobytes = numberOfBytes / oneKilobyte; const double oneMegabyte = 1048576.0; const double megabytes = numberOfBytes / oneMegabyte; const double oneGigabyte = 1073741824.0; const double gigabytes = numberOfBytes / oneGigabyte; const double oneTerabyte = 1099511627776.0; const double terabytes = numberOfBytes / oneTerabyte; if (terabytes >= 1.0) { str += (" Terabytes: " + AString::number(terabytes)); } else if (gigabytes >= 1.0) { str += (" Gigabytes: " + AString::number(gigabytes)); } else if (megabytes >= 1.0) { str += (" Megabytes: " + AString::number(megabytes)); } else if (kilobytes >= 1.0) { str += (" Kilobytes: " + AString::number(kilobytes)); } return str; } workbench-1.1.1/src/Operations/OperationNiftiInformation.h000066400000000000000000000030041255417355300237260ustar00rootroot00000000000000#ifndef __OPERATION_NIFTI_INFORMATION_H__ #define __OPERATION_NIFTI_INFORMATION_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationNiftiInformation : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); private: static AString getMemorySizeAsString(const std::vector& dimensions); }; typedef TemplateAutoOperation AutoOperationNiftiInformation; } #endif //__OPERATION_NIFTI_INFORMATION_H__ workbench-1.1.1/src/Operations/OperationProbtrackXDotConvert.cxx000066400000000000000000000516141255417355300251230ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationProbtrackXDotConvert.h" #include "OperationException.h" #include "CaretLogger.h" #include "CiftiFile.h" #include "MetricFile.h" #include "StructureEnum.h" #include "VolumeFile.h" #include #include #include #include using namespace caret; using namespace std; //specifically for the purpose of sorting the input .dot file to the order required, in case it isn't initially ordered correctly struct SparseValue { int32_t index[2];//save some memory float value; bool operator<(const SparseValue& rhs) const { return (index[1] < rhs.index[1]);//NOTE: this specifically avoids minor ordering by the other index to make the sort faster (less swapping because more "equals" cases) } }; AString OperationProbtrackXDotConvert::getCommandSwitch() { return "-probtrackx-dot-convert"; } AString OperationProbtrackXDotConvert::getShortDescription() { return "CONVERT A .DOT FILE FROM PROBTRACKX TO CIFTI"; } OperationParameters* OperationProbtrackXDotConvert::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addStringParameter(1, "dot-file", "input .dot file"); ret->addCiftiOutputParameter(2, "cifti-out", "output cifti file"); OptionalParameter* rowVoxelOpt = ret->createOptionalParameter(3, "-row-voxels", "the output mapping along a row will be voxels"); rowVoxelOpt->addStringParameter(1, "voxel-list-file", "a text file containing IJK indices for the voxels used"); rowVoxelOpt->addVolumeParameter(2, "label-vol", "a label volume with the dimensions and sform used, with structure labels"); OptionalParameter* rowSurfaceOpt = ret->createOptionalParameter(4, "-row-surface", "the output mapping along a row will be surface vertices"); rowSurfaceOpt->addMetricParameter(1, "roi-metric", "a metric file with positive values on all nodes used"); OptionalParameter* rowCiftiOpt = ret->createOptionalParameter(9, "-row-cifti", "take the mapping along a row from a cifti file"); rowCiftiOpt->addCiftiParameter(1, "cifti", "the cifti file to take the mapping from"); rowCiftiOpt->addStringParameter(2, "direction", "which dimension to take the mapping along, ROW or COLUMN"); OptionalParameter* colVoxelOpt = ret->createOptionalParameter(5, "-col-voxels", "the output mapping along a column will be voxels"); colVoxelOpt->addStringParameter(1, "voxel-list-file", "a text file containing IJK indices for the voxels used"); colVoxelOpt->addVolumeParameter(2, "label-vol", "a label volume with the dimensions and sform used, with structure labels"); OptionalParameter* colSurfaceOpt = ret->createOptionalParameter(6, "-col-surface", "the output mapping along a column will be surface vertices"); colSurfaceOpt->addMetricParameter(1, "roi-metric", "a metric file with positive values on all nodes used"); OptionalParameter* colCiftiOpt = ret->createOptionalParameter(10, "-col-cifti", "take the mapping along a column from a cifti file"); colCiftiOpt->addCiftiParameter(1, "cifti", "the cifti file to take the mapping from"); colCiftiOpt->addStringParameter(2, "direction", "which dimension to take the mapping along, ROW or COLUMN"); ret->createOptionalParameter(7, "-transpose", "transpose the input matrix"); ret->createOptionalParameter(8, "-make-symmetric", "transform half-square input into full matrix output"); AString myText = AString("NOTE: exactly one -row option and one -col option must be used.\n\n") + "If the input file does not have its indexes sorted in the correct ordering, this command may take longer than expected. " + "Specifying -transpose will transpose the input matrix before trying to put its values into the cifti file, which is currently needed for at least matrix2 " + "in order to display it as intended. " + "How the cifti file is displayed is based on which -row option is specified: if -row-voxels is specified, then it will display data on volume slices. " + "The label names in the label volume(s) must have the following names, other names are ignored:\n\n"; vector myStructureEnums; StructureEnum::getAllEnums(myStructureEnums); for (int i = 0; i < (int)myStructureEnums.size(); ++i) { myText += "\n" + StructureEnum::toName(myStructureEnums[i]); } ret->setHelpText(myText); return ret; } void OperationProbtrackXDotConvert::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); AString dotFileName = myParams->getString(1); CiftiFile* myCiftiOut = myParams->getOutputCifti(2); OptionalParameter* rowVoxelOpt = myParams->getOptionalParameter(3); OptionalParameter* rowSurfaceOpt = myParams->getOptionalParameter(4); OptionalParameter* rowCiftiOpt = myParams->getOptionalParameter(9); OptionalParameter* colVoxelOpt = myParams->getOptionalParameter(5); OptionalParameter* colSurfaceOpt = myParams->getOptionalParameter(6); OptionalParameter* colCiftiOpt = myParams->getOptionalParameter(10); bool transpose = myParams->getOptionalParameter(7)->m_present; bool halfMatrix = myParams->getOptionalParameter(8)->m_present; int numRowOpts = 0, numColOpts = 0; if (rowVoxelOpt->m_present) ++numRowOpts; if (rowSurfaceOpt->m_present) ++numRowOpts; if (rowCiftiOpt->m_present) ++numRowOpts; if (colVoxelOpt->m_present) ++numColOpts; if (colSurfaceOpt->m_present) ++numColOpts; if (colCiftiOpt->m_present) ++numColOpts; if (numRowOpts != 1)//if both false or both true, basically using equals as a quick hack for xnor { throw OperationException("you must specify exactly one of -row-voxels, -row-surface, and -row-cifti"); } if (numColOpts != 1) { throw OperationException("you must specify exactly one of -col-voxels, -col-surface, and -col-cifti"); } CiftiXMLOld myXML; myXML.resetRowsToBrainModels(); myXML.resetColumnsToBrainModels(); vector rowReorderMap, colReorderMap; if (rowVoxelOpt->m_present) { addVoxelMapping(rowVoxelOpt->getVolume(2), rowVoxelOpt->getString(1), myXML, rowReorderMap, CiftiXMLOld::ALONG_ROW); } if (rowSurfaceOpt->m_present) { MetricFile* myMetric = rowSurfaceOpt->getMetric(1); myXML.addSurfaceModelToRows(myMetric->getNumberOfNodes(), myMetric->getStructure(), myMetric->getValuePointerForColumn(0)); } if (rowCiftiOpt->m_present) { AString directionName = rowCiftiOpt->getString(2); int myDir; if (directionName == "ROW") { myDir = CiftiXMLOld::ALONG_ROW; } else if (directionName == "COLUMN") { myDir = CiftiXMLOld::ALONG_COLUMN; } else { throw OperationException("incorrect string for direction, use ROW or COLUMN"); } myXML.copyMapping(CiftiXMLOld::ALONG_ROW, rowCiftiOpt->getCifti(1)->getCiftiXMLOld(), myDir); } if (colVoxelOpt->m_present) { addVoxelMapping(colVoxelOpt->getVolume(2), colVoxelOpt->getString(1), myXML, colReorderMap, CiftiXMLOld::ALONG_COLUMN); } if (colSurfaceOpt->m_present) { MetricFile* myMetric = colSurfaceOpt->getMetric(1); myXML.addSurfaceModelToColumns(myMetric->getNumberOfNodes(), myMetric->getStructure(), myMetric->getValuePointerForColumn(0)); } if (colCiftiOpt->m_present) { AString directionName = colCiftiOpt->getString(2); int myDir; if (directionName == "ROW") { myDir = CiftiXMLOld::ALONG_ROW; } else if (directionName == "COLUMN") { myDir = CiftiXMLOld::ALONG_COLUMN; } else { throw OperationException("incorrect string for direction, use ROW or COLUMN"); } myXML.copyMapping(CiftiXMLOld::ALONG_COLUMN, colCiftiOpt->getCifti(1)->getCiftiXMLOld(), myDir); } fstream dotFile(dotFileName.toAscii().constData(), fstream::in); if (!dotFile.good()) { throw OperationException("error opening text file '" + dotFileName + "'"); } SparseValue tempValue; vector dotFileContents; int32_t rowSize = myXML.getNumberOfColumns(), colSize = myXML.getNumberOfRows(); if (halfMatrix && rowSize != colSize) { throw OperationException("-make-symmetric was specified, but the matrix is not square"); } if (halfMatrix && transpose) { CaretLogInfo("-transpose is not needed with -make-symmetric"); } int64_t numZeros = 0; bool afterZero = false; if (transpose) { while (dotFile >> tempValue.index[1] >> tempValue.index[0] >> tempValue.value)//this is the only line that is different for transpose { if (tempValue.value == 0.0f) { if (tempValue.index[0] != rowSize || tempValue.index[1] != colSize) { throw OperationException("dimensions line in .dot file doesn't agree with provided row/column spaces"); } ++numZeros;//ignore, we expect one line (last in file) to have this } else { if (tempValue.index[0] < 1 || tempValue.index[0] > rowSize || tempValue.index[1] < 1 || tempValue.index[1] > colSize) { throw OperationException("found invalid index pair in dot file: " + AString::number(tempValue.index[0]) + ", " + AString::number(tempValue.index[1]) + ", perhaps you need to remove -transpose"); } if (numZeros != 0) afterZero = true; tempValue.index[0] -= 1;//fix for 1-indexing tempValue.index[1] -= 1; dotFileContents.push_back(tempValue); if (halfMatrix && tempValue.index[0] != tempValue.index[1]) { int32_t tempIndex = tempValue.index[0]; tempValue.index[0] = tempValue.index[1]; tempValue.index[1] = tempIndex; dotFileContents.push_back(tempValue); } } } } else { while (dotFile >> tempValue.index[0] >> tempValue.index[1] >> tempValue.value) { if (tempValue.value == 0.0f) { if (tempValue.index[0] != rowSize || tempValue.index[1] != colSize) { throw OperationException("dimensions line in .dot file doesn't agree with provided row/column spaces"); } ++numZeros; } else { if (tempValue.index[0] < 1 || tempValue.index[0] > rowSize || tempValue.index[1] < 1 || tempValue.index[1] > colSize) { throw OperationException("found invalid index pair in dot file: " + AString::number(tempValue.index[0]) + ", " + AString::number(tempValue.index[1]) + ", perhaps you need to use -transpose"); } if (numZeros != 0) afterZero = true; tempValue.index[0] -= 1;//fix for 1-indexing tempValue.index[1] -= 1; dotFileContents.push_back(tempValue); if (halfMatrix && tempValue.index[0] != tempValue.index[1]) { int32_t tempIndex = tempValue.index[0]; tempValue.index[0] = tempValue.index[1]; tempValue.index[1] = tempIndex; dotFileContents.push_back(tempValue); } } } } if (numZeros != 1) { CaretLogWarning("found (and ignored) " + AString::number(numZeros) + " lines with zero for value, expected 1"); } if (afterZero) { CaretLogWarning("found data lines after dimensionality line (which should be the last line of the file)"); } bool sorted = true; int64_t numValues = (int64_t)dotFileContents.size(); for (int64_t i = 1; i < numValues; ++i) { if (dotFileContents[i - 1].index[1] > dotFileContents[i].index[1]) { sorted = false; if (!halfMatrix) { CaretLogInfo("dot file indexes are not correctly sorted, sorting them may take a minute or so..."); } break; } } if (!sorted) sort(dotFileContents.begin(), dotFileContents.end()); if (!sorted && !halfMatrix) { CaretLogInfo("sorting finished"); } myCiftiOut->setCiftiXML(myXML); int64_t cur = 0, end = (int64_t)dotFileContents.size(); vector scratchRow(myXML.getNumberOfColumns(), 0.0f); vector checkDuplicate(myXML.getNumberOfColumns(), false); int64_t whichRow = 0;//set all rows, in case initial allocation doesn't give a zeroed matrix while (whichRow < myXML.getNumberOfRows()) { int64_t next = cur; while (next < end && dotFileContents[next].index[1] == whichRow) ++next; if (rowVoxelOpt->m_present) { for (int64_t i = cur; i < next; ++i) { int64_t outIndex = rowReorderMap[dotFileContents[i].index[0]]; if (checkDuplicate[outIndex]) { AString elemString; if (transpose) { elemString = AString::number(dotFileContents[i].index[1] + 1) + ", " + AString::number(dotFileContents[i].index[0] + 1); } else { elemString = AString::number(dotFileContents[i].index[0] + 1) + ", " + AString::number(dotFileContents[i].index[1] + 1); } if (halfMatrix) { throw OperationException("element specified more than once: " + elemString + ", perhaps you should not use -make-symmetric"); } else { throw OperationException("duplicate element found: " + elemString); } } scratchRow[outIndex] = dotFileContents[i].value; checkDuplicate[outIndex] = true; } } else { for (int64_t i = cur; i < next; ++i) { int64_t outIndex = dotFileContents[i].index[0]; if (checkDuplicate[outIndex]) { AString elemString; if (transpose) { elemString = AString::number(dotFileContents[i].index[1] + 1) + ", " + AString::number(dotFileContents[i].index[0] + 1); } else { elemString = AString::number(dotFileContents[i].index[0] + 1) + ", " + AString::number(dotFileContents[i].index[1] + 1); } if (halfMatrix) { throw OperationException("element specified more than once: " + elemString + ", perhaps you should not use -make-symmetric"); } else { throw OperationException("duplicate element found: " + elemString); } } scratchRow[outIndex] = dotFileContents[i].value; checkDuplicate[outIndex] = true; } } if (colVoxelOpt->m_present) { myCiftiOut->setRow(scratchRow.data(), colReorderMap[whichRow]); } else { myCiftiOut->setRow(scratchRow.data(), whichRow); } if (rowVoxelOpt->m_present) { for (int64_t i = cur; i < next; ++i) { int64_t outIndex = rowReorderMap[dotFileContents[i].index[0]]; scratchRow[outIndex] = 0.0f; checkDuplicate[outIndex] = false; } } else { for (int64_t i = cur; i < next; ++i) { int64_t outIndex = dotFileContents[i].index[0]; scratchRow[outIndex] = 0.0f; checkDuplicate[outIndex] = false; } } cur = next; ++whichRow; } } void OperationProbtrackXDotConvert::addVoxelMapping(const VolumeFile* myLabelVol, const AString& textFileName, CiftiXMLOld& myXML, vector& reorderMapping, const int& direction) { if (myLabelVol->getType() != SubvolumeAttributes::LABEL) { throw OperationException("specified volume for row voxels is not a label volume"); } vector myDims; myLabelVol->getDimensions(myDims); myXML.setVolumeDimsAndSForm(myDims.data(), myLabelVol->getSform()); const GiftiLabelTable* myLabelTable = myLabelVol->getMapLabelTable(0); map labelMap;//maps label values to structures vector > voxelLists;//voxel lists for each volume component vector > inputIndices;//index from the input space, matched to voxelLists map componentMap;//maps structures to indexes in voxelLists vector labelKeys; myLabelTable->getKeys(labelKeys); int64_t count = 0; for (int i = 0; i < (int)labelKeys.size(); ++i) { bool ok = false; StructureEnum::Enum thisStructure = StructureEnum::fromName(myLabelTable->getLabelName(labelKeys[i]), &ok); if (ok) { labelMap[labelKeys[i]] = thisStructure; if (componentMap.find(thisStructure) == componentMap.end())//make sure we don't already have this structure from another label { componentMap[thisStructure] = (int)count; ++count; } } } if (labelMap.empty()) { throw OperationException("label volume doesn't contain any structure labels"); } voxelLists.resize(count); inputIndices.resize(count); voxelIndexType vi, vj, vk; fstream myTextFile(textFileName.toAscii().constData(), fstream::in); if (!myTextFile.good()) { throw OperationException("error opening text file '" + textFileName + "'"); } count = 0; while (myTextFile >> vi >> vj >> vk) { if (!myLabelVol->indexValid(vi, vj, vk)) { throw OperationException("invalid voxel index found in text file: " + AString::number(vi) + ", " + AString::number(vj) + ", " + AString::number(vk)); } int myKey = (int)floor(myLabelVol->getValue(vi, vj, vk) + 0.5f); map::const_iterator myIter = labelMap.find(myKey); if (myIter == labelMap.end()) { throw OperationException("voxel index in list file did not match a structure: " + AString::number(vi) + ", " + AString::number(vj) + ", " + AString::number(vk)); } int myListIndex = componentMap[myIter->second];//will always exist voxelLists[myListIndex].push_back(vi); voxelLists[myListIndex].push_back(vj); voxelLists[myListIndex].push_back(vk); inputIndices[myListIndex].push_back(count); ++count; } vector forwardMap; for (map::const_iterator iter = componentMap.begin(); iter != componentMap.end(); ++iter) { int i = iter->second; int64_t listSize = voxelLists[i].size(); if (listSize != 0) { forwardMap.insert(forwardMap.end(), inputIndices[i].begin(), inputIndices[i].end());//append the structure's input index list, building lookup of new index->input index if (direction == CiftiXMLOld::ALONG_ROW) { myXML.addVolumeModelToRows(voxelLists[i], iter->first); } else { myXML.addVolumeModelToColumns(voxelLists[i], iter->first); } } } int64_t reorderSize = (int64_t)forwardMap.size(); reorderMapping.resize(reorderSize); for (int i = 0; i < reorderSize; ++i) { reorderMapping[forwardMap[i]] = i;//reverse it, building lookup from input index->new index } } workbench-1.1.1/src/Operations/OperationProbtrackXDotConvert.h000066400000000000000000000032351255417355300245440ustar00rootroot00000000000000#ifndef __OPERATION_PROBTRACK_X_DOT_CONVERT_H__ #define __OPERATION_PROBTRACK_X_DOT_CONVERT_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" #include namespace caret { class CiftiXMLOld; class OperationProbtrackXDotConvert : public AbstractOperation { static void addVoxelMapping(const VolumeFile* myLabelVol, const AString& textFileName, CiftiXMLOld& myXML, std::vector& reorderMapping, const int& direction); public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationProbtrackXDotConvert; } #endif //__OPERATION_PROBTRACK_X_DOT_CONVERT_H__ workbench-1.1.1/src/Operations/OperationSetMapName.cxx000066400000000000000000000115511255417355300230220ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationSetMapName.h" #include "OperationException.h" #include "DataFileTypeEnum.h" #include "CiftiFile.h" #include "LabelFile.h" #include "MetricFile.h" #include "VolumeFile.h" #include using namespace caret; using namespace std; AString OperationSetMapName::getCommandSwitch() { return "-set-map-name"; } AString OperationSetMapName::getShortDescription() { return "DEPRECATED: use -set-map-names"; } OperationParameters* OperationSetMapName::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addStringParameter(1, "data-file", "the file to set a map name of"); ret->addIntegerParameter(2, "index", "the map index to set the name of"); ret->addStringParameter(3, "name", "the name to set for the map"); ret->setHelpText( AString("DEPRECATED: this command may be removed in a future release, use -set-map-names.\n\n") + "Sets the name of a map for metric, shape, label, volume, cifti scalar or cifti label files." ); return ret; } void OperationSetMapName::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); AString fileName = myParams->getString(1); int mapIndex = (int)myParams->getInteger(2) - 1; if (mapIndex < 0) throw OperationException("invalid map index specified, indexes are 1-based"); AString mapName = myParams->getString(3); bool ok = false; DataFileTypeEnum::Enum myType = DataFileTypeEnum::fromFileExtension(fileName, &ok); if (!ok) { throw OperationException("unrecognized data file type"); } switch (myType) { case DataFileTypeEnum::METRIC: { MetricFile myMetric; myMetric.readFile(fileName); if (mapIndex >= myMetric.getNumberOfMaps()) throw OperationException("metric file doesn't have enough columns for specified map index"); myMetric.setMapName(mapIndex, mapName); myMetric.writeFile(fileName); break; } case DataFileTypeEnum::LABEL: { LabelFile myLabel; myLabel.readFile(fileName); if (mapIndex >= myLabel.getNumberOfMaps()) throw OperationException("label file doesn't have enough columns for specified map index"); myLabel.setMapName(mapIndex, mapName); myLabel.writeFile(fileName); break; } case DataFileTypeEnum::VOLUME: { VolumeFile myVol; myVol.readFile(fileName); if (mapIndex >= myVol.getNumberOfMaps()) throw OperationException("volume file doesn't have enough subvolumes for specified map index"); myVol.setMapName(mapIndex, mapName); myVol.writeFile(fileName); break; } case DataFileTypeEnum::CONNECTIVITY_DENSE_SCALAR: case DataFileTypeEnum::CONNECTIVITY_DENSE_TIME_SERIES: case DataFileTypeEnum::CONNECTIVITY_DENSE_LABEL: { CiftiFile myCifti; myCifti.openFile(fileName); myCifti.convertToInMemory(); CiftiXMLOld myXML = myCifti.getCiftiXMLOld(); if (mapIndex >= myXML.getNumberOfColumns()) throw OperationException("cifti file doesn't have enough columns for specified map index"); if (!myXML.setMapNameForIndex(CiftiXMLOld::ALONG_ROW, mapIndex, mapName)) throw OperationException("failed to set map name, check the type of the cifti file"); CiftiFile myOutCifti; myOutCifti.setWritingFile(fileName); myOutCifti.setCiftiXML(myXML); int numRows = myXML.getNumberOfRows(), rowSize = myXML.getNumberOfColumns(); vector scratchRow(rowSize); for (int i = 0; i < numRows; ++i) { myCifti.getRow(scratchRow.data(), i); myOutCifti.setRow(scratchRow.data(), i); } myOutCifti.writeFile(fileName); break; } default: throw OperationException("cannot set map name on this file type"); } } workbench-1.1.1/src/Operations/OperationSetMapName.h000066400000000000000000000026001255417355300224420ustar00rootroot00000000000000#ifndef __OPERATION_SET_MAP_NAME_H__ #define __OPERATION_SET_MAP_NAME_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationSetMapName : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationSetMapName; } #endif //__OPERATION_SET_MAP_NAME_H__ workbench-1.1.1/src/Operations/OperationSetMapNames.cxx000066400000000000000000000107111255417355300232020ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationSetMapNames.h" #include "OperationException.h" #include "CaretDataFile.h" #include "CaretDataFileHelper.h" #include "CaretMappableDataFile.h" #include "CaretPointer.h" #include "CaretLogger.h" #include #include using namespace caret; using namespace std; AString OperationSetMapNames::getCommandSwitch() { return "-set-map-names"; } AString OperationSetMapNames::getShortDescription() { return "SET THE NAME OF ONE OR MORE MAPS IN A FILE"; } OperationParameters* OperationSetMapNames::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addStringParameter(1, "data-file", "the file to set the map names of"); OptionalParameter* fileOpt = ret->createOptionalParameter(2, "-name-file", "use a text file to replace all map names"); fileOpt->addStringParameter(1, "file", "text file containing map names, one per line"); ParameterComponent* mapOpt = ret->createRepeatableParameter(3, "-map", "specify a map to set the name of"); mapOpt->addIntegerParameter(1, "index", "the map index to change the name of"); mapOpt->addStringParameter(2, "new-name", "the name to set for the map"); ret->setHelpText( AString("Sets the name of one or more maps for metric, shape, label, volume, cifti scalar or cifti label files. ") + "If the -name-file option is not specified, the -map option must be specified at least once. " + "The -map option cannot be used when -name-file is specified." ); return ret; } void OperationSetMapNames::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); AString fileName = myParams->getString(1); OptionalParameter* fileOpt = myParams->getOptionalParameter(2); const vector& mapOpts = *(myParams->getRepeatableParameterInstances(3)); CaretPointer caretDataFile(CaretDataFileHelper::readAnyCaretDataFile(fileName)); CaretMappableDataFile* mappableFile = dynamic_cast(caretDataFile.getPointer()); if (mappableFile == NULL) throw OperationException("cannot set map names on this file type"); if (fileOpt->m_present) { if (!mapOpts.empty()) throw OperationException("-map may not be specified when using -name-file"); AString listfileName = fileOpt->getString(1); ifstream nameListFile(listfileName.toLocal8Bit().constData()); if (!nameListFile.good()) { throw OperationException("error reading name list file"); } string mapName; int numMaps = mappableFile->getNumberOfMaps(); for (int i = 0; i < numMaps; ++i) { getline(nameListFile, mapName); if (!nameListFile) { CaretLogWarning("name file contained " + AString::number(i) + " names, expected " + AString::number(numMaps)); break; } mappableFile->setMapName(i, mapName.c_str()); } } else { if (mapOpts.empty()) throw OperationException("you must specify at least one option that sets a map name"); for (int i = 0; i < (int)mapOpts.size(); ++i) { int mapIndex = (int)mapOpts[i]->getInteger(1) - 1; if (mapIndex < 0) throw OperationException("invalid map index, indices are 1-based"); if (mapIndex >= mappableFile->getNumberOfMaps()) throw OperationException("invalid map index, file doesn't have enough maps"); AString newName = mapOpts[i]->getString(2); mappableFile->setMapName(mapIndex, newName); } } mappableFile->writeFile(fileName); } workbench-1.1.1/src/Operations/OperationSetMapNames.h000066400000000000000000000026061255417355300226330ustar00rootroot00000000000000#ifndef __OPERATION_SET_MAP_NAMES_H__ #define __OPERATION_SET_MAP_NAMES_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationSetMapNames : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationSetMapNames; } #endif //__OPERATION_SET_MAP_NAMES_H__ workbench-1.1.1/src/Operations/OperationSetStructure.cxx000066400000000000000000000154341255417355300235100ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationSetStructure.h" #include "OperationException.h" #include "BorderFile.h" #include "CaretLogger.h" #include "DataFileTypeEnum.h" #include "LabelFile.h" #include "MetricFile.h" #include "StructureEnum.h" #include "SurfaceFile.h" using namespace caret; using namespace std; AString OperationSetStructure::getCommandSwitch() { return "-set-structure"; } AString OperationSetStructure::getShortDescription() { return "SET STRUCTURE OF A DATA FILE"; } OperationParameters* OperationSetStructure::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addStringParameter(1, "data-file", "the file to set the structure of"); ret->addStringParameter(2, "structure", "the structure to set the file to"); OptionalParameter* surfType = ret->createOptionalParameter(3, "-surface-type", "set the type of a surface (only used if file is a surface file)"); surfType->addStringParameter(1, "type", "name of surface type"); OptionalParameter* secondaryType = ret->createOptionalParameter(4, "-surface-secondary-type", "set the secondary type of a surface (only used if file is a surface file)"); secondaryType->addStringParameter(1, "secondary type", "name of surface secondary type"); AString myText = AString("The existing file is modified and rewritten to the same filename. Valid values for the structure name are:\n\n"); vector myStructureEnums; StructureEnum::getAllEnums(myStructureEnums); for (int i = 0; i < (int)myStructureEnums.size(); ++i) { myText += StructureEnum::toName(myStructureEnums[i]) + "\n"; } myText += "\nValid names for the surface type are:\n\n"; vector mySurfTypeEnums; SurfaceTypeEnum::getAllEnums(mySurfTypeEnums); for (int i = 0; i < (int)mySurfTypeEnums.size(); ++i) { myText += SurfaceTypeEnum::toName(mySurfTypeEnums[i]) + "\n"; } myText += "\nValid names for the surface secondary type are:\n\n"; vector mySecondaryTypeEnums; SecondarySurfaceTypeEnum::getAllEnums(mySecondaryTypeEnums); for (int i = 0; i < (int)mySecondaryTypeEnums.size(); ++i) { myText += SecondarySurfaceTypeEnum::toName(mySecondaryTypeEnums[i]) + "\n"; } ret->setHelpText(myText); return ret; } void OperationSetStructure::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); AString fileName = myParams->getString(1); AString structureName = myParams->getString(2); OptionalParameter* surfType = myParams->getOptionalParameter(3); OptionalParameter* secondaryType = myParams->getOptionalParameter(4); bool ok = false; SurfaceTypeEnum::Enum mySurfType = SurfaceTypeEnum::UNKNOWN;//so compilers won't complain about uninitialized if (surfType->m_present) { AString mySurfTypeName = surfType->getString(1); mySurfType = SurfaceTypeEnum::fromName(mySurfTypeName, &ok); if (!ok) { throw OperationException("unrecognized surface type"); } } SecondarySurfaceTypeEnum::Enum mySecondType = SecondarySurfaceTypeEnum::INVALID; if (secondaryType->m_present) { AString mySecondaryName = secondaryType->getString(1); mySecondType = SecondarySurfaceTypeEnum::fromName(mySecondaryName, &ok); if (!ok) { throw OperationException("unrecognized secondary surface type"); } } StructureEnum::Enum myStructure = StructureEnum::fromName(structureName, &ok); if (!ok) { throw OperationException("unrecognized structure type"); } DataFileTypeEnum::Enum myType = DataFileTypeEnum::fromFileExtension(fileName, &ok); if (!ok) { throw OperationException("unrecognized data file type"); } switch (myType) { case DataFileTypeEnum::SURFACE: { SurfaceFile mySurf; mySurf.readFile(fileName); mySurf.setStructure(myStructure); if (surfType->m_present) { mySurf.setSurfaceType(mySurfType); } if (secondaryType->m_present) { mySurf.setSecondaryType(mySecondType); } mySurf.writeFile(fileName); } break; case DataFileTypeEnum::LABEL: { if (surfType->m_present) CaretLogInfo("the -surface-type option is ignored with this file type"); if (secondaryType->m_present) CaretLogInfo("the -surface-secondary-type option is ignored with this file type"); LabelFile myLabel; myLabel.readFile(fileName); myLabel.setStructure(myStructure); myLabel.writeFile(fileName); } break; case DataFileTypeEnum::METRIC: { if (surfType->m_present) CaretLogInfo("the -surface-type option is ignored with this file type"); if (secondaryType->m_present) CaretLogInfo("the -surface-secondary-type option is ignored with this file type"); MetricFile myMetric; myMetric.readFile(fileName); myMetric.setStructure(myStructure); myMetric.writeFile(fileName); } break; case DataFileTypeEnum::BORDER: { if (surfType->m_present) CaretLogInfo("the -surface-type option is ignored with this file type"); if (secondaryType->m_present) CaretLogInfo("the -surface-secondary-type option is ignored with this file type"); BorderFile myBorder; myBorder.readFile(fileName); myBorder.setStructure(myStructure); myBorder.writeFile(fileName); } break; default: throw OperationException("cannot set the structure of this file type"); }; } workbench-1.1.1/src/Operations/OperationSetStructure.h000066400000000000000000000026111255417355300231260ustar00rootroot00000000000000#ifndef __OPERATION_SET_STRUCTURE_H__ #define __OPERATION_SET_STRUCTURE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationSetStructure : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationSetStructure; } #endif //__OPERATION_SET_STRUCTURE_H__ workbench-1.1.1/src/Operations/OperationShowScene.cxx000066400000000000000000000464261255417355300227370ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #ifdef HAVE_OSMESA #include #endif // HAVE_OSMESA #include #include #include "Brain.h" #include "BrainOpenGLFixedPipeline.h" #include "BrainOpenGLViewportContent.h" #include "CaretAssert.h" #include "CaretLogger.h" #include "DataFileException.h" #include "EventBrowserTabGet.h" #include "EventManager.h" #include "FileInformation.h" #include "DummyFontTextRenderer.h" #include "FtglFontTextRenderer.h" #include "ImageFile.h" #include "OperationShowScene.h" #include "OperationException.h" #include "Scene.h" #include "SceneAttributes.h" #include "SceneClass.h" #include "SceneClassArray.h" #include "SceneFile.h" #include "ScenePrimitiveArray.h" #include "SessionManager.h" #include "TileTabsConfiguration.h" #include "VolumeFile.h" //#include "workbench_png.h" using namespace caret; /** * \class caret::OperationShowScene * \brief Offscreen rendering of scene to an image file * * Render a scene into an image file using the Offscreen Mesa Library */ /** * @return Command line switch */ AString OperationShowScene::getCommandSwitch() { return "-show-scene"; } /** * @return Short description of operation */ AString OperationShowScene::getShortDescription() { return ("OFFSCREEN RENDERING OF SCENE TO AN IMAGE FILE"); } /** * @return Parameters for operation */ OperationParameters* OperationShowScene::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addStringParameter(1, "scene-file", "scene file"); ret->addStringParameter(2, "scene-name-or-number", "name or number (starting at one) of the scene in the scene file"); ret->addStringParameter(3, "image-file-name", "output image file name"); ret->addIntegerParameter(4, "image-width", "width of output image(s)"); ret->addIntegerParameter(5, "image-height", "height of output image(s)"); AString helpText("Render content of browser windows displayed in a scene " "into image file(s). The image file name should be " "similar to \"capture.png\". If there is only one image " "to render, the image name will not change. If there is " "more than one image to render, an index will be inserted " "into the image name: \"capture_01.png\", \"capture_02.png\" " "etc.\n" "\n" "The image format is determined by the image file extension.\n" "Image formats available on this system are:\n"); std::vector imageFileExtensions; AString defaultExtension; ImageFile::getImageFileExtensions(imageFileExtensions, defaultExtension); for (std::vector::iterator iter = imageFileExtensions.begin(); iter != imageFileExtensions.end(); iter++) { const AString ext = *iter; helpText += (" " + ext + "\n"); } helpText += ("Note: Available image formats may vary by operating system.\n"); ret->setHelpText(helpText); return ret; } /** * Use Parameters and perform operation */ #ifndef HAVE_OSMESA void OperationShowScene::useParameters(OperationParameters* /*myParams*/, ProgressObject* /*myProgObj*/) { throw OperationException("Show scene command not available due to this software version " "not being built with the Mesa OffScreen Library"); } #else // HAVE_OSMESA void OperationShowScene::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); AString sceneFileName = FileInformation(myParams->getString(1)).getAbsoluteFilePath(); AString sceneNameOrNumber = myParams->getString(2); AString imageFileName = FileInformation(myParams->getString(3)).getAbsoluteFilePath(); const int32_t imageWidth = myParams->getInteger(4); if (imageWidth < 0) { throw OperationException("image width is invalid"); } const int32_t imageHeight = myParams->getInteger(5); if (imageHeight < 0) { throw OperationException("image height is invalid"); } SceneFile sceneFile; sceneFile.readFile(sceneFileName); Scene* scene = sceneFile.getSceneWithName(sceneNameOrNumber); if (scene == NULL) { bool valid = false; const int32_t sceneIndexStartAtOne = sceneNameOrNumber.toInt(&valid); if (valid) { const int32_t sceneIndex = sceneIndexStartAtOne - 1; if ((sceneIndex >= 0) && (sceneIndex < sceneFile.getNumberOfScenes())) { scene = sceneFile.getSceneAtIndex(sceneIndex); } else { throw OperationException("Scene index is invalid"); } } else { throw OperationException("Scene name is invalid"); } } // // Create the Mesa Context // const int depthBits = 16; const int stencilBits = 0; const int accumBits = 0; OSMesaContext mesaContext = OSMesaCreateContextExt(OSMESA_RGBA, depthBits, stencilBits, accumBits, NULL); if (mesaContext == 0) { std::cout << "Creating Mesa Context failed." << std::endl; exit(-1); } // // Allocate image buffer // unsigned char* imageBuffer = new unsigned char[imageWidth * imageHeight * 4 * sizeof(unsigned char)]; if (imageBuffer == 0) { std::cout << "Allocating image buffer failed." << std::endl; exit(-1); } // // Assign buffer to Mesa Context and make current // if (OSMesaMakeCurrent(mesaContext, imageBuffer, GL_UNSIGNED_BYTE, imageWidth, imageHeight) == 0) { std::cout << "Assigning buffer to context and make current failed." << std::endl; exit(-1); } /* * Set the viewport */ const int viewport[4] = { 0, 0, imageWidth, imageHeight }; /** * Enable voxel coloring since it is defaulted off for commands */ VolumeFile::setVoxelColoringEnabled(true); SceneAttributes sceneAttributes(SceneTypeEnum::SCENE_TYPE_FULL); const SceneClass* guiManagerClass = scene->getClassWithName("guiManager"); if (guiManagerClass->getName() != "guiManager") { throw OperationException("Top level scene class should be guiManager but it is: " + guiManagerClass->getName()); } SessionManager* sessionManager = SessionManager::get(); sessionManager->restoreFromScene(&sceneAttributes, guiManagerClass->getClass("m_sessionManager")); if (sessionManager->getNumberOfBrains() <= 0) { throw OperationException("Scene loading failure, SessionManager contains no Brains"); } Brain* brain = SessionManager::get()->getBrain(0); /* * Renders text */ BrainOpenGLTextRenderInterface* textRenderer = NULL; if (textRenderer == NULL) { textRenderer = new FtglFontTextRenderer(); if (! textRenderer->isValid()) { delete textRenderer; textRenderer = NULL; CaretLogWarning("Unable to create FTGL Font Renderer.\n" "No text will be available in graphics window."); } } if (textRenderer == NULL) { textRenderer = new DummyFontTextRenderer(); } /* * Performs OpenGL Rendering * Allocated dynamically so that it can be destroyed prior to OSMesa being * destroyed. Otherwise, if OpenGL is destroyed after OSMesa, errors * will occur as the OpenGL context is invalid when things such as * display lists or buffers are deleted. */ BrainOpenGLFixedPipeline* brainOpenGL = new BrainOpenGLFixedPipeline(textRenderer); brainOpenGL->initializeOpenGL(); /* * Restore windows */ const SceneClassArray* browserWindowArray = guiManagerClass->getClassArray("m_brainBrowserWindows"); if (browserWindowArray != NULL) { const int32_t numBrowserClasses = browserWindowArray->getNumberOfArrayElements(); for (int32_t i = 0; i < numBrowserClasses; i++) { const SceneClass* browserClass = browserWindowArray->getClassAtIndex(i); const bool restoreToTabTiles = browserClass->getBooleanValue("m_viewTileTabsAction", false); /* * If tile tabs was saved to the scene, restore it as the scenes tile tabs configuration */ if (restoreToTabTiles) { const AString tileTabsConfigString = browserClass->getStringValue("m_sceneTileTabsConfiguration"); if ( ! tileTabsConfigString.isEmpty()) { TileTabsConfiguration tileTabsConfiguration; tileTabsConfiguration.decodeFromXML(tileTabsConfigString); /* * Restore toolbar */ const SceneClass* toolbarClass = browserClass->getClass("m_toolbar"); if (toolbarClass != NULL) { /* * Index of selected browser tab (NOT the tabBar) */ std::vector allTabContent; const ScenePrimitiveArray* tabIndexArray = toolbarClass->getPrimitiveArray("tabIndices"); if (tabIndexArray != NULL) { const int32_t numTabs = tabIndexArray->getNumberOfArrayElements(); for (int32_t iTab = 0; iTab < numTabs; iTab++) { const int32_t tabIndex = tabIndexArray->integerValue(iTab); EventBrowserTabGet getTabContent(tabIndex); EventManager::get()->sendEvent(getTabContent.getPointer()); BrowserTabContent* tabContent = getTabContent.getBrowserTab(); if (tabContent == NULL) { throw OperationException("Failed to obtain tab number " + AString::number(tabIndex + 1) + " for window " + AString::number(i + 1)); } allTabContent.push_back(tabContent); } } const int32_t numTabContent = static_cast(allTabContent.size()); if (numTabContent <= 0) { throw OperationException("Failed to find any tab content"); } std::vector rowHeights; std::vector columnWidths; if ( ! tileTabsConfiguration.getRowHeightsAndColumnWidthsForWindowSize(imageWidth, imageHeight, numTabContent, rowHeights, columnWidths)) { throw OperationException("Tile Tabs Row/Column sizing failed !!!"); } const int32_t tabIndexToHighlight = -1; std::vector viewports = BrainOpenGLViewportContent::createViewportContentForTileTabs(allTabContent, brain, imageWidth, imageHeight, rowHeights, columnWidths, tabIndexToHighlight); brainOpenGL->drawModels(viewports); const int32_t outputImageIndex = ((numBrowserClasses > 1) ? i : -1); writeImage(imageFileName, outputImageIndex, imageBuffer, imageWidth, imageHeight); for (std::vector::iterator vpIter = viewports.begin(); vpIter != viewports.end(); vpIter++) { delete *vpIter; } viewports.clear(); } } else { throw OperationException("Tile tabs configuration is corrupted."); } } else { /* * Restore toolbar */ const SceneClass* toolbarClass = browserClass->getClass("m_toolbar"); if (toolbarClass != NULL) { /* * Index of selected browser tab (NOT the tabBar) */ const int32_t selectedTabIndex = toolbarClass->getIntegerValue("selectedTabIndex", -1); EventBrowserTabGet getTabContent(selectedTabIndex); EventManager::get()->sendEvent(getTabContent.getPointer()); BrowserTabContent* tabContent = getTabContent.getBrowserTab(); if (tabContent == NULL) { throw OperationException("Failed to obtain tab number " + AString::number(selectedTabIndex + 1) + " for window " + AString::number(i + 1)); } BrainOpenGLViewportContent content(viewport, viewport, false, brain, tabContent); std::vector viewportContents; viewportContents.push_back(&content); brainOpenGL->drawModels(viewportContents); const int32_t outputImageIndex = ((numBrowserClasses > 1) ? i : -1); writeImage(imageFileName, outputImageIndex, imageBuffer, imageWidth, imageHeight); } } } } if (textRenderer != NULL) { delete textRenderer; } delete brainOpenGL; /* * Free image memory and Mesa context */ delete[] imageBuffer; OSMesaDestroyContext(mesaContext); } #endif // HAVE_OSMESA /** * Write the image data to a Image File. * * @param imageFileName * Name of image file. * @param imageIndex * Index of image. * @param imageContent * content of image. * @param imageWidth * width of image. * @param imageHeight * height of image. */ void OperationShowScene::writeImage(const AString& imageFileName, const int32_t imageIndex, const unsigned char* imageContent, const int32_t imageWidth, const int32_t imageHeight) { /* * Create name of image */ QString outputName(imageFileName); if (imageIndex >= 0) { const AString imageNumber = QString("_%1").arg((int)(imageIndex + 1), 2, // width 10, // base QChar('0')); // fill character const int dotOffset = outputName.lastIndexOf("."); if (dotOffset >= 0) { outputName.insert(dotOffset, imageNumber); } else { outputName += (imageNumber + ".png"); } } try { //ImageFile imageFile(image); ImageFile imageFile(imageContent, imageWidth, imageHeight, ImageFile::IMAGE_DATA_ORIGIN_AT_BOTTOM); imageFile.writeFile(outputName); } catch (const DataFileException& dfe) { throw OperationException(dfe); } } /** * Is the show scene command available? */ bool OperationShowScene::isShowSceneCommandAvailable() { #ifdef HAVE_OSMESA return true; #else // HAVE_OSMESA return false; #endif // HAVE_OSMESA } workbench-1.1.1/src/Operations/OperationShowScene.h000066400000000000000000000034501255417355300223520ustar00rootroot00000000000000#ifndef __OPERATION_SHOW_SCENE_H__ #define __OPERATION_SHOW_SCENE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationShowScene : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); static bool isShowSceneCommandAvailable(); private: static void writeImage(const AString& imageFileName, const int32_t imageIndex, const unsigned char* imageContent, const int32_t imageWidth, const int32_t imageHeight); }; typedef TemplateAutoOperation AutoOperationShowScene; } // namespace #endif //__OPERATION_SHOW_SCENE_H__ workbench-1.1.1/src/Operations/OperationSpecFileMerge.cxx000066400000000000000000000045441255417355300235060ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "FileInformation.h" #include "OperationSpecFileMerge.h" #include "OperationException.h" #include "SpecFile.h" using namespace caret; using namespace std; AString OperationSpecFileMerge::getCommandSwitch() { return "-spec-file-merge"; } AString OperationSpecFileMerge::getShortDescription() { return "MERGE TWO SPEC FILES INTO ONE"; } OperationParameters* OperationSpecFileMerge::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addStringParameter(1, "spec-1", "first spec file to merge"); ret->addStringParameter(2, "spec-2", "second spec file to merge"); ret->addStringParameter(3, "out-spec", "output - output spec file");//fake the "output" formatting, could make a spec file parameter type ret->setHelpText(AString("The output spec file contains every file that is in either of the input spec files.")); return ret; } void OperationSpecFileMerge::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); AString spec1Name = FileInformation(myParams->getString(1)).getAbsoluteFilePath();//since opening a spec file may change the current directory, we must convert all paths to absolute first AString spec2Name = FileInformation(myParams->getString(2)).getAbsoluteFilePath(); AString outSpecName = FileInformation(myParams->getString(3)).getAbsoluteFilePath(); SpecFile spec1, spec2; spec1.readFile(spec1Name); spec2.readFile(spec2Name); spec1.appendSpecFile(spec2); spec1.writeFile(outSpecName); } workbench-1.1.1/src/Operations/OperationSpecFileMerge.h000066400000000000000000000026221255417355300231260ustar00rootroot00000000000000#ifndef __OPERATION_SPEC_FILE_MERGE_H__ #define __OPERATION_SPEC_FILE_MERGE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationSpecFileMerge : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationSpecFileMerge; } #endif //__OPERATION_SPEC_FILE_MERGE_H__ workbench-1.1.1/src/Operations/OperationSurfaceClosestVertex.cxx000066400000000000000000000056471255417355300251640ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationSurfaceClosestVertex.h" #include "OperationException.h" #include "SurfaceFile.h" #include using namespace caret; using namespace std; AString OperationSurfaceClosestVertex::getCommandSwitch() { return "-surface-closest-vertex"; } AString OperationSurfaceClosestVertex::getShortDescription() { return "FIND CLOSEST SURFACE VERTEX TO COORDINATES"; } OperationParameters* OperationSurfaceClosestVertex::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceParameter(1, "surface", "the surface to use"); ret->addStringParameter(2, "coord-list-file", "text file with coordinates"); ret->addStringParameter(3, "vertex-list-out", "output - the output text file with vertex numbers");//HACK: we don't currently have an "output text file" parameter type, fake the formatting ret->setHelpText( AString("For each coordinate XYZ triple, find the closest vertex in the surface, and output its vertex number into a text file.") ); return ret; } void OperationSurfaceClosestVertex::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); SurfaceFile* mySurf = myParams->getSurface(1); AString coordFileName = myParams->getString(2); fstream coordFile(coordFileName.toLocal8Bit().constData(), fstream::in); if (!coordFile.good()) { throw OperationException("error opening coordinate list file for reading"); } AString nodeFileName = myParams->getString(3); fstream nodeFile(nodeFileName.toLocal8Bit().constData(), fstream::out); if (!nodeFile.good()) { throw OperationException("error opening output file for writing"); } vector coords; float x, y, z; while (coordFile >> x >> y >> z)//yes, really, thats how they intended it to be used, more or less { coords.push_back(x); coords.push_back(y); coords.push_back(z); } for (int i = 0; i < (int)coords.size(); i += 3) { int node = mySurf->closestNode(coords.data() + i); nodeFile << node << endl; } } workbench-1.1.1/src/Operations/OperationSurfaceClosestVertex.h000066400000000000000000000026741255417355300246060ustar00rootroot00000000000000#ifndef __OPERATION_SURFACE_CLOSEST_VERTEX_H__ #define __OPERATION_SURFACE_CLOSEST_VERTEX_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationSurfaceClosestVertex : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationSurfaceClosestVertex; } #endif //__OPERATION_SURFACE_CLOSEST_VERTEX_H__ workbench-1.1.1/src/Operations/OperationSurfaceCoordinatesToMetric.cxx000066400000000000000000000047271255417355300262710ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationSurfaceCoordinatesToMetric.h" #include "OperationException.h" #include "MetricFile.h" #include "SurfaceFile.h" using namespace caret; using namespace std; AString OperationSurfaceCoordinatesToMetric::getCommandSwitch() { return "-surface-coordinates-to-metric"; } AString OperationSurfaceCoordinatesToMetric::getShortDescription() { return "MAKE METRIC FILE OF SURFACE COORDINATES"; } OperationParameters* OperationSurfaceCoordinatesToMetric::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceParameter(1, "surface", "the surface to use the coordinates of"); ret->addMetricOutputParameter(2, "metric-out", "the output metric"); ret->setHelpText( AString("Puts the coordinates of the surface into a 3-map metric file, as x, y, z.") ); return ret; } void OperationSurfaceCoordinatesToMetric::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); SurfaceFile* mySurf = myParams->getSurface(1); MetricFile* myMetricOut = myParams->getOutputMetric(2); int numNodes = mySurf->getNumberOfNodes(); myMetricOut->setNumberOfNodesAndColumns(numNodes, 3); myMetricOut->setStructure(mySurf->getStructure()); myMetricOut->setMapName(0, "x coordinate"); myMetricOut->setMapName(1, "y coordinate"); myMetricOut->setMapName(2, "z coordinate"); for (int i = 0; i < numNodes; ++i) { const float* coord = mySurf->getCoordinate(i); myMetricOut->setValue(i, 0, coord[0]); myMetricOut->setValue(i, 1, coord[1]); myMetricOut->setValue(i, 2, coord[2]); } } workbench-1.1.1/src/Operations/OperationSurfaceCoordinatesToMetric.h000066400000000000000000000027431255417355300257120ustar00rootroot00000000000000#ifndef __OPERATION_SURFACE_COORDINATES_TO_METRIC_H__ #define __OPERATION_SURFACE_COORDINATES_TO_METRIC_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationSurfaceCoordinatesToMetric : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationSurfaceCoordinatesToMetric; } #endif //__OPERATION_SURFACE_COORDINATES_TO_METRIC_H__ workbench-1.1.1/src/Operations/OperationSurfaceCutResample.cxx000066400000000000000000000047541255417355300245740ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationSurfaceCutResample.h" #include "OperationException.h" #include "SurfaceResamplingHelper.h" using namespace caret; using namespace std; AString OperationSurfaceCutResample::getCommandSwitch() { return "-surface-cut-resample"; } AString OperationSurfaceCutResample::getShortDescription() { return "RESAMPLE A CUT SURFACE"; } OperationParameters* OperationSurfaceCutResample::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceParameter(1, "surface-in", "the surface file to resample"); ret->addSurfaceParameter(2, "current-sphere", "a sphere surface with the mesh that the input surface is currently on"); ret->addSurfaceParameter(3, "new-sphere", "a sphere surface that is in register with and has the desired output mesh"); ret->addSurfaceOutputParameter(4, "surface-out", "the output surface file"); ret->setHelpText( AString("Resamples a surface file, given two spherical surfaces that are in register. ") + "Barycentric resampling is used, because it is usually better for resampling surfaces, and because it is needed to figure out the new topology anyway." ); return ret; } void OperationSurfaceCutResample::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); SurfaceFile* surfaceIn = myParams->getSurface(1); SurfaceFile* curSphere = myParams->getSurface(2); SurfaceFile* newSphere = myParams->getSurface(3); SurfaceFile* surfaceOut = myParams->getOutputSurface(4); SurfaceResamplingHelper::resampleCutSurface(surfaceIn, curSphere, newSphere, surfaceOut); } workbench-1.1.1/src/Operations/OperationSurfaceCutResample.h000066400000000000000000000026601255417355300242130ustar00rootroot00000000000000#ifndef __OPERATION_SURFACE_CUT_RESAMPLE_H__ #define __OPERATION_SURFACE_CUT_RESAMPLE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationSurfaceCutResample : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationSurfaceCutResample; } #endif //__OPERATION_SURFACE_CUT_RESAMPLE_H__ workbench-1.1.1/src/Operations/OperationSurfaceFlipNormals.cxx000066400000000000000000000043601255417355300245670ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationSurfaceFlipNormals.h" #include "OperationException.h" #include "SurfaceFile.h" using namespace caret; using namespace std; AString OperationSurfaceFlipNormals::getCommandSwitch() { return "-surface-flip-normals"; } AString OperationSurfaceFlipNormals::getShortDescription() { return "FLIP ALL TILES ON A SURFACE"; } OperationParameters* OperationSurfaceFlipNormals::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceParameter(1, "surface", "the surface to flip the normals of"); ret->addSurfaceOutputParameter(2, "surface-out", "the output surface"); ret->setHelpText( AString("Flips all triangles on a surface, resulting in surface normals being flipped the other direction (inward vs outward). ") + "If you transform a surface with an affine that has negative determinant, or a warpfield that similarly flips the surface, you may end up " + "with a surface that has normals pointing inwards, which may have display problems. " + "Using this command will solve that problem." ); return ret; } void OperationSurfaceFlipNormals::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); SurfaceFile* mySurf = myParams->getSurface(1); SurfaceFile* mySurfOut = myParams->getOutputSurface(2); *mySurfOut = *mySurf; mySurfOut->flipNormals(); } workbench-1.1.1/src/Operations/OperationSurfaceFlipNormals.h000066400000000000000000000026601255417355300242150ustar00rootroot00000000000000#ifndef __OPERATION_SURFACE_FLIP_NORMALS_H__ #define __OPERATION_SURFACE_FLIP_NORMALS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationSurfaceFlipNormals : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationSurfaceFlipNormals; } #endif //__OPERATION_SURFACE_FLIP_NORMALS_H__ workbench-1.1.1/src/Operations/OperationSurfaceGeodesicDistance.cxx000066400000000000000000000074461255417355300255460ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationSurfaceGeodesicDistance.h" #include "OperationException.h" #include "CaretAssert.h" #include "GeodesicHelper.h" #include "MetricFile.h" #include "SurfaceFile.h" using namespace caret; using namespace std; AString OperationSurfaceGeodesicDistance::getCommandSwitch() { return "-surface-geodesic-distance"; } AString OperationSurfaceGeodesicDistance::getShortDescription() { return "COMPUTE GEODESIC DISTANCE FROM ONE VERTEX TO THE ENTIRE SURFACE"; } OperationParameters* OperationSurfaceGeodesicDistance::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceParameter(1, "surface", "the surface to compute on"); ret->addIntegerParameter(2, "vertex", "the vertex to compute geodesic distance from"); ret->addMetricOutputParameter(3, "metric-out", "the output metric"); ret->createOptionalParameter(4, "-naive", "use only neighbors, don't crawl triangles (not recommended)"); OptionalParameter* limitOpt = ret->createOptionalParameter(5, "-limit", "stop at a certain distance"); limitOpt->addDoubleParameter(1, "limit-mm", "distance in mm to stop at"); ret->setHelpText( AString("Unless -limit is specified, computes the geodesic distance from the specified vertex to all others. ") + "The result is output as a single column metric file, with a value of -1 for vertices that the distance was not computed for. " + "If -naive is not specified, it uses not just immediate neighbors, but also neighbors derived from crawling across pairs of triangles that share an edge." ); return ret; } void OperationSurfaceGeodesicDistance::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); SurfaceFile* mySurf = myParams->getSurface(1); int myVertex = (int)myParams->getInteger(2); MetricFile* myMetricOut = myParams->getOutputMetric(3); bool smooth = !(myParams->getOptionalParameter(4)->m_present); CaretPointer myHelp = mySurf->getGeodesicHelper(); vector scratch(mySurf->getNumberOfNodes(), -1.0f);//use -1 to specify invalid OptionalParameter* limitOpt = myParams->getOptionalParameter(5); if (limitOpt->m_present) { vector nodes; vector dists; myHelp->getNodesToGeoDist(myVertex, limitOpt->getDouble(1), nodes, dists, smooth); for (int i = 0; i < (int)nodes.size(); ++i) { CaretAssertVectorIndex(dists, i); scratch[nodes[i]] = dists[i]; } } else { myHelp->getGeoFromNode(myVertex, scratch, smooth); if (scratch.size() == 0) throw OperationException("invalid vertex specified"); } myMetricOut->setNumberOfNodesAndColumns(mySurf->getNumberOfNodes(), 1); myMetricOut->setStructure(mySurf->getStructure()); myMetricOut->setColumnName(0, "vertex " + AString::number(myVertex) + " distance"); myMetricOut->setValuesForColumn(0, scratch.data()); } workbench-1.1.1/src/Operations/OperationSurfaceGeodesicDistance.h000066400000000000000000000027161255417355300251660ustar00rootroot00000000000000#ifndef __OPERATION_SURFACE_GEODESIC_DISTANCE_H__ #define __OPERATION_SURFACE_GEODESIC_DISTANCE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationSurfaceGeodesicDistance : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationSurfaceGeodesicDistance; } #endif //__OPERATION_SURFACE_GEODESIC_DISTANCE_H__ workbench-1.1.1/src/Operations/OperationSurfaceGeodesicROIs.cxx000066400000000000000000000244301255417355300246200ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationSurfaceGeodesicROIs.h" #include "OperationException.h" #include "GeodesicHelper.h" #include "MetricFile.h" #include "SurfaceFile.h" #include #include #include using namespace caret; using namespace std; AString OperationSurfaceGeodesicROIs::getCommandSwitch() { return "-surface-geodesic-rois"; } AString OperationSurfaceGeodesicROIs::getShortDescription() { return "DRAW GEODESIC LIMITED ROIS AT VERTICES"; } OperationParameters* OperationSurfaceGeodesicROIs::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceParameter(1, "surface", "the surface to draw on"); ret->addDoubleParameter(2, "limit", "geodesic distance limit from vertex, in mm"); ret->addStringParameter(3, "vertex-list-file", "a text file containing the vertices to draw ROIs around"); ret->addMetricOutputParameter(4, "metric-out", "the output metric"); OptionalParameter* gaussOpt = ret->createOptionalParameter(5, "-gaussian", "generate a gaussian kernel instead of a flat ROI"); gaussOpt->addDoubleParameter(1, "sigma", "the sigma for the gaussian kernel, in mm"); OptionalParameter* overlapOpt = ret->createOptionalParameter(6, "-overlap-logic", "how to handle overlapping ROIs, default ALLOW"); overlapOpt->addStringParameter(1, "method", "the method of resolving overlaps"); OptionalParameter* namesOpt = ret->createOptionalParameter(7, "-names", "name the columns from text file"); namesOpt->addStringParameter(1, "name-list-file", "a text file containing column names, one per line"); ret->setHelpText( AString("For each vertex in the list file, a column in the output metric is created, and an ROI around that vertex is drawn in that column. ") + "Each metric column will have zeros outside the geodesic distance spacified by , and by default will have a value of 1.0 inside it. " + "If the -gaussian option is specified, the values inside the ROI will instead form a gaussian with the specified value of sigma, normalized " + "so that the sum of the nonzero values in the metric column is 1.0. The argument to -overlap-logic must be one of ALLOW, CLOSEST, or EXCLUDE. " + "ALLOW is the default, and means that ROIs are treated independently and may overlap. " + "CLOSEST means that ROIs may not overlap, and that no ROI contains vertices that are closer to a different seed vertex. " + "EXCLUDE means that ROIs may not overlap, and that any vertex within range of more than one ROI does not belong to any ROI." ); return ret; } void OperationSurfaceGeodesicROIs::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); SurfaceFile* mySurf = myParams->getSurface(1); float limit = (float)myParams->getDouble(2); AString nodeFileName = myParams->getString(3); MetricFile* myMetricOut = myParams->getOutputMetric(4); OptionalParameter* gaussOpt = myParams->getOptionalParameter(5); float sigma = -1.0f; if (gaussOpt->m_present) {//set up to use a gaussian function sigma = (float)gaussOpt->getDouble(1); if (sigma <= 0.0f) { throw OperationException("invalid sigma specified"); } } fstream textFile(nodeFileName.toLocal8Bit().constData(), fstream::in); if (!textFile.good()) { throw OperationException("error opening list file for reading"); } int nodenum, numNodes = mySurf->getNumberOfNodes(); vector nodelist; textFile >> nodenum; while (textFile) { if (nodenum < 0 || nodenum >= numNodes) { throw OperationException("invalid vertex number: " + AString::number(nodenum)); } nodelist.push_back(nodenum); textFile >> nodenum; } int overlapType = 1;//ALLOW OptionalParameter* overlapOpt = myParams->getOptionalParameter(6); if (overlapOpt->m_present) { AString overlapString = overlapOpt->getString(1); if (overlapString == "ALLOW") { overlapType = 1; } else if (overlapString == "CLOSEST") { overlapType = 2; } else if (overlapString == "EXCLUDE") { overlapType = 3; } else { throw OperationException("unrecognized overlap method: " + overlapString); } } vector namesList; OptionalParameter* namesOpt = myParams->getOptionalParameter(7); if (namesOpt->m_present) { AString namesFileName = namesOpt->getString(1); fstream namesfile(namesFileName.toLocal8Bit().constData(), fstream::in); if (!namesfile.good()) { throw OperationException("error opening names file for reading"); } int i = 0; string inputline; getline(namesfile, inputline); while (namesfile && i < (int)nodelist.size()) { namesList.push_back(inputline.c_str()); getline(namesfile, inputline); ++i; } } myMetricOut->setNumberOfNodesAndColumns(numNodes, (int)nodelist.size()); myMetricOut->setStructure(mySurf->getStructure()); float invneg2sigmasqr = -0.5f / (sigma * sigma); for (int i = 0; i < (int)nodelist.size(); ++i) { myMetricOut->initializeColumn(i); if (i < (int)namesList.size()) { myMetricOut->setColumnName(i, namesList[i]); } else { const float* myCoord = mySurf->getCoordinate(i); myMetricOut->setColumnName(i, "Vertex " + AString::number(nodelist[i]) + " (" + AString::number(myCoord[0], 'f', 1) + ", " + AString::number(myCoord[1], 'f', 1) + ", " + AString::number(myCoord[2], 'f', 1) + ")"); } } switch (overlapType) { case 1://ALLOW for (int i = 0; i < (int)nodelist.size(); ++i) { CaretPointer myhelp = mySurf->getGeodesicHelper(); vector roinodes; vector dists; myhelp->getNodesToGeoDist(nodelist[i], limit, roinodes, dists); if (sigma > 0.0f) { double accum = 0.0; for (int j = 0; j < (int)dists.size(); ++j) { dists[j] = exp(dists[j] * dists[j] * invneg2sigmasqr);//reuse the vector for weights accum += dists[j]; } for (int j = 0; j < (int)dists.size(); ++j) { dists[j] /= accum; myMetricOut->setValue(roinodes[j], i, dists[j]); } } else { for (int j = 0; j < (int)roinodes.size(); ++j) { myMetricOut->setValue(roinodes[j], i, 1.0f); } } } break; case 2: case 3: { vector useCounts(numNodes, 0); vector closestSeed(numNodes, -1); vector bestDists(numNodes, -1.0f); for (int i = 0; i < (int)nodelist.size(); ++i) { CaretPointer myhelp = mySurf->getGeodesicHelper(); vector roinodes; vector dists; myhelp->getNodesToGeoDist(nodelist[i], limit, roinodes, dists); for (int j = 0; j < (int)roinodes.size(); ++j) { ++useCounts[roinodes[j]]; if (bestDists[roinodes[j]] < 0.0f || dists[j] < bestDists[roinodes[j]]) { bestDists[roinodes[j]] = dists[j]; closestSeed[roinodes[j]] = i;//nodelist array index, not node number } } } if (sigma > 0.0f) { vector accums(nodelist.size(), 0.0); vector > roinodelists(nodelist.size()); vector > weightlists(nodelist.size()); for (int i = 0; i < numNodes; ++i) { if (closestSeed[i] != -1 && (overlapType == 2 || useCounts[i] == 1)) { roinodelists[closestSeed[i]].push_back(i); float weight = exp(bestDists[i] * bestDists[i] * invneg2sigmasqr); weightlists[closestSeed[i]].push_back(weight); accums[closestSeed[i]] += weight; } } for (int i = 0; i < (int)nodelist.size(); ++i) { for (int j = 0; j < (int)roinodelists[i].size(); ++j) { myMetricOut->setValue(roinodelists[i][j], i, weightlists[i][j] / accums[i]); } } } else { for (int i = 0; i < numNodes; ++i) { if (closestSeed[i] != -1 && (overlapType == 2 || useCounts[i] == 1)) { myMetricOut->setValue(i, closestSeed[i], 1.0f); } } } break; } default: throw OperationException("something very bad happened, notify the developers"); } } workbench-1.1.1/src/Operations/OperationSurfaceGeodesicROIs.h000066400000000000000000000026661255417355300242540ustar00rootroot00000000000000#ifndef __OPERATION_SURFACE_GEODESIC_ROIS_H__ #define __OPERATION_SURFACE_GEODESIC_ROIS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationSurfaceGeodesicROIs : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationSurfaceGeodesicROIs; } #endif //__OPERATION_SURFACE_GEODESIC_ROIS_H__ workbench-1.1.1/src/Operations/OperationSurfaceInformation.cxx000066400000000000000000000046071255417355300246320ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretAssert.h" #include "CaretLogger.h" #include "OperationSurfaceInformation.h" #include "OperationException.h" #include "DataFileException.h" #include "SurfaceFile.h" using namespace caret; /** * \class caret::OperationSurfaceInformation * \brief Display information about a surface */ /** * @return Command line switch */ AString OperationSurfaceInformation::getCommandSwitch() { return "-surface-information"; } /** * @return Short description of operation */ AString OperationSurfaceInformation::getShortDescription() { return "DISPLAY INFORMATION ABOUT A SURFACE"; } /** * @return Parameters for operation */ OperationParameters* OperationSurfaceInformation::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceParameter(1, "Surface File", "Surface for which information is displayed"); AString helpText = ("Information about surface is displayed including vertices, \n" "triangles, bounding box, and spacing."); ret->setHelpText(helpText); return ret; } /** * Use Parameters and perform operation */ void OperationSurfaceInformation::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); try { SurfaceFile* surfaceFile = myParams->getSurface(1); std::cout << qPrintable(surfaceFile->getInformation()) << std::endl; } catch (const DataFileException& dfe) { throw OperationException(dfe); } } workbench-1.1.1/src/Operations/OperationSurfaceInformation.h000066400000000000000000000027351255417355300242570ustar00rootroot00000000000000#ifndef __OPERATION_SURFACE_INFORMATION_H__ #define __OPERATION_SURFACE_INFORMATION_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationSurfaceInformation : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationSurfaceInformation; } // namespace #endif //__OPERATION_SURFACE_INFORMATION_H__ workbench-1.1.1/src/Operations/OperationSurfaceNormals.cxx000066400000000000000000000050571255417355300237600ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationSurfaceNormals.h" #include "OperationException.h" #include "MetricFile.h" #include "SurfaceFile.h" using namespace caret; using namespace std; AString OperationSurfaceNormals::getCommandSwitch() { return "-surface-normals"; } AString OperationSurfaceNormals::getShortDescription() { return "OUTPUT VERTEX NORMALS AS METRIC FILE"; } OperationParameters* OperationSurfaceNormals::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceParameter(1, "surface", "the surface to output the normals of"); ret->addMetricOutputParameter(2, "metric-out", "the normal vectors"); ret->setHelpText( AString("Computes the normal vectors of the surface file, and outputs them as a 3 column metric file.") ); return ret; } void OperationSurfaceNormals::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); SurfaceFile* mySurf = myParams->getSurface(1); MetricFile* myMetricOut = myParams->getOutputMetric(2); int numNodes = mySurf->getNumberOfNodes(); myMetricOut->setNumberOfNodesAndColumns(numNodes, 3); myMetricOut->setStructure(mySurf->getStructure()); myMetricOut->setColumnName(0, "normal x-component"); myMetricOut->setColumnName(1, "normal y-component"); myMetricOut->setColumnName(2, "normal z-component"); mySurf->computeNormals(); const float* normalData = mySurf->getNormalData(); vector colScratch(numNodes); for (int axis = 0; axis < 3; ++axis) { for (int i = 0; i < numNodes; ++i) { colScratch[i] = normalData[i * 3 + axis]; } myMetricOut->setValuesForColumn(axis, colScratch.data()); } } workbench-1.1.1/src/Operations/OperationSurfaceNormals.h000066400000000000000000000026251255417355300234030ustar00rootroot00000000000000#ifndef __OPERATION_SURFACE_NORMALS_H__ #define __OPERATION_SURFACE_NORMALS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationSurfaceNormals : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationSurfaceNormals; } #endif //__OPERATION_SURFACE_NORMALS_H__ workbench-1.1.1/src/Operations/OperationSurfaceVertexAreas.cxx000066400000000000000000000043061255417355300245720ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationSurfaceVertexAreas.h" #include "OperationException.h" #include "MetricFile.h" #include "SurfaceFile.h" #include using namespace caret; using namespace std; AString OperationSurfaceVertexAreas::getCommandSwitch() { return "-surface-vertex-areas"; } AString OperationSurfaceVertexAreas::getShortDescription() { return "MEASURE SURFACE AREA EACH VERTEX IS RESPONSIBLE FOR"; } OperationParameters* OperationSurfaceVertexAreas::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addSurfaceParameter(1, "surface", "the surface to measure"); ret->addMetricOutputParameter(2, "metric", "the output metric"); ret->setHelpText( AString("Each vertex gets one third of the area of each triangle it is a part of.") ); return ret; } void OperationSurfaceVertexAreas::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); SurfaceFile* mySurf = myParams->getSurface(1); MetricFile* myMetricOut = myParams->getOutputMetric(2); int numNodes = mySurf->getNumberOfNodes(); vector areas; mySurf->computeNodeAreas(areas); myMetricOut->setNumberOfNodesAndColumns(numNodes, 1); myMetricOut->setStructure(mySurf->getStructure()); myMetricOut->setColumnName(0, "vertex areas"); myMetricOut->setValuesForColumn(0, areas.data()); } workbench-1.1.1/src/Operations/OperationSurfaceVertexAreas.h000066400000000000000000000026601255417355300242200ustar00rootroot00000000000000#ifndef __OPERATION_SURFACE_VERTEX_AREAS_H__ #define __OPERATION_SURFACE_VERTEX_AREAS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationSurfaceVertexAreas : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationSurfaceVertexAreas; } #endif //__OPERATION_SURFACE_VERTEX_AREAS_H__ workbench-1.1.1/src/Operations/OperationTemplate.cxx.txt000066400000000000000000000056611255417355300234260ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationName.h" #include "OperationException.h" using namespace caret; using namespace std; AString OperationName::getCommandSwitch() { return "-command-switch"; } AString OperationName::getShortDescription() { return "SHORT DESCRIPTION"; } OperationParameters* OperationName::getParameters() { OperationParameters* ret = new OperationParameters(); //ret->addSurfaceParameter(1, "surface", "the surface to compute on"); //ret->addMetricOutputParameter(2, "metric-out", "the output metric"); //OptionalParameter* columnSelect = ret->createOptionalParameter(3, "-column", "select a single column"); //columnSelect->addStringParameter(1, "column", "the column number or name"); ret->setHelpText( AString("This is where you set the help text. ") + "DO NOT add the info about what the command line format is, and do not give the command switch, " + "short description, or the short descriptions of parameters. " + "Do not indent, manually break long lines, or format the text in any way " + "other than to separate paragraphs within the help text prose, usually with two newlines." ); return ret; } void OperationName::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); //SurfaceFile* mySurf = myParams->getSurface(1);//gets the surface with key 1 //MetricFile* myMetricOut = myParams->getOutputMetric(2);//gets the output metric with key 2 //OptionalParameter* columnSelect = myParams->getOptionalParameter(3);//gets optional parameter with key 3 /*int columnNum = -1; if (columnSelect->m_present) {//set up to use the single column columnNum = (int)myMetric->getMapIndexFromNameOrNumber(columnSelect->getString(1)); if (columnNum < 0 || columnNum >= myMetric->getNumberOfMaps()) { throw OperationException("invalid column specified"); } }//*/ //do the work here //myProgress.reportProgress(0.5f);//this is how you would report being half finished, if the operation takes a while (probably not) } workbench-1.1.1/src/Operations/OperationTemplate.h.txt000066400000000000000000000050411255417355300230430ustar00rootroot00000000000000#ifndef __OPERATION_NAME_H__ #define __OPERATION_NAME_H__ /*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ /* file->save as... and enter what you will name the class, plus .h find and replace these strings in plain text mode (not "whole word only"): OperationName : operation name, in CamelCase, with initial capital, same as what you saved the header file to OPERATION_NAME : uppercase of operation name, with underscore between words, used in #ifdef guards next, make OperationName.cxx from OperationTemplate.cxx.txt via one of the following (depending on working directory): cat OperationTemplate.cxx.txt | sed 's/[O]perationName/OperationName/g' > OperationName.cxx cat Operations/OperationTemplate.cxx.txt | sed 's/[O]perationName/OperationName/g' > Operations/OperationName.cxx cat src/Operations/OperationTemplate.cxx.txt | sed 's/[O]perationName/OperationName/g' > src/Operations/OperationName.cxx or manually copy and replace next, implement its functions add these to Operations/CMakeLists.txt: OperationName.h OperationName.cxx place the following lines into Commands/CommandOperationManager.cxx: #include "OperationName.h" //near the top this->commandOperations.push_back(new CommandParser(new AutoOperationName())); //in CommandOperationManager() finally, remove this block comment */ #include "AbstractOperation.h" namespace caret { class OperationName : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationName; } #endif //__OPERATION_NAME_H__ workbench-1.1.1/src/Operations/OperationVolumeCapturePlane.cxx000066400000000000000000000144101255417355300246000ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationVolumeCapturePlane.h" #include "OperationException.h" #include "CaretLogger.h" #include "ImageFile.h" #include "Vector3D.h" #include "VolumeFile.h" #include using namespace caret; using namespace std; AString OperationVolumeCapturePlane::getCommandSwitch() { return "-volume-capture-plane"; } AString OperationVolumeCapturePlane::getShortDescription() { return "INTERPOLATE IMAGE FROM PLANE THROUGH VOLUME"; } OperationParameters* OperationVolumeCapturePlane::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addVolumeParameter(1, "volume", "the volume file to interpolate from"); ret->addStringParameter(2, "subvolume", "the name or number of the subvolume to use"); ret->addStringParameter(3, "interp", "interpolation type"); ret->addIntegerParameter(4, "h-dim", "width of output image, in pixels"); ret->addIntegerParameter(5, "v-dim", "height of output image, in pixels"); ret->addDoubleParameter(6, "scale-min", "value to render as black"); ret->addDoubleParameter(7, "scale-max", "value to render as white"); int key = 8; char xyz[] = "xyz"; for (int i = 0; i < 3; ++i) { ret->addDoubleParameter(key, AString("bottom-left-") + xyz[i], xyz[i] + AString("-coordinate of the bottom left of the output image")); ++key; }//8 9 10 for (int i = 0; i < 3; ++i) { ret->addDoubleParameter(key, AString("bottom-right-") + xyz[i], xyz[i] + AString("-coordinate of the bottom right of the output image")); ++key; }//11 12 13 for (int i = 0; i < 3; ++i) { ret->addDoubleParameter(key, AString("top-left-") + xyz[i], xyz[i] + AString("-coordinate of the top left of the output image")); ++key; }//14 15 16 ret->addStringParameter(17, "image", "output - the output image");//fake the output formatting ret->setHelpText( AString("NOTE: If you want to generate an image with all of the capabilities of the GUI rendering, see -show-scene.\n\n") + "Renders an image of an arbitrary plane through the volume file, with a simple linear grayscale palette. " + "The parameter must be one of:\n\n" + "CUBIC\nENCLOSING_VOXEL\nTRILINEAR" ); return ret; } void OperationVolumeCapturePlane::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); VolumeFile* myVol = myParams->getVolume(1); AString subvolName = myParams->getString(2); int subvol = myVol->getMapIndexFromNameOrNumber(subvolName); if (subvol < 0 || subvol >= myVol->getNumberOfMaps()) { throw OperationException("invalid subvolume"); } AString interp = myParams->getString(3); VolumeFile::InterpType myMethod = VolumeFile::CUBIC; if (interp == "CUBIC") { myMethod = VolumeFile::CUBIC; } else if (interp == "TRILINEAR") { myMethod = VolumeFile::TRILINEAR; } else if (interp == "ENCLOSING_VOXEL") { myMethod = VolumeFile::ENCLOSING_VOXEL; } else { throw OperationException("unrecognized interpolation method"); } int width = (int)myParams->getInteger(4); int height = (int)myParams->getInteger(5); if (width < 2 || height < 2) { throw OperationException("output image dimensions must be 2 or greater");//to avoid divide by zero } float scalemin = (float)myParams->getDouble(6); float scalemax = (float)myParams->getDouble(7); Vector3D blvec, brvec, tlvec; int key = 8; for (int i = 0; i < 3; ++i) { blvec[i] = (float)myParams->getDouble(key); ++key; } for (int i = 0; i < 3; ++i) { brvec[i] = (float)myParams->getDouble(key); ++key; } for (int i = 0; i < 3; ++i) { tlvec[i] = (float)myParams->getDouble(key); ++key; } AString outName = myParams->getString(17); Vector3D rightTraverse = brvec - blvec, upTraverse = tlvec - blvec; if (abs(rightTraverse.normal().dot(upTraverse.normal())) > 0.001f) { CaretLogWarning("corner points describe non-orthogonal directions, image will be skewed"); } vector imageData(width * height * 4); for (int h = 0; h < height; ++h) { Vector3D rowStart = blvec + ((float)h) / (height - 1) * upTraverse; for (int w = 0; w < width; ++w) { Vector3D sample = rowStart + ((float)w) / (width - 1) * rightTraverse; bool valid = false; float value = myVol->interpolateValue(sample, myMethod, &valid, subvol); float normalized; if (valid) { if (value <= scalemin) { normalized = 0.0f; } else { if (value >= scalemax) { normalized = 1.0f; } else { normalized = (value - scalemin) / (scalemax - scalemin); } } } else { normalized = 0.0f; } uint8_t intensity = (uint8_t)(normalized * 255 + 0.5f); uint8_t* pixel = imageData.data() + (w + h * width) * 4; for (int i = 0; i < 4; ++i) { pixel[i] = intensity; } } } ImageFile outFile(imageData.data(), width, height, ImageFile::IMAGE_DATA_ORIGIN_AT_BOTTOM); outFile.writeFile(outName); } workbench-1.1.1/src/Operations/OperationVolumeCapturePlane.h000066400000000000000000000026601255417355300242310ustar00rootroot00000000000000#ifndef __OPERATION_VOLUME_CAPTURE_PLANE_H__ #define __OPERATION_VOLUME_CAPTURE_PLANE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationVolumeCapturePlane : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationVolumeCapturePlane; } #endif //__OPERATION_VOLUME_CAPTURE_PLANE_H__ workbench-1.1.1/src/Operations/OperationVolumeCopyExtensions.cxx000066400000000000000000000105061255417355300252110ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationVolumeCopyExtensions.h" #include "OperationException.h" #include "GiftiLabelTable.h" #include "GiftiMetaData.h" #include "NiftiHeader.h" //for NiftiExtension #include "PaletteColorMapping.h" #include "VolumeFile.h" using namespace caret; using namespace std; AString OperationVolumeCopyExtensions::getCommandSwitch() { return "-volume-copy-extensions"; } AString OperationVolumeCopyExtensions::getShortDescription() { return "COPY EXTENDED DATA TO ANOTHER VOLUME FILE"; } OperationParameters* OperationVolumeCopyExtensions::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addVolumeParameter(1, "data-volume", "the volume file containing the voxel data to use"); ret->addVolumeParameter(2, "extension-volume", "the volume file containing the extensions to use"); ret->addVolumeOutputParameter(3, "volume-out", "the output volume"); ret->createOptionalParameter(4, "-drop-unknown", "don't copy extensions that workbench doesn't understand"); ret->setHelpText( AString("This command copies the information in a volume file that isn't a critical part of the standard header or data matrix, ") + "e.g. map names, palette settings, label tables. " + "If -drop-unknown is not specified, it also copies similar kinds of information set by other software." ); return ret; } void OperationVolumeCopyExtensions::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); VolumeFile* dataVol = myParams->getVolume(1); VolumeFile* extVol = myParams->getVolume(2); VolumeFile* outVol = myParams->getOutputVolume(3); bool dropUnknown = myParams->getOptionalParameter(4)->m_present; if (!dataVol->getVolumeSpace().matches(extVol->getVolumeSpace())) throw OperationException("volume spaces do not match"); vector dataDims = dataVol->getDimensions(); vector extDims = extVol->getDimensions(); if (dataDims[4] != extDims[4]) throw OperationException("number of components (rgb or complex datatypes) does not match"); if (dataVol->getOriginalDimensions() != extVol->getOriginalDimensions()) throw OperationException("non-spatial dimensions do not match"); outVol->reinitialize(dataVol->getOriginalDimensions(), dataVol->getSform(), dataDims[4], extVol->getType()); outVol->m_header.grabNew(extVol->m_header->clone());//copy all standard header fields, too if (dropUnknown) { switch (outVol->m_header->getType()) { case AbstractHeader::NIFTI: ((NiftiHeader*)outVol->m_header.getPointer())->m_extensions.clear(); break; } } if (outVol->getFileMetaData() != NULL) { *(outVol->getFileMetaData()) = *(extVol->getFileMetaData()); } for (int64_t c = 0; c < dataDims[4]; ++c) { for (int64_t b = 0; b < dataDims[3]; ++b) { if (c == 0)//map names, etc { outVol->setMapName(b, extVol->getMapName(b)); *(outVol->getMapMetaData(b)) = *(extVol->getMapMetaData(b)); if (extVol->getType() == SubvolumeAttributes::LABEL) { *(outVol->getMapLabelTable(b)) = *(extVol->getMapLabelTable(b)); } else { *(outVol->getMapPaletteColorMapping(b)) = *(extVol->getMapPaletteColorMapping(b)); } } outVol->setFrame(dataVol->getFrame(b, c), b, c); } } } workbench-1.1.1/src/Operations/OperationVolumeCopyExtensions.h000066400000000000000000000026741255417355300246450ustar00rootroot00000000000000#ifndef __OPERATION_VOLUME_COPY_EXTENSIONS_H__ #define __OPERATION_VOLUME_COPY_EXTENSIONS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationVolumeCopyExtensions : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationVolumeCopyExtensions; } #endif //__OPERATION_VOLUME_COPY_EXTENSIONS_H__ workbench-1.1.1/src/Operations/OperationVolumeCreate.cxx000066400000000000000000000137031255417355300234240ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationVolumeCreate.h" #include "OperationException.h" #include "FloatMatrix.h" #include "VolumeFile.h" using namespace caret; using namespace std; AString OperationVolumeCreate::getCommandSwitch() { return "-volume-create"; } AString OperationVolumeCreate::getShortDescription() { return "CREATE A BLANK VOLUME FILE"; } OperationParameters* OperationVolumeCreate::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addIntegerParameter(1, "i-dim", "length of first dimension"); ret->addIntegerParameter(2, "j-dim", "length of second dimension"); ret->addIntegerParameter(3, "k-dim", "length of third dimension"); ret->addVolumeOutputParameter(4, "volume-out", "the output volume"); OptionalParameter* plumbOpt = ret->createOptionalParameter(5, "-plumb", "set via axis order and spacing/offset"); plumbOpt->addStringParameter(1, "axis-order", "a string like 'XYZ' that specifies which index is along which spatial dimension"); plumbOpt->addDoubleParameter(2, "x-spacing", "change in x-coordinate from incrementing the relevant index"); plumbOpt->addDoubleParameter(3, "y-spacing", "change in y-coordinate from incrementing the relevant index"); plumbOpt->addDoubleParameter(4, "z-spacing", "change in z-coordinate from incrementing the relevant index"); plumbOpt->addDoubleParameter(5, "x-offset", "the x-coordinate of the first voxel"); plumbOpt->addDoubleParameter(6, "y-offset", "the y-coordinate of the first voxel"); plumbOpt->addDoubleParameter(7, "z-offset", "the z-coordinate of the first voxel"); OptionalParameter* sformOpt = ret->createOptionalParameter(6, "-sform", "set via a nifti sform"); char axisNames[] = "xyz", indexNames[] = "ijk"; for (int axis = 0; axis < 3; ++axis) { for (int index = 0; index < 3; ++index) { sformOpt->addDoubleParameter(axis * 4 + index, AString(axisNames[axis]) + indexNames[index] + "-spacing", "increase in " + AString(axisNames[axis]) + " coordinate from incrementing the " + indexNames[index] + " index"); } sformOpt->addDoubleParameter(axis * 4 + 3, AString(axisNames[axis]) + "-offset", AString(axisNames[axis]) + " coordinate of first voxel"); } ret->setHelpText( AString("Creates a volume file full of zeros. ") + "Exactly one of -plumb or -sform must be specified." ); return ret; } void OperationVolumeCreate::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); vector dims(3); dims[0] = myParams->getInteger(1); dims[1] = myParams->getInteger(2); dims[2] = myParams->getInteger(3); VolumeFile* output = myParams->getOutputVolume(4); FloatMatrix newSform = FloatMatrix::zeros(4, 4); newSform[3][3] = 1.0f;//not needed, but just for sanity bool haveSpace = false; OptionalParameter* plumbOpt = myParams->getOptionalParameter(5); if (plumbOpt->m_present) { haveSpace = true; bool used[3] = {false, false, false}; int revorder[3] = {-1, -1, -1}; AString orient = plumbOpt->getString(1); if (orient.size() < 3) throw OperationException(" must have 3 characters"); for (int i = 0; i < 3; ++i) { int dir = -1; switch (orient[i].toAscii()) { case 'X': case 'x': dir = 0; break; case 'Y': case 'y': dir = 1; break; case 'Z': case 'z': dir = 2; break; default: throw OperationException(" must use the characters X, Y, and Z"); } if (used[dir]) throw OperationException(" may not repeat an axis"); used[dir] = true; revorder[dir] = i;//construct the reversed order, because thats what we need } newSform[0][revorder[0]] = (float)plumbOpt->getDouble(2); newSform[0][3] = (float)plumbOpt->getDouble(5); newSform[1][revorder[1]] = (float)plumbOpt->getDouble(3); newSform[1][3] = (float)plumbOpt->getDouble(6); newSform[2][revorder[2]] = (float)plumbOpt->getDouble(4); newSform[2][3] = (float)plumbOpt->getDouble(7); } OptionalParameter* sformOpt = myParams->getOptionalParameter(6); if (sformOpt->m_present) { if (haveSpace) throw OperationException("only one of -plumb and -sform may be specified"); haveSpace = true; for (int axis = 0; axis < 3; ++axis) { for (int index = 0; index < 3; ++index) { newSform[axis][index] = (float)sformOpt->getDouble(axis * 4 + index); } newSform[axis][3] = (float)sformOpt->getDouble(axis * 4 + 3); } } if (!haveSpace) throw OperationException("you must specify -plumb or -sform"); output->reinitialize(dims, newSform.getMatrix()); output->setValueAllVoxels(0.0f); } workbench-1.1.1/src/Operations/OperationVolumeCreate.h000066400000000000000000000026111255417355300230450ustar00rootroot00000000000000#ifndef __OPERATION_VOLUME_CREATE_H__ #define __OPERATION_VOLUME_CREATE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationVolumeCreate : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationVolumeCreate; } #endif //__OPERATION_VOLUME_CREATE_H__ workbench-1.1.1/src/Operations/OperationVolumeLabelExportTable.cxx000066400000000000000000000071511255417355300254120ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationVolumeLabelExportTable.h" #include "OperationException.h" #include "GiftiLabel.h" //we should rename these to not imply that they are gifti-specific #include "GiftiLabelTable.h" #include "VolumeFile.h" #include #include using namespace caret; using namespace std; AString OperationVolumeLabelExportTable::getCommandSwitch() { return "-volume-label-export-table"; } AString OperationVolumeLabelExportTable::getShortDescription() { return "EXPORT LABEL TABLE FROM VOLUME AS TEXT"; } OperationParameters* OperationVolumeLabelExportTable::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addVolumeParameter(1, "label-in", "the input volume label file"); ret->addStringParameter(2, "map", "the number or name of the label map to use"); ret->addStringParameter(3, "table-out", "output - the output text file");//fake output formatting ret->setHelpText( AString("Takes the label table from the volume label map, and writes it to a text format matching what is expected by -volume-label-import.") ); return ret; } int OperationVolumeLabelExportTable::floatTo255(const float& in) { return (int)floor(in * 255.0f + 0.5f); } void OperationVolumeLabelExportTable::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); VolumeFile* myVolume = myParams->getVolume(1); AString mapString = myParams->getString(2); AString outfileName = myParams->getString(3); if (myVolume->getType() != SubvolumeAttributes::LABEL) throw OperationException("volume file must be a label volume"); int64_t mapIndex = myVolume->getMapIndexFromNameOrNumber(mapString); if (mapIndex == -1) throw OperationException("map '" + mapString + "' not found"); ofstream outFile(outfileName.toLocal8Bit().constData()); if (!outFile) throw OperationException("failed to open output text file"); const GiftiLabelTable* myTable = myVolume->getMapLabelTable(mapIndex); set allKeys = myTable->getKeys(); int32_t unassignedKey = myTable->getUnassignedLabelKey(); for (set::iterator iter = allKeys.begin(); iter != allKeys.end(); ++iter) { if (*iter == unassignedKey) continue;//don't output the unused key, because import doesn't want it in the text file const GiftiLabel* thisLabel = myTable->getLabel(*iter); outFile << thisLabel->getName() << endl; outFile << thisLabel->getKey() << " " << floatTo255(thisLabel->getRed()) << " " << floatTo255(thisLabel->getGreen()) << " " << floatTo255(thisLabel->getBlue()) << " " << floatTo255(thisLabel->getAlpha()) << endl; if (!outFile) throw OperationException("error writing to output text file"); } } workbench-1.1.1/src/Operations/OperationVolumeLabelExportTable.h000066400000000000000000000027731255417355300250440ustar00rootroot00000000000000#ifndef __OPERATION_VOLUME_LABEL_EXPORT_TABLE_H__ #define __OPERATION_VOLUME_LABEL_EXPORT_TABLE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationVolumeLabelExportTable : public AbstractOperation { static int floatTo255(const float& in); public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationVolumeLabelExportTable; } #endif //__OPERATION_VOLUME_LABEL_EXPORT_TABLE_H__ workbench-1.1.1/src/Operations/OperationVolumeLabelImport.cxx000066400000000000000000000425011255417355300244310ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationVolumeLabelImport.h" #include "OperationException.h" #include "CaretLogger.h" #include "FileInformation.h" #include "GiftiLabel.h" #include "VolumeFile.h" #include #include #include #include #include #include #include using namespace caret; using namespace std; AString OperationVolumeLabelImport::getCommandSwitch() { return "-volume-label-import"; } AString OperationVolumeLabelImport::getShortDescription() { return "IMPORT A LABEL VOLUME TO CARET FORMAT"; } OperationParameters* OperationVolumeLabelImport::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addVolumeParameter(1, "input", "the label volume to import"); ret->addStringParameter(2, "label-list-file", "text file containing the values and names for labels"); ret->addVolumeOutputParameter(3, "output", "the output workbench label volume"); ret->createOptionalParameter(4, "-discard-others", "set any voxels with values not mentioned in the label list to the ??? label"); OptionalParameter* unlabeledOption = ret->createOptionalParameter(5, "-unlabeled-value", "set the value that will be interpreted as unlabeled"); unlabeledOption->addIntegerParameter(1, "value", "the numeric value for unlabeled (default 0)"); OptionalParameter* subvolumeSelect = ret->createOptionalParameter(6, "-subvolume", "select a single subvolume to import"); subvolumeSelect->addStringParameter(1, "subvol", "the subvolume number or name"); ret->createOptionalParameter(7, "-drop-unused-labels", "remove any unused label values from the label table"); ret->setHelpText( AString("Creates a new volume with label information in the header in the caret nifti extension format. ") + "You may specify the empty string ('' will work on linux/mac) for , which will be treated as if it is an empty file. " + "The label list file must have lines of the following format:\n\n" + "\n \n\n" + "Do not specify the \"unlabeled\" key in the file, it is assumed that 0 means not labeled unless -unlabeled-value is specified. " + "Label names must be on a separate line, but may contain spaces or other unusual characters (but not newline). " + "Whitespace is trimmed from both ends of the label name, but is kept if it is in the middle of a label. " + "The values of red, green, blue and alpha must be integers from 0 to 255, and will specify the color the label is drawn as " + "(alpha of 255 means opaque, which is probably what you want). " + "By default, it will set new label names with names of LABEL_# for any values encountered that are not mentioned in the " + "list file, specify -discard-others to instead set these voxels to the \"unlabeled\" key." ); return ret; } void OperationVolumeLabelImport::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { AString temp; LevelProgress myProgress(myProgObj); VolumeFile* myVol = myParams->getVolume(1); AString listfileName = myParams->getString(2); VolumeFile* outVol = myParams->getOutputVolume(3); bool discardOthers = false; OptionalParameter* discardOption = myParams->getOptionalParameter(4); if (discardOption->m_present) { discardOthers = true; } int32_t unlabeledValue = 0; OptionalParameter* unlabeledOption = myParams->getOptionalParameter(5); if (unlabeledOption->m_present) { unlabeledValue = (int32_t)unlabeledOption->getInteger(1); } OptionalParameter* subvolumeSelect = myParams->getOptionalParameter(6); int subvol = -1; if (subvolumeSelect->m_present) {//set up to use the single column subvol = (int)myVol->getMapIndexFromNameOrNumber(subvolumeSelect->getString(1)); if (subvol < 0 || subvol >= myVol->getNumberOfMaps()) { throw OperationException("invalid column specified"); } } bool dropUnused = myParams->getOptionalParameter(7)->m_present; GiftiLabelTable myTable; map translate; if (listfileName != "") { FileInformation textFileInfo(listfileName); if (!textFileInfo.exists()) { throw OperationException("label list file doesn't exist"); } fstream labelListFile(listfileName.toLocal8Bit().constData(), fstream::in); if (!labelListFile.good()) { throw OperationException("error reading label list file"); } string labelName; int32_t value, red, green, blue, alpha; int labelCount = 0; translate[unlabeledValue] = 0;//placeholder, we don't know the correct translated value yet while (labelListFile.good()) { ++labelCount;//just for error messages, so start at 1 getline(labelListFile, labelName); labelListFile >> value; if (labelListFile.eof() && labelName == "") break;//if end of file trying to read an int, and label name is empty, its really just end of file labelListFile >> red; labelListFile >> green; labelListFile >> blue; if (!(labelListFile >> alpha))//yes, that is seriously the correct way to check if input was successfully extracted...so much fail { throw OperationException("label list file is malformed for entry #" + AString::number(labelCount) + ": " + AString(labelName.c_str())); } if (red < 0 || red > 255) { throw OperationException("bad value for red for entry #" + AString::number(labelCount) + ", " + AString(labelName.c_str()) + ": " + AString::number(red)); } if (green < 0 || green > 255) { throw OperationException("bad value for green for entry #" + AString::number(labelCount) + ", " + AString(labelName.c_str()) + ": " + AString::number(green)); } if (blue < 0 || blue > 255) { throw OperationException("bad value for blue for entry #" + AString::number(labelCount) + ", " + AString(labelName.c_str()) + ": " + AString::number(blue)); } if (alpha < 0 || alpha > 255) { throw OperationException("bad value for alpha for entry #" + AString::number(labelCount) + ", " + AString(labelName.c_str()) + ": " + AString::number(alpha)); } if (value == GiftiLabel::getInvalidLabelKey()) { throw OperationException("entry #" + AString::number(labelCount) + ", " + AString(labelName.c_str()) + " specifies unusable key value: " + value); } while (isspace(labelListFile.peek())) { labelListFile.ignore();//drop the newline, possible carriage return or other whitespace so that getline doesn't get nothing, and cause int extraction to fail } temp = AString(labelName.c_str()).trimmed();//drop errant CR or other whitespace from beginning and end of lines if (translate.find(value) != translate.end()) { if (value == unlabeledValue) { throw OperationException("the unlabeled value must not be specified in label list file"); } else { throw OperationException(AString("label key ") + AString::number(value) + " specified more than once"); } } GiftiLabel myLabel(value, temp, red, green, blue, alpha); if (myTable.getLabelKeyFromName(temp) != GiftiLabel::getInvalidLabelKey()) { AString nameBase = temp, newName;//resolve collision by generating a name with an additional number on it bool success = false; for (int extra = 1; extra < 100; ++extra)//but stop at 100, because really... { newName = nameBase + "_" + AString::number(extra); if (myTable.getLabelKeyFromName(newName) == GiftiLabel::getInvalidLabelKey()) { success = true; break; } } if (success) { CaretLogWarning("name collision in input name '" + nameBase + "', changing one to '" + newName + "'"); } else { throw OperationException("giving up on resolving name collision for input name '" + nameBase + "'"); } myLabel.setName(newName); } int32_t newValue; if (value == 0)//because label 0 exists in the default constructed table { myTable.insertLabel(&myLabel);//but we do want to be able to overwrite the default 0 label newValue = 0;//if value 0 is specified twice, or once without specifying a different unlabeled value, the check versus the translate map will catch it } else { newValue = myTable.addLabel(&myLabel);//we don't want to overwrite relocated labels } translate[value] = newValue; } } vector myDims; myVol->getDimensions(myDims); const int64_t FRAMESIZE = myDims[0] * myDims[1] * myDims[2]; CaretArray frameOut(FRAMESIZE); int32_t unusedLabel = myTable.getUnassignedLabelKey(); translate[unlabeledValue] = unusedLabel; if (subvol == -1) { outVol->reinitialize(myVol->getOriginalDimensions(), myVol->getSform(), myDims[4], SubvolumeAttributes::LABEL); for (int s = 0; s < myDims[3]; ++s) { set usedValues;//track used values if we have dropUnused for (int c = 0; c < myDims[4]; ++c)//hopefully noone wants a multi-component label volume, that would be silly, but do it anyway { const float* frameIn = myVol->getFrame(s, c);//TODO: rework this when support is added for VolumeFile to handle non-float data for (int i = 0; i < FRAMESIZE; ++i) { int32_t labelval = (int32_t)floor(frameIn[i] + 0.5f);//just in case it somehow got poorly encoded, round to nearest if (dropUnused) { usedValues.insert(labelval); } map::iterator myiter = translate.find(labelval); if (myiter == translate.end()) { if (discardOthers) { frameOut[i] = unusedLabel; } else {//use a random color, but fully opaque for the label GiftiLabel myLabel(labelval, AString("LABEL_") + AString::number(labelval), rand() & 255, rand() & 255, rand() & 255, 255); if (myTable.getLabelKeyFromName(myLabel.getName()) != GiftiLabel::getInvalidLabelKey()) { AString nameBase = myLabel.getName(), newName;//resolve collision by generating a name with an additional number on it bool success = false; for (int extra = 1; extra < 100; ++extra)//but stop at 100, because really... { newName = nameBase + "_" + AString::number(extra); if (myTable.getLabelKeyFromName(newName) == GiftiLabel::getInvalidLabelKey()) { success = true; break; } } if (success) { CaretLogWarning("name collision in auto-generated name '" + nameBase + "', changed to '" + newName + "'"); } else { throw OperationException("giving up on resolving name collision for auto-generated name '" + nameBase + "'"); } myLabel.setName(newName); } int32_t newValue = myTable.addLabel(&myLabel);//don't overwrite any values in the table translate[labelval] = newValue; frameOut[i] = newValue; } } else { frameOut[i] = myiter->second; } } outVol->setFrame(frameOut, s, c); } if (dropUnused) { GiftiLabelTable frameTable = myTable; frameTable.deleteUnusedLabels(usedValues); *(outVol->getMapLabelTable(s)) = frameTable; } else { *(outVol->getMapLabelTable(s)) = myTable;//set the label table AFTER doing the frame, because we may make new labels while scanning } } } else { vector newDims = myDims; newDims.resize(3);//spatial only outVol->reinitialize(newDims, myVol->getSform(), myDims[4], SubvolumeAttributes::LABEL); set usedValues;//track used values if we have dropUnused for (int c = 0; c < myDims[4]; ++c)//hopefully noone wants a multi-component label volume, that would be silly, but do it anyway { const float* frameIn = myVol->getFrame(subvol, c);//TODO: rework this when support is added for VolumeFile to handle non-float data for (int i = 0; i < FRAMESIZE; ++i) { int32_t labelval = (int32_t)floor(frameIn[i] + 0.5f);//just in case it somehow got poorly encoded, round to nearest if (dropUnused) { usedValues.insert(labelval); } map::iterator myiter = translate.find(labelval); if (myiter == translate.end()) { if (discardOthers) { frameOut[i] = unusedLabel; } else {//use a random color, but fully opaque for the label GiftiLabel myLabel(labelval, AString("LABEL_") + AString::number(labelval), rand() & 255, rand() & 255, rand() & 255, 255); if (myTable.getLabelKeyFromName(myLabel.getName()) != GiftiLabel::getInvalidLabelKey()) { AString nameBase = myLabel.getName(), newName;//resolve collision by generating a name with an additional number on it bool success = false; for (int extra = 1; extra < 100; ++extra)//but stop at 100, because really... { newName = nameBase + "_" + AString::number(extra); if (myTable.getLabelKeyFromName(newName) == GiftiLabel::getInvalidLabelKey()) { success = true; break; } } if (success) { CaretLogWarning("name collision in auto-generated name '" + nameBase + "', changed to '" + newName + "'"); } else { throw OperationException("giving up on resolving name collision for auto-generated name '" + nameBase + "'"); } myLabel.setName(newName); } int32_t newValue = myTable.addLabel(&myLabel);//don't overwrite any values in the table translate[labelval] = newValue; frameOut[i] = newValue; } } else { frameOut[i] = myiter->second; } } outVol->setFrame(frameOut, 0, c); } if (dropUnused) { GiftiLabelTable frameTable = myTable; frameTable.deleteUnusedLabels(usedValues); *(outVol->getMapLabelTable(0)) = frameTable; } else { *(outVol->getMapLabelTable(0)) = myTable;//set the label table AFTER doing the frame, because we may make new labels while scanning } } } workbench-1.1.1/src/Operations/OperationVolumeLabelImport.h000066400000000000000000000026521255417355300240610ustar00rootroot00000000000000#ifndef __OPERATION_VOLUME_LABEL_IMPORT_H__ #define __OPERATION_VOLUME_LABEL_IMPORT_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationVolumeLabelImport : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationVolumeLabelImport; } #endif //__OPERATION_VOLUME_LABEL_IMPORT_H__ workbench-1.1.1/src/Operations/OperationVolumeMath.cxx000066400000000000000000000236711255417355300231170ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationVolumeMath.h" #include "OperationException.h" #include "CaretAssert.h" #include "CaretLogger.h" #include "CaretMathExpression.h" #include "VolumeFile.h" #include using namespace caret; using namespace std; AString OperationVolumeMath::getCommandSwitch() { return "-volume-math"; } AString OperationVolumeMath::getShortDescription() { return "EVALUATE EXPRESSION ON VOLUME FILES"; } OperationParameters* OperationVolumeMath::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addStringParameter(1, "expression", "the expression to evaluate, in quotes"); ret->addVolumeOutputParameter(2, "volume-out", "the output volume"); ParameterComponent* varOpt = ret->createRepeatableParameter(3, "-var", "a volume file to use as a variable"); varOpt->addStringParameter(1, "name", "the name of the variable, as used in the expression"); varOpt->addVolumeParameter(2, "volume", "the volume file to use as this variable"); OptionalParameter* subvolSelect = varOpt->createOptionalParameter(3, "-subvolume", "select a single subvolume"); subvolSelect->addStringParameter(1, "subvol", "the subvolume number or name"); varOpt->createOptionalParameter(4, "-repeat", "reuse a single subvolume for each subvolume of calculation"); OptionalParameter* fixNanOpt = ret->createOptionalParameter(4, "-fixnan", "replace NaN results with a value"); fixNanOpt->addDoubleParameter(1, "replace", "value to replace NaN with"); AString myText = AString("This command evaluates at each voxel independently. ") + "There must be at least one -var option (to get the volume space from), even if the specified in it isn't used in . " + "All volumes must have the same volume space. " + "Filenames are not valid in , use a variable name and a -var option with matching to specify an input file. " + "If the -subvolume option is given to any -var option, only one subvolume is used from that file. " + "If -repeat is specified, the file must either have only one subvolume, or have the -subvolume option specified. " + "All files that don't use -repeat must have the same number of subvolumes requested to be used. " + "The format of is as follows:\n\n"; myText += CaretMathExpression::getExpressionHelpInfo(); ret->setHelpText(myText); return ret; } void OperationVolumeMath::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); AString expression = myParams->getString(1); CaretMathExpression myExpr(expression); cout << "parsed '" + expression + "' as '" + myExpr.toString() + "'" << endl; vector myVarNames = myExpr.getVarNames(); VolumeFile* myVolOut = myParams->getOutputVolume(2); const vector& myVarOpts = *(myParams->getRepeatableParameterInstances(3)); OptionalParameter* fixNanOpt = myParams->getOptionalParameter(4); bool nanfix = false; float nanfixval = 0; if (fixNanOpt->m_present) { nanfix = true; nanfixval = (float)fixNanOpt->getDouble(1); } int numInputs = myVarOpts.size(); int numVars = myVarNames.size(); vector varVolumes(numVars, (VolumeFile*)NULL); vector varSubvolumes(numVars, -1); if (numInputs == 0 && numVars == 0) throw OperationException("you must specify at least one input volume (-var), even if the expression doesn't use a variable"); VolumeFile* first; VolumeSpace mySpace; vector outDims; int numSubvols = -1; for (int i = 0; i < numInputs; ++i) { if (i == 0) { first = myVarOpts[0]->getVolume(2); mySpace = first->getVolumeSpace(); } AString varName = myVarOpts[i]->getString(1); double constVal; if (CaretMathExpression::getNamedConstant(varName, constVal)) { throw OperationException("'" + varName + "' is a named constant equal to " + AString::number(constVal, 'g', 15) + ", please use a different variable name"); } VolumeFile* thisVolume = myVarOpts[i]->getVolume(2); if (thisVolume->getNumberOfComponents() != 1) { throw OperationException("volume file for variable '" + varName + "' has multiple components, this is not currently supported in -volume-math"); } int thisSubvols = thisVolume->getNumberOfMaps(); OptionalParameter* subvolSelect = myVarOpts[i]->getOptionalParameter(3); int useSubvolume = -1; if (subvolSelect->m_present) { thisSubvols = 1; useSubvolume = thisVolume->getMapIndexFromNameOrNumber(subvolSelect->getString(1)); if (useSubvolume == -1) throw OperationException("could not find map '" + subvolSelect->getString(1) + "' in volume file for '" + varName + "'"); } bool repeat = myVarOpts[i]->getOptionalParameter(4)->m_present; if (!thisVolume->matchesVolumeSpace(mySpace)) { throw OperationException("volume file for variable '" + varName + "' has different volume space than the first volume file"); } if (repeat) { if (thisSubvols != 1) { throw OperationException("-repeat specified without -subvolume for variable '" + varName + "', but volume file has " + AString::number(thisSubvols) + " subvolumes"); } if (useSubvolume == -1) useSubvolume = 0;//-1 means use same input subvolume as current output subvolume, so we need to fix the special case of -repeat on single subvolume file without -subvolume } else { if (numSubvols == -1)//then this is the first one that doesn't use -repeat { numSubvols = thisSubvols; outDims = thisVolume->getOriginalDimensions(); if (useSubvolume != -1) { outDims.resize(3);//change to output only one subvolume in the simplest way } } else { if (numSubvols != thisSubvols) { if (useSubvolume == -1) { throw OperationException("volume file for variable '" + varName + "' has " + AString::number(thisSubvols) + " subvolume(s), but previous volume files have " + AString::number(numSubvols) + " subvolume(s) requested to be used"); } else { throw OperationException("-subvolume specified without -repeat for variable '" + varName + "', but previous volume files have have " + AString::number(numSubvols) + " subvolumes requested to be used"); } } } } bool found = false; for (int j = 0; j < numVars; ++j) { if (varName == myVarNames[j]) { if (varVolumes[j] != NULL) throw OperationException("variable '" + varName + "' specified more than once"); varVolumes[j] = thisVolume; varSubvolumes[j] = useSubvolume; found = true; break; } } if (!found && (numVars != 0 || numInputs != 1))//supress warning when a single -var is used with a constant expression, as required per help { CaretLogWarning("variable '" + varName + "' not used in expression"); } } for (int i = 0; i < numVars; ++i) { if (varVolumes[i] == NULL) throw OperationException("no -var option specified for variable '" + myVarNames[i] + "'"); } if (numSubvols == -1) { throw OperationException("all -var options used -repeat, there is no file to get number of desired output subvolumes from"); } int64_t frameSize = outDims[0] * outDims[1] * outDims[2]; vector values(numVars), outFrame(frameSize); vector inputFrames(numVars); myVolOut->reinitialize(outDims, first->getSform());//DO NOT take volume type from first volume, because we don't check for or copy label tables, nor do we want to for (int s = 0; s < numSubvols; ++s) { for (int v = 0; v < numVars; ++v) { if (varSubvolumes[v] == -1) { inputFrames[v] = varVolumes[v]->getFrame(s); } else { inputFrames[v] = varVolumes[v]->getFrame(varSubvolumes[v]); } } for (int64_t i = 0; i < frameSize; ++i) { for (int v = 0; v < numVars; ++v) { values[v] = inputFrames[v][i]; } float tempf = (float)myExpr.evaluate(values); if (nanfix && tempf != tempf) { tempf = nanfixval; } outFrame[i] = tempf; } myVolOut->setFrame(outFrame.data(), s); } } workbench-1.1.1/src/Operations/OperationVolumeMath.h000066400000000000000000000025751255417355300225440ustar00rootroot00000000000000#ifndef __OPERATION_VOLUME_MATH_H__ #define __OPERATION_VOLUME_MATH_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationVolumeMath : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationVolumeMath; } #endif //__OPERATION_VOLUME_MATH_H__ workbench-1.1.1/src/Operations/OperationVolumeMerge.cxx000066400000000000000000000233511255417355300232600ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationVolumeMerge.h" #include "OperationException.h" #include "CaretAssert.h" #include "GiftiLabelTable.h" #include "PaletteColorMapping.h" #include "VolumeFile.h" using namespace caret; using namespace std; AString OperationVolumeMerge::getCommandSwitch() { return "-volume-merge"; } AString OperationVolumeMerge::getShortDescription() { return "MERGE VOLUME FILES INTO A NEW FILE"; } OperationParameters* OperationVolumeMerge::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addVolumeOutputParameter(1, "volume-out", "the output volume file"); ParameterComponent* volumeOpt = ret->createRepeatableParameter(2, "-volume", "specify an input volume file"); volumeOpt->addVolumeParameter(1, "volume-in", "a volume file to use subvolumes from"); ParameterComponent* subvolOpt = volumeOpt->createRepeatableParameter(2, "-subvolume", "select a single subvolume to use"); subvolOpt->addStringParameter(1, "subvol", "the subvolume number or name"); OptionalParameter* upToOpt = subvolOpt->createOptionalParameter(2, "-up-to", "use an inclusive range of subvolumes"); upToOpt->addStringParameter(1, "last-subvol", "the number or name of the last subvolume to include"); upToOpt->createOptionalParameter(2, "-reverse", "use the range in reverse order"); ret->setHelpText( AString("Takes one or more volume files and constructs a new volume file by concatenating subvolumes from them. ") + "The input volume files must have the same volume space.\n\n" + "Example: wb_command -volume-merge out.nii -volume first.nii -subvolume 1 -volume second.nii\n\n" + "This example would take the first subvolume from first.nii, followed by all subvolumes from second.nii, " + "and write these to out.nii." ); return ret; } void OperationVolumeMerge::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); VolumeFile* volumeOut = myParams->getOutputVolume(1); const vector& myInputs = *(myParams->getRepeatableParameterInstances(2)); int numInputs = (int)myInputs.size(); if (numInputs < 1) throw OperationException("no inputs specified"); int64_t subvolCount = 0; const VolumeFile* firstVol = myInputs[0]->getVolume(1); vector firstDims = firstVol->getDimensions(); bool isLabel = (firstVol->getType() == SubvolumeAttributes::LABEL); for (int i = 0; i < numInputs; ++i) { const VolumeFile* myVol = myInputs[i]->getVolume(1); if (!myVol->matchesVolumeSpace(firstVol)) { throw OperationException("volume file '" + myVol->getFileName() + "' has a different volume space"); } if (isLabel != (myVol->getType() == SubvolumeAttributes::LABEL)) { throw OperationException("only some volumes are label volumes, first mismatch is '" + myVol->getFileName() + "'"); } vector thisDims = myVol->getDimensions(); if (thisDims[4] != firstDims[4]) throw ("volume file '" + myVol->getFileName() + "' has a different number of components"); const vector& subvolOpts = *(myInputs[i]->getRepeatableParameterInstances(2)); int numSubvolOpts = (int)subvolOpts.size(); if (numSubvolOpts > 0) { for (int j = 0; j < numSubvolOpts; ++j) { int64_t initialFrame = myVol->getMapIndexFromNameOrNumber(subvolOpts[j]->getString(1)); if (initialFrame < 0) throw OperationException("subvolume '" + subvolOpts[j]->getString(1) + "' not found in file '" + myVol->getFileName() + "'"); OptionalParameter* upToOpt = subvolOpts[j]->getOptionalParameter(2); if (upToOpt->m_present) { int64_t finalFrame = myVol->getMapIndexFromNameOrNumber(upToOpt->getString(1)); if (finalFrame < 0) throw OperationException("ending subvolume '" + upToOpt->getString(1) + "' not found in file '" + myVol->getFileName() + "'"); if (finalFrame < initialFrame) throw OperationException("ending subvolume '" + upToOpt->getString(1) + "' occurs before starting subvolume '" + subvolOpts[j]->getString(1) + "' in file '" + myVol->getFileName() + "'"); subvolCount += finalFrame - initialFrame + 1;//inclusive - we don't need to worry about reversing for counting, though } else { subvolCount += 1; } } } else { subvolCount += myVol->getNumberOfMaps(); } } vector outDims = firstVol->getOriginalDimensions(); outDims.resize(4); outDims[3] = subvolCount; volumeOut->reinitialize(outDims, firstVol->getSform(), firstDims[4], firstVol->getType()); int64_t curOutVol = 0; for (int i = 0; i < numInputs; ++i) { const VolumeFile* myVol = myInputs[i]->getVolume(1); const vector& subvolOpts = *(myInputs[i]->getRepeatableParameterInstances(2)); int numSubvolOpts = (int)subvolOpts.size(); if (numSubvolOpts > 0) { for (int j = 0; j < numSubvolOpts; ++j) { int64_t initialFrame = myVol->getMapIndexFromNameOrNumber(subvolOpts[j]->getString(1)); OptionalParameter* upToOpt = subvolOpts[j]->getOptionalParameter(2); if (upToOpt->m_present) { int64_t finalFrame = myVol->getMapIndexFromNameOrNumber(upToOpt->getString(1)); bool reverse = upToOpt->getOptionalParameter(2)->m_present; if (reverse) { for (int64_t b = finalFrame; b >= initialFrame; --b) { for (int64_t c = 0; c < firstDims[4]; ++c) { volumeOut->setFrame(myVol->getFrame(b, c), curOutVol, c); } volumeOut->setMapName(curOutVol, myVol->getMapName(b)); if (isLabel) { *(volumeOut->getMapLabelTable(curOutVol)) = *(myVol->getMapLabelTable(b)); } else { *(volumeOut->getMapPaletteColorMapping(curOutVol)) = *(myVol->getMapPaletteColorMapping(b)); } ++curOutVol; } } else { for (int64_t b = initialFrame; b <= finalFrame; ++b) { for (int64_t c = 0; c < firstDims[4]; ++c) { volumeOut->setFrame(myVol->getFrame(b, c), curOutVol, c); } volumeOut->setMapName(curOutVol, myVol->getMapName(b)); if (isLabel) { *(volumeOut->getMapLabelTable(curOutVol)) = *(myVol->getMapLabelTable(b)); } else { *(volumeOut->getMapPaletteColorMapping(curOutVol)) = *(myVol->getMapPaletteColorMapping(b)); } ++curOutVol; } } } else { for (int64_t c = 0; c < firstDims[4]; ++c) { volumeOut->setFrame(myVol->getFrame(initialFrame, c), curOutVol, c); } volumeOut->setMapName(curOutVol, myVol->getMapName(initialFrame)); if (isLabel) { *(volumeOut->getMapLabelTable(curOutVol)) = *(myVol->getMapLabelTable(initialFrame)); } else { *(volumeOut->getMapPaletteColorMapping(curOutVol)) = *(myVol->getMapPaletteColorMapping(initialFrame)); } ++curOutVol; } } } else { vector myDims = myVol->getDimensions(); for (int64_t b = 0; b < myDims[3]; ++b) { for (int64_t c = 0; c < firstDims[4]; ++c) { volumeOut->setFrame(myVol->getFrame(b, c), curOutVol, c); } volumeOut->setMapName(curOutVol, myVol->getMapName(b)); if (isLabel) { *(volumeOut->getMapLabelTable(curOutVol)) = *(myVol->getMapLabelTable(b)); } else { *(volumeOut->getMapPaletteColorMapping(curOutVol)) = *(myVol->getMapPaletteColorMapping(b)); } ++curOutVol; } } } CaretAssert(curOutVol == subvolCount); } workbench-1.1.1/src/Operations/OperationVolumeMerge.h000066400000000000000000000026031255417355300227020ustar00rootroot00000000000000#ifndef __OPERATION_VOLUME_MERGE_H__ #define __OPERATION_VOLUME_MERGE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationVolumeMerge : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationVolumeMerge; } #endif //__OPERATION_VOLUME_MERGE_H__ workbench-1.1.1/src/Operations/OperationVolumePalette.cxx000066400000000000000000000244771255417355300236310ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationVolumePalette.h" #include "OperationException.h" #include "Palette.h" #include "PaletteColorMapping.h" #include "PaletteFile.h" #include "VolumeFile.h" using namespace caret; using namespace std; AString OperationVolumePalette::getCommandSwitch() { return "-volume-palette"; } AString OperationVolumePalette::getShortDescription() { return "SET THE PALETTE OF A VOLUME FILE"; } OperationParameters* OperationVolumePalette::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addStringParameter(1, "volume", "the volume file to modify"); ret->addStringParameter(2, "mode", "the mapping mode"); OptionalParameter* subvolumeSelect = ret->createOptionalParameter(3, "-subvolume", "select a single subvolume"); subvolumeSelect->addStringParameter(1, "subvolume", "the subvolume number or name"); OptionalParameter* posMinMaxPercent = ret->createOptionalParameter(4, "-pos-percent", "percentage min/max for positive data coloring"); posMinMaxPercent->addDoubleParameter(1, "pos-min-%", "the percentile for the least positive data"); posMinMaxPercent->addDoubleParameter(2, "pos-max-%", "the percentile for the most positive data"); OptionalParameter* negMinMaxPercent = ret->createOptionalParameter(5, "-neg-percent", "percentage min/max for negative data coloring"); negMinMaxPercent->addDoubleParameter(1, "neg-min-%", "the percentile for the least negative data"); negMinMaxPercent->addDoubleParameter(2, "neg-max-%", "the percentile for the most negative data"); OptionalParameter* posMinMaxValue = ret->createOptionalParameter(11, "-pos-user", "user min/max values for positive data coloring"); posMinMaxValue->addDoubleParameter(1, "pos-min-user", "the value for the least positive data"); posMinMaxValue->addDoubleParameter(2, "pos-max-user", "the value for the most positive data"); OptionalParameter* negMinMaxValue = ret->createOptionalParameter(12, "-neg-user", "user min/max values for negative data coloring"); negMinMaxValue->addDoubleParameter(1, "neg-min-user", "the value for the least negative data"); negMinMaxValue->addDoubleParameter(2, "neg-max-user", "the value for the most negative data"); OptionalParameter* interpolate = ret->createOptionalParameter(9, "-interpolate", "interpolate colors"); interpolate->addBooleanParameter(1, "interpolate", "boolean, whether to interpolate"); OptionalParameter* displayPositive = ret->createOptionalParameter(6, "-disp-pos", "display positive data"); displayPositive->addBooleanParameter(1, "display", "boolean, whether to display"); OptionalParameter* displayNegative = ret->createOptionalParameter(7, "-disp-neg", "display positive data"); displayNegative->addBooleanParameter(1, "display", "boolean, whether to display"); OptionalParameter* displayZero = ret->createOptionalParameter(8, "-disp-zero", "display data closer to zero than the min cutoff"); displayZero->addBooleanParameter(1, "display", "boolean, whether to display"); OptionalParameter* paletteName = ret->createOptionalParameter(10, "-palette-name", "set the palette used"); paletteName->addStringParameter(1, "name", "the name of the palette"); OptionalParameter* thresholdOpt = ret->createOptionalParameter(13, "-thresholding", "set the thresholding"); thresholdOpt->addStringParameter(1, "type", "thresholding setting"); thresholdOpt->addStringParameter(2, "test", "show values inside or outside thresholds"); thresholdOpt->addDoubleParameter(3, "min", "lower threshold"); thresholdOpt->addDoubleParameter(4, "max", "upper threshold"); AString myText = AString("The original volume file is overwritten with the modified version. By default, all columns of the volume file are adjusted ") + "to the new settings, use the -subvolume option to change only one subvolume. Mapping settings not specified in options will be taken from the first subvolume. " + "The argument must be one of the following:\n\n"; vector myEnums; PaletteScaleModeEnum::getAllEnums(myEnums); for (int i = 0; i < (int)myEnums.size(); ++i) { myText += PaletteScaleModeEnum::toName(myEnums[i]) + "\n"; } myText += "\nThe argument to -palette-name must be one of the following:\n\n"; PaletteFile myPF; int32_t numPalettes = myPF.getNumberOfPalettes(); for (int i = 0; i < numPalettes; ++i) { myText += myPF.getPalette(i)->getName() + "\n"; } myText += "\nThe argument to -thresholding must be one of the following:\n\n"; vector myEnums2; PaletteThresholdTypeEnum::getAllEnums(myEnums2); for (int i = 0; i < (int)myEnums2.size(); ++i) { myText += PaletteThresholdTypeEnum::toName(myEnums2[i]) + "\n"; } myText += "\nThe argument to -thresholding must be one of the following:\n\n"; vector myEnums3; PaletteThresholdTestEnum::getAllEnums(myEnums3); for (int i = 0; i < (int)myEnums3.size(); ++i) { myText += PaletteThresholdTestEnum::toName(myEnums3[i]) + "\n"; } ret->setHelpText(myText); return ret; } void OperationVolumePalette::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); AString myVolumeName = myParams->getString(1); AString myModeName = myParams->getString(2); bool ok = false; PaletteScaleModeEnum::Enum myMode = PaletteScaleModeEnum::fromName(myModeName, &ok); if (!ok) { throw OperationException("unknown mapping mode"); } VolumeFile myVolume; myVolume.readFile(myVolumeName); int mySubvolume = -1; OptionalParameter* subvolumeSelect = myParams->getOptionalParameter(3); if (subvolumeSelect->m_present) { mySubvolume = (int)myVolume.getMapIndexFromNameOrNumber(subvolumeSelect->getString(1)); if (mySubvolume < 0 || mySubvolume >= myVolume.getNumberOfMaps()) { throw OperationException("invalid column specified"); } } PaletteColorMapping myMapping = *(myVolume.getMapPaletteColorMapping(0));//create the mapping, then use operator= to set for all requested columns, take defaults from first map myMapping.setScaleMode(myMode); OptionalParameter* posMinMaxPercent = myParams->getOptionalParameter(4); if (posMinMaxPercent->m_present) { myMapping.setAutoScalePercentagePositiveMinimum(posMinMaxPercent->getDouble(1)); myMapping.setAutoScalePercentagePositiveMaximum(posMinMaxPercent->getDouble(2)); } OptionalParameter* negMinMaxPercent = myParams->getOptionalParameter(5); if (negMinMaxPercent->m_present) { myMapping.setAutoScalePercentageNegativeMinimum(negMinMaxPercent->getDouble(1)); myMapping.setAutoScalePercentageNegativeMaximum(negMinMaxPercent->getDouble(2)); } OptionalParameter* posMinMaxValue = myParams->getOptionalParameter(11); if (posMinMaxValue->m_present) { myMapping.setUserScalePositiveMinimum(posMinMaxValue->getDouble(1)); myMapping.setUserScalePositiveMaximum(posMinMaxValue->getDouble(2)); } OptionalParameter* negMinMaxValue = myParams->getOptionalParameter(12); if (negMinMaxValue->m_present) { myMapping.setUserScaleNegativeMinimum(negMinMaxValue->getDouble(1)); myMapping.setUserScaleNegativeMaximum(negMinMaxValue->getDouble(2)); } OptionalParameter* displayPositive = myParams->getOptionalParameter(6); if (displayPositive->m_present) { myMapping.setDisplayPositiveDataFlag(displayPositive->getBoolean(1)); } OptionalParameter* displayNegative = myParams->getOptionalParameter(7); if (displayNegative->m_present) { myMapping.setDisplayNegativeDataFlag(displayNegative->getBoolean(1)); } OptionalParameter* displayZero = myParams->getOptionalParameter(8); if (displayZero->m_present) { myMapping.setDisplayZeroDataFlag(displayZero->getBoolean(1)); } OptionalParameter* interpolate = myParams->getOptionalParameter(9); if (interpolate->m_present) { myMapping.setInterpolatePaletteFlag(interpolate->getBoolean(1)); } OptionalParameter* paletteName = myParams->getOptionalParameter(10); if (paletteName->m_present) { myMapping.setSelectedPaletteName(paletteName->getString(1)); } OptionalParameter* thresholdOpt = myParams->getOptionalParameter(13); if (thresholdOpt->m_present) { bool ok = false; PaletteThresholdTypeEnum::Enum mytype = PaletteThresholdTypeEnum::fromName(thresholdOpt->getString(1), &ok); if (!ok) throw OperationException("unrecognized threshold type string: " + thresholdOpt->getString(1)); PaletteThresholdTestEnum::Enum mytest = PaletteThresholdTestEnum::fromName(thresholdOpt->getString(2), &ok); if (!ok) throw OperationException("unrecognized threshold test string: " + thresholdOpt->getString(2)); myMapping.setThresholdType(mytype); myMapping.setThresholdTest(mytest); myMapping.setThresholdMinimum(mytype, thresholdOpt->getDouble(3)); myMapping.setThresholdMaximum(mytype, thresholdOpt->getDouble(4)); } if (mySubvolume == -1) { for (int i = 0; i < myVolume.getNumberOfMaps(); ++i) { *(myVolume.getMapPaletteColorMapping(i)) = myMapping; } } else { *(myVolume.getMapPaletteColorMapping(mySubvolume)) = myMapping; } myVolume.writeFile(myVolumeName); } workbench-1.1.1/src/Operations/OperationVolumePalette.h000066400000000000000000000026171255417355300232460ustar00rootroot00000000000000#ifndef __OPERATION_VOLUME_PALETTE_H__ #define __OPERATION_VOLUME_PALETTE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationVolumePalette : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationVolumePalette; } #endif //__OPERATION_VOLUME_PALETTE_H__ workbench-1.1.1/src/Operations/OperationVolumeReorient.cxx000066400000000000000000000106351255417355300240110ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationVolumeReorient.h" #include "OperationException.h" #include "VolumeFile.h" using namespace caret; using namespace std; AString OperationVolumeReorient::getCommandSwitch() { return "-volume-reorient"; } AString OperationVolumeReorient::getShortDescription() { return "CHANGE VOXEL ORDER OF A VOLUME FILE"; } OperationParameters* OperationVolumeReorient::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addVolumeParameter(1, "volume", "the volume to reorient"); ret->addStringParameter(2, "orient-string", "the desired orientation"); ret->addStringParameter(3, "volume-out", "out - the reoriented volume");//fake the "out" parameter formatting, because copying a volume file in memory is currently a problem ret->setHelpText( AString("Changes the voxel order and the header spacing/origin information such that the value of any spatial point is unchanged. ") + "Orientation strings look like 'LPI', which means first index is left to right, second is posterior to anterior, and third is inferior to superior. " + "The valid characters are:\n\nL left to right\nR right to left\nP posterior to anterior\nA anterior to posterior\nI inferior to superior\nS superior to inferior" ); return ret; } void OperationVolumeReorient::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); VolumeFile* myVol = myParams->getVolume(1); AString orientString = myParams->getString(2); AString outName = myParams->getString(3); if (orientString.length() < 3) { throw OperationException("orient-string must have 3 characters"); } bool used[3] = {false, false, false}; VolumeSpace::OrientTypes orient[3]; for (int i = 0; i < 3; ++i) { char id = orientString[i].toAscii(); switch (id) { case 'L': case 'l': if (used[0]) throw OperationException("X axis (L, R) specified more than once"); used[0] = true; orient[i] = VolumeSpace::LEFT_TO_RIGHT; break; case 'R': case 'r': if (used[0]) throw OperationException("X axis (L, R) specified more than once"); used[0] = true; orient[i] = VolumeSpace::RIGHT_TO_LEFT; break; case 'P': case 'p': if (used[1]) throw OperationException("Y axis (P, A) specified more than once"); used[1] = true; orient[i] = VolumeSpace::POSTERIOR_TO_ANTERIOR; break; case 'A': case 'a': if (used[1]) throw OperationException("Y axis (P, A) specified more than once"); used[1] = true; orient[i] = VolumeSpace::ANTERIOR_TO_POSTERIOR; break; case 'I': case 'i': if (used[2]) throw OperationException("Z axis (I, S) specified more than once"); used[2] = true; orient[i] = VolumeSpace::INFERIOR_TO_SUPERIOR; break; case 'S': case 's': if (used[2]) throw OperationException("Z axis (I, S) specified more than once"); used[2] = true; orient[i] = VolumeSpace::SUPERIOR_TO_INFERIOR; break; default: throw OperationException(AString("unrecognized character '") + id + "'"); } } myVol->reorient(orient); myVol->writeFile(outName); } workbench-1.1.1/src/Operations/OperationVolumeReorient.h000066400000000000000000000026251255417355300234360ustar00rootroot00000000000000#ifndef __OPERATION_VOLUME_REORIENT_H__ #define __OPERATION_VOLUME_REORIENT_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationVolumeReorient : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationVolumeReorient; } #endif //__OPERATION_VOLUME_REORIENT_H__ workbench-1.1.1/src/Operations/OperationVolumeSetSpace.cxx000066400000000000000000000135541255417355300237340ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationVolumeSetSpace.h" #include "OperationException.h" #include "FloatMatrix.h" #include "VolumeFile.h" using namespace caret; using namespace std; AString OperationVolumeSetSpace::getCommandSwitch() { return "-volume-set-space"; } AString OperationVolumeSetSpace::getShortDescription() { return "CHANGE VOLUME SPACE INFORMATION"; } OperationParameters* OperationVolumeSetSpace::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addVolumeParameter(1, "volume-in", "the input volume"); ret->addStringParameter(2, "volume-out", "output - the output volume");//fake the "out" parameter formatting, because copying a volume file in memory is currently a problem OptionalParameter* plumbOpt = ret->createOptionalParameter(3, "-plumb", "set via axis order and spacing/offset"); plumbOpt->addStringParameter(1, "axis-order", "a string like 'XYZ' that specifies which index is along which spatial dimension"); plumbOpt->addDoubleParameter(2, "x-spacing", "change in x-coordinate from incrementing the relevant index"); plumbOpt->addDoubleParameter(3, "y-spacing", "change in y-coordinate from incrementing the relevant index"); plumbOpt->addDoubleParameter(4, "z-spacing", "change in z-coordinate from incrementing the relevant index"); plumbOpt->addDoubleParameter(5, "x-offset", "the x-coordinate of the first voxel"); plumbOpt->addDoubleParameter(6, "y-offset", "the y-coordinate of the first voxel"); plumbOpt->addDoubleParameter(7, "z-offset", "the z-coordinate of the first voxel"); OptionalParameter* sformOpt = ret->createOptionalParameter(4, "-sform", "set via a nifti sform"); char axisNames[] = "xyz", indexNames[] = "ijk"; for (int axis = 0; axis < 3; ++axis) { for (int index = 0; index < 3; ++index) { sformOpt->addDoubleParameter(axis * 4 + index, AString(axisNames[axis]) + indexNames[index] + "-spacing", "increase in " + AString(axisNames[axis]) + " coordinate from incrementing the " + indexNames[index] + " index"); } sformOpt->addDoubleParameter(axis * 4 + 3, AString(axisNames[axis]) + "-offset", AString(axisNames[axis]) + " coordinate of first voxel"); } ret->setHelpText( AString("Writes a copy of the volume file, with the spacing information changed as specified. ") + "No reordering of the voxel data occurs. " + "Exactly one of -plumb or -sform must be specified." ); return ret; } void OperationVolumeSetSpace::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); VolumeFile* orig = myParams->getVolume(1); FloatMatrix newSform = FloatMatrix::zeros(4, 4); newSform[3][3] = 1.0f;//not needed, but just for sanity bool haveSpace = false; OptionalParameter* plumbOpt = myParams->getOptionalParameter(3); if (plumbOpt->m_present) { haveSpace = true; bool used[3] = {false, false, false}; int revorder[3] = {-1, -1, -1}; AString orient = plumbOpt->getString(1); if (orient.size() < 3) throw OperationException(" must have 3 characters"); for (int i = 0; i < 3; ++i) { int dir = -1; switch (orient[i].toAscii()) { case 'X': case 'x': dir = 0; break; case 'Y': case 'y': dir = 1; break; case 'Z': case 'z': dir = 2; break; default: throw OperationException(" must use the characters X, Y, and Z"); } if (used[dir]) throw OperationException(" may not repeat an axis"); used[dir] = true; revorder[dir] = i;//construct the reversed order, because thats what we need } newSform[0][revorder[0]] = (float)plumbOpt->getDouble(2); newSform[0][3] = (float)plumbOpt->getDouble(5); newSform[1][revorder[1]] = (float)plumbOpt->getDouble(3); newSform[1][3] = (float)plumbOpt->getDouble(6); newSform[2][revorder[2]] = (float)plumbOpt->getDouble(4); newSform[2][3] = (float)plumbOpt->getDouble(7); } OptionalParameter* sformOpt = myParams->getOptionalParameter(4); if (sformOpt->m_present) { if (haveSpace) throw OperationException("only one of -plumb and -sform may be specified"); haveSpace = true; for (int axis = 0; axis < 3; ++axis) { for (int index = 0; index < 3; ++index) { newSform[axis][index] = (float)sformOpt->getDouble(axis * 4 + index); } newSform[axis][3] = (float)sformOpt->getDouble(axis * 4 + 3); } } if (!haveSpace) throw OperationException("you must specify -plumb or -sform"); orig->setVolumeSpace(newSform.getMatrix()); orig->writeFile(myParams->getString(2)); } workbench-1.1.1/src/Operations/OperationVolumeSetSpace.h000066400000000000000000000026301255417355300233520ustar00rootroot00000000000000#ifndef __OPERATION_VOLUME_SET_SPACE_H__ #define __OPERATION_VOLUME_SET_SPACE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationVolumeSetSpace : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationVolumeSetSpace; } #endif //__OPERATION_VOLUME_SET_SPACE_H__ workbench-1.1.1/src/Operations/OperationVolumeStats.cxx000066400000000000000000000227201255417355300233160ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationVolumeStats.h" #include "OperationException.h" #include "ReductionOperation.h" #include "VolumeFile.h" #include #include #include #include #include #include using namespace caret; using namespace std; AString OperationVolumeStats::getCommandSwitch() { return "-volume-stats"; } AString OperationVolumeStats::getShortDescription() { return "SPATIAL STATISTICS ON A VOLUME FILE"; } OperationParameters* OperationVolumeStats::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addVolumeParameter(1, "volume-in", "the input volume"); OptionalParameter* reduceOpt = ret->createOptionalParameter(2, "-reduce", "use a reduction operation"); reduceOpt->addStringParameter(1, "operation", "the reduction operation"); OptionalParameter* percentileOpt = ret->createOptionalParameter(3, "-percentile", "give the value at a percentile"); percentileOpt->addDoubleParameter(1, "percent", "the percentile to find"); OptionalParameter* subvolOpt = ret->createOptionalParameter(4, "-subvolume", "only display output for one subvolume"); subvolOpt->addStringParameter(1, "subvolume", "the subvolume number or name"); OptionalParameter* roiOpt = ret->createOptionalParameter(5, "-roi", "only consider data inside an roi"); roiOpt->addVolumeParameter(1, "roi-volume", "the roi, as a volume file"); roiOpt->createOptionalParameter(2, "-match-maps", "each subvolume of input uses the corresponding subvolume from the roi file"); ret->createOptionalParameter(6, "-show-map-name", "print map index and name before each output"); ret->setHelpText( AString("For each subvolume of the input, a single number is printed, resulting from the specified reduction or percentile operation. ") + "Use -subvolume to only give output for a single subvolume. " + "Use -roi to consider only the data within a region. " + "Exactly one of -reduce or -percentile must be specified.\n\n" + "The argument to the -reduce option must be one of the following:\n\n" + ReductionOperation::getHelpInfo()); return ret; } namespace { float reduce(const float* data, const int64_t& numElements, const ReductionEnum::Enum& myop, const float* roiData) { if (roiData == NULL) { return ReductionOperation::reduce(data, numElements, myop); } else { vector toUse; toUse.reserve(numElements); for (int64_t i = 0; i < numElements; ++i) { if (roiData[i] > 0.0f) { toUse.push_back(data[i]); } } if (toUse.empty()) throw OperationException("roi contains no voxels"); return ReductionOperation::reduce(toUse.data(), toUse.size(), myop); } } float percentile(const float* data, const int64_t& numElements, const float& percent, const float* roiData) { CaretAssert(percent >= 0.0f && percent <= 100.0f); vector toUse; if (roiData == NULL) { toUse = vector(data, data + numElements); } else { toUse.reserve(numElements); for (int64_t i = 0; i < numElements; ++i) { if (roiData[i] > 0.0f) { toUse.push_back(data[i]); } } } if (toUse.empty()) throw OperationException("roi contains no voxels"); sort(toUse.begin(), toUse.end()); const double index = percent / 100.0f * (toUse.size() - 1); if (index <= 0) return toUse[0]; if (index >= toUse.size() - 1) return toUse.back(); double ipart, fpart; fpart = modf(index, &ipart); return (1.0f - fpart) * toUse[(int64_t)ipart] + fpart * toUse[((int64_t)ipart) + 1]; } } void OperationVolumeStats::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); VolumeFile* input = myParams->getVolume(1); vector dims = input->getDimensions(); const int64_t frameSize = dims[0] * dims[1] * dims[2]; if (input->getNumberOfComponents() != 1) throw OperationException("multi-component volumes are not supported in -volume-stats"); OptionalParameter* reduceOpt = myParams->getOptionalParameter(2); OptionalParameter* percentileOpt = myParams->getOptionalParameter(3); if (reduceOpt->m_present == percentileOpt->m_present)//use == as logical xnor { throw OperationException("you must use exactly one of -reduce or -percentile"); } ReductionEnum::Enum myop = ReductionEnum::INVALID; if (reduceOpt->m_present) { bool ok = false; myop = ReductionEnum::fromName(reduceOpt->getString(1), &ok); if (!ok) throw OperationException("unrecognized reduction operation: " + reduceOpt->getString(1)); } float percent = 0.0f; if (percentileOpt->m_present) { percent = (float)percentileOpt->getDouble(1);//use not within range to trap NaNs, just in case if (!(percent >= 0.0f && percent <= 100.0f)) throw OperationException("percentile must be between 0 and 100"); } int subvol = -1; OptionalParameter* subvolOpt = myParams->getOptionalParameter(4); if (subvolOpt->m_present) { subvol = input->getMapIndexFromNameOrNumber(subvolOpt->getString(1)); if (subvol < 0) throw OperationException("invalid column specified"); } bool matchSubvolMode = false; VolumeFile* myRoi = NULL; const float* roiData = NULL; OptionalParameter* roiOpt = myParams->getOptionalParameter(5); if (roiOpt->m_present) { myRoi = roiOpt->getVolume(1); if (!input->matchesVolumeSpace(myRoi)) throw OperationException("roi doesn't match volume space of input"); if (roiOpt->getOptionalParameter(2)->m_present) { if (myRoi->getDimensions()[3] != dims[3]) { throw OperationException("-match-maps specified, but roi has different number of subvolumes than input"); } matchSubvolMode = true; } else { roiData = myRoi->getFrame(); } } bool showMapName = myParams->getOptionalParameter(6)->m_present; int numMaps = input->getNumberOfMaps(); if (subvol == -1) { if (reduceOpt->m_present) { for (int i = 0; i < numMaps; ++i) {//store result before printing anything, in case it throws while computing if (matchSubvolMode) { roiData = myRoi->getFrame(i); } const float result = reduce(input->getFrame(i), frameSize, myop, roiData); if (showMapName) cout << AString::number(i + 1) << ": " << input->getMapName(i) << ": "; stringstream resultsstr; resultsstr << setprecision(7) << result; cout << resultsstr.str() << endl; } } else { CaretAssert(percentileOpt->m_present); for (int i = 0; i < numMaps; ++i) {//store result before printing anything, in case it throws while computing if (matchSubvolMode) { roiData = myRoi->getFrame(i); } const float result = percentile(input->getFrame(i), frameSize, percent, roiData); if (showMapName) cout << AString::number(i + 1) << ": " << input->getMapName(i) << ": "; stringstream resultsstr; resultsstr << setprecision(7) << result; cout << resultsstr.str() << endl; } } } else { CaretAssert(subvol >= 0 && subvol < numMaps); if (matchSubvolMode) { roiData = myRoi->getFrame(subvol); } if (reduceOpt->m_present) { const float result = reduce(input->getFrame(subvol), frameSize, myop, roiData); if (showMapName) cout << AString::number(subvol + 1) << ": " << input->getMapName(subvol) << ": "; stringstream resultsstr; resultsstr << setprecision(7) << result; cout << resultsstr.str() << endl; } else { CaretAssert(percentileOpt->m_present); const float result = percentile(input->getFrame(subvol), frameSize, percent, roiData); if (showMapName) cout << AString::number(subvol + 1) << ": " << input->getMapName(subvol) << ": "; stringstream resultsstr; resultsstr << setprecision(7) << result; cout << resultsstr.str() << endl; } } } workbench-1.1.1/src/Operations/OperationVolumeStats.h000066400000000000000000000026031255417355300227410ustar00rootroot00000000000000#ifndef __OPERATION_VOLUME_STATS_H__ #define __OPERATION_VOLUME_STATS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationVolumeStats : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationVolumeStats; } #endif //__OPERATION_VOLUME_STATS_H__ workbench-1.1.1/src/Operations/OperationVolumeWeightedStats.cxx000066400000000000000000000377161255417355300250120ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationVolumeWeightedStats.h" #include "OperationException.h" #include "CaretHeap.h" #include "VolumeFile.h" #include #include #include #include #include using namespace caret; using namespace std; AString OperationVolumeWeightedStats::getCommandSwitch() { return "-volume-weighted-stats"; } AString OperationVolumeWeightedStats::getShortDescription() { return "WEIGHTED SPATIAL STATISTICS ON A VOLUME FILE"; } OperationParameters* OperationVolumeWeightedStats::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addVolumeParameter(1, "volume-in", "the input volume"); OptionalParameter* weightVolumeOpt = ret->createOptionalParameter(2, "-weight-volume", "use weights from a volume file"); weightVolumeOpt->addVolumeParameter(1, "weight-volume", "volume file containing the weights"); OptionalParameter* subvolOpt = ret->createOptionalParameter(3, "-subvolume", "only display output for one subvolume"); subvolOpt->addStringParameter(1, "subvolume", "the subvolume number or name"); OptionalParameter* roiOpt = ret->createOptionalParameter(4, "-roi", "only consider data inside an roi"); roiOpt->addVolumeParameter(1, "roi-volume", "the roi, as a volume file"); roiOpt->createOptionalParameter(2, "-match-maps", "each subvolume of input uses the corresponding subvolume from the roi file"); ret->createOptionalParameter(5, "-mean", "compute weighted mean"); OptionalParameter* stdevOpt = ret->createOptionalParameter(6, "-stdev", "compute weighted standard deviation"); stdevOpt->createOptionalParameter(1, "-sample", "estimate population stdev from the sample"); OptionalParameter* percentileOpt = ret->createOptionalParameter(7, "-percentile", "compute weighted percentile"); percentileOpt->addDoubleParameter(1, "percent", "the percentile to find"); ret->createOptionalParameter(8, "-sum", "compute weighted sum"); ret->createOptionalParameter(9, "-show-map-name", "print map index and name before each output"); ret->setHelpText( AString("For each subvolume of the input, a single number is printed, resulting from the specified operation. ") + "If -weight-volume is not specified, each voxel's volume is used. " + "Use -subvolume to only give output for a single subvolume. " + "Use -roi to consider only the data within a region. " + "Exactly one of -mean, -stdev, -percentile or -sum must be specified.\n\n" + "Using -sum without -weight-volume is equivalent to integrating with respect to volume." ); return ret; } namespace { enum OperationType { MEAN, STDEV, SAMPSTDEV, PERCENTILE, SUM }; float doOperation(const float* data, const float* weights, const int64_t& numElements, const OperationType& myop, const float* roiData, const float& argument) {//argument is only used for percentile currently if (roiData != NULL) { bool haveData = false; for (int64_t i = 0; i < numElements; ++i) { if (weights[i] > 0.0f) { haveData = true; break; } } if (!haveData) throw OperationException("roi contains no voxels"); } switch(myop) { case SUM: case MEAN: case STDEV: case SAMPSTDEV://these all start the same way { double accum = 0.0, weightsum = 0.0; for (int64_t i = 0; i < numElements; ++i) { if (roiData == NULL || roiData[i] > 0.0f) { accum += data[i] * weights[i]; weightsum += weights[i]; } } if (myop == SUM) return accum; const float mean = accum / weightsum; if (myop == MEAN) return mean; accum = 0.0; double weightsum2 = 0.0;//for weighted sample stdev for (int64_t i = 0; i < numElements; ++i) { if (roiData == NULL || roiData[i] > 0.0f) { float tempf = data[i] - mean; accum += weights[i] * tempf * tempf; weightsum2 += weights[i] * weights[i]; } } if (myop == STDEV) return sqrt(accum / weightsum); CaretAssert(myop == SAMPSTDEV); return sqrt(accum / (weightsum - weightsum2 / weightsum));//http://en.wikipedia.org/wiki/Weighted_arithmetic_mean#Weighted_sample_variance } case PERCENTILE: { CaretAssert(argument >= 0.0f && argument <= 100.0f); CaretSimpleMinHeap sorter; double weightaccum = 0.0;//double will usually prevent adding weights in a different order from getting a different answer for (int64_t i = 0; i < numElements; ++i) { if (roiData == NULL || roiData[i] > 0.0f) { if (weights[i] < 0.0f) throw OperationException("negative weights not allowed in weighted percentile"); weightaccum += weights[i]; sorter.push(weights[i], data[i]);//sort by value, so the key is the data } } int64_t numUse = sorter.size(); if (numUse == 1)//would need special handling anyway, so get it early { float ret; sorter.top(&ret); return ret; } float targetWeight = argument / 100.0f * weightaccum; float lastData, nextData; float lastWeight = sorter.pop(&lastData); weightaccum = lastWeight; float nextWeight = sorter.top(&nextData); int64_t position = 1;//because the first and last sections get special treatment to not have flat ends on the function while (weightaccum + nextWeight * 0.5f < targetWeight && sorter.size() > 1) { ++position; sorter.pop(); weightaccum += nextWeight; lastWeight = nextWeight; lastData = nextData; nextWeight = sorter.top(&nextData); } if (targetWeight < weightaccum) { if (position == 1) {//stretch interpolation at first position to the edge return lastData + (nextData - lastData) * 0.5f * ((targetWeight - weightaccum) / lastWeight + 1.0f); } else { return lastData + (nextData - lastData) * 0.5f * ((targetWeight - weightaccum) / (lastWeight * 0.5f) + 1.0f); } } else { if (position == numUse - 1) {//ditto return (lastData + nextData) * 0.5f + (nextData - lastData) * 0.5f * (targetWeight - weightaccum) / nextWeight; } else { return (lastData + nextData) * 0.5f + (nextData - lastData) * 0.5f * (targetWeight - weightaccum) / (nextWeight * 0.5f); } } } } CaretAssert(false);//make sure execution never actually reaches end of function throw OperationException("internal error in weighted stats"); } float doOperationSingleWeight(const float* data, const float& weight, const int64_t& numElements, const OperationType& myop, const float* roiData, const float& argument) {//argument is only used for percentile currently const float* useData = data; int64_t numUse = numElements; vector dataScratch;//for when we have an ROI if (roiData != NULL) { dataScratch.reserve(numElements); for (int64_t i = 0; i < numElements; ++i) { if (roiData[i] > 0.0f) { dataScratch.push_back(data[i]); } } if (dataScratch.size() < 1) throw OperationException("roi contains no voxels"); useData = dataScratch.data(); numUse = (int64_t)dataScratch.size(); } switch(myop) { case SUM: case MEAN: case STDEV: case SAMPSTDEV://these all start the same way { double accum = 0.0; for (int64_t i = 0; i < numUse; ++i) { accum += useData[i]; } if (myop == SUM) return accum * weight;//this is the only operation that needs the weight when it is the same at every location const float mean = accum / numUse; if (myop == MEAN) return mean; accum = 0.0; for (int64_t i = 0; i < numUse; ++i) { float tempf = useData[i] - mean; accum += tempf * tempf; } if (myop == STDEV) return sqrt(accum / numUse); CaretAssert(myop == SAMPSTDEV); if (numUse < 2) throw OperationException("sample standard deviation requires at least 2 elements in the roi"); return sqrt(accum / (numUse - 1)); } case PERCENTILE: { CaretAssert(argument >= 0.0f && argument <= 100.0f);//same as unweighted vector sortCopy(useData, useData + numUse); sort(sortCopy.begin(), sortCopy.end()); const double index = argument / 100.0f * (sortCopy.size() - 1); if (index <= 0) return sortCopy[0]; if (index >= sortCopy.size() - 1) return sortCopy.back(); double ipart, fpart; fpart = modf(index, &ipart); return (1.0f - fpart) * sortCopy[(int64_t)ipart] + fpart * sortCopy[((int64_t)ipart) + 1]; } } CaretAssert(false);//make sure execution never actually reaches end of function throw OperationException("internal error in weighted stats"); } } void OperationVolumeWeightedStats::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); VolumeFile* input = myParams->getVolume(1); const float constWeight = input->getVolumeSpace().getVoxelVolume();//even if we don't need it vector dims = input->getDimensions(); const int64_t frameSize = dims[0] * dims[1] * dims[2]; if (input->getNumberOfComponents() != 1) throw OperationException("multi-component volumes are not supported in -volume-weighted-stats"); OptionalParameter* weightVolumeOpt = myParams->getOptionalParameter(2); VolumeFile* myWeights = NULL; const float* weightData = NULL; if (weightVolumeOpt->m_present) { myWeights = weightVolumeOpt->getVolume(1); if (!myWeights->matchesVolumeSpace(input)) throw OperationException("weight volume doesn't match volume space of input"); weightData = myWeights->getFrame(); } int subvol = -1; OptionalParameter* subvolOpt = myParams->getOptionalParameter(3); if (subvolOpt->m_present) { subvol = input->getMapIndexFromNameOrNumber(subvolOpt->getString(1)); if (subvol < 0) throw OperationException("invalid column specified"); } bool matchSubvolMode = false; VolumeFile* myRoi = NULL; const float* roiData = NULL; OptionalParameter* roiOpt = myParams->getOptionalParameter(4); if (roiOpt->m_present) { myRoi = roiOpt->getVolume(1); if (!input->matchesVolumeSpace(myRoi)) throw OperationException("roi doesn't match volume space of input"); if (roiOpt->getOptionalParameter(2)->m_present) { if (myRoi->getDimensions()[3] != dims[3]) { throw OperationException("-match-maps specified, but roi has different number of subvolumes than input"); } matchSubvolMode = true; } else { roiData = myRoi->getFrame(); } } bool haveOp = false; OperationType myop; if (myParams->getOptionalParameter(5)->m_present) { haveOp = true; myop = MEAN; } OptionalParameter* stdevOpt = myParams->getOptionalParameter(6); if (stdevOpt->m_present) { if (haveOp) throw OperationException("you may only specify one operation"); haveOp = true; if (stdevOpt->getOptionalParameter(1)->m_present) { myop = SAMPSTDEV; } else { myop = STDEV; } } float argument = -1.0f; OptionalParameter* percentileOpt = myParams->getOptionalParameter(7); if (percentileOpt->m_present) { if (haveOp) throw OperationException("you may only specify one operation"); haveOp = true; myop = PERCENTILE; argument = percentileOpt->getDouble(1); if (!(argument >= 0.0f && argument <= 100.0f)) throw OperationException("percentile must be between 0 and 100"); } if (myParams->getOptionalParameter(8)->m_present) { if (haveOp) throw OperationException("you may only specify one operation"); haveOp = true; myop = SUM; } if (!haveOp) throw OperationException("you must specify an operation"); bool showMapName = myParams->getOptionalParameter(9)->m_present; int numMaps = input->getNumberOfMaps(); if (subvol == -1) { for (int i = 0; i < numMaps; ++i) {//store result before printing anything, in case it throws while computing if (matchSubvolMode) { roiData = myRoi->getFrame(i); } float result; if (weightData != NULL) { result = doOperation(input->getFrame(i), weightData, frameSize, myop, roiData, argument); } else { result = doOperationSingleWeight(input->getFrame(i), constWeight, frameSize, myop, roiData, argument); } if (showMapName) cout << AString::number(i + 1) << ": " << input->getMapName(i) << ": "; stringstream resultsstr; resultsstr << setprecision(7) << result; cout << resultsstr.str() << endl; } } else { if (matchSubvolMode) { roiData = myRoi->getFrame(subvol); } float result; if (weightData != NULL) { result = doOperation(input->getFrame(subvol), weightData, frameSize, myop, roiData, argument); } else { result = doOperationSingleWeight(input->getFrame(subvol), constWeight, frameSize, myop, roiData, argument); } if (showMapName) cout << AString::number(subvol + 1) << ": " << input->getMapName(subvol) << ": "; stringstream resultsstr; resultsstr << setprecision(7) << result; cout << resultsstr.str() << endl; } } workbench-1.1.1/src/Operations/OperationVolumeWeightedStats.h000066400000000000000000000026661255417355300244330ustar00rootroot00000000000000#ifndef __OPERATION_VOLUME_WEIGHTED_STATS_H__ #define __OPERATION_VOLUME_WEIGHTED_STATS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationVolumeWeightedStats : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationVolumeWeightedStats; } #endif //__OPERATION_VOLUME_WEIGHTED_STATS_H__ workbench-1.1.1/src/Operations/OperationWbsparseMergeDense.cxx000066400000000000000000000324321255417355300245560ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationWbsparseMergeDense.h" #include "OperationException.h" #include "CaretSparseFile.h" using namespace caret; using namespace std; AString OperationWbsparseMergeDense::getCommandSwitch() { return "-wbsparse-merge-dense"; } AString OperationWbsparseMergeDense::getShortDescription() { return "MERGE WBSPARSE FILES ALONG DENSE DIMENSION"; } OperationParameters* OperationWbsparseMergeDense::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addStringParameter(1, "direction", "which dimension to merge along, ROW or COLUMN"); ret->addStringParameter(2, "wbsparse-out", "output - the output wbsparse file");//HACK: fake the output format since we don't have a wbsparse parameter type (or file type, really) ParameterComponent* wbsparseOpt = ret->createRepeatableParameter(3, "-wbsparse", "specify an input wbsparse file"); wbsparseOpt->addStringParameter(1, "wbsparse-in", "a wbsparse file to merge"); ret->setHelpText( AString("The input wbsparse files must have matching mappings along the direction not specified, and the mapping along the specified direction must be brain models.") ); return ret; } void OperationWbsparseMergeDense::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); AString directionName = myParams->getString(1); int myDir; if (directionName == "ROW") { myDir = CiftiXML::ALONG_ROW; } else if (directionName == "COLUMN") { myDir = CiftiXML::ALONG_COLUMN; } else { throw OperationException("incorrect string for direction, use ROW or COLUMN"); } AString outputName = myParams->getString(2); const vector& myInstances = *(myParams->getRepeatableParameterInstances(3)); vector > wbsparseList; int numCifti = (int)myInstances.size(); for (int i = 0; i < numCifti; ++i) { wbsparseList.push_back(CaretPointer(new CaretSparseFile(myInstances[i]->getString(1)))); } if (wbsparseList.size() == 0) throw OperationException("no files specified"); if (myDir != CiftiXML::ALONG_ROW && myDir != CiftiXML::ALONG_COLUMN) throw OperationException("direction not supported by wbsparse merge dense"); int otherDir = 1 - myDir;//find the other direction const CiftiXML& baseXML = wbsparseList[0]->getCiftiXML(); if (baseXML.getMappingType(myDir) != CiftiMappingType::BRAIN_MODELS) throw OperationException("mapping type along specified dimension is not brain models"); if (baseXML.getMappingType(otherDir) == CiftiMappingType::LABELS)throw OperationException("labels not supported in wbsparse merge dense"); CiftiXML outXML = baseXML; VolumeSpace baseSpace; const CiftiBrainModelsMap& baseDenseMap = baseXML.getBrainModelsMap(myDir); CiftiBrainModelsMap newDenseMap = baseDenseMap; bool haveVolSpace = false; if (baseDenseMap.hasVolumeData()) { haveVolSpace = true; baseSpace = baseDenseMap.getVolumeSpace(); } vector sourceWbsparse(baseDenseMap.getModelInfo().size(), 0); for (int i = 1; i < (int)wbsparseList.size(); ++i) { const CiftiXML& otherXML = wbsparseList[i]->getCiftiXML(); if (*(baseXML.getMap(otherDir)) != *(otherXML.getMap(otherDir))) { throw OperationException("mappings along other dimension do not match"); } if (otherXML.getMappingType(myDir) != CiftiMappingType::BRAIN_MODELS) throw OperationException("input files do not have brain models mapping on merge dimension"); const CiftiBrainModelsMap& otherDenseMap = otherXML.getBrainModelsMap(myDir); if (otherDenseMap.hasVolumeData()) { if (haveVolSpace) { if (!baseSpace.matches(otherDenseMap.getVolumeSpace())) throw OperationException("input files have non-matching volume spaces"); } else { haveVolSpace = true; baseSpace = otherDenseMap.getVolumeSpace(); newDenseMap.setVolumeSpace(baseSpace);//need to set the output vol space if the first input didn't have volume data } } vector otherModels = otherDenseMap.getModelInfo(); int numModels = (int)otherModels.size(); for (int j = 0; j < numModels; ++j) { sourceWbsparse.push_back(i); const CiftiBrainModelsMap::ModelInfo& myInfo = otherModels[j]; switch (myInfo.m_type) { case CiftiBrainModelsMap::SURFACE: { vector myMap = otherDenseMap.getSurfaceMap(myInfo.m_structure); vector nodeList(myMap.size()); for (int64_t k = 0; k < (int64_t)myMap.size(); ++k) { nodeList[k] = myMap[k].m_surfaceNode; } newDenseMap.addSurfaceModel(otherDenseMap.getSurfaceNumberOfNodes(myInfo.m_structure), myInfo.m_structure, nodeList); break; } case CiftiBrainModelsMap::VOXELS: { vector myMap = otherDenseMap.getVolumeStructureMap(myInfo.m_structure); vector voxelList(myMap.size() * 3); for (int64_t k = 0; k < (int64_t)myMap.size(); ++k) { int64_t k3 = k * 3; voxelList[k3] = myMap[k].m_ijk[0]; voxelList[k3 + 1] = myMap[k].m_ijk[1]; voxelList[k3 + 2] = myMap[k].m_ijk[2]; } newDenseMap.addVolumeModel(myInfo.m_structure, voxelList); break; } default: throw OperationException("encountered unknown model type in cifti merge dense"); } } } outXML.setMap(myDir, newDenseMap); int numOutModels = (int)sourceWbsparse.size(); CaretAssert(numOutModels == (int)newDenseMap.getModelInfo().size()); int64_t outColSize = outXML.getDimensionLength(CiftiXML::ALONG_COLUMN); CaretSparseFileWriter myWriter(outputName, outXML); vector outModelInfo = newDenseMap.getModelInfo(); switch (myDir) { case CiftiXML::ALONG_ROW: { for (int64_t i = 0; i < outColSize; ++i) { int64_t curOffset = 0; vector outIndices, outValues, inIndices, inValues; int loaded = -1; for (int j = 0; j < numOutModels; ++j)//we could just do the entire row for each file, but doing it by structure could allow structure selection in the future { const CiftiBrainModelsMap::ModelInfo& myInfo = outModelInfo[j]; const CiftiXML& thisXML = wbsparseList[sourceWbsparse[j]]->getCiftiXML(); const CiftiBrainModelsMap& thisDenseMap = thisXML.getBrainModelsMap(myDir); int64_t startIndex = -1, endIndex = -1; switch (myInfo.m_type) { case CiftiBrainModelsMap::SURFACE: { vector tempMap = thisDenseMap.getSurfaceMap(myInfo.m_structure); if (tempMap.size() > 0) { startIndex = tempMap[0].m_ciftiIndex;//NOTE: CiftiXML guarantees these are ordered by cifti index and contiguous endIndex = startIndex + tempMap.size(); } else { startIndex = 0; endIndex = 0; } break; } case CiftiBrainModelsMap::VOXELS: { vector tempMap = thisDenseMap.getVolumeStructureMap(myInfo.m_structure); if (tempMap.size() > 0) { startIndex = tempMap[0].m_ciftiIndex;//NOTE: CiftiXML guarantees these are ordered by cifti index and contiguous endIndex = startIndex + tempMap.size(); } else { startIndex = 0; endIndex = 0; } break; } default: CaretAssert(false); break; } if (endIndex > startIndex) { if (loaded != sourceWbsparse[j]) { wbsparseList[sourceWbsparse[j]]->getRowSparse(i, inIndices, inValues); loaded = sourceWbsparse[j]; } int64_t numSparse = (int64_t)inIndices.size(); for (int64_t k = 0; k < numSparse; ++k) { if (inIndices[k] >= startIndex && inIndices[k] < endIndex) { outIndices.push_back(inIndices[k] + curOffset); outValues.push_back(inValues[k]); } } curOffset += endIndex - startIndex; } } myWriter.writeRowSparse(i, outIndices, outValues); outIndices.clear();//reset for next row outValues.clear(); } break; } case CiftiXML::ALONG_COLUMN: { vector inIndices, inValues; for (int j = 0; j < numOutModels; ++j) { const CiftiBrainModelsMap::ModelInfo& myInfo = outModelInfo[j]; const CiftiXML& thisXML = wbsparseList[sourceWbsparse[j]]->getCiftiXML(); const CiftiBrainModelsMap& thisDenseMap = thisXML.getBrainModelsMap(myDir); switch (myInfo.m_type) { case CiftiBrainModelsMap::SURFACE: { vector tempMap = thisDenseMap.getSurfaceMap(myInfo.m_structure), outMap = newDenseMap.getSurfaceMap(myInfo.m_structure); int64_t mapSize = (int64_t)tempMap.size(); CaretAssert(mapSize == (int64_t)outMap.size()); for (int64_t k = 0; k < mapSize; ++k) { CaretAssert(tempMap[k].m_surfaceNode == outMap[k].m_surfaceNode); wbsparseList[sourceWbsparse[j]]->getRowSparse(tempMap[k].m_ciftiIndex, inIndices, inValues); myWriter.writeRowSparse(outMap[k].m_ciftiIndex, inIndices, inValues); } break; } case CiftiBrainModelsMap::VOXELS: { vector tempMap = thisDenseMap.getVolumeStructureMap(myInfo.m_structure), outMap = newDenseMap.getVolumeStructureMap(myInfo.m_structure); int64_t mapSize = (int64_t)tempMap.size(); CaretAssert(mapSize == (int64_t)outMap.size()); for (int64_t k = 0; k < mapSize; ++k) { CaretAssert(tempMap[k].m_ijk[0] == outMap[k].m_ijk[0]); CaretAssert(tempMap[k].m_ijk[1] == outMap[k].m_ijk[1]); CaretAssert(tempMap[k].m_ijk[2] == outMap[k].m_ijk[2]); wbsparseList[sourceWbsparse[j]]->getRowSparse(tempMap[k].m_ciftiIndex, inIndices, inValues); myWriter.writeRowSparse(outMap[k].m_ciftiIndex, inIndices, inValues); } break; } default: CaretAssert(false); break; } } break; } default: CaretAssert(false); break; } myWriter.finish(); } workbench-1.1.1/src/Operations/OperationWbsparseMergeDense.h000066400000000000000000000026601255417355300242030ustar00rootroot00000000000000#ifndef __OPERATION_WBSPARSE_MERGE_DENSE_H__ #define __OPERATION_WBSPARSE_MERGE_DENSE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationWbsparseMergeDense : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationWbsparseMergeDense; } #endif //__OPERATION_WBSPARSE_MERGE_DENSE_H__ workbench-1.1.1/src/Operations/OperationZipSceneFile.cxx000066400000000000000000000236071255417355300233550ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretLogger.h" #include "DataFile.h" #include "FileInformation.h" #include "OperationZipSceneFile.h" #include "OperationException.h" #include "Scene.h" #include "SceneAttributes.h" #include "SceneClass.h" #include "SceneClassArray.h" #include "SceneFile.h" #include "SpecFile.h" #include "quazip.h" #include "quazipfile.h" #include #include #include using namespace caret; using namespace std; AString OperationZipSceneFile::getCommandSwitch() { return "-zip-scene-file"; } AString OperationZipSceneFile::getShortDescription() { return "ZIP A SCENE FILE AND ITS DATA FILES"; } OperationParameters* OperationZipSceneFile::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addStringParameter(1, "scene-file", "the scene file to make the zip file from"); ret->addStringParameter(2, "extract-folder", "the name of the folder created when the zip file is unzipped"); ret->addStringParameter(3, "zip-file", "out - the zip file that will be created"); OptionalParameter* baseOpt = ret->createOptionalParameter(4, "-base-dir", "specify a directory that all data files are somewhere within, this will become the root of the zipfile's directory structure"); baseOpt->addStringParameter(1, "directory", "the directory"); ret->setHelpText("If zip-file already exists, it will be overwritten. " "If -base-dir is not specified, the directory containing the scene file is used for the base directory. " "The scene file must contain only relative paths, and no data files may be outside the base directory."); return ret; } void OperationZipSceneFile::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); AString sceneFileName = myParams->getString(1); AString outputSubDirectory = myParams->getString(2); AString zipFileName = myParams->getString(3); FileInformation sceneFileInfo(sceneFileName); if (outputSubDirectory.isEmpty()) { throw OperationException("extract-dir must contain characters"); } OptionalParameter* baseOpt = myParams->getOptionalParameter(4); AString myBaseDir; if (baseOpt->m_present) { myBaseDir = QDir::cleanPath(QDir(baseOpt->getString(1)).absolutePath()); } else { myBaseDir = QDir::cleanPath(sceneFileInfo.getAbsolutePath()); } if (!myBaseDir.endsWith('/'))//root is a special case, if we didn't handle it differently it would end up looking for "//somefile" {//this is actually because the path function strips the final "/" from the path, but not when it is just "/" myBaseDir += "/";//so, add the trailing slash to the path } AString sceneFilePath = QDir::cleanPath(sceneFileInfo.getAbsoluteFilePath());//resolve filenames to open from the spec file's location, NOT from current directory if (!sceneFilePath.startsWith(myBaseDir)) { throw OperationException("scene file lies outside the base directory"); } if (FileInformation(outputSubDirectory).isAbsolute()) { CaretLogWarning("You have specified that the zip file should extract to an absolute path, this is generally frowned on. " "The parameter should generally be a string without '/' or '\\' in it."); } else { if (outputSubDirectory.indexOfAnyChar("/\\"))//assume backslashes work too { CaretLogWarning("You have specified that the zipfile should create multiple levels of otherwise empty directories " "before the file paths starting from the base directory, this is probably going to be inconvenient. " "The parameter should generally be a string without '/' or '\\' in it."); } } set allFiles; allFiles.insert(sceneFilePath); SceneFile sceneFile; sceneFile.readFile(sceneFileName); const int numScenes = sceneFile.getNumberOfScenes(); for (int i = 0; i < numScenes; ++i) { Scene* thisScene = sceneFile.getSceneAtIndex(i); SceneAttributes* myAttrs = thisScene->getAttributes(); const SceneClass* guiMgrClass = thisScene->getClassWithName("guiManager"); if (guiMgrClass == NULL) { throw OperationException("scene '" + thisScene->getName() + "' is missing guiManager class"); } const SceneClass* sessMgrClass = guiMgrClass->getClass("m_sessionManager"); if (sessMgrClass == NULL) { throw OperationException("scene '" + thisScene->getName() + "' is missing m_sessionManager class"); } const SceneClassArray* brainArray = sessMgrClass->getClassArray("m_brains"); if (brainArray == NULL) { throw OperationException("scene '" + thisScene->getName() + "' is missing m_brains class array"); } const int numBrainClasses = brainArray->getNumberOfArrayElements(); for (int j = 0; j < numBrainClasses; ++j) { const SceneClass* brainClass = brainArray->getClassAtIndex(j); const SceneClass* specClass = brainClass->getClass("specFile"); if (specClass == NULL) { throw OperationException("scene '" + thisScene->getName() + "' is missing specFile class in m_brains element " + AString::number(j)); } SpecFile tempSpec; tempSpec.restoreFromScene(myAttrs, specClass); vector tempNames = tempSpec.getAllDataFileNames(); int numNames = (int)tempNames.size(); for (int k = 0; k < numNames; ++k) { if (DataFile::isFileOnNetwork(tempNames[k])) { cout << "skipping network file '" << tempNames[k] << "'" << endl; continue; } AString thisName = QDir::cleanPath(tempNames[k]); if (allFiles.insert(thisName).second) { if (FileInformation(thisName).isRelative()) { throw OperationException("scene '" + thisScene->getName() + "' contains an unresolved relative path: '" + tempNames[k] + "'"); } if (!thisName.startsWith(myBaseDir)) { throw OperationException("scene '" + thisScene->getName() + "' contains a file outside the base directory: '" + thisName + "', try using -base-dir"); } } } } } QFile zipFileObject(zipFileName); QuaZip zipFile(&zipFileObject); if (!zipFile.open(QuaZip::mdCreate)) { throw OperationException("Unable to open ZIP File \"" + zipFileName + "\" for writing."); } static const char *myUnits[9] = {" B ", " KB", " MB", " GB", " TB", " PB", " EB", " ZB", " YB"}; for (set::iterator iter = allFiles.begin(); iter != allFiles.end(); ++iter) { AString dataFileName = *iter; AString unzippedDataFileName = outputSubDirectory + "/" + dataFileName.mid(myBaseDir.size());//we know the string matches to the length of myBaseDir, and is cleaned, so we can just chop the right number of characters off QFile dataFileIn(dataFileName); if (!dataFileIn.open(QFile::ReadOnly)) { throw OperationException("Unable to open \"" + dataFileName + "\" for reading: " + dataFileIn.errorString()); } float fileSize = (float)dataFileIn.size(); int unit = 0; while (unit < 8 && fileSize >= 1000.0f)//don't let there be 4 digits to the left of decimal point { ++unit; fileSize /= 1000.0f;//use GB and friends, not GiB } if (unit > 0) { cout << AString::number(fileSize, 'f', 2); } else { cout << AString::number(fileSize); } cout << myUnits[unit] << " \t" << unzippedDataFileName; cout.flush();//don't endl until it finishes QuaZipNewInfo zipNewInfo(unzippedDataFileName, dataFileName); zipNewInfo.externalAttr |= (6 << 22L) | (6 << 19L) | (4 << 16L);//make permissions 664 QuaZipFile dataFileOut(&zipFile); if (!dataFileOut.open(QIODevice::WriteOnly, zipNewInfo)) { throw OperationException("Unable to open zip output for \"" + dataFileName + "\""); } const qint64 BUFFER_SIZE = 1024 * 1024; vector buffer(BUFFER_SIZE); while (dataFileIn.atEnd() == false) { const qint64 numRead = dataFileIn.read(buffer.data(), BUFFER_SIZE); if (numRead < 0) throw OperationException("Error reading from data file"); if (numRead > 0) { qint64 result = dataFileOut.write(buffer.data(), numRead); if (result != numRead) throw OperationException("Error writing to zip file"); } } dataFileIn.close(); dataFileOut.close(); cout << endl; } zipFile.close(); } workbench-1.1.1/src/Operations/OperationZipSceneFile.h000066400000000000000000000026141255417355300227750ustar00rootroot00000000000000#ifndef __OPERATION_ZIP_SCENE_FILE_H__ #define __OPERATION_ZIP_SCENE_FILE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationZipSceneFile : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationZipSceneFile; } #endif //__OPERATION_ZIP_SCENE_FILE_H__ workbench-1.1.1/src/Operations/OperationZipSpecFile.cxx000066400000000000000000000236651255417355300232160ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretLogger.h" #include "DataFile.h" #include "FileInformation.h" #include "OperationZipSpecFile.h" #include "OperationException.h" #include "SpecFile.h" #include "quazip.h" #include "quazipfile.h" //for cleanPath #include //to print file sizes as it makes the zip #include #include using namespace caret; using namespace std; AString OperationZipSpecFile::getCommandSwitch() { return "-zip-spec-file"; } AString OperationZipSpecFile::getShortDescription() { return "ZIP A SPEC FILE AND ITS DATA FILES"; } OperationParameters* OperationZipSpecFile::getParameters() { OperationParameters* ret = new OperationParameters(); ret->addStringParameter(1, "spec-file", "the specification file to add to zip file"); ret->addStringParameter(2, "extract-folder", "the name of the folder created when the zip file is unzipped"); ret->addStringParameter(3, "zip-file", "out - the zip file that will be created"); OptionalParameter* baseOpt = ret->createOptionalParameter(4, "-base-dir", "specify a directory that all data files are somewhere within, this will become the root of the zipfile's directory structure"); baseOpt->addStringParameter(1, "directory", "the directory"); ret->setHelpText(AString("If zip-file already exists, it will be overwritten. ") + "If -base-dir is not specified, the directory containing the spec file is used for the base directory. " + "The spec file must contain only relative paths, and no data files may be outside the base directory. " + "Scene files inside spec files are not checked for what files they reference, ensure that all data files referenced by the scene files are also referenced by the spec file."); return ret; } void OperationZipSpecFile::useParameters(OperationParameters* myParams, ProgressObject* myProgObj) { LevelProgress myProgress(myProgObj); AString specFileName = FileInformation(myParams->getString(1)).getAbsoluteFilePath(); AString outputSubDirectory = myParams->getString(2); AString zipFileName = FileInformation(myParams->getString(3)).getAbsoluteFilePath(); OptionalParameter* baseOpt = myParams->getOptionalParameter(4); AString myBaseDir; if (baseOpt->m_present) { myBaseDir = QDir::cleanPath(QDir(baseOpt->getString(1)).absolutePath()); } else { FileInformation specFileInfo(specFileName); myBaseDir = QDir::cleanPath(specFileInfo.getAbsolutePath()); } if (!myBaseDir.endsWith('/'))//root is a special case, if we didn't handle it differently it would end up looking for "//somefile" {//this is actually because the path function strips the final "/" from the path, but not when it is just "/" myBaseDir += "/";//so, add the trailing slash to the path } if (outputSubDirectory.isEmpty()) { throw OperationException("extract-dir must contain characters"); } if (FileInformation(outputSubDirectory).isAbsolute()) { CaretLogWarning("You have specified that the zip file should extract to an absolute path, this is generally frowned on. " "The parameter should generally be a string without '/' or '\\' in it."); } else { if (outputSubDirectory.indexOfAnyChar("/\\"))//assume backslashes work too { CaretLogWarning("You have specified that the zipfile should create multiple levels of otherwise empty directories " "before the file paths starting from the base directory, this is probably going to be inconvenient. " "The parameter should generally be a string without '/' or '\\' in it."); } } /* * Read the spec file and get the names of its data files. * Look for any files that are missing (name in spec file * but file not found). */ FileInformation specFileInfo(specFileName); AString specPath = QDir::cleanPath(specFileInfo.getAbsolutePath()); if (!specPath.endsWith('/')) { specPath += "/"; } SpecFile specFile; specFile.readFile(specFileName); std::vector allDataFileNames = specFile.getAllDataFileNames(); allDataFileNames.push_back(specFileName); /* * Verify that all data files exist */ AString missingDataFileNames; AString outsideBaseDirFiles; const int32_t numberOfDataFiles = static_cast(allDataFileNames.size()); for (int32_t i = 0; i < numberOfDataFiles; i++) { AString dataFileName = allDataFileNames[i]; if (DataFile::isFileOnNetwork(dataFileName)) { cout << "skipping network file '" << dataFileName << "'" << endl; allDataFileNames.erase(allDataFileNames.begin() + i);//remove it from the list --i;//decrement i in order not to skip anything continue; } FileInformation tempInfo(dataFileName); if (tempInfo.isRelative()) { dataFileName = specPath + dataFileName; } FileInformation dataFileInfo(dataFileName); AString absName = QDir::cleanPath(dataFileInfo.getAbsoluteFilePath()); if (!absName.startsWith(myBaseDir)) { outsideBaseDirFiles += absName + "\n"; } if (dataFileInfo.exists() == false) { missingDataFileNames += absName + "\n"; } allDataFileNames[i] = absName;//so we don't have to do this again } if (!missingDataFileNames.isEmpty()) { throw OperationException("These data files do not exist:\n" + missingDataFileNames); } if (!outsideBaseDirFiles.isEmpty()) { throw OperationException("These data files lie outside the base directiory:\n" + outsideBaseDirFiles + "Try using -base-dir."); } /* * Create the ZIP file */ QFile zipFileObject(zipFileName); QuaZip zipFile(&zipFileObject); if (zipFile.open(QuaZip::mdCreate) == false) { throw OperationException("Unable to open ZIP File \"" + zipFileName + "\" for writing."); } /* * Compress each of the files and add them to the zip file */ AString errorMessage; static const char *myUnits[9] = {" B ", " KB", " MB", " GB", " TB", " PB", " EB", " ZB", " YB"}; for (int32_t i = 0; i < numberOfDataFiles; i++) { AString dataFileName = allDataFileNames[i]; AString unzippedDataFileName = outputSubDirectory + "/" + dataFileName.mid(myBaseDir.size());//we know the string matches to the length of myBaseDir, and is cleaned, so we can just chop the right number of characters off QFile dataFileIn(dataFileName); if (dataFileIn.open(QFile::ReadOnly) == false) { errorMessage = "Unable to open \"" + dataFileName + "\" for reading: " + dataFileIn.errorString(); break; } float fileSize = (float)dataFileIn.size(); int unit = 0; while (unit < 8 && fileSize >= 1000.0f)//don't let there be 4 digits to the left of decimal point { ++unit; fileSize /= 1000.0f;//use GB and friends, not GiB } if (unit > 0) { cout << AString::number(fileSize, 'f', 2); } else { cout << AString::number(fileSize); } cout << myUnits[unit] << " \t" << unzippedDataFileName; cout.flush();//don't endl until it finishes QuaZipNewInfo zipNewInfo(unzippedDataFileName, dataFileName); zipNewInfo.externalAttr |= (6 << 22L) | (6 << 19L) | (4 << 16L);//make permissions 664 QuaZipFile dataFileOut(&zipFile); if (dataFileOut.open(QIODevice::WriteOnly, zipNewInfo) == false) { errorMessage = "Unable to open zip output for \"" + dataFileName + "\""; break; } const qint64 BUFFER_SIZE = 1024 * 1024; vector buffer(BUFFER_SIZE); while (dataFileIn.atEnd() == false) { const qint64 numRead = dataFileIn.read(buffer.data(), BUFFER_SIZE); if (numRead < 0) { errorMessage = "Error reading from data file"; break; } if (numRead > 0) { qint64 result = dataFileOut.write(buffer.data(), numRead); if (result != numRead) { errorMessage = "Error writing to zip file"; break; } } } if (!errorMessage.isEmpty()) break; dataFileIn.close(); dataFileOut.close(); cout << endl; } /* * Close the zip file */ zipFile.close(); /* * If there are errors, remove the ZIP file and * indicate an error has occurred. */ if (errorMessage.isEmpty() == false) { QFile::remove(zipFileName); throw OperationException(errorMessage); } } workbench-1.1.1/src/Operations/OperationZipSpecFile.h000066400000000000000000000026061255417355300226330ustar00rootroot00000000000000#ifndef __OPERATION_ZIP_SPEC_FILE_H__ #define __OPERATION_ZIP_SPEC_FILE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" namespace caret { class OperationZipSpecFile : public AbstractOperation { public: static OperationParameters* getParameters(); static void useParameters(OperationParameters* myParams, ProgressObject* myProgObj); static AString getCommandSwitch(); static AString getShortDescription(); }; typedef TemplateAutoOperation AutoOperationZipSpecFile; } #endif //__OPERATION_ZIP_SPEC_FILE_H__ workbench-1.1.1/src/OperationsBase/000077500000000000000000000000001255417355300172135ustar00rootroot00000000000000workbench-1.1.1/src/OperationsBase/AbstractOperation.cxx000066400000000000000000000032131255417355300233620ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AbstractOperation.h" #include "CaretDataFile.h" #include "CaretLogger.h" using namespace std; using namespace caret; void AbstractOperation::checkStructureMatch(const CaretDataFile* toCheck, const StructureEnum::Enum& correctStruct, const AString& fileDescrip, const AString& basisDescrip) { if (toCheck != NULL && toCheck->getStructure() != correctStruct) { CaretLogWarning(fileDescrip + " has structure '" + StructureEnum::toName(toCheck->getStructure()) + "', while " + basisDescrip + " structure '" + StructureEnum::toName(correctStruct) + "'"); } } AbstractOperation::AbstractOperation() { } AbstractOperation::~AbstractOperation() { } OperationParserInterface::~OperationParserInterface() { delete m_autoOper; } AutoOperationInterface::~AutoOperationInterface() { } workbench-1.1.1/src/OperationsBase/AbstractOperation.h000066400000000000000000000101701255417355300230070ustar00rootroot00000000000000#ifndef __ABSTRACT_OPERATION_H__ #define __ABSTRACT_OPERATION_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ //make it easy to use these in an algorithm class, don't just forward declare them #include "ProgressObject.h" #include "CaretAssert.h" #include "OperationParameters.h" #include "StructureEnum.h" namespace caret { class CaretDataFile; class AbstractOperation { protected: AbstractOperation(); virtual ~AbstractOperation(); public: ///override these to allow operation parsers to use your operation without writing an explicit command class static OperationParameters* getParameters() { CaretAssert(false); return NULL; } ///override these to allow operation parsers to use your operation without writing an explicit command class static void useParameters(OperationParameters*, ProgressObject*) { CaretAssert(false); } ///override this to set the command switch static AString getCommandSwitch() { CaretAssert(false); return ""; } ///override this to set the short description static AString getShortDescription() { CaretAssert(false); return ""; } ///override this if the operation doesn't take parameters static bool takesParameters() { return true; } ///convenience method for checking structures of input files static void checkStructureMatch(const CaretDataFile* toCheck, const StructureEnum::Enum& correctStruct, const AString& fileDescrip, const AString& basisDescrip); }; ///interface class for use by operation parsers - used because the above interface has only static methods, so to avoid neededing to instantiate the operation or template the parser code struct AutoOperationInterface { virtual OperationParameters* getParameters() = 0; virtual void useParameters(OperationParameters* a, ProgressObject* b) = 0; virtual AString getCommandSwitch() = 0; virtual AString getShortDescription() = 0; virtual bool takesParameters() = 0; virtual ~AutoOperationInterface(); }; ///templated interface class to pass through to something that inherits from AbstractOperation (or implements equivalent functions) ///this makes it easier to create a bridge between the static methods of the operation and an interface pointer that a parser can store template struct TemplateAutoOperation : public AutoOperationInterface { TemplateAutoOperation() { } OperationParameters* getParameters() { return T::getParameters(); } void useParameters(OperationParameters* a, ProgressObject* b) { T::useParameters(a, b); } AString getCommandSwitch() { return T::getCommandSwitch(); } AString getShortDescription() { return T::getShortDescription(); } bool takesParameters() { return T::takesParameters(); } }; ///interface class for parsers to inherit from class OperationParserInterface { OperationParserInterface();//must take an interface object, for its vtable to the real operation, so deny default construction protected: AutoOperationInterface* m_autoOper; public: OperationParserInterface(AutoOperationInterface* myAutoOper) : m_autoOper(myAutoOper) { } virtual ~OperationParserInterface(); }; } #endif //__ABSTRACT_OPERATION_H__ workbench-1.1.1/src/OperationsBase/CMakeLists.txt000066400000000000000000000012721255417355300217550ustar00rootroot00000000000000# # Name of project # PROJECT (OperationsBase) # # Need XML from Qt # SET(QT_DONT_USE_QTGUI) # # Add QT for includes # INCLUDE (${QT_USE_FILE}) # # Create the helper library # ADD_LIBRARY(OperationsBase AbstractOperation.h OperationParameters.h OperationParametersEnum.h AbstractOperation.cxx OperationParameters.cxx OperationParametersEnum.cxx ) # # Find Headers # INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/OperationsBase ${CMAKE_SOURCE_DIR}/Charting ${CMAKE_SOURCE_DIR}/FilesBase ${CMAKE_SOURCE_DIR}/Files ${CMAKE_SOURCE_DIR}/Gifti ${CMAKE_SOURCE_DIR}/Cifti ${CMAKE_SOURCE_DIR}/Palette ${CMAKE_SOURCE_DIR}/Nifti ${CMAKE_SOURCE_DIR}/Scenes ${CMAKE_SOURCE_DIR}/Xml ${CMAKE_SOURCE_DIR}/Common ) workbench-1.1.1/src/OperationsBase/OperationParameters.cxx000066400000000000000000000431171255417355300237310ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "OperationParameters.h" #include "CaretAssert.h" #include "CaretLogger.h" #include "BorderFile.h" #include "CiftiFile.h" #include "FociFile.h" #include "LabelFile.h" #include "MetricFile.h" #include "SurfaceFile.h" #include "VolumeFile.h" using namespace std; using namespace caret; ParameterComponent::ParameterComponent() { } ParameterComponent::~ParameterComponent() { for (size_t i = 0; i < m_paramList.size(); ++i) { delete m_paramList[i]; } for (size_t i = 0; i < m_outputList.size(); ++i) { delete m_outputList[i]; } for (size_t i = 0; i < m_optionList.size(); ++i) { delete m_optionList[i]; } for (size_t i = 0; i < m_repeatableOptions.size(); ++i) { delete m_repeatableOptions[i]; } } RepeatableOption::~RepeatableOption() { for (size_t i = 0; i < m_instances.size(); ++i) { delete m_instances[i]; } } OperationParameters::OperationParameters() { } ParameterComponent::ParameterComponent(const ParameterComponent& rhs) { m_paramList.resize(rhs.m_paramList.size()); for (size_t i = 0; i < m_paramList.size(); ++i) { m_paramList[i] = rhs.m_paramList[i]->cloneAbstractParameter(); } m_outputList.resize(rhs.m_outputList.size()); for (size_t i = 0; i < m_outputList.size(); ++i) { m_outputList[i] = rhs.m_outputList[i]->cloneAbstractParameter(); } m_optionList.resize(rhs.m_optionList.size()); for (size_t i = 0; i < m_optionList.size(); ++i) { m_optionList[i] = new OptionalParameter(*(rhs.m_optionList[i])); } m_repeatableOptions.resize(rhs.m_repeatableOptions.size()); for (size_t i = 0; i < m_repeatableOptions.size(); ++i) { m_repeatableOptions[i] = new RepeatableOption(*(rhs.m_repeatableOptions[i])); } } OptionalParameter* ParameterComponent::createOptionalParameter(const int32_t key, const AString& optionSwitch, const AString& description) { CaretAssertMessage(checkUniqueOption(key), "optional parameter created with previously used key"); if (optionSwitch.isEmpty() || optionSwitch[0] != '-') CaretLogWarning("developer warning: option '" + optionSwitch + "' created, but does not start with dash"); OptionalParameter* ret = new OptionalParameter(key, optionSwitch, description); m_optionList.push_back(ret); return ret; } ParameterComponent* ParameterComponent::createRepeatableParameter(const int32_t key, const AString& optionSwitch, const AString& description) { CaretAssertMessage(checkUniqueRepeatable(key), "repeatable parameter created with previously used key"); if (optionSwitch.isEmpty() || optionSwitch[0] != '-') CaretLogWarning("developer warning: repeatable option '" + optionSwitch + "' created, but does not start with dash"); RepeatableOption* newOpt = new RepeatableOption(key, optionSwitch, description); m_repeatableOptions.push_back(newOpt); return &(newOpt->m_template); } bool ParameterComponent::checkUniqueInput(const int32_t& key, const OperationParametersEnum::Enum& type) { for (size_t i = 0; i < m_paramList.size(); ++i) { if (m_paramList[i]->m_key == key && type == m_paramList[i]->getType()) { return false; } } return true; } bool ParameterComponent::checkUniqueOption(const int32_t& key) { for (size_t i = 0; i < m_optionList.size(); ++i) { if (m_optionList[i]->m_key == key) { return false; } } return true; } bool ParameterComponent::checkUniqueRepeatable(const int32_t& key) { for (size_t i = 0; i < m_repeatableOptions.size(); ++i) { if (m_repeatableOptions[i]->m_key == key) { return false; } } return true; } bool ParameterComponent::checkUniqueOutput(const int32_t& key, const OperationParametersEnum::Enum& type) { for (size_t i = 0; i < m_outputList.size(); ++i) { if (m_outputList[i]->m_key == key && type == m_outputList[i]->getType()) { return false; } } return true; } vector ParameterComponent::findUncheckedParams(const AString& contextString) const { vector ret; for (size_t i = 0; i < m_paramList.size(); ++i) { if (!m_paramList[i]->m_operationUsed) { ret.push_back("parameter '" + m_paramList[i]->m_shortName + "' of " + contextString + " was not checked by the operation"); } } for (size_t i = 0; i < m_outputList.size(); ++i) { if (!m_outputList[i]->m_operationUsed) { ret.push_back("parameter '" + m_outputList[i]->m_shortName + "' of " + contextString + " was not checked by the operation"); } } for (size_t i = 0; i < m_optionList.size(); ++i) { if (!m_optionList[i]->m_operationUsed) { ret.push_back("option '" + m_optionList[i]->m_optionSwitch + "' of " + contextString + " was not checked by the operation"); } if (m_optionList[i]->m_present) { vector temp = m_optionList[i]->findUncheckedParams("option '" + m_optionList[i]->m_optionSwitch + "'"); ret.insert(ret.end(), temp.begin(), temp.end()); } } for (size_t i = 0; i < m_repeatableOptions.size(); ++i) { if (!m_repeatableOptions[i]->m_operationUsed) { ret.push_back("option '" + m_repeatableOptions[i]->m_optionSwitch + "' of " + contextString + " was not checked by the operation"); } for (size_t j = 0; j < m_repeatableOptions[i]->m_instances.size(); ++j) { vector temp = m_repeatableOptions[i]->m_instances[j]->findUncheckedParams("option '" + m_repeatableOptions[i]->m_optionSwitch + "'"); ret.insert(ret.end(), temp.begin(), temp.end()); } } return ret; } AbstractParameter* ParameterComponent::getInputParameter(const int32_t key, const OperationParametersEnum::Enum type) { for (size_t i = 0; i < m_paramList.size(); ++i) { if (m_paramList[i]->m_key == key && type == m_paramList[i]->getType()) { m_paramList[i]->m_operationUsed = true; return m_paramList[i]; } } CaretAssertMessage(false, "Algorithm asked for parameter it didn't specify, or of wrong type"); return NULL; } OptionalParameter* ParameterComponent::getOptionalParameter(const int32_t key) { for (size_t i = 0; i < m_optionList.size(); ++i) { if (m_optionList[i]->m_key == key) { m_optionList[i]->m_operationUsed = true; return m_optionList[i]; } } CaretAssertMessage(false, "Algorithm asked for option it didn't specify"); return NULL; } const vector* ParameterComponent::getRepeatableParameterInstances(const int32_t key) { for (size_t i = 0; i < m_repeatableOptions.size(); ++i) { if (m_repeatableOptions[i]->m_key == key) { m_repeatableOptions[i]->m_operationUsed = true; return &(m_repeatableOptions[i]->m_instances); } } CaretAssertMessage(false, "Algorithm asked for option it didn't specify"); return NULL; } AbstractParameter* ParameterComponent::getOutputParameter(const int32_t key, const OperationParametersEnum::Enum type) { for (size_t i = 0; i < m_outputList.size(); ++i) { if (m_outputList[i]->m_key == key && type == m_outputList[i]->getType()) { m_outputList[i]->m_operationUsed = true; return m_outputList[i]; } } CaretAssertMessage(false, "Algorithm asked for output it didn't specify, or of wrong type"); return NULL; } //sadly, lots of boilerplate for convenience functions void ParameterComponent::addBooleanParameter(const int32_t key, const caret::AString& name, const caret::AString& description) { CaretAssertMessage(checkUniqueInput(key, OperationParametersEnum::BOOL), "input boolean parameter created with previously used key"); m_paramList.push_back(new BooleanParameter(key, name, description)); } void ParameterComponent::addCiftiParameter(const int32_t key, const AString& name, const AString& description) { CaretAssertMessage(checkUniqueInput(key, OperationParametersEnum::CIFTI), "input cifti parameter created with previously used key"); m_paramList.push_back(new CiftiParameter(key, name, description)); } void ParameterComponent::addFociParameter(const int32_t key, const AString& name, const AString& description) { CaretAssertMessage(checkUniqueInput(key, OperationParametersEnum::FOCI), "input foci parameter created with previously used key"); m_paramList.push_back(new FociParameter(key, name, description)); } void ParameterComponent::addBorderParameter(const int32_t key, const AString& name, const AString& description) { CaretAssertMessage(checkUniqueInput(key, OperationParametersEnum::BORDER), "input border parameter created with previously used key"); m_paramList.push_back(new BorderParameter(key, name, description)); } void ParameterComponent::addDoubleParameter(const int32_t key, const AString& name, const AString& description) { CaretAssertMessage(checkUniqueInput(key, OperationParametersEnum::DOUBLE), "input double parameter created with previously used key"); m_paramList.push_back(new DoubleParameter(key, name, description)); } void ParameterComponent::addMetricParameter(const int32_t key, const AString& name, const AString& description) { CaretAssertMessage(checkUniqueInput(key, OperationParametersEnum::METRIC), "input metric parameter created with previously used key"); m_paramList.push_back(new MetricParameter(key, name, description)); } void ParameterComponent::addIntegerParameter(const int32_t key, const AString& name, const AString& description) { CaretAssertMessage(checkUniqueInput(key, OperationParametersEnum::INT), "input integer parameter created with previously used key"); m_paramList.push_back(new IntegerParameter(key, name, description)); } void ParameterComponent::addLabelParameter(const int32_t key, const AString& name, const AString& description) { CaretAssertMessage(checkUniqueInput(key, OperationParametersEnum::LABEL), "input label parameter created with previously used key"); m_paramList.push_back(new LabelParameter(key, name, description)); } void ParameterComponent::addStringParameter(const int32_t key, const AString& name, const AString& description) { CaretAssertMessage(checkUniqueInput(key, OperationParametersEnum::STRING), "input string parameter created with previously used key"); m_paramList.push_back(new StringParameter(key, name, description)); } void ParameterComponent::addSurfaceParameter(const int32_t key, const AString& name, const AString& description) { CaretAssertMessage(checkUniqueInput(key, OperationParametersEnum::SURFACE), "input surface parameter created with previously used key"); m_paramList.push_back(new SurfaceParameter(key, name, description)); } void ParameterComponent::addVolumeParameter(const int32_t key, const AString& name, const AString& description) { CaretAssertMessage(checkUniqueInput(key, OperationParametersEnum::VOLUME), "input volume parameter created with previously used key"); m_paramList.push_back(new VolumeParameter(key, name, description)); } void OperationParameters::setHelpText(const AString& textIn) { m_helpText = textIn; } void ParameterComponent::addCiftiOutputParameter(const int32_t key, const AString& name, const AString& description) { CaretAssertMessage(checkUniqueOutput(key, OperationParametersEnum::CIFTI), "output cifti parameter created with previously used key"); m_outputList.push_back(new CiftiParameter(key, name, description)); } void ParameterComponent::addFociOutputParameter(const int32_t key, const AString& name, const AString& description) { CaretAssertMessage(checkUniqueOutput(key, OperationParametersEnum::FOCI), "output foci parameter created with previously used key"); m_outputList.push_back(new FociParameter(key, name, description)); } void ParameterComponent::addBorderOutputParameter(const int32_t key, const AString& name, const AString& description) { CaretAssertMessage(checkUniqueOutput(key, OperationParametersEnum::BORDER), "output foci parameter created with previously used key"); m_outputList.push_back(new BorderParameter(key, name, description)); } void ParameterComponent::addMetricOutputParameter(const int32_t key, const AString& name, const AString& description) { CaretAssertMessage(checkUniqueOutput(key, OperationParametersEnum::METRIC), "output metric parameter created with previously used key"); m_outputList.push_back(new MetricParameter(key, name, description)); } void ParameterComponent::addLabelOutputParameter(const int32_t key, const AString& name, const AString& description) { CaretAssertMessage(checkUniqueOutput(key, OperationParametersEnum::LABEL), "output label parameter created with previously used key"); m_outputList.push_back(new LabelParameter(key, name, description)); } void ParameterComponent::addSurfaceOutputParameter(const int32_t key, const AString& name, const AString& description) { CaretAssertMessage(checkUniqueOutput(key, OperationParametersEnum::SURFACE), "output surface parameter created with previously used key"); m_outputList.push_back(new SurfaceParameter(key, name, description)); } void ParameterComponent::addVolumeOutputParameter(const int32_t key, const AString& name, const AString& description) { CaretAssertMessage(checkUniqueOutput(key, OperationParametersEnum::VOLUME), "output volume parameter created with previously used key"); m_outputList.push_back(new VolumeParameter(key, name, description)); } AString& OperationParameters::getHelpText() { return m_helpText; } AbstractParameter::~AbstractParameter() { } bool ParameterComponent::getBoolean(const int32_t key) { return ((BooleanParameter*)getInputParameter(key, OperationParametersEnum::BOOL))->m_parameter; } CiftiFile* ParameterComponent::getCifti(const int32_t key) { return ((CiftiParameter*)getInputParameter(key, OperationParametersEnum::CIFTI))->m_parameter.getPointer(); } FociFile* ParameterComponent::getFoci(const int32_t key) { return ((FociParameter*)getInputParameter(key, OperationParametersEnum::FOCI))->m_parameter.getPointer(); } BorderFile* ParameterComponent::getBorder(const int32_t key) { return ((BorderParameter*)getInputParameter(key, OperationParametersEnum::BORDER))->m_parameter.getPointer(); } double ParameterComponent::getDouble(const int32_t key) { return ((DoubleParameter*)getInputParameter(key, OperationParametersEnum::DOUBLE))->m_parameter; } int64_t ParameterComponent::getInteger(const int32_t key) { return ((IntegerParameter*)getInputParameter(key, OperationParametersEnum::INT))->m_parameter; } LabelFile* ParameterComponent::getLabel(const int32_t key) { return ((LabelParameter*)getInputParameter(key, OperationParametersEnum::LABEL))->m_parameter.getPointer(); } MetricFile* ParameterComponent::getMetric(const int32_t key) { return ((MetricParameter*)getInputParameter(key, OperationParametersEnum::METRIC))->m_parameter.getPointer(); } const AString& ParameterComponent::getString(const int32_t key) { return ((StringParameter*)getInputParameter(key, OperationParametersEnum::STRING))->m_parameter; } SurfaceFile* ParameterComponent::getSurface(const int32_t key) { return ((SurfaceParameter*)getInputParameter(key, OperationParametersEnum::SURFACE))->m_parameter.getPointer(); } VolumeFile* ParameterComponent::getVolume(const int32_t key) { return ((VolumeParameter*)getInputParameter(key, OperationParametersEnum::VOLUME))->m_parameter.getPointer(); } CiftiFile* ParameterComponent::getOutputCifti(const int32_t key) { return ((CiftiParameter*)getOutputParameter(key, OperationParametersEnum::CIFTI))->m_parameter.getPointer(); } FociFile* ParameterComponent::getOutputFoci(const int32_t key) { return ((FociParameter*)getOutputParameter(key, OperationParametersEnum::FOCI))->m_parameter.getPointer(); } BorderFile* ParameterComponent::getOutputBorder(const int32_t key) { return ((BorderParameter*)getOutputParameter(key, OperationParametersEnum::BORDER))->m_parameter.getPointer(); } LabelFile* ParameterComponent::getOutputLabel(const int32_t key) { return ((LabelParameter*)getOutputParameter(key, OperationParametersEnum::LABEL))->m_parameter.getPointer(); } SurfaceFile* ParameterComponent::getOutputSurface(const int32_t key) { return ((SurfaceParameter*)getOutputParameter(key, OperationParametersEnum::SURFACE))->m_parameter.getPointer(); } VolumeFile* ParameterComponent::getOutputVolume(const int32_t key) { return ((VolumeParameter*)getOutputParameter(key, OperationParametersEnum::VOLUME))->m_parameter.getPointer(); } MetricFile* ParameterComponent::getOutputMetric(const int32_t key) { return ((MetricParameter*)getOutputParameter(key, OperationParametersEnum::METRIC))->m_parameter.getPointer(); } workbench-1.1.1/src/OperationsBase/OperationParameters.h000066400000000000000000000355211255417355300233560ustar00rootroot00000000000000#ifndef __OPERATION_PARAMETERS_H__ #define __OPERATION_PARAMETERS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AString.h" #include #include "stdint.h" #include "CaretPointer.h" #include "OperationParametersEnum.h" namespace caret { class BorderFile; class CiftiFile; class FociFile; class LabelFile; class MetricFile; class SurfaceFile; class VolumeFile; struct OptionalParameter; struct RepeatableOption; struct AbstractParameter { int32_t m_key;//identifies this parameter uniquely for this algorithm AString m_shortName, m_description; bool m_operationUsed;//check if the operation called get...() for this parameter virtual OperationParametersEnum::Enum getType() = 0; virtual AbstractParameter* cloneAbstractParameter() = 0; AbstractParameter(int32_t key, const AString& shortName, const AString& description) : m_key(key), m_shortName(shortName), m_description(description), m_operationUsed(false) { }; virtual ~AbstractParameter(); }; struct ParameterComponent {//sadly, inheriting from a friend class doesn't give you access to private members, so these are entirely public so parsers can use them std::vector m_paramList;//mandatory arguments std::vector m_outputList;//should this be a different type? input and output parameters are very similar, just pointers to files std::vector m_optionList;//optional arguments std::vector m_repeatableOptions;//repeatable options ///constructor ParameterComponent(); ///destructor virtual ~ParameterComponent(); ///copy constructor so RepeatableOption can copy its template to a new instance ParameterComponent(const ParameterComponent& rhs); //convenience methods for algorithms to use to easily specify parameters ///add a parameter to get next item as a string void addStringParameter(const int32_t key, const AString& name, const AString& description); ///get a string with a key const AString& getString(const int32_t key); ///add a parameter to get next item as a string void addBooleanParameter(const int32_t key, const AString& name, const AString& description); ///get a string with a key bool getBoolean(const int32_t key); ///add a parameter to get next item as an int32 void addIntegerParameter(const int32_t key, const AString& name, const AString& description); ///get an integer with a key int64_t getInteger(const int32_t key); ///add a parameter to get next item as a double void addDoubleParameter(const int32_t key, const AString& name, const AString& description); ///get a double with a key double getDouble(const int32_t key); ///add a parameter to get next item as a surface void addSurfaceParameter(const int32_t key, const AString& name, const AString& description); ///get a surface with a key SurfaceFile* getSurface(const int32_t key); ///add a parameter to get next item as a volume void addVolumeParameter(const int32_t key, const AString& name, const AString& description); ///get a volume with a key VolumeFile* getVolume(const int32_t key); ///add a parameter to get next item as a functional file (metric) void addMetricParameter(const int32_t key, const AString& name, const AString& description); ///get a metric with a key MetricFile* getMetric(const int32_t key); ///add a parameter to get next item as a label file void addLabelParameter(const int32_t key, const AString& name, const AString& description); ///get a label with a key LabelFile* getLabel(const int32_t key); ///add a parameter to get next item as a cifti file - TODO: make methods for different cifti types? void addCiftiParameter(const int32_t key, const AString& name, const AString& description); ///get a cifti with a key CiftiFile* getCifti(const int32_t key); ///add a parameter to get next item as a foci file void addFociParameter(const int32_t key, const AString& name, const AString& description); ///get a foci file with a key FociFile* getFoci(const int32_t key); ///add a parameter to get next item as a border file void addBorderParameter(const int32_t key, const AString& name, const AString& description); ///get a border file with a key BorderFile* getBorder(const int32_t key); ///add a parameter to get next item as a surface void addSurfaceOutputParameter(const int32_t key, const AString& name, const AString& description); ///get a surface with a key SurfaceFile* getOutputSurface(const int32_t key); ///add a parameter to get next item as a volume void addVolumeOutputParameter(const int32_t key, const AString& name, const AString& description); ///get a volume with a key VolumeFile* getOutputVolume(const int32_t key); ///add a parameter to get next item as a functional file (metric) void addMetricOutputParameter(const int32_t key, const AString& name, const AString& description); ///get a metric with a key MetricFile* getOutputMetric(const int32_t key); ///add a parameter to get next item as a label file void addLabelOutputParameter(const int32_t key, const AString& name, const AString& description); ///get a label with a key LabelFile* getOutputLabel(const int32_t key); ///add a parameter to get next item as a cifti file - TODO: make methods for different cifti types? void addCiftiOutputParameter(const int32_t key, const AString& name, const AString& description); ///get a cifti with a key CiftiFile* getOutputCifti(const int32_t key); ///add a parameter to get next item as a foci file void addFociOutputParameter(const int32_t key, const AString& name, const AString& description); ///get a foci file with a key FociFile* getOutputFoci(const int32_t key); ///add a parameter to get next item as a border file void addBorderOutputParameter(const int32_t key, const AString& name, const AString& description); ///get a border file with a key BorderFile* getOutputBorder(const int32_t key); ///convenience method to create, add, and return an optional parameter OptionalParameter* createOptionalParameter(const int32_t key, const AString& optionSwitch, const AString& description); ///convenience method to create, add, and return an optional parameter ParameterComponent* createRepeatableParameter(const int32_t key, const AString& optionSwitch, const AString& description); ///return pointer to an input parameter AbstractParameter* getInputParameter(const int32_t key, const OperationParametersEnum::Enum type); ///return pointer to an output AbstractParameter* getOutputParameter(const int32_t key, const OperationParametersEnum::Enum type); ///return pointer to an option OptionalParameter* getOptionalParameter(const int32_t key); ///return instances of a repeatable option const std::vector* getRepeatableParameterInstances(const int32_t key); ///functions to check for key/type uniqueness - used only in asserts bool checkUniqueInput(const int32_t& key, const OperationParametersEnum::Enum& type); bool checkUniqueOutput(const int32_t& key, const OperationParametersEnum::Enum& type); bool checkUniqueOption(const int32_t& key); bool checkUniqueRepeatable(const int32_t& key); ///helper for checking that all parameters have been checked by the operation, returns warning strings std::vector findUncheckedParams(const AString& contextString) const; }; struct OptionalParameter : public ParameterComponent { int32_t m_key;//uniquely identifies this option AString m_optionSwitch, m_description; bool m_present;//to be filled by parser bool m_operationUsed;//check if the operation called get...() for this parameter OptionalParameter(const OptionalParameter& rhs) ://copy constructor is used by cloning in RepeatableParameter ParameterComponent(rhs), m_key(rhs.m_key), m_optionSwitch(rhs.m_optionSwitch), m_description(rhs.m_description), m_present(false), m_operationUsed(false) { } OptionalParameter(int32_t key, const AString& optionSwitch, const AString& description) : m_key(key), m_optionSwitch(optionSwitch), m_description(description), m_present(false), m_operationUsed(false) { } private: OptionalParameter();//no default construction }; struct RepeatableOption { int32_t m_key;//uniquely identifies this option AString m_optionSwitch, m_description; ParameterComponent m_template; bool m_operationUsed;//check if the operation called get...() for this parameter std::vector m_instances;//to be filled by parser RepeatableOption(const RepeatableOption& rhs) : m_key(rhs.m_key), m_optionSwitch(rhs.m_optionSwitch), m_description(rhs.m_description), m_template(rhs.m_template), m_operationUsed(false) { } RepeatableOption(int32_t key, const AString& optionSwitch, const AString& description) : m_key(key), m_optionSwitch(optionSwitch), m_description(description), m_operationUsed(false) { } ~RepeatableOption(); }; struct OperationParameters : public ParameterComponent { AString m_helpText;//to be formatted by the parser object for display in terminal or modal window ///constructor OperationParameters(); ///set the help text of the algorithm - you DO NOT need to add newlines within paragraphs or list the parameters, or give a description of each parameter! describe ONLY what it does, plus any quirks void setHelpText(const AString& textIn); ///get the unformatted help text, without command or arguments descriptions, to be formatted by the argument parser AString& getHelpText(); }; //templates for the common cases template struct PointerTemplateParameter : public AbstractParameter { virtual OperationParametersEnum::Enum getType() { return TYPE; } virtual AbstractParameter* cloneAbstractParameter() { AbstractParameter* ret = new PointerTemplateParameter(m_key, m_shortName, m_description); return ret; } CaretPointer m_parameter;//so the GUI parser and the commandline parser don't need to do different things to delete the parameter info PointerTemplateParameter(const int32_t key, const AString& shortName, const AString& description) : AbstractParameter(key, shortName, description) {//CaretPointer self-initializes to NULL, so don't need to do anything } }; template struct PrimitiveTemplateParameter : public AbstractParameter { virtual OperationParametersEnum::Enum getType() { return TYPE; } T m_parameter; virtual AbstractParameter* cloneAbstractParameter() { PrimitiveTemplateParameter* ret = new PrimitiveTemplateParameter(m_key, m_shortName, m_description); ret->m_parameter = 0; return ret; } PrimitiveTemplateParameter(const int32_t key, const AString& shortName, const AString& description) : AbstractParameter(key, shortName, description) { m_parameter = 0; } }; struct StringParameter : public AbstractParameter { virtual OperationParametersEnum::Enum getType() { return OperationParametersEnum::STRING; } virtual AbstractParameter* cloneAbstractParameter() { AbstractParameter* ret = new StringParameter(m_key, m_shortName, m_description); return ret; } AString m_parameter; StringParameter(int32_t key, const AString& shortName, const AString& description) : AbstractParameter(key, shortName, description) {//AString self-initializes to "", so don't need to do anything } }; //some friendlier names typedef PointerTemplateParameter SurfaceParameter; typedef PointerTemplateParameter VolumeParameter; typedef PointerTemplateParameter MetricParameter; typedef PointerTemplateParameter LabelParameter; typedef PointerTemplateParameter CiftiParameter; typedef PointerTemplateParameter FociParameter; typedef PointerTemplateParameter BorderParameter; typedef PrimitiveTemplateParameter DoubleParameter; typedef PrimitiveTemplateParameter IntegerParameter; typedef PrimitiveTemplateParameter BooleanParameter; } #endif //__OPERATION_PARAMETERS_H__ workbench-1.1.1/src/OperationsBase/OperationParametersEnum.cxx000066400000000000000000000257671255417355300245710ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "OperationParametersEnum.h" #include "CaretAssert.h" using namespace caret; std::vector OperationParametersEnum::enumData; bool OperationParametersEnum::initializedFlag = false; /** * \class AlgorithmParametersEnum * \brief enum for parameter types * * enum for parameter types */ /** * Constructor. * * @param enumValue * An enumerated value. * @param integerCode * Integer code for this enumerated value. * * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ OperationParametersEnum::OperationParametersEnum(const Enum enumValue, const int32_t integerCode, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCode; this->name = name; this->guiName = guiName; } /** * Destructor. */ OperationParametersEnum::~OperationParametersEnum() { } /** * Initialize the enumerated metadata. */ void OperationParametersEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(OperationParametersEnum(SURFACE, 0, "Surface File", "Surface")); enumData.push_back(OperationParametersEnum(VOLUME, 1, "Volume File", "Volume")); enumData.push_back(OperationParametersEnum(METRIC, 2, "Metric File", "Metric")); enumData.push_back(OperationParametersEnum(LABEL, 3, "Label File", "Label")); enumData.push_back(OperationParametersEnum(CIFTI, 4, "Cifti File", "Cifti")); enumData.push_back(OperationParametersEnum(FOCI, 5, "Foci File", "Foci File")); enumData.push_back(OperationParametersEnum(BORDER, 6, "Border File", "Border File")); enumData.push_back(OperationParametersEnum(DOUBLE, 7, "Floating Point", "Floating Point")); enumData.push_back(OperationParametersEnum(INT, 8, "Integer", "Integer")); enumData.push_back(OperationParametersEnum(STRING, 9, "String", "String")); enumData.push_back(OperationParametersEnum(BOOL, 10, "Boolean", "Boolean")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const OperationParametersEnum* OperationParametersEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const OperationParametersEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString OperationParametersEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const OperationParametersEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ OperationParametersEnum::Enum OperationParametersEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = SURFACE; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const OperationParametersEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type AlgorithmParametersEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString OperationParametersEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const OperationParametersEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ OperationParametersEnum::Enum OperationParametersEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = SURFACE; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const OperationParametersEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type AlgorithmParametersEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t OperationParametersEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const OperationParametersEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ OperationParametersEnum::Enum OperationParametersEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = SURFACE; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const OperationParametersEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type AlgorithmParametersEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void OperationParametersEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void OperationParametersEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(OperationParametersEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void OperationParametersEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(OperationParametersEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } workbench-1.1.1/src/OperationsBase/OperationParametersEnum.h000066400000000000000000000055701255417355300242040ustar00rootroot00000000000000#ifndef __OPERATION_PARAMETERS_ENUM__H_ #define __OPERATION_PARAMETERS_ENUM__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class OperationParametersEnum { public: /** * Enumerated values. */ enum Enum { SURFACE, VOLUME, METRIC, LABEL, CIFTI, FOCI, BORDER, DOUBLE, INT, STRING, BOOL }; ~OperationParametersEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); private: OperationParametersEnum(const Enum enumValue, const int32_t integerCode, const AString& name, const AString& guiName); static const OperationParametersEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; } // namespace #endif //__OPERATION_PARAMETERS_ENUM__H_ workbench-1.1.1/src/Palette/000077500000000000000000000000001255417355300156735ustar00rootroot00000000000000workbench-1.1.1/src/Palette/CMakeLists.txt000066400000000000000000000012701255417355300204330ustar00rootroot00000000000000# # The NIFTI Project # project (Palette) # # Need XML from Qt # SET(QT_DONT_USE_QTGUI) # # Add QT for includes # INCLUDE (${QT_USE_FILE}) # # Create the NIFTI library # ADD_LIBRARY(Palette Palette.h PaletteColorMapping.h PaletteColorMappingSaxReader.h PaletteColorMappingXmlElements.h PaletteEnums.h PaletteNormalizationModeEnum.h PaletteScalarAndColor.h PaletteThresholdRangeModeEnum.h Palette.cxx PaletteColorMapping.cxx PaletteColorMappingSaxReader.cxx PaletteEnums.cxx PaletteNormalizationModeEnum.cxx PaletteScalarAndColor.cxx PaletteThresholdRangeModeEnum.cxx ) # # Find Headers # INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/Palette ${CMAKE_SOURCE_DIR}/Common ${CMAKE_SOURCE_DIR}/Xml ) workbench-1.1.1/src/Palette/Palette.cxx000066400000000000000000000360651255417355300200270ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretAssert.h" #define __PALETTE_DEFINE__ #include "Palette.h" #undef __PALETTE_DEFINE__ #include "PaletteScalarAndColor.h" using namespace caret; /** * Constructor. * */ Palette::Palette() : CaretObject() { this->initializeMembersPalette(); } /** * Destructor */ Palette::~Palette() { uint64_t num = this->paletteScalars.size(); for (uint64_t i = 0; i < num; i++) { delete this->paletteScalars[i]; this->paletteScalars[i] = NULL; } this->paletteScalars.clear(); } /** * Copy Constructor * @param Object that is copied. */ Palette::Palette(const Palette& o) : CaretObject(o), TracksModificationInterface() { this->initializeMembersPalette(); this->copyHelper(o); } /** * Assignment operator. */ Palette& Palette::operator=(const Palette& o) { if (this != &o) { CaretObject::operator=(o); this->copyHelper(o); }; return *this; } /** * Helps with copy constructor and assignment operator. */ void Palette::copyHelper(const Palette& o) { this->name = o.name; this->paletteScalars.clear(); uint64_t num = o.paletteScalars.size(); for (uint64_t i = 0; i < num; i++) { this->paletteScalars.push_back(new PaletteScalarAndColor(*o.paletteScalars[i])); } } void Palette::initializeMembersPalette() { this->modifiedFlag = false; this->name = ""; } /** * Get string representation for debugging. * * @return String containing info. * */ AString Palette::toString() const { AString s; s += "[name=" + this->name + ", "; uint64_t num = this->paletteScalars.size(); for (uint64_t i = 0; i < num; i++) { if (i > 0) s += ","; s += this->paletteScalars[i]->toString(); } s += "]"; return s; } /** * Get the name of the palette. * * @return - name of palette. * */ AString Palette::getName() const { return this->name; } /** * Set the name of this palette. * * @param name - new value for name. * */ void Palette::setName(const AString& name) { if (this->name != name) { this->name = name; this->setModified(); } } /** * Add a scalar and color to the palette. * * @param scalar - scalar value. * @param colorName - color name. * */ void Palette::addScalarAndColor(const float scalar, const AString& colorName) { this->paletteScalars.push_back(new PaletteScalarAndColor(scalar, colorName)); this->setModified(); } /** * Insert a scalar/color pair. * * @param psac - item to add. * @param insertAfterIndex - Insert after this index. * */ void Palette::insertScalarAndColor( const PaletteScalarAndColor& psac, const int32_t insertAfterIndex) { CaretAssertVectorIndex(this->paletteScalars, insertAfterIndex); this->paletteScalars.insert(this->paletteScalars.begin() + insertAfterIndex, new PaletteScalarAndColor(psac)); this->setModified(); } /** * Remove the scalar and color at index. * * @param index - index of scalar and color to remove. * */ void Palette::removeScalarAndColor(const int32_t indx) { CaretAssertVectorIndex(this->paletteScalars, indx); this->paletteScalars.erase(this->paletteScalars.begin() + indx); this->setModified(); } /** * * Get the minimum and maximum scalar values in this palette. * * @return two-dimensional float array with min and max values. * */ void Palette::getMinMax(float& minOut, float& maxOut) const { minOut = std::numeric_limits::max(); maxOut = -std::numeric_limits::max(); uint64_t num = this->paletteScalars.size(); for (uint64_t i = 0; i < num; i++) { const float f = this->paletteScalars[i]->getScalar(); if (f < minOut) minOut = f; if (f > maxOut) maxOut = f; } } ///** // * Get the RGBA (4) colors in the range of zero to one. // * // * @param scalar - scalar for which color is sought. // * @param interpolateColorFlag - interpolate the color between scalars. // * @return Array of 4 containing color components ranging zero to one. // * // */ //void //Palette::getPaletteColor( // const float scalarIn, // const bool interpolateColorFlagIn, // float rgbaOut[4]) const //{ // rgbaOut[0] = 0.0f; // rgbaOut[1] = 0.0f; // rgbaOut[2] = 0.0f; // rgbaOut[3] = 1.0f; // // bool interpolateColorFlag = interpolateColorFlagIn; // // float scalar = scalarIn; // if (scalar < -1.0) scalar = -1.0; // if (scalar > 1.0) scalar = 1.0; // // int numScalarColors = this->getNumberOfScalarsAndColors(); // if (numScalarColors > 0) { // // int paletteIndex = -1; // if (numScalarColors == 1) { // paletteIndex = 0; // interpolateColorFlag = false; // } // else { // if (scalar >= this->getScalarAndColor(0)->getScalar()) { // paletteIndex = 0; // interpolateColorFlag = false; // } // else if (scalar <= // this->getScalarAndColor(numScalarColors - 1)->getScalar()) { // paletteIndex = numScalarColors - 1; // interpolateColorFlag = false; // } // else { // for (int i = 1; i < numScalarColors; i++) { // const PaletteScalarAndColor* psac = this->getScalarAndColor(i); // if (scalar > psac->getScalar()) { // paletteIndex = i - 1; // break; // } // } // // /* // * Always interpolate if there are only two colors // */ // if (numScalarColors == 2) { // interpolateColorFlag = true; // } // } // } // if (paletteIndex >= 0) { // const PaletteScalarAndColor* psac = this->getScalarAndColor(paletteIndex); // psac->getColor(rgbaOut); // if (interpolateColorFlag && // (paletteIndex < (numScalarColors - 1))) { // const PaletteScalarAndColor* psacBelow = // this->getScalarAndColor(paletteIndex + 1); // float totalDiff = psac->getScalar() - psacBelow->getScalar(); // if (totalDiff != 0.0) { // float offset = scalar - psacBelow->getScalar(); // float percentAbove = offset / totalDiff; // float percentBelow = 1.0f - percentAbove; // if ( ! psacBelow->isNoneColor()) { // const float* rgbaAbove = psac->getColor(); // const float* rgbaBelow = psacBelow->getColor(); // // rgbaOut[0] = (percentAbove * rgbaAbove[0] // + percentBelow * rgbaBelow[0]); // rgbaOut[1] = (percentAbove * rgbaAbove[1] // + percentBelow * rgbaBelow[1]); // rgbaOut[2] = (percentAbove * rgbaAbove[2] // + percentBelow * rgbaBelow[2]); // } // } // } // else if (psac->isNoneColor()) { // rgbaOut[3] = 0.0f; // } // } // } //} /** * Get the RGBA (4) colors in the range of zero to one. * * @param scalar - scalar for which color is sought. * @param interpolateColorFlag - interpolate the color between scalars. * @return Array of 4 containing color components ranging zero to one. * */ void Palette::getPaletteColor( const float scalarIn, const bool interpolateColorFlagIn, float rgbaOut[4]) const { /* * When the number of colors in a palette is small, the * binary search algorithm may be slower than the linear * algorithm. It is moderately faster for large palettes * such as those from FSL with 256 colors. * * The linear search could be improved by starting at the bottom * when the data value is negative. * * N Log2(N) * 1 0 * 2 1 * 3 1.6 * 4 2 * 5 2.3 * 6 2.6 * 7 2.8 * 8 3 * 9 3.2 */ const bool doBinarySearchFlag = true; rgbaOut[0] = 0.0f; rgbaOut[1] = 0.0f; rgbaOut[2] = 0.0f; rgbaOut[3] = 1.0f; bool interpolateColorFlag = interpolateColorFlagIn; float scalar = scalarIn; if (scalar < -1.0) scalar = -1.0; if (scalar > 1.0) scalar = 1.0; int numScalarColors = this->getNumberOfScalarsAndColors(); if (numScalarColors > 0) { int32_t highDataIndex = 0; int32_t lowDataIndex = numScalarColors - 1; int32_t paletteIndex = -1; if (numScalarColors == 1) { paletteIndex = 0; interpolateColorFlag = false; } else { if (scalar >= this->getScalarAndColor(highDataIndex)->getScalar()) { paletteIndex = 0; interpolateColorFlag = false; } else if (scalar <= this->getScalarAndColor(lowDataIndex)->getScalar()) { paletteIndex = numScalarColors - 1; interpolateColorFlag = false; } else if (numScalarColors == 2) { paletteIndex = 0; interpolateColorFlag = true; } else { if (doBinarySearchFlag) { /* * Binary Search * NOTE: The palette orders the scalars in DESCENDING ORDER */ int32_t binaryPaletteIndex = -1; const int32_t maximumIndex = numScalarColors - 1; bool loopFlag = true; while (loopFlag) { int32_t midIndex = (lowDataIndex + highDataIndex) / 2; if (midIndex <= 0) { binaryPaletteIndex = 0; loopFlag = false; } else if (midIndex >= maximumIndex) { binaryPaletteIndex = maximumIndex; loopFlag = false; } else { const float midScalar = this->getScalarAndColor(midIndex)->getScalar(); if (scalar <= midScalar) { const float nextScalar = this->getScalarAndColor(midIndex + 1)->getScalar(); if (scalar > nextScalar) { binaryPaletteIndex = midIndex; loopFlag = false; } else { highDataIndex = midIndex; } } else { lowDataIndex = midIndex; } } } paletteIndex = binaryPaletteIndex; } else { /* * Linear Search */ for (int32_t i = 1; i < numScalarColors; i++) { const PaletteScalarAndColor* psac = this->getScalarAndColor(i); if (scalar > psac->getScalar()) { paletteIndex = i - 1; break; } } } // if (paletteIndex != binaryPaletteIndex) { // std::cout << "FAILED palette indices correct=" // << paletteIndex << " binary-index=" << binaryPaletteIndex << std::endl; // } // /* // * Always interpolate if there are only two colors // */ // if (numScalarColors == 2) { // interpolateColorFlag = true; // } } } if (paletteIndex >= 0) { const PaletteScalarAndColor* psac = this->getScalarAndColor(paletteIndex); psac->getColor(rgbaOut); if (interpolateColorFlag && (paletteIndex < (numScalarColors - 1))) { const PaletteScalarAndColor* psacBelow = this->getScalarAndColor(paletteIndex + 1); float totalDiff = psac->getScalar() - psacBelow->getScalar(); if (totalDiff != 0.0) { float offset = scalar - psacBelow->getScalar(); float percentAbove = offset / totalDiff; float percentBelow = 1.0f - percentAbove; if ( ! psacBelow->isNoneColor()) { const float* rgbaAbove = psac->getColor(); const float* rgbaBelow = psacBelow->getColor(); rgbaOut[0] = (percentAbove * rgbaAbove[0] + percentBelow * rgbaBelow[0]); rgbaOut[1] = (percentAbove * rgbaAbove[1] + percentBelow * rgbaBelow[1]); rgbaOut[2] = (percentAbove * rgbaAbove[2] + percentBelow * rgbaBelow[2]); } } } else if (psac->isNoneColor()) { rgbaOut[3] = 0.0f; } } } } /** * Set this object has been modified. * */ void Palette::setModified() { this->modifiedFlag = true; } /** * Set this object as not modified. Object should also * clear the modification status of its children. * */ void Palette::clearModified() { this->modifiedFlag = false; } /** * Get the modification status. Returns true if this object or * any of its children have been modified. * @return - The modification status. * */ bool Palette::isModified() const { return this->modifiedFlag; } workbench-1.1.1/src/Palette/Palette.h000066400000000000000000000101411255417355300174370ustar00rootroot00000000000000#ifndef __PALETTE_H__ #define __PALETTE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "CaretAssert.h" #include "CaretObject.h" #include "TracksModificationInterface.h" namespace caret { class PaletteScalarAndColor; /** * A color palette. */ class Palette : public CaretObject, TracksModificationInterface { public: Palette(); Palette(const Palette& p); Palette& operator=(const Palette& o); virtual ~Palette(); private: void copyHelper(const Palette& o); void initializeMembersPalette(); public: AString toString() const; AString getName() const; void setName(const AString& name); /** * Get the number of scalars and colors. * * @return - number of scalars and colors. * */ inline int32_t getNumberOfScalarsAndColors() const { return this->paletteScalars.size(); } /** * Get a scalar and color for the specified index. * * @param index - index of scalar and color. * @return Reference to item at index or null if invalid index. * */ inline PaletteScalarAndColor* getScalarAndColor(const int32_t indx) const { CaretAssertVectorIndex(this->paletteScalars, indx); return this->paletteScalars[indx]; } void addScalarAndColor(const float scalar, const AString& colorName); void insertScalarAndColor( const PaletteScalarAndColor& psac, const int32_t insertAfterIndex); void removeScalarAndColor(const int32_t index); void getMinMax(float& minOut, float& maxOut) const; void getPaletteColor(const float scalar, const bool interpolateColorFlag, float rgbaOut[4]) const; void setModified(); void clearModified(); bool isModified() const; public: /**Name of gray interpolate palette */ static const AString GRAY_INTERP_PALETTE_NAME; /**Name of gray interpolate palette for positive data */ static const AString GRAY_INTERP_POSITIVE_PALETTE_NAME; /**"none" color name. */ static const AString NONE_COLOR_NAME; /** "ROY-BIG-BL" palette */ static const AString ROY_BIG_BL_PALETTE_NAME; private: /**has this object been modified. (DO NOT CLONE) */ bool modifiedFlag; /**Name of the palette. */ AString name; /**The scalars in the palette. */ std::vector paletteScalars; }; #ifdef __PALETTE_DEFINE__ const AString Palette::GRAY_INTERP_PALETTE_NAME = "Gray_Interp"; const AString Palette::GRAY_INTERP_POSITIVE_PALETTE_NAME = "Gray_Interp_Positive"; //const AString Palette::NONE_COLOR_NAME = "none"; const AString Palette::ROY_BIG_BL_PALETTE_NAME = "ROY-BIG-BL"; #endif // __PALETTE_DEFINE__ } // namespace #endif // __PALETTE_H__ workbench-1.1.1/src/Palette/PaletteColorMapping.cxx000066400000000000000000001323331255417355300223350ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "CaretOMP.h" #include "FastStatistics.h" #include "MathFunctions.h" #include "NumericTextFormatting.h" //because the ROY_BIG_BL palette name is a constant defined in Palette.h #include "Palette.h" #define __PALETTE_COLOR_MAPPING_DECLARE__ #include "PaletteColorMapping.h" #undef __PALETTE_COLOR_MAPPING_DECLARE__ #include "PaletteColorMappingSaxReader.h" #include "PaletteColorMappingXmlElements.h" #include "XmlSaxParser.h" #include "XmlUtilities.h" #include "XmlWriter.h" #include using namespace caret; /** * Constructor. * */ PaletteColorMapping::PaletteColorMapping() : CaretObject() { this->initializeMembersPaletteColorMapping(); } /** * Destructor */ PaletteColorMapping::~PaletteColorMapping() { } /** * Copy Constructor * @param Object that is copied. */ PaletteColorMapping::PaletteColorMapping(const PaletteColorMapping& o) : CaretObject(o) { this->initializeMembersPaletteColorMapping(); this->copyHelper(o); } /** * Assignment operator. */ PaletteColorMapping& PaletteColorMapping::operator=(const PaletteColorMapping& o) { if (this != &o) { CaretObject::operator=(o); this->copyHelper(o); }; return *this; } /** * Copy the palette color mapping from the given palette * color mapping. * @param pcm * Color mapping that is copied to this. */ void PaletteColorMapping::copy(const PaletteColorMapping& pcm) { this->copyHelper(pcm); setModified(); } /** * Helps with copy constructor and assignment operator. */ void PaletteColorMapping::copyHelper(const PaletteColorMapping& pcm) { this->autoScalePercentageNegativeMaximum = pcm.autoScalePercentageNegativeMaximum; this->autoScalePercentageNegativeMinimum = pcm.autoScalePercentageNegativeMinimum; this->autoScalePercentagePositiveMaximum = pcm.autoScalePercentagePositiveMaximum; this->autoScalePercentagePositiveMinimum = pcm.autoScalePercentagePositiveMinimum; this->autoScaleAbsolutePercentageMaximum = pcm.autoScaleAbsolutePercentageMaximum; this->autoScaleAbsolutePercentageMinimum = pcm.autoScaleAbsolutePercentageMinimum; this->displayNegativeDataFlag = pcm.displayNegativeDataFlag; this->displayPositiveDataFlag = pcm.displayPositiveDataFlag; this->displayZeroDataFlag = pcm.displayZeroDataFlag; this->interpolatePaletteFlag = pcm.interpolatePaletteFlag; this->scaleMode = pcm.scaleMode; this->selectedPaletteName = pcm.selectedPaletteName; this->userScaleNegativeMaximum = pcm.userScaleNegativeMaximum; this->userScaleNegativeMinimum = pcm.userScaleNegativeMinimum; this->userScalePositiveMaximum = pcm.userScalePositiveMaximum; this->userScalePositiveMinimum = pcm.userScalePositiveMinimum; this->thresholdType = pcm.thresholdType; this->thresholdTest = pcm.thresholdTest; this->thresholdNormalMinimum = pcm.thresholdNormalMinimum; this->thresholdNormalMaximum = pcm.thresholdNormalMaximum; this->thresholdMappedMinimum= pcm.thresholdMappedMinimum; this->thresholdMappedMaximum = pcm.thresholdMappedMaximum; this->thresholdMappedAverageAreaMinimum = pcm.thresholdMappedAverageAreaMinimum; this->thresholdMappedAverageAreaMaximum = pcm.thresholdMappedAverageAreaMaximum; this->thresholdDataName = pcm.thresholdDataName; this->thresholdShowFailureInGreen = pcm.thresholdShowFailureInGreen; this->thresholdRangeMode = pcm.thresholdRangeMode; this->thresholdNegMinPosMaxLinked = pcm.thresholdNegMinPosMaxLinked; this->clearModified(); } /** * Equality operator. * @param pcm * Palette color mapping compared to 'this' palette color mapping. * @return * True if their members are the same. */ bool PaletteColorMapping::operator==(const PaletteColorMapping& pcm) const { if ((this->autoScalePercentageNegativeMaximum == pcm.autoScalePercentageNegativeMaximum) && (this->autoScalePercentageNegativeMinimum == pcm.autoScalePercentageNegativeMinimum) && (this->autoScalePercentagePositiveMaximum == pcm.autoScalePercentagePositiveMaximum) && (this->autoScalePercentagePositiveMinimum == pcm.autoScalePercentagePositiveMinimum) && (this->autoScaleAbsolutePercentageMaximum == pcm.autoScaleAbsolutePercentageMaximum) && (this->autoScaleAbsolutePercentageMinimum == pcm.autoScaleAbsolutePercentageMinimum) && (this->displayNegativeDataFlag == pcm.displayNegativeDataFlag) && (this->displayPositiveDataFlag == pcm.displayPositiveDataFlag) && (this->displayZeroDataFlag == pcm.displayZeroDataFlag) && (this->interpolatePaletteFlag == pcm.interpolatePaletteFlag) && (this->scaleMode == pcm.scaleMode) && (this->selectedPaletteName == pcm.selectedPaletteName) && (this->userScaleNegativeMaximum == pcm.userScaleNegativeMaximum) && (this->userScaleNegativeMinimum == pcm.userScaleNegativeMinimum) && (this->userScalePositiveMaximum == pcm.userScalePositiveMaximum) && (this->userScalePositiveMinimum == pcm.userScalePositiveMinimum) && (this->thresholdType == pcm.thresholdType) && (this->thresholdTest == pcm.thresholdTest) && (this->thresholdNormalMinimum == pcm.thresholdNormalMinimum) && (this->thresholdNormalMaximum == pcm.thresholdNormalMaximum) && (this->thresholdMappedMinimum== pcm.thresholdMappedMinimum) && (this->thresholdMappedMaximum == pcm.thresholdMappedMaximum) && (this->thresholdMappedAverageAreaMinimum == pcm.thresholdMappedAverageAreaMinimum) && (this->thresholdMappedAverageAreaMaximum == pcm.thresholdMappedAverageAreaMaximum) && (this->thresholdDataName == pcm.thresholdDataName) && (this->thresholdShowFailureInGreen == pcm.thresholdShowFailureInGreen) && (this->thresholdRangeMode == pcm.thresholdRangeMode) && (this->thresholdNegMinPosMaxLinked == pcm.thresholdNegMinPosMaxLinked)) { return true; } return false; } void PaletteColorMapping::initializeMembersPaletteColorMapping() { this->scaleMode = PaletteScaleModeEnum::MODE_AUTO_SCALE_PERCENTAGE; this->autoScalePercentageNegativeMaximum = 98.0f; this->autoScalePercentageNegativeMinimum = 2.0f; this->autoScalePercentagePositiveMinimum = 2.0f; this->autoScalePercentagePositiveMaximum = 98.0f; this->autoScaleAbsolutePercentageMaximum = 98.0f; this->autoScaleAbsolutePercentageMinimum = 2.0f; this->userScaleNegativeMaximum = -100.0f; this->userScaleNegativeMinimum = 0.0f; this->userScalePositiveMinimum = 0.0f; this->userScalePositiveMaximum = 100.0f; this->selectedPaletteName = Palette::ROY_BIG_BL_PALETTE_NAME; this->interpolatePaletteFlag = true; this->displayPositiveDataFlag = true; this->displayZeroDataFlag = false; this->displayNegativeDataFlag = true; this->thresholdType = PaletteThresholdTypeEnum::THRESHOLD_TYPE_OFF; this->thresholdTest = PaletteThresholdTestEnum::THRESHOLD_TEST_SHOW_OUTSIDE; this->thresholdNormalMinimum = -1.0f; this->thresholdNormalMaximum = 1.0f; this->thresholdMappedMinimum = -1.0f; this->thresholdMappedMaximum = 1.0f; this->thresholdMappedAverageAreaMinimum = -1.0f; this->thresholdMappedAverageAreaMaximum = 1.0f; this->thresholdDataName = ""; this->thresholdShowFailureInGreen = false; this->thresholdRangeMode = PaletteThresholdRangeModeEnum::PALETTE_THRESHOLD_RANGE_MODE_MAP; this->thresholdNegMinPosMaxLinked = false; this->modifiedFlag = false; } /** * Write the object as XML. * @param xmlWriter - write to this-> * @throws XmlException - If an error occurs. * */ void PaletteColorMapping::writeAsXML(XmlWriter& xmlWriter) { XmlAttributes attributes; attributes.addAttribute( PaletteColorMappingXmlElements::XML_ATTRIBUTE_VERSION_NUMBER, PaletteColorMappingXmlElements::XML_VERSION_NUMBER); xmlWriter.writeStartElement( PaletteColorMappingXmlElements::XML_TAG_PALETTE_COLOR_MAPPING, attributes); xmlWriter.writeElementCharacters(PaletteColorMappingXmlElements::XML_TAG_SCALE_MODE, PaletteScaleModeEnum::toName(this->scaleMode)); float autoScaleValues[4] = { this->autoScalePercentageNegativeMaximum, this->autoScalePercentageNegativeMinimum, this->autoScalePercentagePositiveMinimum, this->autoScalePercentagePositiveMaximum }; xmlWriter.writeElementCharacters( PaletteColorMappingXmlElements::XML_TAG_AUTO_SCALE_PERCENTAGE_VALUES, autoScaleValues, 4); float autoScaleAbsolutePercentageValues[2] = { this->autoScaleAbsolutePercentageMinimum, this->autoScaleAbsolutePercentageMaximum }; xmlWriter.writeElementCharacters(PaletteColorMappingXmlElements::XML_TAG_AUTO_SCALE_ABSOLUTE_PERCENTAGE_VALUES, autoScaleAbsolutePercentageValues, 2); float userScaleValues[4] = { this->userScaleNegativeMaximum, this->userScaleNegativeMinimum, this->userScalePositiveMinimum, this->userScalePositiveMaximum }; xmlWriter.writeElementCharacters( PaletteColorMappingXmlElements::XML_TAG_USER_SCALE_VALUES, userScaleValues, 4); xmlWriter.writeElementCharacters(PaletteColorMappingXmlElements::XML_TAG_PALETTE_NAME, XmlUtilities::encodeXmlSpecialCharacters(this->selectedPaletteName)); xmlWriter.writeElementCharacters( PaletteColorMappingXmlElements::XML_TAG_INTERPOLATE, this->interpolatePaletteFlag); xmlWriter.writeElementCharacters( PaletteColorMappingXmlElements::XML_TAG_DISPLAY_POSITIVE, this->displayPositiveDataFlag); xmlWriter.writeElementCharacters( PaletteColorMappingXmlElements::XML_TAG_DISPLAY_ZERO, this->displayZeroDataFlag); xmlWriter.writeElementCharacters( PaletteColorMappingXmlElements::XML_TAG_DISPLAY_NEGATIVE, this->displayNegativeDataFlag); xmlWriter.writeElementCharacters( PaletteColorMappingXmlElements::XML_TAG_THRESHOLD_TEST, PaletteThresholdTestEnum::toName(this->thresholdTest)); xmlWriter.writeElementCharacters( PaletteColorMappingXmlElements::XML_TAG_THRESHOLD_TYPE, PaletteThresholdTypeEnum::toName(this->thresholdType)); xmlWriter.writeElementCharacters( PaletteColorMappingXmlElements::XML_TAG_THRESHOLD_FAILURE_IN_GREEN, this->thresholdShowFailureInGreen); float normalValues[2] = { this->thresholdNormalMinimum, this->thresholdNormalMaximum }; xmlWriter.writeElementCharacters( PaletteColorMappingXmlElements::XML_TAG_THRESHOLD_NORMAL_VALUES, normalValues, 2); float mappedValues[2] = { this->thresholdMappedMinimum, this->thresholdMappedMaximum }; xmlWriter.writeElementCharacters( PaletteColorMappingXmlElements::XML_TAG_THRESHOLD_MAPPED_VALUES, mappedValues, 2); float mappedAvgAreaValues[2] = { this->thresholdMappedAverageAreaMinimum, this->thresholdMappedAverageAreaMaximum }; xmlWriter.writeElementCharacters( PaletteColorMappingXmlElements::XML_TAG_THRESHOLD_MAPPED_AVG_AREA_VALUES, mappedAvgAreaValues, 2); xmlWriter.writeElementCharacters( PaletteColorMappingXmlElements::XML_TAG_THRESHOLD_DATA_NAME, XmlUtilities::encodeXmlSpecialCharacters(this->thresholdDataName)); xmlWriter.writeElementCharacters(PaletteColorMappingXmlElements::XML_TAG_THRESHOLD_RANGE_MODE, PaletteThresholdRangeModeEnum::toName(this->thresholdRangeMode)); xmlWriter.writeElementCharacters(PaletteColorMappingXmlElements::XML_TAG_THRESHOLD_NEG_MIN_POS_MAX_LINKED, this->thresholdNegMinPosMaxLinked); xmlWriter.writeEndElement(); } /** * Returns the XML representation of this object in a String. * * @return String containing XML. * @throws XmlException if an error occurs. * */ AString PaletteColorMapping::encodeInXML() { std::ostringstream str; XmlWriter xmlWriter(str); this->writeAsXML(xmlWriter); AString s = AString::fromStdString(str.str()); return s; } /** * Decode this object from a String containing XML. * @param xml - String containing XML. * @throws XmlException If an error occurs. * */ void PaletteColorMapping::decodeFromStringXML(const AString& xml) { PaletteColorMappingSaxReader saxReader(this); XmlSaxParser* parser = XmlSaxParser::createXmlParser(); try { parser->parseString(xml, &saxReader); } catch (XmlSaxParserException& e) { int lineNum = e.getLineNumber(); int colNum = e.getColumnNumber(); std::ostringstream str; str << "Parse Error while reading PaletteColorMapping XML"; if ((lineNum >= 0) && (colNum >= 0)) { str << " line/col (" << e.getLineNumber() << "/" << e.getColumnNumber() << ")"; } str << ": " << e.whatString().toStdString(); throw XmlException(AString::fromStdString(str.str())); } delete parser; } /** * Get auto scale percentage negative maximum. * @return Its value. * */ float PaletteColorMapping::getAutoScalePercentageNegativeMaximum() const { return autoScalePercentageNegativeMaximum; } /** * Set auto scale percentage negative maximum. * @param autoScalePercentageNegativeMaximum - new value. * */ void PaletteColorMapping::setAutoScalePercentageNegativeMaximum(const float autoScalePercentageNegativeMaximum) { if (this->autoScalePercentageNegativeMaximum != autoScalePercentageNegativeMaximum) { this->autoScalePercentageNegativeMaximum = autoScalePercentageNegativeMaximum; this->setModified(); } } /** * Get auto scale percentage negative minimum. * @return Its value. * */ float PaletteColorMapping::getAutoScalePercentageNegativeMinimum() const { return autoScalePercentageNegativeMinimum; } /** * Set auto scale percentage negative minimum. * @param autoScalePercentageNegativeMinimum - new value. * */ void PaletteColorMapping::setAutoScalePercentageNegativeMinimum(const float autoScalePercentageNegativeMinimum) { if (this->autoScalePercentageNegativeMinimum != autoScalePercentageNegativeMinimum) { this->autoScalePercentageNegativeMinimum = autoScalePercentageNegativeMinimum; this->setModified(); } } /** * Get auto scale percentage positive maximum. * @return Its value. * */ float PaletteColorMapping::getAutoScalePercentagePositiveMaximum() const { return autoScalePercentagePositiveMaximum; } /** * Set auto scale percentage positive maximum. * @param autoScalePercentagePositiveMaximum - new value. * */ void PaletteColorMapping::setAutoScalePercentagePositiveMaximum(const float autoScalePercentagePositiveMaximum) { if (this->autoScalePercentagePositiveMaximum != autoScalePercentagePositiveMaximum) { this->autoScalePercentagePositiveMaximum = autoScalePercentagePositiveMaximum; this->setModified(); } } /** * Get auto scale percentage positive minimum. * @return Its value. * */ float PaletteColorMapping::getAutoScalePercentagePositiveMinimum() const { return autoScalePercentagePositiveMinimum; } /** * Set auto scale percentage positive minimum. * @param autoScalePercentagePositiveMinimum - new value. * */ void PaletteColorMapping::setAutoScalePercentagePositiveMinimum(const float autoScalePercentagePositiveMinimum) { if (this->autoScalePercentagePositiveMinimum != autoScalePercentagePositiveMinimum) { this->autoScalePercentagePositiveMinimum = autoScalePercentagePositiveMinimum; this->setModified(); } } /** * @return The auto scale absolute percentage minimum. */ float PaletteColorMapping::getAutoScaleAbsolutePercentageMinimum() const { return this->autoScaleAbsolutePercentageMinimum; } /** * Set the auto scale absolute percentage minimum. * * @param autoScaleAbsolutePercentageMinimum * New value for auto scale absolute percentage minimum. */ void PaletteColorMapping::setAutoScaleAbsolutePercentageMinimum(const float autoScaleAbsolutePercentageMinimum) { if (this->autoScaleAbsolutePercentageMinimum != autoScaleAbsolutePercentageMinimum) { this->autoScaleAbsolutePercentageMinimum = autoScaleAbsolutePercentageMinimum; this->setModified(); } } /** * @return The auto scale absolute percentage maximum. */ float PaletteColorMapping::getAutoScaleAbsolutePercentageMaximum() const { return this->autoScaleAbsolutePercentageMaximum; } /** * Set the auto scale absolute percentage maximum. * * @param autoScaleAbsolutePercentageMaximum * New value for auto scale absolute percentage maximum. */ void PaletteColorMapping::setAutoScaleAbsolutePercentageMaximum(const float autoScaleAbsolutePercentageMaximum) { if (this->autoScaleAbsolutePercentageMaximum != autoScaleAbsolutePercentageMaximum) { this->autoScaleAbsolutePercentageMaximum = autoScaleAbsolutePercentageMaximum; this->setModified(); } } /** * See if negative data should be displayed. * @return true if negative data displayed, else false. * */ bool PaletteColorMapping::isDisplayNegativeDataFlag() const { return displayNegativeDataFlag; } /** * Set negative data should be displayed. * @param displayNegativeDataFlag - true to display negative data, else false * */ void PaletteColorMapping::setDisplayNegativeDataFlag(const bool displayNegativeDataFlag) { if (this->displayNegativeDataFlag != displayNegativeDataFlag) { this->displayNegativeDataFlag = displayNegativeDataFlag; this->setModified(); } } /** * See if positive data should be displayed. * @return true if positive data displayed, else false. * */ bool PaletteColorMapping::isDisplayPositiveDataFlag() const { return displayPositiveDataFlag; } /** * Set positive data should be displayed. * @param displayPositiveDataFlag - true to display positive data, else false * */ void PaletteColorMapping::setDisplayPositiveDataFlag(const bool displayPositiveDataFlag) { if (this->displayPositiveDataFlag != displayPositiveDataFlag) { this->displayPositiveDataFlag = displayPositiveDataFlag; this->setModified(); } } /** * See if zero data should be displayed. * @return true if zero data displayed, else false. * */ bool PaletteColorMapping::isDisplayZeroDataFlag() const { return displayZeroDataFlag; } /** * Set zero data should be displayed. * @param displayZeroDataFlag - true to display zero data, else false * */ void PaletteColorMapping::setDisplayZeroDataFlag(const bool displayZeroDataFlag) { if (this->displayZeroDataFlag != displayZeroDataFlag) { this->displayZeroDataFlag = displayZeroDataFlag; this->setModified(); } } /** * Interpolate palette colors when displaying data? * @return true to interpolate data, else false. * */ bool PaletteColorMapping::isInterpolatePaletteFlag() const { return interpolatePaletteFlag; } /** * Set palette colors should be interpolated when displaying data. * @param interpolatePaletteFlag - true to interpolate, else false. * */ void PaletteColorMapping::setInterpolatePaletteFlag(const bool interpolatePaletteFlag) { if (this->interpolatePaletteFlag != interpolatePaletteFlag) { this->interpolatePaletteFlag = interpolatePaletteFlag; this->setModified(); } } /** * Get how the data is scaled to the palette. * @return Enumerated type indicating how data is scaled to the palette. * */ PaletteScaleModeEnum::Enum PaletteColorMapping::getScaleMode() const { return scaleMode; } /** * Set how the data is scaled to the palette. * @param scaleMode - Enumerated type indicating how data is scaled * to the palette. * */ void PaletteColorMapping::setScaleMode(const PaletteScaleModeEnum::Enum scaleMode) { if (this->scaleMode != scaleMode) { this->scaleMode = scaleMode; this->setModified(); } } /** * Get the name of the selected palette. * @return Name of the selected palette. * */ AString PaletteColorMapping::getSelectedPaletteName() const { return selectedPaletteName; } /** * Set the name of the selected palette. * @param selectedPaletteName - Name of selected palette. * */ void PaletteColorMapping::setSelectedPaletteName(const AString& selectedPaletteName) { if (this->selectedPaletteName != selectedPaletteName) { this->selectedPaletteName = selectedPaletteName; this->setModified(); } } /** * Set the selected palette to PSYCH. * */ void PaletteColorMapping::setSelectedPaletteToPsych() { this->setSelectedPaletteName("PSYCH"); } /** * Set the selected palette to PSYCH-NO-NONE * */ void PaletteColorMapping::setSelectedPaletteToPsychNoNone() { this->setSelectedPaletteName("PSYCH-NO-NONE"); } /** * Set the selected palette to Orange-Yellow. * */ void PaletteColorMapping::setSelectedPaletteToOrangeYellow() { this->setSelectedPaletteName("Orange-Yellow"); } /** * Set the selected palette to Gray Interpolated. * */ void PaletteColorMapping::setSelectedPaletteToGrayInterpolated() { this->setSelectedPaletteName("Gray_Interp"); } /** * Get auto user scale negative maximum. * @return Its value. * */ float PaletteColorMapping::getUserScaleNegativeMaximum() const { return userScaleNegativeMaximum; } /** * Set user scale negative maximum. * @param userScaleNegativeMaximum - new value. * */ void PaletteColorMapping::setUserScaleNegativeMaximum(const float userScaleNegativeMaximum) { if (this->userScaleNegativeMaximum != userScaleNegativeMaximum) { this->userScaleNegativeMaximum = userScaleNegativeMaximum; this->setModified(); } } /** * Get auto user scale negative minimum. * @return Its value. * */ float PaletteColorMapping::getUserScaleNegativeMinimum() const { return userScaleNegativeMinimum; } /** * Set user scale negative minimum. * @param userScaleNegativeMinimum - new value. * */ void PaletteColorMapping::setUserScaleNegativeMinimum(const float userScaleNegativeMinimum) { if (this->userScaleNegativeMinimum != userScaleNegativeMinimum) { this->userScaleNegativeMinimum = userScaleNegativeMinimum; this->setModified(); } } /** * Get auto user scale positive maximum. * @return Its value. * */ float PaletteColorMapping::getUserScalePositiveMaximum() const { return userScalePositiveMaximum; } /** * Set user scale positive maximum. * @param userScalePositiveMaximum - new value. * */ void PaletteColorMapping::setUserScalePositiveMaximum(const float userScalePositiveMaximum) { if (this->userScalePositiveMaximum != userScalePositiveMaximum) { this->userScalePositiveMaximum = userScalePositiveMaximum; this->setModified(); } } /** * Get auto user scale positive maximum. * @return Its value. * */ float PaletteColorMapping::getUserScalePositiveMinimum() const { return userScalePositiveMinimum; } /** * Set user scale positive minimum. * @param userScalePositiveMinimum - new value. * */ void PaletteColorMapping::setUserScalePositiveMinimum(const float userScalePositiveMinimum) { if (this->userScalePositiveMinimum != userScalePositiveMinimum) { this->userScalePositiveMinimum = userScalePositiveMinimum; this->setModified(); } } /** * Get the minimum threshold for the given threshold type. * * @param thresholdType * The threshold type. * @return the threshold's value. * */ float PaletteColorMapping::getThresholdMinimum(const PaletteThresholdTypeEnum::Enum thresholdType) const { float value = 0.0; switch (thresholdType) { case PaletteThresholdTypeEnum::THRESHOLD_TYPE_OFF: break; case PaletteThresholdTypeEnum::THRESHOLD_TYPE_NORMAL: value = this->thresholdNormalMinimum; break; case PaletteThresholdTypeEnum::THRESHOLD_TYPE_MAPPED: value = this->thresholdMappedMinimum; break; case PaletteThresholdTypeEnum::THRESHOLD_TYPE_MAPPED_AVERAGE_AREA: value = this->thresholdMappedAverageAreaMinimum; break; } return value; } /** * Get the minimum threshold for the given threshold type. * * @param thresholdType * The threshold type. * @return the threshold's value. * */ float PaletteColorMapping::getThresholdMaximum(const PaletteThresholdTypeEnum::Enum thresholdType) const { float value = 0.0; switch (thresholdType) { case PaletteThresholdTypeEnum::THRESHOLD_TYPE_OFF: break; case PaletteThresholdTypeEnum::THRESHOLD_TYPE_NORMAL: value = this->thresholdNormalMaximum; break; case PaletteThresholdTypeEnum::THRESHOLD_TYPE_MAPPED: value = this->thresholdMappedMaximum; break; case PaletteThresholdTypeEnum::THRESHOLD_TYPE_MAPPED_AVERAGE_AREA: value = this->thresholdMappedAverageAreaMaximum; break; } return value; } /** * Set the minimum threshold for the given threshold type. * * @param thresholdType * The threshold type. * param thresholdMinimum the threshold's new value. * */ void PaletteColorMapping::setThresholdMinimum(const PaletteThresholdTypeEnum::Enum thresholdType, const float thresholdMinimum) { switch (thresholdType) { case PaletteThresholdTypeEnum::THRESHOLD_TYPE_OFF: break; case PaletteThresholdTypeEnum::THRESHOLD_TYPE_NORMAL: setThresholdNormalMinimum(thresholdMinimum); //this->thresholdNormalMinimum = thresholdMinimum; break; case PaletteThresholdTypeEnum::THRESHOLD_TYPE_MAPPED: setThresholdMappedMinimum(thresholdMinimum); //this->thresholdMappedMinimum = thresholdMinimum; break; case PaletteThresholdTypeEnum::THRESHOLD_TYPE_MAPPED_AVERAGE_AREA: setThresholdMappedAverageAreaMinimum(thresholdMinimum); //this->thresholdMappedAverageAreaMinimum = thresholdMinimum; break; } } /** * Set the maximum threshold for the given threshold type. * * @param thresholdType * The threshold type. * param thresholdMaximum the threshold's new value. * */ void PaletteColorMapping::setThresholdMaximum(const PaletteThresholdTypeEnum::Enum thresholdType, const float thresholdMaximum) { switch (thresholdType) { case PaletteThresholdTypeEnum::THRESHOLD_TYPE_OFF: break; case PaletteThresholdTypeEnum::THRESHOLD_TYPE_NORMAL: setThresholdNormalMaximum(thresholdMaximum); //this->thresholdNormalMaximum = thresholdMaximum; break; case PaletteThresholdTypeEnum::THRESHOLD_TYPE_MAPPED: setThresholdMappedMaximum(thresholdMaximum); //this->thresholdMappedMaximum = thresholdMaximum; break; case PaletteThresholdTypeEnum::THRESHOLD_TYPE_MAPPED_AVERAGE_AREA: setThresholdMappedAverageAreaMaximum(thresholdMaximum); //this->thresholdMappedAverageAreaMaximum = thresholdMaximum; break; } } /** * Get mapped average area minimum threshold * * @return the threshold's value. * */ float PaletteColorMapping::getThresholdMappedAverageAreaMinimum() const { return thresholdMappedAverageAreaMinimum; } /** * Set the mapped average area minimum threshold. * * @param thresholdMappedAverageAreaNegative - new value. * */ void PaletteColorMapping::setThresholdMappedAverageAreaMinimum(const float thresholdMappedAverageAreaMinimum) { if (this->thresholdMappedAverageAreaMinimum != thresholdMappedAverageAreaMinimum) { this->thresholdMappedAverageAreaMinimum = thresholdMappedAverageAreaMinimum; this->setModified(); } } /** * Get mapped average area maximum threshold * * @return the threshold's value. * */ float PaletteColorMapping::getThresholdMappedAverageAreaMaximum() const { return thresholdMappedAverageAreaMaximum; } /** * Set the mapped average area maximum threshold. * * @param thresholdMappedAverageAreaPositive - new value. * */ void PaletteColorMapping::setThresholdMappedAverageAreaMaximum(const float thresholdMappedAverageAreaMaximum) { if (this->thresholdMappedAverageAreaMaximum != thresholdMappedAverageAreaMaximum) { this->thresholdMappedAverageAreaMaximum = thresholdMappedAverageAreaMaximum; this->setModified(); } } /** * Get mapped minimum threshold * * @return the threshold's value. * */ float PaletteColorMapping::getThresholdMappedMinimum() const { return thresholdMappedMinimum; } /** * Set the mapped minimum threshold. * * @param thresholdMappedNegative - new value. * */ void PaletteColorMapping::setThresholdMappedMinimum(const float thresholdMappedMinimum) { if (this->thresholdMappedMinimum != thresholdMappedMinimum) { this->thresholdMappedMinimum = thresholdMappedMinimum; this->setModified(); } } /** * Get mapped maximum threshold * * @return the threshold's value. * */ float PaletteColorMapping::getThresholdMappedMaximum() const { return thresholdMappedMaximum; } /** * Set the mapped maximum threshold. * * @param thresholdMappedPositive - new value. * */ void PaletteColorMapping::setThresholdMappedMaximum(const float thresholdMappedMaximum) { if (this->thresholdMappedMaximum != thresholdMappedMaximum) { this->thresholdMappedMaximum = thresholdMappedMaximum; this->setModified(); } } /** * Get normal minimum threshold * * @return the threshold's value. * */ float PaletteColorMapping::getThresholdNormalMinimum() const { return thresholdNormalMinimum; } /** * Set the normal minimum threshold. * * @param thresholdNormalNegative - new value. * */ void PaletteColorMapping::setThresholdNormalMinimum(const float thresholdNormalMinimum) { if (this->thresholdNormalMinimum != thresholdNormalMinimum) { this->thresholdNormalMinimum = thresholdNormalMinimum; this->setModified(); } } /** * Get normal maximum threshold * * @return the threshold's value. * */ float PaletteColorMapping::getThresholdNormalMaximum() const { return thresholdNormalMaximum; } /** * Set the normal maximum threshold. * * @param thresholdNormalPositive - new value. * */ void PaletteColorMapping::setThresholdNormalMaximum(const float thresholdNormalMaximum) { if (this->thresholdNormalMaximum != thresholdNormalMaximum) { this->thresholdNormalMaximum = thresholdNormalMaximum; this->setModified(); } } /** * Get the threshold test. * @return Threshold test. * */ PaletteThresholdTestEnum::Enum PaletteColorMapping::getThresholdTest() const { return thresholdTest; } /** * Set the threshold test. * @param thresholdTest - The threshold test. * */ void PaletteColorMapping::setThresholdTest(const PaletteThresholdTestEnum::Enum thresholdTest) { if (this->thresholdTest != thresholdTest) { this->thresholdTest = thresholdTest; this->setModified(); } } /** * Get the threshold type. * @return Threshold type. * */ PaletteThresholdTypeEnum::Enum PaletteColorMapping::getThresholdType() const { return thresholdType; } /** * Set the threshold type. * * @param thresholdType - The threshold type. */ void PaletteColorMapping::setThresholdType(const PaletteThresholdTypeEnum::Enum thresholdType) { if (this->thresholdType != thresholdType) { this->thresholdType = thresholdType; this->setModified(); } } /** * @return The threshold range mode. */ PaletteThresholdRangeModeEnum::Enum PaletteColorMapping::getThresholdRangeMode() const { return this->thresholdRangeMode; } /** * Set the threshold range mode. * * @param rangeMode * New value for range mode. */ void PaletteColorMapping::setThresholdRangeMode(const PaletteThresholdRangeModeEnum::Enum rangeMode) { if (this->thresholdRangeMode != rangeMode) { this->thresholdRangeMode = rangeMode; setModified(); } } /** * Get the name of the threshold data which may be the name of a * functional volume or a metric column. * @return Name of data used as a threshold. * */ AString PaletteColorMapping::getThresholdDataName() const { return thresholdDataName; } /** * Set the name of the threshold data which may be the name of a * functional volume or a metric column. * @param thresholdDataName - name of data used as a threshold. * */ void PaletteColorMapping::setThresholdDataName(const AString& thresholdDataName) { if (this->thresholdDataName != thresholdDataName) { this->thresholdDataName = thresholdDataName; this->setModified(); } } /** * Display non-zero data that fails the threshold test in green? * @return true if displayed, else false. * */ bool PaletteColorMapping::isShowThresholdFailureInGreen() const { return this->thresholdShowFailureInGreen; } /** * Set display non-zero data that fails the threshold test in green? * @param showInGreenFlag - true if displayed, else false. * */ void PaletteColorMapping::setShowThresholdFailureInGreen(const bool showInGreenFlag) { if (this->thresholdShowFailureInGreen != showInGreenFlag) { this->thresholdShowFailureInGreen = showInGreenFlag; setModified(); } } /** * Set this object has been modified. * */ void PaletteColorMapping::setModified() { this->modifiedFlag = true; } /** * Set this object as not modified. Object should also * clear the modification status of its children. * */ void PaletteColorMapping::clearModified() { this->modifiedFlag = false; } /** * Get the modification status. Returns true if this object * or any of its children have been modified. * @return - The modification status. * */ bool PaletteColorMapping::isModified() const { return this->modifiedFlag; } /** * Map data values to palette normalized values using the * settings in this palette color mapping. * * @param statistics * Statistics containing min.max values. * @param data * The data values. * @param normalizedValuesOut * Result of mapping data values to palette normalized * values which range [-1.0, 1.0]. This array MUST contain * the same number of values as 'data'. * @param numberOfData * Number of values in both data and normalizedValuesOut. */ void PaletteColorMapping::mapDataToPaletteNormalizedValues(const FastStatistics* statistics, const float* dataValues, float* normalizedValuesOut, const int64_t numberOfData) const { if (numberOfData <= 0) { return; } /* * Minimum and maximum values used when mapping scalar into color palette. */ float mappingMostNegative = 0.0; float mappingLeastNegative = 0.0; float mappingLeastPositive = 0.0; float mappingMostPositive = 0.0; switch (this->getScaleMode()) { case PaletteScaleModeEnum::MODE_AUTO_SCALE: statistics->getNonzeroRanges(mappingMostNegative, mappingLeastNegative, mappingLeastPositive, mappingMostPositive); break; case PaletteScaleModeEnum::MODE_AUTO_SCALE_ABSOLUTE_PERCENTAGE: { const float mostPercentage = this->getAutoScaleAbsolutePercentageMaximum(); const float leastPercentage = this->getAutoScaleAbsolutePercentageMinimum(); mappingMostNegative = -statistics->getApproxAbsolutePercentile(mostPercentage); mappingLeastNegative = -statistics->getApproxAbsolutePercentile(leastPercentage); mappingLeastPositive = statistics->getApproxAbsolutePercentile(leastPercentage); mappingMostPositive = statistics->getApproxAbsolutePercentile(mostPercentage); } break; case PaletteScaleModeEnum::MODE_AUTO_SCALE_PERCENTAGE: { const float mostNegativePercentage = this->getAutoScalePercentageNegativeMaximum(); const float leastNegativePercentage = this->getAutoScalePercentageNegativeMinimum(); const float leastPositivePercentage = this->getAutoScalePercentagePositiveMinimum(); const float mostPositivePercentage = this->getAutoScalePercentagePositiveMaximum(); mappingMostNegative = statistics->getApproxNegativePercentile(mostNegativePercentage); mappingLeastNegative = statistics->getApproxNegativePercentile(leastNegativePercentage); mappingLeastPositive = statistics->getApproxPositivePercentile(leastPositivePercentage); mappingMostPositive = statistics->getApproxPositivePercentile(mostPositivePercentage); } break; case PaletteScaleModeEnum::MODE_USER_SCALE: mappingMostNegative = this->getUserScaleNegativeMaximum(); mappingLeastNegative = this->getUserScaleNegativeMinimum(); mappingLeastPositive = this->getUserScalePositiveMinimum(); mappingMostPositive = this->getUserScalePositiveMaximum(); break; } float mappingPositiveDenominator = std::fabs(mappingMostPositive - mappingLeastPositive) * (1.0f - SMALL_POSITIVE);//if the "zero" color is extended to more than exact zeros, this correction prevents normalization from returning something greater than 1 if (mappingPositiveDenominator == 0.0) { mappingPositiveDenominator = 1.0; } float mappingNegativeDenominator = std::fabs(mappingMostNegative - mappingLeastNegative) * (1.0f + SMALL_NEGATIVE);//ditto, but SMALL_NEGATIVE is negative if (mappingNegativeDenominator == 0.0) { mappingNegativeDenominator = 1.0; } for (int64_t i = 0; i < numberOfData; i++) { float scalar = dataValues[i]; /* * Color scalar using palette */ float normalized = 0.0f; if (scalar > 0.0) { if (scalar >= mappingMostPositive) { normalized = 1.0f; } else if (scalar >= mappingLeastPositive) { float numerator = scalar - mappingLeastPositive; normalized = numerator / mappingPositiveDenominator + SMALL_POSITIVE; // JWH 24 April 2015 0.00001f;//don't return less than 0.00001f if input is positive } else { normalized = SMALL_POSITIVE; // JWH 24 April 2015 0.00001f; } } else if (scalar < 0.0) { if (scalar <= mappingMostNegative) { normalized = -1.0f; } else if (scalar <= mappingLeastNegative) { float numerator = scalar - mappingLeastNegative; normalized = numerator / mappingNegativeDenominator + SMALL_NEGATIVE; // JWH 24 April 2015 - 0.00001f; } else { normalized = SMALL_NEGATIVE; // JWH 24 April 2015 -0.00001f; } } normalizedValuesOut[i] = normalized; } } /** * Get the text characters for drawing the scale above the palette * color bar. * * @param statistics * Statistics for the data. * @param minimumValueTextOut * Text for the minimum value. * @param zeroValueTextOut * Text for the zero value(s) * @param maximumValueTextOut * Text for the maximum value. * */ void PaletteColorMapping::getPaletteColorBarScaleText(const FastStatistics* statistics, AString& minimumValueTextOut, AString& zeroValueTextOut, AString& maximumValueTextOut) const { minimumValueTextOut = ""; zeroValueTextOut = ""; maximumValueTextOut = ""; float minMax[4] = { -1.0, 0.0, 0.0, 1.0 }; switch (getScaleMode()) { case PaletteScaleModeEnum::MODE_AUTO_SCALE: { float dummy; statistics->getNonzeroRanges(minMax[0], dummy, dummy, minMax[3]); } break; case PaletteScaleModeEnum::MODE_AUTO_SCALE_ABSOLUTE_PERCENTAGE: { const float maxPct = getAutoScaleAbsolutePercentageMaximum(); const float minPct = getAutoScaleAbsolutePercentageMinimum(); minMax[0] = -statistics->getApproxAbsolutePercentile(maxPct); minMax[1] = -statistics->getApproxAbsolutePercentile(minPct); minMax[2] = statistics->getApproxAbsolutePercentile(minPct); minMax[3] = statistics->getApproxAbsolutePercentile(maxPct); } break; case PaletteScaleModeEnum::MODE_AUTO_SCALE_PERCENTAGE: { const float negMaxPct = getAutoScalePercentageNegativeMaximum(); const float negMinPct = getAutoScalePercentageNegativeMinimum(); const float posMinPct = getAutoScalePercentagePositiveMinimum(); const float posMaxPct = getAutoScalePercentagePositiveMaximum(); minMax[0] = statistics->getApproxNegativePercentile(negMaxPct); minMax[1] = statistics->getApproxNegativePercentile(negMinPct); minMax[2] = statistics->getApproxPositivePercentile(posMinPct); minMax[3] = statistics->getApproxPositivePercentile(posMaxPct); } break; case PaletteScaleModeEnum::MODE_USER_SCALE: minMax[0] = getUserScaleNegativeMaximum(); minMax[1] = getUserScaleNegativeMinimum(); minMax[2] = getUserScalePositiveMinimum(); minMax[3] = getUserScalePositiveMaximum(); break; } AString minMaxValueText[4]; // NumericTextFormatting::formatValueRange(minMax, // minMaxValueText, // 4); NumericTextFormatting::formatValueRangeNegativeAndPositive(minMax, minMaxValueText); /* * Types of values for display */ const bool isPositiveDisplayed = isDisplayPositiveDataFlag(); const bool isNegativeDisplayed = isDisplayNegativeDataFlag(); minimumValueTextOut = minMaxValueText[0]; AString textCenterNeg = minMaxValueText[1]; AString textCenterPos = minMaxValueText[2]; AString textCenter = textCenterPos; if (isNegativeDisplayed && isPositiveDisplayed) { if (textCenterNeg != textCenterPos) { zeroValueTextOut = textCenterNeg + "/" + textCenterPos; } else { zeroValueTextOut = textCenterPos; } } else if (isNegativeDisplayed) { zeroValueTextOut = textCenterNeg; } else if (isPositiveDisplayed) { zeroValueTextOut = textCenterPos; } maximumValueTextOut = minMaxValueText[3]; } /** * @return True if thresholding is linked meaning * that the high threshold is restricted to a positive value * the low threshold = -high. * * This is just a status and it is up to the user of this class * to properly set the threshold values. */ bool PaletteColorMapping::isThresholdNegMinPosMaxLinked() const { return this->thresholdNegMinPosMaxLinked; } /** * Set thresholding is linked meaning * that the high threshold is restricted to a positive value * the low threshold = -high. * * This is just a status and it is up to the user of this class * to properly set the threshold values. * * @param linked * New status of low/high linked thresholding. */ void PaletteColorMapping::setThresholdNegMinPosMaxLinked(const bool linked) { if (this->thresholdNegMinPosMaxLinked != linked) { this->thresholdNegMinPosMaxLinked = linked; setModified(); } } workbench-1.1.1/src/Palette/PaletteColorMapping.h000066400000000000000000000234751255417355300217700ustar00rootroot00000000000000#ifndef __PALETTECOLORMAPPING_H__ #define __PALETTECOLORMAPPING_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretObject.h" #include "PaletteEnums.h" #include "PaletteThresholdRangeModeEnum.h" #include "XmlException.h" namespace caret { class FastStatistics; class XmlWriter; /** * Controls color mapping using a palette. */ class PaletteColorMapping : public CaretObject { public: PaletteColorMapping(); PaletteColorMapping(const PaletteColorMapping& o); PaletteColorMapping& operator=(const PaletteColorMapping& o); bool operator==(const PaletteColorMapping& pcm) const; bool operator!=(const PaletteColorMapping& pcm) const { return !((*this) == pcm); } virtual ~PaletteColorMapping(); void copy(const PaletteColorMapping& pcm); private: void copyHelper(const PaletteColorMapping& o); void initializeMembersPaletteColorMapping(); public: void writeAsXML(XmlWriter& xmlWriter) ; AString encodeInXML() ; void decodeFromStringXML(const AString& xml) ; float getAutoScalePercentageNegativeMaximum() const; void setAutoScalePercentageNegativeMaximum(const float autoScalePercentageNegativeMaximum); float getAutoScalePercentageNegativeMinimum() const; void setAutoScalePercentageNegativeMinimum(const float autoScalePercentageNegativeMinimum); float getAutoScalePercentagePositiveMaximum() const; void setAutoScalePercentagePositiveMaximum(const float autoScalePercentagePositiveMaximum); float getAutoScalePercentagePositiveMinimum() const; void setAutoScalePercentagePositiveMinimum(const float autoScalePercentagePositiveMinimum); float getAutoScaleAbsolutePercentageMinimum() const; void setAutoScaleAbsolutePercentageMinimum(const float autoScaleAbsolutePercentageMinimum); float getAutoScaleAbsolutePercentageMaximum() const; void setAutoScaleAbsolutePercentageMaximum(const float autoScaleAbsolutePercentageMaximum); bool isDisplayNegativeDataFlag() const; void setDisplayNegativeDataFlag(const bool displayNegativeDataFlag); bool isDisplayPositiveDataFlag() const; void setDisplayPositiveDataFlag(const bool displayPositiveDataFlag); bool isDisplayZeroDataFlag() const; void setDisplayZeroDataFlag(const bool displayZeroDataFlag); bool isInterpolatePaletteFlag() const; void setInterpolatePaletteFlag(const bool interpolatePaletteFlag); PaletteScaleModeEnum::Enum getScaleMode() const; void setScaleMode(const PaletteScaleModeEnum::Enum scaleMode); AString getSelectedPaletteName() const; void setSelectedPaletteName(const AString& selectedPaletteName); void setSelectedPaletteToPsych(); void setSelectedPaletteToPsychNoNone(); void setSelectedPaletteToOrangeYellow(); void setSelectedPaletteToGrayInterpolated(); float getUserScaleNegativeMaximum() const; void setUserScaleNegativeMaximum(const float userScaleNegativeMaximum); float getUserScaleNegativeMinimum() const; void setUserScaleNegativeMinimum(const float userScaleNegativeMinimum); float getUserScalePositiveMaximum() const; void setUserScalePositiveMaximum(const float userScalePositiveMaximum); float getUserScalePositiveMinimum() const; void setUserScalePositiveMinimum(const float userScalePositiveMinimum); float getThresholdMappedAverageAreaMinimum() const; void setThresholdMappedAverageAreaMinimum(const float thresholdMappedAverageAreaMinimum); float getThresholdMappedAverageAreaMaximum() const; void setThresholdMappedAverageAreaMaximum(const float thresholdMappedAverageAreaPositive); float getThresholdMappedMinimum() const; void setThresholdMappedMinimum(const float thresholdMappedMinimum); float getThresholdMappedMaximum() const; void setThresholdMappedMaximum(const float thresholdMappedPositive); float getThresholdNormalMinimum() const; void setThresholdNormalMinimum(const float thresholdNormalMinimum); float getThresholdNormalMaximum() const; void setThresholdNormalMaximum(const float thresholdNormalPositive); float getThresholdMinimum(const PaletteThresholdTypeEnum::Enum thresholdType) const; float getThresholdMaximum(const PaletteThresholdTypeEnum::Enum thresholdType) const; void setThresholdMinimum(const PaletteThresholdTypeEnum::Enum thresholdType, const float thresholdMinimum); void setThresholdMaximum(const PaletteThresholdTypeEnum::Enum thresholdType, const float thresholdMaximum); PaletteThresholdTestEnum::Enum getThresholdTest() const; void setThresholdTest(const PaletteThresholdTestEnum::Enum thresholdTest); PaletteThresholdTypeEnum::Enum getThresholdType() const; void setThresholdType(const PaletteThresholdTypeEnum::Enum thresholdType); PaletteThresholdRangeModeEnum::Enum getThresholdRangeMode() const; void setThresholdRangeMode(const PaletteThresholdRangeModeEnum::Enum rangeMode); AString getThresholdDataName() const; void setThresholdDataName(const AString& thresholdDataName); bool isShowThresholdFailureInGreen() const; void setShowThresholdFailureInGreen(const bool showInGreenFlag); bool isThresholdNegMinPosMaxLinked() const; void setThresholdNegMinPosMaxLinked(const bool linked); void setModified(); void clearModified(); bool isModified() const; void mapDataToPaletteNormalizedValues(const FastStatistics* statistics, const float* dataValues, float* normalizedValuesOut, const int64_t numberOfData) const; void getPaletteColorBarScaleText(const FastStatistics* statistics, AString& minimumValueTextOut, AString& zeroValueTextOut, AString& maximumValueTextOut) const; /** A positive value near zero - may be zero! */ static const float SMALL_POSITIVE; /** A negative value near zero - may be zero! */ static const float SMALL_NEGATIVE; private: PaletteScaleModeEnum::Enum scaleMode; float autoScalePercentageNegativeMaximum; float autoScalePercentageNegativeMinimum; float autoScalePercentagePositiveMinimum; float autoScalePercentagePositiveMaximum; float autoScaleAbsolutePercentageMinimum; float autoScaleAbsolutePercentageMaximum; float userScaleNegativeMaximum; float userScaleNegativeMinimum; float userScalePositiveMinimum; float userScalePositiveMaximum; AString selectedPaletteName; bool interpolatePaletteFlag; bool displayPositiveDataFlag; bool displayZeroDataFlag; bool displayNegativeDataFlag; PaletteThresholdTypeEnum::Enum thresholdType; PaletteThresholdTestEnum::Enum thresholdTest; PaletteThresholdRangeModeEnum::Enum thresholdRangeMode; float thresholdNormalMinimum; float thresholdNormalMaximum; float thresholdMappedMinimum; float thresholdMappedMaximum; float thresholdMappedAverageAreaMinimum; float thresholdMappedAverageAreaMaximum; AString thresholdDataName; bool thresholdShowFailureInGreen; bool thresholdNegMinPosMaxLinked; /**Tracks modification, DO NOT copy */ bool modifiedFlag; }; #ifdef __PALETTE_COLOR_MAPPING_DECLARE__ const float PaletteColorMapping::SMALL_POSITIVE = 0.0; // JWH 24 April 2015 0.00001; const float PaletteColorMapping::SMALL_NEGATIVE = 0.0; // JWH 24 April 2015 -0.00001; #endif // __PALETTE_COLOR_MAPPING_DECLARE__ } // namespace #endif // __PALETTECOLORMAPPING_H__ workbench-1.1.1/src/Palette/PaletteColorMappingSaxReader.cxx000066400000000000000000000331101255417355300241250ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretAssert.h" #include "CaretLogger.h" #include "PaletteColorMapping.h" #include "PaletteColorMappingSaxReader.h" #include "PaletteColorMappingXmlElements.h" #include "XmlAttributes.h" #include "XmlException.h" using namespace caret; /** * constructor. */ PaletteColorMappingSaxReader::PaletteColorMappingSaxReader(PaletteColorMapping* paletteColorMapping) { CaretAssert(paletteColorMapping); this->state = STATE_NONE; this->stateStack.push(state); this->elementText = ""; this->paletteColorMapping = paletteColorMapping; } /** * destructor. */ PaletteColorMappingSaxReader::~PaletteColorMappingSaxReader() { } /** * start an element. */ void PaletteColorMappingSaxReader::startElement(const AString& /* namespaceURI */, const AString& /* localName */, const AString& qName, const XmlAttributes& attributes) { const STATE previousState = this->state; switch (this->state) { case STATE_NONE: if (qName == PaletteColorMappingXmlElements::XML_TAG_PALETTE_COLOR_MAPPING) { this->state = STATE_READING_ELEMENTS; int32_t version = attributes.getValueAsInt(PaletteColorMappingXmlElements::XML_ATTRIBUTE_VERSION_NUMBER); if (version > PaletteColorMappingXmlElements::XML_VERSION_NUMBER) { std::ostringstream str; str << "Version of PaletteColorMapping (" << version << ") is greater than version(s) supported (" << PaletteColorMappingXmlElements::XML_VERSION_NUMBER << ")."; throw XmlSaxParserException(AString::fromStdString(str.str())); } } else { std::ostringstream str; str << "Root element is \"" << qName.toStdString() << "\" but should be " << PaletteColorMappingXmlElements::XML_TAG_PALETTE_COLOR_MAPPING.toStdString(); throw XmlSaxParserException(AString::fromStdString(str.str())); } break; case STATE_READING_ELEMENTS: break; } // // Save previous state // this->stateStack.push(previousState); this->elementText = ""; } /** * Convert the string representation of a bool to a bool. * @param s * String containing boolean value. * @return * The bool value. */ bool toBool(const AString& s) { if ((s == "true") || (s == "TRUE") || (s == "True") || (s == "T") || (s == "t") || (s == "1")) { return true; } return false; } /** * Split up a string containing float values. * * @param s * String containing float values. * @return * float vector containing values extracted from string. */ std::vector toFloatVector(const AString& s) { std::vector fv; std::istringstream str(s.toStdString()); while ((str.eof() == false) && (str.fail() == false)) { float value; str >> value; fv.push_back(value); } return fv; } /** * end an element. */ void PaletteColorMappingSaxReader::endElement(const AString& /* namspaceURI */, const AString& /* localName */, const AString& qName) { switch (state) { case STATE_NONE: break; case STATE_READING_ELEMENTS: if (qName == PaletteColorMappingXmlElements::XML_TAG_AUTO_SCALE_PERCENTAGE_VALUES) { std::vector values = toFloatVector(this->elementText); if (values.size() >= 4) { this->paletteColorMapping->setAutoScalePercentageNegativeMaximum(values[0]); this->paletteColorMapping->setAutoScalePercentageNegativeMinimum(values[1]); this->paletteColorMapping->setAutoScalePercentagePositiveMinimum(values[2]); this->paletteColorMapping->setAutoScalePercentagePositiveMaximum(values[3]); } else { throw XmlSaxParserException("PaletteColorMappingXmlElements::auto scale percenter does not contain four values."); } } else if (qName == PaletteColorMappingXmlElements::XML_TAG_AUTO_SCALE_ABSOLUTE_PERCENTAGE_VALUES) { std::vector values = toFloatVector(this->elementText); if (values.size() >= 2) { this->paletteColorMapping->setAutoScaleAbsolutePercentageMinimum(values[0]); this->paletteColorMapping->setAutoScaleAbsolutePercentageMaximum(values[1]); } } else if (qName == PaletteColorMappingXmlElements::XML_TAG_DISPLAY_NEGATIVE) { this->paletteColorMapping->setDisplayNegativeDataFlag(toBool(this->elementText)); } else if (qName == PaletteColorMappingXmlElements::XML_TAG_DISPLAY_POSITIVE) { this->paletteColorMapping->setDisplayPositiveDataFlag(toBool(this->elementText)); } else if (qName == PaletteColorMappingXmlElements::XML_TAG_DISPLAY_ZERO) { this->paletteColorMapping->setDisplayZeroDataFlag(toBool(this->elementText)); } else if (qName == PaletteColorMappingXmlElements::XML_TAG_INTERPOLATE) { this->paletteColorMapping->setInterpolatePaletteFlag(toBool(this->elementText)); } else if (qName == PaletteColorMappingXmlElements::XML_TAG_PALETTE_NAME) { this->paletteColorMapping->setSelectedPaletteName(this->elementText); } else if (qName == PaletteColorMappingXmlElements::XML_TAG_THRESHOLD_DATA_NAME) { this->paletteColorMapping->setThresholdDataName(this->elementText); } else if (qName == PaletteColorMappingXmlElements::XML_TAG_THRESHOLD_FAILURE_IN_GREEN) { /* ??? */ } else if (qName == PaletteColorMappingXmlElements::XML_TAG_THRESHOLD_MAPPED_AVG_AREA_VALUES) { std::vector values = toFloatVector(this->elementText); if (values.size() >= 2) { this->paletteColorMapping->setThresholdMappedAverageAreaMinimum(values[0]); this->paletteColorMapping->setThresholdMappedAverageAreaMaximum(values[1]); } else { throw XmlSaxParserException("PaletteColorMappingXmlElements::threshild mapped average area does not contain two values."); } } else if (qName == PaletteColorMappingXmlElements::XML_TAG_THRESHOLD_MAPPED_VALUES) { std::vector values = toFloatVector(this->elementText); if (values.size() >= 2) { this->paletteColorMapping->setThresholdMappedMinimum(values[0]); this->paletteColorMapping->setThresholdMappedMaximum(values[1]); } else { throw XmlSaxParserException("PaletteColorMappingXmlElements::threshild mapped does not contain two values."); } } else if (qName == PaletteColorMappingXmlElements::XML_TAG_THRESHOLD_NORMAL_VALUES) { std::vector values = toFloatVector(this->elementText); if (values.size() >= 2) { this->paletteColorMapping->setThresholdNormalMinimum(values[0]); this->paletteColorMapping->setThresholdNormalMaximum(values[1]); } else { throw XmlSaxParserException("PaletteColorMappingXmlElements::threshild mapped normal does not contain two values."); } } else if (qName == PaletteColorMappingXmlElements::XML_TAG_THRESHOLD_TEST) { bool isValid = false; PaletteThresholdTestEnum::Enum thresoldTest = PaletteThresholdTestEnum::fromName(this->elementText, &isValid); if (isValid) { this->paletteColorMapping->setThresholdTest(thresoldTest); } else { throw XmlSaxParserException("Invalid PaletteColorMapping::thresoldTest " + this->elementText); } } else if (qName == PaletteColorMappingXmlElements::XML_TAG_THRESHOLD_TYPE) { bool isValid = false; PaletteThresholdTypeEnum::Enum thresholdType = PaletteThresholdTypeEnum::fromName(this->elementText, &isValid); if (isValid) { this->paletteColorMapping->setThresholdType(thresholdType); } else { throw XmlSaxParserException("Invalid PaletteColorMapping::thresholdType: " + this->elementText); } } else if (qName == PaletteColorMappingXmlElements::XML_TAG_SCALE_MODE) { bool isValid = false; PaletteScaleModeEnum::Enum scaleMode = PaletteScaleModeEnum::fromName(this->elementText, &isValid); if (isValid) { this->paletteColorMapping->setScaleMode(scaleMode); } else { throw XmlSaxParserException("Invalid PaletteColorMapping::scaleMode: " + this->elementText); } } else if (qName == PaletteColorMappingXmlElements::XML_TAG_USER_SCALE_VALUES) { std::vector values = toFloatVector(this->elementText); if (values.size() >= 4) { this->paletteColorMapping->setUserScaleNegativeMaximum(values[0]); this->paletteColorMapping->setUserScaleNegativeMinimum(values[1]); this->paletteColorMapping->setUserScalePositiveMinimum(values[2]); this->paletteColorMapping->setUserScalePositiveMaximum(values[3]); } else { throw XmlSaxParserException("PaletteColorMappingXmlElements::auto scale percenter does not contain four values."); } } else if (qName == PaletteColorMappingXmlElements::XML_TAG_THRESHOLD_RANGE_MODE) { bool isValid = false; PaletteThresholdRangeModeEnum::Enum rangeMode = PaletteThresholdRangeModeEnum::fromName(this->elementText, &isValid); if (isValid) { this->paletteColorMapping->setThresholdRangeMode(rangeMode); } else { throw XmlSaxParserException("Invalid PaletteThresholdRangeModeEnum::Enum: " + this->elementText); } } else if (qName == PaletteColorMappingXmlElements::XML_TAG_THRESHOLD_NEG_MIN_POS_MAX_LINKED) { this->paletteColorMapping->setThresholdNegMinPosMaxLinked(toBool(this->elementText)); } else if (qName == PaletteColorMappingXmlElements::XML_TAG_PALETTE_COLOR_MAPPING) { /* Top level tag, nothing to do */ } else { std::ostringstream str; str << "Unrecognized palette color mapping element \"" << qName.toStdString() << "\"."; throw XmlSaxParserException(AString::fromStdString(str.str())); } break; } // // Clear out for new elements // this->elementText = ""; // // Go to previous state // if (this->stateStack.empty()) { throw XmlSaxParserException("State stack is empty while reading PaletteColorMapping."); } this->state = stateStack.top(); this->stateStack.pop(); } /** * get characters in an element. */ void PaletteColorMappingSaxReader::characters(const char* ch) { this->elementText += ch; } /** * a fatal error occurs. */ void PaletteColorMappingSaxReader::fatalError(const XmlSaxParserException& e) { throw e; } /** * A warning occurs */ void PaletteColorMappingSaxReader::warning(const XmlSaxParserException& e) { CaretLogWarning("XML Parser Warning: " + e.whatString()); } // an error occurs void PaletteColorMappingSaxReader::error(const XmlSaxParserException& e) { CaretLogSevere("XML Parser Error: " + e.whatString()); throw e; } void PaletteColorMappingSaxReader::startDocument() { } void PaletteColorMappingSaxReader::endDocument() { } workbench-1.1.1/src/Palette/PaletteColorMappingSaxReader.h000066400000000000000000000067701255417355300235660ustar00rootroot00000000000000 #ifndef __PALETTE_COLOR_MAPPING_SAX_READER_H__ #define __PALETTE_COLOR_MAPPING_SAX_READER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include "XmlSaxParserException.h" #include "XmlSaxParserHandlerInterface.h" namespace caret { class PaletteColorMapping; class XmlAttributes; /** * Class for reading a PaletteColorMapping object with a SAX Parser */ class PaletteColorMappingSaxReader : public CaretObject, public XmlSaxParserHandlerInterface { public: PaletteColorMappingSaxReader(PaletteColorMapping* paletteColorMapping); virtual ~PaletteColorMappingSaxReader(); private: PaletteColorMappingSaxReader(const PaletteColorMappingSaxReader&); PaletteColorMappingSaxReader& operator=(const PaletteColorMappingSaxReader&); public: void startElement(const AString& namespaceURI, const AString& localName, const AString& qName, const XmlAttributes& attributes); void endElement(const AString& namspaceURI, const AString& localName, const AString& qName); void characters(const char* ch); void fatalError(const XmlSaxParserException& e); void warning(const XmlSaxParserException& e); void error(const XmlSaxParserException& e); void startDocument(); void endDocument(); protected: /// file reading states enum STATE { /// no state STATE_NONE, /// reading elements STATE_READING_ELEMENTS }; /// file reading state STATE state; /// the state stack used when reading a file std::stack stateStack; /// the error message AString errorMessage; /// element text AString elementText; /// GIFTI label table being read PaletteColorMapping* paletteColorMapping; /// label index int labelIndex; /// label color component float labelRed; /// label color component float labelGreen; /// label color component float labelBlue; /// label color component float labelAlpha; /// label's X-coordinate float labelX; /// label's Y-coordinate float labelY; /// label's Z-coordinate float labelZ; }; } // namespace #endif // __PALETTE_COLOR_MAPPING_SAX_READER_H__ workbench-1.1.1/src/Palette/PaletteColorMappingXmlElements.h000066400000000000000000000052311255417355300241340ustar00rootroot00000000000000#ifndef __PALETTE_COLOR_MAPPING_XML_ELEMENTS_H__ #define __PALETTE_COLOR_MAPPING_XML_ELEMENTS_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include using namespace caret; namespace PaletteColorMappingXmlElements { static const AString XML_TAG_PALETTE_COLOR_MAPPING = "PaletteColorMapping"; static const AString XML_TAG_SCALE_MODE = "ScaleMode"; static const AString XML_TAG_AUTO_SCALE_PERCENTAGE_VALUES = "AutoScalePercentageValues"; static const AString XML_TAG_AUTO_SCALE_ABSOLUTE_PERCENTAGE_VALUES = "AutoScaleAbsolutePercentageValues"; static const AString XML_TAG_USER_SCALE_VALUES = "UserScaleValues"; static const AString XML_TAG_PALETTE_NAME = "PaletteName"; static const AString XML_TAG_INTERPOLATE = "InterpolatePalette"; static const AString XML_TAG_DISPLAY_POSITIVE = "DisplayPositiveData"; static const AString XML_TAG_DISPLAY_NEGATIVE = "DisplayNegativeData"; static const AString XML_TAG_DISPLAY_ZERO = "DisplayZeroData"; static const AString XML_TAG_THRESHOLD_TEST = "ThresholdTest"; static const AString XML_TAG_THRESHOLD_TYPE = "ThresholdType"; static const AString XML_TAG_THRESHOLD_NORMAL_VALUES = "ThresholdNormalValues"; static const AString XML_TAG_THRESHOLD_MAPPED_VALUES = "ThresholdMappedValues"; static const AString XML_TAG_THRESHOLD_MAPPED_AVG_AREA_VALUES = "ThresholdMappedAvgAreaValues"; static const AString XML_TAG_THRESHOLD_DATA_NAME = "ThresholdDataName"; static const AString XML_TAG_THRESHOLD_FAILURE_IN_GREEN = "ThresholdFailureInGreen"; static const AString XML_TAG_THRESHOLD_RANGE_MODE = "ThresholdRangeMode"; static const AString XML_TAG_THRESHOLD_NEG_MIN_POS_MAX_LINKED = "ThresholdLowHighLinked"; static const AString XML_ATTRIBUTE_VERSION_NUMBER = "Version"; static const int XML_VERSION_NUMBER = 1; } // namespace #endif // __PALETTE_COLOR_MAPPING_XML_ELEMENTS_H__ workbench-1.1.1/src/Palette/PaletteEnums.cxx000066400000000000000000000470011255417355300210270ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __PALETTE_ENUMS_DECLARE__ #include "PaletteEnums.h" #undef __PALETTE_ENUMS_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * Constructor. * * @param e * An enumerated value. * @param name * Name of enumberated value. */ PaletteScaleModeEnum::PaletteScaleModeEnum( const Enum e, const int32_t integerCode, const AString& name, const AString& guiName) { this->e = e; this->integerCode = integerCode; this->name = name; this->guiName = guiName; } /** * Destructor. */ PaletteScaleModeEnum::~PaletteScaleModeEnum() { } void PaletteScaleModeEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(PaletteScaleModeEnum(MODE_AUTO_SCALE, 0, "MODE_AUTO_SCALE", "Auto Scale")); enumData.push_back(PaletteScaleModeEnum(MODE_AUTO_SCALE_ABSOLUTE_PERCENTAGE, 1, "MODE_AUTO_SCALE_ABSOLUTE_PERCENTAGE", "Auto Scale - Absolute Percentage")); enumData.push_back(PaletteScaleModeEnum(MODE_AUTO_SCALE_PERCENTAGE, 2, "MODE_AUTO_SCALE_PERCENTAGE", "Auto Scale - Percentage")); enumData.push_back(PaletteScaleModeEnum(MODE_USER_SCALE, 3, "MODE_USER_SCALE", "User Scale")); } /** * Find the data for and enumerated value. * @param e * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const PaletteScaleModeEnum* PaletteScaleModeEnum::findData(const Enum e) { initialize(); int64_t num = enumData.size(); for (int64_t i = 0; i < num; i++) { const PaletteScaleModeEnum* d = &enumData[i]; if (d->e == e) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param e * Enumerated value. * @return * String representing enumerated value. */ AString PaletteScaleModeEnum::toName(Enum e) { initialize(); const PaletteScaleModeEnum* psm = findData(e); return psm->name; } /** * Get an enumerated value corresponding to its name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ PaletteScaleModeEnum::Enum PaletteScaleModeEnum::fromName(const AString& s, bool* isValidOut) { initialize(); bool validFlag = false; Enum e = MODE_AUTO_SCALE; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const PaletteScaleModeEnum& d = *iter; if (d.name == s) { e = d.e; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("name \"" + s + " \"failed to match enumerated value for type PaletteScaleModeEnum")); } return e; } /** * Get a gui name representation of the enumerated type. * @param e * Enumerated value. * @return * String representing enumerated value. */ AString PaletteScaleModeEnum::toGuiName(Enum e) { initialize(); const PaletteScaleModeEnum* psm = findData(e); return psm->guiName; } /** * Get an enumerated value corresponding to its gui name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ PaletteScaleModeEnum::Enum PaletteScaleModeEnum::fromGuiName(const AString& s, bool* isValidOut) { initialize(); bool validFlag = false; Enum e = MODE_AUTO_SCALE; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const PaletteScaleModeEnum& d = *iter; if (d.guiName == s) { e = d.e; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName \"" + s + " \"failed to match enumerated value for type PaletteScaleModeEnum")); } return e; } /** * Get the integer code associated with a scale mode. * @param e * The enum. * @return * Integer code associated with a scale mode. */ int32_t PaletteScaleModeEnum::toIntegerCode(Enum e) { initialize(); const PaletteScaleModeEnum* nsu = findData(e); return nsu->integerCode; } /** * Find enum corresponding to integer code. * @param integerCode * The integer code. * @param isValidOut * If not NULL, on exit it indicates valid integer code. * @return * Enum corresponding to integer code. */ PaletteScaleModeEnum::Enum PaletteScaleModeEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { initialize(); bool validFlag = false; Enum e = MODE_AUTO_SCALE; for (std::vector::const_iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const PaletteScaleModeEnum& nsu = *iter; if (nsu.integerCode == integerCode) { e = nsu.e; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("integerCode \"" + AString::number(integerCode) + " \"failed to match enumerated value for type PaletteScaleModeEnum")); } return e; } /** * Get all enums for the data type. * @param enumsOut * Loaded with all enums for this data type. */ void PaletteScaleModeEnum::getAllEnums(std::vector< PaletteScaleModeEnum::Enum >& enumsOut) { initialize(); enumsOut.resize(enumData.size()); for (int i = 0; i < (int)enumData.size(); ++i) { enumsOut[i] = enumData[i].e; } } /** * Constructor. * * @param e * An enumerated value. * @param name * Name of enumberated value. */ PaletteThresholdTestEnum::PaletteThresholdTestEnum( const Enum e, const int32_t integerCode, const AString& name, const AString& guiName) { this->e = e; this->integerCode = integerCode; this->name = name; this->guiName = guiName; } /** * Destructor. */ PaletteThresholdTestEnum::~PaletteThresholdTestEnum() { } void PaletteThresholdTestEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(PaletteThresholdTestEnum(THRESHOLD_TEST_SHOW_OUTSIDE, 0, "THRESHOLD_TEST_SHOW_OUTSIDE", "Show Data Outside Thresholds")); enumData.push_back(PaletteThresholdTestEnum(THRESHOLD_TEST_SHOW_INSIDE, 1, "THRESHOLD_TEST_SHOW_INSIDE", "Show Data Below Threshold")); } /** * Find the data for and enumerated value. * @param e * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const PaletteThresholdTestEnum* PaletteThresholdTestEnum::findData(const Enum e) { initialize(); int64_t num = enumData.size(); for (int64_t i = 0; i < num; i++) { const PaletteThresholdTestEnum* d = &enumData[i]; if (d->e == e) { return d; } } CaretAssertMessage(0, "Threshold Test enum failed to match."); return NULL; } /** * Get a string representation of the enumerated type. * @param e * Enumerated value. * @return * String representing enumerated value. */ AString PaletteThresholdTestEnum::toName(Enum e) { initialize(); const PaletteThresholdTestEnum* ptt = findData(e); return ptt->name; } /** * Get an enumerated value corresponding to its name. * @param sin * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ PaletteThresholdTestEnum::Enum PaletteThresholdTestEnum::fromName(const AString& sin, bool* isValidOut) { initialize(); bool validFlag = false; Enum e = THRESHOLD_TEST_SHOW_OUTSIDE; AString s = sin; if (s == "THRESHOLD_TEST_SHOW_ABOVE") { s = "THRESHOLD_TEST_SHOW_OUTSIDE"; } else if (s == "THRESHOLD_TEST_SHOW_BELOW") { s = "THRESHOLD_TEST_SHOW_INSIDE"; } for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const PaletteThresholdTestEnum& d = *iter; if (d.name == s) { e = d.e; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("name \"" + s + " \"failed to match enumerated value for type PaletteThresholdTestEnum")); } return e; } /** * Get a gui name representation of the enumerated type. * @param e * Enumerated value. * @return * String representing enumerated value. */ AString PaletteThresholdTestEnum::toGuiName(Enum e) { initialize(); const PaletteThresholdTestEnum* psm = findData(e); return psm->guiName; } /** * Get an enumerated value corresponding to its gui name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ PaletteThresholdTestEnum::Enum PaletteThresholdTestEnum::fromGuiName(const AString& s, bool* isValidOut) { initialize(); bool validFlag = false; Enum e = THRESHOLD_TEST_SHOW_OUTSIDE; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const PaletteThresholdTestEnum& d = *iter; if (d.guiName == s) { e = d.e; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName \"" + s + " \"failed to match enumerated value for type PaletteThresholdTestEnum")); } return e; } /** * Get the integer code associated with a scale mode. * @param e * The enum. * @return * Integer code associated with a scale mode. */ int32_t PaletteThresholdTestEnum::toIntegerCode(Enum e) { initialize(); const PaletteThresholdTestEnum* nsu = findData(e); return nsu->integerCode; } /** * Find enum corresponding to integer code. * @param integerCode * The integer code. * @param isValidOut * If not NULL, on exit it indicates valid integer code. * @return * Enum corresponding to integer code. */ PaletteThresholdTestEnum::Enum PaletteThresholdTestEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { initialize(); bool validFlag = false; Enum e = THRESHOLD_TEST_SHOW_OUTSIDE; for (std::vector::const_iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const PaletteThresholdTestEnum& nsu = *iter; if (nsu.integerCode == integerCode) { e = nsu.e; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("integerCode \"" + AString::number(integerCode) + " \"failed to match enumerated value for type PaletteThresholdTestEnum")); } return e; } /** * Constructor. * * @param e * An enumerated value. * @param name * Name of enumberated value. */ PaletteThresholdTypeEnum::PaletteThresholdTypeEnum( const Enum e, const int32_t integerCode, const AString& name, const AString& guiName) { this->e = e; this->integerCode = integerCode; this->name = name; this->guiName = guiName; } /** * Destructor. */ PaletteThresholdTypeEnum::~PaletteThresholdTypeEnum() { } void PaletteThresholdTypeEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(PaletteThresholdTypeEnum(THRESHOLD_TYPE_OFF, 0, "THRESHOLD_TYPE_OFF", "Off")); if (PaletteThresholdTypeEnum::mappedThresholdsEnabled) { enumData.push_back(PaletteThresholdTypeEnum(THRESHOLD_TYPE_NORMAL, 1, "THRESHOLD_TYPE_NORMAL", "Normal")); enumData.push_back(PaletteThresholdTypeEnum(THRESHOLD_TYPE_MAPPED, 2, "THRESHOLD_TYPE_MAPPED", "Mapped")); enumData.push_back(PaletteThresholdTypeEnum(THRESHOLD_TYPE_MAPPED_AVERAGE_AREA, 3, "THRESHOLD_TYPE_MAPPED_AVERAGE_AREA", "Mapped Average Area")); } else { enumData.push_back(PaletteThresholdTypeEnum(THRESHOLD_TYPE_NORMAL, 1, "THRESHOLD_TYPE_NORMAL", "On")); } } /** * Find the data for and enumerated value. * @param e * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const PaletteThresholdTypeEnum* PaletteThresholdTypeEnum::findData(const Enum e) { initialize(); int64_t num = enumData.size(); for (int64_t i = 0; i < num; i++) { const PaletteThresholdTypeEnum* d = &enumData[i]; if (d->e == e) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param e * Enumerated value. * @return * String representing enumerated value. */ AString PaletteThresholdTypeEnum::toName(Enum e) { initialize(); const PaletteThresholdTypeEnum* ptt = findData(e); return ptt->name; } /** * Get an enumerated value corresponding to its name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ PaletteThresholdTypeEnum::Enum PaletteThresholdTypeEnum::fromName(const AString& s, bool* isValidOut) { initialize(); bool validFlag = false; Enum e = THRESHOLD_TYPE_OFF; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const PaletteThresholdTypeEnum& d = *iter; if (d.name == s) { e = d.e; validFlag = true; break; } } PaletteThresholdTypeEnum::handleDisabledThresholdTypes(e); if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("name \"" + s + " \"failed to match enumerated value for type PaletteThresholdTypeEnum")); } return e; } /** * If the mapped threshold types are disabled, convert * them to normal thresholding. * @param e * Thresholding type that may be changed. */ void PaletteThresholdTypeEnum::handleDisabledThresholdTypes(Enum& e) { if (PaletteThresholdTypeEnum::mappedThresholdsEnabled == false) { if ((e == THRESHOLD_TYPE_MAPPED) || (e == THRESHOLD_TYPE_MAPPED_AVERAGE_AREA)) { e = THRESHOLD_TYPE_NORMAL; } } } /** * Get a gui name representation of the enumerated type. * @param e * Enumerated value. * @return * String representing enumerated value. */ AString PaletteThresholdTypeEnum::toGuiName(Enum e) { initialize(); const PaletteThresholdTypeEnum* psm = findData(e); return psm->guiName; } /** * Get an enumerated value corresponding to its gui name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ PaletteThresholdTypeEnum::Enum PaletteThresholdTypeEnum::fromGuiName(const AString& s, bool* isValidOut) { initialize(); bool validFlag = false; Enum e = THRESHOLD_TYPE_OFF; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const PaletteThresholdTypeEnum& d = *iter; if (d.guiName == s) { e = d.e; validFlag = true; break; } } PaletteThresholdTypeEnum::handleDisabledThresholdTypes(e); if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName \"" + s + " \"failed to match enumerated value for type PaletteThresholdTypeEnum")); } return e; } /** * Get the integer code associated with a threshold type. * @param e * The enum. * @return * Integer code associated with a threshold type. */ int32_t PaletteThresholdTypeEnum::toIntegerCode(Enum e) { initialize(); const PaletteThresholdTypeEnum* nsu = findData(e); return nsu->integerCode; } /** * Find enum corresponding to integer code. * @param integerCode * The integer code. * @param isValidOut * If not NULL, on exit it indicates valid integer code. * @return * Enum corresponding to integer code. */ PaletteThresholdTypeEnum::Enum PaletteThresholdTypeEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { initialize(); bool validFlag = false; Enum e = THRESHOLD_TYPE_OFF; for (std::vector::const_iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const PaletteThresholdTypeEnum& nsu = *iter; if (nsu.integerCode == integerCode) { e = nsu.e; validFlag = true; break; } } PaletteThresholdTypeEnum::handleDisabledThresholdTypes(e); if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("integerCode \"" + AString::number(integerCode) + " \"failed to match enumerated value for type PaletteThresholdTypeEnum")); } return e; } /** * Get all enums for the data type. * @param enumsOut * Loaded with all enums for this data type. */ void PaletteThresholdTestEnum::getAllEnums(std::vector& enumsOut) { initialize(); enumsOut.resize(enumData.size()); for (int i = 0; i < (int)enumData.size(); ++i) { enumsOut[i] = enumData[i].e; } } /** * Get all enums for the data type. * @param enumsOut * Loaded with all enums for this data type. */ void PaletteThresholdTypeEnum::getAllEnums(std::vector& enumsOut) { initialize(); enumsOut.resize(enumData.size()); for (int i = 0; i < (int)enumData.size(); ++i) { enumsOut[i] = enumData[i].e; } } workbench-1.1.1/src/Palette/PaletteEnums.h000066400000000000000000000124701255417355300204560ustar00rootroot00000000000000#ifndef __PALETTE_ENUMS_H #define __PALETTE_ENUMS_H /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include "CaretAssert.h" namespace caret { /** * Palette Scale Mode. */ class PaletteScaleModeEnum { public: /** Palette Scale Mode. */ enum Enum { /** Auto Scale */ MODE_AUTO_SCALE, /** Auto Scale Absolute Percentage */ MODE_AUTO_SCALE_ABSOLUTE_PERCENTAGE, /** Auto Scale Percentage */ MODE_AUTO_SCALE_PERCENTAGE, /** User Scale */ MODE_USER_SCALE }; ~PaletteScaleModeEnum(); static AString toName(Enum e); static Enum fromName(const AString& s, bool* isValidOut); static AString toGuiName(Enum e); static Enum fromGuiName(const AString& s, bool* isValidOut); static int32_t toIntegerCode(Enum e); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& enumsOut); private: PaletteScaleModeEnum(const Enum e, const int32_t integerCode, const AString& name, const AString& guiName); static const PaletteScaleModeEnum* findData(const Enum e); static std::vector enumData; static void initialize(); static bool initializedFlag; Enum e; int32_t integerCode; AString name; AString guiName; }; /** * Palette Threshold Test. */ class PaletteThresholdTestEnum { public: /** Palette Threshold Test. */ enum Enum { /** show data when value is outside the threshold values */ THRESHOLD_TEST_SHOW_OUTSIDE, /** show data when value is inside the threshold values */ THRESHOLD_TEST_SHOW_INSIDE }; ~PaletteThresholdTestEnum(); static AString toName(Enum e); static Enum fromName(const AString& s, bool* isValidOut); static AString toGuiName(Enum e); static Enum fromGuiName(const AString& s, bool* isValidOut); static int32_t toIntegerCode(Enum e); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& enumsOut); private: PaletteThresholdTestEnum(const Enum e, const int32_t integerCode, const AString& name, const AString& guiName); static const PaletteThresholdTestEnum* findData(const Enum e); static std::vector enumData; static void initialize(); static bool initializedFlag; Enum e; int32_t integerCode; AString name; AString guiName; }; /** * Palette Threshold Type. */ class PaletteThresholdTypeEnum { public: /** Palette Threshold Type. */ enum Enum { /** thresholding is off */ THRESHOLD_TYPE_OFF, /** normal thresholding */ THRESHOLD_TYPE_NORMAL, /** threshold from mapping of volume */ THRESHOLD_TYPE_MAPPED, /** threshold from mapping to PALS average area */ THRESHOLD_TYPE_MAPPED_AVERAGE_AREA }; ~PaletteThresholdTypeEnum(); static AString toName(Enum e); static Enum fromName(const AString& s, bool* isValidOut); static AString toGuiName(Enum e); static Enum fromGuiName(const AString& s, bool* isValidOut); static int32_t toIntegerCode(Enum e); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& enumsOut); private: PaletteThresholdTypeEnum(const Enum e, const int32_t integerCode, const AString& name, const AString& guiName); static const PaletteThresholdTypeEnum* findData(const Enum e); static std::vector enumData; static void initialize(); static bool initializedFlag; static void handleDisabledThresholdTypes(Enum& e); Enum e; int32_t integerCode; AString name; AString guiName; static const bool mappedThresholdsEnabled; }; #ifdef __PALETTE_ENUMS_DECLARE__ std::vector PaletteScaleModeEnum::enumData; bool PaletteScaleModeEnum::initializedFlag = false; std::vector PaletteThresholdTestEnum::enumData; bool PaletteThresholdTestEnum::initializedFlag = false; std::vector PaletteThresholdTypeEnum::enumData; bool PaletteThresholdTypeEnum::initializedFlag = false; const bool PaletteThresholdTypeEnum::mappedThresholdsEnabled = false; #endif // __PALETTE_ENUMS_DECLARE__ } // namespace #endif // __PALETTE_ENUMS_H workbench-1.1.1/src/Palette/PaletteNormalizationModeEnum.cxx000066400000000000000000000277501255417355300242310ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __PALETTE_NORMALIZATION_MODE_ENUM_DECLARE__ #include "PaletteNormalizationModeEnum.h" #undef __PALETTE_NORMALIZATION_MODE_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::PaletteNormalizationModeEnum * \brief Enumerated type for normalization of file/map data. * * Using this enumerated type in the GUI with an EnumComboBoxTemplate * * Header File (.h) * Forward declare the data type: * class EnumComboBoxTemplate; * * Declare the member: * EnumComboBoxTemplate* m_paletteNormalizationModeEnumComboBox; * * Declare a slot that is called when user changes selection * private slots: * void paletteNormalizationModeEnumComboBoxItemActivated(); * * Implementation File (.cxx) * Include the header files * #include "EnumComboBoxTemplate.h" * #include "PaletteNormalizationModeEnum.h" * * Instatiate: * m_paletteNormalizationModeEnumComboBox = new EnumComboBoxTemplate(this); * m_paletteNormalizationModeEnumComboBox->setup(); * * Get notified when the user changes the selection: * QObject::connect(m_paletteNormalizationModeEnumComboBox, SIGNAL(itemActivated()), * this, SLOT(paletteNormalizationModeEnumComboBoxItemActivated())); * * Update the selection: * m_paletteNormalizationModeEnumComboBox->setSelectedItem(NEW_VALUE); * * Read the selection: * const PaletteNormalizationModeEnum::Enum VARIABLE = m_paletteNormalizationModeEnumComboBox->getSelectedItem(); * */ /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ PaletteNormalizationModeEnum::PaletteNormalizationModeEnum(const Enum enumValue, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCodeCounter++; this->name = name; this->guiName = guiName; } /** * Destructor. */ PaletteNormalizationModeEnum::~PaletteNormalizationModeEnum() { } /** * Initialize the enumerated metadata. */ void PaletteNormalizationModeEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(PaletteNormalizationModeEnum(NORMALIZATION_ALL_MAP_DATA, "NORMALIZATION_ALL_MAP_DATA", "All Maps In File")); enumData.push_back(PaletteNormalizationModeEnum(NORMALIZATION_SELECTED_MAP_DATA, "NORMALIZATION_SELECTED_MAP_DATA", "Selected Map In File")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const PaletteNormalizationModeEnum* PaletteNormalizationModeEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const PaletteNormalizationModeEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString PaletteNormalizationModeEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const PaletteNormalizationModeEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ PaletteNormalizationModeEnum::Enum PaletteNormalizationModeEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = PaletteNormalizationModeEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const PaletteNormalizationModeEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type PaletteNormalizationModeEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString PaletteNormalizationModeEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const PaletteNormalizationModeEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ PaletteNormalizationModeEnum::Enum PaletteNormalizationModeEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = PaletteNormalizationModeEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const PaletteNormalizationModeEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type PaletteNormalizationModeEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t PaletteNormalizationModeEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const PaletteNormalizationModeEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ PaletteNormalizationModeEnum::Enum PaletteNormalizationModeEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = PaletteNormalizationModeEnum::enumData[0].enumValue; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const PaletteNormalizationModeEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type PaletteNormalizationModeEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void PaletteNormalizationModeEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void PaletteNormalizationModeEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(PaletteNormalizationModeEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void PaletteNormalizationModeEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(PaletteNormalizationModeEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } AString PaletteNormalizationModeEnum::getEnumToolTopInHTML() { const AString msg("" "Normalization controls how data values are mapped to the color palette. " "The data's most negative, zero, and most positive values are mapped to " "the palette's -1.0, 0.0, and 1.0 colors.

" "" + toGuiName(PaletteNormalizationModeEnum::NORMALIZATION_ALL_MAP_DATA) + "
" + " Uses data from all maps within the file and results in identical data " "values from any map in the file receiving identical coloring.

" "" + toGuiName(PaletteNormalizationModeEnum::NORMALIZATION_SELECTED_MAP_DATA) + "
" + " Uses data from the selected map and so identical data values " " in different maps may receive different coloring.
" ""); return msg; } workbench-1.1.1/src/Palette/PaletteNormalizationModeEnum.h000066400000000000000000000065211255417355300236470ustar00rootroot00000000000000#ifndef __PALETTE_NORMALIZATION_MODE_ENUM_H__ #define __PALETTE_NORMALIZATION_MODE_ENUM_H__ /*LICENSE_START*/ /* * Copyright (C) 2015 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class PaletteNormalizationModeEnum { public: /** * Enumerated values. */ enum Enum { /** Use data from all maps in file for normalization */ NORMALIZATION_ALL_MAP_DATA, /** Use data from selected map for normalization */ NORMALIZATION_SELECTED_MAP_DATA }; ~PaletteNormalizationModeEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); static AString getEnumToolTopInHTML(); private: PaletteNormalizationModeEnum(const Enum enumValue, const AString& name, const AString& guiName); static const PaletteNormalizationModeEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; #ifdef __PALETTE_NORMALIZATION_MODE_ENUM_DECLARE__ std::vector PaletteNormalizationModeEnum::enumData; bool PaletteNormalizationModeEnum::initializedFlag = false; int32_t PaletteNormalizationModeEnum::integerCodeCounter = 0; #endif // __PALETTE_NORMALIZATION_MODE_ENUM_DECLARE__ } // namespace #endif //__PALETTE_NORMALIZATION_MODE_ENUM_H__ workbench-1.1.1/src/Palette/PaletteScalarAndColor.cxx000066400000000000000000000113711255417355300225700ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "PaletteScalarAndColor.h" using namespace caret; /** * Constructor. * * @param scalar - the scalar value * @param colorIndex - the scalar's color index * */ PaletteScalarAndColor::PaletteScalarAndColor( const float scalar, const AString& colorName) : CaretObject() { this->initializeMembersPaletteScalarAndColor(); this->scalar = scalar; setColorName(colorName); } /** * Destructor */ PaletteScalarAndColor::~PaletteScalarAndColor() { } /** * Copy Constructor * @param Object that is copied. */ PaletteScalarAndColor::PaletteScalarAndColor(const PaletteScalarAndColor& o) : CaretObject(o), TracksModificationInterface() { this->initializeMembersPaletteScalarAndColor(); this->copyHelper(o); } /** * Assignment operator. */ PaletteScalarAndColor& PaletteScalarAndColor::operator=(const PaletteScalarAndColor& o) { if (this != &o) { CaretObject::operator=(o); this->copyHelper(o); }; return *this; } /** * Helps with copy constructor and assignment operator. */ void PaletteScalarAndColor::copyHelper(const PaletteScalarAndColor& o) { this->scalar = o.scalar; this->colorName = o.colorName; this->noneColorFlag = o.noneColorFlag; this->rgba[0] = o.rgba[0]; this->rgba[1] = o.rgba[1]; this->rgba[2] = o.rgba[2]; this->rgba[3] = o.rgba[3]; this->clearModified(); } void PaletteScalarAndColor::initializeMembersPaletteScalarAndColor() { this->modifiedFlag = false; this->scalar = 0.0f; this->colorName = ""; this->noneColorFlag = false; this->rgba[0] = 1.0f; this->rgba[1] = 1.0f; this->rgba[2] = 1.0f; this->rgba[3] = 1.0f; } /** * Set the scalar. * * @param scalar - new value for scalar. * */ void PaletteScalarAndColor::setScalar(const float scalar) { if (this->scalar != scalar) { this->scalar = scalar; this->setModified(); } } /** * Set the name of the color for this scalar. * @param colorName * New name for color. */ void PaletteScalarAndColor::setColorName(const AString& colorName) { if (this->colorName != colorName) { this->colorName = colorName; this->noneColorFlag = (colorName == "none"); this->setModified(); } } /** * Get the RGBA components of the color. * @param rgbaOut * float array with red, green, blue, alpha * components ranging 0 to 1 are loaded into. */ void PaletteScalarAndColor::getColor(float rgbaOut[4]) const { rgbaOut[0] = this->rgba[0]; rgbaOut[1] = this->rgba[1]; rgbaOut[2] = this->rgba[2]; rgbaOut[3] = this->rgba[3]; } /** * Set the color's RGBA components. * @param rgba * New components for RGBA color. */ void PaletteScalarAndColor::setColor(const float rgba[4]) { if ((this->rgba[0] != rgba[0]) || (this->rgba[1] != rgba[1]) || (this->rgba[2] != rgba[2]) || (this->rgba[3] != rgba[3])) { this->rgba[0] = rgba[0]; this->rgba[1] = rgba[1]; this->rgba[2] = rgba[2]; this->rgba[3] = rgba[3]; this->setModified(); } } /** * Get string representation for debugging. * * @return A string. * */ AString PaletteScalarAndColor::toString() const { AString s = "[colorName=" + this->colorName + ", scale=" + AString::number(this->scalar) + ", rgba=" + AString::fromNumbers(rgba, 4, ",") + "]"; return s; } /** * Set this object has been modified. * */ void PaletteScalarAndColor::setModified() { this->modifiedFlag = true; } /** * Set this object as not modified. Object should also * clear the modification status of its children. * */ void PaletteScalarAndColor::clearModified() { this->modifiedFlag = false; } /** * Get the modification status. Returns true if this object or * any of its children have been modified. * @return - The modification status. * */ bool PaletteScalarAndColor::isModified() const { return this->modifiedFlag; } workbench-1.1.1/src/Palette/PaletteScalarAndColor.h000066400000000000000000000057121255417355300222170ustar00rootroot00000000000000#ifndef __PALETTESCALARANDCOLOR_H__ #define __PALETTESCALARANDCOLOR_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "TracksModificationInterface.h" #include #include namespace caret { /** * Contains information about the color assigned to the scalar * value. A palette contains a set of these objects. */ class PaletteScalarAndColor : public CaretObject, TracksModificationInterface { public: PaletteScalarAndColor(const float scalar, const AString& colorName); PaletteScalarAndColor(const PaletteScalarAndColor& o); PaletteScalarAndColor& operator=(const PaletteScalarAndColor& o); virtual ~PaletteScalarAndColor(); private: void copyHelper(const PaletteScalarAndColor& o); void initializeMembersPaletteScalarAndColor(); public: /** * @return The scalar */ inline float getScalar() const { return this->scalar; } void setScalar(const float scalar); /** * Get the name of the color. * @return * Name of the color assigned to the scalar. */ inline const AString& getColorName() const { return this->colorName; } void setColorName(const AString& colorName); /** * @return float array with red, green, blue, alpha color components ranging 0 to 1. */ inline const float* getColor() const { return rgba; } void getColor(float rgbaOut[4]) const; void setColor(const float rgba[4]); AString toString() const; void setModified(); void clearModified(); bool isModified() const; /** * @return Is the color the 'none' color meaning * that no coloring is applied? */ inline bool isNoneColor() const { return this->noneColorFlag; } private: /** has this object been modified. (DO NOT CLONE) */ bool modifiedFlag; /** the scalar value */ float scalar; /** the color's name, use the setName() method so that none color flag is updated */ AString colorName; /** the color's rgba components */ float rgba[4]; bool noneColorFlag; }; } // namespace #endif // __PALETTESCALARANDCOLOR_H__ workbench-1.1.1/src/Palette/PaletteThresholdRangeModeEnum.cxx000066400000000000000000000233751255417355300243130ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __PALETTE_THRESHOLD_RANGE_MODE_ENUM_DECLARE__ #include "PaletteThresholdRangeModeEnum.h" #undef __PALETTE_THRESHOLD_RANGE_MODE_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::PaletteThresholdRangeModeEnum * \brief * * */ /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ PaletteThresholdRangeModeEnum::PaletteThresholdRangeModeEnum(const Enum enumValue, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCodeCounter++; this->name = name; this->guiName = guiName; } /** * Destructor. */ PaletteThresholdRangeModeEnum::~PaletteThresholdRangeModeEnum() { } /** * Initialize the enumerated metadata. */ void PaletteThresholdRangeModeEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(PaletteThresholdRangeModeEnum(PALETTE_THRESHOLD_RANGE_MODE_FILE, "PALETTE_THRESHOLD_RANGE_MODE_FILE", "File")); enumData.push_back(PaletteThresholdRangeModeEnum(PALETTE_THRESHOLD_RANGE_MODE_MAP, "PALETTE_THRESHOLD_RANGE_MODE_MAP", "Map")); enumData.push_back(PaletteThresholdRangeModeEnum(PALETTE_THRESHOLD_RANGE_MODE_UNLIMITED, "PALETTE_THRESHOLD_RANGE_MODE_UNLIMITED", "Unlimited")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const PaletteThresholdRangeModeEnum* PaletteThresholdRangeModeEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const PaletteThresholdRangeModeEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString PaletteThresholdRangeModeEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const PaletteThresholdRangeModeEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ PaletteThresholdRangeModeEnum::Enum PaletteThresholdRangeModeEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = PALETTE_THRESHOLD_RANGE_MODE_MAP; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const PaletteThresholdRangeModeEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type PaletteThresholdRangeModeEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString PaletteThresholdRangeModeEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const PaletteThresholdRangeModeEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ PaletteThresholdRangeModeEnum::Enum PaletteThresholdRangeModeEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = PALETTE_THRESHOLD_RANGE_MODE_MAP; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const PaletteThresholdRangeModeEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type PaletteThresholdRangeModeEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t PaletteThresholdRangeModeEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const PaletteThresholdRangeModeEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ PaletteThresholdRangeModeEnum::Enum PaletteThresholdRangeModeEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = PALETTE_THRESHOLD_RANGE_MODE_MAP; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const PaletteThresholdRangeModeEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type PaletteThresholdRangeModeEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void PaletteThresholdRangeModeEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void PaletteThresholdRangeModeEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(PaletteThresholdRangeModeEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void PaletteThresholdRangeModeEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(PaletteThresholdRangeModeEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } workbench-1.1.1/src/Palette/PaletteThresholdRangeModeEnum.h000066400000000000000000000067161255417355300237400ustar00rootroot00000000000000#ifndef __PALETTE_THRESHOLD_RANGE_MODE_ENUM_H__ #define __PALETTE_THRESHOLD_RANGE_MODE_ENUM_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class PaletteThresholdRangeModeEnum { public: /** * Enumerated values. */ enum Enum { /** Threshold range is minimum and maximum values from file */ PALETTE_THRESHOLD_RANGE_MODE_FILE, /** Threshold range is minimum and maximum values from map */ PALETTE_THRESHOLD_RANGE_MODE_MAP, /** Threshold range is unlimited (minimum and maximum float values) */ PALETTE_THRESHOLD_RANGE_MODE_UNLIMITED, }; ~PaletteThresholdRangeModeEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); private: PaletteThresholdRangeModeEnum(const Enum enumValue, const AString& name, const AString& guiName); static const PaletteThresholdRangeModeEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; #ifdef __PALETTE_THRESHOLD_RANGE_MODE_ENUM_DECLARE__ std::vector PaletteThresholdRangeModeEnum::enumData; bool PaletteThresholdRangeModeEnum::initializedFlag = false; int32_t PaletteThresholdRangeModeEnum::integerCodeCounter = 0; #endif // __PALETTE_THRESHOLD_RANGE_MODE_ENUM_DECLARE__ } // namespace #endif //__PALETTE_THRESHOLD_RANGE_MODE_ENUM_H__ workbench-1.1.1/src/Quazip/000077500000000000000000000000001255417355300155465ustar00rootroot00000000000000workbench-1.1.1/src/Quazip/CMakeLists.txt000066400000000000000000000017421255417355300203120ustar00rootroot00000000000000# # Name of project # PROJECT(Quazip) # # QT include files # SET(QT_DONT_USE_QTGUI TRUE) INCLUDE(${QT_USE_FILE}) SET(MOC_INPUT_HEADER_FILES quazipfile.h ) QT4_WRAP_CPP(MOC_SOURCE_FILES ${MOC_INPUT_HEADER_FILES}) # # Prevents dll linkage errors on windows # #IF(WIN32) # ADD_DEFINITIONS(-DQUAZIP_STATIC) # IF(MSVC) # ADD_DEFINITIONS(-DQUAZIP_STATIC) # #ADD_DEFINITIONS(-DQUAZIP_BUILD) # ENDIF(MSVC) #ELSE(WIN32) # ADD_DEFINITIONS(-DQUAZIP_STATIC) #ENDIF(WIN32) # # Create a library # ADD_LIBRARY(Quazip JlCompress.h crypt.h ioapi.h quaadler32.h quachecksum32.h quacrc32.h quagzipfile.h quaziodevice.h quazip.h quazip_global.h quazipdir.h quazipfile.h quazipfileinfo.h quazipnewinfo.h unzip.h zip.h ${MOC_SOURCE_FILES} JlCompress.cpp qioapi.cpp quaadler32.cpp quacrc32.cpp quagzipfile.cpp quaziodevice.cpp quazip.cpp quazipdir.cpp quazipfile.cpp quazipfileinfo.cpp quazipnewinfo.cpp unzip.c zip.c ) workbench-1.1.1/src/Quazip/COPYING000066400000000000000000000604201255417355300166030ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin St, 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. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, 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 and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, 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 library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete 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 distribute a copy of this License along with the Library. 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 Library or any portion of it, thus forming a work based on the Library, 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) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, 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 Library, 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 Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you 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. If distribution of 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 satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be 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. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library 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. 9. 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 Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library 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 with this License. 11. 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 Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library 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 Library. 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. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library 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. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser 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 Library 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 Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, 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 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "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 LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. 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 LIBRARY 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 LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS workbench-1.1.1/src/Quazip/JlCompress.cpp000066400000000000000000000352311255417355300203370ustar00rootroot00000000000000#include "JlCompress.h" #include static bool copyData(QIODevice &inFile, QIODevice &outFile) { while (!inFile.atEnd()) { char buf[4096]; qint64 readLen = inFile.read(buf, 4096); if (readLen <= 0) return false; if (outFile.write(buf, readLen) != readLen) return false; } return true; } /**OK * Comprime il file fileName, nell'oggetto zip, con il nome fileDest. * * La funzione fallisce se: * * zip==NULL; * * l'oggetto zip e stato aperto in una modalita non compatibile con l'aggiunta di file; * * non e possibile aprire il file d'origine; * * non e possibile creare il file all'interno dell'oggetto zip; * * si e rilevato un errore nella copia dei dati; * * non e stato possibile chiudere il file all'interno dell'oggetto zip; */ bool JlCompress::compressFile(QuaZip* zip, QString fileName, QString fileDest) { // zip: oggetto dove aggiungere il file // fileName: nome del file reale // fileDest: nome del file all'interno del file compresso // Controllo l'apertura dello zip if (!zip) return false; if (zip->getMode()!=QuaZip::mdCreate && zip->getMode()!=QuaZip::mdAppend && zip->getMode()!=QuaZip::mdAdd) return false; // Apro il file originale QFile inFile; inFile.setFileName(fileName); if(!inFile.open(QIODevice::ReadOnly)) return false; // Apro il file risulato QuaZipFile outFile(zip); if(!outFile.open(QIODevice::WriteOnly, QuaZipNewInfo(fileDest, inFile.fileName()))) return false; // Copio i dati if (!copyData(inFile, outFile) || outFile.getZipError()!=UNZ_OK) { return false; } // Chiudo i file outFile.close(); if (outFile.getZipError()!=UNZ_OK) return false; inFile.close(); return true; } /**OK * Comprime la cartella dir nel file fileCompressed, se recursive e true allora * comprime anche le sotto cartelle. I nomi dei file preceduti dal path creato * togliendo il pat della cartella origDir al path della cartella dir. * Se la funzione fallisce restituisce false e cancella il file che si e tentato * di creare. * * La funzione fallisce se: * * zip==NULL; * * l'oggetto zip e stato aperto in una modalita non compatibile con l'aggiunta di file; * * la cartella dir non esiste; * * la compressione di una sotto cartella fallisce (1); * * la compressione di un file fallisce; * (1) La funzione si richiama in maniera ricorsiva per comprimere le sotto cartelle * dunque gli errori di compressione di una sotto cartella sono gli stessi di questa * funzione. */ bool JlCompress::compressSubDir(QuaZip* zip, QString dir, QString origDir, bool recursive) { // zip: oggetto dove aggiungere il file // dir: cartella reale corrente // origDir: cartella reale originale // (path(dir)-path(origDir)) = path interno all'oggetto zip // Controllo l'apertura dello zip if (!zip) return false; if (zip->getMode()!=QuaZip::mdCreate && zip->getMode()!=QuaZip::mdAppend && zip->getMode()!=QuaZip::mdAdd) return false; // Controllo la cartella QDir directory(dir); if (!directory.exists()) return false; QDir origDirectory(origDir); if (dir != origDir) { QuaZipFile dirZipFile(zip); if (!dirZipFile.open(QIODevice::WriteOnly, QuaZipNewInfo(origDirectory.relativeFilePath(dir) + "/", dir), 0, 0, 0)) { return false; } dirZipFile.close(); } // Se comprimo anche le sotto cartelle if (recursive) { // Per ogni sotto cartella QFileInfoList files = directory.entryInfoList(QDir::AllDirs|QDir::NoDotAndDotDot); Q_FOREACH (QFileInfo file, files) { // Comprimo la sotto cartella if(!compressSubDir(zip,file.absoluteFilePath(),origDir,recursive)) return false; } } // Per ogni file nella cartella QFileInfoList files = directory.entryInfoList(QDir::Files); Q_FOREACH (QFileInfo file, files) { // Se non e un file o e il file compresso che sto creando if(!file.isFile()||file.absoluteFilePath()==zip->getZipName()) continue; // Creo il nome relativo da usare all'interno del file compresso QString filename = origDirectory.relativeFilePath(file.absoluteFilePath()); // Comprimo il file if (!compressFile(zip,file.absoluteFilePath(),filename)) return false; } return true; } /**OK * Estrae il file fileName, contenuto nell'oggetto zip, con il nome fileDest. * Se la funzione fallisce restituisce false e cancella il file che si e tentato di estrarre. * * La funzione fallisce se: * * zip==NULL; * * l'oggetto zip e stato aperto in una modalita non compatibile con l'estrazione di file; * * non e possibile aprire il file all'interno dell'oggetto zip; * * non e possibile creare il file estratto; * * si e rilevato un errore nella copia dei dati (1); * * non e stato possibile chiudere il file all'interno dell'oggetto zip (1); * * (1): prima di uscire dalla funzione cancella il file estratto. */ bool JlCompress::extractFile(QuaZip* zip, QString fileName, QString fileDest) { // zip: oggetto dove aggiungere il file // filename: nome del file reale // fileincompress: nome del file all'interno del file compresso // Controllo l'apertura dello zip if (!zip) return false; if (zip->getMode()!=QuaZip::mdUnzip) return false; // Apro il file compresso if (!fileName.isEmpty()) zip->setCurrentFile(fileName); QuaZipFile inFile(zip); if(!inFile.open(QIODevice::ReadOnly) || inFile.getZipError()!=UNZ_OK) return false; // Controllo esistenza cartella file risultato QDir curDir; if (!curDir.mkpath(QFileInfo(fileDest).absolutePath())) { return false; } QuaZipFileInfo info; if (!zip->getCurrentFileInfo(&info)) return false; if (fileDest.endsWith('/') && QFileInfo(fileDest).isDir()) { QFile(fileDest).setPermissions(info.getPermissions()); return true; } // Apro il file risultato QFile outFile; outFile.setFileName(fileDest); if(!outFile.open(QIODevice::WriteOnly)) return false; // Copio i dati if (!copyData(inFile, outFile) || inFile.getZipError()!=UNZ_OK) { outFile.close(); removeFile(QStringList(fileDest)); return false; } outFile.close(); // Chiudo i file inFile.close(); if (inFile.getZipError()!=UNZ_OK) { removeFile(QStringList(fileDest)); return false; } outFile.setPermissions(info.getPermissions()); return true; } /** * Rimuove i file il cui nome e specificato all'interno di listFile. * Restituisce true se tutti i file sono stati cancellati correttamente, attenzione * perche puo restituire false anche se alcuni file non esistevano e si e tentato * di cancellarli. */ bool JlCompress::removeFile(QStringList listFile) { bool ret = true; // Per ogni file for (int i=0; iopen(QuaZip::mdUnzip)) { delete zip; return QStringList(); } // Estraggo i nomi dei file QStringList lst; QuaZipFileInfo info; for(bool more=zip->goToFirstFile(); more; more=zip->goToNextFile()) { if(!zip->getCurrentFileInfo(&info)) { delete zip; return QStringList(); } lst << info.name; //info.name.toLocal8Bit().constData() } // Chiudo il file zip zip->close(); if(zip->getZipError()!=0) { delete zip; return QStringList(); } delete zip; return lst; } workbench-1.1.1/src/Quazip/JlCompress.h000066400000000000000000000103351255417355300200020ustar00rootroot00000000000000#ifndef JLCOMPRESSFOLDER_H_ #define JLCOMPRESSFOLDER_H_ #include "quazip.h" #include "quazipfile.h" #include "quazipfileinfo.h" #include #include #include #include /// Utility class for typical operations. /** This class contains a number of useful static functions to perform simple operations, such as mass ZIP packing or extraction. */ class QUAZIP_EXPORT JlCompress { private: /// Compress a single file. /** \param zip Opened zip to compress the file to. \param fileName The full path to the source file. \param fileDest The full name of the file inside the archive. \return true if success, false otherwise. */ static bool compressFile(QuaZip* zip, QString fileName, QString fileDest); /// Compress a subdirectory. /** \param parentZip Opened zip containing the parent directory. \param dir The full path to the directory to pack. \param parentDir The full path to the directory corresponding to the root of the ZIP. \param recursive Whether to pack sub-directories as well or only files. \return true if success, false otherwise. */ static bool compressSubDir(QuaZip* parentZip, QString dir, QString parentDir, bool recursive = true); /// Extract a single file. /** \param zip The opened zip archive to extract from. \param fileName The full name of the file to extract. \param fileDest The full path to the destination file. \return true if success, false otherwise. */ static bool extractFile(QuaZip* zip, QString fileName, QString fileDest); /// Remove some files. /** \param listFile The list of files to remove. \return true if success, false otherwise. */ static bool removeFile(QStringList listFile); public: /// Compress a single file. /** \param fileCompressed The name of the archive. \param file The file to compress. \return true if success, false otherwise. */ static bool compressFile(QString fileCompressed, QString file); /// Compress a list of files. /** \param fileCompressed The name of the archive. \param files The file list to compress. \return true if success, false otherwise. */ static bool compressFiles(QString fileCompressed, QStringList files); /// Compress a whole directory. /** \param fileCompressed The name of the archive. \param dir The directory to compress. \param recursive Whether to pack the subdirectories as well, or just regular files. \return true if success, false otherwise. */ static bool compressDir(QString fileCompressed, QString dir = QString(), bool recursive = true); public: /// Extract a single file. /** \param fileCompressed The name of the archive. \param fileName The file to extract. \param fileDest The destination file, assumed to be identical to \a file if left empty. \return The list of the full paths of the files extracted, empty on failure. */ static QString extractFile(QString fileCompressed, QString fileName, QString fileDest = QString()); /// Extract a list of files. /** \param fileCompressed The name of the archive. \param files The file list to extract. \param dir The directory to put the files to, the current directory if left empty. \return The list of the full paths of the files extracted, empty on failure. */ static QStringList extractFiles(QString fileCompressed, QStringList files, QString dir = QString()); /// Extract a whole archive. /** \param fileCompressed The name of the archive. \param dir The directory to extract to, the current directory if left empty. \return The list of the full paths of the files extracted, empty on failure. */ static QStringList extractDir(QString fileCompressed, QString dir = QString()); /// Get the file list. /** \return The list of the files in the archive, or, more precisely, the list of the entries, including both files and directories if they are present separately. */ static QStringList getFileList(QString fileCompressed); }; #endif /* JLCOMPRESSFOLDER_H_ */ workbench-1.1.1/src/Quazip/NEWS.txt000066400000000000000000000125551255417355300170730ustar00rootroot00000000000000QuaZIP changes * 2014-01-22 0.6 * Minizip updated to 1.1 (with all the necessary modifications re-done), and that means that... * the long-awaited zip64 support is now available! * A few rather minor fixes. * 2014-01-19 0.5.2 * Some minor bug fixes. * API to access file permissions subfield of the external attributes. * MS VS 2012 Express support. * API to set the default codec used to encode/decode file names (mainly for use by various wrappers such as JlCompress, when you don't have direct access to the underlying QuaZip instance). * 2013-03-02 0.5.1 * Lots of QuaZipDir fixes, thanks to all bug reporters. * Full Qt Creator support. * MS VS 2010 Express support. * Qt5 support (didn't need any source code changes anyway). * Lots of minor bug fixes. * 2012-09-07 0.5 * Added run_moc.bat files for building under Windows in case Qt integration is not available (e. g. VS 2008 Express). * Added the QuaZipDir class to simplify ZIP navigation in terms of directories. * Added the QuaGzipFile class for working with GZIP archives. It was added as a bonus since it has nothing to do with the main purpose of the library. It probably won't get any major improvements, although minor bug fixes are possible. * Added the QuaZIODevice class for working with zlib compression. It has nothing to do with the ZIP format, and therefore the same notice as for the QuaGzipFile applies. * The global comment is no longer erased when adding files to an archive. * Many bug fixes. * 2012-01-14 0.4.4 * Fixed isSequential() test that was causing open() failures on Unix. * Fixed sub-directory compressing in JlCompress. * Added MS VS 2008 solution, compatible with the binary Qt distribution (tested on MS VS 2008 Express, had to run MOC manually due to the lack of plugin in Express). * Fixed extracting directories in JlCompress. * Fixed JlCompress.h includes in the test suite, which used lowercase names thus breaking on case-sensitive systems. * Implemented missing QuaZipFile::getZip() that was only declared. * Fixed reopening closed files. * Fixed possible memory leak in case of open error. * 2011-09-09 0.4.3 * New test suite using QTestLib. * Fixed bytesAvailable(), pos() and atEnd(). * Added ZIP v1.0 support and disabling data descriptor for compatibility with some older software. * Fixed DLL export/import issues for some symbols. * Added QUAZIP_STATIC macro for compiling as a static library or directly including the source. * Added getFileNameList() and getFileInfoList() convenience functions. * Added some buffering to JlCompress to improve performance. * 2011-08-10 0.4.2 * Cmake patch (thanks to Bernhard Rosenkraenzer). * Symbian patch (thanks to Hamish Willee). * Documented the multiple files limitation of QuaZipFile. * Fixed relative paths handling in JlCompress. * Fixed linking to MinGW zlib. * 2011-05-26 0.4.1 * License statement updated to avoid confusion. GPL license removed for the very same reason. * Parts of original package are now clearly marked as modified, just as their license requires. * 2011-05-23 0.4 * QuaZip and QuaZipFile classes now use the Pimpl idiom. This means that future releases will probably be binary compatible with this one, but it also means that this one is binary incompatible with the old ones. * IO API has been rewritten using QIODevice instead of standard C library. Among other things it means that QuaZip now supports files up to 4 GB in size instead of 2 GB. * Added QuaZip methods allowing access to ZIP files represented by any seekable QIODevice implementation (QBuffer is a good example). * 2010-07-23 0.3 * Fixed getComment() for global comments. * Added some useful classes for calculating checksums (thanks to Adam Walczak). * Added some utility classes for working with whole directories (thanks to Roberto Pompermaier). It would be nice if someone documents these in English, though. * Probably fixed some problems with passwords (thanks to Vasiliy Sorokin). I didn't test it, though. * 2008-09-17 0.2.3 * Fixed license notices in sources. * SVN * Fixed a small bug in QuaZipFile::atEnd(). * 2007-01-16 0.2.2 * Added LGPL as alternative license. * Added FAQ documentation page. * 2006-03-21 0.2.1 * Fixed setCommentCodec() bug. * Fixed bug that set month 1-12 instead of 0-11, as specified in zip.h. * Added workaround for Qt's bug that caused wrong timestamps. * Few documentation fixes and cosmetic changes. * 2005-07-08 0.2 * Write support. * Extended QuaZipFile API, including size(), *pos() functions. * Support for comments encoding/decoding. * 2005-07-01 0.1 * Initial version. workbench-1.1.1/src/Quazip/README.txt000066400000000000000000000045021255417355300172450ustar00rootroot00000000000000QuaZIP is the C++ wrapper for Gilles Vollant's ZIP/UNZIP package (AKA minizip) using Trolltech's Qt library. It uses existing ZIP/UNZIP package C code and therefore depends on the zlib library. Also, it depends on Qt 4. To compile it on UNIX dialect: $ cd quazip $ qmake $ make You must make sure that: * You have Qt 4 properly and fully installed (including tools and headers, not just library) * "qmake" command runs Qt 4's qmake, not some other version (you'll have to type full path to qmake otherwise). To install compiled shared library, just type: $ make install By default, it installs in /usr/local, but you may change it using $ qmake PREFIX=/wherever/you/want/to/install You do not have to compile and install QuaZIP to use it. You can just (and sometimes it may be the best way) add QuaZIP's source files to your project and use them. See doc/html or, if you do not have a browser, quazip/*.h and quazip/doc/* files for the more detailed documentation. For Windows, it's essentially the same, but you may have to adjust settings for different environments. If you want to include QuaZIP sources directly in your project or if you want to use QuaZIP compiled as a static library using "qmake CONFIG+=statliclib", you have to define the QUAZIP_STATIC macro, otherwise you're likely to run into problems as QuaZIP symbols will be marked as dllimported. Copyright notice: Copyright (C) 2005-2012 Sergey A. Tachenov This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA See COPYING file for the full LGPL text. Original ZIP package is copyrighted by Gilles Vollant, see quazip/(un)zip.h files for details, basically it's zlib license. workbench-1.1.1/src/Quazip/crypt.h000066400000000000000000000115171255417355300170650ustar00rootroot00000000000000/* crypt.h -- base code for crypt/uncrypt ZIPfile Version 1.01e, February 12th, 2005 Copyright (C) 1998-2005 Gilles Vollant This code is a modified version of crypting code in Infozip distribution The encryption/decryption parts of this source code (as opposed to the non-echoing password parts) were originally written in Europe. The whole source package can be freely distributed, including from the USA. (Prior to January 2000, re-export from the US was a violation of US law.) This encryption code is a direct transcription of the algorithm from Roger Schlafly, described by Phil Katz in the file appnote.txt. This file (appnote.txt) is distributed with the PKZIP program (even in the version without encryption capabilities). If you don't need crypting in your application, just define symbols NOCRYPT and NOUNCRYPT. This code support the "Traditional PKWARE Encryption". The new AES encryption added on Zip format by Winzip (see the page http://www.winzip.com/aes_info.htm ) and PKWare PKZip 5.x Strong Encryption is not supported. */ #include "quazip_global.h" #define CRC32(c, b) ((*(pcrc_32_tab+(((int)(c) ^ (b)) & 0xff))) ^ ((c) >> 8)) /*********************************************************************** * Return the next byte in the pseudo-random sequence */ static int decrypt_byte(unsigned long* pkeys, const z_crc_t FAR * pcrc_32_tab UNUSED) { //(void) pcrc_32_tab; /* avoid "unused parameter" warning */ unsigned temp; /* POTENTIAL BUG: temp*(temp^1) may overflow in an * unpredictable manner on 16-bit systems; not a problem * with any known compiler so far, though */ temp = ((unsigned)(*(pkeys+2)) & 0xffff) | 2; return (int)(((temp * (temp ^ 1)) >> 8) & 0xff); } /*********************************************************************** * Update the encryption keys with the next byte of plain text */ static int update_keys(unsigned long* pkeys,const z_crc_t FAR * pcrc_32_tab,int c) { (*(pkeys+0)) = CRC32((*(pkeys+0)), c); (*(pkeys+1)) += (*(pkeys+0)) & 0xff; (*(pkeys+1)) = (*(pkeys+1)) * 134775813L + 1; { register int keyshift = (int)((*(pkeys+1)) >> 24); (*(pkeys+2)) = CRC32((*(pkeys+2)), keyshift); } return c; } /*********************************************************************** * Initialize the encryption keys and the random header according to * the given password. */ static void init_keys(const char* passwd,unsigned long* pkeys,const z_crc_t FAR * pcrc_32_tab) { *(pkeys+0) = 305419896L; *(pkeys+1) = 591751049L; *(pkeys+2) = 878082192L; while (*passwd != '\0') { update_keys(pkeys,pcrc_32_tab,(int)*passwd); passwd++; } } #define zdecode(pkeys,pcrc_32_tab,c) \ (update_keys(pkeys,pcrc_32_tab,c ^= decrypt_byte(pkeys,pcrc_32_tab))) #define zencode(pkeys,pcrc_32_tab,c,t) \ (t=decrypt_byte(pkeys,pcrc_32_tab), update_keys(pkeys,pcrc_32_tab,c), t^(c)) #ifdef INCLUDECRYPTINGCODE_IFCRYPTALLOWED #define RAND_HEAD_LEN 12 /* "last resort" source for second part of crypt seed pattern */ # ifndef ZCR_SEED2 # define ZCR_SEED2 3141592654UL /* use PI as default pattern */ # endif static int crypthead(passwd, buf, bufSize, pkeys, pcrc_32_tab, crcForCrypting) const char *passwd; /* password string */ unsigned char *buf; /* where to write header */ int bufSize; unsigned long* pkeys; const z_crc_t FAR * pcrc_32_tab; unsigned long crcForCrypting; { int n; /* index in random header */ int t; /* temporary */ int c; /* random byte */ unsigned char header[RAND_HEAD_LEN-2]; /* random header */ static unsigned calls = 0; /* ensure different random header each time */ if (bufSize> 7) & 0xff; header[n] = (unsigned char)zencode(pkeys, pcrc_32_tab, c, t); } /* Encrypt random header (last two bytes is high word of crc) */ init_keys(passwd, pkeys, pcrc_32_tab); for (n = 0; n < RAND_HEAD_LEN-2; n++) { buf[n] = (unsigned char)zencode(pkeys, pcrc_32_tab, header[n], t); } buf[n++] = zencode(pkeys, pcrc_32_tab, (int)(crcForCrypting >> 16) & 0xff, t); buf[n++] = zencode(pkeys, pcrc_32_tab, (int)(crcForCrypting >> 24) & 0xff, t); return n; } #endif workbench-1.1.1/src/Quazip/ioapi.h000066400000000000000000000157301255417355300170260ustar00rootroot00000000000000/* ioapi.h -- IO base function header for compress/uncompress .zip part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html ) Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html ) Modifications for Zip64 support Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com ) Modified by Sergey A. Tachenov to allow QIODevice API usage. For more info read MiniZip_info.txt Changes Oct-2009 - Defined ZPOS64_T to fpos_t on windows and u_int64_t on linux. (might need to find a better why for this) Oct-2009 - Change to fseeko64, ftello64 and fopen64 so large files would work on linux. More if/def section may be needed to support other platforms Oct-2009 - Defined fxxxx64 calls to normal fopen/ftell/fseek so they would compile on windows. (but you should use iowin32.c for windows instead) */ #ifndef _ZLIBIOAPI64_H #define _ZLIBIOAPI64_H #if (!defined(_WIN32)) && (!defined(WIN32)) // Linux needs this to support file operation on files larger then 4+GB // But might need better if/def to select just the platforms that needs them. #ifndef __USE_FILE_OFFSET64 #define __USE_FILE_OFFSET64 #endif #ifndef __USE_LARGEFILE64 #define __USE_LARGEFILE64 #endif #ifndef _LARGEFILE64_SOURCE #define _LARGEFILE64_SOURCE #endif #ifndef _FILE_OFFSET_BIT #define _FILE_OFFSET_BIT 64 #endif #endif #include #include #include "zlib.h" #if defined(USE_FILE32API) #define fopen64 fopen #define ftello64 ftell #define fseeko64 fseek #else #ifdef _MSC_VER #define fopen64 fopen #if (_MSC_VER >= 1400) && (!(defined(NO_MSCVER_FILE64_FUNC))) #define ftello64 _ftelli64 #define fseeko64 _fseeki64 #else // old MSC #define ftello64 ftell #define fseeko64 fseek #endif #endif #endif /* #ifndef ZPOS64_T #ifdef _WIN32 #define ZPOS64_T fpos_t #else #include #define ZPOS64_T uint64_t #endif #endif */ #ifdef HAVE_MINIZIP64_CONF_H #include "mz64conf.h" #endif /* a type choosen by DEFINE */ #ifdef HAVE_64BIT_INT_CUSTOM typedef 64BIT_INT_CUSTOM_TYPE ZPOS64_T; #else #ifdef HAS_STDINT_H #include "stdint.h" typedef uint64_t ZPOS64_T; #else #if defined(_MSC_VER) || defined(__BORLANDC__) typedef unsigned __int64 ZPOS64_T; #else typedef unsigned long long int ZPOS64_T; #endif #endif #endif #ifdef __cplusplus extern "C" { #endif #ifndef OF #define OF _Z_OF #endif #define ZLIB_FILEFUNC_SEEK_CUR (1) #define ZLIB_FILEFUNC_SEEK_END (2) #define ZLIB_FILEFUNC_SEEK_SET (0) #define ZLIB_FILEFUNC_MODE_READ (1) #define ZLIB_FILEFUNC_MODE_WRITE (2) #define ZLIB_FILEFUNC_MODE_READWRITEFILTER (3) #define ZLIB_FILEFUNC_MODE_EXISTING (4) #define ZLIB_FILEFUNC_MODE_CREATE (8) #ifndef ZCALLBACK #if (defined(WIN32) || defined(_WIN32) || defined (WINDOWS) || defined (_WINDOWS)) && defined(CALLBACK) && defined (USEWINDOWS_CALLBACK) #define ZCALLBACK CALLBACK #else #define ZCALLBACK #endif #endif typedef voidpf (ZCALLBACK *open_file_func) OF((voidpf opaque, voidpf file, int mode)); typedef uLong (ZCALLBACK *read_file_func) OF((voidpf opaque, voidpf stream, void* buf, uLong size)); typedef uLong (ZCALLBACK *write_file_func) OF((voidpf opaque, voidpf stream, const void* buf, uLong size)); typedef int (ZCALLBACK *close_file_func) OF((voidpf opaque, voidpf stream)); typedef int (ZCALLBACK *testerror_file_func) OF((voidpf opaque, voidpf stream)); typedef uLong (ZCALLBACK *tell_file_func) OF((voidpf opaque, voidpf stream)); typedef int (ZCALLBACK *seek_file_func) OF((voidpf opaque, voidpf stream, uLong offset, int origin)); /* here is the "old" 32 bits structure structure */ typedef struct zlib_filefunc_def_s { open_file_func zopen_file; read_file_func zread_file; write_file_func zwrite_file; tell_file_func ztell_file; seek_file_func zseek_file; close_file_func zclose_file; testerror_file_func zerror_file; voidpf opaque; } zlib_filefunc_def; typedef ZPOS64_T (ZCALLBACK *tell64_file_func) OF((voidpf opaque, voidpf stream)); typedef int (ZCALLBACK *seek64_file_func) OF((voidpf opaque, voidpf stream, ZPOS64_T offset, int origin)); typedef voidpf (ZCALLBACK *open64_file_func) OF((voidpf opaque, voidpf file, int mode)); typedef struct zlib_filefunc64_def_s { open64_file_func zopen64_file; read_file_func zread_file; write_file_func zwrite_file; tell64_file_func ztell64_file; seek64_file_func zseek64_file; close_file_func zclose_file; testerror_file_func zerror_file; voidpf opaque; } zlib_filefunc64_def; void fill_qiodevice64_filefunc OF((zlib_filefunc64_def* pzlib_filefunc_def)); void fill_qiodevice_filefunc OF((zlib_filefunc_def* pzlib_filefunc_def)); /* now internal definition, only for zip.c and unzip.h */ typedef struct zlib_filefunc64_32_def_s { zlib_filefunc64_def zfile_func64; open_file_func zopen32_file; tell_file_func ztell32_file; seek_file_func zseek32_file; } zlib_filefunc64_32_def; #define ZREAD64(filefunc,filestream,buf,size) ((*((filefunc).zfile_func64.zread_file)) ((filefunc).zfile_func64.opaque,filestream,buf,size)) #define ZWRITE64(filefunc,filestream,buf,size) ((*((filefunc).zfile_func64.zwrite_file)) ((filefunc).zfile_func64.opaque,filestream,buf,size)) //#define ZTELL64(filefunc,filestream) ((*((filefunc).ztell64_file)) ((filefunc).opaque,filestream)) //#define ZSEEK64(filefunc,filestream,pos,mode) ((*((filefunc).zseek64_file)) ((filefunc).opaque,filestream,pos,mode)) #define ZCLOSE64(filefunc,filestream) ((*((filefunc).zfile_func64.zclose_file)) ((filefunc).zfile_func64.opaque,filestream)) #define ZERROR64(filefunc,filestream) ((*((filefunc).zfile_func64.zerror_file)) ((filefunc).zfile_func64.opaque,filestream)) voidpf call_zopen64 OF((const zlib_filefunc64_32_def* pfilefunc,voidpf file,int mode)); int call_zseek64 OF((const zlib_filefunc64_32_def* pfilefunc,voidpf filestream, ZPOS64_T offset, int origin)); ZPOS64_T call_ztell64 OF((const zlib_filefunc64_32_def* pfilefunc,voidpf filestream)); void fill_zlib_filefunc64_32_def_from_filefunc32(zlib_filefunc64_32_def* p_filefunc64_32,const zlib_filefunc_def* p_filefunc32); #define ZOPEN64(filefunc,filename,mode) (call_zopen64((&(filefunc)),(filename),(mode))) #define ZTELL64(filefunc,filestream) (call_ztell64((&(filefunc)),(filestream))) #define ZSEEK64(filefunc,filestream,pos,mode) (call_zseek64((&(filefunc)),(filestream),(pos),(mode))) #ifdef __cplusplus } #endif #endif workbench-1.1.1/src/Quazip/qioapi.cpp000066400000000000000000000164541255417355300175460ustar00rootroot00000000000000/* ioapi.c -- IO base function header for compress/uncompress .zip files using zlib + zip or unzip API Version 1.01e, February 12th, 2005 Copyright (C) 1998-2005 Gilles Vollant Modified by Sergey A. Tachenov to integrate with Qt. */ #include #include #include #include "zlib.h" #include "ioapi.h" #include "quazip_global.h" #include /* I've found an old Unix (a SunOS 4.1.3_U1) without all SEEK_* defined.... */ #ifndef SEEK_CUR #define SEEK_CUR 1 #endif #ifndef SEEK_END #define SEEK_END 2 #endif #ifndef SEEK_SET #define SEEK_SET 0 #endif voidpf call_zopen64 (const zlib_filefunc64_32_def* pfilefunc,voidpf file,int mode) { if (pfilefunc->zfile_func64.zopen64_file != NULL) return (*(pfilefunc->zfile_func64.zopen64_file)) (pfilefunc->zfile_func64.opaque,file,mode); else { return (*(pfilefunc->zopen32_file))(pfilefunc->zfile_func64.opaque,file,mode); } } int call_zseek64 (const zlib_filefunc64_32_def* pfilefunc,voidpf filestream, ZPOS64_T offset, int origin) { if (pfilefunc->zfile_func64.zseek64_file != NULL) return (*(pfilefunc->zfile_func64.zseek64_file)) (pfilefunc->zfile_func64.opaque,filestream,offset,origin); else { uLong offsetTruncated = (uLong)offset; if (offsetTruncated != offset) return -1; else return (*(pfilefunc->zseek32_file))(pfilefunc->zfile_func64.opaque,filestream,offsetTruncated,origin); } } ZPOS64_T call_ztell64 (const zlib_filefunc64_32_def* pfilefunc,voidpf filestream) { if (pfilefunc->zfile_func64.zseek64_file != NULL) return (*(pfilefunc->zfile_func64.ztell64_file)) (pfilefunc->zfile_func64.opaque,filestream); else { uLong tell_uLong = (*(pfilefunc->ztell32_file))(pfilefunc->zfile_func64.opaque,filestream); if ((tell_uLong) == ((uLong)-1)) return (ZPOS64_T)-1; else return tell_uLong; } } voidpf ZCALLBACK qiodevice_open_file_func ( voidpf /*opaque UNUSED*/, voidpf file, int mode) { QIODevice *iodevice = reinterpret_cast(file); if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER)==ZLIB_FILEFUNC_MODE_READ) iodevice->open(QIODevice::ReadOnly); else if (mode & ZLIB_FILEFUNC_MODE_EXISTING) iodevice->open(QIODevice::ReadWrite); else if (mode & ZLIB_FILEFUNC_MODE_CREATE) iodevice->open(QIODevice::WriteOnly); if (iodevice->isOpen()) { if (iodevice->isSequential()) { iodevice->close(); return NULL; } else { return iodevice; } } else return NULL; } uLong ZCALLBACK qiodevice_read_file_func ( voidpf /*opaque UNUSED*/, voidpf stream, void* buf, uLong size) { uLong ret; ret = (uLong)((QIODevice*)stream)->read((char*)buf,size); return ret; } uLong ZCALLBACK qiodevice_write_file_func ( voidpf /*opaque UNUSED*/, voidpf stream, const void* buf, uLong size) { uLong ret; ret = (uLong)((QIODevice*)stream)->write((char*)buf,size); return ret; } uLong ZCALLBACK qiodevice_tell_file_func ( voidpf /*opaque UNUSED*/, voidpf stream) { uLong ret; ret = ((QIODevice*)stream)->pos(); return ret; } ZPOS64_T ZCALLBACK qiodevice64_tell_file_func ( voidpf /*opaque UNUSED*/, voidpf stream) { qint64 ret; ret = ((QIODevice*)stream)->pos(); return static_cast(ret); } int ZCALLBACK qiodevice_seek_file_func ( voidpf /*opaque UNUSED*/, voidpf stream, uLong offset, int origin) { uLong qiodevice_seek_result=0; int ret; switch (origin) { case ZLIB_FILEFUNC_SEEK_CUR : qiodevice_seek_result = ((QIODevice*)stream)->pos() + offset; break; case ZLIB_FILEFUNC_SEEK_END : qiodevice_seek_result = ((QIODevice*)stream)->size() - offset; break; case ZLIB_FILEFUNC_SEEK_SET : qiodevice_seek_result = offset; break; default: return -1; } ret = !((QIODevice*)stream)->seek(qiodevice_seek_result); return ret; } int ZCALLBACK qiodevice64_seek_file_func ( voidpf /*opaque UNUSED*/, voidpf stream, ZPOS64_T offset, int origin) { qint64 qiodevice_seek_result=0; int ret; switch (origin) { case ZLIB_FILEFUNC_SEEK_CUR : qiodevice_seek_result = ((QIODevice*)stream)->pos() + offset; break; case ZLIB_FILEFUNC_SEEK_END : qiodevice_seek_result = ((QIODevice*)stream)->size() - offset; break; case ZLIB_FILEFUNC_SEEK_SET : qiodevice_seek_result = offset; break; default: return -1; } ret = !((QIODevice*)stream)->seek(qiodevice_seek_result); return ret; } int ZCALLBACK qiodevice_close_file_func ( voidpf /*opaque UNUSED*/, voidpf stream) { ((QIODevice*)stream)->close(); return 0; } int ZCALLBACK qiodevice_error_file_func ( voidpf /*opaque UNUSED*/, voidpf /*stream UNUSED*/) { // can't check for error due to the QIODevice API limitation return 0; } void fill_qiodevice_filefunc ( zlib_filefunc_def* pzlib_filefunc_def) { pzlib_filefunc_def->zopen_file = qiodevice_open_file_func; pzlib_filefunc_def->zread_file = qiodevice_read_file_func; pzlib_filefunc_def->zwrite_file = qiodevice_write_file_func; pzlib_filefunc_def->ztell_file = qiodevice_tell_file_func; pzlib_filefunc_def->zseek_file = qiodevice_seek_file_func; pzlib_filefunc_def->zclose_file = qiodevice_close_file_func; pzlib_filefunc_def->zerror_file = qiodevice_error_file_func; pzlib_filefunc_def->opaque = NULL; } void fill_qiodevice64_filefunc ( zlib_filefunc64_def* pzlib_filefunc_def) { // Open functions are the same for Qt. pzlib_filefunc_def->zopen64_file = qiodevice_open_file_func; pzlib_filefunc_def->zread_file = qiodevice_read_file_func; pzlib_filefunc_def->zwrite_file = qiodevice_write_file_func; pzlib_filefunc_def->ztell64_file = qiodevice64_tell_file_func; pzlib_filefunc_def->zseek64_file = qiodevice64_seek_file_func; pzlib_filefunc_def->zclose_file = qiodevice_close_file_func; pzlib_filefunc_def->zerror_file = qiodevice_error_file_func; pzlib_filefunc_def->opaque = NULL; } void fill_zlib_filefunc64_32_def_from_filefunc32(zlib_filefunc64_32_def* p_filefunc64_32,const zlib_filefunc_def* p_filefunc32) { p_filefunc64_32->zfile_func64.zopen64_file = NULL; p_filefunc64_32->zopen32_file = p_filefunc32->zopen_file; p_filefunc64_32->zfile_func64.zerror_file = p_filefunc32->zerror_file; p_filefunc64_32->zfile_func64.zread_file = p_filefunc32->zread_file; p_filefunc64_32->zfile_func64.zwrite_file = p_filefunc32->zwrite_file; p_filefunc64_32->zfile_func64.ztell64_file = NULL; p_filefunc64_32->zfile_func64.zseek64_file = NULL; p_filefunc64_32->zfile_func64.zclose_file = p_filefunc32->zclose_file; p_filefunc64_32->zfile_func64.zerror_file = p_filefunc32->zerror_file; p_filefunc64_32->zfile_func64.opaque = p_filefunc32->opaque; p_filefunc64_32->zseek32_file = p_filefunc32->zseek_file; p_filefunc64_32->ztell32_file = p_filefunc32->ztell_file; } workbench-1.1.1/src/Quazip/quaadler32.cpp000066400000000000000000000007211255417355300202150ustar00rootroot00000000000000#include "quaadler32.h" #include "zlib.h" QuaAdler32::QuaAdler32() { reset(); } quint32 QuaAdler32::calculate(const QByteArray &data) { return adler32( adler32(0L, Z_NULL, 0), (const Bytef*)data.data(), data.size() ); } void QuaAdler32::reset() { checksum = adler32(0L, Z_NULL, 0); } void QuaAdler32::update(const QByteArray &buf) { checksum = adler32( checksum, (const Bytef*)buf.data(), buf.size() ); } quint32 QuaAdler32::value() { return checksum; } workbench-1.1.1/src/Quazip/quaadler32.h000066400000000000000000000010351255417355300176610ustar00rootroot00000000000000#ifndef QUAADLER32_H #define QUAADLER32_H #include #include "quachecksum32.h" /// Adler32 checksum /** \class QuaAdler32 quaadler32.h * This class wrappers the adler32 function with the QuaChecksum32 interface. * See QuaChecksum32 for more info. */ class QUAZIP_EXPORT QuaAdler32 : public QuaChecksum32 { public: QuaAdler32(); quint32 calculate(const QByteArray &data); void reset(); void update(const QByteArray &buf); quint32 value(); private: quint32 checksum; }; #endif //QUAADLER32_H workbench-1.1.1/src/Quazip/quachecksum32.h000066400000000000000000000025701255417355300204010ustar00rootroot00000000000000#ifndef QUACHECKSUM32_H #define QUACHECKSUM32_H #include #include "quazip_global.h" /// Checksum interface. /** \class QuaChecksum32 quachecksum32.h * This is an interface for 32 bit checksums. * Classes implementing this interface can calcunate a certin * checksum in a single step: * \code * QChecksum32 *crc32 = new QuaCrc32(); * rasoult = crc32->calculate(data); * \endcode * or by streaming the data: * \code * QChecksum32 *crc32 = new QuaCrc32(); * while(!fileA.atEnd()) * crc32->update(fileA.read(bufSize)); * resoultA = crc32->value(); * crc32->reset(); * while(!fileB.atEnd()) * crc32->update(fileB.read(bufSize)); * resoultB = crc32->value(); * \endcode */ class QUAZIP_EXPORT QuaChecksum32 { public: ///Calculates the checksum for data. /** \a data source data * \return data checksum * * This function has no efect on the value returned by value(). */ virtual quint32 calculate(const QByteArray &data) = 0; ///Resets the calculation on a checksun for a stream. virtual void reset() = 0; ///Updates the calculated checksum for the stream /** \a buf next portion of data from the stream */ virtual void update(const QByteArray &buf) = 0; ///Value of the checksum calculated for the stream passed throw update(). /** \return checksum */ virtual quint32 value() = 0; }; #endif //QUACHECKSUM32_H workbench-1.1.1/src/Quazip/quacrc32.cpp000066400000000000000000000006731255417355300177030ustar00rootroot00000000000000#include "quacrc32.h" #include "zlib.h" QuaCrc32::QuaCrc32() { reset(); } quint32 QuaCrc32::calculate(const QByteArray &data) { return crc32( crc32(0L, Z_NULL, 0), (const Bytef*)data.data(), data.size() ); } void QuaCrc32::reset() { checksum = crc32(0L, Z_NULL, 0); } void QuaCrc32::update(const QByteArray &buf) { checksum = crc32( checksum, (const Bytef*)buf.data(), buf.size() ); } quint32 QuaCrc32::value() { return checksum; } workbench-1.1.1/src/Quazip/quacrc32.h000066400000000000000000000007561255417355300173520ustar00rootroot00000000000000#ifndef QUACRC32_H #define QUACRC32_H #include "quachecksum32.h" ///CRC32 checksum /** \class QuaCrc32 quacrc32.h * This class wrappers the crc32 function with the QuaChecksum32 interface. * See QuaChecksum32 for more info. */ class QUAZIP_EXPORT QuaCrc32 : public QuaChecksum32 { public: QuaCrc32(); quint32 calculate(const QByteArray &data); void reset(); void update(const QByteArray &buf); quint32 value(); private: quint32 checksum; }; #endif //QUACRC32_H workbench-1.1.1/src/Quazip/quagzipfile.cpp000066400000000000000000000067641255417355300206070ustar00rootroot00000000000000#include #include "quagzipfile.h" /// \cond internal class QuaGzipFilePrivate { friend class QuaGzipFile; QString fileName; gzFile gzd; inline QuaGzipFilePrivate(): gzd(NULL) {} inline QuaGzipFilePrivate(const QString &fileName): fileName(fileName), gzd(NULL) {} template bool open(FileId id, QIODevice::OpenMode mode, QString &error); gzFile open(int fd, const char *modeString); gzFile open(const QString &name, const char *modeString); }; gzFile QuaGzipFilePrivate::open(const QString &name, const char *modeString) { return gzopen(QFile::encodeName(name).constData(), modeString); } gzFile QuaGzipFilePrivate::open(int fd, const char *modeString) { return gzdopen(fd, modeString); } template bool QuaGzipFilePrivate::open(FileId id, QIODevice::OpenMode mode, QString &error) { char modeString[2]; modeString[0] = modeString[1] = '\0'; if ((mode & QIODevice::Append) != 0) { error = QuaGzipFile::trUtf8("QIODevice::Append is not " "supported for GZIP"); return false; } if ((mode & QIODevice::ReadOnly) != 0 && (mode & QIODevice::WriteOnly) != 0) { error = QuaGzipFile::trUtf8("Opening gzip for both reading" " and writing is not supported"); return false; } else if ((mode & QIODevice::ReadOnly) != 0) { modeString[0] = 'r'; } else if ((mode & QIODevice::WriteOnly) != 0) { modeString[0] = 'w'; } else { error = QuaGzipFile::trUtf8("You can open a gzip either for reading" " or for writing. Which is it?"); return false; } gzd = open(id, modeString); if (gzd == NULL) { error = QuaGzipFile::trUtf8("Could not gzopen() file"); return false; } return true; } /// \endcond QuaGzipFile::QuaGzipFile(): d(new QuaGzipFilePrivate()) { } QuaGzipFile::QuaGzipFile(QObject *parent): QIODevice(parent), d(new QuaGzipFilePrivate()) { } QuaGzipFile::QuaGzipFile(const QString &fileName, QObject *parent): QIODevice(parent), d(new QuaGzipFilePrivate(fileName)) { } QuaGzipFile::~QuaGzipFile() { if (isOpen()) { close(); } delete d; } void QuaGzipFile::setFileName(const QString& fileName) { d->fileName = fileName; } QString QuaGzipFile::getFileName() const { return d->fileName; } bool QuaGzipFile::isSequential() const { return true; } bool QuaGzipFile::open(QIODevice::OpenMode mode) { QString error; if (!d->open(d->fileName, mode, error)) { setErrorString(error); return false; } return QIODevice::open(mode); } bool QuaGzipFile::open(int fd, QIODevice::OpenMode mode) { QString error; if (!d->open(fd, mode, error)) { setErrorString(error); return false; } return QIODevice::open(mode); } bool QuaGzipFile::flush() { return gzflush(d->gzd, Z_SYNC_FLUSH) == Z_OK; } void QuaGzipFile::close() { QIODevice::close(); gzclose(d->gzd); } qint64 QuaGzipFile::readData(char *data, qint64 maxSize) { return gzread(d->gzd, (voidp)data, (unsigned)maxSize); } qint64 QuaGzipFile::writeData(const char *data, qint64 maxSize) { if (maxSize == 0) return 0; int written = gzwrite(d->gzd, (voidp)data, (unsigned)maxSize); if (written == 0) return -1; else return written; } workbench-1.1.1/src/Quazip/quagzipfile.h000066400000000000000000000055011255417355300202400ustar00rootroot00000000000000#ifndef QUAZIP_QUAGZIPFILE_H #define QUAZIP_QUAGZIPFILE_H #include #include "quazip_global.h" #include class QuaGzipFilePrivate; /// GZIP file /** This class is a wrapper around GZIP file access functions in zlib. Unlike QuaZip classes, it doesn't allow reading from a GZIP file opened as QIODevice, for example, if your GZIP file is in QBuffer. It only provides QIODevice access to a GZIP file contents, but the GZIP file itself must be identified by its name on disk or by descriptor id. */ class QUAZIP_EXPORT QuaGzipFile: public QIODevice { Q_OBJECT public: /// Empty constructor. /** Must call setFileName() before trying to open. */ QuaGzipFile(); /// Empty constructor with a parent. /** Must call setFileName() before trying to open. \param parent The parent object, as per QObject logic. */ QuaGzipFile(QObject *parent); /// Constructor. /** \param fileName The name of the GZIP file. \param parent The parent object, as per QObject logic. */ QuaGzipFile(const QString &fileName, QObject *parent = NULL); /// Destructor. virtual ~QuaGzipFile(); /// Sets the name of the GZIP file to be opened. void setFileName(const QString& fileName); /// Returns the name of the GZIP file. QString getFileName() const; /// Returns true. /** Strictly speaking, zlib supports seeking for GZIP files, but it is poorly implemented, because there is no way to implement it properly. For reading, seeking backwards is very slow, and for writing, it is downright impossible. Therefore, QuaGzipFile does not support seeking at all. */ virtual bool isSequential() const; /// Opens the file. /** \param mode Can be either QIODevice::Write or QIODevice::Read. ReadWrite and Append aren't supported. */ virtual bool open(QIODevice::OpenMode mode); /// Opens the file. /** \overload \param fd The file descriptor to read/write the GZIP file from/to. \param mode Can be either QIODevice::Write or QIODevice::Read. ReadWrite and Append aren't supported. */ virtual bool open(int fd, QIODevice::OpenMode mode); /// Flushes data to file. /** The data is written using Z_SYNC_FLUSH mode. Doesn't make any sense when reading. */ virtual bool flush(); /// Closes the file. virtual void close(); protected: /// Implementation of QIODevice::readData(). virtual qint64 readData(char *data, qint64 maxSize); /// Implementation of QIODevice::writeData(). virtual qint64 writeData(const char *data, qint64 maxSize); private: // not implemented by design to disable copy QuaGzipFile(const QuaGzipFile &that); QuaGzipFile& operator=(const QuaGzipFile &that); QuaGzipFilePrivate *d; }; #endif // QUAZIP_QUAGZIPFILE_H workbench-1.1.1/src/Quazip/quaziodevice.cpp000066400000000000000000000175201255417355300207470ustar00rootroot00000000000000#include "quaziodevice.h" #define QUAZIO_INBUFSIZE 4096 #define QUAZIO_OUTBUFSIZE 4096 /// \cond internal class QuaZIODevicePrivate { friend class QuaZIODevice; QuaZIODevicePrivate(QIODevice *io); ~QuaZIODevicePrivate(); QIODevice *io; z_stream zins; z_stream zouts; char *inBuf; int inBufPos; int inBufSize; char *outBuf; int outBufPos; int outBufSize; bool zBufError; int doFlush(QString &error); }; QuaZIODevicePrivate::QuaZIODevicePrivate(QIODevice *io): io(io), inBuf(NULL), inBufPos(0), inBufSize(0), outBuf(NULL), outBufPos(0), outBufSize(0), zBufError(false) { zins.zalloc = (alloc_func) NULL; zins.zfree = (free_func) NULL; zins.opaque = NULL; zouts.zalloc = (alloc_func) NULL; zouts.zfree = (free_func) NULL; zouts.opaque = NULL; inBuf = new char[QUAZIO_INBUFSIZE]; outBuf = new char[QUAZIO_OUTBUFSIZE]; #ifdef QUAZIP_ZIODEVICE_DEBUG_OUTPUT debug.setFileName("debug.out"); debug.open(QIODevice::WriteOnly); #endif #ifdef QUAZIP_ZIODEVICE_DEBUG_INPUT indebug.setFileName("debug.in"); indebug.open(QIODevice::WriteOnly); #endif } QuaZIODevicePrivate::~QuaZIODevicePrivate() { #ifdef QUAZIP_ZIODEVICE_DEBUG_OUTPUT debug.close(); #endif #ifdef QUAZIP_ZIODEVICE_DEBUG_INPUT indebug.close(); #endif if (inBuf != NULL) delete[] inBuf; if (outBuf != NULL) delete[] outBuf; } int QuaZIODevicePrivate::doFlush(QString &error) { int flushed = 0; while (outBufPos < outBufSize) { int more = io->write(outBuf + outBufPos, outBufSize - outBufPos); if (more == -1) { error = io->errorString(); return -1; } if (more == 0) break; outBufPos += more; flushed += more; } if (outBufPos == outBufSize) { outBufPos = outBufSize = 0; } return flushed; } /// \endcond // #define QUAZIP_ZIODEVICE_DEBUG_OUTPUT // #define QUAZIP_ZIODEVICE_DEBUG_INPUT #ifdef QUAZIP_ZIODEVICE_DEBUG_OUTPUT #include static QFile debug; #endif #ifdef QUAZIP_ZIODEVICE_DEBUG_INPUT #include static QFile indebug; #endif QuaZIODevice::QuaZIODevice(QIODevice *io, QObject *parent): QIODevice(parent), d(new QuaZIODevicePrivate(io)) { connect(io, SIGNAL(readyRead()), SIGNAL(readyRead())); } QuaZIODevice::~QuaZIODevice() { if (isOpen()) close(); delete d; } QIODevice *QuaZIODevice::getIoDevice() const { return d->io; } bool QuaZIODevice::open(QIODevice::OpenMode mode) { if ((mode & QIODevice::Append) != 0) { setErrorString(trUtf8("QIODevice::Append is not supported for" " QuaZIODevice")); return false; } if ((mode & QIODevice::ReadWrite) == QIODevice::ReadWrite) { setErrorString(trUtf8("QIODevice::ReadWrite is not supported for" " QuaZIODevice")); return false; } if ((mode & QIODevice::ReadOnly) != 0) { if (inflateInit(&d->zins) != Z_OK) { setErrorString(d->zins.msg); return false; } } if ((mode & QIODevice::WriteOnly) != 0) { if (deflateInit(&d->zouts, Z_DEFAULT_COMPRESSION) != Z_OK) { setErrorString(d->zouts.msg); return false; } } return QIODevice::open(mode); } void QuaZIODevice::close() { if ((openMode() & QIODevice::ReadOnly) != 0) { if (inflateEnd(&d->zins) != Z_OK) { setErrorString(d->zins.msg); } } if ((openMode() & QIODevice::WriteOnly) != 0) { flush(); if (deflateEnd(&d->zouts) != Z_OK) { setErrorString(d->zouts.msg); } } QIODevice::close(); } qint64 QuaZIODevice::readData(char *data, qint64 maxSize) { int read = 0; while (read < maxSize) { if (d->inBufPos == d->inBufSize) { d->inBufPos = 0; d->inBufSize = d->io->read(d->inBuf, QUAZIO_INBUFSIZE); if (d->inBufSize == -1) { d->inBufSize = 0; setErrorString(d->io->errorString()); return -1; } if (d->inBufSize == 0) break; } while (read < maxSize && d->inBufPos < d->inBufSize) { d->zins.next_in = (Bytef *) (d->inBuf + d->inBufPos); d->zins.avail_in = d->inBufSize - d->inBufPos; d->zins.next_out = (Bytef *) (data + read); d->zins.avail_out = (uInt) (maxSize - read); // hope it's less than 2GB int more = 0; switch (inflate(&d->zins, Z_SYNC_FLUSH)) { case Z_OK: read = (char *) d->zins.next_out - data; d->inBufPos = (char *) d->zins.next_in - d->inBuf; break; case Z_STREAM_END: read = (char *) d->zins.next_out - data; d->inBufPos = (char *) d->zins.next_in - d->inBuf; return read; case Z_BUF_ERROR: // this should never happen, but just in case if (!d->zBufError) { qWarning("Z_BUF_ERROR detected with %d/%d in/out, weird", d->zins.avail_in, d->zins.avail_out); d->zBufError = true; } memmove(d->inBuf, d->inBuf + d->inBufPos, d->inBufSize - d->inBufPos); d->inBufSize -= d->inBufPos; d->inBufPos = 0; more = d->io->read(d->inBuf + d->inBufSize, QUAZIO_INBUFSIZE - d->inBufSize); if (more == -1) { setErrorString(d->io->errorString()); return -1; } if (more == 0) return read; d->inBufSize += more; break; default: setErrorString(QString::fromLocal8Bit(d->zins.msg)); return -1; } } } #ifdef QUAZIP_ZIODEVICE_DEBUG_INPUT indebug.write(data, read); #endif return read; } qint64 QuaZIODevice::writeData(const char *data, qint64 maxSize) { int written = 0; QString error; if (d->doFlush(error) == -1) { setErrorString(error); return -1; } while (written < maxSize) { // there is some data waiting in the output buffer if (d->outBufPos < d->outBufSize) return written; d->zouts.next_in = (Bytef *) (data + written); d->zouts.avail_in = (uInt) (maxSize - written); // hope it's less than 2GB d->zouts.next_out = (Bytef *) d->outBuf; d->zouts.avail_out = QUAZIO_OUTBUFSIZE; switch (deflate(&d->zouts, Z_NO_FLUSH)) { case Z_OK: written = (char *) d->zouts.next_in - data; d->outBufSize = (char *) d->zouts.next_out - d->outBuf; break; default: setErrorString(QString::fromLocal8Bit(d->zouts.msg)); return -1; } if (d->doFlush(error) == -1) { setErrorString(error); return -1; } } #ifdef QUAZIP_ZIODEVICE_DEBUG_OUTPUT debug.write(data, written); #endif return written; } bool QuaZIODevice::flush() { QString error; if (d->doFlush(error) < 0) { setErrorString(error); return false; } // can't flush buffer, some data is still waiting if (d->outBufPos < d->outBufSize) return true; Bytef c = 0; d->zouts.next_in = &c; // fake input buffer d->zouts.avail_in = 0; // of zero size do { d->zouts.next_out = (Bytef *) d->outBuf; d->zouts.avail_out = QUAZIO_OUTBUFSIZE; switch (deflate(&d->zouts, Z_SYNC_FLUSH)) { case Z_OK: d->outBufSize = (char *) d->zouts.next_out - d->outBuf; if (d->doFlush(error) < 0) { setErrorString(error); return false; } if (d->outBufPos < d->outBufSize) return true; break; case Z_BUF_ERROR: // nothing to write? return true; default: setErrorString(QString::fromLocal8Bit(d->zouts.msg)); return false; } } while (d->zouts.avail_out == 0); return true; } bool QuaZIODevice::isSequential() const { return true; } workbench-1.1.1/src/Quazip/quaziodevice.h000066400000000000000000000045651255417355300204210ustar00rootroot00000000000000#ifndef QUAZIP_QUAZIODEVICE_H #define QUAZIP_QUAZIODEVICE_H #include #include "quazip_global.h" #include class QuaZIODevicePrivate; /// A class to compress/decompress QIODevice. /** This class can be used to compress any data written to QIODevice or decompress it back. Compressing data sent over a QTcpSocket is a good example. */ class QUAZIP_EXPORT QuaZIODevice: public QIODevice { Q_OBJECT public: /// Constructor. /** \param io The QIODevice to read/write. \param parent The parent object, as per QObject logic. */ QuaZIODevice(QIODevice *io, QObject *parent = NULL); /// Destructor. ~QuaZIODevice(); /// Flushes data waiting to be written. /** Unfortunately, as QIODevice doesn't support flush() by itself, the only thing this method does is write the compressed data into the device using Z_SYNC_FLUSH mode. If you need the compressed data to actually be flushed from the buffer of the underlying QIODevice, you need to call its flush() method as well, providing it supports it (like QTcpSocket does). Example: \code QuaZIODevice dev(&sock); dev.open(QIODevice::Write); dev.write(yourDataGoesHere); dev.flush(); sock->flush(); // this actually sends data to network \endcode This may change in the future versions of QuaZIP by implementing an ugly hack: trying to cast the QIODevice using qobject_cast to known flush()-supporting subclasses, and calling flush if the resulting pointer is not zero. */ virtual bool flush(); /// Opens the device. /** \param mode Neither QIODevice::ReadWrite nor QIODevice::Append are not supported. */ virtual bool open(QIODevice::OpenMode mode); /// Closes this device, but not the underlying one. /** The underlying QIODevice is not closed in case you want to write something else to it. */ virtual void close(); /// Returns the underlying device. QIODevice *getIoDevice() const; /// Returns true. virtual bool isSequential() const; protected: /// Implementation of QIODevice::readData(). virtual qint64 readData(char *data, qint64 maxSize); /// Implementation of QIODevice::writeData(). virtual qint64 writeData(const char *data, qint64 maxSize); private: QuaZIODevicePrivate *d; }; #endif // QUAZIP_QUAZIODEVICE_H workbench-1.1.1/src/Quazip/quazip.cpp000066400000000000000000000507201255417355300175670ustar00rootroot00000000000000/* Copyright (C) 2005-2011 Sergey A. Tachenov This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA See COPYING file for the full LGPL text. Original ZIP package is copyrighted by Gilles Vollant, see quazip/(un)zip.h files for details, basically it's zlib license. **/ #include #include #include #include "quazip.h" /// All the internal stuff for the QuaZip class. /** \internal This class keeps all the private stuff for the QuaZip class so it can be changed without breaking binary compatibility, according to the Pimpl idiom. */ class QuaZipPrivate { friend class QuaZip; private: /// The pointer to the corresponding QuaZip instance. QuaZip *q; /// The codec for file names. QTextCodec *fileNameCodec; /// The codec for comments. QTextCodec *commentCodec; /// The archive file name. QString zipName; /// The device to access the archive. QIODevice *ioDevice; /// The global comment. QString comment; /// The open mode. QuaZip::Mode mode; union { /// The internal handle for UNZIP modes. unzFile unzFile_f; /// The internal handle for ZIP modes. zipFile zipFile_f; }; /// Whether a current file is set. bool hasCurrentFile_f; /// The last error. int zipError; /// Whether \ref QuaZip::setDataDescriptorWritingEnabled() "the data descriptor writing mode" is enabled. bool dataDescriptorWritingEnabled; /// The zip64 mode. bool zip64; inline QTextCodec *getDefaultFileNameCodec() { if (defaultFileNameCodec == NULL) { return QTextCodec::codecForLocale(); } else { return defaultFileNameCodec; } } /// The constructor for the corresponding QuaZip constructor. inline QuaZipPrivate(QuaZip *q): q(q), fileNameCodec(getDefaultFileNameCodec()), commentCodec(QTextCodec::codecForLocale()), ioDevice(NULL), mode(QuaZip::mdNotOpen), hasCurrentFile_f(false), zipError(UNZ_OK), dataDescriptorWritingEnabled(true), zip64(false) { lastMappedDirectoryEntry.num_of_file = 0; lastMappedDirectoryEntry.pos_in_zip_directory = 0; } /// The constructor for the corresponding QuaZip constructor. inline QuaZipPrivate(QuaZip *q, const QString &zipName): q(q), fileNameCodec(getDefaultFileNameCodec()), commentCodec(QTextCodec::codecForLocale()), zipName(zipName), ioDevice(NULL), mode(QuaZip::mdNotOpen), hasCurrentFile_f(false), zipError(UNZ_OK), dataDescriptorWritingEnabled(true), zip64(false) { lastMappedDirectoryEntry.num_of_file = 0; lastMappedDirectoryEntry.pos_in_zip_directory = 0; } /// The constructor for the corresponding QuaZip constructor. inline QuaZipPrivate(QuaZip *q, QIODevice *ioDevice): q(q), fileNameCodec(getDefaultFileNameCodec()), commentCodec(QTextCodec::codecForLocale()), ioDevice(ioDevice), mode(QuaZip::mdNotOpen), hasCurrentFile_f(false), zipError(UNZ_OK), dataDescriptorWritingEnabled(true), zip64(false) { lastMappedDirectoryEntry.num_of_file = 0; lastMappedDirectoryEntry.pos_in_zip_directory = 0; } /// Returns either a list of file names or a list of QuaZipFileInfo. template bool getFileInfoList(QList *result) const; /// Stores map of filenames and file locations for unzipping inline void clearDirectoryMap(); inline void addCurrentFileToDirectoryMap(const QString &fileName); bool goToFirstUnmappedFile(); QHash directoryCaseSensitive; QHash directoryCaseInsensitive; unz64_file_pos lastMappedDirectoryEntry; static QTextCodec *defaultFileNameCodec; }; QTextCodec *QuaZipPrivate::defaultFileNameCodec = NULL; void QuaZipPrivate::clearDirectoryMap() { directoryCaseInsensitive.clear(); directoryCaseSensitive.clear(); lastMappedDirectoryEntry.num_of_file = 0; lastMappedDirectoryEntry.pos_in_zip_directory = 0; } void QuaZipPrivate::addCurrentFileToDirectoryMap(const QString &fileName) { if (!hasCurrentFile_f || fileName.isEmpty()) { return; } // Adds current file to filename map as fileName unz64_file_pos fileDirectoryPos; unzGetFilePos64(unzFile_f, &fileDirectoryPos); directoryCaseSensitive.insert(fileName, fileDirectoryPos); // Only add lowercase to directory map if not already there // ensures only map the first one seen QString lower = fileName.toLower(); if (!directoryCaseInsensitive.contains(lower)) directoryCaseInsensitive.insert(lower, fileDirectoryPos); // Mark last one if (fileDirectoryPos.pos_in_zip_directory > lastMappedDirectoryEntry.pos_in_zip_directory) lastMappedDirectoryEntry = fileDirectoryPos; } bool QuaZipPrivate::goToFirstUnmappedFile() { zipError = UNZ_OK; if (mode != QuaZip::mdUnzip) { qWarning("QuaZipPrivate::goToNextUnmappedFile(): ZIP is not open in mdUnzip mode"); return false; } // If not mapped anything, go to beginning if (lastMappedDirectoryEntry.pos_in_zip_directory == 0) { unzGoToFirstFile(unzFile_f); } else { // Goto the last one mapped, plus one unzGoToFilePos64(unzFile_f, &lastMappedDirectoryEntry); unzGoToNextFile(unzFile_f); } hasCurrentFile_f=zipError==UNZ_OK; if(zipError==UNZ_END_OF_LIST_OF_FILE) zipError=UNZ_OK; return hasCurrentFile_f; } QuaZip::QuaZip(): p(new QuaZipPrivate(this)) { } QuaZip::QuaZip(const QString& zipName): p(new QuaZipPrivate(this, zipName)) { } QuaZip::QuaZip(QIODevice *ioDevice): p(new QuaZipPrivate(this, ioDevice)) { } QuaZip::~QuaZip() { if(isOpen()) close(); delete p; } bool QuaZip::open(Mode mode, zlib_filefunc_def* ioApi) { p->zipError=UNZ_OK; if(isOpen()) { qWarning("QuaZip::open(): ZIP already opened"); return false; } QIODevice *ioDevice = p->ioDevice; if (ioDevice == NULL) { if (p->zipName.isEmpty()) { qWarning("QuaZip::open(): set either ZIP file name or IO device first"); return false; } else { ioDevice = new QFile(p->zipName); } } switch(mode) { case mdUnzip: if (ioApi == NULL) { p->unzFile_f=unzOpen2_64(ioDevice, NULL); } else { // QuaZIP pre-zip64 compatibility mode p->unzFile_f=unzOpen2(ioDevice, ioApi); } if(p->unzFile_f!=NULL) { p->mode=mode; p->ioDevice = ioDevice; return true; } else { p->zipError=UNZ_OPENERROR; if (!p->zipName.isEmpty()) delete ioDevice; return false; } case mdCreate: case mdAppend: case mdAdd: if (ioApi == NULL) { p->zipFile_f=zipOpen2_64(ioDevice, mode==mdCreate?APPEND_STATUS_CREATE: mode==mdAppend?APPEND_STATUS_CREATEAFTER: APPEND_STATUS_ADDINZIP, NULL, NULL); } else { // QuaZIP pre-zip64 compatibility mode p->zipFile_f=zipOpen2(ioDevice, mode==mdCreate?APPEND_STATUS_CREATE: mode==mdAppend?APPEND_STATUS_CREATEAFTER: APPEND_STATUS_ADDINZIP, NULL, ioApi); } if(p->zipFile_f!=NULL) { p->mode=mode; p->ioDevice = ioDevice; return true; } else { p->zipError=UNZ_OPENERROR; if (!p->zipName.isEmpty()) delete ioDevice; return false; } default: qWarning("QuaZip::open(): unknown mode: %d", (int)mode); if (!p->zipName.isEmpty()) delete ioDevice; return false; break; } } void QuaZip::close() { p->zipError=UNZ_OK; switch(p->mode) { case mdNotOpen: qWarning("QuaZip::close(): ZIP is not open"); return; case mdUnzip: p->zipError=unzClose(p->unzFile_f); break; case mdCreate: case mdAppend: case mdAdd: p->zipError=zipClose(p->zipFile_f, p->comment.isNull() ? NULL : p->commentCodec->fromUnicode(p->comment).constData()); break; default: qWarning("QuaZip::close(): unknown mode: %d", (int)p->mode); return; } // opened by name, need to delete the internal IO device if (!p->zipName.isEmpty()) { delete p->ioDevice; p->ioDevice = NULL; } p->clearDirectoryMap(); if(p->zipError==UNZ_OK) p->mode=mdNotOpen; } void QuaZip::setZipName(const QString& zipName) { if(isOpen()) { qWarning("QuaZip::setZipName(): ZIP is already open!"); return; } p->zipName=zipName; p->ioDevice = NULL; } void QuaZip::setIoDevice(QIODevice *ioDevice) { if(isOpen()) { qWarning("QuaZip::setIoDevice(): ZIP is already open!"); return; } p->ioDevice = ioDevice; p->zipName = QString(); } int QuaZip::getEntriesCount()const { QuaZip *fakeThis=(QuaZip*)this; // non-const fakeThis->p->zipError=UNZ_OK; if(p->mode!=mdUnzip) { qWarning("QuaZip::getEntriesCount(): ZIP is not open in mdUnzip mode"); return -1; } unz_global_info64 globalInfo; if((fakeThis->p->zipError=unzGetGlobalInfo64(p->unzFile_f, &globalInfo))!=UNZ_OK) return p->zipError; return (int)globalInfo.number_entry; } QString QuaZip::getComment()const { QuaZip *fakeThis=(QuaZip*)this; // non-const fakeThis->p->zipError=UNZ_OK; if(p->mode!=mdUnzip) { qWarning("QuaZip::getComment(): ZIP is not open in mdUnzip mode"); return QString(); } unz_global_info64 globalInfo; QByteArray comment; if((fakeThis->p->zipError=unzGetGlobalInfo64(p->unzFile_f, &globalInfo))!=UNZ_OK) return QString(); comment.resize(globalInfo.size_comment); if((fakeThis->p->zipError=unzGetGlobalComment(p->unzFile_f, comment.data(), comment.size())) < 0) return QString(); fakeThis->p->zipError = UNZ_OK; return p->commentCodec->toUnicode(comment); } bool QuaZip::setCurrentFile(const QString& fileName, CaseSensitivity cs) { p->zipError=UNZ_OK; if(p->mode!=mdUnzip) { qWarning("QuaZip::setCurrentFile(): ZIP is not open in mdUnzip mode"); return false; } if(fileName.isEmpty()) { p->hasCurrentFile_f=false; return true; } // Unicode-aware reimplementation of the unzLocateFile function if(p->unzFile_f==NULL) { p->zipError=UNZ_PARAMERROR; return false; } if(fileName.length()>MAX_FILE_NAME_LENGTH) { p->zipError=UNZ_PARAMERROR; return false; } // Find the file by name bool sens = convertCaseSensitivity(cs) == Qt::CaseSensitive; QString lower, current; if(!sens) lower=fileName.toLower(); p->hasCurrentFile_f=false; // Check the appropriate Map unz64_file_pos fileDirPos; fileDirPos.pos_in_zip_directory = 0; if (sens) { if (p->directoryCaseSensitive.contains(fileName)) fileDirPos = p->directoryCaseSensitive.value(fileName); } else { if (p->directoryCaseInsensitive.contains(lower)) fileDirPos = p->directoryCaseInsensitive.value(lower); } if (fileDirPos.pos_in_zip_directory != 0) { p->zipError = unzGoToFilePos64(p->unzFile_f, &fileDirPos); p->hasCurrentFile_f = p->zipError == UNZ_OK; } if (p->hasCurrentFile_f) return p->hasCurrentFile_f; // Not mapped yet, start from where we have got to so far for(bool more=p->goToFirstUnmappedFile(); more; more=goToNextFile()) { current=getCurrentFileName(); if(current.isEmpty()) return false; if(sens) { if(current==fileName) break; } else { if(current.toLower()==lower) break; } } return p->hasCurrentFile_f; } bool QuaZip::goToFirstFile() { p->zipError=UNZ_OK; if(p->mode!=mdUnzip) { qWarning("QuaZip::goToFirstFile(): ZIP is not open in mdUnzip mode"); return false; } p->zipError=unzGoToFirstFile(p->unzFile_f); p->hasCurrentFile_f=p->zipError==UNZ_OK; return p->hasCurrentFile_f; } bool QuaZip::goToNextFile() { p->zipError=UNZ_OK; if(p->mode!=mdUnzip) { qWarning("QuaZip::goToFirstFile(): ZIP is not open in mdUnzip mode"); return false; } p->zipError=unzGoToNextFile(p->unzFile_f); p->hasCurrentFile_f=p->zipError==UNZ_OK; if(p->zipError==UNZ_END_OF_LIST_OF_FILE) p->zipError=UNZ_OK; return p->hasCurrentFile_f; } bool QuaZip::getCurrentFileInfo(QuaZipFileInfo *info)const { QuaZipFileInfo64 info64; if (info == NULL) { // Very unlikely because of the overloads return false; } if (getCurrentFileInfo(&info64)) { info->versionCreated=info64.versionCreated; info->versionNeeded=info64.versionNeeded; info->flags=info64.flags; info->method=info64.method; info->crc=info64.crc; info->compressedSize=static_cast(info64.compressedSize); info->uncompressedSize=static_cast(info64.uncompressedSize); info->diskNumberStart=info64.diskNumberStart; info->internalAttr=info64.internalAttr; info->externalAttr=info64.externalAttr; info->name=info64.name; info->comment=info64.comment; info->extra=info64.extra; info->dateTime=info64.dateTime; return true; } else { return false; } } bool QuaZip::getCurrentFileInfo(QuaZipFileInfo64 *info)const { QuaZip *fakeThis=(QuaZip*)this; // non-const fakeThis->p->zipError=UNZ_OK; if(p->mode!=mdUnzip) { qWarning("QuaZip::getCurrentFileInfo(): ZIP is not open in mdUnzip mode"); return false; } unz_file_info64 info_z; QByteArray fileName; QByteArray extra; QByteArray comment; if(info==NULL) return false; if(!isOpen()||!hasCurrentFile()) return false; if((fakeThis->p->zipError=unzGetCurrentFileInfo64(p->unzFile_f, &info_z, NULL, 0, NULL, 0, NULL, 0))!=UNZ_OK) return false; fileName.resize(info_z.size_filename); extra.resize(info_z.size_file_extra); comment.resize(info_z.size_file_comment); if((fakeThis->p->zipError=unzGetCurrentFileInfo64(p->unzFile_f, NULL, fileName.data(), fileName.size(), extra.data(), extra.size(), comment.data(), comment.size()))!=UNZ_OK) return false; info->versionCreated=info_z.version; info->versionNeeded=info_z.version_needed; info->flags=info_z.flag; info->method=info_z.compression_method; info->crc=info_z.crc; info->compressedSize=info_z.compressed_size; info->uncompressedSize=info_z.uncompressed_size; info->diskNumberStart=info_z.disk_num_start; info->internalAttr=info_z.internal_fa; info->externalAttr=info_z.external_fa; info->name=p->fileNameCodec->toUnicode(fileName); info->comment=p->commentCodec->toUnicode(comment); info->extra=extra; info->dateTime=QDateTime( QDate(info_z.tmu_date.tm_year, info_z.tmu_date.tm_mon+1, info_z.tmu_date.tm_mday), QTime(info_z.tmu_date.tm_hour, info_z.tmu_date.tm_min, info_z.tmu_date.tm_sec)); // Add to directory map p->addCurrentFileToDirectoryMap(info->name); return true; } QString QuaZip::getCurrentFileName()const { QuaZip *fakeThis=(QuaZip*)this; // non-const fakeThis->p->zipError=UNZ_OK; if(p->mode!=mdUnzip) { qWarning("QuaZip::getCurrentFileName(): ZIP is not open in mdUnzip mode"); return QString(); } if(!isOpen()||!hasCurrentFile()) return QString(); QByteArray fileName(MAX_FILE_NAME_LENGTH, 0); if((fakeThis->p->zipError=unzGetCurrentFileInfo64(p->unzFile_f, NULL, fileName.data(), fileName.size(), NULL, 0, NULL, 0))!=UNZ_OK) return QString(); QString result = p->fileNameCodec->toUnicode(fileName.constData()); if (result.isEmpty()) return result; // Add to directory map p->addCurrentFileToDirectoryMap(result); return result; } void QuaZip::setFileNameCodec(QTextCodec *fileNameCodec) { p->fileNameCodec=fileNameCodec; } void QuaZip::setFileNameCodec(const char *fileNameCodecName) { p->fileNameCodec=QTextCodec::codecForName(fileNameCodecName); } QTextCodec *QuaZip::getFileNameCodec()const { return p->fileNameCodec; } void QuaZip::setCommentCodec(QTextCodec *commentCodec) { p->commentCodec=commentCodec; } void QuaZip::setCommentCodec(const char *commentCodecName) { p->commentCodec=QTextCodec::codecForName(commentCodecName); } QTextCodec *QuaZip::getCommentCodec()const { return p->commentCodec; } QString QuaZip::getZipName() const { return p->zipName; } QIODevice *QuaZip::getIoDevice() const { if (!p->zipName.isEmpty()) // opened by name, using an internal QIODevice return NULL; return p->ioDevice; } QuaZip::Mode QuaZip::getMode()const { return p->mode; } bool QuaZip::isOpen()const { return p->mode!=mdNotOpen; } int QuaZip::getZipError() const { return p->zipError; } void QuaZip::setComment(const QString& comment) { p->comment=comment; } bool QuaZip::hasCurrentFile()const { return p->hasCurrentFile_f; } unzFile QuaZip::getUnzFile() { return p->unzFile_f; } zipFile QuaZip::getZipFile() { return p->zipFile_f; } void QuaZip::setDataDescriptorWritingEnabled(bool enabled) { p->dataDescriptorWritingEnabled = enabled; } bool QuaZip::isDataDescriptorWritingEnabled() const { return p->dataDescriptorWritingEnabled; } template TFileInfo QuaZip_getFileInfo(QuaZip *zip, bool *ok); template<> QuaZipFileInfo QuaZip_getFileInfo(QuaZip *zip, bool *ok) { QuaZipFileInfo info; *ok = zip->getCurrentFileInfo(&info); return info; } template<> QString QuaZip_getFileInfo(QuaZip *zip, bool *ok) { QString name = zip->getCurrentFileName(); *ok = !name.isEmpty(); return name; } template bool QuaZipPrivate::getFileInfoList(QList *result) const { QuaZipPrivate *fakeThis=const_cast(this); fakeThis->zipError=UNZ_OK; if (mode!=QuaZip::mdUnzip) { qWarning("QuaZip::getFileNameList/getFileInfoList(): " "ZIP is not open in mdUnzip mode"); return false; } QString currentFile; if (q->hasCurrentFile()) { currentFile = q->getCurrentFileName(); } if (q->goToFirstFile()) { do { bool ok; result->append(QuaZip_getFileInfo(q, &ok)); if (!ok) return false; } while (q->goToNextFile()); } if (zipError != UNZ_OK) return false; if (currentFile.isEmpty()) { if (!q->goToFirstFile()) return false; } else { if (!q->setCurrentFile(currentFile)) return false; } return true; } QStringList QuaZip::getFileNameList() const { QStringList list; if (p->getFileInfoList(&list)) return list; else return QStringList(); } QList QuaZip::getFileInfoList() const { QList list; if (p->getFileInfoList(&list)) return list; else return QList(); } Qt::CaseSensitivity QuaZip::convertCaseSensitivity(QuaZip::CaseSensitivity cs) { if (cs == csDefault) { #ifdef Q_WS_WIN return Qt::CaseInsensitive; #else return Qt::CaseSensitive; #endif } else { return cs == csSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive; } } void QuaZip::setDefaultFileNameCodec(QTextCodec *codec) { QuaZipPrivate::defaultFileNameCodec = codec; } void QuaZip::setDefaultFileNameCodec(const char *codecName) { setDefaultFileNameCodec(QTextCodec::codecForName(codecName)); } void QuaZip::setZip64Enabled(bool zip64) { p->zip64 = zip64; } bool QuaZip::isZip64Enabled() const { return p->zip64; } workbench-1.1.1/src/Quazip/quazip.h000066400000000000000000000532231255417355300172350ustar00rootroot00000000000000#ifndef QUA_ZIP_H #define QUA_ZIP_H /* Copyright (C) 2005-2011 Sergey A. Tachenov This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA See COPYING file for the full LGPL text. Original ZIP package is copyrighted by Gilles Vollant, see quazip/(un)zip.h files for details, basically it's zlib license. **/ #include #include #include #include "zip.h" #include "unzip.h" #include "quazip_global.h" #include "quazipfileinfo.h" // just in case it will be defined in the later versions of the ZIP/UNZIP #ifndef UNZ_OPENERROR // define additional error code #define UNZ_OPENERROR -1000 #endif class QuaZipPrivate; /// ZIP archive. /** \class QuaZip quazip.h * This class implements basic interface to the ZIP archive. It can be * used to read table contents of the ZIP archive and retreiving * information about the files inside it. * * You can also use this class to open files inside archive by passing * pointer to the instance of this class to the constructor of the * QuaZipFile class. But see QuaZipFile::QuaZipFile(QuaZip*, QObject*) * for the possible pitfalls. * * This class is indended to provide interface to the ZIP subpackage of * the ZIP/UNZIP package as well as to the UNZIP subpackage. But * currently it supports only UNZIP. * * The use of this class is simple - just create instance using * constructor, then set ZIP archive file name using setFile() function * (if you did not passed the name to the constructor), then open() and * then use different functions to work with it! Well, if you are * paranoid, you may also wish to call close before destructing the * instance, to check for errors on close. * * You may also use getUnzFile() and getZipFile() functions to get the * ZIP archive handle and use it with ZIP/UNZIP package API directly. * * This class supports localized file names inside ZIP archive, but you * have to set up proper codec with setCodec() function. By default, * locale codec will be used, which is probably ok for UNIX systems, but * will almost certainly fail with ZIP archives created in Windows. This * is because Windows ZIP programs have strange habit of using DOS * encoding for file names in ZIP archives. For example, ZIP archive * with cyrillic names created in Windows will have file names in \c * IBM866 encoding instead of \c WINDOWS-1251. I think that calling one * function is not much trouble, but for true platform independency it * would be nice to have some mechanism for file name encoding auto * detection using locale information. Does anyone know a good way to do * it? **/ class QUAZIP_EXPORT QuaZip { friend class QuaZipPrivate; public: /// Useful constants. enum Constants { MAX_FILE_NAME_LENGTH=256 /**< Maximum file name length. Taken from \c UNZ_MAXFILENAMEINZIP constant in unzip.c. */ }; /// Open mode of the ZIP file. enum Mode { mdNotOpen, ///< ZIP file is not open. This is the initial mode. mdUnzip, ///< ZIP file is open for reading files inside it. mdCreate, ///< ZIP file was created with open() call. mdAppend, /**< ZIP file was opened in append mode. This refers to * \c APPEND_STATUS_CREATEAFTER mode in ZIP/UNZIP package * and means that zip is appended to some existing file * what is useful when that file contains * self-extractor code. This is obviously \em not what * you whant to use to add files to the existing ZIP * archive. **/ mdAdd ///< ZIP file was opened for adding files in the archive. }; /// Case sensitivity for the file names. /** This is what you specify when accessing files in the archive. * Works perfectly fine with any characters thanks to Qt's great * unicode support. This is different from ZIP/UNZIP API, where * only US-ASCII characters was supported. **/ enum CaseSensitivity { csDefault=0, ///< Default for platform. Case sensitive for UNIX, not for Windows. csSensitive=1, ///< Case sensitive. csInsensitive=2 ///< Case insensitive. }; /// Returns the actual case sensitivity for the specified QuaZIP one. /** \param cs The value to convert. \returns If CaseSensitivity::csDefault, then returns the default file name case sensitivity for the platform. Otherwise, just returns the appropriate value from the Qt::CaseSensitivity enum. */ static Qt::CaseSensitivity convertCaseSensitivity( CaseSensitivity cs); private: QuaZipPrivate *p; // not (and will not be) implemented QuaZip(const QuaZip& that); // not (and will not be) implemented QuaZip& operator=(const QuaZip& that); public: /// Constructs QuaZip object. /** Call setName() before opening constructed object. */ QuaZip(); /// Constructs QuaZip object associated with ZIP file \a zipName. QuaZip(const QString& zipName); /// Constructs QuaZip object associated with ZIP file represented by \a ioDevice. /** The IO device must be seekable, otherwise an error will occur when opening. */ QuaZip(QIODevice *ioDevice); /// Destroys QuaZip object. /** Calls close() if necessary. */ ~QuaZip(); /// Opens ZIP file. /** * Argument \a mode specifies open mode of the ZIP archive. See Mode * for details. Note that there is zipOpen2() function in the * ZIP/UNZIP API which accepts \a globalcomment argument, but it * does not use it anywhere, so this open() function does not have this * argument. See setComment() if you need to set global comment. * * If the ZIP file is accessed via explicitly set QIODevice, then * this device is opened in the necessary mode. If the device was * already opened by some other means, then the behaviour is defined by * the device implementation, but generally it is not a very good * idea. For example, QFile will at least issue a warning. * * \return \c true if successful, \c false otherwise. * * \note ZIP/UNZIP API open calls do not return error code - they * just return \c NULL indicating an error. But to make things * easier, quazip.h header defines additional error code \c * UNZ_ERROROPEN and getZipError() will return it if the open call * of the ZIP/UNZIP API returns \c NULL. * * Argument \a ioApi specifies IO function set for ZIP/UNZIP * package to use. See unzip.h, zip.h and ioapi.h for details. Note * that IO API for QuaZip is different from the original package. * The file path argument was changed to be of type \c voidpf, and * QuaZip passes a QIODevice pointer there. This QIODevice is either * set explicitly via setIoDevice() or the QuaZip(QIODevice*) * constructor, or it is created internally when opening the archive * by its file name. The default API (qioapi.cpp) just delegates * everything to the QIODevice API. Not only this allows to use a * QIODevice instead of file name, but also has a nice side effect * of raising the file size limit from 2G to 4G (in non-zip64 archives). * * \note If the zip64 support is needed, the ioApi argument \em must be NULL * because due to the backwards compatibility issues it can be used to * provide a 32-bit API only. * * In short: just forget about the \a ioApi argument and you'll be * fine. **/ bool open(Mode mode, zlib_filefunc_def *ioApi =NULL); /// Closes ZIP file. /** Call getZipError() to determine if the close was successful. The * underlying QIODevice is also closed, regardless of whether it was * set explicitly or not. */ void close(); /// Sets the codec used to encode/decode file names inside archive. /** This is necessary to access files in the ZIP archive created * under Windows with non-latin characters in file names. For * example, file names with cyrillic letters will be in \c IBM866 * encoding. **/ void setFileNameCodec(QTextCodec *fileNameCodec); /// Sets the codec used to encode/decode file names inside archive. /** \overload * Equivalent to calling setFileNameCodec(QTextCodec::codecForName(codecName)); **/ void setFileNameCodec(const char *fileNameCodecName); /// Returns the codec used to encode/decode comments inside archive. QTextCodec* getFileNameCodec() const; /// Sets the codec used to encode/decode comments inside archive. /** This codec defaults to locale codec, which is probably ok. **/ void setCommentCodec(QTextCodec *commentCodec); /// Sets the codec used to encode/decode comments inside archive. /** \overload * Equivalent to calling setCommentCodec(QTextCodec::codecForName(codecName)); **/ void setCommentCodec(const char *commentCodecName); /// Returns the codec used to encode/decode comments inside archive. QTextCodec* getCommentCodec() const; /// Returns the name of the ZIP file. /** Returns null string if no ZIP file name has been set, for * example when the QuaZip instance is set up to use a QIODevice * instead. * \sa setZipName(), setIoDevice(), getIoDevice() **/ QString getZipName() const; /// Sets the name of the ZIP file. /** Does nothing if the ZIP file is open. * * Does not reset error code returned by getZipError(). * \sa setIoDevice(), getIoDevice(), getZipName() **/ void setZipName(const QString& zipName); /// Returns the device representing this ZIP file. /** Returns null string if no device has been set explicitly, for * example when opening a ZIP file by name. * \sa setIoDevice(), getZipName(), setZipName() **/ QIODevice *getIoDevice() const; /// Sets the device representing the ZIP file. /** Does nothing if the ZIP file is open. * * Does not reset error code returned by getZipError(). * \sa getIoDevice(), getZipName(), setZipName() **/ void setIoDevice(QIODevice *ioDevice); /// Returns the mode in which ZIP file was opened. Mode getMode() const; /// Returns \c true if ZIP file is open, \c false otherwise. bool isOpen() const; /// Returns the error code of the last operation. /** Returns \c UNZ_OK if the last operation was successful. * * Error code resets to \c UNZ_OK every time you call any function * that accesses something inside ZIP archive, even if it is \c * const (like getEntriesCount()). open() and close() calls reset * error code too. See documentation for the specific functions for * details on error detection. **/ int getZipError() const; /// Returns number of the entries in the ZIP central directory. /** Returns negative error code in the case of error. The same error * code will be returned by subsequent getZipError() call. **/ int getEntriesCount() const; /// Returns global comment in the ZIP file. QString getComment() const; /// Sets the global comment in the ZIP file. /** The comment will be written to the archive on close operation. * QuaZip makes a distinction between a null QByteArray() comment * and an empty "" comment in the QuaZip::mdAdd mode. * A null comment is the default and it means "don't change * the comment". An empty comment removes the original comment. * * \sa open() **/ void setComment(const QString& comment); /// Sets the current file to the first file in the archive. /** Returns \c true on success, \c false otherwise. Call * getZipError() to get the error code. **/ bool goToFirstFile(); /// Sets the current file to the next file in the archive. /** Returns \c true on success, \c false otherwise. Call * getZipError() to determine if there was an error. * * Should be used only in QuaZip::mdUnzip mode. * * \note If the end of file was reached, getZipError() will return * \c UNZ_OK instead of \c UNZ_END_OF_LIST_OF_FILE. This is to make * things like this easier: * \code * for(bool more=zip.goToFirstFile(); more; more=zip.goToNextFile()) { * // do something * } * if(zip.getZipError()==UNZ_OK) { * // ok, there was no error * } * \endcode **/ bool goToNextFile(); /// Sets current file by its name. /** Returns \c true if successful, \c false otherwise. Argument \a * cs specifies case sensitivity of the file name. Call * getZipError() in the case of a failure to get error code. * * This is not a wrapper to unzLocateFile() function. That is * because I had to implement locale-specific case-insensitive * comparison. * * Here are the differences from the original implementation: * * - If the file was not found, error code is \c UNZ_OK, not \c * UNZ_END_OF_LIST_OF_FILE (see also goToNextFile()). * - If this function fails, it unsets the current file rather than * resetting it back to what it was before the call. * * If \a fileName is null string then this function unsets the * current file and return \c true. Note that you should close the * file first if it is open! See * QuaZipFile::QuaZipFile(QuaZip*,QObject*) for the details. * * Should be used only in QuaZip::mdUnzip mode. * * \sa setFileNameCodec(), CaseSensitivity **/ bool setCurrentFile(const QString& fileName, CaseSensitivity cs =csDefault); /// Returns \c true if the current file has been set. bool hasCurrentFile() const; /// Retrieves information about the current file. /** Fills the structure pointed by \a info. Returns \c true on * success, \c false otherwise. In the latter case structure pointed * by \a info remains untouched. If there was an error, * getZipError() returns error code. * * Should be used only in QuaZip::mdUnzip mode. * * Does nothing and returns \c false in any of the following cases. * - ZIP is not open; * - ZIP does not have current file. * * In both cases getZipError() returns \c UNZ_OK since there * is no ZIP/UNZIP API call. * * This overload doesn't support zip64. * * \sa getCurrentFileInfo(QuaZipFileInfo64* info)const **/ bool getCurrentFileInfo(QuaZipFileInfo* info)const; /// Retrieves information about the current file. /** \overload * * This function supports zip64. If the archive doesn't use zip64, it is * completely equivalent to getCurrentFileInfo(QuaZipFileInfo* info) * except for the argument type. * * \sa **/ bool getCurrentFileInfo(QuaZipFileInfo64* info)const; /// Returns the current file name. /** Equivalent to calling getCurrentFileInfo() and then getting \c * name field of the QuaZipFileInfo structure, but faster and more * convenient. * * Should be used only in QuaZip::mdUnzip mode. **/ QString getCurrentFileName()const; /// Returns \c unzFile handle. /** You can use this handle to directly call UNZIP part of the * ZIP/UNZIP package functions (see unzip.h). * * \warning When using the handle returned by this function, please * keep in mind that QuaZip class is unable to detect any changes * you make in the ZIP file state (e. g. changing current file, or * closing the handle). So please do not do anything with this * handle that is possible to do with the functions of this class. * Or at least return the handle in the original state before * calling some another function of this class (including implicit * destructor calls and calls from the QuaZipFile objects that refer * to this QuaZip instance!). So if you have changed the current * file in the ZIP archive - then change it back or you may * experience some strange behavior or even crashes. **/ unzFile getUnzFile(); /// Returns \c zipFile handle. /** You can use this handle to directly call ZIP part of the * ZIP/UNZIP package functions (see zip.h). Warnings about the * getUnzFile() function also apply to this function. **/ zipFile getZipFile(); /// Changes the data descriptor writing mode. /** According to the ZIP format specification, a file inside archive may have a data descriptor immediately following the file data. This is reflected by a special flag in the local file header and in the central directory. By default, QuaZIP sets this flag and writes the data descriptor unless both method and level were set to 0, in which case it operates in 1.0-compatible mode and never writes data descriptors. By setting this flag to false, it is possible to disable data descriptor writing, thus increasing compatibility with archive readers that don't understand this feature of the ZIP file format. Setting this flag affects all the QuaZipFile instances that are opened after this flag is set. The data descriptor writing mode is enabled by default. \param enabled If \c true, enable local descriptor writing, disable it otherwise. \sa QuaZipFile::setDataDescriptorWritingEnabled() */ void setDataDescriptorWritingEnabled(bool enabled); /// Returns the data descriptor default writing mode. /** \sa setDataDescriptorWritingEnabled() */ bool isDataDescriptorWritingEnabled() const; /// Returns a list of files inside the archive. /** \return A list of file names or an empty list if there was an error or if the archive is empty (call getZipError() to figure out which). \sa getFileInfoList() */ QStringList getFileNameList() const; /// Returns information list about all files inside the archive. /** \return A list of QuaZipFileInfo objects or an empty list if there was an error or if the archive is empty (call getZipError() to figure out which). \sa getFileNameList() */ QList getFileInfoList() const; /// Enables the zip64 mode. /** * @param zip64 If \c true, the zip64 mode is enabled, disabled otherwise. * * Once this is enabled, all new files (until the mode is disabled again) * will be created in the zip64 mode, thus enabling the ability to write * files larger than 4 GB. By default, the zip64 mode is off due to * compatibility reasons. * * \sa isZip64Enabled() */ void setZip64Enabled(bool zip64); /// Returns whether the zip64 mode is enabled. /** * @return \c true if and only if the zip64 mode is enabled. * * \sa setZip64Enabled() */ bool isZip64Enabled() const; /// Sets the default file name codec to use. /** * The default codec is used by the constructors, so calling this function * won't affect the QuaZip instances already created at that moment. * * The codec specified here can be overriden by calling setFileNameCodec(). * If neither function is called, QTextCodec::codecForLocale() will be used * to decode or encode file names. Use this function with caution if * the application uses other libraries that depend on QuaZIP. Those * libraries can either call this function by themselves, thus overriding * your setting or can rely on the default encoding, thus failing * mysteriously if you change it. For these reasons, it isn't recommended * to use this function if you are developing a library, not an application. * Instead, ask your library users to call it in case they need specific * encoding. * * In most cases, using setFileNameCodec() instead is the right choice. * However, if you depend on third-party code that uses QuaZIP, then the * reasons stated above can actually become a reason to use this function * in case the third-party code in question fails because it doesn't * understand the encoding you need and doesn't provide a way to specify it. * This applies to the JlCompress class as well, as it was contributed and * doesn't support explicit encoding parameters. * * In short: use setFileNameCodec() when you can, resort to * setDefaultFileNameCodec() when you don't have access to the QuaZip * instance. * * @param codec The codec to use by default. If NULL, resets to default. */ static void setDefaultFileNameCodec(QTextCodec *codec); /** * @overload * Equivalent to calling * setDefltFileNameCodec(QTextCodec::codecForName(codecName)). */ static void setDefaultFileNameCodec(const char *codecName); }; #endif workbench-1.1.1/src/Quazip/quazip_global.h000066400000000000000000000032201255417355300205450ustar00rootroot00000000000000/** Copyright (C) 2005-2011 Sergey A. Tachenov This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA See COPYING file for the full LGPL text. Original ZIP package is copyrighted by Gilles Vollant, see quazip/(un)zip.h files for details, basically it's zlib license. */ #ifndef QUAZIP_GLOBAL_H #define QUAZIP_GLOBAL_H #include /** This is automatically defined when building a static library, but when including QuaZip sources directly into a project, QUAZIP_STATIC should be defined explicitly to avoid possible troubles with unnecessary importing/exporting. */ #ifdef QUAZIP_STATIC #define QUAZIP_EXPORT #else /** * When building a DLL with MSVC, QUAZIP_BUILD must be defined. * qglobal.h takes care of defining Q_DECL_* correctly for msvc/gcc. */ #if defined(QUAZIP_BUILD) #define QUAZIP_EXPORT Q_DECL_EXPORT #else #define QUAZIP_EXPORT Q_DECL_IMPORT #endif #endif // QUAZIP_STATIC #ifdef __GNUC__ #define UNUSED __attribute__((__unused__)) #else #define UNUSED #endif #endif // QUAZIP_GLOBAL_H workbench-1.1.1/src/Quazip/quazipdir.cpp000066400000000000000000000354371255417355300202760ustar00rootroot00000000000000#include "quazipdir.h" #include #include /// \cond internal class QuaZipDirPrivate: public QSharedData { friend class QuaZipDir; private: QuaZipDirPrivate(QuaZip *zip, const QString &dir = QString()): zip(zip), dir(dir), caseSensitivity(QuaZip::csDefault), filter(QDir::NoFilter), sorting(QDir::NoSort) {} QuaZip *zip; QString dir; QuaZip::CaseSensitivity caseSensitivity; QDir::Filters filter; QStringList nameFilters; QDir::SortFlags sorting; template bool entryInfoList(QStringList nameFilters, QDir::Filters filter, QDir::SortFlags sort, TFileInfoList &result) const; inline QString simplePath() const {return QDir::cleanPath(dir);} }; /// \endcond QuaZipDir::QuaZipDir(const QuaZipDir &that): d(that.d) { } QuaZipDir::QuaZipDir(QuaZip *zip, const QString &dir): d(new QuaZipDirPrivate(zip, dir)) { if (d->dir.startsWith('/')) d->dir = d->dir.mid(1); } QuaZipDir::~QuaZipDir() { } bool QuaZipDir::operator==(const QuaZipDir &that) { return d->zip == that.d->zip && d->dir == that.d->dir; } QuaZipDir& QuaZipDir::operator=(const QuaZipDir &that) { this->d = that.d; return *this; } QString QuaZipDir::operator[](int pos) const { return entryList().at(pos); } QuaZip::CaseSensitivity QuaZipDir::caseSensitivity() const { return d->caseSensitivity; } bool QuaZipDir::cd(const QString &directoryName) { if (directoryName == "/") { d->dir = ""; return true; } QString dirName = directoryName; if (dirName.endsWith('/')) dirName.chop(1); if (dirName.contains('/')) { QuaZipDir dir(*this); if (dirName.startsWith('/')) { #ifdef QUAZIP_QUAZIPDIR_DEBUG qDebug("QuaZipDir::cd(%s): going to /", dirName.toUtf8().constData()); #endif if (!dir.cd("/")) return false; } QStringList path = dirName.split('/', QString::SkipEmptyParts); for (QStringList::const_iterator i = path.constBegin(); i != path.end(); ++i) { const QString &step = *i; #ifdef QUAZIP_QUAZIPDIR_DEBUG qDebug("QuaZipDir::cd(%s): going to %s", dirName.toUtf8().constData(), step.toUtf8().constData()); #endif if (!dir.cd(step)) return false; } d->dir = dir.path(); return true; } else { // no '/' if (dirName == ".") { return true; } else if (dirName == "..") { if (isRoot()) { return false; } else { int slashPos = d->dir.lastIndexOf('/'); if (slashPos == -1) { d->dir = ""; } else { d->dir = d->dir.left(slashPos); } return true; } } else { // a simple subdirectory if (exists(dirName)) { if (isRoot()) d->dir = dirName; else d->dir += "/" + dirName; return true; } else { return false; } } } } bool QuaZipDir::cdUp() { return cd(".."); } uint QuaZipDir::count() const { return entryList().count(); } QString QuaZipDir::dirName() const { return QDir(d->dir).dirName(); } QuaZipFileInfo QuaZipDir_getFileInfo(QuaZip *zip, bool *ok, const QString &relativeName, bool isReal) { QuaZipFileInfo info; if (isReal) { *ok = zip->getCurrentFileInfo(&info); } else { *ok = true; info.compressedSize = 0; info.crc = 0; info.diskNumberStart = 0; info.externalAttr = 0; info.flags = 0; info.internalAttr = 0; info.method = 0; info.uncompressedSize = 0; info.versionCreated = info.versionNeeded = 0; } info.name = relativeName; return info; } template void QuaZipDir_convertInfoList(const QList &from, TFileInfoList &to); template<> void QuaZipDir_convertInfoList(const QList &from, QList &to) { to = from; } template<> void QuaZipDir_convertInfoList(const QList &from, QStringList &to) { to.clear(); for (QList::const_iterator i = from.constBegin(); i != from.constEnd(); ++i) { to.append(i->name); } } /// \cond internal /** An utility class to restore the current file. */ class QuaZipDirRestoreCurrent { public: inline QuaZipDirRestoreCurrent(QuaZip *zip): zip(zip), currentFile(zip->getCurrentFileName()) {} inline ~QuaZipDirRestoreCurrent() { zip->setCurrentFile(currentFile); } private: QuaZip *zip; QString currentFile; }; /// \endcond /// \cond internal class QuaZipDirComparator { private: QDir::SortFlags sort; static QString getExtension(const QString &name); int compareStrings(const QString &string1, const QString &string2); public: inline QuaZipDirComparator(QDir::SortFlags sort): sort(sort) {} bool operator()(const QuaZipFileInfo &info1, const QuaZipFileInfo &info2); }; QString QuaZipDirComparator::getExtension(const QString &name) { if (name.endsWith('.') || name.indexOf('.', 1) == -1) { return ""; } else { return name.mid(name.lastIndexOf('.') + 1); } } int QuaZipDirComparator::compareStrings(const QString &string1, const QString &string2) { if (sort & QDir::LocaleAware) { if (sort & QDir::IgnoreCase) { return string1.toLower().localeAwareCompare(string2.toLower()); } else { return string1.localeAwareCompare(string2); } } else { return string1.compare(string2, (sort & QDir::IgnoreCase) ? Qt::CaseInsensitive : Qt::CaseSensitive); } } bool QuaZipDirComparator::operator()(const QuaZipFileInfo &info1, const QuaZipFileInfo &info2) { QDir::SortFlags order = sort & (QDir::Name | QDir::Time | QDir::Size | QDir::Type); if ((sort & QDir::DirsFirst) == QDir::DirsFirst || (sort & QDir::DirsLast) == QDir::DirsLast) { if (info1.name.endsWith('/') && !info2.name.endsWith('/')) return (sort & QDir::DirsFirst) == QDir::DirsFirst; else if (!info1.name.endsWith('/') && info2.name.endsWith('/')) return (sort & QDir::DirsLast) == QDir::DirsLast; } bool result; int extDiff; switch (order) { case QDir::Name: result = compareStrings(info1.name, info2.name) < 0; break; case QDir::Type: extDiff = compareStrings(getExtension(info1.name), getExtension(info2.name)); if (extDiff == 0) { result = compareStrings(info1.name, info2.name) < 0; } else { result = extDiff < 0; } break; case QDir::Size: if (info1.uncompressedSize == info2.uncompressedSize) { result = compareStrings(info1.name, info2.name) < 0; } else { result = info1.uncompressedSize < info2.uncompressedSize; } break; case QDir::Time: if (info1.dateTime == info2.dateTime) { result = compareStrings(info1.name, info2.name) < 0; } else { result = info1.dateTime < info2.dateTime; } break; default: qWarning("QuaZipDirComparator(): Invalid sort mode 0x%2X", static_cast(sort)); return false; } return (sort & QDir::Reversed) ? !result : result; } template bool QuaZipDirPrivate::entryInfoList(QStringList nameFilters, QDir::Filters filter, QDir::SortFlags sort, TFileInfoList &result) const { QString basePath = simplePath(); if (!basePath.isEmpty()) basePath += "/"; int baseLength = basePath.length(); result.clear(); QuaZipDirRestoreCurrent saveCurrent(zip); if (!zip->goToFirstFile()) { return zip->getZipError() == UNZ_OK; } QDir::Filters fltr = filter; if (fltr == QDir::NoFilter) fltr = this->filter; if (fltr == QDir::NoFilter) fltr = QDir::AllEntries; QStringList nmfltr = nameFilters; if (nmfltr.isEmpty()) nmfltr = this->nameFilters; QSet dirsFound; QList list; do { QString name = zip->getCurrentFileName(); if (!name.startsWith(basePath)) continue; QString relativeName = name.mid(baseLength); if (relativeName.isEmpty()) continue; bool isDir = false; bool isReal = true; if (relativeName.contains('/')) { int indexOfSlash = relativeName.indexOf('/'); // something like "subdir/" isReal = indexOfSlash == relativeName.length() - 1; relativeName = relativeName.left(indexOfSlash + 1); if (dirsFound.contains(relativeName)) continue; isDir = true; } dirsFound.insert(relativeName); if ((fltr & QDir::Dirs) == 0 && isDir) continue; if ((fltr & QDir::Files) == 0 && !isDir) continue; if (!nmfltr.isEmpty() && !QDir::match(nmfltr, relativeName)) continue; bool ok; QuaZipFileInfo info = QuaZipDir_getFileInfo(zip, &ok, relativeName, isReal); if (!ok) { return false; } list.append(info); } while (zip->goToNextFile()); QDir::SortFlags srt = sort; if (srt == QDir::NoSort) srt = sorting; #ifdef QUAZIP_QUAZIPDIR_DEBUG qDebug("QuaZipDirPrivate::entryInfoList(): before sort:"); foreach (QuaZipFileInfo info, list) { qDebug("%s\t%s", info.name.toUtf8().constData(), info.dateTime.toString(Qt::ISODate).toUtf8().constData()); } #endif if (srt != QDir::NoSort && (srt & QDir::Unsorted) != QDir::Unsorted) { if (QuaZip::convertCaseSensitivity(caseSensitivity) == Qt::CaseInsensitive) srt |= QDir::IgnoreCase; QuaZipDirComparator lessThan(srt); qSort(list.begin(), list.end(), lessThan); } QuaZipDir_convertInfoList(list, result); return true; } /// \endcond QList QuaZipDir::entryInfoList(const QStringList &nameFilters, QDir::Filters filters, QDir::SortFlags sort) const { QList result; if (d->entryInfoList(nameFilters, filters, sort, result)) return result; else return QList(); } QList QuaZipDir::entryInfoList(QDir::Filters filters, QDir::SortFlags sort) const { return entryInfoList(QStringList(), filters, sort); } QStringList QuaZipDir::entryList(const QStringList &nameFilters, QDir::Filters filters, QDir::SortFlags sort) const { QStringList result; if (d->entryInfoList(nameFilters, filters, sort, result)) return result; else return QStringList(); } QStringList QuaZipDir::entryList(QDir::Filters filters, QDir::SortFlags sort) const { return entryList(QStringList(), filters, sort); } bool QuaZipDir::exists(const QString &filePath) const { if (filePath == "/") return true; QString fileName = filePath; if (fileName.endsWith('/')) fileName.chop(1); if (fileName.contains('/')) { QFileInfo fileInfo(fileName); #ifdef QUAZIP_QUAZIPDIR_DEBUG qDebug("QuaZipDir::exists(): fileName=%s, fileInfo.fileName()=%s, " "fileInfo.path()=%s", fileName.toUtf8().constData(), fileInfo.fileName().toUtf8().constData(), fileInfo.path().toUtf8().constData()); #endif QuaZipDir dir(*this); return dir.cd(fileInfo.path()) && dir.exists(fileInfo.fileName()); } else { if (fileName == "..") { return !isRoot(); } else if (fileName == ".") { return true; } else { QStringList entries = entryList(QDir::AllEntries, QDir::NoSort); #ifdef QUAZIP_QUAZIPDIR_DEBUG qDebug("QuaZipDir::exists(): looking for %s", fileName.toUtf8().constData()); for (QStringList::const_iterator i = entries.constBegin(); i != entries.constEnd(); ++i) { qDebug("QuaZipDir::exists(): entry: %s", i->toUtf8().constData()); } #endif Qt::CaseSensitivity cs = QuaZip::convertCaseSensitivity( d->caseSensitivity); if (filePath.endsWith('/')) { return entries.contains(filePath, cs); } else { return entries.contains(fileName, cs) || entries.contains(fileName + "/", cs); } } } } bool QuaZipDir::exists() const { QDir thisDir(d->dir); return QuaZipDir(d->zip, thisDir.filePath("..")).exists(thisDir.dirName()); } QString QuaZipDir::filePath(const QString &fileName) const { return QDir(d->dir).filePath(fileName); } QDir::Filters QuaZipDir::filter() { return d->filter; } bool QuaZipDir::isRoot() const { return d->simplePath().isEmpty(); } QStringList QuaZipDir::nameFilters() const { return d->nameFilters; } QString QuaZipDir::path() const { return d->dir; } QString QuaZipDir::relativeFilePath(const QString &fileName) const { return QDir(d->dir).relativeFilePath(fileName); } void QuaZipDir::setCaseSensitivity(QuaZip::CaseSensitivity caseSensitivity) { d->caseSensitivity = caseSensitivity; } void QuaZipDir::setFilter(QDir::Filters filters) { d->filter = filters; } void QuaZipDir::setNameFilters(const QStringList &nameFilters) { d->nameFilters = nameFilters; } void QuaZipDir::setPath(const QString &path) { QString newDir = path; if (newDir == "/") { d->dir = ""; } else { if (newDir.endsWith('/')) newDir.chop(1); if (newDir.startsWith('/')) newDir = newDir.mid(1); d->dir = newDir; } } void QuaZipDir::setSorting(QDir::SortFlags sort) { d->sorting = sort; } QDir::SortFlags QuaZipDir::sorting() const { return d->sorting; } workbench-1.1.1/src/Quazip/quazipdir.h000066400000000000000000000145341255417355300177360ustar00rootroot00000000000000#ifndef QUAZIP_QUAZIPDIR_H #define QUAZIP_QUAZIPDIR_H class QuaZipDirPrivate; #include "quazip.h" #include "quazipfileinfo.h" #include #include #include /// Provides ZIP archive navigation. /** * This class is modelled after QDir, and is designed to provide similar * features for ZIP archives. * * The only significant difference from QDir is that the root path is not * '/', but an empty string since that's how the file paths are stored in * the archive. However, QuaZipDir understands the paths starting with * '/'. It is important in a few places: * * - In the cd() function. * - In the constructor. * - In the exists() function. * * Note that since ZIP uses '/' on all platforms, the '\' separator is * not supported. */ class QUAZIP_EXPORT QuaZipDir { private: QSharedDataPointer d; public: /// The copy constructor. QuaZipDir(const QuaZipDir &that); /// Constructs a QuaZipDir instance pointing to the specified directory. /** If \a dir is not specified, points to the root of the archive. The same happens if the \a dir is "/". */ QuaZipDir(QuaZip *zip, const QString &dir = QString()); /// Destructor. ~QuaZipDir(); /// The assignment operator. bool operator==(const QuaZipDir &that); /// operator!= /** \return \c true if either this and \a that use different QuaZip instances or if they point to different directories. */ inline bool operator!=(const QuaZipDir &that) {return !operator==(that);} /// operator== /** \return \c true if both this and \a that use the same QuaZip instance and point to the same directory. */ QuaZipDir& operator=(const QuaZipDir &that); /// Returns the name of the entry at the specified position. QString operator[](int pos) const; /// Returns the current case sensitivity mode. QuaZip::CaseSensitivity caseSensitivity() const; /// Changes the 'current' directory. /** * If the path starts with '/', it is interpreted as an absolute * path from the root of the archive. Otherwise, it is interpreted * as a path relative to the current directory as was set by the * previous cd() or the constructor. * * Note that the subsequent path() call will not return a path * starting with '/' in all cases. */ bool cd(const QString &dirName); /// Goes up. bool cdUp(); /// Returns the number of entries in the directory. uint count() const; /// Returns the current directory name. /** The name doesn't include the path. */ QString dirName() const; /// Returns the list of the entries in the directory. /** \param nameFilters The list of file patterns to list, uses the same syntax as QDir. \param filters The entry type filters, only Files and Dirs are accepted. \param sort Sorting mode (not supported yet). */ QList entryInfoList(const QStringList &nameFilters, QDir::Filters filters = QDir::NoFilter, QDir::SortFlags sort = QDir::NoSort) const; /// Returns the list of the entries in the directory. /** \overload The same as entryInfoList(QStringList(), filters, sort). */ QList entryInfoList(QDir::Filters filters = QDir::NoFilter, QDir::SortFlags sort = QDir::NoSort) const; /// Returns the list of the entry names in the directory. /** The same as entryInfoList(nameFilters, filters, sort), but only returns entry names. */ QStringList entryList(const QStringList &nameFilters, QDir::Filters filters = QDir::NoFilter, QDir::SortFlags sort = QDir::NoSort) const; /// Returns the list of the entry names in the directory. /** \overload The same as entryList(QStringList(), filters, sort). */ QStringList entryList(QDir::Filters filters = QDir::NoFilter, QDir::SortFlags sort = QDir::NoSort) const; /// Returns \c true if the entry with the specified name exists. /** The ".." is considered to exist if the current directory is not root. The "." and "/" are considered to always exist. Paths starting with "/" are relative to the archive root, other paths are relative to the current dir. */ bool exists(const QString &fileName) const; /// Return \c true if the directory pointed by this QuaZipDir exists. bool exists() const; /// Returns the full path to the specified file. /** Doesn't check if the file actually exists. */ QString filePath(const QString &fileName) const; /// Returns the default filter. QDir::Filters filter(); /// Returns if the QuaZipDir points to the root of the archive. /** Not that the root path is the empty string, not '/'. */ bool isRoot() const; /// Return the default name filter. QStringList nameFilters() const; /// Returns the path to the current dir. /** The path never starts with '/', and the root path is an empty string. */ QString path() const; /// Returns the path to the specified file relative to the current dir. QString relativeFilePath(const QString &fileName) const; /// Sets the default case sensitivity mode. void setCaseSensitivity(QuaZip::CaseSensitivity caseSensitivity); /// Sets the default filter. void setFilter(QDir::Filters filters); /// Sets the default name filter. void setNameFilters(const QStringList &nameFilters); /// Goes to the specified path. /** The difference from cd() is that this function never checks if the path actually exists and doesn't use relative paths, so it's possible to go to the root directory with setPath(""). Note that this function still chops the trailing and/or leading '/' and treats a single '/' as the root path (path() will still return an empty string). */ void setPath(const QString &path); /// Sets the default sorting mode. void setSorting(QDir::SortFlags sort); /// Returns the default sorting mode. QDir::SortFlags sorting() const; }; #endif // QUAZIP_QUAZIPDIR_H workbench-1.1.1/src/Quazip/quazipfile.cpp000066400000000000000000000341321255417355300204260ustar00rootroot00000000000000/* Copyright (C) 2005-2011 Sergey A. Tachenov This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA See COPYING file for the full LGPL text. Original ZIP package is copyrighted by Gilles Vollant, see quazip/(un)zip.h files for details, basically it's zlib license. **/ #include "quazipfile.h" using namespace std; /// The implementation class for QuaZip. /** \internal This class contains all the private stuff for the QuaZipFile class, thus allowing to preserve binary compatibility between releases, the technique known as the Pimpl (private implementation) idiom. */ class QuaZipFilePrivate { friend class QuaZipFile; private: /// The pointer to the associated QuaZipFile instance. QuaZipFile *q; /// The QuaZip object to work with. QuaZip *zip; /// The file name. QString fileName; /// Case sensitivity mode. QuaZip::CaseSensitivity caseSensitivity; /// Whether this file is opened in the raw mode. bool raw; /// Write position to keep track of. /** QIODevice::pos() is broken for non-seekable devices, so we need our own position. */ qint64 writePos; /// Uncompressed size to write along with a raw file. quint64 uncompressedSize; /// CRC to write along with a raw file. quint32 crc; /// Whether \ref zip points to an internal QuaZip instance. /** This is true if the archive was opened by name, rather than by supplying an existing QuaZip instance. */ bool internal; /// The last error. int zipError; /// Resets \ref zipError. inline void resetZipError() const {setZipError(UNZ_OK);} /// Sets the zip error. /** This function is marked as const although it changes one field. This allows to call it from const functions that don't change anything by themselves. */ void setZipError(int zipError) const; /// The constructor for the corresponding QuaZipFile constructor. inline QuaZipFilePrivate(QuaZipFile *q): q(q), zip(NULL), internal(true), zipError(UNZ_OK) {} /// The constructor for the corresponding QuaZipFile constructor. inline QuaZipFilePrivate(QuaZipFile *q, const QString &zipName): q(q), internal(true), zipError(UNZ_OK) { zip=new QuaZip(zipName); } /// The constructor for the corresponding QuaZipFile constructor. inline QuaZipFilePrivate(QuaZipFile *q, const QString &zipName, const QString &fileName, QuaZip::CaseSensitivity cs): q(q), internal(true), zipError(UNZ_OK) { zip=new QuaZip(zipName); this->fileName=fileName; if (this->fileName.startsWith('/')) this->fileName = this->fileName.mid(1); this->caseSensitivity=cs; } /// The constructor for the QuaZipFile constructor accepting a file name. inline QuaZipFilePrivate(QuaZipFile *q, QuaZip *zip): q(q), zip(zip), internal(false), zipError(UNZ_OK) {} /// The destructor. inline ~QuaZipFilePrivate() { if (internal) delete zip; } }; QuaZipFile::QuaZipFile(): p(new QuaZipFilePrivate(this)) { } QuaZipFile::QuaZipFile(QObject *parent): QIODevice(parent), p(new QuaZipFilePrivate(this)) { } QuaZipFile::QuaZipFile(const QString& zipName, QObject *parent): QIODevice(parent), p(new QuaZipFilePrivate(this, zipName)) { } QuaZipFile::QuaZipFile(const QString& zipName, const QString& fileName, QuaZip::CaseSensitivity cs, QObject *parent): QIODevice(parent), p(new QuaZipFilePrivate(this, zipName, fileName, cs)) { } QuaZipFile::QuaZipFile(QuaZip *zip, QObject *parent): QIODevice(parent), p(new QuaZipFilePrivate(this, zip)) { } QuaZipFile::~QuaZipFile() { if (isOpen()) close(); delete p; } QString QuaZipFile::getZipName() const { return p->zip==NULL ? QString() : p->zip->getZipName(); } QuaZip *QuaZipFile::getZip() const { return p->internal ? NULL : p->zip; } QString QuaZipFile::getActualFileName()const { p->setZipError(UNZ_OK); if (p->zip == NULL || (openMode() & WriteOnly)) return QString(); QString name=p->zip->getCurrentFileName(); if(name.isNull()) p->setZipError(p->zip->getZipError()); return name; } void QuaZipFile::setZipName(const QString& zipName) { if(isOpen()) { qWarning("QuaZipFile::setZipName(): file is already open - can not set ZIP name"); return; } if(p->zip!=NULL && p->internal) delete p->zip; p->zip=new QuaZip(zipName); p->internal=true; } void QuaZipFile::setZip(QuaZip *zip) { if(isOpen()) { qWarning("QuaZipFile::setZip(): file is already open - can not set ZIP"); return; } if(p->zip!=NULL && p->internal) delete p->zip; p->zip=zip; p->fileName=QString(); p->internal=false; } void QuaZipFile::setFileName(const QString& fileName, QuaZip::CaseSensitivity cs) { if(p->zip==NULL) { qWarning("QuaZipFile::setFileName(): call setZipName() first"); return; } if(!p->internal) { qWarning("QuaZipFile::setFileName(): should not be used when not using internal QuaZip"); return; } if(isOpen()) { qWarning("QuaZipFile::setFileName(): can not set file name for already opened file"); return; } p->fileName=fileName; if (p->fileName.startsWith('/')) p->fileName = p->fileName.mid(1); p->caseSensitivity=cs; } void QuaZipFilePrivate::setZipError(int zipError) const { QuaZipFilePrivate *fakeThis = const_cast(this); // non-const fakeThis->zipError=zipError; if(zipError==UNZ_OK) q->setErrorString(QString()); else q->setErrorString(QuaZipFile::tr("ZIP/UNZIP API error %1").arg(zipError)); } bool QuaZipFile::open(OpenMode mode) { return open(mode, NULL); } bool QuaZipFile::open(OpenMode mode, int *method, int *level, bool raw, const char *password) { p->resetZipError(); if(isOpen()) { qWarning("QuaZipFile::open(): already opened"); return false; } if(mode&Unbuffered) { qWarning("QuaZipFile::open(): Unbuffered mode is not supported"); return false; } if((mode&ReadOnly)&&!(mode&WriteOnly)) { if(p->internal) { if(!p->zip->open(QuaZip::mdUnzip)) { p->setZipError(p->zip->getZipError()); return false; } if(!p->zip->setCurrentFile(p->fileName, p->caseSensitivity)) { p->setZipError(p->zip->getZipError()); p->zip->close(); return false; } } else { if(p->zip==NULL) { qWarning("QuaZipFile::open(): zip is NULL"); return false; } if(p->zip->getMode()!=QuaZip::mdUnzip) { qWarning("QuaZipFile::open(): file open mode %d incompatible with ZIP open mode %d", (int)mode, (int)p->zip->getMode()); return false; } if(!p->zip->hasCurrentFile()) { qWarning("QuaZipFile::open(): zip does not have current file"); return false; } } p->setZipError(unzOpenCurrentFile3(p->zip->getUnzFile(), method, level, (int)raw, password)); if(p->zipError==UNZ_OK) { setOpenMode(mode); p->raw=raw; return true; } else return false; } qWarning("QuaZipFile::open(): open mode %d not supported by this function", (int)mode); return false; } bool QuaZipFile::open(OpenMode mode, const QuaZipNewInfo& info, const char *password, quint32 crc, int method, int level, bool raw, int windowBits, int memLevel, int strategy) { zip_fileinfo info_z; p->resetZipError(); if(isOpen()) { qWarning("QuaZipFile::open(): already opened"); return false; } if((mode&WriteOnly)&&!(mode&ReadOnly)) { if(p->internal) { qWarning("QuaZipFile::open(): write mode is incompatible with internal QuaZip approach"); return false; } if(p->zip==NULL) { qWarning("QuaZipFile::open(): zip is NULL"); return false; } if(p->zip->getMode()!=QuaZip::mdCreate&&p->zip->getMode()!=QuaZip::mdAppend&&p->zip->getMode()!=QuaZip::mdAdd) { qWarning("QuaZipFile::open(): file open mode %d incompatible with ZIP open mode %d", (int)mode, (int)p->zip->getMode()); return false; } info_z.tmz_date.tm_year=info.dateTime.date().year(); info_z.tmz_date.tm_mon=info.dateTime.date().month() - 1; info_z.tmz_date.tm_mday=info.dateTime.date().day(); info_z.tmz_date.tm_hour=info.dateTime.time().hour(); info_z.tmz_date.tm_min=info.dateTime.time().minute(); info_z.tmz_date.tm_sec=info.dateTime.time().second(); info_z.dosDate = 0; info_z.internal_fa=(uLong)info.internalAttr; info_z.external_fa=(uLong)info.externalAttr; if (!p->zip->isDataDescriptorWritingEnabled()) zipClearFlags(p->zip->getZipFile(), ZIP_WRITE_DATA_DESCRIPTOR); p->setZipError(zipOpenNewFileInZip3_64(p->zip->getZipFile(), p->zip->getFileNameCodec()->fromUnicode(info.name).constData(), &info_z, info.extraLocal.constData(), info.extraLocal.length(), info.extraGlobal.constData(), info.extraGlobal.length(), p->zip->getCommentCodec()->fromUnicode(info.comment).constData(), method, level, (int)raw, windowBits, memLevel, strategy, password, (uLong)crc, p->zip->isZip64Enabled())); if(p->zipError==UNZ_OK) { p->writePos=0; setOpenMode(mode); p->raw=raw; if(raw) { p->crc=crc; p->uncompressedSize=info.uncompressedSize; } return true; } else return false; } qWarning("QuaZipFile::open(): open mode %d not supported by this function", (int)mode); return false; } bool QuaZipFile::isSequential()const { return true; } qint64 QuaZipFile::pos()const { if(p->zip==NULL) { qWarning("QuaZipFile::pos(): call setZipName() or setZip() first"); return -1; } if(!isOpen()) { qWarning("QuaZipFile::pos(): file is not open"); return -1; } if(openMode()&ReadOnly) // QIODevice::pos() is broken for sequential devices, // but thankfully bytesAvailable() returns the number of // bytes buffered, so we know how far ahead we are. return unztell(p->zip->getUnzFile()) - QIODevice::bytesAvailable(); else return p->writePos; } bool QuaZipFile::atEnd()const { if(p->zip==NULL) { qWarning("QuaZipFile::atEnd(): call setZipName() or setZip() first"); return false; } if(!isOpen()) { qWarning("QuaZipFile::atEnd(): file is not open"); return false; } if(openMode()&ReadOnly) // the same problem as with pos() return QIODevice::bytesAvailable() == 0 && unzeof(p->zip->getUnzFile())==1; else return true; } qint64 QuaZipFile::size()const { if(!isOpen()) { qWarning("QuaZipFile::atEnd(): file is not open"); return -1; } if(openMode()&ReadOnly) return p->raw?csize():usize(); else return p->writePos; } qint64 QuaZipFile::csize()const { unz_file_info64 info_z; p->setZipError(UNZ_OK); if(p->zip==NULL||p->zip->getMode()!=QuaZip::mdUnzip) return -1; p->setZipError(unzGetCurrentFileInfo64(p->zip->getUnzFile(), &info_z, NULL, 0, NULL, 0, NULL, 0)); if(p->zipError!=UNZ_OK) return -1; return info_z.compressed_size; } qint64 QuaZipFile::usize()const { unz_file_info64 info_z; p->setZipError(UNZ_OK); if(p->zip==NULL||p->zip->getMode()!=QuaZip::mdUnzip) return -1; p->setZipError(unzGetCurrentFileInfo64(p->zip->getUnzFile(), &info_z, NULL, 0, NULL, 0, NULL, 0)); if(p->zipError!=UNZ_OK) return -1; return info_z.uncompressed_size; } bool QuaZipFile::getFileInfo(QuaZipFileInfo *info) { if(p->zip==NULL||p->zip->getMode()!=QuaZip::mdUnzip) return false; p->zip->getCurrentFileInfo(info); p->setZipError(p->zip->getZipError()); return p->zipError==UNZ_OK; } void QuaZipFile::close() { p->resetZipError(); if(p->zip==NULL||!p->zip->isOpen()) return; if(!isOpen()) { qWarning("QuaZipFile::close(): file isn't open"); return; } if(openMode()&ReadOnly) p->setZipError(unzCloseCurrentFile(p->zip->getUnzFile())); else if(openMode()&WriteOnly) if(isRaw()) p->setZipError(zipCloseFileInZipRaw64(p->zip->getZipFile(), p->uncompressedSize, p->crc)); else p->setZipError(zipCloseFileInZip(p->zip->getZipFile())); else { qWarning("Wrong open mode: %d", (int)openMode()); return; } if(p->zipError==UNZ_OK) setOpenMode(QIODevice::NotOpen); else return; if(p->internal) { p->zip->close(); p->setZipError(p->zip->getZipError()); } } qint64 QuaZipFile::readData(char *data, qint64 maxSize) { p->setZipError(UNZ_OK); qint64 bytesRead=unzReadCurrentFile(p->zip->getUnzFile(), data, (unsigned)maxSize); if (bytesRead < 0) { p->setZipError((int) bytesRead); return -1; } return bytesRead; } qint64 QuaZipFile::writeData(const char* data, qint64 maxSize) { p->setZipError(ZIP_OK); p->setZipError(zipWriteInFileInZip(p->zip->getZipFile(), data, (uint)maxSize)); if(p->zipError!=ZIP_OK) return -1; else { p->writePos+=maxSize; return maxSize; } } QString QuaZipFile::getFileName() const { return p->fileName; } QuaZip::CaseSensitivity QuaZipFile::getCaseSensitivity() const { return p->caseSensitivity; } bool QuaZipFile::isRaw() const { return p->raw; } int QuaZipFile::getZipError() const { return p->zipError; } qint64 QuaZipFile::bytesAvailable() const { return size() - pos(); } workbench-1.1.1/src/Quazip/quazipfile.h000066400000000000000000000471061255417355300201000ustar00rootroot00000000000000#ifndef QUA_ZIPFILE_H #define QUA_ZIPFILE_H /* Copyright (C) 2005-2011 Sergey A. Tachenov This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA See COPYING file for the full LGPL text. Original ZIP package is copyrighted by Gilles Vollant, see quazip/(un)zip.h files for details, basically it's zlib license. **/ #include #include "quazip_global.h" #include "quazip.h" #include "quazipnewinfo.h" class QuaZipFilePrivate; /// A file inside ZIP archive. /** \class QuaZipFile quazipfile.h * This is the most interesting class. Not only it provides C++ * interface to the ZIP/UNZIP package, but also integrates it with Qt by * subclassing QIODevice. This makes possible to access files inside ZIP * archive using QTextStream or QDataStream, for example. Actually, this * is the main purpose of the whole QuaZIP library. * * You can either use existing QuaZip instance to create instance of * this class or pass ZIP archive file name to this class, in which case * it will create internal QuaZip object. See constructors' descriptions * for details. Writing is only possible with the existing instance. * * Note that due to the underlying library's limitation it is not * possible to use multiple QuaZipFile instances to open several files * in the same archive at the same time. If you need to write to * multiple files in parallel, then you should write to temporary files * first, then pack them all at once when you have finished writing. If * you need to read multiple files inside the same archive in parallel, * you should extract them all into a temporary directory first. * * \section quazipfile-sequential Sequential or random-access? * * At the first thought, QuaZipFile has fixed size, the start and the * end and should be therefore considered random-access device. But * there is one major obstacle to making it random-access: ZIP/UNZIP API * does not support seek() operation and the only way to implement it is * through reopening the file and re-reading to the required position, * but this is prohibitively slow. * * Therefore, QuaZipFile is considered to be a sequential device. This * has advantage of availability of the ungetChar() operation (QIODevice * does not implement it properly for non-sequential devices unless they * support seek()). Disadvantage is a somewhat strange behaviour of the * size() and pos() functions. This should be kept in mind while using * this class. * **/ class QUAZIP_EXPORT QuaZipFile: public QIODevice { friend class QuaZipFilePrivate; Q_OBJECT private: QuaZipFilePrivate *p; // these are not supported nor implemented QuaZipFile(const QuaZipFile& that); QuaZipFile& operator=(const QuaZipFile& that); protected: /// Implementation of the QIODevice::readData(). qint64 readData(char *data, qint64 maxSize); /// Implementation of the QIODevice::writeData(). qint64 writeData(const char *data, qint64 maxSize); public: /// Constructs a QuaZipFile instance. /** You should use setZipName() and setFileName() or setZip() before * trying to call open() on the constructed object. **/ QuaZipFile(); /// Constructs a QuaZipFile instance. /** \a parent argument specifies this object's parent object. * * You should use setZipName() and setFileName() or setZip() before * trying to call open() on the constructed object. **/ QuaZipFile(QObject *parent); /// Constructs a QuaZipFile instance. /** \a parent argument specifies this object's parent object and \a * zipName specifies ZIP archive file name. * * You should use setFileName() before trying to call open() on the * constructed object. * * QuaZipFile constructed by this constructor can be used for read * only access. Use QuaZipFile(QuaZip*,QObject*) for writing. **/ QuaZipFile(const QString& zipName, QObject *parent =NULL); /// Constructs a QuaZipFile instance. /** \a parent argument specifies this object's parent object, \a * zipName specifies ZIP archive file name and \a fileName and \a cs * specify a name of the file to open inside archive. * * QuaZipFile constructed by this constructor can be used for read * only access. Use QuaZipFile(QuaZip*,QObject*) for writing. * * \sa QuaZip::setCurrentFile() **/ QuaZipFile(const QString& zipName, const QString& fileName, QuaZip::CaseSensitivity cs =QuaZip::csDefault, QObject *parent =NULL); /// Constructs a QuaZipFile instance. /** \a parent argument specifies this object's parent object. * * \a zip is the pointer to the existing QuaZip object. This * QuaZipFile object then can be used to read current file in the * \a zip or to write to the file inside it. * * \warning Using this constructor for reading current file can be * tricky. Let's take the following example: * \code * QuaZip zip("archive.zip"); * zip.open(QuaZip::mdUnzip); * zip.setCurrentFile("file-in-archive"); * QuaZipFile file(&zip); * file.open(QIODevice::ReadOnly); * // ok, now we can read from the file * file.read(somewhere, some); * zip.setCurrentFile("another-file-in-archive"); // oops... * QuaZipFile anotherFile(&zip); * anotherFile.open(QIODevice::ReadOnly); * anotherFile.read(somewhere, some); // this is still ok... * file.read(somewhere, some); // and this is NOT * \endcode * So, what exactly happens here? When we change current file in the * \c zip archive, \c file that references it becomes invalid * (actually, as far as I understand ZIP/UNZIP sources, it becomes * closed, but QuaZipFile has no means to detect it). * * Summary: do not close \c zip object or change its current file as * long as QuaZipFile is open. Even better - use another constructors * which create internal QuaZip instances, one per object, and * therefore do not cause unnecessary trouble. This constructor may * be useful, though, if you already have a QuaZip instance and do * not want to access several files at once. Good example: * \code * QuaZip zip("archive.zip"); * zip.open(QuaZip::mdUnzip); * // first, we need some information about archive itself * QByteArray comment=zip.getComment(); * // and now we are going to access files inside it * QuaZipFile file(&zip); * for(bool more=zip.goToFirstFile(); more; more=zip.goToNextFile()) { * file.open(QIODevice::ReadOnly); * // do something cool with file here * file.close(); // do not forget to close! * } * zip.close(); * \endcode **/ QuaZipFile(QuaZip *zip, QObject *parent =NULL); /// Destroys a QuaZipFile instance. /** Closes file if open, destructs internal QuaZip object (if it * exists and \em is internal, of course). **/ virtual ~QuaZipFile(); /// Returns the ZIP archive file name. /** If this object was created by passing QuaZip pointer to the * constructor, this function will return that QuaZip's file name * (or null string if that object does not have file name yet). * * Otherwise, returns associated ZIP archive file name or null * string if there are no name set yet. * * \sa setZipName() getFileName() **/ QString getZipName()const; /// Returns a pointer to the associated QuaZip object. /** Returns \c NULL if there is no associated QuaZip or it is * internal (so you will not mess with it). **/ QuaZip* getZip()const; /// Returns file name. /** This function returns file name you passed to this object either * by using * QuaZipFile(const QString&,const QString&,QuaZip::CaseSensitivity,QObject*) * or by calling setFileName(). Real name of the file may differ in * case if you used case-insensitivity. * * Returns null string if there is no file name set yet. This is the * case when this QuaZipFile operates on the existing QuaZip object * (constructor QuaZipFile(QuaZip*,QObject*) or setZip() was used). * * \sa getActualFileName **/ QString getFileName() const; /// Returns case sensitivity of the file name. /** This function returns case sensitivity argument you passed to * this object either by using * QuaZipFile(const QString&,const QString&,QuaZip::CaseSensitivity,QObject*) * or by calling setFileName(). * * Returns unpredictable value if getFileName() returns null string * (this is the case when you did not used setFileName() or * constructor above). * * \sa getFileName **/ QuaZip::CaseSensitivity getCaseSensitivity() const; /// Returns the actual file name in the archive. /** This is \em not a ZIP archive file name, but a name of file inside * archive. It is not necessary the same name that you have passed * to the * QuaZipFile(const QString&,const QString&,QuaZip::CaseSensitivity,QObject*), * setFileName() or QuaZip::setCurrentFile() - this is the real file * name inside archive, so it may differ in case if the file name * search was case-insensitive. * * Equivalent to calling getCurrentFileName() on the associated * QuaZip object. Returns null string if there is no associated * QuaZip object or if it does not have a current file yet. And this * is the case if you called setFileName() but did not open the * file yet. So this is perfectly fine: * \code * QuaZipFile file("somezip.zip"); * file.setFileName("somefile"); * QString name=file.getName(); // name=="somefile" * QString actual=file.getActualFileName(); // actual is null string * file.open(QIODevice::ReadOnly); * QString actual=file.getActualFileName(); // actual can be "SoMeFiLe" on Windows * \endcode * * \sa getZipName(), getFileName(), QuaZip::CaseSensitivity **/ QString getActualFileName()const; /// Sets the ZIP archive file name. /** Automatically creates internal QuaZip object and destroys * previously created internal QuaZip object, if any. * * Will do nothing if this file is already open. You must close() it * first. **/ void setZipName(const QString& zipName); /// Returns \c true if the file was opened in raw mode. /** If the file is not open, the returned value is undefined. * * \sa open(OpenMode,int*,int*,bool,const char*) **/ bool isRaw() const; /// Binds to the existing QuaZip instance. /** This function destroys internal QuaZip object, if any, and makes * this QuaZipFile to use current file in the \a zip object for any * further operations. See QuaZipFile(QuaZip*,QObject*) for the * possible pitfalls. * * Will do nothing if the file is currently open. You must close() * it first. **/ void setZip(QuaZip *zip); /// Sets the file name. /** Will do nothing if at least one of the following conditions is * met: * - ZIP name has not been set yet (getZipName() returns null * string). * - This QuaZipFile is associated with external QuaZip. In this * case you should call that QuaZip's setCurrentFile() function * instead! * - File is already open so setting the name is meaningless. * * \sa QuaZip::setCurrentFile **/ void setFileName(const QString& fileName, QuaZip::CaseSensitivity cs =QuaZip::csDefault); /// Opens a file for reading. /** Returns \c true on success, \c false otherwise. * Call getZipError() to get error code. * * \note Since ZIP/UNZIP API provides buffered reading only, * QuaZipFile does not support unbuffered reading. So do not pass * QIODevice::Unbuffered flag in \a mode, or open will fail. **/ virtual bool open(OpenMode mode); /// Opens a file for reading. /** \overload * Argument \a password specifies a password to decrypt the file. If * it is NULL then this function behaves just like open(OpenMode). **/ inline bool open(OpenMode mode, const char *password) {return open(mode, NULL, NULL, false, password);} /// Opens a file for reading. /** \overload * Argument \a password specifies a password to decrypt the file. * * An integers pointed by \a method and \a level will receive codes * of the compression method and level used. See unzip.h. * * If raw is \c true then no decompression is performed. * * \a method should not be \c NULL. \a level can be \c NULL if you * don't want to know the compression level. **/ bool open(OpenMode mode, int *method, int *level, bool raw, const char *password =NULL); /// Opens a file for writing. /** \a info argument specifies information about file. It should at * least specify a correct file name. Also, it is a good idea to * specify correct timestamp (by default, current time will be * used). See QuaZipNewInfo. * * The \a password argument specifies the password for crypting. Pass NULL * if you don't need any crypting. The \a crc argument was supposed * to be used for crypting too, but then it turned out that it's * false information, so you need to set it to 0 unless you want to * use the raw mode (see below). * * Arguments \a method and \a level specify compression method and * level. The only method supported is Z_DEFLATED, but you may also * specify 0 for no compression. If all of the files in the archive * use both method 0 and either level 0 is explicitly specified or * data descriptor writing is disabled with * QuaZip::setDataDescriptorWritingEnabled(), then the * resulting archive is supposed to be compatible with the 1.0 ZIP * format version, should you need that. Except for this, \a level * has no other effects with method 0. * * If \a raw is \c true, no compression is performed. In this case, * \a crc and uncompressedSize field of the \a info are required. * * Arguments \a windowBits, \a memLevel, \a strategy provide zlib * algorithms tuning. See deflateInit2() in zlib. **/ bool open(OpenMode mode, const QuaZipNewInfo& info, const char *password =NULL, quint32 crc =0, int method =Z_DEFLATED, int level =Z_DEFAULT_COMPRESSION, bool raw =false, int windowBits =-MAX_WBITS, int memLevel =DEF_MEM_LEVEL, int strategy =Z_DEFAULT_STRATEGY); /// Returns \c true, but \ref quazipfile-sequential "beware"! virtual bool isSequential()const; /// Returns current position in the file. /** Implementation of the QIODevice::pos(). When reading, this * function is a wrapper to the ZIP/UNZIP unztell(), therefore it is * unable to keep track of the ungetChar() calls (which is * non-virtual and therefore is dangerous to reimplement). So if you * are using ungetChar() feature of the QIODevice, this function * reports incorrect value until you get back characters which you * ungot. * * When writing, pos() returns number of bytes already written * (uncompressed unless you use raw mode). * * \note Although * \ref quazipfile-sequential "QuaZipFile is a sequential device" * and therefore pos() should always return zero, it does not, * because it would be misguiding. Keep this in mind. * * This function returns -1 if the file or archive is not open. * * Error code returned by getZipError() is not affected by this * function call. **/ virtual qint64 pos()const; /// Returns \c true if the end of file was reached. /** This function returns \c false in the case of error. This means * that you called this function on either not open file, or a file * in the not open archive or even on a QuaZipFile instance that * does not even have QuaZip instance associated. Do not do that * because there is no means to determine whether \c false is * returned because of error or because end of file was reached. * Well, on the other side you may interpret \c false return value * as "there is no file open to check for end of file and there is * no end of file therefore". * * When writing, this function always returns \c true (because you * are always writing to the end of file). * * Error code returned by getZipError() is not affected by this * function call. **/ virtual bool atEnd()const; /// Returns file size. /** This function returns csize() if the file is open for reading in * raw mode, usize() if it is open for reading in normal mode and * pos() if it is open for writing. * * Returns -1 on error, call getZipError() to get error code. * * \note This function returns file size despite that * \ref quazipfile-sequential "QuaZipFile is considered to be sequential device", * for which size() should return bytesAvailable() instead. But its * name would be very misguiding otherwise, so just keep in mind * this inconsistence. **/ virtual qint64 size()const; /// Returns compressed file size. /** Equivalent to calling getFileInfo() and then getting * compressedSize field, but more convenient and faster. * * File must be open for reading before calling this function. * * Returns -1 on error, call getZipError() to get error code. **/ qint64 csize()const; /// Returns uncompressed file size. /** Equivalent to calling getFileInfo() and then getting * uncompressedSize field, but more convenient and faster. See * getFileInfo() for a warning. * * File must be open for reading before calling this function. * * Returns -1 on error, call getZipError() to get error code. **/ qint64 usize()const; /// Gets information about current file. /** This function does the same thing as calling * QuaZip::getCurrentFileInfo() on the associated QuaZip object, * but you can not call getCurrentFileInfo() if the associated * QuaZip is internal (because you do not have access to it), while * you still can call this function in that case. * * File must be open for reading before calling this function. * * Returns \c false in the case of an error. **/ bool getFileInfo(QuaZipFileInfo *info); /// Closes the file. /** Call getZipError() to determine if the close was successful. **/ virtual void close(); /// Returns the error code returned by the last ZIP/UNZIP API call. int getZipError() const; /// Returns the number of bytes available for reading. virtual qint64 bytesAvailable() const; }; #endif workbench-1.1.1/src/Quazip/quazipfileinfo.cpp000066400000000000000000000017731255417355300213070ustar00rootroot00000000000000#include "quazipfileinfo.h" static QFile::Permissions permissionsFromExternalAttr(quint32 externalAttr) { quint32 uPerm = (externalAttr & 0xFFFF0000u) >> 16; QFile::Permissions perm = 0; if ((uPerm & 0400) != 0) perm |= QFile::ReadOwner; if ((uPerm & 0200) != 0) perm |= QFile::WriteOwner; if ((uPerm & 0100) != 0) perm |= QFile::ExeOwner; if ((uPerm & 0040) != 0) perm |= QFile::ReadGroup; if ((uPerm & 0020) != 0) perm |= QFile::WriteGroup; if ((uPerm & 0010) != 0) perm |= QFile::ExeGroup; if ((uPerm & 0004) != 0) perm |= QFile::ReadOther; if ((uPerm & 0002) != 0) perm |= QFile::WriteOther; if ((uPerm & 0001) != 0) perm |= QFile::ExeOther; return perm; } QFile::Permissions QuaZipFileInfo::getPermissions() const { return permissionsFromExternalAttr(externalAttr); } QFile::Permissions QuaZipFileInfo64::getPermissions() const { return permissionsFromExternalAttr(externalAttr); } workbench-1.1.1/src/Quazip/quazipfileinfo.h000066400000000000000000000062771255417355300207600ustar00rootroot00000000000000#ifndef QUA_ZIPFILEINFO_H #define QUA_ZIPFILEINFO_H /* Copyright (C) 2005-2011 Sergey A. Tachenov This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA See COPYING file for the full LGPL text. Original ZIP package is copyrighted by Gilles Vollant, see quazip/(un)zip.h files for details, basically it's zlib license. **/ #include #include #include #include "quazip_global.h" /// Information about a file inside archive. /** Call QuaZip::getCurrentFileInfo() or QuaZipFile::getFileInfo() to * fill this structure. */ struct QUAZIP_EXPORT QuaZipFileInfo { /// File name. QString name; /// Version created by. quint16 versionCreated; /// Version needed to extract. quint16 versionNeeded; /// General purpose flags. quint16 flags; /// Compression method. quint16 method; /// Last modification date and time. QDateTime dateTime; /// CRC. quint32 crc; /// Compressed file size. quint32 compressedSize; /// Uncompressed file size. quint32 uncompressedSize; /// Disk number start. quint16 diskNumberStart; /// Internal file attributes. quint16 internalAttr; /// External file attributes. quint32 externalAttr; /// Comment. QString comment; /// Extra field. QByteArray extra; /// Get the file permissions. /** Returns the high 16 bits of external attributes converted to QFile::Permissions. */ QFile::Permissions getPermissions() const; }; /// Information about a file inside archive (with zip64 support). /** Call QuaZip::getCurrentFileInfo() or QuaZipFile::getFileInfo() to * fill this structure. */ struct QUAZIP_EXPORT QuaZipFileInfo64 { /// File name. QString name; /// Version created by. quint16 versionCreated; /// Version needed to extract. quint16 versionNeeded; /// General purpose flags. quint16 flags; /// Compression method. quint16 method; /// Last modification date and time. QDateTime dateTime; /// CRC. quint32 crc; /// Compressed file size. quint64 compressedSize; /// Uncompressed file size. quint64 uncompressedSize; /// Disk number start. quint16 diskNumberStart; /// Internal file attributes. quint16 internalAttr; /// External file attributes. quint32 externalAttr; /// Comment. QString comment; /// Extra field. QByteArray extra; /// Get the file permissions. /** Returns the high 16 bits of external attributes converted to QFile::Permissions. */ QFile::Permissions getPermissions() const; }; #endif workbench-1.1.1/src/Quazip/quazipnewinfo.cpp000066400000000000000000000055021255417355300211530ustar00rootroot00000000000000/* Copyright (C) 2005-2011 Sergey A. Tachenov This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA See COPYING file for the full LGPL text. Original ZIP package is copyrighted by Gilles Vollant, see quazip/(un)zip.h files for details, basically it's zlib license. */ #include #include "quazipnewinfo.h" static void QuaZipNewInfo_setPermissions(QuaZipNewInfo *info, QFile::Permissions perm, bool isDir) { quint32 uPerm = isDir ? 0040000 : 0100000; if ((perm & QFile::ReadOwner) != 0) uPerm |= 0400; if ((perm & QFile::WriteOwner) != 0) uPerm |= 0200; if ((perm & QFile::ExeOwner) != 0) uPerm |= 0100; if ((perm & QFile::ReadGroup) != 0) uPerm |= 0040; if ((perm & QFile::WriteGroup) != 0) uPerm |= 0020; if ((perm & QFile::ExeGroup) != 0) uPerm |= 0010; if ((perm & QFile::ReadOther) != 0) uPerm |= 0004; if ((perm & QFile::WriteOther) != 0) uPerm |= 0002; if ((perm & QFile::ExeOther) != 0) uPerm |= 0001; info->externalAttr = (info->externalAttr & ~0xFFFF0000u) | (uPerm << 16); } QuaZipNewInfo::QuaZipNewInfo(const QString& name): name(name), dateTime(QDateTime::currentDateTime()), internalAttr(0), externalAttr(0) { } QuaZipNewInfo::QuaZipNewInfo(const QString& name, const QString& file): name(name), internalAttr(0), externalAttr(0) { QFileInfo info(file); QDateTime lm = info.lastModified(); if (!info.exists()) { dateTime = QDateTime::currentDateTime(); } else { dateTime = lm; QuaZipNewInfo_setPermissions(this, info.permissions(), info.isDir()); } } void QuaZipNewInfo::setFileDateTime(const QString& file) { QFileInfo info(file); QDateTime lm = info.lastModified(); if (info.exists()) dateTime = lm; } void QuaZipNewInfo::setFilePermissions(const QString &file) { QFileInfo info = QFileInfo(file); QFile::Permissions perm = info.permissions(); QuaZipNewInfo_setPermissions(this, perm, info.isDir()); } void QuaZipNewInfo::setPermissions(QFile::Permissions permissions) { QuaZipNewInfo_setPermissions(this, permissions, name.endsWith('/')); } workbench-1.1.1/src/Quazip/quazipnewinfo.h000066400000000000000000000117151255417355300206230ustar00rootroot00000000000000#ifndef QUA_ZIPNEWINFO_H #define QUA_ZIPNEWINFO_H /* Copyright (C) 2005-2011 Sergey A. Tachenov This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA See COPYING file for the full LGPL text. Original ZIP package is copyrighted by Gilles Vollant, see quazip/(un)zip.h files for details, basically it's zlib license. **/ #include #include #include #include "quazip_global.h" /// Information about a file to be created. /** This structure holds information about a file to be created inside * ZIP archive. At least name should be set to something correct before * passing this structure to * QuaZipFile::open(OpenMode,const QuaZipNewInfo&,int,int,bool). * * Zip64 support of this structure is slightly limited: in the raw mode (when * a pre-compressed file is written into a ZIP file as-is), it is necessary * to specify the uncompressed file size and the appropriate field is 32 bit. * Since the raw mode is used extremely rare, there is no real need to have * a separate QuaZipNewInfo64 structure like QuaZipFileInfo64. It may be added * in the future though, if there is a demand for the raw mode with zip64 * archives. **/ struct QUAZIP_EXPORT QuaZipNewInfo { /// File name. /** This field holds file name inside archive, including path relative * to archive root. **/ QString name; /// File timestamp. /** This is the last file modification date and time. Will be stored * in the archive central directory. It is a good practice to set it * to the source file timestamp instead of archive creating time. Use * setFileDateTime() or QuaZipNewInfo(const QString&, const QString&). **/ QDateTime dateTime; /// File internal attributes. quint16 internalAttr; /// File external attributes. /** The highest 16 bits contain Unix file permissions and type (dir or file). The constructor QuaZipNewInfo(const QString&, const QString&) takes permissions from the provided file. */ quint32 externalAttr; /// File comment. /** Will be encoded using QuaZip::getCommentCodec(). **/ QString comment; /// File local extra field. QByteArray extraLocal; /// File global extra field. QByteArray extraGlobal; /// Uncompressed file size. /** This is only needed if you are using raw file zipping mode, i. e. * adding precompressed file in the zip archive. **/ ulong uncompressedSize; /// Constructs QuaZipNewInfo instance. /** Initializes name with \a name, dateTime with current date and * time. Attributes are initialized with zeros, comment and extra * field with null values. **/ QuaZipNewInfo(const QString& name); /// Constructs QuaZipNewInfo instance. /** Initializes name with \a name. Timestamp and permissions are taken * from the specified file. If the \a file does not exists or its timestamp * is inaccessible (e. g. you do not have read permission for the * directory file in), uses current time and zero permissions. Other attributes are * initialized with zeros, comment and extra field with null values. * * \sa setFileDateTime() **/ QuaZipNewInfo(const QString& name, const QString& file); /// Sets the file timestamp from the existing file. /** Use this function to set the file timestamp from the existing * file. Use it like this: * \code * QuaZipFile zipFile(&zip); * QFile file("file-to-add"); * file.open(QIODevice::ReadOnly); * QuaZipNewInfo info("file-name-in-archive"); * info.setFileDateTime("file-to-add"); // take the timestamp from file * zipFile.open(QIODevice::WriteOnly, info); * \endcode * * This function does not change dateTime if some error occured (e. g. * file is inaccessible). **/ void setFileDateTime(const QString& file); /// Sets the file permissions from the existing file. /** Takes permissions from the file and sets the high 16 bits of external attributes. Uses QFileInfo to get permissions on all platforms. */ void setFilePermissions(const QString &file); /// Sets the file permissions. /** Modifies the highest 16 bits of external attributes. The type part is set to dir if the name ends with a slash, and to regular file otherwise. */ void setPermissions(QFile::Permissions permissions); }; #endif workbench-1.1.1/src/Quazip/unzip.c000066400000000000000000002171151255417355300170660ustar00rootroot00000000000000/* unzip.c -- IO for uncompress .zip files using zlib Version 1.1, February 14h, 2010 part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html ) Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html ) Modifications of Unzip for Zip64 Copyright (C) 2007-2008 Even Rouault Modifications for Zip64 support on both zip and unzip Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com ) For more info read MiniZip_info.txt ------------------------------------------------------------------------------------ Decryption code comes from crypt.c by Info-ZIP but has been greatly reduced in terms of compatibility with older software. The following is from the original crypt.c. Code woven in by Terry Thorsen 1/2003. Copyright (c) 1990-2000 Info-ZIP. All rights reserved. See the accompanying file LICENSE, version 2000-Apr-09 or later (the contents of which are also included in zip.h) for terms of use. If, for some reason, all these files are missing, the Info-ZIP license also may be found at: ftp://ftp.info-zip.org/pub/infozip/license.html crypt.c (full version) by Info-ZIP. Last revised: [see crypt.h] The encryption/decryption parts of this source code (as opposed to the non-echoing password parts) were originally written in Europe. The whole source package can be freely distributed, including from the USA. (Prior to January 2000, re-export from the US was a violation of US law.) This encryption code is a direct transcription of the algorithm from Roger Schlafly, described by Phil Katz in the file appnote.txt. This file (appnote.txt) is distributed with the PKZIP program (even in the version without encryption capabilities). ------------------------------------------------------------------------------------ Changes in unzip.c 2007-2008 - Even Rouault - Addition of cpl_unzGetCurrentFileZStreamPos 2007-2008 - Even Rouault - Decoration of symbol names unz* -> cpl_unz* 2007-2008 - Even Rouault - Remove old C style function prototypes 2007-2008 - Even Rouault - Add unzip support for ZIP64 Copyright (C) 2007-2008 Even Rouault Oct-2009 - Mathias Svensson - Removed cpl_* from symbol names (Even Rouault added them but since this is now moved to a new project (minizip64) I renamed them again). Oct-2009 - Mathias Svensson - Fixed problem if uncompressed size was > 4G and compressed size was <4G should only read the compressed/uncompressed size from the Zip64 format if the size from normal header was 0xFFFFFFFF Oct-2009 - Mathias Svensson - Applied some bug fixes from paches recived from Gilles Vollant Oct-2009 - Mathias Svensson - Applied support to unzip files with compression mathod BZIP2 (bzip2 lib is required) Patch created by Daniel Borca Jan-2010 - back to unzip and minizip 1.0 name scheme, with compatibility layer Copyright (C) 1998 - 2010 Gilles Vollant, Even Rouault, Mathias Svensson */ #include #include #include #include "zlib.h" #if (ZLIB_VERNUM < 0x1270) typedef uLongf z_crc_t; #endif #include "unzip.h" #ifdef STDC # include # include # include #endif #ifdef NO_ERRNO_H extern int errno; #else # include #endif #ifndef local # define local static #endif /* compile with -Dlocal if your debugger can't find static symbols */ #ifndef CASESENSITIVITYDEFAULT_NO # if !defined(unix) && !defined(CASESENSITIVITYDEFAULT_YES) # define CASESENSITIVITYDEFAULT_NO # endif #endif #ifndef UNZ_BUFSIZE #define UNZ_BUFSIZE (16384) #endif #ifndef UNZ_MAXFILENAMEINZIP #define UNZ_MAXFILENAMEINZIP (256) #endif #ifndef ALLOC # define ALLOC(size) (malloc(size)) #endif #ifndef TRYFREE # define TRYFREE(p) {if (p) free(p);} #endif #define SIZECENTRALDIRITEM (0x2e) #define SIZEZIPLOCALHEADER (0x1e) const char unz_copyright[] = " unzip 1.01 Copyright 1998-2004 Gilles Vollant - http://www.winimage.com/zLibDll"; /* unz_file_info_interntal contain internal info about a file in zipfile*/ typedef struct unz_file_info64_internal_s { ZPOS64_T offset_curfile;/* relative offset of local header 8 bytes */ } unz_file_info64_internal; /* file_in_zip_read_info_s contain internal information about a file in zipfile, when reading and decompress it */ typedef struct { char *read_buffer; /* internal buffer for compressed data */ z_stream stream; /* zLib stream structure for inflate */ #ifdef HAVE_BZIP2 bz_stream bstream; /* bzLib stream structure for bziped */ #endif ZPOS64_T pos_in_zipfile; /* position in byte on the zipfile, for fseek*/ uLong stream_initialised; /* flag set if stream structure is initialised*/ ZPOS64_T offset_local_extrafield;/* offset of the local extra field */ uInt size_local_extrafield;/* size of the local extra field */ ZPOS64_T pos_local_extrafield; /* position in the local extra field in read*/ ZPOS64_T total_out_64; uLong crc32; /* crc32 of all data uncompressed */ uLong crc32_wait; /* crc32 we must obtain after decompress all */ ZPOS64_T rest_read_compressed; /* number of byte to be decompressed */ ZPOS64_T rest_read_uncompressed;/*number of byte to be obtained after decomp*/ zlib_filefunc64_32_def z_filefunc; voidpf filestream; /* io structore of the zipfile */ uLong compression_method; /* compression method (0==store) */ ZPOS64_T byte_before_the_zipfile;/* byte before the zipfile, (>0 for sfx)*/ int raw; } file_in_zip64_read_info_s; /* unz64_s contain internal information about the zipfile */ typedef struct { zlib_filefunc64_32_def z_filefunc; int is64bitOpenFunction; voidpf filestream; /* io structore of the zipfile */ unz_global_info64 gi; /* public global information */ ZPOS64_T byte_before_the_zipfile;/* byte before the zipfile, (>0 for sfx)*/ ZPOS64_T num_file; /* number of the current file in the zipfile*/ ZPOS64_T pos_in_central_dir; /* pos of the current file in the central dir*/ ZPOS64_T current_file_ok; /* flag about the usability of the current file*/ ZPOS64_T central_pos; /* position of the beginning of the central dir*/ ZPOS64_T size_central_dir; /* size of the central directory */ ZPOS64_T offset_central_dir; /* offset of start of central directory with respect to the starting disk number */ unz_file_info64 cur_file_info; /* public info about the current file in zip*/ unz_file_info64_internal cur_file_info_internal; /* private info about it*/ file_in_zip64_read_info_s* pfile_in_zip_read; /* structure about the current file if we are decompressing it */ int encrypted; int isZip64; # ifndef NOUNCRYPT unsigned long keys[3]; /* keys defining the pseudo-random sequence */ const z_crc_t FAR * pcrc_32_tab; # endif } unz64_s; #ifndef NOUNCRYPT #include "crypt.h" #endif /* =========================================================================== Read a byte from a gz_stream; update next_in and avail_in. Return EOF for end of file. IN assertion: the stream s has been sucessfully opened for reading. */ local int unz64local_getByte OF(( const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, int *pi)); local int unz64local_getByte(const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, int *pi) { unsigned char c; int err = (int)ZREAD64(*pzlib_filefunc_def,filestream,&c,1); if (err==1) { *pi = (int)c; return UNZ_OK; } else { if (ZERROR64(*pzlib_filefunc_def,filestream)) return UNZ_ERRNO; else return UNZ_EOF; } } /* =========================================================================== Reads a long in LSB order from the given gz_stream. Sets */ local int unz64local_getShort OF(( const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong *pX)); local int unz64local_getShort (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong *pX) { uLong x ; int i = 0; int err; err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); x = (uLong)i; if (err==UNZ_OK) err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); x |= ((uLong)i)<<8; if (err==UNZ_OK) *pX = x; else *pX = 0; return err; } local int unz64local_getLong OF(( const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong *pX)); local int unz64local_getLong (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong *pX) { uLong x ; int i = 0; int err; err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); x = (uLong)i; if (err==UNZ_OK) err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); x |= ((uLong)i)<<8; if (err==UNZ_OK) err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); x |= ((uLong)i)<<16; if (err==UNZ_OK) err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); x += ((uLong)i)<<24; if (err==UNZ_OK) *pX = x; else *pX = 0; return err; } local int unz64local_getLong64 OF(( const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T *pX)); local int unz64local_getLong64 (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T *pX) { ZPOS64_T x ; int i = 0; int err; err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); x = (ZPOS64_T)i; if (err==UNZ_OK) err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); x |= ((ZPOS64_T)i)<<8; if (err==UNZ_OK) err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); x |= ((ZPOS64_T)i)<<16; if (err==UNZ_OK) err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); x |= ((ZPOS64_T)i)<<24; if (err==UNZ_OK) err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); x |= ((ZPOS64_T)i)<<32; if (err==UNZ_OK) err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); x |= ((ZPOS64_T)i)<<40; if (err==UNZ_OK) err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); x |= ((ZPOS64_T)i)<<48; if (err==UNZ_OK) err = unz64local_getByte(pzlib_filefunc_def,filestream,&i); x |= ((ZPOS64_T)i)<<56; if (err==UNZ_OK) *pX = x; else *pX = 0; return err; } /* My own strcmpi / strcasecmp */ local int strcmpcasenosensitive_internal (const char* fileName1, const char* fileName2) { for (;;) { char c1=*(fileName1++); char c2=*(fileName2++); if ((c1>='a') && (c1<='z')) c1 -= 0x20; if ((c2>='a') && (c2<='z')) c2 -= 0x20; if (c1=='\0') return ((c2=='\0') ? 0 : -1); if (c2=='\0') return 1; if (c1c2) return 1; } } #ifdef CASESENSITIVITYDEFAULT_NO #define CASESENSITIVITYDEFAULTVALUE 2 #else #define CASESENSITIVITYDEFAULTVALUE 1 #endif #ifndef STRCMPCASENOSENTIVEFUNCTION #define STRCMPCASENOSENTIVEFUNCTION strcmpcasenosensitive_internal #endif /* Compare two filename (fileName1,fileName2). If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp) If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi or strcasecmp) If iCaseSenisivity = 0, case sensitivity is defaut of your operating system (like 1 on Unix, 2 on Windows) */ extern int ZEXPORT unzStringFileNameCompare (const char* fileName1, const char* fileName2, int iCaseSensitivity) { if (iCaseSensitivity==0) iCaseSensitivity=CASESENSITIVITYDEFAULTVALUE; if (iCaseSensitivity==1) return strcmp(fileName1,fileName2); return STRCMPCASENOSENTIVEFUNCTION(fileName1,fileName2); } #ifndef BUFREADCOMMENT #define BUFREADCOMMENT (0x400) #endif /* Locate the Central directory of a zipfile (at the end, just before the global comment) */ local ZPOS64_T unz64local_SearchCentralDir OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream)); local ZPOS64_T unz64local_SearchCentralDir(const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream) { unsigned char* buf; ZPOS64_T uSizeFile; ZPOS64_T uBackRead; ZPOS64_T uMaxBack=0xffff; /* maximum size of global comment */ ZPOS64_T uPosFound=0; if (ZSEEK64(*pzlib_filefunc_def,filestream,0,ZLIB_FILEFUNC_SEEK_END) != 0) return 0; uSizeFile = ZTELL64(*pzlib_filefunc_def,filestream); if (uMaxBack>uSizeFile) uMaxBack = uSizeFile; buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4); if (buf==NULL) return 0; uBackRead = 4; while (uBackReaduMaxBack) uBackRead = uMaxBack; else uBackRead+=BUFREADCOMMENT; uReadPos = uSizeFile-uBackRead ; uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ? (BUFREADCOMMENT+4) : (uLong)(uSizeFile-uReadPos); if (ZSEEK64(*pzlib_filefunc_def,filestream,uReadPos,ZLIB_FILEFUNC_SEEK_SET)!=0) break; if (ZREAD64(*pzlib_filefunc_def,filestream,buf,uReadSize)!=uReadSize) break; for (i=(int)uReadSize-3; (i--)>0;) if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) && ((*(buf+i+2))==0x05) && ((*(buf+i+3))==0x06)) { uPosFound = uReadPos+i; break; } if (uPosFound!=0) break; } TRYFREE(buf); return uPosFound; } /* Locate the Central directory 64 of a zipfile (at the end, just before the global comment) */ local ZPOS64_T unz64local_SearchCentralDir64 OF(( const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream)); local ZPOS64_T unz64local_SearchCentralDir64(const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream) { unsigned char* buf; ZPOS64_T uSizeFile; ZPOS64_T uBackRead; ZPOS64_T uMaxBack=0xffff; /* maximum size of global comment */ ZPOS64_T uPosFound=0; uLong uL; ZPOS64_T relativeOffset; if (ZSEEK64(*pzlib_filefunc_def,filestream,0,ZLIB_FILEFUNC_SEEK_END) != 0) return 0; uSizeFile = ZTELL64(*pzlib_filefunc_def,filestream); if (uMaxBack>uSizeFile) uMaxBack = uSizeFile; buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4); if (buf==NULL) return 0; uBackRead = 4; while (uBackReaduMaxBack) uBackRead = uMaxBack; else uBackRead+=BUFREADCOMMENT; uReadPos = uSizeFile-uBackRead ; uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ? (BUFREADCOMMENT+4) : (uLong)(uSizeFile-uReadPos); if (ZSEEK64(*pzlib_filefunc_def,filestream,uReadPos,ZLIB_FILEFUNC_SEEK_SET)!=0) break; if (ZREAD64(*pzlib_filefunc_def,filestream,buf,uReadSize)!=uReadSize) break; for (i=(int)uReadSize-3; (i--)>0;) if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) && ((*(buf+i+2))==0x06) && ((*(buf+i+3))==0x07)) { uPosFound = uReadPos+i; break; } if (uPosFound!=0) break; } TRYFREE(buf); if (uPosFound == 0) return 0; /* Zip64 end of central directory locator */ if (ZSEEK64(*pzlib_filefunc_def,filestream, uPosFound,ZLIB_FILEFUNC_SEEK_SET)!=0) return 0; /* the signature, already checked */ if (unz64local_getLong(pzlib_filefunc_def,filestream,&uL)!=UNZ_OK) return 0; /* number of the disk with the start of the zip64 end of central directory */ if (unz64local_getLong(pzlib_filefunc_def,filestream,&uL)!=UNZ_OK) return 0; if (uL != 0) return 0; /* relative offset of the zip64 end of central directory record */ if (unz64local_getLong64(pzlib_filefunc_def,filestream,&relativeOffset)!=UNZ_OK) return 0; /* total number of disks */ if (unz64local_getLong(pzlib_filefunc_def,filestream,&uL)!=UNZ_OK) return 0; if (uL != 1) return 0; /* Goto end of central directory record */ if (ZSEEK64(*pzlib_filefunc_def,filestream, relativeOffset,ZLIB_FILEFUNC_SEEK_SET)!=0) return 0; /* the signature */ if (unz64local_getLong(pzlib_filefunc_def,filestream,&uL)!=UNZ_OK) return 0; if (uL != 0x06064b50) return 0; return relativeOffset; } /* Open a Zip file. path contain the full pathname (by example, on a Windows NT computer "c:\\test\\zlib114.zip" or on an Unix computer "zlib/zlib114.zip". If the zipfile cannot be opened (file doesn't exist or in not valid), the return value is NULL. Else, the return value is a unzFile Handle, usable with other function of this unzip package. */ local unzFile unzOpenInternal (voidpf file, zlib_filefunc64_32_def* pzlib_filefunc64_32_def, int is64bitOpenFunction) { unz64_s us; unz64_s *s; ZPOS64_T central_pos; uLong uL; uLong number_disk; /* number of the current dist, used for spaning ZIP, unsupported, always 0*/ uLong number_disk_with_CD; /* number the the disk with central dir, used for spaning ZIP, unsupported, always 0*/ ZPOS64_T number_entry_CD; /* total number of entries in the central dir (same than number_entry on nospan) */ int err=UNZ_OK; if (unz_copyright[0]!=' ') return NULL; us.z_filefunc.zseek32_file = NULL; us.z_filefunc.ztell32_file = NULL; if (pzlib_filefunc64_32_def==NULL) fill_qiodevice64_filefunc(&us.z_filefunc.zfile_func64); else us.z_filefunc = *pzlib_filefunc64_32_def; us.is64bitOpenFunction = is64bitOpenFunction; us.filestream = ZOPEN64(us.z_filefunc, file, ZLIB_FILEFUNC_MODE_READ | ZLIB_FILEFUNC_MODE_EXISTING); if (us.filestream==NULL) return NULL; central_pos = unz64local_SearchCentralDir64(&us.z_filefunc,us.filestream); if (central_pos) { uLong uS; ZPOS64_T uL64; us.isZip64 = 1; if (ZSEEK64(us.z_filefunc, us.filestream, central_pos,ZLIB_FILEFUNC_SEEK_SET)!=0) err=UNZ_ERRNO; /* the signature, already checked */ if (unz64local_getLong(&us.z_filefunc, us.filestream,&uL)!=UNZ_OK) err=UNZ_ERRNO; /* size of zip64 end of central directory record */ if (unz64local_getLong64(&us.z_filefunc, us.filestream,&uL64)!=UNZ_OK) err=UNZ_ERRNO; /* version made by */ if (unz64local_getShort(&us.z_filefunc, us.filestream,&uS)!=UNZ_OK) err=UNZ_ERRNO; /* version needed to extract */ if (unz64local_getShort(&us.z_filefunc, us.filestream,&uS)!=UNZ_OK) err=UNZ_ERRNO; /* number of this disk */ if (unz64local_getLong(&us.z_filefunc, us.filestream,&number_disk)!=UNZ_OK) err=UNZ_ERRNO; /* number of the disk with the start of the central directory */ if (unz64local_getLong(&us.z_filefunc, us.filestream,&number_disk_with_CD)!=UNZ_OK) err=UNZ_ERRNO; /* total number of entries in the central directory on this disk */ if (unz64local_getLong64(&us.z_filefunc, us.filestream,&us.gi.number_entry)!=UNZ_OK) err=UNZ_ERRNO; /* total number of entries in the central directory */ if (unz64local_getLong64(&us.z_filefunc, us.filestream,&number_entry_CD)!=UNZ_OK) err=UNZ_ERRNO; if ((number_entry_CD!=us.gi.number_entry) || (number_disk_with_CD!=0) || (number_disk!=0)) err=UNZ_BADZIPFILE; /* size of the central directory */ if (unz64local_getLong64(&us.z_filefunc, us.filestream,&us.size_central_dir)!=UNZ_OK) err=UNZ_ERRNO; /* offset of start of central directory with respect to the starting disk number */ if (unz64local_getLong64(&us.z_filefunc, us.filestream,&us.offset_central_dir)!=UNZ_OK) err=UNZ_ERRNO; us.gi.size_comment = 0; } else { central_pos = unz64local_SearchCentralDir(&us.z_filefunc,us.filestream); if (central_pos==0) err=UNZ_ERRNO; us.isZip64 = 0; if (ZSEEK64(us.z_filefunc, us.filestream, central_pos,ZLIB_FILEFUNC_SEEK_SET)!=0) err=UNZ_ERRNO; /* the signature, already checked */ if (unz64local_getLong(&us.z_filefunc, us.filestream,&uL)!=UNZ_OK) err=UNZ_ERRNO; /* number of this disk */ if (unz64local_getShort(&us.z_filefunc, us.filestream,&number_disk)!=UNZ_OK) err=UNZ_ERRNO; /* number of the disk with the start of the central directory */ if (unz64local_getShort(&us.z_filefunc, us.filestream,&number_disk_with_CD)!=UNZ_OK) err=UNZ_ERRNO; /* total number of entries in the central dir on this disk */ if (unz64local_getShort(&us.z_filefunc, us.filestream,&uL)!=UNZ_OK) err=UNZ_ERRNO; us.gi.number_entry = uL; /* total number of entries in the central dir */ if (unz64local_getShort(&us.z_filefunc, us.filestream,&uL)!=UNZ_OK) err=UNZ_ERRNO; number_entry_CD = uL; if ((number_entry_CD!=us.gi.number_entry) || (number_disk_with_CD!=0) || (number_disk!=0)) err=UNZ_BADZIPFILE; /* size of the central directory */ if (unz64local_getLong(&us.z_filefunc, us.filestream,&uL)!=UNZ_OK) err=UNZ_ERRNO; us.size_central_dir = uL; /* offset of start of central directory with respect to the starting disk number */ if (unz64local_getLong(&us.z_filefunc, us.filestream,&uL)!=UNZ_OK) err=UNZ_ERRNO; us.offset_central_dir = uL; /* zipfile comment length */ if (unz64local_getShort(&us.z_filefunc, us.filestream,&us.gi.size_comment)!=UNZ_OK) err=UNZ_ERRNO; } if ((central_pospfile_in_zip_read!=NULL) unzCloseCurrentFile(file); ZCLOSE64(s->z_filefunc, s->filestream); TRYFREE(s); return UNZ_OK; } /* Write info about the ZipFile in the *pglobal_info structure. No preparation of the structure is needed return UNZ_OK if there is no problem. */ extern int ZEXPORT unzGetGlobalInfo64 (unzFile file, unz_global_info64* pglobal_info) { unz64_s* s; if (file==NULL) return UNZ_PARAMERROR; s=(unz64_s*)file; *pglobal_info=s->gi; return UNZ_OK; } extern int ZEXPORT unzGetGlobalInfo (unzFile file, unz_global_info* pglobal_info32) { unz64_s* s; if (file==NULL) return UNZ_PARAMERROR; s=(unz64_s*)file; /* to do : check if number_entry is not truncated */ pglobal_info32->number_entry = (uLong)s->gi.number_entry; pglobal_info32->size_comment = s->gi.size_comment; return UNZ_OK; } /* Translate date/time from Dos format to tm_unz (readable more easilty) */ local void unz64local_DosDateToTmuDate (ZPOS64_T ulDosDate, tm_unz* ptm) { ZPOS64_T uDate; uDate = (ZPOS64_T)(ulDosDate>>16); ptm->tm_mday = (uInt)(uDate&0x1f) ; ptm->tm_mon = (uInt)((((uDate)&0x1E0)/0x20)-1) ; ptm->tm_year = (uInt)(((uDate&0x0FE00)/0x0200)+1980) ; ptm->tm_hour = (uInt) ((ulDosDate &0xF800)/0x800); ptm->tm_min = (uInt) ((ulDosDate&0x7E0)/0x20) ; ptm->tm_sec = (uInt) (2*(ulDosDate&0x1f)) ; } /* Get Info about the current file in the zipfile, with internal only info */ local int unz64local_GetCurrentFileInfoInternal OF((unzFile file, unz_file_info64 *pfile_info, unz_file_info64_internal *pfile_info_internal, char *szFileName, uLong fileNameBufferSize, void *extraField, uLong extraFieldBufferSize, char *szComment, uLong commentBufferSize)); local int unz64local_GetCurrentFileInfoInternal (unzFile file, unz_file_info64 *pfile_info, unz_file_info64_internal *pfile_info_internal, char *szFileName, uLong fileNameBufferSize, void *extraField, uLong extraFieldBufferSize, char *szComment, uLong commentBufferSize) { unz64_s* s; unz_file_info64 file_info; unz_file_info64_internal file_info_internal; int err=UNZ_OK; uLong uMagic; ZPOS64_T llSeek=0; uLong uL; if (file==NULL) return UNZ_PARAMERROR; s=(unz64_s*)file; if (ZSEEK64(s->z_filefunc, s->filestream, s->pos_in_central_dir+s->byte_before_the_zipfile, ZLIB_FILEFUNC_SEEK_SET)!=0) err=UNZ_ERRNO; /* we check the magic */ if (err==UNZ_OK) { if (unz64local_getLong(&s->z_filefunc, s->filestream,&uMagic) != UNZ_OK) err=UNZ_ERRNO; else if (uMagic!=0x02014b50) err=UNZ_BADZIPFILE; } if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.version) != UNZ_OK) err=UNZ_ERRNO; if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.version_needed) != UNZ_OK) err=UNZ_ERRNO; if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.flag) != UNZ_OK) err=UNZ_ERRNO; if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.compression_method) != UNZ_OK) err=UNZ_ERRNO; if (unz64local_getLong(&s->z_filefunc, s->filestream,&file_info.dosDate) != UNZ_OK) err=UNZ_ERRNO; unz64local_DosDateToTmuDate(file_info.dosDate,&file_info.tmu_date); if (unz64local_getLong(&s->z_filefunc, s->filestream,&file_info.crc) != UNZ_OK) err=UNZ_ERRNO; if (unz64local_getLong(&s->z_filefunc, s->filestream,&uL) != UNZ_OK) err=UNZ_ERRNO; file_info.compressed_size = uL; if (unz64local_getLong(&s->z_filefunc, s->filestream,&uL) != UNZ_OK) err=UNZ_ERRNO; file_info.uncompressed_size = uL; if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.size_filename) != UNZ_OK) err=UNZ_ERRNO; if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.size_file_extra) != UNZ_OK) err=UNZ_ERRNO; if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.size_file_comment) != UNZ_OK) err=UNZ_ERRNO; if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.disk_num_start) != UNZ_OK) err=UNZ_ERRNO; if (unz64local_getShort(&s->z_filefunc, s->filestream,&file_info.internal_fa) != UNZ_OK) err=UNZ_ERRNO; if (unz64local_getLong(&s->z_filefunc, s->filestream,&file_info.external_fa) != UNZ_OK) err=UNZ_ERRNO; /* relative offset of local header */ if (unz64local_getLong(&s->z_filefunc, s->filestream,&uL) != UNZ_OK) err=UNZ_ERRNO; file_info_internal.offset_curfile = uL; llSeek+=file_info.size_filename; if ((err==UNZ_OK) && (szFileName!=NULL)) { uLong uSizeRead ; if (file_info.size_filename0) && (fileNameBufferSize>0)) if (ZREAD64(s->z_filefunc, s->filestream,szFileName,uSizeRead)!=uSizeRead) err=UNZ_ERRNO; llSeek -= uSizeRead; } /* Read extrafield */ if ((err==UNZ_OK) && (extraField!=NULL)) { ZPOS64_T uSizeRead ; if (file_info.size_file_extraz_filefunc, s->filestream,llSeek,ZLIB_FILEFUNC_SEEK_CUR)==0) llSeek=0; else err=UNZ_ERRNO; } if ((file_info.size_file_extra>0) && (extraFieldBufferSize>0)) if (ZREAD64(s->z_filefunc, s->filestream,extraField,(uLong)uSizeRead)!=uSizeRead) err=UNZ_ERRNO; llSeek += file_info.size_file_extra - (uLong)uSizeRead; } else llSeek += file_info.size_file_extra; if ((err==UNZ_OK) && (file_info.size_file_extra != 0)) { uLong acc = 0; /* since lSeek now points to after the extra field we need to move back */ llSeek -= file_info.size_file_extra; if (llSeek!=0) { if (ZSEEK64(s->z_filefunc, s->filestream,llSeek,ZLIB_FILEFUNC_SEEK_CUR)==0) llSeek=0; else err=UNZ_ERRNO; } while(acc < file_info.size_file_extra) { uLong headerId; uLong dataSize; if (unz64local_getShort(&s->z_filefunc, s->filestream,&headerId) != UNZ_OK) err=UNZ_ERRNO; if (unz64local_getShort(&s->z_filefunc, s->filestream,&dataSize) != UNZ_OK) err=UNZ_ERRNO; /* ZIP64 extra fields */ if (headerId == 0x0001) { uLong uL; if(file_info.uncompressed_size == (ZPOS64_T)(unsigned long)-1) { if (unz64local_getLong64(&s->z_filefunc, s->filestream,&file_info.uncompressed_size) != UNZ_OK) err=UNZ_ERRNO; } if(file_info.compressed_size == (ZPOS64_T)(unsigned long)-1) { if (unz64local_getLong64(&s->z_filefunc, s->filestream,&file_info.compressed_size) != UNZ_OK) err=UNZ_ERRNO; } if(file_info_internal.offset_curfile == (ZPOS64_T)(unsigned long)-1) { /* Relative Header offset */ if (unz64local_getLong64(&s->z_filefunc, s->filestream,&file_info_internal.offset_curfile) != UNZ_OK) err=UNZ_ERRNO; } if(file_info.disk_num_start == (unsigned long)-1) { /* Disk Start Number */ if (unz64local_getLong(&s->z_filefunc, s->filestream,&uL) != UNZ_OK) err=UNZ_ERRNO; } } else { if (ZSEEK64(s->z_filefunc, s->filestream,dataSize,ZLIB_FILEFUNC_SEEK_CUR)!=0) err=UNZ_ERRNO; } acc += 2 + 2 + dataSize; } } if ((err==UNZ_OK) && (szComment!=NULL)) { uLong uSizeRead ; if (file_info.size_file_commentz_filefunc, s->filestream,llSeek,ZLIB_FILEFUNC_SEEK_CUR)==0) llSeek=0; else err=UNZ_ERRNO; } if ((file_info.size_file_comment>0) && (commentBufferSize>0)) if (ZREAD64(s->z_filefunc, s->filestream,szComment,uSizeRead)!=uSizeRead) err=UNZ_ERRNO; llSeek+=file_info.size_file_comment - uSizeRead; } else llSeek+=file_info.size_file_comment; if ((err==UNZ_OK) && (pfile_info!=NULL)) *pfile_info=file_info; if ((err==UNZ_OK) && (pfile_info_internal!=NULL)) *pfile_info_internal=file_info_internal; return err; } /* Write info about the ZipFile in the *pglobal_info structure. No preparation of the structure is needed return UNZ_OK if there is no problem. */ extern int ZEXPORT unzGetCurrentFileInfo64 (unzFile file, unz_file_info64 * pfile_info, char * szFileName, uLong fileNameBufferSize, void *extraField, uLong extraFieldBufferSize, char* szComment, uLong commentBufferSize) { return unz64local_GetCurrentFileInfoInternal(file,pfile_info,NULL, szFileName,fileNameBufferSize, extraField,extraFieldBufferSize, szComment,commentBufferSize); } extern int ZEXPORT unzGetCurrentFileInfo (unzFile file, unz_file_info * pfile_info, char * szFileName, uLong fileNameBufferSize, void *extraField, uLong extraFieldBufferSize, char* szComment, uLong commentBufferSize) { int err; unz_file_info64 file_info64; err = unz64local_GetCurrentFileInfoInternal(file,&file_info64,NULL, szFileName,fileNameBufferSize, extraField,extraFieldBufferSize, szComment,commentBufferSize); if (err==UNZ_OK && pfile_info != NULL) { pfile_info->version = file_info64.version; pfile_info->version_needed = file_info64.version_needed; pfile_info->flag = file_info64.flag; pfile_info->compression_method = file_info64.compression_method; pfile_info->dosDate = file_info64.dosDate; pfile_info->crc = file_info64.crc; pfile_info->size_filename = file_info64.size_filename; pfile_info->size_file_extra = file_info64.size_file_extra; pfile_info->size_file_comment = file_info64.size_file_comment; pfile_info->disk_num_start = file_info64.disk_num_start; pfile_info->internal_fa = file_info64.internal_fa; pfile_info->external_fa = file_info64.external_fa; pfile_info->tmu_date = file_info64.tmu_date, pfile_info->compressed_size = (uLong)file_info64.compressed_size; pfile_info->uncompressed_size = (uLong)file_info64.uncompressed_size; } return err; } /* Set the current file of the zipfile to the first file. return UNZ_OK if there is no problem */ extern int ZEXPORT unzGoToFirstFile (unzFile file) { int err=UNZ_OK; unz64_s* s; if (file==NULL) return UNZ_PARAMERROR; s=(unz64_s*)file; s->pos_in_central_dir=s->offset_central_dir; s->num_file=0; err=unz64local_GetCurrentFileInfoInternal(file,&s->cur_file_info, &s->cur_file_info_internal, NULL,0,NULL,0,NULL,0); s->current_file_ok = (err == UNZ_OK); return err; } /* Set the current file of the zipfile to the next file. return UNZ_OK if there is no problem return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest. */ extern int ZEXPORT unzGoToNextFile (unzFile file) { unz64_s* s; int err; if (file==NULL) return UNZ_PARAMERROR; s=(unz64_s*)file; if (!s->current_file_ok) return UNZ_END_OF_LIST_OF_FILE; if (s->gi.number_entry != 0xffff) /* 2^16 files overflow hack */ if (s->num_file+1==s->gi.number_entry) return UNZ_END_OF_LIST_OF_FILE; s->pos_in_central_dir += SIZECENTRALDIRITEM + s->cur_file_info.size_filename + s->cur_file_info.size_file_extra + s->cur_file_info.size_file_comment ; s->num_file++; err = unz64local_GetCurrentFileInfoInternal(file,&s->cur_file_info, &s->cur_file_info_internal, NULL,0,NULL,0,NULL,0); s->current_file_ok = (err == UNZ_OK); return err; } /* Try locate the file szFileName in the zipfile. For the iCaseSensitivity signification, see unzipStringFileNameCompare return value : UNZ_OK if the file is found. It becomes the current file. UNZ_END_OF_LIST_OF_FILE if the file is not found */ extern int ZEXPORT unzLocateFile (unzFile file, const char *szFileName, int iCaseSensitivity) { unz64_s* s; int err; /* We remember the 'current' position in the file so that we can jump * back there if we fail. */ unz_file_info64 cur_file_infoSaved; unz_file_info64_internal cur_file_info_internalSaved; ZPOS64_T num_fileSaved; ZPOS64_T pos_in_central_dirSaved; if (file==NULL) return UNZ_PARAMERROR; if (strlen(szFileName)>=UNZ_MAXFILENAMEINZIP) return UNZ_PARAMERROR; s=(unz64_s*)file; if (!s->current_file_ok) return UNZ_END_OF_LIST_OF_FILE; /* Save the current state */ num_fileSaved = s->num_file; pos_in_central_dirSaved = s->pos_in_central_dir; cur_file_infoSaved = s->cur_file_info; cur_file_info_internalSaved = s->cur_file_info_internal; err = unzGoToFirstFile(file); while (err == UNZ_OK) { char szCurrentFileName[UNZ_MAXFILENAMEINZIP+1]; err = unzGetCurrentFileInfo64(file,NULL, szCurrentFileName,sizeof(szCurrentFileName)-1, NULL,0,NULL,0); if (err == UNZ_OK) { if (unzStringFileNameCompare(szCurrentFileName, szFileName,iCaseSensitivity)==0) return UNZ_OK; err = unzGoToNextFile(file); } } /* We failed, so restore the state of the 'current file' to where we * were. */ s->num_file = num_fileSaved ; s->pos_in_central_dir = pos_in_central_dirSaved ; s->cur_file_info = cur_file_infoSaved; s->cur_file_info_internal = cur_file_info_internalSaved; return err; } /* /////////////////////////////////////////// // Contributed by Ryan Haksi (mailto://cryogen@infoserve.net) // I need random access // // Further optimization could be realized by adding an ability // to cache the directory in memory. The goal being a single // comprehensive file read to put the file I need in a memory. */ /* typedef struct unz_file_pos_s { ZPOS64_T pos_in_zip_directory; // offset in file ZPOS64_T num_of_file; // # of file } unz_file_pos; */ extern int ZEXPORT unzGetFilePos64(unzFile file, unz64_file_pos* file_pos) { unz64_s* s; if (file==NULL || file_pos==NULL) return UNZ_PARAMERROR; s=(unz64_s*)file; if (!s->current_file_ok) return UNZ_END_OF_LIST_OF_FILE; file_pos->pos_in_zip_directory = s->pos_in_central_dir; file_pos->num_of_file = s->num_file; return UNZ_OK; } extern int ZEXPORT unzGetFilePos( unzFile file, unz_file_pos* file_pos) { unz64_file_pos file_pos64; int err = unzGetFilePos64(file,&file_pos64); if (err==UNZ_OK) { file_pos->pos_in_zip_directory = (uLong)file_pos64.pos_in_zip_directory; file_pos->num_of_file = (uLong)file_pos64.num_of_file; } return err; } extern int ZEXPORT unzGoToFilePos64(unzFile file, const unz64_file_pos* file_pos) { unz64_s* s; int err; if (file==NULL || file_pos==NULL) return UNZ_PARAMERROR; s=(unz64_s*)file; /* jump to the right spot */ s->pos_in_central_dir = file_pos->pos_in_zip_directory; s->num_file = file_pos->num_of_file; /* set the current file */ err = unz64local_GetCurrentFileInfoInternal(file,&s->cur_file_info, &s->cur_file_info_internal, NULL,0,NULL,0,NULL,0); /* return results */ s->current_file_ok = (err == UNZ_OK); return err; } extern int ZEXPORT unzGoToFilePos( unzFile file, unz_file_pos* file_pos) { unz64_file_pos file_pos64; if (file_pos == NULL) return UNZ_PARAMERROR; file_pos64.pos_in_zip_directory = file_pos->pos_in_zip_directory; file_pos64.num_of_file = file_pos->num_of_file; return unzGoToFilePos64(file,&file_pos64); } /* Unzip Helper Functions - should be here? */ /*///////////////////////////////////////// */ /* Read the local header of the current zipfile Check the coherency of the local header and info in the end of central directory about this file store in *piSizeVar the size of extra info in local header (filename and size of extra field data) */ local int unz64local_CheckCurrentFileCoherencyHeader (unz64_s* s, uInt* piSizeVar, ZPOS64_T * poffset_local_extrafield, uInt * psize_local_extrafield) { uLong uMagic,uData,uFlags; uLong size_filename; uLong size_extra_field; int err=UNZ_OK; *piSizeVar = 0; *poffset_local_extrafield = 0; *psize_local_extrafield = 0; if (ZSEEK64(s->z_filefunc, s->filestream,s->cur_file_info_internal.offset_curfile + s->byte_before_the_zipfile,ZLIB_FILEFUNC_SEEK_SET)!=0) return UNZ_ERRNO; if (err==UNZ_OK) { if (unz64local_getLong(&s->z_filefunc, s->filestream,&uMagic) != UNZ_OK) err=UNZ_ERRNO; else if (uMagic!=0x04034b50) err=UNZ_BADZIPFILE; } if (unz64local_getShort(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) err=UNZ_ERRNO; /* else if ((err==UNZ_OK) && (uData!=s->cur_file_info.wVersion)) err=UNZ_BADZIPFILE; */ if (unz64local_getShort(&s->z_filefunc, s->filestream,&uFlags) != UNZ_OK) err=UNZ_ERRNO; if (unz64local_getShort(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) err=UNZ_ERRNO; else if ((err==UNZ_OK) && (uData!=s->cur_file_info.compression_method)) err=UNZ_BADZIPFILE; if ((err==UNZ_OK) && (s->cur_file_info.compression_method!=0) && /* #ifdef HAVE_BZIP2 */ (s->cur_file_info.compression_method!=Z_BZIP2ED) && /* #endif */ (s->cur_file_info.compression_method!=Z_DEFLATED)) err=UNZ_BADZIPFILE; if (unz64local_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* date/time */ err=UNZ_ERRNO; if (unz64local_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* crc */ err=UNZ_ERRNO; else if ((err==UNZ_OK) && (uData!=s->cur_file_info.crc) && ((uFlags & 8)==0)) err=UNZ_BADZIPFILE; if (unz64local_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* size compr */ err=UNZ_ERRNO; else if (uData != 0xFFFFFFFF && (err==UNZ_OK) && (uData!=s->cur_file_info.compressed_size) && ((uFlags & 8)==0)) err=UNZ_BADZIPFILE; if (unz64local_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* size uncompr */ err=UNZ_ERRNO; else if (uData != 0xFFFFFFFF && (err==UNZ_OK) && (uData!=s->cur_file_info.uncompressed_size) && ((uFlags & 8)==0)) err=UNZ_BADZIPFILE; if (unz64local_getShort(&s->z_filefunc, s->filestream,&size_filename) != UNZ_OK) err=UNZ_ERRNO; else if ((err==UNZ_OK) && (size_filename!=s->cur_file_info.size_filename)) err=UNZ_BADZIPFILE; *piSizeVar += (uInt)size_filename; if (unz64local_getShort(&s->z_filefunc, s->filestream,&size_extra_field) != UNZ_OK) err=UNZ_ERRNO; *poffset_local_extrafield= s->cur_file_info_internal.offset_curfile + SIZEZIPLOCALHEADER + size_filename; *psize_local_extrafield = (uInt)size_extra_field; *piSizeVar += (uInt)size_extra_field; return err; } /* Open for reading data the current file in the zipfile. If there is no error and the file is opened, the return value is UNZ_OK. */ extern int ZEXPORT unzOpenCurrentFile3 (unzFile file, int* method, int* level, int raw, const char* password) { int err=UNZ_OK; uInt iSizeVar; unz64_s* s; file_in_zip64_read_info_s* pfile_in_zip_read_info; ZPOS64_T offset_local_extrafield; /* offset of the local extra field */ uInt size_local_extrafield; /* size of the local extra field */ # ifndef NOUNCRYPT char source[12]; # else if (password != NULL) return UNZ_PARAMERROR; # endif if (file==NULL) return UNZ_PARAMERROR; s=(unz64_s*)file; if (!s->current_file_ok) return UNZ_PARAMERROR; if (s->pfile_in_zip_read != NULL) unzCloseCurrentFile(file); if (unz64local_CheckCurrentFileCoherencyHeader(s,&iSizeVar, &offset_local_extrafield,&size_local_extrafield)!=UNZ_OK) return UNZ_BADZIPFILE; pfile_in_zip_read_info = (file_in_zip64_read_info_s*)ALLOC(sizeof(file_in_zip64_read_info_s)); if (pfile_in_zip_read_info==NULL) return UNZ_INTERNALERROR; pfile_in_zip_read_info->read_buffer=(char*)ALLOC(UNZ_BUFSIZE); pfile_in_zip_read_info->offset_local_extrafield = offset_local_extrafield; pfile_in_zip_read_info->size_local_extrafield = size_local_extrafield; pfile_in_zip_read_info->pos_local_extrafield=0; pfile_in_zip_read_info->raw=raw; if (pfile_in_zip_read_info->read_buffer==NULL) { TRYFREE(pfile_in_zip_read_info); return UNZ_INTERNALERROR; } pfile_in_zip_read_info->stream_initialised=0; if (method!=NULL) *method = (int)s->cur_file_info.compression_method; if (level!=NULL) { *level = 6; switch (s->cur_file_info.flag & 0x06) { case 6 : *level = 1; break; case 4 : *level = 2; break; case 2 : *level = 9; break; } } if ((s->cur_file_info.compression_method!=0) && /* #ifdef HAVE_BZIP2 */ (s->cur_file_info.compression_method!=Z_BZIP2ED) && /* #endif */ (s->cur_file_info.compression_method!=Z_DEFLATED)) err=UNZ_BADZIPFILE; pfile_in_zip_read_info->crc32_wait=s->cur_file_info.crc; pfile_in_zip_read_info->crc32=0; pfile_in_zip_read_info->total_out_64=0; pfile_in_zip_read_info->compression_method = s->cur_file_info.compression_method; pfile_in_zip_read_info->filestream=s->filestream; pfile_in_zip_read_info->z_filefunc=s->z_filefunc; pfile_in_zip_read_info->byte_before_the_zipfile=s->byte_before_the_zipfile; pfile_in_zip_read_info->stream.total_out = 0; if ((s->cur_file_info.compression_method==Z_BZIP2ED) && (!raw)) { #ifdef HAVE_BZIP2 pfile_in_zip_read_info->bstream.bzalloc = (void *(*) (void *, int, int))0; pfile_in_zip_read_info->bstream.bzfree = (free_func)0; pfile_in_zip_read_info->bstream.opaque = (voidpf)0; pfile_in_zip_read_info->bstream.state = (voidpf)0; pfile_in_zip_read_info->stream.zalloc = (alloc_func)0; pfile_in_zip_read_info->stream.zfree = (free_func)0; pfile_in_zip_read_info->stream.opaque = (voidpf)0; pfile_in_zip_read_info->stream.next_in = (voidpf)0; pfile_in_zip_read_info->stream.avail_in = 0; err=BZ2_bzDecompressInit(&pfile_in_zip_read_info->bstream, 0, 0); if (err == Z_OK) pfile_in_zip_read_info->stream_initialised=Z_BZIP2ED; else { TRYFREE(pfile_in_zip_read_info); return err; } #else pfile_in_zip_read_info->raw=1; #endif } else if ((s->cur_file_info.compression_method==Z_DEFLATED) && (!raw)) { pfile_in_zip_read_info->stream.zalloc = (alloc_func)0; pfile_in_zip_read_info->stream.zfree = (free_func)0; pfile_in_zip_read_info->stream.opaque = (voidpf)0; pfile_in_zip_read_info->stream.next_in = 0; pfile_in_zip_read_info->stream.avail_in = 0; err=inflateInit2(&pfile_in_zip_read_info->stream, -MAX_WBITS); if (err == Z_OK) pfile_in_zip_read_info->stream_initialised=Z_DEFLATED; else { TRYFREE(pfile_in_zip_read_info); return err; } /* windowBits is passed < 0 to tell that there is no zlib header. * Note that in this case inflate *requires* an extra "dummy" byte * after the compressed stream in order to complete decompression and * return Z_STREAM_END. * In unzip, i don't wait absolutely Z_STREAM_END because I known the * size of both compressed and uncompressed data */ } pfile_in_zip_read_info->rest_read_compressed = s->cur_file_info.compressed_size ; pfile_in_zip_read_info->rest_read_uncompressed = s->cur_file_info.uncompressed_size ; pfile_in_zip_read_info->pos_in_zipfile = s->cur_file_info_internal.offset_curfile + SIZEZIPLOCALHEADER + iSizeVar; pfile_in_zip_read_info->stream.avail_in = (uInt)0; s->pfile_in_zip_read = pfile_in_zip_read_info; s->encrypted = 0; # ifndef NOUNCRYPT if (password != NULL) { int i; s->pcrc_32_tab = get_crc_table(); init_keys(password,s->keys,s->pcrc_32_tab); if (ZSEEK64(s->z_filefunc, s->filestream, s->pfile_in_zip_read->pos_in_zipfile + s->pfile_in_zip_read->byte_before_the_zipfile, SEEK_SET)!=0) return UNZ_INTERNALERROR; if(ZREAD64(s->z_filefunc, s->filestream,source, 12)<12) return UNZ_INTERNALERROR; for (i = 0; i<12; i++) zdecode(s->keys,s->pcrc_32_tab,source[i]); s->pfile_in_zip_read->pos_in_zipfile+=12; s->encrypted=1; } # endif return UNZ_OK; } extern int ZEXPORT unzOpenCurrentFile (unzFile file) { return unzOpenCurrentFile3(file, NULL, NULL, 0, NULL); } extern int ZEXPORT unzOpenCurrentFilePassword (unzFile file, const char* password) { return unzOpenCurrentFile3(file, NULL, NULL, 0, password); } extern int ZEXPORT unzOpenCurrentFile2 (unzFile file, int* method, int* level, int raw) { return unzOpenCurrentFile3(file, method, level, raw, NULL); } /** Addition for GDAL : START */ extern ZPOS64_T ZEXPORT unzGetCurrentFileZStreamPos64( unzFile file) { unz64_s* s; file_in_zip64_read_info_s* pfile_in_zip_read_info; s=(unz64_s*)file; if (file==NULL) return 0; /*UNZ_PARAMERROR; */ pfile_in_zip_read_info=s->pfile_in_zip_read; if (pfile_in_zip_read_info==NULL) return 0; /*UNZ_PARAMERROR; */ return pfile_in_zip_read_info->pos_in_zipfile + pfile_in_zip_read_info->byte_before_the_zipfile; } /** Addition for GDAL : END */ /* Read bytes from the current file. buf contain buffer where data must be copied len the size of buf. return the number of byte copied if somes bytes are copied return 0 if the end of file was reached return <0 with error code if there is an error (UNZ_ERRNO for IO error, or zLib error for uncompress error) */ extern int ZEXPORT unzReadCurrentFile (unzFile file, voidp buf, unsigned len) { int err=UNZ_OK; uInt iRead = 0; unz64_s* s; file_in_zip64_read_info_s* pfile_in_zip_read_info; if (file==NULL) return UNZ_PARAMERROR; s=(unz64_s*)file; pfile_in_zip_read_info=s->pfile_in_zip_read; if (pfile_in_zip_read_info==NULL) return UNZ_PARAMERROR; if (pfile_in_zip_read_info->read_buffer == NULL) return UNZ_END_OF_LIST_OF_FILE; if (len==0) return 0; pfile_in_zip_read_info->stream.next_out = (Bytef*)buf; pfile_in_zip_read_info->stream.avail_out = (uInt)len; if ((len>pfile_in_zip_read_info->rest_read_uncompressed) && (!(pfile_in_zip_read_info->raw))) pfile_in_zip_read_info->stream.avail_out = (uInt)pfile_in_zip_read_info->rest_read_uncompressed; if ((len>pfile_in_zip_read_info->rest_read_compressed+ pfile_in_zip_read_info->stream.avail_in) && (pfile_in_zip_read_info->raw)) pfile_in_zip_read_info->stream.avail_out = (uInt)pfile_in_zip_read_info->rest_read_compressed+ pfile_in_zip_read_info->stream.avail_in; while (pfile_in_zip_read_info->stream.avail_out>0) { if ((pfile_in_zip_read_info->stream.avail_in==0) && (pfile_in_zip_read_info->rest_read_compressed>0)) { uInt uReadThis = UNZ_BUFSIZE; if (pfile_in_zip_read_info->rest_read_compressedrest_read_compressed; if (uReadThis == 0) return UNZ_EOF; if (ZSEEK64(pfile_in_zip_read_info->z_filefunc, pfile_in_zip_read_info->filestream, pfile_in_zip_read_info->pos_in_zipfile + pfile_in_zip_read_info->byte_before_the_zipfile, ZLIB_FILEFUNC_SEEK_SET)!=0) return UNZ_ERRNO; if (ZREAD64(pfile_in_zip_read_info->z_filefunc, pfile_in_zip_read_info->filestream, pfile_in_zip_read_info->read_buffer, uReadThis)!=uReadThis) return UNZ_ERRNO; # ifndef NOUNCRYPT if(s->encrypted) { uInt i; for(i=0;iread_buffer[i] = zdecode(s->keys,s->pcrc_32_tab, pfile_in_zip_read_info->read_buffer[i]); } # endif pfile_in_zip_read_info->pos_in_zipfile += uReadThis; pfile_in_zip_read_info->rest_read_compressed-=uReadThis; pfile_in_zip_read_info->stream.next_in = (Bytef*)pfile_in_zip_read_info->read_buffer; pfile_in_zip_read_info->stream.avail_in = (uInt)uReadThis; } if ((pfile_in_zip_read_info->compression_method==0) || (pfile_in_zip_read_info->raw)) { uInt uDoCopy,i ; if ((pfile_in_zip_read_info->stream.avail_in == 0) && (pfile_in_zip_read_info->rest_read_compressed == 0)) return (iRead==0) ? UNZ_EOF : iRead; if (pfile_in_zip_read_info->stream.avail_out < pfile_in_zip_read_info->stream.avail_in) uDoCopy = pfile_in_zip_read_info->stream.avail_out ; else uDoCopy = pfile_in_zip_read_info->stream.avail_in ; for (i=0;istream.next_out+i) = *(pfile_in_zip_read_info->stream.next_in+i); pfile_in_zip_read_info->total_out_64 = pfile_in_zip_read_info->total_out_64 + uDoCopy; pfile_in_zip_read_info->crc32 = crc32(pfile_in_zip_read_info->crc32, pfile_in_zip_read_info->stream.next_out, uDoCopy); pfile_in_zip_read_info->rest_read_uncompressed-=uDoCopy; pfile_in_zip_read_info->stream.avail_in -= uDoCopy; pfile_in_zip_read_info->stream.avail_out -= uDoCopy; pfile_in_zip_read_info->stream.next_out += uDoCopy; pfile_in_zip_read_info->stream.next_in += uDoCopy; pfile_in_zip_read_info->stream.total_out += uDoCopy; iRead += uDoCopy; } else if (pfile_in_zip_read_info->compression_method==Z_BZIP2ED) { #ifdef HAVE_BZIP2 uLong uTotalOutBefore,uTotalOutAfter; const Bytef *bufBefore; uLong uOutThis; pfile_in_zip_read_info->bstream.next_in = (char*)pfile_in_zip_read_info->stream.next_in; pfile_in_zip_read_info->bstream.avail_in = pfile_in_zip_read_info->stream.avail_in; pfile_in_zip_read_info->bstream.total_in_lo32 = pfile_in_zip_read_info->stream.total_in; pfile_in_zip_read_info->bstream.total_in_hi32 = 0; pfile_in_zip_read_info->bstream.next_out = (char*)pfile_in_zip_read_info->stream.next_out; pfile_in_zip_read_info->bstream.avail_out = pfile_in_zip_read_info->stream.avail_out; pfile_in_zip_read_info->bstream.total_out_lo32 = pfile_in_zip_read_info->stream.total_out; pfile_in_zip_read_info->bstream.total_out_hi32 = 0; uTotalOutBefore = pfile_in_zip_read_info->bstream.total_out_lo32; bufBefore = (const Bytef *)pfile_in_zip_read_info->bstream.next_out; err=BZ2_bzDecompress(&pfile_in_zip_read_info->bstream); uTotalOutAfter = pfile_in_zip_read_info->bstream.total_out_lo32; uOutThis = uTotalOutAfter-uTotalOutBefore; pfile_in_zip_read_info->total_out_64 = pfile_in_zip_read_info->total_out_64 + uOutThis; pfile_in_zip_read_info->crc32 = crc32(pfile_in_zip_read_info->crc32,bufBefore, (uInt)(uOutThis)); pfile_in_zip_read_info->rest_read_uncompressed -= uOutThis; iRead += (uInt)(uTotalOutAfter - uTotalOutBefore); pfile_in_zip_read_info->stream.next_in = (Bytef*)pfile_in_zip_read_info->bstream.next_in; pfile_in_zip_read_info->stream.avail_in = pfile_in_zip_read_info->bstream.avail_in; pfile_in_zip_read_info->stream.total_in = pfile_in_zip_read_info->bstream.total_in_lo32; pfile_in_zip_read_info->stream.next_out = (Bytef*)pfile_in_zip_read_info->bstream.next_out; pfile_in_zip_read_info->stream.avail_out = pfile_in_zip_read_info->bstream.avail_out; pfile_in_zip_read_info->stream.total_out = pfile_in_zip_read_info->bstream.total_out_lo32; if (err==BZ_STREAM_END) return (iRead==0) ? UNZ_EOF : iRead; if (err!=BZ_OK) break; #endif } /* end Z_BZIP2ED */ else { ZPOS64_T uTotalOutBefore,uTotalOutAfter; const Bytef *bufBefore; ZPOS64_T uOutThis; int flush=Z_SYNC_FLUSH; uTotalOutBefore = pfile_in_zip_read_info->stream.total_out; bufBefore = pfile_in_zip_read_info->stream.next_out; /* if ((pfile_in_zip_read_info->rest_read_uncompressed == pfile_in_zip_read_info->stream.avail_out) && (pfile_in_zip_read_info->rest_read_compressed == 0)) flush = Z_FINISH; */ err=inflate(&pfile_in_zip_read_info->stream,flush); if ((err>=0) && (pfile_in_zip_read_info->stream.msg!=NULL)) err = Z_DATA_ERROR; uTotalOutAfter = pfile_in_zip_read_info->stream.total_out; uOutThis = uTotalOutAfter-uTotalOutBefore; pfile_in_zip_read_info->total_out_64 = pfile_in_zip_read_info->total_out_64 + uOutThis; pfile_in_zip_read_info->crc32 = crc32(pfile_in_zip_read_info->crc32,bufBefore, (uInt)(uOutThis)); pfile_in_zip_read_info->rest_read_uncompressed -= uOutThis; iRead += (uInt)(uTotalOutAfter - uTotalOutBefore); if (err==Z_STREAM_END) return (iRead==0) ? UNZ_EOF : iRead; if (err!=Z_OK) break; } } if (err==Z_OK) return iRead; return err; } /* Give the current position in uncompressed data */ extern z_off_t ZEXPORT unztell (unzFile file) { unz64_s* s; file_in_zip64_read_info_s* pfile_in_zip_read_info; if (file==NULL) return UNZ_PARAMERROR; s=(unz64_s*)file; pfile_in_zip_read_info=s->pfile_in_zip_read; if (pfile_in_zip_read_info==NULL) return UNZ_PARAMERROR; return (z_off_t)pfile_in_zip_read_info->stream.total_out; } extern ZPOS64_T ZEXPORT unztell64 (unzFile file) { unz64_s* s; file_in_zip64_read_info_s* pfile_in_zip_read_info; if (file==NULL) return (ZPOS64_T)-1; s=(unz64_s*)file; pfile_in_zip_read_info=s->pfile_in_zip_read; if (pfile_in_zip_read_info==NULL) return (ZPOS64_T)-1; return pfile_in_zip_read_info->total_out_64; } /* return 1 if the end of file was reached, 0 elsewhere */ extern int ZEXPORT unzeof (unzFile file) { unz64_s* s; file_in_zip64_read_info_s* pfile_in_zip_read_info; if (file==NULL) return UNZ_PARAMERROR; s=(unz64_s*)file; pfile_in_zip_read_info=s->pfile_in_zip_read; if (pfile_in_zip_read_info==NULL) return UNZ_PARAMERROR; if (pfile_in_zip_read_info->rest_read_uncompressed == 0) return 1; else return 0; } /* Read extra field from the current file (opened by unzOpenCurrentFile) This is the local-header version of the extra field (sometimes, there is more info in the local-header version than in the central-header) if buf==NULL, it return the size of the local extra field that can be read if buf!=NULL, len is the size of the buffer, the extra header is copied in buf. the return value is the number of bytes copied in buf, or (if <0) the error code */ extern int ZEXPORT unzGetLocalExtrafield (unzFile file, voidp buf, unsigned len) { unz64_s* s; file_in_zip64_read_info_s* pfile_in_zip_read_info; uInt read_now; ZPOS64_T size_to_read; if (file==NULL) return UNZ_PARAMERROR; s=(unz64_s*)file; pfile_in_zip_read_info=s->pfile_in_zip_read; if (pfile_in_zip_read_info==NULL) return UNZ_PARAMERROR; size_to_read = (pfile_in_zip_read_info->size_local_extrafield - pfile_in_zip_read_info->pos_local_extrafield); if (buf==NULL) return (int)size_to_read; if (len>size_to_read) read_now = (uInt)size_to_read; else read_now = (uInt)len ; if (read_now==0) return 0; if (ZSEEK64(pfile_in_zip_read_info->z_filefunc, pfile_in_zip_read_info->filestream, pfile_in_zip_read_info->offset_local_extrafield + pfile_in_zip_read_info->pos_local_extrafield, ZLIB_FILEFUNC_SEEK_SET)!=0) return UNZ_ERRNO; if (ZREAD64(pfile_in_zip_read_info->z_filefunc, pfile_in_zip_read_info->filestream, buf,read_now)!=read_now) return UNZ_ERRNO; return (int)read_now; } /* Close the file in zip opened with unzipOpenCurrentFile Return UNZ_CRCERROR if all the file was read but the CRC is not good */ extern int ZEXPORT unzCloseCurrentFile (unzFile file) { int err=UNZ_OK; unz64_s* s; file_in_zip64_read_info_s* pfile_in_zip_read_info; if (file==NULL) return UNZ_PARAMERROR; s=(unz64_s*)file; pfile_in_zip_read_info=s->pfile_in_zip_read; if (pfile_in_zip_read_info==NULL) return UNZ_PARAMERROR; if ((pfile_in_zip_read_info->rest_read_uncompressed == 0) && (!pfile_in_zip_read_info->raw)) { if (pfile_in_zip_read_info->crc32 != pfile_in_zip_read_info->crc32_wait) err=UNZ_CRCERROR; } TRYFREE(pfile_in_zip_read_info->read_buffer); pfile_in_zip_read_info->read_buffer = NULL; if (pfile_in_zip_read_info->stream_initialised == Z_DEFLATED) inflateEnd(&pfile_in_zip_read_info->stream); #ifdef HAVE_BZIP2 else if (pfile_in_zip_read_info->stream_initialised == Z_BZIP2ED) BZ2_bzDecompressEnd(&pfile_in_zip_read_info->bstream); #endif pfile_in_zip_read_info->stream_initialised = 0; TRYFREE(pfile_in_zip_read_info); s->pfile_in_zip_read=NULL; return err; } /* Get the global comment string of the ZipFile, in the szComment buffer. uSizeBuf is the size of the szComment buffer. return the number of byte copied or an error code <0 */ extern int ZEXPORT unzGetGlobalComment (unzFile file, char * szComment, uLong uSizeBuf) { unz64_s* s; uLong uReadThis ; if (file==NULL) return (int)UNZ_PARAMERROR; s=(unz64_s*)file; uReadThis = uSizeBuf; if (uReadThis>s->gi.size_comment) uReadThis = s->gi.size_comment; if (ZSEEK64(s->z_filefunc,s->filestream,s->central_pos+22,ZLIB_FILEFUNC_SEEK_SET)!=0) return UNZ_ERRNO; if (uReadThis>0) { *szComment='\0'; if (ZREAD64(s->z_filefunc,s->filestream,szComment,uReadThis)!=uReadThis) return UNZ_ERRNO; } if ((szComment != NULL) && (uSizeBuf > s->gi.size_comment)) *(szComment+s->gi.size_comment)='\0'; return (int)uReadThis; } /* Additions by RX '2004 */ extern ZPOS64_T ZEXPORT unzGetOffset64(unzFile file) { unz64_s* s; if (file==NULL) return 0; /*UNZ_PARAMERROR; */ s=(unz64_s*)file; if (!s->current_file_ok) return 0; if (s->gi.number_entry != 0 && s->gi.number_entry != 0xffff) if (s->num_file==s->gi.number_entry) return 0; return s->pos_in_central_dir; } extern uLong ZEXPORT unzGetOffset (unzFile file) { ZPOS64_T offset64; if (file==NULL) return 0; /*UNZ_PARAMERROR; */ offset64 = unzGetOffset64(file); return (uLong)offset64; } extern int ZEXPORT unzSetOffset64(unzFile file, ZPOS64_T pos) { unz64_s* s; int err; if (file==NULL) return UNZ_PARAMERROR; s=(unz64_s*)file; s->pos_in_central_dir = pos; s->num_file = s->gi.number_entry; /* hack */ err = unz64local_GetCurrentFileInfoInternal(file,&s->cur_file_info, &s->cur_file_info_internal, NULL,0,NULL,0,NULL,0); s->current_file_ok = (err == UNZ_OK); return err; } extern int ZEXPORT unzSetOffset (unzFile file, uLong pos) { return unzSetOffset64(file,pos); } workbench-1.1.1/src/Quazip/unzip.h000066400000000000000000000406071255417355300170730ustar00rootroot00000000000000/* unzip.h -- IO for uncompress .zip files using zlib Version 1.1, February 14h, 2010 part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html ) Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html ) Modifications of Unzip for Zip64 Copyright (C) 2007-2008 Even Rouault Modifications for Zip64 support on both zip and unzip Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com ) For more info read MiniZip_info.txt --------------------------------------------------------------------------------- Condition of use and distribution are the same than zlib : This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. --------------------------------------------------------------------------------- Changes See header of unzip64.c */ #ifndef _unz64_H #define _unz64_H #ifdef __cplusplus extern "C" { #endif #ifndef _ZLIB_H #include "zlib.h" #endif #ifndef _ZLIBIOAPI_H #include "ioapi.h" #endif #ifdef HAVE_BZIP2 #include "bzlib.h" #endif #define Z_BZIP2ED 12 #if defined(STRICTUNZIP) || defined(STRICTZIPUNZIP) /* like the STRICT of WIN32, we define a pointer that cannot be converted from (void*) without cast */ typedef struct TagunzFile__ { int unused; } unzFile__; typedef unzFile__ *unzFile; #else typedef voidp unzFile; #endif #define UNZ_OK (0) #define UNZ_END_OF_LIST_OF_FILE (-100) #define UNZ_ERRNO (Z_ERRNO) #define UNZ_EOF (0) #define UNZ_PARAMERROR (-102) #define UNZ_BADZIPFILE (-103) #define UNZ_INTERNALERROR (-104) #define UNZ_CRCERROR (-105) /* tm_unz contain date/time info */ typedef struct tm_unz_s { uInt tm_sec; /* seconds after the minute - [0,59] */ uInt tm_min; /* minutes after the hour - [0,59] */ uInt tm_hour; /* hours since midnight - [0,23] */ uInt tm_mday; /* day of the month - [1,31] */ uInt tm_mon; /* months since January - [0,11] */ uInt tm_year; /* years - [1980..2044] */ } tm_unz; /* unz_global_info structure contain global data about the ZIPfile These data comes from the end of central dir */ typedef struct unz_global_info64_s { ZPOS64_T number_entry; /* total number of entries in the central dir on this disk */ uLong size_comment; /* size of the global comment of the zipfile */ } unz_global_info64; typedef struct unz_global_info_s { uLong number_entry; /* total number of entries in the central dir on this disk */ uLong size_comment; /* size of the global comment of the zipfile */ } unz_global_info; /* unz_file_info contain information about a file in the zipfile */ typedef struct unz_file_info64_s { uLong version; /* version made by 2 bytes */ uLong version_needed; /* version needed to extract 2 bytes */ uLong flag; /* general purpose bit flag 2 bytes */ uLong compression_method; /* compression method 2 bytes */ uLong dosDate; /* last mod file date in Dos fmt 4 bytes */ uLong crc; /* crc-32 4 bytes */ ZPOS64_T compressed_size; /* compressed size 8 bytes */ ZPOS64_T uncompressed_size; /* uncompressed size 8 bytes */ uLong size_filename; /* filename length 2 bytes */ uLong size_file_extra; /* extra field length 2 bytes */ uLong size_file_comment; /* file comment length 2 bytes */ uLong disk_num_start; /* disk number start 2 bytes */ uLong internal_fa; /* internal file attributes 2 bytes */ uLong external_fa; /* external file attributes 4 bytes */ tm_unz tmu_date; } unz_file_info64; typedef struct unz_file_info_s { uLong version; /* version made by 2 bytes */ uLong version_needed; /* version needed to extract 2 bytes */ uLong flag; /* general purpose bit flag 2 bytes */ uLong compression_method; /* compression method 2 bytes */ uLong dosDate; /* last mod file date in Dos fmt 4 bytes */ uLong crc; /* crc-32 4 bytes */ uLong compressed_size; /* compressed size 4 bytes */ uLong uncompressed_size; /* uncompressed size 4 bytes */ uLong size_filename; /* filename length 2 bytes */ uLong size_file_extra; /* extra field length 2 bytes */ uLong size_file_comment; /* file comment length 2 bytes */ uLong disk_num_start; /* disk number start 2 bytes */ uLong internal_fa; /* internal file attributes 2 bytes */ uLong external_fa; /* external file attributes 4 bytes */ tm_unz tmu_date; } unz_file_info; extern int ZEXPORT unzStringFileNameCompare OF ((const char* fileName1, const char* fileName2, int iCaseSensitivity)); /* Compare two filename (fileName1,fileName2). If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp) If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi or strcasecmp) If iCaseSenisivity = 0, case sensitivity is defaut of your operating system (like 1 on Unix, 2 on Windows) */ extern unzFile ZEXPORT unzOpen OF((voidpf file)); extern unzFile ZEXPORT unzOpen64 OF((voidpf file)); /* Open a Zip file. path contain the full pathname (by example, on a Windows XP computer "c:\\zlib\\zlib113.zip" or on an Unix computer "zlib/zlib113.zip". If the zipfile cannot be opened (file don't exist or in not valid), the return value is NULL. Else, the return value is a unzFile Handle, usable with other function of this unzip package. the "64" function take a const void* pointer, because the path is just the value passed to the open64_file_func callback. Under Windows, if UNICODE is defined, using fill_fopen64_filefunc, the path is a pointer to a wide unicode string (LPCTSTR is LPCWSTR), so const char* does not describe the reality */ extern unzFile ZEXPORT unzOpen2 OF((voidpf file, zlib_filefunc_def* pzlib_filefunc_def)); /* Open a Zip file, like unzOpen, but provide a set of file low level API for read/write the zip file (see ioapi.h) */ extern unzFile ZEXPORT unzOpen2_64 OF((voidpf file, zlib_filefunc64_def* pzlib_filefunc_def)); /* Open a Zip file, like unz64Open, but provide a set of file low level API for read/write the zip file (see ioapi.h) */ extern int ZEXPORT unzClose OF((unzFile file)); /* Close a ZipFile opened with unzipOpen. If there is files inside the .Zip opened with unzOpenCurrentFile (see later), these files MUST be closed with unzipCloseCurrentFile before call unzipClose. return UNZ_OK if there is no problem. */ extern int ZEXPORT unzGetGlobalInfo OF((unzFile file, unz_global_info *pglobal_info)); extern int ZEXPORT unzGetGlobalInfo64 OF((unzFile file, unz_global_info64 *pglobal_info)); /* Write info about the ZipFile in the *pglobal_info structure. No preparation of the structure is needed return UNZ_OK if there is no problem. */ extern int ZEXPORT unzGetGlobalComment OF((unzFile file, char *szComment, uLong uSizeBuf)); /* Get the global comment string of the ZipFile, in the szComment buffer. uSizeBuf is the size of the szComment buffer. return the number of byte copied or an error code <0 */ /***************************************************************************/ /* Unzip package allow you browse the directory of the zipfile */ extern int ZEXPORT unzGoToFirstFile OF((unzFile file)); /* Set the current file of the zipfile to the first file. return UNZ_OK if there is no problem */ extern int ZEXPORT unzGoToNextFile OF((unzFile file)); /* Set the current file of the zipfile to the next file. return UNZ_OK if there is no problem return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest. */ extern int ZEXPORT unzLocateFile OF((unzFile file, const char *szFileName, int iCaseSensitivity)); /* Try locate the file szFileName in the zipfile. For the iCaseSensitivity signification, see unzStringFileNameCompare return value : UNZ_OK if the file is found. It becomes the current file. UNZ_END_OF_LIST_OF_FILE if the file is not found */ /* ****************************************** */ /* Ryan supplied functions */ /* unz_file_info contain information about a file in the zipfile */ typedef struct unz_file_pos_s { uLong pos_in_zip_directory; /* offset in zip file directory */ uLong num_of_file; /* # of file */ } unz_file_pos; extern int ZEXPORT unzGetFilePos( unzFile file, unz_file_pos* file_pos); extern int ZEXPORT unzGoToFilePos( unzFile file, unz_file_pos* file_pos); typedef struct unz64_file_pos_s { ZPOS64_T pos_in_zip_directory; /* offset in zip file directory */ ZPOS64_T num_of_file; /* # of file */ } unz64_file_pos; extern int ZEXPORT unzGetFilePos64( unzFile file, unz64_file_pos* file_pos); extern int ZEXPORT unzGoToFilePos64( unzFile file, const unz64_file_pos* file_pos); /* ****************************************** */ extern int ZEXPORT unzGetCurrentFileInfo64 OF((unzFile file, unz_file_info64 *pfile_info, char *szFileName, uLong fileNameBufferSize, void *extraField, uLong extraFieldBufferSize, char *szComment, uLong commentBufferSize)); extern int ZEXPORT unzGetCurrentFileInfo OF((unzFile file, unz_file_info *pfile_info, char *szFileName, uLong fileNameBufferSize, void *extraField, uLong extraFieldBufferSize, char *szComment, uLong commentBufferSize)); /* Get Info about the current file if pfile_info!=NULL, the *pfile_info structure will contain somes info about the current file if szFileName!=NULL, the filemane string will be copied in szFileName (fileNameBufferSize is the size of the buffer) if extraField!=NULL, the extra field information will be copied in extraField (extraFieldBufferSize is the size of the buffer). This is the Central-header version of the extra field if szComment!=NULL, the comment string of the file will be copied in szComment (commentBufferSize is the size of the buffer) */ /** Addition for GDAL : START */ extern ZPOS64_T ZEXPORT unzGetCurrentFileZStreamPos64 OF((unzFile file)); /** Addition for GDAL : END */ /***************************************************************************/ /* for reading the content of the current zipfile, you can open it, read data from it, and close it (you can close it before reading all the file) */ extern int ZEXPORT unzOpenCurrentFile OF((unzFile file)); /* Open for reading data the current file in the zipfile. If there is no error, the return value is UNZ_OK. */ extern int ZEXPORT unzOpenCurrentFilePassword OF((unzFile file, const char* password)); /* Open for reading data the current file in the zipfile. password is a crypting password If there is no error, the return value is UNZ_OK. */ extern int ZEXPORT unzOpenCurrentFile2 OF((unzFile file, int* method, int* level, int raw)); /* Same than unzOpenCurrentFile, but open for read raw the file (not uncompress) if raw==1 *method will receive method of compression, *level will receive level of compression note : you can set level parameter as NULL (if you did not want known level, but you CANNOT set method parameter as NULL */ extern int ZEXPORT unzOpenCurrentFile3 OF((unzFile file, int* method, int* level, int raw, const char* password)); /* Same than unzOpenCurrentFile, but open for read raw the file (not uncompress) if raw==1 *method will receive method of compression, *level will receive level of compression note : you can set level parameter as NULL (if you did not want known level, but you CANNOT set method parameter as NULL */ extern int ZEXPORT unzCloseCurrentFile OF((unzFile file)); /* Close the file in zip opened with unzOpenCurrentFile Return UNZ_CRCERROR if all the file was read but the CRC is not good */ extern int ZEXPORT unzReadCurrentFile OF((unzFile file, voidp buf, unsigned len)); /* Read bytes from the current file (opened by unzOpenCurrentFile) buf contain buffer where data must be copied len the size of buf. return the number of byte copied if somes bytes are copied return 0 if the end of file was reached return <0 with error code if there is an error (UNZ_ERRNO for IO error, or zLib error for uncompress error) */ extern z_off_t ZEXPORT unztell OF((unzFile file)); extern ZPOS64_T ZEXPORT unztell64 OF((unzFile file)); /* Give the current position in uncompressed data */ extern int ZEXPORT unzeof OF((unzFile file)); /* return 1 if the end of file was reached, 0 elsewhere */ extern int ZEXPORT unzGetLocalExtrafield OF((unzFile file, voidp buf, unsigned len)); /* Read extra field from the current file (opened by unzOpenCurrentFile) This is the local-header version of the extra field (sometimes, there is more info in the local-header version than in the central-header) if buf==NULL, it return the size of the local extra field if buf!=NULL, len is the size of the buffer, the extra header is copied in buf. the return value is the number of bytes copied in buf, or (if <0) the error code */ /***************************************************************************/ /* Get the current file offset */ extern ZPOS64_T ZEXPORT unzGetOffset64 (unzFile file); extern uLong ZEXPORT unzGetOffset (unzFile file); /* Set the current file offset */ extern int ZEXPORT unzSetOffset64 (unzFile file, ZPOS64_T pos); extern int ZEXPORT unzSetOffset (unzFile file, uLong pos); #ifdef __cplusplus } #endif #endif /* _unz64_H */ workbench-1.1.1/src/Quazip/zip.c000066400000000000000000002111131255417355300165130ustar00rootroot00000000000000/* zip.c -- IO on .zip files using zlib Version 1.1, February 14h, 2010 part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html ) Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html ) Modifications for Zip64 support Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com ) For more info read MiniZip_info.txt Changes Oct-2009 - Mathias Svensson - Remove old C style function prototypes Oct-2009 - Mathias Svensson - Added Zip64 Support when creating new file archives Oct-2009 - Mathias Svensson - Did some code cleanup and refactoring to get better overview of some functions. Oct-2009 - Mathias Svensson - Added zipRemoveExtraInfoBlock to strip extra field data from its ZIP64 data It is used when recreting zip archive with RAW when deleting items from a zip. ZIP64 data is automaticly added to items that needs it, and existing ZIP64 data need to be removed. Oct-2009 - Mathias Svensson - Added support for BZIP2 as compression mode (bzip2 lib is required) Jan-2010 - back to unzip and minizip 1.0 name scheme, with compatibility layer */ #include #include #include #include #include "zlib.h" #if (ZLIB_VERNUM < 0x1270) typedef uLongf z_crc_t; #endif #include "zip.h" #ifdef STDC # include # include # include #endif #ifdef NO_ERRNO_H extern int errno; #else # include #endif #ifndef local # define local static #endif /* compile with -Dlocal if your debugger can't find static symbols */ #ifndef VERSIONMADEBY # define VERSIONMADEBY (0x031e) /* best for standard pkware crypt */ #endif #ifndef Z_BUFSIZE #define Z_BUFSIZE (64*1024) /* (16384) */ #endif #ifndef Z_MAXFILENAMEINZIP #define Z_MAXFILENAMEINZIP (256) #endif #ifndef ALLOC # define ALLOC(size) (malloc(size)) #endif #ifndef TRYFREE # define TRYFREE(p) {if (p) free(p);} #endif /* #define SIZECENTRALDIRITEM (0x2e) #define SIZEZIPLOCALHEADER (0x1e) */ /* I've found an old Unix (a SunOS 4.1.3_U1) without all SEEK_* defined.... */ /* NOT sure that this work on ALL platform */ #define MAKEULONG64(a, b) ((ZPOS64_T)(((unsigned long)(a)) | ((ZPOS64_T)((unsigned long)(b))) << 32)) #ifndef SEEK_CUR #define SEEK_CUR 1 #endif #ifndef SEEK_END #define SEEK_END 2 #endif #ifndef SEEK_SET #define SEEK_SET 0 #endif #ifndef DEF_MEM_LEVEL #if MAX_MEM_LEVEL >= 8 # define DEF_MEM_LEVEL 8 #else # define DEF_MEM_LEVEL MAX_MEM_LEVEL #endif #endif const char zip_copyright[] =" zip 1.01 Copyright 1998-2004 Gilles Vollant - http://www.winimage.com/zLibDll"; #define SIZEDATA_INDATABLOCK (4096-(4*4)) #define LOCALHEADERMAGIC (0x04034b50) #define DESCRIPTORHEADERMAGIC (0x08074b50) #define CENTRALHEADERMAGIC (0x02014b50) #define ENDHEADERMAGIC (0x06054b50) #define ZIP64ENDHEADERMAGIC (0x6064b50) #define ZIP64ENDLOCHEADERMAGIC (0x7064b50) #define FLAG_LOCALHEADER_OFFSET (0x06) #define CRC_LOCALHEADER_OFFSET (0x0e) #define SIZECENTRALHEADER (0x2e) /* 46 */ typedef struct linkedlist_datablock_internal_s { struct linkedlist_datablock_internal_s* next_datablock; uLong avail_in_this_block; uLong filled_in_this_block; uLong unused; /* for future use and alignement */ unsigned char data[SIZEDATA_INDATABLOCK]; } linkedlist_datablock_internal; typedef struct linkedlist_data_s { linkedlist_datablock_internal* first_block; linkedlist_datablock_internal* last_block; } linkedlist_data; typedef struct { z_stream stream; /* zLib stream structure for inflate */ #ifdef HAVE_BZIP2 bz_stream bstream; /* bzLib stream structure for bziped */ #endif int stream_initialised; /* 1 is stream is initialised */ uInt pos_in_buffered_data; /* last written byte in buffered_data */ ZPOS64_T pos_local_header; /* offset of the local header of the file currenty writing */ char* central_header; /* central header data for the current file */ uLong size_centralExtra; uLong size_centralheader; /* size of the central header for cur file */ uLong size_centralExtraFree; /* Extra bytes allocated to the centralheader but that are not used */ uLong flag; /* flag of the file currently writing */ int method; /* compression method of file currenty wr.*/ int raw; /* 1 for directly writing raw data */ Byte buffered_data[Z_BUFSIZE];/* buffer contain compressed data to be writ*/ uLong dosDate; uLong crc32; int encrypt; int zip64; /* Add ZIP64 extened information in the extra field */ ZPOS64_T pos_zip64extrainfo; ZPOS64_T totalCompressedData; ZPOS64_T totalUncompressedData; #ifndef NOCRYPT unsigned long keys[3]; /* keys defining the pseudo-random sequence */ const z_crc_t FAR * pcrc_32_tab; int crypt_header_size; #endif } curfile64_info; typedef struct { zlib_filefunc64_32_def z_filefunc; voidpf filestream; /* io structore of the zipfile */ linkedlist_data central_dir;/* datablock with central dir in construction*/ int in_opened_file_inzip; /* 1 if a file in the zip is currently writ.*/ curfile64_info ci; /* info on the file curretly writing */ ZPOS64_T begin_pos; /* position of the beginning of the zipfile */ ZPOS64_T add_position_when_writting_offset; ZPOS64_T number_entry; #ifndef NO_ADDFILEINEXISTINGZIP char *globalcomment; #endif unsigned flags; } zip64_internal; #ifndef NOCRYPT #define INCLUDECRYPTINGCODE_IFCRYPTALLOWED #include "crypt.h" #endif local linkedlist_datablock_internal* allocate_new_datablock() { linkedlist_datablock_internal* ldi; ldi = (linkedlist_datablock_internal*) ALLOC(sizeof(linkedlist_datablock_internal)); if (ldi!=NULL) { ldi->next_datablock = NULL ; ldi->filled_in_this_block = 0 ; ldi->avail_in_this_block = SIZEDATA_INDATABLOCK ; } return ldi; } local void free_datablock(linkedlist_datablock_internal* ldi) { while (ldi!=NULL) { linkedlist_datablock_internal* ldinext = ldi->next_datablock; TRYFREE(ldi); ldi = ldinext; } } local void init_linkedlist(linkedlist_data* ll) { ll->first_block = ll->last_block = NULL; } local void free_linkedlist(linkedlist_data* ll) { free_datablock(ll->first_block); ll->first_block = ll->last_block = NULL; } local int add_data_in_datablock(linkedlist_data* ll, const void* buf, uLong len) { linkedlist_datablock_internal* ldi; const unsigned char* from_copy; if (ll==NULL) return ZIP_INTERNALERROR; if (ll->last_block == NULL) { ll->first_block = ll->last_block = allocate_new_datablock(); if (ll->first_block == NULL) return ZIP_INTERNALERROR; } ldi = ll->last_block; from_copy = (unsigned char*)buf; while (len>0) { uInt copy_this; uInt i; unsigned char* to_copy; if (ldi->avail_in_this_block==0) { ldi->next_datablock = allocate_new_datablock(); if (ldi->next_datablock == NULL) return ZIP_INTERNALERROR; ldi = ldi->next_datablock ; ll->last_block = ldi; } if (ldi->avail_in_this_block < len) copy_this = (uInt)ldi->avail_in_this_block; else copy_this = (uInt)len; to_copy = &(ldi->data[ldi->filled_in_this_block]); for (i=0;ifilled_in_this_block += copy_this; ldi->avail_in_this_block -= copy_this; from_copy += copy_this ; len -= copy_this; } return ZIP_OK; } /****************************************************************************/ #ifndef NO_ADDFILEINEXISTINGZIP /* =========================================================================== Inputs a long in LSB order to the given file nbByte == 1, 2 ,4 or 8 (byte, short or long, ZPOS64_T) */ local int zip64local_putValue OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T x, int nbByte)); local int zip64local_putValue (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T x, int nbByte) { unsigned char buf[8]; int n; for (n = 0; n < nbByte; n++) { buf[n] = (unsigned char)(x & 0xff); x >>= 8; } if (x != 0) { /* data overflow - hack for ZIP64 (X Roche) */ for (n = 0; n < nbByte; n++) { buf[n] = 0xff; } } if (ZWRITE64(*pzlib_filefunc_def,filestream,buf,nbByte)!=(uLong)nbByte) return ZIP_ERRNO; else return ZIP_OK; } local void zip64local_putValue_inmemory OF((void* dest, ZPOS64_T x, int nbByte)); local void zip64local_putValue_inmemory (void* dest, ZPOS64_T x, int nbByte) { unsigned char* buf=(unsigned char*)dest; int n; for (n = 0; n < nbByte; n++) { buf[n] = (unsigned char)(x & 0xff); x >>= 8; } if (x != 0) { /* data overflow - hack for ZIP64 */ for (n = 0; n < nbByte; n++) { buf[n] = 0xff; } } } /****************************************************************************/ local uLong zip64local_TmzDateToDosDate(const tm_zip* ptm) { uLong year = (uLong)ptm->tm_year; if (year>=1980) year-=1980; else if (year>=80) year-=80; return (uLong) (((ptm->tm_mday) + (32 * (ptm->tm_mon+1)) + (512 * year)) << 16) | ((ptm->tm_sec/2) + (32* ptm->tm_min) + (2048 * (uLong)ptm->tm_hour)); } /****************************************************************************/ local int zip64local_getByte OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, int *pi)); local int zip64local_getByte(const zlib_filefunc64_32_def* pzlib_filefunc_def,voidpf filestream,int* pi) { unsigned char c; int err = (int)ZREAD64(*pzlib_filefunc_def,filestream,&c,1); if (err==1) { *pi = (int)c; return ZIP_OK; } else { if (ZERROR64(*pzlib_filefunc_def,filestream)) return ZIP_ERRNO; else return ZIP_EOF; } } /* =========================================================================== Reads a long in LSB order from the given gz_stream. Sets */ local int zip64local_getShort OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong *pX)); local int zip64local_getShort (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong* pX) { uLong x ; int i = 0; int err; err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); x = (uLong)i; if (err==ZIP_OK) err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); x += ((uLong)i)<<8; if (err==ZIP_OK) *pX = x; else *pX = 0; return err; } local int zip64local_getLong OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong *pX)); local int zip64local_getLong (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, uLong* pX) { uLong x ; int i = 0; int err; err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); x = (uLong)i; if (err==ZIP_OK) err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); x += ((uLong)i)<<8; if (err==ZIP_OK) err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); x += ((uLong)i)<<16; if (err==ZIP_OK) err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); x += ((uLong)i)<<24; if (err==ZIP_OK) *pX = x; else *pX = 0; return err; } local int zip64local_getLong64 OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T *pX)); local int zip64local_getLong64 (const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream, ZPOS64_T *pX) { ZPOS64_T x; int i = 0; int err; err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); x = (ZPOS64_T)i; if (err==ZIP_OK) err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); x += ((ZPOS64_T)i)<<8; if (err==ZIP_OK) err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); x += ((ZPOS64_T)i)<<16; if (err==ZIP_OK) err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); x += ((ZPOS64_T)i)<<24; if (err==ZIP_OK) err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); x += ((ZPOS64_T)i)<<32; if (err==ZIP_OK) err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); x += ((ZPOS64_T)i)<<40; if (err==ZIP_OK) err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); x += ((ZPOS64_T)i)<<48; if (err==ZIP_OK) err = zip64local_getByte(pzlib_filefunc_def,filestream,&i); x += ((ZPOS64_T)i)<<56; if (err==ZIP_OK) *pX = x; else *pX = 0; return err; } #ifndef BUFREADCOMMENT #define BUFREADCOMMENT (0x400) #endif /* Locate the Central directory of a zipfile (at the end, just before the global comment) */ local ZPOS64_T zip64local_SearchCentralDir OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream)); local ZPOS64_T zip64local_SearchCentralDir(const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream) { unsigned char* buf; ZPOS64_T uSizeFile; ZPOS64_T uBackRead; ZPOS64_T uMaxBack=0xffff; /* maximum size of global comment */ ZPOS64_T uPosFound=0; if (ZSEEK64(*pzlib_filefunc_def,filestream,0,ZLIB_FILEFUNC_SEEK_END) != 0) return 0; uSizeFile = ZTELL64(*pzlib_filefunc_def,filestream); if (uMaxBack>uSizeFile) uMaxBack = uSizeFile; buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4); if (buf==NULL) return 0; uBackRead = 4; while (uBackReaduMaxBack) uBackRead = uMaxBack; else uBackRead+=BUFREADCOMMENT; uReadPos = uSizeFile-uBackRead ; uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ? (BUFREADCOMMENT+4) : (uLong)(uSizeFile-uReadPos); if (ZSEEK64(*pzlib_filefunc_def,filestream,uReadPos,ZLIB_FILEFUNC_SEEK_SET)!=0) break; if (ZREAD64(*pzlib_filefunc_def,filestream,buf,uReadSize)!=uReadSize) break; for (i=(int)uReadSize-3; (i--)>0;) if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) && ((*(buf+i+2))==0x05) && ((*(buf+i+3))==0x06)) { uPosFound = uReadPos+i; break; } if (uPosFound!=0) break; } TRYFREE(buf); return uPosFound; } /* Locate the End of Zip64 Central directory locator and from there find the CD of a zipfile (at the end, just before the global comment) */ local ZPOS64_T zip64local_SearchCentralDir64 OF((const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream)); local ZPOS64_T zip64local_SearchCentralDir64(const zlib_filefunc64_32_def* pzlib_filefunc_def, voidpf filestream) { unsigned char* buf; ZPOS64_T uSizeFile; ZPOS64_T uBackRead; ZPOS64_T uMaxBack=0xffff; /* maximum size of global comment */ ZPOS64_T uPosFound=0; uLong uL; ZPOS64_T relativeOffset; if (ZSEEK64(*pzlib_filefunc_def,filestream,0,ZLIB_FILEFUNC_SEEK_END) != 0) return 0; uSizeFile = ZTELL64(*pzlib_filefunc_def,filestream); if (uMaxBack>uSizeFile) uMaxBack = uSizeFile; buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4); if (buf==NULL) return 0; uBackRead = 4; while (uBackReaduMaxBack) uBackRead = uMaxBack; else uBackRead+=BUFREADCOMMENT; uReadPos = uSizeFile-uBackRead ; uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ? (BUFREADCOMMENT+4) : (uLong)(uSizeFile-uReadPos); if (ZSEEK64(*pzlib_filefunc_def,filestream,uReadPos,ZLIB_FILEFUNC_SEEK_SET)!=0) break; if (ZREAD64(*pzlib_filefunc_def,filestream,buf,uReadSize)!=uReadSize) break; for (i=(int)uReadSize-3; (i--)>0;) { /* Signature "0x07064b50" Zip64 end of central directory locater */ if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) && ((*(buf+i+2))==0x06) && ((*(buf+i+3))==0x07)) { uPosFound = uReadPos+i; break; } } if (uPosFound!=0) break; } TRYFREE(buf); if (uPosFound == 0) return 0; /* Zip64 end of central directory locator */ if (ZSEEK64(*pzlib_filefunc_def,filestream, uPosFound,ZLIB_FILEFUNC_SEEK_SET)!=0) return 0; /* the signature, already checked */ if (zip64local_getLong(pzlib_filefunc_def,filestream,&uL)!=ZIP_OK) return 0; /* number of the disk with the start of the zip64 end of central directory */ if (zip64local_getLong(pzlib_filefunc_def,filestream,&uL)!=ZIP_OK) return 0; if (uL != 0) return 0; /* relative offset of the zip64 end of central directory record */ if (zip64local_getLong64(pzlib_filefunc_def,filestream,&relativeOffset)!=ZIP_OK) return 0; /* total number of disks */ if (zip64local_getLong(pzlib_filefunc_def,filestream,&uL)!=ZIP_OK) return 0; if (uL != 1) return 0; /* Goto Zip64 end of central directory record */ if (ZSEEK64(*pzlib_filefunc_def,filestream, relativeOffset,ZLIB_FILEFUNC_SEEK_SET)!=0) return 0; /* the signature */ if (zip64local_getLong(pzlib_filefunc_def,filestream,&uL)!=ZIP_OK) return 0; if (uL != 0x06064b50) /* signature of 'Zip64 end of central directory' */ return 0; return relativeOffset; } int LoadCentralDirectoryRecord(zip64_internal* pziinit) { int err=ZIP_OK; ZPOS64_T byte_before_the_zipfile;/* byte before the zipfile, (>0 for sfx)*/ ZPOS64_T size_central_dir; /* size of the central directory */ ZPOS64_T offset_central_dir; /* offset of start of central directory */ ZPOS64_T central_pos; uLong uL; uLong number_disk; /* number of the current dist, used for spaning ZIP, unsupported, always 0*/ uLong number_disk_with_CD; /* number the the disk with central dir, used for spaning ZIP, unsupported, always 0*/ ZPOS64_T number_entry; ZPOS64_T number_entry_CD; /* total number of entries in the central dir (same than number_entry on nospan) */ uLong VersionMadeBy; uLong VersionNeeded; uLong size_comment; int hasZIP64Record = 0; /* check first if we find a ZIP64 record */ central_pos = zip64local_SearchCentralDir64(&pziinit->z_filefunc,pziinit->filestream); if(central_pos > 0) { hasZIP64Record = 1; } else if(central_pos == 0) { central_pos = zip64local_SearchCentralDir(&pziinit->z_filefunc,pziinit->filestream); } /* disable to allow appending to empty ZIP archive if (central_pos==0) err=ZIP_ERRNO; */ if(hasZIP64Record) { ZPOS64_T sizeEndOfCentralDirectory; if (ZSEEK64(pziinit->z_filefunc, pziinit->filestream, central_pos, ZLIB_FILEFUNC_SEEK_SET) != 0) err=ZIP_ERRNO; /* the signature, already checked */ if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream,&uL)!=ZIP_OK) err=ZIP_ERRNO; /* size of zip64 end of central directory record */ if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream, &sizeEndOfCentralDirectory)!=ZIP_OK) err=ZIP_ERRNO; /* version made by */ if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &VersionMadeBy)!=ZIP_OK) err=ZIP_ERRNO; /* version needed to extract */ if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &VersionNeeded)!=ZIP_OK) err=ZIP_ERRNO; /* number of this disk */ if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream,&number_disk)!=ZIP_OK) err=ZIP_ERRNO; /* number of the disk with the start of the central directory */ if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream,&number_disk_with_CD)!=ZIP_OK) err=ZIP_ERRNO; /* total number of entries in the central directory on this disk */ if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream, &number_entry)!=ZIP_OK) err=ZIP_ERRNO; /* total number of entries in the central directory */ if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream,&number_entry_CD)!=ZIP_OK) err=ZIP_ERRNO; if ((number_entry_CD!=number_entry) || (number_disk_with_CD!=0) || (number_disk!=0)) err=ZIP_BADZIPFILE; /* size of the central directory */ if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream,&size_central_dir)!=ZIP_OK) err=ZIP_ERRNO; /* offset of start of central directory with respect to the starting disk number */ if (zip64local_getLong64(&pziinit->z_filefunc, pziinit->filestream,&offset_central_dir)!=ZIP_OK) err=ZIP_ERRNO; /* TODO.. */ /* read the comment from the standard central header. */ size_comment = 0; } else { /* Read End of central Directory info */ if (ZSEEK64(pziinit->z_filefunc, pziinit->filestream, central_pos,ZLIB_FILEFUNC_SEEK_SET)!=0) err=ZIP_ERRNO; /* the signature, already checked */ if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream,&uL)!=ZIP_OK) err=ZIP_ERRNO; /* number of this disk */ if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream,&number_disk)!=ZIP_OK) err=ZIP_ERRNO; /* number of the disk with the start of the central directory */ if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream,&number_disk_with_CD)!=ZIP_OK) err=ZIP_ERRNO; /* total number of entries in the central dir on this disk */ number_entry = 0; if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &uL)!=ZIP_OK) err=ZIP_ERRNO; else number_entry = uL; /* total number of entries in the central dir */ number_entry_CD = 0; if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &uL)!=ZIP_OK) err=ZIP_ERRNO; else number_entry_CD = uL; if ((number_entry_CD!=number_entry) || (number_disk_with_CD!=0) || (number_disk!=0)) err=ZIP_BADZIPFILE; /* size of the central directory */ size_central_dir = 0; if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream, &uL)!=ZIP_OK) err=ZIP_ERRNO; else size_central_dir = uL; /* offset of start of central directory with respect to the starting disk number */ offset_central_dir = 0; if (zip64local_getLong(&pziinit->z_filefunc, pziinit->filestream, &uL)!=ZIP_OK) err=ZIP_ERRNO; else offset_central_dir = uL; /* zipfile global comment length */ if (zip64local_getShort(&pziinit->z_filefunc, pziinit->filestream, &size_comment)!=ZIP_OK) err=ZIP_ERRNO; } if ((central_posz_filefunc, pziinit->filestream); return ZIP_ERRNO; } if (size_comment>0) { pziinit->globalcomment = (char*)ALLOC(size_comment+1); if (pziinit->globalcomment) { size_comment = ZREAD64(pziinit->z_filefunc, pziinit->filestream, pziinit->globalcomment,size_comment); pziinit->globalcomment[size_comment]=0; } } byte_before_the_zipfile = central_pos - (offset_central_dir+size_central_dir); pziinit->add_position_when_writting_offset = byte_before_the_zipfile; { ZPOS64_T size_central_dir_to_read = size_central_dir; size_t buf_size = SIZEDATA_INDATABLOCK; void* buf_read = (void*)ALLOC(buf_size); if (ZSEEK64(pziinit->z_filefunc, pziinit->filestream, offset_central_dir + byte_before_the_zipfile, ZLIB_FILEFUNC_SEEK_SET) != 0) err=ZIP_ERRNO; while ((size_central_dir_to_read>0) && (err==ZIP_OK)) { ZPOS64_T read_this = SIZEDATA_INDATABLOCK; if (read_this > size_central_dir_to_read) read_this = size_central_dir_to_read; if (ZREAD64(pziinit->z_filefunc, pziinit->filestream,buf_read,(uLong)read_this) != read_this) err=ZIP_ERRNO; if (err==ZIP_OK) err = add_data_in_datablock(&pziinit->central_dir,buf_read, (uLong)read_this); size_central_dir_to_read-=read_this; } TRYFREE(buf_read); } pziinit->begin_pos = byte_before_the_zipfile; pziinit->number_entry = number_entry_CD; if (ZSEEK64(pziinit->z_filefunc, pziinit->filestream, offset_central_dir+byte_before_the_zipfile,ZLIB_FILEFUNC_SEEK_SET) != 0) err=ZIP_ERRNO; return err; } #endif /* !NO_ADDFILEINEXISTINGZIP*/ /************************************************************/ extern zipFile ZEXPORT zipOpen3 (voidpf file, int append, zipcharpc* globalcomment, zlib_filefunc64_32_def* pzlib_filefunc64_32_def) { zip64_internal ziinit; zip64_internal* zi; int err=ZIP_OK; ziinit.z_filefunc.zseek32_file = NULL; ziinit.z_filefunc.ztell32_file = NULL; if (pzlib_filefunc64_32_def==NULL) fill_qiodevice64_filefunc(&ziinit.z_filefunc.zfile_func64); else ziinit.z_filefunc = *pzlib_filefunc64_32_def; ziinit.filestream = ZOPEN64(ziinit.z_filefunc, file, (append == APPEND_STATUS_CREATE) ? (ZLIB_FILEFUNC_MODE_READ | ZLIB_FILEFUNC_MODE_WRITE | ZLIB_FILEFUNC_MODE_CREATE) : (ZLIB_FILEFUNC_MODE_READ | ZLIB_FILEFUNC_MODE_WRITE | ZLIB_FILEFUNC_MODE_EXISTING)); if (ziinit.filestream == NULL) return NULL; if (append == APPEND_STATUS_CREATEAFTER) ZSEEK64(ziinit.z_filefunc,ziinit.filestream,0,SEEK_END); ziinit.begin_pos = ZTELL64(ziinit.z_filefunc,ziinit.filestream); ziinit.in_opened_file_inzip = 0; ziinit.ci.stream_initialised = 0; ziinit.number_entry = 0; ziinit.add_position_when_writting_offset = 0; ziinit.flags = ZIP_WRITE_DATA_DESCRIPTOR; init_linkedlist(&(ziinit.central_dir)); zi = (zip64_internal*)ALLOC(sizeof(zip64_internal)); if (zi==NULL) { ZCLOSE64(ziinit.z_filefunc,ziinit.filestream); return NULL; } /* now we add file in a zipfile */ # ifndef NO_ADDFILEINEXISTINGZIP ziinit.globalcomment = NULL; if (append == APPEND_STATUS_ADDINZIP) { /* Read and Cache Central Directory Records */ err = LoadCentralDirectoryRecord(&ziinit); } if (globalcomment) { *globalcomment = ziinit.globalcomment; } # endif /* !NO_ADDFILEINEXISTINGZIP*/ if (err != ZIP_OK) { # ifndef NO_ADDFILEINEXISTINGZIP TRYFREE(ziinit.globalcomment); # endif /* !NO_ADDFILEINEXISTINGZIP*/ TRYFREE(zi); return NULL; } else { *zi = ziinit; return (zipFile)zi; } } extern zipFile ZEXPORT zipOpen2 (voidpf file, int append, zipcharpc* globalcomment, zlib_filefunc_def* pzlib_filefunc32_def) { if (pzlib_filefunc32_def != NULL) { zlib_filefunc64_32_def zlib_filefunc64_32_def_fill; fill_zlib_filefunc64_32_def_from_filefunc32(&zlib_filefunc64_32_def_fill,pzlib_filefunc32_def); return zipOpen3(file, append, globalcomment, &zlib_filefunc64_32_def_fill); } else return zipOpen3(file, append, globalcomment, NULL); } extern zipFile ZEXPORT zipOpen2_64 (voidpf file, int append, zipcharpc* globalcomment, zlib_filefunc64_def* pzlib_filefunc_def) { if (pzlib_filefunc_def != NULL) { zlib_filefunc64_32_def zlib_filefunc64_32_def_fill; zlib_filefunc64_32_def_fill.zfile_func64 = *pzlib_filefunc_def; zlib_filefunc64_32_def_fill.ztell32_file = NULL; zlib_filefunc64_32_def_fill.zseek32_file = NULL; return zipOpen3(file, append, globalcomment, &zlib_filefunc64_32_def_fill); } else return zipOpen3(file, append, globalcomment, NULL); } extern zipFile ZEXPORT zipOpen (voidpf file, int append) { return zipOpen3(file,append,NULL,NULL); } extern zipFile ZEXPORT zipOpen64 (voidpf file, int append) { return zipOpen3(file,append,NULL,NULL); } int Write_LocalFileHeader(zip64_internal* zi, const char* filename, uInt size_extrafield_local, const void* extrafield_local, uLong version_to_extract) { /* write the local header */ int err; uInt size_filename = (uInt)strlen(filename); uInt size_extrafield = size_extrafield_local; err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)LOCALHEADERMAGIC, 4); if (err==ZIP_OK) { if(zi->ci.zip64) err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)45,2);/* version needed to extract */ else err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)version_to_extract,2); } if (err==ZIP_OK) err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->ci.flag,2); if (err==ZIP_OK) err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->ci.method,2); if (err==ZIP_OK) err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->ci.dosDate,4); /* CRC / Compressed size / Uncompressed size will be filled in later and rewritten later */ if (err==ZIP_OK) err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); /* crc 32, unknown */ if (err==ZIP_OK) { if(zi->ci.zip64) err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0xFFFFFFFF,4); /* compressed size, unknown */ else err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); /* compressed size, unknown */ } if (err==ZIP_OK) { if(zi->ci.zip64) err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0xFFFFFFFF,4); /* uncompressed size, unknown */ else err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); /* uncompressed size, unknown */ } if (err==ZIP_OK) err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_filename,2); if(zi->ci.zip64) { size_extrafield += 20; } if (err==ZIP_OK) err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_extrafield,2); if ((err==ZIP_OK) && (size_filename > 0)) { if (ZWRITE64(zi->z_filefunc,zi->filestream,filename,size_filename)!=size_filename) err = ZIP_ERRNO; } if ((err==ZIP_OK) && (size_extrafield_local > 0)) { if (ZWRITE64(zi->z_filefunc, zi->filestream, extrafield_local, size_extrafield_local) != size_extrafield_local) err = ZIP_ERRNO; } if ((err==ZIP_OK) && (zi->ci.zip64)) { /* write the Zip64 extended info */ short HeaderID = 1; short DataSize = 16; ZPOS64_T CompressedSize = 0; ZPOS64_T UncompressedSize = 0; /* Remember position of Zip64 extended info for the local file header. (needed when we update size after done with file) */ zi->ci.pos_zip64extrainfo = ZTELL64(zi->z_filefunc,zi->filestream); err = zip64local_putValue(&zi->z_filefunc, zi->filestream, (short)HeaderID,2); err = zip64local_putValue(&zi->z_filefunc, zi->filestream, (short)DataSize,2); err = zip64local_putValue(&zi->z_filefunc, zi->filestream, (ZPOS64_T)UncompressedSize,8); err = zip64local_putValue(&zi->z_filefunc, zi->filestream, (ZPOS64_T)CompressedSize,8); } return err; } /* NOTE. When writing RAW the ZIP64 extended information in extrafield_local and extrafield_global needs to be stripped before calling this function it can be done with zipRemoveExtraInfoBlock It is not done here because then we need to realloc a new buffer since parameters are 'const' and I want to minimize unnecessary allocations. */ extern int ZEXPORT zipOpenNewFileInZip4_64 (zipFile file, const char* filename, const zip_fileinfo* zipfi, const void* extrafield_local, uInt size_extrafield_local, const void* extrafield_global, uInt size_extrafield_global, const char* comment, int method, int level, int raw, int windowBits,int memLevel, int strategy, const char* password, uLong crcForCrypting, uLong versionMadeBy, uLong flagBase, int zip64) { zip64_internal* zi; uInt size_filename; uInt size_comment; uInt i; int err = ZIP_OK; uLong version_to_extract; # ifdef NOCRYPT if (password != NULL) return ZIP_PARAMERROR; # endif if (file == NULL) return ZIP_PARAMERROR; #ifdef HAVE_BZIP2 if ((method!=0) && (method!=Z_DEFLATED) && (method!=Z_BZIP2ED)) return ZIP_PARAMERROR; #else if ((method!=0) && (method!=Z_DEFLATED)) return ZIP_PARAMERROR; #endif zi = (zip64_internal*)file; if (zi->in_opened_file_inzip == 1) { err = zipCloseFileInZip (file); if (err != ZIP_OK) return err; } if (method == 0 && (level == 0 || (zi->flags & ZIP_WRITE_DATA_DESCRIPTOR) == 0)) { version_to_extract = 10; } else { version_to_extract = 20; } if (filename==NULL) filename="-"; if (comment==NULL) size_comment = 0; else size_comment = (uInt)strlen(comment); size_filename = (uInt)strlen(filename); if (zipfi == NULL) zi->ci.dosDate = 0; else { if (zipfi->dosDate != 0) zi->ci.dosDate = zipfi->dosDate; else zi->ci.dosDate = zip64local_TmzDateToDosDate(&zipfi->tmz_date); } zi->ci.flag = flagBase; if ((level==8) || (level==9)) zi->ci.flag |= 2; if (level==2) zi->ci.flag |= 4; if (level==1) zi->ci.flag |= 6; if (password != NULL) zi->ci.flag |= 1; if (version_to_extract >= 20 && (zi->flags & ZIP_WRITE_DATA_DESCRIPTOR) != 0) zi->ci.flag |= 8; zi->ci.crc32 = 0; zi->ci.method = method; zi->ci.encrypt = 0; zi->ci.stream_initialised = 0; zi->ci.pos_in_buffered_data = 0; zi->ci.raw = raw; zi->ci.pos_local_header = ZTELL64(zi->z_filefunc,zi->filestream); zi->ci.size_centralheader = SIZECENTRALHEADER + size_filename + size_extrafield_global + size_comment; zi->ci.size_centralExtraFree = 32; /* Extra space we have reserved in case we need to add ZIP64 extra info data */ zi->ci.central_header = (char*)ALLOC((uInt)zi->ci.size_centralheader + zi->ci.size_centralExtraFree); zi->ci.size_centralExtra = size_extrafield_global; zip64local_putValue_inmemory(zi->ci.central_header,(uLong)CENTRALHEADERMAGIC,4); /* version info */ zip64local_putValue_inmemory(zi->ci.central_header+4,(uLong)versionMadeBy,2); zip64local_putValue_inmemory(zi->ci.central_header+6,(uLong)version_to_extract,2); zip64local_putValue_inmemory(zi->ci.central_header+8,(uLong)zi->ci.flag,2); zip64local_putValue_inmemory(zi->ci.central_header+10,(uLong)zi->ci.method,2); zip64local_putValue_inmemory(zi->ci.central_header+12,(uLong)zi->ci.dosDate,4); zip64local_putValue_inmemory(zi->ci.central_header+16,(uLong)0,4); /*crc*/ zip64local_putValue_inmemory(zi->ci.central_header+20,(uLong)0,4); /*compr size*/ zip64local_putValue_inmemory(zi->ci.central_header+24,(uLong)0,4); /*uncompr size*/ zip64local_putValue_inmemory(zi->ci.central_header+28,(uLong)size_filename,2); zip64local_putValue_inmemory(zi->ci.central_header+30,(uLong)size_extrafield_global,2); zip64local_putValue_inmemory(zi->ci.central_header+32,(uLong)size_comment,2); zip64local_putValue_inmemory(zi->ci.central_header+34,(uLong)0,2); /*disk nm start*/ if (zipfi==NULL) zip64local_putValue_inmemory(zi->ci.central_header+36,(uLong)0,2); else zip64local_putValue_inmemory(zi->ci.central_header+36,(uLong)zipfi->internal_fa,2); if (zipfi==NULL) zip64local_putValue_inmemory(zi->ci.central_header+38,(uLong)0,4); else zip64local_putValue_inmemory(zi->ci.central_header+38,(uLong)zipfi->external_fa,4); if(zi->ci.pos_local_header >= 0xffffffff) zip64local_putValue_inmemory(zi->ci.central_header+42,(uLong)0xffffffff,4); else zip64local_putValue_inmemory(zi->ci.central_header+42,(uLong)zi->ci.pos_local_header - zi->add_position_when_writting_offset,4); for (i=0;ici.central_header+SIZECENTRALHEADER+i) = *(filename+i); for (i=0;ici.central_header+SIZECENTRALHEADER+size_filename+i) = *(((const char*)extrafield_global)+i); for (i=0;ici.central_header+SIZECENTRALHEADER+size_filename+ size_extrafield_global+i) = *(comment+i); if (zi->ci.central_header == NULL) return ZIP_INTERNALERROR; zi->ci.zip64 = zip64; zi->ci.totalCompressedData = 0; zi->ci.totalUncompressedData = 0; zi->ci.pos_zip64extrainfo = 0; err = Write_LocalFileHeader(zi, filename, size_extrafield_local, extrafield_local, version_to_extract); #ifdef HAVE_BZIP2 zi->ci.bstream.avail_in = (uInt)0; zi->ci.bstream.avail_out = (uInt)Z_BUFSIZE; zi->ci.bstream.next_out = (char*)zi->ci.buffered_data; zi->ci.bstream.total_in_hi32 = 0; zi->ci.bstream.total_in_lo32 = 0; zi->ci.bstream.total_out_hi32 = 0; zi->ci.bstream.total_out_lo32 = 0; #endif zi->ci.stream.avail_in = (uInt)0; zi->ci.stream.avail_out = (uInt)Z_BUFSIZE; zi->ci.stream.next_out = zi->ci.buffered_data; zi->ci.stream.total_in = 0; zi->ci.stream.total_out = 0; zi->ci.stream.data_type = Z_BINARY; #ifdef HAVE_BZIP2 if ((err==ZIP_OK) && (zi->ci.method == Z_DEFLATED || zi->ci.method == Z_BZIP2ED) && (!zi->ci.raw)) #else if ((err==ZIP_OK) && (zi->ci.method == Z_DEFLATED) && (!zi->ci.raw)) #endif { if(zi->ci.method == Z_DEFLATED) { zi->ci.stream.zalloc = (alloc_func)0; zi->ci.stream.zfree = (free_func)0; zi->ci.stream.opaque = (voidpf)0; if (windowBits>0) windowBits = -windowBits; err = deflateInit2(&zi->ci.stream, level, Z_DEFLATED, windowBits, memLevel, strategy); if (err==Z_OK) zi->ci.stream_initialised = Z_DEFLATED; } else if(zi->ci.method == Z_BZIP2ED) { #ifdef HAVE_BZIP2 /* Init BZip stuff here */ zi->ci.bstream.bzalloc = 0; zi->ci.bstream.bzfree = 0; zi->ci.bstream.opaque = (voidpf)0; err = BZ2_bzCompressInit(&zi->ci.bstream, level, 0,35); if(err == BZ_OK) zi->ci.stream_initialised = Z_BZIP2ED; #endif } } # ifndef NOCRYPT zi->ci.crypt_header_size = 0; if ((err==Z_OK) && (password != NULL)) { unsigned char bufHead[RAND_HEAD_LEN]; unsigned int sizeHead; zi->ci.encrypt = 1; zi->ci.pcrc_32_tab = get_crc_table(); /*init_keys(password,zi->ci.keys,zi->ci.pcrc_32_tab);*/ if (crcForCrypting == 0) { crcForCrypting = (uLong)zi->ci.dosDate << 16; /* ATTANTION! Without this row, you don't unpack your password protected archive in other app. */ } sizeHead=crypthead(password,bufHead,RAND_HEAD_LEN,zi->ci.keys,zi->ci.pcrc_32_tab,crcForCrypting); zi->ci.crypt_header_size = sizeHead; if (ZWRITE64(zi->z_filefunc,zi->filestream,bufHead,sizeHead) != sizeHead) err = ZIP_ERRNO; } # endif if (err==Z_OK) zi->in_opened_file_inzip = 1; return err; } extern int ZEXPORT zipOpenNewFileInZip4 (zipFile file, const char* filename, const zip_fileinfo* zipfi, const void* extrafield_local, uInt size_extrafield_local, const void* extrafield_global, uInt size_extrafield_global, const char* comment, int method, int level, int raw, int windowBits,int memLevel, int strategy, const char* password, uLong crcForCrypting, uLong versionMadeBy, uLong flagBase) { return zipOpenNewFileInZip4_64 (file, filename, zipfi, extrafield_local, size_extrafield_local, extrafield_global, size_extrafield_global, comment, method, level, raw, windowBits, memLevel, strategy, password, crcForCrypting, versionMadeBy, flagBase, 0); } extern int ZEXPORT zipOpenNewFileInZip3 (zipFile file, const char* filename, const zip_fileinfo* zipfi, const void* extrafield_local, uInt size_extrafield_local, const void* extrafield_global, uInt size_extrafield_global, const char* comment, int method, int level, int raw, int windowBits,int memLevel, int strategy, const char* password, uLong crcForCrypting) { return zipOpenNewFileInZip4_64 (file, filename, zipfi, extrafield_local, size_extrafield_local, extrafield_global, size_extrafield_global, comment, method, level, raw, windowBits, memLevel, strategy, password, crcForCrypting, VERSIONMADEBY, 0, 0); } extern int ZEXPORT zipOpenNewFileInZip3_64(zipFile file, const char* filename, const zip_fileinfo* zipfi, const void* extrafield_local, uInt size_extrafield_local, const void* extrafield_global, uInt size_extrafield_global, const char* comment, int method, int level, int raw, int windowBits,int memLevel, int strategy, const char* password, uLong crcForCrypting, int zip64) { return zipOpenNewFileInZip4_64 (file, filename, zipfi, extrafield_local, size_extrafield_local, extrafield_global, size_extrafield_global, comment, method, level, raw, windowBits, memLevel, strategy, password, crcForCrypting, VERSIONMADEBY, 0, zip64); } extern int ZEXPORT zipOpenNewFileInZip2(zipFile file, const char* filename, const zip_fileinfo* zipfi, const void* extrafield_local, uInt size_extrafield_local, const void* extrafield_global, uInt size_extrafield_global, const char* comment, int method, int level, int raw) { return zipOpenNewFileInZip4_64 (file, filename, zipfi, extrafield_local, size_extrafield_local, extrafield_global, size_extrafield_global, comment, method, level, raw, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, NULL, 0, VERSIONMADEBY, 0, 0); } extern int ZEXPORT zipOpenNewFileInZip2_64(zipFile file, const char* filename, const zip_fileinfo* zipfi, const void* extrafield_local, uInt size_extrafield_local, const void* extrafield_global, uInt size_extrafield_global, const char* comment, int method, int level, int raw, int zip64) { return zipOpenNewFileInZip4_64 (file, filename, zipfi, extrafield_local, size_extrafield_local, extrafield_global, size_extrafield_global, comment, method, level, raw, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, NULL, 0, VERSIONMADEBY, 0, zip64); } extern int ZEXPORT zipOpenNewFileInZip64 (zipFile file, const char* filename, const zip_fileinfo* zipfi, const void* extrafield_local, uInt size_extrafield_local, const void*extrafield_global, uInt size_extrafield_global, const char* comment, int method, int level, int zip64) { return zipOpenNewFileInZip4_64 (file, filename, zipfi, extrafield_local, size_extrafield_local, extrafield_global, size_extrafield_global, comment, method, level, 0, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, NULL, 0, VERSIONMADEBY, 0, zip64); } extern int ZEXPORT zipOpenNewFileInZip (zipFile file, const char* filename, const zip_fileinfo* zipfi, const void* extrafield_local, uInt size_extrafield_local, const void*extrafield_global, uInt size_extrafield_global, const char* comment, int method, int level) { return zipOpenNewFileInZip4_64 (file, filename, zipfi, extrafield_local, size_extrafield_local, extrafield_global, size_extrafield_global, comment, method, level, 0, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, NULL, 0, VERSIONMADEBY, 0, 0); } local int zip64FlushWriteBuffer(zip64_internal* zi) { int err=ZIP_OK; if (zi->ci.encrypt != 0) { #ifndef NOCRYPT uInt i; int t; for (i=0;ici.pos_in_buffered_data;i++) zi->ci.buffered_data[i] = zencode(zi->ci.keys, zi->ci.pcrc_32_tab, zi->ci.buffered_data[i],t); #endif } if (ZWRITE64(zi->z_filefunc,zi->filestream,zi->ci.buffered_data,zi->ci.pos_in_buffered_data) != zi->ci.pos_in_buffered_data) err = ZIP_ERRNO; zi->ci.totalCompressedData += zi->ci.pos_in_buffered_data; #ifdef HAVE_BZIP2 if(zi->ci.method == Z_BZIP2ED) { zi->ci.totalUncompressedData += zi->ci.bstream.total_in_lo32; zi->ci.bstream.total_in_lo32 = 0; zi->ci.bstream.total_in_hi32 = 0; } else #endif { zi->ci.totalUncompressedData += zi->ci.stream.total_in; zi->ci.stream.total_in = 0; } zi->ci.pos_in_buffered_data = 0; return err; } extern int ZEXPORT zipWriteInFileInZip (zipFile file,const void* buf,unsigned int len) { zip64_internal* zi; int err=ZIP_OK; if (file == NULL) return ZIP_PARAMERROR; zi = (zip64_internal*)file; if (zi->in_opened_file_inzip == 0) return ZIP_PARAMERROR; zi->ci.crc32 = crc32(zi->ci.crc32,buf,(uInt)len); #ifdef HAVE_BZIP2 if(zi->ci.method == Z_BZIP2ED && (!zi->ci.raw)) { zi->ci.bstream.next_in = (void*)buf; zi->ci.bstream.avail_in = len; err = BZ_RUN_OK; while ((err==BZ_RUN_OK) && (zi->ci.bstream.avail_in>0)) { if (zi->ci.bstream.avail_out == 0) { if (zip64FlushWriteBuffer(zi) == ZIP_ERRNO) err = ZIP_ERRNO; zi->ci.bstream.avail_out = (uInt)Z_BUFSIZE; zi->ci.bstream.next_out = (char*)zi->ci.buffered_data; } if(err != BZ_RUN_OK) break; if ((zi->ci.method == Z_BZIP2ED) && (!zi->ci.raw)) { uLong uTotalOutBefore_lo = zi->ci.bstream.total_out_lo32; /* uLong uTotalOutBefore_hi = zi->ci.bstream.total_out_hi32; */ err=BZ2_bzCompress(&zi->ci.bstream, BZ_RUN); zi->ci.pos_in_buffered_data += (uInt)(zi->ci.bstream.total_out_lo32 - uTotalOutBefore_lo) ; } } if(err == BZ_RUN_OK) err = ZIP_OK; } else #endif { zi->ci.stream.next_in = (Bytef*)buf; zi->ci.stream.avail_in = len; while ((err==ZIP_OK) && (zi->ci.stream.avail_in>0)) { if (zi->ci.stream.avail_out == 0) { if (zip64FlushWriteBuffer(zi) == ZIP_ERRNO) err = ZIP_ERRNO; zi->ci.stream.avail_out = (uInt)Z_BUFSIZE; zi->ci.stream.next_out = zi->ci.buffered_data; } if(err != ZIP_OK) break; if ((zi->ci.method == Z_DEFLATED) && (!zi->ci.raw)) { uLong uTotalOutBefore = zi->ci.stream.total_out; err=deflate(&zi->ci.stream, Z_NO_FLUSH); if(uTotalOutBefore > zi->ci.stream.total_out) { int bBreak = 0; bBreak++; } zi->ci.pos_in_buffered_data += (uInt)(zi->ci.stream.total_out - uTotalOutBefore) ; } else { uInt copy_this,i; if (zi->ci.stream.avail_in < zi->ci.stream.avail_out) copy_this = zi->ci.stream.avail_in; else copy_this = zi->ci.stream.avail_out; for (i = 0; i < copy_this; i++) *(((char*)zi->ci.stream.next_out)+i) = *(((const char*)zi->ci.stream.next_in)+i); { zi->ci.stream.avail_in -= copy_this; zi->ci.stream.avail_out-= copy_this; zi->ci.stream.next_in+= copy_this; zi->ci.stream.next_out+= copy_this; zi->ci.stream.total_in+= copy_this; zi->ci.stream.total_out+= copy_this; zi->ci.pos_in_buffered_data += copy_this; } } }/* while(...) */ } return err; } extern int ZEXPORT zipCloseFileInZipRaw (zipFile file, uLong uncompressed_size, uLong crc32) { return zipCloseFileInZipRaw64 (file, uncompressed_size, crc32); } extern int ZEXPORT zipCloseFileInZipRaw64 (zipFile file, ZPOS64_T uncompressed_size, uLong crc32) { zip64_internal* zi; ZPOS64_T compressed_size; uLong invalidValue = 0xffffffff; short datasize = 0; int err=ZIP_OK; if (file == NULL) return ZIP_PARAMERROR; zi = (zip64_internal*)file; if (zi->in_opened_file_inzip == 0) return ZIP_PARAMERROR; zi->ci.stream.avail_in = 0; if ((zi->ci.method == Z_DEFLATED) && (!zi->ci.raw)) { while (err==ZIP_OK) { uLong uTotalOutBefore; if (zi->ci.stream.avail_out == 0) { if (zip64FlushWriteBuffer(zi) == ZIP_ERRNO) err = ZIP_ERRNO; zi->ci.stream.avail_out = (uInt)Z_BUFSIZE; zi->ci.stream.next_out = zi->ci.buffered_data; } uTotalOutBefore = zi->ci.stream.total_out; err=deflate(&zi->ci.stream, Z_FINISH); zi->ci.pos_in_buffered_data += (uInt)(zi->ci.stream.total_out - uTotalOutBefore) ; } } else if ((zi->ci.method == Z_BZIP2ED) && (!zi->ci.raw)) { #ifdef HAVE_BZIP2 err = BZ_FINISH_OK; while (err==BZ_FINISH_OK) { uLong uTotalOutBefore; if (zi->ci.bstream.avail_out == 0) { if (zip64FlushWriteBuffer(zi) == ZIP_ERRNO) err = ZIP_ERRNO; zi->ci.bstream.avail_out = (uInt)Z_BUFSIZE; zi->ci.bstream.next_out = (char*)zi->ci.buffered_data; } uTotalOutBefore = zi->ci.bstream.total_out_lo32; err=BZ2_bzCompress(&zi->ci.bstream, BZ_FINISH); if(err == BZ_STREAM_END) err = Z_STREAM_END; zi->ci.pos_in_buffered_data += (uInt)(zi->ci.bstream.total_out_lo32 - uTotalOutBefore); } if(err == BZ_FINISH_OK) err = ZIP_OK; #endif } if (err==Z_STREAM_END) err=ZIP_OK; /* this is normal */ if ((zi->ci.pos_in_buffered_data>0) && (err==ZIP_OK)) { if (zip64FlushWriteBuffer(zi)==ZIP_ERRNO) err = ZIP_ERRNO; } if ((zi->ci.method == Z_DEFLATED) && (!zi->ci.raw)) { int tmp_err = deflateEnd(&zi->ci.stream); if (err == ZIP_OK) err = tmp_err; zi->ci.stream_initialised = 0; } #ifdef HAVE_BZIP2 else if((zi->ci.method == Z_BZIP2ED) && (!zi->ci.raw)) { int tmperr = BZ2_bzCompressEnd(&zi->ci.bstream); if (err==ZIP_OK) err = tmperr; zi->ci.stream_initialised = 0; } #endif if (!zi->ci.raw) { crc32 = (uLong)zi->ci.crc32; uncompressed_size = zi->ci.totalUncompressedData; } compressed_size = zi->ci.totalCompressedData; # ifndef NOCRYPT compressed_size += zi->ci.crypt_header_size; # endif /* update Current Item crc and sizes, */ if(compressed_size >= 0xffffffff || uncompressed_size >= 0xffffffff || zi->ci.pos_local_header >= 0xffffffff) { /*version Made by*/ zip64local_putValue_inmemory(zi->ci.central_header+4,(uLong)45,2); /*version needed*/ zip64local_putValue_inmemory(zi->ci.central_header+6,(uLong)45,2); } zip64local_putValue_inmemory(zi->ci.central_header+16,crc32,4); /*crc*/ if(compressed_size >= 0xffffffff) zip64local_putValue_inmemory(zi->ci.central_header+20, invalidValue,4); /*compr size*/ else zip64local_putValue_inmemory(zi->ci.central_header+20, compressed_size,4); /*compr size*/ /* set internal file attributes field */ if (zi->ci.stream.data_type == Z_ASCII) zip64local_putValue_inmemory(zi->ci.central_header+36,(uLong)Z_ASCII,2); if(uncompressed_size >= 0xffffffff) zip64local_putValue_inmemory(zi->ci.central_header+24, invalidValue,4); /*uncompr size*/ else zip64local_putValue_inmemory(zi->ci.central_header+24, uncompressed_size,4); /*uncompr size*/ /* Add ZIP64 extra info field for uncompressed size */ if(uncompressed_size >= 0xffffffff) datasize += 8; /* Add ZIP64 extra info field for compressed size */ if(compressed_size >= 0xffffffff) datasize += 8; /* Add ZIP64 extra info field for relative offset to local file header of current file */ if(zi->ci.pos_local_header >= 0xffffffff) datasize += 8; if(datasize > 0) { char* p = NULL; if((uLong)(datasize + 4) > zi->ci.size_centralExtraFree) { /* we can not write more data to the buffer that we have room for. */ return ZIP_BADZIPFILE; } p = zi->ci.central_header + zi->ci.size_centralheader; /* Add Extra Information Header for 'ZIP64 information' */ zip64local_putValue_inmemory(p, 0x0001, 2); /* HeaderID */ p += 2; zip64local_putValue_inmemory(p, datasize, 2); /* DataSize */ p += 2; if(uncompressed_size >= 0xffffffff) { zip64local_putValue_inmemory(p, uncompressed_size, 8); p += 8; } if(compressed_size >= 0xffffffff) { zip64local_putValue_inmemory(p, compressed_size, 8); p += 8; } if(zi->ci.pos_local_header >= 0xffffffff) { zip64local_putValue_inmemory(p, zi->ci.pos_local_header, 8); p += 8; } /* Update how much extra free space we got in the memory buffer */ /* and increase the centralheader size so the new ZIP64 fields are included */ /* ( 4 below is the size of HeaderID and DataSize field ) */ zi->ci.size_centralExtraFree -= datasize + 4; zi->ci.size_centralheader += datasize + 4; /* Update the extra info size field */ zi->ci.size_centralExtra += datasize + 4; zip64local_putValue_inmemory(zi->ci.central_header+30,(uLong)zi->ci.size_centralExtra,2); } if (err==ZIP_OK) err = add_data_in_datablock(&zi->central_dir, zi->ci.central_header, (uLong)zi->ci.size_centralheader); free(zi->ci.central_header); if (err==ZIP_OK) { /* Update the LocalFileHeader with the new values. */ ZPOS64_T cur_pos_inzip = ZTELL64(zi->z_filefunc,zi->filestream); if (ZSEEK64(zi->z_filefunc,zi->filestream, zi->ci.pos_local_header + 14,ZLIB_FILEFUNC_SEEK_SET)!=0) err = ZIP_ERRNO; if (err==ZIP_OK) err = zip64local_putValue(&zi->z_filefunc,zi->filestream,crc32,4); /* crc 32, unknown */ if(uncompressed_size >= 0xffffffff || compressed_size >= 0xffffffff) { if(zi->ci.pos_zip64extrainfo > 0) { /* Update the size in the ZIP64 extended field. */ if (ZSEEK64(zi->z_filefunc,zi->filestream, zi->ci.pos_zip64extrainfo + 4,ZLIB_FILEFUNC_SEEK_SET)!=0) err = ZIP_ERRNO; if (err==ZIP_OK) /* compressed size, unknown */ err = zip64local_putValue(&zi->z_filefunc, zi->filestream, uncompressed_size, 8); if (err==ZIP_OK) /* uncompressed size, unknown */ err = zip64local_putValue(&zi->z_filefunc, zi->filestream, compressed_size, 8); } } else { if (err==ZIP_OK) /* compressed size, unknown */ err = zip64local_putValue(&zi->z_filefunc,zi->filestream,compressed_size,4); if (err==ZIP_OK) /* uncompressed size, unknown */ err = zip64local_putValue(&zi->z_filefunc,zi->filestream,uncompressed_size,4); } if (ZSEEK64(zi->z_filefunc,zi->filestream, cur_pos_inzip,ZLIB_FILEFUNC_SEEK_SET)!=0) err = ZIP_ERRNO; if ((zi->ci.flag & 8) != 0) { /* Write local Descriptor after file data */ if (err==ZIP_OK) err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)DESCRIPTORHEADERMAGIC,4); if (err==ZIP_OK) err = zip64local_putValue(&zi->z_filefunc,zi->filestream,crc32,4); /* crc 32, unknown */ if (zi->ci.zip64) { if (err==ZIP_OK) /* compressed size, unknown */ err = zip64local_putValue(&zi->z_filefunc,zi->filestream,compressed_size,8); if (err==ZIP_OK) /* uncompressed size, unknown */ err = zip64local_putValue(&zi->z_filefunc,zi->filestream,uncompressed_size,8); } else { if (err==ZIP_OK) /* compressed size, unknown */ err = zip64local_putValue(&zi->z_filefunc,zi->filestream,compressed_size,4); if (err==ZIP_OK) /* uncompressed size, unknown */ err = zip64local_putValue(&zi->z_filefunc,zi->filestream,uncompressed_size,4); } } } zi->number_entry ++; zi->in_opened_file_inzip = 0; return err; } extern int ZEXPORT zipCloseFileInZip (zipFile file) { return zipCloseFileInZipRaw (file,0,0); } int Write_Zip64EndOfCentralDirectoryLocator(zip64_internal* zi, ZPOS64_T zip64eocd_pos_inzip) { int err = ZIP_OK; ZPOS64_T pos = zip64eocd_pos_inzip - zi->add_position_when_writting_offset; err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)ZIP64ENDLOCHEADERMAGIC,4); /*num disks*/ if (err==ZIP_OK) /* number of the disk with the start of the central directory */ err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); /*relative offset*/ if (err==ZIP_OK) /* Relative offset to the Zip64EndOfCentralDirectory */ err = zip64local_putValue(&zi->z_filefunc,zi->filestream, pos,8); /*total disks*/ /* Do not support spawning of disk so always say 1 here*/ if (err==ZIP_OK) /* number of the disk with the start of the central directory */ err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)1,4); return err; } int Write_Zip64EndOfCentralDirectoryRecord(zip64_internal* zi, uLong size_centraldir, ZPOS64_T centraldir_pos_inzip) { int err = ZIP_OK; uLong Zip64DataSize = 44; err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)ZIP64ENDHEADERMAGIC,4); if (err==ZIP_OK) /* size of this 'zip64 end of central directory' */ err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(ZPOS64_T)Zip64DataSize,8); /* why ZPOS64_T of this ? */ if (err==ZIP_OK) /* version made by */ err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)45,2); if (err==ZIP_OK) /* version needed */ err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)45,2); if (err==ZIP_OK) /* number of this disk */ err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); if (err==ZIP_OK) /* number of the disk with the start of the central directory */ err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,4); if (err==ZIP_OK) /* total number of entries in the central dir on this disk */ err = zip64local_putValue(&zi->z_filefunc, zi->filestream, zi->number_entry, 8); if (err==ZIP_OK) /* total number of entries in the central dir */ err = zip64local_putValue(&zi->z_filefunc, zi->filestream, zi->number_entry, 8); if (err==ZIP_OK) /* size of the central directory */ err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(ZPOS64_T)size_centraldir,8); if (err==ZIP_OK) /* offset of start of central directory with respect to the starting disk number */ { ZPOS64_T pos = centraldir_pos_inzip - zi->add_position_when_writting_offset; err = zip64local_putValue(&zi->z_filefunc,zi->filestream, (ZPOS64_T)pos,8); } return err; } int Write_EndOfCentralDirectoryRecord(zip64_internal* zi, uLong size_centraldir, ZPOS64_T centraldir_pos_inzip) { int err = ZIP_OK; /*signature*/ err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)ENDHEADERMAGIC,4); if (err==ZIP_OK) /* number of this disk */ err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,2); if (err==ZIP_OK) /* number of the disk with the start of the central directory */ err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0,2); if (err==ZIP_OK) /* total number of entries in the central dir on this disk */ { { if(zi->number_entry >= 0xFFFF) err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0xffff,2); /* use value in ZIP64 record */ else err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->number_entry,2); } } if (err==ZIP_OK) /* total number of entries in the central dir */ { if(zi->number_entry >= 0xFFFF) err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)0xffff,2); /* use value in ZIP64 record */ else err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)zi->number_entry,2); } if (err==ZIP_OK) /* size of the central directory */ err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_centraldir,4); if (err==ZIP_OK) /* offset of start of central directory with respect to the starting disk number */ { ZPOS64_T pos = centraldir_pos_inzip - zi->add_position_when_writting_offset; if(pos >= 0xffffffff) { err = zip64local_putValue(&zi->z_filefunc,zi->filestream, (uLong)0xffffffff,4); } else err = zip64local_putValue(&zi->z_filefunc,zi->filestream, (uLong)(centraldir_pos_inzip - zi->add_position_when_writting_offset),4); } return err; } int Write_GlobalComment(zip64_internal* zi, const char* global_comment) { int err = ZIP_OK; uInt size_global_comment = 0; if(global_comment != NULL) size_global_comment = (uInt)strlen(global_comment); err = zip64local_putValue(&zi->z_filefunc,zi->filestream,(uLong)size_global_comment,2); if (err == ZIP_OK && size_global_comment > 0) { if (ZWRITE64(zi->z_filefunc,zi->filestream, global_comment, size_global_comment) != size_global_comment) err = ZIP_ERRNO; } return err; } extern int ZEXPORT zipClose (zipFile file, const char* global_comment) { zip64_internal* zi; int err = 0; uLong size_centraldir = 0; ZPOS64_T centraldir_pos_inzip; ZPOS64_T pos; if (file == NULL) return ZIP_PARAMERROR; zi = (zip64_internal*)file; if (zi->in_opened_file_inzip == 1) { err = zipCloseFileInZip (file); } #ifndef NO_ADDFILEINEXISTINGZIP if (global_comment==NULL) global_comment = zi->globalcomment; #endif centraldir_pos_inzip = ZTELL64(zi->z_filefunc,zi->filestream); if (err==ZIP_OK) { linkedlist_datablock_internal* ldi = zi->central_dir.first_block; while (ldi!=NULL) { if ((err==ZIP_OK) && (ldi->filled_in_this_block>0)) { if (ZWRITE64(zi->z_filefunc,zi->filestream, ldi->data, ldi->filled_in_this_block) != ldi->filled_in_this_block) err = ZIP_ERRNO; } size_centraldir += ldi->filled_in_this_block; ldi = ldi->next_datablock; } } free_linkedlist(&(zi->central_dir)); pos = centraldir_pos_inzip - zi->add_position_when_writting_offset; if(pos >= 0xffffffff) { ZPOS64_T Zip64EOCDpos = ZTELL64(zi->z_filefunc,zi->filestream); Write_Zip64EndOfCentralDirectoryRecord(zi, size_centraldir, centraldir_pos_inzip); Write_Zip64EndOfCentralDirectoryLocator(zi, Zip64EOCDpos); } if (err==ZIP_OK) err = Write_EndOfCentralDirectoryRecord(zi, size_centraldir, centraldir_pos_inzip); if(err == ZIP_OK) err = Write_GlobalComment(zi, global_comment); if (ZCLOSE64(zi->z_filefunc,zi->filestream) != 0) if (err == ZIP_OK) err = ZIP_ERRNO; #ifndef NO_ADDFILEINEXISTINGZIP TRYFREE(zi->globalcomment); #endif TRYFREE(zi); return err; } extern int ZEXPORT zipRemoveExtraInfoBlock (char* pData, int* dataLen, short sHeader) { char* p = pData; int size = 0; char* pNewHeader; char* pTmp; short header; short dataSize; int retVal = ZIP_OK; if(pData == NULL || *dataLen < 4) return ZIP_PARAMERROR; pNewHeader = (char*)ALLOC(*dataLen); pTmp = pNewHeader; while(p < (pData + *dataLen)) { header = *(short*)p; dataSize = *(((short*)p)+1); if( header == sHeader ) /* Header found. */ { p += dataSize + 4; /* skip it. do not copy to temp buffer */ } else { /* Extra Info block should not be removed, So copy it to the temp buffer. */ memcpy(pTmp, p, dataSize + 4); p += dataSize + 4; size += dataSize + 4; } } if(size < *dataLen) { /* clean old extra info block. */ memset(pData,0, *dataLen); /* copy the new extra info block over the old */ if(size > 0) memcpy(pData, pNewHeader, size); /* set the new extra info size */ *dataLen = size; retVal = ZIP_OK; } else retVal = ZIP_ERRNO; TRYFREE(pNewHeader); return retVal; } int ZEXPORT zipSetFlags(zipFile file, unsigned flags) { zip64_internal* zi; if (file == NULL) return ZIP_PARAMERROR; zi = (zip64_internal*)file; zi->flags |= flags; return ZIP_OK; } int ZEXPORT zipClearFlags(zipFile file, unsigned flags) { zip64_internal* zi; if (file == NULL) return ZIP_PARAMERROR; zi = (zip64_internal*)file; zi->flags &= ~flags; return ZIP_OK; } workbench-1.1.1/src/Quazip/zip.h000066400000000000000000000370321255417355300165260ustar00rootroot00000000000000/* zip.h -- IO on .zip files using zlib Version 1.1, February 14h, 2010 part of the MiniZip project - ( http://www.winimage.com/zLibDll/minizip.html ) Copyright (C) 1998-2010 Gilles Vollant (minizip) ( http://www.winimage.com/zLibDll/minizip.html ) Modifications for Zip64 support Copyright (C) 2009-2010 Mathias Svensson ( http://result42.com ) For more info read MiniZip_info.txt --------------------------------------------------------------------------- Condition of use and distribution are the same than zlib : This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. --------------------------------------------------------------------------- Changes See header of zip.h */ #ifndef _zip12_H #define _zip12_H #ifdef __cplusplus extern "C" { #endif //#define HAVE_BZIP2 #ifndef _ZLIB_H #include "zlib.h" #endif #ifndef _ZLIBIOAPI_H #include "ioapi.h" #endif #ifdef HAVE_BZIP2 #include "bzlib.h" #endif #define Z_BZIP2ED 12 #if defined(STRICTZIP) || defined(STRICTZIPUNZIP) /* like the STRICT of WIN32, we define a pointer that cannot be converted from (void*) without cast */ typedef struct TagzipFile__ { int unused; } zipFile__; typedef zipFile__ *zipFile; #else typedef voidp zipFile; #endif #define ZIP_OK (0) #define ZIP_EOF (0) #define ZIP_ERRNO (Z_ERRNO) #define ZIP_PARAMERROR (-102) #define ZIP_BADZIPFILE (-103) #define ZIP_INTERNALERROR (-104) #define ZIP_WRITE_DATA_DESCRIPTOR 0x8u #ifndef DEF_MEM_LEVEL # if MAX_MEM_LEVEL >= 8 # define DEF_MEM_LEVEL 8 # else # define DEF_MEM_LEVEL MAX_MEM_LEVEL # endif #endif /* default memLevel */ /* tm_zip contain date/time info */ typedef struct tm_zip_s { uInt tm_sec; /* seconds after the minute - [0,59] */ uInt tm_min; /* minutes after the hour - [0,59] */ uInt tm_hour; /* hours since midnight - [0,23] */ uInt tm_mday; /* day of the month - [1,31] */ uInt tm_mon; /* months since January - [0,11] */ uInt tm_year; /* years - [1980..2044] */ } tm_zip; typedef struct { tm_zip tmz_date; /* date in understandable format */ uLong dosDate; /* if dos_date == 0, tmu_date is used */ /* uLong flag; */ /* general purpose bit flag 2 bytes */ uLong internal_fa; /* internal file attributes 2 bytes */ uLong external_fa; /* external file attributes 4 bytes */ } zip_fileinfo; typedef const char* zipcharpc; #define APPEND_STATUS_CREATE (0) #define APPEND_STATUS_CREATEAFTER (1) #define APPEND_STATUS_ADDINZIP (2) extern zipFile ZEXPORT zipOpen OF((voidpf file, int append)); extern zipFile ZEXPORT zipOpen64 OF((voidpf file, int append)); /* Create a zipfile. the file argument depends on the API used, for QuaZIP it's a QIODevice pointer. if the file pathname exist and append==APPEND_STATUS_CREATEAFTER, the zip will be created at the end of the file. (useful if the file contain a self extractor code) if the file pathname exist and append==APPEND_STATUS_ADDINZIP, we will add files in existing zip (be sure you don't add file that doesn't exist) If the zipfile cannot be opened, the return value is NULL. Else, the return value is a zipFile Handle, usable with other function of this zip package. */ /* Note : there is no delete function into a zipfile. If you want delete file into a zipfile, you must open a zipfile, and create another Of couse, you can use RAW reading and writing to copy the file you did not want delte */ extern zipFile ZEXPORT zipOpen2 OF((voidpf file, int append, zipcharpc* globalcomment, zlib_filefunc_def* pzlib_filefunc_def)); extern zipFile ZEXPORT zipOpen2_64 OF((voidpf file, int append, zipcharpc* globalcomment, zlib_filefunc64_def* pzlib_filefunc_def)); extern int ZEXPORT zipOpenNewFileInZip OF((zipFile file, const char* filename, const zip_fileinfo* zipfi, const void* extrafield_local, uInt size_extrafield_local, const void* extrafield_global, uInt size_extrafield_global, const char* comment, int method, int level)); extern int ZEXPORT zipOpenNewFileInZip64 OF((zipFile file, const char* filename, const zip_fileinfo* zipfi, const void* extrafield_local, uInt size_extrafield_local, const void* extrafield_global, uInt size_extrafield_global, const char* comment, int method, int level, int zip64)); /* Open a file in the ZIP for writing. filename : the filename in zip (if NULL, '-' without quote will be used *zipfi contain supplemental information if extrafield_local!=NULL and size_extrafield_local>0, extrafield_local contains the extrafield data the the local header if extrafield_global!=NULL and size_extrafield_global>0, extrafield_global contains the extrafield data the the local header if comment != NULL, comment contain the comment string method contain the compression method (0 for store, Z_DEFLATED for deflate) level contain the level of compression (can be Z_DEFAULT_COMPRESSION) zip64 is set to 1 if a zip64 extended information block should be added to the local file header. this MUST be '1' if the uncompressed size is >= 0xffffffff. */ extern int ZEXPORT zipOpenNewFileInZip2 OF((zipFile file, const char* filename, const zip_fileinfo* zipfi, const void* extrafield_local, uInt size_extrafield_local, const void* extrafield_global, uInt size_extrafield_global, const char* comment, int method, int level, int raw)); extern int ZEXPORT zipOpenNewFileInZip2_64 OF((zipFile file, const char* filename, const zip_fileinfo* zipfi, const void* extrafield_local, uInt size_extrafield_local, const void* extrafield_global, uInt size_extrafield_global, const char* comment, int method, int level, int raw, int zip64)); /* Same than zipOpenNewFileInZip, except if raw=1, we write raw file */ extern int ZEXPORT zipOpenNewFileInZip3 OF((zipFile file, const char* filename, const zip_fileinfo* zipfi, const void* extrafield_local, uInt size_extrafield_local, const void* extrafield_global, uInt size_extrafield_global, const char* comment, int method, int level, int raw, int windowBits, int memLevel, int strategy, const char* password, uLong crcForCrypting)); extern int ZEXPORT zipOpenNewFileInZip3_64 OF((zipFile file, const char* filename, const zip_fileinfo* zipfi, const void* extrafield_local, uInt size_extrafield_local, const void* extrafield_global, uInt size_extrafield_global, const char* comment, int method, int level, int raw, int windowBits, int memLevel, int strategy, const char* password, uLong crcForCrypting, int zip64 )); /* Same than zipOpenNewFileInZip2, except windowBits,memLevel,,strategy : see parameter strategy in deflateInit2 password : crypting password (NULL for no crypting) crcForCrypting : crc of file to compress (needed for crypting) */ extern int ZEXPORT zipOpenNewFileInZip4 OF((zipFile file, const char* filename, const zip_fileinfo* zipfi, const void* extrafield_local, uInt size_extrafield_local, const void* extrafield_global, uInt size_extrafield_global, const char* comment, int method, int level, int raw, int windowBits, int memLevel, int strategy, const char* password, uLong crcForCrypting, uLong versionMadeBy, uLong flagBase )); extern int ZEXPORT zipOpenNewFileInZip4_64 OF((zipFile file, const char* filename, const zip_fileinfo* zipfi, const void* extrafield_local, uInt size_extrafield_local, const void* extrafield_global, uInt size_extrafield_global, const char* comment, int method, int level, int raw, int windowBits, int memLevel, int strategy, const char* password, uLong crcForCrypting, uLong versionMadeBy, uLong flagBase, int zip64 )); /* Same than zipOpenNewFileInZip4, except versionMadeBy : value for Version made by field flag : value for flag field (compression level info will be added) */ extern int ZEXPORT zipWriteInFileInZip OF((zipFile file, const void* buf, unsigned len)); /* Write data in the zipfile */ extern int ZEXPORT zipCloseFileInZip OF((zipFile file)); /* Close the current file in the zipfile */ extern int ZEXPORT zipCloseFileInZipRaw OF((zipFile file, uLong uncompressed_size, uLong crc32)); extern int ZEXPORT zipCloseFileInZipRaw64 OF((zipFile file, ZPOS64_T uncompressed_size, uLong crc32)); /* Close the current file in the zipfile, for file opened with parameter raw=1 in zipOpenNewFileInZip2 uncompressed_size and crc32 are value for the uncompressed size */ extern int ZEXPORT zipClose OF((zipFile file, const char* global_comment)); /* Close the zipfile */ extern int ZEXPORT zipRemoveExtraInfoBlock OF((char* pData, int* dataLen, short sHeader)); /* zipRemoveExtraInfoBlock - Added by Mathias Svensson Remove extra information block from a extra information data for the local file header or central directory header It is needed to remove ZIP64 extra information blocks when before data is written if using RAW mode. 0x0001 is the signature header for the ZIP64 extra information blocks usage. Remove ZIP64 Extra information from a central director extra field data zipRemoveExtraInfoBlock(pCenDirExtraFieldData, &nCenDirExtraFieldDataLen, 0x0001); Remove ZIP64 Extra information from a Local File Header extra field data zipRemoveExtraInfoBlock(pLocalHeaderExtraFieldData, &nLocalHeaderExtraFieldDataLen, 0x0001); */ /* Added by Sergey A. Tachenov to tweak zipping behaviour. */ extern int ZEXPORT zipSetFlags(zipFile file, unsigned flags); extern int ZEXPORT zipClearFlags(zipFile file, unsigned flags); #ifdef __cplusplus } #endif #endif /* _zip64_H */ workbench-1.1.1/src/Qwt/000077500000000000000000000000001255417355300150505ustar00rootroot00000000000000workbench-1.1.1/src/Qwt/CHANGES000066400000000000000000000444111255417355300160470ustar00rootroot00000000000000Release 6.0.1 =================== Changes ------- 1) Incompatibilities with Qt < 4.6.1 fixed 2) Reduce warnings with pedantic compiler options 3) Examples fixed 4) Legend identifiers of with symbol and line fixed Release 6.0.0 =================== Changes ------- 1) Qt3 support dropped 2) QwtPlot layout/render code ported from int to double Exported/printed documents in scalable formats like SVG or PDF are 100% scalable now. 3) Template base classes introduced for curve and curve data to be reusable in all plot items displaying series of samples. 4) New plot items - QwtPlotHistogram - QwtPlotIntervalCurve ( error bars or displaying the area between 2 curves ) - QwtPlotSpectroCurve ( mapping the z value to a color ) 5) Raster items - QwtMatrixRasterData introduced - More accurate rendering - Several API changes - Thread support for rendering spectrograms 6) QwtPlot::print moved to QwtPlotRenderer 7) Other new classes QwtColumnSymbol QwtDoublePoint3D QwtIntervalSymbol QwtPlotDirectPainter QwtSamplingThread QwtSystemClock 8) QwtPicker and friends reorganized, QwtPickerTrackerMachine added for displaying a rubberband for mouse moves. Enter/Leave added to events, that are handled by the picker machines. 9) QwtScaleWidget::LayoutFlag added Introduced to control the direction of vertical axis titles. 10)QwtWeedingCurveFitter added QwtWeedingCurveFitter is an implementation of the Douglas/Peuker algorithm, that can be used to reduce the number of curve points. It can be very useful to improve the performance of painting curves of many lines ( f.e. by implementing different level of details ). 11)Legend code update for representing different pixmaps for different types of plot items. 12)Copy operators removed, using pointers instead 13)QwtPolarPoint from qwtpolar added 14)QwtThermo Optional QwtColorMaps added 15)Interfaces and code of all sliders/dials cleaned up. QApplication::globalStrut(), styled backgrounds ... Release 5.2.2 =================== Bug Fixes --------- 1) QwtSplineCurveFitter Rounding to integers values removed 2) QwtPlot initial size of 200x200 3) QwtPlotPrintFilter, QwtPlot::print handling of background color in QwtPlot::print 4) QwtPlotPrintFilter Problem with colored plot titles fixed 5) QwtPlotItem Crash fixed, when changing the z order of attached items 6) QwtLinearScaleEngine, QwtLog10ScaleEngine Several minor fixes Release 5.2.1 =================== Bug Fixes --------- 1) Export declarations removed from qwt_valuelist.h to avoid compiler errors with Qt 4.6 + certain compilers on Windows. 2) QwtScaleDraw Wrong border dist hints for unregular scale divisions fixed Layout calculation for the tick labels fixed. The layout was wrong by 1 pixel for top/left/bottom axes. On a left axis without a title the labels were sometimes cut off. 3) QwtPainter Splits polylines for all pens to avoid a bottleneck of the raster paint engine. 4) QwtScaleWidget Calculation of the colorbar position fixed ( spacing/margin ) 5) QwtPlotCurve Wrong clipping rect fixed 6) QwtPicker QwtPicker::setTrackerFont() fixed. Recursion on the Mac, when constructing the rubberband fixed. Workaround for a Qt3 bug added that is responsible for left aligning all tracker texts to the canvas. Changes ------- 1) Project files adopted for symbian 2) qwt.pro CONFIG += ordered added for using make -j CONFIG += silent added Release 5.2.0 =================== Changes ------- 1) Ported to Qt 4.5.x 2) Scaling of non cosmetic pens (for printing to devices in high resolution) 3) Clipping of polygons for SVG rendering 4) QwtRect removed use QwtClipper instead 5) QwtPlotRescaler Introduced 6) QwtDoubleInterval BorderMode introduced 7) QwtPlotCurve Performance of incremental curve painting ( = draw(from, to) ) improved. 8) QwtLegendItem setIdentfierMode renamed to setIdentifierMode 9) QwtPlotCanvas::replot() introduced code from QwtPlot::replot shifted 10)QwtPlot drawCanvas(), updateAxes() changed from protected to public 11)QwtScaleEngine loMargin/hiMargin renamed to lowerMargin/upperMargin 12)QwtScaleDiv lBound/hBound renamed to lowerBound/upperBound 13)QwtSpline cofficientA/B/C introduced 14)QwtDial counter clockwise scales introduced 15)QwtPlotMarker Vertical text labels 16)doc/qwt-5.2.0.qch added foe browsing the Qwt docs in the Qt assistant Bug Fixes --------- 1) QwtLinearScaleEngine Rounding problems fixed 2) Again some print layout problems fixed 3) QwtPlotScaleItem: 1 pixel offset fixed 4) QwtPlotSpectrogram, clipping of contour lines against the bounding rect 5) QwtPlotZoomer::setZoomStack for stacks with unlimited depth 6) Printing of rotated tick labels Release 5.1.1 =================== Bug Fixes --------- 1) Several compiler incompatibilities fixed 2) DBL_EPSILON removed Using DBL_EPSILON in the calculations of the dials/sliders and the scale engines leads to problems with the inaccuracy of floating points. The behaviour has been reverted to 5.0.x. 3) QwtSlider/QwtKnob setScaleDraw() fixed. 4) QwtRect Pointless private declaration removed Release 5.1.0 =================== Changes ------- 1) QwtSymbol::copy introduced Now it is possible to use derived symbol classes for curves 2) QwtPlotScaleItem introduced A new type of plot item for displaying axes on the canvas 3) QwtClipper added A collection of clipping algos 4) Using DBL_EPSILON This change allows smaller intervals for sliders/dials 5) QwtPanner setOrientation() added. 6) QwtPlot axisStepSize() added clear is virtual now 7) QwtPlotPrintFilter PrintCanvasBackground splitted into PrintBackground, PrintFrameWithScales 8) QwtPlotZoomer setZoomStack() added 9) Changes for the QwtPolar package QwtLegendItemManager introduced QwtMagnifier introduced 10)Suffix rules added in qwtconfig.pri for different targets for debug/release builds. Bug Fixes --------- 1. QwtAbstractScaleDraw::setAbstractScaleDraw Reinitialization problem fixed 2. QwtLegendItem key event handlers fixed 3. QwtPicker solaris-cc compiler problem fixed 4. Inaccurate mapping of scale to widget coordinates fixed 5. QwtPlotCurve::draw Updates for Qt 4.3 added 6. QwtPlotLayout AlignToCanvas layout calculation fixed 7. QwtPlot::print Workaround for a QPen initialization problem, when printing to Pdf, added 8. QwtText Layout of rich text documents fixed 9. Designer Handling of QwtScaleWidget fixed 10. realtime example Qt::WA_PaintOutsidePaintEvent added, ScrollZoomer fixed 11. Several others I have forgotten Release 5.0.2 =================== Bug Fixes --------- 1. QwtPlotCurve::Xfy curve type fixed 2. Memory leak in QwtLegend fixed 3. Vertical alignment of rich texts fixed 4. Workaround for a Qt4 bug added, that produces horrible performance when painting curves with a pen width > 1. 5. Background for the tracker text of QwtPickers fixed. Improved (faster + better rendered texts) implementation of painting tracker texts, using capabilities of Qt >= 4.3. 6. QwtArrowButton/QwtCounter: workaround for layout bug ( Qt < 4.3 ) of the Cleanlook style added. 7. A couple of minor fixes Changes ------- 1. QSvgGenerator added to the bode example Release 5.0.1 =================== Changes ------- 1. A couple of problems, when building Qwt fixed. 2. Displaying Rich Text with Qt 4.x fixed Release 5.0.0 =================== Platforms --------- Support of Qt3 and Qt4. Qt2 is not supported any longer. Key features ------------ 1. Redesign of plot items. Makes it much easier to develop individual items. 2. Redesign of the scale classes. All calculations are collected in scale engines, where the application can implement it´s own (f.e log2, or date scales). Now it´s also possible to have individual and completely irregular scales 3. Redesign of the QwtText classes. The MathML renderer of the Qt4 solutions package is embedded. work for all expressions/situations. 4. New classes for navigating: QwtPanner, QwtMaginfier 5. Spectrogram/Contour plots and other classes for displaying raster data added. Changes ------- 5.0.0 is by far the release with the most changes in the history of Qwt - too many to make list. Release 4.2.0/0.4.2 =================== License -------- A couple of exceptions to the LGPL with the intention to allow static linking with commercial applications. See COPYING. Key features: ------------- 1. Designer plugin 2. Rich Text support ( f.e. E = m * c2 ) added. 3. QwtDial class family added (QwtDial, QwtCompass, QwtAnalogClock, ...) 4. QwtPicker class family added. Includes QwtPlotZoomer, a complete implementation of recursive zooming. 5. Device metrics independent printing of QwtPlot. (QPrinter::HighResolution) 6. QwtPlot::setCurveBrush(), QwtCurve::setBrush() added. The area between curve and baseline will be filled with this brush. 7. Rotation of axis tick labels added. Very useful for axis with long labels like time scales ... 8. Added a new abstract QwtData class to plot data from almost any type of container class. 9. QwtDoublePoint, QwtDoubleSize, QwtDoubleRect double counterparts for QPoint, QSize, QRect. 10. First steps to support Qtopia. All examples can be compiled and started in the qvfb emulator. Changes: --------- 1. Rewrite of QwtLegend/QwtLegendItem (no QTable anymore) 2. Each plot item will be painted, even if one of the axis it is attached to is disabled. (like in all other releases beside 0.4.1) 3. Code for double buffering moved to a new class QwtPaintBuffer. Double buffering can be enabled/disabled now. 4. QwtPainter, QwtMetricsMap, QwtLayoutMetrics added Hide paint device metrics dependencies. 5. Layout code rewritten and moved to a new class QwtPlotLayout New layout options canvasMargin(), alignCanvasToScales() 6. QwtPlot: sizeHint() != minimumSizeHint() 9. Internal plot data are private again. A couple of get methods added instead. 10. canvas repaints triggered by paint events. Enables event filtering 11. QwtPlot::drawCanvasItems added. In opposite to QwtPlot::drawCanvas it is used by the printing code too. 12. qwtMax, qwtMin, qwtInt mapped to QMAX, QMIN, qRound from qglobal.h 13. operator= for plot item classes changed. 14. readOnly property added for sliders. 15. valid flag added for QwtDblRange 16. QwtCounter wrap around policy: a counter under- or overflow sets focus to the smallest up/down button and disables counting. A space bar keypress release event re-enables counting. 17. QwtPushButton added. A class that adds rich text and alignments features to QPushButton, like they are used in QLabel 18. Clipped painting code moved from QwtCurve to QwtPainter/QwtRect 19. Canvas cache added to optimize trivial repaints. 20. QwtPlot::drawCurve added for incremental curve data 21. QwtSliderBase, readOnly, isValid added 22. Added filtering of the colors of the title and scales to QwtPrintFilter. 23. Support of QT_NO_CAST_ASII and QT_NO_COMPAT added 24. Batch file added for generating Visual Studio project files 25. QwtPlotCurve, QwtPlotMarker, QwtPlotGrid: more methods public 26. QwtPlot::setLegendPosition added 27. A lot of changes I don't remember, ... sorry. Bugfixes: --------- 1. Autodetection of painter redirection. QPixmap::grabWidget() works with Qwt Widgets again. 2. QwtSlider: Rounding double->int conversions instead of simple casts. 3. Bad additional line, connected to the first curve point, when zooming deep, fixed. 4. QwtMarker: Painting of symbols with width != height fixed 5. QwtPlot::plotMouseXXX/canvasMap pixel coordinates synced. Now both include the canvas frame. 6. Layout fixed for QwtScaleDraws without tick labels 8. Tab focus chains fixed, focus indications added. 9. Support QwtAutoScale::Inverted when autoScale is off also. 10. Keyboard control, focus indications added. 11. Improved QStyle awareness. 12. Printing of plots with disabled axes Examples -------- 1. New example linux/cpustat added. Runs also on non linux boxes with dummy values. Beside showing a couple of features that are new with 0.4.1 and 0.4.2, it shows how to extend and customize a QwtPlots. 2. Added new example event_filter to demonstrate event filtering. This example shows how to add additional controls to the scales, how to translate mouse clicks on the scales into signals and how to move points on the canvas. 3. realtime example shows how to use scrollbars when zooming Release 0.4.1 ============ Changes: --------- 1. Platform independent project files. makefiles directory removed. 2. RPM spec file template added. 3. __declspec formalism added for Win32 DLLs. Requires 'DEFINES += QWT_DLL' in the .pro file. 4. QString used for visible texts. 5. Code for error curves removed. These type of features should be implemented in derived curve classes. 6. A lot of Qt 1.2 related code removed/replaced. 7. QwtColorFilter, QwtPixFrame removed. QwtPlotPixFrame renamed to QwtPlotCanvas. 8. qmodules.h aware. Skips QwtLegend in case of !QT_MODULE_TABLE 9. All Widgets including QwtPlot optimized to reduce flicker during resize/repaint. 10. QwtPlot curves/markers can be disabled/enabled to hide/show individual curves without removing the curves from the plot. 11. Internal maps removed from QwtCurve. QwtCurve::setMap, QwtCurve::setRect, QwtCurve::setRange removed. Feature additions: ------------------ 1. Printing QwtPlot::print prints to any type of QPaintDevice now. Hardcoded printer attributes margin, creator and document title have been removed and must/can be set by the applications now. Printing of background and legends added. QwtColorFilter replaced by QwtPlotPrintFilter. 2. Layout Many layout fixes and additions. Now all Widgets behave well in QLayouts and provide sensible sizeHints. QwtPlot::setMargin(int) added. Fieldwidth added for QwtPlot::setAxisFormat for application that need range independent width. Title and axis title are Qt:Alignment aware. Qt::WordBreak or multiline titles are possible. 3. Legend En/Disabling of single curves in the legend added. QwtPlot::setAutoLegend added. 4. Extensibility QwtPlot::insertCurve + QwtPlot::insertMarker added. Now derived classes of QwtPlotCurve and QwtPlotMarker can be added. Virtual methods provided by QwtPlotCurve for sub-classing. QwtScale::setScaleDraw + QwtPlot::setAxisScaleDraw + some virtual methods for QwtScaleDraw added. Application can implement individual axis labels now. 5. Sliders QWheelEvent added. The MouseWheel stepsize is controlled by the Scroll Page Size. QwtWheel::setWheelWidth added. QwtKnob::setSymbol, QwtKnob::symbol added. Bugfixes: --------- 1. Workaround for spontanous curves resulting from overruns when zooming too deep. 2. Wrong QColorGroup::ColorRole for background colors fixed. Necessary for several non default QStyles. 3. QwtWheel fixed for vertical wheels. Better color support. 4. QwtSlider fixed. 5. Many forgotten others Release 0.4.0 ============ Bugfixes: --------- 1. A few occurences of the boolean literal \c false were changed into macro \c FALSE for cross compiler compatibility. 2. A few local variables in member functions were renamed to suppress warnings issued by really picky compilers about global/class variables being hidden. 3. In qwt_legend.h, a fully qualified name was used in a class declaration. The HPUX compiler chokes on this (and it's ugly), so it was fixed. 4. Macro M_2PI is now only defined is this hasn't already been done by the system's clib. Feature additions: ------------------ 1. Qwt now works with Qt3.0. In order to achieve this, QwtLegend now no longer derives from QTableView, but from QTable. This seems to have had quite a few consequences. Kudo's to Uwe Rathmann for uploading this nice fix to the CVS tree. 2. Getters for a plot's title and title font have been added. Release 0.3.0 ============ License: -------- 1. The license has changed from GPL to LGPL. Bugfixes: --------- 1. The makefiles for win32 caused object files to have extension .o instead of .obj. The 'propagate' file was changed to fix this, using tmake's target platform flag. 2. There were problems with rint() on win32 platforms. rint() is a BSD call, not even available on all unices. All calls to rint(x) have been replaced by floor(x+.5). 3. Some static class data members were initialized with the value of other static class data members (from Qt). This caused programs depend on the initialization order of class members. This is now fixed by replacing the static properties by static signleton factories. 4. When a plot was zoomed and then printed, curves and markers laying outside the plot's scale were still printed. The print() function now uses clipping. Feature additions: ------------------ 1. Multi-line plot titles are now supported: the PostScript document name is not the plot title, with "\n" characters replaced by "--". Geometry management has been changed to support multi-line titles. 2. In the mailinglist, there were often feature requests for features that were in fact implemented, but not available through QwtPlot's API. Many private members have been made protected or even public, to give users more control. This is poor design, but Qwt will be refactored anyway. 3. Qwt always displayed floats with 5 digits. This was insufficient for many applications. QwtPlot, QwtScale, QwtAutoScale got some methods to set the label format. This is a printf like format for the numbers at the scales, consisting of 'f' and a precision, or 'g' and the significance. Build system: ------------- 1. The 'makefiles' directory was removed from the cvs tree, and is now only generated for releases. CVS users should have tmake installed, to generate the makefiles themselves. 2. The 'examples' directory now uses tmake's 'subdirs' template, to iterate over all subdirectories and build all examples with one command. There was allready a makefile for this, but now the process is automated by tmake. 3. Under unix, the library now gets a proper version number. Current version is 0.3.0. Documentation: -------------- 1. All documentation is converted to the Doxygen documentation system. The release contains two settings files, 'Doxygen' and 'Doxygen.users', generating a developer's and user's manual, respectively. workbench-1.1.1/src/Qwt/CMakeLists.txt000066400000000000000000000075441255417355300176220ustar00rootroot00000000000000################################################################ # Qwt Widget Library # Copyright (C) 1997 Josef Wilgen # Copyright (C) 2002 Uwe Rathmann # # This library is free software; you can redistribute it and/or # modify it under the terms of the Qwt License, Version 1.0 ################################################################ # # Name of project # PROJECT (Qwt) # # QT include files # INCLUDE(${QT_USE_FILE}) # # Headers that must be processed with QT's "moc" # Any class that derives from a QT class and contains # the "Q_OBJECT" macro must be listed here. # # Also LIST each of these headers in the # source files section so that they show # up in development tools such as XCode. # SET(MOC_INPUT_HEADER_FILES qwt_dyngrid_layout.h qwt_legend.h qwt_legend_item.h qwt_magnifier.h qwt_panner.h qwt_picker.h qwt_plot.h qwt_plot_canvas.h qwt_plot_magnifier.h qwt_plot_panner.h qwt_plot_picker.h qwt_plot_renderer.h qwt_plot_zoomer.h qwt_sampling_thread.h qwt_scale_widget.h qwt_text_label.h ) # Header files # # If the header file is qt 'moc' file # also list it in the section above # named MOC_INPUT_HEADER_FILES # SET(SOURCE_FILES qwt.h qwt_abstract_scale_draw.h qwt_clipper.h qwt_color_map.h qwt_column_symbol.h qwt_compat.h qwt_curve_fitter.h qwt_dyngrid_layout.h qwt_event_pattern.h qwt_global.h qwt_interval.h qwt_interval_symbol.h qwt_legend.h qwt_legend_item.h qwt_legend_itemmanager.h qwt_magnifier.h qwt_math.h qwt_matrix_raster_data.h qwt_null_paintdevice.h qwt_painter.h qwt_panner.h qwt_picker.h qwt_picker_machine.h qwt_plot.h qwt_plot_canvas.h qwt_plot_curve.h qwt_plot_dict.h qwt_plot_directpainter.h qwt_plot_grid.h qwt_plot_histogram.h qwt_plot_intervalcurve.h qwt_plot_item.h qwt_plot_layout.h qwt_plot_magnifier.h qwt_plot_marker.h qwt_plot_panner.h qwt_plot_picker.h qwt_plot_rasteritem.h qwt_plot_renderer.h qwt_plot_rescaler.h qwt_plot_scaleitem.h qwt_plot_seriesitem.h qwt_plot_spectrocurve.h qwt_plot_spectrogram.h qwt_plot_zoomer.h qwt_point_3d.h qwt_point_polar.h qwt_raster_data.h qwt_round_scale_draw.h qwt_sampling_thread.h qwt_scale_div.h qwt_scale_draw.h qwt_scale_engine.h qwt_scale_map.h qwt_scale_widget.h qwt_series_data.h qwt_spline.h qwt_symbol.h qwt_system_clock.h qwt_text.h qwt_text_engine.h qwt_text_label.h qwt_abstract_scale_draw.cpp qwt_clipper.cpp qwt_color_map.cpp qwt_column_symbol.cpp qwt_curve_fitter.cpp qwt_dyngrid_layout.cpp qwt_event_pattern.cpp qwt_interval.cpp qwt_interval_symbol.cpp qwt_legend.cpp qwt_legend_item.cpp qwt_magnifier.cpp qwt_math.cpp qwt_matrix_raster_data.cpp qwt_null_paintdevice.cpp qwt_painter.cpp qwt_panner.cpp qwt_picker.cpp qwt_picker_machine.cpp qwt_plot.cpp qwt_plot_axis.cpp qwt_plot_canvas.cpp qwt_plot_curve.cpp qwt_plot_dict.cpp qwt_plot_directpainter.cpp qwt_plot_grid.cpp qwt_plot_histogram.cpp qwt_plot_intervalcurve.cpp qwt_plot_item.cpp qwt_plot_layout.cpp qwt_plot_magnifier.cpp qwt_plot_marker.cpp qwt_plot_panner.cpp qwt_plot_picker.cpp qwt_plot_rasteritem.cpp qwt_plot_renderer.cpp qwt_plot_rescaler.cpp qwt_plot_scaleitem.cpp qwt_plot_seriesitem.cpp qwt_plot_spectrocurve.cpp qwt_plot_spectrogram.cpp qwt_plot_xml.cpp qwt_plot_zoomer.cpp qwt_point_3d.cpp qwt_point_polar.cpp qwt_raster_data.cpp qwt_round_scale_draw.cpp qwt_sampling_thread.cpp qwt_scale_div.cpp qwt_scale_draw.cpp qwt_scale_engine.cpp qwt_scale_map.cpp qwt_scale_widget.cpp qwt_series_data.cpp qwt_spline.cpp qwt_symbol.cpp qwt_system_clock.cpp qwt_text.cpp qwt_text_engine.cpp qwt_text_label.cpp ) # # Process the header files with moc producing moc_*.cpp files # INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}) QT4_WRAP_CPP(MOC_SOURCE_FILES ${MOC_INPUT_HEADER_FILES}) # # Create the GUI library # ADD_LIBRARY(Qwt ${SOURCE_FILES} ${MOC_SOURCE_FILES} ) workbench-1.1.1/src/Qwt/COPYING000066400000000000000000000664521255417355300161200ustar00rootroot00000000000000 Qwt License Version 1.0, January 1, 2003 The Qwt library and included programs are provided under the terms of the GNU LESSER GENERAL PUBLIC LICENSE (LGPL) with the following exceptions: 1. Widgets that are subclassed from Qwt widgets do not constitute a derivative work. 2. Static linking of applications and widgets to the Qwt library does not constitute a derivative work and does not require the author to provide source code for the application or widget, use the shared Qwt libraries, or link their applications or widgets against a user-supplied version of Qwt. If you link the application or widget to a modified version of Qwt, then the changes to Qwt must be provided under the terms of the LGPL in sections 1, 2, and 4. 3. You do not have to provide a copy of the Qwt license with programs that are linked to the Qwt library, nor do you have to identify the Qwt license in your program or documentation as required by section 6 of the LGPL. However, programs must still identify their use of Qwt. The following example statement can be included in user documentation to satisfy this requirement: [program/widget] is based in part on the work of the Qwt project (http://qwt.sf.net). ---------------------------------------------------------------------- GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, 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 and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, 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 library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete 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 distribute a copy of this License along with the Library. 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 Library or any portion of it, thus forming a work based on the Library, 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) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, 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 Library, 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 Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you 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. If distribution of 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 satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be 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. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library 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. 9. 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 Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library 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 with this License. 11. 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 Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library 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 Library. 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. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library 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. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser 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 Library 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 Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, 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 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "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 LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. 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 LIBRARY 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 LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), 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 Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. 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 library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! workbench-1.1.1/src/Qwt/INSTALL000066400000000000000000000116701255417355300161060ustar00rootroot00000000000000Introduction ============ Qwt uses qmake to build all its components and examples. qmake is part of a Qt distribution. qmake reads project files, that contain the options and rules how to build a certain project. A project file ends with the suffix "*.pro". Files that end with the suffix "*.pri" are included by the project files and contain definitions, that are common for several project files. qwtconfig.pri and qwtbuild.pri are read by all project files of the Qwt package. qwtconfig.pri is also read by qwt.prf, when building your application. So the first step is to edit the *.pri files to adjust them to your needs. The subdirs template of qmake is known to be buggy when using spaces in path names. So better don't build Qwt below a path name with spaces. ( Otherwise you might have to run qmake in all subdirs manually ). Documentation ========================== Qwt includes a class documentation, that is available in various formats: - Html files - PDF document - Qt Compressed Help (*.qch ) for the Qt assistant or creator. You can load it "Edit Preferences" -> "Documentation" -> "Add..." - Man pages ( UNIX only ) Building Qwt ========================== The easiest way to build Qwt is from the command line - but you insist on using an IDE don't forget the "make install" step. A) Unix -------- qmake make make install If you have installed a shared library it's path has to be known to the run-time linker of your operating system. On Linux systems read "man ldconfig" ( or google for it ). Another option is to use the LD_LIBRARY_PATH (on some systems LIBPATH is used instead, on MacOSX it is called DYLD_LIBRARY_PATH) environment variable. If you only want to check the Qwt examples without installing something, you can set the LD_LIBRARY_PATH to the lib directory of your local build. If you didn't enable autobuilding of the examples in qwtconfig.pri you have to build the examples this way: cd examples qmake make B) Win32/MSVC -------- Check that your Qt version has been built with MSVC - not with MinGW ! Please read the qmake documentation how to convert your *.pro files into your development environment. F.e MSVC with nmake: qmake qwt.pro nmake nmake install If you didn't enable autobuilding of the examples in qwtconfig.pri you have to build the examples this way: cd examples qmake examples.pro nmake Windows doesn't like mixing of debug and release binaries. Most of the problems with using the Qwt designer plugin are because of trying to load a Qwt debug library into a designer release executable. It's not possible to load a plugin, that has been built with MinGW into a Qt Designer/Creator, that has been built with MSVC ( and v.v ). This is a common reason for problems, when working with prebuild binaries of the Qt Creator. C) Win32/MinGW -------- Check that your Qt version has been built with MinGW - not with MSVC ! Start a Shell, where Qt4 is initialized. ( F.e. with "Programs->Qt by Trolltech ...->Qt 4.x.x Command Prompt" ). Check if you can execute "make" or something like "mingw32-make". qmake qwt.pro make make install If you didn't enable autobuilding of the examples in qwtconfig.pri you have to build the examples this way: cd examples qmake examples.pro make Windows doesn't like mixing of debug and release binaries. Most of the problems with using the Qwt designer plugin are because of trying to load a Qwt debug library into a designer release executable. Don't forget to tell qmake where to find qwt.prf: qmake -set QMAKEFEATURES ... D) MacOSX -------- Well, the Mac is only another Unix system. So read the instructions in A). In the recent Qt4 releases the default target of qmake is to generate XCode project files instead of makefiles. So you might need to do the following: qmake -spec macx-g++ ... D) Qt Embedded -------- I only tested Qwt with Qt Embedded in qvfb (Virtual Framebuffer Devivce) Emulator on my Linux box. To build Qwt for the emulator was as simple as for a regular Unix build. F) Symbian -------- I never tried this platform myself. Using Qwt =========== For building a Qwt application with qmake use the Qwt configuration features file, that has been installed by "make install". When qmake is able to find it ( http://doc.qt.nokia.com/4.7/qmake-advanced-usage.html#adding-new-configuration-features ) you can simply add "CONFIG += qwt" to your application project file. If you don't use qmake you have to add the include path to find the Qwt headers to your compiler flags and the Qwt library to your linker list. Don't forget to add QWT_DLL to the compiler flags, when you work with a Qwt-DLLs on Windows. For using the designer plugin you have to configure the Qt designer/creator where to look for plugins. This can be done by setting the QT_PLUGIN_PATH or using a qt.conf file ( see http://doc.qt.nokia.com/4.7/deployment-plugins.html ). Beside the plugin the Qwt library itsself also needs to be known to the Designer/Creator ( see LD_LIBRARY_PATH, PATH ... above ). Good luck ! workbench-1.1.1/src/Qwt/README000066400000000000000000000016271255417355300157360ustar00rootroot00000000000000 The Qwt Widget Library ---------------------- Qwt is an extension to the Qt GUI library from Troll Tech AS. The Qwt library contains widgets and components which are primarily useful for technical and scientifical purposes. It includes a 2-D plotting widget, different kinds of sliders, and much more. Qwt is hosted at http://qwt.sf.net Installation ------------ Read INSTALL how to build and install Qwt. Copyright --------- Qwt Widget Library Copyright (C) 1997 Josef Wilgen Copyright (C) 2002 Uwe Rathmann Qwt is published under the Qwt License, Version 1.0. You should have received a copy of this licence in the file COPYING. This library 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. workbench-1.1.1/src/Qwt/README_MODIFY_QWT_FOR_WORKBENCH000066400000000000000000000027651255417355300216540ustar00rootroot00000000000000Setting up QWT for use with workbench In QWT's 'src' directory: Examine src.pro A configuration item (QWT_CONFIG) allow selective building of items. There are some HEADERS and SOURCES that we need. These are the HEADERS and SOURCES not controlled with QWT_CONFIG, and QwtPlot. QwtSvg and QwtWidgets are NOT needed to copy part of the src.pro file to create a command for deleting these unneeded files. Something like this: #!/bin/sh rm \ qwt_plot_svgitem.h \ qwt_plot_svgitem.cpp rm \ qwt_abstract_slider.h \ qwt_abstract_scale.h \ qwt_arrow_button.h \ qwt_analog_clock.h \ qwt_compass.h \ qwt_compass_rose.h \ qwt_counter.h \ qwt_dial.h \ qwt_dial_needle.h \ qwt_double_range.h \ qwt_knob.h \ qwt_slider.h \ qwt_thermo.h \ qwt_wheel.h rm \ qwt_abstract_slider.cpp \ qwt_abstract_scale.cpp \ qwt_arrow_button.cpp \ qwt_analog_clock.cpp \ qwt_compass.cpp \ qwt_compass_rose.cpp \ qwt_counter.cpp \ qwt_dial.cpp \ qwt_dial_needle.cpp \ qwt_double_range.cpp \ qwt_knob.cpp \ qwt_slider.cpp \ qwt_thermo.cpp \ qwt_wheel.cpp Next, copy the src.pro to CMakeLists.txt. Run "grep --files-with-matches Q_OBJECT *.h" to find the files that need to go in the MOC_INPUT_HEADER_FILES section of the CMakeLists.txt file. Next place all headers and CPP files into the SOURCE_FILES section. workbench-1.1.1/src/Qwt/qwt.h000066400000000000000000000010251255417355300160320ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #ifndef QWT_H #define QWT_H #include "qwt_global.h" /*! Some constants for use within Qwt. */ namespace Qwt { }; #endif workbench-1.1.1/src/Qwt/qwt_abstract_scale_draw.cpp000066400000000000000000000234321255417355300224420ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #include "qwt_abstract_scale_draw.h" #include "qwt_math.h" #include "qwt_text.h" #include "qwt_painter.h" #include "qwt_scale_map.h" #include #include #include #include class QwtAbstractScaleDraw::PrivateData { public: PrivateData(): spacing( 4.0 ), penWidth( 0 ), minExtent( 0.0 ) { components = QwtAbstractScaleDraw::Backbone | QwtAbstractScaleDraw::Ticks | QwtAbstractScaleDraw::Labels; tickLength[QwtScaleDiv::MinorTick] = 4.0; tickLength[QwtScaleDiv::MediumTick] = 6.0; tickLength[QwtScaleDiv::MajorTick] = 8.0; } ScaleComponents components; QwtScaleMap map; QwtScaleDiv scldiv; double spacing; double tickLength[QwtScaleDiv::NTickTypes]; int penWidth; double minExtent; QMap labelCache; }; /*! \brief Constructor The range of the scale is initialized to [0, 100], The spacing (distance between ticks and labels) is set to 4, the tick lengths are set to 4,6 and 8 pixels */ QwtAbstractScaleDraw::QwtAbstractScaleDraw() { d_data = new QwtAbstractScaleDraw::PrivateData; } //! Destructor QwtAbstractScaleDraw::~QwtAbstractScaleDraw() { delete d_data; } /*! En/Disable a component of the scale \param component Scale component \param enable On/Off \sa hasComponent() */ void QwtAbstractScaleDraw::enableComponent( ScaleComponent component, bool enable ) { if ( enable ) d_data->components |= component; else d_data->components &= ~component; } /*! Check if a component is enabled \sa enableComponent() */ bool QwtAbstractScaleDraw::hasComponent( ScaleComponent component ) const { return ( d_data->components & component ); } /*! Change the scale division \param sd New scale division */ void QwtAbstractScaleDraw::setScaleDiv( const QwtScaleDiv &sd ) { d_data->scldiv = sd; d_data->map.setScaleInterval( sd.lowerBound(), sd.upperBound() ); d_data->labelCache.clear(); } /*! Change the transformation of the scale \param transformation New scale transformation */ void QwtAbstractScaleDraw::setTransformation( QwtScaleTransformation *transformation ) { d_data->map.setTransformation( transformation ); } //! \return Map how to translate between scale and pixel values const QwtScaleMap &QwtAbstractScaleDraw::scaleMap() const { return d_data->map; } //! \return Map how to translate between scale and pixel values QwtScaleMap &QwtAbstractScaleDraw::scaleMap() { return d_data->map; } //! \return scale division const QwtScaleDiv& QwtAbstractScaleDraw::scaleDiv() const { return d_data->scldiv; } /*! \brief Specify the width of the scale pen \param width Pen width \sa penWidth() */ void QwtAbstractScaleDraw::setPenWidth( int width ) { if ( width < 0 ) width = 0; if ( width != d_data->penWidth ) d_data->penWidth = width; } /*! \return Scale pen width \sa setPenWidth() */ int QwtAbstractScaleDraw::penWidth() const { return d_data->penWidth; } /*! \brief Draw the scale \param painter The painter \param palette Palette, text color is used for the labels, foreground color for ticks and backbone */ void QwtAbstractScaleDraw::draw( QPainter *painter, const QPalette& palette ) const { painter->save(); QPen pen = painter->pen(); pen.setWidth( d_data->penWidth ); pen.setCosmetic( false ); painter->setPen( pen ); if ( hasComponent( QwtAbstractScaleDraw::Labels ) ) { painter->save(); painter->setPen( palette.color( QPalette::Text ) ); // ignore pen style const QList &majorTicks = d_data->scldiv.ticks( QwtScaleDiv::MajorTick ); for ( int i = 0; i < majorTicks.count(); i++ ) { const double v = majorTicks[i]; if ( d_data->scldiv.contains( v ) ) drawLabel( painter, majorTicks[i] ); } painter->restore(); } if ( hasComponent( QwtAbstractScaleDraw::Ticks ) ) { painter->save(); QPen pen = painter->pen(); pen.setColor( palette.color( QPalette::WindowText ) ); pen.setCapStyle( Qt::FlatCap ); painter->setPen( pen ); for ( int tickType = QwtScaleDiv::MinorTick; tickType < QwtScaleDiv::NTickTypes; tickType++ ) { const QList &ticks = d_data->scldiv.ticks( tickType ); for ( int i = 0; i < ticks.count(); i++ ) { const double v = ticks[i]; if ( d_data->scldiv.contains( v ) ) drawTick( painter, v, d_data->tickLength[tickType] ); } } painter->restore(); } if ( hasComponent( QwtAbstractScaleDraw::Backbone ) ) { painter->save(); QPen pen = painter->pen(); pen.setColor( palette.color( QPalette::WindowText ) ); pen.setCapStyle( Qt::FlatCap ); painter->setPen( pen ); drawBackbone( painter ); painter->restore(); } painter->restore(); } /*! \brief Set the spacing between tick and labels The spacing is the distance between ticks and labels. The default spacing is 4 pixels. \param spacing Spacing \sa spacing() */ void QwtAbstractScaleDraw::setSpacing( double spacing ) { if ( spacing < 0 ) spacing = 0; d_data->spacing = spacing; } /*! \brief Get the spacing The spacing is the distance between ticks and labels. The default spacing is 4 pixels. \sa setSpacing() */ double QwtAbstractScaleDraw::spacing() const { return d_data->spacing; } /*! \brief Set a minimum for the extent The extent is calculated from the coomponents of the scale draw. In situations, where the labels are changing and the layout depends on the extent (f.e scrolling a scale), setting an upper limit as minimum extent will avoid jumps of the layout. \param minExtent Minimum extent \sa extent(), minimumExtent() */ void QwtAbstractScaleDraw::setMinimumExtent( double minExtent ) { if ( minExtent < 0.0 ) minExtent = 0.0; d_data->minExtent = minExtent; } /*! Get the minimum extent \sa extent(), setMinimumExtent() */ double QwtAbstractScaleDraw::minimumExtent() const { return d_data->minExtent; } /*! Set the length of the ticks \param tickType Tick type \param length New length \warning the length is limited to [0..1000] */ void QwtAbstractScaleDraw::setTickLength( QwtScaleDiv::TickType tickType, double length ) { if ( tickType < QwtScaleDiv::MinorTick || tickType > QwtScaleDiv::MajorTick ) { return; } if ( length < 0.0 ) length = 0.0; const double maxTickLen = 1000.0; if ( length > maxTickLen ) length = maxTickLen; d_data->tickLength[tickType] = length; } /*! Return the length of the ticks \sa setTickLength(), maxTickLength() */ double QwtAbstractScaleDraw::tickLength( QwtScaleDiv::TickType tickType ) const { if ( tickType < QwtScaleDiv::MinorTick || tickType > QwtScaleDiv::MajorTick ) { return 0; } return d_data->tickLength[tickType]; } /*! \return Length of the longest tick Useful for layout calculations \sa tickLength(), setTickLength() */ double QwtAbstractScaleDraw::maxTickLength() const { double length = 0.0; for ( int i = 0; i < QwtScaleDiv::NTickTypes; i++ ) length = qMax( length, d_data->tickLength[i] ); return length; } /*! \brief Convert a value into its representing label The value is converted to a plain text using QLocale::system().toString(value). This method is often overloaded by applications to have individual labels. \param value Value \return Label string. */ QwtText QwtAbstractScaleDraw::label( double value ) const { return QLocale().toString( value ); } /*! \brief Convert a value into its representing label and cache it. The conversion between value and label is called very often in the layout and painting code. Unfortunately the calculation of the label sizes might be slow (really slow for rich text in Qt4), so it's necessary to cache the labels. \param font Font \param value Value \return Tick label */ const QwtText &QwtAbstractScaleDraw::tickLabel( const QFont &font, double value ) const { QMap::const_iterator it = d_data->labelCache.find( value ); if ( it == d_data->labelCache.end() ) { QwtText lbl = label( value ); lbl.setRenderFlags( 0 ); lbl.setLayoutAttribute( QwtText::MinimumLayout ); ( void )lbl.textSize( font ); // initialize the internal cache it = d_data->labelCache.insert( value, lbl ); } return ( *it ); } /*! Invalidate the cache used by QwtAbstractScaleDraw::tickLabel The cache is invalidated, when a new QwtScaleDiv is set. If the labels need to be changed. while the same QwtScaleDiv is set, invalidateCache() needs to be called manually. */ void QwtAbstractScaleDraw::invalidateCache() { d_data->labelCache.clear(); } workbench-1.1.1/src/Qwt/qwt_abstract_scale_draw.h000066400000000000000000000072441255417355300221120ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #ifndef QWT_ABSTRACT_SCALE_DRAW_H #define QWT_ABSTRACT_SCALE_DRAW_H #include "qwt_global.h" #include "qwt_scale_div.h" #include "qwt_text.h" class QPalette; class QPainter; class QFont; class QwtScaleTransformation; class QwtScaleMap; /*! \brief A abstract base class for drawing scales QwtAbstractScaleDraw can be used to draw linear or logarithmic scales. After a scale division has been specified as a QwtScaleDiv object using QwtAbstractScaleDraw::setScaleDiv(const QwtScaleDiv &s), the scale can be drawn with the QwtAbstractScaleDraw::draw() member. */ class QWT_EXPORT QwtAbstractScaleDraw { public: /*! Components of a scale \sa enableComponent(), hasComponent */ enum ScaleComponent { //! Backbone = the line where the ticks are located Backbone = 0x01, //! Ticks Ticks = 0x02, //! Labels Labels = 0x04 }; //! Scale components typedef QFlags ScaleComponents; QwtAbstractScaleDraw(); virtual ~QwtAbstractScaleDraw(); void setScaleDiv( const QwtScaleDiv &s ); const QwtScaleDiv& scaleDiv() const; void setTransformation( QwtScaleTransformation * ); const QwtScaleMap &scaleMap() const; QwtScaleMap &scaleMap(); void enableComponent( ScaleComponent, bool enable = true ); bool hasComponent( ScaleComponent ) const; void setTickLength( QwtScaleDiv::TickType, double length ); double tickLength( QwtScaleDiv::TickType ) const; double maxTickLength() const; void setSpacing( double margin ); double spacing() const; void setPenWidth( int width ); int penWidth() const; virtual void draw( QPainter *, const QPalette & ) const; virtual QwtText label( double ) const; /*! Calculate the extent The extent is the distcance from the baseline to the outermost pixel of the scale draw in opposite to its orientation. It is at least minimumExtent() pixels. \sa setMinimumExtent(), minimumExtent() */ virtual double extent( const QFont & ) const = 0; void setMinimumExtent( double ); double minimumExtent() const; protected: /*! Draw a tick \param painter Painter \param value Value of the tick \param len Lenght of the tick \sa drawBackbone(), drawLabel() */ virtual void drawTick( QPainter *painter, double value, double len ) const = 0; /*! Draws the baseline of the scale \param painter Painter \sa drawTick(), drawLabel() */ virtual void drawBackbone( QPainter *painter ) const = 0; /*! Draws the label for a major scale tick \param painter Painter \param value Value \sa drawTick(), drawBackbone() */ virtual void drawLabel( QPainter *painter, double value ) const = 0; void invalidateCache(); const QwtText &tickLabel( const QFont &, double value ) const; private: QwtAbstractScaleDraw( const QwtAbstractScaleDraw & ); QwtAbstractScaleDraw &operator=( const QwtAbstractScaleDraw & ); class PrivateData; PrivateData *d_data; }; Q_DECLARE_OPERATORS_FOR_FLAGS( QwtAbstractScaleDraw::ScaleComponents ) #endif workbench-1.1.1/src/Qwt/qwt_clipper.cpp000066400000000000000000000303431255417355300201100ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #include "qwt_clipper.h" #include "qwt_point_polar.h" #include #if QT_VERSION < 0x040601 #define qAtan(x) ::atan(x) #endif namespace QwtClip { // some templates used for inlining template class LeftEdge; template class RightEdge; template class TopEdge; template class BottomEdge; template class PointBuffer; } template class QwtClip::LeftEdge { public: inline LeftEdge( Value x1, Value, Value, Value ): d_x1( x1 ) { } inline bool isInside( const Point &p ) const { return p.x() >= d_x1; } inline Point intersection( const Point &p1, const Point &p2 ) const { double dy = ( p1.y() - p2.y() ) / double( p1.x() - p2.x() ); return Point( d_x1, ( Value ) ( p2.y() + ( d_x1 - p2.x() ) * dy ) ); } private: const Value d_x1; }; template class QwtClip::RightEdge { public: inline RightEdge( Value, Value x2, Value, Value ): d_x2( x2 ) { } inline bool isInside( const Point &p ) const { return p.x() <= d_x2; } inline Point intersection( const Point &p1, const Point &p2 ) const { double dy = ( p1.y() - p2.y() ) / double( p1.x() - p2.x() ); return Point( d_x2, ( Value ) ( p2.y() + ( d_x2 - p2.x() ) * dy ) ); } private: const Value d_x2; }; template class QwtClip::TopEdge { public: inline TopEdge( Value, Value, Value y1, Value ): d_y1( y1 ) { } inline bool isInside( const Point &p ) const { return p.y() >= d_y1; } inline Point intersection( const Point &p1, const Point &p2 ) const { double dx = ( p1.x() - p2.x() ) / double( p1.y() - p2.y() ); return Point( ( Value )( p2.x() + ( d_y1 - p2.y() ) * dx ), d_y1 ); } private: const Value d_y1; }; template class QwtClip::BottomEdge { public: inline BottomEdge( Value, Value, Value, Value y2 ): d_y2( y2 ) { } inline bool isInside( const Point &p ) const { return p.y() <= d_y2; } inline Point intersection( const Point &p1, const Point &p2 ) const { double dx = ( p1.x() - p2.x() ) / double( p1.y() - p2.y() ); return Point( ( Value )( p2.x() + ( d_y2 - p2.y() ) * dx ), d_y2 ); } private: const Value d_y2; }; template class QwtClip::PointBuffer { public: PointBuffer( int capacity = 0 ): m_capacity( 0 ), m_size( 0 ), m_buffer( NULL ) { if ( capacity > 0 ) reserve( capacity ); } ~PointBuffer() { if ( m_buffer ) qFree( m_buffer ); } inline void setPoints( int numPoints, const Point *points ) { reserve( numPoints ); m_size = numPoints; qMemCopy( m_buffer, points, m_size * sizeof( Point ) ); } inline void reset() { m_size = 0; } inline int size() const { return m_size; } inline Point *data() const { return m_buffer; } inline Point &operator[]( int i ) { return m_buffer[i]; } inline const Point &operator[]( int i ) const { return m_buffer[i]; } inline void add( const Point &point ) { if ( m_capacity <= m_size ) reserve( m_size + 1 ); m_buffer[m_size++] = point; } private: inline void reserve( int size ) { if ( m_capacity == 0 ) m_capacity = 1; while ( m_capacity < size ) m_capacity *= 2; m_buffer = ( Point * ) qRealloc( m_buffer, m_capacity * sizeof( Point ) ); } int m_capacity; int m_size; Point *m_buffer; }; using namespace QwtClip; template class QwtPolygonClipper { public: QwtPolygonClipper( const Rect &clipRect ): d_clipRect( clipRect ) { } Polygon clipPolygon( const Polygon &polygon, bool closePolygon ) const { #if 0 if ( d_clipRect.contains( polygon.boundingRect() ) ) return polygon; #endif PointBuffer points1; PointBuffer points2( qMin( 256, polygon.size() ) ); points1.setPoints( polygon.size(), polygon.data() ); clipEdge< LeftEdge >( closePolygon, points1, points2 ); clipEdge< RightEdge >( closePolygon, points2, points1 ); clipEdge< TopEdge >( closePolygon, points1, points2 ); clipEdge< BottomEdge >( closePolygon, points2, points1 ); Polygon p; p.resize( points1.size() ); qMemCopy( p.data(), points1.data(), points1.size() * sizeof( Point ) ); return p; } private: template inline void clipEdge( bool closePolygon, PointBuffer &points, PointBuffer &clippedPoints ) const { clippedPoints.reset(); if ( points.size() < 2 ) { if ( points.size() == 1 ) clippedPoints.add( points[0] ); return; } const Edge edge( d_clipRect.x(), d_clipRect.x() + d_clipRect.width(), d_clipRect.y(), d_clipRect.y() + d_clipRect.height() ); int lastPos, start; if ( closePolygon ) { start = 0; lastPos = points.size() - 1; } else { start = 1; lastPos = 0; if ( edge.isInside( points[0] ) ) clippedPoints.add( points[0] ); } const uint nPoints = points.size(); for ( uint i = start; i < nPoints; i++ ) { const Point &p1 = points[i]; const Point &p2 = points[lastPos]; if ( edge.isInside( p1 ) ) { if ( edge.isInside( p2 ) ) { clippedPoints.add( p1 ); } else { clippedPoints.add( edge.intersection( p1, p2 ) ); clippedPoints.add( p1 ); } } else { if ( edge.isInside( p2 ) ) { clippedPoints.add( edge.intersection( p1, p2 ) ); } } lastPos = i; } } const Rect d_clipRect; }; class QwtCircleClipper { public: QwtCircleClipper( const QRectF &r ); QVector clipCircle( const QPointF &, double radius ) const; private: enum Edge { Left, Top, Right, Bottom, NEdges }; QList cuttingPoints( Edge, const QPointF &pos, double radius ) const; double toAngle( const QPointF &, const QPointF & ) const; const QRectF d_rect; }; QwtCircleClipper::QwtCircleClipper( const QRectF &r ): d_rect( r ) { } QVector QwtCircleClipper::clipCircle( const QPointF &pos, double radius ) const { QList points; for ( int edge = 0; edge < NEdges; edge++ ) points += cuttingPoints( ( Edge )edge, pos, radius ); QVector intv; if ( points.size() <= 0 ) { QRectF cRect( 0, 0, 2 * radius, 2 * radius ); cRect.moveCenter( pos ); if ( d_rect.contains( cRect ) ) intv += QwtInterval( 0.0, 2 * M_PI ); } else { QList angles; for ( int i = 0; i < points.size(); i++ ) angles += toAngle( pos, points[i] ); qSort( angles ); const int in = d_rect.contains( qwtPolar2Pos( pos, radius, angles[0] + ( angles[1] - angles[0] ) / 2 ) ); if ( in ) { for ( int i = 0; i < angles.size() - 1; i += 2 ) intv += QwtInterval( angles[i], angles[i+1] ); } else { for ( int i = 1; i < angles.size() - 1; i += 2 ) intv += QwtInterval( angles[i], angles[i+1] ); intv += QwtInterval( angles.last(), angles.first() ); } } return intv; } double QwtCircleClipper::toAngle( const QPointF &from, const QPointF &to ) const { if ( from.x() == to.x() ) return from.y() <= to.y() ? M_PI / 2.0 : 3 * M_PI / 2.0; const double m = qAbs( ( to.y() - from.y() ) / ( to.x() - from.x() ) ); double angle = qAtan( m ); if ( to.x() > from.x() ) { if ( to.y() > from.y() ) angle = 2 * M_PI - angle; } else { if ( to.y() > from.y() ) angle = M_PI + angle; else angle = M_PI - angle; } return angle; } QList QwtCircleClipper::cuttingPoints( Edge edge, const QPointF &pos, double radius ) const { QList points; if ( edge == Left || edge == Right ) { const double x = ( edge == Left ) ? d_rect.left() : d_rect.right(); if ( qAbs( pos.x() - x ) < radius ) { const double off = qSqrt( qwtSqr( radius ) - qwtSqr( pos.x() - x ) ); const double m_y1 = pos.y() + off; if ( m_y1 >= d_rect.top() && m_y1 <= d_rect.bottom() ) points += QPointF( x, m_y1 ); const double m_y2 = pos.y() - off; if ( m_y2 >= d_rect.top() && m_y2 <= d_rect.bottom() ) points += QPointF( x, m_y2 ); } } else { const double y = ( edge == Top ) ? d_rect.top() : d_rect.bottom(); if ( qAbs( pos.y() - y ) < radius ) { const double off = qSqrt( qwtSqr( radius ) - qwtSqr( pos.y() - y ) ); const double x1 = pos.x() + off; if ( x1 >= d_rect.left() && x1 <= d_rect.right() ) points += QPointF( x1, y ); const double m_x2 = pos.x() - off; if ( m_x2 >= d_rect.left() && m_x2 <= d_rect.right() ) points += QPointF( m_x2, y ); } } return points; } /*! Sutherland-Hodgman polygon clipping \param clipRect Clip rectangle \param polygon Polygon \param closePolygon True, when the polygon is closed \return Clipped polygon */ QPolygon QwtClipper::clipPolygon( const QRect &clipRect, const QPolygon &polygon, bool closePolygon ) { QwtPolygonClipper clipper( clipRect ); return clipper.clipPolygon( polygon, closePolygon ); } /*! Sutherland-Hodgman polygon clipping \param clipRect Clip rectangle \param polygon Polygon \param closePolygon True, when the polygon is closed \return Clipped polygon */ QPolygonF QwtClipper::clipPolygonF( const QRectF &clipRect, const QPolygonF &polygon, bool closePolygon ) { QwtPolygonClipper clipper( clipRect ); return clipper.clipPolygon( polygon, closePolygon ); } /*! Circle clipping clipCircle() devides a circle into intervals of angles representing arcs of the circle. When the circle is completely inside the clip rectangle an interval [0.0, 2 * M_PI] is returned. \param clipRect Clip rectangle \param center Center of the circle \param radius Radius of the circle \return Arcs of the circle */ QVector QwtClipper::clipCircle( const QRectF &clipRect, const QPointF ¢er, double radius ) { QwtCircleClipper clipper( clipRect ); return clipper.clipCircle( center, radius ); } workbench-1.1.1/src/Qwt/qwt_clipper.h000066400000000000000000000017371255417355300175620ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #ifndef QWT_CLIPPER_H #define QWT_CLIPPER_H #include "qwt_global.h" #include "qwt_interval.h" #include #include class QRect; class QRectF; /*! \brief Some clipping algos */ class QWT_EXPORT QwtClipper { public: static QPolygon clipPolygon( const QRect &, const QPolygon &, bool closePolygon = false ); static QPolygonF clipPolygonF( const QRectF &, const QPolygonF &, bool closePolygon = false ); static QVector clipCircle( const QRectF &, const QPointF &, double radius ); }; #endif workbench-1.1.1/src/Qwt/qwt_color_map.cpp000066400000000000000000000244571255417355300204360ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #include "qwt_color_map.h" #include "qwt_math.h" #include "qwt_interval.h" #include class QwtLinearColorMap::ColorStops { public: ColorStops() { _stops.reserve( 256 ); } void insert( double pos, const QColor &color ); QRgb rgb( QwtLinearColorMap::Mode, double pos ) const; QVector stops() const; private: class ColorStop { public: ColorStop(): pos( 0.0 ), rgb( 0 ) { }; ColorStop( double p, const QColor &c ): pos( p ), rgb( c.rgb() ) { r = qRed( rgb ); g = qGreen( rgb ); b = qBlue( rgb ); } double pos; QRgb rgb; int r, g, b; }; inline int findUpper( double pos ) const; QVector _stops; }; void QwtLinearColorMap::ColorStops::insert( double pos, const QColor &color ) { // Lookups need to be very fast, insertions are not so important. // Anyway, a balanced tree is what we need here. TODO ... if ( pos < 0.0 || pos > 1.0 ) return; int index; if ( _stops.size() == 0 ) { index = 0; _stops.resize( 1 ); } else { index = findUpper( pos ); if ( index == _stops.size() || qAbs( _stops[index].pos - pos ) >= 0.001 ) { _stops.resize( _stops.size() + 1 ); for ( int i = _stops.size() - 1; i > index; i-- ) _stops[i] = _stops[i-1]; } } _stops[index] = ColorStop( pos, color ); } inline QVector QwtLinearColorMap::ColorStops::stops() const { QVector positions( _stops.size() ); for ( int i = 0; i < _stops.size(); i++ ) positions[i] = _stops[i].pos; return positions; } inline int QwtLinearColorMap::ColorStops::findUpper( double pos ) const { int index = 0; int n = _stops.size(); const ColorStop *stops = _stops.data(); while ( n > 0 ) { const int half = n >> 1; const int middle = index + half; if ( stops[middle].pos <= pos ) { index = middle + 1; n -= half + 1; } else n = half; } return index; } inline QRgb QwtLinearColorMap::ColorStops::rgb( QwtLinearColorMap::Mode mode, double pos ) const { if ( pos <= 0.0 ) return _stops[0].rgb; if ( pos >= 1.0 ) return _stops[ _stops.size() - 1 ].rgb; const int index = findUpper( pos ); if ( mode == FixedColors ) { return _stops[index-1].rgb; } else { const ColorStop &s1 = _stops[index-1]; const ColorStop &s2 = _stops[index]; const double ratio = ( pos - s1.pos ) / ( s2.pos - s1.pos ); const int r = s1.r + qRound( ratio * ( s2.r - s1.r ) ); const int g = s1.g + qRound( ratio * ( s2.g - s1.g ) ); const int b = s1.b + qRound( ratio * ( s2.b - s1.b ) ); return qRgb( r, g, b ); } } //! Constructor QwtColorMap::QwtColorMap( Format format ): d_format( format ) { } //! Destructor QwtColorMap::~QwtColorMap() { } /*! Build and return a color map of 256 colors The color table is needed for rendering indexed images in combination with using colorIndex(). \param interval Range for the values \return A color table, that can be used for a QImage */ QVector QwtColorMap::colorTable( const QwtInterval &interval ) const { QVector table( 256 ); if ( interval.isValid() ) { const double step = interval.width() / ( table.size() - 1 ); for ( int i = 0; i < table.size(); i++ ) table[i] = rgb( interval, interval.minValue() + step * i ); } return table; } class QwtLinearColorMap::PrivateData { public: ColorStops colorStops; QwtLinearColorMap::Mode mode; }; /*! Build a color map with two stops at 0.0 and 1.0. The color at 0.0 is Qt::blue, at 1.0 it is Qt::yellow. \param format Preferred format of the color map */ QwtLinearColorMap::QwtLinearColorMap( QwtColorMap::Format format ): QwtColorMap( format ) { d_data = new PrivateData; d_data->mode = ScaledColors; setColorInterval( Qt::blue, Qt::yellow ); } /*! Build a color map with two stops at 0.0 and 1.0. \param color1 Color used for the minimum value of the value interval \param color2 Color used for the maximum value of the value interval \param format Preferred format of the coor map */ QwtLinearColorMap::QwtLinearColorMap( const QColor &color1, const QColor &color2, QwtColorMap::Format format ): QwtColorMap( format ) { d_data = new PrivateData; d_data->mode = ScaledColors; setColorInterval( color1, color2 ); } //! Destructor QwtLinearColorMap::~QwtLinearColorMap() { delete d_data; } /*! \brief Set the mode of the color map FixedColors means the color is calculated from the next lower color stop. ScaledColors means the color is calculated by interpolating the colors of the adjacent stops. \sa mode() */ void QwtLinearColorMap::setMode( Mode mode ) { d_data->mode = mode; } /*! \return Mode of the color map \sa setMode() */ QwtLinearColorMap::Mode QwtLinearColorMap::mode() const { return d_data->mode; } /*! Set the color range Add stops at 0.0 and 1.0. \param color1 Color used for the minimum value of the value interval \param color2 Color used for the maximum value of the value interval \sa color1(), color2() */ void QwtLinearColorMap::setColorInterval( const QColor &color1, const QColor &color2 ) { d_data->colorStops = ColorStops(); d_data->colorStops.insert( 0.0, color1 ); d_data->colorStops.insert( 1.0, color2 ); } /*! Add a color stop The value has to be in the range [0.0, 1.0]. F.e. a stop at position 17.0 for a range [10.0,20.0] must be passed as: (17.0 - 10.0) / (20.0 - 10.0) \param value Value between [0.0, 1.0] \param color Color stop */ void QwtLinearColorMap::addColorStop( double value, const QColor& color ) { if ( value >= 0.0 && value <= 1.0 ) d_data->colorStops.insert( value, color ); } /*! Return all positions of color stops in increasing order */ QVector QwtLinearColorMap::colorStops() const { return d_data->colorStops.stops(); } /*! \return the first color of the color range \sa setColorInterval() */ QColor QwtLinearColorMap::color1() const { return QColor( d_data->colorStops.rgb( d_data->mode, 0.0 ) ); } /*! \return the second color of the color range \sa setColorInterval() */ QColor QwtLinearColorMap::color2() const { return QColor( d_data->colorStops.rgb( d_data->mode, 1.0 ) ); } /*! Map a value of a given interval into a rgb value \param interval Range for all values \param value Value to map into a rgb value */ QRgb QwtLinearColorMap::rgb( const QwtInterval &interval, double value ) const { if ( qIsNaN(value) ) return qRgba(0, 0, 0, 0); const double width = interval.width(); double ratio = 0.0; if ( width > 0.0 ) ratio = ( value - interval.minValue() ) / width; return d_data->colorStops.rgb( d_data->mode, ratio ); } /*! Map a value of a given interval into a color index, between 0 and 255 \param interval Range for all values \param value Value to map into a color index */ unsigned char QwtLinearColorMap::colorIndex( const QwtInterval &interval, double value ) const { const double width = interval.width(); if ( qIsNaN(value) || width <= 0.0 || value <= interval.minValue() ) return 0; if ( value >= interval.maxValue() ) return ( unsigned char )255; const double ratio = ( value - interval.minValue() ) / width; unsigned char index; if ( d_data->mode == FixedColors ) index = ( unsigned char )( ratio * 255 ); // always floor else index = ( unsigned char )qRound( ratio * 255 ); return index; } class QwtAlphaColorMap::PrivateData { public: QColor color; QRgb rgb; }; /*! Constructor \param color Color of the map */ QwtAlphaColorMap::QwtAlphaColorMap( const QColor &color ): QwtColorMap( QwtColorMap::RGB ) { d_data = new PrivateData; d_data->color = color; d_data->rgb = color.rgb() & qRgba( 255, 255, 255, 0 ); } //! Destructor QwtAlphaColorMap::~QwtAlphaColorMap() { delete d_data; } /*! Set the color \param color Color \sa color() */ void QwtAlphaColorMap::setColor( const QColor &color ) { d_data->color = color; d_data->rgb = color.rgb(); } /*! \return the color \sa setColor() */ QColor QwtAlphaColorMap::color() const { return d_data->color; } /*! \brief Map a value of a given interval into a alpha value alpha := (value - interval.minValue()) / interval.width(); \param interval Range for all values \param value Value to map into a rgb value \return rgb value, with an alpha value */ QRgb QwtAlphaColorMap::rgb( const QwtInterval &interval, double value ) const { const double width = interval.width(); if ( !qIsNaN(value) && width >= 0.0 ) { const double ratio = ( value - interval.minValue() ) / width; int alpha = qRound( 255 * ratio ); if ( alpha < 0 ) alpha = 0; if ( alpha > 255 ) alpha = 255; return d_data->rgb | ( alpha << 24 ); } return d_data->rgb; } /*! Dummy function, needed to be implemented as it is pure virtual in QwtColorMap. Color indices make no sense in combination with an alpha channel. \return Always 0 */ unsigned char QwtAlphaColorMap::colorIndex( const QwtInterval &, double ) const { return 0; } workbench-1.1.1/src/Qwt/qwt_color_map.h000066400000000000000000000121711255417355300200710ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #ifndef QWT_COLOR_MAP_H #define QWT_COLOR_MAP_H #include "qwt_global.h" #include "qwt_interval.h" #include #include /*! \brief QwtColorMap is used to map values into colors. For displaying 3D data on a 2D plane the 3rd dimension is often displayed using colors, like f.e in a spectrogram. Each color map is optimized to return colors for only one of the following image formats: - QImage::Format_Indexed8\n - QImage::Format_ARGB32\n \sa QwtPlotSpectrogram, QwtScaleWidget */ class QWT_EXPORT QwtColorMap { public: /*! Format for color mapping \sa rgb(), colorIndex(), colorTable() */ enum Format { //! The map is intended to map into QRgb values. RGB, /*! The map is intended to map into 8 bit values, that are indices into the color table. */ Indexed }; QwtColorMap( Format = QwtColorMap::RGB ); virtual ~QwtColorMap(); Format format() const; /*! Map a value of a given interval into a rgb value. \param interval Range for the values \param value Value \return rgb value, corresponding to value */ virtual QRgb rgb( const QwtInterval &interval, double value ) const = 0; /*! Map a value of a given interval into a color index \param interval Range for the values \param value Value \return color index, corresponding to value */ virtual unsigned char colorIndex( const QwtInterval &interval, double value ) const = 0; QColor color( const QwtInterval &, double value ) const; virtual QVector colorTable( const QwtInterval & ) const; private: Format d_format; }; /*! \brief QwtLinearColorMap builds a color map from color stops. A color stop is a color at a specific position. The valid range for the positions is [0.0, 1.0]. When mapping a value into a color it is translated into this interval according to mode(). */ class QWT_EXPORT QwtLinearColorMap: public QwtColorMap { public: /*! Mode of color map \sa setMode(), mode() */ enum Mode { //! Return the color from the next lower color stop FixedColors, //! Interpolating the colors of the adjacent stops. ScaledColors }; QwtLinearColorMap( QwtColorMap::Format = QwtColorMap::RGB ); QwtLinearColorMap( const QColor &from, const QColor &to, QwtColorMap::Format = QwtColorMap::RGB ); virtual ~QwtLinearColorMap(); void setMode( Mode ); Mode mode() const; void setColorInterval( const QColor &color1, const QColor &color2 ); void addColorStop( double value, const QColor& ); QVector colorStops() const; QColor color1() const; QColor color2() const; virtual QRgb rgb( const QwtInterval &, double value ) const; virtual unsigned char colorIndex( const QwtInterval &, double value ) const; class ColorStops; private: // Disabled copy constructor and operator= QwtLinearColorMap( const QwtLinearColorMap & ); QwtLinearColorMap &operator=( const QwtLinearColorMap & ); class PrivateData; PrivateData *d_data; }; /*! \brief QwtAlphaColorMap variies the alpha value of a color */ class QWT_EXPORT QwtAlphaColorMap: public QwtColorMap { public: QwtAlphaColorMap( const QColor & = QColor( Qt::gray ) ); virtual ~QwtAlphaColorMap(); void setColor( const QColor & ); QColor color() const; virtual QRgb rgb( const QwtInterval &, double value ) const; private: QwtAlphaColorMap( const QwtAlphaColorMap & ); QwtAlphaColorMap &operator=( const QwtAlphaColorMap & ); virtual unsigned char colorIndex( const QwtInterval &, double value ) const; class PrivateData; PrivateData *d_data; }; /*! Map a value into a color \param interval Valid interval for values \param value Value \return Color corresponding to value \warning This method is slow for Indexed color maps. If it is necessary to map many values, its better to get the color table once and find the color using colorIndex(). */ inline QColor QwtColorMap::color( const QwtInterval &interval, double value ) const { if ( d_format == RGB ) { return QColor( rgb( interval, value ) ); } else { const unsigned int index = colorIndex( interval, value ); return colorTable( interval )[index]; // slow } } /*! \return Intended format of the color map \sa Format */ inline QwtColorMap::Format QwtColorMap::format() const { return d_format; } #endif workbench-1.1.1/src/Qwt/qwt_column_symbol.cpp000066400000000000000000000160101255417355300213270ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #include "qwt_column_symbol.h" #include "qwt_math.h" #include "qwt_text.h" #include "qwt_painter.h" #include #include static void drawBox( QPainter *p, const QRectF &rect, const QPalette &pal, double lw ) { if ( lw > 0.0 ) { if ( rect.width() == 0.0 ) { p->setPen( pal.dark().color() ); p->drawLine( rect.topLeft(), rect.bottomLeft() ); return; } if ( rect.height() == 0.0 ) { p->setPen( pal.dark().color() ); p->drawLine( rect.topLeft(), rect.topRight() ); return; } lw = qMin( lw, rect.height() / 2.0 - 1.0 ); lw = qMin( lw, rect.width() / 2.0 - 1.0 ); const QRectF outerRect = rect.adjusted( 0, 0, 1, 1 ); QPolygonF polygon( outerRect ); if ( outerRect.width() > 2 * lw && outerRect.height() > 2 * lw ) { const QRectF innerRect = outerRect.adjusted( lw, lw, -lw, -lw ); polygon = polygon.subtracted( innerRect ); } p->setPen( Qt::NoPen ); p->setBrush( pal.dark() ); p->drawPolygon( polygon ); } const QRectF windowRect = rect.adjusted( lw, lw, -lw + 1, -lw + 1 ); if ( windowRect.isValid() ) p->fillRect( windowRect, pal.window() ); } static void drawPanel( QPainter *painter, const QRectF &rect, const QPalette &pal, double lw ) { if ( lw > 0.0 ) { if ( rect.width() == 0.0 ) { painter->setPen( pal.window().color() ); painter->drawLine( rect.topLeft(), rect.bottomLeft() ); return; } if ( rect.height() == 0.0 ) { painter->setPen( pal.window().color() ); painter->drawLine( rect.topLeft(), rect.topRight() ); return; } lw = qMin( lw, rect.height() / 2.0 - 1.0 ); lw = qMin( lw, rect.width() / 2.0 - 1.0 ); const QRectF outerRect = rect.adjusted( 0, 0, 1, 1 ); const QRectF innerRect = outerRect.adjusted( lw, lw, -lw, -lw ); QPolygonF lines[2]; lines[0] += outerRect.bottomLeft(); lines[0] += outerRect.topLeft(); lines[0] += outerRect.topRight(); lines[0] += innerRect.topRight(); lines[0] += innerRect.topLeft(); lines[0] += innerRect.bottomLeft(); lines[1] += outerRect.topRight(); lines[1] += outerRect.bottomRight(); lines[1] += outerRect.bottomLeft(); lines[1] += innerRect.bottomLeft(); lines[1] += innerRect.bottomRight(); lines[1] += innerRect.topRight(); painter->setPen( Qt::NoPen ); painter->setBrush( pal.light() ); painter->drawPolygon( lines[0] ); painter->setBrush( pal.dark() ); painter->drawPolygon( lines[1] ); } painter->fillRect( rect.adjusted( lw, lw, -lw + 1, -lw + 1 ), pal.window() ); } class QwtColumnSymbol::PrivateData { public: PrivateData(): style( QwtColumnSymbol::Box ), frameStyle( QwtColumnSymbol::Raised ), lineWidth( 2 ) { palette = QPalette( Qt::gray ); } QwtColumnSymbol::Style style; QwtColumnSymbol::FrameStyle frameStyle; QPalette palette; QwtText label; int lineWidth; }; /*! Constructor \param style Style of the symbol \sa setStyle(), style(), Style */ QwtColumnSymbol::QwtColumnSymbol( Style style ) { d_data = new PrivateData(); d_data->style = style; } //! Destructor QwtColumnSymbol::~QwtColumnSymbol() { delete d_data; } /*! Specify the symbol style \param style Style \sa style(), setPalette() */ void QwtColumnSymbol::setStyle( Style style ) { d_data->style = style; } /*! \return Current symbol style \sa setStyle() */ QwtColumnSymbol::Style QwtColumnSymbol::style() const { return d_data->style; } /*! Assign a palette for the symbol \param palette Palette \sa palette(), setStyle() */ void QwtColumnSymbol::setPalette( const QPalette &palette ) { d_data->palette = palette; } /*! \return Current palette \sa setPalette() */ const QPalette& QwtColumnSymbol::palette() const { return d_data->palette; } /*! Set the frame, that is used for the Box style. \param frameStyle Frame style \sa frameStyle(), setLineWidth(), setStyle() */ void QwtColumnSymbol::setFrameStyle( FrameStyle frameStyle ) { d_data->frameStyle = frameStyle; } /*! \return Current frame style, that is used for the Box style. \sa setFrameStyle(), lineWidth(), setStyle() */ QwtColumnSymbol::FrameStyle QwtColumnSymbol::frameStyle() const { return d_data->frameStyle; } /*! Set the line width of the frame, that is used for the Box style. \param width Width \sa lineWidth(), setFrameStyle() */ void QwtColumnSymbol::setLineWidth( int width ) { if ( width < 0 ) width = 0; d_data->lineWidth = width; } /*! \return Line width of the frame, that is used for the Box style. \sa setLineWidth(), frameStyle(), setStyle() */ int QwtColumnSymbol::lineWidth() const { return d_data->lineWidth; } /*! Draw the symbol depending on its style. \param painter Painter \param rect Directed rectangle \sa drawBox() */ void QwtColumnSymbol::draw( QPainter *painter, const QwtColumnRect &rect ) const { painter->save(); switch ( d_data->style ) { case QwtColumnSymbol::Box: { drawBox( painter, rect ); break; } default:; } painter->restore(); } /*! Draw the symbol when it is in Box style. \param painter Painter \param rect Directed rectangle \sa draw() */ void QwtColumnSymbol::drawBox( QPainter *painter, const QwtColumnRect &rect ) const { QRectF r = rect.toRect(); if ( QwtPainter::roundingAlignment( painter ) ) { r.setLeft( qRound( r.left() ) ); r.setRight( qRound( r.right() ) ); r.setTop( qRound( r.top() ) ); r.setBottom( qRound( r.bottom() ) ); } switch ( d_data->frameStyle ) { case QwtColumnSymbol::Raised: { ::drawPanel( painter, r, d_data->palette, d_data->lineWidth ); break; } case QwtColumnSymbol::Plain: { ::drawBox( painter, r, d_data->palette, d_data->lineWidth ); break; } default: { painter->fillRect( r, d_data->palette.window() ); } } } workbench-1.1.1/src/Qwt/qwt_column_symbol.h000066400000000000000000000076211255417355300210040ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #ifndef QWT_COLUMN_SYMBOL_H #define QWT_COLUMN_SYMBOL_H #include "qwt_global.h" #include "qwt_interval.h" #include #include #include class QPainter; class QPalette; class QRect; class QwtText; /*! \brief Directed rectangle representing bounding rectangle und orientation of a column. */ class QWT_EXPORT QwtColumnRect { public: //! Direction of the column enum Direction { //! From left to right LeftToRight, //! From right to left RightToLeft, //! From bottom to top BottomToTop, //! From top to bottom TopToBottom }; //! Build an rectangle with invalid intervals directed BottomToTop. QwtColumnRect(): direction( BottomToTop ) { } //! \return A normalized QRect built from the intervals QRectF toRect() const { QRectF r( hInterval.minValue(), vInterval.minValue(), hInterval.maxValue() - hInterval.minValue(), vInterval.maxValue() - vInterval.minValue() ); r = r.normalized(); if ( hInterval.borderFlags() & QwtInterval::ExcludeMinimum ) r.adjust( 1, 0, 0, 0 ); if ( hInterval.borderFlags() & QwtInterval::ExcludeMaximum ) r.adjust( 0, 0, -1, 0 ); if ( vInterval.borderFlags() & QwtInterval::ExcludeMinimum ) r.adjust( 0, 1, 0, 0 ); if ( vInterval.borderFlags() & QwtInterval::ExcludeMaximum ) r.adjust( 0, 0, 0, -1 ); return r; } //! \return Orientation Qt::Orientation orientation() const { if ( direction == LeftToRight || direction == RightToLeft ) return Qt::Horizontal; return Qt::Vertical; } //! Interval for the horizontal coordinates QwtInterval hInterval; //! Interval for the vertical coordinates QwtInterval vInterval; //! Direction Direction direction; }; //! A drawing primitive for columns class QWT_EXPORT QwtColumnSymbol { public: /*! Style \sa setStyle(), style() */ enum Style { //! No Style, the symbol draws nothing NoStyle = -1, /*! The column is painted with a frame depending on the frameStyle() and lineWidth() using the palette(). */ Box, /*! Styles >= QwtColumnSymbol::UserStyle are reserved for derived classes of QwtColumnSymbol that overload draw() with additional application specific symbol types. */ UserStyle = 1000 }; /*! Frame Style used in Box style(). \sa Style, setFrameStyle(), frameStyle(), setStyle(), setPalette() */ enum FrameStyle { //! No frame NoFrame, //! A plain frame style Plain, //! A raised frame style Raised }; public: QwtColumnSymbol( Style = NoStyle ); virtual ~QwtColumnSymbol(); void setFrameStyle( FrameStyle style ); FrameStyle frameStyle() const; void setLineWidth( int width ); int lineWidth() const; void setPalette( const QPalette & ); const QPalette &palette() const; void setStyle( Style ); Style style() const; virtual void draw( QPainter *, const QwtColumnRect & ) const; protected: void drawBox( QPainter *, const QwtColumnRect & ) const; private: class PrivateData; PrivateData* d_data; }; #endif workbench-1.1.1/src/Qwt/qwt_compat.h000066400000000000000000000020341255417355300173760ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #ifndef _QWT_COMPAT_H_ #define _QWT_COMPAT_H_ #include "qwt_global.h" #include #include #include #include #include #include // A couple of definition for Qwt5 compatibility #define qwtMax qMax #define qwtMin qMin #define qwtAbs qAbs #define qwtRound qRound #define QwtArray QVector typedef QList QwtValueList; typedef QPointF QwtDoublePoint; typedef QSizeF QwtDoubleSize; typedef QRectF QwtDoubleRect; typedef QPolygon QwtPolygon; typedef QPolygonF QwtPolygonF; typedef QwtInterval QwtDoubleInterval; typedef QwtPoint3D QwtDoublePoint3D; #endif workbench-1.1.1/src/Qwt/qwt_curve_fitter.cpp000066400000000000000000000226621255417355300211600ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #include "qwt_curve_fitter.h" #include "qwt_math.h" #include "qwt_spline.h" #include #include #if QT_VERSION < 0x040601 #define qFabs(x) ::fabs(x) #endif //! Constructor QwtCurveFitter::QwtCurveFitter() { } //! Destructor QwtCurveFitter::~QwtCurveFitter() { } class QwtSplineCurveFitter::PrivateData { public: PrivateData(): fitMode( QwtSplineCurveFitter::Auto ), splineSize( 250 ) { } QwtSpline spline; QwtSplineCurveFitter::FitMode fitMode; int splineSize; }; //! Constructor QwtSplineCurveFitter::QwtSplineCurveFitter() { d_data = new PrivateData; } //! Destructor QwtSplineCurveFitter::~QwtSplineCurveFitter() { delete d_data; } /*! Select the algorithm used for building the spline \param mode Mode representing a spline algorithm \sa fitMode() */ void QwtSplineCurveFitter::setFitMode( FitMode mode ) { d_data->fitMode = mode; } /*! \return Mode representing a spline algorithm \sa setFitMode() */ QwtSplineCurveFitter::FitMode QwtSplineCurveFitter::fitMode() const { return d_data->fitMode; } /*! Assign a spline \param spline Spline \sa spline() */ void QwtSplineCurveFitter::setSpline( const QwtSpline &spline ) { d_data->spline = spline; d_data->spline.reset(); } /*! \return Spline \sa setSpline() */ const QwtSpline &QwtSplineCurveFitter::spline() const { return d_data->spline; } /*! \return Spline \sa setSpline() */ QwtSpline &QwtSplineCurveFitter::spline() { return d_data->spline; } /*! Assign a spline size ( has to be at least 10 points ) \param splineSize Spline size \sa splineSize() */ void QwtSplineCurveFitter::setSplineSize( int splineSize ) { d_data->splineSize = qMax( splineSize, 10 ); } /*! \return Spline size \sa setSplineSize() */ int QwtSplineCurveFitter::splineSize() const { return d_data->splineSize; } /*! Find a curve which has the best fit to a series of data points \param points Series of data points \return Curve points */ QPolygonF QwtSplineCurveFitter::fitCurve( const QPolygonF &points ) const { const int size = points.size(); if ( size <= 2 ) return points; FitMode fitMode = d_data->fitMode; if ( fitMode == Auto ) { fitMode = Spline; const QPointF *p = points.data(); for ( int i = 1; i < size; i++ ) { if ( p[i].x() <= p[i-1].x() ) { fitMode = ParametricSpline; break; } }; } if ( fitMode == ParametricSpline ) return fitParametric( points ); else return fitSpline( points ); } QPolygonF QwtSplineCurveFitter::fitSpline( const QPolygonF &points ) const { d_data->spline.setPoints( points ); if ( !d_data->spline.isValid() ) return points; QPolygonF fittedPoints( d_data->splineSize ); const double x1 = points[0].x(); const double x2 = points[int( points.size() - 1 )].x(); const double dx = x2 - x1; const double delta = dx / ( d_data->splineSize - 1 ); for ( int i = 0; i < d_data->splineSize; i++ ) { QPointF &p = fittedPoints[i]; const double v = x1 + i * delta; const double sv = d_data->spline.value( v ); p.setX( v ); p.setY( sv ); } d_data->spline.reset(); return fittedPoints; } QPolygonF QwtSplineCurveFitter::fitParametric( const QPolygonF &points ) const { int i; const int size = points.size(); QPolygonF fittedPoints( d_data->splineSize ); QPolygonF splinePointsX( size ); QPolygonF splinePointsY( size ); const QPointF *p = points.data(); QPointF *spX = splinePointsX.data(); QPointF *spY = splinePointsY.data(); double param = 0.0; for ( i = 0; i < size; i++ ) { const double x = p[i].x(); const double y = p[i].y(); if ( i > 0 ) { const double delta = qSqrt( qwtSqr( x - spX[i-1].y() ) + qwtSqr( y - spY[i-1].y() ) ); param += qMax( delta, 1.0 ); } spX[i].setX( param ); spX[i].setY( x ); spY[i].setX( param ); spY[i].setY( y ); } d_data->spline.setPoints( splinePointsX ); if ( !d_data->spline.isValid() ) return points; const double deltaX = splinePointsX[size - 1].x() / ( d_data->splineSize - 1 ); for ( i = 0; i < d_data->splineSize; i++ ) { const double dtmp = i * deltaX; fittedPoints[i].setX( d_data->spline.value( dtmp ) ); } d_data->spline.setPoints( splinePointsY ); if ( !d_data->spline.isValid() ) return points; const double deltaY = splinePointsY[size - 1].x() / ( d_data->splineSize - 1 ); for ( i = 0; i < d_data->splineSize; i++ ) { const double dtmp = i * deltaY; fittedPoints[i].setY( d_data->spline.value( dtmp ) ); } return fittedPoints; } class QwtWeedingCurveFitter::PrivateData { public: PrivateData(): tolerance( 1.0 ) { } double tolerance; }; class QwtWeedingCurveFitter::Line { public: Line( int i1 = 0, int i2 = 0 ): from( i1 ), to( i2 ) { } int from; int to; }; /*! Constructor \param tolerance Tolerance \sa setTolerance(), tolerance() */ QwtWeedingCurveFitter::QwtWeedingCurveFitter( double tolerance ) { d_data = new PrivateData; setTolerance( tolerance ); } //! Destructor QwtWeedingCurveFitter::~QwtWeedingCurveFitter() { delete d_data; } /*! Assign the tolerance The tolerance is the maximum distance, that is accaptable between the original curve and the smoothed curve. Increasing the tolerance will reduce the number of the resulting points. \param tolerance Tolerance \sa tolerance() */ void QwtWeedingCurveFitter::setTolerance( double tolerance ) { d_data->tolerance = qMax( tolerance, 0.0 ); } /*! \return Tolerance \sa setTolerance() */ double QwtWeedingCurveFitter::tolerance() const { return d_data->tolerance; } /*! \param points Series of data points \return Curve points */ QPolygonF QwtWeedingCurveFitter::fitCurve( const QPolygonF &points ) const { QStack stack; stack.reserve( 500 ); const QPointF *p = points.data(); const int nPoints = points.size(); QVector usePoint( nPoints, false ); double distToSegment; stack.push( Line( 0, nPoints - 1 ) ); while ( !stack.isEmpty() ) { const Line r = stack.pop(); // initialize line segment const double vecX = p[r.to].x() - p[r.from].x(); const double vecY = p[r.to].y() - p[r.from].y(); const double vecLength = qSqrt( vecX * vecX + vecY * vecY ); const double unitVecX = ( vecLength != 0.0 ) ? vecX / vecLength : 0.0; const double unitVecY = ( vecLength != 0.0 ) ? vecY / vecLength : 0.0; double maxDist = 0.0; int nVertexIndexMaxDistance = r.from + 1; for ( int i = r.from + 1; i < r.to; i++ ) { //compare to anchor const double fromVecX = p[i].x() - p[r.from].x(); const double fromVecY = p[i].y() - p[r.from].y(); const double fromVecLength = qSqrt( fromVecX * fromVecX + fromVecY * fromVecY ); if ( fromVecX * unitVecX + fromVecY * unitVecY < 0.0 ) { distToSegment = fromVecLength; } if ( fromVecX * unitVecX + fromVecY * unitVecY < 0.0 ) { distToSegment = fromVecLength; } else { const double toVecX = p[i].x() - p[r.to].x(); const double toVecY = p[i].y() - p[r.to].y(); const double toVecLength = qSqrt( toVecX * toVecX + toVecY * toVecY ); const double s = toVecX * ( -unitVecX ) + toVecY * ( -unitVecY ); if ( s < 0.0 ) distToSegment = toVecLength; else { distToSegment = qSqrt( qFabs( toVecLength * toVecLength - s * s ) ); } } if ( maxDist < distToSegment ) { maxDist = distToSegment; nVertexIndexMaxDistance = i; } } if ( maxDist <= d_data->tolerance ) { usePoint[r.from] = true; usePoint[r.to] = true; } else { stack.push( Line( r.from, nVertexIndexMaxDistance ) ); stack.push( Line( nVertexIndexMaxDistance, r.to ) ); } } int cnt = 0; QPolygonF stripped( nPoints ); for ( int i = 0; i < nPoints; i++ ) { if ( usePoint[i] ) stripped[cnt++] = p[i]; } stripped.resize( cnt ); return stripped; } workbench-1.1.1/src/Qwt/qwt_curve_fitter.h000066400000000000000000000065331255417355300206240ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #ifndef QWT_CURVE_FITTER_H #define QWT_CURVE_FITTER_H #include "qwt_global.h" #include #include class QwtSpline; /*! \brief Abstract base class for a curve fitter */ class QWT_EXPORT QwtCurveFitter { public: virtual ~QwtCurveFitter(); /*! Find a curve which has the best fit to a series of data points \param polygon Series of data points \return Curve points */ virtual QPolygonF fitCurve( const QPolygonF &polygon ) const = 0; protected: QwtCurveFitter(); private: QwtCurveFitter( const QwtCurveFitter & ); QwtCurveFitter &operator=( const QwtCurveFitter & ); }; /*! \brief A curve fitter using cubic splines */ class QWT_EXPORT QwtSplineCurveFitter: public QwtCurveFitter { public: /*! Spline type The default setting is Auto \sa setFitMode(), FitMode() */ enum FitMode { /*! Use the default spline algorithm for polygons with increasing x values ( p[i-1] < p[i] ), otherwise use a parametric spline algorithm. */ Auto, //! Use a default spline algorithm Spline, //! Use a parametric spline algorithm ParametricSpline }; QwtSplineCurveFitter(); virtual ~QwtSplineCurveFitter(); void setFitMode( FitMode ); FitMode fitMode() const; void setSpline( const QwtSpline& ); const QwtSpline &spline() const; QwtSpline &spline(); void setSplineSize( int size ); int splineSize() const; virtual QPolygonF fitCurve( const QPolygonF & ) const; private: QPolygonF fitSpline( const QPolygonF & ) const; QPolygonF fitParametric( const QPolygonF & ) const; class PrivateData; PrivateData *d_data; }; /*! \brief A curve fitter implementing Douglas and Peucker algorithm The purpose of the Douglas and Peucker algorithm is that given a 'curve' composed of line segments to find a curve not too dissimilar but that has fewer points. The algorithm defines 'too dissimilar' based on the maximum distance (tolerance) between the original curve and the smoothed curve. The smoothed curve consists of a subset of the points that defined the original curve. In opposite to QwtSplineCurveFitter the Douglas and Peucker algorithm reduces the number of points. By adjusting the tolerance parameter according to the axis scales QwtSplineCurveFitter can be used to implement different level of details to speed up painting of curves of many points. */ class QWT_EXPORT QwtWeedingCurveFitter: public QwtCurveFitter { public: QwtWeedingCurveFitter( double tolerance = 1.0 ); virtual ~QwtWeedingCurveFitter(); void setTolerance( double ); double tolerance() const; virtual QPolygonF fitCurve( const QPolygonF & ) const; private: class Line; class PrivateData; PrivateData *d_data; }; #endif workbench-1.1.1/src/Qwt/qwt_dyngrid_layout.cpp000066400000000000000000000336451255417355300215170ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #include "qwt_dyngrid_layout.h" #include "qwt_math.h" #include #include class QwtDynGridLayout::PrivateData { public: PrivateData(): isDirty( true ) { } void updateLayoutCache(); mutable QList itemList; uint maxCols; uint numRows; uint numCols; Qt::Orientations expanding; bool isDirty; QVector itemSizeHints; }; void QwtDynGridLayout::PrivateData::updateLayoutCache() { itemSizeHints.resize( itemList.count() ); int index = 0; for ( QList::iterator it = itemList.begin(); it != itemList.end(); ++it, index++ ) { itemSizeHints[ index ] = ( *it )->sizeHint(); } isDirty = false; } /*! \param parent Parent widget \param margin Margin \param spacing Spacing */ QwtDynGridLayout::QwtDynGridLayout( QWidget *parent, int margin, int spacing ): QLayout( parent ) { init(); setSpacing( spacing ); setMargin( margin ); } /*! \param spacing Spacing */ QwtDynGridLayout::QwtDynGridLayout( int spacing ) { init(); setSpacing( spacing ); } /*! Initialize the layout with default values. */ void QwtDynGridLayout::init() { d_data = new QwtDynGridLayout::PrivateData; d_data->maxCols = d_data->numRows = d_data->numCols = 0; d_data->expanding = 0; } //! Destructor QwtDynGridLayout::~QwtDynGridLayout() { for ( int i = 0; i < d_data->itemList.size(); i++ ) delete d_data->itemList[i]; delete d_data; } //! Invalidate all internal caches void QwtDynGridLayout::invalidate() { d_data->isDirty = true; QLayout::invalidate(); } /*! Limit the number of columns. \param maxCols upper limit, 0 means unlimited \sa maxCols() */ void QwtDynGridLayout::setMaxCols( uint maxCols ) { d_data->maxCols = maxCols; } /*! Return the upper limit for the number of columns. 0 means unlimited, what is the default. \sa setMaxCols() */ uint QwtDynGridLayout::maxCols() const { return d_data->maxCols; } //! Adds item to the next free position. void QwtDynGridLayout::addItem( QLayoutItem *item ) { d_data->itemList.append( item ); invalidate(); } /*! \return true if this layout is empty. */ bool QwtDynGridLayout::isEmpty() const { return d_data->itemList.isEmpty(); } /*! \return number of layout items */ uint QwtDynGridLayout::itemCount() const { return d_data->itemList.count(); } /*! Find the item at a spcific index \param index Index \sa takeAt() */ QLayoutItem *QwtDynGridLayout::itemAt( int index ) const { if ( index < 0 || index >= d_data->itemList.count() ) return NULL; return d_data->itemList.at( index ); } /*! Find the item at a spcific index and remove it from the layout \param index Index \sa itemAt() */ QLayoutItem *QwtDynGridLayout::takeAt( int index ) { if ( index < 0 || index >= d_data->itemList.count() ) return NULL; d_data->isDirty = true; return d_data->itemList.takeAt( index ); } //! \return Number of items in the layout int QwtDynGridLayout::count() const { return d_data->itemList.count(); } /*! Set whether this layout can make use of more space than sizeHint(). A value of Qt::Vertical or Qt::Horizontal means that it wants to grow in only one dimension, while Qt::Vertical | Qt::Horizontal means that it wants to grow in both dimensions. The default value is 0. \param expanding Or'd orientations \sa expandingDirections() */ void QwtDynGridLayout::setExpandingDirections( Qt::Orientations expanding ) { d_data->expanding = expanding; } /*! Returns whether this layout can make use of more space than sizeHint(). A value of Qt::Vertical or Qt::Horizontal means that it wants to grow in only one dimension, while Qt::Vertical | Qt::Horizontal means that it wants to grow in both dimensions. \sa setExpandingDirections() */ Qt::Orientations QwtDynGridLayout::expandingDirections() const { return d_data->expanding; } /*! Reorganizes columns and rows and resizes managed widgets within the rectangle rect. \param rect Layout geometry */ void QwtDynGridLayout::setGeometry( const QRect &rect ) { QLayout::setGeometry( rect ); if ( isEmpty() ) return; d_data->numCols = columnsForWidth( rect.width() ); d_data->numRows = itemCount() / d_data->numCols; if ( itemCount() % d_data->numCols ) d_data->numRows++; QList itemGeometries = layoutItems( rect, d_data->numCols ); int index = 0; for ( QList::iterator it = d_data->itemList.begin(); it != d_data->itemList.end(); ++it ) { QWidget *w = ( *it )->widget(); if ( w ) { w->setGeometry( itemGeometries[index] ); index++; } } } /*! Calculate the number of columns for a given width. It tries to use as many columns as possible (limited by maxCols()) \param width Available width for all columns \sa maxCols(), setMaxCols() */ uint QwtDynGridLayout::columnsForWidth( int width ) const { if ( isEmpty() ) return 0; const int maxCols = ( d_data->maxCols > 0 ) ? d_data->maxCols : itemCount(); if ( maxRowWidth( maxCols ) <= width ) return maxCols; for ( int numCols = 2; numCols <= maxCols; numCols++ ) { const int rowWidth = maxRowWidth( numCols ); if ( rowWidth > width ) return numCols - 1; } return 1; // At least 1 column } /*! Calculate the width of a layout for a given number of columns. \param numCols Given number of columns \param itemWidth Array of the width hints for all items */ int QwtDynGridLayout::maxRowWidth( int numCols ) const { int col; QVector colWidth( numCols ); for ( col = 0; col < numCols; col++ ) colWidth[col] = 0; if ( d_data->isDirty ) d_data->updateLayoutCache(); for ( int index = 0; index < d_data->itemSizeHints.count(); index++ ) { col = index % numCols; colWidth[col] = qMax( colWidth[col], d_data->itemSizeHints[int( index )].width() ); } int rowWidth = 2 * margin() + ( numCols - 1 ) * spacing(); for ( col = 0; col < numCols; col++ ) rowWidth += colWidth[col]; return rowWidth; } /*! \return the maximum width of all layout items */ int QwtDynGridLayout::maxItemWidth() const { if ( isEmpty() ) return 0; if ( d_data->isDirty ) d_data->updateLayoutCache(); int w = 0; for ( int i = 0; i < d_data->itemSizeHints.count(); i++ ) { const int itemW = d_data->itemSizeHints[i].width(); if ( itemW > w ) w = itemW; } return w; } /*! Calculate the geometries of the layout items for a layout with numCols columns and a given rect. \param rect Rect where to place the items \param numCols Number of columns \return item geometries */ QList QwtDynGridLayout::layoutItems( const QRect &rect, uint numCols ) const { QList itemGeometries; if ( numCols == 0 || isEmpty() ) return itemGeometries; uint numRows = itemCount() / numCols; if ( numRows % itemCount() ) numRows++; QVector rowHeight( numRows ); QVector colWidth( numCols ); layoutGrid( numCols, rowHeight, colWidth ); bool expandH, expandV; expandH = expandingDirections() & Qt::Horizontal; expandV = expandingDirections() & Qt::Vertical; if ( expandH || expandV ) stretchGrid( rect, numCols, rowHeight, colWidth ); const int maxCols = d_data->maxCols; d_data->maxCols = numCols; const QRect alignedRect = alignmentRect( rect ); d_data->maxCols = maxCols; const int xOffset = expandH ? 0 : alignedRect.x(); const int yOffset = expandV ? 0 : alignedRect.y(); QVector colX( numCols ); QVector rowY( numRows ); const int xySpace = spacing(); rowY[0] = yOffset + margin(); for ( int r = 1; r < ( int )numRows; r++ ) rowY[r] = rowY[r-1] + rowHeight[r-1] + xySpace; colX[0] = xOffset + margin(); for ( int c = 1; c < ( int )numCols; c++ ) colX[c] = colX[c-1] + colWidth[c-1] + xySpace; const int itemCount = d_data->itemList.size(); for ( int i = 0; i < itemCount; i++ ) { const int row = i / numCols; const int col = i % numCols; QRect itemGeometry( colX[col], rowY[row], colWidth[col], rowHeight[row] ); itemGeometries.append( itemGeometry ); } return itemGeometries; } /*! Calculate the dimensions for the columns and rows for a grid of numCols columns. \param numCols Number of columns. \param rowHeight Array where to fill in the calculated row heights. \param colWidth Array where to fill in the calculated column widths. */ void QwtDynGridLayout::layoutGrid( uint numCols, QVector& rowHeight, QVector& colWidth ) const { if ( numCols <= 0 ) return; if ( d_data->isDirty ) d_data->updateLayoutCache(); for ( uint index = 0; index < ( uint )d_data->itemSizeHints.count(); index++ ) { const int row = index / numCols; const int col = index % numCols; const QSize &size = d_data->itemSizeHints[int( index )]; rowHeight[row] = ( col == 0 ) ? size.height() : qMax( rowHeight[row], size.height() ); colWidth[col] = ( row == 0 ) ? size.width() : qMax( colWidth[col], size.width() ); } } /*! \return true: QwtDynGridLayout implements heightForWidth. \sa heightForWidth() */ bool QwtDynGridLayout::hasHeightForWidth() const { return true; } /*! \return The preferred height for this layout, given the width w. \sa hasHeightForWidth() */ int QwtDynGridLayout::heightForWidth( int width ) const { if ( isEmpty() ) return 0; const uint numCols = columnsForWidth( width ); uint numRows = itemCount() / numCols; if ( itemCount() % numCols ) numRows++; QVector rowHeight( numRows ); QVector colWidth( numCols ); layoutGrid( numCols, rowHeight, colWidth ); int h = 2 * margin() + ( numRows - 1 ) * spacing(); for ( int row = 0; row < ( int )numRows; row++ ) h += rowHeight[row]; return h; } /*! Stretch columns in case of expanding() & QSizePolicy::Horizontal and rows in case of expanding() & QSizePolicy::Vertical to fill the entire rect. Rows and columns are stretched with the same factor. \sa setExpanding(), expanding() */ void QwtDynGridLayout::stretchGrid( const QRect &rect, uint numCols, QVector& rowHeight, QVector& colWidth ) const { if ( numCols == 0 || isEmpty() ) return; bool expandH, expandV; expandH = expandingDirections() & Qt::Horizontal; expandV = expandingDirections() & Qt::Vertical; if ( expandH ) { int xDelta = rect.width() - 2 * margin() - ( numCols - 1 ) * spacing(); for ( int col = 0; col < ( int )numCols; col++ ) xDelta -= colWidth[col]; if ( xDelta > 0 ) { for ( int col = 0; col < ( int )numCols; col++ ) { const int space = xDelta / ( numCols - col ); colWidth[col] += space; xDelta -= space; } } } if ( expandV ) { uint numRows = itemCount() / numCols; if ( itemCount() % numCols ) numRows++; int yDelta = rect.height() - 2 * margin() - ( numRows - 1 ) * spacing(); for ( int row = 0; row < ( int )numRows; row++ ) yDelta -= rowHeight[row]; if ( yDelta > 0 ) { for ( int row = 0; row < ( int )numRows; row++ ) { const int space = yDelta / ( numRows - row ); rowHeight[row] += space; yDelta -= space; } } } } /*! Return the size hint. If maxCols() > 0 it is the size for a grid with maxCols() columns, otherwise it is the size for a grid with only one row. \sa maxCols(), setMaxCols() */ QSize QwtDynGridLayout::sizeHint() const { if ( isEmpty() ) return QSize(); const uint numCols = ( d_data->maxCols > 0 ) ? d_data->maxCols : itemCount(); uint numRows = itemCount() / numCols; if ( itemCount() % numCols ) numRows++; QVector rowHeight( numRows ); QVector colWidth( numCols ); layoutGrid( numCols, rowHeight, colWidth ); int h = 2 * margin() + ( numRows - 1 ) * spacing(); for ( int row = 0; row < ( int )numRows; row++ ) h += rowHeight[row]; int w = 2 * margin() + ( numCols - 1 ) * spacing(); for ( int col = 0; col < ( int )numCols; col++ ) w += colWidth[col]; return QSize( w, h ); } /*! \return Number of rows of the current layout. \sa numCols() \warning The number of rows might change whenever the geometry changes */ uint QwtDynGridLayout::numRows() const { return d_data->numRows; } /*! \return Number of columns of the current layout. \sa numRows() \warning The number of columns might change whenever the geometry changes */ uint QwtDynGridLayout::numCols() const { return d_data->numCols; } workbench-1.1.1/src/Qwt/qwt_dyngrid_layout.h000066400000000000000000000045571255417355300211640ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #ifndef QWT_DYNGRID_LAYOUT_H #define QWT_DYNGRID_LAYOUT_H #include "qwt_global.h" #include #include #include /*! \brief The QwtDynGridLayout class lays out widgets in a grid, adjusting the number of columns and rows to the current size. QwtDynGridLayout takes the space it gets, divides it up into rows and columns, and puts each of the widgets it manages into the correct cell(s). It lays out as many number of columns as possible (limited by maxCols()). */ class QWT_EXPORT QwtDynGridLayout : public QLayout { Q_OBJECT public: explicit QwtDynGridLayout( QWidget *, int margin = 0, int space = -1 ); explicit QwtDynGridLayout( int space = -1 ); virtual ~QwtDynGridLayout(); virtual void invalidate(); void setMaxCols( uint maxCols ); uint maxCols() const; uint numRows () const; uint numCols () const; virtual void addItem( QLayoutItem * ); virtual QLayoutItem *itemAt( int index ) const; virtual QLayoutItem *takeAt( int index ); virtual int count() const; void setExpandingDirections( Qt::Orientations ); virtual Qt::Orientations expandingDirections() const; QList layoutItems( const QRect &, uint numCols ) const; virtual int maxItemWidth() const; virtual void setGeometry( const QRect &rect ); virtual bool hasHeightForWidth() const; virtual int heightForWidth( int ) const; virtual QSize sizeHint() const; virtual bool isEmpty() const; uint itemCount() const; virtual uint columnsForWidth( int width ) const; protected: void layoutGrid( uint numCols, QVector& rowHeight, QVector& colWidth ) const; void stretchGrid( const QRect &rect, uint numCols, QVector& rowHeight, QVector& colWidth ) const; private: void init(); int maxRowWidth( int numCols ) const; class PrivateData; PrivateData *d_data; }; #endif workbench-1.1.1/src/Qwt/qwt_event_pattern.cpp000066400000000000000000000156061255417355300213350ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #include "qwt_event_pattern.h" #include /*! Constructor \sa MousePatternCode, KeyPatternCode */ QwtEventPattern::QwtEventPattern(): d_mousePattern( MousePatternCount ), d_keyPattern( KeyPatternCount ) { initKeyPattern(); initMousePattern( 3 ); } //! Destructor QwtEventPattern::~QwtEventPattern() { } /*! Set default mouse patterns, depending on the number of mouse buttons \param numButtons Number of mouse buttons ( <= 3 ) \sa MousePatternCode */ void QwtEventPattern::initMousePattern( int numButtons ) { const int altButton = Qt::AltModifier; const int controlButton = Qt::ControlModifier; const int shiftButton = Qt::ShiftModifier; d_mousePattern.resize( MousePatternCount ); switch ( numButtons ) { case 1: { setMousePattern( MouseSelect1, Qt::LeftButton ); setMousePattern( MouseSelect2, Qt::LeftButton, controlButton ); setMousePattern( MouseSelect3, Qt::LeftButton, altButton ); break; } case 2: { setMousePattern( MouseSelect1, Qt::LeftButton ); setMousePattern( MouseSelect2, Qt::RightButton ); setMousePattern( MouseSelect3, Qt::LeftButton, altButton ); break; } default: { setMousePattern( MouseSelect1, Qt::LeftButton ); setMousePattern( MouseSelect2, Qt::RightButton ); setMousePattern( MouseSelect3, Qt::MidButton ); } } for ( int i = 0; i < 3; i++ ) { setMousePattern( MouseSelect4 + i, d_mousePattern[MouseSelect1 + i].button, d_mousePattern[MouseSelect1 + i].state | shiftButton ); } } /*! Set default mouse patterns. \sa KeyPatternCode */ void QwtEventPattern::initKeyPattern() { d_keyPattern.resize( KeyPatternCount ); setKeyPattern( KeySelect1, Qt::Key_Return ); setKeyPattern( KeySelect2, Qt::Key_Space ); setKeyPattern( KeyAbort, Qt::Key_Escape ); setKeyPattern( KeyLeft, Qt::Key_Left ); setKeyPattern( KeyRight, Qt::Key_Right ); setKeyPattern( KeyUp, Qt::Key_Up ); setKeyPattern( KeyDown, Qt::Key_Down ); setKeyPattern( KeyRedo, Qt::Key_Plus ); setKeyPattern( KeyUndo, Qt::Key_Minus ); setKeyPattern( KeyHome, Qt::Key_Escape ); } /*! Change one mouse pattern \param pattern Index of the pattern \param button Button \param state State \sa QMouseEvent */ void QwtEventPattern::setMousePattern( uint pattern, int button, int state ) { if ( pattern < ( uint )d_mousePattern.count() ) { d_mousePattern[int( pattern )].button = button; d_mousePattern[int( pattern )].state = state; } } /*! Change one key pattern \param pattern Index of the pattern \param key Key \param state State \sa QKeyEvent */ void QwtEventPattern::setKeyPattern( uint pattern, int key, int state ) { if ( pattern < ( uint )d_keyPattern.count() ) { d_keyPattern[int( pattern )].key = key; d_keyPattern[int( pattern )].state = state; } } //! Change the mouse event patterns void QwtEventPattern::setMousePattern( const QVector &pattern ) { d_mousePattern = pattern; } //! Change the key event patterns void QwtEventPattern::setKeyPattern( const QVector &pattern ) { d_keyPattern = pattern; } //! Return mouse patterns const QVector & QwtEventPattern::mousePattern() const { return d_mousePattern; } //! Return key patterns const QVector & QwtEventPattern::keyPattern() const { return d_keyPattern; } //! Return ,ouse patterns QVector &QwtEventPattern::mousePattern() { return d_mousePattern; } //! Return Key patterns QVector &QwtEventPattern::keyPattern() { return d_keyPattern; } /*! \brief Compare a mouse event with an event pattern. A mouse event matches the pattern when both have the same button value and in the state value the same key flags(Qt::KeyButtonMask) are set. \param pattern Index of the event pattern \param event Mouse event \return true if matches \sa keyMatch() */ bool QwtEventPattern::mouseMatch( uint pattern, const QMouseEvent *event ) const { bool ok = false; if ( event && pattern < ( uint )d_mousePattern.count() ) ok = mouseMatch( d_mousePattern[int( pattern )], event ); return ok; } /*! \brief Compare a mouse event with an event pattern. A mouse event matches the pattern when both have the same button value and in the state value the same key flags(Qt::KeyButtonMask) are set. \param pattern Mouse event pattern \param event Mouse event \return true if matches \sa keyMatch() */ bool QwtEventPattern::mouseMatch( const MousePattern &pattern, const QMouseEvent *event ) const { if ( event->button() != pattern.button ) return false; const bool matched = ( event->modifiers() & Qt::KeyboardModifierMask ) == ( int )( pattern.state & Qt::KeyboardModifierMask ); return matched; } /*! \brief Compare a key event with an event pattern. A key event matches the pattern when both have the same key value and in the state value the same key flags (Qt::KeyButtonMask) are set. \param pattern Index of the event pattern \param event Key event \return true if matches \sa mouseMatch() */ bool QwtEventPattern::keyMatch( uint pattern, const QKeyEvent *event ) const { bool ok = false; if ( event && pattern < ( uint )d_keyPattern.count() ) ok = keyMatch( d_keyPattern[int( pattern )], event ); return ok; } /*! \brief Compare a key event with an event pattern. A key event matches the pattern when both have the same key value and in the state value the same key flags (Qt::KeyButtonMask) are set. \param pattern Key event pattern \param event Key event \return true if matches \sa mouseMatch() */ bool QwtEventPattern::keyMatch( const KeyPattern &pattern, const QKeyEvent *event ) const { if ( event->key() != pattern.key ) return false; const bool matched = ( event->modifiers() & Qt::KeyboardModifierMask ) == ( int )( pattern.state & Qt::KeyboardModifierMask ); return matched; } workbench-1.1.1/src/Qwt/qwt_event_pattern.h000066400000000000000000000125661255417355300210040ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #ifndef QWT_EVENT_PATTERN #define QWT_EVENT_PATTERN 1 #include "qwt_global.h" #include #include class QMouseEvent; class QKeyEvent; /*! \brief A collection of event patterns QwtEventPattern introduces an level of indirection for mouse and keyboard inputs. Those are represented by symbolic names, so the application code can be configured by individual mappings. \sa QwtPicker, QwtPickerMachine, QwtPlotZoomer */ class QWT_EXPORT QwtEventPattern { public: /*! \brief Symbolic mouse input codes The default initialization for 3 button mice is: - MouseSelect1\n Qt::LeftButton - MouseSelect2\n Qt::RightButton - MouseSelect3\n Qt::MidButton - MouseSelect4\n Qt::LeftButton + Qt::ShiftButton - MouseSelect5\n Qt::RightButton + Qt::ShiftButton - MouseSelect6\n Qt::MidButton + Qt::ShiftButton The default initialization for 2 button mice is: - MouseSelect1\n Qt::LeftButton - MouseSelect2\n Qt::RightButton - MouseSelect3\n Qt::LeftButton + Qt::AltButton - MouseSelect4\n Qt::LeftButton + Qt::ShiftButton - MouseSelect5\n Qt::RightButton + Qt::ShiftButton - MouseSelect6\n Qt::LeftButton + Qt::AltButton + Qt::ShiftButton The default initialization for 1 button mice is: - MouseSelect1\n Qt::LeftButton - MouseSelect2\n Qt::LeftButton + Qt::ControlButton - MouseSelect3\n Qt::LeftButton + Qt::AltButton - MouseSelect4\n Qt::LeftButton + Qt::ShiftButton - MouseSelect5\n Qt::LeftButton + Qt::ControlButton + Qt::ShiftButton - MouseSelect6\n Qt::LeftButton + Qt::AltButton + Qt::ShiftButton \sa initMousePattern() */ enum MousePatternCode { MouseSelect1, MouseSelect2, MouseSelect3, MouseSelect4, MouseSelect5, MouseSelect6, MousePatternCount }; /*! \brief Symbolic keyboard input codes Default initialization: - KeySelect1\n Qt::Key_Return - KeySelect2\n Qt::Key_Space - KeyAbort\n Qt::Key_Escape - KeyLeft\n Qt::Key_Left - KeyRight\n Qt::Key_Right - KeyUp\n Qt::Key_Up - KeyDown\n Qt::Key_Down - KeyUndo\n Qt::Key_Minus - KeyRedo\n Qt::Key_Plus - KeyHome\n Qt::Key_Escape */ enum KeyPatternCode { KeySelect1, KeySelect2, KeyAbort, KeyLeft, KeyRight, KeyUp, KeyDown, KeyRedo, KeyUndo, KeyHome, KeyPatternCount }; //! A pattern for mouse events class MousePattern { public: //! Constructor MousePattern( int btn = Qt::NoButton, int st = Qt::NoButton ) { button = btn; state = st; } //! Button code int button; //! State int state; }; //! A pattern for key events class KeyPattern { public: //! Constructor KeyPattern( int k = 0, int st = Qt::NoButton ) { key = k; state = st; } //! Key code int key; //! State int state; }; QwtEventPattern(); virtual ~QwtEventPattern(); void initMousePattern( int numButtons ); void initKeyPattern(); void setMousePattern( uint pattern, int button, int state = Qt::NoButton ); void setKeyPattern( uint pattern, int key, int state = Qt::NoButton ); void setMousePattern( const QVector & ); void setKeyPattern( const QVector & ); const QVector &mousePattern() const; const QVector &keyPattern() const; QVector &mousePattern(); QVector &keyPattern(); bool mouseMatch( uint pattern, const QMouseEvent * ) const; bool keyMatch( uint pattern, const QKeyEvent * ) const; protected: virtual bool mouseMatch( const MousePattern &, const QMouseEvent * ) const; virtual bool keyMatch( const KeyPattern &, const QKeyEvent * ) const; private: #if defined(_MSC_VER) #pragma warning(push) #pragma warning(disable: 4251) #endif QVector d_mousePattern; QVector d_keyPattern; #if defined(_MSC_VER) #pragma warning(pop) #endif }; //! Compare operator inline bool operator==( QwtEventPattern::MousePattern b1, QwtEventPattern::MousePattern b2 ) { return b1.button == b2.button && b1.state == b2.state; } //! Compare operator inline bool operator==( QwtEventPattern::KeyPattern b1, QwtEventPattern::KeyPattern b2 ) { return b1.key == b2.key && b1.state == b2.state; } #endif workbench-1.1.1/src/Qwt/qwt_global.h000066400000000000000000000023361255417355300173600ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #ifndef QWT_GLOBAL_H #define QWT_GLOBAL_H #include // QWT_VERSION is (major << 16) + (minor << 8) + patch. #define QWT_VERSION 0x060001 #define QWT_VERSION_STR "6.0.1" #if defined(Q_WS_WIN) || defined(Q_WS_S60) #if defined(_MSC_VER) /* MSVC Compiler */ /* template-class specialization 'identifier' is already instantiated */ #pragma warning(disable: 4660) #endif // _MSC_VER #ifdef QWT_DLL #if defined(QWT_MAKEDLL) // create a Qwt DLL library #define QWT_EXPORT __declspec(dllexport) #define QWT_TEMPLATEDLL #else // use a Qwt DLL library #define QWT_EXPORT __declspec(dllimport) #endif #endif // QWT_DLL #endif // Q_WS_WIN || Q_WS_S60 #ifndef QWT_EXPORT #define QWT_EXPORT #endif // #define QWT_NO_COMPAT 1 // disable withdrawn functionality #endif workbench-1.1.1/src/Qwt/qwt_interval.cpp000066400000000000000000000203661255417355300203020ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #include "qwt_interval.h" #include "qwt_math.h" #include /*! \brief Normalize the limits of the interval If maxValue() < minValue() the limits will be inverted. \return Normalized interval \sa isValid(), inverted() */ QwtInterval QwtInterval::normalized() const { if ( d_minValue > d_maxValue ) { return inverted(); } if ( d_minValue == d_maxValue && d_borderFlags == ExcludeMinimum ) { return inverted(); } return *this; } /*! Invert the limits of the interval \return Inverted interval \sa normalized() */ QwtInterval QwtInterval::inverted() const { BorderFlags borderFlags = IncludeBorders; if ( d_borderFlags & ExcludeMinimum ) borderFlags |= ExcludeMaximum; if ( d_borderFlags & ExcludeMaximum ) borderFlags |= ExcludeMinimum; return QwtInterval( d_maxValue, d_minValue, borderFlags ); } /*! Test if a value is inside an interval \param value Value \return true, if value >= minValue() && value <= maxValue() */ bool QwtInterval::contains( double value ) const { if ( !isValid() ) return false; if ( value < d_minValue || value > d_maxValue ) return false; if ( value == d_minValue && d_borderFlags & ExcludeMinimum ) return false; if ( value == d_maxValue && d_borderFlags & ExcludeMaximum ) return false; return true; } //! Unite 2 intervals QwtInterval QwtInterval::unite( const QwtInterval &other ) const { /* If one of the intervals is invalid return the other one. If both are invalid return an invalid default interval */ if ( !isValid() ) { if ( !other.isValid() ) return QwtInterval(); else return other; } if ( !other.isValid() ) return *this; QwtInterval united; BorderFlags flags = IncludeBorders; // minimum if ( d_minValue < other.minValue() ) { united.setMinValue( d_minValue ); flags &= d_borderFlags & ExcludeMinimum; } else if ( other.minValue() < d_minValue ) { united.setMinValue( other.minValue() ); flags &= other.borderFlags() & ExcludeMinimum; } else // d_minValue == other.minValue() { united.setMinValue( d_minValue ); flags &= ( d_borderFlags & other.borderFlags() ) & ExcludeMinimum; } // maximum if ( d_maxValue > other.maxValue() ) { united.setMaxValue( d_maxValue ); flags &= d_borderFlags & ExcludeMaximum; } else if ( other.maxValue() > d_maxValue ) { united.setMaxValue( other.maxValue() ); flags &= other.borderFlags() & ExcludeMaximum; } else // d_maxValue == other.maxValue() ) { united.setMaxValue( d_maxValue ); flags &= d_borderFlags & other.borderFlags() & ExcludeMaximum; } united.setBorderFlags( flags ); return united; } //! Intersect 2 intervals QwtInterval QwtInterval::intersect( const QwtInterval &other ) const { if ( !other.isValid() || !isValid() ) return QwtInterval(); QwtInterval i1 = *this; QwtInterval i2 = other; // swap i1/i2, so that the minimum of i1 // is smaller then the minimum of i2 if ( i1.minValue() > i2.minValue() ) { qSwap( i1, i2 ); } else if ( i1.minValue() == i2.minValue() ) { if ( i1.borderFlags() & ExcludeMinimum ) qSwap( i1, i2 ); } if ( i1.maxValue() < i2.minValue() ) { return QwtInterval(); } if ( i1.maxValue() == i2.minValue() ) { if ( i1.borderFlags() & ExcludeMaximum || i2.borderFlags() & ExcludeMinimum ) { return QwtInterval(); } } QwtInterval intersected; BorderFlags flags = IncludeBorders; intersected.setMinValue( i2.minValue() ); flags |= i2.borderFlags() & ExcludeMinimum; if ( i1.maxValue() < i2.maxValue() ) { intersected.setMaxValue( i1.maxValue() ); flags |= i1.borderFlags() & ExcludeMaximum; } else if ( i2.maxValue() < i1.maxValue() ) { intersected.setMaxValue( i2.maxValue() ); flags |= i2.borderFlags() & ExcludeMaximum; } else // i1.maxValue() == i2.maxValue() { intersected.setMaxValue( i1.maxValue() ); flags |= i1.borderFlags() & i2.borderFlags() & ExcludeMaximum; } intersected.setBorderFlags( flags ); return intersected; } //! Unites this interval with the given interval. QwtInterval& QwtInterval::operator|=( const QwtInterval & interval ) { *this = *this | interval; return *this; } //! Intersects this interval with the given interval. QwtInterval& QwtInterval::operator&=( const QwtInterval & interval ) { *this = *this & interval; return *this; } /*! Test if two intervals overlap */ bool QwtInterval::intersects( const QwtInterval &other ) const { if ( !isValid() || !other.isValid() ) return false; QwtInterval i1 = *this; QwtInterval i2 = other; // swap i1/i2, so that the minimum of i1 // is smaller then the minimum of i2 if ( i1.minValue() > i2.minValue() ) { qSwap( i1, i2 ); } else if ( i1.minValue() == i2.minValue() && i1.borderFlags() & ExcludeMinimum ) { qSwap( i1, i2 ); } if ( i1.maxValue() > i2.minValue() ) { return true; } if ( i1.maxValue() == i2.minValue() ) { return !( ( i1.borderFlags() & ExcludeMaximum ) || ( i2.borderFlags() & ExcludeMinimum ) ); } return false; } /*! Adjust the limit that is closer to value, so that value becomes the center of the interval. \param value Center \return Interval with value as center */ QwtInterval QwtInterval::symmetrize( double value ) const { if ( !isValid() ) return *this; const double delta = qMax( qAbs( value - d_maxValue ), qAbs( value - d_minValue ) ); return QwtInterval( value - delta, value + delta ); } /*! Limit the interval, keeping the border modes \param lowerBound Lower limit \param upperBound Upper limit \return Limited interval */ QwtInterval QwtInterval::limited( double lowerBound, double upperBound ) const { if ( !isValid() || lowerBound > upperBound ) return QwtInterval(); double minValue = qMax( d_minValue, lowerBound ); minValue = qMin( minValue, upperBound ); double maxValue = qMax( d_maxValue, lowerBound ); maxValue = qMin( maxValue, upperBound ); return QwtInterval( minValue, maxValue, d_borderFlags ); } /*! Extend the interval If value is below minValue, value becomes the lower limit. If value is above maxValue, value becomes the upper limit. extend has no effect for invalid intervals \param value Value \sa isValid() */ QwtInterval QwtInterval::extend( double value ) const { if ( !isValid() ) return *this; return QwtInterval( qMin( value, d_minValue ), qMax( value, d_maxValue ), d_borderFlags ); } /*! Extend an interval \param value Value \return Reference of the extended interval \sa extend() */ QwtInterval& QwtInterval::operator|=( double value ) { *this = *this | value; return *this; } #ifndef QT_NO_DEBUG_STREAM QDebug operator<<( QDebug debug, const QwtInterval &interval ) { const int flags = interval.borderFlags(); debug.nospace() << "QwtInterval(" << ( ( flags & QwtInterval::ExcludeMinimum ) ? "]" : "[" ) << interval.minValue() << "," << interval.maxValue() << ( ( flags & QwtInterval::ExcludeMaximum ) ? "[" : "]" ) << ")"; return debug.space(); } #endif workbench-1.1.1/src/Qwt/qwt_interval.h000066400000000000000000000154551255417355300177520ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #ifndef QWT_INTERVAL_H #define QWT_INTERVAL_H #include "qwt_global.h" #ifndef QT_NO_DEBUG_STREAM #include #endif /*! \brief A class representing an interval The interval is represented by 2 doubles, the lower and the upper limit. */ class QWT_EXPORT QwtInterval { public: /*! Flag indicating if a border is included or excluded \sa setBorderFlags(), borderFlags() */ enum BorderFlag { //! Min/Max values are inside the interval IncludeBorders = 0x00, //! Min value is not included in the interval ExcludeMinimum = 0x01, //! Max value is not included in the interval ExcludeMaximum = 0x02, //! Min/Max values are not included in the interval ExcludeBorders = ExcludeMinimum | ExcludeMaximum }; //! Border flags typedef QFlags BorderFlags; QwtInterval(); QwtInterval( double minValue, double maxValue, BorderFlags = IncludeBorders ); void setInterval( double minValue, double maxValue, BorderFlags = IncludeBorders ); QwtInterval normalized() const; QwtInterval inverted() const; QwtInterval limited( double minValue, double maxValue ) const; bool operator==( const QwtInterval & ) const; bool operator!=( const QwtInterval & ) const; void setBorderFlags( BorderFlags ); BorderFlags borderFlags() const; double minValue() const; double maxValue() const; double width() const; void setMinValue( double ); void setMaxValue( double ); bool contains( double value ) const; bool intersects( const QwtInterval & ) const; QwtInterval intersect( const QwtInterval & ) const; QwtInterval unite( const QwtInterval & ) const; QwtInterval operator|( const QwtInterval & ) const; QwtInterval operator&( const QwtInterval & ) const; QwtInterval &operator|=( const QwtInterval & ); QwtInterval &operator&=( const QwtInterval & ); QwtInterval extend( double value ) const; QwtInterval operator|( double ) const; QwtInterval &operator|=( double ); bool isValid() const; bool isNull() const; void invalidate(); QwtInterval symmetrize( double value ) const; private: double d_minValue; double d_maxValue; BorderFlags d_borderFlags; }; Q_DECLARE_TYPEINFO(QwtInterval, Q_MOVABLE_TYPE); /*! \brief Default Constructor Creates an invalid interval [0.0, -1.0] \sa setInterval(), isValid() */ inline QwtInterval::QwtInterval(): d_minValue( 0.0 ), d_maxValue( -1.0 ), d_borderFlags( IncludeBorders ) { } /*! Constructor Build an interval with from min/max values \param minValue Minimum value \param maxValue Maximum value \param borderFlags Include/Exclude borders */ inline QwtInterval::QwtInterval( double minValue, double maxValue, BorderFlags borderFlags ): d_minValue( minValue ), d_maxValue( maxValue ), d_borderFlags( borderFlags ) { } /*! Assign the limits of the interval \param minValue Minimum value \param maxValue Maximum value \param borderFlags Include/Exclude borders */ inline void QwtInterval::setInterval( double minValue, double maxValue, BorderFlags borderFlags ) { d_minValue = minValue; d_maxValue = maxValue; d_borderFlags = borderFlags; } /*! Change the border flags \param borderFlags Or'd BorderMode flags \sa borderFlags() */ inline void QwtInterval::setBorderFlags( BorderFlags borderFlags ) { d_borderFlags = borderFlags; } /*! \return Border flags \sa setBorderFlags() */ inline QwtInterval::BorderFlags QwtInterval::borderFlags() const { return d_borderFlags; } /*! Assign the lower limit of the interval \param minValue Minimum value */ inline void QwtInterval::setMinValue( double minValue ) { d_minValue = minValue; } /*! Assign the upper limit of the interval \param maxValue Maximum value */ inline void QwtInterval::setMaxValue( double maxValue ) { d_maxValue = maxValue; } //! \return Lower limit of the interval inline double QwtInterval::minValue() const { return d_minValue; } //! \return Upper limit of the interval inline double QwtInterval::maxValue() const { return d_maxValue; } /*! Return the width of an interval The width of invalid intervals is 0.0, otherwise the result is maxValue() - minValue(). \sa isValid() */ inline double QwtInterval::width() const { return isValid() ? ( d_maxValue - d_minValue ) : 0.0; } /*! Intersection of two intervals \sa intersect() */ inline QwtInterval QwtInterval::operator&( const QwtInterval &interval ) const { return intersect( interval ); } /*! Union of two intervals \sa unite() */ inline QwtInterval QwtInterval::operator|( const QwtInterval &interval ) const { return unite( interval ); } //! Compare two intervals inline bool QwtInterval::operator==( const QwtInterval &other ) const { return ( d_minValue == other.d_minValue ) && ( d_maxValue == other.d_maxValue ) && ( d_borderFlags == other.d_borderFlags ); } //! Compare two intervals inline bool QwtInterval::operator!=( const QwtInterval &other ) const { return ( !( *this == other ) ); } /*! Extend an interval \param value Value \return Extended interval \sa extend() */ inline QwtInterval QwtInterval::operator|( double value ) const { return extend( value ); } //! \return true, if isValid() && (minValue() >= maxValue()) inline bool QwtInterval::isNull() const { return isValid() && d_minValue >= d_maxValue; } /*! A interval is valid when minValue() <= maxValue(). In case of QwtInterval::ExcludeBorders it is true when minValue() < maxValue() */ inline bool QwtInterval::isValid() const { if ( ( d_borderFlags & ExcludeBorders ) == 0 ) return d_minValue <= d_maxValue; else return d_minValue < d_maxValue; } /*! Invalidate the interval The limits are set to interval [0.0, -1.0] \sa isValid() */ inline void QwtInterval::invalidate() { d_minValue = 0.0; d_maxValue = -1.0; } Q_DECLARE_OPERATORS_FOR_FLAGS( QwtInterval::BorderFlags ) #ifndef QT_NO_DEBUG_STREAM QWT_EXPORT QDebug operator<<( QDebug, const QwtInterval & ); #endif #endif workbench-1.1.1/src/Qwt/qwt_interval_symbol.cpp000066400000000000000000000171701255417355300216660ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #include "qwt_interval_symbol.h" #include "qwt_painter.h" #include "qwt_math.h" #include #if QT_VERSION < 0x040601 #define qAtan2(y, x) ::atan2(y, x) #endif class QwtIntervalSymbol::PrivateData { public: PrivateData(): style( QwtIntervalSymbol::NoSymbol ), width( 6 ) { } bool operator==( const PrivateData &other ) const { return ( style == other.style ) && ( width == other.width ) && ( brush == other.brush ) && ( pen == other.pen ); } QwtIntervalSymbol::Style style; int width; QPen pen; QBrush brush; }; /*! Constructor \param style Style of the symbol \sa setStyle(), style(), Style */ QwtIntervalSymbol::QwtIntervalSymbol( Style style ) { d_data = new PrivateData(); d_data->style = style; } //! Copy constructor QwtIntervalSymbol::QwtIntervalSymbol( const QwtIntervalSymbol &other ) { d_data = new PrivateData(); *d_data = *other.d_data; } //! Destructor QwtIntervalSymbol::~QwtIntervalSymbol() { delete d_data; } //! \brief Assignment operator QwtIntervalSymbol &QwtIntervalSymbol::operator=( const QwtIntervalSymbol &other ) { *d_data = *other.d_data; return *this; } //! \brief Compare two symbols bool QwtIntervalSymbol::operator==( const QwtIntervalSymbol &other ) const { return *d_data == *other.d_data; } //! \brief Compare two symbols bool QwtIntervalSymbol::operator!=( const QwtIntervalSymbol &other ) const { return !( *d_data == *other.d_data ); } /*! Specify the symbol style \param style Style \sa style(), Style */ void QwtIntervalSymbol::setStyle( Style style ) { d_data->style = style; } /*! \return Current symbol style \sa setStyle() */ QwtIntervalSymbol::Style QwtIntervalSymbol::style() const { return d_data->style; } /*! Specify the width of the symbol It is used depending on the style. \param width Width \sa width(), setStyle() */ void QwtIntervalSymbol::setWidth( int width ) { d_data->width = width; } /*! \return Width of the symbol. \sa setWidth(), setStyle() */ int QwtIntervalSymbol::width() const { return d_data->width; } /*! \brief Assign a brush The brush is used for the Box style. \param brush Brush \sa brush() */ void QwtIntervalSymbol::setBrush( const QBrush &brush ) { d_data->brush = brush; } /*! \return Brush \sa setBrush() */ const QBrush& QwtIntervalSymbol::brush() const { return d_data->brush; } /*! Assign a pen \param pen Pen \sa pen(), setBrush() */ void QwtIntervalSymbol::setPen( const QPen &pen ) { d_data->pen = pen; } /*! \return Pen \sa setPen(), brush() */ const QPen& QwtIntervalSymbol::pen() const { return d_data->pen; } /*! Draw a symbol depending on its style \param painter Painter \param orientation Orientation \param from Start point of the interval in target device coordinates \param to End point of the interval in target device coordinates \sa setStyle() */ void QwtIntervalSymbol::draw( QPainter *painter, Qt::Orientation orientation, const QPointF &from, const QPointF &to ) const { const qreal pw = qMax( painter->pen().widthF(), qreal( 1.0 ) ); QPointF p1 = from; QPointF p2 = to; if ( QwtPainter::roundingAlignment( painter ) ) { p1 = p1.toPoint(); p2 = p2.toPoint(); } switch ( d_data->style ) { case QwtIntervalSymbol::Bar: { QwtPainter::drawLine( painter, p1, p2 ); if ( d_data->width > pw ) { if ( ( orientation == Qt::Horizontal ) && ( p1.y() == p2.y() ) ) { const double sw = d_data->width; const double y = p1.y() - sw / 2; QwtPainter::drawLine( painter, p1.x(), y, p1.x(), y + sw ); QwtPainter::drawLine( painter, p2.x(), y, p2.x(), y + sw ); } else if ( ( orientation == Qt::Vertical ) && ( p1.x() == p2.x() ) ) { const double sw = d_data->width; const double x = p1.x() - sw / 2; QwtPainter::drawLine( painter, x, p1.y(), x + sw, p1.y() ); QwtPainter::drawLine( painter, x, p2.y(), x + sw, p2.y() ); } else { const double sw = d_data->width; const double dx = p2.x() - p1.x(); const double dy = p2.y() - p1.y(); const double angle = qAtan2( dy, dx ) + M_PI_2; double dw2 = sw / 2.0; const double cx = qCos( angle ) * dw2; const double sy = qSin( angle ) * dw2; QwtPainter::drawLine( painter, p1.x() - cx, p1.y() - sy, p1.x() + cx, p1.y() + sy ); QwtPainter::drawLine( painter, p2.x() - cx, p2.y() - sy, p2.x() + cx, p2.y() + sy ); } } break; } case QwtIntervalSymbol::Box: { if ( d_data->width <= pw ) { QwtPainter::drawLine( painter, p1, p2 ); } else { if ( ( orientation == Qt::Horizontal ) && ( p1.y() == p2.y() ) ) { const double sw = d_data->width; const double y = p1.y() - d_data->width / 2; QwtPainter::drawRect( painter, p1.x(), y, p2.x() - p1.x(), sw ); } else if ( ( orientation == Qt::Vertical ) && ( p1.x() == p2.x() ) ) { const double sw = d_data->width; const double x = p1.x() - d_data->width / 2; QwtPainter::drawRect( painter, x, p1.y(), sw, p2.y() - p1.y() ); } else { const double sw = d_data->width; const double dx = p2.x() - p1.x(); const double dy = p2.y() - p1.y(); const double angle = qAtan2( dy, dx ) + M_PI_2; double dw2 = sw / 2.0; const int cx = qCos( angle ) * dw2; const int sy = qSin( angle ) * dw2; QPolygonF polygon; polygon += QPointF( p1.x() - cx, p1.y() - sy ); polygon += QPointF( p1.x() + cx, p1.y() + sy ); polygon += QPointF( p2.x() + cx, p2.y() + sy ); polygon += QPointF( p2.x() - cx, p2.y() - sy ); QwtPainter::drawPolygon( painter, polygon ); } } break; } default:; } } workbench-1.1.1/src/Qwt/qwt_interval_symbol.h000066400000000000000000000043161255417355300213310ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #ifndef QWT_INTERVAL_SYMBOL_H #define QWT_INTERVAL_SYMBOL_H #include "qwt_global.h" #include #include class QPainter; class QRect; class QPointF; /*! \brief A drawing primitive for displaying an interval like an error bar \sa QwtPlotIntervalCurve */ class QWT_EXPORT QwtIntervalSymbol { public: //! Symbol style enum Style { //! No Style. The symbol cannot be drawn. NoSymbol = -1, /*! The symbol displays a line with caps at the beginning/end. The size of the caps depends on the symbol width(). */ Bar, /*! The symbol displays a plain rectangle using pen() and brush(). The size of the rectangle depends on the translated interval and the width(), */ Box, /*! Styles >= UserSymbol are reserved for derived classes of QwtIntervalSymbol that overload draw() with additional application specific symbol types. */ UserSymbol = 1000 }; public: QwtIntervalSymbol( Style = NoSymbol ); QwtIntervalSymbol( const QwtIntervalSymbol & ); virtual ~QwtIntervalSymbol(); QwtIntervalSymbol &operator=( const QwtIntervalSymbol & ); bool operator==( const QwtIntervalSymbol & ) const; bool operator!=( const QwtIntervalSymbol & ) const; void setWidth( int ); int width() const; void setBrush( const QBrush& b ); const QBrush& brush() const; void setPen( const QPen & ); const QPen& pen() const; void setStyle( Style ); Style style() const; virtual void draw( QPainter *, Qt::Orientation, const QPointF& from, const QPointF& to ) const; private: class PrivateData; PrivateData* d_data; }; #endif workbench-1.1.1/src/Qwt/qwt_legend.cpp000066400000000000000000000321511255417355300177070ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #include "qwt_legend.h" #include "qwt_legend_itemmanager.h" #include "qwt_legend_item.h" #include "qwt_dyngrid_layout.h" #include "qwt_math.h" #include #include #include #include class QwtLegend::PrivateData { public: class LegendMap { public: void insert( const QwtLegendItemManager *, QWidget * ); void remove( const QwtLegendItemManager * ); void remove( QWidget * ); void clear(); uint count() const; inline const QWidget *find( const QwtLegendItemManager * ) const; inline QWidget *find( const QwtLegendItemManager * ); inline const QwtLegendItemManager *find( const QWidget * ) const; inline QwtLegendItemManager *find( const QWidget * ); const QMap &widgetMap() const; QMap &widgetMap(); private: QMap d_widgetMap; QMap d_itemMap; }; QwtLegend::LegendItemMode itemMode; LegendMap map; class LegendView; LegendView *view; }; class QwtLegend::PrivateData::LegendView: public QScrollArea { public: LegendView( QWidget *parent ): QScrollArea( parent ) { setFocusPolicy( Qt::NoFocus ); contentsWidget = new QWidget( this ); contentsWidget->setObjectName( "QwtLegendViewContents" ); setWidget( contentsWidget ); setWidgetResizable( false ); viewport()->setObjectName( "QwtLegendViewport" ); // QScrollArea::setWidget internally sets autoFillBackground to true // But we don't want a background. contentsWidget->setAutoFillBackground( false ); viewport()->setAutoFillBackground( false ); } virtual bool viewportEvent( QEvent *e ) { bool ok = QScrollArea::viewportEvent( e ); if ( e->type() == QEvent::Resize ) { QEvent event( QEvent::LayoutRequest ); QApplication::sendEvent( contentsWidget, &event ); } return ok; } QSize viewportSize( int w, int h ) const { const int sbHeight = horizontalScrollBar()->sizeHint().height(); const int sbWidth = verticalScrollBar()->sizeHint().width(); const int cw = contentsRect().width(); const int ch = contentsRect().height(); int vw = cw; int vh = ch; if ( w > vw ) vh -= sbHeight; if ( h > vh ) { vw -= sbWidth; if ( w > vw && vh == ch ) vh -= sbHeight; } return QSize( vw, vh ); } QWidget *contentsWidget; }; void QwtLegend::PrivateData::LegendMap::insert( const QwtLegendItemManager *item, QWidget *widget ) { d_itemMap.insert( item, widget ); d_widgetMap.insert( widget, item ); } void QwtLegend::PrivateData::LegendMap::remove( const QwtLegendItemManager *item ) { QWidget *widget = d_itemMap[item]; d_itemMap.remove( item ); d_widgetMap.remove( widget ); } void QwtLegend::PrivateData::LegendMap::remove( QWidget *widget ) { const QwtLegendItemManager *item = d_widgetMap[widget]; d_itemMap.remove( item ); d_widgetMap.remove( widget ); } void QwtLegend::PrivateData::LegendMap::clear() { /* We can't delete the widgets in the following loop, because we would get ChildRemoved events, changing d_itemMap, while we are iterating. */ QList widgets; QMap::const_iterator it; for ( it = d_itemMap.begin(); it != d_itemMap.end(); ++it ) widgets.append( it.value() ); d_itemMap.clear(); d_widgetMap.clear(); for ( int i = 0; i < widgets.size(); i++ ) delete widgets[i]; } uint QwtLegend::PrivateData::LegendMap::count() const { return d_itemMap.count(); } inline const QWidget *QwtLegend::PrivateData::LegendMap::find( const QwtLegendItemManager *item ) const { if ( !d_itemMap.contains( item ) ) return NULL; return d_itemMap[item]; } inline QWidget *QwtLegend::PrivateData::LegendMap::find( const QwtLegendItemManager *item ) { if ( !d_itemMap.contains( item ) ) return NULL; return d_itemMap[item]; } inline const QwtLegendItemManager *QwtLegend::PrivateData::LegendMap::find( const QWidget *widget ) const { QWidget *w = const_cast( widget ); if ( !d_widgetMap.contains( w ) ) return NULL; return d_widgetMap[w]; } inline QwtLegendItemManager *QwtLegend::PrivateData::LegendMap::find( const QWidget *widget ) { QWidget *w = const_cast( widget ); if ( !d_widgetMap.contains( w ) ) return NULL; return const_cast( d_widgetMap[w] ); } inline const QMap & QwtLegend::PrivateData::LegendMap::widgetMap() const { return d_widgetMap; } inline QMap & QwtLegend::PrivateData::LegendMap::widgetMap() { return d_widgetMap; } /*! Constructor \param parent Parent widget */ QwtLegend::QwtLegend( QWidget *parent ): QFrame( parent ) { setFrameStyle( NoFrame ); d_data = new QwtLegend::PrivateData; d_data->itemMode = QwtLegend::ReadOnlyItem; d_data->view = new QwtLegend::PrivateData::LegendView( this ); d_data->view->setObjectName( "QwtLegendView" ); d_data->view->setFrameStyle( NoFrame ); QwtDynGridLayout *gridLayout = new QwtDynGridLayout( d_data->view->contentsWidget ); gridLayout->setAlignment( Qt::AlignHCenter | Qt::AlignTop ); d_data->view->contentsWidget->installEventFilter( this ); QVBoxLayout *layout = new QVBoxLayout( this ); layout->setContentsMargins( 0, 0, 0, 0 ); layout->addWidget( d_data->view ); } //! Destructor QwtLegend::~QwtLegend() { delete d_data; } //! \sa LegendItemMode void QwtLegend::setItemMode( LegendItemMode mode ) { d_data->itemMode = mode; } //! \sa LegendItemMode QwtLegend::LegendItemMode QwtLegend::itemMode() const { return d_data->itemMode; } /*! The contents widget is the only child of the viewport of the internal QScrollArea and the parent widget of all legend items. \return Container widget of the legend items */ QWidget *QwtLegend::contentsWidget() { return d_data->view->contentsWidget; } /*! \return Horizontal scrollbar \sa verticalScrollBar() */ QScrollBar *QwtLegend::horizontalScrollBar() const { return d_data->view->horizontalScrollBar(); } /*! \return Vertical scrollbar \sa horizontalScrollBar() */ QScrollBar *QwtLegend::verticalScrollBar() const { return d_data->view->verticalScrollBar(); } /*! The contents widget is the only child of the viewport of the internal QScrollArea and the parent widget of all legend items. \return Container widget of the legend items */ const QWidget *QwtLegend::contentsWidget() const { return d_data->view->contentsWidget; } /*! Insert a new item for a plot item \param plotItem Plot item \param legendItem New legend item \note The parent of item will be changed to contentsWidget() */ void QwtLegend::insert( const QwtLegendItemManager *plotItem, QWidget *legendItem ) { if ( legendItem == NULL || plotItem == NULL ) return; QWidget *contentsWidget = d_data->view->contentsWidget; if ( legendItem->parent() != contentsWidget ) legendItem->setParent( contentsWidget ); legendItem->show(); d_data->map.insert( plotItem, legendItem ); layoutContents(); if ( contentsWidget->layout() ) { contentsWidget->layout()->addWidget( legendItem ); // set tab focus chain QWidget *w = NULL; for ( int i = 0; i < contentsWidget->layout()->count(); i++ ) { QLayoutItem *item = contentsWidget->layout()->itemAt( i ); if ( w && item->widget() ) QWidget::setTabOrder( w, item->widget() ); w = item->widget(); } } if ( parentWidget() && parentWidget()->layout() == NULL ) { /* updateGeometry() doesn't post LayoutRequest in certain situations, like when we are hidden. But we want the parent widget notified, so it can show/hide the legend depending on its items. */ QApplication::postEvent( parentWidget(), new QEvent( QEvent::LayoutRequest ) ); } } /*! Find the widget that represents a plot item \param plotItem Plot item \return Widget on the legend, or NULL */ QWidget *QwtLegend::find( const QwtLegendItemManager *plotItem ) const { return d_data->map.find( plotItem ); } /*! Find the widget that represents a plot item \param legendItem Legend item \return Widget on the legend, or NULL */ QwtLegendItemManager *QwtLegend::find( const QWidget *legendItem ) const { return d_data->map.find( legendItem ); } /*! Find the corresponding item for a plotItem and remove it from the item list. \param plotItem Plot item */ void QwtLegend::remove( const QwtLegendItemManager *plotItem ) { QWidget *legendItem = d_data->map.find( plotItem ); d_data->map.remove( legendItem ); delete legendItem; } //! Remove all items. void QwtLegend::clear() { bool doUpdate = updatesEnabled(); if ( doUpdate ) setUpdatesEnabled( false ); d_data->map.clear(); if ( doUpdate ) setUpdatesEnabled( true ); update(); } //! Return a size hint. QSize QwtLegend::sizeHint() const { QSize hint = d_data->view->contentsWidget->sizeHint(); hint += QSize( 2 * frameWidth(), 2 * frameWidth() ); return hint; } /*! \return The preferred height, for the width w. \param width Width */ int QwtLegend::heightForWidth( int width ) const { width -= 2 * frameWidth(); int h = d_data->view->contentsWidget->heightForWidth( width ); if ( h >= 0 ) h += 2 * frameWidth(); return h; } /*! Adjust contents widget and item layout to the size of the viewport(). */ void QwtLegend::layoutContents() { const QSize visibleSize = d_data->view->viewport()->contentsRect().size(); const QwtDynGridLayout *tl = qobject_cast( d_data->view->contentsWidget->layout() ); if ( tl ) { const int minW = int( tl->maxItemWidth() ) + 2 * tl->margin(); int w = qMax( visibleSize.width(), minW ); int h = qMax( tl->heightForWidth( w ), visibleSize.height() ); const int vpWidth = d_data->view->viewportSize( w, h ).width(); if ( w > vpWidth ) { w = qMax( vpWidth, minW ); h = qMax( tl->heightForWidth( w ), visibleSize.height() ); } d_data->view->contentsWidget->resize( w, h ); } } /*! Handle QEvent::ChildRemoved andQEvent::LayoutRequest events for the contentsWidget(). \param object Object to be filtered \param event Event */ bool QwtLegend::eventFilter( QObject *object, QEvent *event ) { if ( object == d_data->view->contentsWidget ) { switch ( event->type() ) { case QEvent::ChildRemoved: { const QChildEvent *ce = static_cast(event); if ( ce->child()->isWidgetType() ) { QWidget *w = static_cast< QWidget * >( ce->child() ); d_data->map.remove( w ); } break; } case QEvent::LayoutRequest: { layoutContents(); break; } default: break; } } return QFrame::eventFilter( object, event ); } //! Return true, if there are no legend items. bool QwtLegend::isEmpty() const { return d_data->map.count() == 0; } //! Return the number of legend items. uint QwtLegend::itemCount() const { return d_data->map.count(); } //! Return a list of all legend items QList QwtLegend::legendItems() const { const QMap &map = d_data->map.widgetMap(); QList list; QMap::const_iterator it; for ( it = map.begin(); it != map.end(); ++it ) list += it.key(); return list; }workbench-1.1.1/src/Qwt/qwt_legend.h000066400000000000000000000046511255417355300173600ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #ifndef QWT_LEGEND_H #define QWT_LEGEND_H #include "qwt_global.h" #include #include class QScrollBar; class QwtLegendItemManager; /*! \brief The legend widget The QwtLegend widget is a tabular arrangement of legend items. Legend items might be any type of widget, but in general they will be a QwtLegendItem. \sa QwtLegendItem, QwtLegendItemManager QwtPlot */ class QWT_EXPORT QwtLegend : public QFrame { Q_OBJECT public: /*! \brief Interaction mode for the legend items The default is QwtLegend::ReadOnlyItem. \sa setItemMode(), itemMode(), QwtLegendItem::IdentifierMode QwtLegendItem::clicked(), QwtLegendItem::checked(), QwtPlot::legendClicked(), QwtPlot::legendChecked() */ enum LegendItemMode { //! The legend item is not interactive, like a label ReadOnlyItem, //! The legend item is clickable, like a push button ClickableItem, //! The legend item is checkable, like a checkable button CheckableItem }; explicit QwtLegend( QWidget *parent = NULL ); virtual ~QwtLegend(); void setItemMode( LegendItemMode ); LegendItemMode itemMode() const; QWidget *contentsWidget(); const QWidget *contentsWidget() const; void insert( const QwtLegendItemManager *, QWidget * ); void remove( const QwtLegendItemManager * ); QWidget *find( const QwtLegendItemManager * ) const; QwtLegendItemManager *find( const QWidget * ) const; virtual QList legendItems() const; void clear(); bool isEmpty() const; uint itemCount() const; virtual bool eventFilter( QObject *, QEvent * ); virtual QSize sizeHint() const; virtual int heightForWidth( int w ) const; QScrollBar *horizontalScrollBar() const; QScrollBar *verticalScrollBar() const; protected: virtual void layoutContents(); private: class PrivateData; PrivateData *d_data; }; #endif workbench-1.1.1/src/Qwt/qwt_legend_item.cpp000066400000000000000000000221661255417355300207320ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #include "qwt_legend_item.h" #include "qwt_math.h" #include "qwt_painter.h" #include "qwt_symbol.h" #include #include #include #include #include #include #include static const int ButtonFrame = 2; static const int Margin = 2; static QSize buttonShift( const QwtLegendItem *w ) { QStyleOption option; option.init( w ); const int ph = w->style()->pixelMetric( QStyle::PM_ButtonShiftHorizontal, &option, w ); const int pv = w->style()->pixelMetric( QStyle::PM_ButtonShiftVertical, &option, w ); return QSize( ph, pv ); } class QwtLegendItem::PrivateData { public: PrivateData(): itemMode( QwtLegend::ReadOnlyItem ), isDown( false ), identifierSize( 8, 8 ), spacing( Margin ) { } QwtLegend::LegendItemMode itemMode; bool isDown; QSize identifierSize; QPixmap identifier; int spacing; }; /*! \param parent Parent widget */ QwtLegendItem::QwtLegendItem( QWidget *parent ): QwtTextLabel( parent ) { d_data = new PrivateData; setMargin( Margin ); setIndent( Margin + d_data->identifierSize.width() + 2 * d_data->spacing ); } //! Destructor QwtLegendItem::~QwtLegendItem() { delete d_data; d_data = NULL; } /*! Set the text to the legend item \param text Text label \sa QwtTextLabel::text() */ void QwtLegendItem::setText( const QwtText &text ) { const int flags = Qt::AlignLeft | Qt::AlignVCenter | Qt::TextExpandTabs | Qt::TextWordWrap; QwtText txt = text; txt.setRenderFlags( flags ); QwtTextLabel::setText( txt ); } /*! Set the item mode The default is QwtLegend::ReadOnlyItem \param mode Item mode \sa itemMode() */ void QwtLegendItem::setItemMode( QwtLegend::LegendItemMode mode ) { if ( mode != d_data->itemMode ) { d_data->itemMode = mode; d_data->isDown = false; setFocusPolicy( mode != QwtLegend::ReadOnlyItem ? Qt::TabFocus : Qt::NoFocus ); setMargin( ButtonFrame + Margin ); updateGeometry(); } } /*! Return the item mode \sa setItemMode() */ QwtLegend::LegendItemMode QwtLegendItem::itemMode() const { return d_data->itemMode; } /*! Assign the identifier The identifier needs to be created according to the identifierWidth() \param identifier Pixmap representing a plot item \sa identifier(), identifierWidth() */ void QwtLegendItem::setIdentifier( const QPixmap &identifier ) { d_data->identifier = identifier; update(); } /*! \return pixmap representing a plot item \sa setIdentifier() */ QPixmap QwtLegendItem::identifier() const { return d_data->identifier; } /*! Set the size for the identifier Default is 8x8 pixels \param size New size \sa identifierSize() */ void QwtLegendItem::setIdentifierSize( const QSize &size ) { QSize sz = size.expandedTo( QSize( 0, 0 ) ); if ( sz != d_data->identifierSize ) { d_data->identifierSize = sz; setIndent( margin() + d_data->identifierSize.width() + 2 * d_data->spacing ); updateGeometry(); } } /*! Return the width of the identifier \sa setIdentifierSize() */ QSize QwtLegendItem::identifierSize() const { return d_data->identifierSize; } /*! Change the spacing \param spacing Spacing \sa spacing(), identifierWidth(), QwtTextLabel::margin() */ void QwtLegendItem::setSpacing( int spacing ) { spacing = qMax( spacing, 0 ); if ( spacing != d_data->spacing ) { d_data->spacing = spacing; setIndent( margin() + d_data->identifierSize.width() + 2 * d_data->spacing ); } } /*! Return the spacing \sa setSpacing(), identifierWidth(), QwtTextLabel::margin() */ int QwtLegendItem::spacing() const { return d_data->spacing; } /*! Check/Uncheck a the item \param on check/uncheck \sa setItemMode() */ void QwtLegendItem::setChecked( bool on ) { if ( d_data->itemMode == QwtLegend::CheckableItem ) { const bool isBlocked = signalsBlocked(); blockSignals( true ); setDown( on ); blockSignals( isBlocked ); } } //! Return true, if the item is checked bool QwtLegendItem::isChecked() const { return d_data->itemMode == QwtLegend::CheckableItem && isDown(); } //! Set the item being down void QwtLegendItem::setDown( bool down ) { if ( down == d_data->isDown ) return; d_data->isDown = down; update(); if ( d_data->itemMode == QwtLegend::ClickableItem ) { if ( d_data->isDown ) Q_EMIT pressed(); else { Q_EMIT released(); Q_EMIT clicked(); } } if ( d_data->itemMode == QwtLegend::CheckableItem ) Q_EMIT checked( d_data->isDown ); } //! Return true, if the item is down bool QwtLegendItem::isDown() const { return d_data->isDown; } //! Return a size hint QSize QwtLegendItem::sizeHint() const { QSize sz = QwtTextLabel::sizeHint(); sz.setHeight( qMax( sz.height(), d_data->identifier.height() + 4 ) ); if ( d_data->itemMode != QwtLegend::ReadOnlyItem ) { sz += buttonShift( this ); sz = sz.expandedTo( QApplication::globalStrut() ); } return sz; } //! Paint event void QwtLegendItem::paintEvent( QPaintEvent *e ) { const QRect cr = contentsRect(); QPainter painter( this ); painter.setClipRegion( e->region() ); if ( d_data->isDown ) { qDrawWinButton( &painter, 0, 0, width(), height(), palette(), true ); } painter.save(); if ( d_data->isDown ) { const QSize shiftSize = buttonShift( this ); painter.translate( shiftSize.width(), shiftSize.height() ); } painter.setClipRect( cr ); drawContents( &painter ); if ( !d_data->identifier.isNull() ) { QRect identRect = cr; identRect.setX( identRect.x() + margin() ); if ( d_data->itemMode != QwtLegend::ReadOnlyItem ) identRect.setX( identRect.x() + ButtonFrame ); identRect.setSize( d_data->identifier.size() ); identRect.moveCenter( QPoint( identRect.center().x(), cr.center().y() ) ); painter.drawPixmap( identRect, d_data->identifier ); } painter.restore(); } //! Handle mouse press events void QwtLegendItem::mousePressEvent( QMouseEvent *e ) { if ( e->button() == Qt::LeftButton ) { switch ( d_data->itemMode ) { case QwtLegend::ClickableItem: { setDown( true ); return; } case QwtLegend::CheckableItem: { setDown( !isDown() ); return; } default:; } } QwtTextLabel::mousePressEvent( e ); } //! Handle mouse release events void QwtLegendItem::mouseReleaseEvent( QMouseEvent *e ) { if ( e->button() == Qt::LeftButton ) { switch ( d_data->itemMode ) { case QwtLegend::ClickableItem: { setDown( false ); return; } case QwtLegend::CheckableItem: { return; // do nothing, but accept } default:; } } QwtTextLabel::mouseReleaseEvent( e ); } //! Handle key press events void QwtLegendItem::keyPressEvent( QKeyEvent *e ) { if ( e->key() == Qt::Key_Space ) { switch ( d_data->itemMode ) { case QwtLegend::ClickableItem: { if ( !e->isAutoRepeat() ) setDown( true ); return; } case QwtLegend::CheckableItem: { if ( !e->isAutoRepeat() ) setDown( !isDown() ); return; } default:; } } QwtTextLabel::keyPressEvent( e ); } //! Handle key release events void QwtLegendItem::keyReleaseEvent( QKeyEvent *e ) { if ( e->key() == Qt::Key_Space ) { switch ( d_data->itemMode ) { case QwtLegend::ClickableItem: { if ( !e->isAutoRepeat() ) setDown( false ); return; } case QwtLegend::CheckableItem: { return; // do nothing, but accept } default:; } } QwtTextLabel::keyReleaseEvent( e ); }workbench-1.1.1/src/Qwt/qwt_legend_item.h000066400000000000000000000037641255417355300204020ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #ifndef QWT_LEGEND_ITEM_H #define QWT_LEGEND_ITEM_H #include "qwt_global.h" #include "qwt_legend.h" #include "qwt_text.h" #include "qwt_text_label.h" #include /*! \brief A widget representing something on a QwtLegend(). */ class QWT_EXPORT QwtLegendItem: public QwtTextLabel { Q_OBJECT public: explicit QwtLegendItem( QWidget *parent = 0 ); virtual ~QwtLegendItem(); void setItemMode( QwtLegend::LegendItemMode ); QwtLegend::LegendItemMode itemMode() const; void setSpacing( int spacing ); int spacing() const; virtual void setText( const QwtText & ); void setIdentifier( const QPixmap & ); QPixmap identifier() const; void setIdentifierSize( const QSize & ); QSize identifierSize() const; virtual QSize sizeHint() const; bool isChecked() const; public Q_SLOTS: void setChecked( bool on ); Q_SIGNALS: //! Signal, when the legend item has been clicked void clicked(); //! Signal, when the legend item has been pressed void pressed(); //! Signal, when the legend item has been relased void released(); //! Signal, when the legend item has been toggled void checked( bool ); protected: void setDown( bool ); bool isDown() const; virtual void paintEvent( QPaintEvent * ); virtual void mousePressEvent( QMouseEvent * ); virtual void mouseReleaseEvent( QMouseEvent * ); virtual void keyPressEvent( QKeyEvent * ); virtual void keyReleaseEvent( QKeyEvent * ); private: class PrivateData; PrivateData *d_data; }; #endif workbench-1.1.1/src/Qwt/qwt_legend_itemmanager.h000066400000000000000000000031061255417355300217230ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #ifndef QWT_LEGEND_ITEM_MANAGER_H #define QWT_LEGEND_ITEM_MANAGER_H #include "qwt_global.h" class QwtLegend; class QWidget; class QRectF; class QPainter; /*! \brief Abstract API to bind plot items to the legend */ class QWT_EXPORT QwtLegendItemManager { public: //! Constructor QwtLegendItemManager() { } //! Destructor virtual ~QwtLegendItemManager() { } /*! Update the widget that represents the item on the legend \param legend Legend \sa legendItem() */ virtual void updateLegend( QwtLegend *legend ) const = 0; /*! Allocate the widget that represents the item on the legend \return Allocated widget \sa updateLegend() QwtLegend() */ virtual QWidget *legendItem() const = 0; /*! QwtLegendItem can display an icon-identifier followed by a text. The icon helps to identify a plot item on the plot canvas and depends on the type of information, that is displayed. The default implementation paints nothing. */ virtual void drawLegendIdentifier( QPainter *, const QRectF & ) const { } }; #endif workbench-1.1.1/src/Qwt/qwt_magnifier.cpp000066400000000000000000000267071255417355300204240ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #include "qwt_magnifier.h" #include "qwt_math.h" #include #include class QwtMagnifier::PrivateData { public: PrivateData(): isEnabled( false ), wheelFactor( 0.9 ), wheelButtonState( Qt::NoButton ), mouseFactor( 0.95 ), mouseButton( Qt::RightButton ), mouseButtonState( Qt::NoButton ), keyFactor( 0.9 ), zoomInKey( Qt::Key_Plus ), zoomOutKey( Qt::Key_Minus ), zoomInKeyModifiers( Qt::NoModifier ), zoomOutKeyModifiers( Qt::NoModifier ), mousePressed( false ) { } bool isEnabled; double wheelFactor; int wheelButtonState; double mouseFactor; int mouseButton; int mouseButtonState; double keyFactor; int zoomInKey; int zoomOutKey; int zoomInKeyModifiers; int zoomOutKeyModifiers; bool mousePressed; bool hasMouseTracking; QPoint mousePos; }; /*! Constructor \param parent Widget to be magnified */ QwtMagnifier::QwtMagnifier( QWidget *parent ): QObject( parent ) { d_data = new PrivateData(); setEnabled( true ); } //! Destructor QwtMagnifier::~QwtMagnifier() { delete d_data; } /*! \brief En/disable the magnifier When enabled is true an event filter is installed for the observed widget, otherwise the event filter is removed. \param on true or false \sa isEnabled(), eventFilter() */ void QwtMagnifier::setEnabled( bool on ) { if ( d_data->isEnabled != on ) { d_data->isEnabled = on; QObject *o = parent(); if ( o ) { if ( d_data->isEnabled ) o->installEventFilter( this ); else o->removeEventFilter( this ); } } } /*! \return true when enabled, false otherwise \sa setEnabled(), eventFilter() */ bool QwtMagnifier::isEnabled() const { return d_data->isEnabled; } /*! \brief Change the wheel factor The wheel factor defines the ratio between the current range on the parent widget and the zoomed range for each step of the wheel. The default value is 0.9. \param factor Wheel factor \sa wheelFactor(), setWheelButtonState(), setMouseFactor(), setKeyFactor() */ void QwtMagnifier::setWheelFactor( double factor ) { d_data->wheelFactor = factor; } /*! \return Wheel factor \sa setWheelFactor() */ double QwtMagnifier::wheelFactor() const { return d_data->wheelFactor; } /*! Assign a mandatory button state for zooming in/out using the wheel. The default button state is Qt::NoButton. \param buttonState Button state \sa wheelButtonState() */ void QwtMagnifier::setWheelButtonState( int buttonState ) { d_data->wheelButtonState = buttonState; } /*! \return Wheel button state \sa setWheelButtonState() */ int QwtMagnifier::wheelButtonState() const { return d_data->wheelButtonState; } /*! \brief Change the mouse factor The mouse factor defines the ratio between the current range on the parent widget and the zoomed range for each vertical mouse movement. The default value is 0.95. \param factor Wheel factor \sa mouseFactor(), setMouseButton(), setWheelFactor(), setKeyFactor() */ void QwtMagnifier::setMouseFactor( double factor ) { d_data->mouseFactor = factor; } /*! \return Mouse factor \sa setMouseFactor() */ double QwtMagnifier::mouseFactor() const { return d_data->mouseFactor; } /*! Assign the mouse button, that is used for zooming in/out. The default value is Qt::RightButton. \param button Button \param buttonState Button state \sa getMouseButton() */ void QwtMagnifier::setMouseButton( int button, int buttonState ) { d_data->mouseButton = button; d_data->mouseButtonState = buttonState; } //! \sa setMouseButton() void QwtMagnifier::getMouseButton( int &button, int &buttonState ) const { button = d_data->mouseButton; buttonState = d_data->mouseButtonState; } /*! \brief Change the key factor The key factor defines the ratio between the current range on the parent widget and the zoomed range for each key press of the zoom in/out keys. The default value is 0.9. \param factor Key factor \sa keyFactor(), setZoomInKey(), setZoomOutKey(), setWheelFactor, setMouseFactor() */ void QwtMagnifier::setKeyFactor( double factor ) { d_data->keyFactor = factor; } /*! \return Key factor \sa setKeyFactor() */ double QwtMagnifier::keyFactor() const { return d_data->keyFactor; } /*! Assign the key, that is used for zooming in. The default combination is Qt::Key_Plus + Qt::NoModifier. \param key \param modifiers \sa getZoomInKey(), setZoomOutKey() */ void QwtMagnifier::setZoomInKey( int key, int modifiers ) { d_data->zoomInKey = key; d_data->zoomInKeyModifiers = modifiers; } //! \sa setZoomInKey() void QwtMagnifier::getZoomInKey( int &key, int &modifiers ) const { key = d_data->zoomInKey; modifiers = d_data->zoomInKeyModifiers; } /*! Assign the key, that is used for zooming out. The default combination is Qt::Key_Minus + Qt::NoModifier. \param key \param modifiers \sa getZoomOutKey(), setZoomOutKey() */ void QwtMagnifier::setZoomOutKey( int key, int modifiers ) { d_data->zoomOutKey = key; d_data->zoomOutKeyModifiers = modifiers; } //! \sa setZoomOutKey() void QwtMagnifier::getZoomOutKey( int &key, int &modifiers ) const { key = d_data->zoomOutKey; modifiers = d_data->zoomOutKeyModifiers; } /*! \brief Event filter When isEnabled() the mouse events of the observed widget are filtered. \param object Object to be filtered \param event Event \sa widgetMousePressEvent(), widgetMouseReleaseEvent(), widgetMouseMoveEvent(), widgetWheelEvent(), widgetKeyPressEvent() widgetKeyReleaseEvent() */ bool QwtMagnifier::eventFilter( QObject *object, QEvent *event ) { if ( object && object == parent() ) { switch ( event->type() ) { case QEvent::MouseButtonPress: { widgetMousePressEvent( ( QMouseEvent * )event ); break; } case QEvent::MouseMove: { widgetMouseMoveEvent( ( QMouseEvent * )event ); break; } case QEvent::MouseButtonRelease: { widgetMouseReleaseEvent( ( QMouseEvent * )event ); break; } case QEvent::Wheel: { widgetWheelEvent( ( QWheelEvent * )event ); break; } case QEvent::KeyPress: { widgetKeyPressEvent( ( QKeyEvent * )event ); break; } case QEvent::KeyRelease: { widgetKeyReleaseEvent( ( QKeyEvent * )event ); break; } default:; } } return QObject::eventFilter( object, event ); } /*! Handle a mouse press event for the observed widget. \param mouseEvent Mouse event \sa eventFilter(), widgetMouseReleaseEvent(), widgetMouseMoveEvent() */ void QwtMagnifier::widgetMousePressEvent( QMouseEvent *mouseEvent ) { if ( ( mouseEvent->button() != d_data->mouseButton) || parentWidget() == NULL ) { return; } if ( ( mouseEvent->modifiers() & Qt::KeyboardModifierMask ) != ( int )( d_data->mouseButtonState & Qt::KeyboardModifierMask ) ) { return; } d_data->hasMouseTracking = parentWidget()->hasMouseTracking(); parentWidget()->setMouseTracking( true ); d_data->mousePos = mouseEvent->pos(); d_data->mousePressed = true; } /*! Handle a mouse release event for the observed widget. \param mouseEvent Mouse event \sa eventFilter(), widgetMousePressEvent(), widgetMouseMoveEvent(), */ void QwtMagnifier::widgetMouseReleaseEvent( QMouseEvent *mouseEvent ) { Q_UNUSED( mouseEvent ); if ( d_data->mousePressed && parentWidget() ) { d_data->mousePressed = false; parentWidget()->setMouseTracking( d_data->hasMouseTracking ); } } /*! Handle a mouse move event for the observed widget. \param mouseEvent Mouse event \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent(), */ void QwtMagnifier::widgetMouseMoveEvent( QMouseEvent *mouseEvent ) { if ( !d_data->mousePressed ) return; const int dy = mouseEvent->pos().y() - d_data->mousePos.y(); if ( dy != 0 ) { double f = d_data->mouseFactor; if ( dy < 0 ) f = 1 / f; rescale( f ); } d_data->mousePos = mouseEvent->pos(); } /*! Handle a wheel event for the observed widget. \param wheelEvent Wheel event \sa eventFilter() */ void QwtMagnifier::widgetWheelEvent( QWheelEvent *wheelEvent ) { if ( ( wheelEvent->modifiers() & Qt::KeyboardModifierMask ) != ( int )( d_data->wheelButtonState & Qt::KeyboardModifierMask ) ) { return; } if ( d_data->wheelFactor != 0.0 ) { /* A positive delta indicates that the wheel was rotated forwards away from the user; a negative value indicates that the wheel was rotated backwards toward the user. Most mouse types work in steps of 15 degrees, in which case the delta value is a multiple of 120 (== 15 * 8). */ double f = qPow( d_data->wheelFactor, qAbs( wheelEvent->delta() / 120 ) ); if ( wheelEvent->delta() > 0 ) f = 1 / f; rescale( f ); } } /*! Handle a key press event for the observed widget. \param keyEvent Key event \sa eventFilter(), widgetKeyReleaseEvent() */ void QwtMagnifier::widgetKeyPressEvent( QKeyEvent *keyEvent ) { const int key = keyEvent->key(); const int state = keyEvent->modifiers(); if ( key == d_data->zoomInKey && state == d_data->zoomInKeyModifiers ) { rescale( d_data->keyFactor ); } else if ( key == d_data->zoomOutKey && state == d_data->zoomOutKeyModifiers ) { rescale( 1.0 / d_data->keyFactor ); } } /*! Handle a key release event for the observed widget. \param keyEvent Key event \sa eventFilter(), widgetKeyReleaseEvent() */ void QwtMagnifier::widgetKeyReleaseEvent( QKeyEvent *keyEvent ) { Q_UNUSED( keyEvent ); } //! \return Parent widget, where the rescaling happens QWidget *QwtMagnifier::parentWidget() { if ( parent()->inherits( "QWidget" ) ) return ( QWidget * )parent(); return NULL; } //! \return Parent widget, where the rescaling happens const QWidget *QwtMagnifier::parentWidget() const { if ( parent()->inherits( "QWidget" ) ) return ( const QWidget * )parent(); return NULL; } workbench-1.1.1/src/Qwt/qwt_magnifier.h000066400000000000000000000045361255417355300200650ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #ifndef QWT_MAGNIFIER_H #define QWT_MAGNIFIER_H 1 #include "qwt_global.h" #include class QWidget; class QMouseEvent; class QWheelEvent; class QKeyEvent; /*! \brief QwtMagnifier provides zooming, by magnifying in steps. Using QwtMagnifier a plot can be zoomed in/out in steps using keys, the mouse wheel or moving a mouse button in vertical direction. */ class QWT_EXPORT QwtMagnifier: public QObject { Q_OBJECT public: explicit QwtMagnifier( QWidget * ); virtual ~QwtMagnifier(); QWidget *parentWidget(); const QWidget *parentWidget() const; void setEnabled( bool ); bool isEnabled() const; // mouse void setMouseFactor( double ); double mouseFactor() const; void setMouseButton( int button, int buttonState = Qt::NoButton ); void getMouseButton( int &button, int &buttonState ) const; // mouse wheel void setWheelFactor( double ); double wheelFactor() const; void setWheelButtonState( int buttonState ); int wheelButtonState() const; // keyboard void setKeyFactor( double ); double keyFactor() const; void setZoomInKey( int key, int modifiers ); void getZoomInKey( int &key, int &modifiers ) const; void setZoomOutKey( int key, int modifiers ); void getZoomOutKey( int &key, int &modifiers ) const; virtual bool eventFilter( QObject *, QEvent * ); protected: /*! Rescale the parent widget \param factor Scale factor */ virtual void rescale( double factor ) = 0; virtual void widgetMousePressEvent( QMouseEvent * ); virtual void widgetMouseReleaseEvent( QMouseEvent * ); virtual void widgetMouseMoveEvent( QMouseEvent * ); virtual void widgetWheelEvent( QWheelEvent * ); virtual void widgetKeyPressEvent( QKeyEvent * ); virtual void widgetKeyReleaseEvent( QKeyEvent * ); private: class PrivateData; PrivateData *d_data; }; #endif workbench-1.1.1/src/Qwt/qwt_math.cpp000066400000000000000000000021131255417355300173750ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #include "qwt_math.h" /*! \brief Find the smallest value in an array \param array Pointer to an array \param size Array size */ double qwtGetMin( const double *array, int size ) { if ( size <= 0 ) return 0.0; double rv = array[0]; for ( int i = 1; i < size; i++ ) rv = qMin( rv, array[i] ); return rv; } /*! \brief Find the largest value in an array \param array Pointer to an array \param size Array size */ double qwtGetMax( const double *array, int size ) { if ( size <= 0 ) return 0.0; double rv = array[0]; for ( int i = 1; i < size; i++ ) rv = qMax( rv, array[i] ); return rv; } workbench-1.1.1/src/Qwt/qwt_math.h000066400000000000000000000100551255417355300170460ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #ifndef QWT_MATH_H #define QWT_MATH_H #include "qwt_global.h" #if defined(_MSC_VER) /* Microsoft says: Define _USE_MATH_DEFINES before including math.h to expose these macro definitions for common math constants. These are placed under an #ifdef since these commonly-defined names are not part of the C/C++ standards. */ #define _USE_MATH_DEFINES 1 #endif #include #include #include "qwt_global.h" #ifndef LOG10_2 #define LOG10_2 0.30102999566398119802 /* log10(2) */ #endif #ifndef LOG10_3 #define LOG10_3 0.47712125471966243540 /* log10(3) */ #endif #ifndef LOG10_5 #define LOG10_5 0.69897000433601885749 /* log10(5) */ #endif #ifndef M_2PI #define M_2PI 6.28318530717958623200 /* 2 pi */ #endif #ifndef LOG_MIN //! Mininum value for logarithmic scales #define LOG_MIN 1.0e-100 #endif #ifndef LOG_MAX //! Maximum value for logarithmic scales #define LOG_MAX 1.0e100 #endif #ifndef M_E #define M_E 2.7182818284590452354 /* e */ #endif #ifndef M_LOG2E #define M_LOG2E 1.4426950408889634074 /* log_2 e */ #endif #ifndef M_LOG10E #define M_LOG10E 0.43429448190325182765 /* log_10 e */ #endif #ifndef M_LN2 #define M_LN2 0.69314718055994530942 /* log_e 2 */ #endif #ifndef M_LN10 #define M_LN10 2.30258509299404568402 /* log_e 10 */ #endif #ifndef M_PI #define M_PI 3.14159265358979323846 /* pi */ #endif #ifndef M_PI_2 #define M_PI_2 1.57079632679489661923 /* pi/2 */ #endif #ifndef M_PI_4 #define M_PI_4 0.78539816339744830962 /* pi/4 */ #endif #ifndef M_1_PI #define M_1_PI 0.31830988618379067154 /* 1/pi */ #endif #ifndef M_2_PI #define M_2_PI 0.63661977236758134308 /* 2/pi */ #endif #ifndef M_2_SQRTPI #define M_2_SQRTPI 1.12837916709551257390 /* 2/sqrt(pi) */ #endif #ifndef M_SQRT2 #define M_SQRT2 1.41421356237309504880 /* sqrt(2) */ #endif #ifndef M_SQRT1_2 #define M_SQRT1_2 0.70710678118654752440 /* 1/sqrt(2) */ #endif QWT_EXPORT double qwtGetMin( const double *array, int size ); QWT_EXPORT double qwtGetMax( const double *array, int size ); /*! \brief Compare 2 values, relative to an interval Values are "equal", when : \f$\cdot value2 - value1 <= abs(intervalSize * 10e^{-6})\f$ \param value1 First value to compare \param value2 Second value to compare \param intervalSize interval size \return 0: if equal, -1: if value2 > value1, 1: if value1 > value2 */ inline int qwtFuzzyCompare( double value1, double value2, double intervalSize ) { const double eps = qAbs( 1.0e-6 * intervalSize ); if ( value2 - value1 > eps ) return -1; if ( value1 - value2 > eps ) return 1; return 0; } inline bool qwtFuzzyGreaterOrEqual( double d1, double d2 ) { return ( d1 >= d2 ) || qFuzzyCompare( d1, d2 ); } inline bool qwtFuzzyLessOrEqual( double d1, double d2 ) { return ( d1 <= d2 ) || qFuzzyCompare( d1, d2 ); } //! Return the sign inline int qwtSign( double x ) { if ( x > 0.0 ) return 1; else if ( x < 0.0 ) return ( -1 ); else return 0; } //! Return the square of a number inline double qwtSqr( double x ) { return x * x; } //! Like qRound, but without converting the result to an int inline double qwtRoundF(double d) { return ::floor( d + 0.5 ); } //! Like qFloor, but without converting the result to an int inline double qwtFloorF(double d) { return ::floor( d ); } //! Like qCeil, but without converting the result to an int inline double qwtCeilF(double d) { return ::ceil( d ); } #endif workbench-1.1.1/src/Qwt/qwt_matrix_raster_data.cpp000066400000000000000000000165561255417355300223410ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #include "qwt_matrix_raster_data.h" #include #include class QwtMatrixRasterData::PrivateData { public: PrivateData(): resampleMode(QwtMatrixRasterData::NearestNeighbour), numColumns(0) { } inline double value(size_t row, size_t col) const { return values.data()[ row * numColumns + col ]; } QwtMatrixRasterData::ResampleMode resampleMode; QVector values; size_t numColumns; size_t numRows; double dx; double dy; }; //! Constructor QwtMatrixRasterData::QwtMatrixRasterData() { d_data = new PrivateData(); update(); } //! Destructor QwtMatrixRasterData::~QwtMatrixRasterData() { delete d_data; } /*! \brief Set the resampling algorithm \param mode Resampling mode \sa resampleMode(), value() */ void QwtMatrixRasterData::setResampleMode(ResampleMode mode) { d_data->resampleMode = mode; } /*! \return resampling algorithm \sa setResampleMode(), value() */ QwtMatrixRasterData::ResampleMode QwtMatrixRasterData::resampleMode() const { return d_data->resampleMode; } /*! \brief Assign the bounding interval for an axis Setting the bounding intervals for the X/Y axis is mandatory to define the positions for the values of the value matrix. The interval in Z direction defines the possible range for the values in the matrix, what is f.e used by QwtPlotSpectrogram to map values to colors. The Z-interval might be the bounding interval of the values in the matrix, but usually it isn't. ( f.e a interval of 0.0-100.0 for values in percentage ) \param axis X, Y or Z axis \param interval Interval \sa QwtRasterData::interval(), setValueMatrix() */ void QwtMatrixRasterData::setInterval( Qt::Axis axis, const QwtInterval &interval ) { QwtRasterData::setInterval( axis, interval ); update(); } /*! \brief Assign a value matrix The positions of the values are calculated by dividing the bounding rectangle of the X/Y intervals into equidistant rectangles ( pixels ). Each value corresponds to the center of a pixel. \param values Vector of values \param numColumns Number of columns \sa valueMatrix(), numColumns(), numRows(), setInterval()() */ void QwtMatrixRasterData::setValueMatrix( const QVector &values, size_t numColumns ) { d_data->values = values; d_data->numColumns = numColumns; update(); } /*! \return Value matrix \sa setValueMatrix(), numColumns(), numRows(), setInterval() */ const QVector QwtMatrixRasterData::valueMatrix() const { return d_data->values; } /*! \return Number of columns of the value matrix \sa valueMatrix(), numRows(), setValueMatrix() */ size_t QwtMatrixRasterData::numColumns() const { return d_data->numColumns; } /*! \return Number of rows of the value matrix \sa valueMatrix(), numColumns(), setValueMatrix() */ size_t QwtMatrixRasterData::numRows() const { return d_data->numRows; } /*! \brief Pixel hint - NearestNeighbour\n pixelHint() returns the surrounding pixel of the top left value in the matrix. - BilinearInterpolation\n Returns an empty rectangle recommending to render in target device ( f.e. screen ) resolution. \sa ResampleMode, setMatrix(), setInterval() */ QRectF QwtMatrixRasterData::pixelHint( const QRectF & ) const { QRectF rect; if ( d_data->resampleMode == NearestNeighbour ) { const QwtInterval intervalX = interval( Qt::XAxis ); const QwtInterval intervalY = interval( Qt::YAxis ); if ( intervalX.isValid() && intervalY.isValid() ) { rect = QRectF( intervalX.minValue(), intervalY.minValue(), d_data->dx, d_data->dy ); } } return rect; } /*! \return the value at a raster position \param x X value in plot coordinates \param y Y value in plot coordinates \sa ResampleMode */ double QwtMatrixRasterData::value( double x, double y ) const { const QwtInterval xInterval = interval( Qt::XAxis ); const QwtInterval yInterval = interval( Qt::YAxis ); if ( !( xInterval.contains(x) && yInterval.contains(y) ) ) return qQNaN(); double value; switch( d_data->resampleMode ) { case BilinearInterpolation: { int col1 = qRound( (x - xInterval.minValue() ) / d_data->dx ) - 1; int row1 = qRound( (y - yInterval.minValue() ) / d_data->dy ) - 1; int col2 = col1 + 1; int row2 = row1 + 1; if ( col1 < 0 ) col1 = col2; else if ( col2 >= (int)d_data->numColumns ) col2 = col1; if ( row1 < 0 ) row1 = row2; else if ( row2 >= (int)d_data->numRows ) row2 = row1; const double v11 = d_data->value( row1, col1 ); const double v21 = d_data->value( row1, col2 ); const double v12 = d_data->value( row2, col1 ); const double v22 = d_data->value( row2, col2 ); const double x2 = xInterval.minValue() + ( col2 + 0.5 ) * d_data->dx; const double y2 = yInterval.minValue() + ( row2 + 0.5 ) * d_data->dy; const double rx = ( x2 - x ) / d_data->dx; const double ry = ( y2 - y ) / d_data->dy; const double vr1 = rx * v11 + ( 1.0 - rx ) * v21; const double vr2 = rx * v12 + ( 1.0 - rx ) * v22; value = ry * vr1 + ( 1.0 - ry ) * vr2; break; } case NearestNeighbour: default: { uint row = uint( (y - yInterval.minValue() ) / d_data->dy ); uint col = uint( (x - xInterval.minValue() ) / d_data->dx ); // In case of intervals, where the maximum is included // we get out of bound for row/col, when the value for the // maximum is requested. Instead we return the value // from the last row/col if ( row >= d_data->numRows ) row = d_data->numRows - 1; if ( col >= d_data->numColumns ) col = d_data->numColumns - 1; value = d_data->value( row, col ); } } return value; } void QwtMatrixRasterData::update() { d_data->numRows = 0; d_data->dx = 0.0; d_data->dy = 0.0; if ( d_data->numColumns > 0 ) { d_data->numRows = d_data->values.size() / d_data->numColumns; const QwtInterval xInterval = interval( Qt::XAxis ); const QwtInterval yInterval = interval( Qt::YAxis ); if ( xInterval.isValid() ) d_data->dx = xInterval.width() / d_data->numColumns; if ( yInterval.isValid() ) d_data->dy = yInterval.width() / d_data->numRows; } } workbench-1.1.1/src/Qwt/qwt_matrix_raster_data.h000066400000000000000000000037701255417355300220000ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #ifndef QWT_MATRIX_RASTER_DATA_H #define QWT_MATRIX_RASTER_DATA_H 1 #include "qwt_global.h" #include "qwt_raster_data.h" #include /*! \brief A class representing a matrix of values as raster data QwtMatrixRasterData implements an interface for a matrix of equidistant values, that can be used by a QwtPlotRasterItem. It implements a couple of resampling algorithms, to provide values for positions, that or not on the value matrix. */ class QWT_EXPORT QwtMatrixRasterData: public QwtRasterData { public: /*! \brief Resampling algorithm The default setting is NearestNeighbour; */ enum ResampleMode { /*! Return the value from the matrix, that is nearest to the the requested position. */ NearestNeighbour, /*! Interpolate the value from the distances and values of the 4 surrounding values in the matrix, */ BilinearInterpolation }; QwtMatrixRasterData(); virtual ~QwtMatrixRasterData(); void setResampleMode(ResampleMode mode); ResampleMode resampleMode() const; virtual void setInterval( Qt::Axis, const QwtInterval & ); void setValueMatrix( const QVector &values, size_t numColumns ); const QVector valueMatrix() const; size_t numColumns() const; size_t numRows() const; virtual QRectF pixelHint( const QRectF & ) const; virtual double value( double x, double y ) const; private: void update(); class PrivateData; PrivateData *d_data; }; #endif workbench-1.1.1/src/Qwt/qwt_null_paintdevice.cpp000066400000000000000000000236121255417355300220000ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #include "qwt_null_paintdevice.h" #include #include class QwtNullPaintDevice::PrivateData { public: PrivateData(): size( 0, 0 ) { } QSize size; }; class QwtNullPaintDevice::PaintEngine: public QPaintEngine { public: PaintEngine( QPaintEngine::PaintEngineFeatures ); virtual bool begin( QPaintDevice * ); virtual bool end(); virtual Type type () const; virtual void updateState(const QPaintEngineState &); virtual void drawRects(const QRect *, int ); virtual void drawRects(const QRectF *, int ); virtual void drawLines(const QLine *, int ); virtual void drawLines(const QLineF *, int ); virtual void drawEllipse(const QRectF &); virtual void drawEllipse(const QRect &); virtual void drawPath(const QPainterPath &); virtual void drawPoints(const QPointF *, int ); virtual void drawPoints(const QPoint *, int ); virtual void drawPolygon(const QPointF *, int , PolygonDrawMode ); virtual void drawPolygon(const QPoint *, int , PolygonDrawMode ); virtual void drawPixmap(const QRectF &, const QPixmap &, const QRectF &); virtual void drawTextItem(const QPointF &, const QTextItem &); virtual void drawTiledPixmap(const QRectF &, const QPixmap &, const QPointF &s); virtual void drawImage(const QRectF &, const QImage &, const QRectF &, Qt::ImageConversionFlags ); private: QwtNullPaintDevice *d_device; }; QwtNullPaintDevice::PaintEngine::PaintEngine( QPaintEngine::PaintEngineFeatures features ): QPaintEngine( features ), d_device(NULL) { } bool QwtNullPaintDevice::PaintEngine::begin( QPaintDevice *device ) { d_device = static_cast( device ); return true; } bool QwtNullPaintDevice::PaintEngine::end() { d_device = NULL; return true; } QPaintEngine::Type QwtNullPaintDevice::PaintEngine::type () const { return QPaintEngine::User; } void QwtNullPaintDevice::PaintEngine::drawRects( const QRect *rects, int rectCount) { if ( d_device ) d_device->drawRects( rects, rectCount ); } void QwtNullPaintDevice::PaintEngine::drawRects( const QRectF *rects, int rectCount) { if ( d_device ) d_device->drawRects( rects, rectCount ); } void QwtNullPaintDevice::PaintEngine::drawLines( const QLine *lines, int lineCount) { if ( d_device ) d_device->drawLines( lines, lineCount ); } void QwtNullPaintDevice::PaintEngine::drawLines( const QLineF *lines, int lineCount) { if ( d_device ) d_device->drawLines( lines, lineCount ); } void QwtNullPaintDevice::PaintEngine::drawEllipse( const QRectF &rect) { if ( d_device ) d_device->drawEllipse( rect ); } void QwtNullPaintDevice::PaintEngine::drawEllipse( const QRect &rect) { if ( d_device ) d_device->drawEllipse( rect ); } void QwtNullPaintDevice::PaintEngine::drawPath( const QPainterPath &path) { if ( d_device ) d_device->drawPath( path ); } void QwtNullPaintDevice::PaintEngine::drawPoints( const QPointF *points, int pointCount) { if ( d_device ) d_device->drawPoints( points, pointCount ); } void QwtNullPaintDevice::PaintEngine::drawPoints( const QPoint *points, int pointCount) { if ( d_device ) d_device->drawPoints( points, pointCount ); } void QwtNullPaintDevice::PaintEngine::drawPolygon( const QPointF *points, int pointCount, PolygonDrawMode mode) { if ( d_device ) d_device->drawPolygon( points, pointCount, mode ); } void QwtNullPaintDevice::PaintEngine::drawPolygon( const QPoint *points, int pointCount, PolygonDrawMode mode) { if ( d_device ) d_device->drawPolygon( points, pointCount, mode ); } void QwtNullPaintDevice::PaintEngine::drawPixmap( const QRectF &rect, const QPixmap &pm, const QRectF &subRect ) { if ( d_device ) d_device->drawPixmap( rect, pm, subRect ); } void QwtNullPaintDevice::PaintEngine::drawTextItem( const QPointF &pos, const QTextItem &textItem) { if ( d_device ) d_device->drawTextItem( pos, textItem ); } void QwtNullPaintDevice::PaintEngine::drawTiledPixmap( const QRectF &rect, const QPixmap &pixmap, const QPointF &subRect) { if ( d_device ) d_device->drawTiledPixmap( rect, pixmap, subRect ); } void QwtNullPaintDevice::PaintEngine::drawImage( const QRectF &rect, const QImage &image, const QRectF &subRect, Qt::ImageConversionFlags flags) { if ( d_device ) d_device->drawImage( rect, image, subRect, flags ); } void QwtNullPaintDevice::PaintEngine::updateState( const QPaintEngineState &state) { if ( d_device ) d_device->updateState( state ); } //! Constructor QwtNullPaintDevice::QwtNullPaintDevice( QPaintEngine::PaintEngineFeatures features ) { init( features ); } //! Constructor QwtNullPaintDevice::QwtNullPaintDevice( const QSize &size, QPaintEngine::PaintEngineFeatures features ) { init( features ); d_data->size = size; } void QwtNullPaintDevice::init( QPaintEngine::PaintEngineFeatures features ) { d_engine = new PaintEngine( features ); d_data = new PrivateData; } //! Destructor QwtNullPaintDevice::~QwtNullPaintDevice() { delete d_engine; delete d_data; } /*! Set the size of the paint device \param size Size \sa size() */ void QwtNullPaintDevice::setSize( const QSize & size ) { d_data->size = size; } /*! \return Size of the paint device \sa setSize() */ QSize QwtNullPaintDevice::size() const { return d_data->size; } //! See QPaintDevice::paintEngine() QPaintEngine *QwtNullPaintDevice::paintEngine() const { return d_engine; } /*! See QPaintDevice::metric() \sa setSize() */ int QwtNullPaintDevice::metric( PaintDeviceMetric metric ) const { static QPixmap pm; int value; switch ( metric ) { case PdmWidth: value = qMax( d_data->size.width(), 0 ); break; case PdmHeight: value = qMax( d_data->size.height(), 0 ); break; case PdmNumColors: value = 16777216; break; case PdmDepth: value = 24; break; case PdmPhysicalDpiX: case PdmDpiY: case PdmPhysicalDpiY: case PdmWidthMM: case PdmHeightMM: case PdmDpiX: default: value = 0; } return value; } //! See QPaintEngine::drawRects() void QwtNullPaintDevice::drawRects( const QRect *rects, int rectCount) { Q_UNUSED(rects); Q_UNUSED(rectCount); } //! See QPaintEngine::drawRects() void QwtNullPaintDevice::drawRects( const QRectF *rects, int rectCount) { Q_UNUSED(rects); Q_UNUSED(rectCount); } //! See QPaintEngine::drawLines() void QwtNullPaintDevice::drawLines( const QLine *lines, int lineCount) { Q_UNUSED(lines); Q_UNUSED(lineCount); } //! See QPaintEngine::drawLines() void QwtNullPaintDevice::drawLines( const QLineF *lines, int lineCount) { Q_UNUSED(lines); Q_UNUSED(lineCount); } //! See QPaintEngine::drawEllipse() void QwtNullPaintDevice::drawEllipse( const QRectF &rect ) { Q_UNUSED(rect); } //! See QPaintEngine::drawEllipse() void QwtNullPaintDevice::drawEllipse( const QRect &rect ) { Q_UNUSED(rect); } //! See QPaintEngine::drawPath() void QwtNullPaintDevice::drawPath( const QPainterPath &path ) { Q_UNUSED(path); } //! See QPaintEngine::drawPoints() void QwtNullPaintDevice::drawPoints( const QPointF *points, int pointCount) { Q_UNUSED(points); Q_UNUSED(pointCount); } //! See QPaintEngine::drawPoints() void QwtNullPaintDevice::drawPoints( const QPoint *points, int pointCount) { Q_UNUSED(points); Q_UNUSED(pointCount); } //! See QPaintEngine::drawPolygon() void QwtNullPaintDevice::drawPolygon( const QPointF *points, int pointCount, QPaintEngine::PolygonDrawMode mode) { Q_UNUSED(points); Q_UNUSED(pointCount); Q_UNUSED(mode); } //! See QPaintEngine::drawPolygon() void QwtNullPaintDevice::drawPolygon( const QPoint *points, int pointCount, QPaintEngine::PolygonDrawMode mode) { Q_UNUSED(points); Q_UNUSED(pointCount); Q_UNUSED(mode); } //! See QPaintEngine::drawPixmap() void QwtNullPaintDevice::drawPixmap( const QRectF &rect, const QPixmap &pm, const QRectF &subRect ) { Q_UNUSED(rect); Q_UNUSED(pm); Q_UNUSED(subRect); } //! See QPaintEngine::drawTextItem() void QwtNullPaintDevice::drawTextItem( const QPointF &pos, const QTextItem &textItem) { Q_UNUSED(pos); Q_UNUSED(textItem); } //! See QPaintEngine::drawTiledPixmap() void QwtNullPaintDevice::drawTiledPixmap( const QRectF &rect, const QPixmap &pixmap, const QPointF &subRect) { Q_UNUSED(rect); Q_UNUSED(pixmap); Q_UNUSED(subRect); } //! See QPaintEngine::drawImage() void QwtNullPaintDevice::drawImage( const QRectF &rect, const QImage &image, const QRectF &subRect, Qt::ImageConversionFlags flags) { Q_UNUSED(rect); Q_UNUSED(image); Q_UNUSED(subRect); Q_UNUSED(flags); } //! See QPaintEngine::updateState() void QwtNullPaintDevice::updateState( const QPaintEngineState &state ) { Q_UNUSED(state); } workbench-1.1.1/src/Qwt/qwt_null_paintdevice.h000066400000000000000000000052451255417355300214470ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #ifndef QWT_NULL_PAINT_DEVICE_H #define QWT_NULL_PAINT_DEVICE_H 1 #include "qwt_global.h" #include #include /*! \brief A null paint device doing nothing Sometimes important layout/rendering geometries are not available or changable from the public Qt class interface. ( f.e hidden in the style implementation ). QwtNullPaintDevice can be used to manipulate or filter out these informations by analyzing the stream of paint primitives. F.e. QwtNullPaintDevice is used by QwtPlotCanvas to identify styled backgrounds with rounded corners. */ class QWT_EXPORT QwtNullPaintDevice: public QPaintDevice { public: QwtNullPaintDevice( QPaintEngine::PaintEngineFeatures ); QwtNullPaintDevice( const QSize &size, QPaintEngine::PaintEngineFeatures ); virtual ~QwtNullPaintDevice(); void setSize( const QSize &); QSize size() const; virtual QPaintEngine *paintEngine() const; virtual int metric( PaintDeviceMetric metric ) const; virtual void drawRects(const QRect *, int ); virtual void drawRects(const QRectF *, int ); virtual void drawLines(const QLine *, int ); virtual void drawLines(const QLineF *, int ); virtual void drawEllipse(const QRectF &); virtual void drawEllipse(const QRect &); virtual void drawPath(const QPainterPath &); virtual void drawPoints(const QPointF *, int ); virtual void drawPoints(const QPoint *, int ); virtual void drawPolygon( const QPointF *, int , QPaintEngine::PolygonDrawMode ); virtual void drawPolygon( const QPoint *, int , QPaintEngine::PolygonDrawMode ); virtual void drawPixmap(const QRectF &, const QPixmap &, const QRectF &); virtual void drawTextItem(const QPointF &, const QTextItem &); virtual void drawTiledPixmap(const QRectF &, const QPixmap &, const QPointF &s); virtual void drawImage(const QRectF &, const QImage &, const QRectF &, Qt::ImageConversionFlags ); virtual void updateState( const QPaintEngineState &state ); private: void init( QPaintEngine::PaintEngineFeatures ); class PaintEngine; PaintEngine *d_engine; class PrivateData; PrivateData *d_data; }; #endif workbench-1.1.1/src/Qwt/qwt_painter.cpp000066400000000000000000000474131255417355300201220ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #include "qwt_painter.h" #include "qwt_math.h" #include "qwt_clipper.h" #include "qwt_color_map.h" #include "qwt_scale_map.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include bool QwtPainter::d_polylineSplitting = true; bool QwtPainter::d_roundingAlignment = true; static inline bool isClippingNeeded( const QPainter *painter, QRectF &clipRect ) { bool doClipping = false; const QPaintEngine *pe = painter->paintEngine(); if ( pe && pe->type() == QPaintEngine::SVG ) { // The SVG paint engine ignores any clipping, if ( painter->hasClipping() ) { doClipping = true; clipRect = painter->clipRegion().boundingRect(); } } return doClipping; } static inline void drawPolyline( QPainter *painter, const QPointF *points, int pointCount, bool polylineSplitting ) { bool doSplit = false; if ( polylineSplitting ) { const QPaintEngine *pe = painter->paintEngine(); if ( pe && pe->type() == QPaintEngine::Raster ) { /* The raster paint engine seems to use some algo with O(n*n). ( Qt 4.3 is better than Qt 4.2, but remains unacceptable) To work around this problem, we have to split the polygon into smaller pieces. */ doSplit = true; } } if ( doSplit ) { const int splitSize = 20; for ( int i = 0; i < pointCount; i += splitSize ) { const int n = qMin( splitSize + 1, pointCount - i ); painter->drawPolyline( points + i, n ); } } else painter->drawPolyline( points, pointCount ); } static inline void unscaleFont( QPainter *painter ) { if ( painter->font().pixelSize() >= 0 ) return; static QSize screenResolution; if ( !screenResolution.isValid() ) { QDesktopWidget *desktop = QApplication::desktop(); if ( desktop ) { screenResolution.setWidth( desktop->logicalDpiX() ); screenResolution.setHeight( desktop->logicalDpiY() ); } } const QPaintDevice *pd = painter->device(); if ( pd->logicalDpiX() != screenResolution.width() || pd->logicalDpiY() != screenResolution.height() ) { QFont pixelFont( painter->font(), QApplication::desktop() ); pixelFont.setPixelSize( QFontInfo( pixelFont ).pixelSize() ); painter->setFont( pixelFont ); } } /*! Check if the painter is using a paint engine, that aligns coordinates to integers. Today these are all paint engines beside QPaintEngine::Pdf and QPaintEngine::SVG. \param painter Painter \return true, when the paint engine is aligning \sa setRoundingAlignment() */ bool QwtPainter::isAligning( QPainter *painter ) { if ( painter && painter->isActive() ) { switch ( painter->paintEngine()->type() ) { case QPaintEngine::Pdf: case QPaintEngine::SVG: return false; default:; } } return true; } /*! Enable whether coordinates should be rounded, before they are painted to a paint engine that floors to integer values. For other paint engines this ( Pdf, SVG ), this flag has no effect. QwtPainter stores this flag only, the rounding itsself is done in the painting code ( f.e the plot items ). The default setting is true. \sa roundingAlignment(), isAligning() */ void QwtPainter::setRoundingAlignment( bool enable ) { d_roundingAlignment = enable; } /*! \brief En/Disable line splitting for the raster paint engine The raster paint engine paints polylines of many points much faster when they are splitted in smaller chunks. \sa polylineSplitting() */ void QwtPainter::setPolylineSplitting( bool enable ) { d_polylineSplitting = enable; } //! Wrapper for QPainter::drawPath() void QwtPainter::drawPath( QPainter *painter, const QPainterPath &path ) { painter->drawPath( path ); } //! Wrapper for QPainter::drawRect() void QwtPainter::drawRect( QPainter *painter, double x, double y, double w, double h ) { drawRect( painter, QRectF( x, y, w, h ) ); } //! Wrapper for QPainter::drawRect() void QwtPainter::drawRect( QPainter *painter, const QRectF &rect ) { const QRectF r = rect; QRectF clipRect; const bool deviceClipping = isClippingNeeded( painter, clipRect ); if ( deviceClipping ) { if ( !clipRect.intersects( r ) ) return; if ( !clipRect.contains( r ) ) { fillRect( painter, r & clipRect, painter->brush() ); painter->save(); painter->setBrush( Qt::NoBrush ); drawPolyline( painter, QPolygonF( r ) ); painter->restore(); return; } } painter->drawRect( r ); } //! Wrapper for QPainter::fillRect() void QwtPainter::fillRect( QPainter *painter, const QRectF &rect, const QBrush &brush ) { if ( !rect.isValid() ) return; QRectF clipRect; const bool deviceClipping = isClippingNeeded( painter, clipRect ); /* Performance of Qt4 is horrible for non trivial brushs. Without clipping expect minutes or hours for repainting large rects (might result from zooming) */ if ( deviceClipping ) clipRect &= painter->window(); else clipRect = painter->window(); if ( painter->hasClipping() ) clipRect &= painter->clipRegion().boundingRect(); QRectF r = rect; if ( deviceClipping ) r = r.intersect( clipRect ); if ( r.isValid() ) painter->fillRect( r, brush ); } //! Wrapper for QPainter::drawPie() void QwtPainter::drawPie( QPainter *painter, const QRectF &rect, int a, int alen ) { QRectF clipRect; const bool deviceClipping = isClippingNeeded( painter, clipRect ); if ( deviceClipping && !clipRect.contains( rect ) ) return; painter->drawPie( rect, a, alen ); } //! Wrapper for QPainter::drawEllipse() void QwtPainter::drawEllipse( QPainter *painter, const QRectF &rect ) { QRectF clipRect; const bool deviceClipping = isClippingNeeded( painter, clipRect ); if ( deviceClipping && !clipRect.contains( rect ) ) return; painter->drawEllipse( rect ); } //! Wrapper for QPainter::drawText() void QwtPainter::drawText( QPainter *painter, double x, double y, const QString &text ) { drawText( painter, QPointF( x, y ), text ); } //! Wrapper for QPainter::drawText() void QwtPainter::drawText( QPainter *painter, const QPointF &pos, const QString &text ) { QRectF clipRect; const bool deviceClipping = isClippingNeeded( painter, clipRect ); if ( deviceClipping && !clipRect.contains( pos ) ) return; painter->save(); unscaleFont( painter ); painter->drawText( pos, text ); painter->restore(); } //! Wrapper for QPainter::drawText() void QwtPainter::drawText( QPainter *painter, double x, double y, double w, double h, int flags, const QString &text ) { drawText( painter, QRectF( x, y, w, h ), flags, text ); } //! Wrapper for QPainter::drawText() void QwtPainter::drawText( QPainter *painter, const QRectF &rect, int flags, const QString &text ) { painter->save(); unscaleFont( painter ); painter->drawText( rect, flags, text ); painter->restore(); } #ifndef QT_NO_RICHTEXT /*! Draw a text document into a rectangle \param painter Painter \param rect Traget rectangle \param flags Alignments/Text flags, see QPainter::drawText() \param text Text document */ void QwtPainter::drawSimpleRichText( QPainter *painter, const QRectF &rect, int flags, const QTextDocument &text ) { QTextDocument *txt = text.clone(); painter->save(); painter->setFont( txt->defaultFont() ); unscaleFont( painter ); txt->setDefaultFont( painter->font() ); txt->setPageSize( QSizeF( rect.width(), QWIDGETSIZE_MAX ) ); QAbstractTextDocumentLayout* layout = txt->documentLayout(); const double height = layout->documentSize().height(); double y = rect.y(); if ( flags & Qt::AlignBottom ) y += ( rect.height() - height ); else if ( flags & Qt::AlignVCenter ) y += ( rect.height() - height ) / 2; QAbstractTextDocumentLayout::PaintContext context; context.palette.setColor( QPalette::Text, painter->pen().color() ); painter->translate( rect.x(), y ); layout->draw( painter, context ); painter->restore(); delete txt; } #endif // !QT_NO_RICHTEXT //! Wrapper for QPainter::drawLine() void QwtPainter::drawLine( QPainter *painter, const QPointF &p1, const QPointF &p2 ) { QRectF clipRect; const bool deviceClipping = isClippingNeeded( painter, clipRect ); if ( deviceClipping && !( clipRect.contains( p1 ) && clipRect.contains( p2 ) ) ) { QPolygonF polygon; polygon += p1; polygon += p2; drawPolyline( painter, polygon ); return; } painter->drawLine( p1, p2 ); } //! Wrapper for QPainter::drawPolygon() void QwtPainter::drawPolygon( QPainter *painter, const QPolygonF &polygon ) { QRectF clipRect; const bool deviceClipping = isClippingNeeded( painter, clipRect ); QPolygonF cpa = polygon; if ( deviceClipping ) cpa = QwtClipper::clipPolygonF( clipRect, polygon ); painter->drawPolygon( cpa ); } //! Wrapper for QPainter::drawPolyline() void QwtPainter::drawPolyline( QPainter *painter, const QPolygonF &polygon ) { QRectF clipRect; const bool deviceClipping = isClippingNeeded( painter, clipRect ); QPolygonF cpa = polygon; if ( deviceClipping ) cpa = QwtClipper::clipPolygonF( clipRect, cpa ); ::drawPolyline( painter, cpa.constData(), cpa.size(), d_polylineSplitting ); } //! Wrapper for QPainter::drawPolyline() void QwtPainter::drawPolyline( QPainter *painter, const QPointF *points, int pointCount ) { QRectF clipRect; const bool deviceClipping = isClippingNeeded( painter, clipRect ); if ( deviceClipping ) { QPolygonF polygon( pointCount ); qMemCopy( polygon.data(), points, pointCount * sizeof( QPointF ) ); polygon = QwtClipper::clipPolygonF( clipRect, polygon ); ::drawPolyline( painter, polygon.constData(), polygon.size(), d_polylineSplitting ); } else ::drawPolyline( painter, points, pointCount, d_polylineSplitting ); } //! Wrapper for QPainter::drawPoint() void QwtPainter::drawPoint( QPainter *painter, const QPointF &pos ) { QRectF clipRect; const bool deviceClipping = isClippingNeeded( painter, clipRect ); if ( deviceClipping && !clipRect.contains( pos ) ) return; painter->drawPoint( pos ); } //! Wrapper for QPainter::drawImage() void QwtPainter::drawImage( QPainter *painter, const QRectF &rect, const QImage &image ) { const QRect alignedRect = rect.toAlignedRect(); if ( alignedRect != rect ) { const QRectF clipRect = rect.adjusted( 0.0, 0.0, -1.0, -1.0 ); painter->save(); painter->setClipRect( clipRect, Qt::IntersectClip ); painter->drawImage( alignedRect, image ); painter->restore(); } else { painter->drawImage( alignedRect, image ); } } //! Wrapper for QPainter::drawPixmap() void QwtPainter::drawPixmap( QPainter *painter, const QRectF &rect, const QPixmap &pixmap ) { const QRect alignedRect = rect.toAlignedRect(); if ( alignedRect != rect ) { const QRectF clipRect = rect.adjusted( 0.0, 0.0, -1.0, -1.0 ); painter->save(); painter->setClipRect( clipRect, Qt::IntersectClip ); painter->drawPixmap( alignedRect, pixmap ); painter->restore(); } else { painter->drawPixmap( alignedRect, pixmap ); } } //! Draw a focus rectangle on a widget using its style. void QwtPainter::drawFocusRect( QPainter *painter, QWidget *widget ) { drawFocusRect( painter, widget, widget->rect() ); } //! Draw a focus rectangle on a widget using its style. void QwtPainter::drawFocusRect( QPainter *painter, QWidget *widget, const QRect &rect ) { QStyleOptionFocusRect opt; opt.init( widget ); opt.rect = rect; opt.state |= QStyle::State_HasFocus; widget->style()->drawPrimitive( QStyle::PE_FrameFocusRect, &opt, painter, widget ); } /*! Draw a frame with rounded borders \param painter Painter \param rect Frame rectangle \param xRadius x-radius of the ellipses defining the corners \param yRadius y-radius of the ellipses defining the corners \param palette QPalette::WindowText is used for plain borders QPalette::Dark and QPalette::Light for raised or sunken borders \param lineWidth Line width \param frameStyle bitwise OR´ed value of QFrame::Shape and QFrame::Shadow */ void QwtPainter::drawRoundedFrame( QPainter *painter, const QRectF &rect, double xRadius, double yRadius, const QPalette &palette, int lineWidth, int frameStyle ) { painter->save(); painter->setRenderHint( QPainter::Antialiasing, true ); painter->setBrush( Qt::NoBrush ); double lw2 = lineWidth * 0.5; QRectF r = rect.adjusted( lw2, lw2, -lw2, -lw2 ); QPainterPath path; path.addRoundedRect( r, xRadius, yRadius ); enum Style { Plain, Sunken, Raised }; Style style = Plain; if ( (frameStyle & QFrame::Sunken) == QFrame::Sunken ) style = Sunken; else if ( (frameStyle & QFrame::Raised) == QFrame::Raised ) style = Raised; if ( style != Plain && path.elementCount() == 17 ) { // move + 4 * ( cubicTo + lineTo ) QPainterPath pathList[8]; for ( int i = 0; i < 4; i++ ) { const int j = i * 4 + 1; pathList[ 2 * i ].moveTo( path.elementAt(j - 1).x, path.elementAt( j - 1 ).y ); pathList[ 2 * i ].cubicTo( path.elementAt(j + 0).x, path.elementAt(j + 0).y, path.elementAt(j + 1).x, path.elementAt(j + 1).y, path.elementAt(j + 2).x, path.elementAt(j + 2).y ); pathList[ 2 * i + 1 ].moveTo( path.elementAt(j + 2).x, path.elementAt(j + 2).y ); pathList[ 2 * i + 1 ].lineTo( path.elementAt(j + 3).x, path.elementAt(j + 3).y ); } QColor c1( palette.color( QPalette::Dark ) ); QColor c2( palette.color( QPalette::Light ) ); if ( style == Raised ) qSwap( c1, c2 ); for ( int i = 0; i < 4; i++ ) { QRectF r = pathList[2 * i].controlPointRect(); QPen arcPen; arcPen.setWidth( lineWidth ); QPen linePen; linePen.setWidth( lineWidth ); switch( i ) { case 0: { arcPen.setColor( c1 ); linePen.setColor( c1 ); break; } case 1: { QLinearGradient gradient; gradient.setStart( r.topLeft() ); gradient.setFinalStop( r.bottomRight() ); gradient.setColorAt( 0.0, c1 ); gradient.setColorAt( 1.0, c2 ); arcPen.setBrush( gradient ); linePen.setColor( c2 ); break; } case 2: { arcPen.setColor( c2 ); linePen.setColor( c2 ); break; } case 3: { QLinearGradient gradient; gradient.setStart( r.bottomRight() ); gradient.setFinalStop( r.topLeft() ); gradient.setColorAt( 0.0, c2 ); gradient.setColorAt( 1.0, c1 ); arcPen.setBrush( gradient ); linePen.setColor( c1 ); break; } } painter->setPen( arcPen ); painter->drawPath( pathList[ 2 * i] ); painter->setPen( linePen ); painter->drawPath( pathList[ 2 * i + 1] ); } } else { QPen pen( palette.color( QPalette::WindowText ), lineWidth ); painter->setPen( pen ); painter->drawPath( path ); } painter->restore(); } /*! Draw a color bar into a rectangle \param painter Painter \param colorMap Color map \param interval Value range \param scaleMap Scale map \param orientation Orientation \param rect Traget rectangle */ void QwtPainter::drawColorBar( QPainter *painter, const QwtColorMap &colorMap, const QwtInterval &interval, const QwtScaleMap &scaleMap, Qt::Orientation orientation, const QRectF &rect ) { QVector colorTable; if ( colorMap.format() == QwtColorMap::Indexed ) colorTable = colorMap.colorTable( interval ); QColor c; const QRect devRect = rect.toAlignedRect(); /* We paint to a pixmap first to have something scalable for printing ( f.e. in a Pdf document ) */ QPixmap pixmap( devRect.size() ); QPainter pmPainter( &pixmap ); pmPainter.translate( -devRect.x(), -devRect.y() ); if ( orientation == Qt::Horizontal ) { QwtScaleMap sMap = scaleMap; sMap.setPaintInterval( rect.left(), rect.right() ); for ( int x = devRect.left(); x <= devRect.right(); x++ ) { const double value = sMap.invTransform( x ); if ( colorMap.format() == QwtColorMap::RGB ) c.setRgb( colorMap.rgb( interval, value ) ); else c = colorTable[colorMap.colorIndex( interval, value )]; pmPainter.setPen( c ); pmPainter.drawLine( x, devRect.top(), x, devRect.bottom() ); } } else // Vertical { QwtScaleMap sMap = scaleMap; sMap.setPaintInterval( rect.bottom(), rect.top() ); for ( int y = devRect.top(); y <= devRect.bottom(); y++ ) { const double value = sMap.invTransform( y ); if ( colorMap.format() == QwtColorMap::RGB ) c.setRgb( colorMap.rgb( interval, value ) ); else c = colorTable[colorMap.colorIndex( interval, value )]; pmPainter.setPen( c ); pmPainter.drawLine( devRect.left(), y, devRect.right(), y ); } } pmPainter.end(); drawPixmap( painter, rect, pixmap ); } workbench-1.1.1/src/Qwt/qwt_painter.h000066400000000000000000000111571255417355300175630ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #ifndef QWT_PAINTER_H #define QWT_PAINTER_H #include "qwt_global.h" #include #include #include #include class QPainter; class QBrush; class QColor; class QWidget; class QPolygonF; class QRectF; class QImage; class QPixmap; class QwtScaleMap; class QwtColorMap; class QwtInterval; class QPalette; class QTextDocument; class QPainterPath; /*! \brief A collection of QPainter workarounds */ class QWT_EXPORT QwtPainter { public: static void setPolylineSplitting( bool ); static bool polylineSplitting(); static void setRoundingAlignment( bool ); static bool roundingAlignment(); static bool roundingAlignment(QPainter *); static void drawText( QPainter *, double x, double y, const QString & ); static void drawText( QPainter *, const QPointF &, const QString & ); static void drawText( QPainter *, double x, double y, double w, double h, int flags, const QString & ); static void drawText( QPainter *, const QRectF &, int flags, const QString & ); #ifndef QT_NO_RICHTEXT static void drawSimpleRichText( QPainter *, const QRectF &, int flags, const QTextDocument & ); #endif static void drawRect( QPainter *, double x, double y, double w, double h ); static void drawRect( QPainter *, const QRectF &rect ); static void fillRect( QPainter *, const QRectF &, const QBrush & ); static void drawEllipse( QPainter *, const QRectF & ); static void drawPie( QPainter *, const QRectF & r, int a, int alen ); static void drawLine( QPainter *, double x1, double y1, double x2, double y2 ); static void drawLine( QPainter *, const QPointF &p1, const QPointF &p2 ); static void drawLine( QPainter *, const QLineF & ); static void drawPolygon( QPainter *, const QPolygonF &pa ); static void drawPolyline( QPainter *, const QPolygonF &pa ); static void drawPolyline( QPainter *, const QPointF *, int pointCount ); static void drawPoint( QPainter *, double x, double y ); static void drawPoint( QPainter *, const QPointF & ); static void drawPath( QPainter *, const QPainterPath & ); static void drawImage( QPainter *, const QRectF &, const QImage & ); static void drawPixmap( QPainter *, const QRectF &, const QPixmap & ); static void drawRoundedFrame( QPainter *, const QRectF &, double xRadius, double yRadius, const QPalette &, int lineWidth, int frameStyle ); static void drawFocusRect( QPainter *, QWidget * ); static void drawFocusRect( QPainter *, QWidget *, const QRect & ); static void drawColorBar( QPainter *painter, const QwtColorMap &, const QwtInterval &, const QwtScaleMap &, Qt::Orientation, const QRectF & ); static bool isAligning( QPainter *painter ); private: static bool d_polylineSplitting; static bool d_roundingAlignment; }; //! Wrapper for QPainter::drawPoint() inline void QwtPainter::drawPoint( QPainter *painter, double x, double y ) { QwtPainter::drawPoint( painter, QPointF( x, y ) ); } //! Wrapper for QPainter::drawLine() inline void QwtPainter::drawLine( QPainter *painter, double x1, double y1, double x2, double y2 ) { QwtPainter::drawLine( painter, QPointF( x1, y1 ), QPointF( x2, y2 ) ); } //! Wrapper for QPainter::drawLine() inline void QwtPainter::drawLine( QPainter *painter, const QLineF &line ) { QwtPainter::drawLine( painter, line.p1(), line.p2() ); } /*! Returns whether line splitting for the raster paint engine is enabled. \sa setPolylineSplitting() */ inline bool QwtPainter::polylineSplitting() { return d_polylineSplitting; } /*! Returns whether coordinates should be rounded, before they are painted to a paint engine that floors to integer values. For other paint engines this ( Pdf, SVG ), this flag has no effect. \sa setRoundingAlignment(), isAligning() */ inline bool QwtPainter::roundingAlignment() { return d_roundingAlignment; } /*! \return roundingAlignment() && isAligning(painter); \param painter Painter */ inline bool QwtPainter::roundingAlignment(QPainter *painter) { return d_roundingAlignment && isAligning(painter); } #endif workbench-1.1.1/src/Qwt/qwt_panner.cpp000066400000000000000000000306171255417355300177410ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #include "qwt_panner.h" #include "qwt_picker.h" #include #include #include #include #include static QVector qwtActivePickers( QWidget *w ) { QVector pickers; QObjectList children = w->children(); for ( int i = 0; i < children.size(); i++ ) { QObject *obj = children[i]; if ( obj->inherits( "QwtPicker" ) ) { QwtPicker *picker = ( QwtPicker * )obj; if ( picker->isEnabled() ) pickers += picker; } } return pickers; } class QwtPanner::PrivateData { public: PrivateData(): button( Qt::LeftButton ), buttonState( Qt::NoButton ), abortKey( Qt::Key_Escape ), abortKeyState( Qt::NoButton ), #ifndef QT_NO_CURSOR cursor( NULL ), restoreCursor( NULL ), hasCursor( false ), #endif isEnabled( false ) { orientations = Qt::Vertical | Qt::Horizontal; } ~PrivateData() { #ifndef QT_NO_CURSOR delete cursor; delete restoreCursor; #endif } int button; int buttonState; int abortKey; int abortKeyState; QPoint initialPos; QPoint pos; QPixmap pixmap; QBitmap contentsMask; #ifndef QT_NO_CURSOR QCursor *cursor; QCursor *restoreCursor; bool hasCursor; #endif bool isEnabled; Qt::Orientations orientations; }; /*! Creates an panner that is enabled for the left mouse button. \param parent Parent widget to be panned */ QwtPanner::QwtPanner( QWidget *parent ): QWidget( parent ) { d_data = new PrivateData(); setAttribute( Qt::WA_TransparentForMouseEvents ); setAttribute( Qt::WA_NoSystemBackground ); setFocusPolicy( Qt::NoFocus ); hide(); setEnabled( true ); } //! Destructor QwtPanner::~QwtPanner() { delete d_data; } /*! Change the mouse button The defaults are Qt::LeftButton and Qt::NoButton */ void QwtPanner::setMouseButton( int button, int buttonState ) { d_data->button = button; d_data->buttonState = buttonState; } //! Get the mouse button void QwtPanner::getMouseButton( int &button, int &buttonState ) const { button = d_data->button; buttonState = d_data->buttonState; } /*! Change the abort key The defaults are Qt::Key_Escape and Qt::NoButton \param key Key ( See Qt::Keycode ) \param state State */ void QwtPanner::setAbortKey( int key, int state ) { d_data->abortKey = key; d_data->abortKeyState = state; } //! Get the abort key void QwtPanner::getAbortKey( int &key, int &state ) const { key = d_data->abortKey; state = d_data->abortKeyState; } /*! Change the cursor, that is active while panning The default is the cursor of the parent widget. \param cursor New cursor \sa setCursor() */ #ifndef QT_NO_CURSOR void QwtPanner::setCursor( const QCursor &cursor ) { d_data->cursor = new QCursor( cursor ); } #endif /*! \return Cursor that is active while panning \sa setCursor() */ #ifndef QT_NO_CURSOR const QCursor QwtPanner::cursor() const { if ( d_data->cursor ) return *d_data->cursor; if ( parentWidget() ) return parentWidget()->cursor(); return QCursor(); } #endif /*! \brief En/disable the panner When enabled is true an event filter is installed for the observed widget, otherwise the event filter is removed. \param on true or false \sa isEnabled(), eventFilter() */ void QwtPanner::setEnabled( bool on ) { if ( d_data->isEnabled != on ) { d_data->isEnabled = on; QWidget *w = parentWidget(); if ( w ) { if ( d_data->isEnabled ) { w->installEventFilter( this ); } else { w->removeEventFilter( this ); hide(); } } } } /*! Set the orientations, where panning is enabled The default value is in both directions: Qt::Horizontal | Qt::Vertical /param o Orientation */ void QwtPanner::setOrientations( Qt::Orientations o ) { d_data->orientations = o; } //! Return the orientation, where paning is enabled Qt::Orientations QwtPanner::orientations() const { return d_data->orientations; } /*! Return true if a orientatio is enabled \sa orientations(), setOrientations() */ bool QwtPanner::isOrientationEnabled( Qt::Orientation o ) const { return d_data->orientations & o; } /*! \return true when enabled, false otherwise \sa setEnabled, eventFilter() */ bool QwtPanner::isEnabled() const { return d_data->isEnabled; } /*! \brief Paint event Repaint the grabbed pixmap on its current position and fill the empty spaces by the background of the parent widget. \param pe Paint event */ void QwtPanner::paintEvent( QPaintEvent *pe ) { int dx = d_data->pos.x() - d_data->initialPos.x(); int dy = d_data->pos.y() - d_data->initialPos.y(); QRect r( 0, 0, d_data->pixmap.width(), d_data->pixmap.height() ); r.moveCenter( QPoint( r.center().x() + dx, r.center().y() + dy ) ); QPixmap pm( size() ); pm.fill( parentWidget(), 0, 0 ); QPainter painter( &pm ); if ( !d_data->contentsMask.isNull() ) { QPixmap masked = d_data->pixmap; masked.setMask( d_data->contentsMask ); painter.drawPixmap( r, masked ); } else { painter.drawPixmap( r, d_data->pixmap ); } painter.end(); if ( !d_data->contentsMask.isNull() ) pm.setMask( d_data->contentsMask ); painter.begin( this ); painter.setClipRegion( pe->region() ); painter.drawPixmap( 0, 0, pm ); } /*! \brief Calculate a mask for the contents of the panned widget Sometimes only parts of the contents of a widget should be panned. F.e. for a widget with a styled background with rounded borders only the area inside of the border should be panned. \return An empty bitmap, indicating no mask */ QBitmap QwtPanner::contentsMask() const { return QBitmap(); } /*! Grab the widget into a pixmap. */ QPixmap QwtPanner::grab() const { return QPixmap::grabWidget( parentWidget() ); } /*! \brief Event filter When isEnabled() the mouse events of the observed widget are filtered. \param object Object to be filtered \param event Event \sa widgetMousePressEvent(), widgetMouseReleaseEvent(), widgetMouseMoveEvent() */ bool QwtPanner::eventFilter( QObject *object, QEvent *event ) { if ( object == NULL || object != parentWidget() ) return false; switch ( event->type() ) { case QEvent::MouseButtonPress: { widgetMousePressEvent( ( QMouseEvent * )event ); break; } case QEvent::MouseMove: { widgetMouseMoveEvent( ( QMouseEvent * )event ); break; } case QEvent::MouseButtonRelease: { widgetMouseReleaseEvent( ( QMouseEvent * )event ); break; } case QEvent::KeyPress: { widgetKeyPressEvent( ( QKeyEvent * )event ); break; } case QEvent::KeyRelease: { widgetKeyReleaseEvent( ( QKeyEvent * )event ); break; } case QEvent::Paint: { if ( isVisible() ) return true; break; } default:; } return false; } /*! Handle a mouse press event for the observed widget. \param mouseEvent Mouse event \sa eventFilter(), widgetMouseReleaseEvent(), widgetMouseMoveEvent(), */ void QwtPanner::widgetMousePressEvent( QMouseEvent *mouseEvent ) { if ( mouseEvent->button() != d_data->button ) return; QWidget *w = parentWidget(); if ( w == NULL ) return; if ( ( mouseEvent->modifiers() & Qt::KeyboardModifierMask ) != ( int )( d_data->buttonState & Qt::KeyboardModifierMask ) ) { return; } #ifndef QT_NO_CURSOR showCursor( true ); #endif d_data->initialPos = d_data->pos = mouseEvent->pos(); setGeometry( parentWidget()->rect() ); // We don't want to grab the picker ! QVector pickers = qwtActivePickers( parentWidget() ); for ( int i = 0; i < ( int )pickers.size(); i++ ) pickers[i]->setEnabled( false ); d_data->pixmap = grab(); d_data->contentsMask = contentsMask(); for ( int i = 0; i < ( int )pickers.size(); i++ ) pickers[i]->setEnabled( true ); show(); } /*! Handle a mouse move event for the observed widget. \param mouseEvent Mouse event \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent() */ void QwtPanner::widgetMouseMoveEvent( QMouseEvent *mouseEvent ) { if ( !isVisible() ) return; QPoint pos = mouseEvent->pos(); if ( !isOrientationEnabled( Qt::Horizontal ) ) pos.setX( d_data->initialPos.x() ); if ( !isOrientationEnabled( Qt::Vertical ) ) pos.setY( d_data->initialPos.y() ); if ( pos != d_data->pos && rect().contains( pos ) ) { d_data->pos = pos; update(); Q_EMIT moved( d_data->pos.x() - d_data->initialPos.x(), d_data->pos.y() - d_data->initialPos.y() ); } } /*! Handle a mouse release event for the observed widget. \param mouseEvent Mouse event \sa eventFilter(), widgetMousePressEvent(), widgetMouseMoveEvent(), */ void QwtPanner::widgetMouseReleaseEvent( QMouseEvent *mouseEvent ) { if ( isVisible() ) { hide(); #ifndef QT_NO_CURSOR showCursor( false ); #endif QPoint pos = mouseEvent->pos(); if ( !isOrientationEnabled( Qt::Horizontal ) ) pos.setX( d_data->initialPos.x() ); if ( !isOrientationEnabled( Qt::Vertical ) ) pos.setY( d_data->initialPos.y() ); d_data->pixmap = QPixmap(); d_data->contentsMask = QBitmap(); d_data->pos = pos; if ( d_data->pos != d_data->initialPos ) { Q_EMIT panned( d_data->pos.x() - d_data->initialPos.x(), d_data->pos.y() - d_data->initialPos.y() ); } } } /*! Handle a key press event for the observed widget. \param keyEvent Key event \sa eventFilter(), widgetKeyReleaseEvent() */ void QwtPanner::widgetKeyPressEvent( QKeyEvent *keyEvent ) { if ( keyEvent->key() == d_data->abortKey ) { const bool matched = ( keyEvent->modifiers() & Qt::KeyboardModifierMask ) == ( int )( d_data->abortKeyState & Qt::KeyboardModifierMask ); if ( matched ) { hide(); #ifndef QT_NO_CURSOR showCursor( false ); #endif d_data->pixmap = QPixmap(); } } } /*! Handle a key release event for the observed widget. \param keyEvent Key event \sa eventFilter(), widgetKeyReleaseEvent() */ void QwtPanner::widgetKeyReleaseEvent( QKeyEvent *keyEvent ) { Q_UNUSED( keyEvent ); } #ifndef QT_NO_CURSOR void QwtPanner::showCursor( bool on ) { if ( on == d_data->hasCursor ) return; QWidget *w = parentWidget(); if ( w == NULL || d_data->cursor == NULL ) return; d_data->hasCursor = on; if ( on ) { if ( w->testAttribute( Qt::WA_SetCursor ) ) { delete d_data->restoreCursor; d_data->restoreCursor = new QCursor( w->cursor() ); } w->setCursor( *d_data->cursor ); } else { if ( d_data->restoreCursor ) { w->setCursor( *d_data->restoreCursor ); delete d_data->restoreCursor; d_data->restoreCursor = NULL; } else w->unsetCursor(); } } #endif workbench-1.1.1/src/Qwt/qwt_panner.h000066400000000000000000000056221255417355300174040ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #ifndef QWT_PANNER_H #define QWT_PANNER_H 1 #include "qwt_global.h" #include #include class QCursor; /*! \brief QwtPanner provides panning of a widget QwtPanner grabs the contents of a widget, that can be dragged in all directions. The offset between the start and the end position is emitted by the panned signal. QwtPanner grabs the content of the widget into a pixmap and moves the pixmap around, without initiating any repaint events for the widget. Areas, that are not part of content are not painted while panning. This makes panning fast enough for widgets, where repaints are too slow for mouse movements. For widgets, where repaints are very fast it might be better to implement panning manually by mapping mouse events into paint events. */ class QWT_EXPORT QwtPanner: public QWidget { Q_OBJECT public: QwtPanner( QWidget* parent ); virtual ~QwtPanner(); void setEnabled( bool ); bool isEnabled() const; void setMouseButton( int button, int buttonState = Qt::NoButton ); void getMouseButton( int &button, int &buttonState ) const; void setAbortKey( int key, int state = Qt::NoButton ); void getAbortKey( int &key, int &state ) const; void setCursor( const QCursor & ); const QCursor cursor() const; void setOrientations( Qt::Orientations ); Qt::Orientations orientations() const; bool isOrientationEnabled( Qt::Orientation ) const; virtual bool eventFilter( QObject *, QEvent * ); Q_SIGNALS: /*! Signal emitted, when panning is done \param dx Offset in horizontal direction \param dy Offset in vertical direction */ void panned( int dx, int dy ); /*! Signal emitted, while the widget moved, but panning is not finished. \param dx Offset in horizontal direction \param dy Offset in vertical direction */ void moved( int dx, int dy ); protected: virtual void widgetMousePressEvent( QMouseEvent * ); virtual void widgetMouseReleaseEvent( QMouseEvent * ); virtual void widgetMouseMoveEvent( QMouseEvent * ); virtual void widgetKeyPressEvent( QKeyEvent * ); virtual void widgetKeyReleaseEvent( QKeyEvent * ); virtual void paintEvent( QPaintEvent * ); virtual QBitmap contentsMask() const; virtual QPixmap grab() const; private: #ifndef QT_NO_CURSOR void showCursor( bool ); #endif class PrivateData; PrivateData *d_data; }; #endif workbench-1.1.1/src/Qwt/qwt_picker.cpp000066400000000000000000001111311255417355300177220ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #include "qwt_picker.h" #include "qwt_picker_machine.h" #include "qwt_painter.h" #include "qwt_math.h" #include #include #include #include #include #include #include #include #include class QwtPicker::PickerWidget: public QWidget { public: enum Type { RubberBand, Text }; PickerWidget( QwtPicker *, QWidget *, Type ); void updateMask(); /* For a tracker text with a background we can use the background rect as mask. Also for "regular" Qt widgets >= 4.3.0 we don't need to mask the text anymore. */ bool d_hasTextMask; protected: virtual void paintEvent( QPaintEvent * ); QwtPicker *d_picker; Type d_type; }; class QwtPicker::PrivateData { public: bool enabled; QwtPickerMachine *stateMachine; QwtPicker::ResizeMode resizeMode; QwtPicker::RubberBand rubberBand; QPen rubberBandPen; QwtPicker::DisplayMode trackerMode; QPen trackerPen; QFont trackerFont; QPolygon pickedPoints; bool isActive; QPoint trackerPosition; bool mouseTracking; // used to save previous value /* On X11 the widget below the picker widgets gets paint events with a region that is the bounding rect of the mask, if it is complex. In case of (f.e) a CrossRubberBand and a text this creates complete repaints of the widget. So we better use two different widgets. */ QPointer rubberBandWidget; QPointer trackerWidget; }; QwtPicker::PickerWidget::PickerWidget( QwtPicker *picker, QWidget *parent, Type type ): QWidget( parent ), d_hasTextMask( false ), d_picker( picker ), d_type( type ) { setAttribute( Qt::WA_TransparentForMouseEvents ); setAttribute( Qt::WA_NoSystemBackground ); setFocusPolicy( Qt::NoFocus ); } void QwtPicker::PickerWidget::updateMask() { QRegion mask; if ( d_type == RubberBand ) { QBitmap bm( width(), height() ); bm.fill( Qt::color0 ); QPainter painter( &bm ); QPen pen = d_picker->rubberBandPen(); pen.setColor( Qt::color1 ); painter.setPen( pen ); d_picker->drawRubberBand( &painter ); mask = QRegion( bm ); } if ( d_type == Text ) { d_hasTextMask = parentWidget()->testAttribute( Qt::WA_PaintOnScreen ); if ( d_hasTextMask ) { const QwtText label = d_picker->trackerText( d_picker->trackerPosition() ); if ( label.testPaintAttribute( QwtText::PaintBackground ) && label.backgroundBrush().style() != Qt::NoBrush ) { if ( label.backgroundBrush().color().alpha() > 0 ) { // We don't need a text mask, when we have a background d_hasTextMask = false; } } } if ( d_hasTextMask ) { QBitmap bm( width(), height() ); bm.fill( Qt::color0 ); QPainter painter( &bm ); painter.setFont( font() ); QPen pen = d_picker->trackerPen(); pen.setColor( Qt::color1 ); painter.setPen( pen ); d_picker->drawTracker( &painter ); mask = QRegion( bm ); } else { mask = d_picker->trackerRect( font() ); } } QWidget *w = parentWidget(); if ( w && !w->testAttribute( Qt::WA_PaintOnScreen ) ) { // The parent widget gets an update for its complete rectangle // when the mask is changed in visible state. // With this hide/show we only get an update for the // previous mask. hide(); } setMask( mask ); setVisible( !mask.isEmpty() ); } void QwtPicker::PickerWidget::paintEvent( QPaintEvent *e ) { QPainter painter( this ); painter.setClipRegion( e->region() ); if ( d_type == RubberBand ) { painter.setPen( d_picker->rubberBandPen() ); d_picker->drawRubberBand( &painter ); } if ( d_type == Text ) { /* If we have a text mask we simply fill the region of the mask. This gives better results for antialiased fonts. */ if ( d_hasTextMask ) { painter.fillRect( e->rect(), QBrush( d_picker->trackerPen().color() ) ); } else { painter.setPen( d_picker->trackerPen() ); d_picker->drawTracker( &painter ); } } } /*! Constructor Creates an picker that is enabled, but without a state machine. rubberband and tracker are disabled. \param parent Parent widget, that will be observed */ QwtPicker::QwtPicker( QWidget *parent ): QObject( parent ) { init( parent, NoRubberBand, AlwaysOff ); } /*! Constructor \param rubberBand Rubberband style \param trackerMode Tracker mode \param parent Parent widget, that will be observed */ QwtPicker::QwtPicker( RubberBand rubberBand, DisplayMode trackerMode, QWidget *parent ): QObject( parent ) { init( parent, rubberBand, trackerMode ); } //! Destructor QwtPicker::~QwtPicker() { setMouseTracking( false ); delete d_data->stateMachine; delete d_data->rubberBandWidget; delete d_data->trackerWidget; delete d_data; } //! Init the picker, used by the constructors void QwtPicker::init( QWidget *parent, RubberBand rubberBand, DisplayMode trackerMode ) { d_data = new PrivateData; d_data->rubberBandWidget = NULL; d_data->trackerWidget = NULL; d_data->rubberBand = rubberBand; d_data->enabled = false; d_data->resizeMode = Stretch; d_data->trackerMode = AlwaysOff; d_data->isActive = false; d_data->trackerPosition = QPoint( -1, -1 ); d_data->mouseTracking = false; d_data->stateMachine = NULL; if ( parent ) { if ( parent->focusPolicy() == Qt::NoFocus ) parent->setFocusPolicy( Qt::WheelFocus ); d_data->trackerFont = parent->font(); d_data->mouseTracking = parent->hasMouseTracking(); setEnabled( true ); } setTrackerMode( trackerMode ); } /*! Set a state machine and delete the previous one \param stateMachine State machine \sa stateMachine() */ void QwtPicker::setStateMachine( QwtPickerMachine *stateMachine ) { if ( d_data->stateMachine != stateMachine ) { reset(); delete d_data->stateMachine; d_data->stateMachine = stateMachine; if ( d_data->stateMachine ) d_data->stateMachine->reset(); } } /*! \return Assigned state machine \sa setStateMachine() */ QwtPickerMachine *QwtPicker::stateMachine() { return d_data->stateMachine; } /*! \return Assigned state machine \sa setStateMachine() */ const QwtPickerMachine *QwtPicker::stateMachine() const { return d_data->stateMachine; } //! Return the parent widget, where the selection happens QWidget *QwtPicker::parentWidget() { QObject *obj = parent(); if ( obj && obj->isWidgetType() ) return static_cast( obj ); return NULL; } //! Return the parent widget, where the selection happens const QWidget *QwtPicker::parentWidget() const { QObject *obj = parent(); if ( obj && obj->isWidgetType() ) return static_cast< const QWidget *>( obj ); return NULL; } /*! Set the rubberband style \param rubberBand Rubberband style The default value is NoRubberBand. \sa rubberBand(), RubberBand, setRubberBandPen() */ void QwtPicker::setRubberBand( RubberBand rubberBand ) { d_data->rubberBand = rubberBand; } /*! \return Rubberband style \sa setRubberBand(), RubberBand, rubberBandPen() */ QwtPicker::RubberBand QwtPicker::rubberBand() const { return d_data->rubberBand; } /*! \brief Set the display mode of the tracker. A tracker displays information about current position of the cursor as a string. The display mode controls if the tracker has to be displayed whenever the observed widget has focus and cursor (AlwaysOn), never (AlwaysOff), or only when the selection is active (ActiveOnly). \param mode Tracker display mode \warning In case of AlwaysOn, mouseTracking will be enabled for the observed widget. \sa trackerMode(), DisplayMode */ void QwtPicker::setTrackerMode( DisplayMode mode ) { if ( d_data->trackerMode != mode ) { d_data->trackerMode = mode; setMouseTracking( d_data->trackerMode == AlwaysOn ); } } /*! \return Tracker display mode \sa setTrackerMode(), DisplayMode */ QwtPicker::DisplayMode QwtPicker::trackerMode() const { return d_data->trackerMode; } /*! \brief Set the resize mode. The resize mode controls what to do with the selected points of an active selection when the observed widget is resized. Stretch means the points are scaled according to the new size, KeepSize means the points remain unchanged. The default mode is Stretch. \param mode Resize mode \sa resizeMode(), ResizeMode */ void QwtPicker::setResizeMode( ResizeMode mode ) { d_data->resizeMode = mode; } /*! \return Resize mode \sa setResizeMode(), ResizeMode */ QwtPicker::ResizeMode QwtPicker::resizeMode() const { return d_data->resizeMode; } /*! \brief En/disable the picker When enabled is true an event filter is installed for the observed widget, otherwise the event filter is removed. \param enabled true or false \sa isEnabled(), eventFilter() */ void QwtPicker::setEnabled( bool enabled ) { if ( d_data->enabled != enabled ) { d_data->enabled = enabled; QWidget *w = parentWidget(); if ( w ) { if ( enabled ) w->installEventFilter( this ); else w->removeEventFilter( this ); } updateDisplay(); } } /*! \return true when enabled, false otherwise \sa setEnabled(), eventFilter() */ bool QwtPicker::isEnabled() const { return d_data->enabled; } /*! Set the font for the tracker \param font Tracker font \sa trackerFont(), setTrackerMode(), setTrackerPen() */ void QwtPicker::setTrackerFont( const QFont &font ) { if ( font != d_data->trackerFont ) { d_data->trackerFont = font; updateDisplay(); } } /*! \return Tracker font \sa setTrackerFont(), trackerMode(), trackerPen() */ QFont QwtPicker::trackerFont() const { return d_data->trackerFont; } /*! Set the pen for the tracker \param pen Tracker pen \sa trackerPen(), setTrackerMode(), setTrackerFont() */ void QwtPicker::setTrackerPen( const QPen &pen ) { if ( pen != d_data->trackerPen ) { d_data->trackerPen = pen; updateDisplay(); } } /*! \return Tracker pen \sa setTrackerPen(), trackerMode(), trackerFont() */ QPen QwtPicker::trackerPen() const { return d_data->trackerPen; } /*! Set the pen for the rubberband \param pen Rubberband pen \sa rubberBandPen(), setRubberBand() */ void QwtPicker::setRubberBandPen( const QPen &pen ) { if ( pen != d_data->rubberBandPen ) { d_data->rubberBandPen = pen; updateDisplay(); } } /*! \return Rubberband pen \sa setRubberBandPen(), rubberBand() */ QPen QwtPicker::rubberBandPen() const { return d_data->rubberBandPen; } /*! \brief Return the label for a position In case of HLineRubberBand the label is the value of the y position, in case of VLineRubberBand the value of the x position. Otherwise the label contains x and y position separated by a ',' . The format for the string conversion is "%d". \param pos Position \return Converted position as string */ QwtText QwtPicker::trackerText( const QPoint &pos ) const { QString label; switch ( rubberBand() ) { case HLineRubberBand: label.sprintf( "%d", pos.y() ); break; case VLineRubberBand: label.sprintf( "%d", pos.x() ); break; default: label.sprintf( "%d, %d", pos.x(), pos.y() ); } return label; } /*! Draw a rubberband, depending on rubberBand() \param painter Painter, initialized with clip rect \sa rubberBand(), RubberBand */ void QwtPicker::drawRubberBand( QPainter *painter ) const { if ( !isActive() || rubberBand() == NoRubberBand || rubberBandPen().style() == Qt::NoPen ) { return; } const QRect &pRect = pickRect(); const QPolygon pa = adjustedPoints( d_data->pickedPoints ); QwtPickerMachine::SelectionType selectionType = QwtPickerMachine::NoSelection; if ( d_data->stateMachine ) selectionType = d_data->stateMachine->selectionType(); switch ( selectionType ) { case QwtPickerMachine::NoSelection: case QwtPickerMachine::PointSelection: { if ( pa.count() < 1 ) return; const QPoint pos = pa[0]; switch ( rubberBand() ) { case VLineRubberBand: QwtPainter::drawLine( painter, pos.x(), pRect.top(), pos.x(), pRect.bottom() ); break; case HLineRubberBand: QwtPainter::drawLine( painter, pRect.left(), pos.y(), pRect.right(), pos.y() ); break; case CrossRubberBand: QwtPainter::drawLine( painter, pos.x(), pRect.top(), pos.x(), pRect.bottom() ); QwtPainter::drawLine( painter, pRect.left(), pos.y(), pRect.right(), pos.y() ); break; default: break; } break; } case QwtPickerMachine::RectSelection: { if ( pa.count() < 2 ) return; const QPoint p1 = pa[0]; const QPoint p2 = pa[int( pa.count() - 1 )]; const QRect rect = QRect( p1, p2 ).normalized(); switch ( rubberBand() ) { case EllipseRubberBand: QwtPainter::drawEllipse( painter, rect ); break; case RectRubberBand: QwtPainter::drawRect( painter, rect ); break; default: break; } break; } case QwtPickerMachine::PolygonSelection: { if ( rubberBand() == PolygonRubberBand ) painter->drawPolyline( pa ); break; } default: break; } } /*! Draw the tracker \param painter Painter \sa trackerRect(), trackerText() */ void QwtPicker::drawTracker( QPainter *painter ) const { const QRect textRect = trackerRect( painter->font() ); if ( !textRect.isEmpty() ) { const QwtText label = trackerText( d_data->trackerPosition ); if ( !label.isEmpty() ) label.draw( painter, textRect ); } } /*! \brief Map the pickedPoints() into a selection() adjustedPoints() maps the points, that have been collected on the parentWidget() into a selection(). The default implementation simply returns the points unmodified. The reason, why a selection() differs from the picked points depends on the application requirements. F.e. : - A rectangular selection might need to have a specific aspect ratio only.\n - A selection could accept non intersecting polygons only.\n - ...\n The example below is for a rectangular selection, where the first point is the center of the selected rectangle. \par Example \verbatim QPolygon MyPicker::adjustedPoints(const QPolygon &points) const { QPolygon adjusted; if ( points.size() == 2 ) { const int width = qAbs(points[1].x() - points[0].x()); const int height = qAbs(points[1].y() - points[0].y()); QRect rect(0, 0, 2 * width, 2 * height); rect.moveCenter(points[0]); adjusted += rect.topLeft(); adjusted += rect.bottomRight(); } return adjusted; }\endverbatim\n */ QPolygon QwtPicker::adjustedPoints( const QPolygon &points ) const { return points; } /*! \return Selected points \sa pickedPoints(), adjustedPoints() */ QPolygon QwtPicker::selection() const { return adjustedPoints( d_data->pickedPoints ); } //! \return Current position of the tracker QPoint QwtPicker::trackerPosition() const { return d_data->trackerPosition; } /*! Calculate the bounding rectangle for the tracker text from the current position of the tracker \param font Font of the tracker text \return Bounding rectangle of the tracker text \sa trackerPosition() */ QRect QwtPicker::trackerRect( const QFont &font ) const { if ( trackerMode() == AlwaysOff || ( trackerMode() == ActiveOnly && !isActive() ) ) { return QRect(); } if ( d_data->trackerPosition.x() < 0 || d_data->trackerPosition.y() < 0 ) return QRect(); QwtText text = trackerText( d_data->trackerPosition ); if ( text.isEmpty() ) return QRect(); const QSizeF textSize = text.textSize( font ); QRect textRect( 0, 0, qCeil( textSize.width() ), qCeil( textSize.height() ) ); const QPoint &pos = d_data->trackerPosition; int alignment = 0; if ( isActive() && d_data->pickedPoints.count() > 1 && rubberBand() != NoRubberBand ) { const QPoint last = d_data->pickedPoints[int( d_data->pickedPoints.count() ) - 2]; alignment |= ( pos.x() >= last.x() ) ? Qt::AlignRight : Qt::AlignLeft; alignment |= ( pos.y() > last.y() ) ? Qt::AlignBottom : Qt::AlignTop; } else alignment = Qt::AlignTop | Qt::AlignRight; const int margin = 5; int x = pos.x(); if ( alignment & Qt::AlignLeft ) x -= textRect.width() + margin; else if ( alignment & Qt::AlignRight ) x += margin; int y = pos.y(); if ( alignment & Qt::AlignBottom ) y += margin; else if ( alignment & Qt::AlignTop ) y -= textRect.height() + margin; textRect.moveTopLeft( QPoint( x, y ) ); int right = qMin( textRect.right(), pickRect().right() - margin ); int bottom = qMin( textRect.bottom(), pickRect().bottom() - margin ); textRect.moveBottomRight( QPoint( right, bottom ) ); int left = qMax( textRect.left(), pickRect().left() + margin ); int top = qMax( textRect.top(), pickRect().top() + margin ); textRect.moveTopLeft( QPoint( left, top ) ); return textRect; } /*! \brief Event filter When isEnabled() == true all events of the observed widget are filtered. Mouse and keyboard events are translated into widgetMouse- and widgetKey- and widgetWheel-events. Paint and Resize events are handled to keep rubberband and tracker up to date. \param object Object to be filtered \param event Event \sa widgetEnterEvent(), widgetLeaveEvent(), widgetMousePressEvent(), widgetMouseReleaseEvent(), widgetMouseDoubleClickEvent(), widgetMouseMoveEvent(), widgetWheelEvent(), widgetKeyPressEvent(), widgetKeyReleaseEvent(), QObject::installEventFilter(), QObject::event() */ bool QwtPicker::eventFilter( QObject *object, QEvent *event ) { if ( object && object == parentWidget() ) { switch ( event->type() ) { case QEvent::Resize: { const QResizeEvent *re = ( QResizeEvent * )event; if ( d_data->resizeMode == Stretch ) stretchSelection( re->oldSize(), re->size() ); if ( d_data->rubberBandWidget ) d_data->rubberBandWidget->resize( re->size() ); if ( d_data->trackerWidget ) d_data->trackerWidget->resize( re->size() ); break; } case QEvent::Enter: widgetEnterEvent( event ); break; case QEvent::Leave: widgetLeaveEvent( event ); break; case QEvent::MouseButtonPress: widgetMousePressEvent( ( QMouseEvent * )event ); break; case QEvent::MouseButtonRelease: widgetMouseReleaseEvent( ( QMouseEvent * )event ); break; case QEvent::MouseButtonDblClick: widgetMouseDoubleClickEvent( ( QMouseEvent * )event ); break; case QEvent::MouseMove: widgetMouseMoveEvent( ( QMouseEvent * )event ); break; case QEvent::KeyPress: widgetKeyPressEvent( ( QKeyEvent * )event ); break; case QEvent::KeyRelease: widgetKeyReleaseEvent( ( QKeyEvent * )event ); break; case QEvent::Wheel: widgetWheelEvent( ( QWheelEvent * )event ); break; default: break; } } return false; } /*! Handle a mouse press event for the observed widget. \param mouseEvent Mouse event \sa eventFilter(), widgetMouseReleaseEvent(), widgetMouseDoubleClickEvent(), widgetMouseMoveEvent(), widgetWheelEvent(), widgetKeyPressEvent(), widgetKeyReleaseEvent() */ void QwtPicker::widgetMousePressEvent( QMouseEvent *mouseEvent ) { transition( mouseEvent ); } /*! Handle a mouse move event for the observed widget. \param mouseEvent Mouse event \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent(), widgetMouseDoubleClickEvent(), widgetWheelEvent(), widgetKeyPressEvent(), widgetKeyReleaseEvent() */ void QwtPicker::widgetMouseMoveEvent( QMouseEvent *mouseEvent ) { if ( pickRect().contains( mouseEvent->pos() ) ) d_data->trackerPosition = mouseEvent->pos(); else d_data->trackerPosition = QPoint( -1, -1 ); if ( !isActive() ) updateDisplay(); transition( mouseEvent ); } /*! Handle a enter event for the observed widget. \param event Qt event \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent(), widgetMouseDoubleClickEvent(), widgetWheelEvent(), widgetKeyPressEvent(), widgetKeyReleaseEvent() */ void QwtPicker::widgetEnterEvent( QEvent *event ) { transition( event ); } /*! Handle a leave event for the observed widget. \param event Qt event \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent(), widgetMouseDoubleClickEvent(), widgetWheelEvent(), widgetKeyPressEvent(), widgetKeyReleaseEvent() */ void QwtPicker::widgetLeaveEvent( QEvent *event ) { transition( event ); d_data->trackerPosition = QPoint( -1, -1 ); if ( !isActive() ) updateDisplay(); } /*! Handle a mouse relase event for the observed widget. \param mouseEvent Mouse event \sa eventFilter(), widgetMousePressEvent(), widgetMouseDoubleClickEvent(), widgetMouseMoveEvent(), widgetWheelEvent(), widgetKeyPressEvent(), widgetKeyReleaseEvent() */ void QwtPicker::widgetMouseReleaseEvent( QMouseEvent *mouseEvent ) { transition( mouseEvent ); } /*! Handle mouse double click event for the observed widget. \param mouseEvent Mouse event \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent(), widgetMouseMoveEvent(), widgetWheelEvent(), widgetKeyPressEvent(), widgetKeyReleaseEvent() */ void QwtPicker::widgetMouseDoubleClickEvent( QMouseEvent *mouseEvent ) { transition( mouseEvent ); } /*! Handle a wheel event for the observed widget. Move the last point of the selection in case of isActive() == true \param wheelEvent Wheel event \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent(), widgetMouseDoubleClickEvent(), widgetMouseMoveEvent(), widgetKeyPressEvent(), widgetKeyReleaseEvent() */ void QwtPicker::widgetWheelEvent( QWheelEvent *wheelEvent ) { if ( pickRect().contains( wheelEvent->pos() ) ) d_data->trackerPosition = wheelEvent->pos(); else d_data->trackerPosition = QPoint( -1, -1 ); updateDisplay(); transition( wheelEvent ); } /*! Handle a key press event for the observed widget. Selections can be completely done by the keyboard. The arrow keys move the cursor, the abort key aborts a selection. All other keys are handled by the current state machine. \param keyEvent Key event \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent(), widgetMouseDoubleClickEvent(), widgetMouseMoveEvent(), widgetWheelEvent(), widgetKeyReleaseEvent(), stateMachine(), QwtEventPattern::KeyPatternCode */ void QwtPicker::widgetKeyPressEvent( QKeyEvent *keyEvent ) { int dx = 0; int dy = 0; int offset = 1; if ( keyEvent->isAutoRepeat() ) offset = 5; if ( keyMatch( KeyLeft, keyEvent ) ) dx = -offset; else if ( keyMatch( KeyRight, keyEvent ) ) dx = offset; else if ( keyMatch( KeyUp, keyEvent ) ) dy = -offset; else if ( keyMatch( KeyDown, keyEvent ) ) dy = offset; else if ( keyMatch( KeyAbort, keyEvent ) ) { reset(); } else transition( keyEvent ); if ( dx != 0 || dy != 0 ) { const QRect rect = pickRect(); const QPoint pos = parentWidget()->mapFromGlobal( QCursor::pos() ); int x = pos.x() + dx; x = qMax( rect.left(), x ); x = qMin( rect.right(), x ); int y = pos.y() + dy; y = qMax( rect.top(), y ); y = qMin( rect.bottom(), y ); QCursor::setPos( parentWidget()->mapToGlobal( QPoint( x, y ) ) ); } } /*! Handle a key release event for the observed widget. Passes the event to the state machine. \param keyEvent Key event \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent(), widgetMouseDoubleClickEvent(), widgetMouseMoveEvent(), widgetWheelEvent(), widgetKeyPressEvent(), stateMachine() */ void QwtPicker::widgetKeyReleaseEvent( QKeyEvent *keyEvent ) { transition( keyEvent ); } /*! Passes an event to the state machine and executes the resulting commands. Append and Move commands use the current position of the cursor (QCursor::pos()). \param event Event */ void QwtPicker::transition( const QEvent *event ) { if ( !d_data->stateMachine ) return; const QList commandList = d_data->stateMachine->transition( *this, event ); QPoint pos; switch ( event->type() ) { case QEvent::MouseButtonDblClick: case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: case QEvent::MouseMove: { const QMouseEvent *me = static_cast< const QMouseEvent * >( event ); pos = me->pos(); break; } default: pos = parentWidget()->mapFromGlobal( QCursor::pos() ); } for ( int i = 0; i < commandList.count(); i++ ) { switch ( commandList[i] ) { case QwtPickerMachine::Begin: { begin(); break; } case QwtPickerMachine::Append: { append( pos ); break; } case QwtPickerMachine::Move: { move( pos ); break; } case QwtPickerMachine::Remove: { remove(); break; } case QwtPickerMachine::End: { end(); break; } } } } /*! Open a selection setting the state to active \sa isActive(), end(), append(), move() */ void QwtPicker::begin() { if ( d_data->isActive ) return; d_data->pickedPoints.resize( 0 ); d_data->isActive = true; Q_EMIT activated( true ); if ( trackerMode() != AlwaysOff ) { if ( d_data->trackerPosition.x() < 0 || d_data->trackerPosition.y() < 0 ) { QWidget *w = parentWidget(); if ( w ) d_data->trackerPosition = w->mapFromGlobal( QCursor::pos() ); } } updateDisplay(); setMouseTracking( true ); } /*! \brief Close a selection setting the state to inactive. The selection is validated and maybe fixed by accept(). \param ok If true, complete the selection and emit a selected signal otherwise discard the selection. \return true if the selection is accepted, false otherwise \sa isActive(), begin(), append(), move(), selected(), accept() */ bool QwtPicker::end( bool ok ) { if ( d_data->isActive ) { setMouseTracking( false ); d_data->isActive = false; Q_EMIT activated( false ); if ( trackerMode() == ActiveOnly ) d_data->trackerPosition = QPoint( -1, -1 ); if ( ok ) ok = accept( d_data->pickedPoints ); if ( ok ) Q_EMIT selected( d_data->pickedPoints ); else d_data->pickedPoints.resize( 0 ); updateDisplay(); } else ok = false; return ok; } /*! Reset the state machine and terminate (end(false)) the selection */ void QwtPicker::reset() { if ( d_data->stateMachine ) d_data->stateMachine->reset(); if ( isActive() ) end( false ); } /*! Append a point to the selection and update rubberband and tracker. The appended() signal is emitted. \param pos Additional point \sa isActive(), begin(), end(), move(), appended() */ void QwtPicker::append( const QPoint &pos ) { if ( d_data->isActive ) { const int idx = d_data->pickedPoints.count(); d_data->pickedPoints.resize( idx + 1 ); d_data->pickedPoints[idx] = pos; updateDisplay(); Q_EMIT appended( pos ); } } /*! Move the last point of the selection The moved() signal is emitted. \param pos New position \sa isActive(), begin(), end(), append() */ void QwtPicker::move( const QPoint &pos ) { if ( d_data->isActive ) { const int idx = d_data->pickedPoints.count() - 1; if ( idx >= 0 ) { if ( d_data->pickedPoints[idx] != pos ) { d_data->pickedPoints[idx] = pos; updateDisplay(); Q_EMIT moved( pos ); } } } } /*! Remove the last point of the selection The removed() signal is emitted. \sa isActive(), begin(), end(), append(), move() */ void QwtPicker::remove() { if ( d_data->isActive ) { const int idx = d_data->pickedPoints.count() - 1; if ( idx > 0 ) { const int idx = d_data->pickedPoints.count(); const QPoint pos = d_data->pickedPoints[idx - 1]; d_data->pickedPoints.resize( idx - 1 ); updateDisplay(); Q_EMIT removed( pos ); } } } /*! \brief Validate and fixup the selection Accepts all selections unmodified \param selection Selection to validate and fixup \return true, when accepted, false otherwise */ bool QwtPicker::accept( QPolygon &selection ) const { Q_UNUSED( selection ); return true; } /*! A picker is active between begin() and end(). \return true if the selection is active. */ bool QwtPicker::isActive() const { return d_data->isActive; } /*! Return the points, that have been collected so far. The selection() is calculated from the pickedPoints() in adjustedPoints(). \return Picked points */ const QPolygon &QwtPicker::pickedPoints() const { return d_data->pickedPoints; } /*! Scale the selection by the ratios of oldSize and newSize The changed() signal is emitted. \param oldSize Previous size \param newSize Current size \sa ResizeMode, setResizeMode(), resizeMode() */ void QwtPicker::stretchSelection( const QSize &oldSize, const QSize &newSize ) { if ( oldSize.isEmpty() ) { // avoid division by zero. But scaling for small sizes also // doesn't make much sense, because of rounding losses. TODO ... return; } const double xRatio = double( newSize.width() ) / double( oldSize.width() ); const double yRatio = double( newSize.height() ) / double( oldSize.height() ); for ( int i = 0; i < int( d_data->pickedPoints.count() ); i++ ) { QPoint &p = d_data->pickedPoints[i]; p.setX( qRound( p.x() * xRatio ) ); p.setY( qRound( p.y() * yRatio ) ); Q_EMIT changed( d_data->pickedPoints ); } } /*! Set mouse tracking for the observed widget. In case of enable is true, the previous value is saved, that is restored when enable is false. \warning Even when enable is false, mouse tracking might be restored to true. When mouseTracking for the observed widget has been changed directly by QWidget::setMouseTracking while mouse tracking has been set to true, this value can't be restored. */ void QwtPicker::setMouseTracking( bool enable ) { QWidget *widget = parentWidget(); if ( !widget ) return; if ( enable ) { d_data->mouseTracking = widget->hasMouseTracking(); widget->setMouseTracking( true ); } else { widget->setMouseTracking( d_data->mouseTracking ); } } /*! Find the area of the observed widget, where selection might happen. \return parentWidget()->contentsRect() */ QRect QwtPicker::pickRect() const { const QWidget *widget = parentWidget(); if ( widget ) return widget->contentsRect(); return QRect(); } //! Update the state of rubberband and tracker label void QwtPicker::updateDisplay() { QWidget *w = parentWidget(); bool showRubberband = false; bool showTracker = false; if ( w && w->isVisible() && d_data->enabled ) { if ( rubberBand() != NoRubberBand && isActive() && rubberBandPen().style() != Qt::NoPen ) { showRubberband = true; } if ( trackerMode() == AlwaysOn || ( trackerMode() == ActiveOnly && isActive() ) ) { if ( trackerPen() != Qt::NoPen ) showTracker = true; } } QPointer &rw = d_data->rubberBandWidget; if ( showRubberband ) { if ( rw.isNull() ) { rw = new PickerWidget( this, w, PickerWidget::RubberBand ); rw->resize( w->size() ); } rw->updateMask(); rw->update(); // Needed, when the mask doesn't change } else delete rw; QPointer &tw = d_data->trackerWidget; if ( showTracker ) { if ( tw.isNull() ) { tw = new PickerWidget( this, w, PickerWidget::Text ); tw->resize( w->size() ); } tw->setFont( d_data->trackerFont ); tw->updateMask(); tw->update(); // Needed, when the mask doesn't change } else delete tw; } //! \return Widget displaying the rubberband const QWidget *QwtPicker::rubberBandWidget() const { return d_data->rubberBandWidget; } //! \return Widget displaying the tracker text const QWidget *QwtPicker::trackerWidget() const { return d_data->trackerWidget; } workbench-1.1.1/src/Qwt/qwt_picker.h000066400000000000000000000227221255417355300173760ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #ifndef QWT_PICKER #define QWT_PICKER 1 #include "qwt_global.h" #include "qwt_text.h" #include "qwt_event_pattern.h" #include #include #include #include class QWidget; class QMouseEvent; class QWheelEvent; class QKeyEvent; class QwtPickerMachine; /*! \brief QwtPicker provides selections on a widget QwtPicker filters all enter, leave, mouse and keyboard events of a widget and translates them into an array of selected points. The way how the points are collected depends on type of state machine that is connected to the picker. Qwt offers a couple of predefined state machines for selecting: - Nothing\n QwtPickerTrackerMachine - Single points\n QwtPickerClickPointMachine, QwtPickerDragPointMachine - Rectangles\n QwtPickerClickRectMachine, QwtPickerDragRectMachine - Polygons\n QwtPickerPolygonMachine While these state machines cover the most common ways to collect points it is also possible to implement individual machines as well. QwtPicker translates the picked points into a selection using the adjustedPoints method. adjustedPoints is intended to be reimplemented to fixup the selection according to application specific requirements. (F.e. when an application accepts rectangles of a fixed aspect ratio only.) Optionally QwtPicker support the process of collecting points by a rubberband and tracker displaying a text for the current mouse position. \par Example \verbatim #include #include QwtPicker *picker = new QwtPicker(widget); picker->setStateMachine(new QwtPickerDragRectMachine); picker->setTrackerMode(QwtPicker::ActiveOnly); picker->setRubberBand(QwtPicker::RectRubberBand); \endverbatim\n The state machine triggers the following commands: - begin()\n Activate/Initialize the selection. - append()\n Add a new point - move() \n Change the position of the last point. - remove()\n Remove the last point. - end()\n Terminate the selection and call accept to validate the picked points. The picker is active (isActive()), between begin() and end(). In active state the rubberband is displayed, and the tracker is visible in case of trackerMode is ActiveOnly or AlwaysOn. The cursor can be moved using the arrow keys. All selections can be aborted using the abort key. (QwtEventPattern::KeyPatternCode) \warning In case of QWidget::NoFocus the focus policy of the observed widget is set to QWidget::WheelFocus and mouse tracking will be manipulated while the picker is active, or if trackerMode() is AlwayOn. */ class QWT_EXPORT QwtPicker: public QObject, public QwtEventPattern { Q_OBJECT Q_ENUMS( RubberBand ) Q_ENUMS( DisplayMode ) Q_ENUMS( ResizeMode ) Q_PROPERTY( bool isEnabled READ isEnabled WRITE setEnabled ) Q_PROPERTY( ResizeMode resizeMode READ resizeMode WRITE setResizeMode ) Q_PROPERTY( DisplayMode trackerMode READ trackerMode WRITE setTrackerMode ) Q_PROPERTY( QPen trackerPen READ trackerPen WRITE setTrackerPen ) Q_PROPERTY( QFont trackerFont READ trackerFont WRITE setTrackerFont ) Q_PROPERTY( RubberBand rubberBand READ rubberBand WRITE setRubberBand ) Q_PROPERTY( QPen rubberBandPen READ rubberBandPen WRITE setRubberBandPen ) public: /*! Rubberband style The default value is QwtPicker::NoRubberBand. \sa setRubberBand(), rubberBand() */ enum RubberBand { //! No rubberband. NoRubberBand = 0, //! A horizontal line ( only for QwtPicker::PointSelection ) HLineRubberBand, //! A vertical line ( only for QwtPicker::PointSelection ) VLineRubberBand, //! A crosshair ( only for QwtPicker::PointSelection ) CrossRubberBand, //! A rectangle ( only for QwtPicker::RectSelection ) RectRubberBand, //! An ellipse ( only for QwtPicker::RectSelection ) EllipseRubberBand, //! A polygon ( only for QwtPicker::&PolygonSelection ) PolygonRubberBand, /*! Values >= UserRubberBand can be used to define additional rubber bands. */ UserRubberBand = 100 }; /*! \brief Display mode \sa setTrackerMode(), trackerMode(), isActive() */ enum DisplayMode { //! Display never AlwaysOff, //! Display always AlwaysOn, //! Display only when the selection is active ActiveOnly }; /*! Controls what to do with the selected points of an active selection when the observed widget is resized. The default value is QwtPicker::Stretch. \sa setResizeMode() */ enum ResizeMode { //! All points are scaled according to the new size, Stretch, //! All points remain unchanged. KeepSize }; explicit QwtPicker( QWidget *parent ); explicit QwtPicker( RubberBand rubberBand, DisplayMode trackerMode, QWidget * ); virtual ~QwtPicker(); void setStateMachine( QwtPickerMachine * ); const QwtPickerMachine *stateMachine() const; QwtPickerMachine *stateMachine(); void setRubberBand( RubberBand ); RubberBand rubberBand() const; void setTrackerMode( DisplayMode ); DisplayMode trackerMode() const; void setResizeMode( ResizeMode ); ResizeMode resizeMode() const; void setRubberBandPen( const QPen & ); QPen rubberBandPen() const; void setTrackerPen( const QPen & ); QPen trackerPen() const; void setTrackerFont( const QFont & ); QFont trackerFont() const; bool isEnabled() const; bool isActive() const; virtual bool eventFilter( QObject *, QEvent * ); QWidget *parentWidget(); const QWidget *parentWidget() const; virtual QRect pickRect() const; virtual void drawRubberBand( QPainter * ) const; virtual void drawTracker( QPainter * ) const; virtual QwtText trackerText( const QPoint &pos ) const; QPoint trackerPosition() const; virtual QRect trackerRect( const QFont & ) const; QPolygon selection() const; public Q_SLOTS: void setEnabled( bool ); Q_SIGNALS: /*! A signal indicating, when the picker has been activated. Together with setEnabled() it can be used to implement selections with more than one picker. \param on True, when the picker has been activated */ void activated( bool on ); /*! A signal emitting the selected points, at the end of a selection. \param polygon Selected points */ void selected( const QPolygon &polygon ); /*! A signal emitted when a point has been appended to the selection \param pos Position of the appended point. \sa append(). moved() */ void appended( const QPoint &pos ); /*! A signal emitted whenever the last appended point of the selection has been moved. \param pos Position of the moved last point of the selection. \sa move(), appended() */ void moved( const QPoint &pos ); /*! A signal emitted whenever the last appended point of the selection has been removed. \sa remove(), appended() */ void removed( const QPoint &pos ); /*! A signal emitted when the active selection has been changed. This might happen when the observed widget is resized. \param selection Changed selection \sa stretchSelection() */ void changed( const QPolygon &selection ); protected: virtual QPolygon adjustedPoints( const QPolygon & ) const; virtual void transition( const QEvent * ); virtual void begin(); virtual void append( const QPoint & ); virtual void move( const QPoint & ); virtual void remove(); virtual bool end( bool ok = true ); virtual bool accept( QPolygon & ) const; virtual void reset(); virtual void widgetMousePressEvent( QMouseEvent * ); virtual void widgetMouseReleaseEvent( QMouseEvent * ); virtual void widgetMouseDoubleClickEvent( QMouseEvent * ); virtual void widgetMouseMoveEvent( QMouseEvent * ); virtual void widgetWheelEvent( QWheelEvent * ); virtual void widgetKeyPressEvent( QKeyEvent * ); virtual void widgetKeyReleaseEvent( QKeyEvent * ); virtual void widgetEnterEvent( QEvent * ); virtual void widgetLeaveEvent( QEvent * ); virtual void stretchSelection( const QSize &oldSize, const QSize &newSize ); virtual void updateDisplay(); const QWidget *rubberBandWidget() const; const QWidget *trackerWidget() const; const QPolygon &pickedPoints() const; private: void init( QWidget *, RubberBand rubberBand, DisplayMode trackerMode ); void setMouseTracking( bool ); class PickerWidget; class PrivateData; PrivateData *d_data; }; #endif workbench-1.1.1/src/Qwt/qwt_picker_machine.cpp000066400000000000000000000265261255417355300214230ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #include "qwt_picker_machine.h" #include "qwt_event_pattern.h" #include //! Constructor QwtPickerMachine::QwtPickerMachine( SelectionType type ): d_selectionType( type ), d_state( 0 ) { } //! Destructor QwtPickerMachine::~QwtPickerMachine() { } //! Return the selection type QwtPickerMachine::SelectionType QwtPickerMachine::selectionType() const { return d_selectionType; } //! Return the current state int QwtPickerMachine::state() const { return d_state; } //! Change the current state void QwtPickerMachine::setState( int state ) { d_state = state; } //! Set the current state to 0. void QwtPickerMachine::reset() { setState( 0 ); } //! Constructor QwtPickerTrackerMachine::QwtPickerTrackerMachine(): QwtPickerMachine( NoSelection ) { } //! Transition QList QwtPickerTrackerMachine::transition( const QwtEventPattern &, const QEvent *e ) { QList cmdList; switch ( e->type() ) { case QEvent::Enter: case QEvent::MouseMove: { if ( state() == 0 ) { cmdList += Begin; cmdList += Append; setState( 1 ); } else { cmdList += Move; } break; } case QEvent::Leave: { cmdList += Remove; cmdList += End; setState( 0 ); } default: break; } return cmdList; } //! Constructor QwtPickerClickPointMachine::QwtPickerClickPointMachine(): QwtPickerMachine( PointSelection ) { } //! Transition QList QwtPickerClickPointMachine::transition( const QwtEventPattern &eventPattern, const QEvent *e ) { QList cmdList; switch ( e->type() ) { case QEvent::MouseButtonPress: { if ( eventPattern.mouseMatch( QwtEventPattern::MouseSelect1, ( const QMouseEvent * )e ) ) { cmdList += Begin; cmdList += Append; cmdList += End; } break; } case QEvent::KeyPress: { if ( eventPattern.keyMatch( QwtEventPattern::KeySelect1, ( const QKeyEvent * )e ) ) { cmdList += Begin; cmdList += Append; cmdList += End; } break; } default: break; } return cmdList; } //! Constructor QwtPickerDragPointMachine::QwtPickerDragPointMachine(): QwtPickerMachine( PointSelection ) { } //! Transition QList QwtPickerDragPointMachine::transition( const QwtEventPattern &eventPattern, const QEvent *e ) { QList cmdList; switch ( e->type() ) { case QEvent::MouseButtonPress: { if ( eventPattern.mouseMatch( QwtEventPattern::MouseSelect1, ( const QMouseEvent * )e ) ) { if ( state() == 0 ) { cmdList += Begin; cmdList += Append; setState( 1 ); } } break; } case QEvent::MouseMove: case QEvent::Wheel: { if ( state() != 0 ) cmdList += Move; break; } case QEvent::MouseButtonRelease: { if ( state() != 0 ) { cmdList += End; setState( 0 ); } break; } case QEvent::KeyPress: { if ( eventPattern.keyMatch( QwtEventPattern::KeySelect1, ( const QKeyEvent * )e ) ) { if ( state() == 0 ) { cmdList += Begin; cmdList += Append; setState( 1 ); } else { cmdList += End; setState( 0 ); } } break; } default: break; } return cmdList; } //! Constructor QwtPickerClickRectMachine::QwtPickerClickRectMachine(): QwtPickerMachine( RectSelection ) { } //! Transition QList QwtPickerClickRectMachine::transition( const QwtEventPattern &eventPattern, const QEvent *e ) { QList cmdList; switch ( e->type() ) { case QEvent::MouseButtonPress: { if ( eventPattern.mouseMatch( QwtEventPattern::MouseSelect1, ( const QMouseEvent * )e ) ) { switch ( state() ) { case 0: { cmdList += Begin; cmdList += Append; setState( 1 ); break; } case 1: { // Uh, strange we missed the MouseButtonRelease break; } default: { cmdList += End; setState( 0 ); } } } } case QEvent::MouseMove: case QEvent::Wheel: { if ( state() != 0 ) cmdList += Move; break; } case QEvent::MouseButtonRelease: { if ( eventPattern.mouseMatch( QwtEventPattern::MouseSelect1, ( const QMouseEvent * )e ) ) { if ( state() == 1 ) { cmdList += Append; setState( 2 ); } } break; } case QEvent::KeyPress: { if ( eventPattern.keyMatch( QwtEventPattern::KeySelect1, ( const QKeyEvent * )e ) ) { if ( state() == 0 ) { cmdList += Begin; cmdList += Append; setState( 1 ); } else { if ( state() == 1 ) { cmdList += Append; setState( 2 ); } else if ( state() == 2 ) { cmdList += End; setState( 0 ); } } } break; } default: break; } return cmdList; } //! Constructor QwtPickerDragRectMachine::QwtPickerDragRectMachine(): QwtPickerMachine( RectSelection ) { } //! Transition QList QwtPickerDragRectMachine::transition( const QwtEventPattern &eventPattern, const QEvent *e ) { QList cmdList; switch ( e->type() ) { case QEvent::MouseButtonPress: { if ( eventPattern.mouseMatch( QwtEventPattern::MouseSelect1, ( const QMouseEvent * )e ) ) { if ( state() == 0 ) { cmdList += Begin; cmdList += Append; cmdList += Append; setState( 2 ); } } break; } case QEvent::MouseMove: case QEvent::Wheel: { if ( state() != 0 ) cmdList += Move; break; } case QEvent::MouseButtonRelease: { if ( state() == 2 ) { cmdList += End; setState( 0 ); } break; } case QEvent::KeyPress: { if ( eventPattern.keyMatch( QwtEventPattern::KeySelect1, ( const QKeyEvent * )e ) ) { if ( state() == 0 ) { cmdList += Begin; cmdList += Append; cmdList += Append; setState( 2 ); } else { cmdList += End; setState( 0 ); } } break; } default: break; } return cmdList; } //! Constructor QwtPickerPolygonMachine::QwtPickerPolygonMachine(): QwtPickerMachine( PolygonSelection ) { } //! Transition QList QwtPickerPolygonMachine::transition( const QwtEventPattern &eventPattern, const QEvent *e ) { QList cmdList; switch ( e->type() ) { case QEvent::MouseButtonPress: { if ( eventPattern.mouseMatch( QwtEventPattern::MouseSelect1, ( const QMouseEvent * )e ) ) { if ( state() == 0 ) { cmdList += Begin; cmdList += Append; cmdList += Append; setState( 1 ); } else { cmdList += End; setState( 0 ); } } if ( eventPattern.mouseMatch( QwtEventPattern::MouseSelect2, ( const QMouseEvent * )e ) ) { if ( state() == 1 ) cmdList += Append; } break; } case QEvent::MouseMove: case QEvent::Wheel: { if ( state() != 0 ) cmdList += Move; break; } case QEvent::KeyPress: { if ( eventPattern.keyMatch( QwtEventPattern::KeySelect1, ( const QKeyEvent * )e ) ) { if ( state() == 0 ) { cmdList += Begin; cmdList += Append; cmdList += Append; setState( 1 ); } else { cmdList += End; setState( 0 ); } } else if ( eventPattern.keyMatch( QwtEventPattern::KeySelect2, ( const QKeyEvent * )e ) ) { if ( state() == 1 ) cmdList += Append; } break; } default: break; } return cmdList; } workbench-1.1.1/src/Qwt/qwt_picker_machine.h000066400000000000000000000121341255417355300210560ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #ifndef QWT_PICKER_MACHINE #define QWT_PICKER_MACHINE 1 #include "qwt_global.h" #include class QEvent; class QwtEventPattern; /*! \brief A state machine for QwtPicker selections QwtPickerMachine accepts key and mouse events and translates them into selection commands. \sa QwtEventPattern::MousePatternCode, QwtEventPattern::KeyPatternCode */ class QWT_EXPORT QwtPickerMachine { public: /*! Type of a selection. \sa selectionType() */ enum SelectionType { //! The state machine not usable for any type of selection. NoSelection = -1, //! The state machine is for selecting a single point. PointSelection, //! The state machine is for selecting a rectangle (2 points). RectSelection, //! The state machine is for selecting a polygon (many points). PolygonSelection }; //! Commands - the output of a state machine enum Command { Begin, Append, Move, Remove, End }; QwtPickerMachine( SelectionType ); virtual ~QwtPickerMachine(); //! Transition virtual QList transition( const QwtEventPattern &, const QEvent * ) = 0; void reset(); int state() const; void setState( int ); SelectionType selectionType() const; private: const SelectionType d_selectionType; int d_state; }; /*! \brief A state machine for indicating mouse movements QwtPickerTrackerMachine supports displaying information corresponding to mouse movements, but is not intended for selecting anything. Begin/End are related to Enter/Leave events. */ class QWT_EXPORT QwtPickerTrackerMachine: public QwtPickerMachine { public: QwtPickerTrackerMachine(); virtual QList transition( const QwtEventPattern &, const QEvent * ); }; /*! \brief A state machine for point selections Pressing QwtEventPattern::MouseSelect1 or QwtEventPattern::KeySelect1 selects a point. \sa QwtEventPattern::MousePatternCode, QwtEventPattern::KeyPatternCode */ class QWT_EXPORT QwtPickerClickPointMachine: public QwtPickerMachine { public: QwtPickerClickPointMachine(); virtual QList transition( const QwtEventPattern &, const QEvent * ); }; /*! \brief A state machine for point selections Pressing QwtEventPattern::MouseSelect1 or QwtEventPattern::KeySelect1 starts the selection, releasing QwtEventPattern::MouseSelect1 or a second press of QwtEventPattern::KeySelect1 terminates it. */ class QWT_EXPORT QwtPickerDragPointMachine: public QwtPickerMachine { public: QwtPickerDragPointMachine(); virtual QList transition( const QwtEventPattern &, const QEvent * ); }; /*! \brief A state machine for rectangle selections Pressing QwtEventPattern::MouseSelect1 starts the selection, releasing it selects the first point. Pressing it again selects the second point and terminates the selection. Pressing QwtEventPattern::KeySelect1 also starts the selection, a second press selects the first point. A third one selects the second point and terminates the selection. \sa QwtEventPattern::MousePatternCode, QwtEventPattern::KeyPatternCode */ class QWT_EXPORT QwtPickerClickRectMachine: public QwtPickerMachine { public: QwtPickerClickRectMachine(); virtual QList transition( const QwtEventPattern &, const QEvent * ); }; /*! \brief A state machine for rectangle selections Pressing QwtEventPattern::MouseSelect1 selects the first point, releasing it the second point. Pressing QwtEventPattern::KeySelect1 also selects the first point, a second press selects the second point and terminates the selection. \sa QwtEventPattern::MousePatternCode, QwtEventPattern::KeyPatternCode */ class QWT_EXPORT QwtPickerDragRectMachine: public QwtPickerMachine { public: QwtPickerDragRectMachine(); virtual QList transition( const QwtEventPattern &, const QEvent * ); }; /*! \brief A state machine for polygon selections Pressing QwtEventPattern::MouseSelect1 or QwtEventPattern::KeySelect1 starts the selection and selects the first point, or appends a point. Pressing QwtEventPattern::MouseSelect2 or QwtEventPattern::KeySelect2 appends the last point and terminates the selection. \sa QwtEventPattern::MousePatternCode, QwtEventPattern::KeyPatternCode */ class QWT_EXPORT QwtPickerPolygonMachine: public QwtPickerMachine { public: QwtPickerPolygonMachine(); virtual QList transition( const QwtEventPattern &, const QEvent * ); }; #endif workbench-1.1.1/src/Qwt/qwt_plot.cpp000066400000000000000000000466421255417355300174410ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #include "qwt_plot.h" #include "qwt_plot_dict.h" #include "qwt_plot_layout.h" #include "qwt_scale_widget.h" #include "qwt_scale_engine.h" #include "qwt_text_label.h" #include "qwt_legend.h" #include "qwt_dyngrid_layout.h" #include "qwt_plot_canvas.h" #include #include #include #include #include class QwtPlot::PrivateData { public: QPointer lblTitle; QPointer canvas; QPointer legend; QwtPlotLayout *layout; bool autoReplot; }; /*! \brief Constructor \param parent Parent widget */ QwtPlot::QwtPlot( QWidget *parent ): QFrame( parent ) { initPlot( QwtText() ); } /*! \brief Constructor \param title Title text \param parent Parent widget */ QwtPlot::QwtPlot( const QwtText &title, QWidget *parent ): QFrame( parent ) { initPlot( title ); } //! Destructor QwtPlot::~QwtPlot() { detachItems( QwtPlotItem::Rtti_PlotItem, autoDelete() ); delete d_data->layout; deleteAxesData(); delete d_data; } /*! \brief Initializes a QwtPlot instance \param title Title text */ void QwtPlot::initPlot( const QwtText &title ) { d_data = new PrivateData; d_data->layout = new QwtPlotLayout; d_data->autoReplot = false; d_data->lblTitle = new QwtTextLabel( title, this ); d_data->lblTitle->setObjectName( "QwtPlotTitle" ); d_data->lblTitle->setFont( QFont( fontInfo().family(), 14, QFont::Bold ) ); QwtText text( title ); text.setRenderFlags( Qt::AlignCenter | Qt::TextWordWrap ); d_data->lblTitle->setText( text ); d_data->legend = NULL; initAxesData(); d_data->canvas = new QwtPlotCanvas( this ); d_data->canvas->setObjectName( "QwtPlotCanvas" ); d_data->canvas->setFrameStyle( QFrame::Panel | QFrame::Sunken ); d_data->canvas->setLineWidth( 2 ); updateTabOrder(); setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding ); resize( 200, 200 ); } /*! \brief Adds handling of layout requests \param event Event */ bool QwtPlot::event( QEvent *event ) { bool ok = QFrame::event( event ); switch ( event->type() ) { case QEvent::LayoutRequest: updateLayout(); break; case QEvent::PolishRequest: replot(); break; default:; } return ok; } //! Replots the plot if autoReplot() is \c true. void QwtPlot::autoRefresh() { if ( d_data->autoReplot ) replot(); } /*! \brief Set or reset the autoReplot option If the autoReplot option is set, the plot will be updated implicitly by manipulating member functions. Since this may be time-consuming, it is recommended to leave this option switched off and call replot() explicitly if necessary. The autoReplot option is set to false by default, which means that the user has to call replot() in order to make changes visible. \param tf \c true or \c false. Defaults to \c true. \sa replot() */ void QwtPlot::setAutoReplot( bool tf ) { d_data->autoReplot = tf; } /*! \return true if the autoReplot option is set. \sa setAutoReplot() */ bool QwtPlot::autoReplot() const { return d_data->autoReplot; } /*! Change the plot's title \param title New title */ void QwtPlot::setTitle( const QString &title ) { if ( title != d_data->lblTitle->text().text() ) { d_data->lblTitle->setText( title ); updateLayout(); } } /*! Change the plot's title \param title New title */ void QwtPlot::setTitle( const QwtText &title ) { if ( title != d_data->lblTitle->text() ) { d_data->lblTitle->setText( title ); updateLayout(); } } //! \return the plot's title QwtText QwtPlot::title() const { return d_data->lblTitle->text(); } //! \return the plot's title QwtPlotLayout *QwtPlot::plotLayout() { return d_data->layout; } //! \return the plot's titel label. const QwtPlotLayout *QwtPlot::plotLayout() const { return d_data->layout; } //! \return the plot's titel label. QwtTextLabel *QwtPlot::titleLabel() { return d_data->lblTitle; } /*! \return the plot's titel label. */ const QwtTextLabel *QwtPlot::titleLabel() const { return d_data->lblTitle; } /*! \return the plot's legend \sa insertLegend() */ QwtLegend *QwtPlot::legend() { return d_data->legend; } /*! \return the plot's legend \sa insertLegend() */ const QwtLegend *QwtPlot::legend() const { return d_data->legend; } /*! \return the plot's canvas */ QwtPlotCanvas *QwtPlot::canvas() { return d_data->canvas; } /*! \return the plot's canvas */ const QwtPlotCanvas *QwtPlot::canvas() const { return d_data->canvas; } /*! Return sizeHint \sa minimumSizeHint() */ QSize QwtPlot::sizeHint() const { int dw = 0; int dh = 0; for ( int axisId = 0; axisId < axisCnt; axisId++ ) { if ( axisEnabled( axisId ) ) { const int niceDist = 40; const QwtScaleWidget *scaleWidget = axisWidget( axisId ); const QwtScaleDiv &scaleDiv = scaleWidget->scaleDraw()->scaleDiv(); const int majCnt = scaleDiv.ticks( QwtScaleDiv::MajorTick ).count(); if ( axisId == yLeft || axisId == yRight ) { int hDiff = ( majCnt - 1 ) * niceDist - scaleWidget->minimumSizeHint().height(); if ( hDiff > dh ) dh = hDiff; } else { int wDiff = ( majCnt - 1 ) * niceDist - scaleWidget->minimumSizeHint().width(); if ( wDiff > dw ) dw = wDiff; } } } return minimumSizeHint() + QSize( dw, dh ); } /*! \brief Return a minimum size hint */ QSize QwtPlot::minimumSizeHint() const { QSize hint = d_data->layout->minimumSizeHint( this ); hint += QSize( 2 * frameWidth(), 2 * frameWidth() ); return hint; } /*! Resize and update internal layout \param e Resize event */ void QwtPlot::resizeEvent( QResizeEvent *e ) { QFrame::resizeEvent( e ); updateLayout(); } /*! \brief Redraw the plot If the autoReplot option is not set (which is the default) or if any curves are attached to raw data, the plot has to be refreshed explicitly in order to make changes visible. \sa setAutoReplot() \warning Calls canvas()->repaint, take care of infinite recursions */ void QwtPlot::replot() { bool doAutoReplot = autoReplot(); setAutoReplot( false ); updateAxes(); /* Maybe the layout needs to be updated, because of changed axes labels. We need to process them here before painting to avoid that scales and canvas get out of sync. */ QApplication::sendPostedEvents( this, QEvent::LayoutRequest ); d_data->canvas->replot(); setAutoReplot( doAutoReplot ); } /*! \brief Adjust plot content to its current size. \sa resizeEvent() */ void QwtPlot::updateLayout() { d_data->layout->activate( this, contentsRect() ); QRect titleRect = d_data->layout->titleRect().toRect(); QRect scaleRect[QwtPlot::axisCnt]; for ( int axisId = 0; axisId < axisCnt; axisId++ ) scaleRect[axisId] = d_data->layout->scaleRect( axisId ).toRect(); QRect legendRect = d_data->layout->legendRect().toRect(); QRect canvasRect = d_data->layout->canvasRect().toRect(); // // resize and show the visible widgets // if ( !d_data->lblTitle->text().isEmpty() ) { d_data->lblTitle->setGeometry( titleRect ); if ( !d_data->lblTitle->isVisibleTo( this ) ) d_data->lblTitle->show(); } else d_data->lblTitle->hide(); for ( int axisId = 0; axisId < axisCnt; axisId++ ) { if ( axisEnabled( axisId ) ) { axisWidget( axisId )->setGeometry( scaleRect[axisId] ); if ( axisId == xBottom || axisId == xTop ) { QRegion r( scaleRect[axisId] ); if ( axisEnabled( yLeft ) ) r = r.subtract( QRegion( scaleRect[yLeft] ) ); if ( axisEnabled( yRight ) ) r = r.subtract( QRegion( scaleRect[yRight] ) ); r.translate( -d_data->layout->scaleRect( axisId ).x(), -scaleRect[axisId].y() ); axisWidget( axisId )->setMask( r ); } if ( !axisWidget( axisId )->isVisibleTo( this ) ) axisWidget( axisId )->show(); } else axisWidget( axisId )->hide(); } if ( d_data->legend && d_data->layout->legendPosition() != ExternalLegend ) { if ( d_data->legend->itemCount() > 0 ) { d_data->legend->setGeometry( legendRect ); d_data->legend->show(); } else d_data->legend->hide(); } d_data->canvas->setGeometry( canvasRect ); } /*! Update the focus tab order The order is changed so that the canvas will be in front of the first legend item, or behind the last legend item - depending on the position of the legend. */ void QwtPlot::updateTabOrder() { if ( d_data->canvas->focusPolicy() == Qt::NoFocus ) return; if ( d_data->legend.isNull() || d_data->layout->legendPosition() == ExternalLegend || d_data->legend->legendItems().count() == 0 ) { return; } // Depending on the position of the legend the // tab order will be changed that the canvas is // next to the last legend item, or before // the first one. const bool canvasFirst = d_data->layout->legendPosition() == QwtPlot::BottomLegend || d_data->layout->legendPosition() == QwtPlot::RightLegend; QWidget *previous = NULL; QWidget *w = d_data->canvas; while ( ( w = w->nextInFocusChain() ) != d_data->canvas ) { bool isLegendItem = false; if ( w->focusPolicy() != Qt::NoFocus && w->parent() && w->parent() == d_data->legend->contentsWidget() ) { isLegendItem = true; } if ( canvasFirst ) { if ( isLegendItem ) break; previous = w; } else { if ( isLegendItem ) previous = w; else { if ( previous ) break; } } } if ( previous && previous != d_data->canvas ) setTabOrder( previous, d_data->canvas ); } /*! Redraw the canvas. \param painter Painter used for drawing \warning drawCanvas calls drawItems what is also used for printing. Applications that like to add individual plot items better overload drawItems() \sa drawItems() */ void QwtPlot::drawCanvas( QPainter *painter ) { QwtScaleMap maps[axisCnt]; for ( int axisId = 0; axisId < axisCnt; axisId++ ) maps[axisId] = canvasMap( axisId ); drawItems( painter, d_data->canvas->contentsRect(), maps ); } /*! Redraw the canvas items. \param painter Painter used for drawing \param canvasRect Bounding rectangle where to paint \param map QwtPlot::axisCnt maps, mapping between plot and paint device coordinates */ void QwtPlot::drawItems( QPainter *painter, const QRectF &canvasRect, const QwtScaleMap map[axisCnt] ) const { const QwtPlotItemList& itmList = itemList(); for ( QwtPlotItemIterator it = itmList.begin(); it != itmList.end(); ++it ) { QwtPlotItem *item = *it; if ( item && item->isVisible() ) { painter->save(); painter->setRenderHint( QPainter::Antialiasing, item->testRenderHint( QwtPlotItem::RenderAntialiased ) ); item->draw( painter, map[item->xAxis()], map[item->yAxis()], canvasRect ); painter->restore(); } } } /*! \param axisId Axis \return Map for the axis on the canvas. With this map pixel coordinates can translated to plot coordinates and vice versa. \sa QwtScaleMap, transform(), invTransform() */ QwtScaleMap QwtPlot::canvasMap( int axisId ) const { QwtScaleMap map; if ( !d_data->canvas ) return map; map.setTransformation( axisScaleEngine( axisId )->transformation() ); const QwtScaleDiv *sd = axisScaleDiv( axisId ); map.setScaleInterval( sd->lowerBound(), sd->upperBound() ); if ( axisEnabled( axisId ) ) { const QwtScaleWidget *s = axisWidget( axisId ); if ( axisId == yLeft || axisId == yRight ) { double y = s->y() + s->startBorderDist() - d_data->canvas->y(); double h = s->height() - s->startBorderDist() - s->endBorderDist(); map.setPaintInterval( y + h, y ); } else { double x = s->x() + s->startBorderDist() - d_data->canvas->x(); double w = s->width() - s->startBorderDist() - s->endBorderDist(); map.setPaintInterval( x, x + w ); } } else { int margin = 0; if ( !plotLayout()->alignCanvasToScales() ) margin = plotLayout()->canvasMargin( axisId ); const QRect &canvasRect = d_data->canvas->contentsRect(); if ( axisId == yLeft || axisId == yRight ) { map.setPaintInterval( canvasRect.bottom() - margin, canvasRect.top() + margin ); } else { map.setPaintInterval( canvasRect.left() + margin, canvasRect.right() - margin ); } } return map; } /*! \brief Change the background of the plotting area Sets brush to QPalette::Window of all colorgroups of the palette of the canvas. Using canvas()->setPalette() is a more powerful way to set these colors. \param brush New background brush \sa canvasBackground() */ void QwtPlot::setCanvasBackground( const QBrush &brush ) { QPalette pal = d_data->canvas->palette(); for ( int i = 0; i < QPalette::NColorGroups; i++ ) pal.setBrush( ( QPalette::ColorGroup )i, QPalette::Window, brush ); canvas()->setPalette( pal ); } /*! Nothing else than: canvas()->palette().brush( QPalette::Normal, QPalette::Window); \return Background brush of the plotting area. \sa setCanvasBackground() */ QBrush QwtPlot::canvasBackground() const { return canvas()->palette().brush( QPalette::Normal, QPalette::Window ); } /*! \brief Change the border width of the plotting area Nothing else than canvas()->setLineWidth(w), left for compatibility only. \param width New border width */ void QwtPlot::setCanvasLineWidth( int width ) { canvas()->setLineWidth( width ); updateLayout(); } /*! Nothing else than: canvas()->lineWidth(), left for compatibility only. \return the border width of the plotting area */ int QwtPlot::canvasLineWidth() const { return canvas()->lineWidth(); } /*! \return \c true if the specified axis exists, otherwise \c false \param axisId axis index */ bool QwtPlot::axisValid( int axisId ) { return ( ( axisId >= QwtPlot::yLeft ) && ( axisId < QwtPlot::axisCnt ) ); } /*! Called internally when the legend has been clicked on. Emits a legendClicked() signal. */ void QwtPlot::legendItemClicked() { if ( d_data->legend && sender()->isWidgetType() ) { QwtPlotItem *plotItem = ( QwtPlotItem* )d_data->legend->find( ( QWidget * )sender() ); if ( plotItem ) Q_EMIT legendClicked( plotItem ); } } /*! Called internally when the legend has been checked Emits a legendClicked() signal. */ void QwtPlot::legendItemChecked( bool on ) { if ( d_data->legend && sender()->isWidgetType() ) { QwtPlotItem *plotItem = ( QwtPlotItem* )d_data->legend->find( ( QWidget * )sender() ); if ( plotItem ) Q_EMIT legendChecked( plotItem, on ); } } /*! \brief Insert a legend If the position legend is \c QwtPlot::LeftLegend or \c QwtPlot::RightLegend the legend will be organized in one column from top to down. Otherwise the legend items will be placed in a table with a best fit number of columns from left to right. If pos != QwtPlot::ExternalLegend the plot widget will become parent of the legend. It will be deleted when the plot is deleted, or another legend is set with insertLegend(). \param legend Legend \param pos The legend's position. For top/left position the number of colums will be limited to 1, otherwise it will be set to unlimited. \param ratio Ratio between legend and the bounding rect of title, canvas and axes. The legend will be shrinked if it would need more space than the given ratio. The ratio is limited to ]0.0 .. 1.0]. In case of <= 0.0 it will be reset to the default ratio. The default vertical/horizontal ratio is 0.33/0.5. \sa legend(), QwtPlotLayout::legendPosition(), QwtPlotLayout::setLegendPosition() */ void QwtPlot::insertLegend( QwtLegend *legend, QwtPlot::LegendPosition pos, double ratio ) { d_data->layout->setLegendPosition( pos, ratio ); if ( legend != d_data->legend ) { if ( d_data->legend && d_data->legend->parent() == this ) delete d_data->legend; d_data->legend = legend; if ( d_data->legend ) { if ( pos != ExternalLegend ) { if ( d_data->legend->parent() != this ) d_data->legend->setParent( this ); } const QwtPlotItemList& itmList = itemList(); for ( QwtPlotItemIterator it = itmList.begin(); it != itmList.end(); ++it ) { ( *it )->updateLegend( d_data->legend ); } QwtDynGridLayout *tl = qobject_cast( d_data->legend->contentsWidget()->layout() ); if ( tl ) { switch ( d_data->layout->legendPosition() ) { case LeftLegend: case RightLegend: tl->setMaxCols( 1 ); // 1 column: align vertical break; case TopLegend: case BottomLegend: tl->setMaxCols( 0 ); // unlimited break; case ExternalLegend: break; } } } updateTabOrder(); } updateLayout(); } workbench-1.1.1/src/Qwt/qwt_plot.h000066400000000000000000000176151255417355300171040ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #ifndef QWT_PLOT_H #define QWT_PLOT_H #include "qwt_global.h" #include "qwt_text.h" #include "qwt_plot_dict.h" #include "qwt_scale_map.h" #include "qwt_interval.h" #include class QwtPlotLayout; class QwtLegend; class QwtScaleWidget; class QwtScaleEngine; class QwtScaleDiv; class QwtScaleDraw; class QwtTextLabel; class QwtPlotCanvas; /*! \brief A 2-D plotting widget QwtPlot is a widget for plotting two-dimensional graphs. An unlimited number of plot items can be displayed on its canvas. Plot items might be curves (QwtPlotCurve), markers (QwtPlotMarker), the grid (QwtPlotGrid), or anything else derived from QwtPlotItem. A plot can have up to four axes, with each plot item attached to an x- and a y axis. The scales at the axes can be explicitely set (QwtScaleDiv), or are calculated from the plot items, using algorithms (QwtScaleEngine) which can be configured separately for each axis. \image html plot.png \par Example The following example shows (schematically) the most simple way to use QwtPlot. By default, only the left and bottom axes are visible and their scales are computed automatically. \verbatim #include #include QwtPlot *myPlot = new QwtPlot("Two Curves", parent); // add curves QwtPlotCurve *curve1 = new QwtPlotCurve("Curve 1"); QwtPlotCurve *curve2 = new QwtPlotCurve("Curve 2"); // copy the data into the curves curve1->setData(...); curve2->setData(...); curve1->attach(myPlot); curve2->attach(myPlot); // finally, refresh the plot myPlot->replot(); \endverbatim */ class QWT_EXPORT QwtPlot: public QFrame, public QwtPlotDict { Q_OBJECT Q_PROPERTY( QString propertiesDocument READ grabProperties WRITE applyProperties ) public: //! \brief Axis index enum Axis { //! Y axis left of the canvas yLeft, //! Y axis right of the canvas yRight, //! X axis below the canvas xBottom, //! X axis above the canvas xTop, //! Number of axes axisCnt }; /*! Position of the legend, relative to the canvas. \sa insertLegend() \note In case of ExternalLegend, the legend is not handled by QwtPlotRenderer */ enum LegendPosition { //! The legend will be left from the QwtPlot::yLeft axis. LeftLegend, //! The legend will be right from the QwtPlot::yRight axis. RightLegend, //! The legend will be below QwtPlot::xBottom axis. BottomLegend, //! The legend will be between QwtPlot::xTop axis and the title. TopLegend, /*! External means that only the content of the legend will be handled by QwtPlot, but not its geometry. This type can be used to have a legend in an external window ( or on the canvas ). */ ExternalLegend }; explicit QwtPlot( QWidget * = NULL ); explicit QwtPlot( const QwtText &title, QWidget *p = NULL ); virtual ~QwtPlot(); void applyProperties( const QString & ); QString grabProperties() const; void setAutoReplot( bool tf = true ); bool autoReplot() const; // Layout QwtPlotLayout *plotLayout(); const QwtPlotLayout *plotLayout() const; // Title void setTitle( const QString & ); void setTitle( const QwtText &t ); QwtText title() const; QwtTextLabel *titleLabel(); const QwtTextLabel *titleLabel() const; // Canvas QwtPlotCanvas *canvas(); const QwtPlotCanvas *canvas() const; void setCanvasBackground( const QBrush & ); QBrush canvasBackground() const; void setCanvasLineWidth( int w ); int canvasLineWidth() const; virtual QwtScaleMap canvasMap( int axisId ) const; double invTransform( int axisId, int pos ) const; double transform( int axisId, double value ) const; // Axes QwtScaleEngine *axisScaleEngine( int axisId ); const QwtScaleEngine *axisScaleEngine( int axisId ) const; void setAxisScaleEngine( int axisId, QwtScaleEngine * ); void setAxisAutoScale( int axisId, bool on = true ); bool axisAutoScale( int axisId ) const; void enableAxis( int axisId, bool tf = true ); bool axisEnabled( int axisId ) const; void setAxisFont( int axisId, const QFont &f ); QFont axisFont( int axisId ) const; void setAxisScale( int axisId, double min, double max, double step = 0 ); void setAxisScaleDiv( int axisId, const QwtScaleDiv & ); void setAxisScaleDraw( int axisId, QwtScaleDraw * ); double axisStepSize( int axisId ) const; QwtInterval axisInterval( int axisId ) const; const QwtScaleDiv *axisScaleDiv( int axisId ) const; QwtScaleDiv *axisScaleDiv( int axisId ); const QwtScaleDraw *axisScaleDraw( int axisId ) const; QwtScaleDraw *axisScaleDraw( int axisId ); const QwtScaleWidget *axisWidget( int axisId ) const; QwtScaleWidget *axisWidget( int axisId ); void setAxisLabelAlignment( int axisId, Qt::Alignment ); void setAxisLabelRotation( int axisId, double rotation ); void setAxisTitle( int axisId, const QString & ); void setAxisTitle( int axisId, const QwtText & ); QwtText axisTitle( int axisId ) const; void setAxisMaxMinor( int axisId, int maxMinor ); int axisMaxMinor( int axisId ) const; void setAxisMaxMajor( int axisId, int maxMajor ); int axisMaxMajor( int axisId ) const; // Legend void insertLegend( QwtLegend *, LegendPosition = QwtPlot::RightLegend, double ratio = -1.0 ); QwtLegend *legend(); const QwtLegend *legend() const; // Misc virtual QSize sizeHint() const; virtual QSize minimumSizeHint() const; virtual void updateLayout(); virtual void drawCanvas( QPainter * ); void updateAxes(); virtual bool event( QEvent * ); virtual void drawItems( QPainter *, const QRectF &, const QwtScaleMap maps[axisCnt] ) const; Q_SIGNALS: /*! A signal which is emitted when the user has clicked on a legend item, which is in QwtLegend::ClickableItem mode. \param plotItem Corresponding plot item of the selected legend item \note clicks are disabled as default \sa QwtLegend::setItemMode(), QwtLegend::itemMode() */ void legendClicked( QwtPlotItem *plotItem ); /*! A signal which is emitted when the user has clicked on a legend item, which is in QwtLegend::CheckableItem mode \param plotItem Corresponding plot item of the selected legend item \param on True when the legen item is checked \note clicks are disabled as default \sa QwtLegend::setItemMode(), QwtLegend::itemMode() */ void legendChecked( QwtPlotItem *plotItem, bool on ); public Q_SLOTS: virtual void replot(); void autoRefresh(); protected Q_SLOTS: virtual void legendItemClicked(); virtual void legendItemChecked( bool ); protected: static bool axisValid( int axisId ); virtual void updateTabOrder(); virtual void resizeEvent( QResizeEvent *e ); private: void initAxesData(); void deleteAxesData(); void updateScaleDiv(); void initPlot( const QwtText &title ); class AxisData; AxisData *d_axisData[axisCnt]; class PrivateData; PrivateData *d_data; }; #endif workbench-1.1.1/src/Qwt/qwt_plot_axis.cpp000066400000000000000000000416221255417355300204560ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #include "qwt_plot.h" #include "qwt_math.h" #include "qwt_scale_widget.h" #include "qwt_scale_div.h" #include "qwt_scale_engine.h" class QwtPlot::AxisData { public: bool isEnabled; bool doAutoScale; double minValue; double maxValue; double stepSize; int maxMajor; int maxMinor; QwtScaleDiv scaleDiv; QwtScaleEngine *scaleEngine; QwtScaleWidget *scaleWidget; }; //! Initialize axes void QwtPlot::initAxesData() { int axisId; for ( axisId = 0; axisId < axisCnt; axisId++ ) d_axisData[axisId] = new AxisData; d_axisData[yLeft]->scaleWidget = new QwtScaleWidget( QwtScaleDraw::LeftScale, this ); d_axisData[yRight]->scaleWidget = new QwtScaleWidget( QwtScaleDraw::RightScale, this ); d_axisData[xTop]->scaleWidget = new QwtScaleWidget( QwtScaleDraw::TopScale, this ); d_axisData[xBottom]->scaleWidget = new QwtScaleWidget( QwtScaleDraw::BottomScale, this ); d_axisData[yLeft]->scaleWidget->setObjectName( "QwtPlotAxisYLeft" ); d_axisData[yRight]->scaleWidget->setObjectName( "QwtPlotAxisYRight" ); d_axisData[xTop]->scaleWidget->setObjectName( "QwtPlotAxisXTop" ); d_axisData[xBottom]->scaleWidget->setObjectName( "QwtPlotAxisXBottom" ); QFont fscl( fontInfo().family(), 10 ); QFont fttl( fontInfo().family(), 12, QFont::Bold ); for ( axisId = 0; axisId < axisCnt; axisId++ ) { AxisData &d = *d_axisData[axisId]; d.scaleWidget->setFont( fscl ); d.scaleWidget->setMargin( 2 ); QwtText text = d.scaleWidget->title(); text.setFont( fttl ); d.scaleWidget->setTitle( text ); d.doAutoScale = true; d.minValue = 0.0; d.maxValue = 1000.0; d.stepSize = 0.0; d.maxMinor = 5; d.maxMajor = 8; d.scaleEngine = new QwtLinearScaleEngine; d.scaleDiv.invalidate(); } d_axisData[yLeft]->isEnabled = true; d_axisData[yRight]->isEnabled = false; d_axisData[xBottom]->isEnabled = true; d_axisData[xTop]->isEnabled = false; } void QwtPlot::deleteAxesData() { for ( int axisId = 0; axisId < axisCnt; axisId++ ) { delete d_axisData[axisId]->scaleEngine; delete d_axisData[axisId]; d_axisData[axisId] = NULL; } } /*! \return specified axis, or NULL if axisId is invalid. \param axisId axis index */ const QwtScaleWidget *QwtPlot::axisWidget( int axisId ) const { if ( axisValid( axisId ) ) return d_axisData[axisId]->scaleWidget; return NULL; } /*! \return specified axis, or NULL if axisId is invalid. \param axisId axis index */ QwtScaleWidget *QwtPlot::axisWidget( int axisId ) { if ( axisValid( axisId ) ) return d_axisData[axisId]->scaleWidget; return NULL; } /*! Change the scale engine for an axis \param axisId axis index \param scaleEngine Scale engine \sa axisScaleEngine() */ void QwtPlot::setAxisScaleEngine( int axisId, QwtScaleEngine *scaleEngine ) { if ( axisValid( axisId ) && scaleEngine != NULL ) { AxisData &d = *d_axisData[axisId]; delete d.scaleEngine; d.scaleEngine = scaleEngine; d.scaleDiv.invalidate(); autoRefresh(); } } /*! \param axisId axis index \return Scale engine for a specific axis */ QwtScaleEngine *QwtPlot::axisScaleEngine( int axisId ) { if ( axisValid( axisId ) ) return d_axisData[axisId]->scaleEngine; else return NULL; } /*! \param axisId axis index \return Scale engine for a specific axis */ const QwtScaleEngine *QwtPlot::axisScaleEngine( int axisId ) const { if ( axisValid( axisId ) ) return d_axisData[axisId]->scaleEngine; else return NULL; } /*! \return \c true if autoscaling is enabled \param axisId axis index */ bool QwtPlot::axisAutoScale( int axisId ) const { if ( axisValid( axisId ) ) return d_axisData[axisId]->doAutoScale; else return false; } /*! \return \c true if a specified axis is enabled \param axisId axis index */ bool QwtPlot::axisEnabled( int axisId ) const { if ( axisValid( axisId ) ) return d_axisData[axisId]->isEnabled; else return false; } /*! \return the font of the scale labels for a specified axis \param axisId axis index */ QFont QwtPlot::axisFont( int axisId ) const { if ( axisValid( axisId ) ) return axisWidget( axisId )->font(); else return QFont(); } /*! \return the maximum number of major ticks for a specified axis \param axisId axis index \sa setAxisMaxMajor() */ int QwtPlot::axisMaxMajor( int axisId ) const { if ( axisValid( axisId ) ) return d_axisData[axisId]->maxMajor; else return 0; } /*! \return the maximum number of minor ticks for a specified axis \param axisId axis index \sa setAxisMaxMinor() */ int QwtPlot::axisMaxMinor( int axisId ) const { if ( axisValid( axisId ) ) return d_axisData[axisId]->maxMinor; else return 0; } /*! \brief Return the scale division of a specified axis axisScaleDiv(axisId)->lowerBound(), axisScaleDiv(axisId)->upperBound() are the current limits of the axis scale. \param axisId axis index \return Scale division \sa QwtScaleDiv, setAxisScaleDiv() */ const QwtScaleDiv *QwtPlot::axisScaleDiv( int axisId ) const { if ( !axisValid( axisId ) ) return NULL; return &d_axisData[axisId]->scaleDiv; } /*! \brief Return the scale division of a specified axis axisScaleDiv(axisId)->lowerBound(), axisScaleDiv(axisId)->upperBound() are the current limits of the axis scale. \param axisId axis index \return Scale division \sa QwtScaleDiv, setAxisScaleDiv() */ QwtScaleDiv *QwtPlot::axisScaleDiv( int axisId ) { if ( !axisValid( axisId ) ) return NULL; return &d_axisData[axisId]->scaleDiv; } /*! \returns the scale draw of a specified axis \param axisId axis index \return specified scaleDraw for axis, or NULL if axis is invalid. \sa QwtScaleDraw */ const QwtScaleDraw *QwtPlot::axisScaleDraw( int axisId ) const { if ( !axisValid( axisId ) ) return NULL; return axisWidget( axisId )->scaleDraw(); } /*! \returns the scale draw of a specified axis \param axisId axis index \return specified scaleDraw for axis, or NULL if axis is invalid. \sa QwtScaleDraw */ QwtScaleDraw *QwtPlot::axisScaleDraw( int axisId ) { if ( !axisValid( axisId ) ) return NULL; return axisWidget( axisId )->scaleDraw(); } /*! Return the step size parameter, that has been set in setAxisScale. This doesn't need to be the step size of the current scale. \param axisId axis index \return step size parameter value \sa setAxisScale() */ double QwtPlot::axisStepSize( int axisId ) const { if ( !axisValid( axisId ) ) return 0; return d_axisData[axisId]->stepSize; } /*! \brief Return the current interval of the specified axis This is only a convenience function for axisScaleDiv( axisId )->interval(); \param axisId axis index \return Scale interval \sa QwtScaleDiv, axisScaleDiv() */ QwtInterval QwtPlot::axisInterval( int axisId ) const { if ( !axisValid( axisId ) ) return QwtInterval(); return d_axisData[axisId]->scaleDiv.interval(); } /*! \return the title of a specified axis \param axisId axis index */ QwtText QwtPlot::axisTitle( int axisId ) const { if ( axisValid( axisId ) ) return axisWidget( axisId )->title(); else return QwtText(); } /*! \brief Enable or disable a specified axis When an axis is disabled, this only means that it is not visible on the screen. Curves, markers and can be attached to disabled axes, and transformation of screen coordinates into values works as normal. Only xBottom and yLeft are enabled by default. \param axisId axis index \param tf \c true (enabled) or \c false (disabled) */ void QwtPlot::enableAxis( int axisId, bool tf ) { if ( axisValid( axisId ) && tf != d_axisData[axisId]->isEnabled ) { d_axisData[axisId]->isEnabled = tf; updateLayout(); } } /*! Transform the x or y coordinate of a position in the drawing region into a value. \param axisId axis index \param pos position \warning The position can be an x or a y coordinate, depending on the specified axis. */ double QwtPlot::invTransform( int axisId, int pos ) const { if ( axisValid( axisId ) ) return( canvasMap( axisId ).invTransform( pos ) ); else return 0.0; } /*! \brief Transform a value into a coordinate in the plotting region \param axisId axis index \param value value \return X or y coordinate in the plotting region corresponding to the value. */ double QwtPlot::transform( int axisId, double value ) const { if ( axisValid( axisId ) ) return( canvasMap( axisId ).transform( value ) ); else return 0.0; } /*! \brief Change the font of an axis \param axisId axis index \param f font \warning This function changes the font of the tick labels, not of the axis title. */ void QwtPlot::setAxisFont( int axisId, const QFont &f ) { if ( axisValid( axisId ) ) axisWidget( axisId )->setFont( f ); } /*! \brief Enable autoscaling for a specified axis This member function is used to switch back to autoscaling mode after a fixed scale has been set. Autoscaling is enabled by default. \param axisId axis index \param on On/Off \sa setAxisScale(), setAxisScaleDiv(), updateAxes() \note The autoscaling flag has no effect until updateAxes() is executed ( called by replot() ). */ void QwtPlot::setAxisAutoScale( int axisId, bool on ) { if ( axisValid( axisId ) && ( d_axisData[axisId]->doAutoScale != on ) ) { d_axisData[axisId]->doAutoScale = on; autoRefresh(); } } /*! \brief Disable autoscaling and specify a fixed scale for a selected axis. \param axisId axis index \param min \param max minimum and maximum of the scale \param stepSize Major step size. If step == 0, the step size is calculated automatically using the maxMajor setting. \sa setAxisMaxMajor(), setAxisAutoScale(), axisStepSize() */ void QwtPlot::setAxisScale( int axisId, double min, double max, double stepSize ) { if ( axisValid( axisId ) ) { AxisData &d = *d_axisData[axisId]; d.doAutoScale = false; d.scaleDiv.invalidate(); d.minValue = min; d.maxValue = max; d.stepSize = stepSize; autoRefresh(); } } /*! \brief Disable autoscaling and specify a fixed scale for a selected axis. \param axisId axis index \param scaleDiv Scale division \sa setAxisScale(), setAxisAutoScale() */ void QwtPlot::setAxisScaleDiv( int axisId, const QwtScaleDiv &scaleDiv ) { if ( axisValid( axisId ) ) { AxisData &d = *d_axisData[axisId]; d.doAutoScale = false; d.scaleDiv = scaleDiv; autoRefresh(); } } /*! \brief Set a scale draw \param axisId axis index \param scaleDraw object responsible for drawing scales. By passing scaleDraw it is possible to extend QwtScaleDraw functionality and let it take place in QwtPlot. Please note that scaleDraw has to be created with new and will be deleted by the corresponding QwtScale member ( like a child object ). \sa QwtScaleDraw, QwtScaleWidget \warning The attributes of scaleDraw will be overwritten by those of the previous QwtScaleDraw. */ void QwtPlot::setAxisScaleDraw( int axisId, QwtScaleDraw *scaleDraw ) { if ( axisValid( axisId ) ) { axisWidget( axisId )->setScaleDraw( scaleDraw ); autoRefresh(); } } /*! Change the alignment of the tick labels \param axisId axis index \param alignment Or'd Qt::AlignmentFlags see \sa QwtScaleDraw::setLabelAlignment() */ void QwtPlot::setAxisLabelAlignment( int axisId, Qt::Alignment alignment ) { if ( axisValid( axisId ) ) axisWidget( axisId )->setLabelAlignment( alignment ); } /*! Rotate all tick labels \param axisId axis index \param rotation Angle in degrees. When changing the label rotation, the label alignment might be adjusted too. \sa QwtScaleDraw::setLabelRotation(), setAxisLabelAlignment() */ void QwtPlot::setAxisLabelRotation( int axisId, double rotation ) { if ( axisValid( axisId ) ) axisWidget( axisId )->setLabelRotation( rotation ); } /*! Set the maximum number of minor scale intervals for a specified axis \param axisId axis index \param maxMinor maximum number of minor steps \sa axisMaxMinor() */ void QwtPlot::setAxisMaxMinor( int axisId, int maxMinor ) { if ( axisValid( axisId ) ) { maxMinor = qBound( 0, maxMinor, 100 ); AxisData &d = *d_axisData[axisId]; if ( maxMinor != d.maxMinor ) { d.maxMinor = maxMinor; d.scaleDiv.invalidate(); autoRefresh(); } } } /*! Set the maximum number of major scale intervals for a specified axis \param axisId axis index \param maxMajor maximum number of major steps \sa axisMaxMajor() */ void QwtPlot::setAxisMaxMajor( int axisId, int maxMajor ) { if ( axisValid( axisId ) ) { maxMajor = qBound( 1, maxMajor, 10000 ); AxisData &d = *d_axisData[axisId]; if ( maxMajor != d.maxMajor ) { d.maxMajor = maxMajor; d.scaleDiv.invalidate(); autoRefresh(); } } } /*! \brief Change the title of a specified axis \param axisId axis index \param title axis title */ void QwtPlot::setAxisTitle( int axisId, const QString &title ) { if ( axisValid( axisId ) ) axisWidget( axisId )->setTitle( title ); } /*! \brief Change the title of a specified axis \param axisId axis index \param title axis title */ void QwtPlot::setAxisTitle( int axisId, const QwtText &title ) { if ( axisValid( axisId ) ) axisWidget( axisId )->setTitle( title ); } //! Rebuild the scales void QwtPlot::updateAxes() { // Find bounding interval of the item data // for all axes, where autoscaling is enabled QwtInterval intv[axisCnt]; const QwtPlotItemList& itmList = itemList(); QwtPlotItemIterator it; for ( it = itmList.begin(); it != itmList.end(); ++it ) { const QwtPlotItem *item = *it; if ( !item->testItemAttribute( QwtPlotItem::AutoScale ) ) continue; if ( !item->isVisible() ) continue; if ( axisAutoScale( item->xAxis() ) || axisAutoScale( item->yAxis() ) ) { const QRectF rect = item->boundingRect(); intv[item->xAxis()] |= QwtInterval( rect.left(), rect.right() ); intv[item->yAxis()] |= QwtInterval( rect.top(), rect.bottom() ); } } // Adjust scales for ( int axisId = 0; axisId < axisCnt; axisId++ ) { AxisData &d = *d_axisData[axisId]; double minValue = d.minValue; double maxValue = d.maxValue; double stepSize = d.stepSize; if ( d.doAutoScale && intv[axisId].isValid() ) { d.scaleDiv.invalidate(); minValue = intv[axisId].minValue(); maxValue = intv[axisId].maxValue(); d.scaleEngine->autoScale( d.maxMajor, minValue, maxValue, stepSize ); } if ( !d.scaleDiv.isValid() ) { d.scaleDiv = d.scaleEngine->divideScale( minValue, maxValue, d.maxMajor, d.maxMinor, stepSize ); } QwtScaleWidget *scaleWidget = axisWidget( axisId ); scaleWidget->setScaleDiv( d.scaleEngine->transformation(), d.scaleDiv ); int startDist, endDist; scaleWidget->getBorderDistHint( startDist, endDist ); scaleWidget->setBorderDist( startDist, endDist ); } for ( it = itmList.begin(); it != itmList.end(); ++it ) { QwtPlotItem *item = *it; item->updateScaleDiv( *axisScaleDiv( item->xAxis() ), *axisScaleDiv( item->yAxis() ) ); } } workbench-1.1.1/src/Qwt/qwt_plot_canvas.cpp000066400000000000000000000704441255417355300207710ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #include "qwt_plot_canvas.h" #include "qwt_painter.h" #include "qwt_null_paintdevice.h" #include "qwt_math.h" #include "qwt_plot.h" #include #include #include #include #include #include #ifdef Q_WS_X11 #include #endif class QwtStyleSheetRecorder: public QwtNullPaintDevice { public: QwtStyleSheetRecorder( const QSize &size ): QwtNullPaintDevice( QPaintEngine::AllFeatures ) { setSize( size ); } virtual void updateState( const QPaintEngineState &state ) { if ( state.state() & QPaintEngine::DirtyPen ) { d_pen = state.pen(); } if ( state.state() & QPaintEngine::DirtyBrush ) { d_brush = state.brush(); } if ( state.state() & QPaintEngine::DirtyBrushOrigin ) { d_origin = state.brushOrigin(); } } virtual void drawRects(const QRectF *rects, int count ) { for ( int i = 0; i < count; i++ ) border.rectList += rects[i]; } virtual void drawPath( const QPainterPath &path ) { const QRectF rect( QPointF( 0.0, 0.0 ) , size() ); if ( path.controlPointRect().contains( rect.center() ) ) { setCornerRects( path ); alignCornerRects( rect ); background.path = path; background.brush = d_brush; background.origin = d_origin; } else { border.pathList += path; } } void setCornerRects( const QPainterPath &path ) { QPointF pos( 0.0, 0.0 ); for ( int i = 0; i < path.elementCount(); i++ ) { QPainterPath::Element el = path.elementAt(i); switch( el.type ) { case QPainterPath::MoveToElement: case QPainterPath::LineToElement: { pos.setX( el.x ); pos.setY( el.y ); break; } case QPainterPath::CurveToElement: { QRectF r( pos, QPointF( el.x, el.y ) ); clipRects += r.normalized(); pos.setX( el.x ); pos.setY( el.y ); break; } case QPainterPath::CurveToDataElement: { if ( clipRects.size() > 0 ) { QRectF r = clipRects.last(); r.setCoords( qMin( r.left(), el.x ), qMin( r.top(), el.y ), qMax( r.right(), el.x ), qMax( r.bottom(), el.y ) ); clipRects.last() = r.normalized(); } break; } } } } private: void alignCornerRects( const QRectF &rect ) { for ( int i = 0; i < clipRects.size(); i++ ) { QRectF &r = clipRects[i]; if ( r.center().x() < rect.center().x() ) r.setLeft( rect.left() ); else r.setRight( rect.right() ); if ( r.center().y() < rect.center().y() ) r.setTop( rect.top() ); else r.setBottom( rect.bottom() ); } } public: QVector clipRects; struct Border { QList pathList; QList rectList; QRegion clipRegion; } border; struct Background { QPainterPath path; QBrush brush; QPointF origin; } background; private: QPen d_pen; QBrush d_brush; QPointF d_origin; }; static void qwtDrawBackground( QPainter *painter, QWidget *widget ) { const QBrush &brush = widget->palette().brush( widget->backgroundRole() ); if ( brush.style() == Qt::TexturePattern ) { QPixmap pm( widget->size() ); pm.fill( widget, 0, 0 ); painter->drawPixmap( 0, 0, pm ); } else if ( brush.gradient() ) { QVector rects; if ( brush.gradient()->coordinateMode() == QGradient::ObjectBoundingMode ) { rects += widget->rect(); } else { rects = painter->clipRegion().rects(); } #if 1 bool useRaster = false; if ( painter->paintEngine()->type() == QPaintEngine::X11 ) { // Qt 4.7.1: gradients on X11 are broken ( subrects + // QGradient::StretchToDeviceMode ) and horrible slow. // As workaround we have to use the raster paintengine. // Even if the QImage -> QPixmap translation is slow // it is three times faster, than using X11 directly useRaster = true; } #endif if ( useRaster ) { QImage::Format format = QImage::Format_RGB32; const QGradientStops stops = brush.gradient()->stops(); for ( int i = 0; i < stops.size(); i++ ) { if ( stops[i].second.alpha() != 255 ) { // don't use Format_ARGB32_Premultiplied. It's // recommended by the Qt docs, but QPainter::drawImage() // is horrible slow on X11. format = QImage::Format_ARGB32; break; } } QImage image( widget->size(), format ); QPainter p( &image ); p.setPen( Qt::NoPen ); p.setBrush( brush ); p.drawRects( rects ); p.end(); painter->drawImage( 0, 0, image ); } else { painter->save(); painter->setPen( Qt::NoPen ); painter->setBrush( brush ); painter->drawRects( rects ); painter->restore(); } } else { painter->save(); painter->setPen( Qt::NoPen ); painter->setBrush( brush ); painter->drawRects( painter->clipRegion().rects() ); painter->restore(); } } static inline void qwtRevertPath( QPainterPath &path ) { if ( path.elementCount() == 4 ) { QPainterPath::Element &el0 = const_cast( path.elementAt(0) ); QPainterPath::Element &el2 = const_cast( path.elementAt(3) ); qSwap( el0.x, el2.x ); qSwap( el0.y, el2.y ); } } static QPainterPath qwtCombinePathList( const QRectF &rect, const QList &pathList ) { if ( pathList.isEmpty() ) return QPainterPath(); QPainterPath ordered[8]; // starting top left for ( int i = 0; i < pathList.size(); i++ ) { int index = -1; QPainterPath subPath = pathList[i]; const QRectF br = pathList[i].controlPointRect(); if ( br.center().x() < rect.center().x() ) { if ( br.center().y() < rect.center().y() ) { if ( qAbs( br.top() - rect.top() ) < qAbs( br.left() - rect.left() ) ) { index = 1; } else { index = 0; } } else { if ( qAbs( br.bottom() - rect.bottom() ) < qAbs( br.left() - rect.left() ) ) { index = 6; } else { index = 7; } } if ( subPath.currentPosition().y() > br.center().y() ) qwtRevertPath( subPath ); } else { if ( br.center().y() < rect.center().y() ) { if ( qAbs( br.top() - rect.top() ) < qAbs( br.right() - rect.right() ) ) { index = 2; } else { index = 3; } } else { if ( qAbs( br.bottom() - rect.bottom() ) < qAbs( br.right() - rect.right() ) ) { index = 5; } else { index = 4; } } if ( subPath.currentPosition().y() < br.center().y() ) qwtRevertPath( subPath ); } ordered[index] = subPath; } for ( int i = 0; i < 4; i++ ) { if ( ordered[ 2 * i].isEmpty() != ordered[2 * i + 1].isEmpty() ) { // we don't accept incomplete rounded borders return QPainterPath(); } } const QPolygonF corners( rect ); QPainterPath path; //path.moveTo( rect.topLeft() ); for ( int i = 0; i < 4; i++ ) { if ( ordered[2 * i].isEmpty() ) { path.lineTo( corners[i] ); } else { path.connectPath( ordered[2 * i] ); path.connectPath( ordered[2 * i + 1] ); } } path.closeSubpath(); #if 0 return path.simplified(); #else return path; #endif } static inline void qwtDrawStyledBackground( QWidget *w, QPainter *painter ) { QStyleOption opt; opt.initFrom(w); w->style()->drawPrimitive( QStyle::PE_Widget, &opt, painter, w); } static QWidget *qwtBackgroundWidget( QWidget *w ) { if ( w->parentWidget() == NULL ) return w; if ( w->autoFillBackground() ) { const QBrush brush = w->palette().brush( w->backgroundRole() ); if ( brush.color().alpha() > 0 ) return w; } if ( w->testAttribute( Qt::WA_StyledBackground ) ) { QImage image( 1, 1, QImage::Format_ARGB32 ); image.fill( Qt::transparent ); QPainter painter( &image ); painter.translate( -w->rect().center() ); qwtDrawStyledBackground( w, &painter ); painter.end(); if ( qAlpha( image.pixel( 0, 0 ) ) != 0 ) return w; } return qwtBackgroundWidget( w->parentWidget() ); } static void qwtFillBackground( QPainter *painter, QWidget *widget, const QVector &fillRects ) { if ( fillRects.isEmpty() ) return; QRegion clipRegion; if ( painter->hasClipping() ) clipRegion = painter->transform().map( painter->clipRegion() ); else clipRegion = widget->contentsRect(); // Try to find out which widget fills // the unfilled areas of the styled background QWidget *bgWidget = qwtBackgroundWidget( widget->parentWidget() ); for ( int i = 0; i < fillRects.size(); i++ ) { const QRect rect = fillRects[i].toAlignedRect(); if ( clipRegion.intersects( rect ) ) { QPixmap pm( rect.size() ); pm.fill( bgWidget, widget->mapTo( bgWidget, rect.topLeft() ) ); painter->drawPixmap( rect, pm ); } } } static void qwtFillBackground( QPainter *painter, QwtPlotCanvas *canvas ) { QVector rects; if ( canvas->testAttribute( Qt::WA_StyledBackground ) ) { QwtStyleSheetRecorder recorder( canvas->size() ); QPainter p( &recorder ); qwtDrawStyledBackground( canvas, &p ); p.end(); if ( recorder.background.brush.isOpaque() ) rects = recorder.clipRects; else rects += canvas->rect(); } else { const QRectF r = canvas->rect(); const double radius = canvas->borderRadius(); if ( radius > 0.0 ) { QSize sz( radius, radius ); rects += QRectF( r.topLeft(), sz ); rects += QRectF( r.topRight() - QPointF( radius, 0 ), sz ); rects += QRectF( r.bottomRight() - QPointF( radius, radius ), sz ); rects += QRectF( r.bottomLeft() - QPointF( 0, radius ), sz ); } } qwtFillBackground( painter, canvas, rects); } class QwtPlotCanvas::PrivateData { public: PrivateData(): focusIndicator( NoFocusIndicator ), borderRadius( 0 ), paintAttributes( 0 ), backingStore( NULL ) { styleSheet.hasBorder = false; } ~PrivateData() { delete backingStore; } FocusIndicator focusIndicator; double borderRadius; QwtPlotCanvas::PaintAttributes paintAttributes; QPixmap *backingStore; struct StyleSheet { bool hasBorder; QPainterPath borderPath; QVector cornerRects; struct StyleSheetBackground { QBrush brush; QPointF origin; } background; } styleSheet; }; //! Sets a cross cursor, enables QwtPlotCanvas::BackingStore QwtPlotCanvas::QwtPlotCanvas( QwtPlot *plot ): QFrame( plot ) { d_data = new PrivateData; #ifndef QT_NO_CURSOR setCursor( Qt::CrossCursor ); #endif setAutoFillBackground( true ); setPaintAttribute( QwtPlotCanvas::BackingStore, true ); setPaintAttribute( QwtPlotCanvas::Opaque, true ); setPaintAttribute( QwtPlotCanvas::HackStyledBackground, true ); } //! Destructor QwtPlotCanvas::~QwtPlotCanvas() { delete d_data; } //! Return parent plot widget QwtPlot *QwtPlotCanvas::plot() { return qobject_cast( parentWidget() ); } //! Return parent plot widget const QwtPlot *QwtPlotCanvas::plot() const { return qobject_cast( parentWidget() ); } /*! \brief Changing the paint attributes \param attribute Paint attribute \param on On/Off \sa testPaintAttribute(), backingStore() */ void QwtPlotCanvas::setPaintAttribute( PaintAttribute attribute, bool on ) { if ( bool( d_data->paintAttributes & attribute ) == on ) return; if ( on ) d_data->paintAttributes |= attribute; else d_data->paintAttributes &= ~attribute; switch ( attribute ) { case BackingStore: { if ( on ) { if ( d_data->backingStore == NULL ) d_data->backingStore = new QPixmap(); if ( isVisible() ) { *d_data->backingStore = QPixmap::grabWidget( this, rect() ); } } else { delete d_data->backingStore; d_data->backingStore = NULL; } break; } case Opaque: { if ( on ) setAttribute( Qt::WA_OpaquePaintEvent, true ); break; } case HackStyledBackground: case ImmediatePaint: { break; } } } /*! Test wether a paint attribute is enabled \param attribute Paint attribute \return true if the attribute is enabled \sa setPaintAttribute() */ bool QwtPlotCanvas::testPaintAttribute( PaintAttribute attribute ) const { return d_data->paintAttributes & attribute; } //! \return Backing store, might be null const QPixmap *QwtPlotCanvas::backingStore() const { return d_data->backingStore; } //! Invalidate the internal backing store void QwtPlotCanvas::invalidateBackingStore() { if ( d_data->backingStore ) *d_data->backingStore = QPixmap(); } /*! Set the focus indicator \sa FocusIndicator, focusIndicator() */ void QwtPlotCanvas::setFocusIndicator( FocusIndicator focusIndicator ) { d_data->focusIndicator = focusIndicator; } /*! \return Focus indicator \sa FocusIndicator, setFocusIndicator() */ QwtPlotCanvas::FocusIndicator QwtPlotCanvas::focusIndicator() const { return d_data->focusIndicator; } /*! Set the radius for the corners of the border frame \param radius Radius of a rounded corner \sa borderRadius() */ void QwtPlotCanvas::setBorderRadius( double radius ) { d_data->borderRadius = qMax( 0.0, radius ); } /*! \return Radius for the corners of the border frame \sa setBorderRadius() */ double QwtPlotCanvas::borderRadius() const { return d_data->borderRadius; } /*! Qt event handler for QEvent::PolishRequest and QEvent::StyleChange \param event Qt Event */ bool QwtPlotCanvas::event( QEvent *event ) { if ( event->type() == QEvent::PolishRequest ) { if ( testPaintAttribute( QwtPlotCanvas::Opaque ) ) { // Setting a style sheet changes the // Qt::WA_OpaquePaintEvent attribute, but we insist // on painting the background. setAttribute( Qt::WA_OpaquePaintEvent, true ); } } if ( event->type() == QEvent::PolishRequest || event->type() == QEvent::StyleChange ) { updateStyleSheetInfo(); } return QFrame::event( event ); } /*! Paint event \param event Paint event */ void QwtPlotCanvas::paintEvent( QPaintEvent *event ) { QPainter painter( this ); painter.setClipRegion( event->region() ); if ( testPaintAttribute( QwtPlotCanvas::BackingStore ) && d_data->backingStore != NULL ) { QPixmap &bs = *d_data->backingStore; if ( bs.size() != size() ) { bs = QPixmap( size() ); #ifdef Q_WS_X11 if ( bs.x11Info().screen() != x11Info().screen() ) bs.x11SetScreen( x11Info().screen() ); #endif if ( testAttribute(Qt::WA_StyledBackground) ) { QPainter p( &bs ); qwtFillBackground( &p, this ); drawCanvas( &p, true ); } else { QPainter p; if ( d_data->borderRadius <= 0.0 ) { bs.fill( this, 0, 0 ); p.begin( &bs ); drawCanvas( &p, false ); } else { p.begin( &bs ); qwtFillBackground( &p, this ); drawCanvas( &p, true ); } if ( frameWidth() > 0 ) drawBorder( &p ); } } painter.drawPixmap( 0, 0, *d_data->backingStore ); } else { if ( testAttribute(Qt::WA_StyledBackground ) ) { if ( testAttribute( Qt::WA_OpaquePaintEvent ) ) { qwtFillBackground( &painter, this ); drawCanvas( &painter, true ); } else { drawCanvas( &painter, false ); } } else { if ( testAttribute( Qt::WA_OpaquePaintEvent ) ) { if ( autoFillBackground() ) qwtDrawBackground( &painter, this ); } drawCanvas( &painter, false ); if ( frameWidth() > 0 ) drawBorder( &painter ); } } if ( hasFocus() && focusIndicator() == CanvasFocusIndicator ) drawFocusIndicator( &painter ); } void QwtPlotCanvas::drawCanvas( QPainter *painter, bool withBackground ) { bool hackStyledBackground = false; if ( withBackground && testAttribute( Qt::WA_StyledBackground ) && testPaintAttribute( HackStyledBackground ) ) { // Antialiasing rounded borders is done by // inserting pixels with colors between the // border color and the color on the canvas, // When the border is painted before the plot items // these colors are interpolated for the canvas // and the plot items need to be clipped excluding // the anialiased pixels. In situations, where // the plot items fill the area at the rounded // borders this is noticeable. // The only way to avoid these annoying "artefacts" // is to paint the border on top of the plot items. if ( d_data->styleSheet.hasBorder && !d_data->styleSheet.borderPath.isEmpty() ) { // We have a border with at least one rounded corner hackStyledBackground = true; } } if ( withBackground ) { painter->save(); if ( testAttribute( Qt::WA_StyledBackground ) ) { if ( hackStyledBackground ) { // paint background without border painter->setPen( Qt::NoPen ); painter->setBrush( d_data->styleSheet.background.brush ); painter->setBrushOrigin( d_data->styleSheet.background.origin ); painter->setClipPath( d_data->styleSheet.borderPath ); painter->drawRect( contentsRect() ); } else { qwtDrawStyledBackground( this, painter ); } } else if ( autoFillBackground() ) { painter->setPen( Qt::NoPen ); painter->setBrush( palette().brush( backgroundRole() ) ); if ( d_data->borderRadius > 0.0 ) { if ( frameWidth() > 0 ) { painter->setClipPath( borderPath( rect() ) ); painter->drawRect( rect() ); } else { painter->setRenderHint( QPainter::Antialiasing, true ); painter->drawPath( borderPath( rect() ) ); } } else { painter->drawRect( contentsRect() ); } } painter->restore(); } painter->save(); if ( !d_data->styleSheet.borderPath.isEmpty() ) { painter->setClipPath( d_data->styleSheet.borderPath, Qt::IntersectClip ); } else { if ( d_data->borderRadius > 0.0 ) painter->setClipPath( borderPath( rect() ), Qt::IntersectClip ); else painter->setClipRect( contentsRect(), Qt::IntersectClip ); } plot()->drawCanvas( painter ); painter->restore(); if ( withBackground && hackStyledBackground ) { // Now paint the border on top QStyleOptionFrame opt; opt.initFrom(this); style()->drawPrimitive( QStyle::PE_Frame, &opt, painter, this); } } /*! Draw the border of the plot canvas \param painter Painter \sa setBorderRadius(), QFrame::drawFrame() */ void QwtPlotCanvas::drawBorder( QPainter *painter ) { if ( d_data->borderRadius > 0 ) { if ( frameWidth() > 0 ) { QwtPainter::drawRoundedFrame( painter, QRectF( rect() ), d_data->borderRadius, d_data->borderRadius, palette(), frameWidth(), frameStyle() ); } } else { drawFrame( painter ); } } /*! Resize event \param event Resize event */ void QwtPlotCanvas::resizeEvent( QResizeEvent *event ) { QFrame::resizeEvent( event ); updateStyleSheetInfo(); } /*! Draw the focus indication \param painter Painter */ void QwtPlotCanvas::drawFocusIndicator( QPainter *painter ) { const int margin = 1; QRect focusRect = contentsRect(); focusRect.setRect( focusRect.x() + margin, focusRect.y() + margin, focusRect.width() - 2 * margin, focusRect.height() - 2 * margin ); QwtPainter::drawFocusRect( painter, this, focusRect ); } /*! Invalidate the paint cache and repaint the canvas \sa invalidatePaintCache() */ void QwtPlotCanvas::replot() { invalidateBackingStore(); if ( testPaintAttribute( QwtPlotCanvas::ImmediatePaint ) ) repaint( contentsRect() ); else update( contentsRect() ); } //! Update the cached informations about the current style sheet void QwtPlotCanvas::updateStyleSheetInfo() { if ( !testAttribute(Qt::WA_StyledBackground ) ) return; QwtStyleSheetRecorder recorder( size() ); QPainter painter( &recorder ); QStyleOption opt; opt.initFrom(this); style()->drawPrimitive( QStyle::PE_Widget, &opt, &painter, this); painter.end(); d_data->styleSheet.hasBorder = !recorder.border.rectList.isEmpty(); d_data->styleSheet.cornerRects = recorder.clipRects; if ( recorder.background.path.isEmpty() ) { if ( !recorder.border.rectList.isEmpty() ) { d_data->styleSheet.borderPath = qwtCombinePathList( rect(), recorder.border.pathList ); } } else { d_data->styleSheet.borderPath = recorder.background.path; d_data->styleSheet.background.brush = recorder.background.brush; d_data->styleSheet.background.origin = recorder.background.origin; } } /*! Calculate the painter path for a styled or rounded border When the canvas has no styled background or rounded borders the painter path is empty. \param rect Bounding rectangle of the canvas \return Painter path, that can be used for clipping */ QPainterPath QwtPlotCanvas::borderPath( const QRect &rect ) const { if ( testAttribute(Qt::WA_StyledBackground ) ) { QwtStyleSheetRecorder recorder( rect.size() ); QPainter painter( &recorder ); QStyleOption opt; opt.initFrom(this); opt.rect = rect; style()->drawPrimitive( QStyle::PE_Widget, &opt, &painter, this); painter.end(); if ( !recorder.background.path.isEmpty() ) return recorder.background.path; if ( !recorder.border.rectList.isEmpty() ) return qwtCombinePathList( rect, recorder.border.pathList ); } else if ( d_data->borderRadius > 0.0 ) { double fw2 = frameWidth() * 0.5; QRectF r = QRectF(rect).adjusted( fw2, fw2, -fw2, -fw2 ); QPainterPath path; path.addRoundedRect( r, d_data->borderRadius, d_data->borderRadius ); return path; } return QPainterPath(); } /*! Calculate a mask, that can be used to clip away the border frame \param size Size including the frame */ QBitmap QwtPlotCanvas::borderMask( const QSize &size ) const { const QRect r( 0, 0, size.width(), size.height() ); const QPainterPath path = borderPath( r ); if ( path.isEmpty() ) return QBitmap(); QImage image( size, QImage::Format_ARGB32_Premultiplied ); image.fill( Qt::color0 ); QPainter painter( &image ); painter.setClipPath( path ); painter.fillRect( r, Qt::color1 ); // now erase the frame painter.setCompositionMode( QPainter::CompositionMode_DestinationOut ); if ( testAttribute(Qt::WA_StyledBackground ) ) { QStyleOptionFrame opt; opt.initFrom(this); opt.rect = r; style()->drawPrimitive( QStyle::PE_Frame, &opt, &painter, this ); } else { if ( d_data->borderRadius > 0 && frameWidth() > 0 ) { painter.setPen( QPen( Qt::color1, frameWidth() ) ); painter.setBrush( Qt::NoBrush ); painter.setRenderHint( QPainter::Antialiasing, true ); painter.drawPath( path ); } } painter.end(); const QImage mask = image.createMaskFromColor( QColor( Qt::color1 ).rgb(), Qt::MaskOutColor ); return QBitmap::fromImage( mask ); } workbench-1.1.1/src/Qwt/qwt_plot_canvas.h000066400000000000000000000115661255417355300204360ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #ifndef QWT_PLOT_CANVAS_H #define QWT_PLOT_CANVAS_H #include "qwt_global.h" #include #include #include #include class QwtPlot; class QPixmap; /*! \brief Canvas of a QwtPlot. \sa QwtPlot */ class QWT_EXPORT QwtPlotCanvas : public QFrame { Q_OBJECT public: /*! \brief Paint attributes The default setting enables BackingStore and Opaque. \sa setPaintAttribute(), testPaintAttribute() */ enum PaintAttribute { /*! \brief Paint double buffered reusing the content of the pixmap buffer when possible. Using a backing store might improve the performance significantly, when workin with widget overlays ( like rubberbands ). Disabling the cache might improve the performance for incremental paints (using QwtPlotDirectPainter ). \sa backingStore(), invalidateBackingStore() */ BackingStore = 1, /*! \brief Try to fill the complete contents rectangle of the plot canvas When using styled backgrounds Qt assumes, that the canvas doesn't fill its area completely ( f.e because of rounded borders ) and fills the area below the canvas. When this is done with gradients it might result in a serious performance bottleneck - depending on the size. When the Opaque attribute is enabled the canvas tries to identify the gaps with some heuristics and to fill those only. \warning Will not work for semitransparent backgrounds */ Opaque = 2, /*! \brief Try to improve painting of styled backgrounds QwtPlotCanvas supports the box model attributes for customizing the layout with style sheets. Unfortunately the design of Qt style sheets has no concept how to handle backgrounds with rounded corners - beside of padding. When HackStyledBackground is enabled the plot canvas tries to seperate the background from the background border by reverse engeneering to paint the background before and the border after the plot items. In this order the border gets prefectly antialiased and you can avoid some pixel artifacts in the corners. */ HackStyledBackground = 4, /*! When ImmediatePaint is set replot() calls repaint() instead of update(). \sa replot(), QWidget::repaint(), QWidget::update() */ ImmediatePaint = 8 }; //! Paint attributes typedef QFlags PaintAttributes; /*! \brief Focus indicator - NoFocusIndicator\n Don't paint a focus indicator - CanvasFocusIndicator\n The focus is related to the complete canvas. Paint the focus indicator using paintFocus() - ItemFocusIndicator\n The focus is related to an item (curve, point, ...) on the canvas. It is up to the application to display a focus indication using f.e. highlighting. \sa setFocusIndicator(), focusIndicator(), paintFocus() */ enum FocusIndicator { NoFocusIndicator, CanvasFocusIndicator, ItemFocusIndicator }; explicit QwtPlotCanvas( QwtPlot * ); virtual ~QwtPlotCanvas(); QwtPlot *plot(); const QwtPlot *plot() const; void setFocusIndicator( FocusIndicator ); FocusIndicator focusIndicator() const; void setBorderRadius( double ); double borderRadius() const; QPainterPath borderPath( const QRect &rect ) const; QBitmap borderMask( const QSize & ) const; void setPaintAttribute( PaintAttribute, bool on = true ); bool testPaintAttribute( PaintAttribute ) const; const QPixmap *backingStore() const; void invalidateBackingStore(); void replot(); virtual bool event( QEvent * ); protected: virtual void paintEvent( QPaintEvent * ); virtual void resizeEvent( QResizeEvent * ); virtual void drawFocusIndicator( QPainter * ); virtual void drawBorder( QPainter * ); void updateStyleSheetInfo(); private: void drawCanvas( QPainter *, bool withBackground ); class PrivateData; PrivateData *d_data; }; Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotCanvas::PaintAttributes ) #endif workbench-1.1.1/src/Qwt/qwt_plot_curve.cpp000066400000000000000000000725621255417355300206450ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #include "qwt_plot_curve.h" #include "qwt_math.h" #include "qwt_clipper.h" #include "qwt_painter.h" #include "qwt_legend.h" #include "qwt_legend_item.h" #include "qwt_scale_map.h" #include "qwt_plot.h" #include "qwt_plot_canvas.h" #include "qwt_curve_fitter.h" #include "qwt_symbol.h" #include #include #include #include static int verifyRange( int size, int &i1, int &i2 ) { if ( size < 1 ) return 0; i1 = qBound( 0, i1, size - 1 ); i2 = qBound( 0, i2, size - 1 ); if ( i1 > i2 ) qSwap( i1, i2 ); return ( i2 - i1 + 1 ); } class QwtPlotCurve::PrivateData { public: PrivateData(): style( QwtPlotCurve::Lines ), baseline( 0.0 ), symbol( NULL ), attributes( 0 ), paintAttributes( QwtPlotCurve::ClipPolygons ), legendAttributes( 0 ) { pen = QPen( Qt::black ); curveFitter = new QwtSplineCurveFitter; } ~PrivateData() { delete symbol; delete curveFitter; } QwtPlotCurve::CurveStyle style; double baseline; const QwtSymbol *symbol; QwtCurveFitter *curveFitter; QPen pen; QBrush brush; QwtPlotCurve::CurveAttributes attributes; QwtPlotCurve::PaintAttributes paintAttributes; QwtPlotCurve::LegendAttributes legendAttributes; }; /*! Constructor \param title Title of the curve */ QwtPlotCurve::QwtPlotCurve( const QwtText &title ): QwtPlotSeriesItem( title ) { init(); } /*! Constructor \param title Title of the curve */ QwtPlotCurve::QwtPlotCurve( const QString &title ): QwtPlotSeriesItem( QwtText( title ) ) { init(); } //! Destructor QwtPlotCurve::~QwtPlotCurve() { delete d_data; } //! Initialize internal members void QwtPlotCurve::init() { setItemAttribute( QwtPlotItem::Legend ); setItemAttribute( QwtPlotItem::AutoScale ); d_data = new PrivateData; d_series = new QwtPointSeriesData(); setZ( 20.0 ); } //! \return QwtPlotItem::Rtti_PlotCurve int QwtPlotCurve::rtti() const { return QwtPlotItem::Rtti_PlotCurve; } /*! Specify an attribute how to draw the curve \param attribute Paint attribute \param on On/Off \sa testPaintAttribute() */ void QwtPlotCurve::setPaintAttribute( PaintAttribute attribute, bool on ) { if ( on ) d_data->paintAttributes |= attribute; else d_data->paintAttributes &= ~attribute; } /*! \brief Return the current paint attributes \sa setPaintAttribute() */ bool QwtPlotCurve::testPaintAttribute( PaintAttribute attribute ) const { return ( d_data->paintAttributes & attribute ); } /*! Specify an attribute how to draw the legend identifier \param attribute Attribute \param on On/Off /sa testLegendAttribute() */ void QwtPlotCurve::setLegendAttribute( LegendAttribute attribute, bool on ) { if ( on ) d_data->legendAttributes |= attribute; else d_data->legendAttributes &= ~attribute; } /*! \brief Return the current paint attributes \sa setLegendAttribute() */ bool QwtPlotCurve::testLegendAttribute( LegendAttribute attribute ) const { return ( d_data->legendAttributes & attribute ); } /*! Set the curve's drawing style \param style Curve style \sa style() */ void QwtPlotCurve::setStyle( CurveStyle style ) { if ( style != d_data->style ) { d_data->style = style; itemChanged(); } } /*! Return the current style \sa setStyle() */ QwtPlotCurve::CurveStyle QwtPlotCurve::style() const { return d_data->style; } /*! Assign a symbol \param symbol Symbol \sa symbol() */ void QwtPlotCurve::setSymbol( const QwtSymbol *symbol ) { if ( symbol != d_data->symbol ) { delete d_data->symbol; d_data->symbol = symbol; itemChanged(); } } /*! \return Current symbol or NULL, when no symbol has been assigned \sa setSymbol() */ const QwtSymbol *QwtPlotCurve::symbol() const { return d_data->symbol; } /*! Assign a pen \param pen New pen \sa pen(), brush() */ void QwtPlotCurve::setPen( const QPen &pen ) { if ( pen != d_data->pen ) { d_data->pen = pen; itemChanged(); } } /*! \return Pen used to draw the lines \sa setPen(), brush() */ const QPen& QwtPlotCurve::pen() const { return d_data->pen; } /*! \brief Assign a brush. In case of brush.style() != QBrush::NoBrush and style() != QwtPlotCurve::Sticks the area between the curve and the baseline will be filled. In case !brush.color().isValid() the area will be filled by pen.color(). The fill algorithm simply connects the first and the last curve point to the baseline. So the curve data has to be sorted (ascending or descending). \param brush New brush \sa brush(), setBaseline(), baseline() */ void QwtPlotCurve::setBrush( const QBrush &brush ) { if ( brush != d_data->brush ) { d_data->brush = brush; itemChanged(); } } /*! \return Brush used to fill the area between lines and the baseline \sa setBrush(), setBaseline(), baseline() */ const QBrush& QwtPlotCurve::brush() const { return d_data->brush; } /*! Draw an interval of the curve \param painter Painter \param xMap Maps x-values into pixel coordinates. \param yMap Maps y-values into pixel coordinates. \param canvasRect Contents rect of the canvas \param from Index of the first point to be painted \param to Index of the last point to be painted. If to < 0 the curve will be painted to its last point. \sa drawCurve(), drawSymbols(), */ void QwtPlotCurve::drawSeries( QPainter *painter, const QwtScaleMap &xMap, const QwtScaleMap &yMap, const QRectF &canvasRect, int from, int to ) const { if ( !painter || dataSize() <= 0 ) return; if ( to < 0 ) to = dataSize() - 1; if ( verifyRange( dataSize(), from, to ) > 0 ) { painter->save(); painter->setPen( d_data->pen ); /* Qt 4.0.0 is slow when drawing lines, but it's even slower when the painter has a brush. So we don't set the brush before we really need it. */ drawCurve( painter, d_data->style, xMap, yMap, canvasRect, from, to ); painter->restore(); if ( d_data->symbol && ( d_data->symbol->style() != QwtSymbol::NoSymbol ) ) { painter->save(); drawSymbols( painter, *d_data->symbol, xMap, yMap, canvasRect, from, to ); painter->restore(); } } } /*! \brief Draw the line part (without symbols) of a curve interval. \param painter Painter \param style curve style, see QwtPlotCurve::CurveStyle \param xMap x map \param yMap y map \param canvasRect Contents rect of the canvas \param from index of the first point to be painted \param to index of the last point to be painted \sa draw(), drawDots(), drawLines(), drawSteps(), drawSticks() */ void QwtPlotCurve::drawCurve( QPainter *painter, int style, const QwtScaleMap &xMap, const QwtScaleMap &yMap, const QRectF &canvasRect, int from, int to ) const { switch ( style ) { case Lines: if ( testCurveAttribute( Fitted ) ) { // we always need the complete // curve for fitting from = 0; to = dataSize() - 1; } drawLines( painter, xMap, yMap, canvasRect, from, to ); break; case Sticks: drawSticks( painter, xMap, yMap, canvasRect, from, to ); break; case Steps: drawSteps( painter, xMap, yMap, canvasRect, from, to ); break; case Dots: drawDots( painter, xMap, yMap, canvasRect, from, to ); break; case NoCurve: default: break; } } /*! \brief Draw lines If the CurveAttribute Fitted is enabled a QwtCurveFitter tries to interpolate/smooth the curve, before it is painted. \param painter Painter \param xMap x map \param yMap y map \param canvasRect Contents rect of the canvas \param from index of the first point to be painted \param to index of the last point to be painted \sa setCurveAttribute(), setCurveFitter(), draw(), drawLines(), drawDots(), drawSteps(), drawSticks() */ void QwtPlotCurve::drawLines( QPainter *painter, const QwtScaleMap &xMap, const QwtScaleMap &yMap, const QRectF &canvasRect, int from, int to ) const { int size = to - from + 1; if ( size <= 0 ) return; const bool doAlign = QwtPainter::roundingAlignment( painter ); QPolygonF polyline( size ); QPointF *points = polyline.data(); for ( int i = from; i <= to; i++ ) { const QPointF sample = d_series->sample( i ); double x = xMap.transform( sample.x() ); double y = yMap.transform( sample.y() ); if ( doAlign ) { x = qRound( x ); y = qRound( y ); } points[i - from].rx() = x; points[i - from].ry() = y; } if ( ( d_data->attributes & Fitted ) && d_data->curveFitter ) polyline = d_data->curveFitter->fitCurve( polyline ); if ( d_data->paintAttributes & ClipPolygons ) { qreal pw = qMax( qreal( 1.0 ), painter->pen().widthF()); const QPolygonF clipped = QwtClipper::clipPolygonF( canvasRect.adjusted(-pw, -pw, pw, pw), polyline, false ); QwtPainter::drawPolyline( painter, clipped ); } else { QwtPainter::drawPolyline( painter, polyline ); } if ( d_data->brush.style() != Qt::NoBrush ) fillCurve( painter, xMap, yMap, canvasRect, polyline ); } /*! Draw sticks \param painter Painter \param xMap x map \param yMap y map \param canvasRect Contents rect of the canvas \param from index of the first point to be painted \param to index of the last point to be painted \sa draw(), drawCurve(), drawDots(), drawLines(), drawSteps() */ void QwtPlotCurve::drawSticks( QPainter *painter, const QwtScaleMap &xMap, const QwtScaleMap &yMap, const QRectF &, int from, int to ) const { painter->save(); painter->setRenderHint( QPainter::Antialiasing, false ); const bool doAlign = QwtPainter::roundingAlignment( painter ); double x0 = xMap.transform( d_data->baseline ); double y0 = yMap.transform( d_data->baseline ); if ( doAlign ) { x0 = qRound( x0 ); y0 = qRound( y0 ); } const Qt::Orientation o = orientation(); for ( int i = from; i <= to; i++ ) { const QPointF sample = d_series->sample( i ); double xi = xMap.transform( sample.x() ); double yi = yMap.transform( sample.y() ); if ( doAlign ) { xi = qRound( xi ); yi = qRound( yi ); } if ( o == Qt::Horizontal ) QwtPainter::drawLine( painter, x0, yi, xi, yi ); else QwtPainter::drawLine( painter, xi, y0, xi, yi ); } painter->restore(); } /*! Draw dots \param painter Painter \param xMap x map \param yMap y map \param canvasRect Contents rect of the canvas \param from index of the first point to be painted \param to index of the last point to be painted \sa draw(), drawCurve(), drawSticks(), drawLines(), drawSteps() */ void QwtPlotCurve::drawDots( QPainter *painter, const QwtScaleMap &xMap, const QwtScaleMap &yMap, const QRectF &canvasRect, int from, int to ) const { const bool doFill = d_data->brush.style() != Qt::NoBrush; const bool doAlign = QwtPainter::roundingAlignment( painter ); QPolygonF polyline; if ( doFill ) polyline.resize( to - from + 1 ); QPointF *points = polyline.data(); for ( int i = from; i <= to; i++ ) { const QPointF sample = d_series->sample( i ); double xi = xMap.transform( sample.x() ); double yi = yMap.transform( sample.y() ); if ( doAlign ) { xi = qRound( xi ); yi = qRound( yi ); } QwtPainter::drawPoint( painter, QPointF( xi, yi ) ); if ( doFill ) { points[i - from].rx() = xi; points[i - from].ry() = yi; } } if ( doFill ) fillCurve( painter, xMap, yMap, canvasRect, polyline ); } /*! Draw step function The direction of the steps depends on Inverted attribute. \param painter Painter \param xMap x map \param yMap y map \param canvasRect Contents rect of the canvas \param from index of the first point to be painted \param to index of the last point to be painted \sa CurveAttribute, setCurveAttribute(), draw(), drawCurve(), drawDots(), drawLines(), drawSticks() */ void QwtPlotCurve::drawSteps( QPainter *painter, const QwtScaleMap &xMap, const QwtScaleMap &yMap, const QRectF &canvasRect, int from, int to ) const { const bool doAlign = QwtPainter::roundingAlignment( painter ); QPolygonF polygon( 2 * ( to - from ) + 1 ); QPointF *points = polygon.data(); bool inverted = orientation() == Qt::Vertical; if ( d_data->attributes & Inverted ) inverted = !inverted; int i, ip; for ( i = from, ip = 0; i <= to; i++, ip += 2 ) { const QPointF sample = d_series->sample( i ); double xi = xMap.transform( sample.x() ); double yi = yMap.transform( sample.y() ); if ( doAlign ) { xi = qRound( xi ); yi = qRound( yi ); } if ( ip > 0 ) { const QPointF &p0 = points[ip - 2]; QPointF &p = points[ip - 1]; if ( inverted ) { p.rx() = p0.x(); p.ry() = yi; } else { p.rx() = xi; p.ry() = p0.y(); } } points[ip].rx() = xi; points[ip].ry() = yi; } if ( d_data->paintAttributes & ClipPolygons ) { const QPolygonF clipped = QwtClipper::clipPolygonF( canvasRect, polygon, false ); QwtPainter::drawPolyline( painter, clipped ); } else { QwtPainter::drawPolyline( painter, polygon ); } if ( d_data->brush.style() != Qt::NoBrush ) fillCurve( painter, xMap, yMap, canvasRect, polygon ); } /*! Specify an attribute for drawing the curve \param attribute Curve attribute \param on On/Off /sa testCurveAttribute(), setCurveFitter() */ void QwtPlotCurve::setCurveAttribute( CurveAttribute attribute, bool on ) { if ( bool( d_data->attributes & attribute ) == on ) return; if ( on ) d_data->attributes |= attribute; else d_data->attributes &= ~attribute; itemChanged(); } /*! \return true, if attribute is enabled \sa setCurveAttribute() */ bool QwtPlotCurve::testCurveAttribute( CurveAttribute attribute ) const { return d_data->attributes & attribute; } /*! Assign a curve fitter The curve fitter "smooths" the curve points, when the Fitted CurveAttribute is set. setCurveFitter(NULL) also disables curve fitting. The curve fitter operates on the translated points ( = widget coordinates) to be functional for logarithmic scales. Obviously this is less performant for fitting algorithms, that reduce the number of points. For situations, where curve fitting is used to improve the performance of painting huge series of points it might be better to execute the fitter on the curve points once and to cache the result in the QwtSeriesData object. \param curveFitter() Curve fitter \sa Fitted */ void QwtPlotCurve::setCurveFitter( QwtCurveFitter *curveFitter ) { delete d_data->curveFitter; d_data->curveFitter = curveFitter; itemChanged(); } /*! Get the curve fitter. If curve fitting is disabled NULL is returned. \return Curve fitter \sa setCurveFitter(), Fitted */ QwtCurveFitter *QwtPlotCurve::curveFitter() const { return d_data->curveFitter; } /*! Fill the area between the curve and the baseline with the curve brush \param painter Painter \param xMap x map \param yMap y map \param canvasRect Contents rect of the canvas \param polygon Polygon - will be modified ! \sa setBrush(), setBaseline(), setStyle() */ void QwtPlotCurve::fillCurve( QPainter *painter, const QwtScaleMap &xMap, const QwtScaleMap &yMap, const QRectF &canvasRect, QPolygonF &polygon ) const { if ( d_data->brush.style() == Qt::NoBrush ) return; closePolyline( painter, xMap, yMap, polygon ); if ( polygon.count() <= 2 ) // a line can't be filled return; QBrush brush = d_data->brush; if ( !brush.color().isValid() ) brush.setColor( d_data->pen.color() ); if ( d_data->paintAttributes & ClipPolygons ) polygon = QwtClipper::clipPolygonF( canvasRect, polygon, true ); painter->save(); painter->setPen( Qt::NoPen ); painter->setBrush( brush ); QwtPainter::drawPolygon( painter, polygon ); painter->restore(); } /*! \brief Complete a polygon to be a closed polygon including the area between the original polygon and the baseline. \param painter Painter \param xMap X map \param yMap Y map \param polygon Polygon to be completed */ void QwtPlotCurve::closePolyline( QPainter *painter, const QwtScaleMap &xMap, const QwtScaleMap &yMap, QPolygonF &polygon ) const { if ( polygon.size() < 2 ) return; const bool doAlign = QwtPainter::roundingAlignment( painter ); double baseline = d_data->baseline; if ( orientation() == Qt::Vertical ) { if ( yMap.transformation()->type() == QwtScaleTransformation::Log10 ) { if ( baseline < QwtScaleMap::LogMin ) baseline = QwtScaleMap::LogMin; } double refY = yMap.transform( baseline ); if ( doAlign ) refY = qRound( refY ); polygon += QPointF( polygon.last().x(), refY ); polygon += QPointF( polygon.first().x(), refY ); } else { if ( xMap.transformation()->type() == QwtScaleTransformation::Log10 ) { if ( baseline < QwtScaleMap::LogMin ) baseline = QwtScaleMap::LogMin; } double refX = xMap.transform( baseline ); if ( doAlign ) refX = qRound( refX ); polygon += QPointF( refX, polygon.last().y() ); polygon += QPointF( refX, polygon.first().y() ); } } /*! Draw symbols \param painter Painter \param symbol Curve symbol \param xMap x map \param yMap y map \param canvasRect Contents rect of the canvas \param from Index of the first point to be painted \param to Index of the last point to be painted \sa setSymbol(), drawSeries(), drawCurve() */ void QwtPlotCurve::drawSymbols( QPainter *painter, const QwtSymbol &symbol, const QwtScaleMap &xMap, const QwtScaleMap &yMap, const QRectF &canvasRect, int from, int to ) const { const bool doAlign = QwtPainter::roundingAlignment( painter ); bool usePixmap = testPaintAttribute( CacheSymbols ); if ( usePixmap && !doAlign ) { // Don't use the pixmap, when the paint device // could generate scalable vectors usePixmap = false; } if ( usePixmap ) { QPixmap pm( symbol.boundingSize() ); pm.fill( Qt::transparent ); const double pw2 = 0.5 * pm.width(); const double ph2 = 0.5 * pm.height(); QPainter p( &pm ); p.setRenderHints( painter->renderHints() ); symbol.drawSymbol( &p, QPointF( pw2, ph2 ) ); p.end(); for ( int i = from; i <= to; i++ ) { const QPointF sample = d_series->sample( i ); double xi = xMap.transform( sample.x() ); double yi = yMap.transform( sample.y() ); if ( doAlign ) { xi = qRound( xi ); yi = qRound( yi ); } if ( canvasRect.contains( xi, yi ) ) { const int left = qCeil( xi ) - pw2; const int top = qCeil( yi ) - ph2; painter->drawPixmap( left, top, pm ); } } } else { const int chunkSize = 500; for ( int i = from; i <= to; i += chunkSize ) { const int n = qMin( chunkSize, to - i + 1 ); QPolygonF points; for ( int j = 0; j < n; j++ ) { const QPointF sample = d_series->sample( i + j ); const double xi = xMap.transform( sample.x() ); const double yi = yMap.transform( sample.y() ); if ( canvasRect.contains( xi, yi ) ) points += QPointF( xi, yi ); } if ( points.size() > 0 ) symbol.drawSymbols( painter, points ); } } } /*! \brief Set the value of the baseline The baseline is needed for filling the curve with a brush or the Sticks drawing style. The interpretation of the baseline depends on the CurveType. With QwtPlotCurve::Yfx, the baseline is interpreted as a horizontal line at y = baseline(), with QwtPlotCurve::Yfy, it is interpreted as a vertical line at x = baseline(). The default value is 0.0. \param value Value of the baseline \sa baseline(), setBrush(), setStyle(), setStyle() */ void QwtPlotCurve::setBaseline( double value ) { if ( d_data->baseline != value ) { d_data->baseline = value; itemChanged(); } } /*! \return Value of the baseline \sa setBaseline() */ double QwtPlotCurve::baseline() const { return d_data->baseline; } /*! Find the closest curve point for a specific position \param pos Position, where to look for the closest curve point \param dist If dist != NULL, closestPoint() returns the distance between the position and the clostest curve point \return Index of the closest curve point, or -1 if none can be found ( f.e when the curve has no points ) \note closestPoint() implements a dumb algorithm, that iterates over all points */ int QwtPlotCurve::closestPoint( const QPoint &pos, double *dist ) const { if ( plot() == NULL || dataSize() <= 0 ) return -1; const QwtScaleMap xMap = plot()->canvasMap( xAxis() ); const QwtScaleMap yMap = plot()->canvasMap( yAxis() ); int index = -1; double dmin = 1.0e10; for ( uint i = 0; i < dataSize(); i++ ) { const QPointF sample = d_series->sample( i ); const double cx = xMap.transform( sample.x() ) - pos.x(); const double cy = yMap.transform( sample.y() ) - pos.y(); const double f = qwtSqr( cx ) + qwtSqr( cy ); if ( f < dmin ) { index = i; dmin = f; } } if ( dist ) *dist = qSqrt( dmin ); return index; } /*! \brief Update the widget that represents the item on the legend \param legend Legend \sa drawLegendIdentifier(), legendItem(), QwtPlotItem::Legend */ void QwtPlotCurve::updateLegend( QwtLegend *legend ) const { if ( legend && testItemAttribute( QwtPlotItem::Legend ) && ( d_data->legendAttributes & QwtPlotCurve::LegendShowSymbol ) && d_data->symbol && d_data->symbol->style() != QwtSymbol::NoSymbol ) { QWidget *lgdItem = legend->find( this ); if ( lgdItem == NULL ) { lgdItem = legendItem(); if ( lgdItem ) legend->insert( this, lgdItem ); } QwtLegendItem *l = qobject_cast( lgdItem ); if ( l ) { QSize sz = d_data->symbol->boundingSize(); sz += QSize( 2, 2 ); // margin if ( d_data->legendAttributes & QwtPlotCurve::LegendShowLine ) { // Avoid, that the line is completely covered by the symbol int w = qCeil( 1.5 * sz.width() ); if ( w % 2 ) w++; sz.setWidth( qMax( 8, w ) ); } l->setIdentifierSize( sz ); } } QwtPlotItem::updateLegend( legend ); } /*! \brief Draw the identifier representing the curve on the legend \param painter Painter \param rect Bounding rectangle for the identifier \sa setLegendAttribute(), QwtPlotItem::Legend */ void QwtPlotCurve::drawLegendIdentifier( QPainter *painter, const QRectF &rect ) const { if ( rect.isEmpty() ) return; const int dim = qMin( rect.width(), rect.height() ); QSize size( dim, dim ); QRectF r( 0, 0, size.width(), size.height() ); r.moveCenter( rect.center() ); if ( d_data->legendAttributes == 0 ) { QBrush brush = d_data->brush; if ( brush.style() == Qt::NoBrush ) { if ( style() != QwtPlotCurve::NoCurve ) brush = QBrush( pen().color() ); else if ( d_data->symbol && ( d_data->symbol->style() != QwtSymbol::NoSymbol ) ) { brush = QBrush( d_data->symbol->pen().color() ); } } if ( brush.style() != Qt::NoBrush ) painter->fillRect( r, brush ); } if ( d_data->legendAttributes & QwtPlotCurve::LegendShowBrush ) { if ( d_data->brush.style() != Qt::NoBrush ) painter->fillRect( r, d_data->brush ); } if ( d_data->legendAttributes & QwtPlotCurve::LegendShowLine ) { if ( pen() != Qt::NoPen ) { painter->setPen( pen() ); QwtPainter::drawLine( painter, rect.left(), rect.center().y(), rect.right() - 1.0, rect.center().y() ); } } if ( d_data->legendAttributes & QwtPlotCurve::LegendShowSymbol ) { if ( d_data->symbol && ( d_data->symbol->style() != QwtSymbol::NoSymbol ) ) { QSize symbolSize = d_data->symbol->boundingSize(); symbolSize -= QSize( 2, 2 ); // scale the symbol size down if it doesn't fit into rect. double xRatio = 1.0; if ( rect.width() < symbolSize.width() ) xRatio = rect.width() / symbolSize.width(); double yRatio = 1.0; if ( rect.height() < symbolSize.height() ) yRatio = rect.height() / symbolSize.height(); const double ratio = qMin( xRatio, yRatio ); painter->save(); painter->scale( ratio, ratio ); d_data->symbol->drawSymbol( painter, rect.center() / ratio ); painter->restore(); } } } /*! Initialize data with an array of points (explicitly shared). \param samples Vector of points */ void QwtPlotCurve::setSamples( const QVector &samples ) { delete d_series; d_series = new QwtPointSeriesData( samples ); itemChanged(); } #ifndef QWT_NO_COMPAT /*! \brief Initialize the data by pointing to memory blocks which are not managed by QwtPlotCurve. setRawSamples is provided for efficiency. It is important to keep the pointers during the lifetime of the underlying QwtCPointerData class. \param xData pointer to x data \param yData pointer to y data \param size size of x and y \sa QwtCPointerData */ void QwtPlotCurve::setRawSamples( const double *xData, const double *yData, int size ) { delete d_series; d_series = new QwtCPointerData( xData, yData, size ); itemChanged(); } /*! Set data by copying x- and y-values from specified memory blocks. Contrary to setRawSamples(), this function makes a 'deep copy' of the data. \param xData pointer to x values \param yData pointer to y values \param size size of xData and yData \sa QwtPointArrayData */ void QwtPlotCurve::setSamples( const double *xData, const double *yData, int size ) { delete d_series; d_series = new QwtPointArrayData( xData, yData, size ); itemChanged(); } /*! \brief Initialize data with x- and y-arrays (explicitly shared) \param xData x data \param yData y data \sa QwtPointArrayData */ void QwtPlotCurve::setSamples( const QVector &xData, const QVector &yData ) { delete d_series; d_series = new QwtPointArrayData( xData, yData ); itemChanged(); } #endif // !QWT_NO_COMPAT workbench-1.1.1/src/Qwt/qwt_plot_curve.h000066400000000000000000000232351255417355300203030ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #ifndef QWT_PLOT_CURVE_H #define QWT_PLOT_CURVE_H #include "qwt_global.h" #include "qwt_plot_seriesitem.h" #include "qwt_series_data.h" #include "qwt_text.h" #include #include class QPainter; class QPolygonF; class QwtScaleMap; class QwtSymbol; class QwtCurveFitter; /*! \brief A plot item, that represents a series of points A curve is the representation of a series of points in the x-y plane. It supports different display styles, interpolation ( f.e. spline ) and symbols. \par Usage

a) Assign curve properties
When a curve is created, it is configured to draw black solid lines with in QwtPlotCurve::Lines style and no symbols. You can change this by calling setPen(), setStyle() and setSymbol().
b) Connect/Assign data.
QwtPlotCurve gets its points using a QwtSeriesData object offering a bridge to the real storage of the points ( like QAbstractItemModel ). There are several convenience classes derived from QwtSeriesData, that also store the points inside ( like QStandardItemModel ). QwtPlotCurve also offers a couple of variations of setSamples(), that build QwtSeriesData objects from arrays internally.
c) Attach the curve to a plot
See QwtPlotItem::attach()
\par Example: see examples/bode \sa QwtPointSeriesData, QwtSymbol, QwtScaleMap */ class QWT_EXPORT QwtPlotCurve: public QwtPlotSeriesItem { public: /*! Curve styles. \sa setStyle(), style() */ enum CurveStyle { /*! Don't draw a curve. Note: This doesn't affect the symbols. */ NoCurve = -1, /*! Connect the points with straight lines. The lines might be interpolated depending on the 'Fitted' attribute. Curve fitting can be configured using setCurveFitter(). */ Lines, /*! Draw vertical or horizontal sticks ( depending on the orientation() ) from a baseline which is defined by setBaseline(). */ Sticks, /*! Connect the points with a step function. The step function is drawn from the left to the right or vice versa, depending on the QwtPlotCurve::Inverted attribute. */ Steps, /*! Draw dots at the locations of the data points. Note: This is different from a dotted line (see setPen()), and faster as a curve in QwtPlotCurve::NoStyle style and a symbol painting a point. */ Dots, /*! Styles >= QwtPlotCurve::UserCurve are reserved for derived classes of QwtPlotCurve that overload drawCurve() with additional application specific curve types. */ UserCurve = 100 }; /*! Attribute for drawing the curve \sa setCurveAttribute(), testCurveAttribute(), curveFitter() */ enum CurveAttribute { /*! For QwtPlotCurve::Steps only. Draws a step function from the right to the left. */ Inverted = 0x01, /*! Only in combination with QwtPlotCurve::Lines A QwtCurveFitter tries to interpolate/smooth the curve, before it is painted. \note Curve fitting requires temorary memory for calculating coefficients and additional points. If painting in QwtPlotCurve::Fitted mode is slow it might be better to fit the points, before they are passed to QwtPlotCurve. */ Fitted = 0x02 }; //! Curve attributes typedef QFlags CurveAttributes; /*! Attributes how to represent the curve on the legend \sa setLegendAttribute(), testLegendAttribute(), drawLegendIdentifier() */ enum LegendAttribute { /*! QwtPlotCurve tries to find a color representing the curve and paints a rectangle with it. */ LegendNoAttribute = 0x00, /*! If the style() is not QwtPlotCurve::NoCurve a line is painted with the curve pen(). */ LegendShowLine = 0x01, /*! If the curve has a valid symbol it is painted. */ LegendShowSymbol = 0x02, /*! If the curve has a brush a rectangle filled with the curve brush() is painted. */ LegendShowBrush = 0x04 }; //! Legend attributes typedef QFlags LegendAttributes; /*! Attributes to modify the drawing algorithm. The default setting enables ClipPolygons \sa setPaintAttribute(), testPaintAttribute() */ enum PaintAttribute { /*! Clip polygons before painting them. In situations, where points are far outside the visible area (f.e when zooming deep) this might be a substantial improvement for the painting performance */ ClipPolygons = 0x01, /*! Paint the symbol to a QPixmap and paint the pixmap instead rendering the symbol for each point. The flag has no effect, when the curve is not painted to the canvas ( f.e when exporting the plot to a PDF document ). */ CacheSymbols = 0x02 }; //! Paint attributes typedef QFlags PaintAttributes; explicit QwtPlotCurve( const QString &title = QString::null ); explicit QwtPlotCurve( const QwtText &title ); virtual ~QwtPlotCurve(); virtual int rtti() const; void setPaintAttribute( PaintAttribute, bool on = true ); bool testPaintAttribute( PaintAttribute ) const; void setLegendAttribute( LegendAttribute, bool on = true ); bool testLegendAttribute( LegendAttribute ) const; #ifndef QWT_NO_COMPAT void setRawSamples( const double *xData, const double *yData, int size ); void setSamples( const double *xData, const double *yData, int size ); void setSamples( const QVector &xData, const QVector &yData ); #endif void setSamples( const QVector & ); int closestPoint( const QPoint &pos, double *dist = NULL ) const; double minXValue() const; double maxXValue() const; double minYValue() const; double maxYValue() const; void setCurveAttribute( CurveAttribute, bool on = true ); bool testCurveAttribute( CurveAttribute ) const; void setPen( const QPen & ); const QPen &pen() const; void setBrush( const QBrush & ); const QBrush &brush() const; void setBaseline( double ref ); double baseline() const; void setStyle( CurveStyle style ); CurveStyle style() const; void setSymbol( const QwtSymbol *s ); const QwtSymbol *symbol() const; void setCurveFitter( QwtCurveFitter * ); QwtCurveFitter *curveFitter() const; virtual void drawSeries( QPainter *, const QwtScaleMap &xMap, const QwtScaleMap &yMap, const QRectF &canvasRect, int from, int to ) const; virtual void updateLegend( QwtLegend * ) const; virtual void drawLegendIdentifier( QPainter *, const QRectF & ) const; protected: void init(); virtual void drawCurve( QPainter *p, int style, const QwtScaleMap &xMap, const QwtScaleMap &yMap, const QRectF &canvasRect, int from, int to ) const; virtual void drawSymbols( QPainter *p, const QwtSymbol &, const QwtScaleMap &xMap, const QwtScaleMap &yMap, const QRectF &canvasRect, int from, int to ) const; void drawLines( QPainter *p, const QwtScaleMap &xMap, const QwtScaleMap &yMap, const QRectF &canvasRect, int from, int to ) const; void drawSticks( QPainter *p, const QwtScaleMap &xMap, const QwtScaleMap &yMap, const QRectF &canvasRect, int from, int to ) const; void drawDots( QPainter *p, const QwtScaleMap &xMap, const QwtScaleMap &yMap, const QRectF &canvasRect, int from, int to ) const; void drawSteps( QPainter *p, const QwtScaleMap &xMap, const QwtScaleMap &yMap, const QRectF &canvasRect, int from, int to ) const; virtual void fillCurve( QPainter *, const QwtScaleMap &, const QwtScaleMap &, const QRectF &canvasRect, QPolygonF & ) const; void closePolyline( QPainter *, const QwtScaleMap &, const QwtScaleMap &, QPolygonF & ) const; private: class PrivateData; PrivateData *d_data; }; //! boundingRect().left() inline double QwtPlotCurve::minXValue() const { return boundingRect().left(); } //! boundingRect().right() inline double QwtPlotCurve::maxXValue() const { return boundingRect().right(); } //! boundingRect().top() inline double QwtPlotCurve::minYValue() const { return boundingRect().top(); } //! boundingRect().bottom() inline double QwtPlotCurve::maxYValue() const { return boundingRect().bottom(); } Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotCurve::PaintAttributes ) Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotCurve::LegendAttributes ) Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotCurve::CurveAttributes ) #endif workbench-1.1.1/src/Qwt/qwt_plot_dict.cpp000066400000000000000000000112131255417355300204260ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #include "qwt_plot_dict.h" class QwtPlotDict::PrivateData { public: class ItemList: public QList { public: void insertItem( QwtPlotItem *item ) { if ( item == NULL ) return; QList::iterator it = qUpperBound( begin(), end(), item, LessZThan() ); insert( it, item ); } void removeItem( QwtPlotItem *item ) { if ( item == NULL ) return; QList::iterator it = qLowerBound( begin(), end(), item, LessZThan() ); for ( ; it != end(); ++it ) { if ( item == *it ) { erase( it ); break; } } } private: class LessZThan { public: inline bool operator()( const QwtPlotItem *item1, const QwtPlotItem *item2 ) const { return item1->z() < item2->z(); } }; }; ItemList itemList; bool autoDelete; }; /*! Constructor Auto deletion is enabled. \sa setAutoDelete(), attachItem() */ QwtPlotDict::QwtPlotDict() { d_data = new QwtPlotDict::PrivateData; d_data->autoDelete = true; } /*! Destructor If autoDelete is on, all attached items will be deleted \sa setAutoDelete(), autoDelete(), attachItem() */ QwtPlotDict::~QwtPlotDict() { detachItems( QwtPlotItem::Rtti_PlotItem, d_data->autoDelete ); delete d_data; } /*! En/Disable Auto deletion If Auto deletion is on all attached plot items will be deleted in the destructor of QwtPlotDict. The default value is on. \sa autoDelete(), attachItem() */ void QwtPlotDict::setAutoDelete( bool autoDelete ) { d_data->autoDelete = autoDelete; } /*! \return true if auto deletion is enabled \sa setAutoDelete(), attachItem() */ bool QwtPlotDict::autoDelete() const { return d_data->autoDelete; } /*! Attach/Detach a plot item Attached items will be deleted in the destructor, if auto deletion is enabled (default). Manually detached items are not deleted. \param item Plot item to attach/detach \ on If true attach, else detach the item \sa setAutoDelete(), ~QwtPlotDict() */ void QwtPlotDict::attachItem( QwtPlotItem *item, bool on ) { if ( on ) d_data->itemList.insertItem( item ); else d_data->itemList.removeItem( item ); } /*! Detach items from the dictionary \param rtti In case of QwtPlotItem::Rtti_PlotItem detach all items otherwise only those items of the type rtti. \param autoDelete If true, delete all detached items */ void QwtPlotDict::detachItems( int rtti, bool autoDelete ) { PrivateData::ItemList list = d_data->itemList; QwtPlotItemIterator it = list.begin(); while ( it != list.end() ) { QwtPlotItem *item = *it; ++it; // increment before removing item from the list if ( rtti == QwtPlotItem::Rtti_PlotItem || item->rtti() == rtti ) { item->attach( NULL ); if ( autoDelete ) delete item; } } } /*! \brief A QwtPlotItemList of all attached plot items. Use caution when iterating these lists, as removing/detaching an item will invalidate the iterator. Instead you can place pointers to objects to be removed in a removal list, and traverse that list later. \return List of all attached plot items. */ const QwtPlotItemList &QwtPlotDict::itemList() const { return d_data->itemList; } /*! \return List of all attached plot items of a specific type. \sa QwtPlotItem::rtti() */ QwtPlotItemList QwtPlotDict::itemList( int rtti ) const { if ( rtti == QwtPlotItem::Rtti_PlotItem ) return d_data->itemList; QwtPlotItemList items; PrivateData::ItemList list = d_data->itemList; for ( QwtPlotItemIterator it = list.begin(); it != list.end(); ++it ) { QwtPlotItem *item = *it; if ( item->rtti() == rtti ) items += item; } return items; } workbench-1.1.1/src/Qwt/qwt_plot_dict.h000066400000000000000000000030341255417355300200750ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ /*! \file !*/ #ifndef QWT_PLOT_DICT #define QWT_PLOT_DICT #include "qwt_global.h" #include "qwt_plot_item.h" #include /// \var typedef QList< QwtPlotItem *> QwtPlotItemList /// \brief See QT 4.x assistant documentation for QList typedef QList QwtPlotItemList; typedef QList::ConstIterator QwtPlotItemIterator; /*! \brief A dictionary for plot items QwtPlotDict organizes plot items in increasing z-order. If autoDelete() is enabled, all attached items will be deleted in the destructor of the dictionary. \sa QwtPlotItem::attach(), QwtPlotItem::detach(), QwtPlotItem::z() */ class QWT_EXPORT QwtPlotDict { public: explicit QwtPlotDict(); virtual ~QwtPlotDict(); void setAutoDelete( bool ); bool autoDelete() const; const QwtPlotItemList& itemList() const; QwtPlotItemList itemList( int rtti ) const; void detachItems( int rtti = QwtPlotItem::Rtti_PlotItem, bool autoDelete = true ); private: friend class QwtPlotItem; void attachItem( QwtPlotItem *, bool ); class PrivateData; PrivateData *d_data; }; #endif workbench-1.1.1/src/Qwt/qwt_plot_directpainter.cpp000066400000000000000000000206411255417355300223450ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #include "qwt_plot_directpainter.h" #include "qwt_scale_map.h" #include "qwt_plot.h" #include "qwt_plot_canvas.h" #include "qwt_plot_seriesitem.h" #include #include #include #include static inline void renderItem( QPainter *painter, const QRect &canvasRect, QwtPlotAbstractSeriesItem *seriesItem, int from, int to ) { // A minor performance improvement is possible // with caching the maps. TODO ... QwtPlot *plot = seriesItem->plot(); const QwtScaleMap xMap = plot->canvasMap( seriesItem->xAxis() ); const QwtScaleMap yMap = plot->canvasMap( seriesItem->yAxis() ); painter->setRenderHint( QPainter::Antialiasing, seriesItem->testRenderHint( QwtPlotItem::RenderAntialiased ) ); seriesItem->drawSeries( painter, xMap, yMap, canvasRect, from, to ); } class QwtPlotDirectPainter::PrivateData { public: PrivateData(): attributes( 0 ), hasClipping(false), seriesItem( NULL ) { } QwtPlotDirectPainter::Attributes attributes; bool hasClipping; QRegion clipRegion; QPainter painter; QwtPlotAbstractSeriesItem *seriesItem; int from; int to; }; //! Constructor QwtPlotDirectPainter::QwtPlotDirectPainter( QObject *parent ): QObject( parent ) { d_data = new PrivateData; } //! Destructor QwtPlotDirectPainter::~QwtPlotDirectPainter() { delete d_data; } /*! Change an attribute \param attribute Attribute to change \param on On/Off \sa Attribute, testAttribute() */ void QwtPlotDirectPainter::setAttribute( Attribute attribute, bool on ) { if ( bool( d_data->attributes & attribute ) != on ) { if ( on ) d_data->attributes |= attribute; else d_data->attributes &= ~attribute; if ( ( attribute == AtomicPainter ) && on ) reset(); } } /*! Check if a attribute is set. \param attribute Attribute to be tested \sa Attribute, setAttribute() */ bool QwtPlotDirectPainter::testAttribute( Attribute attribute ) const { return d_data->attributes & attribute; } /*! En/Disables clipping \param enable Enables clipping is true, disable it otherwise \sa hasClipping(), clipRegion(), setClipRegion() */ void QwtPlotDirectPainter::setClipping( bool enable ) { d_data->hasClipping = enable; } /*! \return true, when clipping is enabled \sa setClipping(), clipRegion(), setClipRegion() */ bool QwtPlotDirectPainter::hasClipping() const { return d_data->hasClipping; } /*! \brief Assign a clip region and enable clipping Depending on the environment setting a proper clip region might improve the performance heavily. F.e. on Qt embedded only the clipped part of the backing store will be copied to a ( maybe unaccelerated ) frame buffer device. \param region Clip region \sa clipRegion(), hasClipping(), setClipping() */ void QwtPlotDirectPainter::setClipRegion( const QRegion ®ion ) { d_data->clipRegion = region; d_data->hasClipping = true; } /*! \return Currently set clip region. \sa setClipRegion(), setClipping(), hasClipping() */ QRegion QwtPlotDirectPainter::clipRegion() const { return d_data->clipRegion; } /*! \brief Draw a set of points of a seriesItem. When observing an measurement while it is running, new points have to be added to an existing seriesItem. drawSeries can be used to display them avoiding a complete redraw of the canvas. Setting plot()->canvas()->setAttribute(Qt::WA_PaintOutsidePaintEvent, true); will result in faster painting, if the paint engine of the canvas widget supports this feature. \param seriesItem Item to be painted \param from Index of the first point to be painted \param to Index of the last point to be painted. If to < 0 the series will be painted to its last point. */ void QwtPlotDirectPainter::drawSeries( QwtPlotAbstractSeriesItem *seriesItem, int from, int to ) { if ( seriesItem == NULL || seriesItem->plot() == NULL ) return; QwtPlotCanvas *canvas = seriesItem->plot()->canvas(); const QRect canvasRect = canvas->contentsRect(); const bool hasBackingStore = canvas->testPaintAttribute( QwtPlotCanvas::BackingStore ) && canvas->backingStore() && !canvas->backingStore()->isNull(); if ( hasBackingStore ) { QPainter painter( const_cast( canvas->backingStore() ) ); painter.translate( -canvasRect.x(), -canvasRect.y() ); if ( d_data->hasClipping ) painter.setClipRegion( d_data->clipRegion ); renderItem( &painter, canvasRect, seriesItem, from, to ); if ( testAttribute( QwtPlotDirectPainter::FullRepaint ) ) { canvas->repaint(); return; } } bool immediatePaint = true; if ( !canvas->testAttribute( Qt::WA_WState_InPaintEvent ) && !canvas->testAttribute( Qt::WA_PaintOutsidePaintEvent ) ) { immediatePaint = false; } if ( immediatePaint ) { QwtPlotCanvas *canvas = seriesItem->plot()->canvas(); if ( !d_data->painter.isActive() ) { reset(); d_data->painter.begin( canvas ); canvas->installEventFilter( this ); } if ( d_data->hasClipping ) { d_data->painter.setClipRegion( QRegion( canvasRect ) & d_data->clipRegion ); } else { if ( !d_data->painter.hasClipping() ) d_data->painter.setClipRect( canvasRect ); } renderItem( &d_data->painter, canvasRect, seriesItem, from, to ); if ( d_data->attributes & QwtPlotDirectPainter::AtomicPainter ) { reset(); } else { if ( d_data->hasClipping ) d_data->painter.setClipping( false ); } } else { reset(); d_data->seriesItem = seriesItem; d_data->from = from; d_data->to = to; QRegion clipRegion = canvasRect; if ( d_data->hasClipping ) clipRegion &= d_data->clipRegion; canvas->installEventFilter( this ); canvas->repaint(clipRegion); canvas->removeEventFilter( this ); d_data->seriesItem = NULL; } } //! Close the internal QPainter void QwtPlotDirectPainter::reset() { if ( d_data->painter.isActive() ) { QWidget *w = ( QWidget * )d_data->painter.device(); if ( w ) w->removeEventFilter( this ); d_data->painter.end(); } } //! Event filter bool QwtPlotDirectPainter::eventFilter( QObject *, QEvent *event ) { if ( event->type() == QEvent::Paint ) { reset(); if ( d_data->seriesItem ) { const QPaintEvent *pe = static_cast< QPaintEvent *>( event ); QwtPlotCanvas *canvas = d_data->seriesItem->plot()->canvas(); QPainter painter( canvas ); painter.setClipRegion( pe->region() ); bool copyCache = testAttribute( CopyBackingStore ) && canvas->testPaintAttribute( QwtPlotCanvas::BackingStore ); if ( copyCache ) { // is something valid in the cache ? copyCache = ( canvas->backingStore() != NULL ) && !canvas->backingStore()->isNull(); } if ( copyCache ) { painter.drawPixmap( canvas->contentsRect().topLeft(), *canvas->backingStore() ); } else { renderItem( &painter, canvas->contentsRect(), d_data->seriesItem, d_data->from, d_data->to ); } return true; // don't call QwtPlotCanvas::paintEvent() } } return false; } workbench-1.1.1/src/Qwt/qwt_plot_directpainter.h000066400000000000000000000063131255417355300220120ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #ifndef QWT_PLOT_DIRECT_PAINTER_H #define QWT_PLOT_DIRECT_PAINTER_H #include "qwt_global.h" #include class QRegion; class QwtPlotAbstractSeriesItem; /*! \brief Painter object trying to paint incrementally Often applications want to display samples while they are collected. When there are too many samples complete replots will be expensive to be processed in a collection cycle. QwtPlotDirectPainter offers an API to paint subsets ( f.e all additions points ) without erasing/repainting the plot canvas. On certain environments it might be important to calculate a proper clip region before painting. F.e. for Qt Embedded only the clipped part of the backing store will be copied to a ( maybe unaccelerated ) frame buffer. \warning Incremental painting will only help when no replot is triggered by another operation ( like changing scales ) and nothing needs to be erased. */ class QWT_EXPORT QwtPlotDirectPainter: public QObject { public: /*! \brief Paint attributes \sa setAttribute(), testAttribute(), drawSeries() */ enum Attribute { /*! Initializing a QPainter is an expensive operation. When AtomicPainter is set each call of drawSeries() opens/closes a temporary QPainter. Otherwise QwtPlotDirectPainter tries to use the same QPainter as long as possible. */ AtomicPainter = 0x01, /*! When FullRepaint is set the plot canvas is explicitely repainted after the samples have been rendered. */ FullRepaint = 0x02, /*! When QwtPlotCanvas::BackingStore is enabled the painter has to paint to the backing store and the widget. In certain situations/environments it might be faster to paint to the backing store only and then copy the backingstore to the canvas. This flag can also be useful for settings, where Qt fills the the clip region with the widget background. */ CopyBackingStore = 0x04 }; //! Paint attributes typedef QFlags Attributes; QwtPlotDirectPainter( QObject *parent = NULL ); virtual ~QwtPlotDirectPainter(); void setAttribute( Attribute, bool on ); bool testAttribute( Attribute ) const; void setClipping( bool ); bool hasClipping() const; void setClipRegion( const QRegion & ); QRegion clipRegion() const; void drawSeries( QwtPlotAbstractSeriesItem *, int from, int to ); void reset(); virtual bool eventFilter( QObject *, QEvent * ); private: class PrivateData; PrivateData *d_data; }; Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotDirectPainter::Attributes ) #endif workbench-1.1.1/src/Qwt/qwt_plot_grid.cpp000066400000000000000000000204321255417355300204330ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #include "qwt_plot_grid.h" #include "qwt_painter.h" #include "qwt_text.h" #include "qwt_scale_map.h" #include "qwt_scale_div.h" #include "qwt_math.h" #include #include class QwtPlotGrid::PrivateData { public: PrivateData(): xEnabled( true ), yEnabled( true ), xMinEnabled( false ), yMinEnabled( false ) { } bool xEnabled; bool yEnabled; bool xMinEnabled; bool yMinEnabled; QwtScaleDiv xScaleDiv; QwtScaleDiv yScaleDiv; QPen majPen; QPen minPen; }; //! Enables major grid, disables minor grid QwtPlotGrid::QwtPlotGrid(): QwtPlotItem( QwtText( "Grid" ) ) { d_data = new PrivateData; setZ( 10.0 ); } //! Destructor QwtPlotGrid::~QwtPlotGrid() { delete d_data; } //! \return QwtPlotItem::Rtti_PlotGrid int QwtPlotGrid::rtti() const { return QwtPlotItem::Rtti_PlotGrid; } /*! \brief Enable or disable vertical gridlines \param tf Enable (true) or disable \sa Minor gridlines can be enabled or disabled with enableXMin() */ void QwtPlotGrid::enableX( bool tf ) { if ( d_data->xEnabled != tf ) { d_data->xEnabled = tf; itemChanged(); } } /*! \brief Enable or disable horizontal gridlines \param tf Enable (true) or disable \sa Minor gridlines can be enabled or disabled with enableYMin() */ void QwtPlotGrid::enableY( bool tf ) { if ( d_data->yEnabled != tf ) { d_data->yEnabled = tf; itemChanged(); } } /*! \brief Enable or disable minor vertical gridlines. \param tf Enable (true) or disable \sa enableX() */ void QwtPlotGrid::enableXMin( bool tf ) { if ( d_data->xMinEnabled != tf ) { d_data->xMinEnabled = tf; itemChanged(); } } /*! \brief Enable or disable minor horizontal gridlines \param tf Enable (true) or disable \sa enableY() */ void QwtPlotGrid::enableYMin( bool tf ) { if ( d_data->yMinEnabled != tf ) { d_data->yMinEnabled = tf; itemChanged(); } } /*! Assign an x axis scale division \param scaleDiv Scale division */ void QwtPlotGrid::setXDiv( const QwtScaleDiv &scaleDiv ) { if ( d_data->xScaleDiv != scaleDiv ) { d_data->xScaleDiv = scaleDiv; itemChanged(); } } /*! Assign a y axis division \param scaleDiv Scale division */ void QwtPlotGrid::setYDiv( const QwtScaleDiv &scaleDiv ) { if ( d_data->yScaleDiv != scaleDiv ) { d_data->yScaleDiv = scaleDiv; itemChanged(); } } /*! Assign a pen for both major and minor gridlines \param pen Pen \sa setMajPen(), setMinPen() */ void QwtPlotGrid::setPen( const QPen &pen ) { if ( d_data->majPen != pen || d_data->minPen != pen ) { d_data->majPen = pen; d_data->minPen = pen; itemChanged(); } } /*! Assign a pen for the major gridlines \param pen Pen \sa majPen(), setMinPen(), setPen() */ void QwtPlotGrid::setMajPen( const QPen &pen ) { if ( d_data->majPen != pen ) { d_data->majPen = pen; itemChanged(); } } /*! Assign a pen for the minor gridlines \param pen Pen \sa minPen(), setMajPen(), setPen() */ void QwtPlotGrid::setMinPen( const QPen &pen ) { if ( d_data->minPen != pen ) { d_data->minPen = pen; itemChanged(); } } /*! \brief Draw the grid The grid is drawn into the bounding rectangle such that gridlines begin and end at the rectangle's borders. The X and Y maps are used to map the scale divisions into the drawing region screen. \param painter Painter \param xMap X axis map \param yMap Y axis \param canvasRect Contents rect of the plot canvas */ void QwtPlotGrid::draw( QPainter *painter, const QwtScaleMap &xMap, const QwtScaleMap &yMap, const QRectF &canvasRect ) const { // draw minor gridlines QPen minPen = d_data->minPen; minPen.setCapStyle( Qt::FlatCap ); painter->setPen( minPen ); if ( d_data->xEnabled && d_data->xMinEnabled ) { drawLines( painter, canvasRect, Qt::Vertical, xMap, d_data->xScaleDiv.ticks( QwtScaleDiv::MinorTick ) ); drawLines( painter, canvasRect, Qt::Vertical, xMap, d_data->xScaleDiv.ticks( QwtScaleDiv::MediumTick ) ); } if ( d_data->yEnabled && d_data->yMinEnabled ) { drawLines( painter, canvasRect, Qt::Horizontal, yMap, d_data->yScaleDiv.ticks( QwtScaleDiv::MinorTick ) ); drawLines( painter, canvasRect, Qt::Horizontal, yMap, d_data->yScaleDiv.ticks( QwtScaleDiv::MediumTick ) ); } // draw major gridlines QPen majPen = d_data->majPen; majPen.setCapStyle( Qt::FlatCap ); painter->setPen( majPen ); if ( d_data->xEnabled ) { drawLines( painter, canvasRect, Qt::Vertical, xMap, d_data->xScaleDiv.ticks( QwtScaleDiv::MajorTick ) ); } if ( d_data->yEnabled ) { drawLines( painter, canvasRect, Qt::Horizontal, yMap, d_data->yScaleDiv.ticks( QwtScaleDiv::MajorTick ) ); } } void QwtPlotGrid::drawLines( QPainter *painter, const QRectF &canvasRect, Qt::Orientation orientation, const QwtScaleMap &scaleMap, const QList &values ) const { const double x1 = canvasRect.left(); const double x2 = canvasRect.right() - 1.0; const double y1 = canvasRect.top(); const double y2 = canvasRect.bottom() - 1.0; const bool doAlign = QwtPainter::roundingAlignment( painter ); for ( int i = 0; i < values.count(); i++ ) { double value = scaleMap.transform( values[i] ); if ( doAlign ) value = qRound( value ); if ( orientation == Qt::Horizontal ) { if ( qwtFuzzyGreaterOrEqual( value, y1 ) && qwtFuzzyLessOrEqual( value, y2 ) ) { QwtPainter::drawLine( painter, x1, value, x2, value ); } } else { if ( qwtFuzzyGreaterOrEqual( value, x1 ) && qwtFuzzyLessOrEqual( value, x2 ) ) { QwtPainter::drawLine( painter, value, y1, value, y2 ); } } } } /*! \return the pen for the major gridlines \sa setMajPen(), setMinPen(), setPen() */ const QPen &QwtPlotGrid::majPen() const { return d_data->majPen; } /*! \return the pen for the minor gridlines \sa setMinPen(), setMajPen(), setPen() */ const QPen &QwtPlotGrid::minPen() const { return d_data->minPen; } /*! \return true if vertical gridlines are enabled \sa enableX() */ bool QwtPlotGrid::xEnabled() const { return d_data->xEnabled; } /*! \return true if minor vertical gridlines are enabled \sa enableXMin() */ bool QwtPlotGrid::xMinEnabled() const { return d_data->xMinEnabled; } /*! \return true if horizontal gridlines are enabled \sa enableY() */ bool QwtPlotGrid::yEnabled() const { return d_data->yEnabled; } /*! \return true if minor horizontal gridlines are enabled \sa enableYMin() */ bool QwtPlotGrid::yMinEnabled() const { return d_data->yMinEnabled; } /*! \return the scale division of the x axis */ const QwtScaleDiv &QwtPlotGrid::xScaleDiv() const { return d_data->xScaleDiv; } /*! \return the scale division of the y axis */ const QwtScaleDiv &QwtPlotGrid::yScaleDiv() const { return d_data->yScaleDiv; } /*! Update the grid to changes of the axes scale division \param xScaleDiv Scale division of the x-axis \param yScaleDiv Scale division of the y-axis \sa QwtPlot::updateAxes() */ void QwtPlotGrid::updateScaleDiv( const QwtScaleDiv& xScaleDiv, const QwtScaleDiv& yScaleDiv ) { setXDiv( xScaleDiv ); setYDiv( yScaleDiv ); } workbench-1.1.1/src/Qwt/qwt_plot_grid.h000066400000000000000000000043111255417355300200760ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #ifndef QWT_PLOT_GRID_H #define QWT_PLOT_GRID_H #include "qwt_global.h" #include "qwt_plot_item.h" #include "qwt_scale_div.h" class QPainter; class QPen; class QwtScaleMap; class QwtScaleDiv; /*! \brief A class which draws a coordinate grid The QwtPlotGrid class can be used to draw a coordinate grid. A coordinate grid consists of major and minor vertical and horizontal gridlines. The locations of the gridlines are determined by the X and Y scale divisions which can be assigned with setXDiv() and setYDiv(). The draw() member draws the grid within a bounding rectangle. */ class QWT_EXPORT QwtPlotGrid: public QwtPlotItem { public: explicit QwtPlotGrid(); virtual ~QwtPlotGrid(); virtual int rtti() const; void enableX( bool tf ); bool xEnabled() const; void enableY( bool tf ); bool yEnabled() const; void enableXMin( bool tf ); bool xMinEnabled() const; void enableYMin( bool tf ); bool yMinEnabled() const; void setXDiv( const QwtScaleDiv &sx ); const QwtScaleDiv &xScaleDiv() const; void setYDiv( const QwtScaleDiv &sy ); const QwtScaleDiv &yScaleDiv() const; void setPen( const QPen &p ); void setMajPen( const QPen &p ); const QPen& majPen() const; void setMinPen( const QPen &p ); const QPen& minPen() const; virtual void draw( QPainter *p, const QwtScaleMap &xMap, const QwtScaleMap &yMap, const QRectF &rect ) const; virtual void updateScaleDiv( const QwtScaleDiv &xMap, const QwtScaleDiv &yMap ); private: void drawLines( QPainter *painter, const QRectF &, Qt::Orientation orientation, const QwtScaleMap &, const QList & ) const; class PrivateData; PrivateData *d_data; }; #endif workbench-1.1.1/src/Qwt/qwt_plot_histogram.cpp000066400000000000000000000427371255417355300215170ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #include "qwt_plot_histogram.h" #include "qwt_plot.h" #include "qwt_legend.h" #include "qwt_legend_item.h" #include "qwt_painter.h" #include "qwt_column_symbol.h" #include "qwt_scale_map.h" #include #include static inline bool isCombinable( const QwtInterval &d1, const QwtInterval &d2 ) { if ( d1.isValid() && d2.isValid() ) { if ( d1.maxValue() == d2.minValue() ) { if ( !( d1.borderFlags() & QwtInterval::ExcludeMaximum && d2.borderFlags() & QwtInterval::ExcludeMinimum ) ) { return true; } } } return false; } class QwtPlotHistogram::PrivateData { public: PrivateData(): baseline( 0.0 ), style( Columns ), symbol( NULL ) { } ~PrivateData() { delete symbol; } double baseline; QPen pen; QBrush brush; QwtPlotHistogram::HistogramStyle style; const QwtColumnSymbol *symbol; }; /*! Constructor \param title Title of the histogram. */ QwtPlotHistogram::QwtPlotHistogram( const QwtText &title ): QwtPlotSeriesItem( title ) { init(); } /*! Constructor \param title Title of the histogram. */ QwtPlotHistogram::QwtPlotHistogram( const QString &title ): QwtPlotSeriesItem( title ) { init(); } //! Destructor QwtPlotHistogram::~QwtPlotHistogram() { delete d_data; } //! Initialize data members void QwtPlotHistogram::init() { d_data = new PrivateData(); d_series = new QwtIntervalSeriesData(); setItemAttribute( QwtPlotItem::AutoScale, true ); setItemAttribute( QwtPlotItem::Legend, true ); setZ( 20.0 ); } /*! Set the histogram's drawing style \param style Histogram style \sa HistogramStyle, style() */ void QwtPlotHistogram::setStyle( HistogramStyle style ) { if ( style != d_data->style ) { d_data->style = style; itemChanged(); } } /*! Return the current style \sa HistogramStyle, setStyle() */ QwtPlotHistogram::HistogramStyle QwtPlotHistogram::style() const { return d_data->style; } /*! Assign a pen, that is used in a style() depending way. \param pen New pen \sa pen(), brush() */ void QwtPlotHistogram::setPen( const QPen &pen ) { if ( pen != d_data->pen ) { d_data->pen = pen; itemChanged(); } } /*! \return Pen used in a style() depending way. \sa setPen(), brush() */ const QPen &QwtPlotHistogram::pen() const { return d_data->pen; } /*! Assign a brush, that is used in a style() depending way. \param brush New brush \sa pen(), brush() */ void QwtPlotHistogram::setBrush( const QBrush &brush ) { if ( brush != d_data->brush ) { d_data->brush = brush; itemChanged(); } } /*! \return Brush used in a style() depending way. \sa setPen(), brush() */ const QBrush &QwtPlotHistogram::brush() const { return d_data->brush; } /*! \brief Assign a symbol In Column style an optional symbol can be assigned, that is responsible for displaying the rectangle that is defined by the interval and the distance between baseline() and value. When no symbol has been defined the area is displayed as plain rectangle using pen() and brush(). \sa style(), symbol(), drawColumn(), pen(), brush() \note In applications, where different intervals need to be displayed in a different way ( f.e different colors or even using differnt symbols) it is recommended to overload drawColumn(). */ void QwtPlotHistogram::setSymbol( const QwtColumnSymbol *symbol ) { if ( symbol != d_data->symbol ) { delete d_data->symbol; d_data->symbol = symbol; itemChanged(); } } /*! \return Current symbol or NULL, when no symbol has been assigned \sa setSymbol() */ const QwtColumnSymbol *QwtPlotHistogram::symbol() const { return d_data->symbol; } /*! \brief Set the value of the baseline Each column representing an QwtIntervalSample is defined by its interval and the interval between baseline and the value of the sample. The default value of the baseline is 0.0. \param value Value of the baseline \sa baseline() */ void QwtPlotHistogram::setBaseline( double value ) { if ( d_data->baseline != value ) { d_data->baseline = value; itemChanged(); } } /*! \return Value of the baseline \sa setBaseline() */ double QwtPlotHistogram::baseline() const { return d_data->baseline; } /*! \return Bounding rectangle of all samples. For an empty series the rectangle is invalid. */ QRectF QwtPlotHistogram::boundingRect() const { QRectF rect = d_series->boundingRect(); if ( !rect.isValid() ) return rect; if ( orientation() == Qt::Horizontal ) { rect = QRectF( rect.y(), rect.x(), rect.height(), rect.width() ); if ( rect.left() > d_data->baseline ) rect.setLeft( d_data->baseline ); else if ( rect.right() < d_data->baseline ) rect.setRight( d_data->baseline ); } else { if ( rect.bottom() < d_data->baseline ) rect.setBottom( d_data->baseline ); else if ( rect.top() > d_data->baseline ) rect.setTop( d_data->baseline ); } return rect; } //! \return QwtPlotItem::Rtti_PlotHistogram int QwtPlotHistogram::rtti() const { return QwtPlotItem::Rtti_PlotHistogram; } /*! Initialize data with an array of samples. \param samples Vector of points */ void QwtPlotHistogram::setSamples( const QVector &samples ) { delete d_series; d_series = new QwtIntervalSeriesData( samples ); itemChanged(); } /*! Draw a subset of the histogram samples \param painter Painter \param xMap Maps x-values into pixel coordinates. \param yMap Maps y-values into pixel coordinates. \param canvasRect Contents rect of the canvas \param from Index of the first sample to be painted \param to Index of the last sample to be painted. If to < 0 the series will be painted to its last sample. \sa drawOutline(), drawLines(), drawColumns */ void QwtPlotHistogram::drawSeries( QPainter *painter, const QwtScaleMap &xMap, const QwtScaleMap &yMap, const QRectF &, int from, int to ) const { if ( !painter || dataSize() <= 0 ) return; if ( to < 0 ) to = dataSize() - 1; switch ( d_data->style ) { case Outline: drawOutline( painter, xMap, yMap, from, to ); break; case Lines: drawLines( painter, xMap, yMap, from, to ); break; case Columns: drawColumns( painter, xMap, yMap, from, to ); break; default: break; } } /*! Draw a histogram in Outline style() \param painter Painter \param xMap Maps x-values into pixel coordinates. \param yMap Maps y-values into pixel coordinates. \param from Index of the first sample to be painted \param to Index of the last sample to be painted. If to < 0 the histogram will be painted to its last point. \sa setStyle(), style() \warning The outline style requires, that the intervals are in increasing order and not overlapping. */ void QwtPlotHistogram::drawOutline( QPainter *painter, const QwtScaleMap &xMap, const QwtScaleMap &yMap, int from, int to ) const { const bool doAlign = QwtPainter::roundingAlignment( painter ); double v0 = ( orientation() == Qt::Horizontal ) ? xMap.transform( baseline() ) : yMap.transform( baseline() ); if ( doAlign ) v0 = qRound( v0 ); QwtIntervalSample previous; QPolygonF polygon; for ( int i = from; i <= to; i++ ) { const QwtIntervalSample sample = d_series->sample( i ); if ( !sample.interval.isValid() ) { flushPolygon( painter, v0, polygon ); previous = sample; continue; } if ( previous.interval.isValid() ) { if ( !isCombinable( previous.interval, sample.interval ) ) flushPolygon( painter, v0, polygon ); } if ( orientation() == Qt::Vertical ) { double x1 = xMap.transform( sample.interval.minValue() ); double x2 = xMap.transform( sample.interval.maxValue() ); double y = yMap.transform( sample.value ); if ( doAlign ) { x1 = qRound( x1 ); x2 = qRound( x2 ); y = qRound( y ); } if ( polygon.size() == 0 ) polygon += QPointF( x1, v0 ); polygon += QPointF( x1, y ); polygon += QPointF( x2, y ); } else { double y1 = yMap.transform( sample.interval.minValue() ); double y2 = yMap.transform( sample.interval.maxValue() ); double x = xMap.transform( sample.value ); if ( doAlign ) { y1 = qRound( y1 ); y2 = qRound( y2 ); x = qRound( x ); } if ( polygon.size() == 0 ) polygon += QPointF( v0, y1 ); polygon += QPointF( x, y1 ); polygon += QPointF( x, y2 ); } previous = sample; } flushPolygon( painter, v0, polygon ); } /*! Draw a histogram in Columns style() \param painter Painter \param xMap Maps x-values into pixel coordinates. \param yMap Maps y-values into pixel coordinates. \param from Index of the first sample to be painted \param to Index of the last sample to be painted. If to < 0 the histogram will be painted to its last point. \sa setStyle(), style(), setSymbol(), drawColumn() */ void QwtPlotHistogram::drawColumns( QPainter *painter, const QwtScaleMap &xMap, const QwtScaleMap &yMap, int from, int to ) const { painter->setPen( d_data->pen ); painter->setBrush( d_data->brush ); for ( int i = from; i <= to; i++ ) { const QwtIntervalSample sample = d_series->sample( i ); if ( !sample.interval.isNull() ) { const QwtColumnRect rect = columnRect( sample, xMap, yMap ); drawColumn( painter, rect, sample ); } } } /*! Draw a histogram in Lines style() \param painter Painter \param xMap Maps x-values into pixel coordinates. \param yMap Maps y-values into pixel coordinates. \param from Index of the first sample to be painted \param to Index of the last sample to be painted. If to < 0 the histogram will be painted to its last point. \sa setStyle(), style(), setPen() */ void QwtPlotHistogram::drawLines( QPainter *painter, const QwtScaleMap &xMap, const QwtScaleMap &yMap, int from, int to ) const { const bool doAlign = QwtPainter::roundingAlignment( painter ); painter->setPen( d_data->pen ); painter->setBrush( Qt::NoBrush ); for ( int i = from; i <= to; i++ ) { const QwtIntervalSample sample = d_series->sample( i ); if ( !sample.interval.isNull() ) { const QwtColumnRect rect = columnRect( sample, xMap, yMap ); QRectF r = rect.toRect(); if ( doAlign ) { r.setLeft( qRound( r.left() ) ); r.setRight( qRound( r.right() ) ); r.setTop( qRound( r.top() ) ); r.setBottom( qRound( r.bottom() ) ); } switch ( rect.direction ) { case QwtColumnRect::LeftToRight: { QwtPainter::drawLine( painter, r.topRight(), r.bottomRight() ); break; } case QwtColumnRect::RightToLeft: { QwtPainter::drawLine( painter, r.topLeft(), r.bottomLeft() ); break; } case QwtColumnRect::TopToBottom: { QwtPainter::drawLine( painter, r.bottomRight(), r.bottomLeft() ); break; } case QwtColumnRect::BottomToTop: { QwtPainter::drawLine( painter, r.topRight(), r.topLeft() ); break; } } } } } //! Internal, used by the Outline style. void QwtPlotHistogram::flushPolygon( QPainter *painter, double baseLine, QPolygonF &polygon ) const { if ( polygon.size() == 0 ) return; if ( orientation() == Qt::Horizontal ) polygon += QPointF( baseLine, polygon.last().y() ); else polygon += QPointF( polygon.last().x(), baseLine ); if ( d_data->brush.style() != Qt::NoBrush ) { painter->setPen( Qt::NoPen ); painter->setBrush( d_data->brush ); if ( orientation() == Qt::Horizontal ) { polygon += QPointF( polygon.last().x(), baseLine ); polygon += QPointF( polygon.first().x(), baseLine ); } else { polygon += QPointF( baseLine, polygon.last().y() ); polygon += QPointF( baseLine, polygon.first().y() ); } QwtPainter::drawPolygon( painter, polygon ); polygon.resize( polygon.size() - 2 ); } if ( d_data->pen.style() != Qt::NoPen ) { painter->setBrush( Qt::NoBrush ); painter->setPen( d_data->pen ); QwtPainter::drawPolyline( painter, polygon ); } polygon.clear(); } /*! Calculate the area that is covered by a sample \param sample Sample \param xMap Maps x-values into pixel coordinates. \param yMap Maps y-values into pixel coordinates. \return Rectangle, that is covered by a sample */ QwtColumnRect QwtPlotHistogram::columnRect( const QwtIntervalSample &sample, const QwtScaleMap &xMap, const QwtScaleMap &yMap ) const { QwtColumnRect rect; const QwtInterval &iv = sample.interval; if ( !iv.isValid() ) return rect; if ( orientation() == Qt::Horizontal ) { const double x0 = xMap.transform( baseline() ); const double x = xMap.transform( sample.value ); const double y1 = yMap.transform( iv.minValue() ); const double y2 = yMap.transform( iv.maxValue() ); rect.hInterval.setInterval( x0, x ); rect.vInterval.setInterval( y1, y2, iv.borderFlags() ); rect.direction = ( x < x0 ) ? QwtColumnRect::RightToLeft : QwtColumnRect::LeftToRight; } else { const double x1 = xMap.transform( iv.minValue() ); const double x2 = xMap.transform( iv.maxValue() ); const double y0 = yMap.transform( baseline() ); const double y = yMap.transform( sample.value ); rect.hInterval.setInterval( x1, x2, iv.borderFlags() ); rect.vInterval.setInterval( y0, y ); rect.direction = ( y < y0 ) ? QwtColumnRect::BottomToTop : QwtColumnRect::TopToBottom; } return rect; } /*! Draw a column for a sample in Columns style(). When a symbol() has been set the symbol is used otherwise the column is displayed as plain rectangle using pen() and brush(). \param painter Painter \param rect Rectangle where to paint the column in paint device coordinates \param sample Sample to be displayed \note In applications, where different intervals need to be displayed in a different way ( f.e different colors or even using differnt symbols) it is recommended to overload drawColumn(). */ void QwtPlotHistogram::drawColumn( QPainter *painter, const QwtColumnRect &rect, const QwtIntervalSample &sample ) const { Q_UNUSED( sample ); if ( d_data->symbol && ( d_data->symbol->style() != QwtColumnSymbol::NoStyle ) ) { d_data->symbol->draw( painter, rect ); } else { QRectF r = rect.toRect(); if ( QwtPainter::roundingAlignment( painter ) ) { r.setLeft( qRound( r.left() ) ); r.setRight( qRound( r.right() ) ); r.setTop( qRound( r.top() ) ); r.setBottom( qRound( r.bottom() ) ); } QwtPainter::drawRect( painter, r ); } } /*! Draw a plain rectangle without pen using the brush() as identifier \param painter Painter \param rect Bounding rectangle for the identifier */ void QwtPlotHistogram::drawLegendIdentifier( QPainter *painter, const QRectF &rect ) const { const double dim = qMin( rect.width(), rect.height() ); QSizeF size( dim, dim ); QRectF r( 0, 0, size.width(), size.height() ); r.moveCenter( rect.center() ); painter->fillRect( r, d_data->brush ); } workbench-1.1.1/src/Qwt/qwt_plot_histogram.h000066400000000000000000000101721255417355300211500ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #ifndef QWT_PLOT_HISTOGRAM_H #define QWT_PLOT_HISTOGRAM_H #include "qwt_global.h" #include "qwt_plot_seriesitem.h" #include "qwt_column_symbol.h" #include #include class QwtIntervalData; class QString; class QPolygonF; /*! \brief QwtPlotHistogram represents a series of samples, where an interval is associated with a value ( \f$y = f([x1,x2])\f$ ). The representation depends on the style() and an optional symbol() that is displayed for each interval. \note The term "histogram" is used in a different way in the areas of digital image processing and statistics. Wikipedia introduces the terms "image histogram" and "color histogram" to avoid confusions. While "image histograms" can be displayed by a QwtPlotCurve there is no applicable plot item for a "color histogram" yet. */ class QWT_EXPORT QwtPlotHistogram: public QwtPlotSeriesItem { public: /*! Histogram styles. The default style is QwtPlotHistogram::Columns. \sa setStyle(), style(), setSymbol(), symbol(), setBaseline() */ enum HistogramStyle { /*! Draw an outline around the area, that is build by all intervals using the pen() and fill it with the brush(). The outline style requires, that the intervals are in increasing order and not overlapping. */ Outline, /*! Draw a column for each interval. When a symbol() has been set the symbol is used otherwise the column is displayed as plain rectangle using pen() and brush(). */ Columns, /*! Draw a simple line using the pen() for each interval. */ Lines, /*! Styles >= UserStyle are reserved for derived classes that overload drawSeries() with additional application specific ways to display a histogram. */ UserStyle = 100 }; explicit QwtPlotHistogram( const QString &title = QString::null ); explicit QwtPlotHistogram( const QwtText &title ); virtual ~QwtPlotHistogram(); virtual int rtti() const; void setPen( const QPen & ); const QPen &pen() const; void setBrush( const QBrush & ); const QBrush &brush() const; void setSamples( const QVector & ); void setBaseline( double reference ); double baseline() const; void setStyle( HistogramStyle style ); HistogramStyle style() const; void setSymbol( const QwtColumnSymbol * ); const QwtColumnSymbol *symbol() const; virtual void drawSeries( QPainter *p, const QwtScaleMap &xMap, const QwtScaleMap &yMap, const QRectF &canvasRect, int from, int to ) const; virtual QRectF boundingRect() const; virtual void drawLegendIdentifier( QPainter *, const QRectF & ) const; protected: virtual QwtColumnRect columnRect( const QwtIntervalSample &, const QwtScaleMap &, const QwtScaleMap & ) const; virtual void drawColumn( QPainter *, const QwtColumnRect &, const QwtIntervalSample & ) const; void drawColumns( QPainter *, const QwtScaleMap &xMap, const QwtScaleMap &yMap, int from, int to ) const; void drawOutline( QPainter *, const QwtScaleMap &xMap, const QwtScaleMap &yMap, int from, int to ) const; void drawLines( QPainter *, const QwtScaleMap &xMap, const QwtScaleMap &yMap, int from, int to ) const; private: void init(); void flushPolygon( QPainter *, double baseLine, QPolygonF & ) const; class PrivateData; PrivateData *d_data; }; #endif workbench-1.1.1/src/Qwt/qwt_plot_intervalcurve.cpp000066400000000000000000000346351255417355300224110ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #include "qwt_plot_intervalcurve.h" #include "qwt_interval_symbol.h" #include "qwt_scale_map.h" #include "qwt_clipper.h" #include "qwt_painter.h" #include static inline bool qwtIsHSampleInside( const QwtIntervalSample &sample, double xMin, double xMax, double yMin, double yMax ) { const double y = sample.value; const double x1 = sample.interval.minValue(); const double x2 = sample.interval.maxValue(); const bool isOffScreen = ( y < yMin ) || ( y > yMax ) || ( x1 < xMin && x2 < xMin ) || ( x1 > yMax && x2 > xMax ); return !isOffScreen; } static inline bool qwtIsVSampleInside( const QwtIntervalSample &sample, double xMin, double xMax, double yMin, double yMax ) { const double x = sample.value; const double y1 = sample.interval.minValue(); const double y2 = sample.interval.maxValue(); const bool isOffScreen = ( x < xMin ) || ( x > xMax ) || ( y1 < yMin && y2 < yMin ) || ( y1 > yMax && y2 > yMax ); return !isOffScreen; } class QwtPlotIntervalCurve::PrivateData { public: PrivateData(): style( Tube ), symbol( NULL ), pen( Qt::black ), brush( Qt::white ) { paintAttributes = QwtPlotIntervalCurve::ClipPolygons; paintAttributes |= QwtPlotIntervalCurve::ClipSymbol; pen.setCapStyle( Qt::FlatCap ); } ~PrivateData() { delete symbol; } CurveStyle style; const QwtIntervalSymbol *symbol; QPen pen; QBrush brush; QwtPlotIntervalCurve::PaintAttributes paintAttributes; }; /*! Constructor \param title Title of the curve */ QwtPlotIntervalCurve::QwtPlotIntervalCurve( const QwtText &title ): QwtPlotSeriesItem( title ) { init(); } /*! Constructor \param title Title of the curve */ QwtPlotIntervalCurve::QwtPlotIntervalCurve( const QString &title ): QwtPlotSeriesItem( QwtText( title ) ) { init(); } //! Destructor QwtPlotIntervalCurve::~QwtPlotIntervalCurve() { delete d_data; } //! Initialize internal members void QwtPlotIntervalCurve::init() { setItemAttribute( QwtPlotItem::Legend, true ); setItemAttribute( QwtPlotItem::AutoScale, true ); d_data = new PrivateData; d_series = new QwtIntervalSeriesData(); setZ( 19.0 ); } //! \return QwtPlotItem::Rtti_PlotIntervalCurve int QwtPlotIntervalCurve::rtti() const { return QwtPlotIntervalCurve::Rtti_PlotIntervalCurve; } /*! Specify an attribute how to draw the curve \param attribute Paint attribute \param on On/Off \sa testPaintAttribute() */ void QwtPlotIntervalCurve::setPaintAttribute( PaintAttribute attribute, bool on ) { if ( on ) d_data->paintAttributes |= attribute; else d_data->paintAttributes &= ~attribute; } /*! \brief Return the current paint attributes \sa PaintAttribute, setPaintAttribute() */ bool QwtPlotIntervalCurve::testPaintAttribute( PaintAttribute attribute ) const { return ( d_data->paintAttributes & attribute ); } /*! Initialize data with an array of samples. \param samples Vector of samples */ void QwtPlotIntervalCurve::setSamples( const QVector &samples ) { delete d_series; d_series = new QwtIntervalSeriesData( samples ); itemChanged(); } /*! Set the curve's drawing style \param style Curve style \sa CurveStyle, style() */ void QwtPlotIntervalCurve::setStyle( CurveStyle style ) { if ( style != d_data->style ) { d_data->style = style; itemChanged(); } } /*! \brief Return the current style \sa setStyle() */ QwtPlotIntervalCurve::CurveStyle QwtPlotIntervalCurve::style() const { return d_data->style; } /*! Assign a symbol. \param symbol Symbol \sa symbol() */ void QwtPlotIntervalCurve::setSymbol( const QwtIntervalSymbol *symbol ) { if ( symbol != d_data->symbol ) { delete d_data->symbol; d_data->symbol = symbol; itemChanged(); } } /*! \return Current symbol or NULL, when no symbol has been assigned \sa setSymbol() */ const QwtIntervalSymbol *QwtPlotIntervalCurve::symbol() const { return d_data->symbol; } /*! \brief Assign a pen \param pen New pen \sa pen(), brush() */ void QwtPlotIntervalCurve::setPen( const QPen &pen ) { if ( pen != d_data->pen ) { d_data->pen = pen; itemChanged(); } } /*! \brief Return the pen used to draw the lines \sa setPen(), brush() */ const QPen& QwtPlotIntervalCurve::pen() const { return d_data->pen; } /*! Assign a brush. The brush is used to fill the area in Tube style(). \param brush Brush \sa brush(), pen(), setStyle(), CurveStyle */ void QwtPlotIntervalCurve::setBrush( const QBrush &brush ) { if ( brush != d_data->brush ) { d_data->brush = brush; itemChanged(); } } /*! \return Brush used to fill the area in Tube style() \sa setBrush(), setStyle(), CurveStyle */ const QBrush& QwtPlotIntervalCurve::brush() const { return d_data->brush; } /*! \return Bounding rectangle of all samples. For an empty series the rectangle is invalid. */ QRectF QwtPlotIntervalCurve::boundingRect() const { QRectF rect = QwtPlotSeriesItem::boundingRect(); if ( rect.isValid() && orientation() == Qt::Vertical ) rect.setRect( rect.y(), rect.x(), rect.height(), rect.width() ); return rect; } /*! Draw a subset of the samples \param painter Painter \param xMap Maps x-values into pixel coordinates. \param yMap Maps y-values into pixel coordinates. \param canvasRect Contents rect of the canvas \param from Index of the first sample to be painted \param to Index of the last sample to be painted. If to < 0 the series will be painted to its last sample. \sa drawTube(), drawSymbols() */ void QwtPlotIntervalCurve::drawSeries( QPainter *painter, const QwtScaleMap &xMap, const QwtScaleMap &yMap, const QRectF &canvasRect, int from, int to ) const { if ( to < 0 ) to = dataSize() - 1; if ( from < 0 ) from = 0; if ( from > to ) return; switch ( d_data->style ) { case Tube: drawTube( painter, xMap, yMap, canvasRect, from, to ); break; case NoCurve: default: break; } if ( d_data->symbol && ( d_data->symbol->style() != QwtIntervalSymbol::NoSymbol ) ) { drawSymbols( painter, *d_data->symbol, xMap, yMap, canvasRect, from, to ); } } /*! Draw a tube Builds 2 curves from the upper and lower limits of the intervals and draws them with the pen(). The area between the curves is filled with the brush(). \param painter Painter \param xMap Maps x-values into pixel coordinates. \param yMap Maps y-values into pixel coordinates. \param canvasRect Contents rect of the canvas \param from Index of the first sample to be painted \param to Index of the last sample to be painted. If to < 0 the series will be painted to its last sample. \sa drawSeries(), drawSymbols() */ void QwtPlotIntervalCurve::drawTube( QPainter *painter, const QwtScaleMap &xMap, const QwtScaleMap &yMap, const QRectF &canvasRect, int from, int to ) const { const bool doAlign = QwtPainter::roundingAlignment( painter ); painter->save(); const size_t size = to - from + 1; QPolygonF polygon( 2 * size ); QPointF *points = polygon.data(); for ( uint i = 0; i < size; i++ ) { QPointF &minValue = points[i]; QPointF &maxValue = points[2 * size - 1 - i]; const QwtIntervalSample intervalSample = sample( from + i ); if ( orientation() == Qt::Vertical ) { double x = xMap.transform( intervalSample.value ); double y1 = yMap.transform( intervalSample.interval.minValue() ); double y2 = yMap.transform( intervalSample.interval.maxValue() ); if ( doAlign ) { x = qRound( x ); y1 = qRound( y1 ); y2 = qRound( y2 ); } minValue.rx() = x; minValue.ry() = y1; maxValue.rx() = x; maxValue.ry() = y2; } else { double y = yMap.transform( intervalSample.value ); double x1 = xMap.transform( intervalSample.interval.minValue() ); double x2 = xMap.transform( intervalSample.interval.maxValue() ); if ( doAlign ) { y = qRound( y ); x1 = qRound( x1 ); x2 = qRound( x2 ); } minValue.rx() = x1; minValue.ry() = y; maxValue.rx() = x2; maxValue.ry() = y; } } if ( d_data->brush.style() != Qt::NoBrush ) { painter->setPen( QPen( Qt::NoPen ) ); painter->setBrush( d_data->brush ); if ( d_data->paintAttributes & ClipPolygons ) { const qreal m = 1.0; const QPolygonF p = QwtClipper::clipPolygonF( canvasRect.adjusted(-m, -m, m, m), polygon, true ); QwtPainter::drawPolygon( painter, p ); } else { QwtPainter::drawPolygon( painter, polygon ); } } if ( d_data->pen.style() != Qt::NoPen ) { painter->setPen( d_data->pen ); painter->setBrush( Qt::NoBrush ); if ( d_data->paintAttributes & ClipPolygons ) { qreal pw = qMax( qreal( 1.0 ), painter->pen().widthF()); /*const QRectF clipRect =*/ canvasRect.adjusted(-pw, -pw, pw, pw); QPolygonF p; p.resize( size ); qMemCopy( p.data(), points, size * sizeof( QPointF ) ); p = QwtClipper::clipPolygonF( canvasRect, p ); QwtPainter::drawPolyline( painter, p ); p.resize( size ); qMemCopy( p.data(), points + size, size * sizeof( QPointF ) ); p = QwtClipper::clipPolygonF( canvasRect, p ); QwtPainter::drawPolyline( painter, p ); } else { QwtPainter::drawPolyline( painter, points, size ); QwtPainter::drawPolyline( painter, points + size, size ); } } painter->restore(); } /*! Draw symbols for a subset of the samples \param painter Painter \param symbol Interval symbol \param xMap x map \param yMap y map \param canvasRect Contents rect of the canvas \param from Index of the first sample to be painted \param to Index of the last sample to be painted \sa setSymbol(), drawSeries(), drawTube() */ void QwtPlotIntervalCurve::drawSymbols( QPainter *painter, const QwtIntervalSymbol &symbol, const QwtScaleMap &xMap, const QwtScaleMap &yMap, const QRectF &canvasRect, int from, int to ) const { painter->save(); QPen pen = symbol.pen(); pen.setCapStyle( Qt::FlatCap ); painter->setPen( pen ); painter->setBrush( symbol.brush() ); const QRectF &tr = QwtScaleMap::invTransform( xMap, yMap, canvasRect); const double xMin = tr.left(); const double xMax = tr.right(); const double yMin = tr.top(); const double yMax = tr.bottom(); const bool doClip = d_data->paintAttributes & ClipPolygons; for ( int i = from; i <= to; i++ ) { const QwtIntervalSample s = sample( i ); if ( orientation() == Qt::Vertical ) { if ( !doClip || qwtIsVSampleInside( s, xMin, xMax, yMin, yMax ) ) { const double x = xMap.transform( s.value ); const double y1 = yMap.transform( s.interval.minValue() ); const double y2 = yMap.transform( s.interval.maxValue() ); symbol.draw( painter, orientation(), QPointF( x, y1 ), QPointF( x, y2 ) ); } } else { if ( !doClip || qwtIsHSampleInside( s, xMin, xMax, yMin, yMax ) ) { const double y = yMap.transform( s.value ); const double x1 = xMap.transform( s.interval.minValue() ); const double x2 = xMap.transform( s.interval.maxValue() ); symbol.draw( painter, orientation(), QPointF( x1, y ), QPointF( x2, y ) ); } } } painter->restore(); } /*! In case of Tibe stale() a plain rectangle is painted without a pen filled the brush(). If a symbol is assigned it is painted cebtered into rect. \param painter Painter \param rect Bounding rectangle for the identifier */ void QwtPlotIntervalCurve::drawLegendIdentifier( QPainter *painter, const QRectF &rect ) const { const double dim = qMin( rect.width(), rect.height() ); QSizeF size( dim, dim ); QRectF r( 0, 0, size.width(), size.height() ); r.moveCenter( rect.center() ); if ( d_data->style == Tube ) { painter->fillRect( r, d_data->brush ); } if ( d_data->symbol && ( d_data->symbol->style() != QwtIntervalSymbol::NoSymbol ) ) { QPen pen = d_data->symbol->pen(); pen.setWidthF( pen.widthF() ); pen.setCapStyle( Qt::FlatCap ); painter->setPen( pen ); painter->setBrush( d_data->symbol->brush() ); if ( orientation() == Qt::Vertical ) { d_data->symbol->draw( painter, orientation(), QPointF( r.center().x(), r.top() ), QPointF( r.center().x(), r.bottom() - 1 ) ); } else { d_data->symbol->draw( painter, orientation(), QPointF( r.left(), r.center().y() ), QPointF( r.right() - 1, r.center().y() ) ); } } } workbench-1.1.1/src/Qwt/qwt_plot_intervalcurve.h000066400000000000000000000075741255417355300220600ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #ifndef QWT_PLOT_INTERVAL_CURVE_H #define QWT_PLOT_INTERVAL_CURVE_H #include "qwt_global.h" #include "qwt_plot_seriesitem.h" #include "qwt_series_data.h" class QwtIntervalSymbol; /*! \brief QwtPlotIntervalCurve represents a series of samples, where each value is associated with an interval ( \f$[y1,y2] = f(x)\f$ ). The representation depends on the style() and an optional symbol() that is displayed for each interval. QwtPlotIntervalCurve might be used to disply error bars or the area between 2 curves. */ class QWT_EXPORT QwtPlotIntervalCurve: public QwtPlotSeriesItem { public: /*! \brief Curve styles. The default setting is QwtPlotIntervalCurve::Tube. \sa setStyle(), style() */ enum CurveStyle { /*! Don't draw a curve. Note: This doesn't affect the symbols. */ NoCurve, /*! Build 2 curves from the upper and lower limits of the intervals and draw them with the pen(). The area between the curves is filled with the brush(). */ Tube, /*! Styles >= QwtPlotIntervalCurve::UserCurve are reserved for derived classes that overload drawSeries() with additional application specific curve types. */ UserCurve = 100 }; /*! Attributes to modify the drawing algorithm. \sa setPaintAttribute(), testPaintAttribute() */ enum PaintAttribute { /*! Clip polygons before painting them. In situations, where points are far outside the visible area (f.e when zooming deep) this might be a substantial improvement for the painting performance. */ ClipPolygons = 0x01, //! Check if a symbol is on the plot canvas before painting it. ClipSymbol = 0x02 }; //! Paint attributes typedef QFlags PaintAttributes; explicit QwtPlotIntervalCurve( const QString &title = QString::null ); explicit QwtPlotIntervalCurve( const QwtText &title ); virtual ~QwtPlotIntervalCurve(); virtual int rtti() const; void setPaintAttribute( PaintAttribute, bool on = true ); bool testPaintAttribute( PaintAttribute ) const; void setSamples( const QVector & ); void setPen( const QPen & ); const QPen &pen() const; void setBrush( const QBrush & ); const QBrush &brush() const; void setStyle( CurveStyle style ); CurveStyle style() const; void setSymbol( const QwtIntervalSymbol * ); const QwtIntervalSymbol *symbol() const; virtual void drawSeries( QPainter *p, const QwtScaleMap &xMap, const QwtScaleMap &yMap, const QRectF &canvasRect, int from, int to ) const; virtual QRectF boundingRect() const; virtual void drawLegendIdentifier( QPainter *, const QRectF & ) const; protected: void init(); virtual void drawTube( QPainter *, const QwtScaleMap &xMap, const QwtScaleMap &yMap, const QRectF &canvasRect, int from, int to ) const; virtual void drawSymbols( QPainter *, const QwtIntervalSymbol &, const QwtScaleMap &xMap, const QwtScaleMap &yMap, const QRectF &canvasRect, int from, int to ) const; private: class PrivateData; PrivateData *d_data; }; Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotIntervalCurve::PaintAttributes ) #endif workbench-1.1.1/src/Qwt/qwt_plot_item.cpp000066400000000000000000000275451255417355300204600ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #include "qwt_plot_item.h" #include "qwt_text.h" #include "qwt_plot.h" #include "qwt_legend.h" #include "qwt_legend_item.h" #include "qwt_scale_div.h" #include class QwtPlotItem::PrivateData { public: PrivateData(): plot( NULL ), isVisible( true ), attributes( 0 ), renderHints( 0 ), z( 0.0 ), xAxis( QwtPlot::xBottom ), yAxis( QwtPlot::yLeft ) { } mutable QwtPlot *plot; bool isVisible; QwtPlotItem::ItemAttributes attributes; QwtPlotItem::RenderHints renderHints; double z; int xAxis; int yAxis; QwtText title; }; /*! Constructor \param title Title of the item */ QwtPlotItem::QwtPlotItem( const QwtText &title ) { d_data = new PrivateData; d_data->title = title; } //! Destroy the QwtPlotItem QwtPlotItem::~QwtPlotItem() { attach( NULL ); delete d_data; } /*! \brief Attach the item to a plot. This method will attach a QwtPlotItem to the QwtPlot argument. It will first detach the QwtPlotItem from any plot from a previous call to attach (if necessary). If a NULL argument is passed, it will detach from any QwtPlot it was attached to. \param plot Plot widget \sa detach() */ void QwtPlotItem::attach( QwtPlot *plot ) { if ( plot == d_data->plot ) return; // remove the item from the previous plot if ( d_data->plot ) { if ( d_data->plot->legend() ) d_data->plot->legend()->remove( this ); d_data->plot->attachItem( this, false ); if ( d_data->plot->autoReplot() ) d_data->plot->update(); } d_data->plot = plot; if ( d_data->plot ) { // insert the item into the current plot d_data->plot->attachItem( this, true ); itemChanged(); } } /*! \brief This method detaches a QwtPlotItem from any QwtPlot it has been associated with. detach() is equivalent to calling attach( NULL ) \sa attach() */ void QwtPlotItem::detach() { attach( NULL ); } /*! Return rtti for the specific class represented. QwtPlotItem is simply a virtual interface class, and base classes will implement this method with specific rtti values so a user can differentiate them. The rtti value is useful for environments, where the runtime type information is disabled and it is not possible to do a dynamic_cast<...>. \return rtti value \sa RttiValues */ int QwtPlotItem::rtti() const { return Rtti_PlotItem; } //! Return attached plot QwtPlot *QwtPlotItem::plot() const { return d_data->plot; } /*! Plot items are painted in increasing z-order. \return setZ(), QwtPlotDict::itemList() */ double QwtPlotItem::z() const { return d_data->z; } /*! \brief Set the z value Plot items are painted in increasing z-order. \param z Z-value \sa z(), QwtPlotDict::itemList() */ void QwtPlotItem::setZ( double z ) { if ( d_data->z != z ) { if ( d_data->plot ) // update the z order d_data->plot->attachItem( this, false ); d_data->z = z; if ( d_data->plot ) d_data->plot->attachItem( this, true ); itemChanged(); } } /*! Set a new title \param title Title \sa title() */ void QwtPlotItem::setTitle( const QString &title ) { setTitle( QwtText( title ) ); } /*! Set a new title \param title Title \sa title() */ void QwtPlotItem::setTitle( const QwtText &title ) { if ( d_data->title != title ) { d_data->title = title; itemChanged(); } } /*! \return Title of the item \sa setTitle() */ const QwtText &QwtPlotItem::title() const { return d_data->title; } /*! Toggle an item attribute \param attribute Attribute type \param on true/false \sa testItemAttribute(), ItemAttribute */ void QwtPlotItem::setItemAttribute( ItemAttribute attribute, bool on ) { if ( bool( d_data->attributes & attribute ) != on ) { if ( on ) d_data->attributes |= attribute; else d_data->attributes &= ~attribute; itemChanged(); } } /*! Test an item attribute \param attribute Attribute type \return true/false \sa setItemAttribute(), ItemAttribute */ bool QwtPlotItem::testItemAttribute( ItemAttribute attribute ) const { return ( d_data->attributes & attribute ); } /*! Toggle an render hint \param hint Render hint \param on true/false \sa testRenderHint(), RenderHint */ void QwtPlotItem::setRenderHint( RenderHint hint, bool on ) { if ( ( ( d_data->renderHints & hint ) != 0 ) != on ) { if ( on ) d_data->renderHints |= hint; else d_data->renderHints &= ~hint; itemChanged(); } } /*! Test a render hint \param hint Render hint \return true/false \sa setRenderHint(), RenderHint */ bool QwtPlotItem::testRenderHint( RenderHint hint ) const { return ( d_data->renderHints & hint ); } //! Show the item void QwtPlotItem::show() { setVisible( true ); } //! Hide the item void QwtPlotItem::hide() { setVisible( false ); } /*! Show/Hide the item \param on Show if true, otherwise hide \sa isVisible(), show(), hide() */ void QwtPlotItem::setVisible( bool on ) { if ( on != d_data->isVisible ) { d_data->isVisible = on; itemChanged(); } } /*! \return true if visible \sa setVisible(), show(), hide() */ bool QwtPlotItem::isVisible() const { return d_data->isVisible; } /*! Update the legend and call QwtPlot::autoRefresh for the parent plot. \sa updateLegend() */ void QwtPlotItem::itemChanged() { if ( d_data->plot ) { if ( d_data->plot->legend() ) updateLegend( d_data->plot->legend() ); d_data->plot->autoRefresh(); } } /*! Set X and Y axis The item will painted according to the coordinates its Axes. \param xAxis X Axis \param yAxis Y Axis \sa setXAxis(), setYAxis(), xAxis(), yAxis() */ void QwtPlotItem::setAxes( int xAxis, int yAxis ) { if ( xAxis == QwtPlot::xBottom || xAxis == QwtPlot::xTop ) d_data->xAxis = xAxis; if ( yAxis == QwtPlot::yLeft || yAxis == QwtPlot::yRight ) d_data->yAxis = yAxis; itemChanged(); } /*! Set the X axis The item will painted according to the coordinates its Axes. \param axis X Axis \sa setAxes(), setYAxis(), xAxis() */ void QwtPlotItem::setXAxis( int axis ) { if ( axis == QwtPlot::xBottom || axis == QwtPlot::xTop ) { d_data->xAxis = axis; itemChanged(); } } /*! Set the Y axis The item will painted according to the coordinates its Axes. \param axis Y Axis \sa setAxes(), setXAxis(), yAxis() */ void QwtPlotItem::setYAxis( int axis ) { if ( axis == QwtPlot::yLeft || axis == QwtPlot::yRight ) { d_data->yAxis = axis; itemChanged(); } } //! Return xAxis int QwtPlotItem::xAxis() const { return d_data->xAxis; } //! Return yAxis int QwtPlotItem::yAxis() const { return d_data->yAxis; } /*! \return An invalid bounding rect: QRectF(1.0, 1.0, -2.0, -2.0) */ QRectF QwtPlotItem::boundingRect() const { return QRectF( 1.0, 1.0, -2.0, -2.0 ); // invalid } /*! \brief Allocate the widget that represents the item on the legend The default implementation returns a QwtLegendItem(), but an item could be represented by any type of widget, by overloading legendItem() and updateLegend(). \return QwtLegendItem() \sa updateLegend() QwtLegend() */ QWidget *QwtPlotItem::legendItem() const { QwtLegendItem *item = new QwtLegendItem; if ( d_data->plot ) { QObject::connect( item, SIGNAL( clicked() ), d_data->plot, SLOT( legendItemClicked() ) ); QObject::connect( item, SIGNAL( checked( bool ) ), d_data->plot, SLOT( legendItemChecked( bool ) ) ); } return item; } /*! \brief Update the widget that represents the item on the legend updateLegend() is called from itemChanged() to adopt the widget representing the item on the legend to its new configuration. The default implementation updates a QwtLegendItem(), but an item could be represented by any type of widget, by overloading legendItem() and updateLegend(). \param legend Legend \sa legendItem(), itemChanged(), QwtLegend() */ void QwtPlotItem::updateLegend( QwtLegend *legend ) const { if ( legend == NULL ) return; QWidget *lgdItem = legend->find( this ); if ( testItemAttribute( QwtPlotItem::Legend ) ) { if ( lgdItem == NULL ) { lgdItem = legendItem(); if ( lgdItem ) legend->insert( this, lgdItem ); } QwtLegendItem *label = qobject_cast( lgdItem ); if ( label ) { // paint the identifier const QSize sz = label->identifierSize(); QPixmap identifier( sz.width(), sz.height() ); identifier.fill( Qt::transparent ); QPainter painter( &identifier ); painter.setRenderHint( QPainter::Antialiasing, testRenderHint( QwtPlotItem::RenderAntialiased ) ); drawLegendIdentifier( &painter, QRect( 0, 0, sz.width(), sz.height() ) ); painter.end(); const bool doUpdate = label->updatesEnabled(); if ( doUpdate ) label->setUpdatesEnabled( false ); label->setText( title() ); label->setIdentifier( identifier ); label->setItemMode( legend->itemMode() ); if ( doUpdate ) label->setUpdatesEnabled( true ); label->update(); } } else { if ( lgdItem ) { lgdItem->hide(); lgdItem->deleteLater(); } } } /*! \brief Update the item to changes of the axes scale division Update the item, when the axes of plot have changed. The default implementation does nothing, but items that depend on the scale division (like QwtPlotGrid()) have to reimplement updateScaleDiv() \param xScaleDiv Scale division of the x-axis \param yScaleDiv Scale division of the y-axis \sa QwtPlot::updateAxes() */ void QwtPlotItem::updateScaleDiv( const QwtScaleDiv &xScaleDiv, const QwtScaleDiv &yScaleDiv ) { Q_UNUSED( xScaleDiv ); Q_UNUSED( yScaleDiv ); } /*! \brief Calculate the bounding scale rect of 2 maps \param xMap X map \param yMap X map \return Bounding scale rect of the scale maps, normalized */ QRectF QwtPlotItem::scaleRect( const QwtScaleMap &xMap, const QwtScaleMap &yMap ) const { return QRectF( xMap.s1(), yMap.s1(), xMap.sDist(), yMap.sDist() ); } /*! \brief Calculate the bounding paint rect of 2 maps \param xMap X map \param yMap X map \return Bounding paint rect of the scale maps, normalized */ QRectF QwtPlotItem::paintRect( const QwtScaleMap &xMap, const QwtScaleMap &yMap ) const { const QRectF rect( xMap.p1(), yMap.p1(), xMap.pDist(), yMap.pDist() ); return rect; } workbench-1.1.1/src/Qwt/qwt_plot_item.h000066400000000000000000000122661255417355300201170ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #ifndef QWT_PLOT_ITEM_H #define QWT_PLOT_ITEM_H #include "qwt_global.h" #include "qwt_legend_itemmanager.h" #include "qwt_text.h" #include class QString; class QPainter; class QWidget; class QwtPlot; class QwtLegend; class QwtScaleMap; class QwtScaleDiv; /*! \brief Base class for items on the plot canvas A plot item is "something", that can be painted on the plot canvas, or only affects the scales of the plot widget. They can be categorized as: - Representator\n A "Representator" is an item that represents some sort of data on the plot canvas. The different representator classes are organized according to the characteristics of the data: - QwtPlotMarker Represents a point or a horizontal/vertical coordinate - QwtPlotCurve Represents a series of points - QwtPlotSpectrogram ( QwtPlotRasterItem ) Represents raster data - ... - Decorators\n A "Decorator" is an item, that displays additional information, that is not related to any data: - QwtPlotGrid - QwtPlotScaleItem - QwtPlotSvgItem - ... Depending on the QwtPlotItem::ItemAttribute flags, an item is included into autoscaling or has an entry on the legnd. Before misusing the existing item classes it might be better to implement a new type of plot item ( don't implement a watermark as spectrogram ). Deriving a new type of QwtPlotItem primarily means to implement the YourPlotItem::draw() method. \sa The cpuplot example shows the implementation of additional plot items. */ class QWT_EXPORT QwtPlotItem: public QwtLegendItemManager { public: /*! \brief Runtime type information RttiValues is used to cast plot items, without having to enable runtime type information of the compiler. */ enum RttiValues { Rtti_PlotItem = 0, Rtti_PlotGrid, Rtti_PlotScale, Rtti_PlotMarker, Rtti_PlotCurve, Rtti_PlotSpectroCurve, Rtti_PlotIntervalCurve, Rtti_PlotHistogram, Rtti_PlotSpectrogram, Rtti_PlotSVG, Rtti_PlotUserItem = 1000 }; /*! Plot Item Attributes \sa setItemAttribute(), testItemAttribute() */ enum ItemAttribute { //! The item is represented on the legend. Legend = 0x01, /*! The boundingRect() of the item is included in the autoscaling calculation. */ AutoScale = 0x02 }; //! Plot Item Attributes typedef QFlags ItemAttributes; //! Render hints enum RenderHint { //! Enable antialiasing RenderAntialiased = 1 }; //! Render hints typedef QFlags RenderHints; explicit QwtPlotItem( const QwtText &title = QwtText() ); virtual ~QwtPlotItem(); void attach( QwtPlot *plot ); void detach(); QwtPlot *plot() const; void setTitle( const QString &title ); void setTitle( const QwtText &title ); const QwtText &title() const; virtual int rtti() const; void setItemAttribute( ItemAttribute, bool on = true ); bool testItemAttribute( ItemAttribute ) const; void setRenderHint( RenderHint, bool on = true ); bool testRenderHint( RenderHint ) const; double z() const; void setZ( double z ); void show(); void hide(); virtual void setVisible( bool ); bool isVisible () const; void setAxes( int xAxis, int yAxis ); void setXAxis( int axis ); int xAxis() const; void setYAxis( int axis ); int yAxis() const; virtual void itemChanged(); /*! \brief Draw the item \param painter Painter \param xMap Maps x-values into pixel coordinates. \param yMap Maps y-values into pixel coordinates. \param canvasRect Contents rect of the canvas in painter coordinates */ virtual void draw( QPainter *painter, const QwtScaleMap &xMap, const QwtScaleMap &yMap, const QRectF &canvasRect ) const = 0; virtual QRectF boundingRect() const; virtual void updateLegend( QwtLegend * ) const; virtual void updateScaleDiv( const QwtScaleDiv&, const QwtScaleDiv& ); virtual QWidget *legendItem() const; QRectF scaleRect( const QwtScaleMap &, const QwtScaleMap & ) const; QRectF paintRect( const QwtScaleMap &, const QwtScaleMap & ) const; private: // Disabled copy constructor and operator= QwtPlotItem( const QwtPlotItem & ); QwtPlotItem &operator=( const QwtPlotItem & ); class PrivateData; PrivateData *d_data; }; Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotItem::ItemAttributes ) Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotItem::RenderHints ) #endif workbench-1.1.1/src/Qwt/qwt_plot_layout.cpp000066400000000000000000001144561255417355300210350ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #include "qwt_plot_layout.h" #include "qwt_text.h" #include "qwt_text_label.h" #include "qwt_plot_canvas.h" #include "qwt_scale_widget.h" #include "qwt_legend.h" #include #include class QwtPlotLayout::LayoutData { public: void init( const QwtPlot *, const QRectF &rect ); struct t_legendData { int frameWidth; int vScrollBarWidth; int hScrollBarHeight; QSize hint; } legend; struct t_titleData { QwtText text; int frameWidth; } title; struct t_scaleData { bool isEnabled; const QwtScaleWidget *scaleWidget; QFont scaleFont; int start; int end; int baseLineOffset; int tickOffset; int dimWithoutTitle; } scale[QwtPlot::axisCnt]; struct t_canvasData { int frameWidth; } canvas; }; /* Extract all layout relevant data from the plot components */ void QwtPlotLayout::LayoutData::init( const QwtPlot *plot, const QRectF &rect ) { // legend if ( plot->plotLayout()->legendPosition() != QwtPlot::ExternalLegend && plot->legend() ) { legend.frameWidth = plot->legend()->frameWidth(); legend.vScrollBarWidth = plot->legend()->verticalScrollBar()->sizeHint().width(); legend.hScrollBarHeight = plot->legend()->horizontalScrollBar()->sizeHint().height(); const QSize hint = plot->legend()->sizeHint(); int w = qMin( hint.width(), qFloor( rect.width() ) ); int h = plot->legend()->heightForWidth( w ); if ( h == 0 ) h = hint.height(); if ( h > rect.height() ) w += legend.vScrollBarWidth; legend.hint = QSize( w, h ); } // title title.frameWidth = 0; title.text = QwtText(); if ( plot->titleLabel() ) { const QwtTextLabel *label = plot->titleLabel(); title.text = label->text(); if ( !( title.text.testPaintAttribute( QwtText::PaintUsingTextFont ) ) ) title.text.setFont( label->font() ); title.frameWidth = plot->titleLabel()->frameWidth(); } // scales for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ ) { if ( plot->axisEnabled( axis ) ) { const QwtScaleWidget *scaleWidget = plot->axisWidget( axis ); scale[axis].isEnabled = true; scale[axis].scaleWidget = scaleWidget; scale[axis].scaleFont = scaleWidget->font(); scale[axis].start = scaleWidget->startBorderDist(); scale[axis].end = scaleWidget->endBorderDist(); scale[axis].baseLineOffset = scaleWidget->margin(); scale[axis].tickOffset = scaleWidget->margin(); if ( scaleWidget->scaleDraw()->hasComponent( QwtAbstractScaleDraw::Ticks ) ) { scale[axis].tickOffset += scaleWidget->scaleDraw()->maxTickLength(); } scale[axis].dimWithoutTitle = scaleWidget->dimForLength( QWIDGETSIZE_MAX, scale[axis].scaleFont ); if ( !scaleWidget->title().isEmpty() ) { scale[axis].dimWithoutTitle -= scaleWidget->titleHeightForWidth( QWIDGETSIZE_MAX ); } } else { scale[axis].isEnabled = false; scale[axis].start = 0; scale[axis].end = 0; scale[axis].baseLineOffset = 0; scale[axis].tickOffset = 0; scale[axis].dimWithoutTitle = 0; } } // canvas canvas.frameWidth = plot->canvas()->frameWidth(); } class QwtPlotLayout::PrivateData { public: PrivateData(): spacing( 5 ), alignCanvasToScales( false ) { } QRectF titleRect; QRectF legendRect; QRectF scaleRect[QwtPlot::axisCnt]; QRectF canvasRect; QwtPlotLayout::LayoutData layoutData; QwtPlot::LegendPosition legendPos; double legendRatio; unsigned int spacing; unsigned int canvasMargin[QwtPlot::axisCnt]; bool alignCanvasToScales; }; /*! \brief Constructor */ QwtPlotLayout::QwtPlotLayout() { d_data = new PrivateData; setLegendPosition( QwtPlot::BottomLegend ); setCanvasMargin( 4 ); invalidate(); } //! Destructor QwtPlotLayout::~QwtPlotLayout() { delete d_data; } /*! Change a margin of the canvas. The margin is the space above/below the scale ticks. A negative margin will be set to -1, excluding the borders of the scales. \param margin New margin \param axis One of QwtPlot::Axis. Specifies where the position of the margin. -1 means margin at all borders. \sa canvasMargin() \warning The margin will have no effect when alignCanvasToScales is true */ void QwtPlotLayout::setCanvasMargin( int margin, int axis ) { if ( margin < -1 ) margin = -1; if ( axis == -1 ) { for ( axis = 0; axis < QwtPlot::axisCnt; axis++ ) d_data->canvasMargin[axis] = margin; } else if ( axis >= 0 && axis < QwtPlot::axisCnt ) d_data->canvasMargin[axis] = margin; } /*! \return Margin around the scale tick borders \sa setCanvasMargin() */ int QwtPlotLayout::canvasMargin( int axis ) const { if ( axis < 0 || axis >= QwtPlot::axisCnt ) return 0; return d_data->canvasMargin[axis]; } /*! Change the align-canvas-to-axis-scales setting. The canvas may: - extend beyond the axis scale ends to maximize its size, - align with the axis scale ends to control its size. \param alignCanvasToScales New align-canvas-to-axis-scales setting \sa setCanvasMargin() \note In this context the term 'scale' means the backbone of a scale. \warning In case of alignCanvasToScales == true canvasMargin will have no effect */ void QwtPlotLayout::setAlignCanvasToScales( bool alignCanvasToScales ) { d_data->alignCanvasToScales = alignCanvasToScales; } /*! Return the align-canvas-to-axis-scales setting. The canvas may: - extend beyond the axis scale ends to maximize its size - align with the axis scale ends to control its size. \return align-canvas-to-axis-scales setting \sa setAlignCanvasToScales, setCanvasMargin() \note In this context the term 'scale' means the backbone of a scale. */ bool QwtPlotLayout::alignCanvasToScales() const { return d_data->alignCanvasToScales; } /*! Change the spacing of the plot. The spacing is the distance between the plot components. \param spacing new spacing \sa setMargin(), spacing() */ void QwtPlotLayout::setSpacing( int spacing ) { d_data->spacing = qMax( 0, spacing ); } /*! \return spacing \sa margin(), setSpacing() */ int QwtPlotLayout::spacing() const { return d_data->spacing; } /*! \brief Specify the position of the legend \param pos The legend's position. \param ratio Ratio between legend and the bounding rect of title, canvas and axes. The legend will be shrinked if it would need more space than the given ratio. The ratio is limited to ]0.0 .. 1.0]. In case of <= 0.0 it will be reset to the default ratio. The default vertical/horizontal ratio is 0.33/0.5. \sa QwtPlot::setLegendPosition() */ void QwtPlotLayout::setLegendPosition( QwtPlot::LegendPosition pos, double ratio ) { if ( ratio > 1.0 ) ratio = 1.0; switch ( pos ) { case QwtPlot::TopLegend: case QwtPlot::BottomLegend: if ( ratio <= 0.0 ) ratio = 0.33; d_data->legendRatio = ratio; d_data->legendPos = pos; break; case QwtPlot::LeftLegend: case QwtPlot::RightLegend: if ( ratio <= 0.0 ) ratio = 0.5; d_data->legendRatio = ratio; d_data->legendPos = pos; break; case QwtPlot::ExternalLegend: d_data->legendRatio = ratio; // meaningless d_data->legendPos = pos; default: break; } } /*! \brief Specify the position of the legend \param pos The legend's position. Valid values are \c QwtPlot::LeftLegend, \c QwtPlot::RightLegend, \c QwtPlot::TopLegend, \c QwtPlot::BottomLegend. \sa QwtPlot::setLegendPosition() */ void QwtPlotLayout::setLegendPosition( QwtPlot::LegendPosition pos ) { setLegendPosition( pos, 0.0 ); } /*! \return Position of the legend \sa setLegendPosition(), QwtPlot::setLegendPosition(), QwtPlot::legendPosition() */ QwtPlot::LegendPosition QwtPlotLayout::legendPosition() const { return d_data->legendPos; } /*! Specify the relative size of the legend in the plot \param ratio Ratio between legend and the bounding rect of title, canvas and axes. The legend will be shrinked if it would need more space than the given ratio. The ratio is limited to ]0.0 .. 1.0]. In case of <= 0.0 it will be reset to the default ratio. The default vertical/horizontal ratio is 0.33/0.5. */ void QwtPlotLayout::setLegendRatio( double ratio ) { setLegendPosition( legendPosition(), ratio ); } /*! \return The relative size of the legend in the plot. \sa setLegendPosition() */ double QwtPlotLayout::legendRatio() const { return d_data->legendRatio; } /*! \return Geometry for the title \sa activate(), invalidate() */ const QRectF &QwtPlotLayout::titleRect() const { return d_data->titleRect; } /*! \return Geometry for the legend \sa activate(), invalidate() */ const QRectF &QwtPlotLayout::legendRect() const { return d_data->legendRect; } /*! \param axis Axis index \return Geometry for the scale \sa activate(), invalidate() */ const QRectF &QwtPlotLayout::scaleRect( int axis ) const { if ( axis < 0 || axis >= QwtPlot::axisCnt ) { static QRectF dummyRect; return dummyRect; } return d_data->scaleRect[axis]; } /*! \return Geometry for the canvas \sa activate(), invalidate() */ const QRectF &QwtPlotLayout::canvasRect() const { return d_data->canvasRect; } /*! Invalidate the geometry of all components. \sa activate() */ void QwtPlotLayout::invalidate() { d_data->titleRect = d_data->legendRect = d_data->canvasRect = QRect(); for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ ) d_data->scaleRect[axis] = QRect(); } /*! \brief Return a minimum size hint \sa QwtPlot::minimumSizeHint() */ QSize QwtPlotLayout::minimumSizeHint( const QwtPlot *plot ) const { class ScaleData { public: ScaleData() { w = h = minLeft = minRight = tickOffset = 0; } int w; int h; int minLeft; int minRight; int tickOffset; } scaleData[QwtPlot::axisCnt]; int canvasBorder[QwtPlot::axisCnt]; int axis; for ( axis = 0; axis < QwtPlot::axisCnt; axis++ ) { if ( plot->axisEnabled( axis ) ) { const QwtScaleWidget *scl = plot->axisWidget( axis ); ScaleData &sd = scaleData[axis]; const QSize hint = scl->minimumSizeHint(); sd.w = hint.width(); sd.h = hint.height(); scl->getBorderDistHint( sd.minLeft, sd.minRight ); sd.tickOffset = scl->margin(); if ( scl->scaleDraw()->hasComponent( QwtAbstractScaleDraw::Ticks ) ) sd.tickOffset += scl->scaleDraw()->maxTickLength(); } canvasBorder[axis] = plot->canvas()->frameWidth() + d_data->canvasMargin[axis] + 1; } for ( axis = 0; axis < QwtPlot::axisCnt; axis++ ) { ScaleData &sd = scaleData[axis]; if ( sd.w && ( axis == QwtPlot::xBottom || axis == QwtPlot::xTop ) ) { if ( ( sd.minLeft > canvasBorder[QwtPlot::yLeft] ) && scaleData[QwtPlot::yLeft].w ) { int shiftLeft = sd.minLeft - canvasBorder[QwtPlot::yLeft]; if ( shiftLeft > scaleData[QwtPlot::yLeft].w ) shiftLeft = scaleData[QwtPlot::yLeft].w; sd.w -= shiftLeft; } if ( ( sd.minRight > canvasBorder[QwtPlot::yRight] ) && scaleData[QwtPlot::yRight].w ) { int shiftRight = sd.minRight - canvasBorder[QwtPlot::yRight]; if ( shiftRight > scaleData[QwtPlot::yRight].w ) shiftRight = scaleData[QwtPlot::yRight].w; sd.w -= shiftRight; } } if ( sd.h && ( axis == QwtPlot::yLeft || axis == QwtPlot::yRight ) ) { if ( ( sd.minLeft > canvasBorder[QwtPlot::xBottom] ) && scaleData[QwtPlot::xBottom].h ) { int shiftBottom = sd.minLeft - canvasBorder[QwtPlot::xBottom]; if ( shiftBottom > scaleData[QwtPlot::xBottom].tickOffset ) shiftBottom = scaleData[QwtPlot::xBottom].tickOffset; sd.h -= shiftBottom; } if ( ( sd.minLeft > canvasBorder[QwtPlot::xTop] ) && scaleData[QwtPlot::xTop].h ) { int shiftTop = sd.minRight - canvasBorder[QwtPlot::xTop]; if ( shiftTop > scaleData[QwtPlot::xTop].tickOffset ) shiftTop = scaleData[QwtPlot::xTop].tickOffset; sd.h -= shiftTop; } } } const QwtPlotCanvas *canvas = plot->canvas(); const QSize minCanvasSize = canvas->minimumSize(); int w = scaleData[QwtPlot::yLeft].w + scaleData[QwtPlot::yRight].w; int cw = qMax( scaleData[QwtPlot::xBottom].w, scaleData[QwtPlot::xTop].w ) + 2 * ( canvas->frameWidth() + 1 ); w += qMax( cw, minCanvasSize.width() ); int h = scaleData[QwtPlot::xBottom].h + scaleData[QwtPlot::xTop].h; int ch = qMax( scaleData[QwtPlot::yLeft].h, scaleData[QwtPlot::yRight].h ) + 2 * ( canvas->frameWidth() + 1 ); h += qMax( ch, minCanvasSize.height() ); const QwtTextLabel *title = plot->titleLabel(); if ( title && !title->text().isEmpty() ) { // If only QwtPlot::yLeft or QwtPlot::yRight is showing, // we center on the plot canvas. const bool centerOnCanvas = !( plot->axisEnabled( QwtPlot::yLeft ) && plot->axisEnabled( QwtPlot::yRight ) ); int titleW = w; if ( centerOnCanvas ) { titleW -= scaleData[QwtPlot::yLeft].w + scaleData[QwtPlot::yRight].w; } int titleH = title->heightForWidth( titleW ); if ( titleH > titleW ) // Compensate for a long title { w = titleW = titleH; if ( centerOnCanvas ) { w += scaleData[QwtPlot::yLeft].w + scaleData[QwtPlot::yRight].w; } titleH = title->heightForWidth( titleW ); } h += titleH + d_data->spacing; } // Compute the legend contribution const QwtLegend *legend = plot->legend(); if ( d_data->legendPos != QwtPlot::ExternalLegend && legend && !legend->isEmpty() ) { if ( d_data->legendPos == QwtPlot::LeftLegend || d_data->legendPos == QwtPlot::RightLegend ) { int legendW = legend->sizeHint().width(); int legendH = legend->heightForWidth( legendW ); if ( legend->frameWidth() > 0 ) w += d_data->spacing; if ( legendH > h ) legendW += legend->verticalScrollBar()->sizeHint().height(); if ( d_data->legendRatio < 1.0 ) legendW = qMin( legendW, int( w / ( 1.0 - d_data->legendRatio ) ) ); w += legendW + d_data->spacing; } else // QwtPlot::Top, QwtPlot::Bottom { int legendW = qMin( legend->sizeHint().width(), w ); int legendH = legend->heightForWidth( legendW ); if ( legend->frameWidth() > 0 ) h += d_data->spacing; if ( d_data->legendRatio < 1.0 ) legendH = qMin( legendH, int( h / ( 1.0 - d_data->legendRatio ) ) ); h += legendH + d_data->spacing; } } return QSize( w, h ); } /*! Find the geometry for the legend \param options Options how to layout the legend \param rect Rectangle where to place the legend \return Geometry for the legend \sa Options */ QRectF QwtPlotLayout::layoutLegend( Options options, const QRectF &rect ) const { const QSize hint( d_data->layoutData.legend.hint ); int dim; if ( d_data->legendPos == QwtPlot::LeftLegend || d_data->legendPos == QwtPlot::RightLegend ) { // We don't allow vertical legends to take more than // half of the available space. dim = qMin( hint.width(), int( rect.width() * d_data->legendRatio ) ); if ( !( options & IgnoreScrollbars ) ) { if ( hint.height() > rect.height() ) { // The legend will need additional // space for the vertical scrollbar. dim += d_data->layoutData.legend.vScrollBarWidth; } } } else { dim = qMin( hint.height(), int( rect.height() * d_data->legendRatio ) ); dim = qMax( dim, d_data->layoutData.legend.hScrollBarHeight ); } QRectF legendRect = rect; switch ( d_data->legendPos ) { case QwtPlot::LeftLegend: legendRect.setWidth( dim ); break; case QwtPlot::RightLegend: legendRect.setX( rect.right() - dim ); legendRect.setWidth( dim ); break; case QwtPlot::TopLegend: legendRect.setHeight( dim ); break; case QwtPlot::BottomLegend: legendRect.setY( rect.bottom() - dim ); legendRect.setHeight( dim ); break; case QwtPlot::ExternalLegend: break; } return legendRect; } /*! Align the legend to the canvas \param canvasRect Geometry of the canvas \param legendRect Maximum geometry for the legend \return Geometry for the aligned legend */ QRectF QwtPlotLayout::alignLegend( const QRectF &canvasRect, const QRectF &legendRect ) const { QRectF alignedRect = legendRect; if ( d_data->legendPos == QwtPlot::BottomLegend || d_data->legendPos == QwtPlot::TopLegend ) { if ( d_data->layoutData.legend.hint.width() < canvasRect.width() ) { alignedRect.setX( canvasRect.x() ); alignedRect.setWidth( canvasRect.width() ); } } else { if ( d_data->layoutData.legend.hint.height() < canvasRect.height() ) { alignedRect.setY( canvasRect.y() ); alignedRect.setHeight( canvasRect.height() ); } } return alignedRect; } /*! Expand all line breaks in text labels, and calculate the height of their widgets in orientation of the text. \param options Options how to layout the legend \param rect Bounding rect for title, axes and canvas. \param dimTitle Expanded height of the title widget \param dimAxis Expanded heights of the axis in axis orientation. \sa Options */ void QwtPlotLayout::expandLineBreaks( int options, const QRectF &rect, int &dimTitle, int dimAxis[QwtPlot::axisCnt] ) const { dimTitle = 0; for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ ) dimAxis[axis] = 0; int backboneOffset[QwtPlot::axisCnt]; for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ ) { backboneOffset[axis] = 0; if ( !d_data->alignCanvasToScales ) backboneOffset[axis] += d_data->canvasMargin[axis]; if ( !( options & IgnoreFrames ) ) backboneOffset[axis] += d_data->layoutData.canvas.frameWidth; } bool done = false; while ( !done ) { done = true; // the size for the 4 axis depend on each other. Expanding // the height of a horizontal axis will shrink the height // for the vertical axis, shrinking the height of a vertical // axis will result in a line break what will expand the // width and results in shrinking the width of a horizontal // axis what might result in a line break of a horizontal // axis ... . So we loop as long until no size changes. if ( !d_data->layoutData.title.text.isEmpty() ) { int w = rect.width(); if ( d_data->layoutData.scale[QwtPlot::yLeft].isEnabled != d_data->layoutData.scale[QwtPlot::yRight].isEnabled ) { // center to the canvas w -= dimAxis[QwtPlot::yLeft] + dimAxis[QwtPlot::yRight]; } int d = qCeil( d_data->layoutData.title.text.heightForWidth( w ) ); if ( !( options & IgnoreFrames ) ) d += 2 * d_data->layoutData.title.frameWidth; if ( d > dimTitle ) { dimTitle = d; done = false; } } for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ ) { const struct LayoutData::t_scaleData &scaleData = d_data->layoutData.scale[axis]; if ( scaleData.isEnabled ) { int length; if ( axis == QwtPlot::xTop || axis == QwtPlot::xBottom ) { length = rect.width() - dimAxis[QwtPlot::yLeft] - dimAxis[QwtPlot::yRight]; length -= scaleData.start + scaleData.end; if ( dimAxis[QwtPlot::yRight] > 0 ) length -= 1; length += qMin( dimAxis[QwtPlot::yLeft], scaleData.start - backboneOffset[QwtPlot::yLeft] ); length += qMin( dimAxis[QwtPlot::yRight], scaleData.end - backboneOffset[QwtPlot::yRight] ); } else // QwtPlot::yLeft, QwtPlot::yRight { length = rect.height() - dimAxis[QwtPlot::xTop] - dimAxis[QwtPlot::xBottom]; length -= scaleData.start + scaleData.end; length -= 1; if ( dimAxis[QwtPlot::xBottom] <= 0 ) length -= 1; if ( dimAxis[QwtPlot::xTop] <= 0 ) length -= 1; if ( dimAxis[QwtPlot::xBottom] > 0 ) { length += qMin( d_data->layoutData.scale[QwtPlot::xBottom].tickOffset, scaleData.start - backboneOffset[QwtPlot::xBottom] ); } if ( dimAxis[QwtPlot::xTop] > 0 ) { length += qMin( d_data->layoutData.scale[QwtPlot::xTop].tickOffset, scaleData.end - backboneOffset[QwtPlot::xTop] ); } if ( dimTitle > 0 ) length -= dimTitle + d_data->spacing; } int d = scaleData.dimWithoutTitle; if ( !scaleData.scaleWidget->title().isEmpty() ) { d += scaleData.scaleWidget->titleHeightForWidth( length ); } if ( d > dimAxis[axis] ) { dimAxis[axis] = d; done = false; } } } } } /*! Align the ticks of the axis to the canvas borders using the empty corners. \sa Options */ void QwtPlotLayout::alignScales( int options, QRectF &canvasRect, QRectF scaleRect[QwtPlot::axisCnt] ) const { int backboneOffset[QwtPlot::axisCnt]; for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ ) { backboneOffset[axis] = 0; if ( !d_data->alignCanvasToScales ) backboneOffset[axis] += d_data->canvasMargin[axis]; if ( !( options & IgnoreFrames ) ) backboneOffset[axis] += d_data->layoutData.canvas.frameWidth; } for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ ) { if ( !scaleRect[axis].isValid() ) continue; const int startDist = d_data->layoutData.scale[axis].start; const int endDist = d_data->layoutData.scale[axis].end; QRectF &axisRect = scaleRect[axis]; if ( axis == QwtPlot::xTop || axis == QwtPlot::xBottom ) { const QRectF &leftScaleRect = scaleRect[QwtPlot::yLeft]; const int leftOffset = backboneOffset[QwtPlot::yLeft] - startDist; if ( leftScaleRect.isValid() ) { const int dx = leftOffset + leftScaleRect.width(); if ( d_data->alignCanvasToScales && dx < 0 ) { /* The axis needs more space than the width of the left scale. */ canvasRect.setLeft( qMax( canvasRect.left(), axisRect.left() - dx ) ); } else { const double minLeft = leftScaleRect.left(); const double left = axisRect.left() + leftOffset; axisRect.setLeft( qMax( left, minLeft ) ); } } else { if ( d_data->alignCanvasToScales && leftOffset < 0 ) { canvasRect.setLeft( qMax( canvasRect.left(), axisRect.left() - leftOffset ) ); } else { if ( leftOffset > 0 ) axisRect.setLeft( axisRect.left() + leftOffset ); } } const QRectF &rightScaleRect = scaleRect[QwtPlot::yRight]; const int rightOffset = backboneOffset[QwtPlot::yRight] - endDist + 1; if ( rightScaleRect.isValid() ) { const int dx = rightOffset + rightScaleRect.width(); if ( d_data->alignCanvasToScales && dx < 0 ) { /* The axis needs more space than the width of the right scale. */ canvasRect.setRight( qMin( canvasRect.right(), axisRect.right() + dx ) ); } const double maxRight = rightScaleRect.right(); const double right = axisRect.right() - rightOffset; axisRect.setRight( qMin( right, maxRight ) ); } else { if ( d_data->alignCanvasToScales && rightOffset < 0 ) { canvasRect.setRight( qMin( canvasRect.right(), axisRect.right() + rightOffset ) ); } else { if ( rightOffset > 0 ) axisRect.setRight( axisRect.right() - rightOffset ); } } } else // QwtPlot::yLeft, QwtPlot::yRight { const QRectF &bottomScaleRect = scaleRect[QwtPlot::xBottom]; const int bottomOffset = backboneOffset[QwtPlot::xBottom] - endDist + 1; if ( bottomScaleRect.isValid() ) { const int dy = bottomOffset + bottomScaleRect.height(); if ( d_data->alignCanvasToScales && dy < 0 ) { /* The axis needs more space than the height of the bottom scale. */ canvasRect.setBottom( qMin( canvasRect.bottom(), axisRect.bottom() + dy ) ); } else { const double maxBottom = bottomScaleRect.top() + d_data->layoutData.scale[QwtPlot::xBottom].tickOffset; const double bottom = axisRect.bottom() - bottomOffset; axisRect.setBottom( qMin( bottom, maxBottom ) ); } } else { if ( d_data->alignCanvasToScales && bottomOffset < 0 ) { canvasRect.setBottom( qMin( canvasRect.bottom(), axisRect.bottom() + bottomOffset ) ); } else { if ( bottomOffset > 0 ) axisRect.setBottom( axisRect.bottom() - bottomOffset ); } } const QRectF &topScaleRect = scaleRect[QwtPlot::xTop]; const int topOffset = backboneOffset[QwtPlot::xTop] - startDist; if ( topScaleRect.isValid() ) { const int dy = topOffset + topScaleRect.height(); if ( d_data->alignCanvasToScales && dy < 0 ) { /* The axis needs more space than the height of the top scale. */ canvasRect.setTop( qMax( canvasRect.top(), axisRect.top() - dy ) ); } else { const double minTop = topScaleRect.bottom() - d_data->layoutData.scale[QwtPlot::xTop].tickOffset; const double top = axisRect.top() + topOffset; axisRect.setTop( qMax( top, minTop ) ); } } else { if ( d_data->alignCanvasToScales && topOffset < 0 ) { canvasRect.setTop( qMax( canvasRect.top(), axisRect.top() - topOffset ) ); } else { if ( topOffset > 0 ) axisRect.setTop( axisRect.top() + topOffset ); } } } } if ( d_data->alignCanvasToScales ) { /* The canvas has been aligned to the scale with largest border distances. Now we have to realign the other scale. */ int fw = 0; if ( !( options & IgnoreFrames ) ) fw = d_data->layoutData.canvas.frameWidth; for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ ) { if ( !scaleRect[axis].isValid() ) continue; if ( axis == QwtPlot::xBottom || axis == QwtPlot::xTop ) { scaleRect[axis].setLeft( canvasRect.left() + fw - d_data->layoutData.scale[axis].start ); scaleRect[axis].setRight( canvasRect.right() - fw - 1 + d_data->layoutData.scale[axis].end ); } else { scaleRect[axis].setTop( canvasRect.top() + fw - d_data->layoutData.scale[axis].start ); scaleRect[axis].setBottom( canvasRect.bottom() - fw - 1 + d_data->layoutData.scale[axis].end ); } } if ( scaleRect[QwtPlot::xTop].isValid() ) scaleRect[QwtPlot::xTop].setBottom( canvasRect.top() ); if ( scaleRect[QwtPlot::xBottom].isValid() ) scaleRect[QwtPlot::xBottom].setTop( canvasRect.bottom() ); if ( scaleRect[QwtPlot::yLeft].isValid() ) scaleRect[QwtPlot::yLeft].setRight( canvasRect.left() ); if ( scaleRect[QwtPlot::yRight].isValid() ) scaleRect[QwtPlot::yRight].setLeft( canvasRect.right() ); } } /*! \brief Recalculate the geometry of all components. \param plot Plot to be layout \param plotRect Rect where to place the components \param options Layout options \sa invalidate(), titleRect(), legendRect(), scaleRect(), canvasRect() */ void QwtPlotLayout::activate( const QwtPlot *plot, const QRectF &plotRect, Options options ) { invalidate(); QRectF rect( plotRect ); // undistributed rest of the plot rect // We extract all layout relevant data from the widgets, // filter them through pfilter and save them to d_data->layoutData. d_data->layoutData.init( plot, rect ); if ( !( options & IgnoreLegend ) && d_data->legendPos != QwtPlot::ExternalLegend && plot->legend() && !plot->legend()->isEmpty() ) { d_data->legendRect = layoutLegend( options, rect ); // subtract d_data->legendRect from rect const QRegion region( rect.toRect() ); rect = region.subtract( d_data->legendRect.toRect() ).boundingRect(); switch ( d_data->legendPos ) { case QwtPlot::LeftLegend: rect.setLeft( rect.left() + d_data->spacing ); break; case QwtPlot::RightLegend: rect.setRight( rect.right() - d_data->spacing ); break; case QwtPlot::TopLegend: rect.setTop( rect.top() + d_data->spacing ); break; case QwtPlot::BottomLegend: rect.setBottom( rect.bottom() - d_data->spacing ); break; case QwtPlot::ExternalLegend: break; // suppress compiler warning } } /* +---+-----------+---+ | Title | +---+-----------+---+ | | Axis | | +---+-----------+---+ | A | | A | | x | Canvas | x | | i | | i | | s | | s | +---+-----------+---+ | | Axis | | +---+-----------+---+ */ // axes and title include text labels. The height of each // label depends on its line breaks, that depend on the width // for the label. A line break in a horizontal text will reduce // the available width for vertical texts and vice versa. // expandLineBreaks finds the height/width for title and axes // including all line breaks. int dimTitle, dimAxes[QwtPlot::axisCnt]; expandLineBreaks( options, rect, dimTitle, dimAxes ); if ( dimTitle > 0 ) { d_data->titleRect = QRect( rect.x(), rect.y(), rect.width(), dimTitle ); if ( d_data->layoutData.scale[QwtPlot::yLeft].isEnabled != d_data->layoutData.scale[QwtPlot::yRight].isEnabled ) { // if only one of the y axes is missing we align // the title centered to the canvas d_data->titleRect.setX( rect.x() + dimAxes[QwtPlot::yLeft] ); d_data->titleRect.setWidth( rect.width() - dimAxes[QwtPlot::yLeft] - dimAxes[QwtPlot::yRight] ); } // subtract title rect.setTop( rect.top() + dimTitle + d_data->spacing ); } d_data->canvasRect.setRect( rect.x() + dimAxes[QwtPlot::yLeft], rect.y() + dimAxes[QwtPlot::xTop], rect.width() - dimAxes[QwtPlot::yRight] - dimAxes[QwtPlot::yLeft], rect.height() - dimAxes[QwtPlot::xBottom] - dimAxes[QwtPlot::xTop] ); for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ ) { // set the rects for the axes if ( dimAxes[axis] ) { int dim = dimAxes[axis]; QRectF &scaleRect = d_data->scaleRect[axis]; scaleRect = d_data->canvasRect; switch ( axis ) { case QwtPlot::yLeft: scaleRect.setX( d_data->canvasRect.left() - dim ); scaleRect.setWidth( dim ); break; case QwtPlot::yRight: scaleRect.setX( d_data->canvasRect.right() ); scaleRect.setWidth( dim ); break; case QwtPlot::xBottom: scaleRect.setY( d_data->canvasRect.bottom() ); scaleRect.setHeight( dim ); break; case QwtPlot::xTop: scaleRect.setY( d_data->canvasRect.top() - dim ); scaleRect.setHeight( dim ); break; } scaleRect = scaleRect.normalized(); } } // +---+-----------+---+ // | <- Axis -> | // +-^-+-----------+-^-+ // | | | | | | // | | | | // | A | | A | // | x | Canvas | x | // | i | | i | // | s | | s | // | | | | // | | | | | | // +-V-+-----------+-V-+ // | <- Axis -> | // +---+-----------+---+ // The ticks of the axes - not the labels above - should // be aligned to the canvas. So we try to use the empty // corners to extend the axes, so that the label texts // left/right of the min/max ticks are moved into them. alignScales( options, d_data->canvasRect, d_data->scaleRect ); if ( !d_data->legendRect.isEmpty() ) { // We prefer to align the legend to the canvas - not to // the complete plot - if possible. d_data->legendRect = alignLegend( d_data->canvasRect, d_data->legendRect ); } } workbench-1.1.1/src/Qwt/qwt_plot_layout.h000066400000000000000000000054571255417355300205020ustar00rootroot00000000000000/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** * Qwt Widget Library * Copyright (C) 1997 Josef Wilgen * Copyright (C) 2002 Uwe Rathmann * * This library is free software; you can redistribute it and/or * modify it under the terms of the Qwt License, Version 1.0 *****************************************************************************/ #ifndef QWT_PLOT_LAYOUT_H #define QWT_PLOT_LAYOUT_H #include "qwt_global.h" #include "qwt_plot.h" /*! \brief Layout engine for QwtPlot. It is used by the QwtPlot widget to organize its internal widgets or by QwtPlot::print() to render its content to a QPaintDevice like a QPrinter, QPixmap/QImage or QSvgRenderer. */ class QWT_EXPORT QwtPlotLayout { public: /*! Options to configure the plot layout engine \sa activate(), QwtPlotRenderer */ enum Option { //! Unused AlignScales = 0x01, /*! Ignore the dimension of the scrollbars. There are no scrollbars, when the plot is not rendered to widgets. */ IgnoreScrollbars = 0x02, //! Ignore all frames. IgnoreFrames = 0x04, //! Ignore the legend. IgnoreLegend = 0x08 }; //! Layout options typedef QFlags
12Y;u!#|Q6d.b-Ucc(.";u@I!9O 4 bqe> +n3uǼz79wleT5".b?n+!K|ٟaQ/9,Aieqo?NUWUoNG/H%lwft{z78-fnH7@8 ؿ7T1? tcqLfnK7)s x!%_4ȯ,NQN7tT4֩s'*7qn=N,q{]}=Z3=FƋuPV;PU9芍(43)N ?0RʄE%XZ.'3Z/(2V,_.?$=@?F@9 J On\ʧرc+SeTG꛲H9JwSu<:'`&][׷ϱ:yifXbhv#[CaB8ųXx'u[`;6N;ŗ\rül~:&'VPSoUWabb>He(oN*;%rB)i j5ǐ^.ׇb1u$ _zF-]E7; pXA,P>^rׯڄ뒣YGFDr¸ԕ=]}l+yu\1hFM!u;WO=ZOLD v:C8ԉ[b?o_PʜN+KBUҙm6(7-r ^(ױcOH?'xQ`S_~0  .NX-iIA)0wߝ6w{;!rk+>˜[E!8x8* K(sz[VneQi{ )‰by}]DsP[FFi54|p̝ȃXЋ/HQQ~N:Lbor nf6A̬LtaT Ƿn!CE (@ "-Q1 FtbrwA%a9t[; q*Xtۨ oL[CƥX8ۆ̀nÚES۫.*I'oqzRk{$uj?-`o8{E%tEdq$[N|gl/si}EDvim䮷9Z޽DaTu`EoL40^s?#=ͲMBG۶ QTT\IEc F%>:Ď>k /ڄ'wfI6F@|(+Tˇf/c6YR=ҤgFR^H#B×ZtfW>zqؾ{a'pOZ}u`;P3}<Xo<گ z7+dLu0c/QR.4.0 .S;MMλLVdCMw8BeߊrX߾}z\*x4E]xTCWazdM: ͧ?N;A&9ŀ/?O:('}f BdqqB}Y}l ۂ\@JxFK Xy⸇In֡@g}ƃCy0aK*8+$>έ jxOxnxHe`8{2eJP-{|,Q?^y~HgF~foc1;co`|&60< Px<ڇ.O-6ySmOc@G&7'4'?w;xg96̽-p|׎r8|pqd1m1}a.++*X-qD㱿/9{ݞ]{i;xCǻ?p 6;< ?;6m`;p1?1&YvǮuyvC<~*Q~B]ec\]|b|3gVv'qPN#Y6//q]Recc \\#O?]{6"]mKW͛wZۗsiw>ʠ?ހ_cB4G#}r"PW?gcL6Ա |gqg$v%ն1@ߙlHácϯ8ʴe:<%6Zy$n +88wK4d3MG%({yb9!,"("|%TQ~TuÇ iGӝ١rt8Syy<6 0bT'%}wB/'Dž2uŭr5i^}<C#—X^u ˬF=c'}6wnT8VkXMc +ƧªLƾe+|Q&5X=ΚkrX] ݾP%<KrlXG JƝ7ğC&}*[bH=zE8V?<c<K|v?R*8 8rhG+^zy'ېVUe`KK^pˠnm17FwlqNOOw d8a0nV?p ?FqƊtVzr 9dYcg\1:c~̠ۗ}ʱDZ \CO>2zFBp`RK81PjsVߏ7]~"u0)cr8S.a@ c!|5>Ğ2*0he̮} ¬WOX*y8ހ BBBKxZ˨g*[oE?` IֈӎZiC9R"v&US"!xUZ5;G4~x\+K) Ghfj%u&޾C[x^DD"LVK;=%6=ckںg=J洴4zj(!\Cjݞ=F;ECeguIq=F w+쨐ʜ͋$&ɿ7F일ݝnDQ bܸqB8CBUYTBA_\(}AW;+Ef?UbDa_ޥ 01NU#V DW\h#*TcG%>u `C㦞x fyEԴT {N`UvJr j-O€R={~衇\bˎDj;da([_M@􀽬| V]xAR6U4qr +G,EDD-|^{W^8!lƻԦUi3IG_bHw* ZRۯu JQtX|&zS`PQh(yǶFPD<}I;M44 >9 LF$8VlLQF[9:'wXt!Qd"$)=﷑ɡ'0(З|H?.; F'N?\|@0 H ` C-{Zўy;O"4c; H\ 8WߦW)d=bǩh*L.Or57unu)4ly;)46Uc>(nQ P0H-7t H$@ 7%,Wgci0ib`IU9ח^1EY rfgTTaikp`Hq d1@ _ ihU8 &,R`~ [RE t믿 -PJPEӦSTd!/p8q!FC-[&ҴiSE=(a#I5 7&i[u4yZ]XB>Gp<55a";sL7U,<:XyBەBcj&o/Q^n,-:튽@Ud/NԎ-,5Bx,5,d)3b\b`S_Hc L$XP-߀ g5~*|٢Z&Ϡ*#͛I,ZhP7} MUc{G&]@kQ8s&R#[p?aApQaHXi[y򴆇  KO5J4pD]5>_lZ{G8.L1A`}[X[y^Cﻁ$52/.ZY}N,b{u`=".eL8,3T;fE&Yz0&{<֕0q)$R@uDHWEx ;! ɢ1zXV-0ޫ-$.te;¡ ed~c MȾQ\sLl:` 7/w_cG,W3"KtqT | ȏf=!z1 E"EꜶt2+W1Mm?^K"RWlX^cAff}V{ػmK}@^dΛ'A,W>!D 6 ^= Ge4*7_b'wK$r:eU>5 i;AnG'Kyd?5xFaB}>f#Hm^rQrՖN\/b@SvQX1V\R̥8kx3J'6MF yg'v{)8#)RֹD 7nJsa/Id+Q 6 UZB1l>)$ !{I:[uV)3 č1H 6!xTPmq X_IdyCW^@d 4=p%1BjO߼90E:@!*7ӑ¨}_Y8 (yuջT-#;"ѣyB,?gf.cG%if UUsD-X_ُq u̻u `~u@@v+8 6JKMzoq AQ+Vp)w?k1u*tTD6*z2 MœL4Re"*K {(swX} { ވ[cA`j61`2@YLF9y<Yn_{Uan{T[pƤR3axgѢ\8䛚QB&Ve8=\8CaT:\ Kw}N.3~|cx_h˸ L l|*"?o 7&3ҡǛϧr$ǸJu~K%ڂ*5##ݣ,oǛh&6Di #%y[kgө\iil /nݺpDPq,bG:`u"4&`WX}}̅"b!&@Ƙh߾c ?8eHvP$+<":Pd:R(PYv'9*W+)|iO? :*,Bιg /8ߞ`ܬqU֭{Pf p wU2+^SP2OzĆ=&%-˫g D#$~"49 5&y5LWvET )x)on%x#-D} [[xNLlf=x vP:1_u_f}$x}=4_}Yo 0UюgT߅k=͸02+/- lLf+zLmht+-J)O+c5-3fc"ڵk@&<1CԴ|rA(թ lE ?bbJb PHoޢ˃[-󱎎/a!ԂM(-,x9cyV]P-Ql5{92N2^Iƹ18Pe16x{2?d{f|Z_CǪfLi>Jmf] .7!MMK jLHXIKټ̓LGXeMPRt"'dFC/d<~%^-ysLGmJorLjW5p!&` m!IDAT6'Rېyss7 d\CG]c3^_ٯ *B|3kCUє,} ڷy >^MkV)I69gK>𬆞78srryY V#.R0DL9psXBM*xPF#ćX3v:?£5##M-sa2aQTZCLldMԖdp"6a>.p hK )f@K=U9ѳ/ yJ8PE8Ǟ={z9# ~\Xȼ@EdńIy@"4Ƥ&=r9xaFPLf% \Otх9BdbKƖv3BdRbոr>}Ρެ/* m4UIIŠLm+9VW@@uP]2110`{o 94oD>O X']UoZb!>n zmOѐA*'j `b-_BȶEk)Pk݃fO bK-";eDES(4u)~ŎFx%2]G'g1\DM72'aؖS3l݄D<<fiN_}^Ի ޖ:S.Yc@c@h0F (6A;ӧ)S9H ]x0)K0:n&7hP MGlb g˯)lEuԪ%Tx37ZB<ģ"]L hb;W\etjB:trjsҁz`j;qzgxRȞÄqB{r7 Cی${e1Uz~Vg̨+h{g\=Htƀ {8xS#D ,W{܃ћ7E%D?بL0|9"Ԃ6 ][.[FΫh LƬY\aFvZjECTByh8 Tx@079 i$M5 Ҹa?lYH0ȫ%2BZO>r?*U s.HʣQx\*.8A>Z &JDߥAc@cDRE{=b 3nZ59^xAXGAg蹇|JC(!F7P@WS+MJckO;6G6`d}qJ2C\coM}]AU$E/[l)DRw ǜ߆?uiGobbd\b'k^!!ig7}zE9`Vx&tH {?o1l_`38C-[A}11paVXC~3Ye"_ea{Q aCÜWV7MYtyy+]ve:wlFOG0 JV\FUJᡕBװ>=ҳèj] *C0{"cdI9e~ ߰{,PZ;@ 8A$r\u,ȚSpV&BC k2A!﫯*j[{Li[j%&r B̆gaX~tهZ "P"Ӓ%i+&iӦyJE907я?A+E7"SRi[^fU<ɕQ,TLP1l(6A@$EaRR/1E x`@,E&c}GC ߸t Oy)ڳ96 ==Qn=lW^MVR]{vrrrEB}#lj|a{b_r\?S"nݻE=I _09۷oo֜+ kB7L}1u"h;q ЃdT6*wq7ڞ,E%ԬTucrD[),4Lxz@ l,l0-.-UA_wg/όX6n0nÄ e˖ŋ=Wq."Ͽ &BxF8L!yߊ^lWC3& <;׬YC+٨xgq4qHCKy9#19˺83'XDqBl1cƈ (f d ƅ \]\U&@F038 _ܣɓ'f:McL08 kAUT&9 pn.oI`R+%9ؒ8 ;[1wgw[:XQ k D&b&(Ap@PXXM",so|mƟm@.M@@0' a1bX VIgOh,q߹KM0ww(DG{n .8_3x#Ulٷ0%"|˟ XmPO<buGnníd`j CBX1m'[%_HR fSN܄њA&멂SG */sf 0LZ9&o{w}c课 {2ޘt4x0A㻔o#2uq)h]c:<(1ɛ!LDS똵E4@ 1&ڶ&!]\/0םqz XRԹ G%mvS;ML֔Y47'bj1Ɓ}@@@@pO ǎ3DOCѼ*F(P">>θC[;Y&:;g!`uH_Y9@evZ1iNJJB=EUœXX"%G?Zs w^[ZPAuH"H$E]<%AkrT9TJmPH}SRt^+DKhE9L\ $1QևhK^d_./E]pf6k ']$m@ٯC{!fr,=`WGܬ-xSC0ƱA8u9A$0Z6gڒ#4m=kNJK-߶M[+aäk8Ro5 4w*6e9h(ھ[?їӏ &t0V/!ߩ056 :Qc !.㳳h2xxL-#|le~hXyS`nlv6eCX (}z@C].zu-n"?l&`O?}m~ 7$p )n6Ax3d}k*iOI\v7!xEQ1 @(M5I$ o]I0D`G/|[l=ʭ]pB1&mWQ&^A,/&b( 駟ٳg{Lqq@  368@%@W&UR[A@[ mi#{ H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_FPLTE366bddrttSUU#&&CEE !!!"""###$$$%%%&&&'''((()))***+++,,,---...///000111222333444555666777888999:::;;;<<<===>>>???@@@AAABBBCCCDDDEEEFFFGGGHHHIIIJJJKKKLLLMMMNNNOOOPPPQQQRRRSSSTTTUUUVVVWWWXXXYYYZZZ[[[\\\]]]^^^___```aaabbbcccdddeeefffggghhhiiijjjkkklllmmmnnnooopppqqqrrrssstttuuuvvvwwwxxxyyyzzz{{{|||}}}~~~=RtRNS%bIDATx10Do|nI*D#f"B \d̟3\P,T@.…&8}0jp-Xh=-~@较wp/.{I..z._ - QۜIENDB`workbench-1.1.1/src/Resources/FtglFonts/000077500000000000000000000000001255417355300201555ustar00rootroot00000000000000workbench-1.1.1/src/Resources/FtglFonts/VeraBd.ttf000066400000000000000000001625341255417355300220520ustar00rootroot00000000000000OS/2=@VPCLT,eϘ6cmapXcvt >-Rfpgmp9)gasp glyf4h)&hdmxEHheadO$6hhea. $hmtxy,kernlocaXz[maxp} nameYͿpostx<prep|a!\::N:: R^0p   t t  &   ; 0  0   C , [ `   0 & Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved.Bitstream Vera Sans BoldRelease 1.10BitstreamVeraSans-BoldCopyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is a trademark of Bitstream, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions: The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces. The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Bitstream" or the word "Vera". This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Bitstream Vera" names. The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself. THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. Except as contained in this notice, the names of Gnome, the Gnome Foundation, and Bitstream Inc., shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from the Gnome Foundation or Bitstream Inc., respectively. For further information, contact: fonts at gnome dot org.http://www.bitstream.comCopyright (c) 2003 by Bitstream, Inc. All Rights Reserved.Bitstream Vera Sans BoldRelease 1.10BitstreamVeraSans-BoldCopyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is a trademark of Bitstream, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions: The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces. The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Bitstream" or the word "Vera". This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Bitstream Vera" names. The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself. THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. Except as contained in this notice, the names of Gnome, the Gnome Foundation, and Bitstream Inc., shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from the Gnome Foundation or Bitstream Inc., respectively. For further information, contact: fonts at gnome dot org.http://www.bitstream.comf3f=ffTbfTfmf3bq%fHZfm99Xm=fuff9{{X3fLfLJ#DDf?;Pw /X#/553X sf+j-j!f#^`3B3\fy```{j\{`bXP1L`%!JJ7{'}3Xy9bsA&%$!:$#"!:"!: d}}      Y    & Y @ &  .A@}>,,G}G  @ 2 d۠d%%%   %ё%Д #&̑ɻ]ɻɀ@%]@%dĐ::2  }& @ ]%]@..@   K%%%2 ~}|{zywvwvututsr}qpo,o,nmlkjihc h2gf2ed ed d@cb c b a`a``_ ^]\\[Z[ZZYXWV@VUTSRQRQQPOPONONMLKLKJKJIJIHGFGFEDCDCBA%BAA%@?@?>?>=< =< ;d:987656%54554 4432 33@2 10100/ .-,:-,%,:+d*d)(''& %$#@+$#" "!!@  %@ K}K%%dd   2     @   @d  d++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++, %Id@QX Y!-,%Id@QX Y!-,  P y PXY%%# P y PXY%-,KPX (EDY!-,%E`D-,KSX%%EDY!!-,ED-ff&&/10!%!!fsr) @ <2991/0!!!!h33h^h@1<20###h++)K@1     91/<2<<22<<220!3!!!!#!#!5!!5!!!`aaE````HFR`PF#*1s@? %$ + ,#,, (/($ +/ 2<<991/99999990#&&''&&546773&&'6654&}osy!dede GUNWWP-.);?7* "*/(BE5;CBBDCB '3c@5  % ."( 41+  1 +%49912<0KSXY""32654&'2#"&546#3!2#"&546"32654&3GNMHHLMGֺ%պպHNNHHMNh{rs{{sr{ؽ۽ ٽڽ٨|rs}}sr|{&06@Y     ,-./+0()'%0' - -! '*$ 0$*$  *  199999991/99990KSX999Y"']@   ' 0   0%/ / %&? ? @K K K/K0ZZUZ Z U(\.\0X2_2dig`i i d&2, ' '* 9 5005@J I'I(WW\ ['ggl ]] >7!!'# 5467.54632.#"3267577oc%Xbi*([k^PMU1ABwCt2>FnkmFDےj5j:0.;6"W/wGs))@ 10#+ @ 29910!&547!י);: @   29910654'!)?C)9F@(      <2<29912290%#'%%73%JLLNLNMXX "@   <<1/<<0!!#!5!    m9@ 10!#hduo10!!ot91/0!!h}B/99103#mb/ #@  10&#"326! ! i||jj|{j@'&@mstm (@  1/20!%!!!T[nT HH5@)% 9991/2990KSX9Y"K TX@878Y@&**"""555BJFF]]!!>54&#">3 N!IFuZzz )~B~DiMLH+-zӱ(L@+  #)& )999190!"&'32654&##532654&#"663 sqlg~]^rl#!%'%%)67jcfi[]V^*) \3 C@ !  ! %    <291/<290KSXY"!!3#!!Z@jRJ=@"  " 190!!663 !"&'32654&#"v,Y00{zaSl 12/FFuv+-# $7@ "% %$%190"32654&&&#"6632! !2eeeefeev_PB[uEgჃ-+11ir E@%91/0KSXY"]@ &5F]!!!e'1} #/G@( '-0 $*& '&!$0991990"32654&%&&54$! ! $54632654&#"lttlkrr|כc\ZbbZ\cvnnuunou)ž)*ސUY``YY_`j$7@  %%" $%19073267#"54! !"&2654&#"\RDZ9$@ieffeeff!++22 "vYN`@<21/0!!!!ii`}}N` %@  <210!#!!idiu}=@29190 5<'@ <210!!!!=@<91905511J!H@'    "<299991/9990!546776654&#"6632!!Bj@95`VQfy]N^@D*i1Rb:4\.FOCB:*(ǿbY9>K-o Ml@: 40LM3 30 07$CN34L **)(I(*)4=N<991299999032654&#"#"&54632536654&'&$#"3267#"$'&5476$32!#?iZYjkZXiYثY|:;_tZked~Yk}٘~~On{KM'{zyZGOPGKɝdIz=;bɵdbg^Pag}}IJ}|b~ ' @@     %    91/<90KSXY"K TX  @878Y@ / V f  t   %* IFGH XYVWhifg` t{zu{t      /]]!!!!!F_}))}+%R P@%    !299991/90@ ""/"P"]2654&+2654&+)! [^^[tutuH|B7fPNMQsbcaay$ռmf\;@    - +21990/_]%# !2.#"3267\j}Lu}jksskR78ef87IDDI9.@  -. 99991/0P]32654&#! )=TMwiffixjq#ateeta 0@   21/0 P p ]!!!!!!rg +@ 21/0 P p ]!!!!!rgfK@%    1 3/-+19990_]%# !2.#"3267#!ʥLy}|@   221/<<0@P ` p ]!!!!!!89+y=71/0KTKT[X@878Y@P]!!+f= L@   991990KTKT[X  @878Y @ P ]!!#3265N3IO@UZfi ]]!!!!mR+ff 2@  -7-+10@ /?]"3254 ! f°±hhgddjk 1@   - 299991/0]! !#!32654&#1pzzp_mddlffb@   - 7-+999190@,  '/V S f ` w w p  Y Y YXj i x ]]# ! !"3254fgk-¾lkh\@2%       29991/<9990KSX9Y"]@66EEVVPee`]2654&+!! !.#yiiyL'O}@f7q^?ZgfX֔-XspR-'@*% %( "(999919990@Tp)9999 JJJ X ]\^^ Z!joooh o n!t t t || |!  !  !(]].#"!"$'32654&/.54$!2{hYuӎ⏏ |~[ {78LP@881/20K TKT[X@878Y@ ]!!!! `N3@   91290@p]!3265!! yy6= '@'%91/290KSXY"K TKT[X@878Y@,  GGHHEJWX]]! !! 5N+= x@J 6  6 6 6   %     91/<2290KSXY"K TK T[K T[K T[X  @878Y@  % :?:?3 0 0 @ @ @ ^^a          '('(%* /66220002 4 6 ?IFHE J ]]ZZUURRRZ U ] ooonhheh k n i o wwx v x   K]]! ! !! !=qsnDD==+o' @E    %     91/<290KSXY"K TKT[KT[X  @878Y@X  /& <3 _P      ++%$%+ :55: P ejo  ]] ! ! ! !omGF@(%:: 91/290KSXY"K TK T[KT[X  @878Y@, %%0@P` %*5:0 O o ]]! !!TTu\q w@% 991/0KSXY"K TK T[X  @878Y@ %)69? FHO V_ o ]!!!5!s8!7@210!!!!mB/99103m@210!5!!5!m`@ 91290##fg--/10!5۾^fN10K TKT[X@878YK TX@878Y] #yfxX{ %@*   # # = ;&229991/9990@L/'= =!?'M M!] ]!n n!~ ~!p' ! ! ! !20C@SPc`]]"326=%!5#"&54$!354&#">3 pq[QeiH"ӆsUst/ LJDMm)f]ˢŸUO..^ 8@ B@ 221/0O`]%2654&#">32#"&'!!syyss{{{Ju uJf稠b]]bX5{7@ B ;210_].#"3267# !25IOT@TWV/X=202177\8@ @B ;221/0O`]!!5#"322654&#"hJu tsyysryyXc\II]ɨX {C@!    D ;9190/?]!3267# ! 4&#" q}K"=w`h3f~~CD015:“f}un'`@    E <<991/22990K TKT[X@878Y@ ]#"!!!#35463L<27DN`N\Fy(K@& #& @ B;)221/990O*`*]%#"54325!!"&'3265"32654&Ju uJhic^[o|xsp||b\CA\c !655@   G 21/<9990`]!54&'.#"!!>32 H.pfQnVon#'b])@ <21/0@ P ` p ]!!!!ff`F =@    <2991990@ P`p]!+53265!!fͱ>fLf`\y @   291/<90@`;IIZ]X_ogvv{:DGJV]g`ewpv|]]!! !!fNNK- 1/0@ P`p]!!f{%t@)   #  H H &<991/<<<29990KTX&&&@878Y@'0'P'p'''']>32!>54&#"!4&#"!!>32DpFNfo@RgphBgthmVH wkHk`_`p{5@   G 21/<9990`]!54&'.#"!!>32 H.pfQnVon#'`b]X'{ -@  BLB;107?G]"32654& ! w}}wu||u!EG{88V^{;@B @ 2210O`]%!!>32#"&"32654&fJu us{{ssyy b]]7\Vy ;@  @B;2210O`]"32654&#"325!!ryyrsyyyJu uJhw+c\IG\c{C@     21/990KTX@878Y.#"!!>32/]/fE}*(/`nejb{'@@  6  6% %( SRP"M(9999190KSX99Y" ]@^ #  ,. . . . . ) 9; ; ; : : K J J J H w w  %  7 ?)_) ]].#"!"&'32654&/.54632s_fcKa?o}ktijIm?c=0035+. ###44:90/ x@    T<<991/<2990KTKT[KT[KT[X@878Y@??PPP`` ]]!!;!"&5#33q>\Ա%N7>`;@  G 291/29990`]!3265!!5#"&hG.pfQmp[.w#&)b]`@'%91/290KSXY"K TKT[X@878Y@| 0@Vf  &$+)64990FFII`x$]]! !!fgGw`H` @J 4  4 4 4   %     91/<2290KSXY"K TK T[K T[X  @878Y@ 550 G @ @ _ l        &$+)*+ $ % /554;::78 ?GIFHGH YVV[ T Y _f`b```d ` upspppt p      []]!!!! !H\+\yy` ` @F    %    91/<290KSXY"K TKT[KT[KT[X  @878Y@  / 3< CL R\ bl sz         2     $++$ 4;;4 0 DKKD o       :]] !! ! !l{{l=#LbF`A@C %    9129990KSX9Y"K TKT[KT[X@878Y@ @Pet $$$5586699EEJJEEge    9]]! !+5326?f-f)Gp[S `6:K\F` @% 2991/0KSXY"K TK T[X  @878Y@DYVifyv &)/ 9? J_ ]]!!!5!uNN`f$^@1 %   ! % $  %<<29999999199999990#"&554&##5326554633#"3l==lEUZnoYUmutWW10#$`@2%   #%# %<2<999999919999999032655467&&554&##53233#"##FUZooZUFl==lmWW͖tuR#@  1990#"'&'&'&#"56632326j`k^Xbk`k^VRPE:=MSPE:=K 'k'$u 'm!{@S!! ! !%! !  UU "9999999991/<9990KSXY"K TX"""@878Y@/!/!:!o! !! # ///  /// "+ #EKUZ` ` ` ooo``ooo`fi `#tuyz{t    D]] !!!.54632%32654&#"!}^_}vtwM66MN56MJH"K+uu/L{6MM66MMRfo\'&sk'(um'15uffk'2Nuk'8'uXf'DXf'DCXf'DX1'DX9'DX'DXo5{'FX f'HX f'HCX f'HX 1'Hf'wf'Cwf'w<1'w9'QX'f'RX'f'RCX'f'RX'1'RX'9'Rf'Xf'XCf'X1'X5; *@  WV W <<1220!!!!!5!VJ#!/dL @  XYX10"32654&'2#"&546HdcIHdeGBz0/11-0|D\dHHbcGHd3/0xDCy-03#W@.    !$   B$<<222991<9990&&##667###$4%3NMMNJAY9S: GZ,lm*902i2/  (.##}@@!   <<1/222990&&#"!!!!3#5356!2FMvqu\'&} F=3?k@8@1:4 %+1@ =!+%74:!=\.!\=[.7[(@9999991999990&&#"#"&'532654'&'&&5467&&546326654&uc9KL ҟquMKUfs9AN$ˠoqKATDC{AF''1/CO Y}u0)qI)+2(FJWh33oKL2CbBO4Cj'` ]104676632#"&'&&'535II245633JI326J235624IJ336633;d &@ ^^9120!###&&54$\fN۲h0j@4.(" !++/"!(%  a%.(a_ . 199991/990@ /2O2p22]4$! #"&'532654&/.5467.#"! 1]EtkAJ8s6HX7bFXT`[efZG NJ%94%@uH9/D7'1Zt2UYnm 4Lb@8-*+'0!5 2+A'*,$0-+$!1g3f$cX;eX3cGM299991/29990"32676654&'&&#32654&'2#'&&###2#"$'&5476$yWWWWWVy{WWWWWXϲ##NOM+i`)Gok&: 1mmllmmmmllmm3WWWzyWVVUWWyzWXV5442wyVpP:NAD7nmmmmnnmmmmn1IH@(  2&>f,X c8e XhDJ21/990&&#"3267#"&54632'"32676654&'&&'2#"$'&5476$+9o9q~r@s.A>EyWWWWWVy{WWWWWXymmllmmmmllmmf%#rs~$#WWWzyWVVUWWyzWXVnmmmmnnmmmmn'R v@>  %    ji i ji91<<2<<9990KSXY"73#######5ww㪉LqKBMmf710K TKT[X@878Y]!#f;;1\@1<20K TK T[KT[KT[X@878YK TX@878Y3#%3#1 =@!      <291<2<2.990!3!!!'7#5!7!^P1}@7%     /<291/<90KSXY"K TK T[X@878Y@&W ] !!!!!!!!!{y}sfb^- +@> +,   )*&& &,+,* # )-#7-+,99999999199999990@p- -*'&!/-976!9)?-GYVT!Y(Y)jege!j%j($'))68)KFE I)Z^SVV T!V"[(j ejlaf c!k(x ]]3254&/.#".5!27!"&''\4SM3RJJgfqMLhfqs>;Du1:9@q.dkKMscdOOq /B@#  $'!-!0 $k*k099991<999032654&#"&&#"326#"&546326632#"&+vIZqgLHw+tKZqfMGzDaƯZcG_ů[1CDeOMeeCCdOMeia~q~n .@   l l <2<21/<<0!!#!5!!!  bb '@    <2291/90%!55PN '@   <<291/907!!55%y@B  %     nm n m<2<2999991/2<<<290KSXY"]@, $+6:FI   0@ ]]!!!5!5'!5!! !!!!N9:1k$! %j1`BV3VBT` :@!  !   !2912<990!3265!3267#"&'#"&'idfgdh!'!5]-Yq#/YJhT utqqtG8 KSOO/0;R)8@'! '!* $$*99919906654&#"#"&54632#"&54324&#"32;'#S0@eID`IFa~q9WzC2EqG Ur|tx)w O@    91990@ &#)  ) ( ) 8 ]]!! !!5 Bl_{Nw@pp120!!!!)JD/@    991/<22990#!#!#"663J'7: Ddd>Dǜ3,B*# @- *(&  qr q-9919026732#"&54&#"#"&54632jciRA@Ae '&>ÄkTF32-ӅhB:Yr 7^YUWO\K=4>3:rWT@LHt8;##uu 9A  @  uu 99102#"&546!!"32654&B7T[[TS[[޾ܾM~tt||tt~7F   @  xwx w 9991/<2990!!654&#"!!&5! #~˲˄~#~zx89xz#VˤUy9yǤX{>@B8>66'&# 6-*>;0*? - 6 & 7 3;?<9999912<<<9990@N>>?@MMO@^^_@nno@@@2=0>B=@>R=P>b=`>=>=>=>=>]]4&#""326=>32>3 !3267#"$'#"&54$!354&#"w`gpq[Qe^waGMz =q}~Heߋ"ӆsUf}unLJDMm)JMOMOf~~CD01kdkdŨŸUO..N) +@> )+ *& &&++, #* #)B#LB;,999999991/9999990@@:5 ;75!8)?-IF KGD!H)[VT!U(ikfe!e(5:)EJ)U^(i em( ]].#"32654&'.5!27!"&''XK/w}HO0u|;CDG"jKmFElMpD)A+CN{8,,eP~--^!M@*   "  "<2999919990!3267#"$54677665%!!iAm@84`VQew\N^@D*ii1Q~d:3\/FPDB*(ǾcX:=L-d @ <2991/0!!!33h=^qd@ 10!#!LZ n@*      %  @  9190KSXY"3##'%`w͑%hN7V7#w@I #"!   %   !$2299990KSX92Y"&&#"!!#"&'53267#5!766327.T*Zd!)AD.U)Ycu!3*Bs_sM#;C@!.9* 1 "9*1<-<<219999990#"'&'&'&#"56632326#"'&'&'&#"56632326j`k^Xbian ^Vgj`k ^Xbk`k^V#PE:=MSNE;=KPE:=LTPE:>K  @ /91/90!!!#-3mV?j' 5  @  y y<2991<299055%$'qsq' 5  @ yy <<991<29905-5%%%!$'^ #@   1/<<220!!!!!!hhh}}} 'k'$u 'm'$uffm'2NufP@"     -+ 299991/220@ !!?!O!_!]# !3!!!!!!"# !2i iZhsf / F& 0ihX^{'3t@2"  .(%4"1 1 +B;499912<9990@/5?5O5O5_5o5o55F"]]4&#"!3267#"&'# !2>3 %"32654&w`hA q}~~HRՂG"QRLJBcw}}wu||f}unwf~~CD01QWTT88RVWQ:/10!!/10!!X +@    1<20!3!3!ddXb`Xo + @  1<20!#!#!TeTe`^X@ 10!3'dX`X9@ 10!#Td`V 0@  z{z <<10!!!!!!33Xˁ#uv@A%91990KSXY"  9%-F1'\k'<uh+@55%10KSXY"#3 J=#/@ ! ! $A !* @&00   '}|~-} |022999999122999990'7&&5467'766327'#"&72654&#"ϙљ0l=6l9ϘϚ.j?:l[\[~ Ϛ1k??l.͚Ϛ7n6?i/ϙ\\\]~'y291905%'q'y<91905%%$'+Bu@&       ET<2<<991/<2<2990KTKT[KT[KT[X@878Y@]!!#"!!!!#35463iJK:k$7DN``N'Bl@!      ET<2991/<22990KTKT[KT[KT[X@878Y@]!!!"!!!#3546{L<)7DN`N3;?@!   W VW <<<<2912<220!!!!!!!5!!5!VJ###!<}910!!h}L@ 10!#Te`F + @  1<20!#!#TeTe`^B V #/3?K|@C3 2211 003%@ *$F4 :02$L3IC1!  C=!'= I7' -L9912<<2220KSXY""32654&'2#"&546"32654&'2#"&546#32#"&546"32654& HNNHGLLGֹHNNHHMNGպֺ׺GNMHHLMh{rs{{sr{ؽ۽8|rs}}sr|ٽڽ ؽ۽٨{rs{{sr{ 'k'$uk'(u 'k'$uk'(uk'(uk',duk',du)k',du=k',duffk'2Nuffk'2Nuffk'2Nuk'8'uk'8'uk'8'u` 1/0@ P`p]!!f`yf6@ 91290K TKT[X@878Y3#'#Dzf\9@  @  999919999990K TKT[X@878Y@T              ( ]]'&'&#"#4632326=3#"&7/$&g]$I)=%$(g]$CT%>;+@9X;E10K TKT[X@878YKTX@878Y!!vPF i@  1<0K TKT[KT[X@878YK TX@878Y@]332673#"& cSSc FFJJFw;1*10K TX@878Y!!w1 C @ : 10K TKT[X@878Y32654&#"4632#"&}M67LM67Lvvvv7LM66MM6vvvo5@   991/0K TX@878Y!#"&/32654&'Z:7{0f42S!:A+->j/_[ .(R<fE@991<20K TKT[X@878Y3#3#-fxVo@   991/0!33267#"&546ō2&;1'M(7^)s{6CI'1 \V5myf6@ 91<90K TKT[X@878Y 373Dzx `@2 %    <<.9991/90KSXY"!7!!'%s۔#` j ~@-   %    T <2.991/90KSXY" ]@ut@ P ` ` tp p  ]]!7!'7ho}o XV-k'6ujbf'Vb\qk'=u\Ff']T@ <210##  !L @   -. <291/<20@X!P!`!////////OOOOOOOO________(]]3#32654&#! )#3PULxhgghyk#ateetamX'(@Y&'('%$%(('"#" ! "! 5((5(%('&%"! ## #)'& !#(%" BB;)999919990KSX92Y"KTKT[X)@))878Y@6f!/*76"?*O*oooooooooo]].#"32654&! 4!2''%'!%7l4uru| uj-.N$%3`ox#y-\8 watr`k'<uFf'\ @  - 2299991/0K TK T[KT[KT[KT[X@878Y@,0000PPPP]]!!3 !32654&#=1pzzp]mcenV^;@B @ 2210O`]%!!>32#"&"32654&fJu us{{ssyyb]]7 10!!) /@   <291<290 '7NNNN3NPPN{ 7  @   129035733!9 41Zm]@%   "@99919990KSX9Y"!!56654&#"56632r_9=4I;>TWKGeD 5P(2>-/oHyVZ(W@ #  ""#@#)& )99919990#"&'532654&##532654&#"56632P\fQDB<_hkrJTbZNP4{FAWZ`nQ$%@;@=/3--piE`d @     % @    229991/222990KSXY"333##5!5#335733!9y+ I 41Zd'@   %!   $$& !&"@ #%# (9991/299990KSX9Y"%!!56654&#"56632#335733!qt];>3J<>UXJIc 5N'2?-0oH|T I 41Zh 6:@ : 9988 77: %.1* #! #!$-*$!#917@$$8'" :!'"4  '4-";229999991/2<299990KSXY"33##5!53#"&'532654&##532654&#"56632#3'y%]fQDB<_hjsJTb[OO5zGAVZ-D7#nQ$%@;@=/3--piE`q fk'* 1u\FF'J=k', duo-'6job{'Vbf\k'&fuXuf'Ff\k'&fuXLf'F\$K%@"      @"B;%<<1/<20O&]!5!5!3#!5#"322654&#"FhJu tsyysryyrr+c\II]ɨo10!!ot910!!h}1r@;.*(1.!2*("%!) 2 +) )% 2229999999999122<2990%# '#73&&5467#736!2&&#"!!!!3267_pKXbXMep_Qc-VY2~cTR78 87NO{v$$ zzOO;@ 1<203#%3#mN810K TX@878Y@ //]!#3\#@  @! $  $999991<29990K TX$$$@878Y@\             ##+]]'&'&#"#465463232653#"&8- (kW%J';'%'kW&F#<2j'<9j810K TX@878Y@ //]#yE@ 91<90K TX@878Y@/// ]!#'#f4߲DzyK@ 91290K TX@878Y@//// ]373f߲DzP S@  120K TX@878Y@///// / ]332673#"&`LL`=<<=w*10K TX@878Y!!w  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~>: ~1BSax~ & 0 : !""""+"H"e%  0AR^x}  0 9 !""""+"H"`%^ChVjq_8 (Bbcdefghjikmlnoqprsutvwxzy{}|~f+B{s/) mRo b\}j331 fwwf3ff)u 1 =+'\^fXX\mX{'\RVX\j7dH)7\1 1 fwffXfXfXfXfXfXXmXmXmXmX<XXXXX5}''m-Z;)L'3u'7bXNVL++1 1 f VfXBB  7VhJLL+'3  B B1 w1 ww)fffwV#j\\!X7{mZHdHdHhf\jfXfX\Ro mw   !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~    sfthyphenperiodcenteredEuroc6459c6460c6461c6462c6463c6466c6467c6468%%%%Nq] 6 N{U?u<_O K G E a > *2e:\u#p|$Z<~*Z@!I"dqjw   - : G T a n { !!]!"""##$.$%!%K%%&H''n''((()6){))*<*++p,5,-8-a-}-.B..//\/////00001161U1t112 2292233v344-4L4~5*575D5Q5^5k5x5555555556 67667H7k778'8W889-9:9G9T9a9::::;O;;;<>>>>>>??????@4@T@AA@AuAAB 79k:;Y<$&$&$&$&$7a$8$9u$:$<<$Y$\$h$D$D$$<$r$r$$$$<$%9%:%<%%&/&6&&K&K&&&&'&'nkm>lk llk k@jddjihihg]hhgf%g]g@f%eddeddcba`_.`_.^]\K[}ZYDXWVUSdRQ2POP}ONA@BL JdI"IH2GGFE EDCDkCBCBA BA@ A @ @@S?>->M=<=K<; <<@; :9:]98987 654543432 321 2 2@1 0/0D/.//. ..- d-,+,K+"++@* *d)(0)A(-(0'-'&:% %]$#$S#"##@"! !]     @#$0S-0 k@-B d-    @    @8k d } d2}-2- Sd+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++, %Id@QX Y!-,%Id@QX Y!-,  P y PXY%%# P y PXY%-,KPX EDY!-,%E`D-,KSX%%EDY!!-,ED-ff@ ]/10!%!!fsr)! $@a ` b  10%4632#"&!#5L97NN79LD{h8NN87NM@ b1<20#!#h++W@6 cc       991/<22<2222220!!!3!!!!#!#!5!!5!T32!53!5>54&#"pkh ou=Ŗq 9< mĖ*M@+ etee)te&ei`+  #) #  +99991906632!"&'332654&##532654&#"#u^unp _2 p,. ׫23"f~?@%d d M ss i  <<91/<<290KSXY"/ ] ]K TK T[K T[KT[X@878Y@ 8I( 68 GH ]] !53!53!!3Xtj%m ky@%xetee wb `     9190K TK T[KT[X  @878YKTKT[X @ 878Y@& //]]!>32!"&'332654&#"#T4Vgqq Z5VդT$$23"@C %c@" eely#ei`&    &190@%   ) ) NN N!]%2654&#">32#"!2#.#"DlB%O[q n¾FȽKJy^ejr@Mwb  91/90KSXY"K TX  @878YKTX @ 878Y] #!#!-Nuo1\ />@"$e ee*i`0$ ' -'! 09919904&#"3264&#"326#"$5467&&54632)|{{|lv͟ҟϴ%]@$ elyee i`& #  &190@ !"AAA ]#"5432!"&'5332"32654&CmO[p m¾KJ xRel%"ɽy @a a ` <21074632#"&4632#"&M88ML99LM89LL98Mh8NN88MM8ML99LMJy"@a  1990665534632#"&J^X?M88MM88ME%Z 8MN78MM^@29190 5Ѧf@rr<210!!!!^@<919055//y "<@ a ! ei `#! #10%4632#"&6632#6654&#"#hM97NN79MjT{~rah8NN87NMc/.ֶ3H+ʜ{o@Mm@;BAKE'N$ KE($ Ez Kzz@|$z+ |z+7N'(HA ==1N22991<9999990%#"&54632536654&'&$#"3267#"$'&5476$3254&#"326;]گ];DCjϳZVd_m+`5l}YawLNsyzrRPPR%oT&']gw}LLF]]{GF~b|i}@Qdddd  Mo ob    91/<290KSX22Y"(]@ ((( (,+]! 5333!53!3$H}}jkjjHjqd$E@'o obo ~ $ !$ !%229991/2290%!2654&#!532654&##53#5!2!+w埙jj~}jkäs8@ndn i`&' %10o]#"&'&5!2#.#"3267BapmIzq%0dap\@Aq=@ ob o  $ !299991/220@ 0O]%3 !#53#5! !#7ܺwRPjL66Hjkvtq3R@*  obo ~ $ !2221/00O]353#5!#5!!53#5!!53q{R{{{jk q7F@'  obo ~  $ !2221/20353#5!#5!!53#5!3q{>{{jk ~jsG@"  o ndni` &%1990 0 O ].# 3267!5!# !2#q]ͯiY88kMM_b;<q^@.  o bo ~ $ $$ $!2222221/<2<20@ p]353#5!#!#5!#3!53!3qGGjkkkkjj~jq 7@ obo $$! 221/220@ 0 P ` p ]%3!53#5!#GjjjkkTVm@ dn o b $ 12990K TX@878Y0]KTKT[X@878Y533265#5!##"&rXXw^qGca9kk"q(@Dd  dM  o bo $$!2<2991/<22<290KSX2Y"p]@  $ '6 F HW YYhzz    * % % & &'(: 6 6F F @ F @ F @ F@B@@@@@S X X U UPXXi i e efb```y  y  v vvyyC]]353#5!##5!# 3!3qGͪ3 HapmmpbEnpbNQJKQkdappbcVobcqC@!o ob o # $ !2299991/220_]!2654&#!53#5!2#!3wjkjs"@@nn i`#  %#99999190_$o$$] 47>3 ;.'2#"`jmpbE6mdapbcVm:C@QJKQq7$@H  Mo #ob o#   $ $ !%229999991/<2229990KSX9Y"3!&&##3!53#5!2%!2654&#!Fe(ٶCpbz'[REjۊQjjkп&)'@B#"$!   M !dn'dni'`* !)$)*9999190KSX99Y"+]@ +0+P+p++]KTKT[KT[X*@**878Y@t( ) ) ) )) )!)"8 9 : : : 99:: :!9"8#I I J I HI I!I"Y Y Y Y Y Y!Y"j l l l k kl l!l"i#{ { { { y{{ {!{"y#7++]]732654&/.54$32#.#"!"&sq֯hq|ɹ˭{HTlt7;A©-+ž{kz<7=2B{@  ob o $ $1/2<20K TX@878Y]K TK T[X@878Y@ /`]!53!#!#5!3I{.{Ij` j`bn@%   ob`$$ $$!1<299990`]KTKT[X@878Y]#5!#326#5!#! H廿jkkkkS@9dd  M o b   91/<290KSX22Y"]@"   (* * ']] #5!###5!f jzkkjkk 3@[d d    dd     M  o b 91/<<<290KSX22Y"]KTX@878Y@   %) $?< 5L FX\ Tm d~ t        &&&&)$ &546 4 7 IIEBFE G D H[[XW[X Y [_______feeehgff g g f d ehjjhusvuu v u w t y||yV]]!# ##5!# 3 #5!#y!7TZ9Bjkk9\kk @gdddd   M o bo  91/<2<290KSX2222Y"]]K TK T[KT[X@878Y@ )(%&65EFVUjicdyz        &&'& ()%%$$/6777 8997FCGGEE KIFIIUSSSSWSV V S [[WYYedeedede e d kllke``eee`xzzzz{yx x y {~||@{{}yuxxur]] 3!53 #5!# #5!# 3!53LP@Iuנjjskkkk?jjZ/@E  d d    M  o bo    $$999991/2<290KSX22Y"]]K TX@878YK TK T[KT[X@878Y@d 6 E [Zf xw       **/::7 8 ? 9 ? 9 ? ???F I V Y [ [ i h o h o `vvx x x )]]!53#5!# #5!#33+uu?jkk\kk,j\? m@#  M b     991/0KSXY"]@  O ]]35!#!!53\{^LzHZH$@ssq10!!!!/jjB*@Mb/9910KSXY"#mo@ssq9910!5!!5o/jNj@ b91290##}-m/10!5mPPsdv10K TKT[X@878YKTK T[K T[KT[KT[KT[X@878YKTX@878Y #oudxfD (~@/!  z!#&` z!-" ,' "*)22991/99990@$*o*x(*z( ]]5#"3263!5#"&5463!54&#"#5>32/퉆ts=kn_`VNvzojsJFIydb));!G@%!zqz` 5,'0"2221/990#]7#5!>32#"&'!532654&#"i6{{6ij@jmd__djuʿfD5@  `- *10o]#"5432#.#"3267'ްeekw?31/0|}f!O@& zqz` ,',5 *"<<1/990@ ####]%3!5#"5432#5!54&#"32636{{6fjjd_76_d)jifVDN@z  ` *2190@ @]]!32673#"5432.#"V碞y,}34ױJqq@& zq z - '6,0<2<91/222990]KTX@878Y``?]]#.#"!!3!53#5354632qaSOgT)CBKNqkjjRkf9D,f@3, )z) #`- -, '5&7*-22199990@ ....]#"&'53326=#"54325!4&#"32653iX`}6{{6h[&&h`ďd_76_dkJo@,  zqz =,,' :,',021/<299990/]KTX@878Y353#5!>323!534&#"3Th3l_zj@jVlnjjÏjJ` I@ z z , ',021/20]KTX@878Y4632#"&3!53#5!C/.CB//C갰hq.DD./BB(jjRk;9 i@"   z-6' , 01990/ ` p ]KTX@878Yss]4632#"&#5!#"&'533265C/.CA0/C-fëH>_UR[Wq.DD./BBzkq!!`Z{;@X    >>M z zqz   ,',0<9991/<2290KSX9Y"]@  &))899HVWggghw           ( ( ( &)-**+,)/6 6 6959?F D D DEY X X WVVVh f f gegaabef`x x x zxxvz T]])53#5!#5!# 3!533Ji‘j@j kkjj;R @@zqz,',0 21/20 ]KTX  @878Y%3!53#5!鱱ijjj@jJ^D0@A  +'z)%! z.#  = ", , '?=',?(,* '$,&01291/<<222990@ ?2_2o222]KTX111@878Y@/ / // 2 ]>323!534&#"3!534&#"3!53#5!>32%5n`o{`o{h3d|Xuwjj%jj,jjTijp{JDp@.  zz =,,' :,',021/<290/]KTX@878Y353#5!>323!534&#"3Th3l`yjRklnjjÑjfjD +@  `D *10 o]%2654&#""5432hFc32;VD #U@,  z"z ` $,5!,' 0$22212990%%]32654&#"'#5!>32#"&'3!53i6{{6Hiʵkd__dkkfVD#S@+# zz `$,',5 *$2<<12990%%]3!53#"54325!4&#"326536{{6hkkd_76_dkJD@" z z -,' ,021/2990@8/@@@@@@D@@@  ]]KTX@878Y#.#"3!53#5!>32jNKͦh6z-c)ONjjTioksD)@A#"$!>  > M !''`* !->'F$-E*9999190KSX99Y" +]@X'' '!'"'#Z Z Z Z ZZ Z!Z"X# !"# !"# !"#@++]]75332654&/.54632#.#"#"&sj|_{ֽTcjutwZg;wv]YFV1-,f,*gtRRCQ*-/o,;'qh@  z`, '/<291<2990K TX@878Y@&&/ ]#533!!32673#"&5ݢZ4FHBkJk]LU_7'u@"   z `,',:,' /<1/<2299990K TX@878Y]KTX@878Y!3!5#"&5#5!3265#X3k__z'Cjjo9k'@9    >>M z    91/<290KSX22Y" ]@H' Scv   %*** * *80HG GWXghvvwxx x x ]]!#5!# #5!#yy++wykk%kkD!'@[   >  >> > M  z  91/<<<290KSX22Y"`]@ $, (3= 9DJ IW ^di itz z          &() & # **%&:;??????:; 9 ; 8 8:9FEEE F F I H HHHHFQPPPPPPPPP R T XVSPb``dd````` b d fb`uxy}}yyyz@y v q u v vvwwwtw x]] #5!## ##5!#vęw'kkDkk->j'@g >  >>>Mz z    91/<2<290KSX2222Y"K TKT[X@878Y@ %*GIWWYXffhhyy       (),,% % ( '&&&))'9?9?HIGFGZYYYWY WUVVVVjjjgh h h gefx||||y wttttxvuuu []]#5!# 3!53 3!53 #5!#TߏLl%3kkw7jj>jjkk9'@Y  > >M z   - 9991<29990KSX229Y"]@ZYix))* * ''&XSUPUPSV V hd``dvwx~~tty x x yuuvvvv(]]7#5!# #5!##"&'53326Fsy++w2zo/c2^9<7Cñkk%kkT|[D;=R' j@" >> M z z  - - 991/0KSXY"K TK T[X@878Yif ]35!#!!53RjffBkVf#V$^@0 !   ! s ssq! %$ '%<29991/999999990#"&54&##532654633#"3>l==l>DVdbVititݓhXឈ"XG10#$[@/   ss#sq%' %<29991/9999999903265467&&54&##53233#"##FUbcUF?l>>l?W"Whtitݔ'>@    919999990#"''&'&#"56632326d]` _\Yd^` a\'XTB 9IMWQB:J\'$um )@a dd )('d&d  %"#$M %o  f'#o!$ ('& *%"   *99999991/<29990KSX22Y"]@://   / & ) "!   ///..)(%'&+/]]4&#"326! 53.546323!53!3wY?@WW@?Y#"HHKrrNH}}Z?YWA?XXj%zSrrP#jjHjsu'&Lq3k'(ud^'1us\'2Hu`b\'8uff'D9fd'DC9ff'D9f!'D9f7'D9f'D9fuD'FdfVf'H^fVd'HC^fVf'H^fV!'H^Jf'H`d'CH f'H!'HJ7'Qfjf'Rhfjd'RChfjf'Rhfj!'Rhfj7'Rh7f'XH7d'XCH7f'XH7!'XH9; 6@   b HH  <2<21<2<203%%#5#p##pFsu= @  i 10"32654&'2#"&546LhgMLhjJ@v+..fiMLfgKKk1.-rBPL"I@'  `# -" #222212<2<0%#&5473#&&'667u#dd\PjsdZt,+ .'{ i ldL@(  seis   I <2291/22990#&&#"!!!53!53#534632Ni q`ydt%UK_ekjRk'\= Ak@;39 (',o$ o?i$B3/9)'J1'*'"', 04999991/99990@D --./5``o55/0123-/0123]].#"!534632#"#"&'5332654&/.546wz 6IYwUOmofoPuVhYwZj8l]4M/7cr%#eiyhTuI6ARz$<T@TN N M  1%=1I 7" " PNPN"L7KCL+KCMOU2<99999991/2<229990KSX9Y"32654&##3#'&&##3!53#5!2"32676654&'&&'2#"$'&5476$}SSTR}*;tL#>1\TSS`׃^]__]^⃄^]]^\^ㄘmmllmmmmllmmLKJL3(DF/DDCpmS[j^^]僂^^__^]⃅]^^gnmmmmnnmmmmn2JM@+    ? 3?' REK!Q9K!M-K1/90#"&54632#&&#"32672#"$'&5476$"32676654&'&&`PWTriwyxvaqmmllmmmmllmm^]__]^⃄^]]^\^=%'mf_cnmmmmnnmmmmng^^]僂^^__^]⃅]^^(z@D  # '%!b) &S"7$S P PP TPS$T 7)2291<<22<22999903#3!53#3#53#5!!#5#3!53##^VVV+TVV}-DVVABBBB7VBBBhBBBhRfO10K TKT[X@878YK TK T[KT[X@878Y3#uf77! z@   1<20K TK T[KT[KT[X@878YK TK T[KT[X@878YK TX878Y2#"&546!2#"&546=0EB32BE/EB23BE!E03BB30EE03BB30E'<@!  r r  <291<2<29990!!!!!'7!5!7!}/H{}?f٠f٠#@Udd  ddMo!ob o~  $ "$ $<22991/<2220KSX2Y"]@ !"#0%O%o%%]!#53!3!53#5!#5!!53#5!!53dZ昤XN{P{{{MjHjjk d' +s@:, +&  ) *& nn&i`,,#* # )+#%,99999999199990_-o--].#"324&'7!"&''7&5!27A| "5@}!"{WWoeNWUC`PXVwQ`YYQJuRlSVVEiZUSG /D@$ !- $'!!0 $U*U0999919999032654&#"&&#"326#"&546326632#"&2TevYQ1UevYQG__KDa_/YYie9XXie~९{⦮u *@r  r   <2<21/<<0!!#!5!!!1Ϡ1yy &@r  <2291/90 5!!po &@ r  <<291/90%!555f$K@]dd  M s s sb"s# % #I! I%<<9999991/22<22<290KSX22Y"]@6iih     89969::9F K M MKKJ@@BBFIXVYYYYge g g gffgjhohojjhu y||yzF]]!53!5!5'!5!#5!# #5!#!!!!3hlR)WSGmjoiAikk\kkizTij;V'@3   zzz ` , ,',:, '0 2<91/<2299990!]K TX @ 878Y!]!3!5#"&'3!53#5!3265#X4Z9^'鱦^`y'Ahjo$$kkkh-)6@'! '!* $$*99919906654&#"#"&54632#"&54324&#"32IH7$$0e՘ݢe WOmVPmmWKt,>bFأ[t}t{wJ@#    <91990@  *]]!#'.#!!>?3!5 nNI =DN)u?$ HNh"%!%)/5w'=@"  V WV V WV22122<20!#3!53!3!53#56JJJJJJJJ'J@%z z =,' ,,:,',01/<2220/@]353#5!#3!53!3T!jRkkjjRj/%#@  & XX&1026732#"&'&&#"#"&546327j Pd@7*8  kOeD=!0 l9TA6?&#Hn!bSA8?S}(,q@<&+))&i-) #*#Y#Y -2299991999903#5#"&5463354&#"#566325#"326!!FP0}WtiWfLMBilea\oFTP30pr-T^FEPNQUah^d ,@ i Y Y99102654&#""&54632!!jklihmliԯP-Lװױbh}')M@( n!iw   '%*991/<22990%!53!565#"!3!&5476$32`PuuNsnccotӶF7SF-W`֗g[`_\gfD 8?@G,2*$ 29z*z2/*<'!6`@ $+-?+239**@22999912<2999990@(AoA*+,9?]]5#"326#"&5463!54&#"#5>32>32!32673#"&.#"/퉆tsmS}t_`V7Ju衟y+z\NvzoF[XIxcc))WZXY}[,L} +@?+*&  ) *& &`,,#* # )#+D#*,99999999199990@ -o-wx]].#"32654&'7#"&''7.54327H&pJ-(oJw>@^CL=>^@Lo99Hv1g=;Ly3Jv56?Ms232? "8@a ! e` i#! #10#"&54632#"$54675332673 M87NN79LjU{~q`m8NN87NM/.ֶ3+ʜ{! !@ ai   1/04632#"&53L97NN79LC{Dm8MN78NN5^@ r 10!#!^=} *@    91903##'%\sB}}`s-Pb;#P%@N%%  %!"!""!M!"se ei$&%#"!  &99912299990KSX2292Y"6632#&&#"!!#"&'533267#5!hɦ3vFbKIPW#1\˦2tGbJHPW\ oVRjVRj8l@9216/$#(!6/,(+! /(/6 6!921$#+9<2919999999999990#"'&'&'&#"56632326#"''&'&#"56632326c]\ _\Ye]` a\Xb^` _\Ye]` a\dZT?9ILZRB 9IѓYSB9ILZRA 9I3VM@)ddMwb91/90KSXY"%33^]<;A+%# :@     Z Z <21<222<22055%)+#)+#ssRssRH# :@     Z Z <<1<222<2205%5s+)N+(#^R^sXXs^R^sXX/ #&@a! `$ $1<<220%4632#"&%4632#"&%4632#"&%M87NN79LVM87NN79LVM87NN79Lh8NN87NM88NN87NM88NN87NMk'$u^'$us^'2Huwu!^@0 obo~    %"2299991/0 #0#]%# !! )#5!!53#5!!53q7#3P{Ryy{jjb_ fD ,3k@/ '--z  0*$`4'3 - !*429912<2<9990@P5 -3]]%2654&#"!32673#"&'#"5432>32.#"h碞y,IEςFIzF}ba`c32c``cױb/10!!bb/10!!b/@  i  991<290#667#667LE|ME}@?[P@?[r3@  b  991<29066553%66553NF~@LE}+?=[P??[%@ i  91990#667%ME}@?[@ b  9199066553NF~+?=[y '@a ar  <<104632#"&4632#"&!!M87NN79LM87NN79L8MN78MLU8NN87NM#u"@91990  9%-9!'\DZ\'<u+@M`i10KSXY"3#7Rh\#/o@= -'! 0 -!-'!0 *$0* $ $*099999991999999907'#"&''7&&5467'766324&#"326^+))-`8wE@}=_))*,_8xEBzQrpqq^z@Fw9^,*(pprs##@Z2105s)+#ssR##@Z<105+(#^R^sXXJ'"}@1z"q z - =,' , ,'0#<221/<222990@ $/$o$$]KTX###@878Y'.#"!3!53!3!53#5354632/^z{Ǯ갰WYVUdCjjRjjRk`Ju@. z zqz  =',, ',0<2<991/<222990/]KTX@878Y!3!53#"!!3!53#53546ף'ٮ갰Vjj@dkjjRk`9;\@1  b  H  H <222<22212<2<22<203%%%%#55#p##p##p##pFE%'BL  a  104632#"&M97NN79M8MN78MLZ@   9199066553Z_WE%ZZ`2@     991<29066553%66553Z_W;_XE%ZPE%Zq L #0<@L|@B?@=@=>?>M G$jjGj=*j1?7`A=iM$>0-'@!' :  - :4! D4 J M9912<<2290KSXY"2#"&546"32654&"32654&"32654&#'2#"&5463#2#"&546WddWVbcXcdWVbcXbdVVbaU"ZܻۻZݦ!\ܻۻ ۻۼk'$uq3k'(uk'$uq3\'(uq3k'(uqk',uWk',u_\',uqk',usk'2Husk'2Husk'2Hu`bk'8u`bk'8u`bk'8uJ`' @@zz,',0 21/20 ]KTX  @878Y%3!53#5!갰hjjjRk?f[@ 91290K TKT[X@878YK TK T[KT[KT[X@878Y3# #ttfJ7@  [[99991<299990@A            ]K TKT[KT[X@878YKTKT[KT[X@878Y'.#"#>3232673#"&9!*0`f[&@%9"+0`f[&@Z7OL!7PKb+(10K TX@878Y!!V)9H n@  [[120KTX@878YK TX@878YKTKT[KT[X@878Y332673#"&` hddh ` HOGGO7u! -  10K TK T[X @ 878Y2#"&5460EB33BE!E03BB30E \@  [[10K TK T[K T[X@878YK TK T[X@878Y#"&546324&#"326sssszX@AWWA@Xssss?XW@AWX#u"@    991/90!#"&'532654&'B@?~p*X.)O#9B,,@p1QY 5-X<f:@ 91<20K TKT[X@878Y3#3#rtfxLw&@   9991/9990!33267#"&546^WC8:$C q|<{/.8  YQ1i?fL@ 91<90K TKT[X@878YKTX@878Y33ttxV)Q@-  o bo  $ $ !<2299991/290353'7#5!#%!53{FF3F{jq\kk\-{a@% z qz, ' , 0<2<9991/290P]KTX@878Y%3!53'7#5!7ꮊ=Ǯf`VjbVk'6usf'V\?k'=uRf']@ G<210##  q M@%  obo   $!<2299991/22<200]%3 !#!!53#53#5! !#8ܼPyȾRPjL66H}1je}kvtfj-a@3-,+'$#"!( `(q.-, #"! '($+ D*.99999999199990 /o/].#"32654&#"5432.''%.'7%5-Q(/l#K4I2%%=_w\B% pاy/"5k7N:QV^DNZk'<u9f'\DqM@&oo ob o # $ !222299991/220_]!2654&#!53#5!#!2#!3wp9jkkj;V #S@+ "z zq` $,5!,' 0$2221299990%]32654&#"#5!>32#"&'3!53i6{{6Hijmd__dkk1@ r10!!ӢD /@   <291<290 '71s33r4rP13p4pq3 ?@Mh \ 12990KSXY"535733fTzj^TZk@6   M   h  \ 9999199990KSX9Y"#56632!53!5%6654&#"FBEJVJaTN^ "hzlKMzBUcLd*R@,& )&h+  #)#\\ +9999199906632#"&'532654&##532654&#"#}I;j_wyHDFb\^ffe5acQLRWFlcHdwdrzJMXR]_JJJCH@A''5 d?''5dd''5 dsm'* 3uf9H'Jq^', uu'6suD'Vsk'&5uff'F^sk'&5uff'F^f)d@2!z! 'zqz` $,',5$ **<2<91/<2990@ ++++]%3!5#"5432!5!5#5!3#54&#"32636{{6Ffjjd_76_dJjujjiZZs10!!ZsBL  a  104632#"&M97NN79M8MN78MLq4p@;-s+!s1edei`#5-+$(#,"5 .!, ",(4( 529999999999122<20#"#73&'&5467#7332#&&#"!!!!3267q+筽/--/c|jz/1w/t0h1"0h.>Eh3 Dh吏7 k@   1<20K TKT[X@878YK TK T[X@878YKTX@878Y2#"&546!2#"&546=0EC21CE/EC12CEF.2CC2.FF.2CC2.FP10@ //]K TX@878YK TX@878Y3#?uJ@!  [[999991<<99990@2         ]]K TK T[KT[X@878Y@)   ]K TK T[K T[X@878YKTKT[X@878Y'.#"'>3232653#"'0)'4`fU$>71,)/ag^CH> 9.dv 7/ir-qP10@ //]K TX@878YKTX@878Y#u?u@ 91290K TX@878Y@/// ]KTX@878YKTX@878Y3#'#tt?c@ 91<90K TX@878Y@//// ]KTX@878Y373tt5P@'  M  h  \<<91<<290KSXY"!535!533#3TNriTTR7 N@ [[1<0K TK T[K T[X@878Y@  ]332673#"&^k\\k^7667u}|u -  10K TK T[X  @878Y2#"&546/FC22CFF.2CC2.F  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~>: ~1BSax~ & 0 : !""""+"H"e%  0AR^x}  0 9 !""""+"H"`%^ChVjq_8 (Bbcdefghjikmlnoqprsutvwxzy{}|~f7q3!JZ?JJqsjqqqdsq)q5TqPq1fdsbqsq{V`9  H\f;{fffJf'JJ{;;;J'Jf;fJs7;'7!7Rsqds`ffffff{fffffJ 'Jfffff'7'7'7'79\3X;d3;#h^5BJ+/}^}fLJ7=#3s wfHVh33VJVJ9Z%Z qqqq)q)W)_)qsss```J#LZV-{s\7RuqfHhq;55Z5dddsf)q{ss{fs{ffZ55+   !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~     sfthyphenperiodcenteredEuroc6459c6460c6461c6462c6463c6466c6467c6468c6469""""Tu_Ox2A?c9^  t  \ U ~(uR%~H;f"sa4{-i4/n/J !"G""##r##$$$$$$$$$%% %%'%4%A%N%[%h%u%%%%%%%%%%&&&H&&'5''(()*%**+7+{,,--6-f-.r./=//00`0112>233H3d344455`555556;6667!7\7~778 88%8I88999:M:l::;t;;;;;;;;;;<<<<*<7/>a>>??P??????@S@@@A?AAAB$BBBC CC*C7CDCQC^CkCxCCDDD5DEEQFF9FFGGTGX7 J79k:;<$7$9$:$<$I$W$Y$Z$\$$$$$$$$%&%&&%*&%2&%<%d&%g&%&%&%&%%&%&%&%%&%&%&&&''&''9(&))))))$N)Du)H)R)bN)iu)ju)ku)lu)mu)nu)p)q)r)s)y)z){)|)})u))N)N))N)N**&**<**--a--.k.$.&.2.8.:.<.H.R.X.\}.b.d.g.h.p.q.r.s.y.z.{.|.}.~...........}...........}../7Y/8/9 /:N/<}/\/h////}////}/1}1}1122K2292;3a33a333$D383D3H3R3V3bD3h3i3j3k3l3m3n3p3q3r3s3y3z3{3|3}333D3D33D3D3333344K44&4&57595:5<5D/5\5i/5j/5k/5l/5m/5n/5/5&55555566K66666777777$77&7Da7Fa7Ha7Ra7Vk7Z7b7ia7ja7ka7la7ma7na7oa7pa7qa7ra7sa7ya7za7{a7|a7}a7a7a777a777k7k7a7a8D88D888$8-8b888899D992929$u929DD9HD9L9RD9X}9\9bu9g9iD9jD9kD9lD9mD9nD9pD9qD9rD9sD9yD9zD9{D9|D9}D9~}9}9}9}99D9D9u9u999D9K9K99u9u9999::k::N:N:$:DN:HY:L:Ru:U:X:\:b:iN:jN:kN:lN:mN:nN:pY:qY:rY:sY:yu:zu:{u:|u:}u:~::::u:u:::u:&:&::::;;$;&;2;b;d;g;;;;;;;;;;;;<<<<<<$a<&<Da<HN<L<RN<XN<ba<d<ia<ja<ka<la<ma<na<pN<qN<rN<sN<yN<zN<{N<|N<}N<~N<N<N<N<<<N<a<a<)<a<a<<==IIII&IINRUUY Y Z Z [\\b7b9b:b<bIbWbYbZb\bbbbbbbbdde&f}f}ffggKgg9g;hDhhDhhh$h-hbhhhhyz{|}&K9;79:<IWYZ\79:<IWYZ\K9;&$-/99:9;9<9b99$-/b$a&DaHNLRNXNbadiajakalamanapNqNrNsNyNzN{N|N}N~NNNN<Naa)aa79:;9<YZ79:<IWYZ\&79:<IWYZ\&&K9;K9;K9;DD$-bDD$-bDD$-b7Y89 :N<2\h22K6K$9<b$a&DaHNLRNXNbadiajakalamanapNqNrNsNyNzN{N|N}N~NNNN<Naa)aa&&<K6 UE@ ^m L GBGSf JBits@ mB'#sVeraSerif6628R00                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     "                      "        #                       #         $                   $        &                   &        _<K Lmworkbench-1.1.1/src/Resources/HelpFiles/000077500000000000000000000000001255417355300201225ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Features_Toolbox/000077500000000000000000000000001255417355300234065ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Features_Toolbox/Borders/000077500000000000000000000000001255417355300250065ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Features_Toolbox/Borders/Borders.html000066400000000000000000000077771255417355300273160ustar00rootroot00000000000000 Borders Borders
The Borders Tab in the Features Toolbox contains options for display and selection of loaded and newly created borders on brain surfaces.
  • Group: identifies the Border group for the Active Tab. Tabs assigned to the same Group will display the same borders with the same attributes.
  • Display Borders toggles border display on and off. When Border Mode is turned on, Display Borders is turned on by default.
  • Borders Attributes contains options for viewing borders.
    • Contralateral check box toggles on display of borders that are currently being displayed on one hemisphere to be displayed on the contralateral hemisphere (on top of that hemisphere's own displayed borders, if any).
    • Draw As sets the shape of the border components (Lines, Spheres, Squares, Spheres and Lines).
    • Coloring sets the Name or Class as the border color source.
    • Line Diameter controls the border thickness if line is selected as the border component.
    • Symbol Diameter controls the size of the sphere or square symbol border components.
    • Unstretched Lines is used to correctly display a border from a 3D surface on a flat surface when the border crosses a surface cut from flattening. The distance indicates the limit for how much a border can be stretched across a cut surface before it is NOT displayed. Specifically, if the value of [(length of border on flat surface) / (length of border of 3d volume-interaction surface)] exceeds the Unstretched Lines value then the border is NOT displayed.


  • Borders Selection controls group and individual display selection for borders. If a higher level group is selected on or off, all borders below that level will be similarly selected on/off. The All On/Off buttons allow for quick toggling of all borders on or off.


workbench-1.1.1/src/Resources/HelpFiles/Features_Toolbox/Borders/Borders_Attributes.png000066400000000000000000001157571255417355300313420ustar00rootroot00000000000000PNG  IHDR~s ciCCPICC ProfileHPY_Das@ 20q`Beqր (5 X wEA9&Tnz_ wrDXTa8ۍ=<k7M wwшnqV4.Pq4n*g\8aЗ.B0M,1Op|HgdrH̼B@9DaƩfa?SϸeVxA(_nVjJrH)3FQoE)A ~0,dq ^' D>i }8A,oφF,-cOxY^+y@%0HA "=~M˻et l!Ԙaina?f;{ :]Y @ <"f3<@ЀPZ@ {i + (pA"HbցM `/(ap @38 .: #A8Q!%Hҁ K 9C? EAB:h TCeP%T BP?tƠ'a f0f~p(ë8Uq _o<(@(wT *6 P%*T= ՍQX4@>04].CC7;7C W 18`ؘH ǔ`1.mcvXl6 [=mcI39q\:.ww7}xK>/oƗkg) A@$ل#6 a(K#:CIMRb=D"iIKIR.tt4DH#1 vQr; BѥR)%c)['Q\Ij@4AZG%B:GD q GfLL̠̤,UB6P6UHVNNWS''wX0EբS-#. KӣiIB Z/mB^NZ>\~|9y Eץ)S;O ,xm  )*+(6(VPTJVڥԬHlT9Kr""EEN-U9ң2*RݯzIu\檖Gژ:UY]GsSza54iZ Z{:&յhӮӾCa$yUYwTOQWPZJրilp61L4,7a c퍅Uƃ&dIIɐ)tiK3mh]ff_mS̏?lfВkYnyˊbeѪꕵuA6T%6[m:lيmmb*4fycfG[tS88&;:.[a'M'SęEÅRU˕ZeJbgt3w5wwp_( ,|^3Ves5 _;~d2'b%%< 4@vGAzA~Y]|`u!Ԑ!!BBw> Q!4\y=J9J\lﲑ;Y~uVJ< Tq&qq\w> +oo,)8YSBq(߉?X8.p ^%$Jz|4y:%"!*& ;WZ_d$IV;޻zB'NҖӐ`ӓ]Psfy懬kddfo~ZZڎu6ZZ_cƼ#޹67%ouoDliS||qVǭG/wն۾ ~.]'laNN;\v+-)޽dwƞ=o{ĺ>⾌}RҖw\XvܭBb[]R=TxӏVzW6UVrXHV.n|槿+(B3y pHYs   IDATxwxT9mH'!! $ґ^ iUW/()D"rED)H($RIosf~L.|LyfϻS!`0!//Q`0vGBxl`<|*i\vĉ/^ujժ]v-A2(--]vڕ."q1X,^^^<3':::>jG5a3g:99wĉiii/_~ۛ9s&$$B)ּ4>{gOO[nK$k*yۧ~z֫ ,سgOUy?lkNƘu׮?AzKվ5BBfϞ=dȐW_}uҥK.]W GEQŒӧ߾}ӧO/))W322ּ ,Xn]``u֭[g @x@˲e˺uwEBHzzz\\\~L-qffԩS}]SO=URRB9v،3! #FX|yttdy!C'(BIMM{g&LP Ü9s:uԩS+WR[FݩS=zWE_<ڰaJ 1LF !ÇͭϔQshp<,' !'2eʅ f͚u!2 !{ݷo[o1sN8YfK,8p3N>=ޙ̛7׺61*k^ ,X~M4/a18QF1޽{7!'9sh4t:BѣG]Kd@1=߿ɓG~&MT͊mh4Mv5a„}ڵ """$I:{lddpuuݳgc_Ȕ)Sf̘1rСC/a搐4{ M/#""ݫjHH[mk_s2&F3`L޽{@>}B?~8k֬cǎYd֦r9oKJJ CllK޽ !ڵ۾}ܿ]vh0$h4Z,EQ$IKKKaÆ5klܹݻw/6L~ҥ[۷OEwm޽?ּ7nܰvƍrbļu~z޽ٳ_L;;;߹s' =<|„ cǎ?~S~* 2?W^yeǎO=Ԕ)S.]z177nݺ5nܸүxgӯ_nݺ5mڔ2y^ziӦMs̱?+Vxyy͟?8B_?mɒ%&L ?uF%˗/۾?rȱcDz0j :ts$/Ѣ(JQQ+}kX0`0_0 ֠g>jj Y}{H##y:q^D:HNvXI$%%mKMbsN߿͛{Oѧ^z};;# A!H%"4TxH[rJTA豰znTMd4l0lذ6:u}P_?cΨ{Z"U|Jk AWbsNV^h3˥T!"[ݢ1!XUA{[ fR[ 1B=~'|-Wx( q tһ7\\\dYVUaRGC`0AB0Am yNv!/_vZYgWr=}0)5USKB0w5p@KvVR&!9T{֭~~~;vw9hW]ʲZFW͛7yyy&PjO+'?/{wuҙ;i) mХ ȨJ{]7n;v*믿Z!mw*TUN-Z0t~ѢEFM6OկdѢE111NNU#:vh>~.^?G"UU !,GGG[}>ܿ$!("X@  [y 6SmSПXT @5USڽѣy͛7-al7Fܗnݺ_[\\\.]T\5Fe~P= c\uz2dEBfEFČfl`b`E% f96j\jF >w̑Dj0;ٹ{G?c{Λ2`wޘ>bp/ xI0т fuE#n64&h'|uޔ!{;^g௾tҀ.|9NbƊ*waoK*ʕ۷% ?5k$$$X_VZ|#GP˕+Wo믿Yŋ>}zݺus*l`+ɺs iҤɔ)Sri;wL6Yf>>>dggn:11:u=W˗/իWPPPOv|ro7>oż~^m4 Ns/F, ~uW I_|`cnSM"[n瘨8}1[,R%pss+WZlٻw˗/۷o̘1W}Ziii.]899:zaz}IIz.j:1NMM=vhZ*=q$UU+x7&N~z0aBrr|{ X\\TXXXj䕢(/\0$$W^Yl]oذaɛ7o^z̙3ϟ?ƍ7mW_788bBof~֯_Be= `BTU'##͛Q;@q@JJ 0B8P|zϦGvѻb@4b6 z7i]_Dv ='. &?vAdWN4'20"V(@8g4!2sQ+Ń;0#ldmz[PMfRM&Ӊ'Ν;ФI-[@ttkܹoY%RRR ;wj1NJJ ~_p|IrrC֮]K&L0qTUU>`z~F|r}T$d[l9|6m6lбcGcռy <߿gϞ~~~VG!V_tBfffBBcd*I/ q4m<`;T?ǬNJo{te;7~>^U  <}i FIV@~V$oP98;:mzצo>ljeLdbdS!Ngyt,xyy[,""šfJu^]~}ҤI6mԩӖ-[>#UkloooZlIOb׿ĉf5R0cR舤NB...yYc2xY%N#qym!yP38t\<=]=æʒH"hDAD {6}wkBZIE@/u4ibm)*qrm( <,@駱MF~!{aͿ Ku/dM~*$FзfY~M#ZhqPzZii,2+ҴiSNՉu _MmDDDlٲu֪n޼922200!vȈ$jkݫ\ :|pQQ5ɓ' 6nڥKرc>]v ~Ǐz'^gxjeY~Щ@hdYߣ΂u˒8.M J1^ WrPh~>_{YE$F#h"]r1|^}bVO~~Fzx0{ɬ~.yЄ/+eʔݻtSNY~P>DGG={رk)S^{m0f5kh(...''guss3Lcƌy뭷7Hy饗rss5k6n8ǐkҿm<@х q]zP].:$ "' า&e=BLuvo$RUi@Uw4DUs5欍p4NnlQ$߾3.vf3}8+$oU%tװ?1WVM֏>=P*z5 jIIuPB,ˎyg9>t2pS]i)o_a3z|%ZAM*`h3gԧ3O[kP2.Zƺ5@O}0\\$:_7vg!:h]v]vmz>~loy wuKnzUܼys׮]W^cV03`wܙ^| FNiyyyׯqƏQм7֫`0 TU'A`<^`0,0G = 2[>B? ߄Çmi֬Yv? ťTQ*$"]8N_~Iٜ]RR$fQz=Bhԭﮪ?l2dYz9q|c!&@QTO0i{iu]zQZM&SJJʕ+W233 5[```DDDF5W =/_6Lcnj%V rwU=5 " BȤu]^:=Qx`XU~g~RMK䆒;*w .&йs&M>u(z-[*D{4ܥ`S.]?9%fYL07[F@t4!@XĤv¾[{t Luh!dgg9p=W %H:'At]R܄K)-{5WgϞ/fgg7n8(((66/=PUUJm[=O/=I؋Lؤj$ z)Guo ` ᜚1,09/萮x DI'N':Ȣ,!ѱAn>w5K*'Nߟ}Л7onٲߏ7ATUxR{ggguU W333w>6lR08z6|:Nzڪ}e=B,Dl0ĩ F-b2.q"^kDNEVr%Y+6vJJ =ۚR sέ[8`3 '|:@l &$&&><44ƍ7ntpp-ǚBe|YyJ&*A8L 3G ~סnD``#^EmF1ٜxS4;6trVqIzqײռ<0v%;ׯ_K/U8o~m6bĈ۷GEEmܸN>ݻwUVuhzZ#<_|-<::ѣ8p`JJ 狋ftJ**߾yϟ?~o}ȑ:/GL 7oܸQQZ)ߤ`B*XT`b|iGY`QY%f 3.1U`[:|Tk0>'I'k%,;]D IDATjТl6[sU2֓1nӦM?~\ծY& ==}РAӧO߼ygΜ0rHŒ*͕$%%_={,d6 Jׯ_F;o>dȐE͘1cĈgϞ}]ְa!nj6mڴ:3_~|ҥKϟ?ߪUŋDTU]t30=Uu8BxDc[YJ:\۷o[-EEE3;4YxxxیUєIҖ)7Wg?JMcŋ;:9v̜9ɥ{:uڸq$BfPҩKG}A1b9r޽rsmvCA@"qP?@ A!QE k0MK Y* =AAAnnn|MuLLLxU///URU5??W^E:T!8___HMKKJJ5o~1I)aaa("!ZjJ:z]VC9CCC322P'Ծm6^_,|g*M_MV5"xI5}-hDA# Z׈V4"9I5"'F$ijn@0 DgELT"*Уh͛nݺ5kܷn۶r8|||dY>uTNN̙3+4~'h]bj݆Oۇ~w\94 JjJJJa;rń [|9sy睜cN6MEggӧϜ9SΝ;͛4=IIIڵkѢŻkhK7n1bNӶm-Zl޼'cfSpFԈ_#<ؗv "ۡ < $hdzuE.%p%R1^p!h3ggg;v,44NMLLlҤ#A>iii4VXhM1V{aa!AAA'N(..о ז}q>sB` =Xތoupv#@ *Mbd-4pCPe ɓ3iM ͘(*:~wsqtt'u(rDD{R׿^UUUQI4u6!$(((((N_ڃtnN۴Z}9U_+vG8!@kB T {} VW@Qz*_]Qu2a@PGUլ-¨xU*gUUmݻwgeeV 3Yfdݻww#--mƍ&L;v,qÆ /׿uܹAeff@AAǿ5k/++kt҄ ]f-sɮϟ}@EQccc`Ȑ!1[l1 }խ[tÆ z?}6 bU)Q(᪩S1 f T~Nq L4~i gΜ٬Yx'' i-[moy뭷vFӣG'xb+ٳO>٥K:p7i$ziرǏCO<<!OO{dtss/=EQV\o 74}8::VuI{NF^ܭWS1 BCCdeeU5<|Æ k#F4nOd5jTk'^O%%=~n[CǤ# !Cz̘6z=\ z(,vQjS'tp c"a`ez=<D^쏅Q8*9^`j(|d0< ' Fz=* QSjSJ!`ԔZa0SC6`0jr^ = (!@P2b0 *bP10Pq,0ڠLܭU`0j`0jף`B0 F `z= z çz=pLʖͿ?e$`L=  Z/,X%OuI !144466z4@ q?-S))0}wu&Jo&F~L%fX]F5D޵? ɷn&qO3@[L]ƻy<=fsfZ3?εQל9egoԀ!on)sF3) `c2]]]OYy7ܨ}I˷~;w}b*U%شiko5!(8򊣣cQQQRRRaah4hД)S֮]{ĉW^yEg}8))iʔ)fzGB0!èjMӈ8 J8ƈDae~@aE|=E9p֑5rFZΩ!&='ݤmO'&@puߩDu 3IAy0 QȋAr6'#9\|Ӷ=3}B 8^vj4FH1^jOkW>p@=N:ɇڵkFFFdddbbbVVm_>22r>>>z^$V^aBfrvvѣGΝK:4,,E/^w:H!zC znhE$i8FFEs8kKKTrם xi5($rZ @O8i5@i%|fK</+A9MS^48}B4nU{ԼZ2SC I%ͽ{С̙3GyҥTX|9-uַneU %%e̘1"?q)))aaaxqz BBB Ihv۵<@g!BaQ+TУi^+qq8@hX9{C^#=7s3Nט&%,$p*!dp)7/ yp!$pM%yWVmE>wm/%omԠ5N?lݺӧ`Μ9;w8yqƕ&={lzB!j󼗗Ν;9,OOOzɚ6tF<†1cBpUT˒,qHV#h5VuQ+ FH:s@AgxvjgyaafrNɒ8$K$ʒwpͼqY+$j%4@_˒!$ҵdITE=I IDATkuN%+i3"n߾C@ӊh9~M%t$r)ZII ZDuj(p&^ Y#j/(:| !!!˖-KMMEƒ\U%3fǷo߮j~~~NNB(((ּOׯ_QQ !!nxa (x^~`Gi/KV(kDFi%Y\ixGV#Qn߸ic߯jĮ'gxN+0w.ՊIZZHPEys5{mp3ZYL=3]=.?nBH^xV+xoVֽ68lyC[dܸ"kuRƯmިehYi9,ZȗS7o9244}֭ςx_rVm۶mvڶm{iP%\RmFGG[L0aƌfj޼yttsx=ztZZZ֭>lj޼>~wӦMYJiyj/8cQ;P?~ۛ .k׎Z&d`Np\u! [(dkɽ8`.5J .E).utqJKKڦ!j();E+*K%.4ƒ"|θ(=>d+[nt: JJJJJJ5_}f977׺PѨQ#a BHzzz UUUUMOOwrrruuf FN<9cvƔ&4 N%β#Af#peYPUʱ@W#A s4`@?e r!G; ܴvixGwr88mqrf,|v<R%'''''{4,>>>+tsk@UUpo?jQ+z=,;NAd_C89zɶC8c|ܹ?j?#t犮eG,sW(zBgϖօǝy{Vڟzu>M@(-ZKP֭[?õkמ$`0*VzB>>>qqq;v黶?6?jR̗w\\OU[S;z=q\``?_Q/^O}V0G^JC*y.2zGMz*W)ȸy`e0;^*1fff&&&:88899Bz*p6lEOe0 =z=p^_^j20 ƽF#˲u5`ܗTzA`Q3zTzpz PQ Cx$C`?UB zCL`0jF zX`TCz*z FmPCz F-`a`L`< ^xXa;`0#5멤wSg0NMz8@62SC끪zuQ Tp1Z(Blzz= Qc^x0az= Qz #0a0`0>L`<js̳쳧~A` ,PQEQEAX+V榧k4KKva: #_NZjhBB!q  qT**--MNN;wq܁]؀R|Ihh MSYY ǏߺukhhqcǎolذW_󋌌k ӦM߿'v튊ڳgO>}lRYYpتf hT*NֆNUt:]Г#BksΜ9۷o߶mݻwΝ  2d֭GMOO#G޽̙@۶m?s988|75oW@:KN^l̙3iii (s?|o֔44Vt* !D4EQEя/)DR(W@G:uj.]t}AVϾcǎy.^r\Tj4|{C!!!zjV[;_k-,,jŸq|||jxTMy M*Ji EZ޾}ٹZwnݺ]tN:aL&+'''_Z{[ݻwy&k666[y/HMMuttv /PB@c#d20֖j;;jtvv6>m>{7nܘ6mZbbb~m۶jժWWWXp!|7|rA++'s@0+P^߂GU*(JB!BUr|[lo̗ [NN֭[+**ʶlСCz|pww?j={veee ,`\gW_NyBWWWR߸qG#zLzBw5 :DQ0UUUZZ>,>>ӳ_~!!!iu?trj 4`_~gϞE͚5!չvZR1F{,[lҤI۷o߹sg>GlABB4CACzPff M/^ѣdgg4~V^ݯ__|9;wܱcj-WQQѹsj񥤤ikk:SPPP~t:TkSZx7ֵ@0s.]4wr.U88sӠUZ["+kPAvɣFRTݺuyDc=4M1"%%e;vl]5G1vvvrxhj TҲ~7J$Z1QAyzzΛ7޽{x#EM(rpp"Y V^j^fBjʪ2&@ 4!׃!@x61[ cz=٦z=dB@ 4f&@0e#zLuKkɓ_{ nXӅ^` "PuzI4@ zxjN@ <  z=S)$0zhP^Odr@ 4!K BS4C !H(B@!D!@DH0 "B#zH!M^^++믿zMт(@Q"{ Z@OFb۶a/Ri︹ 8qlmmJ Wʜi(@gDEDEㅻwlׇ̼)k2^E%B$':y!Ԡ@H8E/ &teڌ8Iz w9Cq1boA{CL/ऽBb;8ڈ( bz쓶!cTR-4!1iIOO/**BѡCN: Î;_|Qyp*gV/yC4<7^^%Tqq)^?}. <^<zVs[" `cz=uVTT{֭e˖M2xʕyyzz:tJEaC:~gg砠PlBնEDDwީ%({%+Tqglٲ۷o^e=sի/DZZ Q3lױC!B&[K @ "/ Nj(U%cu=1[u`mm7~mAn߾΋8FQO?m4 [l`Ycp,>rPX!wK9m)})eR|iK9m)We ^RE5DczjL*&?t3Æ {嗓KӧO<G=s挝]Ν՜uܹ?sٲe_~͛5kЧO?$oL-vB"! J%% "@xQH27RiABv9xnܸ1mڴ~m۶mժU0L~8o͟??;;V*6yCBBz_N0aڵg׮]6oޜ9}tWW7|9ߥYS-<~= ѳ󣢢Ν;gii /-h4ҥKxႇ+ @o\'''Jw{nyyҥKKap=W^dɀN:|B;lە2Z!)(Jg7AQDQD2&p=Ah:zLh׊+`ѢE5 {aaaFFFYY]tqtt-,,ܺuԓJIIY~E/={6o޼|SN=zh߾ Xo<;\E4c\?ի  4-]\\k@tv`…~^i"M۶m3خ]ӧOϘ1m`$mW(@N9{MgbDDQD@@d u['!!wW5 4h/rϞ=)5k 8m^z9::z{{W<55ں_~!4q;vTUU-\߿wfҤIvahfjsiAd2Yff&0]tqٳ>(j۶mvvvaaaK&0LN~*zIUED_~TRKB)gTrZ)grFP R(fcNi4t}%%%666E5 ܹ#ILL1cƌ3q޼y ^3rR+77w4MuhBHT6LUUU}vvv--yի]v>zm\:t?ϟ?ݺuvvvǏ ޿7ߔ㆕:Ϫ])ḍ%HЩkWS{<^( 3 &RB1=\ B`F&$=#G6lح[T*U۶mq;_|qm۶*knnn;vxÇ%Bh̙3g4d |rAAZƀ.\PQQj5ϰ]`T 9MQzS^=lw,rDH_ūL׃^o88\P(!g鬏O1ܴر]1 J_wJҬ\ZʮVіMBУe++ˏܣEE$E 4CS%:g ;h~^A T*՟ٹsg\R59_AQSTTi^ܠ(cǎf*((x)Soa;;;GGGLfC07Brյ}!4577I`>I%jԨwFEE͚5kΜ9YYY3f k+:0u~ᅬY</]dee KKKjժٳe;88 N:U-z]?.[EuYlmmk=rƍtSLjJR*cP\\08pO?4~x|30w}W^xQv!ϟ0L݃Ͷ8GM~$b׮]O߹3"##WZu~{1JJYxo7r˗/MΝ;L}S_~g^ .Ƙ1+'OԀ. #=fT[#H U^+ ͹Hu5OZݺu/Ao&v'kFN/YPPеkW2g\:;;F:{JW^:+}||܆>y:t-;;!(zG cV}[>ǹ[Eu𰳳KII v ghYYY?Im655Uj^zpS?={ܹsTN9B _׫gxG{O>?'`41ߦM(Ҵi?J;*o/h~ gy#/^v9::iA|gG߿N4h/rmY;:Sս{˗BY>鼽q^)HF0>3[Gus뉊===ǒ)Z~}NNNPPPPPV]n]A.]֮]lٲ_|qC >}Vݻ1Pv|5^>^_gGWu^P=4uCh}ZzeXxe}:uj߿,rq<ϓӼ D!D!!z=$ZVYXXpsqy{Eqy<qJ P5KǤ'==… Ɩ9))))))%%̙3Ʃ,DE\q(nټyq7oy@P]+z=->>gϞrڵ{~LXz[nEEEnZlٔ)S󼔎eӧO>|8&&Oċ/XO>gS <!4멥 8fff6IU Xl>NLL\dѣ5YtiǎfTVVu… SN+L8|ܸqk֬9y_~ib?WS_zqBX*0mϿ444͛ MSYYeff4;** n[-Ο?|x׮]{MHH?Ma( HyËXMGMzq\NN`cbb u֣G@~~믿pӧO3 -{vdn0LTTTFF… ?S< ,^x۶m۵kא!CRSSoݺsκ|Qlȑw߿]~jvڥ1f Bos$%%KxqFٳgrr2$''GDDmܸڵkwrrZd ĉÇh4o`9sCDDD^^ޓ-BУ̿t8i47z ׻;vX^xŋЩSG6kkky0@PÇҩ>(,,mȑSLСC`` N_r\Tj4\^ʜ9s:v ߸qXTƍ'%%͜9sSN~+WV'q3gΜ5kցw뛙y'sQi)Sѱ!> >}zff˗ == **E{iqq(FFF24G|H1kFV4t 7nsB++Zjfژ1cxEM6>@FFtlbV322 p2)))ufnt:WWW<6Rph޹s;wDU7mީS7oݻf712 |j)))xDyA^Y 4`4yq}prr2 _1c"""_%Ϟ={8 xFc /M@,k0,,,|/|ƍ#F`(t,˶jFT*}}}!Q20EEEeeeXXX̟?J0zhP^OU%$Z7JRVgeeݻw`0Vnh0ݻVv$h^ nJWFFFNNNpp0^O>y>--3::ZZ&"Vwvv2eJBBBjj1cZWՁ>XM =8?-%Ld.[BpXeYNԳ#KXXիWi]B"c(h@BZcz=˲vvv,r<ϲ}N:Xe98{{{I1+Zqi|2nEYXXpsqy{Eqy<qJjChVL:qѣG>Խ{^z=qU©uBddd^.\hl|ݻ񱭭{ΝHUUUJJíUB"\.8y-7<8nsx^P(qlc3eʔ>.^4xo=h kkWZYY[. *++׮]ۻw&=(^zxUs޽cǎ8q{Rjp&L05? |xРAgΜaÆo>HIIƅ?裰0___77#GN2CYYYR|IXXX^&OSG:uj.]t}A GCc P(C)i7|?^zOP2))):u*((>z(xJe0ʜ9s:vhʜ .\8y$nUƍƽ0WW7n222֯_4sٳgO:>W\[eggϜ9s֬YHNN޽offדG!(Bz Q$U ҳgϘK.YZZ9r˖-G޴i4IQt2vXzt钗WXXx֭۷o:fQf}~ծ];0 j?7cWV-++{뭷ZSCc999FJHH y;T*UAA>޶mBZlٷ~J+Wq{dddLL O"T mll>ӐӧO8pҤI)))Ç:h4eee%&R۶m^չs_>hРjӧ=zTuҥ|۷o7VH2& ^rIj-eII >C*rppjϘL)2>~y/Q)2?L׆믿N>B"BONNNTTo߾}7mڴf)"L4I׿덭3??G={dY֯_j׭[-aÆoYtĂ VZҥC.X ??!Nnܸq֭ݺu ?~< Ν駟UۿR&!!aŊx陙/_􂂂VzhizܸqsFFF24G|H0ݺ!Cdgg߾}[RU0 =zz1x`8##C:vvvNEEⷎ;4m0%%W B{k3yKKKQ۴i3yɓ'U]~]:ӧɓ' ɓ'O07s*SǡC˱cn߾vyOR )Z3gh۷/ =f =;xeR26Mo߾'N(-- EQ666۷h4Dլ y`0hZOi:q\h XaAViulx,ߔP __ߖv0 Sm8eIDAT,,,HΉV) - = @ $T@ 03]vH!f~/Z&+**$KeeeZZ˗˥(j/""g!$<.P`koo˗;99u͞=l0+Hyqvv𰱱iiGAiiE4OOhi(X%yʔ) cƌi]g3aYދ5.d.[Bz=Nqk0,z}d z*NK0(:uCmi_/ZJ.cz=˲vvv,r<ϲ}N:Xe98{{{I1+LϫXk3Z,[q J!6nZ2Va,,,89N{#ݻ8NRp3tu~ a( @DA_m9ϫn&sA'yS"\.8y-7<8nsx^P(qlc٫FXPFB@?hR_~ܹ~y3gΌ94Obbƍ:Ν;ںftGG@cpݴ4ePPP570%&&^xW^&LhV9sy |0XRUJBCC/U؟7$< ع# nHXˬO>UQQѨܹ{=%81;vl[gee-X`cpL8q֭~~~k֬1^\RcicDAֵzyty7nxy4#νuF?`lNjI0 ᩩҥK;vS*,ׯ7͍ڵko߾z 8pù*jԨQ3fڵiܫsA@*<c~nT ,=znAa_w%rwtP,nذa͚5!!!#G߱ٳ'N )**H1cFhh(qv튊ڳgO>}l *..Ǐoݺ544tᅬ;k׮}ɓ'aXB\^9sfiii?.\6m_LL:;(^z /h4={LNN䈈@777^vNNN8c2xyy8qb70 3gtpp{2pu'E@y+pi =2 yCoڱAٞ-?ݞX^k?@BIxO',zZڵk+lï\cX.!!V\{naΜ9/\ $&&@tttppȑ#wݿ۷'N/?j޽{QQQ#G<{ɓ6Et 2d֭GMOO뎵~saÆ k-p )HWW7n222֯_4sٳgO:>W\yM|vv̙3g͚uݻfff^~=))1BBE! Q Q$& =rE)Z41֥TۄxB(ZFSƴzw~WXXŋaCivgFXXXꔔN:=z,--rRh4r|ǎ>:vV۵kQ(}=F@ڵ9sT^z?;^;QW^j5>~С111:u ?a 2cvnr]RXhk Rh{wI]o!Z z۵k_GʸFP[Qԃ4eBBŠ+333;== ** [=4M4=n89H####>cnH!NP2`# .vȾ @{Tm;\6mZXXСC{LY… {h&M;w~^ƍnڭ[c{ddd^^^HHѣG]]]~-88ϯZ+С?خ]^pu]QFf 5?zHHHСCHHȄ -[;wܴi>n߾M{oݻWV7C&0ѣBڵciv`bPff M/^ѣdgg<怺8nk\x ֣Pgbռ{IIɝ;w3MVVVF^_YYiggWUVVVՄ] 16m4Y!ֲXR~׮] ^͛ H#hu_մIOܹ3ܼyW^oq_=c ĉNNN͛r\PT*BP(rL&qwy8㸌nݺ eF#@ 4%FF;cKH@ <- >!AzSe0*++5M׮]+++H!OZرzJTT<_EB@x*ZAa\\\T*UeeL&S(2L&rLV*zS!dM6:Çws$&(KKK4.MW/g EQ 2 = y-rٚb=@B@hrnݺwݼySEikEC.IENDB`workbench-1.1.1/src/Resources/HelpFiles/Features_Toolbox/Borders/Borders_Selection.png000066400000000000000000001354751255417355300311400ustar00rootroot00000000000000PNG  IHDR~3 ciCCPICC ProfileHPY_Das@ 20q`Beqր (5 X wEA9&Tnz_ wrDXTa8ۍ=<k7M wwшnqV4.Pq4n*g\8aЗ.B0M,1Op|HgdrH̼B@9DaƩfa?SϸeVxA(_nVjJrH)3FQoE)A ~0,dq ^' D>i }8A,oφF,-cOxY^+y@%0HA "=~M˻et l!Ԙaina?f;{ :]Y @ <"f3<@ЀPZ@ {i + (pA"HbցM `/(ap @38 .: #A8Q!%Hҁ K 9C? EAB:h TCeP%T BP?tƠ'a f0f~p(ë8Uq _o<(@(wT *6 P%*T= ՍQX4@>04].CC7;7C W 18`ؘH ǔ`1.mcvXl6 [=mcI39q\:.ww7}xK>/oƗkg) A@$ل#6 a(K#:CIMRb=D"iIKIR.tt4DH#1 vQr; BѥR)%c)['Q\Ij@4AZG%B:GD q GfLL̠̤,UB6P6UHVNNWS''wX0EբS-#. KӣiIB Z/mB^NZ>\~|9y Eץ)S;O ,xm  )*+(6(VPTJVڥԬHlT9Kr""EEN-U9ң2*RݯzIu\檖Gژ:UY]GsSza54iZ Z{:&յhӮӾCa$yUYwTOQWPZJրilp61L4,7a c퍅Uƃ&dIIɐ)tiK3mh]ff_mS̏?lfВkYnyˊbeѪꕵuA6T%6[m:lيmmb*4fycfG[tS88&;:.[a'M'SęEÅRU˕ZeJbgt3w5wwp_( ,|^3Ves5 _;~d2'b%%< 4@vGAzA~Y]|`u!Ԑ!!BBw> Q!4\y=J9J\lﲑ;Y~uVJ< Tq&qq\w> +oo,)8YSBq(߉?X8.p ^%$Jz|4y:%"!*& ;WZ_d$IV;޻zB'NҖӐ`ӓ]Psfy懬kddfo~ZZڎu6ZZ_cƼ#޹67%ouoDliS||qVǭG/wն۾ ~.]'laNN;\v+-)޽dwƞ=o{ĺ>⾌}RҖw\XvܭBb[]R=TxӏVzW6UVrXHV.n|槿+(B3y pHYs   IDATxwxzB:!E!@D@zD "`WX.(nA Ki!vIH$@yxx;ݓ|egg`}eK^X~ 6(( l6FݻwK^g+!!a׮]  ~={ EQmVTTk׮'xT)y]]]K˻C^^ƏaT?O>ҷoǧ,^^yB?}RҸO>S{ݶm[Ey3g :488~6lٶm233Bku֭%|Æ srr3foFiܸܹsׯf3z\($I֭[pp3͛$IQ+W;2MҏO=gϟk׮2$cR:tXfMXXXc|7t:kV%(1!dƌC 9sҥK.]Z2&(EEESNu떯[NZTTDϦ2Ο?͚5!!!k֬Yf=!/,˖-ڵk\\܅ !qqqƍcFq֬Y;vر㧟~J7o=ztǎw~RbXFsNBHnnó;eT?[=<󼓓(͍yB㲿H!!!cǎ׬YCr2$@HHH=_O?m۶ҥK5kz V\aÆ N>}ƍ}QXX֭[aFczz:b>}[ny~ƍ޽ Μ9ӢEM6|gv=۷ >[nu۶m;yϿ@-{={r ={4hdɒ{gOOO4mΜ99-zsss1eTe+?޽{Zn^ƃcq\:uccBȢE!O6Lzrȑ+Wdgg?=4BiFϳ> fy޽'O=|ptttV899M0?OeY۵k}qٳgдiSIΜ9mwիk׮ݻl!'O6mȑ##""nȇş)))ViӦׯ_wҗ_~ҴiݻwkNL-[sY֊2$Y `ЫW/ݻ7B~w_|ňGdrяEEEF166ͭW^֭[oٲ>߿EQ,0L,ӏ$L&ͦ$Ivl&p6Ag֭[Bb1>>>n|ꉊ_sss7oޜgQĉv{n'''ww?ׯ_wtbt_뭷wݯ_=z|V&vuu}vz%55O>A96Ȅ'1ƅK,MJJbsṯӣGtb=z,˱0`@rrrVVV--2&X||>~Ν=z9r믿>|qƍ3sqqׯ_^Z;s֭O>ɓ.]zQ]֭[^g׮]ڵk !&Mz饗6lؐ8k,{;˞>OΝq\B _{w}cɒ%ƍ :ŋ?>|ѣt`4M4˫]v:tCYpQUݝ~lcYF#,PTTD]@nn`, !DUޫ|:Rhvss+uv!pB)Sbb˗]|4lаnݺw-^2txmRaV[U !%\z `<9Mrss^UDGG}-HΜ9}6V碢*  !z"1vT,ozj‹Xn!' {޸qW^)ԪVϖaWQ;HoT  9R'" HX,5+9%8/oaÆ߰_~iӦ jOTF1%Uq&B@W.]:w!ZS,߲̝TRrUbV &/ 7:PʒT%L*y栠߹j`+gdd-1-c*_l޸q\]]J^l>766^̗򳕟~SMS*,BH!͛z}ٛD}>T999|00~/oQN=/zvJ҂eue/J2&@:cQ`},9׏=jwuu KMMʐ+**-J:{L-ZЏ"UҢ{C..wU%ڷoo>8~w.^?D"M!j>QG$ Y&gE—`h\=漷fZ>1ɩE@T\\U*;f- 6lذc 0tcW믿V2WR_,,OWi"xͧ YQ8g7x1ӦkuDQ1y۳oz1Ųfdʇ{8^L  8߭?aәZ  Kc&, _~?=ᓟv!ق ў<@Y˭, +VM4* 4MxRƍ{u={<3˗/o{Zsuu-,,LIIAuvFƑ#G f0sh2UyqrrѣG@Q&hW_믿>~„kqyׯL«WWƱ*yjĞ ,?l2*1^nݤI6ngM>ܹs<ϯ_~Æ _}j%9Ð+DIV[SxdozIX,'Nǝ:u*IO<ѸqchժՕ+Wn߾%<<< :uoj1zjTT}رctK?011WUƍ?~|rri?>wkSa#hժSsnڴܹs-ZXn]UÆ +/:O'W\Dt"H<F YI> <}75k_B&4մmן7/[f[[.xdX~[TbG#}l6LNNN<;;;{{{wΥ;wncǎ+^*|~~~pU7:w\ӦMiFTڵk&LذaCǎ7mSfgggff<Ըqcۏ688믿>vヂf=R0#\舤^BnnnNNNxYcXxER$N8YE8!h! lҤI͚54aÆ+WbŊɓ'w]/v_T:Z{Ѿ}{wwW_}u޽'OӳW^FjZvⲲ~i3ϼ $Kٮ 4;vcHJ4gߎz Dtq8|.+999S6woK r+nB^-dW:(U4M-r)hZ:^&M^fG;ny=Jr ![Q EEEzo=lq\Y.HYEQLOO)i[SVXXNNN쎚T355U1PӴZrc⤓p JLFΆJL1\]|*H;Udr,EoAS8]:)½y{{k{޻Wv/_]@@@եFQ3UUoܸ}˗/Ղ1Y;g0Ɖ۶mKMMr>B_iׯnݺPм3ժ`0 4M%A`>SNO<]Bi/^lܸHEwH_lJk=1O/ 3n{xB&#TBHff殟5 r,]QFYv`{ C!d׮].\̬[nhhhll}P4N[=O/I ,آj$ x)}Y4 6㚜1,0:'EbD Q/qkKGag=<3&j?|vy晼~;K.=w\&M/^\vC%:iҥKk.OyEJڍ6] A 0E%    7i9&,y=wzYuKzѠ <Ȃgr뙙PjCB׀`Xb6w3fгg.]ZjȑwvwmҤ -^Oo!1QUFG5Z[SwY틝ŭ[n;v\~K&Ìoٹc6mڌ=zeӧ:thĈ@`ȑwvTJ9aA@%8DaD}Dea8< QdI,$/VJ__߄{/_󳟥.Mrss~eQJBO1@rJ^TTԠaãG...zOJNEq!ԤIdZJ8e#""222P'7 /bo9sFU-m)cJu"/,=dQEA'DAyY$EN9Y$ij "ys}zO?3S+\rpww+fg~~~W^8rM-A"QW_-kG+WI(7+fp#.88(PX2UO3* 44D^8fMF4 4jM]JTD3DQ4끁,Bz`Օ\ Cqw* .AAx_顄K, ]M0Ssxѣǧ~߳g'x>S Vb Nc&M35B4BF@h 7G=[TMQ|}}YoQC@4@U4 ƀ5 Z8.44tiiiIU>(,0j&60l*j4«h6J'YB$89`0j  0`@8A6 6ZMfv=] ƣ0`ńP1TF8 j`J`ȾO;!5 BŋM&Stttff7n޼`'h\dIzŋ[,??yt'N89 dj^zhıYm!BDt:fT.]wwXz3݇~ӵk7x#--y7nѐ`T'#2&XUU canTU@\\\Ϟ=.] Z~:>QQQ7oL駟Ν;A111;v([ʕ+[~9sMeiu( AAA@OZlһwoZ|7#FزeKLLȑ#O>dxxssseff;6,,M6_'|2cƌrܻ^(Fh4h4Fbcchzju%''>}ĉ]t)-[e˖vڽ eT~~~-_ԩ@VZeX ???֭[Y7RRR֯_?nܸ1c׭[xgϞ4hPzz:?~_jU~222>x_uܸqW\9i$wwsM4iΜ9XxqPPݻ CW`JAGew8CdGńcǎ )O?)S"##ϟmo۷oTT,˭[g 1޶m|WQQQ 6,_^xqtoիW?S={tss1co Ê+4i_< /(Ҿ}{7n8p@SSScbb\rmzO>/bhh(q])Q wN <ϗ}Ez!iܼy3((H傂q̘1iii#Glݺ%KBBB]^^^gϞmٲci;w\xfwm 2dC]jՋ/X]ty7ڷo֭3g޺ukĈ(5JLL]]]vƍ`ҥTLL̵kԩ>>>wRrz=:NEQ%IeYW\={cپ={K4w<_ns4^vmرھ}&Md3СC7oNOO8p 1b֭[{ǗR~}{Hx0{KhӦMwRP z=Hd7R 4 9a6/\0j(xWKpB^^^:u:w8sĉAtqq)믿nܸq˖-7xne{͚5[|^jMVzѶmΝ;k׎ &Scƌ1=\Eɇ~3jԨXxt`O>$<׃?[n{׾}͛ egΜ9z3*JsgTUuy߿g͚Sќl777F#ЖG}s~nX,rZ ʝH_y{{W2'ON[{:Ȭ™D NNӃ[^;p@EQ6miZA6oߡ2ggÇL 4"|}}Ãػ{~nȲ|׸2 y+RQpzrzJ}FEDD O<\Z?#F[Ș6jԨW' oPe*!;Cef0*TDz!s1&x5Wf0>z=CZ^^d1 :%w2?ǻwq%Zv۬Yzr rbV|&MS9^>~M[F(w?v'v*Xέ5ŵ B TL,a.5L&SMG*TQcL_-N=@sYo~קEgF蚏;w-k}p챽^u飺h0W_.7Z5 4N|cg܂"K/jf-ߤXH.q2j1ܥc60z꧟~AM6/ڵkcƌ9tPpp?Ogg ZhhѢƍl«WgnҤdJu+ 0d2fXhK/(ʼyyw}nܸIG222&S& ;w͞={Ϟ=0o޼~]6))I$c0e AJ:*c|>Z0ȑ=74_sSUf™ӗ@RsUJ.Ъqu꾲}.[8v߲Y='egLyztY䂔\[ RL~C_[{uewYoLǢ;|ŠW[68J~pP1شlXQ T^NO=5 FO?eff`$IyzzJҥwb.\عs6m<å(11СC]tIKKNHHݛTTq!&&\Ayc|S{?ƥSPv꭫go=/3= " ;ce%% s2Nn_Ѣ;Hgtμ(^qD@wvﷀP3dGDOrb!tfO i89&F!&Hb0AèKGQ|IW_ UV@QQ~)SYm.K}7B,z """555##11;=NNN˗ӏ͛7y󦏏ϲef̘ѡCI&͜9S~ʶ;mмEx}ȿØ G,C c 2nz dR&AnFR>cu_py$d|Afh^) ;& $#I2ϣ2 !@_h`LDlQT^}DiO= 4M۸qéۛ6m6mDqGWfĥN%$$C?BANNpNJ֭ fԩǎ?~|PPиq%He'"T9jE\>}m)4"B'(Rұ:jDt"HG Wf&^qS$1Mx` '6 ,Z=jzEys@۷l6_+W_MHH8y$B(44ŋ4.Ȳ`{A !~W_vMXQHHHlle˒B&!t MڶmLg EII"w8MzqE?B!%E٩$rT"s)TDN"լN8-24S(WD)J+%_b IDATdQIbnCMEoщBNR_~n}X4$r8ND$(ȂNt"E^ЕY]`T*TT,XC,]vԨQDEE_ѣG4oСC .ԩeX&t詴-Zhf[4ͤI;s_|JrO?Tӵlٲu-[t/RdddVǎkSʢENTdQ',uNW%;/|Gܧ ⴷ 3QK:Y!Dˡ ^9wXԜҮ_RdI/ 3Qҕ:Y2xBnFʬ >N:)Ar>6-;+:Fy EN.rID{ ݩ^:~?ϟoݺ5 8RiiiuԡfhxxxB///dz޹"(*****Sϥ煅`xW֜ iIeEW*Xh6yTT=c.f+v/qefCdW:(U!rxA#DUj7>d{%'OL7}B0ZuՅs҃,FطhEiڴiBEF#c/؏ `kΫT1<ڜ]\EHIᑵ\t@À540A#eբ#ltRv(n6gdidD^tvv jԨQÆ k%0j=z=&!۝:u4vX IeՁf+,,x>}4j^f?H)~ Džb!DUS*Mvu…^z)$$~_,,w166611߿yfϞ=y7wcDzjsscO?%$$^^^!GB̙3?#zU#yJ]㱂\JUC{r}СNNN׵D9NJpvv>|~иq7[7oeBUyۻw޽7dee5id߾}wrϟ߻w["""x׊B-۵kwȑg8? BFr r ZQ9Z%%e۶mWcc_~}˿``=Fq߿)nooꂭ#G,,,411R(|{{ŋ===<Y[[ϙ3˖-gf"|=#ux¤$.{7oܽ{WxgϞ9::7ECCL&8[[[߾}򶵵VWW?q\lmkoFD^{֭ɓ'gǎkmkmmmmikmiiiyo߾mkkr88N&Lf[[[[[ UZZ:c Q,33?hmm3onAaݻ第,kkG~ OR}=jJx񢟟eTT̚˗/sI:;;i42$ qC0/_:;;.Q=]===mۖ?+**0k " 1[X;;;%Tܜ\EE?y_~;z3((p7nDGGSTO "wkjjWWW_`AB|XPRRxCHPWW-qرuuu$ 0YYsM4?ooo˃y`D 6q'qd2LQTTdQ,KNN?ԴN8NјL&TPPa28ħp@KK ;|+$t8PEKtuuҺu411w `0wpD/a\.D"l l 妦&:N$VUU)++2ƚ) >Ez}=`z8NTTܹs ---'O%#HVǜ'*$>55I$REEųg"-oq>{דDkYC\k= &M""7S@|;wtttsssoot}}7innʲ0 kkk8my}ǎ;w7UƎGl a Xq :U 8nii#d uA{{{ CSRRo9ħ try<z|yq`ii9v6p+rrrZZZo߾Chkk߽{755UWWXMM->>^VVDf@L$ ZZZ$ O&í---"##LMMZzBb~"}=d$򌥥ikk8acc&e[.,,LHH ԤRk}捁A:tvvVVV}GDA9'ųgVK} JtpL2MYAW'Hرh֬YT*‚ ϯFP(DCg>mmmqqq,K]]])u? ) >A VC h4ڸq㺺ۉDLqŎqh4[IS䝯0'|=3u''m۶?]]]!S>}u֖''' UxȀHFp2 =5deeGᑞoQ(ٍ5JBͥ_NIIyQ{{g}6qD-->x?~!x O }=ø _T1F|ӧwP0LFFFEEE__xĈ}k';Pf_Ϡ@&UUUY,Vkkkssskk+vh򲲲IA\_=} BNNNEEHօgpt]0 Ac1zB\3_# !ЃؼA q}=( z\|=@ }=.& A1`$0 dH dp4( #(}A@ bzP[0aܾ:Ll6Eo %] e꒑=qqǙc~`/HWW<8̈둺pRUz-lf˶sep8l?Ⱥy`kן9Hռt'O<~xVVqx--qIl7nܐ}>>yyy?Qii)~&)''1.\PSSSKKk0}…^rjSS۱c޽{***W())i}>y!$A_i1bDDCCCmmmQ6lԴp8*74]>s淭,xխ\1BׯCCCY,͛7.] kL:::G---a憆_|9&&׷tʔ)jjjyyyK.Yfڵ.x5k߿VC^~]SS(++ssssss+** ݶml6;777((h̙III7oބ_Ӓ-[UVVٳGH1؆@^^ӧOΝ xq~ann۳ٳg?qܺuMmoo?uT```=wܟ~IGG^pb_ڢ_[!dggo޼Nw =^7o_o~aׯ_ܹSWWÇaaa'N8w܋/p'BOYY׏?X\\L&O _~ӦMOzdeemmmEVFAVD4=`-^=_MqbyYIrz ,Xnپ}jkkacrrrD>n8ee刈o:tPYYYWW0))ۻ&;;Bȡ(''GR!戈 BBBt3<==,ݻpB ++Ǐddd9bxzz*++GGG}||ժ94L(++ 9pz ĥӧ999`-?͛7ӦM۽{7)NqF1L$ SSӿ+##E& EEE7nXvɚ5k_^RR6m244$H;J]nqӅkhh߼y8555p:B_2˚'ˋ,UN+*+Yy%ǦTWVSVUeũ;`öҸ8[[[kkSNWVUTTGn8mꫯzOQQј1cz رcO>e0K,!һ|}}edd !DADqƍϟGƏ?0ضǷ%%%qqqBć#JJJ&)KM* H+˫*+NX5r$)ު4y"EEAVATUUرɓ'W-ȑ#ؕ6(/_$"TVV+ޅW7oŋO: mllO+2999>>>dAg'sB0 JRz$yyy+W L\\a: [=~ LLL^X,o߾ l0' zdab5TgTLU*EYasڵL532jj :LʓpN>`0N ***nݺϧP(}uuugggAA)S+ ---ͽ?̙񲲲7p8)$ kܶm[MMMPPPoW8عs DX[a rbE4999[[[]]ݟ9&&F;ٙD"؍b]r%>>b۷AR { "ގdɒҗ, IDATQF?LLL"""֬Y3b++3g[Ny|NOOe\_-XSSS_znnnɓQF`=޽_%V͛7N|rƍt:"##t={̿J^^ܹsN8tPjj B"rssG9rȒ-RRR011 zX,V\\\XXƨQ>F'C1}=XZZ65Gnn6YP+`w0s<@_VvUotqqqvv޺ukyyX[[KI$RKK @ATYY:M۷---,@oGy捦`?z'+Z'***@[[[3!(!o߾-,,Յe@mo 8|L&Ǟ4Mȷ$$$:J?cF2):?5%y%%PT4b6/5kJp8R7qys#o $(  `((`&AA/,mv=zzCIIJ"RSQQܼ?{*_[nk/^hѢ>C!6jdؾX0 ;vw}WYY[544h4这 .RJ7-9s&~,}JmiGL_ raJJJ @H_PCO']%KKˁ.0Ὧ a_D c" q}=&@ !4@Hz7@H izuX ]31yp1A_Wc[ [$uzjUUL7n9Qcbb2wGׯ_P(DD==1cưI&u?n @ 0cl_Op8QQQDm%u޵kױc~w#Fϟwuu0uuuO>7p &&999ǏP(:"Rilinn~ݐkϞ=o޼?wի9?K0\]]W^]RRb+**i4ZFF֭[w)))RfY|9K,ٸq#.**T׃08}= zIqqqLLLFF$ D^gyyy2 {R(bرc544=99ð<<</^tss2akkKLؠWUUpL!z M:JKKϞ=;o޼/vؑ%%9--/Rjƌ)))wnB 1}=C Κ5\% y===SSS?~z;vD]]ݻwnٲڪ\\\ʒkkkMLLn޼iaapBH}}=J1cƍO:ٳOj+b01'$ 8jIuqWt/D,kѢEnݺ &$&&Y,ۡήiʊL @qqqvv68|׮]]]iE\_]]]??? p:l/nݺpbbb廼<;;~5)) NvJe˖eff+++ CNN.""fw^z8<ףg!h4C::2@N8aN7ػa:Ag:1޽{k``0uM6q85k;w_vvv&L,… SL/..v=@"##w g300e!C}=6l񀀀f&2i8N{/{wI$vMc2Lvqy7*++kjj` b~+Q!.((022KϞ={٥4 ̇@ !HoJ,k  B 73! 3J .fF  #adO_4r3 /IL,،=:** 㸍 丸8mm 2 ")R|=ؐHY\vuȑy󦮮nٲe111&M255eDfA3 ***((hp*@ g"Gk=RfOOPbFҐkk\8[P7ߜ={jR?!zB7d2Z[[o߾@̫3}/!ZrssEA7w}]3$m8fnj,.p;J577'ҳ>LӧMF$Bstt4!~.,,TWW'|=:::Ű lƍfff}R8#w㑺z䆊%#p3ˑikcճf%wtqepnfq3tuu/\ 322/󂂂CFcc#BR;::۩T*pAAAȰH4% ޽{#ƍ ϟ?,(~pVQQ){_|=|=RzO=<6Hv!3 ͌@H!4͌@Ha͌@ z[AnfbPaf#4=}YfffZZ@AAAOO֖yMM͕+W?1yd{{{ݻwrɛA@L_Ե 7sBB޽wo6%%СC .yRRRͯ]6zF5kAҥKׯWĠC#af8p --MQQ0sN=`YY?* )<.xͼiӦiӦDyyywwwl\.֭[s%^(d焼d>y31h _Op>7<p(K7l)1HpD8nǏxzz>x~7'Oٽ{71ؾ}_~Tovќ--- E;t7y3183|nf999)E7y3鿯g`!gY w08bh!gfF pHfF $PHfF $pfF q}=- 731(30=66ѣG]]]ƍ3gŸzjUU&bjeeE 7---hjj3gθq`bcc#9tWWW[[[ч"❯׃_0p3O:uT*`ٕµv:rH{޼y;v 6JJJ훚455ON{ lwLLNq}=Xn搐O¡U_իO344V^^^P:eʔ'OΚ5k׮]SLٵk@QQqݺuϞ=_t nU__… !"xC#u!fnhh_}||! eΜ9999ŋӧDn?~ c CIY_T[ 9''fO:?>|CX'???""ãYGG3LBuV777GGM6I\1'$cgaWܹaÆ 6~cǎ3*//OdP(ښOXB $;_ЃGE4tCUU0}L!3̬<>9nGI w΢7n͚5ZZZ0s8fmmmhkk '?%KA@L_p1>73]Y^UYqz2M,'OVɫU)* s3jkk߼y?իwttq //ohhH<=L&vjΜ9O}}'0ؗ' Q7̞={VXx>|m۶NiWZu%K9rd00544wtt\pb 8 gn栠 qڱcիϪU222[ZZzʊxю@ & . X` aoc~sDt̙V]]]"}Μ9JJJ}(1FKp3c &***A@HH@nfb#u\AὯ| .GF\_TA C(͌@H4͌@H͌@ z[Anfbpxp1A_W#g{-xwUURfffVV?ḹ DΤ6SSӀ~Q.Wd_`Qܣh055-// y 11q{N8 ؾ}uttFNBg@|}=>lBf='Ofh駟FXvNq}=RnEO޲eKY\ gΜq|w3n)nf%6) 'gH$rVq..A73?P痛rۛ7b`Ĉ fffZZZVz̖-[%}FDo둺AM ̪yۉ ]gg?|A!!??~ZreYYDO P2̐nehyoܸ`0 y@@@GGʘ{449::bv튈@S  q}=R]nfH7Ѳ!f!f###8s)d655 D GzCXnf*YUҢ,ݰ9hZ 5Uy5eɹ{DNNnǎFDoh/< ZAzx"zuPw3ʕ+6mwߍ=׺***ܰ7CaXXجY 7i$ +ܣhdɒ%K((y_~9{34.\8o޼\===q@ q}=<<̽C, $i 731hV 731H| .GS az$ r3#az$ r3#@l_ Q 7318nH83&nf6}̈́mmmɓ'^ɯ9sɓ333lll;|299ݝpB>|dhF||=4É. p.8]fun<CO5kVMMܹs={czvz/_a//m۶EGG_|-[lڴ݃C}>>>( anׯ QPUUyfUU0Ԕ@Doٳ^^^//Ǐ|w FdzBxׯzLL \t钻;zn߾@̍3}/vCvv'<ؿ#}D\_4zyĈ7o/CCCԈUfv"pիW.\qƿ -,,TWW:::-b<oŊ~m㝯xn|=[!rn]v )))DzUU[ >|_TTIjllR"Jo] {ƍ%wRpzH}HCa333JrQahOqy]nGgǓ@O81jԨWa`3gx??ʪd?niiIݻw/˝1c q}==R}_xqݮ.[d1X -,,-,,N< WGΙ3h B w޿}=`(z977700qMMM:~…ѣG-ź|ȑ#WEFFzxxŕ.]tΜ90=55)00pNC33glnn.**#(lfߵ‰!#GTVV&)3|=CL"%g###I>e_r3#azr3#;_ۃ3\'A (cA~eB3p !G { 21#uB0\.‘l6D"5 S`/-צn%C# GɓǏ;;;"n5n8 ƍJkjjjhhLJJJ IDATSWW700144|mTT̬ollLkmnn;wݻWYY|r"Qe%%%WWW8hV'O444899_x˩SN2&'SPPqMccnzoĪ*@׺m󕕕ٛH;l nu --- ǻ@2EBӏCii)(""޽{;܋/fؾ} LMMI$҅ ?[lٵkݻw 33G;={={K._>2RXILLol;v|0f>{L__~:uGܽ{7'''??"]._,xرK2 ~0c}=\.722Upͥqꫯ 8ʅ? 1&&fԨQIII[n6m~8q"iѢEnnnPx9*jjjp3f̘7o޶mn߾݇2̰0ݻ+(t WɩQYYL&*www###O:zJJ~?מ={ǎ=KzC"z\($W^p#G֭[ihhxU~Q'''&### Ϟ= HOOj___UUUdGٵk۷Œ]Kx1?--^EEѣ0 =|חd.X277B"!AAAP777z閩ti. ~}"?xDo1]ѣGFFFњa(\Hcc˥Kzۉ'=C##ӧO.( ??355oxÇ3FEEAݻ ϯ{,--UTTpÇ\RUU?gz#%jkk9+״p8*74]>s淭,Я\*_ס,͛NNNK.ѣGKKK544~g/_---2eZzzz^^ҥKk֬Yv e͚5߇Րׯ_܊BCCmvy 9sfRR͛7}[RRe˖J_|Ell,=}tܹ/^tk077Oٳg?A:::ܹȘC_qqq;5O={ɼyz<ŋ-Z4nܸϟ?_+ꈈŋ4666<<|'OU3gܹsEEEnnn@YYY9m4ׯ_\2;;,..&įׯwܩCzuYYY[[[_y֭333۷o_mm- ={ @QQ1<<|ܸqo߾=t萡naaaRRwYYMvvvEEC EQQQNNJ2nbӈ BBBt3<==b޽{.\haaaeecLWWWtt4R OOOeeh@xx)TWWY ?n+))+ͮlC8sLRR.[t)J=zhoœWPPe2T*U G]]VVV&M3uֻwvkqFDDw}'Q/oo|Wzzz=V qرGx0QFFܼ.? {y466Xĉ<z$ؽBII d2ǎ+_,8=`&xUh*>kjjttt|}}w8 M9"?888G!~%>]XXzy -^8==ԩS̈́1?"#Jf*~.j*6mJu\|%%% 6{xx]׷@r~y{DVVV]]=,,ĉ̟6r_x7ؘI海b8~m`dy!$A_` 2aX5) SRR8ӧ ԩSaÛAEEŭ[1| WWWwvvO2eϞ=q{;?#Μ9</+++11Qxfiii^^?㝝B2N0믿':l۶&((+@Hllܹs"AN<)ؒpܹpn:/++DDDAU>+-YJVTTTTTt tww:uAD+ggիWηɓ';==osrr\[ZZz94mڴRxΜ9s ںw^ض ۉ$p `>>cǎvvv&HX+WdzX,uu}"""T~AQ,YRZZ:j(pIDDĚ5kFaee5su 9/_鹸l߾K⋶ `jjW|Ht`XqqqaaaF2eO?=z۔4mmm322-b 8@faf=yԄaX+**h4/v?IHHXuj&SںH_u1*J *$RAgmѬYTÑ+c&`0?l?~EEE_Ovq0  )mv=zzCIIJz?q!t`߽{Wp\EXX`, v&@NNq]XAD> u_ .t#_h̙3St8x`ި =@2~ebϧd <".oψ"<DoA61BKDA^gB"Ԟk\ 3tĐF\_4׷@ y^L&7ܜ([VVVLL 111;wGׯS(7f61iҤn^~M>Z!;_3p8m+ 3...#F#Eϟ?aXWWׁ>}?R)88o!)@@@@LLÇ۹srrƏIP<==E q}=Xinn~: X y^͛7Ν[z#G~~ իKJJX,VQQQyyyEEEss3FXXXܺuΝ;%%%T*u֬Y:::˗/711?~|ɒ%7nEEEu=f$GJ^dddvC^gyyy2 ;R(bرc544299ð<<</^tss -SUUp8iii;(8Hvx$u={v޼y_~%4lر#99yĉp(+z<==}jii 57*`޽zSRR X*Jع˫f̘r.B3BYUB355ǫWޱcLս{nhh-[*...eeeɵ&&&iii7o޴ummm~=U}}=᎘1cƍO:ٳOj+b8 H a$$ #b7:8nkk+pD^ghѢn7[n„ }}}}vhkjjڿ?aAF&33P\\ '677ߵkW|||W IBj`_Lz76ŋ[nr8:@yyyvv61kRRP[[[OO/55=˖-KKK$&&VVVi! p====~"!@_ CC0ٔr1 t1 y zŘe޽{500:uM8Κ5kjjjڂY]]]ϝ;/;; &Eu…)S_v h;wsN@:1dL&T*8<יD`?*SdWw _w! "___oo###~ٳgϞ]ZZJш9| =o%ud5B!к! F!%ÈVH@ >{_#%&\OR@ $pHfF $PHfF $pfF q}=- 7318p+#Unf% t9666G8nccߟ73 999..N[[{… HG g4\n?|=] k׮#G͛7uuu˖-4i)&2 QQQAAAGwvvCUR8#q}==zu344X4$$:77$..h?wvv~7gϞT//ǏpFH-|= #׃ =_&)''-_|톆b^ӧ_x)EL_4+77QD[t3+q1N9U]>C"߶snvR^^ǻSTsss"=;;t:}ڴiD"?GGGBuuuףS\\ 6nܘlffg!3BzJn@7(T,GW3J7ƎJXG"&HdpLΉ'g&CbLsc4N2qb1.%p$" m@Lm[u?nWit+MWwnS9y.{ B%l3U7oBQQQ88c ؈}VVXX5x \.7&I. w}788x`D~p*;?ViZa爈R΀8z3QBjժ˗#V{Yf=øٳgSRR޼zOjKJJ*..\̘?W_Ǒwy!m۶knٲ߿ //7ey䌌ТE~mذԩS|ț#|=wz_?#{쉉xuĉE&^QQ))))==^§2 ja|׃n<;y v3fj$a=;[Sfc7s-zE}Yq`0lٲ%"""99ҥKknn]6yd\|=`7s{{3y7s@@@f-Ig^ZJS E\__׮] rʡCryww@WJJb1 C?ydU_ot3 K./񠠠MMMZvڴiVI|5R,:: EvvجYvɓ'.]kjj7"<˾Odl˖-úꜜ#GtttX˛S۰.\۷oכ#""7OAAPf.zӒk'ONHH~WTPd2Yoo/>~zOOυ lL[q㉇t J~,chu9Kϟ<nff[ u=q8f`_OzzSO=£oeYO322&>FW}=nPɼn *b&ݗ9g*@ P(eJ?񑑑ǏkjjAy!P(o4VKtRR]_~^7;⶯ȁ@_hMxa^z'8vihhذaCYYۢ^zG%kCtMDP(fseeٻF W}="\6n˗QPPDQTWW?fWYzucccbb``|'N(,,LKKkoo2e ?}=>fFt:#m,47$IrΝ]]]n݊tQQQwx0RBBS!ȐM)W 1̞x_7A}=aaaw rwaA69iYY,Vb'vp73xd㬬ngYIJlf&EX,ɄYUS<g&jn l$8Ÿiaσ $5Hƙm i%62r߀M=n@lo8#H}6B8Z?=\R*rd2|RF'ugN|Iɘ&0qbU'FFz|fH$=|P|ڧA mE YYb7WIJZ732$I_ب&85elfIaop-crtTT?p3(C$EQSG>m0^c'1NI}$\bL=fJq|_tuuEGG=MMM.]',;w\|:/yĉ$F#lA$0'L&d(J7!(((88XPP%n-_~=;;lNJJvZyy:vغu5l߾}֭x(C3B[r1c&LGݸqMoCEQRTT;6>>>%%%55uĉ&LPT2‹*o#V`岲2N F83a(CsKKƍ\2aڵk2#H$ } HdYt,Ve^[o_aA3ߕCwܙ p!IZLfniiqŔ,0SFW͞$I~?v3Lnt3 e˖aFqi[[رcBC[[[#""V^]SS0qwmx ZVfJ%,c2bRo?߿ƌ7n\jUgg[_ FZ y͌q-cN{{;n CPPR?5lFa搐,<.VPPPQQQUUS)ef8juupAМw.ń xv lٲT6̡*Z"""d+J-_64TP5R<(R߾}i$lXBBCc=vʕZPkksh?bfv7ZJ*?R*xP~{ɒ%mmm/}(DFFҴSY177wdg3lߨN_#B|Gv3#o|d8tvvhhƘLhWܹsD*rT*J% 04MiA,kl6ۙ3gRSSYc7C1f~y54#d2HoD73x f)73xi)WF0> f1k`7sssC=g߁8v;A _Z=fgn ~/p3#L7vۇ).` Sm6kkk###M6{lPMMMxxPٳsٳg655O6M89믿>}tvv6?G|j5FB04 )v,y7.,w;[Że-;g͜18z%K,]ŋϟǩb̙Գrʪcǎ~G 6ٳ8޽{Zv֬Yuuu {B&IAFeI& )Dz[[[=3AR?~ /0 |X,_roFyy9Bĉ|%eeeqqq F`D8|`vCe2qGṵ{}eggԩS7oYp]j|֭[lrw.S nXVbV+>X,VVMxyر֭{N>y搐RccP,'IRڵk͚5gyO>Wu:]XX0t̘1|G'x駟 EQf^$h !Vhun把C֦={wuu IիW\?>W.rl6 {~gqKP4m2nݺ8bfo2  ot3/^x̙˗/[Mgggo޼/c-iiiIIIv"B]B!J%Fn*? nuvvZV\.\.LQTttط(jԩ;Ry7\0PΖ/{2,윕-VfFoݺu„ gΜ6ahΝi }8A,oφF,-cOxY^+y@%0HA "=~M˻et l!Ԙaina?f;{ :]Y @ <"f3<@ЀPZ@ {i + (pA"HbցM `/(ap @38 .: #A8Q!%Hҁ K 9C? EAB:h TCeP%T BP?tƠ'a f0f~p(ë8Uq _o<(@(wT *6 P%*T= ՍQX4@>04].CC7;7C W 18`ؘH ǔ`1.mcvXl6 [=mcI39q\:.ww7}xK>/oƗkg) A@$ل#6 a(K#:CIMRb=D"iIKIR.tt4DH#1 vQr; BѥR)%c)['Q\Ij@4AZG%B:GD q GfLL̠̤,UB6P6UHVNNWS''wX0EբS-#. KӣiIB Z/mB^NZ>\~|9y Eץ)S;O ,xm  )*+(6(VPTJVڥԬHlT9Kr""EEN-U9ң2*RݯzIu\檖Gژ:UY]GsSza54iZ Z{:&յhӮӾCa$yUYwTOQWPZJրilp61L4,7a c퍅Uƃ&dIIɐ)tiK3mh]ff_mS̏?lfВkYnyˊbeѪꕵuA6T%6[m:lيmmb*4fycfG[tS88&;:.[a'M'SęEÅRU˕ZeJbgt3w5wwp_( ,|^3Ves5 _;~d2'b%%< 4@vGAzA~Y]|`u!Ԑ!!BBw> Q!4\y=J9J\lﲑ;Y~uVJ< Tq&qq\w> +oo,)8YSBq(߉?X8.p ^%$Jz|4y:%"!*& ;WZ_d$IV;޻zB'NҖӐ`ӓ]Psfy懬kddfo~ZZڎu6ZZ_cƼ#޹67%ouoDliS||qVǭG/wն۾ ~.]'laNN;\v+-)޽dwƞ=o{ĺ>⾌}RҖw\XvܭBb[]R=TxӏVzW6UVrXHV.n|槿+(B3y pHYs   IDATxwxT9mH'!! $ґ^ iUW/()D"rED)H($RIosf~L.|LyfϻS!`0!//Q`0vGBxl`<|*i\vĉ/^ujժ]v-A2(--]vڕ."q1X,^^^<3':::>jG5a3g:99wĉiii/_~ۛ9s&$$B)ּ4>{gOO[nK$k*yۧ~z֫ ,سgOUy?lkNƘu׮?AzKվ5BBfϞ=dȐW_}uҥK.]W GEQŒӧ߾}ӧO/))W322ּ ,Xn]``u֭[g @x@˲e˺uwEBHzzz\\\~L-qffԩS}]SO=URRB9v،3! #FX|yttdy!C'(BIMM{g&LP Ü9s:uԩS+WR[FݩS=zWE_<ڰaJ 1LF !ÇͭϔQshp<,' !'2eʅ f͚u!2 !{ݷo[o1sN8YfK,8p3N>=ޙ̛7׺61*k^ ,X~M4/a18QF1޽{7!'9sh4t:BѣG]Kd@1=߿ɓG~&MT͊mh4Mv5a„}ڵ """$I:{lddpuuݳgc_Ȕ)Sf̘1rСC/a搐4{ M/#""ݫjHH[mk_s2&F3`L޽{@>}B?~8k֬cǎYd֦r9oKJJ CllK޽ !ڵ۾}ܿ]vh0$h4Z,EQ$IKKKaÆ5klܹݻw/6L~ҥ[۷OEwm޽?ּ7nܰvƍrbļu~z޽ٳ_L;;;߹s' =<|„ cǎ?~S~* 2?W^yeǎO=Ԕ)S.]z177nݺ5nܸүxgӯ_nݺ5mڔ2y^ziӦMs̱?+Vxyy͟?8B_?mɒ%&L ?uF%˗/۾?rȱcDz0j :ts$/Ѣ(JQQ+}kX0`0_0 ֠g>jj Y}{H##y:q^D:HNvXI$%%mKMbsN߿͛{Oѧ^z};;# A!H%"4TxH[rJTA豰znTMd4l0lذ6:u}P_?cΨ{Z"U|Jk AWbsNV^h3˥T!"[ݢ1!XUA{[ fR[ 1B=~'|-Wx( q tһ7\\\dYVUaRGC`0AB0Am yNv!/_vZYgWr=}0)5USKB0w5p@KvVR&!9T{֭~~~;vw9hW]ʲZFW͛7yyy&PjO+'?/{wuҙ;i) mХ ȨJ{]7n;v*믿Z!mw*TUN-Z0t~ѢEFM6OկdѢE111NNU#:vh>~.^?G"UU !,GGG[}>ܿ$!("X@  [y 6SmSПXT @5USڽѣy͛7-al7Fܗnݺ_[\\\.]T\5Fe~P= c\uz2dEBfEFČfl`b`E% f96j\jF >w̑Dj0;ٹ{G?c{Λ2`wޘ>bp/ xI0т fuE#n64&h'|uޔ!{;^g௾tҀ.|9NbƊ*waoK*ʕ۷% ?5k$$$X_VZ|#GP˕+Wo믿Yŋ>}zݺus*l`+ɺs iҤɔ)Sri;wL6Yf>>>dggn:11:u=W˗/իWPPPOv|ro7>oż~^m4 Ns/F, ~uW I_|`cnSM"[n瘨8}1[,R%pss+WZlٻw˗/۷o̘1W}Ziii.]899:zaz}IIz.j:1NMM=vhZ*=q$UU+x7&N~z0aBrr|{ X\\TXXXj䕢(/\0$$W^Yl]oذaɛ7o^z̙3ϟ?ƍ7mW_788bBof~֯_Be= `BTU'##͛Q;@q@JJ 0B8P|zϦGvѻb@4b6 z7i]_Dv ='. &?vAdWN4'20"V(@8g4!2sQ+Ń;0#ldmz[PMfRM&Ӊ'Ν;ФI-[@ttkܹoY%RRR ;wj1NJJ ~_p|IrrC֮]K&L0qTUU>`z~F|r}T$d[l9|6m6lбcGcռy <߿gϞ~~~VG!V_tBfffBBcd*I/ q4m<`;T?ǬNJo{te;7~>^U  <}i FIV@~V$oP98;:mzצo>ljeLdbdS!Ngyt,xyy[,""šfJu^]~}ҤI6mԩӖ-[>#UkloooZlIOb׿ĉf5R0cR舤NB...yYc2xY%N#qym!yP38t\<=]=æʒH"hDAD {6}wkBZIE@/u4ibm)*qrm( <,@駱MF~!{aͿ Ku/dM~*$FзfY~M#ZhqPzZii,2+ҴiSNՉu _MmDDDlٲu֪n޼922200!vȈ$jkݫ\ :|pQQ5ɓ' 6nڥKرc>]v ~Ǐz'^gxjeY~Щ@hdYߣ΂u˒8.M J1^ WrPh~>_{YE$F#h"]r1|^}bVO~~Fzx0{ɬ~.yЄ/+eʔݻtSNY~P>DGG={رk)S^{m0f5kh(...''guss3Lcƌy뭷7Hy饗rss5k6n8ǐkҿm<@х q]zP].:$ "' า&e=BLuvo$RUi@Uw4DUs5欍p4NnlQ$߾3.vf3}8+$oU%tװ?1WVM֏>=P*z5 jIIuPB,ˎyg9>t2pS]i)o_a3z|%ZAM*`h3gԧ3O[kP2.Zƺ5@O}0\\$:_7vg!:h]v]vmz>~loy wuKnzUܼys׮]W^cV03`wܙ^| FNiyyyׯqƏQм7֫`0 TU'A`<^`0,0G = 2[>B? ߄Çmi֬Yv? ťTQ*$"]8N_~Iٜ]RR$fQz=Bhԭﮪ?l2dYz9q|c!&@QTO0i{iu]zQZM&SJJʕ+W233 5[```DDDF5W =/_6Lcnj%V rwU=5 " BȤu]^:=Qx`XU~g~RMK䆒;*w .&йs&M>u(z-[*D{4ܥ`S.]?9%fYL07[F@t4!@XĤv¾[{t Luh!dgg9p=W %H:'At]R܄K)-{5WgϞ/fgg7n8(((66/=PUUJm[=O/=I؋Lؤj$ z)Guo ` ᜚1,09/萮x DI'N':Ȣ,!ѱAn>w5K*'Nߟ}Л7onٲߏ7ATUxR{ggguU W333w>6lR08z6|:Nzڪ}e=B,Dl0ĩ F-b2.q"^kDNEVr%Y+6vJJ =ۚR sέ[8`3 '|:@l &$&&><44ƍ7ntpp-ǚBe|YyJ&*A8L 3G ~סnD``#^EmF1ٜxS4;6trVqIzqײռ<0v%;ׯ_K/U8o~m6bĈ۷GEEmܸN>ݻwUVuhzZ#<_|-<::ѣ8p`JJ 狋ftJ**߾yϟ?~o}ȑ:/GL 7oܸQQZ)ߤ`B*XT`b|iGY`QY%f 3.1U`[:|Tk0>'I'k%,;]D IDATjТl6[sU2֓1nӦM?~\ծY& ==}РAӧO߼ygΜ0rHŒ*͕$%%_={,d6 Jׯ_F;o>dȐE͘1cĈgϞ}]ְa!nj6mڴ:3_~|ҥKϟ?ߪUŋDTU]t30=Uu8BxDc[YJ:\۷o[-EEE3;4YxxxیUєIҖ)7Wg?JMcŋ;:9v̜9ɥ{:uڸq$BfPҩKG}A1b9r޽rsmvCA@"qP?@ A!QE k0MK Y* =AAAnnn|MuLLLxU///URU5??W^E:T!8___HMKKJJ5o~1I)aaa("!ZjJ:z]VC9CCC322P'Ծm6^_,|g*M_MV5"xI5}-hDA# Z׈V4"9I5"'F$ijn@0 DgELT"*Уh͛nݺ5kܷn۶r8|||dY>uTNN̙3+4~'h]bj݆Oۇ~w\94 JjJJJa;rń [|9sy睜cN6MEggӧϜ9SΝ;͛4=IIIڵkѢŻkhK7n1bNӶm-Zl޼'cfSpFԈ_#<ؗv "ۡ < $hdzuE.%p%R1^p!h3ggg;v,44NMLLlҤ#A>iii4VXhM1V{aa!AAA'N(..о ז}q>sB` =Xތoupv#@ *Mbd-4pCPe ɓ3iM ͘(*:~wsqtt'u(rDD{R׿^UUUQI4u6!$(((((N_ڃtnN۴Z}9U_+vG8!@kB T {} VW@Qz*_]Qu2a@PGUլ-¨xU*gUUmݻwgeeV 3Yfdݻww#--mƍ&L;v,qÆ /׿uܹAeff@AAǿ5k/++kt҄ ]f-sɮϟ}@EQccc`Ȑ!1[l1 }խ[tÆ z?}6 bU)Q(᪩S1 f T~Nq L4~i gΜ٬Yx'' i-[moy뭷vFӣG'xb+ٳO>٥K:p7i$ziرǏCO<<!OO{dtss/=EQV\o 74}8::VuI{NF^ܭWS1 BCCdeeU5<|Æ k#F4nOd5jTk'^O%%=~n[CǤ# !Cz̘6z=\ z(,vQjS'tp c"a`ez=<D^쏅Q8*9^`j(|d0< ' Fz=* QSjSJ!`ԔZa0SC6`0jr^ = (!@P2b0 *bP10Pq,0ڠLܭU`0j`0jף`B0 F `z= z çz=pLʖͿ?e$`L=  Z/,X%OuI !144466z4@ q?-S))0}wu&Jo&F~L%fX]F5D޵? ɷn&qO3@[L]ƻy<=fsfZ3?εQל9egoԀ!on)sF3) `c2]]]OYy7ܨ}I˷~;w}b*U%شiko5!(8򊣣cQQQRRRaah4hД)S֮]{ĉW^yEg}8))iʔ)fzGB0!èjMӈ8 J8ƈDae~@aE|=E9p֑5rFZΩ!&='ݤmO'&@puߩDu 3IAy0 QȋAr6'#9\|Ӷ=3}B 8^vj4FH1^jOkW>p@=N:ɇڵkFFFdddbbbVVm_>22r>>>z^$V^aBfrvvѣGΝK:4,,E/^w:H!zC znhE$i8FFEs8kKKTrם xi5($rZ @O8i5@i%|fK</+A9MS^48}B4nU{ԼZ2SC I%ͽ{С̙3GyҥTX|9-uַneU %%e̘1"?q)))aaaxqz BBB Ihv۵<@g!BaQ+TУi^+qq8@hX9{C^#=7s3Nט&%,$p*!dp)7/ yp!$pM%yWVmE>wm/%omԠ5N?lݺӧ`Μ9;w8yqƕ&={lzB!j󼗗Ν;9,OOOzɚ6tF<†1cBpUT˒,qHV#h5VuQ+ FH:s@AgxvjgyaafrNɒ8$K$ʒwpͼqY+$j%4@_˒!$ҵdITE=I IDATkuN%+i3"n߾C@ӊh9~M%t$r)ZII ZDuj(p&^ Y#j/(:| !!!˖-KMMEƒ\U%3fǷo߮j~~~NNB(((ּOׯ_QQ !!nxa (x^~`Gi/KV(kDFi%Y\ixGV#Qn߸ic߯jĮ'gxN+0w.ՊIZZHPEys5{mp3ZYL=3]=.?nBH^xV+xoVֽ68lyC[dܸ"kuRƯmިehYi9,ZȗS7o9244}֭ςx_rVm۶mvڶm{iP%\RmFGG[L0aƌfj޼yttsx=ztZZZ֭>lj޼>~wӦMYJiyj/8cQ;P?~ۛ .k׎Z&d`Np\u! [(dkɽ8`.5J .E).utqJKKڦ!j();E+*K%.4ƒ"|θ(=>d+[nt: JJJJJJ5_}f977׺PѨQ#a BHzzz UUUUMOOwrrruuf FN<9cvƔ&4 N%β#Af#peYPUʱ@W#A s4`@?e r!G; ܴvixGwr88mqrf,|v<R%'''''{4,>>>+tsk@UUpo?jQ+z=,;NAd_C89zɶC8c|ܹ?j?#t犮eG,sW(zBgϖօǝy{Vڟzu>M@(-ZKP֭[?õkמ$`0*VzB>>>qqq;v黶?6?jR̗w\\OU[S;z=q\``?_Q/^O}V0G^JC*y.2zGMz*W)ȸy`e0;^*1fff&&&:88899Bz*p6lEOe0 =z=p^_^j20 ƽF#˲u5`ܗTzA`Q3zTzpz PQ Cx$C`?UB zCL`0jF zX`TCz*z FmPCz F-`a`L`< ^xXa;`0#5멤wSg0NMz8@62SC끪zuQ Tp1Z(Blzz= Qc^x0az= Qz #0a0`0>L`<js̳쳧~A` ,PQEQEAX+V榧k4KKva: #_NZjhBB!q  qT**--MNN;wq܁]؀R|Ihh MSYY ǏߺukhhqcǎolذW_󋌌k ӦM߿'v튊ڳgO>}lRYYpتf hT*NֆNUt:]Г#BksΜ9۷o߶mݻwΝ  2d֭GMOO#G޽̙@۶m?s988|75oW@:KN^l̙3iii (s?|o֔44Vt* !D4EQEя/)DR(W@G:uj.]t}AVϾcǎy.^r\Tj4|{C!!!zjV[;_k-,,jŸq|||jxTMy M*Ji EZ޾}ٹZwnݺ]tN:aL&+'''_Z{[ݻwy&k666[y/HMMuttv /PB@c#d20֖j;;jtvv6>m>{7nܘ6mZbbb~m۶jժWWWXp!|7|rA++'s@0+P^߂GU*(JB!BUr|[lo̗ [NN֭[+**ʶlСCz|pww?j={veee ,`\gW_NyBWWWR߸qG#zLzBw5 :DQ0UUUZZ>,>>ӳ_~!!!iu?trj 4`_~gϞE͚5!չvZR1F{,[lҤI۷o߹sg>GlABB4CACzPff M/^ѣdgg4~V^ݯ__|9;wܱcj-WQQѹsj񥤤ikk:SPPP~t:TkSZx7ֵ@0s.]4wr.U88sӠUZ["+kPAvɣFRTݺuyDc=4M1"%%e;vl]5G1vvvrxhj TҲ~7J$Z1QAyzzΛ7޽{x#EM(rpp"Y V^j^fBjʪ2&@ 4!׃!@x61[ cz=٦z=dB@ 4f&@0e#zLuKkɓ_{ nXӅ^` "PuzI4@ zxjN@ <  z=S)$0zhP^Odr@ 4!K BS4C !H(B@!D!@DH0 "B#zH!M^^++믿zMт(@Q"{ Z@OFb۶a/Ri︹ 8qlmmJ Wʜi(@gDEDEㅻwlׇ̼)k2^E%B$':y!Ԡ@H8E/ &teڌ8Iz w9Cq1boA{CL/ऽBb;8ڈ( bz쓶!cTR-4!1iIOO/**BѡCN: Î;_|Qyp*gV/yC4<7^^%Tqq)^?}. <^<zVs[" `cz=uVTT{֭e˖M2xʕyyzz:tJEaC:~gg砠PlBնEDDwީ%({%+Tqglٲ۷o^e=sի/DZZ Q3lױC!B&[K @ "/ Nj(U%cu=1[u`mm7~mAn߾΋8FQO?m4 [l`Ycp,>rPX!wK9m)})eR|iK9m)We ^RE5DczjL*&?t3Æ {嗓KӧO<G=s挝]Ν՜uܹ?sٲe_~͛5kЧO?$oL-vB"! J%% "@xQH27RiABv9xnܸ1mڴ~m۶mժU0L~8o͟??;;V*6yCBBz_N0aڵg׮]6oޜ9}tWW7|9ߥYS-<~= ѳ󣢢Ν;gii /-h4ҥKxႇ+ @o\'''Jw{nyyҥKKap=W^dɀN:|B;lە2Z!)(Jg7AQDQD2&p=Ah:zLh׊+`ѢE5 {aaaFFFYY]tqtt-,,ܺuԓJIIY~E/={6o޼|SN=zh߾ Xo<;\E4c\?ի  4-]\\k@tv`…~^i"M۶m3خ]ӧOϘ1m`$mW(@N9{MgbDDQD@@d u['!!wW5 4h/rϞ=)5k 8m^z9::z{{W<55ں_~!4q;vTUU-\߿wfҤIvahfjsiAd2Yff&0]tqٳ>(j۶mvvvaaaK&0LN~*zIUED_~TRKB)gTrZ)grFP R(fcNi4t}%%%666E5 ܹ#ILL1cƌ3q޼y ^3rR+77w4MuhBHT6LUUU}vvv--yի]v>zm\:t?ϟ?ݺuvvvǏ ޿7ߔ㆕:Ϫ])ḍ%HЩkWS{<^( 3 &RB1=\ B`F&$=#G6lح[T*U۶mq;_|qm۶*knnn;vxÇ%Bh̙3g4d |rAAZƀ.\PQQj5ϰ]`T 9MQzS^=lw,rDH_ūL׃^o88\P(!g鬏O1ܴر]1 J_wJҬ\ZʮVіMBУe++ˏܣEE$E 4CS%:g ;h~^A T*՟ٹsg\R59_AQSTTi^ܠ(cǎf*((x)Soa;;;GGGLfC07Brյ}!4577I`>I%jԨwFEE͚5kΜ9YYY3f k+:0u~ᅬY</]dee KKKjժٳe;88 N:U-z]?.[EuYlmmk=rƍtSLjJR*cP\\08pO?4~x|30w}W^xQv!ϟ0L݃Ͷ8GM~$b׮]O߹3"##WZu~{1JJYxo7r˗/MΝ;L}S_~g^ .Ƙ1+'OԀ. #=fT[#H U^+ ͹Hu5OZݺu/Ao&v'kFN/YPPеkW2g\:;;F:{JW^:+}||܆>y:t-;;!(zG cV}[>ǹ[Eu𰳳KII v ghYYY?Im655Uj^zpS?={ܹsTN9B _׫gxG{O>?'`41ߦM(Ҵi?J;*o/h~ gy#/^v9::iA|gG߿N4h/rmY;:Sս{˗BY>鼽q^)HF0>3[Gus뉊===ǒ)Z~}NNNPPPPPV]n]A.]֮]lٲ_|qC >}Vݻ1Pv|5^>^_gGWu^P=4uCh}ZzeXxe}:uj߿,rq<ϓӼ D!D!!z=$ZVYXXpsqy{Eqy<qJ P5KǤ'==… Ɩ9))))))%%̙3Ʃ,DE\q(nټyq7oy@P]+z=->>gϞrڵ{~LXz[nEEEnZlٔ)S󼔎eӧO>|8&&Oċ/XO>gS <!4멥 8fff6IU Xl>NLL\dѣ5YtiǎfTVVu… SN+L8|ܸqk֬9y_~ib?WS_zqBX*0mϿ444͛ MSYYeff4;** n[-Ο?|x׮]{MHH?Ma( HyËXMGMzq\NN`cbb u֣G@~~믿pӧO3 -{vdn0LTTTFF… ?S< ,^x۶m۵kא!CRSSoݺsκ|Qlȑw߿]~jvڥ1f Bos$%%KxqFٳgrr2$''GDDmܸڵkwrrZd ĉÇh4o`9sCDDD^^ޓ-BУ̿t8i47z ׻;vX^xŋЩSG6kkky0@PÇҩ>(,,mȑSLСC`` N_r\Tj4\^ʜ9s:v ߸qXTƍ'%%͜9sSN~+WV'q3gΜ5kցw뛙y'sQi)Sѱ!> >}zff˗ == **E{iqq(FFF24G|H1kFV4t 7nsB++Zjfژ1cxEM6>@FFtlbV322 p2)))ufnt:WWW<6Rph޹s;wDU7mީS7oݻf712 |j)))xDyA^Y 4`4yq}prr2 _1c"""_%Ϟ={8 xFc /M@,k0,,,|/|ƍ#F`(t,˶jFT*}}}!Q20EEEeeeXXX̟?J0zhP^OU%$Z7JRVgeeݻw`0Vnh0ݻVv$h^ nJWFFFNNNpp0^O>y>--3::ZZ&"Vwvv2eJBBBjj1cZWՁ>XM =8?-%Ld.[BpXeYNԳ#KXXիWi]B"c(h@BZcz=˲vvv,r<ϲ}N:Xe98{{{I1+Zqi|2nEYXXpsqy{Eqy<qJjChVL:qѣG>Խ{^z=qU©uBddd^.\hl|ݻ񱭭{ΝHUUUJJíUB"\.8y-7<8nsx^P(qlc3eʔ>.^4xo=h kkWZYY[. *++׮]ۻw&=(^zxUs޽cǎ8q{Rjp&L05? |xРAgΜaÆo>HIIƅ?裰0___77#GN2CYYYR|IXXX^&OSG:uj.]t}A GCc P(C)i7|?^zOP2))):u*((>z(xJe0ʜ9s:vhʜ .\8y$nUƍƽ0WW7n222֯_4sٳgO:>W\[eggϜ9s֬YHNN޽offדG!(Bz Q$U ҳgϘK.YZZ9r˖-G޴i4IQt2vXzt钗WXXx֭۷o:fQf}~ծ];0 j?7cWV-++{뭷ZSCc999FJHH y;T*UAA>޶mBZlٷ~J+Wq{dddLL O"T mll>ӐӧO8pҤI)))Ç:h4eee%&R۶m^չs_>hРjӧ=zTuҥ|۷o7VH2& ^rIj-eII >C*rppjϘL)2>~y/Q)2?L׆믿N>B"BONNNTTo߾}7mڴf)"L4I׿덭3??G={dY֯_j׭[-aÆoYtĂ VZҥC.X ??!Nnܸq֭ݺu ?~< Ν駟UۿR&!!aŊx陙/_􂂂VzhizܸqsFFF24G|H0ݺ!Cdgg߾}[RU0 =zz1x`8##C:vvvNEEⷎ;4m0%%W B{k3yKKKQ۴i3yɓ'U]~]:ӧɓ' ɓ'O07s*SǡC˱cn߾vyOR )Z3gh۷/ =f =;xeR26Mo߾'N(-- EQ666۷h4Dլ y`0hZOi:q\h XaAViulx,ߔP __ߖv0 Sm8eIDAT,,,HΉV) - = @ $T@ 03]vH!f~/Z&+**$KeeeZZ˗˥(j/""g!$<.P`koo˗;99u͞=l0+Hyqvv𰱱iiGAiiE4OOhi(X%yʔ) cƌi]g3aYދ5.d.[Bz=Nqk0,z}d z*NK0(:uCmi_/ZJ.cz=˲vvv,r<ϲ}N:Xe98{{{I1+LϫXk3Z,[q J!6nZ2Va,,,89N{#ݻ8NRp3tu~ a( @DA_m9ϫn&sA'yS"\.8y-7<8nsx^P(qlc٫FXPFB@?hR_~ܹ~y3gΌ94Obbƍ:Ν;ںftGG@cpݴ4ePPP570%&&^xW^&LhV9sy |0XRUJBCC/U؟7$< ع# nHXˬO>UQQѨܹ{=%81;vl[gee-X`cpL8q֭~~~k֬1^\RcicDAֵzyty7nxy4#νuF?`lNjI0 ᩩҥK;vS*,ׯ7͍ڵko߾z 8pù*jԨQ3fڵiܫsA@*<c~nT ,=znAa_w%rwtP,nذa͚5!!!#G߱ٳ'N )**H1cFhh(qv튊ڳgO>}l *..Ǐoݺ544tᅬ;k׮}ɓ'aXB\^9sfiii?.\6m_LL:;(^z /h4={LNN䈈@777^vNNN8c2xyy8qb70 3gtpp{2pu'E@y+pi =2 yCoڱAٞ-?ݞX^k?@BIxO',zZڵk+lï\cX.!!V\{naΜ9/\ $&&@tttppȑ#wݿ۷'N/?j޽{QQQ#G<{ɓ6Et 2d֭GMOO뎵~saÆ k-p )HWW7n222֯_4sٳgO:>W\yM|vv̙3g͚uݻfff^~=))1BBE! Q Q$& =rE)Z41֥TۄxB(ZFSƴzw~WXXŋaCivgFXXXꔔN:=z,--rRh4r|ǎ>:vV۵kQ(}=F@ڵ9sT^z?;^;QW^j5>~С111:u ?a 2cvnr]RXhk Rh{wI]o!Z z۵k_GʸFP[Qԃ4eBBŠ+333;== ** [=4M4=n89H####>cnH!NP2`# .vȾ @{Tm;\6mZXXСC{LY… {h&M;w~^ƍnڭ[c{ddd^^^HHѣG]]]~-88ϯZ+С?خ]^pu]QFf 5?zHHHСCHHȄ -[;wܴi>n߾M{oݻWV7C&0ѣBڵciv`bPff M/^ѣdgg<怺8nk\x ֣Pgbռ{IIɝ;w3MVVVF^_YYiggWUVVVՄ] 16m4Y!ֲXR~׮] ^͛ H#hu_մIOܹ3ܼyW^oq_=c ĉNNN͛r\PT*BP(rL&qwy8㸌nݺ eF#@ 4%FF;cKH@ <- >!AzSe0*++5M׮]+++H!OZرzJTT<_EB@x*ZAa\\\T*UeeL&S(2L&rLV*zS!dM6:Çws$&(KKK4.MW/g EQ 2 = y-rٚb=@B@hrnݺwݼySEikEC.IENDB`workbench-1.1.1/src/Resources/HelpFiles/Features_Toolbox/Features_TB_icon.png000066400000000000000000000142061255417355300272720ustar00rootroot00000000000000PNG  IHDRc7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:ϓtt:/YaRJ9r`a."6GWAyÁ92QASؐkr)ۍMTAHi$yy@V^*|_,="czHUxLc&$Lj:Y}q,r}Rl6k4nRײbf5ji)>%q\F#A}LܙV/nZARض>5&n`^w] QhX$ڶ ~>w]P(u!g@X.ŴaFTk'ĈNh4V5B( CV,+%ur9ciHl6cu}avCRQBp>B`/YDV"Q~?3p 'OUuaWҔ |O N_-=±q\IENDB`workbench-1.1.1/src/Resources/HelpFiles/Features_Toolbox/Features_Toolbox.html000077500000000000000000000064211255417355300275660ustar00rootroot00000000000000 Features Toolbox Features Toolbox
The Features Toolbox contains controls for displaying features including Borders, Fibers, Foci, and Labels onto the data being displayed on structures.
 
The Features Toolbox is not displayed by default, but is turned on with the button (or with View > Features Toolbox) and attached to the right side of the Viewing Area. It can also be
detached from the Workbench Window by left click + drag on the top of the Features Toolbox. To reattach, double click on the top of the Features Toolbox.

There are 4 tabs in the Features Toolbox, each controlling a different kind of feature's display:
  • Borders controls attributes and selection of borders on surfaces.
  • Fibers controls attributes, selection, and samples of fibers on surfaces and in volumes.
  • Foci controls attributes and selection of foci on surfaces and in volumes.
  • Labels controls attributes and selection of labels on surfaces and in volumes.

These tabs are inactive (grayed-out) if no files are loaded containing a particular Feature.


workbench-1.1.1/src/Resources/HelpFiles/Features_Toolbox/Fibers/000077500000000000000000000000001255417355300246205ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Features_Toolbox/Fibers/Fibers.html000066400000000000000000000016021255417355300267170ustar00rootroot00000000000000 Fibers Fibers
The Fibers Tab in the Features Toolbox contains options for display, selection, and samples of white matter fibers derived from diffusion imaging analysis. Currently, HCP has not released fiber data that can be viewed using options in this Tab. Stay tuned to the hcp-users@humanconnectome.org email list (sign up here) for future release information of this type of data.
workbench-1.1.1/src/Resources/HelpFiles/Features_Toolbox/Foci/000077500000000000000000000000001255417355300242665ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Features_Toolbox/Foci/Foci.html000066400000000000000000000054771255417355300260510ustar00rootroot00000000000000 Foci Foci
The Foci Tab in the Features Toolbox contains options for display and selection of loaded and newly created foci on brain surfaces and in volumes.
  • Group: identifies the Foci group for the Active Tab. Tabs assigned to the same Group will display the same foci with the same attributes.
  • Display Foci toggles foci display on and off. When Foci Mode is turned on, Display Foci is NOT turned on by default.
  • Foci Attributes contains options for viewing foci.
    • Contralateral check box toggles on display of foci that are currently being displayed on one hemisphere to be displayed on the contralateral hemisphere (on top of that hemisphere's own displayed foci, if any).
    • Paste Onto Surface moves the foci from their coordinate space to the nearest point directly on the surface displayed.
    • Coloring sets the Name or Class as the foci color source.
    • Draw As sets the shape of the foci to Spheres or Squares.
    • Symbol Diameter sets the size of the foci symbols.


  • Foci Selection controls group and individual display selection for foci. If a higher level group is selected on or off, all foci below that level will be similarly selected on/off. The All On/Off buttons allow for quick toggling of all foci on or off.

workbench-1.1.1/src/Resources/HelpFiles/Features_Toolbox/Foci/Foci_Attributes.png000066400000000000000000000761701255417355300300750ustar00rootroot00000000000000PNG  IHDR}U] ciCCPICC ProfileHPY_Das@ 20q`Beqր (5 X wEA9&Tnz_ wrDXTa8ۍ=<k7M wwшnqV4.Pq4n*g\8aЗ.B0M,1Op|HgdrH̼B@9DaƩfa?SϸeVxA(_nVjJrH)3FQoE)A ~0,dq ^' D>i }8A,oφF,-cOxY^+y@%0HA "=~M˻et l!Ԙaina?f;{ :]Y @ <"f3<@ЀPZ@ {i + (pA"HbցM `/(ap @38 .: #A8Q!%Hҁ K 9C? EAB:h TCeP%T BP?tƠ'a f0f~p(ë8Uq _o<(@(wT *6 P%*T= ՍQX4@>04].CC7;7C W 18`ؘH ǔ`1.mcvXl6 [=mcI39q\:.ww7}xK>/oƗkg) A@$ل#6 a(K#:CIMRb=D"iIKIR.tt4DH#1 vQr; BѥR)%c)['Q\Ij@4AZG%B:GD q GfLL̠̤,UB6P6UHVNNWS''wX0EբS-#. KӣiIB Z/mB^NZ>\~|9y Eץ)S;O ,xm  )*+(6(VPTJVڥԬHlT9Kr""EEN-U9ң2*RݯzIu\檖Gژ:UY]GsSza54iZ Z{:&յhӮӾCa$yUYwTOQWPZJրilp61L4,7a c퍅Uƃ&dIIɐ)tiK3mh]ff_mS̏?lfВkYnyˊbeѪꕵuA6T%6[m:lيmmb*4fycfG[tS88&;:.[a'M'SęEÅRU˕ZeJbgt3w5wwp_( ,|^3Ves5 _;~d2'b%%< 4@vGAzA~Y]|`u!Ԑ!!BBw> Q!4\y=J9J\lﲑ;Y~uVJ< Tq&qq\w> +oo,)8YSBq(߉?X8.p ^%$Jz|4y:%"!*& ;WZ_d$IV;޻zB'NҖӐ`ӓ]Psfy懬kddfo~ZZڎu6ZZ_cƼ#޹67%ouoDliS||qVǭG/wն۾ ~.]'laNN;\v+-)޽dwƞ=o{ĺ>⾌}RҖw\XvܭBb[]R=TxӏVzW6UVrXHV.n|槿+(B3y pHYs   IDATxwXWϝ, PѨXF 5cb&5ƞb,Q`ƈ%jDQwݙWͲ ]3{{Μ\1 B"\AABDQ|6P(P(k be,wnݺuW޻w|||6mںufOR( (338%HQ G޽{]M^$-BӍ~۷BH$AQze1BxJNN>|0q666}t "<<|߾}eeehذY.iUByjھhC(Oƣ~/j;;>}?>33s;ԯ_c~Z}@\iСC.]g;o߾RFbb!C|||UV@{G<3{5aݺuCUT1117n7idAAAn?Q}֭[ B*&$$DGG̚5 ݻ J ŭ[,vEc.].\h@E̙3g5ZbŀBxwׯ#üy󼼼sp,Y'X~_1m.\x1ZLK(2 S^=E%K`/^jJ%̙3nR21D(kǎ O>oT &1;?s߾}e2L&k۶ƍw@fͤRibbbxxutt<|'zU9ɓ'N6mȑC OQr```ff^o֬ݻwɓ5kȑ# JSi+@Zz1d7~ٳ' !믿.]tƌgϞ5~_caDz2FгgOgggq֭~8p@"T6ZL&#RV5 MG|X"gLd,˶oǧ1)St:D"!i Ci)VìZ;v1bD׮]CCC 7oܯ_ٳgлwÇ۷W_ O4޾}gϞma~k׮?>03)--}.]bŊqƅ?NvӧO={6//  m۶}OR^ <ϗ8::AELhoh4ee2ߕj(,,16t:X.ܪb;c*TdSgByᠫW߿;wd zn~~!B?IKP%+Wh(ʿ:`0\t)33SՒBmSpܛa˴UB%;t__ H3<< `%&&h >>>;wvvv&F!M^bIʝK^j.v'ϻ~=xɸ ॻ]DMNcǎٳ.S߹pႧ!Cv:o:aK0j^&j./&HM'/^[L<B0СCm~…6m@]Q߿O挐+B~\^,Nh*喚Ա[lY󁼗:_YL(n0ϯcb?oGE54z5b"-5<05dUW= uגeeehV1z*0/~!ӺukRȤFNZ5iԩSIƚzR("k0/7{T( 꼼<z׮] 4h׮37*I9 ^p\qVʈ^W~hώ.=1Jy\[z`77VZUP>}{zܜ__vaQ'bqE޽{JuJm3 558g]vG]dI=.^u֞={JhM$)gD}|E\w={1x}@@@VV֍7M"&ҁj*nݺJBBBBBBLsf bRxe `h>zһ%TF_0 5Q6]%,g(>T:Mrz竀 cJ%ΝnРADDիy_GCy>''gĈQQQϟ7fe0-[ᮝ; w5o|<W򊷷رc)3A4ds pر5klܸ1))ɘo~իW>}H_ƍ7^zU?cW{r5ыZ5XxyyΝH$V}EN8E>V[CM NNNǕAvz&MzyڵG3!t7nՋ/--DuAn3gjccSVVIi:Vf. Dcwg<H2w(o<ػjhl? ~DtPgܽ+-d޻}#4=|] o)q:f}Q``̙3W\I"uI&ر믿>}˗Y` 1 ,۷-[If`E~';;;555[)))!_k翬lΝ-"!B3f8[n:uo F,X@233O8zyya.\@tnڮ];{($$$!!LS*qqq*V޽{Ǐt:R N:UbaÆM4[n=x2csrrJOO'N:7#E1%%%,,;7 F& ,H^=S ݼ_A??q dr)礏@. BD 3 ˜ylY蟍C@E^(4h1;w^|EU=FX=vX4h`81?ryBBBm9CTs999IIIvvvwv@qqV%=;!@Lٱ`ӦMaܸq>vXzzzhh1"}fN>m iiiФIdKKKKo޼Ir(**"6G7m4gΜ7.^|t!T*B!eɱRT($xLf'*G ׯ_o߾_MLRHFiPx!}&-2J4 k!X 8"4gALɥ,< n)BL_~3 @M\N]\\^ڪU RV\9k֬O4w5F)#?|"!r]M9!!qB\FF=qwvD"Ւ 1^MbtL>}wطoHJJrww70!233ׯo,jРA&M~,Fe%ݻwҥի'O|elzE<ř +D˵ZJbYյSNs#G;&2Uc{}(Hrs'-NN 7* &8*;wL0a;vܹsg}f!e6I,XsΝ'XYs5 Y7(1PW3d4=EB  ={$O|߾}Wqm۶% E:tLnӦ ɭ$rJ!D8s e2[n_^~ EQ l֒m L_ycVe$S,B&#՗%IjxK`ժUNȟ{ڵstt;wc&O_sϞ=5^߸q#i =cƌcoj{{F;cwG;f!2C&sΑjዶQPPׯqW cb԰jbIlF*\\\jމvƍ*W^'>b@qii)VCYYRswz  O$))gLH Fi`1U5iƶG8R,))d$''U`J2%ҲRi7(BfffG/++cSkT5` ˲Uz@UUig{`=v<;5jݺ Bqq逶(~BXw(~BXn ׯ_yZfVĢJ+070|u.6b'5_B /׃+¡CzZSar7 b(7`G7 (";Ejo߾=lذԝ;w.Y}V18N//_߱SG[-oy! Cs^{sLD/"@AV/h%€I b "H FSUlp !d߻w...FsG4n mjq㒓|m۶T*K7Y׬^͛L0BD!]ݶm[!dGCnm6I:^X/A=ᯐ_Atb~,JB}'%%E-ZXLPTTt9\qF__߬N:uǎΝ>}J9r`HIIJOO/((SRRo֬Y b޼y+J'1`oݺuȐ!!M6ݶmkvm~M%܃R t-AE z,```K 2Yd{ô4~ 䣽=q:D-,,̸iLfLVLp̜ѸyG r!ЕDQ$llO`ݱcm۶ )!`f| IDATMfU-ZԹc6mڌ=ĉN:5b0r#GԦP0HĀ`0 @ T``D@ JdCX;NNN?CMһ'''o޼i/ig PXXh1͛7%#<6}~wލ1yc  … ӧO7/V;` b8 rl``޽{ HrԌrc&nnnE,bBF1dիǚ1޵k;vwߙcJ2 #r)'r)'2 O^kߘFKXK V*RF2mgQj%ף7nK,ܹs˖-- Zfͼy:uԳg)SR:uoѤITIII͛77 QNv7*P(fϞ}۷ ;& r~JJJsɓ' qBtRQQٳ322!Iرcd4dӧu:… cccccc_9sܾ}ҥKv%I ĕ%B*B*dJ K KϳRNAܓqcdFBdz(*+G=ztVVV~~R$Ǐ?Ts rqq1|z޼yӦMd„ IMg KRR)0V 1&LP*}'|㓑,H!u%PhhիgϞļ~ԩӧOsIMM%@"@JJJ֭ 7nxb"YK۶m1bR4ӪUƍرW^ykc2a D&؊ f=c>N5 on "Ƣ9V )'SPC,i̓\. 4vտUàAڵk7gΜ'4s-Zhܸq͓? *,QA0666f~gZ6<<AAA JLLiӦnpBRcQSLiԨQHHԩS z+,,ǧsfS(x Bh42Kh4Vhw ***99^[nȸxvJ4322V\߶m۷zrecɒ%gzVZ۷߸qN 6DGGW6#''ŋ/^~:L4);;{޽?SFFɓ|„ gϞ]bErr5kr Lwd2Bl0 [ eXcgg<H$ɿٌ0lذ)S.\PV)} d[&g/} lӧ??𫯾{?~|AAAAAA'O>zhzzzjjɓ''L-JvpP( reYX_TViyyիWGs5;{բzuٴEEE lٲɉt??$$$yCCCyÇפ͚5۾}{iiiQQѶm}}}Hѕ+W %%_q beB(++Ks孵8NeffVVꫯ\\\OިQ;;;3I&Z>\&u֭yWްaJ2Mٽ{W^ysm۶ef„ TLLLyy믿^kfM4iҤIzzɵ]ѱK.$m: lAΝ_>˲W\iݺ5˖-k׮]DDYŘijgΚ5*yдS_~ڵ={vNN[UKj0H뫯:t ZͲl  Љ3͚5!33k,;p]vݻ,lmmn:Y8AWɉ[y<ϯ[nOa173zW bwBrss+Ϡ!,jkk 1bS$5jT~% yc&3߼tB?^^^)L3ܳP( ƵP(ֆ bmߡP(ֆ bmߡP(ֆ17HP() a9 Bw0]OvJpر͛7;lxE+^ʱ#3cGNH!J;Bqqu:tO8ѭ[7Pe#⁣)Rn 0 B3dA |U{0Ɲ:vܴiӫ0b=,Ayáht:z3n#'hCEQŧB.988H$AߡX j3 E :1 ܜ~nߵKJTقC.AzFO3X"*wLcǏ9ѪUQJ_]?OM$;w4hٸ11>VH$n\s $"riڼzϲ2b7 s3QpMʸ|Ga؝rhΐ-53xSʩPE aM2b^v-11]vUEL$(6h4ee˖ot+c.A+:ܨ /TkmaC `TE"0~o~cbxNz`krFM[]\0K7!` TNE$ɳ[SH bYw9r_^1;,^ϧXE2%InSPes9Er\(ezQQk<rpgA^>7;lr5W-|- O8zq`1 /-_۩c3t7y ~2goh @FzڻoUݿOJ?e=;4zQu O8z f?xQ4'=~{w\m5$31Ea&8cf:9٣Giٲ'H*R :9cLH*`Xvm˖-=<<Ξ9lٲ'Onݺ5""byy$sEGG7h ""bysѣGO0U( uzw(֧#z.^.aw<]޽}S#t=wo$:68]ߙ745ˊ4nޫ߰ٵiȾL]Y6o3[VX/sԤ|L$ݿL_g<|#QmX/awZu#Xd~/Y n#0ZcԩSߚ10~̙sQ0 '*۷ϝ;5jTQQJ8uꔏ̙3mmmKJJRRRZmvv'OiӦϜ9S*64%%e3fx뭷F= lUw)H9$8C,bz ם2@_^&k'8l:0 bY$qR # HYpq$Ac\ 26JπL}YBs BTF gWM2CZwq?jgЪÐ夏R={l۶GfddիIsss#c T>f̘"aP3j֬ӧ) $R$7dB cw(֧j#p,q\p 0a&G 1;藪/c2cBXr (HXK~AQHYS[,'q0,`"ו0M r>燴bz!o3_/WA{Sd˲loҥK#""={vNv?X啘hV.B!D,zxx۷anNNuLvEW,ô_WUHRF&edRV.2N.e2\)d\㼡|ČO mؔaSQ.(*TQ9yܽ&JR\*# Ac[UH%jV]+rTtE /ٕA@IA$~A!:}F%JArTvwXӏcQ6n@!P HNNxG:u@??+Wfdd ZmYYiƌsܹxA B׮]3%_ Bo߾%%%tĉeBEyTw$J\*\ʑWQ!eR\JNԱ*LIriHDgo$]M(͜= 8I\oܜ~)Kd2dR% 8$TR=g`ˉNӓ/bg#r/%>"W( ?4lސw+dvO4w=lB&1,KH>fLVP񢲕%#G nӦ͛WZϲuryVZnݪUׯ,K<aݺu UVFqM6mƌ!!!.]bYvљN2mgYf͚5ҥKϞ=|MbQJ;99`AΝ_>˲W\iݺ5R?R*$ ae'6,Q?ttqg˵rs50ƥEj[gT ΡrY@rMiͨ-+cm&O{0&O{)J777SeeeeeeU]^W AzUgee|A eggX(͚5!33?G(b\2Ҫ__GmKlmI^I%NRY* 'lPl,h񔝝]5i-*( OOO3ae2@FS3DzQ(E-(qr B[ qENHQ\dZ3 #K Mw8R\W6i`_&88B] (d$811eX%OWacY6::z2.Gt~_d+q!xw?|֭'>Ѥ c <==cccsŦ?vh+d~cP(Ϗ 2kZCP By9am ( b2`vvvjjFMʿRk6qa9`NNNrrJ:K(:.99!Ty|]‚puuupp8˵! < ) 322u~!hccc @PLBh[/L&S(% :yU B&ѲڋhMBy!%u~CG)K-ZӡP^,u PCP ; PCP ; PCP ; PCP ; x Ū`O bUBEP ; PCP ; PCP 7HtwP^:;HY PCP ; PCP+,,xAQQQV|˖-|ǟwAM6=~xPP.ByX؈( Ώ666f~7HII2dHF޽{nm>YYY xyy=+{.yR(uQ z0<`1Ƣ( }L.Yvvv6Mf̛7?|RJze2EQQ0@.***ھ}iLT f̘Ѽy~$tĉ4i2}tV qqqgΜٵkWTTԑ#G[ IDATکS kƍ ԤI#F\|%TV0'Nڱcdzgϒ?cLLO?Ԯ];wjڙ3gv5<<|ƌ:9o %Ū Fh4eh4Fј;wa1ϩSfgg}]ffi;wL>{vJHH8|0?UV߳gOǎڢENg|EWW׵kV.Y$Ϗ?~$aqq 8f͚޽{c۴icǎ#G8qѣFS(/7V7(B,2 0 [ eXL&3MG߿ &4lذaÆǏ?vXFF9;!C]rlllR\.wvvJD>ڵkzW^R422b}Y;uT׮]Ó} yo"u1FSZZZxzz˗/߽{rYJJ~!9ؓ0 q:.##L!&&fҥqqqAAA:tܲe Ijժ;wlٲe˖&1bDVVVdddBB٩-[FGGwԩGmڴafʔ)5Q0sʕrm\LLLnnnhh F>R(utܹ,{ʕ֭[[<==}WXѡC-ZT3/^ .԰Ǐ_xz .\ti)U>E\w3쥥3fhѢEӦMw #Fɓ'GEEϝ;ݨQRǔ[FGG7d{>tPJJ ̘1cƍ_~edddI׮][xpn`;f&;v?@qƑccǎsH"=wyӦM$ oرcÆ [nM|ŰjܯnJjjj|||N;vN:E֬Y~={83f̙3wHSRRRx7Huddd\2//o˖-ꫯQF3fpqqٳgϼy󠊀xxxL6m֬YjQn VXXHAqqqoƞ={89}̙3?rؾ} F}ҥxŰyߩSwЁhX.Hn߾}9{{{@N޽5 ''UV088!dmtB"sRqvv>༑snذĉ5iF'= 4mڴSN2]veeeIIIe˖I&T*OOO{{jS(eN ?~7bѣGkK.5'##w%+R{gűATv" (EtB"_3q/(>|DqWbP4Gh|"FT^GIylAvuu5=}VɲK.iOݻ7|9 ;;=z;wn̙1--ͭ B MLL֭[}ʕQFİeϝ;WSS4^vMX+:ivMif7Is)J J^zICBBT*Ubb"Vm<`fʕ믿nܸ1&&嬆EEEO<&ݻw/^k nV>,Fb{{{eSSS~唔ʞ>}ʴGti.#G ,Xn]`` jΝ6mZʧNaÆhaÆ͘1#66q` [fMk(JWWÇ'%%m޼;{ҥKKKKn"-ڸq7zE*yǻ^~.<4=;;W^0{Lq\yy9*֪T*Kqqa }+Pnn.BO>ϸ^ZZj`` [N8/B^^^ Ϟ=k}zLmm-^u*)),˖6Hܚ󄎂㸂n8.p)@yɓ#̢\<jYaRPA ~3USVV6w{M:ɦmx#GxzzV~GR!y?gAp2X>--BpNkWB'@!  <&qCク6Yz C<B8 @WSScbbҺ?*ѪY~}nnnZZhl[nm ;9!hTD>[n]pܴiӴil47S8Bv?7(d(T? 8z )BGAimEvEEž},Xt@/sYYYll<==***pI&9;;}7~~~?ɓy@ll,Vǻ~-&&ˢΟ??zhWW׹s}} MjyAx^xAB,p#:NPs㐚,8Yl1O܏j<CjP %xLfuB|NN lrotttaaÇ:TPPXp~~~/_^jƍ> 8.''g}痖bbbutt }0WR֞󳱱 }JUyiK{ Jqe5|*TBUpu{nϬ%հ\ŗ+T|J`P{KJJw={vΜ9OGř3fԨQvo lR}>:uo$I||R;a 8SլeY5˲GEEY&77UxhƲj@XXO?puUUU,[[Ylٌ3sر1G}ÇjunnYbcfffRsNu=yYVfmkǕ|A_嗳* Q+ q%WXUO'UB riG%swyyyw=z?%\\\9uwwwMX\oH$ϻEEEױy YXKKˏ>Hc t{_.ܽ'x~$>j(kk 'DDD#;;Bxرݻ{yyeIBNQP@A DH!QP@A @ JZ"ө;ޥޝS( v~Jvv6SLϞ=SSSŠ;wZ5-3tУGU\U~ѭP&ɓ?.?ZvmVVUsݻw(ݱQTUUX{[hM3vIh >/DA*B{A@diޝJ˖-裏"##ivvvvvv0` 0`@߾}q3nܸ/"%%%$$$;;;##{}wyyyvvv}X?E4 FFF^͛斑QSSL4_| PemmMC^8A bضvKiJsdۿ6_BH@y)MIddYJ>KHHprr1bݻEmݺ5''+???11q˖-V4hИ1c^FFFw~ɾ}&M訯ס0 C4(H$ ìX$;;'O899!b t=*aLRmۦVi~)_󼘶%\J%֓zRZO&};ry]uYmUYmUۑ+ 2ѓr)#221LBI|VNά=~At.'O,++iZiq&N8a„ݻ? ܵsNqScǎm+2RwUj0!##''yҥ_|:~Eo4-JBE666{KLLT(ϟCWHcVv=D&agxSݛFv @zRFelhafҲIBh@asǾ|ѣGMMMCBB^ΎE&1 #H|nϜ9Y/BEFF ```x9BHso7B۷o큁7o,**24406mhד1z2)MQ@&^|ԗK@A722,_:FA{ݻwfΜ);Q qIRL&Ƙ5 47Bػw,޷o*v#=@))@ApoA B MUXT?l:Oѭh3 zzzovppJzzzbX50;j'9fϞM`tt!R*'NAu"(ѣR"N;:EQ+***//X8GO D"!N@!JVVVݖNAknj9^A@Z7H @ CʺGo)!wBG }.;:Gw>:GZh5`ZZӧO2o߾ݺuks9rfРA}>ίnjZO>|pժUgnnݺpB''9t9;s̩S,-- NwD]W"g9rժUx{˗/W(ft޽vvvٿn5/JKK'Mt֭ׯy_Nu\T*nj9o&OSΙ3ѣؕ+WFFF˗/7-R=vZ;;}_~eƌ...xܧ4~޽~)))iy?NwN_O:xݠ d{駟^v;y$eԩS8c\]]W~ĉnݺ ><<<ĉ'޺uȑ#]GͿ#׮]ӧO߻w/#OJJ9s&oݺrtt|퐋/zzA}NJu#Rjl2|p;[w}@ccc?jʜ[...RwFq===뗙)?0nܸƕٳ'<<B8m4Z}1lg̙3{֙ėw}޼~a\\\QQhQT'RO|嗿ۃ=77둗|.\Э[7,Ū+WHؒ7k֬؅?SkN5~_K>:t͵J!4Ǐ;v޽88x`+WƌܹW* 5gee]v۫VڴiSnߏwm޼YaÆYN+Ko~_v @ٳg}]^֖SN,/?رcwIdqs;7~fll\4ޮ3!8sjj*/{k>JG =:f̘ƻ߿޽{Ĉڸq#a#FOK.Mvv=dq[l}vҥ(++w}=RK>&x̐CJk=K`/l?uTbb"[c?XOOh#0=zꫯ>]vx.H_o:N޽{w%K4.PRR)..̬?ԴJ.99y֭#G߿z˗/4hȐ!}љT-c Ś5k|||_ !,(Q5yIDAT(wą-!BeeeB߿?o޼$??C}صaD5MѣG9/z9 7gm۶uѢENNNN222j\+((hȑcǎ:t(EQ5:88 J}||,,,jll/Q(| qNNNݐ6<3ZI...&r!5^4E 2!\(.BqD]NM>ҥKR& >K{4!;Go9o$C;!DD|v}htϠV @W| ;Ht!~@ hw!~@ h2;lڴаk-y:..NT'O޼yJP###77qinJ;Icj֭[ǚ_ΫWѣ)**JMMMLL?~6;XZZu-Rɓ'"##ŷ\q<cǎ>決#'t*BZ7qܹs222z1p@̙3OxbMgD9 SSSqu)&L`llM%T*8y<!8n ,x ɺ%h?ٳKKK'LV/]4((ظΝ; T-[Aݹsںs?~8555""y.vލ۵%{|a}BF{~gΜv!TaGSUUv…nݺ^t=ΨVSSS===ۣ~ SNm̘1*44t/^ܴiS55FOQ #ϵ4ڋ+ӧ56l?~[n_"""\]]cccY]lR,..9;;O6M)++0`g\\\EEEidd rpK|СC ,xv*߹s[V (ŋ[OTVV^zu׮]?y+V,Ydɒ%[lILLΚ5kW\a{m۶6lp1a,OHHݻɓ'EݘVr…۷'%%5W`޽Ğo>}ƍimms?߿G˗/ǖ… &L033{wjܹsǍjT*B #}\z Σ/_믿 z*`"~֓&M={v߾}=== ׮]{RgϞ3gcddӧ(%j>rHL6lذgϞeggc۝kkk?3#Fd2\&99޾hΝ̚+y7mmm_(a{yyRܵkW=+s} aVVVfffnݺu޽sΝ?9sٳgÆ q\vvܹs͛o߾^t޽{{m}S5yw;)! E|C qㆁI8J$(qAK+b&x#//R31//O>ݻ7+a<))i͢;5k֋YXX8dȐC,(ںukNNW~~~bb_z?+VМbYhƍoܸ[o-Z5ڹs=<}:4W6 G}ӧORRR``N8addԶ{Q$ v:X*$$$99yܸqiQas2%I tY !~@ hw!~@ hwiucg Cf EA2"چm!چm!چm!چm!奬 %  ! B@@6mC@6mC@6M@(^ #8~S8Lѭh_&BQUUEQ0dU!5B(nK҄166iٳgϞ=~aLMM :!K~```WN : 2E  ;A?r ~c IENDB`workbench-1.1.1/src/Resources/HelpFiles/Features_Toolbox/Foci/Foci_Selection.png000066400000000000000000000771431255417355300276750ustar00rootroot00000000000000PNG  IHDR}TG{ ciCCPICC ProfileHPY_Das@ 20q`Beqր (5 X wEA9&Tnz_ wrDXTa8ۍ=<k7M wwшnqV4.Pq4n*g\8aЗ.B0M,1Op|HgdrH̼B@9DaƩfa?SϸeVxA(_nVjJrH)3FQoE)A ~0,dq ^' D>i }8A,oφF,-cOxY^+y@%0HA "=~M˻et l!Ԙaina?f;{ :]Y @ <"f3<@ЀPZ@ {i + (pA"HbցM `/(ap @38 .: #A8Q!%Hҁ K 9C? EAB:h TCeP%T BP?tƠ'a f0f~p(ë8Uq _o<(@(wT *6 P%*T= ՍQX4@>04].CC7;7C W 18`ؘH ǔ`1.mcvXl6 [=mcI39q\:.ww7}xK>/oƗkg) A@$ل#6 a(K#:CIMRb=D"iIKIR.tt4DH#1 vQr; BѥR)%c)['Q\Ij@4AZG%B:GD q GfLL̠̤,UB6P6UHVNNWS''wX0EբS-#. KӣiIB Z/mB^NZ>\~|9y Eץ)S;O ,xm  )*+(6(VPTJVڥԬHlT9Kr""EEN-U9ң2*RݯzIu\檖Gژ:UY]GsSza54iZ Z{:&յhӮӾCa$yUYwTOQWPZJրilp61L4,7a c퍅Uƃ&dIIɐ)tiK3mh]ff_mS̏?lfВkYnyˊbeѪꕵuA6T%6[m:lيmmb*4fycfG[tS88&;:.[a'M'SęEÅRU˕ZeJbgt3w5wwp_( ,|^3Ves5 _;~d2'b%%< 4@vGAzA~Y]|`u!Ԑ!!BBw> Q!4\y=J9J\lﲑ;Y~uVJ< Tq&qq\w> +oo,)8YSBq(߉?X8.p ^%$Jz|4y:%"!*& ;WZ_d$IV;޻zB'NҖӐ`ӓ]Psfy懬kddfo~ZZڎu6ZZ_cƼ#޹67%ouoDliS||qVǭG/wն۾ ~.]'laNN;\v+-)޽dwƞ=o{ĺ>⾌}RҖw\XvܭBb[]R=TxӏVzW6UVrXHV.n|槿+(B3y pHYs   IDATxw|WH A H*-"*"ED(>H`AJ$ $7ݝ4"$$O>gݻ{̙jBPl'ցBvBcuP(,wnܸqԩ˗/߽{[jաC8y)?icǎ;vdeed2a@EݧO>}PP(Pzzomڴ T*L&8xX,FWYP((lg޽8;;~]p J}vN{f͚U(:cEEE5P>lرOZϸsZC߾}ǎxbx7|||0ƿV=w\XXXR<|gϞӧO//w>|X.T7nT&y3<3o}z뭥K.]nU- .';;[&d2N7y{yyyyyݿ:ƕΛ7oݺu֭[nd}jK%PAlٲg}6))㬬~ 4H>쳜I&-\Pŧ~Za?>eqJJWXg2ycԧO1cF1^?cƌ]vuʕDx_|k׮=Ç+",:;;WYd9r]0ƅZ)0 ˲,j4JemX,+bב788xђ|ݺud2/f`zzsΞ={oߞG[NVϟ?wժU6mZpԩS7o޼|PQ߿/(z>''GEtܹ={=zeŋ37nccclW_Uw{Q:DZ;v())ILLٳg@@ߎ;N8ҥKӦM;tBH{۷wEBfΜ9v؈%K 8ёސO?펏ONN NNNg,Z?,,,EǧO%\yO*Rl( xxx(;w/Zc|9V1ǎqV%d0`(/2۟>}ѣ111qqqF7n\WP( ENv1f̘}رZn-ϟ?#)AAAλw>x?_'FFFN2eĈC wϸrXXXfflnݺߝ;wȝ7nz޽ JC+ M-\]^+H999{!GӦM ?~{Ir Ð]NtԻwoWWWqmgǎ2b0 r`Xxh4⿶BBB1s̞={V.lL&Io'ȟNTTQXXuVVo>LǏݻW8;;GEEU~KHyܹ#Ν;^C^͐I;RW޽{׫WիWfAAA_" r422255u@QKKK,YҥKtCZ1kW^zgM&?SBѥK0 ###???66VŬb*"+W~_uԨQ?s~rSO'000>>~Ν2l2܋dB`Y6>>>00B;i$$H^Rs^ͨV@׮]ޣGuƌ;w޽{JJʮ]z5bĈwy'11q̘1FzWW!KoOO?ĉ.]zqg}6 {@*{~=͛7O0_ߴiӽ{f̘!yXR>/3g0 )--}?#%K3&222$$: Е+W=z< AApssԩS||| e=N^gggkXDQT(z3@׳,P(@ӑJ IM*Ʉ1V*תDgZɉ|ȅP8Dݻ~۷srgphp-qR(ҥKOZ ϢIeX.\i0FMaaӓ9| a˺UD%~7H7&&r'`?IP7X,ݻwvՕD%$̐u@إ,uV٧ FK ƽQ!n r :@ߟֻwdzs__!Cq/ eFB7 jL~R%:l5xr|p/)H0nnCݸiә3gz)h*4#{ȘrF!ְE!_jlpVo,zf]WZծ]w5pʂ ( gggAo1Ƙ{E!d˘ތ  Z/V. ":Ȭx5c=:IR .++IPzAZ)gNAvAVI6_~edQE,&c^nRšiyyydf[wܹΕNbEW~1kA~$jٺ}hDCsIM]?m~ cui$ǵ;~پ}O۷o.]~\U_ZʳgVMݻjy5J.]4DN͊wQ9<4Z^ҏ?sα5&X_ۚK&懀r3$XRIc;; J &Noewczj߿߄ǩ֟<4Jz%-ZhѢu lS[DlY@*, {Nx}I‹%IIe.l&WuY,z&i"a*P*wHh4~~~ <ɞ={mvŊ|^^^۶mC<3|𠠠.]:uJ*b|Ǒ>nݲeȐ!~m6m֭[;v0zhnMlM2rj$djrm`, 5k֤H8qbժU+V8z($''o۶իk֬|gϞ]n۷F fh Qof` %"^<`]B-517_Kr=hk#o=qPgY׾|̝7kS,b,b4+VHHH ߿ӧI. 3g ife?_fucul2;; fٝ"|f`0/G͝3g`~&NvSNr|ذanzwfΜ٪U+0aΝ;95kƘ{qM6}fyРA} ;yL&σn޼`zyyeeeEo02b̗...ǕAݻw+W۷綾^B]~ի?<(+((ѱ433!Խ{wǎ:tNN&VePY3`TY2w82+%FnN( ?؆~|`33 ^ɟ}ZqJHyՌ\кjyIѩuaaaoe j(nذa„ 7oꫯNzEe1, Ƙ\ùsodj`Eov';;;--M 6FJJJ#yUFu:ݖ-[SD߶mBhڴi=sݺuo Fϝ;Ŀ233<7aΜ9Clذsd-Z>| SK.%Z 嗱cӧj&xaЭ[K,4k,::nܸOOk׮Iq\nݤňDQuVTTTWn@ #& ܹ|A:8{ {F7Ԁ\ S[~=  0fZz~h2Q+V "c8̙e˖/VwcH,^zWYBJÍ} Z䤤;884DvB:Y`ڵe̘1޿zzzdd4#}֭=*uz{{@tt4%ŒׯRڵkgΜf͚ r"j4$D,KjJ.--X,$l&+ h4Ç'''F%QkM#bz ;a$k3 $*V` $X m 0TqIBٳgҥlg$R$ۮnnn/_n߾}u7(˖->}z|| z-i2Rx=]T:;;;88F 7%%!eddxzz:::rxGd\ ?-9tl2GԩSxmoo۷K7DJJ4ѼysPffT 9Mc$zzgVX1qċ/FkFTPi7ۗKSχEH JS[K ۷ǍiӦ]nٲO>J)Zm^^˲1'N;ߟLz/ר,x׃Qoj:##C999i4+[L&J"tƸp۶m!!!Om6x;r Z믛5kFn&rhԨQ^bbb0Z C 4!!! -Km.d kIt6rڵkJ200!Բecǎ%ҌFJy''͛9sFU3*ˤ68N"0"̮M᭟‚pz熠XخGZ}Xlܸٹ{'Ol׮]ǎ|x@ T*U*UCb !^) JwoiO[ܽ{ˋ̃tRBѢE+V̘1c/k&5p$c<{o۶-<<|~!+)))??ذa...&iԨQsέƹ}\YfͲr4JHJɤۀևPT?bw$DB۷o'Q-[vԉd...޵kSOWnR!S*edT %U0 XVY6}_6r0uц_ZJ'Be*  l\R!%?\ ozܹY?qį \]]{f5kHkK/ҵZ1""b .:viԞ ȀBN}5I(H}KOT҈.)iii;v~zRRRcJ H\ʧھ}{VVNBi,oo~4"Y$tBi,(Bi\PCPl ; PCPl͟[n}zP(&Obb"DDDt CP8Ҽt@Y PCPl ; PCPlMxRׯkZaE,ZiEf MFJ :coo&Q*GԆu k.l-#WGV!`D2YY$cS0Ço۶]v7ng;888..nժU<ő%`_5)<..رc 8p`zzT˲fYQAEAQ,Xv9s+,8zhN2*CdڰqChhƍyMhYy-¡?Et$d4 ,<6 ,,LbNhgqRs-QcccPTTtIRf͚^xaɛ7o>yԩS5͈#,˭[A [nU*ٳ?%Kdd+A<ٖ*]~E4~" SL>|,Xp wwW^y%)) !t޽^z&k:uz饗gҥ/^lժŋ+D^ ,]O>w&ˊWnR DVB@OCԌD,x ;I7n^^TXd2d2b ڵkBӧO8.!!Gk֬1bD͵~GZ$9C"yNIIE1*:l6$]RA vi\tҠ >|ܸqZu3f(ʡC۷o0Yp?p)*jK,1`oذaȐ!-ZjqN;y}"@L[] `VdP*SE;,ox}IRRRsaHI%-ӺukաPCv>BYDלB`2DQ$ŋ':uSϞ=vqF.%Ƞr wŒ IDATSO>++ȑ#Ç #Fػw6OkO3 1`! 1$bD@$Ѓ$X;FIv'$$~M~//T)|uxI;K*_~]&I vla #3Ct-?N;88j>=##22R&!!ZjA.BAٹ\" ?m?nuZʕ2V!c+XG9S8UXSXsBec,+KYIvGP̞={ݺuk֬yhׯ߿RΜ9?u NО={@/Fv0 eœ%$uTHjjueYqQQBHKErZ1޺u7o޼y󘘘]~XS{B(R)eReB6}8×}0*×}2r< +sr9#g(G3fŋ-Zҽ{vT9Ny_|ٳuֻwI&Z䓒 ]r8k bƍVϟfddd2bz\bŌ3QFk2qSNU*3gLKK#@&[:tPPPвe˅ !ZڸqjO[ly;T?~\)d[ڢuQ\YDt@e f(bJ)TP%z%̯~pk9Xզ6kޞ*LVJLaьRzQM$/..&PJrԩRb +[/>ɆYA$݇SN_UFdeAļ Mm¥ ָ:)ũSZnM u`ruu D'Qa//$???Ȣ4FaBBBN]XX1= LqvvdP)7@rm2 (FG >qE4UBt> bkݡP( bkݡP( bkݡP( bkݡP( bkݡP(Q X,^QQQTTŝBPjϣ;[n=q͛7W Q(&ONVfݸɪSb)MF9}dB<U؝h5xG}TJcP0`Qϗ~ʪx|q=/" $S+j.=)))!!aҥPXXw(--}ע÷nJO4iʕs̉h׮Ν;+jժ}6o|РAϟ)SX/>oU7(SstBNlGAa[Von}I>U7oTECt%55z- 6l8wܩSzARfdd,[o۶m:u׿Uٙ^h@||5kL&^gϞ9wܹs璓d„ ٿ?1qD"7nǗ,Y_ Ne =GLc{蝋E-+cpppy/8qɉ,FN6lؤI"##͛jGcM߾} E}voZhQYG{ݻ8qb͛7o>q}:thܸq={VpP(uErBbrsk_dDw޽P(JJJ$QGѡC%K+ 77 .o޺]v-^b@B*j7n2dȚ5kMVܿbbbn6m޽{d1N:=(PW:)4*gG !-tܣ^Q:5mX r\>gΜK.,[޾}{o&L Guȑ[ 86 7n ׮]ooo___8w\m P(LvUjvsQp..[9YMswc\]YwWM]]F#GYfU8z墢"ݻ[qN:UTT$]\\HXW"//|B.::}?û+ s=צM+V^ZX犋իWǎwީS'aƍG5h4+=gꫯn޼j*r._~3< /6B[ɓ'}||XtRxOA4c *®oqE;󅅅Awyƌ999-ڧjV`v_Ze ):ԩS[!33`YnݚOLL8H jB6W\9wGPC*ΏPB%B(<rHJJJqq?3Y27Z b @!U;%T(e%" q3ׯ|]]]+,YSf?e'vIH  ""<.޲ɻ5=S wAWW!C0 Pb_eFHuj so bΚ{g5&L ņTmwDQ?63QO/5611`V?88nbLۅB UAXuvvܑcx,L&Zތ \ 9Q1YV}{T (z`w4Y9ޝ:=se~P OH.I~I}b|g'ڶcۑ;֭KNM8ѽcf,-"Isw9胷fQ_,bPb:3~PbaE5wFc ǵk6,""u_uN`ԨQG |7#"bcc-ZmXJJKoݺ;̜9UV~~h4E^uJ5w?}周Snn{CqN8?Μ9s߾}0w~_>==].CW5p?ĪL*oS"aZ>cGγsp&i־5.~·80d&_45s=Xs-|_\ k𒡴X agg_!S;Qy浌E3wF ^ΰԺ (ElZ*C55;PꩲᓑqС#G/gϞ<JU.;99wܹ=zpww\… wSOA ݻwȑ#=zΎIMMeߟ_)bƌ3eUI=w;ߤݿ|r" m[7o\Q^ox{)%<3$/b&?IPX cu~ᱝN)v.Ҟ)dr)0fH 3u1]af=NXG4Ƽޡ)Ea"1"DaC95;$b@ ~ݚ5k@<?L4EG-Q!rHHi`lgdd+n۶m޽lٲӧO0᭷R((*9 7mE~g{k3ضedJryzs(^Y)=7OV*2.a"AB*lI^OHZUv.1/V978#+X1S  cAƊ,PlNy>!dtB l޼9111,,4u̼~/Ӣ3id,B䬗o` \<ӯP/ ED(XPu|gl{7ݼUr:M,ҌED[D(8SQbkmgT*e+$!t{%3k֬ӧO#BBB\BBvWe`߾}_u!Ο?oX[aweٲe!BNcǎd@CQ)8\TrY &3n_:°\@H_piN Q%9AH%'kr#oTe&ݼtҁm  P%yeT@!rYǞC K)e\Az3? iTraTrRΩJ)R)R*dH!c97bEvGPHx+֬_~ȑ*J:ڮ]72 /fffmȑ#M6m…ݺu# ˄9kX}f„ 'N ٵk״i$Ⱥ"eW\T*۷oߡC''',GFFŹ=Z2֧SATȔJJ!S*d*L+UHw;gX?U ٔ+Uك"5{H˜;JLƱ!RI0kP䜡Q̾RwO?{H YA s3g Ox4rNGDO-|ؼj}@WP*eiJ"c Z(r$룲{xÓB[ɓ'}||XtRLJ8T";;Ãd7zťもXZZf}TRGGǚ+N<<eYS4D1;Bvvƨ/5tNՕ#%[y.>ֹ2ݼ4d8zp2yu",1"<6b wvvšPSNnZj;z/`8}5ҶC*,[ٲ%UZS44 kZeZHSM.Rr'' 4%幐F0 ʺD㔅%P'=P(6jR.^qiw/{??¸kh,YD0AYΪT/ePjqhLNNԩ?e'g}ӴElPId%{( G)6eٞ={Z?|``??5%-7WLBo&!__ߤ~>[jR>dORR?Li8T0 ۖP( Bi8T=B7FP(Z zjT=`NNNjjFqppS(MQM&Sjj*B*Spwwwrr"d^' R`y/,,hv!lgggBiHkj1f}SQ(d jt(&C/*UmoߐoЩѡPd™AvS(MWawC4aN)vBjw(vBjw(vBjw(vBjw( R(1R(, bkݡP( bkݡP( bk|?) , bkݡP( bkݡP(ɯexvڕ+WJKKEQE"})V!PX$ce-[lѢE]-V[gSPiwի;vh4ݺu=z\.,Kii+W 0 ::ş!Æ޺}?]xqrrrll-%(q6mڴifݻw!ĉ h6YeeYdqTr]ve& q0%%gϜTo޼; Oh1"!`!Ae@*l6ד)7R4MvwmŊ˖'$$5*11֬YgϞ-[T*OB;9r$9ryyy5x饗6L&#.yz L5h HV0 BH.;v|Fcjj*IĆeYcz98N&i4d e˖E9;;,koongg'5M}Pllٲnx_}zDF- |$((ɓFhт<~@ ¥X/BHEaxZϷhΒDgkr`]R3oP͛7:Yf͚5kӦMn6o\X0LQQ^^ҫ[r tĉ0Lvv˗;wLvI䡕Y+HU'](Eѣή]:v(4Ir( ߩLsd2"iժU>}N>Ze䧞zC) `2Hr͚5 'ʔ)R^K^CeA}'9 !Axt єڵk޽,YҹsgOOON>}1k#mW)P8 kÆ Æ i&22rӦMezAx w~~~ii)y޸sС'NGDDرC./PeM ׇǭ_>66M6;w޴iӾ}ZlI#):9 fɓ'}||XtR %%LJk <]v9KR'BU {*eCl6a{!!!dJIIF?S4Xx̬O.Njݺ MbY#00pǎÇW*RySiT`_<<}zʕJ]v:u dcΜ9c4⼽a@+OSP)'goo~/HȢ@]0]GGM666O!;?/r, "(=/ JS!b1A@@&hnn>{*P(weȾ>cee6vY)%D7j" b qB$)ih&M;vkO#G:TYY9܈W\e0ƍKOO5kt.YD" k׮[Pj555׮]svv>}{uww0 vss!nq+W-\PWWW,͞~D g#QE $%%pF#UowF8.|SƯe ic)cVy~/)hzhdp_KɊ $$d޽#G̟?8\_\x1448ҥKmۖB4R ~<##CGG_;vxyyԸ\pa`[ٳa<ꫯ&Mx Hs͵k*w y7np#h4T%7X,"W i#3w^W׋_r~jvw<%KI(upvv8p`t:=(( ?=gΜSN988?~PVVOR Ǝ;'22F\zu-nݺ5k,fkk./%)))Ң)h>?nݺ6  e (^t:][[{/[;vlڴiccc<(dԩ--- ݹsSss3`T*u$31""ɓk֬oܸ-H6nhffpy+8 \/YCrD+++CCCOOOTWWtF:CJ;dZYYtppP$:YWG#vPːwgS~>۸S,+d=etf,--8=11Qrmkkw.^C\\\kkիW+>>RO?uttlP(W._h Q0.YO- hO65=#V]-6 #c0;Ovuuutt #;}},OOO###6ݽ~###;;+W̙3ۻٳg2"HMMMvvvZZZph4@@tF,] H5^ ~~~nnnW^-//Iд>}ϯnϞ=W/ORHkkkhh_=k,yWRtEd@ 8uH$Rn`< WN:uX,+/Emܸq„ \.W `_ԔJ644\|yÆ ......ׯiYvmhh";FFFxgԩ2Cji;OE̊Yef062Y>>^^^ҍ^ccc$S}{ 99yŊׯXa|I__$;AXXG}oeefݻӰaw._|ҥ?sXMMMY,Vllv\\3_rΒ%KbccݻvA""?СCgϞV"qiP---=yf͚UTTtM|$@囘0}566&Ohll3fP" (W_td^ǀbmC5ҥ]z̡S dX/֭ںAعsg?IJ|rqqqzzc];??_0!x̨(y}:Jzꄄqrrjmm%~.C֭[Na+͙3~ݺukdJ̈ST*Ç׿T*|roߖnFFƹsnѳgfff} O0=99{UIٺu">|;vؙ3g޽{1>>>?Hd(Sg7o@[[{ƌfffǏ'⚚}wx\AQ4''ں);;>lɓMLL"% wܱ0[Ppyy c2! @| %$ %_X^r6u \.J(ӓ[jiia0S{zzƌ3hx?VԌ;V)ͳg "^;]]] L&?}\/0l Q 0VGGHܽ{C$: b"aBB$cؼח^,))8>==PvB+}׮]׮]>bgg>0tUn̙2g,Yxb14'O 7xNwquuݾ}{[[3@f@)Ǿ}dJT/[7t$F78 bhh8tmQcc'T D-oXoPm@F7@jԮg睫@of䍣i ?;o73NGD"Qn0kTBҥKDXI---jf6յ`%YYYQ&!oΛD~~~JofIA  D7O \$%%S?ϟ{ẓTVVF".]111===455Xx33sfee!?c777|(3TԱ |}}hgϞB+((hnn&\5h4ɓ' ϗ^A߸q?ۅg566斗7`q(D"tSꨨW&5ܳq@XXXEEEEEHRD"ѝ;wΜ9(y3WWWZceem۶˗/]C:5SkkS<X_Jdr}}=711q# <ܹs2~%oRHRRR~~K&N(ɓ'};;;~ p~M(ovPk!uߑFX,2d${g7H$~)%z3H̛7o AAA'ƢEN8FR D"#lpO]v +[Zw0 񉌌T`AR>Kԃ...7oһh:88$i`G2Œo}G, ͬK(0u)*{IPtd2D"-ZhӦM{쑉vڴe˖iiiD"H$({{ Qw 7!TgH %}f~Klf͚8 ̨Tjfff/h[vmyy/p8Ǐonn1c@@4  .\&DL0Cz3(@vDz_:tuu!i4aOBQرc|>_`TUUq\77B j *Q(2L2---ޡ  oP 4қ0Qv555--ٳ #Gxxx(b߾}}Ν;lvmmH$JKKkjj҄B!@ ܼy󫯾c IIDATZMMO?tAݝpN=rQ/_XnΝ; e>}4559INNvtt={޽{RQQqԩSSSSSSsss_Ap8ZZZUUUCuwwׯS"aĨH>=s{Ĝq{[u p3qd⺓t[۟;wndY|m޽{ʔ)nnn(fff:tðN:ӧH$Rooٺu>^0111[lO+WZymٲE+ ܹsSL ϟ>+V\3"@$%''UVV~ *,,ܶm7ouVrrۇ)))yQ]]_xO(|>)cc㘘GrHH- 8r󇓭Qʕ+_|ŋWKn۶-%%gee%$$(JKKK}}@ 4GGGڵ~>~A@~~$+=r2kkN{~gzz#7o*.;(ʳq`X\a=>}:N *++fggϙ3ԩSǏSTcÉh...W^x[n͚5FJ#CJJJ``̲u퍎?3~֭[j#Bxʠ3E[n=z433S5o߾|A{4Y`-^ʊ` qWbee'ύWO\.wDsrddd >|wurrJKKܹsgڴiNNN r:03J;dZYYtpp fDDDxxxhkk~VV޿]}}+W̙]UU5vD"455iiiKáhGs XtiAAA[[jd):::bqZZھ}>|vZʕ+p_d`jj ^~& 73gBCCq .lڴƍ/_q :2cƌիW8DWWWOOOKKNlAvARgϞ?><͋2Zcdd$ԩṠH6v078!$ = )z${zz=z%//^p+55U `llL4Xdo|rr+֯_SWW'sLXloo';v_}XXG}oeefݻӰaBYfFOnܸ㢢3gTWW [jUll찺BVXlٲs ,WbccrWZU\\]O>⎍Č///:8!N*q|Ν;K^|8==رc.흟/OYxqzzzffommm[[[ouww;99eeeEDDO>0{l)JNNΑ#G^ڳgϓ'O0 #wVE[XLTʤ9s&;t落ipp˗Ϟ=+;d2yjÇOWee%ޥ0H$???AX,ְF:d:D!ĭ6l&&݄lJ55G2>ٳg~22S(>?~dݻxü<\zN8d2]]]mll>DEEY[[իWˌiff6k֬a YJΣ_|iΚ5ѣ_Nj/\n^^^tt4aC҂ ^x-H*++ ry|J/˗/m"Ll*w[nQUV7md2]\\RSScaa򐐐ט&P=&'0/{KrT*EQ#`--- CM7W>p88+BBϕT9&&&}}}<OD"# KAASÅBakk'ݻ"U#@ rj@c}}}bH+NIIΝ;e&MR(o:> 3gΔ9I$HҢ` SAPէ .d-Fz${xx۷Ojʕ,= CthGĉt*v) Q@ : *Л/ n@of;:ۿЛQ4MwRofBuG"(WĽCVNk d AEDRS]YTtY [W!vxy}BҥKDXI---jf6յ`%YYYQ&3m$NwD"Qnn ›`bR?0AH{3*;III|>ԩOkkkwwwttDEE=x޽{$%%HK|~LLLOO;MMM,+>>lܹYYYxȏ? mggux<^AApFϛy)))ϺfFmof "H^J,̔^枝=w\ﰰyQ_D;w9sfiW B8~]~}ӦM Wf*DPh4Rp1L!L&L W|{3DX"7sHHtμyONxb,Z())ĉiii(}@$x|\\\<==7o,wupp$i`G2"l!C"a(1u)Rn5041EE" a}/ЛחL&H$EmڴiKk׮MKK[l?I$DhwwwNN! Aulg(7of7:hk CP J@/< or f͚8 ̨Tjff>-/h2֮][^^䤯ohh/9㛛g̘1P MBtð *W #Hdnd #;/73;!mIȠL<AWWWz F.\!E;wqc0UUU\.mvF^V(2LjWnii ]x!;JQ4ћ<4o* "ux9AG^`0@ Uu;D@݁@ Q5Pw @T johUIA΂@ Q5Pw @T j8h0CAB^p΂@ Q5a_;Tw<<<^K: ȻlgA Uu;D@݁@ Q5o7@T @ o;D@݁@ Q5Pw @T'@ ڃ莶P(T}R h# t*ߡR<EQgB D" |>J ;>Ad Hzzzo:!r!qD#YD@݁@ `$ĒIENDB`workbench-1.1.1/src/Resources/HelpFiles/Features_Toolbox/Labels/000077500000000000000000000000001255417355300246105ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Features_Toolbox/Labels/Labels.html000066400000000000000000000046761255417355300267150ustar00rootroot00000000000000 Labels Labels
The Labels Tab in the Features Toolbox contains options for display and selection of loaded labels (named colored regions) on brain surfaces and in volumes.  When labels are displayed, labelled regions appear painted on the brain surface/volume. 
  • Group: identifies the Label group for the Active Tab. Tabs assigned to the same Group will display the same labels with the same attributes.
  • Label Attributes contains options for viewing labels.
    • Draw As sets the style of the label display (Filled, Filled and Outline Color, Outline Color, Outline Label Color). Outline Label Color will display an outline for each label using the same color as the Filled (label) color.
    • Outline Color sets the color of the label outline, if Filled and Outline Color or Outline Color are selected as the Draw As style.




  • Labels Selection controls group and individual display selection for labels. If a higher level group is selected on or off, all labels below that level will be similarly selected on/off. The All On/Off buttons allow for quick toggling of all labels on or off.


 
workbench-1.1.1/src/Resources/HelpFiles/Features_Toolbox/Labels/Labels_Attributes.png000066400000000000000000000574451255417355300307450ustar00rootroot00000000000000PNG  IHDR66; ciCCPICC ProfileHPY_Das@ 20q`Beqր (5 X wEA9&Tnz_ wrDXTa8ۍ=<k7M wwшnqV4.Pq4n*g\8aЗ.B0M,1Op|HgdrH̼B@9DaƩfa?SϸeVxA(_nVjJrH)3FQoE)A ~0,dq ^' D>i }8A,oφF,-cOxY^+y@%0HA "=~M˻et l!Ԙaina?f;{ :]Y @ <"f3<@ЀPZ@ {i + (pA"HbցM `/(ap @38 .: #A8Q!%Hҁ K 9C? EAB:h TCeP%T BP?tƠ'a f0f~p(ë8Uq _o<(@(wT *6 P%*T= ՍQX4@>04].CC7;7C W 18`ؘH ǔ`1.mcvXl6 [=mcI39q\:.ww7}xK>/oƗkg) A@$ل#6 a(K#:CIMRb=D"iIKIR.tt4DH#1 vQr; BѥR)%c)['Q\Ij@4AZG%B:GD q GfLL̠̤,UB6P6UHVNNWS''wX0EբS-#. KӣiIB Z/mB^NZ>\~|9y Eץ)S;O ,xm  )*+(6(VPTJVڥԬHlT9Kr""EEN-U9ң2*RݯzIu\檖Gژ:UY]GsSza54iZ Z{:&յhӮӾCa$yUYwTOQWPZJրilp61L4,7a c퍅Uƃ&dIIɐ)tiK3mh]ff_mS̏?lfВkYnyˊbeѪꕵuA6T%6[m:lيmmb*4fycfG[tS88&;:.[a'M'SęEÅRU˕ZeJbgt3w5wwp_( ,|^3Ves5 _;~d2'b%%< 4@vGAzA~Y]|`u!Ԑ!!BBw> Q!4\y=J9J\lﲑ;Y~uVJ< Tq&qq\w> +oo,)8YSBq(߉?X8.p ^%$Jz|4y:%"!*& ;WZ_d$IV;޻zB'NҖӐ`ӓ]Psfy懬kddfo~ZZڎu6ZZ_cƼ#޹67%ouoDliS||qVǭG/wն۾ ~.]'laNN;\v+-)޽dwƞ=o{ĺ>⾌}RҖw\XvܭBb[]R=TxӏVzW6UVrXHV.n|槿+(B3y pHYs   IDATxw|El%;B C Hh]PADA)"HSU EDIA 1@B*ݙI$Z.W?|6g̞wwg΢e˖`0w Q`w QL  !y`0UQy͛gϞrJRRiӦAq2 F𑑑767ٳgÆ ϟ/**8!ڵk׮@zq2 ƃ(ߪ_6o NNN( $M6Bz]i]S`0>JHHطo }tVZ{nFo߾#TQYYY;)/\kggקOش%K)S<<<!?snn Ud*Kucǎ]v5=tݻTB2jԨ>ϠArrr0wGyxxlڴ/]t^z]xq...666|8ر/w@nnn˖-g++Gecӧoܸѷo޽{>h!(fĉwusssss{ĉ5 ݛA*`*;gΜ88`UQBb~iQܹ3p޽{/[L$B7|3p?}}7|_Ҳcǎp!d̙k׮Zt)!/޽{tt+W5kw~رv={viIIIԸzN:uw-)))Wn;99) GGJ+ٳgȑ# VzɸUq<بjsIsppy}zٲePLfffII 8++?=<<ݻw_vO?tK.=zcBLfΜ矷lrժUK.SRR;;;SڵkUE)..;wK+{{{qJz?ϛ7oذaٕ8'U,;88̜947jѢE .{xxT<馲1gΜCɲ[[[WWW8N:tܹs'N }K8KKKS 5 Jۯx޽EB.\jJehhhttߢEIIŮ\|-!ٳN۳gOvԩzڱcHт e˖7nlҤIRRRJ ł |}*gϞoVU(GM*(ҍb{#ouVJ }*__{o>Q@D)J}}}+Dʾz^EkkkZh4V]cu'SNݹs}ǎǏ/שSe˖|ɖ-[ÿѣG'ݻw߰aCDDDǎ6lX. :u:thn7nWTT4zhV;{[VqΠA,YB qvv:ujCgggGDDؾ}~UZ֭[ ETT??7o޳;/hٲeݺu378qԩS4,˲Ү]*{ ֢(?mll8sttBV $IEEEtꇄ1.((0z`0TJO lٲ.]&''߸q֭[k]@F <)`0R"0w{ Q)lib4/]jB;Q{L>[َaABBAq[ٞli`4Lr&JR)Ŗ[OԚoIL_ 0B((ܺ܏.y-T+֪ ϟ4hqbR*[jSHշs4lFER ,f߻[CպT=2d:9:HXIvu"!@0dB Fҵ_0طg:,JQeuOpILCO(<cz'@Fd,!q_RcBdS'!@0&2KH)rm ð$JJJ3MѲ[@pڵ#|}}۴iR$f)4MPˮvM81%%J=c Nl FV Φo۶ۻ}O<Ydee,Zjڴil5 t18~g:u‡2idа1oW5?lzC!&z1^IIIUe{СC%w7-իׅ RRR6nI*\OձyRjLSۧN2>000==2j̺^"8V waT@RYqW...| oٲezz#>AxꈈsKF5jd^óU?x޽l^rpp?YʈQ^-TBs4 |LU[) cbA"ZNYeve,JE666^^^$ɒt̙=zx{{hbŊ(IRvvv-<ثWK9tP??:={Th\xqƍ͛G۶n4hw}׼y8Im۶>>>*G7h0TǍ~4>3ڵ;v:eC+W\vm||ӧO^zŊ'Nk׮ر㯿Zv+WdIݻwW''c\'޽cիWo9ٴԽ{&LаaCOO:HeTBBBu5`kĀK XkZcL@¤wq+&9߻1}\:OKɘ% 8WvR%yÊú6fk?U4Jb0Z]?5k֬6###**j֭;{ԩS Ő!CӧkqB+Œ3spiӦ͛7wa} >s(_}=w#GӂDhe.dNNN dYzZf"##^z#G"nܸ_c{n???{{ⴴ4P.]eeNט.~U3s"1_~r`]w7^#6V׽+u%DEu|eyY)wUNe+"&zxUmҘ222ܹcJ3awT:VhnJo7nl߾=]QFǎS/_Nj$ڵ+66)=wi[:w\z5k n޼y޽u^~ݴSJJ =- t*6qbbbӦM#q鴴4ZlYrrѣG׭[Gm111,?~|Μ9m&!À SgXAp>>_:phep9~|e?dpu$\LsR}ڵkϟ_/x(!+++ӂy&)HtJV主Ƽ|}yPijo7'1j Ĉ1P jc lll= E׫jp0?cZU@߱cg@@@߾}?sPw||#Gbcc+Pno^zwܡDw5?ӦMBHnn *V xbX$դs222 B~JE5iɓ 4tj\bIׯyӴ'| ٺuk-dY޲eKhh?B(<<|ݺu!!!XǯRP+D,*683w6hۯ(71PНg^=wShJ%j̶3:U/RTcXG!BjO5kӴU'WOCjHJG `LxY&(E^P)jumWT*j^ZhrRF\|RlԨъ+M6wܜѣGO0ԛ0VBfΜ9v;v4hW_]p!=VtttNNΐ!CzQfϞ]u֭c%Xg̘kbfd1vNT֖gCVFQHD#w旅M4i׮-h:taa᯿JKxmI ̺oCYfܸq͛7eQFWr*R*Hs eY86/ ic-:yh?ok>|uҢ<,YHnutt"WStr_gɠ7 "R)El+[=S*E@`LtD92`ZвeڴiQ&<^!E_ͯbJT$IdYȠwK3Wƍ\5*9gKXYYUVy"""hZ.=Xi]{P!“2AAyه&A5DҪK*Vm̅S+XlŦLmƣRjoD1$$"mMbRȨ/6jt[󣳫 & 2ı}j.3bX5ؿ{Ȑ! 4s֭[-ZdVKAez3,{l__߀N;"&C=33s޽_Zԯ5b @@hp헟KO[S~„Ș%b ZKJJ{A j޼yIIIvqq1TRB$I y "$777&&&!!?O n߾i&:c/W\%̜9ԯ !D^8y`68WQ-u!e@Ĉ%x Iy3j2*??Æ oNAYfCݱcGV6m{ddXXիݹs',,,77;v~K+ ;yRE0< Y1Ʋ,KXXϝ;Ν;5kkܹ'Nh׮.E]oܴ100pӦM$=6H I Q&&|tơm~۩3F1ĀĀ5z6ǰLjҪJLLlٲҽgΜQTk׮KOO8q-[Μ93i$aÆDYi<$)11?~wj̙3?K>(9jK$Iۦ06lذh"kkV[s;v 8poСC/^8wܛ7o֩S^F%''9~rtt &knȑ}O/_~%KxzzdY^|/o߾Ν;rձQF8q!@\D=(6 LdL& D&P+iU1,\@^z^E+WTTn:;#B^uvaÆU}O?488,YR[Z IDATD$V 1nڬ`:*B!7wwwSSY@q pP:l ȢOՀ,,BEKVVjՏQjT=(ʲ|m//gS@S`pps;k!ส?3X" n" cbR|Ϟ=WZ߫Wz暾8`4V_q)9}qƱ`e!//;w~gQ-877h///cX"-Uq\@@I222%jq\?)quEBxB0*b>D^xa,e0*a0b0*a0b0*a0b0*a0b0 񨔾KdWz &`> ɉzŋ۷oRcVfjIMM{{{@dᶶUzeV_.Ryw)BZ† 0#;qX^A]F>K_l=0i7:@ c#\ާb)j?mA>5q:nڿʊ?ԣ+L8|ppp?kPI1 TTSDyS B.EZIcZֈΈEH9uΈFgckCnL?֨"Cu"CO?4ydϙixׯ,1#Ԕ}^NJNG;paCuF~Eف"Cأ3{>4<%[;uswcE=[Lzhԧŀ%I%IEBo1boV9BK& i@KIF_ݪU+wwM:yrGݸqc-~-Z$$$JΜ9ӣGoo-ZXh4JD}Ξ=;bĈ1cU)dcYeQyS!A%rYUIo߼.Pc7Sw D{:}4ʮ =3뙒$5c{B$EWbAaS[ TY[ʛ# Iw8 nvrrҟiz.'9u9]3MvVϔ.Q`բY:2pւ{wo8,wsZqĉoN|ĉɉcnj:}?8}hY9l޼yƌ lll?;uT[[ۢBV5~u֝={vԩ bȐ!EʼnǏԪ_17?HJ| D-/*]NPd$'^:VuD~vgVYyYp/<(^/BL @-a3fXfÇ#""Ο?Ǐ֭[FFFhhhBBBVVy9lذ!44tܸq666 BR9;;cɓ#"":w~zYf 4qM4rJŰ !@GI6*9 @BnUZٜ۳ :,ӐIqJ)(DN@VI!G*%/+9R) lTfd9EBԧ@<_ OVEY.u=zKǴ8hbWgߙjWvM4iذagjj*XТEuDqrHII9rd"/q)))76WHHȉ'.  7KtrB!&UcHRxx8!B cލmz Sfuco!GjF@!pOY@% DS+rrSnXǵ.BG] }bg5j\\3{wǨqJyO?mѢӦMܹ}yJ.^X!5y}QwssL>3erVgj S*8W)RP)x+Rjxd IĐ#cQfr '(? Qmmze޾RjRVBϙvjR􋙾cŇ SDRBT**HtEyӣצ->#&%\>FWN= !R QJ|cjƀƳ79Ikj"}ٵǐqjhYi=,*ȗQvo-w6lX ^z饸* UVT֭[iӦu׮]yaժUjuaaa&7xcɍ5 t#FHKKkѢ;5ZrYBBBv9aɇ̃vq*Fm-[M6<mڴN m] r+3MD2%^qu%U":8:֩MCcvPt%:G)Cc$߽j+euN8))ʪnݺvi4FZqU!774QZ1EIOOwqqȲ,rzzc5k`0tƠcbRU sqLOVc귍?ZV Z;VoKu±l<-66@l>tukT6AAAljӳ^}˯Ȳ\RRRu 3@V .s|;[R /8: !embg^g̛T0Ɨ.]0`o# xYY5kϿ/js, @xN =0yGW>zEDoޠ},X`ҥ666ׯ_v?ФI3f4o|̘1{0`{| ={oYjy/^/\bcc3zh(..5k։'F|0h XlYVVּyͧ.Oˊҭ[>CL+K*@X?vG6H#! 辝CRclܪ# X9Y?T=K1n׮i֭[sΝ1cFfm޼yAAASNWXgϞ]vܹI&jaԨQm۶ݿ)S RlْviVk4gKJJG.i%$m.$IZ YPPзot::h4BZ-̘1ԩSuY`Avlٲ 6kBz=s%0QF8q!@\T  H2 )yZ,yUO;;;YM=>Ctwt#,,쫯.]ItЌ'NDFF}F5M|||pp(3g:vhlժSU#Bc"IU+T@sBjut:[JJ4Jh4o4 ?O?͞=_ }}\\g}ֺu͛7B$I@Joq?G{NWm̕A'+`2&Z *Ӽ`]0ԭ[״~[|9M&r֭BΝ۷o_DDDRRұc\\\|ݏ?ƍަF9z谰O?|ʪ-)T@ 8{lZjxyy-Y,)sMJ>yʕ+3227o>gΜ:ulٲcǎg 66ve0+#?-I0 B@^TRjuВ$ر#""۷o;vÆ ;v,Y txMR^~57.88رc%%%գFQO=ywygOVs`FЯ_{Lg.#W*UԞVФ$WWW^yżwY/6Y6yU"y" e@PIDATd:c1!Y -ZNEwމ' [n~az\]]ݻm۶;wV޽{FZիW}||\]];uo>Ꙕׯt>C@xmU&&}1m C+V`0hZw~AD̙3 !|g`WJDN-jVj }q(OW?vJRjR*8)EN>=]Ω5ks׿jtuuMII1lذA1b,[[[Brrr\\\>Yf绸|ݻwt͛7ӂ@M2󵫕RJF!ڻbA=@`LBP-RвeڴiѦM%s/zRͪR<]є333]\\oj\??UҳB8$U@,|;K; @WɘH2鍹KXYa*BT*Cr*ʼHZ?xJ66"A`D12/\^Au1#XT85ZBHV_~=((HPj:ƿ'bo P:ފ\:H` y@w׳N\FEIN1L5pT9t'!12a}U1e!-&Uwv!\A08F|~ߞ[bbbxK[TrBFڽ{~Z0pWnQFyzzZ\T~8333*.eU(S"@) ///OO R1b0,Rc06`0,HK0gLMuC 3u }q)]xLye0*5RDrfյϨ*i eYz:m۶SN۷o۷cFUXX/ܼyԾ}9>|c/@ttt42e~5eI3?ȟکW~f "=KI"09YoEl͏ήPq\K8ֵO#c&UEAASSShccbŊUV}7UJOOeۻBX4(hEbկ[ke0Edmաnk[q֪;0̰(Viu(2: #.3N "ŠD{ 1Fe|?#&wf͝?ӧOO<966VQª90l6DTEUUfdPUeWW,˳fz]Ws}q /X ++k)>qtrIbO=?ws*rD4Żvݻw \^ym۶zcx@@@mmf۴iSllyQUUq}~U%*T`vv7ƍuVp9"h/{=sQv8)>NH\4cT{t~\18 UL6*=)i:t(..U'>>oiiiRqWIIIZZ9r$22ou?k"##/\eee)))O^ty󊊊DDə;wY<򮮮-[J/_YTTtg C/X <<mEQc!2 0Dlh/..HJJZᰋk{۷o6%ssss燷oߞzk=WM&?.._69lɒgA#。gg0mo7a1fGA6hd'@mm{駟p=zH:zQܼvǣ&LpK.=c 'x6+V?+2}K.6Okgzŋ˗/5%Kc_=>ٳg[1f2V<#-[ >#$$I*`᭱-Ju""Nu>^>/:ɓ'[,dWرc&i̙yҐ3gQqNOb L$ 9"2dmvʊlݺ'N?pӦMfv{IIzI{3gΈ).oٳgE{{{ĉ˖-xxBBɓ'[ZZ8%%%K.Yg(:Օ]%]+$$^t fɲ|ō7fdd|b B:ٞW}rwʯ}:D xQ5^WzI/K:>+V0G%Iҕ+W222 ^z^{mڴi .LOOUVξghk֬zfn_zmkw}L&(bԩSsss{+WYW_x .\h6UU EeYQ\@,;|MMMTT,ˢ3fZkjjz{{eYm***-[t:E۹sgOOOrrѣGnj#N .q=/G<.@ @I\j) ݰX4HXe2 uU'GD 7GVD8qtc]tvvv3FӨiX{/x^|900pJGvjBp???N'/И͛77ԩSzqٜsΨ}qwڕo4 //gIHH?~FFGuήIgjC[|}щi^z9G'綛^{]݅U&TLDjmm:uC=N{ƭVl$}}}E\dO5^%1p*pv}k ox421^rtMK;~\tP^#7p)?~fzW+>Ž*Q2JLc~~]iED"S9Rq>3h ]y/ƘhLFuo=0 *1="砪:Xߌ4wAʫt:By(H sO@Bo/]?5^~yAʫɲhѢg}G!Bp ?8΋Mu*:iiiCLy-z{5XpppJJJEE޽{9#}FD$IX,)))*'IRhhƍ;;;^1+Drfci }8A,oφF,-cOxY^+y@%0HA "=~M˻et l!Ԙaina?f;{ :]Y @ <"f3<@ЀPZ@ {i + (pA"HbցM `/(ap @38 .: #A8Q!%Hҁ K 9C? EAB:h TCeP%T BP?tƠ'a f0f~p(ë8Uq _o<(@(wT *6 P%*T= ՍQX4@>04].CC7;7C W 18`ؘH ǔ`1.mcvXl6 [=mcI39q\:.ww7}xK>/oƗkg) A@$ل#6 a(K#:CIMRb=D"iIKIR.tt4DH#1 vQr; BѥR)%c)['Q\Ij@4AZG%B:GD q GfLL̠̤,UB6P6UHVNNWS''wX0EբS-#. KӣiIB Z/mB^NZ>\~|9y Eץ)S;O ,xm  )*+(6(VPTJVڥԬHlT9Kr""EEN-U9ң2*RݯzIu\檖Gژ:UY]GsSza54iZ Z{:&յhӮӾCa$yUYwTOQWPZJրilp61L4,7a c퍅Uƃ&dIIɐ)tiK3mh]ff_mS̏?lfВkYnyˊbeѪꕵuA6T%6[m:lيmmb*4fycfG[tS88&;:.[a'M'SęEÅRU˕ZeJbgt3w5wwp_( ,|^3Ves5 _;~d2'b%%< 4@vGAzA~Y]|`u!Ԑ!!BBw> Q!4\y=J9J\lﲑ;Y~uVJ< Tq&qq\w> +oo,)8YSBq(߉?X8.p ^%$Jz|4y:%"!*& ;WZ_d$IV;޻zB'NҖӐ`ӓ]Psfy懬kddfo~ZZڎu6ZZ_cƼ#޹67%ouoDliS||qVǭG/wն۾ ~.]'laNN;\v+-)޽dwƞ=o{ĺ>⾌}RҖw\XvܭBb[]R=TxӏVzW6UVrXHV.n|槿+(B3y pHYs   IDATxy|<˙ $DE[Y7h--RՖҺju}#g!sI+10xYf=MYZbbb^;֭[tBP׮]+uF%_{S:u]U0qrC-\ٴideeEFF_666o}cܾ}VZ$qҥZjժոqt:]RdYP899Z~mDsɒ%ObR/P/aeYh4j2:::,1fY|QEelN_r% ϖZ0޽{̛7oȐ!ڵ6lؙ3g"##[j5`h֬СC=<ٳ?,%+믿f̘[o믛7ojOB%Irtt4iymٳg͚#˲w7lL:u߾}$=,U]ٹ0a߾}vvv'O9rHFn޼yYaRSSȷ9;xzzʲsNٳ1ƧOJQF'OxTH5G .,1֭[ ۛ7o111[lٳ' BXfMXX۷KVP(ݻ?W*2xhzF&)<<͛ UVٳG5j\˖97L*KUWo&t-[tttС$IoРAlmmgOP,λB$>>>dcǎ$}ҥ1c>^zk8NT*6q$  Iy`0Oyo׮]vzuڵkyI8sttoG<իwŜ7feeݻy<|7xcϞ=ɩ^z%oeo޼i>7o@rrrejɲ_ Ç')ݻrʕ+Z FQe{{tR͛7z+و}Ȳ|=zkU^jDDDVVVrr[hRڷozBqvڑ.*2Pj%,2d$I| T*UT*DGGܹ]v>>dB( iӦj5 (Dʓ=:99J=i,2S0#= VkԬA~^lƍ~~~-Zz#Rok{zz9q+N R R@_2c8/7&e!ƼݥQT!<ۚ<<(co.{-0(ly7E}-wQ1ggώ9}trr5k:tK˓bJ=i,X)xcǎ!$$޽{/_6^u'mܹsz}dd$yVjڹsFGG?QːZEsG׬CGGd ʃ0.el1 FGEEk*`Æ sQ%T*rU0 Ôzգ/]v]`ALL챲Olh'Zn׮eJݺu֭kYó</7/,˖G/^,g)AƂgϤP2G$e1"1RYy) PTm)y0J`2$$\=a͛GG+BRk@ɶFDQm۶~~~-AŌ'&&wޢ(޿_~-[K|ԹQVa Onٲ!r׮]֭[ZO>$)SȠvjjVZ }􉋋#y֬YӢE rSںu:t,qHz(СC+ Fu%o,Rf@TTԵk5NH ]NPɍ p2?tP%ՒM``''$$h(*B !˗ 0dȐ={۷/99944G!ٸ{.ԯ_<$Ւ䂂WrssI󳞞˗/0a²efΜٱc1Xڜhn$BeYmccV333 A&EL&iR4&&&&СCAAAVo|ݧLbNIII 5jDB͛[L˹;Q)CR>tRϓDx|ϟ3{ /6굢WveE4[w"TJ ;GߐжG(< eC *,cNb0$mwU~!PTZ`AӦM6Y~8߻wڙS| a,`2e鏜33O?IRSS/^HWKTV?I,(Ű,KJ}S*(Y;UVGoBQU L&S(z1 @C*EQАJQe\?7n|^M(zAۗl(L:u6mK0Ƣ(y"@gee 2$11o߾k׾yڵk5M˖--1/V/Zͼo'Md"$02Ƀ—Nݻm#g#E%t,ʈUs(Zv{)KYzG}dOy~mٲqk׮SNu!(((**jҥ$޺u+***++>|+HQQQGѣG```nݒh ˲&I$Y%IeI%YO~֭}gδӏ9ҼysDWFh\vMHHڵkEQJFQ6 $ aQ&A:kkhfCL bM2L(gj%NSTeH/5))IRU*ղeݻ׽{ѣG_3fFy뭷AHJJ$JNNQ⋱cǪI&}7TcXeF׉HXzٳmm5zXl֭[lٳܹs~9sf׮]sss{wbccB^ IDATwy'''5jT~.]̙3;w\ ͛SmdYSNvjݺ5<<6Z,yuҊ!l$c,c%,a2GץRTe,##H13<ϓfkkxbJ˗/G;㸘_}ٲeoVٻꫯ4h+8!$XEU @8!!AzL&P3"c'OT(}]`@ݻw7lذ?/;7~xJջWoN{ AolCrrrFF` 7f͚*iϟ?Y1`YW^u4hv-b1/Oz{P #࿼!d &,a`U)zr9wS󽽽:D:88xJիWx@xxeGQ*dvکTN3%]T )898 `eD [[yիWn[`̘1m۶mժڵk-&+^ҧOަUW^ye(޽{ׯ`x뭷ٛeٞb˙0Hƀ`0 @ T?a@2F@DUxrU5W$;;;o陘hzYr/IRNNNů^|A[KBj6@Jj9]֩[رc$ ɟ`Ν]vU*d/đ#GFԩSccccccy &\~ٳ4y$JxFͳjVjЉƂlC~!?I*JIUpJQrgx:OQeي0` !C:teݻw޽{ I&*aÆYy2N BArysذa666ӦM5kV@@@JJ $.B(44tѢEǏ1cFffG󼃃ѣnjR&Lp-xMfgg͜9$Bܵkܞ&M_YfZ-z\Z+y-R7vu!u5EX1XjR) DEE,{M>eddԮ]رck.;'8+++11f͚vvv$RSSBBBH/vI9,˖qߜG9FFFpp' H/b[+}޿leRA~6j1`c1d,J(dϞeO3*mEQOĉ$tܙ*vW[[qƹ3$I(* Rì1.N6,CBBJ dAJrrrrtt,YϣJaX !4ۙP` # e1d$c\F )(i *7V'NX!k׮( ZmMle>KΎ ֨:8`,jT$$P,~ƦGja*R$ݼyS Clwxz-b0LY'52d%xE$^ʲl,YSfM5՘ pP(;wOޑq#(!u֯Zݢ*aOOX___zOQ0Lpp1crrr^0La'''iߟa BaBMݬo߷BʧzPKɴzjA,klaQ !Z\  2 H6;]ОFU*T8ҫW/apQxZlULdK<3(10`I߈'\"r`HeŋdH֋y01`$M~[6Q'EQe`H$eY'''Iȇ c SAyX: غ# ,c,,T4pc%vt0HLQT*Rz=˲Lib,';:uFR̕Ǧ{gÆ 322NթSX1 bA*'/E Ibܑ}'q oҨiG}/iSSu衱w()mc,` d^MFQU_{jZeFbE디PRRRdY˗/_h4Eoذq 4 # XXƸD(5rGS!Il=Z g5~T)IX~hzZ$KPխZ4~ ['\Ņ߄q (c1*^$(*>=R$I" Ôz(k֬Qի'L`>yOBBBfΜ YfOƍK/cs. ;"AƂERR8qhφל 1bܗsZ'$1JIP8A,Xd3r rJ2L$ ;DQ\xqJJʞ={$QDqܹ\fMDDz7`!3##""ݻ(#_x1ܹӵkW__Ν;ߺugud{͚5+Kii hܸH{;oI%d$Y/:3zA Ȍ0>W1oF Ut& $Ϲ'oL^-74'_3ɺkMRAKF5U^OK5 eL\,_O>u _bE`Ojש9{ _P4y &4h@߹{W ={G}VL2gΜ nݺAFxܹH6  SLҥի h w3ڈAHqyOs葘SmHurx77sܘE{9QėR hgN,;}no gǃX8>fMbhB;v+6SSo_R3SڢǨ^[}ҿk0ӨhVA.~/-[[U2D)ȱ*Kz^*z&rvޝ BR( GGGppppqq12e믿Vڙ3giW^4hhaݹsïzZZZFe}s1 cFƌR<ɫN~t[d@Y&=TI܌>Iʐ1Cfdd!r2ec̜ܾҷvdny֙SٱWب^1Cj97@[cεv+I ݐ#WH 2f$0f$IDd 2F ˰JQTT2÷ شie@Պo{Yޒ=2w~BYB k׾w^zzed;%%-ZDFDDܾ}c…cǎ1bĸqJc]`TtZu~{~.+nYuv8!P)9]Vj w\=sғ_4X|*5A,TJ$JRlԅ_эsT MQ(,p$eij2KR)*i!T!$I[F 6P%5_a,,ky+\D=<Ù3gnݚhNSiii ̘11bǃ?ܫ˲K,QTM4iڴi&MY裏BCCl׵A4ifw>Qx<*ϲXQ) EU}HEQZ`AӦMY=|ӦMS,;;;77ۛ8'&5--ݝ7 :ٹlgggPPPj$IeZVuwVR`V2$LYNKT8ycZGGc`YJLyޖ:z2 S*y?ŝj! ,(bɔ?r^vNNN|q(rĉpIRSS/^>Ngy^|||۶-BeGEdeJ jre]ԨRj45+O)*4ǵpK2Eq|}(* Tjܹsu8|n^^Dc\{aS\L@A@`U6gRWHQ zsg07onY`gW=<ȉ? Xs]GEQR ˲m۶]t :vr}0S:@0 )ڵ#Çq!u/neBU}dwllK2CQO]Ka>c+(~RE=4REYMOnݺEREHH~bbFyNQEk"!5%%ёܲJ (zX!"lmm-/E(*RJ%EQ*2J>E)EQT1tr(jhH(R)T(aw( o /ͭ)*Bh#ğ(: % EQ֡PҐJQe%6jR)ETEQVDC*EQАJQe:"!^@QUhHHPE$T,REQ%L&R)ck4REY EQVCC*EQАJQe5jI+W\t֭[,˲L"pKNJ-}l!bvvv~~~aaau8xzU=p/_|yuփU(/A .]t;vt),,i>V?H ^/IҮ].\GUKRlժU˖-ܹ_߾};&&e OX( Mݻw'&&N8U$r0`{zz7g} dϡ h~JK.]rW^FAYV~_~۷o{zz*JD9;;W!PTTBӮ];777٠AתUr8ТE WWW|NPC %ϟ߷oݻwk׮ݩS'__!1-,vvv͛7?zh5lll*񙙙˗[l٬Y3S'O|w9ۻwo߾}%IZn]DDDeEUKULmǏ_reeH+Wd$I.cm۶)))5j8vXTTz*,LdIeABnnnW^5OWF?cǎ^z}&Z}'Nػwo\\/^xʕFU!PTuUz(nذ!88xƍ|IׯT*PeB,<ˎ=:mڴ?n}U+6lW_}5lذ3gθ ޠBbEQ4 ??7nԭ[WVWF ;3|\߾}7o٫W/pر#F@400PEA!kEUWUw^Y.\z3_pe˖zVq˲ :V:ViuzN7o{&NҵK^^ޒ%Kt:ݚuk{?nݺ^zNtZNjjz^$˲ǹz^_I_a}rrr7_~o}zt#i֬Y͋\;@QUꥮ]o߾ 6lذ۴iSFfOOϑ#GVx2d2988,0 ˲^EWjՊA} ]vaSq}3mڴׯO<_~p! 0d," 8NјLJj|µF8e[nh" 8p`߾}`ٲewްaZ&~U]Ure/n֬9k3߲EUWUb,Fqʔ)<ϓ tɓ!!!ϏW*;R=JM8_/fΜIڷo_sY}=ys!$I(ZVT> boժUϟߢE oOx 6 ]nݣ\|gϞ$UlJ+33qEUϟ$)55ŋ/黟eYww۷OR [)`I弙4^PDFF>e㉧9ĉaÆ p@ĭ[&&&/Ο?ԨQSN]f I/((XZ~;w`(VIBB’%K:T,=++k֭?eb_GU>O>_t:]g;cǎ d2o߾y 4zjy'==dҥK-[_zlA-BzUzzB|r9_|y%)RG+OYY9S+k9@#YXq79@PZH}"ɷo6?\fMaEW\8p,~) >}z&MYj?YYY#FضmۡC~ׯ_?..\I߾}{1v .XnӦMaaacǎ]n]ZjO>rH;vdeeY>{yU`v޽k^7S6ܼyd7nجY0Q+ОK,)M=Y׭[W}-$ISs{b;ܾޠu:+:5f͚| y~J݀ƍwvڑ76m4 `޽;vhoyQQQ7x7x#))VXVŠk׮}6mھ}*RMHH1cFbbb@@|ᇖ [o5nܸGWlISLLHH{>>(+++==߅joo/^s+VY[[oR|r۶md2 _-ϓi߻w/''ɓ^Pڵȑ#;vغuk[[\^UUF`)222vvv8V&dHKϼ7(*:P"IR˗/߰aELLLSS۟?{G@II)%%ZUUN=zXUUU__2;;ۻ˗ vͽ%%%YYYyyyؑa_N;vd___OOVVV'O~1@ZZ履~700P OOOUU՟~ 㣪 hllTWWg_O*M*"K.988TTT۷/<<. x񢊊 V,kժUX_EEE**//I`nn~޽ϟs-͛zzzcǎ]bB<...KK[n +W,Y5ϊ iӦ)((VF^VI (;b ER$0{I0T(&rU;;;x555۷nx[)&&&$$$22ήB'UUUVVV4X8y򤑑ѕ+W,%%bt:6Rf`` Ϛ5kG!_444dee-[6a:رcÇbcc_xtRl[n۷H$;ߤu͛7+++_xiee%`ɂ wl;w677 .\CCzDBa$'QYYY^J8q" eՔɪ J_. o6i :0>;|{}ڵk͛>`K#v ~ Ƙ1cJKKPqq&ޕk׮]x+ <==B[[_~g~{ꕏ}8755}Jcbb|}}}}}1Ǻu,XkuߤX@HLL8q;'6رc)((;w.==Ic?={dddPԕ+W^X'' ݲeFQ`QQѣG=|ǎ3f Dz9eeecƌMd2Gpq"G,,/5kΝ;uttcqSSL&EEߺ:MMM;::G_o/޽~bjhh⃞_3f #yrrr_N `(>Ҥ`2MMM܀޽aFfflآ"{ƟD"-Y(%%%nTTT=o$B/S] qR=Mݻws<{>#***3"My3긑G,M*))AO H"tuuR+`=!'Nܾ}X$HZZZ8b&((諯b_"`h7e:a*88xh$>?qP"`̑  '@ x$/@Z$:L oKy|vTB gR%[dTɸ+LѐȐbDUsB߷1eI"@$,VYI۷Xlݦ#E`X Рodd 3 )))-RSSvvvtt̂Pŝ+===0PVw5yI& β&wҞ?.{Ϟ=_sӂ>>EEE^YY  8,bm.&L())Pv?~,8}ֹ/^ljjd2ٕV'Nطo=vXcǎaF//}MMMV7n Y|U G{X˖-{ݽ{޽[SSD߹s'55ܼ.\a,yfoo簾ëQ^ oVKK U7 0&OQMM=ztȐI2.5s۷o?͛ׯ_\2.?NVZab-//[/ؗ:thɒ%EUwwnݺF8|`0ڰ;T*UZZ777?|… srr؝Z+++s8fϞ+쪫VN{xx hΝnnn[nІ'ϳ>}://OJJ]iUSSs>ܸqC[[cHHHؿÇ߼yV__}egΜa0 044p޽ czz:ɃbVrM͛7o֭D"1..6**JYYFdd\AAA j(2.%9Lmb`dd-inn߿ TTTf͚5c !mmm III'O&VL,?~Bts,--{{{+yalK.M8~P@GGGSS?k<:!<&;;;?~ <==}}}d2दr}ft򒑑R H~mρ#{5`*9i)U=},~ojN0q<|QUUeffԄˉD"k! eh䐐##ݻw ΄T]]ֆ]3-[达>:::셇rC׮].])))4 +;;)P}#Ge<v8VhEEE7oJΛ7o̙EEE0 .nkkhoo urr&?~|2""ܹslݺɩظ )))sssס [II)11L&ߺu\'';w@[8$##)CCCuU Ο?ׂp u;w̘1XW}``aѣG_P(gJ%K'vo}9]VVVfbbbccҒ8uTѷA (377d:::?6ѻw AMD<)ZzRSSa^>%㪯hrrrX_I\2.V^^geEEsΥ@7d```\pAWWqYx]|p0f̘˗/ž>7>>>Ubbb<(LKK Yӓ vp󬓕`fҥYYY^8|GG||$0445k֩SOD2͛7ST###c/ {uV^ oىK%.6o|Ik8::[.,, TVVH$cc㦦8jaeeejjjaaQZZ=z)S"##Ϝ9ceeoGiiiSL1448w\^^k ~&ɶzzz؄*<<¶m۶oߎŰqFOOOKKqmݺ… p$eqNS()SP(77v:.?@8vشiƎ+))0[Q[[6a555H$aW& a0JJJ0h&"2^q_W0 uuuZD\2.'ÇN8s]zJGGG(W&ʴWVVg]WWWgg'}COOx8ɹ)Y|H$mlbkk`u0T]].YEpXU~2(Nx9h]CCh&"2.qaq˗/L^% .v%++;())q.zY'///)[ndeeyds0~}~Y6mw<(P^^^oO 0yJ%`ٮFUeE_/ `xUgB˨Uae}v!2J&!wbtRSBdP/B "R{ @ ѐ\YdF{jм ePݍƍ DΨ:~D9C6滖O- -zߵ>x[/rSaqIIIϟ?5kRvtww߳gRSS  &&ap kΆDppRTT"--oʈp>rW]3:Q螂K.͚5kܸqB9s⯿z NN'fggZB@7phXKXD;mƽfff4C"y!6xaE{7 (xS@Ν̷UdMKsO̙sƍ/^|#?tR%yUSSGRxS8̙V]]mbbG&zݻO< 33c͚5ǏmG=M$#] R!=Ŏ|zl~'ccDǏBYd  r^?55r񕕕0|tg x)OOcۚ W`hh(huOA-ηo~}@MM WϏTYY)@L0 Gr~ >D0茧p)۷o 9 {h5kl۶իf+9W_q vxz6o\TT3/bAx( sry#8pP$J",X36YUA]U˥:&MwVWVPSVPSSSbFs>f̘R쇁&Lj4Aݻɓ'k׮CG__x¢ H9LJJ:u;OOO+W$&&r _i…ꥥ?3۷D"qKWp9sn/BCLb&EEEmP5_m@EEhN|E;u0LfIIɝ;w\\\?G򪭭]]] ^ ܹsZZZO2lٗ9 Ξ=~ )//y󦰅 #QFF&/JU'N''X\\-?vb8u |2sɒ%׿?^f~KKK 7n܉' #44#=]pp?{Џ?ygBBB?ߞ>}zժUmlll^^ ޣh`*@bbן_$ׯ_OKK>}9tuu )y!J:{_~3/ 瞞׮]{ѣG###%NɩNHHY%--'޽{%XĪ!B]]x!DGcǎǏ^z? E"C*dT-Zt=,NrrI|}}0‡uE?…_P(0JT}}3f sO)" r6oSPVhA?GD$,_<**j߾}BmURR}vݻ9>=s挟ߚ5k%Ν۵k]\\>lgg %C,|O>cٳg\!.>N[n(uF SX{ uJ[l]` D {j$SnV6maE"C jn  >!,WH=%|"مT b8@YBl3#$r^ $r,D"C*G3A vT菚j)= M" "`Jo߾e6%)gX{__… كQ___aaaNNڵkUTTURR[ZZ:{lWWWAӧOW\yLvqqV[9##Ç3gill,..8q _UUǏD*++۷o_yy`%L&3))$))iΝ:A ~19J$g̘#GGG<%ri,… #K`:@`iرc&MѴvɽrAAyh49xbSSS&ɯ[YYQ(%K477cӧO}OO?`oo)]OkjjnݺZȑ#:qqqL4IMMkcǎ8qb̙T*ݽ300gϞq>|~٤555&Lhjjtuu}MMM}||(J```oooXXJurrrUUU]]]1/q:y򤙙>eJjj7 .)zܹs)={M`2 w J}CCqq,4663f zBoB ^hΝnnn[nF:b $>>9yW86 +++۵k_ / ӧ򤤤x{///??/_Z=MMM,"K{5***ttt"""tuuǏ/SN:3888“`$&&Ι3nΜ9vAPSS~}}G)));ĉZZZX\;~ӧKKKwkfeen߾]^^~@bb,++ sNjjyAAׯC}W2 ,.qٳg###ܣ--->V~`~~~=ygϠ\JJ*/=>CF8u LbGN1B  `RSSþNOHHΆ7`0hѣG]6o<...KKKOacc!''##d*ʯ .H[*((8;;gزeٳgccc`c߿t钃CEEž}۬,?$$իܢE>|RRRLMMo߾3o޼[[YY:88`A?>>[VV6<<;-y^z5***77'77WSSnxq//I&]pڴi/^ju9s899czA88[[ۨ(eeeF&nPO***rrr455/_}DRTjuuuTTӧ1 ꯪjmmcvڴi#v?x[blllmݽm6%s _={߇ ^\ӧjf͚m۶]z^ڊ[sHUU͛KTVVZYY 5?_sܩ#q/[,::N'&&*++?~חi&%% $%%999߿O>m٧X`^8[kŊL&ƍO? KHH$_9f8qbmmm}}qBuu5 w¿}gÆ ؅DeeeUVVJR夥I/֕=ShٻzX}=}, ?xرo~ EEE ~[)**ع|w}Cv/1.\8ffRR' ӳp)pBҎYȃ{ IDAT =%8-Zh"mff055e/Jܼ?!!!00p„ pɻw6mXbŋ[ZZFHw " ,qㆻsAGGG,n2 1k֬'Oihhxxxܺuʕ+ܗ8)..W=h4ZAAh̔)S_x#8 Vooo''{Ǯ18p`$oJII988GGG0U]J]#juD DHk+h*GIKK/I^^_!z=޽;''}}}$v1:XOHHptt|mbbbHHKp999㉌pӘ&++wfdd y+ ?JVVvإߌUKAPPꩊ3n8++GIII8pQTTG4lmmssssss'O<506l JH?JMM3Q- w hȿmw #'''4HpVQR @y(BlHXpUBdP/B "R @ ]HE*1|HdHeX&oUFFʬY%ׯ_722bkœrMMM###[[[B/bЅv8Uޯ_Ɩ:t(77WV{:u*t'OpPb֖ 0yW{.svvΝ;_-/@`X?tҞ?>00+{ ׯ ,رc&&&jjj:O__teggϜ9kjj Yb^LJNϟ__~/Ј@@8t]_PP?Üx bmme˖-[`jHBB>777ʳ_/&ڴi֭[a.e˖{޽{wޭkVUUj%///===%%qqqS1DDDHvH|1== ( 7n3K&wFc2Ç{{{kkky֭[֭333333t(YSS'O >}7=!,jjj>w\*c[(;b ER$U2*rRUP 2e |---mii ->>>k֬bX!{lYJ'N9(!D"-X;[EVUPWUrixI]ՕԔԔe{ر]~11c0K@II ` f M,b<cNJp8B(bcc%8/YDXyؽU۷OMM .]4++իWϟϹ޽`S a$%%$&&N:`jjѣƲoU__ӧO -[& ڵK"+++;88_*s*@ 2y  gzAoVP7yyLtqƷoZZZR(&y8Fo!!!ċ/pn@DDUUUn.RVV޹sW7n5bX!RI$ҥKE%%%%XTOB"IUD" &1oRR.8}ۙ3gbddd~ǣG2 o++ׯ_3FKK .RT H$¯HIIڵk CQQ]@ I/چ,L: v!G@CCg X"yF)S B][5mڴiӦt-ϑ.B$=h"щOB  !6PHE B*@fRB Brainordinate Brainordinate
Brainordinate is a brain location that is specified by either a surface vertex (node) or a volume voxel.  Brainordinates include grayordinates (gray-matter vertices or voxels) and whiteordinates (white-matter voxels).  CIFTI files contain a list of selected brainordinates and can handle combined surface/volume representations.


workbench-1.1.1/src/Resources/HelpFiles/Glossary/CIFTI/000077500000000000000000000000001255417355300225635ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Glossary/CIFTI/CIFTI.html000077500000000000000000000046371255417355300243240ustar00rootroot00000000000000 CIFTI file CIFTI
CIFTI is a file format that stores data from surfaces (vertices) and volumes (voxels) concurrently in a single file comprising a listed set of brainordinates. A CIFTI file can contain multiple non-overlapping volume components with different structures, where each volume component is any selected list of voxels (e.g., only left thalamus voxels).  These components need not fill the entire grid of ‘N x M x O’ volumetric dimensions.  A CIFTI file can also include multiple surface components with different structures (most commonly, the left and right cerebral cortex), but the surface geometry is not specified in the CIFTI file (commonly, the geometry is in a separate GIFTI surface file). See http://www.nitrc.org/projects/cifti/ for a detailed exposition of the CIFTI-2 file format.

CIFTI files are based on the NIFTI-2 file format and therefore have a filename extension ending in .nii.  Workbench currently supports many types of CIFTI files, including dense scalar (*.dscalar.nii), dense data series (*.dtseries.nii), dense connectome (*.dconn.nii), and dense label (*.dlabel.nii), where the term 'dense' refers to having a value at each listed brainordinate (as opposed to a parcellated file such as *.ptseries.nii, where each value is shared across many brainordinates). Files in the now-deprecated CIFTI-1 format are still supported by Workbench and can be converted to CIFTI-2 using: wb_command -file-convert -cifti-version-convert.
workbench-1.1.1/src/Resources/HelpFiles/Glossary/ConnectomeDB/000077500000000000000000000000001255417355300242255ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Glossary/ConnectomeDB/ConnectomeDB.html000066400000000000000000000016211255417355300274130ustar00rootroot00000000000000 ConnectomeDB ConnectomeDB
ConnectomeDB is the web-accessible database for HCP data.  To get an account for ConnectomeDB, go to http://humanconnectome.org/data and register for access.






workbench-1.1.1/src/Resources/HelpFiles/Glossary/ConnectomeDB/ConnectomeDB_login.png000066400000000000000000004026111255417355300304270ustar00rootroot00000000000000PNG  IHDR 6w ciCCPICC ProfileHPY_Das@ 20q`Beqր (5 X wEA9&Tnz_ wrDXTa8ۍ=<k7M wwшnqV4.Pq4n*g\8aЗ.B0M,1Op|HgdrH̼B@9DaƩfa?SϸeVxA(_nVjJrH)3FQoE)A ~0,dq ^' D>i }8A,oφF,-cOxY^+y@%0HA "=~M˻et l!Ԙaina?f;{ :]Y @ <"f3<@ЀPZ@ {i + (pA"HbցM `/(ap @38 .: #A8Q!%Hҁ K 9C? EAB:h TCeP%T BP?tƠ'a f0f~p(ë8Uq _o<(@(wT *6 P%*T= ՍQX4@>04].CC7;7C W 18`ؘH ǔ`1.mcvXl6 [=mcI39q\:.ww7}xK>/oƗkg) A@$ل#6 a(K#:CIMRb=D"iIKIR.tt4DH#1 vQr; BѥR)%c)['Q\Ij@4AZG%B:GD q GfLL̠̤,UB6P6UHVNNWS''wX0EբS-#. KӣiIB Z/mB^NZ>\~|9y Eץ)S;O ,xm  )*+(6(VPTJVڥԬHlT9Kr""EEN-U9ң2*RݯzIu\檖Gژ:UY]GsSza54iZ Z{:&յhӮӾCa$yUYwTOQWPZJրilp61L4,7a c퍅Uƃ&dIIɐ)tiK3mh]ff_mS̏?lfВkYnyˊbeѪꕵuA6T%6[m:lيmmb*4fycfG[tS88&;:.[a'M'SęEÅRU˕ZeJbgt3w5wwp_( ,|^3Ves5 _;~d2'b%%< 4@vGAzA~Y]|`u!Ԑ!!BBw> Q!4\y=J9J\lﲑ;Y~uVJ< Tq&qq\w> +oo,)8YSBq(߉?X8.p ^%$Jz|4y:%"!*& ;WZ_d$IV;޻zB'NҖӐ`ӓ]Psfy懬kddfo~ZZڎu6ZZ_cƼ#޹67%ouoDliS||qVǭG/wն۾ ~.]'laNN;\v+-)޽dwƞ=o{ĺ>⾌}RҖw\XvܭBb[]R=TxӏVzW6UVrXHV.n|槿+(B3y pHYs   IDATxy|n$FQQAPjVU{mjTjUO[;nnHXOCfg;g1n89b(..>N<"""""""Hdba8mc6e9ZP~炯ln4:MDDDDDh8,m۸\. 9xsbcc,(JKKA -j;7(9""""""աmN SRR̤[nEbb"qqq[DvJVVIII<,Z0,+S)1$>>.]MZZp<999TVV6Zȑҡz0aĉ7˅iDGGftޝAQXXa 0޽{SZZJDD~S+@IZb6YYY : bcc'::X`$$$P^^㡢*rssٰa۷o?j#"""""MFG>f[nL23|pΎ;ؾ};e9{EZZw簾 n7nzAmm-v⭷ޢ'ƌIMM%99{nvEII x^<nHLLo߾ӇnݺQTTDAA+V`ժU^f=fMDDDDD!ir);X"""HJJ"))}QVV6éGɲe˨###"`ݲ,|I[L3k,Fi]KRXXԲ\.n&Nȑ#,%K`-tiJ8:D\.>ʕ+)//<334ʈZ;@DDEEEضM׮]ٵk>^zϖ-[ذa#//CYYMm[9ݑ.NweͷA4|ZKYv-ƍ{,Y= CMMM-ODDDDD7R cȑ|>bcc#))tU[[ 9Mzz:nUV_RSSP. EII a0{l"""Xl!wEtt4. ˲|avqU ;(..}4Mbbb>˪sapz4M4wqqqDEEx6-^77O[MDFF6Mk׮ر926{ojZSˣ-!""""":@ .`РAx^ //%k׮DGG;_}mɨQHLLdEee%6lyD!C૯ٳV?Oy晜{MghyBݖ,_+V49z)GFFSg|x<|> 8B"##p: 6e/..fIee%yyyx^L9A륬 Nap7SVV) o߾(ضZkY3}{C+}r1piK/SO= L-?++YfQVVƍ;w.՜|ɜ|Ջs9_~9jaqxnSgLk bΝ<,.r֭[2d>{gޒ|A***=z/6/<\wu<Z:ke˖aYVԓ}s宨G=?a˹k{=yF3<4_h3f1dg`9wE>}:u*oo*?rHn7۶m#++7o6mjv]b6L< Xn]48-661cp1رc_A+"""""Q0;BCaa!iBtt4cٲeN'_im6lBjj*.cY}Qf_c:w<{?=,`ﹹTTT8VVVӦM?n]vŶmJKKqXa,Z aA]0`mn.rYgP7ŨQ9r$k֬i5MCi:͕;;;>Paӽ^/]tqy<-?cԨQQYYѣIHH%K4>aonQt gɨQx,Xz5K,!==?4|'DDDpEرcyZODDDDDҽ{w֬Y¶m3`^/wvHf-xǮEDD>;"=p';eaYqqqY;wȑ#cѢEsmOxrs|nͽu7UVr>|8_|1{o(((O>kaܸqݻ|oVg|azKe }Xk5 J֯_:u*/Oƶm/_d ~/8u[nݺڵk)((}۶m۷BzIJJJy}:LĶm"""HHH`ո\. eTWҭ>ȱ,x `K.O?s: K.s&"">66>}JII SL|JJJm~1uT6lo}+kKL駟' ::*|M.2"##[㣏>rƃϙomۭXw44Yr%ƍO駟gϞXŲe˘2eJ.]JyyA*sna0 y漈ȑj>i$zMNNaD~8mq]?tX;˗_.sN"##1M}p0d`Ftt4$''x()..dbbb&""ylVs{|V^k׶9(l}ժU,]#FpǷX#o6\zL8|xg}IHHp_{nۦt2O>n$n&jkkٿ?۷oo5@3g6l8~ݞ0Mg"""""r_tERUU[ndggreƍlڴ+b 233EYng^lVZͰC9ߟ9&'%%Inn.w了:%++vZwy']`0 2e W\q/Yn] ֆk1nܸ6.sE꼅͖rՋ+#"t=C]<Þ={SN4MVXѦR\\M=\DDDDDZ z-Ə4.,,?w-˲{Xk &&)"n{8iq '`6|7XEMM >رcuk/**"55( ==СCu=pt l&33nݺa%%%μy=;KtXd 7ndiuQQ.gnMo)))UUU-.BsCEp[X>m>jC5 /@x'x ܴK- .$)); r95#`]رrJKѣ+INDd^|>eCnn{<+ ##QYLt{=N?F w̙ [+3<Vo~NNO?4W]uznp: 믿f矇eee5i'Nlϛ7]vѯ_?&O̸q㨮F 99瘦Itt4>/{-"""""r$G1k,< =(33uV +شi3^p>3gʤIcڵNIFF7o̙>l0x$ׯgٌ=[xb^z&\_۶y?tfѢEay5azSϟ\@׮]f >ݻ5ߴi ,`С? ֭[Ǯ]-Wljȑ`7Mnj34ix<gAHOOg8p< =&&ѣGSXXE#Gk׮lݺlvӿ"""X~=UUU瓖4bРATVVrx뭷gYwo[vs=wX߿?w}w-3))[oh=h1aNKqq1ˀ0 >FϗްaITT,Y;wqưZxIMM%22SVVFeeeV^}{y^p\M 66""" tNDDDDDpisgi&N8RSS^/?v3p@'q\}:(--%''5kְtRt***k|=͏{-^3f0sL˰GA%$$p)ЧO,Xit0_'22H'޽;`iiiƲo>gիYbERÿMՂwӧ~Jzz:ӧOgРA|'RZZJmmm 牊"11 N:$zŪUOZ;={9=8??M61zhjkkӧPQQadgge,Xsj;z>64]ӭ6˜9sep c9p%%%x&k׮t֍^zѿYv-}Nj.]0sLׯl޼brrrزeSy"k׮ :=zн{wwNZZEEETUUQXXdbccINNf瓟Ͼ}ذa%%%$NDDDDDrzkl&##ynyMM eeeNmi|>Մ!myb@PRR$&&@llDz2JKK)** wT4 v{ȥ#nա]DDDDDrP ۶^C;{Ssv9,rnӛӰ}GEDDDDh9lz00WM)}"~2DDDDDDt EDDDD<"""""""tN@H']DDDDDDP."""""" +Vs!""""""-""""""rTH']DDDDDDP."""""" (@t EDDDDDD:7)k"""""""GjEDDDDDD:ՠtAQ.wjEDDDDDD: B9T."""""" wN@5"""""""tN I&""""""r4]DDDDDDwg8I5"""""""tN@H']DDDDDDP."""""" 9"""""""1kG"""""""qt"""""""GAMU.""""""rT8JMEDDDDDD: UAt EDDDDDD:=]DDDDDD?]ĉUn[5"""""""GjEDDDDDD:=]DDDDDDP/"""""""@A9T."""""" (@ĩ8J5"""""""tN@H'x̚MNtIMEDDDDDD:"""""""@t݅.GFeY (+&{Oe89F:2 oOL=b.7mNˏˏ08{?i5Yᛔ{t=.!Sϸ`gbOi#Bj{ٌ;t"۰vaDFCJL33 IDAT)9Ce|擜yO0GZр8b⺐{iz 'P[]Ejfo{~"""""Tpw^S <?# {\3yW}Z{zV|:.9㲛ڜPݻc#k_ξ3ԌC`m^sN: f\pɳ9ߓ֔#|~}0wNYq5UDQ"""")tX z ظ d*J7GwQȷDgWk3Ϡ ?$R3{Da^vmYDzz(_︜=:GLhqI隚hmYx=uQZKywӵ[wz~֫|- +˝u||k 9]f]W<U7dLo6e%(G-X>e'sD*""""r4uXٻ/rGpuw_AMU_}:wMGe#5UK}U.Ǡ O] }$)5.!|sk+Kx??/;[ =m>}\*#}[{U_~@yI!#'L$FN:m߰uv硡PIT};6p [ݺhu]r迯SVTpPy75ua9i[ԐыwD$V|_h=.teoةʧA 4hРA:g䌏| SOU_~;46/o7_;/k#&6=qIv u9woYDzOe Q4>3[Xɻ,h{wR?;3.&{Öh n{̈6'};Yɻ|6?\6=[+;oò?lvQ :e0],>OHe3/u7+hr}nnAYq[->™x=u{,voEsI{,fX*ʊy77煝ȶ7=w^> ?3fgpwl\xZ=N$!9oᩫ%w6ۛ+_95Sf=3j"=pgqZlU!d kA{myt6޿4ڱh%6`q淮N m / {. =݄a4c繿9uø徧zo>y#g|'>q_`s{pnSz޴j1Zʊ x_b)璔b:0xq K-cgc/7رi5o=6ϿqW;WTIF{do^zy34IHFq` ?{wM<FɁ|lgW1f <qIg3k-yNՕ,h./<+jŔi.o4oGV[Qyt6Bgؿts}Q٦U߻N#9-#ac;][߻?eē-(ލte7ٸ+vm^Kd5Uy췀fxN{[/9]O8iVԋ~7S[KUE)Osi{ӷYrwn\΍.}3*9猧e!2:З ۶|^J )a'8C;2}ה4'@//-tw0|:ͫNN8R.=X3^xW۹7a-[mPy{ e&"X:ku=_o? gڼg|g}q :ɩ]GPV|߷+,_Coݽ͙^QVsY`.Q̼:.׍jڛ-n^-rq@>Օgsumu&nnQanso[ZF|uw }h -j3޾K;.7Oo6Hj2<:bgЙo""G9:Q]aۻc۾2,*e33iϦ(;7}h0puwMMFst7߬dYx!7¶,ws~#M:l&&wa >_qн89{!ʸg6A=nC7/yqrܮyן t޳ a8cLf+05m_^i~%$𳿽/9Ĺjן#}cO:y/g?g ض7}F,/ c=ezcq\p]Tng|[}l_~o&mon#`Rv g7}ƟpAmow'{wة3,7]Cמ|ɧ]s'xs\|ӯ9+Oh gܶFehܲfyM jW}g  Bªal^JjwnCΫX]=Wλl찴!C1zVjW͝p˜ѻǀc9V֭6U3` u&" ׿w7@ms!t>1vRm؁ c {<[akl [ էÎ7`7(Gμw/(0X'ÙQ< ꟞g`;M^PVH{_w^82$:|N?#/!vlk>'ڛ>W]׵>8϶oh?vUښ*vm^ڥ4J??7ڙvKL(m L,v* îw|u`aj:,۪ö<-/.mpY>",/.˃a갽l6!p|s6 > , ,|>/[W?yC'<: ˃eyk;(sy!8g_̰ o l5HG:}՗RV\@cFҳ1mJ{0 }yNPwb⏱|>fӽGI?`8o=VP]M5~six0+˫O恟^NUH-dC&CM‰g] ~Bey=WUϯvɎ8v|Gv^O~n}6fԱ4FB,5|S'A_[e_]?>L|><u[qFyܕ?] ;>\"0`Go=C]U󿓆՜]1M0͐֏m2d5TW`ہ ;% f]Mm uBgٿwY8cwL0l/^ ۃ`4&.Ça>L_vxp_gW`c׹?6{1l`'aVRj41L#$|iA蟆<|0gc'u3PW[@~8 sԋ0ǚLob9I?`8&r6y9{5cs<|‰a(&_gjv;Z3gKY\|㯝Vrh,i/s:ȕ{Dj+ypι8%?nvnZLi0MTt󪬨iomZ ö1l__/p>P0U* o Sa\$yj=[`{^eaZ]$r՗s:UƇV6V{;&\%A LfL G,_釿0ˏ 9|ۻ_,7nk6Fܶ|ؖ_ 0M ô0Ll0,,2eaml?iZL \ $:,ox=X:,5@k-ðϋ=u؞Zԁֹ`> ΅n50p_WXe;^ ۋmkV`ڶ_Y@ CoX@-z?; b@DzZ|yŔSgTf<j*JX+}5U}i]OϬTfB^znj*u>W;V/ښ*>yEE?}Itt,{wn/]|oڬ'NEY1_C{4]w^ :6wɩWR]Yξ]['0i;4rg۾YW H~rs7(EӾMvl\6Ͳ,j+)/)"g϶d~7š w>לywxj;T2P^RȦKtˬ i* M?.19q]2HqSUٛ͘6]Ƨay@ Xk#8r6qcb9@ z t|q o=&L;^ޗ}ŭ?E7*^y6/&\qjjkY΋۵}AtLٻՇs<.ԟm>ݟoG:MDc9x>kp`W7Xf 00lكꊝrQ9q ֦h8VaaYqPlzX}rqgwB}\:l6M\t~rFq ,p!L?dnՖ.Ɗ+-> #y| ?{g7{n1=u>㟿2V~UaAae#-9z֡WO=mNn咐Mڽu=~EmZvJ\s=~fy^yuwqzZhmeƟ69eYsٴjqtJ;?kŵ'95G^iw~VWSO/?1cY>yNIaH^qo!E$EI!=쐞|QAι\}ϝ0\rs1molocoɧ8 $g_ '<|9FM>);/tiAX; ykÏ1q_jQ 2 n2 `o. =bٖL#CpChmm~>,z8dڡ.Za˩_YM`LZMDuSd'64_FEi1^ iS!K"*o>atMI0{>x%?g7^'Dgc\ۛ>GoN:r*J؟O 8.SO[7mr}s ʊ xNm;蚒ةrϒٻӷeضk:o?dchsڍaW3z"#=y1kHُ9oK5%p'7ivyW|5K6n CM9?!4ߦA)dO!i\qe?tM i0Yشߜp˹;iٽl@uUCN{ށ IDATnwLg)+)t/3{1f4Ll&M; رqmyrnٺ=7Q]Uǒً18p`6Y}!NjA6qq]cO(Cjj} Jؤfbڬ+,o5tsa{1,czOʙ_?232{qʬ,/!_6uu8W҇d:!أ=gW8o`,9]j t;7̿~q~ǝ .as9 HQ bd*Xzl9zztg>g|ٖ`DQ ""2AD9/X,v7}tWwu2H8ELOouU,e Y2($db/nbE!\duXQG DRAŜ|(.I!NHt5PYi8 g5/6 z䡑/=pxb䪇ľvNP~#haDujVmdoX_e;2zH~x UȳY'krMBY,Zo'}^8r FdYu "< zsgf&)S=9vHGtk(% HΟgz/=}CHh;g`;zp+ia@GXԖâAC"+ԥ,eҧ)+ bڮUn¡y@-[& X tA^7g|+NsoݛP& )Lkտ4 06B\FqJO ](%| -F&I`ŀ2Ib47˸e ͿT|V^C_5>!3f%>rW `ĂLwx+g <;CJ[PUW9n$9]{%X)L4lz6x/2䅢aC]9TA~t,Au}72 gQ *icZ)WR"$QXgy6'\e.d/$c@eIº72r-祉x J-n6B(Zn;NA Wb$g+YSM郍ؼVb b ( " gQF2b>ř-n3[o:aHnRLyIls(<|hUr/04ֵ8hx-iyHӆ! *WVTS9x97@|g$9r_MZ[ #<~{%O `Ƕ$|d.5]`\̢ojψymZh8ރdzg[@h%Uo^uL#ϓ> - ~m%pp$Dbu*\(^NlpsLIds16  ɐ S Y9ʤL;V|9`noS"22H$#񭁸޶,NAqmQ( F]@][3)S1cm`@R&dsr q׾.5QH*HXPpFA`;G,ZHsoU\@촚6,MBBnCguP6b ׀0փd]&?3zoRQFo t[+0lf?y[-2Hzk@i!buZBivlbD"Fy+)3f^gm`.7FL[E'q=FFlmN DDٶVv;ϩVY!Kd^fQFYA ,XLJ-]=(D`YHx%]xcں>Q( u2OgΡ͠Ͼ؃ߢ(3 dH{i'G\ĹPNs+_:9\ s EzM׻m(%OFw(eT[UW1 7YbmBD_ ^84Rǁ@|*ߐYgo7 *2퉽QϷ-Fy+DVs:unR*2HB EU.v!?>FߢsKOpnوS2(wxl6:k3T2'gɵ0\fvCw(nAnAdJvCbx)%A \B9ctPFfGD܊@wjeK&zo@a3A(<+*MXr/2@D,<2{,eT&`|>`ceCUYt"Vm/º(:(BOt\ڐm>Sl` ª @P#"B(Z4B֪ X^X|P3^6|,02 udXȓQx&FW*jr*7nb]p 3 0xj{^"Hl|#٪ԣeo2Ő\"ADJ**>} hk@dV _" Ԭ UnPV䶁K:ɸڞA@}Ǚ;j%  6p16̏^f!r H[B)+H g@GeǤ8X%ϊo&i][ɘǡ3?BǶ57723goތU paѻC@}@nEW$D|Zp.I%4*?/*p) !If(+&'W$$5/$8ng$kx p̓߫tkaWe ;kn=U .oɺ n.EQ0*VDió]7MCA߻@"j*q$2Mp`;sqԼu/|V8Xot/a3->:[ Ec1p3nx-%+^(^@8xJr6FĦH/Ii#5,gS0!zwE=3tyoy^!'erیC+~㩈dZ襔]%WPr%s FPPk8O%7˱$,ȱ}CעCŹLb/e/M wl9Fd"VTpW(GvL΅>,l@CU<*Qx[Rf̅uvErK62)2(1@E*r :\Ksy3Rk2-ؓɵl@p 95ʒMF2lŦΠN6e9>6Cs2yC<Ϸ(ĉt Dz'i2NQcIn]# BXEW&w+>MćNGD98/y7ue8YrGdi'ax!1Wa*8/NoE^m. EJ|`JnDy?&@sјXfl }܉tr\791ș؜Ig4ta`BBlWi)߂%q' A ?8Wg;@Ihr)2 !kT>ꔆHVy@ \ (OVX)g@0.RrJt1=' r7a 9ھj pwEEt U(_W`|?oJ S"]ȀyF۬{QFoJIb`v1:He+u s_ G(ȫc8#I,H+"e* ^`H q}^U+\m+6ƍ\ɬ3x: on(vҺ VIe#e\B F !+6>+Ub΀g0e%\ ։͐oAb qHP9 .Y&Plr6 r{2Ɋel?e<qWWЕ+/W=dG7{>pseąť /䋅wL罕$g^iojToj A=Al%{fs9~(lƖpD|ݢl=qV(0P@,Eխ @ZL=NLoŨ,ZIu!㍌>}nIy.,$q8O_8P)k$ ERWTxIAEO(_p_#kR>T@9wDC6keKjC'Ak Lh*M1Hq } ۖYxOgqYnrI$]x6䖩*SR$בᘹ"a$w{Q;Ge@< *I\Jw#78Bܘ}Xzi} >\bXtimF5zF.l!HŠ+MvvOt(R|AtVZ `WB,kEp{C9(TDfKfV)^fIGu:5w,zN<\=hc$4Lu[Xm7V<ͪNDjk4] y +D?#͍P|ȞWȕ :kW"(l*о%%Uƌ2WA?8Avyb(թWK{e MYbRu+ JV֘~6WgeF ac$e$P"B"-D|5@f^Ik攓Uׄ%l X;(=PCS:3H CH{3ΗP2(A@מ;6`θ+޿[=0Y AGi]|" $Վxp4M$T@",܍"<+SJN;pK$PX)H1J:]rJ^ڡ<;}"ddc/m΋^O%Ju Evt<ˑJO<]2z;TXckt<uSyslsÑ`J$Hk@y}*p YҴqmٙ!!6QlP{@\#RZ܆]U6@hO\ڴ

Į3\+Oz< ~eѻEezIW̵3FkN" f ,ʴX\A{iIȵ<W} `7{w[xq cVɏ%9Β,>S֮j5q2**l 8# uK@2H ؇ɺoe-` @Db-[O%. @ pCg,<~C>e$#ϘKOVda@Q9p@$LtfTt.`2z)?,gD*tCnU 3tv"Qт -k2jΥܶƪ?&"s;d*j->L'^cd7!(jF쪉\å=(Ǒ]ى$[pn;r?p|sԸλ*3_m IDATw({lGm(᧮eGN"2*De+bMQ&ٕsiwBn;4 Cm мK!2 WEIUAb0ǫϹ t pt8j++4dwy&NZpҼ/:W؈`tˋ"7GvQKݥBe]Gɡ8Yru _g]*pdV6Xa$ HSWK/])D^r}Ŭ18WrhdS ,,dEܔn"ّ^@g_"˻J,~}FeT\W;m&&IK2G"5)_DRL \z+x5 l&?[;:D[FU@J͆͒D :( bwM .s[$Q5` _#|Ηح;]FG6'VmJʉrVmC)m^b1`:\GE~Gd`F gOCk@$.IfzZ%[MKIjm"O&^M:bЬ)~8ym ܙRo v]oyd|3=:HF Y2ʨ *ȋn#W.Mt OF28mAL]!`PɲEy^  vV`A!Gzۊ:\z@u2`&f;O6QrL8dN 6eЮRxJ(^VNN5i| -۹,3V Y9NPG(}yCQfӽnTfՁ#gpnNU91e#A  @q*" 5}|2mS05Gm)D} \t=#L JF+r^B<hoOCt;nQwdm4bv8'6nK6ܮ 5fM& =}Tb#e+Xn'&X7CPzǔx];{sJ' Uf<1tcpx/hüK 4Qb9 6jkGBԕ2 RC<,%N~Ņu6㹓ץx Tv=/(G}5>1K~M=M.`c^ 7O-PXG=zB>\ ,{@x`aҎ7-MP4M*aFH,.+~4EjvAX=Νۣ i0}'%đIbXm[im~UpMbX($rgtO$ʋ&cdf(tIiEdظ{]PA{п2 Q[U=+eH/=$/IZ20 I^aLZ(D elLr<~ݲS.DZz _T3&pԛ&1vo}An֧֝J.[of.Te~/U׉R]-%*Pդ{s$rZ2sq ARg2BowvʥfB˽}=FT`}]*_7\G=>( JyjR1d!v%7)`y @ϑnh"z<*m^Bf1o\)}Iv^pQu= k9oe Bk4YA^'/|m]S7XWI\8J%&LrY&{-LoQ(]u:ןgƠHW5I^.߼_>F2AZS{c荀`1ϓ%~+Y?qE]^|2(ȬKkæ+6.aegȎ=%y &M1vGWzkX`sw632 gneNF.:;Zthlt_ť3+x+>_ Q}Emt xY?}w 3f"?-8IWX3bSܩY|؟HIpO>L(E?Syˮ}T*.+ 8?>&N -5.5_ȗV"!:::0i"G:kA6+Fհ/!:;4@'`ڗpb3>D>}"g|5:Bydb;Wݝ& (t_>q>ŠšB[+2!6W̊9LcoCUb{+occY}gh AJ?(b)qYRVj- [F.`˹<'Ƌ^4/ӻ/8+9Z06^ExVcXYwrQf Jo *Yʝ5Y@ d3DQ{N/t O\&e`7XDŕ ܒTT6Wf.'ڋشq\sg0e-xБǚǿ`ixg:&5`̌ޱ) ?'?o?Tոމ Քεw|{N^|2V8J K2RĹN&F :8h(uv:ea?]8PDtCkeN0°Qc5H'W;$Ic?(f0JOkYCc6"( L=@J7Q"{No> Tf+]׿- ˚/HWp{ Ռn <`JnXgG7 3w})7 EF~86p287qĝޣ=1\-Πifp'FNk<`q͍ؿ}=`!( XoϞ@c ̺ 8kcq 7 ,f}8o;[?VPVg.Áѭ3ߍaf॥][4x$^{]N_Ө[]<Ю1zk@euͺp^8}t?c XpPӽ.q-~C;6I64bi{˽6* BĆOa߶hz/B+ Xb~NL%1(o~}FMm-֭X;)7b)ӇQYUb?`cؿcccظixh<~GN]1S0{PYU XݯbuױMS`mG:n2~TVU16xmDkE: ‘=oѓv{[^]3Ы?tFO o#/ vo݌=^G6?/,1ފc0lx+Fl|֮лo,v1F8ކ`POn?F#uGkfݶlCqV? ؆10h8s}%穽~+9q. ~pQzػu=***1z̿΃Bo{ [ּ gNit'FLaaubʧz"S6~ { [^ygNif-#&NCi%Bd ~o0y=eExpP8}h^yA>fxɈ X(xҼpӮW=+q͝Ee(Bmxg8c3.27soU5ʗCʩT9_jKBTÆK1aةwaͲ0k=1q6W0uѭZ: 7` Y ?-֣)r3`a-8\Z{n,p \@ vY7D\C\ُolW*HF^^Q`;bbx'0b`̂t {Wvvb#i_5%E:cM0v/v;/ʍUh(@ wv]B&' @h9߈cI0{(O_g4ySlabnҭ="+@G85.T=L}s_nF}K}^~ ?MOi#XO1qLYtg~}w0S8{ :;bʂ[PY]m>n0lle^_$^~~ 1o0T'@CЫibk0qލ8ux/6|ވ>50شbiY?s3kcҼqlm9 4&ۿx S`Jas]׳76\^a ~b䔹P*F6?u>#7SRޏ>aqloGcY0;֯©1vƢĵC7b%0;;}*0pxxF m\6,/&ͯء]kӯGonaE;u***0h8E8u *aꂛgP,طm&ͽg]8߉mV"0HN&"5O?&;Oöuptߛ&]va:&m߀IM;ض~%C8g+:;1eז; oL{=&̾|'](QcAqظYL]kӫS/FD?KOFoR1V.{} B~@?0؆n6Z9y Œy7]رmf_x핟g!CoR ˟}} F}nCGG;fν5شvkaашN?XM"UE?kywnF}c_\wGas?CCѫo_x LĀO>]ں ^)s!ى_[\.!#!r8yk0k4/R so kV`ͳ0uh1:^+O=z!5K#{v{-j1btl^,50qu8wb5١7ؾ]L&D0FIꇀ)CgT{)!zN!(P!Viˈv.{>.?3.Ŧ0b֍wFL3?Ƙ CG_/phL[_2WZaR~ ܫcGN֖ ųa么y^}L̻^qc*Ll}N}}߭|r3h~_BgG;c׸p8h(RDD gNx/w[]=ܗʺgp`?>Fޣ'ab3kooG1b0jl9'Djjv+/a'qŌ9 ?BGU?} ߅so"`98uE!c&yLy1̹.LIpA?~[<\} #tvÿE@c@<3P[hڹ=i6N?x{]=>_#8 LwcNL^ `Y8s NBf)̺6LV ;? FDya0~\!Ƹ)z/=Oe]j{O!(ʡ_?M}Ν9ںz)L2wc'KX£7qxu++8N<}{bε`ؘI_f]ةGCov2Qp(FO"gw_"Jz?!xz./ 0ߣ l[" ;[_^+͗PۣGƾ8{0Z/{|CZc4}/aq^߷݂WZ'0nFN+-xbԔVW{苈plXN_}<_i<ѓf5ȾIz>@ű%n%nu 8q`'RhV<׾q7DQx< xS)?iM>0j<9KԩQZV1uTߦ^skXR<=RӷrT?9<_¦UO*._<~ ԣg?aȉh9k݇f&횎សrn&e ^5P^5Wݎ)8^9h8H 9$"ؕB=$6WQud6s{sD/T#k6-8␢0zG{m+ <Ĝ)uk"ѪL&ys"Mm;Z~"JZzc7\8#O\5p8Si[7 HiMGbŜQp﹃ yĈx(}JW9I,#ec(wy{~;xKPx@dő_r w36s&Q߭(c?]50dkC]Utr}ܻXMGԑ}Lckr"\|g;FMS_T+?|fW6tt{MmF@hmP^:ۯ&ݻPs ىgg/7[?ѣoz FEe50q 0 Mf UUU/YpG˹8st?xV3\;y*1r:jڮ\TVUߡ6(-CRAj?nQ)\p5huؘ)|m@S٧?>_ -z}#!o4™PPY]/x ?:;?~*Nއ\D8{]#p+h[6rK jWAFq*o=Α&֙q?w .5j4Ǐe6r FCWΝ=:m0Il"bS"EV<[7+WZѭBjޟM`|N;d;V\# O;8c.xf-Mk|Lj1EFeQ33`0`V&vo0̙Sem-FOPi&x-+GipxI=_Ji9vP@G/ʑULU5%-շs'=lߛ%I|TV[b⻰iN,3hϫk<;7F+I])&pjXu!LX ^_4S=EE*~}4󜄑 ԕOjAaWR@,e` #K34ʭ5#@iHC"+rݝc-HamiA'19lSD=쭛~"1L$hv2+~BNA7gZ~=6\HIm!Is:sǔɟ9etO,Kzd^"˳tnu 8hkZ/5{VJ[Ο6w^^MZ^^Re.,AjZΟޣѫ'}u^Li 1X^_ׄz mp=z5a5ze]{›WaͿڦl/bcūOȸCƺvk?)N܅h瀶g6= CkhD[ek>yt?~R8ǫNُ^| '%) k(ҠGc1SGv֙:,WQiVt_Nݏ/Ƀ>M@eU5".> "B!#dX{]CO<]>wYޣ!9Ξy O?Z нVtfg*+(϶!C$uz=uEL^8{ݻU#K>G5K$!lh>W'P\@cg/d KFr$([J#Ĩ3q>,woݿ}3'0b o95VT"~ZnG^`WauϟGmmYu#;:;1jq|~_Xx0dT\< @yPrySk?lxX<ڻY%TV|9iȢ +2mڢ۱i2TV`[нNi>s5C{]ѲLeۡ|}mmlRT*e3<ۜf՜l/ Zc*M8zn/ @.N\WAmUu ~-ÁrH[y -g,w]݈.:)X ("tFoqTf_*T :@lqf <':$ QK *dSŋ/J~6abTÚR'@[j'c ,6$"[CFMr>̱nomx_8wQ] +QSKg/ Hv5 8BYꁱKI$J)oŨc0d}Ы7הCk8w(o[~U_z42ќY<ƾpݟcQ[߀g]d{ :ZQYmf<~Bc`q~ϸڒr,kj{4W.5&ꊂس:֖x⾯vƢ'z_\a/6~=ຏ|GAmCOx|`]miL1={"qΝ)miлv_?g78ݩγB5j`z<3|vn~GH<{È 3 0;3YMhƾmQU]c?{Ŋ~M3Bd߶ UFeu5+gx`!)eTq gO9P8yp7v"z5 Čko8ƙc/\ʪyaqu Q?ZqG>1^N1s֨zKrI|TL;yh7^_q̽^)<۫A;Э+oUF]ɽGdb)6gyB!6n^R}ZAar<X> =Qןd++뱉+vQv]'FNI"Hک32"򱆜Oa=LBvdcIAoG}nmYGSdeD@.r6vLj灼R%M2sha"-[u'#, -N\aC~[iKRJRI&2<t޻$L5e.k{ Nڅ|g;o[-/=o1US9ezQSaǺhkmݯc hS~WEi;y3@OűoO4AY^_Kؽy vo^k_OWh>};v_n>k W9[7^ԑ}x⹓|Ыw?4 [6kWJe߳/<0Z}KVU58؅v+s-b'z{w ŷBf匴aǖ @ xgy>Sqx~MbظIX8v`/طu3^_&/QDH'{,ir/EгW?4^ eր!:][0bt>{71Sb z2ڊU@]C/TTT9>۟`9ؾ%l}uZ/xiُPgy~L}=x4V?C4>)jSGgų'q9بyI!cاᅫ՟jk ^X!xWIu;7Egi@gN^)kʓC᧜zKrI*g>wo^|qӇΎXoi(L\s ?ɷr`?C[k ؈7^yߝ:]1߮E>*?V`cU^(b-QR4@3 HdeK>db)'_V+eL!C #<1m X)t"B܎X>=H"iN cıBgЩ1@DZ(3Dqϙy5Qln"9l)39" Tʸ"Z̀]zeӞC= k02;{<0k1{P.SCʁ@n#F7 g$:xM>So`ט[?ʪٳo,8(W o܋ v/C)nu 4f,^+|T>XX>ѽGOo-sݿK~*QQY} fbp`<GJcTN_xo.z;h$0|\ޜ>ߖ?w\W}{eՋUlɖ ` y$/$1"K-7bU.zݾ;3?Nޙ٦ž-غuy45)lso-~~M8]zgШ\Vξ=VukL zI *d۷?w?e*g}=]X3eb[scA(w{JzUS9w<Ȇ7^κ Ra1=sbƕl[2a:>PO^~!^}eߦ 9vzἊ+a}xx{32dT%S,hhXy&w>˞מW!#*z˂ &)aШIݷWg usR IDAT1ںoL~ASP9utB퐲 z'サ ˗0j4f^[LcCE ?Cw~}Kn CAJsң?竎ңϐlZ8B#HsߧY| V,y2hN`\A0OX)V-~2H;mt!E-|eO~MxFM !2#c+(f9u~J{itX ȔД& œ! W& 2K$NWBz &pkD'CgХphuՅ.<;e|v@M͒IBnGύ 84%ĤY* &|D*5gf^"gFUo;\OF+‰%d{iNeA֒@ 0TU(8GpwHw FWĊsFNah1PC)ʕA!|n\[l'E]A꫔wkzn$%qꫯR\ޡE s\_RT>ź(BeQ(L`mzM :$P눡eBv{ڜXOaqE24k],P&Q Ak;BcBBTEJk),5IK1aP[&5.QXZYhnhڅsҍl<C><A5X΄fΏM/CJ>ׯ^/+K-H46֓_i5 R&k)R,ؘ1" "Cxkxz$2f] CeuϰףߘAD] 呡qYAsRJHĩFqY{mpN%Vj_%7ܜ< ylll oVU#8HiB4Jjj'ܤ5W/RXRa>iLI[5'.܌$Qʄ/Ciא.9Qz̢YE/=Ү[.%k:= ]Tavp_BWi(\.D-n~R:_@~SDꡦgFZ!w)%B1oZ0pÏ\m ߩy¸%r5nqsňhӌy'#@z!> %cnϔw>=QNe ]$C8̆#{r9 t7g#͝Z7*݋+w0i9g/IkG )_ƍmN-JR2J"DOܛ+xj=܊t3kɈ6BD0iz/} y2R:pVhIԼa [5GN|}B=(<%sF0 4V05Bx(5yĒ"J4(6oAO"0@pDP=?+m]aQ-ebD%@Z,/&Tg}uݗ@L!Ɏ H&x$m}J!Al3>AOZZQA&%H9D3Q=cȷRXA>8rKZK_r$-9e?[/Jty)TL}.qεki#IL*%tb)K%Isk3L݈/<Y!['S#ANe5ZW*&%*+Xq"Hoٖ Qў 0kyw%^,ƺZM)=2 A3Zp5CS ġNhTA&D, ]|P^oKrΨM5k5ppr= R&ͺ ۸g`6 f|f|mȏ Ԛ wl0Kz3.~')"@+mUuҩtLBJBvj3>uBf8Br;P[;8MFs4BT Sb 3q˲X(Gt9CXY/}vmI1cd}xDZd]0XC)`0A&76# fNj\-ҳTd+}0Jr?Ip݋TED}YCnS?"Wy j'[{I H6 + tgWsR(G8K猼C1MYC,Fe`CQw t] V|~U"h62"~cH^AJF0㫙eut4܌[oX0֐ی[Iz-'ZABɅי4Cf}Km'{:0*@h%,.~H'y Y)Ts3DJ:Ol(u`Lym=83$RQ{6#ɬXX<`v㰡9d=M/ʔ =O)=?FR&IJuWzLjOh?]6,L#?#S3ެ_ [m;L)04L#L)䮌\'΀zx"D4 n>Dzrk-XgHp'3%xsQl~ccwIn"4 pcI0a +?عQ$dfFԛp *kM0IBy+BM(T!ÜM ȣt|jnC䌲aTiTū0i3w˹$PcmSqVZvΦ[!Bԁ9 C+ԎExqk!U8 ^"4]2N87Q:XNK;J.*3DlW9őYA-q)#jm{TL`oҧ$,t}׹縸;[ b#` E`=pLFGp!y%RLӹ: Ap65i_% D;cHss@b{PhY?"K_D"WXm4d>t0؉Vouh9 wk6OGC.l.ukٽsLbY ; ?N^~kjD !SW)!C]OEh 1h K[૜=~ ,7v6q'm sg̅.tʱ[Z|_9tN;$- Q`4pgPj15B+[?BBHHOYj$}弅- }ױ90t4dPRLWs닉⵨KOӧbx$JR!-((LhELt-7y@B9Q_hO'n[ =z[/d1xlRYa|PwAR:sズ(aZ^ 1Kd a'eA]B\jGH?%ofN>{~2@mepeK=G6Ñ'IěCz:;YAuHU 7!>0[3K'ofḂA+a i" HO:̙t> Iz)q@u^C:U@JoZCmu>/Q^.d̲F-4n,!~ O7I˞ϲW ofԄv^}7·b&9XQI) ׊%" b lcG(8ߦQnpak_{m1rgIAQ mgۚe~s !S> { Dl$/O#fD sHW4$RQ=\x3BCO;vc{O9s#+_)'}0!})TtғF<Đ[2+ {cٿzak)E Xxڥ]zܹ 'n TAs-v'p @V+c~,xP4O R:ݮ=V$$p{ٷ O'ip1OUv,lbgDG%CARGޗ. _yX%=_YΥ &bu@xڳQd#b*6PvH$%"C$PĄ: !O v=>gsΙ3>(gV44SW}D%@]9UhQ_&Ax 0тpƀ) a.)d"x-%2t)㧫)L=Ks81wmj(M_NN:k IILhMl,mt)d/]W[CN]{@ K͹Bnra(fx(韇@uw7~W{)q~ ے¹ۮR4mWu뤝.gWG=6]z'K~ѓ3hTq:*4k5LeD}7s*`(EwZ#t~2a~7R { 8EnZO _)TŜ~vNO@ָi@eR:IK:;3V[kCӹp7%$ySx 0)=mR"g[8=_>OQi9fҼ~q8g3FNfRMϖ9k#͍ 79LϮo{;\DEnuO'/R[?M,γFL]0ɨ? t,3''ּ $߱ʅ;obՒ8g+, dҭmr]@yLe!}J+n?W~E%8ICvN.k^}@?')*)c֠݊]>G[dx8k'/({AL!r Yg:|E%!|)ټLScCM{s IDATKaFY±;~Y>h$SnV)yOyElzgi29ܩc}Y.T'}0c/,Sgj{Sޱ I@HV UwG?Ocּ"O}2w>9g=N?L"c &ϸfذ jk?}_Br >R'~m̞ρ;8g;ELvz;/?Cԭ7cJՕj(b ۯ>c5}رc3YY =[ލ^ulY+/оSg[ f( !x?Q30`L}41롇X0ݹv_T̸[wV.zvөg̜GCAm|EεK(.-Lu~G_g¬8=Q.ŃFMf,~ { L5u.FqcXّL&(,)پ vk)ؕ9 ?bE/~}#0pDXV |9 9w;G<FU2y}xYC6e\|]>ChWg*lU0gе`TR"E&7.t쬻?b/k}7Gvo-2~V҂|3ZR쳟WEY|?9k'ah-$ vo|X,n_rNfFN4l'c9tғcۭe!Yܝ]Ǜxߠ }۶s'2xd<[g>̥C^IJ)زz1x%V0t47woaĄY 7CŦbY;y\8sy{ߢ-Sܼ'2bĀp连ڕH)86tP\ZƠٶyy3?i&/[DYE{:uۯ<Ǒ{gԄ\<p~03qx6MlO{1U'ͣr:w5osr?mٺ~5tlX6o `;K^]Ttp,߾i4PWGc\xcpJu=M>ԫ]{7O .~{UMɕsUٰӇSgSuwlVmOC61bFT$HS}<;Ǜ3E$7/DbV.}CFg(,+3zBе Km o.b1i%ȺWDX)NES璝ώoW@ ϭzE73rsع~99t5 9HH{`ֵX8GMb[8VƐ3bʭt֛+>i#F ?CqYO׼KNn^&ʹS)1V]2JO޶WyqL{ғҡ}[V"NfJOW5{P8ʼ`lL!*톽>tCǛrovXu')(*}Nꥳ'9q`cf,Moȴ3jbz B)2)Ss2V[g;l&6,{k(vmxQut/|$5qDn~SfpDc89.qbI& 7j;ta_ ˋ1l4Ο:ʙ=yl]*FNdR K}u6u"O@HM\xs>ˆ7|ϜCg޸?dة47vopoĀcrZλ]z(. %c5H 5l\w|S8?ǾE;~&\8ä0j#'p8gNRe gM<@942mtL]QI@JIsS/e ߨ1\c^1*(|o> //C޼ }^ky_3ub܇>^I}56 T_sC;qoj0E%Z!RM8dK~ 4Az œ"ď`Mݿz~3)Ӿk;-_ޱ+ KIJ6Ν<ę5m~FNAe_@A~CvK:")'!'OފJOV^|< %Z CaP`NL?$ggaQytj[nhS.&X\C¹Bm޹*5l-u bRjU-B;B[?J:'HsKjio x5#/G߆— }}'d` #)tx!B*=}&xmNYYCn캰Z19iUZ?ķjRL! :qqDW1kyL.(>lw@wqKKl$@t_ ǛƓ-@MzՋ\:͡zb(kߙk"HߧJ[&GPL\>{O2hPMuT_@ÈePutGOr=ǹ3t,'`&ApTtXf2 k.;&u$9֛L_İ RCK[!deg~_>w*q%4fJ~>}[VPG^AQ4v:S}*{(}'^Etܕ9 a6@a=#%%^PRR"po+*UvmM5:uE%eH#*v i~<F(fݡ< 1-YZI\t/=v^%kq[PHc̡9 їpn~Eeֽ1S賴"{#[L"Lv1*oU.+ ~1V~Q ť\pF=&k)(V{^g̱Xc5]j<I2.E,*ָdҧIj\~qf sڥst֮&H KgOC>R&::72f&FJmAHO&W{ 9p>WY#Bbn rj*,U81#45^D:l]YX"LWAap t6\!gn-7GCQQԅI[e3RYc^̾WWW =Z/l<ߗVXnq] V |Ϫ>3ȮPm5ZPWcOFHev:vEO(l $SwJVGGn'õLtCz]U䟩؍}PTZ+?6u5N0T uScE9T&@1M u!^ Q5x4,̱ЮsC(kߙ)[O4eG&c8zx$$4Hs^L<%鿦c)9-$xPw(+mP;4]pptڐfCH)Li: =lEKyRDqԲZ ڝo2Ԑ/R"yz]{A7IUGX>S(*-}Ծk/oI)9*c*yNKVN.<+9}=*wqQޜ\ڍLd,Mݏ@OWdL(W҆p E$icKvn.ٹyܳzJ-qwPu ͍ ̾ 92՟ g5a^!6 #'7<WD$gOeߥN]?v?HS%N}?Oxt񃊔u׹]Zx>~zn}.>Ю8BQIо8~P_9#ɥio6a{'e˜=T(!&![ SnB(%DGzj'$ r i;O^.vlEO־B9dT_gV)җ\pNTOYE!w3"$GyS 3²R2%çc2CrCcB$'ҙSl]`wj @ҮGsք#ɥ} D] ezHjF,?//\Š%O!}IY\8}L΅S~mV,yO4eIu(|*@s>O*T=[ͳC;eUorʔF(e.=p+f]zuV1̱A;>zReԞ@(¦{#:*5z=cw|Zzq'=<ݐ E7^@1>,A*Ԏ!jbܚC%@fћ2|B[돐NgБ0a 3~ p3Vx%HMRO3Od8|OӋ#Y ǩs^I36BM#_x}I\z}$_IZ`7O6h&.#wIƛ~~B ;!&Jť =u>Ū^ĊE?ieZc8z''ݳk^e[BuC b<ރǤ]`-'=82K絯=ͅT_E?}F A(Ջg:~x<HubqٶWX_ω{X3eeש{޽D+~[ <.S޾3GN`UXܿՋzdrY=DE=y,K\Ʈ"B;xgOw}կ=˪Wa寞A#'0a֝ +'w?ֵx)}Jʹ둿P$2vG ?gѽ`G#xꗟeՒ)(.eJYh򑏳rSqK?'+;?#S8w;/[?Ê%OG_04Xٓ}5/U/?%?0aνm})SupκQV,~weڂ)stk' wJmܸ;_FL`敼 3zWK4ҝwlOx&z6]̝OK n4}(MÎvF̓ iF{tVa=e'ڂͮ\.7Gҕۻ$A,vr[lg7TD/,gM476PX]왔77P{= 跑ڂ#z Kʴݷ|זHq%ij'/0|\se8uוۨ45{e KȊ鏦6S6S[{sAcC=xE&D]GRPxsc{ߘkQIU b؎;N<:y7v/Nعsc8`z1E !@E$$^N/{}0h;{5˘m9ƘF]uì>wbfvu`Ž}{ Njl[@oO7}mlOYk'ho߇}o\v9f@2;ۏ&!|::ip^O$]qR*d,@1"AbHn Kή.T 'ްf,Bsh|?D]`P@Kzq Ǭz\IǁAe #ݻa*fRы4{'U@E)ֺC:?(@V뾾tut+KrN$N:O]mQ<#+Bۇ644 >vwƮ>twi0u3 GW~7ц/ij.q 0}hۏ)̙E0i@^\'{PDt/}_c>XZfq KUeqm LtC}γ+{KI?KULkc4&A(UBlJc_q_/ɵ#2SvÌ1Xy w@R/̐,uqHSX=$x]p?~2{G }_z!krٻSKn/0ThY.t<a|۪, գ+'9|>AϢN@ץEOu8SL.(kzW $@NE9#fR`)bl$%"FD$rL!% ] |-5΀T%%J"8LYDʯ@ &BO1 ݯ ?Ju4oUgY~6wGa!?^tu175_4G4S ͯ89Lcu1{?j("U0-)eD"͓&B X'\K%sǰ:p{D3x0C}ĕ1&ʿ0 )Q_-Pڜ71ksviMk֚􉳻8ˠ26ADXMz 2*gnd% JA997+m ezwv$F?BLgH: `-&Fu"Z޾8ѿ' <Z˜ >V>''"S4Ϟ>BKCJS/\+`SnGgޚ77Z=c`i(q'~oKrJ  JP+t6OQ?pno<Q3L D?A (E"Y!ANȉUTۦƔ9I9A+LZR^U)U]$ =4jֳLB dtX>dɊ<ٵy:vܲɻUmCJI9U*Gځ&|M!]< sP>Tn{p:IGNMP>9`Nx[.0l~dRK<5Sx!Qȣ؝)$[{ ̊`R)K s Mi]r %m6^o^!nVfލiT"UiOz3"K'D*{+DFIL-zN,Ƅ` ?l|6+iucei]fl֫=AK&}Mv;/ ez<5cX!=ګ_m?O!R40v q.J2NJ™vVh2~vOE)IdbwA)S>Q&Ֆˆ  jءKXzO n) IP H*yD:G'FR8FnmH2M:NID/BlQ%c@E& $ jЩhUQݐP=ᣑ2RnVeS4{.cQ'v/׌p, S{NDw~YvYӣ;6-3k&B3o"C]cǘ &XQRH?>e}8}:CPG'X9w^nouf{hc}7&rs>" XĖc-N 0پ09 M1s=|/xpC(H;LgXx*ù7x-кf]+`(t1%8kZ eN 57gvRYĝ$pZs:D'b4ͪ#nJT}ZThz<#5yj\LlWV&6E`$~EjYGB+T-Té }P|/`;Thr^Wj(@4RݘQI\z| oyyp`}NҦL !Tmj3YP@B*%8xR&xlRE22f.'9s{D_MpW7iݼtIrcm.I`D:Q'c6ɴ 2FEN ȑ0fHA(cjE?'$h?.՚۪sSɧ!XG)-ƃHo&-0ڱa<0 &fÙUaL3WWQavuGM * v{ ց?E& P*eKcuyVEN&e*r_43NWb~̲rd $s'f Y9ĤRsწ_60$$L,cEkcr-64"a)>T߄);ch]4J &7/eSfTcDX)J RBPQމH+ K0PéDS_+`q=U;A+a#Ce .{LNeҏTL1kw̞>+'d[} BS%~u-QTne\sHIqX&>s4J I LuO)񼌍)%BC$Ų%3ٮG&!|x̔[\`_2Ƒas!2>fJJ]O =7ĖO8HZL+aC~z_\ Vo  IDATeO8yh*kYYx7s7#cS)n9'յyQIYJJrpR7>TxvY6uޘu m<˚Q5 e bFChVSpTlQݬd f‘|uM!cKڪ`vo׮]@r2%eL) L>+c$bZc+ccoҕPIf68P8QY Gf+$TZ1.u*99WՑ[RFuJ!]޸Y "cQ9JG pհ%fxdM/R0+&-0C! ؕO>(̗lQxɸĆ :cmah1rrM\9\\W`!&Z'#Nq6U׾$YEIm& ׿NmڤemJnضy-aGEgcđ6_]ݹ-ocR??i].َ^_'U6jG)|><1R%?Lkp<A/{)A-cN0 |a&#̀vg'qtKG @OgWzkEOiĻRۥxu<:<){0R~`BސRD_xҊcp,k MND'ۑZl֡b CI} C&l@4 2Hot⃦~Y x͓C 8vBzz$atyO xpo޳dI S)s+ S׏:IWVznz~(ρӝ>}h1CioŋO>c?sNғK3.GdX# XiA=Zؗw#1u|~T!) g"Ѳ@ːwy^܇ƭ3.ʷ0zܔ#`3&}xG\dd<VH8/?3mͤP Dp7S#0G6gॐ%/aVLΕzR7B!)TM_=D/&37!ߏ'-ݡ.peAOs/` 1F 8xo-%V\7{R==&.p_K؉j1qcB ag2`ms/nv1hXpbm8oU@@Z9MTLȾpt!g65[A$x"C4{Bi/L}^TW<KZi RSuL2_%w)[_xfx-_‘4S#ulM7 F) BVGX*.&4@ҧ˳2@*@eiFH"H 9xB=Wa"S,=|^ SsJ 鶖8e?r4)gԋ}rS+Qr:SD^MNZ@/t_ƇUcb δχ9_b0v)%-qb H% DhE^x]lnF8⿨kty7Al-X IĒW_mN[p1*,{[$05S8+\JX=&F"#u,-T:N:38 pS}/Cs?}f|*&7>{ى3/ᖟ#_|V?v?;ڰ/a2g=hjΩp~2@Xݏ2cG\97B][ɏ)qG4.a>~|fzw`7OO#BAA~={*' xD*i۹tw ۍ{vzp/*v0k9ش%=cW0xth۷Կ m²#P1=>vKl;>?܇ "\xlޕyW=cT0e(xz3{㿞 %T'Wᶗmꑻ0ԋ1{q;i&#kR۾lGs눲~`/[~N8R="S砿+fdOtyN>=]]Xݘ؋Go2@z{q̬q_ 0qh?' fF_o7܊κ3plں7no6tv`QGQЈoo3v[s?u01&Nx0Y1y+s%^y=XW<)S?O= 4e{wcڌȉ z| ڹo'|:v؎zg0d( #0ruAlN^GM9+G>DZNB^ ^G.3?mz_cҜ9*=dTjp׾܉fd8 oZz ݪ*i$nI~ƭ/J,k'㤱U*ϭJ0QHbB?>w\.N!{5ಸ4AjgTރ=_'"\pQ|[Pc)G ׇuy8 $T{vח_PE%6RlPp*1$@d 1HH(7ax0H^wE6?"Kpp}+tzIBQ"OU$=TI,D HP#I\@ -0lzj>+B 1%p \U;͢Жf SVPCx_d$[B1=X)3w I"VBz"P'1 D#JgRKN'Rb{̻ vJ%"0o@Q$#itۜlAo=gKW ҕީ@r}{{^01Z@jL;i;fs!$a-ˆ#w7\AiǠZ2ݻ䙳T%"WoFX1]qtXjۻC=j-CwNGwp5;a&3Sĩ85LrnSН0MZOjN3O|'`qp F"бGMڔO fBec8{rXpTG)a~ƄV {c:Fc"9jԃ{%Y#e4s#ؕzO*`(BGqd61Ķ),Q&R`aX҈(G*zdeΑEǒ?-)dK Ч2Vn⧩6gK( PMnʙ1>ʐK%RF(;hPpb Up /1NlZ,}U#wc!Iݝ<1Ӎoqͣ9EI"VvGD`H"0I}݉wpW7{noj;?jﰋ΍;77; ]c.N8%SH2d%MR 夬ΉF[Uza9Ii ?^\_eU5z:of-pu?DOwZ񧜏`)M`G 8\#ƨ_ݲamwܓxn}w<[tuQghNǷm|-ȷukBcː`'ТO z9TTV"WJlXwԪ;n_;{Pd׷,7QQY >O֦U/Uը†k_em2@?ݨkhSf96-tw]hjq 0؆'lۼ!zeEk#{UU@eu|Y,c[ݍ/2&͞fHػc;X*?6k~ ٧Uصmu󨬪BUU5*U;w^)ڏK7OFc!C|lz̀٬繹 Gjk A2m7_\N :s>{}*ݩ6{@ꋙ׾>$O9@h6hZ|{cPPYUޮ.wyؿ=`uLBuQ$փw]p{G6.QoR f=G6A[%Fuɍyde,ɨ23&Lc *^w,z<~7X=X >Qy]Bu?}:204ecݏDyJsY~Sh!jc Ij|}QEGJ/0f(~aF K?M~Es])ilS[% O $uZFW% 7Z`^ xejɕT]![~Z.KͅϷqP}+ eJ¯I0C7 qV((S!c̽߀A:j,^]GOF;mnKUT(K@gGxiፗW>?WWyg(|OW=~LOc[|E ^|-g` x~CXtgC1oa8c.֭~[1a&<1sDF"†ֳՁgm{ j®-=wbޙ穵i5v1i#4>b Wc}T{>ˡ"y30e@s aQ~>auGGذGgq19T}x&¢q?}*ƖZcG;ctߔ7ZyWbb X4N1F WژOG5%țWqʷ5ؼa\**teJE Qgb%lDN_]@J RUj}$ ~N11xm̂UW gᑣ;+[:=76_^-)c(c瞌Ϻ Q1g}ϱᶟ5u~Xx U^e,,/oocKoFm}#&M?sO>W ,9 X&y+{'c³1{YTK v3z#7{ 3.+xྛpfFmm]PM.AMx@eEGpu ͸K1N=2{qO!LyNw7Hhg*̘6{w]#|¨3x%~#F0SHK.CpU Ib3ݍ+*Ӳn}= pѴ ߥNѫ+8w{ \KhQu? *p3G ¢q>w\q^jQ~&oI<^batW/.W%p9NT1}10e)#2DY~ͪc֣/4z*ᔝm 1YSwu\AۯPwy큂 B2)+PV=נYS$kl$^J=̍pPU=U B_/s+'tc2r G`)qѕ.t܏Ɩ4`^tuDgfAAX!.YܞC 71EG~7 Wg 5.x_:ؤwK:l;ք68A`FRLKJL???D}t<߹'z1r>A]cb itI|7cA]c\qwPHFܓGCC+T׆ȤƮ}"R`H*CKTE1h߆ƆfDA@+ Zzp|HKtL~R2ׇ&0 DVQ]5Z{y˼A@OgA ~te5Ui*1jP]QhL4ɵ{:%j+U(ͯ¤>Foh&1co'aX=5fd ]}qm?$3w{:A\$"UZj()gHœ90u+Zq<(G`j WM0/V ='_o= r2QO B""eдg@JdA"$T_'u WBl 2F+}IW a*&Tu''{Pk!(17B (i|)z݉s㟴Kmm̶axۍ= D2Fu$T`7RV 2c đ!EBY:4A;%=" _z@@sJX]Ɛ٤u 4E0״ٱAU?FJr.C}*8ɓd̢qD>7)~ [_w*=pgҞۥȮà@Y{g+N3}b&+@KH Ҧ՚W#" T*$@G2 |P9q8}ܒ:!4-Z+9r@Ee@DRc2)#@+^nFMƯ=@ H1H%c RRާY{Xcb\6!|!;i Mr{̷191iW7-5>mP} HIܤ (wf@L DD4S >Xt֓œv0MY GQ;$pBpULO)/ [hլDgHss9ШC&;f1t놛4m$ޓ谻Ӯ6p$iО gQ t֕FBeR(% $y~GSK,g@~=s}IJ6#FW}lp]$``#IN'a-JD9Y@#ℌ^]T=M@H#(V8w[PS׀zMNn=!/N|SX3D2AH}p!XE~]wP)c% -`l9K*gku(`ɐESb }g @Hp$K9B^kpG괜IPiYHDRx~y&f}uBxS2:V~0Vav~fl6fx5L)%`ӻ̏~CBKi,="OrMe`Kpx&wUXPKTX)9`h1KHD @!rީ{fM %E)M %;ē'uԗ-= LHJdLʀ1Dh4:c-,h4GF Vtd,"90*"RA3Y qXۙH !$(y 0|,fy}HDyVB &%bq%H SGC=fO5GR39cpeW`hpǟ[fPwyKn8srJfr|A"/L!y0{VM|.KoXYsƬvʜEN5جW&N[L 01lxĞt%e.&Ry2qmegpHBD"[q-sH&1JI:e*3:|c`0(W"'*0>PC,H+H\G$ #9m~ 7\T;x8}ɜR DZRLL+ mUT%y*\A=dnxߓSGh+W?Xv|A}ONqm'i{,"OơRICn 5ǑT.RhБoodr_pg! 7B IDAT2"j]؞ŔL ZSX yHm6}L|pdY2#m|A2sLc0$gH⏩~k$xϒ+X k܆+HO>5kS:'p|ٝ+}B7_SzIB!}J?h@-Tm+]hsk)&ujntEBF3"~%"# .&%om VpH k iaŶH՚Al燍jbO@U0* N`=kȃ3=2^doôVf.aEb3HWT79`4[5kP ( {^5" cje_hլ} CKj](L!H*\D*1*bP9C $2 D5xRf$ݬl {R fyw[/}.&*8*B8a3 Se6Y^i~Y=2&@0LBZD'yEEdPgy ^~p{ 5}k-'&8#WX˷TzJ}JNYAWʭJBi* KB@݄[ƽ_B"̚VV3FzUp6J=)0E_Ӑ C@@Y] D`@KrЙD$ BU,oC\]V5N,6i#E"H X#yizUVe 2zhWL%%{>UXKJnԒu;\?Ӷy{#Á3LlΙ|̂Bs(4{6]:s/ ?Qve@rDӌ^|]a<]H| xB-s8N* d> +u)Pd^iYId\LЧj[uZ+z֍aW$PRRRLP_@ TSRm86{ybVi F&6V+ۀQ62,jeZFzy\G/[}{wVP/'gR%|M8C +B)k̢Qp]<5!1KϮ (Іv! :UDt*Yc$E&k1cxX+Eէye'8kRzg#%w@]n" |XDmڦt2/HM"za#vz9}6(u c+?M3e. }EN24sL<#^槈 둒c6W 05dh)mAdt4lq_2BΦW!qPVj nfe7#uC=ݙs5'xlVWs$E\˹ynuJR储; 4MC8VVrH KrUoCSR;({a-΃L8 RBQE&@PծTA=vMWq^+[<%X4OSpڞ!g+D$pETA[*݂S&) $TAR}D%ߩ TUPz9%D0/ex2#FXޡ$(J3462s}ZCf3S¼:utFP]ye{4@]"v2 g@@W^/3tguEf$'e{n~F8I5jM"l*QX9e LaƓ:秶HȱˀWK TGe<HHFNn 'ԃàA۱}M~LQ=b:9AF @ 93c6;gN*aBBku*9MgG,!\ #pcTkXE'}#](IY7P[ A$m$O*Hw5DyujӜ=h3`jT}.A\qVئ:Htigh_+v4؇U:@N8]T(L"E3U %:Pb5BcIP@ry"d{i!BjYF;j} J|Z1(x/r^!  ڲ1msi,<yF~_ggh I(j J 3nkڬW'p[G4\%'ۥW:HH&$JZƵoS:z2kǃR+iadu }*yGZc<DayEuSg~<^&?@/KZ}M,{9(,–d oFfۅڞ/)p%&bg s+x/|팪u}T~Weԥ 6F0]A VwB"v&KeӖLKAB&[ O˟VґI˶s(>=.h)䉪$ӫA?liuphd2_mߡQ}үZV Qs( Nj)vﭪL܌v+ΟWOT6޶d1 IU3" )g޾ A.'r!fuxkJ||̅71(} j &z<,th]J$?b<Ǩ/eQ7A۽6-aQ7Eҡ$# \tn Wl!53B^(5D3W@fdJ@eUj]95sΏ_/Έskhwr;9)6m}'`~J*4ϋrPfC[ &mhv_=襠;ؙRRw4UH/1W\;Lصv7d52KaUs6ϵ\ t tsXs=و%p:28ahD܅R{W0F^$ ?f(W ZAM3-!d2]!5Bu]:HfHajﻧ$GQd FQF' #+&1Vl{XOEy`6Ch=OT@+X$Z*k䡇Ynalmx-K}Uyr"Uʹn*Z#R绒lFzy?fϣ"Viۊ}=]yXT?Hc/eӊd}/y+eӉ?MeI M!R,r$'g@n} nBotMr'R!$u J yB@^ᚺp=Pn 3F֋1N΄.jo* (޵9};J@q%nL6˅LDZI깺gUHrgj!Xf ٗf,}/7f\432+em \!@t# z^8Ҹ,GWVpю,q.9en{Ki'\DbhSwK|&0+VCpC15V^vHFg rI4BVebzɆmg[A V$Th(*̊ 3F' >HY\ 9v-dD\3ZGFYȃ2 o[]Vm\_hCQ$$erd+r^z9+hamJ*ԟEqԟ{RNW w5J3ɘ@uXΔJΫ!>060ronC껫Cy=*@GQULLu٣@D*/_Dxo"Cͪ5Mi ukdvestp|7566bUE@#@ޢԝ#Q[|z dyJ{.o`xmN_ebfFc _V5?͞'͜Es'1A0i_$OtjfpąD|@1,҆KtDR~j]8ʎ5TRZ9e$/OyjOSo B[eAO=ZqdH{jԵPFp 0JO'^R +Ugvr&F[v$)Kp O1!eg'WEy;@K,,_4 nБ<ӞIBT)"0'rZ]ͤ1C]M˟ \FnY)T e9iI >ŽUaA Pƹ5QDR2zWͳ|AkgqnWo+V]#Oܵ}!`\n}ծc 5 KM+grseF wNWV#ý:Nx;*F#g S5U6t}%m0@l0OQ}GA2ɯl71/Aܡvm`l!Q&J*Z+DV/#-k ώ~'„Pc99anRJ 0^u^X_Ce 8Pp)׮^?&VA\` #wUSĮ"P'dj^i#AH2H%JP=!UT wcfxcB l R7OQlDGmr!D~>Cŷbu+5g6T@d3SP}(!PZQo\ɢ ! K;{A.QX^ 6lWIU$vdKI +i"ӞWC0(0ZtەtwFWU=:<= ѧ-NhXN9LxpyXXj.Ơݩ\ -۶䧛UNH7j ! rpHN!*J:ZOͧ2i 'V:HdH MHvJHD-DH6iC<80s 4d&K!t$es4EߎsSu#cvwŋlř<ɑP7AjFc"alS%Pe%;zqmAİ{KwQ6i\1]5TUH&scدsy,cr!.Z'q7uЩ @~+Cq}Fq+\,b8BNh͐mOE=PJ+?wf(QG&ڇzhKf^ȸEx.hwXG\+Dhc6c:5U?J@*(ȉgᨰo9Nȓ Meײc{GYTG"I#WTT)f*o)Bvq0M= 25ФW}aH_ۧi̫m)=cIRKDy5-@܈+?Ik? v +Xdj%ʤ^wEOLr&1zWG]YwnNmwP-4Yg먍ڳH@1큻c9ʱ`ORt$="<ːdzoXz4rx{( I”,r#wpavhCW X˨Ʉ"vӴYkÔlSPS"TPBH$i\8Fɕ,E LH,?&7k= PA^#pKz\ɥc+ G5m`_Q~ IDAT) *(phDd|/QC,S)<=yN,3v$@rw7^>?Z:zLsVk c=/ ~_8}!mM_D)MV{϶ VMɭRkݑKwժDaS|xh^ fZWH JOT&/lUێՐaWj`Ȅ:c[a k+WVύN4&B&# P7(+,I`O(҅/a(Dskͺ{q,jZq&Bd^uzza8[# -"Hƽq)sǽn<+M4!µH؊-<ӮTN8WH)z5Sg&0tֿ6&(Fc(++\F%'˼@' J9%iki*-W1[ !rΑ~BSP=qQpExjq)R 7's*z@>MhUYId{-R"u]Xo3-(esbJA-?D|4!թ@֭]>-nf *+پ u{\yȈ4[@Wv|ȏ'.Pb.a~nUJ&< >Go(&<#A`uH !!ީ>/UuF8&Byiń NnnLڦGvT1TU`9/8οzZGy_?ֲO‹.DzO9[e|ܜs%:зaAUuVvnA2˵kg6KAͥ擷a@P{pDY0ҵh A(;g_ ,fxk!;+I+7T#ښu:S TVU=&9l_SBsY xW)D ! t-3%Lߎ`uD+T:7Waerf*K.L(wb!nL嫩}`gsp-2bm9IeCIc[q~qQFF+[r|vRVj\"g4UIH1!sH!V|떅By%H)Lз.ap2PZ 7m)Svo "nNU3}ؖ)mZ\?BFџ I @0㾯RaN4΀մ0 H$ r /i ZQrC]RTOkUt}*{1ʂw$`63h&_ !@-€;fa[Lka kKb⤒1ֻ@/}$OƔer#tkPP+*pά[ڲʼ6u Ìvυ2 'y#T@ݙG L :֡ ]¶.A3:=_i,ywH'e{H~4^nr\FI9d>Ƌl8 )(#5p:mY1.sH+1KUuk L:YN%Q3Iͣ H7RDHRBIZճ$Պ^}PC@\S ͓ ڮqD.'C2~Z#)u>7tE)dur'kĊBFZ7 H+ǩX+gMu?ж'UfM٦|P}Ni)*=g´O tA۳:MˮP3yQ jf. ~ldd<[h3K9ZTXQ2'}s7erxmcn=S;琦F1W=]p~y S1dPf-˜GFI2r.y8A`2K*n{WOB:-0HT@IXfZfRxu 1˚q 3`ruN&eIZNM)#? B?FS, m% +\܎tVPz2 OJ(MUTԸJWls2^AwHmH K/8<*WD4H$[ae9fEEI@.$xɊ6E\meG@#AdZb=M#.u7bu,⮮;Zݱiz]Wչguް1\1kCw"䰔Fsfd^rNUp _3Ƣnd.dșӖj!.,W_#D+}B<$?Ϩ JƸrS]{WDr[3( v.</*J{uB].9YA{ o|+aJp0_Q@aL}[;<]w![?A Hʅk&odٙ3*`%tZȪb`yEj'/^[[bK x}; r9c4_kgוn JAiN<2` 5Yi-<-ї[u8#st[iu5t"ﵣOժ z* WСv<ݮ.3?:9ٯr' e4EGY>Ug\Mn؝/:pg{n"3GD@]av#H/HNƕ!T oRyR $OǸagbOn\\[@9uDj̈։>up,1oWIS&rWѦGɠTv7H" O$gII:g.L]!F))&$vU; DyJ"&* M$I$`H#}f['lnKF̙&f\b]qk՟2-X~`J1 LzY3@WM ;'ǫ931N8|2b:8H aB*(~!$T&T2J l ta;<+1k(%Lڐ)z$܃@R~MR @ a^ZNq`~g1EJaFP?)HD)+<&td ${+s: t8Q%' W#"\YoW|{ 9 +¯j}Wk@V 6"HNGÐ+Bz E~hwAZy©[[@!6) PmjV mp;DzV@"erk* :e{;4pqv\ƄNjҌD!Nvݞ*>IJ"h 9l3MOH/)gJrtvxji#T̬g#|ov&r> DсNvʄpa8S6YEudOnj$K[*ڲOyJd6B-Q 3p:+3+#4=9%2P(^&^2Y5G}ܼ)խUXWXYo?Z+RXW]D"rT$ʴpQuP[J@>Ա]&ZՍ4vZ*[mX iauԃ:ݭar[S Ÿ%N+tLXJpʧ/J™QBꂵZ/#W@*O31 hu>&j_) :Jtua :vvܰ">v E cPĬ &9(7'gO Tkpq+&2L߄sr0WV{1"QnFV#'K\vP韙Z͔U22M:ei80<o?x^@-鷛؍^٘dÆ}HzG‰9Hw Np XޙAR)Az$r,n^@*\٧"8WåzRi (!&WZ٦w l2z~ߨW4LSz/;CEh'wo x& E~㯢븲ܪkdi^xcRBnH{T;_חlksH3= aY^Cn_yJʶc92<񁸙? Y+Wwt%z_O*krl'ː}z%No t⶞L ,܉/' as킁F7ED3R;LQL:8Y󬫾kzdiT&[n}3ZS'Iy2LǸOKdPk[csji:wU)pق]Ar[׳ϼHmE͍,mvE ߱">ˎal }JZ>Xfi5+"cSCӯƀv*sG3]V-WL2%ǔ%$kP^k%*eBAoR4BU v[@_W*EJ*I7v4 R5N$ !Otq; ۀ뫙TBӅtqYeO >Uq)pߵ4r2/IsԜR[}5bC>Opcag.O2IUy^jjհN9[N]~c \Q嶑e)fn/$j|/&Hy"ekBTȢ -2ȗG03a tz5~Dyp$2 eʥlȒN:96uV[$R@^eVy4dq>v[bkp~; OHkCB07SȦ b.g)|aBz$RfRv(Ik`2rU)[$ ÖIOH/i<^trp#C90ciF^^nXGfiF[s(w@+t$)nʊPT:u^"vԉKn`a,WQI#ܳ}Q*GM3%@SRt{9vZ"Fm{BX Ţ7fܫСL ML| o훙~ dJGxD&H)(2Ep@{0bWL=:U}m =ϲFcC+a@!&@g¤k> -y?EHkH`8EO10%rVgl yoԙ@' ߕA@H(:5g׻d;;ɚgDnV0k3eGra6B;l¹؝P`aLMM^9'7BL:5V$:˧\ oc6M @mKHA$%U9K=B T!d|.n\ -H7kToQ$#T!T@"BH|P`3ǪB"!PMjV9q2r/[qjN_;ga<㹺& dK f1n~Bd ̣Ev0R/ksϘ9UNS fn+@np^!&2uJЍ*]-CN%!Pt/ҔsmiVNy1"Hm}ZZIF:, U]п5#uܠ:F§P4Y7 P%RlG²=7+Y] zTFf@b*ekON .RIf4Y˦NGA6' k+ r0ȫdRH Dj"*Im @UMREIm.Bޠ&=NR"I^k>!!jI%aUH3SL`@u!H6j_:J61_O2[)Bc\RQm)ǫ_PnuwzB: Z*.`c&>bC- &^M-ꂦR'ODL:Ho΀:$ݭ(I@30pyݷѪ+nI`#)fuC:`4A^ߡp@~M {TCG{Gh" W ϐi AG<+'fJp7`rwi' E,2|nK2@= $WCLobbEP!hG',jr:TZ,{w.E$Q8:$HdoMޒ?.5FPM?Oktlv*z oKSc-?!DˡMNUg *j '.b1lnڙDg'`BOEm/2jG:IѺNniwOưaKmn_ /fEc ?g)yʞM 򴇳ݼ\*C8"@,To IDATQpH+-]q/ҭVЛO:,_Ҍ-E`0 }bߏ6lwZ, غWeiZ_X#GW^ `0 <|M<޼sb9`0 Ern)y'pdzPg0 c+W^8|Isiyr`0zx@ZqF `le 5q`0|`0 `0]r?e`0 F`0r\[ `0 `t_㼂`0 Fwk`0/Jqރ`0 `0 F `0 `t4ic0 5`0e83 `0  :`0 Fk`0/8`0 `0@g0 `0  t`0 `0@g0 `0  @^g0 !@f0 =PFn{зy,ZGq s7C>}0r&`0 VD8}…3gV\]vx N?tѣIۦK.C=a0 F M[SNŏcL6 'vXz5Ǝ#G⡇lr ȅE /#яve7o_bܸqJKGCss3: _lr6e0 3 =8ƌ &`ѢE/~xW} ӟ:;7~N裏Gq{97W\{]tQF7 nK_:mr $ĉ! :$`t{#xg׿˗/~N8Æ ]d /2=X|#=܃=z#HC?hmm9眃'x> ^y 6 cǎ{`0 lB^xqeEÜr)=z4-[gyvfϞ_cƌIꫯ3!b=={61h x≹yŋnaȐ!83̙9s+MMM8묳лwC-:#֯_;~8;3fի^{E^<q3f p2z7Z 'QTwyӧO})Ӧ 1o|8+`00ydG&҃϶)O=T@3f( {4p@Wbd'l'?ێ>PR$Iڵks!$>} BV""s՝_AԱzĤI]s5DD4a@W]uU4ĉ {$I[ni(\YN#qF/kF3Ϭ~j)`ter<ӥ]'M뮻'|2^~eKhiiQpun0aoF7>)]---x1g ^zj\}5j|Ix-*`0щ577@teEUVnM7݄w{/o裏ʕ+b 7n~[o5i\wu;կ~---hmmų>}cXh~_n6L6 p7#MSxu症tht1i$T*|S ~qM7!MS/ދ/}K?^z /2y 2ǏoQWzzQ~ڔ`0$ J5AD׿v {ӧc}lذ~M7݄wpq0zJ\tEu]^{k<`0E%ewGիW p@q*J|s矏N8__gׯW^馛гgO|[2k׮)ӧ6z䗇z=?0N;4ξ.3? Wcmf:^{-vuW/)^^`0 (# ˱vZ5 ;SnwYgʕ+҂|#}\cǎ>{{РAuuH `Ԃ , 8mmmx?bڵk܌Gxꩧgɒ%8#j-\5kva5k7nX:ګG\ps=,.MS,XsLfoѣh"7tzFz`C:S@<ŋzj> Ԕ{hJ.e `l R:Nj=>Y7;x/F)Nɓ'駟ƫ u]w:ڣGK.7~Xpa_&L_;,X{^!ftI=* zꅖL0CżyгgOG wT~y:-@N|x≙/q->K,Ʉmmm矏N; _Kw2o `0,T{>f¼yw .=܃vsO;?׿pjַ6 `laP'~1`X|99l޼{ֆ pqeٳ'N=T̟??s3<N8g!ҥKH5vѫo=^:ڋO~0}/>w}7$駟'xLh׿ߏfϞ텝_~9  +;3f>(F? Ye)`lKNnmĉ8{N:$9---뮻܌wuWn 6 g}6.2{x'1qDTUr-ѣo߾x|F‹/ofomwc=UUV/ƀJ症zh/N͛7Dg?YDŽ pyk}݇ѣG><@s=[㢋.9r-Wf3gc=VP ym`0 ͛G=Z3yv_>~xL0'7zI't͟??+ΣAQ߾}iرd@'|rMtؑ#Gz:,@k֬WU@Ї `0aW϶49e]F}dxϞ=K.V/ѣ ȑ#)IV34ϙ30JFMK.ѣGS$j*""ڼy37yu症zh1tP@=c`0 FA`0 6}z1_dž9`0`0 Fw~gÜ`0 6 f Ü`0ml3 ]y`0n 6 &>tJwc#:`0Φ`0 Fm, :(#y`0rVIF8`0@g0 ہB|`0t `tHsNsABd0 %Pt`0 [;Q*]l3 ѭ!Wy`08YM7GÑz%\g`0 FAm+ `t+WNڵkov6]\/ F1x&]ץ0 \Ap=R.Wi4M1`3{#C|̝;Cs?><7//4ha̙|һꪫ;tyQeKgE{i*}۩xFYxqA+M6AիW{o|mW[DƁCbWN#1t[eln^Ag0 ΀={6?p3gl2nvvUVq#Ms2mK-1{l̚5 V? bMm@g0 @[[[o{.?|L3 3<'xgq ;oOk_v}w >O?t)5LtIL'|2|I<ÇZ~᷿maySa]TuQ4c(7j1ZTge3.R|&܃>OӹvZ??n#ĢELز<駟\/}駟KOĀpכx57|SL`}6mN=T.?S㦞l]@ߴi65`0 FG"ߜs@ܖ17l؀?3f ƍI&՜0p[oam }R/]Ԅݴi-[f~/Y{SO== W^yg}6.ҥK1qD/M0m4 >W_}56mڄ1ck_.\vW_}u&5k[d /cʔ)Xl |; x?W\q/^\^^˗/ѣq'D=qwgϞ{pqǙ8˖->9|̙31k,0aߏksQ[oiӦW^X`8ꨣp뭷pӧOϸɻ;cɒ%}Oe]pWs=ꭷԩSѯ_?̝;/Fo}[1cJ 5Gѷo_{O?@q?,ʧ]Q]l۞[ͣy_]ve&OLo&4~xk{Wo ?Oh4osL󼵵& `ɓi4{bz',?,\E?=s-^Gkp9ND4e:37""z滋sRR7x~Lz FDD=555Ѻu눈h…${͛GC !";j[l==?hw4Moc9~mziŊ& 7p:=DDqFկ~EzYf͛i5ˋ뮣>nnn&BDD;#^ڋs7o~?Ç,C[[M6sQnܹ;6x}{_M]Qzq4c =z4{tg/Kjmm~Q婧Opi(G(ߢv*yc"~zիiڋVZsΥwѼVظq#O}S/~|=X3fLͺ;w.ӼKiԨQox>}zt(EE|0WʎW=ɓKM[w:66l؀Sb}ѣo{ڵkqp G۷/ڼDVO O̥gŊhmm #%K%Kko< ( ɵoAzXp!;0@KK nf"\{лwo!CгgO,_<.rz:t(va=1bwMP˗/ѧO/LOQ\+V`7.cǎţ>}cx{ރ /W]uUazyql2{!CcժU?%G}4Me]rࢣ瞦 vi'"ף^z).z*5SO?JCEԉo薣(߲:oΘ1#+g 2F 2C A~ihwm7lڴ @{5`2o". ;#>`=yk.6.=+WĀjâ|e^_qV2xht9wRwpGda#8N}>}pG#駟z&ٳgcݺu̟?MMM8裱t ֯_o;`0 F-/RF1+ 7t&LW^yL9ꪫ+M)Sf󻥥ZtBBc}ѮX|/* 'D"޽{M7݄#FN>߸*O#}EeT1,GQET4hQ:=gwy3gbܸqi刡-ʣ+qXsqA s{YKK }Q<ø;dZ8ֆ?OQZ*,X!C#'  Wu\Ԣ`0Bt򝧷vN9p 1bFc⠃ԩSKvZ O>ꨣ;o߾Xr9*}:6o 4iƍ?{UuB $H -&fĊ"6 ~YYk袸ZE]Q, *DA@I#BHd&s~LrI޹RfGV2ss{Ϝ;kRRRبߴg4h:qDl۶ 'Nbڵkʠヺ:iΝիz/L:_pW#::ɓ.>eZZˤr(Nj>VgZ~o~|lذ3glmc`ҥ{ߏg}sBBB:tqE.3b.o7 Z3f 44vBii)JKKQPP_d29@RRR"7?nf"Y &`۶m0aB88$%%!&&عs|yS0[˞ڥ*f"""5RpBWE :7+W:]PhΜ9xƥwx'裏ׯ 4HLLDtt4Owxp9fYF5QFa1bЯ_?_$ॗ^ѣqeAcμm7g|?~<, ndggsEvv6!@BBfϞ0^ZZ.\#Go߾sX|ʙT,ZpO 됐׫n%hO^Kkv؁S駟UW]kPPP wmU" /1cTL&,ZÇ=ՊAףׯGii)~!44/2ƎvyTWWcҥHJJµ^l`֬Yr555x饗0i$\yXlBCCQSS`޼y 8p/N8j{:t(***ꫯbڴi8y$N8Iп_}~C׫LDDJFF&L%HI .Z^k Sj۹q3QUUvۜ?~~~uFyy[kf׫moՈvwzidBSS|ﯖƞl/?l6d2j-C]]]n5hO֋}pVgZG};vwܡZ1PkJi)1Who:#fW|mbZ Ǯ]VaÆaÆ P[[ ^۷C$ˋX}NЕb29f,9`ygxꩧz: Ur=E=;޵f!88Xq8 ѯQFF&B~d\j]tNN٦NAq@݃nO#$$Duew9S.Nk|r /Uuu5.\aj;=T(λM.QUUm$$$/i`ϒ%KtIitΝ;R1lݺURyB(>]jjj I` ==bڮ3?0Owj4ګ# e=pn5 ?DDDsN?o]ެ]4WTTb| /^M6ƈ#w8f۷ooѩ(WGڃ97ϠyvJpz~~~?~<ǏcϞ={1}t$&&[lAzz:"""0c ={p1E6oތd!==*[lW^<#0L;ɨuZYYY5jo1cbz]^^^aa!,XС\ÇGPPRSSnf C||<-[.̛7>(0m4kEll,^uyٳg{Ett4n6;MKk׮Err2W_}'O;w.ϟHi<tRk'g}SuzUK믿a?Qk_oWֱR>m?Y=wuסr 6mڤ_{JmNTZJQ-mT[K=^j,$%%!>>#8tF:6g̅-]zollĺuk.|hhh@ff&RRRtRTTTছn´iӰ{nFy:ɄB37|3ϟ}! K.9s7oQXXxw7ntxVm.**C '''O?k׮ /FL&Z 8pS=܍> ylذVѣG333={`ر贯%-ZRTz]^RuWmۆ͛7cƍx+`.U{R\u,P̧~#ICVjOYFۺmkVk{}3>xqTK[+СC5k:{YYYoڥ4͹Ď~VX!XBqQ9Pl?.-?mĶ?8-~>tF|l㕕Ot:3!ؾ} VU!k&&L CqYc1l0!+DcchllbB!>3BQTT$rssbDCCCmcPK{"--M^{M\~B!~'1j(͛7d!yyybȐ!ngܹsbB![o%mg.L|7l6v1o,X O.>x`+rrr^ϟ`0Fj-|M1i$o/_.CCC۴i1bD|PKZӱ3jSk'~.^Wry@$$$ѣŎ;mPk/vU˾iߴJ_[Ӫm6{jmufQ+J-V㨖VowqT+߻+&N(?Y8;XBӜЭ_Qv?zН<}' B@@ ~Oh)**BZZސ!C2􊋋sNDEE9^VVCakqqqCssb|1]XX1cȯaŊNBvO=,, _5͛R 8invl۶ 3g΄/>,Yv1111l0w___X,z1!!!lbAKlGuc޽{uxG 뮻ڗQ7Ionذk׮UWTz`P(bԩ ĩjәSk|\pjs2335O+w{ZuT*ߑ#G˄ 'k]h!"""fx]3;&L@RRÇH:tHٳB\\߄cҤIN<~!44ǎ=}4kcPK{„ 8xmIIoN簲qee$]k~>c,_Μ9i$aɒ%8s !3313%%%Peeek2hm̘1۷OSO<֭[u%:oRTz]^Rk+;v,}Ys==jqjVZrՏ&xJ5>{Z)W>יGW:;xT便Nzmz AAAݔ#QinnFmm-^yNIx-ރr\jK7+4i-["#33 Bxxxy9rڵkOc޽kpB`xa00uT~~~1(=i$w}(..F~fq #::ZBjh=y$pWVZ.>YYYX~=f͚L466jřj|:u*>7*ئLG}HHHʕ+59d 2!&-}I^IoΚ5b uVǻwZXp!{=^Jqc|:#}G>4?7xb9:zo/\;xĉh"8q}ڵk5Uz{RRF̖G455a]v#֤+KKK… 1rH~~~n]JJ ^z%=]vz=/_QFa1bЯ_?|l+'$$`ǎtkM`0駟ƨQ7h #11ј>}2goΜ9O1~xX,r-FCCΝl$&&B̞= ;z'|>(|}}~AKlXt)fΜ IpˋOCk_ržhIOfTTJTN_iӦ>7ިZf^wyW]uxL0A1o:ҏёѾZuvQ*GGWZ[kv<㥚T,Z@jj{'Ch/pƍ1fo~)L&0iҤKN+## O AlrIu$Ig;䒿K;l6a?ȡfTWW;lgF}}}uuu+/+QKسgq޽[~󃯯j^ee%áө?d2I#rssqwBDDZˠ[aa!?.}~m*.>5=vRJիR]vF,wK).:ڏѾuhrtx8z}Pt- Ʉ`edd`XjZ/qoߍF#|}}4DDDH]9O:n4>xٳoЌFKyL]Ϟɂ_p59A-޽{ꫯ<ov3ZSFo*VJu}kGZ\Jh;+-Kjmniu-[ADDD^JϘKtuuuot( Et]&!!/b /uᦛnH;Oo/O% snt.NDDUt̷}{|c<<=>"5ZU$]Eg=tvNDDDZ NDD}.wnM8IKT]] ^)s=.܃.i!""~4~.aq|Ν:tm~a駟[d ܊uVy=wr;5VHLL쒴lٲG߶mr8%wԕV=oZ,i{: l=fB %$~ sn^NDD ] ,M~'ؾ}%xgaZ݊tW$Kk<=чk5atߊt"""o!_n;k.ΌKDݺ`{?\-[ ==1cΞ= صkkyyy1c^L>+,,Ă ;w.ϟHpuסr 6mڤGۼrrr0o<<裈´iPXXkxXV\Ç#((N@rr2v%ǫVjew7cߴiRRR ӟub/gEECMMM)Sɓ{7oFrr2v1577GXXl2=ł;v,>,&ZYYYHJJB||$ݶjrS*Z퓭c_g 0{l{hm݆r;cno)Z_R\U)N҂&~4]yѿ?9 D]yY+no̴Ų zR_*E^qpB<&U཭56?ngS%t=9?v*0_J,=M)kDDD^Aj_/I"&&9998y$Ə?Xv-^x466رcxᇑ"$''g;7OW>b/..7n|7((('e9RRRH b/g;v !!!ػw/zTTToo>`ҥf IDATbZz5:#G`Xx1=* : ._WjРGQQxiF0?3f?_ʽ|S%* nz$ NM!)t%Ms-,ӧ(++0aݡCqYO?QFm޼Y$'' !ؾ} V]^yyybȐ!B!rrrDhhݦMĈ#mӧUhWNN]`>}Enn8wؽ{Bz[o$!E ,֮]+n&!iii"==]曢\ꐧ}=Vjyl_N{;vA!طo$I֊w}W\qQ466R1|v1}g.|0͢V500Pモˤ&mY-w}WL8QHII䶰/rS+Z)c_grrr^ϟB`nҾ֞J}IZof!_| !C &&EEE52d"##QVV.uү_?Hݻ]PP,Km\6 6Laaa1oc,_Μ9/R 6 ˗/qWc۶mظq#M(*f%O]VV:4ijjj䟓'O:L[Yd Μ9oDFF@sT6oW8p?xii.-N}RQk]@II ]kׯ[D=3jhMKRYXZReeӲj ٶ7֝ckvcm_}s;{̲xz+f|zo=ʯì1gd\LfTYiTRc:\$\tLzM:o~cÆ 9s&`ҤIؼy|?dff& pDGGĉI]ZuI$%%+`U`6qiSg1uT/W_h ++ 'OpyŬTN6A߹s'࣏>I&Ǒ#Gk׮Ÿq]-`֬YìYvSkgRcĉضmN8łk*N[Wg-gӖ;uU]]o ƍC@@[eT۷S/ڵ 555iţ_*UkomrI^-! q@l=րMpOoFR\1*f۬!+nDI7VcQ:V`UqPv$ݻw.륫9DDD^6ò,A9达2e bccѫW/@ZZ.\#Gb?lAD$&&3QsAii)ƏT 2N/ 됐xSg1uT8qW]uk/ %bn-.11=| %%/Fd,Y˗/owoܹtHLLСCa61{lX]=8RSSh"b'wY2lxrz'|RGVPŬ֞j}n@NNCjhJeci`=\]j>.W06-* ƿ\ԗJ1j,h0 ŷDbb$P='10A” 7WKʕL F`b{keڴiغu0 sn)//OH/*vnp͛1n8ޘ˙L&ꫯ2220(*HdmsR˙tIٞ&I@K/uou=`ʔ)3gf555V]ZZUYYp9ܕqt5 刎nzss3]ցdBSS|?jmNf&I՗wdOixrUWqATUU9]2*Ŭ֞Ҳep7#11ѭxjmu4w8PUg#TuV ys3PdEc^8)0g|%Ns3Pc"ҍ2t$QWNwOy kYl}ӯ~{g;vȏAg4?vj,j(&=]GWSYmrlr.UR~~~nqn00eL/K-wJiqAwʨZ{:KN'/ʪՙ/ =ȹQyxwiAݒywNDDD|sy~?a<믿3DɄ=F;emx),X{/5ΧXzW#""#Zf@\ˏ^{gOcccSOurDQOFEE!==R֨ }N 6܀]qj+iw{DDDԎ<%Y7s"""or9t85'""fy yqU9'""EKN/?황\藀t"""/a\Wpo_|m8DDDOD$mvFtDDDDs%j3"""yvI-HBȃ$FDDDC˼Y#""uEfWq'""ny m/\|s"""׭t!Zݙ-Qh:s591'""݃M7755 F7]Cb^̋y1/y555be-'mm'"""Oeݭg'OܝAu^bU#""_ރNDD%.NDDDD-tDDDuq;.'""zuCDDDI$yrÁQ[pRNDD :s""_6=艈ɹ񛈈țh[QIq#+pNDDDDDD BDD 8^y/-8ϠyNЉ<'DDDDDDDY#""ǬIڵk5z9cǎPSS#>}?tL0N[oZ2͜9~;qdee9ĞoSL>#ٳ*~Ѵ]=ն3|K[tt4Ǝ v69++ ǎCDD`Ν0 ;v,oߎJDFFBR˧XO>bq~\\PZZ0.ñcpq\~8qbccQ^^Ç#)) gϖ:t(>lڴ W]u ** cƌcqܹv7n`رӟ_~yWU5t"""aK[566.\Ν;@777cϞ=2d} 8{,.sӧO˯={۶mjEii)0|p:CDD***:Vț}޽ػwAvxCCHܹSLόϘ1صkJKKQZZGGG `pǣl9s`08V`^G}}ZII JJJ1~xzllh*%""""""/g >aw;wb߾}hnnFMM >Cjd2aʔ)6mJJJ~z?~0`񈋋ѣGѧO#!!!I FEss3גp";;%%% j7o&{iu]F{ 7܀ #FNg ^Gqq1>,noݺK.ENN$IB\\ϟw>bߏ}111$ GAssnn.g111={6 ޹sPYYKP""""""z=rݻwjb֬YHII__Æ Æ wߡz۷o$IlV}|:y,1c 77.Q^^HKKdr(CHHNu!;;sΕ+++ɄXVLÕ~:{qDDDK8g%.AKDD󈈈ĝpNDDDDDD xS7xMDD4f+A%sDDD52 :=DDD^5=DDDDDDD^t"""""""`]Ӊgq&""^qA'""""""yxMDD佴aHMM_f)xMDD=Se﷾m<k OO'3ͨž}n:Z gFJJ񚈈zq>W$|Mt:~ y'8'DDصk~g̜9AAAZN+I$I!`֬YXz5㑚9@DD2.q>ȋ6 r%uVu@gɭ_|+QQQZ3Ƌck}P !k;>X4|6l;+NJH7qܳ?yf+{h+^9m1, Mu5aK#WK1eԃ91Y{(Cc 1[wO\u]m]v%|1`1^(|Uyh=ͣ3XVٳ0`I^/2IDAT';;;vPۏ蒟\[|[jl6C$aaaػwDDDΝqᅬ߈FSçN|Щc7MG_C\Qvhϭ'wO6/8㓼ZZpIb8oߎ:qX{#!!?z^0w7}+?5&+,魓62^g f+2w\@_,- g/43xlxV^rY-tޙm;q~?D^\,PXavݵ8t Ga#1XeQQی>°a*Js~<3c/*xxYd탢g+&;. &Ԁ8yނ/ _V"TcNݓm2 |W=,CT~teJuz\69P&5}&%׭263xnz~x` aho\{VׇAaܷ7al<؀a8^~{l/>ʹ scguYQB}1f{ A3PXi?~:1ϨRjVcc# ^N'W, -uCj<H<))51rHfokcu|tR0?+V7c[nE~~m[6z9n`Q퉈<[y\۷/L&V#obZ  A\KnGfA 7G@|Aデ VK'Tz͗1KNoFF$bPkvaD_^zH|>^a͑nq(ȱ@_RyC:QFܐq:<c|h;*-u,f{: xDԔp/CEƯTwJO|%b1*jqƂUoSa}|0stc}q!N":Ho)1/ QgڽVjK-jp x&ꑞ>*<@I"0.86i@t51xgv/ W__+=!f3BBB`4o[? Ky"lG3gf^'Oģ>*C_}VZ}iZ:ux TVV?F^o36 0 0 lUoס#rUU֬Y~oƶmW_!33K.Źs}v,XO<v܉'o8;W/H6o`6q!F@lCH % 4v iې;Ng 1\c;K éB26!$`oB+dKYrόЮGoy=y$ۡRvqYm۶A$ȑ#(((J“O>iӦᡇ¾}P\\.<(//g}ѣG^ؿ? VqAѻfUquNIf.'jچ^ܓ^CT$5H0}Ӎq zgZ̆Wn W_Ku0 g&||_1ή4BZBo;*LL֪ }뒞q_i:Xo^Q+UGj#?REZ>gOVGKU%buaEҟ }O~2}oY~6UsӚѰSnp${6~M[V' ycb0& s F$U16L Zlzr Vr SCϟ?#G'NmSq?-Ŧ6)yc"XNhTX[cv0 F5^mK>otK -ި/j8N\~6ߝ@p >˴rœhllO" |=|rp7`\.tvvF@DDD'x+V@ii)89s$IBSSߏcǎf3^ JL2E}c}`+ގ磴7nDzzޘ~HիWqffY|8~#? k]&|T·='oD- (h±oq[7tIIgTUV 8=WoFXh&IK^X~a =Ð?Z F| -=cH+?sbچ v 1J Ш'bm]bi]>ύqǿ´1t€v?1_Fa xs'J&b 8M7}@e/-W_'"ժƚʞdo=m4m9Xcb~GϺp΅&V`E9FԜw㻫=o>np XHAC96A^$Iw ii=/euuutL:Fuuuk׮|go~Qz>36fkq""IeNlٲ۶máCV鐞yˡV1sL9sF.KE8~q8b`ӦM˗nݺuȀ(xwpy%$+)z2`Iy&h` gjaQc~E0.<`e9kFkGh0aQIcPObNLx<~`]- Ѥ¯lW!ˮZ%dhbat!Jw .o Ih}TuiQ^3CO ZHmg֫.sMxn_+f[8ބ/rgNj&~>“o7cjZ3͍X:ˊc xѴ%8xf ~Xn ͈G݂6|cmZ'j֒d>DQ+ȿAKʱ`0 ##mmmf &L|޽$SN!cŊhhh}1fV ^N˅G bˁXTT˗駟Ɖ'%K_ǂ pwCEncؼy3^xL<wFwwwȸw7~AVVf͚s벳q)uYY^z%l޼k׮EBBZZZ`%JBMMTFfToŋoq\^ }DX+" =iGVGUۥA+@鿮G$hu0ܤ8hM;μN@vG#nON~lFۖ8.qLc6+=>$C~b=6Euuuػw/ʐ_Ş1c>,^$ ؾ}<ҥKxgЀBdeeW^A}}}:YըbXn`׮]XnΞ= rP$ ػw/.\cT8E#\TT\. 0fE=lقT\~VªUQ߹s'rJ%%% K.Eee%aٰuVL4 _bѢEx<غu+>`ҥ8wRRR0Lb:8.GG3 ?tۚCW Eֆ… X`l6 4MqimmZFbb{Gڊ>>C_>>nmmmxwR$&&k܎;!&qϸ L6܌[__?~ӧOǃ뛚˗/Cj $qvqR<K~u>\zx/p< jp (//h$f]qFbQUUxT@owۍ={@8X[#""M@bԨQ!˚zj̞=^۷oGaaa)9 IlĈQQ)qoVRj<6݂gbaj&DwZTIO: 7G8X,:t#Gs] lذ$iiiH:.L&_}-[ģ1QZؕA!++ eee[oV)Sp ##:],,,Daaa\x؈O>zǬY0jԨk$DDt;ʸe˖ŹƱQb=x(]233aZӧO̙38v^Ǔ jHJJBnn.&ML >|ȓɄNDD?~wZ\<)FnjŸqt: Qq2xlFBBf3f3t:-xMDDߝc8I∈HWz=THAB KDDcu'X)1AWF] \5DD7'8Y܉ :0A'""""""R&DDDDDDD H)t"""""""`NDDDDDDLЉ :0A'""""""R&DDDDDDD Sn4voU](N!8NDDtcJЧOÇ*FDDDѣG7ljnXPSŠz8s \ru$""(vd2ť   !"#$%&'()*+,Root Entryp256_78e68c5a2c1bdd6e*PPTRK'PTRK'׉PNG  IHDRx&sRGBgAMA aP+IDATx^]Wu.nӻJ2Yܰ nC $ ГPܰm,wٖejzsϞf 59gWgs<֯KiJ"p=`gch,\ p S͝;W,Y"FAysFT_H,9Z_R]  ;+[nYr$I)))+}}}o>/D=S>Mqg|'/\P։1PZ:K_ ۞p .|_ d޼y'YGFFdxxX eѢEಥӟlڴI|K0|%;;[Zٸqaԧ>.w/o1?.^z~y/rĄvmD/YYY򶷽 @KooZ.MM? 4 .p2L j9s(IFF~w+˗/*eh2K.۷˳>wS0 eu:;;-oy2.?rM7>9zLgWül/ ic\X' +$33Sۙ={2yNN+\pU}xb҇~ ;w/|sR`2b0뮻NqeP^#co(CxeÆ z~A,K}-Z 袋4?a244`H aۍZ\xu0exB@}YZIiooE]vChwjgyZG Șuuurh2q|+Vm6y[&[n{1 yf9vG`tU@c>5>FZZZt}TKEE45 .-L-r;5ҥKլ'=w^նSP^V&-W̝e }99 .pz8>2Mqqy9<߬Y%v&&':\ 3n.pzlܸY0!I$u[\p\O&a\H'9saCw||o?L!k/V!c#!KWG~)^&}B£Nn0l:*5+7N{~$yE\/m Gv,\I|NyG#EeUUl&(ΟūRgfK%rC2%/l֣!YU.󖭕/i<[6pYcо۷S.{{t6Zr%09?Yz["?܌6Ƿb><7bwD,*;(s,={"lH" ^^wf@Ǘ?v"-GKO> o~G}\;oC>&M{)(yLvƫnuWu,Y\v3L0:  W0#gv-~9_C?KfƩ,.j%_"1Se.{U/hN;Bz*` =|ûeltHS/ri:Gv4YJ6#[ YEd'C2ڔ>8zꄙy^KT8U 0^JIbӛw>&NLF$GaӜZmkdӛoZ񃟔Fh~Ӳd&ytcS1BaB'S$"峥Zy&G۟%`|?n 9}cI sj˂j`x&#;1]qFq+PyJ/.[EgRe!C/XFn_KSzlåz y' a#܇)E6_NY>^y?yz!yGк7pnu G!t^L$eLL] tڞT>DB2~LILq%TȤajrI];@*2>P||/X-=@ ,yp}qIueQ@W/$#2B:Sn`T\Nť% 4ղIC ^YڋBo ǯ>9Hg ` d=wk XI;6~ޗ0dMk|VK" b hǓ=CTT{qhƪ|$vix 룠!A@4X0eE?S\tKB0z0Od._" G03 E&{O?83St vPTC7CO'\G MphY_& #/}*{q1(:&u{[f ,cO3d25rT57i!BhV*" Ztu4b06-A £A|^7-@NI:8~%zhvԷ _ 6ދ6>$p.ZN倱qKk̇š9]?x'<1ZaCK;O-${xqD/pD%~4ȀGs'`:g*P³zK? T^ GהX!ܧlZJ#rKr.5ہ[=;:R$+H:[%+9ywJ^q-@>0 fU.rWʡ}Lh=Ԭ"{`dH͚-ZOˮnx<$KYBj=&민Qk2X >7RP^?>?(+> ÒΖ^)(%Q7ā[?,/,v 8GrJADgrb01y+HZ@^V\)+.Pfװgd` uKEri?vX ʪq.,Y2i2 i4 \0ke_+Xdy{@tQ}n;,Y'cC6#? hL ƍCȼk1о#26D#LOnʍR\9Sf/b !Gƚ@?(騸R+$ K74e1/%Z.3'}M g桢92*P (f/ZJB!E296(Y.|dBU@-5ȰT.Xq|l*:13T pYh5,&gdpKBxE>B1&s Ly0*:AR#9AhOSN1ss&y6'x1q002ZpX  "C ȣwy x>ኌqo \ԄŠ;f-(KQH߼ܪ zZ%v242_Vn! !Pe@Cŕ$x*:f%Ӗ_N^ZH2X4zHJBS*#Uh>h6ɀ5 "?LٜոA0wŢ1}) MϕZh* fZ/dAhm'cC y" Uk:h BecRPR*R~0d$*Qȟ f dbpDꗢ g>A;ܯhNapaOW+0<ԃUJks*x0;D>**[ nZ# JLXgحRX^-=Ǐɬ Tլ9@X'T`⏖qP u"(Ĺ6S2R'<FF%.F%B TꏃaSl8V#~C(z#bq5 ׆y:$hȴ Ik0qQ( BIH"Q%Ȯmj̇L4c1QzI6{i4qˌaG] m֯OzyA8:/`-T#t!lŒ _gIa\P DMD&?=E$GJ6

8{"bK}fj9ZF+E:>%b5=J$6R0 08A $up4a߶GO?p‚+CnEZPÁnԌkXRlPPnf?7A!Kͣ:$̝NJA(?Ni:"Lۈ?i1P/2r6b1rV(nɷtaqMw@>1kV$-tIF~P; QceLQ @]|1~TT.U-Zਚ ^"{T&o}NN^\|Wz9aP@xt6,^ 7\]Lc{7_-G(cPo>dD3>7H g(Cn].NW)['(ǷW#A5<4%'Zt47J#\|%l L Yzt%1JڎK\mT fۏJfϓ B%pV\!I|O'Ep-2t].ɟU/C|prU-^&muGԤimp6$٘wcp8t(x.Ypvn[K'x!}熏|WլNA1 lNRl ǁ |{٦_|W]7. fcHhc1GB˯e:N H*Uh"X0g{*gTz8UB+;;>kh00?='? `N.RFvd0}Ƶt;k0z BiyE@ Gm1>8 @~RjEk(ARK!sK5,;76'Gv9QVg&ޢ1)E2{.|_ [)1.AZ?~0$&#WS#=*d"pShqъ"()ai/AI]B-9 |0B&Y}0g1bF蒑q\\L#Ek.FF ]Մs}`bXK`Ӊ~7_vVD V>gCdA(zuW/]'m(C$r.psAvԬ٬'W:Aټ2}2q A:wZM]weX_UDEUʸj7ei2*?2"cptE̅t// 5¹#3NM Sښ.}~ix|\G#$ 27 @L0&i_V MP !u0Ze5`S+g$~#sP9wRGcCzKhV%-Ol6XxpAI2iUrUXː`x( ES`Ù-*/R b vО5ߤCBxކہZ0.:fBp. ^\6GotcY.`<ݳIn@v~Y~0S4K&G`b̓l eAcJ>#+/OrgJf~2g_Jr 7 0 >I4z8zi<L>KLK1GW.+!W oގ8vr"?簠BJI) Ǡ/ûjXtj0O}}L8&Aa;<59 PlCo`&A93G ^'A%C$ ku @층Ӊq5i:\2*mWoX^L܉cfyy 3.sZ-(LlBݐ|@a]]GEѠ$ '9kM8_=<ϊfLo*Evo{JH2BGZ<# ݮmzcOzd ł цOBWŽI$ `Vqq=4 `@<(XP +ZFY.nr '(zkH0`Fh2IL|0]iSC{FU?A]?UtuDQEk]k ~,M2~jRS=ThNVf Ue0eB쏣}>l|䢑 B0H%(ɡe0!\vLp薿gPIIc[6S MH]p͂O&ovF'}緻a8˥J3"7|T I{yHV]r*5[Кp؆{Y9_F}U =G&(=l,3 yنeAy)*'9/lR' ݸ@d.L."kцcR["R K ̔N%<G`LMeo@`vJ2#{6%E<.s} 9>Mvt&y+Pbc*8DL-`,I@3WFB"ȍ>~/ 1gp1`^30f*V`N7̣Ŝ,q,ӁE,"* D'0?+d .QqKQzɗ҂ϔjy\&ehaUK A3k [Ri*{3Iż%RYYr3*G(LJl:"#}!sÕ_RP,fAtJgK kwI Erl3\-Dߎ=:;= OƥI^'m-|9roG⓺8<8(:nnO J@_ipz*=2Ӄ_!ݣ0A$cIx:>o*Jh2)C#2a NkED)%Mp4 -*l-> kq SM\<D th֬ev5t0Y V vO!O>~}iȱ3SKAlS֣{%jyr-Ӿ 'a9aFSS/aI|k 16hrsGQPR&ٹf C8Kk1[jHٜϕ2]m73. JԂۛEڤjbԛKHKWvծ+,SnW^)E2zj̼<ʔ?SJʪ!D fInF4#ճJӱ:[#0X)Uч WUKˁR5{ AZ+\yX\rC^gQ)[Qw'$q ;ˆv1C [`bB"){r)IKg\LIjO]+4g=tٗ7ނf5ڏ8^pSqrئ18 c2s}ؼOOfi YbhlRD(x]# YJ )qHP-e-ҬZפ;a19c0c4qIdvsf)DnRy*"..h0߀9WBAA9k[>&3[F {+vB NU=a1 ~?BaU: O _[l n8N33s99y4W5m6 %S 2ubJM#ixmk0 q(2sKmJ3p0upKac@Jr`p?J{JQ>T:g7Jl\ }2]9>9iD=g20} `ġQL9N^?`斛a~hzdM^?DGbNd"Ss炥2^Y #G)D᠙YPVwqTX4t^R̯*N?MgϧC*Lϖf#GK0 Arix%ۅv f"cKSď9g!99anNЄ·Sff$8̆`k{ϼ3 >kU#Wyv oGc_X"C ueQzxX_vAil56)h)8Lc7Z 37IOUꀉDN./:b 3d0oߨ `M.mrOqGq BAJ9̹lu,)4SeNDZ(m˔"XK/B %:4='3v`|z3 44'b:L &Ne8˔օf aÐ5&[J_5O5GQģ =s턩B'5m`mӴKAa_;e Ck7*@Ծ4tǙ` i:.4ul$hZAhLyyXD9)MBk,L09*Sͮ3KO#Ac7ukAj>m7+j<u22?&9?kI7o&)`y /sEhgɠ۴AG䥖A F@"Ah]}` ZxDeMXZv59NtuTLD{J]`g0I+ ;&8hrLgdJC'Yę+a-8 $ޗFnk88Tkᇈ= Y(1l&8}@Nq>Ki5y,<:aZИ` 鬟| Ix@y`̍mϜXOATF8scT96cu0p8G1+&0 %%#>P(̟StVkH3 AQE*Gd`̼6_!Iu9YLLP`Z/2pΥc49Uq A(dy̙)N-c'‚R/ABUf\Vƽ5]+HrdFew mGو ךp*8)F7Ӊ gfD\18%C񔸷4ڜ:[<'@ˆ"7󎀓K1mds/ 1^pcM韎[OI;h>ܶwb~D3rpeL,^|'_~۷OehhH]zu]2::*+WǏKEEJYb9rDi"##C[jjjo}}Mgw)r7˃>mSp]]]7Y:$-R{ꩧ$ܹs%>υG3j _W䪫RDwxx?]addDnFy衇i_|E .%%%*~_ijv^޽[:;;/? -G `p}<[?tQ_|L~HKK2?’%KT0^ZsՁoΗw*n[!DFs8_,?[l۶Mrss% ɚ5k$HAA25qN O&ߴijyic:wy.@|r̙3G?S?˶nݪoEf7??_^z%υg53]ǵgΘ߅sO~&enY.\@@ oTJ f׿sMYyޖ g+G;w|qc_P M_drrRa(vGܢ^^4 W;yW3SS2,,,;Q\|柜y$o%R1A_0_ˏIOwW`xb2b@0 }}g}-,*vm}b9~ئVϓH4enVv˒w$x}qub g~嗥@KV?[6N∷@o=.|7Tp,QK;qcoofTds[IYb]{ HThW91fҎ1'Ra-\P5ͅsij%\>)Ӌv[u ,[[."w271I <߸qZJ~_@2 oҥUUU*(HK311um߾]嶏}LoD^~_*]M1誫BW=*7088(]t߿_i={\ 7-Oa6Hd"FXYVVj˖-|i H8~g9Kii?IEAyת0#0?z<1-R\{uo^ vle;K%qn8&!u[C7j,_ѓ-h((10#'RX-M!?K!qpN @zj_>q@'o}7B /W DLMHܓHd. gNqq)wQ ,$LKCn:P@b2/!sŋK45 vXdRƓ1(XCeU  j(28S=ZvܥmPP+N .2,xi%KddnV ɠ7-”mQϟ?_wMr^\yd۬}~ sα֮]m3x;G?.D =LDy^8ԚL8fAJ6Iu,^08 2>ͼZnְw } O3-~'eH:JVh5{`zZ 3>#' .LP e 2[~٬&Qڔ Vk`삯JcS;JBР(-+A6o, jMҠl}gEan|2PiWZt.[*{UM8jgyF(اTKM:kH~^|;v58]ZjpEl, 0cf\xO_4nX7-E㪚]_b?TG&[ۤ>]t8܉XiT_خZptnT @\"(mF1xq0-5\#` >e$8:ЯP o}ׯ_"7l |/Z2s$h28 clCC\`=wi9 >75\#wy2~]0d2hP4)ȴrY'Y7(xA}K)o.L^򗿬}B pN8~.牂\8V;>ߌd_xv)8wc1?U(S@02#A4풁(Ȭ F $ Z$KAEơ%@FXxǠCS))(XO6rL`>qlG'gB}g=Vs՟Bm>s*3%sr[K27ۦ@Vd_h5Qpкb`(l~s%)Dtr]8DTZɀ `ra,92-l̩t2J})%ڇ4^s?]9l2oISǓ ;.c*f-_B|6Xc 0A`+.D8x@oBȕf;چZ(}P>ON D 4j[2Vn](5ޟf>"w< MڌmҲe˔SR5)ƤS7eMvj}jAf.OmU;1RӈԺ?9/twegZYM|Z&u_'<Fkepey7*AiC8g鋊aW{FeÜꈨ$?./ O-T}(~vkp<<,}u^/Ȑg 43 rAվd6522QN |$n,(TF! _BwcdNnayO'۲iB@FF /(2x駵_d`8~p5㏫08N= twv9Z'//_wiy=1>1^vE121wЅ:5/ÇE #zϜn4mr}=z>OEuQPкFwWq</- "X/CPp=q-3\8!US8"Yt2C[Gd{Cn0‰wϔ˅ qkSLp!]ތD#s~bP}WP;.+w. ޝυt>bxԵ&Ɇw&+lN'ni;죡'y[6gۅsx`wq h{0Y"Sw*}|&;͸ݑ+)œ&"` 7(s4R1N= g &ޣ02$Oz\imi|H7aO:ѩHp}مshbGek9(|A#-ȅ ()1W PDW2e[2m@lq|чOYPP*' RKkhJ6?\8altL5@@kM5)D>|a m /7]C?̀8O .X;\H7 S  6^< Rn,@y!Y@kx S?|lϸ W&-.0<4t+kq>&IۀG0$cCò|0x!drn`{+ЅR׏9MA&Vxwy5 Sp >L.RqH+ wfP 6]5\8o /m}|g6px*R8=kwOcSK2а,X0V`Mطo~!;x<fYn @MMĈL\ tvvJ{{~@gsz^$ Scuɞsu2q]8eX݄FϬz/ q9g')5Hk;1ƥs KYYYRYUyR˓l==g~N )`>#p.eARQYG~^m< ^'[84exxH,/<2(. >˒t Ǖ nZ)#qH")1YXcѸ\4;$2\\5?$rI{rρQ)I]oT*sh7*Kg%H0:Ѿ<2'/>YUӏSev_J&cIH8Kd(0$+3勏Țʠ 'eVWdB>qilfPxH~+혓Ř'MHYO7䃶lew^ϗ6}WZRsKlܰQo5nx @ @whTWo>_׶JKK?~n_ޢ'~ J3<׳f?3gMM* MM) :(+˃.!e0~ @ <ȿ,( 5`nhu)ʛdb#j M$/E pi(b,ē%mUBp O&%})ɛ @ Pg!BLoMRv( ˥dglwAG0k2J{yY)Bz Hn+WcGADaud$i] 1B! c0؄|"< 9Ң[[[%kܩ)VXgGAJ9>O8Z{t(IHo__O]CJzP$pii3b!P(xO(30 X\ мlzeMd\]֭_/Y^ 6V% i5,[/p0>ZPlxEef~8%1ٿoZv\{ĀO?G pܬ_a(psuv߿o+WLܳ@2b+=hjlF~lkt 'r4Ԣ"Ld8  +HW]om . \Hcr" 6З,p( \k+\pp4WBڂ(-ʰӈIENDB`workbench-1.1.1/src/Resources/HelpFiles/Glossary/GIFTI/000077500000000000000000000000001255417355300225675ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Glossary/GIFTI/GIFTI.html000077500000000000000000000024601255417355300243240ustar00rootroot00000000000000 GIFTI file GIFTI
GIFTI is a file format for surface representations that is supported by many major brain mapping visualization platforms (http://www.nitric.org/projects/gifti).
workbench-1.1.1/src/Resources/HelpFiles/Glossary/Grayordinate/000077500000000000000000000000001255417355300243555ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Glossary/Grayordinate/Grayordinate.html000066400000000000000000000024231255417355300276740ustar00rootroot00000000000000 Grayordinate Grayordinate
Grayordinate is a brain gray matter location that is represented by either a surface vertex (node) or a volume voxel.  Grayordinates represent a subset of all brainordinates.


workbench-1.1.1/src/Resources/HelpFiles/Glossary/NIFTI/000077500000000000000000000000001255417355300225765ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Glossary/NIFTI/NIFTI.html000077500000000000000000000014721255417355300243440ustar00rootroot00000000000000 NIFTI file NIFTI
NIFTI (*.nii and *nii.gz) is standard file format for volumetric data that is widely used in neuroimaging.  The original NIFTI-1 file format was based on 16-bit signed integers and was limited to 32,767 in each dimension.  The NIFTI-2 format (http://www.nitric.org/forum/message.php?msg_id=3738) is based on 64-bit integers and can handle very large volumes and matrices.
workbench-1.1.1/src/Resources/HelpFiles/Glossary/Scene/000077500000000000000000000000001255417355300227625ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Glossary/Scene/Scene.html000077500000000000000000000016731255417355300247170ustar00rootroot00000000000000 Scene Scene
Scene refers to the current state (settings and files being visualized) within Workbench.  These settings can be saved globally, in a scene file, which allows the user to re-access the current Workbench session at a later time.  Several scenes can be saved within a scenes file. By reopening a saved scene file into Workbench, the user can "pick up where they left off." For more information on creating, using, and editing scenes, see Scenes Window.
workbench-1.1.1/src/Resources/HelpFiles/Glossary/Specification_File/000077500000000000000000000000001255417355300254445ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Glossary/Specification_File/Spec_File.png000066400000000000000000007570651255417355300300270ustar00rootroot00000000000000PNG  IHDRC}  ciCCPICC ProfileHPY_Das@ 20q`Beqր (5 X wEA9&Tnz_ wrDXTa8ۍ=<k7M wwшnqV4.Pq4n*g\8aЗ.B0M,1Op|HgdrH̼B@9DaƩfa?SϸeVxA(_nVjJrH)3FQoE)A ~0,dq ^' D>i }8A,oφF,-cOxY^+y@%0HA "=~M˻et l!Ԙaina?f;{ :]Y @ <"f3<@ЀPZ@ {i + (pA"HbցM `/(ap @38 .: #A8Q!%Hҁ K 9C? EAB:h TCeP%T BP?tƠ'a f0f~p(ë8Uq _o<(@(wT *6 P%*T= ՍQX4@>04].CC7;7C W 18`ؘH ǔ`1.mcvXl6 [=mcI39q\:.ww7}xK>/oƗkg) A@$ل#6 a(K#:CIMRb=D"iIKIR.tt4DH#1 vQr; BѥR)%c)['Q\Ij@4AZG%B:GD q GfLL̠̤,UB6P6UHVNNWS''wX0EբS-#. KӣiIB Z/mB^NZ>\~|9y Eץ)S;O ,xm  )*+(6(VPTJVڥԬHlT9Kr""EEN-U9ң2*RݯzIu\檖Gژ:UY]GsSza54iZ Z{:&յhӮӾCa$yUYwTOQWPZJրilp61L4,7a c퍅Uƃ&dIIɐ)tiK3mh]ff_mS̏?lfВkYnyˊbeѪꕵuA6T%6[m:lيmmb*4fycfG[tS88&;:.[a'M'SęEÅRU˕ZeJbgt3w5wwp_( ,|^3Ves5 _;~d2'b%%< 4@vGAzA~Y]|`u!Ԑ!!BBw> Q!4\y=J9J\lﲑ;Y~uVJ< Tq&qq\w> +oo,)8YSBq(߉?X8.p ^%$Jz|4y:%"!*& ;WZ_d$IV;޻zB'NҖӐ`ӓ]Psfy懬kddfo~ZZڎu6ZZ_cƼ#޹67%ouoDliS||qVǭG/wն۾ ~.]'laNN;\v+-)޽dwƞ=o{ĺ>⾌}RҖw\XvܭBb[]R=TxӏVzW6UVrXHV.n|槿+(B3y pHYs   IDATxw\{s"(vQD8EQb h,F4DlQ,.(,^rp\-?sw }qyxqNaDBBBBBBBoBtiEѿ[0AT*Mʐ h88 FV[#+Z AɲwҒdh4M&}%\[aX x<FPJɪx[R(qg9wNBBBBBBwqNSTyKk[=Cee5*Bk|Xl6N7NN+--}-6aR)+r#NRMI-{Y+Y۠R[WUSUhثgSI>U0 0F݊ae;P( ˭ O W@3Wܿ)'.m^*95_{vCyzhڊrkkkP(!AR]]-S(;;;&iR?^~t܁by@*"#/!0҃8eR333?hΝ}}}?j3А[gggOOO{{_TQk׮3gδ.gsssRQQ͛G BGGGww:y? fu޽BffT* ޾ww[mH3x{{ܹ_~L&{T*ꫯ ζ$µZmJJ˗/̺w>`+ugvvv99˗ .|#G|}} ^z5tu֙q Vr1L/ٳ)}kR,//4"ϴ]vlkk됐"Btttff / R(˖-6lXQQ۷=<< 2={<==oN=z455~1YCi4ZBB@ nݺuaÆCFT\2dȐ+V8^WWJ'@RUh4.???88fu\\Ν;WXL0Zjkf׮]ǎHKEYw8ڛqnc#s\!0UZ +t3ZMQqkkk-,,lll@OAPJA X,ǣWh Kwd0O5#8Bѫ[V":T^^~i&OqƏ!.!![nL>]VTm͙3[n D&AVWW(Њ wĂ0"-?9rANGP NkllP(7nhqqq7n7oP(4APbw}Gǎ{yŠiƐCd&a !C?~޽#H$3gĸL2%<<̙3χ (..F޽[PT  aB|>x1b.V6B 333ii5]Q+ks.b d!CT*L(nݺUXXaƍݻwW'N ֬Y8iҤl|򜜜Yf7...X"-((kllU(544,^8 `ѢE ǏO>r˗Ձ@Te˖P3fBEÇm DFQĉcǎ5jԑ#G@/1A 3pݻKR\.jjjRA@DD6p}Cϟx⼼< ^z;v4e,ǎ3fϞ='aZVꂂ*jkk+kjjn߾ rJ@@@]]Bd8ާOKK7n7n 8b&kp#@*T74<<\*$6lx"g̼nȑ!!!2Y.+V(..0?;wȑ#PΟ?Ŋ&JHHo߾*N5kD"E1u={4mٲ%&&uE]A&M>QTSN%|r˗/T*mhh0͔RvZ[[[NNN7|^WM6$%%L5qm@\^[[8dӧO,Y8nܸZ]SfZ"0ݕaK qff ϥ x )r|c`L4y0bA9J 4 \峸6Kb\AU(~_|APQQauuuΝsvv>zho.H++SݻUyyyǏΝ{A\n i̙.\ "RZZzӧ߿_.:u ð+V,X 55b%$$?g~ǻwn߾.-//@zǏ?qĥK666ӧO׿眙YT&!"d[l]]o߾M:\{{kfddڂNg}ZZZ>}zM{K.m޼ʕ+` ( >a:ŋgmmD"AlS*/ꫯ͛\@tJRRR&kPcO<77n 2 m*(tgmmR3aUUUK.߯_>vXuV;;ӧ>} 4hPJJˏ?@btt7|CW ޽͛7Ag+##ѣG8neexUU՝;wm۶dɒDjŋR4))[_A666=||( ׮]/DHegϞϠ[ |^PPoѢE"%rJ]]]rrrRRҠAL&!7۷O xzz9p{N<0 SqE"@AMd2,یO)..G'Țna19&e2q߅Fd&fs.clK`\.eylf3&["H?Xz(Aƍ333;wL&{yiiiNNg:uTXXX]] "3(j4T*ȑ#&L_W\ L0 h͚5={ر<AJ76RtBְD6&STb$@D4л74DNdf,Itɤ1p :Q: jX$3呝=b" ,8Xvcǎ{C^˅B!aB ۀW& ͞={-Zv-T*J%Aq֭@:0 ̠wD!R(Y]cݻsh41h.iӺtBnݺu= `vpp(++k.xs:99988444M6s̯ʕ+NNNM:0$---&&EQ|G/)cPЄ 'BZ;N<֧O;w8֫D"RVZZjq{{{WWt71!j:000))]tIMMm&[C:991 J5WԸrJ~]BS=zT? 80;;dvg:֭[wZCǎ_ze^0xGiȩSN<xdE"a+ڵZ `df\.ÐR X0QiV.&tJ% ` ޭq\R5ɢs~=NcRCg3t0` :HVrX[[DǏc...wTZWWgmmmkk Aܹs4@i J֓&M[/'F T*5D?y|1ddw5ήk׮WjJULr9Ƞ+ӣGpeqյkWA2 6lozѼ1 +++ܽ{gBB {wvԩDo~"&MD5̙3B/--h4лƸWGyy7ol\[!޿jn&SE a666&Wa H.w#=_&K1L*J.޽4333H."?j.]:p@Bѷo_VLWRt钳3 ÞO}<<<:[QQZh&25â"Er{:uD$dRiff1 ۊ<E뗘+EG!-6sIIIeeeo߾EQ177פ r*""B.o޼lAQݻ軙B*kГt:t8qbTTTMM{.+n߾-An555yΝ7oڵ˸JR*J$0>f̘7oJ$E]䄢h^^^tt4Pt.++r666AVBx^ ѣGLeiibvQYYIF$e2ǎS`mMSEOL 4H.߸qA( HC"\~_~fNðP_zsI>3ss󨨨Je+E/>>>:o߾/_`vITFGG^K ]tQEW{-..B%/..VToj-@B1sL6k.c?~\VV65UnH-ތqاb:͉ás8 A0SP-+4A 54ajhh`0<q7RgX`v8Fj _rd` xuicCCFώ@f͚[r8 0o޼#GGX0l2Z Ȗ-[0 h4֭۴iӬYU*˻um"E{ym0ߌaXn֮] *AeeeӧO~(޹s y<ڻw/k׮ݰaŋA_,dR~CJ֭[&O͛UV<8L4hcǎmذa666ZV$5 ջD@#T*Mnݺ3c >?k֬nݺuٲe}٠A̙3bĈS4vȐ! ,x+WLNN& 8x x 4hз~ AТEpg2l߾}%~z̙666l۶ $$$ >\ `?Z&h35EQFtR"A&SQ(I&t:}֬Y` N 5֯_N}||̙TڴiӦM=*ƌ z& W^*f(ڷoׯ8̙3%%%Փ'OMfRFyp5& U2d(Fz ~N>ڬY Ǜ)kYǵZ-vZ! ȯ:cƌ#GDDDIII\.oʱ |…DȌ3(Ϛ5+))Kݻw744p?Lw8ru-ɿ… ơ D[wtY,TA&)*b2AWT,333r3 Zn*@ 5Ӭ\  \T$R0>lՔ\.ܼqgg熆=3{ZHTSS;*jiixccH$H$rcǎ`ҒN:::0\__޼2eʔ'NtIVs\DZOeii)Jl6STHhpx"TSyy5Bqvv.++; <+&iggWXXh(95N[YYi:tLP(/++{oY٩j_{.Z-˕d,KVF&R֍ں2˵8+ zں:TWW10_"tر8YYY >v! Fs,,,TjuuT^*`ɭT IDAT0r9UT`Z f[ZZ BV l4(z g^V9Gj|A0\QQaL&b2 i4gSBPv-` R@za`BʒfF\꡾V4N.z޼lT*5775hO@ /Xp](J}j5 7n\|ԩSEЊ_o Ñ>U.!BXž=JylAt\ f uU )Ѵ8:ѡM_y5n~ƍ{1j(0jpA?F#!jll6m޽{M,6{_vL:`M Ν;bmm ^4|8lwNP 5N뢌ՠRw7q iK*Q |PѴc! OΝ;gs-|++[jnCd!C 2u: .˗///hH( t333t:\.ohhuF.0* tD($''lYxt7nx{{s8*P(3Ab&L - s5ч`禎EEE'ɿXZ2"BD;hVK/U BP&?k҇Y4͏i{-jd2f63|111$$$$$$$$=z߭ I+i:&4$mllE<`\; FʴN 91OO g0O݊TwoVgh7n`0EIIInnn3ȋ/Dcǎ8peޛUNN8"؀/k׮mWd2:ޖ &2pp` ߦEBBBBBG#"RtٷoΎ l_~%$$trr)))QFW39::vҥJVw2HVT*r)gϞĉb?~w\yyX,޷oAϞ=?~zzzKyɃU[ݕC38>~9s444o8N] hNNNw޽p듓wf͚͛7s8Ҍ'޿_Y[[O8hrrrϮ]X,h&eT8|4hТEӇ Aбcx]rss7l0x`oo3gŝ;wNӅ5յl˖-666 go2EZZn>O]SUUŋgϞGѣꀀvD&)J[[Z|g2t:]yyC_/Z[[ϛ7FΟ?֭[[O a|>ҥKÆ rgeeFkJH$RTƍxg-YaBSe{5A @e˖:rȫWmF_ñcbbb={doo$̘1ѣqs||sbcc1cƬ_W^Ƒ^ A߻EӃ=ztlllXX؅ ֭[Gt/ 0h۷b8==fM6M]vԩS]Hޓ~Wӌ,c %Ə?mڴg4HD>ӧ/_p0ceeuիW;vlÆ ԚjFԙj*2??(Ήj 4[6Q(UV?~…{ٵkW˕P(+VL23f:t#f50ZKT>|xtt4AGDDT*]xq5Ivvv~~~jj*A-He{5a@\ O?ڬ^<T*w}mPaRlnO>]lנ:;;CTTTлwoe? 6r w|8⌌T???wwwO<)\]]q#wC/Ӕ,9`;;=&&fĉ&A',XDLSL?D"iBAKJLLj tTZVTPt:F177bx ؍7.X`ʔ)8Ç:u޽{OS2r733>iJOAr\.>x ㅅcǎ ܽ{7 @՟~iÆ  XhXfffXXݻA#F;vlbbIq?=:|H"AK07qV0`~NMva ¤SRR}}}pGQtҥFdM hO4Y>lδ:~^x144ARb kjIVZu)Sxzz>}… C jO quuٳgdddPPx5qtt8p`tttII a 0//CB4hР(\o2d n|)Y-c޽Bx{{`$ٳg%$$vXzz:JMMMշm۶-%%%000&&Wvv6ɡhmtA7oܿjժm۶ݿȐ!;w4Bhhhhhc`?L4޽{ ҥKVVV...;󋊊222/]aq@Rwڵf͚%KfԀxII %cǎN:%''׸@}Li(?(vҥ ._\__̈́3gbz{5LU[[{̙_~%:::22֭[NNNW^$S3fZ!Cod2:tP__߼<|~>}Wccq  7`RI3fN7nxp]v 6lرSLq|f͊ppB;;;_x_m;;\HTYYiood21nO*- WWWW^]~$hPPիWA4}*/^XΝ;k4ׯCnݺlbe`t-W)= N.xzѣGCA#""""" [e@Ϟ=Jlhh(DGGKR6bΝ;{lDbmmMR1 0`ݻwkjj,,,5}5d ت- aY(2Lr7ow=KS6/q &/\pذajN ,b ?==}޽F:~T*U޽=<<=za_ѵm۶/_Θ1ёp4==}۶m/ڵkVVCpQKK\ѻwQFA Jb2D=DQ I(^aFӵZ-΅P(n`FU*2hmTLZXX9s&,,`JH0 81$jCջ:c6nܸs81c AJP(@CF?@%FQaq?I>> &U:~H$ʀ 210 naaa4ujmyy'O77QF}Pzk a*?ݦ#G.X@R17nxR;;+WG+VDFF%`Y7v[QzzǏz h ={~p߾}kkkQӧA`Z_mիWRltRp6}m( bccS~n'+JlٲҧO;XUBVMM cXf;999::FGG999(JR_յk׳g7Ujjرc9BܵK7EQ_֖ۧ4Yۢ3`ԨQnCx!Ch4۷oڵK)H&Nƶ5jQݺu[?覎Ŷw_|~PTKKK*&5P(t:رc߾} cǎ#Fx"hʕle&N@)S ҵkǏkSJ]v=zð:\xð]v͝;7>>LJx f̘f-[P(ܳgܹst:ݢE ީp?,*uӧOᇂH4x(J( u ` D8n-h4#F(:a„˗9rd̙Ѥq~g>lδ++޽{s\49r+WBBB:u:@2޾&={wlnnnkggG xVVVnrRҥ\.왧qRt2 Ϭ9xx`ɤRJbX85!@ FJ z78*p\.AjZ_UF|Lrt:fiAZ   R*<0\0 4`(J. Լ2mA_%r;Fp82`bMJ:8;;( bT*]Y,nAQB5Rq\PIԟ@( P1 n0Pjkk---hPp8 JX,0ðA(DtNR`HR/Iz EQ6Sgq \.fS(BjZ֤QĿ E Cz}]M):|"$&&ۃiafbu?*-j2N˵%b/syB$+BzU!&e0iޣA&=c,WCbBSZW5)f<:>Fe M0l־mhl6eME[FKrmMΛ_f x, rƳ* LzXV Eʿ j:{1엤ZZHHHHHHʴ`Q}[2&ڦ ' yl7T*Հ:G2"!!!!!i rL+ڞ-YF$$$$$$M_xց {MIHHHHHHH Z]]߭' }k'XGL1OO rLh:`Tj;IBBBBB)A`j7R)g0혧Vo%h7n`0'%%%!ƷzҥK(~!!!{' ɚ:jLFDBBBBBBb-??="fFRٳo߾G*bcc###L?h \^\\1VRjuNjJEN4ٳ>}8qB,Ǐ'$$ǷO>]?0..n֭>l{U-7C52ãm|PB_LL 1c`vɓ'3g⬬, N:5|orrwII XPQQd2333j5Ӎ7J$oo~wE//gz{{ւ~'Ю]s֭Y;v 0ݻ~~~]teeeR񊊊0Xhee`kkkUmmmYYH t:ɓ'޽s'O\paYYٖ-[mll*++A&666| |jN3>Ҷ)|>NVVVqt:Z &JU^^nccZcPVVvܹ}بD"pٳ?~y77nݺ0ڣGZ't&i2Ν;g̘ar@ 0ooo>ҥaÆq\ ð⬬kݺuUUUfffYYYȘ:u[&Lpʕn0`+W,Ylٲ9sP(. ð98ݱcذr. ?^P] . > ρ@ĉ'\\\*++7n,XJ"rҥ2,," Fh40LP<<<@0FiZ͋j4BlD"YjUAA˗/5ͣGZCə={{~ᇀ__v.!j 4h4J~y:w| slI &ܽ{%"Mݻv o߾PM:!>v옫k 0,>|{.]7%ѠZަhih-1سgǏoݺd2믷o߾j*c;  뇆rbooAw}/qXɓ'+={l*> &Ad2+--i>!8^gl2=B9}40 3M2e%8P(~1VVVZ sR&SiZROqRb X,<"Z~3fLTTիkkkCBBp߻wӟ>}xaaa```qq1999#|u&NH$ƍ+//qcnׯ_O8qh4cǎ ܽ{7p0aX,>|01(e%''g֬Y d߾} mllO?zjOO˗?|ԨQC j8cvȑqaT*Z ?V AГ'O}}} 5#Gᄒ-lHHH-PkϞ=###t4^B؈aWEEE||СC!4hPTTzN(J1 zg}aD"H$l6;''vŊ` aL&-qV;v??l2HoI_DPPJ8UUUP(@ h0 d7\h)t:t>D dBЛi^.ՕAQð˗/߼y3!!̙3]qqqlbnnW_y&%%%--mɒ%۷o0C&M?T*]j2w#a.]tqq00 ?>""B_DKlPO8!1 S(ӦM;wܲe,,,>n:E7leFӔZ655uݺu >A֬Yw޴4GGGxyyyQQQFFK0 ض{^fggs85gΜ'O`APffcbb>s LF@KaF!.1L琐w&%%޽;66vΜ9`h-rOOO`)a @111;|0a5ddXFHHHQPN͘1cjuHH~Ox,۾};:tСC}}}p}qrrq˫ߠhddG@@'L0uT{ kmf q5}e˖L8nggwt 1o<Е1duɓ'8͛<8<<|…vvv8x믿&Vm;;\HTYYiood21;AKEo1ϵkׂ1 x>>>III80<{lsssO ?hׯCnݺlZhxxxǎƍLҗrҕ+W޹sqR [?/|III>>><EѠy5t | %!靥 *(XE *, PP+*6(H/{!#Ep=>>ܹ9sܹ'3sg޽ -,,p7778p`ǎ%$$tVƦ+W|266nmm.˯] Hz=PRRߚ[[[ŋd2cV/^a2TxmGqqq42, jaag:1uL ԘL&AQQ1++ sfa#hffƛL$LLq8v3u r܊ :8ydee`0g``bŊHqV3ГB477󺨷\.e8j(&## p8&ٙ woYx\.>ӫ /5Jŋ]|B? ɸyݱ @Co&*.L&x-zt:|TŗUǔoٮEEEϟ&MɁ%%p27Z޽[LL|b„ O>upphmm}CWCBBJ9r͚5۷o =.\6m&õ'$$̝;L&8 w! e?~ҥK?~&&յvܸqZZZ0΀Z㽼V\9j(oooCCÚs]z$iݺuPUUh1k֬KikkKII>|/a*pL謌CurrC<~zy9rOUUfXb ,#f۷o„ p!fw<:tڵk cĈpٛŋ'''(P /[WW`ԨQcƌ;[j.S[[ꪣb .+Ыdx@'!$%% ]׶ǏJJJ544455444$%% :c'O"H[ZZJJJ444 hD"Nalkk@yղ/` ĨTj}}=DݶVC222555]2t:immlll0L\\D"׼;Bhp ΢XF ZDjhhuuu2,..N 8/gT*@MZvy^3t:.B$H4^Ix=))) bYiiFCPS^F0,??_UUH$[zdq\2L[[1 źoW>~6K}2)jkkksr߄I]{PtBt-βZyiܹsa$p6n,ޱ,"Geɏ@xSAdrwLJEV7sZ~Uִ>,#ŋsrr BSSɤB4EǷ,%}C n7Ε٦ΕA?ԇP+P(cOH9^dA"H$PFQb$bD +ctD^E@  ܼym@ "KE@ ?1䪪@ 2@ ~b v"(p.N&:@ ?d.Ǐ+**ZXX 63)S 6߿Gt766޸q7͍L&_rŅ`<G!A0lΜ9UUU..._|ILL!aXjj$ߩjc”qC :TZZV@ ?~o,d 15&:jԨ;w 4g< @@  S( =z4m4֭{ꕼ|PP>ꄇ2={Рq= =z;'Νkhhظqc||<ްaÔ)S.//߾}{/@ DCVTT xY,į%weggaǭuiii0XQRRRTTdmmmHuEEEl6J6K{|ܸqeee7nX`A_[@ xѣˋ?uTQQ٦MJKKn6mhkks\iii P6BBB4SN:T`k;;;O7oRmmm666'O޸q#LILLLOOwrr*++ ]]݀___x8bĈW@+أG~>swN878477Ɂsa(#PfxH^y<̞iӦΝ;w;wTWWUgu{߸qc ydN "3f @މM@ty۶m{`ٲe񠬬Q ///^x^ؼGGDQaLg͚vڋ/Kz˗/ƍ10cƌuEDD8 fܬfffnڴرc RRR;v,::ZUUuŊ&&&-3@ 9##‚L&|D" 6ĉlnnr^***C 066666QWW'JJJx 09+ә=_|QQQ}6//MGG9yzzFEE999zC-GGH8100رcG=x`ppp[[[BBųgVZ뛓ѣO>޽;,, ېWZE$奥UTT0  =z:dee^{]j鱱bbbp;wxu cy˗tMdXXXiiѣG E.WWWtBDbpp#B111ihhpss;|pkk+@AAaڵmmm=xi&@BBBKKxMB W!r܂&//w_®#jkk  iiiiiia24:SSS#>888())`/"""==}۶miii˖-Dw033#L^~}EEEff rrrv e~~>̙3֭۹sgyyy EKK A ?i***@JRB> HII&&&&&&1Bo=׿p<ٱc/߷Z=R.XYYr\YYYPP{{{ƫ9r5kocU@ hg^իpĈMMM_=8τBرc0FY~ԩSUTT?׳vpp0449wo@ BRRR_/GRR Oa0$D"555H$ n*b2555o "))Y[[raXuulcc#HR Bss3L&Cz"Cc_ۀefzV@ > 0 FU@ ?d4@  e@ĠP@ O +@ '\\\6 @trvvv_ۀ@ ExgϞ9 Wf+Or@ \>7OW 쾵 3'9@ =dN;o-s Č~ @N@ ~ @ :(A B@ ?1̄ t z\N|p:Nׯ_ϛniiIz0QhjjN8۽̌ ۷o%]p֬Y6UYYF!} lE!RRRfffs1!xÇ?؂ '?;wl@k:Ν;AAA̔XRRӧǏ?~xݺu7n~UPPPJJӧi4ݓ:u*<5hРgY~k뛜|mWVV։'L&_&MJNN7n/ 5772dHLL z*GP&..nĉ .9ѣ)۷?~𒘘??ӧʼnn')**z6,_|˖-k֬ zrss\{Dcbb<<<kkk=<<~*16l ==XYYlٲׯX988Dĉ,DRD(Frrrdd$L 勳3^UV~Z\\<55:֭[[QQ%%%a1`_UYY)eee%%%䆆@cc]xx8L^rIkk*ښ LNN-2MNN铇GVV\/y{{{i.>8";XhѢ} 4^^^eBΝ XblqjkkGk׮ 8> ͵ӧO߸q#DӶm`D2[l0l֭[l100HOO\rԩS\z^r!ڵks)'''ͲQRRzmYY۷i4ڸqٰÇpѣ ++ׄ)))8UvϞ=+Vp8x`(ߝ ,u떟_@@UJJʗ/_'Hu&):( 0 ;y)Sqss󄄄$]]f7GFDD 4Ғhii_)B=yd~~vbbb```hhIlmmMLL'Nq&Akkܹs322^/U0L i.>8Yzѣۓy%&uA}6##3 ee崴4ت㨨}v۶mśy- zٳIIIWVRR S-f wrkkknccpjjjΝ;b@cc#Cqq9yq"WWד'O޹s'::ٙJB8ΧO0 SSS0`c0III#FEXb8ioo?N<lfD(*))q8.{Νwz^aߔ ^{{{YYׯ_744dffBXvwww.KP322lmmEqBlbmmrʢ"OO2|7ti?~;({Ste"V9E`+MVg@UFsss8bx+Dy12 91g $$$<{n]]]%''ggg'h!<믿r8؟:{liii脼V ҵCp(nmmmooPT%hii5jTnnׯH))). uqq9ydDDĻwBBBp1:'fWQ{{;LQPP`X]e>|p8gΜ9rd~~~mm-<oJvvD773gDFF 6Dd2YD'M^x1iҤϟ?GFFZYYu:}>^fSp2@P(+5c+ZQQQXX\=[4N6 vf'MMM_^^cx04vXwwk׮mذӧSL?~XVVLlvC^25yӧOٳS} {& 2DNN=z4@!0--`Ÿ,0+HJOO\dIgzWO>>}zSSӹs礤n޼I?|@PjjjTjkkv4̙3QQQ Ν,bh555Et7(L&Ϲ edeeQ6(ؽ{`#3lout1޴i2dҺ (073gSH(عs'O|Ξ=޾{'GGG e:S@A9s݃!T*۷`$''dN6 ~իpa=8SOOoĈqqq6lHNN~Myy9Awwpǟ?ɉD"n! l#?p 'NTRRblٲ%K8p|Сiii???!f޿SSӗ/_>|H ֬YrJJJRSSܔϟD;wϘ1HdGM4hZ^^ѣAAAÇ)))t:}ҤIl6['!'kkC_t `oof---i4ڇ._lmmIзoߦR111l6ɓs)))QWW>|#.\p%--!C 8PQQdɒ%yyy.fVիW222;vթFQPP5ǧc:|6ٳg{yy)**V`0[m6s<5Ǐ9xūڮ=DSo߾#fڴiSUU՜9s /_blll&M%KX[[3f[@ xxx :… CtTG|򐐐k?]\\=zSOOOh'.ƛԩSI$ѣGg̘qСoѪ̠&''=ƍ/&$i222V';v쨫#fffaaa%;+мɓ' ̟?̙3,+44bℎz铏?ݻ}ҥk֬!VbX~~~A7zܸqW\ٰa\@RVVӋ1cF```vv6@prrrvvNOO,//'+V`2׮]^t).x@D' |aaa\.w̙/^011!m.!@xe><}y]~9O:)s_:b^|y͎z;;S@V0dJJJWWWt8HVV (//HJJtReee8MʈD"4̀ZyMM ÑA%%% 77ϟ_|zp@:s"@EEc+))ud 644()) 455X,xf*pD衊@tDH#i]),,Fii@`ٰO%2+?8Y#a|DWRR 3gV KBll/^`0xaG$;[\' 9_e舼, YpxB,@OT #GH#f|W*MB,bFZз@"ѣq-^!CtEE)^9@ 9( 9x['r@s o-s Č~ @N@ ~ z>7OW >}6 @t\@ gSlv@@@MM9ܡ@ y/^HKKkxΤ]vڵ/@  }j``0bϟ.XO-IIIɞKԙ'O577yyyիNjn%++566mllܥ$H[Z3D?D@p&nu [-k6p85숐l0,hT0~w r=={={6$$d޽666{uܹѣW\qppPQQ~DoPPPW0""罧.%%e֬Y\.Ol۶*99ȈH$?رc*__۷o?NNN&&&o߾36mɓ?}ī.**ŋk֮]+\3۶m[`޽[UU{633ykٳgiii"1;w^z74&&C522joo=ώ^ F)e &6nhiinhhx֭C% (a2ΥSe5eUos6qDBﱽE@ 3 "hmmmiiiddDRT%@`0=8]Nd8ðC1ÇPŋ&L <{LVVvڴixnϟ?777g2&L(//އZhŪ)ÇW\ ӏ92byy1cƍ,ɓ'µwԲsǏ߾}ZxQ(_2L}}#GZ__͛73gljj4556mZg6ɹ)f;::1 >s =qLh BaX{Y[[y ֞7oQQQ?~ȑ#o޼?s D^^^\\|tj2Xi#7KLZ.:+ MjԁXy"CCPߓfCCC@ aKK BIHH000mgLAAAHH󝝝gϞ ڻwoNNΨQ555W^|{ii===-ZaaaqlْK&FW1 F-''gddwY,Vnne~we˖zzzn @aacNNNpp֭[ϝ;hooOOO5k֤Ibcc-//oݺun*--8ov͚@FFFbbԩS)))9#++kjj u7GTRRZdӧS`}}b[[X\ۚ5ktuuΘ9sʕ+MLLSYY N;;bikkgt={,[lȐ!BTwE fff0 XZZׇ%%cǎ\~]Hd2y֬YEDD%& _g /ވANNS\\|߻wѣG?_È@ѿggg3B_P&EeJ3%i2 :!9WIU| ,):SΔ2%$;%tիSPPgϞիWh>R55ٳgIYY_r)))/1003QMMD"=~8>>>>>#fddo߾=!!ApUU՜2<e˖>|xgGYY955~+/&L^t9sߧOï7n봴4Q߈g/**oo={iCݳD%Bӝ-Z4k,Ϭ:(Ԛ!5eRt9MEfJJykg-_./G%RXt9@ylիWKKK%%%I$R{{u봵utt2Pg4}zSSݻ<82xeMM 9s挬رcGG pƌT*ummmYYYڣG޵khllgZtttSLx"a<> ##BxzzH6!FFFVVV+VP(0q֭fxAn޼9uT!7x#'N>gddܿի'>,--O{ƌ3vXvMR}||!_: fX Khf] us=JJJ444( H$DRSS+***...++.(@GGϯðօ )}> IDAT))GEEAcǎ//Y@ xxxGijjJJJD{{͛7wf~„ ***qqq0͍J*++>}~ WRRR ֊ǓH$mm<{B!PKRRÇ###Y,2BYxqkk+a[(99ʸihhhhhڵ뛮8~8௿MLLL311Ғ۱ca0VOϟ?{ԩSL&HhL&޽{0… L&Jd&`-oĘ1c'+88W ),,ܹs4ѣGㇸlĉMMMmÆ Fs".}'NsBgB_=۷m!khi?&A"6|#? KKK $IccciiiBBnoo\ZQQ캺: %%TIIďBt#Gn޼XIIUVVF[XXwf M:RTTqNaP>'F8%%% ݜYWW,JJR(7hiiIOO =(Ԛ!e0 Kxx%g͚/ ׬Y0h cccϟwAOCZu֧OhhhJ t{"xgl KKK;[D" 0@JJ7n0@ .\Wf  pppkC~p**Zt{"89:Y@ G",A 砥@ĐW%I[ @ $QjkkM-^ѳf!@H._ EfF!Cwڿ/@ {evlxZLnHN ث ?/w2-lE]X&훽{v=MB 'evn5vJ& D"@\p>]IN}IʗϒR)6f eƅxÄ}\r\6_yn^Z[Z**ʕv[akU#}p$ /okRH"@$ .$>ar>q8.(knlRt< 4r@LOIHt^LtyyL65026N[`Lii'iг'Ξ:gdױG ,(P25%۞~Ż*z>kہL6̼ ;wZt8i(g))!YZLLMI.۸%7y=jt %|q7y xvIɟx/^qKPGTtw9>FVV7r>sg|)"op|=Z1lPTzU]Uu=!e #8bZvqn`|,>5!`j?S:٠70li))jjj~~~ClTԵJ*Z eoa&16mTRD¥=Q XXKGwνt^U7eڌV?{0z,Lq`U5gOvmzgx\w>5˻w˘LYY9Tm1q]班/=邵rrGOݫVvbKVfs:wǧJu ]lvϜk7Vlw/&>qdH= ɮcT sRakع}|)ڪC,uG-|PA݆9yʿ{8e>qF55փ ޽=cc톘iYY9q.#K^3GǀN69>t4 ;W*&P /urETCBҧĩ3Wa!aiIys/LK3,.*<~M/9$o TUW}d-GGUQQ~0÷]{'+( 8~ENpd={cssgȰ.Zn7Viw2xލ}0j,P\T6qHۂc[sOp~;ػ1m[/DEf_1}֜;7kkkYIIܦR?b|Q#C:ވ|ȡO}8;;F?ypPs= 566S5{?&0mmm !0gUUc'Bs@‚;>qг'^<{ UC6XCNxO&&fpٓ8;we2elrWә/!)yySs iiƕHDҲkht={92\A~~iIaO&-_FK[_}ǴdˎUUwddc3qqq V ;w֕fif?{7y|qQ6i_JKEBBR\LJɋ#u9@ d8w=9=߲}״&ff?$(J;}j$QWvtf\ |)y4PQQgf0ZZZ(G^^2u(kaOyYiIkKˋgOn܉~;7.S+.pn7rTǫ0 [x>J۵pg9H$ c2ed29.~A~N8~@Kq+' W8~2l+롱oՐmAc&( P,,{wRU5u%^DKBj/o ߋ෦M\F1$ L sJ&$i  1i*Y fsSsIIQ?fcQt:|#6CGv_hG_=zvd R(#cӼ`=乹9&fK|W,s3czR FX,Y9y o0n3Cx+Fa~@lAW+ X>jHvv՝iHׯD3sY]wE7Ϟ:ni{u!9i^.av%?Kuu<^+w&x#PQQ̫mgO5559KVð[wOxRZ0o&`|3;; U%e^NT7qLʉ0 Ml{kbSf̜3Tt-^b {B%eR̕[ϟ=2!STBNG hhspeʚ*É*)/YkQt#cآGQ22w݆xWqQ4Ijsg:pcI<]챔xSYYNQ44)S?9'1M&=K@ 뾑) RS"H| ߼~UMpwoԔ?oYL"-M I^Sl֬uyagZZygu9>%9X_@,&&{_oPsQna!ټ~5EUD_H^#foDq 7f_%$֯YAР{TxSCnr c!{|𞖊>^>pj@т7r4w03 uS?&b} j\зb;kSYY<>7']PAUMsh6+(㊉:Q11-~FhepT,+;R_W+'jl--͒Rh4|kk2[wwwtws\3nɳ{hAgǧ?iC}QFv睝J*(LFŲ?1%Ex}i>****܄bBh4ZkK3ʿ" 9Q Ay 78w_Eў8dd˚C޴u)79YgN8rbƤn]͉^b !T!=C/xz;TM8weuvv66sۧʉKh4Kx?ߘKW,E W.H*ֶHƙMp8M-A0cNYY٦Moaaa``˻w92EB@@@@@@Gz(!!aɺ4mG̼poQ i֖=WՓ2 iY;$Ϯ A$i555,{ԩ21frvv [NZZsw0_^SSɓҥKwؑ4}G'$$XXX䤤$G13onnvtt+>> }}}KO򚚚݃.\H tttJq/_~Yvm899eddW^uqqv횦ŋvvvRRR|L|ҥK p CCCo߾P(!!!p9s@JJݝ[D9s.] UAAAepqqHf&8q4Apeeecٻwǫׯ_xܹs!!!޽P(?14ozyŋ$)11-$eee7o݉0b*++صRUU%''ǜhkkKKKyfBB… MF$322{.7oHKKI*++Վ߾} :x`tt4n{~}}=s\ݻmmm⬬E600G燒7*++EDD0/={ݻ&D,XxbVXmlltRUʃݻwKII #xyy}%p4NE)))aaa<֖pԩڵ/++caF@ph4N#&&d  ʿ}JIظ`1hQQQqtt433SUU---RWTjii@BBFhX999 R[[keeȌs˗N4Çp'O***TTT`x㽽奥 Y-[O?8q9##4))ť"//nL߅]  Y[[>QQXccc 3ghjjJHH 1"***$$$,, FCPruuuvvߙWmRk׮t5YBBڵk9sHHH kL*CBBupqp|P__ kk뒒#G[gOA09</(((###,,̿JammATVV>}td2YQQqŊp3N <K($$tmww^dIù+#(,$* iO2c%AMw_+//+ wT>44HTT 3~zIIQhhc·odPEEE{{YpQ(ea0vnx@88ȇ}=z __͛7[ZZN®m---MLL"""XC$%%sJJJ̫}LrHYY!s9 w0*((ܻw/,,LCC<$ xyy]t 8'*k9s_o߾iii p|@$mll<<<<==`0999K,azy\\޽{_ɿJO_---L4nfQ/^[V‰D"bm[$B8,į蓤4Q*Axqs߉_vvvܹ wuuiȺ=\Tj\\+D"a0/_YudSSSH$҆ +ɓ111:MMM ro&˜vqq… ?~tr۷YYY8dX\L0ƍL &NRZZzŋWUUsbŊNggg8Ν;Ǐ?(,,3gΐ1 OPX}kddd:::˗/g~=y򤳳x3f̰XpaZZW)GDDDlll֭[>0CPP(TYY7 d1Qy ,C P 8l!12X"A(Wsqo޼Y__/**`h4ڞ={544 o1̴Ǐp===ǏjkkyKx5FQSSWLmm-Dp%%%Lzzz[[N>}iӎ;%wwws]۰㶆F]]݋/9&\t)۷o_ccׯ_墡'nnnW^ (77ٳg=OXkffuViljjܽ{wѢEqh G1~HHjB͆ < ޽>999rgAHpF;t0^A@@ 9/7[l~www}}}ff\PWW'&&=:jXFxKЦ&F;::p8UPPt]]]rrrg{{SkYJJ Fwwwxg3deeGy @GGׯ_'L C___QQ*{[DSAG/;oOQX!%%‚ 3k.;/{qRBoΎV5Uվ^u7z@_o/oQ4m˴ܜlUU_X8wp:ʊOUi4wn%|*hW/6oXA"{>Ay]]#<\S~ٻxwo8 Οtδ:vk&LPV&%>{MƵ,^C|*Oܿ{mqʛ$AYDwYze :vhT\m W"~q99QCv%z4 ERυjd 73?у{Ʀ4ɣF]|Ǒ+1fz3ˤgس=gi)78:)3Rw͎ ִSkˊ\Bkb xIEy[CŽgW= I$-j%5,{ԩ2mm=di#'ߗ·o]/Ef~|+-IJ|SX)'0!O_}-y'qq [gͰݵ}slm[TUVV:LoHQEUȊ$\m]nu .}ikkM]h N39t`ocy>j877m, 'abj13g[у_%e粕|-):yYE$2`-$NS*FUe+>31KW?c˾k78k@­Gg2^-<(ww>dERr2(p2}6} #K;"Q&4"ɣgNy^a4C!%E[ ^ki Z*;:_hhnkhjy~'ƀ0(Vd\Q(EGOL\FO7VӜ((ĩiNћdbbB%(:zc.g,׳ټIO:e즦F@[[$7iȮ]Gxf҆UI`EHAPRA--͛ׯ֣($M==ׯ0ј~͊AI>dy\}4{%nsLMvۢ/1k܏p>~xkIB p dIJP(T3%p#{{FN)j~ ``NZn>IFqv 禓ϟ?YX¿99WV|E_41pђ^ _\W[{F}َFnҖ&ReƣA5 "~tNoJ*]X)++w.4Mڣ~9p0RTW/y]MMՕ؋{X=PTolb>V*F yw{z浓?2UGQ*ZYYNRLt¼gVrL{?|[\Di㖟'WU]UݧiN|)![lãej8I޽\{bZwo]6HWW@å+Ģ8AӒ-h4(-+/&aBDP@HŠwTFEULUUJSRUUUsssGGGU51/Fh bee44)uLb 8|s᤬Op@RӪ I!Ɂ_coHz:u$ѡUr5f=4EDKezz101d8Cj~ >EVN~U?SWd Bx8sϟ2Y*Y#\ua"l}|80cG>y47ᒒRh FTTHAX,TII),%dRQQ2AuŔs/GQ2⺻VS Twb-^T4<"Ș9 7;78 cEqXiISf,`ZyyY:O3 Hp|Ge,Mۻs_|w* jp===}g!~yeed!!ʎpU'̵-9w!z&?SC r IDATLl׭^uzѷr멬qtܐW(*70<[QQog^n6>5.0a$xʺ裡 C##0}2{ߟёg=x g>iZUn5~I(͆ea0L8 )+ocQ^'-M|pﶜŠU~)/}D)6bG'miנ1#EDv}6<ϘKO][X/s9IQIQ:{:GS3_!~T](>~4G_2ׯ,߱2YmfHxQx(?H$OS~=}`vIeB!xo!`O;'~UUUMk47] v۶ebOfuuuKͱl}lưDJogJrROO7SLxT1oUU JѸ֐$$$qJ]a1$^HRTXb0U}1 *?@ /> %kgg] {BtFJJ|F%T*Ƶ8gW2LI[IO]l%HJJ3M& ;V 1٢b ]%Xne#BM]Sabˤ+|OOJDwo=$)#>y {.lkk-$Ȇ_2mӌ3\J$ R0fQ  _s/;̩crZ&u:F(1xԻG9Z+18Oc`d|`v*jjf:yfKё'Lo?m:Y 7үKn=sprD )U8d.J$woرS3nW^y,r3쥨3 \ES+,P(+O=FfTUm9uHtYÓ u)/;:ڙj%ny_hԔ6Sx{0 EϞ>Y99QCwn%uy:ܜ/ݷ{l'<&B`=K~߂u2qzmԷY42{Wc/S 'Kxyj(o5%E ]4HD3#ʥ.D#cf$9NT*uϚzue=;zOQ3ٹGig}8u>dS4S75Ԛ0e]z{zԔ'̌(|iii>;^*9IMdgi8Q0{H~ ZfA+6mYG]3"s4 eۮ#l, ^q2!}WՔgNxFMup@mu9vSlZ5u/z-oma`mip8{^^nvxؙ{wnxe-k^$qma޿puRKW_tu`ۇO=+,,lbajiۍ Y[L1sdS]{)ֶh4z?,bb~ 05Ԛ0o&Y_WC[;N߽c3W:3hjikz{.?4 􇇝y073[q aeBWLO{O W)$3#ʭ>ܨ('c^0جY-*+YGrG:d\ 3dAj@5w^Eт7r4w0X uS?&b} jO˷b;kSYY<>7']PAUMsP_'*&#Q^MDX}ۆ4-!! ijj1&Sz{ :/ APsS#?h#ɿ J kqWggQ9Ϛ=:GCwwWٷRYe}yCjk&pLf0}==L/HB:;;P(sQSK}ʿtď_Mtwwtw}[Z%%htOO7@Dqޒ /0$eZxQijjlin'Yiiic isSwݡFU1noښj,nZG Е /!>Gb11q%^퉣/7mݮo0i>MFvNv֙<>wb=9;,/1!=C/sUG͞[++ ̮ k^mDRY1uàϥK9̅>°ܕ@Pmʉri4Kx?ߘKWW-!mv'JgvCHoASN}Ҹ13㇌k6VT  Ϙד%kP;Y,°ڕaLd~WR D'QAkp]z Ͻ~@/'פQ;vm:6 ƾ~pc[ >a΋;vرc#pؽYnn-FC_J+$,L`#|o6mڴ~ /_޽{wȑ/? (taFP( ΞĈ /wcSg$''䈉  GJ97J50 1 ^&daU>z𾾾F%%qn>F /3;i} ,F(@yn~T6NϦ D UPPI$(w^CCéQ("8;4-11ӧ&L?mcclnnĉ[@tSTccc777JHIIٴi ~`ӦMloo}vNN%ӀUUUmmmy= fҥϟmiiIMM_f kWRԹsNmlX ?qDhhزeO\Ao߾;88 =>G EIJJ幹A:50Z݌nFCEo] ҁ\) #c۶mZZZQQQ/^ P(&&&۶m}^gϞ?|}}BBBF/͛wA6Y#𠵵uw 2eJUU|W^^-F988|E]]}ӧOɓ'4%!qIII>>>úA@9ƒ_}WO:ƍKZf"L~qKK 7+W\tiNNOKK;k>|w_<00͛T===̾k=iӦݽ{̌Fݻc _wwps'Nyfnn.gv?yĉ{na~(zzzLA FOOԘ}G&Z2;N7FkYN[pqٞe1=== #;;{ĉ(L&={6;;';;;$$DUUBM81;;i ۷og\2J|R\\FkjjFR!@a6^SSy2^nHit:QXX7ovvv|׮]occc4[[>A39Aqw &AX,l\nv&NAm*//pfܹs!ܜ@ L6'%%Fy↝?=_ʊ[za{{{@KK ¿jg ad2{;00P\\V 70ytZ/h䎎Z߉1 y~= 4 _P(=== 4mnnnbbp8 i׵k׺?~755HRR;R(w-\PFFfٲe===p7oL>@ dY(AГ'O777())zzz|||%yI$nhh(6))vc;mmmϟ3g޽[TT@P~Ĝ!"( >#+**2@$''899/^LPG!^244vwwommesǏJYYY(+++2dddIII8pG-r )55Y׸Up8UrrrMM oLL s2[9sYg7"##===9o޼9|LL DŽ{^ⴸt֬YKKKx[IIIVV!QRRc08>Ϝ9c``w4tttDEEUTTA͐[[['' v#,_~[n@!pxII ի...#]`}7eDP\޵7b;zHv8E^ŏkWҥK?~P(aLmmK.y tzuu̙34kܹIIȊ;v477zJRRQ+JJJw^zʕ+^pBSSxAdkkO>zh(,vڢ={pio߾k׮1տB6h&w塡***SL-;;f׮]Cmll{ي+䔔0x4Of?>--M@@YfyxxTVV[.//o2[|x.+))QPPظqZDDͷn[zuKKݻw̑ {(**hp fIUUմiӈDbFFFqq,-[\vmΜ9%n`mmtuu坪 [[[tJ-//RVVV_|/yyyEGGqL{[n?~c7L&_|#FEEUVV~ٙߟl>+**sz7oMMMݤCBBN:^*++Ww1 [@)teX4'sZ_Ҿe$l\W  `ʨ8::RT֫T*TUUQUUu !!Ah4ZllBJLLd9~ 'M8P@@ɓ'***g LܹS###4))iƍfk644dV0111csppb}#Ǵ (G!)))ء+W`0]vYYY MvpAo7nܰ.))9rȺu몫ٓꚘ:::S^+!..̙3~Ѐ]OYWp4tRqʕEpKM?4551ӧOhjjn~~~ 3;w9z'O9F`0QDx-((HMMG,!2qqq{{)SDGGGfX,J^vN3_۶m#Hp ,X`hh($$dmmqT[ڵKSS>Ƿ°ܕƈJ'MH Iq;*coojdd$**ZRRru%%%"""FFFZ'߾}#BBBg]EPcLL صk<C+s322rrr?NN8p@[[ёJYY٢E.] F&jjjT@'Ą[̫^oii%--j*ޥ]~}]]*kv/^mo``uRZZjhh8֭ KKK{- wD6ǁ4y͛7x۷o|f^+B]]]II˖-ohh}JXTYSeeewxǦy 6]v~ׯnLJ[vӤwժUgϞ6m<2,H$Rssڵk_x}*((ܻw/,,LCCp`yy9K&&& OWetN@YY۷o |,]۶m^^^YX+8sVEGGR)))Cb.&4]oN1!!!27P(p`KKˍ7/lb``rJ>(**R( & JݻD"QVVիHׯ_iO%%%mm6d[FF>((GFF+sY999˗O:U[[ݭ#DDD _>AǏ]jmmUPP@\EyY%"7lmmDoߌ`0 njD&E0TUU7mz !!! 0 !pӧOmvr劂TPPXh}vuuÇkhhݻ_pgffF N8$"6 BZZyÇ]mvuu5rAQQ188a@ӎ]EA0 gddDGG#K,A6cwmmm.]BDd3vWLr+++$3`ׁL;رCwጌttt޾}aJB7c?@ <\h-}@ӑ׮]+##c``0hР/^-Ç _~ߌ 5kKKKy x2L&:p86mRPPPPPw SL 8w iK^*}~Oȇhhh6 @AAAAAAAʠ  d]˯Q~hPPPP~{d^ncX;nhll0Zluߪ"&\`*5`ԾUHLx.> XPDge*5 R+$|Ւw@` \M 4p }z]`۴#:rRDPPPPPPP~ ‡2ƛ4$$ 0\pQ Q +Ba)ɉy22y9G;HWwbH(R}n0HT^MK@-r*i5XB!0 > p8jl6.I@`0\g9NvUeV</M(/ !ee@Ņii-.ytFqxUYY凟>bg?.;tE &ݽc3WTR:þ}@1z֯ܿg{)Z͞4~TNviƊ_^i\,EWxOpV<&$)1a2?}J=zN0ܻwoG; 1۱eíki_Ntshog1Ltwϴ?|hϚ5 &?$n͊E z_}fŢz'n?{d͊E{vnO5+}x[w듟?[j EK16Ejj L~Ͳ^yJ bX5׉Sna<'pϴ[6fOmK&;}x/" cQV~߽şd\;Ǻ88 1cٽ+/?RoC7qM)R*fY8le`޼ugQA^~~DZtZ:M-],{رR##[w2(W@>_ ]zt}1\R4>&\]G͡8{99y\2m)(--qb֭(IIɜ dfvQ~^9S3 44G]5SѮv94;a=ydo5T|ƹ{L:S3{νZZ;m1|*.<$,--s L?F0}3tMQQx:FEU ܔKTTT] 2ԉ#n&2~sF;q,AgBox 1KE+ r>|d@HE<Щ7zp:Uk;!p OŅ6&OE;c?A@BZӐl>ᵳ5 5;l.89 ,ןBD565150KIu L@$+'O56sw {<&eAޞtvQFzYd~s MZIc-MtZMW0 ?^WWrU[GS~ `\hga?`ymm}=gT c!fϜBmN;b>; Όuc8~䀅UW]T13l쮄|4|>Ie\z^zWuû!vQne_?f"/C~D3iͨ;ZcM44l*52|HnN݌9˔FZe쇩&ގvvq%"it/SOC0yhd[deu[7i5wDu{Z0Hii J\pXΆۘhZR DoV EoW=]ގz5si"BCN8[j)--8(JpFOm-}}HGi楋Q^tB3=|}&qeo (Uʡr ϔSohw=?Ь \te\6NRRd BZq[[|agmuR+4ܹ0T~:}{1Ka6px?~t)s1c݋b+Jg@Uk7Z|Z:/oIO)B =0W qOu_-;܂z غaڍ~6FqQ9ʕ?qaל?B"9Ζuh)]:8Z 1N,O!|ܵ+=~ &1lHqQA"B &,:؟S$%% TϘ6~猬~ sUOUE-#-Y2(khhn\܈dmwR&=e_wn^>QFZH&^T-&abjmkll(ReЁ]m׎M%6HBbGQQW#f\Fw֥\WWK?~`յtQM"#3%b"Z-NMquKZR3||C;͛but|*.|_R(/~~ӆU>}{$c{xɣww6!tttLR'O.4Ϭy޳ZZZ?O>lϮ-WT!3 ?wώ <~%ȸ} tO#cSױ2ߧ-7cKp2Q T60+=`̌j°T;8eʷo^xmb333s a򾔖jkpMM |N )o9}6|Kݴu!q' 6|T1+ʾGp8Av߻sC]C bP'!ee;n"?W54"Ðɺԗɼ/PS]ðMWvӉrI ֌?ȡE<~{W.]B94gg*ʾ47#K-e Cfo,[mu y ' qUzz<?r0:*r՚@B{ϊ~}d:qH3qeenݸf?Ȩ1ee Fy͔TDpwT#o_+*SQQ~gꄱ#^}ѡV _o߲D"O6CRX,v?<&O7k͊EWnIE=tmrN}|WGbB\1O_V]C32UK΅ :r.=a2ޞs"r{iyUUU3& u<~2߻_D!AJ lVP W)x,03k{ea\w`O1Sss3Hܹ-uto߸=f_bn\t`>J6Iֽ'/^L| X_7d񷄐Q.|%##;cxA f{YY٤c0Ze{(hSyĘGDiSJҷw@};ci>EJaTTՂC/L?j1cH:'NqBRc/oO5iıCjTc1(e.7OyΘuhmM8"))=y9=i /\Fb]-ZfD7‚CY=xz0ǃJ^N:d].+‚/EIII=C Eq BD~*.rUUZZ$.MɧƆNj ;::Ps(ɣgO )i=xj׭Z2kPS]US]%Occb0ׯF9E?q+/̝ƵW)0 s\:# ᐾò IDAT=Gcݿ}dF^\pZ__wUk6"s-,T \R6-unZ< EoXo;f].M0du ˧'i3sKt]E ˭ߌ:PK[W`qN#\V.{dhllH$ZOsV0Z]UYkdlm)@bBfS_  D7jk+.,/" _L}f"fs1,ıC:zNA,5:0蘳N6߾V|*bS=1LGG8ٵMQM]ІwG|𾱱AM]c+2"]mN*;2 &%UU2PV FF!%HI(iF[quڍ22 ҟFz}qҾ9ԟGT0z:*+.426M~[&]w5UW҄F52&[\QaۨaLu'sٴmyՉH 9sû@7o!Ym҅s.uŜJbEu~:U:Pm3S>Oj` 'a` zC[;%rKsf/)=`N @Mu,(mNd2S3@W4_p920 iuQ 0 |*jinN|v/2Y} _[y~.!鐻#,SUMCsY d0JJ"477A$h]Q]UI W|ڊ u l?{)w!/$+#bJ>)**>N#zdw;܄D訫QOAjTTz#lv}]`zC}`_ &d !C sE ]!++7cɏL=;6Xb9 MVvV扣A1`v v%G_s|DI/x*B_'iV *.Ysgbڀ8qp$|*(ҟ`nnnTwӏ^"|/_<^ZBATF=oi\L]-]~uyH]]G- CYYic?֘x|'JPA>M~*~5~+:h%(@\٥@pPPPPPPPP^(((((((zzbmذj ₂Sz9+RPP0ujxb:tPD}e}mڴINN%FRXjͿH m_~dgJKKWXm6;;; ǏL&_%_- CKJJ@\.8}(Ά3?BA(HLLΖXXXp\$L+bDDA JՋ~\.Hh_/i=iMM UTD>a6'iqêRm\, pa0/'>>Ϫeq8Z[[xtYYi<ȀFEE|iiiIp޽եR***?=f0Y4MMM-''Ĥ(=w}bY[[{zz,͛7ϟ?_b\7 奤3fԨQ۷oggg+**ۋG3` 2|pA 5k3g꒓-Zğ-//ի,kҤIC`ħO2LSSә3g rhhhHHQ R޲&?&&ׯ~~~ȿ999QQQ eee~7a޼y377J7B."DWfjjjCC{Oo1qO>u;ڵk'OVUU`0vvvʹ}]]]9EzGijjZ[[EDDxyy)(V2mV.[©nZ8 ,PQQ\e2(-f|QOOo۶mcƌ˗/111l6{QQ}pBee/_1;pVVVsssGGG_ի/_1bD{{;=z7xٳhѢ<~zʁn޼Ka03f ]\\ bXGE4|I@@ME޽; ##@I$RZZ߿W =ѕY\\vر^4))\q . ς=:t)>q5W `0tG\ztDKGw+GMw"Ri zq曥. 211 HGGɓYYY `deeS(LLLv/+W~PIHH|wő񜜜 e˖䆆_&9xɓ'#r\&WUUu+  brJ777lmm"2wk0OׯT3f0k׮Çqttܸq#ȑ#:::AX,Tۉ&$$ĤGElmmFKTTT@>ydRVRRFRD͛7a>x𠣣:!we{+c#G=insΑ#GǗ/_iiin-͛=P_iIi$5 C^;[SPCŽ\I q0A)񦦦666˛ŋ=<<Ə TjZZڴiTUUgϞ`0̩cƌQRRAP xcbb:.\PKKKNN`0.\hnn9wܶNE^~=edjjz)$ӧÇ0aBUӿ3gL8Q^^I񞞞w-,,@7"%%%uKOO3yH$Ν;3 5WWWөT*2*$))RYYyƌݻwE%77/_dff† #N8aaa8c $eĉɓ' lD]啘7˗/{{{?ӧO;6lo,::ՕKJJPZߙ3gx0bll,##C&ٺ=ѕe==  dkkWWO>FRϟ?}#@UU,:>ydeeekkׯ_҅o޼qvvVTTTSS۾};¶Eoo>aÆy󆗎H@BPGpt`0L=_.|õb#)M yζ&#fotb3̅ T*p&LMpܺ &p8)))*z…]Y?.%%f+**D;w=<<gff]z)p .vJ͛!7oF%=ZOOѣGkDKK˥K*++Ԑ7?X&-##|eHKKˇVVV`]w@z'{pm^2#SN9se˴(ٳg˗/x<iS%%%HYpp0VVVgYFUFDD9ree嘘ެLBBūWx>|8e$̙3... N@QQq*ʕ+ &00H$:;;6`ƍϟ?uTTTݻߪoܸX\\o߾%KN `Xuu#~{ڈBBBb֬Y4ϕ+W=\\\޽{={vZZZqqϟxRRR-Zj*wwwLL٭]VVVD"]ʬvPo߾>>FFFe# ׿}wڈD0{svJ&>yfGB255rJDD̙3;mb~Evvv@@[2or9}ԩ3gJm߾]RR17O\\\lllIIIvvUdUnРAzzzZZZٺ'lGY q 2`& Z^AY}ب0fq,z'dff~H$nܸk׮޽@ ^,+22@"0̳gDop塣I"-[&EEӧOx HKKG^|섧M~~~kkLEݻ'--mnnU)ii6ܼysǎ/_ԩSEKII]xmkksΉܕiBݻ'Y4^^^^^^CihhT*(UUզ&Ǐ/))9z+**7f޶J:.X Щzݺu,y֬Ys'O;̞=;,, [n lggzjd. ќ/Cħ卍bbb'Nv%K̘1֭[HtrrZdɜ9s'fT*A ڟ?QSSe3D{Q?ie*JU%I%%HSpæ9VH(+cT*JDy遪͛7edd0 ޼y-[l42ڇ7e2޽C90 1R6䤫rő,%$$HJJ9ȮB.[YYI"x7o:RHf@,ѣ'O )iii{=zh/,.^xJk`/_,*È#VX1*@@@kRRR`r<IB ikk#T{EEEdd$ \3[WW~~~EEETk֬Az{{c0d qooo)Vrٱ'** YU ӧO222qqq NNNFVDWt N%''_zf"u;+LGGGhhhJ)))=y$++OQox544{f[nh>}btbT;J">d27Dd'rӚ 8 g0 &΀2MO?AX555UUU߾}#X,VBB!H߾}D<[[[ArڵMMM  EFFD"]'N\v-@QQBL0)(wDYYY[[[ׯ_)%%%mm|{{{ -(''|SjkkWTTh?BDDCtqޥV$9Ϟ=Dq㆔ 1cM`RAa8$$DAAaB`^|9A8<+W(((xIIIE!۷oWWW>|޽{pfffĉHٳg-***d2L&:tɐ8p@7eʔulD]U |ҥKHIHHP(5kְX,/"֭[o޼A>~,X !!5ec ,lڴIJJJAAAAA޽{0 z'29~EEE֭ZE0 o۶,A|8q36 /^@<ѣGrrr <88+)v[sa8""BKKS:B`b{S6m< - ea` `HJNؾmk/**QSSVWWgddUUUN!Æv+'uuuYc IDAT#i4Iӕ:+0L###0RCN>gRRRB&#,۷L`L&PVV! ASJfdd$VXXHPDHBmdUF h4NGćd&yVDَ: /7q*OFdRJ]]SzW}ׯ_xmg0!tqܜ9su?SC!nذaРAfffC>|X[[A ;v)ŋ蹪bV8޽Lx(S psssuuuW`0uuuYY_xp# /^@A444vJ422Q2hCiii'<I+SeD\?BD ePPPPPPPP~s~g J? Y+ged_ooZ'%Y,έ,,[ݷjI/EJM).*1oϟǂ<)?YoM=)ȯ b] Xji~B(/+ \rƭ6CnE_^6&_%_́õ7%%%%  `.>yC`Ê99?7P`anJrb^nl~^βFa.o`XÆDT9Ǒ~^t~V#*(tW0C;7Ni'-ccu3[@$. Y>Go]ڊ㥉e8=d@0 -E1h5<C7*+6Y|' sr,NRSfSL~]`~t6ea9c'bey Yn(,{RTX0u(tSSrsF b/g::Æ>g̢䏝 E{2zނE nF_cX&x ErĄ64ϙ/=6SzA~nA~dϮ N3qD={@~7i cA%;oߏ30>Z]kk˭'LVQQ`06파M7^+Y.pojxST^Yٳq0y΢<tRa#ttZX,رcFFƛeQn|~RE%e>?$bi|LvAF$Q~-8{99y\2m)(--qb֭(IIɜ dfvQ~^\S3 44G]5SѮv94;a=ydof]/s;-)!|hSSkgkjjuqw"\qr+)AX"?u jl&+'/!!akkkbnk`"% HVNjl2.vs8yM(='4@CC < j0 $ii+[鴚$a~)vR?3HGXXE:Iz̩:CΟ9$"&%>0vLω}VMv/>pxiT\ ד{x+D J..]Q́#,u,`z,a;Ȉfw%勤CI* z74һ G>ru+/1~~[T[FZys7z{N={{muY+SJFwy.Znh '{K]-s楋QTD?[)پ ^}"i]巯B;}.48#=vQDyjeU)g`g{O'tIKZ92xDػtas[0\h?YǍv|񆗎H@.)u?‡2XTu+5>SJN7"[$ g@z&Lcp:rC;III&k` avQW.NÝ 9N巯ӧnٙ `ٟ  >YG;n3ֽ @~ ZvӪe/L}t䙢/ÜnߏKyQ'QXI޾/ܲs-T6펭Vg8{R\TfGrO2|w5τH!vdڿkDסNlV,N,HK]|1agC :j0a }")))TzƴS=gd-[\ۭz*jo2JEEXCCsF$k3˗h'߾ؽv؄=566})EDߴaC܇䁃~ly0/5u/U-دpCC#Ds +AZ;sHů_% D8sʍ8lx(oh(&9҂M 4Λy^aB4x'-r0,d0O\pb-Juڞ+VG>q᫗/VPƏzo?37lٱj+ -T(H#/?p܄)M˰-]I3ux#<(.[$%%%JyaDeeeYZZZY :%HE55˗cS'LJ9Kl-YncLVP,#spޞ?  {vmzQ]Ctk>- ݺqMZ54:a %H2~)6mjnnڶulר1scO8/[qbP /`?.samg[|ZO~1:ڴ7[m FBrs?XZtT 9W54gOŅbs}?q*`d_펺lZťq8\ H\0?o~I̝1&qO9g!ewּiF;ˡ}쓶ھkDr~@A~^^އy?H=*E{9:fLљ㿸%01{Ao#@ҋÑΝy Θ2u fL2^Gс6CZa`h;3g͓&܌RiVmRiW\~[cü^>)ok())mܲc -w=^[eOuϘ[uuFm9hE>{uZΌT _?]` 3FF&>кszik}(ɮ`}vC'uàrU6F45sV]Lp"Mд<| n^,(ht</o`ohGF,JoD_656$ X10qOCCá_`%8Q$?PW@"=|m%<'F$@v{D/-HIrZU/md'kkH![ގyN,=;w׉K?-[8w6L)㗖K[{mR]] [$ p0xނK.~w⹿'(*H~R%NWB܇,.*:qd뭫477c/>~xkH$w֜[9Yϟ>6 Vb iR{7G "E"Qeel<:c0-.*$'NEWUjnC<'=N<Xɶ޷߀}? Ӕ,ҵ[o-NNڝ `{ DTIۨamkz"cfi9'O';m*/|ÇlЎ[{F) ̧ b#M,Ee I,渺sxs LUyOtƅ7py}\e.w9f Os+:ȨS&6zcG+.EsMzs~sSBv)z󸎶&a'4pD82ȱ7;nP(a2g徳?wⅳEY%hvSW/Gy_hN[ aҔ~ !\+ `tp515Tt>mJeeŁ?b@;:l׾6zMٟ҃eK.&kY(653r Vq`_B _@ƥHm{=}liB,]w?qʂ504:[q ر& @ s,]zzjF+4=ibj>qpOnC]X1dݦmu./29ښ\xNT{ 붆J[m,Zںmnx_eQ#q PmbnD-]4o4K9{vMk3]}_v]dU| I"hNCt"@j((hu5;)\ TuxCN5U5* Ҫ_ƿQG Ω( *YIfDJ^,/`Ώ,BScX"(V >P (dg޻{ =glDԯA}}]g+F,khj]Ljk456646 z\`dlz)+뽮Z>}TYQEG֟2~)WUM'>VvWJ{EhE$}PUSo.sJjҰן:1A3tD)軌 kkkdf0L515L/ oYt*+>y&=;zt7O@;9|x\ḻኃ =|g׶CBy^0A=L|   o|ShmmV0_<`0ԕK,c? vk`I'MbllڡH}MF&?{SArA ߖ6M žc_K|U5zO"7nv@ ( @ Z@ :yZ(^v/@ trU&<<<11ݻwwڎ/t_,00000E"@ }ej֭MM-:Ba]ZF"ܴyUK-]tM)))O>ݹsgpp^@  iæ8?I!8CHCTuJNMQp޽o2Tu]]]YYD"tbȗB"H$h455 mmo>Bb޷H$XT @ GkEJM"!(}wϲUވNܢid2F`|̠rdddh4O}"!::IWIZZZZZĉ Alv>}?9r͛7wTSSݻAXX'… f"Bٳg_t 7554hPΝ;7vXUUE?i*++|eӛϝ;p7l0٧%%%7nn3P>|`̘1c͛ԩSH| @YYyȐ!)@jnn@ӧOGnnn35moH:8@ rbDh|lTK:1N"!B1ppC` p_+W9;; cc}u ݻw۶mO:>{,33sΜ9ܮdeeEDD`Nz .,,TWWTUU=|ҥK.\0aqfssse˖e^^^<`2m),0a©S\.k>}:d NІ)CS"pd%< y)?$ߧ1TzϢ*H<#u戴~NNP(}* srrݿ3W^z}"""ټy3>|o]VK$'N/FFFKgȐ!r3^zu111G:bDbFFFBBQUsss-9rdLL +WLLLlnnNLLTSS366d92bĈ/\Jh4%%%UUU '7e($:LURL6Gxs 5IL&(u戴ÇmmmtzVVVTTEQl6***++J>|յ5N6-!!!!![͵jkkwFȿ;;cǎ]t ~}?~\vdJwjZl͛7۷oVVVΜ9#HzvڦTJ2zEa]]]#""^xQRR2z.klA/^lccf׮]wSN>0g]Դ`Svȭd޼yg5jСC[gx ,|"yܼDAp@:|S 3i$&RU裦ksfP *NfҔh7¿ٵT*u͚5AAAAAAk֬P(uuu^kiieddHRSS:::>|P0448p&633s„ ;vHJJZti[Zt@@JHս}v|||XXɓ'*իm:9sfҤImt$IWW{muՙ?;=|pi's΅thXIIiΝyyyx<~ܹU}I$ґ,nnnGݻ76l۷\"tjj*HT8'7e4TeST6$Yw2U.r\6Bs%>OxH$Z~a޽7l p8F/^O8:22EXwrd2yƍӧO_vHE z]ZZ_]]-W8144:th`` ˻EDH4p@nl۶׷g͚vĉ.566X֭[?K0kӆ<ƍ4i4א!C"##.\^zUUU4ԩSϟoF uVcǔeܼyPQQqҥɓ'x'3hll\ԜP!yZ+yр&5I hR#}ހ>$Iοj|4u _b==="<u999ZElH\xqss3~-MMM2#Gͻvƍ#ZZZǏp8SLA十㤤dȑx>>$TWW-RXXvȉ'(vׯە+Wt##>}<|b.ȶh޽R+VhCYYY(VUUM6aEe˖2݈M "N2HGsH$ھc'ՁB#"( D({yiooF޼yIIIFFF_5Z9EEEjjj}0v)|gTTTX,9U(@z׷ul[iq|-jjj* X_uD:(VVVr8 !2((ȩknn]v}}699900p޽+wƃZ$H6\'#flEkkk|~[x<^]]`|PE$$$`oҝ %bjjm=y -R444444F.ғo`(x $ @  Qd@ pƆ@ W@2l:mQj(` aӑN [7]xU @ tr¹'fe=*N.>=-!G*SR\sS컌PXJZR[5CA~U?ZrT$`Ok?}txzݯ@ o|Sf׎8A: ! PJ$xe7xEQ$&KO{K32Sm2`f_1K!H$IEUWה^L>T^. g0Cș+bTZRGpC EߧFl)br(,o'4* bApdϠh]].-Z{g Kg.#{ r!j Ŷ飫ū;rhƴ7⪪:8:DF ]@z´P(|Qy 2ʲegxشˠu]xM,JAݿhjf>a4 *4|D;#cS@yD_&sxd9NgyYyisF媶HlO0e*;t0ı#t:cZ›.3n A MLh ]PP"c,=ͦ?޺ɠ3**MLL >~xFFkߺ:a#»z9읰ztȔy*h|ػ4g/5[+{o)3ml+++. O~k/"NT2S'}656ZtS^+kg񛚑ZX9gKԸa͊^: %^IRPQ.XL$ :L֔9qU?820a#YXZȚ2lcpti' =`+4e e2@X\-,}ǩç̳x &?/Wf:Aml5@Qtƭ32y<ޢEi4 DrssMMoEz ߶3sy'O<ͷ @z&#l ;=䱞`@˩`险IϼGhMw9u⨯`ll]:xHa{{Lz=Klφy㤼\7}Ä{x1 u86z2;-u`7wY;sbc ?}䝓g6>E#1fF6pYK|m8kE7[ij`cs3E 9S&x5cbYo ~IVVVh{@UG>r:.;@)C*AӔ>VD?ں+FT !ҺՔp `;_002kjj"ɯ.=%I/&x6PR\4n`7W9 ڽ|i@qQX/w6{ ˇChj wͻ55oTWW=yˡϏ3A8=b'}_t9!n\9ldV~+]Gt:c "Nco/^2tbH۾e=jaH)&''KCCsOKLyvgN>ZfAnLhپ7nޡK㧵yKg\VaA g3rJN{_vӧ/YW.{KlxΕ q::GB/8xw%}3`Y?|ˡcyHӧOk}І)CS"pd%< y)?$ߧ1TzϢ*H<#u戴~NNP(}* srr 3[7ٺqCQ"Yv>{!Xz}}ݮ=)+i9X8ښر{`xK+gRIrJq#KeK[E *,R\K "{=}Ƭ;q12>¹f|9eb½Y,v3 W/u-;V-_D_UZR[>\lf6;C)Ca4NaP*Q5u l b3LI'3iJ4e_ 7)99ٵT*u릵 mݴBeee~5jhjfϐ~Qeijjzik}P7&6';o K|`I[Z^:< [K"Ortw"#"Μlp{\mAdtȈSυ7-N@Paf~XFF&%ҷjmM- y 9z A* 22ot=xz&{ނ%gNiY@ {ƈC=ߍ#Ӌ傲0q.# {O;?#ߔQP8 *MQelDS}5jX}e҄wkj劵'\< ?P_WW"_E"Qu $I[:PQQ`̶j6]FZ]]jjlܸne|ܭI$%۰!*.w֜L9Yϟ>6 0qUwtr"b/]fْy0';H$8wڭD-c؈3a55{kh:/+=q{|}>yT]]%O`9X[dئ}dĩꪳa}  r =WI B) ̧ b#M,Ee I,樔sxscD"HD".߈råYȉY踺؍p ˷coZҁh'F`6СA8ni䎓c')uok#;)_㢹&V\U_m頭Ë\Pg>Uׯtx; tUUU4@hkbj)}4{mvhO!G>E*ȱ?o\üwo\z!2\qF 6n6;8:\OqnZÇ03bȺMߵ֏d7ZE? u^[[3b7D" afӭo<}d{Iw",2+뽕 ʄWX<,6;bs r_ςTԶE"=;5lݝ8PDz{/^ǯZ+t{Ar2]8QiԷoV 2/ :j`uH EKj}0Vt_ѡN`2Y8@ڒl=NUcK5gVi=榦zl])rUs{D"D~ifg <:Ǐ8ă@0tD)軌 kkkdf0L515L/ oYtxF, IDAT*+>y&=;zt7O@GٵE湹&>M*=rH~S}a H ߔ(Z[[+(ǫ3.n0u{&`+~c}s` r~AOUU)d2IIn񅕃@ B b/%C*z= 'vxtD h@ HiF@ 7@ RtP(\zuUU_% @ gLxxxbbwƏej'}@#).Y:65Ocuiap敫Vy#77wҥ6mrvv<}tΝzzzݯ@ o k'L APT"GSE(+9E`wܻw۷ #55`mm]WWWVV&Hx3ݯ&Qe##6R1eb~HIB#8!@SnY*XMz2L, 4\.A )i=D[niii9;;vY}:=-----mD9*wʄ>?d #""jjj&Nhggeh?7olK@jnn͊A ߎbXB"W4H>6I*%zIY_'ԉUˆ8!V8ʕ+윝}N<o>@loorʮ5vm۶x#Gt@$%%_̨( UVEFF%RSS1jԨ$Q}Mmm;'֚@ e%bq$" 2~\IJ J"v)h@@@zzzzz:[h!H$8p 77, LYYY111@_Looɓ'o۶ݻmڵkСv+Vȕlnn~䉳J)֚suH@h$hzVjjop0ֿAB!JN]TQQpNNNd2L&[XX#biiEw2hLLL􊊊9shkk+++KlmmL@ |ӧ&LPUU1cFCCC[Ǐ?{,zƍ^'&&N4 {+444SUUeaa͍ lٽ{Ǐ1cƍ&&&6X,vtt,..[3EFF5 {=z7o޼yF`&&&"(''Ĥ{:mڴcǎ/// w75Fp8666mu#H ϟ~HHʕ+ cXr{&HW\8p)Sǎ2dȧOַoVV?D!)xeHX?lҋ) 44bIP,AQ%Z2nnnnnnK.}IJJJTTŋDEEeeeYZZJׇȘ1c QVV3fLvvv\\155dH?GMMk]cccuttҤ"` u7++KCCc111t:jԩ#FW\QY? @,b_\ 3dhX,x񢦦fzzz>}``,tg^^^ ^p\~Z1Z^Ǐ;xoPd&ĤS(d"5Ϳ+&Ge*AbI'!4YkkkT5k}||P=wܶm(J]]]ffWO|b=zTWWÇFFFOxݻweg0B <<<7nl޼9--ߟL&])///++իܢdԩS444|||h4S,@@@lȑ#׮]K"Ǝkgg#GX,YERRSSx-@P5%X^*uEgϚJ?s >jȑmWNN'O\XXOxH$Z~a޽7l؀&h|>ŋ](==V||<@066644tss;zhRRǧaϞ=coIIb;$ 陌9z0`@iiSdMGUUU'Np8ߚfͺ~zxxxСCz`gAASƌ<<}*//oա^ZZZ9 iii555 r*++wGk׮]zu>},--o߾MNN ܻwN7(ͭEʬYl-۶ml^XXX)@oFQǫ3oxq#,XԀ@>Krrr gNnnn 2,=W @ |SC#}@ 7G)@ QC@ !T}U6 (5zOH'Q ­WWWWYZ.\˪@ N:t\_2ߍej'R_@#_))ٹ)]F ('$PXJZR[5CA~U?ZrT$`Ok?}txzݯ@ o|Sf׎8A: ! PJ$xev$ 錌Tg: E%?W}sZ{>5`wPejǢR&.WO ttte"d}="U!r>bi*{~HIB#8!@SnYXܝSUaa~c}=LQi $}FrH$7.N@e?t0ı#t:cZEez(ގ50=uJ7T&#=uĩ7Lz! mx($ _&={xނ ޿KqbwnC<U%WSS}T&9pǾ ܌\)+\`[Ss sf(+8G eeYˊ~)H(1ѩ죇 ߋojj4530qJ}Z__w⹑rmq\A kb==}\]U1l$`?gwnNv+o>8>x~{c*++{{Zܢ߽skJP\TX׹މݾ]٢Rik>N3lۖ fupwBiScU?}sSwqM,]$i ^HQ RTzE}DEE@+R\PPAJ I'eyrE}fvٙٳ3gf8" \f}*TVV9~Μ$'&(%s|!/lٸN~3d^NߑqSu }xNylsߴ9yd'+(^`ѱ}_mM|D+-/:qg%9UD/]89kdeKs Kj12 r.L>꽻1Yo2gCNNJ<_zTt˞ A0g5]ښj )}[ז~*ALSK?|&6lWTʫ3gTWWkii8iJbrҋOu2ߒbyiGGʘ59253oTֻoTQS?/ ?=TUAL<ag[<`آJ˩#BPEeȊV"/StftuuމZACGN즛^ hna5 CC- S215Cs/\ïSÏ|uѓyy_D"+e>.L۰ulj$p |/q8M[ pr\JR;{ 'h߲a &cܶ_53*ڶ{h]*6y]]p3#3<] Hu;ꆙV‹'֕\U$,Lt23=vlY }*N+f ՕϜxJ[UƓTgOEmrPk?-SMؘߣPum*ڃh4:Cvl5YN~=[7lZ^[[: G7ם VPTLJԾx.\N;zNJJRطu<2Ϟ/i|- a9󋫐H͈㹄 XFR=>},kjg) u~kdr][vU_-ӬzWU|3Y+mxxxlŃ` -ȏ#1(aGϭ  (,2}*~~FR1?wTFIYc[[[ee%D?K"*++x2;9\r0T[R""882o𱨘8@\;rͧY TWW&?yS`ػQRjM,_<ppW1Dbb̧jjRSc54~{,p#Cf911,uaSS=;47IIHJIGD]2ߩ7l}qI`(L'TUu!fg lW}+IB!~_GG{G{[aa~yy!m/_:wiĿf~ݼ~f=ixI}}%F&}{1H>|||Vmڼ}שAx[7|-y9zhp|W::ag bMRPPh -60g IDATފyﻩM>dk3>h4L"=wVEűtn gϲP䤄9sς]ӫJOU%+z,LrD&%5K~޶Еjk'3} 042c–dB86d1jQYILr⹘G APRRdɠ47Ad`h.;,]w#|y\' iJ߽ 87ja:v kS38hgöNv^<B3ekkz{z @0Ձw_(Bt 8,b: 2fBK-,ë\z =M{U?Mrl3!*zښ*^4ࣇPHedc BϏAˁ~׹%şXǼt%rf_S v4<)vMOSS#}cB`mQ!F#s=}>JH[tB&A$8i>SA~eeEOO@%K zxGp8\oo/X^񃮾фd***rRvVLYnxttD*+W.s}NGr @^nSP@"p^7O...{;b5)،+-)>Ȏ/4#-%]?Μ8e~*겓^%{x/ 3E]*Yzc/3_9ħ1o%]]G%.__2s @h`` O}dr7CCOў,hhhx3 -X8w椔 lk>%:S̰lc^+{QIIJq=;mX#2>89/oOu5p,^ǛAAˬ7+(LmǣP(VbqşWNBX/G#dekwYu$OB))Ƌ E'پ. =~,((D"d}&zjGd>>>֖ǙQY7VFz RUkI zUZ2^} PVV~??n(n^Ϟ475*& јJ}Xa`dr;fooOwwWL #JʪBU >~vjc;ܙS >xokKK7޸t8; 2'h4:ė^%CrsBN;z"t ,utي58,ihpC.n ݽ%@f;2+>7o^ rσ{wz AJnåF/v@"c, :r҆~d2ytE%* W {W{{ k`p``p`q8[[bcNrW2ǿ  `(II .$$b}S-5ɢbq=qˆu&zjx䊕7PTOcϽ{ ==EZEib< D&CN1t07Bh  ާ6|Lٹ{x6BtzTJRJ@PS!`^__sKsZfh ,jPԚ* BƔb|||}A8AZKs0-?[󥡾G]C;:ڑH䈅EJZT')%=fm)7p(*+{{zR_%F\ʬ9ܽ'Ĺ}}UE Bij"#;[J /)E  +_6U]Cy^uhpH,STR^kc'?TOzZRmT WMG-.h'&xu.ס9}p뿭!.\#R|Ο;1EOuzwcb񯂵) fH$RRJZXXxLy^޺^@1Z= )ښj@ Q>ŎR|dggYȕ~[S?ڄx"_D_ ^^!/Ov T());)P(?b~ G %%%opLNJJJHH}… ˗/߻w8u ٳg߿ojjRRRRSS9sOȺÃ?!zѻwH$+D"}6--m͓&yϟ?~i̙+>铘A|||KK @YYƆ[[[ttt8+=˖- vttddd477G۷I$҂ MRSSL){{{ܹpBIITQQQnnnq#G`0ڎ\0+>|^XXMW)))y#<~bMMٳg+++×,))pBZHUUU{{{6^VVeccC^UUÊ uuu YYY]]]ś:::Θ1#((h솇͛WQQ144222 71>ٳg <~BP1~j Gm4R[{)]I`j%pXv valllnnڪqPMMVsss;v? >|@ _xqTi)ӧ߿_TTocuuu:{%p}]f^|gnb2x2eiϞ=8NBB"$$Ҳ>;00 ##O pE& TUU89~MM͋/dKl,I_p!99yڵo޼e2APaaaOsC\\ܔ)Svym7o̘1chhh``޽{SG󓓓STT|95[UU.Vκ=z>|hooy˗/}}}Ç}}}mmm!''GNNIII%ӗӧO}}}\.Dx;wЇgeeM6̬4'',H5k֙3gƐczzzii)HҎ߿9p3c󣣣322y~?>p-z=aFeFen9nhm:s{WgR:::BQQ󅅅aaaBGGi3|||_|[rxu<:*++͛cǎMLOlܸQII ;00`jjpB/JhjjQBJKK>}R-[̚5C33ÃCd],R=}}}i! cmm{n844TQQ}ii) 6)rrrW:@mm-&((C֥1Pf8[TTT@@8qښ!|/_.X@LLݝ]*vx{{0pCCógϲL2<~N+f Ϝx 7omN ź>z . h>~D!G \!tiii3)ً-"SLpXt&lmn`` !!ve{ڤҬYjjjVVV\^ KG$ovqqIKK:ubXX(uqqO߿vZ===YYٕ+WֶpB cclvYYYIIIцihX,9[LL -srHUYY[[[[111))2Gṕyyy˗/gySUU]v-˳vvvqqq,^t),,,''GSSڵksv$\BYrԭ[ǢLG133I/cc)oQ5l<'Iuv}^TOo?A+9Sт?Քq㆖Q(6@RmmA4uƍ%//gϞu֭^^]]n溺Ǐ+))hkk߼yٳW륥iW\ikkͣٳgUWWP˗/&2*˗W^-[ @&vpڵ-[XYYyxxdff“)^^^[lٺu+,ȑ#7ӊY"}3g:JKKd2/Z6f.\dii9Zaaa``FUV!W^LMMMKKx"<D^~}YYYPP޽{BX inn3gG]]qqIIIь{x#6mRPPPQQh0͛7AAAgΜ9x`@@@OO;w<@dccDŽgϞbŊ?y򤤤z|||4֭[޴ު.]t噙psy qH5<<\VVF&῵g33goߖ3>!k֬O|ѣG3Ë/^t)VZrqffejkkf9QWWWQQQZZ:k,8xxx|5'''!!Çm K=\y6 ?ˏvj 2E_% h$())988L:UYYD"џ%Hfffݸ$88?LHH044fbuѢEh4Z\\aŋ`0kkk㮬LNN޴iӜ9sa(iiiMMF++?K'|i TUU%&&nݺU[[[[[{˖-/^IP!!! $%%1 \... MMMiiأGn߾=77Tb)Dҗ?CRzzzٙB;興dC|f+sIKKXӧ-~bb"C*ggg,koo [$pcr, Ȁ[[[++Jݻw_v… ݻgmmM$;04`0N>D" 2+W000-C]b`l٢(''Ic999D"~|ˆ~o֭s̡7>`ȕ+Wr3ѽΝSWW~KW^$''gee>>>waAڵkq8<<AAA~~~IIIFI$ݻw)غẃ4]RRM(COH$رCMMMJJqz16?#CNǢQzFn)Ned(B ;*cgggggy삂7G&OR \O'Y,,,D `...ΝgX¥ ##CSSĘK588p7׮];qDzz:,s| e֬YzÆ {챰x1JH[tB&A$8i>B~~~EEEOOݻ sÇq8\oooyyJFDLLҥK'l IDAT;mK,y͛7WOxxNNDzG,?nb^7bӧOC. yٳg:uj:~rrrIII >>^PPPOO]*AAAvcl?tЛ7o_49ħxNmKIIݸq~֬Y;1)WWW>fְ3nnnnnn'O毦&(IIɯ_r#moݺo^E[E@ y/_A;|۶mpAcٲe;vXbK@TpEPWBUUUKK 7hjjBIIIܼiXx񢧧g޼yTK,TUU2/}ҥ}ia";vlӦM֭KKK9gLMMq8ܧOy@k`$$QYY]{Vl*瓐@JJxA9IH$L޻wھ}mqftx%%%B;%hjj EFFr]55nݥKWS`4؜u`0ǎ~3gMR>}ZVVabbrƍׯO:eXUUu̙'OWN;22Ν;---wf; 2'h4ԩSϞ={%s} ,_~͚58  CCCn"6cƌ͛7^sׯ!R2z455v;a0Z12gΜ聁+fx\n]yyyBBH$fee۷o/%#FFb߾}K$irue166ٙ{QκyyyUTT 18%&&d29##aUUU zv믶5kְ\Ll2,ኊ v 3`jj _@ll,lX[[{}DFFˋ>ٳgYh4D“c9\|yÇ.'''XBvxM#`^400'ETPF;5 @yP뇲R3Zw? ^chiiijj勒C|D/_4662=VWW733}6*''`-Zlhh3,/Wܴ-Zb'O|UxqA [ A)))BBBA[[{W#ps--- lԩ"""BBB՞!119s HUUUiit ~8~~~> ($$|wihh`1?C'**JLLLYYYMM @ЯDL4IZZW^1Xݻoff&--=k,KWkhh899QT* aKJJٳrʤIDEE7mڴxbz.^(**AK!mڴ @`0KcĈbX %**_HW ` YPW[sM? [@޻OW (|xMISlraQ(>TpH2$VTc76e AD}p"A__okk Qf~b NEGD؏ûR~qD"P(a ߉q;LxhQё?m?OEkP(9E7X7h$C*}.JJJkΈ (kX`}]-lHHA,+ ˏwL&JJI~)#+kjjn5vTϟ;}-Re~;Ne~ J| }KsID~BE%zp?Aϟ>zK& /t.I$o^,,<J?v&n~..>tg-EEiV6?֪,hoIDΜ MFKK5~J?ߏC&fϝ?ľԴԔ-m%ޝ9sc`6&q_2nz#rĕKY>q/ f|'+Ǭ(.*}mbj>EWX^VWk[{GA Pq ECSk"7 MǏ|VSpp̘wF[84"εmNVwWf:<<'ͼYCC9oڇ<}zɓI/_tvv0ؾٯjl* 9!΍W/och;Oپ<ݘ.3DvTmڊyNIګRC#\+*n g_pv feKs Kj1&&SL_THHۺS! g2X^3@ذa @V^F9sZKK{qN3UU^|*96ҎvE%1+kr,hMUejf[Ȩwmߨ~48h_b3 >zx,ö96;Dy`EeՕSuGB[{G3#E^_|ꌽ͓|هM7~j47[$ebj>_x_҇9k' vϝDV~?}]~Aa5猘JqvnDSJjkmKJzz$%3/ܽgfLy8?-vOZA"֭246[9Yc 8``dRA,0IxxWo܆~Z:XN1'(7wwu$EB }(>-"Mm]I"|||fff:z*:X~~ $;N  APr p/qw1=+/sUOK\vV-w\6yNI::ڷlX(+bgmfɘai,':3:"m۹a<.ꎎvߕZj7_ '13JOM` nڧbP]]I_̙s0XK?}TA,K3fDoKs*& VT9y<9r-^ .XnfE& yn3P_0S-㙎jk>#YXO1W9Y^nxߕÿmPpHSUIK&)S/JەN9@G0h"\0Q60$k9R\檡$~݊weo6'kgU @ooU ұo, s +m8[{G7 6UyYIuߞՕfFZ_^73\8}#\+ޯ{ ˳o2l4<qay9fFZQ/na"/v%W3,Kc0G}dWt (4/XFY<`] eZo)oQ5l<'Iuv}^TOo?A+9Sт?Քin1= PQ Q=88F?kO^سuϦkhᵵ_}۴EC}ݙ`EŤl M틗o>xzd K뤤/}\3+3ע_./0 D݌jvNp*11*_{w۠ho2φvdhlgN+f Ϝx dXak\ H (M5EYNߺ}WᶶָѯR^z-_ ܺyṊ篞HrX^ʐB^Ci#Ȼ3\~Be{aIuKKGTO/>' kDݿm"޺yuDQ﷭׭iv&Z:/SC3<< }#.^[D"i õ4 >|;=ыgVU>W|yN1<z9GH$}3d:!N3E@0;sРP(?#ݍi!~_GG{G{[aa~9KTd}{2Wlfؓ:) L1X}}EwQ11xL{1H>8gm޾+:ڵ >=}>=iV:sv#r2G9zbWwUM>3ׯ;8;bq8Mif `"o?f9}f+FD;e&mݸŝ =O?Iw/o]=}y}oBj %򲒿d3A,qs_SUI(_g 7y""sq1Tbb3V۷{& gDՊ֖m}ݸ˾f} xI,F$w _fvn9UuFj_,LZ/ʠ*}Ji6oWVQKJ-`4tŰ^Oȏᐓh[nz) :DP!XXNݷ{˻lqD"QWWWO0y/o˂iV6w*jWMgBN$ݳ`i=>ZC]ׯ6Q?}"g5 ՓNTR1542Y a:բ(%-f9s1V(p)zI lin Є]wt`F8*ڹГOҔso:to-֦fqvφm;즽xHR}mMoOOqQ!~:pB` RHuuY/!PZFvߌx_(q txR'bn^2 G;cJץ|hHH!T_[ڗ9<5%1]L] ҕm}M 7lڱg]ʧm4/+TIOPK{ʽ͓a19$!?ܷ@PZĝTh4.YmfȘlv"!*zښ*^4ࣇPHedc BϏAˁ~׹%şX~eEijjŰ6epXQAEȹ eD%f@TD 4  G@@ @@__ߒ^=w#8H,/AWhB2 9{@G);+y:u#RYQrkgwbn>S rs |,#磢...{;b5)،+-)>ȎB.igNtn2 ?UuriW_<ԙ.nj0?q,Er&4/\K-?/)vB3]Ecx%ߧ -Lg)DCCųxȜYm"73'e`W]]9 =Kq IDAT( d1. MZn ʽ]^0Ycl2|2IU9H`(ĿzzZߍrF/RtXy@M66ǾJN2ܤ215_a+<w P gD wVӿ$%8ϞKv6Ys݀]V\ }ҺѾ YN_7ˁs.:bqş~W~I q(c!: N "" ĊAZ6 * X@D@Y Mz$@}?nl!}|̝9sdܹS1jj[w,ٴIIQDAS$J(JPs6^RR 2ؘZOIHHR@_LsX)Iz}򻷯_欈 O:z u/ styw.CMΜ: e?=p%&\IO~).7is5R{{[kk˕6:zu<ɣp4P} t!*o$tpG?{yO RSM u u]ⷶujcǯ^aÚpnշo^3= A6 rP_o` F549f+di5U7v鱗#zN]Դ+~',[Y() Mfo2vK0i'i4l\]/][72̦&*99c`%{k LOc2nje[[[UT'r>"NIq9bpߺW:rl jS=FۚNg;IDDdղ1DQ$ CCt1Ⱥ&ڳOd)+ל ?u0IMD;:<@DDbBs2ɜLyoXdyMu6B~1@ XZXg|f׬^|I22bo^ (WrjkzJ3A2o^mmgki8eotMW6a4`HOfOMMԓ돀=?%&D1Oɯ[ 9kgOD޶stU?Ϛ>cΆ+kk1̦vsoZߥwo^sݺqUWxיğ7ǃ<8e6sϦ38{䒅-,rsBOs/p0S3,^*-#4eg°| n_alka=`BELWlr pѶV:#tX6fJHp+C[U<+ e%$-> b֨I,II)AN:F }`0cCj)F=U:; u6xzDmf`}xZ8m&$`o46RpvOx:b]]}mm_x`6ΰՊ:E%owDg:![z.K=R>]A Ŵ}XZZf%d# +SS] [cgH)_<6B]sx ]]]7$>}~1f?^L9'lX$ =W{B"i 35SL-Rܺ~%^ϓ7ߕ Fi͙/URV`ޗПpr? p j/1BkkKEyOQju֖++͛e` pT&&&&--`Μ~N1$pŎ9r@Tݱinn-VO_hˏ"lٲ+KYYن vmoooaaIKK,,,l673Bhhh @/ ƐRKOrk !ovH2=}"UQzbLj lZ7jb}de}Ⱦr)`nAUDUTTp˗/R(yyyPQQ*''z꺺::.++;ȼLӧOϞ=d2;sر%Kܼyׯƍ2?_~=...77~ĈaƖlff&LojAݻ*jddf0111VVVpD|ݻW\ADKKKڵko޼UWW޿?---++YCCCd "##3|%!>NJJ655=}4%%֖;ϟϜ9 ))W/^222[GGGH$I[0J#ڪR111111¤Wk3qrggg![XXx펎---BP[[{usssXsﷶjhh _FUKII111Ƿ :g e@zե;:"Y̆[ofQeQozzuęo;q<`lDWWWO ߎsx%%% cddt{ݿɓ FII@ ̝;wy۟8qӳ_iLBffhll8qΝ;CCC'SSϟ /l`?~\KKK<L=ijj4i>}zΜ9 s(// )RڅE0G111* }6`P( ˗=zt¤B8;; Y盚JKKpzJEEeQQQ~~~/_b`۲eK@/_vuu^^^NZP Fy-Y9Nr7&Nx8vSv*wLцMW6cbb`Μ9Ch4ZNNNHH111Ɂ3MHDDf`B? ::: ܁2G@@r+cdړuikktYl6NC bEAAӧOBq)S`gg}vxmmm% r]:R=]: HPPOWq۶mĉZZZ':Lİ0N̙3aÎM֦_]Dte80a„ue֕Ն?!QIq T36kk- M wclq/-*$<Z0)񦦦666 FVVlx<AǏyѳf͂>>>?HΞ6m䤤kJ\RCCCFFZYYɹS(ainnxΝ;yFݡTڲe˚a23''ӧ?<**L& 2w\"hll -\A@KKǏ{Ӫnt0{xxpf0x{ 55ƙ% F۷BFVWWhA`3rFFٳI$ٳga+`d26 IIITPPX`l5zhx=eʔ>"""Ǝ3!_`{Ⅻĉ+**8ŽV0ͻr nllLMM>}:%%%Mկֆׯ_;;;+++ٳg?Ϭ{Ç/[˗/:a„7nAinnnD"QKK w榭mҥPsf߯u cc?pUwww4! ʷW'!Vj`:-Qnnzz7ci$ gY38aDEEeeetcccRccN;##䥡cǎUV-_^VVb{ PYYy!mmLccK./\PWW RQQ9y$L啒rFUWWO:5((LTT4,,l0Er lvoOooϟWVV.Zd2 lٲ}/nܸqر^^^/_tssDbYYƍ7mE߿?%%%88n:[[[KK޴)AKK=3 L&G~ gjkk3FpI&7n}P(O>]l pBtttrr/BCCh78n͚5AAA;w1qqq/_R__?m4//J>SVVtQ\\~zMMM]]]X~嗀466|8{l_ ryonQUUf] AĆwTf„ &LذaCFFFvv7֭[qFqq'_̜9iɒ% (X` ïsGV^ †g pv28r6b; bUUUqO>}} T  HMM%ɜ@&ƦT;v5kֺuŋ> Xhъ+hv;v8881<<\___@!7.__|Gwex9Iq9)aVSP;a#  plÇ_|ikkضm[GG7 ׮]۷o@hoo/**z=B2xϝ;K8z eZ,**;wG\]]/]?=I$ 33[Ob?>oa@AAˎ<`됗/>}zpF 91uԣGN:^ uuDNp}IIIssRIJJ6v֭{z{Q@|sinnAALLLYY9**eʔ)p_KKZM~~~GG +=# f޼y9rDUUvd2(%%_ #M8NYY{L@ahѢ%K]gooyf fiiiCC0ܐdAjkkr[[ qn޼9eλs}򥞞yiii%$$zzzH$N^Ha\~ϪU^xP0n8NOQʌ( -H$(ED~K6mRRQP*)Js0xnݺU__/%%bLΝ;IRRM2ϟ?q$QQQCCCLꊎrׯKJJjmmM0ܹs޽/ChG,"..~ȓ'Off?|&**%22rԨQ|;}}k=='9rttt'::ڵk ۶mM$tpG?}dff8p_ *f͚+V:8/ ~KK ]]]/_&H1ƏaÆ˗;__k׮!f{Ĭ#9k5559f+dڴiUUUt:=""UOESSw(V***z 8==.駟`A3B||<J޺uk}pS;s1 @JJ`ȑ3k]]]9.:t萃ŋ?~`PP#|`b2T0dlKp Ec~Ly)i]CQQ$477訯f٥}Ñ#G?[ѣX)))=BUtex&zPUKuMOO7n\EEEdd{hhV W H[[[}}=g,"--B@+k֬^j|_]p022~dʳ롲&xZQQq޽ϟ?3 =lGW" wDPW`/F{0WJ &5 Q 3} #p3 +uV-!Sܵ?ӊ VTX0yׯ+''7n()N[oluT|" c\eddO 677*~ ?hoݸd0N1n4/:;F&s=IkӦRTo|(|).|hX'҇Mq7:3u4nco%캻(+uA0J>jck(|u}f f$}# ~SiY{_}>eLkkwo2ϛ5S1eJqQ좜 !1?wgV ]tx}WCʿ2@ Wʨ-+q S6qIy\Q^&l@c# sW`Q$iڵc'hh4tq8ɓ'ʌw 4SiIǟ *UT݃⢂&19$;~:]}'v6{KEvؔp7Fgآ" NB|8u`0dc3iY;;;sk]11^l % rU\ ȳ66Rn\}{Rr҅kW.C xDqWy_i=JL+P`9+V)S=%Ewok8TUU())VUUs#KՓʊA\q?'Z[[2_?{GE86}M|qJ֖24*:oKWFR '"JKK.$"ʳ_~x!)-;n2 1QqQ,+">2:ړ&M5jNII `0JJJttt&M3v'ٓcF 311{Ou p8<@TPϘcfn)..n?zlgֹN&&&Z]QVR7 ڏAahDĞKn4$7zpFK>/޾ɘ6&Nm ]=/?Y8yo$$$<s[Z É)*)Er8udwIҺ0>eYYdf}:4Uoq5|Y03gϛ9{#*k`@~%JAQ@6OMv  *{v/?窍P OsC2 upKQI {q}$%>ioozRSDEE wo_wuu]-*37ץxk+@GGiB?^9ϛF9uNjYE\\D 9 &6/Ņz#m^jookmm5f6VGO?u'>y:JCs.D]P-ޛHϞwo3=x8Xjjђ<5FWW1$auz͆ kVYW߾y4Alv upktkk @L\\]CcFB&MVS]uzl'{9UOEssW6 N.YKQR@iISod `;BMuUɗ"7 vww8u|A4-:bXwww'~)<={?K+keU 99cY1ciil!SS"N<oe|ڢ:ŕ}@-O 8;O ڳfܕ#"Ο8]DNte0 _/URD'b,lFcP$Iqn}2Z1vL&`RRq޸w`OR\`>Iq9bpߺW:rl {%~mM'$""jB@y(}lWIz՘Kd]UKY' CkN:q&comliq ""r1jqq9dN&UUV\·n,ZDD@`coka`m=^{&^y)2n\Q)LɼNzd1sܾi4] ۄQVd#=ACy'2?z ~J3]MvWW'gcv _ A}7s c-c=Ϟ扼mc~x5}Ɯ kWTc0M` +D[[wKu5޼u㪮ɣ3u5?o'yLq3l5Mgp% g;[8:X8 愅nj{ 9!o L;TwTqNY>r(e S`>[y]@WXTgvH~fmga%--cWP?( xΚZl\bcd*1vcixrW]7㮘Iȷo^㬳@ {*$bx(_Y3 od2=j5VB #J~[1i˶]qM4{MKiId9˷:`UfJ$x&tY,V{{l?75QEDDh$ onnP_'%-͉]TW񘺩b4N]m*gQEGG;CIYe #'Sa`AJ%?O𻶶i }::JKHZ} ŪQUNgYRP)%t:N#mj IDATik`dž066R zΫvuvji/' uJʜ=9՗JmA'a5A/ŅRg.=d2[xMMԛwyd!Jate)ϋU@bii L303LMu;6lbn1r9 uΧ=tuQ&@h#tuuݸqŘd|2h>a$\U~p2[d`u}/RCH$aC 9sX3"-%έW99OJ@ikk4*)HKKri0KrT8gk }m ۻCCOu׫oEՁ֖Lx76^LHO++-UVQ9ۓ{ Еހ0$d~Wj凇(=fӯQPPPPPPP~(Gc ֭[[ZZ6o1Q9s BB™;rȑ#GxQPPPPPP#_+SSu֧XQ>}=/?@`ewXVVaÆݻw[XX333<=*|O0;pχxQQ@lp̯X &"!:6 < 99ӧOҹ 6 b=P(?>5P ۱zfl`|+_yS},++SSS444p8yOyܲXWS}ַ1:8,F6~MLL@{Ӗȓ9,k8</))YQQEEE /))I"LLL|Ɉ#흝رcgϞ^xΝ;̏ YYYuuu'NsrrrssM Ƚ{޼y`0===3/^lذAF>6qDbkk;w>}$// Mp)rnn`gѢEߦz???h?z*9sѣo%''?{N.\P⟞ۯ]6k,%! -ɷ '''##3 OKKsuucT*֭[yyyd2yԩ:::JJJfsqqᛪ7׮]koo-)9􇨸?>((h0B8+L-!\bccmmm@?+VJKKzJ0bMMM/=<<|Օ +x555kkΘy 9çdXlq9sjMM  ugϞ]dIjj9mڴlD5ō=Ԕd@WVV ӏBh` 2* ;e/&[:Nr#nL:%&p9UrF`999&&& FKK̙39994F䄄`084`DDDjjj~Q(뫣/S{xx ,Ǐkii ɴ'֭niit[[Yfl6N )bOTcƍSLnK@di[gdd ,$G$((ɉ;m'A8L|^% (?~H$ÐV999 [n!raGGGTD"1,,8sLyy 7!B0΁"mٲ;DTp񣪪ȑ#a;wNp[۷%O'L025GE%1@DRP|ׯo446%܍aY4 ? cff&+++""bgggcccjjx 533< ǼzxxhΨoO$Mppp899)))~&R+WАVVVrrrߡc+/ܹg<J-[&!Ϟ=srr>}zppϣdP(s%P… 888|7zJ())LCKKKxxgN {zz޻wZߟÏ83DD"}^4 ^#L"TTT{FȘ={6D255={, tuu̟?L&_!))) ,uқM8JSL)// ""bرZZZB7N>maa_0իw533STT Z~ŨQBBB`ߒTlKMaرʜ7nr/x|~~~IIIτͻr nllLMML&ST… 9|ܼ~Y^^^YYyϞ=( >>>k֬QRRrwwyⅫĉnh4ʕ+Ԗ.] 6tsC*h4//gt!%%ή]c!z (MA@ Pl XAaYŲvE,+J&bE肢 wAj f7o6ѣG&QHCC@NIIQVVfXe V55ch\xʕ+l٢~ *gWQQARa`@@w\㓒`Ү^~[]p.\pfff0޽{*jff&..5P(h-wuu= T?~d~SLXruuؒ%KJJJ+**EePrrrh/^XVVFP|}}q&O/^A LWW׵k6X-.1&]7;썔G۰a͛Yp!U@NNɓ'jkk#gΜijjX3m)((pww3fLkk+{)St 2T`T;wZXX4550UUU8СC]]] 3,UVFH'])$>eT\ "FcBkQSS4izyyفruuuSSSo|}Ǐ3奥`D"@VVz%/^ZXX@Lyyg϶njgg'"""##(J}}ԩSG3=oBXHJJݾ}G_gϞWTTbx<L@%֚644bccO8s3gp+G h4Y,~%hii1P(vgoIuu5sgϞA\񻻻[[[[ZZrss\\\߿ARRK*ccٳgp8kk뮮B}69Bh@1cԩS&{ tRllcFx 'Ozxxp{q s //>a777X,֭[˗/gL@PbsZ*++tΜ9SD"1==}ƍvvvg&t3 _z5?x秥%)){ Jx񢝝ڊ+mF&MMM?|0wӧ{xxKDEEW^(##chh{Hfˣo|Ş={sY2ܮZ͛sqLUWWgggw)nh4Z\\\NNNHH[ ǧc,,, ;a555.Fe&"""rrrx<9NTT9|)SFP畇"x-Aae'L4;90k&EEA:u` "?t(ceeeeem۶̼8ŕ1ãd…ӦMsqqmjjYII۩wz{{ñ,0YO>9ZUUUGGd;JeeeSTVVƌQ T™3g$yYY"/11e4 /:R{.Zh˖-888ԩSP8ggu޼y}޽ g?χ {χwG!円Z^!++qd%\ſdRbbMy2ƍΆDzzhdd4~h rJ =!!qjժUQQQ-jjjy&?xPUUE&2Gx(A^^ 13W+ĸqjjj{G+))}q„ K˭o\r/Kau :www5kkkk{{{===aC ;;;[䟤{{>p\榩k׮cǎ 9ͭfܰs8)Q`y+kKrdδG ? 0| >}${vrrB$&&رcx<4''c{ҁ1%K<|ڵkNfee1D"ϟ3hiiDDDXXXSaa!ZTTT4551hoouJFDDH$~6>}m֮] g=cbbr@(J?lG,P#G!vvv555QQQ!!!SWPTx߀ JKK?~ (++{ttܹs'555 fŊh4w޽~ɉG T^9ӧObbbIIIpƐFlUB&?~:d*[fgggwO>R  uV bܖ-[ pRRR7nRPP6m'bx %/f~~~>}*++vi(! D"kjjb0FəcJ+6z\ #'$$~3~׭['$$]k5#[arL㥥9"c%%%'))ˏWvuueYvc-:,**҂|6GEE!l͖cȾ[mkkHţruL2bRݻw'hkkcdc g^ HFFЕ+WÌy1wEӧOg͚5k֬y1r :|/>=7g gM_];^w 7^O?|.1 %%%d2`2,&&V\\<e O@Ѿ|2\i---p.Fxkk+z8?!ՅniiG9Tbv(ӦM;{CQʡhUUUpY5;===tƳ3܄ttt?|RWW{Kz{{߿?,i3wƐ`5'//oBc  [ 0l[lss3biܺn>&Xj7ׯWg>11;X IDATы&p$ '%$$\\\u#1j//={L0AOOÇӧOJFFF>|0**w"55}0wU9]?P Acǎx9DMM-,,w*+++5k0/UIXlTtttyyyQQ* _Fw h>AP(˗6m!@K{{;\Aƌΰvׯ_޺u] ֭[w#d@p8ܐÝ!QTT1GNckk+Bqssy>6y(q  @ @O06 @n72D1Կ[? ে(Gh30زK OF8 o>a  @*S_O1%*ڥB%@uU/{xYBu^od=ً*$_$ @硌㧩 P(@BE"]P/l R>~/X03jnnB:Ǯu}}--c*$_Ca100m?wBRjX2~GiUU ?3AEpكY 6[4vD4JB:|,x4 ('_B &TSSݍD 5UEȢ/*n"Ym' &xS)Jzf+bbbgIk]Vo\!S>NԦ,tXr9J7nmJ޺Hdjnm3%a܍ Muim녅=' ǎ ?M&@\\r-*/FBREE 9~R$pMo7u 6vћB/ j(v߉CwO0aҬ}ِгgR(f3,M=\V89u\YYca{-*՛M㘶 fcs_UgS'_B]mMu__de'ǏL&Svۿ,Y8x\))#o0ñ`1?r( GJ?}$H7o6jPR`0ϟ(*˞=}ZAQiJZU4F\G~(Ox1,o=;hhj>Jf}pŧ?s-cӼ~s Jj*+˧LRpAI ̰5S@Gw< B#\t%6s?4  03: TaRʟ&f#H>nΜ:~~Uk8 PVV9y Oe%&D ظs%\ >dϞhi@ؐ(̂41>Ӫ{.<8A{m6>/$nm'8 2G|;m`8adfeܰqҲ ++wr()s> Ddp*-M)(V,[ :-,E!Q%m5_v܏Җt;FG,ЅP( NסP(8ZDDp""8 qzQ($EWoX,AgO;-]w# ߈_{ *2/̶fRfOV_`ֻa7QTT$,a૗iӧLP+q^KKh.%2f:As5zBIae*NginL\/rS͸+ݺyP;:2,bS3ÖMkaU!mWy(!4vtӵ5FȽή X/Hs#C2&}',,۫ @Pkha0ܜfuc&1c=wuӺFxUU׎>Z[S}[EULmn0~ƌ=}b~qBdÚ2R/\ *|98P_Wlׁ#hOe%t:}sC}݇$To\ @>>g\un6yb[Yςk{z7`ﮭFM ]*3 e4j-s  FLpP6l h;0vKϝ{w#yU}e7՘ć=J PVZ̒#Y~㛜bG9M!7i0G!M˗/v\_Tnæ!''+6 <ŭ pW3̠k:/2I ㇁ϴ3) sm, >1߻rK8{ GNfx5\YsçJcCjkeʹg R/_~vђFGD%7@s)஺Ww$ߘp KQT/w7~a^g;S7560CiU+GpE)pʈ`p"hqqqەBBs^FE QqIk"Xa4-V5u5I& بk2CG)3K`4`ނzX,lwٯ)6lb;NDDވSԢ46ԛMQ^·Z>HINڸy6EWc۳Gy7YEEŰ"X/++bfͱ511+.*-PN^!$Yy}W#6G h4Y,~%ŹP(vgs2jk#݈u)%??m@? E7ט{ ^ 2m@?`AJrK* 68t.]JZ8 F S-g3m瞨KbϜ:6575&ގ7<5}'~ٱvHi,t^'#‚Ws}a)Nw .&ROPwӅ }o;{nnN6O b1r,n?@BBB˜cq 2/[;t딗o%%l-2Z[tzdxogm9p8~psn?$e Bee億0LiIџ/Ӥe,!J1 Mͦ]c`hnޠ|5h$KQFa1{"DM&:_ j"x-Aae'L4;90k&EEA:u` "?*c>e3uf^^^⭸un[ )V\YYF_\NxFQQ1yx<~x۳3`th_v DRTUUrs%`h MC0JxLIe S,mg_SwBMMe؀ 9J;O?Ay ׻5w/vd$so^Gi׹mޱkGͰ˒TS eIU p(BOߐǢ*%!##{/񖂢Қui/PDY)p^qӅb̩x<4}X`8N-U492<eޔ*]`mL3cUHYjcUThɼ[hD-XNa\qQ!i/d̚33mr[CwB4f\׸1=H$DǍ疊@u{橓G=`~KEK\?`Dd/.9veD8ɑ5gӭ#9qQ6уDȼYtR pEy$JFV`qŎ+G]WPt\}0|63bG46/ZlYÏѳ6Eh L2%{O*֝n>2 *Xj}_;DY@wW=znxMaq8KW;_OmsG)CaBHHHTT츷47 fۿn𽋷dT˛ь.-?fw z8A# (%%=\<=dddr2Y"AVRt,ă Mbh4F?rX_@у4MHH@mnjxshY<))2#a& ZٯTp'>n) ufScCڋ_vӬB>- qSOO >==ƆQ^ŷ8|":24<'*+!kjN0u=:2l$5u2uff;zzFՁA׮]onn:z87 <2% IO<9ylֺeojlhjl`ye *zz 8Tnm۴߭^tAtzKsKM-~4 ŎUVavu5q7z{"Bاax~#s^8 f}䧏eo^͞k~yp.x!arDuQrv&kr'aq7s_o}ҘY9NX­jppvB,Kml/.*Ԣh/}5r޹ goD2'OVVx)\t뫡ѿ6mMxzTP(txl[7 J/$N2^ J[^LS+]]|M̞ͦ;x+K)ӄ6Y  &XOIuo;00Bx=ECpڢy6o.]8{$a6QpZxPp} IBͱn81S@Pp$7641К>NorsuZv䵨kAF>J:v Cf:J.w>eN&ڳGC8j2`Ek1ᇼjGY`&5噆 v85*0{H޼q ׺o߹q]U]X"̩:9-XmZ уB46lܺ} ̦q5~}^gP&}uJ̘2IoMVuPfGO /AtdF} %03pYw:w><\~o^gdyu0׵n]TxjO7uЙ3wj%Gx=K[wM4647|% Z %w8s҆N/3RO!M2$0r91G3EGCu ۧt`~OwwcS㋌lu 8N\AX`:%%E[[POO7/mBBB,ҚſÅ uXT֊FTNC}"c[N^aĥGKC *Lyr559v;~g*+IjC#̛:E1_z{郢b43\ z{{z{~tv~EP7Pyj__YY:C.~{?Rp↤N2±3֖fwu h_F?M r,?**}*Q a( HqQalLdgW%tafdCm)W?9z̘*?tVJɣHN߉>X/]% : h4D}K;Y`gz:#KY|$I)+˳~_C M h('F^NaA׵(0UK6;%p1B[[;yxPS]wGtrYB\8T?:~zxIQQ[[7~\ѻ IDAT:v&^2?ɣ')O@'@!xM:`^ @ַ @~*C gcnddch7q @#DFF/^}%P?g_گ<)(G s t/[!Yܶmۯjfff``:y򤿿ڏ/ _Ӂ_jOQ,| ..^PP000jjj錯j577+++$_Ca100~|w?/'~nX[:Nߊ?yѣGYN.0eA {Tz7u:o]n^A7L DDD777KKK)))77^I~F=yҥKyyy4mƞ3gθry@7n܈-((hll3f lOAܹV@fffdd`?~}vtt4 111QQQo޼;v,5111==ݻw_|QVV-YBBbٲerr)99yo[[ۓ'ORSSMLL}ŋIII**ت;%%%88555:::,Îp$:/p侾02TTTFEEcg7nê[n˖-8ɱ055uܸqtuu)) FJ՘FV7-ߵyךv7릙 WNX*!T*]cɒ%8NNNB?Ν;ϟEPrrrx<~ɒ%ٳJKdddF_3g*++߿? `ΝǏ9+++G*?1Y?Likk>~S&L`bbR]]  &xPTTܲe˥Kg̘;.]x))ׯljjj``l2$&&#rSS[:W%df866VQQL&O69NFFܞ={oN ;xb|0V[[[JJJ $""LHH,_AfvZٳg fw4cƌ#GYfn˗ kXXƍ#""¡R[[ 7n?9{UGakk WXq…o[$0јq0C ;# }#_5Gj~*\d +߬ԴaeƍCP/^WWWGPƍχ3M#THHndWssh8,Uϟ?׮]#˝ G);[lQSSkoo{{{MLL-Z^X!2 ƒb,̙#<8q+xDr( x?2=zyiaaw^x|YUUUb {2DDD/H$^|pBiii8(9uꔅpS ;Pۭ122>qℕȆ2# e#࿵'ܐ(D'q?JSk{SK[HXCB0! ')))$$djjjll<~xƏ?qDccc %))7IM ȣGXϟ-Z]]]w"ggg077wttO6MNN0Ikk땕%$$&NҌ훛Gs ߜ/_߿ Uꪨf͚/_$ ٳgӦM7o(ʽ{K,!)))PƍW\ J.Y왎3f0q8;wJJJ651%(mffgccAI$‘#G#gff:88H_t -[FPhTCCC˗:ᦓwM<ϙ3ϹSyV@oo^PP [䥬o߾ 6]^YY|_տZVVk{ӧJ߯믿*((ddd:uAVZ;7n̟?_LL ruu%FFF,Oz19sׯKKKׯ_ϧBRRܺu뺺vpi/^}ycooښ*%%g āTWWxΜ90pd=93/_xbPPPIIhQQHWVV21/.CQN-..nSH|˨}!*.i` AD+Ơ?*fcc3i$uuꦦ666Wn4x{{߾}FFF/_2!Xx!wyyg϶njgg'""}"##(J}}ԩS>}:K@4***<==uuuuuuo#L={^QQQLL x999, `oo_[[kffVXXPPP=qΝ;Ϝ9íH%hfdMB#oIuu5sgϞҒQOAA00((ں```Ēx8ں HJMh4E f̘1u! ޽{/]{1𷑯1>>¢ɓCJcZ2޽k׮t*.// N0!;;9?ijj^~zj)"qFOOO;;;h42~wҒBCCC__ϓ={+ˡ5DŽo>uԣGX|4O0.`+93 (-- ȣ'gիX,s"x-Aae'L4;90k&EEA:u` "?*ceeeeem۶̼-[0l􌌌I .6mmSS%#<޽ ^%`:,777g[& JeeeS-TVVB٘1c8 J8s ~~~[xHNhhh( #sK׷o>E1z:u*55 nݺw(͛gnn~]8Csggg~~>< ܳgO~~> 8 )//744V!++qd+NNNQQQX[f F{a^^ލ7222' 萪cjժE555YYYݼyq ::::::C!SavG*2`DVVASNʮ\ڵk۶m{iBBÊ+t:`355mڴɉQ!""MSSs׮]ǎ177yYXXijj炣k2xq(VJ a0h%9R2 Sg#td`N@DTG,e&77ӧOa޽NNN;v wuu%$GZZ:00PUU5##'n>-K,y𡭭k'Ddee1D"ϟ%##03|p0J8z(Orrrffܹs}||ΝmTCwBKKkرO>e8Rrݼy/_d#q_|a_d """//fmm=g8:qDS6EEE,Kư8G7K.]t)ӊpKPE}iXYYuvvhjjΞ=;))֭[ܬ)a!Uȑ#vrqq8bffcjݑJEEESS?f( '// ###a9s08* ̫&%%9::H$>s3XY" ~MMM uuujjj FHH7h4D"׳w!ZZZ m3+W 퓐?~C:::D"0… u8~oO ș3g%%%pppݻbh ᎎp8RA ///WXbdeegϞ]PPN'OHIIs0T`?MQQS{{;Cl&III%2{»?X#/-[xyy ig鳥P(GXx0 @_C2D&:,;ƄȃТ E>'a={-yxxOWW.##CMMmTKSO>qss3LR('HBBBX,@OԄbrӟCeee{{&CUH$VNuu$mQEGGGgg`"؁:B)..noo=3F[.S:::JKKBaCP~JvwwS(^^^Ah]]]L3ikk`0p0my՞"%%%s466p]RRJ$O5BuTUU%..>w )P+䦦&Yir̟?f(?&CA"""XON/Y:2{톆ЏuNNNffc|hz{{#""=zb"99y#aaa蹪(?=X[[:88j0?A:"|||x-A0 tz_% ʯޏ MM͟s0 ` e)<&&&??YZZMBe ,nPPPPPPPP~!2((((((((9B?\Sǯb~hPPPP~{y1Gg{kkM+ s)Z˴%ŅN3挮@lB[<Uާ58N&>;rC[dL(/޶aێ=Ʀ淢6mxq3r?_$_1vB$pX `@*?7$M P$;;; | -#_: G0v79BL\'3q;nlqr ~_P~ L4;BIϭƪC`1, P(?7..׍B΢P(?SUUVwwvnʊrfHϜ>7eѦF( OfeSPPVVo3_'<:/7W>>~ _F҅yy3f9Hr|ڛQAGbe% gr;xy||9^0e‰vm&~˶>w;?/WPPzw JcǍg}zccDڛ MvZ.+ <mnnz"n OhE7905K+5%1)1[SK{Bⷞ;;;nݸ0613$[7z%=#rs~,I1?>ORS״7>O.Tj?AEUn]m'\\_a qVw|q}}}g~}/VM]!?+.!ilb~{CUz=7CI )=Q =HP IDATEm: J]RO&g1 :xEccHHHhhFccd k}YK\f8zPVV ?!P(gO]&iD3~ۦ5==c|~U埇gO3te?8<;:ssy)E$?uJx[KEj GfL0)7'[IIȡ}sgNas|%e22έ(O  HNLذvhC*&fwt09xbnG[߽V5W/vtv{ϴ;vpRrrqO7771ԴygGwnEO4!]XwyYXɺ=\:wyy6ֲĴ(M:]*b]]>·Ylc'ދ50p@{W^H([egukMYK~.pclxss}s̡ ˯YbDYe2''ɓ'45v4ӧҒ9Rߏ=¦&=9|`O4j*bC>z[w晿pK1OK_(5OmCĵ<[TYVVjiݬp8\nQ%ڙj粰. eu-b,l7m&A/&}/d?qɉr]13ppp|,)đ7Er 5y^}C%EOsYS5/gU~W'na"q/c˺M[WXhSEFQb{0}ϽvuA:cGJ]Qwl[[kI-Mw"TjG`8?SV /b̴uմ\\e5m-Cccc /3™Z< H|cpy3GW̆^Km9ܜsS&]xbY)̰&ij"mXJGCNAJ`1 L{2PIFٱa$EuZZ#ƒ7mI?__MM$/*RZkW/oiiI 5LXt7%Ո03C'.rV0NM۲arW8Bjmm86'0Ҳ=T*p3# o[2[U^t߱myx4i}y֘jزJhmm  2݉6'f̚Aǒ"XC!`0$woUHKtuukANi)h(K;r``o^-Y8GOKT'8 3m&L,ʋ\d:ᝩd7sNV& 2@kkK2xw岅mqIny!Zš>6hm1qGqN%䉓aYa /hjn:Z^n#f;ٍ޸ywmll,0׮\lj!J p8|{Ap,$:?p84-0yAvAU +x7N!UVJKK$%vl])or ‚w Mpu2Zc=_db=@Q܉ +Zoxr;tIL\b`¾>7׹Zge.={>o"ϞS7/ Ȧ lDTUVdd/d7Ox߳'~_S\TU$i%BA`b \x1  OUFQIqYKKK*))+)+q115۲+)1.$wN7nׂBaQf6;)?@YYirbc'GIi֘}pbV"!!ၷ>{%kbٕ+ 'Øx.uH;@_ߴD_mͺ-;T&=гd{mmyNcN ,`s)ammeCeH03 V| -lCK?/]u롍ݵ+N2ަ2rr1~>ɑ&,,u5b- E~^@j X ޻}&Ngͫ9yb4e,{\ЦMЄ@$@1\l4wLҳxersh,77{Lo݋yT%vӘ1kܖfF8Dr4n ,W78 @cy:$oi&53gϛ9{cp.+Q6曧c-Z03sc55 AjkDTL/MZ޹1nėw)ƦK9?{n}"eyrr00mDڳZ/׺{ePc-rsЈeGu?IEEb8aaaK6nŊpp s Hr3MxxVC}/L>XWDOЁ=77OC}݃7G,Tjo_^"5BURQMIz PRR?1gPvuu9uN$֌ ߽ЕgOBFR>yHEU]j䥎֖+aƦjև_TL:}8ܞ u^K]v>oqnnsAa"n߼NN ml&}#ʱ#B.'jmMuC}sXӇ~:? KMM-YA$jT-m^!+'ώ۴qVW_zE}]-`r[7L{ JzVUMnnmmp2rߎi&Ly9$1;1Xxtwxe+K?'=|*-y6eW{vncx<Z4{Ӳi1`D(,8;VNY9yi6`yGs1uL{N&ZZ+(*CK0`he𾵵E\Brm2  <}6gNc)m'd(` u""!aFX#,DC't\zCԭn܃=1 L)T*//_ԭѷy KuP55 :!=צHQ|-_t 032iɘ&VXe AB"MUEg;_{5⒆,#,Ũ|SgN77V baWKJt5u5++/^a6-Y^]U,A$TMԌtU>R\/0q2?Bݍ@ZZ{ٸuSg{Tm fc짛jo'0o/w5Q1Wbki7[wWıS MV&I#Z{,''+ Xk+6f|x.Cdr ;Ϛ" wAMegYfeMuٸ=(**\cpƛƶWeO<:=MYVxF/~WSEiMv% g[YY8tz 8h rs=zs޾o^b]'LmpqQ>oeYah)ҵx#]=->>{`0C44gMm5֬P0U[tҷ2ވ!ojq5m Kܻu5^;/Xab^$l۱g$y1MC66~*-2쬫KJ}6S1Tϟ  ]`HBX,MX,!Z^>>Z_럦]M]H߭ښjq IڢΎNP)<<Di]]",2ioo`0cjkh_)))RPTb?!1X1k,@cI/ӵɣ\ &ZU@A y%մV2By1L2E]hooc%#1 o(S]UqpZ^gdg'e供èZ'gNh&5{x+Q1m& ;76+7zDA?sEjq߿o /ax?ӿNO ;ΪJD'Q2( ArppKpi$KrXm'k}mP:y*y٧O3g;ӯD%;砡9-(O_YQALQagf>v z$(2PzU[ @+Gj :pFAAAAAA[QPPPPPP\wcob``iH aZe"""?^XX8gΜMhbǎ;vC kevn[Ác2+#۲mۺ/$#eeeׯ߻w +++==gϞUTT"O0n)=a1X,@TpdD(3:6e < 111''/77Q__OR/@zzzdeeyPT*:sa+tNCCCOOݦߏ~O=Oy#gҥK0iI EV4qud*'`1 Eqqnz2BLUU^^I xxxBBB8`0<<<#|L{䉴͐8qܹs|||/޵k}@Ç޽UTTTUU4iOxtvvWbb_Fyyyyyy,v2ryM3 IIIׯϟ?/((4iDkk۷srrlmm-,,X޽[__PRR?~<sssY >- 655yzzGϿzj̙3ǎK+111>>{̘1 .֓rGGǵkcƌa*ÇXZZZrʺr--j*;i7bbbf͢H$͛7444MN!.;; &8pͲ ;~?~TSSsppPWW[SSKv88 ]B81<**bG =HPxC%uR:jC?24L& )"a0w?-[744khh444oٲeϚ5k_%//0 ĉ7o_ǐH$;;;//{*++?xx֭[N8!##{nq…ޞ5$;w$"""~~~nww$fLRQQٻw=?~*LvppbusW\)""ŋ!Aۇ<::z̘1۶mz*}/&L=y7Ѓzzz(((ݹ"*C'\slopt;މHkHkp4\dҜSv,S50J`0 gΜ>{Ά3MæVWW/9hhh !JJJCj''-[ ~~~חʹ/_,''7$1֮]6115kJvwwP[[( (,,)Ɔ N"#: IDAT7622rqqa~^zŦx,XÃA?>Վ;ൿ<辰EpL[޺u}豰gϞeTh `x@(p`` -p̙BBB ,@ѣVVVCM5Teccf~GSN СC'N455@K##`n1#; Ʀ;d*b5ˣb01 bfffcƌ!a̘1FFF F@@@GGgAǏUvrr4?%,,5}t={vbbbvvv_IH$ʕ+eeeiAAAainn޵kmfD"IJJJII-[&Ј?~g444߿hhh;wVbb"s…ZZZ,,,>| lOOJ\z͚5KDDfffLMMΞ= x;jjj444֮]N%999sLLLQQ PN c0oߚYFF ^#+///!!30WfϞ-//?f̘sggg?m0$99Y___DDd @w&NL@HHȸq,777X Ν+&&xbǏ())O;l3L0 QTT$7oޕ+WucccJJ# JKKjHׯ_ ۷o`cFFFffŋ0---((Lv޽j*UTZZʠK텅{xzۗ.]*+++..Nyxj*zAYYY--/_ou5Pa>4vR/oni{=.y=Ns7EC0MMwpn---R--NNΌ MMͰ]]]={VQQh"L.**ڶmwpp ƍ⒚ 'S\]]6lذqF}}}k׮511ק=7))o|||2xť-==ɓ'߿3UUU8pɓnooݻwDjjѣG٩B20s)**ZZZ3ydkkkoofe˖/'&&&%%@k=W.**:p]=Nfp8M>ť+//≋p+))Zn2GsN!!!.ava,Y2sw._nݺT%%%{6ô ?ҥK,2/NMMNNN𖛛r`jFeeIDEE_~]\\rJuNOII 77wll؊+:::ୖ%KKHH0M{̙[n?~ǧ.ݾ}DJNNdSBzzBBB***>~XXX8uT8}:\k!!!QSS3n885 h |)66vƍZZZZZZ6lx1|'$%%yyyx-!!dɒ̞=bccRO2@ vtt ݅Mi&W\涱7nwj;v;w.**꯿_|uuu7nܰ*))9|WUUƌǏ} 򊉉1lbs`m… SL#7of:\UU.= _t)햰===7n8}tN*0t8}^-jkk{zz={FpuurΝG>~XDDi]nQTTƺ]=NN΂hn,>=/^tppppp^^^?yE9x𚺆S808ՒY__4l޼yдԢ4R}}!i//W^1Z[[xWWWMMM'OD xN3s 3Eŋ###~~~999lC]rYYY6Ŧ!** ѣGEEE.\qqqnwqqmiʕ4#\OOի]\\h͞._LRUUU{zz**A+'M0je0O#yD''B"&9 TBGkF̏?sssرAk׮_D"D!!+((B{#ms}葝ݥK'+N/<dzg~.&""re+++[? RlCVIHHxմiӎ?>m4e,ﯧn:1iĜ(NX&##N {.`xxxݼys/^#OyΜ9?ipqqN:+((qvv666.((M[@hu4MMM8e5͛7oرcpXCC>+116ɹ,Y¦aM\ha---A?Xhϖ-[,Yt|ӦMjKNNӧOX#@ZN7 ƍSN U-_g)((Ʀ8;;˳č֭sss[jURR g:;;Ðt ;0~& ㄅ1RRw.ٸQL+"!&)*-*##Žyf]]///L޵kݻ'h󓓓VBBnpڊHjmmmBBBkk+@EEeĉϟ_vuu?~NB?|phhɓ'aǦR<(**RWW766 koooii 555UQQᵊʤI;?m;;;sss vZ}};2tRDDÉDɓwqZTUUجNNǏ?|ӧ0$==СC0h´b "X[[ ,ⷴ{eyyy&LX~܇ǵkמ? JvVCC#J~;L>222;$$d@z{{ESSSdd7Zɓ'4իW7o/7o ӎ=|fkӧO~vvv}}'T6 'Oч9D2gT]-"{ihh#111122_9͓%z5ϟ?'p0`e|}KK=m2 ϐ3nܸqĉ{nF?w}ћBSv!HFэBt!ڨ){ 4kkk999X,+555 ALL 777իWaxbb ={ gw7 g&/^;`rrr===$$$())kii?"!++XXX HQQ//Eqq1lovv|ll, 555ӧOPQQHNNP(vvv{333Dbtt4flA6o, PVVFfɒ%v qp˗_GGGږ`vRRRRUU`0P;;;zd~~~ <{?FEE ...333 SMK]]zY&LYYY11;w"R.\\nݜ9s*++ Ei&[xww6c_rEPP@ p8AAAvGd߾}Ǐ}Bp:flARSSX la8o2Fz7ߌʹ"߶ma0aa[n6cC$##6rAVXbdeegϞ]PPN'OHIIW8߱HUUUFFFTTɩ5BKL7cĤ$ TMM v=GGG@M!z}l˫fhh~ >[ r ŋ3Q &2|a6&DA@.9 yxxNWW.##CMMmTqss3̆R(.'HBBBX,bjjjb TVVkjj2T5DnTWWKJJUttttvva,ZD"u;Mƞ?^YYyƌnnn ;GREEE6)BfuMPxyyi]]]EL3ikk`0p0555 U{zzm2mvR!]RRJ$O5BUUU%..>S,)**啑R¡^?I䦦&Y}̟?f(ÀPA&!K,~L޾} '''33رc~~~L'N HϞ=~ݻwz{{#""=z\'<|XXz*ʟ˖-c؜BBBCCCF 3 ioo\ >>_xb  :_Yz"99祥߿3LQhjjȄˁ鑔dOi1118=lrta>@AAAAAAAʠ>QPPPPPP`pM*#̋VbVuJ@AAA suϞ-:z^k7X((((((((l2 k/Ӟ:͘3 m?'s8C V{88w%zo2{ۆm;ߊ ش;M#+|PPPPPPP~ ̇2ƌy| a1X,@TpܒdD(7:6e B}W 2hhG*7HoOOcco"{W@TY3,0CxRސN$ꏆ{CB/y*K vQ;{^J[]f֕KApGV]0}C_YY^_[É~r]mMccH(okmt[_`bgO/| C3OxݽszHii}1 r$*/7{2ߕ~,B /-5YCSE7Yc͐Ȣ=,iQM]mͫk:zpD?fUAXZoݸ}8NJZ {˴2ߵ47KȲιAQZ`֜y}bQJR”iĄ/R M{61!GFWݩ)/>}X]]0t~5C滜\\\bC;ϙا;;:$$@Eu dxpq E45ED]\V*"**((kBM]JDh[k8 b,)VWW}ϟ\qǒ"aaaoN}󺖶.lCz0wZe}cI֭ȅa2 P(H6PIԆNj}Ak 3qBn`vm`e<ƢA]]?$$$44_CCaɾ][G%.O=(++WX|R(.EH{i뮑 D3~ۦ5==c|~U埇gO3te?8<;:ssy)E$?uJx[KEj GfL0)7'[IIȡ}sgNas|%e22έ(O  HNLذvhC*&fwt/yhKSߊFTNvw~nްZJZZNN>&<6}έ):d2yxݸ~%E 8l6]v<yAotщ7>@"5i)dy-%-gqk \B_Ol^Iq#ѓYuӦZeIKRW,ڼ޳sO>w^̭FETz={ґIPgukMYK~.pclxss}s̡ ˯YbDYe2''ɓ'45v4ӧҒ965-{>*ML͠j*bC>z[w晿pK1OK_?5OmCĵ<[TYVVjiݬp8\nQ%kڙjjj,_/--Q"["v< hm`n1b2km|ɧM1kGzH~p.}8cǒOyY?=l,{ ?Yk?ytqX.]n6.;Y56CU{{[B#"ƒذi}B޻wn,[ ~5BPP^PO& 0@$G;۴{M1=;l?z{{@!*m[a>7cGJ]Qwl[[kI-Mw"TjG`8c<]05335RV"pqմt 1 x q]͈ _ _2^{y, :϶MkTDrsuL8vygLPW{' IDAT3]ka* 9)V00EKC%!gƆaiii޴m'QVuSc[Za3Cx):;MI~v5"PƆUD,SSlXZ[[' looJ܌e nlUyщV~NyҤ fڊ>]W[cfc:v*%<4ht' &@1k1K`mr m~VPH3߽eT!-%9﫧,yȁ߾yd=-KSs0p { fd2yL^&k*/r lÇwzԊ9YP3q ,iecG.k;޴cqHS KSاiqh%eZMDZY]Q|%_Mջ3MTP=ACl\9Mi uzܙSTDa``ik }eYK]V8Q1q%/v7P'L8sQ_|$R)_g܆'a]9?ʜ粘i_] pwZgLnYY)fZÀѾsD0pxzT߱:rڊ0~%VOҷ e8:)T ś[^#K^φ ܍4tQ9y~P&%s#p8\wwWVOO''g s#+F,iٍ[vn\n ^^^[Z++|WUVSPKz~sR2dVK?{ &Y;ϸ&6OBH/JE@: vA; ll'  bC(z`Ia4o.<3gg;Qld,nAo?b0\Qgr ?eT*uק =NNIY@&?TޱeƀN]ob6iǽԧ60޾|۠h=ϲ6oK+h``̓ϚT._2KjfN~PAKP_w(dG6 ޽KCS{sQ1{Ow aGd2΁>^ @5edLYG+.*=<{֖ qz.K_Js+K`KzS=hovh&Iԧ\\\L3iinr2սzيU%VOTD,/>~LB;sРP(jk#\Mun*777=mmoB8{渚J+Iw\E_8g5ٖ+@CK'#C*]=[{'o9ٶ}:9$%cИIho8uz{w%_KsS$SIʷ޷y:9Lu47z$o>3l%##gfnKJʪvN 1G u{&!q==!)(*󏓡$"cGB޼GjaiԷor^bpJK}P[S-##,K&n\KR(Cv}n r_|vShR=Yg{.+(]O r|O/vD&%՟|ҹa7CWƎTWW pS@DJEU !9iP'fhdZYY!&.03tpr9{x|-9 %%2?hG@fFZooillLh8aabG+ѐS<DIYԈ ekqlL;2[Y742542,_~l7(=Vް1;ֶ4I՟@ꪠEp5 S_(TU}Q׹lȝKy?LB [q<~jb'?pVR\8iU*M(4Z@P֐ zD@{=Qu55@F?_yrvtPTR7@ΜٰPWyڍ[wxq ɸ[|ϟ=u(PC:5Q(\ k±Xg̚d:kS<\Mw6ʆuD$k    urպǩwLb|hw^zņMGreh%`9L[=lll6؉ J3d!sU_ /@ x,6߻썀$;(ԁ$A?c . xxxv̝ ȍ+%hBÎՐy u4Og6t/pMbmp%>p^i@^ s4>O@AQYHH8r)JKB#PۃlN=L}c8Vh"woHnuPJCOO /.`hDn ʒ'd>NZx?%=T*ی[M?fƌYs}Oswp)h 2ד>dB7)ii8utry]#˼ Kk`0n b.c`hjԐpu_z xM<1J8â HScuwuU?ݤs{$;'qo~[{'# \>Wd,rfOw޷gb5+o 5W fEshl|!.L" q $%˷l]'*ƈ cExDNj0MבrzKsȋ`dޠJ:w}xx-MwR)+},; ڰdsqq)*Ur_ $ǰ6_JMO2 Wpwy8v6x?} gNJ>|pCEΎ؋zFr̺ yEwo+ZY?z{{{Zߖ&_g"_q.C<q}Q ; WTjloinbSX,6ho裇)a;>#6.X'475B9H+%-bCc䕫֭[ '/]y=ϟ"BR3675*)|.ppii&NuW.E 6] f``|ܞ7,PՋNS]rvl/ཡ8j4vlӎ@貃8<~-_~d7oXM?lP Od@V~(bs\=0ׯ_ruKHdgOviW442MO}' gKD}C#~Se\y̜6A=jm:Éu (`h}טMggm2W bGCT7N?w)jbߦM eP( %* 0JPc6^@%$"BHb&+VOr%6DPd2R|^q{Xp}DՋg@mJ5ϟ= p/.&NSjX[XK""cmdDfOw HR2&_VUՐ5~WY|ձHjDȋ+*ʴUeUejk_gl,XVWCn\K  t u߽RVzy.\:ƞ\t\RF f>62lqdvdivq)Ffzj{vmeSn vvRO}-EC]'[ iKUz{mFUY鄋 ێPWf?+#MAZTWCn`0{Xv\AKoq5EG]V]QS"l}^5o^O1g u(oc)Y{PޫkW/+H ?z RB|aѰ5ES͍渺mZES_X4nCFf⢂{r@<<#.޻{RgO2MauYUyϺL}K=egdp_BU^@[IDDt)v4fNݺs SYIID%?ɨyDMy=- s\4C.\JY ' ˼Jv*H ƦQW23uef!@((U5 YS:ղfo"sv`ܭ_cRbDIWhڣx޾x9/X%gOv9jblC%&b4=u7&B>V[[EM-ޞ\yVJ~`P(]F& FMj?nG 575-5u]*j noo`0 ^L\tXY-&JyYA]]E]NNatwO[.Szz>Vʱ< ^Br<[_J!yikmYzziΪL3Bd*jerC`q .;4׉T0԰QDTln}} JLWA AeD" #J8RB&;>3XFaʽyW3l/Cd( HiIqخ/,_12u5{wo]a6˂‚Ǐ x>*nkyqZB|-+kQƦ&Ħ>'@ϲa-d|(@i|18 Lcy_|/]x-(W-C_)P}tBCCCCC9p}ٺaQQXD..H vڸyڿTUU[nΝ&&&:::/^߿ɓrrr?J8p;SU̥6p$๸(4B T*8sGk76e FAFFFaa!_QQ@GGJf~~"%% JRcٙ_DˎRs?6sRyVUUIJJja0LJ ,i.2w?AQ(4 P}Qj#<6_8Z@P~fWU]]ӃDbuu5@PP4"""(H$Ȍ,2ǛX[[(ÇO:Ƿpm۶2~QNNNIInܸq?肂??跑tqqqqq+gϞ=}4|N nzDwuuH/_fff[ ӧ%%%vvv7n(,,555eMNNnnn[YYzKKXQQh#bƍ۟ hcLWTTܹs#>}LvqqILd9ɐSN-_\XXP RPP5W^ܼy˗ß={6y䁁>{{7t޽>>>&Lw^{{;C)))fffd2*=yd uwwݻwi!Ӯ#$$ڵkTELL- "HG|f(֭[FFFd2993ݧNZh(pႳs~~>ÇneP2Ҫu>~$ z{{&_qjgdD 'o>o}޲`D C9UmVd{wpT*@CCBʞ8qɓ(JCCZFMyy9]r8qiiiK~A四G$ӧoܸqt)((㐐~~n6Ӟ>}ZCCcpKGT5kutt}}}f͂Tj__|͍BR@aa!X~)SXD066 ,"CMQQP,''!MK Hpp} x2 IDAT.++ ?t_ZZbXĹ0>~:kk렠 ={8sLAAAwwwdXHHΎ'((F_}}'NYg %L4i; ==Qq6#Cdeeճ(WvC4QREN˗λW5u4?K"{\h S͊(JKKkܸqh4@SSxMMM}}} 5n8--1xAgqLL Mo ;;;LMMɓVVV/_Iږ/_.%%Or?ydĉ...pm۶1xڼ$$$$%%,YgDUU5--jڴi!!!?x񢪪;w---sRWWȀ̟?A@GGw*#%%OR/_vqqMMMf߻w1}L###YYٓ'O޼yРfv1}tM ǻ޺u J?o,DBBB(kbbf &cABBBdddŃGə={S>D7o*m0$++KWWWXX>dkQiʔ)>}z- **jҤIl^(]#=^^^'NPVV._\[[[RRr[[[~[|80`yz(=Tb;$'DIѱO܇Rğ:x񢚚ׯ( 6Euu~,vű%%%u+V,]^UU?LVjjj8 '' uuh@JJ&L8ydmmѣGasεQ]]ԩSΞ=;KᔕQԡ^?Y`L&m޼900022r&MΆOOO!!ݛXf.̰0wwwvQUUzxx|ŋ&&&###33PD]jUYYYppm۠>"""fQSS[\\pk׮VPPh(n*(( ԄȤZYY-_kjj`F*55ݻw566!V(5H$ I؁D"}D"ڊM4gcc3w\iO8qC 5ct钌͛ǏUSSÇ)SYCzzzmv&C#;;ڵkse*}555...3gάqcc#-_?w>Fh40?$򍳜 Džbи722dh$R^^^^^~Ņܼy'Nddž:{@eee,+$$^sp㮬LKK[v3777WUUmhh4iRjj/Gׯ_}Z?"!!ˋCC!ՙÞB\\<11q߾}TUUocccnnP̍7*))dw KKKwwwhGѢ AYY>P[[BpnP(y>B\\\TTTZZte۷G! ?~\\\|ѢEwޝ={6 ΣGR899x[[n8"]PPf`yxx'M44uuuf HUUՖ-[ gNeIeeV/\~]RR'==*""bii /!!!qqqLP=uqq),,j!m)ثWJQ(त$IIS ͚5Ɔޟ u,\0$$8&&&22%''uرM6l%,`ړ4hY<544>|xuuu}}}Pi߿j*OOOڳ+Wlhh6mEDD҈ʺ$%%WZuɓ'Oq@z$^%X,3mw]aIv.!Q_IGCxxxxzzz<==rʞ={Bwwwyy7o쏆EPP̙3P5J[Š=s޻w!::N=ŋ`0?˻CYYYXXҥK 0G04IOOə:uCNJաh^^pkBC CCCPXX _ RSSi#N@rr2H*HJvڵݻw?{~6"> WW9s|j-&&vE[[)S@> AIIMVL3QSS_æ`pJ/_&ᱴ]h y„ Gtܧ) , ڸqEYwuuu}}M6H$SS >|24Ӟp8,M%""/|mϟ?⢣tR6޿ڵkVXYC\\\*++92o޼Z8#b-aGoTsv0 ϟ$DxxDB\BB(II-[1X!q ğ\k׮555b02m6EEE%%۷D"TMgeeA=Jzz: @UUŋ1114HRccczzzgg'@QQ̙3yyygooC"1:p_pѣp~EM}}}[OޏxF}EE^$E^"9_O ovy4X,F}7440nSVV666|2 Ș0a={vHHĉa8úM⦵={6?~3< uuuP.$$$--ߏ Hzz}Z@@@[[A vZ V\9op8---pqKc#Fϝ;]w...qMKK6 =Es Dqqq3~A*##.C$ Asss...EEE* ,ypO ?00dee_z5)277g;22@ dffxƍzzzC% Wr@jט*"""''''' c#U`ΎIIIӦMۼy32:|L&ݷ_Xڐ@Т (0K~\k玟oJ,//700#ZԔC>@R?~CPF[[[C@$~Shhhq~AK555Qa0aS__/!!As\`A[[۰sS@kk+//Q#R^^ѣ3g(((̘1؍J9996B׏?DB"7=f 5xP444 ˳`lll&O ))9T]kkXW0T1,---FO[[ [ӞBᎣiON&G>p8HȎa``@DD˗/{n~8L2e/ZnXW֐ 555[l}6444,,LZZ'ׇï\㄄d;;Ё{1lb\pEwU!p(3XD~666 !K,aXR|͛gll, _YYYRRŽ0/c#4]JDBWVZwU/EVVӧO+++%$$~ ӝ-FdGuߝ~pKCz$$$OR111߿okkSUU]r%;ie ,N?p8p8޵8po Rp[Å1UWWKҾg΁ð0i  EQӌM]d~*BPh@"HG@ym:OP~fWU[[Ӄ<ښjܳhEPUQVB ƪ."əSJHJLQGF`æmc̯ }$-+dem3>[\T*o9VDHx,Wi/F:wRAݽ:/L&͘J#H^>{r:>a>/PVOJmlXG|{"ɶF,׿ 7ds뭭-_i0,_?艳<{ܴd}׮^!HS026?$#3#OM]c|ŸwR龞t` M5XBJ7]y浐٦Ɔzx.qD(⢂Es\=6Hu˹xyep¤P(KOm롶P)MԖnJS)YCFѱkۦ֦-***QQQ.\WUUmmm64ܵmZ1RRҥ%#ϝQZ rXXt\WqL{{ۜ7Plnj:?(ks4=ܵm˜?NR{s**Qsg:ٽ@ -|ZxO]QrcLbWTX /p`߮3Xǯ聼"L;D8~ %9Ft R\T5n\57ڽ}W_<>@_7MнWI/-- y~no_ׯ:ٚiU1̞fr'Y_ߐL&OMJ\OooHK]_8 C5HNOsޱ3nc8aNRCUٝ0SWjX^eݯT5NXLڵ?s( ȶAKJȬ^t@RJ=zJMM}ێ1>VV/, mr >VfdOFmU@DD G?rhSv_4 ݹ#]CEo.wg'cڪJs#a**k[c=E, Z:.}tt|N;m>{`ciqtׁKSG"T;|pӬ 𽻷y.\/ %%3`ڌ9 CEّ^-*-;u >Thqh4XC[_AY͍WPP300@P|Tյhgtq5!', }GmՊEs]N6fKΟ=9i؜^$mWRgca?{2\O~֖\slL[Pmދ%ՕƯY3Lb6i|Cd={XO;֖ \M 4|7_r'!uvv8曗{ IDATژcRT*pei0il% нz>{i7XWCٓwov656l\ˎ:;;b.D89OpxYsݽ J7,DB(u.z{{1 CteU$B*g9:FZ 3XOL&ɳ,+J2"{g2L޽{mdb T* `l: cAXD]#=/_<;IQZXWC|`Rܜd,+s&)Y{{*rbV,!ӆI껺 +Xr;o>^~.fkoJ3Jh4F}d㖦 RBO mqZJ|O1S9!T*5,tܩM 4^s|(n꡴PUs}u')WF?AsKP&1>D_\\\}}}  ۿz?}&D_=1>z,e qߚkW-kkkWWW}kkjkHʦf樨>{pS'((?sLbYNDj9 h79p{ Wtٱ\CEJlAcC{Sjk|/ewlY1}כMq/#L%((m6(ڶsϳc!k'ji x4o:;;?U˗t~?T6 ٳ́;woCTL^]{BBEy)LsFMdi9#sъ BϞ`fn0l-W?\×_|QNW2$ra.y4p{ޠL8{Of>e{AIJKX^+x MH ɀMd/E2ݽM@@y٠Nխwdtvv݇~\%D^7O p8() u&%MQ1񨘄Cwlu}hUWWɍ7LD4C5>VV<{z<gq-gu+y_ǏFEDDϥ2}6 AB{Gp5!.RԍЩEޞւý,Ϟ9 JߦYM%} HĐJW [N#xNi&I4f g5;u[b"##Nݼx^]ܔ|3lRmްiS-͍>x['WZښj9ﳈؙh]=%+R_/KK[WGW6,"ypѰ$2 |,0L˗ k__lQj)S5zzD=CSEU0pX L+++%fN.gOOEnEJJ&e~Ў̌;8рiq"UŎ!'x쉒*-A! ˴˽D SCFF ׯ^ǢVް1;ֶ4I՟ _z3*hG@qQi&UUuGYWXXN q %˼d}EL- y^M}J .3j~;]^d>4_bæ@}c@F?_yr#a* aq6?{Pa]ƍÖpɈ? /@ x,6߻썀$;(ԁ$A#kc0me凮.===sy"r#{ BwwwEEy7Z:z?Pcu5rg9LwC \X:\>0AZBG -`nbPPTNkjfp v%}q!@r (A6v dʱwvSiKh4Ȼ7$D7`:zy gLr<{a @JFVWWdĉ/^&+OI53g͜8~4TL\Ue%,")Vz1UH^O~ٓ ݤi#H%$O`QC6氂 '--P_ zU? v|nOd$.m㏟::MSKsR6Va!N. >>yd{ai dܲ{90|$.L" q $%˷l]'*ƈ cExDNj0MבrzKsȋ`dޠJ:wd4Clin|mmXOҹUJʪy/ch/TyE'|+ZX\87ygoocaWcpwpQ1Qԇ~(STR3{QHN^qp&J|;W;~\47z/>}喖]sq⩈W޸@'w&%Ƈ:wfM[ a㴇0$/EءbM~ -# MM,wvvgc`` r q IWZn2h[t+< JԨW4M;;9^M닻Ŏe``|ܞ7F[dy^yxnkkQ(ڀ-6p̄LV[zb5e7m7_QS:y<(3#]MIjZ?~H´I fylZ5u;h,;+#;kГ#{ph^x',֚U˵U 5DE@Qÿ0rqY?it.br 0ac3s# kstuڬ㧗/LKM%>Gmp Q aZY⢂Iǘ͘u>xMcݿ{!U cNю7`d!,0'K|[; ^J>²b; YYa!/7o`QPVUMs,b0/_ʄlF0t:M| I4 \114hDDEÆjooaSus3 Y9yQEGG{gGG{4fʍ;;B4D pQ _E6i4OR G;u5 8n : (# dr̤ 껷?j5t.twUyύ#pI3U.D&bbrW,w)MZ? 6m2VSCk69e^%5;!%ЕA ?jt[7.EE|~zDPVd^>|ÇOA>|ẇ>|?*iӦVuą>|gh qV&:::%%pA0>|a@>|Çζ25߶nzWסR .ԝ6n64ܹq2<"Ç>|8/0mewaNOˮ3&bPh  )ڍl***:::p8@;4D"B4ghO5aPɃO>-**pm۶ SA=zݻ:21~xqqxtNNׯbbbNNN22ܸ d r7oPT333777JLJJxO>?wׯo͕trrҀ޻w:vX:766qw,X ..mnn~e}}/kO>]rJΘ1ƆVbbbBBBWWIի cee?LX̜9P(7o֞z <辰Ŗs~޾} [n.^_T0KII1g̘!)) ȡCjÞ={y,\c۷{{{sWGGׯs{̙ mm툈ɓ'`ZYikk[xK F?>s'X///fsz=2p`;M d[ZݻFt[{'h҅4v2\pAGGݻw]]] ?X,6++KGG… y֭[/_tҦ&fxyyykk+niiaN~VVVd29##CWWŋG;wRUU%''ǜ7o^rrٳg`S]]=y{  ;EEE ϧgMMMbb/*++,XhEEE7nܲeKDDĚ5kƌ3o޼T)%%U^^f͚k¬ߟ,,,IJJ:r䈇(ޝ7o޷o222>}f{?~|׮][likk344ܹs\jjCxQBaa!Fc[ sd2֖{={L0~˖-fٳ%KsEEE%&&&%% VXʕ+ݻm68SSS8fR__?eʔyUVV(,sW \IIի`=[JJJ2Xٴi+BINN޽{7::СCaaa/^`1cFBBB||}jbbJYl٢bD"Z%Ϟ=XHH6ܥ W2e 43֮3f \)y0jlŭ]VWWWWWw͚5O<$ѣG]]]EDDx<CC%VWW[YYbcc8~o3T^^.&&hkk&FIIIRRR@@,JHHp H@@@LL F h˪MMM@CCC q=BӳNee%kȄh%~GGBijjCK&'O[hÇg͚Ýz{{FFFqqql'N᜜apNc&/_`0[lvpp3f`Oݷokƍvvv%%%󫮮昃~bbǏ9NXd2:RVV6**tŊ666?f 266655}-Z1FXZx1󖔔TJJڵkLpOXAbbbO<)..ݍ%̞={׮]d2^TT4::Z[[{ҤIlpS(f5FҲ/NNN޽ Bxv9VWWŋϟ'$$Qq}}+^ʱ@^ AAA<bTktоVk7+X,˗pm |˜pocA8 1b‚8Do\|s[[͛;::<==z}x|{{{qqMMM%%%Ϝ9 S77 #6b||ӧOpsZ@[[;##'**G&ckhh=ׯuuuGG3gdeeP<==;;; ??|XTTenn~…Ϗ=cx=VWW?~Çx^^^QQQW^mhhؼy3[&ŋ׮]c &Lؼy˗: ICCNJ zѳg`HFFƁ;6K eʕ˖-uuuprKVX6zzz.]D"xq5n8K yW `0%,+uuuڽA{ !!!eeeo1)STUUtuuEFF 0λˋ>} ())IOOV_^~=seIPPˮôfoo{.33ӓ9_a_oooWWWll,y<[9qℳ TUUcya0.9jwXX...VVV~USSӲexvmwSSWW֐'Y=/)6N$ DțN7Ƶ=<{,G2bh4/0 De-<޽+,,,**iiiy($$4k֬@ζijjΚ5 Í5ܹsps հ/%%ݍ UUUEDDH$0G)--㵵NZXX HQQѣEDDN[srr455I$R\\ SL`0rrrt:yǎ0xבoFd[:kkk-Zdmmm۶HY«WSNgn hIIIUUU  źuCBB#qɇ%7---&M}k4vN---_Na*))lݺ/uY111 իWϞ=[YY AP CCCA8f իQ(Њ+Ν܌}e ' !! w%''7vXyy%$$d``O< %ܲκAT4}Y8ܽ{7 Ԅ?ԩSPꪡb³W֝+&[VadeeR l-[FUTTf͚UPPK4VO>UPPp g3Jؾ};sÇx<3+l1ddd`8kH\ 6cʢh===UUSTƒ Or䈲066~ŵkݻ7~ОǏİ9:Mp\`veiA+ / J۷i!*#G|26 mmmK`DEESAPнʕ+)1T$''˻/V:Lttt5"33333s WdZdpo0bm, -g>|Çí+Ç>|󣬍Ç>|FrVFJ SS +>|~zDPCGMRko뾯X|Ç><2ثѯSJ Mǃ8ȇ>|yV j^h|# JH |?/$eyWշX BQ E\1X6Nɦ UxHKQURT';]Dђ^?Ox&`aa5aPO ;o ۆ) qexP_1a<:?/QQ1qND=‚‚3ݸyL" }FNhRwYi)I+Vp@QW)ENcso|}Oy,F[s>ytj3f,Ujq6(|?mii~~2_hEn^JR'O>҆VĤ]:zsI֍ cna$?׎> $$TUYҼe|/޿nJ [82VF⢂W)N$P)έt:]K[g,w!!!^R57S߽YXICSkdU-Z\򐙑y>s==ij )Sg2[!0"r={$/`db{-z,wqKOdt3(FC;NoƜҁ I2Jߟ;ĮmٙOpnjj:zhdd=8b׶ ֢yWRR.,q̠G.Ho47SfOwٸnUww~C}{?= ^ceE³]6f~u-{wﳢ.ZmyكJ2g}8I)4aLMuեuF:>/7GUU̘=~eŗj4}ޜwnr|ȡk~[A"fNk H~^N{;糈붣 votUi&9tuw͚|ikV*L-ϖqO߹u}h64hޕT4Yq9LU"z{ڿ,Q3\ǣP(z&xkK3/(&#]7 F=} EEp>CYigNCSK fT9/e}h7]NO{ sCRYpJJ{vn:}6\Rt,7I*UkyӖƦK>?u7"Rj ܗj6$%^756edmmߞ?eglܼcJA<066 _$`ͺIՇ{yR˗zY ӿc[C D=w zW\B(&(h m}JkCSsܝh@PXaBihKKK=C35M=AA NMSOBk ut5Frikk Lbi0unN |JKSO/pSW6K}G.`K=aVGܜlŒ`07_676/7KDt3?rx} RƏ4# d|gזZKSυ >hG<VҩOvdƄtuvYHWc*%=ݠ9J PE훌4ce=(#˜{0N Ng ],//4inskRϋٲE$ljߙppUU^8MKM`U[Sn3BfXDGEBO[)Iy/1 /W sfLTW6#756UV&JMbXane%C~]`}5vlK{"܈nk$9 ZN0]t?~x' ե @PSݍb?ϲ2Ӎ|q86`V^BibWT_-U櫫* TVQOzvpÓF)>z*RVVLȟS˗OOM>q*KcXD4z)[_\ +|w>1>>ޣU 4sIl#+1=Oqtr$%>om;&8И?Μ!"" K9d~[۷7<ښ}۸e[ 7n!#+8>e׾@^PR\H;w[C&"}ʊ{_~h.gfSJ*ulCc!!!+19PKNLX)J^ԮMz0WᠤT[_%>]FK[WK[?! DWY9yADHP2BBBP .T[XX7dd" :ckwoyr(S,lτ_d$-%izU5 yBBQWnϚU)O@@@TT F2*A]]5POB5`B?bdpZL̥q.͔fJSN·>6ΜQ[ui ^<v8'jo/@(y[*cSs p8qNGw II8'11h̚[cxY_)&*""[&ݹam3l\Qd{)/_зpΘmb\c7N¥1Y W u^1;>򇎮޿`0))i4ۮaa¼e% ߽}W|g2IEaqX6˗ϟOļe?)1-K\\b'Xzr<6Džps U""J$WD\±xqݵ/qK9ㅅ XAA"QcXz5Ŗ $@@@ !iUeDnaI6QYpo pޛ*cĄqXiCSۉox=T:AGvVfkdξw1޽KJJ LO!ɮ3lƌ[5{KQY= %2?rJ,K*lƱF>_lk%ܹPQQ>JQVrk&0.@^a N8Y,F[ll'{u)53{ώI lH]]-Ĝ㤥ebn>LVf6!bs==nD:uo::*&Zbhk5SHlŪu[\m<~LRUŗ/2|ώy9$2c&eƃcl4r KLzBIImgǷg|oQGN_wvRx[^Ld`m1Gy骏c;òr%+iL7;z6K\q`|3g:itsE[{UKu3g]Khȱث2sDDD8JB**yܗ>oALy s^f싄8fWTV+Wi<]d2m%G+9 |mF{u-u&>\&rlGna%\΀Rr޳K+)+34Uǝ5}&8ҢD)JAi뢵kehii4(%L'" sLxtVcC= `h4= 5,4Ah0=\(* װ^&?Phhjgv9iXT5^&+@UUnsgggA]aEHHhϟ 9kx"u -cS+1۾~m}|4YUo&y>aAp{vgg܀PLs466>&pjXqxÄ܈|g HU]G%`=={" zq$CG`a߂E8|C}]C}4華_[av%)1vV_ -^֍Sa0𐕆: M^5V2ɄSj_ꊹw/LӖ.]YHHhwOe%o2'Nv}z"XH.vl ic~<.3+a:?OArN}]IH.+s GUsVVN|DXkkˇwody!2lrs,/La5;wn…‚|]SA(o2_Q(M++9 \U˛ׯhf ,6f`mc6ɻdy駼Wر*\bg`0C,a-l*=Fګ^Tr>_ "q22@JHK$PF::$$QRD4 JF[7~l\{' hp?>{"9$h gFTUx-l'Nfchoak7F/_2-)%4v8k3JUR&]~JEm5yc=̩ߝ+O8z fekG< @j IU_X6,ZZS]eG(#xF c /υK9NspVw#[{Uk7cRs"$\W0"ՔdNݼ~EMǏt5% k`#: &9؎6i5O쬌쬍rBO5 _xzz{BOsB<{Q-!WE,ʚ/,mhWW޶iݢ%׮^ :Om;=yxO,{bof߉ %eŘ'C6FӷnZEm0yl9ܹuBD_@ 9{ ,L^&ff&wM~˴ȲFpUOa@Fݺy=;yj &c6c֝hC3b-lfrmU9sC "Qf71؂h4ڑ&,,EfwA@@ķoܼc8DP탶i)+-v&rs IoU4(L/6K0"!!F;;;ҌFrkeGTWij鰩`TN]m<ӨCFVnb뽰rP(M"#o.J@sq{[[⋸s#&MŶS^^VJR!ht:F^a[wWA'D45B"0I[7 P_WԨwH{nbvO?x,Gʼn-p=!оԔJI$2tS<߷ܕ Pߟ3_ ##+'**:|/?K^K}(0]Kg$-5uzJyYYn??|GJeMzÇ>| jPN5Ç>|~*~6>|Ç0}T*uӦM&&& ׉ >|ÇLtttJJJaaك>\;|ÇÇ>8TۺY^^-FC_Jm/Sw ظqr;wZYY322<B&G^$>|Ç?;l6 Qh4 @ p1 crQ+**022jooohh`0 ֗F%%D2 ` 3*]<2^~`m?ί??U[{~f~!Qp(t:=-~Ϙ6`7AQ(4 0S^||Pk7?CGp )) "B  ^hO>5jà>}ZTTt…۶m0??gׯo͕trr w^CC@U;&seeCg [2(swn=\詨'd(.Q!nA 2Zz"y?ϓIɓ||jooNiir]]]6EEEߞ+V0z{KdYL:999$IOO/ @SRR<<tR<b,)++iiizzz7otѣG###$9= ݷoߊ+啕zzz }vjj)S(ʗ%o4Ʌ2rH$p,ۖφJHHprrBPd2رc O6ƍ %##)ӧ`A8wwRkfvvmǾ,|EƛT" &:q(DC/Wwٺۺo3q纃 ƶfӼ˪~^)H$AtuuQ(SʈD"H,++TUUEPeeep鋩A---_:;;'UUՎFḊz̙7n=zTMM EDD9 {]]DEEq%c***}}}/D233={6KH$mmmJBGUU͛7&cݺunnnl+..0yЃ#g{{{F?[neeex}UU_}}=a?z*Ƀ8::޽/ȸĴidqqq4ݶ ɩS8LX%cU_fײ'O"FtuuϞ=a !GUVV-fC \3Ӽ@ iLr$ BQ(>:kBEEEhbzzz&&&(JTTT__SX, w0,:sdNHH={6tA.] ""BcyxxNCoooLLۙTeee-Z {{{OO𼼼xp-@ggܹs%$$ttta+V@geek((( h4K<<<}x} ]\\#2###<d2y%}RÇ%%%Xeee3(%%`y}E^z9sÇtoPVV0L---obb"}Zd}(0''gdd1ٳg,Xa֓eSX6&&&00e[J$O>=T~AAEss3˫3gάٳg@hjjXptbb" ,0q CCCqqq???(`{reMM͸82EG-ljkk['.]>!jM>_D?rA#V^ti|ߵ+K^^^@PM{ⅶv||Dm۶e˖-^.h^z믿}JMMu`+;::deeϔq_R[[[EEEŋ^^^Xm P?|. tuund2)1N >| NNN666Sׯ:uܹsY=p>> $\mdd`lmma]WWfwww~~~J.))IFF@ ܿY(߿wuttttt֭[wz3!+++$$`p8CBhnn Ljj7l#GoT__/""2uTkk넄d>x`ƍ!\vyyyEDDhg2&BY@R_FFɱ999pq.CCC]]]%%%{!L8qBFF&(((33rrr{BN>:99  |P'2/ 8::|4غukLLӧSSS R򵷷8p 44t񽭭^~~ׯGW3f| }}<vuJLGGSN鹻?)s~ŋ}}}c-C999|RLLgfX65Ebbb~JMM5k4ٳcǎ ~A6kyxxh4_eeeaa4)þY˗/STN> BBBIII&&&LIeǷk.AAA++ώC|;Xoap<"X>^ds\VJS2 W+3uԩS]4--m9--V__NLYfttt ,''7֥7oþL0T8880zkhh߯3v@= L z9cЁp@XYY<<|(++ [l)++O'e$uuuFFF*))yU99+V›JJJdZ{D7oތfhccF222f͚ݻKKKx _ WTT$$$ 3^maJEE~۴i } ,N3W꫰0]]wީhkkC=ϛI$]sN^^^˗zzzFEEihh+\FFʕ+######?kULL }---\?N+&||<<!կ26<BF>ǚBIIɻwn:44 HJJ޽{q8`MMͫW&OU(..~Yee墢"obREMMܹs\\\.\pɓ'TRR˛ȷۡmmm.n\EEty݃ aϞ=Go3f8|3 4-$$ahhf8G ýy&u'n!ߧck~ʕ]v=zq?oo^c ??t||WVV611a3ecjjZYY9@/+hkk3nr`}}}ᲕC/@`JJJep;;Ръ^^^̝;wVTTMmt^8IuttLLL6mD&: W^Ai:CCCpk{{{pppBBBJJJGG֭["pBRR˗8y֭/^ܽ{3g>kHCCB;|۷ɓ'?vX^r%Kp8\[[[[[(>X7FFF>~ k.^߅<|Aw0F >}#8 (**ҟHݛI$Rll詫@ lٲwjkk?~ (..ްa#bI"6lTEGGڶ]p SYLw`w3 j[؇Rp!o߾+--e aԌ`&VXq؛IHHPWWS HIIYZZ2N&JHHdgg-[Wdee]]]SǦ>>>81٢c cӧOZpazzpeFoj"H"$)=='"/H "<'"ψHq?vγpwEEEFGIIip aaaMMM K.Ay~~<3gNxx13m;n9s`I&EGGõ477C}0 BBBJJJ:::ŷ 66p8YUU HuuUMM jsfYYҽ{immuwwQWW)((R... _RRҐDdÆ N[[[o~`w?===EEE[9!))I\\\UUUCCBoKCCCbbbpᡈ xT6-,,dddf܌M$\]]XTPPڶm'uy115kx{{+**655!r111AXF Ț5kP(Y|y蛱/^(&&byyyVX;wݿ?c0>;qBp:flAhl׮]Bִ%?xP" ( uXr? .?>ZdX oyȈ,\uC}gh4ZBB۷o 9Y[+as e .\p]W .\pgƬp… .\~4x{IM nn!p… 7בݿlO8tO_7Y\p… .L)I<~X[S53Y|#K|N?|a&!.\pZ֖;*[yX"pMaq YPy歿[^MiS؋gO"z8┢O.\puW&|}={m㰼hB@ =uG@[æ,AAh V|[4@^h{J*)d2RqOABpd2/// ]*kmǃBPh@!H!~sR*=ljl@DCK'(0!uQ~=~~~;GFaV m=5uMAcŞ?ٛܗ˿A2o|B!M1 /_<}k?s@u??b8Gcko+̭3rvFgg@YYu=: 2!hO0X@DDsooo ;:-Y譺해 8;޹5KJ+tuvd-qs$bʪj<^~^mY9Cca*+*+f3yOkB5vsIӆDDDM+ryD"çO"x̜#! ^N5WTTls/RMӺhCAj sGN;GxUP/>ع}UWWVDDDlll\\\DD@tvr3۹}oT]v`e}/;wpXq '#ܜmZaIG]nu9L"BVLUU;ΚcÇ{wU)\WSx>~`Lԙui(I>)~4l2 HEyq-\[0ʟ?id4L3%iо VMt?Noo[?wzѿY .Cϟ>_6&]o8+ G=ٓW~Zb@P>I~Gc}EB|3HoOφ+޾ujV-gW0uz|i/%k<.*ӔomiZ#= A޹hL"d{5`= GQ-3<+Ⱥ-x-|pyDfWAjʗWVURRڴiTe5@Ӈ'?~^[['.&As-dtwwiʧߚ:Jb޸v">_p!1 a릵.㕈(`dx}I I)|6ׯ|Y Fw=-$m۷:ZX\w0LCC+:X?$%p/S3KNX(`G}Xy[XZpԱ/JkyxxVX<+VTR^ PGYHHxxTRcrUk7%$G+k[6**6g^VqieDyVHW%d qQ[7y]Q/7IM؝?o~Z8;1]B~y? +A);:z]O!D/|P((50Qc4uu'P(aQgsx3f^J@$ X@$IscOGJ ݗ/&,ݡ! Eoyݻks{ٷA~7Q&uDrc_B\t`^fexW[ @P~Do%P(GrB*&Dl{8(PG\?&4z{YL֦P(cE~҅}}tXeKsK+Mi 9!ʆ~ L*Ԙ{ ׭Zv68Mkc?*t*/W,:{rO"=;hH{{qv>}VϞI2MOgR7 ss.7:KW{=1Q?~6ׯz Dㆆ+i}>W/,MtR/^μc]MYYɬ9G}U^g4ӥ)lCC}>mjM#b0wr~kP˃GF^\JYSUP,rq)9>63⋟W0ue^|v3Dc0[;#g]\9{WϞ1`ddq=Hܸ!?COLr>~{ǣ{<9lǞ](ByW['臞wF. 8%BνYfܒە0FWF GҢ%E bxyxИ𥉉QQUqvv677WUU#ɌWdr]]o7MDDڕԔdc\ܽ7hWۏf氝JJ*S턅N$Ls^QQ=\;oD oHbrx/3@PlsPׯ^0=]NNNy.eH*++'j˃SoyS5 f4y:sp=qq X^AsLivÖ䄘ׯ9uNmgG{t)6_kVYHؐh) xѲY7:;vvv$mlS/'IKhhZ-X?ˡ1s ajh4[XNyOL҅7 fɲPR᫼\ |3 ]y4-R( ( 0-5oc.Yx1ݐk&+ qN\8\ L3ww2xUah[7R tt?9O>`hl"*&^XGPr-[ ?q칣c0 N3oDpDnPUӐ?~IXXDRRJP@pefV#8? 8GDNj7l=yUJYYi*6Bje8uݳťҖ,_M%d\K704?r Az]JhɊGO@yz?|||z 8/v-&.鰦l\qTTjhŽ6AM 522wvF笥!;)!Dġ[w7323X`ݪE/ؤvU?m s:N-G?5I` ZQ^ư<2FZdY(/y+㚌ܢ%!r]( ?g^oRocŤx!!#Cd257 ͜=R[ 1;v476iM ˰eLFVuуd Z,4Ƌ ~Gѝ7gxx=ˑkji ^QQ[;>πWbxi !Si#cMAxSZRWn``@@@`r>788X[[S!wu_xdDinP(+7ϩhV]͝D,}oWw7K JFF&'N@¹|+5&? ^;3u % x&W}'HYYtf +&&>~>~I)sq=Ms:pP-566}-8(#xQgdoϚ nO?$-#Ehj73%YoRɉ~Zv.Z֬ 0kߏ.hyEEsN{WpOێN.)/ S*\ž4 //{wfnQUYa ;JL&_NvbcXoKed} 2^$Pz~\htj,ie~smK-S/%fćٞ?Ǵruޝ1Y&Ğ?#&&Wad3, ,QۻkOs}[r1~ F^A+e$[.'Hɉ̳@GooO7l:EK޿ x_W3}fS'$'Ʈ Y2Y3ed,ضw$_=2L" ˞,Xx7fZJRLed Mb|qtݬ[e]׭ݕ!Y7:;Mt 6۷$..1 V/:͵1% Yh4.gſl+x=w!stt޳3,=_Ğ?K/EǜKIX ] mJI ~+:VM]qAsl- m?d=NB\tE*ɦFp/ +i J2.%] t3M3G|۰{Lsv`062MNQW\j^aq }'#f*jj,'NfUK.Y" pc+S]'gW'{sy[( ZtDZ$}! ߔ+?}OTTZYUM^A0uKGzܙ==vC2o^OݿEFD!ɡk~`<~4ӧYNj =LJ] rYcg '3!>9"(0;WETRR9}.% qSll.&ŻN8r||y(=~?ԉ#=z ]|1#uW @gGXHI Opi"Kxldb| x |%g{42B`i6CKK{\44c`رv| 2oTWUZXNDScCrb܆ۙU<**(~{iYs|XNqW0fW~?ȧILpGBn w .\pAqfs… .\pr… .\|>j2e˖>cc~jF\p… .\je>|XUUl@;tСC\p… Xii߶%E_L;vnܼyͷO$3k׮ݱc!ɓ'TQQI… .\#`y}-kMⰼhB@ =uG@~[æ,gyFXX`hh888AѠ0F&b 2pLāV~|ӷ\pKRixPh 4~[~=o;^FRgS044bءDPJJJѽ{7ndܹsgΜ)A7n<{L&0vdӧO/ŋϟvk. j\odnl-zΤǝ&hۚM-~z D"8:oF+++EPʧN*++#D",22RUUBꖕ//;;.9s挽= gΜ՝M+XzJ__K"fϞ h4smmmJBGUU͛7&cݺunnnl-..0yЃ#gv֭ 㫯^d2YNNNKKKQQ^?ǎc|uir#Z;+A);:z]O!D/lP(}}}QQQ4maaajjbX) ןrIIə3g%IIIAAAAN<%&&fkkgΜ gφ;v˛$''kiiݾ}A$<<\IIIFFfN___TT̙3EEEܸqB9>C$!!B:;;9 sKKK=Dl+..3g7o@ x??^|2y%}RÇ%%%Xeee3IRݻK.=~8Ϛ5kΝ;(,,466xppɓ'CCC%$$###\\\8F۷orDDI2x{{_xW^=g~𡯯/xz{{ BNN'sܹ:::Ǐ]]]%$$aimllBdd$ ȥK<<<{l<obbo> B!{O…VFjΑ׵1hÂ_]kڜ `<=={zz` B\\ow-,ۋ Huu5h));w A~eee{ իW[XX@?QQQZZZx4jeTTTUUUd2U2\WWjaa쬪:۹^|D;wK\\|Ν4i=;wTQQNJJ"nnneeeCTTӧO!&8gZm``B,R/Ncc#؜8'ņ'NeffG`ӧObNNN X\x',,L@@3 ֭[cbbN> GPޞnkk[[[{PeICCCvve˗/y&vttRbbrRR @hmm>=9s愅ijjhooo### èy`0tWygg̘K&-,,h4jٱ𐕕hnnhkk޹sAEEV0Bv)..s? ڵkvvv~~~yR[[7jhhHKK/X~IJJJJJÇ7nbڍ k36?# &[O}{RVVJ4]SN:uڵťiiiWFV[[ollLjN^^ޠ+V$'' ZII'//͇fܹYYY....\/ΰWؗxuqqܹsgEEEHH\a Ş={)++߻wGIIއҤI_N*p7o%㢱~roz$+)IIJHKl Z^JH$%$Esr\.$$CPo߮?S(4-((U_vzzz??}]UUEF@ | Fr.?|||}vvv6~.;ݻw=ﯬ,**8::JJJ.^xPH$> K*Q ܏?BWzz#G` ekrrrmmmy5|v7&JTDP lg/(TĊKC@D$$;?VQ"*óNywyw9tPUcccM6QC-NNN FFFTFw3 رc´k׮|jyM%<!J{Qk,ۍB:8 (_6r\g̘`0tI;w0̡CnذAWl2ŋl6:&I0uYMMmɒ%mD[dhhhbbaŧ8<@AAACCCJ9on}k,;Sc8?^زۯ999-]422ROO~rQ"bكI(YNŦ{, ! ¿Z՟eoo.++K{mll8NIIɳgLMMAy:::-eD_WH$*,,FqN YYYׯ_߻wpp;QWWc``!b?`<O$A@t." èq5YXX4񩯯444l}iX^^^VErEE(sFYTT$]?:#24c !|g}\;2!UB4BPP{CCC-Zԭ[7kkk@jj/6mڴe]]vn?Ϗ|rLL߃UV5J;s@Gh*STXnE4z3WQl`aκz߹͐27do=O0ٓG[7c.]=o@ B̆7.cEc@{wIh RZ l,B;S3^zpJ!$M*$|>F @ MCEoĿf]H(%BF=I'0p @H3_ݸql̼?SD"Q{T8%+#[ӣU `ٙlYΝ\tu.x9 K+#cf/9'R/C!|_Ϟ [nC$@׳Θ#/2_zp?9+u~}=+55ϟy֯8G|R|YY)@_߰K 5?I5"gq Ғ Kf|Tq@0䩻IwܪYXv0-5riI+ǀ>e'?~wPeeoa;:#uFk45lvэB{hwP$AK~'+8d,ԑeu*ALT@zLS2d-NXЭ}w233[FFFFEEmݺܼkK~cE_͘L"KQYY1eh:3 TU}>xߗ,%e=;zw)|_@,]fE='(P8#-+ =~޻ׯ QΞ>!%- Ksk)&zn6B.?w&eNL o>W:3ieӚsghikݸvÇJU3Ȉ}] ۖ:NǏo ERǯSK͋=mdlrnNQvn7ͯO/ݪF*jFI<ψӣ>ph wT+'褢>vR.87)!v8h+0uڤQ.\ҷ]Σ ۶m͵:kjniͨ(29~B_/*%2bq&THdUC-a97^J|\A__3vg>?77:Mg-$IR7ٙ,=~iai՚-]̸3[ڧk60118x̟dy`ߣgk2a*ïgX7NN#%yM]F,% 7ٙ.6O^d鷦.Phk/''__{Vԟ˼_,o:͹ߐe+۳I_q7e9/={1#bcأǍJϜ6>"|5+:&+.6ơk<[7ZinZ[zhsG3cLϽ`͏C2ae_O[?ikp\'Fwv>o8 0)8бP(l{wj:yd9t=<ޥrtri~H$w }A[שĹLٻ{8sFKrsf4*gNoΐ_1U7@};NMkuٹɾ˓Z#FHjZJ*M3䉣`v菋CO4r>8vHsoAcuUMCe{uU&wÁ{(9vMme;6*^r]ݒbQ+IJKƍjڷMkBnWVˆͫ2tF GT!f(p2gұe\.ۮ̉zv_4Z0 #Sz:-O=Ս p*@Λ/_ :s$rۖ k7lyogIqQkrk?{WU u ֍L&ͻ,&k0[7?yw[69q˷/\?,;7vцdg }=<% z:KϖrUCyrv7g-//}ژqwQF'Ne+׬\FMEl~F2FkҒ_fM2uFgꘚB*(dkjj-^8BC9rq;tw>I%:9FG{,*|?rO貕/PGW۶l׿PVVNK[g]/_竫kݽs02|vokY٬ Dϟ<~04 `bbvi@CC']@gNZ6{P(|o.X3,0mX%%o͜tLj_+[5';N,*|?ӭoi9ko޼qMí/_Ga._j*]XMe%gO:rywֲ׆7M 0aee[ƉcG\z韈VW015/).9Mq9Λjdl㸟0k;&ٳ_O?Zed2'M)6|w677'hm;坔pK Mӽe$I^pm5x UIsۧ}=_gh4W ݌;aXHrEE%oO行֗y^ήCxTdKNe|C#U5ƊO[t]Vc  6SM(0XtM7#: .|ѾV'g^.}aJJJISgşζa.7:zhAǢM4dl̆-;wFQt:6/]UQqE%ePr: ]]mmzGFJK215'Cڷt_zR_͢رuӅ Tpp>y_&߹LJk'M^}{]|GKgܕ-NO{oZ('':@{y!r ?sȥۑC\x_SSiԵ4%cZ㫗o۲Q HJh̸ tdgrͦcN.8?zpʥ}ssrnTUSWVV15R]۷PSNN3/,mUɾwmā$vOs7*MEERhDqqk _#gΚ?z֗м*Ög)2lN~| I(PV |@jʋ7222+r8#@Ϝ*Ņ1Gf ;~!CGTWWyttu 8w閔9oƏ 8q{?GmްJ욞ѯ\4PR\4įkz FqʵCBH?g:ŒnDGWlxk%M7X_SK+IqDWZԗ֔&tփfJOr ښQ~.ݖX"4VQUf)Ӕ1--EKSSUT5j'YmUiaΟ.+-#B(YƤU BeddJK.Ɵj,,;, ?4`8n߼$c\_LL͟=}cnM 'N=}B$)S9'N\Ko^R=}uk7nK 4b䯮3|~κzR1.ݦΘ3g$j:XiIYCC4rc0:uS~Ni| x1G"[3*O71M&֍9O0зnݼʯ_n)3ϲk23> pquWQU=sҘq)C1\.w?(I6utQCcҒ⤄55T@AgOڱ9H/ƃO;tJJ^ɯs##t44z]RZ|<#qw{r9ʃB`PSS}*0U[׆ oѸ>yےN6};mlwV)_0RX{vA7mLjvO8q;]*r:+{pjna%Ԕʊm[[hS+Kɟx禑]>>ꈴ2jC'E_O~[Xix9L.*|aؼVdZxJ L˴FϞ<|w?w̨m<~tߨy3m[6Xk vwv8޴FƦ{4zh)S IDATVQ<1"F ibf`mcG9b8:t;,]!~yAzXuܻS&;ѥ%'z#ٻIwRS7]<߻f?dvSдb-4If&=lMۘP_jۘBEn[^hKgΚ^23gOgies`_@ 9WJxvԿ-M:+uV5cWTwL\Pu];!#G@=Gםo uKd.#+`empJJK>542R+++ʵuZ j;uRb%//.*QqQ\ Uœ7Yuwn_Ϡ1w7.mgKGVHLUM]qi-0e5Y4gggSZR,'//##R EE%ǹ\QM L..Sp$cQFblt7!EUԥn6gq'^~U 5(aͨ2'GHX^^!ptEo1|xY>Kbcpԗ/vǪuutۧnL@bo\@߃{w8ƞ(ݾ7(g'<Q\z>5w/>j}5J3vۧa׎**ll9{Ĺ>?Q@ BX[[[VZҾAkӷڑ0𕫖а$A CEVWUIX,{ږe h4Ǟj)/hhh6]=ԡw7ܷo54 k-2 w'*~fT $Qä wq@ ?9X+c? @D"@|)_Z ,Zk׮fA\@ DGG'''~zذƁnئM6m(@ }e ,Yt--5ul BBfF6&77wΜ9˗/ٳ- %%ѣG֭۽{A7 @  eo9w^ދ͢p 1  I$d4PE l,wIMMOKKՕ$)!I$o}@@]boJC"=͌"^Ji.%BF=I'0p @H3_ݸql̼m/E"Q{Tq8%++PRRP *azzzR ds第LMM$<<<,,j ܹsO<ݻwTǏ̙^z!]>sLjjR~p9Ra>}G/++SWWOKKҥcvi߾}ԿIII%%%ӧOիcǎ ܹsMgee5zh/\ﯡHOOOOO\tIKK{?Q4D$U%?ԓC։JȲ:QI &*\ =)6p߉ t޽gϞeeefff[nںuyYYYϞ=,Xz*((EbԨQݝÇ^^^K,a***[lqvv.((x߿ɓ'ŧ֬Y3}t}}˗/WVVJbӦMۻw׵!ٳg硫cbb~l{r܎v:w;v}Yles76ڻǰs5w6-;A˗/ta]^|r\˗/wmhhaX.]^|I4}5eeet:ڵk┰>}H$,,K.R)C0k,*_ףG!CP$(\qqgKDׯlܹsdptt w>j())MZZZK(Çl000pڴi!Vj%ݻŋ[Sݿ~N液.@eff+)мu&.ef`ӫUF I{4.I1.lG `fmmݩS'X,Ų޽=a:uFwUUA}gϞ̬r>`>:t՞=K9ܜ6-$11NEE%00Çd_'߽{ @dd~+;xEHu*q۶m={vxx8 ))k׮bܹs̙555wӧ;N5k:wuVKK4+| dzO-[,**Ç4hJpppCCÌ3|}}TOON:yxxPfר °0KKK999fcǎ&$$888޽[2]\dȐ!***ݻw_f#@(իot@|j7\.*#o/:m{N>c-6%g BNO~kx=2QQQL&dUVVM/$IVVV1Lann]pb}Bb55+VZewMo߾k׮Ա5k޾}bbb2f̘Yf9::RygffA!>\pA29sFz+W|RRR~wP>*SZZ`0k<}ի</..NNN+WTHqq̆ 8Ν;w:w,P4,]:&2v6lѳfܹLJ2A988 >J244~%lvnnnAAٳg?|f*ڦM233E"ӧ_xQ__?v@*OXXŋ멙7o$ CCãGB P$ihh~ͥK?Nծ]ͷl")z|rBBԩS4ۼF]ؿ?gMMͻw*++ -,,SNnnnIIIl6[W\T}ʢt%%%* Ν;o&4o28b8xrYNd4&8}2wpp044gANNc :ooN:/JJJ+V It b WWWyyhsssoo/_Rׯ_3@7r0L3H$zO%3DFF޼yrƒTTTx񂚓Ž;444.^8tP*X,V~%%%55 9z(A222...&/>p={N8ASԒ'O;;;{ݺu3gΤtf˻vڔ)SSN=<1Ґ9榯ann^TTr q9C 555q|ذavvvL&Sd2gΜ)5+ +WΟ?`d>nffF]k.]5jΜ9ƎV 8m̙Tfר ={\`BL&̙3ϒ`uuqƉO{nkrN4rfʱ NS<`[^345E"/2dU۷o߾}̙ÔYfOqqⲳv*~ 4-((ӧOٹs'Lt[[KǕ_Cw֊5I+=cddHJJ277'R{{_ddʐ!Cf͚E8p`ƍT;99999Ǝ;i$ ˒%KΟ?OV<`-z% 6zBrrr,j̙3UUUO>5}[nQ5 0f̘ 6Ą6[HTT]uuuȑ#!!!rrrÇ?x𠛛[DDyyySN\2%>> %&NJiL0A(^|9%%%66ݻ uuuqcI3ftRPPPTTluݻwӦMkVbbTUU)%4P_-=yyy ? Z_&-4ʰYLE96Ei3+hxB D$A!U6ŋo޼Yx13f WfuuuYYYϟ?֭۷W7a„[FDDz{{S{*$W^ 4]_T~#""΍7c! >>^VVƦdee%%9u~=I;b Çf߶) M9@| p8rrrģfXXX7,#F1i&MMMKKKdQjjj555^."##-,,Ȑ  4rȪ*Aoߖ2xgee >˞;|r۷ommmXX_~̙tuu===j>ѣNMM4'I___77k~fiISUFE^FULSVƴX- 7OMWQ!T2dud۹N*))#B(.]dٲeBqYYY4&YYY988믣F=ڵk_&I =niN ӦM;~xrr2$ITIǥK]F/C-8uȑ# hy2eJRRұcDŽBaEEժpN/_Ǜ?>uGlv/^|ѕ+WQr #G?tS}-}%?|TK>g< S.|…kȤR29kvN8} ==¢"3M$נ3f0 ---558*]WΝ;:::L&sС6lt-[Ã:x"ͦI,L;wVSS[dI[VDGG+))`}v)(//PPPАR۷}O8!%ɓ' ђ\&~EEٳg6LWWBhcc[~JgϞaɜ:uȑ#nGUTTdX4MQQqT+444飩vZ>|dZ[[;vMFvvvǏWa؛7ogoݺehh(''giiI%6o:t(֎dAE"띜ƍc#)Ҡ漲!UUUA,_:h}L8Q___^^w%%%Rר "hɒ% CQQQQQ1>>B)OC?xK͚TzQQQPPҥK###'xO]if&Pf:=ز48 @0 N|Zzk?TVV,eﵱp8%%%Ϟ=355m>_^^RHT[[uDBmmmBU[[{{}uuu999#,DrrrB*8媨H)0Jk EEEeeeMg|333 [_Z)..mѴ\QQ84yܹ#GIƷ lddT[[ђ<1 mҤf;vlEEeA ~ͨ2&)u߳͐hѢnݺY[[RSS_xiӦ-[s{q|~tt˗cbb$&&ZQ}^:!!A2@2yyyQQQaaa߯ﱛD[y把 ;;۷o{xxF!436} АB èW3fQ@ ~,UUUMYXX|QAnn.#b}ہsνzF9;;5R>=gަ 111999''GSSsĈmjWe(\?@ C @|A0+@   U@ D2@ :0HA сA @  Re@t`*@ T@   U@ D2@ :0HA сA @  Re@t`*@ T@   U@ D2@ :0HA сA @  Re@t`*@ T@   U@ D2@ :0HA сA @  ʕ+? @ _ VQQۀ@  &@  ~t@D$I@T0 p6 D@(y󦶶$FKR0 N lllh~NiC|deW 7u)/O75QprtݙɔA@ ?5sʺwUVV`vԄ!C؈H;.&=KTMII1A}7'ªqcLzv&HA HtgϞ믆2V[$Imݺ׷1O:Kmacu1a0S\[Jb@ 05V[1hWHA $/_6o ---==}ĈmՀN:]pݽQzeeٳg322LMM ڪVa0$ !)//윘hjj*++2ToVQQ166vpp055m5Cyyg,--۶$Iq(izIaשo3<Q瀻̟G;g%ADBqܕW]+(Ij3( @t$+IDAThhhDd >Bxƍ -9Lyy'O.]t֭6P$)"IR `ֹsgW^7lꚞniiI]L݋6H$z} SQ@E|!B$P(P&^ ߼L2㇄le:@)Q@~I $"D@@ . H5]eңE"dGU@ ~:|ׯX,/I FWWH! zw,[ @2=**v׮]ٳg _QW$BBa}}@ dfeeYYY_$턄ןqD!ϕ, 233_eחLźutq/޵;::!(8($$P]]k׮{>|}ԠW;88رCEE[-<<\\]!! .$aA4MMM-++F"io߾O>.\.ь_ KII _NNSNԫ'#q,F k88S#_{">[RR2rHee+Wܿ옱8?vɓO>s̡1999sKJJ~:%Ǝ[h'O4zLt EE7lͥխ[7J`0Ə66t}ڡB1mqa}}=Ic1 N'pA8>uqAF4' 5E îVUVpd*]YCg.tRѸjLVX7A# JiNq0 J$hN'0:p& gB\}}=0 !U@ ~.Y:NS+ij?ׯ.\Hsp󳱱aXNNNϟ?2۷o߾2223fheeXbܸq8>}[DQQQ__U$8ޥKD+mlllllRRRp?uꔚiYYcRR C(C ${a_j Fh4:.''' S+**N׮]fptKYFc1&gp&g2p c `q8NP1 c|N'3WJsj+鄣p.22{?ߍ5KFNn`| i8N03Ah E'd=@tPN@Ŗ>_B)\atCUEySkGN@MUyچ%?Q$QW2p '9;@8IB $!AHA' C{A @tQSSrUTT$8Kc aLSS333V$effz{{}v„ qqqǏ?a|'%%_ pڵC&$$$Pȃ,,,5jܰaÇD"Q^^A'N<}Ç!R i "00޽QVVFDIIInn.1bA"GaP(x Tڿ[uuɓ7nQZZjff&uuul*b ЄB=zD嬨h$8R *@Ĥta Ƥ&0h0IL` L`4``lfle yUIQ&6zƼ2 8-^]5 9/KN҈Sձ>U1c<^b$;T`2ڕuuuGA+JPVV믿zyy9;;[ZZ\2**ͭO>8OӇ[]dwNu׭( q=2 !t0 `‚!vs,`8N'>VD4$ =AOU3ǩǸZFL! /]fы:+9|~EEdbEE [RR*eyظrB555:.NDEEEZZZR DNNN2XmII}>ƋJJJ=cbO"W7üdSP MCCxk9Ǟ{Qk ? D081psïT*F ۏeY~8F7Ƙ1Lf{{1lRp;:aPjZR1 u =~YeY<7rimiFZVivY8N$GJNwvvVWWiJ Ammw7 h?#Qtqw1omm^ UUi{86lww3u}LUU!{u.a ?uj `IENDB`workbench-1.1.1/src/Resources/HelpFiles/Glossary/Specification_File/Specification_File.html000077500000000000000000000014131255417355300320530ustar00rootroot00000000000000 Specification File Specification File
Specification File (commonly called "Spec File") is a file used to organize a set of data files (such as volume, surface, and label files) to be loaded into Workbench. The Open Spec File dialog is shown listing the contents of a spec file.


workbench-1.1.1/src/Resources/HelpFiles/Glossary/Specification_File/Thumbs.db000077500000000000000000000630001255417355300272170ustar00rootroot00000000000000ࡱ>   !"#$%&'()*+,-./01Root Entry'256_717bf60bfe96d2fc*UZ=Z$FtVa=Z$FtVaJFIFC  !"$"$C" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?&,}@#]ZucG## N{@K+F[0AH'u> dN:cw׽wzEf}k\ Uk2jré] dF\ $<`6>;cAq/%s$M@:h\ 3BBcj?m^dWI6c9 G\)_bĺù(w@8<}ȋn@`񜚂%v!$aF@"'wrF!2v6ӏ(m6tzU֢jhK9>js?"'sѝsYTFS}*ry?y~?y~uuOȩ4m?"'s]fS}*&+;и$ 7@pt87ZK+Ƌ%L`:I;qOO@x%&*h$- 蘇Jq9/(  #$&Y|$L.,Cuڽ8NniM>o-a@™ԯ ^jmMIi)`J)c'#NqZ iC*4`{ހ1n4}ybHH~5.wk3yܐ0ʂzTׇTB%,71ČssG}Ν]bֲmd1spPހ3yokwL5]N1kEukR< ʥbgp0=zգ!f;d#*#k{qGT_Q. LGr q;k6_QZ~Jθ`A8A>V5oG5o@𮹿׵ )E3$q v伅/I=jUdXR?GKMw|[GPxo\0ovʠcsP<7o桿h\}?~Mw|[GQMw|[GP@%\Y- )!AǓPԿ4lԿ4]pM:4U"[N2ef<_f<_ğTdſU5/?5/?dſUdſU5/?5/?dſUdſU5/?5/?dſUdſU5/?}]TPt0=,YeK[M.T6z{ճszjnm廑B89r=GW|;8@ZiGhPQ9QCg$pz?ڿ](ɘӫ{GW֞"MoU吴095>n"wtTc&>G9be UK&v>ϲ+n_7fksᖳ'y-F6(P;y29m6v0סxM'|q;qdsZ!Zf{3<[R1%X쓮r(ᘼU]-vr:;rr~SwZ]ը-E;m)Fodq$.d`xWw: lk[ċ#Pz*xZͨjvS^Y+drF9`8p1ًkA̎$4# %иHHFy`sj}}ݭx7p0pC>9GGs"4ck)$ޣ!1xFNIoR`>[dC2(5sSF g(|٬lEi#l+d}NW#dwa/&O37zһ2jW$2}+2|8kY 9r ;'wdOl\HJ"'9ld*=&eՆo},̲BJ`rɍ89M| i鷖.՞IawsmNI<ɒ݌s36~)͆[L8  5>|&tA%ݽƟSi,jX$nAS[MN_2E&g2mJv=å6 ,K 7ź Ƥ6زحx@¾A]cZG{p%N2~f}d 0r8V78;?֟$7~K4y7~K57GGs"&OƏ&OƦ_3h_3hrdCzI/zI/kkÄ|I?%*n?s#28-VU_pv/DdlY6)l ;H· ^xu[IxnP nbaRU͂8ۜm$kϡ7Sm9OCmT6R2  ,^Vq]ZhcNm1$|A#+'-Y$yg%b^H13۷m3]?;_VA'<6o5s[_ڶv8"U$o =0þ0GkoN ͑}q03lhx|Q6̇p[l; &&,>[V'+wdv bVU| 崄 ̂ Tc'PN:԰{ZOr+i4AfN|-Gd|!uO- d>Z0$|ulL "H\ $!*Ks?<#;},W곫x.V[a$3T9ܸphuXjG$R#F7zՍ>1 >KmŚH#y%FJ @%yr&ѿ0SWI%39 #.gR{6{Q`QHm7u32 Xs5֖*ZkO =7nbskV IUJn˴`m'%s,c5b=ۼϳ`o۳guv~1ŵo ,9,Tu-ox4=gds*ﳮA s_u{wi,{bX!)O\M E< >/n E_jVHVߟ1X.6/shn^k(ɵuH0v9;9h"?r *I.#4΁]H\v_|TZ}/SQOQ|T}|TZ!&tm]!\_I zcf`fdG_lJH ccuh tڒ,n'0_U%zKK %nZ֭scemȫ}$]74~i~#IjrKvHgp6~lq6W\pҭNq2I*DJ!0Iɬ+Elo.6ǵR.=xԾT|o${+xv_Ga̼0):ռq!0|7Tg7v2*$A׶I}օ<$cW 3pF,8Ȣxm24Vȫ^O-}gT\,dZN逴oX;dž`ιоA]y`F#2n= pAɹ_t9d¢] 3T0Ddq)r-䵻wkDrT@ l쮭/v c5-Ʊ͍ .Ѵ8rju)i'+ F MiE{:i[O\m,Z/K6^E5kQZF7 dhd0/u9/~(15d'Xe$ǒ{2\X.<1`smv'$y%w:yocŌ^I{U'6(!q#GP\icm3x୿ eTF;ݳ:pF0}#-ۜ].aO9}Ooʹ=V|>>;]U ,q Avߺciƒz{}z3k?C`:oy'fٳ.|dg:e'/b.̿)3{Ņ|y fM䲒G]^u5ݙK!pv}Ec̻9Q ?)6_p0qʜeuHusx#Mh?{' 8k [[`ap˜psN9:ߺcv >/MZ;aƔfDWp u#8[i~Uk_~~Ҏe,~/L_c_~`r2jC#DG(߻_`0Gnx%u[uGbCC A9~^>WMg<̆R>9:kyd&ʸqn㟠,`P u#]Qc Ct(]Ϲ.KoH6rypb$dǞ G=xr+3M Sp8 ǐ;=wu;56e<̑%K<y_s{ _,8xb?;9|˰Y2m= A=X-p]!ɝCr{tXum>9-$(y ðIW.ݼ^9Xu 7zsрl3@,㮟_~s.gc +w+YYF1T8*@ z a1"!ӷowAl|ߞI?c_~s.gFi-BA(-An c,4PXj _~~Ҏe,M$qU@?*?J?uOv >_?*>/(˰Y+Ąʊ<(mvs]wD$ `!m ~lE4ߝyƯ{-_OMj_oθx|%S9u9S7̏!qXkwh's&l)bIi}:]D/bt!kB`+Պs:4^ahf~u4u$gF[-rF >qv#XHtˁsCo@J0 ?x~~Ƌ--F;Mvm5hQDNIe >Fo 'RT*@Kw^Oty˼ooIoeٵΠ=haj OKדg}u;0Dh4cF98thԆ]q4c139Ì\R7@4F{kX|Dɾ+rX JЂd8dVӠ_i𶏧Qv_ۖSp=ɨo<5E㤍$**xč[^a7l&-^fۜ:PO@>m⸱}~K134VXp1b]k`R\2?>Be/R}}>_oΏKԟkk`H/G٥O?֏?֋ϰ{R@yE=nR܇h't}4iͼyaSB+s+оx%ВxXHwPKKTA#NTucm $}O ]Oia~HIoX_vWsç[@@`g`HPlc%~9$k'-Y {kFH?g|6{]N6 PIڵӆo62r6rO6?Սs[:w7oV٠efsR:FԏdYuQyT!'h3*>$gz$p3W{LaYH ]#*1lb#F ooLJ_$\<6<`P-^ g}밞5Mo4!yxٍNӜpG ү4jj^Ye*{t"nQv;;z+]4 LHMq,02I18eqMgkq/RF!kq2yz=׏nM>[Ym$6q̧8U+-KNm3]mK}뱞ږ2nXS̺`C[ph-wF{D@]#;@rIƒGk$$6e:xlVq e,$P|1֋>u̹-nuff>] Ƥ d`Jxµ]Qо7N!8`1J[H$FAHlb^ I]xY˧[g7?[d}1!$F3G+]{&:|{}$8*n/[j^zbH.$kX, J)e!X y$_M&PT+}PAnX n8h?kao]HyQLg3;EphjZXKzs2K F2•j C 2MXS:KPL`Xb3J^X2l,leܕ|c,o Fo_%h䜦#B988=k<^BF[%#*r6mr/G"/$;|bc}8-doYFѻ{v2zk}9I3m7L2,#L[y!{nǮ+ Mra?Y F*Y\aJ{ EԵc]}߳cquv,-ORs$ga'bF a9{CSoEl78V ad* >XtOoi&m|$G< 8W4?e$hH;hf 2z~SK>oH|0I;}i6>C\XL y%b9;0/gš/#OvW50ȅL[2 n# O5vE8d 'Cd8#~nM?A[TnϿɄ1#61W'F\9R9MİF F94Z+YjV(6c!,BH >I48[Q $9`pgk:wdҬS֧K'e;i!|@ns S| Zljd%P!ct,p8 CӾ hw ٺ5 >سxh呎 >V68xϙ|رఆ8|Ov e2Fe+!䌡R1{ 3|.CH:|B?P5:Mj̹ػ r:{_ *[YQXy1rCG"C^Fcx%I\J`ʝy8*K-VQv0= #2n$dadޠqr{d6շoKst #Yd-xj /D&[QsQ=ȻQa~|7N: -yVNMJ @[kd=~Qrj{;]q|K%μ"aˆ\Tbԥ#,2#*= M|;-ZǬ;2z ϦX3׶h;oC_>|[ZvwYzzG/Jm%\nۓ8Z6>CG!kme3[f;nbnˁךbַ#]r`~D~tZ.=4TkkҊMG^G+Z%t>Y w|3lc5GrņXow=XCi>ZH$sQ³<]IW>y 5%X6 .OI y/'+)/k0k_1DX߅|n%LpI'㓚=Ϲ<+,1bL3X"dInVXd)#CrWLӢfZ"޸+e?V/kZDZux~;ؤx$<;a1\Nihao :kR8黮2GAyT:-MVLFFN2N0cP,B/ J&nUrss}̉|GEƃ¶ϰ* @ @%F:Lc:fbIoq$1d&LăwR;U닯06& }eu b,<^Å3a{o5 o1"20ebF0?=ϹcxMqᕎ/ɧ*ٰFsʯbxIT<*K=ЂI{/㜌4>7/4#t00vFrsS5 ,˧MdyȤd|68}xKʟ-óy>"^]xq47\n;sCqס}_:O0/.%X6Ӎg/Z9>)(޽y>5u MA?ϔa,F2yݓOծ"ͻKI؂u330x[x|">7Ixu SlcwN&ٸ|f*;8ۋ2?$,ym y.F9|f1:x689A%wWCz]\y|Pc`trBmsi@dmw~0 :}c$͐8,K bVp[˟3.⇝^[tKܪ!xz/Z|"n'o&.I̹uAw ^l"voTU̅yz2[6A#o"cH rg<Xmʙ9h]']%f&(( gxIA$rO&& n[ǰ*yǽeu"38}S$rq$O[?>6EҋIz` +ۧ`K~csZڛi"->ovt+y +˰o?#UH9- VLvţh@@8 $\yz޷d̓-m"3X Xh?>Ԗ鵻wi,Φ?2Y ~>\񂧦(`NB6# 9cI}{[+}G§jnk0w u0c"E;b6ki-~j pߧr+V%i 'fQMq{qY^vA&co vm${f]_#̲J`rmnNC|6Uƙivon%vaxamKXd[ fLl<Ү;O2ͽ%|rq>YwcOqyT_?Ein>*DR $[> =`lv]W˲=.C\:oBı ǖ-p\`Q.uƺ<$jkXN^0K#u X:Q%;M*. 1X3vZӝ’͡,zKpй!>BہcT9ek} IAn2?,O6.XbQ7TUs]CO.,vhTmЌ3ڨCxJw'o7JfVGJJ`x%2M{ƍ+"z^MB0 #=tj*k{hэ.VQ+|=.bj It Es^U~?),8;x=~\Au9<#mx/pae²@I?)]`ix]& Hλ7d,zrGAmc_cLv*&D?2r˸]v3-—{HXlS*FFz0 q{;cM be ˯G+b INޘ ]SÞ}T0d/^#0=Cxr-Z|i,b'Qd0?x1+G^GSk}btm+R] XaSqV_Q^YĮ\lc Go.cmSSkgҫ*E1NӍ0Cc6du{i%32,k*X >P@Gd鷦2 WAf,bek"9V伛0%F00sþxQDBn[+aE/#Vxucnh<2L6~"m!5yN/k-ž.N=d_x,/Wg[2~pʅON:& f]ňW-~9TyQq*9V}pxwSDtkFF$O*[QI* fov=2'O׎䞵Qq*<}3mutidS^į] g5bK jHFw&XWx8⋭RxOң~)*Beq%ۣHe@$pw`u<0g)G4 e@ǂCs;+:YiљFvu]$g y :kۭ^ựޢn;#1 q8$'msı^_fe $;;3_i{D2D-n7䌘x*:Džabo)hmB@K}Ć%s\rjOAa-'s:{~e {a"H~>^$sGYk⁠(A"Z:R69.#Ax:[eOКRv}lO.ن#nP8t/kXfLZP>ԅ10͜qߊn٩:U.ȱȥ"vVE ꫂ2wth 3 ]oCk& n qF\U'4DX r(!vs2*A+5)97$.%U-.Vt->^8old6d> to+sFoCpی?w'4{X/C|Iiݒ|`{8m扢VFPCB;ӒQH 9Ve =a( LIjXTyQq*9,Z>ZGTsGY KcVEE&vg:V'e-<*8@19ݧ#"l} 쮼j(C2ʒ⁕UAžv EuJ׵v2M߹h/%mb~O'!.݀ynޕ\kmgW˙rjNjݬrů"S;{|t mKCY-d:gTV>њzgjZx!K8lW㯹ViGf\{+6U櫧gC^sZt7mB'y7?`;O1r! OG7OW0E"oΏ"oίQGaȎWĺwn'F?%у8]TurW'V_kYRnI\|^H2ps,2DV'P#+?tzAnM%$ ȸ*1f.8hÑR_ioA \c<@~8Abc g3'aF4džngrO-NTEQQɭiI.u pEd $h]Q@#%).>~2Jز;ZòI6H s:Ƶ۰[v?-W:e&` YOn'|e$ķ -jy 21 Th73U&#=R Z 0A) Wfi7+p|Ȅ{[!'zV7'(%cworkbench-1.1.1/src/Resources/HelpFiles/Glossary/Whiteordinate/000077500000000000000000000000001255417355300245335ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Glossary/Whiteordinate/Whiteordinate.html000066400000000000000000000027271255417355300302370ustar00rootroot00000000000000 Whiteordinate Whiteordinate
Whiteordinate is a brain white matter location.  It is generally represented by a white matter voxel, though in principle, it can also be represented by a surface vertex (node).  Whiteordinates represent a subset of all brainordinates.  In a CIFTI file, a list of whiteordinates need not fill the entire grid of the 'N x M x O' volumetric dimensions.
workbench-1.1.1/src/Resources/HelpFiles/Information_Window/000077500000000000000000000000001255417355300237365ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Information_Window/Information_Window.html000077500000000000000000000066751255417355300304610ustar00rootroot00000000000000 Information Window Information Window
The Information Window provides information on
identified locations (left click on surface or volume in the Viewing Area).

Information provided for surface ordinates includes the vertex number, surface name, x, y, z coordinates on the surface being displayed, and data values/label names
at that ordinate available from all files currently loaded. For volume ordinates, voxel coordinates in stereotactic space and slice indices for the loaded volume are given, along with values for data loaded that displays in volumes.

  • Clear: clears all information in the Info Window.
  • Copy: copies all highlighted text (left click +drag) in the Info Window for pasting outside of wb_view.
  • RID: Removes all ID symbols off surfaces in all Viewing Tabs/Windows, but all Information displayed in the Information Window is retained.
  • Contra ID: Identifies the corresponding location on the hemisphere contralateral to the one clicked. Display of contralateral IDs is not retroactive.
  • Properties: opens a box of settings for ID and Contra ID display attributes.
  • Select Brainordinate: opens a box for selecting surface vertices for identification or selecting rows from dense CIFTI file for viewing (also identifies the corresponding vertex, which is not the same as the row index).


workbench-1.1.1/src/Resources/HelpFiles/Information_Window/Information_Window.png000066400000000000000000002620641255417355300302720ustar00rootroot00000000000000PNG  IHDR;aH7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:F9}l~@Z_IRP8>b1ͧN:~L&s9;I(^߯Ν% ZWήΊ3,g>yFׯ7n tw Z#u[HA\1viF?Fs}oa_G>H? ( M&?>sL \N==;t:]GG{{;M7xX,֕},W7ܱ;1}Z- ~zj^<Đ$`c7Qb k~yt_XX ?fKpyF@pX,lzT]]]3gδ,k4, @"̜9Ӛe|OBz/2HLۏd9Ʌ4Tr\GC7X}yAĕarh.~a2 Dee'2_x勭U..t̹K;AqEkgZZD3Z.%4QǎLb-Ej7l1c}S(iya\\\:;;f0yz̘. 93}y'@dqaY;62n޵5On|OU}Yg _ڊ6d~w-hܙc37?L|Dvo)<|(/2 Sm /n{y}KŽrSo>B_ѿ@wzkW*EAW]!J$NBP(E`aif\EI$@ ax(:kD"EcAOO7P4C[8qf=$j'⿄q@;jꡐ漝hR~ˇ.&nfr#v~R"Wg~`|z" 3~}d8Vp /~}~C~K%y?BF,h8M$RZ*RH(5hp2 >1gi/=qI!ynI$J%NB-fbZ(0ol^Y*C_9 Vtg|;U-x{{y{|Y7sVy_8800YX\?( ;_^;@ө2H!j|~`grvfoood0Y1<-_N_,y[Лo֜&69Ls\d.G}i$i7@ `Y[{X{٭߭3 fZ%H,d# Yi3MAJ!_p>ֻ` BZ(E % iND[̰=+IfO_l6/ R#dx#ǡӈe;l_v OJR&eD"k#Jb&l6Ύ}r8yW*dRVC ϕ|xKԲkۗk޴Aĕg?jy:>$ orrJ [ne:lY^$E"L&j@OOD"17S[fmGyL/[hǁey龞NML ܅;B, _lp}sIO=kNA\yǯ8UcrWDP'1k2>D"1L8iZ(N6M&Yrj6zf5r70RT,[X,F`0X?:A#NAAwq6}d|lJ>rll_Yw  >0vn$AAuu   b"Tww7oĦ3g;v  '%%%#Y1˾駓̙3̏=ohh赮AAq8pl6Ae)BaPh]Kڮu}Z~*Q;AAq}f͢il6 X#i  Q;AAq5ma^`i0b4DAA`mӂ淟)kZo0-lvJgbzSjh.NxRojK7v>kz}kGOnwg$Sg}klMT+\=&ZX]<dܷ,:8<\vkm^ )ػCMs3?l]@[uKRfmXeQsYnbXR5eLឬ5SAA!,KSSS姇}>6-q<]3zOx}]pp8 Tzg%,]pw~Kj՚s4i[ju.8]Q /////oo2`]sV8f%h 9^^^^ Gabj;Q霽q7WDRu/GizL0]2oY+`}me~Ϊ֒/// >T~mJ{>T\S_Nu¢KcmoY|fr}9ɧXח%!4+F`]϶%)oj:zDkAq}y}u%fL&@0oNqק ѣGFU۾9sΝ;w7V~ĺy @_ϟ? ,X߯RNa~?DgGfy>'+=By+ >n^׺Pf#=1M[c+6%- V6W|jeGSZ'\?T+,3(޼Ƨeˊ4ezeR6z/$@H_)q2":|_F,y|DS}5r.+:8vG\ _-6ɤ}ss5I?|^}jJo`i%o[.AA\gGN3)7Up;1$Bg1-tpJ^3!ww]`-oz;ݵX ^Ӽ <#n~7>q5+踯}ۜX^200`hO[(F7oO5oߢEmNس/\x-{R= Cl4ma [LA7#"[ @4^U[w*1/a9֐}8Tj7&PEQ*!y8%$awYAT|UJ(Ulr"W!RTTJq㚏+8U PMQ!E6f"ZwN;cp,R'd2ۡ/wr?m~𒓩W gh4~yi1Q.hI8E<}]2{/-Njoouh1ݍ˝ehwi3v㊇Z|[CQDӧofX,<ϳ,kgʠzc\~`u۲tw}~;b*&;.5tח.J@MB YCv4 E2GE쓺ZV-}~_24Iѩ%Y_V떶 Yh $Ɠ'\2X;QN `r,t hȍ]tt_ɓzRC7y3jCQb@6HG Sd>cv8'?H:&Ǿ_l(J(TIl ė 8NvU#:="6bvXvaSOAOdOP,_DFgX$HNΒin3f+'/rwKSgg; xX,Ơg'jFm~c='S'g {-ȘRx>nOS^t-hvza*k(Zט7U*vI^^W(}+e[o4 JSV%|7co=A^uR)`f' & ΝYrd[຦#~Bӛe1H4P9v{+'?F3%i5vy_)ѹuOwE!&} ՀRY)r|}G  3T2{ B'DD )(f/:Qo2D+:XB@j#Hj8{'D/,P՛a__=tQ` GUBׁYpkQE!wݾ}z˲E#U&+i7hp >olll5Cz %?ƺ<>ơRwC+69]&#p7J? 5 F=Xy[Lzyf͚lU]9俻r)c`6G Ӌy>wRrK~ 92|*E-ɩgbUyƬp+AA?/fPДHP#`B!#BϘf4̏>?=Uqr}㫉7Td^1vNH"rx,_}r݈)ͿY!Mg>XKݑEz {>hI Op,[17{ư5(==ДM㆒KnRw?-}DP@o8=jQCA Pn5ڂ-iR M1Fw\Oud`u}kPsnG³ `vhj򾖽|Q]{TBi_c8NAsE4xN&(P@)^@!B8W9C_?{JRCu;zϻ{EWy۾{Iۼn3oYsg,4`݃zo[_͙3g}܁_͝XI?I}RBrsOd@ɗͭZm6̂6-BG֡RۺYl^:&q7.+K쨇Fxڙ MKlnCNnom(H)h-g-U:+O&kYdgtŷ%Gm*sµ+2z][sMM3 t7Vū7Wv4~^M]Qoj-~vKsK2j^{w$ZB6 [ =Mt /<imޕ_Ͳzm ';'AA\φ] xPM BPuڅM"!nJ$/JoG"!8?*)oVy;5OEfF IDATŠ4Zs&/aN/oo?b7ۭ0!ut#A[EE|ӱn)e9 T 4'EgL|̃BCC{zz؂L[Hi˾駓̙3Go#nG xm\ULi7.gdnSh1ɤN^¸^,F=$AA\x& ob-b Ф&`?AA% ffuc  z8 Gfy`X,.uޥo&  EQ",<%PB UNAA\E%@)(R4Ch":pֲng,#66xće[b3Z- 6)YVW[l˝l͆3"bHIIIQ+dYSМ`[K xw""2G$Dl)ҶUFD\(:%%!6!uiѵ6DbsF5!#VEQE2k3((*bKA O)h_16#vL.rks+~*kir#-ܲQ7& mSŖ+(#ֺ:$av'Yf؞Qf2m-:E%l/S1GmMp&,25  E[giPhh ^fρ1w :}HIzROiz Ԩt^^^^^^s mK4oM.ѤݺB(l{quKle%-mˈJ:[թeW/ͽ,)Izn8j+ˌ. <30gUc&Un+ mڋOHBrCnɨN}F !7(IH)H2'W }bTljZ|IS? ݫWTfx 5= #b;ڗ9AaOMPuPLO!<<-r{#mmm:jvrIT~Ojjhh?"Y |}o&#;1:SٕÏBU+]vGH@Ruz3#I@Lmޚxq%5yjTj;F=xרvY ,TeQ gpm 14_;ҩU'PBRv 9Ns qIlM?ǴN= 6$V+++86(+e^h[TN)u#߆! ~( AS#%P4 Q z0'o.>ṵL@pN}e$ywNv̲7U*vI^^ݔb9cwh}(FL‘#Q{뛵G|& nW 5tu{K)Mf[&:{ [1a]<Ķ(@]nB6o ָw!2lw[ӱR0}qT08ߩ͹ n6 [zV;!//nx) jfܱ4epWI5S-o F\Y05l phmٻ%N}nb-y=oo J :K"CAx<σ %i Mr?P_{NNuW^.օ;wZ,kօ={Xjuu!??ߺPPP`]x7 oegeclcwzژSy쀾L[6$U.My~:$Vu5e&Q!nT9OF%9,[\6`|#T# KWچ{~m+D)!޽F4gl;5@ ɄG lwIlK{k;J 0As 2a-?(r0[`!??Q.2<~W[ J. NɌ ^^)[- V<@A[U/ʸ C_f8f*ZթaTھ@:1qZaS䨙f&'zvb5@6;Y6] =2uCV-9+pӻ-+Bgm+Y4aƿh1rGs}0rOWZ1K 84/w|U:v5n2uWEdGڼȦ,(5*6Y `[}.n]_=!5R7۔⾚'e_nqy{9=v\tz]9C1p^@6\Ljݽ3<[0Aďd!tgJY,U{8C2͉rY=CFF#UL-ls7]#sSȦ>Vp Ebd'fJqQʣ[]L3QɏAA\*<dz,x7Yxka ϰ,DORZWΚCA0s<(pý̃u~ϯ,AA?M3ϩfFdԌc- xoJE5FUV֏,#"aZ¥}O:((b_<충w -nh-~.쉲Io,CRjb[9Cg9bK_sP:܄?زVĻh->QJJB$* r~ (E-0\_et6ivTjOzam}mya^ J^BpiUjQL兣lQu/tTFbVP|Oz9kK[w[)=X_[$v[fCyvD]>2V:])kԚRUSC=/cWity)NkJk%h8.]JTݰeފLO~em}ꨀY[*sC-PEiԷ4ՖS [4bOu KJf(ڨJӔT]8z⳱T{ը:g.;-1`Vv~U /S*4,(:>ɬyƖtd/έ㫨"C6`̨2 eh-߳y_SdCY-.~Ijڦ@q] th4uC՚&B2w ~(P) 4E "zM/g\j &~:\f^o߲- 㦭FǷr7Rwuh5 {TK` 2 Ir_瑾R?epaO^4ڗ,}]u:m@ݺNu#!*JjJ۲M{񿳜~prrgtq@p}+={\y/cx%|ro>+')n|/T=-l$J兑R]vo䏙n|}ʸKJTrUk e@_|U~ͩȞB.Yn/Jxq_T}#`Y٭U>X?8Iҝ%XXA)n(U[Y-=1 =w P! )]4 Lۉ(ӳ!0^f2-l0MlϵS)["{i.xO;>&J?TOO/@{گhwhL߶wxtc;תX`<}8.xj5d3Gah1Nm$@sx kc3"",3&4/='῿ -:S[춍a*49B"; uEW'x a8 3c:cƘf`&P-}T%@Ib4E 5[BL~!gy @{]AE *6ᅒ Ŷ1 Tll()O\RPSAQj{ݨm*$b{Ј:8)*՛O⺡o.uǞ"}6 `d0uxFXϦ@G@Z(Cg?F G 0|ReNt̯.T}CRXvL~Jok2O2? vxh :׭܅0ۨ\/Q^-o='O[Z[[[l=@j輐! djO,uN4oMw m ;5@O~Fѷ"*FtO;xwjgȪp#?H;?HPQB"&y?Eض2huLa˚YF%m_ [ =:Ữs܄ϬHDV鎓7H{u7՝C-rE-td;o6(+e- $@]nԊ$u^A_e@Ck՝ݹ,dIɎy33khݓܹhlxVP<}_wJ/;t"U+kKW,`O۾ڦӧV'xoܹY,ha˜ţ:IAjWg>ojϛޯ:{Ën]=@/l \"/ ](_9r³ y-psN=z>!k0.F:|\ 6oj"( AS#%P4 QGzOzLBB̨ygϛ7o㤾;̧ cU]l '+7~8-Y2 ҋ&Qpv@TWs{V1ViD&c۴ ;lgcՀ:i='ε'F8~ضaH.-Xn]nB6lp54tUȜi97aܸ?\/W*l݄O tb֖[a S߷w2m <>y 9j%<_1!Bc;C14_{_v ^.tyz IDATJ(PkǷ־5g府ޡT |r2pժ@)d6m 6s3ֱ}ގ{׬\7Hoܿ)2piDX0n$JWؽGb'E/c/"v;gpݿ1 ڤeͭڂYz:!s6g'.\6.ZVdjjL zx`@*6 {6nBd^00{%"/AOPxYX IETE |gkNqYϿxݣ+yR9LKnW jh?ZvsY(N|/[xo8SS,H=[#g%7R;Kwf-mÁ J?]YYXutl#4L¨T9gZT[Ftl}Pm5.sWM!S,tT:l;<᧟~$T(s  gІ~d% }@_us)}zUhH*iM) Ǥ t`0 t7x}l_2AAʄkRiӠat`-ׯk3mF%mCKrHQ{9~99mmM%/.x[ uWq |}J4S9GS6w-sl 3>:RCʓZIz~)I ,8"hOjam\긂k%{Fy)H4|a,~^,0[Z!0: 'l{E܈[s&YԩAFh\a+Ү"?u8 t-gjTh\c5?>|ߌmll Mcf,y|TD#%cOӾ5Lgtt|6.ףG ts=WO7oX5111ecօ}`61'wyyTk[O+\G͟NC=\2&@>u@gXLFC_gCJeg0)kbo{j%P &lV4?7(LHX=ޯq8ԃd\Ѷz)T򽇌f~Pg@{IkOg4S4ejݣBh?Q$\{t`oh&=6'w,9k[ .zN#$O=d0%iQ=U|@7d0 C:/L3jT s:TBh޽ wlm)]; P} *]^ĎV+Q=e{{#h-Ex;7yk#HyuBeIb"Y,M4Oi'`6ױqV{!@Lyv'#tc{^zbvt<)aTAo<{S$ʟz} uh|u8Ət͇陙@{#OZn f3o=ܑ!`=Po}gOORE$KLcO6 /yuϷ?fj<_\r1ܠͦQ 𿊯Š,&k y>4z p/4l8+$Ҳwi]Rگe1OZ9s2'9 g8+eȟu_#d|꺯)qYĵpLDgSW^jj)/R)MII8_xuq➏/ϹE:wo333ħC?H3GJ["9a'%QVGz?<})]M5n|fI엾~*am}w>( c_BqڲW 57paSiaVfܩgff +0MDۀ/Oq333+BkU<@(zG fffL6j1miL]ݨ-?'/~<_NLa~E | rrrffzک(OcaZՒ- r݊ea:{{KإOc oBkp~x#/#rfh>4 }ÿS,LbmN7 ĴM!b{m;:|ۂXgm Yg@`e0s9vE&RE&kxG=l}A󚍫._#)"z̼\Ykxi7Xp?u 55í^&Q)v4Bݿ{?W3ʬ/+5ݎ E'|oޏgZww~U0!`Gh|a zmOÇ30 zVFl m,و%ɷ;n.~Sr=8:QqRYpx__hV Jm|Ç>qصg sm (xөukV ؼ Vj0 AP4\U(b& RzuĄ=$0<Ϯr8MO^zrrҮX:cc&')?mj•5kVŋWZ(v|KXNMM@ŋONOOO1 qG l;saͿXkfL}&-(y[b=s/zc#bS"f}[;S\)f/M ÇW&"03?,,p?66#@]Wfxk:99I~~~3vԔKKX,.uJ cen `25CC_|Ąkfo~s,]1 m?y-wܖ|ui(C{]ks8!K@۴½F: ddv]L )qJ/SchNwf^=ޢ𥳐%OO/2ZDZe֡9;0;es/M.S6o"ͫx }gBC %bYb<[C2g CF&}^h6պIXjYz{{MS ])Ey[Iod:Gl͊f+d%ƹ%2YAubO.Wʒ>lYgڡ ]2WU5]gn<SoItJ/U앓>VXPRڷ鰓</+Wgo2~tZk{^F/@ }]u%bYgu6ڲu52ÇBc86b ?fuv;X]ɒznr\bZ>iRo꧞" Ve*«Z5-1P? ug_rG:F^n 1ByL`u}H0w΃.hc׆_y!("Ruw>UV|0{n$QdԪl]]:ݩ4z4rG:14/etEdUXv2cpyA17GDejr9 fT oo*37w9ڴřG57M Ϣ%ri H><6ʍj!T{fkjhCm#򔈆!.jN(U*S]!;FV-b %Muwx? pJRV#)T*e˲cpFV)*wti,3LCq}Ƞ2vvAO֜M?ܮaz/smbpXV/ZR*j#: YEhWOgTO XV/{SKjW/ JDQE,Ӯz[7o׌Vc9·>V  A X`X`Le[8=Bi_Q1YK r#gEhdF;<{c}HqqYY@FψI}1ʵvT]?i+ E6ȻGHzinK䷌ˊ ~Vݴ5m{9~bG A/zgp"@9ݖ,d7>蚔ʬ"Ty@ىCG$/9ki84kmF`|7S2MKFQMW)? ]qv[#ox,9΃%> ΚB+HaC" HJ5,簾"~\lLj9؜}.L3ti[omO~T~ ·>VA,,?M)vX߶x\1E*j*GWNa0gs]>ϒ66y,!P)ڳHs1ܭjGH=4-ԋA\JB':G,DNK^(@Kz-Zwү؜y>cWzZMNcGcƜ5@;yѷKzW7g {eCCu %`hNp jEF?SD"h+_j|Dԩ҆Kz0 `ogkγ;6࿚dmT ++i97=ݴՀ( e+wsɚ~2== -~tDS`.;f碇l/pZ-sd՝ l|C0G\sX`\QHb>x=qO^Q/ 滊ҔfpwF/+,Z ^6,b І=* ?F,,$3gN)pӕ: { R@ߏb){CmjZ†aaІMʜY1N͉nKN!%w;c`u]3pA$e9X7k i騷%:uoZT}Qеn^KZDڔ#Pdr  <[vn_*{Q{Ei"g"H'`c<`l~w5?wXtshCMX;Aǩ)49NaGzv jGJPӢ\?aQ hit^zDNᑷ4B W>+omMwfĥIv.b@a,+;,O"7*Lvb ƀ2 o{\L}R@Er,gE. ro=stQN|9eK$ݧBd0p̈LQYǛ;))9p>{zQq1GX>>OQ}| jnt} JL t(IkEᢘ.}gVp Tp=^"Xi8p;@8ڳ+9&1--18[~\`hX7[3yVV`9*5fG5>|\na0 Ϗ?~,/yJJmc&KF~(~xfU8_&AMx/i4.zܯEu7~T^PPn%ާ?5Qq /Nj Erg @ޱ؟kP- ђUv?+WKF)ӧguRDX؆ |ǀ[oئdcG_+宮 60u21Yt۽Ӥ f(50M"S۪_g'3q6?<...9pxWaookZ)*6~(6>|| pX`0 fffh`}+$m+Ekm[ mišI\F˜]j|xk7=j;SҪ]wI$]mUR@?;Mٲ9"I13=R?ȵ|mV5oRVޑXQ|^>o1$p݆ 637X (04MtkJ>`@TR@qۊEJpݲПw3xWPDqv ^nʶz A%Dp׮;z /jIi?9V<PtWtKUI# 3<"^ko `G&<Pi3LكcUҦȹUmmЦ/Xthl 3>:RCʓZᦿ62jZQѺa4@㬣jQaLnc==&/WpxoCjh[lXma{^lpeG[!88iՎ[ 6,^陷n3}k66 Ml61mrhT7ڤ:b2: ~i6Lfb;[laZ TyA!b1C%Cf$; S322R3vw 2?FZZW!i2YoXơKa Pr_3t `5cfPCI mQEL߮hkMNXj{BHogh/I:H,&ʁoI@____v1 x=FPF-4tˡ>ſ满"z ;ycALyzzf=5doԜVݸE)ݨKo߸6joNEuC@:m096j2tRc+,xXd.6n02 FgF -% b#WQo0YhR!yvwg fj 7{{·> ~4z3XmZ#!(եe;\@qc/*2oaA?e\q; ͖G\7_jvDU1n*MTV~S ,Mvjͫݛ{!/ Ҝgh6Ć2C`mCVia5?3ƫU3A )PST[= Ab MfX6+BJ|=p&ȾF:ןSꂮ8DE5 6e me~ aI淴UegLH+Ȭ/Vi/I*MP iY}U2),p\׹qwvx.8<\hEEd.xğ7ٛBV Qv0s4hrGZնoó/_01|xoEÇ0a0C4=30M=ۍl63o_|˃6F)+8.O_T PՊ5논y: m1^.e"́6G),犤<5]hP/1M `1Bf’\nhq8kKV-N4S1w69@D/ <>l6lM ^p< _ze.gRe/|},V{.(S2iuT@f$sƦ,Iyl{,%#|ДlY@./$/tXߜE\{ M6uuVAVfi1Ӵl hhbviKw]Du]<3jb޽ x u b /`pɮ+oǿ(lh˟Ml`mCКnKOk/dW w$&1LnUi Z_8>|bk"'"8>ô**^p_0=\vw7 3\aTFַ- т@` I.eÇ>fl3`a 6?@0ۍÇ>|㛃#g?6`}bkr9& }guz{Y,ղ.gL+s;Y^ W>[;孇Sg }clj+I˚^45Xt Ϊ:^jWw0o9Sq\,.Gm$ך%7gdf@&++ ]Vm?5[C{stղ>X :@]{u()))q)CL龽%鲺9:g't)Gz5Z_z뱺+=)}gkkA]lVRL͍?ptٕAZ ΟsϠ%5^^KKɣ\Q,_~mq6M֮[JP\b OBunߛYͲK!./BVҫ?޳3CKCc86b ?fue)+b(`UXf"_sP?|%{cTHHHHHHh<АS*[ׇs<6vmSWiz{[="+Ug;zSj.REJVh+Ow:eG:14/etErgarNYNLUcqJj]WVRbĤpJ9@PJEMvXcLJVeW*;Q<'O.USw$5Z_I npԣR|:[ö J1m,bS5ZrYD*~xrh=a"rhϋ@ha?ݣTOtŧ ݙ5J Ѐ>9+UjAO1[}hhHH BI9ޔ(RU5pTƎps ħ=NeIre,M^Vwu5]߯&Θ5#Ҵ+&;[ + _'+ W%EZZY;ۤɼ㑕t52/;z 0`x+7IMuw,Q y h7S"<9sdzM]p[j~N`xrdu(# wV,W~Q2--~0+nH^R?݁[ޝwأTp^[gr؈;11<~EX d).4;pٕy[zb|7S2MKFQMW)gD%: %aiݥp@ի2.MWl%/tPvbP1_Go>||K "#abgo!t>\1E*j C={מuj _4!ה3{9]as]ϒ66y,!P)ڳHt]ξ#ݯhi|]`sg 2== =VATkVn#{$R \]XSm>3 jO(.,m*Hn^5JP5Y\ѫMEjE?$f֬y}Z(Y6 4H|@Y}N8ȩ/@]cLe'KuAMِ65mq.ra1^et%Dz_!.Qv3.$u ӿk[lRGOa%"jNN9X6R_;rb7THu8SLJ;Pc㱿>~toS2M{{`pFȋ<֢mɹe#&"/)`VJʓd3G<y{7/sh=O,RXxged7y=F14j2} ,?f,bڽphՂMXɌ KYWFn*VNz[0@ӎ;ЭM@ړr',:lq۹}EEG7OZAX\`ukS1Al-BN}̓Yre4Z%S Dl2EQ|YR"Y2N`G)χ~9@('I6CQC} ϭE~9;~|S+ $T+-Oڿ{~+##'.b"xѱ!:&&F$'Y&XNsqVo>3#&.=MnUrctC@sA\]q<+mpm[nZhϩDozё(Q 7?:x=%xC Hz_ە( Ťp;kħ3ׂEYq k~ qgWrLbZZcpN/4_ M޽w{q V%L^(}kR YsywZNl.P"@CA)3Y,@ (j>1~xS>;:鐉E@>va0 Ϗ?~,,io˕L&'85IP%[Ut5⽤`@nJr_^.j * @^*,i>@N=7I_LNj@ʻ@99ߥɋ&o_j 5.Ggw_2) xG%PEll!Ep ҦFֺK]bPElذ!,Z @ U Uuܟ Q}J$ P!lC8HzaRw޿ sn0_[˗B6-B~˻bJJG" g))v:b*{KIȼ8+d %W:ƒW9c0>ai/56oeptWBYU'6w\QmDUH(xn6Oo RϵmaAsR;ZY=D-7S/?`q sÿlHvm&mSDII[ ՀYzBOfx2Byܲ:ԏ{ _yA} ۇ6An{P7Xsxx-|q/f qiiJgsih/X:cD"tGSm>z],oߏ5PSD"i[K(l+;5JAvVTD\2(o[in&?u\#5(]sE; ?Py6r5/8ȌG\㢳T\J` g_s-фI@WX=:5r#nt[P w{cr{6&zqRPc ݫ7VkeWE"bx|Blc=s りSQJ:sи= W]өEO ߣh5[>b R<[$;`3`:K}1c06C6iL߶x\)Z;9M6i}zlΆ%koCkd,E@$k2,Q?Z]0z`0\<5nF! <-ʒnIARץhڬ{5wܤ'`(7 gl!k~Rw-^4pER&}l > pH"_vo G"YO>Lєau z@AX Q[O^7̀Pss_P[ 5! KUC& E\3t~HYK`NI-㐢@[Kp }}}}#Jyv,.*U mhPBM@ӤS*`*VtO0Tf0 A#L.X8KmhIp $j{ZxnqsM6k ۵NzN?Q.nM2~Po4굽Ô'ƍ]r`7` I+2 45u,tMu%M2Eʜ\LGY2yd|p:LQ[9IDŽ`pRRR寅^X xgװL)!H5}+L @ h`hPhbŅ%8ĕc:3T821zzŇbvuokEq2BůW+x=.S4;vK5OTծܽu>Bcs=1&#zaabNI}>Sj'2UZMXocB}f׆^Ny.[b ˈv6Ko+XeMҴ1KP5;KE5r+D{_O'O HOO-Lq&rzE F9W eϛ[)q`8__#i9eYU{%2I&;SŠ{(x4l4Bq``PRW*[Ay-3Iꠋewd:7ia-M/Z\Iiڲ2q<OMs̫B˾j/4ON[Vq!ͦi+̦Ip8l&i.kr090ɢh&Q`ʝ[KolLͦV ELL\2{lFPf0C.BH|.TWg2Hx_Wo 7z:7kY,(Bu5kB8AWAlac[<$ Y,Vzz6RzyɓW 7444,6icߨaݷJ5RГ`|ޝJ._1܈Pn_HWWQ7Aw^^`sKKd-| }u=PPp`*e [N<4pnbn Xcx` ;Y؇ d8\㟗&/kqq,P.YeY'r-}+A)ggg e,V+ %}뮻BCCC}Cj%C7.2vWl3>(ˣ6lٓUHiM\cFR|g%w9; cq{g =lY Vp%  +0  ge쥸: Tt3#߬+:Ɵ~ U:핷׾.˙)֝-'-4Wq#{F嗛6mڴ鱎nԊ־`+X VoAD?`1# I*&/aQ\ L=c>1$x0ax푆3s;+9?:on`x$>^6?}ۣW?_):deٿ?+z^7UGA7O[ˍ>~SrQP8>-Ł=??^7,Bw|7V~C5 <`+X!̺ξSVE''FX -IxDV'0; ?$K|k=`Ut?5}wĞۋ~Z{W=z1/^pjз)ϼ<7?fCޑ~ba9@p0g?muT=v}gߛ=-Y(_sjUF*ϮǕN;˜ə:Om5_kZNu]e.u]5G]55.]5]:2Љ̲^JݔBGnYя;r3Vv5ev aVff6ԝY9hFOt ^i.-kq=Qۨ_4jlk~?e|k^<]2җ zg?tޔ,s̚a_r/):-]z .9 [ͺޚd~zoT[?|)Nu;_-̺J̕\1:-%M1AOfYӰ{Fm\UkMem 5nI}oCʚz1Q={w}07KJ03#>]FQӷg"*>SpMnfYc1}쵤$L77e1ot,kJZw#[&ϵ0k[&mwcָFuss˺$Zq.2-^(`]|5C_ '0 ? _ !*]s%ǺdCjeϓ]y?XBR)ReW !믏B*K"<<<<""Z̗]2i{W;;]/qco4Zέ?bnBk? )];@=]S tC)hP

XBi6>E>NАBR*0N2~N}&A(ùqy~F&o3QqYwkAN*$ ͐@ZQ6Xڐ*YjPguTUL F @?h? ?0O\,$/\p Ý i{-ݶA}7!)&tU&M~W1,mrV&&[ To\z?\3d29sӧN2_~ɓ'O<_ 'N|z~ttǏώ;FFF=駟9r]qhhO>׿kZFs>裏>:tЇ~888V~\.7ys_"49kMӚCR*cIl!ћ*{I5Mӓ fʵFh;F]\X4B@(h M#:,#YCӴDAÇ>ʡ it[*<z; Uqu&.)$M=Ӝ*kp9 xu_KSڇ\Ԕ#IV(;]#Biz@ u<sT@=arl|K>`5Yx_^EM4=^p4k =L^ZT؅~bEC@/ub-Ė9 [i%~uD(>ĉi>co+P>Zl[6d}%N@f3pY's)Khg)S.,s3PsK׻΍ E.9WsULiqApi&o EPVo{:_PXrϣM94L6eՆӧO۴Vj]m 6%^w>3 6Uvll^]=u$Q|Ǐ!7;bV*sN7:V~im(Y85'_}_~}g:i4mM=i6%نߩ-_1{eUź,p` ]sEYwz\j=M O/iq/Z%Fc\"[fLx|=*5W4w!DT{ 1ԏ{jn*s\jI[|oKs[_ȋ "J@Hs[p[Khn0 d:NUskLJ&;Bߢ5FRf}LR[Ook)$h4o*s551)`MJ{&#?3jGYraSWK fx4W:Zء77:4  e]Gq"ryܤ5/BOv>ۿ1v.ؾbsڳM=Q{yymr<-lDK Vek{, ~`G~Xd~sG!ve4F? IDATZ9u ȋ) 0s 3s{)pῖk!!-q6ǎ}urv(xdHϫZhk-Z G"ENk~@QK:ř~[TNb?:D>!$~]Mǭ1ŀ6L@m;׋m1GfǙi^ #$̟? *e׻[ʼnQ )5-rb$L&Ad5pc29XVw4vB2BzM0VD\`es[G3\u5z/")!^0OލڸI~0}3h&?~u; Kub⡘5)2S׭iƍ=~O| fF/'~ʜ0r/ǎ;vltG<lVE7;zl_o j ?O9rddį7c?ӡO=Cbv+Ek7?IIBqA!pvL~fUg {kN@(Q4F`~#30;3fm jE$QX ~VYmSݽ8!IRj[!UUJTT~2a W%Cc=⤴^zB~ q8#=}-QJ7VI5:>۞` I+4CQLPʞSmUMH;`gт&dWmkU=o4Svmݽrc91l@ ; @;ʀP7_V^QϤN16>j`4-{oF˹SJq~RVE_kFrm]\։֡1ug8?gϔڏ;:uT;`o)<+;Yi*IcnRr- = r5oc<֑ѽ)ʕ2Ǔ JY NAkV/`gn M,=W^]Q֔| L?NQȷsmYX(2 ([Xy}O`ד|Ύml.'ݚ#Qx*?WV߷4i5pa@7k!hl&\ʘPA_s͋_* '/RZmS5<=厄x[/n+ʯ@[{*]EP_\wdKsZGƇp}j t )#d7'[`\z|ϓbW{+mAsJ?9k@ v5ee97`>ܪ`0` fx# ~ ߟgp)v$V7)%Ԭ I:8 N=oNM٣%YIY i999i}MNNXg.LM8{ .5_(jzrbffvbp,fî!kQ?{ iS9⌦ɻVfy$M*O%p{=<@$쏊`k?/r#RћڧbF^Tܸ76JzvmY`>,*%i""6rɉ<w?gb\Y6La sMBQi%;L$mAz4ۜSillNŃ6W$GN-k /=3P9k}a޼c'@d*ެԻ66$ Ӿp]E"yu.0wEnLö] Z'me y$wf sm߬c )X~ђX vU*6۱ "i|suR Q^c&&>*WJ%acUЊJJm& Vؔ{Їg[sjLFco}0! ^ܵ5 @U"Ap/&\{{%OWF_'r6EWo`x , t (Tob *S?Ԑt (mVySX5O;_NxY10 'ƣ ) T-78:G' d孺]iyz 6 O 14oIhlUwHrP'} 5*Dj+u*x1T)U֝)^vV -O&n`c6͹@+@k#3Pٲ5 ө}O+u}oHdkǦ`zOV'+I][jNN,YdV3V؎g?p@N-Ϯ"LUu=.ps `15Ig~?X i4 gL???#RNmF;?O7[&z̹E 0{[N\)Zu`SYy#yV 3T+Xy}Kg`'L}^VD~|LZ(U'ȉ3Ϋ5,%?\}̘] o,]yW?[6sajEw6ӕgbT'dTZut u 93;9b)3%:dUqŠp` =kF2Ln9B-qeyHSU!a,4~H{w,p^r7nx`66Tbv]'hg/)؉B~8Mn/nWQ/NW&S^)V(~e~%>kG6p6.<rPhËZ"bԒ1tjqvGqU KL\Neʳ_ժd\ax&:_f^RK\'Ƌ_za!N' Gփ< @[2۔XOKxt:c>rvOJ(f{y<6.Ĕqɩ;Ƴq3,efCؼ-޻O@zhr3NTlb ~k"7س8XP<*ر-j^I1U BP\kyX:i fgi)`M.S/X o5 M,ٻ}SV{.x԰B nCrtiO@^-!t{) }A)`D4UIi (x,|ۮ]HmfR=صfA+vE 'OYhZsU!`ש ; LNrZ e?=w5iZ?o)`sjYs5 3/ 'I s4y5wu.IsumpB yjъR$mk;-ScncBPc?**^i6n^ 6xqQ]+r>@ A{`W5jw{B%놂+.ӁdaZJbW`Gl"?\??Lw;oD@{*k*W MC/d "y[j[ P6L5 EOG~ZR Z}޶;#;c_G>rjaEUCyFl> FITBȓRk qbx5&p]×8;s@ Xs] {/:$'m#2 Eޞ Z7p+sp-^XNn e{M }\ IM׌r]L醇z!M Ί.k5fqx2l`9VL@]4IXT LF@;}oG>1, ˊ F}W.90qT,GCi: W ֦t f~xyK&uu2`9R@2H(6MX`6t6U{GsoKKϹ<`|wPS5FsOw0sw:*oMO>[Eez &n_fvlǫ;^TbUt䢄j/+H]G[8͹W"ejj`Dl@&DqOR{ZM$l)Qca5]=*poa _Bu0P\/fà o_#[lݺu-[KA?a,1Y--) HX"}"Ɂ8#`6fcKYHΫPm B[!@qRpI԰m:D9i( W٣Ohi.>#bMP '{ _egt@WJϨ!ں ,a@' Z/ו6,U YqLdt>GR-pκiqjXȺWGzy prWKy%T[LΎmTK)-*9[vVEс3$:U0G3WĴ jN5yzFQWIp/r|&j"eT)jwI>*R{yyEӖ sźj>Sir z4IL{`h(O$ 3K4|\%tl0c2Ɂ?oH @Sg}^h"rXrnU[dg2HuPV8i2H uw cYՔL`Q4!ar xyU(wAQf0C-kEL&UKdd"/uM&7z:7kY,(Bu5kB8AWAlac[<$ Y,Vzz\W*/OߞwO>>Gҗ@+'I1y(VkwFz0<^&[jM]pBuj Kt'"-ogZޥ_+{w]r ύeOMhWE]\L~]\دKxi[.f eiPXVje湂yXW`+X V́#&~` $``o+LJ ҙ2יirŤ-V_W+serrN2W`~?`~  FrWcH}G}ح]FQ3UWMan5Δ2O]:EWtUVv {$4ؖ6@UZVmw[[at]5 j4is*p[n}Ly()5czwZ{33+݋n̬찥u5?9ޭ -yu]meAf[-,'+( ~fS{qmWcC Bv썼ז,(:#{Oo["C۸ʜEg tŜd. ]O>a|2 IDAT‹I&;{WotRfc J0i+7fuq -e`vʺea] VEAO`2 ??0644{hll=z[0ͱRQ2<<<<<<"`6>""<<<<GbQÃYn/bKe88[&eA(\Hŵ|QԘJ&""eScy5 iD)_X.Wիzq.EtN}m{С7/tѿ];q l4Z%;]ާV47l>-0 G/pùkȣrQvBdP#*Y/Uj 4Dedrt Cbp i"J^P"liUi:r|C@ZZGvbA }(v o.ܣRiy~v-ҽYT~IWI0c$d2Yפ64=k嵩O.G'ʈie0p`]*Z!Y+J{֖:uJ*pT,Ky s@vl ]=h$JntʤGI$I|' b^3[/f V+?4 uSH4裏{o~c{ؾ}m۶=o?# m~a=o׿C|yW]z)#Q"i[fJ\7m穝ENK8n`|l:eR).׍zbx'K EPJ)GH{weۃVki VoO4~'G0¶?M޷W^bxuۇ8{ n0Q k/h!H*ekkmi5.j:%ݑ} % tϳ+;;ÔikY9`3q eB FpS4vnh\9ۼxa-Ve e|KF-r;3k a2'G*8̇>x;"ʵ6Tv;,א8vE! ~{F*;` |>P'L&\+0>rќA3[m,$7FI]ǭ1ŀ6btMNC)44О%=>`2QXSOnُ͜7N.C2䲖\ Iޞ^\1&6(>L_?:8vnJg1'kW]sɎ$-n `ѽ: ŝ6gۅeX 2`+XWMЄGϾn3pgA l>|Rb3^tmq}sEE4D-l@{@o7ZXhYAŽ,stmkJ!f8g?U Ov̜*˳͝}WSʠbAQj-p[XB[Uc:E_ ZB~Q/UGn\ V{b0ظ5)S=66ZaΊ[6lvaE_gC*C>LjoR|jt|Dݷ=!wB}0ږZlX-ܓ|"v#<]gK?l6?;Li*Ise2www5K*mפ-&ȗCyj`1+U$nʼǻr^Y}߾Ҥ3ŨS{z⾷;>qv 6re jc!d=#cc} #''Q)OM?P ?ں{l_s )}f/ᶘlacOпX)n uyP%Ev|p8eഘ[{?OC)'gַmł +X Vp)@G ??O3XڽJpmGaU0Yё?oS.Ӏ\ 4?S V1] mm1 "9 QT*YۓRw~xۮ;#eۚV/lZp (ߚVTy;vFrCnq=wȮ͉@$-6"bCJZJ(ߒ? %quƒPka- NOo6~g||,U!b\Pڹ964t](oq%P_TL03x؛R0 M*nDF7U 810}S6_]q@ Ա.pObzd)kgT "yCXQgHv`-(@|AŦ˳BY ( qYK<}jbNUƋ/iix*'*d֔Ы86S9<ɴ%wŧddEHT[P,ky`sLvN@I"c ɑUZaXu D(Uzr*.^8J\}L`?p@S<;u E[ V\MigL???#ǥ6rph #E3^AM۠)tȞf=O # rV*ih_,?&3aFCZ%xg]VZgdkRΊ0as>'A\ܸk:صvi@?u:\LOΘYgO&<10ڛ 0dX!#4q׾}c} Z>(&kSC<eC)Jͪ}8M`ێeTYy\d\9Lc U+F@{~ƃL7/}mY`,d"0(·+]ڎCMK!- U/MѴF*=ǘ0=庝^(q|X>j` ^ۨc/ܽ yjъR[y?ibTiS=Vsx_3?oq)س (߆KM;hnecֶD/IqWO@(QUv-];H[em³_$ F*!εe-G_[-N u}|U7xu˒L;cYooon{CnMKˣY^3UK '0ZSIW7e)QA!: P*g'<|:.ڊVJ:KV,EYV{*8*kUB]cZ2-K]Š-WY6>7]Bm0݀HTPt6Ƃ-) 7$7GH_>hý{s:mzFK/ )j=A膵x0b FVjZ3ԫ\;vڵkO?_mO5jek3 [`+ڧE!*Սa$G `En`(T9JAjgʑ%X`r\SwV)LnH.{5zpogĆ,@_~_nӻݮO2J܌~-I/UJ0w˝auR 쑺t@dAbɮU IԝjfZѨۋRSkyQv0|R G뇵ZɯG1znX;fbxY&ʼbdȘq:r^J׫ +kȀM3o*JR|{Oʒ)jHIMscFg୎8ˋXFm-yynuLuc>Ԭ1 E3jM?^e0ca|Dm//ҚH p3C 6/^`{saXqߋ8>BvCT&ź.mHBvt\Ez$ (Z'Hn} 'KbQ+e 0`EY@`&3wn! cl悝Iis+Utpn.xGXWZl ^LP0ōQ"-nRص18{/XPp䈢mW{&V,<ڙkKWzE%"Qp&$Z .NP" $=8Ȩ8 ?҇6%Ml [kg:->3ҺhzT܂gvy Js乂8\:wņ[)o7VHXOf(oym/!C#dӷw9Bi=+T m\XZx$ QFZx*) =c{xwUaH.޲Fa4*X?>={|ωbYUodmWVM Dg"lBtj[z[,&g.S%fՑK.VAh47:s:@EeħG(ǽgW I!H.*I'r!ɥ5s}؋50INk=;>ܛm1OgNIp\$Wi&fig2F dC" .iɰB3! Ay.g;KbQ+23c-0(X5j^8WٳgD(n4px7Æ"&?iT[o<:Օ9wI x\zןb zumcX<˜kNnn%f~׸&`q0G3kizˌ,b̔l20MFumo|΀e2)bqj@7 %'FF27"(ãe{_&`03`,^Pf,gowD,솁Ɋ\Xܠ z3o hhh10ӌb5lf0ҳQ]@{444444444`x3a0^`y1X, >l,\nf/DWk+5FrvJ߯TZoUu'剎`iP)Z=Wk/j4-kJ5A$mԽwVU_^5sjW & `2bxY}SÅ WkTvR\o'ܒRݮi}1iw0ْY$fuDb?o?ݪ!/tHRԬvZ}$E8MUV״V-H @ZzUdRv'w*cvyTjP:e~JJuv E"U6ɗogb/{QJcCRkZ5f ގ>Iބ>8Zǖߦ&Ese$ԩk;ݿ$,^j=$JL+[)aIlJgdũiSCA'[ڸ:}W6O IeRI$#/"ث}"f6uK:$SȆ'פ.<"N* X x1LXx7 L5MG{%e>p>,#{=?`~-Q^!kmv{u\}sWno_}Qm_O Xp;YtֺLs`.⇟ڿ[G$|1l&-'Xtv\S{Z`VnwK_ڟY/fʺ>İܻlluNoU อ%a0)3of_KX,M]<~sߟ.by{)+ X왃.d-~}=Y%$cP'Rbnuz-'qF lkʮ}RKz0Py|h pg IDATU#U,]bZÊP`&Ca0{.a<"*G삪OnBQ`.C\b_,%+sY[]&=~v?#m,s'v`2xd`1!vmwűLM8dЪVO^xtN roRmi<_e{~E3/ɷV7oaȫÌk#p `p9#Ϊï\SS(49,9͉K) 7?P|voY4wEk|`l)G^*MD]!d0)¢fk(E(Yn)RζdW?5oɮm2^AP a=èw;d:"έ} JȉE-AJS[Z곣sP`d׫U̪% .Nۉ f3ů{C%Xek`l6#Y~[s|ucpw愺VkJP~`y%Ym-iJE;zaP1zk}uj!#UK U@*l#i1K~`+gV+"d%j`qx?72a[ f_ rfq#:'{!Cim,?_o,MԪ%6+gT T4g3 Fy]CZa:]8ҵvsv݋=+ro[ӽ -hyd-*e ) -V;m,ޒvina M-O*\[XY`ٍ*=`Z zf%ٿܼ{@i!)EI8yxXlk ,g*M)k[;= 9suS*͉nߨ^x]$v|/`#XRUDP_箂 ]oaax1`z[L`_QYoyGng돍LY,`+Nw2bAQJ+wsCufgϼ۟:u򳲇?LjgYժρt"FϝhF]:~x9hrܽy,ώ_FϝR{G\@7eRT]2_"U@.Ww{;<6W)r{ze阵EbnPGM\h[w$HA^Lq U+%*z)՚|?:WmhREJ!Iʜ|>f)|Ԅ\iM}Z@_HAy龎9(NXP4mvE9( ; n˷< .ղZE}MO7k9*1q][wn^JQsUi7+U]̲B"56QM0]<~ݐ>y4(|b3z^79d|;92M3f? NArM[]Pt@}E@OJ*]z5n M=S084R^)7ty -3Y^5Y]ɡO /erْ_Zb ŞI/`ێ `ێ%>.WT E/=R./N;ci&t̄#am=ɪ܄o);<7VP̎ڶb8;6፺ti00H>h4s|M&_*Z(/ZpFڙ+< ,52<<222?])D^y%Pa9>PϭT =>"x=䓢}ugmׁ*J.̮:yq("̕w 8=q#OLQ ;unș[_i)i/*ȡ*GUjOYuGۜ$(PS+ID{>/ƪ;b|G QOޓ`N)lVPˏLY lĂu(>W^=;&L& 7֭ gŤSVۑJj[+/l9+9x6;X7\@A}qqG%qOT ;<6u4iSWs=aq{ضgﮭ@Iӡ][~.#cIIhoT1Ka^e@l.$B-IUen\bݱ_t @x?PI P1~!2W..ٷ=_k{'lJ9kf TAa;{Z8qk|9Ƅ2Uy3q{Ɓn1P *+mǝ6>&&-҆n 2P9N;"GؠcIZlS.Vju9 l5Y5F?譗'8_; Ŷ_y@v=6opfC5#]mMJJϽdbuelKN olSm?N_~oG?byو`フoֲy{툋n YGHԕ.D\|p'^ u=ѩH{kOvzX,X,`b^& `0>P{^DR0w&'֝퐖"ğldGRv}+0Ú1-l0 -UsqxqԒ !Xsu^z.7;;reP4 X,e!?%Pҵ%ؾg4R<; n;y.{N߹[_겜CN|CTe!BGbΘ`X{sizL8뙰X`cfBQ `@Ay<)19EMNNoO?.}睫ob;=3l) _{w޼ >߾1 _\n@2:=ue?@S*ErIbqd3p6UԄ̬{$C|٣1 Qӓ (tA$Mݯե_B`gׁd 3!Q6 f6hm [ =j m|4O>$EZB[v@xv0c-˚+wм̛p5=@&Xs~\F4$VogKtqʊ:Fv̋b>䪖B,-øet Glkh+4MRݻ872|Y"/6Y,]o&զSQ8ANb9-p^'S&(ejRڵcb塲qQqrU d3ː!+iϚCmSye ,(+x}G,#U܄gݼ'h[+ T)Mua۞oʂ -Wom# ?fb(ImB'V*t4v.w랃I֮]ގi>Rv~㪹7^ziHNiU B7UVgJx;hjZRkz ޽}ǎ]vؾ6-)Fl~&Wliz6TTOW7k 띿.nr*j@vV7uzЩȀ5 "/B 7v0A5^+C*eAJ6~1PG]wONs9@\ haޓ-{I]3VGmRݘ`$5k @z:=AzeI@jE ^R윷8n߱t$T+=$X<1;m-yyn|u@sܨd(riUk%ž #:mk~90~l(T9JAjgʑ%iJ~FG~墉CB}Բ{kk @-?MuyUe ziJo'>&@}RFB%(eAF@د% JqQӛQH460_E :땚a&; >K uZ#'ZsQP$XΎ}pو`ƇR@\ Y!T <Q /+$oU0CAj͝*7": kwUkbXEj:r_wBb^ny IB\k ި(&gY}s/5?LO㞬>uSCo 6sHh^Y\A`K@.J]q[%/,Bkp|tddtttt0:^ JsQ"IḀ%"a, G=ㆅw&:sb#CcVp8h%ɪs?MN 8o+{ x{/z $K!ϚΏ*c_3\ o bK8kNwgirM^*BlPf bBUY^`꽴`@a #> _$}N(ʑfUu<- y"( D"H`#84R.4cQa g 7ȍ/R(T']܌=yEZ|tdlbXMű7q<]Q^w%(NÇ'[D~䒶]kp[U+zEkmmkF&[mCOrrJy@|) \.2fϡ@htwJi!uzA<.%<)9lP*-6?XYY2'^-(7t-MZ9֓) c&[\RT86<<ƍF\y,O>?&A ^LQ6l_5N&Bsą {ZhN g"rK\wBb^ ^} ??ѺWuZ*v0M`p.⹳rb3>˩$Y.| ;N2K@w$Ҵ*c`2_"P$1ak>qY}"Zb?Z'M ^b_5H2unsYD}]9)B?1kzdr:y,vG2[6T"LgVeTt,^Z[\"nXOR-|P?Bur*2b×4>=?O8+lw){O]?מ={k|~Ѝ`qyA0%ԉ[[`8܅Gxpneq >`,YcI.w8\K ng>,Znxvډh*=c5W屌&|>\v\se-9˝ \N"Ka4B3#fqmefqennzI*Tu-^83y9A|~X.;///?/Дl1-Ԍ<e40ic#8l:|=s=\Wp7<12!EQvwn##4RЍ O|4lMvUj_is\+\ś~\`0n[Iu~<@.{t2mԴ4k`@@@}{GE=(mQ IDATҦ^NC,nPG.*,^ЕYƃ]7uWlw\>W6צX6}sJo [2x{(i3ouʕ:+66*pEBC_fU&0|XEGO_/;%"dhhhhhhhhhGL}뽣o",y`9zR]￴l9uEj9I Gk9W`+Ç wtw^5U[g2_N&^T:rZu->ÎYƍn<qZwqztx?n>umݶbG7 o3߻5qCBB~y  ó޷z#GիW_:2 eϼ5k>鑾%G𷆬|XW$SoXxO_~SS?/\xiΌVQ܉Oܸi_y׻ؘ.K(K%[lS"))Z Z<۬@El;7$)CIQae~YwI~Qr/ÉD L2"phhhhhhhh&bݽ}zhy4196>a: l†Ш&sMyp˿[+L ɞܿ;`g_}N Ow׾{_L޿g{ȩ֤n`X~y$?Fsu5?u&]~SKLXGOzZ,3gdwPֱc{_B2N~/Bԓ3=/=?@Wm} {#EŲѪy'C&Gd^݊а&5AZY)aaapVʆɅ{SfB]AJ+Dʫ*v2.0糫Wbz26 ͷuooo;oŶŗm2emq׬Yf%aݷ$;/[eu5 Syf~`i<.#x;P[ %?r740 |XsgScSư?dL{/ ᥟ{{?m hPL 4U71M&/joKKx=(.گ٨\@41q[3x ʶ7'Jv;{JuLڷ[`=N--+rw𳷸wӱvRn U;7!dq\?md0nt"S,%z% F!Ci2U4ERtRꗊI!E;Hg{Sl˦JJ[mf0_;2[?H/M&O9}G9unF:Xd2FhX,L&dz{{X,BQؘʛnэu,'R)ӷo}ZexۀƏ#_ŜE%ƿlPek__G[tIO0y||ۛիWp f`y3{~[WOm[p((ڄޱ? I#E>wxUqOz~?_>nO^8أkpٳMk"Tœ C-o|ֵg5kOd_x 곱=γ {Fumފ-kgu#lF{kRSB?ͫez[8w|6gLyO8&CI4}Ip.=1Vo 8Yw_e~3ѵT0obO 7s]LCCCCCC@oo/bXX,(}f2֭p8|Abb8m Rf0EL&뎪ֱ|///oi"k755eX8́`MNNZٌ`YDE1L311aIOyx{yy]xb$i4X 4L7ĝ4̘Hl4>>>/iHrbff榛n2$sc}Ϟ={@7QTSU؞ݽmUaj>I)ďm V tfΦ<$v hPuT3V1r̎ 9 9H.i۷eFP\͏OksL9  70L7&l HݩQ@i q٘![gwp[zB UbtMl6Ąh$IrժUс$I.uTG7n=;33C-L&$IbP3e055e/϶fh6e`bbnB`f03A7x=s b1 ۭ-8$\P_.Q֐F5 pnC1`_Mh<pW0.,X9Jq[P㉕Jݺej.O^$׻%ӛ on@A|42>sd t >@|-4>t* w` 0?Gg_{+%(Kˋ`q7nxFgΜe2QQQ6m w\0hK\<++333 ~,dff~ שo\ Y_#4ލ|y@_:"0PRROƌ\ 3 hP) z*RlT̾Q|z DhגNYR%( f0购1p}Wo+醤,@_~_٫]Q TH5`Q}6poG/o'e333cXX,_~~͛7?C۶mۼyڵk}|.smky_J`شn ]۹g%f;O< 7 [}cvWfcV6lĩY|p~L,m4u25A#.lgblP*-6?XYY2'_+B.$ =yEZ|tdlbXMx6zVUfFĊ^њ; ˍ O=TbOJ^4; Uºu7Zʺ}EH7\QEx' 60s:%Ąlga0|yZOv{JpQJ`}\\LCCCCCCC{7|eH:55e0luG133C䒳Q{O]?^dzQAouܷK8mA[,.onB+s5٩8c0yqwW\24444444447eKoooJe?,ihhhhhhhhN>-J+b]aNkF,Gӟl6/YN7׷*4444@hZ (k+{^ ipcJkO8ԩS}}}t\{npYɳjoeObSǤ}MOi~1%5'$aJQ;TvP:e~Y=WR*I)U7G#z3[R54YKk8%$4-.oH DkITJR*ڵ?/"{ْ?pTWmf{֍ydQtʢ 5*5蔵):sRۙoSeJRKiekiDݪq,(;E2[bMI +uoAk%$ɯJY"P/<, /:*V -.uxP'Us)EWp"C$:|beR^;)ζP24[h{@v:Hn,nڿ/^/Ru{Sf%ݮۍ=1f`0>|ÇI]v7^;w`fHkjm{&?i-?e֊l:V /}A{R.k)kܺ/ O-(iikOtzdRnE Q}.. }!d&{#eM}=uUG _ɶ4\VvE^ZeÓs'mW e,456V@E'f)T:/+7BU,Lm;9Vf꼫0hɤ,MY_}o(G.?:nuʀʜ:_VU}3%ɤPڎ2\6[9z+sbCU$l ׋uT{я9Um=}mM 7: ѷ/R.+8r J@<iaLeWlR*BG$#ti{'B2+7V!71v⬺:yY}, 0mݦKexꤔ?:W38VǙA[K"9 Ͷ73eٶcu5k11dG'U',[R9^48؝ib6ۏOLLtx/_ԙ.gw]:ayN~5kk•7{7tq7Żb[>[@?dukW J L@%sA9̯sΜgf`4}?hy_׹\u}SzWja߈/c^PX2ȫք]lm:LL\S7/O$Bf6oxjce;-X>0 V}Y/v 0%镮A Gv }㩥1sK-:w5{.\(˲bQwjh44>\|wsIP6ymQ@(__ Tf`i Lh)_|%+}7auO2\]I0%&qni3O\^afRu&o[]y]irw?n,^n Aź5gz!Q=WzNēh-yS5+e׸|]ڊM o`6UxW)e_,>7_Uo|w}JNy{OimO,qw%M|dlsA⧗bYV{KZfmZ^oBZA*ZsZ~̺Z$ǯ kרv׊rwM^W7!e i9d`VƯ1y"yj%uk$\-]0kzl}Z24MH+|Z9@;G3h9uo+Bz8@5tfOo)lu)꼘K:;'ѷClmF&`\s{$uƷL١7bOw|󱨼~f\;;{4Zowعi <0ܡqF0̒gwD% wyji=&b;P6@|?Y)cQM%Hd"j}W^>Og!:-$?h-L7\5Yh?~/R,xI>*Il=e ]-6{o薡n3 -}'"4g֕Tim~G_o-LG9z C0qŹvT{+6P9nj:E@UPM~P\US=:@pts(l8ְ#2wGSfWRY Su[W:aG*ԥsgɸ~5uw}!jG]ͅƬ;1~Ԭ n;UQj2v:E|=eDYӆ),CWTQ;DOT^i1t>fR1_}CU5 +7.l!޸me҂u |XFZ%|%/~yfl3 ޟ5 ^Wu IDAT;%dt[W2#w IV/ujHM`7g&/IlmM2aӦm:}s [XؚXypyd:/WVKKmuuW[[y^ϛ0qŹV"dl-YH+R-u=.)5;t>DON=$pݏy_݂WV0̪Dzh&mKW}E73. @]Bѐ_9־XzQ|ߩQ11t )Zn gr1G3M%)%7mX46Usl9_E +z~}i`yyQL,3G!2iw72hz_S ]@pa1ͯJo aUsp$LYLw{nW:4]5olBލ@Aó : (>ohuQR7#ln=Z-)1s0e-p7~9JE5PE񗧖Ox]~UbK- xOArkΪ/r8R_(~'[VU69Q aWjJr~1/旁SxLYW{ >Wz/H/h22S._0MDžSC̓J-]4?1]371k׎g شdE~A86 jkIy#{w)L4!vn3@e0C ~HNH95iUt}YS :^DwFCT&V l~Gw .o>e)[ˣsu]ῌV>/GZauV-ϝm͛MB=[׬77֯!SwמXѐ_@XD ` DEMK)zNg'.PCViOa/eV]hIٮҡn1Hj8NX`V~TX;Wǧlx㱙񷿮7 U.>F'el;}`&֜}}O',K¸yR}ÉY9O,BizCW{p҂XVV)㮣£?UMh}9^10qNT~X>27O~Iuy@VSĕZQSIqDt# :َ=PꓐVذkXL]&UHbOW1&UU JWO{t'y'mOz@e{isu>yȭ[snL8J7g3oDZ Z=>bPtIx_XBf"+#31k.ƿaڨ²k=B@j٦:fvcժ6r][fEՎns^f?Дeꤩ/ؿє)\X0fԸ4ʀ^i-Kd{.&ם{|nÃ밈[x tylPw1bhY8 s˪@VY&aI8˘^Vۑ0<8/ ezdZejts`EGE7 q7Tj߁M퓇EsټzKjCW,T5Bx)~ٖ%klyvg# C;oߓ3}WU ^+z婀}8ݎ2~=Cn]r|+B U'燐.w .o<]1[!ƺS*?Z\hx)'ja+2ز.ggCbs{ozeU uo[)JӮuS` 6Sϩݹxݾ!(]ʣ빚ʍ-QJg8N7w`ۼ$@‘xHߖjݫtIqKS̛3dW*E+VJG7Rz60}陃TWe@/{{4x3__Č..\X4[sN)@ƩPÓl_:(}y'}”(6!-w OMY~\TOGO[צ5Su{*w}]>o&Mݪ]7],j# a¸ye[< $nqUU~GR#}ACpn0W9:&2!vʿ~"dYdLUuiLi`,._ x,k7[&?lJ?Ե{/3`َ~?PNguuLyx~G!wo!7{67Wy #Vҁ SViThIE_!2 ژ}ۣyCKfk^?"]ގs/n^lKMRc{b.S{'xOC4v"JW*sS2R :R )z]yd C۸y^9(Jӆ1"g?V5&]]]'OO?{СCuuu MMM}W_}=x}_kEktEֶzOmI+3@O;axI[0ͫvT ;U @.H3Ths{K/: }Z>AVX>im6 2U@ ].Л`VTrȔ@~Uޘ&kK<4 kioxԼ3}Z377)@9*x]Do*.)ov5>[V;ɞ rvg+֛ԕP]RRE]wSЅwgLfA0]&q̨֕^d%XDId͌"|K8c϶m^]r4zLJIa:,^yO smɺJ}Nկ3O< D{{{POC{W+37i1KeߋF շN;]lC:bF>f R,X :c!)ZSU ̍ bYp}SȀn|6}}-`OEa)5 ڿDz[G\[M]"sK'V;7mAǖ/]|K.=Jt @Mc6 s7ނ0<$E&IB{KUwĄ@̼ρi:l<>˘^2?|Ck < }Ox5-N1-V'/ro EK֕X[oH՛gMjQbz<]c{jM6H]mDx'7iv($& 0$Ihz)Sg'*%_dG R~ &#  9?ρd/5Hs=^wLJVSK`̦ML~,-kxDGGO0a̙?Τy%&&q 7t 7@v+cXΝ;i6Ϟ=_9s̙3_}UGGӧO:u///>'N?~>;vXKKKKKKss~'=zd25559rÇ?Ɔ?>x+++W}OWzq( >REOd{EQzrܫC_نb> 7>AQEi}= 2SEQ i@a'|dE_fr7T Tz b(ߣCNYy nVSO_k]EoQ5]<eB֒;%; }ywFQQ}͆_Y2~Kkk[M_۬CՠFKsMi}y6(r@}D ;rׇo50e.ޑ-!lݯtHsUW` kueiPPUSo3-ԓXEֆtK7 ;rp7Oқx ;2k(Vksqs lYOJ/8?j{@5sGT_]ܟ l@)` ~.o65iO˭r>u >k)Pp2Ӷe;rWbCvO^n>nu]ߕ.u9 -ƷMfСr纂iӎojFa:/u\|GB {TkxTi.ˀw;kE#VӯZ}jҐ:D Ǐ:)nꫯΟ?oZeYv\(^pAE*]]]jU]UUʩSTMZU٪x*qU ]ǏW^ݫJ_UX VijjR$V裏TmcU!"YebVx_t̙T|k$A\N{xKbфTumATb 8`C:KU!Q:-$ \ilF9H6MN78Tۨ(?륃Nf5Ż2|&Bb}[V =ùj~]jQ} g/V{?K@+ Ҙ@sک"nBqehPشk~c n:,4[^!=G~j[\7KG {7,fMh#bs~|0ȴ-9VbkBt6l Kl増|&B3$9hFMLl%t{ը4\vd5!˥! svؘa^oqUKG'SxrkՋMl"cc/~:lnj󼺏ڃD簾2g{9̙3ךj^ӱgݸs^xlgL;"Bc|xo>>q֎?N]2M?Hbx޻ IvDAH$sueSLиo{MwGNQڲqW$l5: I6jpWRm8Y\ ;p*Gj"//XRAAwjڹs(eYeSSS/_β,gΜV!  F<^=2fozxx… J@  .PbԿCBB>?>V`HAA.ryyO6 ɓTO=  10/,]8' v  0qČI&1 r|w1!@  :(r7+:x6 NAA|JsI|%owBAAA|uU[VYeYfY^Z%THAA,\EQVngYTeՉvYvaG  YΟ?o%Ib&88XgۘX&*H + :,0uc(F0EQIFt)A\.'{'{'{cRhph&"*lz"({A dgYVj1c ~<{o(L{ 8\QQ%I/R XPEQz{{{{{^9kEM b9 `XYp,Xq.bYaX'{'{'{g8(.H.tY:΅ kn<4@#EQ|&չv5]Նl6+;wvvv.\סoΝ;X꣘h4Gs4<-, C!<ǰ,X.crYc1x \$1.@ +^tAVX ɞɞɞ Xwɐ%[v)CχMPU;E|QGIj\(N322RNO:v3@v),ˆw} ǩ7j. \dĩv56rM<̝4QC4jCL .0 XA<h8SH.2$ y {'{'{'{? %H"A`\ fG;Fj^jPw"\E6m4u; ~_i޼y o: %55u…Z1}6eTdv56F]0̬Y>çNirF^ Yρ1X V p/JH25`'{'{'{c( YȃD1ϴĝ=uRTT|Q57*p8o&_a|588x`ڨ!^}yoK`vz2vX _YO'c&Ό3J!j@, 0yH"! \.8D8eĒ=ٓ=ٓ=ٓ{ K-`IqqqBޝ].((ZVf̘j: 5j=oRje(7`:ue}PCh  .;ǁ}B|Xv(%Qg~nHW ($Iv]Eゃ'LpoYNsAAĵfx8NƶZ= VkjZ^zTSܳ陟LpYmVDKZmV .ɮ&|CQn/1vOO/JVkjGi5۬Vfd#`\0 q*?t  f<>Sj l6[wwGRAA50AAAw+}7%xg6ofڗvXs7׎pNs7->Ŏo>w+a;fA7B[O ?}.-ߪ z$Pc0$I6-((hEAAΝ!::ګǨ$[N5;-\<˂xYݟے/Lyp/\ے_s~S LSx뤙!rnܮfLWWץ AAA$$I:q[o\ZVVF;R0{$FDؒ[ExDAt'B]Ct&Ze_{SN~Gj'  E9:;;萐5j?RGwwwyyѣG]7o-=AAA\ Äϙ3G7xn{aW_EqΜ9ᾒ_JAA\cpwu Q?>|XE?0ፍ9rHnnn7 _7/3<!  =Eq:&)88nKLL8q= ɓjllt8z~ܹ ?<2aIENDB`workbench-1.1.1/src/Resources/HelpFiles/Information_Window/Properties/000077500000000000000000000000001255417355300260725ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Information_Window/Properties/Information_Properties.png000066400000000000000000000577311255417355300333160ustar00rootroot00000000000000PNG  IHDR[" ciCCPICC ProfileHPY_Das@ 20q`Beqր (5 X wEA9&Tnz_ wrDXTa8ۍ=<k7M wwшnqV4.Pq4n*g\8aЗ.B0M,1Op|HgdrH̼B@9DaƩfa?SϸeVxA(_nVjJrH)3FQoE)A ~0,dq ^' D>i }8A,oφF,-cOxY^+y@%0HA "=~M˻et l!Ԙaina?f;{ :]Y @ <"f3<@ЀPZ@ {i + (pA"HbցM `/(ap @38 .: #A8Q!%Hҁ K 9C? EAB:h TCeP%T BP?tƠ'a f0f~p(ë8Uq _o<(@(wT *6 P%*T= ՍQX4@>04].CC7;7C W 18`ؘH ǔ`1.mcvXl6 [=mcI39q\:.ww7}xK>/oƗkg) A@$ل#6 a(K#:CIMRb=D"iIKIR.tt4DH#1 vQr; BѥR)%c)['Q\Ij@4AZG%B:GD q GfLL̠̤,UB6P6UHVNNWS''wX0EբS-#. KӣiIB Z/mB^NZ>\~|9y Eץ)S;O ,xm  )*+(6(VPTJVڥԬHlT9Kr""EEN-U9ң2*RݯzIu\檖Gژ:UY]GsSza54iZ Z{:&յhӮӾCa$yUYwTOQWPZJրilp61L4,7a c퍅Uƃ&dIIɐ)tiK3mh]ff_mS̏?lfВkYnyˊbeѪꕵuA6T%6[m:lيmmb*4fycfG[tS88&;:.[a'M'SęEÅRU˕ZeJbgt3w5wwp_( ,|^3Ves5 _;~d2'b%%< 4@vGAzA~Y]|`u!Ԑ!!BBw> Q!4\y=J9J\lﲑ;Y~uVJ< Tq&qq\w> +oo,)8YSBq(߉?X8.p ^%$Jz|4y:%"!*& ;WZ_d$IV;޻zB'NҖӐ`ӓ]Psfy懬kddfo~ZZڎu6ZZ_cƼ#޹67%ouoDliS||qVǭG/wն۾ ~.]'laNN;\v+-)޽dwƞ=o{ĺ>⾌}RҖw\XvܭBb[]R=TxӏVzW6UVrXHV.n|槿+(B3y pHYs   IDATxg\W)wa(((,FH%&*`"'&hb&Q b^{`GAMy^8& j43ל2/2 #d2@  HJ%ajaW7 ԴW+W0E!H ë+a@@RŲl,R*JJ;Zt7͠!ttu0B$$aX#ǣV333+*Q"899r$qrcwuT5˲"uǞMHP]psA8|uDf̆fUAqAQUn{|KCnnP(|id}:ŋ76]ml]4p)  g_:^͵T*@YYyaQQQaann.ZDu~-כ)l}]\4^yx*/x_YיɓVVV:uj͛-"IR*ƾ!Ξ=kii:u\\\Zn"k-U"#Tm^(t'HkV Sw0,3^ Pmub{ڗWUUjR"1 ÕJP$KLLL|ȫʋ]ޢl%qL5]YrSGi4T̶c,E,n·lذͭ,˪TBalmmÇw9ݰa^wrr}vnnEܙm/D< C7ːu朒 E+ݽVaď*BQ^uŻԾUxPb)ڗvNVRH(qzf)-*IRJ$wUEfڂ8x47n -,,|9YצM^~hqׯ_dd@ 裏ܙm/D< SoHw mH”8UjA :! 0#7``1 Cf8β,I ˋn֖6JH I`x1 TNidRVTPʈsnٲ%55g֬Ybxܹ٫VڸqcLL;v]VVѱcGHIIIII 1{섄[n?_O<ٱc?vyĉ,gg?cǎ6mz7Ξ=ݱcQF۷WXC33QF5 ðr^E6N۶mO<0⒝}-[HJ7lذ5k֨TS^^jj꯿1gR3sd2wwJTryuҤRU^޿ gARZf-H2Ldj*T i"*"Rf0H"1 p#@X,,,j5F6 j .1 P(Вt!I0LVYYbb>z-[hl(eCϟo85ԩ{׮];\RPVJeEE2htt:cZZ0*DžB!HčW:/ |wǏwQGtXRTȘbJS05LM1 0 A,DB K nnu 4R4n)6B:/ocrɒ"B$%RDH02@Ex׀R\'U*==}РAlFF/w 4C!\cfbNNw}|r__߽{u&ͥneeuqR[YY=3ƴjQi4.3KǙ[[[3 ckkKիi&4P@ o7nϬpt=<;cv0`f*1LLM̌5Ufbfo'rSSSNW]]PzxuuN355rGL$.̪)L@B0YU!I:ח ^05k<2 j.碭5+**[n={VV#{{4.*ξwjرcE=x௿ l8ƠJX]]^~KW\\rvmt5ow֭iDu^NիWsrr h䩏zYVѯoѵT*JX ) Mz:, ÈrP(QbY dUj]5E ^D,N1@3,EE+ˋ U_aAx?6Gf\ȑ#f̘P(Yh~[AN;i$B'\t?g<ܬ,__W&%%%5p=n1g7f D"y- Ī1U3@}y^ZVվ\4 - -)~d)Gy xxx{)Gy xxx{)bYŪb +w# qŸD3=k eti5S^Mk @XJ 3) Gy]1ЬZ˔ .un"ĔRRA2a,\7)uEGEj:*0W Z5|x+fjӪ'N&f{&jLfx^K 4[VMVPE v4SUe.%/ f K-faR!Ծx^Kt[ZUe]=d!!$ߜ鳫1 Nd5mX`V!JL{]i*%MRʾQ:nˉPkjTtV6h 0 P# /b* aH0@TEV@ehJm̲ҋ8Wj-!5K6_lkkۭ[*nf333GGGHTUwݵkM...#GϬ1]6>>ܹs-bΝ'Ofddk׮{-4K@GZ50c03c0 920lƻr>O8M,ˢgËFxQ d hea8+h 4(dZ5:i6yO?СCϟ=zիWk_e˖#GbFQԊ+Os玛I[nՙi10`i4Ma)PL&J%#yr JPxm{ٳzu]vYQi_g10:Qf4=(v?iVG=,˰z10l Ϛ5[nPYY1jԨ˗/[XXFGG'Kqԩŋ'%%!Bk ˲hnn 0XofjjZ/bK4˲u@}6;\Gf1 p `޼|6e_>/pT7쓾ð4f1ě~aBl2a֭[WX,>qNBv=h No6l߾}رǎ ؼysff]]],X`;tW_q4Curr񉋋kF,_< 񱱱=zprrz뭷\RrJֱciӦq p۷o_YY u;8hz=MH$bhi Ӭj=Sc*4-377www{n/r„ 111Я_!C@\\\^… A߿?::{s1 ΄  77+Wvܹs 7jԨiӦm޼… 3f̐^}w^hhh6mlٲ^z\rԨQɓi޻w/|SLA999ǎ8rL&kժմixEh)">Z#kҿ FaI666ɔ" &1"$$nz!,))3gιs第-Z_;FjW0UERB## XP1eXhf ]cz1113>}?njj[o-[eY33KII C b͚5Zvҥ"5+Vҥ˸qzqغu+aJrȐ!\fMY-..633ڵk 0dR0ۍ ?>e)S<~6lXTTTvp?~<^0 KQN` A7) [i&$$L<944Կ^G>;;TU*գGPxDD3Ǐ_י3+Lɫs˩r*ܐSnx\r*ܐWAWt)d4,NͼͻYYYu;88۷ov ~#G7l`gg, @0TH9z5x葇75ѣڶm:O=z  ww(fΦk׮hMP @x:|a,l\]v/m˖-oԩO'2g0 k׮EEEpX X c g{YXcЀc$7ҥK <<رcЧO;;7?ى޽{UV`kk{}n oo}ij5QTiiia{Ո yΝ;pr%K8^cxׯ\2zh1bDEEY{che}̙3|ҤItͽ{:αcٲeKuuupp۷LLLDj ^^^S/8_Ё1Ymy ڴi8p,KKK9c5k֮]vܹsΞ={.b!!b!B!`?|8ú0{ '6PH <fff..._1cj4=gJաCV;nܸ)SSŋˆ&vuZjx?~xڴihxeժUKJJƍ@$vvvrssk۶V ""}133i:66FWիWOͻɽ 4M6#MQ5窩I4hyfΝ)( m6¿P빩҆fusHkNՌm>_644Q)..vuu=wN>hN8qQ[[[oo:}OHHٳ3WÇ_^XXئMvĤ~i4m0̌uѩԆX30L3< ԆSaVշnjƅ ۷juFkLXXؒ%KV^j**$$$::Zպׯ_jɷX6T)BKJCKQx}.\0W%%%rmhH%͸/PU.\زZ^ߧOÇw޽LIIyMi…Hů:6˲EnыІƅj+i5u`jC7CD={vqqqpo/v흐h"##{ݱcLjc5jDH>AsiرPVVs`[l\\\ppKHHH"eٔӧ/\:˗@xxx\\ܢE [Ü=˲?777oo? |5GYYƍgΜiߥ>kcT}~ BVmheMMM\׆.fM4@PO޽{Qw]zO>F*((/ .?ZԩSttUrry޽{׬Y3|pevyGW!DLd^^ޘ1cfϞ}5 (*##믿\l}:7ӻw5k TQQ999111"?cƌ9z͛{O?qq&$$ܹ/22nfddd0 S_h{ݳgONN'Áw ,Yd˖-uި>} :R5ꪪjVԠFq jk4ah2/{VѣӧCCC۷w2eJeę:YYY('Nw}quFxUUQt.2R0i?l0:F4t.KT.Хlz <fP~z__QFiڻw$P(pD8 VZեKT:vXkkk333: PޔJ%zZ&%%Y[[;;;q ,XD={li ۸qѣGwE9r$0hDf֭;vDÇvppU(+Wtvv8p۷njީST*gJZQנakDJ7' " ) MӴqFyAUUAoj5<={￿iӦݻw9w޹sÇoߦ7賳"##Ϝ>_64!nQ ^6tT-,,CSSSNgϞFsSQQ1s̙3gx`PPP7lИСCϟ?߹s琐*D;$$$5k&LYoT*9qQ8*sssKKK4LIkcHژ9oKhZZ^ h>>~ii0۽{ev w?}Kӆ@6B LVק TDRTmf$ T=))ٳr<44I\vvvA޽?>))) `K,i8RO?yzz^paaaa JJJ*((hXJJJ9uvڙ'%%P#ktP򵭭={g*qjx'ʨ> jcF\8 䴛- +DZ/E2gp c1e9K0c/`aX$F^C|*5prr/**lAAH$rʃc  ѣGWWW/_ GMn''SNUTT4{={`0SL(tڵSN}Yhhh8ŋ4Moܸܼě)˄+Vaa:+qgffr~ꅂjڋӆ<q\Z HJJڵk׮]mҴ.^CPP_ԶA*hlcƌEDD 8O>~mm#00o}vĉ۷;wnXXؖ-[mL â.\اOOLLLLLtuu3x`__8D$###44ٹGk׮]bW´ZqXXXuuʕr~3'NtҥK:88sD:==sΝ;w~qmk=w\C֧7̀$I ZpVks^v<\Z A=ZѬZ`0$Y\\l*XKDHHD$|{T[YT!mh !$E$."q4we<*Ɣh̬NV[]]mnnΙrTZ_6T* X2 %Jk,I<g٘JH$ llljզLU:t3"`YVRݼyM.7 mi@<%59mh.=zlذ̙3nnn%%%gΜA+b{9mڴYԩS~)IR$* /^R_Ǜ7u=ˆnݺ͛7w}yyA:tP(/_ᅅ B*4mhUjߑ7#o:;G3V W ݘm6/iC^Z^Z^ {9s^u.0 Jiii۷ 64҆ DU4 =k@ i:33 stmha*+Pݳix^cahlnJ'|½`,DUX mL3GyMm5͝;wHQA7 haFk02`۷o- xSo4Vz333@Ѵ`HNNxb@QESU[cN^G@{7ްwxZ ƅ(J*RESMQyHPcy`D"AϤs E8qѣ) ;wcBBBk(VUUر9::pGN4rN?˲B Γef1 (jӧOh}KܘJJJFy֭k׮=Gh)JKK?!CݹsGPšKݻ=˲wqpp@r <*n߾}:1hРviFQƍSSS}}}{@OA-=m4S44=i )))7oެ;o`֬YH2""bԨQ/_FH}9|p} | hϝ;ׯ_?''c"裏|||P߬ FGGo߾255LJ yDFFo}JΝ;k=zĉv4iҾ}VZD._SD"Y`ejN5(кu먨ӧszfN񁁁Eedd̚5kĉvڱcG:i,dɒJ?ɓ'ݻwϞ=999SLim077www{. p@0qK.͞={hе… 񱱱'N3gNlleː&r^^ޘ1cfϞ}5 d$6m9rD&jjڴiWKvvvAAAϞ=kJHH4hZgaaѫWDHLL6lءC׭[ޯ_?n`ii)>|ŸqzԩS 2ˈ a# ˲,j  Z۸qѣGwE9r$0[zxxt/^800s!!!|򉓓Sn݌=%K ?qD:~)S\\\\\\L5IYOOD>}TUUq b͚5Zvҥ"$kkkggg:q 1{R4A 6,**]v8[[[?/;wN6?ܺu{mL;;;jK9sfŊ SN㏿M6>ݻ7uG&&&v…  /O# il d,xЄr=L;$$$=z͚5QQQ%*aw\5c\]]sss =zh\v dVVСCkfgg7ovpp\B*mtSQQ1sLΝ(---));}j-uJ$?kbbuVH͛X,u|ptt;v3͛/x@MD T*'OO . 80,,lРAIII?w KKK$*jP4IҥK>a„]^xM6Ύ ݻw׾[i0iҤI&jkk Z[6*--mݺ5:6111~HXY#![Y[[_B)u)ƴx޽{gϞEˊN:Edk׮:uBCCRoѾ}{OODZ]^^y)..NNN8qqzUlaa^߳g72GFkB P$ vvvׯ_o6Νkd<Ϗ\.2dHp׭[7n|Yka""...22222rƌ˗/ pðY uvvѣڵkWXU԰0V;a„#//k׮^^^Y8zj4ܹsǏ=dii111m%K]v-88xƌOLLLLLtuuD â.\اOljsÇ4ݘtyM^^=p{U/^'O|…7nC ^UF  $8NQ˲cǎ% &4wʒJ5ƿ,[ZZjnn*++JEDˮ,j4Md2eKJJj'0rܸjy9ܼyG,@hh#G\\\ P( ((ǷoFر#<<}>LJJ۷/XG͇k׮yyyT*4,Jm6lذ"kk[nu \\\'O &L`Yv/ZL???P<}`pi`0;wɩ1}znݺ͛7y&WGBwweδZmFFk'iݻ׫W/RKQʕ+L Ô??׽G<,666uM^$R***G ~) GPTt|k4biZZR|Da\a^1cǎNںuk[nmDtnn˗A߾}"x^{`ڴi6m1cƌ3E$Ep> Ir\.#{eΟ?/8hڕfܮ- 9r3gפJ wssرi85wA裏|||XXXjx۷իי3gD###=<<ڶmۿxܹ~999;qqq?СC\K.=uTbby ŲeYn]Sb>B|}}ϟ?z4}c;v, c͙Çwuu]`qСW_}m:'..Ij;wꫯt@9A₃]\\BBB-nݢE [ |˲K.ҥ+'Z㇮ӦɓϳK^IDATgn|qjӦM'8kp'NڵkҤI[j՝;w'W ҆aX&T kC#ziggzC N<9??޽{ə2e ogbb\_+#--mJ200ڵk'O;w. ϝ;tZ ƌ3{k׮UڜK%%%Y[[gɓ{9`nn~ᡡGTT:::nذ۷Otԩ… 񱱱'N3gNlle˲=j\r޼yk׮=}4*Qw ,Yd˖-<G(yy9\V/^| kk+Vp.&&F$bx̘1Gݼys޽'daÆ͛7ٳg߾}봡(*##믿\lСCT0h ¢W^8lذCyyyۯ[.==_~666R4X3|p qSZYY 6- ҆C eX>@6G޶ml۶mȑnTVVǧL2eʔǏIe *wSN!x w>u%KIcǎMHHsΈ#Je{.hƓжm<?~|RRR@@͛Qgs݀M P(?6mڬ\r…5lZnmJKK[nMLLݴ QPsp7>~% 6'˲81/׀@Hn, juyyyBBrϟ(׷m۶HO(]~ϟ/ɩo߾ jTWWj''޽{Y&55222=z4Zʼnlr>c'N8nܸ^z8{q{l.?rʦM.C#)((@~+WF #Fؾ};˲w=voݘyO?nݺ%KhQX''SNUTT4>Ǐ߱c"]Mm233t}v%'')//[nq_oܸ)*""...22222rƌ˗/ pð2;v>nܸ ^:==sΝ;w~q\\rWݻwD+0 Zp!7ET'ONNFr{ݺuCX338;;5`0'&&&&&zzz>|ixYZZ_uLLLm]vvˋ npɩgϞ R44yd4jժszxx :4<22r]tرʕ+]MmbccF[n:͛ϟGNXj71y .ܸq:TPPЂ^>@A.cǒFFhwU*R5TPcV677oR{i<眖 R x!R`nH-*6n.L{gt,/ne.ےI7w85!cNanXJssxzOHr繜<< 娙a?4x<_-"HeeJmذa۶m/w%$IFի!i)Œ=tw %˲),,Lc=ܤG}~>ovAA[n}7W{1/dɒ?!tюZ}衇|>'Nؼy_rB˗/w؁jkk#?~<]B"ȅ jjjBSSSZ;O>eY$ٳi]l.{;%&B48ƯD"n^{+$&^j{_|*E駟/_{쳸Gw}6jttt:vsqZjwV+4|>"vh -t:]vvPvvvNNF& !$gZ DbŊ3gtwwbFɾ>Fа[-B"{CǶ%˲ahh… h ZUUUe]F'&&h9]ݹh4FՇL:Hc9tG@U DA" q8H@$ DA" q8H@$ @zb1500v ]>tҾ]}Ya5YYY嵵6 PfB?3ظrJpKW]Y[[{A"pSdY>yd__߮]***xktU EQwyGټyB7bD u,过l6۲eµ<_k烡`( Ê,cY8.//p8{ATh4YeeY!zFazUn3x0#`񴴴KΝ۽{djii8?Caa<}pHO4oi^d B3]A#1t,&!qd2 (&khhgff!d6}!DWyw8={lܸqǏgFb?R^^N_]Vea{{{vJYe9V-'qi4јN$uRA{$IR:z{Zҹײ,;>>^SSA\̙3,wvvٳرcjjjBBK.IBhaNG( bJ T,AUN<K.]j6׬Y38TEEE_|***A㏯_s\uuuNUä :0L8s\VXՊXGP j!q^߷o_WWѣGBNqԩQzX>z*| w,zޱ1NuVeiKa4|>6 m,$bp8j"##B\H;vt{[%%% $nL8Aг@X-((hll$_y_~7ߌD"j+cdd AAFΝ_322~իW[ֹvw(W\9{l__(VJSq:mmmzA"pbrŋfff;].k4+V>jo˗k4AA<˲|C^`0z^OaxxԩS۷oq+@"NY8h(k%'ƁꫯhMP 0t*ѝ.ȍ$Ł#k~sҥyv#H+no7N'D17G0H7uP̓IENDB`workbench-1.1.1/src/Resources/HelpFiles/Information_Window/Properties/Properties.html000066400000000000000000000040271255417355300311170ustar00rootroot00000000000000 Properties Properties
Properties, within the Information Window, contains display settings for the Identification symbols.

  • ID Symbol Color: sets color of ID symbols that appear on the surface upon location identification (left click on surface or volume in the Viewing Area).
  • ID Contralateral Symbol Color: sets the color of contralateral ID location symbols displayed when Contra ID is active.
  • Symbol Diameter: sets the size of the ID symbols that appear on the surface, except the most recently identified location.
  • Most Recent ID Symbol Diameter: sets the size of the most recently identified location. The default is a setting to a size slightly larger than the Symbol Diameter to distinguish the current location from previously identified locations.    



workbench-1.1.1/src/Resources/HelpFiles/Information_Window/Select_Brainordinate/000077500000000000000000000000001255417355300300165ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Information_Window/Select_Brainordinate/CiftiFileRow.png000066400000000000000000000673421255417355300330660ustar00rootroot00000000000000PNG  IHDRZOL ciCCPICC ProfileHPY_Das@ 20q`Beqր (5 X wEA9&Tnz_ wrDXTa8ۍ=<k7M wwшnqV4.Pq4n*g\8aЗ.B0M,1Op|HgdrH̼B@9DaƩfa?SϸeVxA(_nVjJrH)3FQoE)A ~0,dq ^' D>i }8A,oφF,-cOxY^+y@%0HA "=~M˻et l!Ԙaina?f;{ :]Y @ <"f3<@ЀPZ@ {i + (pA"HbցM `/(ap @38 .: #A8Q!%Hҁ K 9C? EAB:h TCeP%T BP?tƠ'a f0f~p(ë8Uq _o<(@(wT *6 P%*T= ՍQX4@>04].CC7;7C W 18`ؘH ǔ`1.mcvXl6 [=mcI39q\:.ww7}xK>/oƗkg) A@$ل#6 a(K#:CIMRb=D"iIKIR.tt4DH#1 vQr; BѥR)%c)['Q\Ij@4AZG%B:GD q GfLL̠̤,UB6P6UHVNNWS''wX0EբS-#. KӣiIB Z/mB^NZ>\~|9y Eץ)S;O ,xm  )*+(6(VPTJVڥԬHlT9Kr""EEN-U9ң2*RݯzIu\檖Gژ:UY]GsSza54iZ Z{:&յhӮӾCa$yUYwTOQWPZJրilp61L4,7a c퍅Uƃ&dIIɐ)tiK3mh]ff_mS̏?lfВkYnyˊbeѪꕵuA6T%6[m:lيmmb*4fycfG[tS88&;:.[a'M'SęEÅRU˕ZeJbgt3w5wwp_( ,|^3Ves5 _;~d2'b%%< 4@vGAzA~Y]|`u!Ԑ!!BBw> Q!4\y=J9J\lﲑ;Y~uVJ< Tq&qq\w> +oo,)8YSBq(߉?X8.p ^%$Jz|4y:%"!*& ;WZ_d$IV;޻zB'NҖӐ`ӓ]Psfy懬kddfo~ZZڎu6ZZ_cƼ#޹67%ouoDliS||qVǭG/wն۾ ~.]'laNN;\v+-)޽dwƞ=o{ĺ>⾌}RҖw\XvܭBb[]R=TxӏVzW6UVrXHV.n|槿+(B3y pHYs   IDATxy|L3k&3}G"ĚRWO\K--n?Z-WmEQh RKBR֨DI&Lf;MЄ|?>>'ϼ>9ϼyT^^^]]m4Y\ B:: ED;w_~MSkA "eȟ0\.(++jJ$DBQTcIDDOleeeVZ ð,k2Axڶ!Q::FݻUUZ TT PXuҺJwwwr&L :UD""" B} ~Ʋl={UJ7Oo/`/-)6V#OP($I<ϳ,w]JrR*d2Fc``D"i֯fOKvpsL_\Z6XrDPZW)((pwwJ- @:46}hZ/\3I:lXwpYZN@ܷo_zVXXXPPj@EEeN+))(((G&5;_ϹMwߐaBmէoޙ?|Lk>>]voݻw2do2! f͚޽{[֣G2ٹ[yluU{j/ь} 8ݽfuiT+WWWt:wwwBA!f!(F+rNS +u.(Njȗ-nfWk8H0W\—Qؾ}{^:vXG0D"yAɟǏ С|7ы/[n߿7VXѿfڐy?́iAfۻwH1>>>J5ilM!'ݤ2O 7qA$RfI?W/++suu"O;8\E5p1,#x(U2 UBޝ*ǜ+ *gwkPEQTc5}Q@@Yl6׮]+))ia%믿k׮}6ӆ#G/UxJ:y^KdZ'OةS0F{T֝RCkJNSm@Z BGEw}!D!(3 \ZZ`u%MGOw/T&0 C 6Vh)8ItDܧWL+W֭[ziZի֭+**ҥ˒%KZ-\cǎՇ={vNvyk׮edd̜9_~-ԩӘ1c.]cǎq9rO?%qΝWcGZPcV\{۷o+QF38pĉUV5f'W_߿"00p֬YD^GRܱc3f *`8qUN8qĉ &lܸ"..nرp-\}]Dm6"%4S*U+N*'F#jg'Fj2IV(=2!\NB9`r&I~ZZR+Nj\-;+N*gZ$2ؿߥK,9rdZZĉ@ŋϙ3ĉr^ONN6 }]A4iRݟ{~bՂ |>>>ZRR҈#ϟ?OT߿w Zk׮ӧOԶm={rT:qģGΙ3gfYŢ隰SÇ'%%-^oׯ… I:SSS8o!D:qyyyǑnܸ_.]t֬Y}hAۈ{B]^ǣL&Q8K Z.G (lVd2!Ea%5PC:@q=mLT(J MIiZFKg8V9W97PA'h+// ޽ <7n܈8}/L*FFF.Zo߾7oy^ahfYVpbYacǎEEE~-sA>v؏?/:\:qDq\pp0EQzcǎfݻdjGw֭W_ݱcɓ'_z:SSSZ^^vZb 9;#JB~~~V5;;SN Fcr"AY6bFBJlFe4Hլ*jPM9vGAdgX_C[LrF*c9-Q22#V*AV Z-CӘOAt:]hh(yl6sA ?(Jvɱ}a߯aWG=ܼyz#GT*EWWׄxy#ZmUU9ٲe˽{TFo΢LjƐQAA#f3ȮMVKo#|+~auh<ENeAAZ h4HANNL #Tyǎ jH$fY:w@0͍M.)RP U:s{ӽBv4Moٲžb tO'e;vs#G,]RbNW_}uƌkӦcbŊkƁq=MqΝCIVVV߾}G߿_Uٛ#A4y (""?lX\\@Ej5rvF `.B)K5j5Li(2LVU4XL^S%FBI%HPRHD bs2E;dB^JJJN8a29r"6ׯ{naa7HE`؏gff;l~wIt. eY^A//: yw_ UUU|ִÇrrrX=~byg۷s粲> 8k(//۷o]^ǣѹ΃\Ε0JD2D_Ru܃GA啕RTRߵ&iTQQ!YmpՄ#|K-u6e<)N^uyJV;y^Vŭ^zڵÆ #z<+5k,_RTr|'><==}ԩoFn`8zh FrrrիײeYؕ'$$|C2d&5jTaa9s/ quGGG8qbΜ9*jɒ%Ǐ';<9mʔ)ׯ?t'|y<о}@ZMo{ tjr9ʪ}{ӳ( l6Fqrrb8#!$J-KcjRq]vRdїUWuFX,dhJ! \.wss+//W(2-KEE899D",--l6b~~~UUUdzt:ݬYǥV]]]@l0,Kv@R)J wٔJsvT*-++*++#u4i0juEEgQQmڴgYk׮j6h +xVZOŬtRsu5'k+L&29;HP(R Zf#?kMTW*/?Vq9 cxV,mXYDO:f!c ~`zmv ojɦqxGSSlsboW1JA*++M&s'JE~5d*)j:ƸyLFxZL[oR!,k4K uGz̚S׷w-rcSEtM+Z1c>ܜ_f{1GOu?1l6lmȟW]gzW y|?+HKVpDfEDDDH"""5@DD4p >I"""OƸ~u^g5$$wjZ ""V,<{xx@yy\ /DDDRbZZZ+z˗/8qb̙#FPTdT*#""’8qw}yf)y]v̙0}i3HKK+((cIjj*M...-҄ȟqQQQZZ=y7  l6[Vjl6jeYb*J__R[L1>qD'Nܳg9޽{I̙37mڴhѢ߸q#..>eؼyȑ#CBBƎ?*z~ܹ]ti۶>wܠA'LPZZZu77Ç'''%{?~|u }ȷz JKK'Oܾ}>}* ,>}:Y6l..q TWW$jZFQL&sqqQ$ڷ]y͛o8MڣhVTT#g^: o O`k֬UV]vsÆ ʴiΝ;/$&&@aa/^|mV&N7TVV@vvkFD]Gzh"e˖@||˕+W.]J,]4###!!znݺuҥ;0$ *)F4MaF&H-[Fk49~޼yAAApuu%#GҥL&"{>}zڴiR,LII ...:uTVbbbj5 2bZt]aRٙ(wwwgggbða {ꕙItzxxlݺ>z/_{=֍y<4hXX(< <ϓX#ǑFu=}9sرco߾t&f=zj{l? ݊~9ϯ?~͙0 `&...99yʔ){]rm0bڵkd^===o߾III =SPT*= 8 )j#""pUW''O2x]v_???tRxxMo:/~۷FbRCTbŋ3vΜ9sšC~'111""J#MQJl$ʭD"$iDBb[,Jn߾A~Ϝ90L 88ҥKVu[liAdV/00̙3d!00իW ++KqƙLkג *رc36n```QQV8p5kܿL&hN7s 6l޼Y-_9)";#Rw-=o!D㤤pJ%+++...88w_|ņ ڵkӧO?w\```jjycPddݯ_?M6mrqq2dHpp1cXONNNNN =ztc:I04n޽ccc###ɌM EϞ={գG~I3fL8qРAjz۶m۶m;p@sTDƃMʂ 9r޽{ .l۶F#qAA6m<쳞u $R3yh4j4G2Kד N$\R$rh+))!A-++#_ɟ&l3; d^ --ի;vر#dff8q͛ ۉ q%%%7nܸzjQQH$OOO)i V &`0TTT݇ZYT>ݬGX[9lV@wd R)$Co5:1S(Y;nTrPi-,fEo {BB\4JEIiSho_MxRF`yleF/5&as 򻂡@ vw}ԂQ)ħ%rXgt4X+XBuYȯ_})_ݦh$,+L|IbKuŌ`+YJ,VjjW%MaclqQ«e^2"2dAapIJ͛^I!8*Ҋ\.%4 HBԺe0p;`J(%7נQ2%Ub$FPi+LPO嫥 ̳,OQ; D(M^V-TY2O)וzx/J$ %Ni@u7Zhj-wqܩSN<ݫWGa -EdcLz;ꘁw+X,:>DTOі}nPA gʪ" +XXl0A xo~lߺuM6u^x `(2탉y+Wi&((hp`!Tqׯ_uָqSO>ٴiJ[pak#[Dnf+' @~XRXy͊@0`^X@Q6N` cZ4:o[PP?ߨf 0={Z1٩qfA0/ױcdZ=`G%u#@!!aϞ= x> iȭxP)/e8g=/vL~Ѹv  >>###00p!++wyuV6mZnnnBB A޽ [n [zuDD0,feeqV:MǷiʕ+{]tٳg[BE&H"ܱcǪUf1/_/_\TVV +VHHHpvvիי3gΟ?e˖Ɩ.]ܹs+W$/^l0>SPUU}ᇝ;wfYļrllɒ%rss౱ºvz̙Ǯ'/7H_1" 7w 00p<12+FOez`SNmݺ5**I&rwww oC jBPb -$QIVKT5 jǎ$%KJJJHt<9#o߾-B]Ht9TsHϚ5$wϞ=/X… vrR;wm۶o9IlgO?e˖=z̛7oȑk֬ycK.CRfL IDATk6>$x@IR\rfdd$&&uX|NWnݺW^y}7zrsgddlڴ8h(yxzj.V*erWWWo(JrB= 0`#a1CSOb`~ĦVl0J"*|EEEʛvز8{@||}LN\9`p̂jo9 (((>|ڽ{ࢢ o޼& z`]aӦM4zzy[!r86:(KhPBc{1}J0Ƙ& 1iwot1tb```۷e)6Jb="""RO?ԫW/S(l2˰>L& Tiw5P{!88e߾}QQQta;wLJ<EK ]G˂ 0/_޴iСCɫh P,X`4خ=^֊"I(/1-eҩi:}NbȮDcNZeeЉWPP`N+֏7o|@Jٳg̘٣G]6fX`-~P1P(/NIIB PӡC{Z,ɴgϞnݺ'QI``͛7+߸qcQQ0Vl64]QQ1k&&&\վJ9jl)Ӟ\ñQh$Qtr -r ) tKrbЏ~}\&2 )-2r)#c(CIhA~#B#~Ex`QɓS30 r9A[)b/:.˺j;8K$c{L&ԥKAAA3f K$:uڸqc```iiiǎCBBO[&Ol\\\<j2kGG#_>bĈO?_O/_SO?Q5oޞmR_-F MD%xhJ*f/FRimPPҞZ0n*t @t-F*9%a$r)xi$f Y]!@!̳ DDDj{ªUT*M"Owɒ%vl>pիW+++72i4QF):JA&J!"+l7n|833s޼yuU|r^|BD5xb={0 3}tF8@Dq\\cV3*H$LB&3ͷn݊8X5Lk׮-,[#2d͛7f_8~,fsqqaYyeSSS/\z$eY8suu8NL<ƘeQ/*"8~8S*s\aQс0/**y8  oUAZZZFFFajjjvvvk4?c>|cǎgc,J9x^x]<#8۵k [+8^jӦMpB35T?x=zz~#yyy;w7nS7F2uER2̞=#^xGeiqt:ӧ}ٳ>5l{>3UUU#G;vljjjtttaa/^|I5wwy-Zz~޼yofI3fx봴T̙3VkZZZrr/brʕKҥK322୷֭[.]#G$/µk׮]UDyHR"YHV!@LLZ %%eĈjm6p@3޹s紴4{ذad2YJJgpppqqqTT= /8eʔ)S̙3g|򸸸.]_rO<CR9BmʦaÆM%%%u֏>߾|{tx %ɓR_LA!DكSZa⒓Lwޕ+Wy^^hy&1EJc󫪪ϟolhҥ_~^ߺuks"YLat=MsH)Tw $>\N:M"";u!V|_^~ۍF=DDĚ5kϏ׷~ 7oi:; jz< "i=h(*$ бcG̘1gf_}Ʉ'cqƑ-d򯸸w@6/0&L`h <7y/**n:"@|M%%%*qRpW^^`^KtHYӧw*JV>|$iA,?@ֹ ޽ۯ_?TJG?9r$5yeY?i>u&[#:ax''S7nZRRb2HJVGlJ#Fܽ{_$3EL&L+ѐDD0^wRoih PYYch!5MffNldcͦ2335 {""6 ENΜ9gϞ={'x/((tD"8p`3;hH\Acp~yGm۶uEw*l|D҂AP fl0]t!KDbF.7OX *""R8(""RDDDj݁H ;At"""5@DD Dw ""RDDDj݁H ;At"""5@DD*4EDDDc"#MS~~3J ""V?yԩ;8VjsC1컥+>>yR{=CIV? lәDDDC'^ʒv V)$E*M;wK?X|ebb:60Ǐ駟JJJV?MҢ^xqΜ9 èT#G8V|=rNٳgϰ0s΅6ڶm۷ohDDgy0X.ST{\XIL(fP&HvXps'w?:^߹sg;v{nJJV߿޽{{IARRRllF1''Ǟի)))իfs=ȟ~~~aaa:իW[:\x|޽QQQDDOw`Z컆}]<*h(flv ,9p}ִgDhrrrB2yf… ӦM#ǩ|=59:{9Ju>*C}w ::z˖-;!ذao:`Eyxx.[ *++sssP\\|ҥK.3u:RXX8~ŋ_rm۶5vڥ{&NH$EEEx l};v3Q3Xh4J<gRQJ# ώ4J$BJh4 `N~51V(JRN3EQu: OO⨨Ǐ/Z~ĵkb$%%_I&lݺe=z={?|0q""&JU;hRu35v7rSVh-?ԸM6VKu$??j:(k6lؑ#G;>tޝ֭FEEQu…#G<;uꔻkZDD~G<0E)J/WuRJigT ;yаQ s8Ab @cNU*S瑚'ih޿ˇR}ݺuG6ldff[n0o;hѢ7|S&_PH>|!PZ)Ӫ%nT@[W.F⢒Ki}*dIII6l A8vXVVVm9)7d2]l6@qqqyӧϙ3}F2)){ڵ۷xyyEEEڵKt"J<**""zmAj'FW7psGnn ZШ%GDDT:NЦM/{ٜ@sONNNNN =ztyF3k,Ж-[ܹӵk׮]m޼, g}d23<٩S1XDwŃ] 9r KvrrFdoj|'B<쳞= FcvCYYJj ˄JT*^')n[? mR8iiiW^U%of~Rs#"""::: Y:?.u_a8ƍW^-**bY$wDDDXX DD4]d0***CgggVT*#i0<ϓei#iۏbF >7nz=M7\!H Q.|g y)vxnrCcѣGm6ۋ/jAHrffT;W+c0`9^(ȫؾՉ[F"zjVV+^H%bYfa>BE: qc2' ,܊Μ^ y~z׮]9C"!Kp|=AjQ.ZdQR^+q c^x[95/2gt:l-wP?apppJJʨQ4ͣ8<˲...1(X?I9Ƙ8ӂrUrH0#2#?ڲj=A`a&_/~C__߄f10<ϓ`ߔbryv:tF3B-'2L=zty豗oyFFiڷo0?m)͗niPn d͓lA]{Ej%''=~|xv7n4L4镐ӧMfs^^ޤI# fϞֶm[,1GԩS{m0fKFFƈ#۵ݻ L5GFFOk߾}|||UUռy:t?~|nn塓ms΄'4dNLLӧO3gkJbΜ1߰a~1b7-7 6Z*<<|(wCuiZnb+7f"TzIo3i?wNZhCf̗2#Wi+|Y`wЊ ==w}wׯ_Dm۶ 8+չs紴30 BH0qVf6em,byب;wX,&M;wٳg)m,Z,9sر̙3,˲?hРK.ڵKP&M;FömXF6vI &̙3/^z]YfXn߾F,Κ5KV={W_]l?cƌϭ\ҥKf[ZXb޽{,;{Ҳ IDATKV^{.k3ً/8qbR>}p޽{{em&M7otrt,k9sX,Cml۷w;pᇔ݁,k5kVaaaRRRRRR~^ٳun֭kZxI?mݺuvyii]Oii}i"*˯+ؼJ6ͫ$\~%_VqEU|q3 :Pa<ZPܰVt/Ç>DS;}ϟϲ, UȤA`q߃jy^ؿo{PPPIII^ΞMdɒcv),,ZMZ ӧ %R `طo{:gϞ造Ljȱ]~3gV8pΤv;,^K/u9,,ڵkMsss322D)..֭۝;wtܜ܌W_=8Z*ВZ-h4WW7сfhAܙDw}7::s/Wm6""ۀ`_j++==댊 N>=e???oB%''w_wR9j4Ξ=ku!88x)N*(( ׿O>˗/СiA?tDE-_<$$$zpO?T_C4I9E!F€QP? %€0L" IZ"{w7)GDDYibqwwdIk߾=q+Rj K,6mH4,RG mڴIOOGd%e׹sg!j nܞ@O6!/((Mm"Fv=''̤ӧv%=B]vMOOGT zxx !܈Bd>8Y4 ϻ]tWA~~/X>??Éd "'Azװa9}{rL_vydd2mܸ1<wھb!!!Cuh"N0mx۷o߻wŋmݻn:e9 N Mrg-***vu9sILLLii7|o߾ŋ@hh7|(?9x Q{!DQQц wfg_dfKNNޱcGNN>&&ŋ111jNUV:ujÆ rEbK[:%%?kQ(@R!@|)Ѩɤԡ͛dɒ)Sד/^x#G;ĉv~8p`DDɓ'Eٿ?<~>}ƏE(**cbbz1{lڵkǏ0aBIIn޼ >>>_~W^}mȅ ~Yf555sꫯL~J[PC}01j~1MA;YXp[o5a„hq)߿?3f͚5k׮modر_U Tt)ܦ(.A>`/!jPzN[ޞF%R˗ݿWWNXKn744\|رc555ӦM|bY} ]ܞ~6(^^.IC)blJU^)[;SB>e911155 c(JAA 2eʔ `?Yz$ :5lcRdc| w>+$͛ TPEQWX0 BX岲O>_~'OnC(||LeOPS%P5zJU=wD5j0iiiiii3f0JcͿNnyBÍt6s̄5$9Z7u.b 1&r:e˨>?q0o¤Ц>(l69rdĈުiPaq\//!Cܳ}SQQ1*EQ0Vt2|Ǐ0q(渝yIc!u{xD_IDfǻ[N!(v :Q:{~F'PZ(zQeYnRTuy晌e?sܞƘC`"YQݎlaYO^w,*7dQvK2+`oܖ1Hȶ1yk嵵^6}ۦ?s:>JI-;rO [>l?%0H2T]2EQ,˩ԛfQ`$J˕3Ef9H V0F=sB₼2çƬ_rɻ_E&Ӈק+fi?N 0 0[c0Y*$ p zt ˲ZJX,QQQDZ,Kn$E7b]1`aÆ#G1l6 p>~f4͟`}ns7nN>|@ }t_ʕ+1ƛ6mMŹswbM&Sc921(Xb6+ GBRX,4Wo=û,`PglcKjk&Fl 85OM>nY^=CD ZKl]fYX-5N@@L#h8ܻrl6}fFwwwrcaK0 @d 2999 `cϟ;W=&2Qd%//7ߜ8q]N8q!1ee/gRRRJJs^f#~ {ٺuk~^>İ,˲ Qј`tjycjc\z<9cya#މe2, aae@+dY*Ud)l(0lP4Tf 6Sͮug&dwY9"A!AXYcq`4j:u@ivssyX=pJN0rHɔEZdյZMM >|eٛ7o˲Le7448U-òYAXn]uu$I,.X !!̙3eee- Rbbp^ڀEIxVXtc%ȶ3O VbO_&Lj8>47^>:E Fٹ]+߽d76Yu,9+\ :V9<m4zZ@WUUfGT```rrҥKCCCVoqUV}gϞ:ue˒I,q$`もŋܹS͘1cӦMzEE===m6򗿐=8ZD В 7 AƋ7 >><:Э 6nhQx/ѝaoiKo 01ٿlN082CIDAT?1-€Iਢ`Fkz",е| ]@i$~w3g޽` ^Waaaڟbeh4b+**Tq[KqqA&Ȳ\RRңG`$YEl'O m&\)V5;;{_b{x 6e$ Aq(00>TU X(J7+R5upTPZƸ699⥗^ǵv-C)l;vxzz=ݽ%dY}O?$ B\Gbnܸj"##r'nܸqL;+z|cS/cZ7 /˶4 ($I5557n̼vZuuLpC!󼇇GPPPDDD@@@׮];c"YQYYYXX[XXXWW'b WFv$ t:777Py rss:DtPb&f\WWg2dYT,XruuusssqqqqqiI=ejLzժ h$3z(>| .J[1eĻѶ= r:PC´S?Z]pՃ!Qr/j_P']p 4HBYqʕfuիW իWfu gB tB2F~>IENDB`Indentify_Brainordinate.png000066400000000000000000000567731255417355300352610ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Information_Window/Select_BrainordinatePNG  IHDRh ciCCPICC ProfileHPY_Das@ 20q`Beqր (5 X wEA9&Tnz_ wrDXTa8ۍ=<k7M wwшnqV4.Pq4n*g\8aЗ.B0M,1Op|HgdrH̼B@9DaƩfa?SϸeVxA(_nVjJrH)3FQoE)A ~0,dq ^' D>i }8A,oφF,-cOxY^+y@%0HA "=~M˻et l!Ԙaina?f;{ :]Y @ <"f3<@ЀPZ@ {i + (pA"HbցM `/(ap @38 .: #A8Q!%Hҁ K 9C? EAB:h TCeP%T BP?tƠ'a f0f~p(ë8Uq _o<(@(wT *6 P%*T= ՍQX4@>04].CC7;7C W 18`ؘH ǔ`1.mcvXl6 [=mcI39q\:.ww7}xK>/oƗkg) A@$ل#6 a(K#:CIMRb=D"iIKIR.tt4DH#1 vQr; BѥR)%c)['Q\Ij@4AZG%B:GD q GfLL̠̤,UB6P6UHVNNWS''wX0EբS-#. KӣiIB Z/mB^NZ>\~|9y Eץ)S;O ,xm  )*+(6(VPTJVڥԬHlT9Kr""EEN-U9ң2*RݯzIu\檖Gژ:UY]GsSza54iZ Z{:&յhӮӾCa$yUYwTOQWPZJրilp61L4,7a c퍅Uƃ&dIIɐ)tiK3mh]ff_mS̏?lfВkYnyˊbeѪꕵuA6T%6[m:lيmmb*4fycfG[tS88&;:.[a'M'SęEÅRU˕ZeJbgt3w5wwp_( ,|^3Ves5 _;~d2'b%%< 4@vGAzA~Y]|`u!Ԑ!!BBw> Q!4\y=J9J\lﲑ;Y~uVJ< Tq&qq\w> +oo,)8YSBq(߉?X8.p ^%$Jz|4y:%"!*& ;WZ_d$IV;޻zB'NҖӐ`ӓ]Psfy懬kddfo~ZZڎu6ZZ_cƼ#޹67%ouoDliS||qVǭG/wն۾ ~.]'laNN;\v+-)޽dwƞ=o{ĺ>⾌}RҖw\XvܭBb[]R=TxӏVzW6UVrXHV.n|槿+(B3y pHYs   IDATxw|ϔ5TH! Rj|H X@! (x)" TiB "`(@H$}wf ˦ w料9sr!?RB8 BH$"`0bf,{[% pp0 EQ"q$ p'haB #l+3a%t2T!Hh&15 juppRi$ɦ./ƘB<ĻK!jfj0 qF9ŒwX֭*ûTUʌ&c̸!d"aveGaaa~~RT(r b6& (jSOmV^^n>,T*MqƄ7U-VU%ŞbKz7A˨1V땣G& ?6쑗^0Ғ2b|CKtv3 Sp`?jZ,Q^^N\.(O]﴾FG߉=Xltvz,[h<0L.ѯ_fÇfٳggΜo߾{9/_>tPnnnMM{7)N<٩Sjzc߿[n~~~V_~ŭ^K[C75Q[QP8VE^?vbq;w4n2}}}===e2Iv RJTV^^Vh5Oj+Ы5R$>C'p4Wvƍ={tIiZ$߿ ېk׮An[EQY,7)ʎmѼ@75qpb7d& Dbnv84^YYEaX T*TٳrTpW/R"5Px"JZk2ٟߞLlH$IkDl_|_vlg&&&tCoH+l޼9>>m9r3^fM8qժU]ti浄&iH؇44)%6ɼ+ r4!"h"]nɏ A$BiJUQQhrkAPV,h8;YɕU3 3Dye-Z>˖-+//ѣfS(8=;;{ٲe]v}wZ-,Z(22d2/^^^ӧOҥԩg9rdڴi};wn.]ƌ3UVmڴiرiii+Vɓ'ŋ \d2ܘEddd׮]KIIx\.5jԘ1c`ǎ~iS۷o~qKr|ӦM;wiz̘180a„O??=====}_uuuubbO? ϟoض9s/6l@QTM!&UU{j\TH4VCZ)j%ZBRbL_hv R)U(J7[ZP TIRJP+*JVykcTQQ;92##c„ W\͛7cƌtTsNQק oC7o8矏{ǓO3L}g/rrr~w\{հa6K.'OMNNر֭[RX,0a®]f̘fqj-//o/ۻwo߾}̙cǎ_D"8a |w׿q\mï|LDSM!&H$TQ=h/oϟLv &i/oL ix\&S L)ieJwC L!)erT&Țj?ut"""8cYv޽999111'O3{ܹj:>>ŋ,2 /p8weYݎ/p4{neʎ=O48ɴ{ݻwGFFF} &HRºv###-K~~sMe_&_}5,?55UUUApz%Kf,hZBߟnoҤ!d/+nRA(URJ% 0HAd496O%P0Lˠ~HfVKhZJDBu؀aַ;q\yyyDD`łۀ-gIb/߿ T?<))Iקrbdx㍤իWtV[[[?_vmڵW^g 7v>X7Ἒ\~qq- DK8NXdSkmv4)Ӳ.]*uZCh5 PA R)!s]YFF6ZH$X,ZֹK^@Y,&/V-$rZ,Ĕ(B@j-1#^ԩÇ:PvZ~9wWXv#ϑ{IKK?~ÕZ܀rxNzՠ zO>Zth0`L;._裏+WC凄\~m2h\hMn@HFFKՄJEh44| 1]ͮhl6lO1"Iffi4FwvUHk"EXDhRL"!bˮ$NnݚzݣgϞ:.==l6;v ͭaV*3f̘6m#<Η\szmcYvҤI~omS8ZMtŋޝݵT Vwy{bavTjBFP4 `&B,[֦j#TSpwW+Rd4[*S*³y|}}V+V7!$9TQUU%$Iqq1tjVWWBpss+,,H]QQΧb_Fj;Rb0VKT*rypM.T bǧ.Sm0juuuwii)~(AAAEEECR@NJJJl6[mcYVRd2d2m ?6zjZ+WeB XL&F*FGWxT&$db`Rjv;4]V+5Ke'kk4mGXX%Xcj& ]F5z2{ňo`0̞=g6sx[[Ԗs[ms^hK#qXykw8FQWR~oے׫W{-F!`زeK3B]t-#Ӝ{ǵG4b!;[sC;{7/xUCezv&2-dZA7s_SIwE#2=~ z=MzRՂp <{,o~~~zzsre^߱cǻgƎz_}ՄX[l +++ӧρ2tPZ l2|pZ|^abXR$Rp VRRҳg\l.nݺ/w=u|pGBոihZI/(˲x>0VaY#F6mO?}]5yڵkż|߾}O+**5kVݕЍ̵42iҤm۶-ZyjnҥKgϞyyyjժdLU %Ir0**s*{aj111G:t(8m}n'nٲeРA7oN8͟OQۛw._|ƍF7yi6nüyxmСCYYY>W_}5t;s,Z%IJn3DR#ժT*eŋG=Ch 'Nl~aڵ-itCau<88ׯ+Wp7vXټtRlXVVhqqq7f^Z788{0`_~yu0F˧Mr5k֔/\%)vܐKR?p{NPrrrttRt.ʕ+aaazoW\٩S'>~}%IW_իW 2$,,l̘1# %%%%%%"""&&fM9ax@ +!!wXK^zL&ѣGϞ=w~N:a„ 6lذaǎ-R68q:.--իsر B? 1bMr}eYѨhnYzp3㪪*$]Jt.hyJl&L&ٌw <ّܴ ynnnzzcbbL@Au0:.''';;"Cxˇ 2#rg6 Cuu5^/TTZVR `,1hl r0B$GDPJI M5ic--Ks2s5f@A RSnrR+%I-Wl8XdrF0f n h$4S*Wy(ib-lkbcPRΔ:k 6Sj$ W=.JB:=}{&$Ed>ꠎjM!n5,6ZQQ^Fs!==TbJnMv]NQAA6}B̘U]ir15A[ \1P=B}21%Q9Ux(  {Xt=!ɴbF3RAA#5rJ+GL&F+WcfͬP_%e4:,6wu _H Z9<;j|\ 8X2Q`۵A3kRJ V20́סC={ޖ)_duppSis8n$_6  Ͷ`l "|.P]Y>륑_x:8, TnR:Avt,˶t)B.\ rvrq0;b%4IB]#k׮ӧOtAUF\q1w~ܹ .;%WWV*8g9l GA@~g5_~!,ӈी6z뭐>.˷<Ȟ={zqE:8G&:j ^/_t)111222///%%EV?#΅NQT\̲K{]f͘1cny>˲K,ٺu+Å  t ,z[nc"G @NImξ c5C,f"or&×F,YrС޽{ڵw9r> M/6Dı_~޽{3 s޽{'OmVѨAaC$β,p,DZ}W^xw'MG9rϙUZ~s:_xQs\[7630΂E C{[,s`s&WkeɛGmO承|SL9rHppyT*`r{BƋjAA%K֭ZxqLL Mʕ+Aaa!J0KSL :}mϟoxy5wlڴO?U(e]j…$bРA .h4555O>'|dJճgCvZ`ABBBEE333-ZM͛g0VXADmms=g=C[39[YY;8PPP&$ ֭oupf714I`g@OqvC,aP5qd,Xгg7|֭ӧBH$RN?p͔?|+%"8jqQWڴi6(|wt:6j]꣛"aX@.^q\n]Q^ueq/>}~kZ>9stMPfffvqY nO>= oؾ`]@RRȑ#71;ݺu9,ÅpС;wϟ}vºuufffǎAgT*; h~p7+3rA!*5!qț\^K;t|!Z"|.&aM:j$~mڵ;wI/M2eJqq1v;@X,Jz#GVZUZVp /O///e˖ UUUg*T IDATΜ_5PPpȑ#W.++%ʽZ7pzRgi#qw$IpH>vvOJMBih< Dĸq^e ҌAa,7bD@@:EEE7|EEEaaa̍>c ~7h\\ܵk[ڧO!C_~͚5učV^}oN87M8'w.QEuI$A ğ.8q!j{xbyyy޽E"ybppp3>xşyڵ-Sp%bcbb\,Ϝ9ӳgOdG߾},X\oPh69k@^fF sssß?+W/____,R|v4lY_7|EC9rԩS^z>Azɛo9`4Z/o&.*ݤKDTLN!q~ p)b'ߓ$98Qus?&M'>s111hx%"AIII1ӧO:uj޽wޭ[РկEnL&7oޖ-[֯_OM$Ifdd\vs۶mZfy֭!!!X ><ֿifX(5kҥKWZUYYh"~%u\nZ5*ŹRVI.QR1%2%S21%GXe5TY ѯWH%R1-SR1-Ф"]鶊QԔy"64g4c?:%`0HҖ!h#eKLnYVk TD",|JIIfs@@lڵG}7uT<ӥK:88"22ḳpĉv%K TVV6k֬@Ʉ㉉T >|ŊO=Ԋ+.\Y9s$ɤ$ 8077͍ ?ӹRp.ÝwQ']F-[Al_M_EDBekpI#nL:.X۵F,>!""9\^( ̠/Y6&ۻoÃP痔tF-/鋏Rb$y`蹋(5 źUh^=L&i$xI4!~~~|:Χd2LǑ??88˿eJF*$EAIA J'B@rARI&&A?݊HhB&&ebJWa:Ԉd2|kF  4 u+.s n!8`Y`Y@@6L$A[)7] +%< TDM@  $$hl6Zi*"MAkR*ӭ&䤷EEu /Iv R#rW)/x!bu Vn~Z#&FW\\ jKJu6S5bw#Edַw:[짥nm$4ᥤH)%/BWmYn wZ7 MLLyke^jOC{()I ӭ"RRDbMAhD;'h D$d=C!m§~T*(8j;/$;|bٱcGvvvMMF5jT B?&FEE2 v=''_M&ҥKGS[BeeeKnnnRR6fAۄSNvn dee˲[nizҕ ˲VB%&&:$ \mH$} bX,.\:t(0pp%VK2dqAۊOK 84jò#55رci0 ø3 ba)M hfW o0raaX))-ݱcBh%,02 i8rKbjjj^^^[T |t/pX,faY6ofY a6oqòsƶZ|yvv6W_}%m1?πw~%}mmرc{cB1 2}tC,DZ,KԟLcǎ-//?x ?ѣ2[o}7mѶ{OAAA~jkk;t0r}e,"A.xNL{xxS|a&8p`ppqPЊCn޼>vfTa\ʼn'&N>uT~\YYχ <8++ҨӦ7|sn 6ܹsߍE :tѢE3ggϞ}{woD"6b{O ~ך;{Q`ƍ7oޱcuaia̙3)S駟NMMC|?pذa$ɖ-[ۜ?S&M4iҌ3Fw .LLLڵkttӧh߾}SL niӰaðӦ\N^^^֭/}SNPQQa0k]$_:nRH,$w@dCn4M'&&L4i۶m-酅F|2Κ5/?4Nׯ[%.`I z*qM VJNNn|qrR)%19);;g}v7n4/S}_p#W_}5t/`YeY`0D[И}.Iܽ= 22{3f 'N?x$| q4vX(Gueee8}Ν|͚5k-Zy^~}uuӧ id6F#O6mʕk֬)//_paΝ;Νi:**71k֬9s̙3g̘tR {7FL0jĉgΜ9vh왥ьRRRRRR"""bbbF ׮]{뭷֮]R 0wW^yg̘ / 6q7u & 8PVoذaÆ ;vh7xcڵv.]OA;~;mzgaiZu~cYC0vؤJnZ0 SUUըg;stܹ޽{cMq;.nIO M*k׮xcRQ/@{C# 7ho2-dZ!ȴ@{Ci  AL 7ho2-dZ!ȴ@{CiFs M9xiDBǏp6`twwիZ[&eqaUUU;w<}O>sa/֊p8w%&fYԩS{mʔP8~ҥK{챸F!gϞ3gt={tYʚ1cMJ2--9{nnnnnSO=RlZZZyy9GQQQ|-`; ! 7tPiiiFFƀk׮eddxzzxf>رc*ǍYϙCxzv.vyG{=ܽ{~'_ <4FZ$IXGY%I!]MDY 6㖑S(μ˳f6lXZZi8nX(((ӧIǎKKK1bիW8޹sָ2֧$) (D"wx5O}p0#""߄oٲ[7(w3Q2Ş˖-=zСC _.\___ wݻwϝ;777wC\bn,˲H$JRT,KDjU*2-H/99yʕXE8nW\iR绤(ү_ɓ'Ϙ1CӅĤƚ丸8+:!!aƍ>>>})++ۼy 27RTFGGh!잌iPrrrttRt)襗^Zdɒ%Kw5_w^z%4%{=Fv˗/w֭[nk֬cC #FD"ׯw.]ZmuDt:]ZZիW̙ӱcGUPqq4b.FϪ%-T*N(zlcnZǺrFFFvvvdddBBBdd$榧?>&&fM ;{t999٥8H$СCLLLTT 28l6 j^RZJ<4I |6N}p8\ &&wNYlYUJ`݃ #|Btt=7BPXTUϟ?wJ"Jpzis޼y:tp;vݻp$$$<3ImIvʪjp ;#8i<{l@@]P/_ b8((eْa\Nۿ{=a.\د_62g`7$Aw'RHB,Æ s?;>}zSht̙v P?0cݢvM,cnj"̙pGĽ}ӍxqvM73V StnrcJk yڸgpp)SwCZ fm*9ݥnc /CݴiӀ;Vw އDSb}r)V!\Nsvj|IkwP<2]SSh"R0}ݨc}H@{[ksBB_y@0qWUUW90:S,;?'橭>\C vQRR˛~CnE@4>>]vݱcLipP+V3gάMNNnDUիEuԩh`yb}ɴz?}ixg ʕ+`̙%%%ӧO>}zEEmXҳgOD]tjwV3<5yUV 2O7b̙3fΜyU ԭ[7\>{Gu1jԨ4?ݽo2M$kQQQd2} $4˲FQ4-rT*ۮ4 Ô` 5+W;w."""22w=`UVRE5%RT*ux3gNuufÊ@ 4MɍP^G_8#ع#\(bZyGx$I'$Y[[kX\ԏ/O/ M#n8TYY)H\~ZE$yzz\p=$\lnCpZ&.BΫ$Ib777wwwg;E m˲fY0ߺ]E$IR)H\7$MӍ 6 #ho2-dZ!ȴ@{Ci  Aš@x *-\darrrΜ9s༉e婷i:oGPN:u-**@ud.6[VL=p{qϟwww λw!-Z#?K.Em޼yը-z S ; <<<.\ԴQX=~F@f"!C11b]Rҫo8|EJyѿ:>~`>ןoZ_P帔188p TaBVn;^aߴlAAA؀fq'i >m۶=747\]t\20+Bap8(H$.]¡}{:_!4;<L\] ,7l0_Yr/^~X6P֡-k{aTZ]KjX>I $Gd*цl P?l(ʹʴb1 1114MS=a H$@-Yd}5.?'O\bř3gJ_|饗f#.]rʣG4qNNH$z~mвe6md_xᅹoՂ5LB!㰻.]dXn)`6UӝRR$"v IJꆣu]tz5gTj?ngƂNd_2W>LfA׫4Z> BXGM%lv7=l6Rh4Au3Z&ҥKqqq$QwO,O8133sܹZ/N$ryyy3g>|֭[:g Jƍ܄Hܜym?O?aÆ|\2IQUׁAHiJP(x̀ZH)UE%I1M((()"i_]zyee>5ۻ&+|3c׻$$! V< 4 JK A-C+"e+T+ تS $d! Pi$ N (i8{왹qap]>ss9s&!  ~rlwN.~wTz BC„()B \Q;g<999R>O'۴_`2[* !0P~'Wvz& 1iLӀ4%͏qjl! Ȳl񄅪"ĉuuu`nwss3xpprʷ)SڵKӧO(z1Ƶ <=QLJPQh4*k dA\L) 4Q`@ @@@@D _1?t&.>yp۾f LU?D80YOHy9$ E P\aɔri(v}xx8s‘Q\\288t:.]Z^^~ȑH$B9~3gNg{{-[><88#[ӄqզsBѭ[VTT?e1y$m3@$]"QxXN2J`pn 8H~ԁ6mß~}{%Yqݦ݇?} !E/gJt'ۺ䎆e/0Mdi(n;w?$I4FÈ/^c4UUb0Ɔl6km6" ,Ą`m񁁁˗/8LOcXWW׍n<00W$b * F3= Ki3>rxl\@"Fݷ.+Jmr16::244cj23<(r0rJ~~ rss3IPU55[%J BIŸ+]x@ Fҷɚ5kV>x22H$2 a|RJGyyyQQѼy[<%|>B̛SXXt:y*"vߺu:i#=>>>>> "3 Select Brainordinate Select Brainordinate
Select Brainordinate, in the Information Window, allows one to enter a surface vertex for identification or a dense CIFTI file row index for identification/CIFTI map viewing (if one or more dense CIFTI files are loaded).
  • Surface Vertex
    • Structure sets the hemisphere on which the vertex will be identified. 
    • Vertex Index sets the vertex number to be identified. 
  • Cifti File Row is only an active option when a Dense Connectome, Dense Parcel or Trajectory file is loaded (only dense CIFTI files activate this selection).
    • File selects the file from which a particular row will be viewed.
    • Row Index sets the matrix row number to be mapped (also identifies the corresponding vertex, which is not the same as the row index). The Row index will be displayed as the Map selection in the Overlay Toolbox: Layers tab.



workbench-1.1.1/src/Resources/HelpFiles/Menus/000077500000000000000000000000001255417355300212115ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Menus/Connect_Menu/000077500000000000000000000000001255417355300235665ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Menus/Connect_Menu/Connect_Menu.html000077500000000000000000000014061255417355300270350ustar00rootroot00000000000000 Connect Menu Connect Menu
The Connect Menu contains links to external databases that are useful for Workbench users. Greyed-out entries indicate that the links are currently not active, but that a future version of Workbench will include an interface for the databases listed.

workbench-1.1.1/src/Resources/HelpFiles/Menus/Connect_Menu/Connect_Menu.jpg000077500000000000000000000060441255417355300266540ustar00rootroot00000000000000JFIF``C    $ &%# #"(-90(*6+"#2D26;=@@@&0FKE>J9?@=C  =)#)==================================================/" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?u,9%!SB[vyT{V-چ{бc*rD |BGSg iHu*2:UI;z⪝wSTS92ʈ!|(UƢMcS.EeKpKӮj9jtԮhoj1˱ji "prݎxo-k]g,Tsxim*I# MJHƶRI \<$a,>ΰYHtLWi;_4;$G,򾣊5:U k4o*ʩ(#-fˎٿw3=Xea5ϖ$IL^SmgPbv[]>x%KЗ+6A@j6k4"vVvb6r1YڑzOPyo-ͼnˈL 1{uyE]د9=8/|4\Ң,(J2x5-De_03o!q+=i$fdxBKfLp2bXfGPCAR."j kkxơyoI˜$bOߝnXgTsM£Չޓ?PFS=}Uڜ@sߧ OHdOЋ#>cTnsڑzOG^ߧ >ѿ>ѿ"H'A}Q}ԋOG~ߧ zÒ?5f{i"Wټmݎd+}D\$EqV<ѿI:FdF1 SWKH %XS$qLHz9\N>n0z@cYVs ߲_x~g\{ԱYsG&2c㚊]4~nc3G0$b+ yI4LBA'?{Pr281Ӿ}lczJ12Vfnd$ۆy)f7!fq03c"1' ӢOܽ2*kL . #=xdogq;P8<:]S w {~@U:Mӷ2h_kxtx4}i3Fhv{h' qV(eMp)2 }*\ 񺯔#,xϭ,Hr@I| &?ƯIk #VvqHvV ׯ@R墍$v)bsdZCIuJyi#--pС:P(1~ђK-fYد 9IxхRca8W@,97v+SNEi @iY,kǜ̪XrƯ n Ktmo"xx@i) #WHH8cڢ]Jqm1<0}W~ndžSZЪOހ+?tei 0qt ڭv/E ISnG3K."sǎLզӭn`O`qu}%HgҴ$FRB% 9)bcs@IRqQ}/~?workbench-1.1.1/src/Resources/HelpFiles/Menus/Data_Menu/000077500000000000000000000000001255417355300230465ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Menus/Data_Menu/Data_Menu.html000077500000000000000000000024211255417355300255730ustar00rootroot00000000000000 Data Menu Data Menu
The Data Menu currently has a single data transformation operation:
Project Foci for spatially projecting foci to the volume interaction surface (which can be set in the Surface Menu).
  • By default, any offset of the foci (above or below) the midthickness fiducial surface is preserved in this operation.
  • Project fixed distance above surface(s) option allows the user to project foci a fixed distance above or below the volume interaction surface. Any offset of the foci from the surface is NOT preserved.


workbench-1.1.1/src/Resources/HelpFiles/Menus/Data_Menu/Project_Foci_Dialog.png000066400000000000000000000713621255417355300274120ustar00rootroot00000000000000PNG  IHDRel7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:S\ 6MUc8K}/N!JE"Q<[֋/?^.2d2M&E"P(oN-T&txuګfS˴=? N]?---?$Tx!sK3 O>T,˗/]R5ݝW^zʥKD"q7' NC6;lGin1꟰!$ i넟@ӱm닯B uߌvrӢ;Y1/s}{]ƠnS/}6*hޖ5OA_=_õk;wKD YV~ɓ&~'U5|832fի>>>RT@ , P].KFzU777xTL5T&w+~57㺛ӮEr}3C}<$gEԋO)r?|;Ϲ059{9U_'WO)!f;sݠxYErB}KYtK_j~ԗ1MyLMURh~xXpqBɯD" ࢐@*RH(PMMJFAgN'n;9-"qJ" $P"4`o9 w BŽ*eZ,RȅH",gV`PR^W@LˑC]|z}}P_N‘MyPfB /;B l5Gz;4&zccbۈܭ3^֢#|&M\ W%Zc6rG W]쮀BwwE75a ؄A |i "ݽ6AssH$rZ|Ȁ+hF(q.4# V4\k &u_c:bcCl6KC@76w.@R6W?Klv;`l6!yzQo|+sI vAi`6Դ'YLk-jYmFfp/֦:!tɯ˨++(yxP wBb@`Q69AP7559ٌƖwwwao@$iJd]$A*f|Z{} o u$ޘB LeŒ=8x哃? <<ϋg6do2\fO\L|8p9n>RTڭ]E"}b&&kϐ#TЇ\‰vwsF{Kt{ BGz?:io+rqI] 8ԋsZ$09_Tl FX,;Cva邽z+{M` /qkM4Yȯz_cۻED!WK9/{Cyk6ı=> Oػue⼴%_fe=<~ iz ÷x,ٻ?.hdg}2 ` ^?Bw|l6@??a36\Pa Ym X,ݮP(d2M8shEQbeٞl* &sa`YJ93US넟=p`Poisc[8N"s֙خ,3"]`ihhE^x@vr|ڃm4׬0F1Hfi4s\E Į%2⏍6!C쵋WL:j*//Sb<]&b2(l-8@O, Jbqpbqe& YEmi-v;8U<$'d4ߤuAHqV|oh>;3& Dn8O E"HDtl7E4Uh[{;wtf HP(r j2Fjud詸ChJı|KKKss񏛈Olpw,uU'(N>˖[!1TNNMur[~ w ]&lW4&AvG+Ƿ,1[̷UN+ׯk+ہ6="Bl~ ?O!G )DB?@#HyA'v%%eJ_{zm:reJe׷zR3s޾urγXrV~t֞HzJT*Jʾ0U[ԍ;d= 򩬳&p&}ݻʧ2 wsog.^e+viwVRԦz}jRv8=()6Ԛ8G2왠V&eղe}$]sskWvvZ-z&7_ Յj}-~OtgV?uNٵ*'9fŷI_`Zt5a *k zjN32)7H>=a'CWίIIvssl2]^7 X4ڏj>Z1ݱX;]3 uM dgAϤ{-:]֞_PR~2v #ntYմgk;_kl{i]>9kB?> ,M! ?,'S/m5z0Ƚ{q˪],|ô{`򫗰q5ٗt{(p#RS|SVh @5k(?wy'L4_Q9ͩϼG,_9}YoK/9 `VFڻtu,wt-i`9#Ӟ^C&PXzf{A]H "5ѳ0sd|75U[8X@|T}l+`JQ>s홑Ӷ  ;N|pMn=$j @p\gi@P# S@BY; L{O5**9wk[s{6h'g};Xl=C?i;^k&=T/d)pyĨQFyiXQXƩ@1ذb{mƹ9Yoh_3ΪF4DN·{qL׮8F:nd[WkdHgkb O?-yR9vnնixG:rRsmtv kI\y^+JRR`T*Ĭm6ծg3&_^T*}'|t[L?bM,"촏7~tӂJR园eN\77@TƯz&:@uΫw "?}kb9F.rޅvʹ/fdŮ;?_hy{vLaM,-ﶘlBB^wGN=1/}Pvfoo}$_mm ~nqqq:two2jv+**r-I Ol6[KKKEEEQ*{%:ݝi^@ ܟvu:]EQ2L #'vfQܛ?D"JB!>pBQEQRT"Cz' G (B7{%DD/%l~ ?O!G )DB?@#{F%o744-X@ '߮]ꫪS A{oxYM$b˒?QVyK^7dHذra@ mp?]=ϛ%ﴍ1 J`_Er?Qg ecu֨|Ŗ*^o7:޺j(PJtZMv 4ڜ4'ƞSDb%n3[?$>me dO5@楸PfG:{U^T2\61jvRJFeܰ)sl.Eeun.ʫmI, ݹa]@,H+Bߥ[D 8nkVùKN~Ua(wvB@دeWL d ݩ+]Mc/6ǵp9#ˏޑQ_}+v>C`ܓku?'y]b(F4mΘ-mԘԜuj:|C)Cy:1 Xn^A%r{ZgyXFg?H&c >_H4b3IYTn@U p/xma@"wuV\n^xL>[]8yb2YO1vz},asm(׺V甛W(-e6 2D=/[`s҈"(:ؒK[}c{@arLr;E'kM&Rm`]}O~ &zM6SO?5 Nդ㸖9SfsRt;B[b +W]Ȉ yѿ?{WmV2V(-+ٛ;ʫf9 rvf+FǢ(3'1*:%5xVƂMœ^leqRžsc2(uQEϬ^2iEX5XDZL/Gg ^WVĎ<6i>=qE% 6k*ФD~}nE[J; }q%̘_QwQٖW2e--=>?%8 l+Q3]pI1^BuU:v"@*& 9}\㞛#ӌH()]I~%&F=$xfDfWA6B /oխ /{/zRhJv@6a@_ɓC@B(WCL ,|Aeh0Γ0̃,nƂWosxpu' ܷ@]͏]v̕S'cwpZMUۗdh=<GƆ {9J= v6%J:%ް`5W>>1 z ?2LM{B&-Y ;vΫ>=3.4$WRaJQʂUaoهRȵajup@e8 ?%R+C+\ qc!O>C (Z@MB@E`DbYW4_]#\dTz#ttP`Cl]!-?%$4$4"^.gLkҦTǤ{Xf ,w!̟FaŨ,-ӟaܥ;5k۩+_6'І][ދlX?7 :^RXlGfG5[,FFbSEXY\3l|WF{&3blb48_GBs{3o1Om`K=22elB/#!OEL&K=䌇HR 7iܤ`_eJ޼Nwpޘ2JUpwo2X/vҫB^ܩ)ݻ)L&m}IC>P- }_X͈}TbGunDNGnP/F*t Le⦹<'tvڂMs IDAT8ˈ\)! oq؆NVB{7fr$;!{qBy+&tl^,:#rӁDhɒ%ytc6~lŷZ7@W".@.o?ួC!@{|'%iR9@{y<䃕.N [:m WX %FcMԊ e:AW~x%l2%Pl6V:_luaj zxk葉e:3˲u@/aг3h t^?N!FNpUN38Z*muueeeeeyvJK F3K~uq23" #^Ғ2]x5ikg mVg{AN(jUeŕI5)1kA@QJԖRTznau Ij ph h5ڀe1m.@Sb0%;0#IEMʒ2k6l ~LU[z>~ꃢE:ɣ^A )G En[Z S)jkS=>3!,39:,X2,9mVHf -阍]oNkύ{1ldilGsJ͇ώw| ]0'd/=J[1;~J^{b؂Swz|z$L3šQP šɎ9XYp`32>,88O|#U:zh%Z'ݗIQm%W;8%+ &9:[S5׉.@QB?ū߱ɛ>j kY@f!Ű,̗yN jD rَt.F1\߲hPǟ+Q.Y0cmОzC/PC{ qܹAugoC'DO\,)\^ɛ&\&HpxXB2ƚ--WȜ.ڳfs WF@ӌ]xl2,1 ϟۚgy!#hO}7X$cÛ, dd ~>8t҈#nn;wn׮]&͞>3g1bC\aĔ.+}9^>qgoDxgnLWl6\.xGwso|(U.@PG^:>"$kޜ8:l]'s ;gNf~kIJϼ~~ĝ͕㎷Rթ&%n/ \=75\=duLjItyE&OU0&ԜݯwЗܳ=PGq` %>u,9~ <oL-.{f`C$@R]Cď.Sۢ3-()͟3ir;qZl>nVl(  23 w䮏lnηФ )S/'?\Z7k-e]'Mg%u)}M/%w~C1"Ă9} #ԑXLz u[B<˚B=QJ(T7c& 8Nas#ϯǍ39l4pL!cZCe,_7?7c> uXy9i@ղ*u=cuB3խ TaUq=Q}zLcƅyw:P2|S@Q!ehlČ;>LӚ[+ X_4T `uhVF.qόLP a-@lFr//<r]6$F0@LB ;oZSS{oXMfԒߘJCӰnxrhRfg+ܝmc ',-^MW?CY;k(:~FWM 9~ ,x6nLj*T~*]84G ~C`h\'~N uO u8-y},2J׊"x VӃ"מ۹tX'{.#2 w%CsKΕ^5C2/e7/餳T/.DQt̺!6TMCޒ%Sl|`ȸXd$/ZXZY ^TA}b\.B' CBݧ,N;X:=Z VE(i ji l۲\o66ُ*FGM6o)1,I /2N7 > uR)TNnhMNNjwh;Y /evM;|''ބ_*:?I;zde֦B;MI؞p(h*Cs1{Sxˣ:WdT,fcS[O@ٲ/"$>G,W2M9 ֗-ɰAB=Xob9rf5ZD;,0B5ZөtM-~]'_$m?.I tK?pw n~h0pq͸R݁:}eǻpAB~ A )DB?@#"B@ S~JowO ދUO"p E 8HFhll4M9n8ã7{%nnnBl6$$@i]&z @ ;B@ S~ ?O!G )DB?@O^gǞ^lEO_MәJSwZYRt`طH W+;kz~o?3uDw 7}{YEvl󟣿v^;2u˩P]7.66;N\Yv G_{f"7Kڽ{R<]vWZ;|#C.KDVu>PNԮ5d\ {p,A|*JGm-x*;͑7/P덤MvS*ʸ S';~ $D@>vNcv0N۴oO)Je\k~=a6AL:FNcڕ5Ŏf+e>D>gЇƆzfmKM5>q3so-<ut~M´]T+ⷧ6k袄gVVp-~OtgV?m~r|BGN^I8Z;=Q(hGIL[L7{V?y#E=끺{VDi܆usW975)9AKsիF0hks=+fR,Ekp1kvw6t~[?@ 5Fd@Q5n;45[NX)q~rZ>eD=i~nKf0r4O/_yx34ǫlo 7<5XN3z5>< X(wcS6j$0}`-:jx iНj,=Ƿ6Khvd Bgk ڄޭ3^8>pMnMb_z) , $\nmr&@ҞK vwk?WB=0DX6kGۺ;۳1.Jq@ #lOӛicbþz;Vy:N[=ͮ5j/ڑ;^k8@3S_:⣳&- 3+'wz {'рFnĄjc^wq##b7`eC gڟXDwIi~6hJwwKCoV**- 7SGΝT~>?AJeYSv,T*}V-=>D wfwDmצJRjg1pYtNJo ME4riuqI5wocJzk1owuāih>FgfюkbshQ 9떭W׳wbMObcuԳ{X0]ܺ4#V@ sג?g_q 'n~Ʃu/^;g`l@wݥ`)n5jN#wz.\hjj ";eС޽+OWTTr777>&L wl6[KKKEEEQ*{%:ݝ$-@:䏢(L&n(jnnu^ʟD"JBh@o((T*Hn4M>pCQ@ ^9vd@ S~ ?O!G )DB?@觐7w +ohlr{m΃HpB}v?n`&`5M;gk$hM.<]lJP.3T? CmmdbU "-2RzoohL-6. ;u,vnj5I(K"_nġZlC=ovoB|_(<ϛL%K477~e]]]{iӦIC?\ճ>p7r6-vݣ ,˙3gljjڰaC``UT*UG+1zV/?n+{mȽh49><_XX8bĈ緿Jn;3o޼7~箣rpD|#/D"zdYܹsY-{ͬښj9Ce#n}gYjzxxXVV={={XV+q7`ysK~[__!nW*&Mrww |7EǾsqqv@og=^^^68N*l}xߑ#GBC UOAl6[iiÇ{>o1T9urUȤI>Z{X}J xc)"`Ǽq6OSz\Vk0Js9v],sͫgXPؔ{s&ıл.8=Db8m6Ch 0 㤰P i05f9jux[n%Upujۜ||rGyKpyL{Ҝ7, WS;1sOL.6yjxxxxxx̼y, C:=n^BsύKsb!C,\ lWn{N|u-TzyҜym[wqfSc:aZVcK ѸIn$^ozzojzȐ!}CjF-f؞F@z̼ adщv q /wG<59fSAHM8 a]hWSRwSdQ[oܗI; rGj6Gln݈)Cw1Tt<Q%X91e+9]7h'@^-NR/~6 q ^>bb v-)9y[e2\RdqӁսΟ<x pI{ }8%[K=tO;Pq 9"QJ P($}fpeA8Zn!f͊]I~%fVu8^XDI) [Ytr04Eه [aJf}ԃER= u.<4*$$%2UC?6$X_m {tǙagĮ?=LSU_do- ƽn&[?XnB0bV쯂Ǎ(' PL2Y `صk^ 0|8}4zB ,奁YcоFe- UX-QK 3VM(o#=;"yߙys^Gڜ]'H*} azl rrFόEn9Rhx+; K3g39l޼yhI^vm/ J]ʉ1d˙->0KXHS~kVF4q􊯴V+Q{ribNNNpO]UV.Wa;,,gΜ˗/4C8>>=oc>hHuc襾!$j>}fEGG}j:ڿ!dZ-־q9^1$IΝ4hPRRRQiZeeC!Æ ;jZJ a<:[ȭO6lXIIIaafKIIIOOE'b0nGxKn!ţTVV *ۢ@EQ7VkVVbB)tcӌs햮8E Zq{S߫DnjTu~-"Dq30 "R\ v`%1XJ$ܠPUKFřP 7Ygu>{22G}˲//*uxu&~LjĻ3)ķ~<JiQQѶmf#o6A~5544۷n?}w4mϧ߬yx|vc"/J]r`7ks !- Mvyȑyٳ/zcXbРAƍkEZAQկ}tÀac\/Gߥo1@:|}[o|uy3 B6ܹɓs΍#WJik׮۷رc (ʚ_-U>(5 E[Mv,_h~C3?2H'/m?~`ҤIIIIcJ.sB@r蔤bLoXv~ܪǟw_ة9]bM`BGh`_A)Ζ{.VGJTsg/ F>{34E4Z44eK~]gי몪3<4DRj~n;Zt )wj"/kN:6}1xLuLu̴ٟW+X_<~eX4P4P|RE邐4׆.8U& Fk~ԩPCNE2.cI'6 zw;e.§隮>!ԭ[*M>.ںq+%UOUthn u{g_[oh+yoh{͇ݪnпo7䦇_ᕪFSE][qc_k֤,h {̙I ״")p o徿hV{'Nsd8.aքT'TUh\[eGE199ٳYYYZusR\ PEƙ9 B@tU:MK(`]ٹxyϊO2w,Xܑ *Z[=?W!n^N ƈ FwN݊^RUu@k/7f`q:8bSv!P` d,\n;'0=꣛_|n3nl ~i̒*oLyy7@y>;@MKfn*IkKL4lы^3Ȳz\.HCR :Ձ"!ǟ={)vKkhr`QPt˺SQ9 uC^;ےXk[K!cK6.֫e/ç_jȝ:X5~zf!+=;[ʙϏ۶j;++U0g Nr(gݎm]c5s?߻ozº^|yʲUaD!>cC YVz۬5TeD pX D$0 $c`#G0>ȸ;-Po gq \z-cGjc B!"!"̢JL,kGY`~yވPSp%f]rLVWG3䱹1_98 RSuKv# #݅&4<[,UU)m|>H9 BF3E:R^0AC*kD`)a=ӤO_:ՁRN X'T$f3ah zg!!d\S 5 ]M?4'ާ0& =!ԇ!cB|ՅSlA%Q[75:l8A麎164|EJiL4]$!BAEN|ݴK]bLYW^lO<489̒AyT(ըJD2c0ZR__vcccz2BȖq8xW ?<|iǥ9Cytvj`!M#!@cS;A`pH1 uC1x;9e/J)!3/GcG6[!IE'(a'E"rHc'qDa#"$.?r/'KVODGX c'"E9U_P/IRh QkkkbccRRAl}hyOܿ#`|vŗ'[hoa-w *߅"D81`9~xk҃so^ 0ꑽް,7mor'Z=rVI 9K 3n'&&N#""ҒMKW%` C=@ €98ʘ~YL҅yV-I:!H<Vԇv}>i@q4ƢT$_NK65f2jii;&OaVƻ/{Tk|h2X, ck:Z,RЁr]V9Zc 5MQ~{رiiiL%rII{>2-[(rG$v WjCm Qt(ڷ˅t#y3 $$$Idе:w%{kL)k BSd˥M3!!!̾?zu):ǭ&if#_yx- F%0@fSStt[V. <uٓE FhVYYsNBȌ3?H]v E;vlRRR^y}}+艘X1F?RAC)P㕦*Fh?>??ga`=޽{MֿVz;Ǐoܸ1>>~ȑ596;ww yL)PH5]AW|~X7#6777555""ϝ;k׮ӧL&& FPJE/N8!bvvСCwHVTT9rgȐ!zc(`EEűcNJUNL{`栜IMn9둫+*O>MNfff?999`0GUU^QQQ\\|…:/D2-!|ttt=233nx1皚/^x^Q0g`A֭[ZZ1 ٳ>`\7Fvnw}}4C)1l4L&)D64Met:N,ss8.""bO7Tƻ%%%~3 c} ƍC)5fP``(;پ%7N} JA`+++۽{F0bZCv!Tɭv:h}v2=`0ǜ>}:?}?}v& {IskAq-K<IENDB`workbench-1.1.1/src/Resources/HelpFiles/Menus/Data_Menu/Thumbs.db000077500000000000000000000260001255417355300246200ustar00rootroot00000000000000ࡱ>  Root EntryPw%256_368db051b2512fe(!!9|])!9|])JFIFC  !"$"$C" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?xPt[ht(T,CXU55/T3IA<*x.1] x;ĎTZkl`˛?K5h3nilӈ 4ǧJ89S;+oY- * mu-JRoѤ^#D,v,?L*<[o$7_*xC0z}i-Vڟu 1O[RP!NWW`]V+ _t/u">'-W>N՝(Iܵտ:?zU?t o"D|O;[BVoߝUGӿ%??t o"-ioΏ6_D|O;[wN(V!i%.4by$@d T. a1X`?t o"I=nAp$\3fr?'u(qyE#0 ,'r388'm;Gk˫{le `&wsЩ3 5;eo-+< 4dT1sK/-BDO ,b*9ހ/ĒI\IrhƥwntBܺ#ac>!tR(8\C maB88͸jq] 2 uMs_ņ-¨]r2Wfu>,}B5բnӢ`;(*aI  oߝdi}9Qlt!D썵N>QN]Gӿ%տ:?zU?t o"D|O;[ _m=[M~uWwN(Gӿ%տ:?zU?t o"D|O;[ _m=[M~uWwN(Gӿ%տ:?zU?t o"D|O;[ _m=[Oz|aTwG">'-P@X/$ #6璧}ςAۑqYCKӱP gGm%(.pM0Y1i~h~|$MZ[MufϨ/F tl8L`r>nsZK /M#"܏>xǐ_io{$iRaF%D).6^21|j~2<j^rCvvD QQό)nex3ZNuV֊gݥvI)fwyy/%hG$W70MWȱ/w͞:7|`zo| lH~0n-қ)B%$h4љ(8l#ΐaAbbcvDDtxDK*6y+M?G^>0m07|`mXzo| ?Ebe(Pό/M?@tV'^>07|`mXzo| ?Eb/Ny@= ղ$k{@x\Unfi4z$s} Ǖxw} 3sj>'lϊ"յkp,,X '5Ig-o:-qA#Rm[º]iZ9 qtu@K M,(95'J][N W%ȗiFL0:溻}kƱc8b7499ڧc"/DӢw5Hgz*K Ɨq=*$e2Few8\R}?ax;zTo,jWb+۞#Ͼ 1]ț-Z-]2Z"RVQl.CKo+˨t:ȹH$Gx$a6$ftUʕ ԐUƫMsBReYM:\2oIege=C 1< bOo'>K۩--#E9P3>(rj\>COl5L@,2!@e&asq!ϗ}qARVQeuhC2Ap| F8$*`6l(AEPEPEPEPf<kVlʀ ݪ4ƿy"a真ɠmobX Q%,f 'EڒI=dA颷ӣhHg`h;Xӑ]y|0OElMJs~ǘk>/Kyֵ\Ijmu $0@ʎS _Οw5VyM,b@$Rd2޹6N9?Fy|zt%slCms%$F*F71$T/XmtEЍʮxd8o'h9?NOݒ4~(VMu_kIWBe3 2wq9^ Z lවF7d1?(:\74a真ɤ3ouxQ779+$l{$S CrTcx].+V% ISJv0S <Mo'h`sм3xo\:k6 HSJf0m!mC>>_K+C Zީi*׶pCBQfo3bȥG$2kpOцr&{ۂ|G-Z~M}yRqNVN5SIKt.6ٽ2$l8màR~eoD9?Fy|t^bo%Ņ[(D1ȶ27$ZvzLJ$ 3qByUFP̤O-ɯC9?Fy|l oWa2:7nbHnrH+𦟨i|o .'I 9eHP3Hގzs$Vy|0OӾ&Eo'h9?HaEo'h9?@a真ɣ <MQr&74QFy|0Of<k r&+GmmPIX^9צoBSq). p3)v&7*1\-ʢmBI6u D PhZ։," darNY\'y9I3[p&bydb 8 hz#[W1; xтG$H99@~!{{if=2˜3!fP@8$3ևNx8r s7 ŷ%ԝWG^+?˾?&5謏.ߨ< ~h^+?˾?&5謏.ߨ< ~h^+?˾?&5謏.ߨ< ~h^+?˾?&5謏.ߨ< ~h^+?˾?&5謏.ߨ< ~h^1R(ӵv)8"}36UZnjͿV=(*ё #1zdHq Utv=W,0,f?.1s\|> k 4a#B2ʒ"s;b@idr+dQ澽. '/ Gcv%E*USb }e[kv`[h9>m rQQA mQ[9T/XkZ:זuSp# 9];:|1 6VYƮH'YrT.3su@GSQr2=:ח5jroO|K%2?& Y3KVObSyoy猞Gh+z:HAE֏pZZI5IƖȒS,ZZROQKO:'lpdTFG 1=:Zp.5MA3Ų38F;T"˴H2!հ xV,|ec&;+ٰTĿ2&6 I4tlYw7Huxi>u5 =XlAF6HRqӑZ~qd$ 7G ?gc+5K"KxŷSky #fȓx~#U?{7WLq[<!fqU\:IhIx`M["o`71zjYn 77wsFʶx9NNH=8M-WSQbFVkuX2nIǘŐ! f9VnIz_.yѡ88yMjjRIvᙅNP p3ÕrV\VoukViD&n7\7| Pq;Dl@)/=O\>0`İn&j\B A l$3?u?-bkC׵jPm !o{$Г2<:6UXl}V$ݻFP٠3goal&pi(٨wSmKLFH͞s'"L a̰$}E{3O1E~(ٮ^C1?笟Pym k_ R0S-GQ=d?'ٍS-G}TQlY?Oz/o? 01êŨB1j?Э'}Y?OH=?I41kiT#L-+w2$]WG >?笟Sc鲬$ c }*yA\S~?笟T誈*ұnAh(((((((((/^k-,m^êd$8wE8|'1wU? F_.ۘ3&~f, k:n9ZvȞö(fFˏ,qᓀgwT|_?hZ4Եn-g<̆798%p\X᪜'ţgn8.!oyH'LB%|`m?0ܿ$d Q$\ ;ύIϑp3*k;nr%āA#9o!sψ.I>3WbXFdh[dJxG\oV=f_[?RM*8e&Ϩ=QѧI=M0ڕj(wQEyQEQEQEQEQEQEQEQEQEQ4>dZx 2( 0qP`i޽ٿ(OQzf⨢;(;'IS#ĊPP(+jZ}o{X#Bz֕ۿ wbi=/%kK(\!Β LPr ՈG0ۥ\P8FOsN2I(i%workbench-1.1.1/src/Resources/HelpFiles/Menus/Develop_Menu/000077500000000000000000000000001255417355300235735ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Menus/Develop_Menu/Develop_Menu.html000077500000000000000000000016531255417355300270530ustar00rootroot00000000000000 Develop Menu Develop Menu
The Develop Menu will only appear in the Menu bar when Show Develop Menu in Menu Bar is selected On in the Preferences Misc tab.  Currently the Develop Menu only contains Time Graphics Update, which is used by Workbench developers to gauge graphics performance.
workbench-1.1.1/src/Resources/HelpFiles/Menus/File_Menu/000077500000000000000000000000001255417355300230545ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Menus/File_Menu/._~$imation_Control.docx000066400000000000000000000100001255417355300275410ustar00rootroot00000000000000Mac OS X  2@ATTR 0xThis resource fork intentionally left blank workbench-1.1.1/src/Resources/HelpFiles/Menus/File_Menu/._~$le_Menu.docx000066400000000000000000000100001255417355300257650ustar00rootroot00000000000000Mac OS X  2@ATTR 0xThis resource fork intentionally left blank workbench-1.1.1/src/Resources/HelpFiles/Menus/File_Menu/._~$pture_Image.docx000066400000000000000000000100001255417355300266420ustar00rootroot00000000000000Mac OS X  2@ATTR[<xThis resource fork intentionally left blank workbench-1.1.1/src/Resources/HelpFiles/Menus/File_Menu/Animation_Control/000077500000000000000000000000001255417355300264735ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Menus/File_Menu/Animation_Control/Animation_Control.html000077500000000000000000000112371255417355300330070ustar00rootroot00000000000000 Animation Control Animation Control
Animation Control is used to set controls for animating frames and recording movies in Workbench. Note: the speed of real time animation in Workbench is largely dependent on your computer's processing speed.
  • Image Source sets the Workbench Window where you are performing the animation. Only the currently Active Tab of the Workbench Window you set here will have the animation applied.
  • Recording Control contains settings for:
  • Repeat Frames, which allows one to set a number of frames to be replicated per rotation/slice advancing step, this allows one to control how slowly the movie advances, e.g. if set to 0 the image rotates/slices advances quickly, if 5 or 10 it rotates more slowly.
  • Record button which starts/stops recording of the movie. Upon stopping the recording, the user is prompted to save the .mpg movie file.
  • Rotation Control (for surfaces) to set:
  • X, Y, Z rotation of the animation, which is applied per frame.
  • Rotation Frame Count sets the number of frames to which the set rotation will be applied in the animation.
  • Reverse Direction to set whether a rotation of an equal number of frames in the reverse direction will be applied in the animation.
  • Slice Control (for volumes) to set:
  • Slice Increment Count sets the number of volume slices the animation advances through in the forward direction.
  • Reverse direction sets whether an animation that advances an equal number of slices in the opposite direction will be applied.
  • Interpolate Surfaces to set whether the surface in Tab 1 of the source Workbench Window will be interpolated (morphed) into the surface in Tab 2. This function only works for Tab 1 and Tab 2, so one must set up the tabs accordingly.
  • Interpolation Steps sets the number of frames used to accomplish the interpolation. The higher the number of steps the smoother the interpolation will appear.
  • Image Size to set the dimensions of the animated image to the size of the viewing window of the active tab or to custom dimensions (in pixels).
  • Image Options to set a margin around the image to be animated in the recorded movie.


Animation_Control_dialog.png000077500000000000000000002035101255417355300340640ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Menus/File_Menu/Animation_ControlPNG  IHDRpom7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:y``nڬVkhhhǎjJjz/O{A+.٬q+Os#?lwu#!\Tls󵵵'1O;hD~ƙ`v}M NٳgΜ h߾{?:WVVvܙ3gX jMsMk  8 GmMv<CB&K'mHMwVpߤAKk[O|V+p5`gon9k==@7}Ckxr3We>=r%\R]^^~CV}J՞ 'O8$NZ[R֪ oMױ㕇l ???aN'0 hu:2//&Ue[ t\xdZ/o[ ujOs-c! 5-?MxE㻷0zg ϻL⩏%=[f;[FTg Qx?w owVDU4u۩c7*9ooUt:U(xuIz}Ѭ5@ѣMXO;LK`˽=|~KVfs!H2dYlMph8GX0' hNUCͰR$4go200aeeY8h47y9GQp~ZSqbKiPiD ;UuYvb |啮/іz>'fYOO?7 w??K]ɣ/lxqO3>O~c_[b7XOp&0Nl~o &->k / yˆtPytO'  S[\t^o>{ PJeeIl:X~k!O?=??ƹ/W}]Ys> W|̩#˺Ikx-C=\荳X>W]CFڡ'zeh_NŠ$9|os6zvq|< E?e "kڲ2<)yǟJ'~hOS^.Aُyr ƮjqwO>bM:!@+/D25ZA`=<xxaP[ tZF+M^Ji$Im,˲=:SbJ˪5*kE(TjCA˚ Qee~=h7D}˗vu'Z(EQTͳXyn< 3X]9]%aЅuQy+g8vkԢ( wFT``e%Qʳ 6}Oͩ]:@dʯe MGK3#"lB;~,5 Lr7^^7dž?U#׿w#)Rtqp޾2`a5MRAP @i(_ze%JjpuFt]f9oK>ཝ9z$ ;Y$,I$ g*᪶ gϔpz:$IտڛD.8,I䨲 Y*Zm{)J"Nd|uotז9_S5՞J;-)AJ+]2u^T_=zT>ox{LJԃad:ZIUudjp4np8jÃEWSi=9'r,Bu_ڪܔ0`BVK3䡞_&w[t2f|<rZZ@ Rv2.QIY%igVmKP j Fo*$yrDp֖=XIIT< .QO22}Kߧ暳@_o5~n׆\Ru!7 '6={Ysq__^FxxF +쉪*;zTޕv]Wֽ7 4@H;隟KլZj8F-CV]K =4.cM:1<8o#]"0$tzVE@ݤ&%^L#^) ^tPq:TŘ g{]c;<4, rט~}Q0+ڜb3\d3j2>YdΊۿ=Zwd3(_L#ʞJƫ!tKyyƄ6^{%#3 ˆo}X8(͏:O=yU*/)x.]__Y5f ~g,o~nq2}T_>nY4r$5YN ׯ}$yyyΝ 5xytWU*D0}j_u:e[s@FaF<^^^Vw~v:ȲWTT;o`d+!AZgeBt#(T[~UuE5fҰƎ&^5u 6Zt3056x'㪵kTWzTB%\(ꂃM|]Yз 3նZpz(9[qIYs)?Ĺm齼}+I&ŏquN!m8ԟѿ=q_ӱ-)B{0JA7; B!Q0!hBHJBHaAc݄BZ T!҃BH+ !VC !VC !VC !VC !VC !VfwW"BnpKo2ɵR:t)$""""0eBinmZjZuCPdMΘPZSsCB.1 +6o^0d2^p w[&dmKL&/qo5L&SFFZ%{)R/-Qy8_)3t@ɁJ#^[ RFLKQ1)(b⊴5&S@Ɂ LY.SS!4e7+ֲ9_2?gO{c2^:{yNaEEBߏn> ^1Kޝ춺8WTVqb$جJ(fPҊ8˫nݽkn=[}=3U}I_J[=`<;Ӂ B9_gKCYԹp2m+d}B8}9uт dDuBH8Mrp/[*S&#)}c,kΣN  װa®(sx|iaHhWNc(j/Խ,ލz4zH\~vװ -!vkS|i'f,Ϲw=990ӅJv9{oU^p_:y9@֏wcLP\@W H ^Kh `\X7 ~5ٳg?)Ҭ^-dR!5t]&&=|d[<&'yК\aow,tu(N\d2<8s{L&SؠS:߻:a&Ϥw-`eoKfQzԚF9pɤ&SH|ҡ[L&) 4B!aXN_VZtjV`x+3T6h‰J+AZyp:!Z>>f$?YA,..nQ",a|}}z=n7<&?Y%Ibp$hQj=<L$Ae@Ң(Bn}nR;eޗ&oVy;Wn>yBtҟVnM]mV + FsC|5e?}՗jb>ۑwYř+6IDQ>}z]$ԧ!R| RxV& fĔ֙ڣ ╝c-T {5œfR#qЧ@e*$,K%Iim3!%Xэc*!+_23 *@<5۱Uǘf<`c=?cf7 Y5<`ѱA\Ec>QX*oSu6JKMl6?1`elWu'3{KMd>{L{vDu1ê%OV| K೾XT RgvE֪槾-?<`y6۱T9: *>g?!Py-]?V(e]R!M`!;O% fb;s-(9wKyk>0%"X2S&&ƯK}7L \5e켔EN Ψ)u(,z/ذhguN?{*@\9cȏƯM]3Ԗ7dzv،w]3+uU9OausDkN~V kEı7_aȡOŧڙ:l߂W=;2q5=2q㧎"GxÖ}10_`xsNg 0nѷ&eR0_`;o>lI `I{Q@f]cy[ߍj=mJT1s7%<ud〔Mw=oB꺙D`' fxoVx[5?7 dV9g0RM1n0fɞ}x nd8lKNqM)͟,֤TJr^aИ !m?v 2oqQwK`}v+(`g1xѦus}{ɢg F9Z\g뫧'lwvp Wft'?o_8顋5+x=[{M\i ꫖X/B w}w9 sGN={`ڼ "oI›99Bd_:E;:@cDfқy_ڂ-CyCwlUy*_>q`l'{J* |uʕx~C>X]X|0}Iu-(]gEd{.5Ud}r%00># {3TB~f~Ĭ1]U ګ)Kzʕ+gΜ9s{ ð,04POH{A.Q9rru}5+ IDATuܩyã.85e]֫]Zi@I;W/쪃PT"fXtXXřS?ʜm{G LlȤh8&/Y3wTJ^Q1Cn:YsE]ywtAWsiﮛDzfيY iOX,4chtXxĕsLD̊ aS<=ԢWv-Ew):{Wgq-TT*J3f AdY~'8JY !mðS}|~~l]O!ڪ8o﫺HN_UUy[Up悰J׹ƸV,jwıcǢu:X>hoŝO.y>++k׮JJJΜ9ha͛7oذG}r233;t;YBZ@∈΁cn TJ9-BDGojkA诹Nk--\'C~er9NOOOR2dɓ'\,k].=f7hRLn2˫5<== CÖnBZ+nGn&Ng4ʜN2ȧ4YdYv:eeeyyyFwr C\:.""b'Nݻlnx(STTrTHeY;S^T*{̪0X7L,aBn}"p:jÕj NOFbQ&+8NtBne4Ib8.+eYOOOOOOwAiu,h&!V[BzB!Z*BZ ˰4VO!u1BM*E!Ђ2B!eBZ +灱BBHiJO'rc|JE L! Aj-ɡ+}}}kjjX8!N!m,˂ v__ߖJKRl6¬!De8z} r{_BZ =BHaA !tc r1B!*B!C !q;(6=Z |eyIyywʳ?۸%BM7t?4T_bZn(ve/5mK",a!Ӛ\kEy3;gL*BmUtـC>4q4Zt}ؘ]F3M>zW~EEEiq|ӈ5WuJ!,F߷ 2E`S~Dr\IVr`ÄʹCV@(ٰnË){y%NXh2[zb&Fz~s{^׏G1aDĔK+>E9 >B{ J$#^L9@WlN{kd2MXZ`ɴ3&Sb.$H[h2[Q HQjBn[|W?z?>earΓKc}m=niEi@fˠn=^\lyz_CEq/l> |YF1M(~qe9i +K)q9ǏV?<ʺr*,M[VW89;/} KfpY>3>{J~i_W>d%a]~p`[l (uiEaz\QV![ '.~L[W1.N_Į]9Y[' 0H:!ok>d =aۖ% @䐑kwP Y/;|:n'T Gp᜾_|}՘n~͂|̾mg0 s t yBPYs=qBMugx{d,kayؓKwSz4`쀖"'lzfɱAMhv\ 6ݛ\YmCel.X uQJX^_Eݛ!I:y YuqӐaҳg`,_++B9n`];g+c]>ͭ'_?KO֗uӘwN( gaȧƕJii_?쳞 M#G>|ԩSVU$cG j+h|;+aXFͩFcN,K=Zx pjҒ>ѣ:tY9r{vOqUUUѣQt)S4< $X|5jR ,榧 u]zmsrYÇر… qqqZA8sLxxuјH[V[xKkjjG&$$\\+W>=iADI0`{^w[:QlْsυacJjx}-^pȐ!nΣF[ιvyr\.eeY;u4dP@$]v(RB4InZPP'"àLq!C r``ٳ-[Ʋw|\k׸,A<==AAfҲJ/DQA!!KÇ9rdA[qɄps#[$`qr=߸ql;vpwK%7҉垸n^|`jk+BEs eY "0( g͘1CEj⏐p8vq5 _v.X ejJX􈿌{ 'T[(N탺J}$ɲd4cbbӵkWMa%J2S6BS)) Gxc "5`j; ҫol,ɑw^|0j%;~0_y,7\hIDQ>}O.J(L}B"I9 t(]Aɲ,~ѡsy .c0@T[Q%Ir\ t?//Ojīv 0{B¤UczX%6\&&&fd`Mн<%Ĭ{7Xg6Ϗ>n˞7U TH4ޥ|;v,88X)Jh8 4VdYkbٓ"g͚|X9)s=Wif׿]㠖Qp .\ߐjܜeYeTԩӉ'zផd, WJS*oSfl6$TIx,5yLr*;Z^QmϪu?aSūf1'=X*$`6'k>z|^1~1l6WUA$.OU{ 98,,xvj^U?)~8>/cfsR s^˘N*L{_km6kj/$Io.jplT*rokVj6fvvZms/S[~nw펲oN}2|[[˳NS6;}ivy]W׷ϮrQ|}}W:r8lall6[j:IQT;Oe s8;}F`݁*ŋ".6,ڙ}pݢFϞ 3W6Ȳu.߲tUd;ԊmYY߬Y0>9go2Y1x҅_;f1ś6=D0eW<`F5ЂzشejJ`CǦnӊRf l]Y)Ii9Ui˘`;2fm[7Ҩ<|L覻XC{'Rw?Trs#+ujF0 FiȲ J+EI0 THt:FJbYV: 0` 0 2rJFb/k6'HͲ {xUyVn߷oʚ租ay쫨SS􏇇3K=0/K}O-9#,ڊ2S&&::ßď_Ք~HqfeT*J6Z*u;X8h0]@0?oyDLY m_ iѻR 7yV`JfQr ?&njmw8Sg e$t9ԳW&4F/1?w"d&=y@ykB3&=+0jS{!`ENh|^A* #9G2-l'ow~ ؗwR0 MlQݢ( K i.#///ZTjIS:oDNWC4#+_ڂ2i4B.[2"￟1i_@.ز?^ ,v@~7?-ySDj|x?ocK̼-73,x%A*W|ЅV  r7'wPYYk?3fČϴ9:Mi,:ti]vʞD՝{u@Vc6֧VglGD1nhg b~rT5@N =56i%\k`73\Κb RĺycƮLYk$<.WK+#S*~c À1҆)gae%Rx ̉U[7zʀ,l{7%ê\K3` T6i}:`Yػ,^JRGmYZ[wKOvYJ # HIJ2_vל,NrTL^Bk!7hQM_*_=ɾ]0p);oCsJ\ŵ6u-?mw.bfLbIl VY_-81e`3QV^4 sD+CdRA,ea$"m˲UUUvd2]befJօ0@;s׻V9f.<}4Z#y4>s!ں%Z)Xr9J4N}&X0,R.ky40 Ly_… n[ެegeްU+!;Jm¼y< IȪY`Ըu6pұKҳlcYYDzꪊc{VDf*P?6'/5eՕY755֫˥ =y6[ 8bO>I/TlZqzߗ@Tgkη)@bʕ3gΜ9sf||{ahQʷp4n%zoQT,FŲyЬϟL3h *3.yM3R4{//߳> /䯜L|(Ԑs])8/:3v+~XV鸻f5̵X ^' [gRQcƌt>\7Z*jAAAVU9_seX8N7l#Ђ.KkҡC/p!lgv}V lt2龰wrzőr8wLzoT&2fJPPۮƿluo6[.żV z;1F_8j9^DjA_L oo=*noECHUYUoJ0-]$oւ31cǎEGG+aw>e|j/_r5(pڵ+3gh4y 6<裏=Q].WfffHBnAN8yqzxx(ieo[+y gv |36(X+8oXYig1ɶn (:߃ҥ՜A(..C.- Z={7>a;<_ x{{7q",5"ʔbjM٢h|Ҋǒ@V:1 rNR 2ɓÇW,veo+I[Tۇ?:NM^WnԴ"@E@2Ñܾ}__MSa-ZA꧿V:^jM=OOτаC(aFc^\.|6G鎮]vmmmm^F.X[!qN3yyyeeeNSP~o,;βR+tw>qD޽fswT}TXTHhBBBb Nw=Ąkkk={۷;#:::((3h8i!קsVT&)66ѣUUU a,n2Y¤MSwh4FDD;vp\~{aZֽ{;z{{GZ*m,..Vխ7ڰ,H-hX,dbe;0t:NF; qڵ3 vnx++ʌOOOYw>7]X,,JdYV3 Bn ɢj}||DQkEJ; LW4-N{K)&ҦTBi-,/B!Uq4BH`e!2T!SB!Wě.B!VJBH+ĄBZ %ֿ%!9ejAhaBn 1ԲJ׷eYFɲ,nmI>\ [*^^^*flC!8G׷(mG^ ͢+ IDAT!Xz !BU !V2uB!e$ZeB!u-RL!4`%!VVQB!u*%T[ -%;mohr a{ok4S~Kr[%&SiikՄڝ)JG{ ;I s%$OZxP0dZn>eiĚ:)lz|H K+**J ωh dlWDB8 ! bN%hPPr`2"eiO{ ~_ZDɴ3&Sb.sYk% &()_LuC۶5d2&VHsnp?g 'RDH_7A)` 'k6%/xqDbZ ɴPeSBˍ-Z13# N/ PjaEOsYu{?jzO)8?vHbuu9rlyz_CEq/l> Z1gX)sv}Kjv[ <+C ؋v/?SZd+nIe`/ʙah2BCixߛ6`쒌CG%̛cqo[ޓ:?_A" +ޱaźL/. |@5}9M5pf-x^@!˙/4KX\~gg 9ur?Ϡ۳ck&B#Ո}{[cdAJ.|q _؜.n8zhZ@ػ ~@Oy v@[ ̈́cS\9[K.˨ɐ 6 !@.>,V`!eXCwGIZuw?~ Zq_€*끉FGP\ r\/ps%k9nj2i|n=3w! $%UPRcFXo-UlY/V?m)Kݖ=Zb+eJjzF-*UB5*A" L#ADP|d;O^<\&Ҧ.U+G͐lU$s qw`!Bd6?83*nKBP.SOnyHR3lncS2!mb2&ARF`jRƦA)/ǝdR6QwVfeǒ~T*}F1m@{޵}bF3T^JR‡oBz4_3؁#>p>*+fӅl#-!1H^?9vz)d͎8L6P(^ Yz^]Y̓){d6J=P0M0RY2BHwv|իBHqqq0`@>ws,:vWEE˗ E=D"$!OL&mbi vZ4yR:5M<ϗ?pBɩRK.EFFԶ)#ۘMDXwxM> %zBm8X,y^˷|#0<7f Jfǧlmg(Y1+~FxHȶWϊ_ %%eqbCG?A x5s-:6)#X :jMinQfəݙcVi\_/&ay)XYXGf W}]li,cC;23r+kYUmjnѶN={{{{+|R#7%_Mm [d"m|;l_jpDZ^{3#նmah[줔9c|JBt =nLAlEɈ2- )*UvuFs+/#vW3GYkṏ?9oKM ndח;[?wX@:-tTj~9[Ԟ*v" =`5#{{TImjնW4zcWSc:huæ 6I .oKa݃ލ{uj(s!֓D껳 ۟[|th={Ěp|NYH$ Àa,b=XƦiWP9|Vj2fVL[7='bj۶ڰrN&hgFm利ӡD y"ގ/6?HzS'WWN=OY7\x)HT\R0m~WDD #b#jwO;e$l۶ڰ;6t5b7jXO;5c#ɽhKvu40LMܨGV{7>p894{~iP<0|p%w{ڶ6&mk(S`ڡ1:tftE|XxdXt'; 7~xr ԍS^{w 1*(%mVvrRJ ݿt[1=hGIyؔ?G)ٸnI؜^Pq01(bbӂq y80C@#}}'9psOsX,O=T\|<#1fC?+ rgѶmjnҶ'‚Z,+}(*itPNzt;bL<|'9_uwM}Q+^ 4zڵk׵V]+vs7{eꎭzcoi[;bX,GGGyb[XlvH~} ڶ6&m{+N9~ҤIO4iOݟvj̖:=1Aw%Vh4͗TR-_mCqxWU$\͛[{S? pY߫8Կ.dD"//' f~vlĴ\Ú=mVv YYe~ک1: sSjtNż̳dS*G'gad4]\\Ќ7ʕ+."d2uiʞm m[t y<xUxWܿc,[YYy޽{[$!!fYUVVuvp-Ӷm!m+ԘAҚL&suu-**yh~G_Tb4o޼YTT*uਣ_%j[z4TiM&?~~:tJjk ԩSbxĈ^Զh6&k6;mcbXmՁ5lcXT*Gqҥ?SN}}ϒ&;9N1%dz}:9 bbH$<2̷|#0<7 ,HR;w~*$O\dbV$2?~T2w`ڢ0e޲%7]KvZf]tz=eZ+?#sE@}}W72hH%s2&mQNoD p+:yceyWy\Vjp^`?Oh\/7?jG,6.>ke.0|;dU2͂ _fA2%KOy)'H"aD ,b|,`^˯ Ah~0 k_6Pz>?|tD5Ҝ)gV0ƁݵƟD?UI T^]S-WYө iY=e6 IIIe\qws`hW䴽BC£bn3Q/,_>~q>=w}n8z膝FN=Xd}(,OX9JNQwTVb{xUgnu8zDY,%lAkA2YeVRa~z=_3~aɌ#^cgYRy^掫V^\8=qNs.}GzR^M_<#j\T{:x(ws C%m>}~K GT*Jx}~ yaJ] ST ן(UP60xZ5?gJXSA>!լ Ef*.V_?XOI;1͗oMk|9ڦ"g[7gG agoFF&y}U[ @qv*)ۀS

ػel [y,R:b"X_Owr3^_yǪ9s5g9׮^{~&brUKֹ$3IJHHJYAsPlӷEohfwV}Ú`a=sI-x@lL{f[+4]{[a}Nw[HAiyShqwMڠe#ҞOz}RMiiiq UyQsޒj߻nϾgR%Ϙ}u-CCݻ7MƎ`^N8߉JL_ioAq9ン xyƳ(x}|c ":}KWɞ l'b@c oOgV?ebCzrz@tl}aĚp|('u0u|G8H$a0bf,M( ~E q۷u{2KeyŨX$bD[Yf'쮎Kqvj֚.6*"i݅.· #1~qok7x)*UD͚E9K*>ꮾQNRtRvUmuC}ko9:@ZzݙۓB=,48 Ouʩ\ )9ujॠ;pJ P]y 0ax֤0H$bDH$bÈIYR~|ç9]U}{׷'f'[If'%X?lI>PfӪ e(3cɎ muRp}]WdzJoSyu~C7H-/3iFa>25ss2P^ޡݛ&7*˯06tO?5tc?yhC"bN7 `і-a`n["|6\4`l|j89EQ0eĬ@!!src^XZkRhM|%j6.&~{ a @`54,@X䝸xa:LtMqF=<_miFeLxouw'@P41;@k`9X,1-^$µǯf%I2έN?SpŇXa\]](724o0y3,7ܞS+~ki/-=<4Xi]󅃙@Xߒ"omj}cӖ0bHah\+.Wz"d.[_@~Luks۩I]ho;fV )chh'>D.1HFs040ޙ}1 }i,,1.'(0ϦԠCjX_ַ )w45 <#N=HIAgC[-P7fE~F` 荍rVҴR ôkK,un.7IE"1cޚ׫E&s['۝0c30kR#dmwqZtomK45ĿuZڿİ@졯 84,醏9mEJbFmwCQװ$D:SQ)6`Zi@o) ڢ& IDATFbE_Pkp57oWkKW'jjB2qq@ܢ AWZTP}E u!AFpySge m&i9Yfj86kF0?޺s KrG-oK@EAxE?8iݍS;lɲ ݾ?9DטFL70 DUFFF||||||\\ܪU0H0qqy% ==۷O9gC!pqR8N*썱P]ti:_qwEF>|RMɅ,Y7q4MkzfL"A*r3veJ0LgkF6ׅiIZIi$?{`%Wc' 5# )qaH N0 ySCJe V%]8hlAaNݼTbnjm4Ȝ<멋&#sAԚB;E **=߼pdӴ &5#Yrs'l_\0>?8*1MGfxy7g|XiPwqX0>[p?_]ʮ]z@Yk%|]C_>>J3,Pwlջ~E#g|skg2͆O'bX y[obuf=ν\]<=={z2}8^^oWOR!C 脵qώ ؟iSFz.pEZA/ X:V>uEN`jmvJ4եŅ9kC/䴸1; 3ӎ,/ZlCCS\qcnh:`]gdzpoh-zmR '֧LMlXsK-wV E#<coa-E5k7D%55v o Ei"R/hfVgpb2Hmsp \,/5asy^7iKGʥK;y8\Inӹr^q eTg⣙T***^/[ k֬;wnIfNHNr_JarwTpmtg׃U[i_פ}l,pzV|=FmtCI<_VV5P2 rV| ~'R,[YYy޽{[D"IHHh5TVVݿC'_JVV}QLPL2]7ܔK(b6fbRq۷ewy,PR!5LZTTd=ab1Ln***ruuّyf[W5D Wx}hPR!]jE&?~~:tJjځAќ:uJ,1l a\]]{Q:!%Қb^ETwm},J#.]ZC$_T2vJiPR!ĊdY.ad,c3j4똏^?Fι,zzzY&.geYL&D"MT (+2F!U͕'`5^E"8^ĵ^1 T.cl\ _%*pRBb. FK7 -%LWB*rО !%b%C"E.(Ο?jX0\HܐEIQH  ^u?'V$U*$IxBJ*qC妎7!A@0!=gBynݺ1B&DhB7S!0T!8 %B!〤BǏBH&T7!G4ORI4EK!DSSl<]V?yl d׾?u0lꅄ V[Q>~h;. _Q*7]7O4FK!D/vJ}4н?U/ZT*Je̦򓉣JrTbfe:/<}j/5pذA1$Ęu-Q*ʘva#U?k:R~V BAԧgχW;;0b[҈߿4*qVGW&?qsΖhJK_n|4Zmɼq YCǃ>ԧC=zl@sSŞ>{^N$l;+_{g.36k b=\7̳fP{]էQߖ,Yw\ݼ6l\)1Q>-'9Q<x]!@??DF BGZ v|lނ7ק1iҢÇO4+xn(Z@6 9@^-mxQ!ݫB!Ug RT;nٱ*J?&lL22Ԕ i1zGl =!ׅYzosz=Y/9ge Y~ shEՕ.]!nBtfW@!}BH7!:tbqT(B\ELt.=!t'Ug Хɯʕ+ X, .\ش.''ܹs555M}:x':;;wr覹$I'oh4?imm5kSSS'/|w\\FB %r}}':y:Xٳg7bX/ڷoYf]ロi睝y+j>YC*1Vƛ~R:{`լ]WΌ)6!I!1vіm_Z}d$ a#xāʤx\qxC$hft>\%ܴ!,n .ݱ$c׌1*5`EQ<}沼u+p.+58c}ß4]T_o~ nY:?uy,»❘͂ 0eKts,RNE ÈXrXp_ |Ż~S+m?m6;#BL91r4Ԑ&9Jx=:-KCCf/lzKv&&y?U; 7?KJ.;y0{RxHph)lqd4}+O?Y}{UGţv~=:pc1~?a(;Euf X,sKٺbqς e::se +/z\gzZG(0βVW$qz'GNs.}GzR^M_<#j\됀/586mQM k"C'\7w4mLH~gJlfaF[R[Ӝ`tDJQC=:P$ #TkI{(R[UnV_>F쮯f 9tsrR%܉Ò26_fZJYki{* $;V͙9ˑvܣ3^tw! I5aj $%$$ڠp(6ۋ 7j4;8uyԗI V4摘Ú`Ԥ;Ԥ" ,ft^mg҂e|H~mC=ŀ&mP2Eڑ‚mi'E}➝l6/#q}[vop:y &tR-{lc:oɂDMٻGKX0~vprAWo^H@W߻fk&3VO?5tc?ypL{wO0;(a1pHAMf$FŬ/ h6>SUoD4 vݻ k_ oOgmSv.1:=$W_#5%B(0Y(gqtc%>xkdR7W$E'e+^X:b64=_ʵՋ&e$EeY+[~]'T2Ct nW@]X֌ƦnO >fd Oct n7ÃU*ýb7%9&4OhsCz` 2npi{/$E 7~, i {gb<1i_]ãۙQe$F͹zKWoXcCf?mteOxZp 'e&H4LߖGff8;8rN&;{$'[%{;r$/Ь9`ˡ#[?<F@[}]uɏbU% rsQ-ƾ4yG]J9G.AKC?Q$ n=Z߆j64 8&cM#1,$$$t|&Ь! 5[WlW$yѥWsE_@{kӘhڑuF|h25=paX({;wIX60ΩH"gG׈i"=~]݆6C.aeNb'qvnT$P'OR]wa1`#$o:.\d5Vfw.UQᡁ^r72ܫrw kC&5_8 e-ixO`Ș1!2)ק6|'Ce@ޔ@/ҀeFtW]sF+$i%K% `ȄZ=Խ4 x_LWgkC-5h/(m5Wy'DQ(44z_]/ɔ^aȻ3p[ wtޝ={l]눜"v@"oL?c  5Ov.KA3y[Re$M ?kAe'}UƁaO7|8mi+U-y@O> v^ 5';WmOCNM*gk:Πߓ/Ee3Rbǂ6iK OȃSr* ]iQA}eSg'Nh7$ ]Xu9:A)/stw73ȘD`i sS *5_qy{3P?hO,\X%,vr[gO߰>qj'< x8YX;1xs볋u*###>>>>>>..nժU`F$bɸ<KnWގɧC^ سC졐y8)\mLk){ _e2ĭ/@Uc^tt%֎.|쥚 c/YOݔ%jľadSv2WpgJwd#[ S]y:xyltjR1U&/ h)wMD+ dMNL2(8lWj V%]8hlAaNݼPf(+νqaL 2izk޵W ;bVGQmӖ??8ttg8UO:wBA ORIرISC =~-8Ȧ3_[]x8*tĨS} [lԌ4 c|ƳjZم_v_wz5=Ec:pϲuV1^{[-XTbX,byȸ IDATX:bJ_^.Ξ=^=}g/NRܫS)xB57O@fGD<;.`FzM,&?DV< @.梺0gM|e7枧"Q1ӀI)h'@8_r`h$"-i0m?]&WINN~x}ZRX8֠y(zy8eL08@,˚ zUSA :J-*;ݐuT߬,nr=bApsoQ+:`Z{,.. Yh֟|;/_?nn9+`7N{c/jGz(///++ H$i}eee+:AL&S^^w>Kq8d>aX,k,zhʌg7aLv篳9Zk+N0:C=HR 7K;t)&P+`l 1 l,?C/Rp[}G'Z ŭ^É)S3֜R˝CfȤ;Ʀp`kGiDwv͚1QI Gw]~iÛrCQڤt5 ٻU>&Lkmd g5 [nÙ=njy{RvNHL BP6ui?;Pw儝\*3oP^;knWܖbnĞ+٧+9M FmCg-jK|y-HvrW+}AYpm˙W'$oˈn_CkǷ7K.Ԙ2x#. Hv$UEM^~DQO'tؼ?̣M)_>[Ք,oB)1o9 ԰G^ WFH[rN{B9* <p[Q}RzRbF dah'|4ΛB`ݿ37]Ӗyzz뮐]9| MNj;[arqҜݕdגSfA](ZmJqӞwЧY ؒU󷹵?̝@qA/A!KRlƅ;u|cEVXWqskVې&iLZ@<1M/l}"Qny&c1w͂Ȟx5nm'y4l֝7,Bccl66 &DMzIM?vd h[2ud}-w!qе穰=⸮؁aϱC Dw?{/] R6x߅j85f\C@B̫wr=.caΟ,o]rF{C_nuz^:qs}rȐ!=YJŠi}>.Wta !k?;% $xQbE+%T*vXEESJZ=ZVLU|꾴9WjW=NZX)*:m * S&QD!D"H$ w7Ң뎶g(H$T0mlHMmB!H?CGׁBH!]B!]T:B"4QO!ԓ2@!.@!vt!tҏ@!z(ڒr*B:\m]h!gu !B!m(Bq *Bܦ74-M\.eGu#TۈL*mt: :4qv[kX~0Vz~:Ӭoڽ舠NS(+̷}:"\քpH{AfshӉۮ]ayyy dbؠo j:7qqgLi)Z˳ tSSW~TW'ĭ4DD*gV5%k~;+/WT*lPSS"e\鿉K]c~eǎl>lGͳLnO E[F'#2Fgț̬0wr۾j'pKu%C(N^y{(SAR9NAxw8<ϋyAMi|mxt}CBn9 EMϼ1nlooېאU˩C=?n>˝By[YO~}XdeffG_xy$"wNc;}Ajf.MI͜t^ids(=XZ&" ~/!t/? ޒϖt6{uB̞6iҤv+O;H%H%ꬸ\.t:x%f%+RR6o=DZ)Eh[tԈ ca{+Mr}T.TOo764*Ò҆Pt*# f%%" h!mD=ɐ?'wG|37]v"4o0)K ݷ{J8ڷ/R dŪc# &[4ta7;T(Nyt N ~7Wu3ZV^` mRxQ]Rh} -B}uKZv]d>}ceoMZ[xY!sZ(n EZm,Ezj7OKƛKiKFDi[sWkuGI󀼔72`g\$)bf;ؤt1EGo7/*=AM9*ɴqa]3 >}itMV؛{ @so]5 g̵5Q*e8Ӿ财a]D|`m]22%Ɣ%[Zlds̺j,)p[nJ|X_hOxn?},k˔ƻ{K$䎤!Oh[xzzVUUUUUٚSUUUU]UUUuu!uڴcC62V< HIT+чO]SX)2beK|A2j>g Y)_Mio%g,޴D0q{!PA)ٯUd/>`Ju‚mSh2tHc%zu[ 0\xuCfrܜn<陈WinZ$n3WM^9@ +чm 9ʀSRo؟gCiiwJƛB-M&筜M\{ {6e괸Y 9{MoŻ_@>[Ղxn'}`>[Nə5WL#S2/۲'?ж-/yz!>|MY+24BU\P($L&. R.BqLU?x%b"W2C!C(bƄk8`ShK dHW[L)D Xs4;6Pi\oR"$ !#/Pi\Pùb՘yIq֞*lµaFC؊cys2J20@lX9tS7>A[젖~!x~2[ Q:VU5̾, R!WNRd ÈqKs*.=T={%x̀aJd@ f>xEV`{y6YG]d;))Nj`ld@Ɯ،$^sYȘY.o/OIHS#'֟{vkяoOt:c$jM[j\^}Cb]vnR7g4sC7w[8,miFq)GA/F9Tw)|`xT:O{>-|[ُ%n.X6>;rV\GHST<<<<<<J {*"T7Y^{հTF|\sioMtx\ Dן?g[dÿ2KSR2W?JOօɷ.P2 q UVvZ3ODzSrNYĠRwwO-]5>r¼JfjG d5r*j@@Jͨԯlmط|هJ)f5<]C}[5rձ79ہr /4ص[[p o;\(K;t[ XS.+JRP(u Eo"? 3ȚlY {ÐǙ{7_ 2:C"}ff~޿aB* sYl*2u/QhqT/$im6KYQAlWuq=A7> ˊn [~&?YM}ܬ;o+4ͦGKeo3-lfSI~~ɽ]z*sэ 0'?췱L0_-7̷_MoŻpG7o-(1[l6slo7%l_9a*9VvȚkD_Pf1=jB>:%;>&ӔZǂtYSH$/_jeZľ͛7M&S``x4 8fZAq9#3ͦU~8>b`>F`X5s֎D-thVEˉ)q))˧.|5=)aKs צٯH=mt*}qH꣩ SkD^f0O;9%nNl$jqJJjTsOZ.6SO_e'gsf^X\ޚbV iSĮ󙼪8h{4ShAAV2LJgA7{ɩ+=0{%æ{c] "R ,93/OC-k &?Ő$k$eWmXcNmju){07vxq5fO|jr[XvNV7JS=,^H233;ѨjիWGEE JZ_.gԠafRߞ}}&V+Ϩ}X,nޭڠgaTjUj)ot;fGb-8J Rqi&o5p6hfd{;͵NdX9},{IWd/m *?\;vX-DZ~嗃Β֬B!]/_:`+88x…W^T*oi|mٰg{XBK"x{{;n 55 \B* BBPP!6T! B!nCABHCGׁBH!:B"#ut!tRmwߋBi)xa!!a BC:R:B":rRB!Am *=!t%m>>][,ǂ Ϛ5K*u\D"k̙ׯꫯ&Oܩ[z;GBѹ.S(K IDATc㊋Gnw8UUU}W9[F#q[mBHK(t EgoBwPM7Wx.6oNS\g)nBH$={vȂWWUKeZ86=y)snc\E[>H7>͕ծn|7'jΝ[Q7xyﲰfo?~ej_~.,&JF8.NAp\\NATDBtL\QZtƃjZm|zYP>fH}EvI kk)i.jڍ wI5vI0s9nGm}7@h6M_ 7%>Eo s%Hۚݡ9M=U_7Ye5ޖXvts|]e=~ pc]2bFٮEwV(]p`[u;B!βQ %ҙIx2_7RM;6dC~m!cӀ:`Ԝ@%}е9"#7Y$6sF*ِ2}}~d,/(F\rM{ MΉ 7Y>rJjH <><~*ʜ_EpԐ+ч)q=f&%l,l9GOO\T^B{[hP,RPg۶A{zg[UT8MbѤA`h` X '=`Qxnrk\~E"POL 8;l+0uO 'lĐ@Ȑa>29Č pTvmigo22&"T]8)Š"HRD*JT"H%R *tfyIq%2q`;ka`pf oJdDDxD\ /S9Tx`KؿmUݜ?nŷ@G |VSK 0ZnN2M\G9X0zsqY-*CY<6b6>՗E><x4.]z̷n{ŝn& I 9B:\<*Y L5DLT-u2n>ݐ'U Y ?jOn%l}/n P{ZhN\4)(,ָ;W.ipkJ`={֬,;X`ϞTM3n n - utP<0`T kF2f)/on{ÐǙ{7_ 2:C"}ff~޿aB* sYl*2u/Qhq?l5qBT_ i\F&wxViRVKm\Y` ?5~7>ٳ{wOTz tl'P}ryq'̯嵚8Q/\p… ?#H$T"D=!t؟IcN)>Wc_0>9230yOi8lZ]#Gid%m_[3wAlMf){[4ϼ,N_|JxQӑ4̼[+0%d9=NNd%Ԝ兩)uOor@㲚krLSfB(B!8깙o2?2?+v<4=`ׯkeq'Td2D"?uRW#9NT$&MFQ2W;l(|T]b2BjS9^k OZ,VFjeE'8p@ x-i7c=w`=FsF^q {.Ȳ,Ύg~,& (eYJ8z})eY6jbĨ3_ڔŲ,Ήk+<2gںy;N5qĉ'&gY!󀇿^ִXvdF}etl\{\qoOZӥVoЛo͇^k)÷ $o[;zJK8ŮV$9rP1+B! JVr:ѡ8:sN?%Çs'ٴBM:x+ӗ6P˲>1ovtՠ[1=z?Έڧ[2wʊB}ib|<`?< Ud%ODJnK=C Fު~W/UR5ꦅMH4F?vۉAt%#zWMP*+B!s\R]޳s' } O%f}w]x봟77_4hР>s']moÎGŎi_;[3۾ U/\wTfW;.n !jrˏ9n;)n83::_Wyyyu.ޢׯ]65ppo.pFsf|rIBXnV__aF0-Bq;?~<888 5].pz0`@AA/~ RY+`IX ""nVmW&)VY#R?,8Yj S.,*ͧ1yd_UPȧV"L&SR-2>msQ륆~+׬Y9|^hP{S+ N+F\rM{ MΉ ӗQ(~Ywc`}T#r/dKB7oC1Nj>c xxxxxxd2L&>faBѽ{w0@v`Ɇ̔ECF``Vi+:$ w(eF4'iLFT PL9`Z4ӣp!bǎ |]d2ƅM.g^=eT115AڠwHC(}%f:Q#$yHrxJ\.2LnifA&.҂Hd,1Kd c2ĆGGcW n *kY\OB'5Agxt׮/5] ]燈[o6W]6*LA²tN:N stZ:iNE*zyy]xn]\.˕JB/\%6S7&?$dLEf!W#Tk- L]a9\?T@%񳑝zlh 5$Ƶ$&M st:^w… r._bŊ޽{7lfQsfO8je{~l By<_|988sh݉˱DEE:t~ꩧƎSO(**ڷoӧŲl3*Jkg*;_!T:1!6ks3 ӻwcԩSn 5Mll[! aFӭ[ЊJ޾ޞ^E!}TRz!.H,ӝT!aaM_4E!] 5Bq *B܆ !B!m(Bq *BFZ5 eH\qtt!t̞cJ<"֬YVAZ-R]]{ԩ5\.ǍٚB1]G;P\n>}iUUպuBBB-[hg˿꫿/o񆗗]s~K'`6;hjJk={gD7X,Ǐo@ _|T*gϞ]? ~z1yvAe>~lk BYF %6\]jD/K6YMοh 0(8oon^42@a/_V.K$H$7u/侾7/3ꖙ}Ɠ{c ykPq:6횴W%:p_n5h?~2ߐHe*c~_t: 7oW6BɇSRtKAam+Su99 Jn|#.9c=&ZĆKlxuC)Vmٰtrւ"ݦؘ߰㱃οpx @M|nO/pJU\oϳD*JT*H%T"uy]< xxxH$qY'thbZe=|Yon^~2)#02Lzyx{=}{ZֆAEtr<ٳ:lذVO؈!}!C|EK͋\4&L Kyo*uHsleX cHs&%nX 1wFK|\V;d41ߺwr@P~qM&C%-m 2[?Ӳ%,*Wk,]<:{rSUK.I3LFƽ%𐠿wuӫ9p5_NΪ85 حR?y6gmP!:x;3(V@ \Uw`q u|%l׮V{ݷ{/l @*qDH{yp_o 8uvh׫RW?Oye<>^3B[ vɸ_ RS,ߴᯨ- \1}^ܦ e½ b_7ؔ6n_dr5_`>'ko(Z%Y*w܁!/ | r]7/?;,ugVǵK@dGeff:u z'.H箛4jAk#UHK#]{*RTVzYt Kѽ>އ+cɻ{+2N-P L3&}i$n,4m6KyY~uB 0;Vb*M3-•9y,e+uEBg3oi|\.pjL?9e̛e'|;NՇ?_=qj'૟$:_e2L&կ~dd}}yog=pԜ<cV71==?Z 56;n[̖) @NO L^@A|isćfU1R H}m^K7/Wo 5|t߬'>w0!,Uco}R2:6P,a !3Jv] >ߚ߻>3<~k݌xJ&..nǎƍ{t:dl{cwU턧vFm/bXBm5k$$$w /}t:^wRīT%J%p*Win@111 {*65+0JgǧgMs6K >*e}xG9/))P*/W?-x~p?=8 P^^~{W<<ϯ[_iX C߾}5͝sV P7=D%yC{TR)˲QQQ.ס7YcٓFEE,|D SffUdJOs'1Ju&6OG hgڿO'RFps y0̵kn޼cG^.'%%տ yڵky]u͵ݻرc{}ԩ﷮=@.k4 3R]TT!r87n(**nM{KH{jpeFt-44g}(JNwȑC jw<d2;vL& > grOڸ2w[~!JU*W=մYΰ)~D2eÇ R_UTӧO`e[s{Gt%m< wD"a&IiqWY3v)0ޡvmR*JGzټ *40LZ?PjcΏTc !!B!m(Bq *B܆ !B!m(Bq *B܆ !B!m(Bq *B܆ !B!m(Bq76{BH'y[ByB xW(mɡw~󫬬J tBHrxkK>m *ݺud6f1+B!a___JզL^JJB/B!nCABPP!6T! B!nCABPP!6Z{N֊kVjjeGWB1RL 3c{빌6.4Te8U 3Xv[7ByH Õ@ xd84fr|2կGR ;sY Mg'uWARIBʤ.*ܹw&wDe٨~0eY6!Gm\eYؿ.mJbY6jbDvb+wQ,˲QY`=7t~cb?5V _?+$oؕN˲l'?z;q79epOvXꉬ5y'&߻pnoF˲,d{@ByH%T GOr:q[_ء[|4:~wW+2l.sW~Л'MLXn6ν\.~հGLV8 3nG/)Mf,[DOcON!q{yɧm4;miZڳ[NͦտBRH07/=~>usz *ilp]ԄJƷ߰P_m{vc7aB[X ~34%-W~V3 ._дԨjF+V;Mݶ~NTO< %_ߧM +y!a"ETO%='j9ct@ Ƴ;i853)Ə_|w 5ZomOkpPxY*@Q8О=U L-],Dw)#bJˬoxyw≿GX<|{u`“E n-Qz1 G.tk'w MڧC?rU@ő1/pRݸ"ʡ/v~7u|ŹsFO]CrBy< (n _YbpLѱM}C&Vp1nD )^|R8 ˲,cz/-=ӛZeY]I= ^-_ò_?ݻe4cٹ'`N'nI*ȲlҤE׵ eYV;wQUw眻̚d!! BD l XEоnRѷTTTQV*T,$HQ )Md6sNB~?<09ss~YzaBqn:\t8׫:z;GudRwGH,K ۭi#N' `9uw!2gqޑ s$$/ )zIu!X.zo7(! -uJUU?=6W!.B쿪պu:_KWWW>}xiZ^^^Νgsc ֭[O2&iW!Ѕ诳DQq+&˲(`0aÆÊW_}5*JdС%%%`IjBB1RWSe^7 *rEQ5MQ:C(ڢE M0  <Un(M5uUUlj?cڵk?iZVUU!t!~7ߴ3tW2sQi_C/OWDMs.Im*M!ZJuU8,8r!+nco{((*L[G|v5k~"ſ7O,,x E9iH躮i뾤麦ijV~ە??2-(| 8b`aEE~mpUg{}ө۠ j05F)%M2!.F-*m/hLZط1.虱ϼ=oQZZХjc҆|)cg.ݪ@䙡C?,G?4----mʢM՗UAO*~.\ SzfOxmOt*zm>ȲldYt]z}KRA5C_t93oŐg<ƿ]xaBSO=]li{J3zXRk ʍk>PKJjW6+>z{7.%mIV^6ì{z^C6o/1?HDBf BTBPlK9G΃{ jMYo{h>RH$WGl/ٴ`‚GKNΝ {Y=ٷMbЎ]Z? IvxAB)%PJ PPB1 &*p\ob;)'µ/;^%@m@ΐDyTUܜa]:sw4Ӽ%O/r10 SA5G\Qan.0Ҥ=}Jt¿TT|{Sg(܈&ڱo6@)gbH^1 wT3}hK -@ /P$\~ 8| mJq:RdW{A]s~@Kǯ3 ' _gu@og$~]#QoXlYQQpo3gJc! Qǘ,!@{lXU[cf9=sFD3Ժ]sw[2vwƝ @_qH].&Pyr2,=zhe_vo?9=Y%荖 c16vXPUs>~x1#-P3Do>g~IIIZZ 0ɤ2GoLX4X8p 1^4;;bx q|N,6lʲ"E}͘ oީS'8~xeeeNN$IFZO>Yzw9j(4MQԔ.BjEEE.] B\ŲvgD2&J [nhD>G5<-4i􀘔t5"(m6TC}F~EQpMb^۵a7v=ZS-O|iwN_RS,AN>rhLXι(gΜٷo!Pab.bte˖-}~Ң;jVQQc1 zl\%ZDXbb_PPP]]4>>m۶;wNLLĽB͍pSipEEd9Pqp8t:333F9!DbX)j>.݈̦ N)l6BWB$bBD=B!PB!dB!d1!Y(!LBiS(B膄TB B!P !Yh9uBBlMYUU!UUeY BQry<J e:B]8窪~+1I|>/ jZ $$$N>v=J 1&=! #2MB(Jq.B&B B!!LCu̩ 2 %8!I(/B& B!PB,̩ 28 !IB8 !i(.RB,TA!dA#B).oR%EUoEWŅx<׻{Fo^nͤB(1...###+++33S̹B[˶l?@u'DM1;7 N٭md*IrJ W|k׮ݻn$uEzw޼y5jTVV!ɬiJҦM\-HIR<2s?l) Ig]bPA]i;w5kVc6+FEG.\_~#G[m(ݾ {LPoϊ@8f97999ni`b,|KWJw'!tcu=??Gmٲ%b@*94jɒ%={1b&[+w.GM엑b7 @: M^s4<[bOڱ{}"=yaaaaa1c$|!utY&==W^oEmջˎ)Զ 'qh &|b[m]/tˣ5998lX㦂Cv0+dM=B͝Oo֖-[iyMsiûg>ːviVcIDAT??Q݈#c뺪j7bpu]\w:999_|o쎚(=C\ѹCX!5xHoY6~! ʐeݴǶB2ryGF7:yXa+9~Wmz7L(B\ٳEm۶p8Q۱YUeŻ<xXsb7|ɫvD5]E!'&&ݻYM vV~a+h::Wu\չssMΫ]rСY{?[9O /=-%##u5/DAӹAAA W6{▝B B%%%m۶X,ƏK$0|^l![?o=rܕ\Rs[>:z e!uj0T1&r6m8iZY{N'w\ѵ%ZMb8U@ydY-Vв1ܯrw2ynPs΁_Ѫ|7ăR:zAcfMaWCǓ,c:=^}~|~y0&ej WfoVڼrc\q"Nw?xr\lׇO\.ml~7 FkM{epkWOMKcu| |>ziƘ III>/4#ߟ-(4a A8RRTRKOOnZ;TRȚ4%Uk+Uyjnl_YY,뒕_tkeZyuƥ9֎QQvԭ~_~Z-=^͢]㲘@(#kx僓V}ZU5N(aB(*ID,ݢ`0A!_(..NEbV{mXds9O=)ћ,Y6rV0m؀;~_(l PC}K78i8-G^8R<~]T|SaHٚK=LX0 ̸־;AAEpژ(vhM$XEjĈ,PYFdFDJdF%FeJBE@%vC`arMqcsg *]ʺU3okgԢj:>1lIP嗜QKD|>&0,IY$j&6xpPÌT([qGsuߵa5;*gjGNK")%s890]UyUPc555~?11l!Һ7e8;ûC˞{ a3Y'2 %n%J=$rBȍX"ӑrJ#=%[MhůGh &3J9H[dh>1nc6*I  R3D8`izL2dFhOL +}"M4^P+R&Yzco{;1f[1yy$oo7MιsB cul̛m$B5"IR(R}E{ʂ1:777w_Ξ3 `+{c3fܲIϖӳIPjzkۣ@^ g==F-\ VJ|W"3"3" Db"E# *YD%I -sK*ɲziכ^' J3AoOLikI̘ͨ *[Amg)ʨq.h)g(ҳ'{x}.stOx;A]Z5{CTpvSSSeYnGdZ;]I. %(P-pncׯ7.L[dά߮U[6ꩱYFQDzM{3 {gSz2@/}Y"}wW9:ߞRMA h<5!W>rӗ.ڍ J"SU4///777%%jWv]zy^`@`IpBuƴT)iHd ᪪+V1SNY$ ߿?mm<*-^cB_lP~Rk^ Y\ ֳuϮ< 8AVp!'U .sVUY}e#ΝH Ba:lذaܸqŘq 6z(E+[gR?/]tѥ۷oȜ()))]'k/V(R9+oFWii\1qqq6f5fb%i }>x<`˽\UAV0V63\\ u/!,˲|FW7fTVEM:y,NSC-.BQoA{#o4 ʚEE!t#B!d9Bf B!sP BA!ZoeBQ#2 TB!LA!ipB!PA!PB!Į]qBe@5BJE#BՍ( [K"!Ջ( Ü Bt~DTp;BɩpB.Qj`BƹHDT8FBpbT?sHIENDB`workbench-1.1.1/src/Resources/HelpFiles/Menus/File_Menu/Animation_Control/Thumbs.db000077500000000000000000000330001255417355300302430ustar00rootroot00000000000000ࡱ>  Root Entry !256_37042d6e7b6958b0*4++uNV:4j+uNV:4jJFIFC  !"$"$C" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?a&Ѵr 6Mavc O+2$j9FY8'5kK8U&tоt$jb@N 6wޥwRaʗ-KW~6դY.]0<~T*iQ9϶0j$]f'R mAdA2Bw |sۡPOUͱE n?78m:|CF͜$G$]+]SRO Vg@cW Уʿ%~ ?%~ Z(ʿ%~ aSZx9^ExD)RҘx8~|-i2<ط=AtP]r[p$cgo#iۏ4-nN_tǙ 6TG1^Ex~zKӚ8U ˱lz~=koX?U<X?X?U<X?X?U<X?X?U<X?X?UW|yU/_ϤW?N[{oY6W*@,8$i䰅?jUi UݜrT&ޙr1l)F~@bN:b(GF2g$DgaA{  ,ma{;eҀ>/? Q̃h:!4;-:{Ÿʐa05 Y0%wyw pXJ*A8#4ϤSԤ'1N22$~5W A~aH?ثPϤQ/EW A~aH?ثPϤQ/EW A~aH?ثPϤU684TPQ_vO QEfmwx_M_[(8)%wdt,%r3ibӘu1| ּ7WIuʊeU| yFf s9nbl9kDA_H&ڛvwchVš|ZͶoneĀ\0$Ҁ7-,mͶi4eGPqʤAXyI sx7FJ3,>VOe6QPcG$=ף=ת?أuc=ף=ת?أuc=ף=ת?أuc=ף=ת?أuc=ף=ת?أuc=ף=ת?أuc=ף=ת?أuc=ף=ת?أuc=ש`VILv[?ثVM#Fk`.8 袊?T?+7Sk6o}|Ec$d(#Qix𶜫*]Mqk5F0vvFB6naG%A[Q_H0y)+@3ɸÈO]V&MrϪx ~S2 'IrX˴}W7xv+(f A=I u#ѵɠ :\C MgANRKS/Hed@6|=aowCqP3>@(r6JnPpyP Eq2O,ȷ"UU*0g 3kbm22_ }jMM%[|64Ro?hQKɣkq@ E.&M%CdqI$+P`0atOɨn!˹I9$[~Px;i/JNvT$|LIE.\"FQm '^wn8M' G&@{?_ kmA "8ьdv|ݓ*R BrV8+*#- ƚǠV7s-΋pшvy2[vK=1F;:"4~ԫ-aB }rW?G\U2][l 6F@?Jmyoq`K-[6-p o?sT}ϕ9S4(Zc,9#Y l,-j\L}OC>y+sV3^4Cy8,,vMZVYṌG+Dhpv;X2wShϕ9Q>W?YoΠkt,8Se/A|K˘5'v(2G,_ѺOls|*?uCm,no- cg?߬k&T2r=2GAgڀ4~y+s?VR<@l+ypy} Kuǝl5c!xcǾ4>y+s^m&"efuUa9*{`[&$@>Fq o?sUba:8*XI)S|d.3ILk/'P(*Z"/ GRF?k ŐkVA[i^b QFI@4W3|H)Ҿ"D$M_$zЎ/.--dSw>ZpbO[fHq9tW;|Dc"ۮ#;^EfIc0*$drqͽ+ VD͎\~g$89bxE7R2 ga,pE\r|Ah7qfvrsuVQuBter0zuUA^I'hY> H#JК=UY82)ʱ e`څ$U66u7*O  Ègŝ*ߏ{#hZ}ZX˦ۦv{rrpM(u~ucp yNoVT5Kcu9m2DqE2Ѣ$ub~"(D$P2I$52K' ~%ss8+l{8*cX2? kVs>}}S==qnTfgkTj>s>-I%Tb*_1IO֣w?HbR3<'?ZϪ//?ʗyR}}S]PR3<'?ZϪ//?Q[p[8AO֩eiczQESh uNO5mitK[Y%'ܖn $ <|_Hz+(y]KmGpv-*Ą=/:N GIUvG$d]nWQ(r%ZE6U|)C9?7 gs><ko~c&W;=y3~+e}ErrLiIFI@1tۓ,Jسc-.S;L14vHڽV$ty_QFG sQmDѴ!8(v#T*߅)vךBc?0r fMD|ѫpNHt lWqE>y_#`Y{.BB3dԎ1Y^7mփs;=ka (bc9U;@I>zcopov;ۛ;+qo eɭ))芊M{}Z}*!0&䁝ey"2aBϧM|=25 Gl 60WiS zZ+v*Z8S!؆H`Q9OUu(!.}7a `x%xy.DKm3r]FO5a=at}}}iCZI9g2G˞݋Cq UAۂE =OR QIz7Q@ E&(=E-Med p=ݓ(Q@Xc?.OX^"ׯlY[a V,l)]fE}~Z.8bIURğ`94QH 5I'f~S`_k/r΢9`$zu2^JqǞ20h]ZuFd+>f'$OAMRnʑvdWXo#t y^HY ׊e͵]\C,zx;T.i"WXd{V>a?jD&hw`,@Qr@Z&u[գmV\Zٵݼ3I T;-M&rxzG>kmVXZo=B[y2H  ]ZYMʠ7'GZG۬?UI9ya J22;dsOfEeVe 䞼Pno}-{,ݮPL+G45uW{A3{x no}-4Pno}-4Pno}-4Pno422oDs?JS}?,QERi")gǦگ2B=ƒnS"^r=YL.=>ZuUbfc8$u@k: :.6[yzȟᢺV +$dM${y'%ۧXM)>pse|Df%$[vq 䁃N~A].~Oju V5#B*P`.\Nmmo "6?uLcnsgPD ^m0 1bzƪJW?xW!?dFqI%d͐cA׹mHh6]:^H2pwC9{/F /B~J3cq]IaCui_GE}PP_:1¯OʡkwPw ir Ie?QThY_U#͓^BǦ:),/ /7/F /B~ ѹ?:B~вrx~tn_Χв?,/ ܿ,/ /7/F /B~ Ջ! O /9|R+q9EPKldEH8j2 1%m {= XuGEַVU9QZGxgzȒd8Glu,nlbEo*deI$d@'xA}QGhP<[hH$Qٚ\gosnhD }r{M4[kv4ah-vȪc1ۥd^xAPk<3OۆfcsU~+\:%&%8$uZoZGg&fd&(R } 9tm&ymE0| 4 lmu8^m8*(;Hl=O$6#EccZCyAq,;t@QPAu mxc܏;TL@"7PIo Z{xc$l, 1V?E&-QEQEQEQEQET_vOjk/'P(*&??v68.RL$)3Qmv 3ɗ$D*I8+—Vus\݃8|pPo_8bxh>#b2c$p,_I %]/?QG/?QD''F*1Q[握z]@@ahTn=Xd5ujo-+}'(D]K$dCV<<`6ClQ6Mw3;WCs|,pm >U_*_~*_~3!ZY#vP͎wU}kj:QxYd(d_)`u*_~*_~)OI]R[{vBEAG~Uv6nNsQ둴n%I@1q,6 ((1KQKP(RsyRs)TETE2/?QG/?Q@ KQKP*k/'S<pKg-Q@mӘ88+[ӵ-Jyqd|V/'?!Z?++ź66Imn2 3n*=Nk;fq"o*x>e𶴻=r8XlK\Iw q%L644u5~ҡcl2FY#2`2U\hzzݪY@֡7<6>។'g u4`e+}\+]I@]1FP1Ԏ 8meA5>j=!仒srRhs)Q۶My %䍘d2eA헿"4u]E)ھdhe[{؏jP}} QKE'/?ElH? P}} QKE'/?ElH? P}} QKE'/?ElH? P}} QKE'/?EYI&H6 nWA@(пW(Nܿ/?^/Ul mn0 (e89zrx~uE +p(DFX#APTPY$8*ʣӵK?:Т3/FBܿ (?rx~tn_δ( ѹ?:Р~ܿKB/F/[rx~u/AOS<7 A4rq0@/FkKNGukLqj(It_rC zƑ$z*’OԜZ ;QH {MHWꇇ?^/Ul(EPEPEPEPEPU5x}:hB䁔|J##tP3uBM=~M6dV.8 pWkI0ݴj Capture Image Capture Image
Capture Image contains settings for capturing and saving images from wb_view.

  • Source sets the Workbench Window to use for capturing the image. An image will only be captured for the currently active tab within the set Workbench Window.
  • Dimensions sets the size of the image to be captured.
  • The default is the current size of the Workbench Window set in Source.
  • Custom allows setting of the dimensions of the image to be captured by pixels (absolute or scaled proportionally to the window size) or by width and height (in inches, cm, or mm) and resolution (pixels/dimension).
  • Image Options contains settings for automatically cropping an image and the background margin (in pixels) to apply around the image.
  • Destination contains settings for copying the captured image to the clipboard or saving the image to a file.
  • Copy to Clipboard and Save to File can each be selected or both may be selected.
  • When save to file is checked, images are saved by default to the folder wb_view was launched from (or the folder containing the currently opened spec file). To change the destination of the file, click the Choose File… button.
  • Capture performs the capture image operation.

Capture_Image_dialog


workbench-1.1.1/src/Resources/HelpFiles/Menus/File_Menu/Capture_Image/Capture_Image_dialog.png000077500000000000000000002006361255417355300323250ustar00rootroot00000000000000PNG  IHDR=-]%\7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:IdnIi5Y؅ݙ ?wgc}1f  )B!sl_j7ǿ?ɘ?Av=!D"NX#AupD!\ywp#]B!cg/('"Nj=ksq eψc eYaMvӵ6o! _Ѱ,+H[zum%NA Ͻ=|R:!+s;nwCC_oޤը={{CDRUq^oDTT2mrAnsN>hJiv}dRJwԾށ~=y`XQU^;'D?W*Bt y믏~=juDOCظqAA8'Owϻ~WO??`03g.TVV^pyDһwoBnߝ8wFߠbt>˟~AtxlX}gG߶*8?xom*FR&7޶~ܪ:lTړ4Oǣ9}*JWNl8pe߾m;޽{T* #q\ 0?BP*U6ʂCz -['\8]ohE_RdC{ P*i>L_:FS~=yړ?|J~,#dꗏK~s*JH7 v}u>'ǎ1v;<g%Z#dZ&Z=z0 ް<Ri0iڶ$"\'jyslѩTJsC%zLڏtsLjj 7~jG&-}w<.q|ˮsq~ד\- Jau̩.B*AH< v xcF"ȲVj7A= = [R푈>\G,sFZE[Ϯ5[`܏^`6.P-3y?W3$N3ޅ^8Dy{2#|I}~Tx?X`Ky2V x+r5}{lKUc@fAm[د֔s< @s }yᘠSƝ_Kw=5N'X΢L?&u|}R(Pg19+'|.T437G_''>78cbVe<3b^|iqAro%Kf}۶nof_~C=u?W)3i>};շ?ګא  \FTIT*FJ3\O`2^yY<w(]&QTJG&(XB"KY@t <'nApKeJJj79 "#<֎E@s~8gk!OΌlXhe 4> (_yng=P60{׏g*lQZͥ¥]egJ]U==NJl7s [K_zJ/L9t9.@B_7\UGA Yh%*/guVhBzNbcR4\*'EJ`<^Y_-^ҷ/뻧%Q۱,@Xd_G@6v/XuH!t踆,O3tH]iIZ|@.T"J$l6 \^rNWSSS__ ]OhQWWt:-=G乺*ֆId2ed"D#ne*2X?@"-y }hZ z;@t J-gK!-V)5or@ {GUhTXZSd,}ew _(B\=%YupX '> . =A'}1|[Y SX0\.p!K;siy@5$gEmܨ }|PxgXn(J&5w:w(555JvzG]^+*(ϋ* I}@}g|{>* zd{7~ Asy5^peB_ 2h_clnqq 9`rssnx< <=Jiprն3e zvMT*].(:NV, yobF.sw侾vOՉ=z}EGg^vk%W*؞A}Nي:HU.yH}L uD7H sU6S瀯Vbz]^Y'>}}bKY>Z2M^p:yVӘdu}M*|}%_;V.^NH+/gocAu@՜ԹH{ 9_]^B *96q [ ___?bנR0 TFE҃qT*\x#jhhzr___N}}e]-ʃ{GmN5`X  <[ےZ7x/0cENbKYy7N_AS2\@=-L͋By7/SWnuqK\L޲(•ҍ1[liwӥ4nnl6:Wixc˲*JPx t:θJԹ 2S~ZƧg̨YT:#8<<<Ms;~^GfzByѸ\J[Ef(oo*eN|miCDW&7ֲ/礌[r!t~?_/L4,<\sFgdR\zł%Ӝ.x*e~Je@^.]ƌe+.M!ǷR'c焌|he.oS)R}v^l|Dt-TZHcP?w 3B!7ު:qDycS3j_<n聓5/ӏ+Z?fx^Un1`ۮC K!Ѹua`^WY}'x?BzOe "ćy!}NwM>EF>ڊ2zkk"zb~BB!]vJFyVWj4-ISirj!ru# !t'jƔ9sf׆B]ǧ_~zǭ'Nh4___K!+y<y a Cw,K``˲ tE% !F(<_SScX&n1 V% -B!]KE0LCCCg8n) J%J)hBr 0 R6*eE!aF"l < B! -B! -B! -B! -B! -B! -B! -B!IqK[PB!wN=)B-pCߍV\hR-.W>- GkdEQjC 0aJŕ$(nݍ"##r-.r=zm}};tR2nUTT|'os=sIQܺ{t\V]]}-B~~~XX̙3#Ed޽{Ϙ1wOn}ӐrǢw#LBd?~|̘1<ϻ].]__j*sǛfn}U !w,[w2Meǹn ޶m_m6y{!-BHKwnܲ mnF7/le%[\m&ڲP}||xx^'Ay^R<JcuqZ1cƺ7֢36V4/tgoNvA]=Μ]Yʧ_8ĕmKLcvEQ.< G?CAy?x< B; !-u 噂$Ma֒k fN1vf%i[?y,b7e:I9w?}K˿R5@mPr`W_5`oKxA`!f]15 pi5!kжY!7vB߷Xfvfmif9RyKN(ˉu'Bp]= -:'#&¹O2v 8J-N6F1~Ҧ.vnJ7㋚6 _Q<<:dgOYF[Ɋ6wߕ'n7F;ƯލFqCS AAAK]rv'oڿg;[VڅhaŊA3Ϥ-ܛ;=pۋI[ǜVQWx8o~jV?kڀhidםra0(wZ P{JW'TEcg1呇GOSCH=:N h|:]ke(@xD'pi Mpd1) iX+O3fbQl,~_2oL򆮶{C[ֺ{2MSʡS~un(,OHۼX61ce^-ʽbFhKT UϮm3tOBZ6uY(@j(cfak3:,AM] aV-uI 5:l %VZQaW=A#5O1HokaJ}=q֝OTG>OSRpJJW~Ȃ2b6kY`xg!PRX\f)_TF<(ܔ6?9֮2qx\8&`Aob&sP)E6[)sxex]v޼yKIIYjHyVn2!#̊ }0wʙ1QaaQcnb@ԽY)S[WV8O iZ! ԘzlעcŎxRhΤY3W_:9zPTyڔ4l Un u1+7/ENؘϯZ|̈[֤玏 J\v3uoj,(6X (c&Ho*7+ Ϟ?vGKϞ={]ޒJR499(>T!j^l4YkG΃:L,%AٜF66V.{gaر?o~ nd2ӧW^dMxX,q49w JfR+VB}a\J4?Rbv\.ozO>=i$$I}} Kt~>W,[UUU[[+o`FӼfUUUuu[ҽQ"Rj%%%.{%媬,))j;&R ߷oߩS f45b!-Qܺ}m)Z*|&f5G"t0^!N.bd. HWrZ܈Ͳl``V΀ngeYRT*=Ҍ(22˲l%M IDATm!;BN(nBN(nBN(nBN(nBN(nBN(nBN(nBN(nBN(nBN(nBN:۱.!GMqKP<c!ByBљ#;~]]D"aY B|}}g8nJRp8nzBHk,X֛]!B!|BB! -B! -B! -B! -B! -B! -B! -B! -%yv=> !܉R7c);yOH|/y@W_>fO!Nq*5~}ߟ98֣WU_w8K v|Q^?O^W: _ه;ң/ivsFz~a;Zec/eiYv1M맽]o !z]5ռIɯ=k Ŗ* m?~fwy;k]LSo*e_Ig;5Yߘ|_O2prrY?g|SVQv8lʄw`ɺYmc&o|j9 e8![\yW-9!3GE4w7f,[w-xݑ ,tb7e?濋i5E=8e{;A_Ssh:~>!;PWŭ0P¾帓ԩ倷q@sx$q㿛/<z-Sxr9!''?lǹm \<z\>rtw'?d\$ /`OOT\m2^xgG /K~WkΝҌ74E!QtġGyP7F)p6,oYh$^iflP- _P@r{Д?p早S ?]\f#S_}ϜB-wsvNn pȑ>?|`` /nݺwMHH2dT*rl]cu7Vv<+ Nu9Lʾ7i5Y^߅BM[ ݻwϧ D"HzSO=p]v 6mEѦF+vr{5k+Tײs+xo_5o.ڐ>/ccy*83潳RWW-1q :(5(-nxR\ړx;;OZ?Nh>k$3/1Ub^c)5JlZ?k̛ټĄmvqǓ6truPu(9rd@`bF*(**>>2 {na(T쉞 6fxdg ?yvW8Z`QwW^?Rf@1oP)0:go_s9[ZՒkخk ;Lz[}pzd%O f ώ vlIK??Շ{58{5:D_S' ?;F~?77MNurUxĩk-]y\pM2)cxВn !]SFvH7߄Ӈyټ(n[={4hPQQѯkү%W#o,113dv[ukdҺO:Ԏ@+Jѽf$xߒܩRAJ.u~<=Y+HCw[қl.Q:ayqegz/4[iw/ޗ_\`ᡓh`3cz|Dv17FudQIeAc^kiɝ̯Is XxDN 7]Oh۫Jt:.r\NvsǧO>/^[ܸpq0"?595 gL 5$+J} ΢ r 'nĬ'#ZUꤩؖ׿Wn+˝ڼ$Y[#necں᥋w,Sfbr#ss73Wc-?6g(k-0gʯie:*qDtܴ`mU~u|1y~4Q#FD[M+> !w'Ev{[dr_HRW;oRCU'Rbz *\7krfAz)+z1sy/&[C{ 0pvN8 g׶l`n?`Cu=\yвGΥBT) XЮ,ٕl,}ǶC!XvO+?= ˟nl QF.4\\lĔEdNZRCLob)]l&OLMZkEykb=9毥Xrp@޶km:/-Y6>f19e;_?BeqK".r\T* kogH$픮4D@ɪVJr'LY+ulcPotqKݏEJ*cK >loؑ< LD¤`)†`ٲZyZ~rSw\mӹ\>ʘYXڢ̄#K@s0el=R?c,[ֿORfʳ\̌?kuST^C(ٔ #g᪢ 1eG@B\Ct%Ӿ܉#f|'b9[=+ִz;_أ3)$t+F8NAd2RT*r\D&x5M˸UgúB9<~YTwFgb-eeeeeVNPG>OSRpJJ^pSb nZːNFwĬp #i:JuX eQ2\pM_$MEɡNտdڸ9.f&ukq-*=^cB/CZي7YxIș‚6kQa8[r(ηr|u 7Wa8@R^37̩ p$MU[/,?wO2G”0n+'hk4!Clܸqa] ð,xDQ=|pF28majZEY ϫA0n3>m)Ν]gjΔ4MOrЌ%񭆦F,0=}T 6k?[SclN]2۝wankMM,Q!|Q'Vs.>^募ɖOJnl't23qRo@q/NN-n+O_]BY1f G\ᦌC7] x1($$hqnͲgxV$;UWwp8$@Znwʋ_w8yq?Y/2Z3пs8-;x#_(zC@ˮ=Ly:-/oZK\zEE{פg-ui1.ߜ$){A1SlZdNz!&]Oxv4OUd~>2T&J8bEL0g=Og>p$rE 6<:B iKiuAޓQH;*Zpr2O@UA!~BQ:w=-grV:*)c/CL Dz^ .lVӢY.vp9@pim_HiCpp H][˛Bȝ+;#/ȥ*V5.ԷrR4Oҩ )+ >>>-Cp`+ rKAsJˮ,H 6yqH6qu˞_"+F +I{IZBʄkeܒf,E°y-VVQV"}S ]V,g|;ApSb nZ!t ]9^DEE_t>N R xAl2ҋQ q1Ug?k޵6NV_(mz iIƢݩ÷:E S&J ;{Ă-ks掏dޙ:yvHYǖ!yrHSYYYPPp\^0{8\xxϠ /%8Uv`:}ukaw6/~ˮ7o9qiwpBz)yB!w۳nD"#GŽ_g$xƆ puC 9r^RݥWX}[1Bloy<_YYyر#GTTT\.r`0 2dРA(!nO{1G5 555uuuZVUTWliB!)DV}||A JR)E!:aX.AB!]!t'!t'ǭkZB>acB!u2P?!!!!!!!!!!!!!!#&wǏ{_ޚBL"ҝP{뚕}_X يp]Bȝ3XxyX;_wp'^kn%$-ɳ<}fM@2$fܘ%YrΪ/6!T}'9e+Gyq-*e;ySw{#qF4/)7eA>z롵Nyn (,糳u&E\~R}ѫٓC8<ӜjNZYHnr>+KK4ζ}j癵N [WdT][_"qeb'\ -O >?ݖR[`Aj-7.J*-˴V.2Aʰ22.zlK]{'M]j(7@en7n7 NwK܂ hX ?595 yy{,ޯ gŌfREѦdh43m^5@E0g~PNsz|@inzrzn FcPHHe 1(((~Zbo3 u{3JEV֝36{pinz|jncBGōx]iV/NNߴ:ou-e74m4n*i:t#Ke IDATΌdcPHq:bbcPP8og˺yMEW`-^1[Q . +T)bm_-nUf̈=oSI%o1}S!SQ% jT4V<=o9gj+e@Ό)ĩ47z5yE`?f_lt`͵no6;~$e3(fjؚ͹Y٩[Kv&kWaeSOWٔ6uaYykL_Qmӗ,3 9?:WǶ g%~퓣Q3 l[hK:d:^+d [\9v\<<#p֩]XO\]08ش=w7ȕFSʚEE/Y0qںK5Q%”\r1(jZ$;M*пmkٛplo606A{cS`jUo?v؁c[2KM@+GZϲ{M),g'DnjOEO7`ĈqFQ;X==.h { sQ-q#&$&pL?PiF׶[֛ޘЈ@xoLP :GbXxc*Gޫ%=]@۴=%aCaq0:`'{c'DsfuRq_ zw[ⱽP!jyKbMikB;]\uߘt?Ƕ'5Ľ K$z' q@ؘą~z+Ե;en(xK( -2%#GB=))_y4 n:]cK}h r_:>?a9޵ήP"o5jL͏T|x[ VO H.xtT4mKz4'nԶi*ʊ /]t0G!p{Q-X::5i9ZBSUx35Yi*XY=HwL*FDvUy[Q#W`|RQc HZHGFx JڊSd"3P:Hqa\c KKyy6/ҥKy9VD{LB̦5n[OvκTJuvpTx wgiFMNL}[?P"'#17sX ϬпxIψTӜ Ӌo1#uD#[F-ܰzfDD hikCco=X2qfj <# 6 ̜(h)P=JƔٓࡥ*4plΤ;jO̐&/Xck**X;.΀J%w0$x,]8>a~Gg-H5>3q9Y\Ȥ$G |nrHR l{ܝGbnN,ݚs:~Z0`M7ot#Fq 95=P" 6Annn4z8VѴLlZ^[2R~֨ՂV)V1f=FA3.:Om,Vr KzO=8,LRnZ柎5up MjLo@> Ô4gA5JnrmRJْislȵ<M?71w[m;&* W7_'Ҟ~KDDĊ#{TgJʯ?"yTxHC["""7pϰBDV<0"""""'D%""""bO~KDDDDĞx ;]VRQ=MaYV͟?*1 iiixwtt;vBh'eOlpnn;~~~6Qrd2;wfƍ>>>K.uuuDJKK8'̞=DZ= N3ݍFѣ_~6j׮ xeٴ4//ӧ{y ѣGttg}v?8{= HsǷD"[!$˺F $$ad6kjj֭[g4f$888??h>i5lD)Uh4f'''̰loN>M7f0 t҅eYi]lD* ݪde]nF mfaaXկWdYea 4ШюfRlDZ[k7XTd/ޞUj^"Oc6ȪO6maƢ Rae9r.e `f׮]1,2t@ e\bFB)"Č oS\?1C?Mi[ Btwxyw辖 9yKR! =vKޏu?_4PXz(J)죄0U#eCX}lDz,Qmw3h)E)D}+J}y%yܔ쏧O[ Gz9d1{D:v6)IgP PN9iۇ”Ų(b ~Tem7[IܑXf0 2ݕJr@#CC-?[V?}y.]61qF <Շ8᝷-# ZJMy+qڵ;ϗ5HUsPykSr8f-g+J;H.Vc+,P g{Pz@Z|d|r4{w{ϰ+/I.\/:1ڽw++X_U~^ 'm߾}DZĪ'4\T٦VNy>G}:BA& Bq¡|3h6Y/jqr%3_% ~zcS>>aK;s4c:ɉto= dzK;3@F%#f';1W׊2/-p\)3;3aZxǼ:5TS ޔatRݹk1O>ҵML梐*aO(ʧMT*%< _<[Gh˗ n𵓷ӻa&ћjW ak5fr"[Pz DҙJ/GscFF+Xw]7GSاm@;qc G (¹W%ё-r|kIc sL@'GF-z|زgKkj\J $ I H ٦ri)xiOjE5pݖ2=tb};V39}-F1:]} r&ս'ێX&0YW>zYݻ*JoW6A[sVѠy>ŧ}d*i@`}a(J Y򇅽[8\{=w<ةB#ՙQFǾ35W72KT]p~rS[<ʹSO=5f«ͅoѫ7͕w{1f}EImicfpGq[fifq[!oT_2G6?|;GH49@ mʾ\0>|i`O&ΎF[z s&zxuVʛ:[z8鐩Xֿ sr#`mCQDlߕǃۥ@ @bbbnn.~ṱIƃ4ԵobQ7 e#zӀ*wZ;)>Et@k{ raPz  x {ꚮm[8:)~b@@ QSW~^[Ha3.`Jt c(:CIqo/(),..10~^~)(*220<Ͽ4E Cmkfrʾ|{9Vxq#^M6%+K /WP:DģE0EwCܚthr") h^[fFjecR+j҅ru~ q~)aqc72P]I;*4цhpՒ@T* p4M_~[nB%"J#qj˅e}˲GaY622sE~͑)$6My>x`www,j43gP5|po wfrB2ClY^Vi={%ߊأ br4MSEӴ~yII$E4SH$~c&akooʈ膿!2ܓ&Bq2$FKT^'=#C [_;S\!RƆψga1*x8py^FS1GkǙzwVveٴ4//ӧ[,ѣG>Ǐp{4ULN* ]T]VG )iՀBH$&ARU>?&-=Gr7PD"XZ ($#9~B]\9O ,r`g o^r[ͺh'Ʒuu9c(L[6fd@FcAAA\\0BςlNLL|e+MX ^nhT*lp%rGأ br2iD"80:&M#|Rw!=TUiJ)8Mݬ ׯ3s6hg2\<ϣTmŋzJeVhW^ 2)E,y@}b¬7d2/ ?%hw$//Ʋ&SN| ?>شYCun#}ʔ)Vzp.kHtuu2e= ,֞DlUbkSLLH0zbKBgߘ6/ j̑nQ3w[^mM(5{iŨ| as  >3k+4`vu*>>N1/Te@'0 .)G& VqMYWl75;mgyk͍$P|.)ַ)|dAsm[;39$zYfVUUupk[&vrr֭[wgر9uTf@}E/ g܉ ՛d=ne~%>237Ox[šqȌ ^3wю SPV?pZKV8LyX~wN)r@U3_>yawn^ J `75#!kfp,`8VVp&'{DH˱Qоحrv~Rj{EPaHsiW_ )䒱6[ %d.N5O ;wfmrc<0a}C}dEP\!/-:A TyO.dd/1QjLhnWj70UNbJӈ[SdeIlku_AaNp`ˤY)7j&66ɚs83ݿ0i^ WrR^; q&qYnV5W|ՔG͋R 5[rIdjXpWhQrhy]G"1yc yǏ?/=32z;[rn~U}: m>ef~Hݓ{1% z9nF_9_p|gj\Rx؁&'[d_fLIU=G>Ֆ>`>V'*>O?>r:6ZNO=<5<3zG)t?Μ`%sS[[_~F 'k3h!ׄםeЗ=k`ː`O 92䍄f7Dao6mL};0;W ̸[FNU}%_7d`Gw\v`r"3/OPIM_giꙡzyqX@*ڶg\]֭g Lii/JHsz~W], _4?ײO8qĉBa2&F h0'Scƌ 3f̘~z!"طN^~)(*220<ϿtkGfRN+40Izv+ƍ{52Q,**10פ.r(b+]l0yأiNLN!***nZzlfTdX)hl ZZ;(,, cWĪdjS}619|La4<==\z$00P* ܻw??n8,˚L77ݻI 5t,3h)fu$}緿S7V G?ǡ |X9h{40@Ha//ۦ7o|JuuK&\nW[ݲ7&8* f(:usjl@wF l6L&3f.Ӑ$YSSc6;VW62;sdeuNn.7ZK/_~Q ɼW4'l^n0CbKeدɉ{_4]'oQxa{|ګ&ZhdMׯ_֭PHҸ8pireq͠IhvTIqDZoQΞ"K(yZz-$I޽{{yy9;;ߣLlDɤhZwIJ wL&ͬ^]\\/# r hr\uGDij#*oR$IRAnMN9*"""""{HLws{%i [[ќZDDDD [.]Ҵ "˓ZgiAưK%*"r߳vZJObYV͟?*1 iiiN$}}}ǎPj"]-u^ϯV&ܹs֯5557nYt*--=p'|2{l1JA.m1ꈈ!=zׯZnoEV{53˲iii^^^ӧON.y^FGџ}فƏSeټ .TUUQ$meC]rDG=&?޽{O>-J-Z޺9W&I$h,((c@f911񕙯P4 c9.88xݺuFQTZ aYȑ#,FFFvYnA)"I &ypNk)#c]+K7q, '_ێBX@J:FfdII8pCx/_ɧ9,o.\n4nT ?0FDD6u[_U^c9eY.̊ k*O]=..+M@xaU۝䛨)/ܱG k׮Mܑ~ @zk /v(ͱA҆Gj"Mi2s߁@~AH[hK݃YWԝ:6-BX$rIru*Y*5$~}Q_Uv%$eθP;:.uQGo$19ICv(#V }n214sd2л%85(JәL&eñdV'*NyV9mkG?M! eYˏm%8dm gb071G 'MM,Wz3ej] a~K^ձհAQNݮU]_纮^F[J1IKըf׈ċKwge}g8' `6=4!=3++}8w9NLHVVe5T*P}t=0z@%+L@?iO>rl`mo\e& t~Þԝ+flDcO#x.a75-!5uJ9fv% |mKgzx]7x䏢zIOsƲg.Cz`Vm5;@ѐժdY10>Sس[#g<k^:MN1S7Uwv^[vMP F;7W;m ,,enՠa㸤R)A ad-6H6MQIC^1Fsnڿ]v:lMy|~]? tsPg0 Qswvvvvv{'몐h1&`0\ sԿ ӛY`0 U7}IϧZ-Fs׮]vK`U(յ{%?hh=-:3_Q\a L2MĄVrǫ+_fTJ{гUFNk* o٥ߒ:H%R:9GŒ$w+6U)JB E1 g&Ƈ{H?wp=0wA~ÂRgV ~=;{<>ػy_$d`ǜhڹW_ )dc&x0UQ >>,ٜEBFԸQ~DI  : &x]0)?̃?kN?:9g]DPS> ~M ;0jҌ@@WS OY\Uj!zj5 sB9)5)w aBgVlQ;;z}y#? _>j`i wwϠ>]>x~R*E%go=hɊ}=MR|oҢS1̩A*`dUWYg@Uysv(pE߆=ڷF[´Y$H$ H$A6[4Mq<0&l2&٬-Lzsڜge8?yJ\^Uf0LO5-?yK &'+cVtd>o`yOd7Fo4L&˂ef*7uɫ'wLy3՜9ITVѪ[xԘMfAo~q1a'#g6 _L;ɾkܰle6LfȯrWur|\kWL)2e-j?RfpK%SAvIM^|k7T-H j5"n]Qv@=O S6ocl{o)xS!I`߂@$kޞ=G2s4릻Um~(mZ_n]FzGWKþހu뎘ML |mJnK.&.!cߜK2NFjx/ܙ3כBRġw;pt@ͧ.%ݛ8SY XzZ&CQRܕ?,]_#忱3\N:K% R]S^ڹ'MMZgbZS,t!Wga!?l{[Ƽ䌭/}8r3w3rO S/lfRark|oM^ZysCDlC ¦Ix' x IA 9XHb{pp;)N*.]Ԫ_ѻ#CCvtR)Tr'ʱn*g;ä`40 / Y4G7{4u]BG6̧s=eAv.DҜ5:K >J'n &cR_4 E W9dV`S&lV-l}$2{H6UUDHi [Dz|8deTӥS*ӎ n,']ZH$)t8䟆$H LWC:|@?Pj] *yiۓ|eݑjy͗SR0i/ɠA] $IR$zSЛ X3as]q|YrM%&e4)Qr %P )) %r !IJ(EhRFÏou=]\.]]O͙څrq8)vRtc7'gi*F}E7iy* (94)P_V<'9}V IDATf,~⚴^_)*lge~9/,L1+v'&.+vgU荗ZQCշJ_;X8%ԕS -޸.^}x=@FF&o->Ab36gWiBcyzR^v^Пڽ;R)P9JQ@yz}E@zqr<)<#$Nj ۷i1-8uظ4Re#n 74%uVzr |Cpz?PT+@R!.C 11166666vܹ֭C]0.C'$IRp`%Y )tEJr}+@>y(Tt t/Љ"I@EQ._3\p^cя5pYS(@HHLR|76rKhT"r)-P2 N*jX\7GAy3WO إpXO9 |/+9bB_Ȳ1{:!;iADʏ6-k'MU?^Qʾ|{9Vo=_4?ײO8qĉBIyIq#^قy 'ֽC<5-~Vb`&;6y˔ލ}[B= a_|E:nt|(ERݴ$KQ$ \`ɉ]&Q"욹tZcuٹWS@4M3WϦPP;< @AɅ}1I)$ M¹}$))xss^|@F-ǫ MSԝ^PBPr)%IrRA& _4VWīrK)K%rӄ$,hT777aJDGa5k?1QCJ ~0Ԙx%+mzz@UYE2L[Ĭ^Zje*hQ0 c{!'}ZZVob9oJ)GVR=QMgb-=)qa&C2 ~U5u /ku hn(=KЭu0 @.l㠪ǿ{)4SbwߏsbU@^ҫLkO8(J]اB0%G%?5![F1++իWKJJRлv{>ƍltss޽;+++/\R$MSvN:G;`_f `۸)Mxeg.~+&<+g\ևKw3E Gla\~hL?ㅤCcc魿Ahxy ?AxyyƖqh$]\\T*=:-Ϭ0Ӯ6rNKB\z7h[ԪׯwM[R4..κ?gEX)C*d2kp?cY\ս zl5%26^.Wu{okww4|[}rH$ AX UJm_ Ǔ YkZCO U[=iv}n]F" r1//OV;99I$.y\YYh[+{zzJRBaurϤfGDO $a3( йeEpZ, HI)Хgvco صg5rɓݭ ,hΜ9CQmQ% 0EEE={i3*=A DMOn %˂xP]Vc޳]c%"r0$ފ )rvv>|xAAAffV$ju޽m7Ԡ(gٲeKAAAhhWPt!h3#?{E3˲"B%$F7cfIuP:JiZDjA32SSOz$~*元GBEUQ( (k.7wgvru }gٙɌճ׮ff.ݎ1-Bez^.O٪M#ͷ#raH$D U HҨ+VyRyyyEEEI=EyG{ 69;;;;;7Jc<W^f*//z`;aww{̶-BOݺuJGo/[?G8Kl.KDŽP"]wG'$___oBZ@-B!!{ByB=B>BR '$bO(oB'![B -B!!{ByB=E!ĞP"bO(oB'![B -B!!{ByB=E!ĞP"bOڔdB!=ѴY}x!,ؖVkx<@0F!e2Xh4mzСWj#B;vtqqiKeFP<![B -B!!{ByB=E!ĞP"bO(oB'vTUo޼ҵwB~wt%~[i),swN_'KB"۝']]GR^H$a)W~D"DY@wlLD"H^zsޢ* vW(_^"`lZG;{<_7{6u`` QD^\8eu?q<<ۜ}S%(ucb̷g?X ?}5f8cG'!xzr)9(^"`l;ŜUb^S!#w:V3_&3~+c.:WOŌ"ݒ_~< <%ڥ&"dUњh*]3K6y-N$G4^Urf70uľ"sG`bt~ߧ߿WD )7{q>N<`M>g`oyg\b7bwG:u ~ /__RP]DDz5?Ӝ{By4 o[/_LI~+[{D'<s^'gC‡G XۂiH$ےq/H$;kŸ^KHߺw(82Ic$UԎ1dhODsZInK$w#>^яBȣ|n$Rb1TX,NEr_~>xԾbkMDbQTUU, uoz9[Uw3LtT:V 79ԺKR͇A!{bχLE1Qͦ;vlXtN :6C!5;ݦZB~?moݼyڵkwBȟ?޹sTn}U^^~UXܡC.gH!e4ܹsUaZzޒ垞;v "HB!d2,[UU%mqwwwqqx!ؖd2 hk%o9:::99|JZBlaa[ݖo iByxxA< B!!{ByB=E!ĞP"bO(oB'!ؓ aҥӧO{'b~֞={Μ9SRR/]Bx򚥫Δ\U-BoJRvM!yugB|>\, x<r{(LF_̑ـKٶGBQkz&rlyWObU_**:të1 sRs׸e~ڜ9iJFe+YQ|O9n ui9RlzoU5ⶕ7s GQ$5Vj5r5-Wq؛J62#yq5tY_NLeV\i9rr7{B ĂF[2U5@U_L=*u6$e2qu… {}OɎWU:(v&Th9VDR^ HMUڛ׻^Geɳf=@yʂxoo  qYe%=‡(!܇6==Dq#``urʦݮ85OWs<'tn!+bBpDXBN0laZ1&&Pg̩=3ǒt aNFow.C*s.-vՈ'Kl?#.".ܪ0cޜ45P3:`NtndmNnH8\ẉ+^VQkE">uFBmw&Iayj@]6-;  #n;d^!\ˆه7DwREaF٤|sfQyps"jZy;-E$+t$!mL76$~pޡWj[ \cth"oɿDߢq{g8E袦Rŵ C""oLO-8-p_c;uIrlxfV_Q$:U1nGD`ߠ#)=}NGNr۔~4/hR,~~ETr* ti#f fHֱ=ZL.Y^^ڪԕS=OlM8u8VM*MUOޕHwx|;?7~j9*,%nl iilrH55٬B;ygwLM 1Hehf6Od끼7SdzּO;FRf6[Bg$w9C~86.Bȁ `-ed`[CUB!z\B /((˥)@ʸp/җⷙs UiThO~1[$B%5LP/77.Ζ_KS/ٳOȑA"g{>Th;l{V19qW{5@+188Pt1k%x !T$`JKO ~cŚQ@ђ~pf  }m(`ђ##H~p@ۨ+/ Y6kd40*鋐/MF HQ"؂;:9nB@téFVXId`&]-݉o{$POWNW8=ͫ+~!d,(9{w0UR/scF5BOntR\2g)uۯ\HJv?66 +jyy \?gpö̐> B6@HDw~^њkz<;Hs5"w?74^ȔzZ^(٤L5 !-gW]ܩK'7KޥG'ө],rwvꭦ,$?!4$$dUz=Y@9ecYϒVD.['Qbgk>522Iqɫ\˳5^\Ӗ/׻$ؚ%ieryM?¦& ~lثlf'zKXFRiIzՄ1k0u|&/`~9v#$^ R&7˒? ti&!;.NΞN.X5gcG'DAM\w,¨3F>fY"INʋO.'z '_q<HNޙieyqw,@_](Y Ԩwd$ |B!/]I'F  1ߩ~ y%~HI?K >eզ%!T $r^utgKb/l яIt;^IKB Wkf|sҒR 1iswޒ SQ[0+'oWE]k} H' 1*<Ə2z2/I,"9iGXvmw'!|66;pJYbڶPFw6,A> m uWRJeYɮ;FڽJ7htV!^1 E+_rJ׮]/O,z]y)x@ֹZ!/ys紥M3ZE.FG^Zyb7NT݁nAqj/v*8h:eݺZjfڕzLN^'Ĕ:A)nX@ r0xJ ̿-BoܑQO(hb9|K늜Ձ+7JF)*ť/!Oe7nZӖ|/ٳ-T'NP*riڈȥ/rk[WۯMRoݺh'ϚM8 IDAT]$^m(Dzdar(6g- zlzXB.E C{6Bx dRTn2MVxb+?"0: B 3nvڧ3[YN_Fn"!4--gXB> (3ByH~B!!{ByB=E!ĞP"bOlcB!侵 !tCEjh͚5bnC^8STͳhڌodruu 3fS;K!Ay@U?^E[FaÆ>}$&&zyy[}ݿ~ٹ!PjK.{*ʊ k222|}}x rd21 0L.]7nwM0.F$Oؐc']=,tW\1b˲`0h4k:RVTTZnBe`/S(Ʃ ``9`0߿̙37 eYSNQ"5[M)udYٙeYe9&i޽7oqDz,:99,ێ1BjUzxÜ9sV6(^Q~/<۱#&I(,rdo8ceoh4ge4I!MjyڛgSS3uRxR" ۿXhK&̦d>{!Ҳ1~s]9EퟸB!0`d2g04"ػ[5@aR^qǒ1o?i]3Yჽn~*4w*`VYܫ#|¬u@⁄PiϠ1_Ij$/T@#_$p:t gn3/W|.Uv.l:B0tО'& > A.SVwjď_5Q.xm4y|~DDeM&k&WhEwBL"T*#3Zyb7>8Re~ry;c%Y Vc"@s {SQխ-3FD';W{|Xy7BBBByK/?qA&uڵs.!4eY\jֳQ{ݬOfII|f*7>dBLH+Su&}oZ:<.[xvu}l,SsC)]; ۷oWWW?c% ϟo}hT*o6!NuނWQ<D"kaa\L&`,,,tuu5%;eyXD"??'NOԘ8\~Y>?d[Fy!d/m{:s|>_" 2ʕ+2LTZ7㹹W"X!#[ z\n>f6 'bX ya@ D"r!)[ wm Z^!BF ![B -B!!{ByB=E!ĞP"bO(oB'![B -B!MyzVB!ah{i=o9::,`B!-aYѱ-5[{MM 6|!d2,hRաC>VjG!4$:vҖʌBxB!B ![B -B!!{ByB=E!ĞP"bO(oB'T)3%w<3P:d[WN"YWu+B6ovK/?㯈 鱛m[U%yy%S_i{WS$/.Ǐ1rB!v6 yt;w{⅘;f"iɾ*$d,9L"H$3ϫE1qݡI%~7\ݳ8v}FI$IԺ*~dyMɰ7Y{ljdX%}yrSY~S\Bͯo L3JT봨\8>{u3[v?\!/Y-Bc?WQi5;{`P}nӋܟ 3/*E+^/q+jn6G>s6|i 1diB=zH2:8ᗬCI˖8{<  AG;P{{}5f8cG'!@'nO)yxg"u D^c|g] BG[gǐѽExq̘W_;iAb@D3K& [v:}S-BUJ׀5}{n3W7ZUiGk^jC BK[JթT7m9i5>K|Q$5f&շ_~zxT]z[7rφr@Щ N(wq`?fN͢`8u˵*E_:廈d !<l]p1)\%?%-B!60 0NNNn!<< x2Lmoh2,Fa%$]q"J\c ||XWo^h9Ap<0!,bCFZyADBN9aN֦D舄u6 @04!"?cN鿄9OM՝Eji  iyF'e(,\!goѣGf䛭''ʹ۞sT*nVP毎C*t8pmGE!6==yIh4uS4Fh4 <7ػ&}ǧ$"BG5fѼ[$džGmoAeXTTm` Y챓/ߕ#)&s݌b7FkYN΁M2}EvgJVmMK}Gv4hسRZ9Bȣrttdx<ǯe~cx|a&j)lAu~J @Bh'tIʃTWC+X/^zm*ws"'۠4p@$rt4\8rVY⦦4^TtYAQV˛ åϬvt3~[*{udp H}ÿ8[b?lD"BP(:Բ:888-B6n,,\;[jq\' uat[|fHbOkH%t_d`l.܊k^ =,]f˛CYaNبeEwOݻ{XBݳ}޲&*GGGQ-Z ȫlƓ9 (`~kެT(du / ,[3>8zK.[/2bZVیjAf8lIJaGK!Ykm]}aB=qrrr \%ea:;;]52iIRc}lΗ+jeyi~ꄝJ@܊ ZQey9 +Ŏr˲Ǐ7o^u8ⶊ"7ͭE^ b7:כQNSg-NT݁~35^C"FXU(wn.ʜP+lJT*n "٥hcYƍִx7nϞ={mX,8qb߹Hͭ^fwkD59^i6i;|[yU7畆Bu6`!ʖ-&IRݺuh46Yyzz6w"p󺅆l4BҕU ~quiNFTB!mYy~ЌE!}ZQf" ![B -B!!{ByB=E!ĞyDŽBiB!}wL!!8STk@ȃH  !Xzŋm̲˗/\p5Je4ϸ0^i0Sn.a1WWݻ?t{-tjӧν?tһwo{Bâ***Zf2.]!CCCgʖ Ju…3֞={Μ9# .\;w(spp !XF:effϛ7W^4|ÇZVVr^x!]qf3%W+&e [ysߔJ_!UbFСCW^8a@_mL00L;w^py<ިQx9o΢1>Е H4+ȭml.txjYOIO*jكhp=Dei/_KN]%oX)W;oSeJo4Mk['ᩭc@ e?1 c2f/h2]]]CBBN<}5`qFX5֘o*\bo*ۿh0ˌqIq˳f>`GZLY\i9{Z,%ڝ=:8@ <ԋ)ЖG=PU=v=k`T2ZT@h nȭ-3R UJWDͰbm2U-,cEjKl+kk+m4ih|G=z㌍L&d\%^'"}&A聾|Szf-I׮]v޽cbɤW=5d20LwHힱpFh4 auYXXhi+6oו_.زmzCf(v㱧&k8VP7o 88 >ظvQ-d-ʒGվ偪 M^J={3굵tLNZG -vD8׷Վn{ttch䧶'?oΚ5%s ~5 Jpz𭂃)a{l }[AC{7qnW}+w  IܹS\\ܣG;wFP(Qoe2,cK>LZi/_Wu eQwN8swV Yނ~ WS*_ aD@Z[L&eu:`0޽O?O:9=_Y6b&tv8u0Z?eonWNΚL§9:78\.(}k>lKZDl>Y `ۜ9k'EDo8 mCNgX6Xe }6L"}2(ޅз{Jo|z& Z^@\o1h4JVi4jZQkjFh5ocwoAkOh?gh5:' twtj~kśi^h)ݚgkGwtwwwhZNluM)8g-F0:sO'1w0&5u4W\VkZQըT*Vk8a|@ jZ=IyIxSC#:Grr?w1/ryiii\a^^Q"˩04tLIۓeG7ṋQ3J[j3,e+Z [e0J$ks5-uF?-+ýBku8zR+*rR0esSr62Ìʲ 8c `q c>jpCL}oDס3}>Xs * n<~ò7/&jKݖ$>8]˖, ,N~afhoY\?ZyCN\aܪߢ5`mY@g&0hb5`YN d)bSk3I \^p|DiF`f$&F>RO^y< ` a4WQSFv1a@7/z̕S3E5ܻ,\vBcxFŢJKxA/QcX7oui_r>|g,X,7< |ߡkXeO\ᨋ"qr?$_0A7";)|eczyvvpT~R.ݿqKGSQ#tp?1}E4p@$rQg$J}B~q_\]"9sKW}=Ď b#׶WQ<7TR&<{ߑw?_6Ld0hxMZxU"t;wMߦwɠA!#t.vrܝĮMD^Aqw/vR\#-oM7 sݚR2y@$2Y\iK+j?sPKæA{6]兌lJf_>5FH],g8 hTs@2`=Vq Wг7jO@kwlm53791WOpL_w0諚n>yk{F[zn;0|G#H{J{F 5> 83kJk9vC{1e?fs$泐^d+5˺U dR'?ka5!w~2+;Ɉ~l̰& `.SY<OTj4:h:u N3deS2k՘7BGW`cŊ>+񄝞K,rI'yFߗUVVVTTTTT=|F2Ⱥ;r~K' ?ݡN;M[nAV6~qw'WgOw'NNLNo,31 ߣS"tw &74{BS+rg祫@_](Y Ԩ8c#Qn]_̼ ;2HTV+s /V]P? H nŎBZ(ܲ<{_j:my6쇝⟯_X(t9Lc_Jb `gmuGuN u8{A~nwhA\^LS2[n 6AU3Lͯ,QxW2TU}<9u X  pO3v)sG,݋OYunB/^߁[{0IֆCC3[Nk7뚌zaPl9y kyVӁ)l˧@G*-b* 8::jں-nƪs Q%_'^~bJjt[7!oNF0|>% jQ 7 3{f~{7GIg}w .sYt5eEyE~) |Ab>)3%ߖUt5.ɫ4|wK ~7UǷ?%Y] ` ð,V6[>argG$щԉ ǧOWwwF҉tG'Eng KYD_&Ƅ .<$0$kҒ&ŕro7Ox^*IrlHoߠ[ ۋMMGn9giNZ#]+gϽY VeD5dݣɑI<^Vɲu1q7m=Y8~DpS,'RUVxxa%{Rҹ 懌D]qVYUo]$di% KJJ r.wHƼ;f4LeZ5kVTTȤN|-G]<ۯ2-mCF?H)ح6EQ>jɷ蚚vȝsf-ɵ[J9$CjR5kÝ]bk=dž}]ii-^8\畏(\oܻ9wfMKkayG(J c V#F3l5k֬+3ܰsiiiY^Ӵ_ݬ5TQ1AMwJ 4 Tgk'\6k𢗖-O4W|tzYO_ݻwܑ>/͌aNuޘ<^63i?yS >;-Wx'f ;U#VaZz{ R5-p{sϟ06)3oN ϙŋ9]. mߜc>.=lz6'#o Yu[9fڳSylŹ3o˚qZwbX2q>qa̽?|¹cMZ^ȶdMCf,2qQ7?ksȚ;]6o\6#U×hr>!'{j8u?6; 3_39ۛ3k8wDWaGܧ_~a/=v="`p˖-mi?䓛niӦvݼF;ojګVRXР(vwJB}>_Plrt-k\WLPJJ|bs"E峏u=;W]uU6mA۷oڜg2nI)%K|uu\#Gl߾=$[,Y MRb\vUvk>_0Ơn7޵=\5dco_qnK+Afeȓު5ܞڏJmS>_0R鵹s/zxՊX+.V:QN]%8R{j3˙M`d>sYqKJYZZ_=:))f?|F(*))Ypa\\ܥ^_}֙Ok߾ɓ k[؃sr(h&gkSj(vZ+gty;\|:Vo~~;(}2 w U˫p^k-r bUZ~6Qy0f)hՍ1vwf͚{n'- -Z( uvoEݷF@+]0o顠~ oߗI:8]mVw]cA2b`;?Z-S(׮]{[n[ndGt?/UVi'$ٞ={zM$ lrԖAJ=e˖;vl={ׯe˖YP(tС7nݺ5 vڵw޿jDV;!#GTU=Z/vH;42ħ:۷o_n]$93g11UUڶm۱c?CqAҥ.!8EQ(Jbb^N6*v;aXU[RYZ S:-3t݁Q"1Yg I!1E!1E!1E!1E!1E!1E!1E!1WܢBP4[VUj;!rhVO~zsEQ>4B,)i~=Wkq'(+++++#BR%!!t'I!44BHcBqBHcBqBHcBqBHcBqBHcBqBHcBqBHcBqBHcBqBHcBqBHcBqBHcBqBHcBqBHcBqBHcR 0 GΖSH&sP ik7bS~8[$tOSϥ}.aƘr4mΝ999zaTDW诌3UQZj٥KE9uy 4/]DjT~<3R`0RRc+,cL+.`B9;vXl˲Nb9H$vرnݺk633~XQQQ(y_9_}1ݳ.L}A3@?[G4ݐ9yow.tVZ) sVڶm۽ۺu:BbV 8xs=׫WCV`G}ܜ׫U3W&AL€.}~ӳ2q\VfA!&0VZs)S$''sC !+YYYW_}u#DϾ#Wl^uSTCp0 Kt)5t6Xot`!uݐ IdֲE< W9996l7o9R }@35[^\FLQ6)%y׻\C#3TKPA͈2]F ѡID +?mx+ jkԶn:/Vb^2bȰ!# kFX7:º4[[$O/d30nTVtY\i+'+ n 1 Ch3zIi~}~Ab'e?b aMF Rn d+#rłOxﯺWfV?˗WպY%4 @X?bd$y9wC!gavJLL-3NHY^)jj敳?lXg޷խ`QTe|hL 0H$KOOOJJ˫^PI% #a ݀a@]C@)}ѫM/1bjnVdLv_JϿ$_Zu30LBJ0 Y1toj$ҋBj-$Bݻ322l6aEߤȈ_޼k*_?KvDtE"o*_n PJiZ0D"BڢE={tnǾ\IHu u C, c3dE]ߋi*V\T@g~x/?ŗuk `6^}g :rpOhwꕯNv_s03苿MI>ҼWfycS{oy2oѻ: gZ[A(CH&aHH0Up)e|w\|ĄrN ^ /TEaVe4M3gMq Q]=c-  lЂfzQ'Op KEĭ>^ @7>j\آY11R$jcL(JJJʞ={@m*UVE(\E77B0!7:hoqx 4xy83ƅ G IDATY8(ܢpN:lXh! \.W||d,8csƥ01`5x)so߾_?b;8V:W~}E3{FM?0[κy|݅BS.32N=(ntB*ǥ-¡rp00ιL^ nUMc"!_֓aq>`ܲٓо?m/>yI +$r9(9`WaݳͶy;͡51kiQh*K) K)5)u] r5p !c6ũjַcq^cg 8Mۙ?S M_,U`ᚯf3$RJ2UUu\I)cs*L7, W Lc8c4``0`PD!'xW&U@Lq" t0UpPY)l,rX?L.ѓjNJ0 R" ] !{XJE9_"13&5Ns!"'}K|Ru#^T yx Nz%S.ZR`ޣ1):d1flҬHa5eQ"0`0s iVӁm^yW/>zc6LQBbU9Vv;SU b*f6Ue+\ϕ[UN2Ee*8R۞nNj'`̐0 qrDŽ%sɓ~?))j:O8E  MyE_4sw'n}w=c? toن*rԌFB@ PRRR=r5-(ܢrT*̪ܦrΦ׌oVN9!\ym]( ?3Ja'g~0׋v-;22B`:d,@oS8{jߗ̦p­ s5~0Mpʭ**̦r-*(BiG)l pB9c% ַĩޣhalg{'#8s+0iB^{H$Gwm+6.]l]]/^܊`8dl7fW1iZ X,Uۊ5IIv  q˜i-çt;׳o4;oO t8v/n]W 9{Ĉ3j V=λ=}kmf,͞T{ n^o`OrtA3RPN=m2(BaV5--ĉ>/)))>Z xSiÖu*9PEI1eG s' xp"|`ܰIyp 㞷VG  lb[f*))IKKZU}:R,P8g;|E^քxt_.E@/+r?Yl}NRpy1=4oWXv@!]-JBٝv3Ƶ R7GJs}ڷooc si{]r 7дiSnlgc WXII嚩K|A(\" Jbsluݣ|ႂE]}ڵrgS][?$Nr0fR,q6G\E،+6$9Y{G-6kR*K!eX3#[tlҴiSUU ! !DӦM۴izQFl6s^^$6ox<&ꉉXԔ\Et @ bŊ֭[7m4+))HڵvY:3BV횵u:Ow1^Q#DP@$g͂d׵e$?(P0nw=VZx`YP(F"=zhfRRRǎ߿ƍqkn*3YՐǭaɼ~ؼ~iӦ`GB?^PP0nܸrsrr~픔i&z)3hX@ pС۷骵Uf.NMon7@ XpȮ[~&"N:egghт!$ 6m͵Zݻw۷o˖-y8+BСCm۶B>}ĎȈ Z0wl.ZUTTt{>|ɓp1snX۵kg(*-BLӴC8p84ucz  Root Entryz;256_28dd4524a69c4dca*55ْW5ْWJFIFC  !"$"$C" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?oj&VZZGjͻ* 'A@Z>^Xyr`h8c3&5_W]s1?n·w? ¹*`IYƛRn:!R)i5׫35-CRK9mmoqIڤt$ TY_j6W0?k6A*Nw.G^5woYM"kG Ob?se_ p_[4Iay|5'UTRtpe+~uf'?~uf'?1~tlߝto|ٿ? Otlߝ'z7]?!>)6o.B|7>Sl]s'z7Xz5[-Gt"bpV܌c\sȯC'?O &Y[KuỸDi$yDEK"H 0qx[үIł#"HЭ2sng>=lf?7G!>)6o.<Ǻ[&yvSbq"K1R`=0ix\ȳxvϺo,'3ml #>h#=f'?xQoF)a0#؊?ѿ: Otf?ѿ:?OoκB|7>Sl]o|ٿ?OoΏ= ϔ7G!>)6o.9=dF'?O cdF?ѿ: Otf?ѿ:?OoκB|7>Sl]o|ٿ?OoΙqq8"t~U]W!>)6o.B|7>Sl]lf9"[g>q..-m3I%,GBILoO`%弆qg#&F;+gi $[)b`l#*A C ԱEDE@@^q?PXWF b@!m cCPSGYO$$r[cG!Jܭa3o@GwM]~I*|]>f3i3yy}XL̿Ҁ+@-F.,.^|!eB=G5g(2yJV'j<`6>̿ҏ/ {vO1yO͌ 8Ey$Պe}W}f_?@f_?GٗPz*ٗQe^e}W}f_?@f_?GٗPz*ٗQe^e}W~AN2yJ|QjB瓒OzIhՙK#=]fKۘi XToP|.@Y PQ-ְ ;/sqߌӸx3G[ﶭo/X+Hm㴁bW4aC-:$EQbm2Mtb#$axn9FHi㻉lD#y(B W3'}'ֺUᶂqlG@R 89c罯o*;-O5[yB;1*9f'M -֙6"C'Nq[0<^G{߆5k~_:uWx3 UaF@)pWtO}Zi×,T\-o6,S LZv Qj?7\k<2YCiooy-k &x$HVAܔ?#r2x_> ]ҵ;[y-D TffbjprbsR5k~罯o* _IA-=neb 6nR ČaX*VQ+;Hb־2DLpwSU` Qj?7\ė<:hu'̟$ G݃ ㊣d; YXaxdPnV'CARİ%Tw~f=Ty W?\ksf1HXVfZ=nH~f=Ty TP~f=Ty TP~f=Ty TP~f=Ty TP~f=Ty TP~f=Ty TP~f=Ty TP~f=UjIdձ=2}j^/6so{kàK!)$6@%Gy{,,}v7!=pG<)N0䮌RN/]Eh*K9erʌvR[A#[s"`h$z a8DQvS6Jȸ&M}=+}*H12PNKlzCuEZeD,ܹqZQE((c2?*uPp:(((((((*k/T55ݗ(_CP׿j(/hmش`#"6?1$~)=xKmv}.VQ-[2lu"MjZ]y.4{P$`؆EoO- }J/9Cs_jiumwm4r(`T??/j7p45i6bI"NܼɛVkasj\\tSlDW H2H2I>UI\|C2g'J'OJ]COl2i#q oQ Ľ&χMԞ=a!q 0I2d0km'ԬM6PLsDq *6?VƀFfA6)4{gIuv.M(ܲ9XԾbӠs[\CpTKM q;p{sE8#XBeSyn \rd,g آ-M\pjֲ<(~Ld{soⵕ2`zs@ ER.`Vla w^P&,K[.f)bOEN\62p\U#{?d[ %-QEQE5ݗ*^/jke5 kvZYݪch!$G~q޺aG\[(fIvmO8uC!sA2K1q FvTS̜d7 D$ y=0N}sGDtҫ]\6~bK v'bxSZ[ie<&˰L?/<% c=<`gt=z;-mިmiwk\HJl(@ O0y0`1\->H.|5jYOsn,M-юI.FT߷6 H+{x(0SjZ|?sf5Z3E \{3ZZ?n>I w%s1:ŵ]BF8evIԜ D9|Dgn ;"ah8ryQzK_Wk;OUY4M,5պ̄JX 2;['T!x"ݹ [OYtm=緲9Q*Pw"Hĩr24f8T$O\jZ兂A4< d2s"UO ݬ#D'H ,{bD_sauƚno<;=F(:eIBNLhquCdxL>G~Mȥ7y{.0=[5-7Spw˶1&⻸A;c_<#}nI$l)/䃞+vBm+u[?:F zGPtgֿ gֿ VfB74}RMVsƲGٸF_;m 'ڪxgW3Kslg u.߈,.uO6 H2>NECf[ȶpZEmn`Rf(fE=ܚqE%Mce p{,4VvXywWV{pz㎵\'<-ydwvg$^\7Wu gִ;IΤx$RXVZ_C6WBzv jO>o}w=qg.t-eⳑ| rQ0O Ȭx?p2þ9d,m_YlG(ze,pI&ZJsrh|_zV)J5Oo-ݽ0F<+1ЬT6"T[4N~Roɒ14z5,DZhԿk5d&XEHI I†;Jq|ݼhD% h2  a';,+tW7xXܢkG "<,0rpc',d(K\Ky&k ̐$۸VL`;[kŶ{?CZe\K7,o`]JPYvrR&xTx'Nt5gYiw0-S ]i2/o5-wv1Yw1T3t~t??U ~HJVDf,!Ѽfhr>$x+8;2FՠoPu"d `ɲ[s$+Vjc ß9LhۂvYZֽ*v;eoaP #R) 2x h7$M.[-O_xq-am2>T|Ŷ|uRκT^#<]jVH '4PĎ{ϭIѽ$k训X5W d;p{xNK#qC`H IA8#'4uCjUgKg-%gu?iV]K dgLg鲖 P@ܧ+{[5-29=#EϞ#8Anrzlpiͭ8劒NCSX_CPhj ˗yPhyr3<6w/?˗yPhyr3<6w/?˗yPhyr3<7J)\b._1@ <(g)\b._1@ <(g)\b._1@ RrUIT\b._1@iT\b._1@iT\b._1@iB)\b._1@ ~AQr3 nl\O @N{5 ck,{hϚI߆RrخY `xĺv2CxNC'=]<+3“U9;DNJ:w"EL(iɉ.3iln%A;@$o\ּ[k˥wnrE,q4Y>^ 2>GwlrsҴt/隖ӗUk{8nH;\V68+V]SLo&]B9<7kLM4{hk7HE,f ΘAg2<#F߇5RI$Ey` IOqgsX+%ʠ$0*tXQc)YXcr$B36E 4#G/k&^#/2BUv849ZΟoIJ4 p@;m 1ڀ FO4c? ?#TΡbf]szg6mJo!?5w0@zu"/iXF+_#K}<|3jXn a91+W##>+_c? e+_c? e+_c? e+_c? e+_c? e+_c? e+_c? e+_c? e+_c? e+_Mu2*KceP/cŞ o!(1[mFq^]={3ɔ#5i#^ymeȻ`s*˸.O!q==Sp%"6 1;<]VՄ7R!yt7FL& 3٥mx^X#%4՟cpi -NNNwQKÏFC}dMH$b .z/%ý+x-hyO)G٢iWXI'Pўc9ؙIgVʕHٜ0uJL{D4?Ξd8fCU\>hHH.$FHWH1~aoӡhe *Ձ.g=Y?KԼ?j7^)W13 %NFx'_?2=̯~hċiqqfS۵#p On/,DJ9NswP.}X\G3efq)`vc1 y?XiW*edH ߺ`M7İG,(f}hPNpn)Xw9-SՄ5ƟZ\)c i">?_xKO3\H o yj@y$@$Q@(((((((((CSX e5 \tWa/G Jhg'ėAK#$`+ZB] ;2zV{n,mXߦhl99pzP?Iu$Nf?R}j?F4ig> %T\J6IbiYiVԤ,$,$X_7+-gS3LZ8,0!stAm[ļq[.%q|G+s}il-:$kgR3IE;R]#2eFx2[DuVD)6JNCwA+?&#kM6Om|s*s_.:9^*kWdvw o?]ȡtx,L2чl6]Je?-n{,xEU 5>ǪπG_k<7Ep/ӾЮ% W{! uPҴ>)%syUDv2'Ehn1p` 6ɪqJlLǀ\熵9'\IhΫmLqB8Pp4O ͠me5۔y3FX;̑7Ofp 4u.}sTQbk<,5Ղ#3gxG&{GxS^m.wM!eۥ,UP\łdsI~ǪπGOki-FC*smnRT c]OV^SPN k9=ʛizk麥}~/8Q.M.L `c <%ͱ$xAK/9\mΏbͮkz!YdtkL<8h_<|]f>ר]̻zEFwq;Ǹ@'u䌣4MH#AE\fZj77 C""G|n;KO&_Ώ&_ΣuK}VI! )cles8<X..sXI]<?:<?:EHʞLL[*y2t~ty2t~unպ(/GG/GV LL[*y2t~ty2t~unպ(/GG/GV LL[*y2t~ty2t~unbc0uao9Fڰ'MHC~S{zhߓ/GRѫnYIclw.bbz8du¦IKӯÐϴ ]nEs(uMDj pbH$[4o$ p~se%-İc- [x8JȪ(, File Menu File Menu
The File Menu appears slightly differently in the Mac version and PC/Linux versions due to Mac-exclusive wb_view Menu (contains About wb_view..., Preferences, and Quit wb_view options). On PC/Linux the File Menu contains:
  • About wb_view... to view WB version, compiling, OpenGL and operating system information.
  • Preferences to set user specific Workbench configurations.
  • New Window to open a new Workbench Window (containing Menus, Tabs,  Toolbar and Toolbox). The shortcut for New Window is Command/Ctrl + N.
  • New Tab to open a new Viewing Tab within the active Workbench Window. The shortcut for New Tab is Command/Ctrl + T.
  • Duplicate Tab to open a new Viewing Tab that duplicates the settings for the Active Tab. The shortcut for Duplicate Tab is Command/Ctrl + D.
  • Open File... to open any file in a Workbench readable format. The shortcut for Open File is Command/Ctrl + O.
  • Open Location... to open Workbench-readable files that are located remotely via a URL that the user sets (custom) or standard files through connection to ConnectomeDB (with login). The shortcut for Open Location is Command/Ctrl + L.
  • Open Recent Spec File... to open a previously opened specification file from a dropdown list.
  • Save/Manage Files... to save and/or manage options for all loaded files within one dialog. The shortcut for Save/Manage Files is Command/Ctrl + S.
  • Close All Files to close all currently loaded files, without closing Workbench.
  • Capture Image... controls for capturing and saving images from the Workbench window.
  • Close Tab to close the currently Active Tab in the active Workbench Window. The shortcut for Close Tab is Command/Ctrl + W.
  • Close Window to close the currently active Workbench Window. The shortcut for Close Window is Shift + Command/Ctrl + W.
  • Exit to close the program.  The shortcut for Exit is Command/Ctrl + Q.




workbench-1.1.1/src/Resources/HelpFiles/Menus/File_Menu/File_menu.png000077500000000000000000000364131255417355300254770ustar00rootroot00000000000000PNG  IHDRʃO pHYs  HiCCPPhotoshop ICC profilex՘WPM{2s9s92xIPQAAA ((` GuԪջk_t`/%PPQ qfy@DPr`|52 @@P|`$rVcP 9!&q+97_8gG#L  =*1*(<  #tddt{ KNe$C8<>BNkERQ[k JPQ.N8*ę:]`/lSÌlě͉ [DG?d^gCM-px߳D[7(:&%.<4, &,o(+ͯ( :?^B*7Ϗj; OHĸ Df^qEP-;p@HdH jh `9X%x#4!"# b8"?D!HrE r,҄\F[2~~A!8C3 #Qф11qq $dt63Yق998* #2+^ , hVQV V IvG[l:{<kvVWT޵~Ά܆ƨƅ燚ԚOZ[޶z>`|ZLًs/K._~n>СvEJe'SgNҕҵz55kݖ=Z=e7 V`q&fݾԾBo- N9 ߶=:l:<8b073;9}Gջw뼯rs\ukBm=ړ7n=4~8xn}>V~t{Kkqk_M9?8~TrZNY#׳r @HB#(pEd*/FǮg /^74Vt gwXbX99{x> [D j/vGOdTtO w/+(UD#Zu tw GLMo4貼lj`Sc[nWb_PXtιk[{Gנϰߤ]v]PJEWxdD Q}S1oP ZIIɅ{/.Ig͐ٯ{!,~G|u=t>mSWaةE9/N*RbQ:czֻ6.[ O4ZZm/\6E.FnCX2^[B¼""b$o%{*dd*(+(2~TQ?OEKZLC^1 )Wy)6ݶmvuŎEN ] \Oxd{HN8XTr+t"l.|%"IQl"1*q yI7Ǚ&n?@̌CYiS<{cI|BSEOזt=,_RTQ:k^W\kp?Z$Z .E_̺TvF~^#v^W5a{ӧ/ҁäQ;wk]h$˔CiGFm8?yɉϷ.#'.LI=(v@D}g Wo+37.JiK|iw P `Z@:YPQ̀, P=HA4|NyAD qAbH2,PR(;T" 5@;7?0jTL? 댭~JqzLCHUE$?BH/ݩИ*6 UsW021J1v22M2X2YYشئp988qskp/j(4j-sg(ZnUݑ/QUPUZRSR=aiemcjdxԨҸ %m]}C7vW=<.yo_y)#>8'd8 ng2;jOX8JHpRVrujGoznցOYf]9r:'1^s|)%[eyig.ם=wUsSk|%˯::_%w]]f k21j~G)iG%Oy텵7o%ޯ~v[ɖA4 A#mB?dRt! JE]A-iz8t3z # Ŵa~`E78M\_O"ST:T-Dnb>@ʤ4ottkq "&!NfKVFV6SA\\]< <|2|Kv;…=Tܑ>-Cݖ/S((+)WiQUVw0TВՑЕҷ0024f:kiemedsҶѮÆ!VǞ8/Ub~΁GzʄxKCkK% N>_@m&HvґMG o$)BڑId ńREy2Qͨi4G*9&3ec/`q63 =@"D&4 L:BzMK=KcKsV ,] $} C]&Ow,,X5Y'"q˜'TxtyVy\ =AY|a+*1b%IYHJ=$"/(( F\F{t1''~ :kk?kǻwX'NX[ w ޺15u;S=|/|_W'>wo+w=?خh LMOoua>+v?;w/]߿-?9p'5+jcwƇֿ׿S~O_xꎙATƩ,\r~v3wS׽xp]TkDE9C?7}2xa.P9hhQ{zSuj}C_[ۉ6t=//U?=/灠Q;7ңx}?)_k;F_{k7h}Э7-߿?FD"bqѩkWN?_= ko^XSwx6./=^U뇜\7[>|Ǿs5ѱ|3~&_x~|IgY6{nQ|v;)[%k%Yvu'_|Nvow"hjz` LٷxO?7?߹ОB{Bx<3+ C+G 7]M #=z!b7n޽;.}<#Ύ}>(jw\vqGvPt<%wqmX?Б]y7OGo7n>6NsɧVomt\ѷ2Ԑr!:]{G'HQg>2 CP rWrW> <`~'_ &+B!h0t#I1̱#;8w#3cJUV+O2Ap (V=_9wqd{4藎;GO]y(mVUap_.O=~0̱g^ki3{xpr 0n*?{/^{ݰxak>E/9Lk'}Akk-/N?7شbA_Ya3zŒ3DCJ6% WחΞ?'ڝ<] fL!Eo|S"G7;w??v>{M\ hWj{Ew8%v_ʟ_[ީ%}$[Z2T9]'7ժ\`S|ah=TY1۱ac 04`? 1{N.H?x`H+W;?ŗ0ܾk۟ӏ~t>oe28]gOW=g%we|3BVNkGLͭn>5W3ttʵ:CP\1ߧ=*?=1L]F#<&fanіj`-/廢cNy0?uؗ0=ņvi;3?&m]v}lhڵ|1 0]$v//B|>6t1G{dUBĭ 3+!7djCm?[o_;Y&#??%h>$afj0`t c}~`ӠBDe*G-_˲,D3yTk(DJ( )d( L@ %Q6N6"d-c4j~.FM;ٹx2QEA~AL[ԒaNLԂ6H?%Qm DY⣚(FSѻd q#*[arm75@Ak" )͚taI1@cy6)R ܥ:0kfDNs"/DR/@ |G-nE3eìiVzd(+pkD0=#bjN@(y}"m\!bO,,TvC|2b=o M`ɿG?(_QSJ.ƲIm-[yehD.Uf4$l@ioyʣap=FD /`^4L&)II?h* -,";wD[`       NS%Ꮤ\u[$,gk6J>wh-%,Q#ˑ4j,q #/:Txc;)J,dj>]fCVr1P}LQ0hy1lTr?L]m ʤb O6(">:p#n-y75<!seF2,7fwWbĠi2-ZiW&}c0[blQ.?,&r}՜HZbA{LuT*Ah6DŲ/ \nзq!. U2##gJ3"3ňWg )`,oLxR- zxh!5jڙT/vY'ֻ <1Uц˨k#G#\Ԓɼ9qչxD#.'O  EQ_tB0l}΢&%ˠV:WfsZI۵\t1bкҲs|≨m"A l5i#}ꗹ*3Y|,Sך\".xxL40 "&<)ʚdDfG"Y&AE73ňȈ)C3&E]ࣙ͠lmO }61nLzPEZVUQ l3bu0SF3)l `_f Ġ dpM 1F g}3}5S ]5/f ;uYrQ.w{<9觙16[7S40Z5DymHBff 0L0L@ @ @ @ @ @ @ @ @ @ 4 6uKqC}O{.|3S1U-YR]LgOƤV3 >)F@]JJ*aJpxܻw1fM%nJ.f:-ZYp*0&f!Ao R.‚c0w)0:c軙}#J(]B{JtSSrso3E]<1=򳡨qQJKoR .6+-nF &sW3t_O,%D e FiOa,x(0Z &sῙ³a6SHiv1T)w~{ hbbbbbbb TUEeA`Νhk0ao *n >)&k(Fn&*VR\bw@-慂y^r Kpog|d6n!ODk6wg{,Kw=z]պO@ bƦRLb}NӭDؽ]#-l63[־`N eZp,"v|bĀ4o3; {ow{]<ѣf6/R:"~m'gz.k&UxHϪy׏PP+V itYۮ{S5DX;G /sj5]T+ڌ3bRl/]< w @ ڠw A}W|4SgLчDw3FM3Űg3bйohm7{kC͠ʣޫ4D)LtEanjR%3/|V s[Ц^4dn@ :w3jըq-ea1ׇdzҰộ1)&NI*誅In@ j)`nVryx"\j]$\{,MCnLtU I*߉,ÅiN:o)Z 5xb˥*_Z"뇙р58|V5_˲[|A.` lפf 01诙bp74 cnLa|/r~cw׺eGU QJs;_=81b`CЄj kJyZg!^:kM MF⽩hL)[Js$]ĹZ*ZM#'c7CBZuI)V3֍Bf*0TESlS6ӻ@GUa\j9S] )d .qkjc3@clR am6.m؊CSEskqWWem2m)#֧B\fnkuy1l kqWǦ| ML```&E 0S        lc 8Hi=Srֈ%l_zޡ] =c%JnfܭDy;tԌJ.F ;T~y:)Jr1l~R^Oy1\ݻ~MO`c!qp J.fJKA)6EBغC_(Zކ \19ك&ܮvfct̫vJ.ּѫg>zŜ¥v^RAZϙOUE2,GѶy.\I.hb|iI ;hxx±Ҩ{qN ѻQb6'mܕ"tQ(̻=A,kEUDNf%Xޡ`h~*6bY6(wmwO4?KUTu vr5Sj&g3x{:8+*\'Ғu+Mm"9"uv;s7.6&LJnQ"ht.Uf4p҇ ҂_\N׼%qrz8jߩ`p,ˁy:wA)!O $ӍyD / U>M`t)Γ{Ph=)zcÃo`%/FLg"#-#3ƃFtF !`R5fw S7n$ҎF$Hۃ!kPQsIc8|8,`XbAh9c25.qC;~7v?x]`&E>o jݧ7CNr0j. 01hAP52 . Urڊ!lf. K\`4\`c9BZeeM04. 0A' g[[ KF !֕]Ӈ. 0 3>VC˚JՌh4ShkC%TV3Hmƀ Dk}Ē;<\J`E31f F{mbbbbbbbbbPߏ֩&C k|³1عs' -G?s61111111hK%X>6a›t)^ߒ`v>mQԾU=l% L`c)^l,K7;F'&iɚY+kҒň5жʶ2wUGM Ġx]sAP"-ZIv{YmR9ϪAT7~=m(wMZhYU|Ġx=9srԇ],^4o("2+߲01x]ŒT־ws&zMAuY(niov~3ETWwqTs_/ ea}4saj헭fjIm$*&j>ey=YVD;۳D11111111111p10Cۣc`b%у/R`@#nNF0YAkCZ6VºEKЫ1y9/`@ :-zp%m!qo]PLhw|'ٝT̛qX1Ϻ1}M`W?w'ߣBi Z̍] j Qyyc Zn^Q̆eW*= S~wŸ`ؘ0)|cA-Sࡖ h>…!2zXeK$xڼpUrddqsy-ǝX`4Z3fCPb@ [EZo|bM!@e1w 1s .UQS8qD1111111` GDYɚ+Jn6rJ55O\:EbR$yQmϪP,bT+W3ƣL› 6}l5DXj@g#V 8}TMբG9!PP#ZW~N< gh خw@ @ @ @ @ @ @ @ @ @ @ LAbh)]|z#k>/LQ*Ut# X:j5hչVGHd^(hbE#N=۟./3Eg-yl,K;')hm]\QHf ~NLNnցbo7E1_'-,4m,.IG_buY*բng3Ube@vuPT?fEPhK}ZWIoZbAtؙ)EacVrvU */qxVZXෙB=r_D}0_ |RgRg|棑b}#bx!i>+r/۸x"j9Y#- Preferences Preferences
Preferences, accessible under the wb_view menu (Mac) or File menu (PC or Linux), allows one to tailor the Workbench viewing environment to one's preference. Changes to Preferences are saved between Workbench sessions. Options include:
Colors    
  • Buttons to set the Foreground (chart axes, colorbar labels, etc.) and Background colors for All types of views, or Chart, Surface, or Volume views separately.

 


Misc    
  • Logging Level to set the logging default for the terminal window (sometimes useful for examining bugs).
  • Save/Manage View Files to set which files are listed upon opening the Save/Manage Files dialog (in the File Menu): All, Loaded, Loaded:Modified, Loaded:Not Modified, Not Loaded.
  • New Tabs Yoked to Group A to set On/Off whether newly created tabs are by default Yoked to the Group A yoking group.
  • Settings to show/hide the Develop Menu and show/not show the Splash Screen at Startup. 


 

OpenGL  
 
  • Setting for the Image Capture Method (OpenGL computer code) used by wb_view to capture an image of the Viewing Window. Changing this setting might resolve problems with image capturing on your system.

 


Volume 
   
  • Volume Axes Crosshairs to set On/Off display of axes lines in all Volume views.
  • Volume Axes Labels to set On/Off display of superior (S)/inferior (I) and left (L)/right (R) axes in all Volume views. 
  • Volume Identification for New Tabs, to set in newly created Tabs, On/Off movement of the volume slice/crosshairs displayed to the same plane as the brainordinate selected in that tab (or a yoked surface/volume tab).
  • Volume Montage Slice Coord to set On/Off display of x, y, or z coordinate slice position in all Volume slice plane views.
  • Volume Montage Gap to set the gap (in pixels) between panels of Volume montage views.
  • Volume Montage Precision to set the decimal point precision of x, y, or z coordinate slice position values in all Volume slice plane views.

 
workbench-1.1.1/src/Resources/HelpFiles/Menus/File_Menu/Preferences/Preferences_Colors.png000066400000000000000000001225411255417355300316120ustar00rootroot00000000000000PNG  IHDRU;u?7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:599.$9.WVhiDUW}o5hil%G^|6|9|$ q!q!=|J]SE$I$Y*A,Yfyj͢H$I&tL]uI$Ir̚6jZ/۫}/X}x`^ `Zݏ}^=_je-s\힪ʲĕlR>vBe*TfA@U"zB%EmѠAP7i]/֮U(*9 gCE!C-#_`p4?6]%*JPZ=@xLJam߇H?i=r}oW kLloP~rW[nf Ί#@w[<5)>R. ae]c0KFSpd K$!|&ޘxÞ~L$-73zië[{MЀw*@WV._c\33gZY!G$ag'd)Bb4mfXnqj׮]>\|D=tL`jq/%WVLzV*`1ZA&@ @&=11^B|(=6p(-Dշ|/e:G4y|݈+I9(m{e`ƌZXS Pi8R.cw>c5i%ֺG6rb3(@% Sr>b.2"Xs-fW{ą chF);KDPbu\ ¹6So[ kR敳ud۸fg?8xJ&!RsD6]\J%PiЕc }b7anYiF)Zkퟀ9 Bj4BBBʿ8jԨQ>t:jcĠ&(>R5תSXM+7. )Z * ALzsDD͓uBz1jC͠O_>ɳ,ZaߓҔXe(Db<a;&2vc2"Z_vvVNM-N~>'XC-!> _zߒ\>1Q XWX"j ǂ\ڶtxia3evUN)Πu&=5At2|ABP/w=+𧢬VJ'%4MJ?>O[f4(GX< ߬=3u¿gm~чY_YE":`:o8L&Cט%0uoZ摾 գx$:&ԍj\R3#nR8wDױsL˽۷gZΐNVWɛ0l#uu0X=[|q6*1 /Lj9ڭTp)۾1ҐfBHMؔX}7[H%D"5A,~`jT6o/W.5wf"@n뾔{)߶V|뮢(h2t B8}LztchKPP Z ˘fnWq},3 #8h?2l"=dLm^z=Ŀ2 dA B0l2ɏ-n< 2 ķ`Pb2aWlf0SB)MifwHzQ+@eLJOϦ ⣦AMܓkO>pJi9ݻD66d__nzU@)`Inͯw?TNyѾ%4kk0 JӇ~}SC 1=la?ߥnfq/Lߟ+<~Z ?(X0Y}G{ e}mKR7^I azyXB3CI5%h1_979uTUpgPPPu^FY 0"`Ŝ2 a!VSKv`Hng%Y9%9zжq:<jJX =:9X36":?o` Ь^a_<}Ð9.8 =X^ FshhrM[w-$OÃ2SX)},5? ˲,gΜaaa7T *D(f!Qb-Nl5CE9PspZFX6_?MdFAo9,8\5k"BFNBbbʸ#f!+1CDDY!p 5˘YuTuׂG P K.{YN'_%&[^L&S_(ke5dNc)k?\UpK~խuUB` )?ի][^Acpgj9"PJ}gu#49HĽ)n[έj]U?*ǜ2ữ0ƺw{R5 dn⎷΢:g4{7:I=!Cܚa (7lp̙r/ h{{-Zr8Nepq8NeÕp*Spp8ʆ+/T6\y9p8 W^élr8NeÕp*Spp8ʆ+/T6\y9p8 W^élr8NeÕp*Spp8o}$)///''ѣCQEQBvn]3B%:AgX֭ۢE8A~,ڵkXF6qR?{fk׮z6;EEÑYPPҢE ^FW匌{5A7pp8E E9~ٳ{P!\y9Ս*(LMM׎έF ,={vqqqIIIVW^Nuj-c(MU՜}fEQ;St w^iPwoboVN^6ZړC `j1KH~֮]ӪU+~Ź(CynwfffN"##J3(;t$r;t?#9Rg\{ A|^һYe:~kq{əg!KQUh+PEQUb$$$l߾qAAAU%s)EFFz1BH58dzV3 ~aH,LUU@]3V߫(* 1 wh,@Ac9譪*TUQEQ$ԩSѣИS)EEE5WEQzJv~LF\) Kf6}s0/ 4,+, YET9'>noEvY 0Ȁ"IWQ,e*[TU$3 uϏ3LeyV^t:k֬)cLQQ%If\RBhB#(3&|1[2q٬/=R x d"W"y\.Wr v硏^{elL_s8_h}gY mi+; gׄ gGӞq6d?piM{ewO6 [7~팀Է ƆizevZnƀ3}\@k>y.1 <;@@rѪ?.f{U UUUE!0AQn7W^]C7kXV+cRPD;W(hv_ $ nmsJ且c98FW tZ OsRhc1P ,hG߯o񘍀itZR(Ho`Ї"N .*՗QjG㭣<Od#LC(;NjL(V՚\FC;~U*#(B)$ W=єV^JK\.WDDĕaB(Yke?|Onv-{q[EǥRZvaa<̈́vc^V<|Jp5ծoһ_~zmy[.kb*hZH(x ,>O5x[x:P V#'fMKyi/~V cY>Vb+YL`\>u(kLeWvdY'c/wԮg7MXc_O~,qnJc%c;.RB$Ir:z^uq87@`0DGG_pVZ_=;W#wU-M]˹gcbAFpW2ZnL<ܱDH{fV"`T7oֵ.cצ[%KgwN*`0^q@vq}*yp2.nA!!Ó#-;Kn0`$ݸ]VO-nw}^UU^3g~ǜx}%$$ԫW&N8};wz<-[kqT7*6 I.]t;vPk*BCCի׼y:uX~S3Eqr\.nw\,srq,KPPPPP^[(-\v9M|; U s{)!Dxp8Op8ʆ+/T6\y9p8 W^élr8NeÕp*Spp8ʆ+/T6t< IDATXG:eO~~]f$Jtbԭ[Eqqq7zLL̍epnW/g+横n6v\59#,((HIIiѢ ÕSݨʲwQF5hЀ/ũpXQǏϞ=;>>gϞ}W^Nuʫ(JFFSSS###)/ɹPxIҳg.X ...))Bo˩n\-e ׷o_٬(&T{Gn}0 w o^{;]v êڑB `WY,![vmLLLVUn pݙ:uO+ϔj'ώ~L6gwyjGҟ>~)ВtX{{>OĚP~?WfUc_ǻgw#yLQUh+PEQUb$$$l߾qAAAU%s)}(J^^^dddll,+˲r}]3R߰zʬWLWTUU>6 *̎ii烂HTܐeTFUj/ʋ֗;"+((BԩSFgp8wcޢCF(z^` %^UUKNF\ ol^aC5(YEQeŷYIVff[: _+_py^.JUPWUOyYe~$y<Qcnݺqqq&SE.I8jLiuNf͚ 0ƴC$m%%*v/$ 9 W_Z>odw>àE.U {]n)Y+lw Ze?QSf@fo[֔a+}[+E ohS#o&7.yvo4ݲ >؄מl4ó,߶ _J\k)#rڰm6 x}L_;.Ӓԝ:»@>68ꑽdA:u]K^EUB }ײ PFnң ^bXVTvPЀʵ{ߌCyoS\]!fđkyj }d]B!6x2㇝;3$e̟G ua+ٖ~ؒc tŚOMY{ÚQJNi<;cm;HsRBdzWN<=u7Ҝ3wo[ֳWܔPFv$SB).Ȟ;-I_0){uyPﱍm{[^kg:ʏK91hTa `Xf쓃ùmy Nż> #Yh`_;Ȍѓ|u|';),OHyJ),-Fo,jކ}HX6}( [h٭wgJƌ{Gpmvzf@̂z])(ZSyA)2<$B)ONX}2^*ՙKUU@:l6KjuN(=ڠxQ5^ձ;~Y6Y&?_4 rb'fܔ:m'? ?퉿o&nƃ'繰ِdJ/FZKAi0'3[ -)3P@ۑ-B"T dۋKi/C 0j:S1"E8gOd#M_A*; oBEJEphJFK.\.Mp P@R_+~,MKܹsG.lXyJǼePRpO6O8}bVD؍FJxKC~Gʜ?2sH@Ӿ?V-= ʾzH -B&7 !0F)W\)wzs Yg9渔Ί:Xşv;)pCb0nw`Khw߲ߌdhҤpU(#sq#XÂn/8gòyvֲM`Lۦ<?r ]2 m{=t֩=, ^yxps0Q 0dʈjlڵE[cZ{ɹo:Vo c K3ĵ z{i¸"-|nlBXQ+];z0I^Iy3Oܮ>ǼY%Ir:ZvBF(=`0/\PV-65w7,jq$fO d4 `(j+j4C{^ֽ>f>7`mK=-mĻ14q.@Eg Bk9i;Z  ,O􇔗)LP ʨߋnnLmHkv1WjKe{e˖%%%5lذ) m`EEEիW/==F?WYNVQŏFEEQA!\oCxjׁlrזäb><QQ׭P?*vׯ_u2pzJX,m۶EqժU?S>?7M.ږwqfتvCPTTbŊmZ,j80eYTTtر[?~РAsOMٳ'---<<_~ FyUU9s1w_BBBzmĉ۷o߹siٲevnW^Nubo$ҥK'Oܿc EQoqBN W^ԩcZ 3Eqr\.nw\,srq,KPPPPP^[(UUeY˹}hQj_8Ky/! K?ùyt1élr8NeÕp*Spp8ʆ+/T6\y9p8 W^élr8Nes%I9z^ʾԒ׵kFBD',Kݺu[hwÏU2r8t)˟unUUsssfs׮] \59#ܸqcJJJ-9k*,{5jT 0 <йsǏϞ=رc={o`}l޼~8qbdd,˄/ɹPPUVZǏ_`4)) sQ򪪚׷o_٬(Ju^ K4W9RA\WN-{ !*6%_~k׮iժMWn;33SNi}R>9\K!cPu?st-ϑ>kSroWz7ŕT"_t>9$UֹWQUU,KBB7nTe^r8ʫ(J^^^dddll,[-:Ӏ^jij1/hGfa7KnЮO%Խ'̶jBBu]z5B&%UUUHSѣG8w СCFQ(^C ΏAo=9iS`Hh@9QeYdIE@T=۟zi̬׺*$+H|vQ+.J  2HO2EUUI<(=uřLp>HZz*b'7@զ7ƈbf{B9;ʫ]腄t:-楔RJxW\'a>J\@§?ڭuy) j}H'$ٰb P}c_`8`q`-Νԧ>~ȔOmN:>&zIp6,wr3iS3~+_>wakm0v%@O(uexIRJ=yg3vl .t뺎KUU@UUEQ4N3͒$g8pJǼZEQ-N*@Ţ2gSjG3ZJcVM-і[lv~E"rY%Sю7_7MųD66.8TƄ F>4ϗi60Uv+RjG㭣<Q R5RRoT{3ƴSS)l- *vHoaU?( T$\,2sGSZy).]r\M| \5 [ii#c_+]"KgH_ Hz{G=ޞP RQ\X^JʘDPTt">Ȼ@i]"zJqXBq mL"EF $ZZՎS7](eQ\Z QUWjE`*a) )JRwR-}K)v|jn񪈽Խ5hs87JL1 n;0ж^#:y!xgu;c137l I%^{A K$a wg9R^ %i֭U;xjda3KK)5x[x:P Ҡ#'fMKyi/~^Z Z->]O|%FӇ s1fmyw}=i,zcƮ0v/_L qW4mIN`C 1`pBZ"""j;S"'|jhtqļFA0cʴ L`2_DqNzFAYF7/N}f|R1p1/Ku,ˁ?$I x CZȩSK7 0VZ&ID|F9A0Yj(j 9 ֟+Bsx) ]|sg6KO4F;QX=Bd4?Gɮ,^ٳ˖-KJJjذa9׍Dr_%1U^FƘ cMSãB[t,WT >Ǿ5oйo.ƅ\skԢo]5@)v_>666**U; QZO !m۶(Z6pn/_1<Cu}2:PІ}VXQTTԶm[y9wG4;u4{={?ׯz|S(;)z}z~ŋ/!!^zF6QTTtĉ۷ܹlٲ]v5(c^ I.]t;vPE>s tz5o޼N:VTgLyz]..Ke.[>NPPb |tsr,\v9M|; U s{)!D~p87.p8 W^élr8NeÕp*Spp8ʆ+/T6\y9p8 W^él4$Iyyy999Gu8/!۹vH!b[n-nQ&^VQU5777==l6w5>>>88/ʹMp8rrr233 RRRZhqpT7*,gddݻwԨQ 4Gq*-VgώٳgEƕS(*qHJ)!Crn/*T(^ٳ ,KJJЛrWlCil6+R} ~ّkoV,%= [xDפ]ðvү_kĴjՊ_uq&P۝٩SHRL4Er]Is.3͡u> Com}F= AzɇZ*ps8}ݺ)e<=sxy1!DUu@UEUŒ}ƍUέE(yyy^,Uo_Ցmڼn_[U 7Xo?\i/3VFDޟyB%Y u07"UE Tܩ|YEERN5j߿?ù (:t(66h4j_Ec(Zrz5K'z|6$﫿?d`ETȄ(IR<j1hX: 񉡯M~GۉXKȒ(n?܌] ϫYe~$y<Qcnݺqqq&S.<8Kv;Κ5k Bew8Nt9].t\N\NvV~7r\.w0p׌9jԨ25v쿽74%LԬ#v-gRUڄaaH4& ah%+*P].3ج+v~qŞوB8vy de`DX{ \.~3ܚ`jJلŎew\.+1lg;NvŊ G8vܮVuN?2aŮ̴Z3:S?󷧥ըa@v ٝN;9Np8n"kAQvUqp8z-jeQJ} B ؾvKlTfz30{햤[=ֺ@3e.OT/%z6u:l$IUU>ÁswPzW(jg nTNo0?UnrD F@{3U1_|\lEf(Eb|@2v$6ue+^< (`dO ു 1g4AZRfK:N ^T\bLd,ߞ~2Pņv$>ѹ^0gϞFbToBEJEphJG?K.\.Mp h@@U_o]ߪK'˾9^fzDV||A]^/^Zp%"(՗Q/ ʔRkN#f%"kAWb Nv-?)s6^.@Ӿ?V&(ToQaž\[!Z\fav]o!댫.گmǫbvV-4nVHùqJfB`p݁14\Ty{^/ n;p֙/MaZFO^ZB־vpJ-x<\<`+Y@%}6)/9]Pp@V!UypUcs1sRkÁW`ժτ?A 4j8viV G:Ð;oߟ"7ys+ ek5o<]Z6jԲc?I`0Ф]3t/ɻv;" Nv\;(ϙ]]凂 eʢbHhYmɫH^^.ijE+ú^?\My4!HbA٥ݝݙ1`J|{Μ3/sΙ3E3LEw?_6/ex~wL])g iZ;-+ Jg;,os6rURy^ڝ=mۢiÇ߻&3ciJ<[ |iUEڝ=[ZurKzx###e2٭A{<8k֬pvOfUR~ry J0re5F3-W^?_Ζ VZu|x+<%3J^k$Voh^p:hi_)Դ}ӝ7@m\Q[[[+ј*˥Ç~ޮB&fH/ׯ}uHl6XPP4aZF{SJJHH8n׮]V=phf_icdC26n!!Rf۹sfKHHPTp;,6×/_3gΨQR ={Hr8K񓘗(K.=zTѣG'%%ĐBaΟ?_VVVYYiZ>v[#Kmtk@<Ϸ^pԩSF82C cbb 6`Z^5/)/_a1L 8"nGGPT*BP(Iv:FEAdsH+(j_r!ip{zC  bk!K (/@ k!K (/@ kq??h 2}H  SLq%6: >}:+++,, c! ="iƍ] Q^BovД&^9sRW4'Vuq:K]EZ8XU <+Sl*UPZZ޽{?bE `YdaaaRpH[/$ȝ$L~|Gm'aki9EyaY[~,54-د Dn26kIC>hGN"w[ʷ.uƦ}ÆFqb֋ {C1Fr-5{A$yw䧥pkyaƫ{(Ǘ?|'?dN_N] ҿ0®.xd0o}vV:Xeee0i^͎bX-֐T-+0b~\)1"c%+*? 8*ȳHrS csݺ21bq'm:w"ޞcˎܙ\[W̼kɳP}X⽃t6KEW)JEQE@ c^s'iJ[*h9[16Ao j)~p?&k\FM>8 ;8 lLP^$RC\'jGfʒ玌VƐʆYkPrsi3v\D>Jd/;G@ڿߝߠ:۾­7 cݻ¯c0$bi݁{J_KiKG@ \vKO8O|ĞL߮\.زˋW N$ v#8LW7x:({d:()3 @h_KKKKK`(}'#c/li8]U+|u]nN*ŝeY2iSB2eYϘ}xj╠==`m&zc?;(iWz@E vXE&ϔGbg@ߛ8Ϋ܁YwUlDr+ޝzN+V}x 'Mah;P[[[[[ShꛗG<]r?՛֖g**jEZkWEC,4(-2c>~4yɇV(쐐'?˟6=y~R!-L&-O]M "##\ҫGιx,,;yy4-4529֮Ie} -y+;iL ji-\[T9FQ$=dwe傅S-t!4Mƀ)Fpgvg'{p ,{+%k)iY(`׹3))[E<"jnvkniRV.py@=[Zey\ʁ=tz褤A!l6*++Vǎ{}k׀xommp©SF#q$%!??aÆ 0@V/)/_a1L 8"nGGPT*BP(I.v:FEij"B_i@Y:(iWpbk!K (/@ k!K (/@ k}tVVVXX! CzD5%iSSƍcccSSS%@F{)ME^_]]=sLR+d #1uP2h9zo[Bȳ.T{Ĉ䩋p;с.,[RR2~0wWw)=FK?ڔ_{t~s$eZʑ֋ &W_|¹\V^_w6e)TPy@"HSAEAR%%%y ⚹6C갰h7|q\PbvUJCwګuzWnYa<@p<|#YVoj$V>H{z (! pܹSNẃ@x+f;sLtt\.VA) ڈtx FeWU[ `La6p|@E#G4[Vs]G 8cܣQyZQ%ɢbccC fe-K߾}i(J1fa,a,0aa,e ,p )[Nit;5Kvݲ=k5͸u"ſ T07i$W5I?'e0΄7je?_g+e%W$زquFX.[.븚\f];onfeN\Ճ*,%XYE:g>ҍ[vOq'ܥoؿ]&XV^0I%-i4yozݜbX\b&̲pHei:<O*\nڿ;0AzYrOù-Vw髟|r֢}R7#lߟbՒ_5f)(:0iZR)J{@9ڴ6HzAAA~~~R\,Srw§h3ܱv9`21r:LjSb(O Zwt,=4xŖW^q`<8ZuU1 PP\@B@Rg~fd+g gU;jg10GRn@&NB?]_Ug76}T0ÃӎK'tR#`1ƀ1W.=plAc||5K0xڼV{Ai%"( Hv???R(dWI uI+=5|4W=_{d8RT,E&H~ldh{a0{cՆ|*j bLQ\KS9h}a?ppR}qf֖吜ߕRY(7ɰ Q~0 %xp aa`|hqmJA]-h"(.~S !:g.!巿2D 9뀺zݐݫoͽ E1fY`0М@6fL&cY3\-nOWtn·f+5 (jse@6B{a%&Pz߭ P:yF}XSVV+x/~7YڊZ4݅Cyˮ 5?\Q /<5"Hwbi/fPsQ#]\ r}w w}P\<]J` 8Tmcm٢Ԯ"? dwOxwkJSiK>Rؽeۮڸ@orZJ-/>@Ӡhxiy䨕rerOlpY- fmnZm|+;miμw\CR_ 0ms{W\7;5)M)Z_@L9V=$<1uB%V=@x&Xnv̫hAHnݪ~tߓB IDAT`A]z}Ok^8u@悜2Q鶥S0M;mffgTgK,pǼ~zWE~ҥGz\>z褤A!l6*++Vǎ{}kDy } .:uh4rGz_pppLL̰a V};@ ~ad21 p8i~BR B'[ە(-Dv =$R­@Y:(i<C!K (/@ k!K (/@ k!K o<_]]ϝ;g6H!\uC~]dD!h?J{ïv$zҌ9(VUU)ʉ'vijpg6z}II`tqqq70{Q^Bo p8O84LER8,BCCCnnn||)S`$Dy * ŧO #dzHB""$mjjڸqcllljjjHh/4뫫gΜT*Au\SEE-"NLkiU8jpoqxwUjm=K! Jw10uހ3{g/_=#|cm9d6# v37B~>ɏ7GNZIY{L-σP((>yW戩yQx$k˺g!,`P^0=c?N_U⌻Ο2rӑ3Ӈ:?cCTށv[ʷ>;i}ѵ;欚[!۟{2/ b.FAvua^`DQHiJT*v52%~}Q^A/((Oy1㫲󄌞[Wq_]X;Jߞ<$d/ `00fX9y!12~h9Lyv)o),-zD@tPH)DIˀNs l\ UyWU3+0P=4xŖW^q`<8#5sNJ2cUqύy944 C}ar&u~WV'޿tJ<X<CGb2H1I}0>0i^͎;^,NZ8MNee.Q_T*yEp xǼR8NW3}VǗ϶E,y*Z1Kcc&1}݃ls>}V @&c||F#9$}\?rOܹ}sƪ UBLŘrE69`3ौ(R2 uイ,DdsT((f8)E8/a(Mb)Zs+M|8ڕ{ VUwBH1]dWw1nmmeF\I04MK@ؽH]ކ'Κ;8MW1_Ba@O7}ɖ u--;<1ƴ`c0ҖQA דtiZ*[ZJKILFQ}"*O[wh㪽ץ{ yZiX5 VB7Ҧ6#d2˲16\+A{hJmXdjSHCe6֖-@;) c̙/oPfVT-X|TjjW3~bkj+jE)@b@ +5 e/ғ~n\K7 ƚZ08]/TEGNZb򬍻v}*d )xjtEF;RkfC)}é/ܥ{NYRtŪ֟rARNYa ؽ,m4%s};6n./B<[,LF ޭ 2,22ʕ+J?,r;VNà>p69e0M@a @L c aic\nEy9)Yӆ.9ٺq͹R4;yރE0Aߞ)~7qARh{nZ7{:C/??HN+gmnZm|+;miμw\CR_ 0msZ?2mͱ1c^Dɮc޺X1赿~qc䬂G-8)P4Mƀc?>jciYi&N9;kAZ)iZ*N:gI9NXVJR4M/0* Ou{yHL {<8k֬)qFj6L,Omf+]Rl4`9 hyR{rexLr+MK`%F2ؙ#o5JV. Qo_#l"\fKef^&')Դ}wRɼ gE2顡r\j=B&fH/ׯ}Hl6XPP4aZ/F{SJJHH8n׮]V=hf_icdC26n! RO(6mΝ6-!!ARF^DdzDlÇ_|yΜ9Fqk/>???$$8p`'1/yEQ.]:z^GNJJ!f?j>|ر7зF׀xommp©SF#qd@;@ 6ljڇC*K aaL&0/ۑQ(*JP( ҺE]Ep%J= gBiG 7.F (/@ k!K (/@ k!K (/@ |uu^?wl mppe5u!0T*UTTT\\\ll nlGmREHTN8qΜ9]@<Ǚf^_RR~NGf!ܖ\SyGqq'AP%&Lp}544O2VᶡcСC?B@$," (K.ݸq#855| pс򊢨뫫gΜT*AsW?La5xwUjm`ͭ? e.+}Rv٢MZ:eQp7{M] iai{e55`IJ..g D婿 RNBq$8A¢n;(d&&yӋKΫ(Ftxx8˲,ޒ;@ nT*ZMQXaB!?|v?d@HfjHz#1#6,y##@!)ùCqd\)ߺɹc>-4rw2Qc5|{|zFo?Rnhlhh8{u;-[OvS3cg>Jxdqܦǿ\{Eu#ghf [ ]f_(凚OCwJvd{Źs1=/+.m,P\)(:FRݒ6`LӴJR*vBЦAz b^"Þeqr p9C {P1X*NZxrMy^q3[1@Ed g\7sn]UZ9Xs^,o1[s8 kjڎ'jSzb^i͒9jF ԏ6%|i)DIˀNs5lͤb&6kr1v޷=0@VXOt`D)5hSDQ|%Ry^E2p{+xWtg҇&8$7Dds?v*UEICQƀӞ䴛  b9a_&y9І)e^c1L܎ c$?6rP>#9=2La,vd\{: ǎ; s_L Dp+M|8Wܪ|C 1yڽL V^qkk+0W{nM붬x7f&a dr@r`Wvh_ mc^09.9X@ 1`pI҆1vezg)Bf<27;8F(ZRBض*3ƘBn2՘ Sv䁮|#ױv5HgeYpsiSB2eYϘr<-^ sc `2ɮJmPkfC1LSEQ\ ml$gN;#=MahJ5b +_}j551p~b_+Wbg)E|x.w5VSA#'-X1y]>2aRlKRN}.Wwr~@EtPbՇjO~ zr)\Jd',c^|hnn./B<[,LF 1L&rJDDDhhӃ&MO2 sOYg?2\Z}{@)t/g/bјreq1Lai+5Sh0EAk.V1qƽۤ*FF9?X`{_ ߞ)~7qAVzވ!o]_?ٸ1rVA#_(8 he-<;%`Z9j6SX Ӵr%?81$ﭔ7ur'-Xe!Y3V?თ Nʏg<(ſO\~Ueُ?8::O>T/$ o=ETveZc=f/6F0$cꖵӢoKfܹf%$$T*Knml|9sFuKZ{ m}!!!4?im 6:M ԣG;r|III111ClϗUVVZÇ;622|pq+|kk N:U__o49# =B/888&&fذa P>RA _R^En3 0dbp%t;8 BR) BOZ+Q^7(:"C_GVB ,}C4M!%_C@ | Q^@5Dy %_C@ | Q^@5Dy %_syΝ3M{!vb]2"F~JWc;=Dq~iQJĉd2IDAT8٬KJJ Nsz]P^Q\\|ĉ~РAd(aArssL/%6: >}:+++,, c! ="iƍ] Q^BovД&^9sR7k;^sPb?ewej[Ị*6q斝?gsM JKKۻwoGAo,˖?>,,ݟU]wJ7+r 3^ jksWEuQ޳EtEˢu|s٫/wEYw̢{'-),#O41=}:!$ U.OAJTVVvw*KB~:,,,::v!ү\|œ: ~î Zp< F`jY:yC5r]?Rr).* p0`@xxS#jf9s&::Z.K m^QF%]I3,l+EۢфGDgtfF>.sˏ&ɟ6fJOV2>}aae^[Q3}KiZYXe<˰ sq0eΖ4F3n޲tݼub8ln]a~Fl22uR '.;ۓ[gmٸ : h2|;mX,WuX,&l6,+8ghgYe[p=vJV)K  @!=oʮjr8c|BЦ܅1h69~U[MziPHaA_-Pna|SxffNҎyrئ=.)\Ic?{bչ_VVo5<.XstI˵liUaϖY{>~|sK1.;|#F/èyL-ũ 3/M_|# ~Yc#<'sqwM @LX]8#c0d}c VIu\)ߺ' (,ڳ*fwc [?99kI7 S71@+g gU;jgnZ1deހ1bfy`#{f`çB clٺf ((._hzcRy,̓*  4Y뷮*y}Q9D;Xv,} jgu+*N e30xVvޡY2c`Ļ9vӛz晑 yڜL}jۜPgZ\.Ot`D,vL%"+J%(v^s'I nQ>E#o>@/Qʠ~W"(,EQ{YZ06A$m(E0P&)Y8mGK94WK{ʿ7K6Y[I۾ k3M֖rHN(H*( WwE*UYp?s$?6EY2LQ @yypõCVvܪ|C 1yڽL t0ƭ Hnpi)@MCD՛'y-ͤģ0+K+d'Sܡ-FUfi3[n@-E&,8p(}'#O)0O|P~)2\--%r;pqq?mq8o.JMNp))r8FAG5LFQBrs8G @`]p^7c\HNò`4*'6!$Xyݷ-^ sah /%:w?`]&k^xjD `ڹ}Cw< jI(3MgZXzjhISN7?nݨ@d04"H잿ݲfA_vQV4h˟oJӾSLQTY^kG$nHN(Ih봨xZXk^6(JVXډP )J=T~{AxAR9EQUavHȓsOx(&;9䅽z@+`דO6MwUie?f3;;ZUExa䝟 ( xww7c,ܭYsfOdv.-eF0T'y]" ÈD"333P,hIn) ù555KǏc^,?eYXljjjhhhttT*ߏ (E Øfeafi9FcccHRqC;JA233^/..|:!'-07u]Y]MEG$EQ$I$)++ gɛdYi(uW] B[9A!xB!0yBi!4L^r75+ `IENDB`workbench-1.1.1/src/Resources/HelpFiles/Menus/File_Menu/Preferences/Preferences_Misc.png000066400000000000000000001030641255417355300312430ustar00rootroot00000000000000PNG  IHDR4H7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:Z7'rF>bj&|ha^[TTh]4U]if]WXLq|Ü3眙C ####Ӥ!&Ý׿uȑ/B4K[C8߮r -v.nJi@@@hh_WRggC7ñqƐGy$88eY.O%"ʡm1vLnjtرc۷ruUzh2v?䓲pv,G;)RJi``B\7*mH}u/X} Zƌy~F^0 5|VԵqSO<|+_2k_m;K~:ehuO_ѝ=,m!)ܕO4{4٣a}f{^M9gӤqF0ܽXaZ@Y_/NHޗ/I~!^&6ecY@ ԺC[n}60 CN(X yd+Ϣ@nwP0*+Tw` H}?xQ3)kpXPG50ctՁ}ybuJ>w 8M; ƌjæ6 Ы!%bC _sZwp=|u/g)p2z_u"P5BH?,] t\V|B)GZ޷o_@@@#/ڵk9r$ Yf?/ZV?/h mv)u-0oFFr h ̔aXR m++>;:w g̎lquQ>7*844/1ulT0%h l˛'+6>ԣ%a?0;Diث&uYr3<V []RaBv0;_+D$V0p!qMIX<~ZN5U7i_sC}ڏ0k}06C\W0 b*8A 8jɦS a(ꠠf͚5dQv#GNqmmBqY'2GOkߙkL6"e, ²,2ò- <|e[D4_ɲ`Q|A+>bE;y׻K^|FQg,`5c8SqǪwEļyqP呂MJ{spH?{'8iy5O ^DŽC=7֕l*Lfe o^ $kH·_*˲4DF ɭBH#eӟYsY@e PZ#m;-5p_`alXfQ}QzfG~n}(F <&l߮l۱7`!^Th>Xr TNbU0gBi 5R4Sǐ-[n )w=ޙjyLDũ):>z6F٠}k&o|V*=+M~TcXB.da;5y'o0.ϡ !R^n_! ɓ8)s\pnjՊ8㴮w[ΝNxwf'xLlf+5m͉+s9k m i5!6S l8ZlZՠAU<y-#sԁ- x]lЧțԩmJSתsxp?0a!|S\\~=\ڰUVכzӨ _riRt]\3 s.o*G}zY,XK6͚54=7hu>l4:MH mUegYUr6 R 1:8qdW6tC.=H `Xodd_h4;wTzff9]3Fhp•-k[U#qܭݨ2 #?ggB7jjj<د_?n[Fi'FƌHRy[@)jjj}FA.˲ Zn]QQQLLLvn˝oRms׼ju8:?}I&Ä7yl"ɓ'N>Z4 7-[mӦM{F z& UFFF#zeddd>iȾ^FFF#zeddd>iȾ^FFF#zeddd>iȾ^FFF#zeddd>iȾ^FFF#zeddd>ܟ?AJJJ=ZYY2HEU ! ÀBew6)AC!!~CMV59q: k[a׮] 6,00:ewV,2 ! "S?(8c^tZhl TM^ZAV=VǗo4?(ky#/CN<9aRA}䃂Z(a@j6@!R RA@-v~~]u-/ >n`PJA8T%/ /{e/=;?{{⩨쯠~xhކ 돛Otu¥-{vdu7n]:yvT׶lݾAt!P@o& \vL;5`kSϙc߃^Xlk4Ble[Ks(ЗvMm)Fm\Is8b_3F{FinO xjyN>jlkK?߿ 9@T+֝,덢Ts0 oɈi5E")/ҋ6d:^Z2=8 c1 ;p|]uu_Ԗ!lLm/<Wu͂!ohX(BDJ8,jWk!UIP0z8B/{N9Wj\38sӡM֢߄~ΐߛAQ}É?r3w/p;}˥VөV,pbVa1ylտY=gIP/ b]gȇXdN8Fp?XZZt(m{>ϬdkaSn֪s53 MY}> Q5Er/"5kMבRB!1';JO(+++++))7dffTlERHF##SҲ٦`0^!4kj7 h 04B Щ3Gvmخ{P)6z RoG{Ztt }[5@Nz౓ݫ>Ao\gFB ` "P EJ( &(8V)XҧƦC>azsqI`H(6ltarYF{aa5 `Jb6l@& 3b R.1|DZ``;]?glOXݡK|eVrr@ޘkywWKB]TWSi J]+Ca<H\ L@(0(.{QȻ1NFO@a¸DT?ixWg.ZPb+@!qߡψ׵.Ea ѹ pF5V0aJ={vRJ EJN%yh&Ow2*N.2nq`+9(̯WGivǞT q*3ǩZG~[^ʃ*tݵ 0 ò,q@ۢE() {j,o? aXeX"0CX #Mf,C\* :kN ˰,2EYw-YtfO}SurGZ0 !,aQɻ! 5oiXVPJy DbAXTʲ,@ 1/Y{{\\~úJxnnx?"/tV Q"D^H% \ԗ)_)]|bVe[ؕp#aA[6X8Nr_϶z4m!nL͗ɏd-~3|b<{t#xٽi^?v8ú}=2,a֯ q GvR%/n$⿷74n˞e!Z5-:j{ IDAT;j%J:c'Vq\LJE/$f?[~jD▮Z0Hy% T&Uy5 Vd; {*yԡedRQ"Tű yUІڼ'/VƭWo$[R)uH^=ħ_y4y,);+>-W›)M4 q(X )JJ.8!jOP{.UnؠkA B1"9v z[/̝NP(4VDvO1og{d§<;cؾ,CGeeٌ?Q){zNeìwBA4V`9B r:wc'FoX(9]O}־sD7xBX>oȳ @Q}3& … 'Oرc@@}Ir\5(4Aw/-f'AAٻ[}<բ2{_]3Sl6FqxxjjTVV^/Xji4۠plP>e~`;Xi`ncW~ D"@(NEE-~TTw:o4yJRRyi (UVu s>'8͹=K߇ 1w-Z4$uKޢEKZ2tjM-}?a0`pPk RpcU3p 4Y!:tP*"MUgm?\vƦټ+#n`7M΁̟J6kcQ8"6VɂQvz 4e ǎkݺw{bbo~2-#,C.{=-K D%-T51G2Me9r$::SN'_27{"W(tyx;kBBB#c2MQ?WQQ̭ 0!!!ڵkQA^PNRt:+***++k͝LoqPPP EcC2wRxO n,92wD]s)###}LG2222M4}d_/###}LGs)ѣ,ÊThOuh)d]]go 4$oȀ ]Սެ.̟ÉC]6T>a׮] 6,00Pb/s3]rU˱ VI-Z%m#^ ƅ m:uWߗSG _d/6u?sA'KIZӋ'd =ϿSɺ=RR#wiIVQcmZ>e~^kY&8tɓ''P*שdn2B0 C'QBT P_0_W3O(ٸޛ==uTJT4qwQ}it lsrc>:6~)3'^g7^ׁ䥏<C/޶ 'ZTKs'νO{,_iAY{uFG꽉DZNB_K*޸FYF$^Ο=s=ͥoݴDQ!66[c_o6' %c@mBcFo:[z@Z ر.ۜSCBXNl[X d򑍘V3fy]9#AAA<$xWKiK::/%B>^eN<@]4Mz2v_^asҠUTw<?0\.^<]ڙ# i3Xhۙ62* ]#ӊ.U㺺:BCa\ le{bxP-S]G=pBR}`ڙz^@˞$uʕ3-HKho𫣮X~:xb@Ges]AJV&H XP,wUSځ6R.~^> āwEn/M?.P}XVJ)q H~߃Sy&IstsSV {z])S;O $\7 bC@0n+8{ˑ|?+5C)/+M?ޫ2}vL}:=-?aǁD50[1O1NAV&%Gt {Y봣cKlnĔgzi%Kt 1{ z EiщP‘Q >^)O}yR`o7w.X'xp?o߰`ؐ/u eYp8AEQ^DQ%k}n 갴-^¥_˺/΋$뫭6C"8E]8[s4 G=hM|Rƹq=WYSP" VD!iqVS3a+G@OOU2bʪޮ7d.ܺg_wsc{ƥ3PsaKd p 3`rw8 m3$Cku eݎ5?ÐK_ˀҧFˏ6<> 3M]yb-;zI D8~UL> VrފӱI9 aH˘fXejy^[bD-^36|S a`nZfZ=guYTsĵ,!viYj'DS/R^m**u~%lmعpœ){FH}kfv*k yC; K9m'"3ƍC-F%gU<:Lw"{!Q+rƬA[w8 U[v'Rm_7v{w,Ԙ,qd宺)xnfiԂ]aY"~o&`C"G\>ScY:8"TaYzK1:sԨ#/f{9cAt t8p ;N'n{#[hWnཿt JA))Jk"OsڝӇc;q2Sb*"?hL hX.Va4`lpk<D)XNlEl(5f .Xۉ{Nl{7n/3qE4B꧝%Ss4΃'jB%Ⱥ_EgFB ` "P EJ( &(8V) ߧ$%{~lL"IY`e3~.Jɟ3 3b IR ;E,DTa+O-տ v9 8C207m!ydɥ9?׸[n[.`F"&oAꨩ\HuQX}($O[K9g-=wf7(Kv {3 4V wdN⣼v)~/|,{f*Yo Y{cZ]- `ǯXdO0fxmOD f6 @=\ (q.mȺ5V0aJ={vRJ EJN՘w{Y,fϩ]:fReNPUUשTBUTZh_&|+ QTYWWe,p8Sʈeb.EO'Ri}nE+9{P4 @ ID'˫z-]`J!0SJS5j PJE"(ϋR*y8{wˎ>'=@TJ8ҿn}9'P{ƽʛ@pG\hJܭ-YtS+83ZԾsD7xBX>oȳ @Q}Շ#sG!-#,C.{=-K D%-T5 Eu˾^)ò#GDGGwIុ+]u7?zЈجLGǏUTTggGF*0 ۮ]z5eP(N ?➀@㠠͛+ꩇe((Xs.e *$#s LiȾ^FFF#zeddd>iȾ^FFF#ϹiPRRrJaE*zO'Z.덮IA\ 77d@ۄF]oV~UE?B{u ݴކk.A (O.*XZkOWzAFZ~ow_饯f?v]<`*۾TZ-, `}u>X vW<=+zr&J_+BY/'tC}#zgArN) 0a/2AS/`}KKw}С'ON?ATȯSd>(8߭akYO<_=A)H RA;`a~gQqK7k|G-FIcW^O z6:~ʘiϝ{ݮmV_k(Z?q{{+F O^e\ބP07p\_}Lؓ6f\)]=9 3M7AK|__&gN?< 亣zo"""x'<{PYֈU62"xdv왻i.} 6 ߶y-~zrJ\'ERP*=˘wɄ5(UAh`ѳ!͏]8gӚ 6$#c&tMHRճqmx|_K70z= fjٞسHU } qg^zvjB%r@lғKzr%5(6 ᤏ[TdVLz~Ƃ & L~`ۙ0pFdXqȕ傏mQ* JgYHoӦ~djA `LuBv1k8s*2"qKb*O. k*,{ޝoӦ^?o'H2Hyoz< 2O{ڕ#mܙ:PXh`+Ns7s{T(M8/W*[YHL-Ҷf*XL1 ?9yT>R߁|aZ) }NyW&=j>$]2"~zS vvvyxXr5I]vyj]6NJMMMMï+s+ӈZ>șt-O[T%gr& .M_Raz~iκ  6&ڢaFEs-9&.s9C =iz-Q̸w+K3233⑟4atDHt5meSO'f-,ܱ~~fR܄E@J?$}b hc⸅9 6*0慝h OJ˛V}tr:Pσ1+ybeܖUm(ZL\R; f$FGY_Ko̜;pƖiS^[,YX'`vp]ҧ>VJ $R=<( `*\Ӳߗgߗ=;?;~~SQk˺_n]:yvT׶lݾAt!P@0yz\]CC&[+{ R }{,J|5u금y;F9X u{x N6df~{Jv p*=p>Bwt,v\+?mG/{b϶|`~6p8`~FM&a!Hne Ymzk!UrWG"cakyf}ƥ(꿉 .vfkMבRB!urk ;sP5OED %:uOIRudGXI5xjsユ\n.X*2_Yøl?PbTm;ۣ; .ʋrg8:L:f--K=mc빅e'ki"7~ٮӲ *,6A2F sC)rmSAvf&0O{wStdڈ^aZ49-d(8)hby&7yVb(rҿ5B΍_"8mwUSr0~~@JxC~ABxd]} ÈPB(! CAD BP0ҶH D0҄B*v2!L)5"Y}r.AeIv FbBnQ0' ʄT-mYZ81rlTo cd`w$)!1㱈+$|ELػb\f+l<װmྙ7(VWsՎ^wxSӳsƥ O ;;t/M>fokIIqIn1tJ3_F}gazFto{'Gt丩 *me~ҕf,qI5wN[XUS9vҧ~uɀHVrr@ޘK|Z"8X¥ z0.?R>u@$0.K.>t {U9gfaGUqӮ`%n[(BUor~̻rӴM+a \]bjWR761[vsdF s>? 0 #<>px|ޟϙޟ|j?؃8J{"0J)r ˔R9Bū"~͎J*Q^'"j"zcʠ&bj,qܗ,A0#W8]n *A?V^㕤ry[dykAGryUA&](_||0c s::z@_r= Z1ޢLAo Z0#hsnOYZI;%LZ}H 9/`5T* *jEq^u,_~:,/f7A9K@Aq$39ԂV#q0żyfrAT!JL)P oTc41en)c".N4Q~qYKK2''uR{uxWjt(:Zyo&grFU blє?Yɍy7dY!"a0A={vԩQQQ1&%'wEO &7D <ρ㠤 oj##AA)G)dS}8DP 9WDazƨ8NwԩXFzR1WJӳ;&TrP=! 24< 3F3jZ'2GϸLE/TN}h).jο11)'. ~Wp-2ΊZj5klPB:D?رcY=jrMQJJ@*$o_kٵsm+ ߺYWˎgXe`.,I¬+.KeB od)nVɉa%/֜+f ?gؑw|{wCVU I{j7V6 |7=ȂIa?<{5jqy7y9:GYFcp%DbǟWUX2ߢu-lwROޫCM6_m 4_(3yx]]"x zyvOo7^y1D {Rn}k'=\qpbOGśIp4KVi=R1t{O/"_ `ՊUbU.:FՉU+r]%:ܳcԣ=!DhI5 R/tӖcgDM+%e;o߽wUc]^*.BY)f+d=4zʲ._<δ9<Tl6|9w[/+~6;VO^S(8˒͹"Eɕ-!OƸg^ lRz#Ȝ3p̜\*DSԮZ8l6;=?lT0g7HoE G(5tɫ[E\k˲U;/'wB:;p(DVeU4V&+;곒S>y}A16DҸ(Ye l.*lf6w4(2mC7d ݔRJEQ~/=D$g|GGFFF[Y_9!-yvs.yb/i .W'3yb%W^/W^ WK^{U$^BJKPQB4:ŋGmpzmKYyӒ6; |Cfݖſm4߾`SђVOU @|Bj \S|K< f[g@s3;56ٵ1cQ&#~c+}oцPlHS Uk7dbcUǍԂǖߴom[X5Jj`{ڲ ^ۛ+HufSR=N8z0l9`]] #Oo=$9{9,q;N~7ϝq?hi96rwmj:Z^}@Js#eRJQ6'Qơe""8^p^#+_tߴەzHG5_.6yg_λ}@9ə|\$KO=dDx[=#O>B}$#ы=$s!$\/g K~C,{'r X6X:;1%gp_BlyZP6%>&&6Bhp7uhܿA^@3U*ӷ'uH #zЁBisC ۱yibB^J pOGORx'V) 1"f-X[_F>mRR3{G,S^a &ꖻ-b'~cCf}{ Tg? c{޺9yU}wM kJhw{~S@jubb~V1cRC#|Y !Ld򧯭DُfΘxsOe9B[:E9,+erzar&(JO$QAL} U;}G>"{e^EI$pEe>\mn^t:OG)'|' 7"E].ߘY.Ujc_c;lapaZԭGvޟ3\o5ٜm+, um"%IR>+rH(uwpa?j='sJa29vOk~ʲHmєl0q[&grJs^TY8p 5KJTd*(;euX:'RVP^O SuLCy֕:R7=-ĝFg|瑊,*Y/t}mIz#(]Xƀ%53{ !O<krd]qWOc닟 3|>>d(@[3"U&||@ҋ>Y#™^YVŎ惯7d^6u{XYZ =8@&gawXe 6q\cMBdN1ClCc{fhmll%KW[J۱p1%5G`& v ML,ϻ- :9^>{kBbIQequ6769I2 |N{8%gC3'|)mI*KIshM7xֆST mcGzNjU@jt}wἳ$ rtN(J߻%04*/9H ˔ę3g&̸;1{p[!X_09r2(Q )xL9 *As=w7/b]5IT JQ'K?]O.(?49V:Y'i$/GUi}(ҷ+,+1(;/mr4sX=0kjl_ZRS=,]/'6T7D95)@?(hc3j|JLQi T}j]bU@fA6Pr0٠Ȧ2Yf좡`N,YhYQ,ߺ?a<[XӶ0$.Xmn˰ڀuVA:_aE}(2>%U_\Oyd딳}L>XSZ5sGvRJ9 YeJUׇ :vK*dz&JRBhD+" KVp)25ES].`&˭RE7"z^W} ;="hGqQ+x\nDG!C)/>3f %ٝ scٮ?Uy8Ӯe>[ߟy,zys iy=.|{7/ocr& M(ܲ'?˒(dʐ%=Bۺ{% HFrhG۾ Zn:l{E@0"(Dyԫ[CߧpRAPT|=/3官ڦǭc!6ay7k@TyA8]'Cə|\8S<>l/S4ӗ :-( TdJiJP_y8,{kmcDh4% ?eNO+?(0 jZqZ.2RV#w[Nfr& iTZJ "Mu_ܸws LeI"{.k9{ԩSbLJO>0V$0x 39P^0!S"9hLeI&y7=b8 ujh"""zi(N6py|tDH9pt^Mmd$(((Ly*A8Ꞩs#X32gZ8tNh4:w!qu019r;P<12 UqnWW`1QՒ$M89zgx/~Ki97\*2xqQ 3F3 ̝;w׮]ONKK6m*40N#}oO:QNq22F9,9sSkmq xIOO<̚d C :t2cDz{p˅A{ U"I:Ηk90mWBF1Wˎ5Yfl6'g?.hݓiW>OCYOtŶ5.ϐ_N$鱜L&"|/2/ʼHnwӠ2M9rݔ98!z9VD582ltX˫(˳uhLAEم;ix}V犔wTi&U1Cr)#I mCCLI2_msM||D$N$oKB+\s}zø;x'L(%ܓr[{=ኻ\ _hp xӢX 1c`BU!-رB$ßmZ}mpGZR?dB[Se|m.Tl,D`6;VO^S(8˒͹"Eɕ-e `o>xV(9kGm?U904Tg5~ine_MdŦmJ{Z<j﩯,J6<< e8[BgS?Uv}VQeÑ2䲆ALFslv6Ŝ,LEYEfsq+47{Z]/:˜U UtdŵN8lw j'!uIDATtwwSJU*!DI$GY^9oeUr)L_B~G+Q/+QQ«%/|ҿ*/^z%%K(rŋ#ϵۖ:J%m D`:gou̺-*h.[}]%yiխ79~41q~\ T߼L;o[ݺf_I4#aGgK V75Uo]XeVU[kM-6n.[jˌGVk}^a[W$-Vgyή9EJj`{ڲ i޴f_A0{߀FmYshB{a'x+ֽ//,A! K3R3,, ,>8~N)Hn;|RU9J-^]=eM{?.{B~C)%T*vUHO|cCoU8[x>hD/Ȓ̩"Tp Ik8TKOL^݀uoづSrYE/ĖW 5kScbb3l.* G}S'*9N[Qc¢}˭ؒq͹U7ij`KsGΤKWd${Ys8u;5/KZly[_XPgu KNo^oiy fDToO*yG+ԔQ,- ymvBRJj&`>eڇn1ݨ^NA&5K[>P?TnIn~9cvbM7{d {YN!'Frٗ3f/zrk* P nBdY&H2e"˟2޾e?J9c~->Y] 3H^II$#!TO=X==ʿy7HGdL==K>(”p노>\mn^t:];pbwRQpʳ Sy:1I;1gO?)hǒ=2l/:zvs,3ӝTTz~`ZzВm.!u])F lu ~okt}P*Z?dg[Ykw(@Ԡkiس9V X, %|n=z]bmιw2[v5_*%IR>+rH(uwpa?j}_ߛfjrwnY978> 3s^TY8p 5KJTd*(;euXZ*l5rMgh u\t`۴ԑPH'8mơ*hg.0F(οIFcTz~WAZsKtsGNA2W9"KzEz(n`Yﰭߌ),wlQ{n(\odJ:hr5a_`b4~/ "~'i& J8N"{ʁCɱ&cq3nܽj9PÿLR&gP ^'n(P JLA(% >2ZpʲN(v4|!ݳ6u{XYZ =8@&gawXeVbÞڦVq&@`ޖN- t];;w\\:@hmhqISЌ~ڸRQ= .g{sC}㠩Tmܲ j],*U;=Wgscs^R yg{cI~&qum;=A!WPe7KTvQ#-G36MV\됹F~Ϟs09TV{GiQ])RDdR*GxUD8 :vK*dQzx%)`~)cE%[؜5%mӋn Q`*O*>IC1d#V?\D+" V"ZD<.`0j͊0O'.{PX?TwTt.k˞9o-h(\.jr. /f̘a0`Ngw6̽Ge\8Wp]|W?Yxo̫}#vZ^_ry}#5O|9 jAukp~,́o8U>s^8RAPz:_g>`]1M[Bl~凟oJ|SdAp׻N:3N-h5px|^̛h/tZ PJe2$ɔy8Z;'Z @-9eLDJh4JGf--94~ʜܟ=W~bKSQ @^V9tj];uVRESǧ>d$7n@>eYxFj= FxQb8gϞ:ujTTCI]tja&gʋ"qJ$8x=0M PB D8/t4_qPR-5MDDD~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki: 23>7aY)U~=;>8E".x4ejZ/g3_c;v3aIa#870W֏_qܕ#?J+`%Ya{qݱ>dXn#Ϝ\+JeYk*$ȏ*j'jc7u=>Gʞtk1Z,LM3)vx4,+J-F8=ԦeIV&}w&3uʀ+xK[P |SF _FcXRWvRRҲȃ7E4z/)}5uUhŤR]H,Ӏʔ9U]}YTgW0MCׂ o"ahL]9DbeDFc~DYeCw4.7vF 0Liަ@\.Fo+50) r#{Ϊh\.ta~?=lOniu^ S3JxjͼH6oHo10 0yq@Ż'o~mo҇eva*J} Z.g/_0"/z}∸'$3d髦tTY{{zzzzz~T/Xs'Jzf4Oz2w&xX,4aG߈̑HZvqCnxYXDk[w Px ^.+r] Eu|f'7E󉇭(ej2 ˲˲R/StzW55ޮMOow-¾<~%O'۷ݽs7fR`juG|6* "ݡS;e٨;wmXϣ_]kfC*Z+'Mtr/\+^;u`|Z5:V!߮Zi;/| mK10 >QIǸʊjT*}t):0mŮs w^Α9G7l{l/^8e:a.Lnupgw^b_?fۿm-@oAkdGFh7 u~fIZ- h_;j)@gZ-olYjsQE!g;lJwFĕo9~gկFw9*е{PeAtfffS]^^_Wp%z?j.d"\>_7?#/2ҙD lULI]x2(noa*wx!@ ++{ʁ%t}C=vη #ȻgRK|zoW{\EIl;z`OOG9:15O?gІE./g:M>6pB f,%y򅩉W@0nM=:;{2onu՗jzq+rξX@$hTˋn۟b]#,i-zc2xwW$"-IچmwI0KyIZ ED)pLپ)F/|#flArqJUVMH${.^a,},Z~EeJ(HIQ.>vE00vb0OK)};7}ʐO񒡏t Z5ETE_aѴIcu0 P$EQ@)^xjqF/y.>wʭ1KGwӣB Sp"F$AM' &I__L$H IR $Ay͉/4޿-uI%pEYYFĚ)n9ޮkAqE@) Ea{cFX.]-yAPfw~y@&#C”7}~|{隨:5qkXZY4Z5^t_S>9U]h l쐳zi,wqv'g྾gRJFƔݹq{ L H6i3K$|NT]a0Z?n%,h !>[^;+v灄0[HZIn?e:tjGQH9@$EQ"i[$)DfD0&oGg[GO8p|v}@g+ܳO5dޛ?FK臟Ϋ']-Z~N|:mav>G':%cw$ab^ :j g u$ HPg=1>|.8͙eX esߚ 21IbIu?P:GȏFL3솏ҦVB5U[|snbXLQ5M8eݻwĉƾg/F$ڶv?| 5?59~n5^2dSXWfKLAqMΠARx=Nl/^qƸqD"^KG@^n޼)CZ_%j0߶m4*Fttw/l;%I˟ip"?gʮ\LKKT-9xvPx?W$jng%&gVQC1fUtv҆D?j (sǝx`YҥK=z`Ƭnu[!` {UU˲l1sN֭kr׫WG6 ٍD) dY齠;ЦD dEgz@4]S<6.Dx'fޘԇa^L<\eamuU/m51 ,:991 ӛ8ث%y>h Ϋ[]{BBBڴicie-ZTUU5Z?*>%n:MWtuuss4wohQ<+ry+xs#GM*}$z:yMrB^Ld9IYM_LedΩ&J7V \M}un)h”QR5Ii]]]ms1gԊ\pl8~w tJÎF6(p%'ik8.;ii%4Om:wf/* ]]c4gmZ`ieYe.1uԩSMvwuէu<®)RPx\N]kgβg.cCe惵k_+W-0$IEE-p~ֶ_;uQHb`>7L: 1k֕;LN꟱a7h c1+'{E?$fd%gh5v+N\+6x%5Ԕ\;珮L;`VNNʺ53 uf'/m˨u_ϘOq_ʦwnSssN,†5uЗ'zQ/4qwP=:[˗ǧpAVl@Ж})B2֌P w sv#P wot}'Ruڱ۴][ZyM9c_ӻ ..- wh;UY Mm':|M?IwߕVnZ6sXa'+u?{<ӷua|ͩS u`YH$bQ&BR>uKW!PVeʬ.`u={+ (8v[>7v2Ŕi ^iJ$PSX̔S z޴8a _DJ@Y7Q@?u'7|jW{~ͫ-wSr?o!^:0|]Ugžw"_݈X|$!w0't2e'f^l$f ٗwI|](u֐cgmH<}% Ev0ajyԂx+/gu5SZe~h|'1{v//8]p0R>10 MS 5fdO_ؕ"/ke%`x;f,_{{{SSSGyWF^/{>rP/7Qoh((?J3Ӕ9,ElhP@_[qN @飴C٩1/Kݕf'>û)c]%@h~z[wz^oaiԷǂnTG|e*^ 9'THgk{Y*)W%͘%@V^h EQ$IjZac)Zr\ 8{TQ*} ɉ㼝_kτi! ٻ"ͩͱ.K~:зRj4; OTtq!עf'"?"-J=TZ*Vwazk˖@w%v%'7Fkj{#e>n~6DmLѝwfsiPP zw/VžSԜ׳fgYV[.ao\Y ?h53%ulO_t ekX H>ӟl;~| 5z4E .]Cj)]E)7P:\9z 0&r8G+SÒ2J ,h#l]9J,?%7"3cYިOIuu[ieKP.z,Mvy,h&ܤ,+X0z}Eхc;S=*Cn~F,=O s;{Yۣ+v'puK:I~K~wѸCUt]WO?=<3 oeU{Yۣܖޞ4?UMz$"RPenC.^0)$^Ϫem}_M?>-9`袨-wk>$yoKBmɗɎO_Ai^3ߓg Gn:Qj4ՒДJ K*@H qIIG H||nâSF~N~IΚy뀬="Sr UMIqQsWӱ(S+): I%ݍC1 +x_U:> { TS;z/m4C€Ĉ%87;eE@?,.'l>-WєfgS.sYq3SrtmۀpNuGVܒw2c{4n#7kۥƷ3 !;v~i`47Sz:T{Wෛ* {#4J0qQ_E kOM_>ӹz'R/kq\~}%q8ЩS+FJّAgƥC0`Ę缗2_*o'ڳ "_Zq-- ?iO{G%p_[ \qpGsU#Dm?{(%&"U@[Ya> @xB^ٵP=ջ W/::\> w4H۳A HB.R8R@L$IzsJoe@$A$r`+CD:`$ ,%VD~?&I L9hYQ.q-cE̛oԐeFF.+x`NCyY;hT m'ջK>duE%t hZ"S~S>%(c"N.|9|F.ٹsJUIe7Y;LRUF.d}>+DUic'P:nyBT4m#37d=뽲.MX]dh;q`M,O@{( ZS0&W|Y*5j7hQjsxղV7S V.^޸71]:l|o#^6n3R(@j$)" #EQ춭]@?VY|ۣ-@о]j;EPAd(1?n@~&'$")b.č^|>$DA!($(( 8ma^(󳮟w,M>ҽK[nW 8~EdrB?ҜsPmuk?.1$2/ "UG4eח ~% דf͌$i wc>P Z>4MSE4tf8@"u/ ŋLx)s^wo^յֵ$ 8@R-</ /џkò)ñlڄ޻ʮjrU8 jEkgg\v~S3zt/cy(,)E4rKo_8y]iGtALS}1eNB>0݊|S[ġ͚H$ E={W}[tLen߰>[;ɡӒBbO$HJD4QwMU~+<#68."[۶hHDMT*ٍwM{?]"H$3e،"֖ԚI M4]mʌg5kNww_Vbz)D|"ZVF?i4-uxd?{ȀL^5D` > X;LS-?w'ݻO:uӦMƍݻ9<߃G4M? &"`|pyjuR]QVT SZ$2|]~)Uɮ`}T'8$M޲i;v]Zh+z^\v2~NUUU ÈbeVaE]{}%R}YӵK$.& *UA*uJŮaZd55OSF/i !6??],KR]<~c, 1Q-AȖ#\|K.u骪W Ů#;59ļqܵkJ[ pPs΂R~JXZj,{o6???22 RXd`qFjj۷k=/ $$٦Mv5v`!@m8c*J8'&#;:::;;3wK81ohZHX0'P?U <#+eA- 4[h' lN@@"@E8f p͖rC_HL`IENDB`workbench-1.1.1/src/Resources/HelpFiles/Menus/File_Menu/Preferences/Preferences_Vol.png000066400000000000000000001105621255417355300311110ustar00rootroot00000000000000PNG  IHDRS/7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:𳄂B)uҏ/}طg76y }|}}y \An efO%9c;$_Q*(d J!Qh|{ߋm90Ñe믿3gʰ^ڭ(K$]O-nurQp7_7TSPJ2N:qȿllr t7o8$<#IrqSJ?߯y2GyI6FN1jj]RJAY]m"|0no8J,˔R,˲L]PW"t.[e]]{ =5oW|]%jS&ó$IZݹ)2=_A_>?/{'^F@qQx]riNtJ%ٯ:u̙%VmuȲro?yK45={yEy^?ՙ4|s+^֢ϋ+e~qV)GU[J]N-l)?g.JjA Foz%&iN:eꐩS]95.X5aF$Iqm[8{`/>mϚ%%/BSP˕QNJz~.n큕G@ J)%T;ļTdN]F53W?jS@'Gvf|SH9z#sVb!!QLBzMJb`7`|9SuR*\VsDt9HdI2.FeJ% (;DS3saFq8$mQ䓤#*cꯄfAQESz^7#2x_]w-[~umO7Ί϶Lm*b[ʲf|5:A"9A"EQ#-,i;sbeJeMlo{i*ͫ?QL]r\"aw.2?Vſ&NMav 7DŢu(b{˧.vb^,xrl }[{dxANjsFlNېx}8EiEb+/82g!7:?1۔n:hqH6ɥ˟z1)۝±],8|+:|`Fv_7gO&6ux^zQiESM қw6mNr](9zꧺ@l6'z SVw b_z{jC746*?0&'dsoc_#L]q#`ȁ7Ccc#ƩϹY$ 8!G 8;8;ps=סX_GbF9cRr~녤m_Ct_PRݛNGxVO5ԝO1坜~}gNҬЈʙ??ollhHv;% ,D%\*[Jf짱 >+~p[0`Ƨ;Yw!e?=(j:y? }vRX}w=NCܣ>uO3Gm//Smhy~&LMW:>\ցrj :KEEE? ]:FB4Z.Zy)$PR^_5#qq#e~Ĭ6=4y/~2f i(>rG]#ƍ=>?r[7y[ϻq 6Gw=@9, 8gn=;߾A^ 8Q$?S_[07">W)Qk񁗦w=Ġ=IF;4Z_d(:͵qD@/Z.hfVcW#)h >79m| "H #sr6{ [25ب!4b5';qB筢Ҁ@i' աDL!Ox?a\ S[ŹpBcc'@s"pF2J!7\s8a}*{zxRsu5Y ?X<+쎜&!I Ώ nG&bFe4l1%{~msnH}u u4E z`N1-6Q(Q@ss]5/ӂyr8⭜ ބu#W_iUk{gݔQ^_A j?3{8iռ! Zp*@jCÝ=;ݦUvɮ% @37N%]R H( j^=޽{w֭6dYǎzSQTgΜxų7wBgoP 50ṇVV <Gyuzc< ?}< cbrkf.۳`Ҽؚ'_xBHڃiV<.5<<×mYh>4~Ō;8tF{t3[C=wfYm{:CRk8@j70^Lk9(yuE`y_fzW+⡜2h:ؙ G+ 2羿l0sʝ}j^~sTR OUF  4BrL^14Xw^ 5 ? X{KFfE1%5!`6/?x,P UG^rʓK&d䃏`ى#&65ݭ'td^t:BNEʂ ߪoG5#λs+ G?~8I4 }/b O1k>:P<Éj{x"MM!ݣMZiƧ~BhmgO8$N=wԪUZuG''E Bn%Ιxcс3yݻ=U yUV~ߨh%Zzne82WixE&hh%KuiNpʠL)Pq*Z2sP„gB+ZF䃟-2MxʼnyI6u,c>$10EdVxrq7/)<:ϼ^/m`>fߧCDR3͝ZȆ`ZG96?zaٌl'o<h|pdM:xemzA+W E4k s01Z#m'qގ'z D5%;6;̤?i=5*2'Cj:@<5/,)8O>r˛ ӂP*Y2U")yzzr7BTՒژ.v{>}APBx:4oiq U9nEl6.74Y6'.%G:th _ZosjkZKP tݽEysjkBTi:jU6޽c5fKm@?kߌh xڽ6Jsizk5M 3)$ls8??MQ\y0IXjh(6\º|XXضmʦN:`Uhn-nB!FTP48?u ɣs̹Aq?F{֛,˧Nba/珒BӻwwG$I3 FD$De0. s ea.`0,3 Fx0`0] \<`tYg0. s ea.`0,3 Fx0`0] \<`tYg0. s ea.`0,Omq!IRyykkky쾣:!8P09'] ! (sR\pGge=w'PMIq6gd);T7?).$Iڻw$IӦM޽;!K ExqpЖ)((_]vɏMi4N1C$[b-OK\mxjUXfzkV!iE^Q7H[ZF>rm6I\0z!܊44 2:ȂQz@苑^:po9uT*\<x܈UqB \@!R RIDv1%`p)fH5-ԟ,06#Sr &؎FZ}S%=9Q[u]ʗ —f_8q8?˴ yaexӜO}_d#.hDI$"!P kKY1)rkPuaTR=1A^,j&53LXtꞮ2^@zrdD%V=}-Yj܃ pDq`.M(N\;6 ݇==_r[yJ$I Fj!Kp&͎?sɻ_]OsV#I.QBɮ<=\zpl>}廊=6L(C$\cKAVRQOnߌ;Cu(Hٓ /fp yB-E3'C}8B$8Tn8h>#ʪ,|[NB3SW@L<ҋ7Z<~TƝʷ$`> dHCǎm#{'Gq([ W- R񆂬' k ]A5+b}mf ` ƛT`q $;Q~&+=ӆr/vΣ]xd8[ w'w*^Y )$\& B(!!U xJ *Q؝r]: N\|GNm{&7*@pS ƞ*۩퍦q07I~n`P"ԩ6Glp0lړ uղ$40(*qik&r4){ [Ɣ]k fv#AC\(46u8 j Myk\ļgGi7K]o\05F#*ޖhO5rIBajXj'imj/aɭbʯA!L*mPnicqg˔P@ TfoSG69q͠KrJL4j͈1&,ݴ/Ado@ݔ{^^x^a²ci{  ql,ӶS]qLHM4w.$ψHWjL_{lok\ĥbEA4t3?;!Y8?-R-+\23OZj.$!1=9$5z5!=9ɻ\+pǹ09RJ $eJ!pGgۭWdԋv_:Yi1fEK7W^VI6k]_Lb>k!2mj[kk jqsoԻJZ,+zۂRzj^^^JC7xoDByT<93_FBߙ7y|Tey2,wa TU||ۧt:o@w缺P2!2B>Ov5Wx^WN59Rw^ב4J|m5fv+U+oAxS)^Dpw>C/`~ռy<BHۏhʵ*^98zOV D7yi52dPQ)b5 ƍCV+ߋ`܌eW's}R<pJ"hZJ4e\4iLUZQ JyBg'-ؾ~qwMڴ ,"S{tgÅ9u_--ZO]&oR4yڗ|Bp @?.|hT$A%iewY!(jãyս^5bL~kʻy/OqoӥdM J )G5j$^sƾz:  V{AjVqUinhqU}n2G% WjiӮ\޼sۖ n[}um%?6]Aۭ7uz$ ˖CuyGꐛ~,e+/Gݐ%sªV!iE^# :oԩSǪ*x܈Uq\;*#|Ҽ J)dJ%JŔԂ wgn냜%f>1iKbtIVAFZ}s `+ڒS"7{+VwT}aċX HDy dr&oS~A(mx3?KYLMyI%Jm"4tO>>d.8q9ٟWHބqWтdAq] $]a@N֡EU?$IB'1$IEGE\Vdr&oO^gv T./Ԛ,]ꭹ~ߴ+(>'-TκYF wttl(4+JWLO8RsY"%+sCZhCtnUG'a08rK8vTK5ъ2tz(h{aEE\lŅ.˷.vgꭋ*ťָ8ݿЍ\BK8t莒Ka"`0_!{RA$IR}3NIErh_t:OOO s"W Wa_)%.RH"l"e﷬E%j]v%jD644tKb}19Db 3vݕicgdݴ"˴E?*rɰҽ+!b^V[=Sb>PffY4"gؗ´YO)UaŠe%{vٳc[G^aՇũ )y)ӑ:+8 T KJ_V+m)걃ڴA>wCRI=@Uq$K,tKY}G f>q 3V+I.QLeH]r`l>ϋC:DiSe"x$u*,Ps1o\2~)2DawvL(tR:s7MKQU>y7 5`3WGJyz~'u~Gs33 F`%mM+P_iL90XHhG&ż?D.IEpb^Ƴ:0o\05F#*v$gyA `C|ó\ msYݟY?av%QD*OM7EGik{y uf=lUH\4{Nuٛ9ƥܣ,5&㾯LX6&,Yzxl„FJoS'%̊WTcvͿ.fqE5n^9* )K''Dj`т G`DfXEaJnILGz,hH{,ڲA23O{TM7ݽHVtx(0'ߎq$Ы !|4e LޕZӨ85Gͅ4J) H2/˔RCΌų[j$Kk/ f=kL,zA:6tڬVﯺZ]U,rMy6~iجng] nPJϟ?W_ >K\t|b[M!_5 'wf˛^BH;&=ʷ5OƵc.,!ornüSG Wwp Y2dQFS6_hi5 [zx^y{Wh@e ЩgkVӬ(j>kr}q-6\ < ^)mS܂oz";le 0?j^ zR@y!GLUZQ@Q=iLk'+"盼 PJe2(ʔRWơVXX 0zn^~WдÓϾOK)8ARBZoZJ2.4{&rZШ%<3pHPl_?&mZLe=:ų@ B Ԝ:ujފoq7q `_׃ !J F>x{^z_ Jd*T ʒ4_hUS,P`\IZ<^R/_1_&5<'q8p@ɷR2RȔ5/9Kc_m=Րkx!D=ztРAjZ6`\zڭ8f\G߽' ѠwI$Cm>>zRn;v2θiv,ihq173`0] \<`tYg0. s ea.`0,,ih$IǏ9^{o* ݩkR͛D$5~gVf50(z[u뀧F/igVWϼj_n2adĸ MkI𴅋c[,p):&TgCVqձ:P$ *EGE\Vdr&oO^gv T./Ԛ,]ꭹ~ߴ+(>'-Tκ+|t0'Ǖ?? <2[E lku][uathrKn_,R#Ytrq`.2)r\-\w\PKٺ8C Mew-q)T&G'疗d*uRUڤ iò2+*sCMV<{S*LFEGWٔM`0,ʵ5qMuVf*#Be[qQez2Ytr}[)Gl'p09zَP!KaìW.=twQ,tQE遒j*CCn "z뢦K+NDnZtB7RdUw4 {E`ziFJ $)$h9^rίu:9_O+ݫ0/Yl]]]v62V[ֿH.RDpRj:~^KԏAjk@ IYv-)ٿs 몥U%qdšsś̚}tLnAag`C–r괡a^iJw6E=v|+k H?dnYʶ&H:ioIɞMu7'E9kgnfWڀ&9IyyH+J27m>ioqqi27(,> ˌۅ1KKs.iޓ x Ct2=Yo.޼dƬ۵""iƮ6t3UUWWUUUU+wCRI=@Uq$K,tKY}G f>q 3V+I.QLeH]r`l>ϋC:DiSe"x$u*SX ʟ|rfeE@ʞ@`x0LJeAխmb2 lD l)$Xˋ6#mЎ 1"2η ['@ıt@{VlI*}A7*811 8 (_8Q4ۯSHa4V?C.lV[{&uG~=c.V5.ۓieo}çW[xP{bC|1H\2 ݺ N:Nzv K6ծ_BmwNFzai͸К;.jP阿3{]ŁlcD呸`s"P|hq\Ey;Exd84|Ko V?tcE׬ pqSLE!'PB@e=CFݫ' s?RT;ztA:)Fz#i۞ 7TƁ~[Y}{;nR!M{k@ j#6{x{"[BrYӤb1hLl)Fu͚%90b3kJ\RQ8:O{]Cpʞ5Kljܺ $hf[eZ!-Ln G9o boWXd!yYX[h|ʅ90Qpw;{^?&ż?D.IEpb^Ƴq&yDQT>+r[G`$W IDATxf[9/_>ыBُ'Ɣq J]X;LUNDo%G)(((꜐HeHLE^FYթy׭8%|sRc@LHH @w8OQ j rI݊S 3LP^2r4~%W~]K fw1v|įZ"N8:yRj YJyBm6p(418PZxڳ[+r̜oJ^=P4uJK/؅QFMVy8 R*SH68d̜NX`ؔ*U|.`B `Zl˷jb&5|rPueòWB7lJ"jTm YVU].(*Ք&p^Jxņ,Va|v?[mb8efsu:6Ysu?jG'X-";ғ͈0߸Ewa'Ĉb r"¸4zPt,ʃ[Q\vTU{ش:f3n\YVl *j w/7?K5tkr1͆'`qB`r.0*8,!JmYޥɻ8J%( BA(8 N,SBZCx5xp ?8Sh\ب$0Ubj fDenZ֊/?ӑfi{v9!RHIb26-MZ)4w.$ψHWAw\#w]Kg%d;VMӻE֕RE3($>6 kgWL:{T:SNI1;aQI&>"`*b E2HfkjG>>,gMV`P6–$$>9ۢ~Ģce{ܙqD@r<χ=suϼ9'IQbcKYOWђ 5ըeҿ8Xz'Ytup%e,y҄9}bcTqVrl9K-XocWutQ(M[76 cu[H *}粩/CϽiR$e`!o fd cŃ1eL.@`ۙw$)d2W72Ep("#D`AE!P.C0 v\םU^֨[{@\WS nAFu-~^YU5*n]bƕ[yY\=nAj>>qP*YpVkO8ǠoJ?8qZ<6rџTxۿ~Ա yos)rQ v ݇CU 7xVV@.Q4W\z \ӵnX&D3XMu喼::A񪦧qXTRsYJ5{F.,+{I8R3Vӫꆀk/bYcdY)+eY xf?REvJ+[>[ y^QDDQ ? P(Fh ?}u /.y(H2@&`&((@PP@@QjW{B K?π1Ϯ}{'2 eAY +@J^iB"9jN:5nܸIJg; 9DewsPf7b#CYa0`trM5"D Hxp=N˅CM_:GqK8j([)VRPz^/Eu(~>_jqfL 2 )FR~ QdD(5j+gs{le3? x 0AAA "((3B&B5w@Ƹw,  #XDҫAwB@(8VBq8(hw%S( yĉswʠ9C]S[e%365&%' 2gwvp|x ",>۶m;~xRRRdd*rM ȝ|@@mkϞ8Ep+r n s~˭\tEsu&ݻ2o޼!Cx 7_;2ז yj?K J B5J22 MKFCOp'V9a6e^]y٬Ն՛Z~Spi]ok.i߆J-yb2J]/-Ěyf3r eP|# pd(eݢׁ NdEcdC_+Uv5ftmmn辧7 wYUؙ_rE8P܌6WY+$ itya9dh1D4E[uނԜLVxgI}eŬprv}?/]G9{lB9 !‰\tf^=| s-hbbbx3<# Mr^W'S{3]Yx-/=w !-& wGEYϟ&oag:C"͙Tڹn/JL 5ǹ7!]mY8Z_ P^~lg^[1W ŵt\iӮ o@L̔v~HP@uСCyǥkx:w={GΜĖ;;=Sutw7giVoloH7.#bcɒE95 51VUn$sAɭ{jv_Wk]9iP3zSEC/,b;pSUsoh12 % w&S>`4rH\OSo[>fކ;&Ν픩\$˾hqsMeW`ʇLxaك>E"Uz'ƷF*-n_]鳟.~826~9"!N^pB/ӽ+|A/: x !Z֖x&< SvTS ]pY+rtܲ9ۀe g>4m"YZaqlnAx]o;{/8x pDRcҿ.AN^tGNCtrH¾x;ezQ{ 5F|mIO1X=\ W{Qwah$ŰDS!g$@0ό;wL~,WKtCq}m(?l_NMdw؀cŶ(۰@_ DEEҝtt"ī,Tf*]g*|Z5K/Ԇ>[8l@#{rI_$/{?M3CD srw"]xװ3eLNGHվjXd]*ԀnG,͵-G"/X6@_qﱅњպ,'\ Ls^?m[\.Uʃhʓ.KcQ}$,xZ^u?MOc8O.t oF.3 #aYF9!}0æ<2E6<=Y[փ+K+wqQ@zÈ"BHXs8 Q("]! e-@  Vko25!sat@~_Sy_IL)IoV:P\^Env454Z_[BK3/^.ʔ- YFOjO\ -m EY@g@i}Mee[#g[QF?R`Sun64^Z_@YIucj[{j+1w^U*;a5khWM &3w6sҫîyӂ ccnt7ԕd~B9#пl-3"5S+ֶ}#M++n}R]yf 9s1̆d0"fv;È^ʇ.Fd d2F#B&B& "#dRh 䬂{:Tb|kb/XR̢lWV (E>eyH4l\*P} !YG0aZ(#RtRH9zٯ1j'^*4~ߜ_JrE_[ 59 ?Iʌ*X ;dP 9:zө q+Jw Kvo2{g͎Eϓ;`yRoq^m,/69tSWK Rf{gE \(\ưxuUc_dUŘec@My) 1lg>jᆪɫg4>\)?7TK^!r+ f``F0V<Sd4 adԮ/P?ۃ2eLC0TiHF (B\&/ޯ0 Z/”Z{՚B&#gUׁX6yZdbXFɂح6jTJw8aSLJVӉqvkN~yMv˕w^bs$3Z VY\Tj v\xZm~WF?8qZ<6rџTxۿ~Ա yos)rQ v ݇CU ~w+h+^p4׈%*稣%,LVZL& ^v*Ru êT%o!4biyl,Jн%^ s+FguC5}k Vrz, a<q"PڃX HB`--]LR(" (QC;c)-z,ww|W]6A BH_16!g.y%"V  |<@jRȕr;>&5ooDFOdѽ <+`SkG r-HSN7.$$D>ҼNACGQz\.~ @e0=߮v{jM"# ^ \rPׯQPG rCb% E``gQTQ`Fd c0m*U~`EF!2QZr=g' O`&((9""BPy=Q(DFW8㎥!w(@z5HN*X;ͻ.x "<ϟ8q{N4#G`HkjdƓ;ƵƤ$@DV&./O\eG}t۶mǏOJJV%P}pmG322|0p+ ANkV7- srDͩj_q-[G-jkS~ԗF 'm.4tyE?M{jnn~|S j:th!|R\lg i(o:T_$P_k²}m^wVv5|*tݔ~b郕vy-{^]_[[~D2*kcInAA%gjٖ↎V`ooZq%00г,0qǏOHHyp8ή_N%>>8ցJ`:C"Kju[b` *x}ꄐkL֛]L>[{Ü9'|zQ.ںϊq1.ן:ڧP杻89tPt:cOy F#D5Lr?ms}6ڥ.?N$;Mjz\{]Z|[~Vyi%uu4c%lѣumM:IU &-pIDATdm5 ZVTg֊ 0%Ưě9lɘi4;+\͑fh~]qַ 1/ T%ճqɞav@OKVe6: ~xyd | AEB4m"YZaqlns銔iajMQp\.ǸbVL6f *b8L~2-}9/ b',w;N2-v5aVlXKy)C/8QϸYZדF /^ @a+) (כWSԭ1`@0! ~=<N`Ԩ;caUN` ˓ ])DXL`Ǟ鹱wIKHDfySq,!+x ܿE:+^LΪtQ-t],_[]T[`r:=l<%&9؊XMeI5NW*MI'/myj_ {ңB ) W D&{6'y.x OXg*"-_D }Pa!в>dBŮ;)ȯ94fGX~ba倡f*pS:*ԀnG-͵Qrtzp:sVıh`tW,Z`(CBtS~* @ni׻ (8WIm$D=+$P!00P \w'zx!S =)PEH0[vyo@KC&{CXPM &3w6sp<b2cp_LkMPQ\ ,zoW>''wvtg-u[^.t29:՛ʶdGXY@symhtd2P2s87_tCeH  ;~V-R]tԕU7hG, MnB0 F겋R^aڋQ:TbhO02#$@Բ KgڸUY)[s}*P]jx\P@jMXP.3=qy nX;sik/Ū8ϜKc5䊑x*̨UI@EkrR_[u 5V(ݩ.,ٽ}{5;J\XX+֜ tG܊[,'ݶar",؀D{mJL$jJskzoOE+v=}P$ HSmB\֛[m<Mn'R6njjг8՚Zm@TanZbAVK3, bR N W^\R?a-gmY MH-/y?%Bhr8,04mP'rS2PEt9K?^<.x!aꨡ*Li֓gv4-ƭ/3QֱwN 㭔! J9r(ƍ\AA[64bVJĬR*jnn^repp*@=$n6dū<6}/U߉Jܜ7vmu[~W|#+צD{/͍~?uE ic:0hޜfs߼x/ngh4N.VCƏ(e˖yV E#7|tmҤjd~_^c+kֿ~*tݔ~\郕vyl-{^]_[[~LRWVrU {qT^ywj9\>kI2u/85}k_Oݨ{铀Y"/ 9;~xBBNtvuu\7t,---˧x>aқ:@voI8?ld7 5zַ`0g^;}Q~[v~[@Nد?"Ҷ/ìZ\^b (Aqt:t:yBN={t:/N3JmyF^nKVj,jJl5ope_1@%K:|OeSZy G3ŧ.h! mY -@Ǵ&;:+2]iWo;*ێ_zk MV>qeJ"ƼMZ6~KcXjW@1/^jO YUƖkn=-yZڼ?|)y>88y;}?Eq׮]OG!<|PP{?.Kr\ SX}S1;641pMڹOuI玶>',EKv,2-$,hHG᪷v} jƝX. 9̨]@醍\1&iwEkJsRHǹSEl2% _uX! H.jq۔̝Fʂ%{kElJ2L i(5U֛mjMΚPTԸ7IϗAW(K{Z0x9SqVvx"|VcLrdk2*Mgb; '9=vbUCƸdHW05s^{;!^X;aP|( yA{]B0<Ͽ @``oA>Al(ϭ7Y\G ԪkX{םJJe!*Ӽu|q~L˶}:{r}=6B d(JF6ܔ U@l@;pv7Z>r:.s.Kt=Xj9` t*Ct(nZW#=QU=w :sVıh<}CFP-sP }fF3m֋w>a:pm?jnk_ni}3rzʉm11X㡶M^,+~jo) _%  Io_<`œT-@q2"gv-vż7z }Fdo/mՆY {B5aN&a[̍fK ȌQy1vA"?7pm¢rj{<ANM0Tukj/MKR@YԔ=S*urܗUu|Ϛ 13]je8eͱ1 E#0{;e!,F4V9V%gZs"6*a#TOLDJѶ8ET7*= 󇲖g)냸vϗ!EvÇqo0_0ʕ$aiWwC12K1CN!փ.njjgjTJZbAVPi$a"VM.TcjzGR}>P; hmmS*#oĬϭ:?p7h?q\CCCDDӧO766>r3B!䣏>뮫2۫?JYJsrw}]5\K%^<ܨ"ǤgN*(nA1)}*5ȕz`P&(JD}sJ^<.x!aꨡ U[6` \qǜg7z; P{_L]1zQnIV#GEqܸqW32#4h+T13k#Gog{Ƌ'Mǣ}+6Aų,0LZZڑ#GxdO=eA|O _֚. ~9tn5SG@@˲,d?s5jB e|x e`?i~޷~pځ aN[̙W_͛7O.d]]]Nӷ#QPлP\~ .1BxBvZOt VٳR>Q(R 1gΜq8t'IEQt8gΜ1!!!J/h/BPn8J2**O?/Lj=[;B<ȲөS(7iM(A~Pnm<`Y644tǏ7LVd2F3zP O%BhooUV:jNr|!!!&Ly{i\T*Je_B' r[B%BP* PP(JU)VIENDB`workbench-1.1.1/src/Resources/HelpFiles/Menus/File_Menu/Preferences/Thumbs.db000077500000000000000000000260001255417355300270670ustar00rootroot00000000000000ࡱ>  Root Entry0oMOs3uXO>5 |;-C}) .qbl?A8.9i2-N\G AsOiyĚϙ'[ڳ kXj/kmAIoSu[fF7ZWۇ oz:S'Vd\v>g5h Ŋdd|οCh>%Ṵ\}'ܶѼq,rNܞN9ocԯdùaMwFjz/CGi廂A>awaPyO1qRhtRGڙCOk[a e[ DD7:]uo&$MEqHEv6|ֻk7;yzK.b 'IU$JMI%Ԓiw\h U-mݗG#i dW5Ɩl[zgϖ^2GqmTK[f`bG=` 4D yGIǫ ^5.t8tMٞGiIkWF$S-bFxi.Af@H~TcGgiPk @zn2kMjɥZ<4>Krvo֯FuQ1}y:\x%R%ZC y_|NRM?Ohai2I`Fw@TWɼdLc$I0E(\$sch[Zo-6;MRZy5FgIP7tf۷TCh1rG$ {vI>g OCgR,7 W^A o$Knln䲛leo*0 v% 26c(7G jIi⽗$$mN6;N `Ǟt4K}x[WזOF3ywԲd1*pZ;6a 9qZU~-“\-ODni\ow:+ XYOm lA@7t Y_]/VƸ |PZZCBnݵ4h!#UHe$_w?1t Y?t YӹF;hWK_WK_;hӹF1t Y?t YӹF;hWK_WK_;hӹF1t Y?t YӹF;hWK_5tAEkwNye^D-!sV){Z̫UT ?4 :$B%e5^?xV5.5F/'Y7wz 5vy EَI;I8Oc.X|TkIt4(:(ޡhjr#ȗCniV(KlTK+_-&|fXm6jFs$r+#:ؽIk>\2J &w8읇5OV猴.9յy E(| @Qr a#h6m7UAӮ,{yQnM$v|9F0Փ|F=̐xW|_ h݋,rDn25Aʳ Eqv$կmD9hSxlw>Sڻr>!~㻅l4Ț/`+upB6Cg@#i뒹=] ws4qM)d Cd霘 vxoj0i7*ոDb  n'wCjW;U-Z[KmBmOYp`t>m5-m4DHt;WU&\;#H ڹQ@t_j_/o;GڼE?_ Af ?TKtl9<`Cl3p7 &*<[^"+hWJ{ڳ[ߑGJm~EVWK{>/^vħVQ[ߑ@/ҿ^vx?)UħVPox^"/jJm~E)U[^"+hWJ{ڳ[ߑGJm~EVWK{گz>KmKۿ*aq¶+(c^P6簳@5I  ż)oo$hGv }II#-ܖHs`wzoA.tc,T:PN)jєVWo4jV0',^5!U mnD˃N\;FϐyNýFi.ݬߥ(J얰NnBשQ\|Vc檓b $ #pN8Ҏƾ<k6O5 \BcWoI2=XzU=?wˬn׉hyF]h6pR0q]@ Ep3|F0_+Ɛ\4dD#U@Wrr@~.hk |bK| qٞEqw*ծ76yjHV!Xgn*JOhA Rvv{o)Ahacb -sxһ\Ɓw`1ol 1J1r4O2h:f}%̋mv,v+9(R09X隷?}Ht?<[*)vm2 M?#ҭ;x&]heB<(2$|{cN=_Ocf-˨Öu=j nPzolbᶷލWY#v̏+.Uej+ k> Wk BISҦi#in#U20yEFB=r@|U:u8WA-^ZMiYKWWt FC`W(#;bm3  N9P8JuΓ\$:eK"I[q#?^j Z %*"Ml^>Q>UzT m:mJLNɧOes#DS#E0Ɏ026Nti7R_c˵IdrWnTm<*<۽t{arr~F>ny5s []Z=\cw,H @Hsϸͭ#յۭPYce)_'ޙhK+KKf{ )8FkæY.762qzJ$͌n5wVFi'$aN1ћѳ~yI=ܓb]Wf୷ @ pyW=u֕idD+)]/c"9&*~kg?kmi$_ dSv4Zh 7e<3HmAB+kM(+kM(3juQ mǫOS7S7 ?U׿;o=G*ߝ?=Or=Or3?W^PЫ~vz? ?? ڃ]{mAB+kM(+kM(3juQ mǫOS7S7 ?U׿;o=YBθY'sj鿰? ʆLl [JERA`󎾕tJ)Ƥ\e6>OweW~ITqWy[M6ԐL0y8V4d$=`vt@rkJRΎzEVE [ zڔOu4Rמ^\jre K-,L>fUD !|A :^^E4R̡dQq! 2djQ@}2l^d^.F; A qR д[&-) Oiձ]-U-OGum[P\ZeYX!O'n~+MM5XjAr:v^l_?GOWF/F/g=yDqں_m?ں_m?v~l_?GOWF/F/g=yDqں_m?ں_m?v~l_?GOWF/F/g=yDqں_m?ں_m?v~l_?T5#m1Ȥk\AONMOMwTMBՙUe$h-[ qdam#=󎛱=@Y=9f06†U\պ+3ck>cg=ݘamq$@kW#u?)?)ע?)?)ע?)?)ע?)?)ע?)?)ע?)?)׬-WQTV`r뒥О1 +.L~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:;,˲c&IƸP(@6/r1vGe( `hFP,A`VUU]׃/]yZN `0`YvZih5M[#\.Wgxn,"v],K&\Rh4ޢBAooo ///at:yb1IR:R(a[3 CQd2hF!]u]𝟟B"-BBnTxO 1IENDB`workbench-1.1.1/src/Resources/HelpFiles/Menus/File_Menu/Save-Manage_Files/Reload_symbol.png000077500000000000000000000143351255417355300315740ustar00rootroot00000000000000PNG  IHDR7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:wщ|e3hFAPVѢfNEmڴi&hZ-lL@f11($ic6hM8̛{iaq2pss>L(jD{A&c׮Ka`̇c/D֢Ԋ"ѩ&V^^o?_XJ ǿh뺦i1DŢvjُmRJ йuM3tC#ϳLF[7Q43:6Nw=o04k(DZ#짙5ۻ޺ܠEsq"u0+;s20MSIyX9:دt؉a׍lF"UDP߰¨r~D$""BāUD4MFD۶[NB> d2+-'|J7㯺UJ)iE?Br!ZG!i|v Gĩt{ncLJLӔR{;jɖd:|> '?::/]xEH(~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:p/ ~..nc(zA#5#nVW/CVv >%R߽¢h/]܁[ 0(FhjiCZ8hN<5uXAg2333K+OCڎKӅ?~+k;~Cg:{D33sGă Save/Manage Files Save/Manage Files
Save/Manage Files contains save and/or manage options for all loaded files/files within the loaded specification file within a single dialog.
  • Filter buttons at the top of the dialog may be used to filter which files are listed by type, brain structure, files currently loaded, or files currently modified. One may set a Preference (Misc tab) for which type of files one would like to typically view (All, Loaded, Modified, etc.). The default is All files.
  • Click the Save checkboxes to select files to be saved (only modified files need to be saved).
  • A red YES in the Modified column warns you that a file has been modified and may need to be saved.
  • A YES in the Displayed column indicates that the file is currently being displayed in one of the tabs of the Viewing Window.
  • The checkboxes in the In Spec column may be deselected/selected to modify the contents of a spec file. Remember to click the Save checkbox next to the Spec file to save these changes.
  • Files with a  button in the Read column are currently loaded into memory. Click the button to reload the file.
  • Files with a  button in the Read column are NOT currently loaded into memory. Click to load the file.
  • Clicking the  button in the Close column removes the file from memory.
  • Clicking the  button in the More column shows options to view/edit metadata and change the name of the file.
  • All changes made in the Save/Manage Files dialog must be confirmed by clicking the Save Checked Files button at the bottom right.


Save-Manage_Files_dialog.png000066400000000000000000005262701255417355300334520ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Menus/File_Menu/Save-Manage_FilesPNG  IHDR->O ciCCPICC ProfileHPY_Das@ 20q`Beqր (5 X wEA9&Tnz_ wrDXTa8ۍ=<k7M wwшnqV4.Pq4n*g\8aЗ.B0M,1Op|HgdrH̼B@9DaƩfa?SϸeVxA(_nVjJrH)3FQoE)A ~0,dq ^' D>i }8A,oφF,-cOxY^+y@%0HA "=~M˻et l!Ԙaina?f;{ :]Y @ <"f3<@ЀPZ@ {i + (pA"HbցM `/(ap @38 .: #A8Q!%Hҁ K 9C? EAB:h TCeP%T BP?tƠ'a f0f~p(ë8Uq _o<(@(wT *6 P%*T= ՍQX4@>04].CC7;7C W 18`ؘH ǔ`1.mcvXl6 [=mcI39q\:.ww7}xK>/oƗkg) A@$ل#6 a(K#:CIMRb=D"iIKIR.tt4DH#1 vQr; BѥR)%c)['Q\Ij@4AZG%B:GD q GfLL̠̤,UB6P6UHVNNWS''wX0EբS-#. KӣiIB Z/mB^NZ>\~|9y Eץ)S;O ,xm  )*+(6(VPTJVڥԬHlT9Kr""EEN-U9ң2*RݯzIu\檖Gژ:UY]GsSza54iZ Z{:&յhӮӾCa$yUYwTOQWPZJրilp61L4,7a c퍅Uƃ&dIIɐ)tiK3mh]ff_mS̏?lfВkYnyˊbeѪꕵuA6T%6[m:lيmmb*4fycfG[tS88&;:.[a'M'SęEÅRU˕ZeJbgt3w5wwp_( ,|^3Ves5 _;~d2'b%%< 4@vGAzA~Y]|`u!Ԑ!!BBw> Q!4\y=J9J\lﲑ;Y~uVJ< Tq&qq\w> +oo,)8YSBq(߉?X8.p ^%$Jz|4y:%"!*& ;WZ_d$IV;޻zB'NҖӐ`ӓ]Psfy懬kddfo~ZZڎu6ZZ_cƼ#޹67%ouoDliS||qVǭG/wն۾ ~.]'laNN;\v+-)޽dwƞ=o{ĺ>⾌}RҖw\XvܭBb[]R=TxӏVzW6UVrXHV.n|槿+(B3y pHYs   IDATxw\TG핥w,D{5 (J6PAh,QƖ115jb Q+bKCh( 'Ǐ?LM50𖷷YGBחd2 ں򊊊byD.]..U*UEE@ 1\c`.<>_PQQ!JNhTUɎ9 ; ER,ϫSGh4XԂOwc48Ѿ+VA v%..޽{,+$$h/Bфn8N׽{¢3g="E988=z;wNMMѣG.]`"ahG&I3/h/gѢzރ{J (_S)9KcFnAZ@lfî?:y\V 04eW elP&TW\x1f\Z@ -,,Ξ=;bry:m㸟ܹsqgXۊ+bq```cE;WW3fQtN׫jh^k ejrGڳYlJ?~t\61[`aV3W8N4͖H$fWhuqyfQzPR DBµVQ!7#Fo666ԐC_#G?>??<==iiiiii۶m7n\TT޽{ wqǣN8h"8yҞ={.^lILTpp j7mtϟ?߿͛w㏽L4-`OOH ϧiǎ3 ᱱ0ÇObEDD\|oĉ߇!Ej<"***޽ rjJńC?w^ {`WWײ;w&'';88 x a71x8XH8 S1V0H ? 8|_%Ņ

=5jUaN=g֩t:LfVE[.8l MQ4# U_! :Zk mfx mNaÆ?HT(Iһwfee)Jf(,pxGZ'O\pP(HY~D"t0$ /lٲwզ=xI: ..ðI& zGGǪG=}jC>>[n5 (4 @ ^7C4-Wܺv 9B ' `+)uuu\.W,Mj 8NrdDw8h+ *^Ρ:͇ 72wL$ݻO:U"|׮][v oGmذ_~6lX@@׮][YY4gΜI&q8$?bŊDXlb^|||||<ik;egP(`iq>iDp{ ajJJs62T$@+*EyjiL:@{qttT(* q@'C|Ó'Oz^$) >jJD"H$Ű;t:X, ajZӑ$iaa! ӧ׮]o>q55514M FS[[ cggP(bZf2JK@VmoVqͭ.dbΎ䲵RTuuu/N!`qqq!OE*ʄšlF( hT*ϗ]V+ 3aZ\ p\@D&zDqT*UvCAz+vj塞S5!@ lpge~38,syzzY\>~xeƽj󆩓h,O1@>`{ ]gB,eAT]Y`l6-`HF)"Z)@ P(n޼cӯ_?___fO*@ ^/҄;034g.f< e[,@ lx!M:NTF߂ݜiw㵏f/=hqHG D Z5@ vMC[ @ ?#G@ 6MhmbBampns w=F @ ذj9t:ӶxQe0nc@ lj A8⭩H$𤔶B+Jx@ is\kN "777,,߿|e=zς [Ս7ݻgޯ_?OO֫b iM\VU(vvvS @ x]{m۶]~6mZVVVvvÇCCC[/,==}𣫫kPPPNNq͌+((.MUUUjjjDD<ť[nWh4V۶1TZVѠ9@ !_}UFF\.$viKKI(ʸ~4GVXXBqFjj_|zU[Rd~o@'IZR|}@ V"ÇGGGStѽ{,Yr%[[UV}z]]T*˻{nhh(Muuu0ϬY\]]/_|/255gϞ7|# SRR 322bbb^j0쌣ht7n܀===|qL6694hܹsӇ سgOa+KKˍ7Xb~~~<|C èQ"""݋֬Ynoo_ZZ k裏qjfH$ؔ1 Ze%%%M6z~zEEEpppd{ Q(j-}Q^2 P\\=o޼dgg7s̗[+9r䈇G=[@'lfO"$''O0!11qذa"P;vgeeeݻwO< h=E,###=|r7y ۷o}Y[[t@ ӧO߿gR&OL(0v_4h=p|?366f޼y,ٹiii?Hm{j) Z^, P՛6mʒdEvmkkkol[_>y _WW׷o_ @ 38mĨQrrrJ?O'|~4-322wN}R;mx642K:oĘ-koo 02{lNL4i֬YR^oii SD6[J׫f*0 :&ܹS.> V_Ϟ={ҤI4M;wN.>|=l8eʔ۷oÔ~- … ͷIczT*Je``ߎ;h~ѣCCC7mDTu+V8pܹs.\i&Xazz#FbVݻw5j+Wd ?ZʘVb(ܹ￿k.(¬Ϟ=;rȀLI?~DDD@@BhLU.,,uJbaÆ͘1/*J㖖Ǝ9rԨQP+&&f۶m t|T*J]x1((H.O0ӧfNHHXfM+ELVVVhh\.6lFQTs>|ŋOl~BB\.3fLqq1MӦZAjZ8NٳhVr$I+y̙燆^v m`&pF?&%%5 Zh"tmҥK80i$oo=ztȐ!FQmkj@#8M3{rʰ08~l<_}T*U}}=EQ%%%C 4hÆ AAA Q Dlf1YͩѣGoڴ q???&΁ܹs'''gȑEEGG;v? I ?~xhh(:(BqU:98H$2 ׮]P7f BW乢={>O)zi\\ܱc222JeJJ 4ibb!;v;wCО=:{s>uʋT\L}Гyz}``Ǐ)ڰaϞ={֭7n@UWooW޺u͛EӧO?s `/ IDATlV ,0iXӥKK.yyy Ch@@g}VUUqFٰa3;;{ 7odXH__^zQr̙qƙU<-n;SO}}=@,èESZwԩݻWVV~-rtt bΜ9>>>sνvO?tq___-//?w\mm-( gϞ~UUhZoe999W^J222rӦM:TaF5̝;w ŸqT*s>kkkO[x^ҥ 4lA.={vn\\a0z~WR,Loh6̴}+Wx<N> wfN~&maӧO7j/h/N :111111Գ?Zl0Ա ҍ뇻6-{;HQF1;}@K3f̘6mZyyŢ(j/_bxyy 2k=0M&xL޽{lmmogaaLsNӭLs>cv#⸸8#"""//Q/.Ei4?x`^4}…׻bS~^zB{ԩdݓ'O9sL&kѣGϝ;7))iС><}tǎ ƭ[l٘1c\\\jjjnJQCj!ϟ}vV{ٳg/\"4MӴi(VXX8awww6O4 Kr &욚kkzW(m2n M  "H05$A"( Hlvpj0&iJjZ,ST*1 0fs\Z-V5D4<ԘlP(T(\.&Tژ,sAAI@ h4XT*|>z( 5lRh$q7 D"hw[-R@6f-*kkkӔP(t$I BJb|8aXlQ Ϸp86b`P(%*$M<3A,4X0HT__/pgz^כmsqTZ[[ 55@ cǎƛCJJj+JJJZS vhbe7?f4_[&'|ǴLq"0z$ĬWi m=c2қ)m53~ h~fo6a)2@b1)oCnۃl@ hmzn2f4_[1-fkYh: sH/Ws#ZTV3E X\Skl1MHio @ {:MC?A @ ^z5&z.CXZ@ 1c!,Y~݀fkf8mB @1e2 mC;ǰբۄ@ xc`JJJ[7[[6Bt@ o l@DDD{@ @ `4]]]j9k:w6݆u"@ Nm:)2 , D @ t~HSSS#H\n֩J%o:@ vo?\.کS<<< ky>LNN&IK.m_P(5qYSZVPٵN5@ u︬m۶]~ (6mZVVVvvÇCCC[/,!!aʕ&M2 ۶m4hR|i[_Vb4Vm*VhP@ ȯ*##C. III^^^NNNQQQQQQmk׮9sL2l2(ɓk׮vZoêZRR'IZRDs@ 7L/>|8::GݻPUUdɒK.ڮZW\ٴi --mʕtvv&I2<<|0w…3X_]SS7hРUVAqFZnݬY{、$KKK̙3'Nu֪Uݻp&LܠׯC*++Gu"ƍw˗￷ڵСCr|֭;w̚5+==e赵RmZ][[@ ;vRi^^ݻwCCCi+((yf͚zG~嗩={ϿPRXXsU`gggM`iӦ}7^^^O>LLLxPnڡCǏ)**i$::Z.98844 ?6 "JJJ{M6y{{ϛ7o߾}s)**ZfMzz}ii)>M0j IͼTWWs8 VBqY^,7ˠСCuuu .l[e^6S^^~iӦT)o#G<<ܱcGqqq>}/_^VVֽ{w gddL<ŋƍ;qDppp!8qO?5jԂ OH$0 Nw@q"ᅦC(bi#GT/ kk`i "..ƦSNgϞbHNNuvvŭiTRt:Ngz 0y\1az`iioZPөTf·/]ǝ:uzN~zs (7nܘ6ms,Yx[M6;IUVq8w~.\hL/Cvm?y$ӳnݺ;bĈj%A]nܹs̘1@ ###=|r ۷oE}˳#,,7 O޿i)>>~ݫVrvv 38TrMD? EV33c-8EQ666;vXlٞ={VXVtU;;;Fcmm +J^' 4@i4fvgΜٵk׸86ӧOǍw戀[˗7n(^TDc??={7Sa׮] 8yȑ#u떞ޘ!m[ܲLfl޼͛/^xA?~ݺuK.5*}G F5*'''>>^T '$<_~4M򌌌ݻ4o>TN7Blll޽/\Xc0. B +++.ٙf9998~بt83iҤYf7GUU*Tbcc---a Ԑ e^W͑~֭k׮Miƍ3gd,ܠs>|ŋfϞ颣"ZLcmizΝ}XBB_={I&3g\z5M#F=ztJJJmT*l6F??~|˗0Er1ci~m7_m۶t'O=:44tӦMDtiiqr]`l]onܸ”[² W^lg}v޽!Cdff4MS?3(ZǍw^V[n92 Ii:$$~ h@ ?1twwݻʕ+njD:...>>>۷oر3D%%%Ǐ 4hÆ 'On00Iӧ daaQQQ_-77r`2 nܸ;|7pEQ#GK$77Uu_~-..^p!02e˖۷O2E TTTږjڎ;:(Ry7A'|P(Dڵkݺu+,,d<%p1-] >~ݽAǏ֞?^PDDDUUUGEEܳgϞ;wk׮ݳgO&N8k֬={,]t d .lܸ1!!?駟\]]9" ʈhl94¸Ǐ[XX̘1#%%e**11q׮]>>>$I:th׮];vj+V8vH$偁Qק]x Ç8 AQk׮}n۶ŋngdd̟??99>ZfKmz}vvvPPЍ7Z O2q7np…m۶tHoonݺ|kWOӕC ǣ( y֭VիBHHHHJJڿw (..V*̚SoO޵kw^^^'PTTr8 Z@ uꐨ+V3qP :tZ޸qc%ohh=~xPPE$I\sZvܸq'Oio߾pD9::z…|>ʔ) ,xś'hvttܲeŋ\nHHʕ+T5dȐwyy|~ptt駟.]cL&N:ѣy B9si߿ 4M$ivcNNtrݻOEF^HdnA4؛peXԩ>ðiӦYZZjZV 9s|r@vvVx&L|rXXԩS܌)gΜpHvjRX,cSNb$:4j(@PPJ/7nh46lM0`6gϦ8qB,/n޼)Hz~WxvzK.eee4M춛Zhz}zzT*̬t%bܸq2 }N:r7P ~Ĉ Ifܹl6ѱϭY^O4V]jԩ4'` Z"h=DGGGGG'0 'Z 90ϼ&]jU]]]SS<۶m E̞=;663|}}/^XVVfmmfMx"y睴JH$(8p˗+++!/// 5{a&^^JѣGQX57s7ݺuO>ql8]&8$l,%<<Eu/:1)++ 8p |>M74FVp`qghz[j'ŋ ++Ḹ7nU5$3F+@ |q/d2̴Ey Wmmm\jPT*mVVV 2My.9!qqq𣇇GDDD^^1[0z{ٯ_ݻw]$z!Ct72HF$׫j>/-ZrJT X8 JƍץKN:1OFLfaGXDm ={Fr{hh ܼH|$I6FJ4i҉'6n8o44vjaKGo>Ө?ej4q3f$&&ZXXMJKKq IDATgΜyH(fAt.]uбcW[m>úzѣGC ;w.EQfj_'; @ ī[zȑ#[7D777x1999vvv#Mә=zP*j[nJΝ;fDEE5GDžBV---ر#@TbpنX,L, `\MMu}}=3L4,K,3:N BNGP(K2,--kkkaXċ9mB$u:X,VT4Mx< !s#`lE"Q]]bI$R s* xmذɓ'VEU>azm0Հ ZxVq6. 4<$ƖF0x6RTR$p[^@ iq Ӫ2LPPfB!s dl @ DkV^ i#ZOJJ k+JJJš_. xUqc6|MHiYUղ6gh>SLn I_|ۺ vӆj=۰-6 z!M^@ KD l߆u"@ ~\SNyxxzaf>Z[[2=g^( Ӛ,)VP(Z@ w\Vnnmۮ_EL6-+++;;á~p5(((''ǸVh4V۶1TZV͂@ @|HddW_}!ᅤ$///''6ݢicR(2:!OX P*b^j$VU*j @ dx???xE%%%=zt޽%K\tvժU>>>rʦMiii+Wo\?ͬ,_=rIRs„ IؒV5 $I63D"p8aAվ^v288MKnݺ8͛IIIvvv3glN%6^%fmXTTtС VRVV4mڴ+{7(H$E%&&6L$QLb(jԩ(JO:522oYfM[)\ZoF9-}vAJKK_H-[.\̛7[n)pΝ ,K,qttxJB:e„ ς ^ׯׯjn"jᄑ8GFF&$$DGG=ztԳ50OAA`0o߾yyyr<==] xxxOi6>>I&O֯_d>`ܹ<j/VtU;;;Fcmm T*E!Bכtj^\X337A3khUquԩ(kתqoNm۷oEn߾mmmݧO@Ϟ=u0c3k0 ð~!00Cccmq._ qH #05Mff X˗7n(z9lذc6j8~$!5V… ;aDxj+5iD s GsxRg899>f *˿[.ؽ{w'JMc $2{d=W޽{w@n! P 7z;ųW"6Ti !s/&"(wd͛fgf޽;i$33Ç7Lԝ*4Pљ3gz- LL*#^XSS# k%wXJJʊ+D"ŪU,--߿pb %Ki6mt1@`ee$DEEZZZN>]M+\]]x%TSNÇHΝ;}ҥZZZBpO>B/ZۻsγfGUWW0`Bp…:t9s)FESRRlRVVfjji&mmmQTXi;wDGGoڴ 0 :t )X-*))ӧϸqz-|PPPBBBDDMLL>{,ʑe5o޼SP%Jmll6˗//[v饮Ǐ_l@ s'"""n߾ C?~K.]tg:tH(5i\300000|)8|"Iήm۶k^ Ν;oݺ577$ɪ*陟 SkÆ _d]§!Һ{D"qqq$]Z?jX$D -HݻgllI(J"HRA Ўv4 .\v;w|||6nH$_vŋ{y$N<Q[իW/^t̙3###UEJl6{ΝsPP D"P7o߿?..ڵkgΜ!I2..ׯ\R("KJJD"5kmllQrrcǾM1*Ua޽W\aXIIIh[7o g~-ڢr 11QW(kkk{{{ɓ'*UraUUUyy9I۶m0ɓ/.**R}d8\.{ݻwMKK(JGG}EyzzVTTR pJZ`Q̉'޼ySQPٓ'O߿onnrIa l ˕'O8N6m*++뗔$y<ŋ߿OQԵk<==y<uqqAB.\>k׮|> ^z?PSScaaaccC DuYFZ͟?ɓ'~bTqiiɓ' TeGŠѴ6mJxdd5kЃ>PZPYY6Gr̙ :!r!݁BB j隚bk׮U_MNNr -3!!w$I]ǣeTZUU%'&÷MӍ;44444d< 2dp۶mBmr!_~ŅT9=zѣe{դ ׯ_WwRs0J__RUYYijj+Jl3(["*hJSSSskΘ1)%%$ɷojkkcд$ɢ" ޿P˨(L' $$$**h޼y(Zuu5A8.IDC#)+$ɂ>}xxx8ŨTkfdd;4; +&:A;v ^d *UsTAAAV:p?|UQZH\)*6<>$-]_~СRC$8f٨F@Q0\. _V-_zUPLKKKKKKۤ^8{n˖-6lظqcll zL UhǎKJJHrJnݴ $Jј͛ -$}=m:99X,Ѿ0ޡS)**nDA\|gϞ$Ivʕ+RիWϞ=\v1qn}:MAUvVM">t555 .|)rTZ@ttt݋NCbQZ?< Gy|2// sQ &KzrttLHH@b{yU B7PϘbi~>uг Yo߾Hb2qԾƏ^خ]˗/Zh[l!IrӧO?}tݟ?׺u뀀CCN:m]t1bSYYپ}\]]աO>E)0,22R__>'(N:|ccS5hР_~{-[u0ҕR5eʔ9stNGG?Ÿj*EG9i$}}}ooGv]ivV-%%eǎK 8'LFҊU) 2c S($IΘ1#;; VQ2DPlzzzg2dCQ"Prbxʕ=z@!ΝӧOyyyPPPf۷333###e%|vÇ k\300000|)دp +++'OhתUJPآEN:),--EϢO~ZJBPCCCCCа 6A b1IajjjltmEE(5|{1 /-->_]]-J544HD444y< X?<O$iiiz Gl6#JD"a<O,񪪪0 ŨT*En(*4GSS~YYJ|I*=a٤;&TWWlkjjpEb(-C.bPa 2dkL(B/#@u5- PB!*Uq---ǑaWTT)1f|pۇ62 u_4Cpsss==zY^^߫WCd( W~i$ ӧЪ׭rA1IX F"۴g:-ksj$P_0~YVEΰl--&eTQmL#QXu*; D?[p}j 9߲Hj3 hH30zfO+r jyH=bX,M b}C9zF{ ^,SM Eo5;׻Ljb````````lhѢEPPwIڵk%?)5tn :Hi$j4*2 \|Y"Wbr9p8Bo} ]'='3c~΍AY>DFS&Cb7mqD" 'hK"͛7lԳKwLOfJ 64} L)F:╞xxJPfqg} / )4I7LߘƠscAFO#QQ 0A (qv`8.J000000000000 $IVUUUUUX,1(㣦 444jjj86aC:oXVPPP^^^YYr lmmMLL7Jx<2гgO iӦ/,+<11}]~=svv9sfbbgͿH!Cx<ѣGG۽{7O[[^z9sF.„ (uY@|Q~/ѺukTJ|>޽uTˑ#33'''חZ2АV$)) UBɢEd]\\x<7DV~qvv_mD۷onjjt300())J10|gqvv5jTNNηIvGnJ֋~n }E=BDDh=ǏR,,,޿˗/_|y/VZM2cǎ z'..n͚5`ժU89sahڵka3ü|ڴiաoo 6Df͚}իrrJ4 IJJЯ_ |3ϟ;::B- |%jIU555 eҥÝ;wJ߭[ VZ3227ӧO>|XH0rȡC۷o}}}sss-Zt%5|) ?Ǐ,X{zeffm۶kkv͞=-޻wUwÇϞ=gv->>6000xYvv6>zA6T*]vkTTZt???SS=z h&MzMݻ7?bO ŬYxqVBBB޽{G ٻwԩSm۶iiiI$}S\]]i7Eyxx ?z{{gdd'??_Q~EEŌ3ufjj[n(Ǐ;w|.޽߿+**c#W9A񇛛kV6mL}*J-VYY hB???ԨQ恁wܡJII?~|fͬt"QxڶmE_jo^:qD * IDAT TuSCtΝѣGkrĈh~ CH$8UŬ%m ڵk&MСÅ k@GGnݺ%;k,zePV){quu^'Lz=p„ NNN;vLHHسg`h͛7}E;vͅhMOO 0`@giڴ5k֭[iiiPj8jz ;CWt#G]&>|2}YZZ$gϞvyxx;wX%#|8=_jjM u-ܤ:yyy+iӦE՞۷odbb믿:99mٲeǎо}DzG+++Ν;7h HpA~y0 ϐyiiiƙ6mҥKby̙\2***zq矲W䤥UVV6x{{=駟~ۇ_|yȐ!(ܹs'N F3:r;w /^Ҵ<)S6lжmuy{{M?$I=}4,,L,Ν(<%%k׮Wϟ3gNzz:ñIKK;rvgϞ6h~i{۷+߿?ׯ_ҥݻw%(͸*g̘1ѣG6Û7ojhhЋaRQjPrʒJfk׮ǎk߾ȑ#o߾ݽ{w4pc~bbbfΜ`7nOwݿJ"T#|ٳg G:9kzqIIIxxԩSO<9}tR^DMϚ5֭[\.7##5*-`b}sr}d<߿H$*// [x1zeː*ꅚw#^n&=:866h 8WTTqywwӧ𘘘۷1~Ǐ4hܚCb׮];}'A߰#;􉋋 DsDVVZqLLL={6rټysqqCJJJZl`HUIL:=0a¢E&MDya}}{FDDo޽{/_ϟv۷&;JA#'+}}}G~z___K.]x._8¶w޲eFvȡ cǎSնm6`SSo_X<|7n,\[n}i}}4__LiUik.pBdd׮][=Ϝ9ӲeK///ӧO"Vnh뤱1l۷}=&&fժUg&I2"" O?ٳ 1ݻw̙3ZZZAAAjchhY Zӄ &N8gWWt\tܸqsA=GMڵk͛D"pBݾ}{#PܬYEhт/[)jn۶-77 .ܳgO߾}===^z{^,3˗)gGPj8z{̝;{QsQʊ+͟?24+V\zuȑHRSS숱Jz BΖ.<>OZVUU4LVW\߾}GO}l۶ٳt1Ʈ]"Or rƚΞ={w! tE|6Ґʦ߿?I˗III)9Xtی3޽{ᡨ\?{BCNJQL:}e3[!00"׶BPP4U۵kW w|5EEikkoܸqРAk֬A'|v~ !=9r$4i͛7b*phKIY,bl6K `A`+Q(Zx_^^~ҦM__ߜ޽{w֭[tG\𰶶H$( tfcm!E{ΝSLG(Eԉx ;;;OOÇ?|EUٯ1JJJ &{I;wNJJza6mB!Zu .\z内)vy{͍7._|׮]?\oYVVa܀o׮]Rt͚5z:p@ll,awyر}C *..]V8r)Q.Z$I$jժ˗gJS9u*CK,9tyU޽$Y͛7G_Y,ٳǍ7bĈ7n>|xu0FL<< … #G2dÇ}||<==v횔?ݹsHQbYYYׯ Ϟ=KLL0l޼ytHDΚ5ٹcǎ(ȈP(Κ5+((m}700=!Mӹs޿yIqq^޼ycccӽ{w\e~Cwr@dddII ڛcǎ\.+͸͓:i$@PXX8jԨZ<;6qijgϪ*Pau++V^t)((hذaݻ~zXXXXXl1b۶mׯ_G1O$55^^^666VZH$X,YcQcrɬr&$޿1 ɭ[qٳCmժZ6\MfŊ%&&x}ֲ.P_&0h 77N:>|u&H\\\=ztQ777 JAϜ9y9}1:jԨ[[[{yyun֬GӦMMMM߿?yL0@ԡjS r›7o ˗椪35(#ݨ*-[ Mkeo`CVۺY%"EN$a @NX$  н-&D"9uVݻwظד'OvssH$ ,@W{yy@`` 8p`dd$ڼyСC|ٴF!(fݻw~~~޽{_tW^ ߐ3f`X [ e$?#,,,##c…cǎ=yduu5a ԩP(}'(M%PB``3gάXb޽$I6mڴvr~p;#zDWU}_` @Q@IBJR$A)) ?WFIw}e˖r.(--xhw ksOaa!FYRSSѹ+uC,9D* ޾}kddituuUe+3 I2??Ą᠐2;;;[QQ!g0DJ M=Ǐ!w/*77qEd\555rMg:[uuuff &sPT,ЄM6U*H D")//G0441kRׯ_rrѣGuJF*U,⼼< EE盙iN>|W,+++d#jt bDر%00Ȣ3jE*{M'4ob• 7@ik.Ԉ|Ȑ--G b/Eij"xfmPeS/ 8˭C]Q2iҤ zR>]A[SKꯟa`E}' k8u^/B|U20( (p gb,ٮyH-0tBmr_#=4iRjؠX/W14Ơ,DFF-QeS<Դ^*)c,6N0`06XXLDoWg!JCdJOOԩS_)dƍ ZqECk :Hi$jU6^+Y_ʤ` _Q8S$ca l`a[7!|{c 2Sj71tHhT0eL!0000[0 6 g؀`8S{RLwLOfJ 64} L) ԧ`bc ǀgv&p'3c~΍AY>DFS&ÿ  0R@Q@P{}4e``````````@I@Q8APRJA*h:300000000000|%R)@aAR䧟d0 _zիW2000000OAQEEEJIf $H)ݨ>>}ݻwj☛(ߊ)S߹sg6m 2000000|S0 j $$ n׶QTRBÇcccG]```hPE+W/4h۶meeeaa!IE׊[133r[%~&^$/Jqyh`I$I/mLeP@D $IHDJH%ADEĻwL$ &A-[LLL$?Q200͛*MMMmm7o@ 41220,##C[[+ӒJ/^LHH077www֭Wp933:۷njfutt}Yruu_/2(%66ݝٵkWn׿+))ŋFFFommm[[[UBڵeLLLZlY_:t޶m>$''L0A6ZzzC$I߾};uDÇ?xаSNrQQmۖȸ~?zYRRrgϞ9::VVV>|8$$f߾}vvv6tڗ޸q~)[nBZ@"ܽ{իSLե?[;vlѢ|a[CQbllLv+@B`@@F.0 R$?b* G$ART"HRY*Ahh͆3gvݽaݺuvڽ{u]\\fΜiDDDXYY={,**"ݻw?}ƍnڳgϗJ{5, W\YTTT_2(eʕǏ 0a½{e˖_Zb[l .̚5ƍ7n1b7oNJJ;vWw+**>}Zˏ;֪Uٳg#H7nx{{bH?~ؽ{ӧOJXE.\?l`DDDxxΝ;M6x"|!<<|>|:{lxxYYwHv"ŠD":uyÇQF-$;;[6͛7׮]۴iӅ A $AR(JJDJRmC IDATU 4{BF8a2J(B.U2; .]nee5qDݬY3~''K~wVfff|||^^Y7nL<}0azsm8qb70003|c.]dɒс {[N6mÆ FFFJ\\\6oYgN>h"gРA/_ ?ѣ׬YVVVsX,͛ԩSŋ&&&DŽ~ݹsgv sΕЖ~Dfid 1S]۷oZj%>}bŊ-z,jcccijjժիW/ZH[[^$34:p0088Y1`Ạeu\.HTJ$I sl0004*0 kݺnnn...ZlժU\\\0 kݺWnԤ(*>>^.|Ĉnj3LMM7oN/ʕ+(('''>occF߽{~ҥK?1p@taIIرc-,,tuuP(;vl6mFY]] +W|={Ϝ9SZZ蘙޽$:tgϞW^uuu޴iהk׮1hѢ&++6m* gV\ieeդI%K(F}v~ZjأGΝ8i$5B]lhh8xR:\խÇ/V``ׯ?~ ;w yzz=z}=v옿?=PZ>x .D?Y0 S ԵP( ݼy*k׮}VrE]QQ1rH +6XXXTWW#96nضm[@0x`JU W(6MFQpp`Q!3P7 BJBP( Bfƞ={ZhC6-0t#trrp8[&&&9JyF"~Z"xzzr q|Ȑ!bj#Fk׮ݖ-[6lXJJ EI䔕ϥ#M^^^ݍ޽˱c*Y`߾}痝o>@f8{yܹS:TUUѣG_~ׯ_177GZ>}Vںu(//͛7R*..޽{wPPlW۠K[ZZZ\p.^˓'OIwRzڴi>>>Wzg|M֦wڕիϟF=9bիW####7o,XWPP@_VVX nB 1Yp4,60 8,ȞI9bbAO8pa kfeeI$_%IVVW&rSN%$$kNGyذa3fhӦڵkKJJ޽ԳgϷoߺ?{uVww3gXYY$@btttiW$mh=rHSSSCCCggg4\.WKKXKܺuk```>}ŋI\ɓ't2x`HT罰>9P>??K.FFF/_>>;wFsE98D"9rA7UߦMР N}zȑXzYuxxx-s}V$O/^fZ˄=Rի׶mk-XYYΙ3'))JI㏛6mڴi?•V[b0 0 Xlbabalg`KJT p8f8NOEOHRfǏ_zUQQΝ[UUFQÇ#""*++_|ѣ:C lٲ:%%E%iӦzҥr+|VVV/Isl~WV9]Cdzzz޽T/ĉW:Iz444LLLسgOС=Qd...r- R!-Z=HÂ,D;V^mjjri RaÆڵ(i,ӧOG[hdɒ3g>\E=eh,--M<&&>#--{Ȣ7o СK&iii\`ԨQ{4hPYYY>}~bX/_UTTTDEE$&&l۶mǎȟ~i'N R444/_>y#F7իT8_n]۶m'OΊgϞYYY۠ALMM"U<ք pH1  (€-չ*)<OCC=rr(e;``|>bI /\L[[>EΤ_v ~]tf 7n(++#bΝ=z޽իѣ*bܸqɇJ%%%g˖- /^$oeeyLѣGB r…ϟ$o߾*zለ(++iTJoHHH;X:::޹sG,۷mߗcС-***yg (//?tEQiii.\8׆=zݻo߾8/#Qûwխ[7##ѣG=Zqd& ׬Y<@ĴudkG۶mEE-f̘> :LcccwwwYO? .73GѣjkkݻGc^^˗/9Nhh(B^ gݚCa88l16 cp]U!aXaaaMM V΁8W&300|=ӦM;3g^C ˅РJGGܹs3fʄ^xlhhu7k r=<w܀+++SS 6E^dIDDD&Mw{v{⅞^',cꍶ***d999bZիWKR{](zuz=zJOOOݾ};[Px~ qT .\E8u*ALG[M[C jj ThD߾}E"Re%IrРAAAARChhh*3T888oBa^fϞݼyM5kֈDZ)%Z@iW_a@K(k_EE3BMMFvҥKKl$I zgK!I[ )6V144Tc R@$׸L}fI˲eJ! !Tɬk:})P-T^(9rd4hgU el2s:c1E2 T0R;!ԤI dff*$IR___]]oҨQ#-"Ye\NFjoU M9*ň#X%9!5w0_wE0...&L69j``&ɓ'XK(i@888av5jTmvXRpA|]*]Q-C˖-')G}cc0(a< /Y1@ciff0Ѐh柚DKP `Q)McheP,"ax_ՎD U5wyQ-^o}>ȠH=QAH>P2BZ1NwLdo;NyZwC3@11Mch)XUCByC _[WڹWl ꇢK.dTGsn=f΍woݯk6726Ma8888888$0><@!2OVkhn"!G @`oT5vttH^.ܿw;55/?⿻6M}\\\ 2kkIJ2ۆ ];`1fn\DMM=.6ں:gggaUvz0 0 HQAp]JR߆dg|-- {G" @nK[ 0 h{Gٓ(;K^NM큢a!*kױ 1я4L.NEG;-{5utt{ qǏiڦUTTjMoK*ȣLNBBBS۬3~TKkp1 Ϟk+&==@(Ui 5" @q"Ȩ]ϞxNvMb4zUC`G{0w {1쬁S vE@sgNڙ[@LkW"utL]Z[;r-115QI7o\i^͛޺9ǟq .{W.6?`HUU;otQqyX}R> =u拧' ]\%!%I֮+?=8/`!$$$$ℏPB4r?0o&ʧ3|:3O_F{ RU%ȤZb᳛1ñׯwCd?V/gq*^W ݅sg퟽Kzd到Ef޿{~ ӳ3BɶoِumJmOEfϞ%$H$!y Ҩi0fOe_l"|x,eqq9Y.ϟ١GV99VVV~~~w󳶶تc;gV Wڱ}ztjj(?} ^HOK}.ȡ\wOcǏ_uC|_ܣC˸&Bvx?;;{FF<ܧ/ f}x 52;_gϒ}kɍ+Bk O?JxZA#6_}1vH/^8wnڴiє(esp| 3#D@G$ $)"2[ioe)G9{Χ>Oєw*07V*}:Fuڄ0 أ.VW&1u$o7>kކ}7H۵c[QQTy.;̚p0q1֭YN`y Ɋ4hx)-mi-׃{Dw?M&9UÇzuW]]1pIظXcc'rs7jlFf|>Æ )))66.,.YUK'{{>+6Gb,,WcxF0hEs]pܕ-9~,ŵqkW"3UP|C1sB'FлKDT E6-{׭^vڕ2MJmݯ72n L&3zs;W \s94wNJo¹SUWa 0/ !L"HyFY+< BlheҬwo_Vn^Vǃ(;#֞O9ֶEc&.D*H&+R>S*B~6%:'͐H+-&N:\YeK?~Eŷ?ZdB$FƧ_rᣌyE2+ụƸ,ewwVnRcG߽{գӉe ع}wZ6 ڻ *6_D+PQ>́f!< #"5Wd9U! 7yN 8}$}c†M _\_摅 "y3U/.P9 bG _<LC?ԝȕCE(\b{/*is>;ra7 /yKGWБӟ2͎~L@ XM3@޾ZZ$xvM`ߞCȿZڸd6m|>$'&֨0!:+,,4YE:نS_L|G 6_buC.ImțN۽P>2oq?4.m0 ~=.X0I3i֯jԸqbZ%9^mȨQ詋mO8]\5lpOoSB1^HC"ՉvF~-yl܄|aSҽ0ŵ[υpԕ0772^WE'/uݺ E%%Ə3GOW.;{R/]ɸW,_]>#Ϟ9m$'%<~ ilE%]Sؘ /ݥoDh``8gf6NM)?t9"(>m.Бppp:~cЊy9GE4P0 hd ! =Dk%+ܳt3Gu V} k߱bTGg 9V[d[ԟ2|_ 牾 Ƙ$`鳧O;kj櫌5lEQл|$7BHvae]р˯Umif都x20|JdDUUfmqmݡ=v y]iS7wf|55))Ϛ6s("-ݻ&6i܅ '6!<ݗH:zl Յsvoo.ObHK355oH~4=9l޸~ڤqfOui*9uEeIM?蔖O8K08D;;X9ZN<oA{0G}rƍMRkx{͞v<**ʡQ-H:ڴ7`oK+!j/ lۘCvd(YW.]<|(xOX1WƱaGDY2{q|o041iL4EE̚#!ـY.h5U%j3ldfڭsOPK,k(u*$ 6nCț5$Q#~ &MorŚLI) s Ku u>c 7B(`Paa!CdqkہB]4<&uvr#-I:tQa0G޿d-ֽ|EږM,+W5gFq"6x˖3 4аo\++Ԕ$<_lبI'^:~\S.Wu@nnNNvaI #Xx}*MC#ϟ(to.XªĹZUvz{c`h'WiTѵ[ƎA :A}{vg+q OU#UoUKR ߥ+z٣Tӭ?wĩ#x,}U|DUɣۨ1ƏK9*{q|gP Lc`>zH9RT]UG"ҕ$dh(={ԩ:TJJ:UMqUe-[ȧ@&Kі_Bc!s0UӸ&,?!g^@S?6oٸ&)!.iM~Y8aq>?0ڶf m8r5Bd2ق =|yZ6,Xnq|[ܹ}1|)oX^HONJ &IݺT6v͝[,;.?v_ȹ3'Fz9닔iÚrb9:9)RxxNd=U}G'=}>'fCN]xuEQ1/רM[(i11Cc0CQ4f-( JBLU!OW$RJٻX4F$*!U}24$$ާ޿ʮT_:!-)y:gV&zmZ4v%r4ߙ=AGG}=yuݼi}aa!L6X76&$շ'9{+6-dպɣ_Yƌr#!DQEQcaC9zn/V7xi}90q]<۵rhjhy;-iq#Iݕ75jd|A{ Q-rr2*Lز1`!55muνK 3.52޷ŰMٵD"S- e EC[Y ?{jSFݜ3\WB|ggvf}whQ/l3ldQaࡊv)2-~V,+?tuoAvnm_Ř$ qK536=|Hiڻw"͌$3N"ꔸpA:%^e,Q~-wm]۶rXbm Ж6Md˧Rcw?NHUuێsgN!%́f`TL444 @3 5wٰaCv%(jڕ-;@ɵ AQ/4krA^v'x DվH]^Կ~c|MP֎T哜;cʄSd2a64 sʶYeӓ;o OT:/H8VbYrBǶ.zzbq`״ه̬̫7Y|un@޿bKJM]]UU\)9kHCv3Wu6A>@97 ]=E׀z_JY/2^ /6YttagI *rE_݅痾Ǣ&e~jse?PD ]pac)HbTK]SA,zD߾}E"I?r0(:m5 xX= o|IߟY=zcYYEikK=q]Yr vgѼagٱ{tro&"޽i7oӲ7V(°Ť:,z*Gm2(k!|]s#U0ɩ^J#U12(*n޸xd/6aм~ϗ :*B!U˘+f @@3ba$cAVxV\Yz=0߿T$I]=}uum|ˋ*e[ |=BQ4W6$'%D޺斖mڛW!Ma~|$ 7&dm^'"2)o\$ -j63w׍7Q:E4(dJ!h5bBzB孁~ɮBdJofnifnYw >}Q+ U"& |qc fx<䁀_*O_ L^$0><@{d9ˋ*ozAE|xP```m0qb@ijjwիG];`rʓ'Oգ>???++az*fMlQO`av( jhoz8,(;;ȨYTu0Eji !D$ I+$4}Qfqn)A'c/b@>Sg:^/(m[02GG8y)wu{{( Apuk];w 6L(%~۷5uuu !!!4M8PEE֤bܻwoر!ڽ׭ofϴilllÆ cxĉ666nnn5-LjjjAAP(ũ 5U_:ydVVsrrDaС;v쨢Hm*v.]:tt {YCCC''ŋ㿿N8agggii ?HHHӳpuu?x [Y"ȿYX^jccӾ}{ׯ__~=33/"c{JCCSNJ@<{,444))ҲgϞVVڹ5##̙3#FIκunݪ>lذW䔓'O(SS۳@QNhhرc_~]-qpT|}h(0` 4e/taM[d0Ytf>Ogɂwhpq6PنٵX ==U`f}˗}²e`l 11m[I^atuuOqFyʕ+;6cƌ۟y1ݻSN!Losӟ w9))$IG$A7nA5 xʔ)q`553g25xԩF3fprrruuζ ؽ{uvv3תUnzwww###68G=$==_^s?;x`v|-ZDGGo߾m۶ׯǍwׯo߾[n͚5wAo֬Y=Rvvvfb0+EXX̙3###׭[װa YMY[kٲӧOܹxaqTV0׭[w !jժG.ܺukDDѣR[*+ w McMSP(_[˷{#AckShJ;`~Gno Ò%Р~?yHOC:+000?UFe``uGRi@@b͛7/Zhu!7??zhAgddT\\ְaӧOKZ޽{p@ IDATDSNe :uꔑ+ ޽[E]1^tilllllĉ̌oذ!%%vҥս{5kּ||Ϟ=u3qDKK~WPC :t]veKBBB\]]7ndɒgC^_ ŢEZl ޽1bGRRٳG$ٕ{ٲe/_ iuuuYGիWYf…b]c3+9YVӧOsss̾O*pvv޺uk-,MspC( إ Lc`>zrxbʤYwޞ9ܼQ nwG W=o򰵅 `ɠP(_50c TpB0a¬Yؙ_ˆ?֢ ϟ"IҸ˗FոqӧO[XXԎWk?SgϺʍ;vի5* BiӦAlN( B;;;'''ggg6|iӦowneeնmۻwu8гgCYZZ.s׮]sppJ z捼ׯ;::jiiS5|}„ ݻw%9GM0|͛7O>ɓl-[ڷoۥKrݻ;v[x1dgg_"^rKjժ˗/Z[[>}p֭l۷oiiiY[[oٲ5 ޼ycmm}Ν6ÇlM6kkk4H :::{VR {xx(!PQCCc׮] %"Jn)1.}#addTTTS~QQQ>6lXEFFرc߾}e}Ç<QV ֵkWDҸq,EJҰaC,Ǜ6m3c r4$""}z}JR'''E2sp343E3 `a Er )`h{wP#A?w6lg`z3\` XA1 6?FF0oFANN][YBu3s̗/_Ι3G~gӊ+nܸѲeKTmOY`?~L@ ؼysf 8DxsN///D}Nj۶-P>>$%%eʔ)UGeeʔ)nnny&@/^x-[w^(*>>׷O>$>55uNd߯+cԨQ'N8bcc+(*J{74?~z ^UUU/^ࠫ?糇|}}ݫ_~z{{rƍٴgϞݹs'̞=gϞ׮]Ҫdgg^aIOO?qDPPիٕ5d֬Ysݵk=xӊj h Q1M 0 |OBeTn>{xUk#$RgK$t) Paa7o~*Y  'Noo6mlذaq煅aad;uUڵkk!`ߞ(ٳgcձcGv=x`+++6T=((hϞ=uB_~E65B455]SLLL<<>>:u*}x"h˖-Z&I%6;|jjIi&}}}__3geۻwoǎۺus4ϯgϞiii.\2eJƍ6l(7u^Epp|%0efZF12d4A”oYHEMDj 4*6͚6G"o`)V 4# 7Bh߾}l DͥdСm:c KvFH,`ä߫Ι3gݺuiii֭3gH$OHH믿Scc#FfO NV ʿ'kllL˗o޼y/psrrr6lп+VDEEϠQFϞ=S `L/]gڶm "Aqqqr.S]ٳgrrɓ_9rݻww{k\]]?^޽{WroDCA1 3iҤimm1?ؤyAAAb {zz#tmԩOJJx_7xKKd@OO/00pgϞbhv*!!gKah1Q Ml:ELm(%T]UG"ҕ$dh(={ԩ:TJJ:UMqCݒ.bc5`I.]BtHH7###&&t據HS͛W^v9s氻5+bkkˊ!}OfL6m46C'xR=z433SMM$Iϟonnnaa`v1XyȑꑢK.V֐!Cž>}0޽{ W=z\x-ڵkXҚv!nݺG]Ri׮]ٔ^t۷o844ٳzzzZbppH$b-jssrV?7nX1lmmLbddԼy>}шkӦ˿Q 3Jϟ?ӳ777jܸBVV^v\\ܜ9s/\8p@[[{͚5ǏSF@hhhiڵk7u?Ǜ6m-CEDDkhh,[aamҤInP/2zh===sss]]1cƔSO[ڄ[z#0%aÆK,lժɓ2|'OV\{98j$ !'O > PpF=&w޽D E-_RùHɧ-H|@}~֓K-nEROax TU-D*KvvvNNNM ?ǏOIIdg122:x`fjǚu? O2v*_ʗJ%?Qb1ff 233,--kzX>~S"޽CիW1waɒ%ʖMP}⅞bj2/@Y//_400(1f`W/ݻwIII 4}uB痾8>>^MM틟*®$T} VVV۷J*F[p(jpp(tݽ RRRJx B#//txM93~YY>Lj+1RdRmUmu5M-AEpzȋ(iAr߽2e {9 5jT]P{| x---GG9?F &DSx/L"GG$x$"y@  1B@$ Ibf"^> QCuu]^T9|[d2(RO'b+1n8888=Ok|D3@Q c`4ȨN[d2ْ߾kj8iu-GaiiL4EȨ'`П7EZʁ1UvRu)¥ܜlvҭgl&73*rpppppp(  BXkDyuٙ/'0ƺ xՖHS|V. {㗡d2Y~tnH6s F_*rv}[w־)rrS jo[:vR¤̝5e֜-\>f΍woݯk6726Ma8888888ichZ 0(奋\F)!aD8?ҫlkS{OmTӸq1^},ah(4&)hHQJb104MS%(b(E4[MنՈ¹3ضQ^'C5.G-7;OLc8oBL}ˆ7kSZPyS0LV>dәy2\MПlYI<$I B7 cVsǏ֩MM)RB}¬,7GA4 F2(dũ+_aFV,! R¬Cd2V 7>kކ}7H۵c[QQTy.;̚pioe)G9{Χ>Oєw*z?㸘G,?{E73{Sz{]a/?(b/(( tE]`ȹKᣳLyI&//8.Qc#QyyFƇFb4ESs~.:7q*:qJvAgkϠԫ0;8AAvwlac/5fo^۷5,y^[5z@agvgc7q0@"?y.fyR@0}X/w'{F',+-bfv-m]F 4puxp6}8L?b+=;ztikcׯW'(̩~=Ξ9x!~ҫk{ #-[sˠr.[7޻{Dav.! ݉?{յycMv@C9g׶6FZcF˒A[[Kמ'CN<voS%!P]i>rp +*J͎'u/U+ 5ࣇfg}6s$t+* ztS;wɥ# WI!/m+}.zK&N? <U! |*ȋGMk wDU~-p8&Np!{{ /ԫ0Ǐؿp8BК/--rϣ]CsxKNJ(*c~,^ 8#E۔cw쥛o^^_d~.R_ RSm^q[7o\">u<萑ً7<[{O<M=DSS+6Igw˩23>ߦʵmبM|߽/{/_ =x>ѓ={ѧ{G-mH:b46UeZz~{H‚ٷG;> ֮:y(ՌWX|ԅe#`+Rob{Io?))1!+#UCjj9Ӛڙ8Z;W.ߺc ˇoCFrTJ&1~ղExI㆗F>>qb@r faG0 0q0KǸ;Q<}Fno88aXC~fo[6m%YBOPRmD>VW֩o*+pq@v<!wwUq.~w p8 6PowA6]8z:4(đܜz; Io׮Y.6 G55rtەYǎ[linn*$ϊDTsss77;[׹jhZ =]}+kۜ,VwnGXnPpǦNcep"ng|xobbrRzZڎMcm[?pISg94ibO bލ-cfoss>TmѫcSgުu̳?9+̩[-,MLjz*碬Sutt+Oc6wozu{h#ʺrI왓4E{:tUSS?{$v!b:OY騩JL7:\8wڣUg//[=w䬏(|PUU|03U+۴]VʊJJJJ\]bT];lx^?c55+nW6j>rpptuC <a0w]_ 9HչPK[óM??uS3f-\7ƍ uӨkkU* ׅ_W|@ZZ*897rFjfO/.*Bgĵu+豰01 p(1.΅|.r9!76;ix/dYgτގEU2OCEQD3khНEQVZUM,ԉQ: B TU՜Pt=N:jKEQIxjF<(j֍@D>' 0^Ʀ-**RRRZۂ9{:dU))/_SQQԿ8y7669N}BJjFFrut mf.b /;-ِohH%t{kzAΈh;ؑE:@W.[jEb>3%Mnur@|#zmݢE0(`X6mQ୛E]-O.-G zu~JEIQgQc'M7򔈵-aߧJ:j#YF&.v,,? 13qp  $"UQPP|>)|?| Pu.,y?zog_-GuGK 34䏙p،:6RQWnSaJ*]{ER{ؾc뺷ɉown۰l|4+سw_^gk/AH$Z`ڡCEG7uΕay9*A$jbצV-WXRRR͹|LfZ*  .Y~^}Y>I<ۘY ö6221ORߦ |㨇_Rt􀖖v-uضe=* mrm#OV9 kVW^^&Y[Z|r^}~r a^߾uO33R&s~~AKeagߤk fd.\ j@\Ϗ6pqĔOٶe 6px.]G+/ϹAxΧ_.մrQaŪu<_<*.S3C~yiQN8bԸ!>,PĮ5<{dlΓgq q80p8@`p1 -//p8R{W8s8Y9-B>OQ#>/7Lϳc׎^G 0̄ꂖ6hkcZS ;+F :Z# ?3IGG0 ۯW6 B76s^ff}A}zګ'unhjjn2vа+!/ahz_]>uu&{6:&or/* MNp"舭Y3}X[s}צV::F~ H׭SQc&Xk/7s1'C=bjktnߪY?vl=x$]Z8Y4Ue<^vsҵgMڷiؑ+_hdlrx؍WMm\bo\ 13훒mlZ4oq.]4er%'%vhݼE> [id\fCG HG>ۚpvmjytnJGhke+l{,/]`a p #m[?yHkΌIKBOOz bjtacA[;>dQ*E)E%]{_|-,%+]`c7ma#TNVrJO5ϯ;UH0uܞ]>}*,, a88p1L 0;0j{ )$qݪ֭\mZ9N]TVh`zt}*??SA}"$In\ƠYG7J*\2 Ai(&Y[s/ԭIMI=}RzzH$t`ݸ#'lt4U;=yp~b߲(Sa6*Wyޥ&jU4ql*()͹uT d*))W}Fڥ &??m\PxQWrs +XPR_,gjR~ R+W7^m/ZV?}*q\ (95ck&Ydg}7ћa]5ssut3xQwo Tje))If̺NT$I~T + (*;룁a/aa!h`Sw'O14kOu`dT‰&&f^H i_ a}m3޿U[,,ux>dTmr&TTUQWePQ2]*e&aIҟ߲GK_*[XZ[XZhY~ THK*Rz$mQ0F 5 ̇ȴ)T߁a()#IA#8 H.T,_Pr7ggADD N  `N@@1@@SPX2  MS")(H (adaaWD"Ѽy 5k6sf2D4 4`4"@R b Kt}UVhYj0wiy!I2<<<<<ZZZu'hƾ1;-֚˗/cv'O.// ׯ4M'%%ccƌiai:22իW_''\YH$ܮE>u0hc84M4]Ӷ R4--А՟TGQ`  hp01.`@AbT!E}qРA4MC%hgnܸAQԿbK5qqq?:t(G!BLOOӧ~:44({{4cIQ×9]Ƿ)"Ji.c  `3oџO7.)) u1 0СDEEAa3gδK. :ɓ'ٵnݺy}II WVV~=hjj`].\>oaaakk%$I޼y3<P%/6ըPf{֭eŋ͛7߻wo-2uppؽ{w5##v6nhaaԊ#}5Uk׮Yvz)34*ğ/-w/)l;zذaaaaW^9<fh$))@QVVր2\Ƭi_\ٗ[Pƹ føJ jY 0 [haAܾ};::zԨQ.]cQܽ{L6 \r=++  CwܩWa0 sttTWWqյI&|>7i-_qtt%"'Nի{5kLCC{ϟmmm?~ܿ]]ݡC ѣG;w255E=$)HQZ!`̘1FFFjjjC 3iӦ#F(--pITTT߾}MLL4isNѣGUӯNaa޽{{VI;|RR`VדA0Ϟ=swwfƍa@}}˗W,Ua/ڢD޽쬭=hРϟ?eILL7===66<غukSS*=:((DFF;vLl*s)777۷owﯥeooY9ÇJ v۩S'O'Ntܹs:::gϮάA;6lЩS]JFTʑSVUev=tPtckk^!ʭ_dفT ܹS)ŋǎȘٳgrr+lmm322Faddw1T##RŁ$1o'OkNSSSOOoҥ C${'O`.KQxJJZFa0qda ދiP qH7lpȑ9s|qS%%%!+^nnnKGBN _BxVP\ɃN L;~2ySCˋ#BAAaM6q\OOٳVVV 5" ߫W/tŋ^^^Ȩ=99^9|]LL  }ii)ˍ۷OLL(*33k׮+VHKKp8{$ ֻw7og͛׽{wjhh$122ZpرcG/{}󃃃aoo7ox"<<ʕ+r'NLJJZbŢEЇm$ܹs,Xp:_7oސ$Y N;wyzzʏ|;ifU&|#G=z422Ν;vB0HUdwp8tÇ&MR<===q_u_)))SN566ؿ Ə?vXl%266 ?FGG ر˗/ŝ;wzyy@@@VZZg̘Q9>VN ?khܸ;222lق >>zzz[~3ڷo߿n߾=,,l޹ǎ311;w5jt>}͛7kJYYٝ;w6n8h 69XLFFFttt>>ŒM6uWX[[K6m09fEE>HF>x`DDR9KJJ ccc+!*m6}}aÆ]|o߾(p޽>>>tƍ WIU(_SS- X`Rvbعsghhʕ+ӧO{yyYfҤI/ jҤyǎk:q|Ȑ!G{?qjii}̼zرcؽ{LwwlYHmg svvx^^^OѣG7o>nܸVZ]zUN֯^ڿlllf͐+nx _mTu]?AjWi>p֭[NN>}ӧ7ozs֮]{5mmmYkjj8MLLwbڷoy|ݻw51tG8p@Ҭq̘1]vݿN!ֆ P=<|({__~>\WKEAAAOO>>>DEUqk#5;;;Iӣj6M 0`[}ΰLJWWWl*Ç6l[fyСCWN{{{9sD"En>>>Ƈ>pDKVXQg:99W^ԩӑ#G4Qe^~ݸq*s1111bĩS-[n:Yk$6JlTUP l`%.Tj355ϧ! 0&&ɩE'߿ݝ[š5kN:|ct$***6mrrr:ujroccwgfTj 4EŐE@ "#ߐDIIIAA=x x(+v@QThh(2NʊoҤI` c-6cMjsutԕa Z=L׮]~YAdhQkE+,btD3g"_h^9sLNN A$I.Zj$I8,:0SPӳgO@r@@@ X~=ZWuIlӦzVBBBݻwG[nq8KK޽;::h211Od;vpHIIyjf͚ o>ɓ'Oƈ#9"g={֭[W!pСÇaѣW\8Zmii١Cu֡oȁgeԬퟕUvv[|">'OHn"$nݺ>\pРA~BB˗k'6ʫdlmm _|Y^O(Κ5 &Lp49z%2 @WW]ĉ&MWϟ?/,,400ܹsPVV>rHPPɓ'@ro!C%K啗}Vj!f7<$ 8`8`E3S!2 “'O"c>Yιh?y$r0رC___2dƍm㸃YD"QC ) ]v~\D?0B&ZD g橀J˥O>/[ϯ?433322222266644VSSSUUC[}Al߾=66666)p--&777;;Ǐfff\.M"011cVVVnntb<{_޺u\EEޞgϞgF֚=z;$ϟWRRB7ĉ(<33mell\ZZ,UTT<<<֯_󃃃%ٷoԩSgll: EN#n*>URRUh6+PyqhhOVPPpssEWo@ ܹ38M.\JUavڥѴiSa&0ԩSq(v*zq >ּM0/]T__`Ւ=z9::***n۶ ]^zgEEǏK^\|YN:5777䆻ȑ#шtAʕ+!YYYݺu#R__ݻh׮ BYۗ7jh3Ĥ{VVV\.W\>(++ɓ';0Llll-plܸGppD)UYY~BII'ñD;En+~ pٲe(,5+++ xvvvO>\XOOO~{8xYdccٳgX[[7o.}̔0̬Y*Q#&-nhhQLY%.oIWwvn>4*/Qy~\|˦hx2}6>o+8$WZ,EQ/^xҥK]9{TH\zvӎZ(*s@8 |-- B(aV+LINN.**qݻ-,,z5|=US|(cFje) KlWj"_~0"+++//ήbiiiRRyS[RSSSSS+X@_h PYYyh|eee9U˗4IQO((((Q~hz}$ӧzmce=b2zzzUUB*#O2@A Ÿ+PTUSUPRr\]5O>2! UDMMmذah;,˿_RrssO8C֘1cƴ "--mW\Ar\.[n[lJÇ ,7o^͑1ثWbcc׭[qFcc,((ի5޽bŊ F Y3fp=!H\"33z-D~yڷo_!dȑ#FXj_㐱2(z[N(+2q0 STT#˴ }}}UUE'( r"Irr{񃍍wa щ'(1XX~,R}ٱ\7vAy ϓ'O M8@90 4H")i-=E?,,,,,,,,!p0Ec\8 (pXcLJXXXXXXXXXXX~A0 0q0KǸ_!q59h`?*,dd''㧂`+GK/181 @Us?uzE$-_2˗BGfⰰ|7`c8C Zv }EѲ a|xu>K䭈[7nN]:t!۰ gY-`,,,,,,,-0 0q |f 7GSKnguo$fqRJDů)*,mdl}'۹pEܾpތDvYZlؼOsϝĵ{ة0s΂觏7m}&f ) p 100 8ׇH֍-\CM111/  IDATvpެ)hC +MoSq뮡40 0 }^dB+Ą\RR04;+D tYXm\C4M9m/}Hqi5jlv]g(K݀a88'pC`p Iy(ؤ?MP*0(){)lPuN^9EB)%04&fhIJx} sn~oџOO 0lf(i`70`ֶv~ıh.//7kG&#㽰++)g|xF#:`)IFF;uʅ\Vֶ:]@7oE\704l½uvտ6??\Wv&a^>$EN{줊D'7q$~rқ>ug_khhiӢ.XMM[VPPGa&eO>3rhIoΜ !E=ztk%y;JKv+ثGH-{ZSRRv:DGG[>'[i2pt VčkYYFu,ګ˫){TVVvLH--n>Hκ~?`ұUmݥ:9sIĄ+>{jmT~֚Wʔ wƛ7a* 0 aAPQfIQ2ME$)HD$I(Bdm򟅢(X ?%t^ [LyTN(-haJ ^:{ C=zt-ҨoY8on˞A\Z(+|Xx.^d`tќ^ylڴڴim~~^vڴXhe}x8\ةS75޿g'[}ݰvĄv7d,r(,ܿw)*5voo33Rp鿯ZZ*O$;~gnn{w$Iq.,TN-ػnSZMn\ҩx>=^0QUMq777. |csWWW TmxԘzmװ{m=7둟ݚE?{yz{/W 'rwj4yȲ <{5lp?'{SϖK!ndD.mj衽]Dz۔$0WדA01Ϫ?$hdXMٺ)`+*G0zu5#IRV"nDgpYu;&/Ϧދ{GA(( 6"vOzvaAQXA47-?/I@Ի~-3o7og&#<YXu {G'8OйcM/KO8e-f?Ih<)ܙ3͟3DDO-b:[[7M.\/(<~5A':ڙkЃˉ>A)[/4<'b|-/E}5`i@Q A0=%"bl6nfsl6.)QڀU4Ʈ$dUkD^ y9mݟݲCqO=!<# JصOcS+PTTT.Fv?4t#-i_' ᑎ}Q{nNN vᜃ٫TptKCYcc#NO{`kv\ ?;+ðϟƏzƴT*\qrΞ6t՛яE߹ nmՠCs *nF HK,Yp ~8t";`N.Wo,at{UEgI~~[6BP[8`՟GEEQ>|uRj7OW|7;4zG.KjڊK!2;z!(Σw3UTUS^$chTTTQWX|A9*ZUUy32bL3|ܺ|+yMMr0JQaA1 ϟF  6)U֔\bɉϞ]4xi)ixJ p]S+r()CG/UPTex<^aa>6DOm@ښy;qNEUMhٓqN,--.E:x$ |ৢ”9I/3xxQBA\n5v D~lٴBxdfn \??fi3fVT,t }Co;|B3|**ܿgNtLWjt ?O?A`cE1%:牪`9`'P)TAIKI)UFFc ~1-FJFkAҩO &//ץ?m;AAkaÂ.++-T1h_fQ}cF$555Ύ';p޽{xhFx<^nn:\Y99v)XUEȤءOߘGy6m9gMϴ)0F=**,օbjaW>׷'ˍ}`JHHOU,lRs8dsohncϠT;Qk+WFVq`0 3_ z+lvCUUeUeś7i-8~젩}ȵ;iJ#]vU߷X:CM,lzs|>+iV𰐆inAO0E@ŠI>϶|! {a/=NpUT)m:BUeع?1e}[rv_(܄J+(N|'/F vn؉s-3O:xW\1LGv/ ҆cie`08"*ð,[o`$ԐAW 8 `8pL`L\t3&wm/ AEŐfQe%%tUϾǽx4a*\Ͻt BTW#B3Qu544n}y#fEC!I ZĴĀ8ǩU'~:j,ȫ999V=>:<--˗ڥ a:M`fhd$;y~"^6FmS C:o1^>:̃aخ#]VSzںgJ 6*#.xѭ=?> !>CJK!6ng#}y3*Fz;;e|OZf_z[wn7)*ȯHNQ>B+hieM&fSRRyUM]c,pE%eǾ.{ρ;B,[ݬxй,I5+9xZm)POϠ[w6/]:|TEyۍg'ػ}fƥ A=RT)1m:4oieXlU^?~{32b؈ѳgLS^|=dxo:ïdҸ}:oMjjj?}zob?`Z>Qv}yٵp1i3区sc<D2 i px(C1aMSCt:Nhw hJCH’aK1Y,&N)를ӀaN0#BJgC#YF.~q7 aAYމQQT Dׅ?OT*UXGAuZn:IIɍW744CˡwlaX99ٯ_lNxnqZZ ^žGmuR^$Y nƌ)zKJ}f7h5U & 4uy` ?~ۑRVJIJJ]|r6}T,&?Qc>D.zQ y{Ap855EKWvvGRF-{UŠAn𛧠}gaC/߀>@  ~r#U(/kjf`Z#/3W15|zϟmfge7#k9Y3VN'N+(*6V=ƒSVVjvҩ3|/Ar>{S}ý,+Ԟ0yfZ1wij~lV8ȭwSqo_WUUa7b>~\mm7mW?gAg/^{_G6,TaYzۦ\mB+՟=g|Z) y%'%k*._2߳@cg-xO7W_gN`hi.aFGVUUD^<}f[7]E[)))`~{ѲM- ݶ61Sٳș0 3D|&N~fAJY]KG/htpǏL~hvֆvO=}.F޽>vDwˡ|q+R }̙dy|3;+sSO C׬߬ ;wђ2Uem"9ޡ!#psٕB̞٭fDP(t*BR@"4* HЁ<{vnqcoӆFnhW +VoGcn;ð݄.RFa=VRWA.Ғ⊊r#cӖ6Ɯ,]M}+0Vş?)~Op1/x;JaV__'&eF**++ ;hMuU;ێ(,+-QVQmr655UUVUUU+P(oc˦ $40sl55&\I(H)ISd$5GbDCx,r3ddd'Ljbj޾)_ySbz]?}Que+"JQUPP6w,BzRP~{5N4t}|T5 7iڴmwZ!b! hCrᗂ=q:6Ocwm,3C9twl~ںpC=>^C{L>qt1EH&i#q9Fj H.X r2MpgskAuuueTL; ~@M#Pi&ߩ'rFF}\tb8$m㐹F*px<%9EFXn~[CDVVVFF"oOWA^H;'@G HB [Y d u⧧*J*67 !F!RB* &r=E RHHHHHHHH;APP " ؑ=Qax_ySbggADD !*  P(FP)PiBMy;MIHHHHHHHHHHHA* S @@A #uiTAx<ʕ+kjjzti2ABBBBBBBBS  ( wm0C0r䈇GW hѢ;88\xf͚m۶>|XWW+!!!!!!!(@@ ںr kk3fڣ ș3gv$֭6A,&FA(A:ء}TI5PH{ݹ{w8yyyiiinBٳ]y |񬬬Ç;v׷k!Ǐ}VFF&==`mm]___VV8*x߳3WB!EQ ~tBJ P퐇C*0@i568E:hcTJXh?qa?0aEU `ssc4p$]aK;֡8m8AdKOgɊ3*痕!rLכ!,]p)S84|SSS''dJIIgF̔Ȳ2711QVV;P֭C^\UU5==ܼBדy<`=<&&fѢEzݓ'O233 4`=ګW}VAAQL7zzzbj |r**`aaj%W^󫪪jI ۷544lmm  #(@AEBPD`wk&a8c(CQ( a&jl, f%n++^ș< rIS,/?>|@Ri4JP(N߿^^٦M::sϞ=SRR;i999w-**ˑ 6䤥AťK,Zڵk&ӧܶ̆hO>5 t1quuuPPФI?dȑ#߳br4y;{yy?~͏=L_f̙3w^r1cTjVVֶm۲uttƧ%җ/_毱}iԩSNj6EE貲2UUU/_'***p~}{1b͞2eʐ!CN:oOСCׯ_h7:}-\_jU2ܹs,[ ^x<ޤIzվ+[flذ.++ƍcƌ騚~-N*0Ph|E EQ q ௓Lf/MU/_jo,)- QlFA$GK ]A*ѣ3gܼyаfCll,<n߾FDD㘘NAKKK999 booogggaad2Lt_pszyy ..GÆ kS&&&IIIcǎUQQ2e ͆ Me $ݻ+++}}}555eee9И>}zccc"Gֶ8rL>xuԜ8qbĈp`2ׯ_ M' H9_x!AH۵6n2P(ƏobbJbccmll&LP]]OLRSSXiii@'''ϸq.\+**>WWW?yjjjիכ7o@kŋ &xӻwoQ'''UUUTxx.楋𐓓0`4.mYYYh ZVV}vM|['^N&.233sss暘TVV?'M7CѣG̤uuuѱXaݺu|kkkf;vIII&&&O|x۶m:::NNNϟ?W'B$Y FPCQN#45/, ھRDX,f `lf%%Juju͗K7/ճ3[Vsr6N!AyJJHH:tʊB^zаsrr4  :u˗/𬫫7ዝݩœ={455Fq8333OEYcc#NOII155 W~77L >}4dȐM6hǏPʚ:uȑ#qؓ'OVTTpuu~UTT۷UEΝiӦkBc!(+V^ $m(63B'vs >ۛ7o6n8p@իWZmyyyHHȽ{f̘SN:uǏ=z͊U%=qӧOi4JJKK:qyedd**-m`<''GCCc…ZZZNUÔ)S>}t҈# | 6meeWSS?ߴi7lذzjeB4i4m4t֭ qFEEŶm<ƛ7o.Y9ddd56"^έ[>y^CjiӦرc՘,%RAALӋ/+..k3}wE5553lB3̘1c鶶O>x`…C_q))ӧ+)){u|d2{{6lzG?-pH0 A1ΦB !!!}ȑ#aaa7o_gKKK/_윓m۶y}Ih ӧO&j˫'z˄ 8hsʔ)III999?~E=CѣG ү_y\.00ٳ={fر@λv4SJQQɓ'sYdСC[7Dqqmm[Љ҇R??P!eXb\$%%$$$TTTd9ǻta8;;[XX<~իW-¨ŋtޝ?;陙qqq pپ_L1 ~:daa1tPF!JGQ*?|rQnioho+֭Tqq{L&%e,BB" CE`iJV=a`ЄsyN?zNit R]]-//aҥ uu[={JJJ`:HHHs`ppsssss[hQbbׯ,XpXZZѣ?~`0 jkk`' 344 ؼy3S #Gtuu:uGYYׯ` 6mte wٳY%yyy+W| 4jt֭=? # Djlll;jԨ ӧOر#66o.&#`ɳfJMM#׬Yx &?uuuB h=0|VkccM&SVVИ3gÇE]\\&Nپ}{HH)S5͍=|gÆ {m Иe+ DÇwvv>q℡a|14v3~? T*O/bG mĸT.F0*hSYFOT^^qNЛD -SqPX2Ly)4Ťө?eWRs0 sxAHcdd$#+r/\pB4 pd׏Jb&Ņ?0l*M2GZZڇ$%%WZCDhh͛Y,V}}}vv˗/6 hkkSGGGGqqq ,h{ ǎy)OJJ}Tx{{eff644@vcddԽ{h~G)%%eee%+W~xW1x{{3Z̤ٳg===ۃK'1 omVbjjʷEO7nܸq;wTWW&&&UA[2y7L:UpRFM6̙3o߾[_^L2%00 ftt:U#+ իWݻw[l֭cEgٲe.]wSD.]  0 a-Ƿmmf`mD>L6׿{n]]݈#0vاOˆ&maذa?~|QQѷ.KJJ̛7oԩ-DLLLk9ضm… M6{옘}[V3+P(ZZ#k?6h)ܲ,M^}7Y")))!!}O W Lꪻ#PWTTVd( 5S,QQ()QUʊrRU~jADZZ:a(vС;vdfffddڹkժUpUZtY36WWכ7oA[t) O:+WJKKSTE׮]k```hhn:k\r8Ty_\6k.تuܓ'OPuqqׇmݻXh{Cflll``v1=3++ bmmm&󜜜fU4552Iۡv}{`JRR֭[׎*++Ν;k,URRRRRT0\.7((-[h̙3zhh'O}?p `0^| dСEEE!!!'00WKUUUp.̞=;;;;** e˖f!QQQ> __ϟj}+>HKKy頽>c񊊊 .hii 27!!f)L) IDAT3/'aaaaО8##Y*Qww}h(CKKKRRٳgf͂s͘={v\\ŋQjsV=1D郳W՛3K)Jjkkeoo@b|ÇzzzfffP6\ #Fc###==ÇlQ5ruIIIC_x )**jii566fffIKK;::_HH0'O_p1cIKM8pTCC @VVVMMML=͂|gbbWl6xРA88555UTT֬YCS{* AG"Bh%A,\Z?_pA^^dB9s 6oݺUP3 KKKuAݑ߾}KBpg޼y&Lbii)Xѣ01">{ ={9GDD<+++_rE3WaeddKKKE|cǎ>L={6FSUUΛ7OL%br \OOoٲe<-w7k, 9zLb>ܷo?ٳ0l͚5򑑑Axxx6ƍP(,K^^ݻ0]>r߿ƍ xÇ֯_uuuuuuw)T/^ O>P('OlvoߖRVVVVPPFFF={|)LlGjHgEDppft㟐ϟ|)))>NHHHOOsMMM]]]mm;w !C4kQݲeI ^zaÆf$qPݲu@XR4. @}Qݻsx<֚; Ĉ=z4?%(*))]zƷJvvPjhh(--MII122xb%%%222mYav`58GIIɖ+UUU ނC⊊ EEf60iii λm?]WWwcǎ{yyM6 G}}}nnn;ðϟ?wMVp86߼Hh%_|AԴTcccVV^kkÔ)SZn}|MGUx<^vv(//V>chh,/*v8u>8BrZ6m/#!!ZN1 F}EBBpGQӧ"!AdddN:ܼz$J)++xbDDDii)Ơ+[۷o SC߿7j W^reϞ=1۷ovܹg--߹s'$$cwk 0 gϞq '>܏?iͭYNL5:6ol+]]V#IX,O B;2QWWWZZ*jk*&##Kt"I`ߨr츸8hlll  ?w% ɏEp >d4ݻw+**ڲ?zR.\l%n(7-t:9K0LSS(E4 y"qgAtqHFdO{ %rwsl٨(Kwʛïx?? $$bTGQM`h256ҺǼK8$$$$$$$$$]Myd!,4Y hY 8uDP} aʊrzmX`,wL$!!!!!!! MQIԹꪪ奟E'PQ9JAQ$$U)e{3it!sxΰX4^$)1!4$ȉ]?yfLG w;ݣ+),[bUvX|uJr{HK[+!!!!!!!Q {m>m[ۙvHq<55qfv$*۷F[j5Ť(  ء}TIuPHO=;ɉyo^Eݽ gr(r! r/8ɚ4nĞGLa Iwoe2ߥl'WE <O$$tM8YN~ȋ(/ 'RK@QCZנ.a奟-,&8ZD_B`fnn~= ~'ðRΥuD#N" 'w ,Y?ӒɂkAF!rHNA̝uSqojjZlSg STTih`2RRE())#dIIij~zۑe&ohdw (({z98o{يr=&&Fszj Y-؉x)]$##+%@CSN߿?//lo;:km+WPTw><}F\l9^ByzЫU۷eUh"þit13g{T7IL=eͥGgŲuqY!CF{'ӷg̍[w456_5k JoiYZ:~(((^3o#[7 Zv8~xŪ.KṬ2eU@]ݗQmllXtUJ+J?L7{O^hRb~enmz}r3scט9u"ΜhV-A':ڙkk%J?m32mгOcw(ISN?"ɾi@/U$ RYY1ehCme7gItmljs!n&zj۷D h-O?ulu tNMyO{gS?sgivQ)TBʾ%}_;yǖ"(e )}ӾQT16D_s9~ΙsϜL^ T*`0fa0 `0yP4Re3k4$T%r+!M sgJoz&Z%z<EѺx nٰ}[edBsWok._4oVmm /|IO9ST}L㵛v՟JnG62ĹFJcZmG|篭cFhtWkj#/>7itrK1|sF`P9cV欩>6P[hIgL~#ǬAcviZX͜Q-T5.8)gmNJKQRV9|p//:z!Ĩdp8+#qxA=U%HDBKII Eo?q5F" x"G!E(YYY;Gg8=W[AW"B"WUwVl6cV[x6!p"--M߈[Y,]mcc榭#H@[!Jz=Hٺcif_@[ZRͅcֿB¯Od}?~0{!CH$8$&L"G75xuTâP28ȵtCG()c;W}Lcp޼NTZ!rhkahHBF:*_5b;Ϟrjk15x^TB +'mfr%_| FwtB/9s}i*_0>M;V.[ $O$,ֵSv}D}&_yik\>K>r}' vݒK7weF 7Dq.RjwN%2*-s\Po eFlڶe4&A$)Tj~;7}0y:lr|*-;gbu M^'~Za}ldDhssӮ:2CҹyM :f %Q(I,h(!,IKH"AެTT8n+EQRD"륤e!(˛>TTR>&6 Λ^$AmI)vvׯ^E۷oE_폾kjjjfw׾#( ~|Qa<RepLO^{ԅeX[/XvV2"&~=iicGgTp8{wm~EYE5s~2)..ܲau M- DEWg*? Y"TUV(jWOkk˶5\9oBxr{n>g/&uMY41_,`wn)-.jjl|?kOA4B3),,05.Y+܊:c֜G?tyvP gRݑKOqcO{Hm Db09Y/]vzZbs4pZcp3F&W".!2nvD⶝Fy1esyߔ|9Qkf.{A@IqQQQI<];6>;}#DE1 ޞY=Be6s]jkms !=`{ H$DNBhl! EV,+IRDPv%|T*)77צb]1b:s_/TQqIgN7gasx̢VrS=}@Zj /񱠃Ȑ7B0SkѬ,%A´14D3Qh4k7o;@n޸{疻 O׏1kܴIŬ$EgF jӎ@%y_MWX, A IDATh!&^8 PiK.Jp'5uv `DTeNj|~|lSSahڷrقY'\tF:Jh8syD )..!US+**y |3z8Np8QPcVxyTX'!)ijf=hNf=aԩ4kkk/+f2egW#lv]]-s'#^~Q9`$gRIY3.R7 nC&Sxż2*)<{DCC}{wc}&)6Tt-;R= 9!R^QF p8@hmm-+ĎLh J@^#FzFdB t Aƫ+z~7_AA d9uc(#3LeA{xveyK"b¯܄'! 6`HRR*"v;s,);i~F&prPGŢ|,mleO@fyپFi(kx.@gBN4ƌbfͭU_j~/Buvm޷{JW.^' FG;'gN4յ0j,` l>&FwwHWwojX{禘&Ӈ8M6o%#+` EϜd*o/scMc]nYhAdi[78DKKgE LQ|oWp52LG~)I:jKCw骎po:w\]:q;#3g8vTdgy g3zڍ5s&=K-{'v/ڛ0yDq8h)70m;jCjt=*rI_~:x<Mb563+(LyZ<>}zDؿڞ&*nݏݲC~#pw 'J;yФ6l{;@KixI/E݋174V ;[;h>sѣ 6v6,DW$%\fS K"uMY(1-/޿wJ_7Wk$[AQPz0ӛ+Wo\2'?CEE,߁uj ;w6չvlı<߲2:]BXfiy sheLL͕UjwO*+$h4oƄ\nQQJTBpTC99z;&HHH(Z[Sͯ Z"1h~^NScO1|iGDwMZ<]<LEmQ d0tL "EQYQ^SSo`$Ғ-& E%pyvK jjjk.Yia2sut(T*xc *G[[[]mM',:R?ofjۅc>)(*ja'zK_ ]IJJST*L&Cu*hᝊSDo!(~ʌ9ĩF}:GvO4k] UUUffM5;N 7]ÿN$܇sPXuNO%7]ly?;3@ҋwݺs_#Ɵ V'Õy9$>3!n~]=q:&Xv}=}?DCC艳] }L.(޸q˗,ۛTb^xѣŋKKKÇOXѣG8ÇLĉibϪ?qıc`WOfffffC 4h֭[t?1ME|ٓkkk9rd^^^kkk||<奨.mmmMLLϵkS<]ԹĨZZZ?^VV'ѣP?CѸ\.C k׮Rﷳ+--wL d2bٮo߾ݸqEEEwc#Fy׮]GMHH󓗗w(fdd466v.yddʕ+"%gϞ 4d]rwksݻ;wM:(RZZکS:'v"/{nnǡC:Ǐ177#@]]ݜ9s cbb"""hbbfs- MLL0]vu/,/C;wP ꛚ &hnnnll=x _IJJ(ii\E߯.#G_HtqCT3-u6zHRuHR:XxpB=׋=pt:.####-#//oaaall,))ihhpAz{{?|{%$$Errr r322 #G222  FO>PAGYr%?tfArrrp8\YYYÅ\0P]pVCCd2G.d2|EE7s?~{b,YCL5kkKK &Ι3Eѭ[:99qppXz5>p&FQǏD"PLD:vX>}:.gg-[t""/{[[aw[lqqq.9)))P`%>W}ףun<{˗o߾)..ojjjiimi1/_ܹsd2rl6r>x$FW //aW\+*2BIHHhmmF8 U-_|~6upBuX@dݺux M͜9SSS֭[zzzݣcxxbxckk[^^?:99EEEGu0p8+++ BPLLL,--O77oިQ ?&&&WVVvĈp嬾0%%eܸqSLa00rRR;NԄ?BsB(zvᵵ~~~jjjҖgff:}vInjabbrQMHHprripɑ#GB+)BqFvv6Ayt:A¦v0r޽yE]vihh(++oٲE0}Q©L?~laa!//[__ U'^byxx555H0ѣG^1Wcǎ--v{D?vqq xs>lnn.'' COtEAE5e_~({Ǐ|IǏ9sF؝@QQL&?555^^^򖖖B+ŋrrrJJJ6m""?ܦMsNMMM{{/^a0ynnn+7)5Q!%%%eeeTTT|*TRR׳$"""p8\@@AKK ܿBW>͟?r2  VD 5rӻS7c7hʆJW^)R`{'.[UUhBW;Μ9sESNR_ sYCCC2L&===xUr<==d2D244<{>~$fhׯ׏5jU:}} .+))vvvr( ͡p":cƌj^nLLLkk+\ljj:|yyyeeիW]2LMMe2o߆nݺ+kx!emmm<͛7++ @CۻwVUUʤFڵÇjjj~ֺu5Ԋr;vĉ .TSS>u`ۉt1A%&&>}J~}؃*{EEŸ8Qjnn^|CUU* C޿?gΜ^z :thaaaii'o?DrJJJ$%%ǍPPP:ݹ9;;.^ݻwcǎ3f /na-#߽"fB&0_ CpxA~񱳳;tkd}+YYY{]o0QHx))!pӂGR2fH2O=~?AVVZO0geٙ7oل899ݼySFF ~]DKKͭ,.ֶqss@[eeeCCr{{x^{N6¢_~0@ ܽ{XKK ɁǮ]_۷o_phѢÇH$h)!!1}t[aee5tP Ԕɻu#Fcfff1p8iKII |8OA IGPx&P+^V7㓝=x`@,?~b --$*Y(DY?SG}穨(q8V$>!D 7,[ N=zunjMMMp ΛRqqqqqqqYxqrr۷o###.\țFFF暚۷ 455drqqϟa8^'x񢿿^@@m(=zԩS RUU  lݺʕ+>|ׯ_L WZWzՙHLL}bT---M^^^ .gΜٽ{Ǐa0`ɓ'Ϛ5 nňbk׮0`͛7y566B"~)Ch&ߥ2:|(UUչs޿>TAAq„ I&ڵ+44t͚5sSPPaQo;ݻ3~'Bs檪FGGϛ7/(((((hرrOOO'OuND}ӦM\. 'NN6m"'OSD]ܻw`1ݻw=V9źjjjѾ;׹ bIl( H$|( 5SyH7;/_yU"+AR)D"9qΧײG\ᶶPG___JZҥK-% /^d2@ .F 9*GGG$fyg IDAT۷]]]NHzzz^^^cc#F[zussIP ߶mJmjjyb5 %%tx">>***$iΝ-6mٳ;n>'ӦM޽8rH^ϟ?7**㇨8S n"RXX"X9UUU{G컰 sl@F")Td9p8P^^?MNUtDUjԥKpxEy #[GA$%%9rݻ233ٻzj+->ccvv~NNNn݂l27OWVVVJJJx6n:]]]==$$$VO|(|ɇ#&Mb0{Ԏ{ vttсmÇ?g'...ǏOKKdggsr ŋ܆vYUTTjqD޽{cbb`HJJʎ;<؉jkk͛7k,*ZQQQQQ!f`Z[[/^ȯY$A-^x̙p?sΜ9O|xiiihh( ԤCٳgrss`G|rXv"uٳg'&&Z2ݝ+oniiiNNH0aj_nhhPQQqww繉?qĞ={)в;88LʤIxtz\\\FFٳ;87*v'ػw/ByeTQQSSSwK"X9'OP(6lnkk:عu1[,Nd^|F%.˭|2=)++߿|'TTT())p>}hkk/_buL&smݐZnJ#714DSKXŽu~JK촴UUU奥KJJA<}x<ȑ#.\nLKKH$-S(+++//v? lT*" B߿-))all X792 ^ikk{zzlsƍ4 ~6665Q:Ғ/))9`8  ԩSҲ-;vzii)f#/TOOû,+++%%nm~#""Ŀr DQVVEo/000pww/桚ڵkmB ǎ533CQTh&(.Zj/]$++KPܹsaMTTTv/ d)J=|0 'os] 8Yv-DF Y,y}}Paon||֞={`III}}~=}2i*BO``P999+V*6n -T?|0^E>}I=cbbxYPPN]hƪR(rwusMsHHZpo^;un먍6 ֮ f۷op޼yK FP]]]SSզBaw7srJU! ǩUoڸ緟>̛7b;VSS ֱ ƳgƌP ~\yyk׮999u$+++%%% nff\YY=,P}쑽Kǎ jk Kaβ X?C(.+^}]]|2Q E{ ӻLBߕ/;7}*'b8KV PS~~ЋGOȣkW- >uLW` g!)LIqᚕKV`6*2 lŚ)u 7 f֖}--g:!z*!.zę]% oˮ&NkR! Prx {k< L...x{6 A'vT$b HKpܼG<6e_(r$>NRR*{EssSuur].rNc{+Wo_X,⩩"r]-ȮTWLr@*0N>}8ί=KpRWZP&px 8pQ4{(@&-]|~ަ\`Nnn6^#r-*A#wT$e2 3iނF;͞rV-_o`d;)--f67S( DiI1@NNg# @rjj?u7&Լ?fe~b`4`@DNnfOSS?\SǓ^ބ3?LUPTѵoz՝zT3zٓGKII疗#}#;{'g577E] 饠 nYĪ򇷶^.fPq'LN"Bw1II) Xב$3>=]`d WJMMrwF}Dkkk30?{=7d00:nzHHHp\x!bټFu`6R n} [̭jT6q8 г'X`2 ШE9ϑQ x<<!it5(]NBsęeJ7_ mmmV/C~bi݊AVnjj 8|T9pv޴nŏ?ݧ J}*˾+iΝq10z>sأ [66׿sƯ_6&Cq9s8ph WB&XWu%md>aڤqׯ|V/[\T F"n^**2?O[іl6nM\.2Wp8C$|IAB-#?H۴nG75P۱u A>6'#u2bp\.!!-*dp, UI^HIJϬ[#xX9^>[\T(#d]dbo?(feݷg;(  zJKKz~妬|7i/#G`) =bpeE9HI0o^J{aemu (nÖ>hhh,X`@UMH$:tx݆-?E;km;u.ĩ>=!6o t;|rׯ<~篭2t8/. L:cuv)EN S(K ؉s*U06|X}=y?ɣkB_:|\v(((=qpXF)9󓒜9NsĬd.e.r(d$T ~vچ{C\kK!FՒ~AoY Wиu'S rSS[O"xq/.f$¥G]* ƦR28Ʀ~B"QtBDJZشS_&ss-g ^W*Ũa.:jtCm];6FM 0zF?N:@Ki(Wi/]]lmap),NjjOU`iCGHI7zE>Ev}`$|m`2ނ%/Sd?Op/*|& }I_$?{SDc놥 " }Ǎ u8cx@SSス3L #.v|*-406fϞ qm(w^^nPOJKMgr :  "T*`0`0 &`` ifNM3i"IZK[WBΔަL%Jyuՙ3xz 2"Ak6y 5x'`xssӮ:2?WZk:dQcM,d@W/x1%$%OhRZZF?~1쓵Ǭ̪ʊv" %Zh[(55gh` 'O3qϪNPW[rC.P:zkV,/;g W cn]<':]V* Hz,QVVNAQ{E-..%3j# ~?>p7ւEϓ>Ozg;t}à%mF$$"J: n@= 3CPC0B%xiB$ț:>~wmeq(J!"H$p8\C}, D"EQYzyss3݇J*]=bf#C>$&!)Ipc``@{|kE暚ݵ7~"}HIQyE_GJJu{Vb3* s,6 |ʀIiqH~fi@~~ lk n*JISSu~&RS : wtK-ràʊra#:A9 L,KhmRPWT+:j1gLK^b %`|oIZcJ1uEE#m:9]i<}vˆՏ&0" qhL6Γ7׮^NyNQ^xqx˦F}>VV X' kP(D"H$H$2L +.6 U"+A2稪h?x]&+EJ${zӗ"Qay8 !L&aײKŰwo\g2Α7 ix<^* mz~~^cc#F۲qM}ee>޷e*Ԕpݥef}TU警VTT FwC[[w#o43}M>IՒJv[]f;OOfTt aB/6>`0yzX!nj5dfyٿ &=}N_5cH;{G!Cd2r\عa:,a7x<~BIHHnߵrWݴɥ11=yyW߾yM/ݬDmp(*UJ%H+pNFu Ty)H'鈪*oکK**D:MAFBn?Pi<6?Gd|qY!RV amD\t97o\-/|邶Q5u oIo1cVSrz쥕܍o߼AIYuH}$hGO vr2RTG=m92x' y#kSKX' 8WT+G Z[[+9= A4(4 AQ偼<"GG̭z!t.@W%V3o"@ IDAT(EcC;("  D@+) &g_`Wiq~ܹ3gsf9g{Ft;ؚ89=zx]6f! Ι?lg~)jYy*P4hb5k稚N#M,~VxMM[~u%tرF;6^wl r4о#[yir6J#QY%'Qw!FI۷hs8|Dv$ @X{kh2K''*yy<O*++) :}OA-I}}^R(I,X$jkkҖTZϡĬS|~uUe;;IUUʇںڶ|`0,l6r`0t:]mNT:JN<=TD忤T)/0}ldR'M:v㤔lhXE>Ts%|>_Y3wok%s\BŊcNweۮO8L&kԨ+V?x ɋOb;[-өUcz2 N=8k-B\IJLؾus !(+o݌Zۧ-?CYs}fmHĕ6>Y  &I>9KTq.r?H-+-dIR5=O'%In7"@!x| AYIw33t(6e˃yv^zd'zdEw ϟ=i,z Xڳ"8 m@|~Lw?$ ѫj7Jodljdl}#@=kˆtJ6[A}t51= <~0 `0-$8r6Yƪ;dd 2C!b(p Ar[ `0 eAIO2`Px<ʕ+}}}ݲ`0 `.='""ݻ #((e4H}aúK>իWWÂ@`۶m(`0 *f-/^,..G[[ۛ|n0⢚5RiB3lކN-$%%8q]_u\\FFxBBB<<))EVSSkؖ6JcȐ!6m-888$$D^^믿^~} oll:uR;d`L$ PN=x|>_t"O l|!UV5 +e:AINPR͋{lZZZەr[n횟 ?Pk B-$ICJW=`Ћf^:'\UTQ:+$>~H|YPPBhtnSSSAAΥKTTT@߿&O322[QVVÇstt+۷oSSSSSSiJQQ7ovرy'N@aa#TUUݻ\sss;.ǒ9rHٷo_aa:11ٳQQQ'Onu̜97@Yf̘1 R|^|)i7R^^~ɢ8{HfUUՎ*++7ӂ|>}9rDt+ `ذa'OTԩSl٢jp==իW'&&jkk_>&&b)m433;{,455EEE]t NCy;6fa̙n*((5k㓐pH(i#b0LL JG뎤 B#``z&tYBa1Lnj,d]2?== %DQQ ʦ?}<''x999vvv{ <<<22 7h{yyᄒuuu{O ̙3111&Mڵklu\\\E9"""lܸq… EEE+Vxx]4ʕ+R5e@A͛YRROOrqqill455}'/))IMMصk׹s̙3zh3gܿ?%%է,X/xzzJ*YYYJ#s:π}-HoQ8X\\|֭'^pA_|ر{ 4HTJEŪ*++y111h;??߸q_EP[1LP(%cX%`0 s͠t*C0`3T,¦SiTF%|x O|(jIq6mڔ/...͞׿~͛7BCC%xI As :ug9rҥKgϞ,tcЩ!XbǏ`ǎl6{j̘1999+V6mh1Ho#F믿._<~#Gŝ;w՘奥,h\HF%55S`0͠W"##`0D_N"WVV+%4UeTeT9l5!XPSPTUEY=~;v,{/۷m_v-:9Dv%CۥKV\kzyy͓Nٳ%%%rrrT*_Gʖ9s3j2eO|REA655hVgW F}𡤤8iA2%2,pT@EP,L, *QU2w=-SSS80rH;wk׮1v-riRSL}ixxcHHp8={t0ץK1 Ap![^^>66ʕ+ciiﯫۿ &Ο?.}̟?ЕVjG޽{w;֭[fMѢ/$I֖H:JjhhI$Ϗ@@D@VV֝;w^zfffnnnfff" %KK +i\ /3vKnnnuux Eē'Ohjj6 `z,-AAH?3HL`0̗!4^1 `0LK(Mr-`0  Q*:h)pO6J =A 8=D"F N`0GM`hD1NÚ﫭m-]`0 t6tQ"N`0onY> $N:m~|Wo](/U#G{u1,(X{;Z'`0 *b8WzX|$I5 m/oe$VlpU[`<^]cl6utH;Ug|*2e8ו`>OP넟/'~=|*4`~C [YBh} !{5@@~,0KB s)3t*A! $$3_]':t׳2~=(;;M 8. v*~ RB2mSs ץ% f ¦5.35ptrla Y,laA>(++FTTT 33زoݼqMSKkGgסgxOg!}H>{番;%,tTdmMɶ?mXM -Ij$anѷ`0(oj@  E'&l@ Y QX u\ IDATQ/, Jeuj^䱃< 6l@Ȱ{9߼yMRq< I!I2pMRB~޶ш~de劋 7~@SS+Yظ!.Cvzر;wu:`:^ly/(O?<vy_YsOISX?åVCCg;oJIdzZ镶1[KGrҝO} `M@(x@!' 2Bt.Kͼt|5} On;tJ)WH2=|$,x[Bht.@[[ԙ%e.q&/ @Yƌ(y}r>~=' I)ˌzzz~~~Nt@K׈N޽;77r?u:MN+/^hhj}`0aڌfe={Ҵ_6Mם q +'C઻)O[ძm֒#a'?)?|*F=&)$|>_  B}`X](0@e@23TSåե/D"F!AA?}OJw3OOOԙ}C1@OJL@ ,Gׯz u)!Nq]'޽ݩani-HPȴ/b0XF}-;88!hniA&E䍸++++XR PGg2Թ/Y0˕Ӌ3s?UgϘN;o4tM,ؙ]ne%ֻ ꗟ{I)cQbyyYS89MǏ,Yr-u@ 1d໷ŭl'GG<=y9'oL߂ oQYY.7C-)] u8rp>EoJB#̘2.{P4p~ \m-M~hlބ:rA)$Iз|?+ԔV{5gؤG h}~ݹRHsϛ>ڍ W@a}Jf\.˭o .mr\`Z.( f0ԫk t&g;c]@qt$YYQJ2;3~򜾡Iayh4-?2kڄ;q:i rTa8ho#`dj gL-t-8ޑutV,[|73~{(+x[\4mu$ J;z|% 3[qFKʌE\<MMME]r Ο#☮n1;-huUQQɈsQ,[O\Yʜ'o-]~Jp˭Nߺ)wyMMZkTEEy]]?{ܜV3vv𯸻4:;S磻U--.8c0i9A?kWm(*,ήc͸ׯJiBaA~Ц8Ǡ_Pbĉk.xRҲWԿoݼ]mc ( ?~ݺt%>Wa ӣ0$[0LBP}*J٣瑘.ˠS(,U^^cFy<-+:WAcҨt*Crߐ$IєE }!LTD%o;U ڿqi|_$ AJbSSZ 6_}aNNrrr nhd6n97$Xx MkPW015/yq۷nJۦϜcmcgk3cz#<ܾhn-Pxs_/]m_+M[WVVzJJʭ*`2O<7hP kjֶ7KzbD.-e!$~Ul `i*69DSS] ~ $!@4:@Pә$IҨkKW5;o{IJӧ"S-RB^j203$1NrZR={}7 ϟζ5x/IBǽG0m9{f)).CԼ_|HSLLhfLəBx%6fԘ99oPUSpTL8U55pPˢFɭttH8y]4Y`)''[]CsWdZL<}ڤrۯjjj@ mJY؉)3g1rTx-- M-A.%NE7JMIzZ D8HzտbN3oa[nCGٽ ?z0lgQQLP(C{7zpmze'BY7v$ILngO:}22]QQo~AA^{OyI$3^ݺy{4╼{d=:[]#U7@qQa,:>iJE z=}WVVJUI̝ܙS'Eee`0 GA}WDgBPh4ڇߖ#1 BU"âGTTea;B[IP,EǏj@@CCx{Mwώ\rMe.UtMR{O}ipaN(2gk] ̂%QbO Bh ''u-[^ggN4}8{)5¹фQ7l/Yfo ؚV{Us-vo/[K}KcCH ={[:z5wl r4о I[yir6J#QY%'Qw!FI۷hs8|D$ @X{kh2K''*yy<Oc6F4JN45TD i'acdbqSΕ|eesYtE8Nq +t:sm^AdFbjݿxdϮ,su B144ngmFh?̘^uj欹>涥@YY9YY &?lN grX[E@bEV7Y6 ut.rvek`0ZWJ' ߀0gC-C$ڲI]ST5u yyv6|FIAR{`v(P_@A<İ$uVrҝL065flb:(y ;s-#K{}|ٓf)]}_9QX,QP |vڳAS9A(((K4e`L?9~H$Wnش@G Y3bcۯ j3] hMD)bzx`0 `ZBG&Xulzc{{=Aqz<=D`09(`0 `"f`0 f͚~\S&0 `0ڇ - %""ݻ #((e4H}aúK>իWWÂ@`۶m(`0 *4{{/^,..򼶶7JNqQMkiioV"xual6oUzz/LRR҉'?Ugdd'߿,$$ã+]bņ O< [lٷoAW `0 A6~J:6U(>z(::z޼y&!8fhB!HEOuFJJJnn'O._LDXXXWA(**@MMP(;vϟ5$) _x!//uuuB466y4p<N-++kllTSShݲ+Χsii)NWVV<>HT @P\\<}tP-SA߾}_.z Z\Rg4 TBH!IfLN13`{… 333 ( c;BJJ RB9sʕ+-,,ҥK_P(ljj󳰰pvvlaY,l~~><!++.>wUmmmGGǡC~!!!_;(Lρ$G{dĈH5l?Ӗ̝$I^x<~ʔ)3Kw޽۷oXBAE|ݻ#F>C}_x<|p'''I v]ZZ nnn CJeeeiii}.pۙ5kC;w,^X<˗/O<&L0h [7nhhh1cݺSNy{{I\Wə?~II ϷD@ʟUգGO禦qƽ~ÇqqqW: J*hee&veoo1uT%%.*@ ### ѩ<N@D Mҁ٘/@ d*ªFaE^XZ'( Jy0`Ybo#Fx5JEǂR( ޽4:$믿FFF0@NNCV :UV;::ܹ344رc;w477/++stttppXjU޼y^zz?Y@|ٴRQQtFkkw㏏=j{ yyyڵk4Tڒ*OO@6cǎ n#|>Ϟ=366ްayyyW\111cƌy֭!!!7nܘ?JRR'5$I>}>}jȐRDRRҐ!C>|~ѭŋ_ʬ\hћ7o^~MRƉ?t9v-VIHHxUvvvAAA[f OPblllTTԠA|~;FFF޹s\֭[ Auh@  yM<P%B$2xHW"y1@ z{Kw^Z9+y*Cr$IϞ= P(@Nmjj*((ѹt钊JqɓPbFF߾}>?|޽{' I6mz˗/ fll FFFt:}ݹ6muVvv+W W***-@yj'~~~vGnܸ1;;ɓ'hhʕ+VpX:h"ann&A[[ӧO몪3fH)ԨQ(CHHHGv,?|`kkҾw|ӦMۼy[͛}v[fͤITjff-[@\zuʔ)_RƉ?t9y<ތ3 >ᓒ>wƍ233---cbb&MJvhHW&1FAd2 B@(CO, hL(Zf6jj_>ut^(Aav ARnݺy_tD=g$!!]X]:::77sΡ۷ow0AX[[+**R(:88XYYX,eeeeooW;bHrJqEFF'Nx{{9soK.p8Ϟ=C6NNNho>777555{G***ϟ`ooܹӯ_?%%1cªG_ ꊊ9shjjjiiVUUGoܸ6vح[޺u+,,ҥKPVV6ydcii \x3zS]]SIR,!''G[V:4n8)Ś2eŋ333Qo|ϓEp8A:::\?aHܺuO?2sJJĉBBBP"E1m4sss>/;;;ӧ1'=byyy=yBCC݌7۷o777>N$YKKHHEd IDAT8p`QQQw8o߾TssG={lmmOKDMMd8ɞG__Y䘛WTT 3f@Vv떢SRRboo4p@>g4Pi@LBP$!l6rr.zU2zAymCA&Kg"jzCY:U dWHzIeeeϟ?R c޽666 E]]&&&]w~h4-9rd555讛[LL 2jTa,,,=zD,--ecc#N𡅅E]uuu,X0o޼rQznnnuu5Yq(Yll#G@GGg߾}" Ç#ݦhԨQ6mͥhH>;BPw̙n*((5kիW[ѣ>>>i̙'77_uVXlـ$Iղ}}}oYiGxoVnDHHg{O?nݺ[VVy5___rȑ'N߾}{^jN_dIffM֯_̐ ءCi4Z=ǧ`ҥO]]]#&|>}S 755]|ybbdƉ9]vÇ`zzԩSl٢j__߹s'&&Κ5+??111%%%h*++#Ek֬3fLEEEBBrhjjq#{LfԤ^z"}}P kQV0LL JGkc BHLwAe)ʤ{y@V^u/AҩBfy I4MAAA!F~TTTSS@ 9rd ڈk秧"(**VUUiӧOsrrx<]chhjgglndɒڴL8Q4/DQUUU%88rrrnܸ믿 rZ077˗7nV|FR}߼ys{{{lعs;d2l6QAp…"GG+V{NSSSCC#**ã?$xLMMmllh B e~}} MHH@RWTT?yիW"={I~(C}ׯ_777] >.==u2V ?Tu(СCbvڟ~I[[[MM Mя~IIIjjjDDĮ]Ν;7gΜѣGKKBYYFɵӷ|%1N$YRRpϷxGFFFVV` yt:?hjjVbvoo6>E';;hk;ӍФX,KnxY?$Ii)Ǧ*0XtM#>yl2sӧo߾R fϞQZZ*'''%0&&f֭HqB!Cg… >4;mv+77DvvNFNNNٚ:f̘={DGG7>: dwHM[{ٲeѣKBB^J~''''''5k7|#=H_``SLLH˫}9(n=]f_UV ɱ$ѥK;wNKKk7oDh3gܺukddd[HW}HsƍBpΝ͛3f<"6nHZnHAKK+::zɒ%۷o_;;_rnjŋV/\dժU7o,K{-j`0B/bt:N(B$LzU,%Y͢өԱ3e>VRp1~LMM?|,_X@+(UC R@<H AJ>$Ak̘1ooo/iʔ)toѷMXXXDGG,SN:mۦtWsssDW}HsΜ9+Wj{5ЄЕ: "" X.*bGw]PAڕ(E[$@~5_ni{xt3;3{vvvwΞ3g&Mv OOٳgv|=ggSN͚5gmqqq[[[OO 5hhh~E]]''isss dks0*sOI$wH$LJHH rRT@"**%[_NA''W#SI PAIIɉ'8lιs9v豭[Bi@tCCC(;;ǏC%b_^?~<4(ݣ钒x7l3̶@;$hO.]ÙiccC*++JQ԰KvP[+.."x<ӳ);F&yZ49Ʋl[[[---&.._UUU-^L `P(1clݺ5 `޽.\-3dg60;wTUUaP\nUU՝;wLf{y00m>Z^Ws 4&1$&@3o;[v_ ښFIKKKII999ΝKNNNNN}˧RYYY LEEEyyyii)F#d ꥥeee]<Ѓa;[XXHiӦy{{t{{{NUUUidr^ C (ZRRRT55fE_|)))nhhų |}}(ĉ322P6lX~$%%`T^]]]uupEʜxRLL qttܹs'̟LPBBBP=qRRRҶM jjj gΜjjjHKK+))G - 88XDwYXX())?>>>^Df ///`0Ǝr\.4*((l۶ 畺r労իOV\\ ddd JP]z5 ]l٬Y<<<`z@@ L&222˗/wVRRSVV>x ~~~$ؘB={&i_|AQٳg3(jjjz)3p@-'{Pe*'?gΜ=z4o第,%%M6ww#""i4F;zh{E LLLLy޼y\"pWO8`.B񱶶>|0*M͟?j0l[9++N666677"lmme0߾}{2` 'L f0I{J3e!l6Cr 7H/M( ZO1_^޵_X"??bVUU c{AoN6gRE7zAHVVСC%$$AtzRRn4 p~vjYYY',5558Nr))l-..nhh000hj<)--UVVM$hllljjRRR<A]EѬ/jiiM<ݽ[fË177Fpvp8 }2L#))(\`0x ۷o!{G(++400hܜ:N{d'IOyybmDBjsέ n$..zNQ(ݩ^+ުo޼Ah/i(/TZZzSQQJl6J]d@~~ƍ>}@$N> yyymٲeȐ!˗/G=q" ^ٳgo1ٷo@7:㗳~ŗh4#wTsjW^ݹsÇGBai=Dm̋m t:xo1e қ?YYY_kYElXWk100.J!=QUVV9sfwEuWK!Ṱtk`!6"Fl۷Cp5=ȯ:t<)d 2G#b)6X#````y?C p1_ڻsK}}`SU8[OW\]~,?a;cv?l6e䋗aU9yюFKa>Xы?C(H&ŃJzi{ E;*K+e H/#(,Vc kFU5$ݾuWE_El۲.+3?m|{S|k7o9tehH `& '>v:7 !/-jo.ÇȰ3,)1~[F٩m;6B&p (@傋Nŕ٨ljjϮyܽ $/,OA|.]MU--V hhrs3̜t̅yKzGEQ:kIIK HKssUUe}DX,[q^{ OBBRZ@Pe۰H_fq8\@$T] Hq9N%ȕ6Wgaaawy$%&̚=}If&nH鳊 r+8M Hgh72m&D5i9NB*}Jz[K#8!_S#"Q{>'_#;; ȹ*>*!r[rų+r[632dDqQ!@VV99y ٙ Uծڻlva**#lǯ] ))5uMۻ(LE𰧟?TjZZ:vjӤMڑ=(>{CR"l2dTmlkEWج̌QvE }״TۑìD ?}XYY>NLLLDUUJq -pYdt.5}]QA_x9̌B,քY uTtf.s(\1nĩG-wLٯ9߻sAT\TX[[c{;sx9Ei)㇚[%fekਮNT?zp/#ޘ444)޶;~ڶ#jON\7Ѻz# <3v?ַ}W_W8 >y^;~ȭn&洴סU?}}ȵb^]TG]>!O(466t!Ìğvx֖f3s$LJ:q+TWSS{^mR[Sa‚`2Μ8Gc:Cvapљ/ ۰f@>~5?}LTWW 6HzNGOyWdÚ~7WXmƞ[Ҿ|O7|XvVfy3Nx ydge$~JI/8}ȣmvhK'wĽ}v ZŅp8\.I\.łJ/FQIymOqHQI9+3Qo^ܹ_ye+8r8 F,gg>y`eFCmcSlR$b?(}>~(  Z[t\/IXrLL<^^K0M>&}Hz' {AQtνY_ӿ\j(U -H<}t~~{蝕KFJ'ggeTkд:-@оyQo@e*-݃'{@GdZ?,cj<%i&/R_ETUV+(x]311/`%$$ygVYQ\tL[7^kŪ9sbXKw qۡ-x_(t42G"(fs\.g' a4+Woڿ i6ij 7g$rt˖^$HI IЕ꺊?\fK!Q: @cRA IDATǫ?~2*6q"uu44uуYoe+VÍ𰧎V<%z_=&6GADXJ8LKwYL;xСCohE\hdstC܆wN۞ ._cjr_>Ϟ 7jy3W/L7R8}I`+ie3&ƽ}=zYWUb8uu~o&cNU+"C b"'9eҙޯc^߰bc@UU傹.jrVCyuAn_R>'UsۿAB}}&\Hd3=y [3'ލ,#KEvW `0~(z`C }-壇͜>~ 5_|&N<}6^%o 7Q_H{mӇaVp{‚/[XJH//7p/$e#-LҮ^N70y(We ڕKm,_|aKs3m_zVWWs.?fh?XZWPwpI|!zԔi3c'LQ*??bAM߾aݼ~mM0JK}pO d m⧏I)gzԞXo';9<"tՋW/$%&X 1tᬭ*uɂ9gOcc 5oM'OeN=0ޒWp(moBa0 I d0 L5Hdǂnޱt 'FSS cXG{K# Q\ĩjV ƵL%Ji=Eњʴ</&&vzSPPksrr:1ߺyu)ZG}YR\|<ܳsڍ[ps]\EĎr KR<w&Io֍M̍'U4ۿABvV5`pRv:mli)G6q ?]UUran[7zQW.dge!,1k{w@2_;\zK VRYA=yKWdP<yŤprsUnZ3@Xͫ?-u =БSv۳AMM@̀{zG%x Зڻa=4GI yFAp3Qv;͝=r߫L3]y8I"JZ[s39l6Y\TX[#8_x|dLOڒMzn7TsISS#*2g|<VwSmB׹ LL̞EΜ5!sˡY[R\9;ch@dӉ~dᜆow|Sr^N6U=~߁?q#қ1Neai}3[dzMMۑQoqṟ^0 QBÑRRRsph ~H<#y{HA~AFF̘ ?ެflvFzڑC{ysB"-ݯ^螎BӤ3fذa,/Դ3fV3;}|1zHG'M[KK[H$RT9y/1qtA&$r1QKr$&&WTPէY~S^^YY! ^/_VOPOp0c9+*)KHHHd E^^D"F;ީ2#=^PPTsء6~`Bk/pnimm]āFi;1?Cȝ۷oϊ`4TTW$gQ!t񬁖2'myHVk+`ਗL u3D&ێthjj2&x~/[ٰߵk|ݼVYA]#?espe%?M4b$_`pki3f73_Sx<*R1K],K3d!=@/ aK^FXFM]C9ƻmll'4444c?rB466fgg}XظȞ8sd -> "tЌ`KpS{Ǡǽ+x/{"d֥R45 @l{Gtz2}8}|Lm"j!uU~p/ӇWJ\\#Aу{G}<&O6Vĺ bD1yEKק:8n8gY[ 45+Ѥ$ſDOOӇm3fʴS=}TQIꮺWhP]ϩ[oߥHJJAauu),`so](za劕렱G۵},B_mKiy&w]]}EeP&6Y^Fhll7g87t傹."bE8`8)9|N-v%g8ALl瞃˖XnղG&D3d9B6? NBd2L"ľC"` W/d gh0tNev[($GR*QQ!/ٲmu 899Q*.O|?P%O9wќgٹZ'ME^ ǎMvwA%b 3puB++xmg?cӝhaa++ .^yF`eež~o"D"qaO_EĄ45͇Yx7RT(U]]f咢"!q]f%ɇ着lmmiO`Lݸv|2ij,7gbj2ˍᣧ]&*-)"]\x< nΞ> w}NQIy#|u«Lۏٷnp|\-.׃÷@@ ]p8RZFǖKy55U۶ӣ)Z30}Ѳ[4d99D 671/#P ^X% L&ڕ.SO<L&f^SrrpO: Kʧ[cGY,xg,KW }tA IJJ> c.(';s gua7]x,[*k2Ϟ>ZCa1-,M4h[p5Y0uiY,z`@Z&iS'Yt;8s#*Җf&i~W8ڍAՋ ^z͝d lp?B:X| ϟ>r-9jt޼~3m ?T>qP?{쑈1QZjTG{+f^ gw?ٕ(` [\lag2oݾZ?L<}%e%uhN[+Jh4WXFMzHu/$PKzw qZM ]|by^/@̌:v6}-̆8JS\^#$R{sRUU5Uжs=_6}!e1D !*[wvt̢qgAA>`=`@=A?Mu$Ŀu3gPY5T9Pu{T,{sEEq /)#A&z=Mn4 -( Wp8+4ZFF1Mqq ^[[jKJID_Ғ]=?lRE%eDFFSRH "EܜƆWWGiw4ͽ\u Wj )/+UV/l3r$$$Q43 &no/^^VUUg9;;SںJxAde#Зްl6+]dfki ɔF)+-WP٠#(dgJHHS zbυgu5il|ɼ F TI$..zNQ(ݩ8Aުo޼iWAQ4#=-8ȟT(RRҳ7عO}gލ+OJBϷwO5/១t6-+Ku_db6Knu ]6E=巆1c=ERSmko4x @ܻ/)gOwxj#ƿM@%D{c71Ǽ $t!00~-ǏßN;zEΝ=^[]c4hplL{w"C̍V(PYAook<$%%A<)w:Mp ɯA]'9Yqo_ggfuuG x.6?/OQIy4R8Chw (#lU+ ֫/^K[W+'Bu10z~d :Fs}X]DM+9!1~96~"F?m!<*=ǿD;o_/Oѧ`9˅0jZ". k`X[l355]L`````````Zu.px< 3p a10zX11jY~EG`/^xEee%@AAaǏeX 0Xѣm۶'b``````w #eL"Y>zh` FÇ-Z3cWc)dp\.x^\&5'O rƍ<ʕ+ 7.9qċ/.Ywf֨/_HIIXQQvHssseej?yE[bB:lskqA+**Dlω]F(r8!!=e ᔖΞ=WhLp8 _=~rmRRR͛G 5kVAAA||}Ç4K IDATr3zh4)))ܣ폢޿b\\\:,ݻwk֬]ׯ_cccǎ;zhcEVVJĠÇMMM;;;11QЕ-pǙ;wn~._ Լ~N/_?ׯ_Y,֔)Sο+***22d͙3Gk766M:UAߕgΜ)###L&-K.\9 8r}uZΑoii9x`^bzzzll#4 }qwTuu{'Lٞ ?DD{E\\\]]uҤI999---5'=&xQG脨O>UQQ133a/Mt@x bX q8qqqʰlXgIq81 69srСPii鰰7'%%SjjjzSZ%fnu[ȡ7r+9:X`>j A(ߢGpYPxI= k׮Jș3gn߾mnn.))Y\\i&u/=ƍ,--+++N<{'OWVVZZZ:tƍ]?ԩS߯v…*p?~޽R]]lll\^^gϞ>t₂=,, N!ߕvر۶mP(rrr'N...{L2d2b?޵kرcE/((xvvv󑑑K,{O(444txHH͛#%oߎ9d3ݻ]X|444={mRSSᑗ9 [㴃ޑ~ aaa=<<@;/RWWOHH0`G"""K`7^=~ӧOw111EEEكvz L|ippÍxc޾}נ.[[[[[[y/Xf&d2kjjd;<{ m8uTmMmcc#d0`0MMM էNb0mkv>,))YVVׯ_Q=q℆@f33s5]עLlLmWWgop.LRO~ff&1MK =* MII8p ΝKIIa0 #%%GSSAw,WZZڹ[ieeeWdxxxhjjVTT'TSO4iƍ;gPڿ+m˪Uh4Z]]d2ͧN r\& (//am9##˗vNj`aa\]]Ed2OgGQt߾}vvvyllln O>ǏϞ=Yj$rl6r>x*&lܸQNNnΝOO͛7kkkL&GFF ABNBE6hoOTTUTՄ6S=\A}=r4 ۷oGǫkz*))iѢE?'㘘fSKK˲2.44nGGG0YXX :ȈL&d###33C+]@&Q}@In߾ /"?R?^VVV...;;;GGw"K,QUU633_655qvvkjkk]}v7*** .E###&Nի7n?~PYY9c *jhh+\|9sPYYY}=֐m._|b? 99;b P`4i{kk+C Ά-sh'O  8^U.\_h41~ԨQ!!!R 3fPPP7oJ }ō;JjhhqvCC TUUoݺ%)F38::<{qTzzznn.\}}jsΜ9<<;vnobLLEIIн/^IHH׿vٳ={"AAAD".(?mc{M'm+*?t:}ԩrrrfff^&\"ɬ(LfCCCmmmeeeEE߿3vǩ o`0 Cz 2|7onڴt֭]MMMASGzSճ#&X[wkh%Kz)))x<^LLܹs pyiE իW'O 5ѣG666Щ=++Gqㆁ444b677Ĥ$.NWUUݶmҥK-ZTUUKϯ۵}Yxϟ?eʔȈ[lqvv.EEE999Ǐ8nΜ9nrwwm-ŋymښ yk"G4"zEXX؃\"`ZZ̙3:4`.\`337o̝;p=04|'HKK۶m,,"@^ }$:?Ovvvttƍutt͛ SYMM!CʶVUUWWWx}}}kkkPhL&ySrB"c^f֬YCۮeAbbbΜ9i&f'$)xiq12 7h/CcgPVVp-,E=@$"@7_*~=g166l'Na"SBqqq4PF5jԚ5k?j*+3$$$;;TiLbgg7|GGNJ III#ooo8A!eȑ -[~K9'χ]B+pq8ೲVVV:;;={Çp:XCx>/:R}Nj*}ڵ#GE緲̝;wŢ\r۶mVVV=-SPP# [lIIIW\$暘˨|hh_| *//okk cy{{߾}#Фn{^nnnS^^.AxxxM@F^wU֭[˖-ٸqd24,L8:::BE255522 @dΜ9q{q.E$O8M{Cwr'O͙3޽@ 5@EEÇ+V>}ϊ`0|"4b("Dbj@ OPL&H$HD"H$ O xJs2 ߫?] AQzbzH/~ 6lsNXXX/!)IFB!x9%eFvF(mie(*!TsssKKK@@ի[A[n1LA༂5:9s8T<%&x ..u֦&777E?;. o6T齣({ݳYγ{Ůx b P.N~V@A_>:$/l$E$;;;,,ӧ5j&5mݺ>(( TyZS߯={?u{/}[igg0L@@@q^?&&&oԨ)?.\X=kݻ׾}+Wo߾(LK e+WTD8}wQWuĉ߾}[*zգG4-BaʕӢEvuk֬eS^|}}CBBrrr4toӦB<<<"ݻw޽`ŊXwuwwW/L5FC=i ݜܶmۺu3((H%4|͛7{egg7qDPtҟ~iȐ!Fb7hР]v!R Uu֝:u*~aCRTr"E!C.\ܹ*իǏ߷o_G}رcDDڵkSҷ:::7?~ ])xqNZ$ɫWsY)9*˲ظ PA BOP(Ķ"H HS*=Rvvvnٲ+v C ٷoÇm4(-~%`ʕ1 N%K֮][xD)))ƍ1bD"Ւ?== \biiڴI3njs[nFVxww<=Q"VՍҡCd]vUmtjj*~΍5*,,ŋ~ w޴iT b-tyebbرcUFۋèQn޼y!RRR;y[nӦv!zܺuƍ;99]O>MOOhӦ-ɶm 6 0ݻwzzzW_.*|2qVGGݻ#F(_vS/,tT4jh'|}}-,,׵/]QFS asUAV\G6麂diӦ>H$Z9s,Xe˖"H7TPT*gwۻ~~{e2ّ#G8y>99ȑ# |ؼyzիU7_reMP(SHL6{),=RXIC)Lh<65=zǏ u177o׮v/~{R[6mp,pxhcccff6{l믿 =z lbhh*O?w=O>*444 B377oҤŒ%K߷oH$ZD"ٰaNĆػ1 6g~۷oC2dF5kRell۫vuXo{FQJrBH)P(:uTZ4 !}L4puucSN\]]kԨnZuիv튏>^__"deeyxxht56 bZXX srrK-OqJШ+Aeee]|y֭NNN]t2d._ԮRkeeUOL&S*zzz!Z.JMLLIQD$sssCCC_offV[4^lqvkGiT-F_t?2,<|={Y\\\J,Cd2YPPo C!0?$PǏR(<5un޼5i[M|BEaƍ^bߗtujB D=F,{xxUO``[""",,,zT|޽kbbbccCV\sgg_~EGGG Pzq܏ AJ+e 2~@ 877_:PO @ @ZTF@@ztsΡ]6, 5 iX^UFiGloE"ȠNQ }cF:vcA BeqzVWH!B`/U8{C @(?XNQWr;BoYJB 7/^@oq׮^vRJrlݶev% v׭)g'@  +:z⩤آ!3s̥]z +Pd~["Q̘5֮J߻s?)UFٿL QOgUk77kѺ<5?Ϝ[C0uƬY5lS@ ? .UӧfEEHyɓ'W/xIHQYJ&WYh(Mk ~]1->|z g)ڼmwy"eD(J;^eА# B!o_釼y VNJJD8"$+k "KB&QU Nx 8NKi@,ϡJeRBl*xqRtRAp2BTeB,N (#+Pkexp1xzMQIEmsW"JEQ 7a\<ϸQyeDW7011d99bXWG7&FLLL)CC$66_q܍+׮^UnJt vnߢߧ3|0K^<{`koҤYK5_yݣWd.G?{GVFn o=nB߾{VX-Z7iR733왓o_6nڢVzZΝNJJ{{ B-%''';yKgAmچ?޽}311a؈B߾9q0Pԥv꧂n^qZnӫgcd?ܡS7SӒW=׵{J PToMM:tJ;rhs- 55̩oC޸jޱ8WiӣKuA1.<4 W7 yɨ'g6::|6!>ų #Dwj$ 6$mHpns|j~s%%%%E&rB(I̸t񜾞~JJ1c(zݎ;BBBZ5ݦm=~ǿ$wϞ<.T*7_}乚uR Ejjȡ7ëJdĻ 5i^b?(J[;RpοG i#Ч@V5[Xw褕 dUmm_<:x(zq];ܵKm\-?zb&͒zyryǣRS5o Bo_;i[N3sr!{8L'޹m{vO^ؑ&N+.hVCIKM6ilU%C25nVRFPkd3ų&޳o-RR{9tX:|{hg~!o^Dwu=;C2ltZ=obb|I9ç@=Օ[&::Ri󧏟<~+mA͙ 훐7vvv&LװX8 uEEEyxxΙ+wgEF_rhs R\655TL.>2zc*/&:.Y .[2?]xݧxh6d'@$hPbYuh ^h֢u!o^{xV)*v?ӎӢ@/^ڹ}_~ttgE/?g ԥ0C׮P=>##㧯#J}鿏# ߾ի.{}Ca ؗ"GuL(ݳA%ԩ]K( b'W/Ϫ5|}})7Y+WfD"1B zw9v>>rpߠ~1Co ζ&_۳#m^ؠcNm9Tѥ͓%)'nkoYy#_xͦ j8Z)99kIOO۷sϳ5^?NMM?fq%ujx^کm:_f`!=ujx\$'' ֤WF՛6z( +22޶CgV4Xܥ[gO Ž!*ϓlhdLQTJo'XYZKR|ZfY5O{w'/,{iߠv՝=C֩q\Q lRt~x `ϟԮ[l.W/}kKXxݾuG6V:›9qzdDxQNO͟$߽}MNX$ *DrUI6Κ0vXUw[wG=y~Um{9qM=_c*cP6H$RT*T*ʤR!Br9[Bi陧_VԮZ3u4J RS_dF(\(̬C.fN,[j5ؿw>]2 .};rp..\זXc>};+}G`mbYBأWߗ!F6$♙V~!>NKKnaa9s$oUŢ ߒs'4ө)kWJǎ7 mvzxnP#m;tçܴ~];jjOculelbz5硃(fDDKtt_ܤCIGdSG ٺ}Ye ݳ[>sgi!C?op#V]c4ev8tjuןm[]ݰT GJKII)E"gQx 03 hf(*< 4-2Oix"( ]J "0¯D,khhؠq3<ڳEQ>zC{T ,qoC/] JA<[eZjUvmGGLj3 """ѱN:Zrtr-kW/6mP_߱s)f99 C#c061[/:uQջH$[G **"Qc'nA(bsأG*'խFկl·Ϩ6}kN9v/wmvln'H$Ц]؏j}fw;fҹ?}hђETh èFߤ]xSхT*?}1&Z=ñ#u_K~4'55%5%gaT md1~95iB^U]vY5|[j+7m | jX?zfItt6nV-4r`ΝZRb鿏׫_/yꄸ؏_,PVU 5>j\5BZu\Ç0DuFuԾ=;*(s聜e+;9T+Ąxynz+96il>Պ)GIĒk6UƷXzbL;;R:a!wo44425<ۻumvD#F/gd-:b10,2 S_AB%"= c# X ~tgaQY %b@ 4}CBe_gN.\7lQv#^Uc6o\wnI7w2SA :s^xq'^:>y,<DaluG@GpN7wO*@ ֯ƿj׋ln4nݶ-=a\'s*11!!TǷE >sӱSבc&c};7Yk_vZؑ&Oy=iG0u6_8*ȮYY_껧0>D-;3K;{(B"zW/ьmĨ&&>ina9lĘ7?&4> z~cGL6%P)^cЫilAx%'%6j))@h.z{ڤmگ>m0 ~)Bhޢe&&&&={;tpqn\G _9M0jY،ˏ޻p5D.^o1Ni Kk 6:>>u7Zii7`аJ ?!$F >Sbb1vŲ,;SB(PWd'Ч& [vD]Ёk֢{W-[ ֶvj֖aS箕{ʯ!zi?oCW`Yw¬_4o]kkզuZn_R'gWK+W JpW=yFssԉK\doX*t9d@rUBSʛֱem۴6vի,EŰA}||?y#ؚK5M/77 N̬{cY֭lnuWWWjEeef4-y-L*\iiz͙ѧB_֪;nSJy>29)8긺#ꖩY,pqusGEzZ鿏l<ޢU[uu-GGbekgҝ7mckRIc BKF8~)GofV4~S %A.Q-m;veI$P(bH$~B$NoϘHLuL%fƬ1ei)S&&XǴi?EItڵJNMׯx6 ϝW;w#0;7w6:4h3X Q(ΚvMh٪ח}%'%&1 qzԪd\fKGG7)1_.K+o [n^cY \\?z ˏWL E%1!捀 pttnԤ>>^=@*nZoDV9 D_/9{u7KϾ uvq^ЁYPLǎMܰn%4/dgg@Rb1Cn߻cϡą| ^T Z*J+._:pNyK,_S ?੩)38xD,ILǻeHcC.9gckE QѰQ&M7g>wnM]\N}W BVՍҪm؏1ǎȕUpWAr9>HKK=~i3a##ޅ]r"#޿Ӷ}GxU )sÿ[>j/kծw~_vpw-#E]UM>EbE%''EF+NOZ|jٹ-///W&;]`ck׫7AuVu+5kѺWW[oIha6x4##EUG)g׶ V: 5aPa#rXܳgOd IDAT={J$ M9Q@4˲r<6.D$h@Q8AG̚ PFTZ.VF)elƠ+b, 9 (d;ګ[]lڰ{674rc銵&&a.z~xi^?⍌Z_Œ7 EqHy?c&Lʊޅδ۷=v7 ϟ;3eNmw_JƵ4jBa`N?#cMjؙvZP:v^w'^:V|s~kYԭYa߮{zyDر`_~p# l'H|ԪZ<Ϗ=dM20񽻷9"Dxojb J}۸m5:תֶEgʤR'6@mwgm@_HMMYwO+w-_u5nݬ^u/Vnvn۸FAǭ^a:5kTgvpjy{5<1֣RkKlFirzR VuZj!(Q|={Y\\\b1q VKw/ ކ=r Z(}v*+?(-?t(-%çOLL8xȰCGP '>D6{ƕK簳K@ hզkJt~^<?{WѠ7yg֮\tK&b!}\~ߕK+W.[Ƕ_Eh"|J7~vo(&A=mv\ PPfM'Dհv}G!z  w0Yes}}RO"Fi)5G44(*'ܾ ή 6`Pl!)O* P1֣n߾mfffmm-q\JJ ^ zj_ݻwY%|g; _?@ }A>R)>xʜc6YUFiGloE"ȠNQ }ƒ]^,i3a B}$m? (!^;y𹯗СUFV5>R9@ BϿ~zʔ)4MST,[  JeBBQZ:Ӑ@E^^ށ!obPx>LQx~i $rw~yρvԐA.ͱl@  Bŋ/^ի68*Z(J] }}}T^7ݹ00N{9(T[ئU<30%)yAa+ ZA) JZ(<JsB/nؽŃ]|hȡ]KEG @HҫW6lTe1 |/>{,!!ɩy۷oܸܹs`EO:6޼xӇvrXNAɃB HLKu?T*5)˓}Y@ ? r۷vvvby^( K^Sϟ??222 SS|nɒ%!BJBQ"rӱ+3!rss 0"6,,J*jjb@c~?otL8-ͫS2S7N1';-倩zMBV{76pGo5&%òɖMStdD$󾾾4E(Ąľ}6iჇ_n݉'h[l}_zp->~츿ɓ'aӢ_>M,+˿RPnn./  #di!K efS#fe(M-m Ja yʳ׏nbJn} }047Onsm2ޅC*,?֢~fK/e!UjLg_Om0 }ʌ?ܖ2tfJ Ms{&] MP0tgL٩[ftM ƅh(jƌFFF۷oѢž}h ǝ>}!kܸRi+000_~S,˲,+8+/B+X h!KH@ JB4%`i{?9|!u^ÉuZpH$5E?iƝ/`̤谧w&S]Ջ|#HTӒL'?BѤǽj5߮B.K|"`[vznN񜀡m]C]ˇAvt304u%PuҾU7341K +8yHk4 yxMY)_߻ܺ$ 3K&]R&%J?'HRՂ3Д['izۧZ,{c,l%ZᔐZPFYZ,d,#dh =A-lS'@ B5S(2 coo?~-x_0LLLLժUE"Ny&0VVV1cƮ]VXѩS' 3335"0*vxio!+aPx )B}2G]e!ӰnUj5ېm ? yH EQ XefmYsMlc#eYɕ+icX1Gf݇#3k;f `B<.E&7(9)`eV TᷝC\7}qPjEpQ7#QBnBss3hNUB͆-f9sgE,4: /WznJ"r,[X8y 8B%|&'cPOTISР^ Biq8gX^M@((iGb*ɾ,@ Hdeembbk֏3F,ϝ;wҥ666RZj˗/gY/,[J]vM:u#FB!+SNI?[ zEZ:zPO @8p~ܹ}ZXXH$Ϫ*Ǐ݋RZBfffBP==!!L#0RԾ*?Rp:@ArssCCC_ru%x>>&R$1l~63-(:WbSmdr H?'+#>:M$Uz:T$Ě[,@?VfYy0i}uɕ]Β&~V,' !WY!yy(16#v깹@7^iq!@ *0ׯ'''D,,$Tpxu\~!cc͛WT@Tܾ};30Cι<}ό1av{^ǝNXll6!>.1& h4 !DiѪ& `sqҸӹNCv>OS񯘧pA>!yMo: !YZD}{m^B" 9@ex5z<"bnOs^B,IENDB`workbench-1.1.1/src/Resources/HelpFiles/Menus/File_Menu/Save-Manage_Files/Thumbs.db000077500000000000000000001020001255417355300300270ustar00rootroot00000000000000ࡱ> &#  !"$%'()*,-./0123456789:;<=>?@Root EntryP~_"(@256_3a8f3963e32039dc* =256_92049d27154cd203*256_6c9760633af6764a*   !"#$%&'()+,-./012345678YkJFIFC  !"$"$C" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4<1XأnzJFIFC  !"$"$C" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?=85Rx(D ~R852M]jGwYy(sFqZqxkR3ꜞI<{֛l7$ d*:A:iVn+Kq:oh*y*[k*ug߾n8ϥ6]5'8;1+ђ6 t*K>y7U>Qm欁foW|`YxqQ|V:Wv`휜ʋ:c\_B ix~%@Ρ#Xgn3׾;@ز[cs7㯵XiӁpn=֝jutۙdc:y(ҝ$5BY̗C&m >Ÿ?2jk-jV0Kzֺ֚®d sc->Tʝry#ƪ]EkS<'FԘd#,p#7 ;,vZԌV;a-$~c|tW %LJmytf_(\Y,oL횷áZL<#x)ZK-j6 %12z{SOל6LJZڐAH+탏W,4{4t2cؠ r?g[_$HDW,C>-8a5uk6J .k[p0q02+[Ѣ;h߲ݎq:&m]EvtnmfF.w7/c x \5_O5(TAߎ}3<+h/<#5,d"?.mml.9sqX}̶oNT `Q)h<@5w&'.'M9"܉+"0ZiY㸅 \26_g, d/$O#Ѹ5{2C^1͹yU0P/n Nry_V/m5|QBmݓ$$'>zhG fɗ-UHʨ T 0 VYw+g{}wEQKaA844n??MmG,1fhLnaXppFFziڐcKqg{Pf@WѸ5ewT]u986z/4xg"OTlc??44n??Mj .@iQ-$,^Sl8Cmn8>KiimuV+iYs<{B/8RX1`Nt;_OF@W?[|LpPIn2nFdAۇE3VVOOԝ/7Gx+v$6&']igZjGL@q[gGq+ h sG( 7&~?Q{ ͵W\8R`Ge#9j-u+*z'@gw)?.>\:uj%d뵁<zݯW3QBлOZW3jbmb*{miq:YsT~.[D}˩G]Ii\,Lb]9\Kuҵ853!!v78 OUt$?-żDmzFfz'uڳԩrDYi28tOAtW{4}A`vLy8'V79c c\lBH'g/5m^D|5K Z![ya$LY('iV"s]|>CCxM+MI/,PčplbXg/{_?ּ +ȴԣn ӡP|ɎѴt >sNX|Lj2'Y%2Uv'8$G'kL+OC nD$َ۶!s7mf"[£HQɳ#sݳNV f#Llxs޴|Ύlmp#(*]_"h[A:FP[s35sϰiM_"@ޤV`(DP'OƷ~LZ_yj.Wl6n lghNG}hqx =(N0iW$1mgrOen[ iZYb(_|y Y+7^ݿwU$b3g s޴t{d1,n|:d}O\>ru_< q ̫ʪņO, :=lAy; Q<{%I O<ŬG IY$&@*3#;nkNO|@ҊT#(Ec}qԮGCE`#t 0HӔ\V6a!W)~ᶵ=e hG=KchlfIx~[26}Zjq S, : ܶV!NuC=v=/lh,0F@8 !+H̘!PerI mG֭$ݟ!?Jyg>di8x4X6R<ʮnkkk}Nnknkkk}Nnk"\hw)M)7ycGb1N _k:$9Tܝz|ɨGyt'&ȇ~jSZd6QordRc~SMzxDhDqڰ>1F;orb\1d |;!p ŗ1`9\Ώ÷/%5g}+6@`dIZ~uXU] |KD\͞Hn&tZ[5x5")⍘'8Ϩ=7&٫jq:x]u,CY>xq94{ڜXAg$)# >cq銅YNj<;Z@X0Bw=<*ZBuQ/lH<#l !\0ZOmKU[K lY|t)P1q玴{5 5?YlBᣗq8┚[_h)p s6aSf08#[;H'B= Grhgmq2ݦPS2`CSVV[׏$trFPm= $+ ^knU#̯9+YlmCTaݰ&)"M8 p>unV!9'\hvHbX psAt׶<1\7>ǃ\ξVKe|;>3+p >qsju_G?ӭu3&zg5\OkʠW9iaf-#_ nq3홆76Ny9`[Ĩ6#q}~ROt5;y]&u6 *'r?1CjJU$W-Yٖ|+uz9WǖTݲO' RtFgHQgӴ?9N|QA.UKVџ 續CS]<)qn bY_+8\@%ձCD~goCbO%#@ `W;n^F+|~U a\Gv'7J1<ـ ;ۀWU|::j$ғHt/Z26 6 ;nGKQ9ԭ$[|؛8ܯl9Orh09{ج**r8}LiȷЧ͓,o8Ws]>a19xs rG\Pv3hKlZƗrǔ H!HSj8H֣#o$QUQ; _O#H:|#}0 OZl;qoF;)v9'8b;] .H:GuVn9L.A=8ldM?Okm6_]ynfFf! #B[*C./xtͧ>ty=ϵ->,FֺP 6p2Pq<i Jػ*M6,!"ƚJD 8W{I1#V 9;ۑ@?{ڕǀcھfg`n猑Ӟ=v5l4I&T܄lLү?*ϲ}Z\/'~r1?֯E, V+|~T_QpӰeoʁjJjpӱitڒmu-ߘ2pFzMhv#RM9L> ñET@})~'bD^?t>f,z>Gx_y[#8?}?aSx_@kSsp+\cO֧b]n.A g^PLz / o2 ݤʻ ^{s b;m^e>lɰDhU>[IH^-^}\e仉9 3ӌc⛥O oSIJ޺aĄ^:)+R.;M,3Ļqvۍn+6 W rpR$*bHv VleL/\ D$9l`$g,_͢Dh5%+pq&:nSulE|IStW(OMoa${]Շ1ءmjNp={U2/KuE*W̔< ~灝|R|mq/^xߙkGKjzo;HvN@ ;A1|m瑂G?ȓEAգw$ggK7wHK^Fr,FC~Ye?ϥk}q~&" Oa*-lzP:2QoV5L-^(^3tx^$@ $Y2Xw'82-ŵxw^TVx`3%NH+$xx5)+<:gҢԙ[ZE6}~|e[45T·O0F˻ $yݓ~EnLus2TaVsZVڅЎ reӌ~NRۊ} h!Y+J,0:H ۉ8!|ʖzΓgqoyEʌv6㞣t kƗu d.pOod^\Gwt$|oy E,3g8fkD4e}걂 r@q>Cgڛ7EfiWiĚ7i"8@#$1{A*?|Ⱦ}*_Q*?})Eg1{T}/ʏt>fuQkJ]ɴr1|˴n70O~aӁZ&~][}Q[}S>M?J>M?Jw_-{:5赂)ˍv}9?dgG4ٟx2cFOU s-E/T7Ñ R}۳ln8_=}'_z[rIDvR p#$ϭc|1>k6Bu,󟝃Ao36֋c1kacMs'@dz*[* <km6-6Q-vĒscqržnyr_lu)~ Fm7zfOKb CH7RS<2~彛x/Kl!)AOipx/u5}kX#C9!UXFdnErGyVU|6Hdr۠Ctw?pۈNdac''4+~׼Yw2 d,v?yh5lfN_xi6Bt9`s oi|3䌓?6C_b+xٴmS?rqm-[;2|'lXȲ~D>a&(dB$l'<[}S>M?J>M?Jw]ůaU?s\tAcOdX Ã#'4+D:<~#v\*֡r6yl1>A&]RH18XnFO`>bxln?-!z_<2LŽ=ҲWA+\Ҵdmo+/ι=8áy:m/`yR6CNaiY>"G]l \72:|?Ӡ'W48WveEg$N33[[^X_}HAU8s*%ݻR' 2FpOߝKLhwkt  ?ӏӊւ yڌwW9Pd䓚@k -C /߇- h(-Tݦ~\81q鞵s,գ_Rg 59=9sRz r mB i\]vnOo:I,Tp~U9|IPH-y]sAD AR;'U{RSY- 3c  pGq_ҷuVcU `RTv8,d"=l^o߇3PtN8vFw1|ÅE(v]\wv_iW EMkĦ? ]{L\8y 2b<9`vgRZ s&3d/sްTP 据9;A$5ꩦj?eЖ3 ān4ۥơ{L/8s7`ݕiqgw(I=S`0ܤ89Wtֆ ) د1#'5AH}ܱvi;r3e Cr8柽4;1ď]Q|<#ohX[!  NxQsNk#E]=fA^Z)coZmFI>S$8lt,B kjQ.KokG$a`?xc*0:Oyu,H 3ǧ'~mF9 } n%-R;fnm>uW;㎼㜟E47!>*Tӥy63.UC11;sj\X ;vk@I_}QKE[Kuq}qqڠG̑|>-uuSH~qNOx47,7g?%D$0?ڶ줻ᶼk(,UI$.HHf]Gyg7hn" CՅ1{BK#Qwuf>UЅ'svc-*T2ׯJr(ebA7Z ^aq+yhrUUsr~c|vGտ6]zQZ_O=s=A1Կiv`6]zF˯VL_?˯V u}Q?\ɟ`Щm0YFO::sW[EZPb~V_Q=:v}T.^j'ڣ~T}?KꍶT{S.?jityO/ogڬڅu<LsHltr&ν%7?[9s)FRn? k:n@8F2$=AԞ,|-j2j,&$\x< x~/텳KwfKv8ggJH{QK+;K떵CmpAg*ppHIE6\M|p=9MaΖ)#r2 aN-wז7Nj)mNE۸ d9 0rpw^ xSNO!#yꨎ7|ITA@Zh.`L$(3+S>[eݱּKY-uG<!_2a2;& pIv}Wcĭ6_nK$Z3v-yGsm\K{5 J`ώAr?r%{.:{\nL6}čDY^>?wolgDFsxNINqV#eauic+`[t<Q(\5,wQdygj#a#+J PnB >fZ%}2-ᷞV\27~ֹ!TEEW K~^ms9G5+i7>93 1hNG8exqZz#_ŗ`9V^9h-9s09m ͶP.ڝ# -ZGnTUl1ly1 LO-SowhM2p21jlo|N0 >cտ/gCeh0N yr6;Wd HkuyVrne]u=1r JRkI-=h|Q)`K|gO tukɺǯs$qڤr` jϕ&/=Ƀg vvԂ}zB3M*,08sz+-x/%73BudeDm'#StKyY, f1#`'֭wO=&CpTOr~Y& #ވ(M&`q c=MC= xCܧ\1VVX.-U;y[;6`7y?(Tw=?if/qXlw'41r$`c i5;YS-iadhqvy8*yW?s|[cj_A=4F?MymJ.Ѹ|X]Zxh\^^6هZISh.>S8㩭uέ#H/1NHy<拍?BZhLȒr$@RGjb1xG_ Ϸd"7G6~nG}yW?ej^$T]?P+@%>X=qkq y&6ШĨŐ~۰+l«ybcKgWdӣb\ﷷ5ѝ7l#[6D1bs_/I9!ҼmI'#GH#n篨ښ~a9/ V+hZ8U@2@qF Nx_TK-eVTmyUoN|`Mrcjޙ dLM7RDh֬a?-zz|I,<+Ixh&$+bU)NNO5*xbHFw(U-*B9ngCf.Ab>NzS̥K?*bl9NxW}aVWocc'=~Sز5-P<I' wT$$r:K%@:|TP3_ڷ>B[o #.N&QS|*;W?UEwGJ-F~U@?xT]ۮ="OH5ZIJۮ="OH5ZH4}/V,/n?U ?n?Gۮ="Oj(ۮ="OH5ZH4}/V,/n?U ?n?Gۮ="Oj(ۮ="OH5ZH4}/V,/n?U ?n?YoEAp,x=WkHXemITPCK,VWZ}̱G]@6G|dwj KcejWwċ2_rkɿc_wĚޙ$o\>J+̌QV ((*y}xEJFIFC  !"$"$C" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?>xO\ڽܲd s;gj IA$ // c_+OQYz A>gҵo H:"ZZͧ8xXP}OzW]C/i2+5_6F*~Q<*t'8f%i,ͥHm&P R Ph7ҵCQ}6/jVv1IV`K=BЊ*%{U { {o ^fƻwuϻSʨU ;^\OsJFIFC  !"$"$C" }256_4caa88594cd4df44*S256_309715ed637fd6a7**256_f858117befb4dff(+f+!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?Ko_6}"$7D{WxĿ|]Ь]^XxU,a" ^,0l9]m[Ɵ =K2o~ʃ-2u`|8|'zŇDPOZ:iv6[帊1Ai!EzU {7"^wt;?7B.N߻w8Pʀ@ ;QEN+N+JFIFC  !"$"$Cv" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?=5?YLnΣ@E+=:ja0r܈̀Y6dX =N,Q *80<4E~dYpFŇ19:iRnoB kR]Ѧ#,[xUm!~?%X-Sq'@B;W%~u"FܟhQ Ւ[ 1gmn^M;^'hbf"c0'tym>qZgKDV!7a˝'* ةP|Vf\,1}C-w$l=-OG[Izt̳B% xbۑ'G]YٚbKGF qϮib]I8]mr{w뚍Źis|63 >qn;um^x}A.~j@C*pNVwÈAkZGmh;W.ʤ#.:o 0 қi60 Sf_nh`se(}Aq'w7wsA-d\= {`^r`3 Ӈ_Eſ-=QVI"bT"尼s߂'..zsko|fW.iby3o''xx t* w>K<_@ś(<.:(,M B24i&jQ/'ȸ 0Qn3TkE&qY"(Uln14_MѾ_M֗$Hڶo';ToRͬZ$rr)\A_MѾ_M֐&l']Z&2 ܑm jl`9(?||w~+*&n<ĂAmf;"xMA _+>_MѾ_Mׇ.-/;V]@[F1##,qS4@ ?7F ?7]w٧ >?_P#_MѾ_M]iO=cqk N9Z8M'M,|A}Z"^0$kbU %xB#V_ʌAI?ҪiZ 'PHnKWaI:dASBK+,Q0 p[Ϊ*WZ#95gxlkCDԸ7c,);@83ųkGx#+Ϸb#y䁐84xWS-nnl,g W=H?kKjX\^Enn ` c>bQx=#$wZdԥҭ%T[aPF=u/GHu=2[FkY&Ϙ! q!+3]~* S$qƨ^PbN[^m94t77ȑ~'"  Up?&Ua7g>>\r|37 Mن#p^f*/?% ̿g|6A#mmK $מP~=17n;tT]6$b?vs~lTu-@ - '56U @0 pOZ5MI8%E9J`I,1wI{S]E,6=3vgn|@jϟ,3FP bf/Ko~\.vGoЌpp Mqm<14Z_[kHЂ>U$)85 F F۹2{vZ*/lfǖ|m9d#zaq>nrGl%JFs!69?ݝ$S0{S#}.7Y=1N=*ڣkgk6 6c[Gp${y^}Wh?*o/'i$ʴ1 X_bpI9+}W[W:'4Fvj3#vrCaXnܻ}1w=9Ӷz}kvX5$[7w8G53.&߉lC1G N1VmKOQĨ)eÑ2HWԭ0]2H j{ma.G?>6If\hN;Ah Fm),0$q\([hI)Euvy*+UǨqdU}~ !IӃ=@$feF^?0UeURP sTVEa7&ߑ9A<h{? 72JfD$P7dc+ruklRZuǟ1#$[{vn44Ht 8ٸ~6I-;g( <|~ GjtjʏG\پhNdf ~ݨ}AlPמYD4^;TO`{CL~UOJi&5 Xg߻Y㟥[zE53l"y\j?*j.ȑv#t?jڏʛxT;pӱc]k'KLt q88Ǹ񽽪xV̰Ro!6ӟJ睊JzlW Q.!U#Jqm52IP xFi sx_uάMeM4-*K4` u֩ o _Zi2o\L wI kt$m8йkfޘ֓-ɰ`.qǧ&4w㸏YY2}ٰ<oGksfIb|іS8kt;E񅝥vd$ciC[d=nv۴h^Ao\DZg[w@NjoWݖɯ@;).Ӄ-?ڽ߆P-( \[HYTyi;Wd@`%p6]l xw F`z88T{ܨ͛ol#9֌ װV ps c=^#˜P#3Vo۵չ,ׅ}~QRyww}QW 7'jkyC ]/cns#5w??r!XRT񮝥v$E!raԒ[$=9W}D"Ad_Մm\`as,gܫ7˷g]~ ]cő 8#z ~Q0x&^k;»g2E,3xQy/ 7s#sXZ3?f_Up˾>mU|*l޸{$$&Q; G;LdtV#rC/ `>\zf)aohw+c2zp$Я-s'i[\ ̛ppMө_I ynCC.ͻFy^h=GQpxG! cцNq!97%A }߮qZ5=;zO$QaS[.9d!}7Yj3-#DoPp@jԳŌfzk:+yךz[ŜG!`p>N9n5Ŏ{)`[wrg \&wI1o^/c#9dF{~ vpxTZd6K(_b O&5}cs[%^l7Jg&BIJoɌ999hzg7J wh7KNC3ϛefi̠ g|ÃK-.]X }HK;za#8'){V{`ػ ~POhL}㹰PuJcO:QC,GSMr4O4󢫯*ەpÐpEG{8a7In B7,2y=y9i. ݜ,K`BLR p2:|@VHZP",-倠mkao5^ KYu%0V:ʜ+/5{؍E.I\6 ?׎9ķ: ~zϨjic"lp0 VRT`W9fp~R8oj~密'o $[ӟ 9XԨ @Im9*yv E֑F0}-%ڪ&RG+%Ā[>cM Xu9מ'I?w:cy亿LgG,b9ͱ*Wʑle}Bm9Rԣ2e  Ӟ}I'>f6>G7J5W.&\rLJ.|qź?O#russG>gywCV +œ_lFVYADh #sZ7}F?k{>T;R5@[AmĎ vUeIsI?&Fޜ?2BA5>n<¢r7P?'Ns=)-cIG.žX;*Fp0WI>/WZEsq(󘤐 a2Bg:k Nw5[FH@*T 8 O$1۲8KXy'Oł(9H-m-smFX ǏA}V0c -FM stzZ\v?Y[ |A#Oh,p&Hc`3&Wq~o8V nnn? _%q! !fqk^?h6ͯC5)ݼ;sJ8 hIiʸ݌g.8Y3,[myෝIiC>6,dNz.M76n YԱ$9o/Mj_,>c(cLp8\asԚ̲*W/qYmFo?*YY?e?G53c֗W/yuKaUpۇNFǷ89K}*!Lʬmޤ= c7Aov xbh̒Ht.[ { */sbX̄*vq=h~0,+嘼oŁ'{a7mIH9=J/4wz6L$k c`˃dZVc & `;6B ASKa /)(ĂH99vf.|3H@f9cryM]Q7M=)ۄ!n*FpqT[mԲD$Y7u& ?%<ًoI~G7;!0xhcAok 7bdX=?|[ζ;zOmL;On"cf98b3q3[ KɿfA[6˫[!p`9QW RN\e5(uQ1:x9{7ÛΉj&r#MK Iy\uӁEw1.ߘR&5*CIFc[ޏsO]݈I26W_O}nai^workbench-1.1.1/src/Resources/HelpFiles/Menus/File_Menu/Save-Manage_Files/Upload_symbol.png000077500000000000000000000136621255417355300316140ustar00rootroot00000000000000PNG  IHDR27iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:dcc-b``b@1 i'Abddd``0  f=X(3 >F2  Root Entry@i!256_77225835bf959d02*C)+)7ڍKM@+)7ڍKM@JFIFC  !"$"$C" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ? [^KlEP]y‘'$T[ↈEs(d$D\PWTWK%ŧ\:F~e\9}9_Þ o;'H@< $ a>J mt{m_O7Y^G-b\~YZ5_ckd wke=۸kIH[ņHOnD/dQF< tZ[5w-3NGznRQOiᑤfe9#0纑;^Z%)ue 6z'u <Ӹfސ,K#.^2ɆL*ppIxAf{)(y_qaNI'4(G:B%oxš^^LK(i8?)8#2=?:߇%ЮB(cs 2CvETdrB);5< p%+kI  ZA|nIKnEĬ&bۼÆi&K_P9#isX0-O$J:#&5qrKeܥ uX}ܶK$-Ʒg[Aoq=SIhȈo c#[~Oo| :]֝4ZE#DV8 x@J-$#"?:{B,''ėBKk1rbD;rhnzey9biVM3bXNpzgk~FQR Y"bi"jL8j[[V;xPĮ n(I\3sJ zzѸ%sMs2Eq$Gex)UO ''(tHr*HGQ\Z|6,u]vh5dHmێ;ێ:?|6"՚@ôM/$uuKwIVeBr3q";M kY/59,d5H%a3~F<vN^){#HB$j26QzXKVeE*!ˑ 9>063^.L]ť [)EM=I/ݻ&);gU};gk p[aC$)ڃ*+Xe-l Ѓƞɵ;x4~drTy_{g>_z_Z%fB*рc+רoI SV*%x4J 7 $goOMG>[໶+6ѲG:o" .ޤL|·\t{-5kP]YL[1I,6 FEoA<$ӭ|ح w{y mp2?Rk^/_=3 D/@xb{misԱ~c+έ5ψ%3p#I m&E7Yw.O >)A7KE 7F?oҖLߥiJui\XmiiG _H1a ܎sw8Ӭl{K$rE,})$l(CiJ1~z>;,t`/]vWӮ ǔndn3}^_MI^1v,QL)=S{SK@tiJ1~RiJ|]O$ik}o~E լ6eu#qR^:t k]Ee2.]ӑr0CCHQ[̢C~sҸ;m;16Bd<֜Ʋlu³ݿ'5Jn5;]OAiݛd~t$Cצ3'$:2Sp}8 4o/lȄ27,$jGT9'~uj7x[K(T#ѕܳ|I)c9]ž)-zzS]ei<\q zVc&QI=jh%xRhdIbp`{:%>Kzv{a4na1*e,w3rzT*jZ( |]M2S@ o~֒I+刎 Ziz] scw4E!o1FOb; OQϧ+!,(T1*D.& n0$5}"W"gi4q#2OQE~OV-pҴ?rA \]M刣v`x;s$dV]--m兣#o%]0i`~OV;{{KvcᲷ$ڭ2v'p ꋫ0K[m\V ҁ= .}?QG>9E:,M[%+}+~imiayuVmIrq F|s (v1>ݭ[nln"EI b$c.vA_BrᗆdYMOqk-cgYFbK!ۦ6t <7 LOZlǷS#>RfD\^F Rւ#*|7$2p$A:xfПPx;[+4+W0aqr(^_ֿ?z>yww}-ra_ pma89 r[k"TGu\@܎ơ>/$Rx4̊AMI_#ƺzZERSik}o~z u +eH+Ƽ&YYG`'9]p2q^SoSLזk0P/ B=w. $FёBgvGiG, w'vyf=Iwz&ukqk=M ̩4"myiȯ>qns}RPi @fR6[ pO\V5Vmز[sd 6>e9h:,ۻ)p[z z.zdm.0!.Je ";%PiP6_گ.qH$LA@aWeVˀqgTiR[ɠY\}) -u`鐲HF@| ``a+o h-436n@шݕ9>s2o xnhR4['!1Y/lҏZ5Kŋiz])m1 963MNN2xjG)Opy6"Kw?J1zaeiaCgo)T݋3}KO֬Wۙ`D~l,Via_ݝr&s\e~5 "'d&|i]Q1@]ߙWsڔ&/Mc"`ym$*(_e7vqqڒrnAqr⠵k]nd@=15E!jX9.Pv\ w6O}֯"@EQE# sToGAMү#O7aoGqf㪚bh֒PUSIԭ^)$G,$uE]nqڼ^vڎϩ"jqČ6gn"h)uƫ'u5Օ̅ Qm'o<x=+E`6mBojv7$CUX^w2h6wtxL:n'❴%o!,a9Sp}EIE(?^2Q}e,E-yf{w}е_6Vv0GOm,,mX!4-Gcը;-NJn%%Ʊg&q!K]*FL bۆA dkw $H]Vh 9=袊)u4|]M5R?ZJ= LQAּL5Xtk1mqlZuGK?C#8J87[R 1`s;@6⪊]ZM;M~ڙA p U|He*(`JKq6YH~0+o&{$̔Ae w>|I]jUZVf~{y*yejy.v? fj\*^y$y%Hy@b<6Ms\ᤲlkk E[nIG;6nދdRZ%mnvTQE2B((((SL[%+}' ~7+,c-ӬqIP789CLn ._5'|7 .52N^DPȬ 0=5!GŲ\o~L4I'ړk:Ps{[?h/|xfGƱeQ " }=sE%5kT+HTE?k OVs^7Ex/8V8V6AE3VDŽ+i[5G4O3*y;kck]xм"ңmhK'WZ5{}f{T-x%I_4neKE w/|7w/|7w/|7w/||'$h֒PXnV]r1W%>mew[Za)A">v:p R_KiaK "l/8s֏0*'-ln,ը粖͉2HVGl&EԵ]V{2oV!4fEXUO+ ʕopqN>"hVm!@BY±$,qr%Evs$w n:dEsfz6֟c:?lZm:ֳ[–8h< s}wI|izJ̥1pN J=3'}kn4l.@ $gu*|{ v[X )Dac;o6~Я6+ -;>ͽ/!?an1$b\3d{ϨdrWq&O;{N*a+-p,$.bUZBACO\֟<_z1]\:H?FR? ]wg^ݣmcVi\2) ߻5'h+ҬxB> oXYeˊH#JFPZuwME;ƻcU,@su>E((()u4|]M5R?ZJ= %O$#kt\H$h㿸DnjY$./^HҌߝrV?4yT2H>b*~z >FIXIKRd2soMя_چ!z\b#t '!6ixW@3zF ^:q[Xj1~t=­IKv.]i&EF3b*`6~cs[̏, A]]0PT yoMѷ_Nh-#)J]v,$Ԟ:foMѷRmi:6ߝ-i:1~tRcM@ E&oMj=7O7?ZJVI@sʹѩ.mH.`mNIaevH® ҽ+˨X5̶}lUdJзg#=ux5\XQxP'| x?-uKhԣEtf(0$vҺ{Kv&" /ڭ|31;v^X${ xvi!D1_A 20qwsP< >; c~[Hvt,ƛwVxi)Xǃ֌CHZNC=ƛ\gɂߴ0#fRM6H yA]K?ivu^jWX,/8_ 3rrv h4-;X蒈eg+e޸aȭk4V-qJPr0!JƩy=;V!(4`@%>C@ E.QKh4\t;OA \]刣v`~S;xrFEeY|BЯl,h)9U#nߝuϖm(0ł8 %Fpyh\R5HԂg%t $c&г HLu+eo IZeݼ3`U' ~ hxnʰ8H;BO]QI;a8\Z"7EI ǒciKb7\I#OlQKh4nFOworkbench-1.1.1/src/Resources/HelpFiles/Menus/Help_Menu/000077500000000000000000000000001255417355300230655ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Menus/Help_Menu/Bug_Report_box.png000066400000000000000000002505771255417355300265330ustar00rootroot00000000000000PNG  IHDRm,-@ ciCCPICC ProfileHPY_Das@ 20q`Beqր (5 X wEA9&Tnz_ wrDXTa8ۍ=<k7M wwшnqV4.Pq4n*g\8aЗ.B0M,1Op|HgdrH̼B@9DaƩfa?SϸeVxA(_nVjJrH)3FQoE)A ~0,dq ^' D>i }8A,oφF,-cOxY^+y@%0HA "=~M˻et l!Ԙaina?f;{ :]Y @ <"f3<@ЀPZ@ {i + (pA"HbցM `/(ap @38 .: #A8Q!%Hҁ K 9C? EAB:h TCeP%T BP?tƠ'a f0f~p(ë8Uq _o<(@(wT *6 P%*T= ՍQX4@>04].CC7;7C W 18`ؘH ǔ`1.mcvXl6 [=mcI39q\:.ww7}xK>/oƗkg) A@$ل#6 a(K#:CIMRb=D"iIKIR.tt4DH#1 vQr; BѥR)%c)['Q\Ij@4AZG%B:GD q GfLL̠̤,UB6P6UHVNNWS''wX0EբS-#. KӣiIB Z/mB^NZ>\~|9y Eץ)S;O ,xm  )*+(6(VPTJVڥԬHlT9Kr""EEN-U9ң2*RݯzIu\檖Gژ:UY]GsSza54iZ Z{:&յhӮӾCa$yUYwTOQWPZJրilp61L4,7a c퍅Uƃ&dIIɐ)tiK3mh]ff_mS̏?lfВkYnyˊbeѪꕵuA6T%6[m:lيmmb*4fycfG[tS88&;:.[a'M'SęEÅRU˕ZeJbgt3w5wwp_( ,|^3Ves5 _;~d2'b%%< 4@vGAzA~Y]|`u!Ԑ!!BBw> Q!4\y=J9J\lﲑ;Y~uVJ< Tq&qq\w> +oo,)8YSBq(߉?X8.p ^%$Jz|4y:%"!*& ;WZ_d$IV;޻zB'NҖӐ`ӓ]Psfy懬kddfo~ZZڎu6ZZ_cƼ#޹67%ouoDliS||qVǭG/wն۾ ~.]'laNN;\v+-)޽dwƞ=o{ĺ>⾌}RҖw\XvܭBb[]R=TxӏVzW6UVrXHV.n|槿+(B3y pHYs   IDATxw\Ggw,HNQ!#h+jl`DX,QX"5( *(,l- "<8s9q&w?~yy}vbiG=x6V@ i E ` Z%@ >H$J(---2@|d8T*9f4E m8N!B+ R)Ν{\Jd:mp NP(VJbJ0‚l B" ҅u7Ε<)%AЩf\^-HG ~VBhii)~^z9̆ێ@TQ!7}#"{b2T*UFJP(KKKl62c2H$bqX,xNNNT*L&\˹msWzaI6x_@C88NPҕJn;ATH$>*G וd%7n@P.WUUUVVs\;-jjk+++I$N-._;Ѽ#][hyŇG]*dCࠇ[_\t˗#wZuuu׮]9r$ Eϟ?~ ŋ޽{XÇ>#Fxw<`޼yݻw1cD) L&Āa@PT񚡝\-[h4@OAQTZIϯrVSq,8(/ÚT Mr+6ѭIR%mJ%`l /ShdNc Q"RЌCq̘º#lǽss >jH$ҀbccTرc333vdd۷{zzj[ZZ绸߿a7nXlIg}A+?AAy/_   zƣ4=lI!1H,&<P2b{XMBU$R5cF"P(@o:iYG=FR)  dBT̔<2R ;;;"(>>Ν;VVV_}UgΜ9s̈#v% CCC̙C& 8|o&x<^LL 0hhhRRĉO< X|9Jݽ{zq>?f̘ϟTVV .]8?|ܹsm?VVVv[(`pEFFFn۶M$7= ~#GYZZ~ݺu{9~ܹsnvonnP(;vqrѢE[n\oԨQgϞt:~ڴi=qRĥ, LR RJBiPdh`d2  %`RZ9T*J&1L"B*WT 768Ѕ Px֭W]:ɪU:utcǎmݺ511 J++u٩+W߽{_gd&$$X[[|wJR{o)?ۻGGq֭[{3W0lϞ=PիWP:iҤt333\W޷oW_}e>ܳgϒ%K??eggoٲ믿7nH$Rvdff<_ݲeWXk皚Ϗ3_T*rJ+H$8.Ydԩ3g|Eɋ7g^NIiiNe2 d& eDI)# L+^C8KAOz̘LӌJ&d:J#Q dRT*p2c(tB|@bbT*Ϗ|gꬭ֫V!!!999n޽{vvv~Lw޳gώ3`ffzjBT* :0///___7644ܺuk**///!!fƏ 2aTT*ax HDDDDbb"PKi4ZBBa2>>>"ER⚛aͺv~)35bTEVU*ՙ3g(ʔ)SD"QΝL5QgzzXZb0 H$4Lexx=TjKK ?ǣC{;X S L7̨L cd` XnNE.лVX1k,???>߮];@LLiHf(..r82ӧOp`zS [n8޵k׌ۛT*/|}}Not:}_~]hhZ:bS(/T3òϵW+ L&gW\?~<U* 8w* FwHbɋ7g^ۭ xqHK`n1J%dJ1'GwKKKH$H`(U?[g$8޹ ]$RH4'hJ5tfgn;6#ޥ)N:?>!!a˖-vvv|>?11dr\T㸙յ`ʕ*O>9|aڷoYTڷo_uhQ;-D/**޽{uCun;aո\ӧvjkk U5A<\x1ݻ7GnlltRhh(㶶Z\sQ~駳g577azw ryeekzRlll Ç~_|Ch^oBOuwQ)̨fLNIT6ב)5+߉8`0h4͆ۋH$"`( WdaxA]\pHIT8TuM u#d9EcD":u+W-[lٲ~̙\.e…]v%bx񢮮nɾ---}YUUռyqﭬKԩSnzرM6iѥK2 G=zʕ+.]:m4]\\;Q3Bݺu[dɚ5k-[֩S'˗/O.t L 苣!!!uuu6mھ};` h:hЧOJE"&MO?Qԙ3gz0 ۲eˮ]Μ9vI:(v$-,,H"ng5ǒ`Zdҗ ϟ\\p.Q`AXZZX,3R (a4M**naa!&ODgm9,kk B, k_6[1l IG/ Ja۷W*555d2΅ds?~|߾}l6[&D"X FN敕jqL&YK :v133rRfP(vvvd2`ۮ];TDS(ͶdpT*eX0m;wdcffK;;;@ H4`XVVVv/#olhhqd&Y,`a#"">}G >Çk%W {)3`Y`p||D \0yɤh r6#-,,ؖ'5 JA3ur!nh6"0\YPΟ? 62I 6:І8V1&޸O?ԥKuҥ_nwB `8}𖖖l6HPↆpH dteWRdxm$ t*++?O+/Fk:G vAPkjF,ESƂl0l#ơt:] annP(Ŀ =UchOm.6#HRj%⽂nVnZ@ >V0>@ mA 8@ DAq@ (@ mQ@ @Gh;("vPE 8@ DAq@ (@ mQ@ ?Gd2ٛR* ? JmeUMA DT{Gt#i%?իZ999%%%& 8}q^^^``ĉG_MKvӊ =oCU5O\.;vСC?99yȐ! ˗/k޿555|>… 66,??M<ǧO>P~M\P9w\ ӄ &M:kΝ;=3>/ a3g޽M4uРAׯ2dn4 ޝ:u4hL5j?HJJR_|/6dAAA_|0իW}}}2"z}=L>+JCjΈdee3FԩS'N(޽{_rEͨ>ތNLLurMMJJJLL}6W>!AAAkjj\~}ȑn"p̙`ر#..֠fժUχo=<<ճMFlzݡC???u 6,ZH8?B&L())yg}HIIٿVVֱcǒJӧ˖-ݴi/899}999F*((8s ػwoM^^^qqqvvv999K.{쩫;x ݸq#LRӦMu֢EV\)J 0@YfUWW=z4++b0CYYYcc#<D0"_~]v}駺F[n]LLLjjP(ܰa@P<}TR /__|ESSqR i8cƌ܄'Ol߾PYY9zhkk3gܺu ~PVjHyyʕ+SSS?~l2]5ԫk 7nXvիVX5#t%C8 )) CPV:#ҿܻwŸIII}aeeuYf-YDmVͨ>fEEEBB &L5ٓF]z 7oΟ?˗O<EEE3O|||~w_=v1 )NNN}􉍍~nVzf޽/2eʣG?=냂 fIhpp\S9xСC-,,w߿?Euyu+Wr\z8Dbٶ666#GwUUUgΜ#F?3Znmmb PoVz} 99gϞ111l6ɉ18p'A NZb@Pƍ6uԌ 8O(//nvڵZI"""AJJ[-h4zBl0`M gw3jciiu҄NwwʚC9B `u5飙oHbooo߾:11qڵ#G4^0-bėt1V?~|LL̪U7|pG6o II _4݌z^'7!> ]߸qC.QukV%;VkFֶw2d+C>0e]0inݚ" Wԓ)9::ݻ|bi3uT"H4w?*+^) V:88Xc8Jð6Jݻwg0=n%5}:Qo6ސy֤B0ȗ ,Cm! TSSS9[hz)Fh7Ӷ_zo^vW_it:}ƍƍڵ t3 mqrr3%'a{<< wnٲ0t.]EEE-_ܐO<:th?ŋ-4;|Ioc6իWPok)&OPc&ٳgO=z}q3Vi[Y2ܷoߎ;~ooo&XjC#FXn݌3/i ۶m[RR6VjM6o޼y湹رC@`mTR۵k7ٳgF&lzQ/RTRX, + dfJZF ˗2Ikr`YM={P(W^|I"ҫlmm5e Bͽ!U+y{LD&dǥRD"QQoz[gj%$??^F|8$'׳?]LJ7@Ļ󺕕/^'PP@MzN i"a 6k/-Oq,5;Ê^ul?ǏHՏd_M믯^(*cAAm2*ʠAM^H Pu6F9y'o^w?:Ə7o=G2qt/g0| !y%-[ !Lx5ɱp!kW3W׮QMcWC{5\9>Ke~k}M/7=KU]E)!X\jBB'/oHaa(\ٲnZ9J"ED/ZrL&v6(Q0|CB55]]M:z)+4{6[mxfodd2oO<+new/}b)SdotwW/ ϚŶ"͚%]̐D-}fr/`55aޘt>lHm//U\i\T5J3n&M3WjsG {7(k2WŵX SRӲkJJbJUը0tpD#""gPWWQ@.GNHInũ4W]᯿2-_.Q)5gf6飌eU1C6@T<6Ťh~5k$ݻE lm : %C5W9m̙W*"B㽶)y\.徖mŊqd*8kHBBݺ:v3gJy UTUUZ^Z̨إW11ROekGDJ׫6  Jݣhkf$* cGw`СrkqpЫF Kd#wW*KyŐjMthD"LLNꍾ~YTSC]TDf2AZU{R$鶶0B}L"J֮efe"_CdƖ}ҋdooO*/\ҐFlWw aH0|B*tJ[??%40A p59|L.Ȑ͛,)!qDݖ3j9vu56!5dw߽61BҊ 2oZ/ȑ4 vUVJ}bUhOD-poeLM|^+*p8Į]^$qzm0g 5ѥ θмرΝ_K^Yښܡxy2dCgKn&E=Mc2%I(Iƍ& -*P/?3_fƵtܾM YY4x嵏ڲ+QE&~kԌz%BF~ ^k Tc>PVFɀ4˙.-jף+QdF-]`jz%Ր@̈&}IIjWDqPTDֽ%5''J4+QSշMB-3MN=uRXN'Ft:qd8v׮ d1,iIxrt?!pQ(Ď-ˣhn8u'9eM I ufpg2[(ؼy7zD~>`,xx~]QOq%olx|uh='a+HLWJ2(W( 9c\\Tʴ4X54`ݕ;aYY͛cbUt;w$kChJ% 1d xx $ QJ0n b@f5kII h%CjxjR]1;VFW3L.$1sd%lȶl>LvB^eojEJ {R`a}S5qq,~"L{ҭA:z}SxQɺ_GG'NUV|}9.P1 ̟߲zYP_8ݻs lW7'/]z$b%%=_{; afF̝+3szPzL&\\,1̌HOI>Mḻ[mĴ+,9}gcXFDEbccǚp<=mc[^V45aÆt:W➞*&ޫfu3f^}5!BC-8۶1l6 t!!ԧL "+QKշMCB@hjtJpWV Y{o^;i%شg@| iʹ 6뷴`61f̐"5rrhEEd2r]P/WR_>{Fr> C )@Q@ @VGOdk*?# ,,Pcǎ|O>ѣLqFaa:χ2 'O~ P(M{H$m1]iuܹK^t믿x'O4ŋoT̙39cǎzv옅@T>}T.CA7nXvիVX'7o=y򤥥ESe#G0;߿?++رcIIIj˖-ݴiSZ۷/>>~ժUw 1RɓK,_h޽;k֬%K^z 4P_hs;+++r;f̘Ç/"55UTD Xٳg>T_?~ٳg͞=̙3zC^FM{9bX۷o񱰰8x uuuիׅ LoBtؑUo4mϞ=aaaR{ӧ'p* ؽ{w +W rjj!֭ۗ[7xŋCCC kkkY,Ng0t:H_illܸ`0.TWW aÆݼyʕ+<_֭[ׯ_?}t݂Qcg}:uԣG///3WGUwʜLɓ0h~^***,XJQ_~Y`LLŋt@XYY/ 0a ܹspjyyX,.((ySvڙՋ/"##1JڵzاW[-lmm'˧L2r+Wۚ͛751MY&4fĉ ,9sԩS˔@۷oԥK3fކZ@ ' 4 G?8\TRRu߿ݺuz%:::~MR^Y:t8|pnniӜ Sz NOKKrcǎ7NNN~ȸVڵS?xQQСCMQ[駟9::-ZobJ_tMs`Μ9Zy ]v]tٳ fBq@h{f4XАڽ{Ν;\]]o߾-8sNݲp ǫtIBرc%͛"%\,k׮)N:ḩ_Z6lXVV͛ O<>euk׮-Qj5f̘s8~D8^__!GGGggg׿7DVy}6smMrrr h|[*zΝ;ٚ)6?..n…w: IDATt:ѣG۷oo~ǃ+=ߒ rrrަ6L^zww˗/3*Ŕ78s .,_-x\|ĉAJ{ٳWVVxokzwJڲeݻsss,X~h?~~LoƀbÇo`,fl~GyxxP(Kpܹ\"HumfzmPL4dȐe˖F<|Tnܸ1c t\t={Z}DDDDDDoyժU[hbbW N:oY999W~) 0r.]f͚2D=: ֛7o~' XYYݹsG7]D>j]ZxQ77"xHtJbbȑ#aX,vww?x譊 ~k׮~~~[nUtH/^8rATTK޽\bDÛ7o3WMhiiׯgLLzR(z{{wiРABaTTۜ9sZZZtE 6Dخ &]WVVjL>'Jiщ6l6l &L80i$}׮]\RRЧOӧOk֜ݷo.]\#"!!}͚5ow&Mruu4hסC"## .]JKK'O4T'ܹz{{߿O?uww_r%̣g sNTD^<{lܸqO>'Pj_z_|H$kLA~zFĸbP9w\X)S222` N<_vmڴi0ޮ1/_^SS.\N׵^_d֬Yf8p`||<` `U;vxÇWuDk׮L8ۆIq4+++000 `ZMPWWך^z]p9ۛNKT'O]plmmDoU{}tsuuq oooq|}} mIDEբ?}UɌ$Bo}MbbpPO@EE/EEEǏ1bӧOϟ?oaaaff֫W/uq\' `ShOJKϞ= ϟrqq!c^&Æ 322yfZZU/9uO{ jw!e}tE#?H/kkk믿ʟ򨨨C8p 11e˖͞=;66vܹGM.iejkIJlmm mllfΜ[n=tPAA!" qㆶ][n999A[RRҽ{wM%jB< [dcczaVQQA}eAhۦ#V{ҝkrihh޽ڊCx@ݙLfPPЩSN8p‡~WGG}TZZ ߽Z[[|ꢽᨮM֤u˗)K4LVZZ* y03)uM0mp v+x<^]]݅ Y۷o766ZZZ[0mryeT ܴi|rikkPk|D"8p`Ϟ=_ 6ŋ666~G{c ;3fLSSÇ ())9{El6Z5W7Bu4 h۷oQ˗/!hО;KKK77ݻw~֭[ BH(@g:|TѹjOzhhhuuuFF@ HKKS1bYTT(Jgffbp[ZZogg ۻwoݭ|}}_xqҏ:_H$+&hb7W[[KfԤʳdɒƙ3gΠĉO>ٳeee2mmmFqh/ԩSui"C{ jw8::666޻w/..F*[~fwvvvwwwaÆ 8088xРA8o3233흕?M.&OlddBA) 5`xiaaA.#Tm躺:KN6p-bRRiHH5siǎK.uuu 1bDll$͝;w͚5. Wٳ y<^^^ܹs5OǼcbbrTTTBB„ }vڴnҥǏwrr&*~iuu59HN&P1e\a'n֬Yqqq7nPfnn}18k׮~ׯ299 :yĈoii٧O077v9]\\{͛7 iOZy{{\R5$/^Q]]]A\\СC\]]W\DNR/x5D"y5u95bxJ>b|>`yh G3N544*,a_Q B>/?RXXH{M8q6'^9ϧ)Qg1tE D'BQ꿊>{'P {A Ϙ1_6l@N=";}@ ?C#u>K=yBrkZ#-/Q% yߤ=(oOPٹ~ّ#G.^H~0 t^~jsÕ)"z?dE^m ѾVɓ'cƌILLr)9kj~Ev-P)^ %//Oٖ*xKsݼy֭[g͚hѢ[n|ĉYYY:$LNN&!FT5DZ!Lv`ooOnEK!GƧPBWN2166V$-X)22d&/qG+ j)SVO×E\UUagg7n8RX@B˗/INAB- -PPP@-Z^RR… \B+$ eHV?R*A իWϝ;W^\*OH+ WPP0tPwwwy%1c;;;] \S5UԖ +Vڵήl۶m?CYY٪UmFjZ#(ӦMsss4he#>999| 'N|MT-o___>"իӟ>}#fϞMFPV(hU߅QG aSYYjժr/.Z 9VY`Att/ܻw'N}ڵkTFkc---O>`cc_~庻0 Y,Vttk׾UV  >TJJ?OIbٳgϟ///dt???Xcr JJ͖VhU܅Ə*!*”bI$lLֳgO5؃[.,,_~=z􈈈={60`ܿ_ZMV| رcU}iV`B:E cǵ#JJJn޼ o ϟ?'Tcll<䎎*SjkkxHjFΛ7/??_~JjOTgQЏ >|QQԏԪJBppT>*Oߠeb19\lnnX,CUU裏$am۶d2//+W B@iii^*++% )J֊|)5xӆѾ𥟶E|H]Qh>(**jkkռECeX;vFISRRCBBΝ;+ ZI*Ofz.RW7AQy5DM0Y,VDD4r=P-IS&ȧ9y$9rdܸqPVX"33sݺu)))(EЌ ETGjpByAiZyBd 'Lp֬YS__/hg)*j۫W2GG2ʕ+[]] #(PWW(rrrcd2'N[OONC{i_~}ٲeuuup|ȑ={[[[]v!>>ʢVY˖-*|u#QG5|?v%Kr@ۤj)SQOxxx_,Y*Sܸqc>}v%cccB3ThTH-B?RE?811q֬Y0VP+H{>FӦMK;v Wᆅ555>J/>sOEzP(ܱcǵkOhuhO&Buuu *tT 9:ڢBnVO 2;ZxꕙP(lnnV0T9g>fd}}saah N9@,?5 {2dرcTj``@DCCC'j^ښrVCPhM ^7͛7zŋmHQrY$&&#Bzem\7dԨQ|>d ӧ|/L1W\yرcu^[?Κ56 iE.۫W/m\_rÇ&&&yJ^AQ@ H@ A~@ A~@ A~@ A~@ A~@ A~@ A~@ A~@ A~@ A~@ A~@ A~@ A~@ A~@ A~@ A~@ yh}}ׯCd2D""$L& Zbn ;ݰo% 02Aﲁo PXUUw. =Z"tb1ByQG?w޽{fdd544tz%D"ф pͽ{ ) IDATRSS;3 ֭ÇuH}tUY}ŋ5ۻwoʨÇ{$##p޽lۗ3|6o˻>IIIՐTN`qgFK[/l7o2qDw閹PilĴMx$mnviG]$&5HݰaΝ;]/̛7_~/l ߿Ңy% ʮ_~++7655i\ϟ?iao5kErZjgϮ^ٰaî]Ο?|r//5,=...((Oyyyk֬}kݾDŽѢEի4ρ$?5lH>D,.17on!7LM7_G!9Y7B5HǟÇo޼ޞ0x>} B@DD`ƌ>>>6<)))7n𑣶V!Z}}}TTߥK``KKK\\Clܸ… 'ww#FÐQF999EDD@ei!|>?!!յgϞL2e (+VK;;qUVVjRe-:|pdd$FIׯO6mРAAǖ-[ iOkCCԩSرc* qgΜIAY** sO"Sii|ӧO'M6lyҺRlĉi+F$o߾|/^,^zC$'so3/{1^`F s||YX;˖me|;1ٳ7{\a'50߹&@-6 )pis3v:s4C77Aw8_c>>O0##ڒ#'X~j3'3z'F&o۫p__c>=ի9O>>D̞mHFر`loo:s!ժ'R_EE:8_"Mz..&&[p %%KX~@RR~~ƶ&EELjTCGG` Bڟ4[BGIR  }3gΜqē'O233Yiw>)%%%,,Zĸt>?hRe-jllr 򤥥mذa7o ?~<|`X׮]WZ%OIIٺu+W GXPxܹ?zZ󖖖+WVVVZ*==/*@ pܵk*xIFFFHH|֭[W*ӧeQ/ɾ}.\k׮yyy55zkִ-\ؾb^K ַtBA׮oiJ""DyyMb@UO\YvvA4ս/dLGGKbYÇqJOPs7lг:d`@X :ZxZ_Z'`<~+*b*%0P\XȺuĔfGE/:zK7VX[bYyVy9#7 @z:O"c3?_Uz%%6U'$&ԔuuL`R}?'%uk+ xeZC" P}%KO>x:7Y 9Ӡ;u)3m|(Q0[B?ڥKD"H###---MMM]]]c)@G===}}}eaaaaa+~-<<Çuuuѣ˗/c``p\dz׭[׻wﰰ[nQFr8___XCڴ5kVXXvvv֤ 0ӧOWVVуfkwtt|Ⅿ5iΊ+&MV XXXl۶mާNRQ}s83fnnݺ~#""b<oydj'm,K"dggd2rXUTT222;eCCC999 RV`0&&&333x 343gʺ7knn^L&q ###@&~y@"+)a0Ȉq`aA\‚ E}+VXq1`i)'A'Gis|h\ǁ$$DVCsQbjJ &ڿx/(`N8dӧYx26e&14Tt p8+.Np2S(/3v%cHX, `l SKO ^^҇uu89!! 1K K@OOI\АO0 XqqOja!8fw_kkW]f_={fkkpRsrrJKKܨ5Jyfx{*//6mZmmɓ}}}7nfl6ҐasXAjm۶͛7+..nɒ%i޼y0ѩqtt$#_=3ggg򑂖)ShRi0qhUZ+}'&Lؽ{ܹsis&_xaKϟ?,ťPQQ|a={Fb>}F'jee`׏5J!T.Y$66_YebbbTYʺ7N^.I9WX,->`lfFXXndz{|@s+5iR}OYnVVVQ/_>G_}жeda\խii#)mk7$.NdI;F0_J$)ŋ"V^kUVV ׷ ee%y3zJ//Iy9ni){1aӜ dLJm-P@ymkִ*hGbqff&DٺuC  Ukkk_0f-[6{عs9rDˣ:tDM*s fo feeFGG̜9ښ`lꓤ6( JJJwkk;p@% HYiڴizŸq^ ZJ"jHِdgEEEmmm0DW^UTTpK6668?yVP@xxxqq֭[g̘q]e߽{e˖) 8PE(O<13330xcM%C0ECV_wN!dE.^޸R_F5 =䄴0,66p^K\|dy +pwZ ۷o%K^xaccpl6ښ\SGG[x<^``M"6]۷---D`BWWWwrIYZ~wǏe2f:::޸qC(ȑ#ƍc0TYY@O>ٳeee2mmmQ$`+BCC߿___?|vO{Z---v[n} 2-bEDD0 HNJ]v]tiBur&dmm˗/5/Ǔ/_۷EEFL*qxon俔8q=l667pGGHrrͿe2ĉ[_OeD)>uep@%[7r,޳cdDEkkq7 TW  DD UIMlx@M\U[҂BCEut2{qqo/PM=uelX}=AA+33饤puEF~t˖-{p\]WWtiӦ|x^/_NF0aBBB±c tZEg6m@ ϔ߿X,&m '..nС+WLJJ/NLLݻ[F_SS3yd##@??r~Ti]tǝavi }?v%K(@J>B~~T*;v,O@k֌9s(DVvIz{{[&1^Rj?r8 +/==G jn ,^ܞ׻[QD55DF iޘ%IH0pv64 Mg%Wᆅ#D@qk+6z^_>di~4#]7;˒%m662Ue:ǣpRR420>L,22"CB^NNQQF˗|jha| >nڧd.X bczzn$ÙSSSxmmmfff*@kkk[[[׮]Dk"#yjkk---UMLLpjӾz qi)J[ZZ_×'ʎ:CCC5يD(bCCÆ.9>O8*yyy3f̸8Җ-[sss xꕙP(lnnk-U֢BZ # |¨@ JA444U377}YVD+ڵK,ƪ.BYPB+V=z4mLedCCq Z0CC@▖2cK/n:޽+22.lycpp "`R)00 44 |ߚ܌qP]na!Sb VUFk+ֆuFpssB1372?D$|>ޭ1̷2B(իWXYYvY+$ɀ_ H?nG3f_]Bg5\G:LE2//`DDD`*u?--%#B=dE+OqKKر"7NX!ȏ";n@ ;ѿ\TS* b5IeHROmVګ!#=2m9DU#v$s.ZjKe,H.u4=sYhQ39yVjr/}u^ՁIv?66w}/^~繈MNNR^^=h R *n={*'8pɓ'Gvvv^j՟楑¢7nܠ :::Μ9&,^X^n6<Xj)ക&*TSѼ@7UCI IDAT2Uyd3220 [pÇ{n$Z8%% )J* ĆWܹsԜ#4zDQg7iUAf,6?gJ5ww\T ֯a[YJKpOqWW=hBޠAo|RSHu{ /%ɩS.^]TT'UGV <PU3;ZJyI8M|ʕŧOV#? iPjJ ݻwWJ5ʫE*Z (hU7U2U*俺u떓C|\|y޼y۷oDD]XY;K_*6lcci&jdu5St2;#GƉƌ55a 0Ξe'x8Y^OSؿKKKccczzzԟQ+W)OhhhuuuFF@ HKKcSgϞtME4 m+566J}+S-\PQ&hXyIRML{UY1c455>| gR˧OxcǪhNLTe,}epTTTVV˗ d 9+ C4򣚈BUSr)-exxo"ckִ)$ݱeRWW #D@tsp0]T4Ll,A\Ly߯gj* h:~X _/rv+W(رڵk&5551ڪ EI%:::>>>>>8ڵ_~LNNUcgg7k֬8{{7n~Q`Æ "hرB{---V@- ؾ}{JJ TU-\PDMVs[ 177װ(h7M< NNN;vXtkPPЈ#6<{?#I;FYu'Q&e*w2Ul8***!!a„ nnn}پ};D*n2 k.GVЩcΦvvf`abh2z jhLM x-47c^+ ǁ1_^L׮m:f9Ѥ"|Kg0TANA[f è*ٹNC[Qhnjxb8gAZJkVMVC[477s8ycQ&:K+nJ UYb jjj,,,TL@NLTw2ذT*֭| 9#H؏JC`2eJdddE*D;bN$DQΨ}2۱Κ%P&nR9:JO*U]Aa``h"rEnDFF…Lj$IG:@ (@ (@ (@ (@ (@ (@ (@ (@ (@ (@ (@ (@ (@ (@ F~7@ =$**Ju5~w#F WrYXlbb"$ ab1L T* j3TG'`0>#kkkbq[[@ q%2Q@ ހ`8N:N}}6&8OR)$CG%a qeL&0%FjU<,R#!Z[[[[[ OjzE AP$׷lH$bXX,3D~@ 8Baee{^xpx<%pU?JĖ-[bիϟҥ ޽{CMp;;;1544dggGFF8OKKˁv:n82H?;88@=zt̘1VVVd`NNN]]l;wNra'Rξ{UPP?5;w\r```0 0 ___M %S<>"w^dd$mhIPՒ/@ d2T(ū-\n,l0~UeVZXZZԫW/ Gwu [nۧO?|GEEEG9uٳg111/_$ٳ'33cbb&MܹS$?SLL̶m׮]e˖ӧOϜ9ܼеkׯjƍd5kdee)snnnGoll=ztBBv͛7_jLN8`M6 2dd;vС* 9?x3gE,YZݹs(D^reLL̍7h@ ?~,nk&l3SKSCgN殃T O:#PIZfee]topܪrW%׬Y3z;wp8v0`ݻ/ְDCC9s{555*D"IMMurrJMM]|9|1bݺuOO-[wmҥk׮֭[IIɖ-[i&L2z褤$կ_???)SÇwjٵk={v1w\ґ7}9rHDD&X3))/XjrSRRN:UWWgii hjjW x}åavaz =61sodئ}L42lw11+M2e~Qm133x9sΝ;rȆMuttܿ?n:BEEʼn'f͚={#GlڴjڴiǏ''N8hР 6l۶Mz%SX!&HtE-ZW̚ G/zzz0|...Ν &=ztΝ,(~OoԩSʞ|X@޽{!18l00&`,6g 0ـ2Ln4Cmk#ƌxI=D"pB߾}߿mm-իKpppW6K,B~~@ bd%%%.]6dȐhX =z~K&LLL,,,\~Ve\.Ұж_N+ LcCCCww@  ! 0@@` &0-ڏ.Zh֭ph.xqrr֭s)СC}x  ߽{wEEҥK v֭NNN*FaΝ}`N]mߚFEEiCKU&j߳gϯI^^^6lXvagp9R4@y=z>|@ $8Lp c0$+Lb"yo蟷l"+$Bpȑiii c2GGGr D&UTT~oP(|aݻv.@ YVV\c16pۨc/XE#_~)gϞ/7h'E s+W<|]jj*8qa8p@` `q]@ Tap8`2p1 p0h ?@ ?#  &b0p`1`E Ie@"I7%JX:@~@ 2L*!T" KݩƠH8S5M*#/GE AH B&%@`T&2$V и.@ >80HKe@*Rd@&RY!?@ 2B,!d2X"KTJH@$"'GY!aG%2 9QG'G?@  Q@ :E Bw(@ :GH@ A@E  Q@ t";H@ A@t?@  Q@ t"(@ GЙNe\ -[ǿ|ŋ{?~9޽{C&lii9pqv C>}zԩYfas_]& 0at@  ?r'Nx<'''ξ{UPP?m3srr</00f+C|}}` afffx;w\rХKeՀVfff{r0ڵkǏ'ٳgLf~ӧOZXX8:::;;5tuܸq!#x p_9pEAʡ zRŃU_QzlTD@iCVmKnPAE@!//Dj,''̬2cQ BU@7RTuikk#VXqqkk7o?y!dkk}r/^!S߿!B/_RQQa0111?F=x`ɒ%dիW-&g ._q <؈˳|ūWWXA}͊~zhh(#SӧOYYYVVV)))_;v$,ro;wn„ &&&xQQQoXFFFDDH͛7GDDG dFP)*EFE8(jիKNNvww˄ƍtaa5)[WW*,,$S.\xA,]̬ /3fԩS (..FQ]]MR xї۷o#Ȕn&A<|BBE^^^6mBZ>>dzmmmFFBh߹s.]TVV6}̩r?}Iӌ>[p޺u-mFyzzjjjdhll(,,x񢊊 Nwwwp8ֿ K IDAT !êO?&a+++***o5G\ BѣG۷o~PvZŋB֭[,D^4a„𜜜V[[[77Çgee988H",,,l/466jkkQ_|r%8#qŊVVVFFFd",**~ׯ +GDMMMPPBHAA /"sssÑ8==G!a&imm-|>33+ ÇH۶m[}}6l kFiggwI 2w\d "eTL&JVUUFoooV$"*++ez*fmmmll%d2|󍻻;Bquumoo߹sL)Ǐ7nY0%%A9rd޽o}kii!DBT*U]]}Ν`ٲe5偁!R򲵳!ryPUUaX~sJUUuҤI_x8X$&`gפyǏ/X@OoQUUՉ'VTT/RWWϯYYYm۶4_aa͛׭[_KEyyy]~cccss۷oG===nԄSvؑ}v Ə|Edv'N>}Zf̜93<<!ߚݿ?rwﶶ644Dm޼mϟ?KKK\"_El߾]YYw1`!x<^ii7[ipppUUûoތ?G@dQޟ=hddd||3fo[ ?`Q@~0( ("*"BP(T @ 4*(A!T* P ѐ. _5(?`8 3BcQ>A"2? u|pތ?}Q>8o%Ag? G|xD^!{y^G^핡&>88(Q<(O@!4(G)`Q@n0(`Q@~0( 7x0( 7? ? GwG %"..n%%%YYY#F8qB[ԓ'O/^LRByyy/K+**ݓNkkk(f---?#<[XXᅨeĈlgg[+ojj|rPPY055B5!TTTTPPRSS377wqqP(d"?uԢ_5$$Ȕ#x<^VVֵk׌ǎ)Ñ ?P)T3(EG]n6BF͵okkCn߾\F BCC+++l\\\nn/"""jjjRߏ.σϟF\|9""ƍxQTw^qqqYYYVVV)))_yyyv?~|HHN\zuEEE8qѢEd?]ԩS7od2KJJ; BP2_?>77w֭+VEegg_p_nnٲ%00N =j(PNNJ˳B>|X:tƦ$??!4k֬Yf!p(ݹsۅxG9rƍB۶mCy{{߿ԩ!}}G"_!(R3F |-KLOO  <(!>ݵkWxxHb}}gƎgDyyyegg#:;;=z痟nnnvqqyڻN<occ k%r g }:thiix"˭f___ teee ߱d3pv=ё̰ .ܸq?{nee>}t^^^Gxo3fcB׮]-%%x5={VOO;,,lͻwАi$T~СE999999%$$dٳgNlooFq֭ׯ_'%%E!EE#G;611144,kbb'Ӷ:hCCB߉^|ؘ+2ҲNgWWם;wrrr<<nOttŋ]6rȼ_AAAȑ#srr֯_?zh%%7ofggő֮]쌧/^>|xm333\`$$$,>gPgggooǿ+qŊVVV26ld2ɇzzzM6|SL7n}||e"2cQDTG2?*(ɤRUUU"~~~ׯ_'S*++kJOOw N3fMMML[~!www[[[[[Eݽ{7^dCBBpNNN&M.dii!HUULJbQ{ՈN'&&^|ٳ"tttݝ@7R'N7nBJ>}4--MQeeeW^^^;wtqqQUUEرcժUo]59qDRRҤIp AONLLܽ{L[!ڵk'&&FI鏏A3977+bŊ]vKfWWWEE7nh4\>FFF[+Wsqq3f̬Y6n܈deetFa!#.99YKKLJLP(l6Ⱥ"{Ύ3yd6 DJC ԨڵkbY(PTqڪ m --55 NByglٲ!Ci#F? QJZZZisܴ3frA G0( ? m8 JGP"9>< B!Jp= RDEcQo8 #4(zpQ><?'BdQBG.NoO."eN(q."WQzFB Tħ BA BTlףG|`!*") (B!~/""U] Bn_$ _ $>xh4tQ*-% n/E^(**JS!\P(c_yIPWB=\J/Q_JE͵***{Ah̹59z*ښ4 pt&Q(((8(V׼p(uөT̐V[[BRC\iq)@ T5Jե(JUPP3Z/6횝Bk:yd||MBBL-WVV.++2[[ہq5̘1#%%Ej[K, ھ}e:4wŋ8qG<СC\32 5\277r >@ю\)][[;f`` *==]$֭[<cƌC]vAJII9Fffp gϞ ;{lGG 9r޽{EF9s<W8IBүW &|EA! nmm=|DDDXX؋/RRRDʶFFF|~BBBSSEIIIIIIGq͚5k֬!o_@8ЀwbB^^^9---D===ܹVRRv5???2[yy7\"<{-\4RϾɓ'J^cKK BZÇS(iiix<'8z ;;ѣGO8QCOOŋ***8}ٲe?N^ LLL,Y2 d{>YYYϟ6mڜ9spШ(gggWPP)3r윜NII͛qqqdkJB/^>}ي+B #!!!""B+**̘1cƌϟF Э[{655)((p\ HJ555OI>>>/:$zI>L]2L*sX'NL>=((hܸq!???###L//{1cl߾Ԕdurww𰵵]hݻw|EcǮ\וX$[jj#BĤ/666l6;((H-Z`=== 箮AT2V\sٮGUUU'NXQQ1n8&Iv4}gΜS*++Rڹs*BmǎVz9Ή'JAONLLܽ{4455eX<֭[Æ _QQq۶mK,+-,,ܼyu Bg+W"vmmmmhh֖tww[!yfiO:x9s 2ѣ6mڲe˥KI0nddd|||HYYYt:]A+B !$p?ZZZ=B eG9sfذafff ǎ{k8p 88믿&_ eXqqq7o666622ڷo_bb"H]]U~AsΝT*`,^~#;Kܹs"?P?~|5pc0񃥢"9Dn#_$1..Ȟɓ'lGwwweeGATWWǢ?9Nee%N777'{WRR***t:N+)))**h4 BP|>+i>ٲe"_VWW_?ȗAP?'BYYy.Tr^?͘1E6666^AqQ@~GAqQ@~GAqQ@~GAqQ@~GAqQ@~GAqQ@~GAqQ@~GAqQ@~GAq^ x<@ @%^4|>9w9cIA,e[|ӧO1cƌ5[`gggRR)Np8'NXp"f[ZZ~GdJQQQAAرcIJ333\?uT<… >400tssnpιs窫.\3gNy󦊊3YFzxxcǎ ˗/!eee '!2QWWrСuT*uY IDATJr<իGEEfddݻGh3t_~".RQ;2D<'yFF7|3}̞=ĉ}nрPWWWWWgڅ 233ҥK$?y"uttԈaa ECCA>433U`69V[UW-99O߸q֖SXXhmmpDB qqq8_z_ Ę]]]3+mmmUUdT%n&A<|B>~ذa*zuD+LJ "y]7nܸqŋDɺ9>gV'NZ\\4xzznܸ+INNE^^ݻwgϞ|x<>rۯ^*9J|t׮]8;; U__/ɓ666 ‹ t<{YUUU)T__?o<2q۶mR }}}QVV&w\lvCCCfff+ZdIPPۥlfBB@ ؿ#GNMM%/%TŋǎKLLՕmСҬw:VWW:u* ևFݺukȑ---8~~)y'y׮]:::g_Ezz?0eɍmxV^E򴶶-\˗8=66vܸq>>>}T ܹyUPP0m4&igg5kY,ҥK̃ZbŊ+|||;?7b s=pB!4k,Df ؽ{;xgNNNm* c .ܸq#N/((pssfX{[duA~'‰}l6>vy($$dϞ=V244NMM/+YS2 GGGg4,ю\KKK2?믅8!믿633_tY===ooﰰgvttTܹsOgipСE999999I@&L(++# fffma 4ϙ&L[g%H_>%TXYYf'M$ {\xw'9?1-77799̙3|ɓ'O7mT[[K`zM|K={o8SSSEEE }mHII9q֭[ߟ-'88ӧ?SvvvCCç~c~'9rd޽G---UPP&>,YѣM61b_m``u> tuuuuuԌWcr="E}}=rssi4lbbw}sΚ//'mmmyyy6m֯_vZVʪ544L2~ժUMMM'OL8qĉ5551117o>vQ'N2YYY8w mkk/_!0̬qƅz鬛?>ùw˗-644 ~(NNNn/((1bp$w%%Ç_~]<8x`XXBYp!>3o޼ʚ ???o~Z斖sssL!{{{.K={DFFFFFJϲe "##P[P__ tȯaZZZ<?RPPJBmBYO pܹaÆ˗o<{mjkkY$r#=<<***.^RWW?q℣։' X,Vcc[VV.H̔BOݦd5hРcǎ5*<<>=/mmmmmmWX>}N:xI|||TTTVZEJρ_=NNNW^z#۷oX!`0/[GiggwI 2w\whaaf_x!|󰱱Q[[[GG]r;\p AdplƆ Kq3f L&ҥK򺳺:88Xr{DWEEEfff5yDHL֒^B?LWӧ"pY2}ׯ_d-=t?` 4ݻ}e:몫BFC]2L**~ţeoo/|=ZYYN7ءC=<<EG¥͛f?`Z>~x@p TdF)---]]]%ߋ>7iҤI}Vf ۷ok%8|ÑtEEjkkW\fGz,--=<< \UULJbwd2ssssss'njjyƍ#G2 zfw$011)--%otvvvww,..g) 4766O_UV8Hy_W1cnYYW_)l&ɇp}6566&w@ (--sgJ SUƍ+))!<,[VVF Z?"dE<]:EPGUUU'NXQQg/A__^YY911/~ןwߙપ*uuuZuV?׎;`0Fu)\Ν;7oޔ2Kنk׮q8r|eee"7ݻm #AKKKJJ̙3&t)0)p\?Sھ};A>'x-ǏWTT(**Ι3GZS~V~.rwmmmmhh666ݻsդѣ9va'''KKKܯʪƍ^'O|墢"ϫ&rϝ;ج>}Jr3d}n!CTUUo߾ŋOt!C߿ܹs322c^~[$94iׯ,Xpܹ3gX3fxթS(..t&:9U*,,9v kkkA Hw:CCCGG}|_~o%K^Fud2 qF@dff=wiu.K&^:qℙAlٲEWW;&&|d===Eȋ/Djoo`0lmmo} X,faaAѾ[a.+777m aµ888^]~D~>u SN-**‹$<+ދ4Ez ^k[{? ">>>"5p_Ƹqㆹ:$_177㏻pzzzffffff۶mi}2 ]]ߺul RTToۣG]\\***piӔ:舿zqy w^NN:Qy^ >7]EEE _wwwWWs"7$@۵kWhh(N!ɓ' DVdur@t;::DN &?~ƍFFF%YZ[[?gWRR***t:N+)))**h43㎜⟥!OE%''Ϙ1"=p8?_ߟuC$frvv>yduuuYY_<8rG. BHsE3?[$  ܁$gkQEխHc3P`4@~#t>>>|󍔙ݻ'eݻw(`lq $[a}3"icoo_PP իG5rH <]N>lҥӦM9993gD|'Ÿm$B|Ⱥ  481$$_b'>fҼy_E8jccrgΜ|2[RRmppnmm+KÇŇKLLlhh*//'AkhhE~1,--GoܸO?9;;><;;[ \xqƌׯ_xرcKJJ9dbYY٭[tttĿ &2^cYZZ'ٞQ)STUUeee 4` :ݱcpKee%!?cww,1ܟޅlqF\zʕ+ׯ/))PVV_Pc2M6#Lx:%%-[,[,$$$,,O3OUUU CD^&O|С755=zQKKKoom۶^~Muokk344%2%ߘy2 g 㨟_{{)SBt:}񆆆ÇKl%\\\~3gal!ClllN:m6Q(ucLK9b```jjdIq$|59\{{u<ŋ KOx̼5k֌;O>>Lq$SWW~`'EWOIWEEEd!q svv /.L#Dώ_ ( ? 8 ( ? 8 ( ? 8 ( ? 8 ( ? 8 ( ? 8 ( ? 8 ( ? 8 ( ?_/_jy<@ jpx?|>W@ x<)3?0 hr!"..n_`|G&M*..njjlkkkrrrII 777mmm.\(..&jС^^^III!cd:iܹꩩdرcSSS555'L@YYYX, ri~~~mmmpp0BFzxxtuul``t̙C:88Ć9s ƛ7NII sBgΜ3gΤ}jjj|rPPrzzz*_}?~MDҌZ9q̙3%d۱cG\\ƼyKe%e [=>S--K( KS{>s=n:۽SRRx<۷o////s&YXX8xK.eee7o?~Ĥ/@x""""--̙3}M/_\AEGGٳL|)N "#޼AZhѧ~C2EdVZqu֥ŋtttt\\CCC F^^.QǎP(/d<w^]Q^^^YYb:::^ǏޚfߺuK [ZZ$;vHNN...~A $M 1PIs(<+}g]8Q]zoxsss"y 9H֭[ɲׯ_ ׃/BDyy9J=<^XX(RW_}%I?~CI 짟~044\r%(FEEńr!C{XXXlݺMKK355%۹vZ<8g g 27n0aFȽիW7n6)=, IDAT---wKDgώY->>~ذaRf~?l^^^ݻwgϞ|x<>rۯ^*9J|=k׮p<LPD8;;+++UWW '&%%577GGG #^>N꒵+Wrź|2eSSS77;w8<뫫kjj*|[ëD:u* vrr255EUWWO<!2c PkkkHH… !!!wuuuqmF2119zhee ;]]]#F022Z`"[Hݻmllc&&& :~xPMMMjAA6Ŋ%Y]!tM1{WXXX'ϛ7gEhѢ#g׬Ycǎ VWWXVyܹ泄!'O‡I)QPP0m4&iggG";OAڪwk "::dlܸQ8px:???00`p%K466~|Pvv6yJ,\l^Lף\ \.Jݵkׄ ]]]723D~;VCC#>>[zMȫ∈ccV2o=uԩSp~gg瀀Hɓ֭]N ?Nj rix#233Rr\6778qBII… 6ƍ:::yEq|qTҫRAPA@آ1voL-*bEc/X4*EEi9)wWn;H˗/nvvٙgٝO{ɲYC׸q`bkkŋ T[[r+**/^0\]]ԩSo߾HHH0 :WWsUUUhnn3gNmm-011?~nnرcnjso}}}m&|ٰ:;11QGGgȐ!nJNNfMMMBϟ?ohh0,88xСAAAx֭{H$JLL֎[z52dMFqq1a---122:~8aD099033~Ҫe9őnݺ500ܛÆ 9<χkhhX!33S__xp7#QԎVWW_fMkkMMM>] KMM믿>L Yv9x`=x<aϞ=cAAwQaFGG{n׮]...0Q?x>>gΜ!ceiiyͭ[~7&L nMIIo%߿_KKk@RBYYٻw}|S^űX,M ---&&vɒ%?Ei!+▔<==OGII9ĄY[[{8_A~Hjժcǎߟ`KJJ***c9#G4h޽v޶2DjWWW&ʕ+Vɩ dttQQ+>7(섨O4i'O޽{eܑTG axt>>>^C())Ytg,--R]Z*rlnn~IQy?ĿI~=7hO⨹9Nu޽Y,@ZZZW^AE~GOBB.IN]]Μ9SNx$UXExPq 6ŋ_z5+8 YWWG8p ((hĈx_ҥKqtݺu;D(ʔYq43k׮u˷n݊477;wngJ7G} 6oޜH @FR^UU՟~i޼yQQQ7n n< ^z;v 0yyyR://OEEȈ8[jGӇY9팎0`@ZZZUU;~* 6U?22筭LKTJ P`aa,];8R=xp8йv(v_>lذBsǏ455&^z!˗0B֭[0shhhPPЬYaF_^MM ;V~GRG(HJJ8q_==ӦM;x_6p쥺СC3dȐϘ1C0H$:z(UUU_OVVV[[ƍZz%,ɓ\8[[[|w9}P(7hnnnllܿ8Q*sss55zZȤI.] X:vX%}qccqpp0lllw!^|)###߽{ߙIoK I谰tB]TWPۉ(dhff~=3sLy>:tu`Vvpphoo?v>>$$x֭Jx#߶mۃ< kɞ}}M0ICCW=4|jT`۶mfklUUU8ӍbX,UUU&`0h4FDBP(޹sۛ@e(㲺˗mmmrQEL@(/ǾmKwϟ?; УGϟ>*<*.@ >N7~1cF8a\ٳgN> ?~89|9LNN"ŋF>۶mWX&UC_߬_~3BnUٳWU55k׮),gG'-ĩSUU<"]59.=3ɓ'N%?GY2 ˺ZK,Y yrrr{f͚Xq &_}XrssLfzz¨<`*Keam qHE4>;s^F~A_ %΂OQrng6o/uĉ;w]OII!f:uԩS+\OO/##cƌSȋLCY8 ?58_&P]DT@qArTE ׯWVVŸiii'NķbjYܽ{~۶mPW 6 >\b5\B(Y*,YDEEرo޽=== 흝q [f℄SSS M69::޻wn"w)SȏˣرcÇ%B.Gc>yԩ},Y aWݻM7IR]ISJlU#U ^e9/^llllgg;jԨtwjjѣ"ڊ*ξ}0C{{'<7oHVid J=Z__]RJDKYZ8tFT!QLơTQ:YJVd$bqFccc|\juq?ʋ 0СC˩_xj#` j ^~=k,33-[c133E!H, Nv 6XZZnllP\\gkk;yϟϝ;wf'&&;w Z_r=-a'N믿ھ &Hݗr:eTTԣGl6Qbll ~Ns|)ڡURTeV) I`jje˖OΜ9S___(bַo_W0 ۺunT!lxx)S`!vtt(Yi%ϑv^J6bĈ#G-B]|őL?TCtT5JY2jo.}OMMz)$6 gdd|āVepr\Qz߾}7owppnRJ$wB*vcǎ,--:0b_,kΜ9dƦݻwĪG;v+4htYj9cddڿxȐ2|p6x*KRBC:<^D>

rȠAOTӪ#)Yb?ʒt+AZ ܍L$C 9F\PeeIuȈ@MGGGwwoF  PB/Iyfݺu;t.|sĉu֩K"P"YRcҝ_9֑Bׯ),,7n܅ ~A(:F^^فJ;YU~Kj<<յp8xௐ9G(KɌڿnnnddd7ivDh4ZIIIg2to7R0Ul\WơTQ:TC2%RG!)SdeeEEEI+\7o̙3p@ ϟ/_~ &הsJDrgIv:T#"";a(H?kB&.ONͰz*>8>ԩS7n܀v_KKKzzTVyQ}}}G1d:f+!*chii\g?m4mmmWWWGGǐ͟,%ʯ|ӻwoU2+9O:KK-[=zj͛7֭[:::˖-3EusppoEEi%ϑ RLCCÙ3gvFxȩ+mL -1N0N$Ҽ<o"RQQ!޽koogUss3>_"3uرc˖-NE}!B *AN/XE IDATټ\.0X \.SHrguDN:B[t)f) Cʂ<---Dǐg_ (!YWW'wTUUr-655555IdR%ݻw|>_,WTTb $HH }vb1N5 2+߼yg\VQ_ik]uJGȻȢk$?0_{)R#,NII/++ۿxxxbbvڕg":Bw ex'^a2vvvJJјLf``w-è:K~mmm3f̈'t#0W^ Іt{% 4uӓd2a`t:(abX,RGGhwC gJ"1h"qtԩRF [p*,O".:)YQ~*ڵk&L>}:m$ϣpO*P:)YQ`zzʊ+444||||}};l@|(?*UN9 I9.\!uRTq~j@ >CUNNKPH,(U\p{?CQ@ >OUZB(NJb*.:mڴcǎ}POV]{*܆@ OeGPC>u?*bqhhhmm{BCC/&@ %G?yt3>Aϣ@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(@(S"诿"655***2s8Rb3kjjJm#w0]#ƏP,-_2zs禤ۧ?زe 1}˖-֟vf3\k39@tz< ׬A %;;[UUblbTUUUTTL&h4M$ BPxoooQ@ Q@ Q@ GyF@|`aX,?0 7?;="ⳃ50b1GPE 1vbH? *~  <8}t&Sȑ# .**˛8q"xuH4l0ӧ{5`@}}BCCف[~цcƌ100xYNNNTT RJ D"q0x)QƲe(6WTTSMԔڧOiӦKsa涶pڵkՉq+""ŋǏ.XX,~aZZ?_j޽p.]ZQQ1bO,g֭'NӃ)=zpww(ȑ#ж͛7 Bbζeee7"0`O߾}aÆ%''GFFCL>۷o߲X,X]ZZ:777+++#- XMM CWWwԨQ{ UPP< .ub͚5Nrss{.E!uuuVVVp N~ۉR|DVXajjGLT W2 C=<< o>⎪rJjj*uummmo߿[FSzzz:::ĹQrܜrRW$uר]v-77ӳw04%,z-9۷gϞp°0j%% <<<.]VVVJlR\@ d0 ѱruuu5555559::R\%Py}&&&9CCC7nHLٿ--- --o[O:uᵵp)j-,,X,uzzzCC9zA^aX(G8\YRR"}ÉǘL[]]}wbDP//%K,Y+--ūkjjѹ|aʊ+SEpУGnݺU O>>f̘?{" :욠Ç <{]>;dҤI7FFFFFFƞ8qBZpVZu鯿ZbR gZYYihh~fޞfD"y T(HḄk.@ HOOߜ 2D,_z!33Sb&ދ-ZhÁ)ʕCN__?--M#p3f}X, Wajkk۸q*L0`;wkkk}}}UTT^d2*|w0jԨk6lؐ~3gJb~yg… C,_pޞ0&N_)EEE\j1ZJJ |խ^z}3gF*˝9s֭[MLLf͚[RXАaff&5vիW<[h)/_ppp nrrrLi ,ضm#{ill\|ygB >+%LmmmenڴAOQ}^|ooogϞ4<YUϜ9ɓ':::pѣG===mll\]] }C_HPQQplmm}ztUw+[[[Z&&&L&N@;UUU:.FoG.NWbU; vqqV *0 rQUU}I\An@ >kJKKKJJ0 ڕQ@|^HOd9mW ϶'N?O8p=}tڷ%''766*Zx9y$QӴ3ٳQSSvڇoÇ'$$ | ڜN[WWALBtwHy5|@ss}Px?rss---+s5k?X477 S:;UD"\c7ox񢤤DLD|TDoߪ2LyO^qqB Ӝ>}zȑڊO~*k@gQ޽+':QBhMo߾uppxyڴiFFFR^5jTzz:;55u`=== \֭[nnn=z&3zh}}}wwxq|||UUܹsQQQ&&&ӧOxu /// ;v ֬Ycnn޳gϕ+Ww9|pxxSk``dzEEE"&M—7ڶm[~tuujڵ ۬qppаR֮^wޖ6m"WHm̨_~eΜ9zzzMMM^w.;k,iӦqN2Yk֬vځΞ=O~~~kq9::^~\aPDHVGEEرC1R+={I`666>~Æ ŵwm.%V]_k8=73tP\aܹ#&Rۓi%x Yi@ @-} %qOOx ***/^/yyymݺ500,M޷o_xnnno__m۶E|>aNzmDDDBB9bĈ#GJT!vٻwwa_M,%BaKJ@@@```aa! dC|&ܻwjw wq\H$ASSӅ d}}q߿OGwibbB$8~\v|8 'OBBB,YYFqaeSO:`0$qww8p 1"a EnbX-ommDӌAAA]Z[[3220 ۱c/a֭ԄY1 ꫯ8J_"zO}̳{nSSX#Md,%"L%pY.]GucbbF9hР{ʹcZZZLL%K~G55w P$8K400–ĵKKKqLcNNNdffرcǎcǎsGYMmy<^cc#bX,777uյlv'2Ng UUU(} r!q˗oݺinn>w\sssq5bppph%%%n s?rsszX0www65yyyVEEE7o?~|yyDlSKɟx#HmL2^(giiiZZZaaaY/ЫV377xɮի ]~}hh(u ʙ=`];a^G,Dwɏ<bdd;@Z{rZ pKO%ӓfB }}}^zKeX0uX,Ǐ=~888X8SSSw܉߾}[([[[1yd.~z8i`޽۷իakk[YYYSS8p@ssscc_"?66ȑ#o0L,sTUU=zC͵{8̙39PEEeĉ \O>ڰg>{v%Ijc!DWUU=|իW YСC׭[Y"Ott4 Jt=YDEE9r6>>:"yK\\\TT||w%87Mw)?P__3 &SӒ![d\Rla322V\3ٳn:%(Gi4˿;O>,Y͛w-L8Z5katt4̙3ٳСCa΄LCCÙ3gœdl2:~ȑ/_>|XbccCBB͍nJΣrJ;;;GDDHdoNjooӟ?kee@5jT>} ǎ~6mcHHƔ WSSp贴 {鬙3g[XXEl[[[r荋[t3 mmm߿DKCT,O:uƌÆ 9~޽{;e*<o𧣣czz LMM_,^.𐂂(Dm"e9- KKJRRΝ;ŋQ;$x\.W__"P(lhh\ȈDf uuQP>pt8_3 Dzjqvڕp444((l6ȈQ__O ɦi)^:(p(,rwVee1uQV9]/aLΘiӦ3gܺuaXEE,RY[[777^<"6ڞN 0A__lK W_}U__Em'l6Cb&IDN@  _:5*++g̘mC޷o߾r励۷7p\Cɓ'+4`guFܿ___wL&\QkCqOM7 @  ;Qڸqƍc|jEQ@|Fp+++OOOS ("BI nR(hb_WQE g ap:<JQE g1XBB!1_RE g1¨D"HW.DPaT pܶ6:HèTm2("3B${M(h4ׅ0 H)8zN@ GDCCR @ 4_@ Aq@ Aq@ Aq@ Aq@ Aq@ Aq@ Aq@ Aq@ Aq@ Aq@ Aq@ Aq@ Aq@ Aq@ Aq@ Aq@ a~hPׯ_777bX 7ahğ8^:Lh4Mmiid~"-*@^'_'Qh:aOpssPUUv}7n466=E |3q9NY>IqT$]p!''gѢEVVVݽ3 ]X,.++۴iWxx8vu>6>y:ǧ!8 .-\Nh>{0 aH$ٹsg~O8򁏎ON)zHw0ӧO>XW;2|{@d2;VFl =ZZ&L8q℩kw|#A(t2@EE5I«?u =Q.{???ݾD5 SQQoMOO1b*ݛISuttΞ=@Llii9v[WW^MMu :X,0͛7444:_DQ`_XXXX[[jkk˖By= ++MV!"''O>D|\|r^wܹ:W߾}.cƌnGbq~~9`h4{̫W5 OYxq>}꒛MxELihh7o^XXvUUU~~ց<<mܸzzzŸ{vvvVڀG9rdxg!2/^8>/UUU }D"aB#l @o D"a0RSW# ,Yhnn3gȑ#?~/ӧO^:77^|9i[[@ `0,̬ٙfKX!V^bE:}…666?@nܸHh$hd [MԾ% m3p9)zaCMHH;sŋ^AtO"?577ٓd2 X,\nsKKKk mr[\.ry\P([y\6~9>>>[+jjj<<<-^qܕ[[[[[ZZx'BL.QU]qՈssQ# _D ?,\`iiYSSBj~;^7;nʕ++wiNz!=&&fgΕVgȐ!yyyiݽϞ=K>q8c\8XOCe::: NFhFtN[!t?.e]ڹs(Pl]~=ĉǏ7wLe~aRRLo۷L&a4@#V@˗/tiӲ۵kֶ۝-Zd``p̙N`0 :3<:djiiihhڍBQ(.*䎠srݦG8p7Kؐ!Co\8p{Et=&:kgΜ (=$^@ۅʷ o4X$.**Zn]\\\jjۆlw[Ǎ+;pl773p႖tF.>}<}1 ;077ںu+Ff(--$$CGeee#F`0eeeVχS|=xfrVO1hbN BiE}PQQaff&i4L7000?~B 0hE:(kf)涘,L]8j$|2 72D Q޵?rkC1}s{=]#"nܸ/[ɓ3;Hg GXh61yBtttl6rwƘ($j4!nժUk֬9v֭[o\#/Yt={<### 2*!4dfQ(7E1 q|#o1vcbb9#Oyw:f馦&ɤl7L!rƕ,2L6lعsŋ7oL:Ftrrr[[B1=hmmݻwi޸q^ߏ?IB&/?26y8 $%%uuu=z|e<Ay^R 7||ȀFٵkWEEBpVO<>~nGUTT Ku8p鼼<寗lnԩ$Qr~\w:I{96ϟgǧNZr|fŻ}1А[G͚5+55uǎoFcA],[*3#/ WhdB&^Ejmf$%%/ϒO~ȑC9h +))\d{kv˲*رc---)))G .((3gNNN… i޴iytٳg~W! %KgϞ]VVw޲2ӏ>hIII+Vزe˃ϟ' V٬V'}C6o\ZZ_̚5k̙Gef˲,9ödRZZZ^^NVTT400P\\VaÆΙ3g^|y{H;wX,"v1Vn];rh~{m:A+W4;!IJp3 ݻgΜ),, 1bNEV'$$7;|Çl6V%2 h4M;|?zyQCCC16-22lPrpxK$j((( G|P{{{rrǑ]l… ?㮮h򭭭MMMfyNvvvFEEуוwÇccc彏16NS>? cOuE}W555-u>efH x=F1 =eʔsέ]|&/ @RKIRHkʤ!'rm111ce'[ƛ4hkxsBC{_2PdˆFؐǫ Nq9!2OcOuE;u |KFRB?}xjEI͍6l3gγBiGyy!S~f`8㌌!woRODζ-Zh|ޒ4"cy睉8bH u zZTT<^!y~y}}ѣG###/^A3rVtFLkF20>!SZa߿?sLZM322._0l>qĎ;9˗#߿oo߾MƘtFDQ$I4M qm : `0 qhG~*3))`0TUUegg{_f* M&ӧMMM&缼ŋmڴ֭[QQQqqq_ǪO=& 4Mlh4v8x B_ϟ?Jrʾ .^az޽5k0 CWWW_~ntAy^R).GZm6vhݼysppΝ;?XKJJX%QeҎ9}ݻwl7nݺeׇٖ"vڕz뭷l6۪U Z~޽{ۺ<%l6' .1"ݻwϜ9SXXBF~+㎎H/ZEd2 yy:%IEq``jVTT$%%)I (11~j֮]k4Y}}\.Wooɓ' âE&M$r8N P%bUUORUYYv”Etƌ0QQQn{߾}uuun||Fƍ{q:y`RDkjj.11v͛7juffy=jkkzjmmX,sU0^: Amkkkllw^OOBEpynF L98pRJIj:Nh4EtƌPGXPD RJɵv"ZSSagE##z Help Menu Help Menu
The Help Menu contains information about using wb_view functionality/GUI features and wb_command utilities.

    • The branched list links to information about each Workbench component.
    • Open branches by clicking the triangles to the left.
    • Click on a topic to show information on the right. 
    • The open field above the list allows searching for keywords within all Help entries.
    • Collapse All and Expand All buttons collapse and expand the branches on the list for ease in finding topics. By default, items in the Glossary and wb_command are collapsed, while topics for wb_view are expanded.
  • Report a Workbench Bug... pops up a form that outlines information needed to investigate bugs found by users. Please upload relevant data and send an email to workbench_bugs@brainvis.wustl.edu with this information if you encounter a bug.



workbench-1.1.1/src/Resources/HelpFiles/Menus/Help_Menu/Help_Menu.png000066400000000000000000000373761255417355300254670ustar00rootroot00000000000000PNG  IHDR]>{ ]iCCPICC ProfileHPY_Drr$!$* a &DW`EeA5 X0 z.DE8p궮wurIHbr\i!a4 ,0b0S8v>>/~Ro%ȊJa Ja&!|)pS[R9r" DtcVGp?'3H<ħ3c>yج86z[3c,9k%%m^ "'zFd0bx]wK$22JJL[C)2;kۈ#5b82'g͏b2;{~kS>'iO_o_ 2M`2Nm$a(HT%ZRbD")I8Rtt4NH"k4~r-|BPl)aT~J=* OW7oǿ?mNr19A``` i!K(GU *Hu2{'Ia0]8^8_𬈐HVr"G'_o3?}~}TM  v;BtCv mÅՄͭwZxdqxn [7(1qME`"#">3ՌHzdE,Ӂye*fDYEEME[EEOX-}W:52~>;6a1189 b'{6n޺y-'rݹ5)PʆTa$}6n^aKЖs[ghd˘tizs[vQ{⮜]Y.Yuv]nO\k[=;k+f^~If(aqBF+,,8yVW7JJ*zU:zزr }Xdžo̯c܏\ZUKN`Oxv2dOf?H|ezM ƴƙSO;nonjm?Τysϣgv3;t^|E %jhmm㵇uutwZvK E.^"^ʹx9\啘+ݛ^ zǷg׽5kW._~ōf7nj7oזۦ; ]rεFFFGMOAY0 <.y"7ߚy&O>`N=ϓ9(J /8 >_|ܿ ګ?2;zM[u=y~a>ćf>Zί_-&-.r\r@!GGJ( eA+~_Jf^ 5e$FV"phC^6$,.E!N/c m_j>J_0e(厁 # pHYs   IDATxw\TݥҤt(E5y&Qc,1D%{Yb4jH/j,X@l(RDw~rwYVsgν{ܙ33_s٢By(ʶBy+ٶA}dP^&J%nkP@-b (P@-bc-\ʐPaw8c A'^P%bƋ1` <,V' mi1~< & miZ/Z8i$!4\,f9B# y[9~D a\wT/P(mKSdmS8bk`?MMHI=b D/H;-:xBՑOs=zNWtҶyJ\ (Y,ȁ]qkiJ,z5% 1Ap" ,e^:P^*d1\Og~Qǥ=^P22DzD`kJb`lzuF;vDm( /`WwU5[+K$bhBQҦ4X yT,`hY;cpwf~uWJҶYC,ak$72g( `gd!0U\ m^wtA-bVyJVcAR*1(jJ`8wY:Yývk Ꮣ)twݑBi[P),BĀ>>:X1duuPS%>L(Jsf,?ǀY()շ@:PΑ~ HjvG m3χa-Mh )R5T1ۘtA<38] {x:V-u~lE f?(zx@ǶF7GZ 奆ik(s!P(^P(BZ}K VV+#qV"f~)c,W ZE!Sha33Ab333//.U!r}{]pū* 162g=g?,,U*Y`ea8anNaRRfhddD; BRf(xWa,KIiy͌HY=\YJ;Y}7;Y*?>`Df%wnT`rReqG۩w{ƕT1bXˈXG}\V^%(bu"ԈD2P('"Zv#G.]?QЀneKDu0m\/Wv0+#bBP/~T[M1`K)@&-1723UB V)btݨZ1uKIi,[]'4`Y24Wh4vU>(؃9NՉG'r:$6!'nf`.1"@, Ejõ A`ܤ`y"\Q QGzUt= eat;w+.Ԗ׏pW$M.,FFFD ub(>Qh/)#LU#sq. yrUTTtؑ(qEEENpюFP{Xϯ-hfg()WCZV8ia[5)e!`1s2 ws{8u)x/ٹ,sj٤^[}'=jBȾSpUUm$1f5K͑^~3b EwMLuԙVftPT0HdbT- mţJDcȕ=u2(-)`* u™2VR,;wڍ^2Wֱl=`!F+!1H!YV0 `Q-@c Ĉc⍡ %Z>P(̈́xd8x$u8@F@j3d 1 F AY&:fm޷]@y&$9d,m)F[LDmp3'mꌿT^ϩcǎt@AyaYɬjꍵAӚϚ"a} &&vq222Κ(eSmmmN?I6,J<4 ZCkYl!Hh߁f٢Gw%7eur#(, BzBzBWU  )J:FP"TP(/,W. 0ƍ %0YJC(IBj)(FO ƪw@P^/'b J _@ S(-z]҈B `1:npҳ8rȞ={IlڵkSSS[@ E+zA~@R0\OoFWXagg}蘗O!gϞ?Il&Mpqqi,m233[RD EB$!"b bv)rPPP ;vtppxFrP(/4 +:'>2j˂IFn BGaÆIRXrƍ,Y"^xKҥ˗IJVVV;w<~JrJ>}ݹKnnQd27|Õi&gg's̱޽;' "Mi)A}sm 0m`Py@lu0`7n]z788cǎ 4c>޽{!!!]tټys]]]BB޽{^oYZZ7oޱcGPPZ_TTT̥׏|\lY~jkkrsV^]\\ܧOSN oҥK%Kׯ_vݻwTJn߾a^tIJJJqQQ~?zo߾111JRRSZ##_ 4/4PM XΑt*ߪުcpp9޽ڵk=ϟ?|pEm۶pڴi*d2ِ!C<< B1M;_ 'mC ,,,bK?x@`"ccA8OM~RQ^`"\ 1G\A!2&*G<(J~JwhN `ˆ9[UJ ,iz]n[(z<Vg 兡&N#vNbe#mK^(K/ D=Ʀ*)J{G_>-RcYY+!+++77d(((n)`k ȭYp3꣏>w|M''ŋFEQQQǏ>&''D"0k֬ӧ;88DEESLMM,,, VXX/}?rrrt/Gڔ}OR(GFU ellqdk`7n`, _cz{{5)+!%%ÇaÆ~fPG;v,;wurrj>>>ĩһw襤İ␐SND"ю;z`gg'O0IIIwڵ D"177'ݨ!C<66vРASNpBTT`=#Gjmm=dn'R~畄C^zl^8vAHRXrƍ-ZM'r844ŋ\ʕ+t]޽{ܺt¹{#mڴy>QFd2NjccjH$ԩ|'}jK.%ǻvruuurrϽ)'U/dff._{~~~s촴43fϗ.]"}۷GE\DGG{6l\ Hwaٲe۶mHտ7!!!aʔ):u|rYYYjjj>}0cǎwHOO;)..޾};ܹs\QJrɃ "'رcÇ-Y=z4o޼~~iEEܹqqqJ~enjSQQ:bR?f̘AM4sNE&-cY[[ \ׯ_hh+suuuΝ#W?O,_rcaa1a„={ر05h"+Vp)_bE``ҥKϞ=[[[זH$gϞ%uV__߁&''c]|;IDATbŊݻKR7Su_p᯦m>illuys=-[TK>oߨ;Պblڴnر#'NdffjzGyzz:tc [Hu]lllԷ NMM]*00P">}://ŵW^ݻNN\R`WZZO?t6Myyyfgg8q"%%߿O Bc,{]a Ns?ճgt={|H{=s !pOyNZy=0bp~u[UUs.$Ua4Fǎ [o>\tIѷo_lmmI .--{J8uЈ;v>"(44t888GEDD`3 efddXYYkhC//+VL:dddd2/// ϟ?8}[܅K[e]sȐ!],K1,OKKSҧE >Zil!UtRCGJU >rFp֚5kܹ~z>gΜ;F|QF]t֭Tew%[nݺ%QT&&&j5=zO>W^ 3đ#G6vObbbBCCGM=͛7Y4iҹs= ,ʸ[ȿ~"BA0׏9BF(~\ ˲.\{o(a/ܷo); y R_ I̞R_/W˥#AKoW7FU־T*]~|֭蠠 ___///sNn^{5 [$wk׮ٵ'U53fزe`P-ƍvZTaaaзo_sg{DDCC]~ڴ+^s6l{uVCC7|2lȐ!?3 &Mw^7c{NIIqqqݺuk6dǏ1cR3g6:t(..nɒhm۶Ç㢢UVeee5UpP@X+SUԯARVWCDz9T˂8KW!ƭ5$8jT#Q x{{^zСR2p}FP.е GyyybqEEac#s70Ǫ@&HD1LWG?x@` [ZZ!{ju! JDFj0`mݺeÆ/22қp!^ '4TWp"j}1ƘKG X[˳B{$0yX3Q )UD"T H255mZ@#0`mݨ;)F4HB66]7z?>44_~?S{q2 h>zZvcJu݌FmeOaYV5GͽLo-aܔ^m_lPwf73EQi4pFv gzҕ5BM0z.pQZKlj6ZEԉܛjB x0ͯNo_Y .^mhe#Aɼ*ZC5X\< 48s Լ-z֏gP__!uSPPݚꂄU*ac¿~UV[\gW-kjX@jmѢ U5P "P>U-ě:P@\$M 7l H ^C)))ܮƈ?~<199aĠ꣏>9s?(hѢNmlFxC yFurv?gn= =.\`u…nEoLii6c\yC_mB : -!-:zcaSh{O"p)HMMܹcbbP(zx5 ooKXXػ ,HKKqT*JÇɅ :l޽~wK.W_}eeeE\6ȑ#'YI$2eJPuu-޾}[#6Iq}#CsSe૯0a>eʔ׆w;S6{= AgI@~RYRRB<:@Bĉ~~~]t/I]6}.ްa㫯,//[dɓBYķ,}իWd2r/E"Q.]0ի CP(RSSFME}uuu0x҉'N4;;;G U2 :|_]]-àgΛ7ÇӦM#4GRV6͵KNN&`>^zΚ5qΟ?[ ti\rOO z9K >}q; .׏dyfPP9,T*UUUD:t($$D&͜90||=nROLF| GUN<9,,lƍܻQP([lٶmŋI!ے'O,>>>11ԩ!bYΝ;[8p$y75ݾyСkƌ|_ik֬9rH\\ܟ chhw}| /_;u޽;Bh+VX|9)_Fo+;w!\C-66ڵk;j_駟@ff&s@NNNUUUDI@힧[n=Ϯ];sr0w܀ɓ'Ǔ555۷oy3&++j߾}}/] >ZPǃb&g9w/^xĉ^CD;v>pvv>qIX~[ppp>}-[ʯKw())&%%$wLpz=ysrttLJJrss0`w}GooN>x^d!L&J8q!tԩߊG#G8qYxxx^^^aa!w+80.X`?S"##]]]---}}}C*677{) ~mOOϫW yVmm3g-Zdff6tP"ܹs!!!% 3 0`K?/8p1cV\zƲ!'7? 5ʲ2\ӓsG*:thݺug իW^^^@@6 ??_.㏣Fruu9rĺunݺebbTWWjժ+WI_$ޘo>O!qs3TSqyor5644tpY\N69سgԩS-[ 2D+j^Pppٳrs õh_~1c淶KXp;N<٫W/++۷o$x~aÆoʔ)QO>?uf޼y_)ӧO~;r9D<0=ϔH2e+4Ϫ*+++//'VXXX(+ JIIY~Ä >|HH R4y^;ɭ[_,^thtO?sN.B դNNNZ6L&;we˸g'p@yyyyy}:qKhgg_ 6/ .^nݺ!0CVL2Eoh߹sg{{7o撊89]]]]VQQ1k֬¬]Ν;֭z/˷nJ]Kaa322}RT``ǚ5ks5~ Xp@cccլYlll#""NYҥKс) rvv}뭷^ 6\rދ &ϹsXk׮!!!ZTO$ qs-g-_<::Z*^~=00 L>rHnX,$~h6p,Ϟ=˭}Ҵ/0HyyFPdee988IJ^TVVVtЁs4E!4}6FEE=<<27y*ʪ*333RYQQAoMmAGŜ`FFFD;WWWb.mNg( SSӰiӦqRĄˣSN"Ky E;R(O@/PC_2pggF5JB+ DBUB!`&)K RSSӎ*)J1aIJJ)e IENDB`workbench-1.1.1/src/Resources/HelpFiles/Menus/Help_Menu/Help_Search.png000066400000000000000000001506631255417355300257630ustar00rootroot00000000000000PNG  IHDR]A4u ]iCCPICC ProfileHPY_Drr$!$* a &DW`EeA5 X0 z.DE8p궮wurIHbr\i!a4 ,0b0S8v>>/~Ro%ȊJa Ja&!|)pS[R9r" DtcVGp?'3H<ħ3c>yج86z[3c,9k%%m^ "'zFd0bx]wK$22JJL[C)2;kۈ#5b82'g͏b2;{~kS>'iO_o_ 2M`2Nm$a(HT%ZRbD")I8Rtt4NH"k4~r-|BPl)aT~J=* OW7oǿ?mNr19A``` i!K(GU *Hu2{'Ia0]8^8_𬈐HVr"G'_o3?}~}TM  v;BtCv mÅՄͭwZxdqxn [7(1qME`"#">3ՌHzdE,Ӂye*fDYEEME[EEOX-}W:52~>;6a1189 b'{6n޺y-'rݹ5)PʆTa$}6n^aKЖs[ghd˘tizs[vQ{⮜]Y.Yuv]nO\k[=;k+f^~If(aqBF+,,8yVW7JJ*zU:zزr }Xdžo̯c܏\ZUKN`Oxv2dOf?H|ezM ƴƙSO;nonjm?Τysϣgv3;t^|E %jhmm㵇uutwZvK E.^"^ʹx9\啘+ݛ^ zǷg׽5kW._~ōf7nj7oזۦ; ]rεFFFGMOAY0 <.y"7ߚy&O>`N=ϓ9(J /8 >_|ܿ ګ?2;zM[u=y~a>ćf>Zί_-&-.r\r@!GGJ( eA+~_Jf^ 5e$FV"phC^6$,.E!N/c m_j>J_0e(厁 # pHYs   IDATxw\G߽BAP)J;XPk5cػFޑ35VTP4x*J~u?:Ϗٹffg%V(*i,Ԇ7w0o_Y@2|{0|P 0f)8^tn790d4 s%{3 aL_Q%#E4%&q! Lk~$$:#dl L#@$@h 8.`0NqA G,ZB?υA#]ޅy[Mc0ċ7уzD\"E쐁lG()FDC 1~å}gGC7%xP\YGW2%ohӲߓ aRS$imNL-+g߅L{Ƈ%Ib=ln<=iݑu $[}S D}J _ VlgSKF bLXPQ&2\!W() |AEH4H/&2}\_`Y KgUwWhMJK> =b/`0͎qШ@x!Hn26*બ4ٸ'@a v0d6˫T 4; ьr@huSo #Be$5_nG`0_ XPkkIQ Q*| ƀf_>72`uh͝ `aD F0(M;~ᛅ䗓OTi*B%FA4)$IryOS)@:t:#s;f:杌UUU q,$IdeeeQQQNNλBr]IVF-0Tө@uuu%%% W0 Iᔕ}Y'?hL]ivYFdmmL9|$oha|tJpkK\_#?$ɵԊӫkΝ544 f8?? utt 444Lnljp`o AܶsΚt:L3ݻ2 H$Ǐfffm۶ܹsee[w=V J+Re"IPl@ yGȝKgzzzMQSؼy3 MU\UUEp L2mj) LSъ5$Œ4oKVYTȋPy)O1JڬuV)c2JJJ233|>AyyyG-dmerA$a6ʼqq[ݢ|RM"'I@7.())I߾}gϞ=qDE@^^^YYIiiio߾;ZXX<}'MMM%%%###sw鴦@ x:7+zWTVԈae{znY 6t]m7J%edBoUَ=ib@/)y S$sҲNGEihhh&ZѣGo߾]f͋/H 9qIcǎݴi2\|9$$$;;GرJ-PPPA奥YXXiiiTh4y[ZBh:4ZOx_7ki(m 1P`2峔T8k?r+$<^UUɑSɡ۟wLqH[+b'qǒRk3*J=G^S/ 0v4g Cy3SVQQ;.]کS+W޾}`׮]v׬Y,?$$۷]t6 L@wo߾ETq@A***zt $I$=lW*s'24@W~nNjJ\B_ }ev >' $<_w#<{$֛j@@t"][ee%NCZZZFFFpȑ'']GǏwuҤI+W|Ճ~`0oN?3$*fe?zS6/S8L:&0 pmT$:}KWNR.Fw_PUEW 4xB JRU'iJQ_-eff#z.PdhhضmیF]]5(*z!*L j y槶x],Lt@9'I0[5柌oH.$ɚ] % R&zu @uf,3rڨcy~.422T`DGG׷BA>֮r3s kNw_Oz$Ol%X} n6|Aq:֗oS:00=zP8ΦM_~+Vѣ~IuuuE-o---333ƒ \SSx<^jzNF)M ,o/$I uFSIfĭʫ(PS*htd6J%AWS>Cmi еӕI8\=m`SR-OB@iוNW=oӓ}喔顷A/Ix|ccF&;;;##C)-'[@LoqPK8@ ht ) I2$ \K1 hLCD$IH)ZHGx} y9bhWd[hr`ꤤ$//͛"IRCC G?B}u,Ɔ ZM\v$eBjpGҬcR*U˵sc0f$ɪ{q/6J\ͧ$/$K^"i6a:l]Kz1WWWݠ`@PVVvf? <'7yh4mθa sC,k´*31,c@Hh #  F0(iY v5w.0&$I9" |C3.Ԍ;Fpt`N$1H46 I e404]HZ}}* kE7.").TO0$_t4C!a>j?T? H&(/V`0_C UGD`k )cT59k~gwwďǴ2jDžY 0\.Ls@'\´2jŅ{T$H $ Pϟ:!Q %&Ziöm|||lmmsrrٳnݺks\R}^D5B=$ (&^~lٲc"9:?vںuxٌ%K@ hcVe>}u=ݹsI }ߣ ͛'f9bccckksN0r"޿Z @$h@R-!]qQQQ}6nݺY|#G"qD?/ %2h4ͼ|pwՊsGfjb0AYKIH>I H I) ItdMDޤ|>իWptuuG>| ZpSZ_~~a֭(͛7?{ l̙3Mؿqq%Kn߾ O7oڵk9-X޽{K,9sׯ¦MyfӧOWXO?:u> ϞD牚ԟ=t Сŋl6Z~GGG!CH͛NNNfffvBKbcc}}}MMM y''X;;7$Ov $:jPtR 4"E )AD߾}oݺ7oׯQTT÷mׯ_@yykׯ_'''@QQA;Bl̚5KCCc֭}}GvqǏ>|~]K.|%zٳgv5n8K~0upMdKL<6͛_~…Wš Xl˗/;u4j(axC-]4((=]UUuo߾E+6;v˫!YĴ~j@A$k@$h@Tw]Fo߾3ݻnݺhrjjj/^(--}왭-yxxI~~~Î޽{$I=z$Ip`***FFF]tի_PU%.MݐÉ1cz)S~'NO0BBBN9rd:;;۷͛Gݻ7Ãq2̷Iu\iI2s$I.vP37,,,Ϟ=kddD=dGjjjԳ7W_PP`hh.޽{S2رc?E5> $++zuagfflHY-aXGfX.vqhd oS~H$  Q̙ۡ3 hhhXYY{Dڶyyy 22R.ڴiSQQZNNN/_477Gy***-Z>pAkT/ׯuttЃSSSB lCCCyUPtٳ%P42,P3RG9yWӓFǷR,Zh˖-7oTWWwvvְ޽{˗.c˖-|<())i|UǺvqF__ߤ<ŋ))); _~۶mڋ-Zzupp{ii\ń;..\Ahoi4wڶm+<..NqMnnnuva555TUUMiӆ`*++NY7NݿrBB]0 4hN^ :$O&ҒBC*1'.lt-5H6M:9i0Y׍xSL&SCCM6LSe`YJ"-㈡ A ^/Lu0LA f.2q\`>—>Y]N'CȜ`Z; 4 PoBJ"Rw@qwT|Ա"~nie;~>Bˡ>)\nY?}EKskCnnR[6ѵk׼7Eٳg``̙3hGf|Aᛎ"QS'Jp~駶m:99=}-\~{̙ql… _~C {.y.\٧O##9sȟh'''CCCadFF4m[wލp`KKK %KUUUկ_#.D"o$< *8@F,gEjjjBBGEE&%%EEEYҥKбc'N;::~{믿МJ`zzzEExQI9&A_~ 4mرNNNGsΌ3,--A$O|ٳ3g5jqtzxx8u֨Qek׮e0Gquu999 CmA6\h~W7--̙3`ccmffv/xbȑ'.qǧNz/OMTp޽{FFȆIkkk5j(aeݑ̒dZZZN}PrrD޾}zFmdrA޽nL(#)Ȫ%nsbQWWהNǏ Cg$i׮@5/_,,,?^ZZ:gΜB9Sp8/^`0%)/!!b$߳gOSSӓ'O$ի8??? y<ރ'ʕ+%%%%%%ϟG2K>?mڴkFFF>{СC *K,$#d%@ulPPPvvr޸qcfff;v3fΝ; 8sLYfZjΜ9 ,ظq5kݻ'gZZZÇg2t:_oO>AAA4ǧLwޏ?.Zh"jhh߿DITZzԩS;u6l[M<Fջw.]4<1 нۍ[u>H꛺aټ~ʲ2G<@SPP@=گXYY]` NwxZZZms|ccc999#C? Rd$ITG@SÜO#x/fEF\r$d2ѣt; QTߧ( ZvAӻtCXgcA[ 0LMMMgJ1-XM_(w||7^H >`0_ i׳_ !o\`0_+ȭGUqDw|aOF}|w>;5ҥKǏRTԡ)*Cijj2jjjIIIzB%ҀE݈ ӟq;reYزe un5effvӧO_؀sIau"55RΕ;fddA9&D pРAF~dQ#>ӧsܹsJJJ&BGDa4qڵ0nذbܹ ;v,vvv^h;:߾}]vE~Gظ^w422:thjj*Zgll\߱C~˗[YYYZZ.];<%ý>|Xgnsrr߿o߾h*\n]||˻u_gffKd5j} 6 M`F;|&&&FLe?cv<<}!I܋H,7 ws|СgϞ rJ`` u|rgg3g=ztӦM9&矶mۖ#y&NpҥK#""~ d0%%%ϟ?߹s'߾}v[OOoʔ)L&*؟y? 9r… H|/$I`Ijj*[111\.D:\.7&&@b:wŋl6wӦM/^{fEE߹s'~57n@-,,\ftzVVZ.1;Ο?PF&vk.?^7n@rr2Z޷oy˗?edR8qz~zpΝ&&&˗/544̙,R"={v„ _lvHH Z(RNNNǎpΝ}eH's5O/_ZYYfWZ'.0 ,PRR4ݻhaBBz=tЍ7۶mD+h4tGGͯ*uVjjܹs.]*//OJJ1b %9ewO.33{<~G2o =zA,/u;1yd>aC\\\jjjttttt=zKي̬,̤hnnn"kZZZh4PRR}edd޻wo~n޼ٳgO޽;o< FP6@N4/4 Gb0FDY`ffk֬x"uA#Q8eptt|ܹs)ӼlgFP~GcccwD˓ Ax7o ;_;^pkϞ=22?x۷_ǏϱcLMMMMM]\\u9;cǎ.]={6ZҮ];@sE"# |DPVV666޽{uX,ȧuQQ=B]Dž&&L0P~w5(r'N@~l߿5~G󋎎F .|?FJJʤIΝxXuϞ=aÆj3gΉQ|>__zvkoo_\\n:88ر#// ]S>Ç9st~%Ϝ9q'N4#F $):D6/B4HAAA]v4333331c;>yM63g $:JJ> 2Q_2*ے6m0ReeejJnnnƌ;8Azԗ!CYF:G$Iǫ%\Joj5>";(Dwܗc9 g nSPA)["l[hD&mşb?LԛV/ԩ/:::޽{bݻwĤaI$Ip5# RRRϦ ŞR"jۣPaH +G w;md;s\4~ S$ng2)CqFS[GFGE1;lՋ{>ToѡAQ$v§W3IhJ!w'"~ϡ@@Ņ5$YH$Gњ"j#뫬/諗U$hZ`] ptH~*H B$O~QOhv, fȚ?A}_(4hES5?$)PuTI$@P9TB٩uRW9BQ+ڧ(5.a:%īJ:)+|;u+nv$&?UTZ?D@H'U@$[ џs| `/^6xϿEЊ5ϟ2FEnӤr+Rp/,O;SЮrHVuXjR N^ڸFrTjzu DƀP}4 @u"'>;$Q|ly0s| !ORx#00vO᷷Iz!ZW<%%JtO> ?~\p6 CjЄ}ޤ"HS f@ۚO;O&&&ׯb޼jl6Gŋ)e!Cܹo޼qrr;Ĕcbbzmhh8gPF#h4ZͿW:99 2r1dddvܹs͛NNNfffvBK\nppŒ%* رշo_~G///ACCM6Hx8p,]NNNO<%BCC?՞6!ddd9Ȩ}۱cǡC WZ&4kϦyȟ{usJθli3gJKKKHHHHH/^ Z?zj||܁W/w wPx4#^wrwDDGG蘘̞={ȑQQQ j-Z$,zܲe1<<\[јWD}Q]S"Bĕw o22Pߑ7F@` D- uy_1x]۶mŗwd2=zh@\`V68CӻtfYYYYyZBSRRҒ Fөb0-4ߧT$"~G Ձ kϭ`0฀`D# ϟS T/'Owz})kkk++0jNS 'X[AeHm4BUEMJݩ&$$>0X5k\|:vx)uuݻwwwvZYY;߿?""F>>6mV?#.\pɓ'YDLuB/_>tЫWz{{# ڵk`ooSWWW===022ݎ ?@%LիW;RCBBΝC~?~\xwܻw/;;v,009uԍ72cǎhݼyʕ+Ww,,,|1z/?cgg7j(={6r@>x~wssq\|9VZ%wD0aŋ˓F)~G&O|y/O ;XѣG\.ɴ2e޽{]&;xoK.p֭nݺ,>˥zUٗqIh:̙3EӦM{Ր!Cm(Q$epttLLLܾ}q޿% .~ǔ .xyy޽{҉ \444|B===w9$42 8{l~G)I@n<~G??? \ϟ}hhhTTTbb[(RRR&N8gΜ%K\~1"VOOχ&&&Heq`1_Dž&]tqpp۷>}رc8wիWw]Q>>LaP6f Np\P0Zx2Bee+.\ХKl==Z[[78o666 bsw^ZZ ˀもQqɒ%)))l2xत$dp#F FQ~Ǭu֡/FR~RM4;wGd_llaڵk7tPЧ%%%={DS6`„ ȏ(昑D Ӥภ`y7-?3uΫW@XvFzq1̙3:uRRRӦM[f 4==}Ŋfڲep⪪!!!)))zzzGWWÇOϞ=knnN# cPPЫW:wOѣ۷obVZ5cƌOouӧ>ԩS@cbb&MHm@:ul4mժU ]_܎h8[:8o߾M-)++CJqƝ>}ĉ^k-P3f˗̙C-4iҩS"""&LPէ@-ONN644Hrrr^ Uƌs˓1L#qIhQSSs>xbSS1cQéEqA|~nn}qq vy ߷ IDAT 9۷I,(()6Gصk' 6m4l$(/3w:t?i4hkk{zzPOL4JKN/^xر)))WRUUuĈlsݻwԩ}C)6Gs?vؑ}v9i0ߑyTFiiӆ`*++# _ 9!# ;{!==~X3NQv˗qPnG4 i>rwli|? # _P$:qA`#EҒz/LNNΞ={O1;  rǼ;vK̎.]7n\LLB>c:ާO͛7˞ภ`>c;:ȳѣG;FŅ ;;4{3g?~'< 8p͛7o'YgG(;0@__߼ykkk{y*)St=22N<)t={˗i[UTTw˖-Yسgepp<#֯_?uT6mffg.33ܸq͛%%% ,@9˖-ӫWP'66v";Nfw}ڴi'OlӦÇWw\z~0`@VVm|$$$,Xm۶ÇOLL8q)S$q!Ȉ 111\.D:\.7&FV\PUU^8u#Fddd())S9&&&??_]]=!!b\}OD?裾}xGCBBJJJlK\իEEEСC={Xl6/Pw~:˥ZVVVNw1M6=y'I&Nqƕ+WFDDlڴIJ1J4>7;J ,--Lwk׮USS[xqqqu455g̘!i\\\jj+W\G|K.2M.2` d OAȐyy6=dȐǏK,aٖ>NGGAD6mPH/4 /wxΝCrǏHyϹsPbEEE %%@mr//ݻwKK=zzJ$(ѣGڷo߾}{WWW;;;iC]K{9y2cƌW|iKiٖs}yqI~GdRٞl[[[ ]OCCoSLstDDȆ͎[>444***11yѣ͛7]}v񋭼ٳ7nsFEE|/_5kKCby#KٓNK,aiٖ4kb_ct>~Xgé rqq1224hPzz:СCwNMLL֭Vǎ}||PÒjsܹcllI52\Bj0KD!77w::::::ᄆ^vM$'666'Oܹw}Guo:|p3 <8>>^F>|CxɧO\p,Xp^jhhpVZnjjm6 h߳gO6m uuuQuP_VV&woݺվ}{sssUU+V555Gy]%,-gLL ip8aaa2!Xc^^ՐXQSS痕㏿e?F :FiGivUQ\\% #  FܿHu"~G %݊‚ɯhFwÇO<)6l ϼ9qAX#4­x#L6@pgJcٲe锕KC=uFS rwxDPʉ:G](Q.\~G3|ccc'OO}}}ϝ;ユcS OKJJz)Tbbse(ny'prrz* ?I*,,lРAqqqu涾Gq&ʕ+{2dsaGΜ9өSܹX,?|`ggwQ]4朮ji~xyylٲeȐ!S ӧO#ͅ 8;w֭j}\Le2I}lllD4ʨWBZ痗{n9^F9]-vRWW?qİaæMFRRR&N8gΜ%K\~]( =ϝ;:wQNz@$IEqI(...l/+/2f޽{w!++ uF=Z988ѱK.999Ғ/;vcJJʖ-[@UUuĈ+۷opp;?~\]]]YY922r׮]666ϗ*޽;`:t(YN[[[[[[s܍7ڹs;vHOO߾}HR{{eݺu7E%55Yfy{{^O3gμz۷===Q^|[^:t0zhy JxrOttv K.utt

Ij!25!2G<Wh&v;kYT-MX۸ K4-i'慫UֿSK8x݉*Btsӻt4qR>aEEa%u?EOzN#œQSM@(|Ѥ]޶ K)zZ@7p\룁\AP59.)\IںLtk3Qݚtb^*cm!c1c^wcܿH$*,,AZhA;h1c4kl=pwޭsqq>|x̙qBL  u3l#Pg\@"f[__J.D;%1&ǎ ˌX:-bG鶴XuxXGK>JNcdlqe=gΜ155~vK+'MTUUw}O H4ѣG/YXX`<Z4N[ \m߾=88XCCESSsر{ԤI  NbxbbbAA+)D=;ኃ˝j.ebsBW;4_ÝBj]fcgK_Z#}DZcp֭[SSSX,ӱN_BO% ^zիZEs!ݽGAχ̈́ŋTWWoٲ6559b>}ի,YBߑu x6H)aL1cL5pؤ1*S_*{"Rp-A4tp'V6J=Gz(--=sݻO7np8W\TWWvvvvvvnܸQWWwԨQ_zunnnڸ{ܹs Νb8qdcc҂.\moݺ;sx 22,XD&&&&&&>>>S"kkOs#}Ϝ93k֬'O0њD߿, J$3&--bݻ7%% 'xy{IIapp0jRPPSII ;9.g*iAG 8bb~HZI-zkp49<&M/~OtC"8,gd4r5Xuyտ%WH(5j('#^|imm}YxʕCɅgffZjϞ=B5k/њQTTSWW eXƍ;u<+""HYYh?egg۶m6lآE♋˗/'t\EXC?{ʕ+~{ndddTTǏb /_߶Ξ=׾{ٱcŋz,}}}++/_ .}ŋf7mdff{=yRRRGX...qqq0?ڵk]]]aaa4z/8fB4~Ggg!vzIj M41G­hc6C, nWF6=i*1 ˃;w碐Z999l 8yZZZϟ߽{7Ŋ9sf}}={+Xb=s„ ,;''gaaa[no>XpKKe˖d--ŋ;wN }&`nԨQ+Wݻy;wYѣGl56..֭[+V?.ebbbn߾=cƌݻKYYYFFfqmmm3eʔ/^U555؅9ɓ?m>'G ➝*$M1{t}MJ8X'%7䙒I<;S;^vF߱JߑbIHShY,/$b\$p8#y4Db?b`A}l% =l[[[\ʃJZH؝hbRѣݨ SE>R) /n3P}G/@}Gzٲ2#,k֬9vXpp0_ܹS,wQ}@B(K/6wtpp;v4hi:)))~~~b&rÑVWW%jHHHw^SScB)12HKgggjju.\8i$ڣzfffD-aѣ}/[-gk֬YhQxx8M =r SSS?/$zъ wd'ceet7 GKwD#;~HE#G}G/f ]]]=w\3_2D}Gy/000af}Atuu-kn#ݿCIi,Gwd0|f0~Aj~AFFƦo߾+5kKغ%K|tK$i޽vRPPض|h ez(hB[[ێ;"##Q}'Nڶ33^ &TVVƍ{y۷o_%}a֭cj@ZZرc߽{`ii~iii9t萟JǏa5plٲ͛7/^_9sfffb?NÇѣ=<k.TK}}=*-][[Kp= E<==éK (P1--J122F߱J $F;#\#;;;eddl6Qߑ}G@ 8~8ʵk׮]ouvvgϞܞ7nǗdeeeXiee4g>/++"/\ЭL\hkkstttrr?^355j3ZZZN> ?$555YO>2dXZZFEEI˗_x||< ~?zիi XQQ H~~1cZZZrssy<˽w)//A$UUӧKZ)blllccU틊,,,i%eԩK.c:999XG7G<{~غup?ݽ{ԩSoO & B >_ =|Çѽ= ڽ{ݻO>63%))iGqq1Pz*++~G?T鴴@]檪m۶?~'/_.''gggnFJNBBˆ#}ԨQ({hmmmhhhlldl6 uuuA]];;;+zutt\.tNWWno"SSӰM6u) YYYzzzrrrb H$!jQr7nܘqmҍ$I*..nժU" &ϻ]zĉӧOOLLDvqqq+Vir\%4AdS) cYYY}}}\8qwĄUUUEEEfBQF#;>ݻwgСjjjmmmM_~ 222)))M:'NzjEE6܏ޭܹ%-**222ёkuE"P(rUUU} ***/^~:\zxiiiWtc8'uvvo""l QAZZڡC\]]E"QDDĠA8ꦦݣI~El҂{aIjܢnݺu$>>7o&nva//&I344***-hљ;w?iӦ~ sNC}=*Ų Pb555ȲeO`llY__OLgԩ/_ rD455wB˗\.VP(p*W|,Y @)А8dvHHHgggHHHZZlnQpה)SQS|zX ~#GiiiQ=JksssWWU|!;w ~mIIIMMM}}}yyyfffrrrJJJQQիW/\HDOOBƍƤqٳGAAPOO/&&9sFYYYCC?466u.4===YYY>?m4~}}}}CCC;v@??e˖}͛7h؆ zzzC 9pʐ!Cf͚O&f:*v'U|tP͛MMMba6nH-ȑ#ׯ_Jj ,@kkkQQ۷o[ZBa}]][[ۄ `bb"ǣZog`_%X}|@}>G`G000|j0~`>+z nJrlĔ}Gϗ8 iz# ]  HRFt", x[0|pvE3qCKHE:X* xy$ JJJ8׀Fnhh(--)--....---++kll(//WRR"D;w5j6TY$BM8+i NS|>D}G'''c{{;MeLLLtuu544nA̯Ԓpamm*gXX؄ Z | <̔E5|||9#z7@E>]V\)={_q8р(zp%24͑&w:"I'V^EE%77RKKKGG޽{P2bزe˦MbqWW1Zhذa[244ܸq#ɓĉ 11bÇ ^?uTT\ѣ``1FL9 %Iʸd~BG;B湰YЃ޿JLzZ8h ==RHTPPYG '?ζѣGwuu#o߾z5gΜ+VoٲF:ǎ2dcaa! o޼1cJ0<@ÒؕNd&}F3%&NgVVVbbbAAAEEE{{;BB= Uoׯ_+))B,,WŐb" :8!D!Çz4l۶m̙AAA{L%D<111..p 26YZZ&eddcbbfΜ9y䄄9sNϟIl8СC fbff(..f666dDF.#G \b# 6Sѭkӡ:)` )i=YkkkꪪB! 8}NTߑf૯ױ5ktuu8pߏdeȑ^rܹׯl޼a555OcfرAAA/qM?sss!Y5k DljjzƍkגfKO^^@bNĺ͈X<]SDzm۶D;t)q^I$\"(3q$);45n8 Z[[kjjZ[[9NSSSYY#ΪBۚ˗/E"ŋ8j&`ƌO< x"wnCCÎ;sȑ8 @YYB0f̘DқRz79mڴXP7y;w֎1`WWWeew+ N6 D"lɓ'P@cbiA-7n*PRR255ryyy0h \H PUUݿ޽{srr.\X^^Wu8h*bbbrر[nm߾bt/^ _4ス[09իWggg뻻 rrrΝƍS-,,}}}uuu*ټy@ o޼Aw|y\8Ǐ Skq SSSCBBpw'Nܾ}-T={,kÇp$ kwB!{8ZCxGE"z::g74x 'Z\jNEEE|hIkkkxx nv`-DsillTQQGX, rɃҋ/&\.INNNVV200pƌ---0ZffMsssCCn{R*++% bFZRriF:"pK_(O6?Ka%NLkﱦ(z 5 c\ĘƇ p›7o;V`"]Ѝus`-S_h}uukz[T`XTW8OYYYRE\l=Ӄ{@ P%BA:'^%Mw.B ]˒^i /OBAA5UU;=ZFFt[T~/"={+Aƪϝ8/61!!rb1C0l?}G/ABE'TGTt_qw OKǍq _@SŌ#çr;2|0~_0 s!} ,,,lvNNN|||dd۷SSShffVVV_3e(TTTl+>i e:::s!;v鋉ɥKTKK˨K}/wO>B`njҒ\{8wz>L:cҥ22 IDATJ8zÇU*i` R;777¤$BK[[?ݻwܸq0A(Oknn~U*@77Gު<4 T!ӧO'O ƌKJJ\]]y<ސ!CZ[[RYYb 33#G;w"ٳG___ g7bA+sΝ&&& .$(99}6V屳s۶mFFF7oAvvvÆ vmgg)Ix eīW_}ebbOk$ܹsYYYV  rgϞݼyɓ'_yǏM>T,PPPz,*Dikk"ׯ_㠠 ;w/Ie޼y)))?# =<֭[C ٱcZF|#8pԩ߿4#@1~x(zKkjjZZZ,5>F@ݪ<4 i aɅRDxSL)))Aw}}}=zA7 [hŋbČdee  //>˗ KK˕+W?:ujҤIGEED"TSY_&deeA&===-9DP߱/}߿???ܚDDD\v%33NA.Q,GUQh Ϸܾ}%-&ϟ?WPˇޞonnޗo2'D|>rx˅4<_j77W8qb RȨRGGGNN( Q}Ǟuuuu֡!iiiVJMMKR,ȑ#\Tfx4xL%UHL|ܹDKKE% !!޽{MMMjyyyjjjÆ ÅS7BtttRSS-[&yFX^~ ^zJ@ HKK;t萫kYYNS&,F(wvvvuuTWWֶXr}G*-[ íBBUUUWW7++IZeVYحTquuuYYY^^ަMLP8~xSSSCĦSZZjnnbbbbR"ŗ/_:u**8D*Hdҥp-fdddMls޼yի4 B˗\.믿R0~AB}G===XW_A}bA^z;ҧ>_>|}v@0|p>oiiYQQ1m4ra}[GfaRK,>|oߎo|>_YYJ9M#**JOO/!!aڴinnn0\NNnÇ|T㍌/^LѬY Q/155555矩2jkk;wСC~'ߧ,gϞUVVw>}:$577c6!|JڻdiQyiv+U;Hhoo 4bYrWWWssʮ]޾}ZYYT#o>E-!fë444) 4թbO QTTw6l;pnK)H,Vopbp888.A"JnHSeD pR/ItZ[[I?)?/춶6(]TA}G/>Cxy/000aB;2|0 ~wda#t󋊊;~|,Š (]~b_2Rwlii122\j֭[?L)P0H$VyQr>}*++;dyOO |/zo_x۷Ao0RF6mIOO̊⪪*-)**rF}*&.s /@f eXRRHf~Y>>[n(((yF]]0fAA;֬Y믿Rt)#-}ǚH fWWWũjhh<: *B:bĈӧO2Uy<^mmΝ;CBB B4e???I&YXX 'UL%S|x{{oܸqƍ#a>QVV1c%4=zYbb"<4$QLKKKOO\xٳgqq8ӧO=zPa„ vvvvvvXWpBF M﨧(᧻#G \bO8AJϴY,",illtuu͛GeUgRADzGH||ǏbeffXY^^$ ʨȑ#Q~eee-^8++?,...))*GtkIa R UVVvtt ;}GsssyyyQx{zzmF< nIAp׮]beeTYY ޽Q(P1_zhH҂kOt⦮njjfvV]S@.Q\zȼ<ljjzƍk} Djjj?#^^^~qFFFSSg}}=FKKkijjc% iiigϞMx#G򊊊rss!B*D,􊏽2tvvA,Y @n.|^xx8 999IIIs%" ˓'O캺*++---߽{ԭR RF{]r̬ϟ֭۵kСCmmmuuuaÇ P8_0`5kf̘/ R/ CCCCCCtӧO9rEGGJ1 cرcǎ;233|>l @lٲC< n.Ν;ᅴ7k֬YD?.///p6o"7o{>H$>ԀYp\ܪ$ 5558AweRǤRP)&bQ|mmm"HII  HEEǓdf$eEEqlhhPPP;nA\nSS}k%5UUUݼy??/<==Y,Nj1bą >E ^xuFFFuuٔ)S>9 ^;2|0#0|v0#0~FߑsY_/}G/ #g 7?lhhKJJʒ:t(ͮ044`a[dɇZr/\ G"eSRR | {iAf}AHQDWWX__ȑ#}~Ee{D*Ri68n*++{U4ײPfw\rd˗/711A?{1c<<<|A]]]ѐy{Æ )));v8yd'\_2w$jjjjjj0ɓ'I v֞?p֭!CرX[[ٳׯwuujjj֬YgϞ/_⼼|x555WYYyɛ6m277'# _2w$rH5]]]/_ (((xٳ !4;w="%1b{ĈK.UPPXjxbD"5#58x}jcc%gRSSutty<)SJJJP}$T)))iԩ666BRuF;kkk333X^"P… ػR-Zxbyyy333l:P5@EEQ$9;;߹sǶ/_$!//`P8i$R;%Y_&Rw$#Hdbbr1E+**Ο?"##C%U)**D :=??g0Ç XΟ?v'NL>RdddgΜP]] :::8s(ZVk֬'Ug 4SVVvݻw>԰;;;b$&&b4 Ə|r8^XXx̙'nٲÇ6 RȨRGGGNNwDݻ7l0Ax=ZZZ&M8<ۓFCAuInn.Q41//OMMmذag,#Gr <)CIbӦM/,,|_bݻD*.""" D KKKǨ:#}]v̘1;DRtttRSS-[F N>66+ATTT~>_PPlQ6lذaÆe˖1"44T}JRi"E}G,.]:qľ}4]]]rnj*bSNDhƌO< x"\ǹ (N6-66V(M<Ν;#FXqYCy5QTۂcqvv믿O [tiDD\Ӆ/)`ٱcGzz:͛7oܸvxIII0`Nƍ7o600 e#}Ç?uԕ+W+… /_*={/_'IMtt[obΝ;tP~IEEEC𾀢"|2yxxDEE%$$L6˽pBBB91:$eGYrc،?}beeuu퍕զM_>bĈzРAǏ?s [ZZ6o|1-[+=xy߱7|H}G*$ 6Nwp׮]o߾ %'O@z6jԨ%={+ATc0~0 _0㇀ <_```wda Fߑ YÁ3􈆆JKKuttJKKKKK444˕$*((hjjRRRmƦXZZĩnhh,A)`[ZZ|||<==WZedd KKK˜7hkk ޝN7;GGy-X@U^?K"]V\)=z s!eiӦtiX,aG}K}lҔI3YU[[[QQU'I[-55ҥK&L])#-}G90p… y<l!QSPVVv[R^#ŋcǎ2dȅ П"ϟM III&&&SN edd[XX@ x:3>}zׯ744jF"O0N^^ҘMLH &fDe$lvuuu\\*Vx0 Dzzz͍ x@*[H%vHEϜ9}v|hhƍODHMCFGGcS_TUUyC֭[KKKl6iB%bзE̲:((YYYgqqqII*Nj) ʈ$FbŊǟ8qBO0)S[[ u*++;::靾<labbbbb"tw^DDaAO9rW1^P(pႫҥK={*DsssqkA񺺺b8##C3%P}ڵ)&`VxR)D&$$ RWW;mڴn r}F;tPMMH$‰\DP ɓ'PUYYiiiݻ$:+٤)!LU=R,..Қ={6<-- :ݻrӧYYY} IDAT͟?N*[H)8k,aÆQ cV̙3=ox"T pV $((p̘1 .r!!!aǏwXHyf@ ޼yu&M`HdrrAhh(:_;w.s=w\tdΜ95.Ko"(??Pl!`UU$(4zP)DZZZΘ1W8255J555/M233mllPiF666***rܦ&999YYYRiRg01#$WD/\2,… o޼w}G.+,nKU@A^b!] ٝ $aT] _FWyqRMבּ &͈* hY,Tf wEJ!2..\R466&$$ z RIw%# s!`<g<0~a>Gwd|aB;2|0C;JI?:ɿ۷_|HAǠ Xܯ;Y0 RF&&&G}^O>6GGG''I f$*HÇQ &Z/^ݹ= R8qāVUUxAݻw/55;v߾}[TTtǏ^$UUӧO6MAAaԩDeσ.ѣG,Y"Y^^^Pɓ RFZXX,ָq֭[VXX|2ȑ#0S> %(**rrrԴBHjփ2 ++ݽ̙3XǏr6l;wy&Qt(P|>~ImٲWdtڵիW_~۷o=ZUU \R'xUV)ӷ@zN~~Ǐggg?|˗ϟ?rŋ>}o={̙3TB}}gϢB7nTVV>{,--mBp„ AAA0]DDi4??e˖ Ž#GZ… 0Ν;ZъQ*URK(@x'g 1]Nofgv?ߦ&*GZ[[Uϥ===uuu*ۛ1uTD믧J$R{{{B!˙L l={{D֖7UB_|Ess3OKKõݍ7 oll|eBsBZPP/Hd2\ooo!PHRE"{yyrF$m߾S$QԌh7771߼ys[[[QQÇ 'K"@2dqppP(rҥK^^^'N466vuuv<'O>|HH$wΝ;mllfϞ)41A :::jB022b24Mz8wκ~魷211UWZbGB* .߸qpՇ5}ƍgϞ [ <ʲڳg3F#8ƚ{xx{)DIQ7oBcJUUE΢m۶IҬ˗O0D}3333xzzbWvF\MAfee]̙3GZ*@Ȅ ͛WZZwy뭭wIi=ݬhѢ'N;;;/] 755:u>}:\4u;9r$** >*HU?}B@,)@2 NNNvvvT*U?:W^wssfXW^U}`,X ???++ Z 7ðdpVd <}4\&A\H$ԤK ؏|ѢEk^^J;HNN~իW ?rɄGϝ;V^}̙`'>"'Ot;SuV8agg3w2L̪y L&ܿX;hdI]G ~GUd2YnnѣGKRWX! @Tb#|P(sh pJeMMMyy9dgˆ>HB zjRKTQ.ZH 1~phPZWʔ)S<;#3f8x%Kp&3!!СCFFF,))),,XرcCCC]]]݄G}8~E"Ȏsss>hФāGLuTMPG\>88. Jkҏ}M---8`qRAjVXĨ₞~GMvF$ zW%;(" h>@Aq@AqatYwD+h 4O);;;[ZZTjyyX,666g0t:fJRGG?GT(J4@2$G׿^=hy@$;ZYY=F1L rU###w644wtta5)@ RQБPabLr9@xxx?ÁD?~<99+G(,Y2eʔ (bTB]]˭xnnngϞGGGz{{'$$lܸQx +[PPyA$ Zq̙3Ϝ9z'n > þϟ?'bjCGGo߾LvBCBB8'''GGǭ[&t^d(J>h ;i$sssGGG-(pggg߹sܹsCCCVVVVVVpcǎ͛7PeeennneeeUU/oٳ.Jmmm6mڳgσ`ؚ8qb||Ǐh)z۶m{ݰaç~ w PxʕFճ666ybիjjjBBB`  T~|>_{={_\\r|||JJJG7=znݢR4-,,A,޽>TUV|SRRRRR/ٳg/\P^^KK@2wTW*  %%%t:`mm `2LLLx< h*`e˖bhU+**Znoıj*؁oll}e}Zロ={zm8?"NP⨨ׯ.^ŋ^;vhD gff222VZr=<<֯_I ϋDQ]H(tTWzzz۷oݺu>>>G2(>,H---דB899a?_xq'N$j)HS'я8+Wgff>}Z,kRÜk׮~ʍ<eSHHzКJ%Y~GB5kP( ͛kkkqqqZ |!\Vu4X~a˖-U ={yPPЩS2330!| /(T)Џ({Wzzkd2y<^ff ´pZB$f'&&]^dᰳ/ #CKK˗x d2J~GM CD`0au\iơfr8MԼ{fgg"##>\~z{= 4l\ 8q_WWW`` `]]]رcT*;?`ΝoSXXO?522bX,k``?uI.]hp CRֆ%2FQ(n* s~g2bݻwoDaoo/BՄ~Dō#j)uvUTJ׌=פ'qN*((reeebX(9x`ii)rAqAO^I9={vΝǏt>Og( Lz(؃₞ #4@ 𠸀@ 0 #b<F wD_P\1A/Q_̜9sKߑ|;[MMMmmmCn߾b'O􌊊jiiyB?^__{!#(. 8| ږ&9yWVVVBr>S gѣ&@3w\x+> $3~Gu~fGB FJJJSSSZZР\x/B1 #uQ oFUUРaaaOD#@E5\󒩨޻wI(ɇ,#ƚ5klmm yq}7[l|u֯_?ǕAAAK,Yl?^[[KјL&Bz;h/ҥK.\ !eeedrrgϞbccƾ喖|> $CԩS+W%gϞolN<~7oݻwr@@7***>s@SKq\@0s3gl6L&s齽@!RԸDݵk NOPr8l .x<͛7P*^P. ,Y2eʔ X p813cccH2ND{qpprX#,]4??? >/аd7x#?? 7lSwwt/(/N}}Bnݪ.++knn <{ӧO:%~駯ĉʁ̚5eee E"3g<}ŋ>P(D"Jhjjvss Ʀ8mڴK$Lzw.]T(577[ZZ޹sG$YXX+۷oٲE.C$%%yxxTTTܽ{+..~hBoee{}B{n:#ɪ ő#G|T*/ E˗{zzpMJ$>Kuw %%%2lŊB(//?xX~|||ddB=r<W(|>/hnniii3g0vlٲEairss%IohތSJ$N_--ɐw知RŋΝ;}e˖-^8""IkjjnݺEX 4500fϞmffv%//' BaEEů ` Zp8nnnа0WWW@QQGG͛7 qM4iĉL&PSeTKuw <ĄA5dʏoag6}@CC6`0&O,H8###\RRRUUn:ӧm۶{xxر#>>B"ܣjnllx^v}oSNZv3g=♰2m. >,H---'44t۶mR4++k&L())QƉU쾾z8dgg744aQ@ @2d ENNNDDʕ+SK&)d2T /pl``莵5{033;y+WBEQQQAN r"(:::""B{ -ѱ4Bxx8l٢e_X geeY Tdemm-q dB߱!!!ᣏ>裏nocׯ+ʎ@oJɓt:}ܹiӦݿ{Ϟ=;99UWWkXB( RN'xb`#4Dj:4+WáF@dd;; eʔ:* CCC]]]YlllLLLKؿ?B3gM Z[cooT*kjj˃DS2!(. Y~t YfͩSOϟcooo;q%:::))f{{{O20qD77R-r9f񫯾:u*133  cΝoSXX`ǎ\.wƌC*`6xݻwkhB#6֭[/_r\ǏKSSM6'%%J \j|JΘΦIDAT1#///>>p'O~7ofLLL@@f"[zFK.KB;;d2SSS] ㌌ Eʖ:_CTJR8) !Rӡ\.Q077^A&h4 MR9vuuA%]Iƙ/ddd477"Jt:B@^VP\wDr%fCqAO D~1EĸG <(. <(. <(. <(. <(. <(. <(. <(. <(. <(. <(.. PZĸ=75Z Egutvv>yаŋMMM `̒ǩ믿YF*&&&Q/Xɐ6))iΜ9[ll߾}gggKB6((ʕ+ř---EEEOBKvr~-\DO. G˪wwDdjՔs߾}֭9zj@2dŘ3gƍ]iŒfff^^^鯽x,Ku_/˻;"dj%Jv͵8]W2!1omuu? s.DSŋ .ܿ%KpjώeUϻK2j򪞙V"1g@2d孭fX(xA~~~]]] tuu.hώe>ywGDKVY^3VTT̘1fdd!(ߔ>Mz{{{IJMf}ho>ԪcW\fZR֦M 4-$$䭥P(p\&L C#&DijO600@AA (od̙dE #4@ !@A=ܺuP ͛7 tv`IENDB`workbench-1.1.1/src/Resources/HelpFiles/Menus/Help_Menu/Help_box.png000066400000000000000000004053211255417355300253400ustar00rootroot00000000000000PNG  IHDR(NLU7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:mh  T~ qga~ C{=3ws%ǎEQEQu0\.g9PEQEA8]EQEQ7+ߢ(rgZ[N[{XȔA͙?pRdY1;Qh4={岔=!ԨQeYa:\*ľ˭YDgt(mfق>4$pPhTk4'>dj\.vgϞh4_M&h45jr\&u\*> 8KZw.cJ(gk9q}]y5Zֳ~oc|0U}yCz:FGFzl6ۅ jjjlmnlBT*L&d ( JJ

jW#1JVd  V2yʮVE݌xQ؝YViII`(;;߸ElnF HSVQy <['<+O_XMEQ;/ـ !?A&15,6^d2qRh%{PISL&yH`bQ U>R)p6Z;C8BB/ EAU*G (2]>ˑt, "AAE#Ɔ55 "fhX|#' C7]@` ! J c.ժ{=rgl6OaƝh8,)}XR RoHϥu3D@lm8w^X`i>ا#niț.4K p/rIMg(?<}&i OWD(1"Ü|ÆL`6l2uyz`ZpyZƎ"grFQ T٢VF `7ۯ}cPxCF\&m!< |__.6әo}odAQTxj[||j(Rq\Ukfhx}N T˗/+ F#ͩVh2U*nxT#IcC9jgDAljuՆz[lNjnF(538!+ߎːV=OÇx` xE#E{):V?s@c( sNA 'N5j`ZjiRc%ƌ jii=HfE1 חeYIQ!DPX,67 룂(- -g1NQO;(lPuX uF <$X9V#?_1~aa-k\7o̝0E϶XYrBקCQرo#?bŬ!VN8djiiSjB HX``f3]o =u{D]7N.J('c ={/l;(9z3EQX$0r< @HFv]JYRIJZV*҅<[V,¾*K(gK7SCQo>R,GV>m d^W\:EQ?CO`ol4О.-$w8C%vd{ՔNQύ+AQ%p¾EQEQihh:PEQE,YEQEQ7 ((nxQEQE 4((AhEQEQu((EQEQ B/(^EQEQ7 ((nxQEQE l~mF#nXm(?yh4K}]O?dɒ>,(*8q߮P(nXm(?l?C_'cŊ,ՙ'8KKKR(NdC[U* EkaZ9 "!DEk  R(/\P(J E O`aJZ n뭛{G$EþWX\S EQvs&(cF h\kt:[@FR(7z; ލe}E9EQUaF h\S'Zbm7PE]o4DQ}c^ml[`U&g=^GL04tྐZ(-wPE]o4DQ}ۓosmwjU&֢/`R}-\\ ^74 ^޼eo64 }G6*,߲8/+++?#kèR>ѵ#YK(hEQ}D-u{<.m}b@4{~5W'gn-5ܞ0R[~Ư~Kn'| pp3jwnWxr/[cTz?nٲe˖-PڬR(J(NhEQ}v'Ի_bn6U}b{үNZT&܅˷LjV otom ۗ9x$ȷf;[p3;osyOӿ ~&%[l裏{:]]Y)-[l޼YR(7xQT[} o4C5ᑿ`.;VF8`,};|Ԕ;p,7(c>)b,`ف#n>ޝgTQ=3c8'{BuphzzU EQFo'AQ}ff{۞;k޽wE[_|δc~}my)уQ'kn 'g7k#z+1mJ՗BQu9;_{z70?{_~mOgq򉁑i]zS1}p^ (7@inv :*=5jԨQΞ=o߾*Wu=kȑQQQFzzE)4:W-x}" pwhni͊~ ?`7Z}^~p[_pv^u۰ݧN\4PE~k~;ǚ' Nrwh˖=}7r+V,_|ȑ[C"%9r˟|[oK(hEQ}fFxrhE>nS!I?c<D/七LdG8CJI9ww.s@gOxz#Flm&|?'=bĸI7ZKS /RE@#;Fr,b)ZbŨQܸf#h\.Zݒ|˖-s=ct?=<',YK(/T@Gj{ta_{Gەg, &76#t>׫HꑒP EQU%( ܰPT }] \p Ks|mGcW= .iPE]W]^6Z._s5I3^ m6fVe0C"f>q%^O*$r_m{cJ(޺ LrAQʀ ?%(]^01PE]WFǍIk]wXJ(z  ø'u[r_6uaߖBQue˖뭫xQEQEQ@QEQu((EQEQ B/(^EQEQ7H_NO?o%KI7q#UREQxXeo{qևϛ(L[VҮG|x}unn쬪(Ͳ;6r5m w mCREQԕ`0H_^v+\zPJ|}]FVoO(L^,􏭕d}[IG}U(]aKp;vIgUVPc]zdMz͛׵(o>hX8#(J\β\.^d2VkUUU׹}1Y5U6Dx%ަ6=up ߚ(dÆ cnL&0 0 n>#HQ\hἥd(/ޓ]pի:7| <몇;fM; [zRɎMPEQ "-@e<2L&I^= k\d2RF}ܸ# xb]2 sIɜ~IgܓrW8p6^{wu/<^FqՀ~ZYB<yy9TgwoXCMԣФEQχ`#aqFRT* kر~ Ǜ!\ZzK%/U\|򥚊+u{("TB3" ]S %1Og\+fD(^'3421 ;TSYYYmc+}ǖ󩓶eg@hװBo&uI)'AF`ܠ7SEρXJ6L VPqﴡrBKFCB+#T7_"XOm|eJNo?@!6:ҭk|[\IN|p0"_w|/;gsut62 oNJJZ6!lCiBSvōU ?{ҪyIj:6 냷9 Hɞx'r_{w=}ѝ5ةMJQE|H_:Vٳ_}Վ;RSSm۶-77… \O;g"ڭ"*={q-vuRa)L˪,^k/4 xr7a?W>0#õG'wcu'?{8΂gO>smʶ^.*o{MtVC흑"Mv߁J;n[+=*"`^+ 0| rׄi#KNl '3V %1+* pQ"`GhRB"_?9]cU?&(~&AylT2g|T+e!sՃNxh;tI)Q,M:- _@`rX2fv{XSNYnsd sE^[.W1~ /s/= ?ƀ^}{N !DjaЧCNOx"ac,|~)` XZ[[[-eQQQnoPE>?ZL)e~J&@Wr器rQmV}vZ>zݏ_c}}}H8h;W/,;?[(Yɕ!~{}}6 (}qտ~ Pɲi(p>7i`__y T;|}}}~qy۷}>ú%]uf枳ȕ ϝ?AFor%Rj|>j?߀!:|MMMPi>e˖y<}l\ Ԩ] F#F9;=l@Q3* _o⛍2o1ZxF#k+3-4j;ʞ>z'|dɒp u]3rECa dimF#45`48sKp ۯ8|#$S@R)[WY @\)Q@t׉dUm ;k\8hG-I IDATNO]JgW](_|z}C|嵭 ?uF#eۣ wEn}<޽3Ƶe4ȯ&>~yNI.r??9`l(Y j-qk+T{]1:6r/l(Y5]v% O B[5[̔b㗙ҫ#>})1a-r%YY67q(i] EQOFig lh>*ׇ,+BG6MNIH[aPכOv`/rOЫ tܪ5rZԳz5i~ۤ\ȴzUIzAMG?huĩN#W}}[D=I\YHZVZ7tleI%_9l{'3WW~YlW!a#wh=lc׆i5uoI*=3 gd D+98b]Fa8Q]Unh2'8c+p#Љ@ 8giJ,71@(uԿ8jYm2g;v'vCZ脤hgurʛ@Р=swP&EQ}D(( @0f[w[Q=IEʞH&vVk7rzԱU{[79d}v0f8eJ8$c6#֩3M)SM@龽 7:)m39C},^fn3KSg<(y]BO|͙ܣg1( rF2*L2J9T  }<~˖-}[? I =@i!y^#Zǫۭɯxq攧tJrW-8=R 9 ^jǚ, 7?{+rcRϼjuŠVͩ0%(! c Rs '}; ?ek~߻KK>4kZ”9〠{#7 d'@xۺ5mMR󫻻>f;ii Ru8J[hlfGm6P@'wV(C7r({ @!0, 0"GtM?(IXlXTZM/tqYm7gwL[dA5\{=5tMjJė@ hgDwb]p??#kzxY{kgw;A;qp>aE#DFCrVd,ad`,x|)'K=ѸvS;g &(Y7g]/[ 11pCQ:%6zzh?&V68 )ռdU55UEv~+˻- 'ClXMV9FNFQ_!DQ2P^S4{9> xfqaF`E|zf^J10.@sBk.zVݵ EyTt9fİa#Z!}יu"T;YFURKLMQu"#DF!2CXBd 0fC/GB΋qO~q_4}3%Ny)bFxp+ê ^ sήI_fgDvb#s#hda7e祍~ih>pn5'- 3'mC}BfFQE ! qtqI2e/HQ?[j13j]8ƙf;juPf^ ֽ/%s> ll2!5Au/j[ Wu^EQ gϞGӔ~r܈( G PkQC /z^Vg5R,*DVƏx){H5WZV6]E" EX"gFCl=h Ns7*) rQO(Y"ݤ)(zC.(BDDHǃͼ^v^4Xz#W*'t{ S2!LS24(y^8bJ^/+'SV#_ B&EQ -%DDp@<|Oxl*¥*:4Eݤ8Fie &Qҋ)~DQDE8AE ǿн^v,,-TXy-kMQ}g߾}v=00bpGQ*reY\.d<[/Z ,K;})(Q$Vxyxq[m"}!BCC 0n7LaaA0RcMPEQ"DA8hEsvY4~.d20RȲ,˲ d2L&uzd=1EQTUU4فT dv xEBx5U=p&#XңJRT(a\6nܴiӦ͛7oڴiTm$/y&6ZP7Wu}U]oD=Gthƍ =|ߦT\M}¸hm$db\;eщ! S2w{۟'B6]a+/#d}SSsU[7߸H:+m\~*ie]Ѯk7~ !QXr5έ`Ƶ:l߸~=0o^v*KVBȲw_( \MΈSK^O,=;X\ǰ~R'("'<"xa+ Dg+]E&F-|~_;+ΗzCh07pd7^wϞ8٪+V݉K7x:U/2/~ٮSSSKפ]l׽ "n'˺sVŮWUl%daIae 0Pp }=A@_^y!YU]66ӊEQ\=c;jsX8.kCe.qKjį3V(=ދIngrb/*50e/_5p k2,Ia2E 0Zuα@tP뉥?I5BBFX3`²DNP#y>1cFXXXom*8 2&en/>u2;L-W 'zOE7fommP(l6\.%.s~|E;Ϩ ry|`^xZSrsp}<=3;wxDgF3~0\mEiGkcў#3fְ0"P@_!`, \W5bXFͭsNU5=Twy)77הo{"C5,`,ٷmL𒭹.'#h882BqdqkCؚo;!gTUt Ƴ%a970\hto USс3aB?3 J)i߸iQ.(8gz[pMy^4; p7>g7#jJXU[\Ԭ]ڧIevS 'K~wC^W}}!.(:etǖ7וkZ|?8A >o~Hh>WoW3GjOޓw-;Eԕ.1/???׏Εލ_Т skO+9r:MdzX{<8o۳=[xHZxvnhhE~eKaΚOzl/9}Y=^vcsǍ{M7/<*|#}91R?[lں1O,NowS4sǁj*p댹S4@SyYG=OMUӶ a XyGVCe5|?V vqܦM6mڴgϞ :W,3-LvQŒ)dGQRPPp6lHLLӟꫯعs3gFb| ^ϩTt\?c } igTb(bqZ4}L>}{{Y yZET $Պ҇ I޶͎t&QDgEvow&II:R 2MH.PELhES !GEѐ h(L7eNdX=\;![Sey6'䘜 $7FG@SmEC:ct@b[CO'V( fG=2jt)(AoMei1z-mN$JM 5Ufk@(EC#weqE:T6Qiss!^gh@ mLז/Nӻ9BWlSIy]ҪZє=$hm^Q$h&.h׵-H*NQ\ )hGVM'md]q9vQvgn Z{e6DgE{FhH+hVD\u+3c6JO)6BǀH*m`B1, oy`W}$;-]jl1բXӊh@fZrrr}rns~{yu[/|TI3²evw}kӦM==V mxʒco6&yޛzkӊIcV&-O;l(ˈťe&xqa*jk{O7|SVVvС?lؕ;Ͽ>(bcKTr*EJ{y. $@/jmdv:;!q9&t)ŧc8)4]Y/Sf4i6P:Q&E:';mt׶8й}@\]Et&/uF6H)6x^ 3PĤ(5WtF(V\ *Ӟ0T(DBC[,幕P$d*Vfrhu`m_DgE:'92Ⓖd=Sf7ZWS EQ,KA"nۛeO@t!^)Eb(Z8Eh3O:J=WmESF4I] I Z@V\ 1hi7WVVOۥC %V$De(=Qٕ{ R4HfZa8##O ՕR\XT&@iCY KڤjCq4l(JR<3zЦ.NK.t# kksҷ488}ZԆ)mz:x_'ljׅ##nl5F'j4Z&CV:#}.p햂zo;&|rF~AquUy/((رcŋOlEԸVz[NqeeY~k8+m*$TV;u dǂՕ{?Ql|i꼴5 r-VZ|*#'X=(Y^41bJNr5MA(g?Y2u[吱d~嗧NZMuwj/m6A!fgM06GZNtoMG֡}{H)kbۣ& -} -6.i*Цdmvdm0}!oU `f7@а[뺙[* QG,͘zf{"]4 KH aaKuߞuMS  cYqWvBzF(L 4ײV=r8ͨZ6yEWg¨ ZIHI+FwHZ-@3tN  hhiw;\{ ݽSq|,|3iN e. Uz! %Ӂ}tQB,uhؐ)dfhR^~|fxxpTr5%SBBg̏`4-fK'2hU п"gԟt)!ϔvXzɔа;#'S-{r G.Ң$?wt Ieտ9:<=81eŔX] =v\=%Z ڿ?/un>yi{K끶8cGHuU끪}f!cgLSf_⴫&M\ץ"#DF!2CXBd 0=\ؘUYYٓ'rMuuSbE 1W>79#Xu v;ܒ f)g;m6WbٳHs{١Af=[~ܙ2b5SAnzG_ϏD)S*e~J P+ժ#;*.=z`7yؤ9FANf %dhlll4Lh7E Cq'|:$E{O^UYWnjLyA=>ܪYqg|j6 9gև~+mLYBP(y 5d+YSRxUw6a)ib<~5k!@I\{Jz٪}m7`I~{Yxu]0.g>W ea6b'{quhy*[]i\ ǦCwׄh =vq|t ÃTv^4}:= nX_wsm3f6@tJ^eee;u ߶grOܳ΋Ul6v/؉3rW-^UiYKٓB5x8f 8W ±gv<(dSz`Â'%E#uռU@ XP'Ih?;{/&z+r,ԤYNqhG{ 0!10pL{|i5 GM:Zn1f,oР ]},UڱC9VIi#W@U3e /\Kʨ`BB||{ꕒdgV:cy cAj~{=@ԬGd}TJG0d6QϟՇ҄J*9 G[Xt>5vU:~~w{w)3?AHt皇'yNy s(Y7oڵY }gu~!m|P`u+ؐQ:vIwO J"m,o~t{9ݘv-Z[E_51lob۫TOM}#"hP¡bڞޓ0@ƀ!1dDƂAWxdӧ׿2dHU!:@;O\[ @ɏ 3TZ:}~^]]ׯ}(,H-jIyvQ,޿M2lp+&hj|6ksV.eg9ul?\}sjrPQU>** (0(0/l`hj魈5_ a/eSW0b+L4)]#nX~]lz\=MLx)K󠙢OtI+1 339؜1||G;ӱZWWE˚7~ذ Ƅ/O۰tߠivKLI҉#e';/řUc*:)չÂKcFOrhaN~͂FM׭t9fy9Vůi׌a+H碩1av&Eg-?hu7>l\0:8l'>J``{lypp@Ѓ/e5lXY{4ܺ9bGxXZΚ`LjV|6&%_}1C3sJWWNϊ)3};OM~`F({5W}:ic?*ܸQ~SjRWħ'찒MQ1o@o|+̘6,jj0VցMFCX-u2_| -D16X>vеURÇϛ7JF d*׷m{Fl@QژF4mF hU9,ɢlRb !Yj7P j>Tc ^RMJH%/j҆*b+DYhve=Y`姫a^ps9|fr|Jt9F (ޡ\Z.$өʿ;k|Y}_~Rb #c].)%ZD6].8U?"c3oub1z˾Ι{r&պ41^ZkUP75D*I$Y%IH$R`GO[y'?L;:n [%7V_TbÃ"[ܿvjKOiƐ:f3u?tA4PahG ;QD_2dY*#xB<`LMT̴fШgc(eJ%J!QeR@!˸8U[XUpG UJT| @@ `0!2$ JIL$ ($ 3kلĵ[3jGH"8PB8x8 Lb0 O(LET%-IT%"=Eg4L0f<)D  wS-Cঐ)l~ 1/BdeJ)e%I%BD 8y Zhy%`0v1K!(Qq8"DE TAf"hGuu?1@ůFX.p$2?IR?ƌ!''@ρuq hS#Ԛ t%Gψ 5ZK]7w;;{f5\@w/Xx ̌`0G/|yj^p+utMK}v+N߹WT*}ul[V:ulFKml<՜9-RbSҝu:rYŮ{uBVmTY_n{J(Gs1Z!s$\<}ӪXBr=ߓ&_~/ّˌ ݴ5ztue*`\eK||I|駓z1ԚN3;E@)b'mŸeȄߟ .Rn@ݩʹ %>x͛7`0DFFN-l=[Oy\+|xDruo"2Mp`\>w?`-tt֗#dN=,~("y}uB_*2E*2X.J|Wfsyȼ+A'ؓ0oAl5$ i T¼bB#.}\}]3g0 /R]nMP.H^1Q~Gه 6K%t=/zM:Sבŀ́Q%@@?r(@%Rp$$ITɀ@ L·ݣGʿ;tU#ξ F5r!Hn%`0/gnx`ҥSҺ@1V"%w=x80].nK}F450%dnlڭǏ7j<)kvvlO!HҢn4ztڏ~}|B̲d`/oom[ibW B( NeIAQF9kf2rʈ\q%QE.ySeu1|BdmMc6]QRf|yJcV﨩cgųir-U¡*[or?[~Q`LI CnQ|grdP[>~,l>NL.tyzkjA,S*Q (˔Q(_13C,`0fx(?m@#^IW_ZOL>&{ZtAZ_S_Zyt9*M@\;v_lujQFH+dl0>Bn?wIII{ ;j;|NCdкjEYRkY^ ՎZQk4ZWuPks&hS -Ӻ8(LERY(nI%"/q=`0T$>fW( oEQPLA(Adnʹee5g0 ǕxiCr1UdI&[B$I$I(aJ2g0 ƕǕx1rCDA  P`0 _K|U!<;x'G:`0|'f  Jǃ 9 4om Aj|R_|ߊ=Z?N?fqUBOO8#<_1 Ocڊ4>ӍjzF?u_:ZpĘJ(Gs1Z!'3 +_cz6^,bS/;X+N | oƅ;zzXwB|P92;YD*I2$IEI(b@R3;{ 2ʵ^{뭷644(?o9sL*l=kOy'[_o6UxtAP̏6rm*jcS^ ?-> fi7?!+Q`1_Pu=4Lt8*C@=2D Q P-]`0 _K|Фeҥ|KBCCo).{Ȁ=+=HJ}ܚ\  dCvd DH w(@Qck܈ĕBȢn"i)u{A,S*Q (˔Q(3z`_cx rJBHbb"!SLM1V"%w=x80].nKljJ^SIa%SFjr'Y;Vkq=:XM@ڽӌM1̞z6AdH~H2$" QH( ˗; "_=:ACѽaK23/|B9 Rw&l\Hܐty2͇w=Cjs_|dX5=d4۟ZIYF;}m8<' /+ 1~_댗4>-)еp… FDQM+iؾǝ3QipP|XXXXXdxq 7A0kWf/(@)( 8G%&&N)QeWmoV, rjVL`Cb79[Vm;جDzx[얃dm;Z-z'[>68af;{'ue͢ |hk<<l+;݋h8T*Ym°'vo"+u:k]_Ä7Td H,$QQĀ8T IDAT g.w qe'.5FEE-\pM76?;nWU:,ȏ%'\27ut4gbԑIEz꺲h %e\$35GjʢиwSrև,mm zrR8iKjNyRxԷ.O[PUPýDSVR\][v &>=Zd+y47egϟXrVbD P@&H!ʐ %+  {Wʕ+9n8р˗O%``iё?8z `X{G &<g4kڱ~yV{]\V'!#{WbΣ ±ĸP0Ƈ ě_mxt.ϼ^d܆O ]㺏 dlX|ͣϔ`:r{8mGѱS 3*60 (eJ%*CEQeJQ(CqlN\\0+[ko֡~9s`٪v"P^ܤ}Q͙sP N𭐎0|ߪh(Ҁ@yztxxxd| ؗik2 3ÃUvj]&&^H㹏 ݢ<]t@j8@[ W!QPJe"ɐdD!I,_}-۷l}oZv5"\cߔǹ^Y_b{w{ ![%OK8EѮt[3b7kh=iܜO{" x-]kz-4uw-4n,M,,)f1^ PfĎO*GL Y@[Cجi4rgjI:;z,C f.?(\M bэ֜?kZ&gTH,Ht@D$QDWq=W?J-gfʹ)ƹ@T T3={ltJ8NQJ{pkѴ0]e^GF3Tܤ̇{փrZI\:ggY9/}iu!~u^3glfNNNƂc3b|~c+QKݏ%ejrRDqiVO煙{O~aw +WNLL$dK;KS3qݶ#i ΝmHXg{ֳ?Dh%ˀ\F3pjᜳ/K P벋 Yɻ5 [zڸPF%leQuQICݭu{ Y\){QK֓}[*no _İA Dp n{ǶSb뉺ޘ$]i=.\[G;Qz;ߺ99uM8@w/Bv҈iԤ1ֻ U+>42T͹E?2B1<Es~0<,w1M hnS^_|S/}$gM6/+?h37u#_^{,2ǬjG>tRwu[Z|;6ZOa(SP0"ȡ^GCO`/kr+8 A)f=Q"O:dXzio!l=g{HF^j_wK[A)mhȨhʧN+ueT uÊ 'SlKS%LRQ682Q06uJ4hTҮpS(C_ =U>S:cA+>L E: A)m0xǧ/̮yu]R0뀂pJ*QIx¸UDxCrtEѭz R{MCHP7nJ F}F^c"#]"x(hDu]Cmσc޳;j2]azM*= ]M;"݇!\>8^մqNifA u]ұTzJ.mm7_}g3x&_6v߸v{{GnTa;Tux옑"asz즔R%+ ˠtl+{륷m^_~\,Dթx]{C_y|8x.bww5Ǘce(j}M5VQ`W"lijzMz=mAYQ,ŀA-@wE(6R;^l .oH08,-EȫnvT H`饔2+ uE b ,XdTxgRA)mU@;r;,mPƇÊW~NhK6/n.O]@:hakR2k)R  iJz}UȨɌdWj(}'W5-L\Em-ZEÞjCqmUy 5]չJ5js[ JrWVc6Sz)5Ԍa+TiWU6cEPV55}M;j2R{CiAŋRJPM&{^)+jPe*_p񺕯填&0(]-M]n:ܔV+0ޱQjܔRVߐa4-dj6+jƣc"*^W]Vh7 V- 7Hjg~QPkک-5kfP+Zvl܇ dea^c+ppWAENN?g`xAV3|tw8'`иb#`:ޭ/#,XP .bBS7J=Kg6f= b1фl{e'~V 4``Dp]QO :5 )K6Ll_yS00*ˣ3y TBHdj pUw>Rw@Te`Q6:A}x PۻnU,! `=2jX:k˪9Y@Ȑ@:2䡤Ytb>6Ea@aJ$!KنS 9m{?-L\EwxZV9qilPԝu!z>ۊzӛuf"k <6""aN>Pvﮂ&@(ctco4}uW@_ĘhJp O6(_ QLN==rd5)9@yB δzV?}0N (Vl-,@haݦTJGH{,P9BwPn#c#~Wa0Ɓp Jǃ O>?'|r|Z?x]K9؇c;Zu>~ O_P$2Bʟ>GERaxR@`4(z"ɤ /AQJ $@nnvG cPMvvބFԖۏ>RWo:jWنȍC<^}puFT8J+ OyiGJ^0F( @eflB&O=ZZZ^yX'Dܞiƍ;MƢ<7TB_ʇ4wGNk {]3o5'-ecN9^:>(]C&ZXmiiiiiijjx,RljVeTR=KXLZ 1yb7atqh󋞌 <H  (aϫzyf<4J 1p<(*p<_?ؾ}{rrGhn~lk9[/PkEm߾% do wy" Owvv:E!0=z[g}nl֨^ƣ݋ F?N5At[;OlBs9muwnK@[;;۝닓ҀZTA㣓&o_^#o|Hl=/æS?oDrlXb\rKmHV 91>BpzC`Mشv+ 7j}WZ9W^+'F M 68]fה&ftWIN4?vGgBiET:~gW~_j5wN.yɸpK3"{ɿu{EFZ /P^q흪+L%@wg;g[szd5+NuT=y=Mjh@7lm}ČFyΚN-[ʔ֯iؚF}7QL'q BxBx  x(#73\3Uh?_W sJL:cёgVvD#ѓ_晘ʅ0%>gϰ >*=c9{4<3aAx |F#Qƿ/Oj|J5ocyFqx쮎d.. 5\ܷ*(6%>yXxyFM/̤n7f,a Q;,󂣓3Ϭ Pθ0-6<'*r)cS]Knцbc~4*JW3t /^AW? hċ9iw(w DZc/ EMjI&m3Zjo6 JNaM7Ku@gwIII{~Bt:G5v<!锂L]Nk4rA=rAQO]N74j_I.g߈|~yTzͽ~ݏ wíRQ 0*uCo@t9*.S5[to{ϊp\ЄU4)𚱆ٸ78>K[uwƦj *)3fq}GmI EIa~zJl_ϾM@$$$IT1 )Ù˝=`\I| ǟ{nr=(oez%Bp"(C(/M`0f!E@9V&]'ʶ7z.7ܶj[ىû!t֗"D"rxN!7v{v޶j˾eb wybXOeV0.72!ˢ(2(B!̸`0-77}r /иwCRz~^UCGGSE3-~f'MIiVTUZ'jh}zHur5 OH^R^RΊs|l*LM⑚ |R~Rs[WKC^3!2$ JIL$ ($ ˝?`\K| +0]/N*4@ 0R_`ޱ6.,,fsJ^?@9 50>dn5<y0#̅kʀN2*N 0 LSۥ1l/(R*%-IQD?3νݍ{8OS&wwcWc,!'|{+b߾S=:Uy2BȪS/b7k_=7M ~BBX}Ni 3t^oXZzgg7 <ކp]Ȥzড{< a3Xӓؽgn<)D  wS-Cঐgqlk*2gDŏ^DU[/O]طn+bABYW ]YCdf:E H H~!㞚= 8seȦJE^5SI/q| h"Y/S读vR ]b4|jV' -P~g>B2+-Aq>ak.}bqV{$sY%nR Q[$Q('Yf\b IDAT,NZ많uuq}.4{#?H @n;ySǷ\#=@O0/׺mo{Fݷ*M{N [e(.)Fn{c'?ܘfq^xȤ,]nϬ5v:vmk)t7M'Gg/אvda7yjGkzoX2cө[|^"9^s\;Dx[g.[z*.K:O|pܢt/,'M1$j1.oGZ|yœ&K+K^|U uǶ̓+\{!V&Ei@l7#jzO1Yl3?;:;L@nXns.9k;nОB(iјpB̲d`/|懡5{6f\=459mK8vpc>AeTIG^wsKʘBH#@T@YvT*$[yY&3 %a(yT”zLVdXzԝo ϱ겳 7n8ԲQlSlz!#dc R2@f|\4xg%fy;J~ @fJ!3!0%EFBɄj`ܤF BD]Qkl{$xAn,u8*&忬=5]3@jөi?(D}Gγf{ҏ')^lGӎJ숪[1-xPPA]W'噅VZ޾ m)ɹu]%ٰ.)` -%:q~y ք%) @uՏӼ Yk~IY0յ=!`#/HJ/,jں\ @8>l %ɑ{>[qWl0/L^v뻼ݜ>G5/%ZP=}.;# x'G0+:0SCA;Բ /et8,|SF Nk-:J/ϱ={Ueeݑ^]vdZ{2M1ۢڊ"B-kh* skWBkk;sVSJmοd(6wM::Xn TCf'RvJ; >[nvTACs>ZK^I0.#'#* }*1@e|Ў .iH S" >9:-j702 \ N@hï^w*I+E,i1?>xwchK0ƅ9]NǀtG>%cV OoHyEJcIaFQs o?[ K(zj_Rcz !Lmz0x+!9 ϟ u模ϔ4]NNEwϻ=Y,AhDjV-Zס )% #A9PC. c^ I@pM^n/T}zbsGn'Fl }( hb)' k#&- pG;OR 6,Y^\m1W h^KMY0r钷t@{qa&;`gbtC?y"W`$>fab 'p'9"ssɂ_mԾu;+r6:x}ҍZ>vҀZTA㣓&4+t훝#o(~Lb.(#y(j@u'kyvefRxxֿ6 7/Hi-5W,y^WPf?ͥi9 "_l$J#ϋL~b->G~lxx9ǰkh\c#÷܋ɓhamEusA f` vs1F@d&fe]MG><>+ nlO~`DRYf03ـ @4qF8?f=(&5y̹{swSԌ@(gFcQ^^`̩|fewMh2 4HU3_46etmI/>U>f޼%֭uќho&/]XF\~ Z)@JVҸBNaci9!Syupn=p;q/A.^8!飞.Hzb,8qy8gޅaAAL;[g7Bn&\ϜL&|004?Dɓ'^WCv$6\v9`q99+[]A#2].Lnr]5=\ө3q9c\ dsГ_ìrJ| ]Ah#]]A\ٍab꾏udcu:G]A뙳#&zJ{(pρJ@>X=,SW6}V|LmYԫ5MȍYätot:Wɯop+`ܲK}a0*e**'Ė@foKn-4cx-s12R  EUʲ(TVbūcׂ ,Xqg^U[Uz5lh-JBU,˪J) ːU*p 1->b ;;nG?m0Yb|k!* JɐJ P(P-`0nJ>b 9jyĿP#YDѐSK!N`Y [WdE脫ahOM%%WT)UUBݲ"KB!+e 1z`O#P _klooo o})yInUm-3nzh~\e.k>bw3(=9ӵ%gL)$@V*xr YD2z`_# ^–~Q@,nEr{v JT$j ;Z<~>pncH+sؠ=LKǥ+ PJ)BREV)͜  XꇱzduBgxO4/mT$.ZpF{7vZsME3Hcס;!DA44#P G@^AƷtv1w1Hk(=|1cAy:zZ Rlg09!<[Z Qh`0 I}0=)u;{3w0,MNf۝ξF3VJJڻy#D@?KϸOG8h^yp<8= G@x$j4\ב f6q@Ԇ.ƸE{S)MaﵤO~ȯ|=j1m7JAyPTVFg0 ~Raxm['$/9KЀ c! ᱛ覾>F|զ[wJNǠbZaM!:@ >֫*(ȸp*x^C а  j`8BxX `L3(@4s(8`0 K}0ËXhxP T [ b0 />0 PEQ)(,+BensF`0?fx1 Y!P 'S*T!2`0`.-RUT*eYU)d Ye G@^U~\.=˝뢦_YMKjy7;DBA)RBQ! zc0 MG@j+*ص` DD\Vc^׷TLYR Y,cf`O# VܾsF=UI8H*!PKT $ 93 Rn{s&%5n+ߒ0|a!'z(+z!u͠**$RJe(P(͜  @YŹ0H4FIl4攵>g;;݃7P67 %!<(ʁ 7<`0>PEsoޗK\C3,49 0ǥiwڎko ?COO8#</h qs'>^1,RyiIrT0QocJTX%PaJ8©9oz @Ng0 />ZGNJZJ9 1G!bVT_ߠF :u !9p*xN ^c! 1~RaxM3t|3PRPJA/ 8 @`0P/FA)(RY2RxBl d2`0`?B(SUEL (h!\BPP04ubï^Dn]A!A CCCuXln2KSfĘł^67#gwsU.A{0GB2O˾67 O7=Q~xз tW)18J4 ȶ'04446U%4vr/6>*jjҟKɸhqXc;@.+#nU|=I}0ËX gO0J8BV ݢ!(555MMM>c=Fa|i^dBVϺli%et,x[ RpcK99w ph0cMNWI Ǿm_4[#'Z*;\a?VsaOnt da^JtptۄZm(lww?;=dc=`{D6ѝ˧Yך7fx1 JURRDeUe*du߳g~3yyy/V _[܃Ҡ ggMeGn[ۖOA-NL^ΪNսy}7UCKZ%c{]uץ ;wDa~BW:վPmaJz{8]0lz.DRBcr]qIIQ\ޜ8Kkݏf-NxbGwmo\%:իY]_CU35mtV~jo!޳MDCh{$;m/9w?HOӝ3gkHl\_uU#тȨ[wի"co߻zrځۧz1GQ`S"Iށ#)AjMLV / X{bOc:FMTHa-4y`f1F7Y-FVK|i@ ȩRGGKג?ziGePࠎBRKv@JiKq Ă^:p8H9<@L <[F)2$J@bO}CbVxNeHbqˀR(JǷ>PUvjRʬRJ;r(4 mr\g} +6Z EJ;*s[z{|Q;Qm :(p#6Y[N IDAT8 M|QK5' /S~uOOS&k{-HVviW`饔Xֺ q8 wPS3! v1s&m:Pl2+%J)hk|ݩ1OU Xcӎ|3&>捳Ӭ7 qI ( ^BV Qs}vOӉi99C5%фKh/m Y_Ox9T}M~R4_C'}rǃ?DIoQI+zp}'Nʈ8c P]LX4aMFAeՉ쨉/bbܣká]Ԙ-}z᦮^x|ǦhBmˡHW?PB! λ~c0<`a` A!!6`a|H޷PㆴtϬŘ[DߵDA,ߺθ9EɄ}@Jn(~&{ syz2ixqi999OOm*0yfHBl) [ r~j/$'`|奭(s#x(?GϿiל{ ¬E"jk[[[[[KK[3wnz9e ~@it#7  IQ"O7-dƼČRcZc7&JuG^^|ಥ.I^拸 4LN 1xջsp()C HC@Ncp|~l#_GhҰMVŁ1s~WSzQYuj{ɦ$a&uva%ܝ;ٹuexr_.J+Eqrxݷg>#hL_Jz?gL8YVEO?4' |z mȜs_iy{›|dxrt3-7>;"]8uty5sLқ>heZ 8nN>\̽Z,($#-Op8"DC x|oM};v$&&6Fq29飿-{sM;oWd׿<{S]Gd-PZvdom%YtF}Ql_o[\3qbqۮvl\A`KW3JqM ˜[\""7ǺGj=qW1[w`{l@P.{(-a.zvЎ-įִiBe'--W1uw`?=ڀNp?5; cogyqwٶnsOyPy+5>$gos۟9Wz_ ia:xyX @őw?>KŶ w?Zk*=gNj:=V-35kڅl~Ux(.[V&}ƊA94A_sN_ac@/Zcǖ"f\rOlϼX/+) =yB,.I?p5/&gȭZֺ}vOgĹ u-5ݶ-[Jzc4͂G x=nc: <L9.i4g%1zUךu ~B#B</WOsٻN~I-jU:k5}b pz3Оⴉ.TjxcҮZixl Z{rUTd,n$uolHiD)ySYbǿ~9S )Aag)=>8׏]BX젴R<zF8zI‘bK`4y$uuNi+wI:3ƍݘ֑̽] Hɯ:+Ȱ0)e;;yGoGix3vi^2%L R˰kɘ255q*zQwS~D(KHsw'zf7K~m--)2+;(zUvHB#1f #h2M (HyM֑S )c槯yuSrM>^ŋG=C]>ڦmc q^|)qcbX1 WK֒ >uW(86D R [^J~鍵=Ua`VܺI|~u~nkӊb;㦣⠝OWa musfg~&zqtS&ceS{Ovm^5rɤ$W7\.|N7| өz axvNY\>7ue|N뇏8]Ae]AHsS'_Hr)cBv:C|')i&4";8a=#=^ST;^H%it8a] 3Ͼ:NxQ].{9%'>o$FbeFx=^>7ESOn_V"9y:2G"\h̔5Ly-7kR:d;=&ыw]Q={VX4d< o7˅LDQS{ߪ1+l~zhU׿7kB8BxP8M ;я::#1M&&\yTWQF.'%}ɊD\eRPX0+$@7l؍kWǯ*ʕ]Y)(C؏R>o5MqڟG*>ۋfFmx{؄&@ /^o6sN̷Kc! ;'aDNOsD 0hF_[K۲oλFn}OI}5*;:4eR̈Nqkj\v`?RInRWa9/_ p<HfȨ[b0G^8jkmVfgo=oS6FPw7v\VꗚrklNg_fj/?Tn4}ÀͳGsOx8-2`0_? -o >*)/Yk#n_>x%nz@A}pk JNShuat k81iڬGc(pρJ@< f>Bl>B7OJJ06=/85!cBĝG?9䨧ҧ:N䭐qӢA)(RY2RY|S`0Gj1 gV `mCb$B(NU$ChȠ+p~5vc7tGyʝ5wA,ۛK-Tkr5G0i yEtqUH*WMȋW;y`$Rb* U,*,CV!\/t곳''{n@ݘ;S;Үc4WIggRx„aDjzz>;U3oښ)֒dӡG ;YnN;a#өǻ6eēq>1 ?>^ DBA)RBQ! z.*^xR tn0fr`r_{ӽs{5>x4)*Ll~bN}}K sfSL֒==ZTQo6ಷ^yGtfEoe1 5=M6ǜAlxVKᄸpΆ_N\;r.V>1c6kEip?,SI;.vgswb 웒x>؊#*eJUխPȒP dC\v+ΝP9Kq{OA `N\K=csoEs8+˜"b~gI܁|u^>cޒW沤%:f/MkԟjbsuQRܢg:8[͘$񥲲!:P.h}Rܜ5#=!);c 8/t\. i}hI|Rbb'J6͏/3_I~?{`0>8F`JA@T$B <@FLH m 2¨wٮ;~vz zl05Dω0g\*;]f r&Й͟G၃tx8cr˥ur_c[@Wu5a_؈J5\`V\]c~ҟsWG ZGN lCvP08KgX L-ͬxyu7Eω|z]d0[l\S^RRR:}7[{kcc;EUTH**$EQdEPY;@fYp.3C[gs0.<uތ{u{§@eEq83!$$  x|ǦhBmeȨD0}KDp/yT:z{zz|o#!nJ_$thoBBo>;yd ƭǷ馟^9O$OyMNG+j-_vG^ڵmۮ:k^\U憜D\ڃ7l[M!DA44j^-F?png}+1Ųmǎ`f| (fMկKGoooq#y5\%Iޞ|pч뻤2RBa zCXA}8C -] ׂ|)44'J{ :Bƒr :Yʃ#HNM/hڰAJ~BmUO@{-7vˮ!Y 6'# zSk6#x2 KMƌyM{rMK)) /˩NZR >Wwkڐè)2֎jTi)̋8/ĔL#lxK/,J1eP -q?ʕ+"-+:a_M͋k !$hˀ "}#! .PxTYFB롬݄UUH 3}-DimapU dMH֡V[ӊ|pו$KDblwPq8?)>ø$Do vg- kukw{Dڱ6z~`5Ny"p<(*p hJ-)uԺ=h];;m۾=i; Bmo 6, HG4rӋu=V"JtCJY4mЋ{1 уV 9ŭ-EmuG:,}[+uJcz:*yFC'u@=Q F@'q]Mdd &$s=>O͜d;l/3^3~|Hyes46ħV6uu8; ]`ޛ%'۟T C濌B2/[ZZ o؁7bf%ոw.H\Ŭ?ST75G|Q@yk9&Z1^=64Uqۺ^6Z}%ET[;:Zc3qw-vp)Ϋ-ߦY~"''@ρVw9pCg0,Rٳx>3~྘Ј"@gv/GL0Y. W]w>RCwK0l>g,B^Yr'm{3FAbDQ.rBMP3 C[Eo~i{'&z"lL}0aޒ-ٓ@R#O) Gyyx2 c:PRPJA Dq<#x\e?n%Š+!+W$vƮU͹9cNP^{!P!? ]lwƻߛ$咆> 3r6^0JHq]t8 IDAT#1'1⊥ D-Kv|N[Ifs;lvn @c5i܇#1#`O'[ y ϶B9B J=1T :i˟Ӓ`0*e**Go i 1? 8Bv9XjgmY܍1 g̋Oݛ_ٲ-@4ѻlmz [[`V0#[0sgeݣ{ci܉B[[*ϙ1caƹ9e=$x3u bBբ>+ _/ą _ƺL]TAG}Z{Fy홵Z ;0<pOLr~$^ JT*KnEQdBv  qE PEQ)(,+Benų6jŋ_}3c[/_^徾AI 1|Itdg(o!eWc.gߠ1&O![G5᱅-MR蠌s!Yz]L!8_|qx?n<)̽'L6y? ɓIII~`0~⠟%C*tsf f=t:P)ɤzJ-3=_Ao0L N?ӱ3PE׃.Θ\:.*U{{KhhN-.`0n&(UUJJYUR@!kp /cAZϸ0! =SM"+kHy8η,5k.b2 kAT(!(*2P( <ӄ^@G:K^O(EQܒVCHG\]w- .hy q3AAU*ˠ*u+-+(T2nrzqܝgJݒ\zt_pJw}םwީ`0 H*@T$Bs=[b <>o_t r!3/; X|WDD`~P %QJ)BREV)= 8h4]4󞅡4A3gΙ={`1c3 #0!hy(h8!iN;3!3g @j PU 1BxUhN B;m׭0voy˜oٹf9 {~mΫ(:zvCr dI/G K=n_̽H)^6Wuܕm{D]10sg5![cy fU01  P J)8Gs6bN-zph88Y]sҔXuTj:l4럿f `TQT (*ˊPY[M@1Fm,͹9cNP^{ `K)i;] O@JѠ3 L}Ne6۝&EkJNg_wsC]\j탆}4~<`0 )d*@I tY[6Ƙ-b '`\?/LՅ,v')RXBv`=l!JcͷnV ]qz7 ĆaƤbZm#t `LJURRDeUe*d9׏gkzİ {־nt`aI#}}`~{mhK}&8D)[~d|حt o!Fc7M}}&XV<@i9(`Ѓ`0 QPPJTPTȀB(5}tz`BnE타`gH:<`4Y`07 RYU[nY%E@14mzfx]{Pv`0 H*!PKT $ u ȕ"o `0n(@R)UHȊB9s=`0 !hy(h8!ij[O'`0 !' QCC8@8 )`0 qOG8*4x^  @uO؊8O7nR|[o>2 g(!AyPTk0n,YM<]uo>gծloرilEVOŭ=詷꧛~Z&  ''@ρV|[kǥVDVחUԝ;qkl^߱N`ҢF̙ʈ_HM"eWmUig>!\`[o\ս,HIkTujjhMkjlME? iKj{?C~5hfX!q7k̎-Z@Mvvf]XQc2?ysf=gsλ7v6b"""""","PJS\B\ r}qiu?q}v}Ϯgc۫;;_9{n%^dqpd2i_=Κ xxKiuj"%N[W|9>حU_LݛOܲ\ܨmo%$EٳgGX:[]>z緄'w7o_ruJMYi7 hc|e۶X }bl᫧jdy!W/Ehʧ?Kԅ >si:!h4g?{ ߷auNQey7[. H:\+-+2MiiE @BbydKiiovӷX'A/wl@m2\]pS0?[iB;+\@ճixV8vޚ_ewuummuEi:}B6>['0ޚŠe;x$%9VMQx|^vB(He\"J !PI![7{;^+* 5QܙޭtsKf'9w&)u.~kt3x׈OmUߦU05o9Dƽy?kҵ.6 ŲL%ggӶ.n ?:22ZۘB'Njo|ת)hKے\u`hdhߝJ6nN^;%ls0u5Z59uaдWh;˅t]mټY4mjyl^o]LMM}g׿NMM}Չ޽{oڵkO?t4sW7!dĬ^ku/.\_`2<ζvPf\]m ͆.~3ih8Dunw {nk9*alnoh01KWC}+z͟e wL؆ kBh?HƧ{=|z垀f0uO_f8e .xpAk`hi0t9b^gqX (f;B18z: Hk;<Gisb[GٙAKQ9,g`t8mm5p^:4X>`0tB\epi2G3:mMx+۠OHh[6bka5 h ! ^`g,m=qX ͑4xlA8){?wt}gCBPphӦM/C_!h9#WkoBE 8^zu(\1@4C#ŀ#iU8ݣh҄|R(ȟ/ _z&c5{&xGy쵀t+񪮮~loN8a4*CV"WpJVv@q4U@j'dL1־aOAn7H#5 F%:v Q;c&`/Z7<ܧ_0=斦άڻ[5.tkUd:c W1] !G1H#U4яBF:G?o !V-3!d@π7: z@4F!Õ.@*\+uH}jr|ůQcBo*{z @ϗK@[\6vQ:rjhOZuMq$HF~tC uY1z_q8^8ͿMLGx 4BA;AU WQ&qr Yokӱ$mTxޖ !4OiDƅ |1zCuAQE#ȗ0?5=UYh)b2WLY9:Tv5;!/$s}wkokK_N|***Gpxhʸc7 @~d"DMQ+J80ΈpHRy;<ڦL;zzM@wnLFh."K[c'جȺPp3١9lI~Ny㱪b(l-Pˀd9|7@Zfv(Jgw]^`9i]wHV?o2@I.y;{mI ٷܙL`I8=Y×~jł:{H~P8vV,ZqiLMp]+eTY9`j*~웙bΧs 0ұ&a/wX3|ZcZgf7מ10˩Bw'ϲDCN-*f <,TTP{#LMOŅG?n Uå&%ene3[{r $gW5EPͯgHIx3&aBB!BxB(q\(DqBBbp} zC \/ 秊5@YΓ]q5##pF-==@xl'?2o;m}hna-<L㩙\E6l΀L-ɲo)vgC6o4\n83q ԗ`zt*-6/bиa[yFmW%==}P(bcǎ?O=+(4M ɉ;jh8~SS>Z}o^*h]\=l̇/L%ہo.: o8ٳv<0>';gleflӰg+dEaY 1;nX{pYr^]Җ?vuWE`Ìzv^ɵ)3YDoS@"̺Q~b; :1 bVl';XWq _ _•Tz6e~w7SVluLx7<19vVq e6`A~(TZECCCCCC#oJv7vs+ g.gS qg1s ljJJ6-h9h5[[XJ!Q_y w`'f|\gפ ' :8'y Z Ͳ+R$/m:`ݔ,c^Ki IDATi,R'%f|0 W@wZɴeH+T7VTi Sn7X1/kMNIEѳniִ8}ꦏVlIB. OX#92vϮ%9$R[1Dn\JXZzHFFWgrߞC`x(\n7<TWlضӂ;Ҧ]H{Oj@֯Zl)}Q_G/lS.Ϩ忶V@QdvNKzelv)<1@qEs4֢r]fܢ=NgMFi2PQm|_>eI=rltPUj7fmS_g U?-N!Ê5ZZ U$ςtu 83b OpW&~MOu:b:0z{oon=Ԫ[{481:8¼  1:?.ŰskC|{'2L'N% #$sA'xpΊ3{FFFF9v&Cv,1oEW0RHлp}n\HhgU5(<^H Pׅ)x-wȨc/ֱH٨ 5LǓB|I8"xx@2֤}GtW1rc:SH>Z)Z['I~0ptf$8Van7kbFÑڴ4V/!m.f8H+Į 6E&8N{CX !a`fMlh*D=wt8{݀wI#ppg8>[GꨌHGb[ǫ 1_GaH,`mCz*f15cXnk%3-Z1DacDsp'q xYOw{@t0XfAb s{B͘)d#~+*Ɍ +Ӷ!Xmطsу;z7BϽphbcӧ>v蓼︦T銍CD^S( J2!!!!!APrL&Jex8-.MqׯX+MsѢ |< MT)9gx^*MTK4RJ) yoQޞG^B4Nq#B1OLQ|P3|XD{,\ 2BfR {] `V|8 "E,lRr.6w6|I)K|IA y~ &٧XȱOi<Ƕv',\EB Lw{Ҥ;M +˿q6p ˔˗IK\I?3T>4I]YW|}GƲUiBdPB(ޢQ!( b1%PDDAYJ'M삋88()EI( rT&( d29UDžBQPD "IAJ;%x RD&TB(J*bՈ3^""""""""PP RIxKK! VܳuӦ=m;nشgpuEDD.a|m{vv:.Zyȹ,;;ċLJPH(J&ȤTJIe\[sμ#--xMMDDd ^Okjm۷y1)wŵ\UrPEQ}6r.[s@sgu1=xqGu2,[=GSf9}'9""W2 P(D"@B b|zW(2 G[[9'$7? P-='筸5`gj~\x`2.7VTlMta ,vE[|y L|c 9.3[.,K 5cط4WsI-G͎œK6t9`janԤ;ٶǶ o4g`E"QDDD@.! B!qBn[}zD";ZnݢT鸰 v;by= ˠa[6߬(6D[ B*0>YjS3 +ϫw=󼻯 "|lO`gU oybl+4[m.GW `ac:gK*7 A\Fq~!  mq.[W[[&xAK98s/b+۠蠥`hqx:gi0zlNQ/o̯[lv>J-)F؛G=qu:m&-/X.ζ`;Ao*̜k紵 ]6Gͣle<""W<χ!ypxa6lj>WVxu׭\yώe/cUiܙޭtsK 8qT[{ƾ:m[唠N`:587dž}ͮG201o;%/HRMI<_moٱcǮeZʊo200?'}E=OթYy{.)T0 F<;[{-]i>۪,mvQ\#P_ߞ?&}8Ӻ3'+C** <]RfcPCvm{A[1-.Jktabt߭9;x@amZh߳kwdp0>%1)'|tn)eSI־s )Ekkq~zs>kv P|>_ 01w`o9iuƧd嗙2ۼ)]j2=[!Պ\p<(P(( GqvQOrwu / sP(̛ihu]?Rh;.q8>0ce(z~r]vzw`| ڟPO4%זos }07p "otxթƿkû=6vZaK4߻Z]v|kk+Xת힭 [k{hJ,g!_ H^*]Hewv#2Dl\{jT޷E]{u_29]{@w|4swq YMnwR!$"'!P.zW]uխn[d"MhyjT=^!<@ƜYiKIq0ݝ$w٫4%q^|{jCKIYycwPeh Ofl&=0|L7~  _˅RU-wsFh64vX]& &DB8gkZD]$۽`)2dJH~p1S/߭cLo@hI(Z]}24϶q6R?ßS\{㠯Tff/@S'Qit{ccnw&?,^L (~9=6h5EQTj> <""9T<!Ň<σ s,'q7/[ @ZZڍ7x9{hT7$WIJg^ƾ5\s[4$Zlc9deu۫4E,uޑxɀB!I-LsE*IKmޒDZTt^WD2ZvcNz꼍-2ٛڒ&skn ~\^^2$^[1'=^eҎO#ϵ3 kʻikZWoHQEFCmJ*7T#59+V)}몕zp#jH{$sRUX1d}VdMV֚55?dwMI%+V:k U[䥯~ݓ3lXnTg^Sҋg fMjuzz 2{ldڋ֤7e%!YTޟ/L1R/wK}%ݒ:5Ȭ4[V'-IJ]?:+`B}zVTה;]\O3'x&R3E;.y^S( J2!!!!!APrL&J5Dx8-.MqׯѣEEE]ń &DՌS rpj+<leZ̃W Je8T3@`r%.2kDUtD.3%Aac\ s | yEGg]>ߤЏ1Fq$J(rdPz˗-YJIZ@?{D(o_wN$*eѳTʔxQ!"r"SLb ?WUu,# {Z~SEWafϫe3*ei3(P  EIA  yS⌗ȹ()EHdH)H%J +{qKDDDDDDDPJBA* Oq ^r)d""""""""?( EA*dRLJIT %eB(He\"J !PI![t֕x{6Pa6lr^Zk0s6m<:3qۦM[tc-nO ;4r)+w麎>G{룓yfCsIꖖ-/^k[Z{Gݡ"""""""<χ!ypxa6ljkqk@8޳G9n#[JQ7cуϵ@2+""""""@$B @Eot;^+Xhy1po~kzk0` 껺m( vZ\ڶlk$]y=jo ugcJmVS6̚@0~kVcR f {:=tVCؠACU``7""""""!!< !8."8p!p!1>>x7;ܓ:}xxk)[:Śʊy}S>.dL[ f57ܙޭtsKVp^k [˅t]mټY5>ڽ{k?Y=/<mb-i/%8qT[{ƾ:m[T&L0X\T<!Ň<σb\@]}C:/Z>@d?uD7? @ݻ>b-*kȺ>A-ki32֮NS2r,rNnn.rgksˁ?f+޿:!Gs7R "( I0G $GI:*mğ?N}d^Mv3^uV/ cL U#U+N=TEEth%ֈhTx:vB?0>1o""""""""ć( ". cJ "[5 Z$m9]>ga3lnOjd]>R@wߍcPݬrbG}>u5z\[Og.[dV}'/:5o) l~E~y%"""""" PE)L&K P2% !AŢC{. V-e9W$%giPf eII+:d@8KY^JXNOJJRDSG0=uEVN C{9+~iQ)'L{rn6l $9-Ej k´1'K]ԫoT\BDDDDD䂠()hTH!$2 2@BX?E>}zVTה;]\reJZr7yJh#LI)9*,V>f)zP!PgC<Bs8^Rl v~6pMv1ԆQcj(+|dODž/"kvATH4l+f`Ղ=HzA4P&DDDDDDD>aHp!O8 <ǃpV _3<0uaдWh;~kt3ĸ29;F t=T 8wwc#=.N2ՖN۾}0 8k9%h&NM-KEDDDDDD.6R "A" \e:^83 ڼvuZ Yگߙ tݹkSdd⑜Au8Rh$ᐏ=T Tזu}4XCq=+gɈ>'eyC BBy x1%/<16);Ep885`*ŕo뀖;;F)8+ToI_ZT,""""""B$EQ )%I$LF)dHCْ+inPB]R8'noe0r E8,~LVѨ(.nDyI*y=ȟzIfZ>vQ/yתH]6U"4ȅBI)JBI\&I2J"D R%!l5YYYYkKjϐ6hK_*-KgƊ*M{Ya-yd搙@k|,' n{H;@*VCrdmMZ6dzMUR|m mX<74ԷYFJMc (R| =};wn۹^^Qb65E/+ggFlm =NngzSsrqt>U킿9z6<[hDQ>_48 =OKl52C(H(J "$P Ý"V|lPF))*~P`'=HLQh/yTZ6we'($#u ʓRTBgo~~fʵIug21)E)ãw]V!tuuYnJo,-z07> \6yq|>klU1C; | @"97+Lvy:.' 8Au<(4r0S*lWrO 0Ʋ MaR.$)h|gk/XK kz6vSZ|-}|$;?K'hU\pdh|sP$2 JFQDTtj>sR"~TJʴGT(%sy.ٔˊ,޽{ӟ w>Pԙ< Jx,ko[}^o-|p] do{יfy_^?z%|ήwVޔޟ$MtwdPx,ݮ䛊e8WO_pý-ol{|cW' ήLگ=7J媑?qktgή:V~ū߮rl46ws3fqw/p=k38+Gܵ~ Z7㦴ï/u+؉tM$cWU_)\uۗN滾/ѷselmNye+ vw0F[5ozwgM]-3xۡd$VT|Uĸu2;7f/u׿W'{2WzsZO|LbKn[{Ye/9X9{g[ZYFۿsLvZ_oLqGcxqͭw˞vHtJ[.;m}_)zn+-?7Sx-o#s]vZ`=>8P-XOX>ua{艘vv9,fddn(;M洽j6G^Y RyJA29n3o`]xï8˹lf{|]k3SMXQA3`_<?KE( % A* Oq ^r)d^`mO^z,DDrȑ#onwN~x|>d08)?11 osϑϡC>Mۗ-[=SRR^S1<2<44533/U#֚ؿc}u3Ƽ:K&&%sBVk^V7ߪ{c24iܑ֘4v|%ֺZ9_y ZcJ%d@k ;:FH&;[0 0z7!:VhU-U !ށ&!IР[ !: @k/RpG6Mjb 4BC4`%ݕ#4iX:@7^kЪt Xj܄؛"ooSipXԌр0И+C4:-@16ZMvVCе'9O1 c&-N1VXh!k.1xmy4 : c5f7!^}t $mK*ѕ3@MU@ iH7ߪd6!^70fȍѨ_dSGGg =3^hӦM/Cȕ•x]uU&RSSc&:Y4ơ1_Ht3tzfw>uV oLuANjMQ L7!MZ:tNo}^@j'dLyTGVu6u^"4]7e!ko4gop[5@q2bʇ)8\Ào5ת:h"hc}_Bת`#$8űoU}ΘoAz/!vt5f5BA41жʹëglL߆kJ -Ԃ;8B/28#u9<#^32^:x*gU82>30bgHAjlN%3!lLB@›Zk^/Z{{K)^$jw FAFzƲ,Rm+r3`Hv:+ (M' Xe(o,-G,j%BˎVB@R@7v R Ȫ<)C,kSRPM].ZR@h\ o 4R˲@~9]5XfY֦\Ʒ)zc0j7o___u[,'ns7pWRPZ@@¿%Tm/D z̅ 5/뒩XyIIIqAidVdPoB@]z xxr!?2uú8!8_o-J2v9boA^=F&yϓcNY1! zO xجJԶʡGQ97: OL ZWF{a| 'D.<2je yfU$~LFdY<-_aF84mÐ<Ԡx<b^TPMsIQw˞cwkM u>kSA?nq0$ϤD! 4b{c j>Ԉ \(l³qI<^JV$9Ō rY+W+#C"P2y[bF2cy<o^ |av B^m i ?賣7w`UIzT5)riDL8< C13:29yf]<<UXc~-?a-yƼ[lXxsxscX,wD,XAS@x^y啤'O&''gddLaJpy[<8_n<в&oN'.TtKy&<0\3@4ѨM;- 5'? ]oaLٶG6?U|Xo}nAhHt%mֆ|UzxSlX^sT2>{9*wf0=^<^구uK.B,0Pd}us 44t|Y6*VݯBAYĵ%y12yV%\f]Җ8qC. i(ou1a߫pϊKQ{Ϳתe*P؆ C:'=P9܌u//0r[1ֆcdGnv0E΃̲dm}P0MLu~^aSٴ_؉{ƙ3 ӷ9xpk{@QZ=u[]cgzM_Oble^ 9+uY8ѻ`l4KTyIuF;i+ А<n5I1>O-[/ڍñ}IEt\(t1^hZ2)V49CC1M|c he3()VBUBcԊU@gWFaJ\XP /brUUӵ_-$0W U4 ps& .zl1u2C!a`b*?v{@^f `=G{|&CK,pLeBt0`r'MF=sSbli,KeP8o^4-$JcO)+5З؜F{w74=~k˜3gN+W_^q1W]_]qYmlÿP(I e9n܂âF-rwݻKw|tr P~uB[,jwnFZFNQpȌ \`K fUY&Eq)+w(PWR(7-I#z& Yi?Or~slq lAlfCowCݾ&aKJ)ro [Y.icjhCY92M/ɹŔ*-v^|FFq3ACe b$՛}4!GpDK#3—d~a@ QH`@lc_% 1^u\ v8{##~n.E-JԖ*?cƥ@%expW" J z>g-[0oaJ]&adutef{ J\KZienlxXش^b/hԝܔy ĥ)5r S` Xw|D>9j"JZ(Qɛe+DԿ7+dI , 1L1_.4'/3W'?((xQr_]\ۢ333ox,.;3g0@Ht-v2).ӦGFG'3#A"$=ngFGB±HegFFCCEEwJ DcW3'˳eg'O삀7e$sȓtf0“gFLgEx5V]5{X+*ڒ-M?aP U̔ 9NH8A#@QӚS7 {M;֤)i9`2ьnIaș\Af0::z x̱,^,""b```nFvڻ6Hʜ7oHʁ̖`\2=)}Zo ^}ZG9z}[kp 3gΜoewZbq\b{ ?X|f`z.^]oPw63:sL;;-.1^]tnӌHL̝uyn BqBw(wkp4dR9zz. vx4Z<[kt^z }+tE |%k|QL\BBBBtadLB4]̙#RVDnIϖ^e^f/`tk,?JZ஻ ~ 1)\faJ5:J~ѸC}C.hwCY%V$ŋCN0d5 /W ܴUpgi;.윰&MsT]mؐݫо-Y2w)伴-fp^.\V;ySsyY %`@ 3gΜЇ^{\}_=.=qsOXXq'1)W.3Δ_ȡ~\%CTzV IDAT:#9*J"% r\gV wSBttDnJnYfӛNsrVixd7]w ~)PGi m1צH@ @ hѢ%K+V$$$Λ7/44ۧ5mR8qvA@_d-2n5h%a>Ԑ»'񎹋(a "̽22:d]3a=0"6*$eEo{&R}j{1H4;ɽlT^; @  7̙3::_Odx X:!U. q|@>[K7~JȪ!Ǽ Lg8o`g_<@%Xp"grdf*iN%GXk/'z滋v{3_&#$$իp\v<5fFNieS:aIoZnjT\ 8 /lP9>bwpu|ݑ8\ȑW̦5pzXnݕ|-eCkϷ$40C%:Np8殔oJ T 8Xr[-޻te1uM`1w;ZzΜ9s~*28x`0KQAT|$"KT/Ew;AOϹOx7(lpjxMqܳRJ4_= QqYğq~97B0,e}?t+&Kwᨴ8O aW<ɞ2Li?cO&G:MՕcISysՉf7gG_?wG45ζ=޳tq  ][*Y Wl,^|7|wxsRAq"*@'ׄ,\ ;NKY}10UwDZS_8NY mtǞH`!,]8V@s\$-w$F3&UM_wOF[N]([QOfT T4`[4REPjaP{Ly:NҒ q~fjǚH充nMg~<=%ZC7ܩ6G<.5ngYAݱz횘 c_]j}8 !P[:EZ9ɌA fW=w:Аj!!sM+z+봎q-جC3N644dG8qO>tgϞ5/^X, Ì8N5::p8l6˗NǫS{Fo#mc RJi~WP2~'K@&T9IJemZ)(H@bYm9@&QUV 85gHdr /YHʵ@T&ȫξ~֩*$rUZeYlV6h+r{KR*`m2@Vk@k B^ Xe(+Ʋ,;\Hg@>_i:j{m۰j,˲} rW}:;YV-ytRcU(Yϲ,QʸeaUJYՐ\ ϱRȫ}N] ֪Z\CuZv@3bNց1=r((sBjRߪpiŀJH~HR@VcYjapEQdJ-;cMnx쐮e$reeY[(hp,z}Nv$~eW]go-Z=˲*!. uKPISƸ$4gçI |:A}\ӭ:@$} C'/Qa螭bq8@|W'o7'`yRP~+GQ2*;h`1erx[+:ex<޼4ldcW+$,uaP/<'pAOI<U t]aHV%E!Np ڜ@OqMω^X +$m9dUR$Ȅ 3qH|N] ܧ4Yq- B͔(%|s1 J^\,;}Pu04K</)oXhѽ+NU)$Ec{I;ըG-cӅ|u` W:|HV2O?ԆV֪U Voc0{#KԞ P G/@"{6@l#!hu3(Zr}!7?j͆uqhg1w˞c8soxxÛbs߽` um2,|-3oH0'AyV:dNs ': 7q/!+x隫g=w3pÀRd۞>-óSO4O Xo Ym&.nvQE t[uNoIp!\0NU P Eܚ6mm*?btT\\\O*Q,.} jXu[j 07^UUp3uaoN0?-DvTKp@knBNyqwر>}\o]fĖhmE 8$%M!n_ Z?j9ZNψǥPE]FCg?|b^~,;2bk!G Yȿ7+Ndp%O[U5ʱD(mLu4UdREk| :r4V)(KSrH@~ifֲ@AI1v6In$061D'g|)K1zK>}ގYY S4&) (Vs@ʏWůvz$$Ś"`VqzN}ibMNa'x҉T#[W4hM%ӛ~ZrM̭ oppW?(888$$D( >ϝFGG].:p3?ITw \0?̼ \vf"!µPQ)s\v)3#&퓅]v>J`ry *y,e{;еbv4 ;ÌqÌq ;5LQҦە8VOØ vD* @8)dB}+t, {=6DF崦k|%Vk;'64huVa|R9^oP8a{/NﴒDi"vY\0/qd_}%Y][kꈁT>Dl.]Sf 0oѸg;uݼ'x Dt2~x->Е%Ͻke7 ~2̊MmGJ}8ߙ3mJYE-ZsHQuol6ixTR~@PJU_G Gbg11_l6[7n6;ʲ31`?=FMG P@ S3g?Vjs8o*cZ*r ; vӐI}M{~o1]}"~fR7[oF.s{{&*)Kz^g}rO+K& Muw|̩1BmG \|S̷4=4`ŊsCԃEU=\wdKǖÉɁ";g9z \k옸̋S|/&3NUгpjxD7v͛e*˲/9yy:sOzYGN^'zzzL_MZL&e:6/>Y,ג~ v6C e^ hҖet+L]& ɪŞ6`x9I `BqvYk+ӱ4)vSoS====>`3p#.xWnjjJMMx78u!UP.MسRڥ }HIMQ `7P\ D#??i߮^,xv **@l5 'Nt(K .05c=WEd3xO_[*Y;:TlyౕIq|O aO<%,_DlYc)AʊCۼU.sG!.-9vzx嚩ܿ:? ;7x%˝8-GHX:ێzkc< ~*} %ۻ+xi,Z69DD/^#q 6JR#-h¢O&qٙv5}>ju(w౻Z^OQSﮍ=#%*nh:WHwDZʀbm{gXJbl_m:1>KxuPXk6aJuVBqk@ | ћgN{K_*RIv%uNZcڑUY'e))7ei4z.N)xHWc?)u 2dLkcYm(PH QNȑp8q'|Ξ=k4/^hXq:.kttpl˗/W=>G˲T^ J4CMn 7,kk@*RzeuU&.-_PՔKHdRTN[N!eYVJ47rq#LK SU|J!JNjsx mOdJg \.@58Y+UfGGZ PV 6!UXY~JɸUd~'hWk@Hk{5P`ʧJ vm d'

`D>'ühFq^y-8 W663g4N?y~rrRl}4 zO ._O*OVΊlQ #W)U6my0RhD!@\]}B#| (40~2D8yUz-`?ՠBAӶH0=̈P(xo]+`]l$N{jꑾ]tJ]v$/ q Mc3O(a30p4 (^ZO곆 xQTܧ- )H'~ "nc"Zz9#9h[NO z20~KI{d0ՀLi*@Dir .v?cUol(O>\pdnQ%Ҳ#z.5z"g-t땱+3 FCGcE  Kgͯ*ܱVh+gת[NѲGTEKSXVƅt^=;C\1, ?3Z*@? RȪ_8k-͖Bw6=Wt#΂1-.0R\p}T *ꑥ~rUPܷO)+5È_X #^de+äl|ֲz*6 Fӿ)IϓlGj:Rr+PUڂ˥⅀TF#*Q)C8jۑMޗc002i 2yS ;|FX!-g*C;^:ZNψݪۦ!&ZUCEix- &h ̏{NSIJ"U4?*4jXRPIG̺h(Y H~vHy %{aw%b:|'*JVP`?g`%|ΈB.zIL"/lU(NҐRB0JR~H{gnvCNMsi~<)EQNlGto~d/6>脉1XK FSɬp󁫟_r5;DǾG}" CBBBBB u\.%Q%xpfyÓ\vƉtC h`.) ݟr.q;;D">ڳc1)1&u3#4UNL,D-e0*_.;Dh`Mv' &p`&7\q .%}.)}2mC"Y>?cSN7|hP47_4>Qol߫ i3=|<'}~"MZ$$ɳ+}f P4V7u/<_ipKA /Mp+7I nwv=m@ |S3YyͿ뾑ُtᢏ,o.FU5iqO%M>}SrG=7s%@ &dW4X:ˀO_VrK;rb$ܪtP=K>{~0@ACuqp-\u|0m>`Mڱh &(&)E)Spo{7ޡQ4[~(aJyAB`}ssv#\,_D2.uq΃rk)S^՟y@ -dkjUuWWWnIW}-_ N͙QRzf1Ohѫ)ssjHRm0DAZs!u:1Awԃܹ0n@ ה5{*!-QyZ"-eU[ NL 3gJ5Tzt 2`0^ : {TW**PoQ?iltO.@  4E۷۰aӦMmj9Pzt]-|+swKES.]43TK36e[vcg͖|5P3#?@Yv|a,c;7v0Ov>h=i(!zi963+y<#@ nw5MUTKDJ(T֪ 0" =tWhn(,Pps%2T<>(KZ{@8,LܦoP Mʙ_\=P?z ey 煇[,)Cg衢i}~Ƽ:`„6!_ <|PWhM?@ [c"2RbG"3G:eXG |sZr FF;vB#ET.buB[M k/EF E4|aG(frɉ""@ cF@ 1@ n"@IË@ &A /@ 1@ nоgs:ύxcڿ\¬u̷-@ !4X:*vڴu(N>vG#O5LI ۆ8PS|!A 1y͖^cKuBG֯I']uz\һ\.>T4@ >dk*ѐ{ 3i\ =-fkvrS} ݇yj@o>u[ k'm [ky9E˵jř;ͬ[DL/NIYVZӯ !T(LM9'Ԣrev6B[Kb]oj5RSe,z]ϙ@,I$ Z|hx!lt:mc_&oQiyX3coE)RN 5.T F[/{-I<3bmIB )tkiaēH |7}Bu,iɋRWZ, uQOG77d@{n)+ Mb Wq}ST} J3.@ˏ7$or8p뿲^~eߍ$F߶^1ڃٗ?eyrz< ю˫ PN~U?Ȁaƪ⇿9$x7gWSoo2ʚ'2)|_@sX<{ErOvC-p\H&m66tL Ez9޹f]L* 0l\6N90Ѭxw}Z,/:933x^t5zp\?b#HoJQ ֓Ꮘ :o&$m^!4i `АڤYsqU\JK3 2qH'^tۃPJ Jlu1 3 @:0do5@hUIAP]^C Bc4j}gW&T*)OD~‰Ӻ0bJaƨSJ*3mc 0.dƤ%x"h >g$afr@)=E+ j@*@9]4z{ BDDa @(XF Oa¯O?h cS!`gx5RTZ_.l}98בWgBPrB:Tx'":*nm0Ư+[He2F cA:P5|Đ hqaAJJa?I} 4n/,-1Y;<-K؛&yxr`oOxt~L^LAĩ-reuWa 3 Rv% GWx,! }iY4bIA!UTDenjn!SB Gq^oNvvQ!QMW}zXMvXp!`lp*VH4ݿoxkcu|%$"}φ]{r!'g_v=NpޞtT n2۴'w;aڿ-8ܾcÞ澞rzC_`#{\&'bP05nk6]]Z@;=Y?>_2\ _LػwGq o\޳ߖo :]ÎER ~TӁy2Zʲ/Z-N{ҟ6m{w4rn~@]I'9Ytvsnޓ?Tޱ!YcȄY մפֿX^$19pilyvKgnh5G'qkX}.A\~ l-.T mkK^MpQ>Ւm޲:5e7ݖ!avRK^,^MVЛe؜(yS/Sgm"߿ٝJd(J^9C o#7mx63;ד.⥦T%߽dYցyT5j.'-Λxmú޻$?o,l@, Ż?l3ڦJ^huxv@kD\e~j*HuOeoZnD;29ѫ-,g__Q\r$[߫L} 7fmd|0I;J'& w.d:h$gx_U$w&lVITU_IbsJ I/t+-.*ٶ"ȕNI+ +u'lΝ+W,o)+.,V<4?S$J+kQ*VXt`}FKʢ1zTlYaYWᷲB`Oh礥,,9h85^i*SSRӲT2!Rcq2I+)_/Ţܴ7V@Hy畨k~9o{EEdHmRB>cRS\Pw) S!i:ffV\\,'k U3 .`• HrD(],-nibnm[Psǟ,1Tݕir33 +UyT5jHV*V r-7 þP(T0  sqB*JkWeU/ n3LO($0 3SiTw7_j-+_x\447M4}-\4M\ t>^<OFI1ZN_P<4=?PvÉ!v*䪠!ح 0J_&jlJbWe%KkIZ#xb PO 6څpx۠ jz[\Hp5 \=mp=5Lgo޻f,,|697R $:8b"buoȧfx8%"?Mrn bB](oYo} ˗qǍP, Y] @,NbuH?yͤP,YO(TJ OvÉ'DlA\x@BU媸2Fk=l?K.BIfz19wPHd{XRIԎ=76ʧP)M>z >p&稹&gM` ,JDuƘ$◙]h ( [^$Z5 SE!`Q:Q06d%qĒX@ę\ !Σef^ބbёʟJj&Y,-il=u棳g[YQQکgϘL"PHTbvM^ 8XÛe5-T1/0è{gΞ=NeE;Ξ=lH"A'?5q2|L{lmcx]%c'S&6'";,'MvxpC^ 00:ީsHsͺ"S=+Vu ? }uI~-Momg$`gV4nHGgLA+'Bwym.zf]LuZ '}BݳpLL)8B?h΅8,O"aU5bΜ8q޳b]"UjeeY/?ZFtM֖<* yp#/ p 1fb$T(ş~U&iGu%DcLF,10vn$Āv:܏WzBs96 k*-=J G:缓xX !rIJuT?|Ǝ;P!jW𵓶6mmR`}0z);xyh]@~+~J\F(ZOyNjǭKK@mtIVc $@v{Ʈ&@h Hqa{8 N"ڨ5<]I_ӱg@jdaƫZhc+iaN% F$-+QK øZSq\I^.ψ gLȤL&Srp;FsjSBSZK d@v!Dt t, L2 3QK%N`ݡ1C(.[`X]/%41@j Q D)TO0 %W ̏kx1^< Ӄ_lϏY3iO>"ӏd K_$[ ҊL9Ir(ȝ2$90; ZzlE6g`m3ܘQ^, !ص #&#?U K3BbXVf >!ķHi#wlM͞5 9 URHӅBL[!W (2 Gp~pp@:9.7i:dmԥQ*M/|JOl'LP};c4'~RO<@5 0=С"pMZ?[롮Xar֢k6A(DFۨЪCf 6#YCӣVsKhJo brM>s&=Rmf4Tb}SFA .Ɉ!M֡-l]/fu|aS-[v~6|G[YWK_bTy+a熤df#&i@pvNӮuYII@%Y%ۯJeqL6nYw.^'P=Bvn]ɣbm1aiB<Ka"J@%߽d Yց͹8}N?Zjj:<Ь.C:6kWݮRf}@,/%++ry* IV+i"|K&_BGM}3soyvg)35( a|ya3?_9bgQwfx9M(7RBqV´՞QԵcG\Zuiȹ*KvW@HIu'd3?zCeզlΓ7MUuP?_xq [ٝ?, 9~Kп{WjzŹѣ N);^0to?:st'hF`_ `0yǪL@FR۷o߾lI?d]S'<7,."rݬGڨԔԴ좽;3w]VתSSVuւ[o{EEd9BǀPFT椥W*ݯlTz-22[I eOume4BHffFpIkP_"<eٙ{6%ďzՊm˖W2@-U2MkY:~<4?S$J+kQ*SǶ BϛX*Z 5dE$A"=Ig'ٿ;gYlyܩg˚(f8MNns5d=QM{H =pefptNflN**L[[gR)!\]JZ$sX9R8lf dGR)V/]]'/Ey<,@+ B*(>(IKII]YXrk7QQ[zY<I>U3K(7/iWA(o, T6e!~>j IDAT8)mPwGDy-QwaPh{jR2 3ב{-2Dm/{E{hWϸƷQ]`mJRְ Mx,&Lj}T)$G|atFIfVcߦSHD*c7[!>L TS ' 1ޯX\.֚U@UIΆ=K6ĈVٿw6ҖQַ+/,$ܣ3 +٪0Q_ōrRk S͛^Vu(:E{g306>nh:^nL+Kdu{ՔiwYc0 Xjt(Geu mU%/G'Xf{FM˭hG&'zEl_u֏O/\~q}!}} O'K$G 5#!|ׯl'G=xk>3~ ׄD]Yn 8GflQyf~Ekyܶ좊!^ĘAc-e(I6oGHɳ q$rv*xAfr,+?z@n x+!3  ًa]qehg= w~RxuO@>_*w-1#ü'yo<>\d|WCʶՙ?~B:rZG\ c4N Yjܢ7ٱ\$K7ϻ> siC˾q;! 0Fa]74?ܼwWAL4xYFG:d5U du:fӐ}̓f"e䶫σ.]{DI1%.pG7XbҠڼn'=㙏{/))ibxŋ/NJJDBpѢE69z_S*oYVW'N\sܔ_<]>':5N n`Sۓx1\fgg3lI~M$';f?vm<߉_bIzzFFFFĆW=%Ҹ^T*^1T:ͤ`CsE>LXحX"J 9yH,yH׏uO19{ʊ9{ϜݳXz[01&(֛I"y.ǭE=32qՅA)2ݼA`X=©-ßTD0]ρ$:NJq-ρ[9?9}B;Pz>-eɳW$?{!-=/J)u<^8dsiWFB 8C'36Kd {_\ mNK[An /#m~̀7/-yz]3U<3#9XGcxO;Wۘi> vG dB6ًg%p[+yDx_]!u6f-qdŦMI JKdXh5 g:{-fB:&gm-G5̧]1}?f]Z.::ީsHsͺ"Yh3p KN:{vwg/zX"Bʪ(hDKF4OW\F @iRHdg U5"!MHڨY&ǩ\ WK  Ldf@1ķRZKzan3Ь V$bP+5c(0N/2R~׊6윪 MNsK QNe[@ Vjc"k `nv|3Bgs '/Y AhOg?* Gl]*NvٿcCnnJnR'T5Q;eHr)`vaPUP_t߽ԟZ5 04OĘJA>]ۄ j4 [c棎7f­(zQ`^+pvDy $`b"XxR$/|0y3)*3e"SA{ا+qԢ96 +ŀ'|"& 'P=Brg(95beDƚ[\ypNOb@qZTs 8T@N0)˗|ǧ~G3DL{L+C(tf(R> Sw+isƬ˥nr9) I6;pa(pA4jME~Kjiqzk/_!eE+p^E @*Lalt? ~ŋ ^Ao㦆pװceT3˜[ Sի]6u?wTA_HV+i"|K&_BGM}3_ʢm@=cd/}~$ވl6>6 |96S5P<>$.S*v)g$+`~I H*uؐ,}1+*%ZOZ=ɢg` j =R軧VS@iU45ףz9 d>GVmg CFjC! DCi !W O4$OVЁ;>N P!ɺgEx $euXk{y^)pK8^]{v/rl'nxm{4[/)/߱iXQ9;kϊsuVSU$r33[OHz KDT>.BX:b9B,/kUeJ:}kP:A Ґ$OVtme))٥<,UbC,=̽(0^R鵨/Lo)&@Cnz,W!ELGY["jcg -C̓gNx}ך-8Rwi\BZ 7H3X4o93,&u;=Du^s&Y9u;g|5츴$ v3 GPz"Eݞ>nHU4^{$S! ɼwIJ4мnXQ|pZQ$$5k؀MbAO N\FR\HtE 7GNo'G^k% ㋄P< %-B8!PFe$._p^jᓆk.M? $| ř:OJ=D[Tdq ^;ҵa9(Q~ UYe%f{~4p5`-{? ޳aᡞ2{۹g\@y;5&(([Xo;6ii(g7Y80c=>622221IyqR΃ x:qZH#ڂʢM}6m%GjʴX{?EK>ik|9}]\[^N9ac95݂VoeXux%9=v@ZbLg$׫d$F| Y(nZ(fD?M/3&%uP wc?F*p_Z5O,2,7"b3AF9 HbbAUbW\f:bvbغyMec qJE+CkpuC3}C_"I֋??-H^tb3t5>G`!#6Ck+{=zy`jV!"L5SY]棩ahڵ#ܴOlժ`b+u,aJT0_J yKFK͛?wX8bM @(BX|^u@ǬAĚՄ ⇫`6x+Ơ+EGa?[&N`jj4G=L0h%-H^'棫^".BZk>!6}ݼ)T{m{ѓp#Ĭ?󓗬]^ж~ 7ÀIۄcztO,f"Tx2??)P0ij꾼6xGR.7t臫`lX&dV\]g~`E3?~!dShmgp IDATk |X%-^x|]xwW2jZ= S׆9_ꫩ>%Ifh9}]Wĉ!&b HJ bQUt6__`ֳ:Ks"1!訦.(1+֨Ĩ`>[5@ߑϚ` #Zۮj=GC|4HnKo]'!}ڴ7uɹ\m'9rȉ'js4իW dX,Vf!<00_Mݪ6gy:m0!IDM1ΡHWH$1$ y5 0$L>9ɑdcc' A8r0@dD] 2)D<-c{F2婹TB1 Z*ڙ6#jgeU P bx'[ aƨ"yƑ`QHHd2Dg*@ -d.U"<=D^+0")qEx1L*#/UHa-da'rcVBN"#èD~#0dPaQ7H_0=;UE @0B0F=T|@=@w1)"@*)#'I*"{E[sP+;yc`l)a^9[ [@\l.I^/Gƶr %(2[)vmݔ`r= 2СC55.]:wNY-{JϘ[$VH@y5 9VRȍ X$p۪W -wWɀ"#cTHiahcD HϘH30QA(ja\0FƬ @ dIsɻSX$dmQ0K%'0z#2R Ȫ;Y3^Yu8j S88\zj+S'VrjFU׼{ɅY-ja; 7١˨.PԢV^Jrq2\eeoVH*āƨé)uE'edjUHHTF} @b4K*ڝkNP4vU Z̝dzF-Hy *^Nc29FTȼޞ| iIB@c6{n2;"ƽÚQ]@v)Tg{+@/7z6BH{ c.:9R Q8VRed*9 ms ڦ33Q% 0 cl붲rOg7\sLuG. ko#70@S'-)ǛS\ cŧ"8~jUqi>ZJZ%yAmPx4X1O> <Pn4e3y</ ,^ ZH}И=7S҇ٯKt-kqr gW]s;mCR K\DŽ6壸Fc7 csSxAp^g/IHV]_GH(i3t}ZnM" P?R)H 4AlH $i 5~V}gy #j7cTQrF(7dc 8#ǛU '=+*NPƺ:@B [ɋ?KJzZB ?v] 7^?ң 4W `8ɳ-y$&#oR(N W>6ccͩ/> CC^֡y+@]t5~ņ! oȰlR 8 HIcA~FDDqOdz7lspGƏJ "a͙}Y7}/G4'f|}@ Zp`j?nX!;aN^-덑 p.Zrjȭh*3:ETT=u''y?586M|/*QXװg|;~/:/~8%b8yy tJ%v F|ųfeGp>:'>Z`d(嫋?Bs\}u(w$nڶmS)?%iR7fqD{@nVIWJ lR)9/]'AYARVK{e;!yj.}QB@ѩjt89-\OPoԶkTM^Y|=7xqpܹDCB3x cy0pĘxFUS 9'<-NIcoO"Ƞ3SΛ1ʋs?ʬ tqƌՃriej= Xc-~3_ ,~s/nI qKpkm$ř35YV'^ܣ{VĄ#dYVdP@drT^ ,G@aFhPNM2 _l(+Suβ̎\T:':1sI~՞Q<]DəyEɉ)uG'@:LG ujԾTk$Dp"7;5>Z3F|03>9S 8fvXH ^L^4,#9>9"sKh#b *ENaFl@ܮUtwٛ{s/NY~c2'RdĄn'WʔИTzq2NLe׬tC"[_nJ,L]U"S"hѣ~~~BW |vfl~a~0w=s=1ǎKII`o dD\v0=1*8՛xƆ"6d^`M6 [71xnn"=l71+bJwψ6Y  JIլUMnN) :~ͼxdY♂t*]Sw7q3ߡw>8xjLm,Z|LĆu[Iԥ\^98n_"ֽiֿ:`̜&^!%OᩦrhF{l̜=7,69eE5 yj2v?C7BqH8dbw"n8[X]"w!.OY)i~ǩ][b6gwTlIڴtK bYt(/fC [x;4ݒ漿=V\pu E?|U|d߳ #mW.MmIS[+xT'lLb6] `+^rS"쥴#{S]^Ƌ/5. [f jܗKM!Sf'`l **inynṫU=j9 b~~yr)1&PaA{6$g;n9ПJyNIQP5K :}}$wTqrpppppp||/v[{¬ЙZq7tؘ:ʍ^^'fǬJ[h"⹡L0,jj_^<,,j!|G1 Y2?AYO L('QPKAvZ*̮KgZdžad%[%pJi01|x?1ͣGw3vB.yz`e|躐]ɑCl002f|\LgUIb9هL2#7IYGn V k)0L-iۺJ.^ElـtIs!wlwgsǍ}$Leiũ__kXԃ2|Mz 9& ~Ky-#m3Gbw~}n{ >' 9/GU^bCd:gk aYʺ1L{bS՞Gm*.;{C0044>sڀIm3AM'`ڼ 6g":7-[9m1eߌxr" {';:n9̯z.UMZUX3ӱ캊K|l@< ]0 #~O 759q"H_@ՍbFGJ tioKzMOg],UB0~wSuҨa9tO8iVf';:Ξjǒ3:.]tꅳuS8jp$6oj/Mm?%ɳ::>|dGYl A5~Ɍ(nn __`Rg;:deffiGDžgC7kJn moMrb,#YYYG:z/]y6ljtzk6fߠx?UH~8XhkC AyHfcZz ( *~< H#ySꭔazI i[tՄ{v%;* W"IlvΥ-\Ȧލ*?x>:Ւxœǝs8YЃW͵'[0%Yp㽋_CkpuCB4Qᛴ?OL7SKO*^"кbgMx06hc)=HZxٱ5M ǯbkBO7KH$<[eĺl8ǎnuU{hAZoZ'ZjUTdШ[ߌppw'M5}F]x49PK4_i5 |[WsW\aio>E Qb泈H9ŭk5x(1ZbMgϝgҴ*](+hゕKc#V%}c.F~hf6?ɛ'.Gx&!]M:.!~gLhu%'DP5ʔ5MT}up? Bk>;z qjpˆu>XbN߳;w5!5=6eV9*ͫ̏n. mc%6]U/jjF%FSkYKgwmhQ0&Yhcn__@.MԲYV+`3޺DB3}C_"I`{vZۭU?9>m%2h2 0U[R"02 0 dQ˰ws[ iaHg8 9 N+ vܣ*iyt$$%jacOJm2 cT+FcuE.1Hڑ e9%`ȑ#'NPΝh4W^5 &bXVfۇf?j~s7uڴm9tз] 0!9j1K S فNWB*%2Q1Q\ZV $a%R Q1 7"~ʝARit.E0 Ø 2 ̑C@* R$)@4._s ReT^ 0@dD]խ$l4'u@HeRm H$Xd Z$ї@# -ikDa7KQ\=+T{cʥFqQ+H۔zPǮ&!2)@2=N%1a)"@*cWgڙ!458Fƛ`Rdޣy4w|;2@>* ͑&hF {V)"µNdjEBFܨHx+S>:4~Waޞ{z8WFQ7SF^azhй[c5Nxyì wtF  @ޢ׫oU2h@Zf Ȣ1*$)a1XݎZt3Xt;Y6 Bި&ȅHXx52{9*=6a%m}R@V5xWu2 StZM9}\\0 V"r.Ic/0ۥ d9$@* c&2@px+.kUN׈yx ŸY4urJegD0LOkoc]Bm4DM0J!$-kp,(^*bGG7*u`ζ^E?{CaXP*BHK27 Q+$q8;Zr[΋ ٝU5^BQppHHHȨBXX0@oTEJ!kǑ ^|<YC0mzs 6<"w TY}İ $s,@A:nZYTp Dw{;ȍ}Q2?V)-_S`Bs͝dpX>-##%!c̟[xqpX49jL67E'v mUm#RsvL]{ [FRمMCW\*? d|S kzŜ8bHEa eGFљ]'GQ,T=]r dAv:xduFjjEה @ǵVճhG>F >FѠ5k)wxZIWw[iao7uēCej@ _X:YD^3jݛQw7J/v.]j%3%&/`7v aS&&`$ o5to@#EuAӔ Qt v8MԗV4L*h>(Y V}m&"P.J B7}akwKSLY*~+w yzAįr3+$jogT%'c yݚNc#˲H5 qlu'x2@"%p^k;Etzxq&ymkzOo$D\!EFL8f9c;ދ_ՙ P'gSasuw{y #%MBUnMOS@ 2B!oI Sv-g}$(ǂ;2 y@Nd֟,DKfĄ[ Fh_LF6C/,NNkGB%oT$aB[D6N j%);<k~,p78MOL@~MgJxȼC-Wgu#Hyp[UddjlBCB KC;Hx mL[oe]n7 y׿Jx}} m~;59ݐWyo"}}}B@ >>>왘vflwk0g?9رc)))ai@8@mE|/nlqL&Hl#z6dN2IbHiMLr)66d$FL'2p29l&խ52_8˦!àWO /<*Anfg z3h"}ʛDx! @>0ԱŸtS5C_ a fd oƱk^tN෻?? `DI?ozE(ծ(ծE{邦_,<伦]}s-z;pxYҰ+J+J_8t*k_"fܗ:=]Ȧdys/x|nrpppp܆LQKK: >xW4gOb},{L|ϛL|9rBۍ{޹/Knd8+D|c ,/x\aw f_r9xoˠ_/!yVHͳ}i`=>qSnk'vJ9n L][|3F988ncq(^]{,O˵xx1`3^~ѳ^{{nfkOq_DɻEֈ3{X1lvEŋ`[cg̽{ό_vǂ5X.|-Z>ڝI)] flx]^DEs7>nsלuJsCG.sppL+;||>`/un?"8!^W>>M3g:_h)2Y`_&L]?tm'1dž`<"ppo`~׸?[g5v^] sLawƌaax<R^޷@{O,l&ms}+Ơ+E{k}Å끫$ u";yKEhTǓ#M WȘ횰ĸ`S.NIbj'uuE*g8jkMJ-NMu^gkBW_͕>*SX|,B??]pAKT\cC(STAȍX1i1wi Q]aOaߥ×v}`=:/?_/t|}hc:eߏgXf{V3[<_'cZ_O%e9a[RbZbZ롡!jX,˝xX1ӻ%ʚn̎ })/o4f&ʿDo0Kx\uN0M[6sN|'Fդl_ll9щ;3cCW&b&>S Ȁȭ;S#y1Иĝ᯴&D&˯XGܙ.eоӜD#{# Q揩ȫc.{?Klgrٱ#Ǧgl;g)$t @KU`ٴ*@U>OӋɒ| 9\{7^2wLp2?5SxAjQEpBx?wiƳ:g9_"WNJX04=QSթnh7b04edU׽~(baF@NZ~>#1 Na:+A?aڛjkk+*&Ums'KV];s{q dL+;||'u} |vw7 pέ¡:wx |í_ ^YڿsLa%?N'w˭糯#<oƌ{nN2~~~w>o{E^]=Fl|@lڋqO"=&]|q!E;0ij(ͱT%mωǏݻlꪡ WmC{*tKIhHrj^qj %Bd W:9HyX5.4@! O(m]p1[DT|$1h8^FIڲO=w HٲJ_u Û!0,,\ _WMP?O$^,spLݛ\:Ժn<0CZ8k̙CtW_}e6f3MF`0t^CBBw"4y$o:9>LJo9u{! Z[(En|Z/ 8ᶹŤ9 Hb#MUXF_8$y@jdDVU iO!LN+!IY"C]_,W/b|"E`. R Q٭@W1RxlYش'*JF] R#7wl8xHA >:p>k7nDN-uD!XD|{]L4R|`h84o Mn^د{"XԩVo ʪt"! ge jCcItBT횮mIIii&GQkt%XYo:EaJyrGy *+!zq/abݹaîzΪǷO[L5|NFM(ϟ5kVWWhX,B000pѢE . viX|IjtP& u&9 @X+g ȯՋ 1٧W|fb 9CKSsy>jOҗ $K '\ ә9|vD!}IRԛy٩); ({ /O#'W S-JS@r>;_,%QѺ$[?+w(!lTjt8PW?r=Ȯ/|JXŝm!+ع<Q3 eeSTbz^__ߨ[_~yzN7$妯YPTsOhh(@Ӵ@ W ̘1n4n&}#X8gRıcRRR=iFd#,t̘l4m.P' n Ohtg"~LUwդrtww_xaჷI/მd/?U7+H jf8͆)dC*Z>`g-[fF4r"vhKoWȳHBFPQQꫯ.Oqf&)*6'mӥ4{#@[-N9iO}~ f /m[;gEkŰ7RkU"_+^ؾE7nsƵ1 ־KҖTS)nj@wU,IJJJڲɑ٘ [6%mUohMuR1gei;Ԗ]K7x<,1YJ@;4M{69J7fӞvXŦ-{6Ok3fcǎuttpBKK Ƌ+;|p rCyuM}h|Og U[OOBNQPevb U^QU?I,\li|)ZI yᩐuU-==my9Mה1_i*VHlsWY$-W/a!LwtŕʲFNvjF)s32KUO{˲0<@4n}RVΚ֨j⚳vjl`) 3Sը*;Oxi.Owgǝ3($Vи;2(?<;YKnqbYT]# n~D` L[@(>~Tav%)Wm_<$$7ߒpEt{ym:Y ['f-[C:B:[L|DHXƂ ___҆ Tuq]B\kOT ƬJXNz7a-\@Y I7urV!@0X4QK|ٮο ˫*u,f/؇LG!7M//A>*̱y ,|㳨}p8Vi#mI>l4y  g yǂGNX?яz)n0|}}|@ f88888nk7u:vfY6$g >E:SNQ_Ll?{ۊTvZ4/Z}|E9noD|#}# 3̒Pel˪JCVCC@VgJ>58d?CCny~Pޭo55,Ii[Jt&AԬS&yfs&jz?R)K#0DF',s\IDAT7L&VTgӶjZHl'jhtw!B┟JKw% 2iq<@uJ^ifLdx)Pdr^(z ҙL:ײy7p6zxM@/Ka=مsBcьbC9rl \J0321K_x> ѫ2#Cg R (yն("bJFTfG OL͞uX{\Fg&G'*9 'G&φ'+dĆFu7ʺc̉ʩ.̸Of{Uw_ݝNwHHAQ8VY2DE0$E""$#Y!42%$!Mq  M~*{鮺u9s"S[[ۢ؅xuM]:2: Xl^:gIyg"R$[W' FIӧ%/[q.ԫU_d([b\hk$/_q+4X"N|5ͻE`*g]625{[+:LB\U/\ZZ1'Ef($C^>D =:6=(++0aBثݻGj/;ap?w k𶦽~~E=l*lqWu|\bRMTQ1,Gܴ-'eUoStPmm$In;ȲiiGAVnZV?tI| x [_w'ݗeM;:U|||d!js.0\ ROK[pzNI: x[wԁ܈ UN yxִGa-/~aӑa˪g/ }nSf:r'$똎7.!!!Xn'Mm Lab!~,+<Xܼܼ/X1=' w-ԩSoNuUM"/EʊUϏ ͱDD%K]M-8Pl -ƔwF~zuJM."",2j""+?%sY#/A`R^-v)/oz'mY9կhӢEFiUL#g./S:zܖr0'<""jZ(ιjqmH3;]0 LY(w{i}J7&Z6DcS<؞KШ$yl|Zq[>wulpb~7ٍM^gaѲ;FA< >ѷ%(:SZto߱+u}СO K.]?x%AΝ?v"k+om/wP)$2z&S.r.ȵnIImO?(9wrRۄ7;;f[|C+ȌD3=wrNr$FCܔ1We?TzzE =mCM.`eѼ'擓>_F痒cَ;2Ql"o\Is)c;oJ%h Á;pP r߬tɓ>1&;>y*2Ե|Z7]n:qhPUUUUe JɅ{l. g ʱ6 '=>Wfs,,~iSYIS[T>c9sr^* (w3]^o]UʕB-5%Ny<gmx2%i;:Y4DE.gH_SU8+dYEv+(iɣ(z7"Ex/m|xhj%W_}u=$$$CUU՚SU_Kw,>S^UUG04MQ`]5Mun;oE1 x@4СCݻwׯ544N]Fumǻ=iͫ޹u]['tJ7 zmj@cɲ0L>}>|p"V 8zh~,V4s5_1~y!Sc":b쬹"˲*,>CvʍFu]Q/2qfo߾Ǐ:thTTx@$ٓy$Q=^EE%h!zpW%i(JbF7 ϕ}6?Kj?|h*Z}(J_DcՒ(O}>>x^I4Ue8}>$Io/P@fu֍8e9cB]Oct@LDeZ2>2)]sJ6?)}6O.7!?- ,U/zr~ɃJ1*Y$!XF]'"v{ttt =^;w111 =^F2mc6eY<7M|-#{^LDV1GXYm8663%dJ+Q޶=Q0 8yyAl6(.r0YZ-c`U.3˲'_<ջC$ZŌ"~>>hY1p 韾TPhYXy>!um5c{q}2GNeD.۝d6ZE9q֭[gΜ٫W(oY֞jBG%^&&)~O-Ƈz~m6[)WxfiӪqUUmhh8 &N8h o ".]ڹsgMM͌3-1z7mD@vw=33[n.m70v=55UUՍ7S>cYSSSv{0u:u*rq\Ϟ=322dY~/˲wÇO>]WW'r{1 #B\\\ξ}ԟ8qbs̱Z^pc0DQEzݳ$vtttLLjZw;v/;wj=u`t]WU5R^c塩rm۶  acݐH]s΍܈9cǎjb: pr\m.˅6-V\Iyc IENDB`workbench-1.1.1/src/Resources/HelpFiles/Menus/Help_Menu/Help_button.png000066400000000000000000000147661255417355300260740ustar00rootroot00000000000000PNG  IHDR갧7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:'$(`66+&87&:#IENDB`workbench-1.1.1/src/Resources/HelpFiles/Menus/Help_Menu/Workbench_Help.png000066400000000000000000001647461255417355300265070ustar00rootroot00000000000000PNG  IHDRG{x ciCCPICC ProfileHPY_Das@ 20q`Beqր (5 X wEA9&Tnz_ wrDXTa8ۍ=<k7M wwшnqV4.Pq4n*g\8aЗ.B0M,1Op|HgdrH̼B@9DaƩfa?SϸeVxA(_nVjJrH)3FQoE)A ~0,dq ^' D>i }8A,oφF,-cOxY^+y@%0HA "=~M˻et l!Ԙaina?f;{ :]Y @ <"f3<@ЀPZ@ {i + (pA"HbցM `/(ap @38 .: #A8Q!%Hҁ K 9C? EAB:h TCeP%T BP?tƠ'a f0f~p(ë8Uq _o<(@(wT *6 P%*T= ՍQX4@>04].CC7;7C W 18`ؘH ǔ`1.mcvXl6 [=mcI39q\:.ww7}xK>/oƗkg) A@$ل#6 a(K#:CIMRb=D"iIKIR.tt4DH#1 vQr; BѥR)%c)['Q\Ij@4AZG%B:GD q GfLL̠̤,UB6P6UHVNNWS''wX0EբS-#. KӣiIB Z/mB^NZ>\~|9y Eץ)S;O ,xm  )*+(6(VPTJVڥԬHlT9Kr""EEN-U9ң2*RݯzIu\檖Gژ:UY]GsSza54iZ Z{:&յhӮӾCa$yUYwTOQWPZJրilp61L4,7a c퍅Uƃ&dIIɐ)tiK3mh]ff_mS̏?lfВkYnyˊbeѪꕵuA6T%6[m:lيmmb*4fycfG[tS88&;:.[a'M'SęEÅRU˕ZeJbgt3w5wwp_( ,|^3Ves5 _;~d2'b%%< 4@vGAzA~Y]|`u!Ԑ!!BBw> Q!4\y=J9J\lﲑ;Y~uVJ< Tq&qq\w> +oo,)8YSBq(߉?X8.p ^%$Jz|4y:%"!*& ;WZ_d$IV;޻zB'NҖӐ`ӓ]Psfy懬kddfo~ZZڎu6ZZ_cƼ#޹67%ouoDliS||qVǭG/wն۾ ~.]'laNN;\v+-)޽dwƞ=o{ĺ>⾌}RҖw\XvܭBb[]R=TxӏVzW6UVrXHV.n|槿+(B3y pHYs   IDATxg\Tg ,,E*RP)*6`4DMXDMԘD`CETRDE:Hy1s,K >)g{̙3< uuu@ BN"L&SCCC,x @ D?FQ(H$ky@;D"DhTX `FөM, ϟZL&H?{^,d2YGGںa&0 RWJG XHҡ"zɓ'L&d`rY,Ų5jNP(r竪2UU @"q9fǍוO>UUUe2** 1qrmll$G B$Q@ 7J1@MMMIID"UUUQT555**'#LL 4 t&7335533PSSj9L\EUTTdgX[nc2%#^`X円::: L&}d2YKK`(++L^VV')L9L`u%2Dxb1 ZôT* rJG 2IKK5j1h"mmm1 ̧RVjjj B=$F惦ZKKK__NEDb^;5d00yuu_ɩT*B<>a /=4܇8Cp Ð@ d`ٚmM"H$F"( |v577LbT*|dɢ$39Ԥh0-c09BduuJG 툉!bX,c% O0X,uqqIIIaXNNNd2@ zk)++S(\][r9 QCu1yF~P n_:IuuufftxVV֣G֭[' <ࠧG+**+++nj3z@1g~ 'H0\fro}@XzLtX4mxZX `fkjj8bF022***с9>xb@ZXXYeD"vAII 6xPa&NW8D*H$2h4X 2pUb1\(9 aAAAxÇN\XXX^^R<h/(J{{;@$TWW@ d.r8uuu\W)q(*39r9vvr ***GCCC:9@,I@ :_\2227o D|>/>^~ Ϛ* >A.O bN8`0B ;#Ժ___KKK|5^|) X,V[[ J%Hᴴ@8ZZZ\*&g0еl6MCCCV&Qr!UUU8kq\֧T*YCC:b'Hjjj,K,?|6lhooht=1m TTTZZZX,RzzzBG^`Zccc[[|D"2L&`T*UWWW~r!%ڊ_C3F%%%6 C0 kii!p8p5%E &:c_\__}wt :SFFFPMG{ H$omr(2kAD ovGN_}@?B6"CjFFF_ˀ@ _C@ (@ @ @ @ @ @ @ @ @ @ @ ؽ{7ɤP( b!X,_|ׂ 44tΜ9O< ---. ZdddD] :H]@ 6mdhh}||tIl6|/Z… vɓS%%%w]vqB:7*F&utt^^^0vt^XXXW F[D+LH'gJKKQΝ;۶m۶mo&3BTTThhq=zP( `ܤ1t3 1(| c~큌l:믿N>=..,//߶m[AA1xk3cƌ 6y5AIPYYb yNQk<{ܹs%l$MhqͥƍCKCbF!##CBۛ8B2Rǯ^z֭0$<to&???88)655 /--!#)|F'2Zjڵׯ e˖}sC^+ N0Q~|tT{ggg7oeD566S>͛7=DG5662L HkO<v lmmI[[Db2=f ]ygP]hv1$llln߾t̙Ns&@P-3nU (y2t[c>Ȧ*su0N"Zwf񙙙7oKLL`@ C>_UU@&i+k`P @6 /hԕ O)ĐePՅǿSZZZ}-@Bb!-nlR]M>]:2]@ ǼįDA%C>ߕA9@###}-?I:hwѕA9@#b2(r~@ qh@ @ @ @ @ Fʘ^1 k}:Wf̘ iii@SS](H$:DRBD<E {4!B144422"ᴷd%d2Y,s\. @ @Pd2X,CRd2-"Bx_ b1*YPd /rp@x LW"h4J%Hd2Y(0ȼ@ އpIOOQFAtt)@(FGGKk29rȡC>}*7y䨨(ޓ9hb1f EЋ" eTD"QG?ɟ|??k.Ç#""v_233ݻjժMC|~CCh4>J"ZLyYwX|Y#fhh>>>IIId2ݻ߫WN6 z"*++>}Z[[btJE#D/eY''NӤIimmܾ}{Μ9߇oӧ/_n``0|&v˚5k`8_}ժUyg 0~ܹZZZ0ٳ&L011_:dmmd2MMMqwߙxxxdgg]\bHٳ[n~Z]]qԩvvvT*޽{qqqO>p8pa@ y7/t:ZAeyvj|Ĉ7obv޽-[yuuuϞ=,^:===--bɒ%0mEEojjd2<<?Ǐ766~ʕ+ŋJKK###׭[c677߽{7**j߾}|͖-[ǎkjjnݺ5vX?KJJ200ؽ{w ð|%%%##aÆikk=ӓ^~ŋ<E X,fXmmmb˺oI#@:ME<<< ݻ驢RRRrڵuY[[[[[GFF&''++!!![lċ|?t>vϞ=ٯ^qF```UUkAAׯad:~ҥI&r\h;yWW 66 F>x𠍍M@@Ç}ijj2 :L F0aB[[[nn.TkjjjjjjZ[[l6bX<O owFhhh899L$9{`ĉwxp #ËRcccÉY^zpNzzz<ٳnnn/_400hiih4CYYYDDGƋʫ<]UUUEEEYY`9.F&i4sMMMiiiuuu_ @ C0/{/]]]|S|||=Ν;׮] P!(((q(++9rĉ2#444Am#**[<==ill A|X,VSSӣhvFYYYIIIYYƦ 7=W|rIJJʅ z@=a^^J$H&M*RSS삇Nj/0zǟ8q&L077(78WfܤpfHt1mmmssS޽ fOZ֭[gϞ uuu]jjjjiiQ(* gh4Nf|>k.^o7,EIcD"RRR۷H$UUUE@ ==a^ G9m4]]ݮ'P('O655ƒ***pj@&cccF9rgEBBB:hѢ7o777SS}?^MM piannnaaann+'իWoڴiŊhA c0?SXX/ӧO ÐJ n^&&&䯿*))T*f=z2;;0<==QqڵKǕ+Wr8ɓ'bbb={U[[;uT 11nx5 f&Lڞ>}4sL .]ZQQ4;z[t( DP(X>ϝ;geeuU7o,X6mڄ|ĉ555ΝѢɶm۬.]mjj7nTL?ә3gمʹsjkkm޼[X++Ǐ|}jj/_ =_ݾ}aȑ2 zꕕՇ~;vرc^^^p/XYY_ݻb Ǽ ^8;;[ZZVVVvݼٍ4D"JOK0L}}.fE::dB]qq,Saa˗ϟ?|rkkP(c~'.]:|ܦNZXX;8}tCC;RRRRXX(7~н{>>Klƍ|>?!!aLXSSNq8e˖uZd޺ukƍ+++÷ #FO!uM|>wg999)))wP(׮]KHH8q"?\G60رcOҥ],#lmmE"Ν;WZ^p2. %|TUU0,--8zzz\.={_-'Cx:C!+HLL2e?0~ |'?ts BIO-ccc ~l6?CC =Hcdee͘1EE f^ZSS3c MMM2Lhp^|9Pvuu9aާD"L6443B  l6[CCGEB  n s/_>k,<fDPWWpBy \UU oV7o{@N8! H9ީ *tbX>PjL(vΦR+V!'44f#uO0#ꫯ$c)@yLVyYo bٳG'MMNs:s .\|J`s$HT9ׇ1PyYo[dOh)OOOV \S'NHJJڰasRRRRRKį>db p\Hގǩi <aRRR7D l($\p\BaPP={N|+%$555xnĺDuSSSGGK񺽽R"RH, }qEř)))\.l6[-1zږxWZA$o Q@QގaX]]̐㕖Bd֬Y{cJTYfikk+++õDx ][[K;8s犊&i!rz&X>ST'D4iR7^3K.zˉSNݽ{7f`1c<{Ĭrss sxt9_vvvӧO†MleeC@||ȑ#a7w#Z* `ĈGYܕ+Wpm5''Ɔ3L48}4^:thҕ:wܼy---C?::^}oXDhh(ggg7ٳ\rss HC[g{…_|ѣG<oɒ%vvv2$b$Ǜ?3,X  )))666cƌgƌ...۷ocOz֭лtCCCWZyt{{bcc/^ eٰa|`ffhe4666mCU fΜimmu>k׮O%;tZEe̙3566FGGaN&a bnnM :4vX*0fO8J_xa؆ L;wWCrrkjj\n{{;---mhhhmmGA-[i7{dv'N̘1788x̙|>YUU 0OO+W`s&p}Ѷm0 PQQ޽{ D#F0220pĈN2%&&ðw:99otx@tN:e``PTTaٳ݋aѣGCCCa_}Ֆ-[Á`00 ҥ<{lذaYYYFFF%%%/^PVV.))0,77W,y]xcii#],}oQ9""^8p_(be˖9stS9xŋ1 D UUU8;;ðάY;::k33Ȭcpp\˗/?{uԩٳgwTe)..6mڸqcG]]];a۷ l2tC{xx9r0cccu0ܮ_ޑ$xVܼyoZ6rlviii}}}KKK}ufz{{㺿Abb"b0pڵNުZ***wmll??d(w!44TIIICCĤ033311155 *++---aYfm۶-***###222))Դh:^\\,q`?~֭qqqx'Hff&Bo奥d29(((11:::VTT奧wYjÇ/_.Q+ +pssU3,͂ |}}7o\:UG044sNfffee%\} vrʢf:|u裏UHHȚ5kW^ MWYN>/^˃bqNNNbb"Sb_ۃ ߿`0YYYpċ# tZ%FCŚ NGL&dv12$RHWl`]P(b*7/W\iaaYp{.\njj$=0 Kt 6kkЩY8 |}}kkk7m$39s|'iiiׯ_?HW WaaEGGFEE]vرcrJiiiUTT|~G'qd}IÂ['bbqWl۾Wъ+F%V//^ܲeʼn'|>_SSS::>Ϸ,Yt=̞=DsN>3 q`|X,"bor%NQAH0ExGǀTRRhffFKt:oƍǏ'~“SSST"|||$Ρvvv...ׇED7>>/ ...//[fK, ]`j+E0??_ %$$ȌNgϞLܹsƍYYYΝvvvwMOO'k׮A'$}e:۾?%'OI)wwǏ9::;j@ZZZhhhPP>a„K.bPx b%K;wŋP]_G@UUفH$P~ioo{ƍ*IGѨT-$iiiDCS(3gZ[[\"G ( t)))QNAƀO w?㧟~0a„ O > |ԩS$I__?==}ԨQ-?:rP(K,q\ϊ577߹sˤIlll桢S[[kmmVQQ뫤QVZ8N a"Oshll\4@Ecg_D.  %9tt-])PómnnԄ IJjkkK+._|M Y,:.DA˼uֆOí2ݺcvt[[[% |>]]]Xh`0$2DDmC7YjiiiQQQQRR£Ɍ/lDO+74͛7xY @EP DZbZaP(K&뀀 ]W__i50pWN7>H$1ƭ^+Dڼy3\&>ˆY b*~ibtRx%^PB$vx|d`` SRįi4 !6L'p% P$^ҷJ~1ˌQVVƝJFCL 0^:JD}*wT;~9Mf|2 =;D#3 Iwtik^{1....$5@ bːHO" a!!!}-@ A&b^ގG}$x"ɋ/.]DtW())tRx:. ĐB~ ս{vqAbc@ CA!(((((HQihhܿ͛7\f߽{h 1A~ޙ 6@]v￸< ;u ͙3r,XP]]'?+$$K.{oݺ5k,hV,/^XD---HJJQ]Y`'|]̶Κ5ɓ'W_}m wʤ?]@ . u7o^n|ʕÇ9rd۶mTMM?O/%%N_|񅆆FrrE~:VRSSs=+ uuu%J!H3f'88?^fMrr2JD[z{{0~uu5~\kk; .!B(ּl<===Blmm-,,bqZZ/tRKK׷j̙_tI`eeeVV֤IlmmճE".\_~/]'OTTTTUU{xx??ڦ Dׯ=zU:(pa @  ]$)777##ۻ"33SKKKCCwϗ>f0y;w޻wO__: Ɓ'NcggWQQ3qĻw直XZZN>}ڵb855ѣ23fybb"D>}:QׯY,D'\8 ֦@@O߅@Pܹs֭;wc#7779NO>=!!A[[; @‡t4fwwwbI84e+++ Ν;D˗/~g"dѣGBC. D]&zzz::: ĵܶ6X|% =3gfdd?9plׯ/ooo2~aGTVV2 ܺooo?q4flhh_qSS|>;w}9999 ϟ?ƘA~߂.t6 Oh:::xaÆ-[lƌNڳgĉDlmmGalllee%O+YMMaĈpݝbud100駟m&/$$$L>?_ xzzXzΝ;uccʕ+vyUw@ 7 4/C'Rl6FQԆTp8\.?'hhhPUUs~'R DJ:Na=@ &D3fJJJDK nk\jDYf4AEEE汹 DGG1bXh^ypzt`. DEeH]ǘ1cZ\C"~;bD~X h^(!9@ bA oA~z @" 0(2uLHHuuu=zSϟY__ӱcvŊ՝={6cbb}||z.!B(мl vp8...ߑ#G1b׮]ޚ՛7o"""h4ZMM """BCC. DE~b|ӵJJJ,--Ɍ=y$5tX~kkk~VOB3gSΌ ^իWxG=x?7D֭KIImz]RSSKKK{(4-- g>ȑ#B!III]9Ek^6=GSSӘ1c^|)aخ]l?3guCCí[_Νemm/_믿~w&&&0<88:&&QXs77?yyy<7o޼pBwns9sΟ?~z=<<8}捧gWԅ !=ۥS.^ɓ=ͅ666B-O =;}S^x%~=]J@eCK]z1@(|hGIOO8tPaaܖ.]zΝ2s炃L&ŋJKK###׭[+**~ᇺ$ݻwfx ]p?ի}Ʃp86MDO@ b8333%%ły,@ Y,ְaRRRx\.^|VlvjjjHH`ƌiii=;;Jnnn HTYYIć2sŸ yb"֮Qt7oH(jK i c "m: IDAT1 xxׯ |>a5k޽{y|~}}=1-//r>&H$*--J5kkkBaGȬ !NAZjjj$ܹsEEE\.?~|ҤIrPWWr%,1 lmmxxv;+KV@ '##cӦM6m駟D}||`wvhgϞĄ?zƍUUUx>x𠍍M@@ÇV>b@]0̙3֭ VZ0bĈ#Gx<ޒ%K슋I۷oo/\˗/;;;?z i||ȑ#믿ޱc⤹r劧'ɱL&>׎=:nܸyYZZeeeᖹ˖-[v-644:***<[=8q℩ihhQll,盘̝;wɸzr'O&L O> >|xtt4ܹssww3c [[[۷֮]ehhx%"իWqH"!-[¾;@hhvvv3gδ|>x͚5&M(Z}脄8;; B&44tݺu[lYzԩSoł gΜill\PP]x1 \>DGG;99-ZHUUU,%%&""b̘1+?dX^^^cƌ{D&Up...r}ܹ&LXpH$_v6X,?~HH͛qZ[[]]]W^ѣ+Vٳu֯:==~>>>o޼ jmmuvvWbXuv] \\\6nH _BO>ֶx%_|>cǎ-((066k֬ 0 .@ܾ}0oom۶LN:5bx=nܸ_^߿BHNN>~ׯg .--oiixxe˖'%%uއN۷1 zI òL&:pP(0l˖-s0ѣY1 ꫯlقa?!QG}7""BEEΝ;ݻwOfqҢr855/^`iӦ͛7Äаð;w.\P$<}T$1ðB_A>ܹ0ool8 ,--JKK5551 {𡚚Zss3᧟~9РJpԩyxlZϢE...t:8##c 0o޼w*h֬YIIIȤ:pXs`0!!!2..nҥO o|gdrPPPbbbnnn^^^RR̙3yΘ1Ϟ=srr6mڵk***\ݻw]\\F ?~|b"##Lvbo8ܹstԩS=z$ 333###:$Խ4. $l2> Yt)9ϟ_dIG]` ݿ6 ۺquݻw7rJ3fLVVNjUf@]xqqq^^^**** t:qV,Sd]">>>[paRRRRRRPPXL.]zܹ,uuu[[[b+VXb۶mW\̚5+11뾾D SYׯP(.—ĖHofŊϞ=zoŋC,,,( 9|>xiӦUUU={,..nɒ%rJ׈F!]4ϗ%!J. 744ܴiرcg͚%3/_ZYYy{{ g:* D"eeNCEEeԩ]G뫡|1%%%<csww>}zRRJ]də3gΝ;ٳg߻w^Q,sXv!uA7o6mD>tЅ F-~iannnaaann+'O>֭[ [lQ@z/ ra| T*jff~yX'O A]]]BBL`0Q:qF___{{Ǐ_ǧ1**8pvv...wttttt?x??<)(OCCcQQQSLxÇCC5͛BFZHCCӧO&gϞxmmmW^uwwWRRrrr*))Қu1&Lp%X, oܸ!3J ݰa1c:-544L:uϞ=x8pppckk+W`[UUU݃|>_foϻw% 󨨨?sP%eXb'---444((HUUUNnNNN7^騂[[[:ml33p[[[RPݻF:r%ׯtի-Zrbcc/^mA8@"*++UTTZ[[2`Syr$=J5DonZ]]mhh㑝5qAbb"bT0tR}]VV?no0 .?9CfΜ>~J__?==Ϋ=zFEFF'T,|}} {ŵ= JQ@DD#U(*FEQ%L0|11jb4FѨQAAQ,HƯ7[DĈ21ssΜ9#FtssCڴiѣGq_sN=v옻;h4d &?w MLLldllаxUVN<޽{bˋNNNeeel6_=Wh4IJJ333Ԋ^GGDZcX,@ b:N?쳐۷ot]]#FDӧ80w\LKKCʕ+,ynsqqѮt+@KKرc)nll9sܼxbwwwb7M]vC Ak֬8pnZZˆY|O @Oh |[UU4>>d:88qp\ .^8))i <~ *njj*rҽmbtuuQzHHHddn޼cǎݸqѣG_zuƍy̙(Cffƍ ЀFZGGGMMBH4qQFhɓ'92"""::Zosг~hZ:;;tZI0 x, l|]DJJ B@E555J!<Ԥo!ZZZT b1޺d Ia/^yuA$ɼY&00PZHi9QMMM7- bɉR1 #666*))׏TݿpQL&RhmI>l4b<O[[!  M?y$00@J"eClHڄSN:uQ[[/6Hӿ+T^^naaںufM x%FիWċӦM A][4A /7E=~ oIo-hxOd'YQhףCb%hXȻXĉDu8AI%f >n(Ei𲤧|AzHtVH#+SxPM|AiRYeClH^t:K .QF[N"#tȓGCC"WAtT921X #_77N?ȑ#&X,w333'r>zּK]+˗9sɓ=ǏcbbPKOA> }5/+w'Ơ7\__rH+.] ER߼ysDDݻw{F."ѣ$gDbbbF|w-ŋ_Kmm廓Oѳe}E];w.#=~Ѽy䅨ZZZiiixTy_>z---k[[̣dժUmE;w,..FEEE]/׿w!E_g0p@???x}$11%|200PKK9l]5? 9ŎҮ'<==Bff˗/QbXX؞={njdd斓pG! XO1`K.!v|>jkk񉥚Xpeee MWBqBaRREc<'9I  88xRPPPt5/+,X̙3MMMPTT hjj ̙UVVrJ?ЫTč7>:>{l[_\\1Xeuu5É.++SRRڿ?Jŋ/~w( ˽wA D3gzyyֶxxx'OZ[[O6|D N0ҥKwww{{x{{{KKu@sscHHX,_l6R:իW[YYچ#nj+owL2ECCy=#*))IOOvvv.,,|^*._b qI< Ң"bx33Z774ĉg w2=M@ˋ{ѣG뗙b f͚|l.w Ǝ[^^p0 {nbb"ѣܟ~b%''_~ÇϞ=wޡClllbcc Q_醐xNBVC-))yGAAQуe}ȫҼy:dɒ~R\\;eʔuuu++#O>} ZXX=zF͞=fիW/FDxl&ޟ]]pqq155qgee]xlovv6rgsvv>|8=zҤIL&b2ð}x}h1,,,WZ"C mmm---UYYyӧtOOϯZJbΜ9Ϟ=SWW!o߾ӑw133G_wwf"+3gxzzQ 999Ϸml6ѡ V,X؁DBB6111b@SSsرÇ700//W^)V O<#~ܹC+vŊgϞݰa]hH}!FvƷ{nKKKKKKxT^ `0h4˗/tuuѐqH7$򂂂x-=k^FC p8ݫyy~^S,Zc 555eee/wDE"2(t=R?hhhNx h<544hkk`l 99Npp{nx(((㣩d27E-ۅ.NzZt"=0փiyLTpҒYTACѴIGGqC644'dy.PtEFhLo wѳ~ %HZ[[{p˅{^zR110z4,_SRPPPPs0 tFotJ ~#ٳg_x,kfff|M %%zѢE=V/g}4BGoϤI`f** [BdH$jH|}=cQ >BAAABa(R.7[ IDAT>.)SA ## %((kk==8|ZZpp'G^S`Lpu//058''ؼ &,-aL05.`pغ ǁD"Do6NII }K] tiӦm߾.\hee (ЬYD"<eddAPb˗/322Pbbbk9}-x<}{q oàƎrp`Rزm3PRL`f"xMq!/c@ PR\.L 99p67ii_|~N :P(E"D"AbX DW^lhr|r&MSp?>--Ç.\ǏovM%%%y7ncݻӦMÏΙ3GKK,**jʕ(֭[;wܴiӆ  H]]]EE#\P(|߅wԩ0irTHw {PUUͣ..`j `cم;wX,6m(˃'Of8q>dXRRRSSS___WWP(D"P(,//ُv+hhhSSS Xlcc` ysMHHϏ=#.))IOOONNvvvϞ?G0ӧOkii.^n~`mMNDb`̙ ˆ W@t4\ = rc, }(!HЌ“'O/o\;WSZZjddd2bqtt;;;]Ƀ ݶm[aa#Gbcc3{4uR\\ԅ"A~ `oyr77QQ|+pi?$v 3}AU/~]0 ---ѡ`0I$ڑ#Gjjjo_KUBaBBZ)C̆ϴ+C? ...׮]C(@ttWğuuuoзQoo,t>?)ܹo&OGef o\ `l lxx-Ch( zzP] ~o@ysssGG G"UWW6ɓ7͛Аb400>ZGk֬&&&}Q[[D"!zh affO?555UTTH{)**7 A$kϙ9Ԅ hj&re /^ {Gdt:QUUmhh@|>U(0Sr7رcNO-,,>|K.xZZZ+W\hQ\\ѣGi4_|qFkkkyy5~ለRzBB6111bH7jԨA͘1} ~h4PVN҂fP|T:6 `0>`wN]]PQﮞv)((( NgX,K"X,##1cXZZꪪv})V__8Gvv6yk?^|괴 vttkkk+󦴶t%s}}=z!55GCC<|RMMMEEbjXX?vvv~wK+ܾ}[EEd2L%%%%%%7***!D"D7o-/.F@tfߙ󦨫w} 8 >NWVVFqu}f5AUNJEF! B) s^ EEEEEEĪǏy"ѣhz&??ѱ%..]4wϟlBuʕ+eee^rK)]fYYիW!ޢto^oooOJJ)a]燶RSSoܸAF;=Byyy{aaacc#9߇1(uL4ʊ\ں7_mkk۷߸q_}##ܹmKLݠ_I$vx({ɓ?V,vii){.vݐ oQ +E'ŋkO=3f@nnűc/Z{;=Ž;ߏX VVV|tvvVVVR}DzDSN &ArP__^2H`VUU܌v;;;1 kooxlBMJ,`.]߿?bggaykmmwŋ=T(&%%q܎~'X,"> PFeee1x[GG1Xjkk%A'BvXkQT.Yc(P($V Q صk/RD[[<ϸD>}?_%zDa#=DðJ\w"$FⲲ2|ѡ`0k)%j2+W/^|ȑ/ݻCUԣ/^MN#SPSS#@]Kst%>b RRRV\}v1sG9eʔ:{lLL A!•g0R~iBBBGGݻwҥKk֬7nܧ~jll\PP innvvv^tĉH$999$Ip=>>^]]0c(l{{{~PewQ|1c:#G/>p_N|>aNBsogDSƍ;s aO>UWWooo'+V|7IIIs?z{{K$F{{ 6`&ݖynݺѣ ىbXi.ޢ└*==n믿ѣG899YI8/߅E۷/,,4zzzǏ/**z!v___|ɓgϞY0sLvO_~Q&N@fTUUI1u\fw* :~'ΝSzΜ9#FtJѱh"-yuaС_|ŢE,X@pʚ:::44T]>---]]ɜ8~8. ۸@ zjppԩS̰̓a3gΠ"55weTΝtI@a*ccc&-Urrruu?:,{m۶%''* ذa243f0qơم+Vx33Z774 s" ѣAAAė~m}ރؾ}{XX]^^ N . קhѢ4\ZdKcc#J\re[[ݻweffM0!-- 7d馦#G011ٲe'|baaqAؿMFeq ݽ{eXX;J>|ܹs8BBB"## fkkq1E/b̘1'N$jkkO{lybNpѮ<#F@x~"<+@__?(($//믿yJWkaaf++QFm޼F-\p˖-ƍ;|0'/.Gy׌SYYx222 bffd2e*i(Ənhh`Hw;vpttttt )ڵkݻ';MMM&&& %Jvd l60KK={CvE'NoR>:{Yggg{{{{{aÆa\yܻwZp'JtQXPP`aa:jkkl޼00o͚5wϟc_a##mX^^^WW+d%D'yMMM:"ЇvttxEdmmmG"<Ub1IyRðz򫮮ODՊzb6444477loHfAD,%!"="1W0bNi1  $vSH,Qu$]ffd|'& Y-'z>+R'Wbw͌;ɓh2FUz(uK)i1DlRH=D"!fV7yP=gddt)Sae&/zzz>|}䆬𜆆 ի>i?sPB-t#X|Jb DQyhX,aUUUR FS>x`iIA札tR<)Z:N*.SxC2 h482J;CyeP * Mb7iL\^tNKk2UQQ!zGɕf"rẙ+N8^L&_ PR:%-q799$r*-6:M t>&bē'O_>Gr劒ȑ# #G/] Y,1W\AL4i߾}hޘJ$9s洷o߾!gdFJG7n0L"s000PUUʪOqSwwTTTRRR>j(|_%88x۶m[ 7ʴx-}E]r>>>#Gܿ\\iŊgϞݰaCW;vѣnj3a:>dHHHFp<< o .PPW())!F\+Wtn# mmmIueR^^NWXXRVV\}ٿ?EZbqAAAJJJqq<=_\.ܹsO>E)))W&Z[[oܸA5wߘ0aǏ-- IDAT{] J]x+ z߅ǏZ m 77ݻw݆quE](--%YKtDî|Bc+V ONN&e$Irvvv~#Gg̘u`Νу%e??s;wjv0a©S;F@@-#(uN ի=''GII KpqqAbcb8IA^QH%'9jhh@)ӧO]G|HXXXPPPvvvRRRaaauu5RBaRR%SM(?#"|>LZԩS=BqG :;;jqdƃHHHXr%=N&AAA D B ݧw]UUUE^rG+WPpC 6,88xȐ!(@ 022 0a^tttL00:u*((CqȒfΜejj e˖?^__E~#;jN8qfffx$ip ֭[vԢPૈe˖y{{ΝCcXZZΜ9ԔظeddجZj$4hЩSЕ{!&Xaaiϟ/Hˍ7@ggAYi"jjjwya``Ȑ!t:+NAAѷ Ο?.[?}4{hmm!p111k^AQ322\]]QLtS^^f͚۷oߺu… K,AnƎ[^^8`WWW{q\K.ݲeKZZ۷?쳎 ?~lmmBG/N>Uꚟ믿n߾]A_ 9pzݻwR=X"}wtt<~ԩS999oG1 ~ٳgKJJ>矟9sFz~… xbee%i"""/_rN81uTT͛4 p႟ٳgkkkO4 DA >.{zzFFFvvvZZZ>믿&%Ǩ项!"""88O<|DDāA/*w/p8'O{{{_|֭[NNNȏŘ1cB|@TT^w})L&"Ν;.\ڻwP(Df...`ccޕܹb?okkkT-;ʓ `0мׯ̦544jkkܹ`06m _o۶Y^w܊CCCgϞfG|>?++ dپ&L)++KOOϑO_$?~ߟϜ93>>^tڃ7ܻwoРAxz3^l?(󨺺d;Z iiiNNN {߾}cǎ%Cb<QY`?*_Y"W9s3 .1bDvv6` L s瑇aH?Q>>W2e -zBB¦Mʹi}zzzzZZٳgQ/^x񢮮w}ףUVVrJxѣGGEEܼyKZЛ7oYRR1VnݺsM6mذdO;#\[ u߽{=Fp8hl˖-NNN('Z& Αt:gbt:]__Ĉ#G2e ;vptttttDyQAb%rʶ6cccw-.I* F7"##LLL8J+++h``0};w1DFFu鎘qO,,,Wl_|Ř1clmm'N8j(Xv-qN_^__(77ِݻw[ZZZZZlÇHکSfee͘1صϚ5 O;wnRR̡kllDAA!b~~~C%*-->|sٳglO>SL@&MKLL}%%%&&&fffNNNUUUcƌa2o&6:m4FFFb_~O<А$}m޼ɓ'ZZZ---rqqo333zСEEEBLq8MMMaSWWGAMbJ `&!%ISSY$b1* QJ{{;a@!JJJ;^ͨ¦&޺ukss3TBTm!$Hv|I`lbDi1 {⅖Ì'O߿Oojj&FASS0 B%%%`/1):tɓ' T3l0(++V6 nL#GDL&ɀ\\\uuu˥RЄ/^L4 }ꦠ8p zICqh4QW:NN'f NqJ#BZ˒L: Ķ$I~~bTmGyR@6<~4+hrr8䍘zQyKb%>Ɖ'+ꂩv\\0 P>(u0 dӮ4‚ ~gt 0ҥK III\.yLLL L̬>-<U[[b緷OvvvD"|0@KR=ҧƍ0`D"sΥKPm\VÈaXUUq4pH>_VVF#,;DX,0 {ŋxGœH$Dx<\(us  H;֭CjjjQQQǏfÇ@ De˖y{{+A>}TWWpmsss>?w\;;;kkkhccWuÇ?|.ZgС>pf͚*W^meeekk ܹs ]]]ejjJRPnܸaoo'NصkWrrà Mfnn>|I322p8(.666V+WڰaҥK]$3f j⣄RX,Ra\Q0NޭY,bjj&҉'ϡClllbccG uի'OF•+Wﯫ{ɽ{|||֯_~ٳgKJJpfΜ8f~uֵ8p@EE%++ EaF0 +--/,,{,336**jժUBӧ([pp0`ddT[[kcc"y-!755IYYYH6KBC-P[ 믷m :"RD"ѝ;w.\w^PHZsqvv>|8p8%K 2cX%%%𳣢aؾ}B! gAAAJJJ =z4`֤((>Vݼ<޹s~PRRJHHפym%K/@ӑ)P|}`HE"ѫWltȟÇz OFt:MQ,k„ iiinO\RZZD"/׆ ~iD0&)H\]],X`˗1 D"DBy)愤%$DB ίLf2;mgccڎ(̙3ΖO$񴵵o޼iaauVR.榪.s` 3tz)(>&0ydƍt:֭[h755;vlժUMD---qrr2)b7!EA("7aB\SSk~XXXۯ]1s QlUUѣG#]ѣG!666޽{7FRyPVV/--366&-oeee=y?J*.Sׯ{100 166?>><>>>DRSS3665jԳg϶mt:ob׮]<=Vutt222>{lؿ|r&9rO>Ak֬5j͛Q}a޽{544&Lf1 C[ϟ?Geǎo㢪~fc؅a`dYCQ@0CQ׭>ZV.ۛH C,#0qnwf|?~rYss{s `{^v?x'.]TPP?߻wO<۱cŋpWCRG!u{ .s\|u`3N{̙3{0\@hhhffCPP7޸qD"!ǁ/c@x<}]u,𢪪* J?~fff&&&j ;wMJKK8TWWwvvoO|)L*JuuuJ\.H$]]]2r\ccW)saii9" g suu}Tϟ{nCC̙3$/CTTT\\Kf@ /u?>s̻ سgO{{[Ʊ?-,,,C'M߯[499Y[[>d~~~<355UB%R COOD,9.mmm#Fꪚ#^1l6f@Bwwwww#k#`ܸqW\ :9x tptt7nܡC:;;Bw}mggZZZZXX??|p"sILL$͟ׯ_0LTB MX[[޼yw߭gEg2FFFVVVnnn<bikkÃh@b@&L`cc]|]]]5j￟hll\\\éTcʬkjj233krʔ)"&9a6=f̘^J7oÇIɫWvttsL>&`0A@  ߊk[B4M7BP444p8U{.U:::Qe2:::L&djii1 :g( $ }}}\E` vaőYCThuy@ h@ D u@ D u@ D u@ D u@ D u7@ spbbIgiii*--g]]]Ν#J@ ׄa.lڴĐE6mڴ x3PE ^N޽K\ONMMMKKzT* LHHprr"& nZlYDDĤIT;wnPPPuu5;~i剄G.[޽{ڼO>dgΜTUU B}0ND ? م;puo J777dj"fffiii .V[}ff& --m޼yOEEEVppݻ]]]###oݺ5<0 i b dD kvaҤI۶mLMM՝]5~=z^^sȈinn +֭[~~~ODM$'';v)U466޾}/$$$h/'''11>q͛7i^rrr*..H$iii| mЀLI@P( ^x HNN^zɓ\.ޱcjO?_Wttt/ٳɓNFFݝ;wz/((1b׬Y< O.r\.1!"zQ\\ P( FnݻwϞ=}:x'T*dbX,BTTTYYYѾ'O>Ug!!!}ڵkG{ܹĝp0#-Zm߾}߾}_Ν;gϞ=zd- +oll$XWKFFƒ%Krrrpg$ jjjHZr{zzJeQQ?C|FDD|CCC|[+WI,BQWWG"JT*a"EVS`VWWG|46SVEmTؽ 100PgrMq}@PTUUhjjR T*UT\.H_ߏ?Xmjjj"JHDC ;1D#u_C-kN"j_J$T#;::Bǝ!!>>~ŊcǎD{ ܈1x@EEE\.wAAAR>br))):耚ݻnnn-[۷z ܹsǏpA:f̘88g`իW_z}pرc X"̙3#22 v8pY&<<|޼y:b=־ӧOx>TnLL >gܹs;v={_3˃W^]z?χ¡Clll---;Nj0a0a{⦆ )?IrwgggXXT*:}j=&E"QDDرc}||6lؠ}~599믿¨ࠠ ''7n2VVV=L2ĉIu|\~@Bnnn...I[RJj D2s̈Yfir(LbPhA_dggc_y?|ܹʕ+?SII/rׯ]z#;;n2vQsss8a;mڴhT* rssژǏ?wa=DRn߾-VXaعs{i xjժ?PS0:::o&f]S,"0,330P%1 sΨQ0 {𡱱qqqeee%H$200(//^B 6n8{l(sPPD"0,11qd2 cbb0 ۹sgxx\.0l͚5o&f0 ٶma:::xj~a O>0>5kr'Mti_~L*H.=zƦYOO_urr0Ȩð[nB۷oL6ȑ#ӦM[joJRXxi }v؁a@ Tբ?Irv簾&MwLV-[ ;wx}iA.v/'OT*===׬YC̤@WWwҤI&Mza-[BBB9~M_o^P@@}0 Dǎ#"=&ؽ{wBBa geeizDcF~/a#F`0eee+X,d`0Fp8EEE/^r @&MSN9r/0@ 'N߽{T۷/^?~3<\GGgƌ'NXvS.\PTTD֭[\-Z3RRRZZZ/^b~Ǚ3gBK8???؆PYf^:..Nqqq$22r:x^'4-<<<77<$$dȑׯ_7|||ƍ355---K.ټy֭[X8q⯿ 76,}R/{bXp6?OOS.vo"R]c{I*?(** 'OV>p ꈋv())P(FFFSLQ733ʕ+:W? BU3DrMWGG'**xC,,,vWRk2 ΅` H]@/^EEEgggGGX,122rrrrtt٫F! 8&%%ۓb}cMMMssڜ'MҢ V Jbr#FhnnfXs]reXXرcKKKl]Ucbbw^ZZ&RItJW\Y~˗8ZDr)sss8㭫{я?GT2LMRwňgr8ɓ/_+V4BF6/2''gʔ)Tް}rRd&H(Ш mzINfeJDV>k׮ӧOYСC~ ZZZbqwwNccwUtuu*V,!PѣRRRTii)qX,qƇ~f5eBǎ{y@~~>ɺy100000< 򊋋? ׯ;w.˫6qR__okksN /!'Nё#GԞH$'NܺukQQd&rEw^֛7oBSNk*_~\|oOMOʔ)׮]Յ//AAA7oBOk׮]ƒȘ7o^\ffD"a!Gt6Z޽ux{{9sSx@~~~||ԩSU>hmm};::z׮]/&Y4!GpTGGǹs]U[޽{nnnZcFSSS8p̙J[TT4zAPBсN>_Y x ={vJJ 4Ss۷oU-! 33S.3g_ڋ-kooiՖsΏ?U mfhhhhhpB Slذ!,,ԔJ*]vY[[pF͙3'//@mܸ'00#G7;;;㷖.]gkk\QQ7V7ޘ0ao ' RRR߈'N cFGGݻwB7.66~~']tiZZsjjرca˄߼y0l:CoĖǫ6q_pFJr8{{{GG)Sٰ}֭[5QD)6IS{#~Wm/[۪OHJJڸq> 5퐒" """H{)OܹaÆٳg[ZZ~l/4R&uܹs̘1cƌILL8q"1!3Qm%K0LGG@wwwh'1D#7Mb֭[UUUYYY_LNN644ܾ}J7ԔthI-   _&\wbR .'4looj3ouww3]]]KT.e``K)#{zzh4 dp@!b/` Qf!ޅ{tuu]8y JLLL`yGGq_xp> `bbBZR244$5QggB2#>ܳ A(uAbh*K>&5PꞲjm7{3&B!ĝ֐*g@CQCCCMٳg#FCU'aB^~&ր V9U!zx.DDDl޼0q}_]qHI8zRT& MA|g_;gLI/4aʥRpϮh<12` qRT) Ѽʌ>Ļ>JP๬Ry}Dj @'\* 7H% PT10@H Ե UAm[m z}&$?iT>@KK .Eh'w^P(xB\`bVcee 'TH]HӢ{[G[[ԂAbggg䙘k׮сaئM\9{~ڴiׯwrrS)///((hԩXrU-11믿y<^@@> a؞={\\\mlla9O,= Հ5hii' t+T*;,saW&Lj)`0.D/OOϥK[XXN|Ɔ?q8uVoe2ӪNNN&6#G,\~ʕ6l);uqq1euV`` xmvI pc0.fLMM}8#˅[ĺ]| ..{b1K []@ $1DxIDATň>_D'ĉ7o ' {?(hp˾@ hnn֔ry``Q^ˀkxxggŋ_e˖0a>dggλ{QUU\Id2.u?+f^ rr`]Lj@<>Dم>INNnjj̘1۱cNj%'@zz֬ wCZGGǦMRgP0jV&ӧAd$?=($_~G"d20#55D#7MGP444p8V=D-R7ДfڃI>@( /2L4dO8dFGGϞ=;..ٳg#G$Fkii!6@,3̞Na`KK.܊ {#GR(mmmgϞYXX(ZSSS|'H$LRŸq}}g}}N&===R>Do6v+F L&w HaW5ՂXBP( Ғ* _766R^T*R)ޤL&ܜǧE֖dD2@lp2  --@h0{6HH#U;c6,-]8\Ǐ, &46ss+P @$ Թ@ zc0hfmmݻП8}}~ ---շj~9 Q=… nnn|>޼y0$55u̘1qqqeee˗/d0pWV{DÇ===z-===?㱱?krr<O*rE[[[=z/<< EKV^ZZ;]RRҔ)SKl//////СC666񖖖LpyW^uww_|9^m-TK?~Y\.87XXG4.y {mǏ8Á:|yذ|Ib">rnD"Je2X,nllϞ=D BTϻ~¨vQAӦMJBPOOB 6n8{lbiӦ͘1zurr:u 1 GԳ욚 æNaXzz0 JJJx<aba؆ LLLJKK1 ̄x{{e طoa"رc|JJJH-{媪*###([ B!LwaaXbb1 d{^zzzEEEaV5kawNHH0LP,DR[[aR-))!3NzzzLL `eeel۶^ XJ aaV_{v_.ƏΝ0 {D"XHav鉉DT>yz:fceea11Pj*GY!` h= '>>`1566VWWhujjj=8744_G.Z:P{DvEEeww7>=nq8¢:[}z͚5"%f_ f'?UB) MHs Vhbbbw^ZZ|^ B*Jx͟?ԨQeeeaaa}ĉ|\T3>SHJj&x}rs}$/.& `T@bċ0C4R/Ih^xj򪨨077"ҥKda"h4xD}WWףG92rH6z]~~~||ԩSsss_:[[[b|ѣGSRR:::Ν;o>x-҂IT[gɒ%<ڵkrUm;w<{l]]7o>|ĉѮ]gh4TT*KJJlmmy7&tuu;::T\3TVaXԚkk9s@|<zzoܿd2ΟS[ sɌ L@k;;7*T*1***,,{2eJbb")IRRƍǏa}B¯ΈV`عs1cƌ8qD~QF͙3gp@`|>MG7;;;㥫BSg̙vvvnOK,a2'Onii6mc\ёGQ:::_^mTek)T2_oƒh ?V"(V59P:, p8LܳgE"R=r\;lAuH '+O<ðfEzdp%_L #֮][\\ vuuTv$ _+ z;tӧ/^Z߶6###9% 13cccb[XR Dbt 8Z-=###+++++tX,0ov&?~TJ^2ځAPt9kxpPX Rp#` h1B.p8__^"O4i„ ׯ---fff<{iQ@ 8}tiiSDDq13ыBe+iIOF<ٹ:11://oժU7Hlm۶q\xi{:aAAAwMVZ%]F?4}\U#11Ύݻuuu'N455-)))//OJJӧO8q"66>""bUUUt:Ab.]l2( oܸ~jݺuk֬<kmmXX~G Ā3CpQ6m:{lNNϿq>w+V;v֭JOOo޼yl6K/]TSScccCtwe˖Dwww[nd`Xׯ__hѲe"##ᶔ;w:881FOOO1bDzzSccc@@Ӽz__R! }zii)χu` &&Q(֭svv w~Rioof| @#[m۶C;ǚ;;;+++RL###ٳg .\p }xuu5`֭ .\tߓ'O`|SSShߋ?ݗ4[oh4#b!z:ݻZ[[IV_~#G߿o``x˗/q%K<˗/߰aںYu/8˥h?_~}xxs>x P(h2r!b2k׮È\B7nBBqҫjkk*@ 0Ν zzzZZZC-dB0&&fر$łRx Evv۷/_nkk˛xJfQQQ4Mmd. 1(,[ԔJR(KC 00 S(MMM|󍛛[dd$Ʈ. 1`v޽{+JE5\NRվ_ B,+++wwwչ. 1DSSSܰ0,''_mjjxF 144 \vFP0 @AJR0oq5===RZ@ CTX,. J Ba`ԩ˖-ţG7nx ?#DT(JLFPLLL>|A @ \.W[[T*4a|'~۸q#a2 0B!T S90 bX&h4&imm]^^ꪣCLf0zzz:;;t:F"K$uDݢnH-5xȑ%KzDqGP(r ),,4i7n7|%vß%KD=nQ-]]]>GGxwwEwwwwvuvuu( Btȑ#{zzHB @ D__Ĉ4Jh4 g> د|ZT*ǍGP(@R{||ŋ322VXa``___Ŧ/_fQdьS'OeeedӧǾkggP)  7GPh4 tD"!Ifܾ! PcP`Xj( JʢP(|qDDDhhhZZJҒϟ0JOO T(T*u׮]Pe3h4: Nt/I9@ 4!d+[[[ޅii4Z]]ݘ1cL& 4ddd|ܼyԩSuuu]]]=;::HΗ0 V ETRT\Nl A@ FD&&&w<|_O,ɓ/i|>… Ǐ-,,2y={$%%=xm׮]]T*G(vD@  EKKK"g|=nݺGݻg#rss+++;!Θ1̙3T*,???66F555UUUikkϜ9FgeeCMMu)AP(rGKK^@  &pںLLLp/gF$''kkk]?Dnnn_|NS t:˗/OZ[[}ݥK􊊊y֭[gccX`Akkob$ɼy>3M PQ _AgF 1`'O\x1>>fxxJ}}}gg&ð#Gljj9r$WBhlltjRT(Rѣ#@ ĀaسgϮ^:k,mmm:>> ,ɣD" ,+$$dĈRYYikkJ@  b``0n8BqIXx= رc2lܸqD]ҥK:"1h4ss`Lq_U&̹ q 6`6/^<}ҥK@@ 8I҆~dzzzx}4''wH]@ bBÇUUUԨ ( 066qqq622 Surface Menu Surface Menu
The Surface Menu allows one to find information and set parameters for surfaces viewed in Workbench.

  • Information... will open a popup that shows geometric information on the surface being viewed in the current Active Tab. If the active tab does not contain a surface, this option is inactive.
  • Surface Properties sets display options for all surfaces across a Workbench session.
  • Display Normal Vectors sets whether vectors at 90 degrees to the surface will be shown for each vertex.
  • Drawing Type sets how the surface mesh is drawn in wb_view.
    • Hide hides the surface and all layers drawn on it.
    • Links (Edges) shows the links between surface vertices giving a mesh appearance.
    • Vertices shows only the vertex points across the surface.
    • Triangles shows the full surface (mesh filled in with interpolated information between the vertices).
  • Link Diameter sets the diameter of the links when in Links (Edges) drawing mode.
  • Vertex diameter sets the diameter of vertices when in Vertices drawing mode.
  • Opacity sets the opacity of the surface when in Triangles drawing mode. Opacity in the Overlay Toolbox sets layer opacity.


  • Volume interaction sets the surface used for mapping to volume voxels. This is used in the Volume/Surface Outline feature and for volume to vertex identification. 






workbench-1.1.1/src/Resources/HelpFiles/Menus/Surface_Menu/Surface_Properties.png000066400000000000000000000635721255417355300301140ustar00rootroot00000000000000PNG  IHDRQȅQ7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:gVN<`HHHt]NwIr}uӯ%}˳op]kDYtLm޷YqG].{m=mx1>ŗ즨v"4*\35;Y%@#tn n{n7{ 9P[9!rfEͪϫ*NfxRa99= PݾŦ@___RIj% ^jLP(kjj<==]NvWoίF+~ ٶoy?mSZ~`@9[]]u^ޣJNYOJvgy[ZNlxz/>fYziWR>A¿&?Yau\SVr><.ʋdBP Ð4 A(%YLCuP(((h A  x.h_{V{x0 P$MI 8^r_OLe"h߀sz052af^0g=}<Ǐr~衕[=r召xC[E&}߾__O{zS!9oϿ=}a_KVoV^!y}d ɛYcNC{lfы^͟;|/9a|?>6$c׾y\po/ճNεa=xO, &xw];gɎ]oCqʷ:.r\FK%S(IP*T `̈́f2ˉF9/ l8y^'nr7Rpc(RNSrQ4 X8̱6QL¦+]VpS|9$s?od< pԄ%8zU&97,/mkjE=J f/cU__c57!Q_~(XtAwboꇵ^*p@ܚ^đH7Y?Tzq8I=b_=ߦC M@ ,E3%orٯKӦۦa!*8cnoҏ7MEu5קma=膪&A0b9 FOMfhr<(lZkhpo/Uo4_Qw0lb<ĹkW1;k.p^j ^^P 7w r9O Պ\f0&IV;hEL&ô=.Ee@+(-srZ$(@r64Ԛs̵UgIY7ָ oA8~xd q&>y~3t) ?xtر:V3;ƅV A+|>g5; de U(_YOIY ϟBVK6HC<͚i7T~u+ϼ=(_Uzbm>{{^jxz@2)z>`0477ixZN\ hXjyƟ.n `H!e4 ܅S J"H3f]?5s} T 9h5KO tӵ|'k{;'8`ϔVFOY+_rF5GY.:c;ޅWf*/3\8k*Q8Ap9@ .`D`u3tcaF_kr*`{sQ+-D͗?JR pt]y^j{{^^ %9$GqscnŔeYtSs5ͨ"#XLBd*Jܺ?`0( _B0OkUFEo9G  XV9h5szlkjYbkEucXei/&/[߉9 <|Sڶ~#V-cdq[ &2I/{{aX}yF0`{pJ(\KR lYoxJ6ΰmzv}6 f)+ݻۄxr`SPbon E{ j N4eń'B&措{zz_vtwS@Lf)7ʃ{=h&OY_o7@kӍldnt5 گooEBXtѷB_yh=}Xc \S,0>Pr7\[Yc njc/U޾eѺY+OU;b>=܉& hwm:_kjCױ}wplr/|)Ъ͍Mxѩ꼦w_7=?'ۄSV_3c6)A&V6d(JLpY@V[VTTyZC4M[M!x2x(ZFQja1E$l+څ&hX'mY49uA Iu|>)&܌ꙿ>1D϶n IgmK\+|>JlF^|2Ӭ4M+J\.nsgXL&@e2{|s>a}IMw1%q;wt:9Z6 c\Eݫ]AȽ}Uu׶Ip#ۦվXWTZz>ju9sğo$$$<0 } a +4Zx3+XiФ&<䤜&ʾ-|@f3xM`yiC!J[IQbK9qB5&4whrq|iUrRj쯵FFk5ImTQG>BRҿWpOH9űqB]3WU7j/0i==Jھޤq( S._V嫪?A|GT2<ձEWcd/7\s̸2JFK9y6PÐ$I !>**/K0x^,6]T[^Q٨v?,+~.i8,XEoJn;h_̙{zyQJZl8U8oKJ)~kz@<yžu5>= + x/pR >yR`iv(V;ybd(`.^.gEG\Q-g 6$F[:6Ny3'._!a7fnZ>Ix6;)Ե]H Yd6m>n4l60)Z懚o{QE!~{bOqe\S٣&.3;5%xp,,,`aafϟkĈ,`a 'X8Y^#f.]@ `ڗmXW`uNE/#cCO[M|EgۜۧF_AkEFH9!Pe6-O]<'޻ox[:юfNpA?^A ew4 /4x'HZ6rm9Vh[UgGWFFw\U:.Rh4lX:/iFҶ34FmaKؙYT`[Bd\zQ|[7ceAWDw5O\[^h4/ѕ%f9IFi1/i}qbb9 C|G-EΦ-߹^t/2V9Z]^Ҍrّ@qT/MdB%٫%Jm笜2$oFL?|8"s59\2$i2V+q)9F X?i׆6Ӟ wM.1w14E%D$ex4QsY⌄aQ_X_e bwWmNNxどZuRW1 UJfA;.@i☔Ew~x"jsAi{s"[47NýϺGJOf.N_cpeWGe8b،]aF]ԜƒQ_+lx%wrV 5Y(!Lpl @~&y6z,'?Z)lb6L-kveovP, [y:A7ڠofi IG&" @@ <8,/`,8<Fg$>pvmf|zxW[r|LTDg̺7R>ԇ٠@VW/OS] "?ƨk`X=&\aZ|r8U/~4ecU+ÿTF2PТ8.WE @zv])筱6\0|Ɵ_xm`e9l6kӱ^OfVν:x1k>?Uy'wi¾J% jm cW_uBU9B|PTM~Q B-뛴<o]^"Z=&y w,cѶ^i&X$5WlKɏX jj̿v%Y/\g?-k~+FĨRJ@/;pn#"\ے2H\2(+y嵥YHz,B$b Qڤ\} EsT86+S (?ϖkC;*LbEi'}ޑDY/ 8 x1kfP+WsAju;xFdd2o,vgĝS!W%sn tѯҸ& I9M*Q R0RF*RP T0& !c(9EiH˟sxyEEEEyI!skϣK*h_H-d^(N~%P\[9%ڿM\P pdsbP`dGܟ#wwPh9_oOG+3x#`6WA{iA_u\P VC+d s͢W0gīsGm_5!1Ji(C1$z^]Z4"ű-; ch}b|=PGi_X_:>8YIi#S#Sc`شĜu :`U Bp]_p`GعQLX>Zxv<S|#!5>cKO/z#G?6ԘPUhliQ9~*2I Qu'"ΰ.LtRwX4B h>cG$e<P}'ߝ?mȬ5/< &[e@@kh[?|nփiӃy/pG?a}d2EQ.-k2VA0 3ѥ6S FN;MƋfRۗW_=SA @/IJ{_ @ xsfu̠@k}&b]AWQf=QJ?&)4 #8"NIe@ՂRt^dڣ  E`v:j__eӍPQ$ I *wQA ^ sII"YK;~**§wj\PЄTʨFSOOdJ%)ݚhOOh W@@&/ӷ`qxOXꔲn2T+I;}j`vwwW0CQ"j7Cx$QI{1-Vb&kc57 rVR~L'*Bz%[F˔q>Xߪ=vү㭤$OŸXOU76 (B{yN9MPUI(&QjeˋUrZҺ0!'e`HwSl%}Mn2^H|7#G;v")^W.)~̖l;>y|Eqr{K|w㸂:}tZ[zzP4Em-ڋM6MQ(H9}(--=qy-p/pda_Zwp͒#! >|xС,,!>)V-)~sƯ\ߞb_~bEॄ'??=ZIOH8f$`41uDv{qfvֽ?r>?WOGeos/{9 㷼 `faR{׃EȜgΪzzir$ ,K4\{ r9phOD*@qY=K4{$;M'ůZߞ ZN,+۷Qw:`.XMT3nE^䜣`/ڗ)JGfmj q-gX]VkG$†C"5+u7]$wu]Fc{}>O?O}hcJN~gW''kv^uoMįZ߾vթ׷oB >H˦Gu8(ٖ#F|DҮ\ݦ|%,* q$oᅉňn[aCPk'/%b3S.A{] ݲ̷׷wdgk2_:_c#No|TԄo筋q/?o]/M]Xx0 M_IN6}|` t7[ypVL A4΋0>0W}+ ̍(י.j}{gZ}kxW@b>l\0}벳Z8FlġCg,k/grljgP< X*wf^;yGAzĔ-,HU?OPt$ߗq)Dux"׷GjZ7ٳª7R]OfA~vGOaWο1 S}7~r¿Þ~{)>튷b |UY0S&cy{ Oxsy*^7ٯ7>O)}m6 :󷜾=+_߾b 5"pEɇ sWh3о|N o\Ѣ-k5⎂ І\_ D%I(NX-w-D-ph!hٛZ-ՄǖGW7Nۯ;.whj|,o\ C{ w % ,TjYM3A&G3u{8K7Z;ɫշghJnoo1D x";ad CfG#ˠWNCeM6bgT|s@$w?b]֬K[VEvAo9ZI@T%Zpg@A@A6Y2$D=1@^Hf[#k?vH& @qᄁ :rޤa/dgd|4KqQߞO Be$)!O &iyJ e߬\Bhlsb`dRC|btoT#Xk,|o2q%}v}AbZ>ԵKJ!Qkе+ܜɃJ0lpx%F ٪֡Imqm/ʤ-8 ZT)-b s\SEə 5]Ky(IP0X $\NwblY!p(gd@ؓnxAN;^*)tnT\Էw:-⣧u?<@ů}y BFs_^Y1#5>jnRuh'ML :x퉢~6o0kk䒘#oᵑMt%Zq`24MQK}{{!~,ʼna EIQTl<ӻ̠<)g|Q(m$QgVgPn 'M~M{ן3?^czx#{ W T$EQnYw_}9Y9),U\XPoBM.Sipyӿ:'QUhXlxhS:8chdo8{Bl;#b:WEi[ +ewDk1 MyAC1 ôi$4-!)z#e+bƮB/>4|UiJeOgA dT*e2Yկ,4#ʏxˣǫP1efP%㭣Bk}{K}{U_GT:8Kte8wc9ɽvETߞE򤾼0 EM4)33| i駿C6\󉀵IݢE(><_QQ[UUuw$ɀ`RwAlUUUAJ[$;ۑ$0ckPndğMY颮 VH9ߍxWT*#qqh|gɴgϞzGzg̘Ѣ~Hkn'|2x`Lv"qMX#G655m޼944tԩ9…?lҥb\nď?8rH/+xOY&~;vEc=xYo7|SٳgK? 0rFBZ5L&ѣGǏϲjl͛7o66=L&Q ^ncpfjl,lO>oOl6eYaYܢtx 9JKJ* 9v?"˲nnn,r,˱={[Uu߬bYR)ʜC [j.8'!x'T'$9XsR^jӯĕ L&cY8^=Ѷe{y@.;V$9{LZb_ͩwM3U}Wj5) Ã2,S_}r.{j96tbb%t7#OO<22Fneo_33cڶC34%U.^E+7N6lef ;NCUfWqAE.oXjuNQ'r[Isyvdr1PKmtU@y+K6h4Cƙōͻ3bi7o_O+dhȘw| s&ӥqňv~U#+|1 ~nwNhu)f}?jka~FtqKq)UlE^D}dL@]"|ߴN;^] R}8#qã w%h ?0cЙT\xHƑBw4J ֨/H?+?0flqFrosm HѼ$Q@sw*O끈}ɈJ %&IXraZE  9:hеw(f#"XġӴ @Z|TNVawȱYvTG!.[al8iB 25hወj |) @gڗne Xukg%:{b,ܒD Uy^_Tz`Modn)9ZD_~;*n  ߴZŕ}b{0!`YQZoY_>=G =$,&*DYtj+#G=}S7JGW#̮JFCm&uh;#||RR )Zqg_E&Z_EACU:*Ӷn`eASV,xZlHEεܱm8o*zKÀn: k+=q@顒z%=*ihQO9|_{syabV~`N*윒k}Y9'5JO+I't3*n㿵<* WQ8O //yBqTMct֦OZ d%Qt3> %oN]BH]+49gz|z]I`,L;G;u+#gd'i4M)RhV:m0D:,kK2#5 E.g9tVh[UgnR4,(X=C߫&.Xh *ZڙUڍߥeI WFj40h4Goe5ɣpqV@ZXn`(ݛqJ3ǤZTZk}M+NMHԤ/w=m_/,\3xb²#EHv# yzOZm:3% S-G.ă3ӵM+N;W?sWu[pG,rKC˾ ceqVD)@y',"5 ɣp5[rP=KGf)=ԇ5A$8j1#B5O#F|.=efǡxwHˌ)7/ ̗15VZJ[0~ĽZ@㋴#FD Q\ZZ#CK[fڢ14ǯݸW?77GUyT*+Qfg!)7B$̱uW=ZWsou@q#[2w:d./Z Է] v`hyiTD -CKg̮QJ1K3;mTЌ&ގs.-+N>IDATD;"(zq[ji:FƜvN΅Co#zfHTӨ۹]Ƭe5v5%ɣkpWKB6iVxVh֥l+5%%家_hUt;Kkf'VfWҒC,h̔gN"+a6Wv̊m3B& bymCˢ̄b. @}nh验FCfi [uh۶J28<*]Ձ?pGgɣ򦡍G%K,YyfAANG..n}|THPPXŃebܢ@FTX[EhtđaAA!3ϲ-^qpc 'Oμa9rWd,$Pk7>W@u])=*ꋻǵ$a0ZzcDmבhJƿWikL@tY[=}/t¼?:cyz cm=*v>.iQ5H ,qNcA^2Jc3ҋNEYH3J |K)sע>WQύȘHzAh6֖d{e^r,WVX",ZkKgs:kKgm:EO:njRj^*2\"`aGVyw: 3x 9yf|s~}:--M-8n錉aYa2<8wwxwnTටݚn#*nޫ" YW~{ߛ>6ؼ,ϻ<>{`CmUg<.7r\r8ۭ2cYvʔ).vR8#1 40L@3ȰJ", MM{iN`7X>w6=/۾^ptF4@ors y:雗~@6StE 49tO1 b1Lǿ8#*[ְ02Γ'(0E4e/?qWԩW=}t^Ehn즢Rrs34M{[ٴ̎颓ر福lHd 0 0Z?YeY6,,l6pGhؘ(Z]iPA/ˡe%04 4M3urlS>r纻^k۷T[WUtF;GQ(4Mhʀ8#0 Ð0) x;Eᣇ6Bٽ&F>W-`1k&m6\ wۏþPȔ>v:Vs{Bqqq}}}EEEi4eėVؐ;*5,K@3,˲љ+U[|>Vޱi!վ];QOpz$IRkkf+))4iQɫ^tɝ=IS"BT$88Xso8X~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:l|}}k׮m2XiJy9RewILd-g|Q?cǎ? iǎyh b^27nβ9lRZz$I4d4 z=(w8l6f]vhh(q TT+1jP9X$Rnlɗ}U(W_ɿ_>C`D˖>^**e`^t\iǏ] 8~n5 Iw{t]i5[nݼy3$$$888(8l6SZuMVjc;~^_ݷf*m?ݶa7e̎_|ݺZ<knI_~]މ, [ۏTens%sRN=V0uޟ/YSս p\c\\āk>顜Qw=w\:k0N)A)J=˗ah_'=.4|qݞ]j@^OS~fF鳳}}}Ns lj e4ERVZɬ%cϩaUjT~Hmuji~}VHg }(T*+5r=^>駛OdMnuU4 I2kCV\j={WkL+˜?d0#y0h]KʿV kIFLSQ&%@5^Q2i ݮkI-_xnnn@@@HHEQr.(t!!!ީI Gjd4i5Z@yPd f5ԼS瓪Xxj'TSi3j=)HDLDI_HJ:s?x "< Ϻ.\pYo?Ttdv ]ߛѧU?`ШAq;&`/]hBXڟe}hh/^Ml> r!Ѱ/{-.r5R )R$,(˓_ 80$`3i DD`b|j4y-/=67v?-_}H q&ZÜ{TdYdBKW?,^^"u,iÔ:t0.)#S/  |![RhE?>g!Nt9t^Z=nþ] ;t"$]>QN˦^g~}E`:,k !& s^Oxlub=~3NiV  2r޻My9ǭ\W$dW;U+zΜxϟ}eOYK]ݵ˙'Gl:x ZBDڕ+uhRQptR'LAc[lʳG1a`&KWܷz{A 3<5zdol;›hw=o CW[鷾g>՞vA+WmT*hZ Iz=CEx*'(me,! Z^3 CkYFKsd$:%Q$ u^ǩ w̾負csӶ8jW5R1^\}`!2 ˒$I)OU x񃟧vmnK]̪IA]0e}(IRI5l? ou֗d"ID`Z&dp"q E^8n^ [S۔d;0tEmVO 嶭/{`UH-NҨauv5X.J2KǛT"Gz[Mo5=ߜ=gBI@M5Yk׬ɈNPwxk~o$I/kKQD\~SzԢ/wzaDЮ~Cxa sgf~2&p8"Vn[80(*Y}Pfܞ ˢ(2 x_tXc g`ZݬrM 2WQg^q/j.dZY]&H]G[gsc>׶M9.B"NR{dI!Wk~ -/FqNw^8 B#Y2p狽= ~r{mk CEc 5<&lE| S~?ݟ2mfbS?k&M.]L?gp`hzic~~~Vp(GeD墢"e6( ׯGkX!s4ВBFfi, q7_K.ݫt1G.{ tjj}Ln얤Rk&n?NXëYK)Y<($$Ijia~ܓ$[nF4 -߳fuE ,I4>NoUXMExNx9Y_MhYPVXɸM _m^E{jF@ǃd )woh<-eTVo}BMYLI{7@$Tp췔Q&މ]ΗڹN>TX-a1Svfy5;A>ﷃLUkr?.>;dzq5 NoV^QY,gי/!Dh4ɤ\Tɲp8VNYgSvlPDz}Țck^Yíλ+B*bui_@@* K=]$VxRAmݺde k[̾zx30;*Օ Őu:eh4, @E%qShNgE}}}m$bƀ_AY¬<{x`g:U >#FVQى&ZNtDQkٙyv`;jZ_4Ιq-K\3Tʽv)FΕq-KXSph67z5T1Bjƙ]:s@#'#&B+SŕeUwY-+Gj>XZh3F_4<gvV" EVk4֌E"_N2 O͚ǎTg )nY^̺p87/1ZNtHzrFJSTLnJp94]M@YVk002kԱ|veP[l)/Uþf 7FPo:[B=L ^h4%fV<{⾾&?_ k,eGS3ꓶ=jW7 EDArò4)bEػ,&r, XAjY+Sa9ݾ'81,%[,ò,PP,zq\igʷ+%QOEO_믽 (3L l嶚{L{˲^jVI\. ȼY?jӦM+Y.%w2-Q+7OӵS^g޻`2Ԯׯ_탊}PQQQQcTTTT=ܭnG5wY?߇cԋ**L6[ CS4IӰ Ȳ_-,[/7u<4GKJXVsC|^ʶE۱y*&ѰjVQg-i@@QP< @eYv ר՛ [\H(2X00j~Xi }[4'bQ"V9$ganj~!K )?ɥ/Y*`\GZ N xݔ'kegCe%@j_kI5ëmK;y={F7Keb60UtҶ/zFw7U\1=c_;u2/ND5OJ,sb|V'5wlo9?5%8ߠuʲ/9gگG >QÉ+ڲc$;r]zOKv%+)dĚ=^ >j ٘ot dXwPiSZ7o{-LUn;؀!XƟ9a\E{r~jk=rDIpvϲHD"EYҰ "IX AEQ,x`Zt h0 bP$=˦uks*MyƴTz{D Jݢ˰Cܘ蘘ѣG/Ot=uX׮[S Y[7 uVQE(7`Eo h_puI`0֚ g);֔1 J{[D`+SiJ cbF5m 05z7Ƙv~-f<۵kỬQukuqQt7VeZ `Y#X]v[S^kr|'E>;/[ӢmI)#.ntqcIt8T$w]?(zb▸>p[K".QvIpIpKSėdݺهsK]e$$Y  H*ʚwgw\,}4m5IN<[<1hy?g}{S#ة7:?r_&DlqHN|4q"x.!2MDQrEQ(dk;Nz.vicÍ/~uH O -:?OEsO ٸsM|^;̿g e+rQv lu8ζj+Mxhio̎"ˈ$yӯv%. @'KDR\/Odx؝~m'f͐xev3~/; $R^ǵӋ6G8%wފ.vqHܔ3v݇n)_jK~N)܀ k]Haٷsn=iۘ`$˓d_S\$"Yo^q07@ɀ$J{QSﶯ )Q=!܁?%#vE?^lrM#ȝd $~OTj-KDvD"Х< 3v^803=-WVSzva>bD_}A:SDYab>!v"1Q˔ `MY0S(X&i D!GoKe=B  B” JbNrVg΅\>!b ]h6DML:WDp7ywݒ$B$IDDH֚ @泟O<ꁡwBG9qBHPlBhǍßnU7Մ8yz'U8'w}.E HHd(ܽmns%A nl%Y]]y*ǐ(t`Ƃ6kl>|tsmdܔ#Z߹c#G}wj@ƜQݶĞ9GL=i ;JFL|pv6Dhɽ]tȳRI;wx9Ю N]6"D.ۼs\@QM<t:y߿gOyt:o~9Xy3ʒ[K)/-\,<Gnu9 ewny'Yv+Z,$zJ IDAT넡U5*lnUڌpDRRx;{ ߙ" -%M@CGm-n<DnN%Tvv7j}L(k&ʊ0(Pwjvkay/4 Y݂H e@+dIik6= .5-ojc? 'r%/Y[ ԙf؝;yR3%=[ (v݅FmX֡/ݱGJZns:ِ -UO=F {^-՝v55?qu'ndIp BIp݊ gt7CiR Br@jҷvx@Mol;n PyUJ GNnvln+y]7|m+ ~'T nڠ/Nn7+󬦕[o*#m% '6 u]wξ9m|fsN-̱w LKyWQӛ^N܃3n,K$p]vˍ!sOMmB繅b/>ΚiuJ$Q,$!Wtn獳{ ԟRi?㟿|O-I,G=VSOUsP׎lȆܢz`}Gw,A5Cw<}P߯njjfy' [(ݿ~C^b2mܰSE_^Q.W2!ɲM$Tͻ+)w {(5@DOc}*"k%K ,$j&\5?)]O~xcxoˍ _0~w:QT1]5읝'`n)J/S w\@FXxi/7zCo)0@)G(p~HBY]YF+ԨlwEsJ(Ց2MyM"[=@Lň㤚MO{ŴklZf?ire,uBh {{Ljh[ 6|͟V=Uw}` -fb8E`80+_RPrume Z70u'[6 EG]FAQnM㎆[6W1O7uñ~: @֣twbϞW6ȿ~#Oh`oGU4s=m;1KRwyK)S^O) -ϥ85iγ>orP=e uzW8w=UkPL\o P}x#:7E =JmҔ_WN4)l1hJM!|Z P24wwBD3W^5ۀy"DG]Rct);puDӲ)""0vшgW5/j.&&toA)7]=rT%*@Q`wI7?tchehPʴi;}y綕-`O/S}[<+֯u]saitvS3g ׬7:r)rDrvbᠨEQY oiA*(I(Fhr80 0ywkRj%E e<~tׯ_ʑ²3s pUrbZN7'aF5c*6e/2 @ >;[) ׯ3Mk)tMi#ׯ_Wh/5q_h)M1}πf}}RR00ڀ@*ȞvqhK=?"l V #MOt(hI.+is'~=p~G(Zi(se]ù_)m'm@)W6r!!+}lXgf}ɘmovX~_J~r'm*~8h)ju:Xwg W1afYκiOu5d #Hw#S!)慽ũ[i]~]k9FDZ: Xp7 6`PuSs4tNt,Z *q =5vˁU#b;7  ۨE3B,:K94-6*<,,hhqY?mFJrg5wqJ{s#0s 5_WLp￀^9.*8޳3]?/0'ιr&k0 q]vaqGжoN:u"Of񪟝|K]*f V |+ҧ4qջgF߬Ϥ.=l.WS "n՚y`=@afxw(ƊN6hXx [f{kffv\ɽz"/L]$B޹EB,k2}XQO}3X˲i߫QX e/J|i%cK9eǥRqP⣴)eE̕9D{5ʉ["C8fD`O\ٱgKr2.v*~?#3lONq.i|þGRp5lqNXm.Q\VyDUş&hT ɓ'OJV61996`u@gYԒCg[m"Xx Dtmnn_ͽI1G6 )&U?_1pU;D6mSJwqF8%3LPt^"=jcu3aJM'm3/\k]>$~3>e1-ҹ%(Ď,n#suxiLD>AŕF~8F.uRWZA3˲m\5cI[ 3,TX@3~&N{6[&2Ŗ<荷{4ņ"f#Yu3 b#ڦY8 mP΍toqG[˔F_}<GQ[ ;Ef׺:,L#{sD& tgMz-ǁeYVfY}pò4h eЖ/\6o3"'zw8eq ci]1A;oPZ`UC&ȷޕX8H eYY~I%c(j,˷KiVY4#syȪ~OV90Mqe`a8( phSC8A/EI~xr#p͈yʹtX.`o6/r{ M_YCS&ON-@ ?)C^y v$<˲,Gaymk?%_,2d KD"9]BFVAځ\ê\`-OFfʡ'edYnLߧ痔%-O8u>ӡZ 6tU;WR#(1ځkWp͌xG^~m-ӐyoO|Z*;ȋN-ab/ޞo.E~I^q~q~9T.:Eճ04 l\KW.eDԪV]˩UQgq&cv7ҳ.pjթ-;uaȍBjV,*yeæ/b(^c4W[?HSݏ{/@w?j @.~7! f%JݕzjuXy`eI۹mS7$sgvzj]i0r׾a*S;˲̲$I$$QłE0-f E.I(lZ1'b)XڔGqnLKN_=}O-z =w̍=zLgzSu5/u_\gEQr&n\րw[/d0 KopcMYݠE 2n4 zKB)s_ޒb9?;9?}v:|kjwnZ'-z ǂ3--:p#mkA-mM]`P+O)3[/D4-:f1111mH\e($%NLiz Gݙ D|yt_:MG |m>O ;qށO=Hct,uMTr-BT^%wSd_8ۥ 7x!'$>K(O<'!D2Kܢ'lչ&{v\߳E9yw6:Bg[uؿS& <4oq||7f`Q?L eDI @IoW;{mcm%")wMN.I'2WY ?v厶f\W @2ZxCz۝H)];6՜8?.Ǐ7L[W?0njQMM'/{U+S7u&\{'DRQnqW?jƺAFʐRcsCk,-ܷ.x#@URv`֘> %rS6G8%w.v{ ?nP\ %XAHܔ3]"ҿ"wJK) q/o i6O{]RnY"C "X-+br fJ7@va>bD_}IgY†Ų؉bDY,S.5ePOc -gʆž-yJe *gS@ҸN0fsݒ$B$IDDH֚ @泟O<ꁡwBv0 IDATG9qBH{Bqpӭꆷ`3'C_S o^qǡEDؾ D>iҔm9g"RȽOB5{Br/n7!,@""5ۇDJwL& "d.8%"e޴Xvtu'mq=ø24}oss_ WB97p4D"V_}1n$rVuw|^$ {q3U&5$@~z !Z"z=á}xm̻gL^JN?*j|WЃ9G7z}K$n(%,-womdߡۜnI ["."bsIM&-n#z; M:%'dl"!7%iȱewX@Q;81n|g-j1l_OZ5gΫR&ѻvNy=@Ĩ;?iS?nxwvt:<|L3m'xly\FG)ug]ZIheĭWJ^4dt6n6 n7VtQ p?aj/]|=;풒83)܃[LĊ}'b"#^at{Iܵ+O^ g<$u(lumm- !DӸcq{tj'ICD1{X9dPYe/rw#@%6Ȳte{֜vke5%K\n])0]dZ6vȋeooF?a3էdEE$\IY>|/_9]zgkc[Om!-uwI[y^=M1/O<\%Y/k*ʒ$qʲ :-h)E {gֿj/~6VK~e*˲,UO}0;%ɢQ%L_q#_wV' xtNڈ9ldGʒ,yu_]~`f1/$3+xxBŔYoˆkf\>\jÙZ0mQgx}S@Yd;r8Q٫$KRG%Y$DumMHaQ,U؎<"c)mw|+;jUIjWPdI]7ntg~ LiQT~;d:eٞ͊D$wQ^zs/9$IDrgDYPNtLq<=]w0dֱϺt(H )-Kr^:U"+J5s)\L*͹2nd(q+շVش/"2cZ)R &FtۚhBm>Iٕ9hn.+RN *]6 +O@,I~_{Bxű삠d1Iڡ6p1K"N۳~^ $:YF6Mazb_EH1=`kEAnX5ރ9پ{VuKFHHVНDڰ1z7 ۱xq}q4:cW'Ћf !>Ѿ?#N-8$C-v|_.zk=_T:ޚ$/ՒbZ[CyW 5msM-m-FKqIm^do榛!{_޶WCg9'Ӿi(ԟeu5Yv⻴mپ݁9l{&:]o"}J/_1bMuH{{  %_6-@BrBxd LHxu'$< HH7?kXެϵMlH(ɳ&5U%[c?\حޠphs94s*|flы,v:y׼[B ,v:3<xKBj;= lt=tefd{8/4v쯷5=t6Q7 12x1-C4`|y6W/:X$`|}+GAAAAAloa-ڻt{núMdTӀ`U7΅y|z3C'>߶p Q q>xA5(53(! LN t'[̶Gq (a>>gtב/n+(:'f#e\(c!sq&|r̪cvSB(emwl۶ N9!#v]; ?k~6a2}K#AR-ࡤS}ۂ% <bߓtx5sxZ-F>\^}|dWA˾w|Toaû){h*R0Ƙrz чT=UQ ;gIgwT_Q10Bopv,`1Q"]ʨׇ-5?:LDID&fjn]cѤ5SMhԨi[sK?7SBF fN?[FLb`l2 `QׄFK; M=+wjըQ[:I]'t6S ,m^9灗)nСC:uQ[_.%A` `;u {%_{,c>7'pHpƘ(E?> L~aa1Ƙ(0 fu"xˎ.?xk {(ԲG6O쀃R9ȃ 0PFE*G 7rƘH;t*zdTVt<#8s7pwaaڗNIˆ(,!G7! Z1쮕.y*;{eE(0c#׬;A)E^.n,,$dSdb5 I"+WgrU m=1N,1%y_p*"# K4 6Md+J9EEH\KA_ '+ITKԒ b2/WtV# no~)ez齊O{@M?M qp% /h'jY 'lPN;镶&.@A(G2kOKǢOش9Wo7X-!BD / 8sx=~֤Ak n=6=i[NKt[[ vߨ^~'wJÑ,ظ&cNۙ 1j5|V`ƅgeEbWrc%KbӾ{w(`eB|yTn5 QSgQ5m٦COm^;{Mz[i 11nYTٳg=z 7"^{* a!qG mx[}\,(ֲ G-6US",&@q*[#m_{Pvzd122Q%8nT|VFĄ S^̮T8p8K<8(ZBC8Kݲi._J1*\Vq:2=N"XBnW6NsO~.i=mn73g 3뮘3fy"h\BTW.9@EGGϪZ }ȑf͚թSl63u*74Ǻ?XR v7T9{&װI2 Q>yKL.7|:sAL$}#43JA)~R<.%$\4(* 6 E\(3nA8RU|_ YZV(V%S<͡_ԾN-~-⍢A Qt5kj]U9yŅ߬zf@0+(] @ժu՝x-WO]GnmDJA(tKo1 Mj$գg;v_c={\xqNNN^nUDh"Ȋ(^tERNeCOFbW0r&W M 5M1000 >>J479epm`pC)sRUNQ%v_a;a( "FZ { Ajm```pahv\{m```pahv\{m```pahv\{m```pahv\{m```pahv\{m```p!AhTԆvP8vXyyiU, UwFBDƍnݺUVpe4UL/ص#92N+ 6Sݑlā?&0?CBCCtҮ]PgUYNCvp8 кu TU"WS;2͚׎ 1[ Op7'<^q֕{owR6rh;Sc?nذ!##㩧-R/5Mϟ;wn{ݟzmRP^_e=9siU{ok66&S pC s4M۰aÇ|ɘJ)!~888ιEEE .lӦM>}.3@V>xBw-zMB5䪐qX[ٙxad5<238p`РA6MӴ2RoOfxbr(>dȐkŵm1(~C­H~o4-ܼqwr{|Q#h`p=rmvW&UU*KUykN[Qpndi(j <=Bιikaaa)))_~̣4tvAf䑹G Y劦jP4.\QrY%KUey;KK:׆/^#SɮqEEqYWSWf2kÍ?;%v\hݰaC$Un]9׸1-M}KE&ѧ`ji,Bv{VVVu UUa8BVϸ4Yd+PBo, xe{UZ˿Z{8 ~ɶ|/;V Va-+BoP_z0FdKV2i`wF@U*hL [^ovvvÆ -OML&*%887sшqCYH.+倪Ȋ |PHQ#2cl67hѣZZ?jMnwYM\ BdMBD'99k/})%Bά8흞 feQBkmJ<wXH<yICg?M T/ 1h :TroTP{ɣ{<jZL0" 2J@J@)TT`_* m`{ԭ}Zyٹ G Owkr!?5J)# 2&2jY`f!!Vx\d6'z!<<\E}mBjCk?Zc^n<蔞W4awqK'fmHYsɑ4ixLیAM&q3J)BiJz[hE_DPjfV J)PJe%J5@X̂)fJUfK(qAk,p!?×KkskJ%Y3W:v\dYU1F/[U[` XLqĎ1FˁN75f\1lQ:0aD'VXd!ӷ>o3 !QJTGy U3 DdQ( @}5U Kٽ?!"~{ʍ9w4υAq`sDӨMx9:.v\0Ν;rvd !`@"ń_5t]TRIIƕX˸"9:,*PwQJ B) ]#/mh1Àiu]uwF`Rp8.JX%dѳ%0CU6hk޴w~=I P4>tlsw\ L7<4JP"q6ƚV9F#m`p}B1L^7x@ o~`;c3lY+[d2Ryi \;jb1CKϞ:7xh'^9o{<}m.gAYxJN!ntɄ !bDгT@L`IVXLDDL&bY$X/l:fz*>wъI'>k`ZE lzX9َToI 1 d"EbYfE,*JX,UXw\z={t(m~ǘ>`||KD┗z,!_wcȮS[(j0JA@֖=Icg>ywȿ:'Z! Z,5>,0UpԫWl6_΁[֦CO]Q6(00B |@xm1͟77|~3=p@ 0b hAZk @[IP5%!p ɢt|]Ky&-_~Ȑ!VU_[s&r9V ְZtx` P8J=Bf jq:<`l;tTU?O>͚5z<Ϸ}۠:#&F)Jj<\L]u-9^f4N@8BoIRNxoۚ7o0-&MlܸqECL7];<.2m2 Kv񱱱E166e|FG EGq91Az!a5/[r@D 䞵-kƊho gTU=uԆ c#F VMҥK%IӧO/I^7??ΝŊGMP W> guief.]4l0,۸q1c 60zyyy;v(**z衇ڵkw@@˖-UV׮]4i?}O?}78R=&_!DTt-k։JIIiҤj*O6m>|xHH9sIN>   !"#$%&'()*+,-.0123456789:;<Root Entry%g256_77225835bf959d02*C)256_d3ccc1bd753b81c1**256_fcd4ccf34515e49d*/bCK+)Uv{.JFIFC  !"$"$C" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ? [^KlEP]y‘'$T[ↈEs(d$D\PWTWK%ŧ\:F~e\9}9_Þ o;'H@< $ a>J mt{m_O7Y^G-b\~YZ5_ckd wke=۸kIH[ņHOnD/dQF< tZ[5w-3NGznRQOiᑤfe9#0纑;^Z%)ue 6z'u <Ӹfސ,K#.^2ɆL*ppIxAf{)(y_qaNI'4(G:B%oxš^^LK(i8?)8#2=?:߇%ЮB(cs 2CvETdrB);5< p%+kI  ZA|nIKnEĬ&bۼÆi&K_P9#isX0-O$J:#&5qrKeܥ uX}ܶK$-Ʒg[Aoq=SIhȈo c#[~Oo| :]֝4ZE#DV8 x@J-$#"?:{B,''ėBKk1rbD;rhnzey9biVM3bXNpzgk~FQR Y"bi"jL8j[[V;xPĮ n(I\3sJ zzѸ%sMs2Eq$Gex)UO ''(tHr*HGQ\Z|6,u]vh5dHmێ;ێ:?|6"՚@ôM/$uuKwIVeBr3q";M kY/59,d5H%a3~F<vN^){#HB$j26QzXKVeE*!ˑ 9>063^.L]ť [)EM=I/ݻ&);gU};gk p[aC$)ڃ*+Xe-l Ѓƞɵ;x4~drTy_{g>_z_Z%fB*рc+רoI SV*%x4J 7 $goOMG>[໶+6ѲG:o" .ޤL|·\t{-5kP]YL[1I,6 FEoA<$ӭ|ح w{y mp2?Rk^/_=3 D/@xb{misԱ~c+έ5ψ%3p#I m&E7Yw.O >)A7KE 7F?oҖLߥiJui\XmiiG _H1a ܎sw8Ӭl{K$rE,})$l(CiJ1~z>;,t`/]vWӮ ǔndn3}^_MI^1v,QL)=S{SK@tiJ1~RiJ|]O$ik}o~E լ6eu#qR^:t k]Ee2.]ӑr0CCHQ[̢C~sҸ;m;16Bd<֜Ʋlu³ݿ'5Jn5;]OAiݛd~t$Cצ3'$:2Sp}8 4o/lȄ27,$jGT9'~uj7x[K(T#ѕܳ|I)c9]ž)-zzS]ei<\q zVc&QI=jh%xRhdIbp`{:%>Kzv{a4na1*e,w3rzT*jZ( |]M2S@ o~֒I+刎 Ziz] scw4E!o1FOb; OQϧ+!,(T1*D.& n0$5}"W"gi4q#2OQE~OV-pҴ?rA \]M刣v`x;s$dV]--m兣#o%]0i`~OV;{{KvcᲷ$ڭ2v'p ꋫ0K[m\V ҁ= .}?QG>9E:,M[%+}+~imiayuVmIrq F|s (v1>ݭ[nln"EI b$c.vA_BrᗆdYMOqk-cgYFbK!ۦ6t <7 LOZlǷS#>RfD\^F Rւ#*|7$2p$A:xfПPx;[+4+W0aqr(^_ֿ?z>yww}-ra_ pma89 r[k"TGu\@܎ơ>/$Rx4̊AMI_#ƺzZERSik}o~z u +eH+Ƽ&YYG`'9]p2q^SoSLזk0P/ B=w. $FёBgvGiG, w'vyf=Iwz&ukqk=M ̩4"myiȯ>qns}RPi @fR6[ pO\V5Vmز[sd 6>e9h:,ۻ)p[z z.zdm.0!.Je ";%PiP6_گ.qH$LA@aWeVˀqgTiR[ɠY\}) -u`鐲HF@| ``a+o h-436n@шݕ9>s2o xnhR4['!1Y/lҏZ5Kŋiz])m1 963MNN2xjG)Opy6"Kw?J1zaeiaCgo)T݋3}KO֬Wۙ`D~l,Via_ݝr&s\e~5 "'d&|i]Q1@]ߙWsڔ&/Mc"`ym$*(_e7vqqڒrnAqr⠵k]nd@=15E!jX9.Pv\ w6O}֯"@EQE# sToGAMү#O7aoGqf㪚bh֒PUSIԭ^)$G,$uE]nqڼ^vڎϩ"jqČ6gn"h)uƫ'u5Օ̅ Qm'o<x=+E`6mBojv7$CUX^w2h6wtxL:n'❴%o!,a9Sp}EIE(?^2Q}e,E-yf{w}е_6Vv0GOm,,mX!4-Gcը;-NJn%%Ʊg&q!K]*FL bۆA dkw $H]Vh 9=袊)u4|]M5R?ZJ= LQAּL5Xtk1mqlZuGK?C#8J87[R 1`s;@6⪊]ZM;M~ڙA p U|He*(`JKq6YH~0+o&{$̔Ae w>|I]jUZVf~{y*yejy.v? fj\*^y$y%Hy@b<6Ms\ᤲlkk E[nIG;6nދdRZ%mnvTQE2B((((SL[%+}' ~7+,c-ӬqIP789CLn ._5'|7 .52N^DPȬ 0=5!GŲ\o~L4I'ړk:Ps{[?h/|xfGƱeQ " }=sE%5kT+HTE?k OVs^7Ex/8V8V6AE3VDŽ+i[5G4O3*y;kck]xм"ңmhK'WZ5{}f{T-x%I_4neKE w/|7w/|7w/|7w/||'$h֒PXnV]r1W%>mew[Za)A">v:p R_KiaK "l/8s֏0*'-ln,ը粖͉2HVGl&EԵ]V{2oV!4fEXUO+ ʕopqN>"hVm!@BY±$,qr%Evs$w n:dEsfz6֟c:?lZm:ֳ[–8h< s}wI|izJ̥1pN J=3'}kn4l.@ $gu*|{ v[X )Dac;o6~Я6+ -;>ͽ/!?an1$b\3d{ϨdrWq&O;{N*a+-p,$.bUZBACO\֟<_z1]\:H?FR? ]wg^ݣmcVi\2) ߻5'h+ҬxB> oXYeˊH#JFPZuwME;ƻcU,@su>E((()u4|]M5R?ZJ= %O$#kt\H$h㿸DnjY$./^HҌߝrV?4yT2H>b*~z >FIXIKRd2soMя_چ!z\b#t '!6ixW@3zF ^:q[Xj1~t=­IKv.]i&EF3b*`6~cs[̏, A]]0PT yoMѷ_Nh-#)J]v,$Ԟ:foMѷRmi:6ߝ-i:1~tRcM@ E&oMj=7O7?ZJVI@sʹѩ.mH.`mNIaevH® ҽ+˨X5̶}lUdJзg#=ux5\XQxP'| x?-uKhԣEtf(0$vҺ{Kv&" /ڭ|31;v^X${ xvi!D1_A 20qwsP< >; c~[Hvt,ƛwVxi)Xǃ֌CHZNC=ƛ\gɂߴ0#fRM6H yA]K?ivu^jWX,/8_ 3rrv h4-;X蒈eg+e޸aȭk4V-qJPr0!JƩy=;V!(4`@%>C@ E.QKh4\t;OA \]刣v`~S;xrFEeY|BЯl,h)9U#nߝuϖm(0ł8 %Fpyh\R5HԂg%t $c&г HLu+eo IZeݼ3`U' ~ hxnʰ8H;BO]QI;a8\Z"7EI ǒciKb7\I#OlQKh4nFO*TGhJFIFC  !"$"$C" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?].|m,FFa"`qk:eζws̻ 3(@琎1M;O#OGXt??HhuM(pt#/s_U(b"k^_Х]FҦdO[7ڛ{}iUec+'sbGbCۼq'uzԃ/hfh]tb xʧ؀F(KZh&tY2H|#myk?n!|5m7|gYt(%TC8n1GlKg9j>(p<'d?Wo0&sppnTcf¬e;XUcQR{V![R+ʆQpSʑr"Lz~mtukYژL-ռ T8 Jqˋtút$*=+`S x-K/*,I$W{pZ$8| wP֞Ko=<1RVRAdzZ_?W-g[ҬI7ij33),ĒI$M[G?/ jQ@O hG?/ jQ@O hG?/ jQ@O hG?/ jQ@O hG?/ jQ@O hG?/ jQ@O hG?/ jQ@O hG?/ jQ@O hG?/ jQ@O hG?/ jQ@O hG?/ jQ@O hG?/港^#tU[2񪣏V)d0FsڻzݝiڌWKkb-ʳg2:(F@kokijwo4CP)C(' UZ f1kh2F .!O!O<^C&KQ$oR~F*洮Aj&[+iQIތ-_j2=h(((((UYT̖q "Ng G'kQEzr*ׅ76-@' C8@I9#w<KMg__M,>nm2c;+;g᛽Jú}fjۤ6~hȐn3N5m>6UE0H6K1CК@ X^2𽇊ӭ5[kKx%Hav0vpr23&+FNh(((((+^5 na5edDr`J a${ti6:e`&dyP`2XMtz}ek+8a" 8bHJ_xI<$k5N =2kO"a ʤT.$G4K7l6uiaI6|_(d~k4y ?5|1]'Z4O 7Y"raKo<0#vA'}6Yhk{uA^PϹ'rm_͓xI/._ xv]:x\[뵤($۸ps7tK߻ۭE96˴nɑvҀ:6O'G'_#Sw+Vk[d(7 L" e۸F$iܬa8MծC(7 q22y ?5dӴMKkh HdyxK|ې@4{yY1ED#JebT #Pg<$h_xI7h+YE[V0/$i}W5|Ro"EmqK4p@  (d~k4y ?5󟉟|jv۵Z HdX KZypĬc|(0A d~k4y<$k>9R>fOKCu'аVpذ\JQK+xRP,L쑡9ps$q6qz'_͓xIVPբV0o$[hV/{} p*hm']SLY+ I3}-Kcfs@<$h_xI gg·~jẀ=yeH0J:֍k[HnS@dN?ލP6O'G'_(?焟G'L+5%%dw_[rXlcڬOxڭĻIYX3=+h[h6Zm _ H=Jѷ@~ ƛgk,&X'X7AF t#ִn|kB#j+n->F]QIr{x^(֗)EE:_2ӒP`#'vڮ|UkYGCMˀ3A{M2+5aUlzڿIq `6; ne65gL;h z~WYCwVoikvj~֖ڣDundsHxS7QmRadžy Ds9 9`îG~Ƣ^7h'a8w ݏ]xH.Ե35Rr|Gˍ@ی}NeM*_0I kh`6󜓞O5_?kZ&PۤF&BtQ*]nfOZ֙dmXmFRK5;>4p|1cᷞKYZ!f@m*~S4}Ο&fdH7rCgWa1\_hY,l0κ-;_Zx'^O$Z&ngHVs17~+ђK5h/'I6)(+O O-i"oq]Lb*jnڌ Ǯs\)^KqZ[mY43[qx|w QEY|-u/Rv7vo..r:8SY:g4ZV Jl-uYB7ȐEPOh?ORMg'<o& {#fcFRcl+@zKES݈$esȽw]#z͝Ƈ%{zdg?dlgNm_ wzGwo[Gmv;F8#iVv!{1gR&6cnnYwجX+P7g999 MmE}:Hi |;s^ ΗmC{Yhnvo` $ $+)$z?ç [[ۗX. ;R1Q/IVz>LdNXH'|Y+/ ;]^&r0@ć U<;%mSKM<2UI썰 d(Sx;W^46G Bk,ʋ뗊!4x_ӮmzHmY\rr$lP.q[6v?4CL#ՙffI$斞":e֠S<^<~lѲpr-t۽-灼] ~mk]Eu$u)9Ǘ&=} XPvcx%jo!Aᇇ\ԞeGo&|lRS#>/5 n2R3w1#YsъFpG9whOT7Q^_ڣ2?1U63Bhڳ˫̶rO6qmXEmͬ]8us uv,%w&%94IjX*3bD Ja5_څ]1e浍zEl( `1ҦҴ [7Loc@Qd|gsOxvƏkNjG 0B.ᗕ1'CmṈk&6Jm` |#`5藺/txt{I`Ǔi%m xmB08'{MY#Y$roJt}gxQmC4i6{PS&G|C֓> g,j6q',3o `NxGZ<9;/дY_P^M>|`uADMhA,Q$mkW>cR1GnZT_鍦Xmoas嘶KGn#ɭ]k6I_y.%,K.$>zC,q%k**@\Үey-HDV-}=T^\t^YzfgrV~o:B(L38 ޤdWB:<B:РQL} <BE3·z(} }:У·z(S<B:РL} <B8ߊ>4qӆE,K0 lls]}ڬ00T#8a؊le,M*ۼbvg5("p$Bu=YH[H¹G|H<⴫Ś+]5دnDr&޼}C 5^FS(d`H*At˸(Hd'|3Ϟ#@֒DmG4W.Xn\6q9ưELa.yo7lu41/żw5Vlh$YJP8ܱby|GumMqMYgI\|KW\l]& ^L1;pǥi_ KMJMJH$#*eH=y۰7=zP F,-F#*9 % l sE2ycV/ߴ.y^{^|1Ѹ4FU[o]80\Coy#!V%ʷZiKx5 }%d<ľNs\tQEQEQEQEQEQEQEQEQEQEs^65%-t-3n!AfO8X۸|z|3,$Z,n R![vA*pLƏ1焟yE׉ptmr-^IJjgqc$^ c9_b&=9')rK:Lc*V78Qyק<$hxI&Sա)toi-m,F2dCópqg }Bmr-ߝܐF=FXeA5{'GOƀ$xI?_ (~k4y<$hJ*?1焟c ?5'GOƀ$xI?_ (~k4y<$hJ*?1焟c ?5'GOƀ$xI?_ (~k4y<$hJ*?1焟c ?5'GOƀ$xI$l~+4%qR@ g{(][,'~%8>Gjv\^ gxcYxs&61;mv6zO؄pDveicv率aNs[hQd)K;H.f%\o(׊|kh|U>vٱ_7,DbLݖ+zVNm^LM٬/԰%Or) +;Z=t}J}M$H%3Pp[x3Vsmisi0ee n)0֧_ipjZEŜfHhx)'$6085(V5S5ۼk? 1n=+V\%̐=S8ξj^xs_|4c<>jG.$ij]T]̈́Qr>=t}O_6nJ,mc#.g.%z8Z;KX UHlVIOB2C'V [mxMch eEd9'vB(dj [Jd_6)&WS nw'hGP@,ךYqZZ lY`e*+EJͤn0!X"|HUwu!FIil@QMFs֓s@@QQ<)< 0@l@QLmێ( ޔ)sޚj}bZ͊}J75IE03n'>u ]FC@W<=4E1w)=Dtjx[ھ&-s-iO $#\ڝxSXozƃ<ױwqmMMŒE3I8}Кt}y>xLy"n4[ɩ.}zaޟmX]ެKc>K]YӠv+֖>xퟁ|ezu 7T}oep耐I#j] ܦMi.g{h4l( xu랔V('vڍmKЬv㷹یH 1ZP~})? FP:Q(3P,Rnˏ>Pn{QrFzucieYKUz; 8>Cw(LZ*!%MHH)M&Z MFFqޣ)(\ 挊dֳ;Mnn-m-ż!r: ljZX]OЊF{Ѽ]چgqOwhmnmA죶l̗ $[`yl>KkxbxKk2v0 1[@FM=5Uޣiѯk3>-K9]'*s9'5b*uoXȯtI#eUU: $xoM=4}oѮQtGۦ}kj:M=4n{?kQs_(?ERw :Z;?`ylv2}zOr9E.Ǐ'ݼ!( 'GTSm$OKQ?GRԿ*WQ@ Kr[suj8#M/Ŭįqbԓ&rO^EG75dxG?90!+qZsڽ>t|+kmJ1fIn?{ JPzUtx[T#(L 7u6Rdi_D,>i(ƛ0A2]uL|+NG`</_*}6ݧOOfxfEuM1L (<]BVzУUb.thZykvY!)$*['p=IiōvL8AR$\+ Т(.;>k͚oLH*r:㚽b]OwxcbTL0{oFZТ(sYhnyR$+ct*X\^(O˦--})<ǘeW#{>f*_>.PP[b\P( ( *eY=u eC+U 2O }MCkYk7B[5I"qѸd_U$2P( ( (<⿄#;Z]6~]‡Sv@pɹ}ך^i__ni7iZr;TFG 3#0FNxd+J(n}D~x-u-N:(E_)*D S-@|ޱE-/GSh!xSyX\uz~5f|G%aiW:$dEnG:W-dÍ_ 7wq_Oy.$0FTğ,Pȯh L>E-{kG}o*U$  w 5[^4nghڝr@:\3VUڭ^ExizM=S%`lŪrKf\éˆ'|Mi':_"Whs¥/rYAfHze5GdfEbr3N t j pX[jzVZ\^cr̆V, % [4sf_#|9z'5MKee\|#{<V5n:3x;N=nL#?vFo<~ I.Ӵ˫ՕHVMHdxbRdCl'$/gs_M홴 Io,b6x^lQ#m d >MHQT n8'׀(QEQEQEq2^xY-/ Ek?!C@}*O쿈_Mi|1߈?ߋfUtO#%R.Ah{.Ah{.Ah{.Ah{.Ah{.Ah{.Ah{.Ah{oXkHVqTEUd3@#7;xY=e8'-Nhm+nmqm$YLG"8F'i_[F)u=Ǭ]ZDcGGDUBۑK8? 4q'e/'݅DBW!K$t=EyxvucUYun%t7@ n-ʖڄyix~ı#"+ C=E,iO}*|J{n CRx{VXБ<# r7C {{ ]n =cX<]=2He@-s~)WhLpK\'Zb|x'gI^Sw ]0gA~e]}֫ilLmyPtb[oj"xli_cDCn O_G?2յ]|@^I#UQM2(e[Ⱦ"[66̫`P>:^o;Zo;I;"T `RҸ546tD+ 0͎ygJ1O%{8 Sʱ8>ӃNz͏#כ'uQD`\@7sՑ'-]VFJ_r#9mKͧ 3uàȯltQ?qK}m|=nhg3<񚿧ᖟ??b<=rgʗD?*U$ TgtQ?axLO'@}Z}Kx}>/˫8ԥIu')?>Oz-xI4_P:2zƊNP4^yd8hU,}5k)tٗEe?֡7] \?*{?uOz͋Xj˨Z.$BGC =#OXtͪh ws#5\?*<~TSO8?iiOK7vo<8Yg<ˏyG)yS›&;47 t{YTӴVoo1l!@>NG<"JX\Sҧ.dQEsAEqF(((((((((((((M6h$wW_.W gb zo"8wPi+{i_WPworkbench-1.1.1/src/Resources/HelpFiles/Menus/View_Menu/000077500000000000000000000000001255417355300231075ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Menus/View_Menu/Thumbs.db000077500000000000000000000400001255417355300246550ustar00rootroot00000000000000ࡱ>  Root Entry1G5256_d89155bde2f27377*E5-5qS'ɗ}-5qS'ɗ}JFIFC  !"$"$C" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?ֵzLI'q7wjhK;R(c#x?Z?ğ[U-[HHoǻ!? 8VENv#QN{ק3P/4"g;f"gIڸ5&3?k_״߉d'ظWI$vQV $_*3Jx?#"hkGDִmK3o(S4`q#"֮#{ɂ$0xޥZxƚԭE1*uz&N8_8m-{{=g@IlmY^p0W| &Tg@xy'Q>? {-BInbYad8taG^~vѵ? X-tᝣKmmv9GY6ld]O%p%אc ;B\&G\cTu Iof I߁ҰR4KLGMhnnlT|ajHزDEUyr1R1ʰdVM}Rԧ z$mc=8KVG2|x5xOQ7j_ Lho'nx +/] LKk%V]IecA`IүYkZ=|T6!^ hzM}w n:~!kRxU76o}l7zVZO6i:n#N/nm"8h#'M;@ծoҝ^~s鱞i>L ^!iZohgf˸F #&Hf'5{B>-Վ3c1+/8=ynZ{⤚Ƴ2G,r?E03UkFpUu6V61pc dnp +?_7ior7?K=4VP -wkQٯ׭n}Kźoy5R3$ddLF3Ԑ;T[ovK-ƛt$2R3FYOBJ拈'T2fQ{{udBYj_?<}|/m[A=@r)-&9#3^ɥۍ'`AgmJ?o1ω㴛ZCt]RX*NXrA$upsֹ_~Ƈ|RSf>%ӭGjwbKV{ec: 0¥Bm+_•7E?IkV x7EQ\}8((BM$yRhI6>PK#y?:SŸf1_`_O:ؼ:ՎEo*4ӫTs7?A.mKktml#wUq98hn}*f ےsy&+K#y?:Q G u} >0H}ߧU}QQx$OEsm&·?N5ӿ*}(}(b~#@s`;TzqNUfFOFpwF ~cюw5_F>m>s󽮑6kA>ߧc?KyEL:3ӺU}QQgGHc^4?jtOiƹw%WO[Ҏv>pD\;"7?;N<ԛO,V'lͣM]1fK'Rúiva>:8Ok!7!iQQJۤ*lpP1EQ$QF(UUW|>?]6+6o^:Cqqqտ/cxgCxF_2LV1$_16g eVV,(,>/Qial&9澯P q֏ݴg6_]}u>[b[\:ij7H>e<#6]GJ *=M}OH0hP* b(".o_ͽ>;²izx_Oխ|}z;KjY\Y{ef/n|Үk̼ZD"ӭv$$I R5|_MbmH=(=)7lq2(,ԓ@=(=*-#wEWxߥMm.FhhvJ6JMƍƀhhhhvJ6JMƍ( Lh&$х4j0[4ZTs'tH扢t adH|hc:\1TP0+GhoHaĤq)CSyV^_No-F֊IkIe8^AMH^Io+F@`I3k&zå (Odw*ˌ7F0FF*U3owO=ApSoVkG/kn-.Y[Mqf"*cA;?^MOŶ7^E s|dW|'nxICs 2ڼC"cc_?txHX.<+eqe`%f&e`rx=Jr~n;Y|Cl4{=#Rӯ-bg2,fYc50B#A !%6Jk> kWo|hz-3o58!y$ bMtmϋ/E~S9u+]>xw"e1UUf :խlExZ}:ḛ[YYw)XĄg g /?ĺس7X|=y>"Y]L"dr1Ihz ڿc־)x);ZfVmietFs n`Tb{ľ4<45 WzDrVX]$f$*F~PkKmdG $*X'ǚ=l'o~koUM< [|Ɔı8ܠdq:? hҴM7^-ԧ2Okl,M^&kIg;#EMkԃŒ&VosP7=N`zChmuѮ$ceGIwK.C V&5s8%9+xS4t]b}3PC+9bkf8ޡ]1s-oxN/PYZR5ƗdM%9#u /PIϰO{]'CZ]JOodq*7nKyDep9 qi Ү45գrb ǻ$)'88k;v^7_ii(2=ҍҤ#}(}*J(=ҍҤ#}(}*J(=ҼWGA-]jlm-4R,v8{vyoYOA-V\۬E)m',zt<  6nG0}kwi: s./I.[9tVv koACEA|DƠVex[׆5?%i-F[b)b䗾=Ww:kB1 YdE`̞Z1^Ş|0n =wdB+Td]1~#tm#+!۴Å$ _|#;,G5İ3g4ŽtB)`F*8 |NSKA0:۶o#a{ M=CEaK[MV ;h7Aa,g8ldm]v\y%g7<5s& EF}r# ijxDW5Ԭya*O^#~ xgR־1kH[ixMcV&T2, A`@gQov֟M3x?#M}4;++lH3*d݂ stjXhpYmM82dUesȮ[FuW^XjZ;H%4NvWRGQFx{Sw|uY^owR5 :K 6/cW>o:Wl$D8vBΠLe^u|b>k>)lm,uV=JEb[2, X*j,NWþ5Ե?:~՚A"]:=żݼ 5?Ҿݧ<={KZC{lD|B}sz?뿗Ƀ֛/ FcA6v>V3^%:qivKiw,KZL,.Yyp5_/GmΨ/ڿƷMO[}'Դԟ%eɏsch,0q <8X}i2]kbׁ+E:,$`Ulm) "Lu$:,N})ƙH`y査3ied$JFcրJijV;߈?cmk0 78SPu.2gNw%V+KOHb $%U3pÓ^P][qix^vfsw\gTj?ϼ?6]KmR}?!M-QKo.6?e{| _[ϼ? vѶ%| Akk>&FڇֹQ)~m>%Fڋ(%| mj/[ϼ?(]mm>[ϼ? vѶ%| gm/Pm؟0߱J-ca\3)Rc;4=2!~:Uݜ[e KGF@C)F5vT9"I6DִT<@8|j6RyWVuоv̧kc5ώ5ϋI+i6 EpkV8Ez狴 jMƓoHHPPN8/wτuM?vIҠԮ$wB#FukužI ;J7Vɯ  ~ѥ]i MM$l0 Fr۶!¶ N }tjiF?%w2w/ԀnڷsLo5eP =kM/I~x?6aqq4~jaHE&P2\N)Z~\xOnvW?|/mRO丷-u2$$luee9;ARK~2T <麕6,HܭIȬO|=ԭ5?Goơ-Ɲ#E-n99lq~.n C:kxo5+-{"5fn*  )-ne}<[iZ=uh|BY^;H<$'ʿygq<)#^e7Eav#hMׇ &i"IVBh Y~f|8߇u/h;OVGD^.b< w *;)rj7}?|]<_uǪYyc|mĮTKk; y΍MRYoy-͋G}}>+esg;O#OH sQN""IOfAC b !fܴJav\⛔ܴ)֍@;S[7]v] 4f1oҽz׏\gVw|Xyl S0%iW@cr8yz|wxY-/Û)uded2.>F;Ho_Z\|#xVt#8id><+]1ݸ;Vyۓހ*kz>}\,Us\ÿ O"h|nu8-Q˙q 浾%:7.KW7 fK"YZ[ay1rJoCkH3{)&Xmif"(4 ]OLlgI.bY~UR^'Yys-u$jBm,͖#w^q7Zmo W÷x!-Ckyr!Q<1 XtZח$/wtO>^exXCPCuE퓌#u9jJޗ[&V׺o6s*[\B%mq #56v[FAkhT` MgxSxkcQAi-Žv#bag,[rR 9ŰxCMW&\Gch"u#^udKHoG|tY5B%Pcn6 }ء>Irܷ$S~%Lo$<[F"@"|+n,T}|Ú/R?XNDx} WB^>gG.ʩ>a1R~A 7Kq_Nwo[1[Ē;Ǘ#r}#䖳K6+@Xû<+o[ h&4Yu;^o'-( Fv! eI WڇuO񶶭~\8H'މ H~&t(|b:v6 n4[Jfs7ZUqdc>T>nRHᳶvxWP.?h1F)G= LQQCAh1F)G֎h1F)qF(1F)O֎=h1\!xBeRkMxlK{cs+En% /"p9;_5oZm4=5a\G?;u>jF#vwc9kKg?|?x6i-Mr5[pHFq^5i\Xh v!Yݸ 秥y,W#jIn,o;ZJ$FYPN9gZF>$֮ VviS=yy8,þ)4ԺǿI}-o|G EePwfT&*?g/pj(UbjngmGr$dL{HeH8(;E_Ȭ[4!ZCߒH{P2g>ɓ~(a֑ 1֢ɟ_%ht`P>DɁ7H!w}M/P4zR\_~)V$B@=iíD!ߒ' ?7HcTix1oCS~K+)ž^F*!.(1M/Pq$Ȩdt_^(HK~[c%h{%hۘ/̿4yg~eW/n$־*]V?%a@ 'k~/ՓC־!i:'lQchg}u'ÂNhGϵ;㥕_Z~i=ƉwAI\*xV۽kMwO^ _κ+js_3Gn7mMm k/i647Q5b;zJaHZ)d2*qz[+ׇ$%4iHywѵ`PF"Oxz {B}Kg'ڥY|_-.U2qxnTֵwW!l4);dţ,#iA<\Rom[9EմoMSѵ+=JlW6>RAp{Wok!tx.YЮZPJ` dʹgz |>tv7uM&ܒag\1C'J %lj+)b}jR 'H.fiB ITRAE {/"MJiZVxKfR- HTezֆh:4&mc[t#1{D`/#,=Exω&_!{x'ɍ'r[i8ƒ(3workbench-1.1.1/src/Resources/HelpFiles/Menus/View_Menu/Tile_Tabs_Configuration/000077500000000000000000000000001255417355300276445ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Menus/View_Menu/Tile_Tabs_Configuration/Thumbs.db000077500000000000000000000350001255417355300314160ustar00rootroot00000000000000ࡱ>  Root Entry d6256_b8d35f7d4d49e6ec*//l6L/l6LJFIFC  !"$"$C" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?T#Ӵ=;,;WyR7a 2wF$a#G[K CY&^,yc r}?aSs{Mb0R^?rkQ\8=sַ}ұTR"0T8^^4ݭFzjC+mr#6^(5gM"֚js4\ xVFυ|aq7(љ\MGlf¬)UBi;?z7\Z;cCvfh ¶wto ?kiMܜSº 3|3YJY$i#b$nTs"Fp ǎu *-RY[Ska2Ëi#Gy+Ri;Okg#(F1J  R^;/cxM7nW)|@OK]Mg2)[D#72w71g`Abq@1RK=w ʯrB;?-g5ڝ/~Bڂ@`ڸ}8K[-nᴷhC@򊤜VKE$#ME?f%>rn3"}#TTtG2Ԅ|>_$?5P$zw& 6z7GF!.?wto sQ~tj7oΩ/wF\oߝڍ=7h?ѿ6_3@r6aV(7htziW3kͤke g>:}ǖoXm3&d,"R0"pr*Y5G5̱,Wq-ahpJ>`35}}abX@C]q}4exaG LNRnd;A />+7V(| wKσ{G} O|Auu~460FjGo>]' %呤oy+7QzŞ''6*y6L0(AA|ZS'?e#N'Du }p1zӖ 0 ' 3.h.oյMWW7 BgEzw<]+ß4(-lf!osJ,dFTNq_Y_5]ŷ-ў*X %ܣk37ףo=B%$DG諓hi6tT(myD I}9b WUԗVՌSc#+~YS4l?uZҭu _뚺}=:hfkpĪ#K6SG񆧧^kt$AHT>JRMK9ⰐFTw^Rkkr&Q)k3?vtq:XGuq.a4IN]xEQEB0NTYWյi~o9P hۑ[f46K ?L/u+ Oo"#.N+#])f*P[rصe:(d垐UbپA@JIWM8I4λ)8 FX wQw6}j~%mVن1K1KrwqXP[ݻX .8 €$g|jm|QpbirLͻr\ӱd U+VƳn>d+@#<֓_CX$3LF֫#$IhixvGT3I8e8cD"u JF2楸|7mf.[-MA<1ĉ<`~'#V4 cN|OwkJneQSFr@︃]k=w 'c#2HT\MAe#_js o4P,ɱg%ʞT0<7K;ƒYOFPTXI65/ xJ{9,|Syammwb;F-eT@0a*(č꓆{xiNe ӳ8-<#ckGM]ȍ 21fvv^?` (h e~ ]仝>P%Wj`@j/̸^ -jCsaؒǎB3uk=JR6[SKI8^r8'_LQؤ'_G٤|ښo}?4I=Wi?zg?bM7`?U?}O^أSM?@4fzצji*K{;Lw`@HhP[K .E'J.E?*6G\44O:mt..R9##7Hkw_N dTka)pݜ?ٰ_ʍV#HId%p#8ZB>.~g$`x7}mAWIo0>#ݞmX1# *Jd8ʰA@ڿWOE9@=;+tqդy<)qG 3a@GFHl;(k7O"cpN¶WU_!8aEPEPEPT>g|+Wh/]qCygd {arKj-4 n&Y&io [ <ѿ$rsQ@ǂ<1<1DtDF\(\20a4%`VSȰf2D`ac8P3+v<;j׺{r66P0``2jit].M> >KDkh&It}zaТ9o^hdKUG!.ݖHWeS|$Ӧkti#c6"9V)#i ]5: Z( ڎciz^&D6#rnR0}FAޛugacMw"DUmrMdHjmcqvK)mw0KTPe?j w bP)❷TUy/&Amܴl.ߴ z ooZb͸3JK3~jӪV::)HL ,ʙdf~M_|-N}[ ( ( ( ?AZT>g|+@qWO񔚤TGK8 SWSu>{j^>k(l)B*T߅ ?6-~bH}H z-%^KHt5+G0OicI 0G 8nE9 !#niŋ˸u+{kh-V1 j[]Nݼgc-tQ]/7>1t4Uka{`Gȳ?v"qcɿ%mI.5ew3ẽ)wIPW^Ey-N~kA ,NegQB4``{MHnB, ZCNY7#H>E4?憎[*}%Q>?憎[#IO~,=MnJWTڥb+N2K}%Q>?憎r<+lLfM9mKIAd1pHީ[{ m4BvSi!Ry̭=pMA-C&}EYPh:F+ jg&Q-f:IkSJ&&*FG[}[&7#+*UEPEPEPT>g|+Whe;q:DUF<?xڨ?ně VyD*XeJTSŗ|MZ]Um/e-GnU]—;AdPdWeK|kĶ#ۙHvbXY+#վ]]-5k? U "v rTMǓ3c=*տAjТ((( 3?A%|MZf:k] 0Qe֭c]nf^=ʠ1⥦\H$)bd @$ѡo_X[HmZ*%b-.8ʛ tV'Of+1$1~??Ə/PCh-tC,Z_GKYRqԒIɭ[yXi^~.^kg/5 b[I坥ԮVW.P?zғRHCp  t7)o,[ cp$dzg? q2I1:P`3V=,Z #`,eY#)dd(zԵYLrrl){:$,F0Y]wdA|͢4o qݔ4Mαb]6C4e ,qAEc’}cf F8V>? T֣_|-@QEQEQEA~7|V$_\řDM+i4ߴf 1KZ$`v[I e\+>Tm?q@P %i_]2l.q+>/7O*5כ ַ:$)_4CY\g!Y6P#'qlouKd.k'cķ\XH2K>~Q kqO*훏O*Bkq>^Z4_ydwM !P ( F7tX?77U77U! zJZO+h%_ye;k 9sZڌn,vѲ+!RH Co%s&y"# =p>ע}ϥUm?q@誟m?qGO\~iP*O\~iQ>T֣_|-I$5m0%$zJoZ4(((*V3?](_yW ;_yFo|(o|ѵ5z4m^(_yW ;_yFo|(o|ѵ5z4m^(,q6HjQEQEQEQEQEQEQEQEQEQEQEU-vkm&ymVlsWjh-%Up@*;75䪙ygeCܖaMy[DNYuCmkB+.{3yp=oYD-3QD&h`OҨYb{?Ry|饈y$G֖0o4q#@B$o\[PX7 6MBb15ޖ ѐ2ey88AV Tile Tabs Configuration Tile Tabs Configuration
Tile Tabs Configuration contains options for different ways to display Viewing Tabs in the Viewing Area.  
  • Create and Edit...  allows the user to save preferred tab configurations. These are saved locally and can be opened between Workbench sessions (as long as the same system is used). The shortcut for Create and Edit... Tile Tabs Configuration is Shift + Command/Ctrl + M.
  • Select New... to create a new configuration. Once the new configuration is named, it is added to the bottom of the list of options under the Tile Tabs Configuration menu. One can interactively adjust the configuration and see the results (if in Tile Tabs viewing). To edit previously created configurations, select the desired configuration from the selection box.
  • Rename... and Delete... allow the user to curate the list of Tile Tab configurations.
  • Select/Edit the number of rows and columns the tabs will be placed in.  Stretch Factors allows for adjustment of the relative size of the Tile Tabs panes for each row and column of panes. When entered, the values are automatically saved locally as preferences.


  • All Tabs (Default) will put all opened Viewing Tabs into the viewing area with the default settings being equally sized partitions. (i.e. 3-4 tabs = 2 rows 2 columns, 5-6 tabs = 2 rows 3 columns; 7-9 tabs = 3 rows 3 columns).
  • From Scene: is automatically selected when a scene that has Tile Tabs utilized, is opened.  This option can be selected/deselected but not edited.
Tile_Tabs_Configuration.png000066400000000000000000001155671255417355300350470ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Menus/View_Menu/Tile_Tabs_ConfigurationPNG  IHDRRt;z17iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:3,GN;v . /mJ?nlvEQRPPD(RT ϓ o룠piq QI IN(//m˩'@kuk6k4a!^j={lQQ\ !׷vzanT\=(wgꖳY'C%ʧNPWI 7ܹTp8ux2^g 6@%337nm-kZJu}qAnwFFٳgzNjAnZmVv5jPT4Mߨw߿T  7Wc:<8ōjEGFMͿ`뎫 O6$SY y95@99Uo( Bf ln/Q$AAm nά[+U=[#[<Շ+\[ ͣLK ەsq oݸºqywΥ|꫒xS 2KVLΤP\RAE(1KO{-vK'!r6BEQ$1 c0rssHTMQD7/RV ||x{}\K\~'؋wvNk{'P((0<E[(YOVT Izv&% @~SƼ ,KLwh5v'WiuzDխiJEud÷#f84H<ǹyh5hc5S^~QFDCyG6L1Pcico"d'p`7h8dF8?q_Xofu0ggi /j7 +q Oi{A O9N{.Sģ6;Pg>$n4^[PVVTÀf@xE;x-NaYiI $I"X-mWsի QC($HE-X]PdgK$ Tf N\S9JEH(=_⬥iW}Ȼ~Z{K.1賥kSFh9 JZ(aWK緼6e4o gwLnl9o[tҁyVWd9W#aʞ1>~{H D7a5-{Éq럨 ]_˓ޫ E>ډ(=G|Y5ٖXe+8p&],i]gR9M$R5+5akBbq׊j„(Q$bDEdKFS֤Mh"y`E .>hm~\7 |FcԫY-Z-Iv9|kD#[jN(=IK^h|ZVhJJ@r <'nApӪ"֭hB#Q$IRD$A $QM@6ur Ƥm]"u6/JBnTHɶ>cBl1۬BWm[8qשjGlHhk*GZ?h6zˏ:Ķp˦mC눰cz>\ËDUkXc ;NX uA8{ !m'bQ|c W; gRpXm/]Eѭ#.rnX׮e 6K ִ}N3]㽝=]W`8]PksFV++1 5@oSbjsq.M`4MB$64Cs4 hr"v[. xd7mJ^䰹EN~ԒPҙv$JE QPǻ LuFm(QK$)* ߧŧN&#F| Z(La!Bk4zhX,v]ҵtSzNd2y=ZLF`RQj$H*JTSB" 7?vܣłA($V'U׫v\<_?mnBK hZX 2т3h٪̟B:Ap}HMF*O vC`/?fK5έ'l!m1euuڎ_ct  $Ad^FxӘ7|xhڴ+!)Aa7<^Ux-!!폤-@!_>Ź3}CZiH޼'0+q]ذAް#g>v%x~JUGF#G-6eFo ^UQY-,,Tz^ɓ/Id- ˲n`8sVF$PWhҘX0zD~S| x_@9]NF+~\xX@%oYj(җ/'k6|, /,9wu#"y~}qe].$IFQ1 y9 !jiU$+ooooT{e:3/Vug2܌V7 k-1LJK(4]+U^h/4CW74 e+.]*JVۜ`t,Ⱥ뺮 Znw`/e/xPڿzkÏ3{PaoM[8dpz6ϋcmc ̳Y*@%o5ޡt𨨨Р tf>(~mc6c3n?%kVx1#irԣ򋕒zۭ/$4Ų.e]Dm;)͜VKj nV*OԢö́,O1*)f˗Oj`Z<|֊flQn97kGĔk&4n +lҒ'Ւvf7B g+SP۰O?h5wci|C'l`Y7woǶ}n{V裻3ݟW^zI?]@z|v;vO^<ޞ}ɠٗ:6(Ą4~Ko8~zո`F KrdmSžUgg?hp[ӗ885uuS~Iivq4j,f64ӤYbGO8w~=SYȘiFC3ϧ{:i`Znώ#vr=O:}hm|B| C);gi WNv,2T6wq: 8E=CV8VlfS/D/hYdnڡ,`2źCFEUvNO5jԨQL[Hï୹VoPj Ko)iFoGunk ]cje\1f9(4{8<9{۸KC"fsx#YϋGު=2޻ML725xw3r]BFL)lw;-ױ)S5=Ę͉)V0@46ۄAGnэT([ĄfsD?᝵eR9UǘfsPxR*shyAQAOs`jԌ$kiA~KCژCF.ֳ6{HpT\M+]'g`~VV`90mEsV@j^VbA^ۡV_6jp++ܹsv{eipԪU+8S82;;;--M*{T+(ܟt:!AAA7\~gffL&amOS-Kfff=!Oӕ=RAAB>"7us{FjiVb^AᾅB*7s (|<Ôߗ+St U^9vJAVW  _AʡBC {* U%J+(T9)nAr (vNn^QP1ACizw=BhBNr%^TA)\ CZ1J ܂duIy6R K[P^ZT4QRմg:ZW^gO 6Rdu,gR]_7pqKD i}o%+ 8lkr.%㣑~̧ifiP\H$\uٺ}yG45MUl}`DWg>Z7XUjF >/!fEQH!IDIrE_FkYLQk4OKUl}ީ; }Pmu6wDS,|,Ώ5ZF$݂}ėNJJ&=.rM9B$QFwLnz]uꊌJ { ?5{e2 s?6vXְ(vM>wl玽-F6 Suﶱq p%'/QCD-"g({Ht;ȥbsZF}}b"^:ηzG߬2 ^[Ige9yᖜ*Dw D}&=0|+If"F%>.A8=ش|(C;yŃ%y/J.{MZms nn^r Sln,NIawwh*9ONݻ})p&OoOӅkV6pKM-rSvyR¼_8iѨ9\"vx_aUm=p)ΛW߸iՅ6|,j6jҤ>8N+ ;{\pNXv6E 6vJ݉?ȃ|QxyiQ@^֙s"jC۞?Z,:a ?\ߚ{VÑ}/\~{Cn֥Ě϶AܩnvCrIWrHp*Ganף~eQQцX5Yb@p6) I Aޤ=iM%?$rinT9蓸~{]թo ldB(C<*4=r;Nj$fQm?36`4rN'o[rHq}nXғNN3eMChoۿ&5e_'"'e%$IDAW-v/DZ>j *"*Õj`.IQ @Ŕr+\.=%&#]L_hu₫Ԁ.$#sF|!@^ ֢d\5^~+VH]F֭۷o5VHv'5+kzMD!"L]&i(iޥo?Hh'? yz>&U=? xL e >>0Cf n3m7{YE@@Q"H$PgQ"  +پ{{6(8 &}y%ح@7hnGU]^Z jF-FUTzU2|>ԕ:.UBWyG=XsP,BeM]#Jqy|iW:|!۲A1*Z Vk,xjz;_ƶ$PDpwO$t70PELx4)Do( v% E ay(NXRZȆA% Qͻ7' (JAί݆b!W,EK&LA<-[Gfv_CԻI ņ%VLXEy{PHRi}CYąo?}S>;"Ҥ+;Rh hS#7\d$,m*^CWASTɢ PDӴ$I"g޺/?|9jƏk]"I2~efz {|Wv<:a#EhZ:Vi KIs@h4E4-n  H_}_J /G{ʤ9zHxgB( -D1EhXxJNv b@!rqBfE"\s;i٫.ESKk7N.j%QѬ(ҖA}P!}E A%"/B$DE3KaKrrf ##v>;=Ȭ[ݫܡaddS ^y^ C鈟f(lX ŋ3 : }ɯuonjO0uHYM$ I]4| yI{|q`uiP!=V1 8iѨͺ 7';v Eɇ\METrU*ݧnK 'U|9u mH:{hZ޿agZ!Eԯ44MSlݖmI禕 JaW4M/R.3&bo%˲p\q΅c}A?pd@ P(y "1 h!HꘅsKov/K+t:-iKf_iނeglTyxD(QaXBZPdX47|/wUѬfJ}ԌѨ:i--\q~ZVfX5ͪVbel&V(|'OV^VNع k5U <.V Js0tgWXG Y+9[zKi+:xxNO '/; B~Z~0T* (%ͫ7LЀ:}:ȫNN#v^j$'?;;Oj}їIKz_Oi$>kg]W@ٟ73b{.-iXW-n}Q/˟4ﳿW:ktz9Y*3Z%D8ӄ3S_uuf C~֡jBȒ<I4,M[R1pjFg_NP sg5|wv1;9%G]F[=%vL>9sR_?v3QfxW:_?rH\J^N<빌`udn5Vn6/^ylIS {pBW6/ynR2wҰ7I-aFZ]:Η\&Ψ+} iJ$Y9 z=SFgpޤUf/OO\[ob[bL&-em+OT&l~\?kkV+f&) J4khyJE^tf9KF %(pNwsLe}̷"N媷.ߓewh"D*FCʳ"{Ijϲ%aWakg Λ)cgY-ŲroC>4E@OI"Q|ު]ulfQLOO={vƍ|IlK333n^(nܸɓo?EQުK*$I dggϛ7A;wC<+ŁRq.*r$IG=~xϞ=z(|I% }Yrehh?|d{ :EэUQ.3*=֭[[j_:s 8v΄.%UG3kOp{ԓ"Ic~1(Jh0bccwQn][y>|e&dͺ1~s94q'ۆYۢSF6EpѝQ/DQ<~XX%x$$I_ڵfNcKB;IF>ADQt݄5j8qtmeQœ=SkZw%[]7W^?gO}|]Go$VoH?]tTnot:O:Ʋ(ji\fKixA ˆ}ԫ;0pݞp/W?5yr~{-Z˂sr"岺kϞ=u8wp[^oڤcvx⒵쏹o|(Ž^}ՇqV!OrS"oݻ?=x'k׾*Իn|S]^.c*1JlˣK.zrg=R:~٥E4o#xKRTѡ e|:{LB}jsxiRaFRzvֈwCBj6<ש?(ʕ蝍٧YU~c(?n*Y}C6 4k֡.k/xϿ:I{/ 2p S Gc=b\t7T'p8 [[Mq) n?5: y jˎɏ_HӴ$]6ފa.nԒ}xB[@|BM,Edm^/~8DCӴGޯ&GǑx ׻"Iwɮ'֌! `+Q;B4(`JjeSh)vc CƆ]GM35Siئ@l&E؞bԭ9iϓU[:OQTǍ!f]7R%y^nCBB4MeN)a߿ɸ'vʝhſq,MZ +[VZE/ {"*o̚v[GE]q2MQE3 ݽ7r}@)cϟ&-"wCQd?13,{3.bԭ#_.c*wM>gΜٰaC߾}Zy.3N-8I_k< *regg;wSέxmfzy IbzS%B{9-X%gp(rz54M]gYiaE]-зT)Kի[©xolugʫ\/d.>qS3ФI۽tRJ'c0?8&M [|zmݸ莡ǎ{yyyMQ˲&ԩSׯ ~FסC̙0L۶mkԨRn]j{8\*E bqqly{{{I\.ץKv}Qe{ʞ3N煞wS IDAT vرgzf͚Dg{8\*Eevב*,,8q-e w!DRLרQh4ލ +ŁR.uʯFS TVMׇb.?1 >>>>>>j.^!(,pQY湷uh4doE~K\2+ŁR.u[!]:DAA\WPr(aPP^AʡBC {* U%J+(T9WPr(aPP^AʡBC {* U[ z?-jahsxM8ߢϯX>?KhpWnחif*B=JaɤR0[AA?2PP^AʡBC {* U%~YVgpQfZ=\1wԨQgvQ"ICWZ"/aP6<{妒5FDDEE,5dm?ƙgi]w\yneIW<.㖟NhX}k5Zt% hK}٫d o1#f1YΚz|9nNM2oFWח[).׶m|EJWP(@07L& 9*l6 p|;E:lO5%\ͭ)f˗Oj`Z<|֊ĤXWpYfyVf1LI29.o&ML=QK7&vzB<%c8ʸQ;kt..'pzF9gG-ܰڣܴ((A {/dzΚ:q5;&tٙux޽5qL2"DAB*ZV֢VEXb.KJW[[ւ ZTT,( <%0I~L~zLf ͹9oSFBphߟ}7~> lѿ,<Sie饌qQ@]|4OmJH 䌄o߾}_n}]QZYYlh~/:@M:u//p]ʟF{ߔ_Z]]׫73_wwT腸J+.EAЫo||f|~Qѩײ16RCxh8bx3= - F1jZf/ 8V5q3uڼn^C~W^`pLGoq`ƃOǼ4!v]*8zzd~ʿ1!EggY#VyM[x˿ 6bb|;Ix"?9Z_U #<<~RxbdžNyI ŶF]z|Xt,gmdRPx(F~5n/P(|"~*$ U8Ku^SFčMևzdgEx(J#'c^4-@v;&?m&y;915$GP)?<$-Aa3ZvU1j2i+k0hwT&pCQ2K[F:U_ f I\!VC&jaíc GS[{{cw~0ypwg Z¾~jݝӧna*++^*z!FwA<8P__U<<<;;eee{vss#%aXڲ2&EE$'?F`0P;/X,&1O<((A殰hH4MyFQ0Lwg'’Hݱ'3vJNu{Ҫ'섕VAX {;$ 'Cž  {;Vq xd2X,xWK,1t˗/ՙ/FWWaÆEEE999Y&oV=i$ Dzl~~Vݸq!CVX8+++8_Ζɞ5#aOtOO'|R.[fs*Fx|ffy#F7OOϹsnڴSNX}j=i,`i4caǎqezV]nczSJdddQQtdv {3YbFSs<볲Ν;q\^x'aMRߵ[єHOO-Uu톺%9o-㜝99{5{쩨,:e$E!02b1` "c98P9wk2RHV$V~ */(JRLygH2 4YW )\l2rqzoX< iUe.nXWRz.heD*@L(g]>Ks[VzN8#dȰI| d_z$Bk_fHi/hsɩRbV.r r9urjX )I?uߡPH޿T~";7IX 02'- ذ?~iBo%.ܙOx7_- &f\8?|ToabƚWGVc8,`p_̵ nm8qbRFښ9?v9{ @"Q`n]sh_oMaVݟϹ{t缡N-Yb(Ph4gEQ^[IςUCt֡0`etr7iOoY=kLpĨmGV  Baey ^3XWHƏ ` +[Pzި@Eb L<q>H;Q"vQF>7P "e@N౉f'Ҁkyx{}a9ۯ}NEk y.\1#%aZܵG@]Cc}8%46PmobaPYe\?hiN{殺{l)aMe9C)++[}mm-b ! V?^#<|?c֖T+IY6 scظ0^d!, m-az}Ϟ=z=z>++ܹsYYz^qՋy a-UuhJdg>U_r)v%o>m-㜝99{5{쩨܄qJSƏ{o`Ey-gb 86 lÚ>wK3RwѼ1>mAZ³'~=͗Ukrt8 ofxyaZϙv  ᣀ/ #n5u$-;Vf+Ҁf@`Y6D? 902.x^$5+c96Yɇ84if-kIdZF2&PLaRd#Gc*n-TV*)5\KIJoooeLRn)*/5伆fR;ۏ'E*5M&7=83S9 g$e(ۓ~K if2߉Iʸ&.ͼw2߉IJ?~d\!GJ 3MJ3t}1wׁMkjs,N/lO҂왲mj;Ň%x `0 ^`aOg)[r{2/p-S 4=[S[=9u Cg+2`E҅wn`eOZaţ`WӄRIK]33s.m}#ze۲/^6aYƄj3~Z dpED"JDD"QH@DuDVҡha0W}X-\B?K>ܘ]EQЯ;Y5_!^0w=rUVW߬M ^zo J3g@J9sE0Bͣ57n5|kkk72YR! کCJi/#._4:l2rGPńdtNd Z(q7\"%-nk[kulT~>O(<2"Ȁثr~a2YLftNe3J)[dtCUVwbԢiYwG (R7հo8{QISs8v<*{\×ښZXTm f>@ڜjFU{[dC/I\WhsSRw~S gZ܌TMueY$alml5rΟVV''˫O>q\yyol1[-%rsT @,uim>R*}_[Qxl?_l쵦(J׳,, _>yd8DZV[IZն=A4F;w޽+i/)*Ν;Wa$ǁT*uuu-,,bYV<.Fe٪BWWWkD6Tw_uJHeeeϟ$AžD/^bB^\\RDrWP@žD,˖988tbحeY?E IDATH4M5 Ʒ4-JRy=;Gž4MͶ(ظE~'Cž  {;$ XE[ɐ#լT'OGvJNu{ ,=AawHݱ{򋋋; ф5!JK˺; a׿,//\t1c; al/m #Cž  {;$ 'Cž  {;$ XE[ɐ#լT!GYɩnaO%'CūrsKU|w烰:|CE0~ٶU3%Kc,og#k_G@}}G VWPeV8s#;3v~+rvVeš૏lzW+@J{b*oH]?FFjKVdG*|UUHH@QmHtyw{iC1+IRMJ),'kVd5!.0Dž~:V qlm-]OKHhAYٱNmXOHaȹRm2{9{#xwe7.fo Y?i㩎xD$-e*FS Y?@,D5LN7 q@Ĥㅕba5@9dpRˬNRUu3tMޭ.ݰt7PW[?Y0 ,/cIbirgJ33V3r!БKDg m{W%.MHlxlK˃F&cw{E( V&u;sl|x'Y41XQP6/!/M\4`|v(z/DžOK%rZpB΍խleg&NO_|,8p։mbS@X܆wLM9_4so#:CQ_{yyѴ}1j sp7IWC&wi:QstxJM;\4J5~xJ-ɥbri[`4F gTz-M=Qsj;8+//HXOؓ=A{+r VV2At5+9խ" $awH!aOv=AawlƸA41}B^2Cw LΝ;Y0R'Cž  {;w/\^ݹ WwF4capxq^xaNuQgxiG}SOI# fwpQ$j@;e{켨Vn"ݺo.`}h3r7O2vL{˴8[,sO 9f?M@ёߝ<πG*?q:H xw'Qx;bKNnAУgC$r,AS𫒵h@}?}4ֿo_߱6|_x{4 NӾA_FXwԛjk, A66vls|kcCBŸpJm2- t20ioǚNo>isyTVdNy6(( (*'Ɇ=@l"͢{ ig8Pc\N2L#3(87 G3 罃P4;mo rsKp/tU@uR([;[ov.n8}0dUޞ?U NUW ٲ$]r{c7|,|cN ܬ10-(|Β!όof.Y_/}-Y-rۘ2o mrMCљj6Xk؛+웣Fԭ[iO|K/`,]VV\:+ptԱS%If.tW]?s [.ܑ MNAʷcȗg4^h_Ȇ N`ޟl?8τIp˥[wڊvKm=R7C'U|*^ͽ6 or@м3y{JPPFLZnJx}q RٍS>.غ )F^r/l]w..eξz ke'ao12NNĬ>ݷ<@(7?}k3$ EQ(03Q(ۄ(z +Ek\G7-@8)DD%(uщNF a ~g tb RRR._ h4<8nbJ$ "G Gi_9s-pzfDPPiGW~ෛʯݸq\(v?Z Z2;Ϳȶ-A(bX,qh|WhXh>TF^2S/y09c#dД\7]XiȜkn'&y[pD"]vEEEJa#-F6>_8)Q_zy޵WOw)Se &\~}d"HrѰVV6}{áe[Y. Mܽ{'NtDo~`0O?9Vr[Eަ8mT*EEEUUU, pFerTjӁ?>Vr[EiOt#''CS*!(Rq>r4l GYxzjV$sjfAPlJWxďjMΖ6xW|=z⷗1KNOjB߄`y7lo0~!"NC\ws>?Y6ɛn'Dѻv튊ׯ 1`ao9?v_Ikz:?K ,,O0'OiH$jܣD[HwZۻ[wJœtMMݻwx !%I||{ >>~~~dD GŲlYYC' 1u+dYeY4M?O=pNH(iTdn*$$0zH$rqq!c Nv=A;w@6>(;'!aOv=AawH!aOv=AawH!aOvZJ"c='U#YfG6Y,#V(NϞ=D"MV2At"qV2݇U}=bFh4ݝ4MYETuuuw =ADž  {;$ 'Cž  {;$ 'Cž  {;$ 'Cž  {;V1AJJJ `>F3lNO7DУG6, m2A<(/ٳe̘1O?D"|^WտkkkL2lذ#aO .,YdV8P)0 7nXfãbq%I z]$QfkFF+++7l/DͻH۞ h42`0<|ns)jcWӧO߽{w߾}MHO>A܇V3j(wwwyCSx]|戱#/;`8ޜ%!h4 v VlHD{ CAAA^,203 BI{^X~;!s?_, h4JFcI6dx)۷BrZT =EEERTxk0$ -Ih4 j[|ɾRO2mR^`9^rCjˡt!+< zXد_W899#=AGyxx4-T֩jZh5ZFhZV_:`k:-kcě{ɻ_6-|s' ɯ|UjtZȆ7MidUZVj4Mæ5MNVt:S%iݻFtwўzL&bH$T#P%FN 2|jLQ"jF7stvktW5WcW^OwD@ޗqVr?s+vEAЙOŔiWWWUYIDAT =AI3A(ZG%DWN~ eS<=(+1,oKX_N)F~Y$E);*$O k(+ŴXl'M4M;88d2wҞ #tzsh 7 D@ ch>"H,A+ʆd=jQ㙿d(ݩ[]7#?2ڍFsoEQA$q"HiOwjr\KΞ^fZP(Hg8N_nHL"z<5f=|{Q ipz_z4tmxsDt:]mmm &$I}}}Wޜ⨌dE8v}m/3L]Ս__B%A,EN6n:\~o"֗;pw  @*ZUvʚ1%Ε[uf(8NI$uR'8::zyyԨjBavv~IА6i,E"4M! .O?oe_HmMY+!rW8!jLX9_rttl ' qܵk233OާO'''\>5[hRU3en2qZ̀dMKeF]pT&WZnye+++SSS_|A5~=Ah4T~Ν;ӦMS(RThKwwZ'ܴW___[[֫W^.'ׯ_Ҭ(5$$;w2 cɷNBS~ǎz>$$յqggg[o Jb1c>^1W/]2 3f̘>}$kvvvll,FeϞ={eGG#G4-.7oɹpB}}aFѸ3_=x`ll3 {xP޼yʕ+뭤̧(gϞ:th~x׮];|ׯ_'aO ZV޽{W View Menu View Menu
The View Menu controls display options for the Workbench Window (Toolbar, Toolboxes, Tabs):
  • Toolbar turns on/off the toolbar above the Viewing Area. If there is a checkmark to the left the Toolbar is currently on.
  • Features Toolbox > Placement options for the Features Toolbox: Attach to Right/Detach/Hide.
  • Overlay Toolbox > Placement options for the Overlay Toolbox: Attach to Bottom/Attach to Left/Detach/Hide.
  • Enter Full Screen expands the Viewing Area to take up the entire screen. All Toolbars, Toolboxes, etc. are hidden. The Esc key exits Full Screen. The shortcut for Enter Full Screen is Command/Ctrl + F.
  • Enter Tile Tabs switches the Viewing Area from displaying one Viewing Tab at a time to a configuration that can display multiple Viewing Tabs at once. Viewing Tabs are shown in Tile Tabs left to right, and in one or more rows, in the order of their arrangement in normal viewing configuration. See Viewing Tabs to learn how to change the order of the tabs. The Esc key exits Tile TabsThe shortcut for Enter Tile Tabs is Command/Ctrl + M.
  • Tile Tabs Configuration > Custom and standard display configuration options for Tile Tabs viewing. The default is to display all of the Viewing Tabs open in that Workbench Window. Full details on these options and how to setup custom Tile Tabs views in Tile Tabs Configuration.


workbench-1.1.1/src/Resources/HelpFiles/Menus/View_Menu/View_Menu.png000066400000000000000000000774571255417355300255370ustar00rootroot00000000000000PNG  IHDRuv97iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:Rt"1 J&ph)MRHtECݥ5y* oa#L fpqlH>?w|fΥpʋE#ҸB'2g/!`L)28q=XZQw+'l_!tx40wTf 3LE]qszz!w'3J7q:vB} ^$zq7$INFC (=M0F~Ext>&g3g&G^OXqr/OR RaW $Ғn)넄eO(<8q,b1Jwuod7COj?K22o/|HD摋_Jf.Qܣz#{guO5^$[^_*C|<jH !1(T`̃g_a2qW cV4iiWn|V͚=퇯 }3?$*(^r)8LddyU|Hy1R~0*[l_B:XROƎ 3psSzz+ML_.ҊXv~4:44r*ѽ{.|^LcrP4}+}{\…TQf_|Z>)Cꫯ5k!竏xEo'_{zg<9lq:ծU )R_BsnE./5@sP^׫*=}el&iL L>weQ&i& 9F5k_vџ&|lX~ƍzexZIZ\;!KeLa8ί'͉ ;CBB$ )_'}db(wKWM3MbL&H1xHŋ<`mAݻPPfW yyDmV^GiU BIY5m0 @?a-<Ԙ夊aHlpy& ް5j6o޼v:_|1m۶-:> `nJVxҮ]n/GktB-hp\juj;͞Eѧ6Dw .IE}D;Qtq.[mUVJ85==RBDMnc|5kpG6q)iLY~00 W?:!aOiP6[V@f</=Iz+W*pc=Yȹ 'ZޱKuL2:ǧ~ K1t r:rJllklKX?O\CwBu/]d⋯L&1ۋ-UTG#x&i%*LB⅄#% .W݇^v:Xt :=v_+Wmq\LG_?$$ ǎ%-^},[6 Y-6ƍg:t 2 s:|=R~L_Xtg,M0pO/aҍ־ڻ_Gכ 1FFVAݺ;NN@6ی(JP n<|9b BdHUB)ZWLeH @Ѿ!oic(A/Z'.$IiM;1???&&ˢ$JT^xů#?KT֭;1$I6[ /i5R"VuׯUVkp۶W\-ߦ>vҥ Z̬L#Gmڸ׫}YÆ۴igXFxgW)79sN-v͖/Q*‡͟w&RbŒ}v#FfK)JDGoRڵ <`-QO |8ὫWƌ?|Ĩg4CNDž 'ORCHJ:sܛYG̟7mg}󲥋Q GgϞߤIөS?*(ȗ$]\Y_ϟ2[RIVU#GS$*Q S0j{oňY)9Gnet&|2z@2OYzHdp QaȣF`?TL ٘v Qj ]$1rwkeF:tKԪU9TJ1d+.YѣIo3@yMNvr9'~2;((X{oE&cLΝ00KxN?4:_j#ޮ]!Imr9M7Vk f]6jTWOaUQKѽ ӯ/[NXB3f׬ 70G%;v(11 :>'͙9+B佔˾?2{/]8e=zfO|i5kV1Ɔ d2*Vu\UT3L[ze8&yy!BYy&%կS* IJ߮uw q{vN]n$!(gZJ"ő, qHG Lc$9BJ ) OmmPVkhh(ƍ|ٳg\.D%0t:y,qƁl\օ 99]voبI|8aޯS!$mR¨D0jש eE @l:qDJVV&Q%^| @Q%Zñe˦] ]MG1j4I|6G*@ |lXJZ !.]P=mܢe<eʄdffRIb{䑨p-\F~^tdLD8SffDK.}a ZSb$IS(eG U%wvعTGsrU/A%3p\fyuNK5{WP .Ě4D=eBUAyR3U'BVV'?VO=r\B$J> i'WbY_뉉;UV#LyRJ)Ӟ17Ϝ9ݮ}薿1&Q1JHn7QG$נ|*0 "\HMmԨq3 fIeRq$$1JM<~9Rr J)$5͝;wx=T5LkUT;GO+JI)%NŮ7Ǽv_ WT0fۭfء׭ nڵk_MKe "qv{sj|@3T'ʕK{>ek׵;(eDﻯc?xIm{+<ٹK7CeNӃX:U!35jx۶muZ+WZPPpB}ժɿ$IT2@%K[QtZv{A5eDQD5ۓ_oW 4Ec=FJj$Q~Ҏet,wIK畑fpʂF殏p/ǚgiMHٖ$Y2{%҃HETZ%OrYgϞݾcԩ}qIT +((8{L#;v`eegJT~ut$lȆ=w6hҎlyD&gDΟ?wnI%SG&5h#QZA8tٳggΜn[JH}U>Q ^{֭dex;杈޸q}Nt|ْ?/f<}{vDDĀl~@p~`ɒ ~Ӳel]g{+hr k~a@SidFI&KMY4+_NqK|Gסs+W,P>^G/lQ&^رX]tayߙ06lܿ y'Zޕc^+G3}3f|ᄱAAVJʕ4lԘR`/;#(jx`AMJ.9' 'kD'N2}O>yjr+}*\w?gVmG<\>v?k7xcpt7.f^|RO#]<(_mAAMξYTX2e#""kJ(v]m8B2V)de !.A(([GWѦ~$(R\čY+W1-91V\(emA XVV6r"!Y2eҮ^@ʖ cr#{py\|3)!!$<ZT6VT-7&dDQk 1J%949m2CImB18ـ8 b2q.r:EQX-D%@z*69}zSCǙ83o6[<J(:NC] &2J &UE-c`5[(j0P*6cW{ A)?@ro"Gf3oyD%ʋJ TH$ ;SY[' وA lיrrp{pVDA0j{(7GtwK?-gssIO$?̭Y2K!No.G{Go}>7BZQ%?"}.O !PȎ|'w})*F ^Ycg6Lm%AQ_w5TGC^$yW\9p`.Jv@Ϟ=ʔ)Sn+{=&x27Wd󷧭襪_54muInD!:͍=v߲z0зJ~3|)&RW.HȲ{fOeEPxL=aŊ[nr.]]TRX5^SȕtjP|Ѩe'rzoYGN\GQJ:ïmxu9:lhn2ţ?ΙЧOۺ/BnZ w ӧjժ~嗕+ؿ?Zj5o޼(l~2R]2X. >87棯lnGRՠ[w @~^/&OSґzd%#hB]X?׵@TJWq <{8_vid#/R@ Σ|y-{C Ņ.{Φ]L>k@ݣg?xӏr̯+<^4" ^` iL:Cl8q@}+_z4OI\[_,pRwgO/VӣK~ǢGd-Gyto^g*sʙOD]?߽8_\3Z_4,B?)d_S(~&OQw>0JN.>xؠfb?0\̟?o߾>O<9sT2GvC#D#B>KBth˶l"x*?~ٹW})֗?B~{t@m*Zv[yCՀSGLC#"oR+wg&I'^җ2*JڑKwѐZcp[?+|` 5ҞCڝ#>R3^"ehpdNn_LGZO={J6|Lc,Fݗr:#KQd%Ѓƥ;vƴIm.nB)r yRy{qFX(>/O˜7s5 @wR(1Iů9. zb0G=lU|C=T>ިw{e:T*%_{ll@ IDATE[G񣏺`i;D;|5pħץ9/!6L2 !"/$tܩFw%=f,j,.|bo.98wcz̸wh_Zע]X,wDr]2g0ٝ.=9:.۸q㣢SgkڗmnFHVzŵhHZ8f*f[b2IvP FN]Ry]+Y?V&HNNefnϘ(\Wms‚-m?%?EV-~11X76j<.#|m)Ψm',\f9Hc̞gt[KpXXѢgEytq_ e!HTUxхKWw&A8i988,8B1 g>֨kT8.n_ŋXgl'hSzvB%Z {Mk cOns6Ϫ}~͔Ac6i90&4f(|s<:KOUx2&s 'r@ĕ$ͩS{ aKP&vsZ>XBAJ}cډ6=^.p0̻lI4]s&>:: }<7^v|廤ev;S--ݮW9/^&45+N.{W]ϐ|'Kl޼KĐ2muAȚӒ7A+P.oO0u΄\IZRrq@&-?<";7_9= ];h@<)q@|{=qˎ Bn_=4}c\%p@Y`AHȹKD=%aȐ޿%KRDw~D>@BH{NǷ$2QGiR.巭G_6^+ɀyI A/@ Y]_YW@fֹ!zV6]G|AM=*W}(` 7n n'3[׳;Uj77*f}45Y9cтme5j ˳!~1鷿w-\yO~K@)clwl@*qr$ZO6I$)P'`}Esޙm$ZKޏ^isl -H?JHt ''{u{>_A[#_q@WTbF,V8foKU"2j`|4dl("ٲeK< sF~O) :l5hΆ54˾:'.Gv[Ɣh˶Јl"xZ{>520=cV nmo0Ȗ-[ڶm[ګT z(e}=޸ ?-Q p'sk%R%p {@Wz-`1&!$o muAgʭ]R]"de2l9v񍥿c^Lhǯ}g=V܉~מ% XGA~0K8/v5F7OR%{j*zNEo它ڽ[.2yL 7رEGGwE:":GEE4|;.ru;|WH7%! :)dn`UV>=UZmig !I2bʲ U*n=@f|e "2d2G&J@Wl]a5;KݫWF (m ݾcǎCA߭ʧ9Anno3Q.naXD؟Z-:pC["8߯6Hy 2eC^j[Sk5 ib6stU7LbX,AԪ47]؅NTX`TФ廙iZ vXc%^8i:oz"; 2Ft.BCP8\-E'}fyY&:5Ho&koWH-+gZݾhEv}oh[oDegg3U1GFZ20C*?W]l}H:bieک?n[: _z A/Nh:ugʺ@,Yu7Z60*׮v؂[R#7^\nUUcӘF-xeݹzLz?]|U+n?9s3>L}8]7ywF~6cjV ?4~جbޝnLz>Q-@_lkqN=|i-ݽYKڽ04ЄgѼˌwKlñc<19GA0\JiɌ]b!j<$#}}?kp̢^j +7{6r]Lc#%[XW}n#-EؾEŭ s Dt|2sݤWǶ ]w,3gm6Kj0vQ-Cu26^04iuxBS}6ѭ;oQJ髕m}f}~׷vEٲhdKBHbO]{uЈ~et'>iuUBSX{sؖs3'2FS(kSz.=VEmڴK/4B"jضNoI]lu 망g3HOU4 9ydys=c^{EI˻jG7}a/v_O kaꂧ`n@K%N ]cu8c _su=ksuxv.=]IsmX5+c؞!@h?֙c.BJY>JZ6ט5tvb &ܯ|2Oq:ӏi/Gˈ;4|'_ 9Ee5N>ނO<-o?>Ӻ@&5"OMoIF/UG%څ2ctQ!LuNA̭Hr&/W-6lEs=s=UR,k&lC̄p~;re`![X1:qFF'ڢ/&{!Fɨo^˜ѽ:Hc- %J;=Ѐc Ejժճ˗gTsEmd2q:(=_ZW㉋ ,Kv-}UVS_`or ==,~O%Z4Ѧ\sQ/Kp%#ys-WxG@>!}nN,f`;R2^ΛӀ# #t-vlԀk/d]YvrxeydNūL)k$k6oN/}9#ett.L'`Wv}ڹ(}}U=CxA@4:Bn C ._^֮]E i~tm͈ jݙ]@.,A 3pyV2:نǶ_(C}{;Jl Q _Ḹ٪&ЩgK/բ0:}yyyC]SN(,ӯ`TW$ƘW=54pxuGiEcE܋S@#rLҧ<sju6r_SȻ'.Y,)Nz~TvU1P)Hq G@kc1Tg2t4⚽1F+3<eR ^XʃK{|QԟRiR z"`] ^[!f!@{D7=38f}19b!SFjaz9"x+2aD狡 oR+ xبlxa ^jGv{zƒX=8+'gpپݐ3@\-@jmP鋘Ǿk?UH~5X9cpd7Ks|[#*UT)22$ȴx≯c=Ck\%Ij&7O'Һ#ԑ"Z2hSWf[GӈtLe)E?3~sIf5׮YvUke'/ Kz] \ v!~cz!]}QgF8sT([ Vh8`V2 m=eC+d9NzMѳ<+) 1LtuB:Kʖ|k h(/=c_/+ 5 "JI wPPٵ?⑝#8/|TMC?ݻeMr#q {ȊLp+>P.p/b[ѧO,xw fJK! ٝFK. jkoG_/Ң^o~^Q(I'{YEecW֠Ul5-j^qkLpW04{ܕR֓xEQ&[p%w- 3 |qE䤫"*!sa6sƬ@ZVs6r~.ˢx&8uqIg&vM&gۃ(MlvWrv֐!E0uw>ieǵ ԈVN*֕ L~-8` N'F)_ }&(bϩ|ݯ c)yS;zy:J|#t6֧JWh.ٝI-bw; 9Etرí K{\GGs[NMn1yl0@y 0E[f\y.Q IDAToQ *KdchmUSk̾f?k^{`aQZPe[!rZQg$2Q Ihk[.UNh14] .vN9Ƙ$(%*{]@*Tu^CJacp1+fF`}\L P(0q྿ d"s=Ld[^HH&U WDMϲr*uŠ[' ,+UfҢ=@4E0'@6[ O4a\+ d-j8Nog?8ǓOPҹN{!7 8B8pPNn>`D1'Q+H QNG")Dч9@!GSH6|.F21<©5\5Fu6l2>eTG)aG?cOw=*r-o @n뾏+Z9_߬ׯ_[ QoI^? *x#)gA}>MȈ)9M#mBFNlӃBڝ:I9MHUnE}(F`ٖm۶m۶mڴ) \ec[ӄoKx5'OWj_jW @IIܽ}\jI@(tk>@cBO,S^zXGOTpN@|:S>u_VĽ.^G(9odI$Mߟȡ4wQ/0wҩFPWe$s'v\Qkf)\oEsϨx[ߌvw\Zp0h =7dɟӍ ,HtTܱ"3uJ"{Ttϼr ;t)q%J.Qr ʌXhE*͛6aBLcmdCBwQ}6fȐ%~+Y`٫wB%ߕWϞ$;(2oflKolǷ~ r H}™oAdſ32]vڵ$IRnyob% ^D)vPT| w<'>V$)|9Kܵ["?ȀVDQ+~LW;%Is4'͘ڿ~()фDiǎugZʣ zLEI $}Aqy-gN,u)qyI$x.n/99>jT۶m3xޔii/CRY{?HRaRxr\W6KvpP r$ITeK-; ^wAyBPA+;N}\q_eYǯlrK_1b镅;YJՎѥُ@v%U/9-Y6w)Ny0x+ˬ$FP^9mЎt[BZgb?JZEn1ylP {{y8(zE}eG{lm*Exw y@<{p6=q: ^b[?^=ڨM/,^)o]>si^k5ӉVPxG}'5ELdݶ+LVVxuYwQm}H]{ݠ AY:7+~@8E@qfM+ VL Ѥ,Q?Nio$B\oplyɎt7w{/mݕ<3$5lb0iϡf˾?`Ҁ|xFЎ04qՋ{37. -7i(U9}|I,n]v5꪿܅mᗀb@@ t{{̯'9%{g>M)Mpj`ÿݤa9o kctG{4_2.T3^/lOܾ}7oޜ^5M{…),L`WA|N0#`/L{s),L`/S*_'>7XGYFv孒0JI]NP ]yTձwaSccQQCæq YE OqAY4>20(F$}{8}gXRf֭:N:Ugrw~ˮ][)/@Ϊ] 25:y;Ѵ:,#!w'#k(RT2U[ M&6vu?ݲ5hnpPs%-(ꓦ NQ 84:ӑLv=L&PFj_d(=hOc'4"UUG: mAXdi s2O<N$x"pob:X܉F?~{ǾuI71uC4tbѯ6Om9>":0h4VZUTTd:F7{Q#e)m)TGo,'QƷՔlY#R KF fFsUI؅}vY~F9!Wf855Dkhdj#GȄ蚃 al:P/ U|10tdre҉lmJkv_B4C*vۈI*F ą[A?gD{g|*o&y*kKd\t+wv(nPcx~gD[oyA=`"*Oosq* 2df(ۥ ES #zH@\+Rq"0l`A|"ƚ s%& G%o..w! |?H@u%r+m I )L~ H""Eh.G-J,|ſC9`"* ѰpҬ5L IbBX@$&ż}`hd%Ęv|V#C%9\1 xRpS ¼DF-@N=(J+JZ򱮲D0J#] 6l4hPP~c̃oY zfF_t5]%>/E5eY0CPbQI֤ZVHDĘ_ߒ$B1$ie1oD'pHA_"n`S\NW) \mIWgK*/eWbҩ2'O@mZ$3pwߦ 'j`QČAHA d|K{DL{do@q1Ϩ@A| 6Hta'"[*鈧Cל!ɓϠVjgӂGE *z5NqL[+w f;utџFԘ2[߸T,f+a~3G&89H8 54҂~Eޝ9H}&HG =j-H|^_ TR)7qn cZ|LpH}an iĠ~2Go43V_1KL[PleezGn C|f`z[1fxu{"I,2 l $]ޔ2`p ,Vǎ- Bm.4E=;v),Rс+'-l}\V4;e?ρ%*}厼6X\?|cYbiSKV)'ʞ\XK<=wܰfŐ[{[s!bhF3ڶ9yz`Y=7{\苽?qm6ha" "C` Ny+oup墛I2Ƽ|F~u&k/ g,@']<("%#P"?71鞻j׾qM2g)1]@Qjp˩anbǂ&g vę)(85ԆcCMA#:c  rN:3dsNipWgV7RV3s@QKIvץ'1UdMLyZs1Ԯ9wm,hC% fsgfh<[򦼔54c̷k> =Y6HM]%.L%,@tq];h k#ND֪Us-3 7>8'f$urG?\Ѹ\üuT7{>@-oN/(\tU~޹MZ 7a$ O[S@Z5jkvǥ^<$>􁡝'.ywۡP|󊯪i(ѣG,E%m۶}# 7狱/^͛绵;v(}a^lP۶;=uew?S^.r[lҀ޷+V<;JF3d츻g^-Y1g4?y@+W r`8S;[C!/D͛?H$ *3~Z__?h8m[eӾ?t֖<>ĺwmٓH٨κic4};_X~fp{5]_ 'Yˏ_{~/Ny9W維8bƒ% sWco!rxΧv~jԩwy.l.a~.]#?-?؂!/,ywի}WGD@ݦͫ Xy?xKԬ^=x甉sY7ml3V|^ js}~W2)!7%Dc/mɯwz8q "dÃ{^2rCܞM8ԑ TySF$uUִ%Kd4$]ÁI*%a 7=Cee]g,7@ o? rS.ϭ[y[e19UyɺLJervϸ50jPZwϓFuQWEVT; ,i/`NjGt u{ouآ?>l+Oq ,MqÆ)7iH$L| 6߹ɞ~~y:mMU`7>!SJ YL!L HwuUG0, m!Szw V龂_2aB z pJ˽-iG<Io@L ;xbogeLJչCd VMz2x|/yUx]Z {Uw냪X8Sw`tc&Om''Xsfo2vǪ>O`ڽ]yDz>|0@2;4,Dd%OMJiDSvC7(hȟ]9U+&]tzIDATrO<5UAz}Kn@Em((ÓDjH&=Xb'*t1]C=qIYRbnFē϶9|vOͩwemqPzO]>j%Ds冁:9M[n,/I-PC93͈($ &D`tdB AD!@9҉sטG]K.\ ,9)srBS;86zS{_|N.mVB9)q~uo.w}{|4ÿ)Nk;|z~|f+1y91[ )זȶs{8DE 9qf@V\{Egun TF8S\"XDW_.#Vtr #Œ31P+Pen rꩧq%P-Y/!0h7>Uzl׮]LYظq;,-^O܄UcN={|ˁYc.g2|ܵkV~r)W= >Jvڵss .Pvţڡ>[W=c8pgi#lR֞sK^k>v9oW=<̬5GaөV(-]Ѐˁݻ_\W0}59iElgںoϗ/N `ܸ U]f(i3~0ڙr2JU'H\'UcJ#n1zVRZV Ң~%%% Tslr&D'o>Tͺsu--Iƣ_Oi͇N?:?!~?BI7%{)^w-ܗvݟ>5h2HPH&Fk?xٖxf}6T  iX<88 t7_ٵ}]ٚ?{jVNʹn2b=HTl Y?:h4k(o_rϽb}E/ GbX402q)t:۫Dp㮵8q2cFiL^Hd:{[wb '&\uH4^D":'Ɲx2ww;zfWVUR*ňbxM0ai:JwDr|";˝2jj++D 1 G©íZRc'`)TByx%KޛL5w)jf:2;SG yTZG=`鉨*-Ls˴L<"0䷲ʻ&^H ^TկI *S~C)K_a=1v9]K^{z~w4z 9Gfl 0lviO6TPsD# snDTHT q@fZS'M}0+4M>lS9{ E1K6<1:a-^͵fH`QU"|uBn#Vip4^o'H5i4l>d񹛛:)%9;j0b\S׀S~I#:7ә}FAe܂\66mP*}`,zrOmVm!KzFZҁi }8A,oφF,-cOxY^+y@%0HA "=~M˻et l!Ԙaina?f;{ :]Y @ <"f3<@ЀPZ@ {i + (pA"HbցM `/(ap @38 .: #A8Q!%Hҁ K 9C? EAB:h TCeP%T BP?tƠ'a f0f~p(ë8Uq _o<(@(wT *6 P%*T= ՍQX4@>04].CC7;7C W 18`ؘH ǔ`1.mcvXl6 [=mcI39q\:.ww7}xK>/oƗkg) A@$ل#6 a(K#:CIMRb=D"iIKIR.tt4DH#1 vQr; BѥR)%c)['Q\Ij@4AZG%B:GD q GfLL̠̤,UB6P6UHVNNWS''wX0EբS-#. KӣiIB Z/mB^NZ>\~|9y Eץ)S;O ,xm  )*+(6(VPTJVڥԬHlT9Kr""EEN-U9ң2*RݯzIu\檖Gژ:UY]GsSza54iZ Z{:&յhӮӾCa$yUYwTOQWPZJրilp61L4,7a c퍅Uƃ&dIIɐ)tiK3mh]ff_mS̏?lfВkYnyˊbeѪꕵuA6T%6[m:lيmmb*4fycfG[tS88&;:.[a'M'SęEÅRU˕ZeJbgt3w5wwp_( ,|^3Ves5 _;~d2'b%%< 4@vGAzA~Y]|`u!Ԑ!!BBw> Q!4\y=J9J\lﲑ;Y~uVJ< Tq&qq\w> +oo,)8YSBq(߉?X8.p ^%$Jz|4y:%"!*& ;WZ_d$IV;޻zB'NҖӐ`ӓ]Psfy懬kddfo~ZZڎu6ZZ_cƼ#޹67%ouoDliS||qVǭG/wն۾ ~.]'laNN;\v+-)޽dwƞ=o{ĺ>⾌}RҖw\XvܭBb[]R=TxӏVzW6UVrXHV.n|槿+(B3y pHYs   IDATxy|Eǟ{L&p B /fCT\Ɋ"(x!p r(G9 ! LvL&_çLUݿ~*m6f Ғj?,dYn,Ρ(eYcrZ;3*dYE|? Ƹc,UU}ʼn| 4/IJ<ư0Aܝ(nl !(ѫmNn+,;cG$Ib67qt\.י}?A hkl%%ev=mlF|>eنK zNj4v=...**eY;gfHoԅ  m#bUuYeYsgk譈,˲,3 av<ϙ}['i8|x;I:m=]$P4^… mڴL&`֖WTTRծ];Hޯ 4׶_6:Åʯey@+xŒܳgk=;}]5MC{VVVVVt:lһwvo?XkVYiVGK[wsȲci?͊Ijᨨh׮bh4^/@2xMEEh k+=N&N3vrjc÷7Ķ,Mmƌ6o?yd~~~KY^^.r۶m l6;vj>hРw 04M7=P[[`Kn_`!Ct:];n`$Ijh=k K9>a]]"; BqrYXz?c^UUe6۴iC$%Q@6mڐd?pt\pxT 6E7S51֤hNTW㝖+XqqqNNζm iY̬g(ŋ9rdΜ9n78U4Gʺ,M!pNt6 PN9 1E ./)X,J/!D!(3 c0*++^.aicxeT%ӜdEQ&" ͛7qvպhѢÇ>=zXvÇ={)S_I\[[K6DQ(__ɓZ{Uԃ1޹sʕ+Ok=˖-۷o6mɲm]]]ff3֭[wر}N>ڹsgRR~뭷.^ح[3gLNNBɁX#8Ψ:>$30cs&o2FjkB ЗP -6D\ᑦѨ:3j 4:NoFCxId]w'X,|…ϯQsεX,ĉ.]*qz1ru?7"& q>7|3u˗\.lϞ= .|6mt`ܸqobi­(ŋ/~WZO<911?x֭[eY.++1cԩSn*ƍ89KຕyMhhA@ h4t"[rB9aWwZFhYhYbGݢ%MTF$=#)))g駟f͚խ[ .:t(##ѣgϞ #&44(ҥ*,,ԩl6ϝ;n6,;;=z1p:7oܷo_b$)[_|111QȻs)))Ν$i˖-l6;v,--mΝ_;9Kຕ-+cL^0^Buu;|n&4M!1&g5i' #Ьy"[y@(Ԯ1VkJJʗ_~K/͜9?.//իW{ZZZiidRsTܹsٳgIT,VUU㉍ϯIEڵ8qe:u\cnaI:x#i,XEqiEkNTUQ.C)BBPH!p:A4Su\%,˺\.r#\.em|2 OZӲfF4@>U-iWy8qZyٕKD$"(p6JKK BCCFܹsf^{m.^HNNw5iҤ'|20e&</^jСC o,Xc~WTT?>--ި 222##cÆ =-qo7ߜ={vVV^aҥ} ,F\:y2"6l ]*6B {4M_Ө,9:-EQ,+҄śv'=:8O?4>cY6$$DדdX0C0 HU$rH \UnfwJ;*ݥyHI^>Ixx[Y܄ DOE%h 2h4fJQ5lTͩUs*F՜JQ5lTͩUs*F՜JQ5lTͩUs*F՜JQ5lTͩUs*F՜JQ5lTͩUs*F՜JQ5lKs7o޵k+((,85k֜?^x<5kg˃p:-[vNKs-z ҥK{ݹg}vʔ)}Y}ίȼyBBBZ38//L[Rw\u:޺=-4W\`ӦM999cǎm ^7===??ҷӧOM333W\pz5O۷>g/^855511qPRRҧOjp Xz5IuW_}~?~|jjjj{>`BB”)S^i:wQVVFٳn Ӷޛ0aBc>,##c۶m ֭G}TXXxw'&&* H޽{ѱF"=z4<#{ׯR-x!Cg?=Y/`ٲerK\\ȑ#:0${'rVycƌرcZZblr?{7p7v u׫ԩSΞ= {k֬裏M6X 22m۶ vq̙dY^n2?ӦMygY4iҁf̘1gRኢxiӦM4/|8==S%7\nȯ 6md4;9?x>}F=x`ݺuk"7:}O6߸z&׮]oC`͙{x<{ رcC,˒76loOKK2dÿ?aRRRbbbF=y丸={?~>㈈>}رKJJ >>R`Xrebbanŋ @d:qm…C0`Iu D7nƍ Vz^UV 2dĈ&矗e>kxt:{wj4+20[n-..!3RLxbȜ,j[o5bvRS)N Oe˖?߷o_vv=Ә=zdffojjb,**zWbbbk!ez %%%6gTkI d< 4QY,114~`۷Ç׮]y ?>##l,Lj=BBB+;3O̜9s&W<925[ӫ5͍YHW^O<ĬYÉ%***--7O^WWbŊ!Cl߾믿8‹/=sLw}߾}N3:eI_~IDEEQUXXH Xfp e- Qr0 nݺǏs=o۶eY>y#>tcEڳgϤI}+BaalnؘYFRL81++k޽de[ou#G^`0$&&6%KٳG'$$xޯ V"\d ͸acHvmgΜiWw}7n5jMEFF*iƍwСk6X^1cl۶@ 68λyE޻w(Сqqq>s47Ls ,YD\&NSOIII.6fup8+:۷/YD /,\K.G뮻N?yDEE_~iiiFjfԩ+V R\%'O޽{=|>߼y!Oϛ7/==IHHׯ_\\\>}z>33szzzn^}e˖%$$4O=Tbbb^,HCرp|'_#5kVdwK܈4s^z\mwKV'e]KCޞe/BwUPn@{[kG6t֫ʿ Yif ?>"ONT8\ϥo|A}lTͩUs*F՜JQ5lTͩUs*F՜JQ5lTͩUs*F՜JQ5lTͩk~ge(XI"3DTnr0,t`k\׬9-LZZ/U I&kVmNhr-V GxJ/Pv]Ճ8Ti.D/P:p-xi pRS74ϴpci(a:w,X]hUTTݨUs*F՜J1k̩\ʸz -}=(rdM9Q;{麺:Y]Jډ!hҬhMIIINNnlkwXbZM7/bd:owkz߀$_~/t ޽N z|v_~ٱcj5jTJJK%ͷ7a6k /rKjWaAE #sԜ$I|gnOHe-rw6{ȳP!r9tYRlie Ƙ$G['=ҍZMs,ggg;v駟X,E!7_{20`KTVVtԑ#G^kC" b17I5K2d2>}qX ]]N{c{zYocYB×1<@VVY#(v`$~"Fꭑpi/D"c0[Gf큄]k^u4t:n:`Ţ]@^^Y9,,cǎrKNZ(o:t8x`RR@2]^)@^X.y~lڲDO㶑%wo6*c~ S:'[ɝ""tt=fg[a|N弼<^W$~򒒒h裏-[[ YX &IdY|Ǐ_K$xSDŕ.-aQQIƒ}Em% TOxDz/`11/43?gd{XK~}ҥ$Hr+OƕN%f.toN$I@+9ǓCx<,soo11ر^OkcϧCAij(dyt1Ev|>y>uTrrFiԲ,\k";|aJ8LKi8| $|4ߘ]|k!0,ɗxdpɊ t>"@4a ŕv΃"Bm;9k u9UWW0 M$lBR/^xw:Zzsssz&Mwyג%Ky睜A-B"#̛7رc,ˎ?^[?";2{""Wµ.|0/=Ȝ>Z?,54)$dCS2@Ht:.~EZnx$$"K ҈"W:uT=(t[q'Nܳg?x)Dɒ|̙zjĈ|?ݷQ*X6vXټy={Zѧ|矯^L! hiJEx b`0t:sRcn7gic)Xb(()DM3 0|hQ /o(9%Kbrߡn@YqAa]謤ރ4P_,X,ӔV'wl`IDAT;Αh4,K%!WUUe6Fݻ:SN/ȑ# %%%77w̘1YYYc޼y۷~azz$IEuڕt%W˲L,zQ1W|>p!B4htn#G+m١37F)!Uq4:{leIaߦj.G_HH,(Y5:ֿsBJ>i /[͞=;//E+sd8b/--P\\lO8Ala1e(${clXxgKqT ݥ.+NswGuث.`td zGڀo#- eQݒdx1n]+hit:~T(B[nٴiٳ5M#߿rEFFdzpOtttjj/ؕ]W4.j^5rمPM Po5D莝C:ldMVߩ[/B4BEO5gȸF0H$$BO#_6Es!</V̚5GZ^PPe;w,IW_}v^RLtFFzMM(EEE4MO4)++k߾}$eUUH3!Q].qWXB yXcKS<q,>x׏O ܄i?C(oggc>{uC? JXc(EuQ>b9\]hРj2y   !"#$%&'Root Entry37256_d3ccc1bd753b81c1**256_fcd4ccf34515e49d*lv|R*sL׫JFIFC  !"$"$C" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?].|m,FFa"`qk:eζws̻ 3(@琎1M;O#OGXt??HhuM(pt#/s_U(b"k^_Х]FҦdO[7ڛ{}iUec+'sbGbCۼq'uzԃ/hfh]tb xʧ؀F(KZh&tY2H|#myk?n!|5m7|gYt(%TC8n1GlKg9j>(p<'d?Wo0&sppnTcf¬e;XUcQR{V![R+ʆQpSʑr"Lz~mtukYژL-ռ T8 Jqˋtút$*=+`S x-K/*,I$W{pZ$8| wP֞Ko=<1RVRAdzZ_?W-g[ҬI7ij33),ĒI$M[G?/ jQ@O hG?/ jQ@O hG?/ jQ@O hG?/ jQ@O hG?/ jQ@O hG?/ jQ@O hG?/ jQ@O hG?/ jQ@O hG?/ jQ@O hG?/ jQ@O hG?/ jQ@O hG?/港^#tU[2񪣏V)d0FsڻzݝiڌWKkb-ʳg2:(F@kokijwo4CP)C(' UZ f1kh2F .!O!O<^C&KQ$oR~F*洮Aj&[+iQIތ-_j2=h(((((UYT̖q "Ng G'kQEzr*ׅ76-@' C8@I9#w<KMg__M,>nm2c;+;g᛽Jú}fjۤ6~hȐn3N5m>6UE0H6K1CК@ X^2𽇊ӭ5[kKx%Hav0vpr23&+FNh(((((+^5 na5edDr`J a${ti6:e`&dyP`2XMtz}ek+8a" 8bHJ_xI<$k5N =2kO"a ʤT.$G4K7l6uiaI6|_(d~k4y ?5|1]'Z4O 7Y"raKo<0#vA'}6Yhk{uA^PϹ'rm_͓xI/._ xv]:x\[뵤($۸ps7tK߻ۭE96˴nɑvҀ:6O'G'_#Sw+Vk[d(7 L" e۸F$iܬa8MծC(7 q22y ?5dӴMKkh HdyxK|ې@4{yY1ED#JebT #Pg<$h_xI7h+YE[V0/$i}W5|Ro"EmqK4p@  (d~k4y ?5󟉟|jv۵Z HdX KZypĬc|(0A d~k4y<$k>9R>fOKCu'аVpذ\JQK+xRP,L쑡9ps$q6qz'_͓xIVPբV0o$[hV/{} p*hm']SLY+ I3}-Kcfs@<$h_xI gg·~jẀ=yeH0J:֍k[HnS@dN?ލP6O'G'_(?焟G'L+5%%dw_[rXlcڬOxڭĻIYX3=+h[h6Zm _ H=Jѷ@~ ƛgk,&X'X7AF t#ִn|kB#j+n->F]QIr{x^(֗)EE:_2ӒP`#'vڮ|UkYGCMˀ3A{M2+5aUlzڿIq `6; ne65gL;h z~WYCwVoikvj~֖ڣDundsHxS7QmRadžy Ds9 9`îG~Ƣ^7h'a8w ݏ]xH.Ե35Rr|Gˍ@ی}NeM*_0I kh`6󜓞O5_?kZ&PۤF&BtQ*]nfOZ֙dmXmFRK5;>4p|1cᷞKYZ!f@m*~S4}Ο&fdH7rCgWa1\_hY,l0κ-;_Zx'^O$Z&ngHVs17~+ђK5h/'I6)(+O O-i"oq]Lb*jnڌ Ǯs\)^KqZ[mY43[qx|w QEY|-u/Rv7vo..r:8SY:g4ZV Jl-uYB7ȐEPOh?ORMg'<o& {#fcFRcl+@zKES݈$esȽw]#z͝Ƈ%{zdg?dlgNm_ wzGwo[Gmv;F8#iVv!{1gR&6cnnYwجX+P7g999 MmE}:Hi |;s^ ΗmC{Yhnvo` $ $+)$z?ç [[ۗX. ;R1Q/IVz>LdNXH'|Y+/ ;]^&r0@ć U<;%mSKM<2UI썰 d(Sx;W^46G Bk,ʋ뗊!4x_ӮmzHmY\rr$lP.q[6v?4CL#ՙffI$斞":e֠S<^<~lѲpr-t۽-灼] ~mk]Eu$u)9Ǘ&=} XPvcx%jo!Aᇇ\ԞeGo&|lRS#>/5 n2R3w1#YsъFpG9whOT7Q^_ڣ2?1U63Bhڳ˫̶rO6qmXEmͬ]8us uv,%w&%94IjX*3bD Ja5_څ]1e浍zEl( `1ҦҴ [7Loc@Qd|gsOxvƏkNjG 0B.ᗕ1'CmṈk&6Jm` |#`5藺/txt{I`Ǔi%m xmB08'{MY#Y$roJt}gxQmC4i6{PS&G|C֓> g,j6q',3o `NxGZ<9;/дY_P^M>|`uADMhA,Q$mkW>cR1GnZT_鍦Xmoas嘶KGn#ɭ]k6I_y.%,K.$>zC,q%k**@\Үey-HDV-}=T^\t^YzfgrV~o:B(L38 ޤdWB:<B:РQL} <BE3·z(} }:У·z(S<B:РL} <B8ߊ>4qӆE,K0 lls]}ڬ00T#8a؊le,M*ۼbvg5("p$Bu=YH[H¹G|H<⴫Ś+]5دnDr&޼}C 5^FS(d`H*At˸(Hd'|3Ϟ#@֒DmG4W.Xn\6q9ưELa.yo7lu41/żw5Vlh$YJP8ܱby|GumMqMYgI\|KW\l]& ^L1;pǥi_ KMJMJH$#*eH=y۰7=zP F,-F#*9 % l sE2ycV/ߴ.y^{^|1Ѹ4FU[o]80\Coy#!V%ʷZiKx5 }%d<ľNs\tQEQEQEQEQEQEQEQEQEQEs^65%-t-3n!AfO8X۸|z|3,$Z,n R![vA*pLƏ1焟yE׉ptmr-^IJjgqc$^ c9_b&=9')rK:Lc*V78Qyק<$hxI&Sա)toi-m,F2dCópqg }Bmr-ߝܐF=FXeA5{'GOƀ$xI?_ (~k4y<$hJ*?1焟c ?5'GOƀ$xI?_ (~k4y<$hJ*?1焟c ?5'GOƀ$xI?_ (~k4y<$hJ*?1焟c ?5'GOƀ$xI$l~+4%qR@ g{(][,'~%8>Gjv\^ gxcYxs&61;mv6zO؄pDveicv率aNs[hQd)K;H.f%\o(׊|kh|U>vٱ_7,DbLݖ+zVNm^LM٬/԰%Or) +;Z=t}J}M$H%3Pp[x3Vsmisi0ee n)0֧_ipjZEŜfHhx)'$6085(V5S5ۼk? 1n=+V\%̐=S8ξj^xs_|4c<>jG.$ij]T]̈́Qr>=t}O_6nJ,mc#.g.%z8Z;KX UHlVIOB2C'V [mxMch eEd9'vB(dj [Jd_6)&WS nw'hGP@,ךYqZZ lY`e*+EJͤn0!X"|HUwu!FIil@QMFs֓s@@QQ<)< 0@l@QLmێ( ޔ)sޚj}bZ͊}J75IE03n'>u ]FC@W<=4E1w)=Dtjx[ھ&-s-iO $#\ڝxSXozƃ<ױwqmMMŒE3I8}Кt}y>xLy"n4[ɩ.}zaޟmX]ެKc>K]YӠv+֖>xퟁ|ezu 7T}oep耐I#j] ܦMi.g{h4l( xu랔V('vڍmKЬv㷹یH 1ZP~})? FP:Q(3P,Rnˏ>Pn{QrFzucieYKUz; 8>Cw(LZ*!%MHH)M&Z MFFqޣ)(\ 挊dֳ;Mnn-m-ż!r: ljZX]OЊF{Ѽ]چgqOwhmnmA죶l̗ $[`yl>KkxbxKk2v0 1[@FM=5Uޣiѯk3>-K9]'*s9'5b*uoXȯtI#eUU: $xoM=4}oѮQtGۦ}kj:M=4n{?kQs_(?ERw :Z;?`ylv2}zOr9E.Ǐ'ݼ!( 'GTSm$OKQ?GRԿ*WQ@ Kr[suj8#M/Ŭįqbԓ&rO^EG75dxG?90!+qZsڽ>t|+kmJ1fIn?{ JPzUtx[T#(L 7u6Rdi_D,>i(ƛ0A2]uL|+NG`</_*}6ݧOOfxfEuM1L (<]BVzУUb.thZykvY!)$*['p=IiōvL8AR$\+ Т(.;>k͚oLH*r:㚽b]OwxcbTL0{oFZТ(sYhnyR$+ct*X\^(O˦--})<ǘeW#{>f*_>.PP[b\P( ( *eY=u eC+U 2O }MCkYk7B[5I"qѸd_U$2P( ( (<⿄#;Z]6~]‡Sv@pɹ}ך^i__ni7iZr;TFG 3#0FNxd+J(n}D~x-u-N:(E_)*D S-@|ޱE-/GSh!xSyX\uz~5f|G%aiW:$dEnG:W-dÍ_ 7wq_Oy.$0FTğ,Pȯh L>E-{kG}o*U$  w 5[^4nghڝr@:\3VUڭ^ExizM=S%`lŪrKf\éˆ'|Mi':_"Whs¥/rYAfHze5GdfEbr3N t j pX[jzVZ\^cr̆V, % [4sf_#|9z'5MKee\|#{<V5n:3x;N=nL#?vFo<~ I.Ӵ˫ՕHVMHdxbRdCl'$/gs_M홴 Io,b6x^lQ#m d >MHQT n8'׀(QEQEQEq2^xY-/ Ek?!C@}*O쿈_Mi|1߈?ߋfUtO#%R.Ah{.Ah{.Ah{.Ah{.Ah{.Ah{.Ah{.Ah{oXkHVqTEUd3@#7;xY=e8'-Nhm+nmqm$YLG"8F'i_[F)u=Ǭ]ZDcGGDUBۑK8? 4q'e/'݅DBW!K$t=EyxvucUYun%t7@ n-ʖڄyix~ı#"+ C=E,iO}*|J{n CRx{VXБ<# r7C {{ ]n =cX<]=2He@-s~)WhLpK\'Zb|x'gI^Sw ]0gA~e]}֫ilLmyPtb[oj"xli_cDCn O_G?2յ]|@^I#UQM2(e[Ⱦ"[66̫`P>:^o;Zo;I;"T `RҸ546tD+ 0͎ygJ1O%{8 Sʱ8>ӃNz͏#כ'uQD`\@7sՑ'-]VFJ_r#9mKͧ 3uàȯltQ?qK}m|=nhg3<񚿧ᖟ??b<=rgʗD?*U$ TgtQ?axLO'@}Z}Kx}>/˫8ԥIu')?>Oz-xI4_P:2zƊNP4^yd8hU,}5k)tٗEe?֡7] \?*{?uOz͋Xj˨Z.$BGC =#OXtͪh ws#5\?*<~TSO8?iiOK7vo<8Yg<ˏyG)yS›&;47 t{YTӴVoo1l!@>NG<"JX\Sҧ.dQEsAEqF(((((((((((((M6h$wW_.W gb zo"8wPi+{i_WPworkbench-1.1.1/src/Resources/HelpFiles/Menus/Window_Menu/Window_Menu.html000066400000000000000000000071571255417355300265770ustar00rootroot00000000000000 Window Menu Window Menu
The Window Menu contains options for viewing Viewing Tabs and Workbench Windows.
  • Next Tab switches to the Viewing Tab to the right of the currently displayed tab.  The shortcut for Next Tab is Command/Ctrl + right arrow.
  • Previous Tab switches to the Viewing Tab to the left of the currently displayed tab.  The shortcut for Previous Tab is Command/Ctrl + left arrow.
  • Rename Selected Tab... allows the user to rename the Viewing Tab with any name or descriptor they choose.


  • Move All Tabs in Current Window to New Windows separates all of the Viewing Tabs out into individual Workbench Windows.
  • Move All Tabs From All Windows Into Selected Window takes all individual Workbench Windows and turns them into Viewing Tabs within a single Window.
  • Move Selected Tab to Window > moves the active Viewing Tab into a new Window (if there are no other Windows open) or into another existing Window.
  • Information Window opens the Information Window.  When the user clicks on a surface or volume in the viewing area, this is where the information for that location is displayed.
  • Scenes... opens the Scenes Window.  When there is a check mark in front of this option (as shown below), it means the Scene box is open.  Click this option again and it will close the Scenes Window.
  • Bring All To Front pulls all of the Workbench elements (Windows, Toolboxes, etc.) that are opened, out in front of all other displays (Terminal windows, browsers, applications, etc.).
  • Tile Windows organizes all open Workbench Windows into columns and rows.
 
 
workbench-1.1.1/src/Resources/HelpFiles/Menus/Window_Menu/Window_Menu.png000066400000000000000000001025161255417355300264120ustar00rootroot00000000000000PNG  IHDRiu ]iCCPICC ProfileHPY_Drr$!$* a &DW`EeA5 X0 z.DE8p궮wurIHbr\i!a4 ,0b0S8v>>/~Ro%ȊJa Ja&!|)pS[R9r" DtcVGp?'3H<ħ3c>yج86z[3c,9k%%m^ "'zFd0bx]wK$22JJL[C)2;kۈ#5b82'g͏b2;{~kS>'iO_o_ 2M`2Nm$a(HT%ZRbD")I8Rtt4NH"k4~r-|BPl)aT~J=* OW7oǿ?mNr19A``` i!K(GU *Hu2{'Ia0]8^8_𬈐HVr"G'_o3?}~}TM  v;BtCv mÅՄͭwZxdqxn [7(1qME`"#">3ՌHzdE,Ӂye*fDYEEME[EEOX-}W:52~>;6a1189 b'{6n޺y-'rݹ5)PʆTa$}6n^aKЖs[ghd˘tizs[vQ{⮜]Y.Yuv]nO\k[=;k+f^~If(aqBF+,,8yVW7JJ*zU:zزr }Xdžo̯c܏\ZUKN`Oxv2dOf?H|ezM ƴƙSO;nonjm?Τysϣgv3;t^|E %jhmm㵇uutwZvK E.^"^ʹx9\啘+ݛ^ zǷg׽5kW._~ōf7nj7oזۦ; ]rεFFFGMOAY0 <.y"7ߚy&O>`N=ϓ9(J /8 >_|ܿ ګ?2;zM[u=y~a>ćf>Zί_-&-.r\r@!GGJ( eA+~_Jf^ 5e$FV"phC^6$,.E!N/c m_j>J_0e(厁 # pHYs   IDATxw\H'.H/*ROQ=E"(łvDA1300|cX2f6/RLL߬1&+DnVW?;y3| ldq*vkBNE7e>??(eGqqPnr&Lh\200|'XB/30iB QV15!'q~ ~'2hY=o ؀8 ! A}XtFâ8?"p LI$I$Yg(1| ? ߳ׄizGv2B& PcC49ZF2^8ylH4|V-}<*ffU01|P?//z2}0o8<2C}{ٹ$`K*fqwԿ6m:uA&Mߡq\f sttܶm[~~_Pg -YCdY9i{% !F]:Y$5Eƣt4B-MqB Ud;.TLP| 88h1K^2~96vp]l_PK;!7@FeGdee/(B*͛/_ws皙 8ÇY NZ^xwB"M:G/9dWq&Ò$ I:VSAl/$1fL ~@Gm~=En^ 'H }ezGEF `G>z.b<>a&?Z*xdC7773$y<ށ/_ngg ȩS.\0`UTTN:ժU#F{޽{uT6CˠY2^,`<{E8h"Lð PO_In5@D(B|࿸KT62^XJ A#D8Sj"IXj^͓:{|Ν\ݻfdzN:u۷o(HH4000L @l100 d$B\zr? 5AS/LKyI$ ď$p(.j^, TqV U\k +W5vPvYrCAãK.0jjjⳁ<Y``Q㓵l#Ҟ8*@GrEQU]$IּB?$ bF>|&J Ny' b b$AjߎU^AFVW-ML5r\~6]TT!}-Xd]8==:CˀEȜ#5%%>W$ @)^$I īy#Ɔa #ظՠ؍8a ̾u[?K'TV <|g +/uuA+Ca6eʔ0kkk>nݺ޽pBFGE>22qkeBay%;߹s ¥KX 77;vlll~2=؊oT2 ANklo9+㪂 <ޗfY?6 (((CPxp+)))**U |Pė-"Pd=&٣Nnapmmj`UV$Iҽ100|X2~T{u‚3k ?(,$VPb[P20@ c"MSͽ,20ЍW]]3hH-; >jOId```!`a 1Xw 5X?&e  Zi?B}u/Zْa1ۃ5K~6̝;7%%;f#d"aڶmgѣG, ~ڄVoŨJJJڳgOfffS偁YILLJd$77711iSjI!O~]JJJV\Y2X~{̙R̙3Suuug``hxxx̝;t qpp;w.]#k JAAСCSNիW}v---gggwCCCCCCx^Ϟ=CK ---77ٳg999666௿JII122xQ=drI'qAE֭[ϙ3G-ׯ_޽Oqq1qÆ Gxzzr8 6XYY#Gٳ޽{H\hixxq200|3u'O|ĉO>mٲe˖->}:qɓ|9"^r>--तGOKK1cFLLLaaܹsUVvիcV^hJ.],X믿RAO6rԄ8 @@CbbԩSGui>Ev```h9,^СC[&bҤI MVTT 533~D _#_[rS/_NXZZ,hZg϶9sŋgaagcbb'#lUUӧO^yyyNRRRRVV~fgddpܮ]Λ7@F Kttt۶me&YZjO,^`ٲeB0%% pԩ7o| sIKKk׮˗[fϞ=Ν;Xӧ/Y֭[Tlmm矓'OR/200:uڱcGoo+WBǕ+Wz{{wر_~:ujdBBWSSSSS3A ,++STTlժPTTaX^^^}#d``iii)++S.UUUM_A7G>w6ha``PVfV1d ?[+(`L f‹?cs0?G 4 DjoBRo‚hC>B7WWW֭ہR{t\pEQ>߀I0=-BQ0 ÚCž 7*E"gLC;H޽{߿ݺu͘1#==yܹ… )$9gΜZ\^$fFHו0'Pm20"=/?X]]RISZZZn޼|ӑ'o섉 >tEiѯ%4=нc-̧ћZ~A 潫Hs8To"LǎB믿^tiΜ9:::zukkk "$ORݟT[[[.{m+++@/ݻw^CC2$$Jtڴi08 ++hxjjj&&&u>T700*I-222233;yh@4oM e;CILTvdQ;dxsWCK?tPjPH=vƆ$ɧO._8p@KKÇP͛ 0Ç+))q8O'111~~~+Wܵk|gӧP~Y L>}O<ٵk&bFJ䩃*111 ,p8Գ\ZZuVb/1B ѭ H +(m/%ѧ "V2^ jxה-, )1zP(,,,laԩS(aZ@  l6Ǐl6;**J TVVjii㈈@ee@ ׇi 6mcri rrrTQQQYv#3)** ƀaҥK%a؉'%X'"EEE:*Sݧ "{ Hˏ%*x-dOae "-=j ?4uJGFF_n%%%-I\ƕ˻r励ܹs By4hzqYY;jɓt钢fFF=upر?kNZ F=v͛7Y@j GCfxVRRP___;VҦ(w7z_LT"4,_NzlKTgB%)Ty]\\ &TX>nccs5"BԩS) AO^QQq1???|*u j|ȑ#k֭֬۵kفʳRRҔ)SFyAy<)6PRJFMd A~E7C=4 :$0GXdIhhDI&mٲzԼ~z߾}MMM+oOFw:R2!!0zX//ӧ,_@(VUU!:t+VX[[m7D z7f̘mRRRVVgϞ $Y^^. 5"ĜC/_ghhh^^^UUQ R @8eٝ>"mH8Rd_)*;ÏN}۷o?rȆUo'i7_AߡCmmcR{~x & #1p;w.^M6 Xf ﯪjffjժ3gP)̟?_GGcǎ'OJ<666*&&&}LL R;300طo_xx!="\7 ''XzIf#-0K !ȷI3:[/"$nKZ`[`d200` uEԘQCK1=>$I~95-Ly̙۷ogϞ]TTgϞ-n )P\$|||P,-c}޼y92T 6]vz,ݻWNÇ={p8wcccTPA"YYY{a 5` _mvN7'&%egg1+=z#SSӄ(<_ٹQEEEi||yΝ;/J%F= _M$Ys$[[ۖiFUk/۷kii5֭<(,,tĤ20g<>!AjGXd~~U\\$&&ʪOWW7 >ׯ_O6k׮3gΤ>]x9snݺegggdd" g͚k +V_йs{Q.*xgggMMM==ӧ<==9Ά \"#l~~>|(獠b ֭[{{{'%%/xzzjkk;;;SPG5S{$4 (c@@jBvm8"..ǧp222j``cǎ7n=z& ;~xTTtɹqƊ+,X>$Ƀ>{cǎܹ388ŋ⥘9sfZZZ>}>i7̔xTcǎ)((TTTH?4dr aWP%%>}<[nf&$H>fff+WLLL$I2;;;++KvZjjj%%%GIMMϗ$ ?yduuuYYYjjM6A)O>=z`ذafffGbb"@bVYp!_|yeeǏmllbbbAYYyɒ%/^8{.y!'???!!A|a>}۷K.8]Xhť``IޔD@Țd[Hd۶mmݾ  IDATul¥KqttҲ2XbŃڴiiӦs_5kL8qҤIm۶ׇ=Gg`````p`=;r *͏mXXXxx/k.Μ9iiiڵ|QQQ6mڰa3~k߾=\D8ciiYYY z~޿?##gff9SYR"$kk!ŤG@P8Nee%Tosxqqq۶m?nP(544d$KKK_zզM:TTTJC$srrtuuAP"x97Ǐuttlݺ5ϯreeegFW[=e$ Z#(a! 1000|3d}#Ŝ_lXE Z/6e```h >"VOlh@ި^]M^)d``PMQjfeTK6}=p+Tέ ߋF!c -F:3,U0Ǝ]־5v;`!"Bz̜EQ R*ˈg3ܿ/7HI>O)9Qo՞M)HRJ -:7`P jj c~: s?FCKAB4F; f %e```4&ŴgyR{|p|bJA$3000j^gz{bA7gH}hLܹsb"M7/^9gժUn޹aI)`uVczz:a΍_6EEEZq߻w˗/ŏ):vI]Fr???˖-633QUU544LOOoXe˖Q;ߏaXZZ)uIu&ȝ>}Z ܹsn)?/^LNNNNNΝ{i//Ç7Ν;CBB9Ǐ'Mf#ٰaCllcffOiRRݥKΝ;/\ðoaiH+HXnfylarrrNNٳg @8qޞ SN+`nzٳgO%9;vl@@@ܽ{ƍwSRRBbڵ7cO>wGڐ3)) 0ꉽsN~]vݼysKxbN:$qUd'bH1D{zz*((NIiiip yfsss---gggtΝ;D$ٿ7n󽼼455߿/-/_LJJڴiUXXXG-.\9rA^Yhɓ')+Wxxxdggǹdj`x|}'':lٲEb687~xWMzjxL͛7鯿zҥ~MGG'77֙nnn...ʕ+ ֯_/Iyzzr8 6X[[_z --]TVV9l0:ܹs:<.,, (,, tRCCN:޽ЧOCCI&: =|͛7d777J/)֭[ϙ3G7wﮡS\\ 3f U7oZ[[?|_^䦷j|ĉ۵kg``o>iC 'ё2dt#IsڪU+*uO2%::o߾َIIIw܁~ݻ SYY)1'Nر=zٳG-߿wuu}^^^N]ںukiim D L>}?޵kx999޽gΜ7iҤe˖}V<ȫWw.m`GfddQ˗/p8p8&Mrwwۖ^:22رc;wիGҫW/888'6%%tРAUUU婩<644ܱcGtt4u:rʨ48z*44t׮]<_|Ǐ?|ܹs0HdddD*^Sbkii'%%=zTңGg̘q֭O>_8CCCjܹs ÇwcUcppOϞ=kkk+~B!.P(<|A-|!C,XpQEsrr|~ǎAUUʜ9s`|>_MM-((ރ &@ϝ; q/--0ٳ3١C?E.]J]0رc\r W˗-[1 qi8>f///X[[ٳg8WWWi۶)SEؿI&c8}4O___'lđޑ;vA=ΌiiiHIXXp8k׮ ]tؘ.KFS\`ƍq㉸8~e;w|q?EEE8]pAʕ+l68nddxZZǎ+$5 P(J8_w .<|͍eggWVV:88S M7n竫+++ϝ;7qD'Olv@@a͊giiCxxxN)ŭ[nѢEK$@YYѣ\fqpp033l555tp,2h xUYYyqWWWWW'OkXE(&'''&&wECC#%%۰")Smmmx}ii |;ɓ'={}Aɓ'ޅk׮-^M6qqq/_BbݻKKKICX!!!o߾>KcU###{M~\$Tg7ca$###߿b} UUUEYY|||\RYYӷo_㊊3g΄i͚5K\vvvtG89U:$IS5|"K8r䈛ۚ5kuv5f0:vذix82dHnݠFc"422j߾}\\\rr-[իWrrrff 䏧 кukK<OQQ+**Rouxxxlذ!;;vvv...wtwwWRR) ss 믔#AӦM\455oݺ;x`;;;SSS^:-tRwwk._\,4}OvO Z#GŅ3LdNPMHH'$$S555OOϰG :::kҥHv*%//OMMĉ]:Ϟ=;y/^ mD>}jjjJ?>=='33ŋ( 8@8&&& EEET  A8É'p7ݻp-ENvڦMSGMMM93, a+rssݱcQuuu{nTT5[/ ;w8eb ###oaaSoooJ1uիW===ө޷-^dɒ;vPP>/42daA  &9s&>>r͟>}1c:nܸk׮Rz\211%K.|ZtuuGAu VSW<[UU_(:<ڵk# ڵĉO:_Yn|.))͛7f͒?ZEEy慇:uLW4zu-i͛7U}LOOO!ҿ۷o 6 6Çnܸ1??͛7:uotԩcǎ/===99.) %%zXxqAAeBaJJ Mrssx!\uuuݿ^~A\.ܜ^GU]i_AGz544._|9 ڎl۶{...[n=tP>}`8::C6}eAzMA?~|tlj'l:t0{盘Xӧ/Y$&&^N}6lK***QQQ;w677i28 E=FFFmڴ 2 HW^:t;v,/mڴqppPVV" uEAˮCm6>>~ƍR FZwcX>iRUUUEEEr~p***䔕)**j gMMM rss D~> x"233UUU(,,iXB077А>'+ KLǏ ZZZ_ mSWWWSSkL$MEW#k׮2} IR.K<1|6m20004$I1m2vMcL3^a```h(````@kCqv;o<\qFpQ<"?.999uv ?~ M} (LCaÆXm۶?~b\\\y+)) 0@jċStԉ% IJN`EQ7,1xVZ}}UcX xZRRbV\Iy=z_ bxxx[-BK.XCC#==]={LAb 1PO,Qdd$ʐ52Zy?| %*cJzݻWFr^zxˇbbbB3g/sCUU499Jw^Y`erlr$}pI&uҥlϞ=27޽[paeeeBIL?uOŁ٫5HIS.[ĉAŐADzk׮-=PWWYK=/!õMLLDt^b30O>"z詩pL^]] 5)7mT55- EIIɜ9s444:vy{͎;sOŁ9l9!CP?0@ĦMt颩I{GDD8::_K_~JkhhXYYIKJoܸɥϟ??rH}}={ݻ~ JS/_PSSܹ4 _#**~k۶mnn. 66^]]2$$ uoݺegggdd" gϞkiI8q$ÛD(闤#Fz pmGGGqԣGjIwޞ.vkk֭[uH ɱi.\?$ 7!W:srrj߾WbDh߾=łVy׭)zH||ތ3?>uT@߾}B0osOPL4++ $55߽{Ç2$2eJii)( Cj }@uo??H(=1aqc0ݻwt_\\۷o B"-݋]FzRETQ<400SzW^oBBBBBB޶ AQfddSppp`mm 1 }](ɓ'}|x{}&~QVVٳmmmQMNNNMM0BL' b$@ǏoTj[Do!nnnw ׮]߿ݻwIz{C)X>=K$x joDƒ$`}Ǐ߽{'VxDd iKa|OX{ 3I=֒wT7o^||Jڵk(BCлw'OFGG;;;H y*iz@J qss+//OIIzMJKK͛6;;33D>|䈫ڵjPY-PVVpjKDRSS-,,BCCNm̜9R$ ,,,, :U/őFF-%rbbbtuu===/^(Q9&R AOc:^9rI^xAqԽ)Ԇ ^VV|%=IdghP]]}\Į]ƍP]]mff0Ç Y$?~...0LJϏ1r1b?~|ӄӄ%%%;{ ;ѯ~\ IDAT_LiPЌ3glݺMN >m۸\.\米r[xKGp#*t۷/-m۶ݻw {u߾} oޯ_?UUUp#;888pW#"I~AhjBz mڴٳgff&n 5ZZZ&$$ݽk׮QQQ" UBF"TK kݺw KOO!mک_$|&>DsXAĉϜ9s}quo??W޻w6l$AzAK.|-/G>{l#keeeA^_O@~1ThhDHJz^睖Bл pe}|/^0 .L8QCC hZ﬎x۷miiOҊ@5 xHA Hww͛7˗/a 9}~7776mmmo>ooXqYYY##++p!Ϋ'_}=lllbbb(IFrM0K5u[xѩSyjw܁PmX ,XWXQYYh@=X,..-~www5ܘ{*)Μ9333s߾}-*?xV})7=p݀ݮP(|ǫ|@ 8>tE111/<ꖔ5 EQeeemmCQUWWyx ڇRBӓZ]xUL>_0kl(tiӦ)))ݸq .\Aͩ)**jkkH\VP bvz|Ν;UUUY,ɓp۷o C$Çhhhp8ԍ\.O__<`^^^B!NH_܀XXXXYYQpoYz0ѣ1 ;|ǫ]z 9rŒ3%%%GB~hh(URArssaLIIjۛ)-R A/6qCCCMMͨ(Bц/::Z]]b!2~x. c]T =zzqabll   0f<*+++ &vO%>Ddk.-6m 8BsNiYmZpGp7qR=7Aeeezz5TWQޗ/kWRj(((000@PTTxy59r3KW(`ZhcQҪY@QEQEKJ?*ښWiVh$= @U%̒@8Jv/Xر.l Q$ɜ]]]z(//95IfBFA4%7-$I~e4TQA7|Mhzđm_}ͮ 苹VYxMeᣚ A("6H$/-4JE= $IvB/2@o߄kWpejSe+S.f R⎀/W$YIdHYU{/8%]v@͂P"{MزW֗Mt7C#OQ!b {1] Ycld'Q$ qC2{M??O/v4?1牥=NM7%Dsi_~D/~6EZ- x}6i Oi+?VAPF7SYhCB/~_ndSLZ_?QEGv>$kbrM0m$ f}-Vd[{ݬ*E_X) ߽{Wg)==~&-RF&###%jX19xxk<ŋo^ېPؔK_ k>yJ L(j4j ] P-Z祾*モs'E$#ثW/s.:tPSSlڽ{tQJiUy3gΈ8(a(a,fl[AMB2ԕ:1QC6(b èQ֭[`'N;EAh u}'ꤰPGG'444,,رcQQQ...ׯʚ>}HXtss{ehh]RSSuttɁ:sAQvܹCY Jn(##c͚5nڰaҥKx;wƍbŊ;w޸q@ıc޽ %F~ Ar. /e -~9%eϟYXX>+;OVƍ V$)Yk׮}633_ q$-:2YQh@ҥI{Ɲ:uUi IRUU3,,qrrIE"[_333 caaz{{%;uꤠpeAx0iCQu*!hFe䙪eՁ[n\^,p#ijjp>} gʒBx(''۷C r$/^,Y$?"yQ&&& ԥx%%%}}}Ew~ ; em֯_?c>o@ΝƶnZ6a"L0̙3ƍvZll,5~133[r G>}%SSS޽ g +QUVgϾuVTTp;ݻӗᑞy\G&ăhAIPc$$jth֐(k\w5&ĸ$$ DԠj"(#rti`<6S?TWzի7} U~Fquu-**sζmrrrˈիW^t?z?sժU&)??ݺuݺuC[GW\ .A̚5ŋkkkۯW^CݲeKdd$<\Qӟ9sfhI$`&!̙3[z^V6_򻲲rZVպ&%% ,hԒ/r#55}v9s@U]\\,Y}+W0ez8q駟7gϞSK  g.+uq8ٳlٲB}Qb*nHee۷;tlPTTTRRҥK:[m 0LĒշn"d.MoΝ;p򲲲-Z{xxTUUf7otMS(/A~5b-u p8O p8;'>cwp8ñ;xp8vO|ZܥLWQo-\}///Ǿp8ñ;xp8vO|I>`5)Ҷ۷`ܹcLL̃l"h4fffcQO8a6ܹw9rr… s8GϣSmIKKۻw/8iҤSDrxxxvv6Lϟ~,YgI&8-Zhܹǎ3:uz`I~駽{|҉'o;>>ݻÇ.^d:t($$gƍuuufj߾ѣ/]ݻw„ ^^^6m~aJJʉ'͛(Y߿e˖ Ao߾}k׮֭[XXXFFmh:y$E)`2[jr)GkƍG}]@@jrJ\2!EQjGqqq=zݻwRRҲe/_$ԩSAAA?իW_`0@ _} 44tՙP_ٳgkѣGiii]ty`Ĥ .,_\כfI bbb?AiҥK׭[?xJ :?߿?mۮ[Nӧcƌ$i۶mHTrrDQӧπCh4999+WtuuMLL$)!!aРAP3k֬t74sX9+(>yxx\pAZNex****>>{O$Iڿ?ᅲ޽{8__bI믿O͜9NI3f <_MM˼y`y]]̙3GFU[[+I_|{.mرce˖yyyI4uE!QxclXDoĉިǩSm6tߗ$cǎÇ$̙35552;(ht:WWZٳgz}pp08p@AΞ=*9߷oÇNNNuuuѣG}݅ ,((0 F CN!YYYf󡡡!!!7nܸ~:z?:^򐒰yiiiIIIDD J6!!!++_utt=x~\SSaZzF,*$izSEh4%''s ڵh4SYYOMM $`0:tF X,999{$=zsuuu˿]v}'/ 0SjM%$++G700>8p /111$$E߾}#zѣSÆ ;x L|z`0ܹ377^p8'M0sժU&)??ݺuݺuC7ME/L&bO 0tiӦ}癙k֬)--}WկV  %!!!&I>Wx .$$$Sfͺx͛kkkW_}0tм'OFFF"##7oުUF'񹸸۷_~ڵqqqjjjsKuyΜ9֭V}W-Zt!رcK'?ڵ[n?oi $$ħԞ={^zԈ#6lذpBww#FT```6mF#2dH]]p())qtt|zSO=0Lԍ7)ʲ2ǾHMM͝;w<==S(yyyN3$o6p o6p8ñ;xp8vO|Yz%I@yH 񥦦h_~iLyx4"ܹ̙3Daeee|||Ϟ=fMl!?`矓w899]`i_hvvc3;)___VN4hP˖-===_~eXh2Fp1Xy&\~ʔ)7oބDQ\dIV\%9>Fp뉯?t1:zquSLYlsss#""Zn}رq,СC233ƌ71yyy111>>>~m۠Kٳ'99>Zvmjj*,_vmiiO?g:s8׮]۳+Ξ=g9׮][yٲe >} nk~W @9fY|Rr7n՞{aÆIT]]$iȑ/<6 &MX,M]})wܕ <.**4 ,]رc$ ,sO'yYN7eʔ!C3F\z>}:<+!o>dȐ!C9sIHHسgOpp#G9}PCEEE}~~~IJ4ɓ={O ܻcXz̙3Yf)mX111e˖`„ ~~~/ZbŒ%KTp87~ܪ*OYmڴ x"O>_{׻w{knРAj9::M0>3Ϥׯ7cFprrz/Nvrrkݻw;Ӝ … kkk|͊0n8z ˗/((vgUWW=nZXX(bUUU^`[jUDDDee%W "-[fϞcNDŽrssj^^^ Ԯ]5N9r$,4h nnn۷o_n`_~刈x\]]`^ VmժUpp0|(IK/(//$)-- 'SO=v9(jÆsVC\zK.mڴ%I***$L7ǗWڵk>>>Z5 opT"-fsIDAT9!9ñ;xp8vO|cwp-&+(Ͱ>y}^s?78=ñ;xp8vO|cw>_nnٳ1$1cC͘1#//OVݱcG{OAزe ^XPP~8q ͛Uv]SSC6mp8ƾM $$dʕׯeV[pM 99cǎӧoջwoS/^k.׷Q h֭[7+((;wr8m[TTTTTד//oٲeZzu-/^ O81vؠ̨>ҥKr&''O}DDD̛7O>MS͍bwu떻;*֮]h=#I 555z5 4^^^ʯ%i0Girl8 iv$ӮNƇm^ih$v pر&+4bĈ~ WTTdgg3ߩgΝ7n8r-Ss~GfܹsTvvv]]qƒ&HW[[+b\yDT0Pe+iT&4"@9vԓ`m=,W(Q w#q<),,IHHMxї.]2+Vy>o8iҤ{VTTuJksZZ/ҵkWTGGGB&?1ccc۵kYf4B|H(r\\[:blN9_ rɗFz j[ MM[ԫAl@?]KP38f h4  ;jԨ$B7\$ Htф^C<<SN߽JF׳?KJJ4>~~z˖-z)hY"ᾢGuHPSI'E Zl틂0 Z1bz5W\nI4Ti Iǡ(@ hTOD#TGMP<SпW\ٸqAz}rrd={6I)<A7ɸd\6m,K۴i75yո/d#*Q*r!N !<eFe:7Id(b SLG"ijծF Zyt '\QLcOy-Xirh܃҈^$j/߽{/339Z9k/7qB)E =lK(xLj#+m9>=cD f洋 '$-~H~.f*;_n@ D:$ tRD&9)fS5yw6Z_a%V.P ԅ;Ν;oڴI®_ppCASQ.S:\bUN Ǵ8aVŪќ$&QrVC"l8N.NTUM !Z*֠˙ri3 Hr%[\Oܗ-^ ct)1x/s9I!aJ)R9vy^Gܠ;L!JaP %kx9Ej%la"rMA*<4_?@;.Dru'NN!cz Ei'!`.P|PΪQt]0'xFfjHNt#ܨ<"ār5.&!Hn@t/,!AT0_NXYݖ +l8h&7j\rO® N"ttO4Iď, qרrV֖YЙyP9(;/'\y~2ir *95P[fwrr,cVȌUT{QKW8`'1zAxNV=,'}HETԏ?`N: X`A޽ IwBFcVV|! bNNl.//Kddd 2Dp~ǣ ] O11$+Z%G B\ӡȃP #yq$5m#ӱ̏Tp 4zT6cV 1,R÷ʮ+Ҋ #>>ӟqqq`$/˺u񞞞>>>hc6vC· 9 r/SOAms!w JZu(u Th4VFEX(9TߵV+Y:0O-q BΝ;W^^NgXFѷo~a۶mǏr劋 .駟n۶mfffhhhvvO8 lժ /**:uԑ#GfΜYZZ믿 ;w$i׿^{5\aÆtO?zcƌ9zhǎYf{ܹÇ_z֭7o:;;7ʇ՟Mj5# ;Ǣ'S1_8Էj'dos=V=j&ŋxFپ};qgADQ,//h4))) ;vlll$I˖-$iԩ-u`xqIjkku:܄lx#E~15)##c:޽{(v166v(溺VWWh0&MTWWl2ꑞ$XؓixXEU*+v8//OӽKcǎh4EEEx5HxxxVVl>|hhhHHȍ7_~ȑ!C0ht:WWWLnnNo D~4 p =zܹs$%$$deeեEGGzAӿ&{ V4!ĪŞL~O4ÌŢgΜ 5ksvXVVߪUAvU\\l4wWYYSr;;; Cyzz5*,,b䤦Ο?V0aŋVXdɒF;<&lz}޽vM駟|%&&* M;eXN JB~B ^nԘPlAP)$5XnORD%2ir&6hCG(J6:oN՛Q49twʓD_&jE"}F7r(CXoz;% N~GƔGVV#ܪ@.fbjONm5WVp4Q)V`!6PrKEN#z rMH=|uC~K TAL.:+Ρ 3Je_t>B2B QȳB8j_1,A*OAUB,3+7gBbٙzv휃Dj}EY:9j▎%9+Ì I+w9ףbVN4$%Xbr90} sMWZ+@1>&BE9rY9vbOm/^_¾Qh ɸ(±HmT?/>@ Ӏ+zt|jrT.h ELf2&Qφ(f!YGC w g-Lu#vAKNpđ((fO@iۉt~C[Ʌ=Lf5\Ţݴ/r E@9CG#ڧVLWSiꅎ9[j8M *=_YL$Bh'(Pņ{Ҡ [pňKێ]9"qTk" AKXa@(L(14b"<A(bх9a*s.bP@}б'7S G( '{IIʕ+L1n0ӧO'2 }GT Ia!=LjǀL 2  5U!DGLPȳBoP] 'A/sˁǼr 1;L0y%L|G95BK]JrB>,9s挃bq3w g/Ljhl|i0q}Ք eqrI Z=YV(GKX:c'5hP lN3e\FBSXPxA9SV0]pe˖Z~֭[m۶uwwG^R2̠l{j{[f"/=@A&.;BOJ omWE!@-҃KtGL i& VvV:CGr)tcWQ0%I0Leee:v^Tm@IENDB`workbench-1.1.1/src/Resources/HelpFiles/Menus/wb_view_Menu/000077500000000000000000000000001255417355300236375ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Menus/wb_view_Menu/._~$_view_Menu.docx000066400000000000000000000100001255417355300272610ustar00rootroot00000000000000Mac OS X  2@ATTR bxThis resource fork intentionally left blank workbench-1.1.1/src/Resources/HelpFiles/Menus/wb_view_Menu/Thumbs.db000077500000000000000000000350001255417355300254110ustar00rootroot00000000000000ࡱ>  Root EntryzR@256_c5f16835498cfb73*d.L.µ3fL.µ3fJFIFC  !"$"$C" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?'oΏ ?~u}׽4y~-՛2xRKr02fУuWYMT滷ͧo|ϑ?o>[-O>u]bWх-2{U+뉭/gb cTʽ:O xgOe?jծ0;Ȓ2SO}B舂0|ˍ{mלg=ȥ:viY_]dmvUms$tjktbIڈX,{STxxwn+n3o CC>Ϭx=F|(,mw]6:+?;䠽އj˧7-w3$OqU$׹xNkxk& k0d.ZPߗa;߮94{Ş.𖡦"&T[Y|ɷa; _xk͎p75="zgwk^_xѿ:?$שV4@n&5gHC+m NTW<eφ$RY)v?-?4_74ծκ1ymZO{<^r߳\JzU GI 294DsY:R֟a$O&&¥Bm(K#y?:W>JzW?6 fkInUf"6U?0H}tXdexro7~"?[Cyb'3 Xʂ2~`M}UIn݁Zqj-;u"{aJZ>In|EjZMV]]wmnX;,q Qf!k{MSUYWR%XI -\cQ?Dt?w_;uRۨM%$s(/D7M 14/˖''M/G'M/\7|x=7I"W[_?IuP75w^$ռ7_=KO;jisk<2eژ;ݬhZ\"O_&"O_&.qM*VzN]IıI25r, r0kYl>$n:֒^RI$HɒBWe+#N񧃵->PӼYY7j1Ia:đI}!sf(8'Z_~e(_~e* U6-OFԬ+\ND$e% <% vS|% <% ~+$"O O w٭=!Vj bݼ!aw@_~e(_~e+U:ǗVi[+}Fs[YA0$~K̿9;i? v.(OCJclTå?NGvww FIX~U@ڸ ~xox]֮4o.o73[M免UR5RU=3^E Gp\|Fѯm[}(.ui+Iۘd* 9@ ׃<#/oS /-82:m䬹Q]) 7kh#4--Hoෂ /.<` ѵkUWèl&3 wNor#C˝E[5eճ-qkKR+(uzI[U*`0Fd QIogڟTA;}.?kz5ZNi E#OqOSW&|;&Fº\6Z-x. +y*oNםϝ~$7 Z.^6'fKXfb6\ᆻ2x IW^ e(xF澋M_v6yx 5IK $۪úFsl }?KiWօ'_"</Ƹ^-7i5r3-&i/6RM11֤ҼCsW?xn{t{K*XX(Q嗐$(Bwep3Gu}?Mh<ޛ7-į |Z|A4B7N\'*:285c>mo.tXI qr'ݢmrc)`:ǟ7o4ޛ7Zν6Y6a&2NdB7ⷀ7}(uSK}ĜG #mn$m5t?M[h4ޛ$<>']p4]狜0l|6n85hzŦ[s+&r)@4X fޛ7ԫP)Ɛ[MH(/G?Mid'}H>B-ƗJ:P>Οޗ7ҮXk;Š1ݳEl7>9㨠9#is9oH+ u!A 3ڀ$QTA q.s XĐ Ig_=|!m׆hztxFjMf,DI9$揱ϼ?Ʃb=hcxHl{[| ;Rm{_ŝ(NȮ2' <V_Bվ!Co]N=B5;{n[IsNF:c-]P[+\so)kj?Wh?׊=f M:(%v"#EP噀‚NN*>'GoY֢._>i-iHH!62۶Bտ/J߈=?_=hZϼ?Zϼ?oO~.c外2Z]F{Y8xFFA$A\hR@u}~O7JѰqw'^xe=趩w[[?ȷx^AIg Ǯ]ūjZ]흍F3YibUUO0ҼmYitwWW G"FpT#uzoVf6+"OiQx#p't4Da}Yy/FԵ}_jA?fD@Q%TpV+/Lkmho[ϼ?+"MCR|G`{d-ŋ8Oơ +muTdVOĺ u!u}@K]'<7vpҾk{p)e{| ok YDS(;XpFyvմ%4c=!?*u^1Գ4Z_\jeBx J&IݙbD`8IZ{;Cj?/Sŵ+> /M,j72f]4rE j΄*nQt^)4?5wwxÏZVk^ >.sJ./R^6D`csOɠxU5XϬǨH|UrV,a?-rᾉ{);߯Z|zT=+׾":u[Mvզ;g \;|K{KzGMgY/moů_C:cx._c¿xg¾#]hDrL*Y>x=6qںMWsM9/;"kIaGB g~tZYatY4&Љ6MnV  h_xHy-\TT_1Zc.7`\n8M_J3\R2z⍹)R2}ӡԏ֖P @2Ώ\|;~Ox^o*èEhparHkgP $4-}-+il>:(iuTtffps7n 7YW=埆Z"־?Oj62@crm/l۟Yvƃ҅ꞿu-mtk FFqM{g&Chۄ&| $wiPqc{RܚƓZ2As [7[k2ƀd;_!@_P9zjl*6J͎/n/W+UOIS$#. JV/鯮q*5GNO#1`.rY$g}8P2rqJ3V+j0 Xx0ݢ.z&/+`{Rd5ub$ڥ>)~}Fc8ޮ|MO>0;O Fo[7y3٧(6F¤5X"e_-Nm6M)ɅsӜjQӻU){q oO]= Q%5"H 느+Hribi⤋cQͭCɸtsi uS=/y`G5j6ѡKF$,9gCEYkig{2. c3W֓]ڼ <(׵-x-^eV8YI H9koĀ>6)?%o3]\qK䞜ɹljm$(>$ܨ~ukɦh:1o v+6ZMZDj3<|Ź%x"m>Ji<%*aʜx?*6H5;-(o FJJ?JwO ŝ]umw{ez٣h *x*Kӌ2NxÐn9wqL;r$yN;?/U<ӢHaI>k4t/OfUR@P2I`{ 6z*ZX,DƲ,ql[\k(̍a\_<۲;q@FI3Ŵ9;dȚFH E>3%ԶW76vGKInI=͓:Plm4SK&g60 XQ,Hz֧ejzuƝwgsE< t`v/j:>{XGjvFE 6BDtk?^Ǫ~}y|5VĜi|1C湿(`rZxIX$],.~v (lIrWI : PzA}v_HFfU,UF [:|'iΟ^_q"Ʃ"`ceǂpW=rh_cwn7o u MZBndw1.1q^9|s_]ֵ .[9=i ?jP@ l\dǻ0s6kkce{xaXWϙvP1;qԱ$5 axIxviQdoK-0io0XxL>h4Y+Ls3^Lʑ1`7# `'ÝzĿ=;cT-%/0Ҙ?s9Ѽxc"g^]M,;P)hl-БrLBm )=rw` [o xOcg?mtUv1PKd$g53P=&G[MR7Jfq;|tn6 8o?7${r<>%xn|EM/ >[qp>y%$>\BAS*}w^ bEω( f!G@˹`PO''y~x2 GL{t..]BX$ /˜c#$k vZm4u8b3n" Ns}?/Om?e;1.i ~|aѾ7Cx_V:k?j/iptCO_4l`,_8R+!"ֹ?ƍa'9昄FQ,I倛iug =3w5#P"Kh?&ıN̥n]is:߯< VGiֺ}U^e9QϘyȯXw|%)&д k)R̩+k#00]DFq\;uxnCO[{;rªRim/mYo+=z%N%hԺ69MT-`qī=ȈF%p>f8\q*K[}}Qn7[c wb_view Menu wb_view Menu
The wb_view Menu is exclusive to the Mac version and contains:
  • About wb_view... (Workbench version, compiling, operating system, and OpenGL information).
  • Preferences User settings for the wb_view UI. The shortcut for Preferences is Command/Ctrl + , (comma).
  • Mac-specific options that are available in all applications (Services, Hide wb_view, Hide Others, Show All). The shortcut for Hide wb_view is Command/Ctrl + H; shortcut for Hide Others is alt/option + Command/Ctrl + H.
  • Quit wb_view (to close the program). The shortcut for Quit wb_view is Command/Ctrl + Q.

 


In the PC and Linux versions, About wb_view..., Preferences, and Exit (to close the program) options appear in the File menu.
workbench-1.1.1/src/Resources/HelpFiles/Menus/wb_view_Menu/wb_view_Menu.png000066400000000000000000000657251255417355300270120ustar00rootroot00000000000000PNG  IHDR7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:ĊTQQAԧ4%&ҋ^B@( Roݻm?fwmyywv̙3gΜ9g'x+EILϴ+y7-|V~yqcAjjj!gxu! R-cW kJ%B|@L^@Q",-*/Py!냟BT JiВxT* 7WXPZT" L0a„7jԨl /!#!`,RJ8!7^eß]l)h`jP$,.%UAe(|;viaaaĄߊeKj|W;@>q{4il8a̙+=|GE)pTPPUTU)AOCQZT6R-yj m]2tJ+Ȋ\IE-҆K 䁰/$J lJ$1n "x5oERhOUTUaiii.APTEUh@%y'#*V?nz_8.+V3C_|#}۶~D%EQsrΝc7\hEUֶmX۷ݿ,K,_QF|Q9ElkclJؽ[Ve[MSa;pws_9'p|u*5liT0QX@jo\NPM#T!EyJ)*i~ TV X#ErHQyyw؇,Aʱ|m!BJV~? Q5GV 3f&"|"G .Uvv)M ybg}TnQu"aN f'ZV |,r꺞_I)mԨ[ChhvC7TU.l_|1Jk (>hܲeK{o eF)UUҥ A~}UrF/_V-ZU;3rjժ4!!qĈODQܻwO6mE1 XE>J7n&zK+|bcp0 9'}o%  -P{[WImm q]Bcrp5U#7U^̢!nrYrN> mA Y kf]NܞH)"+DCd2L) ILl;;+11}K)eҬY7DFVҥ*w}m[Ϝ9ӴiSՏʃv9t $ݭʦՏ_[r idA0O(z.=oZ'Ɍc7&>V]TP^yW +8.zQGm{af &͛[Cq,فc -aw]j">nȑavzUZ5|(H$_"5=$//he$|pC,Cia&(ryu:C"cxOVĄBa9]҄X*-!m659Asf nR>>Y8.k3'PPBK+@T!c[쳝_{6{)+kB6ן jP91e&?q= v~ʛn/^cNeDӍ~@aܟ1WVLOů%yUCʊX#%:H5{rT ,BX sRUO+/_DŽ fҋIÿ=U* ,  Ȟ, eP >XIIc5+! }iw>mi:ǒ_H{H˾+{d깭A{|T‰aZ~4s_߳Ƀk5i2{e5th?_)Z|+a$]-oG_\_xH՞vMZ{<:oG|-_uJ{-Hw4u,I$[])NAr%AQܒKNAt[2}zhs nC09f?R x˜sdYӓu~w֭F< Ղ,rxVC؝Sαl$ʼn'N>-a~9e9}gwR5x;*CI1c?h 5#Zqr/6T,ˎx_.XhN9,93zss,¼6GoeYM9 0S%Qݢ .At -ܲK\D ܢ J$K,ˎ=~BY)ڗ ʟ[e9g\gxDa@LYs&uZ| ˲|'m>?X|hs@̒Ӓ,Фs|Fڌ;8R,r8F/z˲,LY>-˲p?!ٻ| ƻ Ii^:ѳcMi\ľ姩AȨXIFLN|/<_\ҫtE$=uÜʩ$jRO-/c%4R- D`)K~ ` MX OȓpE{g>x>[jquQ_޴i3 qu; 듟:=ĎjS7`Ad?0١/Lؖm~~(xEGi:GI@#j:99>j!ySOՉ۷n)QE̵>.y.^_vqsH. Ag&NvQ4)7@Vh!>,~Au;kERvS6J)@WF !~MI!KtW } ;jP_3dxA#Olg"C]:gmZ2gZ8 MtwS$u| }oS+7R|ʂc$ !I$eOJt:3- > >X̐=779M丶M|iOZ;eʚli$I[t@bG/X%AMLe J(%IQ6`W\=1>xi1\{țe[;0xsq-9aA@)ثآnŚ\d0H$jDM.c.%);7%N~Ũ;IKڎc$fъF"I)jlCɺxDW ov;/;rrDFGyC >*Fv8d{dOv8d>2*,ȼgGE]PCg,zEѫ+hI)pjYCީ1[{3ڏԗ;ǿkC9]˪DB6%}QxEvuBf"gWuo`BvUqI\ H7 (\obn 6!%/^D!qF߇-?U9BBAkRz-IV LwkO{I7Ŀ%o&!$Xp?D1[oqXn|Rfg'yr'ZpJ)aSG$gʯ3V KV0M|wo;zoto;Ae|LzIrhX@6BR@q$"{zu0XRj F p31ZtY93yḑ_e"UsezVĤ쇑 r9.\&AEQoNJ$Apg ==YbŊ| {׭ sߖxoXv_N_*) KwWDAujޞ}cɦuz-5t\ݢKNUUՌu~Zj/?nSOM%$oxJ7^vѣ_OeIEvuvKwȢDQd 1= bKڜf"ك K~sg 4삖t-Kp\.#8b\7YpO9;wg@~/ݢ$ekKH2\.˕]KN p0;r 긶1t\eC,M\ s$,UUU5}ՇVdY]7 nO\a(z܋ S%$n.һIX-8!nG64N0dSw{ n^\87W4y]n=I@̛~пK;aŝЀg䪍bH!K/X'T$䪍R'’CmJZpO<^3IQrV9%gWnPWk` UNܠRUDJVZմu47_Wכk!Dy: ]- !}c?uVg,dm]ѨUu:h`fbp8rɨ1A&%egk; >-湵DsGv $R7n ! 1~!TV87A8G`3b֢V) ׍F6e $_*ITA @di:vgyww=YХN?LMT.jZ+V"nn+W\CG@?2bY峮7,ŒD(' Y5Kiz9ѣx*I@ *-Y1jK!JB@#l>-Y"85Ph,x8O#d'㹥|%.ښ~ǎM/QpšLn=`κ]dAy}]!o$[XoTW&{]}/NV93߶Yp+$<<\w1'.7I$!G88_Fi6.#Gv2=)J#/{ԎS.=rDNU|w8ό~ܹ#gNtubLW<1{ӑ^`Vr*+\.ؕHnK?oCNqD%#OK;lF4sWl=r,zuRJXBg&ޕ+GuݥSTTT)X YȡQ]N_
@ <~]{z|PJ7Ŀ5㧪5VGn( 0G~]oB5h߰oRם~y+(_ Pv&(<в833 Vzܻ S軩uwbU@ &kٞywBHLh$:Oj!řRh55%ZG(,6z1w=d"IZm0}(z1Y!mTѤ<ݚmNFɄ 3ő#H77P=@E@k,A3 1rbҤ L!,u^2 н!٣^ S!{1H1|X5k֨Z5"RtZdDD(?v9K^v-+37k:P#|X\1@~'&!(V$ Nܼαybʩ@Ha.@=*%t`t̙SPPh/xV E jn#j"Ŏ=bVk؃4 'l{JVyvLgP:VEhbΦ]jn"Q71.3BA{װY=Qu!іLC6!xݾ뙴'r蔯0ۿegk7}:\5iKu8c^H2C3k.Z/JΣp_kQ63hZžص|Z5;1=Ҕ2T O~9w~:M^sU@8-6PP6YHO#rD%Yں5y^՗AJ5..&Y1%W>أ^|qONݴ~ vC-c普U7 WOJ"ng[v?8a/|Ө~Uoly!4ݘPB(zܹ#,^ {u؊)ii)gzOo %WXѬg/8,d)YfØ, j(wE)%,[)Pf~mZ T=|@)&^J̔-OG >C(Sb|Q~ө Z(-xkJ+wLyWgE¬(5/-KqDPPJd(kEi) ltIۈm69F6np3oŪM|/2u&8@ߘG}u]akX !T"ʹQNRcA ^ï&Y$] W0X28Nnl3E_DzK٢Sq7hɾf&oT5 `vPV%}QcUcKL5&j7AOdG ސP;0Xz6u κoAHbHn. f4i3VtFFZuCm{s;]k,G@yeS7ݪ>$-!Y;; GUjEQTHj MUb7VT#QUlgcKV%y3!z4.rl68q[V@E1Ǎ߸tߔ()]EdZ̙ Գe'c|l5q]7#ŗK&GXs V_ >EY3I7FCP{1ik .0-;-[DUtzkmUcb+Ă%]JfƤ$XBkRއj#z){fQc33h |odlRs+WBo&/&E|3u.Eyz\V!WN`L~4CG 7ԋDiD+ 4ߵYEߤ]5Pi)tMt<3cF_c&Bܤ^9QSESD$،=MpAxs󊨎+ۙ&ĴPuDU@a7Vz: `Q_IyO⍕6hRFQtMcek, RKM u2WF*;xŝsSj |` v ̚_$FF1K56e]aZ55ba9/WDSsL~h 6[9>o ^J.%q}C8O|b_#`UhYli|/D(D!>,b·zmwW(U 4kCcAqycssEk7QTC!<(Txf RϢNe<J>Uc *@݄yҧ@d b2ʰTR\Y yrnQjӫS TjaECHSc1BB8k'Ix Pv>kIDտUUZa5_?oZjF ey[XX0s_c?EA횽fWʬ) PIػ[.44<$䱗'caq>SpzE"i;y#e[2rC|n-^er;[)c0l>yC/ܴl 6._i,ĜܵiQ_0)Z)4g tzkAEQ%O4]9) f30pf'퀄W݂9-!men('u~O3\q( e?oj9CQKd깹CHj^Rgm /R{!~UXgtܛEss\ʝ ?(7/8{L;bRhTW\%]QD`Ǭ>yK*H9с .\p…/Hؓca(((pNSnv?I~Y1a{r]HHqvg1r뤬т(BꯦwV>P.MIQe-ʊrCI9EQKؓ-7^:,҃Pu!~(,uYUhb+0| Ɋ[Rܒ!nqܒQVݲ&%O jz3c˿z$C.={¥ K?g2Ȅ}vQEO^J(ӟojlUzgxn=.Q?2'<.ߐhZ.lٮOjhU yS:-9Z!QV(ߔs S79/Lbyr,x"{Me!kBwgh*Lb?:rI^Z>}@[ۋ((o^ -hw/ָzk-5ٺ " nUC2$nwK@#!F=]ԗ7mJBnݎ,O'(Q&hQ~<xnHҺ>p gkZVME-:S[7_poiSgbRrӕcV}o&jF Jd}s٭*òZ[=R3 X6cRݮlgPCX`>eVG+o?Q}"R=><jܡAG;|,82y³S9"ѝ^4_6h#SU]GS%"#+<Y޶1k/O^}wS}9dT{E5ʱ^|%?j60jG)PY깽|N ̦ӄ!8FP7 -[ nQ?vAu>إҎ8c󒁍>؁DZ^}ù'e\8ňZ#oа]W O}oԦ.ץ ^- d+)f'oS)UBE\8s`bK$p68rym`9&9I9SҾ)}9$e'Hlp>͇\]7)( bh39KOTYm~(>9G]00=1wM1KSOuظIR WeYQ=<7GqQH]/(όwV$IuZ/y"fx~f=ьc4GN85&̬}di.qua2$Bdhz/r__3J" nZ{n~{SAM0Ed m=[o.>Ja)9:6s7or268| M(@"Ai*PRJ,r͡| ;ҏ 4IsW]c<=Piج+xUW$dWd^zCVhy/)~ᆾdz$`^z f3c(|9ujq rPsu~TᙴWU)&;x01oN-RZʓ" J]HYBGNfffff[kgTnpo4 4PZ{N+ ʷB( KKR#R5jԨQ#Fa)}ߠt+@:U_4;MR WXbŊ`UٺbWX|wPV߾UNr!vּ-iz &_-TYDg#"eҔC+4hqwe1eO}tΑ. ׎y]iޫmL؁!ZX_~SKF>3 |uϽ@: xdFZ>'{=Z,W0nG.03O\GP^DqsVÞ,2~iwi%b𢕟jT+a./n4EOjK"vL2>hq%$ho:[*L{NZ쾳"/mܰ Hd'f‚w\U~i+u$c+4|U,4~@="V?=]y}m#'vyNsh#3WP!k8l2yN axo? ieF5gؚ?ҸDL۞34Sb=?goAEj F 0yd_z,Ĝؙp/\>gK/*dY$v7k`Djr:s$dl_ڪ[;֣U3m˔3a„[b־oƘ1Cc,;v4_ڰiۜZ ]S"IvCQT|P 7/a|UvɲcFgO*Ъ'e @rdYQ ,U.H(Ɏ IFatqs}OVUGLG@>OR)=}y19A)*HGG uή[r%KPIgJ!]hqFף"!hlj=w7qU\g4'M<䱾36Y ޚ2%';7l,dje!k֯ߑnHGG32ÒZ&㲋 Z::Eɒ-_5[ƒfb!9ZU LijGO1یr?y|# {~El 4Ȗ]>yFo?2F64ןf^0=wfnVfՃw rUnk*{z'Ѐ1=駞zꩧz:>Ƨ]j?I2}ʧÀ瞊YL5*SNZq~+n}t 9~rx 8U_|~E~Eǩms`:!nG=8Uy?nN.90y 5[Aw_B/yl}~i Z?igl8v|']nAOqAH}۫O_}\FYugb־?,9vb;_y(_OL*af-۰C߲s-[∓/_9h=x,IDATv2*B*!y}W.=i v AQ@NQ;._Z?4Y=:%҃{jY>2!w579W_bT!Ab[1A+St0@^/Y/xTDT/KJ)ӫ,Ů ^z"I  ͤD#/822"X=/BNE0Tr3 n F $f *W/!U~i.m_R)MXצĢ}E@`"<7߄~(ZCJVV >/'B %>db.oJ OK䱧8*|ORXb5y螧|!#3;k7>Ox "NR"ALA\34 PnK_c^N$CK'nwF֞rnY傌N&Te ЪԔf8@@?[Y[B մl]pȧyifz|~wV U l~,8Stكlzx'TEq޲]vZ3t3 8(@̎:mɶMåӇB8JC J$AN8EGz:~' JkYtH O׋S􁶭TA*UyJ)TU<뭬{ kʟ51Y(7oWfVތ Q*&4(ETogA5Vb[ Ծ래&-i֍7 Q3xlԤLY4k`M%~Fi|R=gC?OL65}Tc}-b:DV՚wo֠FmغZۑg3_r U3erm\Fm3av&2;y8/ 5OeQ@ش[D7m+{|]aWB I"kHN͕Ԭc8SC+)qptsnW%PClyZ&6!̢՞Uõ՜JM0jL: 叵OF>?}Oް 5k 5mxpcgɪ5]i߮qrEt>G6.:#=^7z$wPj4L2HR4_s#8̀j'FGubՊePMuI:38C<ȝUJ }iU*MBF;8Ш71&<(,3 Y_c{9J*ӻlȣ>>5Ð܁ 83H٧&#BC΂4YfMZ*}6Rp|==UAP mFwWSU7jP{:w7!H~A=BȘ`J5.4Z*dX*z|s$Qk ^5ϝ9ɝg&=XFs:Y#9'Q6޲-][,}ѷÞMflQ-vVp,4i[ [=[ΜˮNT)hP1Xs~zi&Ud4 ^s>]2Y^sKUEIUU,ETPp!A0~*f] h&xye׮l1}^AnV xP!"\h%l]%fDŽbYpP{Q[Hp( ?smLZSgߒϕe(V[s^7r` &3?RhVMmdwj9գ)g6`pߘ!`0C9bMLDҌPBڅ7$C?8pye}ǘ`J@0+.hJAynGK(kE(Pftle0_ ŰGbp3ȃOיHM# T_QЛp[/L9Y77qn _3)~YE9uqzmܪ֗>VU8 Rx΂#KNv nApGZovvWkIP~r!7>8 ^e7>v_HqY!$I$'(r9 S&OOaֲ܋n)-@eY} Ngʲ -Ȳp X!r;ye<@u0?36}X'ضaw>%Y@8V(`ҷNh-6'oVc_0udC,RA>7kgeʖɏ_p$#dۄO|(Y oG폿6)M q+~+z ?<굟Ta;5_{=B#s_Sc/>X|nwUsR.( 8nt1yքAN 狦SyEQ9ǼX|n~=Y ;v?ҿ$lˑeY9=q~A]:fɛ ?'IX/$Ir$I9Q/Sݢn-P=#HGmv%[peyN OA q@;EQt_rN:pv~`ѩKa(zMpR-G)[.(<08S!Ԥ4֎U)zz<"\<<7/Z=_߿ A-u8HNRl\)qx]\:=cћSz(_R&U۝Fū'o4mܥ=[4J~;.mχY+I$Mim솵 [$oڃ#CĬtR%H )ҦqsuJ'i@g "D?BӚ@5%]wPsq{ M }sw|9TWr-*>yL4SO\$Ǝ]x((B(|L/N=Px?J?7m&6 5F7vsW2-ZȝYr6er1&/wIZQ!xx`[v\XXhhМ({v־&yosq] J<;Ծ,!Q]kmt/>(^="z§@Q19r/I[C)S;5["ZC jIY^sd+5F3+G\DahLY@cGmk|| m#TXA /)Vҧ$uQ|,yiy 㤵Vcp!?5Uf&*{$I9] Z:? 8rjާ4VDRՇ,1:˳4 /@J`nۺVc$=Y$VrTHRNZ ]z-YKM!K Mޥv?r]^#oTtTT*xeRt?_^/wը rwZex}tnEoFfj`|jt;+s /U6~mUJn_6vg;aHOyy 0<1=˜3D`;sgWs9٤5 '+T(D2ba`(^+3qrIǥ]^n>׸ukI.0 iF̛PBL<8(U.!Q)H[/ 2jIee&\wRA Vټ3s65]ް}␅+ g{Β8"̪%Ƥ[rvy+XfN?̸%z-KEG&uҏ&8 Mouse Controls Mouse Controls
Several basic surface and volume viewing and navigation functions are controlled with the mouse and are only active when the mouse cursor is within the Viewing Area
  • Rotate: hold down the left mouse button and move the mouse back and forth and up and down. Note that the rotation function is not active in Volume View. All other mouse controls are active in all views.
  • Zoom: press the Command/Ctrl key + the left mouse button and move the mouse up and down. If you have a mouse wheel, you can also scroll up to zoom out or scroll down to zoom in.
  • Pan: (move the brain surface to a different part of the Viewing Area), press the shift key + the left mouse button and move the mouse left, right, up, or down.
  • ID: To identify a brainordinate, click on the brain surface or volume slice. A sphere will appear where the surface was clicked. Also, the Information window will pop up, showing information on the particular vertex/voxel that was clicked.
    • The color and size of the ID sphere can be adjusted using the Properties button in the Information window.  
    • Reclicking on the sphere deselects that brainordinate and the sphere will disappear.
  • Right clicking on a surface or volume slice, brings up a menu of options depending on what is loaded (see images).
  • Right clicking in the Viewing Area, but not on a surface or volume, pops up the single option to Remove All Vertex Identification Symbols.




workbench-1.1.1/src/Resources/HelpFiles/Mouse_Controls/RightClick_Surface.png000066400000000000000000013377721255417355300273220ustar00rootroot00000000000000PNG  IHDRz8)7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:Fx ra RAPJYkݾ ^-ylk-08J)SXq-{<-_e3  k Beim(m~%ƺ^p/vFa+ ggg֎1_ #H8#0EIJۗTaEJ)c9)#Z~Ǜ(UZ]]lL2i*U_i#\ֺ@U, %\ ڣk`Fa.FtkF8/8E]ϗ_6tK:586A{zR-r=s؁:T;*f_IEN 49XeL ,p;$Hhӎ#0#5/\0պ >հSw#iO&q$?!0W]Yqrp-F!Ər"$m$I(+koxZ;}1N ߗC{)&0#0J]WmҺs KM+ui 2T~H;|#(f#̬~&WǙH QˬY7hw@6g u`.,N=63u#)ֺ\prr 8APAmh6 1xZ[,yQ/S:lAJ8 gÑ9!MSc$J8ɮkat]J5#0W(ݺbU!{&D$ /hqqJc[ni1lqرc]%=lƬ8^G6YmV Sa>z{]ysZ>lTqQMUu;ž~ЭHaX---<+5cA@k.p-[ҫǰo9Q;#xp>)mIU$"ШmqRQ-.{1eaFrʥ[W<ܞuM:F!j(Ua:9\ed-3䩤[)6wЇ>dBMQ/vv~vj/\kVgs uuQ9%(γMHxu#=y*Rg)"4` -Fq]Undm+p(J666Ѷ8q8nRWEv( b΋!H\3?_5̗0IJ%W/0%#%c ˢv+ ɕ% Cß+jQ!"nx1!hK, 2sv\ו_у*gMXyZ6uOuFaF\t !ZjӲZ4 hiu9S*<\.8Nd %ݒh< .o.'@!2+"q!!tKk-  $`WH3ΉB;(EP8悏 9I |X/˒F׫V gW<=ﺮR)H*4Un ]|J2kXƠW5."$ zaN~zZ-dVtЄ gUu`l~e0#p5 [WUhFtN(DcT*yyDXB`rdQ? ֣[/|VzeVpK(n[ZYic)/.y?3 )ch<)?% yw6RAM{\qMo}5k]*4e 7Bz(*Q&l, ꂺܽRH*"5%A2$1Й*hJ[1#0U+n]^64 RL.}߯VdJk"a3Aa=>N I_&l[qDNL.2ZdVKd\TÊaKJ0.7XkaFQhhF@ LF /w- If4M\+M#]mN"<ѥIr,2{mIs{I0TTJ[ٗ,#1HF$n* ŲXetQic(Z8TQYw#0#\B`)%_-9n\j 8DA R%ҟ$ݒ~(olzRl %EYΠvdS,V>]ZڦDDiJu%P/mHᓟw=w l cs(F(W|9J)4tj5R6,# r# fVK>-N= +N$4 )j&T(h.sڠբmܩ$ӽ};]MsFaF [ ,j=1 L}RѭJ\-3>!BIU$mY3 K& '6E\&4\^bT,[y^\{{ ~GAax2 Gܶ{|q?NDj>[l:dV#ڬQ~<gY&+0"Hiˮą˚Cu\>:KW*qU>ȵ#0Hd\K E1_tKExtp9P 4}qZVp=!HWEԡ҄t һ1 FdkB0+" ?o<}tkaaj .KG lrQn^ymj8 Q &iQ#ybXܸxW{V>apЩA5(願aSZ<< -8?䬉K Z%uzHBth]_ Qr[u &p2t:a*[3T0 eT!1^G{Ji]L$b X1'K}F(~˖o~re^FE 6"[d rUTTgEjIS1] M7\ksN Zg2}6V/[Q,q,_J" "i]mbT0KKTlx^gӃ WXC9ZUfH>1nycpchZHCTQl}!?텨`=]Z.l1IgWKEdIlImVxm~ #$i_}j]Yzk'}{ˏG޷LLJe?vҟʝ#0 G^oa㏂drq-} "|ý^Oax`D6G<%p$O _uY6!)BXqN┳89},B6s!L tqQ5^0~֋Ե֥O I#ʺ<\KJ[0AJAPZֺՕF S;;mm+~c7`l}>徬Fq-<ȂTxKqZ|d-ɻxSZbV5>??O.^ W D->!OsƬ\YKiEQӡbeJT*aHL> aͱEҫF&U0/z_׶web:?:ur&+$-{bef6|aTr^>EP>vFo˵(Y,b/GӱkY˶9b\^UTOR!D ɕewiwf=\%,MS[dVٔ d!7ٔt4*dJEf1ac[Pʠm)c Qid6 c!B ieTW*T%GI@$ג:`!7A-IҔmHDU鈔eYEbq">֔m=a.^3X>DBeI"e˖vM{<[I0aHT9tER%?tf&zeml<?q+UQPE' .斮 Ix7^C 뒧{ɇ6rԉV]rW, ֋Omv4j⹬һޙpIw׻R)HowTԙ^n/m{ozQSOk{f^wF+dIwq2EdѶQ \N2Fj[]& D0yB:GQ 20Lh{uuuyyyqqqiilZ-&8N\pzzZ(jQB__".Ns}{FAmsJu]\)QGu:~vI[\\$Qk+Jz#GJr5׌QMqOφ W!7W '7E]RX#KHRH H/=EKL*'hyuA%bц%hG$qi 6uA),X &Z-:(`MH*p+(xfKVsĬn۶mVWWip.!ԗ̓!lR'@w u,}Q)]W[ ?/76qR`kѵrdX1Q}(Gc:m {PZ=4okmtV[1C IDAT[?zSyҽw==<7?qgv(2|pIgS^q[4>z[J(8E8)_^qK116-#@?WǷe(+@@ p*@`f:](&}{;/7\tks(T.ŒGNkL- NעteXA $UZVTX "#X&C,˨,* v:I#FͣN&s -[u-r"iZ(o [ZܽxYB/6GZEyG?OnLHMCա8Ib,K푕pKMzĤ\fe YdXPVa0nTIɏ>Ьё=V-5|5;*?|k?Vj-w_^>s_k7w߯ę幱c+c!~ݳ cFE_^W 8rVB~tK󩎗.eSdz-YڭdL^/G][)B _A טO T-  `06,FquЭutseHp#U(?(+rTg;gh6O~g[1%:8֭[e9"YKDE'0+R$1&VG r 4FR1,-- @"=VVa5LڹAl0)CrAʑՖ6E@oV[^^G-_>K2Biu#םlu^jɓ<-e^ $;gNZu⋁| <Ԣj( _dE\W޸;bz*ˠ ؑ=[ O6ޫo|e2Kk3ӝC.SS{6JGf9<bZhzĸ/?;b1Z=#j5~tVRÖryZǽ(jⵅsd.Ha 8Ņtߞydqg7vvَLFMȀ@T P8KôY5썷/*#PA},גJnK 'nx'N,--m9rRf| ˺;>(XxMGTșOƠFcƬxy\=7RjR^VJ5"W8qHPlr`' 0|(["U5jXAh6L\Y{Z[a"Y4UKHi'MSɱ m(>QyaJ%CdؗҘgf|npQV E D@A)J&YJF[^-W%fϷ+GkzQOOoUMӧ:O8QP75UW2c-Y%IMWWS鮷jTCD16C}5gt{ wJ ]T;xr}]}h(dR_uNհeKʎi:}"YΙe}L Z>cg_j*)?vRidhGr{a[e`J@kAI\BbѷH0댙1(knI~A_ QCJKZ K'oAD2b틏d1DfJ偻*I`6jvrT*Z3gsuh<r)O6Ո[Z`dC4lCh4<7%} ٞM {|.1dWfIw_\L81OvE %!ˋgІ$B˓KJ J H`@b)E1QJ& ଦ R@gڭz=5Y=ylýڵK0OzmfƽgOɼw^~Й_(K^lwUJh0nn^2v/O^DG נ|T4݅^9,^j.UJ(uJU؉}i/u=*ޟkv]l{yj;˩BM#TJ e&@fara] ca>E4B\OPm8(knmz,;sjkq 6 ezǏ>HA}Zk.}F08/\.לZ8RÐ̳q  sDLNN:sz̦qd?˦I9[1W*(_[[t^oii\ӧ?OMMMٳgΝFq:SJ86 @/G۵֒93Ni@opPYkf`H(heL dȱg$5'FӅʋP e=u,—_X%&-rK&EDį͸:UH̾|\ov}c =VX8g9 <£1e.cRjv[ SG'/ː\}-nPYյXLq8>3FkUl7CX*8@f) +25W碵3;f8gә۷^&YC'~hݟB[٦n'1iGfgn[Gm?3ے]^"j)R?i#pbƫXԮOGa[{T[8ծ(T+}j$RxOW\Ρ)VˆGTYW@l|O?=??~\dőgffXeD9Z/u9h/DyDv1fD"#wb|M,O'ITZR8Ut:A)ɓ'Ɍ۳ǧ"'9I_R!27}0ȵ SUAO뭗@Z` f; 2|t9_j9#(LGP%t*3 $O7Ny4"gw$Ij|NȷËpr+DH,V2(*l_P @m+ S<cc4<;oт]#qЭ͇2Sr斥|E-֥a;oB''C=3NP{OvI^jdF<Ӏm b҈tHHMt:ȓ^ܼC%B,6W! 2vSxzJneYۍM8(EnI/5GWaG!˃IzEZ4̵`7cd^5.[@^*}Q&vVVŗ)) R+ 3pQv$?ȋ}z! y/ĮnKT*aHyQű6:-'<' rUnj|g *C.~< G!K݄\T2`)SLZ{vwGMPBP@B,ҼQ*T2|G)O^G~ו+^l[V'*L9σ=euC+!2Ta˶Yj7?zFWvOLRVMlY^#+ى[~#~ve?mk?gf'=_O3udU/>ʎ/^]d,"VZDHiى}wkTj;wOu=|-OTk[lژjQ~ZF**(  XW)ϠB# NnlM+W)Wن[H勫nm{Yy/mAOÜ;a]ϊj*϶%I{E;S38i\;:ya入êݘU6CXeL*JlqW퉵iSzL+OD-mWO~#5POxlýW?rٺ}EcCy`𐣟}q[oZV^7?ֶFu'pRV^ЧNF[_ɭ;}a{Pƌ||\9@EYhl<{Kd4.k-Kq vc4ʺVju8o_a˄nἓ7?`2:4I%ph}ZSO=O>$rSxE"MNNٳ}vВfU[w_( 5*LQK ٗ\-Pз ȔZuH#-QidYFs]\.qL>irHs%yVkeeeqqѣTHZjz}۷h$A ($`d!jpӫ IN%b_ǂO#9 GI2½ y-l zH{2ĴXF 0 ժn7"R)ËCp%yut"AF{d&o"w>L6޽3!,^O3Bs6P*l+6p<!dS`bbb֭333R*"-2(aEV7X:_JXn•+eNf*k1& d! p!CoAj5q𪵵5q(𧕕޽{jjɫϳhrung^F#ނ_!$f0Ka H,at :ۓ99?OQ1\F q_Rq &&&z^GݲeKRVLRd{l[VM$,wM*USZ;f3}u'^3>{m!2~c+]8.W=:%ew"kd)3NoU/Xml0۳Yݾ麥V,JPjTm QƱrmo"(y ~B+X@)lOó-kx@%љI8rZ\XȫrwنS1%̔އ%F:-O?R}k\䅒Sibu`.@k%Kg#kRv֪s)t`u]QJe>(J#/F)YLO) ؙfH=kl* l5բ]]K:T]Pe^YǪ{Wȝc_oӯ_㗂u/ݢ3Ѿ7oMg;ɷS|~V^7w;x+1tRG8a R}ܸ7DxAs,n̩8x6WH*ՙ1cm X3q֡FD3ZP{2ʃcʻ^uBG*[?a8Bd ðVI-'qĉ}Ds=wĉm۶]wu{GO## {098SZ\F%i87Ha% kdq>Rx}mYǏk1Ry!ˡk/c 3( 2EHUJtM?^ FbU.iAz*Wٱ-Ajkr[d D $EJcZVvjw1㥯/뒓E&(Bpyt4)H8}Evi6Uyoj B@gVq\V0aY9F)z=9XX?.īQUOiV,XZZe5R\w ? A\n8,?9kgv 0p'?Jy^[pA;gnmg?Pae#wkBPUK]w9s2|"B*R6ITGq[3m;JkGK[fxPjx ~Ծ^\mYPِBa?So8oW{E8g.)R1Y@UE)Ze6Сo}?s9n#\:\tk@j\ yN|Q`:|#GDQĩ[gϞkffffllbeQ&VakC8?6X49V,r\-] weyvY,3Z%0qehXԱq剉ZJݢ\9]+ E_W^E2MW9^xcXbWZ`.A>.<AA!]"Rl*T2Y[Cu]׭VJ$I:.;5 Av5o0,0+I 3FoeΒRMYdl6fClp~[qL!,x:+zR"r V`"Jn Uם+9]wWS 6i/;IN<%47gq/#0S+up`?Swx$Tkѱg4C6~뻳g~ܠd;]/~WH|f_}旚YOU5Um+1`1=w[;˟7;k_3ƤYƥ2ei%3jJm^lO9rRrW Gx)J[v=|k17(-J̠v6)?'Nu?IۡHr۷7RSd%íe4jd1a<%0O6 pw+W9aƕNeDnа=T[a/8I }6L-ǘ5*LWF%ޕ)m_.kZX[[[YYY]]%3.v'1a%اC$th M ӷ"/r;a<ͧ+sxͯCihk"[˒N5;V0azkK\q|c|"ZZ[\o߫K 8;g)<dUFVMTnϲL:z}_Rs\fiqkGfpޓXV)KQRJU(k[ &&t:6|f~8^&(c\rY}|O/,g s]ժpk14ѽ?Ym >-d.J#m2i&gϖvLa;,TKt3Px [`mV݅gӏ9]Zk7,1Moz' Yc{Ios_<0B/o 03$H"1"ɒDelW)ǩrJRvJR%\?bGJbVd˖DIDA 2{w~Kwqw0sX޹o9CtGgZ( ]c$tQY:/f $AHdfk=xzkh((jS˜ZSY5 ݋Z|00;G v1_M 2& h ,zg%T Ud7?⦻6 %9l sGXD;X0)a|CwI.F8!l"A DY6u( BhxQu~z[ +8(uqMQVY+5j_xg}6$Iky.XYY9y'666RJ5 ٠ 3]]x0lZNq1&Mӝd2]CP ,u.t6,cvzM_B8ŹLJ\ZZ|1 ?wέz77M<.'je#nxr,NjzCZt $IR]*?xkFU8h8ʲ,x,'˗/Ժx-h><RJl9RI/~}:~x-F\JᏃҥK33h4;ptTΐ˺YHqY<.(dIdBP CW{gv[~zcҸ+>rƇb& K0plve[avɄ<#\e+ :UuͅtU`a}g]xZbs4ޠB_իyozǏ@d X+j51QREBB())wRRHPY6mKH nQHZrQTkd* u<}.=YFւ% PFXLiZSwuڑe D,ƔX+R*!r >/^Wůe_~qctOgFČ )h!du(RkR/|`%>ZU Qc$Kc`6OڍG"X ZC~L>([]"7JC&i=fV̉>/2A6p'2Ѻ0H*YqZַ~{~[CTjkKD hC ET,Ԗe D$PDZ=~3'ݰ]puxQ_RRX; W0pb*fyyy}}\_sqa94 HT2w/ZlƼbwwpN;`|s|U7:I2fpEMTsxl szW'íV$ Vuu᝛;ϝC qZpYu1k4,\1$2pI-vUZ~,2|@^|H^z+++^h0n/ܳU0\YYqԮ~'xuGלã0xԊ_q< {+KcA+b`zjիWO,rhq("\0/tü8w`%ʁǰV^EGhhK*\WjO׿kkwDQ]}0TyRRD. F AL BK@s[ȮTzel.~C/[cB֘\YFyN!;p X̸Wx[HNexs]E*!"<\$U5=i=kM#Xyx(@ٲ@7e[&jwYKd5B#3tn'Z#l̘i~KнOHQhǍ%v03C DMDM"@欤R*ډf#{7 kBk}5ZPcfʢ$c ({?›=W7}[kHd=Bwq`0rJ KLsyyyccsuRq*5$O21+jZZͿ,1ש; ]5ņůٍvKZ b%U1ςNx|L7D w)kkkeY3ǜCQW^e(8s y]]Ԃ~Ms~9  ^pSCbZ},e0& qt.(jZhu$Ix (awegΜf4c#ܛ<<A9d^~U.;2*#k@2-yyj(+I a~V'v#pwqŠugeLkp(8nd!C\Q0.}S.!JH% d $!J%"@J)Bdd,-I_f X@ -l_>;G7_^j=UA-BJ!EAE J lioV)?x|vGǜLlP4FQUF1%c*8n"bYư00Rqse3ͷ{'Owp\B!?w 4!XkXskŏDTB(fy>B $a= |_tfAqXK ѳ:nL@FaVh2Tacӱ0s9@pmnI FTĈYWه>VJ1HHBIK]X%;Xc_ygݰ]p^]ւ-8:>wBanI1ªh4 IDAT5,^[[[ZZbwilUbNERao3 p8Ed|k-_T%7:.C q;c0`J+96E"L+RLE1Msf%}adcyQ/.҅_Bz+)zuGwXE(E^MrŎΊ*op:Hp!Ɋ%1|Fi2x P1Q5յU9?p?/} &x(x0|p3NM5q;xr[kU{{{;u-<>ˍFÝ]JvQ3w٦up?)~xhajܬ>ub+H")J$d d0A@JY)JD(M1BJRg?WeHDe9?[< %/!#h#!gimMVR|2*45ZD`4x>V#_I@Z@\?C4 8nA crLk5 !@ %zX; 5ܺ' 5[}?OZ !'iDEQb)ԘR)Paa}ocBD+/c0aZؙYft\ Z=ysv_;Z7ƔܛU"ԖoR D$ֻ9"0*CXeIh]X"DB ( XOMvþ?v­W80u$Rz(zJi%L4e.u8 2vk|h :8M<:G)v7yi áZ~e% \l6]Aì˜1&㭭~IJ{Ʊ#7Z/,+yC'>ycn^IJ@ZcyQzg{4؝8΅իO8TR:`kkk4=Y-ÏcU;xx猢h{{ :jbjyyK)]kh4xLզ؇#M/w?0޻d 4") HV { ;̙jm!Wo1JL/B 'xeu֏o0v_+;(Xkh  cP3IJ}/`zGJgh;2eV͂X"T$i)i]0#aM#A T& k??BoZiH+} ƫEkTQF_$9%ctk1]y7T@m~5@\%YCւ ZT dv2lbWD1m+F\U')4MgYЩA:,Sh4RTU-,: 3'vZn9#bh9_xsa#Ǘf;78R"B=4x<{f;q<lƘ<AV{'x,˿;A]`0>O`0h6>?U!]kbl%I[`Tq? 9<o;I,P(UHȕ0©!"D'>]uSw"O""#XųPp3DLp%nwpedL&ȅReAH0zlSYȬt^( ˀ4XcJcLOb$MDkQ|q#Iqܰ J ~Q*`#K"JrzkY )__.ȟ1V[Dp",?5OœL Pp-D `Qt)locti,lדf:AX>~r{3&GZG(gХTuEw {߁J }!H@ kҨX`-Cu΃oOƛ8w7솽~NLBa pX;9 xVp$ Yz<cpKqyP&8$H ਼br{oA0 >k+C-3f*"ߙp-QQtǁ|8a-]uuY;"r[C|k~;zYS ];G}q_N-ryy =^{t-?R+\W':Fרգ(XI% <+hvwvƷw2:uѣ5 W3$ }w}>%=R7˭(cJUJochi6ʗdzƜ<8^O&pq, p &.{kЈIJ%QIRYZ - ɬH'":sc-Ln_RHGbs.'mP,QKzxFxP3}AI$KRaX;n;BDDR" DXa.|64KRF`KǍ0I +(3gBJ۟u>A hƒQ'BO;TP=zDȤ_p aaFP*:(fy`!VYY6;sJIk@@lժl}3O|X(@6vr&s0cDp,[[lua{rIDU@H 0Y}yϻC^xJH$VV*+EMiZ I<7솽nNZX%qZe.0p1W^y~رc^O#/W \Zs#hb'd'1­ڶ:^pilՄ! ^{șZhNɍw̓p<=CTv!ŋ92 +CJ+ י>ԮV셅;Loih:x9&ylOZ8_7.E-Eg\zLgf3>F#ZcD$I]YYY]]z+ZOGp0ŠzBZ,[j~nP,wZ:fVoQqA B7Cz l\ZmOg]?֨3oy3p&eϞhnn/9DIBTVI*AR' G$֊X)DFcqNw~GCXq}jKS,0 ˽/lfϭηHB@`G:6Oiw, )YT A@Z\ .6+JP1Ea|ID 2Y6Bq+ B۰lVaN>ƭGO'ЙzMrNw~9$CV[032"`e|@E1 NJEJ܃K,ႮR֎RJ@ 1$QHg;KX*A BD'O[ L:2-.Yq`Q#tpV2'wo秄 #p,Z $m aǮS->aUs=$-`Q]rPq8#Z#GpP"UT» \*:$K?|kbĥ 8T}LUkiNk@Ddg/s[5|j'(¡L&c64Kl7ϻ.!D2 F3ᲆ*[|hqͧfK ^e :O3r1 >K \`6 i:N)`ht怖dLs. NuS ;{9vj4?3DPh4n _guK;[24f) BHQւ β99mɲl+eR@!YPDH)ոww%KLj.BP_mJİL-Q]D BI h3َmMf`ALrΧ/) TOf^D@hHD E\7j)zXw|1;jֿzv z^Vy'Ξ== ٿ6Y:a1.c+3sςz3Qe9H0 jED !315ߛ&+89asܡEUKnlllnnonwyyT?.%fYVdv>pP,nfD : _ 5c$~3Iun˲dA&t:M-?_t"FO1 '~ZKd(@'O|V|ssxƍ<PJ}_u 4Aۼ,o[YGDcFZ( "JDDJJIk=#sVMlT[SsYuŠRWvȑ?ӮQUy gGg}ƽv#@4Y ^tɉX݉*YTRٌaw rVƫ3*'Т\E||Y5 "?}WBB@@) $YaҒ=o175H[!2=\V0hIZ,B}P$ d mFG~owӹ@`ȏ_=nLE yH%UGDA`xno:_YVh]Y4"J_~̕  h538n4]VZ4\b(2[BBH+act P $ s^` !  vî{Nl>,3Rd >.--q+r,9iy-lLa4";z'jũxxЯ^*#{!N1 a8 '^Qrk6k,p;3=>s/__=G S؇G)pk? ұx(գkZ0 #.:r 5t5ۘe(&h5saPJqtl*U|p-&NCz*+WC\P#ϯpեKq8YUL[bPlT.ɡ2;'x8cMҘoǠP `A p7[ ,!@EW4y ]K!&E#Ja*,7Lh4y1MiM(LTGA Q*@Dky b< Ra|Bɓt6TeYSj@e>WkVٹgI O~ւ}W>f";Og6&ZPj?QTJ듒dq٪(j/# )fsy8ܜ"Mc,sAJc!(ɔ%B'x|߻SO32:7XPy Sm`llTy|jwMicV/!P k?q1>0"(,"@J>=godްznC#3ea-v#8 㭭WEAD Zbűl%E26?0=c-"b- w؃:\3`J-WJqO`ç`4 'sz+8ld2aVQ{{{{{{lnF^y.8 Z/%p~m/n&%2ǭ&_ Ȅ]bGDZzz%:ժ[t@p'=x!D@B@Dd,!+Zc?$CB FqYkhNzZZe̮]Q Y6j}zK& >ӟ+Ӻ\) %a`~~6R8k5֔ư5DD(I)P$0Lt8o5Kpȝq\׺$2|B^:qKK7Ad:/ [u qH !D}}^{y1h %Hxe!(j.: U RrRY%!tEJJuv?@&E!)c= .fjcg,!*ɣ'/,-5Ut IDAT-AF0!"[sy<(o`ġI)p/:@(L0<ϝMw_+Y (!C(wGޔ9a7[nJ-H:j*pMr4]|y45T?u ~xھ7 Ðv9r.g]KC/Gwjz.0]'tB<ի2r|<' 'WM/_okսXH![:w&`AG%.bt='27frAVprJ%udTҔdvJ~ޠ'+2j4n$HgO_sqm1m 4d,v-D(Xi;Z'M4ei"ƒ)QH! " "n>vl (r:F!@N)q<{Nyu$vȑl9ϟ?&jRG"B'=/ғGDξesNu'a|TǼ_-*Z}h8W~I)c^n kP@  F@"AQD B"2XF\{呠 ȠJb2tn ~JJ1@[3,_ȯ6.@+ݕW?|2~:Y aAwMD V}Bڼ,s"#* .QJA4_ ~6 kL9lzǎZeؖژHTPd14oCL&ýO:w״O)3$lRB|.EQRMT*'[[OD.Z$I?^ӝΆR1|>*Y4SY| sRBP8Mdzٰ8E5$Odw]켖fJkh_skE'-<[`h{ZHעbi([yXهvRIb}e$*#AK уFݰp`;'YvlZ,Y5 {ٳɄ<wqzu^,^Iw=2׌@DbvEDݭ8 c*nq~KwV/l4ylnn^p.rfQU>b;r_ϕ+WkJ}J|͚6.#OcݵΡ/P 5^aۇNT k , +A#`)uf [\BZ!@+P 0`"\W.k5:VK(2u7MC` Ɣ[,z âiՓtTW_,Z,/Bj+rR/p3 `L&p8Oj5pTCnw}} u@uĬO~9/3_sܘ>5Ƥix"!D a_ Zh rSvZcsmeER TAT B mL4nERV^5id`!by<0/4… isP7O9.\`䍍 !ĉ'_|ѝB̎ƪ,_D)bPLVPxxDGpz?PoЇ$)% h[د@l[4/Q1fãæҢ @`LAD!R!3/ˌȆa<Y<5kyYIy!Y@[A@>v]KǺ;?Y$ڝb f@Ouw4(WxFz)g i`pHE:Ip۝N[[[aVgH> W%IREy3R ; :i.r]!+Vt:ܜ܉'ƧpZEQpt:i2@ª`q^[[;r wJi-_ T4<0Rӟ!bN(o|WU#@ +^so4 HpnI8mKk%2( H"<@KKϊ/Oʕ܂SPnhtGXB]PPU|uh ,/v L#uRIHH34Σ&o_"j5K~ 2c5B@$"cʲѤ/}1xlßC'RB(KkMYޛ H b>E '``EC5 @ q\YIZIc@Ƶ(ɣ2`@D8aYrv%=5NOByEi@̑n׏_ 6׸U}4O8[.BW[mA24{$q+ӭmʝԝz>-D$\mC DBQd ȴHaJA%E!P*a-a@E,teT6OMͥ #r+ [BdH SBj[9&ֳrv!H){Sż7Lakβ Zdj R(Hk1w ,)uڭt-hٲ}{/8G,ƿ~~nܺ?߉{J-^\mpl1$ D4_XnKYGUk^Շ,ˏN=n6<@D*ź:_t6aZ"׮D꓏|)L'8EdHABb>j41w/INZ/\: (#@] 3ښ޺+S}n<(TD \V"kRX$ Q1yFP%kT@  ?y}=؜ް- |O7,qީZs9Zl渗nv~mUZ͇bȝqk| rƘak$U8}݂'9Dž]Fn/,E$xcؘcIݝL&~ߡ7 ,þw[{~tlX9`b"HTE1H[)iSr;%L!J4$t $0fkfʹz&':^T׫w̼ywsBݎscμ Ye.ԭv=k+n:UZ \ovbdpPk)V^]^(]YYYYYŘsȜ}e\MI$\C7g?h 44vo v;c~˟GGZI/hB0E\EL,1iH٠r= =8<<ݶݫ$,#z 4ldE6Jrn>G膝)se577qŃM9LUyU.DR$%>uVq{8wZiCL1s˞.2PO$IrvSm)n91. y{͇ \"a'&3ք yV !FJDD&Fkʋ“ RxJ_TaP*HdeI-4{Xf2T"ޅPnxnq@Z0@a(Mscm"xN|:[2Ѓ+A2f(l!+s f#R}澈Gv9a0TN], ӧOf fp{b n_7KumIn m٨z_YΧtlT*b2v\LkSZ\%ގQ[ Z}ժ[A`U&(.>J؞Pw|q3دLLݔW2f4L&Zk,KT,z @bq@$S5sFu#k[M4O,(b/#\Y׮ o*R~ɨ0to+,탛iO+:l*J9'z7v+f-,--oll]ϓH%qW`3jV962?P)cYOipGp0Gv^=v'Hh;T!ƒ -V @#H&%RT(%ܝ*k t4o%`A#r=[$ ~?̷M o`-[`!>o=ȖZJ*R7" l51HIhx ?w]iO<"]Jm\X~]t߹vV"$hG+BD;^T<ߌ:jr c!!8u1ډ||h[1yřhfz7煾_ɲ9c4`5V,U$|JBF{d5)XèGZ`A>ϝz;vǾs{]neq1yq4,{yF";rlK%"Nʼ|SRUqpޕ) X0Pn6RޮSeޏG#+.eʋGRk֚.Udj;;;Ѩns${c-01͙9+qs&(,k5MZr6*iiic$I4ޝ.mkYSM'+_FkY[D"6'hfM ܵ@!5F ϋq:v.J 1 ~$,d2&`웷m *H@JB '/Um_xMEtl?]#غZlIaBM]y_]s8o .tHsD7‰O5/_L12WL&UՑDP'[VC vi$I^VqE^U^;) %Dvw榤eKi?%GJq+naR ore1mC:܅i1I)!+ҴjHO]{iZ;t"wz()3Z,xPN4E@aO?(:G bJݠ]&y":-*a- Lt kh( ;v{S}mН =_cD|8,r*rSvL5QD͊.>VȒ! HԤ}qe2 8 >y쉅BxD3s AS f`&UFy!`pɊ"dɪ G =Pۗu@ d(e{էSK6")j '"!nYd]it]9P?c&85VޫM~Go93[F"ƍƼ_`k<&PyBϋPZk P 8)2d޷K/-oR=lVQ @YRBy S "*W9FIn_4uWh/r#0tn/j}Ww2{N1 Cn[(f:Iixd%WU>bh  []<Oz \B%Hh.UBYAUߏ$)to{N;7*YC>bpEi!V LWɇ}{OrvlK}vZS^cZFx7JJr ))o;8n6 VcP#rۮ7 F.$,Bސ"*4Т=n'+F_:^p^9k|`5 /mz^i^'ֵazVmDDri{|'t vdr\$" MUE*BxBLk3 4=W\B·{s/мCMR Bo{Tp1}0r xr-3Xx~$>꾋*+lĬZ8NZ>J|Z; :ӨmۻbR ) YmrShH"B(DP) :^102_?E;>*yߏRR.SPЮkZi WD}T]]zSbVo(+~w}uk:&Zs$&Zk+ n}4 i!A"2^ˆͪ |d5q{ { XJ)V5 ~T4N 稫Pr1ht…sqRRٳg޽{Y`a6@|<_]#c>7JfWdRV Ӑ+H`_?*_SQ*b]Af3Awr޺ԝny>Z۽x*FbJ́c*s(֍Fy.b2_-웬iK2qx3rp+V533jQN`l)17rggg0ch,3y#G9`~~^J܂r .uL{:ĨJBN|c'HZ_ٻX_FmXzf3cQ2׆\\5zb$opNfrX$Z_%GzakjEQ5<=TYJMܠж0fz>yF:Վ!يA rM-`|c帬[aZl<>eƬL(& ncqq6iefsEQ\p͗iy^{i`2uiG8O)Ŝ.y]Gly32,o &9n(.AI=JMa22YHdM ;&3ŵ$HR ?HITk}Ad˻u[}9g* ER %v‚*D'D8srTMHn/=|p':G?ty<bPZհ#l2W4&+Td"[ARKf{rq[;ӈ 3] d: x.{O|r/o5w3^WpEg3 ^If,UcrRK?ox pc܋5\ɔ #512H)°y)=)eQd,\ ӿ8< Fhk(ܬ<ѣy-H>f}5LGjgوz7Ak=OOO\Qt z4ԗ,# ðaNtYkɀ,5k M$FJ-<:};`$+ݱ;]ioU5PfR6/aL&6*Wotd+52헡K"bQpB\IUgJ{yBwޅ'|/+{|79s : Sc]-ƍ. JT<ac'{PRJ<$#!QjEX( Z+B"τ"(@8|bڌo6RfƳNJ(@>y3yû#9Q<E:͚,Ft:px<޵])1wT-G4QJ)X4MVmQqSJv),}*,$2 /O>B #*$a`F'U JB+raQLlR} GV,ƞxo{᭑&V׶Zv}kFj6FPz ^_mCD%o oy Y|Q 8Ǘ3DH+d?S k\7&ghsk5;lm2[q^7ѿC".=gfl\ŗzE]$?g}S 2ٙ_!74Kؒ:}XU77Jw^oi R6\“gDb1g> Xk r MM<H@T6+!^J0^>KoyL7duMm~'Dtm\i=r:u@T3ax=۲#뭄tP +#*S~5k5&a$/v{65N$<[-4 *ʊfP’(z{8un2]\o%?YUe[k={d2qp0&aޞ[X>,#@ܹ۫ N7- pGF6s,s\wTr|nj ÐY,﹜Åqv:b\f -*w7 ZZvL=u溨?ZL0шz}uuĉceUYGQTv.lRSN"ګwVyO} PBS4-rqєz!ǂTiUo7Nz{x~h#Dy+3Z Xڂ3:GgV.*5ƄM)IJoj cWӴKeV,d.候cu y9W ĴA2ݠ w!.^D4;;˨,\NSp2YލWO޷崩}Ze \ :.PLsȊJ! CdQBA-H@AfBW76/k <-Q0ܒ4^$M |\hm{']r[;Eas:3v!038MG 1jcGdDլEoq )Ż/E!! @)px"b ihO;lt$Wgijk-xJusI<}'9Ez2ԥlyGQ+|ɸ&!kC{{i2 lcبމ9c:QFmUC`X9/]_#YR5]V2!M=.?6 (`c?U! 0:-ـ`.XF_|E;3]s)&'s31UE3 (fź'rM"iYwr&; =/LaA&&V[ z2[FSuN)_KF,5mi'P^hIA;vǾʶ+ʏ^X6W#|<}ֿ,V pvbdrGp ءF4M]N(uqbnE̱[|\b\r{HJ4%sc؟ (ܸ?ӂ4FkJ)8>R{G[8va-(c-YWq+ևdV*2GPJu:Ç wBXK^ow\@ 7X}wp)ṃISk1Q+Y5  `X?SOfb#Oy/Lßlh/vz"(JJBDtn{=<@h֚b_qWՑ@&&O#_ Dw)$B'E~705qfE^$k ٮp) M@mG QtwS?v~<E""RRFa< nIYțٯ,yN4NSn9`.qh4*KK0%,9 8_h4mnn}n":_EkQ<Ϲ%<>JXˍvW3iVlro1:X ^ՌX+]X?~NN+pXˑZ΁.#gVՇzAnLg]wuZ.qYvRj.]ZXXf+WneO,1(!<3F3i7f,a8ʳ 5 0C$d" Qm{'d[g4WT]?Ah4VVVşqcVԕ+w !$fJFjsJ)Xakc92Kes>a9浴ӺD2PseV3Ŕ U%I5-@\. IDATGJ)=uyW DABNhB] RA,Zav[+I N"ȃx;mZdI\I[[z7;BQꞶZBBj]`#5J0np-Jbtܓ~Zpޕi7ZpPpv(cf{fd;%#Lߚ|dnjVQdYO&]cTs?A. |{Ù*v H6Q6vtomMͫ7B@[fmk0`<<'g  1OeѿFբDS?#1 v,S/%Z>Ӡ8^da>SϊAUJkp ^Zp?_q?r XkR%j-t%-iF El艶ZEX"ڀ@ q燏߉Pq77c W=}SZ3$q-nrnF2a,'M}-!T;w$r!7>xmJ3nn\Űe9obiC `{{{{{a-^ϘpkmsѣG*4N=2ZшYsN! (T "H6mbZ)49M/mF b7m|ٓy`9o =$ k&z>^{-z64IQ ήSb拋K ׎TG3d-IZE$P۸ƇwFM J ؿ\5P~h(*BԄlH3gNؾ8l~ɸSOεIc>I~Fl+-S{Oa?6\:=Ϯ ꈽEN&}^^7Z58#}>RAP-f2 0T_D8 t`z䷿lloddlwuYz] ],<~_KqQY69H(P5~?! 0>u~ϡ.^|׻eB eH1doχ3dU$d4 AR@ؓ/[˄T$ ߼w[.X~d˛ˍ.8!MS~򟻰kQZ@lnI(;K+p$F~3/=m?wZ VE%5$`A#<>˒. ́<Y@lɍMJI6 hL/}6 ~T}<)O6$_!h\=nv,ۻw wVpXc8i3.l5_>1 {jq/4UAi5vzWWa2vԼt'\޴x97wt8+Ӆw'rk>+*HriE('o^fNhv沷83ȀBZB"'I9~^:iq(f9PǾOp$eVO؏0MhHCׂK՗*4T;Ǐr\xHO_ Hh_ИÅH?NSaVN^9p {vVc}nz=Mǀv7M'BșkgF$ ۈ yd$6z(Oϟa GW?_߮=v{?FJْ*#D'q I;B[/. zvD\v/gM_Kb}p^Bdgw袮J%l ? W|}<N{ӣʎm'aEkFQƭ2zgEdd0[Ur&#OQ'Mx$HZ`HſS;C-S"^-Xd$!|BK_\{cۘ#*afп,>WYBry.X

*)<2$s;&"y;>ݕ[/{ob $sM'BY)%3?zٳ['qSQfcǪjөVLul2\~=MFͽ F>ѨFv ;C8\YGL쇹 <(k(b)'ԕqq_ZehꠣUrL2•fX3fEدej ~ ;v*yBDzo~WWW84NvQ\9)V_H$?yjq5E2'%zׯ;vlffc n9 [-> SjrV: %Uza8ƪBH!'REAЮVQ7n6AHmӗJR-A7;ڐM. zyo<LL.㹹9aVF#>pB)U""RyD;~1D<%$ĥi-յo#/xPj á-i`ěq+ .pWpQ8SigSs\5\j]m$!$2 mf r> +F1"A+0hj 0j aȲ8&ZZsWs6P]ݷK?(a@n3kh=;E6=gyt!u,P o]_t5`_!na*BdU{(K7FݧT̎6_l}mrizzlD`~SqN"::3zpF7kֵ~zL}"hm1N_^V|?)N=_4!"!@ qwtb;}mѿ)a9gsY4&B(jE&&]$?jIuh5uye88}syh"ݎ;Hըvf6Zoe$bݡXd/^m܇ Pda5 ;oAk1\4Šb"]g,buTM1 P51HB/W빐#*N{w5ܢRu ^){J]&]NY®K a9eGyvڵFO׸&h#o)Zív͹&"Zz=cꛛUZ-GTokxte*#'OrnWm:ಉB+uSu=n\.gn87\2B\61r.+uhbCL5Bx`r8(cFqر+++,짺E2IfcyFz꩷ /Oa$I̹u]# iN_}ݚLh `ffp+Mj7c?O(j(n80?vTkQ3Ja  ߯U3Eerwn֯^kך3i6Rx;Mlsz1ULț*WQOa2LyU0_B() ȁ+?\a5L 7c0&ƚB`g>Dmũϙc\28V-{ 0[nd<=L·*m*-*X/Fڳ,xVWWG>?-1駟~G^zFK^O6J֗Ddvm(u.} mnShe9*Ys)٣i🈱7XȢ@hjL  hu"]CȈ<.]49F+Wۍ\p%XΑ O:SZj[+/hDžM`` 8N<;T_nj`hUAH)άc)7?ܔan *>9&ՉRiʳlEAe}"T3/;0f#Ϯ6fk8ZBtfDO_`ΜMqznU.<~vYt~;yZ8^&yit`G:FXǮ$1 P@=ۙ?Ek^v]u\7'Vf΋j(d'`B,)@Ĺ:3:5&,ܼ2HW~_:NT ^:R{ۑ_fD<6F}}a9A2t~bSbyS4x~7g\2`Qg`10@ `րɊ9AvhuϫyeCT  v+^m-]; Py@TN#3LR˸ey6w9~iE2(OW6^J)%eRZe]#=-Ȥ63:}"`LZ^6=^D(t9ЇZ֟~_xycLٜðEf2 6F!,.y'0`C۰mCԄdbyZQE\n'ٗ=& o][[#7H8䜷ZǏs=333$B1XZٞB$I8@l.[ Z^izAJ $QYXUfq\ڭ<;;Kʠ c#q289 .ÎFkژԦ^%*H^u9L4.>ޠtVXt3Ʌ?CÌ "t[3.aA tͺK{mSUL9sHΡSc^3?}t/‡?WB5ReYfqv%YRqgqqS*$[˧MŊE ޖ5R043lR:/%")fr%sHi|z۱0&pӷP1FƸR)-k @ ¬KÍtA>+.4ueqʉr[kXi8X>TZ%\WH>1ށjYW\ӳ^؏/-EQ7IFZsˁܨ\iI:$2wG>GMey#ki$?Lܑ܈ﺂb40i IDAT_[=$Ç? L /ۗ(겁}t}.QZ^)a $MG'..^s_<$B+TU>ZS &fO\| swvzGj΍Vպแ;?2Fe0ϢY{_rq~Z~wºMƤSV[ȶ},qnǬ$O8Fv6Pdڤ5+P.*5+of 7a$J"Ejmt!M;)IzvMײ h8q{! JEjX&!/???jhв>ZzfymQ8>/SR |3XKPEǮ]荆~3!;pЇ`>Jјza>c Y6Z2sθR<`ד = a cV2-{' ƶ .idyx RNg4ZedUnY>V[=.TҨV61Fyj 00n6sRk۟ggff i_W/3fϞ099Iat7~NۺDYXRJ[A~EI_֏#\vl w:1P(Д,H' IJ2x-ke߆lVaV k4w >Sԭk.t6VX4'NTIE,a-R@XhW^{)p}M\YߗR۷)`,ybW7[tbA0ƾк:[JT8* `tn{}+UCƅPr|zh#Ն|8sXtqhm҆qaQdlƪ(  ."Ӓ҉5 M_ziWrW]x_cir3+iay>55^wm)\N,=UQ,}ڪT*0MS[TnSP+E#9g݀5f1:1~*}U!@C>V)Ͳ4  gԕ3 g~EgR%q*\c<pÝ4%K.;'{g^>P8g>xCq[7<A\7(i8fy)Oi3JC)16`e/3̛,:z(N; 0N܁WJ&.zA)Ds ѨThp Hβt8 M#ufbw.Vǝ8O:aC&Yt{.Zx+' •]Y9stS\~2O{/30jrAi^/׺H5|BmT'!%uuN0A1uat(4ˀ`6@5&6P[p|Yr>(Dlho-*$Fx(he X9oe_&Uʢw[(^/gt{Jrw|C.EEqcD.RFkAkBS]R0Q:H;vy6ڹ$8;[N;c>gqOEF1$<#w̗~~nH%mb'Bq(>k.jL`@M&$E+hH!6OHmʘ wxK6bR3s'.@٦Ze|/Dڜߦ刺 ~m3cu`>ԖR xu8qp"J4$ɰݾyu75Yiڦ7ru_MN:tZӝk{[wSsw8^r4n$PT\JRR)Mar(yK)] &*MS>bj8l/^/~/p3ؙ8>jxTUF!~-0 8cB!ZzZk]1AV7}-~Y{/Ƀ)d5ZIgɆc7Sy}(`M;LcO MwL 0C~ڛڻ<#MϟIɘI`\҈uO(~!0pz8q! n *We2e0}nYnjenYNnjM91=w[8T:eIRXQ`OUY#'پ8zhݾxO P0+_ `4/5V̉1:%ZwS:(~<Ν;@ŘZ^Dcnw~~~ݴrG"%nJf/#P(aU1޴oΟ??;;K:!Bae`0V]lGS_ _J}ژ7g`֚+p8c3?x>188?kt}z~c\Jr-SQ\R'SR 0I릣)&D)sԑLSwZv<9{}NbrLRo> z(vs*; y@h}<0xHI_ydv` ɭJew{;0!GVa .e:)S/Y0Ɛ8mN6ŶN6\wѮv hcIEryacJ.w^=w\Ya2wzHЪ%ǝ*Z#-IC5 2rO&ШM֠~#{7K;z(eT9ͧB4M+ qZqԊ"Zh$]zxb~b9RsIП/^}b|ؘ 8NjAm-< ǼQ( c81M=7q.C ' `΁zkIJB(GI"J#W3$#ܮ(&?\syzI?^ލEqۅrQl2Z<=۽{p8 uB*Sh*6m̈ 0I~謈ӅP^[໘˪_i uoL錷3|peZG;Z4F~ h@ɢvK+u=ZGA-kGg&MǿltnՍ w;z_5 ߾si:N;'vvuƹί:󻋋'/ fR_]7RYEIieZJRRY2F`%{e4\߿}ڙv GQ6.F۪)rc  . /8Q< 3痮/'f+z4 Y_L?ңt?\V.p>5l\d%2 `MƘ1ZjJ:gNv = DF'TF|Oɽ}[ˤ rl'ksV_M)nQ8FlRu8.//j5,_>Yj煢+,V)<vrAnPBR1aMRҶj ZH1G2Y{cRixUP6d#MDHӶ]iec'o~rob!JQWmE҈ C00,CéPJ<y /P.:EaXq1,n<y^766(atЛiu #@LH q bگ(oz;&k0psx;2 mbAǐ)3Sih>+I 4п{ Y9L*s ܱK= /~u[l̤=ӕS +{.Sh0%/έhv¥볇zzrmՍfEh ðMϫHI:ˢ8q/Y~%jx2ygIg}';gK3hFʆ:9=; q!9GQ7MGD,Se{3T<]t}lſ7_'wجv_?9+Itt~In/ŗReG6 0t'yc̲Wu T3|-h\Nբ{ce}aނtnٟݼpl7i;=Ͳlss3MS JQmJQJ 5aQZĊt8VKcaR>7Q4EmD@W0㾍=$Ӌ}j~E+ϛ!0 Bn{twјIA>u4JQ ,K|:3spzz_+˒^Rw=Yq a*B8Βף$]N;/}p9qhWk#JJ0Ce` .>f;iBڿڿ3-3-iO?["p )v"EK1rVl,*W=mih<yp0:<\ha'\D$J[|kVӷ2DqYpKئЀ%14t:-//8Xp?~WUR -73$Cfgge`IꢬEƘmy^Re%={{&RP(lE$#Ui1ZmaaafYcZ8ؗe l\ƙWܱwĬ1F)aHG?ϗMθa kuqgו1FVRD1p! %\ytEeqXxN1|44q4%Ɋ݁'N1\ 8XNn\.ɇ&}9tƒ`6o, =+ R\V%I= )YAgϞM_E]-{Dz=鮷c m{Z*bYq|B^q'߻FA0T#{& xLM8^rlwpKk?~Q''t1+.` F1 B]F7p`T:;{p0h_v{eyzf܎CfSW;DԌz=cLv:r毢`Kޢ>r?y1 '>^Gn," OqvcvEwm^bEFG޻`$12h1݂[a7/ܺƋ,5z"~fky}j蕋Fd! de{06EiPvck.5={X) C??lϟ/"x0`f}_.;,V hs*G|oDuYvU.Do&")íV^G_7WWW;N̠==X2 $; cdRgdml6g b<ω@ђ0Ewއzhvvz=<1\XzvoF! 98#FjkYTP#k­/^mt:uM1a#sΕʴ4JA4\x^(kfBiyMs~1$,P\~Ml8l'YbheѾr9dՑC(fYK+MM홛;KVb}ys+6y g>Ցk Nu0Pl丘\?>?ļƐi4t>W\šC-啚ܩ1"*B:;{)em;nY'l$,Ywʺ _D*֧*FauY9YQHISRA)5770րk׮gُp_Xp%qzALN:1?cqg8p U3SڦԔ`[]]={EQ,TVggg4z1jjkHBVv(\J,Jlncjlȳz.j5FzwG}tfffzz@A/UVm@{VVVi("b$MSVkA%4XQYOٱcGEW\g}VE/^XEʮ ðjA J]i"h677IZۤ2 뮻.ݺ9  4tDbM6MΖnm,ʄ닋VO˾R<6ZU}ݟ-vRaNJFMj\WiLiLiL6]tli`83?'g*9|!-Pz@hX瑎֭0ʡ1ⲙ3)0a cDc7";tɤIHRejmvc%U<ʑ8"  ޱ $yjTbbuU{-E -.mDw/,, BVGl{@OqFd3q(ʤw(|v'!=aM$0 ܤqB  Aʐb:ǤDȑsxgO\W1qA42'z(Ҙ@2j L2WBq|IaXw݊1nz t/k_q܊RkEzY$0QW*MW\)'URsة`?_~>Ly5`6urWW˙!B-YUJNpڗJXkvy۫}8&~/bȈ誙G?Xzi)rhqa Gn8c\Wn*snyps+'I4u MO/8L,gYn\gikY+vϻ=|?u8;RW `q%;UrW1F\7J 8g7/pz~pc dDR(aRe&10YtB2 b LA97=*@jkE/5FL*`Cb @Lj{ݲn^J,7C%Zmi2ZkʅJ X Փ F61RX_$:jvDFF$+Vu噙V5?{o#u ~b#̪Ŭ"Y$%R")K,$ZE4Gw{nOc  Fmɖ2ZQDZ$ŭ\+3{8qO݈,ŤBDw};9vww uʩ~QTEHΛR }ߎ%.g^2t٤=˗/=zZQ1ʅ PD:AK]xc'ڌ j.SN.Ȱ7ZFs&X΃Z-jqnKCy?Ro+{ V̺~Pk \_x^ʟ!?SfggXǨK~X4(zn" 2ER@9/Jŋ/o}l׏"4ƺ%R>r{قiea3Z~Xfbm3 -"PBH!nOEQgHr2F]1߂k*@ Pα /+!{2 @6B 4JI|C1;J)|ӤJKKKNYPa6rgZs@3XR5,]1ZWV! ]DXFJѹxW h6@ Sh 4摐Ȩ?~J4na<#VHkՇ4X )TBZa-ALYӬ7k^CXDP r ʤɁ}XGQ׮֊Ź#GRXXQG׉Zw W'~/T"N)ɤRBavz=D*%j)5)*R'Ql/^|\A45lmkJm ,l`4c# 0xR2uVN㄁9 @VB,A.j "JU  !wWLmcna{f$~B+Ðl6766vww@"p1ƥYW39$G _F,C(KJHu0$鋗=7@"hjZ&1j5?/d5$vSМۆhMl;϶Z~48}O8ĎV: RT>z*ѣGhJc3T1u\-ڀ gҙƮmwݮjf3HPb_0l>LeTJ!-y E1:6F[M5=ǀіx- qB:AKQ' FK"Y \BksOyj4mL|;"Pw$w:e9z}#FPJMMM---mooj5dxxZjBh4|Z#W:uVFg~croA7!Th);P6FԦZDY'* %]CfA !!; )dlcq'<_κU}eeEBV V(+ 22$aVX٬[#1-iJdA;" Ns;ɴ10҉ T/2^7^2o>}p]P.Ӆ0L*AՃ\ekŭ0B iP>tUA:/]|2zry!Ġ+B@cc.FjغX]{WVfg{ٕꥅY)p{]A9 kE@l9i8k'!FBwV Llbo5"cӦpߍ=h4Μ9O҃6.,,is/.C U%!c5yo @TiKlllj|>fRF{ꩧjZօ ^A7EZB]tO4tj]b*7b߈r_A!R>ٹty`/^xmQn.ڡ(51?/KDN|X$1 S[vtp?cJ[\\;իWI@_3MJyi9nql} dGua .P!kZ86fTZ _ IDAT!RGt`X1#$0P+M$sdq:Uh{6Q[ 2KVf CA.1I f4Q &jAKvb 5 F' ! Fv CCU+* c-?&F6 S.(;a)o2Q0 kZ,=w~JB{-x*_$Z7hqH& /_n6 j5 "i[q䒯7Ú,r]V \y Cs |){ ҝ.=w-EI>` 09-Bls@1@VA["s Q<:IWILZ H@J(#iD:d1:.ڰbRɊ$hc{&jQW:JeQ=a?NvA/z=zkh~y72n |~*L+a"9H76s 3! 6)վY^8W,/ zM155uﮮee /3x R !!n z $fR\,ECe]3tReND-@>oOlbop /n{ j+Wj5&Iڂf#ґH<+zN5 ߹>QYxA9x~)v Kto< z6y{U:?O 𼳯4ƘO0[hmh},ݮ~#4B "07č8LH$ Q3Q6'|C>V)fכz8/|P(PEŁ(k nd2Bb-N02H`>W:PN 3lm;>] TRI%e:A!^S_M3hVԲ6u24yB( :RJm&BP O줋,_x*JZk7MD^,sWhu&%󋋩t:UNE˫0F:;T\^XX;`qv)ER4|On:t:|s-DRXžh=xW0 -RsSS@%sY bH@ HH4#l` OԠӘjaWG 3PR A=CH%r d cXkvkD#8BtK J@c:Zava>[j:8FnB?}wHgdC:+Xk6c` jmlu /)iATk(=H8ttD(I-2x{UA<)h"H \H]ԧ󖆻E_RoZ1 *a6{,WWrJKw"- RK.5MJ;ǁ0wfĕH$xK7q_RʷVw;⚂*\cn8*(Pr)rG[tP >2Jϴ HPΦ/LhPjx,R!LikFv*DlT׃Je) Z"f0 ڃA\oէpM̷3g:s機・T)PB*8 J"q E* D:r3l*&, lq._]ý$ۺm4c#,E(+;Ў[4]EgC@ߢZtW7+Ħ&'%[{;>[{s% l<֮]z9)e6f," Q^5p9+o`DXڹ~{]d̪Q˫Z5\^1c{gtZ%vTz2d;wd2$A4w>3? h@xQ2-9tdJRP^[~Vvp\ELWT:}Rfvc-:)e&) $7/ p6{ݓ{Al6<0` HәOz}NWvCFRc+A_GvQcUZcED"C.3JPnjhkaez 2t c@M,Ǔ# %j5%e̓8=IN#4NиlB aX 8IuL :"<3Rt2A0Owyj' ŠRo.M~SַyY] YV@yhf$-C} #WbH9b/W56v~垕 !: |7gg 8%' 1t"2؉p\ l"DFHY 4a#RYJTΠEF@O} ߚd`jZ^bR~!J#G>}: mKD Plv+++L5Xx땁^jfffs{fS[f4ϟc֦iR;"zk+ TJ͵ ~M q@J !1BJkW/㵬!Xeь8D"KG`Q*i%(";6>jpx].֧.r@d&Xkbo[pZY K'WUejgϞt:HxyyDJ=qѯ*kxQFo2N׋zav#->~H$殻ښvzzmtZd~O cQEhF#qd2I-1%'+ א}D+]\Wp:cfgģzT*RBTa9_z:Jma4T*E+rl6d+Ncvz1b$OMM>}z~~nm~r j!):&@ {ʿв^'Fcffhvߡ,Gd2(R԰gעkT%r9@ X3*~=+:Ϡ $>{2Bjh7|S~7я>^'Fͽ]OtTC:~畧&cѼPKi?"{1G(DVQj(ˡۅ}GMj@$$/ɨkZvHB;&#GVVV((rס3ЄǏ.Y +A,2WK N{Zmzz/b -zіOmBr9Q%i,:u LK%YD w!y`TӖRH%6zk=J&ҹ F kM@ 0C:^ +!n )ݞB燍c/9xힴ=?ŃCnƳgϖ噙cǎ>vޘ^^Xx4s$_[[O]ȹWJt:}Ӂqc7>ͥNɃ:E\DpQJ!KoO2$ʋ3Ғ_yloc *2b-ڹhwCH ) Z@4_XT0Q,dF݉}oߜOۏöɔT@vA"R -4KϤ C Ekqcp(9U餂Z15_?_׻ӉMlnI/]d{G/8a-2r8*R+aTCl1W23?) Cr[_Y_Y.؋I?aE-vի')AUS_@avcFLD`ʅu:ZVV*ΓKI? .> j-Z\.J)syb4;\455u-LOO7n?5" @KӔshȳg޿}7jJcb6^FnQ[|v|>A*B@J #"k8n7M4dn4͵A Sx7F86qENifX1\&k )[ ӿ x^-}s-//SM`:>t )hssO_I\)˙$l<|tl<985??…N1-x9)j6Oc| 7hZ\F}=r9{-c k~-^qqh٬T*$BX˿[.gOgYjn۱1I L+)DR  ~;w֋[@@lI{r#q̄$+j5wma%vK(\k!tciiZ?+++(H(geNiuN?25[2 .e2ݡԍqΝ;w} oc|cGyرcZbTgCSSSTy{f NpڌR }z\{]}Wu>&JB+ha-l:DX  ZخwL~RVW>+tNL' *8Pf©Lͳ{+h؋G^t]m zI7o>[cl՘̯Jƾ]\[[#$Kn(w1Tkݍc`6'*GଘqEYE\!\gLU*4;^ lҰv'&IW] 56km&B(()e^jۥR J!~J!f|F DT%b v&q:kKV\>ydR!xBT͚'OF3Ԙo/;>ڋ3g9r\. t$Ɍ1&ƕƣ%v:  %D.<|ß:=2@[ 6J !ѱBXcL}Uo|ǴשԲZt5 F/l 9.^w{t%t5oZF Vj5ەJT)eG_,tSgRv߿ ys|F5H .vsw<_?ЕPXX7lDik gtkt:~!ܢAK_eG'+~]. "@]Ԭ)P}dV hcF )D$l]׾wrwզ3Q"dB@D SaP~c dv d*(ΏLlb{uۧpk8Td~V1k^?{,} Zk"OL1$nAOV9blNQΰG2=}3Ɛbސ+4>u!>[*ހk7:YAdY\ijKw$.2p?^EYqS{nKj{sss+k-U}rE9⒒D"A-΢$ǚm[ENEk*8pQTE~`0vDm,T*>0Nw2F^uN IDATi 巛7xOς3xr >C??_.,Kj PN/h /O-"0 3I|Z>?w}ؔ{Z7h'm`08~x>O?!wftVk EslB 1Y3z&k4HA:!nc y-(cb`PIwG` RX:_[}>HLBF&L@%]ehE {[B* lBpMlbjnuh.+𤢺FV:HT*~O>yP>q ~ j}E8m _{:-o~Ӹv:|i0PoC=w^܍a-IeP7%ƎTCFli>O=Ekþ5ŒSR<9\mBSE;#k- 5)T1hloo+ӀF&Oح}HQJ&WIHIVvWgŠ\.p JkU[|Q} 1LRpcZ|y=2aO lek5^e!IiY+-B/nX_ȕ;$-: 1]Y\ _ױڦU6P ZIFH <<~ 7x#Gfgg hM7p `MƜRvT (xKRҝbb?+pAg]eۉLX(zޡCO~򓄗|*Ix tbqvvŋ8θt0'nr9R7iy,P:%U?+ c>.bձ͘ 5 IW:fu"pGo_dTith >#]tYTٵOؾ 51V^XkI+֚ʿpUraRg>138JQ6Imi_ ?N>^\kfNB˄v.D.㘄 5 fOm/9Zϳ˸7{ހq&!dBg}s'gV)sXJ9??aS[WN#}ꩧ>яeN9ᓱg;&z5===55r >K6 Wg1>Rv(XRKB@j:vKk#7$D0G:thm4uJ$ UZct֦n ׮Hה}^/t~_ӿ曗bP,u!Κ PNB}vrЗ7!maP0bXf}q/{ Riffbٍ3g/mY,AFd4z0WD`1w7D[oy d5h0)][T&ad]@\ebHvCjh[~|ciGKӄ2 ?+"2=Y u&9t 3*&jTdN*|E0Q{I-?3YcaQ[,HȞ6hShR,//R)N[u>țQ!7eo2~) \:2$ 5 2W5VSg>vX-ݚlDt8V0ȉb9ZJJ'7;~y8`t,FX82qdvQӺ[:H͎ !!V;vL4'O\\\ 5J&BD,XԘ_;ʣ|ƫKW'_+LWv|~gg;xadzѡ鹹\.T*%\M/'DS>a_[[VTdБZM ]"5 lT}("k7yʊ@Pd8gI>q`bO;g[hp,INӖKRJɷxKV؍'9XYGQiݪ.*b~@Ld/_^__'M9J?=#$}U)z(zec6[owt5EdDCnHXX*l4<4+'_A7XN!g"._Lf߯\VD+g m"yF?cd]'V80\XX ހ>kbȺYa-R#i;;; sBׅ] Ɍ}jgk4RnKh:*oqڲ6TꝇZc16H︳,d`&:(қ֐bB7ҍ2frt=ztffHI.ef1Nfgu60-_\w'6+LOݾIfsqq뮻hw:$F\^sWѠă%*ߢ. cd|ܯeTI wװL|Mzh@!:0GQ~a3M'`p=ڢķiO|G&_o<|޷S^_^~ Ё'A/ncjyK~6u5l6K61eAU[$6׆@1&,..fY.oƨMOOA(+rnTLl6{|c`2csR|,7O;H Lg>~-,`a嬿 ٝ&=r R0} 76 k{ k "ĵ;P H)֢>ۖ+ 2kg Oƞ}MO{5.N¸9DbnCQ‡|/u`+ܺeub- n+\^^ˊ!͝?b.HPDe?~رŗӕ x͘ʛ 4,Ro <06r<֫=܉MoTVuN/Jb~YsLOOeJ<+zl6ᆵ}k[[[JZNon):tB.)ǃ8ΰOTkBiJD2K$SSDI!9Jc9;W[yn}sOJhW\pիW777į|X,8q~;?5bw56n<r)[2u>t:9k䣷Z^GITE٘GP:1f~so~B>?O=C.>UhPNh4jŋ:}H:~S{_YmmLK>Vs!'ʱuz,%ѱS?w{^Bt/0{-FY$$y|ѽ q].bWZ G9d{{;vj.NU p XЧ *\m s|dz:_ ʋq0mF_Og{kq ) /j\6+++N:p@R!J|}kjz~qsU9e4"rx-1BN W;i$[%98 û?xuDZ0 ggg"u#Jq@W.t> [`xU s. ^2F&av7F\>jHаݡ*;ytb}{ ./45hZvñl\.s~Pbk-eI)9=9ic1פ9p w>Ղ{A( vq8õ*˟|)^~ZJ$K-.BCq."k˞ӓWeW w0N+ `wwjU*pO)*6DM@"lRa `0έaƘd2I ڿguqJ+r'ec/nA3ƄTpςg2lOzFGh._TJ% .P}B:ĥ9ИgrtfZ Hg/pױ\E>kOiux@o>l]a-@B^+ۂ^R~? jFছn馛ggg3L&9FԸ^Rc&?/c* \1 >-/%0[(LӴ?kKΟ4, T)DK D>ꩧn5w ٍ ΞN^[j!O[~cF\“q17 ܂?f'9#.LN& Sc6 G?Z]]t:N&sssTjQN.2n^DD]{:6[:lå\.>}nzXk鴵G[iO}0k;wӓ\ -@FK;cn"Ia~N"(Bq&e>wz~ F 9N^kK((#HjHwϸήDcf'6Woxo,xޗ^zncN$ͱr0 J:sGWfT"4 cgwQbhH:zZZka뭭fpeeɓVܵT.FK|YLsV8+.W&2ҿpjt:?#_g4[ (L)\t:}o}[@d^xF4?h4T4E|$I9VE-vvva++DfاWϾ5[l |jb,hG5 677)W܂t:]T(NI“C$:RPRj0U¤Pt[b܇e^GcQwI8b{pb [>7wHE wQ.sp$$ŒJ,1LXѠqg- !c5s1ܘP(lmmQ\z|^C!Z;o,KHA W1bXT(0VÎ)2G\U8+ XlT0a?"L|R C0LR$-,cH36{!PR@kCv˖r:HeBͽGb߸Դ6æE:=(Rح'!tSN---URQڐi@:z<^u$.nt]1UEF.G5&["Nwaa#FPF@J(a2|t{ĎNOOWz<'M9H$$)pt"c~Z؏QInLlb&zb{Sm_-Q͔y'W?.K5&ؑ{#T9ho ykifggLMM&Qcwb4Ϛ|zFaurJZjqt'OAgrgߩ2 .N*KRTdG[T1c]^^0D}=\&! K'iFE{^jw娘$zR477wҥ5K[fT"VFPzѠB/.cܺ3q{o&6Mg5܂c'0aE}+T*BzS|•Rړ9+!DE\ɄbƤ(cc@FUm4>W)!TZoR 5r `aab}Zq#IP, 0^pL_\v820"eZǖ=-9NOXrT7 vww9u,>X2wh\:uY0d2Y*a KKK'ZT$l6*|/$`qEA~٬MtBs8Lc$kƺ|c|BI, L7QVB a։ &(2Z- [NLM[gƈ!e`CCEQˇ-`%԰ Mt4fEQ.;q+ 5&X©efcSdBȧx JӡKougZzi0Xbɓm^@X@@iDV Z#G/}#ʳsu"- ;;;OHCf^B5(3"v2ib&6Mln /K?}~V/\Co0bv%C)],lHb-.lz \" !̘')ΛSLH.#ǝFK~ٺb9*Zp"8R)icX&WQE>!D&)4ץJ;R)X^ ##^ifte;:eEbtH&b-W4ϝ;WV1%Q"@ kT] 2*eؿcS棭֚; Ce G&[EB^n#X,ru&| kA!.328w_K%-qxp)O,R IDAT^1w WGʟZύ{B60 쭷G>qfJejjjwwOeRf`Kzm4Nx?+ܡ} ^7Mlb>[~=066:766677yEQޏ>g=1B2,"o٨XF= %"$TLBYa!5q=pc{zArjuaaa{{Vi_z饣GV21%qCw]%JjbO,xH!Rr)O#""N q~MTVJ iW49H\.NgnqZIHPrwOۛFO*,wō$R$0dPږSR*SJeǚ<|ͽKRH+ڰYQV +IU[;+kZ~V>}ȑ#sss]v⨄g xAkNĕrzxTi':J,>d_VGP)V*"pϙ0QvH`7mE.'Q,z;ÎSrsss/^!FP|Bb)J-TiNGy&6>[lbh I 0P&Y&w}#EkRwGR= d)P^(avnSLkJdU2?}?w D@ Ry|=n_w:l;SS9m .?{o-q~YYu7 @HBI}98F%ٔ96g45}|3##YY43G %h1IQHERA{ڷ̌  XJA,btqQLl-la3v"-d-lx^Q.z=w?c!wBN^ȩmW՟:GDvEDeax R $̯=i/l(5ݞs"~{H) ! 'Ztse矩έ"Ac; [knDžBafLMG^_-,5^0? i沛 89xlF<ұFZ׫T*zbjXIXkcco$w $rcNCDby5C(R*"r V<=R=_s;Q;%ĕ~k|KYFȀ 9ѣGͧ}QqaA'VZYeSC;S,oU1%qvIDR<yZ;( xj-lav-LkcpbZ{ܹ~ ---ziy::!-3 aB?Y 5aך)_y6Zj;u$I"C_ʋ m"w&9k-=V>$퇵/jsN2Yk XZZ͒n!8B4IZ-В<4rMp`ϔ3#`&ڍ{9_|&˲??G s_x-+bm Y_K8Ĕp2ncBxQ<[Q1M44Đ5wq[eu厸PcbZRlBe pRw4D0Pj(t(RjH4MIf i2f0=!XZWYtEmA2 _>q{^/%X$h3Ih(4Nn'hԯ2cf}NR*8;˗x<&$ϴmQq=-la Ë[ |?OzMrxnyQ58rf0WCfo_o׌9d0T*;A̬l7, kbt_9Q$'dT XyR:Eڿ; </p4-& p4Y u>2P• oxm'qyF8Z^994%"g)$O2 ܕ"<$7.30٫ Q #CըXzTLGYZ:;N{t-œΈsX.:V ;_d\ZAv. <+++KKKJ#R'9C&Y#`"uHRiQLpC!,ht]*[455&9_dy25֎==2N0H5HVRz:F˶y1[@G;>_ZoAH0z9-la ZB/΀+2Bbn6,l](jf!IZfBb.^ o?r=kv[B$ M_b)(e!WªHw)iGOM1|9<}邓;17J(/#`Z@%5*vL5_6 G;o59:rL^l{ 4uQ3 kmZ5Όpz^ǔŊ ZSܰgy$O&[3epz^ժ*MrY˞˖q1M-˄ӆK20BMcT^"X@,"o\,ͨϙP ZQE!wJh!ԗ P/I+ߚֺ\./-----=cLr 'p2K[qőp&W|fa [li0<2JtL7*h4:{&j9>B|r@?ύF#`rm3%Lu˧$FT*&)Ҍ2Jv"zOz " ʹ< {=>P/xKx=e 9#P'dB'0;DL''Ϟ˵ZX 1… 'NjRf.LRpz/&:ax/۪ؔP2A_FJXFq5Y.kzI<"ƹƔki> x*hlc(~IC bQ,%|qYk{ŋ$PBR90 OfVk6 "D1}0ؐoif"$Uq̬kS>ɍnT򬉌jE:VQ¯ķ.r%b4K1O( ?;`07qM7]x4r -la nISws28 r%* b-  (?S*ȧ-#! % N̓y>ECmdbyW7|ygHɤCߘht E[qZ >x9/#px 7PVZ/U8㯰yQ;R3⓳x%hxO}fhX ? < Bݑ11k. }vi%sqtB:9x,:T׹LmI5=J(S\Bګ6=%r9"FF,Xb j};DR}FEn)&pKye峨Qj3N6ybX<=AnͲ]is6qN;70sKzR0FRPnrKt:[. /39C zNgccc3‰[S[X_jߍ+|:rJ;9]{c"CℓkM9 ]iDZ۶*>%n?zh');N'(³[8\ˆj׾-la [׬]pKXSj}JeaE hM̟=3`'WYEtHoeNF3-QkiJ!:JjJ( %_A*M;,Kn9B~jӊL.d0G}`KDS.JR3kVDRè/_ a}GZda)O1"VEzEXڐh)JYiCM: .s66 ADA IT,KX=+d%*[ZZj$ZR3-laҮS5Ո%Pq23Z3 iJ1683-?RwIS@M,xSֱ|׶?QYBWn?'rر{mudl}Ro_Zvj9v.EYmD2}4 ȡ"L(m 5S%~N(c59GrUЯ =Y_ [" #ߎ`9X#ҐθWk-5P*RIHf>'Z;`j6fnS;d~iicXܮș@P+YF8%xG:urw@NߢMJߚy0.^ e…-la [ u ?i2mlllmmq dd4B/AOQa'@kML\ -FT bn_z.]Ox UVgdȩ Ð|AF[[[T ĉU.FV+9EN$D\qHrYԽ3P;)?vtH8/uȾ\7Bx/`no?q2pj6o)QC}=5ͽh4rUU|ӁWsn}Q # eP!B{ZFdlrK2Q~#CBN0 NAҳO v4c<88C2$+pgxK0'%taFH]zggguun#L=aEYvwvvaEgf*O^K>P4IIؐrfVU$cUJEQZzv1(h眳Ύm6lj%6Lʿ/a97'vM/"8ԖH‘[K~ |tUZ-la [5:[8V QA\PԤqu WhXZR/+cd~@ݦ}.DZ`U”.EQ[RU˅xhՙ-;0QXu~$%P9KtwwСCi|,sv'nA9imVlJ5!Rkd7͏IjCRHZ4h2X8@CRNbg]y(`=E_,+ -;#ҷ`0&ox n]k.la [¾[F\^{r\2@ͳWDn_өȢ9bcL\Z[THɪTUx$Isυa?YZpA9Zt9vIHF (@~xtsqe@Y`x%BM 0Bi|N|ɥȳ},rH[ ΄e^ .7E*_ш2AJ)jZJ(8ܺ?T }k#$IRݕVe1ۇҕ,J䞒@E6"s/r#;ctrEуԥ,|/0MML'BhX_I%\|xt[D:_Kv|f;87[!L3jA?hjhLjS|8;Fib.&W~6'L40Bi"]r(]sWt2ƃl6v/qЎWZMӴlz=✵:r5ECA%"IIb E0ZA~o3;(V9  kL@,u/?( jzq*bxLDjp8s/-la nyϘz=r)zGB0W#Do&Wvim=8+ c}`ůxdڜmooF;;;u)<v X^^\.W*Xу$f᫱FOJ8"LPޓJ iPYශ 8*"ՌwKpH`P(v:\lƾ: #n*]*2"ad־4<:gZ\o|n@KbHKj=Tt{$Py uiNo-;3HFniW ZE$r"-:}|8輢.h|!`t?Mlr ap8uQy9R:!؜$ iDox,c'ݮ̵@Hb-zׄw a-5f9%,\.WVSe,e0 AlRT|7AhlϽ(`22 JR(dQKSkiH.id}_ [RxGǿv﫼:83XybVdE2=-F  E3Z;{{j?'yk=<qccv:$^d.yvk^W7#DGFaH7k%EXx-z1wdXq w.V&`0fN[/ĭr"wkeeekkƳd 8oG2oazc3yy?6>uJv.2LVM,/Ɋ( l|-^2:P0"<6C)rܡy]M٨ PVƨ`9'|֖j.I$1zV~_dr5Ga㹅;)_8 {H|?OnOlɪ*|u!_w``9 ]8@~ZJ="|LZuSSv:K.Z<֒S/GwM~Vk82֢:r^ǃU|Jlb.LGϜ<6TP.דdi4A;0zpxh藯/7Mu`c[9iH:) ~i z}-la [ٗܖ[3 IwfW`oo=8)W _%(4M999XD"G *Z*(= bH B}Qޯ]tڜeY~_mEօBqekQuk5xK!ĕ»R jgzځ*&3ml6GQX!oV;}l?_`G )߂ LEldݗ~-{YSeLӗ &zfiݺt;07X.iK؊NH(y WP`m2Q @l7/֋R +C3hnۍ/'&7$d*,+B%c$vd0Bi 7Ыc&t R QPRW62RO'ۃZ6SIKZ{c:τgFn+%NxLpÏ,ˈQ"“nڊX`$-,UvMX򵸠#9uy䞥RiN#GrQ R~¹bK*#ϾgN)EU鵟ٓSݒ$!{[ [kuLiڊ5Bɳa^*^C'aZ+5_s䵔e>?-Rh!w}󢌗`_a#W,+xL&?e.AODeGF"5C^I$Ϯ+n%Y80pZJ1`k&¼<7E4 {p˵ppvh+ oxfq7 Ldb7`3dYz-\..*G 4_aFdA!(/U(hI]KoO ةNoq>@T֘@ )HSw|[&3уQ`zF{lt-?uⵣXrϚg?~.Gr&uEZSD2beș! M%UGqQıduE( q9h1p|~RPpV:]ύ$šĀqSΤZ]^A i!\Dg xdr"ۘL¿?üG45/,`u@  Es~ۨ @=B~T`o=gz^(\–pJvVBÐt7c%0}}~:+j*͉@A@'2mI?s=DFQǏ_*@P!T@vg;SZ8> 6f2$ A2tuwh-i^Wc]Me4'-l hOIRSa9*ڥ8TʥY)!iL.iS93^WyA67qS+8ZC0.Ĉ (1{==U‡B,"̬Y{ >oK92 kV'DͼչIkKօ xӛPLV!C.0Dh=Z5__LЙ0|zZ-?8b!W-la׹]jn1g\p4Y'Lϟ^`tVNQZ΄1JZ21ZWfiҲ1i9RnqY,ɽ ncB/SsXi7KtvK%&0BJp`ܑ#Gä%-.N8ra6ͤ:FG7JFkqRì ((VP"MحN]V{VJkL}P'|>f^kg)9Lzyy=~ZFKk~MƓo}W;Ji}ob Ieǚ=o<4':s Vsi ,)Yv K+|rZuZx)WRP Y,pJAk* ?bD|>OYjf֌Ў|ut-la [¤]pKZdH{f5,JJGQ:٬7muw+DQD$dCcbT^zŸ.F.\Râ0He-*J1hs=aU=OsQL$3~<;2Q] HιF/d*7 #Uҧk;iFPvBĊt&0;)弦}Xw`-lQ=uZrw!- \眳.uR \r k! T؉N?zT=ɢ© cUChԝ|#S=;]uA6HtCŽt/Cfais۶߷M\3|W3mhR sxRG?AB>&@"X&N.Q[x{{By=jY"gNV3 xQJ$4^By&W˒Bq*T*+ hȥ).#8 H'ZUOJLI3@Qy;LpǂZ]ͮS% kL{3{¿1 kqHg Ði'hP(`(~vR||jօI:}~#w~뫫!Bӧonu{;6\y꟣p mJx_6Eh0#gE:"4sT#g]G/#شFqA$$x .p֖DVWk4Zz:lbE yI^ P>b/;$Qb8h"K#cX9k|۷}̩S10eOgg> ʝ+[ [kWb]n:~;2 uL*2!^Ǟ=W'ަU*Lt ϟ/˯V !K̑'=KDws)1V(WW)3P]W'^a=XZ&w- h5at8Kor8+NJp:hEq/uY!#Mӎ鴣6@mCEȍryǦhVJYI)=eg9} qPP>&zi/E@?Gi\.QsnJ۰@␸FZ>ZOGclaS=ngpv{ؽs8wnHncQmّB@#fqW$bg<onn>J 3O)x^b,[bň 8ѣD!MB'0ZЭ8Й.ǿG`hFX,^q0!a|L [M~0'gI-if__^׭"@rQڗu^$|>Ofn?//OҢ89L\\Q,ıϛ[8hh_z3xLʓtP|}᳟cHҞ Z[КT,ɳN? ƸOq7(5P!J@HzфzSV+A(HከҊ2u7_ 6ЖrV'B)iaNkM 6 j[??@:=kr>4DAZZ0 0HpB<&-%b( |*TET2+ Zy4Pc?>=בs|x3P-/H(E,ロBmK e…-la [ u !`v?L;m:-t9MS ,ˊbT˕t~;?8dc=b}"(ا{FϟP ЄJ[ OuwO1)̄ =P0 x)4oc?2@hZ.Z3p);z3vL! p1USJ AzNhRpq\.er A*!8QGZi6N}T=֪1TakUFRys0H"hYE0-jv-6NID&;NyL9%%`G/H]۶wwwGu1V~LvniJ:|+KQxW f1zW)D@AZ~e]^kL$&Nb`KRb^W3hvͼd}׿.!ԁP*@~Bj!=ֽpxg)|ŕQF+@oc7 p\u"~0NǛO·hDs[꡷TX 2!!pnnF拭80Hi1@EКO~9Qּ9Ep8$ϼT8T~9)^d G4E ː$j+I0G 0ne@Qpa&w-Lj:fd3SB/ur/la׋?x r~~W- [_+0=J;"(-3c f~6VA.='zQKHAdRD+JaJRT岄[2u<|%.fHfImo$He>!)[%Cd<6 N⟵֣8U 4 0Z@ I0#I#A%b)4 <]t {B $ HxXܯj{7|$CvЛ.R=a>W] [_dЏP&]t;b7>kRYTuL>3r\T*yGVVb"UVzZT*Dpܢ{$es}n+-\k(%u  XyhNMW=Zo|~2UCLR,S?hy `;N{zᔳh\}~YZXR(YP%J3/_Kv-7U.c >`߼{?j7ma/˾J(ݯk79sݾ<u͇ܫ{ [W]p=景(RLmW1 VBljZN(^90gqtSo8"R eQ،1;X}L<Sn})7j_x# 9yq`z ~"W:4fsn"u  $j4Gl=5 L ڷ7϶p1 8hPSk۪p2(_|ag^[HCk0vCVpT`d $͏x14-lz00?n`2U]th`hwŤhUT!JT`b/$p⯈iT*K:\OH4{ƓO>s=5?$]+J1=EӧP! !.A'Z\k6]_$pZ/moǣK_ @SPV;;宥@#PyïLP"P -pMozG>_r*՛/Ҁq㭛pE`|zGA vԥx^~~߱+bZi~c?tW-G6" nF>nr;|٘ /G~oea *nqOҟIJI{eEQE^My0wQ1J|J?kYr@P5їKnn_t:KV+I^G:?6t݋/vݗpŝJBEBi5ZȅD~.TQBB/*!Ya vX.Jm1.WS.<9fkRCHD4^WtN9ou"4\ ."h ŵԚZHZzwlZA.J+A|RY@+r><`Z+bZHNn{;*%H)'Vҡ0Boԭpn8{h4ҢLI_. &)!`#0$\.h4WVVjZ(Jt'=ŋzꩇ~_A3+3gm)xjehJg~ȑ}E}wEhιJvVjxTkXZ[ٹu0u:n:8vE(Ms]x){p2N]:TmVˣPOTTBkID=jVaef goxw.A. QT z-pQ#pqoLgohF9 $IooS{]7wvRL5w,$I~5Y֥`BPv? go|߿E $yCae58>~-St#_5~~|5|a4𥍜ߟDt-2J&QB[rrvs Ugge6#甃`ކe4쩴WՁ̔d^KR[tʾ͝$ki @ SӝgRn H"ÁCLl& a3AȀHBhh!!uk|y>VKj ]Պ[Yy3[V0p?vM )x7|yoÇh(5LT&D-{&'%w#-M,efw^/[rc! oD zqf&=JKh~zVo-~mX2"36\z`B0! c O4o7\+mPO-53JD3}/o 4B=:*_| *=X2#L},fr|>OEly۷H)kZ|9ts)ϣlg}bn+L4bhHof;. [lu 1"?S[[>qW3rm)`qv&ycmr(Æ#H:Nqgޞ :DC4hXm{B،x_V\q-ܘS ZY)K 9NҰ|JD-IduuZFף#50! m=M cMMMMNN?ЇZOMMr/4X&vf$vSC2!z^GJ˲oΒJcs[HpJMrmfrl6K$du{aDBmQ$L^*`Eޗ9Q#rK8J-'D1B>#[lC[YUpk1 9%hXkwq3ŁV!c9&?N MZm:ڞs'=lw^zݓT>O94F L&*A,H=~رJ2::n\B '1Az}ggڵkgϞ]\\$muX;P'BksM~޶lv2| =˗\Xh+hqKS!+"x- , sp!$sp|`},333R)U*Bh)KL1ߑWV2Q롬OO uøH2Ƭ(T QX_K&IMeYr$IA@ [r;nD:tg-a3Ƙ ,%JB5?匚ά%[ASGDıhn䡣FιJuSm|l!7fKˬm"(jln^dmmرc ע6-u iZNgmmڵkΝ[[[+d.0Zh= K*&2Ǝۧf /o 0''.^lܜZ;w܊kKY=v%s!.^Nt :Us. rS/V^w ~βhp6,NC@Rss}w?nܖo~\[3xn:ɼ~lH&SN6[sJ>_Y-".$WVZdhhp\BCjHvƧ?g<,,dw.hL L[Ǝ?}_'q21jHJ}sbg|޷q 3f;RJƊsީщmbFFk.ԖkY΅mZ [Vl۞mg8Zqnq.,d?66q_^u+nM7)ܢ_.L';lqL>A9y9WrN~?NG^ʷlvĄPgP(DQ3 (1Qߠ3ɍHHf6,213 (CuAmiq쐀K^WVt::#]727hDZ_VR @&EN8Nzomm־&}M!P[lo`l7Ƙ3=C+N {HjښЇƞ}qC#}7~GGGlFlz777wɩ)75)۶}____ZZm4_q<8 C۲"eFGE8,e+%VR6[媺E1;EE!}2Ιe DZ瑊? !k_|ix \ 3 (Kn,N{0©) Cj+\XCl6 '6 ,$0Z" AȰ)a$"Y'l\(nN1&eZYV& RFQD.͎㳼zRB}ف?dn&eBa;.dZVkج) /BmE@2Vq e1'sU{ˆVXfmyEߗyV6ɪ^]C*ma%to6$JKuKU=ቬ3#qs YkA`T镣(*NsJm]V>L9%o;O_.4E\Ұb}㳹IE2j2 hZM Y~ׯ_?q;αbdP(qL"EHիW/]. l6kao!&]E";ݾeLD+[@9u)sBh3e fh?Tny$fw*λ[qT(.wm8%h2^3ͱ1,\`s#5FiD~O'CiP ~|>MoS1?2L U$͟7C3Zy1N_lŠY'C/)#!ɓu  yזq 'X=LkA?,z\[\@k0G@[q8 JW͘t;J;N(0CR\:!14Qq(8e(AT?n]dŠ m-|~q2 aY>[[ޣ( fLw|_Nݭ7Cܤp {iv+4(4Nt~tl.[l\}p+!t 担Rlmmu/ *ӻs8sƦ9eѱRIUaHUEA@diO"JإFc{{[JI)ץؙ\ ^~ߧ,Q#{|r&id!P!;u|X#$A ~K}0žf i>-ȏ~0m>jGҰ&[H}Ӗh$ΤKf}ߌw/;*[,:P~M?~ܲ,2wc^?},*f1pBG|pσDQD{HluұFWj08p2 p&>~w966VTjQj,II #M̻n׫z}yyyaaի4ƦN2VkQk%zdj RhSle2N6{[R@HKE\ߟ睭ݝۢYY[ׂn$qp9gLsnI5GGS`GTs8TWC+ 3X H^...~h4\Q @R9|L&c-ׯ\p?Nv:^QjMX=WƑJʘ82m+E")]SS'%+Ols ׊#9`;5ISz l65Ls-ظYvb yƉם6qxC=RwU >qVZ)* Aǒh=nK` *X楃\_<|Aٶ+Y+0B]d Lz%.o?q ooܼpk/q5ub^v|.%a¤A:q=Fa!v*FnnoU2 Ui8`),:RRQPy; QԨ Lgp51ݔoQSZ3IrMB +zٯnQJ@pj9(4w]TZkɦF/dA[;;;%#_:WD#kvn*Zn%W Gr*6ϓ Bض'V_4q)*Z(?158;6߸5V|M bˠse.2 rĖIsGpa$̴_֖Pkat:qIgju9׉dLX"8AF+pL%# AxA 8bqvvRuJ>Q}0|W<}:!")ToPvqrēirLU-BCc~ߤe[pO7&vmIPWX+KKK ga0 Iy8ɓ'y䑱0 B4ɜs777VVV6774 ]Tn_<lq`Ɇe+r W\) hyZ+5}c GR'?`Ng~q)b1ul;uAIaqs2Y8cy wnfgC-LޡS'L0fiZ'_\'sVHuj!u ZC)KնJg>xؿmԀ6?嵗-ޞvIw0&F#3s("sQ]w#l+S 'TTf۶c;^=Xo.1)}wbx'n[7)2J9"ԡD_AX566633suI_]]DJ)PC[$IGL!ɸ;Kc-Zkjmd<;;KKCW^p.)%ͷL'JdO2AGtTIྠ >Ϗi-:tнsQs9>$6Rn2X\\$!8SSSG(05 ϼr$R((SA(P(ű`@ @J5669',jeNI !~ư _a~ iJASfR@7%IF#NrE7v7n9gg3 @v[b0 tWw9;;KNb1V.e.A8GFpgggsssuuuee tt`sՃ4V̴4׼󚟟zzf͇WVFM$EY|͹+;>kn~fCR  2h/do=oWKxI?@CAl4gΜ'>N >fEQPxӛwZRky`IS.}nlllooooo",c2]}o2cB3L3|z[E (-D]0ԘYZӠBȑӪ#.Z'׏uި3m|6Lu0RZXv*MQ~p6XqS0д3R;z GNa-NNR _.3ѯ~?&ިZ>vvjo[‰Z0Wh*si-턹GdB(kq2*Yyt,:`wϫstOV|M L]VչJW("N(ZV߯VidT׳~Nk)0^ b6XHTԿApkdd$ϋdB1n@iET ùI R LJ*ĨM*&&9 ;} bZ+TO,Q"JBZ8JR2! ƻ-73lh.hI{ʕEZE0YZZݝ# c,*n/ n]BG1 &=Ű[i!le3=4>j~Ow$pOru vfnȮ3 !k9"%?O$̽L[[[O~}Uj`9-脧z{}Wfgg-m3d1]bT}ZΈ|X[[k42E@+ͺQYh1<#a&!v6:(̐0D>SS^sk׸%FHyAS۞9f,(dQAc~nٻ6{Z1/YDgfxtǺRTw^t'cg2$cG};#HHBb&fs{{{aaaaaacc^[PƩ_8Pq~ , (S̅lu[~--Fz0ȶe+ + L+HB9]7:gw++'<]a$[[]GYU1UP#3,ˏ|kxQ 7!4/@OpnǼot>rW0j3 li4e+B5_RoDFYGѩ\?a=oysG^SNQpJJZ+΅bʊ(h)R>\F-!4 22,p~>誊b6nWGɃw/~8VS]B IDAT>r[q+^y> L:ޗ7IL&S.lѠeݎSu4$@>ic0Ԙ>&RD2"Ve42S҇lR.!ٚ 1=ѼœnKGx^>4+mrr>zQF&!dj.}L&SJs٠5wWq_X__1kAIPy)eYsss\]]xիW{Y'NիBA94Aq!D~(K'K%I[e: ~ jgJ$@A_}{I MJ5{uSkW(77w~)_=333\P((,ݒ$⥕O^ʻ.M5Xkww;W!74"r9̤}l!<^foo}ab˗Z-߶n O%[d<=ƅAm! ;qQO>20+cOs)Ww:(.^|ܹsTZtOiݘrN[("6@'B;jEeB˲X}$"@+hiŒRӪ*ruT.jns.-ɚ{B>9:goh܊q-л/) QY. < CA@U4D,' TL't&|^!a'Wvq:iKhj', yC&V~-E Df)"[3s ֢; f^jPT*n,cB˿PӉZi""6ĶR`@gP(R ba&gf#,1(K6o4}8p7MgΜ~: R ~VUaR@%Is!I!cVmə\!Kƍ ~yGUJ1_gFٺп8EfcS`J@Dn/&-qvڻ.l^Ti}_M\Ҥ-$ZEZ!"QfD p UrηGG||}/Tȱ֬r3JER^Bq0@;.:6_%M|?O|˜sBYQw-oFy =g#*A,zjz=i{u_'N4w7ÿ5mJ?QZC0\ yȎ-9Y.D :r':&aԐG~/?·f{Kά~/kTYU\miAH0 *sW\jun,#F }2mEqhUS?큷]cjciuAjG6bw7+=#vhQKdQIL I3&gE0nY.9aضbGD#D;Ȱ> ~8roPn6l˄ܛSWOe}{NB\sܤpKӋFEԋBOɕnnnСCKKKTqڵ jBV錁L A2/18|~rrZ6Mu|VV.tՓR1]ԦR"3j/27]z{9PK$eZ$a2&\!)]O:!%9/J{c^)2s)?!C?އʊZl6{m~B^SiKF~ߛ-@"R{ !.eHF] !fgg666Μ9sʕ^G i0\zu~~>c\M+Ej睘"TFJm>9]s:div})doK^I\7+Rbt8Y?q8$J= q|[u;ڈ2}_]]?A@wFC)%8yww{エ bD_hK&VE t\y'?ǁ1}RwhYľ 8~+d#ĀR8i Y#@siȧe#U8wC{=x[jȳ䬫&9*suՁj$(ɓpm * q< a#g?'^{kƭre6gɵ RH/b~-#ޞVlF_a/ɂr:e6s&O]1t~ufBaT,1păJ+-<vC2Rǰ._l7+fa)sʦ~lլfc?h&["z´Τ8O 2H&vRFAͼыf'1d_QPZV* Hc$/֚JMQz/ OEߤS4 o5ffƑL BtxL !HY.SQ/1i#z/jz?ljgKS[*ev*^!0hJ)'lL'ʒRԤ7x3gU2;wٳV*IZ-w0W߁:4]`C*?Rh:?W"!y2a6N:Ij-Bsv\fKj*JVUcKHn~,gEKrgjI}cȳj=di9vLT DP}j&AȊH@thY13#K&똾w E[P6-r9y3*5?#$*NbvjܙLNږ5Z1q4 b,{\g,qȯK)Ýp ڑZ?_~ͮl6Tշme29'U( r* HaJQiCxu J@v)UFA.ӛ78+ ͔f;mk Uz)cHage͕t_o~G.^<7Ǯ}dE,67V;&ԤŞ8[_u*6%?0m!q |0ikoM‡geoU0b%9)FV'k[߮4Kx1]](^>>pxn4mRՂb+; XN8b`+dgR#2'#:od2e͝8l;K30f`J0x`0ҾmxCe`.W8Ά]kWnEz;"',}7㣁#<(d3w<[V㌛n݈Y (r]\(vvvtzNN}#`-^Rs_JPVDU^禂2Ws: U&1q4\.g}gDI7divɧ4h9T)rى *xa7u}NJLI&9/'MLC|/W0GM8ϻq$ q+Hm'Ou`g)%ImI>δ}ߵ~vš3y 嫁*KR6NczFR1|<^>p=%;)AcRjt2ȥnj)F3}_ЭjfdKm*AJB *!e~GCrZp׍sޞG\ZZ f6c6_ p{{,כ~鍍 ڐJzmo{Rqy*q"X__'E4 ޔҥ4Zoy'; \>:u'gkf1PW)[npĈ-ia?}!9R2>!G-d2Ba|qLpHw̝3ubٰw}e9Սls&)+YeU梑c].5>`k%"Aj)[y0Ks5Aij* Iގ<| drB7VG5v!0HJڒRFŦ$G>Lg[ށư=ܠ Sč u] v^F;O{zXw_.GFF ~P|Ӹ8N#pLZ Yq%gQMk]*WVV+,~6\?{Qmv-_vPwlFv 4}9͊ڹ%[m۸~`eeE|~lllhh%[z<罋iėe]ZfCYr8"k%Qе5AT*c$N} c_L&t߯FiIwq#fq|RD&KbZH ٪/M!%Ԙ?糿WuQA0ϴCVW-..oJKqR~ĭ޺wnRJ ٬7fفvl2vaaAi#]3k!]Ǥ rժhB[eK|'6ȳ~=ѤO<bڌ/}YÇ9rdiiRˢ(i](Eѫ_뮻RHLIN4SN )\cͦѷ-7rϬ|Y׍߯:U@1( e/>z/~psҤU2Z%#T@$'޽T쉹69ƝU٧nN~ƭjc43<^Z<37qdnH֮;n\5{I9a. (e lq  (QXkՆZF2ДU*UJ# 7} :`7%ek~AVPp=L:Ҝר7eB<>nF(ndJbB VaZꪫ9O?}8G|#y;'=uőq |>EApRT,2[J)WR=CqĄ’${P*Bom" =d*gBA},p⛞[spH݇U9uz.Ges%\wu###&G|k92٪>^{zy*ngA)PCG+JN@\$IJ@W d9 JD3<#dVjŠddP)rCH \~?cwB2f(PS0I[?p͟(ՌhAF-XSױuQnޭ?bQ#)v; CN~ECA ?fk Zh`)ez0,:yZ, ".L&,P&E"Moz 78Gúq§NH2Ik?'Np]#K(Y9QE###[neΝeIt H^VЗlYڛ IDAT"+IIsj:KO;?&Sބ0g" Tkv7u2@ P K6I:- 92:Af%k 2&Hab)"!kS;d\6w:fs\KVm]_VkxsXnkn+Y@MIioL"XD flRG_[m&1@ KRhfA#Kwu23U@-ܣ ( *"R2 k-no]6ӍJw0# @FJ7}k YU,rR6[b(WK$IMmZQϖKFyy5mn &!g4)BUVŶe;(CWe kQX/?zwG7M aJnt$I+S*?)^5X fGM|\+;Qvŷrzw#.i[O,!r#F(O̵.H:q#?{J'Ibحݩ#iiiPb"Τ!>~sfkXϾf_ n;Am qtLfee%3YJ֮]b(,ZXXX[XXi'wԩ@o>'^ߺq KrkRggg8M>_qd#? VR^f^YY]׀Y%Ug<{KܚlT*znڲ٬+%N.FZHҒ8KKKRLg _l:f뮻zLgUV%V@r9"']j%Eʻ΅lO?C%vor'1==}ĉ!3]r9me֎8po޳g4Fјu8J"$kvvV.$#N V w$6zoZQKnO5n 0e0b:rjj!ޟ'C5Ā .%,RFo (euvm&mYTF@0+ ';D*2VG;Fku |s@%Z7dVX\]iUVe4kO.45e7)sr.W |JYk59H1'.Z f"F(z;rKno;xHXΪH2k) JjkR$̛iu#+m [KKn6Vma@(p٫8msb%(d2ry4-|;fnTszNa<E&V7ʋk5,6r+SZ*h&vRoUp1`ǖծ?|ٮ/o~ǽ* Oe(]S? ~n0YR0ƈ(<'Y&/;q! 4qp5s V(ʩOzNdR9sf}}$ą$Q "T5݋OpD̤ħw'LZ=}?I鬭 ӲΓJE>u(+o|F\#zЗԦ9x.K 6׏,D] P2A,..JaARk׀ ?-J’h4Mw1rR3J( k Bkkk2n3 E,sG"D$専 ;0ueIP(m%0A+zV/ztAG>7f]V)~Pkۿۜ:Tj %u.m 簖ySIzvCx`쥉dæ}^.6iR\ÐFCEeΝ\s5\366fg}VơܩDlZј&.VpuuU;}SԩSݹM}u1T20"Y@s"Je;7o500 ] kn@)MeMRIZlgy:H(ShLŮSv˩=ZwϕGLk  8S]9qr26z\'hGjHa0Ba0J LvY%-l %VbmЬD;oSro;xQhmeYKĕf1nYZBCt*"` ]BrbVE|e4h90p$.eInGl98%۱ml̙# vL<5w[KxÆ5A lFkʀѤVY]kf2{Yαz'O\DHٛb+~jtpћkt.g)b/t0i*XNl֊U_jDYifk (9D R\\.cVWBT{䤒(Dl۳KP$RS\wd9?| lhq`kLԶݚI""ڲo-(BHl6MnSEn>xz9ls|kF!%0cNÍ $M ###G}宓=<_l@ea`$ L4*)nv~yq T¨~k_;Rv"" k4\.Iy!60kn4|^nj \]]]YYYXXrn0{i+ eST%Nʩ;,=n> 9J k^o4o{LZS)NZ\XX8~LE'GR}II*Qʊ^.pr$Ա/j5WWk⍗E ?1l1}%Dů%ut:(dj5G&c'5A\~|z%ȜxzzZAZ6??ĉ'OJd"AM˵HgRؽ{wUT/T*F_Մa6—J& y% j5t,N7NcM9=3??/ɋǮ{b^q/-'nɲo2޾ KO?͍b?ZdT2e#fnŶ1MlW}r8R5V;W0jq0Fayxd"nPhyF{Oڃ/Fw|)(]^o3G[a. r(!V 6}/1o H@P& $MPͺ "@L+o[y*fUDJZZ%$-%b/`XKRZkg/\TA)!*\TX*vCD~`ѽ]5ɬʳa!ZߘW2O8hcM eKَ8 eRJML &Fubݮ,6Z[bȃ::2VBA\!©Go-(9Cq];Os4K^S%G43$[zH]z8!!+=X_~yXh>$q k."+͖enSC0 %­/.$>90;ER( "-mKJyTڷo\я쑪P| <{W[ӱܹrZӏ=O?-}-pW]u^fffCܶb{y^u_I^ءc=m(kppЏ9tT0`M;D<+>Jeǎ>)"KKK333$meeE;~y]wIZT W, Ry׮]n5A M eP}c5.npqԽp4B:= Qh+ qJ%݅3gΌ9šHzX,wRJhґ;v<;v쐼JW.eK=hYSh6("/V&@,7-vFeʜ囜/qM?TMϽ;)qcōP>/feY%TcheOh0D@$5A aOe+:mysUșф I2k6&;LG1niʷKu\!dg j:Lmt[H ~ʩt:RJ{ftBBfV*`R &b$PXbRDFgW7O_W2NBERV+*`Sm ">9 $0P9YUd *ׇJzxǮf>9w ߩ;k ЧQЌ&sݚ6 Jyt[8ĥtjoX}* NYiR"Ciőiq-Q X9f4@ r#s~-W]S%?/}F_ٹ jucc6};v^8⥉䠄<) #<9=^[x;vI9mqO$"ш>S%1 ;)WlCd<%׳6 O}w.j!:cfS={Ο6})a2l|jˍW<~t.}[7\#$|AQpėTvrR[Y4%, a'HD"s/ÿHAiA;Ph'N,//ׯja\+Id!ꫯ!a٬cDbϞ=2yXVP(ګi/Vǧx׻`uuZ:)YI(`vvVʂ)lPgGuxX2E`w<]vܹsllL:Y)u 7ӜDLU`-Kl4V9 }.8M9%= #,,,pk*>yFn([`z J$=P3,*d;vNl@i>QKVߨr-[jY b=d9G'v,0X"g;"hP*>;pe?gGZ͟( k5%4ѩ‰W Lm9.Ɨ ٤^+hA~[;QJ{8:mYR,bC1"jXiK!%|%g(MPLb(ˤ H]-X0L21 O>o}+I{ꩧ"Hf'>x>::z!EЦ~|6{<`f|[ k9.;Qu 'Syq/z-xB>4zV)U.uZg CEw ,{><տS#R"=}?܋~+uUϕ˝OK/8qƘ׿."R#re;S0[<jEg r=i=ov>.F?~\D2|6 ElPbyw( JOE|(ɲ{=1P e eh&#y&$Ŧ ɘC^2wm4`>l P-$Op&аʪrEF; ^{p]53yL!My)a#!(tQiD͈!aGݿ@˙oo\(~|<[OiO -t;w>SFC6͏T`?g|BqUhQ .lܩy}o\o^s#vxxX]ޅjZ(~sU{ps5x+^ p~G?K fZ^tE>gՃ91ܖy3rAnu0\x333z.s,Z rHH =x̿$a9sqh>q{!\SbN Ճp\bfϟvkӟ\'^z޳K^d25l-;vkfHnBe5@A[9uѷRӿwSWk72l5<1WpF,8Q@Wq X͠b3M!`4yV (8?z)P P@u(V:Б q7 7Qv+ ={˻UZn#3<0w``XXsQQ+v2/~ջ^ m'cX)b& )3LV Qd4!eȂWLx/_^BKk% 8Az3a`(ڲw/sBAqf(Wm}n1n7Hy_.M#kEa>W}8yU3lG$6gMl?4VHA8_m@E c&kdH !az&jc\3uc]Sfrr8 iەJeΝFC)%뫫gΜ+差N~I#YsYtĵ-_&H0WcccZkQZ!ϻ87di x9z꾟nﱽwtO;qDە\ y رc߾}\ncc"3?k8r_G;ݧ/k=ו퐘Ce \.JD}_&|1U~ppGt5g7)r_8u{\>E,0?[b%B kBWt{ZmiZ2wC~]@>>;;~ܵZzg߾}h4m+Z~6?;wf$z ϓgw~ꩧuEwdmKSlttԿ1|>\sʽ6^B)qə/'㾃؆Eq_s)Hl;0͗#I8/Jj+V XZcxEHPJZXn^2PD&0290,kÊ&5#Aalfbg%\9\"9 ,-n!r,w:W53cr,NܞۨϮ,\7d 75mE @ < d@+Y pt0p -T(Q< =i/r{bГG$\ZZr<>ȑ#=鿤Ӏ$M }grI?sf6%^Ioz>vydx'2p0 9@  `f3^8ew[cۉ96CIU޺4GYEZka,,]ƘF>*Q~gFY`˚F\щ&@Ĥd +0[ B1+ P IbNrIcA1υZXVʲ_r4@"?HBA3n5ɮvy(Nn=&/>vN_XnW amOs޳|~=ߤVxsܧq/n8Q _0I4#@@ wja†ݨ-Zht5S7XfkV%J_'>#/I?\ʙ9[dT4y*[ mLTDb(k, )\y7?{۵N&L:U0 bbV`0`-Kf( ȕu@#4Z:>eq]ຣu]̙ZM~~ne&Jm)% 0hǀ`  it_S/QG] n8~:9Oijk. ZӧDnA3.?ξx=m^.(dpIyV8V:08ij;@kS1owGޣmTRR}yk+d2Lf}}] AxB~']`pU"4;?4Qݖw5Z{:yF`Yt2f;ksމ><|?!db.e !!(@ "t] #f h9 r@Nd, 2*HPJ*WD^\™3gKvR<&bj2(Oi nO0 X`J1iac*hD*eԕe?ƫ_Ck?j.\1Ƙzf j;iư?Cكa0! dC3SLC1Ȳ̀ޔ#<5/2 NFf%f%zBal-l$2 @6If4jVו2ǹqVυt?5̝Ouwǝ''Vm4э8aT6Zd.@Z2c T1 V3@ 2#SSq!} zvԜ>QRpfYVw)g2z~QnR^;]3&Ng'Vk.'?R$DP<9(k:n5/Uh[B@P[0aZ m1ƳKP 46+bZ0@,Fj!W#%*~յnt1sPb=˔kfv'kkk{dY)3]|h .+NđkX__?tO<* UWE^jFEQT,zy*64yqRdhMtᭁQ AJ@SKx@O-TP%RI?AMWqic+|)Jg7 tq{Ni=EQɤD sG~(*PUMBfe7MALh dJHwzJ3a0X)&E%` ˖MF p**# ;¢j-d;g+Rưc9v2,9;cClyE}$6ÂDD7&U ]\;&Pu) 68Ɓ& R10Cf\ v> xIfUr/=lcH6m6v~~ĉB(/M e[t?wڰ[s@)o_!=8- sOi+=wM|>yhQj7w_ĉZkѲݻx&gq^&Ҍv0=@KʐQmPZSFޣ/tʙ{ <jQ^%"Y+}ATO꧎?8cU<17?0`ю7EZUBIvu-ht-Y6(48 @wэte|G\.WVecUbϤհkx2M'+LRZCCCba-vV=<_k$%:-Gg9CnSZie_rb.{*ͺqbSF|G)SXέqY{];ʽBVh- 'rRٽ{ؘ[?^k`8Fλ8g=cZgrn68(i 9GIMptZzt'04ȩϽ \0a4i(lh ZqU½F# `i ҈4ZC&H|>_*T5iD=YTKz̟,oP};5c++@\bX("AnGYΕ΋:8ϞXSe{ K<{uuw,$. )1Flڡ#r,j6{̄B,iƖE\DJ HX{oy8uWh Ưw=YCu;&/DH^zlzZq'?D"a9vX,uw7"v{ `eeefffuu5 d2& H蔰Xj4=/_׊fg nc{n @rs? 8>CgvVG@ X{(98ڏ͛"8k}qca!uד';?>55U*(V]GP;g`unXImtl }@AqփT|M*h%6iVRP%ZkyrqlΌ7SӇu]Hl)1~^77n⋝N'һ922rs=|Vql6.d4 #V1=$z'SZIcnQ|7;i /WyG%"kA[É@#UH1K%P[; p0=f|f I.Ba??< @FL{`|"J"fЉcI8Sr94֒Y?ZگP'|~' rE*+tߖ!;:Jy\dYX4̰3TK-@AZ敾)w:jO#QCů Lۭj M$y˙L,#ln=v= Pjڵk.Y&@-FczM`E6:.y:z$[zS b2@ 1@5L\h9{(1?\p'ZډGA bQoWZWW6v=j&.8 ټT/QiGCPbivqPr!9+u, vEp. H}xK s{,M$ (@m,3L,N?g1Xpa56a@Lƀ@+|W ːZ7 e5{i=USf.KZNJzOLˑtZdRfɋI'E,h%6KJ LZ>.ټ9AfF}v[ֵk~a /TZh<#0I뒦ί#;a)Jj5 n[JYXXvZ^!k# yb4kR)nb9 k#cV.t5 6.$CZ0 ^ǂ, Y-NZunw!C%ܢV%+h7 Sj_Ew_+?$u=R5րu+' QJN`إC.FB'R٤) jrIv3MH'`f7 D1.%mٛo5]bN4h@'僶ijI90"v4itx 긙MV"C Ȱ"}ʭj$E'ڵkttroFcs,d~gI, X0Ct, I= v^xH-X[>Qrn8_ǀ bjt 1@hCh=G` elgSjBT}cSn+oM v[;52^uT*tH7`I Ⲛ@=TUhÖ+hS:H8Hyup}%p`6e+,6SmFiGv#w(͑b ,?gK|>{v-N<`aab&N?RYVId%!VĐ<-*'_ ; n g\H&N!ŅqM;w53r?bc; -yXSw:ՕSuFGG'''sL4HaT *"EAǃ$|-%Sag${C\_F&TߥB&n+RVN}lW_~Fo q1 㣯QΡktҀ[E09sI=yr9n |HyTi"Qnan4v;LNLLLLLeI3H0BIS478$ 7OFʽ$PauM J@} ElB<"[Oؽ$qƺRY7n,..v]Z 0B>5Uҵp@Z, t~&nQ#̿/ P@l %&8"hF7D TP 1 ξr$v#SSWW.WK`:$zl6 *<:ïc-|m".vM$rc#Hㄆ C}>*{eeɃVP.Bp*#``=]!L@kU@٘V `-)fP^Mh n =; t4ߊŁzk` 3ycZ[v>韗.]zr:Js=Oӎe9'A77WVVWW?R:+I[ >Z@a kݿ;#o9Cԁvt#)رaX¿JRTWx<9ϓ]Hl[N"k9E0?DvȡSѸOR~!UL*FlD0vN;ٹΨB֑.1\U'.s~ّ;IPu {{f z W\޵k0!ęA|q.Ķp ny5 ) XnEm[|"&.-ly)_t/V+++D`@@}w\,q-GNJ8XkVo1Fw{ y(II=)9O%p2@^g|‹ߞ۔V[ `PfQ&4m{d2֒9ȶE\{۳O4EuD%X|+eY[~du3=fydlqoXB3XbDl^v,՗+]`(7#;#?hCʹ^6h./mäRiA0_Ou~nѹz`sZ`_4e $9 _ nˮ*'cB$d VR>U1_C$"Uj3dgX^h4(FH(A[< 3+R ]\kn5T*M9-*VH8q+Þ'@2g'0a- ddCz.[̈́GkK.^G4j ~Qf!|?yԅJZZ'&&JRP)2EEDhl $ ퟲHx#r-A3=H tn3rT҈л,--ΖJ}۷P(ԥo<g=#@̭b B:yF:鬬OAݜp&,p !u5EFyGd|PhI(JteC Ό7&u`œI/dP;35$1xI`Bߥgml.Dlë/nT wR_Iދ7z@-H>wQ9mb 8?XjGV׻F)mw\ץ(GF\qăkv@9zS"Eߛğbm`sSi8RHp!2f I5Qb>裏bt:% 1SJ33z9͞[)\TC5xpb.H!C۬td>OWҍ忽ʸHq @<I7Bs[SP2 g3#.JՂl@rsutaW^\\95NkEZΛI;̀-\#ECT`H-rFn%7n#tԶ)8);Z,sRɮd3P,+(H (a}KF +kGo M``ēՓEY Um596'$v xZ- IYG&IGke1sñ'&&أT r,mъ9[HBx 6ޥ{6K.mnn={ݻw׮]\M-oGZBiiZv6璼 P R kcOjVekr`ks!m2Bd2\Ƒʣqdj?5&y*@k"0?{k@4 l{cAGRB5U3ƃrv}U_,LnwYϼ7gս=JMvO ,Ὀgޮ*Zs`Gdճ{.b^6EOъh-Gr,[V"B(+LHp{ 7?Ԑ+r|}n_H9t|Gz\{^E ϟu; X 1>$V=4⨻@5g]>1&LG?q -N6[y\ojhr*@Ci(ڃ}[?Nm".!ÅBӧOWswbLot?_ƿ]>~{<}bIe̡Ě5 B>=a;< +p.뗽A%1yّ;Lp Y;"&bl-`L|~ll"jdW[XXfӠaر24˟^6|)M%2m"?2&P**0rO ҤfaU4H%" k-//_reqq*X- U,)yc}&e M&Ɉ Xyh@[E1{PA :,MpH*]HVއuJ bCȑ^T-^ZˡM]&!{<-3ڊ fr!'́ɣE::2$}H,L ]Z\jF{6Ppg@f駟d2JejjRPԜU|W"$Rn>Z(Wm,J;!l6G'Is< 2/u۰h6M3E&3ZPx<=} 7yϪigϝ\}I.<#nCqy]K f&i4|:: h Q x1>4»x $$qjbW Zx̍.`m5yG(W)m[(l{k>O r=̥wTv)׍{k?cPM4,:Ite->bI 8@µ RX 'FϬWރ4ξ2//";pMّܹpK؂Zzұh4r\M([ l4%Z&MJ)bXd-qKb.$e#Jն[$tÂnkR b%/E= = |.*oMЖŽk׮5M%( 1N|c #4RoVH$AF>+R]Qg:f2FhfD ǂ3g#_2;Gn.T"Mb-ݻwJa_z>ʾ}nhϷobbbddO(8 ":k[ؘfT,V*RDjʾ$dq QQ2Z2rm֙OYlI@ȽVtS-m&QdzNyTP(^Wb(]뮔?䌍Rsi \0.x>t]uv1' eGZj|k-}[8vwemC](~mk+JжrFQݲ[rsor9 mZz4Xب>8+dGGn81% T\r߼ӟP+2?1X??&OKbe /%ދu|v6W|;##;rʝ nkA7VltFx\;rpڵZRfoV]~ R 0R¸ 6T- fEm_-rP2Cf~=vkArcI uyAl#ǃu:MQMOOONNR &y}HcE˭v^C΄^4Y7"ŠwPǴ 5م;XX+CY(9yiK '0=7~c y1@K\.G/Eyn&{-_)rm5  Re,!4kRT*,}[̱>oZB響55܅V,J+`44 wq{p~M'B[y5q[SG'ap)xx9<00)ȺߜUO|m0YĈ\<wP{<2޻]Ar1t*E `moQhA! V-eGP,mUU|ĻÃY=7r~Ag3֌{\s=/hݸv,w.ܺazC][XZ-~c8 뺣jg|[^^+Dg鬥lmeU_34#:J*ߔp/pĪD;H% T@1tX?9\G%9? Pz~ʕ 8ef3::Jvx>N 3" !^HaH+++/^(K/K/i >sv}'Or92'ZcmnnI&aiҀ9CG P+:BሥB}2䔸d(R||= +ߑcFGG-Yn`0J$zxڵ{wJSJx$8! q,0nNAx$Kn^(ވ?_>;;/q׮]sssOD桲Ѳ}\1 3 SD(:tҨ9ky|۪c&<{[u ugPa$]1 Ճ뢐.磯P`y[nDvV(䏼[F#ɴmRq7\aHjd}\&C67nb} VaZJ!bOװESeB'cIZ<teyv]Km8Kj~FF8|t˗aٙXT*:t^Ֆ4i8H%E6M-Y'G^LpIHdL- f LW5"SC- :*5TxNJEc%HOÓ-SSQv_Y2զ_~Zs={)2p(9\>\Z#Bу6μ?22R.KR>'+ȻE+O#^qd3^o|y /}بL ]vH#0.'S)U)T<3s%3r+݋룛 >NvLed^%BadZ'QY?s!hvyϝu':8QOߕ2%u8`oGF㢭Po Ws|*pc[^zpK 5 H!6VE9it޳F'Q^ϤlVrL9ʤ&*ΰ( +a]HIBFznOޭp*ޙB#h!CQT '5'@#u_~yyyY@X<|'S_Mn)z?P;ܴ0|ݱq"ı9'u3у--%dw7Z|)/!<]z03$2!{nsO.`le &uNC!ɓ'$Ӊ>,*yQ8]*)Mzx4wrq9*Fk͎0 فF` L&y桁T "7y \[1Y,VkS+9:勾ԃ$2 bۡ>V2kgK2052k1JS '^ᔶp$=}M>*#pcm&z H%ȘQnjd,jG&ayVmn.d;w=V{xv׮klju7Vr-u]I{ov;;?/sPW눼M~x׽=%vS@/7B P[M)q"Q[0Qe˅@[18myF%ENmE  `.;=O etBTXz_]c_Urg#ѧ Wt7G@Z`AGp FF j>nqmS6j⥧/<ّ;Xzp+baq :J$vdQc ‘#G.H$jڵk SJR)|{= iY_(-~5{3!B¿JjƦ%(&~#1dK9E#1ŋn$"gϞ=p@.֯Ee5cnIBP zMcE\R(3$Hrt}-3_mEJ~)pbP[D`&e(?vN318;,FS)0U9_ Md5n-|凮),ȉ3yC=t…bHB%hcJ 4zXB8$҆ɓ9Lc3 o&D"AtDID7HX$>!j9'RG(4[y^5?{lpd3j)c&.#0ޒ:O겙s.JY^;Svrjuamm^ d2M  DN ֚ht9'^K9ّ;^pkxSOwyq m9 ;˕e:DR_3C{򕯜?%C<߻w?bHjݝBX nTژrw1ٶFpPe&0~ZZ^,M84֬`LٍAQoZ*Jl6 $kEOnGFF(`R+=[3tzPJrL,HN>ǾGVĻ%ǚ/#4G}Tx8G/;RT.?i#MD[)DZW(AWt[rɒ5 ݙWVVZ1F=AqF\RkC# r-!C(H2^F%}r ɏ;mF&S)eL0"W@iQIsfwGF;F +u5Νw_|s9#K#gk%Kçjm;Ox+O[.8*WtdY"6P w.H )JAQ. t;11P OAK# H0awUO}4=(_9 ϥ%ٍ8\ps񯮨nb,x>|ݫT6g6%lќ (`ЭcF76Då9KvZy M ,]P 8|atݻwS|qt:tg20\KY7#LAIG<`P77hKXLgGͥDV}76< rPJE@]tVfMݻrLJ )7ʲ>F`Ŏu.mgѰz]OL`HY ȔJ8Fh1Id(eޗҼԐSrI;a^OXbCx϶ƺ) :e\3u]Ι&O.zN,Ar-#?/%oPAqD@/S21tzQfff/E=&73)_R_" W3=3JgrNv|7W7PI)cz6;גvO9`]<( Aշ\8.DžZL0 aM'r5YMKxfm \m#CeRj8HM ?r">eлvgqjp 7k N3g VVӵ^[##*}W>+G͑*I]<8@F5*zryrW+W~h[斱˰aE;v=ϫL8Q{> >>w]m6333XlzzZZ/Iujnnj1ښ!#v}V#X#^PnZz " H\YY9%kڵu>NfӀ8ORu#n9sBoCBK,Zj9拾K-+ܟHڪN2^TD.?| EIdp2vVOKӡMbŔԪjvRLLLN9%֑DYܷbǑsV-.....~ӟ >\ol69d2t`]n!"ȊHF1 ;Pgex씍BNr~V[^^ƎPey!XvZkxR!V&@Z ]h$d;2րPfH{?v:"qfvո״b`%Q*Xh{+FG Jzu=|ԁ ޿$cF)?9==^^H*=uNl!w=o'znLbP 4Bl4 %o BĮw߼ʦD'JA;:_Tj/P,yo@G}$7`jE՝(8r~_K3t>*GGnrPIPveƘ_{t#/ _vAw51q\?wᑇ>{1G+{0>>z];hQkav1JS*U}7_}ّ;Yp KZh̦R5 ߣpVS:ZDBիW1=x`R!VZ,̈́qtPXټzoow{Ҫd_#z)IT+*KGoǣ 7"Խr h%kWipz}yyy}} "R{$dHtlDX,֢1w<pq^Ј7qՄuݭ۔y ̽?+7wM/7!7"*Dy7^;؞{W.?R{w- <Ϛ vlF6+L8c2gEw4.ыopȎܑր[.L"v+SP) nɪj6!!UR[/ ,Gݽ{,rAPV/\.|)(.*j2LW ňbZotJ:$c"ir-т8rORda<~+0sBQxuuŋ.\X__wmA'Rkwcjj:e~w-7jF]#-#BS  ,] WY$ZSr;-vS"XV |NSNNZ%_"B%V\[[rlvuBnt}%$LD>(Jt"3d##j@1+d߾w[/Ǘ/WSJ9݄AACjaj6̕ SavUoʔ~as?]:{k/=/溱t|zxWb_ s^>=# 4ѷ:6؏?t9|9ݚ揻nZ?o%>b,l;+WeߋR.qL\X; ՛9AFAPDΘB~?/UY@.0'(|P{)K~Oz뾳m'TO}!/(6D̲< ׽i9<}+77kx<+p~F KhU;Xn 75崎LL`LW ڑ[nl1+V' sERR((Ӛ! AzTrT*}c;wܹs6qϾo^xȑ#ǏÐ2{^:F)R #dB5 Pw;>hm U1Z8T:Q\f6^:[Buhìu1 ,J䧫jή( =m$nCȭuX,/#G1Zd2uNgee1jQr\י"rߪ*k/V ADEDɲV[l>lvqݣ9:Ɩ,J"!$P@̬xo~\훑 %\%ŋ߽-E ^eUn΍%$%_ JZ]DcJ4@jI^K|e10^yDl׋\Hl 䚧8pKzɴ6X, "l4LjJSш7o&Rd +Z!3t:N>/ KKKԧh`իzOצu%9GTObtkCiy6B$psq/m+V.#TlYVkЍ$ ip~k6kuAU[wy%v0s>mX*TL! 5ԔU7J*$B)y_}qb>~z,//uڗm۝NP(x~X,N*H7 ˗{;$}}?4NeQ"}/Cƺto/C#K2lVX3plP8y:Ť5JR1ԶmzrIfLj5BY8DU[F(Oe`\s( t!2d~c*ޚ嬘P2:NKJc&5>%Wv!׿LqZW<[f>qCJ%U#o5uktfJi*,L߸SKǪp$M'>V׭K?H&D=) vk6 D>iTZzC9XVk^TL$2,3ν(FU8߃76+JONƼvsK>~r&p6BaBP Fc\# Ǻ7:j>{aS(L)(ۿWe^2IXξ 0{\ނEEEsi[9Ŋȩ'>ݲ˪D"( d2+ .*8~FM82ƿwlh.wR^yV6Mm]Y9󣋷`rUԋa~M]XW %XR:/h;4ɛ Fk~pڃ[|?M_=(E+(*{e% dZp]29nJ;B8Wk„?OJגR>5R@ˆ=Gkh'S\Nɱ<`J"ϟ:uKz2-CT*2sxRhkF  b .ْkQC`[x 0`x }>M U/:Ga3L4ˇT5t DZa!Zi$nY͛5](~& tY\p[or 23 U>-իC^TFF|ByoJoGFT.g% 5}Y>.2u&aRDBe+NSɖyjբULJ'''i@J|6> B4 ݛD10G4'ܳv‡Cx2h;H2 TI.mtp]thyh8a`a ˆe!X F]f6sK/~}ws6 Ҟw<'NGyF~ p;gO5ʫVlec[+K{5.J4LƨakLӘu׷?vi,6ֆ@5 DC#]H\FAT*d27Ux%׌)Fbs雒ȓ_|O)$U.)͉l!^*P(DR$qɢ[r1" DX~pbbbhhH-)L12n7g Y t8C(˽rLCPqOWG\ڃ?Ooy  [ Ř0-(MxWeҽ:ƒ+ 1!Bkh ݅"A( ˂6p(ȇ`, .Nc.,SF<']2Qk:ֽz X8t1\4N`M~X7 joTrj 3Bgݍ+ڋkd+ :0̙7>|~G?Lmصc$•R 9Q\ĥt:jh_aׁ[k}ر\.l6iTSl6/]DwЅ%8'G~[k6ۢWLC;Eo6ֵ ixkg+D-8Jk\!CXKKK'N(ZEEYwkJ):ipej]JXT*ECH 9\V<ւ@ג|u튣L/YꉎQВ_\bT +z_޾RS6{x7py$qf _b֋r*JPt4g IDAT:_=ue:pWZ H HgEKt Lňuݥ1󆇇e"/ɲy!0` i#?_4?tȒDD^uhYDb(jkFAD"L&I"-ȭ ֡Ô611AjD $/6^Ip%O&kirSDw3{7#P(j¶fB'EmSk*-%`JnF%s+ʍfJݕZxnjv1QĨB|WkI>&`[OMg HfYVFGG[i^}R]nA(nۗ @k2wɢ: y`)Xe fp-\ൡ;O1糇b-]NzHjx.nB!#`Jab>U/#-wٲptnp?/cZܞ* 혘2O*Z]#u>nim{xX\J9ts (-ył  CflºqUiiۅ|~[ܫ1aז]p W"Wh*Js*FZRj۶m=z… Fv{۶mƯ8><0~ +̭O;<giǗ9@kY" !.h4 G]__ QJFo&Z$tAyTeyd$N٨ڞG(g@wZr\&G׮i ȁV2fs^rJ`R D:&G+ \LGd/BVcd9B31jX,d2yl6K 9LIAInQ"rj( \%<,]KSirFiH %e6{ŒɤeYD~@=_ւkٿYN+4uϙ,p!QvÃ"$D$$HuFe.XKU[ 44W_j[G}7MF㡨eM8셬78FڌGg9rd6锚!=ϔ/ryR@ Ǧ7oo, BczyK;WnߞM/9<36$nV$2p&` e c`e! K#WP2D +by{}ګ[0_Iob. Qbl %Z06M;H0BaX ' eTQ[M`b;CܜحV׎,,;/`\cOaPY_aَvlI > /K}:_nt\OoY@r[n`vh5}.ct)GWq7lî)zL_]&xvx(J=T*E5E'qF*ںu+}yeeON G馛l ,%FL_5,)?HF⏔RQVߐDwdp$ṚeI@RT*sssG%{.},11ch4JlHD}(q׮Pd#H9jj|[dH$O: Ҷ<$+GeLj_]5MYwM;\`I PJyƕߙeat lEmΟ?bD'Z޶mڞ+pz>88ȷ&qjZPX__h4 Zt:M܅D i7R>|^ށ2-Y ߉F_˯=~0?c"vݐ*Mw 58eS$Pu®86ֆ}rIݷH ^TgV??!;`vv&vcf|ʤ>9}0=N[G"O^h|KC+@h3w 6\`:g;-<}g4Gf@jomȗoˡö` ׹\ G˹%hYʗpa\`AG/?j FvJ(1qt$Tz󎡣tHYZ>ionf(Өg1GVY;^KTj|rbwhǽh;NVT&s\r+vumSO=m5g6b `-ӟ:900Na:@g۶"ly^^ORwuױcǨ!.rHX̙3ܹ򈸀EvDC,>S prC9Ĉ$1'ԱoGqtHD"(ۗj8=tKwp3g=zܹsF*%}=۪*P4ZEZ2֢z`%(roTX9WL2roDVdJZ lR>gL@ŒO^eKajOxk׮d2>.|Hӹ\nfTxH\>=OeZvX,j5]d1T.ݸJRZ]__d2.BuݑB,tR}x[=䱇a¢G<|[7~82G*IWV(^{G9sj5S!%b1X ڢ,MWN###SSScccTh'-#3pI{˨J'ypVg0fEc vصcՍXN؆2\~r*Rkyԗ*s:ٙ38}v52U{_랷wݮ)b ] ̝Q B "oh;2u充!dm]dinm IPĈKB/]D2L_I*Mrش(E<@J_ hpc'0g̙317|s&uU}& #`9!EP n?޶mJra T mf3Nsr\(VWWr @.v{jj)/E!l6;44L tm2r\~5@S 5"oZD^mۄ B^'p?b7":k85`Нp7~ G ‘}mu&=5`\P "vVa.<á=!;I*ٵcc;37ݮaZ7k3aל]p?E| /HQg!%6Jb1-!3uxxX, +++,9rd۶m&$nYFzlJ4Mm(dدeojB'̸+`z%w>B/9s&)A0dhh;[=#A9l4T* "|׬V??#B]I3fy-pH.EBʏ3`zAІO)1%oke ?~ܵkhG߭+b-C .\8|& dEn i>k:W9CM{9P* =_5l-,_єή"l>gϞpBT966v=]=e]*J\.GMf)]7Z+$ 1**EIȘih C2Tܒnĩםp[(._t9\bhm!ƺcOC)<*w}[Wa歈B Fa<Ēah apG}[Њ" <[{|-'y7?9y  ds1aXHcXX=-{qaG``"^<|l*z- :lw%->R~M8G''R^GzpW0َ [ݭncv]өUivFcǾZmصho(+>C@T⨳֚b|f҅'FwNgϞ]XXh6Qk/Zurv5===>>NYQ`_^k?H7>viɯAr W j*jc=)D<њ&o޼{ᇷnݺHΙ֚)M$ KR^T*iCB/ qWca"zSxdPAa&Vc'klh4J'j {J֚*Qy^,pc|fq 1kV+7^7>66 {L&I/0W<(]t|B $Ҙ9Q *]^bsa ]6֢nZkKwjY|bdNN۶)jZZr{y =a\nݛx:v|qν-J[ʄIPJO) vʑG/?cػ۶adC(vo:_*Iwn,.̆N~affZX8S>Fw- val'"q$0+N NMqd7bqݷ @y߽XfrYt%l (,:B vކnG"Pv@(poᯫi|YӠX_.H8N6E|ur~j* ޾B+3*uhQ.cm 8q_~DI7d7"5ncttNV]tjNx5 ۰khp }5 Eb}LR\'E?҈T*E:mZb[>CO<ċ/XՈ*pΝ;L&C!q*tQvS!c6#XE`]:(r&zJX,.//_paaaaqqyi4 x`ddtR1cL٤z(VEު-|X+06+*+,ImY>+J1;#0.,j|vji Y~'7N`2@tGymn y~?0BvŃ>whDM% ET(H0ߴ(^eMⵒZ~^ M8l_\̒P#UIoO< Fi/5,}Kwlق dpurxeE>d#Cp7NMY/_*W뿜@ xbv_o1\89D#ć$"F4љ~{|8<)`hӿ]ȫgw x({ORCNtj^IX;\?)lq\?x+y_ouf꧶"x&2 '"n"C?gOnzqs (9<zJ݅M*^zn#vvtvݮ[F}ӏaF7 Kg7FsJQFZMkN8t: f2sV`Yu}=O>ߓnmۦ^Z륥/3?p=YZ@蠦U ϗ+vt[:1K[xs~7.+`QqWEBb^rӝ޶>";/JkM$7/^ZZZ]]xKhqbNdfffx￟{rzk1ZXKh}&WY}&-5SzWtgy2Hc "C`! <ൌ (T~^pEnWBW>-8:zvhя~\r#.;F>_^^j4E0&{N(#)|!;,}|,F\.S~G_^+HD*6F\T*d2IHADKI d2ZJI6r9$e'Xi#HPdeM/ߵ ,>t`ok*0`'?<ύs6uPekmoCOOVCA i,8m^?V_;Y}1_﨟TnwVLnz 8wO>Ǣ ؄X1܌v* S5xX[[VƘX,)XK}|V]A ?5*:"Wse-y%2~*uh4JuZ']n7IF"P()vԉ 6>sTB$*-ٻ-x\jj]3>vnUun#.]sD @oO&׺yNh4ֻi#GavMn%VC@o:bd2sMYwT*رctttǎ?Sr9vDѣG/^iӦ-[LNNNOOT n)bO'!"Z...x1TE┨X&I$׀1a9** kkk+++$ B E#ƶ큁x<55jVVVx<ER)$Q.9ɬS{u+W?G"@f% M't+OxLK'Ɇǘ}?m%w!o[$'Md/[*!(sJJ(3?3=;00@ΗTK>-p^x$El9|dx`fcl_OeV*ji>m' J&jOIyyHqYH$2 U-//S N3<*I'h4JBՐȯypI*>42&2~D~qE裏uduۮDP1(o@Rӥ_47jzm/St5dW4L2j[Z2XWW1?y<44m6jLNC5`T=E*,"6lݺu߾}[DZx"9H7)T*Ν[__/JVVV]2; >pRXˈ!w]'X?E91KɤHeU_daDW n%b„9uIhE$rH\˞Cxk w}57SaHD-!cVVVH9wt?-bl8` ybG֭VNCq9|i )Dсl6k!Q@W(p& Si\nee.(%FC0n8tt?RKa-jZ׽BJtUljWpQ<0X'y`M9d4eYx# o>z}(5oF'yп˔m\4϶o{ T""/{}F=PwN5@W;aO9ݖӬFNϧxf6ڶnޯfٔшֺZr8[H}u\DC~l6n*Ї>gϞÇ Dobx] x&fv3n1#•\.hv*J$FPC˯?!riyydB9qH3pD}Cv͛R|'7HRD-wUm;X>k ]%ZL~j Z/S>w)DUc-DɄOzV|(d`&'6Q" _ĵ7dCc"ڕR0L |L^ѵ2>qϱDSiupn&4NWqE]ը+NSזhNRöm7M$"%_.%:H[ǰnb`m7k;VPN#ͱl؆*[̏I 5"'͵8N4%vDۥL<" gVlo} 79rȑ#2/d_z^Ֆ)/LLLP `t^pB\t:Md2Ci R'hT !2KF;8?h^7 vh#T^+r <-QfޱV IJ<+^Y{ wvƯ  ?s.8nQn{JqaoJ{56m 1~qcAЫ &L&CtDֺX,񩩩o"zrP(΁s $ty}*g2_ 83yz}mm_׹RZ/*`]%̧WI%_0O|/# MBШ ~ԤDFYʟ8 ,? $|o^5U ?lxx1=ϣ\.` rY[ma'o_4$7G' QPqݻw'IK47(0NS*ŋ2 rkC)nb'~2: l.?`v > YObnނCRNo"H z#٣}jʎ%" 6#VmAi_~O_n۳goN#SG3ӁN::%va]p+ELz^*CNZY\שq&زe %?:qGԀxxx|~u9s󫫫~nDY_7ovٙ~ꩧ0{>(, e%PD⍌y۶mկX| ;Iߔ KZD^d!{'owZ~_Su\K4B=t=ϛrO62Kp<_WWʆ]icǦB d%8٣R,..RgB!+nFȚ| hAlaF+9 I3bRK.* ,Q'*0n ̏%d(OB$A0fniw3v"rn{?FuN˲H(>>11ge# Grc!Jz0F+RJgJa .2}8M/sff2r]|})$!^yx?nt;ҡgUK,?&l|&JDV-tIh:1vvz׮]h4ZֶmX1O22' b}<m\^ 蛤:3::Jh LLY}MrQ2###d76mۦ+ݘ𑙄Cq1w +E kQ*mDJkw i!/m۔F\pp*MRdTDv[kJv޽}rm۶bXV+$@8~\ gX)XHp< رchhhhh(Jk4mA1SJ5M'K.Q`V#MŽ0b=}kA[DIjK-FIz% gR/]J]U%\z,'׭`Cc%E~ِ+8;6x;rKJh4rp2:%D( L xZ9 B+eἾO>eQƶmΩ[MfKDm1_,i Q/ϋQӌ| Y".J+o$#ta'Q5¿q^ W4h OC0fm؆Jv­~db-ikPR%.4"vmۃNDX4ٶfZV, B>Vԁjdh\z4=(pcF7wn P"HR,1'.ϠP4e_E\mAJЋȽFAifD_]yF̹{c[" $7>m&?7%%X  <~Rcc:E4 F`o2bx*["v~~C?1~g 0K$dj뚮8RF|ț! ~.//So_wKp(iжmIXZ~ ?30kD#tE8&I$Ɗcu8&(틿^36R74vl3{R&C@. l믽ќ@@kdו_{GUo6K6d) GH48 @ ȗ|pp  1b 05/AiD%59g7}oG=9{|XuUuER&~{Ω}{w`S[)/_uZ4LQK_YIH`@FP2l:/Q1"ğJPH,1;s2|S DCZ{ҥp ~6I]>BMe/3ܡbM6^H!Y+dhzo-)q}QGqʲĺ(㠸( `wpXYN>bk1OS4߂MeaA6y->ʂ g!\ᝅ3(N> ) !_+89Iyz/qMb?᧞X18A'QQWn-O/lM"|iF#)t]VgϞ]YYv#!MK\F!ʴ `+|+_ @/| +DZb c1$,d<%.`pE IDATK AaH`Y06Y|!wd2 ES(4ȎW x&+4`>4ky<@PYfM䅈>G:*Gzzl ă K_ҫ*(*EQD?ZJJv,)]8VK| VAaj:acbV\6cʿ_Ra$hMUg=rk}ꮣc<˴`(Z"pww9{歮 fR=īHb<_ c^6/-`lx`Ћ*f⪒zW\6{{{W\!f)G+.{G`GIZ^kaX| Q?_Aa~ȭőHB!nYZ96ՠf Qm̅CʇEoMWhi(+y)e3t>R[.7UG<~#&rQՒ<(yggG %I$\Č5Z()&k_m^_j^,GGV,ň)RZf ߖhʇ;j9xwa/ة}|0yYq$_"oE.3t@tA"*D×ւ,6cµ]7}#Blه=p8v~?hpdLT#ieD:qM2M2HiDqɀN}$kuu5N⊫Sė9k8n$m|CpD Ƨ)uZ _QuMBxNY8})v[ *+n\璢(NֲK;5@f|2H 3K!>!N6V"#nrky< l |N4Gw+-ra8#f_~RB_vm rKYB|Kd&xyN4& A,0/`6ξ"X,a109s?:M,JZrYX2.}DhUUu "<$AB뎭ʼC Vp2rZt"'DDA2^zҥUoG-}cFdV(> č4U%\L*enWE%s#|zpCaR)rTi{DQEQB|'+ʋw"">vxC-Xl2İ.]tv}q]'IY-0 v '9VK^,.彟N$0HHt#nd"rKdU]ג/h`;w ?|YMsl~cMYMd;Y,,ȗuBxEfmooz.;( aL&8,o-'˲`c=ئ[Zdkb) LXDždnmm}5xD-ȭ3~G%㔾Gbz –RN(En•E1!@\^}xc(|-@q?a277]X|U3d2 4sPfáb|A0;+[ ǘ+f 3=iʒ"e(x`0,Fkl(/AENAeY rK|YYVv[쐨 E0h+TV,}%Sιh*tt q̉aA̗X'KD`<K^\)@W7%_a̷$,{}h,{n6/`aˇqo08_!vD)qA%KA4zCnnM)ϑ\ÛM V6Hx4운?jI=Յc"mćpG.Gh,䣶myJb\, )y: .4 C O ZPV$ ̅wt'+(?>dr ӱɾذ\F?tk )`꺖T$I㱄܈"ĩ%u 9`Io0]tR0/b!N^qc &# 1o.ẎU5bpMT]L؜|\RLl.(X, )59 /wA-X y𷉊4cXx<`yGR%OH ^k4=.u棪WӴa[>}tE!n.SS<ԧ6\kkkR=.?_ 㑬0#֍EBA、By?\ ai1"zD or.VO%Op,D:mREQe>|r=|¡Y0o$9TQdC@\l8WEQ( J_?^xa@mcY}u-꫞[Hx풣0YxI$ƺ`M޶3|e-1c?lG 'nz_  #!  Ѕ(w"?Mpy .axHvռztkXYY2$4Õ-H&$CYu(+I"" @jHiJYYY H1˲F[[[W\9vAbŒ).c( odR1'yz8h M3GQo*5?Fj{_؍F((>{*IK+3Eh9꺶\ai䖉S~aBDq1 nlT~v{%C-7z}[bT 5U⽈ zױ mr@u[8X*no/\Fb-+ ւQ>=Hj/+իW,[YYvq$,a8r;f 3M>EK|O+Kda 7h4etZ[[ S< h@pO$.j.[,׌1+(r+B+-pa,_/YD)~A,[.q1JAֆgJQEQ˭e"\zgqXBjMWq`  A|7RvG nI 8-RՠeFGy,[N$ 8?B BerKYz0Y0 od,Ғ+gM*DyYъUV0b*e×]%n>KEo`[zh'e[ԍ6ÔLGde#H<@ ,y /y7hJgh>P}őC)[.J$/duuUšTRX`|tijiķOؑ`S/ZKVϕZi 9LYr,vww% &X8`OpqD@MEyjܒA1 %.l&Gk {ᦣtxACy4h8eK0Ƅ2)C(sʭCh)~/,nnl|0x0 3 .FwVl-ԩS67/_>sFOX0ltynd[,0Z[3B%|SȗyGJXVt2✉/ؾDt{s^X0Nt/?5P?wa$a }r+x,ƆC++\nXkCYr@EPa5^’%KZJs$=f։'fs.-aE8p|wkV>*H0s0dpm^FXK܃l)r '4,P$#k- w-OQEQTnʂEC;Í)6  A,|]IXD1oA.|AVkw~wny'01>8EXN nVCtS '\fnsMf0jZү78~qHlb7"Q==ÐwGO @C$qE6; ʲ׵kvww-D#/`#"(,c֍y)K\䝰pRoU؈kJZ2VK>GQEQ[l6;~͕CTl_Ph(gl.>t7ml\m7=[c=&f}UUDz0M|"KC%<&,飀=Y> ҇$N$/Y}2looz~2i׻ *r<ȅQ^kLSG1 c&4$9/hx 0q ,,X zow(ǁz\CPƘd%g)NJx=[// y-%ك >CU'b¼ .0xhY- Є5rA2*dBaT\(-EQ>6EM݇~WAEQw-EQ>֔e-EQ{N*_`G|KEQ*>hs((MWEQEQEQ~vTn)(( *EQEQEQ[(((G-EQEQEQ#A喢(((ʑrKQEQEQHP(((r$REQEQE9Tn)(( *EQEQEQ[(((G-EQEQEQ#A喢(((ʑrKQEQEQHP(((r$REQEQE9Tn)(( *EQEQEQ[(((G-EQEQEQ#A喢(((ʑrKQEQEQHP(((r$REQEQE9Tn)(( *EQEQEQ[(((G-EQEQEQ#A喢(((ʑrKQEQEQHP(((r$REQEQE9Tn)(( *EQEQEQ[(((G-EQEQEQ#Aǂ O&ڄ` 굋NDO\Wn:v_}W?];Uu.[ HKQEQnTn}|AtiS$., 0pf,Y$4p}ǻ=P!ۗuv1&ẶxcN{}fk>&õ ;&ioPw۶)|f0կ~>r ǽr{]N\=rUij @!q% -RZ͝~d>%-Y`3<*~*EQAmkB$m#4adnb`fY{]ǎg&";7Omy@ß-헬m΀< "&ɴL.)9qk$Wݯ}oT[̅>=]˽ޙ{[;{|n{&!wa %sa~EMH!"?ƛ_8' })(-EmB D6'0fAhƵU5)̵N7<4 Q4f01jrf8]zmt$+ri ~c.XC]ӒOS_0<I _??7KS)3Ӎ ~;+7+yn$hvGX€eFr+h-ϰAk1<͖ o]s(~Uw)(ʑrcه5(*K`Z Z*2BNhγuWgc5\|DwmGyRdxe7Oj}YNҌv$ad"1E a wʺeYV#_.+Zgt:ַ]QDEmy󟝬Ή'6?AgOO+(,7IB֒-.7ˏa0iq( !qj]9tP˜_.EQE9BTnj6?V Z"xEj&.5PdX '}$%""`}|c{0XkDgԎSqpᕍl-K;k%3F:ь;xuGOu]~4ϳ? IDAT}xPezZӺw@<-=>qn\O{]?''ݕ(ZYflB6ۿW+Q6fZᇢ"4 bFtqT2GIhĘo~j isx(*n?mB͢b5H$aL#sBFuȨmc[^yxϵDAa=y=#2Zt?1\9ׯY\5o9רx,ީ\95$)gҎ=gf"ʲO<;nթP\^t2>sfEf3If$}}|5Em9eEZK6\PK."22-m,̧U`XS>caݢGh H½6VV5DTdז"2y`fp)zSRwC?4l yVppILLd#'ҚZ^ReIHp0)iTqRig6dw_}woS/I]oOx׮^GF%1r5~@3ÇLe? >cu|k1 #2DuWZʞʼ!C(QIvIJV)&n8QOq1Rc 08tU11cpm6\Mfvn ?}}ʹ+<;.ԣw>; N+k4:pm-$AmClf)#.m[K+d'"rLd쩞.3C]UmҥR?3WkWk*Є>Yl&gxk~Qe&tN,'W.A d|?&3x]~gA &J8(3Hj&LR`\yqpy\Tc0&8fM7OTs:[9gO>_EQ]rӄi:hYok3kS ׺_LxV}2$X!tb`BuLS&K5Z"1 UӭG>rdV?9(e#;W0bȀ ɲiY$UNdvYiM&$LQkg팲ٟS@ 36pw'YmeguV[˳_RګON':'{gOgN+ϝM^< Zboyo_\|G٩F;YE.!0 /v⊙[ `_bhg;cLHM)x"&ހ Ij87ΰ!2=o{~VEQ8* O3jW^IcLFDOȱި\G?롿W^rT#s>O36TS{<m% ҿ(>tܝ_[?8s[((ITn;.wy;M4͓HV˲D1Yci MZvһn/o㍊'vo׍ 1ך]Qgxˏ^Xv5 KZ"f"T 4D<ޭw/ _8uv=7h2"sP\:Z`nz $' ` seMSIRR=JU6{εqkwciBi ̆뜹M=ljE M U\3yC]S˺=aLu~tqo?[rc4+G B /sܛWFSMCMٙP@Zn-`I5enw;Eʢ.e9Σ7ke{DL0;HD#CƓI ;wGy6EQ#ʭwS6m%Zȣe 0+,XyMU<Eݒ_qPmTrfɬ^O2lZec;MQywM3cm7]ͯ8R<}:n/I#JBAk@SXWܖy$(x3p}Ŝ54B\Vh;{U2Y'S5&rMVh ð;':Tn3",Z_Sťl~nӶ2dYSSmJ=qO*l݆{6 OOh#'QbUϒ~ўsRaaxp.4+(CFS',RiA#ʲ>11$kaqs 四~ֹMxOd'?x([cOYf-I`nhԘ6BKf9W&e9Nt:eXxw8g m0hӬ$kEZ w?)rl\;oKϒ vsm='NY;$f;-LJ15~B~>}9gx&rHSUrR {]RvIcᙼyGZIrb4dxCc|<$Ё۪4ãJdE&Mc-'g'{ U)Y";9Q g7V9Mj\Q3q']Il"oYTUUMLiZ,H/c,`|< (,4q`@g-%TV%ȻйiZi$uMuC40itPHfEIeq};up};ZgE;r\HV(fBΊrK" Mp~焑s٬*2WX'>a0ӲJU6qQ;xfHt74+n#c3cg˿ES+n&iN qu F =gΧ&&ڍ[d8)fWv6 XKsUv娬뺮p0W᝛[*R O(@o;Y_ܑY, {#ז$oh)U5w޷ie, =SCOusβg}eOEQe [|gӼ0YEt.{~vOygc4͛%$I$CJHјP!R.!@6 jz_zc^9sNNJfjmƳ(kL]#[~V6Lyd!cljx*N^9wֹ2 #gϬOu52_fc8ن ΅WHW &BN@\572;]='>}rXJs_? GC}I `K Ed1MҭŏmIN:I [ 㙋izuR;g D.n2rl ̷dz2s]G,S+[Mi]r.-}%b"$V!4" 2xYȀ~Wť((s'y;&o%Y+M$ɌBAQ;vdHMO>6OM%޶ G&[)!ҙւ3_||$ s 3{R =j!&glLƉVEeX F/T> 5A6FrlR>aX2d Y"c8!9ScU6҉Zq)..2 ÁxdDM/ 8_Jеlwq-?#棛I+8SxTz$k&1IaGd\,yfg!"P,%jugKI iRSΪvg:9ӝndlƇ2MhUfnR)#au}N"2&U(TNd]"<$ ͪq"釟5KQEQPg.|&&oidjMཻxkY&IZY YR덳~z=>հ!X R:] `cO6g&\7q@ȴgԎkǣ]+Jj$56zK-Ѣه6aohs°[XeiNu8O5&lLUco39:"0QR l0LMohYKIe ^qwS@5]__ǥcV&-N `\%K$e@5Ib&D"A@DnHD3*r/N@Gl8!CӭIϺGd)4rD9ז$S(.}]ƩdM33\&_6F6g1mxTMlbOl1xсf%HIR-pӆ :1?'Fk]^Ҭ ?K.y2@J` ًߣj 3(C*n(""K~˽^o`"X~gqgjLW3{}N9ϕ3ӛ?'W,hяF(feDoF!~A?Du76Cc9IܺɳqjgVjG5&qXI"|606рg јaHK =\b u;f}(qIz֞UEQE8ʭ>snVy?\+-ZXDnkK2$1>FN}% $qk-G/?rTK[M2*ߞiP5Hf.idGzv/3ﭪz4`@:PCbI!!9HTaQ[a;1,2eT8A+dQ2M8)PCrv@7~K-fGf޺/1Vխ[yosKu`?u-Qk"1J*iYD[ϋ@ BޭqʲO_)"JpۺBs౓vO|s~{d0C"&:1a"l,gU`wfc"ؐ_{D6em6u1[r-InWT3o t%U U5& 7B鵚Qw界ĸE]VaVDK(JIV̼ jVNrO ' @ENrrec[eQ*"g>6WjY;v.ѭqWeaL+4sC-g.}GA6*V1ҥGڭ.Np]1ϟKҙz,B U}#G'Y\];?" piq. DK=;W)*kk@qQB0bgp,VNn:7-^̭O߱14}:V[UV!cX30̆!6_4Ɛ1Df}WR vQ<,h`湲ܡw mNU9S~k嵜|QUJӳD9<ϯ.&!"D)0=mSUs?."%""2ʢF! VREiX؁9`[wvhZ]O$Rq2=pn\Dj\U}]24i-=-kxix$ܾ S>wou[UJ J"$o=ty6sם8=ٹ^MEyG˛ zpᐬG 1bmPq4*%R+bI _VJː|<9| _Jhk5fb>|{PI zhOΣw˓<8 b Qbs[ֲl-T>JnhAkd3FxRhѱ-QD ALzJO8/n+&3Q>QXww\C# chjJt F TUI, 5zmwqh<`>@Эn'TLUw\(Q;Drn\UVT\JUT_]p6U\sN_鍊wM&N%~z43 kiH%A0~c1;Um !nm{d\: _\+Q$Vcsmmc$9BG@9EBRj }Lr+嫥GUPtC-b=5cpt ?_?:qS;` H+Q&VM` ֒1`Vc1idʖO2jl;:[w<0eo= Gq"(fffg+*RjjXc0BnD/VB 8 v @nU;zcswx,hY̪*FY5%2LQr=D0`,tSUcRv%"ן.Z֦QUz^ $̙̚ڣeCxg#,}]|tjkw E+;90BU~~b>ߝzhcM:҅ʢb-V֡s/=n{\7 h aP GK (Dh  DmNyReh$Qt:9M5K>Zpp~ᓻ#?q5 T@ 8(+1DЊz)+ƐWZ2EaD`C ;{!SJ\[nqpۗ[x TҵR>cתuI9q-V$(`K:t u"AU4ɑi5.fLh ,^&P'1$p*(]P}^!UOrמ>`xӭO W53J\+ Jz\zjUzع1.-(ݴg/ȋۥ-z={ ;tͶuu >ωe)~>py~MWv&+: l>^]v~fU[gƋ¡@! i++ &1ƀ兆H葫¯y[ԬRٲPJ*"!*ZhX-4 ѦS & 5;]1ɇ/A6ཀ8 (\+ݍam@ ̀u"%cٸX&F2@)*KLiYc:}geZzwEblALtiMkqQA9"%c( sR Rnc6"H׃Q2c,XN*`B] 0`gl5uXU$9hGJ LsֺTe XTIIѴ|&U۝+9O͛s-ƵzWf[+͋OIukNj;u_V5m~C_'8~)6uA=AIa1GNrVb;B2@MClr_q7݅wE+@TTU% qn ֈR""Zc\ B Y?Ø@3V6N먨n,T'j̒nLQIpg>9 ;_7s-Uʹ8eRbJZJ-j.؜r"W98ya$-*郷GE\ 0-zSUulmT$,ǹį혭1 6 R(%k e~lL2 {\`f1xCX^Oͷa> P(S]̏^Yl;=q-'d̗ڊ/拇[-BDp4DEBF@ J!$˒wZ8Y,*]K"BOQQ vXXY|pMPT'aK{#W4Cg,Fg|#Zaa^t'"59"! <ÃWU%*ؼXNќ 33ń)TT6r.L ;M:irJA(B2G* $zdb .B0?1ABLJHҔ#W/1z%+>Cig)M;U1Wzǟ>շm 0`8ޟtzLUEqqJ{qb\2ݪWsѵ,MĬ, QH^%(607lF%*\ Ur2VU1^!ɾOUpcߛ oKy2.]#5ȲS0"Q$"^j)W 50xWM*Z9-Hh۹[S*E3.D$UiڪsT Zc'16amc#g:Rt~/6'ć*]KOzl͏пxg~(N~BXL "Wc!ADDLgΒc, UQMT D)ȼ嵩H w#j'Gsu$tLpm0^اQJQ4"3xx]ߗE]9K*"-H#i$MaH*;+~v|s+."lm-Br[xjh0`>(xҭO=`l2'E ĺʔubce%?a_bcȰFM&H{E"X"p\5{*L&ծ珃KG.͎{\>g?vȹdK{݆Iჹ oĉF/cLU: L+M8ΒX%+<6f^j-6vb+%R-R^=tr-='*&66&W FFa$XdK~O"Ŕ42%`dsψP% \RNddo!A)Q9d+*jֽf 0`;zUdUBxKU56seevr Fn=7A|M$\4 X" vp|5棁Gs?qn)7HtںbU>*:RVXE7;~ek=\Ո. UD!Z8kbjX 6.4QJDWH=3k+ޗhTLŖ, kkR|*ou,P$F;c۶ߚYUk}0=̓-}&$F*ZȟO~;1[G)5TDb"bLEDMyƤӼ۪c\#.@Y c`0O=&n]_C &1FsÓ>3_a@֚a)+yM0 2 V1B)B\Y]-0]X!tՠ V!QNAZ>H0kݙ*) R>Q "()"w˝Đ ?`>x_ѭO=`9cg TU562^uV_- 6FAGM^( E1AIUզpbFGr:^[I[\;Pb@Ŵ,$P4[wS?S{\=bcT ȸ.}ʥ;Ѳ jrc¬-}`/TΪ]iZv_9)^[Yoj7خҸ6JVZ&%1ZyfkS*! _k/#׾_,N۹1e9P|lM+.CdP@RAd&ukar덻.MJ ,]j/\A8[[v՝xnmjB[}=Ej$q$$\97Y(eԌr)U&% ҐZoc\H"j%UGUPv")XFsC` xWC~j6N vHTyWUvII2%#]] {V 0`DcDP#APM*N]Ȩ? ѭ<{ԭwXcD|lϭ)BvVqM n-g>ZfI=% ]ۻnb:Wr9ך kvUAT,ZtH[5g=Foa琝Dy4(b97>Ԝ9?s?7 YD۟ywoO~\I>}OА\ŏJDL[&\DVE*Ty"q-0.[(_G钐. #K 6|ōOs./˖XKjEpm4Ql& VVdl&eO%̅ҕ$ҀTX]rD fޞRNwm-0#TD?|O1`+>[/q-SU[k΍RDvaJ:%9@,A|5H>RG BHYUDkd.SW̎ʹD!y ~+Xtf„~r/_B{{ĕ?h G' :0kVkzsewΦ Q%g S2 8-~ivFe~J*iWLul/~@.?f}A>}O4E.D-,HI( m)%"X c0:@0r2QF?W2QIvkoG._GߙDXEi QbI }QYS (€BjB:0YCʣ30{yʚ=yemReQc E}hi3t1SiTBSF!eugM3vr:B IE[ M=,J*X)1N@ LKf -TeVN=p䐙1`xoӭn6aK̑)2*1tUdY˶KꊵYZ]FIHd 1O ڈfr;9` vC* &E[_kOܹgt;E\V$LVIFxņΦ֥rWKFHG?2iZv, . _`9I[8tw۝YxHTG/\~%NOwD\>Lc:z;MJ"U$8 N6@1($kCIJT(F 10L ƕz}?+íg_Jb~Jwm5mi:\^ѕ>`n8W#_-|hboFB Bl8UkZ2,&,ŷ޵;p0P|OUȄz*0 X5d,dDՖFr)\+_8JP(@V^{̀ wy[ڲ!b!LQ%߶;6qk1kurl8YU9 I QR,!ӭ6*$rU$իGoM9~<]"eUCgss#G/eY?ݟѳ}PH.K.5IUMƘR 2hzNK?T.8mUhXu!%tUI}mt4;(u7{w"02E0a>nTwsؤi1s֒ԍ#llpK ?PLWg!Tt#765~y&"e׶<9 mQoֳ ņZ5˫teN{+, 2)S4,Őʬ&d05%0JVaVVA9Fj쥌f®~ ̽RJ+j?~ηo 0`o;ޫ'NMm؀X5 _Ďul]K8WR0"`V%)FQڀBIXk"[e:G>|pJ)0l2o^AU"lskG>zrLM'H[ZO5\l>/1zc*:&'5JY$W;-W/3#M3LJ!\L޿.}a­dےM=~5zkkP`S2hOW|x+p-4hHluB20%) 9iHwVOJKE#i6j~lZ.21NŕqUyhEʢҔc7MMm5kY4!rPPR`fkXAPW؊'Y%tpNڒ X)$Xt: (G% T9 @KMK݅xT^ uJ< {R:uU3dH #z64ΦS[8XKbBk IŒ۞Pc9Lۅ7|SD "AE DbL(J1\Wz5mOs]ƆoKwU,q^^U?lj|1ߘ7fڶsIVŔQ5zl YٽHR=oa.bੰ)z;6UeI6wTBӿuܸV~B;ֿ٨ia-FV{x'>ߡq:; ƅku?C s-6EJiL^\e_g&,mxژ6xc>hƛhs_CռgѵG,$*' &1Ybn4_b QXERXQH^с$Bӵ0:W'% ZU0?~: 0`IukX2D$D"fn|se#cS3۟3bElۮpٲabh|d4^<6PY GdȢD|]Okxqt/ kS܏;?bl-Ӧҋ:;!J$I%~=K%X."\KmGث2K'vCR_"۪s#U=7_o==rgEVuq>ط=>;~Y=36$o[A@IHsp,$}; nyh+VgüWIǸ,.NTE>*pUJ*i[x7oiSonE(Gɚr]ǠHyeQú1^粈+eZtu\D "hTI9*A ,KJQA"Y2=s2JhW"Me J\ 8~ɧ|+F΀ G>~=, ózmm;sAkql,LjdM][^OhJi]BF/,Q/VJQ(FǢż,HQ<{F6PExA|.{Ț㇯NDrSZv%^Xrÿ69qєL2b\M3kYv\+f G3I .a|%WP/S4T ֮R+\PzgCWNED; F"0ƪsc8vloۖWʘuF1r iy[Mj޺wZ|BB`\CUs4miLƺ^L*f 'SqR2(Ghҽ)S!t5i=)a>$+b̥9i6 foP0`VC'QdJh۹Sٔ`,Zօd¯͹s]'H>6z4Mssϋ轆QޘPgS[nm\(t@8q㇯Ca9[ߠWùu|8>_s~ҿ|m"kz!6q-c,K&G8h^ƉnRHF?7¨MTSo[7:qF <7 U\v| O~}ow0+JP> eWmŝmS]?v4gY]i'o*6&pbֶ󦙇#94%+9kYJb:[Rj>?O#V-Z>ܩ{A eVFwu\rwRV+;vU:lQu΍̇./|{go9 r_wio< 87uylI'TSR0.;hV&VJ<9n#1+50 jq曍dd>z)]s.+~߻^@,E IHIQhNCyaK~ @i%5+|tN@(K,BLLXP].*1O.HSfͤ+?MJ\+HIr5`ާxϨ[Y"ić荢̵1 vw)n5Wȹ~ZFQ茱⫦Ƽޘj"ZOm6ƶGE D XkO^wO{ױQbt9)дW]E< @b'p?)}/ZjU; \LH&_ *V=}sLijܑFݹUI&lTˏNA=*[-j2[PR c,F5Va[O&a3M\+U$|Uh*9 \eHj#UNmw|YG=;M ^G Y?mVFTҙ)Vj]8\q4.ŏ!P$D J]x𮉶mySOkbR(fݶNe(!a&G4<&;MD]zaҸӆ('t9?w%bkux 0`o=tw6H3(!Jd#kؤ[Zn{{ֵz\kRU%aR8KЌh}^f Q>J1hhɴζk.bb{ [9zH܀2K}sf3}7>w 4W0(AO1S!~x RtKaoWMں#6L^E#Z_Tj"Mi !jPnRk=W}ij, \st2O.VFAhnj<}7_yc3ZY7^%HQ|"k|EMm>FB+")+OIrC!<ǵrKO:ח=SWq@D-_ KWM`21e(^/?/.`3A]aeչ>kk.%.0),#c¸损``*t9Q$+DTsxs:Y^$C; i4<ŎnW,y%F ,1PY_vTc׎8ZA@-D-'usM=m멯*5D5jH_t,0Ks:t^']+|CBs`ȫZIݢIJnq)Lqg&W&K9uַvH 0`o/t;v)dXJ( 1MFulp\J-YuUj쪚jFK+V/M'k^_ԛc9$YCԠչ  I}_*W~_g<]b:9 [ պI?oD:c뚪* )}O>7a8W9uݰ|rΉRsCa‹G#.olB~1⑔0"ɚ#FdCd&IJZ@"2B+\# :qRɲqCd2]#NAi@IfLf"=&aD\.IAmkOLܔF&SpAC:gx:.٬ֳh-;Ds%Jq \.\l {0F]hXg7AM*tssS 3bh";q݈&kIƔL#Np$hyd#Ɋh\4%hP4R{r_jz!#(Ĩ_~ƎHcAIt37c9_:ׅ??u.!J/3Fy2ّSX@X`-MG3s]-_"2G^3E`w鶘4p `nL])»/e,%ODA$qnpH?Z~oތhK${?9}UO 5^|ʗÎi"0PxE ZK-*q@(GMѕ/>{Mu,upURϒG䧏eAP\a94"$$NgvN8o4 dLa,(\0gGPCBи-Cp4 ]T6 O2dE Otb\OH;%Le[% /'e:KGpNaĺ qF[:qalhDt]Y q&COb.\|AaŴ[:S3b_p開wqs~ҭ"#Ȱkspk1$#p`0(IqUdY$]Q8 qNAE r5+W* h<П!9T(G Qk*qʲ_f~z%%vDbtT n ; Kuz y2Twt]\u3 %Z6&E ׾W1Ha$ds 떑n.[eY0d$枭0†,UU5-!*mdY]4!5LuUUO}UCHEvvz55s{ 1~yc2c$iKLT5!a\4$s@?¹Lht= CP(|;彑>=>#I + Yӕ1&IM8g)i>:ƹBU9eu t Ti]A_GO}|Y<4O8r?h0?TPz4$!ep U hOSC}!5"q$14AU g|LOb|ByeAd`:g΄\8uIՙ霙4Fxn5MCNwξlsſ >|AƘ,f8eĿBΆ6ҭsDM83 ߰aüyN(8/o$TCk\'@d&ˌ$\`gZX 83l1Nk1.ZsVL+p3gH_]p… ̬uF悓n]rޅ ɌӉ4טL"I|1+dKD!QI\7he'ZNFpX͡'.MNmӁd(AŌZJ9(9|N(8`n>w]- 6Ei ӨcNl oc%Bi5pp…s%t2R##AcPk\LLIb$Ibkbe\xcZ-|a r… PUZ'ݺi3h*tġ@R $IL&If̘&,Iœؾ)q_bQf~ ͳe؋ŊvE?N,c2>rţ(~wYݦL]Hh[(}2*ʒmV4I"0 ePrҲiF^u}l%ǣc rj8(RS.Q[gM3O/_ƕRp7m24GQ9f;-X{uysl갵Ӹc=JQBeY(ZiR;6.((G(2mn?U+Jcp6_@6ţ ֺCQ" 'Zb~a8NͳEQvm6UOSefDKi\p… UU-떮L8|ɌgtU׸ bp-D$.KܟW+fcڊõLzLTŬ :D  7]5{i @f?Ŕ` }DgL,,WwPz _Vy5ڒ<Flr/X Pծ}o !gޕ +wv7.ݷlٮCwZDJsE&ӿ=8~^?]҆o= z.+b.:mer/(^~6WSxl‚+񁧝sWzkU>iٺ͹`yUzܕkA?wY!z{XS#t PxF1eX&I@; ~,S?j@]@՗dZطq= ذoŧ f^coن iwSsZWYO[RrΤܚ۳kF9׮*EՔ9+Oϭ],?Tx7}qj+.δ3#2Ѱ77qX/CG]Ѳ5MR"JYMy|aS_2pkP-VW|lcap7pkV?E&8c 4߾0C/6T]vNAew5^@}fiά];ռ_Uڂ3.YoUDxŪ>_jϾM@,T7eSUU沜*ng}.?~uns77Xs۔Y ?d]?'-۲캅ZDq… .}`OKxRX1NCTM y yHIbL$> Xv-" 8mO?J'_cyjku{V_7h#Ƨ߰.,{;RÞ~`T?(N+*71qn?;7kJ]/a ]-\3'v>=\K4};]Ss-߾{-w+F 0-.O+*}i?Jpo~.~ʑ~WWܹq[e 36q#vRΘJ g^w8.PeDBm_}C`qPW_v|@Jdl2̦>[awy[gTs2JVp8/1\@Ozq(PcUPXvu6oZ<358m;nna.5ȹʅs'K3@|;~2(of:os\1Un bK4oZ<74ڹ\ )/G%kif;͝2ä;e9eL("XvEVg_Xs߱'Q,>*`aXd{hgZqkf5N6KoJSo) WҥKn Qn rg\Y'L8W5R1I.pM^d,ŌҩW\7EM)<[T IDATkv… .\X։܋%b zHW H!Ic–ekcFCȘmb┱~]qF>I JrGQF{,S3GQ@1yG m{7_kf[3/=w6P w4ŧ+GQrJ Gϝȍ m0+8wr;k1YzEzz4nQ>Hu`d0-嗌[֐y礋9YsǤ)'s-Jo<+>LW |@K[|Cw^vtw=zK=r3h&]ڱ1zoKRU--7on 'n^=`n55 D͟^ ͖׼~5gbPȷgLl|XH_sZk ֩p^MK"axvg蒊9Q ^RL\G7ioPsE./jzݑS'u5J H3*re^=]ss=QRYԅ .\7ͱ'Ly6mAheILv- eD?rne<ް8H!c㥔MVlypg6#Pڛ}ᔯ]:tTmmMx`Jx$P.j"-dƒ3Tяa)p9\Sؤٱ/"≼\p… ™OB2q@qMu)b-iKL-K""[dι)?ff 2),.귯N;E(`´;&&\{@kmvhg} v539]1;?MhL1bshw7Q+3l޺ncn vG>?<)rZr6߿&`;X==;ݽI޻ewo|ra-PhZxg϶7m\min};V݂,`;J)VmWO殸;?6ҒB4lq/veH3Oe!~Foc_GB[?E&?|9"?Wlj4n|ƢVŦfi#?5mٲ^;:&UjmضiӦMWojyE)6K/ye7;*]#)bJ;}W4lrF1")n"%o߶uxw/]Y#Ƕqcܾ#p`w]cA__wۦ%yjOEMMVPk_[}M}~_kӶ̪%M:7s g2^U6Ti,I?ڣ꺪IHHÏF&*pLXՈحD7ҭjOMtݺjϻONE]UjuTܫr]G_QuUUUWU61wSuMi:W Yr?o?Hv(GWHtݺꢷ=-OM!ƈ1aUTOBk_3~_N~m~RFDß.F޳]0&M22>ET]d|T>}v"y[oϯd㜍ww󍇯vrcW5MUUUUw+16#b]L4)e@( zI& +:ĉPYP( pݡP4ˀ2ou и.\͊-։3| B!_MZ -@᪎1v*RcRh.{b4zKUU[nqz_P<{DSUUUo03UD¹n'Te&$$ϪzKFzO!&=D}nUW͹=#B,]USU[z~VnxUR+$|xo}SSu)=w=G 6̛7/t|oJ)DB? 54HB$Ȳ\Q dkLh}:r'emKQ1#>JV"JtvAI 0#(>f,fF m C-̌k1Q߰kR32سa|>MI5Eu:E Ծn_O "-H!":BPF鸣2P}>?óGeD|Qb4ږgK':18TTٜ?b~G*%$isJ]_MEЙtv xΞ@@USsr;)}`퍦U3:Syk¦ (D2 _7*l3jAw<P/e+4Ԓl3l%uhU崜lٰU&CL؈ q%uxzlo/<1@T(YY6X c!׃!ixV5B|woJ4 1fiy's .\ "?Aw?uuuwQ-BDC%]C22>L$qIF FuSx{9f izBդgf;Ti6եeE*պ1$"IFfLNZ rZFFS rZzFDSLLe|Z5~ɩY9Yͥe FMI"]ݱg΂׻ibhhrFVv1>=74WҲ2:l$de†{6'yIZvNp-78t*ZryZKQӲFeQߞY$-+;͡Y1w$ ^ˢ9 FI2sAHiX8lpfe s:#gf:o]ppcxa4^h .\p/(Э/'zJd&,̵`xsp%rZ1 P @p:5x$ _'-_3 81_M%dYI3%U8\ٌt<#Q'6(S(.FPy%M j_OFƠg"]E;7pGb(#5eG#F;K9fͦ&L7ɟ&g/FȒ(.~KΉ[+sE;{NqqM[!5]%ܸN… .\$8AK*<\=Iƶ";q&qAf*4Z99K"WJqWª~cLbm0S"D# Fב̑_6ƢCwf́KjX;az+IW3vlym˓">Q$@\,6;S,c`"(_qx&O[+᳂#w… _\uyqDC`L3MȦS8ަp9Zb#[WPx"ZҪx,XMLPV4C0j%)#;r3&83T4!m*y2:?+ p…%k"3&C 1H]$Xc fNBv[ bO>cRP:zr.l#W vS;2 UGE+n=vȨC#tLX!nL1Lpw(ٛX۾qE 5HXюDLw… .\[N(U!I&L[9h @h,,뜃snǩ裏).*u1Fp8K `Ĭ#8VĈS8s.6gLb0`tQ/sCY<_Gȯ1M'{$جa)p쯙BT X`V)Way ]mLL9cQVu@ܯߨ<1NYjO]3~rΉĮ b霈Ή#uӹf-yt'y433:13Uy|1y'yt8:g`D,ayC:Dz\gtXơL=?ny,0Oy8 Dsnu!0EdH"'!I fkLWsE.qh|+II6hC눷uV*Ce!J;L΢rZvc̉٦!|TU`f 8ۥ&NӑFq]?F d+`M9o)Q ?ֱY0ErMK[''$'Ӝut'.\b@.\>wE2p]n!D: iق T'G|]'$4՘hkE_\+H,3cHȵ vN8t$Knb,P]sug4(= FKZe kŸީB'SF.\p8[ zPR13LK,""ھ5cAJ`Z]NWE$"*zB ?fg"VFg5áhXvu!s(@(Cdl 45RwD" [副"{ bډ[#gN;ĸ>Mv… .N|2CDcDiڲ;EɵXﰮά4W 02S '36f048G89&J~\˩o4\[p… .LA$SH׸bn}G!y@gOcWRĭ$'y w1ڒ=h:aNd"a1$h񰓦USخDh,UD8$Oˆ 9QJC[<:.<拉# !HG&Ea?5]؈!]Uqkȅ .\pq8t :$Q3xMӵ/q #"N?p?P\Q|-+_=n{yT뒍 o8G=Q<ţ(׺c! 0׊$Ps%yQyGJ3;v4~wg]O$Tfz%%RÌKm(E(Ks/о{mL[.~yfywlPsmKQYQVn+ڶDQ{&i2m}uety.X.X٧Zw_dNnN*m<%M*#Fraī!'98U_}FÃ[m(x<"ڱqAٞrQ_$j4y`SR=Ҝp).b/C۪W.V(ʯKEPE]@myQ7WeGk(4D$g P(lv… .\ >wup ܓ!8qHӂv9H?ziF%vd׎Avng.^޺qϺeFv^VXz˓0gJuC*PSf݃eĩoAF-/CT=ު]ol ++i175:!s#޸PG 4ίͩ8nVo=GUuDл+b;+U9}e]Mm;i[Proͼ*pqekPe%㷵9ƪkb$/ @vvjm^ I9. m;WVln=Աw*4T`˛٢ n#*.A1DŽk8no=RWډ9UNWj3+***+ʿ.Mm>ə.n 0|Jcz'+`٩?h^mê͇\Q䷯%KڊPOa/uw7W>UC?8U=)S-qSkPe~ L<7Y=dKno]YEE*0}:_>?ձ0'qb.\pmذa޼y[{^t% /A)}t'1FIş/}20wHK `-·HDu}3^{f `𯟖pldL9=whЅAaMsrc2KF{M>,#YqnHD qN>;MR <:u¥@W4?wKe-;|N_3/M|Owㅕg^]Yw{nrn8k"lz|ލ}T{nbjݒ)kg4l:X`V? F|{=ui㦔l}>eۂYJ5i~~l1O}*Ȉ>h6"3s?6FӟyiznQ~2WT4m]3z6g OO tԯՒy!6SG{j=.<6Ӝ4iYʟ_]"\n:e¼5MҮ/䦞So5xuyRPY?XjigNk1@Gὅļo3b~Zwſcpi7Y.O?}{<EQ<ωnIu<ȁ1Cg;燿?xȩ-ݺ2-Nj8JP2&YONW-_chR. o ֛@Zj{]UDwйLV 2ءzZD] גC*-І W`J=/N]=̯젳y[ Nr\4<@1:ظݝm}x6xŌe|DjRFVi\Ѕ|YL1, KoGp\ιf6"c5S*t… .fB@+H,'}N H0=/>,]P)$ZaF|NeS 3—q64`Uf|-S_yNIrIs ҡdX]<Ӆ˦|Jߢ 3.v;;"@3Krn^UsnS ۻixS,] P*/e沜(9}q^z9':WT {rї59%ZOέPlVM(=4ґ~ҊͿrI <;޵k׮]@U)%x!_iM#[3}-V~9]߸69]p܂| gLٕbl=JL>[{E1G] h=Сbx$9-@pO>ﻟȖ`FqGZp~^?GN&](\8F!:*T]zßoDwv>JoYMmSm\˦XfksW9ǎ*OcRK {o 9"Ƃ  qNj|C8!+ &bT@A MM|"i#@xXh$mG,ۖ%\5Bh4D$(ćDtsٰIvfٝ[rvޯy%3~>s>Q@`q߻0p!v 0b{ 袻nɯT*1UJ7.'Cѩ7WSpn\ e͌PIUzᶅiC_gG/I:v抂7WYzl%*;6ךRnhUؑt5,%;,~ Yuo< W+~~ i![2zS8?nfŧ|lu?nyK?RPGs`E)b&MɢϬRR9*=, F&inԒ :D`'JC ds-Չv=Mvя՗mOR{kڂk)LȫfՕԵC4\N[ MD sHWWB;*-T65a" [vZbN6b+dLg]OƮSw7.0G Pupnڄ }@VIT<.ZMS  mrBRo -xTV )f`ᔥh*8M)RGG!Ų (P@8v !CǴnVM CϻQ\\jZ!RPUn\4YBO}qV&G> gÓщem6==z@N@ eO'c8Q@hp(eWJ(@gM^Rȴ(ݴj_ pM,!ٕSFc t6@0-0k=QTl>4y>+_@btae% #GRk:6i抺s^7~~umZpoxv4lؐ{ 3w|'&G7|a#aucyAks"TWDݢ=PP%6I0-F!$ZUGMVj)ޑX? 3xqOaF̶()g\.Z x|Ȗ€/3ʓ;(nxiGR"Anuzwk@)O儀:n_K&x[GDZ?#wwq~47;)6iQYr;.qΤuZ=)^:6~Cg.'j玜ߜ<^N@a028K#g6̘EӁWax{}4]?yɇt=Iy]R+Ru8Q (XEdG{o^We_w193AKnaZ^..5Œϐٳ!72@M)I<^D` t\:G&U!-XyT. >Zڙv:52NF޿9Dʍ*`[{`}ݤ-3 'hi͗M :}= k([z&HcrwCqii?(Ӷ%aNq c*<'mRF Ġ.r|0-ݖc€en>!`{i) )iRj>ULbfn :kL,U>ILB(Q ԑ#\w8^mn&11 0QM/˻). (P ~dBv"U1C=.HlټJvDtfD-F Z}GnkniiY+7j4SA:V[N4٪lkq񕽿hi U@wW-Z.h+Q_}M-[x+=ᕣ89jY:q>aɣ я<ۏ\l͉Qw&{eUhlz( hmi!ިx}U[6/ 4pLWZ[mT FJAiOSE?lW[-Ͷ}[7lu@TdW- ڱ䝂'_rnֺn'͇+~+ɓ׶ҀT-S-Gru``E6v̟YAۅ,O(ȝ@,ێ:νYeӶ-6!񍷮jKshں|r kj 1ӟ-"%-NW{EyqZ||Կv,+V'B!bFD^S 56[ynq8k4R7t4[w)DfZ:hgZLP\2_wmݴuMv l,Org=rzt4[wvN9Evj3M^[nݲtK=G+UL}4!JF8,SzAuCVѴ mJ5jM|ʦ];j/\ [c^|Oڲq˖7~tFI}r{CM~l掬bU>7߲Z,BpNWCMqƢ@~lF-bow8ZZ ` ` c)CѤ؋&sPyU*Y;Dk (P@Ahl޼*wq q+Lq/L+gĸ 6L81n8sB# ➋[|\qq 0*gC;|c/1 lj ]?xը⧅_{Y؎$˱=!x%Vz76(CA@TDx1nO_kşgG೯y;= mmP~Sv+-rr"u>#/Áa:p9Ѽ5tt5â})9 xp]^h4Bˮ(et:p@"r9ۭ~dh*=wdL(q:nMdKz8C#C6\RuN.Iwjyxxu9N'mo;xXp.vtD-H< 611:5 VVmktBNmqp`GBsSLxq}b~r6&qA20D6aD4Kmjokwh&RMֶ9e$pt^`ѪfdqB{JA.gŲnQZrwj\;ύ [Ň_h](Is@jŇZM (PpQP^s5W[ֱB v݈C\=R-INxY rY[ x17 V0ꢽV@-yC !c"%K-Ρn vhjZA4Cc$u颣.QkԺ(pQuQFP_ sL6:FZhPD ='M(o 7::\OxK\E'=bH59k_v=A2cbGE-uzqZLtGd཯E -BvFmFǎ ~Й_JhYK~>Y֜+J\AY늕@ \ \q =$R EG9n=G$wۻ/F|J\}~O()u.YeFHu zOXs1X%6PeseBDj2+S?1˒ails;RG.P-si BI\)% r |K)K7(_ו F0JK (5Jlbv۩jcvHp"vvqiWĒZ|lKfk˜ʁM_֕eBH\ P#&D@D & mRiNR; ,,,qDzj]HЫKԑArK+$.*'Ao!2/E Q`PWNuh9P@ p_s Q :a9+3"cĸ6wCڗx}@Ђ ޯB M=‡nF7BWZR6#a4PÀG*E@E\$*Aߓ{EB J(\Wb S&er]xH yq麞 :.D*WVʭ ex ZHԪMԮ+s (P@k!nQ=`=BeiOx/ u#qN 1}pn\ǢCEGݒ3(ǟ>Ad-74! ZODdg~bbh"~]:d AI\‰p]Q6*@]Wt_nW (['?>~.r j.BMd2`))#S!BZX9EK\^K@}ex뤊uE$!J:{рA T ^-$YvQ%kӷa%+P@&2Юq3)AtZ!dTA^.b8x bp="zv1|ڂDXA(iI)5VN)e7$R]"i%[P(%2.b˶B%2˛J  (PFQ [=G*MCz<&x$/8}ƛ$B<08+g$IOx<*G& ^Z_z"8J)ad0 @´'LLk=4}^ "}dB)T!=_0= 3"Jy|@8]LOMπB8aH`zz_tp큯=a<G#_OCBhW%3xLR>ap7ЁƓi@T@Y!#=;|{$wJz%-Z湈oI{ofq^s,Q 8eZW!JUCPC . MN$_Tg@l x5!2KR@Gq1 *mA |LA z"yD2Ln}"[i(ͻ?|Y,oXd'>e(ܾ%Q*vG傇4Q .'!aYO(4\c“'Q[R+}݅W 4>F'۷4!/.|"C (d-!bp yr*aփUT7ZA cX(&Ia /.P)Zz&`v )?$RE@E (P@WH} 7BE)xjTl@Eq$GB=Es|g4 (oL-T^ŷN7|x5X%< OC(kQoMuyS-^,K5ddB5RE= {iI~i (P0v ;7ঐ_0{!`T 梊"YrW>u[R8UAKDi, xJaST[ HbJ\ P4$9h1SPڔi$6k?+qgKbR֔ (iVbC(!;$@#.(h`aWŁx(ʫEKBw`0:krI0GF:\7"l~SH3jiX0E逦2+?#.έKYM (PLݏO^p}@GEebO샳Ox1 =@E+lR;ǛDI^0rK[8<\4XeT|}-FYM (P Xན'n` X@DG!R~8h(j! ?Z?凟1p [-h`@YK@@W% = -t&YJ@ %`u\aGd)Q5!)*y {"+]*aSV (5nxIE@: E/,.ppq$.m =>^`q'@GK5IVExpIϳ1KT:} KO*B'qٷj4z`4]DDh""4y,|in.$ I=6O,=&>x{ͲT ˎkL`$"N#t6j4 VٶΓ@S OMWXmky,UIX{]֣Һ%7R lOlh2IFr۲{E'j4 ^-5Mdpv!(!!>&"*\Dh"%kFm ѹR[x{m(XK/B OJ~!H1ˇMMB) ۼLIdF(lQB꼵 =i.Jb\-] 5z] --b=mGhrmn Woma25ZwAE#p{ R(Pokq{<˸!C E]57 CjP!(vckY}n_y~7DD7ǿ.*9 )ksڀYe)?icPYlU AC7 75m{~VB۱?v'2Ֆ@ܭ7qBgƛ"lw.^.\d.!ZM}N@_a#4 e?|djGkc"0uGܶw3&/]ܚy)T^0[׸" [ L; wҶ<1.b}frm+"y!aƌœ͕ 3V򷽼4ք̂s$-ֻpC? kXк=oU3k4emC]K&c߷wۊI[=~|o;tY⼤)Qo>r$8 50/ AOHD]T LNnくUb{see XSp//U! )9sF~晚ǦW(X-ŬAS՟Jdow @1jI9]O姢PuAC8s{͵1u"vx"7Nysgcfvʲ{3KvYO (lR (n'-;]VpFh&6MJs͋h_|tk/5ܵ5^#iW磌\hREgRU,RyMռuhD3iq?+Ƕ, dv`WXS 'Mκ7hԩkNh:uE0t SS Vo-pW/mX=5! G{9jJ5(B*7-KЄ<Q}Ļr(l Kƶn[kaηr0$S3Q|!mWayG}Wj²NǻRS VOרjMš`Mp S T쑶68\͂1UBW󕫟R7YZܧ*RS j4MD¼}~"CsInN{E&2 rIjQ!u]S%M*lbY.a-(.:ש-[|Dx[Ge4a\۸տ-R3 ڕߐl ?@?fۭʦo_w\ڣ2;. %lKRV״Lj²]'L Eԅkm⪒ҞF C`Mn FK2"F?% C럻M@<ҁw8w7`9h%`[e Sb7N/%Ny7BuqiFиjvP.V{}FVl q cgl3l)MSrJ"yRٜE7vlJ _glSh `!kb!] 1LQ~b)N l޼Z7!8K{0.~&4nLsco_z 8ee=,DZriD&zjgY'h@I+.~6H֎n!$ꡃ{ !P;~0 aeَBmv^R7S0;}78|YK^w`_30d~U/˶*'^~WulGdNc ,9' IYwDzÿ0LʡNec/Lji>`% !Xb*BW/ _wGU|ޕYaځ0 3cpg}aT Z9:aRrp\ב*kI;LO$*fg}= "+wYH񁙨H[(XIC,Ye*?v[gd=۲ ne3\6h4 O zYLR4rd IDATv ݝ Yk*,u Pl9'Qf͚jCV\͍n>gms;zC|Wc6 m-ZSVvgo]m̚|Ye6>Iڶ\6FP{DZb`q$@Wl,#c]ٺ—LV&~O6$WJN2!dnecyr}S ! }#¬;|^$y5BtM$&}'`XweَF3߅M9DDA)#!$~;_UxVxvs}Ob30ZubIf;ɲ\璘"q}*&<*8qO;gT?XP1/f= J>4EWun,se5.k㟝309_JC~vʌ|.Fsn>VH՘-+h(K(dٽ,[jyB-YmϹ, rNv*qs-SZ˲,n'5w,h&[ j$䔝eYe)0el B[/O{{X8Q^NHeY#!eNe/BTbLU{diz_6&Y^6h~\iJGiPeKmDȲضeAԞ[ k-b)ŭ;Vfm k,Fa2Pdp=$ߍerqo nݍo^R ^[I <~F@_\{/jkxM_dc;};C][mNf0-vU>r.[pH&^B|K>e6 "&ڝ u~4v@_;,`0 qKՁ3&rdɓn@ \ #z cI!MH˶"aFll4 w x*Yt?> U}_ :ds}'74io9~%ڿ3f'@30<(-jvk[^FhFbǧ?:wܜkv~v:윯h4v^+qa#z]LgFXtt;lEjPN}\ի]t]\;z:[vΛ:>g'Q':8}YjX,uǢ N>>#ь!|tvdf攏2ﶾ'`k N>eI|D/ظa/drؑ#LMN|jecJr0Mpd v[hm;a?jJi0ǹO7u[`gk`|BG!+n3; ~{ݍ D:][EES'I,`V;Gk&nigAI ]zқmQk7 ΂#fwO~Ӧ 9-LߣƔK K:v ͈!#W^ӸX\&[Pl0/[Z+%X~~V74_Y:C"׀صk)LȫfՕԵYz=݆ȿf[VRW;?`˛9'4.vnyRLqQa/nWW9" )ִ ] JcM^w`FB!OOb4bntmE}' ?ÏޮC@Hmx2-pOWi7o@>E2B᭓'L"܀tgO*-ihqwnx{"AUЂm},~4:M(|߾wJMD_)ZVnDom?37=DžoޟnM/]c|2/z> ûAT }?@UC,aXd |{%<^'Ӓ_tҍ1r}:fi^%FM@xYW:ll@kқ`ȭxveڨsL2*}LP2`\'I[NJGP0[!Pxx0y]8J#o4$˞N!N-s#c| !#ȝ:T<KVr8Oʎ~NF]HvK|?.&jpUpH/m}xh}6OVRȴ8ݴȳ| !<a T /X:(.cow'P>x T7:C$(-/wKks.I{0.sd9 32viϿ0eVxpƛ2A_bfb48)H0%cI}0{k40ޟ8ϥ -% !pGK֪&N+HWkwZlIfJo Fa QA.xQ]˿UF+Bp=´\Q|-oGF?_j s/E}RFݠT(N8?&/)v׿7'􉜪" MMa>^;MM11?Υgp. ?WХU4AQik 5/D sٯ=. ȎԍJ֣+;F= /Y8)tl/-4ٮ",B-mi~Dz3xCt;Zݔ8cWuyz~9@T>~2Ag0P nPN 6b[~iڪ#'C(=ЅHr ږrx܋:F>sTY:}zZqU%; GՎJ6 mMKl^1!7#+:ـKK zq1 Yy{{iizUi_"]\<3"@Ƅ~8z侓'?z>^  %m!y|봹kk]@6/M4h'K Z_uk+7M׌SfO Лwެ>аŜUW]{ Wpt_ivNg\g6 V O HxSqPy0rɶo릺V.rնw#F;XZZO5m*DQCpP@?ɩ)[뎷6Yk*5b¹/r(n*FhjیWﲴCoj7JTVmmۤ_T4}pT0UV[[W 0/N BH8A?l@բKklmEMd8`TCo߬kh.Q؎(3~uno,][ҎPuk*/jn AGRbZ(h7*'OXVcknU/J#]CNlXߪoeX,}2U71XJZ|si6YO54-[j4kـdػxjk[۩-+~[.N"+~ ےo8\RPYIV[kLqHfCdxkՖS-MmxP2y{HJu@~GV3?`|Ư?>߽T T#Uj8T\-sNV K~$`VqH{ٵ=׎%ԎkFLYSSppۈH`'@朵vfdM_)G/.klz觌 .UcWZmMSϰ۞5I| b~lly,NW{Ey 766[yn1/yPD{--Ͷj{߲78⑘Ȇ-oG{|~X-߮t>P?yn]j&(q(wķ9TFxw1ۧ~{qɣFt^}C\=raTD_la=]T;x=OHKBPhlA2o\W&3d7&b?>HZƒ}mᲧGf0OrR|e7gI2_;c~E(6kEO#envv~me|\*x[LhwΕ{aȕ$^,i\6Fe5l|OZr efst{udLʄ(m2[60?hK|2_ G ,Y@$T#=gY>=CH,vL\|6!LòV!ZweٞӯB}NI~-,˲GGUXteFBr^="f1T[Wm2>TDJT9p1OCT ar_]bi#UKr[ yrDq,ٸ^"$CK!?KnVAkUm;'AОY@z$T`7K" 2L#(+r}}v6oGc7 >wo'청}|knw[c,!v.9[Rn_FAZ)\mkzQuFNl{+ R!j첔֐m?6ݍEV6v\v(2\ 7o?_n}o_nĺ:87FE|\nnuڍNvku;63&8QR %֥ pKZ٬p{ kTԹva+Ӹ&SfIy7656׈;,ZR@Z6lDc+홬wd B;O>`͜sg3̜ >  XpaΒ˗g Gfg+~ e'+YzxN+-,$왉rBlIaΰ'dɕ eʜ d}UPlz|Q@>$RyʔJĖp3~CW\Ufm1JUVO=;$\yʸVCϽ}^f+brŊ"ЌKP#^8+DgȾ0dLئ;_m3a((mw~c[re\SdR  c]T9ƶY/Ue J/ݦU823CmF&ޘ؁hɾ$pV7'ΘܿZ|[}eHtεLlDENcAKM';x*ld"f̕n5-fivXzSr,CO&],=+F&xH=N-.K?Wd4AfK+Ie$;'ZAnn7#djl6&w >"/ʀ ;]x}$$$> cḸ噿dy3;)Xs!8r2kCfseeMl)$mi@Y^|H5KaXLUԊ%$$ń o {O~c{!qb5 Ȥ̅DkBgk)M$Y暯,Ix]D Bh1͵]RyK h,2I ,2,%&<ߠ5 ty;DI,(Ȥk1e G&\]M){s$rPj2JFt08aۂԂA؂buU*SJ6St%3mRI[IV&!!!!!);%9؂ɚ{&Re'LAt@V ϵb.#!!!!!ϒO[sa{ C'D6n:<4ܦ֖VIszd'8MdIf$IEge t,9RYl8VN%m棖ՒqkgLj"ޒ<^& IDAT]*-N^ZzǴ$nI_Lg9I;<=Vo٨<"ިq<=S-Ñ&-6lS缼{LgrّK2qC"WV?vLiRiSd<|qGKO&\jl H=IdYK[Key0Ξv-IC)'ɒYeë+[_u˨'L$+Ӗ;F>:W?a=$Y֞薩nSdˢu[:PGT||_YYdsx^0 Equ Eio9JwWalXlƏW:˓Ǹhx0 $O,DL]o޳.K_d87վ5;G:)yh5=5zRdc`=tנ-@Ю-Y@>ip ^YNJ_6^~ wP'A`4C+)2>*~\YM0 2+Q $7nk [.ڨu v$cfppo zwBmoU;g`]^P׶vH?%' t 3.ՕPr_e U./+7[Ca{!tEڮ >׽#S,e bY{}YYc$Imڞ7^nwÝeYȏ T (5VicyN{g}H$%cU.xTTb{_{LptJGW{b 2Otl-ar,@x{CCo*"-B~ 1EKׁsԟdhkkח5!ʵ=˱XoV[F[X+Ұ K_Z^ϕ#Obfb̉Pkx[[&bh`Z=%:ƃ5)ׇö/+k8څle-.P!Џt_[YpL4/ת{!WoS][,@{%)SdG~[GaVk6`[jm'mD#pAֶ;B v[B:= ;lRZ)È' 2U\Dd .&zxMNXt!>nb@c~8qP~`84t]MMv[T.(ṱpˠvGzŬO-q Ytu7& ]$R&+ṋt!8EsLi]A~V2Ps`dbRVMlE$_UJ,m4n&dwk[(?Ċce}^=T;Ѥt?:@cj2w]ԥ[4r*zɨ#{| =HVȽ:  <Ϗk y#X *ZEJO ➧xF:wr#̲rc>H:bbQu&ՠ@8q:5hM=X"toZ̃c&ฎ ӹen}А|fT#1Kb14l+ &l1 y8c}4&nm0Ҍ `OVeX,]t..0OfN8jf TROgOZlחȜ~Dg05hD&MW!dw؊Oc4ܸtzzzZvA44|sM&~S ˄ד4WEZgkMw/=zۮܤ5{Ƌ?rY`Q0G%ḫC!P؁8&juU< Sakuj[TWjcLbsVo=lسCT[:pxܪP[gQL'Иv[G ֎b/W֘[< '+C |9<֢ܽJg)=;?d׍ҩY]S,`mm>3dž8s1Ag̒}"B! :󖋧 Op6[`"dJzx//x '`&?~ӬAMܘSD\+}/g]N`|:ƚ gaDEb9;ʚ=Em@O/ Ո43/a6ڦTU 9DBBb[qVly £}O/)e]kJ*El eDeK՗hg?k>ir=GI :O =mkfz&2gLq l߸1Ԧ4h,6n,!O, o/}jGU=ϿUT|MkZL\}n `=w5s67% 'ˠ0\|6cG )5 Gu!zh.,<섋f:Kĉ]psU(80-]W7ms?( 㯣`{XpzsBct2؊]ݿB[AM ` Jðkp|~q&]b}sɜ8pys렧jjZj M!>Xk݀.+ ϻqlT@l8C沃zhrnWяYm'\q#YŅl&sc/<P_BOPe4ٞx5(faE1r{ =_ ֎EƚUyy FB'=C/ W,8buiå;jT/1;֥N]K+Ի;/Gκ"Y 7]K񢣤6P WI %$1"Hw iidx*ϿMbxڻ[oj߽J \!EcM8ʳchciu+tŗsvL[E PC8kc>gb**l ˩GꜺiF@*4CC% f?j/)ktyGFi{g#q-W[VG?ry^u+##^3+͊ tJGF=’6kxdMI$͓6U=@?}#/`8ک*kR Vn~o3QZ4U6@ΐGݜ5QTjHm;u@>kkqi&rBua6B&n#5- {x ;oNrgA^u5 SXA GAhoqC؅eI.cŅ}534p]o {KZA5l_+tϻ|j?t藿E_8țoe/Udӯ {eFs6vj=ٗ'wyG6gly8n&kH~=o_%^=R1[`I3C=c1{qY6Qw$X}ATXHkc t%D}ſDz/!*ܻ Gާ64ң#71qؐ>)Fs8];c^! Kx[<{CKtduw{M1=V}~Wc>QiC:SorKU B?e`hH1 !xK,^cFs19F9S;d1.Mx1Nl;j {jn:l[cvCM bU_ f]CH"7luY]Ccj8">j ": DBsh }iK :,hVWjkﱊD?!0㘥 _aʹVx JSCQ Q~:>X_(SWYS݅0Cp2X}lPൔwf'&6 "C}!n]bhi#`񍉤裭 I}fކ5B/h؇))[EmSrWtPTc#Dr:R5N.+J0p ɬM Tq1i7GBeH|2$ <}Į 78,yʜJFl0x*Xb)q8'S*@T(vo9K._B.qLH6b+Y|qz֟@ N\}> +n]>Ȍs _WF&12 W*U dq2|RN(9,%r pUJ9XO٪X'1TS,G D 3\_}sQXe+U4t rՅXM`cy^yG 鿉 Gf+FBqp,pb 3T&5 `yJ5G݂ 3\9 p<&ЮfY. X-maE,8\|x͚5YYY$IfeeI +U8~LYꝭR!:%gǬ)_JUh⫿@cͲg\Hyd3ɔ93(Gn22dӳ (QHV)V sQIFWdR w$UHTpLOOH찵>N'Q.ACL+=ri6EC|135ࢥ-MbrL8pUQ IDAT  ݦU8PffznJ釋oKs- d?ҏ2H3*nNvrSӋ>bivXzSr,Cvsͧc:U|&ԚsPzhd-k fPڮ>0gH bLb$IeIH,jKHHHBZL(ɐPLx1=ִFKBb94V8CHo$$>+vm?&ۭ9s鍖gIHHHHHHGz3ωւ!!!1'*v-1tAAOI?9M#V-lq8GXo'IybŊ.s|$Ke.>S=1rJMc$Yvlz0ށZ-vvʤfFo;5 <ܥt`'xLKĊ]1[zwO>Xb +_@kㇶ$Ij[qIF$IcB~kIn3shIb$Y֙Ƣ$YYNdp @}\S 8$I9+)'ɲvOB3Ю%IuwzH+siImik,̼9a+[smjlYF%!!!!v ߞ_R8hj + 8LwO'4情+' mp>Q;P!|Ԧ`blXlߨid˓94vZ ɰ@\֗z]}8}k6ײ\럼.wg9ͯ~~[׷nnL80':jK^2n NrL(ӿݓ5&4pǿ,Z99K؏$#/n [6r|2N4]@ <Ր#xO3tKÎ} Vݥ1 S%(,;wZ 4fWSNh,vEu4̋掺 Q}zgOnf)GBBBBB"ge̳L`~KyLVq-)4WtfvWGqzv*XƞpxaSsPVx5mc5;8n.$4mɗ!?7;k-@|c?`-TA-`&o^Z A@w]۶PrwQ%/F侭Jm!UM[%7ڕ/v]E?]§C#GƱ1㤊@Q{j}M?mٻ1>j!InY h*B}f^Zj׮-'-lҡ9/K6yOw@o.,}_xtWAg瑪Н C/H_,$1B s7is68cvWʩ:xu>YBBBBB"i1!)qة/}夺r`8@h /<)앿ݣ%}o iX{6uijz-&e2~Ez`>/ ~+-OOO{{js-Z'($+ '+$,v u4J5yq3A`t*G}3Gw{&u0=vWd@:HÈ̤UޑH`*ZU2HŃ ZumϛY /TFBy/@ Y~X.B@_cexQ6F^wև][H>Q=24Ab{_{L0\\q%)K 2Rt >:+@y=!RYk2NCZB*CN;*6{gPvn0z{/E.amgr21O@>! c=[MM ?b9Wo(0re׆#`7Bs7>I8ƟܝZR.˵=~a:%`O-6XwOc=3W8PQ7<HL3 M:cZ[98a./3j4MQY`tz]~[_W>Tŀ1=OyD@GtE3?U}Cg]e4Mۚ v'̽@]鶝ݐ8}.jk{쀱 o7a<I.SrNgzjKts\l2[:tThL@ !OHq? vt<"2^i,{Ͻ2 @ F CPCz}殪{+Lkm [U/MFu%=bOR,ͽa@vp*~os:[Erq<^G**_֙)Z?<UX02vb.;$d,4^8n̪4Sz{Pp!D9A< ֚|"VAZF㎍'8֪4Sqc]:@cd#hO`Иݱǘc@*vrǝj1Ɔ,SX4緛OjMa^8 @ocԶXu!&yb@c~8K^8mAeB:3$ k: 5$CޒE粔9G%j"v 7[ P&{|8HLڪG؆ &";{fFgR{3Z2t&qm0v qDZCW V`&1}oJaȢ w/h\ٍ taQ ,z"}BH'DOUy~ B[W[ZmO5B0iGX0a;9@)Q;g (iq)=a`P֪tVAm Nga MFIKHH={2-!<>ɓ~(yNqu5hެ.ƭw!~>!kh9/u5=[H(oؤW=oܰ6?@W`,B8oϐu:1$5ʢ@O|xT0:)S\^hlUjR[*X-11C H2"( foqsކ/l:aT<GO< n6}4[?U[d+i1Cw\lo(nu&K03}nh\kTN&.jdb ?7emްLf1Ksɔ;~kj[[?BИ??cťFkv0\qi.S=uֺ^XiȘ{޵(c1[mrR2 @cу/~pks2aϛO j^W.F4Bb 1^%&juvU|.RE{nMTIKfLTRɴU9^% M&+[v;vm""UZ=N74urzzZE_^" }BB-^xiskmFJB桟K@]Wi}4(::*wDٞ8:RPZȅ!9RR/C?oipv1gLA@ὔKJ*|yƱ #HHHHHc"M␭-:lH[:}ՇşA{ +)4TkKϷ@6Nee1iq<ϚiM]?kS'sO@sۚE l6yoܘ tƍ3D9c䭻yc~1EUhJ[i@k}}wLDّA9A'`hytFR7"d'\DZLt-)\FqŠǭyJT1#3TeĺgG\sͥ;Y琫- Cu $lN|q UҺՂfQg=[ꁣZ.E@vK+wʩ'^bvKSA @mm.ݳлⱓ6P WI %$$$$憴AeM~wOs 0L&cVy lUw fx X}St]rݍWsƕ +8e좊i]XK9㱷UT؀'`58k #~&u2#uNݝ)C̖G @%e.0mlw$cjj<ݹGN\ kn}e}xdqtv:*ݩR7w:葑a@py{%9$6"vF7^\TP S hl)[X7&'g:GL.`[wꀎ}.aTMJ?hX_H>!dl6]UߨGh{+LzZ: iP7*`b^^Cjnܩ}SqSυ9lwg_9íM(2lJRշW5tmynIW~P)9k@ P!~zB)QNya$92`p3|FcR=y-z /mxGi+ӿM9-M}?~=o_%^ČH2Bh a=3dt cƘc^>'820t;4 tY&茽._j dZNqׅN4J;a16O1 "XuW(H1`qMOt~rˀXLVW^.zBډnJaf8?+VA'<LPHZ\L8l)!k!5$5ŸSR.'M8k7 E V5~W#qL̐E҆{J H 3v0*n< yBmkDp Vw/GfxG0ԐE\ h#BQMnUfb-m5hrB4Q+.B EC =P!C]H Gˈ讨0 .E\ĆybPz7DIHH|֑BeH|2$ <}gnY ɕ9̾ YpXTdczqT*@T(r)90g˅Fy%+|Ë"%8 `B!b 2AVܺ|@._2Lb&e'@TK,Y 4 ҟȐÇY&++$ɬ,ɕYp劅"S(#;eR9CW@cZd3XBPS$c,=+F&sXtBbLMgd]}FCBBBBB"4ݒyn$q>Bb)ru~u@BBBBBbQ"JHHHHHHHHHHH, tKBBBBBBBBBBBbQ[4ݒX [: $",WLjUk*[z\ .vd',FZ2w7I_o>F.YI=q$Kڎ_;P%PNd-tgydT[\Yy̽iIR"RA=ުj$kt$Y &yCУ%Iug<=N\OO9It}Zhk-S-C+YMʖ>?:%͆|''Զ'N(>=גde7K_PNF5I=%I[$;i2l:$ISH+nҴ;-{4xڴ$IrّaYc_ ==S-ñ]}j&ة`IjϥFLo/e|J5ՏOqO. ֢x6wt8muޱ ܷt[̖ +-5Й|yS_h6t ]ݱ?tGϜU$/<z/,`V}+㓾<^Y* e2P&_^ _ul$4F!ͶCc +)_k4lP(FF!\.lE}1.= .~r 2l\هz\4y󒲨jtYvqk4p%|j"5uSv@@}kvEb ⴘ\r_1d3^3?wb=tנ?8vx{'R Sl.eo+ܹ^oI9C^+ YY_\N&.7QG\g%Wy.plm4{ų*ے*hsnUbbzf~n sb}?Eȥǝ^ #./M[e/*3Ű|B|?,O{s g5UVcj IDATP/p [nF- m[(lڒjR:Ϣ ):|Qů;||бrWAv^Uqnd[|eΚ۝3YF4K~K?xn[ qG yolp=^Of2lMc2{R`lx2Z\zUw XkS\kl7psd,] C)n춍92s9T(9S8|f+UrCGˁ':ֿW0b;4rTW G0m2=n?GKjBX.k 7K[UELFޯWo?},dx{js-Z'"Y]GTw;F oV]ֻۣzaɡ~zAHe+2h J{*x;C*["ח58IaiĄM,w]pgmx@~d,?,BqXD|amW{ZvM.8ݼ$I"mN(Q0č7MW-mDY`,jCvMY(8Z;QFI#iF$_F<~cs8mUh*mB#*Z~rl$LMxHyWLq% G]CnqҪI1ehezcA}v,%J35eG8|}euqCըVSÎƐNYݗua?RԸ:()۳mmOjlUm.G#/kMYWJ ms42gԴd~n{Zy-8/im/ϑ[ O 1<x>KAjr~#k_ Yii:I t㲅Vn*j U_g\g~RTs^X%-]w7f4SF[UeE*xm6Ԁ*B)>qgU1޻~W[m~p- e$:i[fAw|C מ3RSUd d>^mDsOf p݄7?0-6X#l~P.m0]qXtVH1i@kqr1NL8q{بZ0~=2qLц&[j};qC+ҙ-KcL&YC6`pG<!sfv0}t>$abG8ݕ&{U 2HwVluz*n1Y]VPiv{r/Un6(C,q.@ed2zc57bpҕ9uMMMzLNH3[: *^.["oZ˱҈fFJo2lzZ9핥Dt7pl443+B#fL!Xhhv[% @kvE;ؐdK=J=q\Qk7hs8*u(c7+)=7֤s86Z6kHtZSb@Zs{bfwX&:fׯl`hl~ *kOqP;_L-%6*'kfyXm@kLa&*=\)Qt ne$e($q(Ckwwlnw j-X`ͶvC!ȉ4,.i14ȴYP mi[]V2=Fb,4tbyՊFEfNCoX,Inx֔R-{Űڟ/; 5xu"Ѡ*`J}}-f@%S]U,4pe2;"p:x ָ2wTw5Yڝn xuO,9{>}oa Jt7x+rx =>.ժv˺_jր}TeN "[:_I驀uY%Z<藥=Q&5`Ҡޗm)?5QhhAhKq*O7˸7UjmV~!5<CC4X-eA:țYL}8ZWҪzwWu bx)ذN 4=cr!NZe($`BsQ M캪ܪ@Q0M4{ng$Zk$IM$%,nhv ~W wLh/+̂㓣d-,:뗧?M]||%_֬M7  ī 4 \g(2BPen)g֬o~g66nYnkǚ̍ca͙rԜ>iڳJ}{\n]̩viJ/ qO! ěMZ{OAmϾͯ j^Å mtJ} lj2~\zjvfwGVXjK%]]&:vꘔu bm(p~ߠX5ޟ)Q25h%! ch^PM_3 @ӯ6K^N*k)h( Zk$ʟon֢}3/YT`}wLߗY@(DNzΌn[+d|T6wZǷRLPfUWh9\wu&Eע/*J>zv/Eԯ͢lP39 jr~+kT56cGN<a},VzGXu#;7ilܜ MXo`1lH_M24 ~w7ߓk9C4:z? fO #iX$]6aߋYhagel5 yke]PSOvb*HJ祽+-ъ`¿v[a6DgT"QKjoVG:3W3Th-ˍ-&{[CVGs/fOR̽ozJtE9|*mPp{5>>uׇ>@ܕԬ~$m2.K*odzyʒߝb7qo:m# ~> ,H@Bxゾ8-S> -IOTYZ䑿F R2;r"PRH&@ŏHyf D'$v4gۖ uNwbU98=5'}5p:%sW[niq_Djt1msWc~.:[:()9a 'j\^wcOTVݜLJm~PqeۻɴfhM?\_zO}}ڢKok-=2qw z朘J3@UXocjaWc37%y#z^W\kv_ ԃv𠳫j,.9_w_p{-9eݕU-Qu*R Qw6g*g&- ]acVV<V,_@n*7tǁQlAtO|3VYb8.ސzK +Z^g[Un "`|'NCn8u jݵ=KϺdUݛ]VʬG -<|S}lV:$K'6kx54ԱNshkO^jKmw%e66% rrK(r *aWW)U?y4;] 1^OF[-__>Iξs$Iֿ.׋[XcpױfW6!rcg*]Zm{Jˎ9tf>t:ޝrNoߥāWc%TƘ*.[c'\[]Tk7"Xtc3t;~:o*6&!kTlm2Z-M#Ǹۃ'dܐ n1}H`YQPз 7ͅ`G¥mWI<[ZC1Mza:E'kV]0Jr' 8S?qB}V]NXQ2``CT ܭrsjTouO0d"~@\c&+ v/bᡫl 9Рr$X-> ;YjPw V;Nh qΒ2`F*JfP7 gͬPk"҅s)1tJ)vЉp~P&DdB&0 [t.6:(DA䒛8y4?HYN7,%mc'4"u&YO5֯9g@\ -PY('c}Rd!Y%<_VFzKOy2q\rJZ70XonjsdJ 3:JRTi0%Ɇmkrzg=+YiKT* qe~%DgURz ))*>d~pKRS/τu|f!U d%sŵO1/kc~bYjc!|ֱ[&|t$ Xo/Kye=&{u# e~)0R suIJ|0z6Kefm>FR&ok]x`G r'>]e9=fok)âEn8 ?U Atvvs=III$I&%%)wDrJdu_wVJl&̒ _bI$Re[8$2ɳ̭t)iHMv*E4xFD: (yHMRT3g_"{sSDL eu~'_$z1/3;H2#Sv>uy* #m(z4{W_Rة[CLO_39Z{l#95m!4:>ReEyS +0;[,,"OM_J0?l67PA RϚ=ƒqv'":#e[X l(HɫwXwðn:)P7bu<+P%2 (P@ \(- (P@ (.P[ (P@ (Pp]l(P@ (P@n)BWKjgjQjM~ǂWȺI2W}6qN|דdhK^%s$[{4desk<d0dm3Z>qq6K9 IH+qk$U_ZM}>h,$I2!j%`AƻqVkH?x _44eQDc^[,dm`"sGD5Iem1eF2\SXa"mXīO }}$>5Ц&Iuq2$YWcah uԒ$Yz"ևh(+T@FYxmw ߹y(XH| Ԛ70õ6'$9BOȄ{m鹻A[z/|IIJzV=s:5k[}/W74v4m[o\-G-fe7ߌE5<ɩɕx`[EAuWW }箱[3K%[3qsVsl<dˤ|yLO-Š6X^ih?v_! Lc{$ȕѴv PO0БU&b$p:4=1,x΂=%w2W_zf]A~mE- ۯ^ohКZw]v>-ɻs i rY;u4 2Jss/0i/miCsuo3]$,͎o{(h(8m㶵*۵/$Z[ڷdBkٻ%`~[E6ɽ/1JX *e ?G.vA&+ocI_SP.4AoK4z[f50= OKWw9$y2C$'2ҿcQ? >8|n‚,~ @;8X_a5TǦ|1 ЖkGƺta]X~Ghyڒ [-Z@ۿPՎ9a= %y5ɇwh\V-(lKSת es%@ eFne`v>f9G7XQ<.S IDATfHlf#Xu ˜xi"aU8 DT&p7dh52N-@U% zn_fgă`>zdY)jZ0)1A0NH r#КJ`'g١vFgNbzz:3 ,^|Esҥ{ˊ3!)ԣ/NWO&''{}+_][4ͧamU*7uWhNIGU^PZQgE~-oRZ Q֔d1B&GCpejJUwF]ooFSt;+xuŠj!ZDSHGWvrѶ*@WH{[;\u?Q?eC1,a˂nERᮒ@kyNm?Vrш*rWh4 -b9Dcjo XV_&e<z$+8N!KM^iā*5-&p<_^uX{]c],Rί$+Өk_F z^*HuPD2 T)*LsOԤ>ľY`;kϯjs9y iZRFp5-}jCl>d)-Ꙩ#_i)8BH1eh%V#A҅~7,x%; g3q7SX)48}ZWknJ NZ灂ݽ0;go+'׹sزT0%A Yfl4$ ZL)`{3 Lz?J-}`6Ys"ՇCvcymGELE(6F i8n:Snn6(C,ǟMn[{ʿ7bpҕ9uMMMzNx9 .tfYG@cKK˱҈fFJo2lcՈy%MfUMܷ[ѲoZVI#x+>啤Vn kL޹6~r7F76Z6k56OࡵDABfB&Q?VRO#/ WXh*-6{@# ]k#48iA%1 dLʒ*p4P.d ,'8^P:( L@|Ѫ/ Y,O2mhnI;FuVr [)vk[+g(10-+ 4k84V-`Y| R 햲8m)ŠD_sXw+ܯ2U5p܈̗Ư/[VP7(LUl1n':81Q6qcaXp6#ې%vo$bCd) ZtlpͭӬ8n'IEvڊTU_+*T5b˲% < HOQ 3'jnS@lEnx,K dUsM-~%Рۯ}c]e"M[T8Y]s,ot^w%@Zg%s򼍷d=&m-8cV-9qt6rmA'Oi98z:DKG IߐPZSRVgxn^EBYM;dU8Ld(gU6,m?tO"5qfngE P&@&,gaD Lk4=Z}eцd?P[ >(΄HN˪GS%ԅ!wW?S }nF0KcPݟCn+b1gY ۪]0e.yi;PQ{SQ&t62w/;t:hV_x i)Rp_KmEa&P9k*xܳL/K%>v;aw %5n~d]iݹE +uv穽 %D&}G#1jӋ)ԯ+W-^R+z͝},۫]FUug`i{\Tǚ%jRShO(,|K5xEgn dY*7 ]t-aϸ"sS: lׁ̢n6? 9{>.Gu%@Uܺ6kXOdx%O g5qW/:/hɰag@:y[dY[}iٮj( qI.i\6(bV7h3iL׌8Tr J+]#֥ˮ#n`u@JdT[o}5_ٿm-$ֲHsMXx앗A5ݹ9S'5@{] bo Э\9.R\uIgm'{-(fZ o8C)۞}_+SԼ h麕|V,#}M__oG'ޱ@#{,_Q='̪˵k]V ﹎+?ݵ:&%㬽u])hC3p/ #VQwZbК{\+j`F͎vd~K9$-&@K>EkX/M9'~|׺Qͩ~x}Q^wɖ ɈCFmQ+hxϠş2Zs<3rہZ*s;c\KJ l"@F-::>Cv!3X˟=^~ob׍gYrs76e͢h?6$v&]x(T,^'߁|u۪zƆ iL$gڰ!s!=Z)YuY%o,iyB^XuTlSe y $ U@ĖhERR0_{.fATjyF%2D\JDT{ h>9%圧j~@kM˯Гm YͽC-_@QhwTK7QTϧ+5 o/)Pf{o(jUގg󊷕%S5;l[H-!o$^ԱRm~\ |ojLWve˿ 0j^W^KJ h-ζ2!dk=H!^ǝM\{uoXKI߇W7wXL/;oI^H}F/$PyOiFkxx;w5ï/=JXw?:Y)(;^Ot3gT*D}zݍ?,;6ĮF\F͎)% Um}8`Nn>t:hloʥS=nUQ+@[-NV/\ؘu#+SӶsk^= xTWgWwm'yY'H{Uۯ 斬QN D66,Ѽ1 g7qk6aKԸu __= OPtf>t:AZ VޠEQxS@s$Iֿ|$5Ȟ%\$5Wiiգ豖>ؕY c8yc>zs<(PJ !z  O эBB /)| g*6&!kTm2Z-M#ǸCO  ~]Lj}+_\j7OS&=0ZpSFmNlE^w-x2(1=xq\'U٪:1JW{(`‘BT ܭQ2Fd5Ɖ8yJD˖M/Uj/HEDhiGcF %YkWB‘V9)bn"KXd xTgl$". &\)`Qq!d&B_˜!NƷ6tߢxz̡&YO8>Z Jl2႑Ӥ T oxSΰʫ}BH'hr(y@_8)Q]{m#P&!aRm&8sMÐ"fs9<忑5bŨO9 IPQǺM~Ŷ+1^{%da}P *TM~'ש2q\rJZJ7 ST~3Α))*(JQ `f\S2ٰxMR\JwuJ؋1>VZ~3΁\L<3DgURz ))*>d~pKRS/τu|f!U d&$L}gkXt,AdXD &A4y=JO2v?gpyyx]n?]LZY\zyLIfurxrDÇf$ ~OMMq755oܸFQ`meθew{"cLMܗ>ߗ 7 (X}>Nu^ _sE Pn) vavF!oxgܩ)oC*P;}F7B J&c ,nhƬ@@ HQ-F@ (P@ (㓽݊z*P@ (P@A!(>o^(P@ (Poon<oLOO(cG[wt (P@ ("ļ5l?E-Zhhh?oZ_<G||gZ; [բj՚-x$0p(OT<>!|;R!,`jV|`U!#.[c [nly5aϊZaiא1zE,C I֖R 8|4}vo!̖$Hǰ+#}8\B;u1D֦&*)#▋+!ܷ`{HRsh! IiBGI8jH,;p$"G砳$y؁C$IyaJaă6 ζ`cE~ڢXHdC4-\$VIR]%8Rdh,m9}$IY'57&wKmUq$ھHʜuՓ$6x~?رc>ϑ$I>+WgffKu-yqo^]A.\1>@R^ǟ;lrruCC {[0_zv lծ_vxrg 6޸fSⅫ/l^S$ҵ~wЭ`8OL\96F eP~fLOޓZ kx]ͱ 5{=Py9 @ζg6ݏI<992 X[w׀oqwV6uh20Szc;W<;qrsw͋;{_ؖ5ɲ6vsKs6W |?^Ͼ)X(ĝێTm͟D%{jY][k{2v`+_@T $߫w[Fw}ZZJ^ih?v"˖/<_+ YmE$p%Tt@-8`]Y{\MnJ0Zu#޲V7m6w PPWYP͉I\lNR2~ _\Xxo8!_Kw| x-wk]YS9_B5rf;/Yinca[Vo~~ߕYK[_n•kK0R~Q;|&ps3zcQaa2 7gUɭ>Ж5wfc_,z $s}/Ha[#f=q=<2Xrg%{͓~(T;sڼ{,Baʾ[|Bg镺-*X:}mް1?zweJ|CciÍsx$¨Yިߺ5[/sp[Bd_>2VG]G]Ki\NEsXFaݰjF8={K.O?tJJJJJO?srr.]zEPM IDATg:{|;aƍq.K|0u[R#Iuq'ڑ:X|eU/ iȧamU[yU׏ Ы^y6yՆJ;wՊ+V^Q?-;z޺',Q._|05%ۢTz մT*kMYFb'E.,pP m` IVWbo"IR,^|Z.kqOY#>:ɰeA_)@p׎kB%Rh[UqFT hЈI0*\㡶CejR0G`(M?:8i+9HWo{\x\9}>w*ܝlV2GV{!iUv޿\)gYy#b^LE{/LOOOunL ubBdvp&0~/ i◳xw~ ?~Ea=juDxy1&fmzG4emmyyUm|[5e-^>Y!y~x }Ѵ :ks~hxFFbj%LIIڗæL p4u3v$Idp8ZcI-̀-DBme +jC'}-a̠WdBsŶ &L3;3Csx zߔjꪮaDr[bTs?-ZA~WjdI`JfSl/u4&///OS1˫bhۯ&{D Q]#;Nm+8敖yƙL*5-&Qᓻ]ܪ&,)So G64 [^2u~0}}euqCըV]!1!CZKB@ڥ~m0 Wme0wWhU5mej$kÎƐgY]ϯjs9% >JQam̉:T_CÇ^裏>o̻̼O<ģ>_5" ɛ =[ov/\rΝ;k>}jƉ7A;u | 55AΝA>YMQsrlTw&e09E|dAd׍o'"r 0~&#{='AlwN " d!sb'A|+3"Lj"xD3 NQ^SsA=@`g/Aʏ9 1}^>E=dxOg]9AG.6 uܻHPDvM/ 2vvWAD;\ pdA{9ql߾#>SGcb+8S@d;sl{6A' jj 2  cKm"M/sNXьט}߾}'ފ Mu@ p YJDws\(|{ͱΚΏ˪FhJU7~fAlyn'Aٛj:GdלaɉDdEdD`vn"DHDv]ɓk2 _Ֆf|_X?^x1A 9ƒ`o݉=u; 93.8^C?,N-^k2n9EML9WCŋ?_Gg&˼a,+d$)$/^a"a׻<3[OyO}u{gA% z!f}g$GFG¦x `%DGxA";?~> CiB(sXޚηKc- geLw/ 2vr {&fff|hffff 2+?J< 2^{#C='xrDdwV?y G/Fd d<kp߿g|Ay'^}ޭAdp-Z~p<֍" |hAq2‹/x.]{]| ⡇j?Mza>C=iӦ\>C1M^7߼x_򗁁:u/|C(& U05po|~;AƏl&˧ku| )ފHlEȅJ&'( N;OtXrUG}VAI.. bӉ@ 0q 39#دAxGbiqs_P:NX\HLcY 81=S N moDW*[G b0MND#BUOf eKW)ը˨`{7Q{ uvٞAQ~"2a%yH: eSVЂ,ν"WPyZtwpQ'l"p> -،st N^  9? 9sy&b~f9{YgLOO]Mŋ?M3nZ]}7 ?i:CSB?&m-^x`׿l"?@(s Xp~Q9$(G$ڬA侞@ :d\iQPǏ;c-s@j ?V@&)3/Zywٶۅ>R66<9q'vft':Fq5$c6 (8%Q<"XXij*X-Z%m7k\Fw'z.ʮn%:^AwEK↔*|TL0Kf93s jՏW1<<|>s,S,SzMZحӭBHiΣCQI?M@BKZ,^ jlXpӭ-1mO0bXeY8ooYuVPk|p >X j9㶷7~d^;suo4$/Ѿ SZhSv>1egyAf.Bƿ*;ќ)GOH\0mss`w#n:SކLEq8 >6A/(4xyyyy E4/3\I]dLNM-9|7d'COpqyyyyyDp`Hxx i67=)ax0q DURߜ9ّs2?E)Ax!e9fB[eI,_҅QQsvrꁲ'p'UԥBh4kx ޥOWGOx~U楿`ǎO={%ee%%% Y4{W?3 ՛t-8M:`cXu=i^تooGP0 lp4k벙1ѓVi  @~Q8 Y'J F` B+8azNRj2uQ}Z\Tʈ6o0vӠJ=V?VkAgOMJLY ۊ5e.Og3LIPvzFrlZw}Iŋ]x`8{>}ec3˷1nt'[N'}h w& +sWk悍Ͳq?;.h{}ӲaN4S ,sCV&ٝzo< Y[\aب'Q.(C<^gqg=w+H]Ӹr0@g!Th{| S}J,X H˳`Q[O-OxAR9ǽzM5l\b1caFfN8yUȃxEfvGvwܗvooFsJ쿎>M%e<:Ӥp_ 3¨9I&|l<{gopJII5T*oo;nae&%l8?K6Ew3lwڽ&J+U4s'`skóiUs3rS2:-Rϕ-4z#d{jw8꫏6x(tG;%Oz6/!˷>^uQ1m8B)z⭛Z3=,C=t%8f̘ܹsJݻ|1c|}_ PJ?7V3- ٔn)]1y (p}:gn ] S)ŦEO[[eޟRtR}23/ڮ$& &9a|M M)]#'LvJę7(=7a0G<>nց 3>"ޯ>ҍkUP_R9 W{jQg1xSYAɌb_HT!nbg֜Szhws7ey=Y=bkP:ݷV2KMBti3־KV>}-.ξ[m7Mbsg~᥹5ؽv*}>?7/9'/ub$OϦ4czDos/?hB7U2A)ijGr) Aֿy{#{b`β>?:6wX Jt˼WR44=oŷ4-&I(Ɔyoikĵ!I+vU)=y0#eS Lwx?Xw쑇cG[+.ƍga-g ̞4a"?;yf+'ӷ0߃B]ɉwܰ{y˳'%%"w8Qn]l<-[]~2:Yo ?(avp)s/xN>cw0 F?LJJ3gBP(Ȝ9s>Cؿs}ּΝ%ùs\pC;6>K 4͗'pő!ԷgW2-Qsp"dz|/,͚]񫊁'9ϰFq R667WU -b&*477U`ׂK5g yn҂ߏ藸e5J%?< `yUր@cEE^`6-=rGܫlGD.ǎH E͝5ܒ,Pp]VlL}Nduz3B؄͚%/:9x>=թW-^h_* cBK;0`XWCz X4Y9"MsٲC{w~G??\ K=t@W0 چ&tp@,*:JVNKN_RöNyY> k#ej`Kxeެ9]~z؃ac'>l.x?;Y]U͍^I&}|q9?:a[u Ru1Ɔo(nԷFx}/{=l,6%"n-olj/5SP,)6V7GKK=bBPB7lAS6 {al!͵K9 X9?g0 i `r9V$=0-QWw IDATkkQ!2/ifLϰfxŸyMMފr~TTaf2}? %sZUe%(7~3xXZZ~奥(ne~-s6Ov'Gضm2lݺ5**jĉǏgN:ug}v'Nںunp'y{ӧOk4'O8qeee%%%EEE?q{37}m'C\e>NJli&j*z0*#7RW8Fي1B.^ԥXcWJo^젔;DhЦ )+--=zE< xꩧƍj :4_Zό}oC'\C*uEyn -PV +^05<| A}004=3E|'; /}Cl۶J᰻ꫯyE/@7*nEㇷދ^#=~{ik{ ).+}]J?nun{^}ʼE/~5<^J0e{ձeW/zы^hn CE/~Ġ2,j?H{ы+]CE/zы~< g}~e{/noUi 1;E/zы X8믭rիW^rRh۠!]JU(u} 6o.]Vf459i:NoQ(N~7AFW1_c> 'g(Q'ֺCK#vZzk̸-e:g)@QXNAl,Vn矡jg$\͢"h{RPkU{"ߢq^vmC20tO17ؿt'S2'V(p;މEE:?sD5*g'5L;읡 Whwew)KEi[zV/"qmc53 EB^sRb:{G߷wBX`oH\3Wempp5[&ΟP(RZWtZBꑵ "Ug!g ѸnLB1f8 ZV.dnlU(Y {O?$d2L&x]w"R+Qm~7af-;N3uN|c瑜+kW|_k;5uX?&y{CW3gI26ixL]Fo6Аws?]>__C0z;dKWqW& 747ީ5f<0ݦR_WW,\*BmLu=M'8lPY憬AJ,/^4(8SiwWW|-n ҅p.`aBm˚,uws6hNx&>4՞8MyLob[ x_fMT0^^+hl [}1 c`Qm8.k;;Oy.:7r#ޥ%um6]<\N}5;sf%"'yNtIJ~lضmۍ_~ڵ+W\|ŋϟohh6tKh+Մ5jB+;(d2^ទCHxpB!)Z:ᄄuIVI~;([N&oG@!ܳv9^Dm) &A.<z-먌HF rTG(njh81<2 ]0#RFMH F±R~%%.&3k2EofgUv{z[w?o !u<%/s !ʴZ֨b|eGe !) SM8!)߅vDGem`UBԕZz0BbJ-|Iۢiy ګ;(M!jF@H. h+pub[+hő!$hk8ɼ.=nhK?s@NNNCCCss_}{ MMW?T8Mȶ?z(uezKtu{#YubC^⌠iu6.!"hK5W$x_>C&n.x Zy4Fkfu{AK$f1!_5u6QnoD:߽ u 镠Y7A:-Ժp(U:PjOTן kYY%0mZޢDB u+#=-5\* Rj9`hH_j9K|P\6IrH|-+qNP]BDDBz^zD^)w\J;PsN_,p([vD[27/CԪVsIۊz~ݢ2q%"\vŶBQK ᪔@b]JUBr}fYIn|8!Жp:R,3VK)VBr;n~cgYV2 !d^ҋei@eZ,p̊*BTg(ZjR /=PF^Z%ħU1J)ZQRC Lq^OF̻&LVSI3 sնeZJڲ,Z>ҵj-)\ $^4Qq5g:麗28T !JiﳣRZ#u2B֔\J DŽ>H66C'PLa⍦.h/XXJX}x}E,G5usd2yydUDqd?NL&?b9 Z%vb9(/фˑū<|oˑ9MWeTk6**mE\ɲ,;'(B(ZVq7&,:9(m6Dy^;AǣX6t1AL&$j5DZ,ˆYq^rQYs:W(u{rΘƱl) BuSl=AuGש9]Q<xX6l]X˲RZͲ;{>wmJ[ʢ !$l-(I2/9PTvX#$\{c8yz6sqxUQcYKVՙt@UfaanZJT:pU!1L+c)HSX^c8/E&w}e:^)Պ{bE)U1xH.fQjuFy(\[l\BHx|ZaI~ښXjQ+ !WieJ !%nz{ZiE)5p[-)2yn],ȵ78.Z]!^qǥjAV$BrϛDmvȇ |[ILzIpBHLJݒזxBȌ:1wQ9â~ׯo^-YO!1eekfB op8Al2V[v(51;^{=vӧ<>裏>ڷo_FFƞ={vޝ}-[._/IXfVoA⸰7~Ƭ:l|j.qQ;ۅQ 8Q,-v)A1o8lCdA⸰z'qK>q( x\z3DžUQƺ1E}^m<~C7uU}r9,ҍvA_8.v27߰ʄER/r[6( ;|ŅDI3?wM4!ae+ɚ^B83[\YPʯGNR TSm!D]-ͨj( ! ɭ'(ft+>SJsjejR,ǯ tTD…eT,VeYTD%N#jƳiJ:.Py?i%6ͧNiJRN( =l>VzvM !D) SpiJBHXeZGB{y^cx|fN.Q^\B%T&Իe+KWSsXDi]M7RM!:eC7V8MQ,DžgTyXF T,HB/A3j<][IxJ 5 ܋T'MDkbc "nB4,BTuO41Z ])-NW zq pnW\9sL~./A;*dyh4vttv>Bզ?`\;I/mT>w,_bg4̊ d~Hhߓ/LGlS 7mpῪ3=X ̍,!Vody[xhʌgC^=bp)  g>-Be-Y;qcEGNZf:yg…Ffo]@ƜQs,L2 &eͬeLd_ `HP(d E䢵/^<)Έx7`Ţ1/>6/L檼)+dE}L:=CP|h,^x: `[}aI1pD@oOS*:{ J+) YXaCkcy՝; xD6rTMQ,~QttS4vN]P /3g&TNK:RA+}ߏG0?EOl6/mBzZ<١gk3)x 2fp0Voi`4v>LZ0_Irhu="JRM'oΗcʋ)et?Mxz ܬC/ka5V!A !䦑R*㳏X2722?`OY9Tk:# :8p<A)־!֎1vPpaG+Ҧ.Gju@/y,t pRѨ f NV[d dq1 YQcg蓑HkvCRƿ0ulXc ZSJ:`\C B_-MMy~e"%\)ƍ'55uw?8r1#ח)I*#5wԑ2|7N${O $?05vg9Y//H?cMIn3a^hf#O-GDFO|%މ6Y4R| zvꔱڙ*t6=nbޜ: NXXl$ L?+2,4 a8Rm^\DE|<KQ"gⅡ#4 <48Ĵ|S޴wVgܝ)7xۄ,P0[[{&ro o^{fmIؽ-p oM!2Mt$8yu\Mp<r/^OzwB-q:q'~/wfOPn;`(,&Q I]9m4xS楒wѓgRJ>Jaa"jW4W>0/AgOMJLY ێYXy.w@P؟Ꙝ %~0`&`Ѿ ޢ]T$GgO=l2yjaYq$%LYe~Op]~:eeN}u┉?|3 ',Xvd@Of ;(tY(w(U'>qZk^l4Z !뉷;Z81m@B~~~MXۆ|kKnfqw<)iof DH>oKCr=!_d @Ԥ @!čEl'DH]4G_>I4kY0`>ܔx%tn8}h?B^ Z.us~Ԍ߰ !)ĉ#bp}0w?:ԝm2vN"EgJ/) ?}l_//Oj.mvWfS8QR47Ra:=:TWo%G1aЯVl̉JK^^}v|i Ճ3w,ԕxR\7/]_0;sh=#Cա gq.I9:8ؾ8Ad6> xc0$^o/XgJ:bV KJ(W\{Pͦtphi ĭ੅'{cNYtk2?rJQP*'qZ?5|OZ82șkʰgjhr04/_Sfhy9Z~lf|=xك?]Xc۽ytҼ]潜4)Nf$Y?=b" P co 2=dȃdq1 sӑ GVGΓ3cY3*yѶy:Uz4 ~ @NM=?D֪LVuOG 9v axp_ckܝl62dد+ġZЀ 0g_<scڢ{^pHoZhe.<>;-ӭ~̝_I2kz3>>>,kY]8m@W3cáK.M ~)tYV6}FF 'NrڥN- {+4z&:m0bQCEemQ )k f=ρʼYsrƇXvRd}xCz8-o- 遘Һ-EMl YKcdU5]jI_U <ˁ>ç545UKu\ͱ`ԴWQϥ455ڛ^ }҆g.s'aK 4/+qoiӵkj]UlHQ^yeіQq9@cEJȉ!ҥ ':uA>f[( ᩩO濜|ӗ(-y/p,RxK ׮WT4wND;bG EM[Ӥ545UYFa#q2}Ni񡼢gg7.fF/Bjᆂ 7}O6be>#l ABeʡ^6+W_m?ag54ZM2RS͡%!K^t0,# ˩ 7|Mcq*yw}Ř>-8q1csGdr*0mA$;"ͅ M/l*F< z^BξܲʢrRӵ,3`lt-U7d/XEϕUv2MǹinyF^r"(aBʑYu7D)JnX;aj"0,c5vO\eghBO5.Z$&ԃ7[܇KOѲa%m?lAy~^QeU^92q37].JJƨfHaY(ݷo߾}}}}||2Bm8!ꕔW *(ոŕ:6)>H &bGNԄʺ-yu=[Lcm y5MM5_]#zPaQiֆcɶԆe?@ **6,Pè8w'[CMJR>)R?Qi]S֝35e-MLkj8u:yNOU[hҚC)K#9[JK(8tL#pHyyii5MAO A ]2̢ſP- ^~Fc܎Vށb yƦ]O??8Ҭ 3툁͒9YMתCWcD"Ѷ4U&iR U,ȳ|*\R+{kW+2r^ !b*\k ˴vHqجŬNV9.n*:rT y/3U9uFe G#2*^őuNɒEHi*.,~Z‘7SIt9Wg#4Ħ1*kJuViGIMţzmQciP;Qc)vDŐ|ǣ9[c^IohX dj̭u56Մy*kuŒ'[UOKH L#ը jXKMvS)2=t(+cG);N \[ $׻yn%;z0ڳ1p,qZ]vX])al%;j$EvLQAdwk1Vnx>*nUZwӦeר2UÂ31G-VL!>rgcfdҦ./t1MBt2{Yc ATHW+,Dg*m|ֺL5299-ՙ6?$US5!6֮T_VqykUdHU\SBEݎei[T٪%o7\GEHBbKx2JɅitO(Gr"9ͼZW'D%YwT`%̍c˨{ّa{dq.6vf-3kvg?B}_F-%ċ<~iZZJGii%yYx&x|gY>|@Tkgv=3{f۸P/5(oؾ˜9v]盾]v1|7e9MEge})LWt})ȡ#uq`kkp\u¹_\ 111u-W(oVݙPe}Vvvɓ'I6n[}} Mgaliv@kձY25U,OR6R,>ңN2Öxwɰ2׮lKMCx9U(l7i|qKx1M>>e*g%B->VdZ)boW=Zk|hO2#(>57qSS@^Me^&S/a),[=F n%9N HM͔!ܫ%- `kU[j*i)lQݴd@>O,!8, ySS I^4}T7{ Zm¥HYOIMt_{Q/2lvX|O=o#I7|SN$Id jj|* @=}tgk_~r &|zzX`𸹉Z 7ĥ>**Gj+qԩh觪=gK=Q=ȿ帉}=y!AQFv_陳V-y T6an<}|jHtג]ݶC'󂤨<F?8a.欮uy! GΛQ7x(xlcW gs_OkC]Wr[ֿ0"Fn9bDu=joRl3ƺF-ʃS7SuZ Gm|C,wKҿ@vTߢ=[+B;r8ED iA 8'F5«)Bϙy3! #Th)$y.pVps)J p8 q8p8psp8p p8p8G78p8瑐먌uUrl\7DxY/_ܻw/j|jBTUAUU8<><>achgˊ鰆7-6QTň2* oQTUqQ LvdgUvDpܺuhY:G:qK$ߺΔ?R_lٲhj-{S7;ڒxI1h=ggSeEQQE0mX\d&]E{ #𯖋LUs+~Fp|Ө!W篠`sa/|ѬQFD4eʼrqU4 IDATiӴi+'c,.) 1dX7Hr>o^0EQ A"uK0AQkrjm$AEhN֖.I!3r'a$/=h#+JRe8/H楺AXWHwʼlHX3hjvd$uq[ yL%Dȁ|ڿmQ$_jmi_>8 P) Pc1[p8!['&EY o~u`ST[l_/oY B0}TEaDE>vLU$b΃5S_ =**DQs+bŋBrSi_08qc>f뱻gQ ɓ( j"KB){7#0"$I$I0Pp,ii$HRȼ$=}yͪ^X"I!3nؠ(,D*?085)TEQaE,?!D PUQT|SeEe*KЌvX cDsE:ZUcZ3}*l ( SUnpq8"Geh ENĎoӨYR+GhT:姬? vt[4zǠ♨Ҹe-;|vru4O:|űMDEA $geI",AըsCƥo5inRkBB&?4!YL<,J)NOŐ!3R`q~3we!R)2JdXO6v1X6aCnaJ&pߙ[mO hâQA$EoZ\0QM2.& rU<%<(H\6J t/Aj 2%s~J:YSUyBx I ˴CF(r̞%Mٚ`[]BFm7 BHxHHHHxxxx S+$%'$^_5,_BHPȨ56 QG $, YPAܺhӔ 96;ɹ*g̈́! yAC%!{ "$iفԌӛ%$IA]gpT~`Lj-a(6WW2rOA(d D1 [ :R;CT\i9)A@op8EB=-#>-ۍV+34O ‘`Dn쨍RkXqcr6J ;#qWSEw~TYwMV/N|M7u XA}7" K Ik2yuyw\}cɸCgF{c+*֪DUm]qOkzqt~PUU1C_v=9@գaL,.k|ͣ;umycW L?mXa;L:տtF=3E8r97F(?`hՓFm2/k9'lKVRj\KY˸q;79IֵSBǮc bc' jY~las|@7mbpہUZwڭ/7EllvG%n7lNRčqWDO%<[Wl3ӚL^Wk ^nC=SMdF|!,"cnNHX11bΰv&hfvzJ|Ҵ>B8/:"{NʻRYqOPȨeN۲“lqg7E&*=!MȜlZ]n zbDҦq|s"' cN?f6W{3rN63e |D'3_~`2`/a V>u1 '8oMLLO?r…o_0+S;Xzʍ7f#x-|Ϭ@go1'ggwL3W\t҅ Ξ=&1r݁V^SdN*U4\9TE/RJ <%"c> lhWTJiׅciRJnh@)tXᅔҹnjSJH2giI+ #9gGOZS`u.+SJ-bw5~@ʘwjޞZiI )ms]VԴ5՞S^;DةՃ)m&cϮYz1Ƙ}iWJ,ɘVvSvG:cK) 3gEQJפ0Tclόґ Wo@iWv4XҮ6x5m1X66{1Ƙ~쌱])tƎ1f?ܕ6s܏zF;Sgj>v=(iZM)mf2AI+m;V])#9fdGUk`O7c(K)ŽZA.G*J_݇RƔvq*1Ǝ-C+*쇻Re~EU$ D&r>Үkrە6;>Hߞ1bqSc*l7hNM[l"vgWߧ&*]V-*_+*_æMϝ;w7oUټYXҀ_2mZVq[0ܯcɁg/[oeڮy3e=tjΎcWl[;xF&@}s,5=[qΌ0BRTUU},fy@ȸÂ)c*c;Ah9ILR&4}% Z6dri: p#XVSk+}k_Ks5u@RTiIXT~2cq'/^vm0u}7e4knB@˰m[NՑu%I$kd,p#g0[gcϔس3%URQo|9LKjy??w-JtxGHT.tҜOcސVǒc=,#.p˞6+9.g gx9\;o9aHxsB;>P Kq d!CFǚeؐM|-"\ԄLwQ"P.<&IHijDĄ뢆} 2a;uaZk2Q<5E ZIfOᮄp!meNG.dAw2gtGp8 \Wh0[ٜ@ܹp8rx¿uLr [彍hQxگ7|8o.ѽ҈Z?zz 'I/(sEL.AAX0>DN3mQñU v*VZνܮLŀ[*tOEJ-ʈ4] G{5˄1*Û(8P.Y#TBrE!6k̓PiޓZ.a~/1vs AccFkbU,δ[t& n*}>@`'snЄ vZ;&\T`Pd]NC:iIac cǭx{r'IofZJ3ߖI>ӿ;fnб_ Vڼ^eIh17,gMDQȶg,4o𰫇̟9b[{NWPυN@"=HE((@Q TT|2s8Hr;(%U}$eq8 4hNcn\yua/F?u͈E7"F7^|&,]{kCRӳGqȲ,Oy篧et!c*p A̹&D;OKPUj^Ú"f<|jLB4j93 U۶@l0F ߺj |LؓVYL&K6:նW^~  y*eL "ֱ:"ZPVM S6l fz.oE%85븨ACZ]]ޯ,%*HC{ӏ(~;ޒ5JXa^9 ~1-Ԅo49H~gŖ)ink }ɣA+5 Cl;2|}@qߝ&wr}Tdr c쾹jVC-*dؚmǺ7|q!H˽S;T"9[ubv-޾9Nr z岵-R K޹ŋ{XGM2E3UUUAVjK z u_ }nΤ[,ۙ_ByG܁/ DR>/$m x{ 68.Ls) oOU:eω-tq"-50|Pdky[@5@NVտځb=ꥌͷՎk⧵ $0b/;!QyWVoZw󔐺A]"M5R8?9 cGkȳб-nmf\<ɳBAԾoZErsxuŵcÖDu ۤ]γ9E,鑓g  ; %j+@c"c5Y;K౱jsG芍a:V?[s1[E_Z1Z{*kq@ܖ9Wvw6fDiM ui4uy#4I@_B+/q[j3"vrOb䅽3c}([u2C]=.48r}&8xt|v_h _4 xsyGקGč n9*UBTVL v|h%p8?B9 111u-W(jҤP^d lکC'Nի#ږjd>SaFjj 'Z; [YٖVRSӚ++ʘ kY?-5.y dR3xeђz,gFj"Y,a)C4VTa3RSmnɹ]qz+/uӔ:VnrFjZ&R9yΡ0K):uL&IL&osSІ{aq6z2'p*ʘ[K }O!˒c3&kwUL!+&p8_w'$8Ro[ץiضp8"*yY8O.s?믖p8ȝ ׮][TI1zGeYQmٳgfh?p8O@h>[ cY,YX~}ʒ$׸iEe#܊֖ˋZv.*ہq򨵟[Jp8cF_xVVV,p ]&څ{ZEeƘOف8Js;Nw ˽Jp8c@L --M37YiEhNALѥ(j%ȹy(JݘX ,p>p8lqqp8ǏPvunO8ہj~(EQ 0CEUU)ed20%V8@vKv"﯅D̿LpˈZkn[n߾}&iܸqDӌ"^u~}{.]f対jv-yT{nݺ,4i˼NW^C~@32=z4%e *_n7[TUMJJW1+W/_^j.\(S w&$$k׎Rsĉ#G֬YS+Nvvmۂ+'ɓ'EUb˝87ײ`R1 `*s'3kJG{ã~n*gQ Gܹs~~~;w\rW[vܽ ]k{FĿHHD)5L&l6k u`g‹LkivRƨ!*Tx/\gOB۶بQq6_C+LjղN4+K3][ŊRr*գi&$q߅ CBZgggUU՛7o֪U˘G͚5e2*W 55ԩS5k4L[wڵD.]jٲeřfdf>|3glRp8G?lEAXgq7Ԗʘʘ¿~u_$bymu=֣E(s W?U8: TZUQJ+T 8і kҮ6lvW-I|A}T?)"RS,;,}Lˆq!ѣmB^xayݺu V\Yxq=iK(Y$V2E]=;]/ˮWdxM~f']$ԌQOJ>Irر{ﯛ KVžM4$Ӫޘ\Bb޽DQdݻSNfժ@U .TZD+my8~W p:inyy{` P(~Σ]wL =[Ti<:y8g:fP]z1loDo %ֺ((j-N5E{拯l͙I!T8v䇸8(ʭX[;G%n޼=iҤݻTt钷7גӪ.DQ$@"HDJUL3dhI,1Ƙ$IfY2o߾{}Ldv?QYKʘ|g=tq[n:oTM ̸s78 4xz%W5!DK\;h IDATbuxxKv%턐'xBk.F[zK]7 tb|*SPӵ빍ܑ.0 ܒ7~6$& DvւyOIN12޾>Wyo>xIB_}Pݮ V΀#xߡoKfd$M|ͣV~X+~ ^slew1SB{.1 Z]lQVhr=)0d\LZbmU:JOIJJʭn/붥v|fde@VV3C;%0rDJZ:hEJ ٽ~a+YfӖm.c&}+C ʺ]}ZTөGkD l^XǶhF[6 cG}iQmnQ_f^v^KǘGjF\E(+ƣOrom};K_bmgU08S(UzMJ:DcI~}#'씣?!Gp˰Q ޣv_ط2'#BK˕}"mJdZ@ʭY T^{.~fAp:|uose"Aa6`a/Ro=tc P Hbj+ўP ӼfIM4Jd/-jp3RՊE"}kSǭ:\³SVRZtۇnTTr^u֨um=j*h=Jk={-ӹ2ul탬ώ,h.Umv5K xU8{wxgԀŝ 6K_.$>鸾 ػYEC|Q4b d]9,v{!~''6 *BEQ4{Ls1Z9 gn#lo. G}.)) EQdI:~̉-Kg]ڍ]ʟ(U:u+lnD0l;~!⍛7M&IX^:n zСҥK#櫕S;P_bD$*Ʉ2&"3QL2S)Dsf. zt&Jj5[l^\; kԆAl;3LAqkvbffr~[@KݭӲGίwuaF>m?쇓C5uSEq&˲~.6;.,^ T>un=o9/ϔ7gߞJzzͶ-՚ukY @GL=+ W?[hX>^bm00JL.9W *u ?7J+m۲Yn8-[S:X[lC +(eڿmC$I/rgـ#ޞkg&N-~*QJ-e$S>o%4m y=_5hG}y;³2cK„ޑ$7F M⑟-rWA]K9O!;ꏫof#tg{4$I86`vO3j$nmfq2 [^\A ۡg ~J&_^XԹG'|U, "ƕۀWc?]zVDQ!D_4'@$l'?'4x&$Ac>@TFSQEйG=Y@7T\}u⻯nyyϔq<$vN/ϭOkZ,8~Cש\p m+9x$n޿4mZt䛧lRmen}j͖քN^3ebq7ѩ=$꺣_>ӞBU|߷<h`J`ܼzd۲*lO$9_I/]-[Z~dij|_U^>o^|^D/raۀ Bز+fójMf#(ߌ72{S{f'l807vJ=o ,IҭC[!t@C?vڲ?9mlb6Vbq 9ӬJsT愪Հ]n\5lPAӰdl7(Ib>hhz`$'ڌv="hYSWL'`xfFN_8˝x0A@6}^y EI~ƽhruNƹG,Rd2QQ$ٞYئI?룔3E'xsUcU=ɥWC7 nL$4 =#-4Sw\9:{o, ZڛemDaZL&,ۙHG~*2IRYΜryxݑZ` Q;*:Yɮϋ>3m+V$u>{֛JL#*0"E`]v-ޡ zF|.,Po7imzi) Jb}q*V/{*AV"hnݏ{X:}rVXpY)ER ~Q slͬl4\td@x]^iXN_U09u&E qNZ)1@`߾Uqx0U b&|(nb2iY ZY=Wth{7yU޳3tpLFm~˘Դ~ӀgU_{=)drS.~COR̟陼4b,4lޛ'?r#0a(y L51!jgDt zn:kN/654sv*ݙl6ͭd{n_<[ep/\Z֊4sKo?%M)n!-#N>xMk?-ݿQKSź72;t/s(K[#GX|7nDFrW_}uС.ZLI*QʨDLdIbD$-].9T@1/rxxXL(ЭC7\/]2 8@1I`r?3up]Q,+yrn稢(LeV8^ɜG8 Ce??ͣƷl֞cXcA1>#G|Yf.Tm8c?F^7ˈYfLrIV5\b9oҤy+ՆƬzέ ޔh7yVdћ4ėZ r:V0&8 oX>;otvoLӌaԏ_~<#zS"jAwL{MҿT{}Os6N>C;T8{ä @9&{3:e &g;ЭMcϒO7&T \eiO|q9q:)qDzZ96'LI jEzΟ| X [G\bn Jo?==WX2c@OhIL6R ]!1Bd)e2!M%{'MKQG%X8O?!=/oDѽ[FWfx[=D_V +v̊DyƉ~śϖBvNm"4ՔӬda~e,+y5&Ǵ;b$5k`rc&]>XqF6svzMY:l_UےutS'~z0z.V%/y^0KRL\ZmC{E%@>1oD)%&oh:^>xk|<`b1n9qo/ұ83֎j=O_`/óvQ R~^3fna)u\'>F&ˌVLlD._ e,WU݉rMJx HJz'f`C~Ps ᝊ;'?q75fbQe0P9bDx(ԋ8"ȳ3wèc{'zaa@ѧnkI탵jW GrrY2i 6֖\=-IIH^#F|`|ʡżJ<dDɐU"?5͛wySݻ(̚5ҥKv*[fr{VR* @ *LTD&QH&&D*Qu3dzrf&),+Q#ѣs?.c10T >B%.I1$QH&#(ep^[=5f'IvV~(GOLrر LeMVy0œ3 ^N)2-[#'>4'm0h8|pWg'ú^f`RH6|"g<_ !1IP3 ,/iK{T_`?32\NypV^_3#ֶ5v^UgCLvru￶OUס$} N~1yxt+7֖6k9h1M^Ĭ|k]!{_AoGQ++|֦W ¢WN"QJ\~⺠1sX9bo[8d 6,U'OHy/̍C_{v bJ;w4 54l!y $Zx͗LTBΨ+wDOH 7y"qۉa8ĆGv/f{*\ GK'#قؠօZ7)vn`R}?=*_!tiP*O@u{Ǒ=5<&wcǽx]s[t$7ІY*0@%s޽;o\׷Qps8pGEd۲*? %lGgx{A z,T0lJ6U,eJO>]N !Ygv?)/ek'ӗ. J\7:?ZP.%v1BvVU lVӷ4~Lr?\3nZvo l=u꼷7c Ҳd5ƙc?׫juFv}U_<xiŚ)Wd2ؿwo=y7'~k p˓#jF6`a}҅+ސeP2~>003vxKBkP ig;-9y yj s>_^"'< 6Vs $/bƎ=88}m~8ZZJM{+WݡU?{E۽Rhw "G$( t XhJQ~2g IDAT-R MBB'p+۾?nĠ.ξ;3;M ˧{F7yy֌xa\D1^(_X:nޠPAU9Ƞ H;QZE8Bp{s3wtC}jĥm>LA1PbbCN˽J9\d/2xAt, Dm6mW!$ݹ}{_w_N_!(",S`P"J2ŀahniuzAbs۽ֱc_ԏpꌶm~_а]vS(6z[_t4@ >'eJ_7?&4,+6ntFQ9_t+iT2 ˴(F\ p@DʗC@hq"0jAΝ|XZc-9~~a_wޮ(ف6άdl.yC0O;~|׾g^e#uj^4.Qm ?1ϸ@U/4cY=BT|fSw0 `.[]eY,q1q"9?YTZ1rǁѹ[Z.dVbY aYeGhbb%Xc'3֯[< Vت1s+0q=ޜ,cɰ^AOn,?eMl²;VGt:ej.0hc]-ci{r&uVߡqu-z9;;|9b[,4hXe4*;ЦU@m }"wł ,4.Kٗ6jOP*.˵E0U!%Ͳ,GwNq 250Y5ҳkO5vF^DbQ+AGq gفs7O|~3Ab]UcHV,nע6cnθtb(γo0+'q8ЦV` {GЭۛ*׬QHyes*Vؠs?{4Y}~Z;6 "h\'"DQ"8c@` tF+Bɵ}e$Jv$% H-pKBtí?6Jv[>C C =h41 10(4%"pˆp^w߾} esl6B… i6;Yc@M˲LHLQLdÀbc-T{Jɲ1˖_ϋ;.4koբ4EI)͉t1::Zucmv7owbe_o&0 obz\*` г/K(ѝri攞nݺ%''Hѣ$IA׫(Jj}5br?w@٥34FϨ@G SߋNF/v#QV?c7{)`9+թW?d)Vyi m>HeL__ 7qC j +N3.gs"i\3, iwQ"[^֛16hq{r@Ǹ+%6_37%unwr1, t/R.hFq:#A+X#e#{޹# 볤aG«Pl 9bTtҴ,Aڍb-:*u,˂f0R>M~u-s~wU9BiT<`@2pMVҥ%psSPprUO18$MM]UZu;/V;#n`rR,˞؍ 8jtNO]9;x@d[~oy*YiQu[<۪*S]+\W3/DcA[BdmOj0+"yWXh '\Rv3O3܂fBl{]Q2!U[F)^ a(JBCIȚUk !a$,BQ7miX,۶m3gNhh6}: ]AG4M(PEP;4C4M{-AvC髮;0l~RGUUu Ye܏ V^YXxreW6Jp=[PEq'eXLQZmFG 5wmWz`ggm:KeδsCv۷oCL^hhbqdy L&VzzUp+e<]<)s vX- P?Q]%^])7g|ʕ JeD;ܑ9J>|^x˕5M:7q|g[/Ь3gΜ>~]y,c;*t { GzpjkV&jDuިȱ&=`F1#L%axiȠAo4pFh0X,3rl}Kb_?Sdzzw+XWqXNP^/* ~EC"ZGfj3'ZGw(f(|j"lzhz @6J({"6($nj" Q9y~ISn0j8eh$muiYhǹ,}pq8dz= q_);WX$eQ E ˲,;iWLBRU;DZ!8_t*4e^H]U@N >}D^ I+7&?Ӵp/6+{ϲlgԾ1k ,X٩0a`e8Eh'X6e I+Wtn> 8 |nG8p,&IjWud҅0?V)8O~|NXQfBa_X6tU!6M$_0M zzzPpltK,k۶-|N2[H"SMHpHSh MZ]RLaҚ/ЬׯҥԜof %Pz6QF2'E6;Q 峿yz?ucMvP,<܇f7YUۡCׯwԉU$p(o)(unEjm7ӆI<_Z^/4hqȱLF{Ҟ{} (Sd3KX'돴/gӁQ3Ow0OˀmLA瞵z]Fu0l&MiGOу L^o( Z5Ԝm?ԨO4xv1sX'ɷhF뉡;9 "&$}X;XOA\vLz=ukk~rωAz tNyk(^=X}>9b9H,_>gΕ\o{?wj9f˹-_>xŖSS6DP+s?t!O8L&czS]cW3z#~OJ XOD;,ژq 8Y1k/ 9}DYƻ~3#T7LěE:Gd+Q\B\njUjogZu~ffʻ Ȳ$INĤN8o䀯[wLqm?|zK,`ufzLLW;) [~EK~vd=F8Ql̀h8.L`eRqzBt3[ۗ7Rï%N)8Nunj-7Q??&}hmV/1 hCfϱGGG ٳcƌ[j{ܹsM4yG֮]@zzz*UrJ0222RSSUzCn!8̜xr5j HS؏'eA&4Úȼ% x س`ir g2,YVg X&%4k4陼Ll6]էݒed0Uy vq 4?nɲXls0n:t>qQ4! DBŐnæ0dȐ$K,$ӳmϤM{7\ʬ/Tԑ%B* |V;8:Wnf9Ed` euQvv6g2*]Z0|\ eoVܝ=V{0a%øG&1x8d4AzL&uc4 ug37_/OR!h4rˉşy1h4c]vƿh4Kmj~y1&z&phE" '-ڠ7!C\J!IDe~7vsXQcTW{6ϓޛcB|7Mv=pT(< W>׾?>}N&$_:TxmDy_v;C <zW޽zWR%\3swk׮5nXYTx֭Ǐt~Z !&)%%J*ƿmTG-k>} {w9ECHq w $c(yߵdE26d@2EX;҄{M'ar OG)Ol_71⏓= =כy1EN8*!-]EbO|?GƏğc'"7I@|x\ڽ.5ـ{,Boj.f^ fN͛7=T Ô(Q"00N: hW4M?ͻxbU:q?(*% /t9Z79ؾ%zKy Tt:!-ǿGϡJԣ)| ="y*Xsoy67n[XT(ɔg| -[6&&fÆ SNi~ςRJĔ-[V 3=<O|O~?=;­ŋ?!Ϳlʕ+;w/IPւ,Y{ 9oQsO<^[lO²lrʖ-O~x{o?ſV#?'߆o)?;V?s~~~D???fիW6[UE~~~nD h!DQX,~*l7n|844u&IKV5%%-ZG\~~-pѣGJ V.)W?%0(oh\lC(6mZBnܸ1::!v?oh zM>t:[ .4jH\m25jt .ԩS~E^D㳲ԩ3x|`>3$/jtE>q:/kْ &@ a K$=쳳fͲlZ=(;w… >)Z%m !P~_u:Bd{T)Sz/e޽{6oũ"UUwyAjYi}\9qCMn ,`0TRܹsiii˗/$aY%I(j&''+; K.m0dIEawn_­3Տ4fF=PE$biUGɫ ΃(Ru+OϷ}ݭ[7̙=hРŋo۶mܹ˖- Ro)GJ'ˀ`~'"M)vPc o5?Lޕ"G7j3/ot:,, "CUDQ$jժ!C(iDQ<{lݺuQ QVBΫ~_~eiJ"° -^2ܡ $$lЬ]+k˲|ݒ%Kɻ?P酗lSӨf3 kJ<#IRɒ%/^X\O|BӴ(EIt$I:}tӦMeHO~_wʋ߆|-Q$?f;,MB(d95܎e^FΙ{Z8!ie<h4; &ݻftJ}vppr xޚ SfLYjUH]iWI@dtJn%j ۅÿ:oSk;σ z_*]N7$IE1..N{I$Q;M\y Q!PIKj~ IhZ)WdC*\# 3ota%Ȳ{ ACJfJ$)&I,z^$<|OysAo\^):ԱYSJҁ2S߭$''gʕ.$I8NȖ2F,ޓ?!i&M|ql z{޼yݫYfzzjURsa\[5\en4d常6; G7:4dE9{1>6&&6E#v\ltL|q_yZLll\\\lly,jz穟obr=FS[KⴘWy\:d ;,Ž۷̌W$QvCe^&8nMӊK$):a3fL>}7+\y^-ϥ߿߷tS8wWT%I$IߟwܬiZ; oq,LuךY(FP>,jȳɲ2~ ı1~LRm\˵tpѳ~풿>(e26&EcK 뎥i9.&E)m~gqnJrE|#?yş]a4{'xѢ?Qw[|LAa?}q hƾyc[<1>SV5ںi#"TnE"/*>Ï .-?{l?2R]u׮BJyC(-eO~Ni4!Lظ=>\jjSJ(MSHͅG5X"(pjt2:kҤӧ=`_^؈+{9O;฼i 5o=lu_o:]}¶?C)BtPiƝ{v,y}OW횒8&lωkW޳E9҆s h`SG줁 &TS"1gĄ2ڔ.L(t焟X L/>vP*KRhxδخ͇KEQq;X;Ҋ?m(h[k@!FضebkCw_.r.""k,>#ħZ<(dff>4e VP Vẍᇀ齢i)喴4 *.vW|{"tqXhM%嘱-iO|7,Kk 2U"BphX,f(B;K Zth{jzu|p;dC QE~nKQm@L>5}) ѯ"te\c£ Kr!/!vA$!-h9C@ݨ6cgkWw0}іw ,P$j} gn^4ӲuEI^pǵ{5{'רOXf yYkIB me~47oeN /M1}cZ i{>AE(TQKsBH)pik)?:6}Sګ1 Uv2#ʑ4 We9}ѫMO8]/ݲq@%YiӷFDھu}w=H$XфHzqiPJ @șkZT^ orI¨b/~ꈟ~~fWNYn6/=](.=TKg(zF5wRқF;)&2! ,{h\Io&OS-˲Y%C#0y(t*s(t׳o=ZS'&@Ł ݑV'4 $I$(K9ϛ`\3gΔ-[V}W^DQ-R`]ȡl&,`Gd_ȟ!")It~匥@W t}b+ .Iik3_n,}hϖdwgTgRrpAڦWۿwꛁeTڨ5[DZ6+m꿶QSG+ }XS߭RQcZ钪U3(ApmQ (oKõ_z T~?O)\ZvqZJWngJxnŌ@i|ׯ>mԦ*ʋ_:{"Iֽ/9MRmɩҸ5[n]ʶK? Xh^P6;weX,6^_.@%3m;s2l6&=򕇞T+v !@8g=dxCY(CdARMΌr!.u:#N@YyBm*W9ox:8d4o8sίw6˲$p/mhvoG$H6Hf0|-j 0ZfS\2+n7xxvܦbyy3>r̝@<q!оoE!%~lwr|dyt̚eӀ *N1{w=dڷ5FE5rf΀5[Q?o}fo=jj.j.mY6cGWN.[l eSɻJ膻DuJgV}Ɗ9Ce@]z3T'DEOƕAFET=pu|%+ڏ4FʴmN3m? 'Uv^3)xb;.ٲo9}&l<"4+[晔#re=8`xt[>}ʸ7y'LDŎ |tf9Q_-dtfg5|ϡ5_DBw:9R~3\Э Y19z[J >e;w?~9[:NgCco>ҲLz%\g 7,fxxp"޳  ({DQR7\(*g爢XF vNhK1@֭[jݲv1j6o׶N 6]n)Ub0B$;ٹsΝ{vnw >E}3!'ܽmxSr&mO<&8zr)[ގmVPlkOΏRRrr;VY(H 4̺vqGy3SDj88{:2::s2kk?7ءFwRg" *\Ï 9gFldkmԼC" Eڽ:(d 6f5j3a>`X,]gV׮];uRJwK!5 c NֱӸ Lg6Ϙrk7DQv\W__yes"=e(ty.-x](BH> @1w99h9a5ovjٲuo'=eMhȄܰc0UCd5"It4UڏBW$M}5:7O4I=TzakNi sg @ IDATŒz6/r9d)úg\ ~R3q :lYɽ鄘BÊAAK؟4SFzHv*˻\Btf*n%s!C y~PP4CLlH~[)'l%Y/e9 P_LiӦzʹMnmUEN 4̕EnAo7G*v@ ~G(*Zf<3rC(O.~)Uh d.$}wSk܂RJM8~ o?y&tLҹ|!EuM j͌P;_v0rӵ< @@%" !.>>?+k{RǕLĞ_$!c_n@z!PJ)@wtmG8GJuTmU+w6:>*:H96|]5\nVPH^,FRҦDsdޘ_^6mhB6 !NyvFT=H`6rse^ߖ21w6u$]d @ɻROM{>2QL>(盡U{:`!ިi銐d@h .:7ɋ ɋyk6dd !xe״(: ѩ޻eǑ s"sWPQpĉw*򰰰QQen!J'л+V@dIyd>VFDi3ȡ^B`\[F2\0B'/r zmKRVt(NٖH!Ԁ"E"EBXh (UE?PACQ*-DjIؐlrT;؜9sw}ȶ.l5U"zyV8^#'_1Ms@?.G9{maXnN,:^ $ p>Nj amLD}w4&~ 0kGL8- \X2HOa;.*hR,!J0@pKgđ4!C-0 W8_C: xr|9.oiw˫ X[?ߐ P\p+Oeu`YD? ^$CXM6̂4ҵhVapp|Tӌ_~ݺuׯ_1ܾ~N \ʆ遍c[ΰy۾^~{9<{c˙Q5b*wQRt{yYQݻ7DzΊ2 V#$$jzz!%,X[LaW;ʓe2뫬sRɀy5p0/_s+;L)M޷u'Ѣo@ܴQSg?bW% VU 9xфF0 _mP=vP҄7tVmݽs_5[~].e%gw ߍ_#_ˀF/?9ޮP,na:_g~5y_.ÄkyVs`>z)@ 10, Y2dQL%6Qȝ!<.^<}ySϩ"P)"arܝOF9"WĎ;sq۩S'wVV3y4޽q@ʵ^\`x# =:\!zꩩʢի3Po׹^U|n#b|2+I`0lA%e4ykzS]f:)HtlB1y  }B"4 Z.('f*@x2̭ &)!!m6ۡkwbNw5Ycb ِR+/sc 3u _ԬYO>rt_ :vU\~M`lEE_%z"bnu*[sYeYnݺB:vXL![KO%.4a?)6_uL͔~31nግX%XW&øJNtdݪ$M6o;7} l,bƺ#&5x9cfgr9z,}'K=:wQL?)Woܸvڵk.ݾxpbQRlcCA} ò,K8Χ͓4}Tm֦/n_nV<"kulՠՇTmݺrcV@@."Kix}!7g򣉈mOʖM8-IY^G|QmzpfY͢¸Y5TvI_DfLv5ʲ,úGQJ)ϋ:!c6[QQx`7)/]dXy 6[zSiZ ~. p#R3|>o_C+5uGL6x-q13N[YVI]8̉DcU] _4qziXy֠ Π;fo2Z2:^p: p: XG>Ki.b]sh2m6uȖڬN ZPBǎrÆ 3hذ+<#ݻe>?~qsP2w!CƟ7QZ5/+*TE8/fܙdfLO9g9{gM5o+bg=aHD[才3^9jmtuGT>C-=fmNkkr58kFJ!4mX @Er%P(Rx}A 4.sx|G]?2@:+s\kEUx7'+øqF1cG? :PqV:ψNSa30?}q(n7 3۷vZΝ90Lvv(yA(Wĥ|{zgr';;l"ǀa97^h4mBd `+8VQV$N}G(*جz4zBFOlZY4 s;RJ1orov\4WcS7^ C˔V27jݸUg3 Bes~x@~t\Ѳ d-&'lRYGgF6l0UCoɛ{O2<8c^-rePʜcF7atzla7O@;t^y'Glpo{a䋁9, || ;.4zDmX +16`=A\ w|*WyHŐgv9x;p Jiw ;[: x:=q:0x^p 8R{xjFSgl@:qO($$AǏUV2e\rV.ńWPPJ8%;(Jt.1<[CAӇzNߞy&rxOփ1`v^cP ^;0͐AX[1B-'mk9mV7  [=sn=(R<;Ý[)<+lgT<_+zޞJWb$xBGI<Ƽ]ߗ@rS3jѷkp:CXy9!s>|S*z\IW(ް ?C`ܩMUcwsl6[ͱa|ܞU|R~} y-h~J-1W-q\avK^|Bslٲ5k,{ƍ+(IIIYYYEYX?aYW\Ut8klKΝSz y^uJt.z޽{au?2q̒=Q:_-Nӧ/g~'SLUVa;wQF:]ɯZGQ4o޼RJ̲,_rE @)ȸ|rʕNF(ҲDƕ scȭfd9]`/[fN7DqE)A͜i2YENo5҆OMEset?i6KcH#t>ST@c۱&[n6Z^㞒V/_O~<}=8-z=$F<}=ǵo5DBxO__O__L&EtΪq-(c@AALeJDYڄgnOjlaX~;+33eefFEE)vw;w 9m6[PPo)A{);T_^Leyqƍe*>(H.l8gc69ŘqFOXyoN0(S3M}짼!Ɣ+=N%szQ >}oʶ.trh9"hb=|tD.qE&ޟ޿ouǵ/Ҭ"XV멼tKE^R|HHH]$Io>{lժUq[SFa Ȼp Wv .#DFF;޽{o˲AAA˸d\ -{v3/^ܶmSZMCqo>eY~S,;{ʥZj}*TxgN㓔`9т ܿ_'ShN[m=pt!~ᾁO+"z8t>a_ݴr!e+p?BܭZWї@=ܘrQ.ι5c_tzRt!O^9PO" X/yҘ<R߯W}.7F\:o^4 l'nj[t((%B e˱lF xRt9p\`GrJiAY]֐ C߾}HFɋ_Ŕn/8Vjz}7I,r5O4X J,ʅ{>Tt}e~ߺziztj:]y@9֬@ qkݔ ٩|#"p7&C։yM (>~VbB /I9t/FāL("쇄{(/Ay e$x{{{yU}WY}[SyE[~pI,ӧ2+V|g^_j}mذ~rI9q-Zp NB CRRRDDF]Ui6f#?ݺ(rz?y;W5 ϼ#ik:;~F޽;Ľ-/ixCpu/TB$JB XY ͽSCmxOOOsHQ]!W Tpr:ɰʕ{h <<x .zzzOL;w0 SlYjgFůo|CwZ2>ڻ2XHEENKL&O?/ߞ:SU_;"P ,ɃFhGxs;e7˲-ZHJJ:zhffkeYrEFFTyóV\Za+5*%%h4 *6Z_pppB_FL&j-}8:NR %Ň*T_x Hd2>Cblٲә]RڵkwEח*TPQ ._, PreDK.ݹsjkt^xᡟIUB *"ܲlʷV|ûy"') _\Vի/Ňn?OW_ ӦM vRSSm۶`ѣG\*TP${9OA9+xxxSlNJJ:tPӦMՈK *T,E[6'\K_wq0$߰ZbEoInZR!C:7JpH j]… cƌE De˖,ǁʒ,jjѢEݎ$IϟxbFF˰2CJB0P\?|Z OCB<ڷutڼN){nZZbqIuŔ[VOOG1q>>>KO << p޽_|QdL8ޠAeԌRZBGAU/$POT;ccc>h&NX0Jx;11qJYjذam۶U4](KòdzyÇ>e9qI}5ŷ|se%wVn t&oߞPL;TeQFI~7ޣG___uW?b9aQBMB|q媣ݾra.g˒$UReLG]olXnܸq…U>h ˲NdI+Wܺu[E) ""7o,[lCe(,3 c6/\0 T$sgϭJ*T<3 P\pkõWx1 83Td<-"Ե`FyE[!ۼz^o/Yd͚5ޮ[c9`| ^Zh~X\3L;-#']q>oܨ 2=,Xa]9*Tш(JGȏKDEq#G%Z}NLsլYSD"o/hnP\^i,#@aXB{wJ@dY*WmZwpĠ<<455588-[z$) \:xb>>v}ڴig9>$$Ν;-.o8yvw !ׯ՝u";WBL Aj97,yݛ{hi}ςLW֭gW[KaZeY#F_dY$ɑK ~~~ʔ!UrEWrU$:]vEaS2}֫WKOІ=rY6&* G0{o`9o @s QU~|eYBeRdYPL[(p;ջTY+M?}<1SJE 󒕑N#֭ݻ.ɲjW':|V韐ʲ Uhr_G/xWmT}.QŖgh  ~$I,}MS]jĔL˿doՅ'z]G@ VSR~gҥwޭRJzzlvrq\[[^n={ itĈ4كbJVhΘWmaY{+\ȀJ~XxE-%$Qx TvlQͳ#bPJ9$I$ $y̘`h=nGI>DI)& >|Ç?7 =xq𘘘??GωF}%QIܳh`0 zCyqrߖ4PC1,KNW3/#YS9ј۾$hJ}V7y1MSߨ7 ]αS>3G JD[/Ӆ&m0Q,r$eMA&0`9q`Ov~zϑӗ9x>U\!ɲ$˲,8ph4۷/'9'̲,>FL~Cg[w3|Ḻx̺wkF8e`ٚ÷;%B>E)EQm(<0dI$YCk1{|=ո%n%I[o7͒$e_?5/v{Z^:444_^d+rhhhhҥ;숽e+]{;MXkO(d9scb۸k+O%N~:fI\unϾ~jI\Mݾ͌[2~щ9r#+W]2sK/8k p]g^zM߾e㖌iPρg7h}yɲW9K$?fwn=!{mY6)1 ٟOXнy8wa7/Ď'G-^y֜v~}?o~tT1qqw vn2`w$l:n(bhαӗ-˶M8 q_s ]y<:`'Vz_)7h9ǟK{ӯĭ9xV b{G쎛<["_Yr&nm0V$j vQ{8sk¾l]vE*4[EpZNEW+MҬ]ڼ֩c`K-6\t2AGns;Aɲr~sRPz3gHLLvsmOqJW(Cy\n놅gCr {|ȯbPodTظw%IMgF6` ޻{ UjWӾv?RmV߅~}^l[vݵ+xaZ?P~:VzVo/LOV]]ɀ=kB8H>[^dސufH-: ?HxN <{ cHp9avT3P@EcЂ}hdٚ/?}AM?6Y"i =ּ+..Yf:ahζ9rxYu-W\'HG~'p!IZMY 4,uMvf׮b{SJ }?W3W0[=|AF!xP8\u !$pX5L>>>D9JY5z"Pm᝝*Ԩ׼ɲ$I"ǃ&iƭ@pzjt{sܠj<+12bBցh>w@#,͸|_[-5V9zmOWlr'(P,u5n[1]Wu%Y:~8jG}׽Ҩ .̊]|](IB etw>y1L[qKKvM+,K[/Qa%| !5#4kiS4>Td*<whvھt>qim~Uor60O#N=Si^߳ W9{( /먣Wm~:7g_{~< \ƋPC'K#/?}hq7diLʐ_ (Ў(,M"XBA%TT^EqU`R*J6A6٤{".av2 8;NWY#:5ZYj9 DLe\;\caΞ=ڎD/$QG GH ̅8ńEh-S"RdY-X D Ԭ۲&UY]ldHOAs|U.m*wk5?ꋞ ^RrU(OPwAYoGcOf8rTF ѠtiY5/: boVѡ6̮DŽ ؔ˲,9 mG?sy axqy*JEuy&$yH+}/=qw}`ʎLt* \q<,=<ðaKgggg۷o߿ذa#FsKq,dyVPY⯿[kSW3p̞={aQa3-vui}ײݨ[.q8%IC-a,lQߊzih7cޫe`XܲfV Y[,]`,q,ԶD lr}j6K5~nҸ;O,VJ)!w7?thFS{+Qf)?B$(=~E1e6Rjd@6-d@8qfK Я`(jYnF.XujD*:Ͷ lZ,n:w;}syf-$qתY,I]`δZsr$*A4}qs#7]NQJnF,aO%fNebJ/;V{Ԅ/'MZۼ]u|cMgMZ @ӆY{n],|e'~ʜ*΀=Oү'X,p:8֫aV2n+zB];͸='J5'۝]%"y\!;jp h<żȡٴl#a@}DѡETD!TnߙQQV2 !xO' Ѳ.u^Oekܙ^ V+wFWJeY"$]pukٶ2rv7*T`HV\}#"0CkYEiVJ$I5+ۦ%쏚@#W,u25=R&5֠m'vv9j|#TgC(: mڈbjni*SP֮788cH%$l9]+ffS ⯟Oy{rgI _X~URR|BsjƠYFauW-<|upV"YDYx>Ҍ}wlfYf޼wٻwohh2DE9K8g[cl$ @MN^&!XnN I*cRΟ|ZB 3BH޽[8#2Uϰ,K@@bRpݮYK$QvAp#9)Y-HINP@A+} 2z|uwn؁|n&D_3]ERۥ30>S;/K)UD h;ou fq_:J Q 3˒eax(rw o~e亮_Xò 8mpwH[##8~;C` HC+g)=٤av]"K ݞDNYm%?WR B>pf?GO_dGS/ݞ+,WnBd  AU6 Z2Q..>9 X/΍(M\y{IR`}sؗ2WkԨ&kԇ]K+WjL)V1Gw츝 ?2*+]ƩݧlonG?{ݤ b~PI \RqD9DF4WWG)l).$lڴYsty$^aD,kr W}_b^B)4SGq6#М 8WDCLBc5Gsݛm~l ﰊiʗAw*8Pj9m@a="ń^& {2!DcIEkL` s: ) @̜zW-wrS.c0wPRHU}9ÍSDe*Gp x>X#웧-zࣲo?M\ȡ=L\(: qӾNy=k$$]gSYSGc4n%@ ߎkKr"oN:ܴuGJ!M(^"䁋nZV]A$YNsgW_u_ϯYwv_jw>+Vd/X8oݽtnښU{邅ߤ 6y+_s9(fCWyTe˖={rʣ}0 <#w1B$MՋrp(1 gOv:˦Cq*"V5 LͫBɃk63\TKWNuKU+YQ#ݿvn0Ia@җ?֬0L6x՟s.x`8<7qՙ{Ύ`]nh=@tJ񈜺niLrnO!>0 q'_JF?o|>ƽW>4?O[Ntׄ" @ׅjze&`d%#7q6FzN@}zE29vx VgɓŚ$ܳ:ʍJzJq0P2 燎$AZᴹIn\$vA% !D@+PVMK>'sf1tPQ$0,~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:~1(}l`<yNYц4fY&SFZ|, ZM\{ӾDݶm۶-h.m۶mi+G/}9X] .FZ`}a,6Ơ۔҃(M sG,o4MKߢ@%Wg9#o*C)Asy#6?6ݶm{U6 ʱmNg鲽t+Pş?q>Hv wjw+I\8 sׅqnx e+ [Q`PYi#MӬ-$2me|Q$>Η}n3۶mۆ뽶[u#i I^u] _HH%8%F]w}N/JT|ȁIvy8%I=FZnbB'R"7d 짯K>7K .̣v]hat.-Zlt4IAHh+F#KH^*|eCvJp-iR{X/!],3HXB4M%; y`yżo/9m۶튳tmP1JK_yG8Cy%.B7ٳ.ҕ| {& Xu%I  ,mv2܌Ye|^R mʿ}[j#?)5%UHUÊ[nqA8m gn| kQZ:1~" ϯHBW+Q/zyv'VBqSEFϷwk` vm^嶵@Y 6Zu)[i-0 \e8yTQJ/󺅏|#vdΑ8z!.qHW2@DD}Kތ!g rrԥV)ia螒N@)uypbbBήHE\.&O#XuynGl%xԮ{ pG]>& ˖$ җK,(]9ѸNjۍm^mtm۶mKa[RT&%S 1 ׺!_B5Ϟn 7Kb]U*w 9Mv'I8$ZKH)ųJ)//dG$[Юgr4K~qG*Fv]Q< a RZa.Z9]#A-mj Y EdIl-$m!LyCN0T]0-~u]yhąeoPzu}_uOum۶m{1X#8J\yj8a+$ҕߨ <~/#AH T/_$qwt2~]-}H1Jd4J]vtƟy]Mn 8"D㥯}YZJh(p(^cOOtdQ8]#ےg KwԖAPRij3Si銪{il.b۶m^m-{yaڔʕ,* y;}߯}g~]M TJtfyNHwȱkVL:.{2\`I=v*KR0h7B%[J Pohj 4!C0hZM-]6m en$Xių6PäIu" ]tWƧJ=Ǣi*.qT+&<ʲC&k8KVum۶ZHr2+}%:Q)u]ZWNdQRWt:V'*?`"(Pk-)"6.Mn qL,Hb.D[{]oE>sBmbmJ%t 2QC&Bh4H O05"m}^%d,{&q JHh;u}q^GQD-&y\e-7d"*Rڻ5>?nݶm۶Wm-2nt!z~.bLpiUB!` 7B>"w,ܹc6C k`0`K]S%1TVb3}ȵS |W=b(pg)#,. ZQeT)+ cF$`5yvD k 5%Xҵ\%=,Xh4e>upi])-(^}G_ݹ%Nf۶Uh[ v1J vsFeLeyarv O9 X3Gr"{&.>`~[BSëG2HO t،r0ry.IB׮'e^8=_)y9eA!~^d-r/$۶mvګn0vino6AaH`BtR1fy^txQ.> 0]XX"M 3O@a!M6),g~`ym t:,{PBd-3`>>/93"rJSl":t'Z%ܖS(|9Fz0 "{. ťAST+2ҕ]ƾ0D.Nm۶m+-]ɷ_.e+DcJĭjAav&E.h 2RL/FҼWlpo;ȏHW$Kt€IHA]29k)&lF%6>aq-nqK|QEtyp7P+!| %#NG!"Imi$xB%+YbJ>y]hk/.t 4'_["wiЫt0Z΁y{.ow8oc1W5'>}3^mۆ{z`i#>?rt_m!nBa*]&9BqK!h,'_Bs. g9N ƋO_.RFnRyz#M]:@8%D ұσ`T?Lo/}vƕM2o|!ag+- t!% +B*K%)$-!aɍZ"ѭ)I2m3,#l|i@ETNyF-x8qu\~guΊ=x贋ˋٻut5~Shۆnmb@Z>lܻtd+9Sy٫7'~=jT~nܩ?ьb Fu]#@"eBZe%%KF$Rd0ܴ,Q@Q!2 c (O,I[sJ78('IRErƌ0 8$FTu%K3d3ICamlm0E"W%TlxDSZ%̕.7a_R! Zj1,YN =WT^_2̋X+Ʃ*l!L\]RteʆL#4s5D67Ųqˉb}4FEQ$ח{F5"Ռ?ixAv#>t߾; l+KgW~? 0=su\1j:ofoN[[v{eo5Ӿ_j4V>4XNǡ}73{q]{9aūT ޴߽ܷr@/>Qrx.t+sd,n[$eJWde-1e[[[㹿-Ӟ.n8qD^&񄤟y Z#A\M+dnm+[\Em{R]B /jcðⶄq\jXhs p?UX8宏.Jj "jJAjbFl1 ]iX܉IqdRd,;:Wr֕.\`BW+(RǸR5 [a]_Pڹ;wk&w4<ߥW7K}vsʙ;v.¨~uu=+Ng_};9rӫwZ{°麡WðQ4njnTEA'[NigtVf5J+s0_/|=U2 *JWA4<[`[U"x~O.MvZqm)QEL @4?? @t{^m' 7 ĴRJf8yoAfR/#R"cxw6[mkWy[Q cz˰EF 6,p;;yM^aH"?hU ΦH~dgdF۫ˋKKKv~D8j5 i"VLk?aI/-----zN:xlNMMZ-*\N Bz8>ы䳵ִ1ǏZvUWQ8N;u8XyK%qK%b@/H/=,B*}kF&cG"LUċ#|R1Rш z? Qa+ygz@M yn ^oihE |J Prڵ *b1U$CKɮKQF0zj^@u*cֹTF_l6;qR`kѷ__9>}֚s'1w9vhGtUC-4Y:4ynŒ6{j|wO}oukV'Gvǭ}s|pIS^sK4=qkJ(9E$)_^ṣKI11+# Nm'Usܩ]9 P|~5P\ނE"*Il.]aHwsS"va]t+ JU4h"hzy8qb~~~yyI\6Q$֥.zh4jӢZ${ϑ IDAT;<%$J4y+++8uj|||jj2Ju+4kZU ^} rɓQuݱc-Ǘv0p-2W4qɏW/^}N%MѺFT.J,]fDH #CVrc2aY\$2ɲwhPnK1}q,^ðJ//0GDj?h^ *2>999??ib΍G#Vby SdbzSNF2<})[}dAv$J=)4QUȁJU=FU(p|f-dmuoĪo¿9n;ZywܥJ|y3|Y6wߘ| Ƿf?7{h7&hU[D_z0O)`>n/kW^t4}bz,kj=5#~!Jr=;!4q̎kx @+B"roAHrUmi.GĒ}.Rcel#nϝ;Ow:BJ)t0Mwp;w 9B+g`A7Aj6:5"" m`S-hmmmmm,//߲"'!g϶nk׮fYtPO2?#]O, @mxvzA9{9/FiL+~)\9,f&)Io]at_KVH9_)I5VE---Qf@z! , *eE|\u ^y>z9DTo`eb :-Ri j4nJ 6 M0+0"3ƺ."zB!!R*"RP:y.BJ`m/~M620 P<@-"@A) &'7N;TC#@t_~i_ksعucscV[SS6ʲGf9<bZćkbo?w쌋1ZtFxj`߯yꄵ6IQ$k gr/\Vp ١ iTO_OWvݍLFCȁ@l- PT57/[7ƖFGiiam>QASN>}ziiIv|9jVfy1IT'Fu]F2Hɞt r~bv֕JZm>SCPZkYKxwvv/gcE@mlv$41XϱeB56ƀ7fQR9i? !Ї6S*R٬J5µ(:J=|JR6^ 0v%\2,˲ m30+;+Gt!X;iad<Tv,ÛRJNX;n1)F(gf~sQU ED@AJ h&xhlGh?fEg9,nu%y{֬3ij5ul1' ꇦ*f̳k4Mjzv;VMz(V}=v_ZbvgCr%UZ#J0Lūv^q,K:N};Oj5TG٧kFC'O_(_Y.ꡪr PWX(45`-H(EQϘߊǶwK#]iK]ܒ{&2I)Z`6a.gΜ9~ӧ"`nZS"RAPՈE xb].#Ȋʚf&ra[Ǡ0͋qX)г"p@dI4M.ؑ0vQu]QA rW0?\[W@%bv/:.bZbNDV%]Y:!]`\ˌ/ :aVJ!*Ď; jZT:=@:7ɴPkaS-jD҉R{/1| N F"L"'hfX۲<6")-`OO_wY-k v_dyFrQKp@i-* Hacm!Ԙ֨jaSHqE0d Y}vc8p2fnym]X^PJt! ծn<ӷ8m7 ^饵v)׎NO|k]?-_/_Z肩Jv8P \uqnMlfת碰=c~R,%'Vj_%Jj7>;p߷cjbk<; Åp*JU| $\S 5ؚ‚‰10mS*P2gn>[H̰u.Ո Q7Vպ"o.9`>{m]v>O>9??셤hVsAlnp2"L-o#hy cW"/rE A|^R8Cq^ouuWT?s yHjO%&?:K^#CWEкZ0-1 , \w˃b^ZA$): &Gʑ&BF166F@%nPpw$9,Xb%t#yw`?$iӡ391Ss<`0-N>#{ cЂǷj8Qx#9o M>8[-=z.Õk?~ & 1elkq&4",WPVYe#Up B^fh*33ۭ'EWOiU\Os򄞛V3\{LG)tQtKEkUwniRvJAiPQ31؁Fdb>կ/e5 1}W[:h_"H^v]Thav]JEJ1f55v-8pYv8J>15ݼu+gxJb% B.3R< *0i[S8;)@Ukf?t+H"ޭtK#rN]~~Z|3T,BGKKKǎ{ꩧΞ=Ksg%l6$Pqγԍ2NJ8EC%LkJ!y a<'rѷl<~$B$YXX .eGe%7r4F{% H|Qoҫ^.5Qixr\j@˜e[㖆jHYiC5ƈjڍF^oS[$\s柠 t8tS%`*q6k_:]S^X<*Yj۴JT˕Q&=weٞ^S]ǴJRӦW}cđ8l!?~ӿZ_v\nRXP" ٣*) 7b0 k"r1/k@cn[Nع GR6vJkX/Lޭtqѱ5P֎ \A:^j z'x{QxAt4999y 0%KNh F\'ȗf#]%F^Ċ.iX.j?<'Ut9uj$ Ngeeeqqĉft:?;ݼ];TLKeu ;q>5 EѮ귽<6zy#?]/]νm?Gk;-,aL CGʎx:ȟ*Q5&Q:kPհ^vl3={/差) =(5|X NvoZ]x=pAR&ZҮe: VK&5gw8?!;~k_,UՑ&4̺){O3HnșSB䌗|#GS'&&v9333>>^THx2" X"] q%NrψZk 49'06I9F=I98RE<ߺrOMM9Ej3֦ƗH2Rm}ѶPl( ?*l -0rAZX͑^$R;DQĠV\"xG$gq8ALLL ,N8cǎZVr%k+XHрarGjdH/O}\i\?wpp J@/UCT Ӄ#qNRZ{.I'kL*i ͇WhwSuXK=T̕l.IANބσ]9zo)՝L5HW2#$GyyuRZv83<7R{#kcxƨ#s=X>-=,qR˲VQuQzNCy$@#L$"+!3%GEjz"ƠT6zyG E嘇_H~Uȟb)uW}Ƈ64ʣNuVk9Նq}֮֮֎.0J%W4mۦIfSXEi]W]g4r UuY> QqUSTr/+y^˭cms1iM/+ܽ7kxt-y]) IDAT7Mn/buzNrX ЃSwԤUrkka RmNO=د|u}UuYXSP@nUS8䧢p@Zn5=w5RkȭurZ(6hJ2:ބ?λ?rԲt%{;EO%OE=cM`rvi52KcrI/yG5LR!HfBxS;<+Yu]/mmU /a.%w9NheZ1#k"IO8A'!-*BR0txa #N!$t3>ǔ0rYBPΒU*6cJQ O)u^<%\F("ڒ0.OoRWtDa~~V>|KNnZ1)o-ģedXگD'IK@Ҡ,z".mreQoʋtQ-//S2Z{frE❌w6=ZS_AK @֭I#u9;Bިn/s[&܆=MRNfSI8 35Ea:謩H Օs7ϽZnj2\3fYX+.fom,\{y+ۻ~}Ϳ tȎޠ]i"R@j h[3 G=eUP簉U D}7_i\8;?5sK^0 )0QOjO[pVk366XQZYh >B9(qk}ݽ } Mu.Yip?R3a6 }尭ӧO9rg ZDj>|xff̜ \ Ay$gyO2ǐNK]PNkP !MUv`GR <!JK88]םs9r:E=3O޵k5\sAT8'|  D(%fm7I(ƕHi{ܷx\^Df*h}ƸPCu\9q1+"@r_ JnG˳ti^GV^@_V,ogA&eAya²dBi^I)v:QT s?&GXTu4@vG(_#m-SuZH lI{5j&*(Eggk8xWwؿopV=qA[Bu! YofTڨ/M}pĴTgY3l8qDLja3FQUj*[p`kkp*I}*t7nry|SfOg`N~Ǽ7TEysFI)b:ckǏGQԍ8pફ#тEK蕘XDڂ(e0WN%pQV^(q (HhiW>1^ L$IZh4T-iw.?_ XR/Hn ^Z6GQѯl/1J\rI ^`Kea*GV%i8}E臰+h2D6+3MQ#M|i;vu]Oj(!QYzRF_u5_ot8oIհn6-M8C>7.SahڸpFyt~&XKlKNwJn_~OC. f<}G;W`\(C1_G[aϦVNW p`?5PyDϡoѳ,Cm7g8ژ66A7m^rZR*M<ό1u]_kwϞ#6+;z|>c^xS?W0zߘ{f3cyٮ ZX"wr$,SYq0]/߽!tujU˨,QdO_)7q`l^ ~cw>\}]{:_|֪5Z">xpͽ?jv:o|yF̮R:ugyG ܮ:SW^%Ev=2> s0.WeEZG>}RIScjûwnZJh&¥e*H ;:#!x6A6?g@ZB/G^+/aWTBzxWkTkkkD S='Irٹ;wRJ+}5@[iF0W)RY +Vg/Gρ\л"UB4)(* Xa~aUtfh ҝgOYJ_'J\iG.(N ^c*c?63U oe~Zm4Vkmmmeeeuu.cՇR 3f$P-P]X;-Mr5nw^wk`+\wz6ϲ4IAgmt"m#p]8=Z)5Q2JՕ)kE?p^r[>@=q/REW)ZU8c ~iۮ{>vfSO g}~4dG?MN pzIlVnOa첵ͫ1@ǻCkՠ4ƹmVa+o(ncQS[LW9\;3Ѝ7Ck7˲(>.,<}ߧ@)el Fk`2 J։zrB.LC{;O _|ڷ(}FL8ΒAUS}3͝i6[ hd%؅(\wbb\>Ȕsܳ"it~jxA)vB|l잵(OE%y$NC`J$=ϛ \Nm3K-HAN/*Vu`/ًSqJPu3"}Dv,aYta.7 *(,-koKe (G6Z6>>CC| t:AOMMQz .F u+Rz4|#@<0#K@\ xZ.//AgP",MS'eP;Zr?y;tJQTh/B^".&u͵U^㸿|5ԘR-M\I ԋsyZݽ3w><x#A ) ɒDJdlW)ǩJîSIT؎ʕĖ(-3)""@1w|+?݋}}@ $fx޹眽w[j jm0y( (ݛ26Ikgn޿Q7ܹs3Cח0zzskm+D9uc8Ny; *R>BD(q̲l5(0Təd-_zuA$kɯ6]G:{Wx>sO>dZanJsΝ={vss\cL AŨNUZ.IqnY 971c /zTC@Ź""W?fbfMӔw66pyTJ%Ai+S YWe~Zs=nojɺlyfYƤ Wp0M`l5jF!a seQmtb_0neiF#!q ZHTN񗖖VWWd'hq$@( C-8<9raLrݟ]8%qjhZnp4M&Ȳ,W9 JKRAY4x}ɓ:Xs~[ k!}Q{Th5N'8z hJ0'R+9}隵sNؘZ|w?4DD<*c CT,/He4Qw3oDBۓǀ,ufp6dռxRQ6`m 5jo.%aƂR)tld7^ᅛ(A ^FqBxq-o]ƫ{? vvvvVJc'&tL|>9!+ jЍp{ YspZ&,Zs IU>mNRZb(H%[O!2Pl4~mmmuu7MAa8WWWeCn' do/ ?, *J0`?zW7kCے4e'B9.]2DzJiBIJ̝&cLIlĨ[n¿{;ާG%,goog 9#Hom<_kDTQimbRH6?W%mt7*.Q-wޓsHD俍ҿB4L<*aA@'^_[+)lD$ vݍ|C3S}B@jB X“w5ۮbw}3~7ҥ]h kLȘ W6 0+p; an]___^^fC1,'*wXqIÄYQ\s*tk]fFnL6 HD˘Azxx3w/-*3A.d_ɼW'g$IvwwY~WnqyrkYZw$Xr5;B\B*DO/v?oyϲ@B)`)ʫ!J)qÁ 'nn۬[/.x_P}tW;'Y,t)+eȲhVX6tCދDK|el]ZeYu>O_ R:$Ⱥ~L׶ mK; 7$YBd ]yTbnJ3iU_Zk55Mc UjK}žax;K v/PW lxŝOY~noujV-M#mH?ea,d4 O?#SO={K290o^G~,Z7<yH!0}b 鿈wvvYH dxd Z-~g&T%Z[k1 eQVA%8'4ADq>~S'm-G"@*RuF9Y"Te$\"'Apj+Ih(A#[owz3pu\OǶȕ1e9 AipF⯕cY|W,xJ\ObZMD+MZ+M@Yќ;l4@IŪZw>~T%}}nvEh﹥3 {EjÈy~vUzGL+P P0y\ w_ZtYZ~|wy8z7mRQ1ºhs˽\NՁs9׻E1(ƻDFIR;pX)ģ@K)$R6{ev&&r{ƽ:aip؈ Ǐm{{{l~9'*`@B֟DqTRF1%֕67eq5=߾͔E6mZ̥1#~g7 "Zk|4{kC=@@J: xH谘MC-~uz;qmrLPWMfxjoʲ賏Uїx%qmoy\4"xmEtS"B$FXڇTjB0v9wڵ<%%sD\ZZ:}t)GDAZV fSXew10e\¥H'||tyu癝7me9񕹬 QHFr Pyk9\<($_5w&VplTymfK.2 WWWBR_gNK{5/V@ ת75b07:^"]N+T"I(Ev$._ sC0"l@3j^ouuumm}! ?O"]~/綴?K!a[8*%T:_l nF4Iz3M JM99GFb j\^LF'uq٨#"K}x3AN|'ϝ;ǏOyPĒ0!XK;T$N= %h (  (&mѤ7BuDU'HD>zUs:W*,l69{&Fv<RI\_yng?{j㶿-["P ;}zs(۟㸦5&xH#hy^jVIFFֿ|ID rY6Vʤi;J)Nkl~i7K٥/~]>Ow_[n#oNt7* Fk11){sTM҄:~82DJ@HƧg=ۼ]~DS?'o^"Ԕsݿu"RWAFť4k3t֢9B:Bѵ[YGRBTO僿sO^xc! ?@SՕfiV`$իW\ss>u<aW-~m52]LUbJU{ ;Q'/Mԁ[;ržrsU n Z^/O"/yC\ՌC#K ?媤=&.s(NӃ|TUc)W0aN)0̱fl!SPB+ї"KH (Pez29TPfɄ+ᡘȔs+O%  Ir u]j۳OOp>x6t)T/s K8Y @c֊7y%iT]k/-ZY7suK`[n_r-exHXFBPe{Ĭ @˪uX[-{%P̥UevKDJ.RUԜs Z窟<4MO:%G0TFUy$!ܑ5_0G LDzu>tzʊT"IekLCQ5<HPE [z_3<7k,ʪ42V~}ZZQT3T'd|ir _. Oڪ,|KT 81{ql3|gJJ?!1 lh= DtţwڣB)TP""27F{O};=p;f O|+[6RU;u3<㏋-UJlJHSy3Zz.\$(JKᕫѨ#(`4t6Y\w-뜣fm1k4mf,{ q P簼|op up.Hɯo_"CO$+"Ă*`P'%P^2BZshŴߐed# u+x6N{沌=Acx^% i\eRт^&h48o49d0 GU `A!Nŕ+W67787&o„> FIo lh" A @ÌNwGUz# H1ENR<Q^͓VnDp?I443iÁ}:w˗}ѵ5z?ǫ覃s0_XRpo 1՚Bx.ʤ}غ)UZ:jQ!A3erTZ2#ҩQ{)Q%vBDhZ7x>?> d΄J%pvAekcM>p#d0gk}eA{JX1UEΩO8fz_C@i@$DT@@9+ u9c͸A:&+<2꽷69Iɖ,~ՁJȲ T<6ڶt^lq?nB;Z5Lk_mLawsl+ј8IZk (k`4i6ԩ;ҴamIRep컖o{om^2*#m0@77hEn4 2ר~Y6IZfU5 ͕v{,ll.+@O&$im6,yBa{n07@F)ftjK's|-fw^n|¼>ˌuёu( ` wso-4o#g-Z9H k4A}—Nc"Ď GHi4N"})@)̕~K͞ZtZVfs~Ϡ#q>x…xLDyq|̙nmcc뉴s0 ,>MqaEQHT٢e 52rs:"Jm!-:dw1Z NRj0<xyy' tt] vڑe3x V!lPDfOs$k eBr-lCowpC'+^& (YUk%xJ—}0>Q^)+ xഖr{KͼZ嶗#pΕ+;~1jpuK1ͣ> `6ru<ؗ^ Lyj \&jp|SNJ@w~ײlD0KKg.N&q\'rY6Ԛ p|\yfs۟^BqJ4I+Js~O>;,SPi9kNU#wu:cȧY#Јc-շޟ88Dt\!:ȴn ̏{o&70`O%L0U~wmB*mU|Q_.)hX%ëbPRX,2e 9CtC jl$z˝N~gh; &mRh-˜IW)D8b>+7e>$ZUԹ"LYBe6﮼Es֞4>ZeϊwUF{"[v__ sc!UAM,I'iVanbo$(/b6Fp8 á;e0*WuZ:*/2e_\/m =9h(А\Ī_s,@ $G F:R*X[={\f A8hp  5VȔ1o3LSrP9b\n]H"Phus %} '#!:HNhmY,(,J$J6&z⸉b瓲;(fy~X8yw"~~c[ALU5(ȕֱӱ*ll%۳٭J!׀;<Dtż?$i9ec (6W2*[TiÏ_1 dgfۗY69b8G3V7[[]w}gS{豯7%VI[ͧ{Mx߳o/,=~-Z d7.6K慄<-%g7?h]64>ȷ&ٿZ˕ѵKanst8NP)&c 2\6`mam1{-:-uد7g㜬u @0m4Sfmu?y_+m(זH# gSkA&b&-rYWc8RGS P_y3!$MTf2)+V1ƺt*cz,䍂[PA>. ]i `@)ӷ|W.!\d`l6;krx `)^F!x<^ZZCᤥϙ.Dx]N|1{mK,/TnsJ ^2̕D{8n2?蜓 06 pQWUhoyyykkkkkk@@-A,ht\ǥ8 : 6t~iqbůB_*qE 0* 4T tXNjn8Ӽ1/,eT?rFf|>qST1J6Î?ۥy#+$h :ﮬ|ژV2LQ!Ң@Ȳ|W^S(|an9L63af#q0AWTq2p çڶteJ h O>8VJV^kg5\/Kk#mPG N)"c8nj4mdx6;(,9yyW1g UTFMp9 Xy^zVMu?/°R˧֍ʲ{gLJ2NnyyӏOw4[:vmVJ{@9P[[zjOݛ[/3r߫WڛVӋA,@0C01L`0&<1 ̄Yb c3&,B ukCRoV/]m-#NjnܝQQqv9y2~e\W9d?&#xqNhz {q~hmO.aphU|N$_q >p ve*MW>I,+‹39~֝5 ^ڤxLGY.,M@:gd7>՝^WX:q΅whJeTA%EZEu&_37sO%j;pW;6okG/]˧@eեySZzKwhLI|oݸʈwQpN l:4z]em/=WR%-a؞TE_kZVөMJv70 +jaDZ dJMѷ(v<حV;<sͦ' ;,I3+W~R"[ս{Z- /\h¾&la+A<[܃푧>E*axt "]~]ac;ycw2EFnK߳8&7F &IDBzı`Xiݸ3m48Tʮ0 Zi'RJ/P&cfxl j*ɡtq+vn_ fǾQ*P: 1+++~T4eki$Vk]خӎF\Z֔dQL]ك <56(Cb(N͠\NqsNC2HFMA c\d:%p{zQk I\zzh<QTu1wwѱt-j`l(e֜1QD5݃GQp!{>SS\ jLp nЈYk~ٽz%eTҡ e? dߊwoEjǏ~9Fc *&-jwСE\0@fy\WTV)gEሸIǖX6лΌ1[ EDTJ0@EAⒹ v(!"xEbn/[yֆٰ>I&JG#' h8KzZq0dfh>_c?N$j "]7RnVFR L5]:)[&8VZooo;7,u뭝N%o//T#Ye^RY IDAT x$*㸌}'9If$"Fy"^a "b!)ƤbfÀysEFK F + c\}_yfBj`6ƭApӉ9`4iz+MC-܉w>LK[me'F'??17ws 1 3LS9#)RUϻtWAQ {޶ܸ~92A+Lp2LS=!tq"gKq46Cn)){ i7 [_T2cөIKtiAFxDBtL4н}pN'ȫ4}k 2PtTE9HW%"h4}g[otl?+\ sa('3))BYlPH~]l\/Y UvBƈ9RV`m]ZAfӡ1[<ֵۛ,w*[`[$k3 Q~Pãh0 F+s`Fo>ǎ[YYHkir:Rtڴg4}c5~w%.j-4VS/;zh6#_³nthӱ][KNPr{) Hhq=۪G`hT#4a'qV|.8TΓ811~0h-"| ?ڪW&L.LLRՅ_v\\aw;H[p;{%с]FcuuNkcdaUe.ŚS-kzd2iTbM)ly7KE0LZr*91Cd8i gFXK{Å "f8cBUPk(HC愙b`i ;^;{r`52Ȟ^4i^'ڨ#O&"0"s;sg?SdY|Z~G%‡~<-cBDyME߫Vt\fyE1I@YNO%ыwre | xq^UӘx~8>"&p7j]ffF?AwVXgYkBs~4T׌1 ƹQ:71 FƘ2APэ 5}Scb_#V~ąGx|͕OhvijI1j.GN^a͓_kvOU ~[,O>Q=28~5 (cTMƈ)M1TW 1H{-&2m#W\@ek>!5X5{mE4369wjTK.]pG+޽{m. 6}oY~,NPX&LEA8**K}o9rx9(Ur]ny>.Z׻|ꪵ@N_f֕JJFaWTE$ofWy7Dx^rRtmAVeuH4%u`2zn9IJT/_X))Ic2"d4\B/xZMJAA#+d) Thr ԤQx>yF:Վ! I[)e9ku X)Xyh6?eL&`&qk+Y5K)/]dkQ(yR , gV(Na37\#҅%uo+7L=JI3^Had45HK{ňBZpbq.z^y$Ƣ5[(clחWpi%FN5\Z`A$H 5aZ`iCҺH~O"yx! Ű ,a1t:6iB͔e,[ARKf_xr S;ٳ  X I̊~>gn쨾7l ?<ɌޛBn?Ʒ+)I`_u%gSk .tq*n_T:C4SI5b`da*{s)JTBg.ahȁx@g3C[P3e[Y7<0߽ldk9a=oڃ;xџ$[t Օ,0lh10S%3c#nI>32$h9 W̃S00 : C]{Wlov[~XXnvk ӻ3Nm:]u}x&3%E׮\lsQK ^w.:iUe~yjwq5QaY ˬ+)Eejfq..ys}ûHSUUY*gc͍/؅t:td2ٵ\K)uSZ\Ha%R7PRjdȚ=}L>^-XQeҽaD.9822 τO>7'ə~9=\| ##0"")Y6T[̄nǧ w]7TʨZׯUk7f{Z+`b rUkX ^zR*y7\+ʿ5˳2@bp~fVVhˊ?S k&_7ӝlrcѾ:lm:[I(΍~ޏCq!U?4c}d-Z;h3Y4.?C7ids3K?Gi+R{gb<%T%~FZ9 piOc46h@,zc\,Mj"BI0vwwx.{;svz=P]z0u+}߮ۼTVm^+޵% 8nb2r^="x>8# HUaS5cdrui?_C).`"]nUjRiA8x\x;V\Dbn{N0oF*#lΩΙ-$f3 ,݋ZhFOݺx|fAV3mO!C,fV p UL"=͍-vMYOA% _6:o+7Tw:W_n;Ι>Ȁ esKl q{ 6z7]^>#77QY]QsFw10 pF _^X#^R1=0bb<&E??N aț*plzʃ>h}l=w!DȲl8UG墀’tqS8v0E6 xxK.O A޾^XX3gc~Q^ܟ;/XBי4E+#]{֥hӲ-SfrrHDBKd 13_8s8S'ABh! jFD\kTqطjOzqMDKes4_j2USireۺw|~eE]ccN=h(@l y/UgQ8R:_~$q-%ssTjo8׾HES #s޴"e6Nvƃ;wgċ w.9/DMɸ/e¹ \ 7 R4Ӟg7ۂ8tyLQܐEN&e /ʫaI"úU֋fmlLv`Eh:onnz@>7L1hdic8g0`u_Lv6tJ B|OvUfj pFynFz]ʚls^;mFFcϞ=EQDt1b!Dq9-hq܆k]aS KKKvX].Jj`7[vt{fQD)sQ,-g8TBz*/0D1b@NH+BkF  ʡw&{{*ƙOc3S,̃x;wAޣkYW{v[P7`+\)o;hV{a}ō땽h Tژ Ϩ2Q}8ģIz<^ZH8|K8?b1SyO=Ru8q:~񗿢֙ ZW̭&xmKeY<N՟ 1Fd9B;Mg魆W^_м!jvz!&Cϣ|.=>+xAc"0?EU:')\/9ͷztI-FN&z{^e7F6SYp_I?r'$)Mz{i|z~Fqᨱ}FYNgDMQ }02`8zNrw^|`SDϐ!Ha1c2{w=BLjC&›h]O\p3NBUb7#ҽݚ]DT8w-[Y)^Βp٭[ww-zíL' |o_oo}_V(7 TQ2Vylwn\d IDATWhZkptZOYW݈X"w[PmZ@wqHx =m΍2*5e t+v;DCמؾu;cP#خvݮ.L0\YY;=ZV-DIJ,w<[8Gkfx}}…SN?V&;Vϯ 8 ;lt`g׋{GXs^p 9.{~TLyA~zJK[hpʓ?WWuMbrL xzk.okKÅ$HfXx#1$BՀUj' 0  0>ҿu~Ё읗/?Ȳ1CƙĠIk27EG$~ Ni"E8Teジ`W~3"]w.oV_ZBm-DK.dP^̟K f$[fd0>ѮH{܎wjsijھREsssfsii;/+vƘfHW)e4+KMl")RFt͊ou鲢ݥʿ`PnoZ܎4eւ/lp~ V`oc%5]*hzN8(=)ZmeelbN-0cU $ @ hW֟N¥2i; 8`~Y_D%1!8hUlM)sQ_TEkoje VC!`BdU{rk㋭g*fNk񦥌t*eTTs5w [lkڠ~#yJbuE#'u/mG_'AyGRC?XX@07*<=P_}O3q/ 3ؽ4˦ (K1ƣi{iz<[?VVQTGf^86nvԠ=ç'e>p~맏X4Fw8iv bqlʦIJe;B^104<[ (` !{oAc0y 053VL`0 L%#vpdA@ U8Dje`3NЬb^T͈t˅kEUIqA(~}kqYj#l 7JUR(MBE1G9`BXn_a%"TwޱcNYj=zƦ_U Vzߴj  ],ѣtkq3wD7vΈ57IWgۓj2Xun"]S׭z{ȚNmoo4WJFqر{wϞ=Ī-Dpd4Lpzʵ߰LmPɔ_uk]` `ff~*-v1ܿHj(("f80? fyf.y\JcF= <.kudJA)rTQ#cwhz;umD䱼m1ZmF|GN9`m(hgΪ`5#Gq|EZekv:{.nMp < C84 .;Oww{׾bp%#gݶV T>pfHiDkR&Y6fxxh7\/֟ &L DM2c HgR~ǐhT"Q1 6 V&s= h`=>xUd|2Fv6^^k"d$1s0-s*O-̕2C|"R1,n&aO`Dff78L}_R6Ó# dO?8 >~GMy>LqXhXk%e$FD12ZIVIȁiU ;89hL=?O^N>;KkbU7h?yomu}{8ӝ<{}׏13Ʋ$N,xiwTFY)F=4-=ʰf&5 /웙{fm7*{QȮc3brtʲ(I4lݩ]]kUsI:VWe𔬁9fk&_%Bf@2zB晖XduGf цsQy3n`+P4{aPޚ*W3j&wW36.@fx'7{/1u{ftr"W}}I k o!vEzkA+ {YZ8^e4JP,Vfׇt6FaWM/(LJΟ/xJ#]-|F\;ӍVquE: iޜsujt&kLEiYyjmxזr;UrgYPU܊ $$k~ |5 ́(zfx'Ii7,)m.1X$,+İH8- ؾ  ^]NEkɃQ,4n6}_ 1FcjjT*mDaGB,8nr܌[qDg!s]zFQ8~5{RӬ uvE9+*6L&2)/U_c'5&4.q"zcTa+РAz s h8j'^y,jIc0M;(iIy~V\*hZM $xz9;T  1r~r>l{ v}u`3 {q<:$΍Tq'4(~'XpL㬚A 9WׯWk߷ΟѣZWGԭkxPpzP(eE(q,Nڔ%?tvGDr<32e-|K*~ZfŇ3;yU=f2q4_t<='t'p/3ɭ=Q/3Fͮq4r0fF`lbT,=WQެ53-y=x<(\ؽCOxi)0MTs :I(Vsb2ĕi֞J#^P]Q[ [[8eYuƹAeIi/ӋGh%'ZkQ0ɾ@N`Bx_:!=8eL*%I<w}]1#2z__Hdei4wj8v!>*q\`շ+XgpW>5JƀsUdI2s@0/Oc `Ž}qKw]1qJZ4gYh-g\ y@0ٛIJ= a c{ZPll;fKQ#u%+#WFx/} ٗLOOS0w4:m0-RJx%bB(ubۇ1n{@SH;46\^H)REǸ]:a\z(JR9oz,7%0FP""2jZDR$9Uoj'O{I5K\J>`럙Bz}^zw3 Uka.1ă@Jyɢ{qXЎĸotĂ `=4n V.e2c%A7ŻEN8pRZe 0t۸2I^H,l=FƏUy59|hI&awSP 0q}"B C6V+ sN9s.˲& 8(d&I"MxL6(UJ&㩀ÜTڍ8Ƶo쁹lzCKsx\zçt`}vj>u,βAT]7(gID(Y)OЩ3JC)d36dU/sY{1v#Ye(v_v 1!%RIÍ#'X :MѨS2@㛑淋߽]w=똍d޵hGk!NbIbuŕi%stu|liE_p;3i? n. P80*0h, !])=S3J4 GTû( f4(b&`CYNocP _y} XZgvyr|ԩc{pOM?Nofa2PEH7k "F%LYtdB܎i_Td7?[|VQ`au"s6m2jt(9aؠLMB< :T*Ǐ)3`1Ʀql3{,5Ɛyx{.QZk*}B%!%ި^@arxg+ 6pb>c5lK7wCM8޷Aᗫp!pOQV\0DjBИFDk8i֢|Rd7H@xrIpf^n:R1:/ 54A v'>vչH!i|ǡ={ pH ^ K#'7&v ^V *}^0oژIΝLy2emPcS][[c1/7<;I NN zOU_9_z!']ʢDD(>`dǣVrsu/jUYjG驪 rc˺o`AC4g37Qg(]j:xTT&[.O~EJGJ(I<* Ź|oadZ%ḬDFNwsnv8*3:ĕnnSp>7lY gLhĥZx8lT@wW_|+lYsMI8-d]^~A_}MڋGоp&fQґqˮ՗>LITaHd^ꤛY룇?p.I#VdL:yYHr(%fNz61Q, xCA L0&{'K͈tm+rv-,<URI&k0.mM?%QhvرVuҥ\.:t]w&1bh]Zxne[Z0Vkr%Ri&6:Ov 7EP^@>~OQ7TN^ךRJZy!*chH-R#ؕ^醾zݻ p=L0h\ezŽ{!l(zN_J_GG WHgٴoq/\0??O&k.BJ4M!fmPVN [f2YPrmR_5+Tg8t 9c1Ι.K#m$ 44]Ӝ/ri4"#HCWcSi{gH;v``1;FM\@uHn*"-V,MHVa[U^d6}c Zޱl;/  c;܋3W hvWJ%[}`E6T=5)Ç|}al`}#lx<>v4?miv橺A\Wؾ-h J _p=Vk]8&,H*"O/o]1[k,f Lkd_:wǩ R5ƅt=!sWM;c`Q8]CwR b?oBUsV32(ZpoZ0B3 Jр p*JyC?\o]_'>sM96 Rsssˤ# r"(hdGJ.r-/ HK蓝PP,|co`&X0(ehdJa714F[t殰HFpgBއ2%`co cE\m\Yt^}l ?Tf͋VJQRRdR-%#8*S*IX,T~>ovg[_fJcx&bJl9H2!0_8wc`TZf(WJ/j;*Uhvyng,,;Njd[&.\ JL՗_@T*0 ̤u-ULW(=.6H0FFViP*ISs+{WZ׿~c7(q`=:\ރj0ffGO̓/# E" yh.MN6LfF\JS9xk[{`ٹ޾S\`,_ns! )ymC܈ EV, FQD[,4QF\wɷ*+;]7{<7* 3l쵨[:yTm#$I̥EQ@;xСC`iA}#Gl}iBZfsnVJŪ}?ߪ^y;vaRSBZFZZ޸gf@5!yR@(IգS&ːKu19h~kS{K)R#] T.{2z0C!s(~1t v2 BI;q,J#SRcܪ)&`p "=]ʈIN8QwijN'ueݻnFph e&2_ ,UݮT*рQG :O-.%˜t"%h30OE00's^{wy`UǺW\6Fe~ h@\+Pnuycɠ ѩ3o?>d}?Y>]ǎxS+fz[~IiU^sx gS*5ZKq'Ih)]߯Ak[Riq&IZJ*R_h6u6.]/o{g۹v ~Xr8]MsiJeF3L#BK<&= x í,+tF0^:~+Orei4${ڼWϖcjo.O;tu%~cS7=٫LJGfd2P+΁eಳ'R|(J0)P5chWuC]}f&ЪIơ5yΛ O!'ZC5zb2jZ[[[zh4<FkkkjږGlzAEά-6UƘ߹z=vZ>?y$!2a Į t J)bS(J>vOHD8 (^TfffJJ/<33dtbv<Т%H!15|G+8-c]t+&=t`Nz0xJ ){mݦ"]+la u͵0T*9rT*Y5ǥ?$bo=ATo`Orҥ[[[mYIJ_㹓RVUrWWW~n.fpa~sr)NƈF"!C"ī+L<_,&.cvc6d{>nɞ1PERcґDapc\HaKq<᠝w2>ҙ{giumVfkYޖ~w^EG.;+o~k'gp=;/^4ɿ`er5#^_Wh_F0juVK9t5+_>1;c@@X +E$I{vn(LA滃`vv666V ͮ'ZJע' w;c9BrLI woݭxȀV_D4=`F !XWsx ~s/{=P~$/tb|kGIVۿT>`k' ~q< 6R1!dYBr08t_ A9{D4J #{zVY*1F #z LreKen҂0 ,X^4=,2gǀۂ`Zm @cƭYP ;N l0R@,1&5Y5؛ҡ}"v1yuGC&0lZVk!)i= 0!ivBOJLaۮc@1~Z,`gѓZg>3yPZ9졊n2 KZqݡ KeYDdYF9U$cueZdnQTgo\Q0* :]]F]4QQ\(EuF2K%Iv=[[[  (OŨ+ %"!k%a{s$9r=:# ^I4+!"f"-(4P/? IDATӳD@wΩҭjԍƘj0oL*nI *@hr1J D2xduz-RU.R:.#)yFa-hmRuLU+O{ u>moY2ÊO$_V{cAp\ˑ?xm_.F&ut`>/+X(h8.J4ODJ4}u:\eG t[Z1Fzץ J P>^I O{?`H)]Pr}^l2LB8]^9y>_Y!?2" [:1''1u7HJy|X0G*NgeI $cjA[*5ðY(JT1F+8Ia؏c^cII8x\>#?9glb0W*5δVqsx0ˆQKA[Rq<|G,ݵaڵsٹ  ]V 31APݳs]D+6C>2$ύ>y)'>…]x/V@c`t1 `{, n>Rw ^F :\ <.6ҽ:18}lw:`w0PmIZ8z}Pʂ#rj\qv'lrhQ' d1DG&?o{7Ik8@$۔[p…;'Jч C{ WZFFۥvc{~/-bqZ]<ʫZ-Whdh4P崜Y+mf(n߷QQ2%o[n"OxxǔfadEb jҥnmvvVú؝.H |)Jnޕx:)|B̔j[>2rخ^=W^_p{07IBp!8t(<7fPgNnWT[0!` sHΔϘcԫh" Ūf+_~֕nx%0zq0@0@r0ߺޖYZM`@f{Y455o߾u9pD.J;F0˲b!5Nz>=/tŇ~wy@ ^GtuZx3 &2;GI./0 |ꡏ^%9꿁<%Gy9|0>K(t0PwcnB(Ƹc>cL*\v{m8ܢ~TsR$ xE$ 9W5F34ϲ,Kxc{/x~$]\~Ut4jilheѾr9dő#v4|ǔJgf-,_3F-,n\r>ͦYzY-FFeE,$ RvKU0}0X{?&/61}9nMk^sH_%z!ل;31D`2@C~͋t-~; Ea]L]hBϲHyٞFy^ף;Qv[z sfy 1ƚ uЦ fo'Ov=s? tFLC8TӕXeR>8>ZlnuņmoE.Һ;n ymEZd86(IӔt 6hZXX>E;!Oi"jk1mi~QrEpP"PxuuóJrf>7>\ ܿBī:k L)̡ٕ+(:{ߩfٖ1>gy_\H&^RڗUiњ܉߂0Fǁg ,*,T%-J<"'㑱vZkőBI"P_JZͼU45L.C`!#@fЗq3 c DSK^# D#'[qxSS&p XxzZ.%>g(9)'B,NSB\H2sN\LU*U=g Z+H0)%2ip8n מx+p%!$2Ƅp=TT+QF+Ôa3ïCcxӹY=;{(Mnwq<ƅ R#jRs)&YC3|nUu/S0@lJko${S[Q='DVǓ+q@E3XӉ{Vin|T;o:4Z-RU@C6` &(ݍz i.雒}r+ +NvVwcc.\0R'IcQovV\_$ xmS#=%-~z=*e9X;~xnnnvv@$n[__W*1[~f}}}vvVy: C$DMX=Uy4M}ߧ״ڵ+ Õ0 ?D݇Y_IѦZ0ԘB e b$NdLuK떍:83@p8^W=e!֊Y4Q°9/tRN2\ie&Mq3 fsLyUp9w3WW˘!JZpG*M+9ņ\k]V*ƞ[, 6XnfG4q!zqSGxQHH b*_MN^u;}װ$1{Gy p+oR_s=+Kgpӵ`W/wFAFA'|uMtqC.%hB! b`,t:d Nl)8s%AOZ}jVnJ^ x5dQ1JBCᓉ}-=|tw˵qo=~L"_DH멹?P/ <d e0 pƸt*Oaok0jg*uܒg@TnTx 6:;HWh#S%Ҍ2&imG>n:/z{2jDr.9wt=R*5ív{%κR⮆c 4/).3+^uço]NكZh L3D1$P$,MMl2 a85ѧzH 3fq.Bq`G+8Aid?T*Ϲd נpp0 htn: 3,,7S('\dv(ܤ\ky))]pfqM5y&iW~e΃6jO&ol Nbk6W\k6t:ԝNvFqdVEOyEY; eՒ4HיBBKN@zk׮4ɺ1daet>/^Ї>DȟBYL .{ .BlHGH<̱cǨb0YA)F ,eQhGMRg< 义yieΏ8M\k,ƅ_9w=xchff+1JiU@ic9L`Bs&c1F3iw'z[Pf kw'V<kh A28 !r1I\?}!݅4=*IiU{Y__v-r{dP.+ vrtP ;a]}?jhNQ7bP#e*CX!1(Kf~?A= mZVC,CϠMӔ ft+}y  h4lh4|! 5ܽgG}B<$u-vS.H*M`}ϳLx 0Y:. Daqq1(‡Y>&~̛$r6C%(tvFN&T7"A"8s YR4YD2#|#4i5zO={^wY87lV{饗`Q6!iV % O^.S/0<$P8rZc3uDR +o( \N)E ٫'xºZet:.]z[?z|Vҙџά޺ҩĐώB$ :BPwvv\B˗q$J M:lZuuU֩Tj~~իv[;2~u:*Ǝ qe6iyrᯛK4 MܡH%ٵhe2Rb4Z+ %aZ$ R3)9mщ`,. `H2RE0Ch1$TR eR #4$sl6;F%tUds"2⮉:qaל:^.ax 81S0``z?xoPRRH&S7%笢K B lAmh֮xP,͖*qLnX8gX^^^YYo./_ş5wq7LU:7\/^xܹEj?[yH<^;g_G-a9WN ]lVcz#B",D IDAT$CT8.dJְm J*צRTgm}+:l=BQdž]iXFB!T^~skQ)%D6L !h+IWw}vd@(DpҐY+$DBgӲd2XJU)RJe~~<i.qIKb(#?S(dzcvs~/#"$`OvZP6kbiY@H.0C(DR@Rba# <րP`Wdyy %2BPL9 \&TR90q:n%~<p\ZC-X?zaVۍ-:?*AJ )2BJBg-D 01,b$w>{R.[[ td-BYL/ _HqH.ɣ1A 4 @U|0&b\h"d TEn6%K.oC2v"?ҹsХx{;55E0"/0+>)cM@[P"zwwwqqd~m1.S៘:1z_.J=m4v㵙grVg#m @Ϟ?Ycl"č 5 zX30ڿ0ZT:4HE b*`rGr)Ic+0{,ƃm)klt"nǁ[ZJTj")vDO+\pj]tV-tznnʕ+vtu6L%3"WXX9;듾Rؤ+?;~qCAVf #/[%K,HZ-Zh]2n. N*D0{J%Z&GԺ?ï޽|z6 1 @# 0I)1#PH$@+[D;. +ȭ, dJt DR0LZ"lTV&22ks6sHd$06B4޵QSVJeP 1Q$Al4{B݈}s;?THHD0LJTss'*]R0/ͯյbe)%3{kCa5m\W++>p';=sec)I ":1W,^xiy t3\t[}2': 9|IUOiZ/tDUUooOF$Oy!L9%ֺN $W0R6l>anll=tFW|+ҫ*DGB}zpիT?ֲ 4`ν(lvmnr*j,N`ڛq^ 4A$:r9iܢ K@ >|!`&r个"^'iB%A-F$#يM@@ԱcΞ=H$FO-Pkl5털C2Rx1^t 淚= ]3[tlL&CO-ElGitri@ `}nc5e?z hm0D2Hg!T̳Xk/cs!Dkօ syN%rWGtwe43skW5yS>aTR"Uɽ#xTZ+Jrlc~ZpW9MZJ3ˍ*cu/_ #ӑv()9EQH$W,\0ey?~w[yg,,UFJo1OG-MiK4&ӣlcƙܒ@/Nͅr=> |ʀS&&Gyŋx^< n瞊ΝK#:ybzƻ?]Y,_c9>KT}и4\҈.H+֢ t}B"(5``ѳhh`J$I$!5zB aŭB*iћkQꐵ6ɴ9!$5qG4:=x]LB2RKROU8{V, _j$0>SJezzznnn4Pk1`mm8pvX0޷Lhv_L_Ҕ *T&׾vs#iOj3 DRч`׺!uIf8[J~4[Ky>Wc f!+!Vv7 RA 6󧪉 F 2{Qs7b-D*-U:ątRTiͯh5nGR39I)؄|oMк2x;}uq >@`q(^qWU9BO4XẆbƓ“몉Dk:}Q!^FI #]2vmr)k̙Ds>w/r93eȇ)D# ]^oss*Muӥp:.%B`n>BҠN07Jx0n`j^ۦ6W @; bl&PQ4B4ZF9Ob:&.P(ANmlaBN -6usscl߻hR2FZj+"qq:MijJO޺wĨh$R`sUV޿Ōtps---;9rM}j@8lmll}{xkɫZ'BJGJWo?:sss.]zqS#nҝNr3gWvBA*K%t1Jx" ԝU"FrhX3U]XLU#XA2! t__y{Yu:mY bL0A0v5IWf/ 23N\s[m, ޤwX\律5^0UA#za.'q +t,y5,ۥ .K>^ l9Xk׮u}l6 HƘw&v/S>tMy;k%߹6_rE܎Y$h2K8.atauFy&0[[[ d)hq:d!]wS)ti̦1Cw:NCnM0d}ћo@a/¾nakklJ«'#]?^^nWUD \%gxląxL?x\.Gz6FR IrSB Nߎ]|~a(8B+ U|$Xkn'ݧuhXkv%b9+G0OjbM.;uZgyfyyyaac=3 N噱ڤRPn- w՝1faaa?p»ݰĕ?6Ƙ馛Dα[] ctƘ0 {fm d<K9b_}B" 2^CGZt $5M%d^,(-MZt'8 ¯'_ᑵnRL&9F0l2wdF @g+Ó ecz"|>N]Kj^:5xcLɷy%T:v.3{ċl6 7BDQD" fsssRmgM_y-߼JRӖ1ЈӥFn4S{5ЭT*OVdFFM>} %#N^/6.$;VTL;:f"'{xUI){)2{!$ p0t #,~/lY Lq-dJ !1׿mkd^V\NguJ j/^*3Zfww݇>P(wݼ۩ti4F:j,h}M-|;o9ώK衦rryxw H ~vsw>_?Нea9s.#1j?S̀^GךuE"]?) o\@G脆ODa7c ~vwwWVVfgg'uu9|*9n*#LCҹӬ!) JC?~iy "aVTVgffhf\z\y:(VN3穻1fqqĉsssJ%QD\>_c=Or߉gwO_ZzM9kT)UJa8qb0|s? 7u:W驩F7\0 vP(<樅"]7  mboq#+o\"R)I$ d !wk_Z~Qng`H' %h݃tab\ O^2l___h%j:;]8-`EWx%QXKR|/i18'/0!Gݿ l2T5 CR'?I[>y{uvͽ4rIeNza]| ʔҞ%bPxU *VƔ8BZK0fcLQLLpHqo4w+7\VI0WsDZp3̝(*AHH{Yhl.?bnwnnOMأO pF[㕴BP2ºT;Mr'^3d"0B^#HNUtb:6Qt^*;Xjy4 !X_5[0$.:uرc333qi: b8<-cx-_dVO֫kH)RWhႲvDX GiZ_?OT T).0J˗8sCzvxCn7ϓݘqZXe&& y0逼LQ .bzFH7S-ʛGt#Pܷ[[He;AZV((C[>B琰H{ IDATŁCqLQ kmբp BZju)6њ8aV RD1e8Bx'iZdJ0*`Z%2>Da`e&#B`.tF8jѥ4e%|JyeĨKX8.KRnK`])jRʹG*ХnO<я~R+LCPRx H'YT^9ł Ot+孽 RrFJu%ZP(h)tt69vsR0qK%k** k#}}⺷o.6^0 ¸|zlT*~Sw/Y'%IW/uZ؃2}ɝijFܿoL:zR HR4;;__U,ebq2)v; t:GK,juvX Ðhc>#pv`!8= CDu,@qeb{У1?;gOHd$FL"SFؿ"ZAFN!]vbx0ǜ&JX$UX'{Ac-r} OΔٿV:]!R^ve-Wu/{ ð\.OOO%T*=~-KS(Vh[f/{"iX[>H8]^ŖRed֜G_|~0rRDvC q'8]N R4f2lXOkZ 8c/{Lkf9RM'$DR&Ż.1[>%† v/˸駏=:??:Q\&vB\#?z|yeF.P`!vЋ8oЅR}6VNkq= 8BڄR]m6q$$zRƌ4^ǧ'J02W'Ƚ͞7|<+6/~No;'?\Kn[ @ C^XX.=^R.z^׷ ,OZ-*9PW4TL$4Nv ;f$T ) Aha{jX{oܷWZ^^:BYrA")`hA*/7^3'>XxD]_^= >ћ9OXrFo}^wO4~\y•Kd$Uv ñu˄,,36̱S*HJt<(a_߫׾ez:̥gB\BJh-ZIӹ;ciiZ 0g'OXy=v%~'.7wwKҏ4?|K~mfb;ժDK)/od[  ً/RF&w$M"W(G馛* &7W{պ:0D0yPT?xCRlh˄EZ,7p|[ rLk~jjRЪ/rr v}~k_TJ5L&swr-\ȑ#B̂qLf" : Tz7ɐxR| Y=>1jӥqf۽=Ѐ̜7Zn{ڵK.moooll_DT:y{wuoKˀ N͘ e%d:N$|!Zzv u:`@OK{ٹ|7 ! 'ujbO|q?@ @Y__vLBrJ ~>"ZZRr="=c'הؔ #A &OHΥ~848ƣ R_u8J~^b:f_qo (_rZ w1V{|PErn4~OHb "At:juB1M< ]Er\8 C6gg8DcRMsWc{ RP\L?ɯ t:Y08f)#f8}p">/R*J]tE@RfQL"Y /-|M٪ؽ_77ۏhdtxІ}\@B^BPy)plnf@W[n[fffbuNw:;e8K/͝-Y']e8\OM[ L:~O|ځvh1I,l2, O<zOáJ233rs2F ׏`+l韲@0:/>AwChwҝ o}XB"rg]YYzD~Rt:MC5OED !.ұFR={[ny Zk3. %Q ir:/ɉ"5뫫/\pũ)S46+ڻ`_L f4c.~"x}P Al6;==M>fyI% eBk6Zr̥ g{BSA򕣔)6S38CxesXCy0Ǹl׿/=6HQ,`5Lg/OJdX+,1O]V3b"cF12r})A呥Vjc#EpyyӇjlh\:_6"Kx~R|`.`FH Ma\T}*vȑ`RBe+p#Wpeq2Ço#i:f/BD}[VժKA<"|R]sxEH,uŁáR* C? 6aPG`m!%v}Xm؏S +J߅H}.o!#E@muy__7C\މAcƎDz896-(tvwwAPg E Aqˎ-vd2,FׇZ#H3^jۥRZ s)2F9/cmnٌxvvX, (;.@vǮ <?YD"NSD1q F ͸q/_ ??U۪y % /6tm9drTXa-0R+˃o۰5N:-  Kb_Lz3g,..jrqo: 5w\S«gF HgH>4)O(3˛PL ]֤Q띟oGߊ1+ vRf[onqTVk6SSe*CzR-S?=߸7U?ADbOP/DxNg}}ƥ)hx-xo4TD*r\񘙙PP !]]2q(FdDRIӝNZvvvnVc1|Wf\Tp3y;Nӎ[2i]ZZ`<.%I^5N1L?1<ĸc+t¸s@` Y] O+5)%!@,AJ%*|J%F[XK7\({bAC~Dž$|PB4lEu㹹SN+Q6;>eOK^"=cj 2;{ jQDޗ~We3{J8XktDw%l6kyϞ=;q>\.ggg\9GKVkZ4AjZ$%qp5ż`7q#něqr"}O)E&i VTFȇP \RJ{:+^ǔPs0e/}O!j+%QXR0??OvԲYr"f-+IW̊Fu-38oAN(*un[B=;|my6a4J|>O0 www[N̑x)!T*U.3WvrE \ZF"E>hpg.V)' s_N#.0a"$q{/^$43FE!H`$`G"ݓ &(Z- <`tm'BL{1bs q Lp3VBum&1aE|ɓVT!kQ_`ꓬTsa#QBqI/'̿Rtv~g/ , "+FuΩTرc_?1o޹֚ŝ0fl\n6$J6^=j2K/|sXoč{"]-++ԝ FaTfqMa˜.NLuPb!/[7з/| /\_bv :ћqN>H;rhycC}l6"ٞLx]MЍ{5%1I)ƝwS&|ώ,fEףl-셹ܮ^pV?.Q"H@ץ t7eU2kaG>Oi ͍;U10vF0+54 k1&7JwA|yTbu| ֳ.:pυU1M ?<gax dKܻ2+:ڽuOD'9_"Hro|٥ͪj^%\. C~zT˚$j[[[FCkK/?~@'tSWNTtTiA'\4 X'N BR ϖ@<]:*(` M 7r|T*s]y_FK&6 4QfFV/_^AQ$E#NWm%/tq \mjBlP:l]ӆF=KIR ZceVM FqcǎQ/J=A1#3ɱU+f,ÔsT`XWv\ta.֧x=tH\ZǗD_K"huʺl\v]TzN;ggg/_LMg 9 6CdhByPQ(zD7F܈ rP.PD]cHJ>%=/ceFsi[.(R:_&; xʄ*p)yE%/~ܹ}` F JzY5=ོ6>< sU#s|ׄ`:1^_{RTTrfZKt~ݦ251|B)Et&pVnJAE̺ [ɐ;v* ,/_\]#n[V JOɐ׈)dEGEȔ7!ehЅcX__m!   ЄTI}V̙U~_R3֦B#2蟔h]ka!ڝzՊѣG9Bu<SBD^fdD&`t%b7",0fe7ҽVηxk*0Y ?p.әëU!)_Տ=egujhH^^Y=UU<Ɲ(jP r:rǝNuvrϨ IDATk}.=>r#nč8q@.^F@r8nllPM3;kj5Cjnn Yiϥ"+˥R˳1NH7v'B!ELlCmB}`:C `X EӆFJyH `/VJeV3wNV9c Iػ@Pa0 U0JDE\0PrTx[_ߘ"~?55PHO9&^X/qY"q._+l&1j_gpSY,O:e6 6 " ]7@TrQZz衇Ν;JZA!jH *q OH͞ɵZhL(ވq#l86Hc ! v766-U* ^d#R8 ޘe ~J%Y8vH#؋&xSb C~T.Y*d-Mռ9nbvcvmEptrOﵗȃG=D/OP 1Zb;`1. <_Bm°K÷Q*2N$vbemT7 \zF>&E riK_; vJ\`= NW1*Mƀ.b%+'s wϟu u8qOǚt {<JPm+$&vt:59...uEe|vrގQΎMkM76eWk\`0`UuڳԘ|`0! XXX듿cN@%&Q_AVJf7C&p )ᏺtfu;0g{EF8-1|ҽ7ǁCx/Srctҳ> j'NED_'OR.]E^"@k |]™94Ķv:(^gwn8^r 3YNOO_zu{{[Jh4.]tB =SGͯ.wy2 qRTsz f#$":&8b"( H%A $B$B%?!rLDH"bH"W\/bdAfYz޻Lu2= 0~vW:[UzsBZ@dN4|1 Ckq7{)DGh[Pnoow\$~;)aJMN9،f( &a.Ֆ&0,B7H R\3K[`p1Y= "ي"PqCT8SUMUXLۻfM4gFXIE5QE\@|w ~?>`bsi1yuٛfRX4=*P-V-FUW&ſt=~btVy8s"BHW5JlI,hMƧVQ\Ab~J|,OչֺGіLNiAػP(D$dDYEW+iiu@$uhT HUxf&Lb[)@4TD UuRPJ p kA=AW^\^ɮkOk P4QcQUBWns*P(?mf$ֱz͑h~4 ` a@dX!5cD&U2 bn@{ $~~]]]ccp^1,,s5![ xhf첐8R2Z|lc" ~b~mhR5r{mbX.Q>PSvSw(/{^Mv&oҠAY 9Y2XC0ݔ0T]hH5XXXo-T/]3+.jfR3a'7ޑqRiVi;?SHYw3,K?'&w斖ZfYXU).*v)[&%I;''H~z!3^5]5mpm6LZ)떍%R=%g\a:KܨN">?3WY_|%†D3Ī@fH#ѯ_?U\,DJ` oG6] Kd}{]6=Cv+ r?[c&-h)Cwo>ϔ 5lO-]{eI_ς1 ,gJK*ΩnbxZ21A7󶶶M$qo֮Y`K0!]2r@b2yQD5WъSAe Z&d cjq {u n4 ABGޱpBڠpv4 + tŖ)e۷^i Zz*pRWwһtrFʣC:mNV54bHlxGӶc߇~e~HEf&?gn9Y΂-B2#~{~ǴuC l8-<U$JBoSԭ##iLړ4P(LRyě)z}26tfHdz^4ܬjJn?Q#> L[Bg.g;fB{3@|rlo!]VeᖖBn.Kp`3en4ZA(6)m#i(^% D~PH#-|U7b!˜ . ..mfNJk #bq⽭D>,oЍw\r`)lPᶶ62mۥKH֝EMIs>0g%7QosVK)? K)H>g{P3ԭ[.+FZYr֙9˒xAL B!DG$KBd,(D_Q '\K |==Ҏ=j%eF|!/v[$7JuUjYnM;ޢTơ8M4$%e ̵=Ӽ^#qv%m r͡ޜ[B0<0ep4Z[[ |ZTX8 kv RʲrTUaTnw4mll4!`~碋rR֒mfMm4DRn ioqgCES[[KӰaf_pT.h@3ؕ8qaDA"nEh.JKX,v  v&$<'y݀>]ۂa%ܻy~~0ps> OT{q{^^l{2bϪIwWrk3+\%HFs$- G[__)2_TTdqK;>a&͞# ׯn`ELj2}Fq;EӞX PWWW__O*pfc>BgEQH,\:uɓ 6uo-p[g)tCb,`;ebj)L]~ͼYkjWv2EQT'wpܷGw \(Q' m]3U鰩[R̶$8cR}Gw_y񊢬?4KS X},Re֡ +<|Gw/b)sEjh1!u9=0A (L3G)g¹e,Iu白tNt96g9YtH#u]_ b)Zr.&+y_X|ԋ2*ꇟq`_ }հLWJ]t;xnk_kT{e}_Ä(</jh0{/i>|ww-Z1z43ΨfyztlImHfC'Иi xm^ޒ%O2k0W۳?_0Ƿ- xgo-7vfF8X]p&?ԽN1$nH΢jn.RaԺ=$DsN4kZkMsZh}"ZvV:c t:ax#Bu bZXUxgn7tm~~>-ͷF'jjEq?@ΰ=iĮ+1R&7dQ&r2RR4';D WtRܨ\nF3a'Z6en{N;?ڒyݫ< xw%.LY{>}GȐ=U[1{#!|s~|ߎ-"vuQkN ix}z v>H"NAޜd}Q`1)Uh4Ndș*DLQap,q'z.n|Y†u "Hcc|%Am6=" fӰ˦*.KQ[ioP݁"cKm/;Tsh%WsE Mnjo1~wP|.i7ZQfO-*Kjr`K |xf3o"V۸@G0pY͹O4@-2c<4U˿yN{3t[Xn9TȦ&ƒ66OZIBn5pe4]G~n`qov=o"}픪:gYxY!NѝeF lrOeȾLKL&Y@*||a FE|hWd"EytIK`tvכ9B(\44m?hZJBE(2>3@voyp8~!_)P xrw8\\8:z D|u#t޵lvcǓו +z䇫7ww޸Gcu#^WhWW\Ѳp%՛'w! oU/y߸z P1 ˁoK^cP9G]\X+o,W ּL5of8kFkҒl]>} _ZO\#[5Zo~ƭkZ5g܈u CT+γ@ୗkOM(c[,.]_Dv6|JlOʼz-ڏM//L_R7V"Fh5 Oڷ宊K -%c֖?0ό[вH#vvԿ k]R{, o[cKڜgYH: x %ILvqn7iI ڒ H$IepEasN( I(")]x`,-Ht:ʹnQviZ[[ۙ.gI\.WYY|pXD#{رFQlV+R4NݽiŽk&kozm[/oeUKv</B}>*oޤ93Z͞;`v5rbޥ&6{n ܵ6/Zٷwս6`7Vu ˷)E?=6} w<0@y&kb¡[tMoLpwH_W>sǤ>UӾ6mc=t3.iǟ|{O߽l G׼X'޾k'aNoB9zGOM^OGgwjذj;"e?e )M'$6 ̵iɾrۄ,anw@v07%פ{r}-= ,VIwNkVĜsv)/9ną&u]Q Zup:EEE!(҄-SUb3 5G˿Ii#Y `|O`- -(PhIY]ә,=O,#"<]XRvu@GL]G"'^}]a/}膏nN{/q!oU8 4@G!5;Pq7^%ʇgQx,l@$MK \lڃf =-O(tdX`lH%7NŢ]/P=]*8}e MmGEvd?{ol[k|0"[cieV10;X9@rӿnCrт캱T)m]bs-vU<%%۩e}; Y lmm#V4Rx~syR.BzcK1ɸc[ QPP0`S$I,S4z+ $. @mUt6QD?wKZ]Y`/ uդuK q MMC-Ǯaƛ/?nP5+FLFՒWYz/;);3Gʉ4)+xcc[m _1a 5hЩsVv!+|qxV=-ㄷw>6'^[}˄Meޔ92Vt^znqi)_ IDAT.;֨./; L'NL3ʃO0"_3暓C9J/],HT䐃9'zt ( cLHhAg!B! D'Cr _oaa!ׯЖ/76ip_+.nŵ@-/ >=o 7gq/5ql&Ѵa7^ bwTl3ܷo׈'v˧3 1-O9gSR(#|k_uZkn(_>sLrK>>,#UɍFW5ڹr/ɀgŔ(߽[UuF u4\4g{t7߁]',GWoi\cٳ~˾_-`w `jWnH;W;]YkBZ{DJO禁tgjj=@QRLy~.c}ӵcE@!Hljr }%E/B0񑴛JUUI,bSFb)b]u>kVhK 5w4tׯqߚ}?9poΊ(AK_.7/ڿhb":[LQEtYUmݑ͜jUUX-Wv>eMsZU7b^WW/}2e}l´ =0[cָ9*hwǓfKy'Q~ׯ4܃q{nP9r؏kͼx͛0:WюC/'!7n}~a-/E6{WWU9ڳo'UH+1~ ~ܸ{'׉YX$95Ssfܖa/ZvE*7oQ$Ii^cʺv Z: -1poQ(![3DHgiۿ5 w"WQy:`)%./$"b|j_ġrJ@-Ҹʄ#MlhݨO7꒝i-34X9i9Y>n)@_ҵBK2sNUTn46T28NXOU%'M aG{[[{RByna&=̡'kkۋWpckIgL y wrg;+ŝD]‘9de\JfmoSAy/ZYd`)^e} ZvZԲwA[hNx3[ZA Q2")/yc`P4znwoqc8jq|?/__|PΘD8j)uHĉ1diD cėo|}fúϨw xzrg'swլ3!u3t',gj]:0ٞ#,$Cg.V7Ӂ²$g9٧fV, Q )Q/c0.mD#\kHb1q0 qXDDm5o5!K q4Gwz'^F-!L2YL" ¦hYd&Ƅn_{eJnVp}qGm9u9#yH5]3 ]q9 }aDpr&nCc U 8WGBX-TUp{\9kW\qhP  7^sjp c^5&KO9m'G=Y"%(c.9[M]G |˲P7kH$8Oes&l]u^X?z՛dk\gs"%Qy4l)9  cf0T@4fcNJlj8'$ZO(=/tTK s=NuR,Rrt(g< d4 2\@)r$΅;:[CJXs#'nWF^,[uEO`c?ywnöӑH{KosۄXBaŲ# IoJ3r"^.Io-(46]:PŬb9ya#U EF@7( ܸ7D~ `?2 : g|%$'u`0HeɲiZagzF-vCu>b=\܉2+&VDgm0yv,h.Nu):Clxܳ Y:PXǎJNp:g}~Ȃa痸<EdIl7޿; ׻=k߆Qng7:y6tzulsݔnZBfU W3 5 RaBbWDs-SvA^3kfQ uͦiZSSS4R\.E*a/~A6vX,B ~II1??p\P(By80õ,qi,Kh-{eaYvQO{`n7š* > s{E`9,эvԖڧJ۵N6i5#%-ffV3mc RbK-s+_7f^3xXOS$ɊeIb7;jIlb;>Ww-dZ'%%G-_-K&~3) <,)I %Lc}MB,KQܐfs șÀXo*rkft,\ͦ(@{#G t:t]<9ʞ ݜu L;4#҉M[6nt̵,eH%!5IL nMW\\ߜh ^O: $ "ӏ93LPPo,ߌ9;k6j17pм$XTu.+.R4jtH2@?h+ym< a![&h~JOg[(|Mz>^wZ\ H&qq9$^=1I̟'gc'MSGX̱\cIkMzxW QɖLJd39la Dvޱ)sK11.PS^)R'ա 잔IeA:[g1m3ѥ̓Iq+* n͍]pCSIqoM2;R[H.iSgLzvϹl5Qͩ:x\SK=Ts '=6ʋq~6anCˌ1]窪::k|{ L{G4onwɲi1YvMŻƓOvnK}u<-7H4BOЏx4ZV $yB.G3|@#D_8XV# X,FƈiZ,#)`.-4Hxm3)mBA_N  ]AAZS @P;YM<5e)̍]&܄yE\3&eY@`< 71p]:RcL%'HzHL%>W#AR0 y'a/.gYبGHBļ NGs8h$8<ֿ;]~]QDTZƒJ{\luMQ܊ƤnE RI%IR[W_} Qk!nŪ7dtD$)4M>t!V,<2}5 LBB) mkkrZ66l0\sF!YMUUYyeo477766r;tcv̱\]P^ IDst]=(};V13׉Yf i.gw|F_%*݂)Ksf CgH3 fr ōt,/ݜpK=҈/Nkz_W8g]Qו#A4 v'Aaqѐ_htࡌ1 LmN=>6pע|xa2t0B*m=cBήe*c6IR/4~apx<ƘÑ}Vx58dKXCWuB Q"FZ]CP$QxG7J+(/JF gS (JmmY@fq ,Ϸӿah-"H$ijjeFeeP M~pKKKkk>43:CXSa{bb%~R!,Sґ we"%k%8ō::\Vgo]Ÿ)ӧ=&/On}%M<xHY T:t/3c  xOGS|`2x , Vj`.8Ф߷۲wuEĎBrh HKd +ze4bq01.뺮$1p/WKy_|SP|8$զcu_;~-I6ݫ(vιͦʲ(NEqjs]dI1&1&\*yv%㝙P/ҵK#b|͗DsFH[=CipPw$I p)F}p8Duj$AVU1\ί9x7g0 ϴ1>L,CS|{2+sN].:M=Ǹ \lh""<+-2 nyjf04$2&&:+n2ύ ,0'J$C7 H;%}1ݔөO쀋A . l,O10IAv획e(H nsQI`-9fl2l6&+pMz6R_osnG. {6TJ¼fScrs˲+ jZ1 Gfv;^M7t V +ۗp%c) "݅ˣ sUUmkk Hin@t.\.!p8nt:N rb0P(?7x#.rKK$IHݸ){Kok$C]Ͷ[2R UMU ( C)=|6w:%VMxFKWnHoSNno4OK:Ÿ*s~1IYƈx:g>gCas)-3ӌPK(`+i/3$ґAf+nhK$cy]aLa<tّ_Y}mW1RAэD qp!"0lQUu5F#jǢj8~kq#?Bya-]dY3&EAl$ɒ$38cF}3+Ʃ3~ַۭ.hfN t]x<$ABrf3W1dk-P5<;hu]ooo?}ty<N7oL eYDm$IsbR#,((fZg]4РiI $4p7Zκ!ѧS.5z83+Ȣ7&mD * tyiUwa6%MS*''%7!K`n]WniHvqZ tZąnJzj 攠Cg8d1' +GQw^6fp0YRvXd1n.:S6;ܪ M֬=5mݷB-u=)ΕFʲբLb]cWUo):[Qe֎+;:V¹wM DDG'ܣaL1( Ɗ$661nĚϿ%%az {$p*]w,U \{xU)rG/=}aA^oRl6Eqrw61y;6ݽǛoJ-7<_4w[fhp M+VBwl0pD"hH]r~C*=c{޶14{ۗ4Oz#~|%ADiL`äO]wmvs^<7XqSIy Y7=y;VqM~c&;q>=l Fzɻ_} 8IQ|mX#w8lȇ cUOzثWL8Yz| v 0}ѵhxa9sV2NUoY4mŮ[CE#Mx^Zs̴UKm2f?-"sRϝ:(Gu:pm"WXȗ'% 6F\#*c`捉ܕlɜ;QuJXL00H 6#ڙPp/;8ZNm'd[}[)ۮV|QW*>5yƮǕ%J&.rۗ.wI(-S6_0Oجoyo%LI*_4I|ghO[4oϔ7/=rRTkC?0}Gw?6m$pq+0iӢ>wQՆ5`ONh3I1HۺYEA6//]2~no(+,տ6+//qhM\-cNG0ic]kŠߝ[ j,ٽn`&k[U?{uWor2zmic\_q;vocj߿}X0q7Y8 V^4yo}OM8c,ߎ8(<aq ` S%0VwWh]IR-#G^?7\%c.XbHZUtIlĚ5O^u76 d{A}6:H9#P %MJBnbpR"@#.5ɌId]GyCj||39ۚ>^AX .reetYXXH^ucXkkk04g.BxYr.KvITr5QYeYй-..zQZZZ('A$ Â?}uB+ a% dB5H B0@,EfSO왿 s.]6~'v>q_(ٳ~ vϮ:X[@]3m~`/3fZmK ȈphykM+ֵK;U_yԈ Iw3=mXa4-6\9o'jvPWwdԾ\5ofQç}QM>1oLŔ^$tlTbx|c{5ZuNaC ?ť]hjK|x3+q/ ַ1Bj[RbĜ3LG߶ڟ琮Z Si IjKKK[[X Yt:I5KF\f&.* #|1%%%Ҩ i\q ki2}5,E&KFCpdB#A`[NPq 8phGQǓWi&T'_5/WZsJ+?{G,ٺoIWW^oZt8cEQƌޜ94Xn-y%(IYHGv#f?{b|hZq( 7}O޺abeόi✟?9ss?Yscn2,m0krtVa=y +^hbs[]1o [jzx(ٳnLl]9gB1&8>>W5ܱbs[M7}Ֆ9@?Tbۂ*df1cYg+XÍ/u6?=0_W .C5.q6]P8؋>s|ߟһK=xLآ)HBDFG*1֣gӮ@âlGVnX9g}` j EǨם[>xM._㿧d#`zVǭGk8Hԓ'rzpMkmzR^o'LS_ X1?xX0o+J}l*m9`un{ ule}}m^\Xof W{pu ^g-!@Vx;1{&)ֈ5?!c(P[(X#9.% Pe˞}Eڴ3.w y.[[.,ks$JDo 1pˆ!LlnGɺOŐ;lR14R[9d&uR3"#8PJOY+ i`T(ut$ 9 #dHa™ono&]T{Ͻ_Fdc#qަ`}^J555wttر5HbpT?,fT¬ YHw9|%%%JJKK 4hР@ dJ޾o߾{߿_QD<FX * \NgII˗lYMM je\.r)78@0 @)y4 jT;::Tb3t#FԔqƝtIsdIuċY W߯r[^5u1 o W]uُ{}$b13*tF<=N g DkoX~V~= dkߌ {Őp:;S^sGm#X]mn|nᢑd& ]P$a<~_y~38d|KtPՋsX'sߘ6?9vn|r ׬znt Yϟc3bʏgL]jYTG˗>ஞ?M;`9ߧ:oy= sZ+R5klΝ1&)H"ILi"C$ tQEm]֨~(Lxxݷ'W0sθCurR紐j&pq/=9~F7~BDxqr_|uoD,GZz-\>~Z}}S<<v?޸>jRd8k|uaMOLjւ%+-SE]3ܚ!Ǝ?s}gkwj_'Z=)7~ٓ箮ɏ-X/4~u.Xջ=A3];Ͼ#׌r߹^}uqK_@΁i˦.\d[&UasRuuQĊxQV5 X}_bwp5>ȧ_8axUrN||81淶k(zYAKVvVc$;{䚿|]1yM}Xֿ_y1=O$U_~w>}QQrClcb3bl"l\2 eȉGy`ؗ }ө`X^zɿ޴X%_\"nkLLM`e{+K K]Ꭰ1(.]5Q<<2MY2d!/*bjRZX`Wuy KKK:tВ#xx]95 w=vGs +K/+d1×ݹX\sս57'}mwv5߿ظޫƾ|(W8`bݑçOw67oo\kn:4M;[^W7h: <w&O}V_SWeO]74577}4:`/Voh\hݜUD;N3e}gs_OL_2 p*+*vMh9Ҷq当~9U:Kn}Mh,RKDDjfƶW? XnJfꩯoL^c,xՄ X߯ "+osmH~D?gmM7lD#;6xanڅ? 7lxÝoذ~}c;bݼ[l_1|>xJeƫv`BM|m[ ]whm]NNVʑk]%iizop\Y_o7;*>nx`bɦWo?7|OJasm +V<>+7+#.Uk7.W]=A.Q+TNܚ"(zt]@q͚ю=opD5#E*]/ʦ1zH)7xv~ wv w+8џ6lX߸#l*| מ}1uG5·DD#g|g0G;_o⦺Il~EU筸OϞlcKeמf W7lD#+;i},;["'?y~쌱خ1mk6lkD[w#f2\r`-?]-ٱuTh|}kY:ܚ67QNR/hX0Gt8IdY,K9-M3i;ry}z /.ߪ̸* }q׷Kjc&DxP '=ϛr8 4CMK3.\kS_sʙ&\^]ܡq !5a nn &9 Ƭ !q|l DոKkclKJQs꧗y& Hg3JRwy0K_L|?ɭC p8fc3ipɤ+D#zgJZR%97yL鴓42(ON÷VnI8L :~rV1|@t-^nQ+j4@ P^^*1 C `UkbyyjT*U YĀ:K&hTEup:=kDRJ{0~([w=gҴS#.ڇo7yu#-.[r uVaէت1cN} ?w} 1cGMO-7_;b߭*YtD> {/Iź ɺg;#x!NPzw?whF{2,j8?~~k?8鶩i {ugݿ;Gݸz}#]DpЭGfqҼuRJƶLݔx>'kS 3xoqBh:q)pĸ2rsw??豙sox`эw^̬'筚kFզ.|l-3:7,y=/ġl&y3gհdڌzUKq_:9n]/B[ZGBlO- s8e+|9=q dܴsnwp]nMCs/JDwy>Wg]^_&@SWWYW1@'_33ؾn\Oscd`N[ܲ;8:}RnOݒ5;nd]s/0ѩs^i3?͢K6/J\3?U;zVU˦޴ 5]ቍ?YO5 0ԙ B~̶+urzF{|ox&Q`!^U‡X1fȤǮ4͌ \Ң<12egL0B37%g` B闞zo{ iviVapk\0.$$)L5C+@:YR2KidnkۙՌeC:Ū-eڎUV|t$L2_PCnwuB Lf MSV-%Q8k3MsYx,N \\@%e7r0GJHVFSNA p ڗ e">(`P<2VTTXnz.]JDa~'O6ʸ [DoSt8~BVb0 ?* P8t:k׮X,fEZP-Q+577ٳGӴѣG7y*1rছbXyy1c*++~uݪMFcg(tZ^ktA]׭ɉNJV1z-[.첼.:"0"--4聊2)/_Z#M\ޞon4skDH$ +Nk'R4ҝG:LD"IzV[nO4j>Hϲ't\.p:}N[ѷ3M0͌a-P МN+9 Hkն?WI'&/?npgÉ:?! #cw7W8ڶ&\Hh4uSf9{(ta{+Qo4Y`VIԦi[VV0 7vADMZϞUb,HPAxE|| d[Vui ^Ve JJ).(әjBn([IuU +B1xt`\ފgZF‹~z}4כ}=q\pY4o(>s4w/Tp8t)R6P4_Zn良[4Zo{z.gXwK^zM{BM{o ~7EURQUP_oY/?TsayJԔ*_#Y`S\*,vpy4c͉ǹrφ6W}|oKա[VEs_[Iy-z@OW&ʫ5w}9#XV}ܤ-{%|eUE_ްOu/?YnDVd8OT:St'N;D*5ɉ +7oJi$1əqbK)L4q6 {E{XN\s75!-˙0# r"2vHHQ*L&qct` z:}Hn_P Pr=hOEi#);t4RqMANW8uӝvw:-hY%fӆJЩ娀Y$~w#[~{K|mWGEkEѺ҅/,f D¯ATX4͎˭r^Vr9Xa#yc]}>_0T$aeK2[ `,nvn&X,> oV OzZnܩX.smAdQSA nh޾/lG!/+ȩ{#F?b흾>k{.bڈ;^Wԥ_ *׶=͇y?ȇtyY=>7-d8trGƷEEI{pvl!]bMɖ̢r팯eL*o<W.H~M2 yhY_ )YH[Ÿ͔Wќ}rnw 0MSY Bh*9&I2F IDAT7QD"L*|llqVp(c0?`+J~eGjxᮢPȀg]((h)ĸ sw:ͦ(Ȣ?NkjŸjW"OL\|i{5_?fmO?H:BʗejR.HB R  pricO_|n@=b.D7I4oypNOs1,R^))I NLcjˤ Uc.cٝ~EasqԸ |Fd09ȦlQcmT*ݽ?s )ŦNhxa{GmII81Ď` ~iΌtn^"kCZ 2OvW)'By;lImSQZgDpe!."E?5rRSH&輛.AS/Fp3Dؗ)?x _/=.l.vJ 1aTvC1mmm-++P}%ݾVrYv,0a* PQaPLJk7]׭xܝ̮Uv{T*:(KTH׊f NfpI[{ydavg Y*վZQl[loC?Lan0nѧ]ݿ]k#YA>R(CO81BJyɲ1( ksamk{I#vIхT # tKs]E3$ s `ƈF3{Us_ƑdSO9\ɸ nD D$I&Z0d*c 0G4ӉD-o*@\wBܶOd4TT"I/ƺ]<<ŧ])AGYˤFfdX20ι$I4 )] 2"ag>J$pá0x0 h~]i8n|c ZPeIr麞N!("ra4uݧz44mԨQ>OZ"DdA4vY \.W \eʩR%P"W XR3 CUUUfE{RNjBA b ¾ѪV)~,N[ܒQo dW bs=V3Ō<+TzD}xt1j}^@'ySs{5=ȌcbH*b L0(ym{u(rr,s%+T'1 ӄlzѵ G'rWw )`]8e`1N ƈи0%#5& E4 O>-;JpCLR0s)Lr&$BCʊ XS;}&4p474\^N:Klpu>h=}cc[X$O9`" ^A#ފ d`tA:?4;7[R4ZKZOLgSJLt}'3E BH# yD1q8(pkh~fwi]El?Ufu 3GP!r  Hյo߾J˥,)7nwSTVҘ/-U/(X9q' qƘJfV(gMAsb'GY߁1Vt]濦oޱc@pޮ/ 'yf-2Ȃ vgN5>h `6сR.@N׷gc˾B,1,=[]wX|{h]ɔ"&Q`9s{EĐaݚ;.HM[8e\qs08R291``1$5&w\H =w['iv CCn0F,B $m SJkEEQ70? 9y 'Â'^ [.aaԇĬڨd% ah>RM㐬F#<6TP._̰Cet.#Lt},!ӤGP! A (pb49-xvQG=.lhpR9ؿCM&سgBXJx`EKoj,wW}*iApX: ãNmy,GIlv<6څVNg8裏7</яD4bnvitU-hn!i r7S=:ݣHeGd~c*e9. " '|H GE6R(Ls˽1#^g\e]]{\]Z<1ӆA;#ޖ?;p9YV-O$s`WiicP),8#$c9g^C_{ƭ:@.Ca JT(Af5@IJ-b bN2;$`zRx\˥[*[r`NW>J#Zz@J"@R2? `y0N^8LͤA2\gn')3jouǧBK6adG HLˎ 0Al-[hcZ\*BZ9nsNKKK uX,tvvTTT躞'{øing7-o6st~J`R6v&A&jw8t[ɮ.e-TTTX1׬NjlٿUYN(Ƶ簟3/Վ# nwB\Q3|D(S:)4pa\N벂?A)\THAFl,e sv㛮}2aI H`4~M95C<a= \@hBJpN`NT3)'dcWج p: 81&\ IIn2N!q'"F%14ag~ = x~7wc<n2]8qt47;~e (ӌ ]iPhNzX.ML&L Kc^z'1qq\Ԓ~$0O0'#7 So%1+CYA p8 %!à1nA$~/c8jn=zAǎ9r:] !RR*i=蘅J=τ ٳi&0\.W"ضmN;pP.<Xqt&øTڨBQ.V;8;\-iKN\.I'wNHD|L&Qr &vߞoxD 0?z4%iH2q\?MIxr?sƷ~sTBv+* kEtdС۶mb*`m2lmmݿAT0Bk:wU Ӱ+fw̦k_+dYАz+(t.,++SRp:PRy-}ߢhl _n*@KK 4,SEXZ#[=_?l;*s[~"xAŕG}ֻ>Oyxz{<+ȜE^}9LJQ^/ω%10&KS~2IQퟪL&<ht?pr.#K9#z.6|}b9i]&qΈeq"H.)+^lv8ɘə)37~8Ϩ=^Bqe˒AŮ b2A0MuIj% IBBrn iRW 3#]d2r(R#O#PZlW9CtבwVی >-I"r0͠!ݦ) iMmLs<^I@>0b F,7jG$Z)g?(ҵpjU(&tQn}?LCb1ef$ٻwѣձV,[Ҥc;,7AT >ޔd $vU3t]W|A*nYYBZS5x<>gV{XElxOeVS)gUE3 6o_KaWh2Sŝ/׎(ݖZ׿[Ai Smf$~Gʈɤ0>H9Y* k4"3c?_ufRF&g 0@(W!i I\"f^g\vO vey@$L2$HbA .cĈ [0Wҋэjnh%pq5 Oʸw_<}nUv[_hyϯNat~!U6>/7L ~AAihi]@xnU8QŰ8()8Dcg5DI ⨌"4A0h/Qʡ ۭk?Hqs aU"UUUc)m2liiQ-Z@b;|ΐcU{a$c8~tlA,kM V&~2dpx^%p\̓%H{c~{WXwC^->x+/a)4|d~L}nc( ȃzz $><׬OLDR΃DWvyPBrHڅZ޼>L$c$$zK0Ʉ)! *8IsU~V?m]bq;vBx1 fQfD M02%h0..O-ծ+6;>*WD{M;JYƚ-X!.e;P+I+LA8{1 p H.`0F alu)(K @(]le[?fCiKf] Iٹd2J,paGGGGGVȜ癵O6*A5BvH6_H߅7YMp8T41ۭcU`i,qJ{2zB;:q싋Ѷ}>cB/ncm,ehY_\KdG: }1>iߝ834 .BIBFWn44!`&#bĸb$nD .`JK 6a)KBZbXi]B'R'⒄dIZAUmQO x~CxIk]W$K9@M1)߶tYr4N ^JvGSbCJN8)2/M@!]ځ@?g 2vʯ8֐.,UVAEJ6h}')4?uhzXpU?L?˔[ݢG:Q ݣr'G4{̡VVt`%:e9#4Ѩ\Jdh4i#)AѺ@ş%K$.&//J.;ГW]13H~Y_[}̨vK1،r-#UɈa Qvtщ@1נPYN9K¥sӇT-4ɤeY~?^im=zt߾}H$ Ʉ+b* .ӅtsfKfMH%ȃBXN'$ u]gBrs?'?u7BDɖ1|}7ԑA|k9'\upQ y3)˼C2[6 ]W|.q(N'}.V?:tr9;^fiLTDEfF{/,I<ֿ,r7H kx7J/hBwfXLQ,32P@\`cL 佽s2{ʹ-%]܄`0(1+l;Ke²ߒU0#===m(+ׄ.Y!rݦAߏ3>y]uEqT62d ߇94PޅcZ@hB^p@P'bPTDdґ^gAAAAQQQ" Db2s1xb,ppS IDATvx ,Drh) \KO{)Y&Ȭ+#92stq b_Ky9|_L|5w~m`dh}b~\ѐ䬔 '#>m<;2z t>G`q>HP)"<8W_-0;9dsƹ J{M n̐ 6)6Km[)XLz{ w[W&|>y|&hW`ɭ)Ql98X=<ߞ}G`8tt+ύ"ÈDwKS0a@q B. k ϛTDȤ1f!'ǓJȽ8\%YVVv9[dC0=z71bDII>~AݻdG³+r90]fvW TJ(ۙzP%OZmӅ|M>'X&/Ϯ82vA-HcD@@nΙbg]%c| ]9-HbL?~6BPKm \y/̥{GB BG̣>BnN_UT0RҪ &8 2-&l)gÓ mnÆ͠mp :L ;/8Q2{ЙDžbaE'|d2)3(ߙfD1 m^8 *v|sT1`&Bu9^*dd2 ]SRd'[W!Q(U$Ӆ'\+lG^` r•I4Vkwk)6gVo״O#'X TnҴ>k)u=vӱ`nF+G{W}Li~D˫4IwQҷ'h"E-w[_9^3ӄWL 5W-|f+gK( fsYF*B{%8!>aԌ;|ڊx͝^ PlMX6 NN S>Y $Nꂃ`Y2aY0j!|< asi_Ѽc>Ow=PLb=heM \y"k]h~~M* |d .h0=`2vG(phtp"kS ġ4AdzimRaÆ1WO1x1]S>!+HYtt rtKe$9Fb&i_-'ڱlPL{)t ?^4z`O|GK׽+oLG#-}|%GJ}B.ej0= >/K -EW>,יsdC@QX۞^aݷ 4 2pέ/W_ Sl~-Ϋ zzW@9_ NjK\`.h{x%n.r\ߓnmtd`)0.,д co+ &lxZo ̆m0 є5ajL=Lӷ`!-p ӀEZ)X,<0W ëd2)[O'r0ْI~)2'PqM)&1-E*xӱ(׎G:kb_`8lAU&hJ`Z. 8Ր.2/ M dT6#GD"ԑܻwogg'fBfOWѺWa2 EH.#4/T&GhO*ً+Aʟ](uͻݟuŢnwٰ+okWՐ>I4P쵆{R`KzI?Ps&YinK$LU✏]- ~7wbF1xFp@ŁkڛC}M|Ɔi}GPxh݃k̜_\4bsn ($wh?Yy;yvpWN+WI6u[#}!6+9pX}cH`sKmC`Ȫ04)iR0IC7J ksexI,P>p\c9WϢQ:tUgov(mQb҃1[Ea08 &OTQ( q~݉3i9B@ 08"p`/)k%K+nA"bp8&KxRL2} d5M B===|;s!08!]~ 5l16~O܀dt+稟PN@NZ` xAx<~Cހ],w@ZsL"9ȇ&yN\OFL(NzڟoMĮ̹݆wiMi֯' clf ͹}U}UmE6@`?orٲzYAHqN߳ڔUy5./h C''v~`&fVG_6(fnUW>y|\#mہ@0 Q6$уT ssq-%I&E8Oa `.61tmJ Cucf3>8rV${:vAƘ E,k$W xjIf2xpӛM~ WcPp6tI:wm<*^(ށ );%@90 -'@있S ~=^80 O"SYYY^^`RuF(KfLWFrme9m`e^7ӥ,]Lv/Rģ~`no?=/q |~+1v# 1?ҾdÖ+b ,n̚|Prr 0+>ؒxoC!}QՌϔ>a˖Y6&Ml{fQEu5ee˃stݖOͯ[9D0~r;og" (o^<􇔀t#×tOյ7Xy,{Ӥo[ůXbĊQ/`҇n")%^X|[P߲u,I ٰvƢο}aM ;MպuUUv\bM :K*.~΍_;}KZd~#7>V 1,įqDŽ󦺿@oϿkqƷ[SBt#;>͛䁱MyֺM7=Wθ(^ 3_s5SlۑV9nٺjE/ݴuK/V87^:wVda _}f>/n޵{;GV_)LǻX@~墹W->C khFEaUKV䁅;6.].l{U˖'?eO9l28=ngJVox?i[]Y@߾Xiu+?xȈ+C+J@i9b\'@: .|,{zLJ;\GF!0H3m[KD`-d"]űަ8^3q)GoTbK$'sI\ Wևȁ]$.@mߋ@\A$RRi|]nc 4QE^O&>qÈͣE!LjoaG1SA(*h,\r^Qwp(TJ@miiiUUձcǎ=J >M+---,,5Z2 ^LEД uGkv ٌP%G)c0ejV(3RpKvBSF.4lgݎS8~z^MƮ[zgMr={;xzӇso.ɖ_AG3O-1oS//H\G6ގّHK<< ՆoIV^ⅻX~4cht\ }QГ?.Toڽq>teUo+V_lo}3ά炯#6PeUzf=r(ι'w{d'_=|s59TĬvrxrdG6^[n TP<γKj+/oˊ.εM_"L]j$5/cS왗37(@lcCO09]>w\V!+6whƷ?j[gw,=P@kz z#zxxiNh@b W=ZKgר^Ԧvf|nk9pc"}7ۇT/t?|[gPd6#ӺtL_5[c.]mmpuMvs/Wx3"Hb/./.~vɺY9̜s~g 7LDp߇6=,cEl w?}]XY̹lv9f_`n?+I6(sWv$t%c"n #%bWreZ ;iz@L1gdYV2~m%Q@6td:^p֔9SdiD Q~ 2 A[OR͐ltȽ[9'zWV>{4)a(ЙVi*0('Ij`0ꙍᄐ,蜜ne7DQty.zi~0#r=1C~3svOlyz/`y ܼK.sQg*em {P0%y 7 *j[1B3>pɓ6E?>?) ā("'pr| O EYa3` o7v&;7{g'pn Ccb}0 UWuEiΧYK,YRA_Y[ZY j|f;?nq7<0kna_ H)YFI@hHABXFAY RQft8{4C͒_GtrLoi{T p>;_pſ_uN6hOTZ06<)gl$K\p.}|[mWn3|魩#ǬGW<v7 +&c3C4׌ lc;jV?w^|;wu_=!^Jo k 9ʱ0 ۩6UJ ĉ;Иqmaz V?L 8E2M҂X 08tXaKPl9: 4 +We bJ~={p#7YYY9{7VF?SU=cڸ-#^>OχH'l 8Ѓh;*$&#h pr"]\@DBЙXAAbdC?ѣBL$#G466N4 & .+tgrI׊GБY[*MC*%29@YUUepWNsv.$K-1XLqc Ttv26=󋻗~cߜM _V㑠*KF-6m{M5tjbKgac]]Vwt [[@M۞elENRqE=~[(q糯})$9~Ÿx} bE{w)\!%G.(6hgmdp| /.Ѻ4ѣq˘$J.qV0QrѠڰU(@ l0LGΚh6 0Fs5<΀m[8ӊ<ϙgY`i=hy޾.UXъg]4z>MFDATHj@F@AmTEWlw.`PabLͻ=M=gcSK{![w v IDATH$t]'rx&J=^oaaaEEE8V7%^/Եָ%+vMd!;x&b|\(W^|}%IDJz#HqzCԎb*Rcp.^nS wmbo>pck755sOor"}a-5WYnk㡦WY̺(?Sh}X9f[7}b{wo~7ct$R^^^Djm>eXw T_)rَ^\x45ھ駷d-75QC5hj# u몋Sw˺]|߻]ϞuKgUԠ[|n^u@=+V^ֆ>Fsn |zkCsGP!`_ݗ>'PC#{eӖ7֍@ہOZ@f[ÝQ}[P>Ѳg/߾Ps*Zvͫ^}СC>rF=sn]A^xQ_v{[ݻuw|+,櫋nz+0Muky[?i0<Ůkk'|ȰA!0.w96{f3"ʤ1w>#[Ȁ䩇 iCB  <>x' UKKK$Wۉlrdj12TmGD"%%%#G,(( xAgz/lrQggL4QD!C-B ਂ&F_ GJ=vho^4/OHvcB<1vJ F߹sй>吮ܾ pN"GvvIVEMBy&*WXuѣG>Fe eLom2dp_`Dv07qr j}9Un6-4k-˽;:0$ϥ壧OPu_<{=[̪[t gνq8pp^ŤRyg:̓~Vή3a55oLϹih筪oXQq _ΦDF%c?ύwg޹ty3 e0>̔+$Ѱ>~x[WM0aKoݲ_f̘7.u]W-x`nḿA+bg~}zj}3׷Wc&]Sp\ݲ]=~ot}sG.'"~z#C?ap?/6W b zѬC*iuT8wsY\ӯJ/̻BT\/y:* tJ&E[  $d)}zүUN8抁A a9pWr" mD2fź'*m{OYg5!R kg >2  .8Xq :INl܅U̘̻^o܇3O*w[=mʡzYK kQ4tE]'J k8SMQgo|0 p`ҤIÇ/+++//x%p!#iK0D"q嗯~P(T^^^\\\PP ~ę6q(MB R'qɮsos0撆pñ;f={n־*=g7~yÀ*%ё4E"xD4i ’q+c@}&sLy|ptAeXgk rpcF;z DS7/t"0-|r4? X d,j!QPgɘiF R ï;zYaW+j+i~թPf>k'DGOZA$O i\]揉Q.a (NX;@i޹^^zp:s,`5韡kjj< %j.=Q2HYt?<a&`&`$``4q\)D!m+Lq ;@ ֮LNL8R)a4¬wf,f|T+*8z02o~?oPf[ lIϹx!ː A G _bX:[t;bsx2}51l*dž"/ 膙@[ xhgY2+jLÝXs\SNi=]Yx.?"t5MK$X,AR`x<>sL&EZ^^>jԨD"CGmjj*..|:$4IFI 2.bq v&2%:Mg3y\Пsi]MeFj.^1&g&8A YD0:S<niOHsE\2>T8LSΘ+Hk/ʍ" %( ",c-ǬG/μ_Q> &Gsu7xO  OleB*W ܡ{@xW^K rIױ:W;s}I ϼbBɹJ.Xu2Ĕ|vC'׻:,qXQ P!]^LX [qq>h>h3gvfpT'`4}ɓ'[4mv!b/(ցQHV Y݃m lH"v)TCiЧ8&:@]0q-+њJ:&~e[<"mYd%}u1B A+LN*0'knK_OC>Hf/\bGWl$3p̐,r .mh[d>,1F P[`m(|6i$[d;(iǖ&#B lO%Gj &2(fM@p^Fdi7LQzwz ą4r 3vU/;s`z5UM=]}f49Q3ؓ=/1kC F و pq(@ '+ ҕET@t.*vuub1یle%WF$/kpΣ}:;;KKK =,[ dd9cp,ӎk*iR p!]\HlE>uw Y=a j.xA貋"BW0o]OZq%;*@$~h 0y?r(e\dm5ahwd?Am˔}1} %4m+N+Em:(QvzdWAK1ܑʜxd@v֕T/>PNuXXD<3Tw?#qJ[^ؓA8]IJ.UQ$,tњ@sjkWNWhڨڔCfu=>h BA *@@"3b&,$5t8ՁXm+[Qy u-wC|*b&yb>/|>` ZW]BEEEdäxa'1 ACPy,3YW 3b(fVʒKv7\\~88XkҲLu0`^V6tlG][>ÚV̧UYØXQE( a1q-QDu6<>ן,+E} *!f4tˆ%pa09r$m&0D"o>¦P#AP02g,S)Zv`WhOܰ rE!#cV23V!OTu(+X0e"6ׅ}OssO2hdy2`ϩ|0:avr\ҵ$·_?1uCd\I&N18$֥tolpPcq-e2006+@|axU0@ž v0Ti+Y*YOLK8lw4t*jP0R%LqsONKRa ,ł0pպ1}69Nr02aCv ]ka)^>:V U61?fBO"mm{?^tɼe~U9EL\'IM>#Pb%LǍG $j4mll4mԨQJaAXa0Md2 a \UVuuQXfq\2\T{]-"A-ɆpNn0$Uv%D ;+&ûl|2J`wgvFC~@^z_KVw2 +2k\|qyMQjck,|nI%h5K}Yτ >]-M&|J4 _rY|r =$艣+}uX NCCݪCUȩtǘ%gY%Ҧb@ xm񦦦H$*v9D yn7Dyp8 I)̣ 1$ٶMRy!)U]b(Rdz?F>4 K-P,x\WG 71JOD &ɞn:.L[5{HG Èx\uzP( GE 9аtL+՞\*+PbWY Hr^BQ?35v046hڏ׼`0RXXUC}#:Q/'r[ &8P_6.ܚv b^O_Xl48QtC{Gz0>\ag(XPazQ^.RS*d|#F 6^һ ND"AMKU#FD"e(.KE#cv*bh4j#GPؙr3+5PjIII<?x#גh FWa" I#xN+XkFww-**TqT@ aB<Rx|ҾX{B/nrZ{kL6TqBMtOQAGH$8C8>uKF}0qVof'@/> @7 /گDu݉F] [IO-vn0 *<<0nc;c܅8hz3C!CcP|ŹURb]Nz]_°ӡ3W wTw*?(ty,ˊm%/1#g\Ma/^9{;RJ%17LƏu#ڎǰ(ƚ:X(zمm1[>W+O4Y¯4^-C`0(aBub? UUU{4h G]0?{odq ~z( R$!Q@r[jٖF==ݚmwf$[,%Y)H@PT}y͜y@JR@z.y3f~3>0Փ7RӸxTe*8zL%Γ-\)klLUb]# y j2ZK+ϟ?o}+L&wyaCk936z)n0~U$.<mX=.Z%[hI \z$+kNtiO"J-aX8 U*>9J^+3cB7uvh&x⣏>gϞJY@B9 ԴgvfW4b1y6.9}q=7𝚽ZJ%J|KhC Ȅ@Th4f3 )8{(8UV ~嗏8~2 E/=o[RXd`sQWMzUkiVKP7}\ִ2Ҵ 't:m%>X t5̽ /m;IF6/L?vuNcy}.ͩk9TaAؘY[fx卿9QMte.SwEH\.눬`tLkM ]. w}D6 -;vؼy3Ͷ4R"T' \~ >q7d, ٨ x ` V7tQ ˯ݞhZZZ?w\.#TJ"صk{n`.sU 2J4!aDftS=6Q1$r{% HF@2yVl?@'x [k{{{8eɘ%̥vbӡ|(r|G[i2k4_ׯ_w۷X,JÁ1y#0Q5HN>8݂mZ׉@@2e>R V" LA`=tBQsT*=NrJh4״}݇޳g,˵x<0 r)bqqq&0+ljyj n}yD Mw:m喊܊&:p6pG ^U'a&HZ/:,..FQsP͍0FE,IRKcT*>(ņG)#g*7]zUk=44NפQr ] Xd%ʓ52ՃNӄiPQbK#lsC{J$N`WDsnzNFR` : mmtiLRTιT*N*P$rwd/=B(bN^fI(jmDd4FnEjZG6N,TIm]ҮKnTdԐh6[Pㄇ?{n[L!0/] Ykh<̗dљߜ tcNVb~`ӗ@bBcp g(@CQ!U4t` c` L H0pp(A `1Z2,+( xTԋg Pz4ڷ#W"3Ỻف}APY-jqaTe#'n }i\^@iV-- +,s^ZTi,.^zɯ! b5 |hbRTqrc,\u&y1'O\XXjtGq"HVLlZ`ox! Z_Q$P L >*j5ZwaCYxJd7ĈK噙_~9.F{챽{*J2_h!,L$oVVrxPdkޑ07qӵ*j!N"]RHw%OG3ИԾ:_+SSSHd֭J) FmT[j¾֣Zfm6"A?n Xl6'&& 7oVz>DL`>{IK}D܌vӂ{歔lv\90No0OR ph<h6JX,ezR悷12M sFFFIt?:mSn/ôO;Q۸bEI_ KϽk߻bcmؐgNkKFvD M[i˵%qRcSɚ5Tj{, 3n-t ԋ)$_ZC<>iТ< im깾@`=wS<| "F{\-ja v˫-4)<vV՘R0gG|= <x.mh5j"B8PځP tȞW94;jnzҥ~羡ߴ9Ց%C[1>y9;hf=YsFC/lûPޢYxOsf9?܊ mV[ 䧽ܒjn2P&Z+8W>n7*KΗ߈6Mt'RR$y>l1D^WJmٲǏONNJ45͹Fe˖#,%HDSZ+Q&JWcދ9֏k}q6#-t]a j"pڵǏKf^#ݻ.9yfV0 F7"l@l  s!b 8fѥV(V4t:e46xhINwD7.rKx^IA Y._\v144D|-siUl:~|ZV( B"ذaCoo/]jU|тdsXky'x8jvЫM R8n4w]ץB[DlIsVJ%I27E6J+"!SrȖ1"a;kH8)h{@(2H8`ohK[whhhݺuRI=rםqWXcSї;v`8" ېBzkz;k\r3\s6pfSz*6틧; 0ٰR!llDzo7a;P(48b7po;&eypǻ.;:3r2Sų=Mo 7#Kh c,]I ka튿60cZA( cٚl*zj7ѫ^9]mB#łh6PU8!D" C+hv 7(calE*^J?H;vjIp\{2{H2֚nKm[ 6%EvˉGz__.̌]4]lq@يrnrol)ΨnFh.:WkM]T͇t! ?\l?%yhdͅL&7oLΒfWZ)c۷iӦp8Loݖ)Fm=͑WwsoݧCZ-B<_KDy*p5IJ҂M\׋K?~95kNc}(%J ֈb'.)Rh`.cv׈tr˭RW9^.Zy#qJ);Ãj:-0b#$2 9h F/ul~ X,6==y^___6d2DjeB,0al^re~~vRC+ˈc*MAV™TTr\.3ƐуL. 7sZF G (RRUUE0% *JP ٘1a.=\+!$@Ӭ'Ɂ:%1×?֍kWEQZM*Ϗ]:v"pBnvT$njڎC_Prpxu[&E!<.Ԏ;lՄi_:VS*8w#'=anx0x&mFz^yn nk^]kjc_^ֿq`[I\"t.x{^e}И?ɽ=nu{~qbx  'pp][p+H8Yր`((5q0sO>(65< 0{OJl({WXLX t'EE4c곥Ja}zLԫ+LUn..^76Jc<ͺ[l4J<ܓo\kD\0Z\3 }oMR8w缡 R$ɓd]q(VɎ}v2\:Xl'DӝVc#bps2̫/C4?8!zNǰ\` ܌tGjϟ?/VUr`(H$|䮻VR8$#+-:a.uƣΟ"CU1nw(Mn_PE7ݩejb!w3X>c*u$m<<| ر/H)=9fol6K{B6'fY(\B k3r 3mZ9h?]*HiJf\ZIVZȵѹH$蚕JEW;O<*X`P.f,tR)<`.o#s=J[\ST뇇{{{c<2JysIt_7^. IDAT<~ȻnsHq#HHG7n؁V\~t!hcCXG9׉Lg&[7 D-) L:[>ݾrdaE<}\[&v:w܂C 'GㅇO?u7͊t3g4EK)drPKX!X,nݺ__y饗^xᅋ/ZFK/_|ҥ{>ct5N*1GdOαk_HJY|HK̵Z7`ievslb&Nڜ_6|>?99/yii$ h@0-QtdӲG.#XXX bP^ "z &[Kr?72..bN5ipP"Mĸ=vׁ`<(M(`M?]xoݷo8pOTn.m~?< >Fwt$j&*BaiiBkŋb1ƐdYaO@ 3 ;S,;.HAde'2$I'y(JRS0GV.+˂%h"KZ <yoyAy/a"mk}奣#; #mAؚJ:taPD]7>zs((g6t*VCgFpRB'k=O|]x߽F?=} ~簧R _63߅-8#= !FIdඐ,-\BJax_~Zhʧޜ{sWgۂ.8J]:Q`۝FP)7N>hdO_ß~e\*g쭿/0k?g҇Oxl7*M/LF>mO>CBքHG)̌Ͽ24==<2:z'\./>߾9Cr|<;'g4J璙j ܹsS Fpر[nٲ%x~FV&K$mJ'seZ<DB`8EcHYtKKK.\8‚Rfz&lsE-]ڟ<b"yT^`H(gM'Yբ@LLCב{XuCSc 7_51@`ڥu l$w/Fhѩ`7:q/-4>< ERvT*qkwV7+WLLLi޻R b=}!xp\.yV Au^5e͸SV[O8&Y3#u+e#kیg?A\q GjJ,uRi s `2VdP3t|&pS{~.t"]~'h^gi-#]*OwMDQ(c߱}j NhoN@5R?}w=8u 9hԚحYLlWN(bBaf{kZFy-kjӧOIۉH]B9v ERkZIb4ddߟNS7MzCqSO=sAidc%@q%9f_bYdslyg`pLr=S mO8+LNNyŻTɡx#yƣ$Nfb:V%RFP^5ё6sFH`xt۵biբl*r aL:Bwtw撘U1=C%6bߋ?5@(ibAc7~]`+HsDz`-Jb1NmD]-d}eY0HtzGQ4.jp8LadޗowFmp'LiQ]?1&[=k|;vfc*jyQ֎=4M;z/Mݶ\ݟmS$WS;̕cPm_x~c' c޿*ScE,,BóAAHY_۱8t ("\` 7$ DtO87 W(X 1X ilڋ ' <)\8zrs82kl>̕.U|Idz>svdq]T*Q Y5x3ՑNVnViy{~nZk\lVR]kSO}l H9U`{7g%B72SR4_!n4ݵkW*zW^J %ZBk}Bsa"*ȉ3RB ]-9t/+ cڐ C o.~ꩧ&gFG9rƍ?c=y橩)B0L-..R ]ʼc18f?%i][<+yJNz=3m1VPr"يX}jA_'aϧN)KPm7mT SdV0w‘TZZZ$~dSSI-N^^^>?76u%hsGQ>xÉu>T%߉ky!4먗qeP ;&plbgqw6U@POMTN}zjl-`d[O` B;N_*T›a6{pA$E,Ջe)L\9 Ū@o㯒y.Z-;._ R= [-4(0?Wq0zܐSWݱǭ5V٬6UF,//_;u7I_r ]tqQ:GxJ&VdYBdܹ6ogpB\&ց|-//W*۷?FM{Yqe=lC]tOÓ>S\.733399yթ)||͐ ZĖ,2icyN7F:Gi}pU/Fw׏^.\fWѱ10@xf|_CJ\`ϩOba$254/a?p>E^ Pg-O_s eCԅ򰳼0c`#p& KX#sasn H$ FÉH5j}sw/]7Rj ,/%< {Nݏ *^z}8vn7ZZYi4FQ/V+G o׻:HqScR}zӦM۶m۸qMCs&%6}^gwBY*-x |A^-8®a/!+qBann<%'ZA. HdttСCk?c"=< s+JT! LK!WPk0 y% Dp"1~0ƅ YU޺{S 1lLgfw'aq O|j_!:> p@OZ----mݺT*[맢zpf4n2O˲^M/G(i4JAkֆU{%WؿD?~;x&ڈ5J{N{ֳ\֥_>_??0=m|nU4&2P1ij_?wcs?0gB]EhlT/Ws&Փ=? 57P⮊0 ż rmP!R 338qg9gw/ N3t{{^٬zl|uj7RH\]"3KOmR)j?,*b1HlܸҥKgΜag^'&,,,ŹM6B~I0E6/lrq"P+bP EI'"@c Qm?ܕ+W]v%Z9% 鱱Ç?#Z-˵spep]R,6yq}W67n_/T)U$caC,ѥQ%IP*]͵*-Fry$}`Vrw!2~j tA2 q"쬵!G7MeޯJt([e ]@d*g^pT[zR I "D)`pT"p8nT~M`` ْ/{zzȋ\.OMMQo+#](aׂ/SOr],c +Ć[  on*Bz$p<7o> +l嗾7Z2?JEֿymumC^o=Ջ/[R??uW1>߼OF݄.R5q~]֕j$*Z ^ŋxY\>U >hf\%S6*#/~G[UnAKeUk; ($^cFYH@L-jVv;NBW,hhh&2ӳ7奥jJY A#A+BQgqP&Գgwl6bTb*q`Na81KKK[•o nߜ%ЪTBP"fTVe 'Rӑ$沨 Ȼ׏mju޳vC?(4sX};ޏnu[HO$"5RsftϜJ#::qh>IU;h3vvJn{KEҺ7h(׸H-^ EE\k⋸x4E( @%)߱;2vUV[V cgNO#[:sc&fipyo===DRpt#3|~۶m۶m{Ϝ9@k*-Dv+WlذaӦM֭`XHnIȵ8WY*&&&;wZKT%ICH7  \$';#-d2~߽~z>;;K\rf".///..R\ @-$FdQײ̼*]`Fjd¬"oGnKO]+tJ*9`q xgM[H#>9Z 5aF?zH1OL*63 u2 r]ZV*EԒX $tF2'AhGR"a"! )Zs'H.]>e{iZ_;97M$L&Nyff _46WY=\P|\ 31/ǧ{$j(:{InjK'Gzz\=H"X TW&y4_vߡf2FG[sgx=.][&* ?H+?mX&&رٙy!FHC5Z }빮jYkۍJSUͧ.2҅pNjrn<Pbj/6l@&(<#C5??饗fggmm4ħRW>3Z 6  [Os1@snV#;77OPb?eAIi=g-[P%15.tYJXj|ן͛7qDTr ՙ""t2^xqyy,eo!Mµ׃W'?X{CJ7+c]&Jvmn!ZRu*;BY >g;J<t-Ubm:{>? rPevHD"]d3R@©.PzID]t"\ L1!mYerS$HfFLZKT=?|>_csraaavvh/+FC&zv99/[sy4"6#-,r_-u !BWýk<ߛN d;&'/`yX_}6ۃO>\/m˟K IDATo [~}ӊ +J%,,`j Wo P,` Lͦki=n۵RTn6ki^n`|/˅fp׏ZEWTb}B&1M$DnOE&M&'v}sΑ=M5iՂx|)lPxޔݍog>nA%c^J~z{@} oiPB kn[,3ɫ/>{ll$N^&u(,f`KqareկbT"6n~TV%tey{E3g~ X^1x ppJmy冕qv^k6˕vcoγ em!7 % +KZݦ$d's<I ~'N8vرcǤ!SqJR.gffH]NGFFHxɖ$'`@:v wx 'ئh4iF}}}۶m;p؏EJRVyzFi۹\P(PEƸ$W,$@vAHuboHV <ϓ! Mީ{;E`="_ pUDn,}#j_3ǥǩzUS+r `45 ?i_'88"@1D{?ƛ "ѡ8dՉz=...`3k:ܞ̧:CA"HVrf/zV~egHhyƍ))@XC] Cx#P<ϋFNg1~Cz E&h=XYZ|Ni|+om)/Z޽?} ^sv*ԄrL۸X弭=?[X׽-k;o =aA Vs:fۛH$(h(b+'-ZƘ\.ׯ_?< nzwu]۶mi8&LE^˭uԩӧOOLLy$9J_Blb;b&NyZn$ F?={l޼j AVL$4%T*m?}hCoj )n"U]x N;; #'|)ɱˮz D$Qx0er'( z98J]c{ۉ Ec`~FW jQm4}n7}}a,riDdOFx8]6MIٔ`"1u*˹\niiiqqҥKR"˒NWB4P%C@qO=WP$l6H$(e$ZR<"`H@K .%":*ssszҮVDu5^V*=}wk_KEv=]/%*dVD:5gFz$ U y@=G2}*Tz’ibbb"y ʓN:;~@ 0z1 ]5qܤJ5!4'._۷oK;ڻRqqhB; -R.Rh4J\)i?1?;Bl.Ad>LR "!,&WG㻰Kxz1>4=c6C@.EPi jj(x0,}.zzhthL̐kjjJ*^P!3!]b1ӋZL&d6nh4vАaO+n9399O)`Pm?@wq=Nuá$8߻Lt\.OOO%EJ{Frځ׉nt{k)v+/y97zJS 1:Y4}NMݘFpB'R7Q]bPoM_:~===GA Q㋌Ou'7v`|QV;8r )(^8ke}eG36KabMJ#40,vD/#4\rӬD D<0˖>Sp . |eƯ<yiP6".YMcMH+ ]o"vʭ-U ٱ1b̾ݺr!]*r_8vO  DPQJQ vJuye{'%e;#Ƃ₱L"z7ZP?D$0<ld,E-KYhVDВeGA<<<.?|݊p@҅nYd=0RtG,98z/3\7&F''x/佘>@6_!W*x^tYkkfBJ6WU[7#Фv5#] vevLv!.f}gz\v5t\Ѿ0W^\NteJX,FxEwjptK{R0=)+_sN%r|p`lH\{wA>;#?y X$`Vd20] utĎd AŠB"nۇbFevP>]J&VIUk׮ZW v΄<#tM7Ͷ!T4l̡j?]-<`1l-OX@g1`YDR$7/¯doo/17"KB% haO@yL&) y<gm-Fwg팪y99*#]GO:ŊMyS)kQ.-Ar"I^ˉJҟlXNiN(蓓uH$ O$c2wWP:ÔR||eG1&n΄y RNT"k!]B r?^0.jK`H0%S% KR7@̪$fu +k y]c<d\L&lذ!;8Fi>H$V:~d j=fF1 i `fLu8W[qS}no0a\'!~Ğt:Ml.+{] d6;T r70F2`%Dl7#g.^YC#tkw;坲FEе_ѯreeAh6 vYǴI9Hx 30Cz  9Cd qܿs渊̓aIqt,--t:\JB|雁OWB-x(9q _J_5Դ^za5E._W 21^r;I~ee_Eě}w,[Zl&qPpw̸'å)"LRڒ 9-˭t=3;$0s7J$v/0";ڐ Ç^n;;;!eYKM0+{K_7ֻ&xXkaA=h4 Sq+`:~YiA|CiM7m 6ӥFDihӑ< 73Hq/~ٯKfpZ{ƍ/u!m0gвy_N󧌴R5&Rχ':C?a0}aw|2_S;Z{t:b>}x+/Eyo}AC_E2H-S|@b_!J( `!dP+&..3oLN$ T-S|M1=pmj.AH`S-Ƙ ~49>nwww\_A /K/+{|(3tBK,?7a3fbƏHD~$0=[@la7 IDAT-f kx*d[ Zk˲]? Dmq|/3Vkm~ w3{sWjά-iz+'F|8"gftk[`xY VCD"qT[|Uo8./fv!pQ=)nqp$j~fqFba\2SDOe:R;F?͚oe&@87E q2N: gNxrD`U?p#(覓o@,(94sb)nwaa,KR_uQ6k~8 nQᬡ|X":Bnԫx}MRa|h4q#Gz3b繷}jf%~3 ovE $3Ƅ7 OMb><#×+ZAxyަ,H_|8QT8(L83l}!a2*<> pqG+vkiG*YsFKx_U7[Ou(?a^Aí_I9@ϳsᙙbZд >0ո}7𒠌5дm@i+Q5V62 xޏXaNw JG[۶E-}}5ʲl~~>|x,"O}/or1ņ0zDiKWvXAC"ɭw{ʔIGx~F;8V\3"(}w^ĭɭtj>@X>\HBa⍙3_IxwӶ n׈a;s.xLÑ0#f$~|H2>ѡ͙sa`w}Na¨lIX8#MdZ;ZYY]5b._/~a!en9%gv/>~az+ ߑS>Sl1l5(ԙP(|ƟE-ݫH$1-sñ{a3«|PTP4b͊Vfrт7w&O76n:ĭƭtdfEA٥lͮ׌܌7)g X7!DAEM 3i ?~WQL7EQӬ3S3o+ 'N8z?Zkm]^'栩"-`prfAoL8H|KT1FigC_v˲  UU׷i╮ ?̕-|e"}}$LwgyxmMZ|]n>`AτċT +m;].3 `'HR|[^}pVVE[XUUO{ϮM_>777766rp4_7.^dnm4D9 06Em_mboD6~qӹ~XȆ߂,Qk4;9I"x/P)ݷOp_~K{<hR,ˋ/ߛ^/|{nohF5XDd1-C wh}ZVyLH613pDqH$>r{nR@Ç/}K&{ɐdn"#)D"Qb,raa!̃B_|6> /ߛH$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$D"H$~o@]z{C>cw^|ސD"@$_C P|\/(✘NSok?KGމ\͞C!D=ſ~w>Y"/rgt9ynw`\<%kBN4XF3Ͽ#H$>$f5t @A5YXJ)fb&b"P3bXڌv'7&ϟ/¡EV^˞Ի4Dd2W]Yl$2BwWћˊN])/7Z#;koL~7 `Q2@8\pޭ<ᅬ0H$n}&ո `jT/;:DDD/& w,V3r6͗\xs[^vu}i{qN\6rţ !J-ƱkG{חxK w6oܸ0ѻ];_92Կе#K3ͬ81VMv_BQSx b4N7H$fIJe)[`_u"Os?j#b"q@:+q-6YXg4T+J}v+j 9\\tCݭQp5\/1x9GG۴A mKOynd3D",u^D2q3=>]zØ4@-0^&>rΕչyjX^HH_P9-ƕm.M./^j#fbbrTi :uֱe=踱QUǃh_z&-xt4t_㞅եm[N2ͬ䅨=BAٔh/;tջ?$m$/)H$>$~Y,J&kí4:4vՂJPFhACwݻRWp6Dsu@XSVۓW댳YQZWA &bWWit;Ε9o: \D׶w@-` ;,32w<ƫ^:'p΍v^?r{u96&@ N/d"H.]owynΠ*gĘʘ9c1L+Hܲ<';>NtY`4.얶?!w3n/4}mnnRpiA"{KeĴ#yI&IG}~1:^F' [ N`$ *:vG?vQxnX'N)Lu DdrtwԿΥ%u̟ՓN{ &T^<ɚ h'Rok3G#6fbĘ9q9kLߏ!i9}LaZM챝Si5nJR;U}M ЉAEh3晤w%LC?EMt\#yk!H$>{>}~,0ԌpⒷh\ ifD045+,9"^+|sb""XrsԈ]ub& 'C2VI-|lwPa^UA2=ZkKԱ2̤`5PsXgT6K7ZD" OF={U$?<9;Z^?{ksWʲ&K7H1{-EqSe7τu!eM X: h #k?sC⻉DCLRk=9R*G ?G y4>+ OݛS8uVJ3)gb'q`cbAD$8Tx3^9U7?V𶪯ehYdN.%SC˔,Z&bf ufC=_yD?_vt6厕zzY+e\g녨tL]:^ȕFIw4]UP6 bԭdU0C@1H)kDIR0gϞ7JBThoP3QmBJ0 q9ϕHMxCH&d$o " ։]MF=,8R1,+|Aqd2 xY;ٰ,]猵6r/4JZ䓟Lvy7qKq#{ecpyTnӈ~#g:vY@v\ kVQ"@'8 /,ONݝӓLĎ]ٙ1)hL֞/'3C";ƅs% Dl3@'8S>W syFo4WD,(#$3v^R[qr@Zx.̯t&f%)10%sp@3H]"QRύ}de^w\+╮H3&{3ʘ6Vj6܊Z'˃냅XՓ]ɻ˯&+3mڪ ʉ'_|T)ؖxB~ wBXZSw884ˑ.MX{"))M߉u;|QucҀ;<դwćt>XVtܰp\E4.t<-tmA F;ueyBQr wWb"Ѝ-*sd|@Cg9uDP4#34/o_>~ t(ӵg`i; a s H9r63 u{v eSc\Ѱ#]YUWxk篞K$ZΜ9}eZ~c{Qg&!ۖN+N7/j q۠q˫fjl;wm^2Ϧ|<Pzrgz:ş៥[ݶ8:&Ɖ"MD&DLd:W^vv L[ؒ翛 D@R??xVtԺeL:c;wvaCqdj2^0Ƃöդ{im6ss-I ϸ8 62 Y̪zl[koq3 Gn+J6"n{Ui<tZ(IX|LrRclV#lPt' 9"zR#3.vvXSsC˻ɣbT*xO9N+c;[ǯ x4O_N>+/߹# GB"ue&f2nwܵдW17X-( WMJư{30ߎSuZ̒r-|3Pc^?:]lҩN=v_M DOR?!dYu>:J]]f5t(W|w7?3ulXܦBtTȡϝ~{sB)8׷6\8({;v錿 É{sy)Vs4:vs 1AA:Bz"0ӊʳVu>xF8-6vl&mV'|V]S}Gcʕ;۷],_T]Ec:3G~#s/-, 2d2 ~k;W&=o.Y_+@mUy9!BktF*C3(e\\ȀVtP-ZeU]?3)H$>$i e5q\rS\:{SFfppQw_Jl(rט#L N{ ˂s-x+MRښa5Yz]sG:bV_Ѽ^@ZN3Vtq5&Q@tk.r*uAĦ[<q=la9 60@YDO/v]/ޫ6}\7G&Cʖ)&<`hXu s&?|+v}n?ڬyKw%`3t2.t""bfx4|#bq "kÀU:,LN{ܷ;D=")ݷs䝞 ?5ysHƲ_Tmɨ}[ F̵Q4hZ打2/%bq\!ND3,73'+Kwurgksv>@k9 (A's5_d=],zb8 ƝagstF jni lؿ=P\[kOU*Fx8ăYzy4^+cS헶k:a. IDATM7v1M6&w)Jb·nsY8cBFF-~^3jٱ8rc8&0=rQ=zg޳/%H$1Qgk`hLdۇ/"",ҼidEIb7ZQM2ݱ(+;ʻtqTNZ7Nix>2B;b6stm틽( aEW*M5!.bw:Pw pD$B&3:";8;s~;F"H$f<Y'h\Dq\&bqdg-99ޠWk2 v_4We@N5*Y,W.bw1HN#0>82.\?Umߛ?t;a!+oiJ7 {C" PB+ P99vM-7&ŠC^  ADđ_3PЌL?k~_zһwjWںSˁQ4j)@[/j"8@ gI~\ WOl=\Hk&^z6#"˾k'|b?C u(8dZg3Fc|ۯ57wEH ed(5p=pg}D"Βu5$cPi*Ymo!o\ "W(̉`E ™o&H$ҝE]:h] ȏh%}[sֹZ>+5Uzo<_r!+ ֘[rh]}fjR M jh_ٸ7~=ŕN^k~@Y[,\hKt_='83Ȝs@C:J'Bg2MiB&lnr8FZᤱ+8ջȶ&Ї ?jBn:{qON.7.VW W?|W)Y]9W9',p8M@k%zNf1]i!<*q 5h +FQ|W@sF +UmϔbV1M6롨=雊m{GrM\RZ"{Ȼ* ͘5j5X眵6fRN&d2(8rM+–.]dti ፹ġCPz5KB'Z#osS[\)z}o 倝@ gHR"4J,sEVteő8mN1%GHgrH{k$ʴe艃]tf 1}A(9RWS"+mDeX'v4p.~׸K8H-!VD뢧 ֊~:k1u]8ˊ,+gT/T; oEpTk[Dv 955$v-OR gNwy JJ|७s9cL+tzYV*9Ӯk2FM \9PzCL'O.[8{NȨӿ?#wtK7ioWTo!)XɜHG$qr,mܕ ##"(딵T4?0 ZSe ,xvuFNKJ_XRAOAwYk9+G֢FLB錵fjY'hAwY '2!;|esULEpO0T/fύ͏f7zl4dsɮ1,+h]ILԔ K|c& )Y'|,N@93{xϾD" HJ9hQ4,(!!K|/Bw.=uTƙ:I9qLNRp@M4qh#! HΕ|ΕU/s 1CC+"Q-;vĿl}:s yyɬtmf'}7YI9yv'ZC BȪ.s[*[hʲ(Uu69Kaw>n܌MRYx?sj>ErYG9 "hy ROk:+Hhk hL-XiVZ]ݑk"dXR,Ttި-UST0iO+PN9V?^^V)vd8\&aӵ2״UƤ'Ήsֹs.\+v^4bh)'B#Ӟ×iW虇n"IJOrl:Qaj>@}  B rD 7 GvW *\ӿ'Hi-ZĢʡF!V3k"&X"@#JWN4Mk㚈:Եi3*MP_PhaqV# #틲$ ߺZmuM%Ni]0jL@Ej "ΛȁCS1x􁇿LD#t/t]ӝrdp0T91TRtŬY+VJ֗kEN ;q*d `Z,vzŠ1_ L2no8>ޟkvFX`nwj}c&i&"VPBJ6i "@.|M$"]EfA:g/^rxNpcJif** G]o(5}!"=Ewך3FYCQ"$ceX`Vܻ6r.댕&K͚hvŌƍ oջ90D DPeYSֱud9j˵؉eǾYYڛ%`0Զ%ߍkfR&N֓eYH[fH(@TŠ:aZ39)MjrDP;(9DP[Եl}p]1E:tE ,չɡŝc񼲙cKZ JT@9x}|9c = |12Ɉ6[yXC4Hچn,gM$Egz]IO 22/]znaXgDhdĤ,S7;[Puq-47EޗTw'>E^˜k?p|N`aTh~ÿe~ <[&:ľz G&{@fe~U5HU#+OȮ40~Vg(K[^ 4FՅr[(jJ0@$v?ST!kDdcJe ,v7'CZu3]_,3 9!"ܨEDG`v*x]-G I,f֕۴8gGc<Abim/@_`"8\!J5PDv}Tg7HZ|g.e_5-!6@$jWzK^{B['˾Ascq{Tј+t`'stmwG.?vɌKK0UKϚXG7WlH?^RnAn!G7VƪC ~P+ARe낝B┵8U ĨR#4eyI}d  7~o_!^cF^}pNb}jwJb7fyT]1,tiLLh/bl'Z:C$1+ځ V01] ڂ>G(ˢwWzpYׅ2s숴2;]眵@XP?sd9pm+vZЍ6E[;M0s}qCu,n/M:'=|7M-GK>v1]B(74 ܡnw9}uB";>e+tGW""Enݾn_k] {Y$tEO8vԞA`/~O] 1Y뤶29|\z[=R? ӊǰň60.{c7.+f$DGN:2VUFՎډaþ߯zﲯ)`pMK+wC벫Aca:QD0ԩ?1s5yF9@ b&֬4[uF1Xe&ff-RDuQ5)nq .;|wXLIWv"q}=/;o5kS9 qdkeDCz\So⇅~O?[M$ROA כtyZ2y/9't' 3g̅ m=_>ŏ}W'hdnD(ITnD)*m|)Jrm mrrJH[Z-׆dž& i.b_(͗`h7]|bf'Maj:I?k?w?Ov$>2YS%kiL,+*SZ Xl(fDȭn%o`6w0}WHeQeޓ 0o~;Y(ت挋K Y(ǟBҔzq&ҋ6Kۊڽĵi+DLD8{ޏfq(X8{.]z]]ZYc3[uǼաŜMIvz^M]s1#o NÁjͻ}rbVn|-'< Az7y oN`V~bɴ3Sd98GX6FU+*^h/0]wоREXJ`!"0 T ))l.ҥ;3g߱c(ϞBRs-pl`P[[c +EX,+H)"3&f"G{m\K̠F=#'qvHLDQ~uF7>)K[!*K!9"_AmOU4uvvB"h{a̡|D"V+ sAZ>4{ůt\YV8׻y^o"0m"\lHٳ.Lxtλ~w,F1=9 95}NNփ'o rpSKy7~.ɸ]MCڕFvm6:&uGGֱT92є5neOJs4w?/|/y4P+]!F}>Խ&GJ|ndTz,pgO+)dʱ%`1 Xk֚f!kbK1ixOrƽ;q<_P$JjelۮL1z M 4"  ӆ֙$ vȫg"R "_3y J x㯟g.D"+G<(U[VN^y[ޒw,h2henh텨+ GPhGX Y8DDx?zΕCal\E7/>CP >#bF?9纋+yq VXNhĴ}y3k~ !6צPVa2 PU> ^5`Bc6 bϟ 5!9-pPL3*d-7Ut@\&<~rPQd(P`+X CPJ5݀YuCW1!nL!i Dpv28@Su$:VuƇEfVֺ̙m65^9振%w][Z,qiC5+N 9ba@O<_I%͇Y>Eѥ"\"#lYW^fYݢ([] fN/oR32e_È}{rJKSdg`ki :'aTN[=P`ǿ̕%Cn[_unVYbQβP6c5bj ciRш,b/[jno&5ggšK Q })7`Ut?TϭE%*Tnb 5)fQ:SJR7?oj_c"%JW%,?Ѩrv.ր’=V9i ̾b3¼OwuZ lZ5,Fz8K\¼?imPP*cA{,"N Z0L "\cD}Ct8X^t)/8/9g^aQBYZb<E׿6*a_qq|ܵa/E6$R;TUi'xʅ"+yzZ &?x DWrυ'˹eō3ʔO7w!#׊*fjAXDEmęw þt~``1p!%RLdUERTvJRY%]XCeWȖ%WJ)v,?"$&T2EY) " ν_ʏsĥp@U]=o>ݳַ@\O'Ze Ċg$uը e;]Jgx>pOkg- 4@ 8(+1DЋz+5Xcȴl-CĢN0E"!3%(y%,iSЖƇIrcpG|pbs wi$ZRh+%K;6kWhvłKhNDJUfv!R`4 IDAT2b`V:$ZWTIE.hyMܞ<}X:J|s"Gk'ܴpΘ&d*1˗jqmͻ h8-dW- ɘl D掛Ϋ%^B^Mgϝ{?|M7aO|*?gl-ndXNj_N.;Z ݗN'McTI Hyxgbbb1y!@[#=qbjS94- T=W,h)P ^W/\anw_}:qg` 榇 9 \d,טb"pt*j1 -ŭY[cndktITDbRjJ<$o*n5\.yPKh0J$@TD$DG\wFHC/% VX((Tkuc_# ?b1-K#"?;K i`ܼ\kKD$ !$hH>Jzǵk\szΧ9kW7I(PdB6O>۴9}Ke{0uoJls9 hB@r^kDi4!Z\Uh1q^SBР){fItERU"!0A3\>j?\U 8ĪR[j&Z"]j h]Er!piHZ*>_\ vױu|c #SӲulv*^|>\ʹnvl=h4%1sz_|r} } _r]o;,f+3/=iPj].N][Nzd!׎ut{`^%-SU|&x6)( "QBQ)"7B)G*j$:2&7ƄS3ըШSTTFwt%aV#:\9( 9sBak 6z}#ʓ0pȿzCp8Y(|թeH 4:2kZc:y7'G@ ffrPQYȅ2-0wD#c6ʭn\LPf T)wE+[ϐqg5IfhJ4ϗ }ZzJLiZJ{?Hmڙyoĺw:ֱoD|S!wԴcjހH|?Ƶ.#:h[ZfTtĬZH^g*Rۧzu'\ =v1VU1 B^~yrmNp[0BHW3^=d|kgaDHD3S'xU !ja;+941vD gT4A[4P^W+Йbt8)#;H)؝cov7M:LzॆW?S{:qkA*$ډtЦ!bz11FbtIDd Dkx,Y k`1ƢD$OxDM MȢ3hi(]8$'?tDNYVa ]pLp}0^'kA%(1`W^A^Tk7%@@i$I$/$qhB*J^"lm+Arϙ{{|u㵎o1\C62"fb]c Rѭ1[6\0 9]6 iE^$U!Q( Ms8hdxnEpq?bs#R&4p1=rMOO>.W FFa$CϯQ;K"u eJ̰RjUEgDֈHnVΊoz"#JL E!'#XmYzpcX7 }v א5)D֕gflmC-lZ {<-HׅhD([yrOψ!U0H~[4f|ET<\f7Q}4}n7fD'q_QPEs&6Ř`KuMt/Ovچd2KP.. .V_2\\'ٴHǾ￲旴Um '|<|B^ 7!gMcW~XKfep}4Ql& fZUL62x C3$JlX.UF (.0`]&}=occX:;̅aM j텼sd°W`?hkG|Onz Q,=NM.9taޅiWťlcMGKC8S8/!sj")?)#M<ؖP+S9U[/]+#aV&#oz}ot[Ӫ[FȂH$7%KVi&X cR4ph&> ێ>SΜ/}1Mi]6ٍeD ;숖8e獲(RLp"}j1g!ֈu64fK+2Mn:p,ݼ sUU'>4w5O=,_}lswC?ݞ^ݽ4m1ͣε6 YJ}OMA{Һ`gL0sj=P-{fZJCwUYFfϹ+?B{?3?WI[{/nӟGkj@rc1-@dĒz$d$LQ$[ fu %4(Hs÷5U,(ZKEcaf2Ͳkgh޷h*&'ۖ) ^zCgJ3PUpjS &(B@W7ARe5g0%6J%InjX欄J$صJ9Һ4mXkoHȽ5l92EB%t~i]C֑l0 sSbr4QZiфB^>" }D *lhcb0+^?лzsFԑ|2u ~SkK[hst!&YJpWoE~wp-Yľ8ڭVsg=j$o}3_N}4Q!"&SF%bB2+EUVL95eqɤR*@P,U c]>X &%3'RFNnpdnZP29P uc߻<02eCBJvbme&VsЊ!g8xeUBRZlK>dGQT8ZVīx: d;:}aΉ϶)sEڝoWS~?i݂GUﮌd+Lw^Ntݔ6ͤi&εZc*<fjs+~Z×E[2;ewHC}w>ctٳ]Jnz O=[a֢uppN}mF:tַ_rnrɧWBMsrb-Iu1IDbœ;uq EdqiDDٳ};r O>m>d@,uv,t!ݖw7]p>8/6wW殌*\0q6&eRhX !1,YMFcUQ # 1BDD473V  {S1+jsnc7Ǧ5l@,c/|:#c[* 1;k+c1΁XsˆzēFA:K%aXRD2bmlX(N- }o>v"TŻP}Q0:cb𬆣AE2qdQQU"cwwM+B૯Obѣsql٭g7Ns/ ^tT}~)"Efw]vHIFJ HUU؊@5VRq-R ˵3@ek*. `R$VU걟}w?(*Ԣm#w?g}|O=>q#>?EdzAI`k$DHV[uD^M9sm+چ<Ci<d;_bFIc0wK];ifY rJR[6E#/ֵdf1} KVT:V5DWY&tTI;D9*A h*2" jj8/Ud7%$WP$9왳_zK7bcXG7 }=odXkC rccca^EݤlhzY*EG ]@GxgCCj%ͶB-$5gN/3rN+߲\>s'~NQU@T3 b8nu07f GuolR"Pϡ̚!DAH*7g4%p\$q-0u(ђ_1VU}魿?{G IDATR)9A0طgڻSYWw]2t8y;Ue"-yM ^ aOVY3W9 1Ko#( rve­7[g~ʶYa#``Yͬk[x蛥pv`*8m>1GdT!E. XHID9B LYNLz"ٗRVz 즋MZB3`JX:E㍁tu EF`x _8MeLrٵho΍5>KD#Gk P{|KgOzĭ(|sS@'Tg/^bΓ֑J |@~Y]Wcu?bʄPUy; gV.vu(UX+浼M<' |YoB{ކR'Q$ah8霸&f1YG?z|~{_m&-O}O3Dq#t o@v) E"MJ6 ban'%Ciu;IYRۨX}t>h\ca*p "-7 %IFc(*Kcem̸: mCRUE)-7Ĉ Ni!4Pzq셲PuL.qi [4 ᱎuc!R g kٲd-C5qƴ7wdȹqLf\nmJ_Z y7A{Q[ Tųϻ/^<{[ҋs`xPft{T(׈ 5U(g/b "PUf*O8"5==g Ej! dW@|5* 듪RE878 Ņ>ιv1ȇ_]0.{UBΨVBj?$BT !O I)"J~8_cKS.0<}nGZ3 b%i=qD PFvv7KoE{XLkkN,f%LM<,FlFHvcH]&k^9CheYBB 氒S^Jd5pB9#:ֱuxsoD.z!>\c,FƶRM񛺠%hܸiF-ѺhE]X͢k]3f͢EsA} 1y8Z ɖ0‡#Go?rv_pn\3o+$ 74UiH4zPՒOH7Ť l@Opx IdRD58Ndd~³[ƃa#V` F  B!"tL'(>ЂLcCӆf- `н,]trU,12!R&@ZfE'Y-5#B3azRhuݚ]:qN%f!Ac72EB5,ǝ4Fێ&uD6ڍkԊoY.F;vgv=ACc$O77MW%pO֞w‹/rJ*bk?OݽQBP"snܒMX&:.3@\i!-THQ CZퟤ4FZׂ*v{j]}tr`*ƀVM3WPr c,F-6[S78\JlI}u*epD!Y."R5{Ky؏vPFn:B Y?뗋^FG5Oi ҚјҍTBH@(Rjŋ*Qcvһ.ھo];/UzypF:CH 0!Ek󤳉3$f(8mH)ɢFtXXiMk v0 gM:ֱox]#w}аAD Q"+Y&u4]%:e6ws'm;qnlLkL\\-$~kdh6o-Fۋv7sO}>EQBACDOwO0WyԽ 2~/=PV6ݺ-^KOtW?m>sLOy?^_z߇Ћx)TSءhJAݐA]V6Wh+*wPDhG2Ŋ0^e߱{OdLn'qҿ37`D?4; fFM\la퇢chCPE y܊Dc]Vp.0QJ瓺{=~@T郟K4v-GtdK c o9l1њ`92J K"(@Hfo]vYΣE3!yU&-M%X+Y0h0T%IJ&Q#0"T Cb (dXcV/}ٷ $j *,X`nn Q*ϪVa4t+Tn\!9:v֏Ӎ+++]6}[% Qr.KTəA"s󩣛:7_}uͧ_о[u}{_ھ<~:> cM_y @cr}!!t11ը˭ϟA{7vZyz%x_UCb0-VӫbOjPGUJ$<4hCpZ$uS+kE$d7# #J `U.ןe~iuc7&^ }HT I2McaZkrӇ7)mI+"BnKHi~w4}JʚTZlU8&_Rlg㟿a#kXǷhg7d8] B`!f2d 0w鬍\-i6t$.F|Y`䛻l]3|ighc@ O ڥ]"gk- G?s/˪ET >ty+^=;n_>7#;-hNu_HHH)фtׄla@sf?gފ  ^-E)P!czB.#MduoM꾪U%֖H*^U+/,3Kb/f #݊wwi] q2 A1 ef:G~B]{ &K۟ p >nzb6 2C"34#wu9-H7bVv`zLl "!o|c,n#v}mͼomAUI(W)^!Lmgݒ3юզpCT)"j'R֥(s앻FSN A0 2T 7jU )g|u ^_Wsgma(a1W5;0$BlFӌs#mD\͢o}3E0͗ʹk-Yy(r -;bb2[Ej{[>4 J^0W4Fx?ZOm/v|e빶SMf j7'jղEbq0LkTR4%Yc;(u5+D^-8ѺADB7vyfj1=MsvN 3M΅2Zپ4ݸv:Qhaf4/$ыKU c¢C'F#/B|uQmkm+!pG}P8]"&zި'hc=zmV7hY"ƖBx;h;YͲkf$ P%aH2`:K;\WU/CDRiI'K9G}^*+ry֫@xspVe%MJM9bJ:Ec_jucox}]YSk $LMWsِ1\;z6om'M3qn4#5 a/d{1y}0b3l&[l5_.moQ}T/|s{=Қkc۵1DknTl_ "(v[|/7OvƑcW̡ص7vk;캓ҔgR4&*:,0W]P: AjJӫd@W=4! 6CEvURa3صi抨{.'{+!;{18w|}u 6?<$rfܶfns_b:l_7h|FcZF"&En iM>͝AH@c;0i„R5mEi`ol?2޶Ͷ4-GhU'*to EOߣ3x}-i.oi ˨*^M%b@2j(&eDs9 ̘;[w#:ֱoxq=$Q$Jɏ nuWS/snTt R6kCukY#n4]vfڹioK7,)"8DըY,:ιeKK=D"Nm1>~5/n< zƸ=z;B8ܽ+[/{ 57]/ZG8s]s[')Y[uVKW w3*CPeQlˆFrMY]r j8EЫa/ezkWvh9yH$UVb:`٤9TdZ$, ι[7ߋ{8'o537o_*tڽ/Nڤ)]͸'?9(EEB΄<;?rǮ;vn,ҕjAG118c\ͦ"!$%2)SLC]:(Iwa(AL3$uyX &C ȵgYP(sUAƳY@Dq4:ֱox G%OGJE5d<ֶ}Zk}{}@Rq$Yؖm8 7p NAIQ P HmOn&iѢ좮׉GXȦD"ER޻==R~& |88{ƽooDm|[a _kX4MjgUUEWzWVqH>PTG (05֎+kJk.Z$0Re'u >Vn1cEE M}vy:Gg_d鬼8[N|MXXGk;"j}ڟuϳۯIQrE^€iC{/M:c*;)pRV ˴Jk(6gZܼ>IYE.oe&D3j"_[ջGm㔠d /w= IDAT秓6̿nx-.awXus&Aks@jp4p\CJ0ff&KuX[ը](NK ћo!ݜ\.RGNXRE1@qb>+|â h(H馯J1[CozhMsk{ߞHޝ5_nod&mR&MW"f(ꍴ"T9u ;e&mc^U,@AW(L ߹*ɽ5٨N0hb 4u;)Db D>Fgn uVn֘Rr S30E6E. "AY#G QIZ֘t%6[jo{C:(] XR %DH'UepڍNO-IL 2Jv#f6sGx K1jT)[6LJHw :f\Ss36yY7]vQE햍) h8AB=sca1`MJ3Ew0~G!<|9~x +@~$/~0T밪Bs݊Oph3)M_?o5 !⚄Bv:=.:mF_:g?y(v>SP1GТH [Btr^>[k>/6uk7%{BlNsFX=.:ے8Lvt=gSY={|{te UaN" [D˭b9_l|^^.?si<=ѥ {R0F bs;h㗮[ ;.lI^a1ΊZԣ)prҕݗx nXm `jJ {P44TO~0DF-"!GA>C FkDI JXDe~ E0! Ҋw))@ӳAkHnf33X!A%Jd#IS1 2g)u!Β6 \r02?s٘ᲡAQF A5(^41kUm2d;W]] V[W( o(q¸ѯ1c04l^=aܞ. 6 %91[l"M!_~'ӗ~᣻T+fqhĬ[SkC6I?ݒ$jl%6N7k4Rmu =.6tv]mYz'^%6gx[bNkt7J\_/}ø0"K&3Gn[3kb j̤Cf&a:Ik_Y" G[m];:ysG]1\b8v~l0_TjQ^,b 2h*c4?Ca:j 7VoK ؎A|`5qQfˁ[Iq0Ct\ ER,Y!uY,ELR}ڞQ (Eڲ)u,.(+Hb(>fm&uORY:q2 ؀6Ycfn4'H"t4‘q0aKƲFِ5/h5֐aUqPfsUડң^K/5G (D!f6 uUa̖pe\YUCnlMCB0 iHD&QA}6"a X^yE]c#[KÑ MLnѢy'xw'מX'RUz9V}L<5't?5TMN NyLɔPH J|Ӓ c7rm,}2N%3cN^~IuݞU4}p"/͑ߩ{~݇kk$3iUXQ^KkQr CH`xK"?{xkK>]79 i16QZuXf4Z !,MR&C0i B܈1m"- %E jPrimmLk%Kga`Je3%+X=a/ɚff#f6s[D`FI U$#cɸF4Lͭu=cGM*{ MZ6XhTPé6Sd.֕OyD?^Q(#BZkP^pq o ۣT Q泵'l]pG߳6' m8o*.**v10"&%ܰ|EUEг_6$u18GlaCdpI)w;j,JJ"S>.!RG)^61y[oOzO8-8fo?ڐ-Mf>#}s~o4]AZHdVF>u?ZMljxx4G)ua<\ &`MC&&_}s *P s!&V`]Z}iNK!+b4DQfu+jhde W\ ;ڹ78|Ous* d(c2F<*IM716FlD 0Q%-dThMIӐPot`.Eۿ&L)^7%(LBԭO/Rۇx lf3oO;!&!Ҡ1j`ȘtakZksfکʻ6ˆW*h ZBjaApY=e0FԘF+HL(J6G m|:Ǵ[q/[͜C0Yn@[jƫ0O  hS_l ? 0nD> <}߻UьW.'͋w%,jqtuȫR[w xPq[[9V'`5v:m33`t@wC/q vG;q29{+~'ϗ~#BDq71+fc`3tGNҦhclaMKS,+.,{wWW0V?#>3d 7"+h\qI[R!$@#`ԧ5 *=6Ch͹cd1_R<<_=w'l3^Nvs3~*@Lla$ M`|9v(/IOWV&P|EjךAޥk޴iB yI"%un6YsK"_f6?μoHtdYIK[ `Z2l` ZJlݶZBY `/?%ίa&7s}Z6ZifzYa62րXhc5lVpw>zxpC@5o็?~ 5>xT}~~j/K+ :7}UEd`)`fxx-:#bkka-Li>w"RQ#Y Ev!ߪ?͏*Yڥ98s @%g5#w~RAJl1,(@$iBf֛ ӑ&ILTg:%*&nқZfr I ZG\vcNd"G6fnǼoHw&̉U%':é°Nx}k$(^ !2D@1%(2%lbjTTWn5Cc6ҥq-̢**8֘h `7·qkO:Cbֶ[V^,B6Ya_8}|ŏe*t[NW&㩏΋j$䚂u8NBekEiى ҕB;c-փY8Xgsz1̒pG񶻴|Mw9IF[IUٹ7l_+&RaDԺ4nᰙtZ& s-ݱLV/{ zK抹a wx˗R|*v~@8r;*I(M(i@TrS؛7*>uǩij4v@ 1jX&ťϖqE-Uh` YCnx得aV ``(d -IĶ腷Wƍ&mvi:iAwD2=gert7 fn׼oHב%f%DG 1X &tۉVz C[L1u;ֹUjJDk$3b~GGϸf0fjrDKRmS<S0 үmVn͘@֑˫xsk.l^JTk@;nK:/k[&?|p(Ǹ]A#k ?ut>d X3[e>N3ʤxvkOAGfq[5-Nu8̛b%1) Y8©?qڤʀ: uR Jj1X K؈MǤ)՘^㞟G/gPgߩ+Sך <_ӷ<4D>0d>S\2 4^z@.m7= _lD e~cUIe8(=jLV3k)Ї3[cjT5zXTXDURD6s)ZjˉTRr2ΈJ L 5iK~Ʉ-bOcIAfn׼?H7;!tA Zdž05܄zsVS$J A(7@b(Rf5-Fo}?6oRō_IHm+v87}i-1C b8c*r 2٩ѹݻLJCx_jxrckrskG*w]s4];x3>f=4.BF"vV[k=ӖBsӕ,|QB X9Foj]ʑ8]N6kU[H+1t7D3wjtMkɷ_.#Z / J& 6yc9&hR=VJq` &$'&Nٕ /ZBnP‘"^Swn%6ʏן=|nf3d2BFM`L²jN[g6W"c  רDh*fV釞{h蔋YZ\v ;) IDK] 5ۋJR|a~e#u-U#FT2d-eW~gMq.!m,~)wy[DMpf`ZbtƄ v[ R ,Mх/RKDzJm"m3*!ɆKu8@@9v=˗c2fS l6{t}F!A#!5" c4)tHm\l;>Z #yڌF,8A=. aܶ@b쟛u"ݓ$ݵ:w8.W#$ 0  tYk14ػl~>k[Ved)>]~1(Kzʿ榟?ڇ^?ϯN JòKLBʳvzD'`4k3DfY.EruP͎JB-"&8V kn$2&K].of.8䬶5!RzKt&E1XDe﷕ԹT,_zu /oUVx3x? .MUyE_C4Ne{rt=[ qjQ'NwXO9r9Y=3W7/]ݿw^4MO(8Vb*߼1Y̱y3>C&Z;\Ի~Ֆ5o%xƟ}hlR{zA [0kg ,nAt4[зs՟1<&!`m AZ7%v^1R@8D#BSH@8ZheRpחƤB mUzix nt-?u>[.dnf3=^#!gI!A:,[6m:6kReMDZk"ԛl"@uE%5ea8)gϞ:<=ߞ.L :0ˇnNI5tuwXSs۵@}ví 1†r5jܷd^yP6׷^zS;k_z6 ==aqp.l_eHU47.ׅ"Lkr\esa=[B G 8_Xunp㚼$W$ *DTf bbE# =Evw8&/M.aj9Ai7{d⹇F~]2d t{̦)⺧uUdũ3 e@T*-- :oߙǞ_?|>7Y__^qaaFz.$ET O7/!BzJWbܓIXm6Uz-aQl )9R-JDrifǣuΫ*)D y9 N]-"}<\jUC%C /8^ݗ;7Νo]ɝۿv89⦬ɁSi^dq,[-ObuV QM6`tƲE2FeZAw=-`.6\xlvf494f("ܪڵՍ)m3m>)!"Rܲ唇\Ng̮@"OMb4غvԆdd%-O]Մɤ^jOpAZJNy, ]yJ;&+s5o]o>QUz/;_gOE[=|B &\bA=ѹan=k@""EN̮P%1bUUk^&a5Y1Q߃ݛ߬&hlWzW5ZGd(Ql+- ȡۢoT1ҸU5"ՔEe2%HǢa X6s~/~ۻw>~SõTo 2*yRzbd=}:IEV'GmƠ>A$Fc칯 o~y[Nݕ7aR*'֙HezYRO5v"4JA{Ҏj1=3G83ɧgxvض^NSnK pfn˼ӕ1lRF`Zf-1W <xJUPEgsSQ GohX٘٘d%d&!ݴ-~+ Q0$O]i*D4jd5wY%3^_oLvK`T#/|8.\~x>G9ZW)e꿮=Mln¸.C#OM?_^K|lmhטcb!`mz(gsۇ~\! +-̍IH;֜0!:c8r1=Vy/m");_Y3ȳ2:WC81"24WH2P# Jf^S4 T\B03䳧o~5<#~szIԞӭl%!,pmVzc@(ccӅ*`@DfVƬ#)ih<"PBv90Ʈ^>umߡz#ldx0ՔRGGVWfz\1j6A} D`e&cYʞ;V,0{S@7|2q:F"JJJ, ( .j!ڧ e8utQ؎oF?Nw]Nfnf32R+4)ӰPf0iR;|w%:H[Ic\71&0Y,LkKC:WENaNhQڵBߋVF?Dš`dn |ɋHco/n֦'Yml>/|p_נΥM4m=]b+}Q[Eܱa],3-]/vfs=}|SKT )Kˬ~9ּ>\mVE faNf;$z{ $UbQ ǓǓx7nl$$ƅD|+iގjhPc)xHI!>PcPQu {4 b1@ 7d~o}=j ZCp>8 iuiz@Cx M -!54&@p$J!" 06DD$R :%G1L`1F)޳oif}w1{ {68["M`Hwĉur'`-ξu%DcޮY{r0soqIuq}io6>OG Dٓ] ,1q|~f `3ѐx^`1AFmXސL`{/3OY|Zrt+Ƙ>͔P8LZLWj6/grh}ԌWDldp2r'T HޒS"gh}=XE9w7'7W*;ݒF%:32&z`ۺ"RGA#|*\EC൪y>C(K$K]g\w$?sVːU1PԆ\UE EC=[wt(0ђa2uZD\!VUCm|e_O_/>?_ c3P,eũ8_t".C"hL;!='ƨ1 iG`C KbZmns iyՓ@K+'N_D_>fE bGae s-³6|Jfxf늺;eHn4l=Uw( ,YJfbN:nXʔJ)&*ц#V"yS.,Ԁ GO[aNh7-ĕH`U"3p=X鼚W[j6EF B%r.E)dL0PozX|5<8.ƻp?*#) LElmmmmYؕ$7؛*pV1P"!jhh2@a0@P1L<{1 o=Ϟ=~-[ J_U6]LrF6N Bhb"A$DI1dXFsA(A `D\A1`hO575BHȅ2B(pUo  0#ɳz>"hV#DBH+~ ƥ.iN-tO(U^.wOIJ1iۘcL0Ӫ8bbsURc̺# %3ԋ0Y @ O`tC;=S_DAՍTw3wCgiHQ d%bVn@1t]Bj &/?,V<=|;}{b)&!Rq$; ɓa8p OA0?ĀN $ˆ+38KlfTsyػԹ6&~B:YϮB)rihn-Ti1(f{LnE8;w!bCwQ9fSqѝ*'7ܨ꿵wt޽?ۏ:9济oWmnPޝ;>'d/nq4یKv[߫hU:2$J_x /;IvHPLҭMtR7:GO\oQ Q]RK^1G*H!ت)(ˊYh;gYh=w!Q1 eeKBQ%jo""P@م7E *PDH>jDt yXq2ԘfX,_GDdV֬jͪdBmyʢ Ifԡ{q?Fg1"1زm]T !%GTĈHAYDVJWΟ߿n4" N9)N++%UEeEMʏ'>00QUEE8E"2j}{;}#3{4Ln2 q.ۤZВ溉`&R4T\RX&:vO&sտ5}B}#}VvI?Pr_0 6t)Pg5lhLjХD(fi45lLܞC 5"h6hn 岪Ĥ|!^|^^X73gA$JT" qMNݷƻG޸Jk/:v Npt Bep+r9s dS EBO;uCUn+]kLlUnW7m$՘Y~4o ih\g+[7 +&D!!R!T< Dq; wguUHM];CjjTV,&A(j`W|5 *co9g2ɫh pi0tizJvk4 k-/ǐz*IeRx;*e+ 4d@ 5{3!d& 9v #TN:$(齟7KQYImtV٧d,E%t1(D R'[khB\''o\=O>."$݆.Uk-!"REWmvǺ5>q~]OZ9((ÉA6~O` 6x .FHʼne [3K\wZZZhf\32<%P=~mY %V(SQ,4慙γi~xf+SIg(ZJB(-@,?H7EfH!SD,Vϋ:\ LX4mn!u= H wIεʢ3Ѭ"Py^  Ԧ?}犼qel4E@xCKϤgG7:W΍>3`fR}Ftw uf(J wՌ=mY7zr>-@[q:3 ]96` ~`|P.gA1N] wOb 뵤iWNwN9:5gh.guAژJ7z[JTw$nۛ-2ZZ^k.^^F+P gB1xgbTUfE2h^ѬNpdm)&Ԋ 3-c-d'q0uME4^l i5Ou(!1ss6 bzm&J-x0fG+ZDB.[$D8gĥM@hwfT|bp_dZXEGC`RyYfp{Fڸ 4{bPJhxxssXyDFfaD T",`f<p@d@ ɽs+՜Qι0UC1V!Tn biFǭ}ihTwqŠ:黯%5qu*~clDD!Y$*Pw*X^qޞ{~!-C`TZ儼oP.YPMM"UcZePkɺ=I_;i\s34I6Z[76de4pgY,K‰"[KQj4V-W4֨j_|ۼ}ؿtk8vqiRXKiG}d..p&g5Q?~K'> QP J PK_2>׺ζ(cV $!XI ;'f-J3-iN#'!BT rz#"rH<3, iUѤwo>^35O{J;e;ծ/ ^рkKODۮHC,YhG&G.ג/w G^hhǟk/V~9,b<4@ZUؖDm,^>XxEL Nm¨[FE*q0F[S,ӭn | Ȱmh||'b)DǻWҧ[W64ƿĵ9.vC!C?Oz7&ϹǗ f\jj)b}eUA5R;stpT f[rnP,5mpi؄8yi]vujΞ6pwۇXZFsAddzd8+lSQ^+е֯'@U({|lO&żűǢֲqJAu*?x;/zǛ@SuyLi=jK^?}x.wPE1Aoٿxch;Ņ g慝fH ;Z:.xGFAPRX7;fʨTj"`ՔGgK% ZfEgΎ*2%%5d)Օ(%wto3yߌ%ǭiS`.M^z[/L^$>U&lb}q_ O|4v>wp8I&![((XsޣvM-rPDYɤӒ ;q pHNCp_ X ,fFVUpDTц=Yt|9yyA[XGV)GˀW #C9_z *+A#䯈+{@v݆Sk9_ųoD;~ 1V&=oGRiúZ?gGQ̞SeM3 H(jd"Gl"7{s^2iQYK@@툐 -؝^,?/Wly| &EYA'LN@6&]4Tjhn"q4d4zaxа{gUIl}[ObUwKu{4""j0V!_~}ьuh*EvVl̽Pt8uڲmj`SىufÇ;:Gٮ枡8ݬ;hvjc@,MuT N3@ 2ո|ZPBDoMf! y+"^UKA!XNOʯќ^;cl 2L50,)`_FFvˆ捙ll:y3ۤ1,b s^c7X\dm.Yy鲢&e2wp4wZ-Q fE$)HxYu\URepiٲ!M]n%I%I@$SQʹLr)/Uh~|! p*/<H-m\0REȁhvS':Hmt~|tW7ͥOOΒ%mmSݳb3#XDLTI21$jϹGf|.R{fn5w rj `i83p՜K[?97^mIoj"UDhr4f#B%m g|sA^vެ1, ax{!bBq{ȚCY 6ڵZ%];fܝt+frlCb_@ޫt16F2*-7}'mM$ZuRE\1l*TKnPL1&>/[dZ[]5/X1'ܟV^֜zS\DR`?1 aI {:ѫ-S:Q^m%!%cbb`C5UЎNln;vF;T @/ i a( Fn5+t:1~*\ӿokew÷䵫4լz1U4Z]+P@KW'Oz-u{G-RUm2>wG'ݻ޻*|=v;#^vKKR6l;G@=2 \XrXJ,Nv^z%_wO?r@1{ɭjMrS_5僀2w<}^%տIUEʠ؞6{v+\Dd?g;%[k[-g}滽b//蒑 %v9*UɼE6Tc8, [V&P)&?6_l[XK2٤L&A|LaOTjcMgN4_5*$B$$JFS]SnVضI NKbv0"i,( 1rƖmRiS|sґY=r%|fD>$tbi79-S`NU!uɠ@,xe FKΏ^\%T@T6V[6f!4S J}Ľt s6lzt+R,̌J@PHC^I29%RÝH|_J,rۥAZ~@7")ZU97{"xAk{Ez:] ֻWn}e(i!$_8+z= TPcR&aZ^nB#=kYt䮑Sp疯3&d]in:>wOv- b22lzW]ӴYjжl%AL D0DDB'w7n_хT,Dg^۩ϴ԰z=OcJ恫|-z|CHi{6(yMV7mOD-RG`8 𫟱b[ٕmri좲CP P- jvX^:,`O+]֍ Ik>hV5踝VgJ 8FBL-mSL9 d ܒ^f9.*jdwэݣG(ن]㈿hcPRc:qj-9!>Ϥ 6`2]5Q:e[oq=>O%;l/ bC֐uC01/WumU ZUBpTao~I?t0|쥖1sEhg%IIH}lMƴ3wyOm区d( 9.J3XP5.'j:Y$;g0Pd djTYn1FMH>JhOŠStj,viYbc t%QV*bhB/}vϾ.?s_e3IVuTӯebܜR73wڞ$`+89 7IbC,LMToٰȪv{r?Wж+*;o޵y͓NԘ%EyےNỾC kdw]Эkӏ6hx]N-v?;c/NyB!CT9K .u\Aޫ{L> C1i#Oz=ȴH{O쀀'&OΛ}UaW<tz! Saļ+J"iUxVt=ôiCUb{6sBeGfdכtLd,%ݘLghmsLP,=jxBpDBe˸o~BQiU͌1ޔM/'lMqbUmUռfb\<6֧$kٞO^?k'FqJ9=?JfU}NN_٘.p$sY"zO&Qnm6̠渰 C ]Uڿ**QA~w.j]mwn; 0bj0!u.+\`5фEggT 䩊ot{ܓ_e3z6.+L L ]+Pu|/nQ@1NOSKk>@Hl+e1"(pVvBYY({5$h(K(w;v>"t,Ŝot\"SH5[4l[)ޚ:XN`n7H$YJڮQfi5^W祬! 򮪊lھCQOK̺Gplה׶Ir t5HuS<zW%מ{bΈR@Clҽ zbu훽j IDAT'YknݐVwToaݞDZW8J @wҿ8*.niBYiBiBԐaM+*}c/O;z*"""1F}ܼ,p.`'cމj21fͅ:ٮy(pSWtu0)M9%^ eNԉ H~c~Gf~OQQ$* NnaniZ@d):nHܠ*j,v'{~j~uyGJP[&$./_ta]/+r"R9Q՚>b?o,Š<2~=C5#&C;__hMK\a3575 0H%E@p-͡1=ߊ)ɢlO|TS#ǽ6bkӼelGiʹ Bv5j<] Y_?Ȭ50C8>$&GGWGPV{cիWdW-%l}-MDKMWD%6:''+N%⩜N{{vn_/_l[~5i 6^o>~zĩAKT^ yE96` X]hQ/"ύߙ+Ó[3ݥA +:S{tiHN[۪͞!prQrq+,,v50 Um{ꥺeD5RD Wfwxy\*a2}d'8W0[hm$LáeULfke!2X$yLϘVik[3/ɤzFӁ.a҅KAE*N4+,YNj M :I_SUBGdivm zý\[nDk$%BU11V4̧E>{bX3[-Ç]9 Uds? }9~a[Vd>H/~&ӿ̪w޷Of:6ol዗P٦Do|<{# #!4DD5wi>PjOu>+(zO^ܛ!j\qǽ/(^ۺ_g[vpZ * U索(eQy)I;O/쫂21{1Լr`It)/l$Iʕǯ[q <9;bvݛYhQ`'ܿjyvϘp:R8J}2m A\btcv| `23|x{6n|&.K[Be( T׊/B,,Vwriq;3[ CIX[vF *I?(?{iZekuO6+B괂s kScc !T!Zd6"L1l4O|{ v극,RV `b!EY&-m&SR?v4tsQ{`v8ANq41lǝ(I a%޺.X $ B/c B;Rg[wseN_1@Ifj,zz'-څ^sfIU\?7ծɺ*4 'z'{[wJBu}zbCWKU-!LΟ5a4UϤg)g7` Jc] <:;TE4?EnOvzǝ +KkL%t6kZvNv0M[c:'.2]|i^Jf1M7Vy©a R.GJZOTz_'}R(?sE, Ds@Q+ޗ̖5mv,[֝9_%IoeY'rUw#7tŴR6*i4#R{Ld,DxƬGLJ &5X@TyvF;ֽ~zNlm϶H,d**iZԽBmf"!IY!(fehN[ gzK)K4|e"|Wx uI i.hE+G8e Uv.#w+DI0 D{2*Ze.3툷/TYĄy8E*E"sqD?1V1i3'FDeU-X,JQHMe L)Ϫ0y@"mY&#{uwvPjOhڃM.6` ~|`$u\nkLX:m< T6 `lؑc13 ?ՙ}f?;K[?ٹ?V[][x iԦ}[KNɋ'-ڈ"+Lhoֹ=e1 TEOel˄ƽ`L?=٤|VAfDȾLe2DgDB@$O"GvJMUY?k L@n|N~v哞)VU2yHqh&b$ƚ:cH $Oz{.aVnHo|% i@ۭ s*8/;O +2:dЊ$qicu}wZD6=\νql*uat+F-gv!Xڏru;/Z؉(A:1+<#p ˬKWyqntD%w!e)M@8iS3LWaS$^P Qw Zdvw3s~tJP2m$M6h$"P:jFHXޚ}A?7Iۦz"}CAh.u DdW#{w[?ӏ峋;VIuwUq{>ȖWYK#}UlPӅL]\|uGiwt]7,ܒ掁!0BptvwWUO~& d(Ӟy~e:N!GM7ܐeQ88d򡪋OPKBS5 UN&%U*VNE)&f%Qu{d%E` ^gR/wWnL¡H'lM\0"N#B!ؘh]LVH"5 פḰH 5w&w{m{Ҙ7N.}әBSL`XڎхP~s[h?rƅUI"-Ie-NT PI23ڦW- S(3>͑H2 4Xw5S H[ܚ6Ћ`~qgx   @1R&b[&>11QCt檓DԓR B xb[xw qjj|288?88^]#k.[ӭi#[6}uwDNz voX߰lܼlcϹx,ҥk5)[-"/[X߭iǹp >4iw땋ܫsr!<`^Hr)bCw{Z'.]jVbMs&IV6jHD1Qlb%&ex2J5"LT!Qޒܝ}驏">m_q#/_>wxSmr`sf!i2cM{-ɮ+w|\EQ%J-Ԇ0lؖrÖh?W?0lh@nX YՂ(LbCfoSL眽Dw_fDR 7/n܈gF $,!(d*p"ZHfFi͉H-l>ֺGû;A 3QsbM&ڭ >/zBO{S׶8^19x?kmy&6?čq]Q__IDAfɥ@dC؆5Yy 0;w&0H2X ]Gm╪"RJ)"d.U;Se9ЬKG&Wv%9.HV^igoZ?>1.?ls.lJ 3T‚jXUH-roM'H ?[pN'Ƌ (ax8N`K|ϐ*-)#Bg/[ u ޿DnNn2+>Hx؟~vJ@E[Y/Jue>Lvc +OOt]{\<:ce y2!%0>ӻOq=n?/knm}3zkARqYCv ihJajdAvE[kqЖ!!"!"VAd:4L+xYmߐKS:abkצ MS )XXɳ Ako\0upHQj%st\zԊuzp=YQLBq0S*Z]fQvA)-DB-0H#l2{e6urȋnR33DՀ3q[bl2ouO&&U*LNJ̌E۰ x{z_|_=j`Z ںanQS(_}QnڛHź  Gtũt})(QΓsn\yk(Y3Z~iX&w3rM%0 HL 2dӽR14u ^= 3 @0H:HXPIKx4V\3wd89$ ܚ^*c݈5_Jz累Wd>"dRA^w= +\ħto>oSЃK}1+{"[a Ͻ;:y}O[cjَ%o? J:5.AekA494Q JSvBEkưJjB0ZinJ1AH k:p4Ə>El ]c/%Ϟ|3gWL $Pi%M|H0EA1q#FZqr%AB)ɨ{2J 3l2ݯ:HqEGi`<ſYP9VJim'Nt> +s9_Yc5N[ֈ߃p< -M\ƴ!Ja>9 E D!u d1R["XA@#u'?TռsA1Maa]bX"\!T\ _!B}W;Hc^a3A`[40}HUǏd[ar<#,M=8HPW>NZaV|j (ekp!;ӻͪL͗ ŋ<~=|ve߯sn]:ֺ<_b scSԩ\Iz%/#&tM[SO xgy5fe ҠhB%0)Cf&M'{{KOmvkp$^w fa +.!rH# jAXNGYߞftHӳx:=9sh*HN;*PKEDj͢U]G5Zd.)h3]7f>#Sl`] ~ #o̷Nw[37κ[ ?itKB]U+I:<ńj\BT T@BJDhF ZZWqiImCgc7,f[Szsv0ht)|AR3tHgL>;\s9o?(o-#=,(^T"thIDh ?|LC6jˢ-]z5{.v!K1~~mܩ[wwMla^57 ۑw-܅!A(7oT5@i( )4p q̎Q.دQrM-o{w'K٦rZr-BP RML)/F$)%*EA벴w=S_~QѯR:*&, ;DnoDn D3Tёy辙ӽ3);S7ZNcst/<22Ip$ϣWQ]@慨O]Û8XO7= S1DPiDŽt~|u!X۪kA~x Vx*}LW y6!YZ{p&џ@ok9 S o97%bMA)gBmL&e{lI?;&uoӣp) [L)t*uS=c*tUͽ)"h (QҨ Heh3?V`P$pH:džEG^` :y=RS IDATl,ׇs>> J񗊗UO4#rn&btVXgO醹nrkq6l\~][_slo`}A o~Tdx_Y׷q88rrȵibm֚bLd)\5\;ݛG;4pj}c֦a"kٻbZ۠( AbHkoZ5D,zٚ'r*,rsQJ-\Da*%XSRPh[#R3Q:uХ6Kewtn]z۷orYtu e6oaƿ/Ɖ|gC=ɚ@z2ls3uͩpW5Qm*{'-SƎ. cHT b/ ]83[K"U͓z331!{5 RB0Nyɳt;[돷f;G{S.Tީe/aȽ@ "q>ﹲ@G+!RQ'" &0X11k(8 Ci L3Ɯ*8-pȠ`JC 6pWS\_,^Y0Az=}hAs{ m'VXa>M+;i:62].pwZ{a+|3)6]d)ӃQߣ. [k捞=882k F?;%X@ CdEvw.'tul'e7zq;'XἬVbEs<dBf::$Uf Ǔ`6;.IJZ+USq4WY <٬6: 1}VEv`tM-3ϭ+`[St Z A{4q7Svp>y_JخreO;?#IOw74փZaX+6҅6P/tVXC2].(=:;kWP)@a)>7ï37n:[à^_D:+νCy=v]Ke_3SٳKs F 60ԁ`l*:ֈBWOo]O@]gvOlޤJ+CEKpf ]RצR"M7q1:rzΤ{ܜHɈ9ZJzj ;Yϯ~}u QuҦi@]9"!qB {_sF .N8&aМQ0*AF0)pE0%PFlzRܫ io>?E+$ Lvֽy_.~FV Cf 7oڸGC w%FC)G;ܜwJ.CS\!N:0)!MѤX4˟kgh>ܻp=]Lws5sXT]BX7 &t<V)ĊRZs;WuQLӣ`2?ukLQ*֦B+Mr&@,aڞMjt /{DEơ "2u270!5!5!InV N6ߛ %L\spDf(2 {BRF}ʕF5|~jKV7.QQ1gQx~wN8rs'wE*i9ajHE<ŬPh "/n7Iy73/"\99Y*6TYRtN{{Oݿg;GR8xCo8Nq5RI!q-NdJk;'DxrΉU:wyU+"eHQyԂBP3::d3A4AIs<{yS,G>~W7+^x_?(K/^..,c~v +H|zL/zY&9P\QK=2! zX{xF(X^5O]y{fH-X˨߁m!,M{n_ E%H-fAܮʆ$Qږb(ަS=v镧2F6Ke!## D j JA)҆RAS |,gy~6NaYDDkXItcׂ1R41fȅÇ·0wj`5Ȧ545`ҜLޖp™9"O@>@Ggdj$ˠPfHu"5& Ȗa_k/U'ß{ sӧBgMKb " #ڀ x"H+#Wi3Jl6P;@ 9(*"V%XoL]Ue]Y>GO?msG[oq2txֹw8R i{d"ֶkL録d]QvOK;B1seB͍#%0^ ~1`mё z? 2M P4ʻr'>8ggC(^-,?I;ZaVx4>ՌLypi1 EH}v,cwkܛ㎺m>cL?Q"&ڦkH{*S:)7ZUK_P+(lrDRc-(}LFD ,ڏEZ)·뺬(fh6;qZįoL + F)\ DSSX4cL}ZםfVݼZt&w*rpܢ\$+ޅ(6r|}Ww'??gjNOPV{ _W^Q|H@ Q`Ť}У!c׭~=κ~DN)Mص̥]7N}X|Q2 Q2Εu]Tռ,睢?L{,g98{3oz`Biٙ,8:Ng[[w8ˎ̽l:(&sܕ HE tG35~{FR/, 6u06Ȃg 8#i. L6d_<%G,]?K/R(wʒB7Zꮰ +,2Cg- 0ămWT, p_) H=HiJs޽~e޺zko8$j t)I9K}1e!黎;JA'JAŗ&tEZK"ղUh‰!6ZL/:/IfG|~}Eͬ͢\"/r{L#J y7\!,DE iC{p`Lm-m'[i6IaٙI*IsAs4&o߻;+m%mx_wʳ umL2sN U5sHPJf AkKjJJ犪;0{g7R榿q֛My3wi-H R kgW|{x=콏S|VhO 5|d@@C\H4^yƓ\,oʢG;z9پHj%…DjdVSeA^a$`V^ſoE6! X6MmyJw9ޫвn죂xC6%vfNtQw2Vt#ҴO>zKq$,)n|m 4,$ Z)%*^[J) V_h8.9:YQ8:'RƤf$Z[:,4}.!bi]ԲEז/k{S$qZBAHOVgeLĄLf.)I "O!onMt?|?{rx/ӽ<φk~;2z[ݙ/c8Ŕi362ٌ/i.LĪ"\_Iژ*I8(Web}R蔤Jf*("R^y_;W9Wutދ.63&gM 5 CR$H՗ 5N҂֕|{H>Vo>[ӝjdOXS-9U1][)$L@FoUf-J 0EP uw?\Мu1.OU_vG{/f}\\B {U~ +t&an q)JK^1aJNIjvRMBpΕUUtc{W3}t-S"B2JuVdک;}jwzk땝3ԁ0( n"# 7..cZN`T#յwOS䥽>MKh^C.-קsS'WZvVR4W<7>QZaV |z?3Ny!z0=:): t8 c%zT zة.o& ha-103' ߀yOn{^Pc;]WrTaΨ,Tmalellƶ܀(ήܛ?`sR&nƜ,s|rZw^ECCc(,57q[,e9Ox<7!84%I7I*e܅S˰Zph~; ж綡q#DevDĘ}h"фH"Q:| @A;n+펻KBhvU*,5CJz^pPޡq8鹚O\(&߽w;{;Es&f=PP&R+"("؁qYHfSvrPPE HmH`̎مBCCpe E>0 +do;\sSCp <}V@P䙮>}%s00XӍELex2 ^AK#t20c^/ޑd{a~Ř|gsAp]*xVXa>6˷ܳ r͍4i0c=4gayHR'YO~n3U/D پ$cw/O"۸X"whe0)L ;C M:0]۸:|t|')P[ZDfy&Pהn]MYIX1ALKVK {g]{A}߇t:ʙ~eZ 1bs&U9?1E|sn_Ju'si 8V_/L _D 8#?1DXiRi[q;^IК-, <ͷVX _zYBn`A@ȌGPC$=X18'?*.uU;g4z AQQJ|%0xkKkiXMTJ0JYf.$OWpǬY$ ͠f"3`aӏʁN 9ʹ Jk@2vZ4LJJN.'\ew&ƽ|/MDL&&1ۆr I%e0.1k:)J;+촰NʼnEM1݇. _yPg߮*|or|yp_XQ!VHش*̬h) <  B&kj%V]}/4Ż$2V(J[0)A%߳\ `N@ڜk? gzni$u@MxnBfP+|t#$CG"@U9M& QC;R $^jSPsSmQљhcRBu]u1:z \!"L f!w 48ıYDXu;/# ; ^Jy-Ĵ./B;/@Ktv9At]EC47i gFl%K+V`\ΙʛٲEig:" ᏟqY}@hao~|jSNW/Qo.Wxhn~g2 p^פI%@TҋsD1IRec H")8# "jBDPX1@PnĦ\BEBսcι!$x9<]aFWvӭ{7y޿|0{B:x&L цoN3W}kvٍog?cL0h#a_HJ[#kDLFsSR?JR-K ?OrVXa>;ꅈw.om7jZVO@< A!1`+ ΁̞SP JKci (Hdf27j"xBGvY%!#k8IBstSֺ:#kZ4d(Čv@`]"?8WgU5 e9kM$zH"7,ۗ$ו}NDd]FIeD@I3Ƕ =h-//?~_?,ߖe{dy,prHt[_/ü8gU`_pVB:++2!k+΄2-&QVF3u@'1GW.:1FiXOօ*Lis庌r;IcU]5E_-jbW󮚧T˪o? 3lvZVN~V~Çr㶨1y 5 2Az"vEV%۰A~Ը_e~V[-%./[k{wq){a'`gǀ%KEqykxxgf5 Vf>\0Zƫoi&;ϜS %Aߡ<>=I^Z)/NStrӖgZںIݢϳ7 xxuQ8X``+v62 2^U%R4 JoTIpl]9I]kcӄ2-\"smL4|M;eѹCњAr 9$ }^BʡϱϡjYMԒXXᒽ}Iieewmm,8]7ڵ~uK;|<)PfL!XPJTi3Ftܠ甁ՂłQ421-AdPCID$6+ ioՆfS1@"\6,lt@V+g6,̪c8t3]n]--i \~k#̪oarg[G7-!֐ m;<lb2B0+oP0~\Nѐ@ݛ׀w?8+_NA(PMbYfe#akk|[[CkWa|lCy?a+ `_E/QTصϦS@F"VF:`kՈp6ұ%Sf#c"[XcfSQ}9Uf̱ \4n`ā<28 N9vE~d# %tcZx@0*6T SDڤqYReI8+'$ EHGSI8AƪDzP&lߠyc<ˏKj~o?vswg>^M+8ww{7mV>BUW2tNh[]S+gL463Rc5HG4іŬ8TsҔE`=5xuh&J&,TeA g@v\|-6,|-nM۾ nflGD@-b77@dU4pk0tZM-?a1ouk?^{ҝ̻nv8Xǹ'|T.J80BU"8c8#ؚaػPWQG{x~pzn>7|1[5vzDx֘ˉbkl sAو@d4׈3#3R36@L jj**5J6"sBq&P `ۊGqMtno0WU])Q,2 6Z-KP#FJ <4 RJ+HIE*ș۬d*e-rzDt|u8MͣoofkKym]ؿ߼^fwowv9Gׁ6C!ىTQNjEjӮxȋ@Afû9X#\'EUXTF1BE9P+=]Ǹ⼊hآZ MI5BDNX(BX -,fJ 55]nҡCF^ goٰK=DQ @%QV*75`|%3hqh[n'@ ́Aq![ "9 D(H55192w/O\ o&/8EሯWγ%!8pETQ/j@lLB {CT)K51QS5 T  L%!8V&baVHWaT[=0N8qGE+WALZ6>~#` %2($S1B<-"ǢM8S" %5QU54z2,@lQ5EKQKJ1RKhz4ffj31>#Ow|&>pj۾Ԏ.s1 +b90@Y.Ċl\ieZF[V@x+"vCՠ& PMYf!0fFuTKT-r*XDH e`8&A[tOW-%CA\~ ݗu E5c8 '5c\ ~p 'Ħ +8н q3>rJ_QWlà<%`6^8 v=ҹ^, 5wQbe;}HPmYGj CC9C`(h 2HU@$ S1”MEPd # L`xxJicsvJJYB?-Kj,ZƺT2F;.0E$siE #R Q8J55ja05SQjIerh/3u z#tFK*X{%TU.o֏uv/^EeKб+@wt;C L)ڡ>8/C>I,"e/q%cqj{ꂓuRg[L tM qqm=>\q8;ӝ.o|X`ZX~%րY qo-|SG4lLM6ō$h*ǟP2=B!6L4h&JP.`&jtR 3#Jp 1 SW8,=M9`(w ʊU1 ^-\daQI!#0ܒJtJIDc͑5f(b$Z] 2B!,3m]|QKGe͉ }|?~)_7cn;kUMofMyJe谝f}gPfpFn/A MkVRԪt!HIDFj¦48x@ES.69穫?GO0C @nx^ճn6' gP[@]>VF`YzAEXZƺAJ=ֲA# Y"KũTbitN_cF 㴂v-\\nm+b,YYo2#e)gc PclkblB5ƜC/ϡ ْXUYmy,u7u@,7ҵ-5_ Wrү>_R~#g[c 熨Q ؒ`z٬ J==DIDATtPlMn-(L |LPyW1> Uf͛EA\y L@^B>vI -*:FډSҢ9}sΞ=NIqYh́\:sx%:;.ьR*@o? zjk5T83l}#}F=su;?1qfӫͬi[ o~k[hT-UfcbpT'ƊfM11# }:UuHUJb.O0.M2@w*ŏ;7VcU}V A#EJ މǭc,mm4Uf,T =UȂ$RDz%yl.~t<;xgqf#zj0l"ZgmzmJ@Z uÉ~. gPo٠:ENf %R0)i_1kEXTbj D:B:B>!ul5|fh3XWN6& >#g~s/j9 ~b >qxxW\y/<[Ӭ`8u]_A+Bه[glp0< -T K16H<3H?8B3aKP:r!6L<Ø ̂(g Y#Sb ƪ&Àc*j}3qWcSVn ]8\fAs(A$d,,beY 2[H 1c㬮guV׳PGԖ>U̽ 'Yå/\A|^o\/_}ݫW}v{o]}'DnINʨ&& +q=$C&F(ZTbA6v^OU´6q 8ߓa?m:MoՙKoÅ!_e;o?eyqԲ [):eCllq墫CA! ⹧pTK`ׁ`?u~1?ǿ'T_@hPG4Oqtn뮯 wk`g<#8{;f(g38VOעBB`P9B(OS@0U%RcڈN]@3ܚSd*Ǜg'WVV\֛M\}+ Yi\-Mh2a$zT"}GWnIׯYUU,TLD0SSA$>[ez]&Y˞y/v"i ]HU5!D@̄MF;Ѐ*`S& t!/Z2zS \SL9R , !iP [ nS GifݲFԀbz׺yrC!ShϢ416G~ qܭwqÞ.C7/,b\:ރtgg,3 j]BcqQ쮍Nu=y|%"`2{W2X^9LQEStQ0H%!f,J LD A !*u!UA"*Љ TV%TVWX*gel㞐BZVՌLe@}@"˝ uEKkTRi] iedfݿC7B]Oi0[Tx:Zg5ɪAdi}26P#n@j8C9P  Vꤥ-Hv`h'GhYքG^?:.^_/,Ktaṁ,㐙Wx{b1>ywH1HKOtn#gEXЙTa'n .f.I$ԣm8좝Ez6zOZ&#Q.0hYU*Qcu!U!W,!P+pu`(05̸Sނ[oXdūp,sM%XcJ}>R>\}^++ Abc/!+Y,.kuB_qmy;O*P_i[Ɉji5Ils!Ct j̪{A" }F -E:Q^x_/wv baQx lVX hT(q 'D)@@` .,Ȕe4T@{HvrxF(P@}9r$<,zuT4s@*-'3yk¢5CZiL)X#,01[ê)+AƚSE +R&k{]v:7ls7OǶ,03eb3f`6J51b([CtVBt=Taݘ"*e0G s $L0" Pe*Ҥ@Bn} v.&jUUo{jzwl>;fѕs`yzf%9>*.M7xjAPƣ wȂG;_?OXq>f|•nJgv4wkXaT _GW 21 f!g< \2#+1F2n\y)2w@5 !EFJs e2BV "|u88tL 8t"pC]ʐ SXWdF shBBDdP5bi(Oh{[tXtr=.nq/x_u= &a0yVմ_=n_{ŰOwwwNޖ]!ٯPEP&V-q ;NGMsUR#CK`)Aet "f3TB8GʑWbR+2w˵c["F!klBY9 g,L]4(טh+r4ƺ>S͢ǢǢVd?~׏}_yZgƑ((AADl `F0PÛG̑Zr%7AHB4% 8aZޜ27ir;떸Ct۶vCE6ϟ]7l_u|Ds9ƕ‹z)2w QBŏ[ e3HA)8LՀaE!elXSAUr)iJ(ݤDm@RE>s IE^+$kqa q nqmAڑ߶??PWoX Zu wH4 X ֭5h?R#-z˚nqmc52/lqu8s{\~H)̀UdeHel䎋h:bY,ΑZ\B:wщ;}f0 :ffӠWMȄ,baH"KلEbڱW=7awߒ_׾ZSBeD*hD@ gpf[N}Hގsy|kPr!(Vfو$PO005h#_bmf7c3 vF1]G,< ^ŵ/._g߉/ :+t?$vPlL;j:j"pHr°>\ak;6CҷĤ<~7J  h)5-%CY5r&jY-I&;f?yy3`TIFU4 "@h4s: wH]-r<qB1;0$;2t[Mn8|pPs0 A]"/lt;Tb++ŞR9N7x+vtN.]PƼFaeZEjC$fCڐK𣗟{ghLʴI%踦Ơa,PDzql6= =5!!fun\X5767j;8ø6ehvjHi4A%:wS{!rAC0=s>N2w!3aF˂jI9"9^$L$K_z@<`.|?x4CY1Yd#4zfҭ;jm"r#@2XdۄW0?3foߵq瓈+d3"ѱn&CP[9pq6%!GDFYwCWH ̴nsN9w)" ńk`Ufu\|$4"*qkؘFL9+"N5@4U)ҶJK(+7k:wqOA߁uڀPak~eUQˠw)sC$`dbA4f\8U f}IO qSjnѶ)f QEZ/\|񝫗/]y|yk Y DD(5P"nB|xdSX # Hr;Ƿ>ŗڻ?8'~hH)N !&f  2qY>2UWX 0iLcJlf"ťJz's<{7wV3ęZc%j 2HL* Bp8n+XChNDw.s mnÕTXq]q!11$ѸO-CILF@D(IC%eF "SajFjLP a*,ͣeVJѸm{č:GP`<"gwA3&MQ"!̙*V*#94E4!LSC?p2ٍܺS:|gO3OLe0E1* q:MjŴ`DEڲ*TM&Тq4`PHr_W2w_o|Ryf1$!@Jc4U!Ig#-}1T8s[\~0n#٭bG ЅVo@5C(V ,&i.WMA "6AbՔf4t5Uy @^Qc YLCt{S78 c|Dzh)JZ356P UX#%W*) t Iy< 5%܌8w/IE^jMb 6#GThGa {q X_X$eV%Te C;@#/H6WJGfQ_p =ǹcpw{{ۀ8s`"EpywP]=qOt? w"vAVƺ6$qTI`+GhxN9uKya+{WO8fһ S'CI$bו8έ `GpPY6@!wZLshhd}w⋗]:G5aһ*P'h`Mwܥ[rJbFn9P`[(c2d-A͢V{ 8.^00ַ8I;~[NML>a20RCJ1nZFK8dzebX8dfhKD Hsǹ-tջ0hU ,CLQbDdɹ,8ǛPZӆ2qp{7UOꁍ7A\CL-^ك+CI`<0 5,&A3$[8+ݻ)c. 6Þ1]:'CxN=p掦^Gnuyo\CNI^ac&E<{Kq/;;;4.AZ0Y&v]: tﶸ+]ޒ8VNv/8w+]q~nqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq.|  Root Entry\Lc256_b476b7dbe98662b2*66NI.6NI.JFIFC  !"$"$C" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?PΠtIK3P;h[&-KK35f$a 1 s{<z[CU8ɻlqf`ZÞ V",@@ov4&&Roh"-eءr\WbZ=Ě|;xkWljNy*_`x3KEGLJl6`;W%|3u ԫJ98׊#ZswIѵ ,ahCz_FP//&_3WnZHF?TeAa2rqI^cNjn0iv4ҷ |5}c_Xl,=פ71xv]%[k Ja.{צZޥX6Mrs +s?,šZٳ䐑146&GA𭯇.|;j@̐ޣ=>ۋ>%`V }bƷkeiXg*Y>N{~u&Awc 7 CqxMaE9MA=LW[;s{1SiU`$?Cּg}OA 77C^x;N7S-kS5뚏(r-1l|4o 7Ru^B|?F]ї~x7/!Ou5uh4>۲&CW>n?|pMz7ɨiOk-e:eYD8e 5MVvi$wUk/Ϧ6)VuFMŞ<3 ҵ$ c;`ް+94Y5="c$ܤyd2Gׁx_7xMGo}o3"Z42rnћ{[]ogXgkqypI&yuZ&Uͻ ^*IN?UDRvTW{i]ϲ>}5})e ݒE]ԧ8>Z-I63N9 Κ^TrQ=AkJIʔg{U'ߧ3guU_蕯N1xW%kөQ@Q@ڇVu?~}&9P:G'AjqdA7̌90ss^&ZԒKU5 MglRV{'-$8-]<䃻gbBӠ6#UiuVv89H$TeQ +i=cm1Ԑ}W;gMxo6KmE nQׅQrn.~G: [+|&oꐈƬJvG MmCA?Xx.H*@':W__i6{{hgCz+`>\ xº6ׇ5;X͙"Ȑ>e_ aAq^N"V:I%&MfA)\+Ckۣ) |Q+o뗖Ǘ3/_|KD<1.1}R;V;@$x2@2-Ɵ'YeEho9 ijU@k&Jӭ<b+;̱4f!~W~]Q-e6'ʰ s zfHK닍\jB {inVӪ}oc>Tf! tϩ~ Qs;<Ii }=*6,wWShA=exS5[%O[iqUPgp'z;m$mtm|*0xj%DʓC7N'>ìX0\a&fs`{E6.,3,rșaO{Zδ m_z4c aQOp;{יC Rj6WG&8Su-+2 Y"QAoG ''sT5+;VڌJl=zj|Ay 3gI4oCDoa<>s `J𔰪sXi+&W;~SB[<5o>k uMn$o?|ڹ~b1+I.MĮRaPs ~Z8WƗ+ɬ'TrSSf3m(ٲ*^. d;s{^R<d'#N720pE'ԕʛm?S?=[3G0ȑn/`P.~|Ež_ih%Ԭ{'NkuxoLJFEufv7/z YMH`@{QR't|_#%/oxHA ;  kSƧ_p$m ;U|%oaxDdO޻kZ] ԮZ$۶j#56c>)GZkd &l}U_ Mj?*fǰ}Mtku%6 H W]]q+yܚf'C^ߋ/ﳈC,Юq߭znuBr3)y%Y#nf-+#P1 =k'l-rr 8?u~"}|-b6\M]:%(14WG^i!偿gAntq+fG$Bʰ<,v(x%>R_. : q]`ڧuSSԌ[P,pgS R拎5uq>ߡv-lK}&K"$AŒ[' OZśB𷄦Eѵ_j]lg`)1~4uQ׵;H.vqf*º?IoaG 3}Y$o!Z+rU:>onuoV)e]꾭{qۥ,R$*o%ir6Jqr\jbY-9#ŊɎH8l|R961v7:̠LzjsRqkєSRV9OϠ#meݰaiEi7)gN0,e99]ׂi^*גtפO!}A,rk薂'K1l1O$r}kT$㚍(҄idq}& smgq hdS1^p: s4|?ƛ} I6-춠2?|w\WM-{L4W#@C/?t\ljxI{#]Iã~Xj剩*tZӗzuFU*K_3õ[ᮣurqXuA׳nԬ$76 ?^eKcm͔eLQJ>n)Sh;oxCnTW=U$45酑8\mN1 EuiY٣|e~u~uAh p?:_0҂Z[+~'sz5ѯ0AQ̣pĞks]I `K U##O hy$78_ BH-$}=꼒E33ei[?&mK&[`ݍՍIo:}#b?[~2/$کQZ{{'_[MGԤ $ ,z 㫫۲1.'ϯ񑍾DC-rw3g!u JI[>KycrZmִ堺N~OySМ^WEg:Qӳ;F&۔ ^pW!-^\k,;#"@ ~WO-ah /#C}=x~$Ki$?ұ]1;m)#n,WNiRңOo/EϮ6sPa.o-,zχ.fpdW^&-2[R58bZ78_=xC5Y,n9AWtkʭJUiu]?feQEfE?Oyqms{-a !qJi73W ﯬ_ŦiCQN 8< X46/ol@EO28`[6vzl3㐽+֣ZvmM|")e|ܐ$}Ό1[x|] n48`b62K8Ԏ#QjJc.dg7>[R%ErALH#'$q @_,tv >'P@|îO{`Їm?rfς?!eY4~ofς?!evs+w'٠'l#B\?>Їm?rcofς?!evs+w'٠ ivni[KXRcYE(V"{GKh=[4l}?wf տQܟ&?xԟf6}lm@VuSi-1kiHYOei??zJPդiNh;3:vZ;G{ݷ#zV-ֻo|@uI_ V?C0T#"i,G-\܃}z6Liip=Ӥue[,Ip#nGͣ1ֺ7VbQX`%?bAnoA+>iW}!xI6ȓW~t 㑛jR>U]#-ݢR)1-<:-osR qu0pxu=>G_hua.lmux UpH/>}rKd ӣIdq  í[Ж f1۔r)N7vQ_kL"(mpFpK1H 49nl![#Eck0+3K{WfD%42PÅʲ2*@{u K弋v8 ~apFy[}Bņ:t]<ȉ#P7u ۺ3jh<9O+2bW\A+ug[S}%-Dr}LVr6 czcXJ"oep<^(UKHt})h(5Sxk٫%&Tm<۲0ʸe[Wk:|9 xV{K-N3:$nb/ [ɺ L23zg֊.>U<(c^CDz0cԣXpO_ֲ&m6)$ռ_̜<ڑd$6_F^%y  OaW&mWm3K+qd9@Cp:w8m>1&?;d7QּWRro*W bt`bOܷkG"/@%:6\~闐GwVQIma{h^ieUcvDS.>~V鯧烜,2rr>c xr]A, _)Qwܦ'ˌ?#XL#;YA蚯a6ʪibrCa>.:_ݨ4U[q3JIZF-3C#Re#[&7s&v搆͖I-g6 |Wr󑞜ԎHIu˵8'܁}+S۽K-{| Koɒ^8=DrgO\I~ux[kC.'H 1ɐn:$ Ċ1<ѬSv}{c\II'Od'p;# -ga=Fy\2TWdewYxd!JluԤdMG(~Ϲ`]c/RjG/B9R1hvIC3x)?u.U]Y\*y$qߠ=2K&/ ,e6K%21q< | ḺV'oAJ؇LV0|PG بdw$Z 'WI6]5ęPv%sc89蜜S'S75 j0Z r3uΝͻe$o>tk]xH1iL"DX#P@*:|t~$sXx66)1>RG6mOV-UI Dw1/vM,p:+1rM'ViKaxQyG*/Cӎ&}xecn] ɠ :dbX/A\c1\Y8,ۈtaV?%TV*bb` <6[hZ:PD[r@p0=3ƝwjW^7,i4$tz@Z\]_GwBFXw4ËkŐL' xpJ8 GcƳ2|jGvG^:!ɢSV]܃NP忊|7q4P` ~, ТXЈʼ_3B}5>WeHDxSI4|CX6\Kg(9'֥`;"a{¢h`P@=:tu$vW&K]ܓܓZ4Tק{YdӔnUUGS^TpXc&y+{g]G?" ɼMGMdMYYGU)@Vgx$@XXUx 9B+#@:q,x'M'fs ݳ|`hyuV7J:=m u1XMރ#:Bvy;."y;j]CQ$q5ݬ)2m*$ +8P Ǟ` A,wjg,V&}J Y/A# 4wUс1[ }* _ ]kʶ|0O*YzSRzeRS>I3 (8x犹iO ݅6Iq$)Ċ?|3vh@ b} O:Rw.q$8w)s>ķ2w }cxd\7 @##oѭZB/1ml߶k[?"P"rpP?Agj8v'08"+ĺE&W###okVCkA*H'{wc稦4j8 9~3\Y 'b; 0$k|ӛFEmT)pA/;q4ʖYm$3(/OP*Ģ+5ƊJbAjc#~Hw;vqǷ>_^ o@0܎2d1\1ɬ/t)dKPY 0@steduڭ `c18'?$M$y[3JBm"J⵼4(F(q-"O4mȻ@dz_SK[kB(V2:.Pz(Ưg o8V+K. ɯK ?}p<}p2[d3?pEՖ778oR0u0KvEqw,W{|Ė'URu:ڰu/-G޸UFU&븓NQ^^0P) r,o9=*MRԁ.mp':Q2a>cYiQc빗>80bX`EԌF)Ȍ@O[k)PDXsGPh!P`ڱH@ ;W^g~PVnjw-G0hY26lt:a8CʁF_GB;:hn]@FIp \& [[<[7cԊƠ(E']Fư4@s|m[[dy .#;eQ@3ooZ-Q2 M;>]g#'JUR]Bٱ`tĝ(.m̢S-ёsdIQE'^sj bbVGٳb;yzuKɊWuEraEPEPworkbench-1.1.1/src/Resources/HelpFiles/Open_Spec_File_Dialog/000077500000000000000000000000001255417355300242135ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Open_Spec_File_Dialog/Open_Spec.png000066400000000000000000007605551255417355300266160ustar00rootroot00000000000000PNG  IHDRCHf' ciCCPICC ProfileHPY_Das@ 20q`Beqր (5 X wEA9&Tnz_ wrDXTa8ۍ=<k7M wwшnqV4.Pq4n*g\8aЗ.B0M,1Op|HgdrH̼B@9DaƩfa?SϸeVxA(_nVjJrH)3FQoE)A ~0,dq ^' D>i }8A,oφF,-cOxY^+y@%0HA "=~M˻et l!Ԙaina?f;{ :]Y @ <"f3<@ЀPZ@ {i + (pA"HbցM `/(ap @38 .: #A8Q!%Hҁ K 9C? EAB:h TCeP%T BP?tƠ'a f0f~p(ë8Uq _o<(@(wT *6 P%*T= ՍQX4@>04].CC7;7C W 18`ؘH ǔ`1.mcvXl6 [=mcI39q\:.ww7}xK>/oƗkg) A@$ل#6 a(K#:CIMRb=D"iIKIR.tt4DH#1 vQr; BѥR)%c)['Q\Ij@4AZG%B:GD q GfLL̠̤,UB6P6UHVNNWS''wX0EբS-#. KӣiIB Z/mB^NZ>\~|9y Eץ)S;O ,xm  )*+(6(VPTJVڥԬHlT9Kr""EEN-U9ң2*RݯzIu\檖Gژ:UY]GsSza54iZ Z{:&յhӮӾCa$yUYwTOQWPZJրilp61L4,7a c퍅Uƃ&dIIɐ)tiK3mh]ff_mS̏?lfВkYnyˊbeѪꕵuA6T%6[m:lيmmb*4fycfG[tS88&;:.[a'M'SęEÅRU˕ZeJbgt3w5wwp_( ,|^3Ves5 _;~d2'b%%< 4@vGAzA~Y]|`u!Ԑ!!BBw> Q!4\y=J9J\lﲑ;Y~uVJ< Tq&qq\w> +oo,)8YSBq(߉?X8.p ^%$Jz|4y:%"!*& ;WZ_d$IV;޻zB'NҖӐ`ӓ]Psfy懬kddfo~ZZڎu6ZZ_cƼ#޹67%ouoDliS||qVǭG/wն۾ ~.]'laNN;\v+-)޽dwƞ=o{ĺ>⾌}RҖw\XvܭBb[]R=TxӏVzW6UVrXHV.n|槿+(B3y pHYs   IDATxw\GwwrtP$b7ESb`%֨?Ac{"(E (E  Fw88ߖyϽWS~Nفor۷wvvv ?J]WX'/^hhZ jHHHHHHHHBZ) _ Ga T*FRZ_ IK0(8 ZL~ZRADelu"d24 aAI&{N*mB`...<FQ(SɁ:i*_L:u~nQIHHHHHH=7VU*yKk[=C5U2l:nEQV[RR;q9l6JB&d2GGG:NRMIɿwY+Y۠R[WYSY-oTس)$+aFjEZ˧0 ~{A( B؄eeeVVV Shl|QIHZV?} /<=vG4MyyyYYJ(T_PU]]]UUVVFPLQOwqsga܆)5삌ԧQpx:u-iCCCZZڻw===6BGR_Μ9Ӳ}||AHyyÇ۷o?~\TT$ ۵k 6mgg׭[7S݅ DA}^gؐ&޹sg߾}OTD_陕emmoiiIk4W^u֭zWt/  rrr<<< c5x^(b"Vv횃W_}e4Uf.Xw3glg~M6h4s'XZYjm /^7n%?Xׯ_ܹs!H$Ovqqڄt ҵk:4'+3&{3Қ\TJ~0w5Je1Ywrt4L.˫쬬l6h4`"42L]]]-6u 7vW@UjW#J%\'0cx0ȊnoӉrjcsJ֭9r$F;{ٳg7olmm)ׇ 6ݻ4-((mE444lݺۛh|||`>qD޽\.FR-|rرۺu#@~;yd@@{}}}RRҫW.\#G~zȐ!֭3*ĉرcR_~ѣ)}>hB(++74"]vlkk`"BTTTFFƄ / ˖-:thaa;w<<<2={<==oN=z4%%1ZCi4Z||<۷ooݺuС}zEtGՁWA/6n̻B`Z8.A*)2: F0䵵666 J AX, f)4եc2pTT∊Vh!~UW@ m*++x"Fq|ڴi'Oܸqߵk׈AORJeۊK̙ӵkW"J!JMM#x` 8H *Ϭ#GM~С<<~{޿RUUu̙h)S9sfhݻwj*JP 433{aMM nX,Q5ZC\.755W%%% *j*U68GՁϟO0IZF'UP̂QFPY}ɓ]tP( ͡ ZjkKKKG;GB3 UHG.ce(|,l %ɵZ-|R_0h|>ƐjUsgk+s4.ffssSgjU)?{Ri||JT*mllJ~~~o.((0lƍϟ߽{_=qČ n͚5&MIII˗/Ξ5kָqbcc %rZ\aXCCCXXXvvŋ-ZT^^?yd#FX|y]]H$[l 1cЄr{P6l0 @dEO81vQF9r|2,, A9! ֭D"dRH(H! 4 AJHDm5$66vAAA/0ﯭ ۱c)cQ=vؘ1cƏY<1 h4VST[[[LVSSuիWrT*74޽{[ZZ޼yܼys, 5ZC RI$'O 4T%%%aaa$ٰaåK?f~#F>trYbEQQQTTTXXXee%aܹsG(&?zh+V0U|||DD~Ν0PZY@O:5((hϞ=-[DGG?###/][1+bҤI'JrԩO8pH$ z5V(֮]kkkK8Yo?jLiӦDI0&d83 PLz<{lɒ%ƍ7eF-?ޚp8\if\ B!W9 &Ha ,D x3a(EB3@ 8\>+`,py|_ ]6nRTTW_}E\(~bVWWw9ggGo઎JMM:u޽{AZJ{qܹdzfΜyEB( %%%>}e2٩S0 \bł RRRX,V||-\]]ݸqΝ; ܨu _vmzz-G~}ӧ/^lk׮]|yW^e@1 j/_?kkk ŋyyƔ+~Irrr@@!h j 6ӧ%RB'~* ]?cVYYtR++߸qcرFe֭vvvӧO?}H5p˗?~hĨoѭ[[nVzzzeeǏ1 q ʻwn۶mɒ% DUK.%IbbnU7lll0 {,))QA_W_J!3zϞ=۷^D6lTTTYI$cUWWG.--$RٳgCBBlٲ}&:\///HT]]ݣGW^( 0jZ)c]6p@777D2avС_~;w<}tff&xq<>>~ʕW^uuuݳgQW8>tܢ65ZC۷o윚hRR?1ad4q9ׄA”Rd󍖋FMVE$%%ɓ'^zy{{_rղez ðFy)//o׮YvvFy!DQ~~~Zq H׳gG˫ӧ(޸q㫯233ӭWF޿ڴi999J2''R)EדMuZZڛ7oϟ/ '9TUUZjN~,ƍr2~0P7pܹ3fc  FFFƓ'OhE"`r0&400ATY#]&ת,T*` "Z[eL١*`h,*Cc2i ԈUV !  L}dee > a0#ڵ رcгgϲ2PaP(7mEn{g6lآE"##׮] ( BAmUUUuuu]]]cc֭[taMҥ At׻C$934޿ŋh4bd] iӦuܾ}0PZZj.xs:99988444M:soիNNN:0$555::EQG/̔eee` (hE!fǎ'OLMMݻݻwqkBCCE"B -))1[㸽kZZADyJد_Ν;4 y^ޯ! kkk 5xA555nnn2 Bѱc{L&jѣ 0`ȑ b2:uYxV}ݻA&t:о}ׯ_W0 :499[n'Ζj߿ԩk׮=ÇC 1U-uf jwf_|YQQ0zhѣt:냫W9R*]k׮ .4U h[DCT*ܹs>|ؽ{wô?SDDԩSCBB&O dZ"0ٛawR[] `df\.ÐBX0QiUΝ@ӕJP( {bqRijoE4Ƥ294fPQa*DAZf_ۛDO<#߿f]\\ns{2h!H'M۽_& mmmTÇ=kUUU@ӧOC@& [{c_tzjMR.\D&,@ z3ݻwOOO cXz qgX]tADW+ZaÆoW^M;ð҈ݻw{zz>>ǛD"pammm_try>}43^A 0 {zz>}ۑPezjUAA[=K.ݼyի׬Y{1Tofʕ'aJC +J77oݺ_52B%KD#G*H$ L4q{va搙`>63q3;=MnffV  #V+ Zmjdɪ*! tA03h0Pfۛwo? ܻw~ũSfΜ V830pu33޽{[YYyxx:t(''ݻwdlѣG(dwЁH6H$aÆa'ݻݻw幹(ZYY7!!(YC8DsѣSRRJKK߽{hcccNNQw嬛UxxL&ۼy3NhAA{Sr\V7Yjڵ8qbdddMM.J sssܹ#@n555Myݷoڵ˰t B"TUUeff1cܺu Eׯ;:::99( fKKK\ ~5QC ݻ?~W77,--Y,Vnnn]]ݎ;***ӈL&رcuuu* 1Uļe2͛7𡟟Tnݫnܸѷo_ZݿSNaXMM ޯ_~(_|񅹹ydddppRW_}uiVۧO+W|0ͤP(T/ޥDY{xxxzzzyyyyy Ν;wYwޢHA꼼"Rٯ_?S Tr̙3l] m|Iii#"gZ"S0C[|O'EtCq&`ZLWSij0`0x<DqBQ__bLari7ED)8 B1AƆjE@B s͚5[np8 a޼y#F`Paٲe* A-[`V׭[iӦYfY[[+˗wYؐy0aX׮]׮] AӧOd;vPe;wܰaC\\cX{EQtڵ6lXx1ܿ sȐ!dݺu`sɓ߾}jժ8GÜV vر 6LuT<8%%e<oʕIII Zhl6[*۷7o̜9Am۶aÆ l6J"h5EQZtR:AFSQ(I&f~4:3gSECxjӦM6m:zT*3fLPPLzP(={*J6h>}nܸ1`B7kk3gWWWO<D6R1bđ#GV ׯݻwt} ju||<ի9}tPjfb0Ǐouhqh4`h  39AW'&&s~cA… 3f 0zőׯ5kVbbwnhhr;v8s\"hYDx?0 *4/ϺЌbAT$ ҪB+fm-Jbf@233= AЕa`T*S ySj%ZX2VZ%Y V{r8;;;744MO?Իwٳg֊DQTKKKE"QUUZ|> m߾}yy9&0 ׃7L2ĉ:tPT\. 4,R"l& 2R"HV8JJJtEPT''2 l \ZZ ) y<^EE5WL&ή@Q9s8kkk"&Nh4EEEڵRT. >_ZZSTu0 ۃɩ:\$i4.+JY,J:@tKRA}}uEE(e.kii !p`%uYY ڵ-b`~UUUꀣx<p@ `{_ IDATX`8NNN`ϑ<`0h40‚JVWWMR C&q\R VڪTzlemmP(h4FC[ U*|ԭfM- z dX,&)yGMVt .77ܹsz1ϟwvZO+˺&6A; 1߳h!_O2W] FNYVVVXXنy# t!ֻ^xĢ挋t{̀hðP(JmZ7,!!{ x^`ZjÎ `2L&$+ptt_ IˁGW@BBBBBBBrȎo x5*I{&vAXo8NBBBBBBc3mZmU0Ҏ6̓s9M>)"-G"phd2A$$$$$$$anb0Mnnn偣Z˗/yyyDcǎ0`e>Uvv68LX/K.WHR:ޚ&á2p`3_EBBBBB/x^^?~LH$ٳg߹s'+++66600~`?''G7n4tjkkFعs֫bJJjY!JT* ASuuu{>qX,Ǐ7uAˈ[h B??hoooaإK.^N^޽+6m4`3gܿDDD9sEG2APQAd \t sΞ={'n߾zرWRSNi4I&Ç;wL,۷ϟϟ??--e=-̀>˛HHHHHHnoO̙ ޽{xCC8;99ݻwŋׯOJJ֭ۚ5k6opJJJ'NVkmm;?Z?tbt'eT{e-JKK:t(Aǎ۷/ÇvٰaàAϜ9{9V2j(WW-[TTTmll[غ]T*Vm|>N8Sy93M.]={ oǏ$TP(lmmkjeee;Ó'O.]dmm=o޼O[+pႛ[׮]jEH>+ۛ_|9,,,..nС\.8hð̃sݺufff JOO:u۷'LpՀЫشicGGG)1%K$jO$9h__ߊ7o_Xaee/@ԥKT//SNyzzs;wBR|rxx244zrZ6z=8Bã*??ПF# j\̱ɓ'|ð=zZSNIEPUUuԩYf}Td-]ڷo˗/DO >鬚9*"HTZXX7.<<ӝ;dɒ|r=j@ c˖-Iuȑׯ_o۶[?cǎEGG[YY=<11I1cѣG,踸s!cƌY~}Ϟ= #_v vz3 e111/^\nÀ ðb@۫WwYZZⴴ466riӦiׯ:uʰ7#t-qe=$MV]]}ف)t:_<{ ðRL+&tٳg dĈϟhOL L&d~~~q`رwF?oذ- ߽{70--mcǎMHH0*NѣGCBB A"tOF1̪`Сôi> Du{rrrPPoff&(.]tԨQRԔVRRS\No>>>C;w.oߞ8qb@@@xxL&ӵ<<<<((($$͛'߿_~j~5A.rwb8,,Ȩ111[lihsN``X,:tR .6lʕ+AS̏ƍ+++q0Z+C$))IPT*  ܹB(:7.]ZXX#&L AAAqqqKBBBQ(+Vv5jժUO2eӧ/^8x0߶&`quuѣGDDȑ#؉1 卍yyy 2FFFc:BF,d5'Oǎ{n MVWWϟaCE0,??BR(ZaT*}Qt]1t:j=D jAkb4!|P4oaɒ%8q">>>==]&%$$M6ܹs]ڵ|mrrrjj%Ko2/--}aRRX,n>M 8/??ð9s$''?}4;;zYOO<}ɓ's̹uߠ(aÆ-["#0$C tRjjŋAL:5##۷/^4 fZiJ*RSn7oÇ1 w^^^ލ7޽ f U%ٳGo>P劋o~̨( :utر4*k)۶m[rrr```tt4(,MZ nݺ pժU۶m{wiTa\Jц04i ˗222\\\bbb;;;_|0hJڵk͚5K,!̬bE1K2 `ǎ:tHJJӧa:P pP]r֭[gΜ)((tn5̙3KTTTDDDaa۷]I>'(:=|̘1**88X7xݾ};2dȐ!C|}}sssq{vrr#^^^&0d40QYF1s̘1Zvܸqrw5tбcN2Ϛ5+<<~q۷Ǐ4hPXX… p7|Ӝ'vvv999"ޞdb: XWW A_q㉉>>><Eё#G^v Dӧ\.%ԩZqA֭"!/[ aV|L[p2괴UV߽{ jڵOc˗/?}Fq<;;[TFFFnܸ,=Yzҝ^zuY*J,޽;J1b۷M)&c3z466BL}ӦM,ťqww3gyq p“'O;wn֬Y\rM&D"Dr…z X/_ C)w=FɡݻwdGNIIQՆ IAzɓ'Rt„ @э7JJJV\Ckk+W(ڱcGP! X\SS_80n .IFjL&Cİ@|tizhζwׯGG㓘Q`3gH$K.UTuuu'p5g; 44444ҙќ8qĉ+a8P("G=zh؜hxxxxx^n`vӲѣX/ tp8(DfY,ܹsgϞ]UUemmMR1 ߿{jjj,,,}1dؿaY(2Le2۷o⋩lK㸋˄ .\8tPJEL1)))x;00$$MѴɔ<~LKKswwdrl@U6ð9skӦMwٲeKMM ڎ3^]z0oիȑ#y<1333 H5t-[[[@``?--m޽F:~Je^<<<?~a]ݵm۶W^͘1ёp4--m۶m/ҥKff(f.pi+z5j(Q*L&(I=۪ F8a>QZX~V1LWWW +**ٱcӛ Cջ5:Fp47AnD+Wc˖-8q"xSLE &,_ȑ#3g΄ f> /auXYYՋr8Ɉ#^СC2:{ۺs~_C\\^xNNqr$㙙]vd s2瞞yJ$b <$$$ۃǘ&etu?)j4N%b/ syB$.B:UB!Fe0jcBF=c(VCb)>ajii2 gBl6Fkۗh46b`rSF4_[FsyB4QӋtʧ)1Lma75SӚ<[LRZy"1W4X7ӶWX7C )L[ٷ7c u|ɶJ[ha4J,&SfSok}d1t_ I v!ZA0OFa$$$$$$亙DV:ܠ`jTjIBBBBB9AijK$ g0mFo'hݺu`ԗ H^^^+_O˗/(ڱcලJn&Ce*J*g}@qqqL&{!ڷo_vJ$CCCbqBBUCC^V Vj"'O{nOOŋ͡Cƍ!P(ѣGĄ^xqݺu Cݻwb8--f9rڴiZN2{GGG=ztӦM#Gsq`B0FWaV 2~())ill\bHE1 :xի;aÆ7*JVkZZ[[+JKK˼< ]&AhAtkPJ]yuĉTQQќEEE&Lw^sDK?{v% ߽{"L2!>v옫k30,=z{ڵΝ;W -p)Zl*`Z"Z3 ɓ۷o3LA۷ZЫNBB/'$$$'''::Z&{{{|8???wwwO<)\]]q###T] 3MHRhggWRRRSStBwqh4{{{ ri`ӧA8m2e󫪪*rbeehAA09e4FQ(͑GM:UT* LqW(+Vb U~1cDFF^6888>>{M>ٳg88=o BF#J|Ξ5kٷoH?^s/^5j333[c/9rdܸq) F3a„ǏT*=BӠ ___B^Mq|ĈϟmfCBBOBquuѣGDDȑ#|:_\؈aWyyy\\ܐ!C 8p`ddV H0 |_|asUUUUU8-ή]bXaXPP[SSkìڷo׿xbٲeL#G*ʨ(0SYYaXYYY~~>B P(j0TI@WFk :rZGD"hZp0th Ϸquu%BP0ʕ+n݊?s pWmmmllC~-['%%}o߾MNNNMM]d1 k׮ݤIϟ/HVZ?u݈a˗222\\\bbb0 ;p .\[>Ƣ'Nǧd ry\\ܴiΝ;l2 Ç[E 6DGGoٲEVQѤ[AaÆ0" Ț5kݛq/+++,,LOOwvv|2avCk4,M"##̙l ؿttÇ_xah@h,0 S%&JA#|޽ݻw̙3 жRL ,0yGGGϛ7dÇbq+u#!![Aڌ3FRg۷og2C 2donn.|>wNNN8{yy566uP?~ Nx^m8bOl2??ݻi~ݛӯ_?0ޠ]]]=<<͛z38ۿX,,,\n]RRׯ1 knnq99RRRfO4޽{0ESSTnÇܹðAmlnnzC$%%G)))6;;;2ºupee_~ӧϟ? 3L>d0`ذa/_ pcp &:99ذlyy+Wdee媪!ІD"Ǫ *UVVӓa6]'/xzzzzzr//////_aNjnnxo777777iD"z\y~:pA-[/PT2} å'$$̛7L&CW !m0a²eZZZĞ|xժUxíJKKcbb;ƽ8w< 455/dggwtt%$$[NP6w`57nxzzvvqp;-ZtmWWW*~֭y!y#oOi׀ݓaii +++I$|HP(\ǏYfرڅ'N0aBLLk]]㵵 &xyyZjFFFϟ/..vڍ7H$,ٳ-[`gg#%%uQq2]CP555 $)) z:[{Æ ?zZr,6,k'NFXŋaKxx8nOj`va??ׯ3#GpJKK,Y"lxzzzG;v,YVt:WWW]]l+Wr8VVΎ'kh !99upuuuu 7III#1 766nlllnn644lllLNN6lX:kjj L"D"Njjj !KшD" glooW`jjjQԆ$))mkkkoommml6N755dddjkkÃ[mt6&))Ԅa8DW|w Bb"F :Djll9d2Y\\@ \B D_Too[ z t]?= D"H|:g#H=F@ }Dyݯ]&@ AuV_@ uл@ ِZ@ f@w:::D"߯pt2c!?oŋO|SN>|ojjy&wLz  yEC +MsέvqqKbb"|qIJJ򜪩7nL?~}:׿~ZAA!((_~½jjj?بy}X… ?~1xb]]555'7mb6n8uT#G***vC 1 SRR x deeṥKy͛[ly^ NWPP{n;;< N8aeeolNOOiooǗ,..oiiaX0Û+**Ə_^^~ͅ F@_p@@y``˗/;6dȐӧOm޼LAA,ᛡ%>>~#-- Y3GHH 6mڴi%&AH7oف&{V[joo2eʦM`JbbbFFSyyyDDO7EÑ#G^v _@D _=f̘G믳fz}AAɓ;湹0Vz3|3X,'w9TPPf3}y 2d׮]gnr}qq7oYB1كAСCcǎv @ ?'--}v;;wijj/_< ***3'R yIq_u֭tRwMk\r~ ?~""JUU~MMM-++knnnǏWTT<|2jjj+W455n}';;‚L&zD"ihh>ɓliip8௹TUUabbbbbA&SRR 8m3wnF>_|QUU}1,ޮMsss<== b=ꖿcdd$ f <~c:ޞ`aaիWGGG>~ӧO{ >@6d rrrV^M$UUU1  3f>lee/ [z鱱bbbxݻw geu֕+Wt͛aΰcǎ +ȑ#EEEp }.Dbpp ]$%%Q(SSF77#G׭[ޮWVVaÇ7o$$$w_%@ zBZZh4---vᛡ@ _Hkkkkk+ƝűZ!9l6AYY+%%%^^^~~~۷oOOO_|9t&YRRLްaCeeeVVŋsssw UAA.ٳׯߵkWEE E[[ џ(`UUUoJRT! |3HII&&&&&&ŔB緥?600x"<ٹs/[=XUUp RUUUXXS{{{-_5jڵkwЎlU@ ?k׮yzz644Ñ#G677y`0^ Bٹs… aÆiӦԜ8qYೳvpp022=<@ Brrr_GRROa0MMM$D"577H$ n b2o "))YWWpXMM\SSHR BKK L&Ü E D: ~p͉@ DVC~Q@ ?2 D y3@ o@ ?7@  u@ :䜜@  Ǐ߇<|ԨQ}@R_lE\H?Q_lC:X}D+'j+M2@{vos D~ @F@ ~@ țA y3@ f&NH/^¼t+Wg t:}Æ 閖t:\+֤IܹKرcG/ EEEgVRRVUURDGQܹs~D/# p.|I<%))Nڵg@tr_+ wRSSVciiOooo !۹|[?޾}{}( }LOKK;~رc{Vܭ[._,y@̙3sL@QQѣ 6lc!Eyq,Xеx_~}HHHԆ@871c()),Z_S8u?ann' aaafff˗/ojj]֠Aw\ IDAToѱw^KKK++Ǐ_dճgTUU`UÇWTTw53D"ԩS4b?_~\MM˗0̙3Y,`ԨQ"b`J}}?|ɒ%>RRR7op'^[YY=y(VVVG.\hee;xX^^pB###!C}0L]]}l6[__`%''96aʕl6\8uTXXMrrr Jݨf9ݻwݻW\\ ׀3 fN`oo/''͛ƬPݝP(kk̸8[[[Q𳐭[Z[[ZC6EzaRR`ܸq<:$$f@D:^WWEs )ZZZ]fx}fϞ=kii9jԨ: o nnngϞ,,,>|)_#d@KKF+899|rɟ?uԭ[tǏp(J{{{mm-JmkkS깻={6**T%x}La2?cpq^7C 555l6;;;. ݿ89PWWf: ok.# n͛Y,СC <7UD܃©5k+B]>}sܹ={L4)<<<::7#hʿw%t߿IT\ݿ?HIIɜ>}:\jMJJvsFw92..nƍ)))o߾? px 3a„ .899H$89a2449r/-ZpB1i$eee~x˗/_t+** P477ѣGaڵ***G^^4***-- ƩxĄH$:88ܽ{788x̙Brvjkjj3? D =zJ'ObD1m𰶶6lXII˗,ҒF}ʕ+֪/ sJ`XާNJKK;wniiƈ#Fy˗/kkk:tJJJK.p8ӄ.X@# l6H^^Bv2EEEb#w%2Ԋ}o _~s/߫7IWVV 5W 7n,Y`pH$ \x& 9Og茂ׂ#%%wFa-zc >p8BFQ㻆Pq`m7FwݕdWde_* @"/YGCt%%i?&2@s75DСC|ӷmփRY6%CP(U5&@ =d@µz>WOW dݻםPv@  5jT_@ Ebjkk@ ~<]ezz:ܑ޽{<@ #)._0%DX_Bi4M ڢW[lƍmll.]Xn۷owyQMM@ >A71Vq4*L$ `p#IL'aq8O~YJJ*%%0hРra5,Bt8ÁtttDv,߫Oyy9BU@O f~ofT6׶CC!08+C$_1755QT d8@F^^@ IHHt(::L&[XX 4;̙3 ƩSɓ'?;رcGuV(++[XXn=Nu떱 999...33SQQQWWFOO~h ++chhx왡ȑ#ӫ_xQVVpBkii166y׵&L]H{{Kvvv[[[ll7D"-G8h4CCl6#NjԴr8MFvY#]Vˊ9b!2F~_߷o߹sBBBg``PQQacccii믿v_֮];vUUUՆ׉@_~#""^x{RSSgϞp𔪪*OOO111ۭRRRD ?s΋/N8djj;ӧO2ӧO⢢wu #H.\8p@ {UWWsrvv>pxyzzzVVVAA `׮]׮]N&rʰaÌ;::OG ױJ2pcl9y 9AS˛98Gz3mݺ555555U]]AGGMP8gdduYo?~ݻutt–-[@ z???==C| 9sf@@@\\ܸq`ʕ+Wlll444lXoܸP__?gGGlaŰ0yc۶mO<𕛑sL+VpeX^^^C W^ikkwYa6mdiiaddtz3C.% (i:UՏnwp?6sDBӋ@011a0D"ؘJRTccc KKK`0LLLz0tNd\aÇL#OҥK'N <LNNn555xm/^077g2'Nއ^xl}}}Oi 9j*%%%==h~ё#G*((;VH?edd7mpww522zp靥ڵɓ'w܁;v ?lmmLѣGcQSSc``[wwwYf5774h //Ϟ=ʀ>΃ٳg9ɓ'a F+g5 :_s "t``spp{ĉG}ٳgh6& u䁯h ЛʚؕM^bҊ5ё\ ohlVdOhRӂU4s(?ԛ 544Lnii1228VR(CCCxxx,Xpܹ}=ѱP[[ڵ+WM0ӳ`_|?~֭yyyd2 455߾}+))}GAAAppzllȑ#ϟg(x <p̘1gff&|Θ1CVV6//o>~̙\_|Yn Ph4CkHQDIJJj "|LBa:.F'($tttaXgY,VNNV%~̙3풓'O:;;lڴk ŋ$iڵt: 0`AIImll,.mڵzzz f͚j*SSӽ{VUUSL133 1""":`0455srr&NXTTdcc˗R!;Kh9w戈i&WWWgg?gooo33@PX,Vdd$~9;;3H@xx TTTrdXB9 FkkA{!zW1"++kΝ/.**>>>/_\ ðhG NKHH)((h4m.#F?}4))8| F3ĿL&.I#I↦;M#daEJJ q4,cf;fnn.))'r1 QQQYYYt:رcc CCÈ|۷رc?K=z("""00P7=E =zϟ? Ph4CkФL q$MAeHNUV62f;f)EgJRb׳{솆:f͚{ݻw͚54133Ǐ=(T]]}Μ9yRQQ'Z9NjjJ" I$ғ'O$TVV8033}ǎ Brss֭[bĈ(GEE%-- ~)))|+7'NYlYsgΜihh7o8͛tQ2 ݯ b``P\\*((oo{"<ӊ+ݳE%Bӝ/^<{l:m(4!5frRtyY,YVLn D99E^.ϐ #\vLRRD"utt_^GGGWW700%!!{JbGGGjjǏG zazz:9|SS7&LPXXrܹG3f477ٳUj?W^ֲgɍ7@ꖖ>~o3gR 6TTTggg3fpJI^NZWWw% RRR>|(<δ033BxzzHv!V\IP`m*++gϞ-xCnݺ5m4!7|!'O  dff>xI&F1/,--f̘]cǎ7n~w\{R>>>𐧍| F3Ŀ{ahiiYw~xBۋ' IDAT*f,KhZػfz΋rNǶM't(///---..ԤP(D"$I]]{]]]??B -Z$&&3?~~.tR .. ?~%))nddl}?{{'*((t777*rƯc>H$>} F3Ŀ½{Ətttl߱S~M W1 hkHzM7?LKKKEEE diSSSYYYBB^o٠AJJR))) =>FڲeKII2wg!MMM!׷HQQ466655 0 R\\90(Niib77볳UTTDiQEPT BZZZ|Gl.B_oð/_ *@ZZz?ӏ(RAAڵklbbsbbݻ~>Л}9۶m{wfl_kC6G  ð2A7HHII1 #pE}ȏ //n*CREߌ?N׬l@|/&j@ A@ K#@ 6d@u7Ud% }@R_l#+I,kˆZA拗A@ D+MW.y2+3ŕޑ?NcSRCh4굿)[K+Q+WMxvޠ=kx@ ];bGDDH 08DWdRR~ 0/%%RS#l̚+*1 n#gupFdQ?~hUBejoYݩ`5ѷl6mJ߈VD D`XFjJl# f?&**A(,O#''O4jw'DWTAf&3,?KZqnz'^ :vĨ[D,aAǏlݽehdi\Nv+mW/22Z:T*w%Y/ɝ^SSՋ≢KJwFׯwohs}჻3F|ו:rhS%%<JjH$~ `,vDI uL$37ib$* |}} Bvv3g665shUYeTTIV 5 l<yruy3ǎOOBTWW-C x~6} 3+?&szղ]/τ\li+?3-5  no֍kVJ%UI;\&|ZdA 3ܽX z[99ynơ3ן ,6}`1֢:#y8RGMu[m~2? ױJ2pcl9y 9AS˛98EЧz oؒ7AUCMP8ghh~ÖYor;~JK-^Y'ځ@0hص ԫNeOƌ)7_biu_v|~74us%''8~2J 27~S<]V^^ڿg@:Du_.Z5s.OvuyLZqHprq k\%KQBY_tX}}˧˫j+ `#~m搉.#%F&R "hmmObbT11OF&--- 4Ȥסit:DV`va+s2}Hus"_ >r95xm_5|@ϩ*++`b9N_駣& c>gӿٰWC!fcaG'9T:NHxmUYvlTVV̙9UGMnO/,]/?j=;Pó1q@gNEmm`Äo̜hfCvCʹUexKaxJYrpve]ť?poJdz a/ TTTZ9">&$J9om}rs0֍(grO89 ;k}O"]ܹikmqji$Q^S]UUȞ:ФhP$~7sbQR2Ңg3jmmP(l,\ ‹!Ϟ`3 ¹v c7jۤe%?^߾u=|dL?o/’")nIK"q@.5 gHHHv_sDQTXp>UU o|0( ܽHrzG<牎rq߾Kʘ5gwL/_ݛ:K]f픩b_:5M;cJ{y<#;+c ]o܉}$^L4 Qi9 f̞sV]]- ';399q4@Fz*:deM~1g.tikk{~v.zqNNNaojjj/>q*LAq@3 ~f55'C:veÚqN+Vb!ۃ9MTTTX8ej*r^nA OM1$$McH4L"Iq)))2$bT؏:~ޣ%%%oDoDeeetA68:?|wk۳P5o`KxB  &>Ukbj/)*([wR1w\0 ++'19 v;Rchi T9rﺀCd -H _ ~5[|9=>()G\u K3oL&O})< ð[fhhhsjz-AUU=+?{$zrmڶKNN}祋Ϟމ`Ǐ"#V#N>pm۴ݻy>~i%ZI20){ 2SF7C2%ę4]!9WYM|혉Rt)IeJIH \ 0?%d744-=衽[6hYY>j3ܺq 9NFz2/D_ ??53PMD"݊y|/ؗ=95}@UUeeE93n{EKk.w`@5u-K܋}9tAEw/=1ilEKyO>K5s·?/ill!61wn<~盬t D,Bt>E$%$$ 85#_O0 WPllh~$W7 .mc: q`BsfN]7]pN1ѓ!!gN~ӧN|toxUWWfϟs3tk薗xo3ũԝ6VVVfki錴sArIs~ן]aXZjʓv).*ΤP(SzHv! [Z]6Hm݆UU|YoȽ~=ק;ϿzyrNF]caݯ5s!-BϞlooomiu=y @UM} .Lv6PU /S]͛/;:: , #F,Zl<(e%I?|xT/ rQ4"ښ ӿ"o@ ST?;.ms zvtJWTEQl("*KEJQ+vE,(ҋH{"M?f<쳳3%J20ЖG%QD I~~W]vc |(JRTtp{I7F Z<~~qtv4#+ط\t<k~ QS"fbLdUT"]IJw&k8Ielqan?HWS1W-Θ9gTk[4zքJ7F^D@?Jnii&xh!T* z}+_(1!.?\"K3gްysrsN&MXX)iqaAP,D|0l;~!'s*U~lǎx,/FaaV|qqB̌AE\ l܎=*b9Wx^A]]]M o`0R2cl:^79k(F A W/H*ֶHLUUEGJx?%;::ګ*+nP5.P[30KA@t% pib0# M\D D'Q>oF?Bl۶hYao&..˗ Y w`x(=zѣC#p웩ܹq~~˦Ct]ݷDfAEEņ kaaqΝ;߾}{P ckf6]ŢQh4 tp ,5  t: Qt:Neg 6w`F'..]AT*^cz'}WDPWF@q`o4-sL.*_?AQ(4 @!0?% P^^8K&r9પ<O pei$%%Q(TQQ@{Qrrrcc#ǫdII6h~?eee-,,>>==WXXͼsKK'uuuhtlllxx8ݻgϞ1cwxxx,X`֭?f..))IWWw֭H׬Yɓ(ŋeeeռ4e_+kLKRG!?BvvYfRWQrR ?]k*+D% {?n"[3H{{{uuu;ydEEN``ฌ7֭ +'N2@K.ݶm[jjnhaa34knnqrr y3M IDAT%77ŋ8pٳgm%%%.--UVVlڴ,BYd(dff.-+311)))ѹs?5bx{{{{{# |8b (&iO츗pɭ8*KǢQ( DBDEEh.񺺺&&&(JTTTOOo]g֮] MOO722suumjjo.\PJJjٲepW^M>H$*++ãWls^___}}}999~)~gYYYMMd8i…D"QGG'--mTggΜavhhsVVxWW׫Wjjj^pegg'...--o>0ZJPPгg.^H&޽ K捕L \ld3gŋ!qqq*$$ɉ)8*.""E#""oys"f?FGG[YYMVvޭlGG}RR۳gϞ }-LtXyIHHymr 8( $##~s333eeeF6z=ؚ 5К{ZKD;F߽K2+AMtaB[3/^P(pUxaoo_TTDjjjfΜXQQbJx{{ϝ;7555%%Q;l۶յŋbbb9 A)))>}z.\@"RRRlmm}}}>3///"XQQq!_:$$U ߾}aPgdd\~=11q…߾}stt*--ߗ#Mlܸ3##,nnn899999}5$$111VUUڵΝ; زr۷oJKKsrr-Z(,, FBB`,.\l͞~'%%%55HƩǏ߾} ttt/^6Š+|||322.]ʻJPPPsNqq!Doo>ܽ{mlpP`dddLLLZZËޞx}ر `a# |8f|84χv^ 3<'ڸc18 bfTTTTUU) Y R^^jnn䤪:RT*'##C&ꬬRRRq/_nhh8yÁX,ÇUUU***o@ |||dee%$$ -[?;v%++<55յ¢~\߅U [[[3!!8cccQQӧOkjj6"$$/ 3GGPwsssqq?gYmQ(Wh4OիWqqqs455D (sHa{#xڵkeee^n]MM 痐 ""š ___..tOJJJ@@wrZWW7--ӧOCt ~ƍVX6NVΝsppQ3a1(,5c(_'Mlߌ}XXPYY AyTVV&((hdd6dŸWeee~~?????;w0"j;vn.9pF۷O[[iK#5&!!!--;-رc~~~7n,++U;wXZZDFF2{1;gGz8– CCC]fgaV[KNNиy&Xŋ!gxE23gN@@|~*F'??v||<`߾}t:㞞aaayyyK,azI||ݻyɻJ9ɓG属-//L<{&̆+++>} Nmmm3Y̕Dqa_LH@a0ה|rtF@D8KWW{zz JHH8p@wwwiiǏ}G BwssH$ 3^ee'OH׏4'N s477766*((sCۥ .|K86ׯ_&Ka //5G|~~<$'NXxodee9\b###\\\@ [nsqva9s sX7d2FJJ|rƇÇś1c… 322J"((hccn:ooo2B***80+PYYؘy cߌ$Q@%Qrrxm;7mDKH`$pDAIQa"\~AHHP]vkhh޽^ @ }Xh__߇J z{{a:9|JبZGL]]DYYYeeeLfff{{;F>}ѣGo垞x<ajШiGGۄK.{ijjˈnR444>~ݽʕ+?~#oJKKq8'8kffyfOOOƢjhnnƅܾ}{ѢE~~~\brF"""PYzuiiGeee^={6/LLLdeea(Bz7FՏ>cX___[[h4/0 Dkll{YaΜ9[l555UUUgϞ)NXYYijj YZZµ[||<Avvvd2YQQQJJ*55Ρn֬Y F]]]FFŋO4ibggʪxyyH =z$''EEE!nI"35=mssYUջ<&ܻ!=J106ϝ@P>dge|vJ _zYZRllkȽ=q‚|111if\g+[|YLDDo[[Ɔ0G+).@Pfv33|*#=y>mI ݗ -( r.FGD?]ސT~{ o['j}VJx1X4"X^aAދ/eD I55u3 Md[GruEn$h4-48>Np2r>~ %-f It5~H }͆uX,VHHaj/I s { | @XXfCQ3R%@ոsh^Oo7i nZC;%BLbUP N4k';&--ǏGGG_pd2t߮_^ɐ#}ͭI'+EF=[AEE¨g'h.-;Db]jkgtvn}}}ܳRs]rUU~;oܡ5*S[6iakW/LGRDԹ[r#+1n\у u&aёZNu԰^BГHdf]]g;ݿlllJR޿3$aԹu~ˇql{yƬo>ؔY0_[GS|]= `.Z%'癿<1:nai5 6O^Z"Yn,>vilCGJn|)+9qw9%J$eMr|NZF6>oGOH*qq+^Vݽ5ȋWZcK~k7?zo x'ɥ~u"Bnv1^&3,\ .&ۗB~!AGW7׬]!HJJE\>q_?ď%ދx8Qc (&iO츗pɭ8*KǢQ(P((677o9LJWӜ7B uufZYngNٸy>mdUqOM6,uR^ڻ¹3ԕ$ '475rʁ~~<A)l XGVR6n Xmma!f@$߽^@GyT3}:{ƴ%sMMv::c.1kܔn>wKY Bg*'PKD·7,zFN2Q&-52[Y6jdm*)̌S4H>mpN:郙%|R]U9s np'REKzJJ^L̝(#=}3΂J_x6ogɤ4YrH8Yܥ腋Ҿ+;9PM8) :`>Y[wwWzd%LXIxA^^q`N}|riH$p0-fWM .]`e/plzh=t-%|"mW^%^:0˗,u>|b}}}j: ۨN?maxҸ `ckWZRDjk/c RtJ~)+Yz,WwSҞ< 'ܿgʪ>z."*`3 ܴ淋ZZW,yԙsMQq@SlnyuӔGO[ܿu϶[}MST{Gg@{/Qu.DR1=ZA~q2fۇͶ)j!".&?xvaZ!ILtU#bl3ijlX#jEÊ'%)| %}V(//}mz%&r7J?mmDf,X)(*ݸ1e֟-iNysSs ۰k;jUr G-9z h*?"c3.WW[r܅U=Wvtr8Q^}߼zlg4î Q!°~@P+\WGL'όo3w!۴8s7CG9F(*~˃r15ߪ?X£ d|y =`Mh459AXm a7dffZ^^N)Jyy "*JRTj8i) MrC}Ŕϟ2:\Oprl8ŦUZFvUESWdB8vhO5ߪ#$]y/6.{{{Z[[Z[sssJY"C8{Z[Mvkw]̇/]8o;́28gɐTl F\fr-2AA+;^6->&*\حǎu456$ߺf9ݿں95yRBYGp4{aKdF/^z-2Z'v[p9"&o_2Ԣ{l+Ef-7緘b>%Ϟd`8y ?3))O0(,ȋ9G_innM:T62EEf9~*u~Lb;z͇B5 \ ֵ :6N8t()s11q4#$K IDAT$,))Fq8\iIt11qI)i%dPUU"/6bsX#pyX2I==A5EDDI$ٴp@502ftK3' ô|BA><+?yELX+++M(4:MlߌT)Vvo|7Vg< 7 0KUVT())TWwvvl2Cao 17je@~Ik˦uO%/x𫯯_hSg"T4F$")#=۟0ɀ#-.i ATT / Ir.v>1plbBxĿ䣘+D(4  @/]]]Y 9( ]VVG=P(׮ƻ0OzeDRUfe|󞃘xȩpI*o^eih߿evX =+$$Е5Pۣ$3aDk+<|#dAA$]}N 7 sf0g6ormbbp|Rҡ:::Ϙ>HR644>vS*.èfE~47_8}򨴌YIf:pwYrR2u JPL,3_>g栠t9b\Lԉ((;vw{#-KR._:r -EQkvjjfy7G8u (ʰ~gO Mdȸ !/+))Ctw cDAWTxuǷC~k֯ٴC6U\@ l8$G[FQksS#hX~@8~BHH JX"%'ݶ{&)IFJ'I%K߹@ a0*zpn} SC{T*$456K>|^<9b7=E^gNN usx%J2FYE ~b͡03<)URUUǧ/e%t:^QϏU}HV ZW|x徘ی6J-).Lf`v_OFs{m5l*vF"<,P_W\T3 I.).h*گo-p`1@b/ORR̜yfC[707 ǎviY{QO$wU5ryؼe/488.3l>o͠P(AD !' L5QDIQH#UwWݘx~µ;{ Y*UHH8ƽg#̰ߏK`ԕ%7j覿Ɂ HW]"NR';pacj~԰9|)+XA43"_:w| y.*N(`kA?ǙB]ؠ$kfWbjwzAm5ٜf-Gy,={J/c=uSCS7oXWM8~ ̈m}A֖!hÿ̋T5%$~la50HTL<` |Voy<eu?N`P(Ԧ-;4DEEm}Ԕﲘ;0']QS$>"M!Ars\즚-pXja\^20P0^A~nD֖[|V1K|ښoOS9VJ2{sOx+Y/Y28h01054`4Fۢ-Θ9gTk[4zş!a᣿jMwv,7.~*XFk\[{a\AQQjfCcYi:oaTGqOEvnۤ"=Lo~Ȏ9ZZ*>^ a"Oӛ@dRK <.{:gaWzef%dƵxqNTWUJ`?/)SmleӺJk}^}eL.3l!j@=xf5J >,kd`*(0z |H{_{ƲLQ=bv֦R҂B~^.@WOy{U5͑F'^HX%o8Vu.9ʯ9hQQ1FHss,.N2G!ZxqONMpQA_JҞ=CEEe弬9zz+U]e;4VVNm'e8EͤB:'뚛4YG]JUTym|(b2C!c:==ݽ==R2߯11q4$DGlmii&81 KggGUeX2\(4774ã̴pqax/I]C.ORZG;1NoI)iFմ6Qž5APqQAbB\WW'"Kړ=3WfmM_wnؼU`Ts}ܼܜ'WP1 :<+a`` j\F0.df:0$0, ~uھ2Z`f9o$ K&zf9 Z㹔͠S!A]]]M F0GʄWx| rf k9hiih?2m#M&Yg^s`UUEGNL`? ջw0xQl_5.0.N1.iwѮcg ojjRTTz{s\ a>F0ߪOaPh t*)OIyצNhzzzx<@6$ ***"c4JNNnllx5552,))9+T*5%%ѣGvvvmjjϟ4iw Ao~B166vwwg~Q(ϟoذADd _|YTT4}tGGG̎7o剋;88XZZryQ1 XUU֖n`.]***z9okkkzzzCCÚ5k^rB̝;wʔ)̧RSStuu,Y"aW^UTTxyypPNwwwBBԼy1115OOOaOÚիWsJ ggiӦ(6{400-!!1eʔ3f0wlc#Rc„-[k.^{zz | @DDAJj/!9jn`-ZHLL}ht~|K/Co7vMݴvJ Ŋ [l166hjj:~xtt ?N&,,,LLLl2Μ9D???EEбg;;pD*((Ȣ6};w HHHL:۷oپ>YYYx${VT*{>}:>РRv KMMeܮQnuVxhAffi]8u5k((((++?x𠵵S+W\ti^^Okk?<uF^FU Qgg۷̨Tjr6^^^===#-F;v<6e?޹s'k/իcǎ)((޽{P5556@,MsboT,#Y;CoŽj{մHS*v]pe;Asss'MBϜ9ۛB&M 9ss[2˗/1[)--EѵK珯T_^EEgjj:o>~2J UTT [JZZ͛^NxxIxQ9n`cc5hΎ{ [[܊!yt, (JOOOTTFx<klllbbBDEE}vڵnnnt###111WWWF&߾}pB))e˖‘_z5}t" ׌ls`C!---"""^___}}}999!I޼y3|ҦΞ={oܹssCx۷KJJ( ? CED"BZ'޿oaacd5BD"ddd8;;/^L&L^xahh(!!礓>0F\\\*++srrVVV^Zdd%KVZ7006`Xr2GXd a,;;;qqqii}q)bѢE/_NV[[ epJ5gΜx8&&yxk1N(**ˋmիsƲMؽ{ի!vMg͚Q|||9,t:'<}քC  RQQ6CCCmmmXgauDbgrրW\quu6#plzh=t-%|"mW^%^:0/jkkP(plmm/EEE4f̙X,6""@RKJJΝݶmkKKˋ/ls`FQQqΝW^ress3#ŋϟonn/lmmSRR>}ѣ í]$00p׮]G6,֭[w5R\\LR 8p2uTrssdcccǎamjj+Șϟ}B̰5xTܹsX,m& f^n]AAIKK3*}xP+++駟"##&oiiqㆿի[[[o߾=l X* Gbl|QRR2++חK˖-Ȁi^:g!!!`mmvss㞪m#ZuBP(VVV?O{{{ȰM{̙7nm )++_t p}FGGWWW򥸸Ņ\xyHH 'ڵkם;w}DEEmܸϯ.==vRPP 훌ɓ'Y`~SQQy-`85A@{{{EE(skC|aaa%hP9ks A>,?à'oFEELUUB0P(媪NNN.(JRqqq222d2*%%'88x円'O~=b>|XUU;qɁAPPЭ[=zdddOf0#+++!!ahh`bb2c <9~,+}'́(cF;㣣SSSa9''!2ӧOx{{߻wopsɓ'CR5"+fre cAAA;;;++a ۣ8ڵkeee^n]MM k8]]]UUU''YJ^>}( ,,,444`7泬8 ^t)|E{M8۪tzTTԩS455M`fVnݺuȑJHH``'sptߋG TTTO:5&&5'p ի4∈زe0D%\`!??5۾NϟcMMMnl#ckO_H#"'J֟mmm'''g3-^t)555@zz:s cbb)Uοs٨xΜ{|KK˰ UVq6 ^UUʧO¶788`gR^^nhh8֭ x $%%mll<==v'DFF7nܸӧ_~h֧ylٲFؿ!8s6T\`[u<~zCCC"cǎ/_~%%%ƍ2k׮d4/Zjَ ˈ H---k׮})[dN)''qM8%rKKKHrrr\$g@ bBxĿ䣘+D(4  !< IDATƅ/_tuu n߾ tww~q[4B|I$yƧɓtwww~zs?{rFF%[;qDll,<=www!#ESSSAA!%%ϏL 9"lׯ__333{g`6-|||/^tpppqqGߕ9èfM;hjjԈh<={]֭[7w\ .bhhhii~zakkKqv[B@"Vt:Diiiq cH6*$644ܸqՕϟ??a33 x/ Xח#&&&%%dffb d!9a„GΘ1BMMD"![9M>ѣGΗ/_FLʀ DT&*) uu^$QV*)d%+K7oVWWKIIa0&e]]ݭ["q$%%u?ޔN{nҤIDAi4Z`` UYY)&࠭XC^^^RR ..3z3gddd lvee%Do޼)..,sR]]]Hf@,Ǐ#)iii;zh/! .$UUUUUUMMMtvv^rcΨQV\`*kRRR`ft|ijj÷77:ʻ o!""" |}}ũׯ׮]<ȹs缼-[;ͻ|!!!^I$_zUWWpB'g۶mѱN3̙3GR'igggXXqCN_v-BXx͛7 /ȈD"yYrrr\tiBx}SSڸqCRRׯ_TAYQQQTTb0 ~뛜d2kjjUf]]ݪn1H(t:}splxFgr=;N3p Π4- ~Na_߹cdS߀QSSSUU72bĠ`0۷ʚ5 `nnnݺՄ "%%E" am&MZnYOOO^^BL8nܹC$###_"OMM͎d1[JJ600GDDp s999+VL6MSS##_.AǏ\jooC<eddTUUٳn]2**JH7nHHHX[[ /ͽCF7[25447o S{* çO355ao%0 X 7c ثWxqqq99ŋ#;vPUU9rھ} p&&&ĉH"x#;;@ qA|C]nސ!C^x G |{6 k׮-++( W?㕔d2L>|0bm޼YBBBNNNNNݻ0 O:4ƨn7C}" G?xd2ۯh:q(AR1001?$d'ؾm#:YYYHJJfeeLMM۫322,BvUUd6*$lvii)H]smhhC6b "uuu ֆt:Œa l*ᢢ'O9sF[[G ?B[[[II L1ʰpX,ַo פ$Hat:Fu򭤥 ^UDeeemmkGGGaa!B^W1ʏw jmm;tHmm-L&PJEEUWW_X__A\sx3 f` ]FFFfܹH,)$6n8d@vvvffÇ9mDA:;;=z!(#))7tKW={t ?#B4upUl?0Z]]-hJKKa Բdɒ_% ʯo8 ^C3ܱ^x ȍb ,tKzN5 /OtӋ W((((((((!6"\$_+o%o&m) ƮmL,._ӏbNWJM).*><-?@L巖WcDb0r.44I?SB) ذjæmVlb#kdM @`IMyEBAAAAAA%o[[[Zm]=?A/`2ϟ'<}>tGR)oF?M.0 ?zp]F:03>Ń{a02޼LydtJM)*,֧) FNoR]ܐa?*`o=ýSgW{OE|𮊪PN c5иJܻfzQ(4?yPMU² łz]ήmg״صm&F =Y\  }>oز~XGۺZ}}Ћ/QԺڱCwlY:vйߦf)U|HW+:sqhfA~ޅsg(Bhjj>e-^^AԟAcG|Z\ u7oXu`:]xUL&s19Y};OvO(:L&k1QB2;rI ꒔^cvܜ־s+nέcq~9ձA:KG]jADZ llhXry^ܗ=Xrqإ}}Fş=}v⽻p'qhڕ?bǖ ϟ_!{I"nITdʠAߦmXr^6y ƙcM 33xF}РG?GFX4{έs?r ׇҒb)Ϝ:އ;H*.*HXEĩ ؑCp $h';a&L&>ܸ~5e2 ̨,+L?c+{ۤ/dU=jhl [**#H˖-1ZCKb;VVVf``eۮ~YogK!,WPO"%Oc Uzq"ppOI.[22`êk8<>nIUV G'g!y9B^M.^{u-|,!us!hΞћ]O_qx7)fqhOJR"wޝ[,ص0@CCsMݧa0OŅG86PXl%%޾\ łΘ5l ϟ()Z[[zJKOr?N-;yS8<a6%+X*<삹e_Uk6#N\ݺp}`0 -,iS}T\hke_Q_@ԏo@$bd--okxr;Ɇb%R\&2bbbFzFx +++ed&:`W7ܤLe72Xcb]]-zAFzڼd%s4MZ.:FA5pa~-~2_h{+F[Pwв;;:IzifZvL.=$"&%>2jǤ~k;MaϺMB2>ew> RA< wO,}F43Ԣj>73_>g&)/F%)-I&>fc|ZsL@DEkOp&O8[__eMMÇJMA.uuu9zz7#<&r~|(ks#2uv ·cyXwo8"p6%JT#gIȨ46ܽzFxkdlHkilf^4N\\Nk B:aGG|aciur 0ұ0bU~:c[]T\m=ܵm8/u͠m^oŒutR_$?ysmȅp `=IynUaű -~ `뮽";m\nӟnQ\Td2GqN4|w3Ξ&lg:|`IV6zijs>׮^s 0a y"..ηښ'L󘙕eSVRH|F**JJ7_ij@4ѹrYzz~E7MɓWݿCьM{zM_>q>zxpCG7l߽s3g 9TTdLtvo[b uG8 q::;boٴ(${s뎽SSm߾Lo2Eg!p,COŅk+_]ؼq|߼O%!D=}gN]]]>өT)f={y斏S/2"s#BVg|,N{?IÀ7kEkkkb8I"x6#)K`g?(|.)->(!`1b͐)c6B0 BX[[;-+2L&u=\EYUWZ]Ui3<cbjnf>$ /$qR_4n%$).nX@$Jz͞&hbjo!VNc]px(6ߘs*$[nWy=D#cSx;8X3Vsgq%փxD=벲2x&"9s@[mϵ'L$^xn('FW,1IR|F^^ '/,QW1bUkDGQVvSc8|4T߽}vE[w۰fY巯|kjC֍ yeҜy ݍimm\2?q#ÅE s:}f=g9;#=Oƌuކ;5v.5rv12d"z9F9%̐ssvx R $_x53OhRY6V$&(Hjk׬p)= ëA'L DI2@bLlKkƏsf*ZQd&IK}Y1X,0d99ydڷ_H!#;|އܡ-AI02D GqaXܽ}CUM lHJ|)۵mSnNI ʮ>/ LL|*-O '/y3<‰㞓/ ?taŋZ(0Ӆ~֬ 7zxNۓ*|nkmE\ABJJMLE XTQQ[j'?O@E)(*9Lx>t(:*b})u5#Gs|3]OqvQw"v=Jo}S񍨫M%.#66F߾VTWU SRR~{DQ/>jiQr+DNDSwl@"N)J),gSϟvⰫ7EgևvS'ʿ|o7#1IױO_VUM="Kχ :4lލyQ0w_UU,I6폟 hmAȀ|/ơ8!H$qrR`&Z^NQuĘ 0f؝] %ezd,)J$wmhoo>h/@hkk+.. w0#\'kjb0;zIҊ8e9^͍ȯS_h2Ӹ;Q׮|%%%=s񴰰J{GkJQxS4>8FƦJu7 ܻs]O_p }tMMB^p%J*!L3eIĩsJ Q0Ƿ}}jU rlS='O8vXEUMjӣ>JQIEv*ؽ}^sHŽf\r)/7}PvrXlFnXX7t͑{?MPNKWu3'ٌ(,Y1! BX,,I궤%"a'ǵ4jͼS(5e»O.]P[5@nw?^/_xv[|&$=fW"JU(MTR (++(@E7]ZYILQUR *JRO~7 IDATQxx/ZRR 0̽Z5۷{+H~pW?fؼm7ӛF<VWU Uj nEf٢PXE ..O؏}_>OŅl6JCe)Tԭ*]=.d+ob~᳧# CॆΞ'TWTWq܄(Lggpm#G-Yrš*~17JMafs9U n<|+kE:=J(/Ɔׯ^ 1]̝SQB|mZ m^< Eg0ۄ8/{[.MĕPd ӻ9wN144߻3e jfnFE;n(U}ߤ"8 bݎBVH* s z,HLxxJ1ԗ) ` MͫcμE^&ߌd2 \ 9qpp{ȟZ=strpw3S`D)q8<yFU53 ˋB3ߥ}ssh'gdRط Dw6A _Mċ++E+@fC t C J"PR8:j.Yڍ{RR҈# 2JIIG<Yy %e _g";Y44Iw#/S̍S&^ϪeTyp $aKΞ9,C.4r.Ii$g`⚚jM-̝+7rWi?5V|n rA@ϙDgݚutM[CaaTas?64;rPw螐IO5m͍ȝ=sAY9e0dMNyj}0c}u,nDjlaLb`Μ %\ڂji9ԂW\q҅dfzQ< }fjk(`C@|&j`1U|1#kOyG ?ȟ%xG sMgܲmO샻d͝SFUS*i55r3 tkuG8vKe2GYvJ8cw_?$lشzjPzDWZRh?TEY()061WT?NobjDs"[!I5ϟK"MALLLVVRWWg+t]Yl us0 |*jkmM|$_dۤ>ȷhoo++-!i{2,UUMM}_ۀ itJZ[[ UuATWU.tvtj)Ƌ C?w޾ۆdvk[돨 (T$/.uu ut%%ں6T!/ 5UJ*L&^YE7w|/J R Q‘9k.G3}|+l05bgm H}yh 90bAǞ޿D勤{% /I{rd __n%=k< 2HIN!ȧB.R( ښjA~14O&ʥ>&MH#pGᠯoЫ ~V>m%@<ݬ2Y/(|R&<Q}+2 ߒ/3ɯo"o&m)EAAAAAAA-_|n(((((((f}mټqNN%FόhͿD 0XG" (++[rmll"##7oNKKۿpp0LxPPPPPPP~ g3[i?0 0lpQ Q g~aOb---033kkkaHw`ȉM+ W l6忊_ґ{{Ӛ,+/a L&_-߱Y,ˏGT2%:X $Ab`pa^N|@ޫ7\uvvVڵkSLQVV`0666=s-??s '>TWWY,;vvM]ƪnbD\ aѓɀ,'n:KKKZ}}Ћ/QZ++u~}q X]]]&9a„(aN]h˗/{݀a8++oţ7l؀, sxQ:;;tرcoܸwŋ hyFue[L nnoֆ֮:ga?t̴ʶ,uBxkI쬬,###Niii1L XlYYӧzUZAAa̘1yyy111_KK˓'Ov+uqqj$YNNH$޼yCۋ(C: ޘ]]]C=z4I{u]v=Z> HKKMLOOG^͛7{uk6jqI$ɦ[Z\;WSTSv8 O K7ʊY[[[YYx<ollliiieeAI,Y=00799BNNn„ ȤJM>]YYyΜ94 ɜ:n8---<plllEihhXZZh4ڢELMM͛ѭׯNJ"O:$">}tȑ'NftI&")x<Ν;9TA/p666"fx DRUUݵkof 1cJE&|+IJJ277WTT9sfcc#']P{L@hh#?׉'gΜL4)""6eRH߾}Cr劗C=}[#Fp,gΜ<_RR]J#Κ5BӧOJIId^c ޘ[nYh߫III_~{̙3iiiT*… 7q8Gc2eׯ9|7o8::˫رmn0Z߿_KKkĈo޼#5 ŋ (=#6cqJ׊ 61rfǹٍm 7KfHŋT*p'Nlhh`lvCCĉq8JxޮmǏKHH0̊ "xFww{'zzzaaa***HA;;'NNЍ-[@4ZNرcutt>|ىL]|ZEEy~HOORRRWd077Aeed>wwn⤈nk. >~s1c}m&<<\BB#ٳgsssa=TIUUH#fyDiӦ͚5k ܹsg<$$88|q 3m jp-- ccc9yyy33W^qJ9rdׯAR߿ziiӧOsF}b 777 gMRRr޼yjjj7VVV...x<ɩ-77s)((h„ jj8?b;N455 Hc'"""BCC>}uuuN8:wL:I<{SWWɓ'JU<ɷWb0"8bĈZM6]pԩSQQQ{ߏqㆽ}qq.]kb ,Kη BLLlوի''w\̙V\\\ZZZTT*((,^xnnn{Lج[NZZD"HPcVWW]vo:$ĝH$JJJJHH(++}#'&&~Q9nVZ5xs.mmm'NӓE }-OuꔅTqqqtt4DũSzHDJKKp8ܗ/_ݻǽJ"\rf +ABjfɹmmm˨QX;v;Ǐyzz̞=3AMlee%TGGr= .ڵ+)) \x~[[SNeff***.\P˖-P(ܷsBB{]]]ABFx%%%%BYtӧOx׈w-򫣤Ἵ 8G޽{i]][nxGHs 7o^d29))ȑ#[lAF$ ccWϚ5p,{ȑ/^dgg.y߆5Ac6uԩYfqJرC\\7͓'OJJJy"sC w֣nmG>p8g#0A/'"X,3qr#L0b0,) ~!33ӧOD"qӦM0 _vmϞ=|5G`0`={&|+--'O$''{xxH˗^3g^xҸѣW\A ,aee.%%ՋgFAOOo~~~ĻwJJJ *%)))h͛7wKwY!9xxxL6Q;ʥK\]]'LҲ<) g[ݻwy3 pa555CCCJJYY JJJ=:cƌ 余Ѳ[' y]?v3gNhh( 111|kYf bB@$gXSLDrnOԘ>>>QlllkkI8َ9tҙ3gĈ7":D"aҥs5PTջc555KKKkjjTTT8z PjJDEiAYA\ARW/ڸyJbeEQIVr@ yfuua2[lݺu+D̪xS:;#C7F D*++אd2l(5%%%!i :::G>s ճfWVVH$<͛⦦nUUUUQԮ.Yx3 |Ǐ}=z,YpBPUUUUUMSS0W\qaԨQ+W\`AUUڵk)))0 l$ 455߿[[EEEDDN ]BEEEqqqTk׮Ezyya0d ɷ rΜ9qqq>>>܉,+** Y^"ӧORRRO}ZNNa0b p8ߌ38^W^rrr/Fwء:rH55}qLLL‰'s΁㕔d2L>|0!11q8nԩۉ5 ȨxwvvQ(k2 0RCLL̛7o/D IDAT iiiihhL:qF@ fX7o{. ΜCmLn?wvQQ6 ۷owrrB>FxAj'Nޡ /eg|𡌌 155 xcx,=""q}a薎ѣwOL}MZ9%H8r4懤۷ |"+++IIɬ,i{{{uuuFFހlPUU%---)ٳ ;T|"kVmhh,Y[[g)((t],KJJ a$hχ"Z[[ B~dX6Z**x$Q 9u(**A*.'HJjh(עhQ~ z|,>c5uCmF88^JQM}M50iwaу;2ҙL)c+xes%+{8 UjJQah'瑎c--͑s4t`kiQ)0͑=v2䚚 sg+,Ȼ}`>z8ɉ::F=fYzLnS<>nzxUoR]@/.e%::o{s &NQRR`0VCm W7ZSy}9'YXvvtD]vЅV-%)~٫ ù9Ymm}+~V0[7D_NO{rcWg'>u78޻vA45Ic&҇aM쟊U%ŞSƟ9u7M}T\T(+(S@cCڕ|.M|vL0&y~n\2@ӎ9T_W%Bm3@QYVxי~VI_>*9z0;lUTG"-[f;b6@]C;vp˶]4u?ϗBC6Y&1GDJKfu?rrpOI.[22`êk8<>nIUV G'g!y9B^sM.^{u-|,!us!hΞћ]O_Y CnSОD;xYka&O`0 qmf&I x>4cA柍 U wо4uY2wccv!V mݛ6 /E"ZaPz@$bd--okxr;Ɇb%R\&2bbbFzFx +++ed&:`W7ܤLe72Xcb]]-zAFzڼd%s4MZ.:FA5pa~-~2_h{+F[Pwв;;:IzifZvL.=$"&%>2jǤ~k;MaϺMB2>ew> |RAfK}b1H}060|<蠙U[ݼ*4q>30LA|4r.IifcI1NZiJ[S٫ZZݜ܊ !O8ؚkk(,煤.[<߄IFE^_{Q(:s|EXGo_^ =|HpFz |J8G5U4*m'ox>~zpN @77"k. ')^ZRAxfDvV];[, ƦComMp-L62߉tm=CAE[ϰfϰ4zpXTb*}1-`R Frhd~*.\6gE ڶi[qed|kf`6^bwqM%]BBhқ" E+ *+ X"bXEz-n!E֝33gd̙yUsf"C%xlhh;99r8'77WGGgNNN::_]9yy.r#/)+7,/+:у--,&M ػEl6)} p-ݟ ~D>~xeΣ`s1B>[YEUhfnG:%(@n*_~Xzz‰&tt޽M+.*t1<ܩkb!EÇ.Y8nϜsO#93qDr0 LCAs[x~嚍d ޡ3[+֬?rv(~yPﲶؽnҎ "*ddeF\:--S'.]FkhzP]lD?pD ,4\"p]yDbVf' KI]!@R++W Z^VZ1m^Q.J&S( (#b)djw Wa6f %ca b,^RS7{KMM{/''fksF&:vM~ 5$e!x9 HP48lqW.UUy<~GQ]2aÖ +_|k-\&uRvvŀGP|PzyyZŅu`s( 2!9+|Y/]vV_YIJ ¸Mj7bɂٓ]2? IXuZfF@B vnݐ򁭥 AU5tmm]'4 EQ֒r,6n|e밳!߼H)l X4ʥ&&Iv¥ntq}ȿד RS>`o0 b dfn٥,QTd݌6gO8,^S5d)Syv`ߥUk6J/MQ}pϞ1aܽׯGO>)4\Uȿ~Yd`#^s͘:vߎdHj;|#x{ݽ'L6HRK\iin>e\zǎ.r39Ņ::z4ž877^˜?2Ġ2hd2H)|PTQAQ*}rnnNCCBٹmcSSi(^|!`.2ؘ[_=\>OSu;NWlQ1Oϝ5UCSsϲΗ`(vDD/'cA#/{Fֱ9`MZss mt@WO_MߣgY(HEPML%Pn.`ʾ=;' 8"/}ٞkk?K"Ca))6jhn J%e~F :{S'<p1@_PQ,zgm~yNmlgN'I\no> :8onjBeU;v/\le oD?Teg/ػ ] ~ XzKEG?iV,odl<;ɢɎvPWD{8'\PGڼ?彠3%dgzLvf 0 9qϳ6ĉ06٬;s8UTi*n^k]n\^K*ETԯވsۗ%- ǡ,9 (d564<|p?unj9[8^균1S.[KQJKT.)I0---LB8%Q^VZUUo`q׵5;;SK[GJi"*u:uV?z\.sM\t_j]\j%eb;,ŹIC[3(fF^khNehdғ}eڱiu 6<} P;j>KbcOI-xx^?ģ0 GO\E5@Q\R_<B{ԓL,ssL#O000R?FAA^]/#H_ʠ{ 4kC+W#}D>;@Ϸrb@ _vW.@ ]k3aaa &ujC_$@ k3%Ǥ b,15=Ll۾͖>bŊm۶FDD6mŋ={ikkJ@ -b i D APǏSTBoT,%>#NOIIXXX466VTT|,M+|-|>IvHkkkeew 8 t***D—/ނ@ "x<ޓ>v \V>>fCjCx<^7.6MMM$J?fӰX,AөTj**w)E糧N2j@@d8PGGyHppcǰQ%>|p,eeꆆ""##̙C 8˗/=zb 9w[BffGFѱ .L8QIIگE ]p!))IQQqСG~ZZZz-ooo캃.uH@@ѣGtY6oܙ,>|HII>}Hrrr#GLTVV*++Hkoo?~|NNN[[[llQgI7fV~u_+oW6k9ᧃ9<0qAAI#֮]keeekk[YYi``pSN>}k׮y]{3 IDAT=r())R?j@{=zhll_}of׮]666)))8ܹsǎgΜ9t萰˗CBBBKK*o",1iҤuֽ^lu555>>>>}ҥKLMM׭[ 5j_eccr|OOOu</ ʕ+)))4e1116m/<}4 _~[l2م7ed 6d%uҼ̛gx,4x8 ?O^QaJE3%5oZZZZZ^t@WWH$:t(//73&00{yzo5*88xĉ[#p;w޾}{رD/~ܹ6m lyaD"暚x21KO6G\~]t޽fddTWWv/JJJ}oݺJ]ɓ'SN23M,pT5mu.YQ][QUsz'HMu133p655%H$Ayyy33o_s;;;ahh$8EKee۷"""ܾQZZ ((hĈJJJ/_ĞbcŋOIeeɓLÇ;VS&%)B``) =mڴ7obO>Hs"]Q$)C'N;wNR[lY`X 2ihhPVVRhmm‰RJuu5ĉ%8&,//?j(l\Ǐx0$$I:"EW71CFNsm}S7/546-H>|"WP##7o`+A9ظH$&%%azHii/^x-;99999})00ٳQFX/_fee͟?P[[s }p_~AAAEEE***m+WDFFNÇcb~~~k׮qFUU՞={Dʟ3gUbb̙3$t UÑdt:y*OJwC!Df̘?Koll<|<<"segg&Mdii)++;|p FYYYw'wss+..MMM-++@ DGGhkkc.bd2)bΝ]^^UUU|>?$$~KOO_|9sȑb3^~}߾}ъbx< , ;@$%'''8l0̪aر7n]6!!-!!AYY9v옫O?dLX BReddd2d n њ!x9>>>>[˳ihhFȿ++'N_r~ƍ!CX[[7nlmmV/]{ࠡ=d"l2KKK&qƄثWM>}̙ LŋO>K.& .7o޸qFձoͮ^xq\\Xcp;w/***W^>g$+/X@ѱرcgXGw޵kĞNII!җ$t֌*Hd%&DHoZJST+)YL KOKʕ+4 s͛7˖-\.Q˗/ڧLRWWhJJJLL J9sLuoQ;RYY ߿YYY\\\]]XDOOoԨQ簾\ \.^WWW U??*///Iϙ3'555$$'L---k֬{n0-Zt̠9{ 0uTMMMA#GFDD,YDD޾}[[[T*̙3aaa/^i#Lvppعs'vĉrrrfn߾ rʴix^ ܹveSPtXR[|5Eu3Ԃ&5I-fU3_m񘖖%|;***JJJD"Cdz쒒Ҋ5xu։$^xde˖awUSS#H>>>(;vĤj@޸qH?L"O<၊IiiرcxǏE2+((7Yb666222溺,kϞ=ZZZXl&mɩM:`p82`0Q3`ǎ(b"ÇP=< H`,ZKokk522zH[555[ !ɏ= ޽TUUao׮]K(@ m).. ȬYF^^>//OGdgg(Z[[mۆ=–RVZEӇ^^^.ҍ(n۶mȑ$tΝ;"&ݵ{ 2((hk{cm[{?zVV2Jܼ<))I__3GQXYYK&@j9XH Lq466655I:ȃ-؈D}}}NNXo:pܚnEkjj[[[:MN2Ho͠(V__/%I߄RXXq8 d@iǾ"C.")io e[3E%4x:/uCQA`ŋHo"6Ĉu.$''~\/#њ@E_}9-@ @ #6@ ȿQvҷIC}J>;@(}ӥy**j+r8ܗZ[*i J"}L+GpC E3Rbc\<~(**hij"HT lEEt2EQzEgfOs._ 60QP`[U >>v5cjʇdutjiF:t^Fmll~5r9Iz$+t9r3 ѱƫ/x [#cSIZI"1ᣇq-F& S(O/] h5HPUUiGC#%^zb|@ѣ'~Q^М&XJ"i& i㖝Ŏ9qFO>s/;Yx[kkŰ ?OgtC7@5| }lrxᧃ9<0iAAd5}/1woi*CCC]]]ArrrN<0eа^}(p_KK`m~]f޽MCZ ߼.(;{qRz̲PSSy!,m]?woɨΜ^bfzaSc‚f>ԬYmBkk˖ kih?K$@]._lÉ9j5ru+ Ƭ.q\豻~ |9fL'sx<^on &N[КhbI4/@1 M^=.ϓWTpRGe>(yά46t!vZ5 ]"xС<##[w~Qct/^fxPHuǮ}BO x豂D/kήi"/{̘%R2r٘x,8D&wLams3Xܽsc\c<LM`UOS>?r`{@ϟ,X{y;6쬌j-mݮfTP`M@ .$Q ͇ݺgEumEUͽa\>:|O3C"W7146p[ȐddH&f}ׯ: 0x )7I/]u5:*{w\1}ʸo```84nOʓ]$Ğbcڕ, ·IUU)zCMv勧'T4Ѯ!?9: 2p'Mkni%ztdL@VO$2RmnnHRgOb2"47/3#`P? O;9ؖ}9=7'k^J`F ='_<ްv׌mgOzM^(uZ#=AѷDJ8tά%>Fzj՗-[[^tpk@m>fw]DkH)oU5 F̐S\[ua l R?-he3HUk&[+woBKK1oJ$&ZG?J^Hz"{@iIrtz׀}.FMtsb*?~],yڂ|;t1|5~|(TVV9N]]7z&'>f*(0S?,YyM;np=6Grw23Ҭي}DO|!,rdQY=l];63 "trsUU6܈mewIVۏp?ayMۺ}z?O`/_˝U5‚][_J-VԺ6op㯫W#cobǵޛ>vZCCڍ{F\@dI[SSXp([ 7ƝQ㊋ ͟I.3iߎʯ  pܜ 1lPe8IOӝg@Oɏty{9,Od{wmF[GFGG'77?p8:::vrr vnݰs놣G.E#n6CaXQcN қMWO_NN D]=mm2&QIh5hY|>i,_cbjgMM`3"P0ޢ+ -Yq^tQa6Z,Z֯d3$Ö?A~7JRIKK@8s5;k\uyY=jWb._MO ,"k0ISNo`t)2f"lsf$}}Lȉ rݽ'L6Gu֖n\뒒h;ýx}HGxT/sJTd))Mz%<ae0A ?2Ġ2hdy S6nvܘt NaH UN7ܜ sƠ%%EŅAvnH&޿{-ee R44?Ii` f9e]^,^.)WqO ظeo"4ZWbߺ~­޾y] g.^TI2݀Bt=w g\e%,@ /3vdA1_Hd_VWW-^8[R3fyg 6SkKMkb%^ =|ŠѮn)<,`!v\ +MKjNdRR\E$'Mb"F~pz.Z?U5ۡ+.q?ϟx!L"M&X?PWW{>,tOm ';Sx@Sfa)$`*EEDX 2oP@,XL@ūz9f+#޾p r(|y֥kw|~0 C6_ef`g6aɲ5 MWܶ02Q Q. ?mv?taGpsft;NNdk,V$ʥ uTXJ^ $頡Ɏ{ZXob?ڼI b$47j0]l ͛L߹}nko >v")c'B suzdD*tqkm-ݽf/4_{w L3nבIq(K pQF9ihwN+͂ IDAT*\0#Sg/bӗp/ BB#3 ‚c3퉐06٬;B Y;wڏIrp;@<@ +g Xw@ "_ KHHȘ4iWT|G:hllXW `}8Ώn|-[3<rWL+GpC E3Rbc\<7455H$*ZPPPPP`6 B$==J|ш޽{W]]C~|'__eOMMMMM2E=&>>|ѢEdLbeeux<,_įlW4ZN` r@Pd`߈kZYYVVV8pԩSO>paeeڵk{^ĉlvjjcz^ Eϯ[KLMM׭[mˊή^UUuܸqqqq$QCC]Hxxx|||WsA 2f SyL G+*[8Lh棲޴fPMKKKKKcK.uttCp);;;::HMMW>m4??HٻwQ hk֬yXɶ6 ϟJbpB!D>*U@|h}}ݭ VTVTܻ'4 8!Rd{S]Aq8MMMI$D255FD^^̬3$ Ehkhh  `0*++?644|ɓf͚,I/bҤIϟ^/[^'$$L:{5kTUUĖS[[kjjmjjˋpss+++'Od2>|8yYn244[y}wY'eݺu7n b8EGݻw޾}[J 6o|ƍrAȆ+W.\4>>sث'O:th7nNzرcaMlPe8IOӝg@Oɏty{9,Od{wmF[[FGG'77?p8:::vrrau{~ݻw ͽǎ+##Maaa***vvv?{lKKˁ~Z0_k\\yʕ+=qP(A D+d2 JJҎ4#}vww1c[Tn ŋ<O]v-Ng b{v횽GKK vĉ#GlooXXX`#kH<-Crh>p詯\<*x626bŊϟҥK˖-D̻tRvvـ =d„ #FrvvO2uuu . 544^|Q%&&FEEM0!'''66VYYYQQsիCv,}ѢE111 l` :f̘hfnn>}tWWWw5)~_[lkhhJX,-- kkkV@{e55J)_MM-**jAAAAAAXT|QRX,f >|CC` [@ } NbPe42D$fge(؍rC(ok(J~%999''BlذE .ƬoJ:ǵ/^.fO0B ܹs֭۷H$K#UUUH$CCCUUU===TS,Āpرc7n(++;qD+++5DAAx E__?%%G`jj*%Hml| /_,OIItsaaƎVYYYMMͷobo555)ʳgϪ͛iQlʔ)uuu(ĈEufH/_fgg Yݻ,"8}t<5d\.BRJ… XΊ :C Ý;wl KjhnFZФf4}݌jF+߶m?ұoGEEEYYYII6Hp?x6]RRRZZZQQÊ/ B(caLfkk+qqq:::4fczRRR\Ǐ_v-0EII ޺ :88L>PCCCIIK3224hcLLLץcǎzzz***?UEO> x*Ο?`0H$@`0-ҝ#͛7O[[lذa555R Q(?Rkjjp8N,++"0_D}}=D"jjj=MMM***_E=TTT477w榳fEEEm}}=L&"hqqrwZ+++1?⪪*&)ʈ2eee*.*+))QWW͸fPMMM YNNĤ/E*,,ܸqhffcrr``f/HQ$eΜ9_??G hkk$TW/@ Bx:wDQA,dŋINNIQUU9sDKH$U_(@ [ 渚@  W qA }FiI& )5O0iHOVs8[՚Y XlWT @ t)Bس Y'}E:CC?_$@ k3%{dbEq8)id/jh),۸n [ml^ecҫ~&[U@ ]hk:BsdCp8A@ P>?rOQ ))J.(OFZ655VVV(fN[kkUUz?> ]|KݱhU}!~nUUe[k+'z?v] `(|;"U!??<EJ \V>>fCjݩy<^oVEE-MM$JMBNR54zhr=6hC9rF6}_6PbnO~SQ^(99^:5CzZʤ);#ME;zXXt8EIz$+t9Eef={8y(sun\KKMa0?d3DJQ--v#_&PUUiGC# wEg:ٓ9 ef]g\K'$! EZhW RTR ŮWb^QQPPrł"EDPQAJwayrE}fvٙٳ3gfoS䙳Nx*+3-=-u`_GWo7NHloog͞s|$mݸZK[7ݗ]]Ϸ]ήyhhgJRGɚZ[ȶL@43u1 U?746=o"11aS J>ZKk6ZzMkT`0%q,qhol͜Z[[BCC###]doe?оƞ2O)**riDiTjِ1w}72ms]vm8007ıxP_W[S]5_xSa2X2ec<#pX I pݵxС~(<_ TUՎ=p+5U/T)ޏLȉi[7 ss^2 Adwߏz sK?r2h '*)R?hg޾c .ߎVT"y__Xq9Jr'\n+w=+HEg^p{l \VP\͋}8 xWGk ewf 92*DZ "7T>`IfwϪ*Ťd =h3(+Z\@ lܸAQY FϜ9SYY@*H)Ͼ1re%m*jVX RZV߈wؤy48h_|3 >z#<"q,W;][o?`JdaEPZx|Dk!WIn~`:;;nG/\EA'S#/ZZٌBT+7f斣H>sO8*#1<>(**3{;,'~R%2`mS;5 a;.m'L^|tdcY9L/)q]1 U^HHH~,$2|q%Aay-)߾u=}=h GC h!T@hN30U b4tM1m]1J`0X^ lhぬ߿Ynl=Y?">zB$uVc ~ /p*O>L;Ϩ9u6FϢ??{DDd<$NǎPUU_|5۳2-؞z尼&:QM2RS\VH1ze`mnyJRFz*-JN{{a*KfU榥^ 4x[AN+ vҘn>#5lꥶҴ'tt~KntwO?՚ai) W i;00F?YK^ܻmӺVë+u}_ggGmqښ3!J)9Zں/_ܹ܅+ 'C^ >vwvVeU-# ug/@"Q7"/Ɲrb)F]Q˄u5,P(rbū~[-ظ{x%rptSIHHUmٷEyq64gfcSs}#NZJPRRf,LB8Lwf [&""2Қ{‚qb=uێ=Êmmm2y؛ /?KrXV”BZAቕȨ;3\NLyB iin\YP\z톒aՓs4(''-:S}7+KW5׮ߔ򊠬xIpO?<6aIqe:*?Qm%+022MLyhښ ŤFxm'..;0p`,bu bP9״TmMuG{`iL2]һ[Jz?}_Qjik]>˚& Z@+ĠD=ρYaQsW 44c3**NNN?H$2xL&H$UUU<o`hJQ IDATI b6Ȩ;ÎһYm?|,.! A])6agCO%0NvScgOwU#X$멪-m]z-KTcq8@iNpL ;p!D~P_gnnwFY9iȨ; 纴}>5GNʟ1q)uuM@IձJe܌|rZP_VVb`hE˗Ν?w [`7]H&khj322>R8ucooOIq|%$bd+n\,RuT{^&}x^P_KsS\y48q+]fqzsB\\B z&),,2zIϖ[|ogPmCGs4pBBBBhAAX4M!ܻoh:.S53m(EJҬ mrخUeD|*Gj^K2F<>h0"8!A,%e`b}}99*6H HXYO߿g뻷9?Nxz&Fƒx"o`h|<o7m;纔V4 p&)љd eR[]۷}Ῠq].;v>r"өJwcs8&lOd+.gCV$QFV0zb2QBEE :hnj ،S#_v=o&8:*\I*ypo>|`[7Iuwq,1ݚ.*,2TWWSTX@PV 70̊kɯ\훙 %) _Xs6D|\ ma6Iࣇi4څj*֭{=BX1 +'m~W/_8z~^/yy..v+Q۷inܼsa'L9kFƜ>Gk'ƈpX4[WQ\JfDT #;_>HBBB{{{. ۧOp"&㒩DًFz*9YӝgväB*/[#S{G۱7NFSAI D>|}1?Tܭh)L`K>Z ?\g7r\fqt癜-sPSהϞ& M2JHHd=wXY/\ә;r\D e._6}@ldd ρeEf*Ɋ2? ~452w-X4o"3'ed` XSS;A^(S¶INzz,]%5%ulz#Oھq {b𧻓{~).b=uZ 떳Q"(Ox:sEJJtkT*;,&&Xkg|k %+K%Qyy{m&BJKBx1x/?47 H @Ph " $$$3*-:+HEPZ MoF$7`Uu߾uTUm]ϧy/E>}}}Ϟ456* kF{Xedbv+FOOwWWglu*BT5>XU~3e}}&?o\~97rNd:Fht@O_x 9uQl6%V㰸.:188xVll۰eˆMݿ{M+h4HsS^ ((*ы'YuwbcnFctt߽m Xr ,5% PA"vx.ޝeC,zsgNrןVSl=?#cSY9>T&}-,ضy6_}]- F{x!Q{``k|BLF J8!woߴnٸm w <7.+~`o IR@J !!04Й(.#$^cƟ [?}(*BWXh4ѸO$n0ˉ 4#/]g֭ߔ:SUA_5+$S=yϸ[pN"'?)IyFJgL16oރGRLOᄄ._O|vXfƭjʚhV oۆu-MM 9i|ܻ9߳aẓ "/d۹L3jSg0R(S䌝,ͅDi>s`,;K {F Օ]!X,v.uVFNʊ Yy <=3.pf`/E >|H w|Ç>#M\_D_ ~~!Ç/ϏrÇ>|9~e5L޽{wggc܅>|gt~l&::իW%%%#\ȸ@P>yɓ'Ç>O86S_m煅 HL)*ÑI pےQYYe˖ZZZ޺u wcǎ|Ç>S;âP hߑBH"-G Ҿ|"**ZXX044innh>4?_1VZZZ}/A&eW:nU+婨CP.A e| v 7x/f4-!1'q}?{kJ4צ"8@C#@ kaJJ2>v];S@RfoU]]ۋbpm<@ >hDPRRR&Nhiiioo?OpႨҥK7Fe~ zUTT444O.&&.(((,,%->|;2ljjH o߾MOO߲e˄ |ׯ^*..vqq>}:GKWW׃|"!!hee呐 PUUaB=== Β%K홙MMMׯg[nyMx񢿿ҤIBgϗfZ500h"qqnڸsNPPnoo ֭koog /((ɱ444z`nkkw^QQ̙3UUUK?>=PTT#TtM/--MOOѱc xAyyYfM8lvCCCs)//LIIio999KKK@ޛiSyyySSSc>}?HRil[cKk5Pzh-=ԦNr̵d*0wP8,͂;w455lii vZhhvKKΝ;Ǟ9B .^8T*+,,72mmm~~~jkkx/+ϟ?߻w//1,w^'%%bmm][[ gBP(?VWW?x𠋋 UUUϞ=РP(nnnqqܾ:|…/^YFJJ#L& *((fr^4iҮ]a:_6m`ݻw駂֯_jVTTpʴ/88nG=}ǏlmmGs___|__߼<@kk+@UPPxQJJ ===Kɓ'W\jtwwߵkϟóLiaaQRRxH$Θ1̙31##H$hݻ8́Q111|FCbb"B__ڴWH3el;ko؟--[ODiۚOw?X^;>V ?VPP@ ϟ?_PPWPP@ )QSVV& P__?XQKKXtUUUmnnf QQϙ3gΝ=$$DYYL?LYٴiJgg'|_+84* |£[n1c𱩩kkaa!eãzyzzғChggvϞ=qhh2B FWVVr[JMM kgg%Hҽ{$$$XCBB$%%Q(TSS%...$$t=N8akk˔ dII˗/͛'!!)'L^XXcٳg&277wpp>}:=> xTŮ]Xyoq%Aay-)߾u=}=h GC h!7@ bbbfff&MbX,vҤIfffBLLL__ӖX,gϘ:gΜ8**>޻|?ORR𘪕 ,,NZZ۷p5k(**N0>ill,..6;O#""b߾}LpQ-_\NNN^^~ʕpmm/^͞=;88˗ׯ_~1e…iii{{{C촲'X%H$gt,tvvϙ3>b=<<>|XZZ v$HOIJJ"oҹCL deeN,X@ L4… p3`ڰVHFF'\`8ɇK3f̨DFF(++xlHܺu-==}aaaϘۻ1[f+ummmm?iNN'llldddl4,[\\L"X.Z(66Wffٳ"Hmmm_ooo#o߾9tktR׸~uu5kְ=Krss#""Ν344M7Ix90ݽb EEE7oEǽg^,,,(h͠z4-;o 2D>G0*wC-}4Of_ ߯ @ TWWw``F\~},y)**ݻwڵVjmmWVVvsMMUTTrssuuuoܸxիW aaaW//+WR]]̙3+++Q(˗r Nii)F񩯯OKK{eMM͒%K t׮][nʂgU|||$%%+++nݺm6XTPPPFFFpp0`ӦMFFFbX $ .X[[sVPP4uTaŶ<|ʕpիW/^ 1F7lPZZo>xT~膇geeP(Bf͚USSWTT4z222t=D͛v4R744|u``3g:ݭ|mo)0٥|)))ӧĩ.[l``Ç?...椞7.yg/_nll|%{O,]4++ |ܹ3g.JKK) xS[[;}t<۲2N+D"QHH(99HZZz===ϟ?|cehh]WWwl#\rŊYYYK,޷oߣGrHdddMMMyyyIIɌ3ע3۷ܤ?5,pa<3 8[3‚h RTT{U.,*6uJ!AD#0?wlFEEiɪ$L&3%$IUUɉIIIƼL.X_SSFKJJx{ ~,BCCDDD0 `0p!YZZ566dee=cǎߟ>}Jl% Hgt\ ASS1@:vСRy 9K޶|VCs.[ɓ' ,garr2S*333WWW,%pJHH#sl"H!!!{{{aJ={DDD\p!..ȑ#௡wcǎ +; SNgH@JJiɌ+䤤fT6ؚ[*+++((}jYtinn.H(++0^Z~mf͚hXZZjhhHQQQ+V%wbbbzzzΝ;)&&>/_LMM}Evv6&Դnݺ۷ou h͚58$c4Cd2Ν;T*ut;...̄ȥϧC$wܩ!##iB[!' b()kES\<7PA2A?wla˖-999?ߴi#>>H$si!#b޼yvvv˖-svvnnnE^^өG0^5m4h]]]֭j:RQQTee% TVVg'Nd+.ӧOó-VVVD"QNN0uT77s%$$0dQBMM Mc8`?M㈈'Nddd¹Ƿ; IDAT,Yd]7nܸw^++GSE/|Leeݻ ;۔ddd4~~~x<ׯOMM3SNׯ2<?rxYBR޽+//W8l,<6>+Ɠ&ME (d4{}K̟?K*.TWW6a8qxoo7nlٲСC4-44PUUUQQ srr^~Xyy 6bV́u544̞=6<<\CCcXʹs3.pfpXqaE޾uťdmA4L !0q!??[HHhϞ=>>>ݾ}ȑ#8Ǐ<&"!!q%ee嬬,͉ǁ &&&:;;߸q~2I H˗/)%%uMup.**|81tB dJMMə9sSfΜɰ"B 84v޽Ç~uK|:\~3`eeeSSS.s7fffŽL<ݲa+DGG'!!52w-Zh"ɓ'` X[[Qox6^7oބ7/-fmF <K. aii}vߠdɒ;w.[ k*R+@II1 F!jhhdddo˗/jٳ9sSZZZ.\0++K]]ǼH$xڑmL;y˗]6==^t177p_|ï +^HJT/DIJ"kv]m4^@J )-K ń{555 H o>uuu ;2 ccׯhJjj* wPi ]]]uuuK.1aSNjQ`;vڵ3g͛F=y򤴴TKKݝ׮]hooG_kז%%%Dbvv6蚓cB2lihh X,۷D"-X6X999SS?߿?z(w|||EDDÒD ɴ%URR TJJJBBBo޼imm]z5K,bhii*//0 V_0&<{ 7 F{yy!Hxs05˗/ӽp>~)''wq[bq8Ӟ={bcc.^lWh {7F>(zr2iw>^xhnnnllWQQA@"i (x𡐐ŭ[4 ``ccc8i!`,;qīW= ]$%% JMMUUU!cAddVTT={vII A'O*++--(($544̚5 DfddPTggq8\||<A!!!/o:V K3e:v%$$TUU544^qqqQQQ deey%Sݻ3f^qIj__ Fh@޽{ߩ+WL0A\\|JJJ]xQ\\ B ڼy3`0֭[x1}vll8EPׯ:$++kgg'''wQF0>;wG c}8wcƋx񢞞,6߼yAPYY`BXSŞNb111K]b`>}:a$i``F_ͨ[HHȼy}> իW (+++**.X~CCC ???Wa .g'!!fmm}qy&%%cX1111* A^ZZ̙TϞ=v6L={aGGs <x'O/ sspvv߸;whjjdeeq):h>QlCCòeˬI X%j[3gdO)JcRNf8a>` A@ `S/`  3MMMyyy㲫4Fb1RweMBBB@@ ,}X]@@IZcc(=/HmmmwwSQ!a ^NN`+++;j}x ;99ҥKjjjs]|9}iNj2.ܡR'NdOREDD w| @/444N .m|iiiohkk$0%KPRD"QCC葦K=y]]H *--QPPQ‘(##3`B3MXtdɒ6xБ8ޚ (::D -[e#dCMMݻMLL_|?ydHHOևscbb, 222X7~:|W]>rݵFEEe{sN>fdd;w$$$L>V{kAPwwwSS HYYYQQZ!x 6Sj )cDGGgD2:̸ ?W^H$99E >pf` KÇ>| ·WÇ>8?ʑ>|BR*KK//>|A~e5L8SoqT>|ÇwF?w;M+bYɜ#XƸ@P̩?Μb Ç>|?LCc7lv"{ ^ȿTT"H SS]k=&[ޏ޻OW"|Ç>)raQ(h4pH!9 $QXc=e AD{苈hBQooOKK3}h~b  LTPEGL϶ûU~L&P(b{ߒq{4hq??O}kRMs76uShh$B@~-LIIgۮ?Sԟ[VbXa!ښj=ئ#XZVTBI⹜T%?tDE^Ko:@MMJjjv'L Y-t%-ud+.gOZZʪSlE[U6m=.F5K'L;s2榕3F+-z/6L9{d)2Rutzxroog͞Ǐjp` NĘ?1Ξy둗#\~wQ0s]ll3sI7ٯ @?ST-my a0^R=zxLeeUbK#^+fwuv:iCCC>V5 H3sKI\ϟ[KGaRTku zi-jSIvLP*!͂6ުEKK+44422ڵkڭ-NVSmy-ZpDRI׈+FJ s7ݗ)>e:zN w u5ռ~/TL?LYX8}8VBR3lj{wm=th( 2mzUUG-=~MuՋ$U5u pD>r""bFZMk5ܜ#L& **,ar^x zf y=g`@9 ޥ 9c)ϟut3ho߱e}uUT;rKQ/_<߱e=S9}"hǖ>Fs@ (d3jkk5U{V~Ĥ/SKa.{ ͟yQdA,+ypp`rR]'S(Q3޽:µtp.rCe!fxdf x񬺪RLJޣa?Ӛ h߁_ ƍljh4̙3::qD|KIY )Z_c*HiYy"#r޽ca*4rbƛ\wmb Kk++I֓B Kk{Gg ⯅\^&^݂숻p7N38L5hie3 CSޘ["Ys/>}ȫ4| 8zppH$XKZsHHH~,$q"BBB2eJkK3^Z-5%>~9lמ6lpTQFFalݾgԈ 2v2{A#rb^R\f^~ >kX8F oߺܾڞg4g@ uE' XXXi bjz&fffBt]%0,A/R1,'>k|"oTW*saʥW/v">C[8I{{֍klיӬMT$jr'F~.gd p\&: YvS55+:éL>}0 ><|@L5 +5j67g\ woTd73^8s#^+9tjg_ge8[6ױ=yays-Lt"/je(fOSlK b0}TtL(47-ZA;ؚ rE2c-7њA 6R[{iZvނd::%܉||7`hUZhhjް4Bi4U@,Mubo% vݶi ZՕߺ笳E]m͙`%e-m݋rD Jjddd/}];;+𲪖U r N9Fqz^P(eԺښk(J9[wU_Sl=S^98$$$*|}}8 سs'X%())3?kcXVBP;3baQ~@Pli=ZQa 8Om/ĭ-wb^>Yy؛ /?KrXV”BZAȨ;3\NLyB iin\YP\z톒aՓó(''-:S}7+o >yq?7q8CCC[1#9H$ݚav@C}ݩ#N`$計-_ky F> u/ڰv,LI|8"V>NJ &LXUTէM1z^V䡡rb)/3..O?<l9^FYk ##ĔW/ PLjG7GAIqQ}S1L2]һ[Jz?}_Qjik4l HQQQgmsVE~Թ+QܱU''ɓ'H$2xL&H$UUU '''U5h $eX~,AAA=݉njlb/a|%$$YOUUU&߸UK[WK[wߖ`7m@W7Y9aa  .-K2QwBO;w=Ś# [ H$2BPWd ԛd@ X;POu5܌|$ as:jr~߾mq׮Ms$  RR9:bة{{{`[!wbȭ;qBB6SyڲcwLTDD?ǝ>kinJ>kFNor2gY [TUM-XQ׮::;:cq8;fL`nݟb=uBρ~VCzd-;EDD'm۴ŝ =?IOFF&BHI&TڂFJ߼زsI l힩^牉rSs\RIHH>M\f==X-T8©V47mq[Ɔzυn;L2F XFSШT.\2z̴7lLz/ʤ*c [vie{1:b80aDp BX4Ju rr2T*mLAbcwos> ~M H$>A`[t7or.M"\"sIJ|t&ByVWm1Fkf_/ꧯfr"өJkcs8.@N~"[p!??6'[HDY9ήn/{ȴfG ?5@L Mmz`sS#AFfR 7yk|7QBO>NJWk.K|V擭,ݺqMZVmW۸}Ôg'VWtw`+)*, (~QlTVVh~RR dWLO[YOׯy,9z">.f%ӡ?c몪$y,DGJ ?hϡ|YR߿y"i)(: %SopJ-F7{ .[{)g_VU`qb"o\8{h.AeBO^R#NϝpE;DZx(aT+0[% IDAThXʇ)|0 b|L߶]|TQxOx1 @N(# cѿ N+.h49۷t7!2AF0v |'ʻ.\AЃOp===DbO M%SqqTrQ\3!CW)(bQ,xz ' `' "E, HBH}PDw2;Ofg|hG'SГ+,ȇ?Tzv7Sꋤ73'MS[ :Bg>V= %LL"%><ȡ}s~ fƬ>s[[[|ڊC#fyLP'kZZZ1P,[<&+ک,yb``8Â9{gNURVa<(y\N%<k߸lwu 2ר e(<ӴuHh5yHWr͵mmW#GڌXk|xױ8gN˧N8w#566$8n߁ O?{ S޾:q#'xfEKV.Zaq-Z^1NWݸq 8t; ł39i7Zbjl32}jMu1]4Z̕cXapx:sw?Aܧ<#?/Zu°x?_޾y`47S8|)gW`0GaXZU&"CϜ:[fyg IIΉhL&ޝX8NZ_W[Xodlg)@rR/`02^r5u ՟Ju+++yDFi sV88:yVJ*٥H?yj!]Қ8n,G6{; cthM_vފzp`ؕ7`z"^TQ䁼}PGDkִI!gʵNwYTJZz$7׽ k7n޾o>gW >nhd9krK zï5شn⒥bGY޹u=e{ע{˼sL?zԘLfZJGM|5gt52ib_ %oij;+Ckk[ss+!é0_w>MJ|oPvqQcF̜g |j )>yGaLכА3_UN_ ~o?{<8q쐊$;[$vg g'mDfC4w{~KYiQB"c.̜YP"~},jX/_ʈ"ׄ&@YY9*@$Jf.i uRR<?MMuUGG)X,O()NE%k+ # R=yBߵt&Og0qiga0̺<޺h4&)!! =ݤFҨ$!_1 ڦF}]]%%EZK< u uLifƃ`}Zi,` p4556S0th%::z\Qۿla u Jn'޿#tBffʭ{qF Ibx{3^no*K L=30oހ[[mgx1_:>BMcx 7oD'>}v)z?^ ϕx.4nҋGpZB#E-fSs价o\{k=z3AbJRRRi+FY\DŽ{KZ+_APP#TTc?J ?/S23ʔUf\e? o"xԐ̘A??Mj$.<He @~5~(P((((?zѣGQPPPPPP#훩i^^-VCwD=ɂ$~#7nܸg{{k׮:5*|z3?I4a7^TT#"`@ ?+@|۰<$''QJJ*//`aab`W7]]]?>5P :>$HhUҝbCܓŨ(.ZH__+d<2oh&YVg7~sr,ˣVYCUQQQ[[\R[[(''G ***deeW^]WWGdddy-s0v?~|ɒ%nرc̏ qqq7n܈˫1b~X&'' AGGG?yBqtzfffttCQ>}wիWokk~zLLׯEEE++kjP455M8Q]]}׮]!!![n555}*++v'Nhjj ho'Md``p#G9ֶR$iȑ:tϝ;gcc,8͛w}^vM@f;;; dIItyyy2X_ ֐ X]]qqIOOWTTܾ}MD͛7٧߯r̙9s-˗/AL&<(,,|QH56m4ydv dӛhL!Ch4A'';v'Ojjj2 A q8`tLfH$Rhh(;q̙rrr/9r䈓SK7h.oJ&L73֏7#| #D "jekۣ( M ,i/%*}- LFFFDDxSSSkkk ###cff6P< ܻN>=&&GEE͚5 o~~~$)''gԩOOO@ppq]]]_zP(+WKKK[[[T+++YYYrҥ]vquCSS(UUe˖"Ϟ=7nܴiӂ?ihhΝK"5k,\A@kkÇi[Bii){_t0M>====߿_TTѯq7@-D`0o޼2Jdee}Μ9{l2ljjz9 ?!t8x III_`~6vttǓ'Ocƌ/xUFFfĉ~7+y]z755N6}-կֆW^9;;)))/35۷߿_x1 r ϳmmm&Ly&ϳ\OF"4558080'K.UWWWRRb<uuuuMM͓'Ok׮yxx YgP5|Q2PLZ܊n-jf-4I 7idd-**Jь Mp8۷o"##s-uuUV-_^^^c--- PYYya--,cc˗/.^PSS RVV>u,啒r…&UWWO2%00\TT444t00X,~oPooϟWVV.Z`0m۶sK.mڴi̘1^^^nnnD*//ߴi͛֯_okkkiiO4559`pɁs9--ѣG Ζo߾I&;vΝ}mllye`ŋ_x8pk׮-** ܵk앉R__?uT//J??>SRRbpnذACCCGG>GVÇMMMbY,^8==>7nܘ>}$<㣫keeuy#okæjĉ ^*..^r)))! +V耧Z[[,Yܻ`OOϜ9sϟS2ؾ}BIIIRCNzaN+++?\XX8y_$44ٳaaaEEEЗjmm-//gKP# 1^ +%%p^ˈbXVD|xf&M4j(mmR:yNjkkM4 έ AAA{䉕˗/?{;wp8ss̱wrrmwiig6l0uT1118,::ZYYаv̘1v[㢬,!!aƛ6mz1|-N<ᡢ")))..N š<<<ʱܺu7o?O%X,.:$F;onn`˘'L&?3111;;;)JSS{;"\9sFYYyɒ%=={6L sqqr R666x<ť:%ФrrrgWb؝;wDgg1caMvqҥs߿W__-''CUWW)'!!!SL?~_7+{{{==="**jҥS$)--m͚57o:u*Bk8s挾+꺺?ٳ 矯KR\@ DEEakwY9pOjN.^!..7+P bqIV(ljʛ>Mۂ**JL&d!L0a„ 7nɹy@7o,))1333g7nɒ% o)à ~A9YWh Da[Y^^ε&kUAyy9?1bOǏ-CII `رgΜyH䧒 @jj!;ΏW+ 3e֬YׯǗ.]:rHJJ .8`ѢE+Vu>|_|iooͅrA˷oߞ qWPJBJKK---)((ܹsGUUu͚5III cǎx{{ kbbRUUU[[+*M07ۛŋ̚5a„ ofp'N},@***tuu{?} ȑ#G.\x7&&&޹sg^^^, rʀ;;;@BBBff˗/ʕ+W߿?Ü^vڴiNNNaaazzz vR;כ!He%e% <N[[]NV^yDЙ: ñ.H$رAׯ߿@ ttt{.*sǻ^|~¨YYYzd,| W\qrr:|xG߉a{()))33sʔ)ǎ2e ?NoZbb")YYY_ B]vŊ Fww+WdW7nܸ|r8{4AX,Vuuu===9544f)dԩUUU1114-<<VoAss3}])V*..~ $##Ιܺu+P(oߞ?>f :8893f̸u/_ m8mw*zCEҐT- yCE^S̯֍wݿ ^ uuu555ZZZ8NDD7X,L&r-!'RRRvvvjjjg \#2{l<?bĈ/TWWÈ$ICC A$mmmIII2lll<ȻCCCuuiӦ"RTT4j(IIIbJ\}}}2 HmmԩSXrJJ tuuݽ{7{Ð8q=R]][\}z{{;WVhiӦYXXx\O>|XM& bcƌAa{9r$; jWWWvd2>x>`0 $$Ą4&>>~ʔ)\)8p$g[(dž `@7!MǤ_mll$$$`gyggg}}۷o$@5*++#\L&)Hgg'YDDKZ]]T#'UUUFFF\P(X,OԨ'Xttttvv\C!$H A=!!:::3fX<(--Ғ&YSS3b_4dJJJ"ȠhT*4_b0  mdd{H[[[xi@ttt4nv`h===K1֦ZII3)**TSSW{L# hnnPϯ ۛA$???::Z@%K`,o$*++wܹ}#G8?~|ѣGO8[#`7"%%%00+122~p xꚑ>v؊ _x{3AÐ\`Xeee))M  eYvRl#gmذ[w7XT׊ITTt^^^螔?2|"p7Ӣ|w+(((((((?8hpC7$ PjP6PPPP~xHvookk5[eBAAAAAAA4^3#p9Cg(voq%G7S[О8Oo!o*+wn۴mnQwn^liY'>vYkUBAAAAAA.f&8N`DD0 @bgU\^w R?*phll@C3q N0ZտewG6pO:Z756p}oAYe2Yy39շ3ĺX8,F}KLL@{_sLpVUUN</A]O#/`; ƋIϞڏܯg"))5kіv RAneڌQvS_$'uuьMz.$3rggǝ[קN0Ug8xQ\RBhq_=zpTGWmWYmSsfq}}^{=w& J*6\Wg ,O@8eU +! _vd"xjbQ:Yf}YJ3턙rZ"_o/~d3١ɓ'O444ljj<]? ZKf?r@]]ӥ Ud>q9VKLs3e m[uuu69/}j*+ ϟ==O(cr0Mkkܙ"r$ҹNN4 ьuUm:|.M(1cb\mm;]pʊ/h2L݉ԉ#BR6_GV|ٯA܎[ 7G2;7sge>ٹE=[S'غiħ[Zd47oݸKT~V.s8_eenkkUR\uٲҒy?wzWHO)).|S[P!+8uwz'auFE^Ƿn\x:*7p5m?.0yjS'_e,=,D7 v+.TPL&[auM;uTyy9TVZ,Jeվs3`~LRVk_o]07^4޲%)=R~Lusۦwx|^QUyyQf}++.v# >ojfq/%zy ȟ:e# cm/;ύ r} ɔ3f͙d{wy/^Qƾ=;͘b?zEdMuCHh$@]1vos-q髖{[YYC au __6ʠG-*!"Z_=~Ԝp/BfRY" (>b0Cc3)i;;;sk}11^Gl 146䰫8Agҽ͸y#߸k6<]vm[j}Y0>qyh)͙,Lٴn'2ujr^Ӛs CNkkKtԥ-:)~KtUF_ilO JMy~-:nѓ.Րw1IO=iIjkku1?zK(//֨0كkxYs\R!ޟgCEr$ CGFxbJ>dairp`o^g.Y8Xs(Ka`n> F1 ~B^s#+\M>|e'N1= &*a teChș:ꤕK}紸?3Tܳ'Oi/&4Ѻ Sx)W|j2x^jx67<<ڿ[[[w g~m1ݛ<φ_<14,FQ2tՐ4jjlYJ8',330Vv&䤱~l='GLrß-]WQfpDNfS'`B1i֯nq+JpƫАF* '1Lgh4}coS}㮮.[{kثsj6_a ^QQTU5~Q]UyDfLCH;ΜPvJ%%Z0#=ٰ/5LeX󹤈b{XTW[05+ 璢o[E_Mcxz'MpqH|6W]{LO9}2%?zKB1a+g" 'o Ka!d(т=8{豛SlSS1ϓz/^ S\xx~!N| wxYx BDEEy il_0c܂71KiL}MUeŁ|PPZ;mmq~/"CRS5ǂ:붝{a5wU, %"19)aFgO:8g﹆Ƴ{2EK--_ط{Ԓ?^I6'1QUosJLt frr/2o{}Z-B P7HDDbX)))ׅED/$dXF"75iҤQFikktγt:T[[nҤI:ڃ/Mzd {LyN]}'+G:i3昙[;~ P^^l՚SJzu_<6[Z`_/_ʒ֬d`hl`ho㳄p6nJ*bxAAAQ\\mښj[[‚:rxԍfcT)rڟCb]]}DSs {b&[οJ 7o\ (NJln4SrsrD=HGo[xLrąq]==3R#m\&cǻtvv@rrg[W"M[w1cظu{LԥKa݉=~[c|(ym[T) Ǡ޾b255APPT:vrϲգvqsth=`.vW+-%ޣgwH|q-:Z`FKxN$GdU<ɳ$D"Q'&DEE%> 0gu2UVVNAQOL6e%/^۸U[GOAQiע_ZD1$+MDGv&mZ%&MgDlxfF;;qyǦׯ2srrܽbzwo[X? `xt3oϜnEe2 a$G :{IUh٪++~m۲+:e(uK9`ȿFFœ<B#=}؎r(--QRV8rj},BJFhi2^Wk?/{Lw='X0jV. !He%e% <N[[]NV^yDЙ: { ǜۉD=;;;;FǏ %%9ޙYʝ8biN٣Bvl~.^3NiodYXjX,~\ҷX8xttI$kN\`TX>qb@#y߄@/޼Μ6S'NnYoϟ,[N|H015WH #=~y ?xmmmGD '1cDWw7w:YҺw>eYYdeR;58O=B =Y03gϛ9{̩J*7%u ˩=s?gF<j$i.0zo H}]-RPTho,^2f__;IO::ݧLcrn3~"4mm]!Aּikhi!YALlރ׬]yꇏ?6#m§mf R'KHE(QUůdfEyyX[w3Tj'P|勑EOфqϟ=)od8vLiߢ%+xBC}t֍ע5„D4~ڍ׮KsiX޳A zzzz鶶Vیw9ﳳ߼/h]Xam-#/thܜL̩ /hH r 2̎v~hnʉP"Q4pIkb.ij:: LLb}FIY=٩<`muQa`AJ?w'?Ox I\Ztvv52,&YW[:nd1%$$45gtvhTD +P_WԨo`{HSK[xiiSPTbØJQ6dA>IHH SZZZAAĉmmmw񣜜ƍ1{ccR^^`gѢE222aaa5kpfӵkt̙39O%''?{F.\8ׯ_5k|V[ioooaaN,((HKKsuu= SLֆ8k,v(]]] W~zGGGtooo eu񁁁®h^anZckkkdddffXBTTTJJ˗X,TQg?NLL7+8UUUkk뮮yFûd2,q>Je=-Z9M ѸH9w.sBR{KFX\ y\*JRsss1Inn.r0"""555+vCCgbL=}t]ĉ`.ڛkiii4Y,FAd2Aaa!ǏBiӦɓ' `ggsNxlmm% 36B Ȃ |}} 7ng'';v'Ojjj2 A q8\yyU%J 111Q<9qD)mmmD"9rɉGN9s܂ G2AHy߾}2m۶3EC*}AEEeȑaaa0EH{BLLLΟ?/LN6=&&GEE}||~7???3uT' 88xܸq^E(ʕ+եabjjwporҥ]vquCSS(UUe˖"Ϟ{T5 !CWTP@ EVPlXVEQTVHQRtAQiR#b 0yiPyܙ3sϝ;̙OM6w\H2|@kkŋD^FFn:WWWAo߾V*v UUUgt4tvvΛ71᜝oݺU^^SED"B55KJJN>=++x<˗/Ũ̩S,XcsssbeeuYYYhioo777 alQ({zzc3W)So,wNLLl坝Ռfx(޽{o޼AǏ?w\ee%L>u#ɓ߿SCCC///(arJcccss<K7i&Fıfuk׮ݰa ,`=z[^'OliiX3m)..3fL{{;{)St 2T`Tm۶YZZ08n=== 3UFH'w s KZ_I 1h!쏵ͨM4I]]F1hUUUfffvvv<\͛>466gO[[D,tR^h'OlڴADD:(((ƩSf {߇ॱP]]뫧e˖0^İX,P %%%MMMGn۶-??ɓ܊Qf?KD ́(ݱRWW!666<<ɓ'WG89Ń w:99P[[[ ahhʒd֬Y8ֶ}ߥe84G N:6صkWXX> 6577'%%YZZVVV;vLJ[/0 Fv s //5a///X,ƍVVV˖-cDPbs,_>>VQQ+V(**>}sΝ;wv պ/_>}cO>988?~ߛ4V3ro߾' NXMM 2QQQ999<'668p@TTbʔ)#(wZD..B,`0NrvvvvvNNNfZ|yll… [ZZlll_O*jjjeP<<b>f6q7662JJJRRRz 8[z)pu޺+W744Tvww' ? ݻwmEGG{yyiiim߾CNs륙铡Ϲ2qRX)1<s]H(L 0 >|&vussC$>>x<͛7pႪjNN|b̲#b߷r |yyy$Hh4:==G[[[FF&::ҒJJJ|-y@%:t&---77wΜ9s6!;=vǏ3:V@JJT,mׯ_?pgϘ_xghѢϟ?BDDD^^>22vَUUՉ'^|d^QnJJ {d,Ydɒ%'N(**0Lf%''~XUUuK׳0ǎCì˗/G۰|֭`~۷{xxpt dO{A+@EE1?Jb Òӧa㍝]pu ^~mhhxs:u N%''f@ XYYxxxp4Ϩfee9;;H$ƻZiӦ!cPq,/G&QJJ5;w{ Ƞd0DXo o_,&&F٣w^(2gffBkJZZL&QԨ($o܄VSSSZZZWW@SS… pqfyy9NwssP(Ы <`cǎEDD9s6 :~rΈI&ijj z <Ԝ1cƉ'moo/\Ҳk.nE&GܻwѣG0$//ѣOKׯ_z5ojjjjj.uJFGGH$~>}͛WZ?㳳u@dׯ_a;`X9 qpp gbJƒ ׮][QQC@eeϡknnm۸D`0...h4Ҹׯ_x͍G T^9ÇbbbFFFᩩpp`` ++e MMM zzzJEE@ mmmW۷ׯ>|xp=8K.ei)$ ýx񢲲7fȭx"8Vܘ377gE%=***Zv-#xn5sp8f׷m6Xx@vvN vΚ5wŊIIIK,׷;A+jV;=OA^!(ȫ>$ _Y-ko/< /---MMM>}RSS`0BBBA$ӧO,KG[իWaxFFرcX?0jC urrpcƌ|2GZD tPMKKSWW#HzzzAxx 's-++C|ҤIbbb'"mmm]/hBff࠽}`< 'hhh`%#--BΞ=8+%%%..PPP!'==I&$$$""bff0{l륙WhS(3go!SYYYNNn?ԥK$$$6mڴh"zABBB+G!lڴ BaX//K2⤤p8Ժu`MxQGEEaX}}}<&+p;쎙CPPsǏʪ8qJ !HZZZ QrpLrRͱ2z/^ /իW *++;99AO[x_~EիW!  *KKKsFÇJJJ8NRR_ %hkk3cWX3K~ IDATɈj*UUUqqqKKKuFYZZ xEؚ-Ǿ}6ޞG#X똙2e  ;v`O?1ǚμBA!!K.A[ˁQse8S+_mmm1ǏϜ9s̙IIIse(_22RU\p~@3?9~)sZdd$T666,!+Wd^tR333))RuOG3A}F+((CB]x~tvv5ek^xŋM6:lڴ|K? }i$3uyQQQ߿ooo'^^^?7)f O&w @:C%W @?9J 'DQ *O@'@!F?1F;ogWWƭ߰X @3򙦄?gWV͛?L|ʿ LK O_Qab}O]Y=Kș_k$ī;^ <[@9ڏ/ _hTvㄅPBB(  sD_BAgge/y'&&^`inڂ tobPZnjUI@ÂF}϶ݪ~/PO ur X,GK?#ȴFAAH8,D4JB:/~8(7_OVT__ۋD u{Ȣ/*JVWxYٺnr 3.¿?w,yu_O̝?l2󩜬i}z;rooOrR܅l)*(OmxCJzf+"g11qF +.]ܐo&'u8-d ~nmJ޸_5(3nKk&j7} ?xOQIqQ\tQ'[TjµNK$%JKJK/tI"8|;Ko 6[{;iifYwb-M-Zutt#""dr[[=;FGUJ߇]0gO]MzP}lO7𩡾oё|)Eɔϋ:|`&8n꧆zxOOSi/[~;3kvt}4 u>TtYVWeb~m5~?,=P27"aìi~lp55US&)JXXWm&薾/-x}ÈF0c/q!~΁#6Va!Ss+jea񧉩qpғggf09mGOUkEh4Ce铿,(W!mqɿ+#'#m]hSY&&%\u[ {&cG# b/y `d턅਽wOMYy֮8,ʝ:}|pֺYSEd\N_Ϲ[{rΙ6V*v 55Ugt4tuuFErǘ\p.ևr p>Ę*&Pvoz|xcR(x @C=UGPOu$} ,?s]eBdNlE]7}z^ 4~jXoj4ZJ8Dˇr: ڽ)֭YPY~`-wrg䩋]?ζ SIK J}6E{~=,'l?`MM ]*3 e63X5bBCH$ISxG+).:!yXn5ضkiܖ=+Sy~)% Fjͺo{DkkkB=yŠ7u|Ա*IOo߼0NػHl{ks&ho e X F8@-pAVWcgYqvϮVl-X1?II>x I|w4&6*Uaeޛ,!3T*Y.^x=rho\bJiU4}sVrDԵy YlC}2s~ٴ5;3!ׯPZǚ\ G3"!!Z\\WH|,̩.x**.i5%AD+Ơ?6fgg7i$uu*FUUU٩k2~)9pOCSHIDY))i X䩯_jj23]~t L#/Mnnj4~HK]aYOg&zS3g9+(aE8<^VVB%̜Լ6B9yku\%hfdM8n bw`088+_C}sk7>qw] 11Q0FVVŋYHHy{RB )1n/sxn앤ghCG{N ę5vI.r3fݹyěDn¥h11qYY9!!! SQ^,))iY9y>KȠFm|[3)BG`04ڍ胃C?D.^64(!!RThI a QIfCWu+GKDpa Sf-OK\^QQ~pN Df,XOjwז/r Sn$hK)7+++ OC08.%|'4ϟeii-Ms0Ctt`_S1QaA'<|,L'Y֭YeÚW{!Ԡbyñ8{4n5t|uuumin\ P&XQ8/uSJ&%'[=wQ]CkXEUV&(ρG3̛+ܝY]90Vx6VEFߺ{16(p5b%)8++-K b@%=h3u(iZ\s~?srnyHNhhj+4J/F{)R7n@ o]?~'9j<3p|ň3fϚ9LR52@9rcc)^XO\BttG% ΜWP#`mm}&Q2r_-rv{E^Ayt!+Y'\nnj\hpg{h,e?e iۍ ORh&2oUQQmQF8y?x_/TX.es0ܙDDDd߁^6xuAi;h׎p_} >sٻ:[uC2e8—eOס ^F KDnrB22h9, +):FKw֖fQQ14=00p^-SC  -wS-'edɬ4aaaMm6U *z-.ŧpnB]S)i/]uuMi6x%yB9w6B475*-X,vq1!NrM- &WctwuuDN0.D]S훿5Y:(^0{Ņ+# sȉGҟ5'^닍gbLsGҵ8ml5U*?TWU{>k# ep%N\ţO47`Venro}C1t{.nX­jppfrTmnj,+-&,~W{rkoߺEk‰*n$3I7鶗#~ٺyǛ·o:mlG\@=s5ϛ򩡾CYFGːs=EnX2f*5?VΣ E5prr(ddPD)iQED E+a~.koIHt:XPO&޸aPYlIv192,¥(hZ,RΗ|ʬ9&2b9MHHhJW@hX4Qv$-<{Br5 YCh¹voΪ5 EC8f2`1YW wgN5gǨ'*D{k qjTj?c8/)%aJAV޲mنzzJbkρǏj(y=w4~j@Pgea(DMMcMWh_|l0xUCt&Es e_~政ҼS&/r^Ojhj#^IqŐ7δ̨~g`bj0 y Ttwg_CMMcüen{_ޞ*ڐ fppq}}AQQ1AZ}}> í/(}A7Z;0Cﯬ,WUS_|޿s_‘#.nHZ; 3<\n!{<>3mok";F?-Mr,?**r5Q y4 HYiIB|Lw%zuǍ<3̧#vo)f/8t䘱*?tVJMуа>߉g9Y_ >: &h?d;(?,!n+\܇YjeM&IJI%%\XS=?5pnmi6wFG94G&tz xZ@c<(N::m~<%STX8ǑkWKK;:ڵttڪ Fsx>U̟?Z7mGNo|X[[;$$$((hѢE 3#`ĭ%#˗o/IDD Xl aժUf '2|ۭy왂¯nݺh泃pCBBBƍO#96aU0FGGcg~"  Bј/װ #KcK7cA:ȴܵk< TUU@ ~ 3***˄E"/^d.X@ZZK?nii9T펆5vky<=jcc3ȺGn+o:C$,E!Q%5_tݍ֑z3fX.ЅP( a+BKJJ ?pO8BIJJr!y㨨 cOOLJH$:88,,,ӦML޾feee ',ccc)))GG\7aaa{aCU{zz****))\0 L~ɴiΝI&ܹhmm]x1HȀ׭[ ۷J.Y왎y12q8[ˡ65C%(onngcR(x ?DRPP8x {\'''4~@{{{ҥKd2ppidd$##l2X tɓ'ٳg>uTUUU>/8q /^,''|r"Xi+++߼yk׮F2 'PTdɒ%qqq𸭭-++kܹ =VUU 7հ#f^xamm----//~oͫW /_?,`޽k׮E8-2eiP̙3D*b JLww+eYO N?r䈲j``ޟ z#C p`"ͽmti";\ C!ZCZ)tDFF~ZXXOOOBTOOzJWW722r4y)++޽{ڵVjkkctvvϟ?×*@]]ow{._ ;vlppp}}™3g`K.v0gΜC _xq4)//ܞnnn>}HOOsww /~~~aaa[l:uKNN̙3a*"XSSe___(ȑ#7q+UUUfg:X5nĜ?^MMmʔ)|PVV6{_%YOŋϝ;Z^^.**ZZZ R555 iKhFT#$Aۻ 9OE%$cf&M^UUEјh*uuu333;;;:7o޼CccgϞ O[[Dt,ZZZZ¾ɓ'6mrpp.c111 d2qԩ?%|[^ թzzzzzz[ly|,İX,bP %%%MMMGn۶-??ɓ܊Qf?KD ,( >92881G 䉈mmmܞ <<<޽CCCmmm~ 044LMMeIebb2k,gkk%P2QH\\#SNBڵ+,, ,--+++;04=۷oΦR:::0N@@ф  KHH IHH:<"**jŊSD"1;;{ݺut4d*0-))R'O<~fkk1͛7?a, ,, 555XJ )--ʂ!ə|#+u)+GKDpa Sf-OK\^QQ~pN Dfllllll6oޜ[XXqFMbbbeeqpp7qӦM𰷷oiia*q۷  L>9\lkGaWXSSr 0Dh?3f GP 'OEee"1(((%%@ p+ @VVLfBnwpBꏰǏgffB[XXXXXW^kݰaݻ-,,n߾ ?~.** 555;w,**w|G!UUUFFF}||dee֭[3rqqcbaʕ/,,vZNNNJJ >Ru /_p–ׯ3Na0SN9;;;;;{{{Co!SavGZMMM;$?~\VVVVVʕ+7o~qrr NYffff׻1j2?DGG{yyiiim߾]JƱ38w\KKP---\ظjժaUh/J8 PFJFa Gԯ4AD%~ĺ\f >|M v H||Çx|OOOEEś7o࢒#--}UU՜hh4!VTT,^W\/pLtt4Jx;}*СC@iiis 3g mmc>~1pK%**v<{ٚ#>ggE}|ddٳtĉy8;;<2#Btuud Kd,Ydɒ%'N(**0Lf%''~1ccc%++;k֬7npp*Twwn߾Ãㄋ֭[aovG***---$!466BS|ww7 q:::fϞxp8h044IL"\̀c%gpرM6yzz]ӧ0|=9 VVVqm[[]w+,AF KDnrB22h9, +):Vn$<ׯ777聁={hjjjiiݻn%** mԣК&,, Md29//JFEE&Һ666.\x%͍BQ^ŷ;v,""̙3#w-//111쌈4i&--7ocMM3f8qXz{{aҲk.nE&Gܻw:d=zu޾~իW&h *M"x eL>}ͫVytv˦&2W؎{UQQaؾp~ T*ttt@ \vmEEÇϟ?.۶mx<>3g ӧfWͱH>dOMM?;m逶){)AŽqDzU"8.TLQTe{CmG[J-CN$'yN$yTjRR$:ZhHܷo_}}}ooϟC_KPá 2pZg޼y^llln޼<Lrr2JRUU!ș˘"##cgg Ad;s̈@~eGݡMPvޓHOkB)HzNAw!ﺐ7m:팞_}?*eee,G&YH>>>AAA SSScxyyym``YD=8p Hr Hee%t &&ݍ H\\Lf)$IKKKAAA±c !6feeihhhAg̘Ԥi4}`Lݐ}C*++PQQQuuu üSDDDPP $$$--!/_tp#""xyyMMMMy4ծ.MMM;;;2$%%wލM]|YHHHDDdÆ sQTT@_DDDOOA a C V^=|7nD</""f~ii &=zYK"Ξ= /_ ٳ9wA~xq2;88 mlK_^As<<<`_bURRRPPprr&peeeD0|,U4Ы),,TWWpttloogi``W^%H %ү{jhh?P#g.m444 iəhǏ777_xǏ1 t\*20QQQӧOgѧT*czSMHxÄ `@Ocqz(**266燶>==t ?Mӿ|2JQTTvvvKX,Kn555CpchoofFʩa,h7pC EEE/^TUU9seKGGGII2\:dh4ZUU F  R__ϘP(]]]l3ikk`0p< Щv Bs4440spoBPI$*++AByyA%,Ф2Jmjjbn&gKdd󫫫Za?A$77744DВ%K/)$===wihh]ggggff0?W_J_ NZ ((((fl.)sd/ OaьјwJ"X `@:\<O|_lAAIyy9K3Ύ:C?4?^tw74+"߈=zE驪,&? IDATtzxn<<"">맃*={Nrjgmvyp,:D#yGh4ڏVeN"_Q^1` I 5QԄ1q/ddeML[Y*>W,ڲ}0u@$OjkTU'XOz @PPj߃܂ܙ9 xbA'S}c gvf@}H*9aڍ8 ujrQa$ ֓9hZ<#""b5d9/⳧JJ*O|0{CCtl-mnOd&kV.>}e+0G+,Ȼ{o̱o$'uwSuFu^HΎ{8L[g@l2W򹨧g8\\m1~sׯ_<_DUMn&ښϟ.oaL=z}qg|(ZCS!=}(%-clb}{Uz=7Si1F#.zF!DRcIuj;ږK}4`2i^TGYw-X74kjjiii54O629gł/*-F;ZXDLSS㜙v;51OϡО2Ł=;޾y}C`nj+C˳='Ӡ4ϝ5=$QTLŸ~Ӧw)5;6?rBJe?9';KEEsgMklsU5*2pOi*uDڛW*& dut9"߱{p{wn1y8ͺMqr} q䑭)*c^c9}NNNg?rp^GOr9}..BE`ͺsBsQϣ9ۗ2GZ~S'pn$R7kۆ-<׮Xh3~CEioRGP`{sϽ|sA1O,޸'.554E?]t<#Hq10555g1KTkhll`tt9kK y:oa.N}i?wlY(i\{I-rԉRsf}H 455nZJWKQIVx1 L}4PE^١~8EqZZCCnٱy(B455z/VQ[vyKK3Lbj0uBg3~I/oj?Pt񨔤s6]Z[[&"BOO7Nܽs.vqR'KL4=yd>&O4|y^esYViׂ.u9ݶ֙3l"a{7+ΧO35 s;k(:=0Ѿn2]-E-aД3t:]-EQ37kᾔpπ>NZC']sB^!斶GwtXu)H}fo\33S(U _SU ňt3#׆,9yvo^z Ҷx\QoʊӾފJJ1 o4t/n{reIsYRRϟIV-[x\@KWCU땙_ 4"8 t@_PjE|4|*ྜྷ&q8D$ODE2s{lܳoE{~xk#]=}ƃzzz^%'o֖e bDqQK}lhh())cWRR"!!7F_EUe;pXm=cCn*ky'Km ٸJKKc< /\~;TJRZ]ClE;< #wjjxl\TQ^9;qK Hк+!J;6HIHJIܞ;Ӯ1W.TYNwي5~gY3}))~p3⑊:`ˢԔD]^!=QQ11[6嘌5b,֦oVsz 4 HHJ+(2s;,z pBCΦƦƆ̢1t칳>{ص&F鎉VbNojMggGA~1|"f~mb[Ys3qSʊKH^vcꫯMוG?} Y99LVUHzٿqy}zxΊ6޿S\\7қn?k ̴+R/!26ϣ=G2( uw/g#e*вR9y[_K&_Cnddf+_8b2ּXJZ0v%7#w((S~ڷ+!>T >N\\~xxCYY*yPW[ t׺H{Y+АgN>~ϚMƚ5YxӺ)]zݖmv=z>U}hoɂ2AJ"+)Eͤ􋮞>+VyK<~x_ZFv 8/JL\j ywNf=|ArqvCn b8-..!..1w!nLx={GU)MVmidl ~ͳؔA x9dFw}uwhPs`̒ekjjs4gy%\.+v]lP G3$A?ADD"p + 3DĥOGHۇ 0AYR򹽽kggg܏sD(..1Cwe۲ՌgO7P*>>zy1g)JJ*g0s\7׹--ܐJHJ4~d۩DZYI_ߨte}TdŨ+hjj={dr7iR2pŔd{T|$qVWy]<3s -AjkEBRו47=|1vߟ)Sǚ-]JH}|<EEƀKضEw5=ܗn^ѳ V0*7'P(#΀?I|b$I1FVr%7KJ`q<b|r|x^}]-?RG멛8zhtH__Wa> UJ"'%x5 MV===opf ~SUjk^TT,'L njtϒy]]]Μʵ5,B {ܳ 'j=ܗ^ v%f}}ݡ/߹zmp"dm=ψ' |?|Ukj#'_<}O=u !8^jjjܾcђ$"YpFOO훡 dn<|))~:u=7voaБ5)}`[L0102c`|Rooo7yδE|Wg4yz63rΟשT*u* \O3Z[[e&2F0CPzKgOg8>|qOuK/S C&@LcD0cLյDD1b1q ! 8YqMފxb0* wXt{O܏r_7܃0?.kNk|58 zWI*"QOm5S3Ǚdeba9ŮZp5TTLfXum__"杇7CiR0eq\{|dY!3#Q!X,jB=-J mcђU^H$1&c4?}̠kW.^>q!!kaׂݐ@YXYyf5gֻa 5gk7P\V¼NyirW#]5}ͩ6[y2m&ʪlXTUQ,[f;7UΞ>6-UUAlf~[[MqX9 sƅ,t4ci)X/7'٦&V ? 8IkWzz{.} :a[Ln TĊ x*1Ln%驍Qڽ7/=yrfӺUZ*zKŸ~B87֊.s{`.cD =-X{^n0$xAPx`Ѧ;>|x 7;& ʠDEEYMc RL110tw}_1nǮ}/&iKI?Nv`^Wggm]mB{U؊Aӿ~GchƒP(""b->>~܄bYrd)p~5U,UԈY95UR2]l} 5+755F<`fߟ~8T %:_F^KYYu nZ@::;;Je34JFVn F P/!)tsȤ P[SPp LOwwqq 9P{ .UA;UJ47124ǖ'+W1Z 0 R~+CzAA hiyfh#woܲCo mVvV?N:#'FDm~:9w֧iޘwo`=yȹqY ===wnƼxp5|x0KKACSK]^۴԰q-+nM9A1ioo/Lp>5 >]MUJ/_ef993/E%0;`= /(?WE=,,f8gR F3+G9"__D_ NZ (((([ot"O}}}ww$٦ׇM=Og7ҥKoI4էk۩t`1 y911nt:FHmUVVI$8`07QԘϟəY[[*υ /^gϞa ӧO?|PSS>ydaa謬7olll$%L:;;sH{%N ޽322rvvf}6!!aƍBB8 ///999??nɜ177za]]@EEe„ f5jggѢEߦ5k0G˻yf__߬Yƍ|+>>>66B=z…|q-333=z4[1>}*++kddAԒ+WvuuTSɓ/))9{lF`oooHH ݻZZZӧOWQQ&ՠtĉ:eYz_|ϟ544555VWW?ydҥ?Afee丸0G ʬc(^h4:Eo7v;uz}/,R LU$@΂۶m322233 Ҫ733366޶m5{Ç\Ah>>>w_466zxxtww}RSSۿ_~}왺:Jtꤷ bccW\)..իAAۇΝ;Gޱcfի'P()SDDGYfRTTTSSKMMMO> 7nѣTjG%%%q/44?s!@,X\'O@&=zM*0<řcjjqZZZLMM ҘtWWWww>qhŋw#;=bDDe]c( ** GWWo9rBSCS704u)A(Xv `0X,xD"H$= sΒH$"ˎaaa:$$auss?=<<>}4c 94?~„ o߾IW\ $$0'%%#NssիWØnlltss]lYss3L;aoo/_kii=~P__?w\111x5k.\ Ǐ,(((twwt͛7axmmٳō9bjjʜ*!!aرJJJϟdddڵZKKkTBKKK@@#crH$:;;GFF Ť]]]Aooo2,--?7odѣ/\t@[[[󵴴m& ,m2P|14mڴ_fffǏe%%% ̸R֘6w\IIŋN*..fNۿo3l0 GWVV㤁$7oލ7uCCCRRKJJjPoZ[[JII8p1===33sl puw{]j**))aѥvvvbbbJJJp>4{3K.UPPb<4 N?r䈂ׯu5`p4[IkkNX+$v.kn3Lppx}&qqqILL|rCC*ӧO?tPii)tp0􁾠UUU/_,//_hJرի6m?~KJJ Uquu+--ݴi͛aVGILL_D__񠞞__  ZZZJKK]ϟgdd0*++:txzz߿_ZZ:%%ĉTBAAJesŅ -,,8G2e7 {Ųe`ȕ+WBBBMk:thϞ=*gRRRx^<a ?6' E "$sX^Y!".=~=BGh>A>`Ds{{;߮]:;;]]]uÇI$RGGGQQQFFo 3fÆ Pi2011!HP .נp菆|LLի>(??@w>>+++%Kpi=(@mXGGyϹ*-Zm۶%Kp133۲e ܤRTTK]]76 fv;)))MKK+44ijj6mO$b̘1˗/YJJJIIId2n ǎ۰a۪U``> VVVYYYsPaP2||b$I1FVr%7KJ`q<b|?R\ݻwkkkp8Jݳg޽{(~~~hdV\\*žIozMMM\\\kk+@MMmҤI/^LOO,Iquu:uR_pرӧOþMӟgfضᚚ+B)..VWW'Hܧ1 *`֯$TjSSt>["##ϟ_]]=g!~4 Hnnnhh@!!%K@_vSH6{zzܹPWWyI___fK& F}}/_޾}Ç,===QQQaaa,ǯ^_˼l2Z,.?55500ʪ,((~d}4ioo'---((B@,k׮YbR$&&&''̛7oʰuS=(S O:NdFFFFFFCߥ񗕕EFFx Lʑe }(((((((?owA+(((((((8;B<g\~)1~):h% `k1뷌X((((((((3[S gABӧ<}%?€ꪶc_Wy} "{nSP$O S^Vcӎ]ǚݻseg4?)H(((((((?G3GcFOP}j*c1X,@tp8>*"}ݞA''f ,;;;xP/'_z~#3[o} " 7SC#-ZHAI%FMʂ`]zgMƵY@nFi$^ w+*jyxyCC_```PPVCCksk+{YK\|NQPP,ϻzhg|E޲}Ehjj3nǖu=ڣF֞8)9TUV}_ƾ8g7ϝT8zh |ehyvtdT湳>D"]oڔUn7&{ǦGtS(R'dg?z`㗗}~F\:<!iW6_NH{jPdAܜ%b;ݹ4ޞJ7-ߓGnZ++'Hy񬹹%榦D?zpT T*uhEܾ*s..7Yx^ƾغq"Gn\1#0FG)[Y9Q^ƀjxRʏ~d57=əK]\ܻ3=mZTV#EҘ+mݸsO>{Q#FDTf=kI/OUk_uik-X=.YRaq1u]t#G3UTG&׭[g>~j6 IDAT*@VAӥ:{y sKIql̳^ U,ɱC~)OIgI:;WU874$$$/\ ?Nmߵ%.ZQ#)o,|.N|̘:sǦ["1b79кimckj3zu]{~+tb;IV&Ws_Lz{z_ !3gϙsprbekm(睞 ?zaHf..7YDE^4A#V{{[\s?NJ\7:W `Ӗ] >Xb PQx=L=KD?ڵmÖmko,\?!k#3Q/J̀3Dx~`e5Ƶ>u)A(Xv 7# bT5Fyytt 10 " 1X]ͼs; ^߾ ^{/ -s?-k:iܲW.w:QSYjLoaMVj)* O4&Zȋ8;44#NKKsh-;vXaU755z/VQ[vyKK3Lbj0uBg3~I/oj?Pt񨔤s6]Z[[&"BOO7Nܽs﹀.NdIƾ'LקUr䉦\:1gmMm֖3S"qQO"? Dig~xBNV ^#r{G~͒y5 |@zQz +ATlb㰀ll  bÂP4]nʾͅ$4'l;3] y c0p '2o}u ®'96UĖ/L޼yelj||[XH?kbfd_8/GFF}:o-5;',߻MOCR0tt/^দ gQS3*J2"$Eɰ=;*'t[9u~QaBӸ82˗3\_2f)QZD$KRLpF߭MT8{O,osf3WPS痴؛ ]ݽr?!4,fRL 4SRP(6URbM 4RrdoXbem/;qg秺osayj$QWn=q =~Bx䱢Z SǏ,>K?9r,ckTL~!%0ب\}e9Fc]Fôj/P(;mZ>g|י[qLxlks |_%E[zs$2 ~D#m=<>{䬹ϟ;k>VKwu}KvM vhcН4t6mLxcWpPYQFPl@Q}RNNԂs};[XqN hkk=߾E|>ŤioɊ=}) tߋe[Cvldߣ3,c7Қe>+JKD3jUURRAh+dI,+-ٽsÑq8;x~w/ްؿ% ~tVθz3#aF4f:e<{|e:DZiWBȡ֔+%$-٥lqBښݡ[.V5A}`Ph\%)<_孠<\?)&m炼?sdgD~5~gpǗྍD5{zaϝ>ާ||k.1k<%3ۃFcj +.wGa\l?>uXQ^ ڌ$)~4CŢx^4|,PMg=c /Šq?W7`llXUUE&cdrUUO#t_ū2Nu`%eU,+$,*i3hp8SsW/r2}V;:BϬ qIURsSE1^ꊄEG}!}UHj$ ?"]%$D~/O p8(). FFe%-Mq {m^]{OTSS0~ ;SSqLPUqJ*b<0p[XϞWG@@F _ʪZ:(jc*u>㯾1WoerNVTTP1h DԩJR+/tmw$;ׯMmݬr98xIv==ﻐ0̱,$5% Ƭ &YZOj}m׮ܿ|W4]K53|b=[PϲNڏe= >crr 9Jwt.))bJ]'[[GOWOu>SQR{zQRV'ǰs׎ t-}zrxZS ^{ '3n]m|`X,Lz9F˳=|W;LG ܾhrAyuWʕ f,sLKs@㜬k2x酄s0*py^֓B$`O<Ʈd o`b>y$$$<vFb:9pe>?y?U?#Nj[8|pRJJJ 43斓{<0e+VӧiW/UVVjkkN ;x Aطқ:{ 3pMq8b?B߽Lj>,&1&ok|_\vzڏkU࢕j5@Jz<جRBR `nauhRui ##0d[PvVfooijjLhtbmŎcx䑊*܄ wu徫qB|w[9726326-_n\(#Vm}j>vwu{3ڃ 55!ۂJ~Q, 7sʹRK>~3 pu$rĀ%\I6%u+m(4ZHX֐1zD@xY=Qʃ@y^ uzPVQV/@Nm57o R҉)7:q ;!݉Z)I(j|X,vރ3f]=p_|ez<\jkmerzQ?Y8C""* Ȏ]abbs=/$ůX6aʹ+g, V!yw2sGhGTUEKV455wnjnuXb&pɈh " <L[_ZHT!d*m+AXKmaAU.>>===sy!r5 BwwweeEں?BBGNi*=ͅ@05uU+/pKr{Ťad/='b0bPRVM`fnYYi >*ȍ 8搞ΌYsvv~<9Ë8uv+@FN^O`Β'>{GVR1.+|7a vlGKHJ*)(Q1/S-\A=3LFw , #-iiZ:^ r*MvGKa뫢'Z  1,@8 #F+W@=7Bo0sQHIvUU MPG(&.PQ%qI秎kΒ^vΆƦgv%MܾqH`0 ewVC#]=I||֖[i)/{$7HN|**47=~g3%ݫؑ8ƫp;$%=u0lo4[+˕U /$uww}ܙpNXURcEeG&w3.`ODt1dp<0yCԔ}O 9STVijlhmiXln?̼C_>8wQl)Ѿ!e< j9>dd8 eXZMZrڕˠbWR/1 4m{cKsׯ_zϟ;8YcYKC}ݥmX>u&'A;΢%˫W<ȸ PUS穮/m^/85m^y px]Z~{z$3g6q4v ϧ)C4Ubs<1ׯ_Y{UxOgEd=('voRTT626{q/GOD#݉b~wʥ).2rnufv  ?y8A(C`LL-N77Ҳ6A>KcEDlUfOw$2r.]H#)Ii*̚0ƫ,]#M 44cOht̹ :$9\]m͙I,6i* -cJ4m oK': KL;͸ oU-@҉>I&hb)wZ@[HO}J2"N$ueNᒷ#i;78Tql00O_=$;C [ ⥾7͚!,ljL<TWfYjycC= X7te! J>~kV.VQp%<TIFdC? ?,=}d c9n+VIYg͟meke瀱9(QFd[ǝ:uy#.YIW߻!OR<~$1u샇O,zá nIQPGELL|b@Ey{K}-S6o?8RV2rqG3k]dn5QKQ_[ :ǔ S0ȨIa2o%ҐJ2"ilLe] yB9 :~*6kn,gv=RL.1>kLMI!O$]IH+pƵ cMo\8g!tS)L %^&vF|X]S @U76m.">TUL2#uz{z[s_**tŲ4|>&k%J7Bv!!a4 tcSf*_@iRUSguGG;R8M Rtޞ+Ƃט?.ATzx? JS\{xytY]JN^anRM RY4*ȏ H{[+K]eYHW n꭪>:_YY.8~. bcj4L5ling__UU ˥c?9: 򾲜H?aDG K9@(J&s^1,Iu}GIev1DQG3\L!E$uͱgF7iݽs6[>/z[TpЁнusm29v-9YW/''\l?҆)K D$iГܜaOD:.\]4Ϟ&Ɵ5IJ8zjkjmif/i,Ls~:o_*'9yOW !)5s(Wq :jj#]k2VW[~zyY)|F3ξ?D@/W&+.\pG q… .\D!?^/R_ LW\p?1.\p?WVM6uvvN8ƺ .\p…n&!!eees C9<<<<<) .\p?[LC͛7bxXxsyrrrg YS]]v۷^pygϞݻ ?J\p… 3,d6s"yx(4B 48uO۞~ ++۷]]Lkk/Reh4F˾ydX ܭQo<ϣ8iuu4/ο\RO Z6wQxiX BQ 32#"*3{<O$kjj8CPD"qJ# qǛNY_~WVVlx;<==;w|PqqqJJ JpwwfN/^577wvvfʘhdd>ғ-[G@@ɓ'RTTT\\Ȳ@FZwww!!t>\~A*J{ii=ZK7Mm$'"SLa@,objjjgϞ$Hׯf͚k.99'O(/J=x˗KCvGGG~mm;wzj%}qtwކ >}z &lݺ>aUXX8,>}rrrڼy3@[ IDAT}}}RRRǹ( bggWXX}v'''?~x Ꚓ髓aaa'N\|0(@kt/]qFhɓI& 988Ew7ayy2;::|}}o߾bnnEP_GBI=<<s-___CȲ |2=Woo}xoggBdC_a'/__~ؘB1ĉ-gϞuqq),,w޽͛?ʮ@FZްV0·ˏ$==DooҤۮToҳrh O[Whed?Hz*v{{{7Aъ455Q(cNJz{{{{{?B455iTTT膆eӗֱWQQ1pD>}Gw%%%x&((=̼'O~yYYUc o__ѬY_!KR𠬬 aVcݺuSL$88xzzrH 8Y^^0 /=; 666iqdd=11ӵ޿Haa j ?ncc#..s}222tuG&N($$ >}ٲe [JJJZZzɒ%>}YH$Rffʹi>|x9tM@kkܹsEDD444`~~~Giffh4 \]]axssYDEE vmbb˜+;;X^^ׯ_566HիWGӧOxˡ4#n T$""B_|ijj:&LIJJ N7{l999--'}0o<, L^zE.M2ǏXKKKyya^8_##ǎ RUUk ._\GGGZZzC=)) =zhڴi+eu\YYY *x<>33s``1˩S.\I$`&˞vbUzzz8q~֭>>>eӧO %Huuu]]]/8<,Á3qQ]]]8!DdUUճg2U؇ҟ%KGvk௝,osf3WPS痴؛ ]ݽr?!4,fΝ;+> QCC竫;wn,璑ټyҥK՝ӧOv߾} Ϟ=Јܾ}̙3 &?~NRR0gNNӧ~ԩ<<|~@@ƺuܹCJ"##]]]q8@3i(WWzSSӒYHJJٳ'00˗ZPP">>ׯWQQ 8ի#((FŇte*c @RJmm-cL!}OOO{{{[[[AAG\hѭ[fϞ `_{}\x<ήJ}9$%%a0`>>ɓ'[ZZ!MbbbN8k.]ܜjeeUYYw^z%XYYiiieeey Xpa``'H\xS,KLMMUTTJ||ŋcuh4zpΨ5{ꕐՁة=`ٓ 6sȑRUU5k/^:tek׮߿Ν; `0h4Ŗ>zHXXXBBb5ùX,LNNNR ƍTUdX,vΝD"lș x 8~Fѷpv cJ Sisu3k׮+,,tիmҥK'Nv3sLE9::Ч_,fuƍ08I&1&+\v|eddP4pA؉UVVJII]]]=ƸENNhӦM===Lw`hhtbbbmyug="H@~&Yfm[111ɁsNofffffX`e8`Zjfff7n܀"Ǐ_F{zӦMEEE|,JOOoDKĮ\"--xR111kkkh KLLd~oo۷o-䞮BBBa644ruW&BD?b1i}_ JZڻ"4L |%#B]]]|||AAA===^^^\xq׮]쒆DXXԩS򹹹PGJ_ŤFfGEEܹsZ={XO999 ñn>TUUEEEϟ?oeeGr%%%۷ Bhh<<˛:uNJءh~~H]]5k@###[Xa s QUU0aBFF} HKK#:::rDv˗/ܹɓ'sJ鸹͙3ӧOP^^^ sM2 8n KKK9bY:㺘a .%%G$(qq/_gmmh"H[[[KK˄ :tyK[K,X~E4 :6l fffك3&''߻wfn",;=pptիW ?s挫ҥKy.^^޽{YLJUssVUU:th޼yuuupRYtCbcc|lEeBI ExDDP6/ CbEb"|bĉ?˗/777c0 eeee[­D"QD޽Ɂڔ𨩩H$ҳgT|lkk{ԩ|%˫Йj~p8޽{Ϟ={a8hn*//WSS344nyyyB9pC̾@ɓN*#**4znllϟ?;\ @YYݻތ: .J@cecccII >D"{) {˗ U^ 2 .\?xE7;^~)%%4M=Z=5UTT`XOOO  C%sOOŋSSS?| }}}IxN{E{ً!H~yы}=j%q\hiiijjjhhPPPbh4 #''ȴx\~O@@@UU… 0<++k„ 8naaa'NL96wx<~gΜCŸl? `d'''''iӦmܸkcѪu.S2 e:D]a @/M˖vl͊D"ZuttzzzUUUȗh4ڇbTjWWЈJkoo{Koj4TZSS=/\u$v 3p===? ,hoor hkkjA***߿)%%3fx{{{2]UU0LevPԆdznQT~~~A6^'B|B/z4@HJJ SDBMYV\OU/ENNǏ3 /Gjv_\~pCFh޽koo'H+VqI\p… ! .\pgnn\p… .<lGK .\~yDQ_G&CmS[w~`p… .\-M)|l!7AP` … .\V7e{eߺq]I$;#c3߹W4aVoIꚜ+<|/wXSǟO4/YǘK)di3Mreeg=SМ6}{z^`0C#Su 1V`a``_983675޻{k1#zG+Ȳ@^[뿜_Nfp.UXT*{ii=ZK7Mm$'"SLaaD;lde0٬UMM-22266ٳ$adk[6\gVkj47RXj%4|f۱e?V y*~[8,t޵s 9{ˆ:ק,y}wq.Bt/~[oώ39y2B;ڕG쏉>`j9gyOFtL RR\5ׯ^0޹uӕKß=>eׁӮң"w[)=~\ƽ;>u0#p׮\r0֦P(O̞pfBsc,F[lAZޞqt}{Ck?]P폥onb>oEB "NqZzi3G3lRQܪU,me2JX,[x7= IDAT23۲ZI}7RYQ.4 CUVn>lDכW+ ]OD=_4o~ ߍ`g9)U8o\p*UWWYkYOqyԯLs4W/}WAǠ{Lg秔 ss(qF1'M,L|6ӻ(Ou1kμw=b ߽s¥!{22!ۃ͘`W:EA<oU7o7|_Ynfy7ƴh]0a'WYZG"Fݛؾe!%C=[=m,;lu淍4PՕ5TƯ^b9y|G"#;Oq2 ʭ'NOP|>ŤioɊ=}) tߋe[Cvldߣ3,c7Қe>+JKDڒjUURRAh+dA(+-ٽsؐmAN.5m7f |ܙ4?Μ߱+\l㜇Vθz3#aF4a2z"HL&~3]ISϟ;}OEljKH{c7Ӯ g9gKc nݚt) nވׯO>s322rk6g<Xب?|,2>"4>15؏fX4ϋpF g,`4fd2c,LRTT411qppPTRvKxAI9p>; 0XIY DDńfp斯^>TWWdevtv^) *FSs쇙c Cփ~֩4H~k3߁n"]%$D~/O p8(). FFe%-Mq {m^]{OTSS0~ ;SSqLPUqJ*b<0p[XϞWG@@F(eeU@M- 5رJ}:W_W˘Rrbث2'=mEEC/NUW_}1o⸳m&ّ~hjf=ϔKO[O醃x߅fe!)I4f]`0zp [k7%Dv%໪9Z7[w۪Ɔz%Lu0~(џ,Ňu5rr tWh1qSqzKVg-lp/om=]=cäz4=`Fc{NIE5xZof}̇6u|~l`h_6Ka$>1,1|87@tb gN<6ӸqBxa655z-\2rh " <L[_ZHT!d*m+AZAx[XPU/d{pOOy^\Mxpn]YYQ浶9pđz yOshۇSB Rܞlx1)`nY9@gz_  3UEDDS.$[1E>+y f35މ)Q/~Te4M$6 ڰj w%oa5x4"/f%eUfz/YAs;. 3/r'3q"!=z{,K:; #'g,Y}DgJŔ@BHwn N̙gv=.!)GtDĻ^NG3^r7K{OSXz5fRp 𯈨[EǙ0SGڵT{GgcI$B#MP@]Ełޱ,}E]QPTWT@(*6iP:$͛ Il?>\=d)gfl?pOhdl3wW_!c=~>vdJ?)?_94 }.^N@""D"JYaիIh99D$ŇrA{c** 'Rhur^rjhiSR}8?޿xzttt8 uC|֝{/G?v&&%EZںfVWb.~r9pVL4?絆HǏ5ppC=5.\ih#'O_v5֍Xp<&xǦqyt!ihi4STݵ@“ϒهBx/55֭3oS뺻+Ѫjd>M#G-YrEp"o%7_}  ZO륿~m~;;ZlLWgg̥HAZEss˫n{[Wic@Yiɫ쬱=_ض9;{IWgA0-yV:NwpRhVu}x뗞3- br 0 8{9JCCKOPU9)ӼTTXF;yeHp@ᨀ?JMuUb,;s2َ0'>(Gý5B8*'"/r@N%KDZkEI( F[w\tU͇W߃g(NL&SBB2k %E3<ɤz"OùKgei?:L쭍\G f#er#kI&\{%!e1C|BOV403DE WJJL&zʊs/s-s-23Wmp I a^⢂qGXiNynx &o=g LOݹ{?ߪ-?*>>iG(N?O|5A `!(&u7nnD TSۀ JysF&zjJk Md2|)# 1 Uzh22h4ڤ FsVO9 ?MMuU[[>h _((*,:y4~shos. (!.ׇ)\ښ,! }|촷)-`()d0H )LZ[P]xAmll;URRN<71?V&NÍtiFq ~A*&򧱱ؠCƏZGW`J;|Kjx> A@qo RXw5R޳exfp=;7\2cGBoEm:9q`34=5֍qwF:tn*ZltG?˘#d<`fiA3'p.}3֜|[ 쬘#*+.G_pq3VBٚ PO/WPLP>pK (0uL/_̌Yee JSy;Yn?*|KGtſTUV?STX9_q| w)LB T!B%PL B"D_Qn@~1~):B%"D/psh!B"Dȿ/ݰaCKK5CE!B"dp ~l&:::==p~A<8p@!B"Dc356oxWm/nD&)//_rmmm\ؼysvv}(I!B5u{84 FP  N8!(w~춧\$''QRR2//`jjV__d2>4?_tuu544"a2L&noWV:O+i7rb/q/C #uj+]Š(R)/11(e0?ZUTTp8qq ,MC"P(TAAtzbbǏ fkk<}rJoo)So%'''%%uvv͞=}ڮ^ `0FFFCG((ԩSY===QQQZZZvƍ|==khhj@mԨQ}oeggo #w,a)*///..`xyyL9(**JII9r${xYY٭[>ᡫ~7++e„ _>䠧gҤI?NLL޽dkk(x=|PYYҒ2 lbڙ 6ƠƜ?.SDd)Y"((ֶAWWÑϟ?|^CCUPPП5uݻwSN (-8x724-00ظnΝo޼477o޼:t TRR3鮮>|ھ};_|y葶6NwdHHɓ'233 :׮]322Z~=f9jԨ1c\Njٳ' @EEE]]=>>#Ϧ&s{{{˃anll$***KLL$b˗//^x>} ~lFsɓ ruuEPG[xӧO_~=ʲoii),,f[RR2nܸGuRSS KJJ*++lBBBnܸ7; 'Csԥm7˷G$9N0w9vtVCtVêQ:֣/r\RGGG~L&377B8q"77#777,,LCCB)AS\\Fkjj6"/F}}={T=iҤ=СC:$$DJJMN244~%55|r 촶:u*dvvvPWWon ^>~(V7n6666mזfxE-/^( 9=ƍÇt: X,O;kF߼ySpүH$9s8eYYYoooA8T5VλvJj% AšC9---O8?!x;vkkkk% IDATѣG'd׮]...df}݃_ 9F-".hqe]o_\=[OkolJMg"S;"h K2v@Ph4p8###KKK+++ %--mll ȣG/O4)&&^GEE~}}}@"8igg?F)//KFJIIFedd<<< ߝ戈-[x P4WIIIYYy0^RRȑ#'Nٳ .ݿ0c "h`` 3 ={6 ;;sd2W\T*uԩrrr{챱aO2|puu0۷o7nX[[|rA>i$sNQQn gܰD" G53TTT:::5 !!!d2YQQq׮]}#xbڴid2?sss̜9SOO68fjff&'' N޼yÚ]7nܗ/_޽{1b/(///&&zGv݉D:lsmccc>Lf̘!//?w\V\+&{[xyy]|^766M8ҁb˗β ;v%3%m߾]EEں8&&FWWÇ8'NXvݻwfH h.ᒒs;;=''ݻwsd֭ZZZ~~~\~ڵk\>}:,,,;;[OO/""pqSSS*#gnvZ[[ϟp%@iiFfϞ͚ ce  Jmg43uGRhnv76ֶ3NŸi`bjk… o޼400P(5500b999.\ʳTUU7o޼x 666[ZZuss3k~P(/^<|ܹs*EEEYRSSϞ=NuuッEDDΜ93W1L^_PgϞUVVΙ3@Ӌ֯_iӦUV1b֬YpVLJH$Zj0={/_nmmmffzPwwwJJʡC%%%---Y}oߖVWW=ztǎ6mjmm511پ}bFFQBaa!Nqɓ ]v3iӦMfɓ sEEE%'':u b.]ZTTe8*g322DDDfBR'L0k֬~SPP`t+))QVV^b&G߼y,˕ 6xxxhT@KKKFFƝ;w̙gϞM2%)))11~wyULZ/!!ѯb!sȀ#66vҤI𖯯ӧ7'@ѣGH/_HC(!$$DLL,33M0ѣGwqqٳg+ΡCdrbbȑ#(5 @www_wtvA())! fff-jkkLLLҤ+VL0ATTzFGG+**֎1Κ:|5V^m`````jժGJJJbbbx<<<HLOOXz X_$&&:::ࣄӧرB899IJJFGG7@ΝvZh4V=#)477o * puu}捌chh("Vduuu]]]Ϟ={iRRRVVS%K\z@A_qqqQQQyyy<b{{{ccc V\k7;X, -- !O 68Q#EaEL,z~zTII`v2"sf\\\\\\V\ŋ>\vmbzڵcccsss>Ej@L2eȑsssguֽ{BBB` IFbVQQ%Kؕr܂C#_xQ^^φ 5C;;%%%޽˾L&nذ!))VVV\G"`& --MOOcx=յyfS"""ߟ 30gΜEw^l͛ݻ_|imme}i 6lͅ8/LJKK700D"ݼySYY9 ӧ$i֬YcQ ?~亖ҥKK, ڽ{7b/a8|*&{W,Ν3uzvO,{!OOOOOO7o XKKKouXSh4H$1ZR$''+k}$|̅R_S zرuQ(gsH$ 'H$i/^\r;LÇ$eeegφK /^|ݻw.]6}hnv,YR[[;qDGGpmm~Em? ^'#.&#X flꢷ2r#F{ LED\j߅w}@ lܸAW޽Ƿ}#݈aw-\L1~dzL'8|+p( kkk<G(>(:::***,{8%FnܸsL,<==Og QQQ .7z[ZZ*((^dk&.* p%%%cJ^^۷o\''yqQWWOHHHKK$}TTTH(`W,̙k׮yq𳵵]f RSS+++Sccc}}=¹ɾk׮}䉀===AjkkaMAAe=z:i$VcΘ1###CKKK@<<ŽӧOEDDzzzQQQz9[[[ӧ_~hii>}:'' ]U۷o= |AQQՅ Z[[[ZZΟ??|pZ[[۷ZKKkvh8ToTTիW7nȑŋcccx1c6nx]v:u߮vMM ,'O{9rd/hK.ZQn>[ZZ`tLdQF\r…pdի0LN]]^OOGBLLLMMqd„ UUU111Zai655؉_xqqqǏ%%%YYYŋk׮eM1 pٹvZNӝ4558V7o޼|LJVL:eggc___8{H8jjΜ98n۶m ===?x`0"""8Z$\$\`|Î[->aeeQf;?UUUX,v֬Y NP}۷o[ZZݡVSS#ϟ?oll\heIp]ٹ9QtNw|݁t"9HN'yՁLgƾq \xӨP(X,F䚚Z%ăΝ;ARRRGGʕ+0<99YEEELLlڴi!!!0c\6m6lعsrAacH$uuu!S 2l``0ķDFFx===UUՉ'"RTT4|piii ;;bے\2 Hmm 0bjj*pss۶m;<5+Yvtyy9:ymٲ%22L&p8sĉY!::ZVVVCCC[[BlooRRR|aw>uQQQEEq_/;CWW 2UUU7oތ:{̊+OVUU ȩSdddLLL +V@PbbbK,9s&k˗eddp8L@@ ߱cȑ#.TT1?~8 >p; mA222hٳgasΝ0\GGX'NrO:E&=<}ZSSsɾ׿;mmm E@e^0aÆqZuvv2 A999>| B 644***<7N<+ӧO-hŹ bD[[[pUuuzN...FڵkWmm2/H\444466‰ˁ HQQ)ȡSWW^?(:17<?A󣣣yMTC͛BrrӦM6l066|ݻw8t萚OGȯ\1affٳػw=G?;:::>>>&&c@jjj]/\ <~?-ؚd W%wrmP(!n5#GllpoimmR.`0aTP(ҥK)1R*))yyy}/g_vXh?˗/_|bŊZ CO \d?kXwI¯!B"~]!B"D/Ώ<"D!B9uw(g D!|'Ȕ/!B"gk&doH͡c84 FP  N8!(ټ)WAfzZfm „|0LE [/wUq5L\\BJJZDD; N?wƆz,VFqE4[vG̑Jbb1(4 F&}KLL@g# ϴVUU8N ^UY6 P%ExqqUաgO()+[[ێprPF9kΚu[(̯ O~xJUSW_G645Gqccb֫z| RRGO677=L.XӍkW齽'Nnc~+#-9%iWW vU70^A֍CUUV477mںk]KnY)+yVL|ʻ}ӟ2KLLLTMM{wn|>^]]`EO_^fg}miq;a9swO{ :oXބSYV9QyÇ566qsvޱeП5oִ|8{z@i FرCcgԔijM~Ͳn}Cz*u]޽< AnYғ[ֿ|Cƺ{os.\mmy? (IKK)cw܂dē7fDMuiyN1:co{w̘2ʊ/I 54tƬoߌRZXL~9Aܶ6˝[ܺ浫/2's6[Yjajj'8lnjZ2 qƺ:8 7o(X ޑO!gIO֮ سo&=kW5c4 ;$H*@=KaOy8tQ!|PVZ5uЬԒs *i^ŗ?@B뱗256|_~xQl*Qŗri9Sg&"AlU\L^lUuM&=zhyym8TVZcarPR\DShZ_}۾&gH|arMm!'$y# ]q+ >s .wX!{w}.I}H.[3aMWE_K[& IDATH^Q_qvu/ǧKgdlzҟ-渫3f{\#Oڍ59v3KH~nem;'L:}S3wᮽjo8y:\RtoVyæfVK ߛ%Rj 瘆jde)I oOF5τ߸mҕBLaffG$`՚Cÿ<.^cni=fAzx!+K|WYDP-K1냫gi-M LqSBa b?S\ g`,)%Fmll M,5u EEq8MCc +++ %)%g`*-FkdׯyinI^5w5gW7v N { i,//ojskB9E$);+C5EIs#ㅼo6 Hl[[Sn΄ζЏ9uLS7$@^fg͘2VKM̐PxL^/>0ҒAԯ Jmg43uGRhnv76ֶ3NŸi`bjk&E[KވtvvjP(jtuuawosl- ._ʳڼzKho_<>}uUC!j)/t Nɳa*-TPP<X2R/p&"P[S=sM[w/`D.F+|w>1L^_PEsjk?iuUe\N\RsۆUA?u?p鞳]\a*YY2[6qі33Rl ZanimlbzPwwwfzJ𖐐|R[0[bʋ}xWښАYi{mmF&7nWPOL߱;D%tn]֠?E&S:{iV[p-6'>sÐK]|n#Ϟ*).H. xp]{vmQOLIC={tO܂E+Nx򰯪y%]9W+.,=k?;qW}$i=/ݜmG}|)Z-"ϝf |.)b5RUYyj?}y8Up'->Yғ l9A ͛m^a*\#Y0k|33ts*+o|nAi| $`׶c'T4"%-xLrʑ-/W]UIn==={_ޭqQ,HJJ^F/2ޥKJ;M^@`1h;6CѠ3fw{{{KKK544lllƌ+M@v}Ӥǣ,94mMZ:X,VF ʑӍMlGygw-59iqrDEEaVWѣ;M|tY]y쿚\:˗ϟGǺ4593GZZfBg%>LH[uM}Z-nemleYU9CC=uJ Wߺ;vv3@ ı$<b[7b f>/Ud2o>U&9DeZU/&HDqX9 ^{/URR`0ݽ &ܱ;g[7zŇ޺hrV{ZIIyx)#F.>ٽ*..'2KyTU|88bV]Y5+"zK|*ǭ/ ?-aL]=0B%8vζX+--QPT;88s;+TU/?ڶ1%9[q} , E !b898~)fķng=7w2>.ZlM&wG]Oښ eTTڶ1?/N9 c߅θsqK#V4VY3f_9|A ZںBTT_2JELM-*-15/&F.P^9) )q؎dʴނbtvuz=52 u6=RQS`0w>!D][rSGr*a]}{Ni)O_z1}GvZF%r5۸n%caaa?bx4 /fM-a*)ϒ|,a>zx@742ᕊ@usr΍v=J`b>3ZZtpE$y3z6}@nff @`UOjX⚉ޣwFϔi^Sy=[::zlYɑ[q_C.\ėkog^Ne܄@6og۸+uS]ʩ#qW/e̕v,X* (p׬91Q\\֍gI ֌ m.[  <2dcJK@xoy#>M|:v{&5͟u{MxTؑE +aJp-jdwde-륪WgمRIHDV"?zA^>e%$] qpZW[Xo`o*@)Eӳ2D@x9ָr_e%us=Lq NojbU`x;{Gϙ>RAQs+,ƝǟrWX"`ۺs19 ˄@rt:nNфQ]_ L[3(p"(PD0Y"au?yKW|x=Bt:\ad2%$$n>v+P\R=CLnw!"(8 $`yVfL{|u;~H4xl@xD,Qupm2i$^U5kwD_T23L8foYXXRfȳ4qJIID\UYqeecμ5UfGExD42ն6ձ4-\gQ.c/\x>}SVr^s,,$"Tooo5ke ;6 /9G?dXW5AX;uvth]u?~hj=0(IjD7g;3CJwwkv ]A W9u'sD޸e{5޽}3qjkQ(M9ٙfBh.X1K!׮h }J\:=޵] s]\T0n #)]7ow{{xUi(ZhH//| T||8P:~(tWkj"(&u7nnD TSۀ[JysF&zjJk Md2|)# c u)65dddhtGG;@Mh4#zj$+?UGWCMM4 ӯrjkXm CMM9FkAJ?&?K<9n$o_ߝR:E@e^0%a\p] &C\\AZctwtv@V^~CP!Ժ]>]]%%E sV~t hDX/8TUZ:Dbillh5 >,--pGmmoiDk]XSH  4[:o@@qo RXw5R޳exfp=;7\2cGBs_6ӚLLSo݈wgA&F'>yD!9O9O:4t֎/?H'Op3g9 u8wFd2S|w5@k_ #())9ę|2n.|P`_ʿT'/Lp/J`rPUYs[+ ;8JOQQQ={>R|w56]dr'999ɓZL1&hPPPP&<˜Qֻ((((((((c )/wYu D Z&- Zcd2y۶m===&&&?<^.((((((((_f+**|7BÇfDAAAAAAn7tۃf^=2??ɉׯ_pzzz[[@MMޞy222zzz EFF¿]]]yyyVb ˗/ٳgOw,X O;KKK}}eڴi{e{W K_DM߻wqŊM2j,@iiiJJ JekgWi_zE N:c IIISLcE˖-y 7B3yl!cuhh(11W\\ enT* $ѺhZ[?Om!'Ş'Sy%T͛MMM---۵cbbbccÉDb{{͛9s۷OYYٳcKR=zLggKppАAKKݻ_~} >quBHݻwOSSBxxxp:u2,,̙3YYY˗/'\~0؁ HIII__EOMMߺu+ɓiӦ :;;_jJFFFWWS]]]AAAwMII:u>B:F*aaaAAAW IDAT^H$???z7e2jxGGG C&?>@v͛ %==)3gaAXwwbkBCOe,|C**D kookP@FF2>-k;mMlC#n$>mO|ھP'3jw\tL H#SNh=== r钒D"JJJ"""0^II rjXlSSEC0 RSSkkkcSQϚ5k_wcǎCwXXh?qϞ=7?!!AIIiLb]VUU2eʜ9s_688+@KK˨QT訨{K16l!EHHts 8\aa!!D ޽{n߾UTT( |||҇/_X,ڵk܋qppسg_q III'''ORREط-c!bjjzi.e`WY®o666^oM<922Z;wB;2. QQ~0luԼB8 kN|'B[gO[Gb;'Db0|?taX 333}}}<MMM08xAc>t֬YIII?g^xS%%%VVVPaoo/--ss劊tv^^Ow˅Eٹxb999yyKvww(D"1++3,,qqqD"yIJJfggWZ`A===VVVo߾eFQQqhhF._[[[̙C LMMoaa+''\EE%""͛۷777ĵkrS===f͢Ox7oVVV$T$|Ғ $  SVVݳgsUVV?s tqq̟?H$Drss vek쒛ۧO111666***\fPZZHҥKнvڹsBw~~/=UTTlllŋaޙ2{I}}}߄s,1++kxx1ʹs-ZݭD"qeSJ-%HgΜa7kaaYK$,Y(##2 706 L>W^ō^+V駟:::=@Mo87UUgϞ^xpݨ(BDDDCC'` .tttRcc̙3[[[{dSYYI}A?~\__pBBܺukHHHtt lll J@@dmm 6nڿnnnXX`ڵSL166hxx8''رc~~~pAOOOmm-ٳy6(ƽ{8qb׮]!!!}}};w-((8t7PQQAPq.9s挪5`%%%{qvv 5,]DEEggg={[zuee޽{wpV+22e"eee'##C@Z^^~ݺuJJJ=bGYYYhhE0 n|rmJNNW .edd=)\JJJpYOSSӫW%vm X[XXe"Xb׍L&3 32'2(##ccc8'qӧϛ7eܲ2__߃*(( uւI&𡢢͍с,0RWWcǎ[n1)((zjJJʼyDeLcuuu6mzqKK =/_N(!~>,#""`# >|,Z*ϋ~nFUU\MM%555jjjjjj]XX؍7߿obbtܹsCBB$%%RRRp1mwMMMVVֺuᢹDYYY"lcc9,|[0f?>|pƍ6lw𐓓pp$ ãҲ )))شi˗/=JQmmӭĨټy }pׯ_zu^^^QQQ,+--=r]Bb444`01PW1@RRRLLLVV\!@gggGGGQQȎ'O s]-Z##22331cwtt%$$fe".] tpp4ؾ}{tt3gRRRVVWW}ׯ__XXX\\vZ+Z]]m```bbٳ]\\ڄ9gw֭[aaaJiӦ1ʕ+o]v@C>Lcʔ)/tBӀ,G6ʪZNN`ggqtƝ,%%%۶m`z"333_&λ{8#tOԀ}CCCL̙Cۊ>tPnn.Lsx++++++… -[y 5kBCCnݺgL>}+)) ۶m+))OWe"555c,%%u5yyUV=zTJJγ%%%E=|D"yxx{nC,[PP>{>dff  8n./E㣣k[8RUU߷l٢J4˦ڵk+$$DOOÇAAA:::PAnu׮]P;%+Wlnn|zzի#"""""F5*... 0}P@@/.x.j|#Nq@hJBFDHt  EEE>|ܾ}@@@ ﯪzɓ%$$Ν;RPPUm\LZbvTUU͛7/##ŋp '={(2Ǐ~hii[[[K'WVV޽;1t`!ݻwѣG3g@/+0n{`}}}Ç0HdLJZZetAAA;;^^^;wڵ,((.uFy%555ݲe La 0m&`ӦMW\yЗlX65܀TTT*1)))@?qׯ#cǂ]嗞֭[x+XGMMϟGMnT/]mᬣت@ ! ADPJR@ZWR#/_-4pFi),#MR^*,,CPvء/@+XBBBPG=>77jS=z pD"ٳgTMM͖G}1}sνz $% D"9r.f;x`ll'`FݹsR[[,..'66\CCcd"o޼n ''Çںx䶶۷3%"$$t+W0 8;;o߾ҥK{9{Ɛ455Z[[,>>#Gܽ{ٳg8~W^\zeZZZZZZL1<< 9teL6m? JNNGF}(/_.pJJJg2wwsX#ihCeŊUUUTWW?}PXXi&F9aspppӦM0.***JAAA^^ֶŋf2%e#EݿŌαTjJJ lnn.++uD{zz@X65fbժUW^x 8Q%㬢JJJVXe͛7===rrr_mY`cCCCUU?!X˜3,Y n+;C#%=룽$!W$ 򒄼 !6ڕ7û=/mmm---MMM|||X,7<<| Hss;lnn.Juqq_a"T[1ٴiXmm-f@++;v(++C0¿bbb}ܐ(!!`~wqqqQTTTVVC:?fz%SRR8OKK緰ussmq6Dvuu&b"SQQQZZ:44瓺pႨu뼽9{! ,Adݺu í\rڗ.]񼼼Vv풕;p8@@@ɓwh#RPPb/\v 0̡Cߙ3gD]DEEőGXLwC΋/(;;f׶p5<<,##b6mD&ЀXG65L8NGGGEEŋ#5UUU[l@͛&O\PP.ʱc;? |QnLEꚖuVrVy1sLB?p`l0E@@^#0L}ѮYê*333!!!8sohh800+--orF񣠠 ӂ*'..>:;;鈁_j.,˔ZKK=na*NQ INN``@VV pQaa1FÇΝSWWZx0EHMM*˖AR&Mr4988HR^$@ pHWŽvs7CCCjjjܧ`8Kʮm8Hgg'l,apySh6 +sOKK 2zrSt0<<,%%u˗?5º7 HYYYbb"jh`` e=dA}}}HHȶm&Ol``x]QQÇ;A8=zW\IOOwrr7NLLHJJbi?..=~e"0}t&K.Y{̟?B\\ҥK555,Fkmme7#+++""/ V^o2ϯV'2, ZIՄpy F}gg'H\r%7Pl{3.\hQ&A g FPPPPPPPP& 1H c]& LZ((((Iao#{~sIڟX((((((((3M)ɉ>ͯ5ʁ S':qރ;T7򱰬E& o ٬=%dM}]m [jfny-2-!^< ?ۑJʪ?^$JGfyy, 48w8(-fOY ߗ)_ 4oooC`?h3=O?2mm֪Гk&2P(;LEL4XwTV5}!`1 K33"qkb*#[</$(P_֊`@Lue⸔Fُ90zfܹ2J\ 瞾x7G HƝ_PFƓ0~dWϟ\^Dd*+4bJo;/+8܊L;#TTԦs>󹣣&;ggEb'N|ֺt*`ﯦ&S䙞^S/ed?՛@֖ntGd|mwGȺ{1Vqx5uM-" E]9Yy#Sq+*˼0y}2_?wp迖 j+'?]ki#}H§ϟM5WR0J8{87R^Duhm~Z{?{LSP |y_Ǯ[ٚ9;XutkkkƆĎvg+)vl*+ޯ^~^<s||> ( tϛ=cx I3ݜmաA] Э~ۿkhpsR eS557{uWSנP1Qn^K}?]"1Qt3doYynoDECC3'MRLL@pmmAQ4 W>TWZLyDGWvlCue v\0nvTevG  M|KnXGf|<\-,8 8{xTW<<<+m, _U*)ps/ b",,244XTcz57}ZI .VֶboyMdLv1fxnXOq fc#o^V~~O>f=sN/{:/(H cؾB8g IDAT kN|'B[gO[Gb;'Db0|)1!aX =CSu-=~~B ^]NUUQP,rq9).&ꏳ _0f^~q'Fp8[iQ熇cιso^D?Wb0 ;T7 R$%%\ rO" tuuvuvU<\i=f O6st9 N$@\BBJJ]"i)x<6 9XZٌRߴ-)>::̍k)G֚~#j˷U>5U4=zt9`3nufmooIR.'NSRVI(#-Elmij8Ǭ7khaXO/oCcg9˿t3]<\gxpe+骅o:<"y٦S,t sh;o[**&FP( 炅KRS`'!.zɲWeZ^IYE~‚r</ nރ{wcxU`h۷R u FxmmM'N1`db*&.B<~pŪuُ7y͙72:?TkT|~f&5uM)i DDD6]1@X87 K/ '[}o J&Sicu3VSm~پVүVWW= `>xyy^Nt٪+IaNBO R}>Y_1X$l>չaTף먪>"=Z[161ckxxhߞ>oρs?j`E,.xAe+9u{'ZjDYE1J2ڏcZlE0 u;eAy9(I?g~RS6n aȥ8aa?"d2u Ϛ3rEi w9h a<}?։c2c 4,ZeiK+,ς{wo𪭩y%%-#)IaSm vx g=PPT(/;Ϗjmm x2T[3;wxY|G)out.3? 6<7Օs'翖KO(kZFUVwsg1)K ;3!8B]CK~BK{n!XBoߺy= TTԸ Okμzz%؅㗒8;e E[$Ҁ%we"{ ے={/=yⰌ6QEdLeJ&%,ҔnV@% |nVPRy-5,Iv%ţaTkҿwvvp.ރw(/۴~g!iWji1L]]-c׍C.54pɊÿu󪎮~)Z[xXHkKO_vgZ:s߮P~s) 1ΊKMT=ʼXS3=X(оG_v,/`c@Z,`OвoD:X#mjm-y9{{?Co>y4)!fMmoκ̘%/ +'o9ն%9镡΂EKgJMN\JK2rF&{z^ ?r~3y?vYiIG{ GfI;7;&S 7x;,!!+NwrmlON8c6upt9tpWEᯡae+q1O8 888ro{c.c#'`De7`x^ii IFBc4EGSgFR #IR@#BX!["PT8m _?aakwRgfa,Z:488f'7;+cҾ]1I>} qͣ[YSІM|jq7ʷoSW`j1X{ϛCI$uEI܄<f]qWWcjN{8RWtq2SMdo{%֬Z ȨlGQ+z{M f搃{2ljU-QW|9ݺjeuEɓ'<T]Qr`X,7ksoausm-l8dXN|lԂEK- Z` `qgG;\UPTR~9"Q]XOu4-,g̜5ut)ֶX,v6m{A*[__LO/&#+Ǹ̩p)Q k1b6REU]]SҎ_ݻ*~%,s `T+3=GgWiG{sUsT[deA P]^bb*j JN.齺ҒgOvuM sFD="RW0:,L^36ca_ vwTW?k{3 ؑr&SG `uƷnu<Ψǚ*)22BBJֶ֜jZcc}]+P>111%IN~ҿe78 5?CUU}_b){@eU. ȲsҨBBtvKII$Vb08֖掎v-mS?CCՕ*jܧ=m-"":%X,_2eʪ8ĄBtwus2m-)i.uTw~3q?/Lqkư RQ^!߂@x3_lj߿;t[ &[[>/yWRtNRP1bw S$f>IA}Lg*zо|#UϜa<4)>vmC}ݥ8GOm bjkoC>yK(?7+Rn28f;GZFVDDd3Md%"ZDŽ~G )4ud?==>x<~AC}]Ɲ<S}hKJݴuN ȓ§?͞rV?NoOcB @ e#)e :@AAAAAA$.˿.d1@2AMg;dmzzzLLL~wAAAAAAAA_ILLϯf6cP>|Ç>>w˛BR4Duhm~Z{?{LSPxvƂ7o655loo '햖fff7o޿H>? ( tww cǎY[[744rrrp&tppsR ѱXCCcΝ޽{ #%өaaagΜZ|9@xɘ %%%}}}_=55U__֭pjΓ'OM6<<<88윖FUV)((dddtuuqEDDDPPйsNoKlڴ)::߽{XI|_W.\IIIyyy?(ddd # H~V3:=4FħkNq=vZM42F+))`0***O.))!H$$""BMM 镔)tgsrY==?kתSL3gKeԨT*tTTT޽{ǥ6lpss"$$MMM9ZRvGK GGd޽L/ۡ;<<\EEB RQQW[[dH'JJJǏgk%|DAȰQ 0+$j8 m=m]o$Rh  Г80033x<^__ s錔ԬY>AN:-..nkkg͚śŋGDDܹSAAaʔ)UUUIIIwޅa SVVݳgxr鉌5k>>>7oެ`0XHOIJJb0v.#|Ғ $ G`sUVV?sS]\\'2\cccMgW&_.}ccceܹCR>|=/_~ zu֝?gbb"..A/ŋ:u*88XRR7""^ZZŅh+**),+K{ڵs΅|___twwĬ,{{{OOO@{{y$%%uuuaOJJJn+؈ĈAO˗/{xx䘛DDD0Ӌ^imm3g@055ݿBɾ}gXqKjj۹ z6 Cw*YO[kiAnHLll,Hp8ӳh]]]8H$ƎvoTVVx8NOOڵkHSSO݌lMLLANNn?~ (//_v ]]]]VVF ǙoHaa!ی_\~={ sV\\{n''oʹ%&&cYu3/_`jj0Kcc#޽{mmmiii VTTf #u3;vn8^`ڵk.\Xݡ"|޼y366VMM  7nڿ? --}J*zڵ 0gϞ%wSZYYYJH @ 0@B$88,s ;wqvv^bϟϝ;ghhY[[y-`q?V7lnnVSSC&䚚555 ggg55qMLLʕ+Dpnnn^^^vhLK8k.UUU;;; IDATD"VRRDFF:::~`ddDhLPi%!ᰰJ'bbb,:;;;::N<)++xX fff6c <_VVHHHHKKKҥK<<sLJJ sCjkkkZZmuu%uuu^d/]Rid* A~hofӧO_~}aaaqqqjjڵk-楦VWW՛し7000..nժUIIIN/n>>>##ZJJJq^ӧO}}} _G]]G$pxmff.Phh(Ϝ9s֮] чͅsoeeeeeXpeˠZk֬ u\\Tm۶@M+,166j$RRR׮]_jգGMaaaIII!!!,9}4L&'$$lݺUXXx޼yqqqӦMuuu?^r%˸L^^uVXX2K~4Ɗ -((HOO={233edd&M8z(|VVVrrr +Wܼy}FNrӭKeeeq>SjRRR4=,͚5kӦMK.>MfDB8qa<炠7Y'/dADǰ>PTTÇ>AA۷  o>7oLldd$ǔ6gΜ 4/ ʪ}*++l6ѣGLx{{GFF LBQQQ/l60eʔ̋/Qjc.^XWWu֙Yvmzz͛7AAAAAA0vX>yfIh]v }}}cbblق'>7`k}gCCC z]TTt[3:t߿M5IIIb ehp6= !LMMt钿GЛu !!!4\.cGYjUpp0ҭX,5jIZTL۠(`@tυrn  C>|(>ީKG•{/_&I$ Qh[bd2 3fX,===--h$ s700`Æ ۰al4mGΝrhigj߾ҥK[k,"""BMMܜ ;vHwx0??Bb?JgϞM͞:uQQGQUUp8 CUUuHrJ>}6u=tͶr;wln .l tpp0a ^|){5SSS%%%###$l:l0hA%]\\Ə ޽+GQ]$Alܸ}4hUsM/,,(C:::7n܀ZXXuqٳ !RWWg2fB-%Adi~m|,[,,,WPPP}b0RA5=k֮ӰptUdc@?$|\ڊkoo񊋋>|haaLxPXVVf``\DRSSiK$}}}BYSSs{J㢾&&&@V&$ |J @PXXXZZjmmxꧮ.##Դ嵵"eeeEfjjj$Ix<@O5jTaa|m|jkkytX䤬C|:H#wm߼kB `0T[qEz!D(6y8+Zd~{C/æY𧅋qt~2(K޿mv|.a0 &4klX{Ŷ$H  4ػwJAW RSl&BH'%?OKQRRN ݁ǫ---ߝA9~fd UD/|Poi+HػF̪AI a+W.C@0kZ x< @oC6&tb4vbruƝ;mmckCߛ|?sŴ qNX,w ~xVRs?o''efϳ{Duuչ3B_8[ZZ066ѫteeV:S;p˙>y|v*woVܾXRR& ur!fb|B:M~cr,]pv̸BJK6kkxyINwϐa__i/|._:שso)̿% p:GZIq-]Z+)E$[j&d_vpw)++ܶm[XXXxxm۬J=]]\3x|@YDEEc󯧲2.Ǯm^ 䣽uM Zfe@ *XOԔgf׮@r__|ԬX,=b_')}?߈Ӭ)Fwa6B?}*ݯ>&+{/zPP'63鮭iѕK޾?͙OaR˫}p.eahyZ `٢QG#:'L0(,xgxh;&Hį4իT yrO}J?ֱ7 CU4-^1{FpOGfH8:=т \zmol}79a9996ᇣ,l[>Ըeb?IB>}bBs7fdЌ/sky죯>)^(d~eVs[Yضo2+#T\s<]\uF@_n斡6Wa]ێݺ{)H}3º:6eoI7eD>8wj&@CiEQ/2z9ahdܒbq'c%%:Wr\V_N63~Rzv9jxp]NL Լ=]~xcGFGE#ƏgNgq~٪KQm:[nZsFiu*=26;tDܹ/2A?Iһ:Ae-<Z~+3h؃w dc} pI]bqsܺyOF'|+7'Ov{zNyy(٥W M$ɾ?#_?8e29Hֳhe>sڄ}{w-55ա!{t4;@iz5vVMصű{[[jsȻΟ0rIAsWt7PVV:a_C ǎ7Q{wG0CGlܿ3ȣsА=h}h D~o%%wsvhGV.UxaF}{;nݴý;@,;w۲j_Yk*Ixe1vo+cD:s `)f*~Ukݻs V$+X,SVط>xs_S򋥥>۴~!=zΝՖFGE^viw[d0&OŒ֬ZE `0p?g鹓xiij?|pm\@vvs쭍u8|0T~=9n|PG.NХg?syyّpAoF ^|W8.M~ $QTT7شmy:{@eߞC=z.[x{-Ϝ> K"sTL}&/2~eO L4NMMqګi3,[.ت_蝕[vbB;Ul6ӭǣ5lٸ&衖/KUU۷6om.Yץh>2uT协hbУ|Fw'BN`D"yߛ<QNbuyeϞ=]3dNk3ݙSŜA ̡c?lխW55uVs?BOpzwwm0gޢCN߲q xzҒؿb\zz$skW/L 8baq^ GEhk[Xv3Ui=>C͝ĬI};=zo&^>ISfJ],3l ?+1H$ߵMGi>w/<ko0G[G7''F^ޅou"=`0^kdd.g3x$A PUUb=٣uA WY~3\SK{q]ZZW,[}wH Be+qv ,aߥ 🡫-Bu}3.={r_<}4TbOEgeewang}0 1G#N4=&*r֝H:gd2m:ny$~es_Ԥ3ndo VTXR\!tQBaoJ|0y,qϝ6*_K7'qr͇rz;i?_2o qg'}YgF&MVbV&My6I|Q.=]cs-i#oW[+=wҐ#F?WZxW]]iX"2]=Ÿ3۷nE(**;~b !YC{ZoX$ҋ$ɻo^8foNvvZrӗv{Vt 9dggi9Ӻ9X8{_V7{A]¡sׄ-AmZ5EE|rnj9kޘq[^'C WVUr9L&E 3M#U ^} EZX/*qv˚U+xBO۲q ˭|].qصښ¥u_?G4Mgx=^MT?F1_ z ׯN*^8־Oiy׭piKK:"pʷj͕a1YZ{B‡9p@Ђ l;;93Q%7Ye-ðhwM:V6 +Z5M/H$ZX/ "Ϟ0v܄1#}YUU 004(krg̜0<9e$5-]yy|mȡp`+HMpyK-\Ezڼ9|nj,F%WI. O^J4~d{CC-^_1L][0-Uҥ}l2~{v[rMw#WCYASPW'8- ;WKР4 *?| Ο9YZRDQX,^jyNkW"ITPP,-)>{U|ɼa~@F}ꥬ4MG=7zBaԑCMƻaBԓ1n''AiF>sw\fK_$ݺyڍ>!REE3Lr%E%D IDATEº:9嫪*QP(:H)#Wo3̙1 |+).2GQ6ho#TJ<zɏ"dE(o+b̝f&N~yEwikW/ ~[eҔo5wnruԜ=sA7r,߽c3d7Vj(1zuuW["8mxc 1v>g׽G⒢c}߹}s^kC1+bq&f(D;ԯDQa. Dqs?痥u BvnpW.9~da"݊IJJ!IZTLik #N5NtfmnFk@S()=fWb())b‚i%%'Ecsu$sruq2m}ag%J45P;fj2!hO dclAla`Z 4]nyiF8o^}@VT򴢢|6B9o_53Ttwqh"օoF_WoPQUqD/~ݲqG>Ʌo;ɺտ8fR)g8mdOD5ks{wګ/;ɷo`Ao{O'~3޿֬ 3wN]V CcOutqtw@A ,/+EQ5FGcF2uh2tG:8hHn}]݇$)4o3o%c7SSJOtګWMM6ݫMG;ɲ.ɼ['^)V:暚Z ȞMK+?#-kgeޫ fΚRﬨ8'4ܙ@;쳱s_H$9g'^z)%前Y{uf}BC7M[7ܯ uCu<

u0mfP3MsD1kM1;wcN@Ql&)įMSUY)+p8.yqNgH =jSϞ>n mMs;I9^i0̯C+g /IhwrN]u0 G]@  `0 +}cwt`%`0wwY `0OWVDEUVVv[- `0G龙/^ 0 AiӦM65b0 ЬoMERS )FHT~X9Kirrr̙bŊݻ=ztһw[nϞ=&&&_K ` Z3W\~\A$I@i;#j~ٴM^b ԩSmmmII Mw}ehis2#c0HĔy烲U}ĸ _O]DriikĬ:I$Af}"k׮~~~vH$w^BBœ9sڵ?>`˷'N:֯_?lv866`jjڧOKKKSSS;v(-gܸq***C+**O.[GD?CxS||իW1cY.**:{̸_ -----py==]~Na0hwP"lN9~[GR]R+)Kk%ŕtCp9% B̟?k׮ݻw/--ܶm[XXXxxm۬JKKw8l}ѣG &=o߾0`ҥK\֭[{왟 ]]]4*W%:Xb˿~… bqyoܰaqɓ'khhܺu룆gjjj>h[[ۅ a)nrss #&&Fk͚5ӧO700066Ğ={MwO!FÇߏ###m0r2gş;^篈҈ҟ6ܻ[;r1w7 Ag:vHݻ={|gcjjJDǎ={>R&y%$88O>r ر4i̚5Ĥ}ݺu:t(HӴ@ @.&HƋ/)))-O?%%Kv׮]G-0⤦6WYfwia ӦM!\zu/Q޽/^mfll,!/^`2999-lK$YZZJU`#4f( @*Y:wlIyeIY"4=tfT`- SQQ!I֖p8[[ۮ]:::bgg3C 9pTڵRUUwk|!C"##Cٳb nݺeffFFFZZZڞ?nذHGGgժU3 LSYYo߾!C ;}tFF Ǽ I:UNDiii |A[X^`w6l{'`ԨQVVVh7n888hhh}V*oN'tv׏?ظ_d>)))`X5yff̘YfotQNNN#F@pSSS+QZZrsrr믷oYvwwڴiSFFD"9yǏƍkhh;wMi]z9m9rBb !H$:::/^@ ?G(ݽ{wVV֭['Gqqq :u:u*Ik0$WWW~҂UTTCCC\Z훙555$/yOHHŊ0VY S$IRVV3$7_=NPTVqb"_7cbbdjj-dDlSSSgggSSlKEE%** !} USS[r%M B3|+W*++GDDXYYyyy={ طo_~:u>hbУ|Hp{{{ vH$$//O@dddXXիWQ`<hrJ;w 8wܰaÐ9N~jkk555--*9rEQK,QPPpwwի ŋ?9yUcbbznݺ3g"sIrss/]4eԩSϜ9쀑#G #nnn:::VVVzr労aÆ-Y‚$Ç;88lYkhh͞9sԯ֤6/\8s󓒒BaRR%:pޝ;w611=z9s:t윒"UppAfΜ*4ٽC ޽󕕕ЂSNKgH?HVVVBB͵Ǐ/ݥ۷B Zbĥ)8L}G>v0DB EBWf۷o9sܹsӧѳf͂3EGGgeeuY2 F@@Ok.$ DL&SN999- $IDyׯ_HYw=cffHLL ѱ.]*+:tYq7nwqqqqq7nҤI-?ҥK]\\Μ9@ .E={ nM%ll9ˬ3sLMM͓'OM>ڵkQMMMWWѣGƎaÆ%K4YIxxݻ" .TRR1bāBCCׯ_ͭ:u*:Vvt̙36l@LR P'NqqqO>yfll,Z+F$In~3۱cB==&`_6mZԏZ߀&šsss 2o޼'N sh֚*sT٪J\.ɤcxӫHB EBv*<~˗555 /xcǎ;v~r=ҥ77qm۶xyy!>zZ4!C4>Vګ566ڵkhhgt󥰰000rvUTTo(EEEY,'N_oݺ%MS^߾}37bikk8p_~^^^(czz:SRR%q6Yulll9rȑ#6mյXYYVU]]$,,,00I .\8q!CFUYY 022(rߙ#F>>nnnk׮Mc0M#:WKNq&/Z0w&Aii054U fw'N(..VRR(J,/[C˗/$I***"u4gkk?=Z 7f̘K.xCxƯw{P(|P}>SM'Q+^B /%%%EEE&&&L&$I=E@V.L 3f`XzzzZZZH.:o``f a(`iHϝ;r6MԾ}{--KX0EDD9A;vxvȩ ǏS>&&b9;;xyy_/-B[ZZ0en|F߿]vg>|a~~>088XUU6Ξ= 6=uQFI9p ӑ|ʕ:::}]vl:f\Ν;… &L m /_J^vTII 6뇆%ZBD~z?y wʂVVVRb x } &J+++ݻXN A",]bB===CBf۷a3Qh_E€e˖1  B 4X,^v{7" uCBɍ%)VtttVTTD_{{{W\\C X  + HjjjTUU?~DRPP@333kjj._w^333__@i\6;;Y#DII BK|>_CCCN%A {%Z[[7055mym-HYYYQYsyyI<РӧGUXX(_4AYMMMKK~חJdܸqm|f iiiMT#ڵkr}N6A^^ޒ%K-ZԥK;;;@JJǏ7mڴuVCCïP)?ƍWn [`0 3M `6ؚ`0 Ӷa|`0 im@U@$a f0 m SSS>}˚r @>9APbk֖h0pPubgo+~eHIDATfs8T5af `0aJJӧ;wbu D"QmmmJJJBBBeeСC'VH$W_ IWnܡ:e~z uurA f̰aLQTGak`k$I\\ÇgSSϹ][A sssmmO>mKۭ8pP͇Uuէz6l֒3j h2l6q5`0/4MťΝ;WSS$I. B(HN:y{{$B6x}}}OgdOxoY| А~Ws:A7K]eԒ%Kn0 " O>y# H4jףn|h]!IRJJJ:::-wombq|3 ^ANIHhB $H$@B |A#ɧʅ" wAM2(X P1VzᵦDņ1 ;_zW^(w)(OMMMKK9rdku@EEٳ WzzŀLLLZBi8OhVVVٳ7,,,[Xj;--YYY:tprrh5AYYÇmllZw4MH3Yub)&Rg{+ @:۠w(&cޝ7?.#?Di4 dpt'g]%Y`0ՍnB\믿?4)++]֊-4p[I$iHDD544?.]ԺްakZZ EQǎ۷o2:>pDr;9me

}8!0((H?jJi{DXh!@P'@y<^gFVHi Au04DHUu +ŲW|/\%U`rlhJblI^݀V]G{E1{0@XƓW;Aw[! !AQ$*ʯ(>$i`0P@w仨H ID/X]WZu޽{~Giii߿>}С]v%%%1 !THMLLx8|toqqQ/\t8%%e/+ݠ(Hi&94vx$ P(lumgffviY,ք .\qza!5J;Ni$A3ۺՉ(E" YLŤ $){%1HAE0($@#oVSQ<?S]&z4$W:geu[QW9xw)!$àIP BI2( L$Y ĖPuuuP&r[3 ݁ڵkd2b7&yg~ 4HMMm4M>}$s8G‘}۷Œ3lE]]̙3+W?~|UUI'Ox֭377WUU566FMJ$$;vx T`ʕO>%IĉZZZΉ$IX,X !411)AҏEQ J(`0LX,Zc.//WWWoR9;wׯӧ̔3UZ \&$9LEb,`1(b1HdQEY 4?Așl lf߻+/d1(G&V \ /F?da׽l$b,bRQ0I0HEYPPb43M =b_CCC'A [o(J^WW7##+H222^z5qhwwcǎm޼ݹwq朜fΜm߾}nnnyy!ԁ)hhhHQTll,͖-?p~O<955U[[4 n5>B@PYYĸm''gϮ^6&Mp=@```j! (4i=HivնZ}Ȳz~=CS IdAI雩EI"̻@CGPBSGDt—7w_>y lIoj]YdB&E"C Hh$@^*(5ف&  b BYotC:!+A @9Ǐ~۷ss󲲲D`.]JOO7ojDBBBvv6En߾mmmMQѣl ޾}+Hrss) :y;w]B (ߟٳG(RUZZJQTqqqNN9r$EQ4 X,X,}嗪ɓ'WVV"yVV֕+W(*))H$@ibXViwE%(u&7c& &Ak$[\BVY"PBApm)Zm/ Df8 qH(j[RԖJ5zHM;8ē9XH7Icw?$3>?(v%.3ɔʜ`F3b 1\sb 'xwKhSzGLM>C0dq9bFg9EbNB013veJQ!B9/?:,B![ӧOwttڵ?ի󵵵޽c|qBÇVkkk;:::^zߛ7o|>@r !r]~ҥK'N9suuu/_W$)w5jWqFww޾}}!ٳa޽Νs\/zə\Duu5|[||v755y>Yb&YSlf4{p*gyaTg8#93_} OwݺHO8_eo7叏0#f}v˜RV&eoD %3Ea0g(1[P Q& n:tPeed/0HRX:d,cM$ID^1TH$TU}q Xyy9 gϪ^q|HboeDjet:D;;;nH^훵ԓ'O!HnX ÈFCo_g\ra EL=_,i1IKc?9YvdB$I1ex6{'-aHn/D[́f155FXf c--Pv_d+$$!1W0 .B<6;konfToddN@!BÁ@~%^ޚuuuϷ:m.Ƽ8ᯞ=xjEb$F%B%=cř4kzPD"Q"QE/$|\WHoo/d3P0Ɗl6UU{zz***6m gppŋvә?vZګd*))> ~rsɄAJHJ2((]IHHd-}~וVҲeιR?~= #MP Hccc777,sT* \dZ*bn\.t:m6JW7pQ*`02kq1cj:-ʃSJE* o߾-RfniMD~u&>.))Zfl6/g*^u]O&$eVHXu!BdX,(iw9vHez{{!bbr]G⽞3|=zݻ/2@SUR ڋ˒ϕI]IENDB`workbench-1.1.1/src/Resources/HelpFiles/Open_Spec_File_Dialog/Open_Spec_File_Dialog.html000077500000000000000000000044331255417355300312010ustar00rootroot00000000000000 Open Spec File Dialog Open Spec File Dialog
The Open Spec File Dialog lists all the files contained in the
Specification file, organized by file type. 

  • The tabs in the View File Types: and View Structures: bars at the top of the dialog allow the user to view a subset of the data files by clicking on a specific file or structure type.
  • Checkboxes on the left indicate which files will be loaded once the Load button, in the lower right, is pressed. All files are selected by default. Toggle off a checkbox to keep those files from being loaded.  To check or uncheck all the files, click All or None in the Select Files: bar near the top of the dialog.
  • The Data Type, Structure, and Data File Name headers can also be clicked to sort the list of files by these attributes.
  • The More  gear buttons to the right of each checkbox will, once implemented, allow the user to select options to Set the Structure for a file (e.g. Cerebellum, Cortex Left, etc.) or to View Metadata associated with the file. Clicking on these buttons now,  will display these options as greyed out, indicating that this feature is not yet implemented.

workbench-1.1.1/src/Resources/HelpFiles/Open_Spec_File_Dialog/Thumbs.db000077500000000000000000000630001255417355300257660ustar00rootroot00000000000000ࡱ>   !"#$%&'()*+,-./01Root EntryP ު256_e4adf671cbae255a*[}[fm,&UL}[fm,&ULJFIFC  !"$"$C" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?K?Iolm;9:b% 'u`Šr1Y>f[9X.. ;xT_`:io/,XVgV$@bE+լ(+}ߙF=j͝#n#`y2s Xj?V/]\څȂ8 oTW y|vMw7KpJqHŒ`'֕8(EE3+hֿ;Rsڅ-`15q'Gzۼw2Gk#A |EMi&yObHcWcjB/ ZMeAi)lsg?1/cEpi-eg9v;x玟/>XOHA[U#e@>i }p8t' hF]snNԗǒ@K<fcf3pFT`?_t~-7Uʀт',I9$gTv,,R2y~Ƥs/ ր9+{[ȭƠћg6WnNܱoN8X& \Pݜ]o Q!/'V0,x qЁU{~ͤLNb[uq$ 7H9 *˵ pN (z TMă|EIY ҄d{A"%Y9ub-Bay rz[{&ͬ$Ћ5]˜d9q[F2f9nh܀[|qbKY/t뉣(Co6x#r1mū@nz(98gw*=*~7ۮb<#3N8;b,b }#I/,A$EQ ɓ%q׾z^Ē<1C}_# [dӼC}W;~@G+53Py(j?G3Qw C}?JL(?J(o Aq0[uUc|(["Y\^K2(yYb gLtI46Ej.Xx$j4yK䘳ܝ^h!#مQz l) sr7Sѐv'S47ZLk=2JµLvggY=9myJgIMO)ԮfZ) :pSZi :upI#3W7cS:.G|-.b]p~nF trMkx[ uݻn 'E6ѷUB$V\4d*NO5|L.vF5;T ;[X/yK% Codt_Q.ĩޓ(<088=k5_E.Aq[_g׿'dt'@?_]5Oȩ4l?"}{>ϯ6O@S}*5Oȩ4}^l/. TFS}*g׿'dtl?"ѳTG>@?_]5Oȩ4l?"}{>ϯ6O@S}*}^l/.״MI_3_)XN4G*o`Fg=vbӵk=K^[Ui9uEU-ˑe'zqDҾѦϯ4Mϛ' w q0]u[Ԣ║*s7st X>#O +TMBXaHO5 8{}܏uw^|9kiy<ꫛW/+Fk?m=s@\o'̧2x`sxj]6

\׭ d p_Z#Qu˛oñav;i> tKhV9UBX+nPw99hg qGe AS1L뜨e$`x2Cָsae}A +r`#hS}}rH9s!4y7>S}}rH9s!4y7>S}}rH9s!4Tu֊\|kQnk5t6П.8OLz'#%w ePm67FޞQh)^z==)/$Ul ׹٭ qcnۀ~oh\ Z8bk*YǑ+m|;ٌ|3޺?2c1>M72qYY5cEh/l-mV1(6l2llhh^s-Z!,Q$<}qQ2dsq_x, FlO9>D~4R\E+C@єÍ;oQg.X#QZ39`=s3x:t)Q%Ǫؤ0|+a1&OVG5h]?gtu[#_֟+]/͠ZS÷Z ?&\lQp ^6".=V{$ 0Ӎ?KX--h"^',8c<84;Ć{\YC4ۥsœ nFB}뱒d|a[Fs?Qz03KkwϦqXw5yO.<~^,`IwIef0 A2wgqp:S @.@`F#b +p >uة>eI⁖o,GrC'@ʪN9#suM}fWm9p9 O_#6:}lYʿ Gչ%K`& Tf8# ?' q֋>uؑo4ɵ 7RCo=ʨdGetڈFxn28m$ZOqPYg=<;B2B m:W4C+2#߹MB+*pqxțEoլb't3qϸ]vocO2;~KEiN?w^imfq=7O$ pVm PU~]uqW8pvgtuj0xϰV' XOByw"{cyWV&-&SӍy >uOG딻tcp7' Ɵkg2aoTޣ?{,q=Ig.g<_?rU$U4xl2L\(,Cudrj7SY B |֊M>ui3xJo5qZ,Hi݃ SszG"k˵Dau ۬[pdQn#uzhҷv2{׹ZM5b4Ɲ aTݳb|ܽ};{HJeu2rr95xKU{uido~ϻٌgƶz3X rhmLhq01_/%3\sox/ijcK,6ICswcK^> J'O $]'Mu.bGe,}{4 ]^ޭƽ<2d ?-x8vfAEݏ/#- iGPHŸ]Ą䓍z D? rҼ;i\4$R>JrB1]??E/"OC☥{}ti+B`A<9Mmyj3(݌1}s|ccIwhuMBEBWɄ*1l|1Гl;ۋkhmVmBjMp*^_8e6IH1I${hf j>!32g,` ?sgh~s9@BnRc:6AXwaԾխ]5,f# A=敧M6%gkC,TL>Ÿ+C^F~-j!`2߻=~I"umHi<&s- ލIJ3oqXoX'HdIXV qܪ{i "h5FpedTnGS1sEp]R!$r#s(@JHğQZm6א]x-4; 2vzmwP@eg26c*YA*0xȧhw ]3QO*V1yUC*d 9p{q&+ymȋFv,F%dxF3eE]ꙠH;g$OO$gf|/sDZ_C=ҡBG]:٢v$ZyRGQ &x>T2ZȾ9Ce~¸UPU(q1Ut/I<57|ᄻx!X$QPŧxF}6޺U1 T2Bpy-yIi˦[a%4p{wl砭)V [{i%!\ǹu[x7A rGZEL8Pq?ý&u %p(\a@ cpԅi@'=~CXpKOWTY>#)o m`l}~Z%z7EFO({yֵ\EM:DY]]m& +E̘oI迕j*/(SsGY9/j6v+i6yRIAPH988JՂ=62gq`!!ߙNT|HI|Aoqo&^@),P  Nx2EQ8/g ěvJy"珻=ϹkZ\03}o &|Տx͌?<0 $i$0,Eӣb"1ltۀH3_y<3Hād42&Y rēOͳKUE*Y:2ONQ̻szc:S"Gl<|03t'яS+Coi֐߻[O 䜐3hcˍ܊9`b=I3{Dc A13׆o8|9\p c=8:=g\FM am/*iL5@vb2}=Fs~Rp8h >34KynE/-m$EeWRsuMgD'`I?4,UԷm r ݑvˇqׁ}?iwsltG6ֶJܱr#l qA;,YbT\IbI~Xnq"OQ/(]Ϲ&ᶶ? n{j00}/ҵ4kMl-"mY唐 rzZb~Bh >+v[xx!3F~TUu?!Fؿ=Ϲ[Rz/Gڤ_ʬ 6}q!=򢬅E&a<,sxL1ͳG`\gD7]hn"A;p| q^.MW;Gšg_:|oOD(..bX7l1"BweH8Ge,s|uU#=v]A|&~7]-cf[t6FZǺ]ğg/ی3]uamy=m9K`[6omd?APںb0pǦx5>/G\QbOxe޷UAPa;Ge]gyIii*|'atid徆2>HU2wg^}.j:/:Dѵ'ĺ0} WxEhAk Ku@2umēc̶K]sRw%Erc*Vʯˀ; ](_p0z|v;cҖHY$ArNNဤcI6-Ҷgj+Þc_OolXRu#hC6cwl؛V6F@Y oc1+ź| fv((i^z:^kˣ؋| oⱴ]ΡDxo6K e6l!Tv+rp溟 &W-Z<7ryz×X9<3 jVwubv8!FZGlgTA(r##s9eW]Qm9:j6$k٣ <*;g^<5,nZStwĺ" 2#Kb^HF1O]뱛mx*KKW]**$9$\nCםixFCX[(|0ЬY~040[2FD/c@$rAu[oUsu_KF!Ra## \6Yw x]u-?.NX)*6r TTm^X7Y 0m5%vF<ށwm5O`m`R#* Wn3W0ORFr/Ӗ!rFqT}DCaYW?wp|sG,}{ .4=9ЭX?NxZ~:.?H.d%M AڜgziZ,<90V W9RۈOY&ѡ~$նȒdBF0uN9bAiw ƖvC4-11FdU>t#mPO@ yArN;H*xFo`+۷>c(8`vس^={mݝ:-F`&IkGUŞ`ZD-pqQ%99YVc34iT92}q$-H09P`\q]O4;{)TkFiq.- #oj-y"h+{mxߩH#L`V2N`A?*gѼ;{M s2!%Gd$fC^E-|_+>epʌ3.vxڷ}jͬkޱH@Wc*#\f?dZYeOeK A8 z7mZNu ԉ%șI$VUqE/#_İwW6j1dF992n9횳\ٱ-TGr(p;~S_͵=AT`c I\;rBN;jhgt֓Pg:\2tREWMzkh!֢xK4䃌G٪YԸ0 ^N;׹M2^/$P$' ;0yI8;IVbj܋##*G?/o;ryCAcC-eat@ k}x\aN7k&Z6 ƅ`"oO0+}E/"Յբ\eBq g=' MK|mI< NybI9l59?OF^68B5$LFM8җK0_;Fimk0O7! #N G^ER̞_-D(].ķw ߚqֺ&ZMbP \8;<`KU/o$ܹ-| ԿcMQ$1.FVV Q%d h Ӵ$~:|sެEAAA y8Ǭxܮom^CȨLe_rg0]/}WEKY[(C*>s/,fm9V7{6GG,;^Fz0KfSE&.;udƺu~{4 ډwħfO #'=]Iq~%ƚbXLY 2}4X۹A(qw {kUYF14/^,"IcKI̛[@vOlNǚ=&[_ͣ'$hgVMy u^f{&u*ȥ ]yNNGZ6ℓ_)Hbw`BKi$ń3 7 8WdB@ڵOxeuuKۦ[Uۼ3saqkUK}c?s2F9?6p:`9,b[KZ.O-3xi1sZ޽yxt }:գbN$m0st fgxUmlSTyefa+npqwc4 CB].&9I+@~N@n@$iTt2+;ɈF9l\7U҇ѳOp ³rڶ.YZئ2~f*OL)+6[| SnOY/p'>\;GO}qaö:LS3g+'ORN1LXJH<%RB~ت) $?㷩ó0nY2~e8-O)w ۽$mUqd~;yfi4KH Yw6ss9,IAPMp:],lpܖ u5-C0͢C43 UU gtZ>o ƖĠH70PŸ+ͫ}CUrZ(VaKnFqG4{ J+YFHCȬ xH&qM/<-k7yX~HAʧ;qьV]kR[Bڗ!nG@"AH7gA_kv|>|󥺤2c>㜜sG4{ ϹsR8>s Gsy$Y@[c0=G}ZԭD́)<8d3i>||r%g|@#ͥg6UJ7zp?*9,7:qa5鰈f)ef#$9h,Him *b.2'*?/G=Ϲ\Ucq;35*_ix+=N$pShy9prv0s߈%{z|xTxKAqlZ&IBmP~mq^.j)Z?aM+-RhZ"?v ˒I)鷦ލiGm"C4g( ~P}\>k V;ŁҋN02  }M;[IRZyq)V-O.I7nndiَhT溁u8k%Һ%—dˣ Sv4sOZ$/Ҿ5NX~a/c6e[F[ByS99*NO y7.%p*9#74l4Wۚݒꡅ6󌟔 } gnc5J&ޥy<F)!i[6ʩyP\;r5cwydZʊ`mJ'zi ULn^ݙ'K|ĄAڤ14gi^-ahJtx2؄+: ޠIV*r0Gw|\q泴Y]]d>\MnjrNL|e6 5$@~q%9Oj9,4OKMbei .`|{#?6Oc^Q;s/Ii0]-9Ww=M/|A} I:-nH #fȰ3keФo:~y+q>i DAabGA}nbI%+1ҫ`_HĂ3ŹCi>le!MF7xpQ\z*흷!_R#Ioг'un M.iv D[?}؊4Hާ IՌx8S5K֢6xBKd?/?)U='c/ۧ/ob߅_/nqś? jD+\<6&7J\ $=2S`M&`|rf(c:u2~b>\gY͗O.$ m0>l N6%WZ~.@N4h 9O-|2rkRiFA(dnu@u9ȣ}:Qy^C5{E׮'[Cecfoh>i'<|躒N(&g,"Da䐮0s9Abi D|>7?Q\֖[k-5%֓.-mPlלt$HhAd9ݳ94sOY~7?QG*Xa5=̬2)jG4܅!W[cZvGkw|>:lwWKHHepS|.FN85e[=|J/ӢK,K 285݉OJ誥hƛW[ BMfä>a_ 0-v9fy¶ʧk/$qr׋Whq6(- Ni݅a(TݜtEjͧٵxIbXz9'S8Z]ͮ^ߒn" 6 8 WHt9l'mdHԢL ?7^{+6 4jQV\'pTq~wֲKue-%8#|+XZrߝq3x-ue^}dRKu*HL g;]z.˯;M2F͓AHn1hp9˻߆h?j]&rnp<%sV;[{{fi V(~\99UQҌ/\w>mGRm<'b<Ŷ4rq9#ir˸7,VFGhR˻\n;q~n#o!-\NY/5(oGnp1xKv$Q.tG;xO3KYmHqX`Ip^ ]%+YU=s4mCRWkGYJˀs? YuDY^@3{'.(K ho/ّJA"Cp2@9joH'o)&Y+ ʡFq`Z>|?|?DX[ C)(UQڟXߝAQQ>x5=Z&[w֚5[ĦLIQd`T*NNzl_μ_KZjfCb#! g ^+E[UQuʛwZݺ\Hae>H6ݬLddcoJ|F5Y!ü%~v:aԩ:>oh>oַ@?a,LEXӏIg'n`u* O=ј&FÇw;7_d,@_K0B<ߋ<]샏HIȣ=tyQM*jNUc3WRH YM.53@[qʜ:Cw㟏vϥ BiĂe|o($Nw/Φ$Ve1V byqp6n#qL$$ #mQOMCϴ9][FX(! sǷik  )i[Tƍe}m/Y$V.ts=71Z= #[3Zb#]EFÐ9NwGukV^\(g#"Vlu xŞOJ^k^(niU$p3@OW?Cmq &%GWy8C(p:՗R{W{9 _̏ AdHO͜2bivך1Z)"(O@NL*6vcmBh".{=930(^-myENdmbO;OLh <`ךtS*[[I(c9e2ppq~[>B05lZ8ne-}HG[:Rj=oTbޙ#j , X{~À1˸ddף p&ZublL- iDcn<0+z6i'y[kGk 5kBDy$.O;oۚ|ky .ZkI'a~5i{w#L@bwiv.% vmzk+hd$Tsל~yX^=gIl&Ūm C]O5}:i<bsW|Sq5ŢCؖ4@v8MKɍŚpD3>S@ 6p 'c^=9}kPz}q% b雝a5FRgK\IE)9>Wmrdj/ ujkȩAl gdR}9e$uUM졢26wzd8{b]jK]I.ni.W[Fn]q:-t/WO(m*qWuc˼{ DЁ0 o>L䊘j7kgou`# '.Xx䁎h{`|2;Ym%Hd_neR=m{ PHmyv!R8˞zr킱vU!x$t͞xFsҶ]^[G hvyr6z`0SoKw#ݫXgcW?GBj~  X۬<*$CqF㜍GaMpLb' pJy$1U*~sFYF I8/fs:ѬCXiM ͠G8H˖bsq1W`+u.~g&QenDnO1VjW2xNdѓw 㪓d ͼMᛳs0%c }8V 3d{e$R9g YxO^О &'Ձ'2TfMnOg9X9-tpUACgk K0GjUA^=)mGeclǝ dzV~MGڛML_y1pQi-1C袊MǰYsxOim=[y~8-6Wv#n*K> y oۜgzcUM+^>?LJW(B6Uě}rKhPd*ɂ`;[xMc+!1u'ݑ njo]fR(͢H$$`=Af11,)(`dNs{` -n5o~ \rNsU!K;75B̊p29$I䝏:e\:4Kh~=I=REmn\jER7Z.o_h)\[62 rHr }ڗ#76۬K}N#;A^UhrjG|m!pN0\#i-qg3jo.DJq 2ӃnaaZ٠+#$Ҁb-r8trZDGq 33f RkJAo5A~ok%NI~~ӊ'5F`NGi>QK* E<1 o>\Y\dREU0=;jkqM]1iw愍v`'"-Z\+kpvK#Nnrr <80)fp] &@l|t\Oem M}5#rynw*>grJgU[LȮX,. CֳfƚH[v#?*7d9GXEHH/)@?9I2y >2Mq˖^'R9ʵvNaQX#Vi z})6ޓ?V}6ޓ?Qk?g@6ޓ?Qk?g@6ޓ?Qk?g@6ޓ?Qk?g@6ޓ?Qk?g@6ޓ?Qk?g@6ޓ?Qk?g@6ޓ?Qk?g@eլ!]c\-/V[S{Y4!@NF:r=9+OLLj]cXY f$DTz:}@;OQ Z/E CX/->/R'u$ [vLz_O5pJ \ܠAgc"Vu= ҙ.%XѕUmMB$2(R32y82jo=ͼл4#8rr4o¥WRR6?Ҹg2So&*9R,"t%1)fm-%*t̩vOvSA\\zN=/EN1I pHYsgRMIDATHݖO[2miBP ( ;Wƨ1!A@l !`q.%MX`QQZRLk3uI ^c~{rs"P%G #'HJqO>%ɍ7ӡu >ÇK6ennl6뺬133çOj2LLL.\"\ugiiǏC4EUՃJ)5OZ&f0 Fe"aHUUa2HȎ*_x!wvv8u)lZT*K8F4aNrG y=&ɤz{>,E,۷o$j۷os "׮]cll3gΰF ܹs; ffgg,x<دU߆  ~wx9l^IENDB`workbench-1.1.1/src/Resources/HelpFiles/Overlay_Toolbox/000077500000000000000000000000001255417355300232515ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Overlay_Toolbox/Charting/000077500000000000000000000000001255417355300250105ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Overlay_Toolbox/Charting/Charting.html000077500000000000000000000114741255417355300274470ustar00rootroot00000000000000 Charting Charting
The Charting tab in the Overlay Toolbox contains controls for display of data graphs and matrices. The Active Tab must be in Chart View in order for this tab to be active (not grayed-out). For Data/Time Series chart type, Select checkboxes indicate whether file(s) will be charted when a brainordinate is identified (in another Viewing Tab). 

  • Loading shows loaded Data Series, Matrix, OR Time Series files to select for charting, depending on which Chart Type is selected in the Toolbar.
    • For Data/Time Series chart type, Select checkboxes indicate whether file(s) will be charted when a brainordinate is identified (in another Viewing Tab).
    • For Matrix chart type, select a loaded file from the dropdown (only one matrix file can be shown at a time). As in the Layers tab, wrench and colorbar settings buttons allow one to set the palette for the data displayed in the matrix and display a colorbar of the palette spectrum, respectively.


  • History shows a list and line color for each identified vertex that has been charted. History is only active for Data/Time Series chart types.
    • The chart displays a data value (y-axis) for each Map Index/unit of time (x-axis). The color of the selected brainordinate will match the color of (one of) the chart(s) graphed for that brainordinate.
    • Each charted graph can be turned on/off of the display with the checkboxes. Most recent charts are listed at the top of the history.
    • Show Average displays an averaged chart for all of the data displayed on the chart (charts graphed from different files will be averaged together). If displayed charts have differing numbers of data points, the average will have the same number of datapoints as the last graphed chart (listed at top).
    • Show last selects the total number of charts to be displayed both in the Viewing Area and in the History. This setting is not retroactive and one may want to set this for a high value if multiple files are being charted at once. If a brainordinate "falls out" of the listed history/display, it will be displayed as the brainordinate ID symbol color set in Information Window > Properties.


workbench-1.1.1/src/Resources/HelpFiles/Overlay_Toolbox/Charting/Charting_History.png000066400000000000000000007067501255417355300310150ustar00rootroot00000000000000PNG  IHDR)fJM7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:Eѽ}{y.s`aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*>aXlmoy]pW[.Hgff>/2~^ZzRaY gVa欁p7RP(ihݻjsy޶e˖\.~Ѩ~?z=C=t7oLR7ͅU 攳k׮ݻwa4vR r\*Cob4j`,3M Y500|eqqG?׾վVo}m۲l:F !\mۭVhT*n !ꫯN&:\<VlZ@ p>ëzwzzG/_)<apeַ5˵F~?R7@0y^n4|RDQ]ǙD"X}#g>Ϯu3~===t:" PV^ r.c!T 6<8X?FlVVL&}>߷վ#36>zEU0nY\|\}baFN8m۶Q۶~VBq7Bh48>iZ(JR"j5 `\u?B 2'x a9yi/8R׋b\BaCn$1 ߨ֠`<0V'?H$fFq1 iGGC@XT󼙙x</ G"GCxL&5M;/xuؽ{ƍmx<( BdZVVaxóoɤeY( A{nÆ a !p81 V{RiZ_=}Bu?cuo,0 0y^"|/h1mT:4M d2(3dAif6;>O]}ݲvZ0$!wPz[($jB!4! Bi`в,۶h4h Fb9 <H~_8NѨjhۼyDј77aa,q?Nb1u뼰pq @ i`Yu GG]]d C!jl6{ȑVEߔuVJ%˲ ÍFcڵǏkʖ!=pn!~eYGzn+3*Ơ*p]I+BBُZ' Bԩۻw`@,' "`={*R0kAc@|@ riχ X511166V.{90 0̩5=g2<\.s9( )ҸBMr[\Tz꩑D"DRB:X\ BhClذBߏ`qǏ H$B2P5B! c"nY_z_GضM(rhT =zخH~@x4M1^ïڮhxae !\׍D"b(x !YQt8OC,ˊX ٘7M7o*aaA?b1hC61(VcPOO*ѨwFQAGA]m|>wuuE۶Q_` E@)!M&z__@!4M!8в (*Tj`*%u,3\ 0 0vmۑHzj۷o޲e LPz -ZkSO]xᅙL&D$x r$`ptp7dYHTžx(mRD"y "HDJ套^ CLAVv}ok7L&N#c@c4FFFƭaaa>|X (LdKj<oZhT5;;{R@hltW Z\\|'Q rRA-ph@82i&L&S(,˲, Bk͏>c=ZaaR{\}}}}D0 DzV k֬A;Ty@CFq饗 K/:("Gͷ吏TB˲&''D<:`}AټXՠ=(քZ߿sE\׭R 9V[svsemJ2PC(Mhv,C3d? RXtT tQiZ2Xf͑#GCjE( 4k cX*:qiǏwxAע*!HٶNcLd2b8??DBeY 3X2Vy^,K$KR\>q~U|^gЇ8Zb1`mГR`#&Ѭ4MqJT>'yI "SZL&x.}gyHbZ0` L&{{{g}vllRB\/̀P,@WH$N8㏏i 4 㓟CU{l 0 Ü%5cׯ-t* Jehhhrr#b#R$ _v~l۶, PH}WKyk׎ `[6*tYd%:@2XNyXt4MBfJ^þ0 #lܸZ~+_Yvcǎ[& C. Lbt:j5tRW! T(wuuE"={`1z qTc}eχ5w'&&jUYX I߀.ǎ+ 0& 4o{\Ň0 0̫@{\p6lJP(;>>a"=Z:ԈR_=z襗^JgPņ:B盘, tbV5``LšZuuuh4e&IXp8`0RfMxh[o=pwzr.r EJ'Q恂xOv-XHPH=Y577W1 ,! ۡZ hO>Iji= n!e]hll GQ 7БBu0cǎ=w^<G#.۶׬Y0ayj2 543,kppskx1E2CU?kS!B}ɆXܱcmhKͯe|5+y+>x<4 ]-B_#8|&|_2g``XKRe\ȧ:BHOʛ&APɚ瑠CހuP͆AONNݻ7HI 2l 3=۵kw-[貊I[9!D0, }-..r9M'?yZG0 0̫W@ٕJX,"G7f N#Zxthu/"۶u]rVTQm۶ R)MR,4fcC{`7hȄicp_0_lv_i@ { 7J4@xc]P[M '<D&J#7M4%{Fj#^ &.`n4e =䓓4 YDž7obqe9=ԟaRl0qر\0 0̫W曻^ڵB#סꍟgDnoݺl__J Ȳ>#]tp3ON>4hٮcƘA2ڣ\.<q3r5oy[C~wtUlE'Eƶ!@#[lLr<* =5A{L&G?ڹs'@T5Mh!;}Tx?矟NqKKKǎ!5 hxߏ$GFFǏsaaW[iB?;<:)LVСPH40 B]tы/fǥ&% ApndsϽi UTP(]x|:ƒS딐C #ζj=֭sWmPy,]K/TTՆ*K/ڲ`a rF]w݅XH) jagE'J#LLLG"yЖCF/--azXyFZE[r Bʶʕ=@C,6*d&q)a5$*A`pꪫɀt-*4S=B]9%5!ׁUGR)N@W";I,*,K$@ j_|1>Ǣ".">/HJ%\B"Յ}d\d2cccO?Ą UFJ/,lLTB}c{g؂0 0yΐ>zZw2xyt:MA 6q)CXRJD_ ( aPh4q4*mGďJ~ x`.<+Ҏ8ðLQ#;4:H؃h aq@#R_88f]uUַbXlP>%8^@wyWiu &(X!Ȃ%MYjP:R{P'4_t@ jxaUmT#E)a<\ʘxgBj`R bB +!`|2:i :,Vo  Q` y4 Zڡ`0l64T\YN2' #i6SO=511A%qP} ,X,ť%]׍Flba6W{tMׯǖ'Kjy^)$XަhDB-J>:j 4MFr9 >==aÆT*eYazT*! $'ۤ"l6lV !H~uDGp;FyW"|>LJE G֔b fZHmjh]#z=ŰQ(bfR!TAKP )bMH;e `R"=lxgY^~ˉ"Bhc<ShqshԷ͑#iH$v]#M8 ,*JOm;LZuw>?Vc0 0 s9; GUp8FGs \Y# qlF)]l+wZVRVPjl6q_T.T*E ȱ#Nm*EQlNciq4 r鳇m#zB{`P#" C腿"/ݻ%ZJ%۶1+ȡ9wpH]&iDfMafZU_,uN?>Nɑ-qc#GzZ˚&8tknnT7 ٱFW3R)rb1PMz8\8ZoP  \8zMof_2aasZ֭[w~Dz,bgU.!< V+4TAI͉'8M0NGb$jp|w饗dY8d"SEuPk M RTԁ}qG֟4E2JCֽTFBWiͤF+I^BD̲l6_X͛-ĸIRtB&"s}JLN ʅTwb"a1y^PJ٩FB @Nqd2 2]5 F###>o޽ǎ4 $4M7@TЉ':t+)]+JB0">~gt0 0 s:9kٶmT*u&:%TK5p]wnn+ƒb)]ץ #ʖ.㘰EZ:^^SvO{<9)bs=f͚l6XZݻքvIPU@*T(N%/]]]7opƙxG__TNS!͘3J1!4>44"HP~}П|E"uѣG=ϣN@w]V E"۶R4(%U{քhr\.kYJJ^xᅵk׺JgW6R6}Xxa ({BqO2aa q>5kضPh`2pp[>U@(=UAb1LZݠUM'o 0gYD(sbX,~/Y؆BZFP=O!E(S̩3/hs5,/n&wooeY7nu=؉'8077*%O?L&cؖ-[P^T:i& MK$Lf͚5>#c<J Ar2CCCmҦVf{{{'&&8CQr'@2birH(BWZ0СCBq=X3yB۶#$䝆{Fo_R 4 u sss{ , Q")hG øLaaN3R{|ׯFH4R2St!qP-T;vSҨk"jjRNAlI؊  -D)V.h^A䭢X\R1\Rd2T z@$8N8bP*_x}0ܹs'x{jP.8$ot]]]gxΝ;-ˢ< ]#Pc BE%aÆ)|)]iu:o߾7 uuu%IU@OZ322ߺE.ҔH,P/51rB 4ה R$J={Gi<44L&ԔeYj Ty睇l "Rmۆ?9 q4\Ƀ=D!5sdbt^!:^UZ0D}#oQ)h`08==եArPV|&gg/ou0 0hnmÆ ކ=AO6T7gd>Hx+:m2x"?=) (m9\T ;cǎQw0Bp5)ujN\._xk֬1M&8pBʘPO ()FT%br 2L>c }5_s5_|qwww,ݦ Mi/ئEBd|>ߌ9ro~Cd}ZZZC[!DNRBx 3!#G"t:syr B(o6fb11 8j[2mۍF}(M2)!dd4Oց@oi' U{#(σo @k_i_O 0 ÜN@~ 9Iltܑ8rH.[9C@C{xuxl+H$ҖKi.R'''5MZMh2^Pekמ{h*NQ`B$ghn-$i۷'馛ǁh B֭G?:þ=vFW[>rQ?ٳ'Ln4'X&Ͻ5sjy{{r98[Muy;77gmۄRx e ^;jl*B @6=)O-KA~@ &ɕ34HB{Pj@ڣV  NP ~9[IB۶GGGݻn:U~M lc6z=s0 0W=>Gr!Fƒ6Vu~o߾AEdw[ ~P줞+Wڮ>ledA .'&&# QM 2(vZ~}?ݍ蕨HAhf%H~h8ؕi|'{ᇢ:>obb?ݻ)D"_|q8g>J)+LR+hˑ.w,GrTb!KޯDOsdk2<)oNh$:t=tMf7o| !"H:3iLfa,:0]'^VX'BiPKt{80~Y49rH__ [MviAr9w7Aa9͵w܁LM6xj5lSLƒ4=V&@9p@__,qmҫq҃jBTĶT*!BQBRA nݪoG F.zlذ ,g˄ (6WZmYrY1SFxmtp8n۶3mΝxB}mh۵Zmtt~'"zF]n8UP$MيԌ'Kk=R_w^j S:qE˲֭[755Ajp8%wDT*ypE&dZi}}}/# D"L;ee7H@7ⴝ]ή]S,ʫ3!ğODIts:Jr>T j :dFMUFHpL&}@ !+d.D\r -Hjb1U=7t(^BdD3l[I u|Cw_5ӈ ~܍;v:daM7ݴa( (Q-M;M |>filPj/` Bn):T(F$, :B+5*JBfj+uxWhg8!<> ~#H,D"H4yʕuU3jK ay+Tk~Wp8}=kgpp0LBKPZczzCx*dZr_Z=Hj=@Bgu#MGi4IR)r J~T*xo$J]AxhԲixX H$CCCsRZ?jrda:I-A7GShRɐ%=:ZY`&IS6O}Sgl1 0 JxytB K!"5&#ґfD!ErBFTc !"%pҝT)% a7)4cq'"xx<Sx:Վ 7bTԎ,+PHJp]1*K/)[[>j9:\uUhF8[_G޷o0j^O4)5q̻?p7n_Wĵ_̨[-VE[8 W| o:lTlST D7׿ PHSrK6oǎD(K%,f IDAT4쬚;:t;-uOqy3M tzaa!*s], vZRb|;Pa?88x-{ァw1 0 ympl%֫ᡦ#`FYr~?6mF0±]nrg֭l1Ԏxmۛ7oF\guaZ-+a B(N$h'g,gM1<|>Ġ>oaa!JQJOՂ4lAO&ׂ]Tj۶mI wtS$<?x( u6S !nx KKKo؆G(4a{ZT QqA!&ki|6txj4pOsٷoN- wndfO?ɉ4Oңvފ} &g9VɏD"177RSZ)z\.b{/ֵ0 0+y{0ʃ\v7 QUxО4Ylji"":mxOQmʁ£xAPxoYV?.AD8۷o4r>'% r;|ɑʒэEܑ8^(]er ḣPGq&i^j6C$Npj %͸PqƇ~8zL|P[֯ch`ٌFjN _[h{:ᯪ=TR{e衐+q_|ZEtK~<, Jr 4DM0|GL&wd2 b1!Ć ƁH/Q$PWCM{uT:YˠjRD5dOȒ)|HnY7|ߜ0 0̩wqB)%:J{ka BwwaT; D"d2Cx@! Cm.:ȃ8F' *U P Dd; OJ A+g麞dp*B<.p9u ̊B! 9%E3eGOE(yG3t~xA0<{Ӱ8O oY)n('BׅxPUb!i*f;'Bxf YA.)b^m@HfP`v.[ZZB^n0 0Zg{n zu:hBJG@5!x&i\I*(]^zi___.D"xnBHɐI$0j@MԐ?۶MӴ,+͢N1UL #>v],Q*%h$jLjᄮ4 S?oڴ 7 {gh7sozzz``6!eZT fȽwgOlIۑHV/H8ҖFxFP(P2Bv!D(qF8䨚Q!yЖ5t\nddСC*j! 'HRյ* "!lWGQiC#PLsp8ZڶpIx`\NRt)Q(fgg3T7)ĩjJ]EnkVOO|>-\?M]}&''QqSu]W E*IR)L!ƒS!jҔ*v(}MuK4-$:=*g ӄ{U~J,!j?~ӟ$-D"Ν;~a*kQ52oL&3== >R ݧ.^BgRY2RW(0;$1 #Yo6c܋T!Vmpp$;ژb=xr`,׿~%!`馛/*Itp!Bv=55566f&T()kX*Fk  ϒPj1'QrD" VTtLQn9;=\ >h\.&c"jw]wttX,>t$5PU.]ױ/gff(ӂ 2}!M r4%AEJСCGWww}u52 0 LJ>h4*YʩaPQf~#&r GHs Y`[C'RP8Eu{{{4Bv!#z]v|>4Дd!դ++qVhRDRqt?**7jZ * t:a]E"tѥtjXcXNZ4Zf`mfsppĐb"Jx $<53bt[?~Vaɵ%4s@L$B| >0 0<833d^6ccci2 0 JUcddpTFRYZZBخ.lPwI`õ\.#AcWFMv<,+c/HjK+E9K;ԟ;%!308:P?@P([ !Z(L\E1.K|TY8 !"t6wU<G y"Q >ftCMZ *"b鑣%exMePJKEgTA%[a`9:%%ɾ)$()+r}_=t#mru]^(>9CӯL$5"@+!SO=ŠɚJ~K_:} aa߀V{u]#AČJd4&QT-;H0#Hb1Xp(,G]hPU;MA\{ջ{r2P,GC[ILvh^8# 7/]zRq4B*4U/..T*Jn%j܄b$"R+SH  E}}}ǝbv__J xNDTPԖ>|J'Z酅^@>?a۶Ǐ=&4TH r'u<71Fmk#LbCSJhH3nZɡip>aUɵe]PG\bt37PPp Ø^xM0tQˇP4 mAp.GNQ@; IPXm9.BdP2t, GSRx'T}/Yp|4Z.9GiYc1t9gYp7x_O2ݵkNr>ϲ,ʿhW{"pdr r0DR_>j(9jeWZrat!_G s۲G@Bu]B<Zl@EAWxM֭[W((I> Qd2I Vꐎٓy6|VF2Ӷmi|~qqR躎Rf9HC7G# %I,]4XWW׭z=ڥ0 0+c4Ŝc(x,î3e:?:t(a4h mq}2b ʑT.kcHeG]کS[Z9J] {(+A ?)͢ˆQ؊F lBRX )}lT˲<ِFţGj~!K̅/ikujZ,v"@25ГUB@6 _z>)\Lgyq'''՝xX~J@CFqXq!i-X(OIŪ*TE@@C9e`Hx*M&6lOJ{)\T9'N*zqd.{ᇩ 0\.w !0 0kd2ibtjS.=K$i333(:>&+gy4YbȿNcR)"jC-@ **a"{ڈՕV9 4Z IFA;֑~S/{4׮]{j_h4[ș~Ge烨jp2'}VHpe+gMC!ed&."T<0{zzZ]ꏂ!M<>@`&>_p8L7%XrG׈+zX,u, 5_Ac=`C daaaaa) 788X,EH_(:/#ȹ{qfĉD[n{ORdap6ԩz(C|>!<Db6hmIk Mi'X`A^ "xd*2SDA% = L(T Uz k{>bX,"vWa}tO\x0??dLDm Ѿ}POG{6l@~,l 1v'8 nHp隴MW'ߛ:[XnM웽oK'; 0@~|~ܒc;iC.Fɲz\B>TTTKOiTMNN37P(::..HER cT*?+"bN )BrtW\`0JtɅdjHd|̔P jK#qk鱶VXXk:<ܙxÊt'nGGGERMg9e04"_Vi4if/^@6'''1JGuu YE.r\U@Xlff(G, $eee`N-%`GR% 0FI)8CϏF׈Z I9LhRd8"ǴpRI< #|>RH%}IMH=2(-aSS%AzBpf3ϿL&ϷX,k׮={Qd ʥ(lkdV.PAXȠXN:g.!;1r$J~b,/r;UfLpOD" /#RBl- x JVL&7yqR|/Aė2'&&f3Re]MΧ_\\\YYy>8o~?O"E.rLc؆ n+gNb@`]xG C. ExB肗RWY:@hA ޭ4w'wKKKXpY'y8_Nu"@\_$Pp@Tjc*@~x<,)# "p6,]cF=RRuCC h)(є/rOӋ#Ax!$ԏEχ>XRK $#J Q PJ$SSqqqx|ˠ@\,U $y>5J828$x2N FT& |Ql)fOq[I&11c178j!v xSRXX~?Jʤ2Fa.r\"!=}ă+2 %#UUUK܄IHu n;-J`a(++3;x,//NRɤR^EDPJ\!8("I)uݓ\Vtp/T7JUS•SzTcxc,`U*nTqknn1K"0EdcR[iTʥ!X hks1b=%Pah)q/]rJLGq 4MYYYWWN}կ~u1E.r\\gq??0qHAF9Je0PI&7f111|s@v ReL"Kn,cqq`0W0, ) h ,5j G't}h8$rS9@`tttppp||D"cPfwgn IDAT@cDG| `<هb3 w)_~.ƥBRB^!]8Bc>/~; znnס,9+T$|>,jxKAAAQQQYYy0ӛL&t5򤆀%%% qNA&0XZ"T(pN &i4p#盝MfUKR,~Ɋ ""r  RoT뙤}|^rʡ!| ]А7:RD5D{E.r߳Hg="bEE1HH:DVX';{"ty$!ˆF@ffo"@_d,#"\F:d#OOØ4µ2)UfuV.F^wpp`OH0WdR)_ o߷NOO'H$ri^/&c$ODwL&*x@8?%ԁʅ,䔔T^ORh4eqe X5D&&&Zmv JWNx̓?Nc]>/nq)t L/'x 2@(9$\^^.s}F `kGT*Zfx~ܧZH Y1|9T(8) >9_ g711!^Ќ]̃\./++Fccc8 T*?_j̅V"{{"E.5ҸR(9pl6ze+p k! Jq;@ >hH$֮]zjl6/2:D-0 2r  ;2uԚRnIH$ ppooL-3 y~J2~ldznT*vez{u g n bb"rBb>3:(??# cI^VPPp]wou~ _y!')UkD.)9EH UPM!Xv4%1ɤjju* _TTd0Hty^o$hcH`>eeT ! <. vH$ Cii)JilKK@ e*g@3@c݃oUSA?+))lݬb&-\,N"?F.r\/7MB(--EA*l3ω:VL05H9@*O, ]kVVVVVV"4)3LǃqR42X @IEP;d2 TRJEURi===333ܱ1!9K=F0 T ;yyy%%%ZVY~b),,~ѭ(Q5==" sss333Q1K)=34ΏIFy^ m>֭[n:NMQ%\;)0"MrSRJ&$?bI:atrpd"~ѨYCcr[\\\RRz/b )ևa.47;; '6M!xR|($*JT6RħP(fgg-i7*r9,|eeeSSS^7RRa XVc.Nq\"wJEEAU(qHlv8Z",#b!f`;p5k֬^d2 Zds6j%kb?//֎PU!8bo: I=? BwH$G"\n.~0%z'".h]!؁pxjj]v<"`j[E"|yސӂ 3Ǵ#t:X7n489&4D">/L&@3I;"&"%*L  B-!_ '~0Da܃E VJBwqq12{$2SH/EQ:,x. )94@ٞ+6kAA8N`0b1h6 .":B QH J&  % lNsttxK1KB{"E.G\;w,Od2q&zc4))ݗF2BF؈8Xx0lWH( |Y(M IJE vȉP 4 N8Dž_%zшZߌ!~BL6KA _SZd`lx0;:EZK} LEO*Pj .+o\WH7bvSQQGI.Th4H$<@wv^At2L(f#S^^DePN%Bh(o$ɢ"Vwt 8S܌XrK Isd2Zvܹs%`)...++ {R^ziK1W;;D\:NQ͇=\Eii)2(, ...g@Ogҥs/NhOIbv/8D9<WWW;Zł$02.R )0XI p4a,gx^.ȥ W-92(^|>@lVLC.5I˧<OOO ’e hq&C OJJcJjNLLs0\ lٲ^JB!!]@BsDžBSe`\. _+TW9kK.$VVV8IL&*..t9-Xl||z= _2jX"p[@ YAҶIt/|s j;l 4@ds@ ;J 8W֬Y2t˞/1Zmmpii)x;Ic0r\\gXsf.>=JJJRR鑑5k֘L&.* ;>MIؤLIV"JU$ҷ)+׳BJr͚5uuu555tj$7JDB'(H3̛@F8YZΆB!OP/FAAAEElF{0֓䄙.r!1@$<9P;U}E&bJPҊB bx mXc0WPHB&bF ~`q7RrXd楥ׯiL榦fggcに Y+* F'j܎C*=E.kIf ===$(q.%%%G?ěeOAAǃ2ĮisTbD1"wn.rqt庙cjG.>PfS*XXXhhh0 H&u026+edz:OLLLNNra.D=DM[nerrR9Çߴ :q>" %SbjjZ`06թtC믿~D2 :6 z~~^ծ\vŊeees4^T(. 9 0MAHHĩjd[ +-- 'YjZ,p8!Jo[]]$r"JGGGQpy$W3;$I04, 5 ׮nn/*WX&<Rq]ɤ2nlldRķ3~3L555'O/zyUc I# ^ovttTfr'љ܍8D~42LV)ɬhhh`_ۨ(]V?RGl#N*++3V+|JOD2LRdaہx\&t:x.H$@*Ȭ@i)lq𾾾W^yEʊY7kxʼnʾF\ LE.^mE!{p9Z-eH0Ϥ$v=0xdBg$8UpdMB?(ғeAyC2Ϡ7,-[wwwL&Wq wyɄLQ,{74MQQdzbQ(8L/Y0dHWrQQJ OXoQH%@ d2d$%jdd+\t^aN{q[cez hhl@@ѠTs=r\\ 6l߾}޽yNEDXl6sƷD؉薛s\bP~s#9t[b25𯽴$dn)xRVTTTTTX,2Zh]9p^l$T-YJm'gP(݌F#...d2#GgWFL= r6mڴvҪ*\n6dQQF} cccCCCn{jj  w"Eƈ@pmt)dl-ˋV+8P3zqV\\|~TE$%ZG㒱GM}rQ PY"8\**>Ë*LEQ`tv"yyyǎZ85^EI%n .r@1ı)IVP\\l4'}>H"0LF>w@[VHmyi@ ̷~`љ0 YHJqL&h4333onvqW+ Cee h0Zo@؉^&""7y,Ll۶fy^r7lR]]] =z>C"*"Q7ѧ8sJ^D h,..ƿ]<]v-dYz}̌sΊujd2 F f\CtT~6pgBQ-ʞ`B ϏþBa(\ܾ@FNrG>f20]Мõgrrrbbbtttttϋă};EN6Ɖ,")gN'@  !JU n~L!|Ϙ-[@"`@$u|"Hn ؉GK($"i<`ݢ812NHfp c&L&Ϟ=k6QbETTTtytD+wct"pEN8Yq$)&7 "R fY&.K^5_ ZƧsWH$ 2!՝ |uĵ7LMMM8ei kXY֙$y睿o;Nsbbp.q'3DD4HCI.nbG\͛79rZh2ȕ0cD?, D"0?10 ,R f J F'( 7Bd0L&`0Ù.p8 NNN'#f\^R>gpY)j` G&sկ~wecYW555MMMv-;A-p3HdjjjrrrxxLNNbUbPEH9O3`{xrhU 0Y+WF_lCqAKJn{U")KjE,~ kq3XH& J!ȮW C.KH$`CTt%&>tW++Rә_X<_s%~H[o555ZYYi6RHgz<&tUVx"t4B_sX|D`p7)fD&)FQ \jŃ[?Ka%j$LR/2ˊ/BjŋˬuuuǏ99>t7k-YV+g6St:]0_pZQ2^{9w#/v"W#~@DśX,hiQ%FPkrpjG ѲN3tQPA b;x#gA"/yDdrvv==`b^ҥKj*myqwaX QP YWWWRRRVV!q&'i`(AULr꤄̇ ? #%$'ҫ3T*w8sssFp2JRQ(wqo[L}WVV!|I3O!I{LOOOLL` 08RBcz rSRcHx7 Dd2YQQQRr(^zp%I8 P H s=UUU6^;3H$, x@0;)9,',JJ;p- }`T"{ }~‰ LP)4v$NJؘHoZ&L:o4{V:pw߭&07-`.c޽wŇV5X@?,0Ll W ^/r"驯_65-GZ|dEZ+رuDq;t(?[&8iIɓ/HjY IDAT_~$ӭV("Z %%%@]|>i(jDBB+`35`;%%-Y>L&q\P(mO%3`0ˮP(F鬬BGj@C_h4DfffFȏ9Q4&.ZEA;[ܤPTMnaqeR vJ+u]BߎhOb2x<\XQQT*U,yxP*rLkh!3dzL;'ӧOkZm *J `gdddhh… h MY,ǎJ-Ѷ +SiXXZx[/=aR2qj k J%*kd2H%%%Fqff"%4bOJ0ܭ⦃aDȱ1?l6G"/,Goo/9\L7rdBkܣ@tܹ=ADzZ6-{ݼi]Yl6Ȉ^֭[r;vR&u@gI] RTԥRTTQ]]Q$TLbZץ}V2EŜ ͆D+EуFƍ!R0<חe#\066vYtʶDhX&*..^zuƆ$5.7~lSZZ n"~t:`Rqz\Ph9%fff 8T*J&FFc4(1dD.x<  ׇh2#E)G[#G&d###6*p?\:6ozvgI?\c~0gXdǝN _|رe4,hIII6A Bʪ55! GJ岉hhnR]'HO=It2>Ns}T4sMcǎ B---F[[k'}/kEtC՝:ujyGJ|hJn`qGS-0IR!:8D{B)[nhx +}>fq5O# ̈́Zc^G&h&&&<X0Ļl6/%l9#)*++WZe24͆ Z[[o+WZ,R\\̕yZes.];44 pŊg&% Ͽz\{Eb)bY."@]Zmyyy]]df^ XN,-QPP`X٦\Hwf.!|l?cfƠt:^UUVlu\- #a6˓vI&c>%5NI&/^tfl٬V[ھ|c;- ky9A#9 W}Oǽlp.Bѱ10|c !HPbTpNzspj:ϧRoLxPNʱhSƳn~_Rܻw/}|`{nɄA5556*G0]Wy@Jq8D :LjZ祹a:::o_56_'*EZ_H?@[f` >OZD,VUfoIr7$!׎e,Ŕu7?#0uT[[Gnjjr:WYv#zhsNl6wttn| 6QNojj:zѣGn.GW-,,܈VDw^AndSQFLmΐ>DQqqZFZ (k cjjj||<L8ODi[&&&PH$ l2N'4"VKT*YVpX,f͚uUUUL&&q(B.q Á@`nntbqqp[y:ܱh/hØFīƝ,zNte1}q+Tf~Yaam݆j2Pz1 fἼ<ѸrJժ鰛у,. H?.38ÊM)qj.m"}>&snn"BQ\\\ZZZSSSSSt:n喏~'|x`L? |T\\z(<~1 Wo㥅|AXL^盝}Z\\\\\ dᎈxm!f9fTD122X,j;BP?iO"MG#šYw4N9*Xws{u`2nD++E:t\;ώ;_}櫋z HXF޽{w?Ȼa B6vWUy+&f>>^ak}^/Op#R?)z ыD$1UD${MDD{)+xWXOrl#Jmڽ{Wp[VBe-^)l><K~ؒv,ϼg{!Zw?CO>Jeeadj*bYJƍ<7xg/]v\SvJN24jͦu̙u떞|E(TJ\_>FD[n}HѣGRޗ Jgt7qE~5Z;ie[`TF!5@IS7>> ~?G"Phzzz\.ۨ:E>4eaB^]B (,, q8&H2R׾v=qV@-#Ttzzッ}RMMMk;>`.~|}FB@h-ʄUbvqq1dԫ%/&xW`P l㯬'd 1~AAAYYfC~yqq1lQʾ1DvFחZ,R9]$G{Rr1)h4[o:ur) EcN3vq_җ֯_?==+VZpQ0T*pʜn ƂƋ`/^zp^AQ| 98 0&P>5n f,frEEE3Wtvuu;J/il`)[.o9zjHJd!rݨ].)6mxxx߾}?`G *WIg^?8vóٜ.TZ#rp:A>ҋ]j~;vkk<`FAD? Lɻ=/%_嫺~=*=P{9J&O&gn~+Vi_2YlrGADF}A~dp*O{C^vQ;ˉ6lxG;::\yRzMdl[_;v<B\%bNwu 61hbD"DxOX}:y$&j66665-1!O=R2&}ffj\νt:Ϝ9ÿxElA"lO=4>>nȤJJ HUDD?exQMd^NG?QGG˕tHOJ?gc.WӹDAÇovm"$65U:u P2^n:wfe===K Mq T}FqKKGݿ֭[3bR [3=Dc$HvmmmѦ72g}^SmݺW^!:ԩSWO?ɓ'{[(QlN&.C(''IAiͧON 1B[ T :~,c 2744 B2Ar Q^^|&~B`0קR6o @4 ZJ~~o3==PUUߵk666r4#mI/^rzzzX&y-RH*/Z q&CR&JpYŢv ņ=<(ouy )i"ؘ0V`0R)? sy|$"1.))3gDBz$oooooo?{|hBj(&E`y['x;I\!O˲fK&===^wllB!=jXcNR <33]g~#t3*^Ú'늰2I+\Q_5O,C| *>Ub_e2m\5Xl&B&fRGE_Eq3>ݻ[~lnnww^`O,?_Ez^S}G-#ںuh1]fK=ֶm?B㏋F枞;֪ =9Q枺QIl| sj5ڟ!o2ᣏ>_M$?& zHSZ[[owtuohe *m6ߟe E>Lx7.kG>)&鮀.+yFfW=t:]<:'Yּ<-+6DGKzuH7flйk׮wM=J?rvb/@& !IK)]@ss3 iU^_ %|p[U*2*\.W$YR>jiitpx_Yli" r;0:wttBڵKv{ooUk\xbQŲI\'O߹s#<">Ϭ^y&xѣ.nK}޽G. [Mw:;::fѳrr3&fgX[0mcPpCF9ޤD1+m挟VbF34 |>drŊ\Biuvv544^nO}jk֬ٸqիFn{<*^&.2mC7}jjjjjj,`sf_pppɳ$^6-༂J3f(iaaI0("BܕK8 ^o2dId4KJJR3WR(.[1x_|Lw&Zl6WTTխ\h4DLIYN{{;8t[d؞ұs?3g`xKH"t2iu:,-#cX4E5b_F1r!%V蒭M)ՍI,ov8wy֏-U}m"r ""jr8] rBct:kܼy+rfD6ЩSoq!壙Qf[?|G-gΜI$ ='h&GHt|a{~P7X>-k,\߿aÆtƶmz߶Z+NdڼyC9RK6ےDol(JTQe'x<  .}Vh *URg.Blͤ1hwkkk$9t萄}2i,dVlֶpPv"N'o nɢwDʉvHDz>4D瓛L4559sva(?&{,d+-~dI'Dܼugzvjjjfa?ᰶS ߸L{{@©!kQӝNuQϑ#G^8'xe͢0{nuɶ/|7nގnfY,=Zj%NץT2d7nxwsfs1F2NN*Ayg.|IfѣG{wyGB cָZG3UpjΝ={U"v]Qe( ?2<\h@9#jZH$P CCCDBbϼh45ؑUJf3L^w|||dd([b`pXӟ{lٲe˖;ck׮@iuxbWWWggٳg/\xٹ$teRB/{|rx|F+& ep;s AK+cY.{8r_Yt̊ iňTDQ #{{~ EڡRuD/}(Ft.aA% =[PY>"dB$UGZӟZ־>uDbtXAڤR ®{+LJ%Zڟ˱bAQ&}8,;/vED?SN9=}ĉ3PH@HevHd^ 69s9ظF8:OlڰdyWȑ#zDԓaغuxMҟ}᳟C`9p׾m6H{?2euǎE!""'QىJ;<Q֭[R~F ~d\qyz}0~juh8p!iK^&/ JΝ;D[[_>|&V'*GDMDJ% pkvǡPSW|旰sjRZ)rC}}}ׂ%+|9At)'tNxOO#]:;;;::\.s:wTgihſ\E(vOfs|菚?Btu3^0>>T*E!7{H+5iSf~vB!ׂ$!oWqi`w7n z\T7|F%vr(FDNyK {7!;D%z2\?3hoo۱-THNj&OF[ x?jtz#Q(V?x)hQK>8\[[xx&uvP6':^Ғw(Qutt455 ;ضm!jjjBݻ0~0\mk{6m+DS`6T*f|XIOr+W%DwGihh׿;?~tD_>x8r9.?~8QrC áT*a? ^Z"9'?p? }BAZ}nE6 nwaxF!zbDtHfm1@N:%uD,@uH uIgg{J\|ۈrum"zhNw{{{nD0Dp8̟-[ھ/9F#v^9iӦoLů7IۤMGJ-b Tuꜙ]g|Ϯ?GqVf;g9;ʌ3cGAp*`ڴIۤIIVC4y{aeLݹsgF5{SȿVUJHrP=(sFf^dB'I˨jЈEl_kzz*Fro鼧={6sH$b%y3f8!`P 7~[n ra``'nԽvYf͜9X7sGy꯮(Q~R`%6OFt vm6[H8Z:;+Mv׮[w]kݓωlݺ8"] <)q7$J>ߥW_=aŊ}_={+ZZ^n+++Z`]MM_|ٳgӃ>sP(8!pjGygVQqn],hKKҥK΅7G`}77_~.D:>[o߮ĚMv. _ _O =u饗.Y)ʹXg:zK{ڴi"x%0gϞfr:6BP%KVmٲ>5/={64ih%K*S odg>x,ZWS<(%ѱX{ݠ~-ZT޿|%6qWHn֭Se]hѢm6>tWR@ O#'sJ^ +W~gIa[[3<"5=MM5ҭmʁ%@H56΍w+ftvz>OZh̀SmѣtSϚ5Qڲe˂ v4,:'~!o _@(4!Zg޵O|vttM߳PHym^͜z[۞*&KWXF;::^||0|S[4g\XUUiѶ6yn3};VhW_{׮*vܙf-yi`z|Ӧ<Ȏ;"xX8KX ؁-ӁӁH$6mr57F'j0Ե555 ~Ȯ][uvC=q:`YNGm?:'Ι۷oW+^z{Ξ=j-/w{TSۦQf`S[[_lYsss{K|%!;k^%9~TJkXh}e?Չ, e?xKjZZ^W{B!`ݮ]˛ <_ƍ:M>a?'W6`Pe=*Sew  < ewرh42Q89N9$b$g%EЍaZ-sfRt~˞}f|7<r_:x ?ZȢ'(nj-t襗^*~xr#8s[=:eʔQ&`gMgk׮fdS{8l2o{ۺpkk7w B!}xnq3g~V5ruM45<ΜÆ)S|>dE2˥ҟz|p\qלֶcŊ+۽r->_Uv](ښjhpΞݮc޼y۶GZ^/Z襗;W~xN Q jU90xXR۽'zڌ\MudJT8Ñή%EaX(Z,X,ƅ$RWZ2i>V ]Lᓕ)rdCBrt$PsmBe6dfHx98$Ծ2X[[[WWq9& D&Y7<o"-Kww5M &'7{M<4)Ih+}?9\9a>@rˬȨg:uvv`-[~kwttl-'ko6qvx"'3883c-bQSSӜ9s^/]z>8ѱx9gΜ~Xg"J$nw$(y]Yy'}>OwwA$Zr%F ,`2~N}Sy@ L&&紤D"/i{ײH$~*#U)Q--|;wtttpGl9xns]v`p{y55}MZ,e%s~ (0wڵr IϝFM vۉDh!#rL^^Y3m==kzkvpYB5?.Ѝk=$ֶaO=Ԉw+:;;? O^ٹ+6:/`+]1:O r[>5o^M[+ B"~\@ڵRm[vGǁ5sݯ !;]@`C},c'/Km[GGǎp\^g<^U/N=[vR޾@`%cOz"l 8%'S?byy[KvEyHcD{ MM{<_.޲p(yY0鎎~;*`߶keKܳv-ilTl7#`-Y#㺺Ql6q0c z["ۿKYT^~jΜ+"P߼y3a =YBHHU=0EG-sa8q.^zYz=OO8@Z}0PG閭OPM8KTCTUU|I&V 8v>] iGv!,Q'<=3@?<<]Y4)՚%+j0:,v b D"tZJI•/*(gD1 + B9ހBu*eAɖ2 \W@aaauu5joo߷oL!pcv0v 555UUU ;NΪbFdY#RP1<<\QQaZ'OovB;Ħi&9NSk'01sn߾};p8***+tiw&4,Q&Jx =-Zt:gϞdٹ _f}qP?=5{ґPPE)6mѢE+W{VZ? l K.(۶|zƍ7n8橉mf@#0n9sVj<ޞU5Jc~z`/Fj9<5ѱ;mm5r3e9. (JI]8Jkgݬ!ȅ}=[n z:^oΪOBsfxx9Xf͚n4=T`[A x 0|mmm6U[hf@1!K֮͏ٿHf˙Ir"hkkkkk)(ckkkGs̉D^:!2K3նk "eLeH%ۥS-*Ho?S 9@YGG#@Xc8g] d]]sJQ,:;_Yv=96B*[Ze2!`+>"ڳiীpSSSgg' h^`˗{<EQ=e|>bO/Y2ϵfңX(U.H邎Mݷ F"+ozHQ>^<$8^z9ڙ3w[_@^hzRʁ`PZ'TY2p\Okenˡ~ZUξ n s%R&,>wީ`qHd!=xW7Nܼq/}K7p 1}}}[K=/Z9C]'F8 <cR 2A֑:eBಎ.:qjFeOQϐJ`/_ yCɈ[ OV+t5CӋ˴I(GIq~HllZc^SU f^Kq?aa.**8qb{{{0itWSplwĉǏTVVz<ܼ!riH >r Xֺ NVU'zual,KWw][[[QQ)eU4qg֍ \25(Q ),=vKKw=Z}t&Y|oOb`f;nuy/hސc=l79thk}&8N#'5-Iy@@]vڵx:=!yU> Xd^{D J;!:b*)++3TaЙɝQ/"Zav>W^7 3 (Qaάt=lԬVY.r"'R.`=Piu2%ffG?tH$\.Wccĉ~{rsW$AF#[Y/"zyjaQ 2٩5FvHAgtZ`VTIU(7ۚO7C8rD_s1ˑC20IX,QZZH$H^ѣGY\U@H:"NR`k!RzR.9rws06:ɄBҌ3`,{MPN'8C5MСCV4MӪ6"ѹt)a&9pCC{wزd .%;vќ3]Z 1Y,+`;/fpvt6 ˖-zVqq.MQgkdѳf+OjS#M: د@hn OBQYgg F@JkM-5ʒ3nR1ʝ͢?2&NGxX,q,sNx{W7=8ٗmWwҨLӴ-*8NP(sĉ5,Qf%z{{K $L2/KАeqMP(NJ݊bpr \.Pѧ7徐kr,޽K_N?,Jb(|g3 cT)|㏐( M{gS!67uvvN2EMGюak|R6ucԑ;=K$Xeirĕ=hV 3וoк;:~s/8=s(GH$t:MsPf Lu`c͗_~9J#ݑ'D YJ̄C:2b/X@-(( OS3 Qa#>_WΝrHN(0M3r 3" 1U{8d&&oN:9l^] &SUʹRw(g:6U"@].;,z4s6˨\Vt-kبKV:9exC#S$ zh"8E.d4!x"=PftuRY=\.Wee)Sd$;wn 8|={t,ggj|($ɍL qb=f+d2v`h)ᮮ`0hX|4+n7O&u"0x9PXjȹ%IȨZpՕ<\.=;blr0#rUtN4S&gw}?bёgLEc[8v4uON=t쿍xKlPoT_hiN5>'h4:<<|n^n{?:Gj`2> m6?:W^y, YNd Nz~ݪ: 3AH$E3 GDpC~;%g(Q)HTͯ#b+|[߲f͊bv;dC qe /gZ8ZI:gX,DQ2@1U?͔JH5[ū' ˹dАl[FloUׂ7^m}?Ve~taYNy]bkĉr6w"gdr"8Qbg]adfs=x[o@k!g$gjz}ZU. +ʨ(Rbv. ӧOH"H0 R@B16mZuuuii)Z]j¨["@k4lO^! I4?cƌL&0<<? ?GfR=ΥyNyuꑴou6LT`}{F_3{@I/4k Ѹ'}:;e Ї ؽ{wYYP#t%ڌ]HEեLcQ--8Ta\II Ea>ft* 6L4PI2 Z<@CP5<<,dCZ]=817&)++s:%%%T)G$СsuP<4I2?r˱yN|Tڠƙ {Ψ9J"qmyF3҃9LMàb qm[U,2dXqT*eR>*s(\ TTT\~߿{{74ɽ_)53,QS2 Seee,[d/~Fx<9Qe" U<4iҤIئfӆ{apvO\n P@2ڿDRDX5044`%{n4B=T_kaq IDAT h>sf݄i=q IxuǷ[f35[$a"8TQ @JDJ%Td(S0D3+++ڏZ`vܩ3Jx aԏ9rM7Qbx.2 ){txW_Bc՗^z)#Hסc( f"Cv̽%'N:q9}:Մ-٧3_)v᪪*ԦRmí>29&rFS#Ȝs1p?vȑ#'OgTD?Ң+ ϋ &Ӎ%(a {755?~|Ϟ=;wCr$,;TXYF# D `vww _֕\hz(X~„ l)HwrafcޕiB|^ 8`J4v{wUWo"8|i>'?sn|a,rOqՅiupT|*TVV:_bX__i.`CeT’6b)I)¢3τz7L*l]v X(;w˺X2̴iӨ*- %kXQבu΋LvLiӦUVVVVV2z#Ii*i96=Jɫ4!! :ĭSHVUFK/H` hd9G{7Mt:Y ?^3w%XY`4dG3ddY5Paa!EUUUfyuGW8!3*L:TȁN?x} ʯ^r%ͭoLHɽ&sehӪ鞾&-Zn*Fޜ5kaiX{Uk+944 &TTTTTT\.7 -=w:̰""O*ZDR("lu䊊b/))4OvmEhUD++M XZmZ%%%e<88p¦;vlٲ0d_1}6xRdI1.æzϞ={͚5dTYcic```tQQfH`Zյ"knÕ 9g)^FoHۄ~fYū vuuϳSm)yΕ-MdGEzyV4>'`= 7}`޽fͪ K&&`0G:tEPZZ*++ `C)  iSgĆ YPbI̸;KKKiF!59=)E@)9YTT4k,iINzFO3'2 `pT啤G~Јe-W^y2Y\)=!gOL HR5tbqպuUqF׊xWWב#GlOoVJNa:CesA?dj/=iaiiiww$dxGv+H239x<.aÀ]. 7xܹs8p@ghafA+LL&/޳gM`1_/yĉ,Fp8ơ肼U1̒QYcD___0[$? RTYXF.iZ~ӟS3hԚ'ՋvaX-jOoEsmVXs/lsBGMhm XjO<)&YUs0Xӆ0 2\.4i 1GYYYF)黥 Z%twwwG"R'Nx RHi_2dOv= T3Ϡ5r؈̔bǏ3gTVRRVܐp0‚t.l3&!K^fT&'8z:ȡRl  e0Sٵdr&qEHy7dD"yUWN8*wV2%FRH[C.(Mv"(C M85XG|bc& %yFH$(kemᢜT*̨k7pCuuuee%=/ӌEH҉w}k5MTQz $rbhyqBr$vaQ/b}Ξ߬+z#1 ľ˖-;pkVQQ+%Ca;ɉl&MfŢ.dݜd: H$`7lMMMwwL>O:JtBP(eeeVMeĒH,l#GU86v{\XlD6l+$v`%>U+@AF#ŸE;_X$nPR 2vV}>L&SWWGU5d2ht``СCՌcvvJv:}}}555?~Rf/2~#u^s &'6U^x1K="jN#q)%LCR=T=aU1r#Fi4CCC===s̑9ZzBXTEKѣG{o=n͛Zx\.,iKkټyT*vy $gUudȿ\dm22eppPtNU5ȨJhw>oG"3O!Yɜrx͢3W_}uww7>0D[D"'NB!YnwMMMMM->^T c,m"r_Pd"iQRu#F+U[{ױcSl|2'ϰk7'.@Xs1}:yVpq̲]|9t:MTA4CS ![  (%CJ`ժ*(NSK>3L`WJ\"}6N7N_q\rImmM(*'lHN2K^j۴:Xsh~(RTHa Z___$ٸqY^c޽{E.Raa uG:馛C^/(  4V9o(a ܀o T/|E.4êZ}AQtҗ_K.1dtOʼn9`6p܇Tzb555iC iO+ >+It.!&ۂ㖳M^㻊ޥW^5-zqrs5n h]1DhOa?9WޥXz F>p51pprK'D8FND΢U.R96 * Izbay"D"G hjjqC+3O\Çϙ3gɓ'O.))!U`ZH8t!r@-M0{W3Spu~?e]VUUx#!f^믿T]]]ZZU- źebAR)a)$T2 z\dRHMu=$Rs\m۶)9AI6a9qIt:`A YPPPVVV\\̵kfn#7fvR)US[C|',Rq\3 'MsΟqǘ:W6w~?̏./zƉȜ5z\?g==}3? OOXTρ좦`4ej9RMugұX,7tӎ;MjM&DDP82t7ӦMP$s#bpr-uuuUUUo cD"z/ImBQ4ʂс8tK嘠USk'0ĉW_}u"`i\|{뭷z-WZWWǦts0fumcǎɓ'K;dKM -404 eSi{j^veeee;vH$nfӌw򹇡bO<d2*ƨy}L&gϞ=ci:߄doiUDa۶mTtuYU56Vx6946q;נfƪuasB|8@N߬RE65D",CcSmpV"VCtu+zn޼.L2B(&AcSXfm߾+))W:GWyfΜ9qD&YI74DJ먝4D$ʏ'r$+rO<,ٝҪ(Eww/Zooo$կ~uW/nYfQt:Ś3gi7oK&N+K?=zPE5F2988HA'eD ;N:ںw^{*Cq +Z%%%eeeoI}p%+\AB0Mɇ}}}ZhNUu IX~Ge3\3h}?8*msOPgbaseN`裸 ؋S0e<hy=s=6i瘥.|'EYa~X4syU[SqӪ'P\^wu?|>_yy9Ue]NaX?Lsٹs;n< 4<<DS.DK$YIS-TpՈrAtY/wI9[%MȺ{+**Y կ~5}tM=%5"t,=ǯHЌ=H!2֬%\<ٳX#;Ri 0Tš_7xhXUҬp8|5\ve&<$Dn /4񆑭mA2x,xǿ{:+Tdk[HъNtގ8Ϊ?ԗGg9sUQp.! ]{|@W \3QKO?#}zEE[,Ωԭ,w%E#%u"K_qcرc/6[~r4)..馛xŷn:ydFX͌tTTaP<\H`)dVbmIIN,~~C3flݻwn=@o^!nNt:].̙3Y$!AjH"Ȃ r&P'<,ZҜ{걣A:$Nx6m>4{o3`X?9O=`N05:ꅳ^uq1N|DZ'6>>/3kc'|PII td[e3I2*(N!OO0*.]fa!әs|1bwƩSRI 9 = w/d^nI=uǏ?>K+}b۰a_x㍓&M*--e՜9s~ikȅA`_MӴZL"-++coFQ_C\z饆a޽B?; BD'Baaaii%\rq>"d,:o 6T!:5gq Uh4zO}Q؟@[0fh 1d3c"Wa- gOnP8∟Q;> ]W5{c~@\ IDATay晅 ^~.bHA4En*x3z~>IF BrHR=w;e0΃uټye~ٹt֑CLL&PWW'yg%_jdt`G|..*XJV<f&ܷohgä{ss*҈H$r')**B"7D mIII,!GIRgwC|!=͒)S߿y/ 79OЙF "AٕeMչ6ty ` 8cBY5(qO{#Ԩ-sAe(;9k=ɏa#~q{%z.=Nj;U{}:z7|MoZD6vBح{=@.}dCh@mmm]]APcBHh|yjjj0CƶX?aI8au+ [J%P(CR Hx$ Á@@E٦MF誫1cULeeeTGps0)Kb 8p!o\~vd^Vۚ'_KgSLb޽{Ie9G?]1@II-ih ݹ  nW]QUS/ %@ ٪laXo),thjz!|{QiH{ ?8usǁRhD:8=l2;f:|ьX|gk]G],aqu8dfήf^plotwBmm-: `'n@x+p@ilQ38@H$ڮz7uB:Qkn7$0Neޞx$b (p?'¶w}wmjjd2ӧOg2\HTZs Os8T\Ҟ~bqhYRRBlI zPͻCeVB+6l>}1fhF=x!'Iv>F q X"e0RA 7?1a0i1gn<<%8y0}fWkodۙ>~_|+Ւ!XT9)$PHej ÈjrL>44$3 n&2<".TUU5w={pH"{w!^<)~L*"_(tq8?%<_fGԹ->`}9QvQމNʹqnhd P (4Fg>- mt==l@_,V n%"// èzW è bc:8Zu3MpL<:th&Lx<̛׳d{i>}ĉkjjJJJXT"GѾP(Dǔ)SE pb)kQF(~b$ vvv;v}:E j\o'jځvܙfEU7P@(jO&dFO$4ϩ@(TH?(iwFh4;*䍊ewts1 J[|>-oyK,eY~whzыBޡajǏ#{˿\YߜDu,_7&(H7?;oqu5犸q?(qJxIG t/\~40R2T>@؈{5Z*NZ.K߿?v >PT*JTZA;vh1 )3vfQX$ ٳgy'!%QZ੧:t 7peR)j"8@EѡEPR%NO$Bx4cяg}Zڵ HPƟB={XGyG|N&dr׮]QB i鬬ϓbgg[v!.\Sߍ{XCVo#Z}.q< \}D[QDcuXIQӘ+4d/w\6la@u[5ՁInYË{ d[o"D?yE 'zꩧzꮻڿT`+ yͨFsN>M} GFFhr[A+h4=\ڵkW&Wi3?~OMM'?I~llҰmmljiiٳ<6_`@E7Z{CqP SB"3vD?'Ir]D۲n nK9䖱<'4)Ø7Vg2^|cxxXo C˩[Pd2vBXLWU;VVV B^v%Io`sJ 0ұ2477wر{lOU|CF*ZUTArDE(1&ɹsUUEQƨ H24KV+IVF6C-m ;A7ᆾ1 m~2\%=ckT,RL9= X؂@^j:m.@pSlO!}{i2.,8:VT>\d]zX!eLJ-X2$@uEcRD]5 B$LJhF! KX_yJ5 `\{SSSX؀ϊpT8&H"eyvv;v #J .G_uUfܹsƊXDan_ 94 ʀrzd -p>u*DF]b`~?{DwDNj4>J{m1$SƦO#P&M}v. M`v>QpeY:;hQTByP~"kkkTjjjjttjT+vP5"?O~a|_ۿW_}5eOi(H ըhZ[c޽CCCsss333jubbtZ8Я sss$,{4]Vq-Ε}%Z㠐Fz k>(UezBωƢ{l tt 5uAo#u%=yaj5% a_(Cn`sL f jF+ԹVuʀN$H^S{+PJ%X-3Sׅx^~;wLӑHdhhHT5EB ã|'N5&t9/ j5YPmD{`@uRݺGUZ΅ܢ e`p\%$&7;BB5Sr!sH+Ge0B_8ɉ>u6B\pǫxf<5"!K .{/ZF(COd vl6r,CyJBVȲ<111:: UDKs.gggo}[{B77kF49f N(3c,H$ɑBpe)d%SmYIHT*DjOCPFquc׼/=jvs 2XF^U\!,m(8+[Uk`uV{YI*;Ә݃.!J)X"f =@V{A^ 7}2N@=:t.qŻNGT*˝N'JҢ/Y#C-EWo P3gعG8Ow"qY,6\>ln_B%\ypd' % @Џ!5܌Zkˁf?iqs5u{ ,q{oMԥ5di2KKڬq!-=.>pG{-I?h K\dؘ,$DS8$zPEKbulVuWT)DEFqܹSN_嶞#/6xfG dX,vi&N"bX *"JeaaaǦgϯ\yﭮ`zO?vtzm'0es}C vi1mمT>d ]b?~( . =~23c5y.zݘ;S"W[|c ؎mCL[yElnIJG\^s88 ǀދ@( t:$Q](#!I^ ǏG _>cǎuN\nZ10VTUrX,o#D"볳^{}؆7~Gţx;LښL|[_ CE8GOAU!pa&9Զ8Y@7Ƕiw1ʜ+6!`|m f8Lt=pi%5'6xGnčݖ|m!t}k_~9uscfIR)vPX&슇:uԉ'n?@o(l6/r^H2 Sis#eL5J8$ DriEQJQB077OOv xqd䍫Z:\Zzݶ{m8w#8y S>wr;sM#[{pMձ8qK80c Qp+P=vmdfuz\Qhuqg#Ac+jvi#DCjhG 3CQ#"^~sx >|My"[su3g,--J%EQ(\x(9FɪN cbb Ǐ?zh@<\x駿oOOOB%wcCCCCCCDI1"AjYT~B$ >S.WWW/}G?H=%U-rwWOor $ZtqnMoKbOiw=1kӫRSځkl4N|ܲ6n~NS3JJ~q)aa [`j}@\ZTyu =IAV\qu^2}rW%%G%~2 46.-݃m=[zQrzĉBP,r\VrT*(JjJJ^'B@]t(j|k?|ᇷp?RX,J㇨F܃$A!*D@\T*D-_qz^ը7ӧ_~~9.jevSSH1IU,hM"C3ܫ*C[mRwE3_yԅ{-mOPPn Lo¡OӷVfSr*'(Locs=B0WUk2TPw?!]ѵ85v)pYTj4ROg6s,Q+AWtn˭Mˊ҃xP>eAΕ?w}wAmh9jXQg73, wGDN(J*JO~_e[5@޸7+$T2uE5jCcCrHBPT2L:&BFNl6aE?fvKL \ݻӧA;J#]EիWy[mGY>t ٢{ *kj(b#- H6|Y䮞Ȟ^PW~Gj:8ugnkTmx=l_AL,Ыy`"ݣy^"0^W^9<={~`Jqde,{q(%mk@G6F{cQXpݜ8c{{a1s _I2PQ{xXw>Lb̹g8)P{x)ʰ WUWtV-ݗ6{Tĸ,- {qxVܪ~f,~ $}<6-RNF02z=|I+O?Fq,S]T`-R\E (@)sssG7 {Ou~p8(PS!eX1R9"6*iuر}"?OEv qds8{)TDCD,QA,v'Bkcaiq<i0)+lEwjN\n|ۮ=j0ќx% |prC[ty]e)%J3_j:!QPr? PPit1A; .z_*<ҏ],<潒o :T$`qv q= _j&;(DT z>=zh@<\x_~gϮ,//+"D*c%Ñ vbTX,>x8}N7<83ZUzNbr3cXVQ 7cl׀,+y 46\-æ@ 6.ʹV3X`p~se*sE$,[&8G٫*f{U[=$ҏK7W# /s|=OΕ-d)^>osڐ |?|Ƃ Cr խs\׾iZܥWhWT"[y ciiرc_׷wl_җ=zԩ啕A|Dejjh=?v@QILYȉ,PcT|wvydY;4UD•bԚGtTa7O8 /&1F#W9 `5ė٧|HɎ-K+FkX5c)w 62ȔQcmp^s;8OOM_yL-f)NeT^k-~jUQZ%Au{?J>?vXp{WfffVVVVVV׍EhX,?gsՔGwEpD͉̰&{#{M5F؈mUΕicC40kj=1/p;n@_ï9o{ = 7~:K=j,5C"U*e{W7Y>bdV= ۃ{pPvb5$=L9WQ{0X,"[6~=p\ˮEsKAҗ|fnn jBxə@pi>'N RTI Prկ~u|xwKJbUGȇ`-K<6,犆Wcύc34sLOw3nid`sy3C2h{Mɑy&]R/6DPpSTVeVW{RXoT4>>ɹb@p:q-YTߣ=q{0`"Zˬ{%`K{"IX鞖`,qE6K l?|ܧ1فqxX};!; /c `x衇~~rr2LRnUݮJR Ey'{&&11[q{'s/_ ]}\:r9 04L=JqH_ˇO 㱍߮)A^ "_Џ i`]$Ej[5]s^WpV S~sN>sgjyGpf(cZTzn=tsp *Tj5v7^Jx !ll5"\`b諉fnx}=fnٻ}lQOĞFr;)"-nϫM0%&zRS=w%|3׀ 0߃H7*-&Nɧ6HUbZѪ6kƫLPUid\`{?,z@j %A6bjЯ/-N=B H+i;EPӯj7П3΀oGyM߄2F_]GYdHOccڕ ՕBs +Dj1QPبPzȏ~tz!(K(ZSHMfr;@Q2 =6%{~YdkQT9P2_Sw|~?8bXZbAOy2"-~~c{Dv5{\",M2M:u9W%Ѷ8]ky(!t mDʹ21?iW^mVP , m=-eFEp5fpPvhCG݃kzL#."i=~r)p3ٛt}2'sbҪ8}JyK@YgS)gY~q;;2JfwsTU @[}~o~U4YSw$eb؟i>›@2TU:̇1\7~Mbrs#<9z/ GBI,wT`'leǜ&Gk5?G=^wb0Q5ZDqI]"!,~%,9sQ"Ж3{P֒Z kf. *ָ=ܹܲjuXGSX;awY=v`A'䆃2~ywQvEbM^BE,U}Ś9?a ޺Ǥ2I'SUUUvv{ow)yo7^V76\zm{"Ʀ9Y N~s*ά#w$NzRM*JiFqvVļ @8w+]6/7 J70h z 2t΃OESN*眐>",T5󸯵ax/y؍(GOtߙ=vϱəzuXD˧ꚇ#9gk(r7XEjZ!+r:-84XAUﱎu LJ0tJf܏?P{ ? 4Z!t= HYDQx9xiH ? 0Cu#YWs5 N_݃CVd]H !w>?p)X=F#x-,O;ZSjRM;2GP$̹?>~"WHem7 \ B2=$jv K,s)jjAʫPsh>TaTr(ysW0 [tn>%=0s%oH% IM{^Yq5#,Ji-J3VqJ2C" !A&7O2(VC;ہḝ9,׌gO Ð:DiHӧnn2 Ĵ+׈ҧ\iCRhS%dM&*~0#07v(iFgÆhM0M cZ­pQN@גk#C],s p.Wso  T|٘p5Yx:$p4F @;՝h7ICb)Mـ5*lIYG>r0H7㮗jϴm߼"3xRT5pLbr+%}ݽ$Wq+M*y䡢ʪi41:9.ZKuec';su'>'py584faCA82Vzu=jLGG( yY{Q@/=uNþILYQ{wc&05HLݤ~ Z;QdTخ9Ws59u{X]Iv]Z6ʤψx1W"2!'EG` Ps-&|"##ќ2Pv͹RjY-kY1QB9YN!eܗO1窭n#-\KX^o\crۻ:d^`U^ 06nKkyt\y؅ pKpDQBMΙ=ʸ>}3s{Dqb7!+ϫp=C!{D}s@M>e9-a sr['%D:UVO-d@؏ 2hj7]fѳk̹AGx]?ssl\(Iѷ?5-{)id=9WQל/'Obڞ$7ͦ%kڇXqIuFڏO`u>-FG SPHBC&˓{]Nt5hW,RͬLl7rG5׼4UK86~/YVsD.{] iUF5ȋZWq*R$&39qxx1""WkPiȸ\p:;i9WI5ٽ. {,`XHZm54P=mVV܅RC15a(=H=s ;|ya]UJ ,dۥekw3 ww#@ Xӈ6D_sUQaZöؕG?+2}L7ثZduإSժ&QKe,UsV:YdM-ύS`s=/71 Y\TJL@n~t0ƙ#i[tJZI2帑䑏q=g1pΊ|&=c9 BDZgusPM4mRJ%}k3C7*j@JIx C|Y=1!g%Qv\Lm6#.SY5ݣkfO Ί,1qX6gYqH;R ^8BjW%IK2E;@ey9U%ʬCqw#-F6hXdiYFrT{. 9Wv؎V.$j)#K-.Veμ%wWWqa !iqV<8"!EFR[ X.kj=,9W  1s\S4ү5ʹ}quk\1%p(#nŦ1Iթ_PͺG~9W3ϙ)0wYh͠u ran# y硧a˹"*GuC\h(3Xϭu@# Gxɀ&?0&lLNrH6ђ[&3(('1{IX&1b!EݮR[jLB"vs u\y~z9iڎ@R(\DqnDe_}=$UTЖva:^b_~)kE߳ۙ1m*e#CqX{awRXڼʫU[0;h;9W5C~FԐknbEvWŇ{Du۵Iۡ{q#nZo5X:<3V*SYF^d{0 )kQf<]dA$8FǴ{kpMX{U]= aNIrOwguguWtjw> 2BY\@,!: Vǀ8c̉>a|9v"1.`WKk+ٝ#{V_LOUVfVv<{l .uոG)γرJW:e8aq ",ÑnsrDk`Mlb{n,櫣^")zq=8LCvVmRcAf`|+pEE!((4Lj=`u3#=U_,mgV9#(cNԧ1+=V:y#J[u-X093Р HCuݒ>u-Zf!+BRrM6=4gOˤF r IDAT"1Fw./D,FWFM2zWbD}sElsY{j&}XH";qj~ϊD='4r)<,XMI4a;M0pNbq7!N_[eE,V*TQ!Ptl{g'^s9M`Ntyo\# lgjLKu@jn=ԚQmޣx4}\փknoH-{P#8N*%un;iոǶ=^6*ESǮ6hf%lȮ{Llbf~7 ^'MHi-=T*etf 5cGDqQOWHJ3bv7VwgDq%w)Vlddon 4(Dkt }3/vڂd}/Br*˲cd+a{x3>+C0Ӝ84dN{0wp ] Qe.JEPթʩ6DE|Kjq8WW˫€ qm9B3?e~tуO`]߀=`Tki3f9hbG2 @m9M c\TW\l1{Rk.8&d( GA1̔G4]5 ȸGF}0)i2-]v@AnTxԕlk%mG[U~ܣMlb{1X߹WJWp2gPQq+M{0C\W38mo=\`-ԴiaїSX;#H+)Z@p)+S#xjT1VEo>sك`@zJ&CO&&L:oi{jΩUƇpڼ] lmWW`qsYհ樍 @kWmlk哿ۇ~[:j= B8V|&X18q\Gs9]VK|\4!xg@QdES+~nQ6\y"ր ڼZ6k7i#{hHCᇡN').$kWqP\yJzDCFzDj]0bu?<]wnIEz#qgDZl8(}&9~dHi1hN&.qŵdqO;QbψQƶYoDFpq3,ZnDc?ڑuԻ宊QP˷vI8TtYК'8W< ER0=`Mlb{n,Ў|R/@ 3ݍګ7}.^'0dE\k)J5ԂLv="+PŎt9WԘcxCODN0܃f3c,f Y9H.Gn0u4W@"黻ܵ#o!]kŌA-Rkа*jGz{NΝ<~Lz\X}R F@yld.a5Ժ1Ɠ >z-@.F!:꿄_Q)TΕQ%{LlbscIK/;Ow0=qiqctd2~%90Y_\|49ix)p,@9eA}v>Txd7tp"-I (b֐Gڵq)hj O.ߤ&$JECn6v[wt89^F~aI v 4J{ ]5cX8!ةTNF'qMlb{[܍ޢ[E(azȑm/ \ߎw‡}RXaz}szk"{Q2i˺G6j҄ЂLеcx`6c`n^;ߟ>;sxO^[C #81$Gl 7àa ch=aV/{gx1/bQ9x!++iz*늢i+s}=Lfu5r]ʽr,:`ޝ[c@wιHͻ8e-QP0 {cNDV.{$1>cOVO: Ncឋ)A6&ރasl.v2q&)y( 9?wʥ}}Tkp5&6(]ܿ]ofUEMT5܈*#J{d̺n:biJninc`(hr<7{D^W\k.I LIy~*LwMTE#!]9-#P%*ȸ!y4yq}ojZ]ot `pp~mALY@9:QffX_EՆ9abs``|CBgO4rb &DĿޤ49jg1->ڤP%Ӄ' =g{#CI;r j(U~OYprv0f`)Z_[MMMAEq:\ џ{9c xPX:EN5 la|DmAhqN8W&w<ɺG?vu8.5mt܈=| 7+^e=ܣayL}5UEui+s!qzvOOK%o[yCD"G==2n5͹ AG_@T[. ewʹ2ntHGtuHʦ_J굦lb{;̱hi"Eknq455!L5ڐ)-00271ѴV*n$WH][B p;mNUk~ȰbGz   ,EHS*{2~WU`xiQ3’zUTR.333d5"eÜ ^sF~pO/m٭+Bܣ&Mlb{,!|NO+gjϴxjݪxk#]aCcTXAô$WƸ왍&őFy/{ *fQ^ۥ :߀r“L#oӶe q]/6:SnfFi}i+&܁dg$f0w/Af#L ]W6b^ZcwgHtBZM1 0sj@4&r4=Ŕ7e5 \5U]iT\ ;nףnWxN0MbӘ8K]tH ¹}LXаhJWX Mz^"FS>P, 0QEPJڤ-[]|=LR=IoH*KtE+]@'E%{Llb{[|qvff^53|~vgnצ =mj{4+Or\g8L0.-qU{y~$Uk;m-nJuE[dk6NհGPw8ÌJ+#F`50t~I"`Q47C=l/K)>(bf8qʦx@m2PCٰ ~a<;q,.o,cV56ƹ2k-v1{ ,vl_m д@yy5zѮaJ{ ȋd@tlnx@=CI #iD5y,"m{"i[,JL$WtU=UX""gt4 Q$MN{QgL+C5g7*:6@+OmstTpy #4E6GW6lq1 2zϹٙNI5^DXx />=Po%zi-EKqPk'ї62cL' ˼Zs;.>?GfG61-{˸L {el5\4j {@&6Ξ…Oŕ^o|qűGvTAA^te̎2q5*EZk)VqUTOCCu=פ1L8a.[v5ixe '3cm{;<`0\Y - d6HC S=8Gߩüf8&O0ɤ"٢UL~F9q Iry@ hŹ[iv ]Qi{D{fܬ2QvM윀Uc[embV-_= -b~ ܊0f$1MnS.|uy.k6[xk=δ7-}8W&GHXɦz1M(6@@vRx B˸mzxbNvSh6K8pVQJ N:/~ lS]Y6+MS{ -=,2w /oQui͍}CVuc#Tw9ު؃UE#ME <{FlH! w3=!"pʑ{)S&=v(Rk>[5Oq'k IDATf)1p1犇)[A+8t =zF5+N۰ˤ,,ebP TaNeB x?(sA6g%``=bת_ܟ==YNDG#]+PʳtvU+ڬ&1rLrsm/!q 7r-{SJ}g]p_Mlbςzd2__ o8tPZ J255xk_f#'O>:ǮtqF*Lp‹{E;~çqNܙSORk.8Wƚ ȋñBwa5ݜ~Oʿ$ 4]4[ŴGG:c5V{c%^ꒀsE8)nzN.Ī a3/Y9ga"d[8=A L,aDE,,>/}u]p"V5(WWՁ?kU!`ʝ2r-X;\{m=e{{{7RJZzl6???j_JRTr~~ܹ˿|!1;kw8N>fV뮻:u'> 7 /˙L19d2|T* v[[ov}P286je=hfdI6C`q46$h9=qz׆cpգP2cl:E7 >`Y 5^2cK9G߁3 >84|uow݇ZYYJl6f{^Y\\ܿÇ{ O]:x\X, yW^yѣG~ ^'ֿ8s.N_^^~_C}oy[~|ӟ~A7*P,{ԓT` c\pphYdsƴ>jCo`;Xs’#7C_i͹1QOśah>h xr2+WXz-6&fȞTw3^Dٲ,lvmf.[LSmP񜆝N͸Y$uH"Qolގ ow *j|'Z4tXZ~'c!$x>8W%tZZrY.o=jiޏfP8uR7 |[b@%_~**3 vm˗lGe&M 1sϪS7xcRf[[[FC>z^vZ馛={ٳDzB0zn;==]/X'ҵ=W_}TR) BA&DdcqqqW_}_!? .h[T:NVۻw޽{o駟>~z/~{@STN97pNA^>x7/f2q7X&.wRO2r!iJ&d> (d(ݨq4nXe:ڂ§~ȐΕ*Ռ՝uc8 g=s%rɨҵ)#CiZsӟX~ŖDG m|jӶVJf #SāvsIV⥋b4u^)N)3{jpIe773 (>&=d:>ck((‰HO9B|+,l^,,,lll\tٳzt>~9 |oZ6??|C[v-˲m{zzzaaaeeGҗB{b/-{;߹gϞ]v A),+e\.dӻw>pcǾo? |t:v{mmk*ZmyyPJ)m_s5wqǩS>~ lY^VnJ{\>J=Ԉc›LQ@`*y٧SQ;mWoni1ͧ`#8rcN 6vޭD7 Jd, ʉ_%V}10N s10Z}!pRɌКK'jhcތC'{ 9XF:犡G{Y81nC0@hj# Z=t:IƹFQuڈ-fq"r{e>DeRn񖡉b=֫sj;e^D.'ѵ@ӜN ƹ*ѳ+˻ȁp۷`RDd܌"Qx&kVj5qcm;ɬ:u p2뭯o>tlTLF8gsRdY Q.rRYZZ:zO>|!gab?׿Rjr\**JT*ˎ ˲L" t&qgvvvaa+r@]v}{D$Bl۞/}KOBOɋ7_<8+J'pYaI3Mo"YS'I2.XFxsG2/^5Vz 4g6a'~ȋ-Ćm&ڼ-Agl)*݌'Uy2D:IA eԵqTqqX2qj]#B܌ҽaHZPs=ԸGr=:*J=-374wsLXrȇXjh((\`dMϕ-Rl۶,K:a~eeeii)ڵ'|'fff7d2u_׼o^^^mX,r9/ql6K B"sss{ַZ .m;#Y׽u:NZ1۶looOMM-,,0"C>o^șz|itXz/iq8JVӴadn(f`2M<`cܣ%la<, Gov\e1 *MޔP+1Osņ=R6ܗqC;IUT5ylfeC#]C1Gd4e? BHH<{BP#ը5|t?FI\-虣OX| z Z`p1@0s+\]CzcҬKMv rb8 ޽{jj*'{>Om;s_ .\.433XՊŢԔ@Ptn+o', rKPcyAqxꩧxAL%jwqGP|R8S*'b5 FXpkO"aB$"@H1"v!ҵJByYZZbd2\bY\R'?}a2LEF=̞PU5߶`Op$t@2U1n$;3n>Mwlޣksu@#lWM Mp<+g?qҌǥG2mnG$Z]vyh2n*93{W$s%!ñꁰH xzcv'r;;NJ.߱^{sk+ ؕa2M[ꠓAF6<%'d*  b>2VF,ai>EԚG71RJNN= ZaI/x,3;p\}_ f{K?cH);{qcO~w!z><77d|?s?7 :Nr(+j%#h4t )kfffii+|_톶vg~fiiRd2zO8qb\RI3ĊbD|C,!ć"!e媉U9vhY&8J%1%GQ0aRiyy;`}_~c!5JFlS њy?:AN$n ځC. J{h+i؃2؄cX)>%`ء#(OE qo?Ϛsc`E3݋&PF}-^2ir">i#YLMs8pzDFŴĆ$Ӫ:<3`S#`Ѱdq5Oq2oErz\?l CiwF=e>\(n>@Ji&ޛHlUT\&α lS K *™, h~/YBN#Nsݻwnn.rn{v[>bF';񎗿ZMԊW`ZVzԆ"$ Dg=$ >J!bX8+ɈJZrsԩrbhY-2LX8[ՂSH /*Q}9cj kZ oBP jN<>@CDj軴ݔ,p_^Om YkQ4h (({ jE!pS G DžVznN {d#jh}اL{g3y6ɤHSDqh|Ǥ U0bVk{d JML5NSm)bԦ1p!.1d4l3s-Z k/ `ye[Q8|ھxC/޸Nį׃_r4g P%mw?ͅzCO)`\IWdY Mn21LƹRR X/ȓ[ܝ96l˵bG^6&x QKYdܣO*U/'CXҾIkc&O};C;jjD,MxVKF'zy$jo>q26isǙL}9/B("!L` Ek.0]%9o߾b(`PTDFַ5y??Oȋcǎe: PJJ $x}ߗ!Ks.,v)YuhCӔ .lCEW=jÇfabžHͱBvr{Eay/oVZc+X K{~J#>ŧ^57O s 8lZ1.P,eAd1{4(ܕ5|` MH[*a;LՆݦm>SqHr4lN=ճњWI}̸e=(m'oλmsqQkA&m8FՌ'rFk ^_<`^ϵ]TkK+yibTR]qE,[/}o{ĵw|)=*sRNuQs\I0)+ ƎO9p\1̎9233dnlZ$!K.:һ&ŸҫSH 4D;fʏJT.޶8E AID߹o{ޟ>S漂C:{TiG~,5ԋ\By+հ̙GXI~ Z iX߃6v-E᤾4S8IQU=U_~=9 8}oy qLȗ9WFѶ]B6**x):Ueխ-X=\l,W– 305J&B,.{,bGW_>=PL.QG]-UR~=b^z|)ʹ\Zeہsm>~qYC%C}lI?UkCʹ0PxטcA>ky>-1pvxz\/ǘ+;y{HZLŇ=ڵKxr". T~={j \!vULna R+*a'(J rR `0t]B@ɓ'?O>aZYYB [^$ID*dLm_`4EŤ~$Jb5_ Zz>;;+W/!pHP<ϗ}G9X|mL6mұ0 ã^ūh[n{WKC.V؊\ M1ќ3!JC;i5-`A!.F(6 #=N̓W@ad9W~'i}5R?^ȓ]lЧ} {S6zs{2Y:[#q!~~G=uKs+X=  ڦmtrb5iwJ/&a%?Cc} IDATqy=!|i VIKjT>!r,ϺD/n z.*?o›(_/f1&ؗ,tYt犁^[0\*KG!&"`5qbH Ke;hrŇ=ժ2ZVRtرfn]^’+)4xFH nHfdE#j TŇ1ySERvT*8pફzի^u|/OLou{DnajLCdjV35: A@o`o-[kX 40D"Hr[[+4=ĉKX[fKUeW!-cW5|6^yR#GtȧJ4xK~6َ/2paV>մPc(t5Z&q/,P$4ScR]9. Yw" v-2] Mn{H{ŭpd8B@Ŗ6,ZC:u#ӢѲh})8VW,//oll| _8{p%m7|ݻw/..:#DbQ u:i' Z~r! bhq{SQ| #C"&2j6AjۍFcaaApIDE&^(C \.u]Bj%<ϟ7ߙPc;.Q^p`pd2 Ys (xK:@ڷkإId~xFj<[ʖv&o͵qlj{= H"0z[ոzHڨzMjSIj6ik =LYutz uFAS/Ojr I7 |Q#=uO0p$>|p}0x? +P*, (Xzðc=5UT0ŐF{\~pdqMoRz֖%EQIVuC="PD6n#ůѠʶꅩDq$Q%"SQD>R477k׮/ˏ>>8Ir|ڇ?ᥥEịPXu@UV$a͐Z ߐpWb cʵ܂U`~WUug0#V^p0 `S >$~ Fш=08*1"VPoIC,C KO<*;B{$sd?[o]\\! iuZ& M>D}@uO=u]WTv%HHxH!~XAO=Cycuݐ) ?$zDo4333x+n{ꯞwݯ}kwU.!P|*Jo%PP0s%Y]uu?@d_ us"\{z="?VJEOj]v|c{oOIo8608_eY֣lBQl%7XT1G4UEX͘9 ջA7S=$nˈ{R9)R@zyBq+Zmhx=F14No`xp֮#'3sC"o5r4(إ]` ňl,Ǜb Qu \nϞ=o~[z~OIoX[и٬07e5pz^+kǺmQ @l%7 X8 '!qlPLXJiu,EqBy\p#+gVe ,\ߌ#,ŧ& H,+h \kh+r7 8#h:&3[8_1N[R znu=)twfkXcsGt/kމ!6is\U\, 6ފI"`hRm=T;UT)7@ @A;2QG}x#m&ՍazߤU7¹=o^;8x`\^4Ν;wAJ9 7oS;q'C\d>FʯF6͠4bV*و\b4N2ְv5P+s q[_*r0K\!+uF kquó|3/ qh<('~=bd?=sᘂy+65i͓q9jvoE,^cZs( bqS˶Kв[c='eOx;qR$6SE4ZL&3??/'Aawsזb>WUIL:rZKVOQM|(@/"!+2w?, =9H1RK&m+;~޽X\\!!%G?%HUNi0ԕ,?(OmYO( ^ՅZ퍍'xq(izanzb)Jp"zE)}_SO1JXB R(DŹ??y^Oų8^~_az]0\@W:m;a`&9pbsТ3p 7㕑ƾ?{o%Y݉Ȍ̌ܗ̪^$uK-uK-5`dAƞc 0xdd@α}l㱟~a40:1Z[R/UյdV?nƭ_DUGXWs\ނ ѥ*aJW D{b#u#]p{!]K9_,e8W=z\>P׹v{lLBk׮T*WUUCP@򆪪H$LbrbZp|5GF݋]bz_p9ec(CsLEC|Qvt#w9YkhyI/UF܇++e,/@10V܄4ϑkɺVvr ptFoi2 Q5*漬]̡ö | F*7/3M Zy6i:ԟ>5";;da1!-ARe>7 PŽ4J3Vյn3Ϭd{nḿ@ٝj44Ђt=bZg9:~v<@餫QޣU)WK4C׮9itNG/VRnۿ)F=6Fn~A`W\qE.F@:b~O*y+$EBAQp8!jն2f2?w/n+8]C6 BE쁱giLdbb"lݺu߾}Gy5{ݼys2rP(lM7_JRa|9uEh։Ffd8\+ԋųg;v kYLb(@ɔq"$)G"lO4&}xd==rc\ A"E pE=Ґuݑ"tka G!=g]iKy\\5H9e&Xw#Z8ߞv]%7NOüSbf'ʩP_j`$,!pv)@Ze#VbP0 XQiSA8?-jޣiN1wk4R9 G9c+S6a Q;)$z"]dq|"Hȕr%(tN셽t&3]"Ck\fBnMvxF30k{x=n+2ɠvOh4z$qK{`^z #9Xe^=P3B^Sqr1aG1i TkH@|8MDR-4] ]ױdaa8׿~}Cxc/>G?m۶tׁڢ{G{z!;i~7 sB6aJenn_FC>#(Tf*h}B!dNP(O 3̎;^)rvȭL{#0fN͹7WJݬ0܁1stviͅ#п%(b=nm>#τP_˼b]WRwbiWإpЛ>ͧ|ˉ$ scZLSJҕ⑪l窽]bB2 E5v;-@`M6 5 b%Ehj3|hC4nyO1Z}E[gClWemW}$B|XXDR&2f|p쁜:ÜZ\ ;A\ZT&߰7xc.Cf u-4 vA[Bd2iYV^ZRK|D"qȑP(Rks -1'0{'ڒ$._9ԡ8n@5%=(BbJ}]lGT*5999>>OO??Omȍ졇ڲe B\FG%QuMwӴ<; f *$V$+؋ʝO|+r<33sQa]8Vt= tvnIǑ$ *LfΝ>???ü9=܎E=)gURߺ9W5֋CQZ@pnsA Q\9{ *tˮ!#<gMz-BglZ9 t+5b kaMѴ6/< p Cەdev[GzV+b6;zhHFuDY5+\o= fFΕ-@&i#z!7's/ h/i#ȃ 5 'du4z=N ==N=IKsagǑ7(iF{H@8ˎT*U(fffv^ я~eYd2xX5LKv^{ ;ye-:●PvSd8SԀ?YJ,tz۶m+L&o}>%EDTsY~kTk>ZQ6F JXn,)G \!TPe]}uNfNգ6%(2̴r*Z-CTԚU^npP%\i5<;opgR{mpQbꝓjJ |tK0}h}̼4jޣT nW v{ [& 'T5zX5v^`OO6oތh4vXR{W~?HLLL=z; jȑ#dS:tyd<###lȃ'T[^zbpɋK g&GVɓL,˅BlNOOk_~OzFϧd*HuX Uſ$1 (!u`K(6lD<@+( lYiz}nnǏnjB!CUT*4n3p D:n7bᆿ,ԩTǟz]vpQ{{ MO۞~ZGXsB~5Hٲ+oUqzX,zg9lh-~X\k5$$Srl96=dMC څbsj==z8cRcAh[ 151Ǖ:pBډn؃Bcd=bĦ%uU>|z> ' O`aꃰpsep5\VZzSp$cuT {7ǽ{Wd2dIIXԖ|t}w)cɓ'e}> )rcvSOt^HÒB*&73mڵ p]6_p<˒Luxɩ%ϲ,DPdrbbb>l 8###H<=woe28PMM7Msn8'OxlZJ'O8q&񉉉|#ႷR,˘{~gSp:[Oأb/iHG0 í:3aT;o\gR{b<'Yk~ {PQ*=Ͱyxi8~B4F-X l܉+ UB?,Of=iGW좲ڵ Z\X ?z: :.T=9f7#"_u v~ ?Nc+<6?VΖ|ZpTlf\Em@p%Vj b +: 'ik'\i];7V@i/5}l޽###[!LA OVr9XNG0FO=&D*"\$.(FrVA%xʉ7f{?{ݾv^,|1hx@"B,IpIu~;رc6@nuDzX^$+To :A˒KK@6UUs^~e]x旃Vmݺ5Q4M@HEs}onz/㣣%J٪dkwM_ 9C!e'9W :!6VTJ|T^Ap^7MP(`+|QY!Z$cDH:޻w=$ɗN}J[:{s,8YR~#u.7l~5y5]q=ƶ!ڒ_W'ZwB*{%%kNr\}7wPIN%Ou ƬI{s'pARnd_I\ ׫\ MLq=:.'%ld)OMDYxv &^80⮳|[CBޣwt|y֝LKVY<~pyDz,Vc[Yz wqk^JLmBt̑v6ݵk / 2t sq4^xZ i#tMV C@r%+Fi1G;AoZN2y҄:L&IJq7iY7߿ooq7ٻnP(lڴ >.!jRo(\Y5%d^I W\70íS'˜ZReM,z'1 ^UUnݚJΞnY9+S,˚뮻~aPMc>_\Zn6WBGQo2{:kUP; nz:ٜNF?iޣvSY8s-W+ۨ/{t>*|WWWaf;\4bg$]ki%+w{]7!ҭب\|r2>ͧ)Z͹C~5$t;?i8 XkyCi=]ܮgv=$<*GO=@i( axc_hqw{8{pΕYCwq6ِUq,X2w幑a]˲% Dblllaa\.pV TO,%+@ wk߾}{nWW*$5w͡ʝJUeʄu=i$&X.cA!f ];G #Hy|4,V WPN# .|m <>%g~(bF<7xe>Β,p@{8+y$'k$_5]Oh=Vؾ3zB)ؖD&tr퀽PJ{#ta)y@KJc^p5X /;d;iPS=UQbR]Fv?n4'TK|yG:Z R]5 q '¼)펖XHKi-oqּkzFp},C-ӎ =68я~tӦMT*ߴl=y6^䨑 Q3O?l6-B\NaR)fD*'f/Z%!S\xCr t ߞC"*E㓌FX" B;v,//|??{޷!cvb|>U-VJw`))hh4!!v%MB#躾2==H$d72G& |1@kO!T$IA6]e6ߌ@ ig;hkPX`TQi(@Jt\ J2nyD 57bGjqV}ϧg/)6{/8Q3Q#$H.ӭAkVN`μǼ2O3 ̬{U@Wm1頻:Zkk{t3I=ivG˜@TIx.r;5=%]d+ FA-580EXpwC{z} d=۷u֭[aDžpH16dBA`G ?o߾ȑ#?ezl6KR\e9`tlje:|8[9.r?8S1᳘^Y-P#Ȯ]W~?} kvrr2LFQֆW*=(aj PA@n[~@;xu'&`Yi/yfd[ay+VcP^n]vHq8nEQ޽{wzcmqP"H$v-.vըJ#?Dj9W[ѧ>"4{|<& ̄pSyvJlDWweQWs.@A{^kx=z,f2=Oc*|l~AtqbݬT}5sX%U'VRSdoaD@>C6P @_yKs#99WD4/ֽwaoWo|%Id2x`_3`*H8.lDfm3DQx]Tj$', #gdbwTg6aP0[yOsh=Ozv-/ncߏɟzk׮|#?Wաu۳gO6E,J3 %7W/8{eKN# rCcfшD"L&aD2ְĺdOe˺jxo= XO;vs]eYo^7r?]]ihc~l)TgED3ܹ鯧=\W9u0 H hmLqM6$<{ j2O_FksFU}f 2jVD>^8W }}9!4/apP@x+UPwn䕺ɩ'![ < :MPq=gZl3ļu..2Y岢(aX7Jb)[AJLGNR:FѣD1/'Ga4*K/mBDpU&‰qkr)E#1&iM[bJt|JBFGG޶e˖|pհQ}mjj*Qr6xYۊq7&--$p%(#X.s+&m1l6Md2`i4Q7SdD Ut X,2z욦~߾}k6KKK__ `g~?_)ӟﲏp)Yܑ6F}vsC+Dqs5zz09#ȫP6Wly gST$!9xAv{lJ)R]uU/KFRo+LQJsuWC \n[NZ/\v^X .졂}pUoW[\uQ",+˹Jq ;G݅ԭwD"puVi$EŤNݪ MEIRMz駑t,ƌgA} 9KN!z5S\I"8B6 C@8ddl~? "&ϛC6~-["kDzԌ\u+\yo(U| W[~t|!;>/ oh$k4B!Nc3AĮaul)s)RxNfRر#'IEQ6o| yO;Hd!zߜ+b]|K}vW{xi0<:O8~m1x-4yfAUsbt{l \ݞ;TkCG3؊b R˱xyf 3ZunemM08_+u\h"{t.ii8i Kn_WAn  ftXDΕpC{ {|L&ޡU.Qې&@J۴'Sm200:~R)4?Ǎ˾a>v @94 gհ!1 xQG泍o~׻b;7ڵk||skTo`nPʹ&-g@{17h7N %h`lFd0/jz/=BE_ 3g`-ATNLL|`a5:@oav,b vg:WB%-fpނhcO/_5[GKT s8JP2ïoEJ RLC܈peAX=[ J HllvhKK"0ѻҲ#`X4Р656)wt•@g`f+lFz0 raH JYl,G2 Eӆ={wiMTU%gzyyٳX,HL Dܹ̍& !]wG%Hp8<77w`>WngJ|a9ybqPvd 4(\Aȁw mp h4W]u? ~}ݿksp8)˖D#bVu;Y^5m:D?;" z-011f4*=p]5M|5MC+$=0Ak W4hxn_^^~ꩧ.rZՒ$aѹ+꯾ot?K@ň=5 M! 9W枦cּWYx6:=>ŪRmjMڥDhu/i/FQ]o K T $aG}((.J\QU*s&C4"|p=n[]o`w!p,GgU<Df K%Sj#KV񖷼կ~u&A xѐf>}zӦM[,:vv@Ǒ;a!`0:FT0wdd$///>}@cQu 8 r{nK|MKL.L=eMΕ:h0MLɲ(8?u [nb8Wā&@xC@3 9f禳_8Ut(-\{n1ëq룣t:Lb1,:UU: C؀p/Xac7 Cp8|wf2/}K]Bn7Ǽ8.c [v|h@5|~egrS<E(6 _Y,K# \5/fy]^Zٳ=ǹj; Y9&* YUs s a%(^PUuCH١ڡ :b`:{t0\CMv;\vax5о ?]~T =zNnG(4maaayya\ţ,T)s7N*26D\FXP0 cqq4͓'OBXn_)RrVѕ\y6>&%A8u9UB_d P?6b;wDr= \d-[` 1* <#nAl8:F绨5Y/<]x7D"n)P(ّexj5TzeOK ZB \ .X^OӤ`0X, oVUU/f4VEkk Fj=ļG].qW@k=emCȃA:=AU'~P, A5sx= l-{%q IDAT穅Ft6A{ =~F|J@ɥ`YYsy!r.MO}jjj h:TJ&sssz},%vOS`qOڅEӮ`;b;*_PjFu]rem߾hT*bH~!,Zp0HGt/.t>x(I5P.N͏/$*t:}M7} _x;aU/Q-lt6 >}V!Ot %C,'M M1NSo6|#تYӴRT,kZ0ޠ40ob}-eNH(BB VTfp IV4AX\l6<,ڵ_;M>o}kP!xۅt,նrY 'p.78WIGƱjH$\bjJxj6/ZbOl6C9Ed*FAIUUͦ-q*fc ĩ{6mD@|hb[nbXhLĦ:=TPchr>;s<*Ujim%|띌 ˏv)t\qrvҝS'ˡU%'t+Q zYry=CXhm`<o30ӵzڵ>UCn8[x(0g+kk}{9a=x@=~tk=:pGbK2 //80:yBX,,6y4UU]\\ܺu$Id2Ja6CQ قX"LURt̙X,zbC=9éc;Ll17[!A gEAD0> ϋ=ym666(J(T\\I2qaHMD8m6VRDZ6sBO+ge 5BvĜ(Æa(o]&0 5%pIvAd[;]7EqU(!_yF4~o+ժ?~Іd:'!-6}_zV|v5@YWL;%JP֣i]CP ؇qpx9Wp?TlC{ g`f3lRvvTCiQCEM:W*H/|YkޝsM;*5ʫ.y56K {u]BiTzMFMAe!A!oRdk_0Ĕ %@kKPUH$P؉O4|aB[0[,0V=z4q0B b*qW!86\h3HXlbb oxiײm}kGGG3 irx`wUt43Rxea\\sAJ4$L7C ,+ a)] Rl+j0u`Y$ 6֒['CBچaje7@&[ij b\.o߯R5fΕ`Vd8Wv0KcXLŬJW F‹7?Nv{ :aUZV% o޷mfk{8j1 )M)HmFjI =n>WM[;yP)<.-kww؁EK;Q W=rq! >d]^B "XN $H~+Wf\BqbsT*l6Ʀٳg%I󊢐>^?T;hF+2*w} //^N=__5IӨeFóOS[NŶ chCa~lii)J޽{dd/2#+"KY,e1 ;XK 0, !!a2κ"b nLŦ,VW0rpi Qdrdd$˕JӧOBT*~>-N!o^S s rUv ] شiS8޷o`kwwQ&~8j6W+7 Ir:,dϞ=wN&C_x''rbcRn0{3%:rzt[pYwh>6"")62:@à\t[`+y9u;Dga6/M5EXlORa׹Zek=s+! yaYwXZNIh l8x;s̱cL[mGpGdaa؛L@Bn9 ?v_ et [x4m^(e׶2]mΘ(F,Rt:|L&F&@@0,gʅYgO1,5p8|K_[Q]ݛf8V,u& Lr'?8ϊfaT*p8|# MzsOp;iALo DYtdyKbYFE0azpM6͕~kگrRܹsϞ=ccce_9@[$|yh{К!ijF\pji{Eg#\w+)BLNA9$LM(e T'nyA@dKçiނ\t¤NyஜSYEEډɋP贿5d6^\\͝;w|>EQfgg2{XXŢiX C[Di9iB4B0%ǽ=0#O:"R~`zExST*ZZZz駷n݊84N "+WfbݗN$%Gm۶XI=UW]m۶l6( *LCWrgB8gzSSSX kLQ27n iKᡣm hZx`'5YJJG(tC$l[d_M^f3QScugb0@ʫP}ƚ\Smhdtʽ5߾}C=t= :W_1ĎW!.leڡЪ0揤-'u7|Oܺu+6iD"XnüVU{ru ױoDh0?iW#bVϊdUXL&vJrM7MOOE"h4ZTx&`Q/SAAl|\ϼp#ҦUּ?g v+؃/ʝs5tނ=@x["W,dLg-ՠ.!k 5¢xc53|Wë[b/9 fͳ En`׽u|u5l6;55SЗEXH3O#hyrY,xzx<BW^^x"3n8 y_.3nT")N<|:qXb  ]h sR//z||ozӛ <M$#J0yBA!\e3t:i/Jk0z^uDQD0 pG@b9RJ%xNaP(gϞm۶T\T*U(1zl=x{pn1; }l9ѱ벲]ޙ;u.ނ!Ѓ:A=5(ƫy+{҆?fKPBzjE2/˃GƂPvBs&4b{m۶r`0(Ilv˖-?'275F [QK ˙+6td*O2 w155d.lL AX].k/To$ !n8n4DdW*EYbQiJX\nђ|fsyyiWZ1P*L&^x(KnMZgav VUG=\&u)-r}^}nL+T7@=/.Ej*eGsªQZ.&ozatPA!E[F4>?~Йz=?>>{NJF\.ke9B:u }p8̝!,WN*w0Fk׮ݻwchLqСh_>.UK,hm1i$Ixe&EhD؞"8B ,f/L:D"~g'B Q2?cFI I9'0 XlllLӴ']]n[w؁0 Q6 Н\5]kZ,.hP3AF;NG0L/(;RFKHrytp<,΂r<]R477մ]S. Ø Fw1n 5lݺ5㍨K!JRfwv*IA}-qxܽה=zM HZ.1=ccNļ \X09Wz;y<4΅jڛCh 7={tsҷBfjcj?=Xw{w}B5>itzzzñXyTay2 pə#gxd2J%%qH-wi>'#F6aYV1R[(No1^ x 3JԀ HzSka---8qbyy9ɐ 8<<]2Ӯţt;ph$J$I"1gCwbk7Xorw ZpD"H$Ȓ# CMVz]UFQVVӉ<¶!rg5")5`F_ pji|>͞;w jkTl+@CUӧOsxL˲Sz=߿?& Z f)ɵ1@F='ID/٩p-t };ƛ;ç˟pКWxr}=֋sh""΅;*؟ya|f~gCkxQZ޾};F=1zYÛ7owiɮ{c1yR)b1=k1b822-;<,>plt.dV1g`J;bEƤ(˚`%Pt Ĵ9TibMXI.IR FHhVO'GB{񹹹~zbb"LbJ X6Y5-hSEO勁.dYz>aqݻ|"eٍܲrՒsr`Uϔ%%hLhH VrۢEQt lق pr9Add|>MӰx(R1( 3RvrEd2Nz饗|<''X-)1 Hdv?ر#N?kRz;L[Vkas:;fKm46)rq95MM],''T* ITUj+++t^R%l6IN:l,Cȍh3Z".c 9Oh ? ft@lZ9z\!UU/fO|mݶm6$}iGݻw_lܿ^{ 렟BpC)*ȶQï{p#4WWOS^.؃׹>+wd6pT ؇9ic]_,:2)9g:/a{|c+ xtkucǎǏ~뮻nbbd_93N'vsPvF(Jîa`rkh}3t<`\.9|Nsc L&C5*ryii jP&@5::zرjd.HNPrHG3]vl+ݔlL&S>ϽݫtnattnᬊK*5tj.-[ĦM2 jɇeQ èjܹsf3NNNܹ4z4;;ըiUUV]l"IR]v E UX<}t,n0TQSj`B] h ^l<7 cttχր+-gΜyGg =ܰ~d>G03>b'Vzs: Pnu@U o}G@ hGG<ِzEBYIT G1㢄,5Іyꆞe[MzyIV:|o@"T-_q8Ngx_N)h*`YFm۶rqٕɁHT |eY|>J)78˕"fyyb9bX"'333g""!ޖevk\.F56@GSlCRF}Bs6J5<Ow =j4|>eo7|R J}\M>+SJw @*9w_FQKs`3|Iќ&N{bcco{^wuבٲFA*ng_qw06q9[^x={ U% -t(6Ux^ q&-/Mχ(:ŅqYD.k66m4@;w?dY&" 83ǸO2bi]*c>XZ.ɲDp2l< 7<rɦ E]mPs8AtХ[k### CZJR,rPؽ{w8^XXx80$:1ruN6BdcfddDm۶sDVYu\.Nx"NᴴCv+2$J)>$6~pF+p^FFFhȘt+0QEC9Tud[t8Wfpd=ޣG[~étkK`!GϱGNQ]rNyl SiH!;Q, T4j||||?O_<|H'd2xMG\=\tppbӟt˖-TX ʲLD Hd.x+D59nJ] '=,ጸc(˓\]Դd~ۆ=<8gaWW<9W2e$_ͧ C;;(ۯ;F`W 0x+nЯ!}?R,yǰ埘r6sUR.0P VOB7 lG#+A{111QV_z%$~2a?Or\ >3TXM$&OmD_YYHj6W_}u:. +++x|IU0/w̸)>|m\R{aaAT*<MӐ*L Q̘2 aٳ'O, H5k9 g@#p|v-CSID _;5\Jb⢦W7x%g[ r)Oɥ;wy:I(`岪6mJ$jԩSZ 9{6&baN24N' c$u~<Y @z$a\$R߲x. ib܁@N >zoK!U,+|ĉ OwJbSSSHxE:_p*.r-w{_lo}*M9vN-Sޣlh!=1[ wXzGo+6]k`ic@Cvu=wCsG"r^%G^Z=/0Uݹطo1ZO>(7w";ɲ<11q7~X;i<_D"P(ı:^x`L!!Jf]St%sJٳsN]Ϟ=nD2I"& t۵D%V ZN$!92@(`R.q4E5Ju\d.KfkqqرcX46\+pbĶ NYp}R)"{=3I$Ntǯ걱1ΉZ]2kZX,JiR;v,--zYsTKb R:EpvA KF2 v@˴5d2mZZVæH$L~,?gΜٲeK^'njw͹BA9Q(\Rp^ݻwrr'gp/RT*! BccZ]8v1Ŭ:B/wJfKpo֓}q@K@͹Tl/8'\xd>^gQb@_,C $Ii5y'5У>׿3Bجn"PBCr̙3ǏϞ=M(aAv84CĪ5!Lĺ( kPBjU4b1ysF ,`}iiȑ#tZfitMAc}?W$?Bd#]C˰à]]u[;|8qbhh4pRs:BJ)h[\^`qu:o=-A1FAG.s?brx@MI⭂!&"TƱxdOF^R=jZmllgO~CzSݢ؅-Qva=*& a{lϞ=YPz\δz<~o2ءmI۾-\թɷ3zZw7:X@D$%?ȋݟ9W=aDwo@wnFJ^B;Z}?*> ۖ{|goGtdb5aLNNb1+kȡvgϞլvկ~xEY=diRPBaeeenn~PfT4dޣ(b0Dڽ;-W@7"i ^@r@@jȹ\i#a1*X`0HϗL&NYZs .13DFTOO駟ԭf(N UQ26?-܁,&T*E |.9 $m^ڗVy7WΗ3"K Q5V@Ȏ9*tĽ0 >΅9\a`z&|ڵV511s+o{G=n jt^v8V3ӾqZ}7К?܃t{uh';{103´z5yT*w{>ұ/^xK@ C zvDv:H$b.]t:Z@ rD" [h$m;\:9lmȁ;h\l gj5p|>x≟xgin@&:ommtľ}&&&Px/U*$IT|nw"hۚﴻnﻓ*I|z" B Ƃu?P`WZ%VsidvBЀr0{'DqyITիWbCxhJ&xƸ (Ν;g$OXu~#JfhY,?\.T‹h4 hBH-=N6Q " 6V 37"r`#<ݪh9ܑH˙L&r%YV^+.\Q#˔>|}oJ\F?d6LҬ6GRjB n;2D"W摻oömTVh%f4Q@ "fn;mll=ztyyy||5֔T ?P=p.'3ihtSSS|>ݔsǣ0?F=^K{%n41}}ZS\#D7GY宴#|`>xu{OG;Цm?둥&YV իW9L&9  L$pWՕ]ؖrOeL?L@ [1bEuE"D]e;`?x~gБh4ŋ?:tEZɪC‘)Pl4mDTϜ9S*$"q}szccc@K\DF;677BġJdRIHЁ?j>JP>̵/\[}{|[r>wv{\D"N*޾ x;{lG2 jص'@tpGWN;iCP"@l=:NV+JoO?}axy%t:o6gΜA< !Jm\.0rBZ`9uv\+\J'NCCID nL:oiW*hV\P/(B<NtgD"J GVH!4`M~wr4g$"W.d•#yMӄz r]בSVP6: X,1絒_k%ʒg8/',Px ێ#y8yFKB044677QXY!q-P],jV;yMMH$355511L'd0~d UV4Ƶk@QES~#vKݝS:# :Wh{\C=֤? 8fGc;*=<F0M,RW_)%(!Z<40\.^{\. iIɿ _@%WeC9rEha2B%$rTd{ b\e;d l8/BLN 7G\VkHSƑ""߯[榦iWMaP99rwhn D{8qM=TAK|`mGV{ 6vVB";S. sԭb?nөV.\g?V(W1 PVE,-ţii6ho WfK[֗e_*TV֐a5-=BfrqnsIDf c, `㚶G9L$zN 9!8l=Ouhs|]7u= `CjxxoIb1ȋu8GWX"RV\./A3pHR* l6 @P(H$x ܁u8MRrG$y`0X.DۍD"x#2_Z-^\z>11Ƅo{|m^ː"lmm }`@" R!/*9ܟ/!m|r4Mny0fR@kqL VW޸wk׮"d2T 1"Z__( \nkk ڀB!,;„ c.Zdy晥% '?xj7{LtQwDUCTcc`kpv7%~d6nZj58p pYMt]tm@) ,#f߼i5(?}cf3E49G z!:|T ;… /_&"f&'\*閶I?l_&ꫪJCuRym(ʢ@: Eh&Kk sa 5Mt:_s6}afoZ z0c[}rr2@PX`i.-- d2*x#q@'W7Z K9?m",T*h ?G*x  RED "v}ԩ_|QmeiX|>D`r\6][[+JC(,=zh*DDVH$&''2~xk`SCoK]V, zl6{Q4K|[AuҚQ\ٳ.\z35MkCQ*2ZR*G?>x d,dzFDhyL&\Zu͸ǝqSBs"jS3pVv>>=܃n.j ;w{}P}Y08\.cǎɚZMM.%H$Mf+++sssT0@ Ƿ֫FePs}_T*J)YWb;(yuK[zښO~v0DcfYKF-4H 1 LJo@^]+! h8΂^1 Yȹv]Dt6ߙ0f9 iWO?42kw 6N퇍xNS( uHd ]vl&I H$:3a˳kK0l6-ZoS{M@ܰt5ANm 1iݮ3w`.[\\r/_{u5xr)JRVuDB]Y]АTp9@B%bl6O8/o&Ѩ׹s={<.>p"-nŝ60qwG*4jjG}4JB!D14X諫o&|QNh4ZPFD /z!nicP8=pv;gC 'Obh48`ZٰlV0Wv;a_ c|KɄ+9bbrdrHNw/zC42qa$3:*D Ь `kkkF<Љ2L" =%$XM_Am?ȉHz|$Q䨅mvrr74d$Aj̯5Mf7DT*K^z%vDJwFkHPo59~dٳgQ khhHRG4L4 2jVsԞ={ۿ}vݛ}fRϺ=v[G:Jw{wn&&>яjVV O>yQvxKb/BVmP4 ĆߗA%:qE˕H$4 gO~ZtF 9"|Bayy_u}ff&ᴜ%D` 8J='@D՘Ϧfd=wl$GztM(@jZЅM.&f^ pommv,/!DDb1`\:u!Jp̙ތ1 A25Mr]Ӵ~<66\ǣ(WvFRzRB̕ӧOKF-g?p `%S/g܃8477wȑ1@rj <,.:l[N4 6Sv=LkP=6񞱑os=裀\{I$4,S&xܹyddqfUR6(Q;2cثzgff:N 0- -D,qa"4Y,WVV^}סi2u{H>l^1$4)G^D@6}Md˃j"~+:8[8VvR 9 >-Wzg?3HfozD$ٶOms%(2m $vGiV*]~?2@< –* n@08_F_ @dFDQU$wj۸PYYYy7>3tZ_ê'Ce{d2LoX\ZZzwxɝB-/]tԩO~b rCb5@>[V^o4J)x(hPN Pvw.V1۲=to|޷ۙ˿1B L5F Wj`Ԭ2Tj- -v 9fv 䐪̭V vSm7 m7q10cLHԔJ|qxsJR* (vW,|4E4Mdj7!.///..nll`:0MЬXM&?#(BX.D&|2:%;4u:_~/djZmV%"\SvN-U֜xΞ;9v=kS}BuJ$``g|>K.?0`M`b|4c*ߗJb@jVM1#(m Q:FJjZ,֦ 녭YJdE $ࠇ0|m32{I3 PE<6v*yp!Hih:N+7ZĆq \4QhmiD.| Vmkٹe|MGv&+!d5&)B+Uf#㛅KwE%70FFF/]T,Q79rD*R)ͮ^zuaaARyѓO>yGyv@ Q;ϷT*!"4ko~9Z@|#Ç,lX,4vG~K_ Z[=  5i@f9P8~ۍ@1E]N#LOO'D"T0Z655|77Jҙ3g^uy66jii rUxǁV\Dt<O"X\\4j^o٬T*(d}-gU_'<@F p"cbb"G"0tTjhh(MLLNI+++z&>R\.٘V7dv Ym)9P$6.!)|/Ƴ&Sy}$ٰ)#*1 q**߃[1]hqFZw{ (pFhbȑ#CCCtZVTjZ\q:;+hn޽{|&9qoq6,kFF+ƬDnw$hAl6Q:M2@, !^,B#Y0HF*;1C-f z429^LrmJ rn7x~Gsm$ 4!idK͊ݱoqst})x5|FUPo~Yt(ÝK_c[ o2,p/DHqW3|G3 hPYf ~:Zv4rE"aԽ8yАEA4\E)oqq/y:WCנ*3|k7&$2E4M fG:4<x`*J$yz{Z-Jw1@+FQ"Ga 4_~X,dNX pp wIwݙnrqrؽK.rԽl&hV[eurd.5 '>񉙙tBp/*z^ M!w@ n:4:::66vYP{5VUf2RpׯWUS4 sxTs%NG29j:13E+Iwp3:)8x<Ǜa3"$c 6d$bk,|{AڂlN*?:-9|_!:ո{v с}n*@6C$HXZZ'>i|vwygxx8L"3v3FiD`d"HkkkNZ`Z9~?$(t" Wv*3`a :TWIxxRV B )!Kn#fI|'暦 T PָȽ]Bm/ &'V'rrM34տ)$mrb#0轍GH=z,'D Xa\ AqHRH$ pTڪT* G4,8pȑ#d2~Qƪjy}U&8~JEQLj\_=++~[wP~:X%M)ٮ;}=zخ$Ӥ]],ЭұdFQV_SOMOO Xa&*.,,>}ziiiff&!ɴrey\f `gffx*x<ƃp9^/FJ ˱8#wZor ek'pd̄:?lr[F(,CGakIC\~zU`rr GtM:q>[GoxE#Ԛ#F6^p4[- # jnI2xB]rWo#cczem2 izΝ)$QP(.kxx8a(1JR>/ zˮ$$=#c1Pr$, g+(f ?я>D"t:y⚦۹\N2z  '+t:{vZaquc`Dڻ~(Dm%ۮT*O>$Z% +z뭭CEQsDV*ؼPx|bbɓ#2x|hu}llP(,--"RT*8 a@ % .RB@ "cf,^>Y , nRqze wr1C14`VbfM|&ʣ"9`w:wR>H8.Fs[ ,{a .r$;;Y|]e:Z L$1xaqjZ#Ď dRh4$'r8nN>#G"ҟSkE胵Lxf桁)ߩSI ?]rP(`A{0LE}vgh43I8=cGeׯ?ǏGTk|iiŋׯ_bȷ6S;`EQmP$I366vԩP(F%χ>br}]||>zD#"Hр/Jd ;5naFIRhTs.קzX!f.kg`x]V+JV3M[ȃ,C,0r@s>0#߀>OD(@4_PS[hƲ`L7dcԏrبn)ڶ;v!@'C6x1z>22 69S(bM@$ƞzD"DPU"c IۂuNR*Z]]]ZZ {IAdqq\.F%5X+˛ n 1iqXA'Ȼgf d=Zs6Vu?SNRdiQ^pܹs{ BqIv?-1;Y.گ]zɓH)&.:22gϞ .GP^cJ)kU>V n0wPŵLn8y& j r=7KosOaNRT*4sY}p]x[6z3 =p9@!+>ަ˻ٰUyxFVi,&x85Զ wh? W Ք3R4+r"r QRATrOeݿ?d E@ 8R D⩧:zGؤZ2C8k>ai4vT*uYFd Y׻V.1w:|>V\, WDz_{KA;P>V@k>i]޶tAFZw{? *fz'8011ǑiͽKòͭYr!Eh o 6MnСCDbyyRx:i2JQ*~Ϟ=˓E4X,tȐ (g0ʴAaeed,} 7Ff`q^"]w:$sz{H!o ffvrS(*!;_D* 톍P(r(dCfUL|1+W룔BgpIYjiQYVCӗn!*Fajҫ [#pVq@\.rݻE Ӈ!zNOOf2?@i:~S@u!3V###_׿/?c0*~.0ommUU,&v#eY$t+m|m~򗩼sW_]Ͷ=j " Bz)Rh{//4 oPܬplJH$`0iZۅhSSSHG [p$DJ-*hۊWx <T*tX\XX(˚! <44499Y,:kY-D͒Hߗp/Nt:PϣpMЙsBa9kƂϔetGxO;w. prYXR ,[fhrêEr۠FY%hH[@k>om"$ckŃk."R*dBT1ݻwrrrttt||<  xz>}0d2 nu>Fnp!PT*p0kȘOR\ U8GQe}gggyFnwkkKjkH)j9 і1+^tEWMxU5+KKa(p"Q$9_z N̯xzVUtj_JIই m-D x#M(@pR]*4BloVG? bNY z؞jr2=;%ZV{Ae^ u]oZRisssuuZ&LV?~?9Ym9E&`O&0xpErԼqg p% ]HA uғ{0{+8FP\(I,<W_w7<qw(I0YNnyJD̔hehCpV/Hb^Cv)t;]!+_Nx6fHrPF7җ{p:' bji4[[[ (lG طo_:FD/jvĉqPz ߐbx1 T2a Սqfr\h?g>,jBJ$ȒhxAk|$j(-..>|Zb1$\(~j0:@YID jDjF7B.[rmlr?8đUWB'`PEYlg. oَ=:11J6M3 |L&CDHf8ʹ\ tHIH0Rᇫa. q\/{{ad2D6 q\@*jil{jo?ih;mn\D'0 A0366J p"0MvCq谲ldd/[m0.Zmd.dx skk+H<ϧH$DrX"2 YrZmZW\Aٳ;0zE;3ӜŬ +9IERm`9x9bJ)Ȉlqssh|P(h&dL<\./ t0E%"&#cZ?>qDTJ$R;o(NW;/^j !c Oj#Kn =4 6JDf_!_y4ѫnx r"":]Ty,l\|jj*b1"1ݒ£D&@l\\y;YCU@Pt|>WQl߿k_rw^nUv P(T.0~jE۠ 1i"{ l`a:& - B{Azw,u= ZvիW^rJ:12j* spuYͭK1RJ Ȕ$HӦi\.W*4}@ފI POi.\u| Jedd^ฬf՗zNkU$A.!)2wZY6)RSn7AB/>lwc⁤8Chk?ˣJn\>ĝ/V^]tƐՓtQ@5vMl6JDA o%L%=^ gV$}I&/LcIV5r'T*|>8#LLL'I֭*d 萺tiZ(t[D#݊d IDATjG9{]sK8vbSa}cRjff'O R 54K1Dt1G{-0ȹQna`]1X,NCkΎCxU!VW\xb>\Y4bXDaEI.g tZ~D'6QC,TRJmnnbp8dt544x|Ma]|\uR{ٰL ©8BbXQ/k zk5l/xUi: uNTwn(2hfQN^A(lkk ^z饇zRp )^Z-rP>.+WIWP"MlD@h(G_"WyȈx 5DKnT*F˕RJNDO<޽{3 q^%@ 8ֲjMzCCC݈?u=EZGyWyjllʕ+.\8v,s\Jr$n]r|ovkXu>Кl[5{ l`a{nN`0'l6ϿKǏ U9J$5rpcXo]:T1 Z S v0rNӭV+...&d2YTdۍD"H% Btzvv9sяW\d2[[[|yHD|<)ME՛Ęi -`6/D6( $G9;!kU;)+{hr9Djx<~ȑ~4$Ih(r\DMӐ((%d U7[yG;*ü {ӟ>h4 *\__]]E]>b7xZ`a1{/G8NP( ,] BX\^^~7t]hD`JRܼx//ְ:h,rlH_aZepZP(fXs΂: |PNTRJmmmυB!]&VrvoG$ ѬN&/oG.mIN9WcFQ"$Qs/*p ʹ䱌~#I|7+:t(Z-?3Ci-(Qq ˨+E[*kB`e|X;D.佐TdƖ1Zq&`KBaۄr`fׯC%,L9rrT04. Zr90R0PDNwDl%*J%Q=Z^^@>ũSO=ꫯ^|ȑ#,9X]]=}t\T*'Ot0ȸۣ@1oAc`{o{{@ PV]VTbDp[Lv@}Зz)# `>'p| ^|BX,V,e +d2ȺviRj-//g2z iFr ^19-0%ipԒ3B!DP( +IVLBXi&Ϝ9#k_8B_\fj5&*%8_F<_BG8D̶ԋ |w0݊\Qo胍21N+ˍFcuuիfA\|>_*rz" "Ȣ9ضdr{ȯb r4SƙVpohh^///MG2cǎo|c=ziZ?3yep?s5imp6k.~U*M ZrJ&&&@^]]T*@I޵Zʹ`C0H$&''ȴZ)+?gEmSJQNoN}41dFBzf5@0j:??Q^g- svL9dHDR;.ċ,(LWHxyipC4ǜp-;sȑǏcGBfחжă#DG 6[n_OE8 7#Mׯ^&BM_D&թS=}jm,1D5ѠF۝6ȹn~=?8]Z-dN#ijpX4`u+nl~w}3|qY M\$ĎWbHřBHN'b1dBEܵkז666Rl#nqٕ#änoll t@yB-'.1-h:S$Ԗ&AxH;…J߿;rH2U7^;L5p)Ơ*sY[4%׬+Ob6OTVmuuU.r"N&\zZV>____]]]\\zDB.8nRl{11 G "L>^o.38JW^=ev5c`{os3@ <133̐4@}D$#G--#T@::~j\.NJ[Jh4p @%:N&a$ [T\rZ Js`a8 HIјFGGOy2HYErmӰZ(aES$*J^x0$ɾo^_e$8իW/Wdd$qaiia d}Ȁ0}cxNyɊ-F5ݔ{Eap!` dխ-9>>>99dRT<FPt]sH&8񤀱p 67Ӭjr` Ht:U/ZQk~Pj`9WM!"Ƌ, ˿vZM O8$8[ 0p0." C$ViWD"h8 777Ţz]fD"T\ MӐ3Lp8 x ׯ)!\IA$6 vQT^uMӲ,&.i '=YE LwSYՄɑ,p#H|Ņ/~7cKKK`JVa%͡44^} Y)X Q7Y5lyDxȿzYI&74l6;77wfgg4@!Jen/B b4E ~0y(FB.GAGX 1 S$5ӧO` >|`:D:Qnc`f b#6ǏAML1z pd, o~~>ͮ-,,<󎯢B(AB!@⺾VtZUWum+뮾+6\TPABHI@zϭ̼dPnܹw>3ϝp~ivRz,C:ZpfRt 3mE}lf1Dp*9? Xx @[?wyMY+d!2v҄'6ߨNN=+n0b>|xС111&E8-Q`>Ӊ6=P(X,2SZ->#,bJ2T&** ##L&~u\@.gy6_.aJEWO* :pgz[27Aĥ j"Sr())gԨQpM+i<nG.=Z^j'oii< ue6?W2?Q,F#[Z.755bdEoNRWW^YYy+bРAXe},hc0a§~*ВCmxgK%gosWN/?߃!dp8y7ti'eeeIII[,QF,/tP9b1؀ާ^/B̫㵸{3jjj>>^.㊻}1sш 4 Qp$ICCCMM )Co?mA,\]]}!tb$%%,X__ :\5xvtt |"M:\i?ª`68T@ms=mΠ竎"g\83 ߺuk~~>.N,ݖ7*!+/!}I@w:XL"tvvI,{^7gbG#Ga='$$V(Hkanllc@ Jv?hZ4z3CB{_uU. X\#G&O͕R( 0(J&XRL&SlllyQx8N@hW>lwLFQU Hߣ 8@ ΀_yfˋ"2L(zD"رcA]]ZhANhZLf⊊xGlmm֭[9 G \XUJ;O0thr2+{WVTT|s9RlBr}h~DPfx)+n_(bÇz衳܇zw˃%"]"DPKK$xVznj͚5V'<ሏDz!0H xaW\gzT˖-={6֭ T[[V***J\`uAqq}v?~|RRݻwGEEF̾a. IDAT=zǎh 4 6DNDәftرj-|G@ whRP(ĪG=VOOOwG=dȐÇ?>>>N,srz@~$ WeX]>[رl6WUUY,^|CݨRGz,Y.େ}}nw# Ε+WfggZjف@hl~eBZc-?3rرaMlHTa3l 3I{A\\ڃYhs)?~x@p147z=Z m%d.R/uii)OZ؝Xih9WWH&i7Op{С555|PRi4l-UJm. g E(bP(z?n<o\9k?>CH$QQQ% Os%+ެ@3>|lƌ3fP*ÇG&{ssQ0 #9݊vxƈ |+2!!h4jZ7 z)Z__?|׋E:;;;::V\y#.,ٲ _:! "=8ΐ a gzj4!C`tN(*uH$pXVL99/ ,j415{x%,((1bDVVh4ш* qv@ Z[[5㩫ӟtagꫯs=7jԨx@Vq:/[̩~#eYz;VYY?G"^oT.DSad&s~-.ڊ+ 0 UQQQ%%n ;;;PU{{{[[[yyĉ\ApC P-S R2Dz ADdL.Qaʔ)؂BaBBZ x_qqq-7yXLE:ңH$uaJ% Ղ秘3.>/???)))..볩T*=Dc1AnX+++'Na^DžK!m)BlGAAD?+:u*MD%%% 1bWuƕBP[bD"d(j5 ,x;P~@wbQ,Zhҥgr|w^x}Y۝)0;&&xMΐ`0hZ0555555=X珊Ʀ(&0ӍOnRrs?-R,3f D\BXn777 6S.F{8AH{w}'N4Lh,L&QlՖQ4:1(b!C~ii)jш[ػv N' 18K5Fbƴ tdH߳~??Y@EkknH$ލ~GO{fu=,r1?Bfî-*Yş@ X,\iqeddhZTT*1'U˲\1_PUGGGssÇv:۶m@70!QࡇH  =N mz^PСC 0 ؗ ILjC 5FQ&vnHt:^-twrЅ!{? dq.LaL{{~RҔӮbBǧ'%Fau\:KuvvZ,I5' .sH{l6uTL@Huuull,Lt`2vX1qqq; ~6rtl6巃`pEV>} "}.q4 #C(w SrpR0*{^T#X*---##cL:^/'O&)JxjF#X,$tw$S¯Aa}loͲlC1T*./&zr^^,z^l+_Y9Z[[RSS[[[)  ~iͻBl.Hٓ\(rjbN.~c<~_T*8VMu\Zr\."@Gc0't (LvX@ .EGῘځY=kBd2T*X6/Vq%z^q8bdggss{C`X`O9 8֮];uT/SRRD"޽{⢣W{`: ,˺\.LWQ~t: qxuuCcKY:R9b1z6Pcpu:ptt~,f>Ly( Œ1|͚51AAgisロ0alرl6GGGcp8̵6GP(lPHR.Jnp\(E0 s1φp,".,@IGNuZ؀U`(zt` +(9Pi:xՍ9d2anXvm`| B)..͍sdر,|210],b(h*#k4.]Xl?ݸp:F& {d(3v V^7|>byH߀˂Kj'U E!DN@ *^7 b*\UU=`DeTihh+nx=zt.2+S֭uAAiƍM* ;;;u:J 11Q*644bBVЃ _5ba], N媪zW#{UUU=0.e`C@. wA_+c?\YYT*MT*Qu`6kjj>|8{<ZQQQZZ@Aq8lڴiʔ)fèv1sLF<Lv-RDe* 6 vگ~W^E_^{> BU*P(T@0*I.9S;Nhkkkoo0`@JJ   2 ֖߂AYqB,$&ml"tFz,AKyf111l6=zۙs"aRiٰgD"~.`[[vv8\^Fr/G/\011 F~[ԋD"F}U0r:vfl6۝ $(90nF+պ|H_7E\\K4bAqV8lٲe">11Q`cAQ7R9@ ^=O[[ѣG>3̓'O[l3bbb=0BP՚fNc2\.BH*Vvr1 3`^Ѱp~ZWmmm~il.HHSVȄ _Gz,A˃￟O{/odoyiii)))& [ :Hd2z=ľ~eY^ZZ:p@ԴjժvQiio/jk5!! _$=.IZmMMMZZ^W(,byVP5QvRQQlٲH8wĉLEx ,0jm6۱cǢySS-ol ?9>{AGBADH{\@nݚV `j"a9\6MMM{/#=p6PHF!z=02ZGݺudzѯ0F nl{Ȏ  ~ѐ3ĥnG~h4* ^n׮] v)qMMkDzAq@qHe[o ,R*p& ر#ң#  ͊+"=&Jy?6p"UT}m2SӜ΃6۾^D,$&XL`{h6$ nDz wMš'K(Lc$OL/6^ R~Ayz7.WUC$9Qk] رep}g/$Xbh6}NɞD黈95M_45KSSxv cJ~U]oP2ts<\䮀 l.=_qqjH[o?573fT X]=g->~E'{N1e4MɞDXlj ssmX5gm4N$R˘ 9AE{{ў=wH8kk 6x{E:Ŕl"MšϩUQQ;:PmKN_?E8-?&Aq~8DsFxD"@ 4r1r.WpEHD)f_qD黈X <:r:y6ܾAqTYQ',j eʎi-= ˅Ϸo<6T02\~Q KSL/>Dݻ(>~vf11WA ` +P Nc>olrذW+H{A)v{ uBR/|D3F *|WK,Vk4GdvkN6./x(ѯ'N>}eGGǶ*U75P0' .J  .*hX,;ZmB><.1q^AAUWOM;3Q!TjJL'ʓ?e}f(YVXLJ?gr'a ?yHҲXmb*swABzC]֪B=a%TU?[,B_Uv$KZQzRCR|wCg?<b]mkMe|'Iz#Ͱa/ma^?Sqja\̆O+9A ymnׅYԼMM_=I6n+,+{jǎ#=#G^jw?t?H$7ie`21e K '{&`4iպ'6aKJQ*5fek&X: bպb &XX Na tCqmwdw'aXlja@"k4<}fHA &8OF #U:](xK"t$΁7zH$m*U,WgGl.HMV Ǐ_9kVرMMMvowɾk$}ryy͝5<~s8XwVֳiih쎤 ѓgjNM'.pGX))w]{3 Q:ݰSw/aԅq%xƈ"taJerJ$z.  3}9og[10#+뙤T|푚zvƍ HIY8>#Go7s|6:z2kMMB̙C.Ə9糨=G yh2gt`4Ν돏 7<= 8oTV*+5Y8F_i4V `u:v[f:MJ=e-eEh4Ķv{ גYTi4V*5ޘ`>pF|c߾E~#  6UW;#X}Bk|5t$%*!a6n(mcƈLr=2LaA`OPFu 7r3#XSYyBq `='Ԑ  |r+)) _?[h1w#__ IDATW&8xjyOpB IFv@CqR9 A?C^:" |sמ=wl[4V"">:$'ֶm㏷ee-6ƝM)\AAgV;t̘uaѓl=@Q.YÇR%kY–b:;*%)iTjGcW  8G;ww~F3XJQDCS(LMM_<'Y,;֝O$&΍!J ~$ˆ}[MH 2ɚe_ge=;b@P_I\\aTԕsR}[HW   ~RINNIYXXX EV{sZP.JeV%)wqZ(߃  J `mzwl8ٻ?' s= 0=zpb*!                e˖Ez AAA\X  lH   K10AAA>=  )߃   @OGee]kkk 99yyyy=9   KASSbڵk׮mnnH$B qqq89   KACCn[nP($X,P( ^/|^{mg9AAq9su 6bJ5k֬ "''端r6lHKKKOOq :ǻAb\g_n ̚3pX|ӓU8&"~'Aq>5߻wjj3gμ뮻,Y)w}*k6y|ŏ>WΑQ$'o+sq;zZWƻ+?ͺqy#KZlAAD.GeeBJEEEW]uUrrc=EEERTPTVV/LxbŋoذdAܥrف{&{o,,p֖;#,^Z*Ů&#F|pxРA㦸c{cɎ% .iy~?Yڮ=E`\=i3>\ n|hmd=]=s1>ST,y_+x|֓?]At;b^{#lckO;A@@ >xBl'X?mzɒ[IO², `'DidAD{dgg߿nXjnڴI"w}iӾ[Zm0{/qVWWsqVP]] 8 h_^O *'7k f`sc&xi+I9G !;f 2@8@ܻl kG ,[q ``OM䓐DI w3w_73C{|]0Db-?| *6Pa{3ju 4umV~8{XEA t-%''O0oH$6lH$5L&D&LHNNnh"/HT* O},AqE_ssŸ#ϋ ,˂aڧS,B{j-^>sKH@ *'>z?s`֭fV11#_giݓ5}GIC{7/8@LpcoCrYU"KG,T1i4dκyCEfYl{0]O}Mw\|!0Aqj^o߾ ᨨO0'9AV{O!Tr>*BnwH}|Ur)]2JsH:T} ?>|V@9&%-e! 8~αAĥ]L;Gz$ADBPVV1A~GTRq b&O0,))ijjz 9S6LHQ_{X23nHⲀeY\#H"= }Ez A  L&nqtI~0Z`ρ%-;% __ĞcǏ\J{Xebl޼fƌ$?=ݻw'$$̛7O(.ѣjiwﴹC?2p\J{<1"P(?˗޽{ر@A]0 SWWww6R | ͠Koqc²I=k! ApX& p8˲o؟DY[zS7,;UԯZzY}Vca#ʤXJ/-{51FIa3cm79yi>oZn(꯾J ̎Oj~H.+=8_(J_Fv轢Ou̩ 곹?]_Tnٵzu}­F+N{T='BYlvm*J Fwf:Q<\a3c^)eLl#e'(tPX-Ba߾"0&Դc[=bq{ƣ~R2؟>T^R81;R~ǾK޽pgf%>ihخ3 }-jyad:ݞeY,*1c S| JOp81WBw @ 0lVc{O͗] 0m_0eY2:d3 +@8"sO?~ƚ۳cX9s{3e?-30_/䑘>JѼq c^@of|ǁFcӐݖ=&pY6'\HrPd'XQ]u9 YpK6R!᎒Gͭ*HIJ0|u6n:yГN2=?a#jp}~0\~ l AO@ 3C  B=[Uo9ƴ8wldmKU2IrVohW4\1fRcc.\qͶV{n/HXg Xמ= Kp ̀ћ[w',VŲ,wW{oрaݓƧ}i>L^Z5]o +i eú'Yq]So~,*^]WT`cQΫ+&O{ N;كP[bn @(p3?A1d_'C ?>6'?zy'LLyxǦ>{o&&`?'^_ccͫmcJyq88/͋_请{+jo+ːN?w7t׎Q߼>n3ksK=0Q Bhۛwo+^Fq@Lxب%[ort)4= {| Ϝ)`p(b|,aP(ĄXo[+aΕk\0Vo]?c;W~ذd𴜶M(YD<~m8$srsxY(IAXړ,Yk$ߞK}Sh??_{ v\]pce{V6 -VifE}e$ç49o?@ڃ ސ 'NVf zy6gOܲ|۾1&a0!Gz+7wSe06lBc~xo4\Yeڽu9@kG8pW_g  Q{-2RV[,~[ո?yW1F%,6 p{Kn2KR6ͻůbb~̚W_ō$o\5ݥۧ=iD YƪfN-9U^v܄>qmڕ.>2.cW "/}x?jš<9烫@;=76_u/(!+M?wxvOu# 5m-j=n/x˺o ]BuЏEU`H-"I'z\j+jˊI4XڼAKgsw8G9fh1#Ԛ+-!<}IWNnUTi{VUznrRSe C=荸1%ⲥm.@ DgAU qy' )/, ˗A,IN0 OPDa?4o @[53>=t9BA^GTXAgRt& n74). A}8~ٰ1"̸fܺbiX0D"pX `.O3ar۟{}&Pk 7F>3pFW!E i_;77ڢB9$`Ԉ+C̀99q`Ho=asTe_ a N<^xT*w&Ɯ8=ICK~ p@5׌}M{[ u L 3zƝ7}Q5M]\;Ym*6(gX V\ PfNj]%rd @ϲ]C fM7W訬H($9E\ÓGβ,Fq@ ˋ4 AJr#Gh4VK 2tz^0G'*3!~|Ǻo;$rI@ I*:JRFT }W}VJ  y])NlZQYE&e쫇` H!=GnO!L #f&% RP!?{U̜&* R󂩨ɪY*IQ--mܲ6o%+R&If*i^P @>qf&XVw39sys9U}@]].4]qZhSШ1v$g쒻$5k^<۔+6#vndޠ:F[Vpp B^-~<0 5:o=>-w5"Zaخ@DOa0 DHG<%rۿz X70oJ*x_/z@vgEM8 8}ߝ;v•fgЬWa\CE0,:t6ah1wzgorN涄3'?o epIu@r:T)s"W^(B[ Oܧ aI-(" ܛ.\ESKH"sa9m;?uk^ S} QEQt:]PPӎtS]kWB4oSG!v YPX'qSӝOf1)UN' 39rV <  lu:hSm/>qhx/t4H* GytXEW6r\@XeY( eYEpXi-Dxkʝ>cnXpy?g@eѲoT[}o`ճVB}S_Ʀ<3]>{w!M9b*C;ر/>nhGe!O VUmȲ =t ]>QN}o96c͗MlG\].ϑ`ZSx6Px쬵UoBűU@˖@UU~V̈́cX.<po ?kWeV$ IDATA5FRN*޹M<E)"^FAQԟeY߶j{??s $ Ff6|v7Wv+!4w:y^]C4tC]CdK2DD"s̽l29P1M3"hxA7a/[Ozoˤ&Y/mg HgFx 'p`EdcL@,hw{H >z4z²s?[Mƪៈ+#Vx){!hհ% bF-y_ϋ̗ g88$&Aa+s]^7sz^.,ڹy8?-._f_GwhsOv w}lPݐ -_}f;PWپ^35Pw.(t3x!Bׄ՟\ϸ5S29 .?-gѷX)X7腠1OO\渹coƍxsRw Vr:5g]Ԩ'GGwh9 Bk8<J|nk3w='[=+-MbDsZ,.^ghKDNSX Z99v~˜v AYT!vzPtFC-HQ$It]Y,>}n'7iȓ]ef *͕ow >ձ MӪF`?7pΨG<'h h8i(/zGw>V[>-q: Bc@` 꼌bCk f߿vƻ g]l3^^1kn5k ~AA "?֍~bkL:$IX,/lPGEE pСΝ;{+jMD !<ʏo4_+{[0 qɇx?ŌF57ޚDim3cӏq7]{zvz(0,5j.EQ(ʍ8j2^ҙbzl∘ 0g +7v1s ğvm טD3\ V+jEQ5ЋEQn,2$%%%77wȑῧzQB"?`kڄF:C3glݺ5??ʔ)tB)EQйEUe0##… w1)B]تUVFjӦ mQK$(((7'I!AQEQEQԍ@EQEQEAQEQEQԍ@EQEQE4ׯ_sAQEQEQՄ m{tԩw7<EQo_}j}uE O;T_kbB~!]6F}ydIQ$I/~yoTEQ9rrvQ !ZyxW_,+I(,,TS~FQu ed2M2E} x>6lCݲ! Irn?ܕC'(~=eCCCq\Vn3 `FeFEQד$I,IVmE!;#𷽒\MTnG_=DdR ˊ!*C"(_eYa ^o6V+q ð7RE]6M}(,Se7Eb~WeN&'dEmK؜_u}rb8EWO^3 oHծeS_yk]&E~S!ST8$.).6WREQԯ1Իl èZVh4WTAQuH$oF^QYfl8gH}k ageEVmWRuʹfCR\>9kb{"kuȥ}3>?i\^Y[Cm+MVBdd)+.Qf$쓅(Feb6??x$+CIaF5o"yڈq+ȀEQVV>z8%)+J@! E K2 pKX;ˇ-_; 蠭'k 1\D)*+QE](6M_~_"AQu}t:)II"h/M|:mۖM [Elzoߵ Q2gOLWM=ѓV+zgŋ>VY%Qbs*bw)6lsJvlw8%E a]2.yX1K9/~oN~#:x*ܥTXp7{]IϤzӅ8-Qu:}W=exfK~D%])rCb٩\2D7)REQME~8rȞ={⋬={; CPW7)pmIw0:rdNrR^}tpǷOio(\~דRsҀǴי&>E_ m3\f|VfZ~{^En1uʂIG^wx#읟.jŬ珟6EirsRnȥJ'Y0ďhia=|qe^}fb 9" 1<2]DžS_=:|҇"~SKEMR-hK~fڭG {}K+Y{+pIN_=C]O?*<6! [AvD6k3?#!ogmL@L ӡ>ff5 ߔ44 @og՛ C$JݩvB8q!" Ȁ EKҧ]"i:&K>!Ȏՙe P$̃r{ 0oiHâlۘտc[`[C Ȋ@V,+ `Xjݻw5q)Tz}xxm֔Ȅʢ"^/NS((.tuEokk{\tɓ~~~tB(+Wl6u!dA0>OE{\zp8\MummBH``hREQ +d~Hn0$._Gt+}eEG?Y D] G` ?Fw ĩ4u|Z=w!tyrĄE[좵M ^4ik^k ]35 hV]QTڶﮁQ˖?e_xNx`޽]ڻ*:yhhh@@V(JEj(&R YS4qw]S⺜'z+م^#g:w%c ;NX'0>3OdXe0~VaF eY@aHjYqz6 Ki, ̭UZ!tuoBNSCt͂ +h9!8AqS38mn{MsmIj +MhA5tγqO^i#Ȋ:JYyQQ%Zz(^EQAjM.+( [s&@4Be [eؙv{ddoe٣Gݻm:u*44)}EQ*#44T}+>>##_>ݜԅZjz댂(#0 h$ /hNi5DA+CED(> >}.EQE !p tX -^ "UAQEQEQԵcXxqX CijжEQEQEQςa K 2IIR0$$I(ԩmѶEQEQEݢX crYV0 aYm6ۍo{ή0Q (KCh{TTTdgg5(7lEQEQu,0fj8aC$oOQdYQ$ELDQeED Qg-ؽ{w^wa.C| 6z((n(ޠ'(0wF80(yoȐ( qD!I$AdvΕ$Ieee]veYe&5P.]ϒTҡ T ˶_eoW3flέ6AQEQEQȲl6d'__ YEEe8EItIQ(q 5$`0 S[u( k0|KG=aϬ{_i? 41ve?6m&+tfdU2Ԫovq((n&v<YZZjXh4` <.Y! %CR ߂s&a˥@o/ٱ@^qS*ƝuVo& _)rAow. EQEQMNIp8ϟ?vإK*++fVm֬YvuֲeK7o,Y E(%I(I #g7V++(,˒,P5V7@VF\amڴ wEP쇧-:ak[_KE".H _co8``" mOhǫؔrH>w"jw}6L|$>>~tYӵs>86.{t{zt}]=Xz0M{ѧ->ݼ+{СoNƣ;OI ^(M'4Hʼn<1uO6Nۜ>Kqwn7uݔIKIIۘUP~fs:f(c2_eۇq~pn{F߄{6=s))){o7$߾u(RZZxE6``Ѽ~YO:p8nBa %а p@SqK=Z-!Ļw0O0,êRLCo=3vosCG 0 v7tX`oܝo2_>u؛r̂#l(N}UQy(+x@7'r+tk"iK}60}tߤÆvïǸػUķN>_`mc& 1tsGgyas萰މ</Yq,U8/T:+Eo+ ӟu|szu{"nqq mvΏv3(qboEnKSJDX yQ`+X}+1>>~3RSgΕqqm-(xy{4ݝAqwd,K3'.Ͳ]؍+YO7㍯ d-Rk%՟Z;VZGVs(~w'7_}2RZv0[^?bUukn@IWl4+S8ON+3"KXtܻ;ԇ?{ѻ9v}ӉF t7 #JY9Ptbx_J{ |߳IBE l>~l:QU/ NƢZ='|iFJ*P/K|*X1P75q 2yvSܔ5߻E<'ҏ[ ,z jzyj^9k;_`6='[eܜ:UaӹmG3L3 @Eo`hW>\U40a Xe9°`p[X8Nh4sRܯ5<,s!qWʘUVTA1DV:FsnS_98';vvٹgچCZ _>,l!1oêq{Ǿ5. sebz+@wnMLtgC""#y"9JϺ.y p<ER]M.ɥVEl/fb3uͻ.=?HNQnBۣ|Jز@Ύ5+F/_lP~]CFّs gô=gS+9Znq*9ynˆf@mڴnݺukJ^|E֭[n:P('59j&ҭoxzjҀgYwP'ֻE/{wgԲ%ܻSb;kdw4Oɩ'W-vB۷KO_^oI+BMy]~ꎯˀ.O7̆ٔ2z|jNd`-&+ߗ8 )=pE X8KvmY1myҐ5 eѮS6fϟa'vXry\E#zY=??O*Oisӛt?sL[w /w˒3G'4\;q /wǼ!q{ng~dI=MWib6Xʝ5uO4wD٦( 6힅yھ HX)5=uqp7FN}v%&7D,1~qrzz—=7ޱauDvSKM]8'l?7==j(UCxM08+ZZ>oufn^Ӓ玞x~גc2srdN]7uo̎]q`]ʮMfֽrj~@p(fS",3Tg"Fs8qŸď`#2@k?=ϟ/..n<뎀@QlPX( Kii\[7¢τ.` DsEQDQTN|#ン 07"`޷dnv슜eSc\C+T@jrΛ{7$oIs /<s.Ƭ^ĨN@tKOD5j)oWTfL>㹍䗮Q|9aWyA5}L;qzV"8uߘ]^oQzyH7?6dU2 ~7HJxtUr/9k ?!,! K2#D`0ur-,Ft:Nj)?TɜZ .kWF5R;'Ôԛlу$Br#+=1q씬QPb3?v/DO.4 4q =gt~MK(/> :w ;Xw"X;+nbb&`ބM՟IEqyBr1PY~$ӄ@ 19 _?N>՟e cVdl2]-jM==S 7&_qvtb}As㼣xQs$mܗ?4g~ŁMCHV]T[~ 2(^7۾E}ݫt*KqqW2kuse"Usڔt =i!6=פ޳ڗzrnٍ7R\3gk_c#޿yC##|fڧFosdG`آ!!:`NZYzoSgv 6D噎Hȱ`?h>C}W>PƛODGFv>"ݖY/ƣݣ{}\홱x^s+{%}k{SRiaykEt*EN[\qe'k+fkn5GKVm0lPWA+W v*0Oh#}F ==ٳsO{0qIf,όezW[x ɧY;vܚۄrͺr,޿zlŘ (,zqu$ \WJ' j]OƯ&swʜaF!3Om={#XL#>[g07rJ&F+imŒΓ';=O5Rt6bQ!{fĥi;K꫌6}(`ҜZRSI -lٝs:L{l^f}3;-RTU%F6ݝ9+*{t ^= '-UlbI~VVV-,!wx#gn%ڊMk_L7u9r`qϿ~Pcwہg0&}tu0ɥ99ߡk(Ez.,;l9si^T]&Tq?e>q ۊ5IJǴ'x|;TĜ,NsZ -==ZzĴN~/:q>R1fyQ Mzd@*וJ+.u:rbI -ِw;YH{7N:y:ՉP@LطrɫSf50lx%09݌{E=g/[ư /m8]tzi?۸16&tӣ+m+*[=-{M%6@ wL,5e1"d/~j#&S@rĴF 6"t 3g:^ G㦬 C2KX Pj pwAУG<(Suy;5aq6=sg߼õaQܤCn|hpPⶣ1kK&|VRr Ss?jK8aŕgL4dyD،Q#q`>-_|݅o2mV[~tA `ں'',O ϳ.$fXR_Uye>A';:w 1cfh$dgJP1E_#>Zɻ/D pd`̨ޡ6ߦ!a'~ƨ9 !F!~/n?mYHy'#:ML˽9ªn  9K}:y80?}?5?zVì|_r`k-OX~&NqZ;{[>5%U{@hb`^f֌ڴ[A~%s2$L2r ^=w&9orԴ_xh1X5ۗcL跓֠~Ǭy8@6Et&sŶ6>1[.4QR)`gCf]Dm~S u|7"Xf[r#t=rv؝rXFY6[U@N-j\ Dq L[(6:߻0KFN:51z @@#BeI 7( E!,Gxa0 T=X=zƍӯh47u`]ox hl:C)ݥiGJۋoŶmf4-K+ht\^ܾ ЇoRKh Z%Pb/PU-sQ[sG^̚IGd[MgƤ|֬ŒWI_ U[tv;99}5u7{&R ͛6ܽ/Mφ7 X7Ȟ9$W}Hi*Tr䯢޳ڗl3.44Hر!IC'aK[,Ta1|_CQQQ@݋@j\`kTGJM\B,X² #DaH ܔ~@eEw%N ֩Uc"##g̘1mڴ 6mڌ3"## ''֔-tGʸkj`~ &Ȩooh`#Zs1CF67 .HnBUX0( o}x x-B#| ɓWGge1kE[W[Ե0mevXC;YZt7F=0H>b9^JJ*+Tyȝ3rHjM@j@Լe'r׺baƤTD<E7S?.23hl"&l®To-م6%&zAۂ[+ḫL1%3̜3Ǚak#s׹uDh IDATS__Xs=( ⱍ$ ҡDNN7([?l1MI"nv{L䙯hzj\FRh(=ѡJdļ_M1R$>NƯ%W|\جۣlٺu-;-j8+LVU˗dя1:Ƶ=`Ye?T*wXW+{/}PO[!\<7->-ԺXItrY0/jgy_bSPܟ =ti\{59 *طkX ewe+rEwlΈ\vbp쵅}} DԗŊe|n[v5{5ȅhk k(.Y{UuŮp z΢^>K%D2DbQo) ׬\{CquiЀ;}ڽ^7HyHKb +Қʘ(09M <cT?l͵HgW;dgc^[DnˑB%'*L$v. J,S&/+"(/1=7a5]nʋJIW w;JDƢ|N9TT'% X  xÀ*qa/6Hz|-=kQԿRk’ߒHϭj~wy.۷Mu!pЖlXP KI[[$էPށ<` :c8;tH|Y޷Z.J5ڔl鰌?eE}hZkY1;BW%eyiɹRQVŤE%XUrؼ-'4:d+1Hߞ_*ں u#pΓ﯉nu:]R 杉I(yaBsG3>?e%>);,?-tUWR^ka8]kkl*s4'}i388aL& ZsBVǏu]S_/]iw31_C[[&l舱w++F>,ZFtRu:mdb+(Y !˧馔$I/ǟ4Dĕ0EFTX~E%7M~moRaAGd+HH!=60viagE`*Ho䶜ms&G2 C^;M`Z- \oԵiF`X b#'20r0M2lY U&vl.)ƞV \MvÆSEA7XG`9,88ѝL&`dYaYN \3tm=DA.b!$IH$\%0M؀cW@@@@@@@@@@&Lӵi 1BD  `{\ظ6q6Mp4kb"1@"!ŤXL"1D"$L RXX(dWbe)Ȱ^iȑ#JrxxqBRa?>뎙6pAB N N.$ĄYz..s_ђ={,Y8{{k=cspd4]צ "E  qmYtM74ŋ.\,{ȑoWHdVO bҥ~_gG \W]69#8BDLu$$ lÕ,I$d*Hzә h4>$X)<㶷XeYF@@@@@@@@bMÂp0"c # \_8Rf؝wGB bEsz/?>;_ZQi:ÄYk ϯe9339 X@@@@@@@@`rf4kpq,g0qÀ119.l[gzX,xA+?@IrGyŮ;]?8{YsZ.{glߋ?8kӬAr8bD&&0ˁe&}yc B†[xI9TγOum?p_?lXcl߽/`{OCuunOs^\lݖSW2 @Ǚ; y:u?\" p]`qm"8L˵i88re,ˁa00!s]۪՟+?Qt?ِ/v%-o |?)S5S|췯Xtÿ}k$i}>8toVٺ;<៖V??Z&IwYw PߵhĦ_?Gz\A awbYVDf߈^H?7Wsf^kaX\86bh@ 0AlDFFs9eGm}7ѯ&bIƆ-0 $ ox x]~ymK{?/9?VbOs)YS_t]7;}ߌ;h`.p龖GP|r`nrY6 x7?&%lxrݛĤH$Xs-1ik-iKiu >ow~ S=d_?o;1`?99,d'pue:NxM7qH7ËVG$knT|S2SצYĚh8 FeD,0\kͭ-tΙ/ vaQ 8R_sF#O>š'\_xЏTMMÖ;WV`'FZû\[/$$$rl y_-}i,"+e?Ro)EECoc,|O/uO&Ke6i˭I;R;5Xu{RR+o2/)z1#).NEYo&%%>^R⸸==NaTu/l߯IlgS\fz)@IJ@O*n*IS;nw5$ʵ֩[*e$UV_zLۺ߮iuS37YRlBR%UR/^㣎 vIVYWYT9eRʺ8KJuC3}V 4嶚gf :rGR'̯io(/޽) Ni5q'JmPޣ[w[=uNVYTiSxS{j }III{*vR{fXlPUtf֓damTFٰߪDj'gMM-NiUȀM *zzOG5gZ%N_X&uߥڤS6ʙZM;=? scr6ƅѶf%ٽәVLzฃWg<zǻWeNz=/?w6͞\" 1A" IBB$*Įua{5c/G{5Kd7nwoxb+N}< k6n>9m?,o#C~|O/9w^?z G?^qtU&'ys@jq"r+T|M?4zהY(8T)Y& m0Vٰ|%N?4*!F6R/XWV_zt|MԵ|>xI+" ZkR ",_4M4 ( Am_*0&!F9IǖC]1S+zjƎlV Zom{anZT夝09om&G&a7ձ68*9=Y B*'fWuwd4jUR449?( :Ɇ/LH($z#`~6UTT&tMR {6YGgW3I(-zxhKbVl? JX]R_UUQr*9*PRXU\өwF&T%*뻵9.(4+fGe_oo/YX%hXI氢rGwLqѫ?/{3Ě͏?9![rԝncTCiT$K|⾖}!=b1AJaHCbdzeosH އRnYz3--gGYE245PTusm=KC hKaU>$ ̏y!x{튜Nj7-Ko!z[^>ݞ>/=ٜ޻bw(l`vKAah3?zx wg$N:x6^\$$bO 黩$i)bR uLۏ Wj]rx6OJ7 d ?(6U#lP/Be>;_['۳ ylj~FY3o߂<<%X_\T_~c$fs3we5mt!A4o{^ګ &'$W=Y|iJ FPaHץ?0*'i IDATO ޶ ywn^io\U֠  9mAoq+m/o L]'俯|fBQ}hY?\=]K]]}gNRIUU )/pewԤ>}ɓ'^_f`YT_, xI}OW'O|?5n/N_Y:}/p:nbO}UDzl^;;_N*D"b$%Kq}p $IbH$\{\]Aҥk|ACC߯<0p^7p‡+ Ka%"B>u Kآ-s TJy/Kؒ;6(qˈ?g9h2 nػCY6ʴ"\ Zi_ey*ADSjH-Tn,oǗצ:VYmv]i? 1 $}|I`l2a/!Yukzq:Wr7`\,V| S7Ǩ]q5 ;@z]avrsO!H"#|Z=J(E>Qҿ}N-5@;P%bKJwu뇏?:=dH7-J23 ]밚S`թ2˛x+nRv5x?4[TՕfwMS7 Pҩ)թΚ)'O'?;n^JZ]) 2WHUZf-l+XO2H_<\-G^j-Z0|PܪqtPԤ%ĦjMkZw[PRhÊi _$" )ŚK5IմR9rK hթ֫I3߈}"̀M rw R7m<r8c`MX0 #˘`4blv80&kbL&a#c`2000O7.lpXpɒ%K<c/p9Yp𘘾|c0ax8gl.&h4 Bddcbb?%h4W[e¶J-08TV{UFrtv$ ۃ5ؼi[w):8<~H bNQ\Jԇ&9>]QD:2y>>>rl$yܹ! 2zŋΝ#k+D, ߺC$?| &KHB*I2v{ȀAzAL3.N9V~;oi}y0?q0:"ו)T ;/r+;§0eFsj@qaI~(@~4ƪU@١G( eM2 ^ZY^_޾$WJH_U?]Ӄf4}z2T MZ:E 2j˷ɥ礵/[<Ҙ#+!bd2E?(l Rͨݻ-:$ȟ|?(x9RYtȭ906>+$(2X {`tPO_8rHD?hzt*{l#/IZֿp!s>  @, "e\!+b1?)h~~Rؕm k ]XHvƠrq"6(*&?dkeWT.-w@G,O]sZt#m[5p~]Enu5@5S,׷aC@ߎӌB&X&JAEGC~.I5i3 dȠDਚNܱkkۘ:ެtqƍ/ob>$\=VN5Jm7/DmZm4^WmoįWd׾+74*{SHP487>$~+-'>g_XKM24+=ׇ+IkK W)UȊ6Rʺ6_w&˚ &Ӱ8jdGFʀ=1Pc I?GavchrevTo ̈^7p4`_gOOV5 ypg0ZmOO۵=7UFww⯸#82C.]x|7\:\Ui0Eiy"> PF⸣[-c~ߥ@E“u:Ge:6.^zǥ>K+}ErK\.[lE@ o^Am2RnkT$ZlيR,.I"MTeL-{P SPrM~́y@]u 7d4',5$[u+]w;b1umXyy/ +ye\DN "lr)_0!`2p$sז4 <Ư+j=M>Ux'dr-EY1 S@X}ooY"Vj"VXlEhr;u}IsYJl4 )$Y y:ȼCv>fª<7` ?rwY">eͧLzjj3H !TQlᚷ^GʣA=G%UR=Icn^-cԸu_f׻҃ЭqbF98p(|* 4}=#wIHE.@mA4Z 2Wr-%aYӼ׵mTIdѻ^9x0w#9?.emOr_632 ?9a2'KhA-㜮Xer*w"xbBkӌ`F@65I(h+ \ش]Cf` qI8SF1;c;{ÊH$XϿXKLD.Fdž(" sJ7|o͚5pt{e y,/R޴ -!I27D9Gǻ-]^8dwX:o~v*υ^>~PT>!ii?&?{ M֎st^ 5mvvۧ.:\ 0dfiQK;ّaEÜm6KD?^"ٍ5m|F]Vza&.-2uܨOW7>{< P7|c}I@C@ ;#Cpqpz P >;2 (XfYwy\?oXZDٶx|ETVFUiro"EsVЈ=jz/]Z3X?~|.+ "{㺺af N1iK!Ӄv҈c7du,kF̓a$ڶ=+ٛpoXXXުXm3|~^OW<0^fe+jv$!$O:I5qʄΚ|<8yq]G߉lM`sQE ~ HpZM;(C͌ʓ@I,՟ FY"堞/ ?GyBV"j>rS(|ͺȨv/->c7qKyUU)hZ:93R9 t%*#|9=ԉ\毆,ٔOж*9?Ra'6+T0ihmQN_r`S=j`06fi=E 釿:%Z,|}[w[jhdlhx0}qhmI;ff{pߋfK` y1b… 瞥K~'oF7xO>Yt=ܳpBx n.<ݽRHI;//Z ^-X4Ovâg>ѯ"3S(FY'SDPMi[3kQ;n|'26CQ\>d@ͧ=RE1(eXсmR讒D$?W=YotKA*JݯhP?U>J߇%hw6(5S)ەZFnjNP"(VSGQZuOkkyjE2e@nԊJG~V֨ 8VH۲5i9u=Z5^Ќ5r?222<222<<6f3p$P6h0:UK};f3iJSJ3[% -T(gҀ{oONS]ijuWqFQk'wh[[[[[['mo(ֵ=h)2 .&(,Wi)Jlmw024'434$"0 m_iJ^gqhs!QkN Wx0ZGC4UU"cg/ je/"u2~1+/lUWDl[lݺuK5'Q6SI1*`}l 0ښ ƤLydvܜfb~p@۷h6¼}J Ei: 3rkgCթ=gxz&(_I(awRfC/NUŕ* Eï@6@7/n(J|X7=MRNjbQIӪ:Z37Yht4C;+_/z>$nZzg6Gw`1 2._7G54b}lWN,)P1mt=}~Z,ܐl)ZU]٠7ú98m_ZU+z*d}<6$}}h`㘞FQjxۓ>>>ā9$ÛoG/h¨}n:G4̹|1 *S&h0 6[$Iz{{{zzz X $ܮ A7KϺ. A@.'p8 0( -cyt˵G^ޱhu`|LMFFIoO%h\_*/2W)еê:]Vl胊'pxJ'|qmiiE yO=_l}P.{/t&Dt +!q&θlEnB2cN,?ٽo!Xm F́+r(!Jb[UuŮp z^>K%D2DbQo) ׬\{qe;(/k@ؗhͭ݋p.g~)<iIz;aEZ3@"mv9W,~394psQLh`5עg#}U<ymQ/G 601mBJ(KLMظjM׮[b#jm]AhE_SU%n) +ycVamGd00`ʄew\k>//׮ZW؂&UJ@+ ’ߒHϭj~wy.4P7Z޵}m^k}R\d$mXlf/=R ɡ@FYU-!eV/I"+:#f|ȶfK؂|4@EzTEX^x\;>~ETyg9kn^7<9@dw&_o(h;bnD6LLZD)SZo QR37ok?4-" bˆ?PbDb\HkQ`[ᆪ]_􌸚h"1\ Xp8&ŕ+zyy ^쵊]>D`L&DZpэNjZ,?~2/Z |8~xTTEawl tw'Xj=ttj|]N1v`Hrof`T#Չ+8R<`4%Jt7Ou J>+F>,ZFtRu:mdb+(Y !˧馔$I/ǟ4Dĕ0EFTX~E%7M~moRa=9V/)$R+A{ Y…}ٺu=S!,j4իWOɓ>Uyܕ{=U4oBM8Xީ!| o7UNl޼yIIIsi2 iXo8um:yf],踰kDZM8sT6ew?4όzRtBCW+HewbL{v,kkx 2Tf a4 <%W?r 6A;%2Cr@ SN?Niw<04h-k2oM^AGޤ8~RԯM2eUIR^=@$bG>?Kc #>o_4vrZX`o '#7bف`!0{q-dgg={ٱbxɒ%lcl MTrm9Le v~krU<"j| jW6n?j M-W:}??վz^7brwwwXl2FUWxY]=<8L=f ͛9yY;)u;AD͵ ÷DOx5Z-jPO!]]]=<h^x5`{\\k,@^g}` QWWWCC@@@@@@@@@:\fɴx~(JR)!qFh4+WB")4 p7|Drawww`iz…k׮]`MXף0Lww'Μ9CQ}~ x[Rs\A )nֵkגX!4 0+WPTCCCz^*zyyYoJ,ˎ{qsw#Gd~jB}G1E8q߿nV۹'[FSO=5880##cK.ϵJfY$ITJӴD"quuH$E"L53Y222b9buƍ!{uW~VL\Mi+ Uh'Ov@ [#L*@Wl:fP )iZ+Qr!?n{νsܜ9s5W^鉉p83-ڭ[ZZ<<?82vt#h={#6pmMӃc?f{zzF{@nqw~???gSFb63n2Y+|}ayDjt'.}kdtt0! eT(w}}э]0(=;0rH陋ށO?ܶ/g|׫[dQ,+z eԘ#XDWwSҭ쮨[&fd jJ]~)]&TnHH\8!!O5rUĐUN]6ehr:cUU9 9IDCי#+#W:q5)y2ٸ6IOY_}mVgU)SA$*;*>jMZ:)2ݛg %ٓ_6/!o얤N0tu`j/ǽPpL4w\oǯcR_u4B̼1::،'LbW؇)*_뇾,os6#Ly<::j2X,V``JWgnf<5F,zܯ4RY^jEڟ/=tmő;+/46W+ ?3u GW7Lk*K _Sa+<@eeYҀ@,+,;tW/ʲ24_0E]ʌߨ_;}9!U8XYee٫!!1LQt_ˆTEpn]s4űnJ*QRp${>jMZ=ܾB]Ûeoic]^7bt`JLC|mpLa\O צ)q6{ :u*((ˋ9r8#bS,i13f<@|ߍ߷Lxg.~}H+,BT9}z߼k=g0=`'BV̋lS =oe}ts;6wx8j[vb ^ܱ^JߞC #~[SϯTع,Xq)c&lJAQ%xHk[|_=ɐ?ՕNGwN cGy1n  B*?m -ٿ%@p뭴 !| $d_g){ŵ5v'n{Mi~Α5#%v*bgCP;csWHm=[څ]b $ؗI#O52|ZAr9ciNR҂1a@X˒ZwmX^o5ߨuf}S'ϯaܒ:=֬IB z1%KJ6֬laNZ֫@~8;j&w̴aެD 'DŽۑkpm<TWWo۶4(yϟr933`0z`2  C!0d ?a~g0tS8QeLÔ2(zV$D]-skO="D";>z@ o_*D&yeV*:Siv_0ucAм0)Ftu'nqUU]RUEҫJ3lrZ[ Ѝ,ISTJ]rYД~K"R{ULsJ&1m8}+ݶw@B*ϦRD;lt:3*"SbI{fFeV)[*$6;-\b{;n;,4 lm>'N'3f gp8l6 X̤mc; N|xz`7{b|6 ,6}{e}vl#}l=7ݝxώ;tYl6x㗟}_G|sp8N l6 `dN`&|]\wyYo\ǝ sQLf0uQK'tKQcY=t/К+S @QխTP'"5Q.ZkYptƖ6G¹ZVkp` zkx#@)+#"̑t6Έiē$%b ݭOxŜ<~p +j 5lUV.ڦjcw*^x>fܨ&Ȱ':4~H6$Tu09z@ywQ̅Җ  ;7&S))Uռ3{{k S#W6Z "=(imIAe\㎦]me/+EZU}}}Uq[vLM) *W#` z{*wթ(`;ZAF<{j|y>x8^RƴV^@[qbbST*JęmU@A]oO{CC^ߤL&(fUuj]i땇*[Ot5<'a*8)"y犂== E[{{KҚч&xO:0x,#.u詆2]>T,T]sMQbgBE/ZqDŽo6Mhv٧w'M[5=ET-3l5kƺJw)oZ#<}r #7H+w=)5?Yb\Cz3CR}kWxT~׽{eⴒw6ꈾSdo $KZI1w:'Z(j b8#ypM]gUakگs79z#C!-h#J/z-\ ؚC|lǯ߾)jp,j=ǼN# i%9-Һwcx[*2L9V84={_o9t+{0qXkkq9l6`9y@|Պ+~R p8'>5{~*2Ϳi`*퀙EM|OwloʈD.e~ ϛ1ۋ1k&l9sJ08^ 0<qn]:)q%FW|eK ijx@T&ō@WWWWk7<iQu:֥6/ U}N<.@~bE IbJbu{؜u#z.\( 1%)P6A\P+#o/Xr,rٲ,ɖz{ iw %(j0dY\  (xSn@ٮ0 @ZMk .7聁+_7A`6'x;iH1^U&״bxtđpɫgWgHm;޿kL5'lžf׾}Yz*Z8HN*Oa Vby |*ZA}i|kwփXW7ZlSmlMnY7椧o7(iuYr+7^؛%qWG1lp"[x4 qw5_tJZ㡈UZ:H(h6Y,]8v\joH^E01XbX,]8xy5K83T*ER knvf"Ö3-FL䊑Wɫ #'ջQ'ι \CYu?*ੁb#ÌS- Ⱥq;)z1)?"6wһg[&5ݲb\U3Ch1_t]-ÙcjIst v\nYfb<==)rp1p 72H(y2ſ~4uveA9\pfrO+LQ>'-[ԩ?ڗ!>T9Tuj`0F,顡!OOKc9~Book_n{AE>o.ohN<)*SV;2+:t_[Q¸ݵ}z=׶TdZ4fN> &fX!(ns=>**JR*zZ[XUEKծ>p zںO@kkn z<Pi N`E|6 ^٧6Y G}([y:^Qhjۢ8eASf+v.RM0hmGJhZR)YW62׶thh|l"cGJKӺMF.KUuP5-MmBvdq=-:tTm7!{إ3<7랰rTwJѸץUYM4m-----j˺m-Zt-VI\7\ΟӓUCƁ~v.0p>Sh7h`oJ5eE}M΋EQtMuTs s]ոx8=W'mؐaCҖŨ(r^}cEE# 1 `h#G*:gelqi@SDVQ66\T l vPRu}iqUFږCoX0e*Pu4* ް\@]}ZY_V[Vo߀w2WSׄ^VS{7pbQVl4n̒FFakX[_]ٙFzmlaW^ i%k'| sM2% ]syY\da*HZ}}*? 87QӀc-f}g&@d mvR'BV`:oyj-{]f{Hɩdҭ^ `Y)዁܌ԝYc ߎ“#>6k-LjMZ&f>eI[oY=80[k H)Z(˖gD.rK Y 9egp`.׸ב㨜DŽۚ~G1:Ãxkh8NggU}ɀfFAW_}U__tw|f*u|0Ũi+^ Fz Dz x |'KzX0h6;w7\n=s&ͭIgggll@~HQ\)|WK/LZ27)Zlx|?R_e|QwJ3xBfSz^"L/5́:y6]_'Ih?g nЈ)Owǣ8y!iJ/ͦSMD\4S]U]eRzB Z &L$rc^LR㮟M&/ᘂ)SOS:'0J_a-AO8(8./t S-7ziB.hnIKgj' z# r"8<܌@nrqDc6)Wp[N0}}}K,2l6ϼaX.] 4{vX,%I@ -}`ڵ|VeNB#w*nCno/<@ k}צiq~jժλ;<<iA=, #@-ptm󳇻pm=?9 69O@ -ʵ6-JKK뮻X,ڎqA @ ܢLtmZt)׮M3boX(=@ [kpm  D֚@  6MOd0 >6 6 ZLm۶ ?غuLC pkk4`6d2L&`4l6@=!f==oii˛iY@ fksmfsOOϥKF#M,ӓq\p8<<ZU2s*wveIdz򛸖S+$*^Muj2&>klcbՑMݜVg %ٓ_'Եy }it\UaQrYw _wttTg70ϴSh4sժUS>yd@@Ù53-@ w+V\Or׷dɒ)c?y+V櫛E>\BN'@ 7u>W3@ 0=?YA @ b{@ X,&@ @.X@ &RTT`wy@ fhyI@ pSx'?쳚>J=@ '991~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:}l߾Eiܸ… GRRR͚5kժUe}z˔%r9y.) (fJv/%Q^5X> 7wBnSϷnz̙oĈǏ2e ^x!<6?/4qկ!LdYeptxB (b@@(c`Q9(z)e{17|c0>-,!uxohTXѲ;_]/_&=մ ȪW~\<źƯ[?MMS -I͓/3*O[q\9Epu?1i%ߤA_y~Pb~>eʝSCoz ^V|غ߶q3wnq~N. ꁊ]UJ%M_jtXqgNj#!BÏlMӂ_}*U1iҤwyܹsq˖\@bbʕ+5MTYB!7WPhgu'өxxXSK8 S>V4t꺤1_ @ "S;\$3pGi nձ? XߠΟs_e3U[at{Cio.|S|t ,"ZX#JVz>ڎZ2:B-uqytt=OÆ TrG}~հa˗kK<(_WRe !4*Zbº)cWBlj}nSL_Ո##ڵ9=Xƹ]9_n1y':dD-ʕx]I$D_|_2hٮeb4XB%+Q97^w.T 0ABn۵k׹sΝ{̙+VȲ 'xk׮˗/ W^+GpaǎLYB!7QxbĆٴY=^Ͽ[1я?ᑣ˜*1Mu M57G*]{p˯{GcAGuJ!]_Ǐoɥu_|Ū샧fxkc'UA8#i{8F?:t]u\ǹSs=bEJ,条Wq˿v5nqB!E\l۶ҥKeYNJJen0a6EQl۶mdddɋGGvݲ,l6|YB!7 ^C#}Q9768s'tP?'nf $ {S ./66u> ~ >bZqRI_`:/h>꣩ U9Nx0_8綂rr%NlgOk%*Qķ/WZsG{>jY !\۽{wk֬Ynӧ4M4|m۶L]SBr\hh c]vllp60oX ?NV94m6X͢f.Y B]bIGٷoߡCV1,fTuUvꮧ,!nֆXouS!NΝ;ou!u4w[ZaBf'zo~q)2A1p[)sV8%""qƲ,Bn;l۶m ۈMJJԩShh1Hl)OW W=~^TywDCk֬ynJ)|kι999+WLOOիE bR͛#""/B}G$C }:x/kU)yK&<ɸ{0<ywܶ3&݈^-vrW҂ _S'sOK>ܯRj)w悅 k={ϯypyXr:lԱߠFLe_}|@5ؚS;2me9@p&p5 1Y,8hfKK) 9V+}rԮ][Bٳ麮/aliM}乬cܝ = ȭHqkܫ_hJ) Z-ڎNQw܂coťs{RO~tָܲrn؏V]E=eHqk2[ܖ:Ľ;wY ݛWM{e汯t0_&Eݳ. RKs,Iԭ ^u]/+z\ǣZT*H;fawF梃/DD^sMYf]?y)^q0.RO)yG}GwaGp|vo3`#k^f¶U :-o}u].xu\ԕR|9M8犢h"$$2 !z(,!Ţi1JU8oGKmJp17u5Nwy9 K Q\=S/9ׯ׫˷R.k|xۑ/ƕ¸na\ub\!^C!xDQu M[mPf /l]r7#mշKrկŕ{4{} p|O3:D_\_@`Q\fb^?_gsV" [y ͣ䇷ΚS@˧3s>M|jct|O[ꧻ>8/]o-~4Bi&oZCC#sؒW?tDG?mT{Kf xG _ծ ~/uɛ5 vU8s)=&/VFk?)eXz|JO׿?zM36>bc,sl3Z,,Ȝ Y>begi'_Spbܷ{*E׽z< l=f4hsd-zƛU՘SoТM7Ώta?9_U ז3F>0Ԡ(xUUoi[-s?ψhǞ3\.qqt.1n #=~BJRr.ux\NgS v>?vfðPlJ^X{doH LM~EyUՎ.Ig{HF|ge!t^\i@OiSdQ~-ps&f3Oqfbu>zsD쳕yx{a|CaM4j8kӘ1y[ޛ1ephU'wd׬}ZX)V^5Itw+-œj3O9qZ:>yr# Z2u]'hs--E9 _q-ϺNÑm2g~z2ZtO>:ƌ"d>fgf-wڠWa@NOǬxz椚}gK&J[ ŮFO?X!v>s:]Y׬Wn5fql|Gi ~G+5RG__ r־۬̿w:˲ڑmGZQfnMGB֖nTmܧU I_;Hjz8z\΂L~Zͪ;@F-}7 eqxsNN8;Np;3%~ MRSӪA߼{H5=W+J:9'ޜ\}7|n|l iѨr]s{|w:|GIym+2-Utq⢺#gٗ38?V'}2,4)\\T ?!%ISN !\t?R׾u{m5\~%9uǞ-q!Cܔ6Q܍Bw? *'Ć׊ --;ixK6 VxVEA  ~kVTPZ}nG%T\GfMw;=2G@m+Gh]48CfΩYтtytO몏\غeCw:ʴp2صr20q?;ֶΜ& ^?;;|$ҽ[I;ƆPjzѕӀgz649GηlBzL~t@1haxWkظqm B ^}Lƶ-h0Y|0zׂR1vaeWfҠ*֦idSL٬N]O[5uJ%*=zUWW64`ך6n^u/> R- 0&du^3:3I$k?n`Ŋ@ɳpQPnVZvvOhdߋ$ v]iYgPNFWx GFI{6?k/>sn cev{i1 !dZ@37! .8NccƐA`ܡK'EVƘ 5W vU\9*ɿl RZJzg{VۣZ?н WdQ1GWs\;\4[%@+`$^ Q\6`%$\4UQMun<8!+.9W+j|-qRYBŲ8Jfm fb|d N`EAy,Re'{MU[jZd &X uɺUP+X9?3+&6͜ B|]{tO \ Z,f+vqAGZ]\Pa&l= 4{௡o^HH"S\P }ϟ8+(,|_&BULW|[& K(\SYb(nN:XlHgq}; 9%$$$00l[ruvKaaaAAA$ѝ ɲ|)Fa{qV(DM_<ɮD|&DQ ݸ_G7-/[5 EL2 W}u /hxJ[sز|t~" O܍ӡ»4v\{;_R+x&dE IwS>/_m&$Y$ OȒŨ`V,,$@%$^"сٮ)@lkaBKV߂ny"$&( Tݨ_("JM_=Q8HtY[{+4Fs^Y0AETl4 0 (VuĿ^qk\-XYV^5ٳgQbQ 'N狢1CbEMSݺjʼn|ױ.1Vot`KB߂J4^S 'ȯ\߷pn\ZMO@nn>DEl Q$M?`boE^uzTȐ“ MyhhhpppWy+ѷfwȚyPlGqj9yH58α! >@eRu{?_?ǵf>y䠫B:FF޲{սey{8R&kptL"B*퇾^}b?DH#_)_CC7n\3@QFJ11؟ݛGo_ׅ&/2gPcRm>nMzDݘ} ר9f9g0ooRR]̺o4.WE {ӭcyƴPyI ШD޴RMMŅKS5޽KU|[ty,OP*"_Ea<8b(B ݒlV*˄cl6F0IW y)MoS܊}kfB`5˝9m#Ujw IDATPE sePW mkRv,)0.lX\ޢyU(چR8YV,?`4gSç/8ž_ [诡͒Z-6j*!|aȻSҷwGXlTD,Y-O7&X˽cr_ ;ވ 1A (rMfIEKL^O[m~3?X^f{_r/@W0`}GEc?7l^.iB]ٳn;<<\§;e9{^Xl2^E ~y%Sܚ([,Mry!Z,^  XBCJ]q &As8` (eY/,6Rڇ%B e!/pvGG6{ ӧsU@/Hsy^e_s9.9W:n@4,R|( TG_{v\əߥJIIeB8\^ >{n;j.Dh+E8^bԏ< !j\ y\/dK,jlA'N0LABHat]BHB4;km kҸQRbKlgJ@S+?B\ _ŃmP< ҥle{yΊq?csV͝ }g(W*b(+10,b`f -k/5U.@=[x|栈2_;$\h|3]gsQ>>|E@ͯ^gP&J`ȥjQlo+K/24pLBV~B.Re۶mu񟫕֡CrYd\@ov{O[Q~q(3qw_P (PoV=sUBlٷ$I7cq#߈>;Pι oݺO>i $IߵkW۶mӼ[rWj١Ccn`=NbPװ:WVBp0ʦȖ"&ӽn[lϗ$: A;BE(veƌ իWN8TִK S"B[PU5==}6lM.%CS !E~ȑ 8qLB) VW+WӧOj(#C!B!&kB!B!BMB!B!&B!r\ܹsoa;!B!wA] ?j׮ݲe[B!Bdǎ^788r3Ͳ,K$˲BEMn#G@$B!\Q#""Az.KAt]w:~B!B( ՚/I bMB!r-0119?d2IAUU_-m3!B!䎧p8(T=!B!x//l6+W.**aÆ+VC( VG,f{Ű Yc3NO֋*D&cn^ι999+Wo{1@!u]Ӵ{:y^smn3aNH];֪U?B-<,g.0]@oSbEMa B  0{ΜUVuBӋVX3vIV{LpI._ 22(B!0/=mȓ]\^511ό ^)giYۙǟsޱC3ft0@!:mߓvʛY)n*gfxYo^\9#?rha\*9/ ~ܸaFڹ}[Sb[T opES^~)h1'88XeM( v7lvSHd5YDVǏ;vq| !q:iC[[^ {4QG凓ʗ'?QyO;?QDZ#ϻ15Mq\} -~{3_S.,~{3.soWz3E/8t/ ! <Ϟ,YjSo-BT 6S] T* +ZZܳgE( F4fu\ѹGUsd%Sf>jS(w)LjqnY97}j.8׍BO^8㇣S9wƜT~|e߾DžK|U)ph9WHF!M2LWl,&dSh+gg傥1+B,oWd /6s/g(TjRTU%QPt@](Xu\ZG++A 00\B10hr_2-.7rCk'{[n,îVjH6l5 $&tBn$,e9zyxGwzuWw=.h.n/wy%mYޫ?vryӣgn{ U'p%|r6Wr |:.Z-ޓhi2y7{Ӿe孟lځ=kۧL}>*ڞ)NWUUSUM-O^UUU~A!ߞWUGʷǮִ j=󪪩ykM 1k%vo5xPBbJoa9h4.\Fjky ?!F v" &I0ERdl-26V t]ϸB|#=Q$Q` tw<Hcb~ oi)ʉ<]bgeuqcai`xYvLB=.SY_#/tyh⏓Xѕ8B1a]Y/U.80!K4PxαV>ms91 6^"SN,Xιι~Bȍ$ 4L$V ƘZpB2Sc3sg1@8w'K\֮UCq]}ˡzU7PNsahڱ-SVY:klZA: L2ԷV<7N_W}–h2{·{f9lؑ Q `̎kզźV-[׾&$ſplƺiIsVz0>iԖjOuIc H_:L\کdx91Ųzj~UkD!ZzJ*q:8;iޏ@k!!v5Y̲(Ls$0DmM׀tK1QmmW0'eǤa,(pn$[& {-\4[%@_X[H {9,qX>ga\sA|Ayo/^E Wu osslX.oP 侧L:aɇt_>` s-KHHH```YVB)3XA7^pk:9׌o𤐫?233‚$I*_Bɲ,"3˂"A[")ƃ:- |9E;UwS>/bWmIEEZ~hH$pI$YRd"y$Ypїb@b:ʒ*^B)I+G)DAhx(1Uva{O7UhE0Y@|XHrMB܄@QBm=y6n]_|!(:sw}SCCCoK!ιV8/k1&0Q3I$"% :`0( nh`EIE2G2E :x($:kaT?zzWӟGZ2V|ؼ"{QYNTw)uwķQґEܟ0IIDeI1ˊ,$pId=W?i>g l$q 3gN`/]6.?غITTL-ffbEԩvdt-qq^VjZvg[ j.%n^&.MAD9z젮1Ţ(j-B%ƝcW5ς(1YD ЊW~fEQA؃BafEȒ,Ƙpp`F)ݴ`!|)}?:ɄkjX#}ޘ YV,VE% Z,mfU,RQ ^ |_8i`f`y,{? n[H,6ie\+AEѝ>x۱o(76]{&F"sn2;s|సK1Ɓc+hR*c(fʹ "8u\tjPEaÆpQwٲe+~#U ! Ξ=/>-kT Z͒$_ǑU-$NMO` )H lΞKDJq?csV͝ f(e'⍾݈h 4Jf ,ӳu6Vd:i0L%2BFЙr:t^ nHf>N!7(r b( d `Q0V==}e\ dSDn  YѠcH ֭[s I!w)ussLUU]PU:T;H!7$IUzB1A|%n:չr}{ L "/!/z˖-D?Bas֙Cӡ^$3 !EK.3fXylG 1Ro*Ux+cRg=x} 6f0Bȍu:hܣjWӸjPUN=BnXxx#g[5sZ+Q&ά?͙ `V~-B! L` 1I$"%hq+؎@}GhW,rN3~߇B({K\mРIA?pȁ 4ME5ujƏG^mX z7=zylBB1898`L,C`DhE[;`ﮌG>0sSv)JH8`DuI&<;"@!w2WڪďGX vf>QZ_hf&W|ַ^8&v!ynujFt~vHy|cs׮Yc]P-_N0`R%:="o W^X!e&%!\,sp0]U@VO? ql'sR\&mޤ>6X2hb:<}쥛69o!Rp b!&,\Hr/TNmjɎuz|{Gꄁu>/賅n!& \b󎬛3CVVO=:-oO3Fy9>aeŪ|AsӺ6On @ޔ̌7Bi :98BMqkMUU4hFHxd۴||&5'oT`Lv7b7c)5cϲߍ ""M6aWj7oդII@!wKM1)֣.m؄&QAA*lp'2m#ڴhjп`ӪEaŖSjn|ĎM>}{/o!~QWR${CT?!&R5000 *C8.zV?vP ;^C>SQg`^S}9bY4R,=C}^O.B=g/1'<7<.c? IDAT 3ԱV=yPꈧҫ55 ̗<=R 9IX>BȽs]\:SUU9TU/>{?8Wt,aMw6䃧<Y9v#Otpq1O*` ?{VyN~Ж"Ui:ڕ:[TleOw+?`wr n p`v 6DPMzKjaJ,%r p $vQ9׹~z_?=Z/,ڃ+SS}|=|rc,oO)f,eLf3 ŒsXX"#ϿxϏX&;> tx`6LmZ~Dʑ'<d כh*gGNMOz/8qq;_~/w'w?DC[~@ɧ#s/chO;;_w/ \cgSǭ??jcaaaUp 0shi&srϝ;#xŋ+Yԕ) n?;3OUJ&&(&I&fڏ=0O]#):pGh >\&H<1A:[_Fc~W!KegVgy0ZvC~GOy[`4cUU5k8~$mvE0Jy^xXXXX^p^r0nW-Cnn.dC;ж}  ˊAs 0>|9v,,,,,_;V-_ڱk{cob;|#84 ˭]Oݹ`aaaaY.Ap9<..8xs>w[?;rCM444/B]`aaaaaaaaaaK48<G\p98]`aaaaaaaaaa~h&8c]s#wt/+ EM& irx\%xcp(y\ln3ׯcaaaaaaaaaBAX^KcP494`2cp?;7-_.pgisfcšsnsFYXXXXXXXXXfMisEQss4 P9Ps gfsgw|+ĬlGggx 2͞c>(ٞ!=9 1?tԽ73~[~ٞjXiojn΂ԥr&ݱ8Cg{l܌cpSvyrK5_-Ѕ(9i413G`i͠9w13fƦSQLGI꫉Yi]i@O|na𓘘&ubZtn*/YVx>3u$#b"N^,'$n*kRT5ibŠ86-F1CiVJjkZK [=R *vv@LOl}1fG'EPXUlP_tj_I̎55~֘?/n)n+'/yt=*Q3LC%63AdUEZl3 U= BCZl?9Fuo{}Ua8ߣPLk5$͠iOVҫ5 Ziv[S(H7I75=MyzZ!;l5YuȤNl.jP%U8ϱ8w`ZK22[#7f&+y4Y0---^|1/--deLjT9uMsyC/ʾ-%zB%Xc2>eH&5Tk+6Rejػ1\Iyn549hznnLRfd6Ӡ̠(8=_q돳fϿ4a:ܧ_~ZAϸqnH6_{h̔&<oehyb}Ĕ Q8(mozzK3 P %t%텏meqkoj7/Gt5UeQ7BZ[ 3\(ʊhg5]ϗbz;^^9#=󷕭g04?>'*$lwT5M -4Qk& 55l딒? F T;eZpTZkg(MTңܚT)-M@[b6 ([Rksm'Au2D%sde}ʎbYEֵɶokTBzN\s|/>=.emjv?2}ʁ+Ңp#Q#]ҋذ!bmm~zºZD$HN\;O_!S]rr*6[TկnPUose6Ie99dα8^H=#]Ri+'W(MǾZ2m<,lT*H8yB*qKǮ~E91m8vU}ō>E[cu$dYP=ҶJuw7VfȺ>u+/_o-!k&4))EQSf>xp3/`n: ƙn߂n'WKPY|~FP*lh|dxtNDz *9?(|tTG5t C˘ ˬ-b.ETƒJZyxu<wբqoxX|vu]*r*:\ (z,+ \%[@OZh:{yNZѪۣ%+.=r4"Z;E=/ e.@FNN:?Z?4& lñhQXxJ%O:rh=R):ndŲxIߖquڤN̏ Lq8>\;{1% C@pCvxhXd}Y2W)<ݾܙFFFzM8uvI{RXᳩL3 @_@juߩ###:zqYKXWO#&]uJq*שH޲LS@KULK[^Nz:e1?=kL4e3͙MYyc5͚a2Daf)mz92 "ͣP~uWǯQ?'pްQDWsq?g4 5^(TybqU>ֶݿx |X_Iꦼؼ&UOضi\~|V!ASh[Lo9@4N ɳo-wb{K*"O2_kym s:PC@`oIȫ1W`lU{{y5^:ĺB\c[s&5yɡbAXW@kka,%4>:D:Vuf7o5L *=R]iOYe߮ng67VLK82iTVRA'׫H` g >' ]Mפj[ |0`yƊ"Mb"ͺD $9FŐ I`胷> FJL/^%eW)Odpn%bT`9/6>OL^<z!Zܚ%*ȬWr&׻0K675ΪLj:H.mt6K.*-F(av|?01:P`faXPUqcv7ػؠCP֫KD%%NR81k`7 5X,."qey oKf$l :!ac-RC#Z{6͹8]?W?/2܏ B Ukm:j*-Auo7*FGe(jd`6&H{2UV([>VʑMJZLlbxt(@fH,I{-ǯP Tgsژ"=fƶԇ`j^`.ѢZɳ@:K Aݴ.bߥƁc]ZSsQ'ݍI_95kEёȬN@i fU U t8" rDSmJ,gcV(ZR!MȒyO넟/--|,I䧤յt+kN6@[doVnӵOTZ s޳A%=vVdo7 @V*4bBjPUJ=YXoYj ԾX* `XQذ%7} *j 61mFc6TJHں?!tMجiQ046+1}ꙎPP>Q}`0>bHw](Y `Ng-~f{5 гӋ%P忳C39oT)2޸C yT/TIYb&uG 2t@[ǐZw 1W,w{I6+1EܭLE~&p^Ӝ)6H$Hz{K#>x.)6Fp }źzj좼c̺OizlފI- S7U孋+=ZAmNqlO/0b>Z ʻP_emdt#פ%K+۔Ã46jTJѶ}y WcKNmSmԜd4fL &8뮫;9ooj_0ю}<޽n_>|/l!imW ?I{ 6`uyޝ!I{2r%*X`vkёaV=T=u4>rwbb*Х!>7rS@(ls$=ڰ?>Z,d,§_2~sdohphʺ$1Z2xD\/x*̟s-ÂVI9[!ȨS:72:9Qb.3@ev=.oHOm!!awwW(ЮG##x22x`|wcj\! B,@{#gʾWE!!a@}܋\,*A?.W RPP{ EȖASݑY#R Ze@]G|+;WLIEmc7|v)O'A!ɈE=ϒ[l >ŦAOkďFBH43d4Ns-4>#Ik IBHQheM0nݽ;\2ڐTK4HEeωȑ!<^?vXf5B#7pȖ)ՙRKyBB#6)qWvD#|ŤWu 7?\R|DT7?uRζOAଢ0*GV=r0AxK-ܽ_%7vTWkSQ9^w Ғɫ|1|Mx*' #]=qvA$ȹH<|>'k:U0̘ȿ}$jsN=^?VZNU DIkj2r&2MoO. Phtdx(d W)n߽[8`VLG(Y2I";^](Zl-ʘTwްݢ 끙 &C Ȏcc1mp8?/Z=2e6ӳ4mAaD(jfhŌ zݸ@ }~y}׮[Zqo7O/_,؛%K[ThL ϯM7 lv?SXՈqqTm'w(T1| 8$Ot˦X& vqHn`~.$nk3N |o?@İFi}.$>[YE<`1^XqF>DA~|k͒ivq)x-@}V%Q᫦ce&p7 5W/EE-# C2)ՐUGmj[,.bxuI(}UnP]<1->cE vByNXJE.]g{):05YH>;s0͐bu}3Iv3敝LNQQޟo_:g7H IDAT6D٥5d@yj̳$iol}zUy~nk̢L 'rL89|}/C^#ښrUvL78'B(Kv^`~\y] HopapTH/ZG9oGTzoc=՛Y81ޛ=7tѓ-Ȋ{׵33Qq`PMZke;(Nr΄`ϱmqo=КR-E0!qw{C UߢfZ)) Effff+TE9^5>&GdkkC) ߕ7-X Xĺꀀq=U&Ao")-:tU!{*i4VVO|iqcOH@qmۊuL#N^pJi~bi{o<_V-Rv%h,B-)$kB/[ b:%6i^ 6`f{ed`|a-qCcwG8Kؒ0OM/X4M,z `gb*D ;Rd$nj0pVxC U s v0WTp ~!Cq(rw{&V L`#dv`h–Q%`gv*Ԇk<4"-CsQe&Vv08kbWreEzE@+*8Gyz QAm*qmKiZl`rd|vP}[:/fQtz(MԴxM@'*HUƄ ٻ{KHVt;/0D' ǽ Sqn:7N_Sg_E`ǨrÒo-~O.[ \Vڔ4˾G7hH89s$L ⳏ)iV B| l/ Ãd˫E6<'H$?x,½d5 B+!?1)By ~"UƉK% W#&$䋊9Fw;0lxa?J' ?-niGy6_lMź|Z:jKÑmםbO>q3Tr@$MT޹ghH 4?6,C#1("$X^AptƝpe-0v@܍-x̝93vWL9q6L׮]3ZeUsA|? kqZbZB),8 \K]k!l .-( xrhp%fyC:k{zyZF3\h5:9v3Jv iw3D]v&.)K2T^8W5>ogި@=6tj$ש zIl{ 8Vߣ74Mr(8 Zp[R|JK!UJg\,l봽u O] z20c6۝tԪt$S, .]L0 B}BMmڗ \%*N G*ёJ!5CA1Of@UZ*1WTCR PɁms fE03AZU\v@9Ժ4Wc jI2\ګ3[J3-ČqQ 0"YAܩҙ}Bnhq镐VP V&JJ$Ө{z `j\SwV!Xr&QYRKXFQtS_^8?4VjJc 9;k_KRX:auOZ7PMkR`]wz{zzzzz^s;mJ)l7DȏTSUPgZBźzj&Ke{39-C'h$1gTvו;huNS`i|Dv뽘^uv٦iUښRT]m8MW[拘Zt%Bvtev@Yr5tl Ʋa2Z 5MooR\m=*y߃ş6ɌP(ܻw7ͥ,J_r<^?~M-$֣cnϔ4|OUQ~ " pC"Hp믊s7:5_ teO.b,YZכ>IHѱ7m\OXTS?\U;_LfQqѺs1)R;zA')~ҡ zIw[L0z#VM[vn! rQ2eb,HOR(^oŨ<~bG٨ps+N_3_s;C A-sR J}h,RO^3^x~nG"@#a4鮱_@zZ,UkrN N uM05ěO)n'o,mJ5 +}YHydZfxb}SLl پ鞐///Bm۶;@4I:nnn1  K|~jYQzg9i=7pL}G1BL +#[{WRiRM _bY+MnWՈnXkUIjAfMsT{U\v>|[>ؾ* {}":+'K'#tUS;ї(\6:~|>rM&h4&^: חͽwa}m.ԇA ?)^t{Xn?zEQDhw>c9anl=.emjv?2}ʁ+Ңp#Q#]ҋذ!bmm~zºZD$HN\;O_!S]ql>eG"}m'U*m՛\Yz ERYE+}s,kOy8KcԲ-ũRemݍw*7(e%Xlk]HU;rP`EF.+ Hp7h^z?===::\r˗/4uֵk^ [BD׮Mi:>zE?:vSR3|6CumnkOV kCi)g7 yv"u BxgY̻]4}!Aa=Y'8mWYu ȱ ( J吔 w {)ț- rO,iq ?kl)T燖1E+Y[\8@]_x5EA[TTtz,+ z%[@OZh:{yN@h ⳇKk0 ֩YсcGƋD{ ﳓӧ9\U)KZCt2DmqEr$U_m8.ڽhw {X}dtkN+D .p,;ZR1zS9ETJ8,^lҷw\6)pš5+rd2p Ԗ(t  a;B -du7srٓdwc-$s>s(b O/??8(B:wxhw*=le:P ./U-}oJ-Mqx`ccc.]r ݲeΝ;>ܮ. +<:mZwuZus Ot8wyzݷ!z66 A^eA"RR]i]sgVٖ<ۜPs8r936*ͺ1S@U 瀘 O;Ҥ U :Dc qMM O,$uS^l^^l[E  Yj=ݴzhI#|Sc_A̪b{K*"O2_k\6@4V q,?t= y= oo/OkY.pu^UbT!-sk G"^Zmr\*ZPk,۬$,IvVeZ.o, 8x0X[a"W&' dK@B_?"1=d Ȓ!:-9y:klJԫHMk8O5?g6- cm*oq~u=o O;Zw8S5g ^%qh4%U%bWY!_R픡r@,z]f=!@b*XjJnnSEl/,-]wY>QY~Oq BeIk2Qnq"]b{moH&[NfTDSmJ,gcV(ZR!MHN^kW+To 2GQ1:,CiH_MT&ӵ1yE83m-Dz VNYŔʶ)G{ o:0_ 8ΐY;ӵ1yEz̾mݍ10Լ>*}M]EEǦ$UR%ai]ľK##}-e1ǺbvL%IHPDPt7&K^ FG:#b;u)ߚU1T7<<@Wg~S\% ?_ZZXOIkVQjM7PŢxY;sT0}[ЈMB@yNLCV] +dU/c- fU-v^nb<*J`1cEaÖPHxw+T0 Z${V;tLퟁ?\b *S) k?ZV[a6]C>6kZe3M~!JLz>ԵOTXc3OF182]!-Jlck'sE@nBq(f:/S +;.n w-1[*j-b*-. p߉5X0N7?78 `Ng-c~f{5 гӋ%P忳U\FF::"`}.W}b3vQ;pOme';vcnj6jy݈E@~¦ؼW;u:Zt('8ߔNT.(2>ha%;E=տ=/B5,ڑ|ierxP^> CF b'\ -X*<﮻ |Xpi;xVz 툊<؏zVzZ^V69^?21y,i(Rd>6{%^_X YW? ѮS. aHDDDFF=l !A-G~7v *hX֘iʻZ}(s-`VG= QI@6av|\ 09xĂN870|-8<:2a!iQGFn {mNGGEk@o ?XY<5Ԕ\/x*̟s-ÂVI9[!ȨS:72:9Qbs9]\'UgJ*6z\Uߐ<'+ ڳCBv P]FFT[י lZII$vɯc1N3MMMn3f*f#a茁X1v J*M*F'+ 4A1D,Y#s8q$#Nj>~} ~+g,5F[L2'uǦCe`2p1-FM"Ad'\Pu򃁶i&F?M%AT uy'/@rcs!sDDֲ'ݥ |y4W~e;9lP+d9F/Vji=|Ւ4>a޺kl+ /޷qڭ;vImId>e;WvGUHDꔦ 5팽 1QǻwkvIׯn۳gKnknp/;DC\p|u-y ^U)n1)u(Z햮]PK`fhz\0_wq{tpcQustmep0nXneV+S?ZQBԌ>bSe;7,"82?Q%.})jY@>4AG(|alQ ,L,ˬC=/m _em%^zl?i2!kcI`f t* HZ*\ iNn%EjlĚxJFh=]8_mVP(uM['[`PCIV6wVETsvG|\ma*\|3a=B;sX\,Xb?(evY[ K>T ׼r `*:ֲ>pu0*vJ0$@23>eV^.7̚x@4{oG< ;>9|ԤMe2zDېif]$8lg;"2d4]Y@![ýA^8&c:Ϊˬmc{B7CY3+|u=O|,Ҡ)+u=c=r2,g9Fa5;g2KgCdB٦.+HUǿۺV,Y_i[N3t+`Yyyyyږ?DR o[BPPøCl9K i(Kͻ${u,5;[cqD*{iD"c _?ӒCL_"=gefd٪kV.]Eb;. `U@sQQ/@|4zż:3,1WLfݘD1jXqr}z ʹs%^N^1u sOl'!9MyLJtJ=!ACaE>IQUʸda͂pD ǗÁƦ5(36?mL<\(ʹS=6?3s9uTx gxlp-JŘ{2Cy$ [ wغV;b{lH4wXZH'K[*څ5A'ُ:7ClqnvEn#GkyFJR٧ ~V¬ݲ,\r V"de`s{!/(~UwzOA؜#*4>s͑J(@zj"diH%>ZwpOQv{S ;C[tnd ]zt_} H>ڮ#ZU;OҧnasD4@) s/KFI'N?>07߸o~"- N=_`dK+S%$Ha1v.d΁dv/qYlܗ=Hr9ˋj!Uu=+AZ_{#~/JR1[T/P&-L1`|H˜a͠>[m^pBx)1ŋ"sw<<:j^Bա?[, N@~w?"fұak؝XH1AQmAD~eChn@ pl钌UXVVH6nؘ+V\U+ J[#d'W}puj=b֖S]fI[26n)j~$ZWJN(ʵ7Twy|>1[SRe'-fI:K+MДJ?-ެy?%\>gn~X[Xn]~'ٷ=$ ,$o$m?y 6^cR&`a֕l րд>reF(UrZrvHU  %h@㛓^߭ 9;V t*MJC W~[\>\Mf J13)Sngw1#0d|.t; lMȂ[LSs:uH)£tS]ʔo:hě*4WaYM1>Ȉp8933 S`~=2ߝp~ }DS|nTW+pOtc=nv=m>r]\L NI; e@m5=>O< lb.]ѠDu`<^pc$ xU3KV|_h:F{KMA Kdžcsb1I-hssG}DQ}/VdVt:Μ937QuOgq^;::L衘(k zr;jXy6^?:,?K}BZ}r=^/\g (Z$EQs)KzZC( 01A-jOQ=RTC%Hy t%ߡbd; k&+ NV|Z(B$%EQؠV0 UFEQC]Cmn,:#B?2iOQLCB;abC@pԱ6T]9M-_(5CfO8ƖCj)Րb/ǹ,-΀Y$qCX݇5oE7) G;! ݹI^JRe+iXY/>7{~{TTmj6th# dd6]IX}({)E ݕPG ] eREmrdH eظ^)E뎁84ςAT7mђ(jܢgUmcq92$$qu̸ &\E~;N՚ \B<ؿOӉ $l |3gV5777==wy9>r( O#w"rrr222 6};AP"I >4&y-gU#Xcr~+^QTvQg;=J wG. I >t +["[AǛ-e x%L!KHg:$EIܞ@xD"f&.HzfK/(<ӁxA>' fDI<^Wp9^"Cn=w- MxM4o@ 5ge2 ݞ#]ՒRw%Hz=ߏ}?ޚK- GRxH#buY"J$%,>u% F32 lq:-uiԖ Vi4Bg^H=l.Ī3hlΫ MNk)U]j.TqYB\KѲ`{5%VNWQ!z^ZRm%.r&2^I<}_X#nIrD9OKn=ex{}%،-B%-GH}lBl&+)In hئ1DhĢL &0&;C$ь&(J"U8[ .-Ŷ2 IDAT;>'b^x>uT䟧NK-!5`~|){_K[|AM dSgo']x YgHA۳jbW x| Y?c#gy<$C`0CsgIo}nRaw Qd|sYxWZE~ÏW8p nC`XV^}K ?zAY,b@^ / y@^M8;;?#泳'333##C(.Y~|긅ii_<<<<<<<<<cP8g ۏR${K#%x JZ]dG#ihu'ut}ĸt47T핕jTdH75ݜY%GKV5cfw>B%wAJp@h9z 0]SRy!L_(tl}^0hжC0"(,ʭ1vZ`&@6fcz C\uJL;׷6zS/76 ,Fv3Z7/fT4Kѐ}ݭ؞ y+k8gR3g ^̌}-J []YQEJM6ӡ-\;K7Aw~gYZr4VHܾ<ԇg[?_Lݪ33WBzؙ3Y\=Qd3am,49s'g,&TB 3k .@;~m3/ Vו@f+l2 Gn'%FcwTŇCJk ٻs.CqDG++4*6{_[bӊmS~QYepsʋZS]CƊ 'TPCDkm$ͭ`͚ˊn$ )0ӥw{;V7W8TRX٨tƲtҵG.ߺYg_7Nֶ+1NPK$jwq fwO[mާYo(k]Eނ/iuv?@p=( r.M_H>Cw;//č@n%K\k׮0UYYY =X |g-dfϮ/r,U\v||{3jzg$ Z: :? r~ yw0[9<9~J* dבbA,~QAv۶ؽm7`dSSQgxaQJPcOĻ#"z,e?4Omg3Sa LS[&7sTp}[7>7(Q[ڊ\PdEߠe!%Hf&g9~20%ΞJK{zttϡ|3^#n9ܤxcQOa ;Jw82>6'zpW-o(qۣI68yƭn/#Gtrozvˀ]沾IW`J\k9{tס6ZC[oܶ闏rOׯFV)Yl c/صutW?rۢXYXlS5BZeft_PX W?o߾t=\%~†~=ATotT SkLZJi9C򾯳*#CRLXL"р9p qD?$r(yu]6CVvɚNJa)kDg %\'Zk?`7xwD$$+1z E{e\@YqDc?hK3 }3kp8 7eY| {YEC׀,n{\3au#k?\IꛣTqu2<6j{N!V~v|jl :죣ꢟt:P1=mxt֡03Tjoϫ5zF--&{S`7tOZ-OswKeJGxއsYiaeE4L[!~w=IуBR #Ps]`FySA`狭j[n_LXL"Y!> 3{"\D^/ѝҽa{E@3OAT![Ÿe-]?fscO0x7ya=c㣶Lݎ k9X,*3p.5=,iH%%a5B&'J[u=aξHԒN0`tՏb:a*,OQԷ-#fbXNؙ3g&RjoÝ|gLMMysxx8.+D@۰?/PLJ}54cm H^t6/o9IQ^r~nEB?NQE (oC3ucm @5LQ++@Q&)jMg:>,:90B1$Em+yϨQNTA|q$5У 5 ,W.5ЮWϬYj8"cߡ ,1r(V! |9t9{zبC h-^g,&ʀ]T ^ R)]Նh^BQ$ /841u)d) R*dPX7I S3JQas=G+ߡ ءx)@E" e$!= qːA (lL_e,I1倪- OwI:@ HЏ:j}ߘ3ҩx)RەᱱQ M!~wT+ i8f~6/)*ɸ}jRre4CRJCD]\ס)8IaQ8ٙ؆Ƙ2 &\v3=Rd]ZJ)>&QHr㜖cBp鱵ӥ3bu)J6T@aNR@7ot2Z EQXi] 3;r\E]Vb-rXq<[Ұ6^#( hT!SPjboQh;*j.oFt$*?aLJTB(*`wZXN^9Tbm:G`WI p\v:V5oywՙ@ @ŋ?Iy xgD]t.j}' l!-[ \l(PHg<"BFMN]f 6G1r)@g_U`;2:20̓N9GTh|#P=L1 Ef'KC*ttW˪[ e1EЙ31E7M&IշP_=qdJ>e]Qc]]P-}Zz{8G${L40NdZΘ4K z2v^ӡsÞ+FwHKSO_@$<|!n͕srnCzcr0;g2K@q;EQ@@,s6K.hE_ ۺĠU, qiqt"rLkrqY2B)ߔWr2)LFnp6U-#n4[sjt dlدa"u%C5 *4-f1{J?Roj)B3vElyBq &@fH0nL@%NȠ{]ViRJk/rwpm}g5kT)YLw;&p90Ogo@bEe:ЩDDRNA|nlv2W0.$/kVCᶚf<ȸvw*Vꝅ'>ݵ>B>kt&.&GwV /:f?\^3}nbrwkRce~=w9rD IIr~4`top:F\2Hw7Jwd@$-HG`$UAqtyM}8oOjP=䂯Ϊ&;b`ʯhtN?7Vaal .9;➖|=ny|V`,sYw% W7x[J#\E#1gcY0UC12[o bXN ${g'|2>>i||YS5ic 3n1lNP8)?xp8fX{䅢ٮWDKddjjR' ]jpzPR$rLYt K^r@H8*yn'W`/'Jl]RnczC"GbqSDꑿOM2^]!Cr(FђڧcKqA-NQEMuj\*Uj; 'VSdq1R~yeҦu6r(C/EGPBgR)5cm.@by5_\_hF6x1RK:ZsYZ I҇ kަnRh!0wCjj;sBJ@3` ͞q&eg>:g$7%\=LC$:A (FJ)b!ZC͹|~ΐEQ~gggxXo߾{7@` ONKYaN>7D3$}i=LL#["΀F}cvVdvָDz<^0qw N%yQ>YdyORI$bf₤; aD<3x¬lF3u%`q 7ў%ƒ&яH? erFܠZ5> ܜ9=@2Fz1;;r9C^t{MQlOO͛o|>f~UIZZZNNX,NqsR"zZޓ (qb7NJGpIq|@&ğ-PG-LZ`fMc}Bo?tAӔY$S 6dU=&/7}VYv"].x+*JM=Vc{ط5ZNR?RŷV]7~XMP<_:f94\:~|){_K[|AM dSgo'm}#>d`0(,g,Ew?BOS <`0!ʹӮ>7B|XvUR/׮][DtaNӿ}s >oV% \Ώ;k׮}lժ (ZBI_jUq._l;\Dw>PD&˖'ZGr%1|rW \ xY-E$߂: JÏEdvv }~%?sk{ϗ~s3x7xxxxxxxxxxxn&ϟӟ?xxxxxxxxxxxn,}}}/?ṁ7 5EegΜo~{-9Y>df͚s7_,>̱^/B/VjWϝ;700_jŊ?QYm-r[IENDB`workbench-1.1.1/src/Resources/HelpFiles/Overlay_Toolbox/Charting/Thumbs.db000077500000000000000000000350001255417355300265620ustar00rootroot00000000000000ࡱ>  Root Entry@ֻ5256_e896e4c3b0bbc508*i/Q/`4Q/`4JFIFC  !"$"$C" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?qMnˏivQZA$c5,O`zׁ ]Ilj~ <[CH=?,z_5zė [ >|HLuێ}E#6dfincƊ2r>״ i Yk4[;evǃPjPKqwv*}}.?鯣$xvĐ)ne5OV/=x׈MGvnJ$/fS_5^? oLKmBd#RF>0oj|ywأ8* -V6kVA̅8_KZ}H]ᶟqbpw zWg&0-؞64ggUqL#?|__5ÏxC84ͱ1ʄ+`:S_ uƧ->eQHВqnˏjhdզPy(9xZVٍ%D<%iN1o'ۮwRu/6/Owm~OYQz}54Կ'nKz͢]=u_m^g M-u/ۮwRh?jim~OFwESKnKz6Կ'(>[u_ѷ]=fEۮwR5VVjZտ+w]B45h/֞$կ:;L00QBIO E^P`E\@ֵ,IlmaHr:[}[:!;T-mjÈrp3ڳxmOIk%Z(Qom銏SӴ9QM !WonK{hV6v7Ĥ13ۜ@ 5:JKZ:ԦOK!9=:;Մ!U3Ddێ3؅Y;_|9=\Ds0) PsSu#^ km<[+hq ǽ]ȌT<^2GnO9)tC iM*6&pBˌ8}k qtk8&=>X+hB+5t7];hj"ٕR@x׊6m {ko6DV3WUjiq #OW]t'=Cñ |}' FYvv-?^|[ŪoKMP|"#>܃[ ."c5CIk_,d0!WVϸ"VVBI%d?^d-vtI# F5|.9]>vH}sdWkͧXXjmmhJb 9m0s۽sa&I+{{]V# 8.LgͿ'oj֝pZߌOC +>`;õy{^ؗ 5ʊzGN=2k$pnۼ}֗(QWaDo0}ةc"±`AMP.+Bk[ /Sqۦ1sBn.#}j3͢gjIUrǮ)5#)S'4)W^kȄX+0b8B{qpMtKU=O^;Z>SteQWqT;>5~q8*#ht&P]V-bi@@}XF=9>*-6B1xm*Yfc<rzd1O{[Kn'==T}^`36y{ T >T9קQL,UC3K2ʨJ|8ǯ5I;џVo wЍMen"Ȑqoq뚇Wwp)__S|jji̷Z}JywA}3R]_]J  mq.FqOS_hͮ]=U8C$`>Yݛ':I#sL;xd(LЩ^s5PQèjSG=ۣU+W+t㗍|k:͹x6=L^]E})О6Vݦq(OsПWY񞒚vvbv]@݄~]7KIgb?~}of<9qtј)PyvҸ Zt Y ,IC䟺z׉^{[~fj2*e8W ( {W+(Ak=i vA`*#Yh_D#;7mz12(S}Ai#a)M8MT2o_1u2"NGEo7 䪢aGz׌l{CneKZ[dfڱܔf#2*%{7meĭ,ȋ%uU??*β{X\@y޴|M¬!8i?_ΝǝMLzP18 +105Yp>|p/0.q!>jɷVi5#sq#*spvKvyC[ɜ\*">n'vT>2@A1kP\m4,12#%*w^uKo̿$>Ewe2sҫ\ BfLojyrĸ7TQbgU߰)Liv=?+/ՍfRhM܈x9/AkW:y Ḝ|"o"CU5kMB5S֒{~uuQuG5ZY$8K+Ur Wڛ['COF8® @+7~fM>ͼGf͓ f#'@jxgX@E6ug\yN'K)O%<ʔ'#Z2 s%̼G|g{főɜh ]mޅ*H^{V?!RzpoĤW0! ? K=cTB1-|>hd5-'NT28SHzyRr;M麜, 8;pc^rQ/#Ev<|O0x-QgYjHgc~uDŽ1p#ub!`qWö^|_X#KSP=7\ۏjtJ5ԷTFNV Kc_]E[N]iYI 儏c'c^o$7HT͟I!h/ K2Փ m"铎k|z[:2! :9s.MSZ_}k/F%kE'!2Ic rq}V%ll;aC#M0cRjdbu7mSGBn-]1m:L*iM.B$=Ui$fJ~Z a}J)_GˈIu_ \G"m׳Z/YogNUfؚFIgUyȮp63"iIc<߄RN]sFXi -ZY +Y\e@$ 䑌s_;|7F4?xfO6mm!*0=:'5|axvN:M@:|G'ր=7X?;l S<&FytF7qڏƻ 9Y+ɷe9 -^y9=i($buaiP]KnNpg r^ue׿Ī~mťɒfáHh?Lhqu m[u8"BAjT* @ef,n.2N/O_i.CٗVjYc}t衳E..K1Bt:rޅ)/"vfKpp]G~nt'gorge߅ܭRٸdV?DhY.L}oګl]9旽ՊU?s~e߅{)y}諕>n-m$]# 7OT+ٙXp%Yc?7?6X_.˺jViR'mqrN P]?Zh 6DWhw9*MWyb2"Uti9m*|-cx!u{[`~(aY↊b'`b} oŸ)sS^u`R$|GJso";/ E9gaO73ӜEihֺM7??O.,_? >?'/s@ҿwɍ[3bg>?Ü}gS}8@ycSq'sZ} M /䴊'YR8<$TbO/X&M .c`KC/YUfU%nO/F$Ui[+k'e眍LwZHuq4bO/F$Uh=GV -`'@N>W"ԗwety=t<[mEթRzcdžoV._ y1' ܕ|<#TshaU |x-}g$2S9qbO/F$UgIRӴ,[wޫCD7^NJf# xI6yqc4{}TWQ$N,2|݅C d­cht=!d.z3|C`Hpzgu{_2ʷ^ymgnE8ɗ@F;CQPz{ztZK˫@^hAC5`׉b誠0FCϦGlm!֚ծ4M-gg_<`'3{=-Ŧ$*yk1mEN9z||3wi* ~AFݟ:=>Pz=E^!W%Rt{3NGNq~(-\x{EPdĒ! F@$cZ0.ߋ?缿{}TPAK&+?Wetjɋ]Ѫ:CNWbQNO$5tO/F$TB4v02 |ly``sG|/et"dH~ bxﻩ`md4g [x{}Q?缿oXۛxōͰyc qLԟa;(ğ_I= ߡUukX4㳶똇Pi]1'ڕ BϚ8nIો>3kh-j9VII !m#p|133x JoI<m? ѰcpvHcrOy$M]KVxV2F[wNj{ou}SI mf;FFݤ۱[M"-2bK C*'$d:Kġ/u!#|(kIi̮rbNNv'dmO,wZK6[p[sw+)cқsYK^Zycop;#ho˘USkH)+$d&mZUmCKXa;qmL--VP76 q]#5uԠM$HH,G8hw8].B캛#>UdV߅$~f*啥a,? O\b[GGd~&z 1*_a8}~y,cfL9$p8zki ^M2[v;qߠɠ pk̈́M|$!>|`oϠt֞6gk}[Kʨ$vn/#-}I.b}Os֞^G v`8P{2rpj5z֓r-X$dGdE8L%4[0gb pK`cJ ȎWm0jUŴr/'b²FyhS|k*jZ]‰EY x;!heX|[4!_fۅ >=(ŵ#<鯬F Ns9LjvWr#z@%C)ˣ,c -nLs5_[I;jo{0( J1zI{[td|= <㊖<]}}{bES/=8P.vܜsUu= ^EʚQ^ǤͿ48/ _ "B-. zzT>)5]&KgXbs,[ch=x$veUB>XL]$q1icxS>F@cu:&>>SL tLQEbli*>'TQ@d_ʏ迕Pi"$y m#D   zT4_sDEލ$f'8YJNVR1qOu}djr0czdٳo8< ($w*)9!rin?E2'#:;cϗ3#(so3dΐ]H6i%q Ey200HґdbK`rsL*(RqӁׁRɎ2G'E*ιg `X #9IUFևw8V8N3֊(Eܡ*S8{p?*Qw0qzQ@ s4 ۀIrz_\F5`aP~W:ޛJa̙۸(workbench-1.1.1/src/Resources/HelpFiles/Overlay_Toolbox/Connectivity/000077500000000000000000000000001255417355300257275ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Overlay_Toolbox/Connectivity/Connectivity.html000077500000000000000000000064301255417355300313010ustar00rootroot00000000000000 Connectivity Connectivity
The Connectivity tab in the
Overlay Toolbox contains a listing of loaded Connectivity and Fiber Orientation files and links.

URL links (loaded using Open Location... in File Menu) to remotely stored files are often used to access large (>30 GB) connectivity matrix files, loading one row of the matrix at a time as needed, rather than having to locally store and load the whole 90,000 x 90,000 matrix into memory.
  • On checkboxes toggle on/off data loading from the listed connectivity file for identified brainordinates (selected by left clicking on surfaces or volumes). If checked, every time a brainordinate is selected, data will be loaded into memory from that file for that brainordinate, regardless of whether it is displayed in any viewing tab. For best performance, toggle off connectivity loading for files not currently in use.
  • Copy copies the data from the currently loaded matrix row (for the currently selected brainordinate) and creates a new *.dscalar file containing this map data. The new file(s) created this way will appear and can be saved (and renamed, if desired) in the Save/Manage Files Dialog.
  • Connectivity File lists links or paths to loaded dense or parcellated connectivity matrix files. For dMRI-based tractography connectivity (trajectory or *.traj*) files, an associated Fiber Orientation File must also be loaded for display and is listed to the right.


workbench-1.1.1/src/Resources/HelpFiles/Overlay_Toolbox/Connectivity/Connectivity.png000066400000000000000000001547761255417355300311370ustar00rootroot00000000000000PNG  IHDR737iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:q7|ot҅999UQW(1ǧلئB!U]Gmۺ (n&/zc3jM@{CzI.a/4f1yTfr}#>~w5 9ulT*njcooԩS^^^bbX,Jٍc,',,ٰC5UB!'ϛdSQLo|umgZ1sVeeoz.N9*h j4pٖ./v`7o6i!ubH$*++y睍7v7y###D"F]ٰ0?#\~?k,!B!ORvxԼx??b_[Q{l#hT?ozMq*d3VY9GL;8NYodu?_KnDGI֑9K3 sc:W`>Q(3+>o'GY\}ꮁIz'omL4h4AߜڥùgztRdWW];~$ۉC_YO ۇZCJMY?%jw7Yf9h4 ޵iS.[iO4}{}ߝV-+Kԗ=pldž3(5vb.A?nuPh4Ms{\Xr5r+.M(exi <.I H}SL`cgg;}pLB|ڌWnv! |mF,1ӆvSQQQMM]nnٳl߾ȑ#jɩ>Һk4UB!'ȸ>}7\R}>(bµr 5 *`'S*ǎ h`q\5`n&цfѤЪ5;z!8c=Cs±H?a6EJ9Q*PjspGB!WS]U v7i37C!GT={H.3 ROOHBjWF KD)Mpϐ qֶ_~"iw3g<>B!~wH늄B! :u_ P(ՒyFR8>{6:ST,kS+(Nze}NS#@ ش6mڎSN 2'+B!h4JVK(e tc?)`Jo4<5FFҖ-[ yoB!QFFFVVVj8{^id7ݐK_ߔE*ډlȕ`x]|øs'4jj;_!7Է#nQw?qgii)Jjy!BȳF `o־ٙ7;<F4--RSǑc)Y7eT76&>|j F8!G~rWқ4iS'wbkL"uޓ? ER?EW /8p<6dY)ukyho}Y<9Xu+KU1eNwyuJ;|eFNbLy*cK mMQ=QQT:w ͖Kix9K8.//!iB!YQ^^~uFS8 ~*ʈ(rNGHh4kQ C_ݾsg/^;ƭPBd$p\:wFn/Jq^k?j5Ts:OϬMqw6m]s-/>ֿu˙Qs5v"_(x?\?9 1@]1T1Ԝ nicc Ju+~*8 W K<ucݳ3d;7}5Y5з2 moq<%,Mg F4O Pظl9,6\<|_&+:;^ϛ4D"yʞ2+B!4 HjjjBF1xG TJJMa⴨_wxܝ%2Nmoخ2.G/L!sVNZ`a̰hݐ%|/MYSRZUgK!rյ@ T*Z N83ʯ&;r9z3n_qC'sD.Cm(̽vg8n *)LWh;j3J߷ (2=~"CJ= y WM0z)ܣ?dc=D 6i5`WaO2gG0ǥ|6bd?-_ p}뇝u|/ܿrgvowŝA~׿0'|`_fϟo_8*!qŚ3+?$~ysG8Pen{}YxgC^õlx}w?{<3pbO%R{ `DO1FR+ΧZT*oTuwٛg;w P]ctf!]% Ջ>zٽߜexfR71qziPʭrtyΈ)]7J'ph;RRͷ;}pz9oC%nټ`KsTv~AG:ڷ?[T});\ U > ; Q,X~Ơpx?QvcMĹ|"qeF]]p̹F8@Yv6{{ŧŞt7iS͍RmDnPpFQ%}T]͒cE\t]x/96KeT.kJ9o0tr3*^j*[*sr:`^{XPVuܬ\Yz_wwW)l\ՏR3Fy\PUY,vIV%/v97PT*qATZga>r(d\'BObAoOoM/+UuD'/Tcol ./!Ç?{K FɧUd?^uA)>}F ڻJ،% TN|le/}[Vy;-[!q )/@KBpNÔ_78@>wp0gxZJþ`&'/d,et9!8yLaE5?߳7.Jxþ8m2 CW@_>(ԯ=hPo~(;iAoۈF*0Fu =l|W|z<-_fφ@[qٹuKG8H!vnEfܸXz.)8V<[FܿqU]eLé٧r֦ìNop2wJ`ay@.B 0Fi}z03̫Րd:Ij w`/**r1ӿo qV0D"ILL4334|汗/_633377NB!w\.翩0&> gAT|fgĐC0lar; >%ÉVU~]6٭J3?b~e :w|Fe0JH 0H{,k)dD6͢"F 0@`˩_-Qڽ*A]sT$"'`jX͚;h,R1+Bw!6b!VbV*Ft cDj ڊ]=MRZ 0kخPյP( z;;J_*JXT'7.L)=hF>hw JH^pj bqЍS|k"c fo #8$yر˲~B!ނba"dg?}aFUdɪ Pjn`YV*fYXN#  Yt V.VUZ4Ŵ8ò4Bfeaضk5k cY"+Y j X`LuxG38Cِ Ip_8q L%UB!C 0B_hPξvoNho+[[2 BP*2j%j/}A>װ®p3B^Uu@7  Bb|%6-y%YPȨzNݽRݻHmcmmmiْO{9i[!!BYuuT* sE+2r̭TYP++-LyAϽ 1u#r襯q;fN!<;I( 8k>sF%v-ye={Hٸѽl4ʛy7M0L}O0deylU #mFyW{tr25Y`Zq#eg\ @$hQ\*bQe F|PAҷw`׳F s[.yS6^* Y~DbQ}'z_.߿l% (t;fѤ3k׿ˤ1j /T81wLT$P(טa"0bH"#Hx,>Q}:Zp" Z.l/3R6+ $>4.eCJaSfY ߐW8!]7.?~B #tꛣW_,t qS IDAT7_y3$f}h]JRL7ݛt*SlƲJhWuԌ`Yn^Jq ە$|ެWf!dze%M7I(`V$ =p^xQd;jS'jaKBj$J9.>O3rlׇ_bfl'طG.CpZzh'Pj2cV*n,q|56r$|o)w,r}!kWO^ >?7Xbm=Η/7#򶯻X,XVj0sW,޷c;HX Ϭ-7Ka~5QSǮ"5px𺹹IϞD[W$v6@FP9y":,6ƩyYfiԶqعmq(unܭcƙD, EzJbb%"}n7v?MƜI%#O34iK.:#?ma/^<0B3h~N΋BH~v^IJD@(0Mݿwo7@{KN8,a~]]c[M!V^^^]]ݥKeucӖz L-MX*+d_8aL378HV$&&iRTYYY- PW޻WP(]TBb"BT5Md"i> xWN͗ጢ{׎-j+FVR'w!20mlT+U"iQWʔȴ˕ղJH/mi [oTT*6uc^K_jEe\JMJHbˤSSyF ͭjQ(4QT*NAݢ"gZo޼)Zx-B! TVV_[{Ւ5[D 纗LcM=8>e[ 4iynv%41o:Ʈ{/Нa3%`j,5==\oH1557555jTkR cb8Ŵmm90~̮&-X*>Y ⦯>l94BsۻH[1X$5jQFiˋ7 V+++6H:+B!ԓJgΜٳgݼumUPX=!_t}O>toaXma4.Ѩ{Or4oJI [j_"'\ѤM4iwhH!Re٪LOOϺ[3eܚX`a m(gi43}.H9yLrj]KK<ySߧObuB!q F޽sI$/fsPjh)p zPTׯ_tҜ9s߻нm!Bh47o]% o|ڹsI&u\B!BuK!B!"!B!}4W$B!抄B!BisΧB!B!\_+B!BGsEB!B!hH!B!D !B!裹"!B!}4W$B!抄B!B\B!B>+B!BGsEB!B!hH!B!D !B!裹"Wz B!-HOMͭ`N犲ܤ/ySBx)^g/9VhsgƳӓ׼ﲬ-w JR 4{IRJ#zoaB!ůfwAyw#ѫCj)RQzDmQ0rm+Vo0umÆ #f'oOl?wUtq c6v)ОsR$.S7Hk[ZEm#YK6:{keB!䉫Q6x0~nۿ˺cǎu2(mD;/4[2%d[|/kk`p]߹6E2CytJ` .D_A۽ /ߍv[5e I Dm#[fRn ]Q^c6 )K? X(W;;Q+\ e(/1!s*G/HeKfOS=!iC&DYmG-:g oWd˫"8nvdg;;!^6S=Ѯ{_Nu=8U,1? :Oߋ!z4,`Lw }痜ΌB!'Nls0`?iA?=:*`>pl$-𼵁^Cmtz̚j ߸pQCtCVMe0x}M]|{WKk6gǹwgNԞ]GG 7mO@&7i<ђt m>\d(Vj'X,^٭%  p}MxmO<]Q2+J^s5+q)Y?(6P _d}?+"$lA]_׸EokF;>=;O!BI;fJK׺EsuSH۬. Mof2Ƀj"x`? J),-MJ ]qzH xv0}\r>]LkWX -Epx}3n/Ԩ%>q1T[wt}A JdK+L]Fg{VLVE.,En*Ɋ7-Yπ>6`1!K쏆A֓S&>>U,Ufe( q \mI%Ik Ya;Kd W׸Ɇ7dO㒱q^;O!B=G˽g/-*SnӉf|VPP( ^ǃK~EAHNϪhubw ԨÁaoAP(nqa3{ڻ.Zb\"+RY'!|ktc>A :ZK q^7;7+,7(&}%R :ԫ}/J1vd7}?oa =̦)G}wt^^ GaM2gkp-Lt8}!5od_Pb6oo6>F $N]ewG[[[w9aI9k5V^Dflp{ ]r}bl!ǿ qg"a;R=,wq;- JV\" dنJVR"SҦAyBkiYȫHb㗒 Tڪ-8=|ҏ[}[6 msd2JL%n>lyYA^uB!<; Dª2`RUQ!cYi+~1Dj`ҍ`U Bbj2Bm;^ִSD6 NPܿkL1V$^d~󵲁 Ĵۛ-B!<g mb汖6m[Ӗnou54j0s27خab^Ml]^vxzV/_C[MB!Bp{P !B!6B!BhH!B!DS?ꫯrH$biwB!8S*=zx!_O6se٧ӴJںu+wvZ\%B!FS^^{A֞΄ZR8]WS ͬPԥ ‡> B!B%Z]]]{A\QT.[ݻ{nMwM)Ow>q0u N>j.@ 4W$B!P'wܙ"CCC ʲOk̐B!BՖbQe|wE Pw_-M=%t{_ !B!<@[抟MqIDcTQ k<ۥ?kYKcdӫ̜ VS*I9_ϫ)Pvrヮ<-eVO[Jq^/#rb%T9 G,$z#ZbY/T57ޭ)IÇ՘?r.tu[w̃KmIΦ0b֞v'"0 L7I>ڞ#Zܜp݊9#Fn&y655 Z+'5!:a|€Ӷ>z/_p}yWվo,.KlJPI ^9~/ɵe9Ik/$>_ vy`@ /duDl`PIZZ/0diTRc?L{BB:GI)/`GT!n:>>@N|SR΃(IXP[w`tZΓKzHޯ?~!.5hYM15>ՁIɌ?ue.Ů׎ԢFU=hrB̜&\oo\]ۙ襁~+Z \''7=;oDM&SOjq@u$mƪ! Թx5I=5 XReo 9M*?Qu ͑`eHJrU-SݒoTwk"nRj3H%+ BA-PȲP( 'lߡ ή=:ڞsԥG7E!.GbD(Y_N0JPݒ޵cɮ ,0kx9Fl([y6 9Q_'aJ?cW*kgTc%|F玿ʌPhמ qQe\xp˦Cfeyb"**72xSכ9XY)Ƕ~;)'fݟE ǷD^];ϝl&E[R&v&-V<Γ9ǡ! b Tr9+ϞV_g y}돹3}OXUp<b1U!˓gڵ/~$xɚܡG&@і@Q cϜ?Cr9c#͍<-?}KB|lΧ^=F͜''~ }?[.:2\O=cȸdu_}هŻӗ:M^q>O[(l 1۔sp|ÿ97"mn}qO Xr벘핆$"#h/%XM;uO?}gc1%dff ܃S9CmH@3BT߮zOWhξHgeJ ҧ ~K[OE]wlOM˷@f>T9-ʏF;q7 9+ŐO䫸ұ|:shh hHF`ign}ܻw@ۥKʎ⸡~*"#70L>ŅU+u Vb>Wg)0vZXXXXX,V0 S}%zV`xP8( F IDATssssKҔYR3 0J,6JӬ70"q[ 3ba0s&$n™&0M3CܼWGG0uw;jD.E1'dGDN7AP~va&Rgyr?F$17,a]s:~5#DhZGW5x/M Ϗv Du إތؘ[/+[ 6^ݰ>@#KBi?-]{vO2Cܼǯ{d:ں6%n~U{p9|FxLJ+7mJvDxnqcTY@m//p (ivSK@Df9Tyfu.E͏=23|ۏe,j[ v{`֎ ؒVJ`g]uo{'i kKW g=:Oj_k]w-;Ң$Pn!~T0&57h.LL)TnaPY!>0zVa2m`xS&S{~]kDD{*3r5 ){%g dMU["H!iPrKO3 eӀ,=DhOѪ6F)l}hrUeO厸(iCАm->L< hmwRG8/'28a2S:#Exzjjw(o1M^9Fi Q{l , !cmv[Zy]_Zeg;b/<L!,ܷ>@jNBXTaM"LYxXHbCB׮BaD(3〧Zpbnp1I}0 ;m?.Ȭm^^#3&%݃7e>z&{yb"iU!):o0x@!i 5rB/fze$,%s Z_q'93*m]-Pp83q8E8Ο8ILaŲ%_ǫ>+1#=xEw_O^,eb=|'ooLU)nO(:ذ8!K--ZL kzᶿ+z z?zhC|bMaɇ)?nՠ8:Yu uZNigc;$7`:d-:aw[ HdG2 JMGV,wiPj8̫6: 3$w?`UT$|z-9( [Zw' [ @B-qKe:9Y >%s3M\gʚʳ~֖xkEt yTy1'Js@).)j~ "S 45Y?wm&Um+J;ٻ*: ?T"?_ p! s+Qw!REα޵K0z)D0zzeĊyK}ˉM{y?~s:䎨x)z.Kf6/r88,8ྟf"\x%ӛ99;c[b `{g4m5WgCB#Sc*$$@T$۟J7Rd}뭿fذĚmBH;`o s\ \t*7[[J^e Dm!۫O)^Vz~A;=J'\gl h@[f A/IMks02)š,DSq]eKW9n=3MK9 .zW3?[EdV|Dmf=`@1CSWIhg"Qh]w,@ 12kI x.vE)"i<+^1Dsuёc^`QyDi8`xFem .wC1΢gv.+F^aPW۞qgWĉUc{cC=j2VV\.+e_ၪQ(oR*` J1LN07ƨP*e@a Gde!;CّiVd U76PbUBf%1eQҚ$R>qMh.nk][n<̃bN: 9guh!i D>VO%12 >D1 Y V*&ʨSñLAu"/CR"Pj8P7(ͫ9^tDYp-9A!~!eK-$+\)ޤ- B! LY‹ٷuc ?v᥇]33o/'oK{=<9"͎\ wRNNs|C5?.eq>>րM{\:nUJN0ȒrecK|GnLN8o V >t;pp}8 ņLOGGyɀr$u2h41 {>~KmDiNOXG*Ěܤ_8rvfϫ[ bYިyEhP=ⲌNEr|p.ur6G3gUaW"NOO<|ߖ~IkQTWN)Oa6UTtH']NcN˧ NI\EL9}M ɉ[ܺ6: J"VVӰA,{on{e*@az P(|By}_su_ʂ[Dɉ[Er9?Aܺj㊏ ~ȇ 7U NnG8@i]<*L 4D ’~Uy +{/P,עT@{zEޏš6(&PVXu[, Ok)FMzX-P5dՁJ!k-1 7n͜Ȧ?ȫk% I%wE)XZKKK[KS?+% *nqpuoЭTHm ξi=-BZ!Ҕo/T=u?` sG[ܚ>PDYp-MA`e_g]e>~W/`-x=\"D g8ܰmem^Ń XGū|̯<p8w~(5('Ld54=Im񑁲$u=yE>QJخ[̺FQdRpO/{ܱGF!SV3n,Y_o549]zcb0SF8ޏMoVLB]`8fa G A^ޮ&1Pm=Jf^Cƅܘ=I5=EKMkQif[~$ .9; c9k4zkl ypǺ2#* 3J˲֥Ña^[5 0%oR OE 2C/ag.!m(~#y>#I^\v@/Y烼 (kKy"MiRIc$~OC5W~y@n\jYmms,zwM r0ܸ9k;r(,/ܑj5̽Tz;LS `mrrUy ?K^+08fR!pdQP]lE!4IuYS2q` ̓i%(Mm_mf~ ( 7Zɓ'/MOv]eI',_*×/_rʕ+W&&&&&&҅/bx .\ErQ."ةK.:S|Çt:dEU !P[zbXFTUVAIUcDKuWX&.+kDzbX&dkºbk*lOY,E%% i2LKm$u ŀZW&D#QqbT*ZqbX&"zgܩcu[^A\~B-DU @\7aX 6UR!HLҳ5Iu#V)0q exv EW͇[,*lf ,N uJ D^ЄYVU(M"V1,IiSLB!ZB}AxcT0u*Ҍ[,i z-X1fcmR[V'N (v`\IJq7%Z2['X&IkQj[yt%Aʘlc iUaK63$u96!Un%{b1 8C24AϼSW@^ (YFҤ>{U7u}k'w.+} A&TǜjL 4̶/_̥tX_ĵyU&W&v.&uCKM8詄.*uN \(#hդH:0X+bO9 bXLUf!ls,'t7ydt󜢳`mj}||N>yffŗM)?@_bP;>Wg;OpތLJVleE-|Hοq;{333?`jjd IDATJ <4L"(ĖuEIyzbl0S$|Z}Uh>8]|5dƛIøOP 3iԭL93SI@I{Egv0 㾾|ƆIø !!f OU"a`p)r 8ع`\ 3)s|:63ޭK{g\M[imґ޽**QϧONn}xNF?rB!MVIN.orf-4. 0kȠLY3\ib^Ȍrse~ =}tg0:xA4{0tdEVl"⅄^nq]ۨN5 7Ü4bi_L]*pfR3  W-(F&q_ʶԴ6?`>c;dt#W$=u=_ 3i0PYj6<>nܜB>+Ipq烴gj-Zh"6Vd ,X`qm̂wo%V<~J{grߗ鳑! ,X` ,X̉ku__.*{z.&2ȳk~v^j۹2n)L1be,X` ,X` %V,>|s/$߇hѢEPx-&y(S -oɢM^61z^R̭*.cZlTظ3po3zTg7ą|?RYiKW7Eĥl] ,XlnZiܰp`ĂAgffkӝ_aWu/M;?ߞc|s7:3S.^x SΧ$SyyB񍡯Ψ'/Zތǘ^hcnvXK:? 2WZeOeKko;7&h{E~>hr@heo9%7i?־0aQsӷʌjk_ǍJaDcYZ]OO~?I**O^}׫I ʬQL|.Wtg]_$XԩV*R& waj +մqäRyM7:)/M(7M6` H*_6rQQn['L+G]HLJm{AyxqJjՏ&U} :W^q~oH*TjUDY=fվVIťd*51s{i SVW(YlÎ~U'S7TT,򢜘UWk}1F1 ]]UST51jUy~]Z]-}}]y;3rB^ˉ5ry7ճ ֶm/Q?jdx*tjWk}'ٜ̓н{H(=^[L) &4wԜSEI~|(ٝ"P<~j|zUV9}-Q)=)Xpǂ333f_L_}~//q@EKoZ!_/L̘|bEk漜hX8Niʿ.{]5̓W<{Z\ t~`Z6t.T"l6~~ZVNz0rr$z0|§7d>9|rX[F~pk<`(.L}xXnӘaϛ,?op6 99h| 2œg qt5ٹkyc3~;6cYgɝf齥; ^a6ݖoE"ȷJ@+M&ҡcY R ѱ;v#x?~/"x3R_[CS ӑ\qxdEY%H Qѝ![W*o=[ZVe_Y?U%oOO)`XY@a&n |H۝NVx qq0( Zl$9}w־ʄUm}ڻPnnORB:h@d8`@Een¥6V@[k)ZrTP^–GC]%oyVyzf0Rpx+ׄ/_:YdadtrMrewg_,M]ZyKp_V8΅.]t҇I3uQJkݳK,Y$(JTvap8Wrv~MC'hr"+۵49r3#9|6EjNv PȄ|# eԙZ3>AS#I.ǹ~4lr86傱|ZZlH60[}z3JۜqB6fGfT67KpUjI]mzFsWu=$ݖј_ZaKhwKw^{vi#*[[˶~2^`ДꎬStךwh>tݵ ̤u2۞]jOȮn͏p"Idke˒Ȅzqڷ͖ΓQq<~-y̞?rg=]M aFg##xHIpy%2G (c8$и3ag١뮵7QlNBl~dO6'VCdmek9`f}rUKHg֢lғVe_oKl̶̏fGZWUG^x ??ߎI Dָ.x[bCb絞 =7ye5^`/Vk-6y&3s >?!Duxwt#ݛ%K+PthSCU:=#Q,g|en+pςs"_h*j>mm9?Tg@^z N MiL(X̘{|ܧÓ|Yԏ⛟; ùzא}[U)h\ eAZUTMzMܑ!#*UZVET0=QlODU/RfZ\meu4,{j=yJd̆f@m{+YS4X7׹]UPTen|MRU(nFiUgHYN|r^f{kzTMK?֢j :*I y#CT}}UenяYI)'\v_кP_[}Aׯ~oxX$-ϊqe,7nk-,W U'i).s xm>_*.u^)Jc~G$h]n]-CC}-ݹJƿPe]y6ݕ)5&zFF9:=;޹Yu8TPw-E2F=lAL~U=;ҋ }CCζ-0l6){T_[[[[[J$l4ࡄ5`r]+v̠]Ger eEÍ'+s I9 bnG<@LGYյt։70.s@iZAq駞pPSC(S@Ìȿ55 ,`A&^Aԩv9.g6% >Hp8Ȍ~G L4G-mVݝ$ڿY64y<Kv-D9g*HY-cmiu}Uk {T"H^?0eP*r9Y a]S5Ȋ0fmOa9 Gv _*> uCQP[Ό^ka<@z7'ÀO*BSS y]zn`GO̘Fd4v:ݔHZ |0o_r1 $:?_E2uK "H&PiBo6ȊΕ6 J yU¦SR ;|pƀS5*jO/%g@aKPaakt]H^=#@^%.~ɑL2\'5'KG~B Ԩ P ּ3g߮DC) q#{NQ)# ?U {S|xM$por֭|)#Pb9 `kTm<;F.d30y!OHN/S( $l%<J2I~.ikֆÓw(ZJf$!flr,_{4]NHU4 es;jࣙ|iOZuVDeDA@6fVpP RSMTʼ؆smFGy˜zA t5vhּzX@0.*$^ eNzqR @ԷKt" mػUd{ee~EmNgsCC4\b6 !> :GZl}j.s ûDz_l BT!=tZ.S_` WO8ǨI>  @L!-v[7 %n?\Fu?rΓ7(TO-au:v2-ˬ;Gj75X%/nI A5&?H (sO4ij(ٖtȩD J rsv-Spʃ[Ξ08">S* Hˁ遻Fg% Mn'C󙫝izT"neݴ6^O?("B15Z wUt]&a3$d#2w5_DfGdh;; o6K jZ34:ui}})p];]UOuRp#'wnW-̜&ȳ"UM+`0W9 gJ䐪v،3 hT6"~7=,z&yiGxj^Ȭ]T՝ic]w:)# yK^kcT8+1 IDATQ8JKQV!d:dBg{?8Dm ؆WYXUOgDFN kfG[; ~x*'Oh W l@)U !ĔDqKk:#RWP>qMh.nk][n<̃bN: 9guh!i D>VO%12 >D1 Y V*&ʨSñ̂ ;%5 ?$/lIಥde؝6ݛ%!|^?$D2Kx"{,D.+>pf&?WMmqޓ7r8so׾6GpKޟS_ix(g8u+.9٦K X&:eyco O;Gؽ4E㢌#nuT09iӀBg JѩCYRNwb=/֍8 mOr9 SXDf/*3M ^R!CS)w3! &7W=*;~y9ւa7jzxG-.83TϮ,dv!l n_(ܟ q\xYUؕHoSO+߷_ZUm`zSes؆M/.f}e+iSų~mn+W@aSNpSBrb9ExNҴ~ꆕUhlf4n*K^[+n'|iGŷxu#`q kxD( iGޡ@@t<$`;ֱQrh@m \{ήOZ7BM3C/+ۃ1Pl7|yI 4D ’~Uy +{/P,עT@{zEޏš6(&PVXu[, Ok)FMzX-P5dՁJ!k-1 7n͜Ȧ?ȫk% I%wE)XZKKK[KS?+% *nqpuoЭTHm ξi=~ W\K2wSP 'Xƽig-sW,i` $%jp ?y}!n77l)sYWhÃklQ*>974τr8g_y89 u J ,Y MGORf{v|dl3` F#I]Oy^Onv5k3/q5٧C,\>K.w߹Q jKV9[ MNamX" CwhnSi۳"k=SvP.2Yrx2}`QB(){Рuk)Iz (?Te(mO}fĜciH0)ZnZ͏J5k$itiO__ѓ^gsɃ=֕yPYQR]].\3ڮAOw?-!pc/Vx*-ʝ\0-z ;u 1lF@qSIpLG x:%eU@YS@E^ ȫiJJ{ $wu{"rRjkmȝfQ[Kn f8)yP~vqߦR#vU Oϭn=]Z&ѻ-t=Bȳ2*{z4{Kk/}$qP-j"+u5X: sͽ݄Y\]ح K !Oߜ](} 6&ҍ 7K!nسGwرGw~ASC"D\"I+wHRv5Wf;}WpzΏ4k(O^֬#Iw*–V(V_ hZ:KJ >(RH.@L84 j-i=@+~BS}fpnMNNLLNNNLL i[r BB_o6Z_*!_`ʗeZLjZN({K.i@iBX8]¦yaxx8ɎW?,=iqW当{Þ5>|W\rW\0_ __p%{ W\0TՅ^;uɥBg*/_>|Ng2*!j}21 VOX,ˈ*(jbh)!T˄eeSY,DqMXX7bX,qM)b$M&PP9r^Y_DHba$*nULJR+NY,˄ZTZ;5pNbK˓5HםOŀZ~A&,x_FJ*ɀQz&|R`▱Xo+{ZLͷXU|-VYY8" OP$$QaEHCc YҦ>U RC^ƨ0C'aT*PX,@ [[hcb) #&ڤ.%*(NQ<, 'enJ0e"O,$Mm֢B[J)z1@SҪbÖQ9nnEs@n1z5PmbtC#PcgbG \˝d2t $If}nM?yԩ^,88xΝЕθ:5@Hd&@wq9I,]Y;-;Lˌ+Q#$>NIt8w|?T19aosy6:\?sZH?zV!|;F}-dg݆)!_ ^j6o÷dL_\b{urpt3o 3v'^}8K|KW\axދ7!݅w6q~OLf[9_W\XeeEPUp8-9UT٠5fgCu$'&mSrr֋sNW?m`f8+ގ؈.Q3MlSjTTqI=|ehVf&չY&]{VBr]t {ɹ E9<|e)lqʭj.JH.2Q`M/[\sN Eɇ5NҬu%0^VY E60qG?(f073]~S[g4KI|;JgKo<^Ӕkܼ*؏"øAmo.JHt^u[ϑ7%OW_VVZrQC?ɇ8!borLɜu|Dmu&!fwo6%8۪MiH8bk?4~jKnw%;\S Szq˥at13/N**I{sE______???____߱[<]~w6lH\0-\H_a8 mx/~PGJo}C MZUNucUYPWMi:^rO Xu>c=`kaAh.K߱eY`S0i*۵eWWXp=x! bbt}?U([G}T+uAA y1k )qK` \1>1XP2CќڞLǭJ}Q71)m}=%?d5/mͅABKJeu#+},"[ڲpUF{0ҋ Mo[pK a[v|S柭5sUptr`smHQRd+vasYZXpFuwgcE^m1[RΞƒuqJ'']\6"ܧPtX+{t(y;a6(znkZvhYWF?F' rd@dϭE@RYC"wneN{鉿.\٧yOOa'Fœ/ u"@pꉅ .\@ q.)妪$֞,NZ|,O}ɓ㱶id"`BP^3iKm zuYi*"Rf Ud;#3MEP(彬tvB*&]i~>obj.I:+6Z^. I8ܠM&ԥ٫#roeD Pm 8y@Wq@(LM P(HO${gP9 Zl;,":+d*ۭ=&7Be[A7Qi-Plnu1 @ IDAT3Ζ\u<"scBPQfͰ!V[gyt "*5LouVľj{kq괈JK# օެJ7ha"yZdeWأe2tyL؊:/&;42DYսzK]ϙ.O\Aox(,aaU/,|V5.m L3@M d}g|^v&O;|89"Zԟ!Bd$>$u1j9>n?Odr]?x#r(~PȏvHl⤊Rwse?73ANӕ5=̚Z'$t8Nm0ȶwBf~["kҦks5q.+" !6"62uiiսvmajdܵJY fYV^U8TO,P9`s^먌D_yt觕{]SKM;PWojElY-MN-<݅]Nr\Q(1e!KGFTWטԥ)}EğϽGws/=_gGǾh@4:o?始}/>S[͑tJ 8OMؒߴ~7y;ѵ*gq&Ntg@g\Y𳺳lT)6U4yKƅ'e+Jk5? )F 贈JwǤwT0;6.eq4^*N9$I5xΛICo҈iVݷVƔ**>.)hV&'F,^@Us[m<]n0*ҕ}w$Rbv\jlJZk^qŜ┘>]6_dK,:寫Ã݅(( oj `V(5:Lݗ5ÂEPeH%JQqXqtcmGٹ|sFq(_{wbMUs~c+ >RvXsrnnINY'rw=`x\\,GliijjjjjiQ>3tVPO4#`vSQa ,'ƫ 釕@(D$zxɛnQ-.sps:Fuy,~L >WV?&A²&T2"  Z|xGpDZ]۽gkܳOCn9.e*;1%My}eR H*WP+Zj]]'.l.Jڢem͒{.^_ph=J)qƊT8:nFKndT85 zb [ =SW0&;/?Vm"ѼP t78[15"w?;NPG|&/@+D+:Meŏ_푅BHJxI S?}ro~/yA0~GITa2Pi0Gn, ~[dԊGmJ U,=xhOūQ1 V8ySdQSD{P͌tP}`tExԁ*c.tg4ȏdJlg(eC">T,>8uӶL0^n$pqd4<\m[HEm͔$HQ:G޼I lwMׂEP' K7C^|5 y[7Ig*RdmՅ2UNڝ}o{(̀OBuE1m~UdK(]9Pt÷iy(Gaʣ~l3i8)jӁq"" /w0'RQv(у?A1' PԪM'[,Ȗ.s%_;eXrƨ(i]r%R~_K֭:eĆ 8.[Yѹ;%Ɉ+7Y;rb'?|~MeڲX䩿=<2ӸNq6MedmEmD~p`#EEEJ$7 ~WPYIv1)O6zD)2">{0Sd>IF^"*T,^[o' [#{m%qT:ՏEEIsK񒤔VdG17 'bG~ _qE+4|v8 A/{o-CM-Qܳm2 ;&m=]$W(s J$PcµӖi2;ǎX7MNx߼9ͨ/|+3p=;Pz{xNMx7·8{sXdm6)pzfwRYet`kk6oEG.U5gc%MiʜֽciN7s4\ݡ$#z piLJqasMxOЌzE+E^6zdkxtn܇Y y?B\ D / _#uG] , _ y *]#r)gᨏ1gx ď fFAO piOA3P]!Sdo\Xk(v {k#"N4 Ry<PZlʍub80uH `Gu&<¢Pv3le;[1=^m$ /T+4`MY+ @$laG=%(7:ƌYֳA h.(d9u`o}zPnYr岕1)}BoiݻJʠi=nMBݜ/-~Q֛ao\)Z ePcފK_G% eP֟vV¤IG4Eߒ_F(=H*d>%ӧ[[[[[[O-@Zrs`xtGoB8(4'g@[_6GC˟5[s+:Z9ĆDe#Lx7N+k^BQK8n;m/LҔvb:_.Pv C^=Gl-zޅ-<+;W?tG lO_;6.[le AB H\ӛm7_̤m`=Ɓw W<u71(e ;.,z81ʖڮrv3j=yL\#4&;ˡޑ^đ)%[ 9K8.T<&f9i `v# 3|_ |lo.U `"9s÷[vpF )UGM)JȊ2yqwޓƞLW s毷ٶ"v0~x bބ!4b`p$_"\xzo߿!-O, Eq$P(Kh܏<ʏħ|be63*.U.v>*P o,Ë~SxV@XԞVqV*~O|anȎVlXd>fm_ Hs-6'Bٽ aF"\mxr~o[ iS陎C3) [69k|(Y^ [7o.^'NBz n{%MN^^YXND(&UԘUdr_-QnGy޴liΞ5N7 C(/re 1nΣ('/n|DQ 5~ /u{k+![1FNJw{WlU!̙Sy{GPX衃XʷnbZ(4,|kUc>KuֵbӞP7TH}Qb}4.Ll[3qzٟ{ p{D)HQ){1q294ug^tJ V>)dOVȀo,ޖI=b5 /<k>s HŞ@Shbθbzzq"W ڊ?M2;1CT EnAG"ڜ{Qc2xݸyI}mTž^O H[E2/wLe+ $bT3|VIPXе?:eht&nRK49`b;ܳ<{OT.mQy{wkkS[)> wֲU(_U]ޮ'hzK+<\7aexDtWp`Dł)4@%w- ]* +D|I&o(LI ZxT3hHH󔇖Ȥ~]mxd2XО/^$L w{x'$YD]uSnB3, p}Tm)MTAg<I3K䚂- īPePՔ| bzT $⒤j҄^=膴 i tOTKk`tG1=o&u*:Ki5--&`'C f2&QfUWo l ڮ=*qZ.>(OȪ1~Cg3@.cSQxdJv=~`T:JHwhKJ TJd9-TԊBv< ~v$AHxC|9>Vko[%O~=[MƿiC{<Ȯh1^txhd%{%p;|CC1d)Fs⾃BtA֜L2#@?j>+CZ1A9-F+Fǧ:eL]Wůˑݜ I/c?^|"cCav/U7D@D`,CeiuWc0hNtZɌ{Ab$>{ݻw޾}ΑBwju[6ݖHt5@NԹ-ڮ03sp{ prhTNdQܳI޲}A^עљ_ xs FjM`ftva@ޖrjwI ×&Fuچ#J`\5oKZuG^7Ң5xYpCM-c00:~$=:&[YPo{q(h'(߸8xΉ;?۫hu199v3 H?}EM]Vq'hz}+N(pV;88o^`Vi}噉֑ 7aq{cS]c8vصk׮_~뎮^/l[/_t-̥^_4x2'KCT]v16'CCCƱ36[,x(f( +X,Ýe̒$Nb`3\2+*,˰:Ӷ2DtՅR-FzShf-e*GfXXÆHz w3i M'+X,c9M%#e`[ȏwʧ ˟+*u)#,j8}Aud縗ۆ06J5 9N!*ix2/-oEȦwP hԜ,Y%Y[`_}>=! CtU}auG܅aL8'en-rmw"66ޓ)3na!l >z-{fW_ݵk7nfċ{&F?f %"wwu ~( O0cKVU yfׅaBH*qXP0FԽēId'bsTx3A?6P&M0KMzPY4W Ww,D"? <7<21cphdo; ~* U7>r _g- sYjbF  4`6u퇗o=i`oUqM^m( ܖ*NzLЩCz41C#. ߁v#pg0XžFf!)lV- ݐ1nua3hbc׻=`􄒰Яv͡㱂S;[ݯ׏6 zf|KL&N` o!3빢b9Sզrkˌ*#>tr-p{9|PԌ;5d-ok-(W!jnm>^_Sn97y߬B!BCD"|gB!B~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:<S+ʃ7Z%wlNOT6.F&IARD&/sK)]D9gsPFT2d$SI@@${q$rqb;MիׂI$ln3g6I>l\_]ۇBDBa "$2,,<}{"\~Sbs""*CC)smʯn߻Z7=\D)^Z]a^?_0$<^ܺ 80y׿:%"lo9Tkj޶iW*}Y!) 2t`~_% O('Q30x6i18~wev߻m i|8qWV̌hRJy6h\zFhcCܹ _+Rykkb,+SnBSfY~9.^>%ҋw߭VWYccci}gؼtE R@ " u+ܹO "]kuTӊW tQS9X"0 ÕK"hxW/b* d(r*SU0͘iЃQg=d_TWo9 Z.BG/CV{ |vIENDB`workbench-1.1.1/src/Resources/HelpFiles/Overlay_Toolbox/Layers/Colorbar.png000066400000000000000000000140121255417355300267570ustar00rootroot00000000000000PNG  IHDR&(ۙ7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki: Layers Layers
The Layers tab in the Overlay Toolbox contains controls for display of data maps on surfaces or in volumes.
  • By default, 3 layers are loaded into the Layers tab. The top layer of displayed data is listed as the first row, the second layer below that, etc., like a layer cake.
  • Checkboxes under On at the left indicate whether the data listed for that layer is being displayed. When a top layer is unchecked, the layers underneath that layer are visible.
  • The File pulldown sets the file to display on that layer. Only loaded files that can be displayed in the type of view set for the Active Tab will be options. Files can contain one or more brain Maps that may be selected by number or name in the pulldowns to the right.
  • Clicking the button opens the Overlay and Map Settings box that contains several tabs for setting display options for the different types of Layers data: Labels, Layer (volume layer settings), Metadata, Palette, Parcels, Trajectory.
  • Clicking the button toggles on the colorbar for the displayed layer's palette. The values at the top of the colorbar change with each map to match the range of data displayed.
  • Clicking the button shows options for moving, adding and removing layers relative to the layer in which this button was selected.
  • Opacity sets the opacity of the layer. 1.0 is 100% opaque. If the opacity is set to a low value, layers under the selected layer will show through.
  • Yoke sets map yoking between layers to a map yoking group denoted by a Roman numeral. Navigation between maps of layers in the same map yoking group will be yoked (move together). If files with differing numbers of maps are set to the same map yoking group, a warning will appear before yoking is activated. 



workbench-1.1.1/src/Resources/HelpFiles/Overlay_Toolbox/Layers/Overlay_Toolbox_horiz.png000066400000000000000000001706141255417355300315710ustar00rootroot00000000000000PNG  IHDR^wmf7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:gC@ `޽m @ < x=͔/QbMqq]Iqjʏ5p3emko$OOl<PG?^T򤤤~|G233rR<|+WҡDDDوݻw@ 'YG4G6iʥ>NX Ԓ'ei|NMh&V"Ⱥx <"S68g7]3ژ=jɡrrrd2L&h4ӦM~קMh999bJFDD]uڵk׮-u@ s k{(vռ.xgc5!!kKaԧ7 P 97 tuuuutOC~޹SWׁ?EEQ4d*sp{85,[֕ZZZZZV&(<ڨc(=lI&W\5mީQ4TPא5Qst>@ )5iaa͕JeyښaQpi\WW׷zTvZtڸhҒx|MYϢ7\eϰUgGPcogԜUIgX]{Ng*Y4-sٶO\ GZz['׎ bY>F2A pɺĨnv~<7I>c1K.)mI=}>;* ?|wbrWU=s}5;0pѿ+'*@S\ƍߺuykkpggҽ иqQiܪ DDD$&&<_S\@x\}!E|" *<-n q~˫k㸻 hb&BFx"A-[ݸuZ ʾ0ֶSVDc\I~#-.Δ#vj3Mx|P3竡WDG)"{gg]%&lU%mzdNckS|)ҩlJ-nܸ뽽322DEEz{{ݻ-ZU>RjҜ^).@ <.mvbFI}6Ll0laAvv钌"C oj&b1`i0K%EK6Ŭ(Zv<*,SM?ftؽ"2Ȁ"N, !w^vom ^U("8} Q BS6jئMӧODGG۷O&ɓw^sss6mT}H-Q: ##@VVcӲ;?y_`.}Ƿn 49Sĩ kJ޻R֣CCp %^/"#4K AA59oPx8:EwUӏ Ag|3{m啿7/͕}xnWf~7 DA5{#^>tbS"ejMl|]57S.rS6jk.L{nLfaa@ڒĄa___ϩqNZ\\,̤q aHݜaoI h"E6H잮(A c-QlXM¦cQq~gѝ9xǂ}NeQm/& 82⿛cݶ完~2#ck4e~(rH@p~x )ȋihRUI2AߝhiVg ԙ3g8p 77WZ8yw]O@ ݽ{zrKˇlX4$С'-DY&=D󋙩|ܫ m'ۮ;ج.Re>;6Anʲ0+ɛ#(U2wr ᤄ:3~UnC;ksVXdjmZtFʩbiIB0qRe6+(8u kgYnʁvM*^ȲX4X…^qS#3ک@Mk.@+dm\DQik}NHU4@0U'.ja k']Gda [6/@YZJ= ^8'=`&Ǖ4q<sv7n~ow/l{n~|̀nӥp2nNWӱNlNJ_C&hN#>i`n/( t;Fnnk7Z ߵ⡹0ɬž/TX"9[.  ?8<*g_nCw_sMS)`{hNQY"s(-Jmc1?|HX>}ۧ*U56n_/9OFuZNɻ:L:ʮ%A6~\>Im"o{slʊyT,1mJ>ݣc#9 GO@f-rw/W 09DLZU2NF-Glۣ*kcN>kEJժ}N.=vddsS:nqv={8HFý[ 8h.;1cJkkSFF9G5Gv5Fý̫}<@s}疭0GÁs/א_Z.g+Y!-R@oHZ)Myj3GY:kx;ՙآЫ}-ԫ־‘?sNn>9ʾ}z냆/shv@̂þ?fsӐ~SϢ,-%wT\[jn.2CJiFOݧ>bTY[)f,oڹdTi& bV|6аsXD{gҢ@wec[Vs!`sТ=R&n;^.-G#I9:j+Ge7:vo\Q-`WcWaߩ%9wBgh'v`|Ϧ0E늴%p.lU 7S$]=zNzw3̵ZiJBjXqKi޻C+֠Ֆ.6\ۢ|9 }Em!NMŅE:{eԂkt&ϐk4I]>/[ѤG6%;y nn5tg鿓voQe( q X999W^-*zpGD xܿ[Q /BiQϥ[F t8mIN-;&dǝƀ~vrwiJL7ŚPopRV>\J5Rc Mn$-75EG{`xC ͯ'loӫxZa؍K$M{w莫M}|ZI&%W;1YvŵR9Uxsߗ kq:ʯQ]s&L](z7u,`?p`{wXNUܿ~C&vH{g~:eO#rxN8⭁J<Uu4 P70P (+w=u)d\P ԪD'Jhhi,debh G&L4]`எXf<)Sr+RkZ4 72iT+V ZaTao*EлitEiv?#d `oNW6}u_UJL o޳=w4{:(mJ(LLbHLمgZ-KnkRI8x1=PP4Ps/T1Uݝ",޼X4n(c,|P--3 (\%+Dd,E3,׏HL,M2IQ(J_̳]/ _pnԨ˲d&KL&Me e"2)xKCQ! V)|P@MfKvlw~J94;m7 ,DDžjY"@(qIn;zŲ]cz^w6U.V%,1k/{G*c4 4%)bi@ #\ߵ_Hߖ n_ HTYL.7 0!+)@4ܗEQ f ̋vfkDƉ`(]f-N34 aArk{err)@ mM(24P`( CØa4xgRj=~u2akHQ)YIǜO%>,1 m"=}ǟ\y(PZY(p3aA(LrAم;hխIk~#Է~9uCx4`0fI(6D5*-ꚸ7kE-KqA@S+3#Q20с<˜4 @0^kI Z~w ĕ>W Ύ|R""q,EQEאQ\\(dJ91&rFҠ-C(PWl! [J0/ʹ4ЬX@o0-?6t^zwofAJ Цr4jhtUK/&-[PʔJU)ծIws:֫rtҮMQPҶoG%ju~f?{]E)+ yjf24OIU+o]߹[lΩ Zu,4M&q/;9fwaշY1OdlS\[3`M#pGq{ s?tijre]l. ? l]tEk.f *Ps| l50_%Elpz{2)aԩ~NhaXKK%&cY2f ' ߲ b͔3I 9EId"풡T*ƍ~b@x{n6.Z,˰4o<{k`afƂSk4PX3F~OӵiQX(LjXiuX Ymz;bn^5}C+,1f5R˂ՂiwNf6+#G n흔X}TƲSKK;䏌]Up''LX7ʩօ%VR^ݽb,f*K(-mjҢ4>z(F.L@xQ !eFT杠VP*!c!->h@DBZڬۼmFOe +KZ&{+v^k8w}NܷCm.ʲ :S4kG)Obl(4M pرC>keMoV񳳥h 4m/:fW {̬kkx worٺCeG.CxѢ"%MK℗af͚Ĭ7ɫhyƞDgQBl@JqիWϝ;7qDj>᥇,C!pڵ7o  -Ivtt:thӦMGNxy@ y<@ א@ `@ #5$@ )ۼ&::A @x5 ]VZuC @x6:tHA& @ FkH @0B\C@ |7@  ASm @ < eŁ+8Q=k;πԴgm@xykHg'=-)ؔslcpItEwaؐuk[P07d u4IXj$$$x j򴀀ZMx$bfޘ[NraY5{w\84$$4!͹`MxxkXqز$Y5k^BVF{Ʃ6ƤfUvjPߥ7fo܄YHsѱIgk3Yʚ&gl$ii̪?UX:i"E _OObb~qTe%M\s>x)x@Gnz9>,aYf"\[22ztؼ>@׮ASPѸf-L \͡yؼR7~u> %kC܃BC߄9ecD˘Օ}1%yvy9qPwaO Kq(޳6Qޘ;;;;;u5J(9,I@8XL | ;;oYiN5/p_-4;k8sR7JJCf,O 7ayHɔ:MF&3/v%3ЫK Gw,fq̴]J `{X`XYpz~vvGҷE+yQIX.!zC5աJ/2Y 9\ֶq0qtF}auh ]]K1j`yƗmr;1yi=Tct鞒u!fvc-7Z;sK!wv+/t @jưM6 Nޘ3D8;RS:5orۖt x#9M{ :v s|a۬}<+VҪ'GxC!}8; WcXPcYvVER-B7-ҵC)߯ k[[7tP8tXܝy-Z{hݹG֬U 7@ΖQu]gȭZ6wǎ;&%]QmmY I">؅U |}zi g%%/Ez-R$fqW@u@t4*)[ϓz@w36gg C^Koݰ@ *ȯC -@J1fРVP[%׿4鵀UANe!~*5,7QO;% pz(}tXBul@wf |;55]Ss (xqB]݇*۰`:9r]#'i3h5z8M_`mJ}!aj\:nwabDxbft_x*:%¯ѳJ9جYKrDL BЪƻ| ~XGoo/Ge/SPoix fGBB2̚k }nXPOGϪ$]Cs֞@xtjy/&_T@@c}~Xp0.j 3Jc=]c~qX+PyJN)GIbƑxz,˾<$~Ѹuiu֪T Pe(H8,lyyQS'Y?z dY{a]׵C O"3t[^iX5P9ܺcNT=WT> 7 ( YmqX[VZYkG+9K3={MY0U{.`] f̬ʏ`96}|~ 7_ڒ+͐zN^XǁPeaÀYK"|"y}|ֲS~P6,* 'Z>{ >,>sOfrs(!n 8ɥ܆χM_$Z՝ (cLG8xlK½l]u^a^G_sJߣ?* J^eOf gĶ3)y:ښtZ;sRe/<>zBw͎.Sfn@xf%q _j1L @x)P~Ȗ@ (@ sMQԳ@ \PP~:6zߧOCm۶-]6@ < ~}⽰9* }||hIߕĉg_mnn0* jO?-hشұaQJ]?MHB)ᅠ]bHQjcwH?A:q ڵOhvUz}zzzF#""5jTϽu֎;-[6sL33:@ (_T-"^5*In\U/)l1U5z)^|Y͎֟͛7zIGaaa~~۷hbҤI޳TE9::?sllQڀ@xިZH7E 8@Q_~ °,D˗kJRy>==ܹs P(i r"Y6)(Jj߷f4^aRGF:4zj!sMU]iZrOJU*URrrÆE:ujQJɨv6o۶y t2{رU|ͨ2YWWz`6wyС&M 8A8NӕsyOHH#F& A:v24MQRS(B!" 7 ")`Ѡ: 3󌁟e":즪FFν/{f3ϧQ> {3U~ۻ:ti7ov&,ĉot!kR^-q';wbccEQܶm[4h WxQqsMAՕyM9?n&w ylQs&:^ED@ŋg#?<Ҡ(ad9XqU/SSS++<^̰27oZ[[KQ(*H@JJRGIsU7mdcmE@zҵk1qP^m-iR9x89w=yԗ4냤 5;C GEy:/=J;%ȘoB|=sMKg.k}YY6ǃ_nb_]4|3Eqע̆ҘCtΈw@9!l8 ,Y-ʭkogGPwjre9yYIF¢-r+Y5';&̎V _"<ˇez4"A }-]"ݨVȡ,6C?(pߡ)v",y=0?<) RYn_-((JO"':+EEE҂Z4o|$Z- |̺fރPϔ)It9 Iub^5ԦGS꼛^6hjjZ8(|Aԩy;OڜMC ,ջ憞`ٜ{U⽙Y&m`/g[7fֻ3{CD 5WT(os+';϶y?=tk>Wb}E^u6^!+)|,ݼzC~e;}?sV 5cڮ?~3aܝ~yɲ gΜAW<\ t?v]z̦aZ˪ݥ,FI2 /ޛ6-Q}xbZZZÆ QU>G|@jOUyÆ ߎz(;SzMqzZ믿I> d.?PPS܆ɧ6֭v;x!j&&& BeSEQ8NIyx^9>ЏG/ܴsH煬, WWFG.R:::::::9;XZ6wvrttll+D\yIpttttrrWoX+:[XK;JÑ91x'2RQcƎ͕4qٻwe8{8;YOJDF_m/EF~;yo8XiWlRהl]dO\ʁnWG}]W4˳BI^|+?,zR=]~dW]o IDAT=U˳Rxݛw{hYhvjø{(J .XRAJi[,P1r)>I-[͗8{_^ۺC.rF,3ٰiʠZ*7:I[֣Oncߟzar\ʻ(ңdjie(> èj+++tr <3@W|GBԢ-l<I3L,}{ ٴQ> *AѲj墈=1҈5;g`"/Y؏"w`. N*؂ yK+AL:w>^Ϗ>:+B?גT䛷}ޒ%l$ל>9bA1b.#fѢa;ԱEIsWjl(:s3VĔ3O@Ҭn:t׳AuMQaμ:{ p4r"4MW`;f5O6@Ӗh ܰ«zoE|1u|#ts"%w:2q s 쀾"wN4MәtZc~jJ"2݃ `y; O4c-B&}!ېV7 \9E#YQ!IyW/ G3z;I/O)7 :`ʷs}gQ}SI34M_;4lk$,*?->Fxx-XI{̖£GЦs)aE\b ڛMOPqYC$rR!s0dj즯܉Ggu~rA͋JS"I$;;K} F$Wx… r J<}JZS.mČ =PLgAD |[{wW#@ ^/QEh7' :kKӪc.EQ!Qb I |ևeYϱ,r<}MS~薀$f? flWcY '`ʧU˚ p,Zn tddz.t`#O^r1ƂYwaā-=+ip];/t"wfߑ -cηgBn5j&uZpoSmZݗ>SWh ?y[kؚ7(1/&rtS`mŇvA-cB&3><"h=!-9ΘӠ {Bck7INry;Ȋ!}weby906;-J/k1h6YYU'!aYU9* r%q"ry^=p65g*Jlw ,n@"ac  76пE-ٟwg9~-qm{\JcG1mf9_uhzc/*W!o֮w͌˱۴{.MSϕ~KS6=?lQ̽b[!XN@ 㞮 FvzugeucK|B?8eBax7N߫B26wAA@0}oxMeyXiiں, cDx5|/ ߴ|rlkC%2k>s Vfj̣潧=I;:1 ?ij+yۺеֶS޻{# }7C,[_孑n9yNi :8_O ̋r|K]hYp9!+1ԢR@JgV*y#V{ Dǰ ޞU*W\˵lz~]tl0X=yW\U-%,OoTl-XA` :8tSΎFϦ=lF]뷰lnjiOe:+_u8-)QP)+5=`j27uإK3c VYJWNM \ Yk =M{Krdj͝?]WIU|E蟰RDIv:uv t{iwBQSwضW6cyi>pO?hwۗbTRjic6%%Eզ!`;2snl_rjJj47\:аM.k"B7OnH8@ӧGM=R"Tϫj'런8w![1[j7~d߰;&G@t_]Kx.ic7K}"YeX5 Wt5@ͨ.}öEL@oo! m[&ͪ>7xkYO]>(J_4z@0EG?pme#廹s0||<8 NrS"5d!!9Uk\k4kٲe~CdUЭ#z`ýӧ?MH+Wӳi7ɑ>fB]5{ۖ}گцDYHNN)q!Q ZWAߏke& Fu7{RغW6c9M%uBt!6Qj}fޙݫK~/^su7xU,_(j8ܲ*i4hVn [nMLLq Lbd拐 $b?L56-xOX-_&!e m+1J#`ǹ۟o!#@)_*Ґ]LxٓrẨP߂I%#Z.7mMaMqZoFC| \sWIo! o=㠳۾^ NMk'H9tCZ\A ?1xc[BD.kC5 DbWBr@`P0>Oyhx*Z'WØU٧ ?AH}7@#D\5"! `w L?5#]߅*diWcXǍ}gz3ݟVbI/%M+`eDYա6",Xڡ5\qoIەalbg^H"Xtʡ^RCs @`@fJġ+eF Fņ)ek4nJhaVw";Y,*2ꝓ>ٸl!~H4#$aGYivdoۨ P.@UȽ!ayX@} |5bW8cӺm UPKV!|W69xdm?54V7'sq?585E,rDG_FJXvNHSs~nFR*9l9Dҋ ӈ BIz GYD.& "H ַL*7PFnJgU7R$ $Rb]C "i2 DhTo,IK.rB۷o3ptG-%[2/,CPABCքָWUڷStP -֭Ç1ϟH{ =Ԭ88z|':Ok,?uM^3}y) ID 2$11y F(Q&iFN5d/g?n5ıHD)i=ںI Bqqf[z14u?D<-wMY<2>~DjQN>3%EҪȱJɉb_+[޼{cQRs&.#4U۸6#}ި`Yg7OΟ>B$"Itol秇г+AO."A+I"ݡ?B}5 a3gEkƭB`[+'gd%I@h+>߼ĉKfH&tB֣. YfS= cK>!qarOV3Zᢐe9ZDOZM[iUQt|$:v 0"AEHV8P.>}GYwbc;3$)" H$S0^1*$ٱ\ \JDV^bF Yw\YZàOiC6N\>4@O$U^SHmG6\;k B$#[O_A^1qZ&UM |0zt͠tOO"l]NmZXDQo6FtI3WO M:l}Ia99B@`$aİ)E5Jf $銚% :=s~_:2Jii3D9KP"iSf{#!Y?hW*/K`BUTи;rwG~moT iܑ.ԩS7ntwwba(cU9>>WUKgjLjHw|-=o??Ç  Zp!YZF_HI,Fb/ !Uik/Y%!LF$IEyzz0!ԧO5jP% ߹BDZ#3ؙ2dg=K]|$H72,BJA†nf -2?{A.0h }IJK3>}zqoDޔ{ hۤz%E)L̥gv&=2|Soc*]='Ia6S ndR>2R_OE2Hp%Q$IUm<|VMm<MI`C7<;cak J'D桉(?}-wlX 3EQurIpy1 I%%:f(7n<`8=X`PjR0Wڝ;ׯ_y3YfHL<Y4)nHjQRa$U ||,U>z45555ѣx#Iՠ'2R!'*9S*Yи9&}cT(BN*B.SPB)SM Kt(j mc&m=i2k)r$)I:KL&@*H$Iݱl.HH6+| UcO3Йr IW Iy9Lg2_$Iޠ]k⁢<[C&kɑ4$I `C7<2)vx&\X@[= oF&EQ|ƍW갪o}PRt1(~M,hmupHU;iS`Dlҳ,$5Fr'/ +U\l%>$ Z饄͆t3HϹ=G`z.kg&ݸ\A}#B>8}L؊ IDAT^nͳ(J$}N~3WR ;aEeN ٛTWe%S)|:Ź?EHY'ܫE-k'-?x'O֭y#Ě V^XDgd;v3ɰj ՜9g͚\]]O^O9)Ob'6뻀˟uI|aT_=\~NB8dt"p%Bb2B5VԘ:E Y19RKGi=;8ˊ!$~TǻSRSXZ)e}@z~<Ԯ5it,`EQIC[C\z|r3G@1P4L&!*P(%"U*#LaZ踰ERMt0PL.yc!:E'9w墤r][8PyJL&5x{Z{ag^2Y+Ь^~[|4MFz5(ʧeUZCou]Tp!՗$ A&s9Y@f hUڳ@!SUyp+5Ux9q'gV3]LnxR.mZU|¦B!/%7v=,en6VϚBw\?g@/X%CZ̻ZX HJ&QZ@%kaD-W) erno$IIk%=\RZ5$ܹoV/gg`__ҲcBŴ;WG~{Ձ"J(ڵkذa֫͛7&pe 6Kҿw+Cg%~i+m :E3jyÇH|7 ,h֬Yi:{ڵkPraW6m233:vX~}k*X?zH쐝 8;;K ^yQ- dJH0 PNjGt9N:1H :=䪠7 tu*[]?rY1[QI\PQVl"!!y vBd*UK G3nnn:.%%رc7+ rmEq̘q9 Pxzq6304}Z9:i-i%k7BN* ^ DDUTc1I"\lU"*JޱV;+E [Qx9> SYu.e  m+^RDHT *+ʒJr.S ۾r8[yӫKYwbrs7[շT(uBٹ˓Y_\5&K%&po bq "Aݺuߨ_Rٸq/8puҬtIϟ߸qC&uɚa T*VW\.W*]ÎJQhVIUQ*9|xdv{Nu7_ {Î|W,}AV qQFW׿>>}ܸq2,ljP(QBͣG]\\͈cJ1.ÔvT`*&3~ۃUwv(Y3'e !==ҥ=z=s͛%~ӳm%8ov.ۺ %WR]l܁Vj Br]kݢJJJֽ7n0aB*UN:iӦݻwXo)d͗@WZ ~ U w{­/~ ;32VH ,Aë#"O>fYZ2$5ryA)B> `;(½{xG<)N_\9f~^*`] bDQMmҚ˗^#A[<܈iRYd]BʽDQ|A59I Hw/Q2(wm׮ٙcSvJCɩ %g5C;0$B b0ӧ1ӿظ e"IRJ5D6cbPTj:;;eE]xd^^^/^puun)q2i嗌ts]9x iN ?Zs'8 N嫗vm{2~KkON{O"@Ar9$hEQɓ'^DQd) qnnnT"Bڅ6SY\uu!|.q%)'߄M<R5]]?OG4eF^nUթ:QE)Ȉ1V((bDHX"=WPkEfݹSU5,J#=!Fq߾}Æ ݉h D(:88H9byxW3kw*$mӶH_( X ŶÔ[Iv-RfOZAn>ҸE]+Ti(yfKQȤZ4ᕞ^\v͜?4RQ⫸V7!;;Aޠ~ &nٲիW52 VrJ.C*}".? D 3OI3 "Vv7n{̴e_seT{ꭽbXөzX ]Z5G뜁X4oe-i1EQ ۀxs- tttTT*UY'<;uׇAЧ6),,,,,481ƄI5K?~i֠CI:|PA=~JRTJU-m$J&0i oQe$Ud\z(IWX,K$&O"<"6M6ӆ=x̲b{hg64tmR= pD`L^Vr7Wg6t&а SԐɞr[:o~|9u 'iinU~8(xPZWR$ɬl\6y!ݺw;wV 6YZ9%嗼w^C(v]%9w9"!zO?mv7;tjʘ}GR㘍0~_ݗvR R tvc'E^QAx!+awQ#_8st^c7]K7dgAWi"}\\T.FlH^}qkӉt+ɷ:̣s4t#M:yA0^~(p`ꨍ?y7)6jVOnmzgmKx~qҨM_W@7_xtWiNN4%*G[b3V:gH5$~Vɨzك[Wcw^{|AGNռ&[=O5"aMBDYGH%[ q'b_DOF|/ߐ^yᷳ&Ұ^ޓٳ<闿}t>@џ6: je]cٳgwTBRq!HyjQo׾kGۨ(X-p1rQǟ4V;*TʫJuCr&o/:p(b~\<4bv7G.xbGZ~t8׈u׬t#bnoW_YG(dp"*eq<+=9z_әRʭ0UJ]!oMv-c~x # W( %A Ng4W\^lY˖-kժU@xg'UÃIH =cD'$Ԓ:;<j˭S*(A=:ҫ:&7/*rkƀ*~Iz^V#myZ*c紹Mv\>ݷ8lL cxQgWADžNߵį2gz8Xc R$-H9(RE0xͣBȯeׁ4 {yMdhloیXբgAK51ob>d)"GRoE唇.H (vw׊?0jUC"o<=F{t͜ߙP@ZQ e'+xQ(R0?w(@*/#/}ܡxDB, Oiܪptt㶝 &NԛF*A,Jc0ih񿼸w]FlF"ݣƦ] 폹=}2f5&DQ, S%H!S3+PK̥j./fc?}wbėFQКI BIIIAV^h4>IС={L2[l)~%o%eB0mc檚5eY802D~Ҳ%-  IDATL:Y[+r(RD"!!zSC hh0F#0 M4xrQ\ޣ4mss4M3"4mb9`Mz?_obBnq7o=3sJe$ ߎa0#À _f^Kp`h6<>7Y<`wGY,'zMӴ{Z&y77 d$V}$iQ̃;sNpC+?M$i6"04Z*pSn~V ˡIrY\Nlc?->"$@h4b^2 C9iZ"ngZ"\H3ۍl"g*TNjXon;i]4BJ"ZI2 CAs"Ξ8NsGCsng4yvTNdZDw0@3nYLME;%F3j&Mәzgly#!Eg|7iŞI+L\{0|-d,c\F̢EYä889뒐-.M6v<:xviyݾy[7 ~Fc,6`-ݻ7XA6Kk$c0m׹0en 43^Aj{&VW6)>"@1byKi7G34M5x JRm}m:Viy|d/ob-BVӛgWcV#hѵ }A;vʤiι5;'V/?4͘ q+S40ē+`@OiZx F0>MOt} r\FJ9aT'wnoRH7ER1);ɹxR- #M+_xq…W^-Zz}7J~cmp^X 2_۫n2NJ (@AʖR~ */G@EEQ!Q<ϛL&e98kD8XAXe9>Ц=z(9-#džsJW X% `_H{5 r%9,%] _*~x;@ਐ3A|Jic~z[Ԓ,Q)>)ccn_:uٳgϞpᗸN' ,qsS,KCGF{G3;P 828k|GV9DZLO6j|8ب4,˲lڲ+={=as\x!AZׇXVζtU~SmBJ`l`]f#8wnvApS$~ЙVzͭS>[{]E'j:8Lm#xh k>b67^,{3z _1[Xe󲍵Ŝxj÷1w90U"JJgV*y#V{ Dǰ ޞU*W\˵\();wSgɓ'vH=^ejHju[KG~ѹU\N7`yAT=|i hH~^V]BMa=93PUg_Wn%\ȅ8JorJif,՜չe@.0" 0|6pOCAM(ҽF 4~*uv|r,uh] ak-&B#€ 0 @HE0B"兇NW|#I^d2NK%Pk׮]~jYQr1.ߢW+gǥ \ed>,])5@dԏo޼k|O%!?".:s BA(M&G  0|ޔ9d@B?9 p2_ЦVq`//ʰ) BI_CjI/V&$;;S ;VM ؽ \Z "ϫ=Wltv64 X#ka;Uw0tj4aꐦ]6kkoQ*Y(wsR)dI~4|s-Wπn}0L1vrV&8-/&&Y uG,g,+{[Æ ߱øzm :}(&Oܽ3W/^2ػ[6M6 H@ @GRDڣC*&HSzU)(JS! `(RHHn2wfl¢l=̙sgΜ+qq}Hl A[>ps£Qu.vs֨Qcرa,uVB$Mr)NSճ;_7h?dv8版(w8GB_ӆOW^Xp|8=׏Qu]LX`:X6#;}vޙV?b6ܞ#s`<_v \_ہ0•D:L*]OoBdS&%E*FMo\5vy#jW~1oT֩]WaAL!  KyQCaH_^ 2&2 C ;L~dDJ$>F`nk)Ew$ V ` Tnq?CK/!(tIa8DG60@LZW>vgf' 2D0:E1ZaCWJՏuDsdVd1O1}R$ ] mG^C!@i\*KAUNJ.΃=0DTptԩS =88iL (-|!wSPj•'W;VC {:1ּbBaW! 2Nw~v*k{yy.:e sm\ G463n]`fÚqI0go̬"8*j 5k3ǐYʎ  rb/P몛@L|/ɬ 2)hBѕrbCð@]LN˂%aoN Qbm6 C؊Ǧ C:tHh4y !dݺOm6ҥKՏDU\UHc5Q\$I֯Dzf b/+,4kmټQiجo[={8pXxx7&;n1re*['kV#YqhY~<ɣ2jts9QׯDzʪ&{]3H`u!%,Xe2$ɰd)E RKKbfASP[ޑsTsURvp^UU o`ӘY6l% >7.GUS,o׸N OnUP;o―G_U\;Y$Ai2XeY߈60mG^%gʘ%c%!'v}cH@@@׮]]ݩ2ǟoM}]驷Or!!N:w:uXwlqn ΢_\?jل,C)1)nM譆FeX=%C@$ݵ#(W//ٝã{7v8kƹe4O82AWoF$ 0p#zYCfccIB=aY jr^j32,Hn08?6Jmi }eD&^&,@X=âCl[iևeYsSnSB5w>\!$zkE*HHpB`2ۏ\L&ӱc>|f%>zꠠ W~<'}ŕMazBŊlhYu+nh~ ?쒙z#ULgVиBnUTx?KQm眅} ѷ}]+{t5,˲,q\rzIܹsJ8S<_|("ДXlX{Ƌ96)]+n˲,:'xwPj#øJNt4a㞳i&-ꞽۀzjcu#[M8iYsS/|wsCO^=.wnz?&>-ǡOz7SSSSS\siI3oB\qqqu?`l{)ˆ-= N9*7O$lH€PwfQNCE謾ΈyRV_fNO=y2,˰I|Aky#,3"DdzZ 5^}nLZsY]O5: հZ Zrćc[Bcvup7l-/"yNas Ur&MrhҤ븚Ju8/L%(M_2na/=q6PfMѨZCnY\]vb5TZg'? ŭy߫)[tdd< ΧftK`ٲ/dd\f^iɀDrϞs-##koܸ~m۶% -“WJV| x* N4iҤ쀀{Ozy'}3ܑw2uQEݮe:v옚ڭ[7GIeT)'㦪%"f8`)}Nq<[8Sd:XJi)X5tۏW7yS\DXυJk [0i̗3 B[v^EUr<s y^4 zе2s^4K*eĒ]:~u>?ǀayUBWu:?Z-k9+! y&W95aG곾X_RuƵ1-B/9V ԏ[HEO>Gjߛ:7YfmɁ C_o2I9U(eHUEh=vA(p@)Ut(M )^[vFW;.q|i-xtZg4+ל/A Y5nr3Ԟ'`>tlj˽ᵣ; Vju:eYd5OаP<>޺_Þ Bt3ñ, hx;X5ƍ8q"::RJ*]]zRt|mJJ*X8%;YYYyEb,M&2UsӦM+A?d>l%o1Z+WYܹs͛9sftttq?iӦZmXǏ׭[>ܢE q=Bk׮)e[y{UVhZјb4y'ι$J >T R5Ҙ]D!(?JՆ*J0@|*v{q|||233}||t:]MI,PꁛtziP?Ac[ KZmh:IV*d[Ʈk0tMJihs}=(rb;?$*4ϥ)B1uQOB+N]F)#IܹsI[lVEB^OII h4:iq/JcYPENWȪA_woK -"g Ը kЮ_O.߃`#C)U_= r+W XY z}o<sEQ -X採P˪:*U< x qƥK 봆d{.0+WVWcn*R[KT5|z>VUݱk1Pzn8p%G02*4KBfY6$$e˖)))ǏQG X5UT qʕ+;vQ? /sP&pȗ8@T`]ڍS{⫯^'OfffJ(CydYMKK{QF yIr'0ǧL2: pWUzݔ^TqxJ:VtD͕:vɍ5*J,-Zͭ널+o߾}ɒ%Och /_a+._~JJѣG+L!~E/z(2`4M3VMy_[7Uzha0 Sz1cƤggg{ݮ(zÁʕ+:E lڴdZ>>jb\ti(v`9Td_jb1 \')I҅ ._2Leq YK¤UX 3Uo>tpv{) ?% ׮ǿ\huɒR^\Ŋ\*"ewA)ݾ}{˔)#K(:PzyҠx [< U?wψ`awEDBUܮDcX+Νɗgk~j@t6N Rt .ܺurʥjY9*w֛K.)˗tT$sWhݶMyE))(6431ޞEe9aG50N/9N߹.#\x ܹz#F(SLRRҚ5k>sO$Os@/)>RM8{ji~?jBxW_}$C Oof7l/jJπRhDQ%#d͒$BDQܼy믿.J|||\%I:|zDI$"Q҂{+e#m *Sΰ 2$V϶ږ! 9JiFFFrȆ#v}%IR&[U56X,:м{ɲ\\+WTT?'bYV$*)eٳM6ZheMy^:JM$I^Jtc4,Ca( Sz҅}{R\$I%AaYV*9$.}bzh4feeٳg~ \G9rЦO/I==;}qh==Wޣ5ʊFY%I=z#I%I"N|$  %B&ĵf?N'k7"]|KV `=k6 Tq dg~-\J52DV5f˲,+NFeJVeBU-ѝ Ak^/:u%y N) 7ċʓ}5/^}$l֪q:Gc#>H~{fw4X3fF;^huE}}_L=T:Vs5VќZ=f7MBfq޲j~]SbjNw/m(v oڪw\Z|)~ߤ(> =uj0g ?pn5zŻlxkۯ)v/7fQ JH5SMnči{2 1qr&! !Pj<wݽ [ J#L"J4:]^^reYY8c-w'95S];67 茼%l<ިZńy*U8|pvv;vX(A źk-@Hu XEBRQC#frҽ"zi$<+G8|$PUζ_ムX,ݞҒY{F,ffei4wq}}ᅫW9slٲpFin?=璿K%}pX2g܇gyBۇ 0.uJ${yYg?csv ?0^3 Or( ˲c>#QCJ(Ee.Z$IdI%Q<: m};V/x}ךn%I'q-$I{ik7ӅU_bA5k6\*T=fwm]/Iov9a/&]/$֬Ġ72&vѩJp|ލkv%2]+s0aA5W|O7>a}]5 ?Ws9FKT9}3;.v%j]HY É)DtR1G[f𯮝dcSwR.:dԸq-[P!Ҷ,+!֭[gϞpTԮu5|mJՊA@'NC Rin{O?]ӟ:]/Kw*\qFBta={פ> ZHiI(]6ju C,GYpa TaK` e.Hau˺m{cU4,UGqeܼ/tqqqka(_L? +oakrrTh48:¡X)z˲&h4ej?,hs!Q Eݺ+2DA`9EKva9%/'I  ȅ>^Ȳ>5 Po~b9mˇ>hüj_u%:)6]DYR蒬J%fSPQ~7 :4yyG9]K(!j7l0ڻv RJeP&&t?O cn+}0>ZT7%@Akwwn,xef`cJLeJڗpܹ+J%IuZ<|CC`_"KBP.B@ݜVRWt9}|y{.RtSnQl4&_K[rtVֽ[1 ȔިRL* ոMudŅ ÖP!///+3sWܹÑ#G=+HO$n\@{AŠ.ӧO lMuOp55|܋JhN5"I¸ XDQT~+t=/ܥ*bdbv`X,6n3Y,Q5&5ݬ6h v:}O`VM]>RHAnbro?tU`)>B$(ǀ0sڦ& PJ ȦlAMX>yVJefZ"fXۉ"rԾ5]w,,.ɱZ}P,Arh*]l6Y,VJ}uVded٠iۀȹ6 tIRX3?}I)z.ݿHBr./ㅥI"}RL9JMج Y*X^2oLRpW.=ӟGH@h`1BK Ȣݚ_<Z- aJkm2hf~6)?KFOR45nNߙ :ʑ}l2 l_e[^FSޭk6 y"GÀBH܄"WLE*Ce*Uδ|Ô m2B~LmjQQ!*m^y7.~#캏.e_z aT%B L~ӹaKu?q"n|M4 0`wf<׹;g`nr#˳;,7w0wf/5}K}o1ժ0k[2tx}7\mS##oDDNе_+ҬulcYf%w”nɏͭfI}Y3y_~^!J#Kl kz]FҲTmw *H2],AwߝRsڹnTfHe8!]I+ v?į ~}7clYJo$'|_5 c֎;'gN6hv ;UPʙR<e4f:]VٷnҴ>QAapzG>M7IYM5PA4.O:^$˲DR2jH_oܺ޴n:ICbZӁ9qƒZT-cd%{1'PS ?e$i!.GV:;E^șv y,X&{$@x$yR?}=yԻkëǿ;ѽ<GtӻAgv5[^~}CޮkO3u^Xcg${@c^^"+-7z 35FV:1]a>zQk|=P&.`26n\Vfnٳg֭K, -%eJfm投V€Rdg/1igf'nJu8_`JkTS)!/]@](_:jU( ! ee5 \үWP^3[[n7qgZ~8}\PB h0O_`#FG(awHwM{76g؜](o;bVA~|iz؋k֎4g{~[RKG!e| O~~k?^sg$nٴiӇ}ч6m|Ӧ` ǭ({uTz!ƿOΙ߿MGO+K!hJ%IDAT7[5lByEVC^*b7>!c0w>SLyp+V b۾8r7B 6,=zGrlP(`_Ti9ƚMF׼Qf˟0L %BA0D  ) RPXMk.p y@/_=X.8jժU+ET/\xi(4?CU&9P&}L@PFn|i`ԆMU2,pK1 pf MF4 6!Z#[!(ztH%zb4kXt5J]{.)|TBuҽܢX6Wh|xe~'t@Hض) J6AI( ;wn $wZk׮]t)Iok~ܫ_rsg>ߘձP._!ND` Wڪw nnGw5vDF"EH@RFuͯkurk^^"]Wzn- u!~2:, 膽9UJ2D4 a+Zu!BY,u>٬K.U3O]NɏD}sɒ$[_:l18YlrЬe #GSӧcoscsݴ\Qȕ\^lfexG ZX.fA\ġg;5$ʨ)ӧϹsDQd_~rf2SڞF6ܰ ,a0C=YBJiQToҥYi2}w.\ܹxkG [64f֢Ϳ?CIB㢏Ǎ9tՔ0 5>qAS[U e*T~ۻ8`}+WU)0t!IPV,òoD#/ni\!'tԵMmͨmZD V\dXo^kЫ+?OeY6(nȼ8ͬ ^Y[j0CYeAX.?KO]J|:]c嗿LJu|RӍk/u(мødMӕ7Ҽ|a~A5 kTj0> ÀV-$,c <9:4O2U_9e*CeȄhXކFrF3-?Or3meL0Rw߱$ k׮Tᷦʾw.[A~\H'?9АRN eN:rAp87JylVZ\YI{K-OCp'|k`$,~/ĿkV-ڌ؝j,.h쫡#VڬQ_j4WP\]5ҽt>½ k}2!BH "׊U>0 e*12Lc~#}fK>|8))iAAAyO+,?вLVxAA%33K~߿G RqݢT2 >HaՊs>ф[۾=:yeY8\r={$tܹRJ)n/\>G\hw\v,M=aŌ͔ys^eYP^bщ<;D(a\f%Q']:qٴl͖}um@=[1-&4٬٩;~9{ס'u׻wnm'=͛Wܹ$7!S:`MvXKeÖ'Mظljf;d]IUAq u6deg8aʻKI'D] yWǁ`Ni$d6q֭'n 8X8`ֿrjͽy̵80#2naYeN^ lk?佳&Q/b 0`K1 )3==Ld>~?=G" 5<GϙDQYrgN>|WLܬçtZjXjx-j9ñ|JES1ĿvAՏsl6{ݗsּ~?xRUH*I&9N4iu\M %c/L%(M_2na/=q6PfMѨZCnʿMmS΃Go9zdz?]>ǕkcR>`[Ye9k)'1H>w%eY9(r:fkJ]-Sn^O/E7 ؛_Z1ccU$X½KCפTߦOZ<22ƍGݶm[RB?8Tѡ`69+@6zo^Veiu1jҬ^s^&f6^ԸɝS{FԞ^xo'.և׎0x|Ҧ[Z2鸷^eeװ>EBBz~] {;櫿* mղ20p Dz,y<Vc)ׄBCC7n|ĉJ*tu [oMII%16qd'<<<+++22q9~b_m:+t j՚Rkz&#V8GHj!|A ;}1} iQkui Mgt\Z^wN}g5#jOg̼vP?Ztj"K uRsSnA=Z|yeϜ9&[~~~}tuyزezlMKK[ac y^cǷW\9=  6mׯ_ G%w$sgk-yKǎXj ޿Ν7o̙3 M6jKj?~nݺ8{sYw BH^^^VVV``[~# 7]JSelĸ8Erdݻw˕+leʔኌ=%x}ew?Mdwsbg2 $lB%\J@=rPKОĥܐJ!* QHENLxzx؎~Qxfl2?7oVVV8P8dTd~~ŋ SoɌatX|ƟSmrYXV@L&!Fm2%L+nd*C #S.jF###m\p&;4MJߏ|?XL&>}z֭Әޫ|sܷ~wjg~MǏ|Cly bX(,0 e},qL& %ru#J49 :O0v+\i kʽ^=زFxr=^n2MbKlk>we%f!wI?-YzT{.E o_}Wnm.W.DЧ| U+Fk5}>QGՑ'5]߬9ߔ|i.CGar)'ȗDI|gϞY|_t:Y0o]כDSS-Ѵm{uuUn-\ SVp:li/8i_Ur+0LŢ(`Ɂ>{oMΥ-o+5gΜ=ElygxޣG޽{ƍWXnm{vvvtt07(>/uww{<[8j}]fuo[ɬ ٬,Oa}/:]aM5Fk ]TDK0,ڳg/l0څR]]_"/J;K J ?̌܊c&IB]|رi^4K/5M;yիWN:uȑ|<(z&+o-̆O=[bΝ^>|xtt4Htww$QeqqrR<_\\/@-[/-r=߿Y"D"h4z+Wԣk,F#H`0߿&Kϗfw` oxJX a x?ef^v?vuu?˗dݩtYʕ-ڷo_i(e8qbyy9J*;T ir&_ +0څ/^]p"J眔jlav]W)zՄ-.WqBa .H|8օ.RCH "5.RCH "5.RCH "5.RCH Ri1࣠*cGeH "5.RCH "5.RCH  x"> 4 ÇѨ뤆o߾=88hF<g@`sN4}q&a!. }> s v¼p|| Overlay and Map Settings Overlay and Map Settings
Overlay and Map Settings sets display options for layer data selected in the Overlay Toolbox Layers tab.
To open these settings, click the  button for a selected layer.
  • The box contains tabs with options for different types of Layers data: Labels, Layer (volume layer settings), Metadata, Palette, Parcels, Trajectory. Tabs will be active (not grayed-out), depending on the data in the File selected for that layer.
  • The Do Not Replace option at the bottom of the box allows more than one Settings box to be open at a time. Otherwise, if settings for another layer is selected the contents of the box will switch to options for that most recently selected layer.
  • Labels contains an Edit button used for modifying the color and name of labels in a  *.label or *.dlabel file. Edits are applied within the session when the Apply or OK buttons are clicked. The edits are not written to the label file until saved in Save/Manage Files.
  • Layer contains voxel drawing settings for files containing volume data. The 3D options to draw voxels as cubes or rounded cubes are only active in All view. These options should only be used for files with a limited number of voxels to draw (*.dscalar files that contain only subcortical voxels, low resolution volumes, or highly thresholded higher resolution volumes) because wb_view attempts to draw all voxels in the volume, causing the program to hang when large numbers of voxels are drawn.
  • Metadata will allow creation/editing of map metadata for each layer file. This option is not active currently.
  • Palette (see image) contains settings for adjusting the type and range of the data color palette, setting thresholds on displayed data, and shows a histogram/basic statistics on the data distribution.
  • Thresholds set by the sliders or entered values in the toggle box are shown by the shading in the histogram and can be applied inside or outside the set values.
  • Palette spectrum is set for the Full range of the map data, a Percent range of the full data, or a Fixed range of the full data values. Positive, zero, or negative values may be toggled on/off for display with the checkboxes.
  • Mouse controls for zooming and panning are active in the histogram chart field.
  • Data Options allow a palette setting to be applied to all maps in a data file and/or to multiple data files.
  • Parcels sets the mode and color for which an identified parcel is indicated on the surface. Color Mode: Outline indicates the selected parcel's boundary with a highlight color, Fill shows the entire selected parcel with the highlight color, and Off turns off the parcel indication altogether.
  • Trajectory sets Attributes, Display Mode, and Data Mapping for display of tractography trajectory files. This tab is currently inactive pending the release of tractography data.



Palette_Settings.png000066400000000000000000003332151255417355300353440ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Overlay_Toolbox/Layers/Overlay_and_Map_SettingsPNG  IHDR7mt17iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:]͊s|4v;~psy{{/X`y^^^4`hH}ҥK/;weѢE,kzr^O?򅢀Hъ{F;,[5e1Y4a#i2Aw/W;͍xnxuPP8f~6j0ïb+oӺ:_ H?<It:L3`2f|OvV.ЦgRg~߇,.䙾+߼67#AongKt~x͙z r=+Lo݅Bc4oO` ,+\i.`;J7oGfF\\\f3|G}b2 ӘˠΥ3Itw7⢪k=x[i<֎;z6}[x*&WwCyG^:WI?5}c>Vt)̀& Qx𫴿e_|ӛ}`:o=jQJ֦Z }]iwҷ@Fo}f1ILS&I@(g|6~b: w1&RqqMzm|:0zc7ouP~qই_zœЯ݀ᡉ Ғ}q#=uI /ge+$=rMO?uڧ|E_#0zV>VMi{1.9]Yv-1ɟ|0}|{o!JBA߀0]=lwW :4HN]F~l~h;=7Fct:N`6i4 6=aOL$/p0h.t3̤i4L&6klb!n!!]h~K=186ine~aۉ>CT?]yoH͛y{*q.xwn:^ԁ7,d.[L& &WjmƼoy5 %U_G߶aNqPC7?UՏHw_|*Whzlj7Mf />Hg1kwl6Lz=CIUŸ~4x}MO_OT>IF]MKJc[Sd{63ޖoW e'R'5և?k7;wX.7dU,L&̺^:~hmJ[˶[c؇P𰯦SXo M&տ2*cj3tJEO5&<be\hnnps62B#FG M2z+]&cq30LO4f,w776Jwa1,W& c$9FwfܮWJAqLfQ25݀$G=/H _y} .Kc+K@GnHYNLfl&I?F#<&oG>;bғW63dmz?xRՏPȋFA58om k . /6ĚM䔷4-&}nٻd_6f$InsJ$@iiǿ0h]p`㏾\ڻ7Tݫ#&yhs@E[~sϒU+B b=&|~ z݈ L$Ic"X01/,-_\&.:7 s0$i(/pٱ. 'ků}^ ጪ 3&Z]t)ѐ a=ŷyX.K(T`VL~ot4aVŪǞ ~OAf1Ru#kCkQ@n<#| 9Zdp.g~^x5tIc8ASoY^V<IMzb}KŤVu"W-H9>dλ)(9m"7777e5H}yCYO$IMMUKx ]q얂<0O]`2LZ__f,эf{ٴ-od˯o{c}ff}m6 7ۼfSWi+>sIKC JJ˅Ȭ>iO~X[}o\yxDԅN# :khy J?O}kD$x_W^\D'G(bvL&OOOŋ&E^nl6@KG.X0<9ys8szr0uJ5|qP'2)(Oh|wҠs94pqhbMҹnt~Ոsgy ;]Vcb,Y֫48,p7hdp/kO FA'.]1sS5;`A*ڼ%-bx,/5)Xa`U$`< 6םiX3\ IR׏Z6t_)(~a=cXyM#?v^C]VlOĦwe.|y0}AȺȕstJ(((C{ab]mmYR`d &}|w _ t\OF5h; 4\dž5"?8l9<Aw bj awq%댝mJJ{=we`U_y ê?v&1:<8:cg-66J@AAAAAAZ]HAAAAAA1[(bXOpӧGF~_yQPPPPPP/[l…N e[`{;UkJ+nS9ojތ#7 uScbVJgTO]{yDl3)ώ6k ;~ո)4bC{mZ^ }ႬJ**=9B*?N-j~9|}ϤUU*:06L>\ud;+D Ee5`IzY׹.&)3HDoQuG[겖5D z&k9X)QI bJzOWUg&=L4 0Nj(oW^]mMJ/kR7mt'T6j* 7Xo@IVy=Xe&P~Y$J2lr'Y&V.wMm>JiWTGv75U];;EUQ'Jt[wZa"#4[Ԓ:C4 Itm'צDMaA@ui;&ʇꓷ£mg{܆ɵ=@)x8QLvzeuQkW@|&=g;+75)toz/HQvB6 :S+%ٽ% Un]tH c# Q fgf?:vt]ٺLRӛY&Ϸ+q]L&3Rb"Ce_ n$I7nL}xU?{Zyd kj+:)mƗ{:JjI#06ߞpqk6GBnz 'GwY[,]J@|R]A;%#-DO[qQxAxE +/aw~뉳jO+ N=9GƺL];kD!9\:/-u[j1AޒL]]p:h]gU`o bTvtkjn@EV!E&k?KwBWeƦ*{~$Z,3!ŕ59Q܄ 䃖 ؍kšcsD(zqw/_Dع1x97!g}[ džmy0 [HDuCMT/HՃaB(:X!ѩ{"6!n9 *J7lPo.Ox=B`pD:/讵ˀw ΍޾a[/ɉ HM43ygH*sm"n>X^&4ujG|(k -aZ[Mʼn}2`*?ÉU@Td75!{jBR~d{XHdҟDvDs@ѯ 2D=x gu8..S9,84vwM:PlE"-&"!*`@gbpOzηZ*RcB³+ƗP:^nYM+}5[1긷^T;6 A!|;3 '%!lxW(UR+v:wֆW7_͡A7&ȍ fvL /vKľ" cFo A|:U@_`cA-U׹?6,${e%FF2;!845"d%0ѝ+?W ~;[9iQ} l: (5:8$8$"U\\`M X#zG ]C^dLNq}#߳V}0ℬE¬YP  Di@G6//6kAw,8#yY[Ŏ;Q {AuRX8${s ژ.ޮA/rD(;N6xi֊"wz/\3*)Ay XT?4wڭx^9 tH{ '!<ǧx 8Q-Jegq*I6@cr_ղ5聑[;)[JKoh^RV')y]w﷙ GҠ꼓v᪎ |d~#y2MD:(ϸRDZ P]i'xo(JK-RH fCZ _gK z@X6z?U>'#w,KS![ ÄŤn,z G6mKgrϾ795H kwO~^Z 4Ϋ9({O4$W9Mɴ`D%hn0o.W"8N7*;KE6[ũ g~Q v 5 0>_C\`Rɋ3F\\XR-W>ymfm)͜gս@nt^[׀NRȁTW'CYrJINGT&aze@!gN+Jg+߈|D\.oaGu\ޥNu`_.6`f;,`)7CvvE)6tu:G>eJ\  Ys5A8B_}7PPPѮTvUňeWFJP rRTYo@M@g݃e¬CtEE޺NR^)C]+@O]@Ooaޝx8fx߹a@$CcXjed"P5Oө:3lb1f`@Ze׀N7YLDI9Ohb&'Kpt,UW>Ԫ6IF|}u}:Pv6h0&7c* =*N:$yxcʞl)]Q=S>Vu7˕':C+B{;:r\~e=YyZN~ʄRޥ"Y7`:]aUtu)U`ؼ8 m3]eQ{V٧h[`OE|+:)ySR){H*4Q"Xɒ] ?߱e\&w7J o}ۦtQ8V$$9"q~BXֿ5e@TVXr\.Ev~-IօH@>bqe׽!hƹˣS'K z$u'Bbd@T]s'G ˏԟpz؛+Ejb).Mp]nd]E5KH.\+\'౜Dʊ$O x4+*)7ae5؞Y*@"R/\R\kSY''hKK2"5, %١n a*u uY?e6 (@wZ˱c; i1LK{O+oͬCOu4-5֏6qXFHs@w 0"-f$Omg^rs250nGj<mM̵#J)ܗ:Tr3U 8Q[Jm1Q4<,N@\aKdFd }4hin+ ~h2%$)"'o)́+WFO&dZeө(5h0~=0 %hq')C^Ⱦ]! \ 4+-KˍҒRkRiĊUp4rcW4 1&ōR/^>)\GγDt( ! V5M))鍘zH\y;@Ab4ϝ;w p Me0 <մ[3Hivoܞ׿)jѤU 3i, Ο-Ď {9+<1{ ʚsx|9|N}Y !h:7=;uHBhnGΪW v*϶kkֿ L,#ɳ<\;BsN(wLJ>q2횫t6>Ok7A'o;6;;tHjyF:쓷~5ްDe:NjՐqc{]БHzQ`grU} 7T*Bo7PPPPPPP0JR(=FDAAAAAAeTp~>(bPv/R|]UOGG5lc[.PuKAߌ}yovFFvsIӷa*Km˓l׼7C|}R]_$cNC rI|iVA*g?P+VJf͈=謄k@w85&f!Kj$o[xƔ3ucUfRS>qpxd TbXi~1ņ'A_T%y226ߐI\2m]BP7q&ux63|ʼm͚brmRPPan JJtNʏS__8vgMMehGek@Y J(.+o/MUFKOkuI-)o z %;RI jpv:"H̀6qW]JJZ俜tNdUX*?3)fgL_뉆j(oW^E?]mMJ/kR7nmY[SR,3X`' X[QZhce]o1E…;+/H*eln|țeRi P~Y$:p rcEmۊ I/[U\X_Y,YUn|m1M˲*w%6ɬouuKAA߆}־V˶X ̢!ʹ IWzNW11 (qOwc"PzgUGgcs^Uۺ=2?0dJmu=C¶%iIt;ŵʎ]"{_a&WåܮYU0qV=P{EͮaDsD9;‚C-NDߎ@E@6سg >'ҪlݱY C_ 4ǵo6L[LJ-ת8I;cbU֧|gT}6ٲub:7}ś<"n0% /{O./>48%0Llg{@]ץVm|||³+Gl}Kڬ<@\k$|$τM :T"g϶Ϡl'Ǜvv9xFw}pPNJm9r0'Pl,/ tT >>Nji@ylT[9goIPt)uIu~ϾCϾUdo+iBlk&n+5]ymm% B XzV@WΆ:e&mM^*tgb)YOO>%3/'1e#GoT yu:;^1]OE޶f)((~(  D9q@t]E58j-hGQI,7>0jeei1IWJқR<~ UX0>y%gb)/͉JXP\SWV'TנתGSX1nI~ޢ{/kr_%Σ{:ښJ#Dh#@*;J5 連R@ kצ:: ɭ=!W"oeB5dzd:dkY7'/Y~2zPe ||| Ź⭺ߔS}L~Ӛ 6\Ύq^z aP!MJlmi}]qhቁ{ܓOe|Y: 7Gōel_9*Y89Muo]}ݮ8'z-P* G)9ЩY>9R$Q|^E͢(iه#ߣ_+ N U(#)((~A87x,csEo_#.;7//TjY-FAq;OVF>VEYrE΍1 q'j7 @xBrote{n6`нʋuz ;8;^eKgπm?AvTg6Z:MU @ueg"7F NUIwRj|ö w#GZ \{gppo̾¨j8)fVwt6 I7ay@h"n>X^&4Oolqx{puQzX6LEJ [ر18$:JC482}+@Y[H(l sbǾr4@zr/:3:R"bSkWxg5 f|;de`?I]l*I/5 ++=,H  vsn*r;'@ݹ>]Nhlb]oN/{wUH?36ê;JP*[<7%x8~5Y,-3<Ϛ(iݿyƳ8@!VXr X58(v~$$k:[cl:#'2r׆$@u36soXqegf(5dL&_l-lQ0-:{B\yBM ¶*="* \>ة3۱]@~#YU> lka |avuW,`;m(L[q},J,g8te-b|qBAAݠGo ̹fIh.0 řR@Z..B,tD6 qv0;4`:j vvE)6tu:G.! }8%4tN%΋mW[;b@9}F `RtW/(U!3켤˻:Щ|lwXaSnLya' @/%a͐%?W!twJeWu^XveI.*JQ+{I t=\&z>Oa[$P$@*嵙2ߵ‰3ۉߛ45ɐ;Yuo:5ө:T׺6|<f<`tL/7t( B\Lg;({}=u%QA~l%mQt<&VQ^'WԄN%?TTd}֣kD&8X* !O IDATh{)((~8.2޿qU_xx6ı"1%Ų=5e@TVXr6z28`.m&~m/A^BjtqJ,(>1~m<:5)",0Uw"-$FHDݵ:wrT hkHNKތ͔'*vv#ť)ˍc`2 x,'.!?9Q黲 !;͊JMX+n ';2`V8 de98K9E@u;{/5::SY#T]Pg(@0(Skc 'ZX;#",@M6iWS-B5pLo1!]u03E1EĜRqb NG&4MDK9v8@gn8w[@)9ks,rzZ%`5?ԄNDZV(ǡ_ ^{B,a<̎Si #ԩ. ^\8G$eu(wNy?(᧋`^~=ϧ'}i5#l׳ZZ;^??BNf 奋ϙsW<.n3waQP2s!5-1+}Ҳec=mbYYaf&n 0]~aQ@嫆3gΜ{ssm h&{JV-)oȲ,IRAA-[.]4i$:4}˦k۶mcƌid.WP.)ȒdVeJp\6 ~'T(y<;OepJټRnM(( 4M ju۶m#W0tUeyJ:gC#z=gڵG<>Oʩ1*!KyA,muGNSS<0Zap_%KUc(?y\@dy[csrS%55l~_pR\[f-Y:t6"1{c)b#Yeejr\%74Q(j4IGgzrޫ*Cf4а;gyܯi+[ۧ)$ɒM~s狮[o+\t+6,NIq`-$SuBNT7vQvGo|}gC |/u{wOOQ\0@3՗,^ٻb<< [?o5.ױк}&왌K3N\c-Q NH|Cx)}|@ɅS筚Y@$o~ @K%W+cOՖ0BEY5ͭv9"qC(JMUt``eYEQD3~Jw5r{o 3* 뱌cF:N\:E+7tuoXY`'{_C9D3_o5!^Ǧ+e@$`' NZo5'e"gǒ2eyTg6[}lu)gE $I<4XGԈ MaI'9)'ސtt!D]?7$j[57`.< DQ /cO+Ln5w]@c%pͥiB-@wZ7jᑎKh?ct(l߫VKDVq%|_ݟN>Tg̟M+v-2p ݵOp5sOgn:-}ʭ DM$ [#OW:#k>9 w(Rkaueuv }0dY@UNqShQzc8'%jؗ /{lP,!EAӿ~n_H|i3_uXLEws/m>Q)x ,߼0ͥaɚS!ꐽ\}]@a=XP=*%'o=Y\ܰcL߰'ODŽj}7`.}Gp\=~͖NGM+z)*)@Ty_)ƟZmp5s  r)=rߔiKmumyiB@x{R\ DgϝrEyJ;H,Rl6[up؋,\A)Qغob =h>co'=!_q>EhZ%ҦR{Q快j՛lvY h/oz׍6Ϝطu+8u7ysgv~pnFĨ]mvZ,Nk9GhgxR?ςֳ߳6U.ذdyN):lݱ;?ɱX>턍O+JZKL}ĄNlݚ3Ls{m=^{X aXʭvW;;]}ԯÝ]C>jodrh)P^ T<tj2g Ra3nPzw~pg s8-L8ӥN_ȋ4=&*{OWl#iFh]E>N3 W=9Cgڽ\} jJM/rI~Ulm{@hX+әKw(TzǾcZeH'm<[?{RRJ(_sKɕ* bZ~Yy' 6,[Jߘjľ+?rT=2ʲ,ɲ( $nh\qåKΞ=kX6[II .Be݀t|XyWQCt  &\t㱓yw2O?c1t<5ZaVW숊mŬj 6Brڝ! ص[*_ o[-<th":Lʵ'!{%!jGuQ_ؑqMG¿gl0s<;i={6CBhH:8N]6Tph 3oG#5hmd\mڷ5rմXhȕ6J)V.g3$>mOM]eHX"4IJtz |/ȗ۲sP!NwUh7p 0Jػ]<4MF M_e 6)}jg11>pf10zH@PpԲ@IU[xLLlU+Wsm*v灈ޣv_c@!C7`Е 8tWd 0UȲ EqRJ(YWWP.҃NiZ15o1|#}*7,#NE0lYT>EtXn_3.D9 t#svQ;R4MQN- p.wd믢h{w:he?%/YeQ@ɮ/):wݯ%sNWARUk@% 4j] hT^u7Y /G l߇P͚'fd!];*֨*p `}UH֨]*ʡQi QraՕ+TiA\}1Hi3niPu6 "P=;-w[Wo_\s^צ)x5fpTQULa._WkM+P՞{m,ˮ_6eYe}}+^ $IvɓE@rrrBBB yfq˗(q^kGyATgo?z۽z.[yXa@r* Pq4"ExϯJP>w=ҾW?\;ONy[5(ʻ`h^M?* 5ed(Rmvʲ%TLSI$/G^_i4riQ\E NUU(γMwv3G(Z04~:W5(8f5rVu,}-nP~=w})')c'f8oxk|C,kT,dXըYS+YfX@Ws2 @ͳ>~5קB ޯ>u.)/4 v@cqrf/7r鶒{Yulv&DvwkV׺CL߹mm=fCubs=5(7ѧ oN÷M¬-6|߰o *5H1 DWnZ?>3wkƖ;MVM\4<+V;S 򨻌|zݧ3*U:hf5VlI_fsIkKNK{z{ow/-})1|>#OvxGzvkFyuJLxﱯܘ=sxԚNTlVL36E8oP*~ 0*DTNJ68)1hme[]LiZPY+EPH(RkSܸhjשUPPpԩ#իWv{XX˲t^Qlyh3xp@b0 +jUUl)BvsFFFǎ\l6sNs_} j[!lco1zP,̢lsfv5TS4mzl W!NYj4[TDH| Pj67T5-ls2fnl*)Ǻe~K5ݧnR#:l6PTm\}Mӭ;S;ll:lsVzvϽ@ŋ*߿KM7Qsss۷orfSf6{ty70:_UWFjFWerٛ߬˲uA-#\ Sl׷Nޭ WUs KZT<5k5zϋO/K +Ha)G(}>7_U[ht?QzrKrp񆋭u&{TGU*mK6)=Ch6s>74Q&--->>SuC#cY={XT3:}[n_Ŭic,W{Z*=׳A\(IҡCRRRnJňG&eYrȑ}RKVR5CImP V&o17bV_oЀW=s^mY<**N<7ƭU~f3n)r󈧽qkn ={_~9~ɓHl$I:wڵk/^ Д[0rjȸ恦騨3f4vE …DvAAx AAx AAx AAx AAx AAxFAAT*>  w@SA-7A-7A-7A-7D`\n4F_+i,$[0lsB/qƯ{bodPk A5#qA4 =elI_©BYLY~2#==}\'Y99RNG}g{VNN ^|rٞ*eSEk) 6 m!o;noF!Bw8GH,8wK:Ԁ n*>M @ 8PbC!:tJ.O6Y$2M 4J0ݽB! IDAT?`^R򖬧{U !ywӝj(K'O&?77>RG ZbL~fjX |mswN'n5 Fasr_[%bVb4޿dwGqPRn#{4D%>UX|?CJBԥ`-Sbh|խqOmlq?ͺߖsRJRQmepUcP^XmG]NNuBBBCDS`0tAg0 Rz#>t-s#3l43lyf2A+W͙f 2F##*Җk9:`lhtW <\-B1X  A4; 2.5L}`ˣ7/u&@J78ݜ?y}ɘr_Oosr27D]M!yHyOfgr;_y{}]>d Hj"d>oX:k c6Le(x t'360eރg!bŶ91w[fό>q$js60J ^.i {fP ]IƟ܈]6 x <qU"6C̛˃C])uak{耸&XKF Ng} <җ:w)n79?u4]}m `.7sUruq?{R$)K07vO2Y, { 7Iz(71W!8`Ƚ{9| Čz3-~ONx&uʅf vZPf\m,A4L|Bb_e4tew1!V_PE%}BzO;wr| ԕeE."b P78+}:}ӥK.Azr23O{Τ=s7gwF7- ek|{.{H3wR;h 5HG<+{ƙ{_hlزS}k:`RgN`4;L==cGqlҧ;^#[:sXr *((8uTXXlQx׵fUՔM0̌7F e'r&A4wIC rCW*NW:o5fcu  ڸCEQ5dζZ7^Ѳh46mڄԞ  s gffz&" I̤(*88s#j; [S-qCnnnPP``Yn *eYɔLi"I Aj(t n$IE|A *yIgAyWT][L~-7=EpT'Dj8c qc(bYo={j9\{&32A-MPh0rgޤ9Ȩ  E  E  l7xԩ3gΘL&f$Y(!OQ2pQ98X3OjUݤZ[䚭 2gA4Aŝ;wʲ5w[ð MS2E ɀ,Cܜ_GnrZ5[Gd' *h?s'O$i℉(яj-hK i(dHHцPǞN7+O}sսjMK[A (fffvm(P<[C9y>S|9-&3D\ǕA]u]$Ieww띏O-Ilaf ϚuBV`aϽF]/S( P~Ά=,ߴlJV* PTpI)]*0$,iK$؜!$U8l^ºPZ-1ģOlf Z" *,,4444g.?f cfɲ̲rx<8EAө-|Nj8xAIW2{~-N.JvA ]M@ͿقW(.vQMSTM],BC._=^|3'p׾:}kOzuec'zP-Xf[ıcWiDú13f)/B3}rƃPFy ˲a rџm? `{9ľKȿ{_K]) MA>{OSGu=T%bլ 縊ĔI31c{ۓeBr s/] 9T/N|k)0l nXĭرʋV5nM({ Yæ-[w7ہ /k=llWӉӢW Ô:Q%IEQDI%)w}tɸ:w;[;$IRn$A",HCv_߽o6d(%Je<'Jl"w]bYk珟āâ-lra J.5l#޲iwX%^5j#hZd]hPPG ~s-L۫(ʛS_VW-oع~x:㫗MHJt݇}4rLpԁu*h3 CӴjAy2Oy$`:)RrE˿8pS'][#&rI/u"EӠ(eH57eȐQ$ Q ɂ$ۤUs^@/Wc0%=  3acJ@(/uع,:oj1?xl}kΉ N'5au;ώzbGM0U(7 ^z7@V \tPL ?V@KeBA4 @-AEQ+g]y])JPveYeJD$Y%5Kꪯ uf3gF,׊ sR\6&֐r# (q n`8>jpr`bnVhaeM!mIMQywoswp0M?XES(mNأh[7tzCfy23w+/##2䊬1M0IJ.΀}slx~7!659^0捘v;"ԗϟT3CEiasbXՇ&% R#?qx:1a!MQhakNLWs ϱrLt]|s CN~+ Cd A(Cq5]N_9o.{ xlq `h)9b xkEiabi{V 00tZn=ʋƭ A4zy~RF9}˅Gر.o0&lVںO֫KN\,2 V}inb vӡ󆽴ST1 C( C0 pW2^k8W1=*eesIхCZZdaX:'…C?ڈs7tzCY@:b\`41j`PN'1&S)7\;m6X5b9%{RTJGk~98:I_qe[F04@Qh8Fѻˑ=kMרX嬔xQt|={$["U6,˒$ ZM.k`ַ'JA` 01̝ee׃z=8 @၅><@kkYlNkUq -heYط6w}:0+DZ=LzQIzg_~.2`mӨ.O߿@xUa( EP{l֩c=UeȐ)IY!H"XA-kk^SL>TS0rA_ klZ݆Wuɕ**e}yGKӪ25e6l,:&Y fA pAjm<{~rN *JVGyfe>JuWK MAS(h|N7o^ YdLb 2,FX;,ۃDgb~O/Z[]|kd.xOFOok hAyHJؾCb(ZmFFFU*F񜒨d__X(@c|2 KʡE!JPAvhW`SS G|Jռ%h: w,4 5 Ml1F!I2CKr`eV7D nf ]3cf_|LJA8jxrp^kҤIMQMK [A( ?~u^f:e/EQ!!!Ǐ kj]-KS^uGz"f7i:**jʔ)&I:MWi?? ؍i-XZؚ#2_$A4EQ*h4U|cygc[45KQMܠ 0AQ&q{AAJq2F@h|Jh4iS%mPH%e5P D}p!]W\u!zhTA Fs{ZVΖU.:l+wJo~˟rrWxu@wP]h=|"Ν; 5"$2ܔtmbVBpr垤 aCH]tl`! IeX1oa %%oz:U TKPeWDvvND &=tiϔҳm9L-@>6O܎Mw&Tqsf tS߿A0М-+N7 +!!SN*+rѣGZ,%KtW^ ~ŋ?cZۀ\Oʧkڮ];9=d2*EQܸqc\\ܣ>JӮydY(Gy?ްaÈ#W7<$1A Eօ,ˢ( "6:ǩƮQ)m6[VVVrr NtZ,f9ffflƭ6AM@,˂ ڟcRQYtם,\o .ft4-u1dP@$) @$ͦj( `x?Ntܦʲ|!͏N З_gД+jkz0_:xjvYaQZ  4w5kdY^zu׮]$QAш؈u&hPPF~7= Yg-5m\6|0忛-5~ƾ1Ȳ|B72r).I 3tX/ s6QYU* (JDQ(JN$Ae@VND jPOa՛7m;Sis'5ΩyU&f{ʨ=)˥˥B^pD(1="Uy]٦z 8 aZ#&GP$R f=2fUU܍SgH$Oqyw'wZj/I,U!1ubߑwD8XNfmbiƉC=F|lS'apkY(1:vn3˦Λ<,cLt߫֬#ˍ9fC%XBG/4M!b^*4KӶ<9Pj:b_@@aA(T[qFڄE?}rWeݞw {ý׎{ pJ( %2@ɐwEQkb+9n`QPt,nXNlfNh-`֖}HLݙ7LDMf xݼ~no%%I*--i-qfWAbh Sj-"R:Gw3l$]y=T.w#y|Њ@%QR U} E;^{8^~ @ }8}kA(R&E%zE7ynq XןiUmˢr_hBbJb&Y-qq"$IW^ժ!9hhISMA!EXFSɡF7ȲϹ̺Sgpq=aV[z oq_MTAymc } ͎AUqW(Cw)UVviZV[[%A4:LثNuh8`k nl# NH4?X %N Ԍc4*Nű*9Vtj֗g}x*qX)|3A$Qͩى 6l@EL}cQ tSqAaqC^|-?QwP=)_ ʕJ@-C^ 26>i20oc7+*k//*rG@dDv.*Sk8("~#GȲܾ}O=E4%Id\$Q3@zЀ(jcYF[}®#BCCɁ >Ԧ6 m{KÜHJE;WvcC0eޗ^1C|: FxL9p0 04M5jʕÇ $I\U!/ADskecy#]` L_kh-!˾fr\ݳ%{ޖs>[9qOr?U~.c^u-tj6]MUΒBh|‚7(p8ZN r̙{Wip8NA nB`k]ɋWF_:t0TGy$I]~ZaN:cǎb4Ѿ}32_$ANHP+ÑtWA TBeYUVz>>>^E`ay'LA"XhDC LEӴRmVjJDs0b=@̥b.nJDܡ rLJK*N8r<~bƺn wPu AuG#7.O%@4\%]Qwd?"ܔh`.  E ũ_WQg5(h$n ũm__=Lu_BhX^o^s"XZZe֯_رR`YI"' JH@4N:5}9GX,K,ر+7ϋ/~ZdH * qѰBCC۵kg04LB(7n{GݓBʲLQEQ<aÆ#F4n 2類eYEAѓb佥Wq+vA ֥a%w*]!¯gk.oEdZ0"M'S!qMAJq7/?st4Txܗ~9Ӈ9Zf As WvS=);UiI h4,K6yV{(H͵sRȐiO> 6k;QR'.:-U}ynQRR4߮}{M[kqA_IfhzE<Ꮞ6I@o,?|w1kN8nZ \?.O瑞 AEV^1}v@Lʶ󆬨_T=vNa-C|wφL~Om\Nc)45ۊˬڽc2Ahl+ ƃl UV>PHh Ÿdjx"Y~ Q;pmfTx62|3]?D|& dia{P8mŭ.8]8u!$J)E PdcV\ z7􅜂3ZHY0La? .^2`pS6Jp\OMx3n&Wtd+֒H?a f'o1ym ,ޑTc@ޞbOegcƋm_ioN2lT`:vQJ$zxZx;l|E0T*U&Z?JV7Ct`_8Wo׮ُ=Ţ,5}/eOԂX/V~x@C+zx.0pګV5?;Bj E3-y+nVf10ඥjB̔£9xɳ>a>@wNܛ\i_+f7=Z* ]&g-;_+\Z^4=1E#vポGTiÞTĤiۻ77#FymvZ)'(f( AS@QRQ$jT"}}HЍAp򫡤[J׫LoہRآCqOo?"INtQEUʼFV}Wn g1th(Pҿ>/@]p9ӒrSyh䭔@q ^^k.b(۠H )q?eEu2@l'ݧFM]u@̽#_`L]hȶ2dZ]q^y{?uKdr KvAs>:o`~ɓ4=gav;Mw[N ЯlAݢKqCzC1oXC|cFy9.F4D:F=d&J޳v>n=3.jϣ̲#3&r7ךνJ 9x(7Ԩ78YhW)+*P |x\c`}}^ɮ C}fl6D0 04Mo̙>^ẇ-MzA7v6 x/((oy&xxx̟? C-K@xԸ0p`LCΟM+YąDžխ }[E/Oն_w}^i9sfd%bؽ읽27(0b<撩v93b;}b-3'%a87 zNロ4iR_OOjv;Vl6+ɓ'WlBf'dŧmۖ{ #Xv]iVo?cP"g]jy!6ed_!l?proښ`X?CB3iigW\T"uElk%.@0KqWjqbiZ?wwwݽ @ڊI/@\ `w崚 {T`Ib5KbRӟhθd&$&kaI⹳ K ^_.lt>RG'J+r}C d{Cmmmuu ECCbX-j@fRsҥ+mpr%Çk#6:oMjZVlF-`ٷzaĒUcֈkʛ/`mWN{#^}67.q]x@F"#Yʢ_1wCjZvU[h3&r6|m],ukM9P$ zuEsmݗ[ u&n&IѸKRnX,qj׳vssh4&2acsoW\nۗh哖9!\b٪y11? s[jx޺UYEޟ;6o)p\b .IٞL]yNcqK>Çw|`LY򑢮,/=16y۪AaMٞ?D8}i6$Nk(?'a]٩L2\T Zh[m?;cy8m# x뽼 D"aYV"H$aYeYV&7( IDAT &KK_Ŵ_eɤ!aa3rLSt>>Ic9yN`lJ2ґȸvjK!>Qbx8 QGy)[s}ÂQ#r< * $DXG)- '|F_`qEF }GN[3G fSO2>k6+Ep|d191@}YdpBf\X6r+#?7E/HZpG;>2urTakPTRV?`h-MC3mǠ9u!u_dCCfiZR14n0 #v}ha07sQ0Gl(<)L q~`] /I{C'KƌQ_{X`Dhz:El:o.Z*#5G.!3%x{sWȫhplG؞09$$i\l#XRP<9):-q^ȨMG$o3yʣP8=4@7ɓfBp}œڈ n$Mt]n!{?^әiBQZZjZT*JrL&&Μ9P(hɼF/Y/I _r g#:!=vݢ(m˭ M:%g#r@ԐoܠG4L*C<,6>e{A·YJwg*h \PY '>b]`4 6ek!,sUo GK2]T./嬂r*?4 !W@` qö׌l0fdV0C|t#I p** ` b]E,kAظqqT*Ur'/?1S{LD e|%1:%@3|V8R;D/Sz*O cffC?#95CvLx|9'Ny,<}8@*i~5Y,n7? z;*Vd.?;IV@՛|b)'k[lrPYE?fZ<Px^(52@ x7}c㙆mN>?n1=aCњ+NP3IwK=N >~q!1KX86i@A@ʟ~ǧ%%%w/ݻٳΈ'BYYي+|||x}^b8=7lh4a~AdXh52QIji#5LfZ? hq̫i4$rsX>o1ՙ!Q+;?b2m>hn!o1յ;<ܹCtEW|ZQ\\ 00kԖ>S=9#nԸ`vq n GG&ґ/߈Gpl8SěH9n<=Z>H}3wwwy|ذat{M'N-ڷfs'Pzxkov_WwO`*] 8eSZŰuC_ysUSDZ~yyΜbG?Ć%K2q)K5"^y߁$8f1~˾yrAa~*cd>e_*-gU+gAƎ|U|š ~i۶&Eg5]X=Yx^2}[Q}YOXuylܑAt^!SB֬6+bٿ]7x{{{{LzZooooo_> Hйt`_ AN V5c߻ u gUIwmoFv8T#KEqƏҰe^ex5=ظ%+wNŇzvXlB|aUJ9N]2:`ԕ'wDb׶ՅcL_s+@* o ~85,dI\\\\S¡|&....nIb+\Cd~GO}xS:wOXO~Dq#gҫ>G{;UAD:7,Dx;3}i/j\W tA.>\1yn2l\6Wjڈe &ذ0bn˾ #l/Ƃsה7b]^6Z9)x/߸Qv<}_jgZӔŰ?E&y9Ea~`˳ #e!#"1țXaJs:C,#"2& ӡ ⇣]5I*g88wuA}8ջ]vp5+!khڈ [9Dվ KKK~j-]bL Z*W/p3C#"JŜL{{$}iN|D觕Mo>tfһ ee'^Ć* t&n&"GqUIlB, G6hU%tȺL&v尉1@J˷+.kK4KIܜOYkF'Nypv֔_i (c]~+x~K~vG <052x֎ <{ݪ]EΌt1O|8ػ@Jמp9 Mzן9*>Ic9yN`lJ2ґȸvWUl-)T z[c'0(G=Zl1Os3 %`t>qbd~g4#q+̪UKF$f'dΟ=?{lp3+yY8_̟4>lNL0P`vo_9iK!lV:4$$@F}ZrvpBf\X6r+#?wm Jj$7q<ݴ@~c!Hn#bIcJj!QEQ5PvA-e5 罳b,#e{f])uUJ$. - ^g"Vf. ƺbhXQHx1BR0ߞ*>+5<%ss.1#lTV9F%NN Zӄ vEb}U98 p^+d̔)]}^j R,&}ws7>k#'!oNNK2jx-L@^oO_޴z̄9sKqkb%@%! ]. ENӴBNrJ)G!(P;?bQ51m4{;XeF`Чl/2Y4j >a_d1Ug, 05tȘu}ٿTa2`)ݝuʢ8d,pE@y[X8Wf7Z2jvqdأ@U& xUwhݹ*^7T @G_tQ䗳 -Ӏ;tM-8:=ȵQ9(f7jB̔c8??PC5|}ҁR<Gt$ş9&?D'ȫT݇?Q~Ud5.pqCS3XF&T*N&'$&;_cGVx$Ӿvx1>t1$f4Uth G^{tob!@nq:ԷS)!$HNt𐝱_ǧ%dj%OCL~VX8VܐF_f_\pJ#۹ѿ_E#7N8p/9QKIHu$!${2>_MAfH qw7ݥ(]5"n!]}NQ Ty l]bD¦Z?!0 okN?]6%Zj U \!>dӸQǍZuXܥ(MzD/Fk5eykٱf%o~_EyxhYY /E|^ xDfԤӇx{+nGKˊ-x! ! X>.`9p/NC@WT̺`ޫņ,Ǝh؟OB>yx=Ⱦ9d|hׇ@wFK5dADk+&=] 7k ëY47 { wSMsr6 }`{e^r&6Sz0j<(b5 nz*DZ@&}}0=ʜ〣M f9P]-7mC\< :9M6힮At_ \>Ns(Ff̉|"pq3ϤY~~Oo qM _Mn${ '2A&z.7_z"]2nrqxsqSZ{|MyJ9JƦe8NH)+M,߄ fr6J)},CR #|7qӕ n y ~TJ Pu4ENy'k|҆?6( ??~tE[∣7,pEtI;qCEEEIIZvqq뿘A"vjP! $ D7{zzj4e2A܂A8h4 ޡ(WWWJEv;EQ1AF;qT*4MrT*AtvZ$ 0$h Ρ(aD~V HDȜ Mn#}$kF ZE}}{2BdAz;A܊l@ĭ믗tԥ~_~1  {)iH:I1EFQnbGI}#n_8T IDATgkB7@/ӎy <#dqyDža(q  $NO6~̲CkW-: n sb%R EQ$n n:Lۇ@mPĿ CC5nO?|ƌӱ\t []Oj=W>GA3>znA=tqw2TK hQU7L<ϗqQ%'M.3n4 m/CՅ wO/n|9#>`''^fNP4 k e3G`'z46^:3݃}7L>gf݃eC{O.{ζJ hO8D~5 N:W}>#͍P?,>]Z7 Q~k-(A PSy>£7Ap>lܼ4`ۯw@Cq>?=k<0,B#g{fߗ|<#'oŽV>{%AqleYo)(@EQWtuoc:_ǻ]͢rzGŞzCԇ_tnEPpL[\s @ Alr8``(Bp'"}&SR㝁-\FEIcxr7-Ai}lK hQgq?e4mo@ -@,Z9,_Z9ݓΟڝ^2@ex}HzϤy .&RSǎ2/C9f}M.lvN2㸦-È?4_Uhd{aչWG\ ج/DD:!a:FCмN(;*+vG:qALmLoI|%oj~x_3nP(@Q]_ s>r$fACXl: ı?f F.WEBN~},~M αy /"T\9GdS$e;'}O":! 4`8N7Ņ}wgGSMGOz|x ?y+0-憆Goym )n~#?E.̮_a|#cnۼ׾t>9ڕw{׿ ZQo//taRkhNd?>s>]mlln^>-/'I1l4q,E4(J8IE=U6N.\2+kQ;x lQn[ &L @+<|]<^>_ĽOQ9Aov#.ޣ[sӷ kw_m,Re lj t=ppڜlNf&)wܼI kRܬ׼S~y @p\'ԟ*f)fp[g<-bz+R0U.MǷQwީPu4iŞO^mybVŐ56rI{y.Nd[U97?.I/YxJ=wlq @^LV;']؝~q9Ӓr vl'8co/Pu|?E&([2i@ %P 4-@ ]Z@QeȤOOl*qP&NB Aץ MW3 {8^&HΛ6s%w5^#3;))Vݗ-ARшݯ^Df&ݒWCY <iUV=v:hcH<.I,-д4E B[|  vn.ciVo`yTfK _>`Qy\ݪpE AA|NA40O9Vn0haBi`s{&\=G2^cī7MQh1Yrc}~zoݨwɅA Go'^ח={ptg0+LQ4M+=G~KC;.]&a)4FOZ#`h!hFv`NQR1qCL~6 nѥae1n+t\z]gwjX3hf :Ͽ"kAL bĺ`XeQSaŢǧ='Os:8W@:.IF.eh(1zӯ E.xE v;8.e$x e0 :7HRQG Ӷ,$ EQY@!WJ$yfO]Ig%RDe;L2򑓻L}.}gK8BB!H.,71<[#ϊƺJ(jwQ䙱A{5eyT* +$_>X:|B %7~Bʊ Tu#9~ϧzrĘ` ZvA)ed$n95.IDt>nd,J$1{9d6bGN KkVZSGI\\,bq~Kfcj<8N"Qr_8kr_zqܦ5# cet}JX'4R]U,pQݥb9ұ }byյ\7-?])c2)P(J|ZPf=c.Ud]σ`4*)`GoGl *l6qRT&5l[/+)J'ek❐.ޮr߾On7X~ZI(ii4h q̅ZJd\Av)xe*cEb й(RYXX8l0TP('}Y+*_8cnI+>#j C#r̨H9GRkvQ4t:Jt<)Ƶ"orߕu`"GUൾ(sܹDA7#7w\ [G7^ hma?N\,W35#cwH secnЖg&Ύ.m׶=%fu='*n˾ޕ7@-dRϮ]%'36JF__ E򪫟M`X\rϤGӓ _U6?39vv>b󶏻;2YNC{x]4@h316%!'vw:m!v_e'  B%77@D$=vp` T((#緭@A^8?\2S/F=сYKRgf8 0҃bv-=K1~ɲ.ѳS&AAp?x`Du50igzPBuZ|+P=Jdg;B@9pH<0WͿe``.o@ -0Ŏ"L  JN>.OOyݻmaEf!A `̰=f p*%0X`+۵An5`z vs0$~A?~6AIR. c3/X="J7quwFv9c%jb!UuA}%֌EL r53{"@B_KU*; |3S_+P9d?Xf1,JETd6י fGw/ ?FHc]elc %@5B:1}$v4;&%e\sgTsv@vHzCl/|\C{i LP+Ot/S.XUAX;f`C!k/q$4@)]OWÍ"ֲ y=N]h#1=H3Le<]QH(CflFcC9ut8*_Vfƽ]`ԗܹ! ˾v 󭊲I4 zC]72z7!HNj,MN2M=eE'n\Acd>&nSe֞gy2/$⻾Aʹ7PKp]8)k?˂eA3'5S_=;v;$s"㦧)wM|LhR `,Ԭ|ێ^9F',1kg;xs)^L:vJh,0=߇.gxNLٕ5{ϒ4e\vtׄO%Fo4A8*9 N eo)0$5Y1c`gU]yVaIhAPҥK>>>|~ssskizW_w6!(,IvP7: wD;1F+$,:M:>}4P*T٨fTXwHgF2G>Fkkר`_Q1kW_*MY@&10C(5dazzanܸѧO\.F^xAD }w0wn#S^az r."_oHj=*"Tx[ wpk喉;8"wՔ3փ5:ekXl>5vւ]uPe(+-DxpssPPxCa |}}#|\Ji&J'NXF(Yu/5|̑esOݰV.sTJVbFex @ MUYAp);wnIIIpVxvʿE;-|>,k-&u KRSx{%vDt}"MlJ IDATgz^S%ON Uo;~([7R2Fmh?y4ř6L&ӟٷo_F#Fef3Z/777Gǚ] *]]ĵ{gW #>xk֗@"Y U|۸3✩{悩J,xu璀#SߣWb"ҦMM߫-À{3 I4#lmųj^YݺuŕFh2L&d4f`H$>>>nݺ}vUǫ'S[4>'ٷx>n-sSKJ[3?k;J*-7u3Mbj\BP(·,rS<B_{@ji*@q90w;ùU5Ƈœo&/ƇﺨprE# wȪsYiek U}*—,_@xiDosb2bF΀d2xaPtFH:5]'3bzz?Yׇ2-jѝ|hhdzEyyybP(|, 0t:b(BT燮(Wd jHҮ-6YI,ty|tU,`EBEyHf:Ό]<-U lm,jj`F(|&M聰m'? t)[mktLL*]=cǾ聠I#|$KU P V6QόʌHLm+6zxĤ^OX`lw$h{#@:J͛J4){o6@"+++f3ܘd;0 cYSwrY{9ЖuI֘ؽ*MV R"@.&GQE JsZǀ=$|BOX]E&4?K4ej9H M h|8M5"[6Հ5)PČWTuZ9=40XcL=im] B9zyyy|>_( B] G[,\"bl K>|eeeLsӨb$+-:,92>>O?G_dz=w8U;.-R" 0\U~NlF`JeDgA$AHڔ^D2]FmjF E;id!JeH@ Ecbbʜ3;EQ~?H$ԩh<}tRR͛ ?޹s}  Vjbp<+mSymκ['yƳS޵BH$lDt@$ WiE"]mjO <(*vh%'r\ϵ+ܴ0;X[2[n\.59T.?S)Cn\.rG?]gZ)%_V\.L퓺'Іkv OlWu oYo.ã۽]h5N觺{Ɋ75ذMUC%Mp៓V|:?U=QZd]alr-~]RS;'T_^jvJ=kp!epok(K8WBg:B֍@,{mXr[ WYYCv 5Zz|UV}~?t(fYZ;.y`*tDooF*V4g[`طo_NNV?eRiǍ'V֢X(v"Ӗb6juqqz,GGGOOOB!@ ݕc }Hx{,_W)JoPÅh]D l[ mS=8W'7m:kv0yG)}My-y.6 Âu.19>Y;Gkާw_ @j4zN|Bpms5w=(^y}K_S~ݔ:0hР888oAd2;wQׯ_ܹPXXw޵k׾I\]];b)//}4YYYFrrrt:NRC !CSpV,j?D&5fx?9'u{ ૗-ZOBވS(kFz^5Ri|4Խuz§_2^ x/w׶$`GC!oߛWڢcAb yA93>4# ޽{ܺh4a8Я_Yfنe)(;**oٿ jQ$INڹԊpsesssu&ꜜN<ۯ_ci6/tq`kG휽lv3+#(ݾOfyόYkRƝ49{ָ?lCsݓF5?~jGw.[ʦWԖO!]œWbwo)oVCO7bwg=ݹon̾==,]?%eno㕔0\fԯQͩ# bWywl0jѣi6LfYׯ\Pi0RBCCsss Cx<^16zfsYYY޽-ae{UZZj6b[_85s)oꝈqfwȮ7/u')ǾJWm=;rvWo1i+u?uפ^ܙu AحЮ&8bƲq!V œe1wh[b =h8P.8>$ڤoiP▥CrsK?|_ Co-K{8H*?&}=~9 ʎ{zx_%CޱWhͥkֽPqAg ~[ ZgUn$=?ڧLľe=cn{Cַ`0N8ѵkWw^hX,VL07ޠi`X嗓&MܹK,{zzvH4MWVV nEɓ' l^^^CeYLgff_*<GiF0|a4Ȝ%Bat \e߯v 3:E)W^~:)!f[;Xp~uJJJR՝slKJII(j)h9I lgwĝF+UyF->j_.HZ<(rRr|W[?>c0 CY4ҩ_Oxe5r~6'ao]/o>7&,,l~c>}s{/_ͼWm}eYeJ`` ˲(/md( 2ww4`ݦ(u:,߷]ft,JPuu,QΞp삙eg6`b )_Jİƫg)Do>?'ngޘcŞ`Fw7jYP=0}KTwz^FGvl5׌m ;0'}!'bܵ/ppp( U(,7i(jZ.WO]!~\?jc~_ιt: e%w"8@Qwdp/l9tLj5׸o3)( ,mEJJ 8qphA(CfiLakgI$+ĎPJwO9噾4LՅ"#OёH{~UyzV2z1^^M^UIJUfP|́MlN! d:[A֝0qk=V3Щ5J\|()`;{u-[ Eq (Th0 5;,{IEѭ7E- xmY]+&>5(W/ +ʂSߏ}6POSHPLW'JW_LQToE< T]9)ɰFP2evdhp͋ny7 /M|9_렻-4)_ jڛ~_>0ܘcPޏ.9â"@6&7`pkRXv<*F~ m㾹nG[TiBC^xQ.$^d0z LVm̨x2G>F Lr)EZ9hȢ\Ą}[e '0rǙ^9_Ye uN;\//|ZP|f{?"KKKCBBw3p ØǏ忳 IDAT7/Rw߼3??|mfo$3jU#⾛5 Ξ/~v?oo|?ett0+e}\˩/Rw6[VJe~,0b1IhuRRR^|*w榹55 hUqX,۷a֊3`Q=4-/Ui]=]k.,/(:yHϡefgOimAQn>5kּU,Fs1//U5=t͛Z4q#H]=:\}|9c_޳{Í@B+|>֭[^^^܏ŋm?4PTTԭ[4ҥK'V8uRw/:`0H$U-lݺuKdMZC -Dhel^2w&>ln5Qx΁_O/2|?|{b]j{6B!sT"H"L&K) ,˚L✜D7p%%%Tu,N+))&zﷰړGS><5@+׊es~5#rw`.M67g5+1iY[cJCb V7o|ĉkVN8yfZݷo_7@(gggTTTܲRQQQPP]^^.@hMl47Ns{z8utpf(e2,xі}_d2lz_܀]ĥ,r=Kw^CwG!f|򫩥tXd,:wc]|-tE_# l@2^v 톎8OMoBBB.]tȑ[nلx:u֭[/Ͷm,ZK<4y+(J"tڵʕ+6k ]\\\]]% y"u8~t_;V ) ;V|8/u9t )+n=ܫ@wl\/ĖϿ߷ύurapwgV}xf9/OnKF=9 3Ǿ]8~uyoO>K}kNY:&Qo0LB={&d2> Tڿa?>/D"Q}daz}s>x<'''"@u6u>`g>KߎŝR:Eu~3(_0kʓE}W6$@i2Ч T^rWiulϗ|PP% WO]=c-=vUOɏϋm߱ {XVh.^;҈P> >OH$6c2JJJZO4[{D+Uج/7M9/1 Sަ 6x惧Bj͠2_AGQjϨ>y+w 8~>t3%T<D(Dc c}"%$SG&%%eƌp48uEkERq=RǪteAee%b P;Е+izꤶ lڴ}8Op7p)c@UG\mUٹf~z|Ӈ$sC7ᡇ І{ВAe[woA @ ږ:wh#Z-4tp:7Hv0V3g-`0۷/''G<ƍ/Xdqqq QlfZ]\\l4s:::zzz* ,v>5jP7)#}gѽv[u]aHܧNG 4`QL&ӹslzΝ; {]vZgt777WW׎=X,嶏f9++cԨQNNNNөT?cȐ!Du 5p&mfKڔ7x{{ݻhʸca8Я_Yfo[y{{GEE}70aBm;D"ԩS;דZd49˲YMN<ۯ_c!K-КtDA(:::o)l0joI4m6׭[X c~ɩfm7沲` cC ^YݫfV{YHxڋ~qi]`04j6i1iiiifivssX,:; 4M,+ cX,˥K/^dDB6*ջQMﲽ H$fh͛{aY6%% 04MӴX,nn{,@ ?eYV,{NgX,a,BF֟8jy 8g@3G4wN);?`gxKN XEC=,@40ڶu+0E4u7xfpttlv{gY,쳜~`XΜ93tPmkE)W^~:)!f[5ͭXp~uJJJRޖ{`[RJJrFkpjh:G4)Beg3QEQ-Mou8 n篶2|bai_I[QO_|nLXXؘ&h:{9,,l̘rm}|>ɲlX dY°^[L@x0hw3,X&-+a"~䲾"u#r!ݶ"(}S 9 NMSk=bnJʟu3n3!q|^zQ_zY3Z,)n}7b}Qq_E}peՑIwjɜy?̌dh03c_/{g򇯯?SBr.aaYkC Fi7{g  $z B_Qm$2V*;U 30$01 1{ڿS/Q>m#k* bg˚8ٯMZs ǯSڙ+زX)ё[p`o)i?4~0aS6,ۿf\ƣzlX6?n6+e?3wԬRۿgl l-j!Bô eMzzI V%LJ;z4=i0U WNELRɣ{6os؛dI t 艰Ж3Oqss*3.2G޵-1,sǩ9MoFmƖ}QaȎK y|Hg)6(T9V@QT_ 2Ə{̲7Ok(sם >e.ԡgzq{\p#{ܡFq)( ҈ ?; MS 鳤ٿ&+ĎP(FE;]T{231AHJ=rdo2&zں]-5ҿ1B_Ogc4hֽFz*8Lou8PMo`6۬*f%jn Yy2 AUgXH$Vϥ7/p {[µC_K Mm"870:@_^e§+l9bƞ2W?.=Lc@rdd2e\zO5j ᠤ9g.n*[)=M 6YGO@lⶀ&wõ͟`۟.z=d=G@Tx@m4 Qg:|-was ,?Qz/cݺugϞ )XHQ*x/CYvdWMuSZue8MRo:J-KkFop::deerC(eI@h*dG;GEŅ'En>Htdh_@je樠 sqYGvJW8V%pePoرcǎ }ԋcy \?ydX#(O2Es^S|_rg\Awi[(iS"{{AHVwgԟaC{?o|k]Ǧ7x[² YU17BJKK/]SOgnnw+2z LVmg^o0FbrCD $ǤYkn2s{Ty.G>[`8qD׮]888p|߹s3<0l>~{5bu d۲(j' rǬ7w/sf_>xDwqk%MoY@qK,+++hZ^߭[7lZV*,0ׯ_R07nӧO+էK7}T_/6|t@@ <0 SPP{&_RՎI{؛R/e#mgδ;|{nAUxzBQd2LƎ{g}x<^7LMcoBæ:vz-N>f9-"j<]fI9]< q!KP(ʔJ%;`aMәwAڳOFxrA~B@+Գ{:[>_TTt-+dRԖhhrҥP'']) CԥK4|Ng08 `Phh0t:݃5UzgᎣuvǦ0/xJ.Q:~דeOtaGr\.?)\.**j:;j6哳tcTTH펉ZU _pMR+@x uD"D"ɑdBl.//ɑH$5&_|>_ 8::0%%%q|w@o9 c?^蓛#x|Kԕj!pK/=׹]QS~qGPEơ u=&84e~bvCaUg;cYUY|nV?hci1qΜ|wh|j伀@ \ {ܠ]9nn>337@(_~\.;;;B0W@ 47p{p[7Pnnn!!!.]:rȭ[lxN:uͭf6%Z5QWAQD"ڵkyy+WL&S... 2txPi5_Ǐf҉0;h pWLU omSQNUz삾cuKz{+h3$ ]Q m/dUg{#@ Jg~"H$,a^֡5yHT 4Z(QLjɢyA?VRhBL"XTTpew]bCwΜ9sfvrS7)^u3 D5=7?`L<{lՋs@ro,Xx0;3OOO܄b14/|~VZkkJ`C8&3=Z`nqBpsߚ>wz8_O/9í9?H{7mJ8YK.e8k[S_/z/lF+Gk&Q^o`a7t RPlZڸTj7\|jwC;ܪّ.UpyXAr<~ x?~/:k_56yGW'V~ ?\@Oc|ngOwwwEW^'[7~)@]֩}Qvj I濬ǷoC4={VItppt$EtBDRPH$IGGG^ߡ0C$M֔QpϺ3gPӫ IDATĆgjZ耩SN:~BLyi<&՝ڋ(O_:q&{>NJFMz8Z=5>'ռ~ƻECZ7|߮\9Ak}}L،=N1 8Qr{{rn{{3k=_3Rk{HQ'I"I$/]\PP@t~~˗(H$I~tޣ9ZiZPTWW4}iRG`2̖1y֕~Y/L{7889 5G xudmzkʧ}\2\Vj=c^o{c4l{^ hWOrrS@4|6xXdcR[̤%nvuRZsxK64Pa҇di#4M)1,ݼaQXxXn޼Aj_&oԹcL/8g  1{뽷HѰU_^QoxmOtο9ZU_#'ݟeOYfX?>#:ui%ex{CgNR^S#35?>wWK ۴}MBdՇ |Fܝ_(+)?*"wџ uPIGfnDMdǟnm]ߛ͡Fw<&./JۜlɆEA%oIK>8'}PKŊW=?;љ@rp85Cٕi΃MӍm,N*[?ѥJ/˲wߛYywoZOxŃ^'Zc!ozz&l{]83緸ɏ~x}ufe-ׇ_/'OY9.mC3a+'>zn1n3>@J%i[N Xnh0Mg_K_(~{?r(3 ٭>?4KpI4'$.)aT@+ko׌N΋vVjnPEls؛)T+D˿l;x;7+ŦG,F%xdήCP]p 7v7O+}7ml_q6ƘiH50)NI~91+I^oq\ iXjX?cMl>~o{ jnRIgCX %3"qgA ),ψM?P@% JF|#k[,?tFѾY9Á? ș M2o\$ݻB%Nmmm;(f;`*M2vFvZ~W"};цfA]_2;/)T@ạ7A}'Nq"93h@8y\q`):%NAQԡM8 mlmTNyzզCGe1WMZJ~Hsm??tǖ(L7ZTElY aj j"_->#)O<mF8PVϼ1~2ɡ. ɱM=fl$Tz6 ֐-uqƏJKx: v֔ϟfPOU$!n\5+(}pp|ߙgT7 )}'si[X/ANЄbiMzs~a:;XT2|6w QqM`PA<&z=m{V]!#7P|vs3@kߵ W=q}<\/,#@0)ȼk O6@](ӒId4Nl7 1N?D4 X`slp9ϑWN#vXJWqtN|&kPG;z֓@?6Qg_Vꝅvn·^hnn/7HvJHJJv^4h\HrJz8N `r+/ȃ1qF!E (>"v!&0-_i(8GAɉKR@TWisbdIC%RޞVs0̣iIPqgLn0fxiygXҸ?lAF>exo|ݩV\>1S# w EΊo۶.ޕ pf^S{5wP 4|"^ȑ#GNqKv(+Py@ˑ ދ l6~`a ]SpF0{9qPmWm?GYطŹoQ'hA,ݴPٻizɳ;/A6jډ=^;Ԧ$7Xc`;eATlp\. Y6{>WhclkclD4oA~1x26?#ޛT}֏oN(T FT3/YLVcc(`t$nZ4[nmO HϪ QЀ򜗖v(4tt`ƽ$o;8}2a|M_LcŬE:-`C m MUR,LT(||V)p$MnnoQq [@);TɔٌGIi]0mLne6b8gΜ~zСT IXG_4} &'w1mP]Q V֣WֿZ"Ӿ¹M90$Ĭ}|}mԨ_rAۻjLBT&To̿mBsc~9 nxڵt57Mi_Sဦ SWF%z*iiG&ʻ9r+&W򻀦^pv'7Wp }a|@.+?CӀpQ|o7d\?ޢ6}lhjFeH̩?^.ӁTuF[oҸ痗sܕ+W D3w1\Y_hUU=eX]R4eG11fa:;7 ZVpƌΝ{g bT*Vۍ9=3._V<|NfF,ri<޺rΠ}&p9% 0ӳQ$1$w9GDecc3%V6om<5l6\vYo \v@-?9B;89ɉpv  -|||1v ޖ/J|33Qc*@Xq 6BnSmvx|ߠ(XM(d %W-= o~C^1zΏaQav|gҤVBzT؁jKr6)\t }G˺:zG 2di(ŋ 'Z6p\''/655溺:C }֞}ŷ: f5jˢ\+1#FW(k۞kB, ]{[@CQZf9lE 4$۶li8asvxG1zU j,z/ MUqX<2;<)hce]B{~~1慙<23,g}n-,qOe[WV*752$ƯwHGS<ٵ2#34)ɆW@Mf܂O wtz(ن$"ir]e`ѠzzzxzzZbYXl{i VXO?,N/ǚr3mb]I~AakYĬ0Y6f.$Je~!Ed$0l$@#d@Ia>!q%i;ZY>%ٗ =J $m/rq3ܟ5χ/Kd?rܣf: $ @"=ݠ-, $Ap8,,5³Ys|+!jЌ8,"ze@W!gϷZsm[1V^~!3ˁ6^YS!FYO,iH+L]fb%ur:5ӇI?ȸn0F&PU \z4 qIEцZ E?lnRȥ9I%fBn>ÛCv`s${^O=EыEn` ]3َ`qv$Yƪ+^uub;;]\l8bbx~Ȍaaa3Bf,^ [* RrvIT*,Fz;WIJցMcIΚbxxXu\0bb ޖW.h&A|Ji IDATfh͘6cFXLB[$=8PSL '|x |fyD $g㛠=Q#:1V~!v L(yUyy_[h?^u!b"qq# GGwGƸ98N#akr_/96+7hv6b ܊)o7; .K_w  A%is _,N2;>>< )cO,$۪UH[p`=y`w(+z4:Vȸ`t|\ kŃjt]R rGvzhT*P.vcEQF釹uV[[~. QP(,Yb*QﯮV(&qi&̙3; y(DAiZӹJt:L&zFj㹸xzz`,7_K9!qL U_,;}emG5JKpgu:n9~ $TCiмd&L6:{ڵ4\o߾^z^ojj2mt gg'|R :R)JE$<P} Jÿ(pbkJ |C|::.|40k%dE\rJjAfo3%HJI)ݘ9 |,D欍`#H)q/j6.cVElYy~wvRg~PcYblo-}"֙s՗O;_A5jҥ׮],.bu-$ ,E~,3ps>'߄~(7 ?~C4]H{VItppt$EtBDRPH$IGGG^ߡ0C$M֔QpϺ3gPĆgj}z}7z<Z/4ܔn0oP<7 XȌ-q3BCq|fccCis8GQ?NwL&9P(>|mGFbQɬH$)lG)b@X1CSWxp" L t0x#خa 7%N6ryfj͕$k4yr$ӘخAHBiT˫NgVDyIӒ quLB=\6b+]/rpðj E\R3nl078.N+Fh>@Aٺփڋ޲)+C<@i"OK@S'`wn˟<=igU~>W$: "Z)Q}8Cg==;;S.ZI9{Nhkt:B:t?SOy{{ߥ7^$ ݼ$톒v 33;Eqp!fOܶ?n[rzϾzoW9 &tFX:gJ`jbWb:¢C']1a0zewQz=EQΝq܉ˊqvw涟/2?9T  YA37QNJA& 8=jGl!dsi=E3{{EQUUUK#ҥ:99L6mÆ 4MtxE7Cf0D4_yf>m7uφ#m^O#-+W9'u {Ԗ<$IEv0U#G ?xtIv7Zs@68.E@ H\%~ [qL#ϊDVvTyK'f_RyH_A3?oms^I6_׮78Z :rY}p'(oߜ 6}= 3>2sSN%&&1nl6{'Lr…_xz%70qvڐ-'ܙ؝ۆ!guB+T96)/KU}k`/ ɉo`we*g xENj۟ߛ9ڸ1b(hfJz!z4 `d+3W,(;/o?~u~Ei_a@[Ғ΍xL~'&eSos^z=nimϡiرc..Zxh UӦM+++suu4hPC &xf?BEY YLRyN(C *pO!*ӒSҌ xLZq!$ťD@BHf6#_gXnMid@x)?G6|>547xEm:)|tnT0vZTirP&NIqS"Ox]Q&goڍ${UR&`-';wnժUE,Z~ f4izРAoƦMX,VHHH*^d^Ws&% {3IUeGMg3rECy^b:y4ø6-L.Ԃ a,sJ6Tnw0v¥eދ6D8PUGƊ)3:X1x?nmm ^3~|s;^4ޱZ:g@s(kqBk#َ`sBk g.{of BEFeMtZ>vؐ!C\rȑ#6f@f&W_}5eʔiS#yey/-|?_  } @뵹b$|ugڼً_%}A(Xfqlݞ/ܞז*/k)RH< Iի--- Emmqa O8q{;Ej4@rY,#0?F]WW7n<=?Q*m+䱙v({\Zy.r]xШ k0{r{\.%+\; #ohPӃ5*R!.QH4M>A􉜩Vg5C:} q}@mbZ~7v_0G8]&O?tŊ+VHHHذa`zGEEfl}6rXpx!d5`@AN5ׇ aҴh3 bo͗A0a}%L;v F ,ƦVղlFVxgFAmllX pK΄lVkgg' E R8AX?V!zAL̋/W}Wo~tǪ-o}y&ZXR4ty |#բqKg,`9gUY,u]nzQ~|zLy>~yG?;x;F8X^^#x>n\xB uzbdGk$Ecږ?=X^^is i׳VJe~XnwOl6fb,aRh.xv'uw*OLȬe}}F]Ee"۶mw:o9;!%jZɁD]N LޞϺ,4.tᴬlmnjg|dMsC~D3 lL pxe*_}?h9a;{QA\k5*CVlo!cܫ-"[W06f{9#(GEEqpon\xK (]x55 ysӠ\e5yĞWs/;rּ8=JQ/xNE%n[aQ'hA,ݴPٻizɳos^bX,رca;v,(=$1 IDATrJ"I(.kmmmmmmee3ri(JPڶJ6NZ͐WRT,<0} |d*5,h.]3+qA>d&b* NR=/x|ȿ^d&&0qX&GXmOj7рxtc۵TH!-da?٦B堀3}ffJ?}㜬maX@zIǿ&76aM_a|WzLz|M_LcŬE[/lݖ0T%Bf>~FhP왴RXHa,SvK +u+QǿZ|Ә1V<l&n+{mc쭿;080 IK$-.iԆ+ʆ_J/Ffb505;&)@E~3\f3?8/5K{9瞹ss9mՕ}Kukrfo]+<:d%~MV|# N <ٳ#""ƌsP@&趯V׿T?n%mY}sњ `ԯ_[3w\٦68{9f x3@]aYV-.fYHQ^qn\mo<ӖGѴ_U^1'g~5An0K9x<e\el5ZG$YΙ3{U"tsKFKLYoo>BcKV`0 3sNk3nնkaj ?01+UۺAk7l]?Ѷf6@ w9Ш<sH$5,,ŋo (a@@jvZ=_ܨka˟E؍*F9Y]^8:({=U;8gc> GCCB>0 J[CX,@hNΐ%F2P[X.@vj)wrs{P<% ǥ1>> rS%;WY%sCZbRst MMɪ\q>Ʈמ˜"M)A;19PnZe[\?~7|3i$pΉ^K;E_x]ƬA6#(<ԤK8nFЂ̦&5_l/5>Es=ky@No.4/hRn/?m}JuhKtzfl̟?ĭoRbT/iTjRLQ3cεEB󎎎ҐspFsuۦiZ.?S?cIIIKKE1b̘1&M彼4AS m<O"566^t92D"O"lmm%#a~,m3Y㗯r:]Bj4_ {i)ߓBn#2ym4́6z:eܹ۸4$fʚ|8'.-6-r ZĤXhs?+bS0.g6EKM9'̍ ni ٷ: 9"`S\iy̘1w?X,y=ɩ-AIDP(qvc`Q #z8Wwy,B^֗ ʝ5}hP.˃9vg{{{AA2rpq5N?Xu9B?|'''T:ee-'?%y=Yef(~']YV(rA-ޙN8}|iPܕ2ɠ۞j Yu~Rp2x @3:=U܍i j!b8sg*$"^ o^E QDo u#8)M@Iar #T|is3ü&tArn a"Ҝ%}x1n(ܗh>af̘!Jw2YzS(D"㸣nѧ`C]P"E(ɔg㑖>01[W.GNQE=̋?N Hh &$=2TWd$й?rsp99hsE*,̔G؆+sh>wH EpR _o>ϭQx?ֆ)Dwl4M9200$55^zaʥ3$ظN?}y0/󚁙 G\pa˖- YYYǏgƎ;L2@ -]Z5j"aSUӬ ER?6bϼf5\j \܂]IA_ *gƸFZu+ʬ M;LQ^~s S/:t)2z6htjWyJ@qIKɘ:-96g 1l+pQ~ ~RtҤIϟ?q7!pK{{qM/˥Rرcimmmootw>0d|;;;Dbcc35Dp`;=#љ2i;g7 BQ0?! |.i~b|$\7l9E'՜:0[pN d2}.ЗuU9wnfJ;,iΝ[9g޸„!)<'j،r}_3K4W;=u#.mݘDN`/4ڌqmrU"6YWr9\l ʣ2ָ`{jnǺƒ ܹS'wϝ278HLOOXEsB(\h7L+L@\"&EE\f̽WÎ33wB#)]'<"kb֚)sp t=5G "R .<!x e_>q>ˏ˃ڵ{ر}J,姟~O?ߦX2\V5h6F̹ӢUSY`& ޙҗg˲555&MB? ?'0tq^׆acѢ? @[-w"DfN@c<cE@RȊʇZn O.^1鄇PjCVP|ܥA\s-o a(0 Zamq%wV}wZZZLK/GE"~>;vL ,_^ҿy"7 7jUdX[Y&ӪrEŽeK_tu8Fa úN>8Ni>EQQohA";k+^6W8/;[s<½EծY_u_1,B$ 7TkMכ;j;5uEE1 p{XV|#KXss]TU%WՏK#Dn p~),,^ei}˲z]خc 7ZUn4wlӃ#yx6.rC-s3 Rsvڹ5#5(TNр.Vf@C{E$TN]w555`0Ϙ1~@}0CbsO^ W/*s_/]99{Ϲɰ:@[Z* Pl,2C' z2 zWX5Y /! q7_K j\^[!Bů~uB/+|i&$YYY{2/s(br7-@imluΊ72 +t~pl~XZfGab:B?]UVVe]iO8qȑ.ZUx{_ ͬ׻JWqykNONJp6*vg~hhjLJEO x׫-j`ʚ,Lȿ~BlwKNI5U"3^o?S7m׶E^QYXc81XU~|vxR 9KB;^t,v悹/;V ؀Ɗ %V!Sm&+%HwӁ:ہfy8;{L/OV9cTep4ϔ]J uGYQ~ ?mo3KC>OfLoջF>KIIN6 saS8)@Sj>wH P@^9 4τ"5CnkxtmmCd̍Bn/@ܠzCm:nә=Viy.ʎ[s՝KNl0~xn7>|u4/JJIJHHHJx9d@hF{1+**p͟)mRԆo{%[g9 ?kQBBҦQ@QŻngqc:AJbO&>9Zt&Nو|D( =)޳s<,%BX s@CYv +elc4 qSomY (>"?g4_ {id̀KPm 3wQ@SIIcUn6*bcx|v`wrݒhGP띏pZXZF5FPݴjɓ=<ҷ3; U%%%-ȌXdR۰5šyQg<ҥaz H7u:΄X,zqj+nB4SePlINy"=- OJ_d~RN b^WdZ2i+==<|yFS]V.f+.c'L x*$B>Aj ;HRNYd|rͶkR]{mCD(o鳶: 1U3ݫd&l{]gF+A̕":ٻ_ l-03@LFᦸ04 ѡ3.UIKP)V p[(j]~̉]NS8lIDATsSͬ5۷\U'lM8欛>F ܦr 0 ôYaal܌ 7F-I .m,,-ݵ9#6}_ *?`n23CU4qutТu{@ڝ;*JN+r2K9d87SI8{$l y@ַ8(M9ɞ;&#;rglVlݱn[&g'*++ʌgFRgeX F874|ï?EŸM%gt5 %5Yޡiyr KKr}Nzgհ[ނ$+ݵuH*Ӎ?#wǺ *LQVqOW4ÃdH9Ise+_Ύ(`4Bi>CJGAwҖi؝_,dP60Lr &pR'mfkhj*vH+G| ,'<1/;TWWWW7hXɔgぴe-4W+O*afy@/B Ef@=j-䉡KjT9u&/? oR_ :狿b$rwv~=#CKrs c|a?GltʈB E XoqH6wl9ƇѹہАǜհ{w1(}m3yf?/NV䟼3!cV`{{ )М=u:֪m >Edy)t!益8 7~wxW%"H'8$<'QsI+]l>ٕ(HyY./;b3csU|׼乾3#3%WP0388888 k҉мoo9'n C'r@F\\b#bA;2DyF%g3Ea[UUUaBcq9b.4uzL ʺ::FUD![s4/!a2@D@do+i4Mw_`0z`jE97yG,n^,;zօ8NokuzbP̟f_}w MUz"yg9ɑߙǹSm:-t8h瀌P$\,ʣ2׸Bsjhfi7-O `a4ko-\(St9kVBa *2 \OiVi!L߿U#?zU_ԬRC ILkSU/xd}L*iVk-+MB3Sɿq7D/$I^_Ԇ [V@!/}{ykfIB7v)g\%bu4?y)Y>kJ/( m8yf b/]? {#UΗ (0 HnSe &t:]r5hƘE@[ :ovtGl OZ8#< C= o4YU}΄,Q44[f;}@FaXlZkeUūw3o_j2mD>Mf+5y*$`VjZ`dPޙFgSOٵ#J G\vw `gކwi:")-T*\jQDlK%;y"lp\EAn+|kהަb+ӓvdH{?҂ؙ\bwXĹ0U+fqOu"%^ 7ք/MZQWnndԭ˂FEAȴ?0h_n bwkrXMI]Qy:"6542mA] &M׫c/e!v_$g~LksCUr`y h &ɺXEϧbNZ.+H[f#Y[4POĕ:x)!󐑸$wS|HZW_.yщi+vOWs_ `o#MټJ#ciFH.{#JfS_?WM}ESf<1j@y=̋?N 2_ 3;[ʭ <oҤIK,뾺('''T4k8 q1j$yUtҎHn9赅&ֶ,7?ji[1LxJѹS&1EX37tb{G36Fe"+#*zpGBxk׮M6'rQFu˽"+ugj4PkцH_42qC.)ܼcGQ9yPĦ&Fe&70k (HMy"nm3-ϳL$:bŻlt s³ͅ;O*۩I Uieb Hؚٱ,1rĜE>ýd|Q XC5gxD)݁? KE=DF];F%n|cȧ+2#Uh:iSCҊkjO;Dx<;;;T1B9;Id;Dm `x?U \\\tYYYկx >ߺB}ZlYOXG4TbY hHd 7S4eRaո j!IY%%VW"0Z=nËNzۓxqYuW˖YJѥf}^,{$Gr姟~O?ݟ=w{gfX4imi:""/Lɜ==&ӥ}of;>_fYn-kql$i-ր/ސ1{kZ5J4\8@ ~&S`0Ta12%%{U3}ڳAݵv48õ}C|رc'L@ C7 0VWPWН{@y„ Vq{@g`0|^;v,4I a }G?Fw rp P4 V+ u/woaw}@ XpOg A2 pBx@@aBΎi. zryOgDB#AS@+Dn W@ @ B_!r@ B@ }H0i@ V1 ؏[[[@ UZ-!@ j:/@"PVj5@ px>>>I$VR***:E0C@ e ?8~{/_^~_DDMݏ7668p֭[=]`9r>kOM /³gϾNNNEx:```Y͛c=NQ] x9z'C=x{@ < ӧO>}:::Z*~w-#<}& <dz݈;wM6Nϲ[N\3jdYs,Zj-[uH6.  9JꎦOEkB܌E~xK.zgOLY8wZ{c_EfQ}t@  |73fprr2{Avj?JOO+xΚ׽X=O~mߠt :b)=zAoggSO}&M2gX,t:.]&?,r,ky>qqqqAIQSG/Tk7K6ovV]eGViq,mmߜuttIքqj{ sKy+Myj+ݭ˼lZ-^uz3VFCӴH$3f̏?h,/ Nc9aw]К!+%{2#{˯;bG#Jot+Uݖn`Wot8m}7cK>+*qxs /GO:ݴPW̌ J=6A F#Je2MEqQ<Q2'Q8{)Cό^W8yo3aic=|2>ta:b<R@q4kȜ\P=q\^Pot|/+Rݼ xwٰﭙo,V__Z9B YwK@x8iamlmQ.qqpښBGS%՛Y N*(j/􏆚d-ٛ`5s,p iooojj5  Ͳ'CH$/^<tGl{#h69{&eoEeN7o{.H<ƲSWժ۲7/<`/`Ϗ*Uʌy:gFTJUwѣ9m HǕ7T>X:We|i8| ФSWV @ 9:xvYФB]ԩThjjI͗vC&.\gϞQFpo^NzvZuUmF0[aujJ TeNdIJ7oO'Nh9Je^^^kkk/u(0W?Q|QUt@ `hnnQ,skGj4m۶_W2zhdƍ_~%{@ ph.^x7o.\^Ï9oӧ?裑#GO0A(2Bꫯ>r@ A`0ttt=z̙3"7 ݽ/Fsʕ#G?~\x{{?䓖f @ 9:ʕ+ΝtRccV]?lx<@0nܸ)S3  @ < p.!TKKK[[˲\tMڎ1B"H$ˀ 0 @ K:}υNt.,-̯?'K @Vx<8^w7 u @ ?.4dffާ'@ 233{k"7@0]Pv,>)Չ IENDB`Settings_icon.png000077500000000000000000000146151255417355300347010ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Overlay_Toolbox/Layers/Overlay_and_Map_SettingsPNG  IHDRS7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:::==ڢ'W,7BƵltL]#]W^^F~F59p^z{rvsR Zu5^3E/. %uB6IENDB`workbench-1.1.1/src/Resources/HelpFiles/Overlay_Toolbox/Layers/Settings_icon.png000066400000000000000000000146151255417355300300350ustar00rootroot00000000000000PNG  IHDRS7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:::==ڢ'W,7BƵltL]#]W^^F~F59p^z{rvsR Zu5^3E/. %uB6IENDB`workbench-1.1.1/src/Resources/HelpFiles/Overlay_Toolbox/Overlay_Toolbox.html000077500000000000000000000064401255417355300272750ustar00rootroot00000000000000 Overlay Toolbox Overlay Toolbox
The Overlay Toolbox contains controls for setting the data to be displayed on structures and in charts.
 
By default, the Overlay Toolbox is attached to the bottom of the Workbench Window, under the Viewing Area, in a horizontal configuration (top image).
It can also be attached to the left side of the Viewing Area, in a vertical configuration (bottom image, set in View menu >  Overlay Toolbox). Either configuration can be detached from the Workbench Window by left click + drag on the top of the Overlay Toolbox. To reattach, double click on the top of the Overlay Toolbox.

There are 4 tabs in the Overlay Toolbox, each controlling a different type of data viewing:
  • Layers controls display of data maps on surfaces or in volumes.
  • Charting controls which files are charted and, for data series/time series, which data for identified vertices is being displayed on a chart. 
  • Connectivity controls which Connectivity and Fiber orientation files, data is being loaded from for display.
  • Vol/Surf Outline controls display of surface contours on volume slices/planes.
Tabs and files available for viewing change depending on whether you are in Chart view, Montage view, Volume view, All view, or Surface view




workbench-1.1.1/src/Resources/HelpFiles/Overlay_Toolbox/Overlay_Toolbox_horiz.png000066400000000000000000001706141255417355300303320ustar00rootroot00000000000000PNG  IHDR^wmf7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:gC@ `޽m @ < x=͔/QbMqq]Iqjʏ5p3emko$OOl<PG?^T򤤤~|G233rR<|+WҡDDDوݻw@ 'YG4G6iʥ>NX Ԓ'ei|NMh&V"Ⱥx <"S68g7]3ژ=jɡrrrd2L&h4ӦM~קMh999bJFDD]uڵk׮-u@ s k{(vռ.xgc5!!kKaԧ7 P 97 tuuuutOC~޹SWׁ?EEQ4d*sp{85,[֕ZZZZZV&(<ڨc(=lI&W\5mީQ4TPא5Qst>@ )5iaa͕JeyښaQpi\WW׷zTvZtڸhҒx|MYϢ7\eϰUgGPcogԜUIgX]{Ng*Y4-sٶO\ GZz['׎ bY>F2A pɺĨnv~<7I>c1K.)mI=}>;* ?|wbrWU=s}5;0pѿ+'*@S\ƍߺuykkpggҽ иqQiܪ DDD$&&<_S\@x\}!E|" *<-n q~˫k㸻 hb&BFx"A-[ݸuZ ʾ0ֶSVDc\I~#-.Δ#vj3Mx|P3竡WDG)"{gg]%&lU%mzdNckS|)ҩlJ-nܸ뽽322DEEz{{ݻ-ZU>RjҜ^).@ <.mvbFI}6Ll0laAvv钌"C oj&b1`i0K%EK6Ŭ(Zv<*,SM?ftؽ"2Ȁ"N, !w^vom ^U("8} Q BS6jئMӧODGG۷O&ɓw^sss6mT}H-Q: ##@VVcӲ;?y_`.}Ƿn 49Sĩ kJ޻R֣CCp %^/"#4K AA59oPx8:EwUӏ Ag|3{m啿7/͕}xnWf~7 DA5{#^>tbS"ejMl|]57S.rS6jk.L{nLfaa@ڒĄa___ϩqNZ\\,̤q aHݜaoI h"E6H잮(A c-QlXM¦cQq~gѝ9xǂ}NeQm/& 82⿛cݶ完~2#ck4e~(rH@p~x )ȋihRUI2AߝhiVg ԙ3g8p 77WZ8yw]O@ ݽ{zrKˇlX4$С'-DY&=D󋙩|ܫ m'ۮ;ج.Re>;6Anʲ0+ɛ#(U2wr ᤄ:3~UnC;ksVXdjmZtFʩbiIB0qRe6+(8u kgYnʁvM*^ȲX4X…^qS#3ک@Mk.@+dm\DQik}NHU4@0U'.ja k']Gda [6/@YZJ= ^8'=`&Ǖ4q<sv7n~ow/l{n~|̀nӥp2nNWӱNlNJ_C&hN#>i`n/( t;Fnnk7Z ߵ⡹0ɬž/TX"9[.  ?8<*g_nCw_sMS)`{hNQY"s(-Jmc1?|HX>}ۧ*U56n_/9OFuZNɻ:L:ʮ%A6~\>Im"o{slʊyT,1mJ>ݣc#9 GO@f-rw/W 09DLZU2NF-Glۣ*kcN>kEJժ}N.=vddsS:nqv={8HFý[ 8h.;1cJkkSFF9G5Gv5Fý̫}<@s}疭0GÁs/א_Z.g+Y!-R@oHZ)Myj3GY:kx;ՙآЫ}-ԫ־‘?sNn>9ʾ}z냆/shv@̂þ?fsӐ~SϢ,-%wT\[jn.2CJiFOݧ>bTY[)f,oڹdTi& bV|6аsXD{gҢ@wec[Vs!`sТ=R&n;^.-G#I9:j+Ge7:vo\Q-`WcWaߩ%9wBgh'v`|Ϧ0E늴%p.lU 7S$]=zNzw3̵ZiJBjXqKi޻C+֠Ֆ.6\ۢ|9 }Em!NMŅE:{eԂkt&ϐk4I]>/[ѤG6%;y nn5tg鿓voQe( q X999W^-*zpGD xܿ[Q /BiQϥ[F t8mIN-;&dǝƀ~vrwiJL7ŚPopRV>\J5Rc Mn$-75EG{`xC ͯ'loӫxZa؍K$M{w莫M}|ZI&%W;1YvŵR9Uxsߗ kq:ʯQ]s&L](z7u,`?p`{wXNUܿ~C&vH{g~:eO#rxN8⭁J<Uu4 P70P (+w=u)d\P ԪD'Jhhi,debh G&L4]`எXf<)Sr+RkZ4 72iT+V ZaTao*EлitEiv?#d `oNW6}u_UJL o޳=w4{:(mJ(LLbHLمgZ-KnkRI8x1=PP4Ps/T1Uݝ",޼X4n(c,|P--3 (\%+Dd,E3,׏HL,M2IQ(J_̳]/ _pnԨ˲d&KL&Me e"2)xKCQ! V)|P@MfKvlw~J94;m7 ,DDžjY"@(qIn;zŲ]cz^w6U.V%,1k/{G*c4 4%)bi@ #\ߵ_Hߖ n_ HTYL.7 0!+)@4ܗEQ f ̋vfkDƉ`(]f-N34 aArk{err)@ mM(24P`( CØa4xgRj=~u2akHQ)YIǜO%>,1 m"=}ǟ\y(PZY(p3aA(LrAم;hխIk~#Է~9uCx4`0fI(6D5*-ꚸ7kE-KqA@S+3#Q20с<˜4 @0^kI Z~w ĕ>W Ύ|R""q,EQEאQ\\(dJ91&rFҠ-C(PWl! [J0/ʹ4ЬX@o0-?6t^zwofAJ Цr4jhtUK/&-[PʔJU)ծIws:֫rtҮMQPҶoG%ju~f?{]E)+ yjf24OIU+o]߹[lΩ Zu,4M&q/;9fwaշY1OdlS\[3`M#pGq{ s?tijre]l. ? l]tEk.f *Ps| l50_%Elpz{2)aԩ~NhaXKK%&cY2f ' ߲ b͔3I 9EId"풡T*ƍ~b@x{n6.Z,˰4o<{k`afƂSk4PX3F~OӵiQX(LjXiuX Ymz;bn^5}C+,1f5R˂ՂiwNf6+#G n흔X}TƲSKK;䏌]Up''LX7ʩօ%VR^ݽb,f*K(-mjҢ4>z(F.L@xQ !eFT杠VP*!c!->h@DBZڬۼmFOe +KZ&{+v^k8w}NܷCm.ʲ :S4kG)Obl(4M pرC>keMoV񳳥h 4m/:fW {̬kkx worٺCeG.CxѢ"%MK℗af͚Ĭ7ɫhyƞDgQBl@JqիWϝ;7qDj>᥇,C!pڵ7o  -Ivtt:thӦMGNxy@ y<@ א@ `@ #5$@ )ۼ&::A @x5 ]VZuC @x6:tHA& @ FkH @0B\C@ |7@  ASm @ < eŁ+8Q=k;πԴgm@xykHg'=-)ؔslcpItEwaؐuk[P07d u4IXj$$$x j򴀀ZMx$bfޘ[NraY5{w\84$$4!͹`MxxkXqز$Y5k^BVF{Ʃ6ƤfUvjPߥ7fo܄YHsѱIgk3Yʚ&gl$ii̪?UX:i"E _OObb~qTe%M\s>x)x@Gnz9>,aYf"\[22ztؼ>@׮ASPѸf-L \͡yؼR7~u> %kC܃BC߄9ecD˘Օ}1%yvy9qPwaO Kq(޳6Qޘ;;;;;u5J(9,I@8XL | ;;oYiN5/p_-4;k8sR7JJCf,O 7ayHɔ:MF&3/v%3ЫK Gw,fq̴]J `{X`XYpz~vvGҷE+yQIX.!zC5աJ/2Y 9\ֶq0qtF}auh ]]K1j`yƗmr;1yi=Tct鞒u!fvc-7Z;sK!wv+/t @jưM6 Nޘ3D8;RS:5orۖt x#9M{ :v s|a۬}<+VҪ'GxC!}8; WcXPcYvVER-B7-ҵC)߯ k[[7tP8tXܝy-Z{hݹG֬U 7@ΖQu]gȭZ6wǎ;&%]QmmY I">؅U |}zi g%%/Ez-R$fqW@u@t4*)[ϓz@w36gg C^Koݰ@ *ȯC -@J1fРVP[%׿4鵀UANe!~*5,7QO;% pz(}tXBul@wf |;55]Ss (xqB]݇*۰`:9r]#'i3h5z8M_`mJ}!aj\:nwabDxbft_x*:%¯ѳJ9جYKrDL BЪƻ| ~XGoo/Ge/SPoix fGBB2̚k }nXPOGϪ$]Cs֞@xtjy/&_T@@c}~Xp0.j 3Jc=]c~qX+PyJN)GIbƑxz,˾<$~Ѹuiu֪T Pe(H8,lyyQS'Y?z dY{a]׵C O"3t[^iX5P9ܺcNT=WT> 7 ( YmqX[VZYkG+9K3={MY0U{.`] f̬ʏ`96}|~ 7_ڒ+͐zN^XǁPeaÀYK"|"y}|ֲS~P6,* 'Z>{ >,>sOfrs(!n 8ɥ܆χM_$Z՝ (cLG8xlK½l]u^a^G_sJߣ?* J^eOf gĶ3)y:ښtZ;sRe/<>zBw͎.Sfn@xf%q _j1L @x)P~Ȗ@ (@ sMQԳ@ \PP~:6zߧOCm۶-]6@ < ~}⽰9* }||hIߕĉg_mnn0* jO?-hشұaQJ]?MHB)ᅠ]bHQjcwH?A:q ڵOhvUz}zzzF#""5jTϽu֎;-[6sL33:@ (_T-"^5*In\U/)l1U5z)^|Y͎֟͛7zIGaaa~~۷hbҤI޳TE9::?sllQڀ@xިZH7E 8@Q_~ °,D˗kJRy>==ܹs P(i r"Y6)(Jj߷f4^aRGF:4zj!sMU]iZrOJU*URrrÆE:ujQJɨv6o۶y t2{رU|ͨ2YWWz`6wyС&M 8A8NӕsyOHH#F& A:v24MQRS(B!" 7 ")`Ѡ: 3󌁟e":즪FFν/{f3ϧQ> {3U~ۻ:ti7ov&,ĉot!kR^-q';wbccEQܶm[4h WxQqsMAՕyM9?n&w ylQs&:^ED@ŋg#?<Ҡ(ad9XqU/SSS++<^̰27oZ[[KQ(*H@JJRGIsU7mdcmE@zҵk1qP^m-iR9x89w=yԗ4냤 5;C GEy:/=J;%ȘoB|=sMKg.k}YY6ǃ_nb_]4|3Eqע̆ҘCtΈw@9!l8 ,Y-ʭkogGPwjre9yYIF¢-r+Y5';&̎V _"<ˇez4"A }-]"ݨVȡ,6C?(pߡ)v",y=0?<) RYn_-((JO"':+EEE҂Z4o|$Z- |̺fރPϔ)It9 Iub^5ԦGS꼛^6hjjZ8(|Aԩy;OڜMC ,ջ憞`ٜ{U⽙Y&m`/g[7fֻ3{CD 5WT(os+';϶y?=tk>Wb}E^u6^!+)|,ݼzC~e;}?sV 5cڮ?~3aܝ~yɲ gΜAW<\ t?v]z̦aZ˪ݥ,FI2 /ޛ6-Q}xbZZZÆ QU>G|@jOUyÆ ߎz(;SzMqzZ믿I> d.?PPS܆ɧ6֭v;x!j&&& BeSEQ8NIyx^9>ЏG/ܴsH煬, WWFG.R:::::::9;XZ6wvrttll+D\yIpttttrrWoX+:[XK;JÑ91x'2RQcƎ͕4qٻwe8{8;YOJDF_m/EF~;yo8XiWlRהl]dO\ʁnWG}]W4˳BI^|+?,zR=]~dW]o IDAT=U˳Rxݛw{hYhvjø{(J .XRAJi[,P1r)>I-[͗8{_^ۺC.rF,3ٰiʠZ*7:I[֣Oncߟzar\ʻ(ңdjie(> èj+++tr <3@W|GBԢ-l<I3L,}{ ٴQ> *AѲj墈=1҈5;g`"/Y؏"w`. N*؂ yK+AL:w>^Ϗ>:+B?גT䛷}ޒ%l$ל>9bA1b.#fѢa;ԱEIsWjl(:s3VĔ3O@Ҭn:t׳AuMQaμ:{ p4r"4MW`;f5O6@Ӗh ܰ«zoE|1u|#ts"%w:2q s 쀾"wN4MәtZc~jJ"2݃ `y; O4c-B&}!ېV7 \9E#YQ!IyW/ G3z;I/O)7 :`ʷs}gQ}SI34M_;4lk$,*?->Fxx-XI{̖£GЦs)aE\b ڛMOPqYC$rR!s0dj즯܉Ggu~rA͋JS"I$;;K} F$Wx… r J<}JZS.mČ =PLgAD |[{wW#@ ^/QEh7' :kKӪc.EQ!Qb I |ևeYϱ,r<}MS~薀$f? flWcY '`ʧU˚ p,Zn tddz.t`#O^r1ƂYwaā-=+ip];/t"wfߑ -cηgBn5j&uZpoSmZݗ>SWh ?y[kؚ7(1/&rtS`mŇvA-cB&3><"h=!-9ΘӠ {Bck7INry;Ȋ!}weby906;-J/k1h6YYU'!aYU9* r%q"ry^=p65g*Jlw ,n@"ac  76пE-ٟwg9~-qm{\JcG1mf9_uhzc/*W!o֮w͌˱۴{.MSϕ~KS6=?lQ̽b[!XN@ 㞮 FvzugeucK|B?8eBax7N߫B26wAA@0}oxMeyXiiں, cDx5|/ ߴ|rlkC%2k>s Vfj̣潧=I;:1 ?ij+yۺеֶS޻{# }7C,[_孑n9yNi :8_O ̋r|K]hYp9!+1ԢR@JgV*y#V{ Dǰ ޞU*W\˵lz~]tl0X=yW\U-%,OoTl-XA` :8tSΎFϦ=lF]뷰lnjiOe:+_u8-)QP)+5=`j27uإK3c VYJWNM \ Yk =M{Krdj͝?]WIU|E蟰RDIv:uv t{iwBQSwضW6cyi>pO?hwۗbTRjic6%%Eզ!`;2snl_rjJj47\:аM.k"B7OnH8@ӧGM=R"Tϫj'런8w![1[j7~d߰;&G@t_]Kx.ic7K}"YeX5 Wt5@ͨ.}öEL@oo! m[&ͪ>7xkYO]>(J_4z@0EG?pme#廹s0||<8 NrS"5d!!9Uk\k4kٲe~CdUЭ#z`ýӧ?MH+Wӳi7ɑ>fB]5{ۖ}گцDYHNN)q!Q ZWAߏke& Fu7{RغW6c9M%uBt!6Qj}fޙݫK~/^su7xU,_(j8ܲ*i4hVn [nMLLq Lbd拐 $b?L56-xOX-_&!e m+1J#`ǹ۟o!#@)_*Ґ]LxٓrẨP߂I%#Z.7mMaMqZoFC| \sWIo! o=㠳۾^ NMk'H9tCZ\A ?1xc[BD.kC5 DbWBr@`P0>Oyhx*Z'WØU٧ ?AH}7@#D\5"! `w L?5#]߅*diWcXǍ}gz3ݟVbI/%M+`eDYա6",Xڡ5\qoIەalbg^H"Xtʡ^RCs @`@fJġ+eF Fņ)ek4nJhaVw";Y,*2ꝓ>ٸl!~H4#$aGYivdoۨ P.@UȽ!ayX@} |5bW8cӺm UPKV!|W69xdm?54V7'sq?585E,rDG_FJXvNHSs~nFR*9l9Dҋ ӈ BIz GYD.& "H ַL*7PFnJgU7R$ $Rb]C "i2 DhTo,IK.rB۷o3ptG-%[2/,CPABCքָWUڷStP -֭Ç1ϟH{ =Ԭ88z|':Ok,?uM^3}y) ID 2$11y F(Q&iFN5d/g?n5ıHD)i=ںI Bqqf[z14u?D<-wMY<2>~DjQN>3%EҪȱJɉb_+[޼{cQRs&.#4U۸6#}ި`Yg7OΟ>B$"Itol秇г+AO."A+I"ݡ?B}5 a3gEkƭB`[+'gd%I@h+>߼ĉKfH&tB֣. YfS= cK>!qarOV3Zᢐe9ZDOZM[iUQt|$:v 0"AEHV8P.>}GYwbc;3$)" H$S0^1*$ٱ\ \JDV^bF Yw\YZàOiC6N\>4@O$U^SHmG6\;k B$#[O_A^1qZ&UM |0zt͠tOO"l]NmZXDQo6FtI3WO M:l}Ia99B@`$aİ)E5Jf $銚% :=s~_:2Jii3D9KP"iSf{#!Y?hW*/K`BUTи;rwG~moT iܑ.ԩS7ntwwba(cU9>>WUKgjLjHw|-=o??Ç  Zp!YZF_HI,Fb/ !Uik/Y%!LF$IEyzz0!ԧO5jP% ߹BDZ#3ؙ2dg=K]|$H72,BJA†nf -2?{A.0h }IJK3>}zqoDޔ{ hۤz%E)L̥gv&=2|Soc*]='Ia6S ndR>2R_OE2Hp%Q$IUm<|VMm<MI`C7<;cak J'D桉(?}-wlX 3EQurIpy1 I%%:f(7n<`8=X`PjR0Wڝ;ׯ_y3YfHL<Y4)nHjQRa$U ||,U>z45555ѣx#Iՠ'2R!'*9S*Yи9&}cT(BN*B.SPB)SM Kt(j mc&m=i2k)r$)I:KL&@*H$Iݱl.HH6+| UcO3Йr IW Iy9Lg2_$Iޠ]k⁢<[C&kɑ4$I `C7<2)vx&\X@[= oF&EQ|ƍW갪o}PRt1(~M,hmupHU;iS`Dlҳ,$5Fr'/ +U\l%>$ Z饄͆t3HϹ=G`z.kg&ݸ\A}#B>8}L؊ IDAT^nͳ(J$}N~3WR ;aEeN ٛTWe%S)|:Ź?EHY'ܫE-k'-?x'O֭y#Ě V^XDgd;v3ɰj ՜9g͚\]]O^O9)Ob'6뻀˟uI|aT_=\~NB8dt"p%Bb2B5VԘ:E Y19RKGi=;8ˊ!$~TǻSRSXZ)e}@z~<Ԯ5it,`EQIC[C\z|r3G@1P4L&!*P(%"U*#LaZ踰ERMt0PL.yc!:E'9w墤r][8PyJL&5x{Z{ag^2Y+Ь^~[|4MFz5(ʧeUZCou]Tp!՗$ A&s9Y@f hUڳ@!SUyp+5Ux9q'gV3]LnxR.mZU|¦B!/%7v=,en6VϚBw\?g@/X%CZ̻ZX HJ&QZ@%kaD-W) erno$IIk%=\RZ5$ܹoV/gg`__ҲcBŴ;WG~{Ձ"J(ڵkذa֫͛7&pe 6Kҿw+Cg%~i+m :E3jyÇH|7 ,h֬Yi:{ڵkPraW6m233:vX~}k*X?zH쐝 8;;K ^yQ- dJH0 PNjGt9N:1H :=䪠7 tu*[]?rY1[QI\PQVl"!!y vBd*UK G3nnn:.%%رc7+ rmEq̘q9 Pxzq6304}Z9:i-i%k7BN* ^ DDUTc1I"\lU"*JޱV;+E [Qx9> SYu.e  m+^RDHT *+ʒJr.S ۾r8[yӫKYwbrs7[շT(uBٹ˓Y_\5&K%&po bq "Aݺuߨ_Rٸq/8puҬtIϟ߸qC&uɚa T*VW\.W*]ÎJQhVIUQ*9|xdv{Nu7_ {Î|W,}AV qQFW׿>>}ܸq2,ljP(QBͣG]\\͈cJ1.ÔvT`*&3~ۃUwv(Y3'e !==ҥ=z=s͛%~ӳm%8ov.ۺ %WR]l܁Vj Br]kݢJJJֽ7n0aB*UN:iӦݻwXo)d͗@WZ ~ U w{­/~ ;32VH ,Aë#"O>fYZ2$5ryA)B> `;(½{xG<)N_\9f~^*`] bDQMmҚ˗^#A[<܈iRYd]BʽDQ|A59I Hw/Q2(wm׮ٙcSvJCɩ %g5C;0$B b0ӧ1ӿظ e"IRJ5D6cbPTj:;;eE]xd^^^/^puun)q2i嗌ts]9x iN ?Zs'8 N嫗vm{2~KkON{O"@Ar9$hEQɓ'^DQd) qnnnT"Bڅ6SY\uu!|.q%)'߄M<R5]]?OG4eF^nUթ:QE)Ȉ1V((bDHX"=WPkEfݹSU5,J#=!Fq߾}Æ ݉h D(:88H9byxW3kw*$mӶH_( X ŶÔ[Iv-RfOZAn>ҸE]+Ti(yfKQȤZ4ᕞ^\v͜?4RQ⫸V7!;;Aޠ~ &nٲիW52 VrJ.C*}".? D 3OI3 "Vv7n{̴e_seT{ꭽbXөzX ]Z5G뜁X4oe-i1EQ ۀxs- tttTT*UY'<;uׇAЧ6),,,,,481ƄI5K?~i֠CI:|PA=~JRTJU-m$J&0i oQe$Ud\z(IWX,K$&O"<"6M6ӆ=x̲b{hg64tmR= pD`L^Vr7Wg6t&а SԐɞr[:o~|9u 'iinU~8(xPZWR$ɬl\6y!ݺw;wV 6YZ9%嗼w^C(v]%9w9"!zO?mv7;tjʘ}GR㘍0~_ݗvR R tvc'E^QAx!+awQ#_8st^c7]K7dgAWi"}\\T.FlH^}qkӉt+ɷ:̣s4t#M:yA0^~(p`ꨍ?y7)6jVOnmzgmKx~qҨM_W@7_xtWiNN4%*G[b3V:gH5$~Vɨzك[Wcw^{|AGNռ&[=O5"aMBDYGH%[ q'b_DOF|/ߐ^yᷳ&Ұ^ޓٳ<闿}t>@џ6: je]cٳgwTBRq!HyjQo׾kGۨ(X-p1rQǟ4V;*TʫJuCr&o/:p(b~\<4bv7G.xbGZ~t8׈u׬t#bnoW_YG(dp"*eq<+=9z_әRʭ0UJ]!oMv-c~x # W( %A Ng4W\^lY˖-kժU@xg'UÃIH =cD'$Ԓ:;<j˭S*(A=:ҫ:&7/*rkƀ*~Iz^V#myZ*c紹Mv\>ݷ8lL cxQgWADžNߵį2gz8Xc R$-H9(RE0xͣBȯeׁ4 {yMdhloیXբgAK51ob>d)"GRoE唇.H (vw׊?0jUC"o<=F{t͜ߙP@ZQ e'+xQ(R0?w(@*/#/}ܡxDB, Oiܪptt㶝 &NԛF*A,Jc0ih񿼸w]FlF"ݣƦ] 폹=}2f5&DQ, S%H!S3+PK̥j./fc?}wbėFQКI BIIIAV^h4>IС={L2[l)~%o%eB0mc檚5eY802D~Ҳ%-  IDATL:Y[+r(RD"!!zSC hh0F#0 M4xrQ\ޣ4mss4M3"4mb9`Mz?_obBnq7o=3sJe$ ߎa0#À _f^Kp`h6<>7Y<`wGY,'zMӴ{Z&y77 d$V}$iQ̃;sNpC+?M$i6"04Z*pSn~V ˡIrY\Nlc?->"$@h4b^2 C9iZ"ngZ"\H3ۍl"g*TNjXon;i]4BJ"ZI2 CAs"Ξ8NsGCsng4yvTNdZDw0@3nYLME;%F3j&Mәzgly#!Eg|7iŞI+L\{0|-d,c\F̢EYä889뒐-.M6v<:xviyݾy[7 ~Fc,6`-ݻ7XA6Kk$c0m׹0en 43^Aj{&VW6)>"@1byKi7G34M5x JRm}m:Viy|d/ob-BVӛgWcV#hѵ }A;vʤiι5;'V/?4͘ q+S40ē+`@OiZx F0>MOt} r\FJ9aT'wnoRH7ER1);ɹxR- #M+_xq…W^-Zz}7J~cmp^X 2_۫n2NJ (@AʖR~ */G@EEQ!Q<ϛL&e98kD8XAXe9>Ц=z(9-#džsJW X% `_H{5 r%9,%] _*~x;@ਐ3A|Jic~z[Ԓ,Q)>)ccn_:uٳgϞpᗸN' ,qsS,KCGF{G3;P 828k|GV9DZLO6j|8ب4,˲lڲ+={=as\x!AZׇXVζtU~SmBJ`l`]f#8wnvApS$~ЙVzͭS>[{]E'j:8Lm#xh k>b67^,{3z _1[Xe󲍵Ŝxj÷1w90U"JJgV*y#V{ Dǰ ޞU*W\˵\();wSgɓ'vH=^ejHju[KG~ѹU\N7`yAT=|i hH~^V]BMa=93PUg_Wn%\ȅ8JorJif,՜չe@.0" 0|6pOCAM(ҽF 4~*uv|r,uh] ak-&B#€ 0 @HE0B"兇NW|#I^d2NK%Pk׮]~jYQr1.ߢW+gǥ \ed>,])5@dԏo޼k|O%!?".:s BA(M&G  0|ޔ9d@B?9 p2_ЦVq`//ʰ) BI_CjI/V&$;;S ;VM ؽ \Z "ϫ=Wltv64 X#ka;Uw0tj4aꐦ]6kkoQ*Y(wsR)dI~4|s-Wπn}0L1vrV&8-/&&Y uG,g,+{[Æ ߱øzm :}(&Oܽ3W/^2ػ[6M6 H@ @GRDڣC*&HSzU)(JS! `(RHHn2wfl¢l=̙sgΜ+qq}Hl A[>ps£Qu.vs֨Qcرa,uVB$Mr)NSճ;_7h?dv8版(w8GB_ӆOW^Xp|8=׏Qu]LX`:X6#;}vޙV?b6ܞ#s`<_v \_ہ0•D:L*]OoBdS&%E*FMo\5vy#jW~1oT֩]WaAL!  KyQCaH_^ 2&2 C ;L~dDJ$>F`nk)Ew$ V ` Tnq?CK/!(tIa8DG60@LZW>vgf' 2D0:E1ZaCWJՏuDsdVd1O1}R$ ] mG^C!@i\*KAUNJ.΃=0DTptԩS =88iL (-|!wSPj•'W;VC {:1ּbBaW! 2Nw~v*k{yy.:e sm\ G463n]`fÚqI0go̬"8*j 5k3ǐYʎ  rb/P몛@L|/ɬ 2)hBѕrbCð@]LN˂%aoN Qbm6 C؊Ǧ C:tHh4y !dݺOm6ҥKՏDU\UHc5Q\$I֯Dzf b/+,4kmټQiجo[={8pXxx7&;n1re*['kV#YqhY~<ɣ2jts9QׯDzʪ&{]3H`u!%,Xe2$ɰd)E RKKbfASP[ޑsTsURvp^UU o`ӘY6l% >7.GUS,o׸N OnUP;o―G_U\;Y$Ai2XeY߈60mG^%gʘ%c%!'v}cH@@@׮]]ݩ2ǟoM}]驷Or!!N:w:uXwlqn ΢_\?jل,C)1)nM譆FeX=%C@$ݵ#(W//ٝã{7v8kƹe4O82AWoF$ 0p#zYCfccIB=aY jr^j32,Hn08?6Jmi }eD&^&,@X=âCl[iևeYsSnSB5w>\!$zkE*HHpB`2ۏ\L&ӱc>|f%>zꠠ W~<'}ŕMazBŊlhYu+nh~ ?쒙z#ULgVиBnUTx?KQm眅} ѷ}]+{t5,˲,q\rzIܹsJ8S<_|("ДXlX{Ƌ96)]+n˲,:'xwPj#øJNt4a㞳i&-ꞽۀzjcu#[M8iYsS/|wsCO^=.wnz?&>-ǡOz7SSSSS\siI3oB\qqqu?`l{)ˆ-= N9*7O$lH€PwfQNCE謾ΈyRV_fNO=y2,˰I|Aky#,3"DdzZ 5^}nLZsY]O5: հZ Zrćc[Bcvup7l-/"yNas Ur&MrhҤ븚Ju8/L%(M_2na/=q6PfMѨZCnY\]vb5TZg'? ŭy߫)[tdd< ΧftK`ٲ/dd\f^iɀDrϞs-##koܸ~m۶% -“WJV| x* N4iҤ쀀{Ozy'}3ܑw2uQEݮe:v옚ڭ[7GIeT)'㦪%"f8`)}Nq<[8Sd:XJi)X5tۏW7yS\DXυJk [0i̗3 B[v^EUr<s y^4 zе2s^4K*eĒ]:~u>?ǀayUBWu:?Z-k9+! y&W95aG곾X_RuƵ1-B/9V ԏ[HEO>Gjߛ:7YfmɁ C_o2I9U(eHUEh=vA(p@)Ut(M )^[vFW;.q|i-xtZg4+ל/A Y5nr3Ԟ'`>tlj˽ᵣ; Vju:eYd5OаP<>޺_Þ Bt3ñ, hx;X5ƍ8q"::RJ*]]zRt|mJJ*X8%;YYYyEb,M&2UsӦM+A?d>l%o1Z+WYܹs͛9sftttq?iӦZmXǏ׭[>ܢE q=Bk׮)e[y{UVhZјb4y'ι$J >T R5Ҙ]D!(?JՆ*J0@|*v{q|||233}||t:]MI,PꁛtziP?Ac[ KZmh:IV*d[Ʈk0tMJihs}=(rb;?$*4ϥ)B1uQOB+N]F)#IܹsI[lVEB^OII h4:iq/JcYPENWȪA_woK -"g Ը kЮ_O.߃`#C)U_= r+W XY z}o<sEQ -X採P˪:*U< x qƥK 봆d{.0+WVWcn*R[KT5|z>VUݱk1Pzn8p%G02*4KBfY6$$e˖)))ǏQG X5UT qʕ+;vQ? /sP&pȗ8@T`]ڍS{⫯^'OfffJ(CydYMKK{QF yIr'0ǧL2: pWUzݔ^TqxJ:VtD͕:vɍ5*J,-Zͭ널+o߾}ɒ%Och /_a+._~JJѣG+L!~E/z(2`4M3VMy_[7Uzha0 Sz1cƤggg{ݮ(zÁʕ+:E lڴdZ>>jb\ti(v`9Td_jb1 \')I҅ ._2Leq YK¤UX 3Uo>tpv{) ?% ׮ǿ\huɒR^\Ŋ\*"ewA)ݾ}{˔)#K(:PzyҠx [< U?wψ`awEDBUܮDcX+Νɗgk~j@t6N Rt .ܺurʥjY9*w֛K.)˗tT$sWhݶMyE))(6431ޞEe9aG50N/9N߹.#\x ܹz#F(SLRRҚ5k>sO$Os@/)>RM8{ji~?jBxW_}$C Oof7l/jJπRhDQ%#d͒$BDQܼy믿.J|||\%I:|zDI$"Q҂{+e#m *Sΰ 2$V϶ږ! 9JiFFFrȆ#v}%IR&[U56X,:м{ɲ\\+WTT?'bYV$*)eٳM6ZheMy^:JM$I^Jtc4,Ca( Sz҅}{R\$I%AaYV*9$.}bzh4feeٳg~ \G9rЦO/I==;}qh==Wޣ5ʊFY%I=z#I%I"N|$  %B&ĵf?N'k7"]|KV `=k6 Tq dg~-\J52DV5f˲,+NFeJVeBU-ѝ Ak^/:u%y N) 7ċʓ}5/^}$l֪q:Gc#>H~{fw4X3fF;^huE}}_L=T:Vs5VќZ=f7MBfq޲j~]SbjNw/m(v oڪw\Z|)~ߤ(> =uj0g ?pn5zŻlxkۯ)v/7fQ JH5SMnči{2 1qr&! !Pj<wݽ [ J#L"J4:]^^reYY8c-w'95S];67 茼%l<ިZńy*U8|pvv;vX(A źk-@Hu XEBRQC#frҽ"zi$<+G8|$PUζ_ムX,ݞҒY{F,ffei4wq}}ᅫW9slٲpFin?=璿K%}pX2g܇gyBۇ 0.uJ${yYg?csv ?0^3 Or( ˲c>#QCJ(Ee.Z$IdI%Q<: m};V/x}ךn%I'q-$I{ik7ӅU_bA5k6\*T=fwm]/Iov9a/&]/$֬Ġ72&vѩJp|ލkv%2]+s0aA5W|O7>a}]5 ?Ws9FKT9}3;.v%j]HY É)DtR1G[f𯮝dcSwR.:dԸq-[P!Ҷ,+!֭[gϞpTԮu5|mJՊA@'NC Rin{O?]ӟ:]/Kw*\qFBta={פ> ZHiI(]6ju C,GYpa TaK` e.Hau˺m{cU4,UGqeܼ/tqqqka(_L? +oakrrTh48:¡X)z˲&h4ej?,hs!Q Eݺ+2DA`9EKva9%/'I  ȅ>^Ȳ>5 Po~b9mˇ>hüj_u%:)6]DYR蒬J%fSPQ~7 :4yyG9]K(!j7l0ڻv RJeP&&t?O cn+}0>ZT7%@Akwwn,xef`cJLeJڗpܹ+J%IuZ<|CC`_"KBP.B@ݜVRWt9}|y{.RtSnQl4&_K[rtVֽ[1 ȔިRL* ոMudŅ ÖP!///+3sWܹÑ#G=+HO$n\@{AŠ.ӧO lMuOp55|܋JhN5"I¸ XDQT~+t=/ܥ*bdbv`X,6n3Y,Q5&5ݬ6h v:}O`VM]>RHAnbro?tU`)>B$(ǀ0sڦ& PJ ȦlAMX>yVJefZ"fXۉ"rԾ5]w,,.ɱZ}P,Arh*]l6Y,VJ}uVded٠iۀȹ6 tIRX3?}I)z.ݿHBr./ㅥI"}RL9JMج Y*X^2oLRpW.=ӟGH@h`1BK Ȣݚ_<Z- aJkm2hf~6)?KFOR45nNߙ :ʑ}l2 l_e[^FSޭk6 y"GÀBH܄"WLE*Ce*Uδ|Ô m2B~LmjQQ!*m^y7.~#캏.e_z aT%B L~ӹaKu?q"n|M4 0`wf<׹;g`nr#˳;,7w0wf/5}K}o1ժ0k[2tx}7\mS##oDDNе_+ҬulcYf%w”nɏͭfI}Y3y_~^!J#Kl kz]FҲTmw *H2],AwߝRsڹnTfHe8!]I+ v?į ~}7clYJo$'|_5 c֎;'gN6hv ;UPʙR<e4f:]VٷnҴ>QAapzG>M7IYM5PA4.O:^$˲DR2jH_oܺ޴n:ICbZӁ9qƒZT-cd%{1'PS ?e$i!.GV:;E^șv y,X&{$@x$yR?}=yԻkëǿ;ѽ<GtӻAgv5[^~}CޮkO3u^Xcg${@c^^"+-7z 35FV:1]a>zQk|=P&.`26n\Vfnٳg֭K, -%eJfm投V€Rdg/1igf'nJu8_`JkTS)!/]@](_:jU( ! ee5 \үWP^3[[n7qgZ~8}\PB h0O_`#FG(awHwM{76g؜](o;bVA~|iz؋k֎4g{~[RKG!e| O~~k?^sg$nٴiӇ}ч6m|Ӧ` ǭ({uTz!ƿOΙ߿MGO+K!hJ%IDAT7[5lByEVC^*b7>!c0w>SLyp+V b۾8r7B 6,=zGrlP(`_Ti9ƚMF׼Qf˟0L %BA0D  ) RPXMk.p y@/_=X.8jժU+ET/\xi(4?CU&9P&}L@PFn|i`ԆMU2,pK1 pf MF4 6!Z#[!(ztH%zb4kXt5J]{.)|TBuҽܢX6Wh|xe~'t@Hض) J6AI( ;wn $wZk׮]t)Iok~ܫ_rsg>ߘձP._!ND` Wڪw nnGw5vDF"EH@RFuͯkurk^^"]Wzn- u!~2:, 膽9UJ2D4 a+Zu!BY,u>٬K.U3O]NɏD}sɒ$[_:l18YlrЬe #GSӧcoscsݴ\Qȕ\^lfexG ZX.fA\ġg;5$ʨ)ӧϹsDQd_~rf2SڞF6ܰ ,a0C=YBJiQToҥYi2}w.\ܹxkG [64f֢Ϳ?CIB㢏Ǎ9tՔ0 5>qAS[U e*T~ۻ8`}+WU)0t!IPV,òoD#/ni\!'tԵMmͨmZD V\dXo^kЫ+?OeY6(nȼ8ͬ ^Y[j0CYeAX.?KO]J|:]c嗿LJu|RӍk/u(мødMӕ7Ҽ|a~A5 kTj0> ÀV-$,c <9:4O2U_9e*CeȄhXކFrF3-?Or3meL0Rw߱$ k׮Tᷦʾw.[A~\H'?9АRN eN:rAp87JylVZ\YI{K-OCp'|k`$,~/ĿkV-ڌ؝j,.h쫡#VڬQ_j4WP\]5ҽt>½ k}2!BH "׊U>0 e*12Lc~#}fK>|8))iAAAyO+,?вLVxAA%33K~߿G RqݢT2 >HaՊs>ф[۾=:yeY8\r={$tܹRJ)n/\>G\hw\v,M=aŌ͔ys^eYP^bщ<;D(a\f%Q']:qٴl͖}um@=[1-&4٬٩;~9{ס'u׻wnm'=͛Wܹ$7!S:`MvXKeÖ'Mظljf;d]IUAq u6deg8aʻKI'D] yWǁ`Ni$d6q֭'n 8X8`ֿrjͽy̵80#2naYeN^ lk?佳&Q/b 0`K1 )3==Ld>~?=G" 5<GϙDQYrgN>|WLܬçtZjXjx-j9ñ|JES1ĿvAՏsl6{ݗsּ~?xRUH*I&9N4iu\M %c/L%(M_2na/=q6PfMѨZCnʿMmS΃Go9zdz?]>ǕkcR>`[Ye9k)'1H>w%eY9(r:fkJ]-Sn^O/E7 ؛_Z1ccU$X½KCפTߦOZ<22ƍGݶm[RB?8Tѡ`69+@6zo^Veiu1jҬ^s^&f6^ԸɝS{FԞ^xo'.և׎0x|Ҧ[Z2鸷^eeװ>EBBz~] {;櫿* mղ20p Dz,y<Vc)ׄBCC7n|ĉJ*tu [oMII%16qd'<<<+++22q9~b_m:+t j՚Rkz&#V8GHj!|A ;}1} iQkui Mgt\Z^wN}g5#jOg̼vP?Ztj"K uRsSnA=Z|yeϜ9&[~~~}tuyزezlMKK[ac y^cǷW\9=  6mׯ_ G%w$sgk-yKǎXj ޿Ν7o̙3 M6jKj?~nݺ8{sYw BH^^^VVV``[~# 7]JSelĸ8Erdݻw˕+leʔኌ=%x}ew?Mdwsbg2 $lB%\J@=rPKОĥܐJ!* QHENLxzx؎~Qxfl2?7oVVV8P8dTd~~ŋ SoɌatX|ƟSmrYXV@L&!Fm2%L+nd*C #S.jF###m\p&;4MJߏ|?XL&>}z֭Әޫ|sܷ~wjg~MǏ|Cly bX(,0 e},qL& %ru#J49 :O0v+\i kʽ^=زFxr=^n2MbKlk>we%f!wI?-YzT{.E o_}Wnm.W.DЧ| U+Fk5}>QGՑ'5]߬9ߔ|i.CGar)'ȗDI|gϞY|_t:Y0o]כDSS-Ѵm{uuUn-\ SVp:li/8i_Ur+0LŢ(`Ɂ>{oMΥ-o+5gΜ=ElygxޣG޽{ƍWXnm{vvvtt07(>/uww{<[8j}]fuo[ɬ ٬,Oa}/:]aM5Fk ]TDK0,ڳg/l0څR]]_"/J;K J ?̌܊c&IB]|رi^4K/5M;yիWN:uȑ|<(z&+o-̆O=[bΝ^>|xtt4Htww$QeqqrR<_\\/@-[/-r=߿Y"D"h4z+Wԣk,F#H`0߿&Kϗfw` oxJX a x?ef^v?vuu?˗dݩtYʕ-ڷo_i(e8qbyy9J*;T ir&_ +0څ/^]p"J眔jlav]W)zՄ-.WqBa .H|8օ.RCH "5.RCH "5.RCH "5.RCH Ri1࣠*cGeH "5.RCH "5.RCH  x"> 4 ÇѨ뤆o߾=88hF<g@`sN4}q&a!. }> s v¼p||~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:|UV~~~1OO[n>|XPꤤ˞,͛76mibccӵ_5;CAvBDd-S^wIY F'n{O4R:#_la?6I?Mog~FG=:-0'Poo2a___Zίj֬YYYY$y{{ naӦMۿ(𯅉fkJeC/včgS+a@RR|LkwD9yT={h펴T2|(AAt_ / v- d}Pw4鋨7'ZOwUÆM I͢,JTʔzw\ȤfNT{"n-0O"޽{h4֨Q799o߾V\_F={H d-l]tm`=|ݠfxX<snZN|MZеiWI`'U^Pf1\3R/(n׹hf]̵3TQ1F|@9)x@+PPpf!GR5jռ}&A}CEaR dqgm_Zj .deeEGGgffݻy66yGGGNRɲlDD_Ewذay{{{Yd2=[׆1J5[E>M.n?K)kf}1Rj8f}pl6B(UA!Dp߾QIszFGr:LyxT)m^؊h:u:U@!.,K)5WJ)ӼQ|ӵki6b!ʦamx /^wG>DQE#<<<"" f5O":9=g폠D(^h4p㺞.K 4M S ϟW\yf(SJ˕+\[E^kv-ӛbܹso 6Z`g/)`ë(n}YT,S0Y+ R7_,7^(*'447E9sj4}۳Gey!0DNXowT2 Nr捀+J> !TkM7 {ރ)9V+R"I !@H*{ZF%5SSʱD\b'!V0 Qʣ[nk֮=yd&$IN;DMB%]=W7.ԉEs J`끥7J)mڤ?\N;o`O$Q,(O e?$:˽w3UU4*7zL̽y5ծl5ƛgng֬xQJ)uqqQբ(p{Uj9FAD)Bs(}~_zy_,oFD%Y--Zb|g߱Ts0MMs[bW|Jhcږe˴YG%{ DrFa@HTT_OkYVj)$wǔQkSPe7wڲoٿKbiާki?7L/ۧKH-Cږߏ r}x\ }Ƚmx;Ϧ]zTW6/vMkb{w?p)[-|^?f"lI۵Z$KJEH)u떝^;F*?~L%JD À k';PX9ޤ R*P-(1^tb&UKiS/Mݪzi+YҒ) 9ޤ}G&ZQ [.$$:Cぐѻ2]I$IZϓ;J)Q)j^z(7/]Z{D B"@D8R%sV^7)1%ݎ۟v~?VoL7Z5㸄SPGOW=?p|n2YS5N 4_C'=GLp, /b-l@%캦sоq˻^ \XԊwĠ"jxIxꍽ f(sw RK8aͣxPj%z@ k~:<:4noeFSʪTs~٤>Fg)h~-T@ڎ?]+4mؙ߿͘  O| P福N%dxQYA,exANLrp׎3&)QP{{d-QP{4ˎ=zns &!=ԍnj߿aYU|VŞ1FʟN_طn lnd4$"r֢ Uc9 WLd8!}gŐ :㉺7]m duC`똎~"wuI='O&k=$,f]ʪOE/ױg]v˒ (0?c&/н8-A3.e|Zx&u?Woӳip]K9 ^UF52 x|, tcZDSdzeA^6{ەJ;K{z?<6&?n7vg:4Vw<3Ƴ|ɣf΋ >M=#uQ";KRCFX!PUTi|.a3Gg'9dkJUm,Lvҁ珜Ik4Z]ڙ4ІͼʸUy%^M/'Q32L&FZVvbSZAɇWHo.lF{~@ӕM.>EN`.z_`h~oay?q?:&:l}gL ~O#~*(O*Ѣγk}ol7%g?-PSw'w2ױ7V4pv NOwBXqiScDp_/Vw!^+xe씯t d9~VSN*<@Y6)g^)[鞤Z*U?И{} JD( o2hWp&%"RPP9O(X/ q=$ te6e*Wg*F6*vîF+6C[c?DT+?ҲQo]~.!;[4+[*5|)LPU÷{0)&&/Ҹ`2+ri&YϮr W߇g5=8W6kVoDMxA(j. L9i%{gֶ8`@5g#6;ۺcw5cأHCEmk:ꡮ$knhoɔXͰHy]v8R $S>]v^4vUȦi>YL%'bpLN,MByRV/?.f4WJ| UR\ xm[f-ٛt:A9BrR (_]ΧLpw.>tr U:w7/-/?\_ >X+prJ<6xGPƮ\gDSJ"*={;n.\?Tlϟt#mc "JPTÇ{^VQ=yQ,%r,aa{c($2bd"Pf}@sjK>cbO\`B*njC Q7+q` VfgҜZ]π,]%~!B^|;5ϖQTo Ef`* @BɺylǞ9<\s!A2DF iɂf$zdf"0aJTL …y{PF-R8{YؒwWa&-3 àH9PX4"`;C-ЦYM|Rb aֽ}U^Ra (z#>;-Ū)_ _䲁5z)+)pWށa`usE3{4<\VcؒL{w…k^\}a!!1aY)uVC[VdqoMQ,+f-r ^0 $aYKɂf Y^VBJR'ru{bA$= dx-A Y(R{nnU^l8uԿe,,sQ:>dq^?D(Y\; (ڷ%6N7bNZ^Bɰ/W.]VjΎ*{eYv.IZN`Y<= >HzJ u m%ճ M -X5 t=Xȳ\˷2e`PxӲTDrww_! 888B^[5 W+*,2 gs֥߸ `R# 6f @yCZ EF#.fQ3FrYxE1;~:* V )xjJUV@ͫNV%sCf$X:m*>^ssn y"1&[=3%: Ôf* w\q[+٫.fmj04% /%whLjjqanЗR#dv>6)/_pF/ rWqʠ;3%-Ԅi߈pZјΣx׆%:%X-9h$lIZ!ZƆڴgC?k᳼߱gfFzo tvrCa~RR,̓ CQrFQnQ{6Y%[s8Ji֘ƿIU@ k>d.6md#pՍlj~EWyu? Xۑfka)%rի"ʬߤK##{'|oUa鉦޻re{)̕Qt+dB9>:0|mNk?sCR,*#Sv>nm7 IDATrز?Λ9ՋP[:^ {~KOJ"eYjtP!yey9JM91tǁЖ5;sa(Qr$=a^s:^ ?~w.*Big;;%q,c"5jhoAhrv`u=v~1 U9,6tz!OI? M'O"XVr/rF_Uϵ'SxɕmKw]osx]&&& ѣT..|rM]\QBқx|'Lh WqPX+( &Äˏ)/ 5z-߮@:V^#5Ndͨјx]FSKF^bxU ^,J רט"\`~"oۋ{)B '-$NˀPFqRkwA]a,+|ZoY4\\u/R›Wx =Bk쫃/ǻ/SNiZ{37P+˲-ZXl;gk *+i?U7&x#|#PTƾ< BJJۯ\ҿ>! 6`KÖ@m^j 6%Pl1~Zٳ ^jժY~}m1 0iJK/HP@qM`TEyۉLZHTز i Hgl߄= ENv\'%%Xزh4T>J>bl7%d:ujvvvժU  8+LH (^tʕ+,JT.mǵJB^h i߶E!}k1K&cǎ) &ܱ|A;3NwӟLϫWr\(tnJixQHKK;r$U6{>sLڵ I2~0u |͜/^h0._6{Zx81`$N=IJ*P0^ƴe5>X͢4aH޶@Tx\%iKBsI[ږ֏ !-Z{7 :TR/^jE9Bkk, ^Ys}7+EZ.6C[f/{5VYO]qTF(^xZj(Ȯ3ZK?&Z)[OYF"<,{Yf>|V4hgr$RB6sD劸PEQrۃ J`BC@k/ٽϋ}e-9f9;;gffiӦ>|xOO{ &|,ȡDG,1Y`Rœ׭[2۬^B@(qފ߿d6[]_tv6# rҸ֘;`nis;9@cDdd7FvyK CE*?7nu YỎΕUp0qB'u^G`X`1Pj\ +GE}&2^(K$MԵsr$IRIR=I8a1w hֲ#q/OWK@I^o".T**V:%Kש[7##CXJ * J@ p^v7ݺp @F2%cݺѿ[ C :aQbC9}\tRDQ,\ ME3ψDE5?{ZK_a״1C?1HgϷjy35Q̯jVkɷ7CK}5jY/w r7EI5ZP.1F);yC3 ]^ʪ tHܴS?cڠ5?1Ⱥ]֌`XL"D}vZ סOV K >drt~3WpNk S= zrM-{OUO;{G-qϦ}1 @֥ 7Wv]ɑ+3=sݱ팸\K^stM߱E.<::co soԩoњ$(0@ e~p/BPPJN($`DVkZgN.UNQmx}ﱣGRڻvJkͻdDA?w<-.,PGڭG-]vܹҥK#/(ΤP/Pk֬qrӣeOyFk#@n_]V@DQƍ7nܤI3C-*;G5hF1 AQEQDI%Q3zFtu;7bLӈtgW;QRG/?U{yyyyyz)*xyyyI/^~]P%,1EQ+NX[P.DO^{("פ^Zɣz_-(Z}]fAHNu iaǎg9)Toڡ%//˗OmQ߷m09ܲ|4Aɽq(zMM~k [ڿjZS؟ʛՎ?n(zF1럈(wΛVoÞ?n=y4>F¦YcVn{ecګ}vqL\{;ajs1+~qv"5(Q  (ЄqF۽5~(jQF5ID8%'B^QvJ .W?cw]Q (ݳi@i΃5"Qa,u^diPR3 juǏ5sfhh_I:(s<6>#igwp׫ rju;Ms,z`׬zk׮1j4Wyb!Ֆ앗C{dNV~˲IJ8&!ˎ펪PVDcPIR.uDQF0p~!mV[΃?"sSH(IַO*0|rJ{lǨf?*ݾ3IT":}Q*$IuEQ?[WH^ <~!RY4_D/* h@!**}hGne-`DzSD8*q &G!rq;js܍\c$PB@ ;7U@.qdu3 K)@E I5W<Vb@޼S̬'[[Or5(VK_jYR'!$11aXoV{ݻiÑ#;udɒ* ֜no(@PYmv^DѧV;w `c*ܒ&\vܹ˲uγ9,0Nh4ZVu:N®ih3}ntfRN%H&N3M\SsrrH7.t)gΟMc_M & 49:NߙtzIk,`(A2,tzWe*T$A23wv+$>ܼ|Qz}҉G:Ng0JMz ٿۧƯK8<ݽ̭fe~,N˽2ID`#X>h\N{tӆKt:]ޙ-IwNl>~r~ڰV|} &^9GSt:㔃S{ˤ<H(̿?ە_{FDA&jqzQ ?Ruv<W-`D}\[MnN3 $.&2bNҋYztհ٫^U>ccX'fj RN_hNZx?Y<8 ;S(8=uV(HvffG:th,̝;޽{g`,* ZdQ|q"ZOj qhDQG kVy3D*zjO+>בo!nRl>q躰 ~U6CgtB9#HG,4k4e Q! &h4R&9xzٛ `2) I L&Q~lʾI3[ow)qǩ&BAa.dވyc)H!8z'|kcQ(F|>{9mەʨeR*t&6ޥ^5\E;:ϷCX׹!|ryCͷF v t“UQ(/>^^Rذ{L_,1n[Ldi.9No9r?i߂Y–Cڪ~2F ,_R#jlսjee<6FV/ /1Su͸d4dR9nE֔;f0>SiUo* isEXeRPJ% RQ(RtTqv g;Ej1cHCʔvS)U¾Cxb+VlԸF1vڰaÜ9s<==S)f k6gs0ewz€RdeIݻ94kb0qG)HL0XcǩlFĬ2˞;F֩RJ?!U3Uy=m;DD4mڴح)[\Bfm'/3%NlRϙzj?vd4Kw-jzvxg z&O3y lBb_8j1Mj 䛘+Ȧ5<3Z…j/Sv⺻<>L[ Z{E!Fҕ+W\rJRҕK7j ![_-z z#1\]:}7:u qayײ!@ZޥZ~@PКU;uB!gv-]Ws{7:q%n㫏;x|?_wSG!Z>ޢn =Fyp&74q^ o<+}Kf}ڒ&^g;ig7<7 %zM;֯[3ϙ5SV/4X>OQPu+>nل߯Q W L3aJ%a(BA( F-QB <Osh QjpusswsvhkW k5odvuP"Ջ[n^=d2 ۷~]TTT %gJϞ_`",ٱCSeZ:r&׉]v+ @ ٜ#?=B aH<6V,0u۹՞gyBmerŵ,3q><}g3 ! Z&8`@VU?!b6¦~mwb6(qԷIGav Io0Qw|}ki?\0֢vֈz_ 0 |J°qM4Zǩ @4!D]>zޥ-!vzz| hgLGX;zdʕ6!vFāFǎi4GmWO-? !|6:/#;?>?r,wiI2%Nd&-Ec6aT7 nbqXH`Ċ}? D_( V9F3*QrǪxVųj5ϪxF%ǨxY%*9F1\1cN^jGRRnTw?UڝpcK+J{89ە$Xg+BH\\DP(:FYA?wIZ?LI,XȈؿ@yt +}A=K|uuY%м:sf)waOGs:~~7>\+7OR|޵6yx#rf^{E^ɚY :jOc.p +gLy ):aXܙS6sl()_Lz#/o8(2D>Lb$_oli,XhneJUJxN<Cy׽kNՓ T ^UV0JYK'PBH⮝+w7x77RNJޮݍ *,V4a]&777>$!Ǐ Ѹͺ_>$Izf6Ǯ;vg[gddcjժ,))fԇɇ,z~F0d nO2Q㼛^>88vj91w2 ~)vy,*{2"7ڻ僃 g,X,g;MҢ v%YɧN% ,[z6H:/Zs\tܪ˲M:Kn<+1c^UXX509k."}qq؆ R* 9]{U/'B,szm3pqɧO_KכLLԫq\.)Ν:u̙˹0Ff26XǤ(9TU^}?Mp7zjjrrrr9IJv]>靂 IDATk ק);1:˲N嫇~VJ8Fͳv N)>5>ds2VJJ8V%18M{xűo𠐧C.XH#W# ##eK"5bŲ#Fhт&lˡ˖acFT@yߚ\\?vqVVѣ_%_ڌ"~áѐ} A; |[ܜ-#Fm;nW>N9W9yĘ<͈ mq(qvSs㟜Yx}`9WTZq\N<h)!Ƶ1[!R׶/ո]5u󾵚F`vxpt&O@Ę7ks9j;;3Mwy[no՝>ܐw8]_Vs! ٘whgԄxlgq7ooomJJJzk!ƍ{PB#˔)R]GjW\ٳgww EqР!qqN]fgv=Ku/*WfyBpqqJ 4`$W<}^Gr-m;Jr_lQ*n~% ʿn2>e?,GO#"M3M˳ KۣT)(P(yn$h\]gRՐ ID Je-@34UT7CBujmLɓ'OhZ,x,BXj}Vg>TR6!j:--jժ BRч%FOyQQ8SiO#Ų/EYAxHM:-sP@鮖IRS Ph juѠ(J%eϓgy8ZD$72X!=== =x)z#x)S ,<Մ@(g[a @%̫U=JL$ʺ1e¹]Q6l+u06hn۶mLLիW۵kWz?NPƛ0flnr#uذamJH=|`^bq(wWTm.%B"##-Z< 'JEEFF8loz-%KEQUT5jTFFVuP'(}V*Wk7 /Lcr_ 3 ' (}gCӘW;% 爭N8+@p '^N8(?h4>I&Olpk׮seggۯ`W^.]~s!JeN@֭[V-ŋ]L&ӥKlF3g,W}{={,Y2fWW?sC8P*U<#;;;++K-ݻU6x`.j4hʕv ׶ҥKW\ʢ)Z|0Q ŗB,kU ,d2)wKZYuqyS۪R._D}KaVs3O*@߰T~z~.㏧:p/ZVvc\:'ėR. ݎK&unqߜ2UwSM[Mgc4\ =cǼz͘ԥU?sä7ME1kX%P 7hStja*JO3'JXruT[ɋ?v-sX"M;lC/t)> f+xt&]  ]!MU_΍yi7 Ax/Ix޲_}˅H iϭG@w-b?0]G7%.b8R&+?D(1B ]#?In {AI(E^$ ;qf=#ޮ](J%Gk񧃂@hdϬK|#>zvҊsO 哕LO ]rKbDUu@wϧ ,`KW}az"A/~ۈ m?|jR|G&y(DXr6 XbIIFKOO/{wScW ݤQ+.ގ]_q${# o-EJ h7o)ChS%[q:|E ocl֬OA(4RWD4(770'`ͣT}MUN/l(򥂃4Mt:$EAQ|.# tz7mPAŷ~,IEE W4EQ#/xd`$^L<g͆ML03/(Jz3 sy${%v]bWM2uۙ# ~ᲒEIQ|#bIfcǒ7oZǎRAE䙟Yز0`kL!vr*G~B;>33JAT֑чY8X"銢(rqĔlYKVإW_MD|8GK.A.ΗyveKzߎwEQs/Ί&t~RkXYQy.ǃ&Ӛ!OmEATO{ykY(ana/)mEQ|tb{w^ F/RNM2"#.xaeY>w1o[,P("7S$Ib!'q\^5 8=3Sv'!+Wr h 3 G, G?儬\1S/h bADx@M73 x.~i> 0hs<Иd)7Ov<ׇzǕF/GhQ \K^t\ b%AU1 CEyi& 3x~dmҺ?'̴!SutY?*>陑 @K:Hr%ky KqxT 73ṱZ>1aޭei7j" :?_oO?Wީ%kY$N, _;ݪ&{$IfAs퍭+&R~_<b f>,V+~!̢fdrH}I#W>=-(@&p}w#Uz_Q.K869h^jjO!~V2N&N6١!͹^Q_lyh voX'i'Vx"jAL|ʔ, hwvneZ%)I'r΅;6/]eL&9ӂWY \=GHK)5d%Yuްi3vd$I:`lp/X1drlP<__F;ornTϏ +o^QKwaw!=.<~m{~ÍFG.ݾ-. ^[V; 8- RZ.5־$^YN@hʼvJV ~!o6UM#M/ % J%%""(ehEW"eض'))~ & zQw,vO :=<e#FA_GB$boEڤC+[RzկQ3[]„ްO-sVvE!<$``~x- kWmZ+gtGz)_Q0<+mРUt트S+ U=Du|."|ܥZr[1%8e֦$\}tBVo6_@S|xj7|<7mRGeKz ϟ'AZ ,H9b/dBlڿ'7_/ .]z5--ʥ+t|A]FF"%%-r:jB!ƫkfDA4U1O8Kq@;W-Sva;̊#mڴ[H5ī( ^oi姪bW@k2.[nY~SLF!{zV$$"$E!*R\JiVU JR*V%. d]hڅ\q)粉WV~wRf{6z޵6wc723> iS{D "Z?| |1W^M?^O_t 5[Eqfiq ^+,>D5kkߜ`2PnK֍:ʔuG "448פ߁* B%@Q!F+.% ;g`Pj*S֝7x H6Z26{HI.XGcyFO׮6|ّgM:&ogH 0ct[rXz$0$x¯OY.1,-LNȍCwn#EH@mz|")6ii-&MApc+?5n"4484Ҩ/ю/ܷ>: VpIW>jhBQD >ȟ~E4m_Qjμ_7Bȅ=ҀqUi&EQ4!4EPMӴ$I.,da%i!S"@WYHEIȋ QЄ-v͂8Â{f 謗 0={$Wt<4 7Q9ڋoC^J%G:Qv2 {䡱%lۨEKrθe4M{7壃OMڶÉ=?ABQT@1+,`@`վ~>@DCZ3+pzmB{4|]"OAfkD!XEQPՅAEz(,mgxcΐwrjח=iu<`YŶ#!s![M}{5RyW؊.5g l 0.3##Ld>y8#[dar*ZNS ?kuI96#t²5u|팎3.7FEq_m_`e'gkF/4˲,!J JU F~AY?@50/wtU IDATF`[k@EI]@(kg40"1mñZT2dl_~Vk`bX*e{t& 3Z0}f$Uza 1L}V7KH>rtK×.߂a}3AHJJ2L=ztnШӳn4CSݩ ٝ{ug3|cqЌͺsk. ?[ݻwر[=|ofA ^y<}ZEFo0<ŋFc~Y ' A.|)P"@{^DHr 5d\sl:7zp゜%'W6ZI3arIbY g/4v΢sоڲU-_˔)s+VlذvKq}&N^ t'η"~FϘۼu^ºSDAm۱$6?d2rT kϟFʞ/e ŋԩ < jGkkG;NC\1 єHNu?.SV> F $x:hGlP_AEY݇.AAS &Q]tA $bW>BM5 \PYY&i̙h{'S4sf`om !qqq^ºӖw ֝~u9l69iḝfV0mbD>3s 7FvQ>>?A0^^^r} )=QA=D9SkUٓ.d"qCm>H֣DY*둮EQ,A'J#P'<٦kVE4z y/k&/BVVzÇѣ7x#33St\%*&q_A@"x%3z]B挟o#߿bOW!,"{r'I0 '`x糯UVՍ'쿩yiӶ 4!2***jDTTTTTdxԎqz1a,=ޱ ="jmr}dZVUщWr㹤Ʋ\n&U7 TCr̄^ѩ:`H=U_yץ΍ uN~51:2b1gZ$ZV7s9<=LrM>ޚ7V[Ш]rFܘ=oFɶqtlMަLs_Oq{D.8-rڎEo͍l;㖍ot~=T\wu{dxUݟΗ.mHV;Ӵ%U>f(F^2 0 0򒑇گ>zpseQd% ,J[9<6_xqp1A}W^0_E, m?hR(XWkȑa=[juڵsΕ-[vG7m(' Kʞ=&}e=iO}F>mĉ'N8mڼa-+CSwWfegFEAQ̔̍Ϛ]緻{zA&' `ztsE|\+W _[]"_B_M2crI+zZo_ܔ^m7'߳{:=ɛ'̳r_ccUO;#/&]~d'wr 6YS ;a٠WTV-i,&1S̽bvho]~|j2 E(C9ϗߗ( p/)>鎮P}Ieύ8χܨv#f`_/釾?Xvߘ5$ <(حG^ힵ{7ѰseY."aOOlΨɗeǯGh;*$Xj!hMnY "R7/Y-:r`=B4h}NgEQDSX2˜}? |ޙd}$ kJ6Pmמ ,Т(7iA2?OqQ okC/7.!XD"IEA -XE=JTP_xGSдP%#/xD!!D!yΚ :&|Eђ$ F3 sy< LU)2t£\}XYgg܀zdć"[jnB'!$55*޾u;WmZ5k׮111oy!s1RG׿ Aϭ[Ok,C}IGsfVݻw`c?\LT)=vkgnљi(q=43mgGaqfI2sg0&&3Q=でz%h \ /ogZ44.NvfIJ[tdu,1IqƑ {g>w޵MfuZ8(p?[I&m`D!p6}hZ8h1;`8 eCrDz9_.A DxSϷ#7d'b`ว7ΰJn8;, 9zRnfb3Ƃ$| MX*,G 3,$e&74p}tɱ/"۹ugΣcvN 9'x+Fn\R>C8Nweff!:e!wIR-'b;+3K|VxAg&6 6X">1㩁 Yb^lɉl!@PZҔJAg4E7Ѵ@Wdi!*UhEQEQO>MN>V^}>E<8zGfΜk6&Bѳ8z OMϞ9ғ"7p%鄢H]ٸ^ƍ7ء+JC){^Ǜa|*oK.T0 !D%Fd2ff Yn6f @&dܹv/xjK f$9l2z6wW)zO c;8k}2dzv4ӻ+N[S|~;m ?^yfUms @Xؙt9]9~?vÇ>| o6m›{(7YNQl?#%KE eiuo~ 7Njix=l6d>kǔ w.t3ɐ]q1L&yZE>in)=} + c6Lڛ&\]KFzQl~7ɉݱiJO'6Oߴu?mmըOo,wڹfiɏ#6M9uaCW' "Oٱ?m>+?~lr~3w4M9w74^3n'|f3t½33᎖mogw;τ;g½AP'< Y@(;Ed< : PH˕PxJVZO|+:n4kM&SRRҶm-ZW-d~cVӥ_IV+i5WO?ayG$ K2t2hfS6B)ذ"j\Vz} ^)*'Mf [vV=?oif}[HvY!ijA ?n ƒ ?r#HBxk"JDhAWm3#)!%OxuÂ)AAsjHUFnu*BhMX2Wjp=Q9 ~Yr7֯g-S$vԾr\سʚ<R>Qƛ]֢ÃJ0h3t6mղ+p^. R:7S{8#jհ'!Dvtպ6Dڥ;  G\XGuS`}57i٪+pA.! y9mͬ#ȋ+q ?Tj6pXrjo YiJ@F 0_nAtQ˛e|*qN%\q1u^{(Q"!!(JA$ J-JDDPp7#7Wy_hi 76WmBD]&|Jr5{`g:Uz\elwiK x(!yӻ~| LE¼3{m'zw"gl PX1 KJޮ͏,:02E+볭σEd;̐׏<!D @EʒM [IQ뼡zY abkfԔ]&E+%!`"FsB(;)+CJR"Dl.kRH$@iQ$Ita(1M]KRA'ܽ}hA/F"t`+ y~ֱcD "!k֬6 6[YD.sqyYfA"#]_FM-Y\ s͚V(Yِ۷Э[}0 )]a{UywEL?EZCа]4A˝*(6b"m]*IRA|leE`4X?j#*'tqGú%l[Лn\TyBYB>}8s>_<4]~m&(`+ HiڵZ2˓4^$ڶԢ&Gn_DHrB6 Ӎ) vXEI׆! i3ouu`hƋvNCl^t>]3We5}(R֢8`h:QqH줅G4P`lTܬrm ee%a@0di3~\˶m0olP733sw14 nڑ#Glْm/ vRYkbqcXI@_=>< ZرcX9pW|"e͙~Cΰ%>xF(_SP_Xc X2OϝeycFbT B!q(˲gH!ARc&, u^M T~;ܙc&\5r-(l^?Iâ𤋮$ evpiҡZu xǏ$Ea';ٿ 3 P/t䎉g/쳺i&)ejc74 !Oլoˍ|QU(`sn>{d;j]O}һÛlY[tdvC_Y?woN痥 |sf$]н wG-zoBfYɳ;(H$ې7 Kowӻk⨯n"*#hŒޯي:Ac&޳mI^ƉwMO+@f"Ukm;`_6>7E)@R+hB(}6MΝ KZZZ|||NN{dl2`9P%͛7ƮP!evvŋo|;ˮ(_>m03 wѣZf >O?nݺ1$IʺvZHAOT®(X1^Ank:#(}}=F}V}P"!7u:(}|FiїIY^Dž DudGH^ Fs3;y{{NIIWv !$00pܸq? YhDt2e^^C^^^Zʕ+{qGSb+08hА8u.h3{u~+W~!s)A^nP(\\\lJ 4W+~^UOJJh^u#G{dJUXJӁRY$Ιh&9 0dţ~WqO;:𣆞T/f~˕J"ke}үJ$>Q%x@uv.Z D$ DY 4CY\EpC80$9^6PvOQ#/|#>>' >%~&#$IlSmΞ=B600g޽{Z>W2\ء( O?G"•+Wܹqk.v*>sQbmذaz7+yŋ?vg}Vv3gVP;fee㏋/7nc AAA~Y+KZ>Y9ydf<\kT~qAC$IwygϞf7ˇ']s+ MQ^(ʷoYW4vgm Z'3ۛ8烂APߴ;w;vJ*:uYEQTAhԨIZ)=s B+Pf͋/޼y󩧞zAYz$Knݺv*b0nSz9VB(PZas-~'U d(j 4tB>ڳbٱC;vc*VE($ Eeݻm6EQnڨQrɒ$` *xVej]^$IxLeYVR;#Ȳ,r \RJ,6vҥKjbŊAI^H'I҅ 4h J"]A[>_s9BS2NUξw7\yu7&+,K‰xCԸx%(ZVEQB۰A$B(6lx7EIRNWhEQz,jqoYY CB|\5PB 99j&%%uԩPUcAxhuͦdd֏4YU"L[2naYUvpWO9;LO~ z͚ȒB)eEaʯ9DwsO3K{޸ ! {cۗϝ,ٺ?ա浝uM7Vjԭ׫]7| 0lѦgLz>7чby"Oڏ?Y]]3k2yfKn~!]ޛZۉSG~ٺjW:cJڪmi_/!2˖:|}\3c~m`#Mw~1g֔4/ბn?@׉.\"m׼w6;T:AdJF9V}>_}2{K@QENp8mvssuymOϥOZWu-#&KE" ,D+~`w?[ M^W.-+Yn[=ԋ@A7U ,ur6uߢ=Sr]ꖵZ,1&˥6LHs"tKwfj?eZY%Q;E_7?%vӮwg|籝'.i['$Gq"2/N9{ۏ%K2/*‹ *AeQ @Moud(m}V ;~lϾn#­z1 ATt>EQɲ,_Xx `ꉯ4jиݫoz#R- @)3m'MRRsS*zeؤINJ,رj$+IeYr: QLr#6 `#^ؠN?JX7!k}{&G$I2zl%+P咬{)_vieyIdIdYǭ5^mP+9sZaǎ#ٲ$[?ݧE[;"e%2kٽ̋E H(+(&RsnxI /NY )sL r7U,g(u-hۤvꋶ)[\jk@./:Q=4MADQp\?* ~((L-es}|+m</,t!*"*Št1֍Ν;lf(VfO[V_ 8ky#/Y80yZ@),_—[n\8a!+N=,)A@ BP EQ:v8c]eȐYcY8NE@,0q"&NO ˲N^cYsIUqxm,˱.KVX'/̧ 771eY87Z~=8}qp[:479}UYjNA$'NWpGMg֤JFӾ:`cYa!\$ Nd9'NcYݦ۵tj}i*S]=~JtHH,߷tlhhhhhhEd@p:Y~. =eYmZ }!$dqɘ~w[+EOjC 6$|a1j簦UPݺvFj*4୯ ıwٞz_R^A6@T|r|Jz!ꃭv EW͠gア:D !uhZT#PŞzp0CC&m"pFntH+am60E:w~͗O.BH{{c\#ϧfw#fEA -UVmνyHփXsA˦UMGEQ(B /H^ \ %`@%? gm* /꫷#N65)+ֳ{Lrɡk}#G4S` )=wBm—x& ⎉AԜFT5_!g^"0a;CEԭ`jo}1{Tի[Տ s  4FQY}?{TP!L^m'#bqUZe-s,"NAr!pG%9ݞE#+CT ^A1(B {P  PʁuLj:eur)uE 2xHB Y7!*:Nͨ~;~{\! ^mP E1ٿb޸? >wA5媂"DQ|o~Q*$W_.> itg^5ʥ{0Σ6qO\V0 f룤+8.ƕi90 JW14M]'ts6Q3/j wƀ(Uf̼~2s$Rh"#̀E {зaF0jz#>|)uiv؜NkfS M fnY|gh4 wЭaU푼Ϛ`v|^bFn >M2k4EUPW^ٴiS.]T@.ZazK5~ٴǣn*԰ZUc0EiP MӀV\b7 k֬ɓ'6l3GGMA,_g~Fĵۖ5jcLf=1f z3U5kZ7ʰb~qw2q yM'9I#EFF^pvcWE@Zz%Ek"3QјO;\n_ Ѡ0z]UfE0Z1Vh4P&0 sgļ2kP<~jBOp>c:&jӒcZkV/Hʨ_a5Z.S(]Z=ݻK%-APU߱擒/ivY'mwIt3PEA} z=0䊢4}WwZ2|*R#3Ao`#'͈yu[F77sNܝ"7S/vS{"Ojh4Z`B.GHn}=6>i#5%=_P#h.p|HFƠe(Lf݆/ g'm^\Trm&"˲HSbҺhr^pEz.mng *AqǏ_>+Wy:Ν;*T0eӣ_eLvY\A A{+yyyٕ+Wj5'Nh֬ie7oP%;;իO?I *a)Ԭ{_Lf?jDΖ zYlVV`Lf?bNcu@.{9yp[l"+E9gro3E&QOzIWI&{>(l+=<0B'!:&} 'k7DŽߙC(PȀ"Ies \{ȩS%,,^asss˗/xZv„ j@ y P&4ԯE\\eCq$طo_5RxaP!ۏfByYxOEQ=߇CQTnn.˲EËjoa>RU6=TvfapԴB=5qCz}[3z\^f.S C+ƪjU}UoެY/jbd PZhz\+Y?Vz}X~ zhZ5oϛj:_8YD7O4E@CkP` ~hE!Y\k%Óaz}```jjj```PPz"g .Kqh4ׯ_tRxxdnCl;wPSOywgk4I+WfL=Jc3 SPٳ^f#(?wäQI,+4%S+ \є@<^oCCC[nzO[UFDDz'BHZN:eXj֬X^?BHnnn٩dYmڴc {bԁz*;;͛<{J:"(((88$4MK+VrJN~鿭ׇ(LAԔ(͋]?wÇ[C*[n=SշQ-`ׯ_{M6!5JMM=zCW =9BL&SFo]A$n(ɤʕ+WKQ0E!rqqq[n]`Z_EQaaaqqq+W./qjfqb()qaz^mǣ?jgwffjP( ˮTBbOd!Dm<\JV[Jʕ+m(~_CK>?>?>'Q|>1ql>3v\TX|-V|o>>' >Ovw}zߐ>O^W|4!!ȑ#Zvڴ‡JV{SRR*_·kV9cǎA'yJ֭[f訨wk IݻyٳlmдknE)TBPNE-k93sg{؅ Գ:a7dn:t(CM6-2o}yu4gJ6.u+9]pt?@v6@h%JVOumAO<{+1T"T`x ?hϊugiV:kg7efú],aْ:*LLzŇl9uHIIIib۷o-]#ʕ+gϞe˖}[29p"v(Hq%yWE\gǨ26o}pЂW)^Imڼ8>>г~h=wf͏2$… 4%D jԣ}rC$B\3r3஄J,) '[:zPrż$z)ա2m4EQ`ꥋ?ݫe$I*BӴ:`"#Fc```v3g&%MXX؝;wf3 T:s)fxmmٙ !6l2/yv}KWXz5 P'l_R{;bO\ح ܵ]{mLO/ IA R keȓ#?.k(vR@cs*N 7]1Zz_o \nepT>0*˲\4SK$IVP]e~7M_NBcaOzzĈ7VXq:uX,)#s{Wׯz Pg+Gg9@k=EVt2gf*U*4=U/2*3YK˲0 #IzҺ$s5yv\G???h46+&ߋ{Il'ō5jQF;j {b$U>*..n[&!WK&Fh06[x-wݽ"*7u{{e'MVL0ᑸ̤Iq?X K(q?OWƍr?iS7n}jh4-j>(f%M|)#]I}Nזy͌.4d5G3{/W٣TF~{a|bMEqG[nI-V$ȯ&.]$16_*Mdž٣ϫ/+'7,,n|pى)q rlC_|qKq!NIvSSST"8>>J8%))A!Zp}Fs3Gp?;CVNr/v] *_٬$j5 jK[ĪiKvV3=:/xճgϖ/_^eGٙ׭[ 5 >,;1>ľF}7˥V˲4,e@85W*@~s2d/]dӗı -m׊!B[.M87:$$$$$$t؜YDZ샌3ҡ3ʢWpMIu/CEQ&D5*nfXDsXr6I1<E#眪9emj]Kv.ݗ0DDϲ}Kdž[뾛He?r@93Y]٦u BBw xœ]CB*T !f;$++Z9I@DAyUWHB>/\8>Ey<{~TLӷwwt*'7t76fBRgVWkȻu~ jxxt>oШ3z֯MB+6m5Ϸncy߽{M,XV-:Q IDATedƔֵ~ U]?J>gܐ$=g§qݻ(A?[T^X1ի _dJISYUQZj۶m3 {\xe͛G!sɱjAFk 'Nz6 kvc~]JʷEM{ƽ3n;^j׍0{Pt!B7G  l Oփw` ݢ}A˦UM7(\z^Y/XÄT6l;lWv`51V(Y! ! ! (JAJ-+DdPp-{YʱӅkwl.c:Ste9蝘ɘR"'l=e/@ex u&0dzq=.}ʭLq%pYBV32_?;C_GZp췻J?I׭HY rXZmVs罓QR?Ȗu@_]P qMZ|:mnM:"|o6 ][_6BmaJ\R>޾ok^}?;J^S_A'h%Lɑ^ߵ )]$^ܵ]]MDtVZEx ZS4M2hA)W=!Zȷ1’0݌sZ\YY~>y4M 4h+zPW0v+qM\r鏤YN,v=i-j, 1'|+a婶mq;KZ4hЧOG2jBl܁^Yj3uYu^`i;}^4L;auJViLIMh&ԛh;FqQ.e5Q\:~sVi{HW{y?XbsryIp7⍘cbz֭c"e=nHOOOOr_'tӳ[> ,"R h`{H]4da2ջϥ[mNzE5)ؤ@)/vEp>%˰9VKShٞK63'+ԩt0 [;fz-g(6j14݆NupyV./방&ޠeZZeZ 1Qb4 7]V9` 5;MY{r4/=:xb 0zE[GD]zѣ۷oO춉BeDYn)ʬUӅi!}"ޝ:IVl?~|)8m *$7\o/ ʉd?dqVU[ޣX1oLtz4b̨3X A*^B2h4WRwH0 ĭ99}TE1 '5hZӯ5{)3߽_?Jłe/ܴD%|ecú|ꦫ%>0Ƨ(OY~s a4Z-Z<ԫ~ɐ&ƇF0 3 j4ZF17vܬG2bAAa /{ Ek<\43b^5t?gr{5 'RvUCHiI1_$eԯ0~[NMߎݥOIfIA͗`Mh;vÂU&'}E@QՆd\Qo|N \곌V z9iF̫^o'xܒ6AɝcVwz$ɰIzݵok=0nUDjm{|uGo&"˲HSbҺ(~:MgϞ%555!!!//{TFDD(Y322VůTFݶm+W.\= @>*U(5k Qs? 30_u,;F%\>}8~7lذ$'EQ^O.!(Ԭ 'j9EFj ;MS`౤[iϱ8ч2e/N.ZwJaP鑳Lf?jDΖ z9UYY129Ya,wuLцGnɲXb Z](9&FlV*TuwJ  (8##.pGq5A><.8㌠^pGEDN/ʾ%$!Iw?*il~/_/éՕԷ9Uu^۲v}?E!C<=z8`Ŀ" eddN(u$ڡ2eP6~$RW^Jy$9-.pn_.{pG|psCy8PGY?.kK~aT9'tCt%׽tG= ngo;n?1OD"% _w_y8\QvP$rpIHDkՊfږ%%KhfepfhZi-wmWƺfֈdKw R999ͼ }VXi"`WN^A,"?e~={,--իW(D">4 L_~E4-q*~Νצ=q@4??̡YpV}h!W䈥ğ$L-QJ?=]W\OBq;dKh5M>jIfDƚC0t]wÆ %%%wl~#"x60Vu$t$yʲD) 'i򬲵]"`s߅ݑ#uqR&zRͦߍۉmsԩS׮];f̘>}SE*t7~>^5qs71ִu^:il᷽v*))0a̙3{1 eO0nh/H˲z9y۷WTT@SMi}`0 ^31[L8[|4Ki2&|+pxq `C->ChGq]W7C,5kҥK+++=/FwkY֭ktaN&}I$ɵkn۶-op8ܹs}6{)2b2dPD"r?kjj|ɾ}N2(=@wo>rKVVaߒo~ٓg2\paAAѣ555?SP|;wٳgnnnK+,//v]wٽ{kSG~jΝ;_s5=ܬY.FaΝ;wR%~ ~Ix4m5@J@ {1 7Pfffw^D]]z͚5'xb$N<>l͚5 jFv]wʕk֬)//-^ߋeYriJiwtqq֘3{Njh!"`0# ZvfH,[z8?ER2|&Md=8㌧z*EZ[u;pu𫫫6mڴz m4o7vdr׮]#G\Ԟ^dSEdzĻ&.(// ~W%ؖ6h#:m>Pnx񯋮87ܔ>cWD~~~2􏴷~O?ڵ1h 񛇩ر#L:4}ڤTG?=sU nZ"H^V\yc=od2y^8v=oׯ߲eKNN?HdfæbŊ+2 r+նZ)̱,l7]Z<~mgw{S_^zÛMJdff::v͚5Kk=s̡Cvs]q"{r?u]8ӧO馛"Za! 0gfU[KW~S¹=)˖.^U:%T%ӦMKGHSQy^FFhGn{KWzie߃{Ŷmu{WXٲe#FFud2;O;tڄSn\N9"JY"en?=;SY7t=q<'u~ m ؚ`/qckaJ/}[rFo5IC khPRNmJSzcϬ^r/ty`1ãO&8et㟟9'w.cBe9&k fW[6h(FRP˲bZq-YG/`#޿O=,y=G)c)|ц&sG+Hz%?M[oEDι~qBᦇ*?a>ҧM4!D߃ڕ'cp(?JʫRVTS=_EI=q8v<]b^`=Xu"?YaH^jxj6Zj6@Eσ'rIHnݴf4bVn/o8_IӽR3T0wF?#yqY_3Kwn_*H$EtbKzW%II.Ǜ -z뾻{7wDjwm f#{w <+Kξ+uqU[_=0όޓ_tYRpH}'Rշ4GKyUUݰVj]-VFΑѝRGtlLqܵcK6 P@Ҳ,Ŷ9 Ej] t!rwݻEV.u\.ѺNͦu6lWS~|LrdF_|`ϋ 0@d߯Rѱ~'GݓԮH!Jߛ>)}PhY3"7҄wZsLo&(kXVd?DIu_ϊpB 'OxmbȰ~yOM߰On{k^~F䆳d%|_WzDʏr˲<,,~XDTzbii` ,IDATw TD>4&#""D*6*&>\^oDݯJ$'3$)"~^{nB`S/lMSU7\~$pw)_.2i񰨳~Iixl@ކiOW/]JD9]+Q^J}ϲWJ"N*UAFeT(K)%U)2kGe) u>ѳ]x˩CO=@ton~pf'r߁nry2D{C+@HH -KYuRc"g'D2V @EDHPnm6Wm)>wi8{/_>}LfeY=ʻH⠬Yti sGw"˲_cCγ ;;v%wyT AiK%)Rk>|[\CaQj$ް3DNv$)Uw7w+ߒϟҖ{G|;v;t0䓕֍.eY!TۭNv"sv[Ƕ|t8Wٶ-eXm[k- Ϟym9μõtH0 6{5 wuؾ!&ʏ2~K^q"չG Ͳ'㉧TV}lƭ$"[oܸq4q˴j'Y"7"tT[[m۶ӟTD.Ҁm7\7m |ڢ9T ʺd gft>uha`_71-uS͟zR5tI N:F]SEdG>3gsEo;M;747n/68"{wln {;_zn&"7l޼y{@/8D,kpSm;DΠMw|Kue nwY ؾa}Ϟ.ֶEO`0Nw>&:<Ƕm;xmu内/N/k۶DOW;N s,x|例KV$MP~Gv8dV$hg@fF~UW'"#@8dCp(C: _n>t`J͗-{J go}DD䉹[;MAZUTTtΘ1cر]vꖦsٶ(Rzw\u% 9G3زBmA 3#|Ç_x!CM_b +qe}=kǭՁh^8 +%UR)]K/=XQmcf/{"-(U?^֞ _iJXo߾ 4 B _."qןyX,=N}rۅD&={^C;  D23 s~;w^U?ވK-?>q ~}ϛs/׋w;Y?7s闝wS<6!Łx_ PoDzrUk;*O&yc[":3dg->sŊ#FBzdv.-_B-Z4h پ}ڵkG'8>I/ ۷>qjkk322얮q{b"ʏmTm:^}ǧO P*m۶effm?Sw]Y6n{]]]ǎT;qg׮]޽{KJJBP>lWyDb".//_n]>}$mzTT$hV8Pv팬plzXTTK~~TD***2r:5q G#["ru{(f!ög+.V n{hn-Z']Wύ%˪bm婜hc[ѳ򪪪N:^(R@?@=m;4t )+k&233/2?)t5Lc^g=^9Gmbx< ,kԨ}c8eUUUb1B!t+g'|~s.U'Nlb]Db˖-)јL&ľ-:uꔝ=h &Sᦝ;TNNСCKKK.\hFC=ma3~cM9?-ˊF;vlkYV h*))0a̙3{eY&L())io,o WWWÕĂOo/{d(ڕ?wW/**j.gIJ)sHwR* uڵHo /L-R;QS`Cwxox;hvy6-l>@].<"@ P0D! `C("@ P0D! `C("@ P0D! `C("@ P0D! `C("@ P0D! `C("@ P0D! `C("@ P0D! `C("@ P0D! `C("@ P0D! `C("@ P0D! `C("@ P0D! `C("@ P0D! `C("@ P0D! `C("@ P0D! `C("@ P0D! `C("@ P0D! `C("@ P0D! `C("@ P0D! `C("@ P0D! `C("@ P0D! `C("@ P0D! `C("@ P0D! `C("@ P0D! `C("@ P0D! `C("@ P0D! `C("@ P0D! `C("@ P0D! `C("@ P0D! `C("@ P0D! `C("@ P0D! `C("@ P0D! `C("@ P0D! `C("@ P0D! `C("@ P0D! `C( h>#)֯_DfOqD" SMQ@Rd^~IENDB`workbench-1.1.1/src/Resources/HelpFiles/Overlay_Toolbox/Thumbs.db000077500000000000000000000410001255417355300250200ustar00rootroot00000000000000ࡱ>  Root Entry`m 256_2bb5b4d9bcbda2&{6c6d̺Jc6d̺JJFIFC  !"$"$C" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?kW=OPMK-;Qk1:fUL۳9*0$l|Cmut+k("I縚ݧ.P#$G#՗V K r;zZvizȌj^G1v\ehS>(|+a\k$es'VF{m5H[CԃzaaQ-m"O+GUWќMIҿ_z7GFˤ4C ۻD(Df܊ŗ ~ԎV/R\Gk_p#VF#8 zSXb^2ݝFߝf6mBY@Ӵ7ddY;2Bv{\Y )I/=E~u 1}$oʊN,:/ʛ߇-vpȣ(ߝ/=t:&Imu|UHvT}ƥMLLsw7P?h=6ݮA$LاcnqwPi|No'X.CG *zOjgx~}&\-.lnb{72 2?aJ=)$G)2 ;QMy.3Jݾ":d `z>r78T[#9؎\,rN5o6Y/ۋmL380vYUA'S^ ' d`D"LW2T`-t }n;bx"ggcHu.v־Hom[c |>!0Xp ;mŕ3wn3;:RQmѿ:$Ѭny/T~AB6=ǭ &$,R_8_)F_i,7& $7f$GA|Ŕ̭?\9a גhT[ܪؾ$u.~P_ZI'bH6rSz\K JMɭYYx?pZǢ7Fe$rYԟZo3çYpn|g82x>Vzk*-'6s{իt2CpC|&nQT%hKK[ZVo'k8LQ[m-ojEhviBFmU|Aaw]Y()lȒD2#'8 I[Ms<&sr 8&piҚZQԩ4k@KäFzzZr@ ، |xG-῅gk!(WRqҭoE+'#8tzUkk}H~NV⿙OGIix5C!K6rp-+icA0z~&Ү";6M\EHpH"!nTj `OzUҘmK;0)ӧ3W.NP, ZYL0@*fYKn>Ga;t aeFQx h.; ~718MU[SlRб!dQTՃ]X[G,n#Ufb8A[$ u{Y5Z(vrXaA2}i1B+{:WUI(6P(ESufPO\֟}ߦ:?StoL.O,.%W6D x*̣*)`ڥ 6Arp3qg\MN,N~Uf7NF)Q ?}=kF]6eI6)RC) =GjVv}ֱ`+-`  v@Moہ(z?!V<"βP. cxⴑI.d`ФwJVЗ\mA$he)b 2# d)(88d*WKqve!O_izڱ,bS* 1¸*HV5=RM+R_*,ۈRxP%ր+ipp08Ǡ-c&v #1f)91Wi5;02,pA @jiڵZ-So"m! nE0>qn.G)ΰdEYi-o$ HAlp8=Z$rB 8r9kyA^H7h^§Υ}L5'eg>Loj -"8&s iqϦxOZKl@A9)T6Ќîj_^KJx kKxXή(]$ RŞ^Gs*[n%J+0!rBJ^#u&m-1sl:"OCc2Z:l| {=R}B+_n#& !2L%.J2l\2W>xKVk?BdUV=\mv_P[Wü$"6i] .'oM;xQF2ZXm bezx{ χ^["i%ڑĪd#)㑉&d3r|=`2$RahyR“$Zo +JWؗYPl'y̱p?8=)tvnj qqu%D"Wjdc ryhMzF} ݔ&Fl˜,0@7k7;oOo M?g*ځ56Xvuɼۖ4C"g˵Y۞1fLa.ՖXg[{ ,XDqtFP%Iu|K63 l5oT[V0$cEb6'kQ"a`|6>Cſ*Sm>X%8R6| QӞ[ӋekȻAlsmuXOYYa>g3>EۥVq-F%ŪOV&MVT|`HocK *T5h,YL[c`9f(u{t<mic~7UG6gvzok}cjz=, * 6zsַtRTq1~aIB0$(?1ҽlgֽK^8U5잶:OfԂIkuLcތ܌pqDKigKgWg`@Ld3kĞ5mvmb[$qۮjox~ ֧[ V$$>l` j])5誑r徦=eVPIx0fZMD%Q)HcmNXy_\.Iyfi]BHdbh]%oyj1v:mi@X = fYVRMFú*:6,V+J^M2`CЭ$MM"F3{,h>wtO wڤɝ͕A+|/4;-n23֧ S-qѯg*OW&hg3xgKӬl.ExˌI]e.Sg(d NA=9,52Tf}mЗP˽s=rk6Y.&h CI,ÀMcC4'*UtOfZC蕮Uǁ4.%m] ʌ Vzteзx< Y`~\czqUh)k1HbIu[ÓrG>p=}+Iq]LKi2P FImYcd$ Y -3nH5DrU5yUUeaapH?u1gIVom>']K'#2**rqy[oa4£'ݏ~0)*qksέ! !,S#RzjNnЛ PR,z̫u -0H$5jpjW7z園%aqI V]{ɘv{%rȿ7`~q'PZC$g_m1uSk[Rz/ _u}RI8m5AvALĝ/NxJ̳ F%c+!7ZǛNTI<7[sjPjxH#c~fl$fl`hxYu#n[6͸95~={M[4Y"x$Y7$x[kzeͿ v$^SMPcSoO@hZlڅ6yd6ÀXO [+r+p>XcFFWIPRsqV:*# $qOZ,G#ԗ߁6-r1%vҩ6J,5~/ZݜYPvQjcZ[3{iqr [dtƗw^[d5܍?ڙSx\eڒsvNsЃbz嶛|. HIUHA'znZdEٸo / &ݷh\8틐WKji):w!\ l1v: *(!AucV5̖2\ܓ_R˅Ds'q\o$қC#NfJ (TºE>]"gŘmf R= Zi70Wlkb**qӿJ,$dhj:EsHTgM\͵u5ԶKicdV9:`wF9tQi#~>'KG}? #URo˗ 1[o4Oߪ ȶ,F \ ;*ji\?/yzH8J2\)={-u+hI>5x' ieMtxox#/[AmbĽpɽzYz:ߊ&xoJZg#'SkI }qc)3鷾FK VcF=7S)G@]AN阓HtО~+Ya; dIRp;ޥ㥳][RDX=ER'o4`W=lKguf.-M 1F_B1+#Kny$7RXatdHqWo MڠH`<\Ki&z1Ʊn)&գ6O.A[qMm>;(.漞 M"Ei՞$VU22F0pFN@6*Ukm?ine1R vx]eo3h9 >쮠i.-܌0 =3YxK,0޽ċx1:c1e]W#|uܹvnǿj`UUJie6P,ys n;n>,$Ѿ2ϷOis8 V4bT-KbDp%#ɓqYV6`#"Lg)Al χ.bWGБޛqW<HX+봑$UkZf1k=bB X9̼(`A=2(-7i%ݵn%ժ:A3A)xcpg?JvVin2 `@>b?4k6kı1M'E($@HQr1@= 1cFLFg6Z*FLp|9p99? ZhBmo;y@bROƞIR ERPNO}S^3ֱDZ:A);D3+pz0zqcºQrGc0s5'<5%þ#6FJNݶB9ۦ; #F.*Oq ͛Ccwcp "Y@#<+9kKk]+\3ic@9 HQMZLh~6KxrBUv7|孬VC<<~Uvl0HlaF>ijmVOѬԒAOy8] cw .?>ݬNE[4P7۵(v;]l@n?\uEc}X.kӿQq6 LyՒ[G3^ X!¨k-Ţ+#yl8workbench-1.1.1/src/Resources/HelpFiles/Overlay_Toolbox/Vol-Surf_Outline/000077500000000000000000000000001255417355300264255ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Overlay_Toolbox/Vol-Surf_Outline/Color-Vol-Surf_Outline.png000066400000000000000000012230221255417355300333650ustar00rootroot00000000000000PNG  IHDRiQc1 ciCCPICC ProfileHPY_Das@ 20q`Beqր (5 X wEA9&Tnz_ wrDXTa8ۍ=<k7M wwшnqV4.Pq4n*g\8aЗ.B0M,1Op|HgdrH̼B@9DaƩfa?SϸeVxA(_nVjJrH)3FQoE)A ~0,dq ^' D>i }8A,oφF,-cOxY^+y@%0HA "=~M˻et l!Ԙaina?f;{ :]Y @ <"f3<@ЀPZ@ {i + (pA"HbցM `/(ap @38 .: #A8Q!%Hҁ K 9C? EAB:h TCeP%T BP?tƠ'a f0f~p(ë8Uq _o<(@(wT *6 P%*T= ՍQX4@>04].CC7;7C W 18`ؘH ǔ`1.mcvXl6 [=mcI39q\:.ww7}xK>/oƗkg) A@$ل#6 a(K#:CIMRb=D"iIKIR.tt4DH#1 vQr; BѥR)%c)['Q\Ij@4AZG%B:GD q GfLL̠̤,UB6P6UHVNNWS''wX0EբS-#. KӣiIB Z/mB^NZ>\~|9y Eץ)S;O ,xm  )*+(6(VPTJVڥԬHlT9Kr""EEN-U9ң2*RݯzIu\檖Gژ:UY]GsSza54iZ Z{:&յhӮӾCa$yUYwTOQWPZJրilp61L4,7a c퍅Uƃ&dIIɐ)tiK3mh]ff_mS̏?lfВkYnyˊbeѪꕵuA6T%6[m:lيmmb*4fycfG[tS88&;:.[a'M'SęEÅRU˕ZeJbgt3w5wwp_( ,|^3Ves5 _;~d2'b%%< 4@vGAzA~Y]|`u!Ԑ!!BBw> Q!4\y=J9J\lﲑ;Y~uVJ< Tq&qq\w> +oo,)8YSBq(߉?X8.p ^%$Jz|4y:%"!*& ;WZ_d$IV;޻zB'NҖӐ`ӓ]Psfy懬kddfo~ZZڎu6ZZ_cƼ#޹67%ouoDliS||qVǭG/wն۾ ~.]'laNN;\v+-)޽dwƞ=o{ĺ>⾌}RҖw\XvܭBb[]R=TxӏVzW6UVrXHV.n|槿+(B3y pHYs   IDATxw\GgoKEQbhlDc5&Dc՘bC,رXPED.{-k3g [Μ93MZ @  zZVT,c#w rZ7@ rRTTr|[[j @ >B$l6r<zV @ ^C$<$OSVUTT\P^?^66|>`VOqUEy勪ٔJD"mpmCW*n\|&UE"o{N~2 z#@ ^ݼy9ZwVRPT^[WfpNB+^~!YaX◪YȻqځ JyNnDoqcH$I7'@ 8&Vwփ}nb_>x\r^ n@Νlo vk)նso&^?WUW~l7avA]{#)}˱o.~}Ժy~D},@ Ϝ '''WW׎;J$w$##C7nx:t?o޼QUUU b'$$$$$߿i5odBqݪ7Ĭ(/Ќkk4gڴ4@gϚR6[=O+/=ɝќ+8\_ 0Xٓh$r-صD鳊Z\*-,=|DRSSdɒH3gH$ۿ}eJKKIttt|=xC5kX[[Ÿ+WܰaI0 3plv4Jp¦MB}TTT@ >J8A<E1JTeQu$/ Z.qy|N!i$OkT`(KcE|KŒ9*E+_^v" j\p RdሬDBʲW4q cǎcǎ B l6ϯy'֭[6u7}׿|BTD\լY3" fI4xREmZ(gϞÆ ۺu͛7߄Ĭߑ4a[rXղz-q1&[$/פauTh'[b[ePl IB.[P˫M/u\&ur P" GX8.n-7eG 4J2))iҥ8ھ}ϭ1 S*SN]`322Ν+VjѢE]]]RRݴi?ݻwgϞݮ];EQǏ?vXYYE222޽?sʔ)]tGo6...333$$d[lIII?8;;ܹso߾ٳgJJJfffҺv(JHH8~xuuԩSauo9244Awގ;/_nggGQEQ)))}ꫯl6EQqqqz}TTԸq?\fggܹ377 8p„ P;w111~?ðNk@|U<@ 9\.l60@Vo45&%HxjK'+%B |.`0 PX>X֒jqRj岹l6cq,p"(ROz>n^(jxReVVV6wܱcڵ+==}_~8xbՓ&M5kĉ]ܿxx/e˖,[ |r .$&&nݺu׮]'Onٲeܹ111z>000;;I(aÆYf9;;6,''gСSNꫯj4:SN݋xAHHHiiivvvvRSSJɓ'~ᇎ;޽{ܹ{챱h4> 6ldիWّ$IQ\.~?zꠠ;&%%;wnϞ=8=:<<\B.]V^-JJJ={,X ??}DEw)h fǬ|>RP0`@T*L5|7`|Bij,Dl@81`,LE"P,q,gsy,58' CC,|񳖜aѣڶmۭ[h̙3'88Xv 7o^]]]ΝwEDqq;wooϟd֭[pK8[fz$W`~~~ADFFFGGk4z.ʬ`wIDFFFLL||r N<ڶmۺ1cݻ… C D+Vzaӕ[n-[jd2kkk#""N:֮]'O`뛞lUj9sRJAP6l؀aV˃>Qcd.E  Ĭߑᮓ9V,HTI$JƔJV[[hv,G"bls6C#vNF%q|@kVC'4+#o8+TyytT*7nY p9Fp `,a|'Lڎn2ߜgE+䱹6Ʀ E϶ z!oKKtAz ONL @@K+k%Z`"!pMaس"n69~N\q9,IRJڎ*v+wq"!ED!ͱ :"J)JҭFgVEq$TV'X 'I8UBYSj2@ҡ2իݰaþ}lmmZmǏokkrʕ+W*O?tjX1iFAK,Yr_~iooVVZEDDmڴiǎYfڵkƍ>}zPPPC0PԽu։H``R3W *..1cT*%Irʕ8XcqǷm6a„~mܸqE߿lmڴQսzZdT*G6תBpݺuK.t ꫯ c;/З9|)S4݃@ >:cǎ>9$z}mm-Rfx?#@ ޭ$p= J_{@ M xĀzྲྀ;/@ hBw@ C;@ Dj@ xG&?~ͩ@iU@ K6?w!+#@ g6n؄f͚/&8 ̝wt^x޵D_{p#=z7$ye$0~x9pǍIK#4Y̛7]+@4֭>|hzK8+:\\"Ijq `qe W׆wY1k !opÑH$ ܌p9x ޵"DK[-t"mp3OS"'me*J22|\.tR=WrʡCz&jCߣG/g_vگ ?ј5}S62;<,SN=/[nmccsm<1 &;H$ss?~i;t\VV($Izyy={l[l)**;wť[n{J֍ғf?/xFp~-==_=Q󒢨gϞ͜9SRACի˖-cJcefu]]m8%hѢ?"??%K͛7'%%ӧO&Nؽ{ 6̟?@B޽bqYYCCCaBHII?~C F6y>twT*}1vXRZZ:}}'" lLo CDR={6::xΝ;w~ݻwW!< UVp߽ER>#  NwyHH'?++LNNn۶sss]]]u:̬۷oAAA...)szX,yiٽwl֠AQgϞ{4/ 0Օ^c_lS5c e˖lf"I`ivh =zTf͓dÇ3dKOOWT"e˖ްaØ1c A~?NێΝtQQQoFyFзo_^wݿ`ooOgk9lGc srrzꕜc\6l0KKK@||'|_ &(ĉ͚5 mvEI;`6BaNN S*++.]#//w`0C, :88tOO/^LKK{4/44,,,bI2#7_iѢ… 8` ðf͚ݹsb)J>ODYYYyy[ >~xذavp=2ZmV=z V*k7qrr:|qII@Vb=1 Nڹs'Lyyypsst=p:|ڼy^ U̳h>pQ`\.]5{{{!I&=رcӦM33x5k@Jbb7EQ8oݺȑ#F8qmAQTaaȑ#:8zƍ 4w\R9`''e˖-\0++O>6m۾}@ oE>}zݺuO>XhQ>}hQwc߾}WZ%RSSwddƬ۷o_n]uu;v믿]  7nܸ`s:tիWӉn &I ۷o5 </oe2Y׮]q駟`\&A l69L|q$>eQ#|>=TRRVLJ~qkm%nց0 HRp8^xѣSNnnnC 1C#SP+߿CN8ѭ[dF3p@?o>'siӦ~2d6lrݺu;p3OmTl޼yԩfJMM=r/n IDATL2,77dw2dIIII/^wؑԖ-[2rzk> 6xVjUfN7fJP(F!0 0ygRT*z[>~.]$ʇyy5?{CqqiNNNa IQԀڷo_RRT*333.]aXaaa.]hEꑩQqNΝ;ZhaeeTSS47qtԝv֭ǎ{Ν0SSS_we~O¬l5\u:Fdq[[ے4hСCV^CQTff&EQEZZZdggwvdruu( z}F w u:{1߿qq1A@]eeSz=h4.\2d1ƒs 669rȃ|~\\+V%%%ZָΜ9u֝8qv8SSS]\\ILMM BN#tףk ˥ t:XXX\~]Ryu(J~2-0$IŌ$ H82,ͭ<---:::77wɒ%_!C:glۙYYUViFeeey̬={I/ ϟ?i @Q-[JE=zbS OZ[[WTTj4MadY/^0򢢢,--;<\. xbrrɓaV D"QMM LoZh+^&yyyu׷/`SL9{X,.--]dիW-ZDw2;w8ο֒$p=zT]]M7TuuF1vԬ^~xp(??zҠӑ$;w15;MMMMJJ w}@ >6jkkVȹg---[~_tR4I00tU0!Iõi^+b=y$...<<ѣGϞ=۷oߴiӆk׮+Vo;2ezyz<|OTܽ{̙66AA]F{=zBlǹ\ɨ$`GАypFV~oZPP#F֞9s s6od9΀/^\[[KOh޼9ɓ'Ֆr PT(pV3gSl̋p8^^^O<:ul L_B>}zbbMrʕ+z>|qF8QcCsڵ+V 8~xҥKӶm[~ //f͚4ڵkI<}ӧ  ??vZRSSNJdffZn۶-jvR\lYJJD"زe۷Kw^8Zh|>$쬬m۵_uuub1 (RՓY0Veqܹs΅qܢ;_~ٲe&M߿?ULNõZݻ) [./w{Nv:88|g's2a.xZC;¡j撘C ٴiSvv6}͕;wCL;(>}zRRRV~ǐѣ^|yŖ-[FIwEѣG'Luׯ߼ysƌt˗t/_iii(R[[r ./\rΝ8_~ɓ'~f5,ヶH2'''jֽOf]BC΄a)OX&EَzCEedd\xO>qcƌ)((ݻ/_ZQQq̙7Dˤɓ=֢_n***]ZR [wqq}ٳFߢ=_Ba}k0 ܫ&?ѣ+VA8kӧDFFN|^ >qDx˗t3f@7y-QF5b17n Bӝ8q"44t֬Yvvv|>s8ܹs###J)nյk1cx{{t:''UVڒ$j.]:o< Øe˖VZuޝ^XXHdzzzᜧw6 N[rgzcii@=prFZS0S0 x"Ͷq,ё\e X&0zjvvvb888ٳg_~%ܾ}ٳgZ-4M~}}}}nyA>>}MJWg֠?&1 {YJKK8O,tאuyBcnNI8GFFSE7)%`jR ϟС.] ^ПvI&u͛Zrݺu0.b 9RRRs۠(:t/d2\Jl &LЫW X,c<O"h4%Jd2P(rE zb2 C3gg'5=Lz{T*_( 3)~@eSϤL6ݵkWQhIO^=zH%֭۴i7>vL󑙧!?:|T*(J'1/lTDbggA7;;fFÂLziAz [/ h49e6vd&2-Qcp4x ڎ1~6>cG^-hg2o&K U+@9Mi>CG[ o ΚLD2+7$3r=Ɖt~~Gqd^# @ 9Ƒv=[L$9H)d"x\(&/@|p<}]@4 p'*ZuA )wn*݂d6LL.ןǏ=.@+>e mG4T@ 0lqD @  E6yC @JJJ9maaP(oKlxHHHxG!CE&>>GjĻYyoAs8oq<66ɥV$˗/7nLh=֯_/H L;w.Vi&%xָyi[޹iXxa1u3 ) 4j---PfMMMee%]٤$ &48aNNNǏ߱cGbbg}fDGm*Q~tx9pLtREEoEQ666={nr%KkWIkb3AXq"Ɲn晒ܩ}v^$I޹s (سuX@ P$ o^9uc&Vprrz_ty^_~WV7mԹsgd;" 0*T(&&"###%%ei͚666cǎ_A$۴icD(*99nnn^^^111=+h5~KM+M |sa%$󝝝qft3̌|OhrSZzO:u-77~@$)PMe;Mi9g:EV'%%쌌#;ߚMqt3S@#m/˟Vř,29aL4Q0 VZ lٳA͛7 Jz@  j`GUg˕8OCr X )*';s>zo3qʕ+.]rpph߾}.]~]mbcc%ɐ!CfΜ٨Mΰabcc.]عs+*55 (1ۍG5кzjXXN0fSEB0<<뎎}ɓ':EϞ=ڵ?@mmmAAA˖-wkZ?ݻw?wV qttGmC~Gڈj崟 MS A"q)K^Vv ?޶m[;;;ՃӐryA&1TTGvnݺuڴi"m*ل:6$Ih m]Ab$ EQbi9s#Gd߾}^^^G=zԩS)SL0@ƍf'L 6tSLzv#g ^ ^i44___gggǵZ-mP)trrjٲ۷{mZjU~~slllD'G >}z͚5lcaz}a"HDJJJzzzaa#H=>r*/ ׬YqHT~]~όq8c{0#~PHg$ܻw굀m|~^ns碢L)A5vØxdzgϖCBBX#$y<8Ap0A`O'Ib@DI^d!Cl[StMaAK o.k30iTf,K'8N^0 0 z$Ig< `ĉ˗/df͚6mZ 8xܗ59e-m[TER|.ϟ?x۷^&VUUJv)22rٲe#GlѢ3e 9XhԩSGt&L8p t:jjj֭[lٲO.^xذadϞ=M8V)rppMP)k^555t_M(jViu:Njz^ъD"ggZ91cp}1mܸ1(((**ݻ0%666::: &&N9s毿wߺukŊ .mphEm޼C-XxRJQ𑄐?#d03]Ĝ#G,]@KO/>eUz4bsx<׈{X,_ȵU*A~!dͦS'Yԓ'Om۶ѧLi͛7^zuΜ9-[LKK{E3ڵK54:B iӦmŴi${xA&1իzN׫T͛7k)=h4oS&ԙ9086>k(ӓ~ ƚ'xc,*5Iq I A6qR%Kv܉)S\41,g`g&/եI*t$aaWۏSu%icǎ>|xܸq5j 22/f\UUUsi߾o>}`͛7{ݢEѣGWTTŋ?E/VPP_^ŋY̙#VXaR :u@\\\޽{ƎK$F2WA|~XZZ.777777$,--e2#IRj5u:֖uuuO$پ}{sz۷|߾}[n+Ws玝Ν;a˗/7mvԩv}׶'NX`A4I0~-[wpK$n;$<չs猌 cci$IXMt`LֳgO?__sus{Y\hRˈJKK+:eXn^ǎ3.eR7~t͛/ZvΝsbccWKz#<<|ǎiiio%H$?I&Bh$-LW&};}U4 ى^NzqFvB;)ٴ:M7̥_0\uVOb=BJRTuPT*JRێ"hժU'N7lf&qEQa^*gzaϰarrrhoY똙 JJC4j,6a&o.]XYYIҿWX[*F=wY,s\6/2 bcc/]ԻwxП!. d[nϿzqzr}}}Gk֬\.r߫r=|PH$n|hڴiEy{{'%%QN8qj;ֲeK~^zeff&$$XXX@QGAT;;wl?]aEtx}\|UV111Džϐ :,CBBE^xP(͛7o޼gϲX?`2@jݺJeMḾڶm 4!˖- 1b\.7(gDDDbbm'Ê(7n4FnnnbD€P炂///f'ȑ#hgϾ˽{>|6z"Z$$$t1V^QNp86m.\x(-+++3+c wto߾}`D^t)>oa!޹g]'LwprɓY&ԍEQϞ=+޿Ϟ= 2qaFˏc׮]۷o(aaaUݻ}||&L?~|QQQϞ=كٮ]k׮̙3/]ԯ_?x3C vŠ=z={`&ῶ㫯>=o6A,Ր1͛7k͛߿p׮]kz>553f̠i+++z[h͌B۵kW^^^ǎG=NFvxb߾} >3ق޽{322:w@ޜ4'N 8x W^NNN}m 3d2iڲe˯  ]~P(ܻwoVVVqqqUUՉ'Μ97NNO?taOOE ]t:uׯ_,S?btLY߼yرc/^p7|3xm6ńRo% \.9bRQ]+P$'HNOQdKKիW0f3Dzp݀: ͎kY,ۧOޱcG$mf_~ >駟|^^^ .t\*++kȐ!F40k%H4 yr@ x<+ː$IJ%H_xЊyCHy3oookkKDt?sDGG_|ȸ:;޽{0t040bt0 KMM={l>}w~ʕ4kkk"MHڵʕ+Zmם4iMlImߖ#A_޹s{6Zvtt1cŋ̙3ggg:X^ф\xcѢEp@j7"; vP(###/\`R#:iӦYf5\73ͽ{]_j# ~G&N>|BXha;wL >>ٙm39.&+YlCb+?qٳg]:w&@IIzktnSii)I~0~naaI^=rCCd!0beQTcǎݽ{wС(vȑ#ԯ.MU嚙mڴHz U8:l6gϞW\|{ŀT&<<<""""""<<\GqqqO~رcQz)SIR\4aaՍqrrB[.YDGM>Apӧ>|m۶{Aw.++;{~{֭;w)Uk׮[vYV^=w\:W\vZTTԔ)SΝ;k.bUԞK+++Gӧ?U>>۴iHfqqqEGGJ R=z`:ur>u!rҥKOSSjeda&xtz)))'Ntww޽Cn݊wﺸ0<ڠmھ}AКJ>p@DDѣuq]moĉ7o^lل cf45h^A5绸[￷{uظhܭdرcǎ*--urrNGFI*ݻW*J$LJ^\.WgVTT0HJr@(srs>zx¿<_R>>ǎ[j>Օ7~H5 ^0 P|0 svvꫯڷo2lذr>)ϧS~捡!݀qݻwvvvh5jUהGlÇE"QNJT>zRVXAcgggމ޽۵a8=i@e7Wիo d"޿ iZaaa1 mۖr-3SۺuvΝXp~,@I=zf-Z4lذƖ ͹!/^PT6S LLLzqƍ~m۶ h۶-ŋqqqO>m߾}=GE҇CX!6 &>2& 4zxxdf8nh(104ݹ{~۴4i~ܹsMUn\PПAF8y٥- ppKLMMo޼6T);;Bb1]u(,,tL䆍qGJUiI#2+xzbĈ'r҃`z ~mV(]vQp hx8>dȐӧOS/F9ROecvSc㸁Au6sh\Zhx?+++*E ӌfTAm6ƦO<ٱcpZhfnn^\}ѣryy?ɥ%FII#cʎ437o`7}tm |ڵ={>cFg/@۴ܜlVňn[nU'եKF;S3s2P(nnnԜAz3uAvvչttС{DXOszCbŊ 6jjƍl6ͭgϞڵH$t:W",]O>bxܸq|РAӦMsuuرc~~~LL RwZenѰ\m88brhZ0T* u֮]*6н{N瓹f|,wDRT&5BX\\ѣB6mddرcG*}XGkff1.gFݝj"18::RuzХ64/&uVrrJ~""##cۭ[7t% rJgd2kkk8,xռ<*|ԨQ(}||<<<5v 5fv?...bАFFF#СC~~~[NMM=w\AZlJk{{|YGm%$=zt .n}otl{߀HmY*hU ȩɒ xErrraa! g|2v 0lvClzRHē'O\0sL &JϞ={gW&MW@\޵lM *45pbMtbLMMu떘x֭"/%m6mLMM d@[.ggg'''jՊNۗ#J*9<ȑ#]\\d® lS՗;5rHԛbTtmU.;AnU(*Annn111APo~\\~~~TDUN?xY8!Dŭ 9, 0WϟBݾ{qaqvvvppב#G8/^ Wddd_px .nibq`*kH *L/<9Wl7BL-U3@Ŀ{Й3i -SSI&w۶m$IzxxυyyyC^;1S$ h~vndGkԞ\mx6CjB!gSB`DվMBAf VlQMꞈbj ?աO8WB5YOJzhh\ꂎB=R}, D"RlVn)ڛT*]`A\\JRM>}U2HXP$ߥ$Oalc{LH=11~{.R7|C\?r'OdE@ NQB dIZOG=RA?|Gƈ(u!ξ}vϞ=  ͬA߾}ܹcii٢E /,ʞ_^^7dc@,$=^_jXX&*j;Eln l,k\]]"@ҼiDuU&XDϟ?|H$Rgv~2tBآE-[x{{^py.QNKoj"QPp 8n+JAR'9_rwH$AAAz[\Nf2j IDAT+չ0LsKioR%GH{>|իtgϞر F] Z"T C:~y'S_FW(btO9&@H I(Wan5^Zz˗*J.?|JRr*V (ǏN222t?6OkK.q 5F֚bvhcv8P]u;o"##mllO}M6R!CԲF#!RX,5xp_lYPPеkڴi}ӧOa0Z:۷M$cH}xd~>obbҿ'NPcdd1cyyy...:t488xȐ!#F:u?ػw/`Μ9yyy'Nؾ}Ǐnf/^ܷo_;9"'N8o޼^2Aall6 ]3yyy@P{Ʀ4dyP 0 syo8IHT($ͨJw/^r$qc8**BݻD@@@xxxvv%C=z(׊+nܸaX^-[Rϯ$<Ax{{Vjժk,rCs9e#iAΙp]R*sߺW3ѣg̘Q\\lhhO<p_~姟~ڻwQ 6踨ڟ0+++>>/5k֤I:tlٲNP*#G6lX2vQ(//l J׹s=zT*;t0l0 0L.= ̙3׮]۲ea@@n Lk㕗+I-ZcNV-k ($ *$G(dKUYk$ў&&&\.$IwD*ZqK$2K.׮];sLHHHvvvBBԩS)1,, 'N,Y2k,xùnܸ_ݽ{7ڢn޼y(E+Wtrr?={Pę3gbvYXhѢTWuɓ'7bJڿcތڣ>H z+Q &$d6޿.\LRy-!U(k&|DwV_%!$ę/ӧOYYٯZ38".\m蝋{ 2˖+I ٳ_|Eddd~rȑ^z0{={DGG)ϠAfϞ ~-[pݻwDDpȑ]ڶm{ƍLFBf"=Z,oٲ_~9roܸÆ ۱cJ,+F^.W1 $a|s]~ֵ\&IH@$U$@ +h;|RTR7QZZjaa!tm-gbbRZZJ=p 6mZTTT@@> o߾qƾ}gb=AUlll:t*j @X2ri]=r0`̌-,+ Bb1ĉǏ\|95==}ȑb}:UB8B QVV TXTTT  ,%}oT>իW\~2}zϟ?m<2A Ȱ'I{`M\"* 0 @ $Du|;#6ڭsOgAIH LNl>GUlnh DBT8f`@A*JPh{ ۷oOLLΝ;ON߬#/_4h mC"$5V1,0NfXV >plM)Uūʈ1.|#H,9k𸐌WL-@* RPBEz ڽ{wxxxYYY޽Qebb":&I2))Icr\jԘK۵k?V,@rbRSIqq'Np}׮]ɓ7o޽[.{yyJСC=-DeJLEe0fzT(U`$$HIT$ ,.'$9c! y p o޼AЀ^J4Pk$U*Ujj#NNNڵ[zJرݻwQ9$&&/_-R~zƌѣɓ'l/_DmSs+zO[lX4tLĥK\\\LB=SX۲eɓ'ڵŋ;I/6mڅ j ̆@5IMPOeX (fg}Oðz,MB3p`) MpsX8newgggOO+V3r:رc)))Nd)'oBqTx?~իG٥KT*555#;LPRZ |ֽnݺ={6[TsJI _W H\ XtGPLuEEJ" 2U %R&D"mg``kPH۟8qĉaootfk{{[n|~׮]iӦCQϞ={}dLFbEX5֤r 78NWC@III~~~*JP(JLm۶ }I߾}+**Vf9dG:ŋ7nܨ3Uםf9˥Ar\.U)v<##CPhThJ.: ']gS`j`]\13exzPPPEE}޽{0{۶mS'8}Wsww믫%g̘1qD__Ν;Zb֬'J;1aXAA/ `llb,1ׯbĚN9c$ CU!IACG 8lw6ekXܮ]G~H砪afСC^^^ 5ȨO>Bٳg{{{$m6CѣOO?h޼y׮] ݾ}׮]{]N:uM8XP3foEA.5%c+mJw@78ƈ!x{Zj?H A"n%iF݂`>rrrЯ|>ÇkzdhU"-iLc$Wf׸al6"s*>֭(\f`٪Y/v|Y,(q2ٝ?l7&N*Yfٲe-Zʿ/ܾ}fΜʩ}5ag EHd;VY-_[;l^^{{u2]"Mn2|ap >IBb6j^>4 9ơMo!<955u͚5 .c((k֬vvvVI$#GcooT̙3R%#ǩÇ>;;;_z5;;BoIRD8QfϞDI5k֬3gXXX Uެ25k2J[nԦ;JUjo]J *P g9sBݡCsss2>=Bۓek!f ϼʌ膥FBMX,1bsj |yKmRG} j|98SSm NBr"֭[&L@1 rH$bлX݅F/qp>ӧNHj`-]9lPE#,RZ']gfMLLzqƍ  0 󸸸'Ok׮GyJ?H\Ik׮¶m޾};..nϞ=sY,zcwŚ5kTyTMc**Sz/yezB>y$77޾[n(Y: >쳸6m4BrU* v<<%PTᡡ*V+;vq@唋TF?{L=͍Q`B֡ 6$P yZK z>amڴ9sf^^6~$233a5bMtݑb-^xnBw?~yYfpGJwDkm_YD{G)S4c{ӔE">Q~G88y3Cg `~;P.kii9p@77733Zcǎ߿y󦹹ɓ'p34>EGG[[[/(hϥ~zРAT9ORihhhJJJ``KjjjddD"ﮟ={Ծ}~铳I >%`EWY :u={(ߵkWHHL:Yt{j m6$ j | ݶmj!;vxOuo`f``ڣޕJu늋ܦLROb4.\.ZFӣǍfhѢiӦ_QMx hA~&<~eEn9hK{$$ ,(yfݱЪ!} lJMWXXXZZ FFFbX 4)œwcKQP_Ý첲ПK.544>}Q|Y\\O?W&&$$x{{/]rܹz N6lHKKp Gh._g]vm޼yh3ZGI/Q$uBGF0"*#/]4|po!JfPNZhCbk!K-DL6$I[j߸Vޣ߿rk_ǣbekp„ ǏOMM:88Щ˖-C0 srr*..FohjKp;bGg s!bh%H"H `r@g]mh !|Rs1  ͭ ROhKctܽ{ĉϟG{W@۸8CCΝ;6LTYӧajjz͛7>|x6m8p7Z,%(|ǢǏYX]͒JV0gϞK.H$2 :ؿ'z}v@BBٳ/_\I3k,tFFFN}WMFOԹ7BT*U*ʂqsHa۶mA)U q0!2bhgB!B I B A,\Z$k!,Pɼ *-^%Ji\+U0j.]ݻ'P8I޽W`` !`^y5Ih#ru̞=$pe̙333˗/[ZZR{ hbڴiz9Ҳ]r1 =6cVK 齠]UF$IE7ʛVc^T6$]˗ 1 8qyXX޽{nݺv={-_ŋ:uBzqqq~~5eXV۷o~YGmI|ѭ[iZش屧_%|6)@$I붍,a 4~h[]7 γgڴirJ`J {#`HHu!$ rX ujAH_{ Riٹ IoeeX,eKWNƢP'`V9rngg#~Y#FprrDEEdXquI.:ugϞ:u IG~ RZШC/%CvvA<|U4bCv[ArXa8 $??}ֈwtf ERRDqh E:#IH$rJNK} 7SoN~p7yk6^ Y0dzGO: $I@P>:06!gҤISNq $ɩS_>iҤ_~-ظB 0?u(0$:HOOG$ݻ/^|˗ST߷o:a!Pۍ0 `uwlƈ(%j&3K?XꛪU0?aE%^)++*.KAJѕ J UoVc> B$0J};! DtMR=~jٳgO<9w\uuGH" ba@8>1a"CwܱcGyy9Ѳejeeeo߾m &;p˗/?s4jf J%5U B?$?ꫯ.4CS ƍ[^-\=K @o'[2Hٚ$9066^dG=|pLLL-05klذaѢEرc Ŋ+P^^e /^ܶm[CCǏ6o߾+W0`Ǝhgggll|iPZ] ߱O>7nػwohh۷Qe1eʔ)SZXX,\WmBcah*«WҳuU[" J׮] CiSd2ŋo߾ݳgρ TR>?3F+3\JG)..5jg} V2צ;SÆT] ѣG~~۷ommmccc'Gyׯ_;88T*jwAo_aTFB 1 >hBHRD:Q(_ 4h IDATպA.'H܁ cw7S ޣrd$q#kѢEbbb~[nJ!j˖-Ǐ… 8[XX?65j(S^^@y+K&2*RqdB +|+m㮗.-CORRҞ={n޼IYRt֬YW\ Jq@@A/^ P,;v SkWXXX@˗/oݺ52Iv횃CvN|z V27!ՇX5k!//''3gvU"\x|Խ?x< (CHeHð˗Wk[.jl7w۷:"a!2cGWWXީS8xŋܾ;OOϧO_ϯ[n+W:uQӧccc7~ۮ]NjD;w=zTPL0aΜ9?###%%Aߌ/))ٹs'cǎHwrʵkíO1']oڵXblQQQFF:ʺ~ԩSBO>:tcff aa\.PGr>vT;Rs[I ٥T=yţ._tS %bk[ OF ccc??SNQcTTѣ>|8??yɒ% 1tpyyyN277߶mm6pBe6PadRRASoݻ֭U{{htܭ[暚R7K.Tݘ3gN\\sZZѣ{lРAˆ;v[K./._lԲQzzzQQ333o޼_8qb޼y{9tжmz @ݱcǐa7dȐo`z6b͌$ϥ.QtXZZ"'C<@윜ww'R( B! Q_:t(b8.̡Bdպdwwr<""~N6~>WJgv(gJJJk X\e$w_NFyumH=lanX;B|>JK:u&&&}͆ !W XB.6m߱WNѢAr%ABi2b@322JJJ($~|Ν;m7h`nnNRo%K-55 [ЧD[!dk!|P(|D"166FljR)J?. uO: k׮-))9sLiiiHHH^ 2 J'KQ8 OuJDIIzdf2,6qi!5^cfRk`X .lT(a]֮];i$T( ''-oBڌqƭ]600sBTjjjJ۲e9%%%MSĵtVP=uYX@27COg@ 0'rTB.@yGL5`'(T$BN$ d[8r={DDDlըDtLdrr(K}l8f5YPܯf|ηoNJJ&B ݻaX׮]uFFFQQQ;X{\pbcccGձc;wP,\(iӧ 66չ͆4IOTð OOOS< [2l ;V0  К+@(Y[ k5++ݻw/Z~322 111}fϞ}ڵYfm۶cǎ{ׯ߸q,X_Ν;ӳwӧOGQ;vl^^W_}eddP(F=ٳg3C\߽456T.^[XXD `(r@(,.+ T'O$NNNV bX!C?~<99>22R& 0nJN.#F#Dcǎ]v]nV'TJ:B}h75CcS(Uqccc__ߤׯP1200urrB`.a3o޼+V8::N4(--MPӤW ++SN͜9S ܿ_.iyf=q*иUFBCC;yd.m۶#Ftkv*,,DV2,`:&kC\qwH$ܹ6 Jznj1mg/gZZue87Oo%xzOVիbX _BCCCLJ B{nT* !^^^7n(++C}3f@u=%44믿FNM4-vBqGnFRվFͨTAͥ"]v1m 00,,/,-7L9reP4”)S233U*͛'N7o^ wvawz} )#qcKQP(u Pl333XJET?~x?nڴ^XޱcY2)زeˬYlllT*ݻQr[[Cn:55ܹs'qDyhh?" ϟ?###ӑXCf=j*'''\ٳ%KP5Ci T1mÇs |Pt.ۻ:ݐ"SIP3J= X,d/@Ru, WkLSu[X29^\z!Iyyy/_~m<ڷ`hM^*`ܸ}\gffVP(RH]_tʰo߾E!5]bbb&LDb|IYTPЙEG|V7EݻweeeFՌ ///} rssUneVQQq}"յkWsRv9}tDQAnjٲ9992L]V2?~/OX r7ڵk...)c$Iz}$9>@|m3ejjmQhuČ3*[w<~u۫QwԇWIQ8kziMLLusw97^'wĤ?⪿ tuî)@IZvuJ2DFևe(o75U @$i\C`XUFgggumq W~>}O7N0MU0 300źԩPMh~)exxCL_SNcǎ#t(tP#wmݺ# r3"ʟ={#ٳgUF7$j&35Qk|^7o_5޽{79YܸYvX-'0l}ݛ7oj@JJJAAJҡ#2|Bt?Ξ=;f̘Ǐ#++999T9277G"QX,H$M'cD}\W$e݋g:Ϊo=Arx6mܾ};99Y[D9 x~i5Y$`=ce U~-Ez^T"66;Ո4urBpǎ'N3f a}]||<`ʔ)f͢믿d2T0vعsz{{uٳ/_?X,^n#GZl)BCC,YbiiQ7!C=r֭[q:ڛQOw?k:oqH$ MMMj&7+5)u/3P債訩? wuU%%MJ***/^o,2} O'bLLLv"ʋ円Zrrr211ؽ #dL@٦M .L2jOBnK4.1.QA#mʯގ Nƍki&Lh" """Μ9aXddd~~ݻw8d²2JTaXnn}?>lذ9s_ӦM#""LMM߿iӦÇw:Ta@@ e;\4rrr%&E kVkQӇ{Jf``@U:ʫXz>*xxx ӧԟ2wss[jݞfgg?aIu==۷gΜAr0Nd p#p}O;lٲD"6 bcc]]]LB e˖'O޵kWLLСC؋BP=!5eֆoܸqԨQ[H$Ϟ=菵s,D}4@carvT/5N&L8vH$7n*)++Ǔh 9t8#(~wF_x04E!!![l)F;>qׯ_x<H;j]O;U=*C85`4\CNƢp>য়~ڵkWSS+V ÇtСC+++]v.hmms wӧϝ;w[C\@߾};..N 5MyrUS㤤""+$I8qǏˋG%ڵi&BO/0?رcߊ 333\L&#"..?ӧOիWNJKK/\p…74$Yfva֭\_uddСC"##:vػwשּׁ,C!D tYYYT*5ET'%%p^RjZmmmMӴB[10 AU*{UXr:n`5*ޏ$ͭs]vEinjڥKoo v;C{nڷoind,^ãcǎee]cǎ~~~{r \ #vw'ZkaܹӣG[;;RIRVbqHHȭ[5JA:sΈ49GwXffL&ӧg0iii0F.gggqǏ40L{8ydk... oݺunnnddѣG322`۷NzYfq$$:װTDVSj5C9yyOfԩS~~~vvvla0C,⹹g޷oZæ</===""el|5dg|굚tحFg1n++/À`Xxq윒RQQF...999p{͚5pC*9rl᜽nEajIaV*M}g IDAT񇇇Gfj5roӡ1 흜ܳgOwEQ'OlѢɓ'#"">4P{,\>>>yyy&MZf5È. '|W͛7gG.7oޜ:E)qㆽ}$:i0 # jq_("BV/jb05CuWmL im6)) a&..~cg׊ aP!|JKKe2Zi=^}} =rPv}>@1+zEg~GHNNyyyb\N4#I4MQTeB"4k,--L4aÆ#G޼y3((bhP'dx?8/#WWWDr9OOO l6ۛ6m <{, 0fm_kTJ+P3f(** {YJJ?͛74M7k֬ΎPTo߾vW_}1 Z@@M4|@86])"ꎜ5GS4634ׯ߅  x𡓓clllFFƔ)SGr劓ĉ۶mdɒI&i&..ȑ#?A;vٳgv4eFHhkxo$ʒ4糿u'щ8E-ЌPUׯYkzs1uv#\ږiLfiiimm- akMQM$IXYYYZZd2+++p>|x۷oq;b4I1cÇǏR|evvvf6nUrKKUVݻw:77`52Dk\3[Sk #--ƍ7nL_re,XPg...͛7)SwIT )͜H_s@?N"!!>I.VZJH~)77YfW^۷/ϗeee|>׷nڻw_aÆ-Zܹs3...???##y扉3f;IQFcuabb" ]񉎎n۶-*ѯ)ru!O=L2L'g4]QQaoo/m;lbbbmm]QQqpdeeݼysԨQѣG_r.V-۷o.]"""`ǬY߫W0BllϠAFMرc9gҌ;655k׮>>>0YFܨUWƍ;w&$$OwGQp>>99yŊ111-ڷo===9s߼yLKKKNNvڍ7f̘sM@~]mָzv@}hccγɁ^j߾=24QˀSiVթݺu#XQ#E8qbȐ!Z%999'N/>xG" QJ*x;_.]ګWӧGEEK.$&&* hR55kT*߿??t9Rב FDߔ}ϟ?W*666j(s^N (ԔAP( 3jԨ{|wZ)RYc! ڵy[nq~om@MJJ T넬e˖;w2(.AxX,G+t._K.xdn`9p֐!C9U/BBBp//={=xߟ{{{D"c5Gn}7o7o>rHȑ#\BE\빚4-QŻ655m׮iA0AЋ0 \HFTT԰aÐh߾'J !~+i[3j`w+++ݻ8pȑ#GVVVyyyA$ oذ |T*uww眫ސLS?!!hмys%'<<|ǎw/D"Q^^>~ii---T*۴iSK ViຒYSxjmU4ԩS'L-["-Z4o޼6mڴO>z/^ gぷs!ͳyE:0E\7oޠrɓ-[e}޽===mll`ֆ>. )\{~Z,Ck",oӦŋsssmmm裏._ojj*T۷oHd]xd2ggghYi۶X,-G9:#cEðaN81p@Ĩi _z5$$Q׸gNj 3xW֭.ϕruݔ0oST\vGH$x`pLW@kannι/;T7 i iw8::Џ(IR//⊊ %-o*ߦ@HHHc`\$u@T3DRTTuWvvv(JR)Hs:x`h IR&iÇw͛C.xÇKKKG٩S'XXRR#++nݺ8c AV:tÇcƌ1P,@G(8=P:eTp0P;99zDk`?f&s IٵjۻUVvvvL1dȐ]6\|~vvvii-oJ$;;ېEEGGh"::(\W_-Y-HOO ,--utt4hJv…6mڨ;vE+0 7 :Cӌtt aܸqǎk߾-[̙c`!sàuVӗ.K=GqZSuQZᮮ|>ʚHXy Aff-LLLD"ѓ'O,,,`g"u*ɓ'"ֵkh^~ѣђǏ6mZϞ=5|ۛ]nݺuR7| u<8n8/xXZ/S0l\rڵtX>>>>>'Na1cZl#u#Gj>y䔔۳ܽ{Gbȕ/ zU+h;"?ܻwoMnݎ9bju"sN)_UK+f| I Шb5{=EQ[Zj_9 EEE111?sPPW=MT(EEE0E֍x|xQQf '@a:v옐MIIT dw߿ꚷK&J=?Y*///))quur\P( S(*R!H5kV^^^^^n8Ϝ9̙3p(k… :jժgϞM>0n8?O0?2e |.]׼ܽ{wݺu1c'T8~СC+[xxx8q0{j5g[ߺuáX& +l :<oԨQZ0 \A%Ν;{XgڴiaϪuu+&6mt҅ j/9}֚idVVVBihwqpy $MLL`e0eʔe˖ݻwoܹ)++ ndee7oN4ѣ֭4iҎ;r/nٲҥK8aܹs_~bŊt@NNΗ_~~z FEEA!pڵzmW^>Q0::n\~]ׁˉ>Tjnn~YX~?~_ZZZǎQC0|~ nnn mݺWΜ9V 5Y{{?_}Uu#1 ɡ34MWTTۋb@B>k$ kk늊 M̙rʄ7vQd'O+++0F޽ayN"##_NHH?~=[9sֶe˖yyy;v~:?( CrnWZ Gk$ݛ+ZhG-Z  E+( MO0.]bW1b_xQ'62kO?=~ӧ R{0 h]h/Rd*$I>Yn(gKVWVV|Dn;w^`ɓN5mgg>;իWw gL@.]MAeT~UcJzXB œ=  ѭR@9I+C=L(~7둦iK3d1IH Yj -\BznxyI |ogAfee$)>|t@}MiZVk}ܿ&7Ft&!--mΜ9;w 8uԎ;['{JJ { idd^'{FYYR.Z:&Gоayt?~ s5PW29s 77܆`]?ymiiijjZ^^V)"HDee%EQ_|YXXy<tNh"99YT^pȑ#&Z*[~~~bb" tuu_|||ڵk+77ڵk޽&MDyyyh7CNJaIWILL4WIHHi[nğ&MQf 1 H߿xb`iff޽;vd{ռyx}*8Jٴ")JMM}uYY dI$`^`waL>8r\k%|hiҠȲHĜ9s=:~xDT*92}tH5F3ܹCرc---̍R'sO@߭K è):uw,.&&8s.BׯT(c;+zXN~GܹsKKKvڮ]Og@Bgd_|Iħ?MEm HNa  (gd5yn{1u%I~4ipcC׳k{@ rʴ4777Zq=z͛t_EuQM 5^?/S? ." rh(݅~o`>4_~ֳ`HԶm[N}w!p\z;Kmbb`9EQpYZS'pAA)S 5 ys>aϯ6[f*"i CC޺DZ֪iQc]JYrTQ՞*MgΜJ2d@(Y`8?^p!\Wz-߽{ݻo߾]v*Z @$ ֑?'7;k߮< CьBͨh r@ ;0 =C{#0/_T(5Xi=&Ju,[dj/jHTwdFVCkH?ѣR͛mva///KKKF34CQorMѵiGJ!@fẠ{9ӽϰN݃kf(T&xbZVZӤ_/MZF1([kLMFALc㕗[XXOf͚/_^~0/w:t! 8{5k?Eib YVTmQi M_=ծcpҩv]z$a %] *([1̇xGhqd;1M$Iru5ebx<$rZ۰}@ P0`u=zԺu믿}/^ٷo߲eˊvfj''+VΉܿ~~ay~~ \\\o;wNOӧOGGGϘ1cӧOyۓRg}T*0r9uTDaO01ʕP4 PS 0rHNE8A݇ט&AӌZ#@0Ϟ=SՁ*U"0;ɓ'nW^2eʩ_O94رcG(@9|p###7mmv0LTTԖ-[v߻w1c\bgg7c NU*5[h\QO;wTTT,Y+V:t(\y|q7orn"ZfFT)2  )/G0GfJbO$l;`0FCYgdd@5ݻyl9ޑahkDwRXXhټ%{L۶A ׯ cffF [n+/ѫW-[`;?sϞ={1{{'11q޽ݚ:thRRSL;vXZYkn' @&=y`<[3$ $ G@I4 9 '0sӵI^1 v+ ## JT" Skغ#Z\ۭ۴Fc~~>0nnn0 &>$Z #F3333//о}{̣PNkkk+kkP(oL_~nݺO(?Ͳg3rg"?I`T8]O||AW5V1Y$6m888Ǐ?$ʕ+:t@ H3]𯃃Ë/`9Mϟ?ӧH$zH}tRXBaf̘{uL4cT dH y$;C[=%>c$;UQӀ&(!`5>c1LSx<&&&/rJ||<,INN޴iٳmmmH$ߺu뜜B@Q8j v4 ryxxT* ZvmLL̦M̙fff</??…SN{v(`޽{M8qҥaCE3 ٹs07o^r}>3f6aR5y-_|ӦMF4hз~{ve,3'/LV@ګC\8װ/<;~D440b!D,@U߷` zv* SS())ի-{XMj' WVVA%RSgbիW ôlZ9"SΩP( Zɓ'FGGX~ee%MӦ4Mo`$~k[!$  ݑR^s{Lja0$hFMѕ UaÌEZSH~ 1Ey4EJZ &&&@0^^^@CE"|ƍW^=ɓ'b1*tssn\r31i*!y$ H@Na wZ$0 ` !(P#JdR`0Uϳ-L X|Ͷm BX܈ t=..իqqq;vl,ItamX|3aG C3ۚaMԯ=jct`0N#vUc0M>_QQ_4 ={ٳg# P%ܔ @@i Ѫǖ"n8|Ph ^YY pwQOդ6}BdPPѣGtrrrttllY+ 0 D{ciiiDp8wB;V:91bSФi`tLMMd ) `0 Ѯ;aW5`0 xGf;1 `08pJ`,SBQAYVg?II -]0 CQqGg6SPxF퍈40 klA05wG=9AΝم׮]kݺZ~ehhhx(*)))!!ìY:u4m4RyP33GD"AEEE}jٲe]^LquxQFuVnnСCQFڷo__zuڵW\}:XZZ_xqtKKK9C,Y"J-ZTN {}i0 sgϞ899vU*h+5Uag͛7G\X\FW^-[d|>?_tܹswzqiӦ/ӦM[|y5R\p5]G5 0~ǚ[珶R\v-R3a„/^jՊ]pĉux:#·}Sd֬YBhݺuAAΝ;>}ʮS^'ӧ))))))Gݾ}{[T*Uc{tUe˖۷ow \|iqR#!##֭[ΝZ<&&g*ɓ'7nֲA… k׮ ߻͙3khfh՘}m___Psή]lҽ{wT): n}S䧟~<|*ڌ}R(_5ؼysrrҥKWLB2ڀ#@caatTrܹ0 s9sy慄5ݻ K.{F cw&L[ҢΪքB!0Z0Lii#i:**jԨQ=z<88xʔ)HN]PÇ>}a{7.44t…f8ս5Kmnݾ ӓ&M^zu̘1{޴iEQݻwg͚իW3fc5_E޽{5jTrrrCH_KhL@g@H$D-ZhѢh5vZYY ##㯿PeeeYYYҥKߘ1c6nr |7o>>00nz]vZ匉ٷo_}.]̚5+??_OyYY￯]6$$pΜ9}={#RSSQzdWj6uc"--iأk׮\r֬Y'Os}O>dƍőj}1FGG;vlѢE'NDyS0M'# J/_ 8|PP囙y?8//˗fffwޥ(*11qԨQoMhz̙3ׯ_0~'H$GGG333ݢy`~~i铞~z^OOnݺ .]d2s(֯_קOBK筭7o^PPСC $I]ppPdee%%%;~3֭[o޼{;wB] sS0FNQ2- p̙aÆ]p_?x |&۷oaccp-DҦM}~4M_zu!g=ѳgN:-[,"""66=eMvva%sT*ٳwxyyx ;99yxx,^N/O D?f2Ӷm=&&+(KKKm'Oչy&0W\ݻ7A!{;((ʕ+<}+$пUI`F?4h16+W\fͰaÚ5k&֭[ױcG333___\ݺu|r vLOO_t)I666+VpvvUyṹW˗/ -Ւaʔ)[n ^f -ъ ֮]{! Htرj8p9;9 $5k,[,<<֭[^ =턇ϟ?Ȑ!tia IDAT/ 3finݺdɒ5k,\p۶mǏܹq:thaaٳ-,,T*gΜY׬ cZ/rɒ%-[6lخ]4Zjҥ'N477'N5֬YS\\lff֪U!Cٳd {Yvm5kP(!O>y-,,(ZjL^QV\nݺZek0;uce`۶m;wtttm۶E9bbjjiiim̩C )--uww76A˳8?JD)CEEAUzz {=fCaa=d2)ډ'N8qbXFQQQXXѣG+** y j-: [DhKJZH$i׼6uI...@-=A?mfmjosvWcQW:tݻ7n܀F;I^j'0<STYa(zM.])J5˅BVQW9SSShzKn}4J'4Ǽ-`0 iGْ1 `0/ L$ԲP}c>:kv!&mx0,ǃ 011=> W:|zA4@V+JBY+++a Ji{U**Q}JDȰ2/h.a4o:*D}a `w CӴB!Q lN 7HDN͹r$a Xn10@ -EץT*ٷm KTo"l5:mhJ]]䀗`0HO.-pT H$RVu@3P RMLLL" aSl]J%&ˡbtFRhj)PE$Go N7*Djuee%T<0G$tjH$:EQ; ePTPo#IPD"X" TJ%p/EQHD2pVy؃ }hn(JΗ EQs0D웥o[ 4Q `0C1RcѴ W4k5Ґ- ڟؾN=FFKc,||17"ˢR2Bw}h9n 4ql~ZMdbEd5 $a,<(u8mkQȆǾ΢E\ ޙ;̺* Y壳x8(`!VTTBB!5}ppTF(ZלNM?8;M1)ʌ`0MkK˧)ve*#g/-M@FvT[M݈LJ_HA i^8R#݈f@I\>wAt,Llrbak"f6772!MK@kD$R5' (Y:9ȼc4 SFKRT 5MLL8Yj͍@>Q"$ך&aC&4<8`0 c(ew155ƑmAIg(9>kN1Oo= \EP^^^VV5]"M y>bti?J+++%%%pC&q cA@-\2Q*B5BQ'`MG.vGhQBHu,^ JZʠ255E/sɷ CTed5-p׆썍&$*73EO$5WGh3?;n@DVCPTTuNJ *lWE36h-cf5C#9Q#[:)xGv&Ps^eG!׳\.UTT HRiM09ju]F36]z4H1gg`0 Yc0 ` xt?Pf5]CZWJǙUݮjֆz}N*TR***1777//PZZAKPSl 2!(23fffp>bW ]h ;+.R 355j4H,C)Ul9QUbIy-ɠ{ :Qr.ed&Tyy-k8F>̞a5ȰCu5jS1 SOhϕ.frA=r0-@G"MW5rЪ며c 7odggLf6u(D"aRbFKth25{V5_;V*JpG3a5a)aSknnuGXDbNx(gd@^uj H\e.--EP)DO ۔d@L=tAs {|jӡm:P$ y1R1/lJAfB'IDii)o֬ƦE`0ח߿ ~ h= ]ȸ` g0а"ØL&dfffeeA߮\."+Y396Z b"I3D9g$BeeYŌB$%x׉̙H" (JinnY^YǍbuY\P `m-77ۗeiֶL?9 z$h,L H$(&Єnh”d Qs4jx Akh}0~ Ç{677P*JHuG2Ϻ.9hU g W5H߅hEVVVZZ ;; (p@# vM.i) =u$*;ya礧ۜ?/-(4(,x EJ,VeVV<<5+C\Խh)= VY>32!@$Z*JbLfRRu^g({wY;(HB0؈ļ'9)RM;oy&j1FO+"Ho ,[\`پߝgYA@P|y瞙{埣>aBweelٲѣH1A`hh@  \H|8㹖x' 겲2\($JG[; ?<[ψhE;6uԪ*ǃ4M9zh8 lܸvԁ/A^QQz8Flvǎ'E{ ;[WӷnF=H2pf2jUUvV6jMꂳ'p],(t,EFa)B[:klPWI$|KKCaBzmtUҘ5"~^M(uSb7w !ƍ;ӻ|yqugys9fޓf !z{{Lu=O.?)hӈBџW]kWY}}aH(\ؤI]]";xG [b~שTe MDgR?yyI / GYUU%x6t]]sεu[AsҶb8D.j0nYH= 6E+ڻc/v:pv eD===\;^bEyyy.Cχjr+\.W>OR}}}N3JIuS۫ÎcWm 8*t#EVzZն )al6  L {7ڢeYFx4춍(+\ΚT9;*)Ig9t,^6wnGOv8o;Wn-R!t6ME"!D8FnO~|β#cw߽Q0UKÔgBEh%^yappa 7쫪%,J:BFHlFȐFylemϞMz%%%BP($ `HG18Dv —LRP2z[yV"v,ڻc-4M$rfx[#1uk2 #FѶm۶̝;4X, ~?܊bFJ&PAr9 R<|~Ν{E{[Q.pvo\J.>kVק>˟P [nK>Wjʢy=--~C>_~˗BlR~sWoXlYg?[\hE{Ν[RRBANSQhrX0 n(7o>^]p^\X:^o,d2@Q}ieee.A3L,۽{wh'{If&Gd)BWx]A8k˲ BCA&eה2{4t<iULrg|@-YkZ^d m4袮XLi<2iF7*1>4M^"_~.p~[C~9tFaEa6U, $ .z1۶E~Ԇ շܲe* V,'!@ƁbqUfm9؉D`R)eee|ҷ0!22C&mxъ/tbK)un7Pc& O>=0Ul&r]],'nxEP,0ABoӋ.^@`f~O:zYY٨QڵkOzD>w;|D{HJ&bMLHɄI!D>G]omþ(| 6Hk :+׹hE{O~?1CJT*:fQӘc1bD x…  %cos%r<SUb`0zK`*x<_~y+rJhi+jE۷)(W7tSǪ{W㏌xxsIk Bɱo5wϞRKKD3gf~`0TMU٥kK: g(7ci)SLヘ6gglK_u׉vhE+{ΝiVP?M~VDRsy.<@_cǾcX^^(JYYaLQ@X.L"/+U>ي@d(Ŷl ç5fh{ &F-Ӷ-rw$c7T]Ha6ad:p@SSlChCGe(/ &LCW˥ۗ/I`~-kHFG5U!DVJml6n n_W$+\>cF8ik ]=qb+$bb Ňg˾:=+r;}Xj|/Tds"S9|͂Z^ /%%'G/_6f̀8UU;*c0v GQ!D4aI*6 !mukjjt˷ ! Bo!,ZOv8HVpD5!^C KhJ_4ٌ6⊇~D;y饗:NzD @Xɪr%I===H@*![(#H;mڴ]ۼ42{l 52s'E Xɗ#!B{B477O7{HI!6C|]xFK#FrR?>mZ\]t9h?>_P2re&''Zsns碩JCC_G_kk㱘0aG.2 J))|ing⯻Se9,K3Kv-EOiO`ՑTN_o(UTC;n{!MT :3) <4\.fYx@~z?d%ACjÍjw||ъv6ۜ9s*++^/ykfT />` YcrX&_Я… ׯ_B4s\ti^x '$ h4SZZveYN+f3#b0(z"Z>VԬvKׯso6qbu}d$ٚ5~5kfe1%Xc72wn֭e,_~TZ*֨#:69v1CZֽh6m+W67.m.m.߮ oj`UW-8p ^z}-ZъYfUTTh'f)xN$I؉Ft]K84!UQ'pa_~9 @D4 jh4Mp U":K+{a?ˠZ:|CXeg Vf($ p85}z.J?CCN9-9ߏtY"d[FSk.^PC+Dn7*4aQ?iяbnHTJH<=;yyy:kݷpsL7B,^裵6/Zb{h$)," ]伣i.GP2ǧRv734TvzPLdMfKAOg K,{Bf$[r#)2,ས:.jaw8x!E| IDATH8ȹ7ТE9rC4M p̈́Zk `0ݭ(J8v8C%pHe"|+)PEEE[[kgߑ`_~Բ 5!'21T!ë &D!D" 0g6&^TQYYYRR3NDɱ#{nnt㍛(GeOjܯ<_~[!B-p(,u^/GBb.wlΝ:GD<,/Ov;μfe.>v[XPmu|*%Oc ?7_y|˖!]ypH5&n[eswSVvMPڧC~8x !"bj(x (COQNj=h3g$<M8ʲMoRN;fjuRᢑ?޺tݟ[> y,`)1JV->5~_5qѓPR2r9)MǎfI"~@tx=*~O~{kZ]=u]ڶtڴv1xB 8zy!C [㉂$B yOJYTPԃxgvA4 N' g/gڑzH>VZ(B9RQK.#pjerZ6Xss=+NL:<sDrDJMus6'9{*(+gj'~ъVf͚xY(aNRR)uDW$; [F\.zҥK߶\pZɲdd+[0L*2<@LG M:Fbh4FL(-An2r%Y$iE{+3$]q,]8P撨2u+RT5iDI;p}}}I,n,++=zeAe:نPGgx^,ZTbn'7aBi:Hmmlw^U *ΐ_\F^~r6BH_6 cޞmyQ=J%NGNݠ JнՃ{?P5bΘcm+ċsGImn ;Ri69clwuu!ѫfaBTVV6nmK&)X*Z* +}`$'2<?Fa|֤9|>8& o$+\QE穰g!$t#dw &a"y|}>vkھ.'ujl5aGr2XIU|. BD"8ņ`o`NHhas<ԩmOOyzsfIO']>7B9iwOi^H=VG=t(UsdbĬ="_@ /x ~VvFpȄkIOxWVnIz&9 ]29UlЩib[R,Ru!dC!-Zr:!Q 6YhK`,e !8NUU=֜u1e2G7A]. J@Fr(rvV$(ʜ9s6mt:oN^+jE噌cɒ|Li'&6 q%>+:˶`*:&M:"hjb[,K+魑ܖ.O(<{oӦu !zzAE+G9)9 I&jLҁ!HAEQ(=0~BOjo@Y1;Sɫg" iOtj4/'"D"#Um>.*h& /m:X\TxFcIX !:;;/hmmC|nmmرc555BQ4aF?SXh]=۷i`rpenjI)<"%jjU*j6D");ND^x-w~oW^~4c Mpb[m_ȃ߻^ΗLSRáqgxk^X6D7K6hBk6=QMNH&SEC󔂱|Lhg͞= NB/M QXj#OPD 8eY ׭[7LW1`"0-OM+a}ΐBvR\.bEA%}7_,% zHh{#*bSx@ q &iKHx-mKPTЀ; ։2k6b!رc0P^Ytp )'`ikMxrt]Igr[嶨+}_ARՂ:0cyb *\(haOjF^puaxׯ"P8l|7gy~`?d+*{_?vٲmĜlvr9,B( tOU8 soА/ Mhg!- V{s 8Њ"Moz:fh̔߇1=cxlcZq"p\,iZ)2KrRu=HȲi!H18 3™j7n<7h ;beێ暷V:箜3u^wf-ɦF⭐vˑb֦Mk{֭^_?.κ7wzSӲBtha[lY(4 QXo޼YURzXr9 Ƒ$ 2 9 QcN7Rr RUd2 X ZF\r9cf2TVMB .Sz )b$/saB >K; ahaxķ5-1H(lS-9IRucc,HV:Zd{1bJT,ԅ- 0PREqQFprܸI=Zφ %k\C+liN„+rșixa6y\Nt9'_W?ǡ9UaƤwCmѣ{t9Nm29^$ڸqw׼ƨ.-Xʘ@- zȅV|iڼ6-o_?R $I&&vNF4 [&IR{tуv(WFII % bŠz;;;cYYY'gxX-z-qhh,XP^^N1rj<(7.Հu _t+XŽQeAJ]M0N _MhDLNsʔ)m"(waݐԱXU%LAĘB@UUUkk(?G>Us-o9SLٳgϩnYQ>Si J|3H\Wɴ҅>=:ܳ'9˓55wȑ--۷v$}d-[+cxmDr'y+伦&*]]Dfmm?#>[50zɤd4-;cFʕkhE+ڐtJx2,M ᇫg 29ɲ)ƈP(SY[jr4_v-kPT!K-?TΛOjZF.g$sUՐASxmdhQëNhk Q 6}ܻ톑&YڵrvO)S~(N&[XaF*+M?!R}>iVzǏui!+8TsAF+Lܩz<BHRib!A^χɇSd믗gYTmc̋vY;8ڬs,2NX#Mؑ\OnZPDf}ALlkkCxu*:\48N!5jժ#/iSOU7vy!D&# !R"$a^oGƗ.a!Gnכz!Bg?j"_2kPIBt:Jy^ʄ :ٽ;tbɥ"Ʌn6cpo2fΜ7iHƓcJ!D.C13G{,9W{zZ4K/m|W_hQR]*H8w \F&#ϟwE]˗!PmuVW.GJo"M<=_i< lO#7Xp /Ģ>[dIii&!y{c=3X M~ ^+PA5g_,M C @迅U(V+cƌAk:5MrHJL#8+T!FXJx !pt:8^;u]42 ,/F" EYlY4ERvV`Ǣ o:~U5<8q@ӎ;[#^ϯ~5{zYYCڿ`AeKM6^}TM:Mz̥VX4NPNYom (9mZ4>bD=׏3MYӲ=l;Em=W\c\xaOlj %[淏<2N!~Mu)SzLmows#?IvhEʕ++112KLjE_~ aXT*;|dX9##I%`S; zC\@YQp8*$4P{6o>#ktb)7?gǧMBdT|vW(]ww}'vxKcIe?kʲ*;^8`-$NA% (絟G"S۵H$5~JZ8cY.e͚·"$sG)$>ֳA1iic[G=j*!Deef„޽{Kom<٬?_esΜ}رIY T_т>I|kӇ;?K89rpPzcB;VW}-LdNږ,Yrt,DAZ3I͎\e$.)$Ii^B,PlQզI#V^Xiڒ%K^~c]{&C2f<ͦ-( ;$R)\Gf%&XS@ƍ̙Å ě׫ *ao%/=IǜhXp>0D16pIJ,چb !h9CxSOooo{{"x~1!,*UM8Tu2͛H?yڴH~<D|_>oks<ԄÁ˦)d҇?p{$IV'=i5}[)s7H11RL&Ӥ5%l+Hg{f=l6%DB1YJKuIjjG}t֕W>{dJ1WIk=018)*0k BMQOn\qGY~@ovջ$ksύj*yu-/N\y øg|}% ˇcE)~"hkkK ;Vm BZɉo0Q*Zmxt]5D",]HV\.v;H.JUUUiHl6<eY-Q5&3dzފa.jLyA"oC[9ɕHeP$$I&} Ϩ6m1c\!Mc$!cêMf̘cǎTYQ>{'*6lj~ 'Oeeɏ|#z{>kVyI \JM}q&޾pɹs:αB,]ZB1{ƒ^K0)^'ye:;gBsN5{ő=6yǎ2n\{2ܺg'%ITU%ݕ)!ԆІ fԝfϿqٖ-.1|h[p!ʨRrʤ*7}}}^7 #Jr-Ib~AFvO%łpgp1$GAs:ֱX zAi9zWt:[dV7,J=ȃ H@${WWW766ڵ }O0#'t: MCD ),ӎ "($SqeS οH\pTժޑS!eSb,#4Oca,KahUMPUU]w7cI89Сm-E!)t^t:8bXcTܓT)VÑ#kDӹ`Ak_r{!YG3 (/HhTގJ`5t;c=كz4\M %^sH:xlŊt:L&#FȲlVX%әdx*J&iG0S&IR$WNG!2_V0rJ-PBjH"*JrԈ6!O0Cק3iJd8#Oz1 B0,P0Tf ~טyM%IR|\.v #.+ b¨J`sעE^}STۻȍ [14IZ0UV![+!M"=Th RBP)JZ%Iokԉ^%T}ے)IBd Z_;jZ%Xz`(cco})551: _DPdȻNM8yo;{Q1#y} CZznM`0DŽY/gw] MҟVj+)GU !6%+[Y?kU !naߌ/4jxԸd+l%E9ꮚr4K[|[Ȧ|niiJZy_߸?zG$?}w\yysKXqwa.Oj2GϾn<`5hHM\EL襤M-E+ڱlժUT ࣪ꨗ3foV-N3N&H$Bc&o"&H$FEC(6NT9XA\Y\'t:ל PZRr؊q0-M A <:5@> v+**:::4iL4)~xa\t8ۭiڜ9s r;u!Y[H7( ?(q!D:vSo-|奯-[o\Euuʕ{$I؜;UVElSW\x͛jDkpAS2_yiUtǎ4ys8<n7yoy# Ø:w^ NV0Y9+WG`䕇ܣ[g_~B+VykN[`E$J"\.700@!!$.!D(-v|&SUt:ȧ :'rHdE++]]]|`j%e$A#&y`;QQF >1^4Whx@jf2Z[#p[zBpp8pZONQ+T\c?cBIˌ{?>c1cbc]Q2Jui]NmӦE+{V^jww7ϙV(4c ׈%1(@eY8JUUb=dĤS`$Λ7t>_E;Y;~NH.Jk_Rq>]™p^kY9cV&+?sb5O~:w6 #TdsܨQ / | C|8hPu 4GL3gr>C#cfbMJ6m¢ I{IV!19޾N?O#iMH?({1omC0?7!\¹,[Wn{3oaB;)BYោQ]ׇEH4!yJD> qL&Cn$dӅ8K,P EƱhǰիW{<C, +, WeJofΜ b GQbI!|BÒL&aJ*6#| ZNu+1d:U6Le$[`cfKR?S\Z1GYg=V;0ԧU|-ZN}i#تmm4ReF4;YJ9Rzƕ&JS}Y^t7;vgώctIIwTc)<ݪëv,{kj9mK!Lt#vUUhi6::6Zw潟y3K"BA+Zr}>XL4X!yJpakD( siVqLDD]ApV|(G X` ,"wl|IXVRR$ZRR]uщ8£hat0 ʿH5e#,]ɤM['"|180 ϟ?ƍ'@t .JxzTXB1H/`y.NҩT Qzzz01Qd+vaeBx^H$e o\f,r!]$~ψ{Jܟ$C4yQ²'vv|!1Q]EU=2Hip Q)%3o|HПli.|[ U ITmmp V\z6+5+FW=.5J}G# B0 ޑC/߼&Ӻu^ݛ3a=<=9}rl2,;9HS!'v~ o[d?_ukٮF.x; hɤJJJsd) iul "Ͱ76'Z* H$< gPbE !VHKndՖ$) ?傼 y^\ַ:f{8v*C@D:.^2~ ^LbI^yTVFLȲ,d2hd2iFx k!yOb.vA=C +Yhgq~e+ 92RH5"0#:&GI!DOO«! -Dz 8u$!7G۫b" ?&Wn8'RrKD" 2>撨\ʏ"aib:TD2 璥iCgjc{j$$?=N/n!t/d #u%SO޵KK4%MO֯&tHg$1ekMiŧhRf҄ Vt:[L N? !Ħtnyr?qb4܍%[L_z`iinb#}"͟ɤ%Iyx&/FAh JC%zf+***TUd2h|tȣEg䏧&YMb&j{r9_s4CP 0šgMc8h`Ims)4UU1 \M4VBj8mX6Hi q&:H`Pi#q`t6HM^xuUh8vb.k=U_]ɱ^~;v,Sǩ;gu̢=;%m/+;Z8'dP&-L9)P0?l顥i%r|1P(gSE+ڙ6Ӫ bjiHHSXk6MIHGhrH둋')"%K"3_an]tbEV=`G*-ȁ#ǵ_AR$ sRJP$ WԠimmQ"nu8[ pd(B??pE;9{y̶<'RswĔ6T>6vJ VkItu&e-,`G2B{iYBLyyG{@6pnALd2+HCB0m`tE`R1Ycgs$#bN_.+ZWL:LSԧ"fVGa=o(ik~\NG]5w=-[^og!JK|lcp%tipk\#L?ktG K.9|mjLDF1|>_8O300Tx!\S4E >t9PR5^i]%A-g!}l-ZdYohD"QYYD~1[D,Yd*3z*:`dPcJ S''t7^*d^=h{` 1`nz멀82hR:u(J@]_eeel:I*;[beLe[ 8#Ȉ uC=šBb1Q.#fأiڈ#w( u1}izZY6?ᤝi9mH'|"z ; :8f4 V"\x 7re(AA]S I!z C6 iǎ.7>2hpHr.KU󽽞^#H KTrZץCtxGt\?R)_='@b,sVmm;.SQbe!IR@^)(؟4MT|+GӺ}B{IV&0ۧ!;6fq #jSjw2Qê#E7gAQ?9[m9jDO %JX8>>'Ir)#Xq9bmtҰq-qx+, \Ȳd=%,3W;npav͊Yd]]hEYyX;ƶGlzfu8;?#΋_]_?g?y[ԵH1W*ުip8+?fd̓u.b j@~p8́#QhUgnK!ΏDR $\.H$0!GލxVD,;rf#d+MPqh;P1J3v!oY4-kxg0#,7j(bp mc=N!D! f20E;{lyGRfwTDQ*GS<[H7N7P&p0fUUUmmSQW9csύSUcȨǓʲ>\uDiu!HC) I`,!ik hZ& #i !q+qde2!g~PqVVKYt2>mڸ=%II9z8QWWx%I #!(t:5MbDy+-%y&Ђ,#<>|xɒ%/)~N=1IiH8 k)a;RjzlR W'_.apnLth/4ͭ[k_܄<83:gVuP I +d6wEzC2jkw u۷7ieP(HMCʧW +6ژCѨf1]h-U}׍M7 a =6:9!E.49r+c3cw{v?WbL<]MBL!Ea=AMAu8,y; /!q?ѫDHF0n1S*iCKzH;.Bw9[V"Ji}}}uuudE,b9"IHd$5,))šjx#T/*rΒ{;*,7=Nqx<$˰e1MBeС!'Y9nMg2̜@M015HX#u  JZB"*~H%m~?cǎE'҅EAU.Q? B킐"HEEy{CƢh?h ʠr\-%c x< ,8O`ޙ.8o&U( pܒR3qWAR<8uvv3LP(E@jV6BVo4-S^އZ}O=)'Л2 ]$N҈BB\w%OSwϚRjн vNߣVjQZ]w_m?e~XmcHG?_d2m:';5qB^ч+WF~nwuZpXy/5۔_ε@cQD IDAT:RH}L&)cEExDTq:aeTXxȿ5r<@cч4n$ y[P!?@2t_nkk9r$=`8Lc2:>SH=3KR[ʇ6B{O@UP,DxS23,\P$:KqO>#?6аtnd2*K.X\.np-LM&rC*W.R/"HލT5%&r DS(C ÀTgª*!\sH'!Xj650{naHW,Gg<ʒK GƆũ@ok$b-}bkKҵϽUٳsECs(p ,\'i/zc;I18d4sɖ:ƿ Mm]d%ߋjժp8l&<|͒Ƴd0pjx5#Aҝr(ʈ#0Q۰#׊ĸ=H(_" DIL&H$LI-.nݻ*CD=XV2NCa͛MӘMZ2 ,|$577'IǥvH3 #2Bm5FukPU":<(JZ- 6lhhh|&aE0X"w ϕkKhjH$200G>0Qҙ@\rPa {Н.Dz,0i6w\o,>|UپTd2hmw8}ojc7rEĊ, -;uҩ^sq9#<ؔ/yٲk= ?N[޲W_q] 6'6gvd7yhЌow.I&˝VY6l9 @jN]رH& )ta&[xỺ|_|p:6p-C7t6dBٛ+⏮뼕vh{3=촑qy;$GpKzkΛo8묗a"m X}"Oy?~#L D8~|I=!cYѶm.QMJtG&k|-8rYCr&_58y+B+o'9g}-G~( EBST! $Nwuu@cc ȴD5>=`wsx8\"7FVk;ӒXbeVTbe<3ZɃ@P 'fP&b1? b H-8b1n1.Bp, h8%﨔z7yy!,_bk;4 Ȣ}L;R:G~enR%eF|3 J˸|laBO !`!@.Pq pvuu]q j RX1 T/|r_/ȧz` R |vrE# R +'D",k\vmzs}CFs)Ǎllwm=5RO 裇 }7R+|2wgs{r&kiձ\.kEzN:|pCO'vA,V}rZUg$L2yx<*ȭH$>ck! B*N(jA\1 W*G@Rl.2^ґ>W)p'0qԊ͐nRﺋʥU( (|laS$KfVf -.?I$HD4-2 G˶dYExUsd?7xh4bC4ݓ&sH> ('fO@ls8@?i큦D|o?~MN9匁3(xY}5GzgD~hB{퟿}oվ%uɼW_;gۘ:oV*̙[N?}aIh*YyDbF+mM޸NoM#ۍ0Eߛ"w$5ֈJ5Λ6oY|C-}qy/?ji^Cw"3w{N)FDlGOvJ1A»,P+A%Q_&W& Ay]sxðݴ:dVFWNfΜk֬ihh`Fɴ)ا.2`q00`ÀuDK p{93 X,Rh4",= 2+\K|g/<H!Ar J=X5e9gbT.)" ;w{9zU ]F2#& X0sd+ TB L&ݨRT*K7&r֯\qŁD¼O6#W^~eZKNe(m` 6y5a!'N_H$"-?'/Z/d4N͖e]r%˓k{{lq.S].QP2G?'f&M?Ewu諯\sͼ`СP#DK E"d2IDTuet:Hq0vE`G$}>3RFV^崥B?Xvʪ =ziK.mnnFd( .9r$3/.*@IEB]yY[I*=P80]4k> v:>@@^s$#YeYZCC d2<`_P,{ZY4/eqy1MS%IIdܹ\"⧠>XIM;a>>Zzo34~;mji)>1ccKct>G$#r#ukk?%y"wz5~|E_6PU^5v`nWxk>PS7/?r3gù.| 4|+V*'PhiPFQJ%Lv&…Y7%I  q!v^D%\&9IӁx)R@ 5hMӌD"tV&G0<%[H$`Nʾ+n; @2chPG&q#BW`HxhdIgg'9*^Z˖1 M-G>rY=V|>^OwK>0y yhu?Aͥ9K* Ղ`8\m \˲7RjٲF":E >|GT; u$hRO\N$3gNww… `_ʎehaGFOa5.G"g|#gg!ێì#1DTSS\Dt%X/3Xk-ˏ1|WV'rFQ3/ag, B}ښl*6e{ŋV(T0ͭIȉRN=Tjw彣Z|;DĵPW ԝl'P"sliMW|º?~뿹G*\{;Lݲ%r]ƌ\yְ8lpW4lyh 5(X+[B!kBCcxrr 0`AUف ~IMZ[[ybx.'Jt:8L.,y<5I8+G;J<.3c8{3%jvP(N-b\.g3ثVGN,ZN:JYpa糮vɹ:pEp&{A/tzS6&ݻ~}'2yrʿ z}G<20WxH,$d5̻e-򧣏nѧJio]/U2k֬T*rHB6Y+Jb,NkN6ݝ832C@モsCj_RIێUm I- Zo %zPr V91&fU2`eC$._GԄ2d.Cp\H1455va{Ve2xGMP$xpHZo$pA wZ(noX0dh*b嵌ʕ6+4fLMzW[r!C%e^i]]4'iܑp&2erzu=±Cs-Va[H3 C6tAXx6+R7 6l6eJo\‚[&޲ſ%bEx!!$fc3CVxВ,ƚ~'{h>==PxLOb6^"^&'pti_d&$~6@Dr|ba°Ppl[:rl6 L&:T*H$J`U\D rf{P u o!R ˀg'VF4!tFvAFn?'̪f[d"0't!ir#6XLpAbXMMM*b+s/65M^b[g'O<2$xG.O1D4oR"G'< Iu9}] r9NE/CʼnDSFQ俆voo̙=޺HӶ5c?^8{7KDe]ஃO,YI&ɶDیhL~Ri+*IV%4jks٬_ٺWď HlTSˁaҠ! z#vhC+IJ<8_7;_7,=l2o}v%3{k^", }oB0002ҵ©rTXhP)|0.\D@Veh>iIe3$H/!#Oź ".QN{ (,bS.ؤZsƾf&GII+: ~nqi](#¨t8c<gM\dPpx>(k>ipKtwwi'pB67oއUDv"ol"82 "D]y4iƎH˵ BTu9~Vkk'?YFTRA/LNg=+lե@JaVA`6-峳v|dV:mV]'b[5cc˧:N/Od'E1 8c˝sa.uٺȺ3;{˾wbljJ~^:cݺM7=IAHd)LyRUdG^G:vpdMsx׭_ұX̵A8Ƅl'L -bU5aܻk2-)b cIȑ ;ɹZGMXvb,#s=cRd2#t#G,Ie?ʢ\c&t:Z\P[>Qg<ӋfL'LaHXYu֕' X\5M+G}]*;#Czqݿ;Z22u[_}QW_Uˏ/տ$uss&!PKKVA{LL'5>wumͫo⊪*F̙C;cMM 0ن{{;c>@BD"lC TreZE)mKpJJ5Wa-Ѫp\<#+S9֭.mQK6|)>\fTLTMX a I%4DkF555uuuY[<#d+QtRŅɩƁYpM@7DR'U9xE\ s5[M, k/BՅ۲tP|>i E4gm];&;đ|>_$1 # ĵ5%TxM\)W5 /@~GXr anp9NSf> ;bD?$'=T4ð|@++ hҡߘ.TPTV;_yQE 4Gռv7FQÇ/xmM{)﯐hê& R0͛Np:cDu-,&~f( Q<\6#?#Z8Lk_~fXmGD}}}| w~̨H$j5oUGD7޸zF]ׯu}Y+OO:ͷ-kJױxt/ۍRwU՚ hX)Ypḟ衵/7XW6I7l89srVCƶq=0'JPCnQyXl.'+Jgμ!y]#:WL_SSL>,}y  b㌁Z3 )V;fڙe\Wp1|pߙN#los]oz KC<β,D@yG$/IODG) ؒ"ށ=S)w\0db,Lbn X9`cPqglHA/taq=y*)'$҄څu8ʓ7Lͩ;(lQ-YQA9ֵZ|n:n. МF\#gr86\%r9T/UJq`ZݙPH:eh40/w >r Fq Ţxq㾪XUM$xGb&&Mr"(Gwj|iTϧ-߻9yU|2sL㏏?u69p( 1mUfL@zz;aT^IwD3EU%|cf̻h6CWˆ 0 4^VP(Į0lk <{5DA=Vȕlrsu]0l%p!H 1lGym5MT*7n<.8$wynamߏ$"rK>S9jqҩir:pF#QݲKh6hOv@YRqʶv͓.\SS3mڴw}CCK+P$pz?0b;KŽ V橓Oq-pa|="J7F'pMbڒ(ˇiS;F3a1"B [u7>n&2=~xُiښ Bt\B0euuM7̺aSlSM'=?ihh4dTզ5" ?i "Olˎor%^s˟^u5D4}z㏏[N[0L?ٱ89}1pp,k[ 0F{3mRɅеլȡ)ܩdj"#PLҊd;r 9S%@ $ Mkc/y#z ];z@iMMM$ gBYOM;HNÎ3-F04H &\%MB!xctcOڢ=[z@.  6[3;h50W5׻'Ca쿲ys38iRVHK际VCS츲UJQ3hys{ !lg޼:W^SK˓'QggtU*RTX#{{3 $=2TP+3DbX4eu$aq,R*8݈i {K7MjN3^Ab pI$eviZ]FX JrͣWJT*0& ](e̙ fe]Y d[${#"A~ܲ^K柀2X[(|͓_G)8X];* ",ӧD:k00izZ h`.+S(\D(ژwD٬?bxR|NENl0QNxPhKnUdͱռg/*"OLL[q#g#m:|VO4$sҏ~|I_Q98:;CJU/X0O\k=7nԉpÈY,\.t#/r$.ӹ&[#VPƖˆae2|4[$ItH" C(!\#)m`s\.Mq9``>iӦq!A؁j r7?~<3L 0jTJ bwb5?p0NvBy]Ejo-g|.oi.J˗;6q#&+TRsM>~8ؒ( W!,h \P Z ֜юX䊩(B8fliOi7N>i|2@#x׷2c+1|A; keBP }ʧRL*&vjTGG0hP<`mlwgcɁObDpjsNwFR<[U(YNVXSڣMjoRaҁggg>&^A%"nVV*D"z]y^C??jLDWDD\.Ls˅&L̚y}7oOL!|7p*ۍ}#BNF荧 l l)P'SWZ.CPW056W+ŚMYRHHC^lǹSى%@R^DKOp\憎,˪c,h|vT,`bCd$@2Va~! )a=+>"tӃD- PUphdX|%x8-`#٥\͵`GW. + ,R#FH$l @6p4ws(@g lKsv߶DsͩG#-XpVJA7b:? U+ Hn@F#aUq7dHxoذ|UQqVY;YTI/=:W^YXD4Ql4kZ+=>ͷ&+ef[ͯ=z}5ճ}^{,. ,:9ORm8$œNj_vok̚# G=wΤ7eih]#z9rd93r3-'0g BEt:7?}GMlsM>kfsmWbT*F:蠚 ]ǚ2dgn WeJ5+e (KB1rFRpąG%ddAi#H \.s`*oͮ%G.RJi kcTA5Mc40v>3 e9D\-/MJ-t>t/DsT@[ANju'"1sX(g=\(ݺ$cuK>ڿ-3p|k[WW5P%Ԑi8~m9(R ": ɸg|Ъߨo=~cǾ#RzL&HNZwW\Da쌏9l?_vuEG,'OYY?Шc'7 IDATϷQPCeEL[[nm++@DJcޱ~˯5*QޗF#ܡv-nYWd)Z\_ OL8QQ3a4 v;*pN9()<=,t .yGPɼ#Pvc5Г*B:D1immyHTDZj4J7K߱BF:n;Xvh `Vٓɫ/qrSn0mmmÇ緓f8Xsp5|>_P`9#o89VjY~YƩJȰݒ-}%Ʊb/n#\{,zCN=\vt/rj.\A fjjiOrvb-.soORrf$zI=H⑧"Os뉨msP€oUJz)gD5|9Rl[m1u]VU9r!SL%^__\s7ohcdkc[[[6qEӴɡŗe6VSkmʮU*K.1 U9Π={zzhڵlL&w(Lf6IV#kzRlL;e[Ͽy^/_۴7 Bȯ0b3cV2e*d޽οnIhIޯ4u]w~k~LEH6+yHrNM-"L0q?\5yXŎCP=P9`t{%I$ `r?d(H-sL#zm,Z3Jp,2!y4Ci"URx/LpUy˲WF ґcHbI=& 4[2XLksjHCEׄ!nN94r9sL,6­FFV*$UC˖y{o|Gߥ~!ᆦHYHmM~ړhP`f%`.\/e-sSsh#N}a(Rw'Mþ>z (E?O#v뻡w~^7A"2iGY'&NMOº/і-o55~@`3A$"S3+Z%`bVpAx]詧Q?*URWWgF$.p [UtN4a[)uV\7хh]AN$8c9=oȉKR)H)񜄛.*ߝא'lq#6u:viQr|" -95: |vporY>c@ f2R//a t;N _cX,R:ܙTta}48-f묽#8yr;=Lw?/~k9=Wԋ`S-לLDl^ŻgʕLƍ7.s9yȑ'^\K_Sӟ>5?vc_T*uwwsI&QMM5OOJMp$קRDD>#"߬kR14C>Eʺu~m"ڲeKmm-LKMu7U7,T**%r]<>"*q.>}鬺[]9#Q6)9%s1QŨ4m /N?}k=DGD(.Ba0|X1˃;lLr.7hUهiZ(:$6Ny_#<4MD dw1Tu+MhX).1)GWtA:trɮ2@d14/|ᥗ^Ǝ$0p; $EeX,&EQ+r.ɞ4}Fި^T.7i|^"c k|2.9y,2R.{X# 3 ;* KM(΀]Ot l"'=u^d|6M?(n\'ٜ#-V5>̍SRXnij϶D"ӻg27qѕJ/ /rѮs￿n:n箮.":v4Li5m+ÉnyVcTw{8.Z^zիW@Fr-;/m&[(y-Ж*{20 bѧr[Y5n\̙?U-ЩhfLOoU W)UѷwLL&KԔ+ $)@Ds{#skc p]yGE P>}.sAM[*o:db&')T†jP,3.V  Fxбj"k:J9H ,5"6k3Oq;r&/CKdc,K&eS^MXbtP$K<$hp™3gZ6>3!iH͎%WCI4Z2oѢ7㎵{̻:aٲfҕ̑GڮēOߴ)ysTҟy'FQ `JF*U *3flWԔPū. ?`=R=+ER&7dI7"*͏_T,OğPku\6~,i @ +Ţ/f .OdYreúu5O}ɒ%G> BNN-555|E=g)leYOJ_?灓lMlG5g` n۝U| F +a;~<DmRBX;Qa"wt͋8^+\NYCsJ(+h޼=t$SS#G^r3uu[66:Srxa=6> ~҉( ?97~|][sGj:2I?wR4{ݽf"6m"L&Yv@|v;WvjzjŌT/2o_"+Wf#-.5 ^d>sJDh;8rT `g`iذb(&u$ 'ykTW^kG8OJv!rY_ڻjQƎ~]<:DsR X3{)д'a. U@O J*WD@\^46OWz\Cƕ+<;YsH! DPC Jo-Q8 D  Mivd0.*o8rI KAL=e(G3x#Ik"K81>8=[>5k[+۶ՖS% lbG%6aQ*Ǜ ZP')m&kԝ08v:\u՚ḥ6 oJ^Vsx 6$ >c[O.%"geZ 3WϘl \b E=zoZe@EFXE}0Nj'eV?lMTw[&ɻ] 4W^y|IVU[yjjj^3/3j^˲VDxqch Hc- ؉4 ]כ.E0x5kR6$6mray#>=sDM…矯yx_'jY*/Ӵ:-'!"BN=7ʹD ,d!ٓ]Ptbhx} \Oߢ*J-Z4aZf/ʁcX=V&r^.h(8@y !#< ]?$X: @ DJPkrE0Ho*~\4G%ȉPs-.& Psp-R&_\5g} [HY.gv] >jb ?܇R7*OdΜLw督ފuVg<3@4M# hSu]54\?EH܌2V|i{SjV [ _nWOMJzLl*7y=oÆ4_}c'K;7護F̟?|Ŋ…5K&@0h)]W0B*e1 ba/%bqkUW;MDiHDNe>NqHtcPrBI&e^^5T)3oOsj%cl C^_n8~Qd'ٺ]9E2%vozaP(3&Pӻ>.IgC*lG9Tv<]CД)S\_BvR˾詆ѕL >C\Ol~&4XerJ{Kyͣ7裩ʟvZzҤ<9o W^I-Y42 4;M$n [Cpc֭Kw4:!Fz|i<%~J4cr1˱g/O>߻*;yV/G}fg į4Ky6x$xG4;DZ~S(l"A̅NS.*AIꭆD)0Udv]&Cq!6MJ=;ޖ7\wV;z;UG\.%\CjQԠ"D؊iLB.nr4#x??vŊ`{R( UBz_/t]f4ZT/~q?Ls" W`9.=-yo3m:A"ZYXi ;)XlH͵qF"zgx≮vԄqfg0,/P@kG(ԸHa3wܧ~z\T*EDuuuÆ ##F0Tإ9J J665v>oZf}46fF/%|C=׾ù\ "MU.76G,ucf&'H%ŎZlc@$W`@·W}d2.-9-\Q-gnkkb䙦d"I9=^|"ܠյH b<}̫%{*gYDEWaJ$!Y}:n]]De{5A"gF!Sg jU. 2 d%XvLI65  íʎ#ǎۛ]prl L$,iţmQ~ZVU3AR;vRO?+R.*eQ_V1öL_TLs+6AG>0T]dz{CHEa>ڼ:֬=:x7#O[a 6l6mmmDq ڞy"Ffڙb4Dr[#5ܹskjj":戃D"Cp@74qa/X"4 }骫޺٫V%_ld4 T*--i^;mn'KhYcGiF+C$2. >Ѩ\xdU8_0aW.eY===X,%pK5 gJh05wX@ >;5윋 ;dOMx)J M-.(O)+||>b-BrVB^0ޕ*ϾQ`0feP!4@_jEvrnjL(Ox%y\I$ 9VG"= 7tڵ~TT*Q}}1X,~H{QD6z/x`D4#?#a ]#Bc&WsZlKSӧQw[~%?K_<{؏~tgϟ6mޯaU 3A4l/|cIM4C>gU5(ÎBܺm*es P81cP՝q 1KP"á&Ty79. 0荅U~?N}H$'<Ջ]N9QAϲd\^/: ,]~ 1`tB%q4lf&=e5ݏl KUe謽EiOd.䍺ӕR$Jw1D(o7>!4zt J4eTrp(k"6?7g>o\n>5>P,#|n޼6l@D>,gv:Cv,~P+X]5. Ν;yR4nܸz"J&\1NKZD+MY0?#c_ly񤮓}G~N8_}' j gÎ3ds5IF\z'DB] M%ń>F(DVςn QH+x;VĪ XNQCɐMKKɞ*@vo0$)|y p$LЅP%LR^Xvh l>loPkΆrZXi8^ hU7"kn F5 aa;p8l 7 rJl*t;!r2g +;<8r(˅B)dEc׎pbݐ;*{] >Qkp'RZxfDT- TO]ͮ|vN@-P,oy%~uoq?r;|gi(7L-N=|/LV`ǒyr 566rv"jkkch4:~,'Ҫazh :˽kD4|~ &rUC!.i`0nqP…?ٰǠvyy?DD0nˀ\$@D5IAJ Mi&%0㈡JJ8뽌K~]%>B|p8ɸEN}p1vs)󊯔M)WO4k4AU%e熪[zKo#2eiz5UI5U2?V_sҟ{leۻu"z?{vUG%82VS6D@$ ===bQ&c\nuJOxpO|`p:Aj"ՊD$Y4A| EW/ v}mo%L^4(N)T:)]g\JR<6Z[ &2oq7d/r&;%XCZ.[y::x:7G "NE[lJ 0,iG7E\q^.*awe9d ~7uM'&Ob* IDATyɤ.ܭn%+:E JzP(xpTnQ{[ZZhÆ F/s|%DE*Pؙ[ L͛7oD GMD555`MVNL F;&e] .~#~1O/"gJh7vgL.{, HY7%kNBѵ$H ޑy\.ͅ>aPLDL$6" 5F1۟vF]ɼVeXJH"b 8SN]feY~_*x!#* ]ДmkfxAPp09# rKK%5:hٖ#u=1"!}"Ԥ`hHms פe!J)t]g=*G6MZkvP3f+?#݄H4L4r&9^ݪq7d_H3ΥB^*GT L&9 ]0X,2b "LYT1-ܿeoFJo?'2~|mBDUC W'V_>r' ? TF5`8>$Gٴio)XJ=QgRo C]r'ŋ%Hڙ62:WHc1u\wuտk]"jeY'%wg,Zr'-˂,ۥI?kRŃV*PXDyU/V՞}ƜsN@B`@q6Wwmkm׫~V/j\}Zv!b  <籪?ԏV!DNUk^{%N&d\.Th>+Q (92YYI͆C XCKh3Q:Y \oYVd"r[3龅elMp\oL9Dey] ~1˅B!)pӋHr23QiV*bh6?I1̊@jRy}FNȌFy2_c=3hGhYu3_yV^X3|Ǥ\ 5g*cp8 u^$T{Hpim1&V"5._j>e^ Vn[|YSZI-XU+PjY^Tr=z4 YXTzRQs\7;_ɑZm۶mDsNٳ\AP|4!{yNj0B=5Y͛7M2ib! ś"u*wd@cՐ`2JA|s 9Pwkxu' )-.R yw$+%gPȷl 4YjxY] M!`52 4;:; a/f}وs3_4۰jz)Bg=z-䶉./^2Ss, =p'>l6v;9yMLHI˗W*Y'FNn5.c8>r ]Pڶ}NkR΃ +QAT\$_Ŧ)V`^%T[y`&ÓCIhsmc;EB!,wgmaůtArB -O$\rGH /1SSSVE80ŪXr8ێgkK+9 LJߕ4;. ^8xe^Ju}ףGf}2qJNQjK #Dr]̆5M;tWիQ1kXܲ1d.j۶m?<UpvBԞwi°01`aoG4&Բ<==GGz_}בÉ[KNp{]~+W8V@c-KGjk$45=P=(},NDZkLf֬Y$_*#Kg%7%3?[ c;{IhFΊ0pd"v-Pm db۶w֜I&I@GB:VʷrwzŢ{SƇ 'OÍ;y8d@Fmy)K-.ׄUj R+AJ}JӯVpE`s[D5'GPqLiz.cr">&ϒ0Jos:11:kS^fуٚ>5GosAˑciio/|O&7wv\RX `íeDݣ|1Oj21DJRdtttX~L>o&+QhZk v쿑,k׮]D2Y`CJS̏ܢ}-r |N$S' !%? |<;Ɂh~[voڴ{,8Jm읐8_|qeSƒA[*D"iLFGGΝKDh 9T !`1 :xG8jdc͵2]7 Vڻw/9@Z1=ʶm clBam RT)9GBɕ"aC|{_f`^Z4$H;(sR6Z(k&%kV4@`ժU* H0@͌yc-[bcˌZ{lQ8\;OvLGWߐIǩ״Z?&GF>"ɟ}Dq9g$_32#0S.b>BX,yT;L]F%)((# ,@E6 4(xXf@4M@.5HHҜ(2zԤ\\aW=YEʵG~<.N&$Wd ˔%ζSa`9ά`KPRg`)5Ͼ{;OwH$܉!3gm;=IMe<'P(PyϚ]Cx*Q.[N.qD󊈊"FaQ57/~`w>ݛ{^LGi i"Ħʚ"ȪM6oy+EjkFh_DN/aHs#KPKӉxr(=^>WuӕFw~¨&F+g((k* ?'M&l76ӇRrc#4iydi|;"WÁzו+Iw) PmGD\5FR|M`YV P$7KCrc? /p~,YBju/_&2{;>344DD?<[֦ԪjHKFA,dC+ 3r@C`yiKK P2YuX LN& jiIYŤwp8299IDB%N@hɶW=+$yuFΤ+Y$*Vr9ٳA8ks~!l w# vݖ0a~? Ǜ h NI2k;'B!ڎJӟl,ԃm۲Rps#yj Vi˪Sh9,8&4p2Tht&8ϚIJ6Sy̌\ED]$|;Dtu3 g o'hR:z{{EO֎DՕo{{N3~xj|<Wmm%K&֭xr=#3drr2N0:+1=BX{bGv^ Vd:ޔ5'ܨB,UFYaM^ЕCy HK%3y 2š"d,:~&Lh/v_2}ΩMB,mfSZ?0Bx|``Gs\tq% $* ??#Dt}eiTT8ĺPBly4MLLpdMx=3%H$f]" pf&E~f ..Y2NU9ݤ:; dY&Dw`Xsd{6vx^.&vtt;vv|y_7fV zqǻ+9?BDtNBPkgOVg0bVlE|>ZEx9]lKN22+kdZEȳQOԋ/B'ΐNH{}y/n|VW*eiO<^0gN~g U[ŬaZ̳9@Rsd;w.:itCZ3sMdC$xGC[PƔwٕ]v3k4>)u _Ox$e˖ٳH,p uH}3lȎ*Er\!')씸hb =Q9h2@a^dywPw>0#g@23dxt(^ۉb  bl^hс ԅ! *CvWdJc/֥Jj宎mcx`Cy܁###}{C=j(#Oew" \ۉNb|!W6+]TNܕ'g}A # @ᰮ`ior٬0vD>&|LI.$< U)Rz?:tјd|>tttc{{;]l%HØ}o Wlk];1:mُ?޷ys-9)ĮPSS/>bz>X 1nI A~vF8б LNI9G0rǏ'& ' ;k|>rqer/r1\ uFC[b/bo,K5π_oﭚx %Uj?F#yAΤE+sDlGXsv^315)R|>wܳ0췿} \Nԧ֎n}eGGi732#'ݻwA#w GX%*+eAn[/|m{GI$D"ϊODfe9j_rSD)0 %!lJ%v&w9 wH_o $xG@,f>$0 ?#Y:I:6ԀD-2]Q֌UGmWl$bBӹ$RDdzmzUP/)E.ǓK}T.4r<>>gf@= jiiU/QW{?hWWk_۹jU :Dž _ʳwݵ'?9{mk+Mwъei|rgIlZBf/J^j511pUBz6 i6SW yNT;vuS![L&paUsJx[6"d2LaV4yKOH$ޘv卯wZAD d-YUz j3mrb U{$ϡ6ޱX B?]JX{{Sp߾w|ȚFK9vEsdSp844b3~[aDbᔃD7ՅPCN:glIP`Mĝ澁uI;lM{eYT*Ns##ـE»_jR\'b A2$J|Jf֦ GI%2" $Gc <`kPL@=pYU 5*˓Ņo}C IDATd١!^Y1ZZ^Yn]KK Om7Bdf܊=4S,V%KVrHE/0 +&ȞDZMCZIJ 5".Eh>ysbb"yr~(gB|_Mh۶p,kmm- ccc2R95 W_}u:fsn#~w,T??_xˌ5$#Ʒ| `ã34y普Oo}np'H֟%T8`p>p)X>!Yrl˟@Wmd@T vӦ^={M{<n^ջ32#V%?|bx iBʹRsd;x<'˃i;mی"$`&NeeV D Z&3,>Gq\TU$#Mcձǫ ̕BO3ݛj [Ѹ/,bcw2$ pFD}CRye˖uwwX,&Ya9h𝷽m<)םi_@}ÉxBK*,|K"04>>>88!>x}e9WVIRt`tsl>g-C5BĐ#l=9.@Ĭd&\7N$/%j2 VBqPf SUCx.o]|A;x/ \.7ztww}k_r9f q7S3'_xI HWKjg9)ƹ* f>ӯj4M[\^ d w˭;l4`Dses;MSA+Kׯ?v} e8>Znbb ̙~NH.h$w 'CY+- Q3"i5ƴPz;"+qЃi{]xy#*NqߓLbGi yۭ5,A6@Ð2)\Ǝ5IJ@ PVM#Vi;bk {DEIFYf;g(s`GQL@80n I(suǑLL& sDRA'׭[H$8촄>tLum۾ꪫ}0zbG,o75^"rGwGc,888866&-1dɪ:̃؀ڎ'?YR.ZZ*6XQ1J Ş|+(HDBv$:r+1>(O|,&`Kqᕌ 8 Ƴ- m?#3);mK{{NR*=wW  l6VPЃQq Hp$cL#+TjJ46w F]{= 1+{ZJT/Q5P4_>%Κ/nug?{ %'"/Zuz( q1*3#W EO ET %=9 >-ŵk2 /u(* B/ P''1YBlWaȏ\P ObR$yGY: -lnRT`*&#oN^/2}e9n=Oݕ+}t] D"I^?=BD++)| ?2yV \Hkiy Yk(}/'W]HJDbS6D.̬Z5gOO~/SHaFf +LrM:thɒ%gs\x'Ww8E'0tbS(h2ך*3@Ղ _X  x$h5s lrZB!;q@@âM)`%K;ۍNN6M ]7D4fC@vuu F!}'ms|*/|_x=&AHnRoYI锤|c&<88#y\.BؐO?=PڵM3\RkU#hO D2ltvT[?bF?E)ɷ"esP v&"l$T"꡻-~daZE?Cxer .ϖJ7ԼOD8gU]edx2Y2i;x<iϗd.hݱ(X#e!|ް,-+TzaҞxM; d:]% CM>90 .V{PWW@ "hBKt]8TWxFήT*f%!MrE1M/NRHQjˣtF"X%|j-D<)?3 #QN )sBr#gF.t:E8!x_n̶큁;#H0IFn^ Bv̅6pM_ޙyi(_!0!Bhn2`  ZI*bzaߚyva5+g1};ڎDc9@1=:k\ Qsە_rǦE) E#ζ|BkkkXfB[*3Ќ׏:lL3eFg}c ǣšeiTS>MFt?BD.;Ou}O?F޿Wi^Bo9+{mSo'y5˅}v%*^7|>h_|cOz:3׮=pȌѥ^q P*JTd2tJKI]tEmmmѶ!IDŎ@Q-V< fr2jH.47!ؔ6 c䨓oM>b^l?aj| oȲ޻<pX#zdb*,kǎnð2B2X$RŘk!9 hO?9M'YuJɩ50*6 ^%cLὫ ,3)?u!A-[6Y)J{*OXr9~jĴbtzpfduq񪉾I;` 4}9ܡG=o`]قK&tX ;˒лdNhvM/sϒ_K/} "||m"`qt]ͦS) pDDDd'YFS K诌~rmX3rZe3bD"e5 XfǾ/!%g` 29)yM)Y9b ntଳ:E6݊u92 L2fEL9?&/5FޒSFNZ30OAH$6\.3e .ޘ2>:bY_ՖSNT 9׭{>xt4Q}?ڈ(8察]k=1wޮ,XP>%\?ŋ_@v\?~:~uSMXtV3j53,Zfi瞛W.7m[OϻmȌ1eժUgf9 ΍Z'Bu\.F+J>gE6?W^ɞ1ՖxQ9IRHQnĕ;J4eT( 8Br)5}.(7`1s@ B!fIu]7%8CRQ:WVR&Gc*,lXhG|vܻD#2Y9-7]Wiܫ G|sB (LP4vT9$$3y* o 6zi4D cczЇvC[%Ƴ)zp+ /۷KJW.\vl!L~ZbRy99lڅ {M% *\{ Z)˹\9{>k/ "2|K7kǻ'&l6N8{w6$@X,vrRt;{&Y"1[Xayv8>3V پsJCW3 ap̱Xel) <2fI,fF(OLa8{KoG*V 'r* ˣɎt:VdYTj޼yl:ɫxwX$QxG gĎ ѥ@|< I!{j6l@ea9'Dԣ!uǿg{^/pN» .EҢCVDmAClQ bO Ο6DH[r|od91zvUI-yu,RŶmV ּ*1HJɖLo={իv$tDΞDse7mNÚ#>0-)/_o/xN]J'R+p< |I8f݇KkgDZoߍ[ &6NUY.hy)Qa\X0o12~曟ۿYrzkkgjt4s֍<b#o` )uKBXpSjϼ$,,uT֒ `E'1L$vDz如ftK{{{(1莅7"7/>3@ F[[[#?cg ͵1[6,X ͐Ӆ!xc NR$2#úqfG3I b=]]] ,@+dj3gg8Q3CĞ@rSPIKEQ<9vMӶSf`FfB#vd o7yF˲4ðeFgT AD>Rjx8W]1;!ݷ֮}iwk~E'gW&= ֦ ~[-T1Prܕ9ĉyA-jaFFFZZZH[>WB:Q0 YӪ[,gpq!$?S9tPp讹wٮ?CӚuwl9wC[ƍ^|uh(S'"rHL A`LMpˆ& )Ǽc(ⶳpSbH)U("8؛E9}-=\B(V gŋϚ5+JKtQxR\PkT%(~`ȍy< 4h&l'r9 [eqH]fAnLA<7-|(=#J+ I({90d$YjhGjv%@>&gdGW2p^rxGH)/4ჩ<^HJbcQ^5G[͏*rX ؾgl,<͊I{-nA={ڈh͚'XXyR)r屮RX/Ѳ\0kC=ȕXM*+ld2)%6jU]?+a"?,.NQ=PL% frri(_MIym /D/`#8 "Ft` Xlf)`ġv^P"{ȉWnmAJd5Ӊ7tÜECLJ%$eV6͏enutA^bɰ0Ab9~P>'vmWeAl'$e)˚眔Ju1{1t]pJr⅟;CyiDt!qQTjl6Lz'7_ $ ^l]>V;w\oW9T|M=9A%Ѱfkk3-׏_W]ckFܹsX;neyOsȞ=]p32#aBμ]Y]dIwwwkkk2dg2M@9 ')|LaCf3cJ˭)քk= [MB0ci`0 A0?1e^g/"T  MxQmsrٞ5Z ocw4=(@V Q|4M5k֜9s$ ι̼JґM |>C}MFps|Y8Pk;z5G_`|mR8rLѕJ,'G߬ՌbџL 4',*6=<6mc?Dn4L◿\OD_\$RS)gIDWeZ]ZM$hDTհ:ߩ^ԯP@T#"vOlH8ƌw71Wo{4esAexGv$IY6$u]~w-p5M|˖i/_ 8i" J^W}KY3%IPX|WE.hgZC( 94 ^LRqni|>xww7 X. C {-(cHVPt2Mj֙gmc+k`yG>H&ZzuGG8)՚5PaL`Ӽyʷ Zŷ\ rRdٽwjj٨ eL)Csi8M>[0 >y! "Y*M+b"K] rVւTfϞֆ.G9^sSO< OT* ܓ Y74`op9Q9e S`X Qݺs 1n3?P(J"$xCDނEK % 6?p6 \"`.WV\%iwGvl2]/obG[u$dľ'?Y}xxLZ𠥰gđ0@fqnxT,;s#Bkk=k"MBg$~;Zceq o@ZK~É ;=rU%BCmVy7#&*Lq~aƸzEx3/rhb"vdcGe #х8 B$9]BF!<(p5x~z6|W~|٥Kǽ)Jؾ7o<Ӝ0#3\ ë~@V !G5v?  @ʉp8&''GGG>T*ٶL&#*ȌO -iӔ3>>>99ICW#t:*Q(m1\f͋SE![ԑwdȕ|-DG>c^| Zo| j "ڵkR)`mO_x!12>>v>T*^+OԒ2It94wċJ3i eVLGQuͬux'6>aWb3A KyJM$T*[3s WRrښ2 n (iaE]LAw`б9! zdNGNt&R_jZ-NR6fBZv}UX})sgX/^VXioElzfRaݕ0rqǞfYaU% U r@VUXǔ),pk6f$m<xL|QV9}6%qT»nx_}ז-}wƧ>İ (ƃ{Y^LOr5r *]pxaȨҶ[ a`$b( ;b NkZTz~1gzx?Z~HoQ4VL3cA" N,*n`9&ʆ$M -<\ܻ>6_vr,4H%O_|SO-H41}|/};\yܑWrmS'$sp4ZY.{h˙7^߬(N8SO͛??smNrzÙaw%c(Y8VTb)YncH(Ijf"9M^}լ&m'vAٓmnG`08šsh?Íα-=2qDc7x3H$I6RMĎ UU+c,Rl:<<˦0mFc-t`lՑgjsrZmmmjiiimm P)&x4?*+LO.8 `1˘JR 蘕J%UUMӘ̝vVِj5qd28;>>ek̨ohFg} 2w護>iٟuxK/=~Wx`w \s+}SOvKKqѢ"ͳ7'jێUW޷Ƕ.m,?x8`-wW&/nKyx 7SK90n4|~---|p+ b+ЮH(9 9dԺ[[Mnh6h4snK8tZm' 11ЂD4"0cccm' la#9,i 3U]7L !kr+(s0 OIUe2d+|).F͋w{c>.ysO,V[-g<4d$D^1C̳:ڋy޶m`!rz5z#ۉ$g0(bxGGGM3;* n_N""G A8[g)oܸ0oƤ2V͞==Y0Hsk6D@GGrq0њ 4l^`b3:DX2Ȑ} ¹e{C8rݢC–LgNV#%,{GsM"]謽|^7>>> o^};r+"Ir q2%9sS玍ž7G"%KF̛}X7kW{j _b']yqg+e<NRD(vNL9[4}|Gs(H40] ";[?7C_ϷO Bpp6 r}}}drhh2 : 獃 i Q:l𫢑!a)70yMhР{@ D.\<moߝ7X)lFR;7gO޽.%] I*͉-GPS˦k+ |IOYś6 pR0Fg^%K55$b-N&L)LȌ&ٱcG:9'DLF?P3]p'4݁]4ǃ$NLSxad:޲clj7 ~1'BD~3 G"Az!WրDN$"JȨW9y7Kl`uQtiNNN.\-cY,3E Y@/XB|a;i#DrxS!FeEqLo^wfrgɌcTbj5Lw暇~[2RD?}SO-$Ç^|- Bz]7M.|ӛ]rn[QggayȰ/kY?OzsMD>WbE_[a8rbQxnF9Y$8|f>(HGow~r=bGfgu zkpKRBbl;w%eKuLP.!氡4-nƋzF#A0uwW#z.CB8 H U0_oܵkA4Zy۷_y>vT 7ݷ/=?E_ұHV*-@J1ŊL&Ä4$̎z1cg%\{n9m8t.`'CI9Wtfi[@#a@;$ L/Iy`Q/;"Tʔ (gM(lۆe'r9Mm۵Zc:8$j (NrfΆ\/#3IoHzccP(5ࣴ-yKQ,kZ29P(p y&8_W "-Z,$P.EӜQ$*JB[ˑp3M3Q9ǻ,Kl6HDժo|<_^+uvEO9#^ mۮV_F Fblh͚a£##;Zv( 9T{eM MpbwPMSL&C.;ZD߃|6jJdLNnH"`@=(?q Xnp|Zf L.d꒜ʉfEy'mJ%8\ "yܗ 6?>'/h(kB\m?W3yI3Kxa&&&XќL&Q'F tgt5\3k֬;wM[Z622#GޖɈrb|#1pEzY"`5P(h3N.h699ɽ݋q_KmaWKJLAY +?[ x| z-ݻwi.NMH`J'b;t%Q\3X h&u("g>Gl2wޛUiY;ll!aIB  ^'.+5723:"KDeհ%}=9OSɄ@yԩsk{ԩS##VjsVJ$00=Pm"BF7dh\I-*ܿaC%ٓ0moPpEdSDQ%igLƒD ul8cA~Z8p8C̠5P C$aj6'F6.{J J_Ft܍#Y<ѣUmYEx l6 ֆN\CȒ[a)u]=\z߹sSO= ld61i6d/W2$ojIfO'mF ^z)W&L卐awahvxmEv|W#/~cg&c'(g5L̟稯}m%B`GFF #Q[[k Ϊ6IޑA)*"47aE(JVH$~t]/~_4 # `ᣄ2VTl6z8?)Ɂ_|1)Ss-3g3< (Qa×б:zX/022?k %VLUU%""" ӥK',[UG6~G4O?n8q۶֛o>c_'5LWLihX( ƹ*xkySK Mݛ=vwSw-h\t:a"D`|駟|I"*Xyh4 .߾(̀/ 6rH@,[uE²=zaõӡf숱bE[N+#͛I^INE[Ozq 5ܣ۾Ɵ̫˗ zs%"ߏ|g&Y!nҢ=a18RuYM=/<c]R2a"\.EȕJB;P#PٖMwt*\>oll,ɘoSP S]fAw)R(iv ܧ"QN'bܪB%mGƮ}Q6TK&Okn!a.ݤP";:r(3v#_g)BHFӸQ*5kDў9t;u'wwwS,toJzO:D\P%ǿa##zn/ިiVҼ jRRt/|ɚ/w+m|wKzx{~w\\?UUd/v[,zcD4gƷ_;|˕W>#`.xT*Jb6v5, 1dd((&[3ÒThsJi (naŦppu;lsj+gq-I{ "|8}SغhvFyGgkdzvF2$ĎMb;3T pFz5wxx[vtRSċ,hF˲f GFǩCXܲϱ8~|CW%2|sӝ#bg{mdX}W^IjorhǤXKQK/]TkwΜDXfA+KRWUk`__Qޯ)ۢ=Ca4C4$ l|iu}kժiT*Z  vދDĀY%wW$u<;iG'aUU`*a>mqa+z=TlAr,iYtPU#XTz2u)nH?a ygѹh˟R$$!5w#-jYaMSDj#F %^dhpmh9y)'L[2jϲc:B CQjchD 0XrEDb\.avlRl*,=7EߐfF8\4)Z}KrY N5 uu{۾;">:_nMKm"hȋ1c(VvfiZcx 2߾Zw* 'ǮCC{9gܹܫz7򑏬Zm͚Cr`I2 b1@JZx7lgC %^/,Xs|ElPx]סeh  t6k#5*d,a>u,[zcB?|`5;eBe5CPR"|%o6J%lNE^m fsaB_p45_&U;lbUd:A<*R'F}^/ Ԩ,+F"%"abhLnsCChv$N }O;Ԩb?Dkƃ}P#"00FFF@LFh4걳yL#>eiۃelz;չd7rGћn4͹sBPQrðگT*y\Zf j T^պg2Uz-OcRu]GY.qceQ$v`GGn@5R#W$F炖NGWF,#htTo_dYg U:[eO&SLRfw؁^D;#Сi`15[@MXbX b4ո%[(7Z ޑd`y8"+L*r2vv>\Oww8":#&[zG?zɟo}Ϻ'^#fXK(bL9cd׎΅;|7ilPSlFUibl6 HSFѨa; / |pZ|IDGQ*׵u;"|C[Ϸdhk'ja.B]HJuub7ځt;_Olْ_BD 7zV1'Fl.-jH@q~Sva2 ꫯuQD4YΝ׊RC|}c9'"QѾ) ëC ,HbP&~jsp.L}I+?0'},H9N7Hb,bv44MXJ0EF! iGv2! bGff3;waҘa=X,Z* l37T88qbWWW< LTYu(=$p%SQ/5|b|3g\b 2;:.J(RW`D Ӷ2 ^<p^ﯟxbJڒv$u ^{\sv>]OA2xB[hoojt4relڴaydzY7mm`Uŧ~ۘ۟1ϧQ.988RKD-mې\cci$m*ʞB͠)b̐ח-lgIIu ,;L*Bs(%ExsS{ d_I'с̻&ɮ#E D(N=E3qΑ!eYǰ}e 4QtS)7K^ k{wVB@Or@m)#ۙ3O4ٳ;WuG$f#0f.N DMJ;Z^W.|җ;cÆ>_ٓҥӦMv|uOaC@4Jkڨ4\+D]R zZqtHOOVR=?iŢghv?{3zVzO}'?+&]덿"N0yslٲ"N8x<g8*x;7 T*ܹs„ 5T%κ\.ve+x*;:d2BYɬ'$7}% ?yŴEմ4_C lH(>ԴJR((y51se}J9vbGz0pgn4&aoaZQv)麝\CvI%8ZF+އ1"RDnޑO`-#ɪ1MhD>F GR;꺾 ) ;ʣ`E7AlF] 堳FO3 "NʔJrY ]]ea$ 4'T&8!&L(_tŋgy9sB'hy; R먚iO1^UE hx"T7=;'cy_;VёL&Ѷ}J:OKj6$;F dVܧcwE Vŷ%rDӓnۖczipH7y-OD.O]0E:|ec]:_߭nGD];y5_FX~hDoT*3d2)[{ۄ.9+~␼M9`}5;M* 22 鑑7䘗Pj`,ءo}+;S0ǦFʟ-HDSS+.]؆ :K'r' Q6s$R*] qդ׶X-Ӌӯ}5 ,WhE^jN66aBv͚Op+~?c0fܳA| h ݶsBяW# BD\.)U iS40m|1M 0N[K6_b{vKDB`;#f 0v3,D|>J W&1qu;ϵR3VmEIvj2r%įƸą###L::D6@ #H8ܭp)-_<xK_:g+;|!f*U.7@=VUM=8?n=ض#DDuRMu$whPj;--F3GϷK.?If^_/~K /~s8n4ilӦ3L;㌃VNoL݁=¸  |`08:::<<+A-qoa w>f2V ۼ4lad0$AF"c>SVDIOl H%*< .QC6ߌY-˂{ @yT ,lJpřs%fxZ3"~ k9DZT*544D,*!#:R;"j(ȾdmnP@yNDi&Kn»G*JOrEJiqKA a(|>lWJRUuӦ};,d`_]NL y5 fx׏;.( uuj"/Yy۞?/ͻ o T/_A_Xֈ!}ZEQwl! -1bFfV8]r9֭N TΆ>xlFK7Ht-YY?ioIV#m?;|~/mĎ]]RO9gNb/tb>NHM^ Ct>} BRHaeVHdfUUST Or 9ѹt:kߪdP`Ɣ/,[eo!`@M#($UX7;31vDH8NRJXT\BF6jGDؙ~0 B`#fN2e&Cs&IJ:}9 TAl:A:qLӛ6mR,7D˲UEJUX2W=ۈw~xsf;2AKj5iy?kLVBtPUۯ{S>R78w7$o3z#?_wtgY&G]y#)'e=o ezky#FH^(iӾ!}Sف.]$sx=.BtwW֭[osA*PzHeCH= ngŶ!dw3b ,[T}g<=[RO?2{*q/H]*5r ;_d8v|Lv&Fw;TE># ؖhժa(x;L>6)|oQiѷ"ϛot1Iq]dчFDxL<̹swQ4 WV ɡΫ!ka:Bbq<~4MZ-~ooo"t4WZ)H䥗^RYϒST13L0 ";u˝^֎5b&wD4t<h ~xYwrd0 #0 (Kb! Ld)>2lu%@xwIE䭖7pH`:I>;=#&;;fT}*WU+/5^׬T>gLzjT;|ܿsavo[MqkWs)ktΝ?$3g)%܆-ց@V!bSGD- ,Md1Ys"Pl&B1DR#>:xfX;#x=kfL[}:rG}~ZD"x|ܸq۷ovQҍ|:wWWWkkk$a3G5Q0"5DdرiQeS#4c%]Wl|~ddpT yX ===MM3L>hX,ô񹴊 Z ]wV'' ;:&fy/:HɂqFRL&SoL=Lvnd2 hO4M[OQ2+kpKJiAq{iNf q(ΘPDSkWRuzJ]S Q jFq{M8f{c&HL8w|ߵp`!_5ǿOn+z|^۶-ۛw|Kjivl=8kJz׀rZF=/],zN8鷯:!'Noyo/өOaYx"Z$O8ysWLxG^tH-q@\єcdRVndoPbrVn5J$_3fAw, >)dhll 'Aa W Ob?SNbVSQzcG.$FP!sOGGǪUFB%&MFHdЈ;54D"CH(;:ФM ^],H䦓12@[9~x< ŶtdN|>Q*sܫ*g Px).Ds|կ< d_aGE9&"%h4>Mȯl6jU " HEXtD5~vD(T?昴ޘ?u2~%ſΟ;t.S#֭-[:Ǐ7+ް!Օ ^٣ke`aPp2%L+~w!@[m5:+=#)/Uo".2E}*,ԪS.9nܸx<0ęT,bb08/g gya1]2BnҴiɵT>^cyq{>: iJ%L8>Ƙ!|qll "_~ 7RԠlztt/}\^F/Lj."gX4\;g`0ȩ~mVU:!r9/scőF'o ݺe;h"g֬cs>awۛٸ1~uEQ))Eh9FcQ3ʍc6%t\"M4?Vͥ=J$|Z"0*5kX ey?W\a xrs mNDP@ߚ5h^W7XotÈ>UTS(\ pE^C8ܹ;oA+ވDG c&) hitsI)rb1 d? vj R^LӜ E-t 6f8UUU4drG}ڵk^/.7`^T1X.aŔ\D9U;U4cGw)ezlooM!\F5n8k.?00pWz'>7{: XY{o_ut=vfiǔV43_tpez_Tjy޼G}{''m l/~?[.́#]+%Ṯh%[&:S9=y.r-+[yd~0X-9sܵ*j-`0<_ 708腍ؘ2DyWU] hiiV###---C3 n"lPE"Sf2ֱ1fHY|>_.9R (_r]5E+] dnCA=:D;4|p8Ǐ:(G$c'U/ґİc86CFX:r\.Q!8ZUځQ‡a˥RiԩmmmXLt.2zrɍ\E=)\qjZP_vuQh_YnFm 09N~`eRIg@<<#$8{|'Ȑ%>xc9PvO8+rJ7]9iR~ڴMb˖7RB3qɓS|ў [;~֎{/i[re+ҡՌ~i_|l^5"Yi+Ԍvjwl8ξRdhi|[CP*+dwPz7c`R(~ԡB哟|ZZu3Qj*Oo$x\.cyߍr93`=Mdѽvl Ǝ!L|uWWW\v1rd3g=sa!4*VWm֟VBrʿ\BŅFkNHQ"|eYZgܸqdq3MNɖB066ud2:|0Mx藝;w"{\+-p.xY+ĬdX6>ʓWR)Gk>"&*T;vDR)o\yezS˦A}P/~&Budh7~5oǾ}w_E.ӟlgxR 0"5CM:m'pZx\_Q#UΚ'gFEi/k 2~,[*[C9xtz3Y?*24g*7z= >ȹD'nnmUBa(wT,(9 NS`` R{ytDVƌی1+ـ߮6Y+"=^ }vHɃ>x0XrQ//q}E| 8` PlN,cxvP(dĉs=x2L H=(*"Jinc" !TFӐD"D"+ ,ޑ=_&NH$8.b?8JU5uiړؔe Mʖe{W ^[FQvjht„ 1aJ998{@^ϡFCCC7R8 IDATn˹)wlCDR#W1j w*h T 4M x<H/gԩEU47(?coU]zZ̝(e2e˼#Jx+;ƒlVzG[==sc ^fƑR枽L! Y*J$Wr4C{G;q bWԈ#OE q$ɖz<^+ժJ69MD-[vf&^} D^}}׳쑕#3Σ7٨veMNUS~xzɒ)##K~,ϢyI" ?V1!k;."l{ي4nP1ZUU$&OZݼyI! agNۉEᘎL!aiV+VEV*|TRVm4ŎR$|t_I=jv:G)րFsxe}CeD"ŒL88iwՒȵ[(L0@ 0iҤd2DW)j/b °k~\._T*?3iw]@-ZL7@ gb%ߥ)x<k ҊB˗jzM_[6K^-RPCajl3%tlئn::NjFGO=5VO~S9衭xϊvAH4f|xo @i|/~B.}4Nq57Ls8$8x^JVj"Q](Ma˪'"OhysjTEmxp!|[?s\e֖?om"~{}l[/OIV;M̕+/d=5)WlWooQEHpe<*ݴGh&oGQ~"A n#qTcgnGl5eA ]]p~"Lm%a¨80+dH$Rs_{զOqi[u }"Z^8Wˊc\V[Z z#e#.TTa/r"j!Nɓ^D p8=єy``0FvC6DHŦsXi Wv 4mŊ\K tT*AIKB,##]AM!;ʯx%gBb1Kiax^PYp[oꫩ #jmƎ~88:^+h$K˶dfw. ~auuuutttvv&IDYac6*&)s<ใ蛦YTt]O~'H WY2'<긾ٳ9>;+z<\u/߰KՎhp#U67.N,o#{c2I(5|yK(T:mZC \yt P䒗޼pgϝZ[ O^52_uEEz!_2x'abF8Fa͉]5!|~j\FH|>ٚAǁa/(eM{C(ʟʨbLQu EGa$dZvCٽ ~c]rɚU8gٙd/~ed9cϴ>^i޶Qu-i]3}.[k3R:=@$Ї"s$;IJkYx1[w1Xj HvF9c6#%SkLAԏH|]+O82i1g W?Q%!v$z쁟b:~駋Ţ*#0"# w[INKxG0R8Nx`4}Kj@lg+0fQT>˺|EN01znw0 v,,w^w^iV|RxJͤ<'I nx1M'2xޑjr%#.rx,Jad⢲ /X {XEC)ž)[gNGe3J浖עf r?ٶ-48TkΜyr~W0m=o|[^_5tpp-M[5Q&ˆ}]'onW-̙䖀R-DU+`Beb1T[V\ ={HJ__TohOaXn,d2ccc¾s,i3kvpd^`Q 熌~d+Hpz@6Θ#e9srM6s=&jEQ>5BGlS#pDQEhCؑݍA:"cSjȄ .SSG9 ]c6nfV4 :Ӯ,9kT*5<ᐼ%9cѪ0E l6 # lbe>?Xfa汻ߎpKnl7\s}g>{/nd/C9v;^wWAE5=Z4/O~cӲ^Kr+ﭷ~VuԺK.yrl n:6G~mIvbXvR,E',!2uTx1"q֭[o=?sl lXv쨊HѦ:ԸHgӦM,\Mgՙz{{ (`, 'K4j, KT p8>}oO< ޱT*8B:Ӷ6ىە#\UP qtKт 8㌾>P0'85ہD5yQ) p  _(EQlN6srqܴiS" Ɣdžp@#Fů8fY-~HuY3$r;˲p "$^X񊣐eͪZY>+@T,킎R_ZY?;g S lx??뤓4~` PVG;$a.ξ'z;ΊyogwYTvQQ$(bcFEc"Ĩ`b,ILQ`OTD(R]^gyy;o{&:RԀh.CeӘ*~ZM=UjBK#Q0{+8rC>^tħV̓PC@1@2 m& C=E^}}p7jt] kUR^⊅@O {`MhMl[;oE3$6z*39Fbqmǂ[oNWf+'L馟U`e٩+JB\}}5xa|Q2c--k=G2 ro\UB'xa0V!x<o"s:D;rcC)nd*++[[[VZUZZjZM$r?xʊ tAdȦ1뚦at)CZTu @P(TYY9`|#6 T*v9Bwl[ZrE + XA6d`\/GUVV $%5#_&ڊrO5|<(;U;%h4޾n:L0 0XM$ H7|>|ꫯbsS;$;ZYgMU0??U8*8owBs?.8-O띻ܹuӣӥ]> Oa*Qi#/q&%%KKf 1c >ml[WCg_rXΝ&|nNZY].zƎn "uQma > zuWBdU>l]_~fs?X O_I)LaDMiZj"lGdF3 㥗eڈS\YV).k怙W|擈09Q#AFLwW]M&le/;p004gUa-@]~}0W05mEd X=?pkMo`5$؃G8 HQ f݈ahd;+-tvŇەx:S.i"-bo ~Q91Ip ~iiiyyymmA0)bWWW,P/`WRRћ=^w8L*yFA ՚[KC^/ u@MJ~ЩT/lX@ 8]wݭJ icRxv.vϘ#"\!`v<;.\5Gt;tZS"BFwEzLoEvojA,-g=8/^viNF=wԱ^ CtZ|k\Y%݇ _TK>!y_tqnv4iMI%Ƶf ,mS0jj: INUỴ}iI{ZiI\Ŕ)nA*?}7͛'"7:t?|K/;fL{;#ыj> ]ڛi8Ou=l2(ʒ%eѨk.`W`աD"j"(̤05]+5P?OB*g8`A%ܡutఉoxi<5+C>`Ng>Gǔl6KGԅ#P&:K,GD&#TDZӴ|q: 8qOW^^>{l{Q"3H2.\U}!L4i8*NS2 vW&}m!W c8pF[l&ipa::KΚ8g-q!qf];rP/jcI]Y+(= Mi'c[_:h[ ؜t]}={?Le;Dr'^Tz_ߟ?mx=?d>k׾ҠgJ]nwl?ټtS.596 o8!w>*{hq"D * mV:t,Cf]ՇQ8b!㷔Jg;_^p磪ؠW"kǎtp|]9#ƀZ/e̙^/8"`5!9_i EB$ӪQwnP׹+Æ 5K#F0m#]r*H=c^$fDE"Gy )jJK:oUsX*ˆrƑps9#~Z"q(8mJУT͚H$4\L&H`ꪪm?0Ė:*"&V&vЍn.?NMۖn;cfdBK/7z-Ýw먨#zJxIK?4zu̙g7Ov֬~AرrZ/vw̶^12»Swl^;}ˉuqqpqhIOP(p? #evWLBᛇ޼R#G))) 3q{ș6#&BK.pv!|hooׄ!!2C ⴃ R,$NJpsAD(l!&h07ʼdwԙ K'+~K]K%`38 񍥧/q:o#hՋA*Ro/I\''v7 SrS{;C~?Ufq?zwݷ4i.7񾏻 /Rk4i6iQ3f{or6e̠?e vkg;ƍkMi~|J#>X"i`WZXj1=U$P"#G351MG ù\uyI0NZq B."XvKE%숁94$)IHOBͫ B@|F<yB؈V?ڢ~ߋIbbxˇ~UW=ÄV0,<14*illlnn^fՅB)4D z2nVnZ=H˟dЇˣTtnoEK Jc2DŽwSxwo.Žr{W4쀌Ex:Y=8 `]--x/s`ՎrF]׽yҒK񏢈VYaoz^qo{5|PAiꕟ6}׭.nýҽ $s!E21ư+b`4_2sȺ[[ghGlz{L8FXUXk{ u_MUU~? *:CoyZXtOrZAxKvg QG0%9nn LK({aۀ|>[m/*Ǜ~l:1D's Vd1cڼެaX0|KX} z}cG\ @(1ؤ$D4ҍ`%1fpe"4*3\Ețp^޴i=҈^"!B' [z!EaЉB%(aGr(ɋ(6A6GU5Ihw\*M.G}7%[n+x}Ad"@B/"=:Ƕ-[N2dHMMM0h $z*Ba8i8IXކ&KH7Q# .hQ+ 2 вC1 jĎ(xRrL8E1JU"cĉ_}՞Z+C:kЧO2ylo ʨ.~, mư._6v FўkҹPTJH,ҫyp~MZ2}kۑ2B.2x}}螣e݊wvwcx~C7y'Onwז#:<>Oo :<0njiGoQi,:|4 'BmtR21Xkf\r\eeecc#\RqPBY>Sy푎tq!];uB=MD4'/RRR2pSO=u̙q UAb1Ďh;dI& B&MOb|"8J4d&+ /?g|T/G:Ґ|>Յ񛨜th1 c7V[t?k"ywS+h&~g?RC8bkǍ[&}E!2Srf_z"D^q~Y4n`*>Ow½֡޳kF9LH+bl"P(655>/ _bM(0btO +T_PmX3GrJJJϜ9`d',tuu]#ӧOp8\UUU^^^ZZHG>6{M9Ҹb:{ ]!a"-pPx&o8Du;bXq>ZN<|>O%pPc*Ov]^B0$D#s]`"H֯CiyEJQ%*OEӛNa]q/qէi13?( ߊ w@ o|Նx7~2*<>mwcF i%3hgT .D/ Wos܋/DH9ԕcZ`G~0DiXmn$݄amp _:QTUߗ#?jj^{텠'SDˋ85l~Ery`a|$\Bp H#ej&f97TQ1D&( ITUuݥC 93f͚) pӦtbL:(%Inwyy áP]U MIUH^4M<# ;Ku{8bG"͏ټ.{/hm59sr}y'UDn1DX+{_H@W->>Ui㤒)/"_jjbcƴf͍7q8BCs7ڬ^O 3:fl{L*2k1ccϟhipZ&N\w:.jCM1/?W$e!F aF'ߺTU4]`|2reéT*EiG[*i;8, ~yKC:LKK].-9ca7ޛ}ٳgWWWl%wlss֜3e*"TNs꘩Np)T MFMD *ZԼ`mOޞxjVЈ\]|g|78㮟\vE`erg>`6p|=64D" ov.vxeFi"iCw RXM3>g9}Wܻw CO1q30mSL{߇dEQ J[&5ssʥ?a@5{4O-nMǾeYO""mF9#&&XkkWa@wp\H;Ewĩ/>g@5^!VovCtRa]PtOߏI&X X~+x?~Fˌy v A!e|7?cYsh*[%'W^ʘ@<*aWoP@! fSDF/ j1 G6xl J r" LQf+"%mt=IOC|#KKKKKK0ّqD@6 rl2LzW\A' 㡇RZZJ؞Hb9t<Fx|ەC^߲Z鷷}ZbӝW_ow3_dS az >w3Ǻ##ѨGvy-0a|ND EfUU)\@  ]A$RY@)dWt4X,@BHh/W~bUU󕕕G"y1 IDATT(K QzX5 0N>O$>';C%#6)#]]]1 xZ4 cٸbb1Й:ǎ~Ŗ$0F/J7@T)E8\VA;2#J!0Ő#ij]V&>zbl"0T Q4&" W?.~}}ꩫN;m"z*\ VtC.ה]d4MN3m6GD`˟d%3*f{P m|fY msL h[[߲tzAUSٲ`v-nyș{onoo\.@IIIBfpƒ//oT77^,qWVԓ躎n?>t?au9#g\`]o-!(/Or+*+SiPaۼ!qOI}lauK[oR78|B! 6!\V$mRKXY )lN#_ >, g&A3#L+Ea<c^;Ŕhc C&:&iXAN"(bNlիW#vDgL&;EI$hQ#; t:z"E/&5ԤMAߚ""Œ#2dA2G*DQs%ExQ`pˣ2 k2bGM+!Kl$;=#ZuOM4I9@mp([o}{,XP+}>)A/!yS{lcqЗ&2ALGf27s+SηՁ5? X#̤[τs @:Fxz{ꅕmooߴiTBPAZھ%LRhkryp~sjnHr ߠ:n ؍ !X'T>԰++"LwgжZԷo"p>:DAcіm`*3rRmi!{PL`;t<#bPr9N y"6X^ "><<.Kb؇)z8}2XGi !KKKKJJJKK+YUUD~xLR4 bBhЩ41 a /BDUUdː</^tPѯt#9bs"0把z Ya**4{J.!&b,<{_a6XhBxt2r]KgiiWNGu$_=4bŊn/~aUU𛵿yko=?lB˺8_s~ușe?-\vvs3'k^[>X2;lIz/Ӧ˥g"G{2(D!.$w饗> '[C!ն",#QWUt܅YbGSr zA8K+j/q",&jF"H$KR]]]---[nmooG.X,FX!dǦCNKUt:F9RHj|-[`dp8LaGQ( :bGjT*ގ/Zȼ\.'aYX4.9'%e#%6?pN&hϽk].Wyy9p+1‡|lʮƎT/W'q4:Kw8{k re/~ /\ta@7O6!׵[NiO/I\x{dBE-QSQհyM/Y;E‘*2O*HQBp$sibS&DҢDZvz^`?թVXW!/ k"*! V3,jtO?iӦpwJ%&ᣙ4_z;2/D>WVxVe_h7YXrZh(;J_J+)iRKh멩_nP߉m\1N@EE&b!($  LvuuEŸ/k[˝¯\g Ç ?_\/N I{5/Կ16p`Am>ŇzVŽ9LǭRM ,Y;e?qpΜ9)̆4M5ߧA 4üm㐰#*_E A {1MèFш"`0J!j'apfB/m1~|-[[[׬YaЮ.Q tT^) T*ǣ9Cr-?/0.gҤIΙ3XZ^SX%9d!1h,\2]73ÑSEp(G \:NO*%$[.1oo޼[_M>]gߣ}v;xWk~Gz? _PC;G9~\roM趥ٖc`;/y, xۣ3ߜ9:: i?묥 !#E|C3|阭( *&Mѫ1̫{9D9bCZ! т4m' o&^ZslOSDs(6IRjc5!`XeժU~_Q$/B IG*$G DŽ txGP(lݺ}>qp0 i +xBKؔ }DQYvq 9Tk"Ex4Yi dz-))inn=8c'߂Za\ u]#h :!$+F2xtԦѦ~Ǔ}aog˖o8_9EY ?K?yMxߦj ax/m(i#FC:?ѕYq0brOW766sfr:.f(:8M6m|o_:ݣR;#BGOf$Kgq:,y"Q]ƿfqd1|!O:bY%v?jG rfrʊpǓð%М 1ܫkϚ,} &~CNju#hĆk#'9v?#"k09"mhN(ͤSDdE6Pur_^+D"zq%$jAXOȒJ֪Ҥ %0H!L!6;"ׅ,x4L&{ѾnWXb۲ʤ#\CJkSw`#o\ZHi'`xR9ǻ7f)܁tFTuwwoذP1q~tk_BTM(* Ì>z))z ow>2ZZrΜׇi˥?|/5l&`5i#?mN·4 }ߩ=4y4D-(zYt-pcJ'|rbu&SHVl"@i3# "|D K A^f_ HAlxqH绺P MP_czX MUṲ|#8? d2 Il2ttt8P(%\Ƣ'bIp<")q {QR|Wl|!gK/p4BV 2ʹ+QsEy4"Ҹ.BL~Gg]D{ː!x֯/2O.]O=uoݕ W>g;.OwvHu`N[{Ƕ /4OݱN'O|靍O?-뭺tڱysiߟOD=vo1 c= e!`ض}i=eee)Q^?A^ؘo A B ? a*☦Mv7n>p4kvDeZ *Eq?Gj9v8L&(10d߾}iƎAR,Ԣr9zձD"2A0\"lL UDaNH!vEG@>HmЮ@ ^`)xCߚxiOgOĔ)XjDDz%Z$4`+LLA fwsͥG`S7yspڈa(p6b1[0MeΆ)SUUp;6` vjRwI}C%UF wMȗ i8ed~麮KKx"@jZvDP2$Kqh|0J!I&PI l5I*'$WEQsa3^wРAX6'IWӧO[[I'bŊ%K?ʰTBD24DKh~5ڵ555ltYN'^-L}Nj+3>WwwTTz-K8Oa7et%V/~&fX($yo4G벋Yu|vbTz`ؑOc0w^>m6m '7nwF߷t\g?WLjǷi'uf}s9fɾTXkjI"t?\jp\X`֭眺'6_rIR-(p/zVQYB]dO&j~֯ݫiF&sjaú*+HM/ PG Bq ;BafKUBZO#@(ndb{v'>VUreY4#4LhdF@0OQu\ "|8T TUŲPiXJ`W;pѥ#@†NM|>_(*--F|jPIFhrÇc:i6~7#!U3z %%% <>O#}pz])ykK#H&=O>4bT‹QfE(5ik0p%CFDd-ID끸Gg%2ajBwܱe\֏o={QwǴi;M+M2҆mV9Ϻ㎊0`g[Ձy%?s$fMwfذ}m3w$OJ{P8Oh0&M4{lSKproQS1%H >"aP(ˡ4G~R8`%z%ZUw) 9 vrjdE s"8c8&e2cI6oތ!xDB̶߿HGT`lj T}fU޴ҢXTb!ʅZ*<.\df̕Ge&عH.q6bG—;j~dgaGc?+~%=j/H.z@0c|:M&fx_wѢW^u^v6\=ϙ)sXaf"`U_JگOT(aojMl ,.b㭭nՅJbz)(UnՄ2R: 6vrSO>3j)@4Osִz8Jo!:8y-5ioe#_]z}hF"gdF< n#xpiRI酨<(A9=I2t&9bp$a - Wȕ'CY[ʘo>|TE\.gGr%P)^ kPA=ztCCCuu Xhz$&OXPd)5ZeTfPUQcku._]f3G5ijj^~+c,Yɂ7~syG` ͘zD؅𳦝 UiG7 Ő^h͚74󗿌=5[ (Qz+޾ęVAӚ f1wQ |ȯx3Kuu5 +d YD#Hu=L&_yӲp'w=? >C>Wh7x$W/ϫ@_'HOHV,}]˜O=8RɊ+6)_%SĈ!T'u7o )E!]X#B!?>$u.3Н*t:x^ĎiJk >\/&X6$gp;օp Rtx4ᚚQFZJZ{aG8"iB?~ڵk12#FӪ4x<>a1Va "&|!4[Pn(8,qhSGázii)*zPgha4 Gwl6k0'qM8Jކtb{мjoAgAxu;0-MChKӬvv!/^Acy|rȇV\_M纰/lC//^^2,>LUU<} uX@ſN`ӦM4aE'Kx  tIA${}?ƹ79ڋk?XGX5PPPEfy͗iÖa?p kJUѕO?4M#@60H>:!#]a_!LA1X ֙h/t=S|#L9C׮] pD% E6q(Cb6Y(: %$ 5" Hvv .܍oWulT*dpG竬۷ʕ+y5M;$A1 o+NPCqMhtر @!2(ٜt,n+V|{iL8UBoO IDATP6D[0^K&̑Fin_:6#cĎxRuc;!#nku~$*B.ȴr UFX鍦9Ǝm?ik=7ި3MsoV, &<@>GL~pɌՖ\" s?L^89 ̀U{ } % ( ʌd2xMF%^#@rRbH+{B'|@J&a2篦in[B [^cTѦ hf{*G.,PA9/)k @D D 9E--- 8Ef'MN;) \xI؈xSzqNRl`0O&ґrzRp&TQ߄9\X@G ,J:p6ָ(h?r*Z*$Ipݱ@^$WӦM+C3itߥ1zCFP^>g4gNg^}!'n<` ,bưa\a:\M}8y@S4foӭEOW r AL] zgyˆ8΋ݐޡ%X.V(!)y4 QY ]ѷH8K/ۗ/m?s[)xXYwPU T_ y55ۯm']=7fIIs\<X,+6>[Be0Ti:?.-cN9eekזZy :hL:yс|+VTZ[nwAQ U\yyPP޻S c ;4Mɰ7-҉'=>胴}tkocſGNR>!;F:d*c]^1Y}fI#) E XM |> 7B%s, r|1 v#H޽ ֆi*'3%,F#?Ld=cV~+U#;[ƎKy#`1v L8Gj$<5o^bW3ىA{μaM6M5>S!#&2>lwLxwVx4v7[;4GΗݙh+˴t7X7iy昖k [qw/8Ca.[K~NcZ ~7_4M;(zOmoL_'W/\X=~͚5dU(@.&82-hvG"n͆ÙBA͛#PfƒD•L/qcc}챟}2" - V_4{u(r!4;h"Pg%Xm~ߠyҥKnjp8xhF"[Tf2KW`8_- ]4ܶT{1ED y<3M2VqY#D$Lbk2t`!/[F'c+[L+9 u;iӤfZ___]]]QQ.2&Cl^z IJIM{eQ!mIDl44HJ56 *P/"n fd~OHUU,o'wLvΚԣEo*őM]nH `ɓWOƮ\Y:o^[^^mtt9hG 8Āez@dcpv[9wivHy2Rml[$|oJ8jFjy09Mo8߳hL,A.qnt)yRSW4q` ("x:DQ лӀԴʰ/qdd2-7ީ_6ҵW\qsuwR]nF댃·)=JaQ_ߤUQRаİuuO xZ8sr7u;כDaө;=k/f2 kz=7x>'6A4`42E]%H7p(5X[G-H'dcU#0|9zxh$}(X8`n:`]FRuaMs58it1\]E䈄OUiBN9vEq\p6HDM6d#bp8#Q[gTH߯ U;>FYWWW[[[^^<?(9\.=L3ס+n4d6 Q>D0L&Snpݛ7o.//$= x;H5-҇4T0#6L!,{t;R{Zzi4ytϷ[mȻwZ=h,h`L]14DDP^[N= }F'<$zvvURDYɻ6+78~/(uyvMyo>~QOvzLgگ&l8q3glj ytZ;߼{;&LVޑ6N)"BAw\:SG5pFD``* d!a("Apix<>Y,3X;DsQX?`Pua!||y6*=gΜsΟ?@t$0"V~Ჲ2l|):thmmmmmmiii8F&B)(Yi(I\ij'g2h4҂N%n;*Vފm6 ,ME8¼X`MӐ9rͥ'!w0MK3Fxb#7V{ {O:i…5yj&O^fFDr.W_Ym]*FH$+ ~КꌷExCd?o:s`v)ᘙ7vie҃grf嘍u!kj" o[8P( xww7ttt$ ]ee Lk&}`ZxX4M߸uzf+PP;:^o*Q]A6麮}tIo46xbVGל|͆Ć7yׯխĭ;1:~i# ci#{hJx;bD_M7`AK/s˩|t0ePwځבܨ{";R=9|&)80 i%E!G 3o-Lty`H. RUU47DGqC S* [fs.VU 6l…ٳǍ;bۍB؀fÈִᢻIYYYuuu}}}^" !J&O@ևw`kQESdF!=uwwwkk֭[[[[!!Q GA)%" !c"OK4iu]d2ƍkoo߱S);wT(( ETTt`Bh;F+G>i3)Κ@W֭]sS`S-Usw>S!nCw,&ϧ( ^d2X],]}F~4B1\>dXkrsЌ_ UU0sbc0>T*$e PΒ" ,E8B5p:?_3o^ɪU~HTӄ`PUa˩`>!u'/\{x\}l wg{??V" IZaQnbs[{M]Y>sV#m9^쳱ٳgGINV0KVi`nՑ G O;b#Ls 7bOI*i۴H"M08GN@rp/qB {~Z *NF04GJD@|7sN1|+;bqki١n`I4"1:^zUTTP(Dq6;L&JL, :u(C OQr)Dggg{{{sssGG)"hrIϧVb%lDYa.P8N z5E Q#d29yt:n,{t;^.T]uE~;$ׇ n7$$/>Uut;B ! ad"82) :0( HHNt:Iw[Ǔܺ$$ѿ{Vz84ovfs^/Y^W_l |a[DEŴm2MmܸhFU~^ǩՌ_gbc=N$w5z6i2?˟ܼJn~cgg-knp]Oo n~,^qݺeou|AL&CP\FpxG$~E 1Gk[)|H }(;d}Ӥ=0c4($ƤcDKAt]g Za}$#\ĸq,|$9VU%͎#({V+ H{ԨQltx =9J6d=5wL9Q3##LKaY,4;Œx uy >N gl\.r:袋v?N刡ݸTh.0îo?hnd*E8FmbPxF Vp!1HLt^äW5h-~<4iҰ3Ȟ+NcH*4#/\TW2UieHpbOcI[Ѝx|ԨQD4jԨnwYY^MS3xPmx+Z.Mlf\p̙K.0cFq^L&Hw@gMef.KAI5?xm;H65sfNI\cfׯ>眓IƍX'o{ T]U/M2xPH,Xp 'L<$K !k@,Wc q8feD#)I ) #XU&;=BtU cGTrjh !I # uttZp! W^b>D癬/* Wb$NAMTVXg]*2kmFeW8J<̓9bP(顡!F  h6)&CT ; f]R.52m~z[ww޶w!OѼG$,2yy\T*@QSjk&kO#pӕWPs]T;x/ukhDM&}CCp4g?t@Oqpo0r9a_:2Ȗǫ;r`3ݫ;BPkk+uuuW2wH,fuuվ-&` 2|[;x{G~ijND>;̧~fT:fϴu8t6H= ͐)Λb|9ιB cH`inU]Dqǥo)?"Y*kraæF{Gyxp]Uq%E`Jڶу-)D&&1J 6l/ĉV^,CE.(G/lR1C 2cHWHCy0uNIXضѭȲ,ǁ]GkkkV>}e A 96zzz>*NV^_/&Vv-gLD HQ2|~%ܤrYTp6X>Ed2\JjA&RQo+syGHG σ q&JL&=\ v<(nYDjz0M\}1i89w֫z!]oW8f3o\y0fGEVj@ JhiJE"&"J[^_8?ԉ'̛boaC0.dD4?:ᖇQժ>gGv{X]vӷo+W-Kf}̿UoW׮}{x۱2 3PkReMO^}O>^e 686oKkk+pY^52Hpw`P^urwpqMb-$SSs[D)b'.G%o'%AK,X'L#uZ}ב3LRGFoNKm.W\qwTCh4 +jdK8c(3L0 IDAT* ث]bq>E0"gO tVL&l߾}֭Z-N#$@h>R (\S8xVpB`PYJIhd묛?Ey4!upsSR  Q27ktZl[m\FRɧiiuV~ߘ30 MۥĿ=瀳:kK5.{I#Y4myHynuz__8NDJbYZWW&lYt#tzx͚UZydM7=GDht``TJ*7oRΈqRH$sm,F sPHϟ;;;ADIb7G$pt́e,ňA RWa%$fC4-Aي\,C^)jB)/0\O BM D۫*hjdojʗur\ UYzk{fpk_KD:XC:k@yDdd8ތl77^9M۶<"*zeChi[5z=lbE8xO]YHK?9I="ėr[5P+; +&;}" $N2 #AH<r'Yj2]H]! yT>XIl‹j VÁ#4G{_SFd2d5MBMOMXDG]wcĩ5$h6~8? IȌ XZ r…]]]Ňr`_~q<^kVWI 0‘eȈpJC⅍Mb1˥RT*d7o\*ljD"x,UxJyZ. 䨐=8t*(:Jf9]h!Q^Myqǡ/8*f.kDe̘QGm?M`i!"݂ BKԑ#}g~ﳷ]Rэcr0M=ya9{$_7'Rޯ;|-›n_iF?Q>~oѷt6yq,"ٵheˀ{%TJQÓ3c;SCDcA ;*x FjTmV8ɵV8QOV[kzRՑC=\S,AG6Fؒg]hd+FYJ2_fO$hKXTp\fWb|_0sH0Xb2@4G\9I)ѹ 1:3'I 60`xjM8j*t6F|dPZ=1|qɓ'{UZ֔VIz2@cGyv׻P&xyUJMCa8d?{~뵧MqЌquvii;ߖnc^=%d2ЍrG]%"\J+}*s潟}aY7ovƳu/L;qODtQ֟3y?fڸgƃN%A;"Zݦ`g H/ҋuN0P \S+ȸYvD\cƷd)9Xv(P0HZQ_D>5;cS`7&<ðc1;DZVؓ%Qp8HRH$.b^K^fC<ݯڄ tѬ0[zj օmّ V۶aa@ͬ ՄޙK% q>cD'.!~lf!Ը4}5GQY*i.a7mqVѨ,2D}C_Zzg۶9 D֤vA1MJаcsMX,f2A8M ^bA e3Jݤ);K9*{\µ0 Cu--zy#_4#ſ  H!HQ%Ճv|P;tqC^v[^@5 U_4uuDtk<" ]S^m۰v(<[po}//ƾ ҙ7w\jp\ ,"OaqeekkO4*kv~)DToiOٰ!ysb<4/d$RؠƐf H/o1 .JjZDӴ}wyY2ЄN])/%L 7 _ڙzgn6 eDlB14sH(#冐fj#rT*uT*0%6mV@K(/wd١ywCIIJ,^Gꚰ@$JyxY(L * BGGǾ5)lЏP%vtEY"&n Y08>1:czќLqb%vd޴y%9#]2<<ގ Y,G8b ꓵ@ ~3rYC"LtlWjr454ei ;|B4x<.Ǜ8̟@\cQ젮(+@I?:J=*I2Mh]y9rDi>iww//xş2W.z衇)=~95h]'2hZ L&N!7 bCD-Z\ R{.hHR fH^/Zhܹ2RX ^w\4_f=W]'_Áűf)\W\&[`h0l=D=1:޺VѵNt\I$s0^_w޻m;Oѧ?KmDm-e<63 = 9f"˻F2F٬.=ٵR˂ Lrr9XPxmذa̘1W)%34KRPc5; iɐ!QF$IRʕ+V>j7mOcz%f\b\x]f5(51%ԄP{3:3$vFmtg?0:t1FA0sT2(g8k j6[L2DΘ 6ߣo" !urw"n fu>/2DIs`:j L%).w3G!r;ML!Ou" ٬gΜW 8FridqɆfa #"J#yK R)Yy`[=4CONɧO!"kmn\[㐳!hejeK{A~ JWiڱұ|)=u[m 'ԪUݶiږ %E~ծ!䚖|q?141͝mb:gsN4Dl@xyTqd]-͋QPq\}MgGʡFyHV4iiqvRDm9ztס@& t^<~&fQgKg-#l6^t#Pض]b==7#V BXDcvurS^p%P#|4xxxʔ)===>H!Åx2vaM:嬮0nŰe _j1<5SORah"!K.hnкJ~@0=F, W@4R)ܼy3~kDH5CC y婃}neɄTF.fc~ׄ :hsebv+zT3CuOcDZ7 [u}5ړɓGڗﮊ25+~0ɜD]6jlCJya5B"-S{RhV 'gvީByGeEa`q,c>0ׂY%py]e'4 zj ,Q$kH "1jޤ64C%P`ƩsFơisC6ɳ N?=IY^hx쭞wm , ߾ԩjnzoF=ω3ۜYx -- Zw}`Ai'm)AR(1dYp(tD@wT}x̄B!, 9&pi0g LΥ.׭[d&Nqv(uٌt]Ub3(O`_}}mIUpښCփ7)LGiùl<:8-$?XO-oaM|tqJߩTjǎHfa$٢Gd2r٤h#Pr`IP`PFRc@I}.twpL 8yFbe#ϟh"+>x<ȅ@7 ;6/1dh__0}nȐpteH۬&Ӆ.n|5" ûKc˾b/Z$D61.Ca>f4yGD_.@&#l\\9f.[6MR\g2åۀ.R;.4/aJ%v-o/n{1cO{;Wy4Dfy>hTp;4i'WR<>oh*&-L;4u k~uҭ3rD|7h룃A>mD ԑ# Y/yG1H[lT6rxL&S׷m~f`Nѣ1ҜFGc!Y+96lR LiJy-#5M~OႭ ~SLFPX,bX,zFԚ@:3 Grp0to6-,G#+Q Txw=2s'wuCYsx}M.ՄNwiE? œΓW&لږ>P) "~<5JMҍ.">$/LybJ3Z׿3֯oᆣ>{Ç?R]VGgI˒_=prkf 1axƎe"!&~> Vp~#qndVO¿تjזͦκ R7.c 8$\`0JDX (}-TR%\*;CL@"q0s60Gl5jc@ĎUsh)`G$F<Ըp\xupܱc<D"-eYPڌ H.L M=DBʉWWyHi"5dkтW$M5Á&l=".zlYł-b18=#x'B qxxxhhfԨ;DZ$<ּF,5pt#븞8q#eAk<̛wwجgSuj1GQT-̀0E@ʶ+ֶ[b{wC^ jUcSr9HHx=:Q P.yBe79~Ӵ^V̚5D…κO+Oyͺ?c/t|3g~+-r2˲ЏF3f ر]Ww1׈Yqv15A|)qwk^z8X>>Q 45R-Dxɭ(u|lk-Z샨ܰR)lhj 6J:*/p Y2)[) >iHe5stuHMiue c sL8W7RT@>zDlVb2 Q%Lj8Sbb˲hƎN_~7߉DI¥4^YT)Qxn|n[p@p8D"pK"V>sL2L|2{TNB|5wh34'NP1,%8E̝P(;-]dwX{JaMY~ۦiJ_}OD9Q16 r=sqǫp܎$njR)+V._[ޢ^uՋ6˗\ɿ󓳆:kVQ~4Gc:]_~\^9o.Ay 9l+V3g0>Cxg@(6h\8ܖA[)M>)Ϝ^:W؍qI Kn6`D</J [[[AEQID99MqޤP4… OEݾ (s/Բ/m &+ sBP"@QFwbVd. N.]4}oX(}[/oϷ)j-̩ViӒclvEt#֑ˆJ $i"dQ-z+ w_tO~ɚ|;::J瞻ڪJ+de8e' }۶mä*0V&:aȝ%Xu3  66Ԩ0F4^Cd]FbRWvZ*#R|]"D"H$2@$4Hz{/cX87.tʠ]-w.ҦiFQ~,=[Vߺ6,ǃtzܱcG&a4D"(yD&@GߐpHNwu!VnB$\c.KR$W>Hk t/{ Y~;dI#* CZ"f )Q{{Y.&.9;vyGe $^^ jjՆFD"IDmmmPzD3*=qV{8WWյzY/w;f~jç"f^E?9t }cnݵ#I56vngW^W|}.m[f8c%W.4 {>_'9'Z팁ݽ}v"*h8a*ngK2 7O}M:Kh?Vk>{^I\ Y)8NJ---^T066igKpPE\̙3-[jO3*.k\i,QQ#;5 Qukw'GLT+-/ݚrA8t wJ|4TnVgqkDݱ2 !E`k* f5M++W~|uwTxKRk|lۆ{ >}1),GXmYnz ; |фGQ ]g8mK\?d'_Mm Ǝ ]WaKEyZm}}}XLvzF.hzC@3=N#pXGP"ߏaF_}$a 0yQ&Pzc:;vXP`.wh?k?B1L Wv|wdH^ ] 6D^;lmfo 淶#*!H bVW~/D_XZLJ?tZ4?<?Ӛ3LsW~w9h*5/m0$F1bvoGi֬ͥo)O>95Xn>ztaΜYv}ÚRL۶eߺkWEW]잭ևD掎pT 5z4l7)^KP[駟30,jYQ =,bTgTb]%-eAGiGt-R5:B[/7p:SVu]7U\hHAHp]b,*~83{X* s^d2b߻~, , u>ΰ^E1=$;jꅀX`We`D~%!d iib!oI1RՎr|^CL&0FhRIDܻzGˁ+#5Mh$5ڻ{[z= Q$3(9W0iڔ)k;vdۊz1AP&F0b1򨼜r ضx<VC/{Mv ^K6_)9%^#z`~y;TsL>;w~IY"a7Cb꺎^hkkQyH`KZP&NH08"vۤPOO+֘fC'>9``KGD?KK.*ŋϞ=7K"r[xq"Ӻ[:JK &Ӆ矸`5.+G'3tPokG rHDy aCFZ@'N3 Pf0d'k,Lf…{ݏ)c05TgTszTFkD :`cHy`flh *r v`#[=b(RC8'It$DaeC {$l6 uEX1%j_V3c=Ε:2tO(oߦBͲ(=_.C_Y իY۶Eʹ:M-ֵiROOfڴPQ8\/hQl~gK7QW뒭r/j;>vW孔LVwB3._u`Q3g._y@@/) `e\ I#y˘{68R25CXV撼s!ypZvTT8}Rds.kXK1t}e9:Hp}Ӆ9ft_@;z\.W(i]4m5&b'ars9exk] {5 #Óɤi!! HR# Ӆm۶cCe_a7iƆ++@RͥR 4ɻ$i:/^)#|%]dFJkhJ cm"*(*,"~+D>hYkF+wM$Kv.~C tt>f:&%G){&[x Ū*\W)3(Z2|}׬Jef ;Nqg|~zS<0e-'W袍=Цgg4ī e滥%ɠ~G&k,;Uz1lc87xh$bN9昡z`xsk1!)Ck(i⠼%L5}UVʤϣw{j]%zf?`pZ2x&-jLmէpx#H1"3#ZOPXO>SyMq9#3*C7A  q&uVKF[lߗz~Y7µṐ%5fLa'g>j\ՠF)ؕL 3 ^oݶih(dbѷeK֭-kv=,0=4zu{ر( xOƠHP˶R)κ!ġ~$vwM=ȝw k5[op-eJD2/i//tp-$T*b#:rsutEnrfl jVĎoUerD"+8RrDP0_i80µFMY*hFt\]'%9@p?@֮oMb \3g# B@p82&#f&@p_]CdۣF/FƅFh* "BlrNSBʌWXm72_Q~XS {}Ux2۶9z ߸]80./o qFSzMQ)c&qu\Yq K"M2f+*lZ O=^;];vt]`o  Q7:>;j5vP(T Cb2'LvE)(胗R1 |Yay|`Qkk+&" 4^ YFȕIJ "ڱ#scV\>Q.{7}eL8]QrPqtƚJFG61i"hii%;v,K)FJleO#}K/4zh4Y1[ql эԪh`تXjY0ٰӼ4L(ϥy-$(dp 6qyto84Љ{M=8'a!l 3z ݓ*Y(߁|܃JXte/a p'Ee@"^QRcmAEIWZ<w^tлF\ʉh$F 1]h"LVҶ0}mP 70yxr⋭g&5,.Qw_[xT(D&S ety œP(8 '̕J涛ɡR1Ngn]{&oΜi˧N'zݮT+WCM: ¯D0^nC.!7/0tpTxmY#mۢ?Xg~hgXc!@1eMT,˶5)RR ZD-8bD"?pQx",Ԍ\J^ys>33cfn/pҺ*_z 1ei>2%n"D""j뭉)-)S:Ԕ_͛g5k GRR, w;k"" m6H IDATPV̊QІs91F$5&Þ"GEcڒoT*I9$ y0d3߽[9-UUhFuuUwԚ XxGSPԔԘ'3<ut}R&@A2W8-b2efVe-[]k'\vN`S,Z2q˖-F><<(bCzt~^|3Dt|G:_Vc;,UxG1LfZy;M4""aGU*<q3~Jxbwwc͙Zrc50К}>k‰ժg?30!A"TND'jL mˠ!G1D4{vKs啷q?2y,W@6yXJZɅW<*#І {{{t{T\3|0CuےВ^_A6GpQzܡ<3y 4ǼL\*vyQ!o7b( Y/=("K.=裙YWuC,k͚5H A؏IP]F8"- otu (:`.$59j "1 "ψDo+rlG u>'988@+ ƽM0k@۶ - @ 3QbƔdX]yMͫ]xaK/;n('w]PK6ռik<Q#LD]x( jʺiCE.Nlgݻz{ޑ&LȵT׬>Ư='N~;vKu1#o?/0qIժgƌt  @D#G-Z4%]*GutTtছxW/{jUW#Ѩn}^1*DjO>`r?t+Y.tx}KF#9wGyم hɶm˭{`AocHs߮³ZNxwvks3gk̛ 'NcNU*t ]]]qf.Cg=5#. 孒b͕7ip9IX6{lł + UU^I8hC:ggsṫA kpO3^xEIx5emcA˲]2TDE8@  =" #70*BMpJ ] e0`.$9fDXs;cyUt]uaGLLrHHQZW~H%Xu&)J?^˻OL>$uVĚ蓧ofHj`0 ' ј1c8,>FG -^ݳgj,MoLƿw^>xRc 4P#@d }xB0D0 H;tR znCn7KK':5qbWkӟ׋zQmpq/U<@D`[}S}rwzU1NaVn|MKl>/хgH `chf( {m[KB4#CLGF=ݿ>}ib(9ϾLry\iU3d#T䚩[?^c[۵-{o]wus_/_e`aFɛ"fvpNX$u%4cGI7JbQPY߲,qBP..c۲iHH裏ŴzW{WgMD_ƾz|` ?>8w\򷫄[66)4G!Ms4 ]]ӷ̝.1Guoѷ63)4&|r>nnCEXժ5=a;<2y'N 7休UgyさoM~kt+x[gyf֬Y.Bl$FB#;::0/P>,˜JC YI9 ɚMD)~51r )upº]@( tmN6BF@0#BXRR*~?jF'aɎҚ2u4' $ViS 5$; yTxTJ] %Ďk^2ʫzp<Q4MKRX !-=*CiB!J=:Er3κnߺL8P( X CH|ryhhr!PĴN<[n9]"iɩo9އwǑۏFDWf.ERI>1!JL@z<Ԩ}_WIѼ~"jvڦNĕ^5c M:k[C|}PB) 2JqR<'M|n:o)"!~!(ˌ`6&O0)3KDghM!B|gc %e6"!7 QaPe6mmm/i+]i M&}KW~Jʕir$˯gzT;BEغopw 2 a`nmm&](09@~. UۂVPW~dCMl1\f^:0~ۋ/v &ZZj5Y\oD(Ԉqr%aF*DĎoժsNE~Lwo~tե6GzUW=uި֫;ھ,Z:ᄝ=?O §V"y9˯c;_3vM/%%KKxm?}kk{}τ},(w;[c :7]v|z{r=oL'{[j]M ~yѵ*$ !$4 O;weY7oBX,GQ VH̶\. V[Ukh`ImSBGO84o.,+p纺: E1 AP7'S>P\gZV@ؤ eBt3pG!*SM\?'2/W&Qd?q.;& ʦÎB!N-_vAh#T(yG4?˼cP`֍7]$`@;sy+׭;~~vq%\bs/u ^!IPʉ+C?ûWIdm%wED +AD9mqLzd,jTɭeehGJ6vڂt?}X~!DD!e@CDoDbX]3ۊ⺮?`s4jt8 ExU*I(yڼTj76?xv9??ivy Ap|hV㿪*9i|8s&K_1Ţ}^ZۮPIՃDNeVx4Bg}\JI"_D򎨃YU={ 744aDFR X,sE?H444`"Q`GRS"rX=z(M5zB]X9*p)$p->-^矟aiʨφJ Z H+9DN{d8x kؘ*Ю&nRp gR1 dϜ#'۶jD2缋58q(J\U՝kBb 6iooq|aUiMW]}{z3uR-jh;7-8zֆȉL X;p&3Dlކyy"Ω#D4E~;w]ԋy#9[~Wm˩3$LH6EK&-ɫ#r9h4|b O״0OY֬ W% :>n~);vVW/wĎ( EBaSu;7\UqbivK-Xd;J8Ci߼y[ͻ# 00^=44:b=RQkkkgg'Asʓ'R t:\zְ㞐uqWax;~ZD`A$Pqq2(P<(|A`bG'Q.H""^\O0da'_q,!,򉴮\>!4IҎ#! _dAC%Q]jt:=99ɚb AE6-܉ԼuJ%4k\me}wygb x$0d9nWIeѢsYMv+W礩Bw=<K}F>f/;G利h|=PksOgSSi}sE"j߀ss/S˟N9/f2:lt?wn_uᚼM'd D@HH"[C, FMN_Z"H&͍x- %q%k.Q*Ǵ<B\$&TP01 YF T(CM8L*sp*-Oe -Zb #)NLL0jlF㶫Ylf6O÷Ʈ4+uֲOI$B=]q8ԲYfY"7y 8~ZEjmm,p@r;wQKK!Yw6onl{KO}s'|._?2Wْk(^+{5OcTBX9f]ܤ*Wݿz!BI/Ч>u9|8yojW:-sҥW]ٶ>0PٙBz 8RJ>y%"$xe(##/|a;#q;d5IZ{\^XJ3 cظ ;= kȊov}s\75m,"M_EѨ} `+uWezf¾tqf9Ci\ЮuI$tX{Y=!7|^hF$*Se'bx#9QFD~ddyt:,1ȹLj(ałTSm&U&K^S\’ N : PE^=d] aX,n: >;R+S{Ψ1ϧR) .5ݖj'r ڙ'\` 4*Ə-|xQք\ST*rWm۹\.Hp6T7WƻIYNj#F*ؙz۶m0fh<(HӴK.y ?8ǩ]WqsM=z:B>$-"ˆGS$.LBx+nt(M٬?~RK[~L8vpulF1g>:+4| H1}?2nKGaygC1Σ" ]WWEʒfӦx]y :tZO6g.Є2j v ptaG/T xwvfLnuJ,_cRF#Gʈ?Q* !;v岮|Z5kkySzYO8":tրye\QMvlڴ W{%\\]?0DM MNN2(dXc?ȇgK0A:wyX-YRH;@@]ᅣWGD<2H BiBX$Mܶm6fE:sQgrTjllڵO/ܶ2QҊ'q?FD`@H|MQ.s\gg'b:66Ə[r [m(ow?k\y]tҚ5Mݶଳ˯9-Z{a[DzUU=Ybىxd#',wN&77l o00Ș0&fQ_j kP<`exg?B+G9+W}ojާT/ȋ#HNXE_޳Bc_[_\Jd}@C\TRzTuP&XNTd xy~]Mz:jGrV"g2Ff~q)|uh~7ɟ/쮳cF}-%V4GQoc4Ѥ6awzn.^7nl s/v!a2܉>mlzeE fe >WH͑W7_dIKvMmp7y̟U.}d ecD݇'8@SbvuuqerRr:k>XɆ9;>YEq&t@@'.U ?Ioo/#p8 &hŧqL6l5֮K)|̱ifx.^XWAaIEFS$2\砹˜+U:P9AGpR=f:sé$OxU%ݩ!\!DDG6t ?Yf\N&80ciDaIUy5FEt9 ӓiƥsY9Jƣ-1vwaGMJ{{;OKA!=w{ ;9K"}G~{tE_gI{]CWdm "6v!/JBl Snoum0C[o3{ǟ =fEHԉPOD&0pǨL ͼ#ȓO>XӴP(2m* I{n\.; `]EA٘N6L^w1́c󜏇ӠВR>2T ynRP''|r =nTw]jG 4 ʾ"|#KAhbb`G n.7ڰ׿^2|wC\sWŵJ!Oodr%o7d5*ݟ岈G+W{e4ad(n[xoFivpyy(BlLLc1bv `I.CJd#@ ~Xs|Y=kx6të:#(`v5ks=wMdMJ,.>:'|yM!"s t&": .\fL:KzE~4,# /.5ajqɩ#N܏BJ%k ;^7\GVMrR[iCCCH}eY58 1z%+_!\4U ;F]]]Q,OŃ W<wQP ~ OKgi.x }O8׶>_Yqt&l޶\$z&MSOM&eaѵ ZEE@G<4=L QC%S嬄0G1U6q,xi Y!yr  UY$.|rہb{#qLF\\+|+ehj.OmWu[i{%f$&5gZ6[-r; Qy@x@"N _]Tt>|zS/}rG#cFb{{]>Nq]+54֮mYg>gUFܤ>9aL'EAY켸cǎ%U Ą>8\8]V5Crw~J67Tyt TO3DLlێFbhp 'O| HGNH K x]eL# QqHtV2vi'8c,@WF,ʜ8:R3|da%#P $@q!G Vp6G%9r\cwxs!Ŏ]GߪFcG;d&A+S)\:|<ڈh``d&~ǎDk yz_t3G9xRy2Q]޷pO[c/Jcǎ~"*YLRwwܹsy֋YLjP p'>U'BM0;?*qTbpXb.%Q` [w#8t̨" F`͈6a(^BYb02 H= D2SR;zK+9J.UEy$5q`CKk b,y%Qꬡ f0ټdS+o|$  0 *RЕl6 g\.SO7;>*3UCRcuNDd6n(MEr11 ''Bo~W?su ß̻?-'ԏ7?<挑?{߿̗3 Mɣhj[e2ڪ :0\h+No+\[AMv__P#L"cٳL&9|`4Y_6;I 穱}vv640 2۶YO=wVf]@ {g/ً#ު)υ#'~ݝ5M-n Lsvi%d̞?_Xh}nVn{;GFuu/xao|MDEDaiƋD"C $|B|4|Tf+pxmI&m{Ap`0s4l1frPbDן.1(7u ǟ'?g3.d?V1#(ܪ YU HJ[&1北} b6ݩY4𦦊:sį$Γєo~;v<rפ=. _9YKcy-` 8`Y&ƣX__&uqÎرcM U2'Ow7:|?c,뒕|0,ImOOv}Gʖ*/4.;~[(]/<{ካe:n\$wB1d8Š~9 Foo9P3E"nyϗd%iʱ O ;ՙuśj4#k—ܻly)I[XxeG\᧳~eN8;>`y%y#~g kt+P8`httt۶mD488ȓ5$UP 1xB:eYNp9AVbGȻJ3p#BNtοۿ͟?kyӬiF98ZO%$cU.BZhЉv@UeBm%8K]-#ٿ/ nm Xg8CP#6%V"9t ڶͺvv慆Ƿ544%7};;u]~T*YwW767l^Ӽf[ݶ?ל.~"⮷S-U%o;͟(\#ݹn,W=5Mkj*who<[uv|B67P(DDlODqBSyb(u(T(ڼ# -,kgh覐`m][LSOjnY W} n@ 6B{zz;ingd óf"۶&D)KVS,+̂U-BniNy'KَܶT*U,eٮneTϫ.+AE=iȴcX_̣+G^:4>g raIvZ[KR~*ڕŋa_WMя~ĺ~&1/U>lsI"x6\T\V¿ LF_]Ys:*lWm+k\Iv|25Ϗ~3oDj:W-]]~7Jz}ٲyR_Ӊ.EË /Ix[<#d%{{HFzoLL$0 : \e@EQ%Ņun8˓KZCm 2>41V Z4&8Gsܺu+'4 \"555q.zABW`H$%mCUoT!u]wGZf' d /Bh4أB4x+u(ȐԌT%1m7QZr^1;F"^FIN_ 5a+5aUQ\._eT萵tzbbbxx?q|> לEq60߃:(H- yHD"6i۷ooll#;3N;\*#VT]]4Se9Ato˚ T*u5k2~ր/;  _"%ѬYD/㥄oW)VBUg(*C8䓷=Dwۼ9y$C?찁!"K4n7ƿ|/Y}CG`ʥaam6X UJ= U=m9V~)Ҝi{`(d1jg~qU^e$VjKhKY+wY]QQ5HU$*/sܒ%,_8>$"`HkAk\(FFFlBDD^"nYiʨMu(BXK R WkQ ;3ȃ>xaLAT@$"JiZ4d2%0| DMS%-d'ûP(NW<#\xE$, 5ׄ,l!J Tړ===:sx=Llx_3z~[x)Rq+8d d^ гeȋla;L[[#U*y] bձ~mÆ ###$3HhylllRv0)Dʧim$k[gsϵnXr?W+pUsBPӋA*ă'ETǣO8ၺnxM#@hx8Ғe.@n™娧'xqQ*I]kS3O))|p5Syw#(D{="q*nUoj|ѨhфVWȂm644߲*쑰|| NA8Y>f% JJG# i,Uj0M$ 0%܌LRѶ;PgNF P"=!Ex<wC3YQv&R.򗟼;oܲÉ+ߝ+\{A~ۯ6Z=%h,fO$wC[[K=6V^\'n|/I"ҝ]3tp0NwNMje{ "2RA?FD>e-J_]CKԄ<itZI\55A:9Vs\*ܱcƍWX144dvSS\/}5.\|0b5 oddmU&V>10*LG0On+H@֚ㇸP&,J5M)zMK, /YDT__IDMMMxxG>eYvF\8"(SnȲ28QՍ뿞ӟl_~V^wFG턖q-T.3W YLy$%qԄE3u FnY(·-X_69L{/kY@`qAS kfcӦ{ߛG~EY+?~׹? M1P x-_U\ ˤq<4Pnе]&Ţ9ɤSֱzyG"i:cǎ^n|ֻYت3_1,WJ,ɺ'*(OBdj۞:JM 'wpXSڨ<>/FQ^95@1b$C5+P.5JP li. m/e|FoL&~voH$ٳg3p[{ERx:bh4DrlxAe0Â=B\Ty%ZIc(MNN+?}tGy䵵pM)(K߅M!:k< >ܯiM$Dյc"Vep"*ˬۺukcc#555녉DL\(o4Ї˗7O|K_C%;.! +Zo\q-U;b%VLEs/Z[ WF 4v#WT-7׮ 80&:쩲;\SbG/(tjX tBWDD:ǃDdPR!{5\ l6;00@D۶mGmmmcD*4"¶]qe_v&֭[n>β,vŅ airP((ШL7.b Y*o G62FґyG\1֜ft:=>>><<ت^S[GGGo~󛯶y 4,#%ŞBIRD"Ng2l6y"34Bc] [d+\ wCE DQ ŭ&_j:7N>ߨiw]pGB$ ǠuV݈斛߾]|+J&˩>4Tq7 2>n|^'ˋ(G\j|o@\xΧQN?M2"8x0Z\B"o!U%\W4FH3e08::׷vZ>P|]BBH$:;;׬Yq3(9(hH/DHHy' &A[&aV5˱%{#`0ơ+4xI@ws#w6A#F4{Gu5%o+r\**(LOrBȟZOdr%ϳuww뉈SV_ V"Fߎ8X0!:2|%MsSO3&>@s^jw]AeV_.q/S&`Og811a˰PFScr|p jyK`O^ M[&lSO{^dd  EЇIJ(m* h [%^#y,d`eիWϞ=۵zA*)=X0>ӳlٲ7)C%+O>`D B.b]$^A6eXQiswT*q7tKܶmdAM1.ڕX ajzdO.Sx(qyЁ48h krgikPAsaGw-UExj6S9c1sJqcӋl.,25MU+ˍnٲٳ>X,.]t Zm؀\Y1[As۶~iL=8mXʪL۴dȵ# 9)yME_aGy]èPIC^ p;%Ʈ.W0˲nݚJR򧱁?w t…ch[mob&JjSUBN%"] AW%%5]X1˃>O]4\uv4K ˅I3'rГpQJ𙾲V.i%]2[;TMx3oQhd4Od=|o,e |rZip08< T 0G 4a"Y*ف="y8OfB@R`s^DC^qykVy>ޱT*RBu֡!F_]]Oj&(!T M=fCLT8^6v$.'t8{{; #6UZV_S}*J8$k677Bu ؚ쒚^/'';vD3,z顽] xY+o mr]f}>Zqz*+fcms`JыʑGe;ߙ=jR7<|Cx<Χ;wr \{lhbxE^,+3>Cny+844aÆŋS8&864cPha3:DM@p&@qP(B[lkIR|UA2v壂ض-M ?1Κ5ֿ1[=6#aDC)QSe< )jTi)WP.!wwwQ2Lrp lhYV&!m۶q VL0!aԈ!zZc-[S1I=)OJٙ6H V 7F%.~mۂr'n-|WJ%Wi"\g^`({G95l>6w;L8M>ll7-κnđ]D8 F477sX,1yrf{G׼vZ6kur?<9L܈ȶL&ҒH$8M (v 4[&g;.UZ6yǶ6 U*bGSE"N:{I3X]ۜ ,¯*iZ|0qIlJjSv8k^6К, E| $r`vB\3gN"ƫSM^MQmTy`:aǘ*B`gmؑ`:::8i<|*W0Go޼bV<$B` u]d[lٵ>p 8.nia,SFJU^~@k\<8w?XYu} z{fB  'V=pwZ8MMx匉 Lm7~7^#[v9O5CABF ;&(pOǞm>|"ajW^9kz]˪ (q R)3ζ:"Uy\d4R6ܳl1QLE؞F^Ƿibl6!2C_|]`)f)Q1ĝ+3 p$ˑo0_&ihhؓ둚z!Z62?JunnUoF9w\"^)7agmkkQg&{~in/|I+M/Ew7Zդ&{B-[PIX,ۻm۶L&ÇL[%˶/ԱJoGQOb;O BJD`zJCX }؀\JjCkNj )J#qeήuY4Do^p<Ǝ.)kiM#F9xh4 s5y=*)nDw4E@c!VzRznouB8ihh>HL?Y7TimQOOoq1򾼮""n Ur]hXj:F\^!P* erYzHOD s+E\~s :AWHh`Ȝ] =6[[ h\.m6 KOҟ$!]ǀX.@Zi* Z#R >)j}>_>=\7X8Ƭw$N]Z|Gӓ ( ^$6E#mmm---cccD1i lo^__OjwWZ7R`zb`(`v.(qfju<|U-q?Z~Vap`Ny2;8ZCi"eb;YJ'v[ijg}>Ӷɶa].K^{ŅU6]oN*DwU8\rIʕJۈE*#剉 "Tms#* D"df+}x ؑWR!{1rSC<Ś5yʣ>zgi_[4;/J;w4qx3aЧT*q ]q)Iq@G~QZ.Sp ]õ/N!% 01NX1IiQ!Y>/sXrXJŷ#t/ 3F<Ԇv$,p%<ŋd4_Wl̚ԂdfqvE5e<,Pn1ƕ@_ۍOY񷿝['4WbԎ.//1cL{٘337X{ҥtM:Y!z֐R4>>nF"qv8ٶ1JIe;Ep؎E]}La$T(vXB%22#ƺR۶tgr|2 wj2V1PFs /2@dC0BP"7 DR]h]Qf``8M\1>ϙp- d2bƚ~ٳ~֚ЦH*yUkp.d>K);}R644ttt:pɧU"!CūC$Bb(0=zz+`neLJUj^"5ȕWlt+W<9[瞻oH|>W\l7vUSJ |re;֬:˚S8l^_q IDATR>"(uN]&}'UD~ѦP{NCT헩8W|q?{V5e=p`",kѢƍ{lGfl 2 gf14,*¾eĠ WNfҹ\}e)Uy6嬯[RX7 $+y"ϰLW6NN))w RT +>yT9w X]]j5TRn< ,iGq?>Mi堬6;B,NʈU57%R;6J2x|37EC|Yi6*CƎ1,|I b~\˞ŽS-N_{Th*C,a+)r# Fۛ(Jӆy'bțnccc"`S[el/څ{]Wz]w:;K<GB#f}V?v\:N8 bZ(kdј-t{oO$98yi7GB!;2ea;5Q#/t3%*riVr=r&N;>_@l@ ]-2=Cv>CW FNCCCnNNNa( n+mmmɻ?tO.`3G$lFDlsmBSCgMtr'rbS%4bJ^ "b/^ [GHI'uUcrn;F2|}rr R_~AME3pQiӛ<q_ver s.D"DQx4Pt᧓8͊DcccCCC$1' T) I-ol"%E¨/@/^,w^"f__Iu uu)}iJ69zh^K~k{4ǎ17vzkg?OSN_p`>xhΝb$R}RjyGoq}dȕpeҼXdJ]]]$qL *0#T6j[2D\qM7ȣpT 1d2tte'f {u.?ynGX0l 9j07]mOp<O$l#rJSmwf@:cA+L *'po K4P"{\g-iHt y'WZʢ,|掎"5x\.sަ&,9@zC6 cX(dϟ_bU¨o4e҉^^j{⇯񞹩 312{8l[omloqc^tU- Ξ]а-LY^&?c_oT:N4-}(*zܺosyJ@mf>2rjttXk:B!^`ƣG y`FKWu$Bi&uG2%`"744|>1 eT*]~j|ݧ2Hv14 XWW+gRk+Y/C <\TKi* ^H$8NHQ N^bGn ~YIKRa3dGvllG 3 ǵn7(F">6iik-:˱",d Gp $ H|>|Hx{+ x"0~7Ѿ"jjj$Ż@7 諌Ţ?lNWSlOIUvOӼMT|zͪ;DNۉΆaп/wֆauYg~+sﻯ;a3Q^+[֮'-uua*y}lOgsfv|(PN* 7^u-ud"15DHjI1qx܊D+-8iVUPTTvTg_L&---DT__$s}^<+,?1 I>U05yISSGeC\.ǀ}bgVS'-09R($Rr/rЄԌq N ` ̗obD'EdԖ$Og n?]ad h >rK3ҥKqpT6}w` t{KFG}-\'2ȈW՛HyMw|_姷{÷uns-ݚ=m|8]ﺵִ|ku08ETn:}^-%f,͚U%/+V'&ro[Dp8D e'3!Q<:uX,Jޝ+9<"M(]@4M H$HضSR*|63~6c`ԣ!gM#J%!c;@ 8N,㖄&|bygç~*$[(2v 5^Y/&I`G%"1@;Y5M7vtݵWޠ*xBa|$a>öm~T5Lkk(jPe(P( ){ɪ`tmSi0?COꓭVŐx2_Zi44]?溹j* =|?N>v} = a'Pڇ?<[o:X?X3w5al+% c&xv^a9Dw4tyLmhZw(d;K>{yGI-ƿ8.hnh0(-Ck GP*=epppD4<< .2kyj U]CFDHؙ ;"ԩɛTX E&,tphh\a=IlbO+V,٬oRB`p#H>r3Uq h49PX,+<#HV4|P(q(J3'e*uFbT*;ήs{3I! &EDAQ#4AD dR&I2}^Y{dh ͻϝs=g}^kƎᫀnD]ב"ϧL&H$bAd)ۻ[쿻qhB9 kfͣci#T*Fp.CW_56$K5\GQ\755Ih|G*|mC5#un!o]L eJp hعsG#[[z]Eݚ{vjU}Wu:4M;+l:K_Z_j!dKKF95wSMDtT⨟ 7Еd+I3.#wH&8+^)I@FV#diRWoP7#bqlLmjʫ۾DMy`Sm6GJRp_i,v݈nhh|6P;^ZEw9t:HSUX,0OOko[WYO,Z96"v:d2p8XF6)(bԼL$gt,#cF؍ iEpnGp\.+/80t4X,fؕ`!Պd2\)HF}BJ8ebӱ(F.`1 lKǨ ^%R,ab|Ġ(֮]mE*6@zi!TݣЧБ벁=_za6oMg|nsD/暹oU+-D4k8GkNOfvjOWQi(6ȃudF1S7Ax gqLQ0Rqd&4 , :(eܚQJ3P)(v#!RV{URT4x<۷obbL l?.P'eX,6W4ā97Kͨ}( *FGfSs%Il([@v2so^t"g>aOG6Lb7[(%v PD0)a ,X@fD¥MJj=BSDق`Q*,VQ^Uզ&[Zv:w-{[L'u k|ޑKΤs8 z˾mwAtC Oq[6m*O߉^w<-| R lB$n9YO-=ʞL73F|iEMcC=|m5E;/xtb1QhʔdQdܕwbmۆX0w[iZ"S[[VZ V\.xFrie†x 7QIB:~:CF8Zr8-K$=3^F$y(W@;9n &02UQto'!PVJ(YP捌T_Mw7]lj;Zn~w;ƻmIOEkЃv[dS LvL|G4u K%l&CqB)"ELMM kldKB\.=Lfz ބpSϊ?\އ j=D>mrrxʉdSsfL2̞D[3W,b|m!Po'Pw7InMmU$!/oa9[ZOޔ*اATx%;^t?H84\ҥD&plmۆjLO䈆ojj#*P t$`tXHЮn8KE)BCe*/¾+&ՊhrrG 2$(d:c^"H___KKyjX$* tTl*~ڀgY!#jdq!X5!9/&uDdX0aC*\(G}Qji &B8GGle: tٕK) 9?\)-I)G>bޑD9a8WGj=VARiUe{v-N4 D"fG18Aա(r(!Aw(Zmm,]ssIYz*E&X#Q.ûZw'0M\M$ZTT%` 4djk36˽ nQt/Z$\CMEߋRj#&d$T)! {P[~>Sֹ]rULE=^38=7)~)rFh4}v"ںu+5خTE#b[[[𹺺r`p`HUU DGp^|hU⿕`xcǡTAL@!bbdQK$v$ca{`QN Fш%#}lVEiDX3qטT@b=#9B2|Tt]GשF۷766z<\"̾&QFDrxu&|MMM*[ะRl6:AB?y'ʘNţ.@; zd'n577#!'IӼ)Fp(ˁw* DΘod)躖)N7EQqEQP(TUU:h"bQ7BmXSqKةA$#9JCg +rBK+I3^7f#~'_H3ˊZ*̙!T=߾ޱ5C6yQmmf{{c={Pwwۚ5D#D439)4bYޱ|E{Rnt_bխV.L-es>ujӆ x<,ikz8L>Cddh0, .dFFD"@?nODZ-M*?⇲@g"F (4Sh90nr[lc E4&tsdSu<w\~8f,)X\_ MQZ̳l"8#du6Hl޼̊Jx5oL_ɍ$ILJg2&6n7r`CD8br5AID,!6" Bp䓡0 ncTz6qrUU4:IIͼ+eIqFOž%WCe>ӷr&tJ?*{ .ohQxH ={~xkem׎d:TWy,"L$E#g&vwTUIw>p^ͧԫ/߯}mګW^uww#5O:!az#AUKKK{{;#R0Өbt#e*qD|TJ25cC9hb"3nWO H8'(B-#!W IDATZm-;'n8D" xrM0,+̄*H?K2v,WdG|Dadp8Lw{XVׄC3V%nJ邑I3rk(AL:g{3~v-y / %%{O=й=r烟fT<1ddrXlzXӑ]&O`!)jJBgoVyx>6z,iַ)kxut>Q(]u0L ۷P7HV+L:uD1Mtó/gsd oZ|¦j1xD-mi'>"HDȓO>9k,C*_#7MdLVYDL&DR-ǎ$*T* 0TQeK5iXh"A)V"54 rHhcDpWUfWx,uqjQP(TUUmܸ3hp̼S0=$+(ra$&O A3C… HR8`0#Yqbh04d_ ꤚ+Ʈ[-[[[z=]Uɡ96{6G.oWA)4O>'o%Y?~ 7׿;gd[>doml77R'=p^bއW~><* _϶X,TqlzGkm^w]wssbȧOpmm-T|7ϒb8W^-.KO[%["-M8eQU5L[˅7/JAA| D>-Q]zE$Q ȿca###h@2|dш|]$I-N0,ݨWnua@d2۷o?ØKiBau]&#"%a] / F^N!"$""5%422&pqՊr%7s#y$V6$"χUI>1 D42^sV.\+(7>T7\zɭs$Ge$F"Gh/PC5SP Nilqkruqkܥt;kVO?RS5+(};7WOwzцC#>`QK8W{;"t07:B=^J11)!o$ᏨibɋB@o1%VePbB*u%;v"2Ҍ.r FO(RQy ) (hӶzb(J6vVʘ#|>_]]n:XρCdfdTUt2164y|!TE%vgL&ىsLƢQ)WdOU@gAE 4gs  G0 W.y$! JN^t۷oGA-)Y[-YYƻr Fpڙ3﫺/f]zmCM0Z̨4 ~W"# r)zTMZӭEj5;\; 2==hFD[7m1lJWgk…1;eSFN4;f [`wF^w}l{Oz~n|rdY~>ÒMXٙ+_袞?CWwM GVQKK5on2%¦d~pwu H0sٳgQGG0@"f@eH:2G"@!͚lF|*[Hx8VR{ɓ'#ȞH--- b F*o Tx͛73ÊP(Cʌ) 866Fqք2Ys2݀؄K|? /IRMi>{zzRE ,r;ΔH7oTó\1;2:7u{ѨaY~M&! +Wࣱ"(G#GA״3VKSƬX1u԰Ǔ7N;7w1տaM; DЯ:~[ow̳y?LCLu^uSPCC?7nכssN9el}DnȧDwpAJ̱rJprH`"a`b"/VUQE vڳX,r*rV:|2?>D Ӑk\vOߑ & $ʑ(`[[[ !-Cj6m d𳐱UBn63 L&M&-݌ 5#H]db=؞1˞77rN[BRSr#Cl6-P(`΀` ? D!/xڴ}*._vao46f~W_z#}^N&~G%ҿaRaci<"Q<Ynj>Wlxh[n_͛^uFP(l31|zhhhhƍDȏOl sgg^{EDgFV)@2|URlG1 DF9urI8 K7Gӑ!a:R~p_ROl۶ ꘒrMkk׮zΚQ~b1rcTT { :>D%Ddf4GJc9iZ[[[|q;|{!OG&ŰV8(&Q*G& =dhrqN;::LWT(4TftUHf5l޴70o;ok=zW]xf蚽S{|7b.S-xQ֭ӟ=PPނS-nX,_uUW}}by~Eoa{ỉM8d!jg}?roӽ7OD_㇎_xzUU~7ښ{"tȫ̡R$hFn-Z~zZ/(Hx*Mc n'r"bSYw`3:v%CyDUU_W7Z& ':NԴ~giD2 dOЍ|y^H&@`%ٖ#0^c"Iv2"ը) D;#J}-FhLr/ED>87-իSq7D8׿gqGY㎋546ܺվ듿ޔoȗO;HfqssňRKxկXPi&"]IKk^V)TWtV-t2Qmm y䘦i9o&·aڴSO?\kUZUym6m7I_AV|{ awl*eB8m͓o&ӇO?i줢R$~W m^o 0f(tNAooܸq֭DxvG\./YdD<$bY$ͭ՘eX8-r弣i|]CȮ~"y衇+p/k+ 5Ӷ^7űKKO<`2~.j$)AR%c?/y^WZhCb2iWUr 6- 6ZV,QG z@*=HUuk՟w^mi^qknMMTj0gTw]rFN/zͩ߬->ˊjQӴ.)%,^VeDD6l ͛7#w=GЭV+'Z[[/^LD{Rc$ow#<"&W,: ai,nUߚYE{"&cO xO=TKK \01& Ē+rM˕S$^AL"ekFy2p---|Y jmL?ryRYndlOU-nODE Md8666+P)5aNRbc9sUcrG$#4g'JYF_.Ҭn:DJ$E"F4aykk+k@\7L<oӟV\q>t$.8O*'|nocdzrn9L&xS?+π'>oE{Fs:/yMOr{lWyG֌f<9u-?n_ow֬YtCs,֫YYOXRT0g՞323~@D|mH?L"!#f/?4Q\MvٳgD.WDL&XE;ÿŴHr9GrNe˖ .=؃[/~ᔾSdSs2 DbGf\^t(k:57$+j4VjB櫯/vt|?_;su; Ja6CDdd$d^8&凫vj/|!oܬifF^Ù,wuuц 1.\HD(,\N7tͨd1Rk`0HDHCD(?@7poΐ瀄sobP(4k,6F7">#ˇ|?W+!%Iy`\.!d- [KC,F((<* sEQyEqEQl6[6z&Au)MFGG=J#( Lcέ[].3?_s=M+W b}}7SSO~y#t_}33&wFvpmt&nʨ5fP&RU;YP [Q>+įRUn1_ݰbiX2kVgR*e s$(n0?>d**;_!p?繎|;@|s뫯-lih(-XQBB p8Nwuuuuu|d;Z۽^{tAmmm'O\򅹇pE>XiT IFGGd0đL&bCAQzLp;ȧCFFF֯_sCtp0/~<*'H)?b:Y%% IDATmX?sfǁ9s_*R?`0k^ڱc9Mg}(M.;$t RFITaaG~d2Z[[9f%P0*R)td2O<tyWʘV}8kNWPr>Q6)&"K-66#SP(i&, ~zmۜuxqݓz%Ϟ;昁e1v`{m3oKlO;t,n+ix-z9ΒmFxFȈSQh 1 QKm(i.}r~_lxSNu?YVW _ՙ,TpBeCK_^"Uߏ|Gx駫c1M7Mo&6+ Ua؋ٓU"r~k~ѢEDt衇N6 _qiy"Vm@u,% #EQ+{OL#QJ+ۛf~b"L:2uM6ax ~!'2r&|ÇO6=X":ߩ9yyRQ&Ax v%QB AE.]HxrD~z``01膷j^`JDfiD„G孹i( 2K2nLQf)9#w5e{*"ʺu4M4iNӄhڈΜ9_omm^Pܖ5#"_R_s,8 x<>66(_5“N7I1tjE 8Ħ,s@r9DANH$2q|"{9z*g --}nS4MK$?[}[-n"Zi Z|ۺf~8{޿dl x^G-$]֛nzm'<|syԼro˽V(չ_{{Ρ;.]ip6gNG˒iT"ygY~j~X,r rv7-*j.P("W[vaYN7RAD|wƎ-pbh @\sF]3| ͛~q'HvJv]^\3˒Q gH6E0P, B`m$$w욦%ɾ>UUD%c(UV(Qw]l e_6R@w6 =P1U74ýC---B!{<1J%wؙPiPq:.+'I[8{v?|4x駛?C?(tЦM]._ic@`vd?+b|I[_׼|Mt(tiSDTJ$=؂ N/ ȋdO熉hݺTʺeۦb]8رc?\ozVn(L&ߟVZ544{PEv|ؑ]D+D_>ollD;V,K)H$@Ɨh4& Fâ(ߤnEvX`Μʕ?<ҧ:<*zUH(YnyGѽG>ȫL,fomM\xZy}]XL#xFPp luUɷlR=sDnwk޶W_4)9ߕwuUu, / c5dDbM8RL]TsQA)\tan^ Z,\xa;#]ױ ĚRu!LպB=>G}ԩS͛J$"%~LM@ )Bt:Xl6! kpDز[;*r`NJJgΜJ  7VSrGns|M>bZz$M|HɉJl@ m۶ӧnHHD(21vܲe>x8\tnY>ջd&H8S?rpɭyx5& aD|>\Ǥ{+,=lY82wn,._oh=z+_tƦ^?yގ>].X$?ԁ;|<䁚RW^p3?_kCvMϩ=ǣ{ėjW(T /r@'I6+ m K@|ɓ7m422DPM4,0(d9}4=LF{El!hOOOgg'@t}^2+A 6[rŝ Sl^D v^m)F6OGQ@T p0ҶKf B8~ꩧhzv%d+(B9HE$ak(jDiFL7UUU&Mv摭eu >favsb$b9-I'8z}};<mϷVZt/ /)Y@ɏrr#@34M9  M5āx4ݎI&GB;WVٽzjlVpgm6YY{h>C D2PG\o/i5lկF8 -[L|AX,R7|sdddÆ 3,w)J.^c?>`\!f#"~b>ts,G`TLxPTjttt 5nSbp]]]lXG;[j䞔tO22Q#A'ӫ (5LL&I]&wS"Z3Tь{nvCA)4d>s66WĽv|?V=bSK{=w_{5b:;~6=뗿o);Z]Td^:*detxʵtQ[Y1Bv{gg h0^(-B-S (¯\UUa/ǖ_ik}na% Χs;M&k5ȑ'gN㑻 0)i]w#XE>q#}4^? h38ݠWR)w7z(dV9P' Qgg'z_{5" \#\&r>oh */FT#u+VX gϾ4K_^2Id~sLވ5Ukc{nࠧXTneE$ΤRLO1]# R"Bh0 'iJc0cl٘m C=ON`S>ǒ0W}_uy ?a }tRvh_S9&"(G<,n G"Z`qGD .TU5 0F跢?و ' h4O"͑spp~SDضHqIJr;r)F{h" 1PKknn^n]sssUU #-Z!jfz2:Λ7#..ň5MK c_?^)6Ez Cb1ٯS[œXM$T "L[) H$$ `GeUg)xP*XLHAT qXׇEHU =ADgpk Hmv |njD4+8?tk~q}Ԝ}vqm疻>sL*s` =&'<#FYg}f"<9Р 6nH*~8En s7.$*EֹZ,*-zd-Z&˷.ǎ&']Ř%vAYU^Z?:j=5׌r" "{׉+p>kA\.hٲe/Ɉ7Bl8 " x nD`&#_% @F13r)GT-ջ9"ny;0NchFmbԠjԕƇ &* .0ˁƒ;<$ԎDYjkk~L fQ4cΛq "ˉ[-Z4ugӨHE*[^OR&=)!Ҭ4`^osssP Bp_i` ٫MGf=knn> 豔Z(È<8ј.ƵJ]M1 8X,L&HUU8n@7RxsC"]>v#Olvu&2(NjSGacO9Ørgt]G쎎6j !Ȉ^:MDd2 n;Әh3&.87oU\2r<{IKӾ{SM2udr稪 JbGG==>UUn: 1a#'{MM@2ik8 x<%h3 ?Z(L&mFp?g@TÒvHu-3xxwmc%S.yg9tP D&;SUbJX,> |)vd4h[LبQe+ PxW(wLw֮zXQhZM?(ݮwݭ(W;~UWBW_>"#^:dȦH$BD7oƾ{ 0mxv8 , OʭX҅#7 cGH3nUHFzk3~d<|L&4$͂`<X,@fd >8kz}١b=VXڒ~bk'U#[,k&aHD/y^QᣲYڴ74:w2S/&]בD:o5Qzebzp}byL]W[zE%7w: !U9k]B/K)` IDATYurn$I8G*F w6C1"4X BqCpގ]4q{t]G4Da{^K< W 7-[[8>ct~?;3ۤ dC0[r-W?|y}^ZT|Ꙅ%~.ڳh7vjYlxBL&GGGƍW^}7xcxxFJ+8H-YO={%KpjP .*Ӎl•"_Lfll p8 u4bd2N#S\#mMqtt5kՑ hp &,ip2 2T%a+ kNӡP_"|xB0Ӓo8MDH: OODf$<Occ#>i&l(*epMDE"7xqDbDT]]U3 uc7<`m]:|ɑƷfo'3Ϝ=a D\1t:nns{i^3IO8Ŭ%7E$457FM~UU-RQ#mn~.6P;,kmle٥A {1Dcܘ{n]>c[- l,Kg2s?^9"*V/y|sΜ3;[z˶++ZQM͚"(`V(u:{/WZ,]BPdx@ifxRN`01[L&5]5) |Q#W|(,,Ē_+..yHѼTJpc:._ܝwcAMܹ366cG 1ʹ8T IU R ]Mj:;;%JeRƃm$##O qqqMJJz48C{PU'Tvsh֭w"}aO'}9;(*j|AMO>hI1CaH鑪:rz}:t:ӧ ?? O⍩Or9… PZZU"""ɉܢĎ[/\Ì6mB>I$U>wP{BVmz6ϸҸ>}u:3k"jPkm_s/_-[·*QJWJVH JD;rG+qʊ/e2 < 2nÇ ..Spl6UʎRgā&#BEEE147 w$j%PucĺS999˖-k_F\\\@@}:t?"H 8R-$%q}DE17qhʨ`U>ّqbKŔ)CRvShy(DDdfwXޱGY%yqҧ>^Lb/ʎ ";vL # G,XtUu5M&FԴzTҞҗk=m_)) iӮ Gx.]PWJT=җ~+}?qvvF0Ɨآ"łԘ]-QRGG~pȑw*pcڴi*-%(Dy.* ,zUQ#be5 ։X]`0zF Zɫipi<zL5=zֳ?Kj٤ٿiIS%444<<qV $YK V޽.2ZС쩧:Q] OiOU*KW^k=V8vRWv>>? 2ܨ5F|wXGgX5b^pqqkG=@wyB٩SKe{s|me.]- QZlgjE3&oʔ{UkꟀO\XX˗/cۼ<\p/O1J2 {Я_? O^DԮ-KD|НF#1 HeeexwA@'u#U(h;3tso1y:u>>>~;;;SBil\UyB*U*RGHt2W(Μ9/܃2iҤ-[j'Mëip VS7k?yT7t߃0%ޑT9ASIF5"udցHEi u\Ui514300%www||2J~DmxAj4`6F# HJΠL \~y$f+0yvڕ8+Vd_U]=tEtikn۵k>T*[`jv+/WhvZRtiF[*P!%-8\;R6n4dtbNiN X oi_\{%\d:/-+ZW8c%0.(2@fffjj*Zsrrh*㟴#OE/khhh~z^^^XZZJ}gqzeTMd //F#PyARu&nX,kAKe꿠zRϤO;СC* M(-g4!H3F ,4 ^O1yȩO!&X-((ȸ7#,^xر^^^4HSë ScazّqKn]Wӝ,ݷ%?_Z;:w'?L~;9CXZJPKݣUe+;طɈd Bj &wȑ!;OV*G5k 3eٟ>s\A ƭq;ȪR-4tdUiw(J]G/f w[4T8@d2|YsΞSwR TZ}gϞ>>>xΐ~?ioiܹh4”d29DHJŹݐjYnOGegT]ۧ7)QQQxo{q* P}!pt$ q8k r P(]G+vff&VI*Jfpp Ą/(jFΙ+m3?2XW*9iZ<\VVVBBARgM궈P8|T*7cRJ]ؗ;$qp3T^zsUz*j4m ' լ>5R8y2`1DVQ2LFCз@5GIw'''ccc`LDD^HW*ɨXpСγƪ`RrZ3ơT즦[d>&;2]v#ÕJ;J?[i~ D/믿U1Y3 q8 m۶f9##Ca6JStA%Mh7nxϧ`4$ޑBUeRGfi?hC8T74{`w`{xx *fiB!B&Qjx"ԩE#LjQ0***:}4`0 p@0`0 $`0 1dG`0 F}3G`0 F=A|Ĵ4 pwҗ`0 eeeI޴iӦMix,;xjHC IDAT1֝e0 `VrXw q;`0 x(f}ҥ[rggCFӮ] 6oݺYf͛7wE+I)../`48yyy(bq~:uj;v,?? qK+sqɤ fee͝;^y???BƍM&CCCF}Qp֭[^觻vڰaCm} ILL5k`dyyy~incKN2z=@`/_^'OLNN0`'ƌj 6s[_`;&''k4Ro߾}㓖o>Rhk g^̙C?3g֭[k`0 Eqqĉ?쳾}~={`A[1^k֬qF,_A=<<:hРf͚ 1''GP(ӧpڵӧT*ZP(rrr}̙ K,"2 FCzjBѻwoyzz.[`0Ϙ19~O?tϞ=3<3tPx77oaÆnݺIy7F#Zh1lذ~I&/?۷NNw|q`` ˖-;#)J@>͛W\h"Bh"A{w FoL&yyV0^y<;B?3%HOk`0 ťKB[׮]/^8k֬ &L6+V[wyg͚5ZS(OΟ?OEw_p+>Ö-[[nƌ'N/l'vǖ7x/7o޼/2##cɒ%k֬tf?njC{ɓwuuE  ظq|O]vݺu`06ˎ~~~ 7xI&4G'|WDV?9sv%Bm} @Qz㸠 GEE9r?>~l^`xرX?C_tQv$>~СÇ믩 yyy#شiS;9bw׮]}̙3 !:t8ڵkl* r 6BTbB"(~o̘1{XXXA rD׳,wlݺ3gV^m2vءP(?ߧO۷kZкuﵴoJJ S@JJ dddї`0#G.X`ǎ{C'xBŁ~.\oEQl׮dmb_C35o߾}С駟V+~:bĈzUVokzۛL ??ݻwwEV_lV*{ׯ*feeeP^ (曓'OaƌVR\3zǠ]nٲEPlݺUPfTTP={DZlowoR+:kqLq# (<0ĉ{ ƽfmݺ5((gϞK.JJP|Z\r*0Ҩ -gS~7qd6!6Sjʼn oӖF;2G=zR 낣C,>-j RP. /6k맙HDPOL:[~]{u쓬8dGF#Bt,|ܬۃ]j,HҨ ef~ !@D"H#~c䘠31$ T* W "_+&|u FEm.Yv\.#Ӗ4w/R#6\qg  H,6jTXƄ<;;~ρGTTT;~ܹsIH1T9qGjEQ$bƴ4ɤhɓ'O<9i;n?w\XXZqjl^r%77\3 AAA4>^W:9;y{{׸OAAh kcUډMp $#c(JYc~Nc}jUdtٖcMCå)ܴ^faP(U5Ɩs|4^.wr~zŅƽ[zݫ}l6ѝ?2t򩗓R.'M{BBDB.@l,Ҝud0e=q(,,LMMnT:g?ȑ#IʽoE]9e6ϟ;۳g 8-FQ)JZ5oT(pHk67nXXXزeKwwsgϩTR}ZJKK !:{;:G5auZ*?}럫w_ ߗÞxj雨n>w/ܰO{Cz,ʙޝ9E;(ϔ}=[I|#B^:䬅m \wxBY!e"؉LԊ;TƆ0jd2LV0*G@A ݔ#F;qK%K<䓾R{n?S[w~fuSva0p0hР:f:t l" aZ;ܼ~ixNb28wbͽ]{#l+S"˂S8 5Ɩ3 0ѭfM19`K kFZ, 1  sȓ>)]E=gx]p"Q$6Aډ,Sg(1¾ƅZZ<ϋXFv;v쫯>}V.ZG @IMM=x@ܾ}(9~x^^BhӦ 6^LNNJN?tPLL`ƍ2* !G={,qQQQΝӧMWQZ,3gSPT_lee} (([nh޶mj={V֭7u6oooۛ]vedd899ӿIfXii5!To5lw͘ !mb]*3G+ ^2)2g`VspצnW3 kO~ㅄ@QAޛYo-klZs0z+vmH仏-nps皼k K,"j¹qFdyqgK nOn&1yJWخOlmYw󚷿] _wjhe%~ܢM4!PRhg׻{M{knǸ3GA$v Xl$&S)h|0ّѸ@ceeem"Vb6vA0a·~}Yf5cli6oܷo_X`AtLl^|+֬YcZG9xȐf͚C)?iӦ/_~㓝pڨ0gAAA-jf%$$t-**:x`ppBl-Z޽{ZZھ}BBBryiYYVVq={t˗/ҥb,***PTeeeYYY%66M O:ս{w# w޲e !dp͛70ӃرÇE}...O<˗w3j(HJJ:p@lllxxbQ(aaa׮]t l6;99 윘 2LPf%%% mڴyj&&&>7= ,B8;_ޗfp@˨fYJ =?iZzy3MVuOT[ 9Zqnk;r, č>_E>sS,̴2bF D9}wRiL!;s*LoOgTʲ.˪pp怤,C_A?m{.W(@rHUyZAzZ95ڱݭ}rʝK?Y4IW?{7{=(..M0Y会=C9䤦0QTV$ZHҥKyyŋ### z}ڵe˖uj@DDľ}0ǵ7|G7v?|gF:j<߷o_JB///fa\.ѣV1JUgؤ\c˖-|8fddӴnz۶meeeZ|}} 00W̾}VTTxyyWTT899]p˫M6P\W>d2竟-Z$$$tؑ㸤p B1$$$!!!%%%44ҥKqd28  A^1UQ=夝/ȺyHd'wī2<,vP?om"2+ZG$_f癷z8 DF=p@8yԫ J5d2^q<@$Ϲ*^wpkn3W"dN$2!tU]t\󘾻W|N̔zoRdQEp"dW)r `<8995mG{ENg`0ٌG+ Aoڵk 08L6~ &L+Wv޽I&QVVX4gůڢE>C)8jܙ8Z(ZJKK:d2)ǩT**=: /Yҙs K˫?FNq^v-n؎$&&вIr w388xZy׿CӎJ244ŋ-ZpB۶ms*4JZY_X}0u\x/u70S*^PRj*oo7P-<߸^AQ=FypW+rYzr|zZZl.h] nBP)w89/Sx;'dz!dRj\.9AqF@*0 dSjLc (B/*CH"bx"qLoooWWW\ΒT1m E~~>k/};*2eJdd$꜂QKԭ[7??5k֬X?.mڴ;w{1߫W~ԩNBɦn8sUmx{{ݘ<===Ǐo.\ TGҡj SKg\Zt钿G_,..F\\\xG)v :vj״Μ|/^ԼysTJOCǖ-[[-ZϩPyM㵈vrSMx}>vtKZq]5'zj@5V?\'_J;9c((yq'qsG~l/'osmLJQj TMjN5S59k B$7lvq@:yeա@qr^U>RnѡEVˁj'*8BDHxA \h4,#1^T*FSFX []파 ooor?ܹsnj3dȐ3gnݺg}ĉѰK?QPP0vX777:a„~G!ʕ+SLqvv_s頱q:v;v,ZH j\accc8pرf͚޽{̘1RE,n$$$={v8*jV&EDD4JUUQQQ Fy~۶m ! ǧ%v?܇BsΥKt:6`]v۷ҺuM6/{.- %o:T[޻wo֭nε JV+* pJYZcKˠNNjl k3?J {z~3ȕJJj\Rplom0=`#+*-25 Z̽F=7yS׀ߠͧTrZiT :)__^˯j̔5}m@ʏs&+T/1f^Ue8Ͼ*e IDATm'kN*R 8e\*pJNj%Ϗ.#[z54lUxcpD \OCD"d@m5*va4\ѨT*СC~~~15O.KE+++Apvv&FOOOHMM=u<S/+//ãNϟoٲe=wF,KII`nB0Ajn.S7W\ٴiӔ)S6[VVfZ1J|gѪcl6i}| ;99݆xqSw$Ub/)44p@wjJI6x<(+Q5\ZiTVw6er/ DBvRiJNP561/~۷oo@ n\~xNNNI#naYYYgΜiҤIIIٳg###Ĭڸź^#ޔyDێEQL#`0 0ّ`0 Q_`0 /Lvd0 `&;2 `0*;l=.mtd`4 `<y'NaÆ)1BȶmBBBujh4LJv={vӥK;˫='ҥkѶm|||"##kO;hTlٲ%??zÇ[ RXTT4y䌌cnK.kILg}cǎ OIIYz^ڵk2qĂÇ;wԩS옘XXX'N߿?6.k׮Mvd0[bܹf}߼yvz`0ndǏ>(%%e޽6mڠA^{K6$>ݻw{xxƻW,1%%eǎgϞKxxwq>r7|s. .˓b0޽{{r5 X\\dɒSñJ6l͛/_ ӦM[p_~5p'N .]/KGb&iڴiӧO/**>±cܱcDž YTTԱcǽ{2'R!;vpc?~͚5jժz M?͚5+--%O:u h۶-f`7|DQܺu+W0xbŊeee˗/o׮z"Æ +))Yn!$))i׮]?xGx8o>\ެY:zqŲj*JRz*!!aݺuvd2nݺ}WԬ(++uOڵEN> U>`Xn݅ fΜYgY~}BB!DEtNHLL,..ٳ'v{Νn/i9xv`0Ldu<ǎ;w)So>&&fԨQf͢dҤI͛7o^```lllTTԒ%K@&㏗/_nӦM6m222.\ה-Z,X7hݺu=8mڴwʕqƅvaoڴizッ7o\'MK/K-ZA?hcbbڷo? ^z1BZg;w={v#<33f=ztdddDDk׮mӦM֯_;3f 8q]]xv`0@!ܡCx?}tlll;@PP=ʚ7o L&%Kg(iiiNNNSD6gP_-f?8}tqq1MIquu>|FOe0 dLJVZ3#j=woiѢŜ9scnnƍ͛7sLgg4_`0 w]vܹsIII&"U9d@k_7ȩ B|}4Zx]|}}CBBEqqdmAg}V6"pqɓ,X7A5h~vy{!R(_eZG/_/dU <'@!vA(姣/mOMq[b#]leej5 6M&B6o|ကQv{eeTvl_?`4r\*;n߾=??'.!ӳo߾A8{l6m삝sq>ɍ^P.TݹsqݴA}=EQaҏQڇR׏6lp ֶmÇ+ zl6 /\III/^ݻwݥT߿?8^޽{d7md4 !AAA]tخg,0=z4,,yxҤI:믿BSa|||nnɓ''%]f  !8kjj6;" /@7oެdee 6 _5b #aMx^ ֝:uaq߯ZUz3B@ܬK<H?we_oqŘeÂ:U 9sH/ŋ_u~~؄ ǎy˗/oڴI51L B`:@/^7n֦Mn/d2Ynrrr444onBcmmj*--'O/((8{l~^~=f̘ѣG$~zLfeeuڵG~yߖQ0aQ.޽{mڼgc HL|0kͿ;kblbŊ`\e˖~;wnUX;v,66ظvԨQ]X$wm\#iFf׮kI?Bh˗G NNNK@?^~kLӦM,@X&ikqɗQٳ +em޸qCN\n v'&fc68 5oUZFa!?6jH*G s˗/+/: ?MLL T9rCn&0$sǖ U.s {)nV M{]LMMhZB)))YYYӦMU_x155u떭4-%$${{y[XvOh-6n:o ~;fhXyfggիW\>n8oooK ҲYfM}+{&&&g>hލY ;gOt70_d( ?L+p I͛7rȜ;rӦIZZN'Nݰ!1@BHPrz˖}:8+**Z~eK V\竩UѴC˗/_xee-B ՆL 2,??ٳgϚ5{֤I)))>>>_nڴo¾ٻwY "ݻ \.?p˗//^ F9~𺝏ψGN8!0cK.) ϟ?̙^^^}^U#F8vX`` 0!!aƌrLJ !@KR-M-ы/JKK7MӴ>x(9*444ø}X__ɒ%Ν(jP X>bӧO~=-y,!@dD D~f~~ottt~ғH$UUU*8/-@Һr/tЉ|]!U4+x۬ͽsNnn۷o ))СCOQjyTZYYYQQqX vk.awqqiBصk?9y]$9u{Dhkk\\\h9 !IOO$%'bƌb gggGGw)ӧ[n ! 7n0((HWWԩS}ĉ322 Agw۶mIIIxMB|q$ilwٳ#GBv6lƂWWW' w&|Bx 8y!7o|xƍxý$IÇFFF !.˗/k︸9E!?AȩOw/!@ X,`!#$ JjhРA-%^z]|S111xQ!ԥKs@,RuҥK'NĒkhh 7+u8ڴi JNNްaCbb"Ә y.lM_ͅP$DEE0|jjjڣG~/LDhhŋꬭX+Yn.];w p<ʟ>>>>>>m۶;w|YΟzzzx -ug~+p !]t),,GqMB_IN7ay[nq{1cƱc&YnjjJdBBw&<..իӧO7554iH$oCBBΝh"Zt?`GuA?YoiӦl)P 8񒘅EEEEFFFuu5!_sEQݻuѣzp[ZZ哒d2-x1E"$)!) },UWȪ},BDHP "!J{s6lXqqqqq1vZﯾʕ+8:s̫WT9B->ߺ Q4߱D˖-;K0y+VBYBusyf&L,/////{+[,{Q]]ݖlAQ[HHHXXXyy9IU7nNTYYLEQ%%%]tA:u PSSC{~S2dȐ$>F~lN|JZbbY4444^xqC [nZ ;}r4+8p$h| ;=ӧ={$'' _ճgO~}/]SիVVV$IK$[nyf…/_;gĉ9윸۷ ׉Q.?$ɾ};v̘1cƌ744=z4ws~V/K/))yX,#akB>@!1QLu9$@PZ*-- wRww5k֜8qbƍıo4?r!EEEr\KKIJ7o DO$qm=hL}CGGop̙3cbbqTTԣG^x1sLq b+>}4bڵ9h\hRKD4 6v_;S> K#Eed@@$Qf.\(--m߾=s̘1xرc/^Xh|߾} |ɓ'۴iȁ'!E~~~G‡#Ƈ?~<^k۶-IO<2e n>TjkkOpuuw*77NS6op6;wď䚕Ș1cF=?޳gݻw – !bŊ .̛7i[^^ﯫ[__gˍSzjK,_|6@(nݺ/ 8qփԀE,:vxym۶MGGGCCoy\`Ayy_~)ghhvZԩSχ[ϱݻwsA˖-+//ҥT*4iRhh1:tؽ{7O'O}&`ѢE;vl?JW>|8hLڶm?1СCO.,,7o^ضmۙ3gΚ5 ɓ'dcVc"MJҘv :qƕ+Wݻ-44wjoKͫya!ʕ+$Ifggd P([ h>T(7noeB>ҥK6m,l%ڵ#B!h*++!P֣P(^xA R_Hk3LZYl88E o7rssz2d2Ǐ4PKL?Q\\(۷O>gK,VUU]| zqMMsΎ;~&LP~aãɓ'm۶O0 S\\ldd$Pz*))100xL9%$og3{STTTVV֥K&N[7 wR){eeeV?M6SaMM g6 7_CiiiYYNTZPP`aa}jDi{=s_QѱcGP_ze`` |psSL)//?vX+ٯ W\z{|zCAhӦCߡC;wyE>xꅱ~={&$$=zt=jzy?wl񙵵KJJZl ImڴQN XZZ BDOv[ h|KB `Y0a")%754F5ObX[[:::Q銊 +[(jӧOo4C0BBBa:_k\%===55(==W^EjU熷mVf8 EQ ~j#ՄT!GeˀjiJ?ퟂD"IKKKHHppp'|EQ;^¯]vJjƟcǎU?<<<<<?\_o$lG?X-((HJJ*..t |}};ud,++SN 898&OdYO$P fJO 5PC 5*?WH~j7A_e ttt ?lS?5PC 5>q|<ߕ B333hj:kPjj'3v"5AO 5PC 5>>OPC 5PC 5x@49\ 5PC 5PC 5B6PC 5PC 5PY@'w`ڵZZZ*[0555-Tz쪪*;F?\ 5PC 5h%sٳ{B6o\.sgmm-[V\ihh_z믿nܸqܹjjsGaܹsr YwX6|=~b5BkXܴ]ڷooaaQUUU^^0̉':wZ7wͭ ԧv^q&! !Pj2,@ "Lf+ٕcOx~ee2\.ѡi PJJիW̆ IJBdS?5PC 5>q|* B( ^?~!dooo``2BH$ ?5ռW8-ϟwttݻWas`Yƍرc`iLӴW>}z/\ JmllƎ+Hkkk> IѦ_gΜ122k¿»|iiEfffWM7k>8kלwΑyyy3ءCPyyyRRt2shuuu ޜ~y<>>cǎBq…skoM=^^^nnn/~O=znee>}zĉKKKOO.]>s:`B'n(=q;vhii?>,,}LNN644tttkII }7moT9{Gη^bj.}|kBq"l6wD) |k_"n޼YQQ*))6ljĈ";G, <㭭;u߿_| g`1 !DY~;y~.P1"u4`!)( -s̑J_|ŋ|e &TWW;v͗/_޴ijcΙ?!0?Hu*++_x1n8--M6mݺ_~166dnݺihh4 zժUZZZ'OT!_PPp~~z̘1QQQGnI~2ڵkG-` 0 \.v˽{hy0exlѢE۷/9W׏1b۶m~~XϺuBCCi>sΙ3g2,<\\ϙ_znܸq!>xwGη^bȲ,-0,M8lV $ɚmmm_ ?+W6mpڐ6ce&<!cwۻm6*[Wr !B e/I֫a\N;ѝ H @gjA/789}۷oppgϞI$___ӧشī(cH$fYEV~^:??6m>k  >yY_ ;wr!$sݮ]$occkNWTT!4x#y''wkV&,42WW3g88:wx#e,6lȑ#׭[s@@+@&&&˗/% Ǐ_ڵkffiӦY?\ 'o?~ܷoߔoo qo!b|XXEd*jھ?oǢ7 #|!+==}@uMI$Lvvv7oΚ=_;MM/'O۵kborss q-ϟߺuX,vuuE06gΜMaC%l#B8UW^^nff0o4hx>L=O0?f̘1c`Oġ/[/iqH$bP(HiɥbXvCE*zN* ee2ٳ_}a4M#׭[WRR2f̘e˖5YgǙEIhX^ ?gmCNFYİ( FTHYsnOUƟZ۷o\\\dddK>†UC;v9 !T>8{MlllPPE R)nÇYΝ;4CHRTTBXXXءCqㆻ  amM FAAAaaa܏d2T*ھ}{NNVPPPpp0`ԨQHᅮ$) opW\(**ׯ_UUBTJ4ðSNņ._<={ݻQ___E76dRTSSS(VUU)ϝ;޾СC?͛77nHQҥK 7o=) o޼АJ[nMKK|ӧ-[(**zǺub1Uݺukwޥ(*00p…x8]vmYYٺu븫޼y׾}+W᫬lѢE+6lJNNӃܹ۷oo4M;::$$++k='Nprrd*\L* 4i҈#˳gƍ:thwѬhMi(T&4>~555˖-?h=z4pM\sN!={|щ'>7~pff˖- I288QQQ:jS9zyHI$:<*bXeXչ@ ,R( ih*>0xXx}}+[ZXGGGoݲ@0LaaaJJJTTTNNNdd{?w~B.P7cbvi9aPVe!xcs@ r @17o޼|#O&&&* c! aʹcKB*˹sqqܽ{7+Mi=`׮]vvv4-W!,GGiӦЏ/nݲzo/o N)*r9Mmonj-7ozꞶ=boP"ǍmiaYZV4k֬oe/XĄӜ gӻq#kࠁ|'3,>wEQ'4[wٳg[?7~`/_&$$,X 00nΝ{7lЯ_?˗/MMMiZ^ZZPɓ,X`llLI&eddw0?ofmm- ,,lٳg !lTE7=11Q__M7Z|GQ ˗/ӿݻwoذaۣ֭D3g455M#x˗:u {=/^H$r_o344*..RG &$$̘1ҥK BN}|| 8BD+h#<115NLL4hX"zzz3B.\xʕ%K>{[yyqJ7n{RR7oqg2\?zH"ѯ^3gw޽~z77ׄÏ=ϟ?v(g952A+!ͿC ܑ D+vIBVs'|4>q׮~ذaݺvD>G&Lիׅ M@ EEeEeEӧO_fmm A@6G @"Y,x$ % )џݯ_?_+++$IUU # a~5:t7uWaee; 6+`sssܹ{;۹EEE0HJJ:t/SZ^*VVVVTTܽ{7q>B8ݻw;99ڵs']\\hv5tN뱵uwwE}>|iikk\\\h9 !IOO$%'bƌb ggg{H@`?99y˖-H@@iiirrŋ"""_7}Ǝ;t˗O:Ӭy3gֆ [n ! 7n0((HWWԩS}ĉ322 Agw۶mIIIz/ɂ~Ö$ٮ];R  4( kٹ3 ۷ۊB.[:rȎ;sSaaQƚ{3CCÍ7.ZSN?H$222Ytillw} TCݭ?!񡨆sqBpҥ^^^K,9{lc+<<|̙vvv7o=zU W_\5e?7kڽs+PRRfܴŷr~h~zر|64?rU#EQE ck<˖d^/(( ݷoÇ7n܈0HÇB\ ŗ/_{qqs(B,$ ~=S޻ h=_BmW$(⢢AEFF8zue۷oϛ7Ocܾ}ҥ9s A@U^o):wlddtҥ'bI544pڄ:imڴ%''oذ!11L<6ӦM/ B(  0h i=~} ~o}Ȑ! 矿k׮BbF:x Bhǎupp:u*?95jƍW=z4M\f͚}fee7_QQђs***cZsBΝ;#JJJ }Ymm-|299yFFFݻ3~f_fl'?8@B Ə&q2wE%$$p:UI*ӧO^^B\@cǛjOoߺ Q4߱D˖-;K0y+VBYMlsyf&L,/////{+[,{Q]]ݖ@Q[HHHXXXyy9IǏqDAPURRK555$Ijjj߻wJ_.8%C )**JJJiiiiA&ħt%&&Κ5KCC/((HKK(ŋׯ_:t@ u֪Up}ADDH$ (JPDFFs~4bOɘ2` *p:u/v`]yc̘1"(** ! Ac}%K?UEam۶mEEEErL&B:t敔<{L,0 EQّ\تOjjjSdffZYYqU#I!55udcJ$/_,AΝq*;7o8dgg3 [[5'O:$Ɏ;i>rȑfffxm H'6dŞP< ϑnnnSL =Q߿_[[kddԿe:a85R=ؾ};gذa?sBB)Ϙxk^j[/izZH. &)me! |$q| B \(Օ$ə3gbGGEEooolg>}\]]v?Oܿ(% )?H`icE<QS\"$BXH1%b"6pBNNNFL6 ǎ7h ggɓ'|h"2aaa/^ԩ %K(Jw}PTTTaX+lB7n_~0 }3f(S(СC.],X@WMJM'ydɓ'ȑ#?ӓ'O^zu`ll\Ưwus2_C0իקiƧǭX˫} Eߵ9rٳ`&M^7Ry)mjjJQTQQB@ղ,kdde``_?NӴC~5(Jϑ@ZMM*HmFd/`lͭϖ 3277&$$tرj=zR*kZɶ^"Wq7n !ђ yyyM6jѣG˖-[8z2 }GaaǏLLL=dXRyr Z9ВdQ5ӓդIqU(OaÆFFFe5PF>--ںT|J),,,,,W`E#>==ʪ yөhRRR>\L'ɓ' U!a}d,FB;Gegg<ѱc&'':/b~U=!׮]kӦ cť#|HdH'ܯx'N'.ns…ElRż._po)Ó*SRaaaՆ|>66v]ty nݺZeGQ1Ǝ;!\\\f̘Qٱ14M[YY$gggRvaf݀)V@ACZm ` <<Jfh3n|Pa juRRZ633cJ?ƘeٜrU1 R:t(8=SC 2C%ǬKUͭ] 4(?[Sv\K5Jd \R'KIՓfaȁWu-p01D*Tc,/@vFD뻺^pQK%0LnB)9D 20mڴ;28w/:4M hҤI]WV?duߞx ;;h Pr01zpUƶ};MjW\lmm5jVlÇ7nxVcz688xӦM111efɼ˼|o"şY]`A^^^˖-LR 'OtÆ 355S{5 T>r91 /zc {!aT*_ZJ%h߿gq!eB& `^z_^Jɐ!C:HcǨXR_V&P\\8&Q2umWEBM!+2@AcZmk6~voq)X!ͬVtfff,R1>~k4h0`@,9qZV;e!C u=,"I{2L3X 6}}v)|BBBV8C"\HU( 45Eˀdg60 .Pxyve>֩GY 89STzFFLL 8::m۶VVVsqxWw?yy{CD TIDdilNs{)a֭ l8lx~,˚!$>KO/9@@b"r2> EPZσfق ugUu+J8W '2yqSNxT*Pt?˒oK?q!⊵0eYajOzqEQ BF̴^V̬_>%tW )2CByڽ,OzsJЈBB'>}  >{;vX ['vyU_zZ|}}য়~iӦMmVd^_sqH#*:ibwmйVa18NPt844ӳm۶F+B==wh{ϞtX0/@+pI_)6mF8p?䓔_G1bڵkccc׬YS5_A^|G%1a=|ȑFFFk֬Y~]ׯZy 4sΘ1e#-n㼽}||\\\###GT!zԩ]~z+W,ӕJCCCZmӦMoܸsN:W~<<ϗ.1ļ/`CΝ;vرkWnjP\\j׮͛7̜zAtiiiG\׬^w^_t>}:,,Oʇ(;u~2skּ5x`twsoٲUmVd^_sqH#(yl#W [t7Οxڢ^_ Xň My4]PPVY4oJ$ǥ9RMnݺ `7tB0BOy u(c,uBaU+*_w& {nXX… Ȝ9sܹsO>󳳳 quuq*Xn=:*o)))ϟ366I!!ރ};wQ$$$<~ cA@(JLL@nݻw޽{`Ȼnݺ999CL?ƸO>zyr_+WbE{Ps]i]N>~pswrrrriR?_1bĈ#͛mmnd^+k.tP2mG#7[9,:pIEB}:rH۷o-6o|ƍիW߾}رcbS\R"_.,,Ծ ?o޼K.,;o޼Ç 2 KM7}VZ=z#IR2kڵwi:((hϞ=+WСCttw؁1ڶmKjP;@9ȼ*J1BބGMN:fVR5nSϮe aUB@ 8ߨdxWjNG2iBh4)))s̙3gΌ޽{gt@7iWicƌYfuΝgϚ駟K 2 X02ՀFy ^ΏuVlllAGHMM%KMM(KQ)IyBTر2KJJ8Ϋ<==޽DXYcYlݺ޾M6,B>A&NP~2dիϟ;wn,@{(..5j`ycu:5j5g~Ͽ>|JOoڵ/nݪ5Ν%Jt:ȑ#93+#00pԩ̙3X+xNiaa׫w/@d>y,%%˯<}40SL=jtHJӝ9s痳?~ xϞ={8f͚5uԩS~GǏ!IaaԩS7nܸo^RŲȽ{޽eY5r>$gHǏSztm[-,t:ݢEP|ϲlvvgH:_xaoo_֞FeB>_8cd(JOI=ɝK34VwgT4CSj;J{}@ H.#K ٳks{{!C|}4aÆmڴINNE[ZZ6j䜑vҥ TyB#0J ap((hFnmڴk׮Jg鑑*Z*w,~Nsqi{Xm p7ݻ iiiR7ڵs'0Uk4ܜ{''GL۶6lxaЁ &_|iӦtf4oPZ袢;::4kPu)űWb#""vo>"_xzz78uƍG3s ˅VЦjS033751%v>˴v++߲ťN>s̭[߿|y&j Pmbcyq?Ʀ ,/`rܱL]LBɮ!DVVV!@( ;U*|qB紕MD^JhLxÆ VVV䉍7>uhO "*qRC.] whiiٽ{K.w} MW^ION MIGTڡ2ȼJY[[[X[+͑NneIYXVڰeKP(4M+ʁ4E1 #塴!Jy(Çk47t:a +y=Sz JZ_I뫔~hs~4S)JZOS*T JT Ti xիWqM6-]4iիWIO?dܑvĈ@5jKT8߱BSTΜ9seܹsgڵ ,P*bނ~͗_~9zhCCl22B 377L[ t%88x֬Y4M3&&&&>>sbI *((iа~'7o* QI߾}ҢYݷo߅ HuO$M'٥dСCSN500 SSS/\0o޼ٯ_?B믿~7YYY$PZE;߹sgѢE sժUdgϞw666vuu%¤ W)윞Cx븸8t4M$[H5jԡCs΍5ҥK?&~zӦMŻ('wMIIy&XP*ƍ4ݸq۷o pp? J ٳg ܾ}[4EEӮ]VZڵԗG;88 6lذa#F 988۽{ 6|W?se͝v0 dX ex18ڵeEEE={$ULRso߾7ߐzԿcǎm޼y߾}3g$MމNUd^k3_sqH!KVVRff9ӂNC*^ dUEOӴ&!Bhƍ6m"vqǎ{ӥXw=} 6\|Y/%Fr^_ŨEI#D>8ىkz n֮*,cA ZJJ2;{lff-s-ϟ?;w.888رwΏ?>r䈱q|-0VPemm=t{{{7hxU?311iNj-?~<)>FӪU+=޽{ȍ;999''gڵ%hlٲ*\+W&OܲegϞnz۶mJ2w"P(0 ,8{g}wޠQF3f޼y$z&Mp5k֐`̙'N>}%Kvy…ѣGK4nx„ ӦM5kϞ=I0 /Z-󮮮~~~+W+W7nѣG1I˺u낂SSS.\> 裏hooo/^O~={v G>>>$IҗL1Q*Rs}ҥرcϟOLW&׿y-i|af?nڴ)"8zhddm۶&K+55EeyIHHM355ݰa 'N8eʔMwڴiĐё,ܿJ"fONN ;v,us)_4[l5jTNݿ+!bm4-F!CO2e޽LM>Z2n82g?.'s]i>|ɥ~rС;vX[[.]7G"J)kb:'O:::ell,u݊k.AW^_>Mӷoԩa9[Ҧ;:i"#-dٳf)8;;~kҤ6dI:>==ؘ, _}Ml) ! h57o(#9 a1Ƽ9^o4n?[2\TбcG=fV}葋K=jׯ_oٲ%z*99sΦ+q7n !ђ yyyM6jѣG˖-[8zh` GaaǏLLL=<ϿzF,G<caee%ZܜȋJa!((IKKjҤI'ְaCf&Y>++P@rmkkKuRRR5jDfzAx䉾'iz^|iii)}Gs1>$C5j炂gjB?qčE=ztܸq'/21vZ6mxešMC|OPߟe9S**.] EiCQ@!@c 0yCCCvC$hժM,oݺU*?Ue>^\oE~^^^Ϟ=۾}c[yC>UCqH߬]\\f̘Qٱ14M[YYI ))YT_sS6D(]`Hc <<Jfh3n|Pa juRRZ633#;aY!W1u?aBBBBBBjrg``qq32bbb0m۶x8}}}Ļɼ˼|{"qݝ.S((DQ%À6[Ip,~4<ϲ"cb ܬςn9dQ>AeE BYU]50J8퉌y!q\ddԩS9*J:ZY7ODz[qBb-/.ڐ 8y/{.˲ O<ӫ%v-S#2_|!ǎ<_I|NQQ0qӧOa@go]y k4B ==d0 lll:t k=Q{ 555UB ^ Ŝ!  `^ [o L50\aV@1/^rJf͆jaaQq;w}']'|" TFDG*ّ#GYm۶/dcY6..ŋ'O661';ʕ+RIcYۻcǎR=/^<{Fquu1bTaa}i7w7f"]ty;y?y򤍍M6m;1|pccc?}˗/ǎ x_N>mkkֵkW &;A7ns<秧WYz6ovj_ӽuZccju떅Eǎ{]FOTTTv횺4 YYYNNNׯ_o֬>B022:}tuy޽{ >\'&$޼yclllܽ{wKK?Z^߼ysʔ)$JȡC$%%ήѣGv5kTfx2Qz cLF~AAAϟ?9rњ5k֯_k׮Vm޼y ܹ3fLAH8ooooȑ#Uȧ:uk׮_>|ʕ+ V|hhVmڴ7rrr8ЩS܏xy%fq5ݘW psڎ;Vv1c aÆ~C˗ݻwʔ),Oz__?33sڴi js̱t) SYWwH77-[{;84-U Rь;h̘1뛘E+..!DB޽+22rA:nݺu]v"_P^괁c O*Ő!C=|t=zH}WqvvR=?wǎ}]hT3N :t?}}||'MO͟~p1ğ'Q|||TTԐ!Cczd^JcGP2/شG@'n?cE=.;i@V#hIKsVܺu A"n1|1'8` NE7wFQ4X0b!WT vMrݰ 9s̹s>}gggZ_jT*1 *"zvuUŋSRRΟ?gllBB w HHHxƂ TPݺwԱ{Jl*wuuݺu srrFe1}?ЫW6ovww7oI:AV+[k~nnvo,'`˗/xBIG,\0 `n4ѣ7n4h`1L8isYgmѣG;w>~1V>,l3|Nyԩ_1fff{=z4>LZARSS焄Yy/QF.iӦ3gN4 ,XlHW $&&> y ѓڪukŇVW^0aBeVa: %BZZZ_cXfڵkg̜!yg{2IjV!Vo݊'3|Ĉ#FTȼBJ;* U(Cۦ<^>y4rcK©C;9wT$((3UmC4EQMqxI4 2dǎU7o;44t޼yǏo۶mhhhzڵk{왻UJtʢX]ȉSE ?miI[  tH1 U$5EFFFyݻwӇbaaѹs爈EUWRY2j1¥6!HP+s7oބ2Rѐ4hВ%K5jbh4FY!^|ٰaw֭ ,KuaAw;wtfVKhqqq7ns玑Q```PP|R~E%cƌ={6yիWֵ͛k׼<mi 5 ˲}۷9,˲5-,,*P__Ç W/^8F8䷼Qۂ 6 }|}F 6L/>裉'N<99)уeuh{YZk5=&1T2NQΥ_3P*T )U5> ~q$% zܵ!>>װa6m$''C---5rHpsstRz*_"eaÆ{U˷o߾}0}/9v@9s[nEaaaRRIϋ/,YqUZ2J޼yzYͥJ,{8P``'ի^rA:v~~~6l~{DΝ;ZmPPPrrrΝ͍q7O>ݯ_'O\t̬qDٙ* )}iF4_}d?2@ :-[Fr~:u4cƌ8rxףgê8ݽ^ b"*1F4j|5]!*hbQA6DH{ww?6 F\~;{̙33gggf<عswU֭[7{l77=z[f͚gYB<B ]69cǎ?vww//'͛7_x-*"z.^a޽{?*|Qq Cʊ2qsxbСCs=s Y`cllmذڵP?z!Y#|O8%ÍʱK|%?P2zMe2UXTJ{vC@@c=cS 1 P)SEӴaX2 4M@n$ o21iՕ鸸8L&̞,>QJ9M;|`O[[B(aQY#""ƍwaTr֭[8qرccǎwޤIT*U```%ŸBiޱcF__ߺuΙ3eF?^l 9s7o~նmےj֬YNr?iqÇݻԩSzB>L=Ib!!ԩS;w|rΝgΜ+R^(iSN%L&cYv֬YIII$ߎ;n۶ٹyYYY={T+ ؋&&&ηn"<:V^MΦiZ 92}8q"44T,ÇEaΟ?0[ 9qg}Ɣ!5|a^zu޽Zj4]f޽{wܸqb# شiSJJ MO<(eٙ3g&%%SP߿?;;a]vyxxLbgg߬Y3r n߾=55uҤIeo ܹsɩm۶$+Ka;w_I̜9۔cRٻwoȩbӑ`dr %tNi}Ǐ?pȑ#k֬!Ch4u%y([nKWݻwsrr-[V|6ڵk{e,ٳgGQN'OԫWorܘ;gc<}Ǐ;6...22RVƦhSN%32&N8cƌK㫯:pSLLܹs7mtԩ(Z.=t1cƌ?>,,]v$8;w~Z8??EE6l/|H//3f4o߿;p@[ZZ={ܶm[Ϟ=ccc:w :uꫯuFR׫WM؈H`;vΜ97n{W^q\ff#S"|Y YYYjmllHzA]J> 'iUϟzG$'nffqF),,$'x%:A>=RTIlZM#j2j W$l*z|E:xrrr_t:<ÊK_cr…sQv1NMMݱcG^^yXXAKΝ; -C'3("iO (B1 B @&q1ǁ{<'a.{υ8v>}t˖-{d}$ lr 6K69uԬY kגs"+{K|;ud8PرO<"oܸ>|LzʦK_W\c"7n\VV4mooonn^:p$LLLRSSrJh]`c--QP%T@`ii"T` <r-κfbҧLVA(JKKTKKKkkk X䐫Bl'ɢ?xyBFJյx ~vNGGǷ:/7~wv}ذa pqHy{-,,_ W~p#8KKKsuuj#8:)Pcc<wM5PT~~~NkԨ&'Е+Wd2Y֭GrJ (|uI>\nM]tYfݻwv*,"Fxױ=|xvڭÇӢ%E}P_R^i;;֭[^x177W iNl*~$H !b}̪3(rtt 4*r劧'y]#Y >\ $H|<[BDzj׮-?"t:]JJgAAk֬9c qŋ_uɒ%111I_ $H Jc|ǥܹsGVc^ÿ'%\g i.(NNN5j԰8rssj5qܞ={ "U1&V^f05[eO%^%^8~qZO>=77N:#G,3 qt}R4|dA3 E* x |Ƙ74bk*L&S( BF}رcYE^M3 `ۯXBј BI A*;Ν;'˧Lb,MrrrQQQĀ\V,D4B@A*179L[:Y~Ό?>rA*@\XV鬭z=EQ_pͭknyeZ8v'A 8{"h<#owgRSY3U* #}uXDsLpq$|_ƿ*hy"e@Ҩ^W cy9iO|f_T#.,˲&&&,r,)EVVbb"8!!A<DZ,RX'//Uqyj Pd QTv0hG[v6&8^ommMFtH 0reYoqBem۶=8 P(ij~^דwK>˲!⊕zaʣgY(5ߓ/˲EUZ6;;EX/fj]\ȟ]8ؑ ^j"ky((#Gc@c&[t2k410dM&!lׯ_Nr)vmlK.*E,!  `_>ψ]:站+Ls<.bΘg֪Uw޶e&cYvӦMׯ_o޼yXX[ ֓yF%^ʕ#F[_w9{ݻw;tЦMoݺeeeզMƍS4e,=i&cwwF/Uv .]-SwpH e˖>Gs/^DEEӧIM4IJJ:~F UTb=zKzj;meeUfW^yzz^xVZb0..ήG+m۶ãM6VҥKՉwSTTuVOOOr˱Ǐ[jU-[2eJ{I5}jlR?zhϞ=iii^^^]t|yСF IDATp9ř~3"v^t5kj|Eܷoc1o߾,pph`gCհ &5;wܹ;0>|~QJ8h숕W<<Fȼ(f4؋@&ʌ1,˒D… Z7|T*/_ljfyLnY80B@D+g~ۻiѩ-czbX?R1fFӭ['O5Ç...兆.[ܹsK.- #sdƘ0~_|g}ffft+Vl޼Z'|vƍ"LLL W7˲AAA3g433ۻwo9>ܪU/_,Zo߾ғfRfK.ܹif܏c98ܾ}zftcb^1ݻ&Oljj4h !  п={\wpo"gqqq#Gj߮X~vv1cjR/5/lѼxA@3f\.s玍 _ӧe˖^[n>}|||OQrΝTkk+njӧOV-[a~aju^RRR^ڢE n)oTT۷>}:mڴf͚ ŋ#""ذa˗/[[[{zz rx:}t=Hm<&~R~-]4>.Fp\pa-6lh@KUV&&& I#8^\~}СDiӦ]|^zeځ+*Alܞ={Ο?{z/^B7zxx\zy󦸰DrQQac```\\YVϞ=۱cG^hh{njjj+xV!a'ߠ;pIͲ,KJϞ=֭[2l'N,mC p7{nR\|޳gOƍ"\2ˋ8p`=JǏI;wTv5k\|yׯ_733۷o-$_qyV'C~~~ioO>Nb`C! &&^z߳gO||ѩDU}-[e###o߾pB]vmܸHkР+!^&3(,8Tuxͫ9ꗉQ_i;K ~eŎd:ceWYƳ-[ݻ7,,e@K^qXǏjN'O~ԨQN'$&L:cTl ㊏ie/yq,` dcshesڵsΕtF Wrǎ%ӇĔpG!thRSSY hkѢ9ܺJUz}`ݺuuO+W6dѣW WYG9׿+ ϑ F+; I~~>|hoo7)Gd#G n`b#YRvj5>3*[d:}Ia.=vį'( =^'|Ϟ=뗞{:(Uʢ"NЩSI& 9 $+ga8ڽCJHَ9rĉϟ2e 9OzqNNW^=}18N=Zn`gk1CR!3f…VVH}L UK.zooo`_FϚ5{`?SJ%%ć_tݻw;;;~Aۼy.\زe@\\t:]4?{յL;TaE%/yZ! 2SrܼSx E~?ajn*hQ0w@xx7oݺG3#R?@?y̙3ڵ|٠~geeŷ}\#a@( a@ ^zKExxJ- q!2@`lޱ/x{׷B?U+Q<'''$''Lyfncǎ͛6dk4ׯ_ܺuჇDw7nx֭ҵ n֢E ^UV$Sn֭[+͚޻wadmm 66a֭-Z!??$@HQԈ#L&͛7>aK!::z6lڷoyK6egg۷q'O?~ԩS3_d[9sf65k矟8q }ݺu۴iP( 'YbC6l'0/}eee߿.22O _vP`8qO?tWGJ~%N(,,6mk퓕UTTt٭[nٲꕫ I?)AAA-Z 9c_+gL>0d}%4sϟd2%_~qƭ_k箥eك.]deeekk[ZA=vedHOOO\^A۷oo׮]rŀPDžIϒl*!^&W)T\)cl6l%ѝ9lc5yG<͵m۶&MԩSFFFLL IPr Q J (k֬!@Æ =zTZ5H>qxVh XJ^rE_``ȑ#oV^-Y]}:wܴiqEDD\rE _lXfEO[[[ɓmlllll~'%%},Yd}%bPF :tɒ%+%Xy9s4oʕ+ڵRrea!:uzyBB^?uEQbJb=)]hw=z v)L˗/wܙa}̙^"%F77n̚5KV@v8p ((HPReV>a_Nr^^^5:qDXX]E Ü9sѣG `/\PfMd2 IKKx"9J 0 C1vI1R(&$$DP,\077cSrrrvn!|uݼy3Pw^b^ww}@ppp 3glӦn Ui|28 ]BPa$_R)ğo߾]PPܶm[4tw.tBBciz˖-W&|׮]O?ō7-}:yd^ƍ;t啞w^ssrJLFZEQ{_8{Q9 g͚5dR}nݺ؎Ry-rc{nNNβe˄ɞkXgώ1N:ObĈ5kv/LLLdd 6|;wT(/& 8 !4t'N͚5kk֬i׮Bhݺuaaa͚5+**jܸ)S'pbLF *+V 4(<<<11l܏ %VbaaQVA m߮ZjՄlܸ*޾}Rv޽qFSSyeNlsP nUϟzG$'nffvήQƅ <<QA+_*8s'{(JԩYf_v-9'^^^U*OV bӳ*J8F*JkkkDa“迤//OMMM*FWu}3cccԣ '))I|(,;/ !߹ϟ??l B"3LHP\E}81T,}}}Ǎe14Mۛ-H011IMM*J|uUF( IDATDCRE <c <r-κfbҧLVA(JKKTKKKkkkʏX䐫ﯺL&) 5*qHmUմiӜ$S| k-^=,FZ||OeZ#0ׯԩJR@qƘ1֖B/+d2\]]Z`a( $|4|8!nSM]tYfݻwvJvl+u{O.ޟv,_%kDliήu֩/^itww $Hxqey_y>---!!!33= === ʮ\I^׾HpV9t:'\.wss>&BR~! $HPAT0VIQ$H AʾT $H AB;J A $H(Q $H ABE!xy9l_'O,0&11빹wZZZ֯_W^%H A R@zj׮]Mt)))Ÿ˗/Y3ı/~%KĐK A >>xq\JJʝ;wj5M<_.01OJc''N]<#7P* ɩFGnnZ&9۳g!CJ1dSTTի fIKK|o"ƻǎz~鹹u9rdi8;xN۷U:'q' a( PIcP| 0,g<}qݥC^UGd2BTP(g4۷ǎ˲,z}lllhaϷo~ŊLRO $Tq8Dwwq9\>eci"D䲪v^f!9GJU LAazȄsfy R,Vt:kkk^OQx.\pssڵyƆeYV+I A*0;>țݙ,FV >?zJ?~Hc_8.99nݺ,"\ A 7Z&|4꬗6cyE,j=?>* ˲ ˲bz1NHHhР=q,˪T*eIKK|{"h8w޹m<'/T)BUr"đ)I8N[[[!R@q91yya xc+3W\$ x*kldkwVs:18zl`L<_uvU\.gY8۶qbYv۶mGf9( l;߿]ϲ,Bb%,[y^O4Y&[iǏ98:53n9::2#q;rw֫Qm_r-/߽ra ̄unpWtF1f$~ݗMtԩC.Ů]{%q X!5z D@qK|5s€RcE, 3QtZjֶd,nڴ͛7 {+_A-95jǡ4w?{U^ߠA^z2z+WFana^~u߹sٳwСC6mrJKLLu떕U6m7nLє`7qW5>^6iD,'))/44TRMj%=)tE4iҤCroްaÚ5k@lllllŋ+bK.} Øo3%%!}C|JJʥKy۶m U=r[k;meeu;wG}$^+0{8+U yyF4yQW9hֱcL;bY% jZ''oFT^t (Fqz/bAa Ww$ӢSO[%3<9 <#ZŰ,+~c̘1[n'O5jÇ]\\J'  ]lٹs.]Z>_Ab&zG%1a^~>lҥ+Vؼy3hO>ƍD#ne|}}gΜiffwrҧ>|UV/_ YhQ߾}'ͤf͚.]ٹsgӦ͌rqq%f}v+ļ=22R-_UVKݮݻ'OD?p@bkk۳gݻm۶b9_%K Rƍ;u4k,(iGoȑbc-;󃂂RRR>ziӦ/bU8}~~hի;vٳg>$^ 1Sftqew{5lXF-[V6e!2nH ---4L`;v8m35jiÆ<<Dc"ױ@# !>i؜8u8EcyC/(_pMeb7oΘ1 ѣGO8cJg/'BJ޸?7KC/fϞÓ'O['Ni&ɘ{r(*%%! ZiӴI[nժUXz??u>''g۶m{6&_h&CZq.\nYT 8tP# 7y9еk^z-X )TqG̘1ӧO...ӦM.\x%77Waذaϋ f AfߣG`[ll,+oI苉vy;;+VҥK.]:n8YշvQ544$$$Dl]8!dt#c@@:晃n[cQ0x&- 8IyǐFM!˲ª#zFE1 hz=X\ndggO2ٳs% ! B=RRQW|1cc`9aFhxsf*f˖-;v$Zm֭֬[g͚e/'\^I16mڤ"""ƏZh\jժ7nEDD5EEE)cǎ4-0` Hϟ:u[jkbb-ѐ4RׇJ 6mڕ˗իΎ_EZF155幹ԯ_ fffSNҥ 4hYXX8mڴk׮6m̟?_XVdaj 000..,PgϞرNj$ܿ~|``֭[IXu֠͛7cǎ}g2LÆ /_&SVVV߾}n*jܹsO8qPb|Nl7FGG3N?dȐٳgܹ\L͛ߗ9Zjǎ~ӧzԩSCBBJ|<N1kn߾=##M6Vc-.!!a׮]}G}ANNNb;9sfƌğJsss{qF"mƍ +/pqk2΂C5[޼~}E1yޟS7Z^fZ^0 B1˲dFӫc,ٖ-[2^8cǏj^j'O?j(WWINzLl1*6@q󴲗<@X0ԀF9 BߏvڹsJO:@ZZZF?]\\+9cǒo㢪~]faQ@EQ@145A 2]3_5~n!i(V`Rn$( {?ܦF4|?13y9=l6V%k! 4vl4_J7_>+vYj5@QVArrZғ>Z^zzzvvה)8e'O8q⋄/jp׮{ݹsR 1|k24֨U* ,,l~=:w1c"ц |||VXѳGOcoDR;vĈ9VUWO>/^8k,t"8NRvgodkכ7oZZZ,h/4=.lN5V&7֭J$nԩS'On:gΜ7#IVR)JOOY3g$2d#G5pjρ9244֭BۃD"t Ets={vG L͐=yzz?~<%%e999;v@2s̘aÆX_UUU\\7o*:]tttQQʕ+ uvK6V>3XT988,_㏽ o>aT*eV~mZZju 1cBLM%[ 0MYc?˰?NLL1b@ @~gHUUeqq1*êS4j\%4rwnj jOYyiDDDTTiSM>}jZhyf}}J=c˱T!І D8`\,A9^8ah,y}JH4I[13ywuuu%)CT_ԩS *(^PPPYY??᳕d9AA Gp`d9@I }͛76mڄVp]UzbV9Ņe-;6u?L\\BޭWհ/_r˗/]t岲2m7HMMݳgώi֟ݻ5557oD[p'7gCA;w_VCv>=z>>ׯ@$\ 8sUjo HM=DĴi D>>>^^^B~S޽giii *--g'OΞ={>!!!CΜ9sa3Y0:hIv`. !444 4^xQ իC?O N?w͛7}V\NSNGYrS|;XwHJJZhѶmۖ-[6tPB%BMLLjwwquu_RtwC}_,QvqH"SCX,i1.$/S9S3R8DO;E{$I h,6$$$--m֬Y%IC#a555B$уp֬Y nȅ4!vñ[O}/8`G) q,iXBF@PXqYY[ {>s ҥKqqqz9h "))ҥK㜝?1d٦{eZr?'''kkSNe!ǎ300@ajVܹOQZZڵk<n3bĈ)S =9w BKK5kք 4bkkj*ADGGɓ...|J^Z;_gg_~%ڡ$ =G/9rȑ%&&ZZZ222477V/122 9sfFF*D ˫;u7+A7o>pѣG"""Pwp|J׭[bŊ`-P Rʕ+O?h4)ה)S~QF=?7໵/ٳY?Ѯ|X,@oHOy=W\quuիT(^Ecǎz5H7o^ddd\\܂ JM# u  LLL/ `ɒ%k׮m_񇥥%:TަUO ވNBooTdq555&&&ڕ[Xb.iqκB*RR)Q4iXK ܜ4-NǗhCQIT充Nz$I.\m@O|WE)JQ4 " HMiR, R,MZ|P_/P$ ( (R"4Ag+JZuIDAT^^^^^Ώ#nڴ)>>}:{,9RQQGQ݆aaaI'Su?P;vԩSHaÆ%KYGw… Ǎghh(r9Z[R:t~˲)))666R莠iz'N={\.'I;tПw &JgggXڃH444ԩիW~EJRSSj}233 жm': ӧO700@ 333i...>|`` EQ.\OQyQ%SxbP8|57Ο?6ݻ7DrTު*CCK.ݺu>Q4'|(SGGGOO'NE\3gPLi+|ĉcՎ v}_~{ׯϜ9//999n߾=77۷oCj5_MFDDWVV$yA1cF !KLL裏;[W644tq,˶ jd(7lp4FsNQQH$=z4AQ544hW]<]Y|\.(߿~֬Y111ڥm8r,&Cwv,-P?fff]h`p*P5-|aez/44 ΡA[n-_|ҤIz聎eDM;??fÆ `˖-Fj)ӧMVTTԳgoF NKQqK,9~x\\޽{###ryXXT*U*ŋQ􌺌s.]t!!!#F5kÇ;vr۷gffN8Q.7N[ɓg̘1{A 2oOJJH$(Si iIh}vTTIVVV7ntttf@0~xssTHq\.wvvnhh(jnnnaaa(k2dAVV˲BڵkPP?򼽽 ErrA tȑߏ޳gNz![:t`hhϿibgsrrP#= ?~~$ ʅQQQh.{…aÆ%''wvժU4M/Gfkk+Jw_޽;<<\R [˱.gϞE?/^ׯj4㭾 PmB#@Qs%1z!q\PHRWB6NށƵFFFٙħGfooodd6lذ,XOV꤯FmWUUeff=,{mH s@խ*׋gWuWZ˾$IZXXeeec$IJ${{{ J{`08O7Ѧ2O e SSS_!:((H& eggd2흀= U*p : !EDfga0 z!{e6v+1Rfff:tx+Ϋ~ yyqȣY?E @{!% 8j|``0 2i`8*a0 `p`0 i-8v`0 x###=?]y xICCáC.^X[[/8N"5J `^Jpгgݻ NR]rZWWХKKvA;v駟֭[khh>a0 y&cǎD"ijkkr90?4i$%Eǡs;v8qM6:t(44FF1 ?x MBݲ ^h*ZOHHP4(ꇒ7|377,`0 E=rKPT*333ZaZ~ܹjZh4Th EY`0 iQVC.J-"-e5SWJ w4Fa4F)+/?tqe 0Fϰr0 ǎߵ%1*kk뼼G? } ٰaCӫ999 KNN.))y\[nC322rrr/ Fav0 PڵeY pBK `^n+v]&!ɗ_~Ԏ= |{hzuݺu֭{wΞ=駟&&&:Tܾ}{M/۷ӳgϞOATڵ+;;gȐ!mZjɲ,0|%ei0 Ĵ8gVccc_a\nooҩSVXcǎ>,***))):::::Zq5{)))iڴiO޽{}h4j@ peA `-ƎӦM;wܶmt|TYYٷoߢ"ݻw훕5~x''hJ5{.]UTTJJJyb$<}o!ª0::zsqttm޼O>2lذa'֭[!C:'$$tЁqܻwom\999K,۷ytFGGoٲfwwz 0a7 +++88ݝ/a222ZjK#""VX>1BDw`0 楧رgϞ~ҥKKKKr4͍7yّLII4hP׮]9RXXo>^իcbb{\f@YYرcϟ$Q0())Yv/s;֮]|_=ztEEƍw^~N^:p@mL&zjK֭ۜ9s,--/^,))Yn]ee={ܣ.\pB$\dI```AA?hbbsK͡Ö-[Əߞ[gqǡ]?2 ! g=wN:͞=zѹswygʔ)2gիW<722ѣo999UTTĉ|ÇƾkԈ[C5553g˲'I Jmuݻw3gMQ1A>`]\pp֭'Oyyyݹs=zhqqqΝxkرR۷o#|}}aB  @H`0}P(/==j B&B{$?RZZZQQQRRr޽Y;LСC}i{~N>#,//wpphe d&$$-X͛De-[T*7*elٲnݺk׮9;;O>A(.- ^`07i-Zdii YE2y$׮]iƆ$g,4wǎJ^KKKsrr&MAtlll4h.%%ӑD*B?www[n7nh:Ac&9# d5`0/;5s-211v䔟_[[{ʕ'?CPPIcƌACoXkAA˲wﮯ1b̝;W(~'(F>}1cK&WVV>AIe2vΜ9h|||B{{{pJ44h "))iƌ3f x `0/7GWF$%$$!СCt2a„%K}lٲ'(͛bZ8c OOO p2<<<33jN~>n۶ѱ[nO`^A$I$h4DžQ$V=qG `^zٳg;uDŋʟrTqWSS#JJ-90 <;wݝa+H p@Baqc) OHҖoInnnhchhscEM+++tBQO؎~PRô#GDYQ__h `0O6R{,(J.߻w Ŏ`̙xekkkry[G `-^D"DWYYRs8rR*++$Ie `^&(шbWW̽{zzzZMӴX,~b0 iSpqq>L_^^^VVVmm-0%Dboobaaǔ `^Jp RJJJh~QPtجBJREYYYyBHQH$:`0 cOOvQg9AM7c0 yE `0ւcG `0Lk#`0 ?WjVBIENDB`workbench-1.1.1/src/Resources/HelpFiles/Overlay_Toolbox/Vol-Surf_Outline/Tab-Vol-Surf_Outline.png000066400000000000000000014123041255417355300330200ustar00rootroot00000000000000PNG  IHDRi9d ciCCPICC ProfileHPY_Das@ 20q`Beqր (5 X wEA9&Tnz_ wrDXTa8ۍ=<k7M wwшnqV4.Pq4n*g\8aЗ.B0M,1Op|HgdrH̼B@9DaƩfa?SϸeVxA(_nVjJrH)3FQoE)A ~0,dq ^' D>i }8A,oφF,-cOxY^+y@%0HA "=~M˻et l!Ԙaina?f;{ :]Y @ <"f3<@ЀPZ@ {i + (pA"HbցM `/(ap @38 .: #A8Q!%Hҁ K 9C? EAB:h TCeP%T BP?tƠ'a f0f~p(ë8Uq _o<(@(wT *6 P%*T= ՍQX4@>04].CC7;7C W 18`ؘH ǔ`1.mcvXl6 [=mcI39q\:.ww7}xK>/oƗkg) A@$ل#6 a(K#:CIMRb=D"iIKIR.tt4DH#1 vQr; BѥR)%c)['Q\Ij@4AZG%B:GD q GfLL̠̤,UB6P6UHVNNWS''wX0EբS-#. KӣiIB Z/mB^NZ>\~|9y Eץ)S;O ,xm  )*+(6(VPTJVڥԬHlT9Kr""EEN-U9ң2*RݯzIu\檖Gژ:UY]GsSza54iZ Z{:&յhӮӾCa$yUYwTOQWPZJրilp61L4,7a c퍅Uƃ&dIIɐ)tiK3mh]ff_mS̏?lfВkYnyˊbeѪꕵuA6T%6[m:lيmmb*4fycfG[tS88&;:.[a'M'SęEÅRU˕ZeJbgt3w5wwp_( ,|^3Ves5 _;~d2'b%%< 4@vGAzA~Y]|`u!Ԑ!!BBw> Q!4\y=J9J\lﲑ;Y~uVJ< Tq&qq\w> +oo,)8YSBq(߉?X8.p ^%$Jz|4y:%"!*& ;WZ_d$IV;޻zB'NҖӐ`ӓ]Psfy懬kddfo~ZZڎu6ZZ_cƼ#޹67%ouoDliS||qVǭG/wն۾ ~.]'laNN;\v+-)޽dwƞ=o{ĺ>⾌}RҖw\XvܭBb[]R=TxӏVzW6UVrXHV.n|槿+(B3y pHYs   IDATxg\H'{G`/`A ֵ׶leVbAT.^%a<1pQ~ɐ̜9sf!0000000000KmnϿ}:U>5LU``````A*++Uz Ia`܋ gG;v e=u/C EZ-d"iA9ID&_Y[[ hUG 0V1%%VZ)͍;v' YwYzF&f88@9B;2|jAtttpppڵk#3000|| ,DCC :@@TQPQfEEEyy9AVꉢhLEGsLCԻMQoB#8*f`DDGG/^XuM2ȑ#*1# ...[:$IjjׯeOyLDj`occ+ڵkbEє&vܹuZZZՓbDY[z3 ׯU;AfiS^^>x1c̛7+W0LKKW^+Vprr,Z(;;;""$DEEM4(%%*=Z ܺuKܹs̙}~!ѺI&Q>x`ӦM?U|?1k N_۲00|ql6@Qk׮;/HY[[iɓ'ry"+PղlL9g̎JKK%q;;K޽kaaѳg'Nhii5KO*88ڂ=$Q Bhݽ{7$$*z )yJ*88pرA:99Zbڵ+::Z \r0mڴ^zm߾}ҥ4 }(--}A=`bMMM\\ҥKU)+C}k?>qDRRR2w/^899KT,L?A`D(R80v^z})*\.- $˖-KLLQ;;; P(D&aeeeUC8^sXIWWWu)))ׯ_o׮ɋ/,--b1III0`@~~>a)Ez(*()})`iiYRR:Ғ p~)A̛7%$$fkll8p… qM֡6mG 5HLL _d>`&LBСCo>aZ8󋌌pi;Ɗ Dwtɒ%AAA&&& H$˖-{ؘ,se)((~Wȑ#utt?Bŋ[jA&FFFݻ͛3f899vkedd\paȐ!0bvvv_V= ڐ L}D"QQfBB=zx{{?T=|pyŊ*d"ҪU$Ekkky<aeee?4h_z5rHDH$JMKݻ^mm]fffi׮]+*.vwsPWS' </;;VVVn"N.]J7MێP5mwiǏOpG366labPP̙C3t7BJTT=ARtϞ=666Ǐ6m `ܸqfffgΜ;wnǎC kkk dffbŊܹ3,,l߾}|>_󃢮\e˖ׯ_'0`ǏlGfHKK۷oߖ-[.\ЩS'kkÇgddlڴ cǎ˗+p̙իWnذL|q.]p0ɓ'ǏgJ0iҤ_U__{RtͰgϔbQ),,,;ao GU33bj6%2Bύmڴi @A2lg~w]]7oޘXZZ_|wO<6lDfMMMxxO`mm톆zZcc#A;8;?Mz~!T4Tn߾G'6Tq%>onn.{ŋ/// K.ڎ>>>eee 7nX`|˗7nիW'LtÇO0!!!ʪJs";?cƌ{-^8;; ;>}:3MEHOO_|_r\Ν;azbb"ҧO Y `۶mԲ} xeccc:::ϟ??wʪwL00@hkʨ_T*=p@uU¼rggg DIII6mرcyԔ>>8p3g.^سgׯ766< Pŋ}9s~2Ds*++7m4dȐK;v,55Ç0BCC@HHHxxE>U!~bhEٿ۷s8={:uȈY~5n)v5{l===jGDDD%f͚aÆ8M yMQQQNwuuqFɓM6}vgEuRe`XUijjjƏߌML<ݩzAȝ}rejii޽wDG[kVUվ|ng:&榑IQQIFFF^*$bРA:t(..MIIIMM]z5 ݺu#գ^DfcCD*yqΝ;;;;UWW~ê*Jtٳ''NLM.ClzOl5\uĄ8YYYǏㆆrȐ!gΜްaCTTA)))AXZZ訣S]]ֽ{wxۑz2 7n511)((\.իWɓEEE7˗/K$6xƍaÆQX0?3f\222^x}vX&</,,Ғ,bH$[tW^}-[.^XVVVWW;8wܙ)v>߭[ǏxΝ;oݺf`pHC XL~ ZZZo\vM,n4hAvﮭQ'2𤞞^yyGccܢ-*ټy0͛7쀀ϛROIR@[WW͛ׯ_9s&LD zuu5LoYH+X__ή[n-~5kVLLFIIɪU޽Lw0se> <ο8fӫȂjlluUWWoذa\.A~BBt:84fI`_ð~} FXuuupb+$6&FGGǣMWNdzmm=UAk4)Ḛ0t{{9s=СCk׮Un;Re:8:|bqyի ;wdll|= ZYYYZY+BiYR)Ñ$Tg adQs\KK|Xw***ƌSWWwUY$ٺukf rʺ:rG֭UUU:::irwKYW^%OQQoJf233fϞ(۷[SS3wܨ({{;w uI$k׮|rǎpboƬɽ{֮]K)SW>vXv`ÇZjE+M68~ʕ+W`%>>~8444k׎)vHmmmHHH\\fyyݻKzվ};5kTW[)) u& yTKb~O 6lΝiii?rssMLL6nHY~=\F >\!CjՊ}Νy͜9s+666ŋ-'Z`ܹswUQQv7A]UU5wh77?ESN޾}{MWWWLs5[z8wԩSwzG͛Gܾ}Ny-옐;B֭[w kk+VTTT8p@*>x 33_Ie '2Ү ZzЪU=΄TZVV"/dehvJUd7oRٳ&LχѣGo߾ݣGWN2E3fY`G5}@i4|c]]ܡ漣 :|ڵkO> yXAdd$~֖H$7n477C[neee<{]|+Wbcc9ΨQ֬YC&Ÿ ,ظqcddd```@@U 6lP(ܴiӞ={٣=roaT*Ds9s>4 $/_P]] w؄U|oXZuuuA2UUUY\?&'QM(˗/ӧ mDŽsssK^'ʨ]vRm۶E$;;x8^AE322y<ބ S__fá]B3cde8фȖ"(BF4)ٳ۷xǎ x5kiӦ_k׮я?jR&j׶MBB♳g<=u囹) KJJΆ o``G d|:uj=;B&\>i9s\|y~AA_( Wb@ 033Ʊ¥ɟ'N=zD">m4xJ[[۷dyA7cǏ?EQM/c…FFF<l6-JgDSSSllGf7MMM޽{ZZڄ b qDW^d r۽{[^8'&&vyzȑ#2֭5jNff&wRΝ;0KkE)߼yb8rEMMMy<m*YAp@JdD"##2 .]L>n۷/&&F$AQE= 8::.$|C8.JiA{899 adFMOҥKϜ9ӭ[7B 8 7m4cƌ; mɒ%@$l"p#..N6 n߾ݱcI&å4l60lԩɆb`(J=r555a|Wrrrcc#ZZZ&&jjjv '&+ ?71\d+}VKKk̙ԡ^++Yf4rU89:ZZZd)|ӹsV쵵4 Ȏ;NҝI+w~Y BeժU0/OJ~:4jԨf]?׏n.0%˧`ԨQ2(o߾!h֭i:EQEQmmm555]]݆f5SSS'&Wʜ2e҇'W&޽;(V'O$Zmxm;Hͣ')wlmmw5a20,FFF#U_~FFF*ffGQb``Et>UylߨL/Y#5j*Cp$a: CC1cOU񸪿k5C#Dܳe@w2SRjE30|:S8f}Hf@vVn"#sCR#DNM$+;B#u1000|F5yV%e8CsLm)En"hr Aiiip7c`xVe011;Q544|i]>CƒGLnadLD (ϗ xUPPPIIIKݞK,c mGfA9OBҠǑ"?ޱY100000000@|.}F 22򋪣 tqÆ 22MǿigR45gmccV2e t)G*9rť[V۷onTH=nݪ+S0&Skjj^穁 t唫r=gM6nnn\.e└g}}}֬YcbbB-:@{9shhh|2aǿ 8h@ͽ?p8˗֥hJm#?ޑ:Tmmm ޷'ILLȨdzzzZرwlDP[[ -!ɵY%m)r瞖EŪJBjH#k C=irTXN ʬ 6::aԩGlʔ)>|8ɄU;wDriw}鵸Eu>,gs'H$u)iG|E3,,, 111,o߾dJFFFVV?Hȑ#,YBMD&º)-- 0h8R0,99966nFFFg߾}h 7a-YJ-+MGGGI뱾tx-+q8xիEIRD"9t3Yl6 p }||vHu=2||ۺ IDAT1׬ P\< CEvU3..K:WTu8t@U6M=ZeFEqoVK{eDfff_܁RYYt[nn HOO?5WNCCΝ;ۑF~=<..n9Z'N m۶k"q䒒+++;;=zhkk5J5(|@Z=+[+J-+˭311yyy|>_63y EM3Y=Z@ H$ t_|VVV~~ p0lm۶x\`dŋɔhEddgSuߠ}T>54\b*^NMndA tjFannae̅y\]]cbb0 #߁G1ݻ|>s*>2^9tVm9,E8Ad\Kd%m}A;@*޹s֭[&&&:t֭ݻȑ#Æ ?~mqFyȑիW83zՍ 2|(FC#uFhݽ{H,#b0L* AH>Phq}1 SWWJTJŋA\p]vFFFGVYE>E\hBPyd={̙9lA-qgB~_nqǣaʄ Y xbD2n8@ffiӢ!a_\ AJ#%;gglɓ'WTT <855Ͳ1 ۻwoXXX훥'E=00֭[wp|+++WZ H޽wZNMMɓ'NP]]?ٝ;wܹsgf͚5uT;v8qŚ:uɓ'oީS/^̚5ݻ_k"45x7xmeeؘhnn.JE"i!H733suu}I߾}i0N<rƍ?BƑD2ðĂ1cQ %J={vݺ\q&wwWjoi&؞JcٲPAA/:{z8ٝ۴xYYrJ u(zjE111eeekkkOOOY[>\.W*J1 O0 ATzԩsJ18R1"S.9l0ٔl)AKsRQ˺en2feW/(Jq%A =g'J$ǥR)O6?ׯ… ̙Ӻuf}Rp9o%EQG7#ֱ !J~^qK8P{'O4Y&deeUVVZ[[Jv?$$zܸq0ϬY0 Ϟ=;44J,O:aYATWW'$$lٲ%$$+W9rc>sNooSNeee544abbGɿR*MTd_JMMA$"X,E"D"ilWTT)G tww5j8Ç/[k׮-))ѣNJ+>,T:\a&y@Į]:vl g#"==O8db`fE"""VqqprqoPZ^nkgٽ1c(r928qk]ȵkׄB!-?bR+>>ҥK,d޽)ze׮]G{ŋ]]]>QEEE-"0lΜ91g 1 Ci)>E\d#;}}}RD,H$Bp׮] ɿ)>>>鍍Sԙ|˞4$?1X3~wJ2w>*KLTIB \*b8c8F~Yt֪U8 Jg͚zq5x@5m=jjO,)*)=B=lA8~< [nѤM8~ɓ'cj3:~xӁr:tptt߿?L|Q߾}?yrTrɰӧ;88^͛7 Y /^׮]+W 믿v?@XXX߾}r'NH$ǏWa_.++++++utt\."H?Q,(ttt=_qC -++ 566޳gL455]n]RRс`۷owieeu/Xŋ˗/o"Qٺo߾Μuhjlbiemөgԥ^^^ϟ?[Jq|umw =K+EQQQr2<BGGCsQ;UruC,n:88̖ (--=ra)WOE߿?!!رcg̘ }< p [|->ty:Ի466R H$0D"JR Ϧd9Qn)JW~- `Ϟ=rD%WԄBP(P(6B~/n۶m63ceqeUEZͷpl J8G=lܹ?E***rk7ocj3c'l\?ӣG6oޜ۸q-Y$>>b?~W ۷0`hӦ ٶm[29|E *A~+qXMMl蠂kx <O__j;|vZ'''__ϟ~ՕvٳgdΙ3Ύjii(j``1,DF555C kvpdXV/?omm[ >*bׯCO<۹Sbccll`4}SNvp$wrrܱm |S3 B%dAӍzK.q3gH$`, g"XȦ 𸲲rm۶m׮… SN%WNYbŔ)SǏᒿGvqqܹ3  2n~z5TBc<}U"JNL*-*.&#;0kJRGɖY ͚ Ӥ݇#G ~U$_GL>U4L5@PBDk߭[7]]]--ލ(7fr0IYS}GuV߾}U !ׇrٖ+//ݻ'Op8##VZ .]ܹsY gffŸ2wӦM6mZnnڵ+ ݃򨪪JDQEH, B-KKK@~~aoo83hРQ3Swa4h?/sƍ\x.S[[c#G5:tpѢgϞ)i2[--~Õ1_Ϟ?ɑF,cX MM2Coo﨨hϮ]QGѣGn\B#܂%"""n߾=rH8{gΙ3 {{h bŋ]VUUE]]] }}}SRR"## DEq<8p@r . ^@g%JKKGlٲ:=zT+mgW\yfG3Ⱥ ٨WՙzI|>C`G p8[ >/_ͭaӦMJ^r%pxj,mu٣}#,BXG泹uTv`&$$${̘1\4Q~EEEݻɓp9|6 XYYwª*E IG#JBAß쨝T{{{==.](#ӧ8qٳ۶mSMnllS6[Yy9.f=ڶ[bիW ySLԔԴN]5<<ܟ$=tYXZ۷o֭4id0"y[;*u-{>L461s̙3wYFވ /;6d) Y~\ڷoANbt=:u)S s1Tz߻woW^ e~-???2ChbTUU;v oߵX ʘ]D"Q֭_xQPPp!???UH$oߞ7oH[iiݻw\QdT*=qϽFVNgiӦ>}PTTkff6`ѐjQ\>5޽=zlݺUMMĉEEE/^z/R:7o>{mpp0 ܭ[ٳgoݺu֭>>>@=H."[蘐~o޼yƍ_~eСڵkniu5xjj|30㩮IwANH0\$-gm2Ά tߧO@OVܖSwww2 lQݷoܹs;u㸓޽{i[N:uر*?}ʕ+vvv+Vu\*55uذaFin50|>ry.CDžB&rӧ<?Eǯ_pֶ޽{d ;;;@PZZG VAN0bToZR]pr9\ )J#2K] ] Z$Ϟ=wޤIOh5HˣG F$//W^}quuuDD 555aaaSN,\ѣ߷B>>/S˅ ##ax<L z( Ν %*z4O- 544ܶm۱cǠall Ȁ6}ƍׯ0ww޽|A`NKKSN9s̙3NRrk׮EFF=./7uY;AΕiݺ5Xڹsʕ+58 aaa.\9s'\\\:}SYTyU̮] ȅ cnnH ԳgOAy<^ǎ\.ܧAo?zH$DDEyX,X}]]]/\@( BNEVɓ'O:599à <==O<XKL}Cc Ի *l" 8i$3f 2)'9//O[[ځAQVÇ'3Q]s6 ;*(-J'"==]*ZROٳ;wVXҿ@zzkRRR<< q߼u;Шg[XqG ?Ο?RiMM uUi*eee0 *picNN|XENN? 8Aٹ(##!rmXUI 2&<E1((H$xWR'z5y䂂++++ٿٳg߿\4׀<$С IDAT$_$hii 6 }BK`A/(_{wAnj4PjZ.X4iFFFk\|qe>ԡrٖx޽{, +V+ND _eeeHC#Vv63gTԜPׯ_=:u)/-XZYyzܹS60[ڃۺt\XTl`hn"WWW͠n&3Uxm;hnAE+`Nee%ŢSZݻwԂ"}hjj38p۷///lVu;yJKKOGQHSS_;(4Ǒ:xl[UU6ETB֝  hlly-LO[311Qᚚe7lP6~~~+/߼yD*խ?AqQNnuup'O#=mĉ % xYfR]u>(׼VEKH$bX]]~~~~999Q D򱱱NH222|}}7mD:\vvv۶m;~ɓ_N7 -z ApycR`V[[ i٨Peeltq9s4==}ٲe-5Z.2To߾aX^rtpŸ~ N%+V/5550|4hii)>%ɺu몫60 ͥi5ꆆA=+nHٳ~w'ʺ Ӥ۷o(gaagJ]PgΜٮ]f|U7/Ͻ{efڮX|ƍv b7om2_J5 %Qnr _fr-ZDp.--mժDpYdwx۷o2%a)))K6F־}{꬝j"ѨFFFgϞ)//mqX6Yթ}9 irUQfnqܹs?rK.*10|Oȏw$r84[{f'l6;N8ߵSǵkBQE J\ש'GR) ٹsgn&c";Ν{)Sp]vplƌ۷oshbbbAPPPsctʾd*k'D+Z'PESl|Ŋ2(џXUUWQQH,AzRS\T熄 6G0I]J>_<4ssn[AVVVwR!&)))33f۷oߞ}C][DIMM.I#qss#}0rIv<|r:RnreMFEzKܿ]tKS}lw.w64(B$BpttTSS[ȉÇyzziӦ^Ms}6zܹ7o@㾾Z͍j=H[0S ѬҏwճCpOJJs\.eٲI[]l6 vmeJ e).c몪*‰sK&A5@ LL|ZįEe G ^|y ;;9s b7ZмhJ`giq J{4E&riM Gk=Ibt=33Pѱvpp000D:P@ x GDv(_Bjx%8zd' E|>_6U ɐ'1W%tAId3ȽD^apG|_BĻM 544[ee寿z5)T*9s̙3#_\ AJ#%;ggRyGٳgK}[@O #۠| F P~o,A`KLou!ShvGa#Q @oooC##X CwY<|Ԕ޽ٳg,\=aBpzQQ㲞OYVպʗs&4TC%KƠI{/tvvnq<ٙ+RSS˼ɥW.SCC#--MIdgZZld73y@Ymmm׮]׮]kooOȬJ:=>-\/kC MFX@+H$Ђ$/6l;v pA#F4m0 p9o%EQG7#ֱ !J~^qK8P>tеk޽{\i߾U_pm۶RLHX#/nFEP؜xD nX")_w'Mq/ MzJ*D'O8::KRHDΕU-ApOȆp?^1`;;~%|0-[w1|ي";QU%\;oΝ;gii9s={l۶#uuxR1tgQo~~Ξ=KڎΝ5j رceeeNNNVj׮'OgzI`D!T.;,(hEbkl`/+j[bׯQ`GEE4 E"E:EYg3[gu􊡉8)(HTKnn @~mwd*+**޿߳gOWUUEQN8堺[XX}5*$$qC'+q[F $0Sa٣;?_E_5п{VsMQHBI)౐0TSPjsDB'o IĄc%J---- Cqs^reذah}666رch̜9sܹ( 1ӿ{B|>rB8d4C _BCCQEE"LhӖfLM) *2˴ *[0R6C UUHTWW߼y355ikk;99} ո ~';wnXCPr8:u8]MMf#RYnzqPPj.VM{ǖ=C*:Xwr\ !=EUcx<^>fvk^ 0̄$ IAhɌNѣ'OqFt7++k̘1 om=%̻q|Wʿ6 !A(O/uW^ݿ7o_,_%tE$dggggP#~֭FJ 0 @ GE:v;{;ѽB 1UYC$Y\P6cjD8ŒC$rJbX/^\tiΝt嬬, /x-b^( 82k:]W l Bx@/SYf13Ypqqqrr3\ SSSDÇweͦ%//7o3Ys>Flܸƍ:t( H>C">I*bYi`Y'\.F"ښfW嫌Q} <&ygh; RPBR5b P`q>|ܹsC/^@E+ K bw}WV$;UU"duJo%eee'O:u*ͩB/ؾ}g>\SS jŖ#G={2\+)IJ)E;P  BQ ,Xm 1mHWTT8 ߈QZS%J_~ݾ}{G윝n*JuTG^x.?@f ##c'Nݻŋ pqqrϟ?G}US@ nK677Сf\JKK5InܹٳzϚ5СC7oOOӦM;w7?v5ϓ R`Rb(6aMt^25j x|. @Wu p$ITװ 0R0M&L@ }G+K.D":txX,vډ'ux=t#G췴b2%Dh%լ U2y$)X%$W^?rXWWWKR$l62p8Z`وD@\hv5<<,<<ՕO666Ϟ=Ch)((HHH(//p?|푑?éSy׮]KNNFi&[@VH#*n1рj*`Sfי@]WWWJRX,HD"QpppuUDŋ՚% ؑiv޽[g8blÜlp8]+2CyvvX,V8ڪ4+v267\,1#aU|ܸq$^4hP=v,o4cƌ$''۷o/X^O~!A8FA11  `8X?10dEF gϞ]b9jX,dr)OOOfp;VOOBŋу({{`K͛s޽{-[,&&&00p;vؾ}'z5gΜ={'O.,,:uX,0aʕ+Uf,lɼ+ewM`jx#K͛7ڵk7dP EQ$IVWW R͂9w[}磻\.ѣG]L3.Оv SXA+W0B,5R#ooo]a`X{ yᧀ(q$'׿X=L:U&U:޶mۆ iӦM6 ]311.\ik[`x@D#ޱڷoH~Ѯ#B۷o}hjCԆK^ۭ&}ctMosP(HQBˆ6uW̑4%jiiۿ~z۶mVaP mfiiioo/822U3f ѣǫWPMWvE;<}燮ݻgll !駟uuuqGB###:ŋh-Zp|Η<2 Xmh5eJ{n4f8Tz=T*RJ!\zBfllLg:"nPʔ+;C#VuM:+b,O8>|+W(  G,qNa_DD\|zfGg٦*8qjМn#$E;;;SSӄ)Sݽ{W ҾMC\,)P8nkq.['o@0ssqBHA(gkqhW6 .F_3{]jAWttgϜ{-G}@iI-i:TRRҥK#G0|V6[ZobbBضm[PytM$]U3Q_}YM IDATAA7 !ݻGGGwԩTB#J$°ΡRܹsRh6JHHƝy"PQ&Q(o6"5Hs3Cȹ%̥QQAPa:uZpaaa2H BaSZpd #Ak׮]reBB={,Z82*yGcyeYVRe\RX~W?ijW )!bdb~eii9p@KKԐᘚ<Ȩ֭[rrr||ŋ5 @Fe.p?#t̋C>2Zv=-.. |ꕿΝ_~CEGGwulRȞEb,%"I !Q~ @f#R4+j _ >={Vc! 믿/ ôU-stDߖ9::Ξ=hYp8F&MbX_}6AW;w.jhG֮j2%b>HY]\N]r~>e!EAAyw72yH T^hbt%%%P' y<^2ѣG=Z2.kmm{̛7oرh?(++۹sH$ LIIѣMMM.]ZPPзoߠqƁS+y <ݹsŋݻwYlYVVJH Hy%,  !Q/Q~-???$o"5E3S!1Eѵ֬Y3sLuƥsΥ(^)TW\INNp8jEZjkt B8eʔɓ'~Bhkk4Eذaa]YY2V"xZ1 м#Y_?#n5|. ^RV]Cj  D"acy<2 6h,HyMf`ׯ_G+ _uttQ$ɶmRSS  s˗rqZ+w,--=m٬-[a֧O$"444<<<77w,^Ν;ui-Z.fΜ믿FDD̚5O!D96Ea_Ytܰy\! /^,[L*".C"9rd^<")g߾}UUUوu4}tyq< ˕!6aN{^Z$o~6668p $$DKKK.OOOO߼y<%2<+s t2 KJJ㋋U0?sdrw^,fXDH$rY( h0qBD"JQ!!]tNQS<\ `81a\dBW|>B ) BI,/.!~2C25UЌN˞2?1(ӂѹ+<<<III"9S{zz=zFFFx+WXB(-8222(BxbΝ;ذaÒ%K9|NNΝ;ܹcjjJ633;wn>̼ eee߿Wsu"11gOݻ)OQ0l0u:~շӺy\)@(p x77B/k|$IKKԩimXl..I6& yG)qDW|#=1!7/_Dd0;}}AȜlRPP0f̘/:??_ |YG\rɓ_~Oݻc"hG2Q(۷qqqGE8cƌŒJ?̙3ؼƓ&MRw>}4<<\SxG:6(kwl9 Ijz -5fLzC"C## &Mjjj.]ԧO> ,BH:$Y\\ S$Hjjj]}||Pf5AJG/+r)6c@A_ѷ!&/L,Q[X,X,NOO033#p 8P@EAYVyqX}ɫHcϟ&ֺ;sl|Qms3V.qV#E ꣃL"/X{9}9sDEEyyy8DXLQԜ9sߟ>}>|_˫y|xڵ'Oܸq#\}>777..G#x/Tb1 [%<Lm_愥TZюy 0OOO:Jee%}p8$I$)H͆wP$)s>ֺ &&J?~Lٳ#ދ(@P,1  C(gBIHs( !;TUUzssz񎕕o߾m &A'Nx_|ѹs6jdX X_|6cƌ^b{Bfbh$IƾܴU[vlU +omd>e~86ݳgOf۷mllRvӧOGEE5 5\zgϞի?>qD[[[Xi&;;VUUݻðkwEGGƣFټyA"/_F&/^@(cccc=*ٳgϞ=}&&&Vj!D !10N{G{ыz,z D¦"D7o&&&g<KӧO^^^&LPI(R@VV֙3gƎ۽{w՗0ܘN3<\`޽߿߾}{Bp{ȰJ.ٿj{G2Ja0~ EI$|>ǎ躴tС~~~NNNz$I;TO|0d\><+k]C~cEb3GG۷իJDEEڵ+!!A& 'OqƎ;B&&&'OnLd,GPD󩰰;\hJ"qX {#ݡ-KOzz#GSoqqE޽۴ ]p'OaÆ : ᅨP(!wx:tcJJJd;qƍ;vDAbbblmmmmm/^Jŋ]\\ڷoOw磌mllgtHKKT P8dȐ;w4w|e9v)h" 1Rk8((H&zEUUUVVV֏?3gwٳo߾}T>iҤq۷~CAՉ{gff{ʔ)fffÇ?}|47Zg ,744i-S⨫PLpvر{n HvZǃ,YҫW/$;wo>R_|h"䇱tR _5q/^ԩ555q<''ƍtFQqƥHR'N"p\-{]v}^r_z%cW^^~A@n݆ xǻwdffx/_>wJ;r:|L0ciiV6ȣem>v;k@yyy2vv¼ 5Yǐ/;{̯2Kg˙Khʕ+Ǐ@O8qۯ[U g8K.?y$88fZ ̆*<صZP ռUgۏ7nܸq޽DFF2SV ^z=zTPP`hhH@j,Y$::>33stFfV8_tiذaț))--p֭[#_XXPHa_'..nСٳ @kCCÞ={mٲE'mGyXX؉'#fϞB۶6#ޙb16P9e~Z=@L6<@ PRPA"??ӦMSZ6s9K{ő׾yh9aH(6Ai);&%%viddԡC>_EQ}www. Ff*ϟϝ;׫W/b!..C3f̠uأ[n5fV zYkCS@#SUdZ$IVVV::&:J1(4*㮟r LUVV3yc~:--mС .|_~;w)6*--QuNNN||_~lٲ3g9rԩS]XX:؍1)CRfذCml{G&LMMՄ-䅭<6 .;9959&@,b>2dHFFȑ#:"*_zEQDzd'']v 8߿… <##iljiiΓ2-;U{;Bz 4Ks}C@)S^3f,U;B\.JGeVCʕ+ VXѰ%Bi ye v5xw/MoffBT ގ1Tfgg,ևΝ;]١Ltccc<++r6gllO?[ɓ6lYUހEc֬Y?T* mq9ȧ:lEUVVMty|.l`|1R_mWYYqt}־~ɓ\;`@PPPhhkא:Q^RR \.TZ 8 Lߨ ΐ o=,\.fl% J(ܹs.]jLq6WO'l6A c=CSoaK qٳLׯ?tG h;Y ,--ѣGW_HRS2B/YoLu}4#- ż#SUMBh-h:|@Ϩ̄Oz ̴JLVXRN BٙIRM4իׯGYYYG@ չsg4D:d*P7;;y䷥s uJEbfr|_=7Kы۷_z" o߾:::C(..VXJMrVzP$m aYF+sb6gɬ_ɥ TB[2]ّ[݀%p8\.bS"a&%<33sӧO-//G|>SL y|# p8wU|mC#$I5l-T(G9rdHHO?4{lobbr4666mСC'Obdp8iҤ۷C n{UC(//G_Oܘ󏬹VP5yYX@4AMg83RN|w(@W,5EPʯycƌ9rȅ ***Pj4Ћ/5EQ/_TeN!Z-hۯ6|r=z7B$0 իjFOOʕ+H:xܸqY P}}۷o;[n]-%%E}yN:"KgϞX,!L4hR3$Q[jK}1 vuuE$L,(BCCCCC°zex0̊:-@:s}}Bq5W[ 먺7 Ç^~QQQݻwsYQ,Xh[իÇi?u@#PچAT5Ndmh a[PP@ sT\h{u:.AHVUcc>wCg&y3fÆ ca999Æ חJ{쑷G:ueƍ?!:͜9>}77oj|FtݺuS蹥dfў, 5+U%IMK嗿blȐ!!O3d'a``ЫW/Q^VV(i׮m++kԨQK,yܹs###,XpCC~| &]8lذ^f쿝PǬWYH\0Я\Riff6ͻ׹(#Bلb855H$:~-[ia^^Qx8ɍ$@ x72hp &<~[n2ӹNzT@WWmUۓNaeee}IjR$D_"Xʬ,s ^z񌍍xʕq!k_r믿M5o%\n}HV\STT|rNd t]UUЪU~64n O_>9T84 iV޽{ر;vMKK.ZQuC5¥@atʪ/nSL &MB%<UҪA3$S!U ߽{7vX:;]rm\ß b{G/$ +Iiiiqqq:t BeqqkN8ѯ_?GGR;0[(R޽IIIȻ ꫯ<<>>qynta̙ӧO"R5&o"&09.Bn"SS͆5Rh<2l_~!6n܈*=~~~111'OdEDD<}7*e˖Bmo޼'pQ>m(uT$I&%%8㸡ӿ넄TGBd^&={!8q"[aD" fΜׯ_nݺu֭ݻw> /^ץKnݺ 0 ''9 V#__"D"D"yfRR[7%D*JEh6430 k׮]@ڽ: ӊnK7WNS@ WB%h@Oc2Ѿ}>Ã{>a„ www''޽{;;;3߻vouU^m4k׮ܹСC'Nȼˬضm[n܎=z] K&YmwDGBXPP>}bVJT*x>>>&&&RO>tP`` sXvvH$8p-7oMUUUnn.a񩩩 EA:`ooٳgh撒!C1cǎϟ-\NNN;wիW.\(C!Ik|av(J|T*%RyW^FFFSl 4ԑ/Ztv3g 233Ɩ _Cf^M^i$_JJJA0*x"LIId# ++v(|̾llOLʬM$C5s߆&yyy-w҅UUUQ8Ho޼)//_~Wv5v޽{ m#cztT,Fڵ7npppn}uZ* f !fپeԵj t ,(..2dSRR<==޽(¢F!Hcbb$F iiij^^^L5B_2:A "#%)$Iwys 1gbwѣGwڵk׮/^Dc䍔q_`3gLR)߆zzzwF,`EE͛߼y#|ax9taooOeP_A]+QPaKNФPyӣJfY,fv]#a---JGNNN||qǏ{>08Ś5k >d77@dp¡CoȐ!(ggÇ?ð'!O'fddxxx8;;#fyqBVIoCo/_sLUСC|>YsPR)Y`q. -TTCWI}5Ba߾}I|A~Ӊ8BÇݻ3$>|HQTff#?~SWWW.6qؿ}x -A{q=yð;v{nڴibť;c y{{ѣ_~QQQV3R۩|5 x-{SIa~ⵀVU3!H$hUq#d )*H$J###?Grwܹm۶7oXYYs͚5-B!rq̘1tBOO?S$vpr`ffv}ŊȞfڴi>l׮ݢEBa^^ܹswrWܤ3g?~ĉk֬ILL$ :a.$IΛ7oӦMOU` hiVxQ'QA)F?.As΃pA(p:ȑ#7mԱcnݺYy좢" u7h ӽ{u%%%u޽W^N߿?FB{GLܹsbŊ%K8ŋׯ'$$<}tƍ׮][fO: ;7o>}`ty j]c6*nӃR{GCCCPgvׯ_wڕ4(e WkRiFFFϞ=1I.]5jTPI^^ޥKf͚w/sC!A`+Ǭfy)v .]aÆ={tɹ\.zHCCCݻ,X9r˗/|ӧO4EyHHIW}`>4N\p4hreСD'OtrrRAرc3f@QH Ҭ)Q\~j*rGoo[nذ6QWݻw Yvmn֭[+V:q渭(q|Y~}~ϟ_$쯓5_pP(:t(AOmԭHOOņR*$sfv@Z]]Mdyy@ `jjj ]tINN~رccǎUcEQ\X5|}}[6||cmb*S(BF0t5 ÐB$G@׮]Pey0W%wu/DGGgÆ Տ=ennNɓ'N:%߼KSXz!Cې;7@V<"YB :躺:##W*JbD"%%>>>^BVeFinfccc9i6oތݻw_tIOO &$$$ߵk5{Lc8T_]]axփ z8|+**v M N:ɌR+d)\.SN[lYz s@njff֩S'#ȏ.: 5S2 j74&}2U5ϞˢnݺX~ 5%gĈ/33S,5Ǐ>}_~i@[+pȑSNAa8Qƹ3G!IeAv,(Nv  (BXSS-Qj0O1`Xll]xڵuֹ}Wڵk׮ܹs܊9s,Y M4)**?ON؆64Juִ.q;!!aŊNNNC ҥ JIIqrr600h'VF-]햚GW믿 ###]]ݝ;wk0f̘o۶W^\nѣGuuuQLԜL0J,ٳ1(NCؾ}LLLA0nܸTĉY~}5tZd+n׷UcYΝ;mڴ,[[[5k֬X&Ok׮ExaŊ(H?GzWݻwtHpf92T߿yZZھ}Pk6mbb2`CCCy9)7ϣGx<&r;;_~%??BأGB@h8$wڵjR.,--dޞ!wK@TT뵑#qp755tҰa[ʸ)Daa{|||+H :-84foFPF0!ٳgzz:} w|if}}e?9cv~?6DeQ(wp\m2AiйL0 ёY_13C+ІfR @oell, tRRRRYY zzzh/XΦ>>>-MB۷Wx Ӑ0 >|~qq[  b1GbȐ!#GDE"|<,e8sO?ݻ 5(L:sLYYرcw KKK#""={ӳgOtUH RӧO// &IU@ ykĔE FY+\jU¬Bdʦ@+ІACzz: cc:899999uXƷ mh65ã-++Ԃ/]T(%D]&I2""&""BM+*33u 6eeefffÇCy֭[vvvRtذa.\Pa :AgQF &Mt…]۷oɒ%47X)})cQJ[JyvX,V5І رh<0 C'cnֆѣ[riii(3^N"q\u[111Eܹs tJ)SW J+'''f}}}o K.})%<<)..>uԤIX6SC˱>2b{nLLLff&󳴴7o2n Ls3#fiA(5 EʄvƼ2oC (2Rա2:O>@YS󎓢HSݓ6]6H^vYr YQADxx(ᔻEDw( IDATA  " J$QR6C}OoMu53ڿ7|]m۶n۶mۮ]^K,O:vث:ͥjSEԪU+VꅽDڕq{Zf鷃_z?~ >{bc^XXXPPo?~{\:wZlP]|˗/.ׯEpnPQ&۷w11u֟~}^U|FvvvIIIbbbFuO79ٺue ʨ R5P|9eyyyl?dt:n:-˲l5ЏC?njk׮ǫWfUNG͙3{Ǐꩧ_|#_¼*3v;v7 .\_-W̟?_~}եp8zowi#"""22d2iF4n163 䊋{'f̘wɓ'/sΕs㼼_~_~{wޡiM6I&ӧOɓϞ=;k֬ݻ>}.\Ok]v:u˩ʹؼys߷o_[oݸj*ze',gĉԇ7?ѣ۷oӡCV2T~YjٳgyHII ЬYSN]RYfobccoHnipwdjiv=66jJDb>k1ݛfsddn$pҤIm۶={_:lcƌaaaA7Ƹ[i{ǎ.] gϞݾ})Sz ͚5kFGG7i$??C[l _ܾZEo%K.^p87ngee5nX A+vu>|n#F;233SSSVEfZpy9j=p@۶mA${R5MS%;;k׮>+N>}ōY; ə4iһ۽{wށKNC鷚[.]^'uFyy9V.>>AԾCӚDu-Tk׮e$%%ŋ!=WvPuxxxPPPEE(JdXݑ!˥jYYao묬ۋcAsqx<ׯO M|BU̔;vp]~gNJII;-Zdɒ{b,o΍(5cX otvqĉݻ{}vMӺuVW[yep8tG͖lٲiӦs !$i%KtPԩS= ѡ U|)St֭W^cƌٰa%n߾}߾}뮰 &/'OꫯN2 Z֭KHHx뭷f̘tRڸf͚!NrCݮP⻾Q=;t-33~Nf:&d5LӮiC>xBp8Nw sUå<Z yO={i7nlx9s7on؟_~;w͵X,G_ܹ?`i4O+9S{k&69!>^l-[TUU? th{ i*2O !z۟ -uN-(Pő1ѣǶm&O1xt8z <ޣG}uw5۠`ZzW;>:PY[3g$%%h6'C\.媝k:)V^?0ƧUiMDPp8N[w{l U_&Pߑ1y䲲.];vl3g/ e㚓B=4!$5}N8@QaJB5 ]!!a c&7_d~euIҠ?^|9Leku$IٳsrrRRRnڵ;|0=~DZ999:tPT)TRoBV:Exڡ(bdL, jĭ_9΍tdzgϲ9?#blq!4'""n}]6Ddt[*S-tA5S /:()̟~﫨FpIhT\2 P5M#ù;&%%%W58r(Tˡ>>:"zN)kI*UU%iڵe}w}d"Go;n喩Sŋ/^x׮]U)knECPb޹ cBDАGU#nH6$)p8 񎄐,} ;M_Rs ;BE8"?{}` ~t)_QQӺuk68шj{׿6Ue !rx?{:G.y58dMѐ`EVCi/ Z>ZƷݑ9ڎ^k4]S%Y]0[f}F |Νe搐Pw ~G!7|3w܃Ÿ"D#K]ֿ6ЊVeyhږ _wN;{c,B4ۣZ]-pnD;Ry4,4M~3zoS#t(Bi{YY|KtR=jȐ!/Ç5k3ϴi&33s͚53tҫWsj(ʫ駟&$$̚5}tr.]lٲVZ͜9<;wLJJ~o6@իWZjܸq/RaaSO=Bo}np+ O/Yd͚5ܱcǒ%KnJ 6lΝ;'d2;??ĉ۷ȠtpBRRRhø8"I;\*|iӦtZ^"77wĈ]VZ@6mn`2Rٳg_~cǎ(6Ο/K*rE̒џ/caA@h$"9 >#>k zc~ѣGӞ7k׎uf&U"A_amhECI5M;y۷ooXN<Łv 3nܸmnٲp2͘uc*n$`}%#9GSdBԿcRQ4Аjd>ks#;c9NCDEAD/fi6n믿ҖΟ?駟#Ƙo֬م $Ib(_x=lٲ%''!j*9t^z͛7o͚5A_ERHoѢ!dݺuPQQ!IWlH$X$lI4lbWyhؘA$ZM$ZLYf%,$$DSRP$ڿ{zk^}ӧS#MMQ !'N߿?wMMM%,X`-2nܸ_1RH5{n̙9rwcΠ,s~[͒Y/[ finx/:܌tZFQ$\ӏph߾}^NNNvAAA&S}o߾庂O7UBHIIjѢEHHU"u&BȩS!M4DCaaaPPb1Bg̘1EEEVd.KӴ M bbbdbh1BtGr_8l$@YSՈj.\T̸>KU.h_Wk!xE1LfY A*GhXzQQQ۶m7mڴnݺ/*jƔkbA6,`@0xC{f#Ahj(RG 8y7őip V/ҲeKdZp#=zlݺuӦM[nСC]Ip .elZaa6@? MUU$H(gwXёp"~U4PDQGRRRاO>}ԡW$>BF1PiapBy&_=\(s8 Ov\+^O7Uq^>#G :4--8QY=J߮5Ӡ Z>Z7 8J§O=8N@{?K/ 6n@`cbbxp87 WFdgg\.pGy$??% ¢ip'W4,hÄB_pn@|;tM6 x8I1zSsj&Vl~4hsj96Ep8s|999Up817ޑkp8 xGr7aјUUB5ZYQYxSsR KUU窻6h|Ts?o:\uHʣi!Ds-\᳣;v$SN͛77kLQTEܹst&LбcDZczlر3gάrN+\ۀ/^˫&ZXZoֵ[om3o=m80q:԰nګW/z|ݻwUӴ>l{'ک6l>|ի !{} 0uԲ26d9rksH5 PlX,Kƍ7n\iM*:t̙͛3 *//?w3}toԨQ:jjƍ4~8Ν;`,ԄKll죏>:o޼9s(riEQ`կa֯_hѢMinϢ7ooEqĈ_~رc~m Xx1c,\gƎzjAVZUQEڵk@~~cz^}}wy… w -[VTT`>FնmA-^og}oOӦMx|Ǐ9s~;ShѢ+VIi\Zg`̙n>s)_֞6mھ}^x~aΜ9={-Z_ٳ'=>uTzzO9׬Y?3Wܹ  o?Ӽy_TT4iҤA}7#Ff#Feԇ{Uh~gyٳgO0/ܽ{7o?zIIҥKjժ/^Xrje7>?Kŋ r2Z %w?uzeݵk-ܒߺu묬Ԑ={cǎ#Gڵ ~^3~^{mG>p@Vjݺ>hCBB|Ncǎ8pӧ_{5٬UVݺu3L;wv8P^{}tYYYnݺțn]vTY'`~z @=pܹ;wϨQmvyjN&OܴiӞ={=zl6d2t~hd2o3gݻw~;䫯ܹ3i޼y]R-,-=x…۷5*!!!&&;>^hȑ={$&o[f  IQQQQQQⰰ0B~ݻwݻwHHȘ1c!6l6m٭Z 0Ə;v1cĉ7lؠOԓw=[M1c'''{'OLHH'<<~"çΟ?_QQ1ot+ :w\lllp5mڔԡפI/ `,((`!xR<N=zbky_p8wnx5..ƎK={.\d2'55bŊ!!!SL뮻8}C[MÔ)SVXt:XxA~ڴTƍjjdddݻS';;&ЯFI$ޜ9sfʔ) ,替ŋ@ZZl̤ ӦM֛Fjti榛nBjz][f .ܿ??rHھuVlifӦM'NauMN:ENweϟl.{ԫ[֭[׾}k!33gSVV(WC'O%%%~r|~zp'sej&M?8p !]tYlp8v;!k׮.\ozڵE_srɪѲ+2!d݂ $''{_u]E))))**7-zPXXx~G`A\.|t: !U<o߾Z[jvZ^VVf͚$%%?~@RRRAAݻ+**!&k׮ .1+V`vذa5MSU2'b#GFF\ȑ#K,^T:uZbѣG !999[fW]v5ń-Z`#-//llݺ2hР۷:uJUot7@;УG/[r^zΝ%]-Wįmx.UYXI$Izꩧ֬Y' UUnݚ-;.((رcGX_~RRRq[o!D|{q?pճfkNDYJP8:?y睯>nLٳΝ{=4jp:t i۶֋nݺ (ӧOGEEΚ5+11_Ç_x^׿3gիi*x7|sҥׯܹswOϛ7> X,_|U];BCCYcK{͛7ooԨ펋|G SUuΜ9xv#G={/,ݫe$-\wߍw:-[d5b|999mw}wYYY-*_np͛'L}vCX9Ҷm[p9GF;NYW8}˩:;wbT҅*riisvkfZ !%%%2i(,,lX''QPPIJKKCBB04p3f(--}7Xi1118_9K TU---eee!ã^f4U9yÇ&JEEE^^^JJ񔖖z;!Qoٮz\˵-y ŋFJrʕ+W~%%%V牉v2>ٲe /n: [5D,xOsTsTG-Zسg?Lv 15WI}XZ BeTO̠zΝ;388E&ɧ@PPhx\5/Zx em_id2]m׬YӾ} .Yfذa8Rv+g)sS ipۺu~m׮]QQQ-jٲe 椖GpMQ~ BÇ{ iXƌCk_111-ZA?~ds?Ў;Zp8N@dՒ9p8/ U,Ϫ$Tqzc>k}#=Fzׂ Ҳ bu$L7{K>B_-4t( fi(xv*.YiM諲,zNe&*/.BCglNDQ-@?in F{d-. 4=yc6 dYWed Ϯ"30ܚ%p! \C43}WB4c9f+eC 7KGX,thh(̘yiZ#&흀7Pf42dz$- ]f` ^vGɦ ٳ6729p8R5H$~quX6D7ZVP`S薕@EEEyy9vjQ4Y 1)3iƬ_쾘GX#fm?rQQKKK0BfZ좣閉tXt,*Ep.IƦYZFn7+CIxMYɡ 69v O_*`_rp%gm༮;рDp8;z{$ W߉rezd6s  j<1@t#\EQ~SZZZTTTwTkaUP 26W C>l63Ǵ^dx<̟[\\ TVJe2N?˭aȔH}>|y Uج3tRv;=`%dYY` s?5H5̞a/>4Hωq5>gp8é,h{wg>1tjo z 6kenSsŋmKPz 31(33!!!􀹆>bw ],UE_NPfc@P񂂂bbb%YVj bj\[XņͪZE4%Srf˽Tx4McLtheϚ.$OoedgF}A|f^S 4p85D=+u]\05,\vU3-:轟>V^0T)++;|^^S 5B F6޽Nl4088f1P\%KgU޺#JQ2;XClPS'ohh(V+y z5%58 AN\VVR0Kc:zሏ(:~YLgdU};Ʃz(\p5p8Sc*awy! ]̸]\a!Ml0p8 77ܹsԷt:HJ.ͶB^jTTl%0#Us4\ͭw/\D6$el>ʊek}Z?{_}ne˶r4^Z?3(RuHHcp{ 5lDӹ`$`t_>l#kvY7j(&&ެ "JS GUO\ߤ! !B4 2Q;rE\?4$ݑp8A6T:r/ڡC`A%D `AX ]tVQ&CԴ$kt j/UM-=h(ydH}sIm?N .u^fo-vztpp037+ꭒ g  Agcģ/5)EaԼiL&CV^01vÇϜ9p<7B$88.99:8ؖ`36z$r/npF#XI,Buaa!v 44eA7>.7j/N L*2}㏶D T@aHpn9z.ݩ]gA4#@,"V$"`+hZD=rs}S{ceu*QepVҙ[w8z;UF}t#l|z-C5Ŭ,;Q-t dAczE}޴>0X)an1,ƑSY'mRbآRY"Zng˕ղaܩaLCHfOAzÇOn^jHEEIIK BBVBB,:z~KnLFu}6٠{0(4S=2111<<te~'D/ #\y}ŋr87 ڴ7@TYLΕefZN8[\ɲ;3CvPE!!AwէiƆ}NQf"$,6'ҧ^eYǰșn*N399bbb<'=)2Epe9NDb  <d 7V$!YRAa`рyC5xeV, (+!A@TDD4H45w!I$4US:UFRsNvNå*~ʼa-'ȔCJ{G7yz.)L%%%C} ȼL*{CCC ,,*+~^A_e6ܬAwx6%o{^-k$nm땘dFgNC>;uܛw2 sn4l޶aîѡ ԏ@Uƍo9痜8qSEe ܚ[Uq̞4U8QgM6iI ӻnce Iq\$㡱 }-w,[+[S DA5w^snXtnPeD)& "YdB@`""jj6.(fU??T LL* DdQ#CsyE a2!F4{N@CD=_ tVȬQb$+4 Ec*#cfY,"U Ο?1ΦR Uҷh"g֔KJF[m[ʨ)--O\y34g.UDgp؍ .۞3Yn}N46szFG~E_׻oJk ދmb;\.6>$PaBBBXdjaa!hWj( q˲L_x<42$$KX$w2_V8{p8m`TAED <kPDHI0 YQJ5(+"iMHhh` Fh (Du`i3#bXpLG` UUFEg)ay$" i97|w[Զ}k*6QvPfV_rb"e4J&/=+Zʕp8/wHѤPvBB XaQd#&5 Ic @REEbYE%ꥤ2=z@q\Pݠx@EAJ!F2YvEI5IG6Nqhh\ddrxDl05L #(,Ș84MHvr E pdRqtU#FvG.77zf5#vgΜG`?YBl[ب(f[N3z2ަˌL~m=԰?a9/cܪO-G/8όZtz_fj+7 !آ;}MJQ_0&!DUտ8fܥr}G'?z2 ai16*KZ-CBBnS\;&MP3c1%ۑ`Goway48:vb %D*bѭ EUQ\1 Q#A`q, h D$X<["&4%ac"Jd8@6\+--ntJ%"`4UE #l&,a $$YeBDd!N݊K\fXC#a b0+*ȚHB&nD ;[+{ѧ/H0G3LB시^v=;;ϟ?OnGf(m+.Wv@"C,+?w4+*\l kqא>4M_ۜlEAj!saVtizu(IS(Gw,&6o,\ tGEQ<0M\*CM+=_z!;/Q^7T[VfA,ޑ7Lpp0Nk?9NK,7m"##{07>QHuG/}g}p>;i$ť [8@FPX"Q1`X=HĀd-V c̚dT4'Tg*&BL  *h")D$vA{i99؄Y l]&;rbjDDEb1G2˜$nިAcWHtaL2ޛQAw4؁(̍8yY~̾%I0 5H}t#LC522 !( ՁU~ 2f*kJ’Fl vy8&2ro%.e_e+`\  Ξ-`lB 1ELd2[3ni{to{o~©H5fl6C!ۊа>V$IBn+BPff&M,T rV}CYU!N"uFXX,T.wd1+Ksx@SA V8-YS@L, Q*wʠEJaa-EbH )O)M "BT%f&"p%XmD($f"INӬ1R5 Q%B fM#f !{,MosH4e\̑ ˠۻTט!ȩ$'Thaa܋zcmY;\l p-a hٜYi A2a c`dsQHDUC&3"G٤h8- fIUA0@4XYhWKtDLX@4"p*'.@-(#4zDd-VQ##Ⰰ(r*B 8Av,!B@0MVJ6U|\/ѧŸ_x.Q}mYUB̐LT)..>y$IJcUU a w N㭭O;Ϟ=:}akm؂x| {}>S~Sgb) VU]h!H a0L6}X V!e%QLJb 5B1F3g}kfFcY s Rl]h3b+> VS8Wd6x9="VJ6bJXHz& bUѭ-Cz~|Um.»pM…Mo~lc-xcPN28.gF\d=Ϧd2Ȳ,=yw`ss3åkٟ Fꂗp~xO#-4vL<."Er:y{y'`Z0V FUa-<!u|$<61 (hozJZ[""Zi{E#AD2Թ91F@"LH+hx%@(YF3Z/7VT '7_iߺx4-zg^u4O6 + Au7fͬfpWev3'hEY(e, *1O-xd3Ǥ\i5`@MPQ*f;"c{( @+7km !  (<(e9\s^!(42\t7 _N.ȤXf>'Npw<,Yuhх/fG恖 rEfq\c~-ʻ x !$i/ώ8:ha\ .169Z<^=o= KgRL'B<, _poٹt%(ѓy{)Byf>Ӎ!55L7Q˗:Zs ODuڨݮ.o6XY 8.+&r"=(|unq% )/E+-:y@XӔR#s"( fP|KY@yV9^9wy3b6BtJ7i#"^{QbωxG]M9+|w-~Bc{]oge\j JoLI,FBZ|,]圻x"G ፡ ; ;ĉHxsy:߽hruM8'N޹qw2[6{ꗓ2ҵ03YΜ1t:W+pdAgF/ˣ^?uSwlSs_'ar~lM3YсWƓ+QڂxvLَy]6}I\:ݷ<)}υ j=z4K󠖝7{&a ~Zpԃ2SՇ:Ye8FOFeǨwc͠n.U!lƀu0DEi&Ϝ:'w fbb`VLBdLc`c5hc,R^9)&BxI"Q(Pr7wH@$&gi0~AZ~ZY:^{mW .f/|yWjR[ocMfe̟_VX1T>6lں26FODJY"1p3GS$HQKdxu=0Gf 1j"֋hbhڠ]'/6^mY!zbUHY)e6藽beE6H@ _# Jw 7-B͜":ƾ#K̋zwRj$Iwι UG_~CseZ7ke|4Ŝ)3PEHO9᳋<wswlŋ#G1c7KF.`^&5ӈJ9Bh(g1gIXʰ-L&( ("4r.oIXNq+HŠZĶ2/#Jl:r FEɠvP Q(5ƅA5iLJ\dm#SxߖXXn;æ^h9?DP0[YJ78@{>1gϞpiAdd8&W=ܳ^ղQ)vз|ٹscāî<:UsHwom{?km3?tȘg&ً 9zN>q緎?5~m6f~ͼ@$}_/1:1d|3wNVl}傊{{{K'mK.uÇv\6ֻ˙VJDR9u`eMn "$FD@GK+\x)Be̺_gPưb^u`"LJ8> Fb@5nv 8I^)# jNz$`A(2s(mi@fVQoXҐ&3"!)Vˊռ}vkoύF#=R.LC{Sf?}qk8{F4 ΃W*VˉԥMPu0&]ѺK@]Ʊe0J)cHzmT!F(Q ȂB@:E'> X D*}Abu 2bbZ27qV0A1 NW.l"DF/aɟcC"$)Y.Ovi #ƻj4ZPUw1Bqe|55/2K.%C& SCU\ z2Q&tXv9|GﰺQoynN<%io& 9{~+~&iQeY-cbu,}=]KW'&@/w샟z‹{>y:wu!b!Ot([{E^0N d2<_s19y֙`\awD^ <8W+qw >HaڔƔ̚Y30}6ڒ1EаXQep{vT#AkI*"P! : A' $;D I1Q1XCS8N )5I!S1m}J%Aq&(D-1m[!hm53QQ#Q5gOxߴx6sVwtWq#IFC~Hg۱,dەe\&[uəx$ ǰ6!뮻rXBe>ӝnd*๮w`$|3g-:(J>]h3ǾxѰael1AgLkYu Nbf]V?!'X) Wxp ~fW_Zq΃oG_xm?}v3㷿S/퍧Q}KލؿpovuVXg5G@ AqƲ 1Mʐ12o4i}˪]-Ov"1 1Fqm@tz0n%E"]+BZ R_4<AFڲ*E"DeBtʻjiQ2,{ֱ UӮKUOY'"~C U[jgu턉HI%/n+"FLGo}OnfXm3xOdݲ *@ɮ]LOKθT|>O2/&}BeF9r$% B݅ʇ_ti뛆C ٷ:x}sunp{DvgP^W"1Ou]'l:fWuf1:39=a€IҝgreO>'+<ϩ\F{# m{&0 ˧_:.뽲m/];瞓G?xh8vFc7ͺܱ… i1DgwC% up7; ?pE6zF}eV{u=Ydɬ"EĞQʐPl%t鶖,WkvwwZ0Ǧih4:rHʢUNZP5y˻}kݏQ4ꗾ~8s~6<;]i~Suc_I3'gSRL&bvL⧿m፶m̎Tu@({kMX_0}֏Cdnm\ F!HEkɧ~G77|tjv7_Ο~zwgczd~ww{7;ۧwVN|&MfNV^ 睓3OϧLf,- ѫ ׻⛘ Xa3<[)E/A^}'lz|%q J 957fBdžLFd$j|1)mgje]Dwza,=0H1k"XaNn4K`nu,&VO|S82D!`#^<ٗm bW؉7D0bdLU" V{kXEѧto/%|ZcS4?{(ژR$v'Ⱥ^{|?ǿۺ;VX:PxwgΜ׳W?^yxkOٟ=w]>k;N?.oSYN?68ğDOVudkwo|{Z*>M\.>r7;x7oGWXgO?hL!"!xmMdo׍w[(_Yz޽u/h TJY ʵ5WDڳ%L'h ؏~ 6lKAP X)*$L 3AۭLO&[k@Wsʗ֓8z25! JD11;A3m웾7{m[6% nW2UUzƔM3snnmUönVҞ?cgz>/ڠ|)dLiLDԮPTZx: ԥQʴw{eۇvbCJ/_{قg,G)|eޑ4dkkk{{s./Sn=YU).ݘ1A_{wW&Q:umk óL;']| ]k{Xnjw4<Թ?iտ o,?O>|po'^n?s䝰_:@)h(͔mawwk4outy]Lf=%2n5묻<|R+ o=ac "L:J 1ʚ(׬t!8lサ^عㅣGO5׌^g~fI{`߈c"lҭ>^e&hlj89"`@C:*(Fp 9"B.HD$5%GZKQzI-صQ+H6!FuAԊXQ~0W0C BҁmŨ&2mD`6TuRLw}xէX+U1`xwPESAW0!jQ(}};ݾ-mO(ޱc-5.^EpNveƘd4 l;.ڋ9 _r%9I85`mm @2zAJMwoW.{dT1O}c;S/=K9_<=wo2Ǣd?uJvmHwN'}4 uLdS󤃺ӿU%xWV'"/PL؝񻎛ar`2reV`fy GBNISAU~tK_zv6yv۫뷾պw}_\OC~xGO?7>%*?nFtbvvv7k޾|2`0S*T]u^E|Ryu-;[KYae|ß !L坈(A 87gm;+l1@o$:yN)om?*lma0[رPb?'h}} ]m Ёu2I@A"E$FQ`Q DJ*&Ҧd"a>T׬%@\TD^! *lA9f=CV^V 6S&r4]-׼oF5͊)0Y8Ak(BUB!d )z[CYi]>SɷWVx[ztuqNʁnY9H)3Ť*Ç:th0df9y“5?x_o5t#gFN._{.&f(z]?rS{>r?:6COF_nHdk,#:|K;kաGҷrbWrήS#/hGo| _BWҵ]ufI&+_9[ߚc8`5K/ϝ44!/#̃9d6'?r{=؟,񕾰}=Tmɬ?C;"dQY( us @=bUKHkz0,",BH V1մ$P Z[&j҈1=N)]u !Lg+yIG^!NS-9a 򘭭E1HOHf@i ^=rm;\Lnlfu\BTP
yOln;Vv|O8zVϿ;- +wԟ%S=3o][x>{ /@+^]7y?oͷ9t_{o_WX/zîmb f9|mZ'L(eCƺޛLd⛪Ɣ!FGbNoUl'!.wb}dgVKAG1TdXHaz (p4"*USi&6,`^­Y8y@{xE> )rZ[f0LA2X\;/):Ϩ$A'}tRF(l߶{~i/PbQDC5W$'5` 2(mC'N||7g}]r^/_܍BXK&uȓ3O%<}1l;;;W^[(^脅߿?(ݡ &T鎏?Uo|շr zefɫۚ=argl)qoooL۶"3}Ocg?8aL!Ej7:v;̏Ct] `.ϼc70GOhϟ8FV{smZg}7NW_K9~;9nVm3UwΥT(3 yUdJ0ΝLB D!08s!h!V,)L0K 'fmm֥WS /෿_.ĨE$ #&C J鄘+U:8 ];]r!f^SOn.Z߼fYq֖o]JVč9cz^l˗/'mvs 0ܫR1f?3_xO7f]YЭ (CO]u%2,Rf?->}?Ȃ(ϥ /б5O95bmmܹs?({Y2mell؍ {7 >Ms=o% orl6f1_x={Th0\W}(bv)9SJeCyy 7e }#\X+b<[ >K2k \brvbt޷޷jsߝż5GO+b/Rtj{.g8,ݨT5\ۻLb=E;-=: 1:ih D(^A@MC1 B 'h7bU՞bK B@( g1 V0s8j]Q/hL5lb,x[a!)qaPP*I\c_; y}ɧ~mn=>wO<1'ş2$ b'yO|cwIvC|`8ܬ}!̦V!>7xKXWyCm@ZȄ`@UQk24͔UqkkR RSۮx4bdsHx?XWդ1IQԖ4 .AK]-zͪJ ܥ4}V1> En=.x7C[HO?4:2(t0LAsk+ /# f" & a( sI6J Dlajinh"1)f[n_v"V!m h[WzmZY[nܱͻ 'f^A,ˤ&*y|<%FrJbβ5Ƙi=!ޕzt%t$UK2yE-pi Jخ;(SFAh&ʛaGJۯ~-j{ ݴ qaח><[8}R3IsMPEf׮KΧn2USˤi}f=y9sG'a6Ry>XL<\L˕!o2ƮvJDy rXaOxcnn|ŋW^{䑣e>k\'y߿<Zl^Sd2I0s\G\VxfnB\%zb]>x}[oԎ{h-ɤ5 leLD(̺m'!xpQtTNמb^:Þgܰq3lm9ٞ8Z ^*xN֛%r;Zw)yxޱs֚yo}[ĽnەzpCTvvAkKjSf ۲jAz"z(&7m[7skoɨ4(jU6Ү A"6u IDAT1n@#(HVǺޝͮp")P@dD7u$Iufb;U`u z83iV!D9(rB遅:Fnķfo0Rh\ɞ@1>YIiVs^;iq-| 6֋C8v{[[+_/{ͮN&˳ս/m_m[ =y$F>>s[+yъ7qUHps]h'yıuɶ^9tB<;v;2Iu]ooo'u4ZIwrرTB&k[ɔO77(>+WxcId;i#8t7%&u0$W9fde3Ru8^uVZc򙜠;/h.+wFkg_}^ZXp7%օmP ^c2'W^=;=yr#wG0_K]Fc㤪nvA׻I4SUUޣkۻ4gvUB^|,cu p=uH r~PF "b1jP'*V"{paHEAh\$ Bp{ΥnL$!1`g~v#@V|3ת vV0CY(S}+Ť(M¼>5k>@* 9n|?ۯX3+Morhڒ  @$U]#z}m;un^VkdIX Ġ)ڑ蝝VZWڹΈ#x^9  @ZvX:57M(F̜عyӌE%@!Po_@Տ]hj7?2@C?>xw^+|'n8Jt-l2'zGKSrVUN}6Rٹr:_g`08vG|79N2CZ뮋}R|@umv_\neٶm6 r4~1]P^Y+"d&Y~6dlǬΝYphf?{\pfh |GoRx9 $3v6ycF7ϳ>Ox͓Z}@15lV\68 "Dv)Ӭ="#hVd>6y NF[E*{RO݊˶j}]B;DDR-8iJwJ|jPQ)b$Xc~=7HL ),B RikC$)|nDbYn) b o3V>w#* țu}6\}:_+^3/Sx8}z_ן鎬»TuDQM[cSpQ(̼򁛆ZRY1ĹѳRahMh歔2`Y0QBT־)t !(S(VGe KZ `׸R{=J cF(XX$P joaXAb`W:tib@֪R:xM 蘕҅VF)JvlSA >v M1~vOR%e rc u=vW/=Ds|7ӫjg3lhgjZW6qm}I33zQ |Y0>tj+x!ɜJ㜓*g/ryᷨ1sK.%&s09x1yȑǏ|nv4.ED8^8|gxe1]5:Y sgiocp}Dw?_՜)Ee&u3**4Kj`ŝsJ<<뉥[5sߌ:!maWUyr6rLOonu()Po<+<*U0JFmp}!̆BmT LIZ6 pHMp@JGCr.(D;Er@͝mC@(5)'!Ȁ X/#t!Pbo%"3G Q B Qk- Dj"QJrj"5ә5^T,.4Z}'cz!8FM Z,44a6NRsUit\3:d27*h@1GaDB뷪#*/~ɧV!؎Ar@aNzɃsl,^ɱ\ cRSc,2vDG2g ֵّC sY]/Gyq\$ſ;w*GڵNkk@t핛:xs7og/!/(ڛ.#dq\XBtg?~xvkMl 捚%i766vvvIޞ7!N&ٍL͡vyyb,M~E1]]؍\XcdR`f^5z`ڀX5xWY l l.DM(kO1FEuzŁPq,ĶPE!Y93+@Dk@5%լdA!j!Pz%P%c0"@1D$(DE$*D*zf(C"m51+-H*Q DĠdqZ^sA HD,bRF ͍)'+zJʇ(@SD7Wm]5!hŐh!sjW}}5EX[n V>w^TN}tG~<:L }'߁G.$wBctGVXG>iQ ʂh#٤^?yW^ΫZX}Lڬ,Fד|;ި6KzVs AFov BDDO" "%4ڶ*sf7Xdˑ%UL @T*!5@ *&c2Nc `l0nc F%j޻t{nVm+I֓;9{:wfŧdcSMWPT@DH(B`AbQI'@ΩG0FJC"59Vi;DDgUW)!ڒJJWJUJU? F$ƕR3w/S"I)͵f 툚Bl.~v/\'{~BR@@VJ "TI(qG듷J"!"@!2QK~=B[)OT\*a2:t|X+S \Z5fޱH*ZJm6;w\朣#lnnfӥx9 ٩5Q?OШMOr](el}x׮뫌B92%<%'~G~¯Ɨ2b8Z(wvxikTkNǏ_,{^הg%ޟ_!oiNXp1Zy/K|25;2Lrꃃ4r>\VԩSpG,7(.7Q zRNKAraaǯ "Y7P a.s5+զo+?)]LӋ '†Zئ.VjB$ ٹ>tձԴ&rVy-34W RH#u]OEX느z̘Dg%t51 b"", RUM7<=Xb(@*M&fHo 2!33',6Z9Edw}0hMK]zN(uZWu=QJ+U3@[;ֶ+쑰]u`YX65E0(`E'Ĉ(XJm(郉|^jA}םqĪnQr<2._r t1_2:YJ."U5r .efR3g̙3gΜg2 [u%K狾#v =cX.W|?~?K{W{~Ã)Ƙ5סrڶf9Wt?m~ʽ.Xpl|^5~oo7ߪ5Z.<gYN-u*9r`<W8R/nLN7~將燄0<pOx ^H̱&V*@4} #i*Uuy5 }n;l?NDR ;DbXPc5QQ8$:idK%!RWZh[cmBDJ-Sl!1RER9J*˶](Z`V}iccTRlHP(䈴H$lJ`l, 1-01-@)EֶF4)f?kV;w >XxqY_Fq4,\Ds?X?;'Sou椌&q;c?!s'z,GqOXSk[#DP؜q;d`ufAOUըΚo?n]{cnϼUٝhl;mLC;_!K_QP1n@"TZ7@)ShH:Ǥ!FP$ŚM:@C2K1|sJYkRVEp@קD90H $9L@P) )iR$%1Ipbg}I"BBfc4:FdA$DU֢2$N]ƠA%^~9{Dkۂ*Pr%L`[4(]_cjҿzFgG 27ZGDYVJeQk=&v2F#cL۶e#9ŠZZmܹsvr;+ՙB֞:umۧ=iOnfcc#:7?̧uEF2n=_~W /.ꀹsGXpԯ2h3YXX.9ADWz{w8[}rREM{M"ʾQN]kW'^krIw.Snކ 2oY^EAn}߿K4Rk ktY^^}]י)/,~/sĉdRpR0,ɒBlHws=P%2eO8}Ż{p3ΔQ71cTJi4e)9|;uv^HC[6 &e:k>I.fO9aoFGk)g"  U=⺪A4@ H+9su;ݜM߳_1;?IuvҹO.Iڳ˫S[lUרzΎ:_[uj'չF>&϶ѳJVmyWWjؾpzz>AG@Tb UB@)5EM,"( ڑ~Ξ?0@fhb4( pDOTJI)+GĔD@@j *p`1,FI;i(Gz9ȁ8"L%G!꺃~ÒL=^~Ibj݇^1L=Ķ^l@Ax0K1Sw=R<]'A*!T9ͫ:(]cfQZm`>R*g={6䲃–?~<[LӒXY qp _|G>kg5oa 5N+C@xpkϾ?WO?01w6OZА[2{lI.zW^,$*CC,8M)vz|o' e ڵYJ)庮b=T]ɉOt]7N_JTIm&{`}_jex/SGqdrlگq0$D,H- 5}[9U5u'a3:ظR R0JdW_u߫dW? yOlTk];<FbSjiDtb+i ; RSwA+F&bMP,r[4zN=A{J`B@BD!3cHܒl JU#I +oňNV8m#7J2SLIЄ=Cl G 16E1136"y?GֶM 2t-Lڊ N*I8)_S#Zl|֍W^S>wQ֗j`<>nѺt$N&h"@L hTn5ۿ/OO}W|g<$9ɪ{v\nqb_5:4NRs"dNuJoNΠn0'vu+;V}!,S1+V !bҔFCI-#Y{ 7 T%twb1 B)ݶ[Z R ^$2 HV %FK|ԺҠwR0J cL^݂1>Lt DHN _i2!/Ĭ=Ϝ)66>]6W}P-l'h GF)DH|s.͒<˷=TCrtj^+S^_ G[t;w.od,3DxN8 5ޥ~Bc3oj5k]v5'c񇈲ZRR5{w_Z뢉a: D!,y>VWUHS1~ڡhS IDATRB(~#ۿ_emcJsrd9B|~yf'O,SW(r ǜ7Ju([6(Zr s%˅Q_cQE%}?oꠥʲ¤V&% 1Ѕ h%أܡ+W[\M((qs|lZA[Hu='O^u݌U#s˺&:q_Br1',=T:iWCJ!9QE%IUB QSTH,aDX! ft5D3պN)*]$rN QE8XV ;kD SQI%P4) KͬkX2`S I3lFv-WPdWkaU%4 MEdeUU=YK$qb)Mk)pkbi0rbEGu]~?>~.UUg'2,RuA-ER/`e-n/Ox꺫_c\HiXM#[Kq)4ywyg>>uO u6ڒ`O?g~UWf;Șﻵv-?|М)1ƿOs}w~H0OeQ4-/r ]-|j5]\(_b4MUr4eZ.|GO&IG8s*ʢnR5Tι͒($@QIVV%""sK"58QԂΫj,"ǎ=Bf UUEڹ iuɧشgnѺۻ W1eR$Q I2hcll@D[>/nᶇjo*Z$`JR)JV)b |kauL^B7@ @Zfk(D8/&'TZp Pj(!U[@`D?1%b؁PUN~~3+@+ mS%|JUqQMd $Llufj넵ZGPqbǯN'z8<^2v*Kww|{׿_5Bo~GW=WysGqiQu$MOt֣jȈ0G9 Kllv:~s}F=PFMHj"Ӧ %B,z1-,{"yE8bɅVak%Yl h]%h@e4kШDk,$: *A\b]}IZ~ 3VӲJ&D 0$KJ\SB!=),&*e2(Z3bI 6vs]rFPn#. Qgt:;]7 SFfӏR&{?O09pJR5l$)ҽQ:xe?%.<e̚x]p4erEpQA+h'n˖HB[~h4M777ZcUT://Zu˵жmǹPfûPТ=ϋ2{ɧ+n5C.p;ΐ]w|}Angopz.6[Uzcke.3y2!g}~~}}'peIY]40)9/ts*E& Ŧ'_pa\fpʷzm뺈Eg/LL=#ȯ@s+iwlm]u"Yeϧu Ʃi2Ǯ:l>wO6[w_8^[!=^U#eM}"XqKHA ClP3W̮cYZYc '* l, 31N!#%QIXQhIWJ:T$5_:Q9 ^pDg!f IgYtnhL+9jhDĘ@0ZmBb׏[ĸ|7 ~W%/ ( r@;nyًPC ;g髿֗o(yK6iSO?k!rgw-a0!hcc677f=y|>?y$ &2k`G>o4 6Eι#ʀ|D<X:l"H "IU4Qń"T((Ċ(ZUդWJŇKp PF$/UD`d8[, s R +& 1(̄J!T͹I٘U06:45Vbt}p҂]Ml0 hQ#Bn~|cjDEc앲ZWD؉ `OX(.Ub7c+"5q!)ڙr9j6ƓcXtl묳9떟;8RA""m,R $ nAySmvI>UHS 1:DF@rI(%q eнR paar.JbȊ]IX'\P+G{v>0H# #FMQ%R[DR)$.z9d,20(_,Rm6$D`v HsĝU]E)RV LH"ňdRg&D: P3ɠ֧-*zlmuzaCO_ cH= #ͱȑklS3Af Y 1%;;;Yg\=NaP=,))_켌{1/k?T+Úⵍ_Q!RD64\rgE0`.,3֟m777`2d a -^)?o~C^f9p0vB ukk9 .eְz/.u%W9W;U)E3QkvEGla‰ך%arѱB5,ZO]ŊijeCb/ .7ήV{M5wiJwzBC"h@vچ QPAT̜Z8Zx1Q0)!PU@8AGGdN h DA¤0"f5=%F0+ A1GI!($ Ism|#: dB"J-[tư2,ha$@~tNFQ$s-mIkT #A#+2.ҍF5"Z[凡ĘFcPX*A0QLT}4jzbqᒮң(qyaGX?ԒWd܁쏳X'Q.ƒ wܹ}_mc`kkr|Gx0>\m_rk>etODww3%3OZ:5 aDXo\-y o`+  n{?_Mϼ:P v勐[t:|>/݌FQUP4a& ܔ﷒Y|t=C \+:|y/7(EJYPR-ZvXyԶhlW{=;O;L),gm 9xqC:X!cI#wݨF"҇лbR &pDHQ?bRSbL } UUL H"! F%)&D~mn=<{p $DPA@BdQUJ82`P甽@ a*(R k1*@q"kԕ l~JOU @bdaR $DqI6 E*qpF'fF)a"ZR,^qOqTK}$nO}أj~d2>7jk1գGon}?ۏᐞ,qӷϹw;?׫AQ<[^hS4u(Wnf_ۮ_,.i>{35詪٨Ϟy l¶ gHEdl0%KƌEDĥBE@±$ B(5 @RȞh{tLmbW+H*ZbOP%q[j Ic UI+MH*r$+W.i"PJ}kU`Ī>X4* 06ca젻RfJ|tr@ )ݥvWC3&1!j@h@DJٔ<,[N&'!do{v "S_[Ƴ$ls.ܯ+GA_b2WJh;ü0;)v}gqggϪbPg[[Ǯj oR[H2>ByO}Y# ׊s'.wy*Ee|3'+ׯݲjz:PjyQR/JË\k]̷ jru?~~'{{뮂A@ YR*krⶸZ9loo:͇2m6* frWkHbG'Rxoy3WdM3DSF9=ylw~E-.w/w6v`,'MAzF`C$)6U]]جl  ιy(C"n\W57V1 5m) 2$CB(YVR,}lҢuDpg@`@ y(+ @3T2/C ;4  Tl4k4ɐոN Gl'QHDx6gtשYJLѹek}FwAga )RWؓl1P`EA|JbՕ$m+BnG]rKAbQseG!>%Z_q 5p[ZWQ\| n'3恋s-D1.͏y<]Jd7oH?׽5/> #pcsc`YƟ+r5_,J蚓0c.B??gg<8-*'Z[$; jF@A$cLN@,~77 ׿;ooO=7ED.%J{9b2>IIxB(m֒b3ƚ~q'CrE.38>PSPVx)"mm$ar }{;w0;H;ؿq$+x4(@Zm |+R@,[jS*%RH)x)jR&A"Z1Tw|b]XU@X@GFFEMV@VbEI%"H1J}<ӓ_\WZ lDH0r" bq΀@ H5^![ ZJF1D Q;"4)P\͢1s[a~%ݪ]9-fZGs="LRSMltiԓE}4(+H.\`Uכzl~:Iued{CAmZ,7/.Z;~ƛnm_ڗ4=;)jOw]Y9YP>bzx@UǠef: FZXmFJE>&XR(H]", XXD!&L e9V5GbQb(WV}鶓'n e"{Q+ EX4j H8'`v $VVRn6 iPBL1EĀvVQl bJޘJD8S_|Rfczzov=F`x.|)T ((A@c"tT}?N)IQUDP!nE,"huX섰dIʴ+zkqWwcJhk?9䈀|lY^ ݼ 1ԣ3Ҷmq/B\<$2ձZr+ƒ%x~t:ͭGQྃ]uk. k$Z81/P0>Ŋ8Ҭ" "$"]aF`B¾RAo*lT&mVH)\6I"72FPt DU<)0'e@'G l@S4 XYe#i@) )bRBex_5&N6:YHyWBA2X Ѝ]zIC$TNJR#+ԔP-QO;1mu]RH)z\Jm#=rF\Unɣ ,. 8-< ᬼZ?gR|""ʲ|>q6FyTu]onnfͺXko~sԧ>xWn;E`}8-m]L%(5k~=} v-/%-;<-Ñ͂8{xd~٢ c{(/]sѿks;o庒_̪*yt !fu]^C)H_ٸ'9ߣQ:\WA;&/}85ssіl˄/v(tD-k[bju.hd1IR)XP RT *%$.!P zvUCA;Ak+RJzܶV/)" QFA"UO L@E"W7RRF$"-h "+"c1yx#fH܈QD"fD#1X3;#0,1R,WĘ6ER&R8ZyeDkl:bD-:X,j%I+EHcTQΦ B:hD"dA`P!V]'Zl$pкHުXz6}2[nq׺7f$-R):ef IDATnY'?qIVY\'o]w?WmW]q+ϻ|MӜ8q"3gG1^}uW(O}w>9n'[uʴKzY$޽Z׹14";yJ1i)V.Fhv[@@tFcN(D m*'`MdtZܒ≫dIոmlSc*"܊9s ERH  f^4˱k+ Jg SsssYXN 7@_V"̈́AcPU71$x@1lo_eLJU DP%0QDEH+"ޅd|#*7j[''`4ۀ>m (G@P#0 bPSӎ fUȘژiSLwgU5"Ru=&m%n;B$5d;>n7t M\.ٵRPxsrea?Eņuk]6DӲ />$Sv%=[}|3R%`\eE" ﵢR2=3ךgluC+Tȕ996YϺi6 %vVݹK%)(ܹ%sDSD SbK:Jp[c8LJFH5`Zb¾V`M6ĕRU5;!8=R!"A@@W Q+-F $"LœVEA)tbK%@I)Z%A4ƨAJQt W h VEl`$2vH13"!iL>2A5R;Y)IHp[Ot;$ sb 7vU5flv֘9]lcDjt ""2'SWfdc~WO0v\{fƎC4VI[Q@M 5X]E$Y̑d<9A;]w?sU5} _qt*1$By‡Ō\gE/z5hf]߰= L?DPZF /yz.)|z۟?YʏtGQ1KW˾`é;Ws:GY&}YVp=< 1᣸LYϺq4ޮ&t*7^v,!2B44m9pnim\Ё(V*0UFCIi 03j@`@+IhV;9ҾRTױ1e"]SDpn]󮛥Ћ 0R I +"@A $Q M#ѵ.%j2u~qGkjD(,}@HDtb%LP$LHGn| JЀFc]Vdj s0kwݐ7%gN9|P#YTũHQlҢ t vw ??0mvÐ hALMRTq,yz|yΰ?NaͪRU*x'ƉcN[N]YZ$ +˪2ވd`꣎,_)$RtDܳ ^Ƹ ]t#JD!Ў+C0H1ƔWMZD@$j}DḂԘ"L5^ZP7nW=_G?Sݻ?~OWka;f/glTƺҎmbId2:#c]Y"Ws={kaԓ_ӎD-[?u$\KtBrGak83+7 DX'`lXQ0, :C+.ίoY_cOoϾ^x:'KnBr_.Y63@."EhMq%?cв,C:>y>ֳ}%JN|є"B,}śo(bbt!D`).FY:‘?F` ZQ xPB)JemHJ̞9&34FVJ+eդ|UE DAtԖ0;\\=ǂGhר3xfAYk^#pDxmJ"^q$(m0*k# 1 뺕ֆcD R\1 jeB[S‘Nd;**tpp:@""L,1PժD;A61zDTjS3{l5 nQgi)kk4ҩG}AzbZxs3ⷿ w< ;kn5<ө* k 'd΋ts׍\t ņIꚶկ>H\`r8L[3:j*ԵFmneYfMtSE۶)CNDSfg !<)q\sY;ՁnL1_f?dpn!I`/,zVk|m)~WKvVb J"!Hkʴ /o.ۦYg`$ D@0V@4$`@bEH!";KsբR6ƐZ'3"c!+a%LfEB0TQS|@_ c_*!H 9EXEhw+ݿ`Qx+P+(uXa\&IJEms(;] P ُFE1(FtMm.G#`#*mUmScQe[n4Ak]赩bSsseJ N"1I)C2״}aAnw 7xw13q \ơqEw\Jc`f(91&y0Ju=tY?vwgl>kpvӈTTcO zdۼ "lҡ9RWes6S|E$8>O+m*Cwì9);0Wm&s`86֮{K>_|"tN$&֐91֠#!@.Y 0 (a/"0tUZ+*NSwչ gks@v%"?ׯ>8=>pz,i|7nޙDHB)K ̑ ̍ɭ$UUSF$ftAzR%ILlkHU1QChQ$2O1v0ushVtA$" c32H(O~5)AXBf?vf M4 Z±Li!DJSN[Oݾ}< * PD8j = > FHIcg Ȧڑ *ض֎oc2n^C!O@D(FקT# *e"M1߼|zǫn1MzUtO6hF5m0"ശAس#؉0NgB_:Nuqo߾R.Ji"eL Dj<>cnNZc&;' J l,T 1m<#'YC^OgNc"ՍCsh{{!sƌ'2RkʐdNmGF*L6ۊ wp.]XlAs}0msct' [Qΰa 2cǞ~IZV DOcjJIyƘLñ[szp y%M͔*2Ji 蘃'qօ "ʳ+tS*^@D F'"TDQ&f2,Z)X#7n77]{"}}(C&EQiDre% D ^AD"H7K`6LOL ܱFl@a ),y Mh1B3 (AB`tRE .Rib2j-ZHM1p$/wfgtUzyC A 5ᩃ`TN9]ƇDUC;Dkn_S""j+":FiVń[1L%ԽfܕnQuv~x]ߵKZ@tu!x߅IA_Њv2"kꇜ001kuTаOp/kKcj m;q͛ѩkE=lVIIX}J,rIZ蔈el`vmeөth}gFNﺮtJ(t: UcLǘ"9enϏ0(Ȣ|a9*'Yn^wYˮy[U' | MIC打ʤ0sg"b!8U, ]Zs "4"yK@"Sp Rm덪q]WlpɂPў?ufu4޷,N#")DD$$B }eznq+ )V0U#0   !yz1mo*0 RE& "ɗ\$ a9,"4OQխN4pTg`$[D"Wმ\d.{?8?o, mo^@O,ḧpMaD:Fn܍/{]#cC*4~7+r5pBH=ms]{G.uʘ/)T},ą ƍ&X߻ףMD2(1 {%ε!#3{e2ڈz 1 Q|UֺD( RDp꣎:7qc˹buJD ʩNq_q+|y*&BscIRaRhWZ CvD WZ !a'3-*D0L(¬Tjg/!7?>wse G&1 ""!1a$& fY].{Cn5+-FрI̩TD(i0iq$ ݸ1xkc +k39g dfm/YV!0CD"vVUUMsW@=6h@ZaR*HT©xc}byA_z|}k!g0a$>43ᙬe,{2I@yCm&g&#+S !d**c ^PXHDR/u]+ЅJ$Fhz2f3-3l#3#}Z4 n>ÍQ?ZYB:4 TVRe4y 5UŅ 96xn.7YC93!\Ani/\n4M:K;a@ރ?^c"]lKozN88?SOLv uKkk0D@R(P1{2 "6D29>0+h-&AFQzHIZ O@T.@L@)$|+5G$́Hĺ1G$&Pb"Z1zc>0 VpDX3S_gY$5=!tRV A EQPst,## Dc#y#B @YJ H$FfƊ˃קY/зⰕ\"- %b%maX6%1[wArj[t=b `P!Qծ1eYS"sM}~#ʑ~Nq~ٮE~H~-?Y.aڋa!o&E")4^yr*߈/n!1f;슕sb5,jM9I=2K>aL-kiTwsm4;ʨ}1A `@ j!"0pE3"%bf"mEA@,(2^:߯z#F@3XU;c DViMMFBJԻRF"ؑ!FQFHZ "0R0sEZ<' S5`X1  0f`q;-d;Z.H(@GEVJ}#PT5>.7wv^Y>,pآDBĈt G1E9WT-݈Dfvh%b LDdD!Nbq{42rn}G O+{A7 *؜_r{~Vdw/W!"MTʹ^$ *orx:וWw=B %j|ԩTcYbD8ϜT uz`Ln>bc e|ң2d+1,w#nncDuQI&cѫ"SJ1G授?D$./:w*R u'06XG$pcwiE3Ơejo {2=tO~/:ʀ6YOEK=g*ƸZK/-vwomZϝCmYi߬H\,ZXFN!}w]t))o޼h˗/9 bڶy&t]7LM5oqXYSYF^[1|1'2_ "noK?xҼ" o {r* 1,ILvg=$g.z.L\5u u)zYʻ"(":G E R.$ \[wQɸł*Ikd|$f~vT^D1O3w>b~UEz#֖ 2!2AkˮQBKAr>c"a@dBH"im{|ӟyϲq]xS7qFȱ\.WI#Ͽ{n͗˹U9wVpƕ[} C/}侇}p{{E*DYaF噃}MA;[LӥLoꚏT((>D]iP)D*5 a ( `!h!\uFƨQ)i:6"=(LΗ]_5-%ܷJagֈucj<5 "HTXӲx8n,P;*DF4+ ʨ{RZ*t"K"\N \<>L&EQFPj;j J&$tԹEGqE ^dfD$¢U;;;/<[-6wY86}Qݻ!)ߕ܋FBAc:*F;dy6WtSᤚϤׯr's3;@ #zj uQ9E8.{~q/xCк(j԰;>a"uQjsjl=.nX1 =Yu_{m5{?o1z<>u4(Kuk7N/۫Wj%7[yƍ[n?0~'q<_p|ϿWVzFi\j-\#csp `gzfkNF{?~V;_t e"8@&3+eDDqO?,S[;~ZX)xHC-^ g_0j mUeo9ukx-m؎xz3_|]"6Y"?+Pp/|WzWW?;J9k}?Z+ˏW&͆-ylsclRec ۫^ڻsg~p|i_c?p#D2q%ZyooGͷ77nQD9"~#ٷ/wuV*L*oxx&s McvMٲ4}[[r.B5 :7ߟ?wm~z{fX]^G o~_w.ʧ8=M=yŦusēcgGA Χl獯(})211)m L,teBvP=F-6# ) W@XOa1FD"\ R1fbXU& !`9IlRG:ln gcuI $6{ Y1*dD K~T)xf! R`IPD27?OxIj\j0]M-hmH(ToE0k8Wx yqOwfO1RȚA(dP!*"E$ODҋ(39מ~-xlWwfnB1[f:}g\WTiюzd]7ѡsI4hзQBTAqW?k>uǮ)}P$xk+c`I3 ;mݲ98n-qWJ.ˉU{e?ץnöم8okIpuyI@nV|xx /}t{߻'?{f(!s.ӳ?^,W׌{ܴ31> "]=g|o{\.Snxts:i^z饗_~9]?F.o=N>&93QxmQF93̓'F!upoG5ԇ[=onZt%ܽz੧n=kמޞ9ϞN월Nf{!EZy28vN,˴Tl'&_w''½xC)Ҭ`0bEc.,X1Xoo]۹f9搾(,H6D*}"B&L&RL d (J(zi$1 B͝k6/m\b9銹nCλ%hS|Q)ej-HHž0B=G DP VhJU[2! DJ"l xfJ-ojߝRpֵ0D0*Ԉ} (‘P9M׶W~/Uo~n6n#ew3֖JY'ZexDDJJJ}Ӵ{{Ǜg?lڣ^Wk Ɣ5WN (}7)TJ{/{E)TzBBgkgdrcN~kn}d|ٌ}!isNZom3G2^Y*օQʘHw_X)e&ͩ3/*4 BMIW(At<``3{kE1m&cU$b+S< MC阃b?>1:ʸ ͸$(o2U-Y;JAj+&I)"1U!h&&O~2=R@Df"Ql#iV$ƀh " d m){+ uAnQp:܏9xt9ݍ)R4mPlc$ONͥ~Rue!RUqR;}6w+[ѺTʊ.̸.!/""Ldkgvjʃ^ƭX,"ڻ#~T٧=1*wjG˪3"^9;f0qyd:Ox#b9Nk;җ.Ox%&[fG7u)R<|ja[(? Ѭ}:ڵ;Y_ӡ%ޛ /@۶ׯ_rO[H&{{{u]߸q#PtַggϦOY甪BOO%Ƶי>YJ*>w6czqfܽF[kbfO˧?.]RrC/:T\?9c+w^,.fuN&\~к$RD:gF) 4@/[}q_#7G֘reitYe9IN HBF|$"D#CZ;[.j10Dsmj;{_޹@D߅FHʖ判bM}u`L\;K0DBČ,"lLnyJJsrq)uNPB!7>.mӾ @@ FRڔJYkbmKVqHJY)ˍxiD_TgGlYNmK61b1E-ɴFBFX DEĹ%$FiжRo"HIuHY%*ˉmέ|PD BlY!y"\U_ۿ}%94o&svGzioxEUƩmr55B7ĂU`UzPQ]kydv*Cs㼩JI&#bs6YIGf{^E|^!`$Lg]0?Gw(^P͝ҰpMEȩS;zG||/Jr^{':9mww;l{ҥ7n^1o~fȳ5o%֊\ZH{O%C=AwF_<\6o)~ܛ2:XGC2A:Q 1*d=(Qa4 /hl]O9=kA`1V1"2EeQY+0="ںuCpN7"1oL}vdL9VWX7V$~B T}ǣDm%= 1;vH)K>hmBM\|Hau?JiTk kEBQ#ց86.hajyiK߈Eblپ s`Mh5m%y %^Y[qL/<߼Ǝ>/xm看 >DޥH ScϿ׃BE,˽ LZ[km?iY:ܾqYyEuWh!#r@vDzQS`߯Rc*/J@81u(#zܹ֘ rnV,vHh Cj@lcDT޷$L(PJ :p*|®2U*Nd}S("rG"A }_/14qX  BHJ题X[y&î[)H9YLN@rcX,vRcў7 sP v"(sH0J*d?B'Z1fR1sDLu[vYR݂F!*"@4#;Ew࿀g|XFw[/(b<}d³b T# RBZll\+p֙nm-U7-U% (VCZv:m~]h/xmϕunu\u(φlr6oyPȒ|aP#3i yk'! J=}&g9[5(C砼s2}| ty$2z-S|G{ #r R]Oblmc(X{{q˸O_GaiQX6J¥;E$iR#kެ IDATamBr. (IÁL"J%WE01ppIl.H+oZj5VޛG[a߷37C(u ZCK!! $!++@WKL0Z8&&8#9^B@BAvw-ǪWoә֩{[j՝Q}瞳>{~_6A0m&Krm@92B ;8)y"Kj] M`P!c1X 1A xS2("9l8@[tT/WLd(NAjQkI2$"Q,0ƵdjmbAHlR7̈́sȂ:fƘJ"$Y0\Y SSqcA{ֶ% 8gg3=@Τ7=}udcGD֮Bt<]  l6 =K88fEϫѾ +1\ueo2F#=uB4Lw\}~?,SD4w[]@"LPa<[Z#kwAOR}~ᜟ:u* .^|q5|ɟ@Q՜vټٌuJ)Y۶cx<>q5a0GVu芘80aKCD>z~kk?}z|]0ݡB{ 8"\Az;wl&^ \ޭQ̮V챎Oѱ5\:W8iZ(Um2X>wC>Oq'g/^ܘzRr.kJըE(W41rKkc8"s!{2ȄH9C"Ͷ`0<3s&L>Yeì jvΘ8fIQ%qnDH퐼k~bmwԫ ؎cDT$=R1׷Fl33⺢ץs3^%k5 "bHPlGZU hΥ Rɹj[ ri#1.YT&BRtPѡ>U CRmf'IAr/@t$.'=+5 R֭KZ2QTx Υ{w2FgP̠cM?#Y/ }?&iՇ.pS5ֶ6bG^ 2Y6.[EV<46m 3- CShG8\z0HD΅m-&IR1W(l6ybv{\e!ҲE).@dF[W)QNjkq@#;y"Tӝ+szb篞zbZ98ʂ5Bl :dlڗuڼଗmZ$)`؍zQP$*4a ΰtȅK+txtFDS?v[4uGk77 K.G?8o*/s$IH^~;X__py8baL-AMw?3_c??[j%6I|]KPx{J8HyzZk]mTFx^ @ 2N(%d;juu@;B'pHwֶ6ֶ!I ʹ23&T*lx1̑߰ =|"z̃cb9"i꽳ִmպ{o{,E2i!B1Iy} Qx$%enu:c*혳~-ؿg!~キyi92Աk~G^~g+k8W^9NP:~2'N=r 4űoMI";84ZIn1 [L83o GY%GBH!TJ$u\(J٠JlԵ ~ ͵ 罱j]x+X*;gv@z&m[xoY/#2!ScOzF.Z`G": Ҋ'Il潫1 պtHDh LS` @BV$0H "8frnN"`pkF'#g>lsxs-y+sR3*1ƔEʹhtΘf.@I@,-'zNFXo<N7U?Qdm[V$ҲZ UwRe ӈ.p]@qX>_̋1&|ehȖ 6bDtQ̘r]Gr!{$!8bc'Ȝ7~[ovg"첅64MdCtT{N</]h>Oi 7tW!?裏={,F$IbuSF#>D܋!QSJι1F)|o?9tpG۶vGS^廓;]1FT.~:wkm$cTxf!ZJi_;1!P.#FޓV=V_3'O^]t&skZWg5B$Bc0{gL*c*I>"uN#zx{ЂqV9cچss@LN-c1!e*DbF@[$ ZG exc 74IyPչ\+ff;Z;c+3 P 9뜍Y-YJyo ,-pp{0 UaXYB4t)Sck[O g`O3 mjO4M!9Y& ꞛyYnZ1WNNԼk1M8RD^ ZoC-eUPVe~ yLwbm*Mẽ qtx,pLi;t#WM7}7E18 xqg͈L!DAʕ+g# z/n!m<+T*|pO p}?Ce|# cL˶oB1}?p'W6Ogft[ӏ uED8;fT͌ywS)<ot8d2 zLZ8HLwCz՞xXwSzq8Ą x24ͤ"2Y3k%[ j~ 0%G-\яU1I"gm59ejuW ,piֳk OG콷Vycu69!2Z9jlk%qƙ='y1jdM/[A1 p|Pt'lk5"\"2 ƭCꨅ:  nO'hM;e+ȂN- #c"8xQTh5422_9ȊVy̬mhgΔ4}fy0B;Ip'yL7TYcsu @h2d v97rMWpN/LyɵɭFَo޷L}:mP)L}Ǘbǜ>~S?w$I_[,sֹHxeaNGע#*ެ̦kB'l{2Ƙds8jZwWwAڊs%D"eߴmyoQv$($hBBzh 8"tq8" [`(n(eo8Y!T$ &XY@dw}Gn]6' 9fPyRR&Asն9!I}T #b BHq{ <-ځ1XA֖ `mM5>7cv6fSᨢVXX B@U bs9Yۆ#2B@*犈;+Ƙ C@k ˋϜ|YEQ: 9^? LHl ܄8˷Tw1/#-w '\'d$l_VamXUU# bGr $, &J"6/dvkjǓtɾ'?#$#5՗NzL ie$5s(XW^ C=/n]c+Z~z/| icF0Y8>vxf Yqvow!N?)o/տy?m}!h 6fDd1~ݿF$5j]סbnV$EQ@eUU g80d0 H/}a^G:Nзˢ Wsl/DoB(Ƹ*5n:Z *ȹ- oLw?ۛןؿΧp=_Sr3S΅8ԁ1R&+ Z#qjZY)O3"R9W!{oBuH!rI!RZΓP/D\)rL8bH y$ rPsIA;`{z\2G)pR6xy pNP72=1c1th^[%WpxJd>oJӡZMy8REgN2n-R,5綞3Å!9cmK9Ix1vb8Z2ȑqp!8/[%ak_o{7ܓNU~|v3߳&5T4jM@_ĄNYk#-'`F(*8qиZf*dcf?k\]:$]IDj($`.ַlTS[+vWZ2& {z*-~$h kktu{qM*Ry];O)cz.׫Tb}x숼̡dd7"q P$Rf ΕMgLK^9{ޛp!,eQB$R&U1N1k7{dδ@FˑBy^CȘsFM3 N {{;g$I^S̬T*cLkD*yDTO'&1#\I r),[Ӛ^ژ<8OODxc0X̤^,MO>:3ԛɲlq\emێ|qȮ>nQb~䬣*Au. t<_f,_ oxC!W {1ˮ&$&_ǻD]i[YYScںVҴ2C|D% kn[7UC-o0ڂ#KޒV;: 8N~^껾+ݖ#mփ@JJkk??|??{rl`A_9륵qʅ3/[c۳;}-e~u?.Ȕ}}y&gw_Xq. X<nbw {Z7A!\emJ,*0Zzƴ ΅sZ-R'+zXU!Tʘژ9!XȰ} y1ST}H4J!b̳!1Y՝[ѻX`ds i]>>cUU}wo%#`tۯ*s#V*3ư~|* onn^r:y-w߂X`htJ~?x<-`Q]; rІ$Ib~`6#WUpDz,xq.A}?C??Ê826w *Vfİ`d0Grqvik8l/ҽ1uk[61[5(!+_QϬaڷ;6\;u0i7>..[. NXm5/ߗj(KARp+B"J~T-i":HA*eҶukQj I[Cds!%S%Ios85<I b5IrR$Ofodi؀ _k]:RyAMpܴm9_k6nD޻M]7qlΥLC,!ZO)L{L EDr ©-FOߜ%=RM͵hUw`|1<ܔk1a#2E۶\H?@l?DLyey)Fܻ%D"2@D{Ͻ3l_+q!.Zw. rV Wvdpڰ:9EQ2IppHP χs=qC "*BJֵW?֜>}z}}}W](](JԲ骣J08J3WJDR{o3шhF;g719~{?5r4\Sy:p9B;آyHFD&'ƀ @lqK萇?a N& 8DϲaeF;"к!<'b$r&%CI]굖'LR^mRxY@qK.7UyC nB|'pIE׺rH;gDTB{ ԡJ!ϊFZu$۶t8y(K]귁"_'Vkku[^f2o[?laH]جϧI7I oM2hT:SvxkZ#fԹsmt >ؿRUƴI"@9爼Ys/^\"ኀ- ư8@b'9@b lf,bu@bEeaYZ26>S]HoAIJ,/]~ryV֑R*yЁH9Cp͋/>n- Kk^3ϟz)x©N8-3RDb>c۶*<̎gYp9 #Jxzp/2Tw:)q-rBm߶mӐ 48)_{ِꄝxn/?u5 [wq1lU|eOe!*JtvcVpͲܸsSҸ=W2Q.H8xB Ri P "ZƧH M5"KRX@b1&D*ee!c\:H 7ֆAD.=Tiy!@j`#E$1.umٜ1!\߹D {=?yK$Ѓǣ*^]Ԋ;hvTʹb2uN{o4]B߯_w1e2cdgvtB?CosJZцzR.Oʷ^Kӫ'Wzӟ9 t1mۖm;C(e * $Uդ8}}︰cװn8t:v2!bX GQnZR1zl!6q9ۘNEX/R?$Fo}%8^cadCʲ DeYƫ^2+bק.xE1@: {xRs _җ7h(m=GQ i3g΄]+ӫc/*8.s.Bp; 1G4trŠ e;>8WΚQ-Cg;7M2M;N),;E{v#.#p}R ck̨Q*'"_b|uOi67Euu[~;gW݊)o=09H.[re3wMZ:i@Gw?8p7W}%џ'aO" J*PmH@J11.Γ4-8WޚژJ1 Nc'WN}AS'IѶm%̷GS{ﴮ۶jyim첡&4ԠgZ"l,1]P]k^#$<ճ_ԧ|[ΝlV􇻧V|ϘiU{7no900ƅȜ+BWljI` O M[(nPT׵Z0`)2p5L)e766&4L7%'B2y]CG?r)J g.1 1^.`]6 (m"ZЎ rNF 97!܎t@NSƘv6 .w-˲P0Jw],(>v6Iw+}Aie.d2 ]uauaڨSX!ppuOИú炴GTyQ'MӰS Ab ekk+I ۝> cwF]S{9)gatו}smiQ boԻVU`oGfK#| m>v̷מ$t"1ʕ?հAnG=B_xDg~1 Bp%! CVɤl0 ePv\-Us.yxs1ƌ ilu7:c\{sm"Juw<皇*{tMLÑ?Yp %=Y+v7f bL0&":""2Z,&I14͔^0Ƹ6͙u=Is[AR,rD뼪&;) m!z,{ŕfjA4R%.&`|mm0Jp~Cn,=Dzk'7~(Vr9㚹%KJ xfLkm #o屽$;w|dw |XGbW5l`qpl#f8sDŵ駦wp抴<߭V*߸t;~gx^16ںo&i_XիquF3ঌ,[ɲ>@`4рLV槀eLZWm;'g1`| oȭW|4!"g{ڶ"rB("カVPLsW +4`mb9Hndxmn~ _sAcK/Lv/p}(Ǹק=k WB'J7v;ٳfw +W( .̽?K~qe_>׾-pg=]H =9@آ=0ֵ1myk i0pB)` 4pl_") ]?bpƆHTM35gRfD>F]Mj9Sc)s"K8[]59+dN jc#U֌1&R%s9~R*ưmg`MmQ("J1ѺH_o2xޠU6.&)EbqNKY˜kic5ei`g\ozMʼnKwqe}LLtW*,3&vKjWh54YrIvK4 :1IH0&rhN7i-u&jʔ5޹uTڵ^Rov_8ٻGh*dѿ-瑵!r zxβp,3yϒ1uYo~^=*jAm\tl:L}͌;fPD$MS's[w{v]ݳwkLO|Ax QIs̻9xh4!bI˟ g oXYQvvv<&Z9u珇9<wK IDAT2u]{zqA4ma3;X.IQf Ր&>n_᧮nt;'nXV]%L0>~{s!+@^75jqMN;l{oWX:Wf)toz/K?5O6n eg B45ӅsN"=:V8mJvS[8zBd87F1ƭ2YDƘ@Ș!M2.B27sh'ADkҤiKm:u bYQɮeYdq.x(+jES>a\I"."jFCZM|O~}LGm@ƘU4 gv'5>{"XE 间֚,Ncumaa^źX¨QP4[..yoyW DNZE34Y/g&g.=Lv`n:}8Z #g`5FCV3 )"Y-kG,CNtWіY,4 "@ԁTFDYx?t"DN|-7:8i9\Sk]k4QQldY7ϗ5AUhf\yA\o9؎gCV85x[Y@ bW"2Ƶ*q1fL c Σ<_HN]O8gLH7|v[PbXS3KJMdZ͹dD-<[7doLr߬78}͘PtV:%)FB7|{6i.*|hLLb"ƒRB.8QAKOIX\2e=LIM>Й8t@0Vژڙ9 樀q[o^h?n̜zS9MEۮ^yissV ;JSQ] TT ik'_=ɇ֛xvퟕg`ǙpmitțPU-( |p?$ նݼp|t:9nk8=#[1ȹp?Q$ @<ȾgvmNz>_)xH%νg4c$ (jq vfo)$&{+}1BY)?C{wpp Ξ=uN않@фm)oߚuyܽ8ޥ e5i!QN"mʇ_QT Ll>?Gy,A> (Ҿc `W\)ΥѺS#bfh9G28H/'`ZWY@dAV)fI9>Rse}bfzM1+hncm:rTz:=,C!b( b7>[\Y_4ށWg6+4 y.&gkqiݸ%M3uοT^*sAi}mRuKPFюmJFȓHJL8fT5liZ♛g>3]1c}2Ɲ3zuP\Z}f^[.$?gG%Mhx.koB3hoë_>O!D 1׌^a:d=AZEtB5{8hϥx?3O=Ab *(Db2;ؼ|O\B/ӔI?UoYib \xwfw}bn>GWq9+wU%2$dgn22!V82LiBHf2^ݑ*N&iLfqH̲^wҴ/|qQ{F:a(ظMhf8BG5 R&ҥӏm.?5+\Xm+NX->Rݲze1Y8ֱf̹CMb,s82RcTQ6M4Ӫ>ҝK%IaZhGKB$Xx/"lTUUc1j쟽γŽ3X<4iԓ}aa70ˑJ371X\Ȉ ¾laI 3phekcjz30lɗ u өͶ,^f['=kPM\6IY"uf*D"E<?Mvgô+Wv?g ?wԻWkŧٿ[;'}fj\朮'ɢy?{~:G?xT#Oj|͛~}ߦDLmIzUsc8c_yh?)?8q)P3r.*6񒅷EaGB[,wt hiY{F nc/R&R&:艬ս pkkIYr8gGn1DEv\3,_AbhQKf̅ן9"W^p*"'/AX"n*l:򈕍stR+ nPDt@@D@*Ce+M Tth$ DNH8Ir"Hk]MSZ9V{ ӄ\]O\tqaLX,Y眵KꙧYNC<#)ッKD"O\,\iD9[.KeW*9 5k+BKX,Է.pℐ Nh  I3~pyMQ (`0,RzM;/XsǝV3r㱍ȘBTkrQAZWYg|{(cY1^I*A0G VglKkdm_X{,fk=P?/8;nx?$_| x˗nי8μ9yѯo0Ǚ>51{SnKOL$qcP2lPד$ O?ٌB"$T%2v)D 7 tnH DYksJrʒvZc2JJ 2sP$4GҮ\jI 9D|nEˎ~yUK ӗ^6CKk߈nM;&\C=՚FƹgpaL3c {[7^Gb}?4җn7}l}azYXc^H`0@ҟH[O|EvͰ6˲nzPWuћ=?X3usi*co'R(M{z/(UE e"r_DbsؚnH'H93\M)ڟap Zj #dd͢s1Yv"2=*$D}gsFJ 3Q*QD.VK\L;/~sIps]aӑkfϝj]qslv\z\PM-T%:pl:^#H%q8Xq$sa>sꃩŲ3S*wc R1s1"٪y?8 #2v2aD>WZ@cP2vKh,;ݷS;}cWܜ_ )'a 6'IU@5Ki ]Z旖B̤Lgģ<-lCSo(ph,`¯^= iux`cŽפ 9oZGrxg:JU4(]wեi)M6/N:3zfν>۹뺮v oRSb0s=y17F M{'c{O>?CÏн![¯@zz>`[ZYYY* kQW=5\pv_'_/8a)Σ p#qBz<+'iY!bt+ i$G醘nCp`"ՄH PiR‰B&؈8߉`sW@e";)rq *Ƅ11 0NYv*J׿stЉ1ѧU h]@ J\<)!DQOﹺh*؈Ո  yۏwwxACB$)o[{hk帻E[=EW]V>\D v&\D9ǘ$re  (5uTG!27j;3\}+q|voOb<;_8P !Z\f;ieÙ`CW:ēzqTq:w~y +0[*ySPpAكßѕXWzm8ӺY9ŪUlXѴ3?Fx|nO mO2}Zyۓ z0x蓏R[oUF]>JAOTG~Ps` ֶu?|dY6q!RG-//cө{B t !&0JS/y9Vu37J綷-~{2{ߺa--RJ|/T,\Wѷs33zp//<1E@Z݊4?"`0SNy>5!`6{Ưs~v=DQ*Dr.>xZUDP+FYف&z[mG_vm—xxN{_xjk?p;Ou=˘"\2载=_Q-xWȲ,K 84,}QțTbw鹢o|NBzsUuyB @n&28n<:hȞWǁr<^.XsR {p?UUy0MS"RJ-KSSc4実NQ sZ4mpՁ ֦C^9kd(ހ$]r1|@c΅s9 cLY"RuQ,]McƩ6EB̘wZPS#A 8Ad@KNw (#MUMX%9Bx%m֕hӘ~]Ƒ.8ak㬣q1[TjEo橧O?]H吲+-ŽƠ{eM Q=\"vNY,N"B{.2giX"j)elc^Cfx⑃`0~SR9Q4QS9爘1ADIRyTU$)]UjC(v騲D"Dxwx Mn-lpryq tzdSb C9Yܷ SY0o.CN\w P {4$l0 `&C5`߁D-R^/6iB,[jO]BfIIdbRr-U@5ѫN?=s k*Lǽ`5wnmm>5Uݮ)1A^=7&J헿Mٳ{*wqRh>'N^s~GW.f^z }ho׼>88=>cƎsv_=QZ3=j"McY3i\ /̐XM3k YNYv'G|͙{Gߜ@·G|k" + B ,"9eq *Hx[+4O4(ЦhmT0 9qo\>IYSk^puunLo<ظ<1HL"̷o~ Me?vEY̲oTeGX+c @\$/3W=W9WuEDy-s(fVUc""֠@\Bv3&ZTU2数6Y>8; 2 ˵+(uVoF٥ós%N! І4]眝@h3A5=`u\㾨Kl̩VK*> 3cQ-ke}"uCd.sZ9 DqIp.БT:{ǝw>SF>Ԟzꥢ~w۹t/,[<6}?v}T*{ ?@=yυ >Oy}?Գ<~ms~eytRKOa9lG/2ͲP8g9k2""/PjƘX_a "Y9-ŞR_f/g9hu5 9|kk$m|XH ?JmwapW{^zi ɓ+56c>}g3}NJfGv9g lB=TТiB9kp~Oٻqmdrmyʇtz{{s];;;ⷄ|EwB!9w+\m|*02v}CǞ}ٲ,}>53a.;#'ll,}?/=կtxX~Cw>={ Av !d[gYX];34ibc4Xa*Q*E<:M׫Ӷ8ѝ,*O']67=Gf{~ܬ=ztRT+0H0Ǘ7(W7^}bpA!ng*7.vj9r,n%1kR%I_RIGx]x,Ƒ`QC\1[w3Ғ?QUUaBGs:._!^/S t;篼ǗW~W?UZ",6\\ۿ; ߹pv{r͸b~^c.L;;;J)Hp(]?}AO+ό :`8.fC]ݡ8._W%51AM-5*-'[/L'\e5|m- BLBL˚9' C#v9(f@AZ{YG✱ miΔX[iN}1S6NUPٲffj4h4[qx& @#x<?gK#nGRq! 4zB$;e%hYBsJ8j,fꜧzRʘh뷨]Ad;ruш*Tuu .@NDUt09Qet Ͳ D8MD{~jΫpx.%.@!,CE`ٴ<\ݹթ2n%[`}~!۴^'V$ @4X)\himTd gI@0(aծ M9st-bgf;O`-}cx }]wGo:yr'777Gn46^s"~C:~ݮ[PQQ'D\wd/Ԯer4c$2Ƴ/EQ(|a!r"D[T`l ?a1 $ 2q4C8YæT9RQF-DMEIDXד4-(e f C^ %}Dޠ}xq:eOZ Dm^rͲt[KnWvg`,~xkLSVx3L<l/؃,ǾKͣw}`Ve((FFDAL LN2̚Abi izS볭.n=zn?,wMkeg9a,F7Y'r to|æ{|KY:X(ʈ$J$xy|.EcY^+l Z3{8KqFH⍬wRpl`5'CP,AԠJ&#ŵHgS:[%`L˜A+x֍1Pi5H@ !S5&xz] `b~GxnnhcǀN9$ݞm<{}t: m'$Iv _M\3zKy6Nsw Px% /xfx?YSPW5M9}ԦG>z(!L6e~ioAˤ#kέUkRL4*훲|~/>߼JR18 v%p~1(>P$a+<@PWKGEr}$e ~=жknn?,cs5HXjʪ)UIt:2N8,"FDD,%P\[1;A|~+%[ `= r܂mLSpChVbqªJ-hefޚo2dqr U>FQBH*"W<_{sַJ59ǧ<8` Fҋ($8>Q m?dυ|t_ƴ2Ǐ\-S,)kvvUӖ [(԰NݵD r_ٵ D%.Ň \@bs_@I6WlHQ"?uE1ցƇ]{,Y;+nU v"RKmPR֕^n.p@Ǜ׭oS Ji`e'ERaġT<ϛG+5/{ڼG+|rxLjJĄr+猭9l01T7 HJA#&ERzRB`+G(QNKtbcw *Um8$,,yTPUsf^az^ rBnh}0a &IVǹ*|\YXIXh[Vyn`&{ 4$qWAd17gճ*0ȯAdyLc%a, !,A;ƃw;ڤnoz;6LJ.e:kJ&*K׮=ڔd(Ky_W+;8 ұE4g1l\O=d_~DXO2WS K"G^|mםlG^8}EjDE$@,CCDD\;([z&Ԕ$ Q2d9험CX3 IDAT mzPTQ?Ζu]dkGnw7FaO &[)>r 6 (y>8xzvڂ(>yOԟF䇽o|5/re?#?ҸԿl:ҫ 5_+$K,>/|}v^cmPcܱLPHۑGRGW)30Jfg&*L^^{"8Ùa`܈9v!O& A96"(;W">:{/vAy*4%O Rz+j] +JH oFKA̔ ȈeoӪ~ep~7ZS,N/[u.\ffN.3ђUUtcIlC[ZwE;rtt\,˓QmVtt\+qpyڊb}iߧ EaZ,2+ϧX* B*]fto!ʍfɱ 6+3z:<2ɗR; bTK a5r#b礶U/v;L{,jOFr4p=kd d !'c31j" !}kH,qz30)X;ҭ&5*1Q5ZrP&uT(t!0ᆸqLlkÚ3HF 1-b#gq >m$א kxr踪YO)[ |$Ŏyߟf8^jFCUU8n+cama@)w÷!v- צt5>M76Gn{5k7֭uAq+iG0lpU^w^}xqsa[!ZwlHDMGð.öd-ơ m^i:]Qwv6xO>ys;^elqN, nM624f?( 4msIWjc󼁘~np"q%E\q^w^mݶ? {aD}q4rzuuSEb[()(zsT0POD~Tk)2 p X Rc]q)y]u-tMZ6вW-#<'⥁,$pfTfqmzw +九ɞ1Z䩩jfBA_JE}?ZM sH_X+ʹ_DT>Gg̅s#~)aAVe, gzvm;+O|pW>%X DDdFͽžAtK甬$ѡc TWCDI{79hua Ξ:.Iep|}J=:g TŽݻ!WIE9E5A3q2}lUeeQ=#›}cJ-|ȩbq' 7 5[ F<9яy|Cߍ+b`ƀt\vW2%遫i'+l 犧 q|LV+v\a!1xEwz4Qo\TF/0p lPqgfy/?F嵵6>䟲,kAÛg9$Қ_om{̘lTK{4 XxΚd/idXDG$h-?yQ褩 #lai޿c撞Ogn%?{d?ǞS9 ax1\&x'e_Vꛮ,`b W*W8c8X<ǣc+U)ǪX,bVL$2!ɘ^^jz1tM"D|@abb9a 3Ǭ* kN3U;__Ka=c\H,Y QL V (o\ʘN8枪Իi؋Vco;a7vliMhi+^lthl uPs Z;6h2( i[U->Z4-c>[Y斀B4Zt:my1+ q:︒oﷃF/.}%G[X;6+l`{f+ZA՞5GZ ]J40 6jogH{$I<'cL;i M;E,~^鮖\{n شa+o:.*SČYsv7b,+/_/ׂRxRW}/e ICpZB!< HjjE {ߚ;6tEƴO$֝Qk6CNP_/33o~I ðy2.1_>)q,XuUHY8\$"CWZ2똘W +k@6tMw=YCc&g&J|lpqE4ő7 HZX AV a^ZV)S oz}!@{ցkӊ̈gյ/l6VaϘrg_$dRyt L |#^쵍wCTeT1a$y~(ȁ DTCXp mMpB$Q=ܳg*S!mh$i]\HR]#W8NA> <ss Crmn.ĜKȷjHQF)P!N01ȁbeso:Cy,oGYuK%V|IQ.ab]dnT9j`;!׃1!R{b=:9K*(K8},mב}'/9snawY_7˲|vo;$/?u[ot[nm{ѪE:)6#>#Sgp(HfyoFPpp7Ht(red5<>Ek׭2ž& HaS5x /R༩}oϡB/8uClmpeĢ‚hV+i)@}vzpS[LAЋaUZey^!4ccvQjYjYc4%cUFe2. jQ/Ÿą7{{&Zyy-dP3Ȑ,9Zrd.81B$ւBKX"CNU  pK>ByҢaꁟF4]ZVi7'jMԒ?D_S sMB#pJ3KZ(|&@选Lu]0FȀX" Q\ ^@D" CsX:H%L`Dh"ܺSKuB%K> TC}u[1:41jk!G YP 7D`f `g<8f\iGNғN{RBd}+u]7L^67eZ7k{6Qn6[iK[*"jEZ\|3}CМDZy-vEɷRNf%º4p4ITj(T^,-Mܨ={v4=Mu1&I$B4]X,Ʒ~n:ZK]a"/]0- io]ɭ罭 L ]om ny[tݰnS[2}o n;w hx<ϛh޺TȞL& ! $hڶdL{ {^7{'uRQO[RwM5`2X|ܿV,T:وyMlX؁JB<A@UilY6F1w[W4=srSƜP *Y8[[e煾{^ǃJw,Ӧd4 I=**G(r55J%{\Li0)G2CMiIם<"H A% XI+Ԍ1̖pC7;=fq{ LA f=BTxL!qDgX;{Z>lAPH44GTA?#|C^,1ӿf:Ww\Ӥ"K|ieY# Dˡ#_bxmm u5+eS{#-[ЮRʶH>[A.Pⶺo>n̓ _t}5^wp;ue(Kmcvzw@AEN!YӁ3'ܹZ2W dȃfq$wTr9)ȁȁ,*Վ U>&rF9D1gȇi<6Å)jk Hk1_ <@a&ZR!lQ :)t2KSJL;|6:\eB;r?42 MWBh`>\=K2K>>7콎IY$3_^1!tB8) Ʉ r a +T@ބK 9Wknδ$B!zbDP!6ĐklF8PԱ-`10@*/-Dd:KUAzP!@l 0D]%pæczȁƒaΠ(K^;H1DoI)0}LIOD ]V͊CJ Z W~\DŞv07i`;Ϸ4=+Mo]y߁/7=qn(xs[_߶?6Ͱ10تx/$pgv83; _#88B>h >^UP:U7,(2;"+W Pibܜ>"4ե! H8[1T= ERAP ;Q$q)ԇ)uυnG8=.Qר4JF/` ih3Ti"0gjEoErg*XSMLc)0$@ʲNHX){ W%<1Yh0X h@p2 J~j G9[Cn0=DIzDB"N|PH*4ҽ`33=cB0hǣsqLX#ZT-=3'{ם!$K8`$D%ex_hkOCCX`ǨJ, c`aA5œM` Xo!Q0Fہ<`QkA\蔦j!wA)`a"w" `{V[y>y֯;(>vІp-5 G[ef^[V+Zmɤ:Z! mZ1.r~3,w<( XI8mXKo&^k-F'b e8&Emܸ壏>ڜ S5D\.WzO>T=Gk3B'ݖ쒑+ݓ.6sO\ٹュ k֯kS'eMXzwݵy`W7=ϧ IDAT7?F5[4MMR68!̫UљݮXINRy>'~B0aX 0IqIf҈,y:7tYBJ1kkS w%g3Ol„!MР-$< Ah'k$ 8\"jZ 0B?$A/A@C#1)B o/f#1ܶڼ>,!b,c<a| l$9%RSkuoo<k&"$IrrY*%1X (g !X ` 0 0 5(#18{@4 qiz 18aP칾鮘O1d;9^֨ DC.Y o ` o U@J+a,Lۄ; nd8KkM7_±Pjp2p[#мj:ZdJ"!HEkbB#.=9< ecIH\9X`51+Hİaħx~R-Zl;>1jǀ5a4CkKs!>WBM!/vPI{.Ls0Ыгn'%>KG?;pnurG4o쥈&P~}Ckwm\ 2Ӹ]NxbF7>|z1m/MeF7|feY0yl]=ﭾ@:S_ק=/{G mN큷߯R> Xz;!r%o-;]/Υz (ث8p_'r>db[f 2+AXC0Ѓ( pI R Ġ#WEHrìOAwEp4`A^@k3^%Ր|cc.0QŨ5Bε$pB-zJUP, F#nd>f@  ^!EXCPYx >V[Cv 2qj=q .zn.-"v7"jf]3 B rpk`-P3 ^b"jq@""pK0DClgCsB!v*=!H|F9a@as\jb" &`o)+Q ;l-iqc'և͍]ٚeVUk֧LdWh.1wy2.zKӴIiC \|O|_{{{[[[wvv|||onˇXVmſVpZ~ճn6+m^Kk깟~I?{_x764ʝ{w/${^5ݲX, =yCpk5<ַWt+y_;u"&K(-|T!+ܬQL1L& {ǨSvw^E 0[ Rx{`-E0l*,yڃs1XX` `z@52o<rX1b7`! ؁-s95}Ab5?`Ma+fj]C*08Ap^v; t²@4,9#[ h^htWC17P  p49$ !BH.V59R !k̕k_ñ%t'%KbEGVxK ԛ )8:2Rc> Pp2B`pp^c GPgP>s9DcD W a"l^K|lOK\b$`ނd",o.N[T~/DuoK kWOJ+^+UUYb` ~Ʀ54MGQ6ֵ=/ aomXرen%[*\S8zrjbmw[eWwlm0 Q_5# ^(~ReUUin=_kĀ\jwݻ_isW. }hǦ'MnsHٺMQƭf7ؕy:̯XhuڞNN?xXUfT3_S}~KEQ4~juceY6>,Jj xyXW4޶ΟDKsB!g08 Ǯ³8Cq8@4`#Jx .ӰpTRF9 RmSE$ HT,j Ad iqO%*H1 fHVBF`Vp,N]B[hM(;a ),Y43T ҃: hb$1%eK2>e`@ DOӇv{F@D C_"9 % 8sSp I3=x*ŠGA=kBDtᔤ[ Ƣ`'p`)^L+L2\:>*C`pwlE AC` g~ߤu^ j0Zzז̡"_[wO ffig'dA*.Y!(;˰H?Yw~I zo|%>g~w=̬zx?}ZCĮa. lp2ʿrWͻ}3xU{fwGKQrPv7]?'χ% N#V-P`Hc=1fC9A[Ρm@oO %\0_x1\ 'khX3/p$i@TC YCH"d#.8)$< 'Bp`k8+a^e&DOt>Ŏnm ] 923U?*T)/''~DJ8=/2 4/_r#'xpгKW`H>j [%@ L@>3@Ňv@HH$ csyg Q=})Szvn(t$.Ŝմ!ypd4bP `rc@YƇS tڠX1V`g.b-@:@h!(+8q? @ u.3e ABmH H  ($^pgYO OIEls֟*'95?gebV۬.X~+to|6iok`0Z%. b޿h?Y.ǿGtѮ&_ KnV|=g?Gyŋ-%Rp6+<8Aìp봕XηJޝ>}z\޼y1?5rJӧO{HO7 8MX9IvEWѓ!6wnivnf>wuamuYJ|O>{f?{olIv~svno^mUUwK-u7m`_Yym{,KJYF+`V ]* {ʕ[Op4.“ W!=o+RBja/P9t  cY1y[>4/b# BC=:b8"#@6  @{a&a [(HsEFN5$ P|CfH   1 b%;uz40/ً#p(x :g~]`4q L@Hg PR H H'|AYښ -a|r֓d\3v9=(p0=CgTJaLS1s}ڮi ȁa\GGcy@(;ZiX@ q!砛`!i?έ>3 8N2~8R>/u/E W3,&ڗ2e7˃,{H?nu^u;VS]߱VlQʒZ=^Lӱ>jVA.V+"[cc ?( $ȁEo1-/C[tkgɶz\+Aq%p]ז)p~39}Ulc1?gYX$۹;_D+/i)ty^ KKK%@a5 Tbs_[hdVS.jr_Nύ/I^)5[&o[{,|{ _8{#Ox]Kڦ)6( Qy}[/ZG#lރm_>]"u%kTwLst.ꝞB9`e,(AQbVkH!bjh ΃K0 &)@\28L?ę^,l1HIKAv%sd@"@\(T"& ͠{JX29RF. k!rٱ.Rh3)F] Qjk@G70pyn}^`2 { r@q€r{Bptn| XD0̜nb!lt5DF8>c'a^a\ވCgY)VnXaûfatQ,?✯\z%z}ߎaOe| Hy̪޺"U{gG{>T\YOښFrV,8N٪i+'?$\ZhwwR춅4Omgi[mSizHOt)?I#`,FL%»7ղ;nj ed]:PQ-M :d9 6d^,Aw*D.ocbs7AT聆%#Z5b-5-L(CtrU"H\p%`/%$xB9ѕ JA,އ= TU؅8YSE;bGp'Q~EXl+-n~ᨊZF4"q8!82 Q@5T((^c&8 @) @/ߘ;Ruk@= {y1#2`&XӮ$]`$5bno!1L߬]`mRljUtӊ+wII -_]e&p4`KAl邦n/r/7iXu!!t ˆSɱ8;r3q!{Y\-^6vi}e淿~5q_78m2F;.gYG|Կy~pyMG_s~%[IN1jw|:7mծ0%^R|>mmxlrwq_&ݰdk 3Lm*/LJ8G@0 !0a5Ƒv٭ Q9Pp L9P | m |X,P$s,,@0. ej6ieDC _OڶhUnAdOLw"Z44n9AsVefe%"ƘcԺ p2ȠD@M!t-z'5e-A`Jd0 `82`_dYacPa5M(DyK ٌmeI<0J9ir2\üҭUH4(ÃeDn+p?]#1~ϱ s3 Z@ 4tI.L cJX#h`>2 RRrL4/HU(Z7C\8FK]1&-$A}s)qY vj,Y*/fd8u鉅+2z*ዽ~4UD$B66uϛu=~:w ySܒMnQ*S "sK^lLq6:8Vžߔ_*7V^gOͪ]i,///,,c@DH]Ĭ'k7K2/˲Rgum{tS\f?}O/rr-e[rt1 S~==Uz'7-g ̔e 72n cRMBnʩI]o+;DgQ^ hG1`\tqLG;Pl=# x~Jv&v0?%q,PˈF`cU PҠ8Ev&TkY&Q0 00#P0`f 0]0!=Hv@Jh Lr5O\O(FT v'%Yg r)shF Ҍ1.le3;*2hƄR˜ FPKD@ JqP IDAT@o 3`4[% @>h& HA>49*)U7# rsD?zwL*'-L&K=jTg# XgrUhkʼn'veWߦ?11ǪEh:W[5r3N ohVljeS*lMBX8tPǧtk4%)1ms9x!`1ZV}XgVYu9-b8+Oy1Y \-P9ڏQZY}knvyǾw5Zr~烿;xwYM&z,cV 80^t||AM1+ӌ :ξ&ݰWdM'eqk?ҐJ2fDi5c.1nvvgF46u U{% hm]\{%'2!DWLN3BO,f<ጏt knnC}dχC?]OuþRw܎p5G!dFmJv9 :Wz[pgTb$]o]ycC@Y?Ш!P[{C> H@AqA E piXR$Ek,Ϡ`P1*P҇t&ކ` Q{i*RM1Q &ћ#660 ^&" 8THe*6cs'/\)nn%P!8G(2&Tr> g&8ay b`"ۥ^:z.6`L)T"K1AL#6¤CcέNࡁsOCf\ͩe{~Y  ":y.vm-F^/"IG+|$c a0&ϯhC{j$C!;omC!!uOՂ:FЬ_MQD?h8ZzTx䭭2ARj}~~2%RUcW]>PEf*z# .VR,Sel7,9GSLL5ȺJ%N Uf\7qRAd<+cDy^l!THxիF\ܶPPSh2 ݸse6,9W˭ZGKsu8th7޼wҏ{kkϮQ.ݟI;D-GTDZ2ya's5Wo"z!ߕ)!*l,^Q$ȸz؏Bf0 W 6+[kV hK B1LkO H8jgPaq{18ƲG=e`h Gp6:` 9 X > Zؿ{g {P9w? s]߄X)&Ϣ>qQW cI(l F 0:B0h` h3)`H@ GmE5/@ H1yJ j@X>q3Y ZȆd[|ZU}*ƕ*D.ER5BD<1`E 5̲y9P0dD y#`<[}رAڝ0&ɠSh39`{Å@$@π<0+F!dYT!#@1nG>V]Sr/2_8K8U\]bOh(M"ֈX]QxRmwˣq[.n8YX'ukWsq\hBͣxky?bmd+lYmY{}M^A_k_"v4zl/ZzliϷ^_+V&I1Q?%gh$M2~!O&_V[ ~t磯ioW}=B3\$femI`Z!G$ R7sTTYnjw~00_A86u*@NjII,f+ǀJJLC j =7G 0)4k( ;ղqP)'u4sI8Js2QCHĀ M02v lE~YȫP7@=u*Ug_~0׆4H@lC_HBQd/#N1`0aP_͈ ƏGb+8z-_YT.TQ/Ƴ!H\߱sugGoh!B8KK|旳/gxx%&C3C"`n ؓmʛ8رdgqKá%p2PɞG#qa5A0 2Ή q&0S1 L8VF; 5ޱ:DLP$0CPMHB Mlfظ~wS}1gIDgUU7X~t3g3-vbj~St4*B8戊.56腲TeeM˷tOa},1f `Ru)! 5,^&aIӲ_`k01#}檴iqZ;ږwO@Xxg? jl&R.X)w=?f-0Sʨj{y^bh4[Ȏ>ag#6PU+1NSMD8` B8Hd}r͕,r5A7zt`Onhv94 (`.Ӂ@DAbǟlܳ=9ޖ]858gq8FH"P[@p+jh1Čހ'00 HƓo Xj1X=`@y=EO1iY!Ik?SHD7ЎPkïíK@Z"R,@VGݛ'@ t2Nبs.B I6;P )-ֻm>d4:BhR|:xD7C #];y3Xѫ*h) Ν,\7w~y/mPc3uHtg]bN$'^;+nh{v\sA$گ<G+~/_~ssGGL(z7Y0Gn]&?wFΉ?}51nj9%*]y󕅧㠷~~a1.bon4L$L0t4٘-yE@q"p-h<9W9bt ¼7x8׼BPˢ'] %:)>?ѩqmD6k :"/i?D$erY঻>2 EbQ8҆R򌓩2dp*P_D@9`hOΗcd Bt)P$O"PG! SqsQպd`ѺTGpQ @ߠ~}IwDݾc43{PE \y+Pn. |M-x",#( ɧcnbv OE#LJE|}p֑>[Y2sNĈXՃ7ō;鮹cǯNZfq.gve t*m@DSdgՃ\`$eZ~?Uzh eg:[ygҩ~U%{[/{&–>IުYncՠ{B])6&ǧSP%c~S"嫬M%HVRgI5KEcGRnEQE/ݯK*H)m!r#2K@rZ?I8֘J\j/jX]$/F@i# ,yFEe˳I)!pX6mTK3A7e1pp8'@GռXvx wtVg53#((JaW2$4-]!@b~Ǡرz>|d 5#I;\ <$M {%"I@!Zͣ1n#G!@E!)y,CC9( d (#5Z7E% vߢFS9wL>Ex@:;C@jgsh@ Eb؃f꣦}JwnMѮRY6tuHTQ$|F\0',:!jAn*LۣT@xA29TAxr8Zq!p'i\V~ډ~婗{_~u_"}xTxLf4qVU`=ԩ+9vlu bkX~<+:MF3ORZ*ڰcAj&`B3Za?'ERҥMXgG{du=ކI)uv'+횜ʠP9S\لqTpi[FG|JUYU9?G_EhFXyUsw''7,ׯSCճ75es0b'e]d.H}sH|d;RCd r d 0`:ށ4g5Pqʱbҋ H;M $C8Z9xP#!Αvp轍 }/ӧsZ~JI8..Ooߒ4ûZnЙ.lWQdՠȲxi: F{ W ql78O.m_y>{q/Μ݉"vm/Z˷wzwi/kK+^y\ϨOouǚF/A%۶߱utCSN 4cX4;ˍR9W/8+X VE#۩w32:T\=*1*JHلV&67RB<9ϣ۰tplWG WvWjK4+A 8^Ljxnk6ϝɨǝ}jOKYf&+*UBlpRug {)I07 R ,nTA;:}~2 T VrO)u27tzvB3l߸b:M:t\  I#l7-Iw}_[a#t# JAǫ;m^! k^wkYI,NRϐ;hdXӸ$ k/$,7Dz9Dnj2U1 9Cʑj$B:D:@N4 %Fta No~Y0F fzX^,~W72PL+ $ ^݉SU*:M7UM햡6f>jZ}߯&.xXwχ>^^nVћ>YkUw\=]Lj"f !ibz~)YO$}ʂ+q@E|TžBJp\BjQ-!f9ڊ=fJag֦ ;\6S~=Hw;w袰(r1vvzuZ{,Z[ /TG5h9_ .cF\8VB~?QySԫ۟o K*R]1)2RJI 4DbˠhLF 0Zż(zgZpi!0<p:&;pk<.ܹ#B:f}2o3dP7w5u"w`հk3Ue*|ngLvfl2T'yeRq]g"~Z"c5MrzUSާt Y ð\Kl>0ZP&@"x/؍A^"Hp:ۼe2zŲ*KMokؗ9S7l֞zZy=̵Y#s(1pbOzC#6j|3fimu67iQ`E)cK.>.N:8uY+W y,5 XV~#;u 3.\|>wMc维x˩*X`L F p-kbfG#frALROe# !FO`N bo #զPk5VA_챍O[mw*T)Yictuo8C>-f8v:~MI2vF<|Ñ3(-EG6W2R0Ș%HՒ6+Az׳1V}`5Cooә*cm&LvDsyJw;N޿۞z>p?b;ztݮ?z1jeY J V oHLW`?ΚuaN1(U2n5m{1յg Rlܹ9XY-9mֺ|Rj5az4^n\wp !l+ZS-Ji~f6fyZ~yi^mcyWVYŹb^?r_7+8nƎ9I̅rB3aX V׹4 v[/ C&hEGlB "qcf#1dz3glo]t/"P `&濕:?]&W2{D?~ h8(`R@BOE{U2,Hc*J2SU'UƲj =Yo‰Pjx,߯Org)' wCA(dI$EV O+ E j2ݫ@H6e`5PP!.5sG@wrE6.R.Cp8 7_p>4Ag&y7;Db&A(RDAZre-keWi^jmpZoI[+mJZI`T A&`̽wnwLý " [S=>}: _ĎFIi"8@b5V2|1-el!8޴]HhQu):4ρFugJJδN‡W&&ͱ$?`3ZɵέUc\DZrŕr) Rj;Ӏ0 zf1,?t_z}&ٚ~B؁Ɲآl+t:DDe-Ds+H9۟}>b7cn{s)G~M;8qmo;\캏Ra uKʷqѱ #ǭ4]|o6O6rwJRhV84v\`r+D~/wwtCsG7/0$8ݖEZ &;Pwmc;!qUv,4*q(sḗwg"}\7{d2ADBH"hs.<.*P{FH0+jT%ƪVLWlutHQn0(%),n?@e,ob![1$A3 4 `a{`)x| 'ckɎ&U1ܛ}\K<0vzl*C\ @BR]|ґh5d! i,CCeuhÐ AfJ\6pcx;JX#5'Nsw=z3_Tmm ք=!I8>2 H#!I r ywgxm1yXwq%Eւ(yӬ&/ g}!I ;=P7|$,:#2J%rcy|Vٞ Y֙1ΘrG2N#pkgƑʕ._6 ck,+6 Lf_/v'ӜÕgd*{y[=|''_|sH)?$j~SeՑmu~`c=-L ۿiG|n=w h#"s))=p߯;Z'LOvHU㏪Q;nII{~ `@"p V7 `!&v~qsKpQ|=Iʛ훢*4<;CPhaO>c=/az  ]j״^p0TK9,O`cL1c,D4h|cر1FW ?ChUeMcرȀj)s@VSHCXE %BA6@@1h-`WI]ggG%q`>Kjc:6{{g[yGgCZoK_}Mw-&"XcYF}FuB`N#8j&}|QwpzR^. |lݢ?35 ;${-lë ɲ4ˢYw=^??eط<˚2ٰ^ؾј,2&W*!"ƥd w ,6Y..6l> &E]b#rFzSi7izCiݬHʭv Q&!)SՅ8ΟW"dm[e"l6;eϟ]]W./BwdrQJƆ:lGSowG;Bڡq-Kcv_z?Dcm]V$ǘ_`96ZڸC ~oRVknw܍U kٲܿLJkqQ.ë=+8%CRp\a<8Oђ¾{L(0{KoB?> qJ,~ >x޲%H?/C L tHjƠ] v a$! ҇p lİG`2اa ԃuVmpyqC]N~.Zn`  `1Z&Gt:]0Шn#UڪdcRԈ*T9#U-Π}hY!#KtC \Os͹ŅVwgɷ cľϽٶ֐|a4@L=3g}64=MjBsHFlP{|mD \b0G"WP,BVcMyq8;/G[o~ ̝WS:DM }8lf49u|m89{aĠWYtPU/'$QeQ>j&+W  TurKCA`UD)8A<7ozMqQ)_ۏƗ?혞g cL"3 J*z^)K?pvSߗJ6r]tTJ80no}ws|Q,1"C M Tθ?Y9Ϻ*m\V͏qXL҉jJdy|Ln;NrR-ς_DD}~A]yp^SY!_JZ~˱w!mE&jw 37y]iG˴Jt!5-q X.*, 옦غ(O_;N[vs I J%Zs4?@4,\JaB=ȇGxvcլ[eg1\ a%u\:,msvܪݛ 4ssW@ Kp&,b&~ O' F '!r? 0&%4ryQ&(O_z:=1-&.aks16|$oGr[>z׿x`}ɠhl=N=]Mh|Panc8k¦=P-zxuAhIX=D>@#1H 2*`$ <`.qוZq.U#u]0F p}"FԛXKs+y0[0SU x'9M>:B51{dzj/u}eET趜0O3v$?1ұē뇿S d1Ht mcT~Ꭓj9K76Zk]]deW)h4 '?ΈHB}7X:yK!r視+贝۲o5p;xkTjëYwxiz9h>zoƟ}O>~$SX[xorl1h^Fw/nRT}ZLEmϫ%I1gaiU8c10&'(]|S>ί+3]<7Cў63K{I:YܳzGkf߰  ͤ6:_KɈ hn3!]-J8u9  :d5p{`#f':y[h>U')nVn4e]:6w5-gF.ݵ|k xh1L3xറmX&bp:vtoD A*K^Q gqޑa3ϭVٷ9&@AJjG"XgL+ 3lO٤Ob%ٯܿVRD:S6ꀱ[}6s'6uʅӝQ[J?c4M+C=Y|->f=Byb%g+ >rK U~o4/tp _eRc^8ࣀ )6;,]!D:qW)_7tfeGQ]˦F`{D hUwE֚U*Vk8rCK*]+p)+C hƎ5jcjcTX` 4qpB3-'͛wKXnzsi|4 6Exu#2N !MmnT'Ӎiy0:vG=+,ˤͦl6Ώ;gx T*Eu%kX<O82U IDATy#;\I֥6]hˑ1$ {˟nȢ}Ϥ(%ݸvVPPRwŽQFMa,eQ6nzx=y^8c$IRFcu5Tnv'~y]彺rP))"M^=a}کY^J) }-"GOac(4o[_]0g^7쯳KlJ} _ Mϫ ienتn6cڜxk/$\}j瑬] ^^r Tq%)+W#~qBw1]2?wx B",Lp!}@ @D8u85 pl+k?Jhhvǩ[yf Cr`m#C_W I݋{sNAVƋJX9$>:y` gxS(_zKu)A$ I:xxD#axtCL{f,%:rSS'S0 UTvZ*&+Κx >,NVvʲ_Y[[x&_?M= \"2񾈺lfo7|ABcs\|Yi-==4&%w,0$Znȉ2No<1%y,⴯;~S|Vtg{T[]`8NhLuC!@g?VBG`@~ȵGE; O"pQ pǽ"ĎJ,!"j,HJyf~kL(Id|M A#{ߛ+b>+OxhrkG'wpㆂ`b &Fk]JY]'f|i)$){W3Bz;/:6wXSFSYc|J*v'\wXjXSNt:AQ$E5E2m\/~1J\dcP|CrM).#1h}sQzH:*dh?11h4QԔw_ЍFËL7TyA7._NfX6"VЬYk;<nQ@U[;YLL: wO;:#1v$"YJV~1Pd*$T*+TƹsA#kHyw;6#P#2_`[pLXQGҸE[53%Z^8º*A?m&93W" jgPp Udw{6!CL5jm#\7p9D_=_?:y * @ #|yM< &sɀfOvkkToکA㏧y> T֠``8E$*"(K&|!Pb}S8n<S*o5;۪S@jS& MNSWڗ[X8jr})f m!^oYZc"3Hsx28^67`sY&62H67Cp^ek-7 :D+f.f.^%ܸzn0,ΗtXB3\6&==/7`A5{;+lð5|[焐pn T6iVcv% .Tcf$x*k`#VaHzMnQ:vT]U>[,=DQT`ǂlY+'vvw|!q8L܍oxg" (L}3߹pA--, ,J*5KSjlj(i,| +R1C.gtP`={04-_a;z y7zr")3ʴPez:x<4dYVb)3f0}ZV8V2xٟI7Rwy^~۔On՞4A_Jr74XHwx/w/_E ~W79Zqa)Z>2u.2ҳa,bxC*Cg9!0h4ZāeyYvK E8S{abfILD^ӥ0sFIVVig{\6Gݞ`~q 7|sS{Bj+p.@Ur"lXۄ]}~2lfd` 8tW,;ᡘJ0yRbVhr}{]~q%iz)   k12@ʁY^Tz~Gyxq=Dاo9EhY;g]Κ-Vl!s2mME˭0=2r 9~^ҚF4cc2R'VzFq~;C~;r`# .{L2>ku:}+L1[)>-% !ʲ(q]|H3S}2;x8ĭips4j+QǦNBkW= \:˅ˉRXѰafVk$}{`PH Øw_մRz}}{eLnAoVO^~챳?|_ʓ'<нƑ_$JeҜ_kGݛ]`VV4xfǟT>3qMtmbFƓ tFd5`8籐4F$7fø8>q́f24FVl7Cٚǔod L`/GK̓|G!1JLTHjGӘ@߆ Žuc] ikւ$le{Dda{C[\sHNh#;ݛ]ݸ՝6uFDa0&_sO:ҕ[ ]vF'77<^ҖRֹlAtT폆*j8qg5:GP -2 w"LH[)X'܂tNF6`s`$*:޶db>oof]2^iqޱX?|(یFYI eq2cLE I{;З M=>bu݂,ܜ uCnюZ _@vp^M[*EQrĮ8N$aG|W +ΟG${3&ƪjT)TcSK_/ZAcIfX?`+ kc18~6:iϩjeKΕ,&"xs1qqQ#d\Z%YnNpirgMw̄Tu1ЬQ%NpCHJ!b@V9 ӁIaL; 3; sV"}&X4u|jt(l|j;}lzǞ=y>oYiP sZL(@FADY^km;R.-5SÚy}TN0H"[?K`spf΍a aЗL{V|qe9}3OeL9g ֚[k Fx[:GAm1[83Ď;MAT1qGQ<77n60oXΝ7|/}[:4Wd*zXNޗ2Ny qSSS@طu~Zl6Kz!*1Y_:58P˟/R|z>}"5ަS:wneFbQIJ=-PdpR; fsPnG]Gxx\9M2XDׅp}JdM4~DnLy dDL&WĮxWB pd(~DmF7yWUZBɕՃS9I$4Q\I›[cK8eĞAmgT dA Fx:"` ###PUt2/y&웽􂹴<76g{5?V7[3dt՚^8v⡸hPHDs 3_-i`vZ`DGuaງp)eP(Vk9, >v;b;`9 QsL*(IܢXSQΥh_g>kź)=E`B4跛WafM:r?ݖoo!A]ʠ~(f` dʀl1Ӂ%M"#n=:mov4Ar:l¶U}WO^cu\DA4(Ma(2&DZĉlSg*!6LGqg#8$$4s ,YŬۋ~ºK8`* yE{zȰXTj..N{-O>q WuևKs^|i=V$)ψbXe4.9DVw̾?jQT׶oثa}6zΜAuف!<;h}x =u&u"J.眇5!`Ό ul fݼpy匔KHmo/ &Sg9'Iض@*` s80S(1P"HQ("~.gcqk%4_7 1sqjoN ~]33 ClN,lΕ7 Mq;`kUwGў3 a؜Ex0T4̚VKk~}|;B}CMB* P|rERW]-O5B~ֻE%1e\p$IZq!8&d9V%APԙJ=~ʇw7v,ی{daߣՐ6S%ۦO=4nƻ 99U`"!):Aq[#%'Srܐ <ѧe)8Gfz0bFLQg{3>##6Cq:3E[X3Tn(-/pjǞޘ83l2K} 4?go!ۃ|4@' <0̋$|GMBV l`xK2|}szn$bbbD 9.PiKhf}˙mI>Zvnf Z+r.΄3ݟ\En\V7畉#6˪ #A bE1fb[HgL#ޒ4Z)`hƅaC\!Z']z a+lh#x{Mv^jsJ%3&8yyeC"Z yʔHIS?vL201Rh?y)l9cc3ٳ r5u9f 7PWj?{?Ϝٚj7sCbbbm\m]C2]6aoݏ3gh}871|c^ooFwR_W{E ߰ґnu9/$<h%V ?٠"-B[S9uCcr"M8RBJEqwa_n4VfֿB 4Y>g_#3KU)IL jl%krv5a[;9yg&A?Tsj0 K"s Z† #rR6tDZIiG]8]Z`Ϗsw :>hdPSl*<q6m' D,Z#~{I[#IT$_1wX5An@ `BymU7w{^ů/Qc>|0L]gCG6ـCGnpĞ@ @9dTR,gY>}WnwbۙL:ZQ(!+6ǫm0F`I9 @Qxy&\MUOaDj#ȚL:f5.g<y *aEt?iq_Uӿ97G,Yb`TE,NIY`UjRRzZ+P(Df8c @p}}%.ꚟ4CrrLzti8X߄e^[zqONWAee9=woz7Gicg= Mis eZV8L{kdv=`0 ԟO;>Ar[@?G99@)x#Hxgs9($D9X Mh@ַbo\eL6IcDT]TR+Mq,AbF`9 G@#X Ԉ"2 ."@@`: ݣ3DAi@k3c ͬi9>:Yy|ѻQ*C~h.&+Ci7UMۦ|٨i@gi occ,MDKKKD4L,&aT|MXR){P WXkw`NGp7jZAUZb+-VtA^~QcPO^ӹs, asqsMִ{t|ٞfZQiѼi1hyr ;wjMlNQ.\Qs߯~EJsa_HԂnyZ7Eϫ 5QQRO8Ne8l#r709 'Ֆ|r[,h MM'&g0ΘPq4XÌk]d.T0c"fX$1Zgw>RZ2Odi6w6tӧA"Fy}p'UC))i#"~{+ xf  bs2gYX-js~mCՁT4摎fF*Xw"@3_9ya'0<6Z~(U[r^B5TNC;1K, .e8boqΤUH *̛?75n,>{w:2ƔJy<<ɲPF.:XwW - Y€,‚ $IgBB$yꊧ룹o M>CQa1Tߗx/ο?c,_*7Z;&y[? Mw|#aImSNj0׆-OTِd5ܒjAj6WpWs1&VqX\٣АHQ7n=#_\X84#D] ^r螚 0vMى=e8H̺7=*__`:<N],׸TiȘpD>Rckݜ{ڡ[њRt<1&eSɐ IIe!c*flx]8#l~X$I`i$NL1+,H5 @SF,'` *BwO2Xi|ST#p sokL/ַΥVucCqqn W?򍕍IdehgK8h<0FȍRDFUffs]>kw @uTTbV[ E GeO&G4Vnf̓ +R1>綵M)m.jS~M5.~LۃSNi$*ۿy">Kq_YoV˳i)垎-e?b7p﹋i+R9S^<[<ϋlѨn(RV7_P6vO~rkg˴0XFQ%[ujZj\}"vEJ8KtE'W2c㧞FG9'O}|Y/. 7;]5]rZ[wn9 sq!RACT2sa:!'3LD,Bk8YJ%[rᐲ;Ru3>} gPqa1.cĘa$G0VW,8Ƅ 1C7d6O0.HՑV,m. @ @ F dsP!}C%Т-܈hCc v*J$ҵ{?냧t>@6`48@(kP_VLKrCp酥 P3=>00nAL}̹x%]^;7z5o 굥`lsm~$5: pԊ1n8G=tl7got6֟y}F j2$Q;._[ͰѪ9I%GP ba 7&Bx*O9IZk;|{ʷ V: cLrctu8ۭ\ bi3  h4V+3.DpWrO S1ZkUh 82&,@p#IFjbzc:{^uw'&ᳮk^Wfiԡa/WW|nmQ{0x+kٕn"s鲊R;XףnC^(fX Ŧ=͘.wHY]<3e}7ۛ^my̏[{:q(~(YNc{B+ٱS)[iARSqQe,Ϲ~-e'@1-kڞeDyQE1ek ;EQi%ʸGXe?\Gy9[]߾X.Pt'kw]N=z2pgOMzrgxYr-ލ7å `3ϫW*M[ѐCf#J 9(X1qBT:t8w³Vk3H'.ecd2A#4#hwh ɍsKxRM@ !hCl69&Fh0":Q# =^M" !`!0T#  ʈ 7ȗ ˤ4Lo#$gW?f  ;L2@@*7@ &x+ݳfN hge+?}X Zo/ԟZ2Cae"saj3=s5 lrԅ[?/>E@ҊxL<:Cc+뎪d,{O."Cn3wrf)F%Z֋icҾ9nu-m !\PP`t _ܽQ^Zv]?:lH:L c%yI2βΔ֙&{nu1/[ϝ9pGGnT];xK<q<@dDdanSb pPB+IHʤ&xγ'ܝ |$Z~Mq~'V\=|c;﬿ 8rd+W6C-+8|lV_V>Nvww3GTh8nr$"v|arJHo|_㯩d+2PߢNF!#+,= GJ64F^]޷P{R_{+6ʂrPJAPkkO?o?9Tt=wJ)]-4se4S\ZJ}Ҳ/;\M/ZxiSzp2cR\28J~yyě YPK7* s^(\-S>?Z(:4(vzNYsi6Tta^ݿ /!iw.*op8݌Or?-(pK@*@\0&(IJ&G cBȉl1Z+1xdLnwYƸ\Lg}I6iv@sN/=:R$5fm׺+î jc,40l<\OK0!7 α#]on%JmmWs=<=h.8wsu:qAByTrOI)%\{i~mZ'?ni6''~޶x뭫dїQ NM?2 _p:ϥ cL F+'uw=;zҹ)`xRvqommi#tMAL9-++ݔRPη2즔ӽV OmLq#e͡_YcpдZە_y)T"%N۴UoaR*X9Zr7=W,=BWj[=ZO;T!1kGYH*+ cCp8K!<(D,K°8)_HS fLp.) Iܒ$$<}裏?b*C;=}'񕛡1̎fd:$ t\`Sf` Nǂ9Y-k=mΓᑷ 3XcU˥pL/&rDK+"ݠK`j'7,[lQ 7-Sc:M k) PX_X&3 _sx~% ,Զ熴- 7LgĐc9bFj3v8o*y%m@Yp nAN(5ikn9MbwT657;kWEZ͇Nw,bښs8l[LXJ77E3Ɂ;#اo ނ?)T7S3%kjksMzp1<9Qc8OZDG n3u%JW>@Ju>y~HdA%-lZmNztcolJ`6"ӟ?Uf{}kNQ~f_£I'^fTky.]t:BK.wuSO=Os_:puqA>y3\f"6Dk9`6[fAi( ߭5ͥ܏I6ʋ81B62HH5CMX 5Ph[! )zw{AD}w`ke1O@ UThx Qe/?,]Q3erfK9 2F,3&㎨:2w8IWbaLe+#ycZ;U{gZtd fEY9W_k\v;˻'ѪgkB2rMkN5hۙd^MעyȒrb.sOb`!Rxyf}T]V,E |*\U?j׉fRRꕰ鍒,02ǞYO_qZDR~C]8{?~axi+۰|u!?Xtd/p8 DZ[STfAwlR%Ub{`r@eRsEX[[[XXȲ@ɤuhZ;Oٳ KKK'&{cWG5dxeS0e-b*W4';V߾{UL92"K'֞81q2κH_+`jR9`?:[*f-4͐a_ A#rm1mcist]Ν h 2׭~3M' ь 8c  n$30`HCdNf̪jW/ZJ0Wi=[jZf+#C2ń:O?u|t,8ǒ'OccX߂7_ew?9gxXsI&Lc',̵պȱ~޿GY[o?{ŀYj]v5TXlƎB8If [0^h Keǎ^4@ !\>! sbb`Tq8Ѝ\3S I"w"J&#k3DQ*QeqCG/esݓ;jjNJiOaHGr Y%C/ ߊİ,2%Ov Ge,eŖCV'~!=L^=4ϸ?IǫHJ.megytMWWWwR_׮ˏ}~wK\\MQRDnr&?M0\ ت3Vwl[zgvU[t^_Y_pWlL}V`jWdëZ;̉jlGnۯ0MFh1 \ 'Aތ`0tF犿MxhiW\,yB .cGc 5-HCЅ 1 'ЏakPK 셁4 8`]offXc"I&b$unh~Y&91p2=sXGoYʬ1"8#9<˫Dԯ]MS$Kl40 ͚ew`pLb9"K!#mf2JNޥ;~r%=u1rE@\u%oB3:"+l f?16/͵Ŕ`c!#G v$;H C>z8qp9\ĉ 2Yg]ܫ7wXICd~3O~-a}YꯙYyz=ןt:~Nэ[lv^XX8v}',"bӵo7O8p8A垸hq@[!c""tF +cHYE`UwYu'feer[PA נ,T-R+XKQ8NP:D':K~alQW;qVduVح￴\]Z;7f;TpƣIjm!(T>hؽʅ[Pv %4Z BT0tqfi:~|C5ޕEaVBąDiȆ> Č;oe'vr "u]ɷ맟<;H£㳑 3 ^HvU=;h(r|&}wR5Qɸ֮GKWƳS;WsD!Od4A{yJH`{e!-["U]"+`~;\YZJMB2d0r01 I (mnrDVkUf;M>cVw`.}88Vo@ rS#h"hP횽WEѮ6ڌZhwGܛg1p!Xå`FIh0Oߚ1p3q4tO!Z'ZBD3:rƇ<G\piuCGdk4V,qfp5qwIƸ֌Ds!:}Vwv淎ZMq@ FĐqǐ):z>-3rwB9LvȆ"rne,,@@E;,#R6!Ɍ AUo㹫άk;L,pC"/yɩCca2֔uX^󟪃$ۗV-ʗAova25K"5/ OͼM êLUgY~etjcE/y_;f\`Rj w9 9qK׼L[>^E Fg筫%m7(ŰU_XC0ǣ|Gid$߲sGÏ-z6<MdePw#oX/RV Zk9hj@ntܕ &VzZPo nrpƍFUc~X7N0Dٸ<Ƅcc}`|{w۵Ͽ:mCV1Z69+sNUՁ\`m(Sѩ{ؾk?]YixQr]@>⪰Nkj6kg*[3'ҸN7eΝ$7^&"NMMɍɹ5R#5n%zwjZ [>/SiKt%0ZE'| IDATF w̍E}HkMsι/kɝѯ|虹иIq7B՛tn|! Ls\G輒"wUUp9tV1!Hڭ]2&=jx.眃 Qu5Hr2d5V#amwĹhL1Ggk=yNn@CW)6_o;-i̚<;TGKd#aM^D+mͲ0Mi:4憸V$ˈPRy%y}goNOwB wRh%-Y ǎ1D$ ĒĈ6`llAp$)7eoݵ}w}3H͌}V:U~BWa fm[μ5\VĬ5ЬmKGB.r`1/\5ȇ5_-^$ qU&y6g>o?Rpxla+P'; ^SqQء?SI(5V1!kR9ci4-cP' %tg1$y>wq.mO2¤cQкk 2\09Dn _quV_R#+!g Pc>2 /ۏl>k,f<ܹgݚjs.wFǓ<~sgʑGv|@:ƞϿWNjͬU"󹵺kMUZ9Y' !1"c1J$.{AަP0TXwJ5J”%츈H'#,eEXUYEXvw 6\7TVO7|{.5uQ^?Rj/mZ^&}iS0oLj8'a 0l"߱B 7!sȦ5ZW&#Qc \GG7aEF'߭ d^mZKk8 V3.^5q.,ދȞӏ7w-'ܐ4w,#thp`0BxKt00\j ` $2%t 1&!瞔afgn¯y`x֥RDue(9gð˹ rDD?mOT;Oֆh{o~wo6Q辨ݿoTa5{a Q.f*~4=ʟO'h;TYZF\+mhSTHj>粪kBj 9 X$\rD^siP#$yZÎ /_>y_'ouZe5>k+,:/w ýk?4лw=tϾ].DH+ZLw AMDAs0` 8 8 JK @"\a/zK!=hޙlʍ4+Uz wK<thO1Zrp~Ϟ$3IaWbC]_20VzEOٛy0)gg[T 6fy6J1Mu'xp1Kl0l?'3"TX!D`BĐs,sQ+ጇ_׿OQ.mϘ{nDG<}ۋOcuNQLF \D WP9 tt&=TτA''a2Fi]AKcyHh,|cO]뎴s[τIǔ`]s1&a|+<9J֓?[%!ꈙm:5kx_tpxjƹhV{fP< c)ƛ&g'W2߹I ( ~M)#iAׁahZzdw,Ȉ#;Ww4S)U0ƃ vƔ(+cUug9`J2+d= A`*Ey>2%^J+ךXWXw^MR&κb%ܦ1Ka_+ջ3?޿;O.>p~\eOk5IxH)՛k_].a5?i2N°P--}MC]׽NІ^"ƚolDcC.&#/=ˎ\jjC-2||M6nSr1TT1in K$P";,-w 9@F822T>GP9ŝ~;Y9?~z?n/bvI6~㋕HTL [8%쏚S5[߸m:ݺ%6ݖ!tZ攐7 8 -% B&nYڠ^yzw.[1!VZ%2/hEBxSgq  Xͭ +ߊ{".;>7h?H;W+f%;BŸs9;ǿZAXicMO~"YUSQ=i  -A>Umi {!Vk]8c Ŋˠ~_v[Z◜s8;l7cG_+?ftg\貶=uz2 ~M*%rjWfq.6 `R`9yOB)bc*D`C|v&;t86ȐڤkD# ѡآ7'k|T$nj()Vaf"x^V:D\1 EKb}/2~E20F<ĮoW7$/5?oP)'f&[V5oжhwz§~cgW>XR%ݭ"4%87hD+SEQc7nފ84,|9=>88t:ONt2Zک)=Xj6~₡y`^M;у [_W_[_˰ 9qtdRkmZ`;5ۉo‰!'}F9]0(e(;Gȅ*ErNKS @)AOLcuµ+[м?G特ڭsGoT^u&G?3dcXpIW˟`c7"$8"Z#%9rƒvx܁T3PT4dwXys/c!|HX{Y wK+ U&1G  Kj[5Najx6t(1SL2' "AzFVmoU 1">OY\˜6/`[xVw}}Nsa?mj7Cۋ庤驵̩dkc>}vL+ңS{pX@n4!Lw*ԢJQU0ʬpP :^Zw6mT^ڹtWr^Ilbq_=od1ye :{sH Ė߹u뫏A:wgި|t|g '߹JcV/<&ʍ󑭭Sv+ݽ-/~c]f_?_wt۬oegKKV;|SvGA!:S]s)oB 8lhy'ݚt,Z ` d$qQۣ7l X~ؑi*iEOwmKk Xg DD2ncdIH.)5!d|uMb7޸͍G?}?6B?Y+=v7bL:,J8T "H&{ke% 0E""Ma&[zlǙssL(vq{a|=kXnTɹ`L>d9zq|ҝAN'kzzqkeFɲl'A+MM.w!SٟĮ X fe;ܾ. g?X|z (<ULSa3onbT\;,9EwF9 .D@w|֟~{N֎ oh;뗟)C#xVhwg;or&AЮCjSHΒ5uD|A**4it,).s1:|sPaָ*,<*F\;]d\~M9?<ߘCW6n)GD)C@D@ gb Ū*l/-YQk6_1{eyÖ&Uz Dh^qx7κj)MJh֫iwN+Yf8ox[X>l/楔u`yz3ݜ)e.n4ԅ˲FY-:Aֻը)мݭDnb՗oE,oݺU{RF\I|.=Hp‰q}vV疑R^~Yz waOr:l0^zKscKxwWK0iiǃGmY+6rXwφ`81_r#26TKMBrVh-AR93\8^k]_omm3L1s"拘{[oyK6.v%>/X98ykGgJJ{ZFmmir3]O߭'Vk?~p]>v%M4TRK/ݦ5&sHǞ?+gU]Ǡ5 RF8e ~gaنB4Mac/k@h1[F<]SϣtgugFAXf H?;M1ݲitSU.'x81z;ak},|y^i/Yy~+ GY@XeٖxRzeD,g a&lz;gE~'KTa{5ܘG*Lź?I5H螊GTY7=?~L0krl{3Oc+ȍΫ#1Kn]SQgò cLkKb"DduAq)ggm]LCWٚd@^Ks,;aM=%fJ[&/M"^\ XOXn<~0 _} V`Dt,/soo&bKjQ)U﹘_id "5I}ݮEQܾ}q /~Bp1xZJv;_Y"\-s^ӻDKȘVxX͇ƅ^]V݉g\gu=Y 6*ٕpD`s.$+<. ?І <@\Q{BֺBtTUlMa9֊d}0d^>d*HGP4pS8t$9.oLsbѿ t9+iОtua, c,_<̄ȸJw'#4Fk"G92dh%8÷ c@HDTE1%rQ_g(^Fq7WB ev>c8爀 vLPi6enwϲh{-ǻӇ4/AiAZ5&3Dh*"qǒifZ}ƗUD$ x^qB&}<ܼuxqC2T# "cV31.ÿ)gW-Yqg3X/w4:PXʂX! x:3xdg[S}7>+<2H}hb' ߒ&P~ID QZ׉<ɪܒ#r:aȒ cĜ ^蕶{y~_l2?'>־˝N4uSWja]jmmm}%I: |M0Y={խ)xH| IDAT@p $@" Vk>Br@~wE&@pr H"" `AA6~[1aV,]Q}gބ<;޸%\0Ms͙Z(|۪G 8^C¨yЂu6ަSxi%s#3K(VUjcN1M4 ]Yf -!"ܫII19^&翰uYzs`C[0(@"=8 H~s&f뷷oԋRu1cBԫiW@klh(boWRU)=Ɨ.tڑz?{9NG(2ե4vfcR- `^ϤoV*"g s1>܉>1$ڍ*NgY^ĉ/uWtAx8ض>@bwѽ땱A/;y•˿ۭN]=)B@YFEnGލ{=B*[Y{|x<񆭣dsx{psx1T*x}ܓ34#"pȠ#-m*8"['7Jz+h^ucf -NbȖ8%ޱ ^n4"b氫'q.1(?#~u8>ɽ|bkRHjƷ$q.j3YLd2*{MH)MroYOYIAyfMB8kUzn\"$IGw,el_Ds`52F(ٸ%yD,[*ƴ2 q܁Ns$й4"SJm$GD%iLVs{uDKeZJ|t{lW TUJ\8 gocї,I9bй)c,y,>w&Ug36wA3_NZ/h~4n_ҕ]n*U4u]BWYijuc*D,9U:ƝU Od(<)E.k>{be$Բc.Y/3KYij,qUz.a }7צS?|[) 2H۳h/]f.:5^~j@L' Ĭl6spk\UE(j; V,J(9l*˲$Ij&}9WW=#/(֦a|>':IPM֝mG/֌otړ?|Wˬ65N[]Y5^Ž@-,b?'ڪؽԤ1>cɈnw"Hmk;ΑO9RW l2 Ǟ!2!dUiJV0+c9j ϧ"p, z6g9zU;6W̓no8X͘,fi>(3:zFj: F{q9[Xgcuǝdq!v`TƞGlСikNdH\βBT3(8yD=@ۋǷ\ǚ@fU+\»WSe93Mv'vxQl_\H-/~\DnoN,҅P"%T2*kօR1%!XVi#nIV=:ɜ!n] Gנ=L ).c(1~W^D`+X\a3i/|0"8®WD|SsD 4IztNv*?y+y72ͷ;Nў~uSyT |>U,֕R1;1gB*Yh}/U98W*$:RSbH@T;90).&s vB'[ߺZ7cx<ƬwΫVhaA@DuN*T~ zRJ"j XVEՅ, 'rם׺9쏷}3O`@mjL#6PzK0OZDî2+!"2tK~"{8YߏY]49Zl_FT7g!.bݧp(('?u>9{8@Ѷ Z+]YOvԙ$i.uvv'SW6f}DfQs% ymfOr=Z0aE\ ̂Ϙ/ 6el,JH8;CBÍ^'tc rJe9D(1~Y$pݩ\ YUqmL%_TYU[Q(c*kf<ά䪐 QG,d'1C,QtO{p͈\{)@A~ܽj=+y2<}=>x~^:ܝokٙE<9WY.Z5/~ء{x6mLձI: Ug~ _}e ߏ(gILQQ_m hWӳ3VQe97F9gΜϝC"+ %#Ɖ,ԅ4IZ;J.gv<\JU^,-mew4 Їb_^:޶q'oxv-V( u=s$m%6kQmhM?7m] jB_eY/;Xmhztu׼z&ydžTJEQ""!DM4g/ns.:ͮREexMDlZQmooA#EC +hc2kt<C882ZBxJc*"XH Dk(f\9D,SΥCTpέ사aC+TW̟Y|;OL*dØHDJ`^0oJ .[n ".G~,WځZyĝV uIɃ[o3PʷwşKyتؗ#֝ {ؿ@ʝiwOKs[* vQS5^cϡÜU>9=ۚnŌ11&.ǾndCC$t)2L*te~dvLw/ЦVUZW^YzVUYmi X`AZnl@dsEr3- bx%4~~c=6z;oxqϾ;hb,.uJejdJo)CѶ8I%żV)9NkNJi{>Z\k.~?9Lң(fY3zX?K+U9>8%[]oyIw,C$n;Gyg@سb}=hm]zN}kvKf~z\Qߘ9l+clxwg_qhO7mXhK(¨2.D+aug66΍w6RN7VU]/d#[k9Sr"˲q h[[ #"S*up% n[%Kk5 RRc!GdDec*eŹDB cVkMr™FӔ+E`Vr:KJd59S*{6sĬ!(t/yia%&BWs}]d12fIz E]lͫKmnNg9hIfoO?>|K.&"G,}Xlޢ24N ZYЯwn;.Y$,wu=[4RkU'kO>U]Wh:vg[٤wgлV J1ڰT@1%kDa3ȘjHsT61ertjf0 _[ y5o4qݟO%澌c/uRQ!:Pa.`h7ᣛw.h]VUґb}*LnvYyQL̒r Rs6gqf2Uw43㸇"`ƐDN9!  n&=HA>3҅qVFs>&n4uvcտ_ǟ+8 s&a؍.1^:]yQ[ʭƔu$a>C\H%`Z0"1bÑ[Y11`D)ۛƯXfTN08cG^,d pQ@ LX}ǜ%g\jd,︨ _ E(Q-%E{QS[|0A6e6{Rut ֲ4]0lW<0EQeY;wjun|YFMZv) ޵J^esY{:@Ӏ-5i6eV5k{`˲lc`];}oko@|`1jFQ&.@y\!`_u;( A\gp}Sڑi]xV߽zꓝ<9B> ڏLKI@*KVR1Zku"sǢ,Km0Vj5Mwhky9A:Oh0}yB&5O j{s|]w~Gޜsu!sh7(tDl}n y:Zfs߬J4 /^de٭Z;TWs,؛b/?yj>1 GcSY8@c(Fk[6^5QUTX;9{v;fM*% bCF ,1APg9SW ^ 9#,!'ZȊbJI턱AH'Flu5ʦ8ŲfVlY4,_Λ^_mnۥ8*wV^T8w=Jѫ>rx{  &)Fbz#0o2'(Y6'2(֭Ę2dZ;\L:@_jV/7lL(ƍF9--K\ IDATDŢ[ qQbL1#T`Tβ8E5Q48 x2v]׬V=CGУ*h0n/%s# YLv"b,8W9gy5 Æt}jT#(GǮO,k]HADlfL(.!mlQU?ɚU[:m:NZY4(I(!u24x}3` ,4СBD3 >oSDiy/݅ ;wN{e2Ɣ "m47,gikL!wIh%"2b C@+9r)99G`'Z'O+,qK|.Gl&5Aqc8H\PsKSTJkr>5{V`x@&H| ~ffu'8O~V&}yl!^]l6}y -0=#UN_z*nUIMO7 iƚZꤙiWL !z鬯zժ>/R|l]L~bT}u߿[npx'Y^M?XsuL*UY\g4kKTwmg:wjU颞+K(K c cKKKaH"Qf6;;ڵx=#gsRͿ:}0IJY*^Y/L$B7kޛV[&Vi_E&/&R*-IA`nCb'>KZpG ;g9ᣑ0Tf o<c4vjۯ0l2uQj%Gl\:0GVw=SkuNGZV 8+{7 !8#SeC4 Z1cx(v۰'^sΞ|Ujמ[}g4O6uXk, Z5F9?+rj)1T9#pɬ,B.%$Rʈi]UVPue²1߆ 㱣N鉃Hs>s#im>hR[k37oj}Vd̤,%4A@#]fL.-߿@DizZ|e{Ɓktv;e0l14 * ۅKj9Haak~ H:B6 &Ǧ<8P1bAOX h-a2刨)M˷>ѽtyDn6°5H1&˵:ABaزYJBf. &,q9d1"Rظ1nvn&ePp&'Izhܷ _g10b,@cC Fjb8~9\OLuu4ZkeYzU}dl6|^j/${pWw`N}|ID>::K[cdy uo'I%766VVV7aopܶZhMH i--=ڼx4lQr!$'<~ z|Yl-WO{KqY}l6DDAت,ǜ֖1 g*p1 SJYgsY6KƄ֕R%"\y)4[]|˾n(ۙDlbxXk%ާ>u3yu}ř354/_ȫSxeWѨ7-ZPl~;o=3yg~毽-io G"pߘ +33Ԭ\0+IE, ՖU$钴"Qƌ1"wc#5J v3Y6\OL/=ƉoY~1N;h=nlGyڞ_Y㕏q+qWSYTg K̀q" ^<^Aǃ4.Z8nQPJu%)1E1i4z/̤b`#eH(?ɫ>[o;PS3 ZNR֥1u2hah Ct T1jwEUeL3!|'3G>89 @%/TY.@!BC)cI;w!,yΙe!زZLGl(Ƽȃ2qRIf&g c\3Uҳtd.+mK2 Ј`R-|z֎Y_m#JU|ՄN/3Bá-:g)/"땠yw|bvy' rJƔQL=%(DZZB** x`)2H@Z VWV=u}>s& DM-sƆ<!9mk>{ZfSuDKvkẃ̩?.j=g43l֤ЂWn|>Zװ:'2 y0Wq>?>fd"ˏ> "I_jh4wּf9ポ0co(Nz=^^^Rn|xb nu^vS5]Bթ|~|Au~j?~]ѳ_!y|-￴ol"(ך,i2=gCsjn/j-NY hAq]9;U&UP!sNfz*r2/_=We0͌N[[a*SgqPqs m^J,Ĉ.:}^͋))0∬.ȣ7¾yDAHwU sx^8z}St#j!l)L^O p$">hTdik@U^mEP,KW0¿pjfy>/ ±eTm NWVts|ΟsmmaV7~᫇nm:̱n*"z_R0 =\篽7gſ7I$s}RcZ ȋD_wÁ)NP~4y"_@Yl ;Rċ6"cww>k?lm}8:=̍¼ Bzaq;~C,GRDZƴ|a،1UQL*-g`@HFMDg}sĈPW:S1&sNjB:XH" [4mR^e cUq*Ch ަ~p_$MlcUNO^[G ޛlz/u8|Cju ⸍ȃafL4K+' ) `{q5lsqKRKm /m}2t6mA^ &*u#j G 0U!r LDVdBL6 4jg˲|!?}C>glP(@З={Ƅ1Ґp@ܠsZ-[ىFb!C vxK:fӇS&#+!RDT& , BX ec7SNg%z4ٔ t|k qx0?lݪA$]wܛ#H-"Gh$y:zoʓϿ̈́rTa> k[[9U**uPYfA"tP!3L#:"rm׉IPkIeb4f+Yi{7r9199˧>xk??/ƍwyώHwZ;tp,8dߗizwo?zL-n_h|çٛݐFYBِ2F!$(Djs\m͸# U䂇>o0|8@nc-gTig,9BigՁtf퇦%DV*"w0CϾEhW=+w/^]z6{"h'W[ˇ>󫿬Ujvy5K<:]|tmrd7;=^h!x>_Yפ?|[QKXbL@:3dFkGH;T۴+ۍFubugNF:?~补=֟o_|wyWG6t= x7 C;6m pH]:G QMFΓ^s]f kQ)U =.u;(YL`0|m1z@k굆0.8dOZݝj b-|mI׍F+םw) i@0GEм7ʞx{߇@BDY6dL8j=;4 Hr8RB٪HZ3k,*dŹ2!,b HTU3km%"k "sNGš<DOy_:I\*wvl\=#F/?pJ &re9S*%+I0ewm=HNfQqWǣǞ|u S" S Syɵ"eEifNp%Pk2raVc3prc@&e0;T͊UUl6g>0j?}۷gTBQLgLZ9Ƹ@.d1&,W;'nC`ؘkѴ}ct7|$e$K!Άd͸jaT W) ͷy\bYFnއ׺мLs~?<,(/_Y |oɡR l`8p?E> fu_0y9,%R7f^l3DQq;W?:Ҥx-4_NWܭ X{|ܾfC5*z]<=x̷9 úLZ5/>>`I6quH-?MmZt{rp_7!aJeRR\T3L5@0vCV Y7VSΘ"Q q΀4N^ey>b"Lnw)Uj!b"'DTn-j] H(%&"I[%.?3.`{rҾIȕqep/^ $(^`=vBؠĬR&3?]Γ}9pZ.Iؔ-8|~zϟ|脍hiؼ2^f JI^ ԖY$Sac蜵V;gE1A۳u۝:$&ձИT>k,3$i;_DW])՚1Z{{{EjwGWux|LB"mACs+5ijMw_ߪ޼HwYy3ԁ).d)x5°֞O]Ww,Zi'kVϷS}5 (2/?4 4d/?~hGfկmkj?eo$ _] jpFeyku@OFńOvBBSmxA&XU°#r h(Uƨ=fLĚLCFș \-"eHQ) ҐCKirTYdJmkSP1ADƨVke2aLt+EPQTsЙ5Zgo[sVT6&"FQ#[Wܿݣ[&vi{?u[q"@<A\? .]Y'[Vҝl z BI #F,M8[S富G'dLvrl1)@i`9e,,:B" LK:kk%̲~' IDATl6 ?ܑH쏆9%7 "" ^FP0Ƞ>zƍ+~@8;飗k^_ʲĈ ta{;.seTvšd(X?MGinDlYC#a޿^AĥDkx]>?\SkJL$qSȾE6IщzvdqNNE/܉uQԘk UӉlTЭFak0<" <\2&oTP,C& ȴ䑲seJa gy)v3+2KO?׷JT5x<4sI67wv[-/u¿p6_|oo'~m̉C׮:f|XdK7[ɞ|^󐈴.zҧ?c4% cnjqDEda* 9#e+aZs˲Lð)Dhm!e9͘\X+*au)eTKIҋm^z6*ZN=QY66X$!dUeL%ex?ST 1,KUsjV͉ǨcGk׺CN\ Fk \~¬Y>`lŠn}Gɧgy^-Eʝ3y0VY `\:gɢy%T2…;G^L>tXLG??`f&CPqrXAs^A"R9°ҫR .21=w.3;Kk{_J?'nw3ӡ.g{%(#|0.I2q#s(6B&;AkSIuygah5kmluHC+Ϟ[8]y MUM(۝Ά޹,[LJ~*D1!DV&zguDZ3]b+ J(˙RֹsFťFg3`CͰFaח#k*`TSM-p~A4_VUgAW"(R×O?JcʝNdJTs)o P,/'NX^i3΃h{<pݬ5aSc$= ܤPS(y%c?U?_j5tvG2^;V,I5s :Q]x#{'fvtNf12T 2Qe Q>o22 :Aˇ}5v| Z/ _/܁05Û.(7r-ooO\?*,5rKy0|}Jݾtj߹9q:hO)W<86Ԟ ̈́>7GS?׮{?s5@ PmwGτNTwǯ//l\EwF*jj%uȁd2hTzW0̾v75-QWz7o`穗۾] ~kU4Mt|@g?Xv摢!9 QTu'] MY#@w3qj @aֺb 3B$l6\SD k+c*Ƥ&H\bF#؉"ɿs79dTݪX1EW4e.\= r?.%Βaqꝸ*̊,uΆכa}WT6 3۞3/:޷J7;ӳoн;?ЀeVhdUeD(+E`,W2ZPĴ8hH[߷Kb %6H.(k u0P#0GnJȄn囉qWYCǭhCK ^&k%d F¸q챷 }i*r#O/[L a2+Abq MӐx fdl HpOfQ떕K+t3mjYLmm" gh=^j7i:"灵RDUY[9FUrBdUGBH(߿G>}o7XZcJ@BBӁqiHpHK^3b={䕵fe`S33w=v v~ʯU56i ֺW:|wm ȏȯگ-//-j#,g>H馽S{?h F O}z:#:<>{VUw0&1Y"uQUSS@T\x@V+!"I ˧fҴK_~(9E(X&A:]+ r.o *[iC:ٙiwf! "rZ*1힪C}y]PmgB M;ϴ\~4av*T9 KgVڂCm4:@E$`8-xrY|!VJؙ"ń!H!WVY6z-U6e.qp)8X~!F*pLf}8'mɏwg&poVr^{[Z(:k5R==uI5''o&OisPilDyGnEQM.$/[/jܧmi9']Hz@wC fֹ,4R?{??/77|y{˗dQӮA^8[a S;܅%?4_pcu|hި9 3>}YGыx魵ێ KcOY!NS;y9|R677r@Zq\(U̲Al Xmk]օZEĝ>~1 "rNKG:(cĹ8!sDX<\FQsiXUPaHuueEf|ly !*X"Wll3G&.-y&*/] T#n6E;M9WV-iUqqEm;}=sHPS;R:ƬYUVHZ1.DZ)Y8gM%k㘫$CJj=4""rbL `ðeET茕 dji5_oWz~):86&/cZ>AO~bGoIeC{]?Ծ;aX{Kk5Ykc y~-75c+h5v$ YC{O+ztY~^PF<C 3 ȃ9`Q3__Z?uߙo/>u(PnxF(|Wܼv0(a밿7j^C[*?f. } 7zCFJt:eQvj4ȗY;r8]Ď7}@,T b uA@9Cd9KdDND[kQ2D@"kxu6 &"3.t09C=—vQԌ12YF=Z+UUvο ֹqYNoId.N) 5)>Yޖ H°fsfU~qƕ:fbo.œ]X]=5_׺*i*eLR6QqKFb xQi&0f&xjwd'plji갏*6E&]V#d Ƃ;"hw?{u #{e+_t7L$f?k F%|41cRi[ڱ) :͐7Y Z(bdA_cpqծ8c=-7  E@)"iDIBidI3Ljjf<5SvkTdqI,K$$(FhM4B7:w~{㠏.4#Ab]߻}>uo}#´)JI aî.Q_R ÐFXcI cֳQ-_Y̕ f8w"3D* V 0Z^1aRyIk4F:HReXm|M7'np_4UQ4;ﺁd|S{F/"_?~ <';~n~&sO<3,6o{HѾĉ=^cc ֒#9 ,ҁr#Kq)vQy^#$1Zmz94ŨfZdxX {eRQڣ­UYB&z{kn߹8>;:鴧, ʢA{<0]dǶSB;@mr۝y>.,/;jK ym❯[gz~1LȦj8Ξ}!Wh4H 1:kw0#ʌLRm ??)., AuD;ǎc3L`]86$nZy{esVpwͥqU2#R!@\ˡن XW8kARwS2E/Aڲ"0;N1sC=ut?sˣ߫]Q9 Z2 .5 K=pC F11vR Uk <\i6y%lI'eJ\`!C0W im18gQ!GWRȁdAPZBQ 1c5F*m(7dSJؿ 2Z9dqӟ(TV@NU4M9SS+_v,?"7VЗ'w:\W謼zV<~1;VC*Ϭ\י%+8+,˲,_y (~гp\R>RjjZAYww~_lǿ+G6~9=̲BaQn4^je&IiYBe< !aTV"!pjUx7+^q?8Q`vOJT[]y^V+ (՞TUUMr]N}uH ^͍UYz`9Y;JZʥU7ksS!^XX((*_Jo]%JrVΔ͝^Ig!DXk@[a-R($$`@Ur8l(Eϟxp[]sEmY.CXThDuZYhYގO 3Lyӕimy)|d= ud`xl/\tLհqjezSMklokԽ j^ `&;#vc=gM!_n;RSY8^9m5Mc*8)<@I& jWQÜ2  RMX&FuV;U:+Ի-H6J/of+Lr1ZC<:'z^,e"`L) npFK.?~j3 IDATQ=Cy{wclFF|tc̓2CPJQ#P #3pXkҴos`(Z˲E 8,e!541DܶFXXac`)5)SRcJkbL]8Μ]Qs|ਜ਼`Ǟ퉇5\9}"=j!DpMFKYj^a(@Zx3M1E}\,ԫחsi)J`Xʅ@+ 9BDuLeJpQ<$qP˭},)9 )3" vxzkdszJM,tk688\`,4A P \^$:ֵ8 Έ]u;.o可>h!{kllڧ bs=&ջ^Jn)0 -RE|wŹfxHŰysй{1E۶nC^x_>n?5MW pee^Wl5٫?c?X^8>.?9}]ӓ5w}4Ťgj9 |e)gVxDkgEbdD (;(GRf0J|BƔqrh^K$"$oծt 0fBLFt:Lב@!E g$>_ɄzחX͆53aܠ578 X]W\2QjZrvM!v{ Big8V^P3;8 BFMG=XƷx[r¼)WFU=-F@ [x$:dۮE0 ` p9rVY vyp@@' bAh,%skX;~S=nn[7et:; ZޫT[K ,@pc&!^-$^' HШo|g?)u{{!Xk{/zto&;JI8*5^bjse4hς!"_f1MQKqi\Mc6Œՠ4Xkȟ}z?͍7~Ui6=TU LNL"L e!B(DVp`NU ۪$rV{]r !/[q҅lv:Et:% ;1e3:;]Uٓ9Q_[~ΖRVb;7S0:)l:;;U92VY{5nr5w'Bڇ>gowK|Gxae9{<ށl>[}j;ټQl-I8컆gS3NZm~(hT)+sVU掌L6Jo*;gꮟ͉ot#K`@JX>B2_nx-isWVɅ~BtnM`D{o^S=z77੯=?x_[_yGݎg ̾c<{;O#L=/. B8@!"5c8\:`<55>el]3F9=HJy)|rO(r9c^aSSQddi!嚂w*<<||'RofǺ{pv9tG`t /Zj)Ĺ=j]6!!fPB()A 1U3F(#KJIqkFcahfv?:{4Tv'K _|;^>Bpc S*д_rS쌃r0S:{+n=L{GFKm'ƈ\%-;jԟ./eԷ5 %1::dakfe,aY,l,ի'n=|E]'gƖIT`\_tX>E¹oβ(y>Q"۠Q*1. 6GZ0j^ M1\N=oN&ۄ0Y j,Qٙ8Wӧ;q=7G|uֳ,1ZΊm+ s^ap ?FϜm| rQ,9'4Tl_9Bf_s}'y^1ont%땕࠲.u0;gZ_<9঎Mo#Vqvf}N,YWli17Y̬°˟T+Uiagȹqv߶G d/GTC>U慅 ?h.kiAnwcEbxsz~}c_&|jkrrn)K33'y/jଯo !>F#TwT(!Ax1AZdaldtF!L#3*ΔYXd,V b 0^h]j0l8ή>fd4m_cʿys9m%VY9yxeA$\ZeBJ9d;op9{M)LV`E[;:ԤlS:5̰qŁN:2@ Xu{l;勧|h8yz"!xl p~өnod?3`IwJ;Oj%HK/Lr7& lmG?pmg\Mwc/Sᾣ`³krk5 $1"ɲAQ$pәѸkN6Y,!jRd7RBBt9ZbB i1ictt]7K|hg6RJAj 㪕pٞ+eTG2GsUY2U8s`f^r*٪KW*B$I(*Fh4<2-mʽ9˲2NӴJE7_zᅑKchzTJ)Pv{SF\fG׺NW>H«E ϯpjN4'l/Y,*};6oѯOt%zhLӲ毺1*t:bgj9f+W_މ|?ZNxVld2FUcByஹRϯ`:K!o=8̖$ɲY saXH!,K-P t+DTN,X1!4e^F&Z-”*!Q[bo Aw9XN;|fBu9cWDѴGFcq:s%rmefкp)Ҭh0I$,qsۥPKցxWky.2by>9ܷ]u㤴78o>u-hR?xp9[#??~'{coL8(i ߯}b'W1_yRSH (Y,䩉P0FpiuzKʄ!L$bH]Z3@lN #C0$EdkM0):8mG1ZwSضi\^ay\!n ov &]o_]]KT)Qx9CGE^Eғb"4jX1ŘfBB9u)A\ p!$L8p8D_;q;)s6$w=A\F}s$D`p4h-sAX\=Y#aF>D{ڶ}y|,E )'~xQ0$ ăs>KmS/w++oZx GL5=wf94_Bh'9v* .ɓWg(gZ[wF)yVL]ve枌MC3 D6͌B~V/fQ+tTy˟'O|g <\L (;QH~grhM 46F r>XEL d7rzs9Sy>r%Jd59qB|Idx9g6^1J}O(m>roB{'I_Vm?f Mc @=NZ fFfvOM( 1]H2 ðGQaeJ:I/>}``Gl6#w!\3Իj&JU#d(J|S_Py:***웻Z[;ԕ̘"l su*-K^z\5;Vܭ7oE?׼z+E]-g*kt:@ku ~EvŭWVI~Kٵ;^ߐ2ǘ8Fsχ~kE1ENmZZc*ˋa ņ aGB x]z>8unU΃1:AAs̖/UZt7BC/2:"=$!-_߽4>0>tF1AQgu'S6S.MFD+x1 8S`_G $7\5lۍxZb,CMHȢ\#3C]4]MQgK`m֖k^8[;}s?kU062J) 8e"@\#MPځ:8 [WR,K k{^\)B$-i1U*22d!]"š6\NSw]xǗ֞Ѡ&^.E~,/b>~hFlo8h|Vӱ&)A1 1v 26<@}?KdFzFE<7o2m;Jy<&uz4w݋%y~Ӝp,x[_, nf#u#m 01L'h@AlLV<4-91f,VQ8JRRCO21c=Qϗvi2lЋ0M})=b ?!8 V/ƽp|2>oH4UFva2(3 Z 9BV"Qzoxs9b36z~cxdǒ$)1'p8`P:cWR)ʞE1E!DљlQLNkޔ虣MnMfX4RBy+̊666Y.px*ٳwll,kV>=wJrKP+Yp\lLvYN$Iyjʴ:ҩ(<:5r%ѼZbde@i[~~VyXj-˷Tf]$V^x]O$_25 -"ilL$<O81grhzBw)ŽX<CB8B8 ^q`08BQREb l-yضe BL $婋C7ɡ֤97}O[I朥ԷVY#<EҞɯŴΞ3Y xd(?;enZ|w}=:DCSywN6_OA]0//I8) b8cI Yjmd6vC'P#D3h\Z]"TyZ<;\X -sQ$O>ޔ]f:W-rr2!cL;NYxDr#zjZ!,tʹs[_svg.hk&+FJi3ٗvڀ+DUߎ25v2*c7Eʩ!;=!D5f7#-nؔ'r*Y)j2Ī"jUYeHs xe>ZO|z ~eאmOK΋pȩw6Alac\-'ƛ_Om66;w slTvBKquu}?[ 70ۏO|k^\?־f_\-hyLR6NB4: UߍU] 7z1iM}Y%y`(j|4Zg@)H[eVbv€%Z`L1Zk..h23IxiЩJ5x{)c{/ȴ r0D:UcsV tnlB*`5ym7;naZ8/A1Z#nW!40Ja7B{v^䰝<(&WG ՄXϫq^X@o-sn[d0E&ւD9^zܦSnC#>@J !r.dco't, b1(RBbx}k,8$&4tJa^\{Hj:NH \jܶENz&ƨ0liXaBq66nnA~ߩԙ޽jػ/|Fƙu&dyBrf=y. z 5AP7Ft$ H]0hEMRL P#T-'pb%h1"Ayo}igF#w_ziSӯ~k_kCs&o|[!BRܷ:7v|sh,9*][b3? :V_2#jnܯ^ߜ >'n93opnߔ0FZ |,'v)s v_t{k=350(U Ԃ0AVcLKB΁saSS'݁7;Q_,m4<ֻE/%E9쉰^ pΦHʴ,%a/F!襀6A> . wQS-K;zjul# n@mz\9Y ~QMĹd0Q;^zcփh%DcW[ټcIM(m5KF)Ta{〜7}5f Ner8; Ҹ=| t MPn܆'@c0B\zιF:(8 5$aBI'(2Lпrn3u},0BSY$8hXP y6`I6:lox}9`?<[_DVuuhS8%4B)-shTk1Vz5̦ۍ@sn{YVJ_&ڭZT0`j%98NyS{%8r+>'SA>f{,,OTxfSesTuU*r1{ٜ5O lu򧗃ɥ_P؉nG=$~Wf=f*Q-1{«UE]_ϔ_4{cujKq8TQay%,//7#Bh7zu!397ԕ3~U܈JhB F(t- E܏tI<8r?OYJJ}k RBRHӁ1j:Z+J/ hRmBjx}S>s N/7UZᕕQԒ:#L+o<~"vlo%.4hٌ'nIDb7{Z\Owz7=%:pL`;VC[[UaB444Q|O}B̤aukmqAlB,-l6FLCS'7tnF )1AERD#=Hx^Z~5xy%/{#kF,SCJ3"E @d"uν'VCs Pz?[n]߷V7enD9aRz]K/X)"fƌ,m`]@nblQd2PF&ƀ ɣTд[mek cRjOD)ef7(Z\fp0d%raM'Ɉ1&_朰VeyaL5&<b=%k Z@L0.16AZ'iolceoV>Ti,j3k6%MaW(NpݩK1*o㡇#z_]RQAFcQ oY! 2JAVtȿjyV,ɋptۜNj"2_9AuMuҽQy~s_ujγWV"_,گ8|tsP]5Cu 3ًZ_EZj#;;j**]"CQ_v(+U܇s__l+? 7^97KX%@s"bZ__/)gmGsc=InڛkD+g]D1ƹP*!Iv6~#'}!i0!tMO Scro2tcY\D#|糣Znڙ>D&0E0(vROJ@5Ddf}>t +v@ט09}4A 嘖$D@@ī|*#ӣ&ڃa*slqz*Ф"it✑2(=W!<P'(sXk0@];'.IH`bF ~TN6c\L2^۹ R+ .I5BFڇ}@.u3pd s7G ($34"k m޾+ hnw_pn\| B4QJ4`QL'-  'Q|?rZ=_`DN0hKDE/1dI/ t\.2`6A HKG\J6Jedjnj`ē_n_YbOG<ҥ}-FpGN9 P=m߸/f#S^O`Hki4_n;'.߸㱇x̘D%Mw1x\&mWf4`8="߂!\a*J3,N,ϴ/)ÝQɃ-t<*eXG% LUH|EE!cD|f>w=7jw}tk:H:F̈́qD\84Ry..v{)[﬿7fG0L~8C6Y a#Ydy1ba `IMVw2ƹ$A)֐.@dQas1;]&uNDK{'ho 1SG)lz wJcRYQZɶ1E &ʈJ 7;UL=9F(ӂ2{, 0N+8R{m]`>g֚++δg&Sw @ !-//WڗJ+SBe%S!C'f%GG_ﯖ~fwcâ(s_ˎ$>wf?SD8zz*M53T @R9_`q J >EkhMWDdsZp5͊/Z|g6ΡjkA}ܫS<&Q?ٳkujyJq0&)RZ9YU5*԰[k{H]+M{Zepk"՜gEGD$7V#2 hb4;>Nv1!e(%!2)3ZZ10\hA6#A 9Dԓ.i45~ywvoyѵ<"Jcԕ+_[^^W~1 c9KZbYw)p_2õ&D$̛4is^剈1^'$r)(5 㶙,r>e39J%BM,4`:hqݎ vS 뎠Efhk-K>4]d#m#~&F0<RBJ 3͹(#(JʸS udNꏿ%\`L1r2Ƶ\°KdE8GJ-9A1@ nb`|ȁ' u/ 3YoυrLZAa9xVp hpv}?"r{{{ࡘ:;k֚N:߈a|4zcr<"NRR1e1sbȵپsw -L{Rx~,;`ܺu}νj]d٘YƉJx~پo&g*OO_vwC}RG{}mA0LfA?lY3Kjt?|h@-=7~sO־Wio ^;W+ky>/`~|sREYgȒ8xyB62hZQZJ~Y= C"j6r1WK@/_wZK,4r[z(G{r GŮpg/vMV]U8g(x^9~e[B{ 4XP"rBanjOd*l/ra؎1E (f{Z cBE{ pCr;[4˶^y;}ɓ IDATR:-$+-:a.,:g1F1&\cO4q.eR`4#o7bj%G#Z6j^f4]hKA|. ¹^@6129gtR-F8e"2"΅;K<Ϸ(Elup@ lzs2l.%ko+ۋZ Q7d(&Sw%ޒnκN^fXF&J\ 9y*\8zsZ]XVū UDWRy}^3VjЫ*RJYgF\-sju'dq9b5uy.f nN|+κL^ruDfuܱ\ {챯\vGx?b4yAs!" t(e32KFϤp8y,FĀ1y @dAh]zH#NNSwP,oVf{JrܛYM+B Z?Xxnh&7;T{%D˃ agoi,i܊:W?Ftvsogo;Rʒ 3J:˲U#jp~2g]lk]uV@>ug?TzW+unxXF<ʜjQǹ. WiըjYo~)V@}~heTowL[[[κª[EQG-+d"bfWǬ8@)UXqևM{,Oc" Ls]&/p%veMK9>"+)c2I "+$cHle?2jo87}zlȋ-Gm@ۄ_y 2lz*iSHǣ"K[Jrte6Q C.9tN:+'L]^}2Bb=3d E\E]\Kx/^ il*J.4!t""!Ƭgs< Lxu˲Z%htd@$O+ `E"dE:o`Je!v d~a23q[6Y+2hDJO_<}4Y 0͝su퉗׭-°Xg8 :r){Η[j bL8nrxpv=x@Om'B/I+&)>e]Q1y8]P#9G&/QSg7'#" 531r0`ĆdL1ȘiIszM2& }n^eL Y"pwz󯣽}ᝯ ]:\x*r+*b)|rWI\~׫pq;=/|_x>«wݷZ]&uή**RVaaVét?>)e90U׿`cf#GѤ5ToMvkio~ ~('Wh4)?eR)21}7!B*3Ձ8!Ƣ!AT';黣D8?r)^?^4 &и{gM l13D.zَ+H p8G䌕gL$hS$U._scˤ5jzfGD`ycU&sK_ׄ}?/>Oz[g/ӛřwu?ïOp/}_vnM֖LѢ19\ ᕛe\Y"*2B(Zl,v o6:{}sY 8;Τc_1hPw+N/+\J7qÃӸyF?~O' ފ%Ee⋈{[6tۭ㭅wf+7i  w+778,Q]WgjweHXcճw#sʕJ^}ppP{TYKV%1766uFYm/oEWu5z =޴Ȟ|wza1 %`Lx^D D(kx`QԵ a-jBI/owsH\^?֜,qZ>{m*Ƅ1*IƁmdRֆJek 8pD$M , ɘ(ye4gQ˗o姕JIYECQ^zlarltd]O19UNA[S9% `1kN-Kv;hs( Zpu08D/#y3 hj?W.'VD|XX~v7FeB$\H1@9`\zaZ65JݿuDp0ϭe綎=J Q/j1:Һ4ߦMXVS*W#"*ҭHf;v93[[[s:LݽO|yQ9zo?rhW.|9 ^wL5oww*G\Wu}G.ʥOt9og/u%Q(ehLӎE.Y6M(LnMky) :_v"'i:i)s3=ߪnr}#w|!|}`u'{ɷ=o ;r_/7~ه΢[h{wf暛W{߇sO553EN`w,Py'+?9r̀x;gf]:Ɗz G萂'Vua{ym"HҬxUeDJ9Kdt:G5Z\0WR VވЙS]#p֧{>'Au+g.gar̃f1\܋nM65z^ȴ.ለ2Ƹ+6o<;KQ:mHvD6z{2hza/ ;-F`Q"7ٱb?xK %?]ÞƜ]ܣeGShq8,@P)\dn@i΀#9Cvn*_>OwwɎ5(,=/ӟInqYg2|Fl,uM`^OQZh61 ٸpNY!4E~fvv#REs۹pt,ma]Y2\Ak9lt u7C}^ښɯՉ=;9leQݕRneRFqeAA)p0?>77w??wkA)uTtt#w#K\W`TwuA#>wYs Xc?[٪_5s.u:Dh4*FYK7B謸s&< <7:<7ozM[Qxnk[7{Ykhq\|X_u`x=Q[U ̲l+yuU---:u ZV)B 䕕r9ZpGWK >?~n`?'y8OӡRsZHڜ99!|k":%&Xmu'fǥ4&. sm5DPYZHϖt`Wm=g嬻O7܂rIK/Fzܱi1q嘓Ec:Xw򥫧6OO[&l; IDAT{^#U,NӉ1 !SL;؄GE` NX tPo(lEt"ofDZU#ƤQ| D  cQPnw-g5)$V"&W,LMR̰r -Z8(pl rcݾ%iײ}QaIFc 2Hk;a6!瞃 O8P_@#!L-zIs5Y7qsx vN<}"j J)EMUK"e,L&lح߸uI Fqku-SO6,hȤhZ[=>n.' /TTIٍ,s[ha`0vZ#b+2~_E2c-[8+^w 2dp.NȮ\z% ioTIYݍT)A븪=S*粬MkaG>P}F*Lr^P?}<:cQ)UyA$I+V*ˌ}9C:%rKkR5 8|*ˣ1#NeYFQGBo}c [lg{/*4*QD3AᏛY)ko$KlPҕ3nV@2׋|lzVWo d&#c RTRs jPg<@'qoPzh.yVlb츷p9?H!|d )PEZ[0&(R:E!:Lh:+G "iWd 7URFn.)m6wO]`En^:FZ61,!6;]g'|~OrL t#rLPakܙqd[{KQޓ͘ },&M<5zpೆMTv}0#&+h tTZ@"(1ιV[[Ι<*L/xͯ7`'Q3rtZ YI'{ީkp:QsZÉ>Tɪ]Nmllb$e+UR jZsTըxx\< ??wU㪃pZqur2_dqnUĺwzxXǺ3Tgx۷~ߡR욎6ijh $I5)j9ֳQ)Y^^.2ιJT`DowDc ?|Pwxc@rQׁ9莈>xt3-p*Ju~X%? J~<c,gjZ?uTl_^ɲ䙽%-Pw:O _J0Aa1l, h[j- 3D71N5B\ N\N/x[eq{?dSL<>Ky ; ZljLADIr`LQ3!BIö\oҐ9b>J{7RT&YQn/Iq^P RRNr璣Gn&~5aӥr˘alQ'Ε٭Ck YT+<4#IH`EJ!3vѤo{*Ōv5 RlK[갳2< VQL҆=EMYXr%@uﴌ[#2@ݍ3lk9<~&UcYcjKU%*lgc-9;s߷[lb(b,ɖ-xlOxl$3,d1c` 'P&lɲ#YIE"ޥnrGun[zQ]U﫪SUwcf9*ԩS[)rpUuUhϗ7* ^ϒU\;%q#;{g~OްUZ3bk=tπenoT6|Z1(D*Y__gϖoaIVo1zʀi$WWQB;"f/~~2z"-x\{1V<&cʖB1"+;84ua!DDC_օ﷭θҌH 0T/*3(,o|rͬYG%2#a/'zĹVw i:G!e\PZSk5c\Yrc<*C#>7;5WG YB/IFGGβ c/E=im-\l ˸rum kR)q0^QRzƨH⸜sLLc. ysweetP8S sa·ůn{i]ͫj5݈u9P^IO)7\q#ZBx2}=};Ԗ 29 d XGO{f!N_b g]Iӱn&w7hx0e3VjJ2&,_b̆1JOhovUÎ՚0UTii[___o}[q߼[TiF?lޔ}:V~_D,1D !JtFUL-c,0oCc,UCkjM?Ŷ~|uoiյHḧeRiZeAGD,缼eR>{R29Vp*fYz@eS=~*_5{'6W.|xܐ^ahoDz t}!!|mӆ-!X5Y66F]9" 4YQ&W#7iՍQwg@8,#ܽx^ci}}[d,3'IIE8CG&r7r6=?zqǺ aQfU^Q$ "S72xʤ XWOäΒq*AA4t$<<:V.J "Ƅ9$Cw|>&hr}_3~4 㸜 I65L1%H[2ynAcĸZ:[ ?~{q:7b(vZ=O\_Icޕa}H- LSAH@֙3Imp%=7lj'䏕iV.CX=l|Z4Y|"}7BΝ4GX ^.I~ﺜp{]/$g^:p@j2B>{y]gD~#8g_@ܜ#oiN_ BO_ywAa:kN{!GH؉S_p#+@̉ nBi" -y$nm)4yͷXYTYp-\%WnSh^K27qfĎd{h=p0NDyyd*j+&n^$Vk i~27gSvGl\; F"2r-+XXͫ$k ̬UeR [׷O=1_ܿ3^Y*cdӘLօ6-[P D:̳^|rNЦ,cCŹ?s'i!Qh, @Ƅqt48EdY$vM?$mӌmaG8/ɏir6Ϸ[φcM!AYn,3<Kdѐ6V0F^:^ѶKwaFssCh0Ag^peEkjRK07YڹG~7[ ǯ4<8=-3EU~`p]b qj:" n_i(ʲ#[[[eMf)#Byޙ3KWvv;;䋫kk n6]t~Ay!DEMP%>77WIBp}r;DV*3-J^<\ʅ:QT;^MUH7]RS@RI0~X]ATE*q(3Gkua^mnt!Rtj ז1DQD12!Ơ(*miO;/.fZP [ڹ'dw< -6yLB.eA@NH b)*}y/~9vN3CAE.Ф ٳ A 7Z:l<]I-)$l2!`<!(:T*\#y*<w/F@XN W6cP*B߶Co/n ~t tw./h,G` (t.l8Gg8s'+B9wa#mh+M4[{w>|ً^֓!(uh_|MOvYW 6P'DY! F`uyԩ2,yBqݮ_^M}|d7~8:0Hwԩ+afn(*h2u)]_G9SsvE!cuN}}nR^j!D@]V)7`hTry^%VӗS {nxvKXNE/k&ھE{I8@1izݯ.ԝV1Ya%vL&F;ӧ`eeZ[ƄԃU?17ѩ,*)n?ޞz.|ؘ<Z=!\ֹ1"gL8,WwbMx9*؞r<ӖfW٤+O_EAwY!.1 tYq܎j=dmޏ[?C>;WSO}>MƜ6΁AQF8M)=ѪHU'fNˈ q@.ɉcb=GIhʜWdno0Ʋ,r݆d ^b)yH,2/m:с. t6vY$Zk50*X475e Fz=wXs!Se8csiNI!1cFiJ Zɾ1Z'bG}@UC^Տ13666`yy$sRVgUes#Y[?snl]xƍl4ocffF!s'phXaz3\ Q"ctNs,0B\Mpqs)B2XHI*Uc h!-wA=5Ro;10 LskfĤ(<j͹ؿr>.fw)Сy]"kR:r۱Uɣ8 /mT"[-3R{/qX? \h06tqp?#5sH]'IQcE86{tʣyEm$ n?j-><+kOy'K&xQ>.p[17:za?B(P!+8cDY gW5d~ñza+]n1v%zơje͢毝 ό6vȖX|ɹS:NCz_gKǞجho=?@g'`^|ѕ\|O]-}m^6qpRq~>;;SO[!$;2]7xA+ZLak7gC9X5#mNRojCƒ!)=d.bz>49IRzX ne.璱2w¹h^=H98kI4馍BQ!"j3 w}cŖHT|k۝ء8ʶ幭H5{:ȁ p |w8e[Q-n[|R ͂(dB%1f؜VL @`r>p}_:d.' |䑒LKHs ZQpаQz{odS''+-(T#VϮ^^zsu9QCM+^hxqc dr""ןtA("k%2%IȈ^^_›Xrx; d;Z-PD,ܸ;;;u~$(D766ʤ 1R%Ybrfsa!AQW>6SPggx24)kut6Gdj)Rvnzz":gw竪~ULbZ%и[^UKY3ƾd\TLZp‡+(c$e)>cj\1m)4YΝrR)u$B:( dQ0ayy r4G !"p44ak~'8`1k}6F+c4X-XHҼUoϽ4!Lp#Г u4|i 55l(xo QXJ9~7-p3Awk[^voE I >=6vSy<T!vLR|k^I_ ?ltn> c<ifFOX69b!BKd$q(viow~-G0DIvFAJI/t򠿳ߝqC' jh8Keȉn槢}_YP+z;d\V5"dSt:K/-Q+ιJT$ccnjv?g_7=$k]Ny:,^Zz,!P+/7kWĎ-nhIDUi{xU@HKokeWvMٯ)ԯq֨&OAƦ{x\.'oz?zNQ-@*Fr-(zs"x΅ŮFBH"kI 0Ѐ+@chgR;s7-/5 QgLyNT#R$R"';>Tb JxlIQ $Fp3֚w/IHE5B @}z yT:/:µ0cnvi C_—߼N4~}'|ܺe,g`e9onfHކDxWb[؉.{ً{uɲ%h JGs_A'&QDƘpfތ7O?-BKdAd$c)}rƸdMJs r-ecwRHcC(#Uti:1@dh,XvҺ{;Z-A -QxȀ+;{Nj矀#` @Mʐ(ࠋkcPЗ>J-|_qeЋhivC [>f2.BiK(l3j  0+]ǂQǟcOZ[J0R!Y6,|lIRj',0{#c4-3dD=&9c;x˟I]K;S$cV),nMyWl=\K2ƪS(z )a#,xDy#o$0 H&t+3:B5۱y$vwaANuQ4'Z%kuy赜窖c< ]w&9qD$g㨽."QDʚirF5,,GYs,RvuړƠ^l=w(=LYX<އ*AGSp8.yd0SlO2<]Т \  <ԘX)#xAZpz`=!1%Wz囻_\: ydfRiE*e\X'D& c#vȠ"F^]_ň3VRyι6y94mݼH̘u[ "V#rbq)1xϿoۄ+ŽޱSQk]zIYͺ:D5KUb_?"dmmm@Su@)UjvW\rpUM6-~ s{W~e^-URwAg!P`Eձ3_nNj*ȵp/[/3+5KcU6uECO]fSh&ݾ 8_lނD?6kVk UbqǻpڵhT) ]^^.c٬6ZꊦWiUH2xf=s#@NOm{\xYsa:vu/gs6FA0uADy8#7Z!#T ZpI90Hq.wuJ"6SsWf,cD9k1n`@*1ƤEӍڇP,-@\`QHw/_woU;G5$<%@@ 3 x @MʡA[?{߄*9Cp"  %Bri,8`Hp"5r"8 pIug:^"Yuyxn'8-NQ(Er!MZSn&ovL+eGygn|:C܈x oay%6yY6ɲqGRl=IJe2^E)={;Ѭy~Yf>>y%d}n-=鄟\lkN8^glZG0UHGm ץt|WE& Blx!6$!q.b;Z8ʑIv&Kn#@Aޜ04`P7igEhL@X8/0@eI_p`Nq ڰ_D9aW,! 1 `H " 66!@ hX~C<6 `p1k6",X гV9h;]ͩ$<t=s}_ã3k=NvEh*L9Z jnS Hif3̳%HD>َe@N+Zd@ܱ]|ReY@ŝ=S*gq. j@BοϜPoyS*dJSc\5#Jez)˲*us^FkKncccii FY.˲zv&,)RWM]/>}>3_[O|RݦTraxXqo_]njq7hӑ]ubS6+t6o9%gO8u9zvp\X)xzi~}S:{U>{;F2?l]]65zقzJ@/Jb0w,*mJdCzu]>nvg#`C444*7Y,^EuB"u=6Sqdu[ kF5׎:=6K @͍Q ;YF5lLW]θC}̓!hb7]\>ė,(+` 0!0fq> 0A p ': Ng& <6 ;Ԉ%1eP7sq@`5Ev~i@dE͕ \]8`8a;a,pG04hZu[y7eKIC Z&9Dt 4G^#췣b&{;&]UL: A?m粄Z猉LR:n޷msᙷ6v[Pc*zHxsê*QjR/JӴWWWKP8 pK}v]<`0tz2PTZin5W׻TԼf[Յ KPCSV׻gꩌـTR\lVǾ=Z%=WRK)ѫZSk5i4_qLJAf߰f]ǬvjT0ES/\}꠹rB骏J#.ӫoܸqWXy P`km{V `buSUU~SwsW:1x @?vAl?(Y16~lY^lgw;;客(cy^gU,t:勳PY]u>+ޱT?/ʩW^vީj/~G.޼nn9 s7METV]ZʤpϲHOf^$kd +W8S:HY#-Yr@1A!`@#_pƙ`9doY |P D=#X99aCHaGl t PTVϮ K@@,ۀ^? ?:ͬ.@ڃ/%M,A'P"pv oTs {1eO' 8tziG|eL7DgPQ`p#{'W]4&`h8CDdBi"K\K e2&CdA?>SsGXv5QF_;d HKncE.Oij1c"m)\Glyf%741HTcJ*ZuV][.M 5~8tY;Hq)zgMt`mf]J[u˕Ul˅$Iʒ{{{% XRTq5 eF٬TuUrE]RP2c@{þ/~?";Y>Cx|<>tpS.U)c: 9Z0&әJ2ƓhsI|n[[nA$Ȧ^(9oa +CYQ;v 9)@A LLۆ2f_0ft>f Henu2@a-2?G!Q37)0 ar\'[6&:AI F"+1ZalzA}F w k. ͥqcMAF[2@@( f,aHIcdUάuzKwf%AbGqiElL %"nS IO*Wf^n2?@}89=Ao|—9Nf끏Uij[E[ 1Uq*hUa2qeeewwࣵ.ت]xppP>6Ͳ]Y3/bԦ,rѪU~Žzjvvsޱoyé婨ǩDTkXWUm2j <Q%vt]wJm*Z($J{˟RCF\ZFVU/c,6!<:Tc h40 MT^8kmղjUo8"S5p*#$)j9Byx=UMiן%+y"@hf$0^y B;Zyd4KRDn)jK鹮'^9<7K'lswb!'B Y!E]~2`F|02`!䠇k"69v?oƃ4&B:Э3"[=ںK-@d@lP( uxHX81XR\s~c:I&As3L 97$ 4Z@Z` DNacw-sa?C 4b’S/3k6F 0e{I΅ 2,hq8!I k݉`_0>(b>d~RJƱֺnnˁ7X5EN2IHqu'=`3qa'ö8^ )9dy}ͷy55[{ ɶwXڞqG+ӣwLN" ӭTdf&1)$@srKky "ϰWiZg <"\RxC5#hV0,$4g\ ؉Y!i&sVAM `P1țYztݡLAEEEmƆVmQ44@ 8]lv4~XڃCV"q `APhnsʭ͔Ԧm` '`5\e efkovsEZ i,qD .cl>J "!+XІ P@Q@Kc4ֲ:p;oX=d%JdJ,[V' đ`dA`GH~ذa9؊bBKEKIk`L86WUo6O[U]tz oѸ}g}j}k},M hq.zDr;FEg K)=km3?O34eL&'Jv[o){܃Ê1jc]иB):[>0^e%sɋ/VpOs-/%$UW~ppPqye[ȥ{<ϫD777+ڍb0:X (U9J)_m=cG? [l-/D,i*fs,/7^Dѕ?8x LEas9¯G'v۳lu6,`0Xf ,VjE>B47ba#U <?5ԟ$($- /O!l4J aC&䀁Jv4~ïF[g >TԊOySzIx4]ej uJ땰ևb $`t\0E*.TOj>d7!Vk=Kƚ D (₧`G)F[aHyZ!) ;>%c)mG1TTC#!|ZB2Qc1}sk(ˬ"^J,_k4CM޽|A j!Vu}zenשw, \jblnnWYU'JիnvG-E73\QLB*xe'vum|ws~RQk-%/ ^޳sDbI<˺lv_Mm;(wsi}.]۽cʖح\}G?c[jO\uD)5LV$SnP~*T_\ceY:rq{SK/9k7 gHZ0H+ˌT%RSϤ aM~|[ު)3/âu1:S3d.ͣECƨl{\+M%֣xdB G@rvLS˹V7A(dE*&AX$-u`်D'gcx%A܃G?s?`)DCƵ{Sζ -C B԰ Su˘: z;/"'rY@C؁8n=d'X?zp8>;8:w'»Tq05PucI)0@ *`O#"rЇ`d\ eQۛ7US3Qm7yl-R1zww\;7}6/qQ^w}UrpK] 㰥:&48PԐpq JqzJı( w%'ʕ++]-|[)%}ۡ_KPuK Kg~F矯ڦΛ#wKxNǻ>xwd5jĮVtqSf`ߩ>&5Vnt]f,oTy7)+s%B}-~zC]'w X,Uꃃ*{Mru_ Wefӥ1$3=b+i+(%f.SlJfO}zyafEa  r[s'Q{=&Zg(TF.ya3k1ZZ+(Wċq"GIeN(Z,lc E`V,-YjIvU-x_-ȏ~%tXN7$ⲑ6X@DyGX F$36jg<)L"!o EX(޾ٞ#AmT eKvSv.mV!"bw#m"ݮn%AðkjNlbM5*=]bamFe 93]G/mYSQXL[&A^ν2"ĘD_ _>' >]|r2`kaXPj"Ѻ:/ӅO v$ɤ=Erv85w?t{^jTEUV9RJ]sCp80ͪ];<'h\mnWɧV6),ow o{R'=ϻE w\e.m}8{3ݫYO9g%T|@[T,Y{prP]-Eփ V(VVGؒJ.}հc'nSp_/MӪsX=w.xZᛛFUEղzC) 0 NCYNդjws>Ȃn#ʺzQ4/Y"0ƣLe.ZO#/YIl[k7FYk) #T8o'Ɉ3m^m.|4iHw#z=혳~3gƛ=˦!}|~cgovߜ?sXd߰֓D>ʱkd?ПPac)+"NLA˘5Z-H7FchF>SęA\eZJx`RM H  }1#E:I8 U >y9@\\1Eز.0LIinĢR|m ^@CKb,9| $B%?5όcf G>2BO-! @&!"4p!d?6?XxmS!6`# ppB ^3dSnq|aæ>ȷ<<3曱V3eE4-QLwṳubm sJf<#Zr>:hϦ&Rօ?kA>oVO0iVc.Rge[ѓϼ_ 3?_a4>sLXf$\؞V I3X].NTPTGkkk\µ]VE+*𝝝pP70@UY;1Y, W'z-$`Ҭ"=سlq+|#rtOK[=q{%F3kX̜l''NGs}bv _J㵯*#d|Unr7[-Z=dO!^m/I$%j߾/{J Uzutg[juYGw]y|`XTOYEUWR+cS}bCÍ~&vQ5RDđ)+N2f,0l*', e?h W9c˜eɘTA|FvNb1'ژP#Q꙰ 3B"hH.. Y?/^PI5=E>AԄ@` ,$Xm/ig=x)}a#q@ 3>S-:KplrQ9okޔ 0k,O"~,hƆ4&FdYfcK!uFH@,3km48MyCǺ#YN[UgTTX/H0kBJ?pt~oq23L?A=KMz5f9|NӴ"šͦ rG\*Rʹtn_t!~zy|j/<_ C璺kqܨ×|/g:v˕NqV{L&Z[zǝ9-1\oi__o^kӓzٿ737z>K֐\Vt.DDW*O~SAܬZ #q43ubyZ9eG֬0c B_t27uEj٩:^"0T:n[&UuާKwk@T&IR1IW93^\jwSxG:>=8~)Gfi/fsX=SN( )5 'T\ ƅs *}kXXHsn"Ϛ#و [VF)1 2rzf.r6vh/=8"ɛ:!56(& Bxy{L|F;3f lUSV=))RX$,Xv4'Φm pC@# '` !6m>ȤÎRp* 'Hu6(hH2'2eePIɎ-fDt =5Q3 } y2 .*H-wv|iCzD&)Y:BlTVQ->z⋙7̌%r\>kG1gfڑ%7쁼u ^_׳_mhg^(ۈƱt垄zvlK=vg[OQ.5RJ9q΅%Q "+D]S8*Zk8D,jkW1a: ߔab[&a1e=XiO#!pa%@)ER&^~Ɏ f0h4͠8WLoN,bk Ӈ%H@s(G(cRE)Gqj^o0>IK r3b'H(oDgK9>쩎i)uΟMͱ{ ρ8+?q(^N s 2Y ۟=/ikc@ uGH$]'$܂A(^"V,ʙ2BkjGNQJƔE OYq&`>fW47FŦ(6$)3); ]"[~gLi:Q*'vL>Y6SQ4?7 vf5#>'(c}]o1{} y\ NLu,|4M[SweHv%ڗ<ϫIITݺYtZ\ollTcwpgt=c^#>;Y sϪ˸4z/.-{t[V8By+js _ԹѪ!; CKHХL%ݎHqnOjA~qR_ @\үYELqC55ogT/E/_#if:o,ʪ^[[VUBZkfkjed2qը<+#q)4n1!/i~n`ˋblQ$!#xlbBIpKJRBxAT*(jE"DTNLU#o^-P[xWlqll a&@R߹؋2`Df,eM+6'hb샜A#KDCVX A9J@a.1|?c /Ϲ4dvyO<QB-iKIk:_4K[qtvbiL80yr0U龱IEjal lFIXģRn i]Bb -Q91oiu0>g֮ شB ^mE[a*D\2=](4@jA{gm6?fs36snCk}˸hLٴfE^c;LwOn=coXP+'{ǿx ??1Oݵegqr1!GGc;o qvvn׻fP~`^`z`l=1:lOl *b,X&91{614Fq> K3/}6Fo ~~k+g>@4-TOr9o3C 4=8l'Dˣ&K$m]+I_d@Bx@ 1}Dx ma-R17u^jaXyuEWL9wJXEDEz.#p7c0o$i?*8Yk10S*EXk2k4y>3*U(U2&8ִ L`ZlvorLBʴf4WJLSv-M82{Rfys':/,qD6bt9g\@ V%p K,w?6 o < d,=&=XkL,K9uqXI9Fau|y$(tέ!LH{`: Lg%@SSaf2+Iގ97v.=W&ٛ=7gщr'Wh}4/ds챟?f؎53u;·ѿ;ɓޑډI2\q)ʕπ{Z 8f-/(4P(V)RR\B($9`Liz Tv1SB5w3㰅1@Xb!cџvH\B1$`6[w.HضZ4~S A+ʼn|< ʶ#mP\댷y#]dsK{MW!-% 8бr pJݾWY<8UFQqևUKǍ[S}Jz&R]v0jVq̃]-afӝ2AXA];6XVzWs~'&r%nͼ1kآe7mlXo^깷K6pֽ|.SQvƥWu5W!Ϫ?aѣ JU!u%|r^]),֯ɫlp1 t: Jyܾe\Susy7f3UWϺůȬ1-i99̆\bޥHRTj 6,3 5g6F$ح+EQ$D1^iQ$e\V;V4SIK9+¾lcs cќLCKΉ $GD /YM&,A ,` ^cgY c934GqPl`C)<6sFBp( A05=BX 3Z&%;Q"K1'>72OtYd/SK O;ö+'Y:nǰ1>x_*d] H˅=COmM<" VšXEV~u[Iqbͭ:G[Eb``_-ڛ;.EՏ;Ganwv \rtѨvJ4Wqz38 [~ioT8HsfYVׯ+S9hZ辵^v=8Vrpc})l*':R>KVߪx67Dۼ< [T=Q)~ۿljK"xrZTV[R*TX֚X^;g!5 KFhplѩ.Hq":+MF](njaCkz-wLf x h;qt0s!<YfA&&WC&$ m |NsHMt"ӥx77az$.NZK2p. _JCronhވcz,ϧe&'Aɉ`k44< `gfr=M7i1ܓE0_M&{y>2;>Eh>)4ֳ FL $\rS)C39stYr%~`2lq\iTvAȥP3IO !NĉFQ85 ZWzjUlVCP9܁@Vv0Q۶KW%%ZKUo .Yro+KJ+Kmoc8i:zWzO~o{ϕmvTWfi/;N nYKN-Uܱcs6K~[OɣHON-~.pmTF[7dXΤ%bZ찍i6 覃U/۟yXf|RGɚ`X۴y9`G>̽$f r*0:MV`Ļ 9&@(.6=a}&l"=⫎& /B4=lD<%.-^(E޳/bk>}s؋#vx,ڗݰMDIh4.|zYXw?R&0Od '0a 'k{X#> ѳW^6G: 9|) u Qxf&-a? nwAE6sxaM|ohRiJ1s{4,9Y⃘ T^5~+?`cMZL%cLH9:1-a؜'{r11AЛ q$ _br7y_oi;d{B.,j~ߛ=77[[ӇG?? ~_O+o`SZōͼu"{:lf %G؎[̞>&4D2HE_4Se!s^չ*q)%D WEq VJPqҊC|=߱?4w[]q]WzxjRY?rl:26D˺vH7~Mۣ]Cv큿i~g}˯DttFJ^v&-]c=Iwz:Ԓ *U]wg?ϿOb&9o. [2]FNM&+WT)2ܪƾtEnc=-;2ѷVy:wKjj[ ɶm | .0p0`0!\.x``lcd[lŖԭιO9bU͏)ާ quYV}ֻ ݻF\,Kw^/5ȔVN3 9GB,U ;~]1ɧ[Զ rc]㖩a, gyh7>O|&f*a'LPB 4L[Vթ `qdI:y~W;\Ͽ۞w˞WJt6G庮LQk!v-&< +۱^ҒRqSKTA4lɩ[>)~hIVb߈(G ~k\Wdc$\Z#, A>ح`9̟Ag/P/m>#&*廯o\lo\l,6ŕ#RmxUoK 6W Pg~co1Ou.4MᛪQRH.C}™3gc$#U z6cL%k:۶mSgc٨m/$ SMj;@;(F">xd,00jSm ˀ1)>U6R1WOϗC fc9$.~n4Oˆ;raoG~۲.JH|:wq˵Ɏ.]K 0A%6քY0r0K!w#X 4^X}TҞLN~rg_")0%NG6]9IaAZM~Wq!W|i,K!J)XdÇq"X. 9,%.L#m`99umZ||dH$\6;j ;7/|mR>'IN:e3e?N'@KF6L+ȷ10rz2D'> _{beʍ AłvwT2@3pq•^8E^iZw4͉ dJah4eg1EZ-r9]EfD$K?{u^e!"Y//-Lb/ۇg*QKսC~wҢI `K6_=e1=N;^w-1X)ɪ&uź{ JEOi7wa ɉM6 W-U^Y6}$ٵ$bCO%W߷=ɾĺ!2Q)},b^;AGur;vp׫j.Vw)jg}0TI۵~j4\Y"Vk.#;@}*cٴ=;~}3Dc;yOΝMw*ڑUD^Ofr{o'%Qo7;i1ƅRRF)e-٦gyu T|q ;%)! t4 %KR{:5jeqq +k^0%`f`x 9!@$ܜK7)qFL쑶r~8f<^O4v; TeM8 Pra1HnTzA9oq/0" e0U@z !0MQcIM؋0uA@B&eEt <xA(Z!ih4(q"|s,Dǩ $]! { "N|b<{S?%pi!ɩ̅^:ND;aū'dشu{HO?deY UD&5^PNGf&n mJ$zr6~.}ʺ*Qo bKMAѹ;;z_y!/7m1BYu9K7} p귪g..? v+^g|m 8dF[g5t!D_|%.3d !Wd޴J&X%YYY9y$/3j7gpuשR^GT6x_5_0TݮvQ88rf۸%sۓ~x.Ӭ 7 :_#ȶ0׽|crRoR?\r*AL|~JS*阦CB4.M1U+k.?L./KXuT9ŷQB-7 i('!bLմ5@!/<~i{}LjFɶhI'DNVr0 s*@TqjN'cx2&5'g$GcԬbk*I߷-qA m̑A$sCzEN % f Jݳ7~^=3]JsҖ3CYM^pӒHJ(@,.aa!/`ye66%a6@C $^@mCդGN)nc[:ž!:Aҭ,ͥġ?z*h#Yx!iT\јtP/X).I.`n ;@mWժ4Wh,dkdmVc`thQJ#ѾBAeBۅ j=իOcU MՇԒaH)c ~#Õ<_v,g/6.3|Jd7&R~'eBrCVmMݙk8T6|[OFWf7_xG?>wgJ&֓q]ŕ4ƀq}pMgsswI떑k4ɪcetTu>yz=p*C@1E&%;'PVz[~QժQ鍺1Fv62>٩ٴ'yAR&&$rc5aF6y Vk_+9-jԗ׶sY0BPmĦHX]u@vؐ+ק%C S"b;`h +2}-vL.?#GE?򹝜@.0cO)-L$Ha,H H]Rsk}=zr( B 7-k\4ANsh/1p':oQAaG&LzIܙs-g[Aq,54L#,]i.cf?`>Zr|K3Z&` H,:tn*~C~3mK0\j,}&`CnMܴ ՕhiJ]~_a*,8UԳm:>PJV\τ,6\6MZ"qz 5A*QN3??QiƓ$!Ƶ~bh໿EX˸#F+E[g郑WPJOa?6+-z%j@*j%Wup6$AY>;F[cLB=rFQ%gmFw?U"GpՈT-VQPN8*w@7 C9NtM8RPw$@0Ms4q՗)jm%UkGl#NY\_ĦNm ?ORյ ө*Rzu7z|L?5x{C- F ^̝N6f2A,Avi>46* r$KC3sUr&qQ+}0U֝ ?`'εnOgf)фaxV~ON:S/J?t2bkrr?*p9ĀǩYFאgA}< Kx" 缷T,HNĵe1/VDč{8wퟨ&pdl#H p@;t 9(^Q3@`tܓ[#{II+HMv"ʬLU?l >*^´'W%ܣ lrZk;f]&=XVXC†8MBۦf91_- HO}k,]{N6>|/_f:c,buimKrcd*=>ݛiW7cɒ0:iQ)C2/B$ bW.><|IZbPh&4"vDh&Ȃ!ŭU>f;!Iapk!"2&Ph¢ºIцk |6?Īp"MߜBH`C&ρwS!AJr5=i' 9'9)&A}Y|]5=vJǺk|5샍&SF=x9 A 0@)h S2H[v[Jgp| }=ynChO#i#n"#A[.ث L 6@[c!wSY{}T6pMh(͗/츑9۾zr`#H1֙Bga閍t:,+fgVVSؘα`8fy,dm*ݹsn>;;uFJ^)o~fz~OnqݛugLԈ?2:Qƕʶ/ι&Fb6kQGϒ9'hq$^n7}-+@f:B.j4[<ۙYȂoL-#'Zx Ngɯ}j$U#95E ?~ i4b&OU۶Ֆ馛:`zzZ.ۦs|ݫ tHG͌ 6;jZ#'7^t,Z5lO|+z=G')5i Kᴠ|r&+|DPk@V'gv0,,Q~'wz`ӯ6#+ޱIDv))rc \Wb"6 1<" c[vS d * 5 `*\nKty(XBVp!35 7Rvë, |Ow@ϛG{y܋4gWZ9 zz(T%Hc:C”a(]X5ip )P*뺞B8.JWR9[SuOcm)e>}_'Wj_˔Bicɻ$2[GJOqɷ` F!uPKcȬ#vUD l}ٖ,ѭyjyuA/_FIji( 5 5Qg[ZOmpnnzĺuBuli.>PWM-nNkeozT~( GIZǏ'Np}=VCMMMr-sssb1Eb8*DZC%R+W`Guvh4 V+ ~r(g_}2(`ϰ n e?#lŝ录_l %aH!T? ɖTcPIo+'ozsr;NP0"GH?A&! -&s))48X !-puY[6B!j>D!cD@IA:X(6e' #51 ёI(^aq QnD2uIڲE,JEAr4i- IDATKm{d?nڨmj/]7;9+zaBeJ|G3Wg+|]Ӝx7EFfEӯV/6Soڦ=77f.ay>Hw֪p8IJ0}竗ٞ?>;Xٹ8*Lrο40l)DA⸎`$ %$gI BZ71DH;]-s .vL0ZejC';-q> ʀ5UY!7n'gZi!d"!AmHDNv[ 9 GBbL ̊My4&..Ն'EHQQqPH" LHan,F@!>ҋH  0aٗ@v1XS$B2$=l tdL℥cYŴw|"~Y1;jAq8uuRQSQL.XM2aTL뺅B299bLӌ(Ka]T)0 P ܵZ-)QBtq̪;f knz;q3nMZ8a|A!ʊStjSHm׍,ĕ\)1Jo=Qw:{gmd)7JwLt8S뺊w:&Au9^.-^}UYFSXo$\_کp*M]jE4  P`wl4j+ z۶ժyxի^577uV]}Ņ4M۶cR 8Cb XtʣcK`%`!ȵ >J|IZ;'&\|E&8Y!7zZpH`x"GxR $㉃uQ+ުrbJ#:C*@h @ QB ahILԺu.e3% <;b "? ^B1wH0Dks zH>v 6G㬑q"Tf?w^?L|hϳl|Hlf6eYvDjW8jN:Qg ĸR;`x]dpxsZ&W>5VOFngRgQ(3NYbde!lc٭j>uCo~ uٙdPzزP: qp%,<;;nL׬9rl6*ĉfO,"w,pmڵKIכֵtT ٬UWAPz=j #n$(n?1fmϮ&4e;yf ˛pҥrγ9`L?=V:30Xtse)\fř JXΘsl_3 YLa`ZHAvIq޹'z!!xTE$10AH$DJ;-`҇]-"d rppn2 d?t!c }DbR*&?b8٣!#UM*v=wf?7a!bJm V 6AH@t E> ܆a.uΡ~>vpRESI1M n<>G%Rb$a!Y ?K'ӉHuRJJ*1BZVVi=S(L~?)2fDIԼ;Uhjkkk*v[kuatvKS/jZTrd?>~S6lLj_vj$$ן"H[R%qC=H;9f^VWr0,uawe9(++3wDݖ{bnmh9 һa\S+Pz.וW>2WzSy=jUjݕwUЍFرc*lA9V-*wܱwqƘn}_LUEq]W,KAAǪxLEfnGQtz^n( @Emj-.V^Y1<׉ ӳFܳh℁&"2و RF@2ч裒Y.wcQH'%dscBkN׼ـ:g &Ё x 6D @VAA (AAT!,+6cl.-Ǟ9sIX. 4!bD1Jb4Eg KTm\W(Y&Cvqѡn%TD0sN~a6ۚ<ٙd]B6O{\ZWN1xyfNl uiYg1XC z!:>v7t0L(Pc0HG 21pCq;O@06>{&p|iًsgxd5Rz!JZbGG4MUf3[dUKBT* B$Jtg|YZT FDZ8nqզV. kȘ#R <N, 0(V (e8jCfhYb=E^#B!AhnmJ[r^섨TJOo]If_ ]fJPU,vWɚG$rG$ˌړq+bMjp89?j1q||wsOM=@:?|ߴM'=rÛnd81!Qa: B =٤oMw0Yp)X&V0r^WGn^y+\Hl`pޜLLH Z _ A> t$Oli\(E.§@\vani_ (a A&J>_*O/!0)vh%|K dTm2?7ݸƌZz8"K4` 7y+N!`50 !%4)|!]5t% l Na#VH p}<օ/޿5tY_YfYzCd(uԋ5I2Ia4k(\[Gf(\.Jy%yhloR| U4=ϋXJ:DEwz" G<\ sn(e@I\@54G>u$E7d, (2I:^S-eeqU0G{}cv(㜋D\翾~tUkL&Ze[Ȥ֤PGv?m?PcGWUYVLud_{mmȑ#kkk'NPb^jXOi.3 㦛nz߼o>@+Uj5 ιm{g뺊z8n ðnNG*B9MS~嘳d,;ntvեߴ5$H IӒ݄< cD)dXH=HJqwR0LHP! H?+2LLNWTΎ60<kqJ)6?ampBh!db DLŖG,0ƶMrmI8@"(#.XhX:]xsoc3!(Sf3z8a˔ MQ6SB+rV8N|~^8~b4Wd[.X?nwߔ0`+3)rxMExDVI m۪z`% j5\U\.RJ}gȸ'j쨠4 ^l6}ߧ6 󔏦DS1 C!]}cҞ%3;.q ՜U'PXwbG+h+O'Gf5kŽ ʔؐf*^kIͭVuK ׮Y?GzHXw*:0 }Otyyȑ#*cRTj3zٿ]wݵ~~GT`N:cFVVh8*InJVQ{4jZJгqZH#_7Mm=(qD:`^;dDO@@F@b+!8no-xſbXl"v368BurD]#MC*$;p\֍Z/Q3'0HhE CpAn󱵏jC-)қg|G*,2yU MA@.B }~Nm ˣXvaY:(Hp] \@"K655[(ܼ|+N^K2|,`t/e[q_Us\٬_BG<:xWkiiB{_I_?Pd@U,?aB|r 0g7JXc}x.2g-fY) W(2st,Nѻ+? (H1}RxP:< M7egcv0s)1LBL+H{tvOl[l7E22L,P/_2=gƈBij MFٴ֭fy?~I[ $!t@v{Jr#D 9@*zMx5%[>wE$$7tRvpV[G g¤H#⢉Bt;>xJ?crRI(%01J<(%Gl0$tH ) `UF ,M 1(Fٝak)8/Q/_(/db>Đpx/v"j.cKENl~!+LZ~QssSsNn;wkJocAx {& |#"!-BRFl"L11h Ddb2AõvA|=6 #M&H&-0com;R#&7T>]z Z 66d&WDuF?b-ʊ>o۶ k^c= AUP2HՔBM JW<1k~h3{gY-vNл&òuv25+ndװuq+wOr=oN:ZhTjz³+ >#Q)bEz8z=$Kg  N߿͍U JAVM Gy,XXXx?~!:ַnRTl$mP9SRnzNG.  2/;N۞2O&}PBIBBB R\QtYauWEuE\qEXQ鈬Bu&6?vq3CH IDATםs9yw8PJj>TGsiϺu+!ǧt s2^7 QF$I"@ brpt5H=zt EGi?@ p8!@@\ Sq.crYp)n`st*It00,<4F'%.h/ x9QT ϒWܐF/"׌\ 4`B`d7 =T⤓v>R)8l6%~RUg!Fvmm-7q˖-z{{]HE: TK4- a椄=MȮЎ&-cKYL{6<_^WyT -ܹc/vraaZF>-'۶mۻw/h4'cL{03`ĉK,0m4zr2d2LH;4:}٠UFRSuc$a՛YjXN}Lļ0,[hCٰ᭙䈾QSBIJ]$FPaʐ2P <! )p@ A"`O ePD .d IkB<L q\dsE9VkYwT)_{AuzwenWأ 9N= 5,/Nŭ<ꥍ#έ4<׈@# #5c q}b:8*)1%KEnrSwGW!6A"0 2)?:<IH" mh:ѿ>#oR r)A80#H(;q d>ВAeZay%ƊR[QBB5#P\'éqY'iu WTT$l6K*7M j*X,tvvt*d]Y5T;wR'vU-XyNiavCͮ]FsyYv-bÙn>Bh0/[F#&["&әs^qܴb~0:{yZY Jݣ^;(~#ЙЇZ۩XUU–۷iӦ{&\.G7m(Y'O\ZZtYfUVVJDCvAtn(:NvϚQSu.D"hI$TQS5ò;gb~Lϖ m]]m{,׽zʻDx. # Q\ !AC A B$P4.D00 HqӁC9pCL1KhDrgx6^ee( Nw2_u(OjɫbWcUNbj%m xxplqǮB7.= ;@X 8y_]$r(UEP,WH,0;.C@"HFFZ*(&r*R *:שq2AuV8ɀQ(Qt$C\n" ɀGZGC13jtzGExM/hzƟk}r& _p?n)G(%;`z:oWFCnccI~Ūi̖*@\M sBƄ'P"+%P I6ܪYFjɤ pl|")[ }SM?񺼺p{2y dZw>Odn]w/Ai鍠V#,ZC2.錭cUrTcL΢E7wxova@8s%[=/ͫL'>~a҇t_!7םfɦVu#۵kWOO{キo߾t:;㩩zNlM" ˪hlKzn%Pבy`eoM&IF:q|WW;%Lmh*$D7* ᢐM1H75jk3( 0tiFBuC)AHH 2x |Y]*wYQuNk)H iŸL@8<ڋPɩ!;%D-̬$:GrgxiC_ Mf}>M.buu5MlQe!Бin *NQG4zN'yF[*Y6@[ooWG2)amJQVַY{0*篛?RjeW0rTT"dYZIf~`-7\ǭ lآ({ w/2gL )m],yߩmk$YpGS.+Ls0уD"Flٲqf*_.`=3UӋzɓ'-]t̙YǺFYeY~?c1WUUV&!d2X,SI1N*2UҡNvȦjb9i*Dxp< S2SB* E@ 8!+3 Z!;`iHAa Bp`7<194F9Ar^.%5dZC.Vm$p$H֕^h/l 嚝%_Ek3\#JW;*e!n7sfՉc-s p< Z!T/T'2&221h&j H𴣵sDLBk"p@IpU)* P!*U _̓$8EүN_Hjڈ@ABtrHmy*rBTߦ`-PVIl?xH$m~<xqEUd]`kU3٪^#k{ 90}0R0<b@1@  ɛ@OH  ^8%:2]ȊK=x s *V|BJAX2RHuB6U=RhDTd&3^VWW˲d`XNA܇4_2LRHDUUYBbfV VڣKHݎЪUަeY5IJY?h͢E1*%bZ]*M5 !Zj],LEJ 3^}.Tiq*}/<7E KHz6me|UywҭDt %at]O}}}dr޽7onmmݱcGOO dzAVе~YOtO>j(Yjʹtc>it:M#c<Ru<g 4atuVMϬj|8V!m Ͱ’H9,4  p#)Yd@x?sI31FSLtCPPD20MennC nT+VTZCBAB"!l1,Iu`\Ч+ Ewyc! x/mGQ44YQljKHl {R/ 5 2@B 9%=3\0 HĜЏtHd up>UPHHjSS[v<2b si@&_cЄj&ޣC1 :4y,N.\}s\2T%p8*VS1JgvvV>WA3= =3yGjz_#w6JK  FT\d@@(AdyVi4q&_ؗQf9<&t$gZ<\gƹC͜ :'.4x!λWx*\XAHBpC !n <pz IHAMMsD9=fx|dqXPAV脐0$!  zhmP&šơ'` 89-0:!FDA~ T@ "AFB"/lSSfCQCrP+-ϝ?OQ1;OoJYY$(T,nh # O;vg3v1X@2KKKn&j"L]Gn)3mZOC.n{W  y `q}sMV?~Ilխ%A^*FTiG*odL1&JwAS1 *@nܯ0^ 7tR (̐ky8c}R9աuT%KMؼq3`ɼN()mPK  K9]0ڠǠ 9q&Lf<`J0[JK  in.dDdr0҈ a[p<8dGLfO=BɁ9]%窋.8yXv)(([yq "tq9?4˲9}7.1#D؜4nkl]_b Fo1~8 =E Z98{}2CM.sgP2,GF&LxR7\qNO\zJ{c[ &Qn3H?rFYo8xaPq u z8߸ aNb~wb6zBx\ I: Pȩ*02@נC)~0jt #!9@H$5h>4xۮsq:جɠ_ڰs۳јBe`fTIJ*%:(j<U/﫸(Iq\%%%$1zIĬ ?j&x<D:;;S! ^JөqRZ:[^xꯖ.˕W77r= ZWk/'>o[[)ٞ1t0 j*+mt`f~ 9%KD[ q`ݷM+!UU?'i^P2dF3ޒ*0 5 syUUM&===ٶmۮ][ZZd6Q:z"tpI/ѣG/]t̘1Ŋ\.g#qt:5USO BH&龾T*500Ũ=Ђ44^-6/Ҝ ƱcqS}#.rK%H:D< @]Z'T!;̰ÉfL\~&Y)')"sq'To$fAW7ZnHEF}7Gu+-oE̜jeyxZaā~v7q߅^2ڏ,,v]J+&4dedH@ӠsPx(#B+I>H#e87T7x1E;l;W#+,>dH->γ0%K@mE3kjj񺰰pݺu8@Y9S[境P+d6 F9zh>MCg%2qy.` _,lx6?ƽ_fV}( A[JɕֺB:_VoEvda5+٬nA6UaDbQwl=דs$) f$!b^nD٢redtׁϙN٥H|Lgq<"b숶|/xZUK"zva_ `m3{󓓷{ kP(9 IDAT 09"t?r6ky^YuHJAlD}8XM8䚧1J@$N'Uy<z@S(((`p\4UPihGUUiڵkw ,ܬﯬ0rȚEEEtpYN'[SYAݶ-|:m\D眓5+a!0B BK*Yibj›X<5¢>dSewEz5|) >6lVUQ?n顺^ϣ`҈l j0ȝ>?'OhѢsһFPLȼݤp\@<%28U@f2z(,M= a ȡFUȞc=*;ΰ=Ppi}e*@7 LMY! hՅL"A-\Q.~H#uzD?Lpʚ,Ӑq#ٌ(>OG%9 ܀\H39;\dUnlICC&t9&ŖOgNّD&jX#|*k@.=,SA ^7B!`@AAA0苟I V(]rTްaS KeG0 Q(Bd]]Uh_&2!Ϊ' {N dzT*7eJn„씳ԚOݛ6VtIG8\ {2h_Zn.vMS4ellEC*@ؗ!=Woooss36*%I8*X1<$IΝKC'L`&I-|jHTvb1x~DPGXY|ɶ:aEFAT<~!gK(܀K)!]'8̤1@|}a:?T'F:+2 9uKK2G:8LtTsq  DF~[7Oi9H. hxaz,AC:!Lh8R*8 W>%ĶY O"7$V#{" NSd&p8VP[S{t:v`U-YiCٽ`b`Q'Ij淋{zziGG:,;1#4~K.UXH6C|ҨjX"~B",2>0TH#H^d58YɻVG0U1qZGh|v}9@.Cpp9RH`x_z=P;Ks)Ҝ(:< laL(~QI3zADn$$t{xjI*r-Q<0ȚH`"Ļx&qɎ DQx<)T:TR BT,((`Vl*ȲLdN~4P .(z^zv.bwvv;,Y]qEEE555*++KkM$f6gTm5'CE x@Q*eټ===X{/#qK2vf*9Tvdj ԩPDƞ.zh`{:#0Gjgg0_gι|Μ9,X0e:g|*1؊Chn;ID*8҉1Su"&I3ʳBD-rLZzڇͭAFm[v̞\_/ @G ]rD,wF)IP)lp>*:42֕ (R:늇Zܮrw]ʨ5If j;R""Ne S'tB&q0|L8b۬mllllt6l0sB 8 py"d"J Gǁ&4fVć1e{')&\=J/C(-L~JE"7lo`sRp1$ J[V-,6kkDv2 Qu# )...**XH5C$N1 L&sNݻ&WȊ1 t: +**hMEEU@06o0^i0Aװ&*< ۝#"kIKh[|۪nJ Ph.cF9t4nYٝ'z^xy.馃X]n Qb$a0!3U'a8RTGdʹ:}|P̰'+H61yZ='5.G&^\$"( E"q: x?N .": J'tmlβ#bZJNc3Dp8b %: Ѫ9i{e~aLrzr]]]ׯ_~=f*v7Y-*2jii) .//g/h_6_ J_̾,,ͩf1LٙL;1X62}U4tzB=uTfB̜mu`VxDtbt# fٿjl6`~4OhanfȦª<GhmNέw`oEӔ+;~PX J=1[V5=٧a\.V Pz@tf/u&r1yKX,ck׮i"CÄH` .5P!2 Qq[")c 欦]]]KOQ4,04oءQLd2 *NX URdحȖ/I?0sL\4*۱,t}ا\ڠcID>yyTUeWۚ>/T};V7oˎ6666$O{666666666>&llllllllll>ڰ9eGEmllllllllaMMMΝ;[[[NN_+VX-I?G+#/Ydɒ%G9:::ʓO> r״}nq؜f^{MEdztm۶QrM6^JӯڨQƌ7/hg%q4MZ#׿+_qՇy.쓼1X-[D"_/~fEE!_D"[l=zt(/?.}jժ^zGvywB!~ӟΚ5#?_1Z|i---uuuUTT<G}۶mMMM-ںu^[TTz-[}W`cccsBޱr9իW_z饵ַ^p\akh_E]t=O^{/M7K/vڛoߦ Vz="Ȍ3^~eZ/**Z~޽{.]xQFllllN ;H$IR:TWWhooeYv:$ Ms=uuuqTx3HtӯsBx;qƿ/?O~}_t){̘1/OOBHOOwWرc-[裏VUU׿~7O>: zuB[fͽޛ*++レ?={Cz G(" W^yꩧ{1I{10nOnmllHwy^A^/ cAA! ?Fa}>ϳv*AxsZy۷;n6A|ɋ/0>]p $I߱coۊBin?aee#w<묳vŞyH$oH[oK.Yr BguпkYff }mlllW_}?o\|6l:4/￿4MsڴiHԩSi9?rHBHaa}_4_瞛B_lٷW_}up+׿~a<[Z|yUUU4/~A=zٳ,Xz Nr8pza{˖-v~p\uU6m{'666gݻ?YfݺupF0 0sΛ7c}6666Ǒ>%%%G>FCqiiʲLς K_zWiգ4x< JPȃ|p;wkjkkۻwCzz{9nT]zT|6666 w_(O{"66664܎;>9؜ba'=O^94m۶m]]]lȖʓX, ?Y|BQ=.~8Яsn-qs)ŗW;ll<[~s4^?~aa!ue/JZlyq @7aK7Q=.Dz*g(@@1I`j+'?W_[{66llNK Ðe9 AiO{^G1mOWqYEF%Y 븮k(YKwGkaI<#&BL&tNE/acs&!vwwsd22 pسgƽH18+j1M$w íHrC򊂂m۶m۶ |OO= {ƍ甝ga[uM? ֲO:tq{@87nqVN''mIG.&99a{"W7=7^<@@7W4Q.*!{a[lxwnD7ΈG7 K9Ҍ~dHM{/C{ B!&@t(L;V=xy}sF---JGB{cojkG2ɷILr]֭_ql6۰w/k ~pPUu UUqI9fٗ_~9N00ٽG;iI&OܳgOYYYIqɰƌ3G*;~/:~r5Ϛ~6;~s/>a{au}ٔ*~ϼը/>޲뾨Sjʜ ˎ޽8k1vmï4v\8//du?F&P^Yx18f {vn]Oϻ#gh y/K~nE'hGf͚x<~7\.rY}(oF(x#̤~#D3`+3iԳiK$ܻu;wnK7@'q;%n?>|;0-l↯ 9^{~E[./_1gY-*ʗ?y&0 ̿^o]Q} bD3LU'xw͙ۜY8NUUA0MsX?:k͛7{ǎ^[nK_zS?w}_$_UQ/w??7q 7})SlٲOI&}MM{ƏaÆ3g644xA5M{gX, mjjjj>}5k'OiӦݻws7eʔ={\r%( IDAT\(vh|I.6˭^ @mmO ٯzYiwz~ii)륥lvժUn+vܹcǎT*t:-ZyyWWW믗֏s-^i=/njjZl٦Mhǃwyzs9b͚5.kƌGލj4^s mh _ן9].Bߞ4s3PQ}6=l ,K2sYfF58kWq""8޺K̮7m=Bb}wr=XZ^ΫϽ_C.Vz_G^*,`s~ouwgغJjƍ#`yOg=r_I[xeM뿾ޟ|V3W;<3/KzI3A"~ޯn⬙q;L5ItF/K9eG3 wr/j!$Zuø|+Wy\s9sv+r饗xg̜fx'|gUUꫯ^rF 6l$zhĈя~ouww]dGyرc=YӴT*~sbkIiرc/իW9Rd*q~iiO#G LMMѱE'*+OMMԩSQQQ5Ν;WTTVo߾]F ???JaYlxyy4i!Pn]ã20GLLLrrݻwmll210ސc۽趀JKiC̕?e8λ{uРcɎ iMjX:ݿw-(bjÿlo"B3 #Ҥߧkdr% Uf*s; j"V!‹Zػ ^ST1"f0fpd$bFijJ+-Ò]Z7NP[ǘNpJtMO2Vxij]c@D@ sqEah33v@VתU{;w;(5 鈐?Eѹyݾ}@1 2t[mRRR HƼ2 B$MU9cfff&q\;D]EyLejaem/$# e G e,i AB*DZ޾fFR`-IV"OP~#[' O "X"9Q(IDQ,..sBKMM8nREo#222ܹKnox/7V1cɘ;%jӦSTTԖ-[{oɒ%ҥKV^=vثWȦrBFVU}rrr-'_,˲ѣGBBBz}DD`MA*7%;99V%[KVUR{!2G\033cYDiR-ʕI&F MMM={VK YSΝ;wju:u@aeWΝ;ݻWTTUPpob_[9t*ѺS}JYIP(Q%g J#O-䳕e2db.hyE0I]J92! "rȦO7lW& hJ TrVmAE*9\0HƖs 5/}w02"ϩd66ƘyZʑGmSSSVJW)..VT$1tF³g<==z ??d YhMpp0:{A5mcmkkJ!ҴZzHKiڴ{9L^ժUYYY:@qgjj`"Jhb-㸦MFGG[YY 0>KHHpppE1!!F^oKKK˗/[YYzd׏{NT* L#ɟ>>>{aYO>RCIfԩS/P.*ˌG4WefѠyGİy7|rw΃Ғo'\<< \)reJ.:Q.SIUnfzĦ(2;WŅ9OGJd dh*BHƲeo!UjkncG.I/Trs-RxD:jŴ짏9UeJgm4F-0ҢAcRGe^>۲fm7TA1+crJEwT\/r JmByw!/aQ ٲboo/InܸϚ5kɒ% ѣɓw ?0++k:nС3g$cƌ111ӧ逆6i4bWnIbaa1p?c H8Q۴i3g\tۻv'N0`@,sƍ adKK-Z<|PPTUl@@L&;sLll,ZnͲl׮]9aM׮]~'O4cܥKcǎEFF]:;;?~<:::u{M6Vٔd$'G 3=uꔯŹP*dJ Jp閥JݵjfBj%xx}4?95N.+8P9B_8ڨMw,A.TXӡߨLa9YOXqWW[ǚXG N8!R${d2P"2_=,k^ &S($baӷԞQW䭴BT8"iXqKjALR+EF=zvNI laQPA1'0@U& :B&sJP(睜.By)..vrr80$ʥ_ `bb1̴4mիdC A*9={fkk[IƷnݪW^?neeU+1dYRQ2].S9?x1c*[PP>8hZ𗴕Ezf!kcDaaZ~x0c\\\RRҀwb7҆2U1~jiSReИFm3<;yNkm#Y6Ӓͭ>>RhRqrqss>|E׈ӧOi׮]ՇʾF?_~ǎz3so] v 9ʛKJFDjAñ.T=G;R]~UV TӦM{}ֽ\^v-V9/.V5bkkXZ謻7 ygM<\zVVXL j˲?s|||kժ]JeفZU+d!MI`$)w]`#F(VREѣGxu B"dacϞ=CZAؑBP( rHH( BP(UƎ BP(BcG BP(JUaZ BP(JUsҮ۷owP( B 4B/6[nӦM=( BP(o#o:ߑBP( RUhHP( B*4vP( BT;R( BP ) BP(U]zm; (BF 5B/)ʻWaC._󼿿^d2ٛc|ȑgϞzxxiܼęΝ~x?ycZl7lKٵkWaaaYyܲeiӦ:r䈃 ~S(jÇ322?===o޻wo zIPQ*sssRRRq?jժH''kDvvȑ#ݻ7pu&''o߾ҲUV Ь޽{߼yի1>>>''._ѥK"իWEYvXQHP(%Kh4F?.]ڪU+ 6 T;Ο??99ԩSd/<<ޛ6mZdd5oy'lmm%Xbrrq {{7T_M~̜9… +Vxi7lB^K"•+W-c(kyyy7nҥXPСCŋt˯fAnnndd)S G K[G}[VåKuޤIt6iԩS=z2dazB1?TTVHHHTTm۶aÆ?㧟~Zv|u5kڠA{ffk׮ݬY3gμZ!]xxڵkI&֬Y}FyϞ=۱c'O@Ş1^t `񾾾g0/\Я_ڵkdffS( reNNN6{P(o'Ǝy(hС$$$@jjҥK322o^Fe˖E100ܣcƌIOO߿}RSSǎkɓ'}m۶+WΝpm۶ߟ5k֤I/_niɓ'O0aBVVKzܔ;55uɒ%G511>}|p_3fիWnj3cƌWk7ƍ]jj F:wҥK޽füaaa_~#T 6mںu}8n:/gܹ{8P$W .Ӹ7oBY%+rAPN*Oڵ >>>!idUV ,[nN^jmVVVecǎ;v!n(Ч~jiiٵkKGΝ۹s5ji9sfddcڶmKRVYw>yda~ǛXZZ>xԩS;w~IPPPbbg^^;|ԨQ:u3 4sLrmذM6dezN82ݻ111H3qr8B'9sٳgϞ?TBy;pcZܹsٳgcr "K\ѣGe<~6lHlРIeחJ*pI騃CEv֭y;w**ť gL|GIb a ((())l Py[hQ690`KܺuI2iӦK.uwwUVM2%((hӧOW( ߥ_~F˥By;p0Çu7CHdH}6wޕ╄`iikW^ ;n#\\\.]d(qvviӦ*YfU߅XW_T*x\K˖-g ˿k׮,;ydu׮]{ٲe/kCPvʽ\P( cGL6{#G?~H.\'X[[F  _|yUVMdĖ-[ 6oܰaCiԓЫWϟܹcp>}T^;wΞ=K^DGGsWvJ􌋋+..޶m,Æ ٹs'ٙmڴYl,***((ުU_v J礒CVVV;w}ɓ+l>|]bbb0Ƣ( yyy5jh߾<߼y5kڦ 坦By;a*8pɒ%cƌiԨQPPP~N*FFtҥK5m4 `ƍ0ڵkݻ痒A_KxyyYfƌڵ֭[xxxݿРA7WZUVJ=zs:TIG1qĉ'zyy1QR5j(>>5??heeաCOOϾ}~a׮]ǎ={v%>iҤ^v׸q]v͚54iw``mhhBy )rAPB0NNN,^viӦ&zԬY{sԩS(@fYd1O|VnXnY Te3[Nm4ϲή*U0 wv!==eY"y>77hK&2"kjjwP(4e/ -!666 @ǎ BP(af) BP(%hHP( B*4vP( BT;R( BP ) BP(UƎ BP(R)o ,055>{ 5O.I4;}]'mK IDAT1ݻJP( AcwWq:͛ҟ+V3ga_uҥ'O611P( cGAn޼2Eï " =?*`r*cGGN]GiT˻E^^^vv6-===G)}cB9::Yf߾} 2GTNTN\zC^=vg҂ o:_~xi9q 41>AĀ11-%wӺF^R)3L* ?d[BJZ@ܾ}{ʔ)<#@HGDD38 :tXrF1auv? BTs^obȫǎ۷o3f̨(͍7C22JŽs! V!`DLD `jsu? 2Jm`ǰZVYYYza0ƇuuuڵEښyVk;VgP(J5!ƎOlbb'+g}^_pzJ?pr5p ???^!@nA{BVdI2^$Mv3k`  b5ZognjkhZ>y^V1zleo-X}VUWX.< ж[A@^wqƙU ΝsNppp۶m Mo߾[nYZZm۶q T33315klٲempx񢷷wv1|DsܹOOLHܱc^޽{&M >}ĉg*Paa;zakkvj4m۶۷1Hommwv#""""".\`$j/TWn[j5cƌ2"n޼yŠ ???I~ΝsuuݻAӧByK}}}%Uw9w\pp+dggݻ7!!WΝ:תU"{222<<<.\Pnݪد^Dff飣OJJJrvv.lȐ! X|yLL̲e*͐I2ƘH 70pnn#RRRhjjlٲ+W/NNNjի?lXZ6ǰy޽{^ΝkjjzJ'''9ruϞ=߿ŋWQEiZ///ڵif  e%rm7773S*1i^Q1=Onbb8|p)}laÆsժU}%z.\;8:9̜4iRz_t+((XhQ-ʭ( ߾}͍̈5azef嗋1yy;?f͚ٱc;vH߮ݻ6nuٳg1BӭXuG5kݻwoܸ)++߯SC b>Ê!&R Oaj^ ^۷'!))o߾Pz%/i;wyUȘ4iRz$yffĉ{=V;m4ggpmS޽KLEA|[751$=SC85B.K?yp@A?z¦ϊD]F aYEP23g`̙.\Xl4($"څ*h$7c ?׫WGfb^" !Sװ9I}do6 bECN,, MyfҘcǎ~zDDԞǎ;yGT*U>}ǎS i`QџOɳˮCoIJJ:uꤙ9;{}gyC&nܸ-[b,$ ܼy!ڴm۴I[ngݺDu־}VcܱcYfb :t7nڿH/&(ږ-[AAUvo,"/ k׮{^p%g}2{lbccӬY-[̛7"y%Ž QC;ir!Tv7n t{77ozxxƸ4FtV*!Vfͪ|r6m*/^'nloo_XP@cHN:uT VhqqqW755 1bP(?βdذa~))3g|I֭jBFA ]6j(bX˖-g͚w钿 lmm+9 ͺbF0l={.Y~tRppEfΜyС ,ZF$ͦM˗GFFd?c #H%7ni^zI6nܸaÆ4kk+W6nXE5M~~~XXXhhh^˗/_jUlll۶m;2\;sǏ7nxҥK.-Yڵkϟ9s3gz̙3oz>(((>>>**jĈ;wnܹs:NO 0!0fc\Zaܹs'1MѣaÆ۷L裏7o>l0YZƨqVXb$DW^%͛֩S۷,/ jϟ??o޼u988=_6nO?7ndܸq!!!k׮ݶm[ZZZ۶mW^]2o$jɶ_r|Z_}˖-355С1I&C 14)##cڴi.\pqqҥqADMIAĔ+rD[jkѢ9ܺuՅt^*u\\\z]%:4zJїSNYK b9[jq;$#IIIvv3qQL>dv+id=OUZMRRҴiӦM6eʔ޽{ {;w=Z$%%uQ7n\dq`PЖ[0&jHm۶}֬Y3fuԩSgΜ4N9so׾~={nnw.#[nmӦMVU Ջ! K+_.YqP=zhڻwݼys Fejj\zcT _"=_ׯuسwdzgAAgΜ!Y=yTZZ#d2/\`anaccSQ;ի=JIIyAmJh_5778pMзo߫W>|qiӦUTTTHH_=bĈr/˒_I͛4h`ԨQjSgggRwuС'ON:f3VXQEٳgaaayV+JR2J8~խ3g ХKÇ1*l?GڷSjZ.(J<:Qj֬ickӧO,m6Go!*\-W8- tlt4 zAX^鸣ͻ>^"G?2R2BH.N_.m%ZyvѣGժU (rUX`X"lc[Ecdb+}g dIw}Rn''\Ϝ2"[4 c!weݏpBmڒ6_r1Ҍ:n…;w>ݾ}UwoԨQF`ɟOF^HUNǏӧϱcڴiC䩩$M^^d'bB(55u D%Mϟ#?~\^= !%-g~1qPkkÇרQcСΝsuuE ceeդI^{@>}VZwߠ2CzP.ڒ~aY֖SAףGz46]񢠒&tɓ ^t:BaRSS "W\ٵkY%]6*}!'pG/]tڵ5jP(BBB2227n<Zjڻw/BW^dWWרu}7eʔJo9d !"" `ԩ3q$0Hˮ<==Ξ=;tP!t1ZM&ҭO333۲eް.YdϞ=*I/M=FM@yR(vvv/{l\.uXTϟ?1cƐ!CLLLʊ 3& IDATDQܵkuEB&j*,,lԩ,˒ /_&'Qnnd002,##N:Ç@AA˲&&&NNNo&K.q')ԩӓ'Oݫw0$C;.]j{0aZ&e2YJJʥK:wqիWΝEK!s~(WH&C,DIǎ=zΝ/@ǩj''[nWv3'$$$66v|>=wܶm6lCr׿FzjNDz8..7oNBOLPTTؾ}{/˲[n]f "Ŏ{𡉉T.|艍bYMR]z}Yjja:t(iqAAi-[^|N"W>}Ǐ`YYf ߿4h`oo߯_?lllmۖ8m4˞/<##ѣGJo߾ PZPN. ͜9STN67o,uJ~tuu-t$;qDff#db)))ӧO77M6׮]033DCnBċsݻ͍AE  DY}yFIO3ZlT*oݺE2n,_dmkגWr?wܸq|}}?~~z\^;p8ٳO81e6;;{Æ 9s&%>3gβe УGO>Î'O?~dddtttXXXvv!C xzz5jҤISN4hPvkZA|||xbXxƍ===߿6~xb+FQv9s搁{EDD|,@ݥ^oT*IB80 ɸtc&iz;ZZZ.Yd˖-R)S|6m"7id@9+]m%UFz_?Э[7b\._rCBBOd2Hhjڼy)SVZeeeVɔO>$;;{ٳgӦM&&& , z>B%ׯwrr^=s0W_}]NFsq5k\~ԞR}8m6gggA6lǏ8qR>}Ç8#GLOOgʕݺu#''۷1^^^eɩJ )`uqqٵk{~/#899yر&&&0}Zj{>J|{?гg۷6~KKK2eJhnn>~ &{[1%"4MDDeΝ`ҥsٰaCVÿ{#2rETzsq:˲׮]#<5 n6%6N/MۧS?8c}:uꘙx,@mii0LQQBHVkZ^ojjVGy>%%eY77J3\m[90g@)߄>.nja.1N1/b}s͕k3%ym7nhҤRY h{{{uPEV{1t2߈x77tӫCfffVV_S$''{xxM%y~~J2znXj>+jժ%m ӧvvvR׮l?@AARd_ȑ#w|y5!dߛcGqbbb dx 7OOO{{{BP0JfT 2` ƀhj5@q Xx$'=)qB;ƎgΜiժ4HE#iH W\\yÇoܸ DGGϛ7HvZ.-to*Q>o޼hCy͚5׮][y:uPNr=",Z(++ɓQQQvСÿhkC c Y{{{}6eY;;;33#ѠVk׮-U*ՋekKla(] U1"e6Z~ [ Q*VVVU4_䐣Rl 'Ǝ;v7^ ,qH7 E",,l.AD-Z>}ɓOnذm]!nnn^dʃL&BrrKu Mav*1"!-[T*訨@2LN8L֦Mi5$PP(/VZXm۶m߶_!/wXҥO?tΝ]֩S筝]&Nz>tPf1cư[`a87˲mڴILLpB^^4βE͚5 Aw? BTs^obH_(&''ݻ7=== 0}qww7*swwHpv*tFOE, !B8RY[i~ BTJ␗yidvfUf՜~ B8ke^#!\*}R!5^Q( REC3hTDGP(m_JP( By{`6"P( BPcl BP( \Btǻw V A O.I4;}]gN ޽{n NP( AcwېNwM+Vxyy͙30v|鯿tɓ'KR( R]x㱣 7oLHHfVĢ ˒cQW #0vt4}ĘJq8::zxxXXXTU;N^^^vv6-===G)mU1&֬Yo߾A V[r*r*j.q!;ٳgF~MҲZͭl9q 41>r c^Rr78n5k*L&S(o BF})SxG^;f,qEA;trJFcjj*)GP(jC yq111r|ƌqFqqqP\VK/B6BA.17Z8Z÷sZGAFՊxjZNgee:t(66յknXEkkkZaXݏBP(՜HT;>I볉z}U*O+b 7n| #1yV\+ /&;㙵] 0XDQq1vc54zZB= 삲↊+w PA j\ĨI\n0 q7Q#1 hpQDqE;(0,3L/QжC4{ɓ9}ԩSUI$0` V6,6u6 1 cffFftH%yyD/Y@@~D~bKEP<ǁf&<[ c,Xe9NB,&$$L>8 ū0x5ÐgJ?˲!oÓo4]WaT! G8(rilw~yRY\\lkk16BVWWR5{tsG..rV4`;?bի]Tb% ^/&ݻ!$`L}|ͱ.]G^z̳W/+djG@_?]ݜo l51x\b~UU7c 322233۵k7|p zXݵkWvvv^o {=y8>uƎ[^^>zkמ;wn͚5 $gxw1&?}t̘1FFFk֬Y~ڼysPT۷~zhh=fY߿m۶~;vX>}^|9jԨ+W9R+T*U6m.]TRRo߾>-88[.q˝;w2{yϟohhجY . 6l؆ 'zbbbN0ѣG߬ys3g:TR͛7̙3굧>!!ͽcGW Txs퉣ܐ uرrr8zc-Z4p@m~ٲۿΝ;mmm]]]GѦuCQ5a„™3gds3sСC3f1bI-Vܺuʕ+zj1kGDDܹsӧ-ٳ_x1,,l]v|"xq̙a~1yarf͚ݻ#@oi+<</c4Cp)GM,v;eRa +**LLLyij,YB.\x5kB@m%yE͛ ߾}{??&M|5˫Y`|є]kRڻyQ1x j/$_n87n pĉSN=~X__?00622E@qr8yf"{.]SMHߡCΝK7R͛7>|I,Qi Eݺu! ?ڵ&u—$$$ >\~-Z1͛߬kyD$8 QusGmw]e={{j<}Bks̱eԌ`?Z;-,,\??` w2=vqi++_-8~T*:u~u2{w[.DٳgO MLL3gNhhh.lٲsUVc3;;;QTa„9s4oޜaƍth0ن5cƌ3;t萟BVSP?f//dKKWeF]3j1wݻeX@ٳgmVZz IDAT~M7ӦM8hѢÇ#@رgj:??~MAA~ዬ o߾Pbb)gϞeYVͨSRR 4o޼O020lذym'''0@O_ZV[XXL2uܹϟffǏ(JH)O dҔ-ų> }]Ϳj!Co^.egg +;v8vخ]w6mӧO_KKKŻ9H^h[w~5N-׶m;Pii g7o޺u͛7n޸qf~~8 RRR]RayRYZZZRRrZ muV!>O`!Ԯ]3<%ד٫?!0{xx0j!⒑RRR)2eA^ov`ܽ{Ϯ֭[źCӣ.\X3z}Ν;wa3z0g̙3ݍV\ղeKowhDN100ٳgM0d2}ay#FpqiߡC7n)UUU/vrr266&ǎ۸q;{d6mgϞ߿]tw^aa! iɩS?Ϸ'Feك8A[< ~ڵǏ?yÇ^^B%&&_ ())ګk׮-ZH= ;rHq;rϛ6u9aM耂/\ԤI;;;#UWWgffkVVVR^"_{xxՃ1rޗzr%Kh/4-131sssz\7QiHgϙmVJ$_xɴe}_|||L$H433315ѰgϞ=߿L* g/ӜoC&ӗM dzRkWїOASllr_p<Ʋ5&:*aǏOdf!L&=iƍ)PnǏ|xJJOqqÇQϞg 244 ' 9͛kgx3sss;a„#G̘1ؚ7o޴i:w W^ݷo_ͫTZP_R~׵{z'N[nHHHaaaDDeH<Ӗ"omm-A?}ѣxғє8I\cJQa;?8MI 0 ::Z[]v=w7n| ]7nƸu3fDx2¯UV666gϞO$Bĉ$m"*D4ccxA^\PZZڪUo/Brjʕ0`u S5uTWWmےZQ#.uǏ^nJR oذʊتUcǎ DQ^fϞ}1Tą#' U׫7۷"lmmt)T\.?bĈݻwc7o,A\ݻO4FzK9t W/[gϞYYY}% DhsNIIpL8,$j c\XXheeM6$C}@ƆTo߾ݺu2eJrr2O\+ԗ\U)*!Lfeejժ iռy"""f͚5DAghֺ{{葛KxB1jQo _C>з064׷2f&Ν6k%eaA[YH- ,M [j_"D"iZRt-Hgx$ֲe˒r#^|y-TzΝk׮O#J%IQQOIIQT[?Ii='֗rY%U"pC=L/d=D.J. 4k,ss!C눛6mZ|9O?=<ɱ=ŋFH|;zh gKhw7r7|sĉgk׮]xL& д6W,X0vXCCCBP(Jm򕕕 gkkknnMLRwQQQ +WNTZZ*LQT*-,,$奊 7o~(|D" 4(???%%aӧOS%$E<}t?//Rӧ/_6mpέ`?˲=zppp iY\=RTR3Ӓ .?5}GY,c2\6;ydQQmԨQ伷?}t/ 0Ç466no 1"kkÇݻW8y=1 8פI>|=qD|JՕ驧wmra-))Yvm[l &9eʔ;>ySN۶md‰ cx'O~牉 "88ܼ:44t…${&Cܹs,Yf͚ѣG͙3͚55kֲevu鈈B1vXVZM4i̙QQQd$mo߾/RRqrJXrΝ;[juiӦ۶mnݺpgg缼%Kݻ;n8[[[vJ&&&?Ӱa kjj:x`2O ȸٺukmU 0aի]]]ɵB.%h2qΝ3fػw\. \jJÆ +++:ujbbM7o9sf^+++n1gBѤIvڍ?^"EFFJ$E۶m%I2!J8{n[[[va!;'?C^^^v7J$GEFF4ݴiSSSӟ~c/ĹcQQљ3gzݪU+aݻGdH7¯:..;w$.ash ~˖- :CO>j4t4 >66tP/Ÿ;wnǎ{~;nݺF_@MQKO(++K*zyy ~$:u0s:!^i͛72u֍vwHS@F>cntS/iKKK//w^xLX iĤEm۶;?tA>p\tAtxOԩSۇj[VVV[M6K,/^V^=k,CC:蠃:ݺu+''GPc^8j>Lg4qf|?.͚5kٲj/GYYB swVZM8Q8cLޞn֬YDDĦMRSS4f?:^x69rDV9:'U&DBQjc  ǀ1f9_^2-CT*r\Rsβ,4HJHP!:蠃8m"o޼Y]]&I?3 aP˲Uxxuw%HKFr 77nݺj| [W\G]q Ø!$%'/Y@@~D~bKEP<ǁf&<[ c,Xe9NB,&$$L>8 7Bf<WYEPl HcϻY([yH$o)TlmmAt8R7oN5B;u{CB?8C%)=zz+JD"!3zfccӽ{wA^R޳{xpSSSq x!_+ D@lc|7&[ x9WAj_UU͘Œv >¢^1ewڕݫW3^O9?q}^|9jԨ+WjLb+VT6m\td߾}}S[q,qW\;w888eϟ?аYfǏ/\8R]]=lذ 6 N$&&N:aG׿YbEEE3gоnd,?C^***?zܾCOLd999†C͜9sĈ{)..ruu1bD֭:DQT34:|pU 88XPܺuʕ+ ؃1[/m/7k׮/_633srj)y>""̙3MLMk[kq,ǭY&11Ǐ +~ |2DZkּa_rʞ={ !!ͽcG0xo/њ;<Ϩ<"At [iOĤիWT$/*fy5 4€`O?Ҿk/"qXJ{7/1<ZWVŕDMGyƍ%KhȜ8qԩS? tqq7P\c\.'yEȮzt26nFF:w]vT*yÇ===1Smi Eݺu! ?۵`Ȼlݺ%%% ÇצceeeZ J%0 ~I&<==-ZurNvfii) 6NcǦTC K/\/ &/.))qwwӧw}WvMLL$: EffQ*4M޽{ApƸD|>,.q„ ÇG%%%9rd֭zT"]t۷oKйsRdyb \~ƍqqq۷HMMѣGJE0 Ƙ111 Ǐ3 ԡC͛7_tiFFFiiiPQX+++Uo/\̙3 ,\pԨQuÆ #b͚5HJJ"?FZvm\\Mw^reSRR/`?.]WUUqcxo8wH$!,֤5˫RjRIHR>x𠪪RͨJ͛7o޼ٳ?qؐGDD$%%}:ӂk֬g/عs35yA?̗/+x>` dcsh~իWϝ;Ww{Immm9jrCa&qݻ,zG/}iVaea2 njgg׹sgQ7 7C&OԀ~dUPP~ԩ3%$'&&WWWtv"OQf>wF]W^K. c~׮%jz̘1~~~- ¦O>!|B9s2jF9Z촰r%<~ť݃,|Rtԩcj K+K+Z}ĉ?NwС ݻw'&&,QQQӧO>}q:OJggӧoܸ1))Q&3:>!!111..a ??ߖ-nj z0ﯧW]]0ꀀA͛7/$$(XBk޼ _sŔ)SΝ{|efnJcBA(Vtu||Jߡڧ$w('(B X-CC?q$?#kŋׯK{;;a'ТEΝ;KK˖- _9s^=o#a@( ? €x0l8@Jh|A``MSR.=AB@ۺc#_qj$nm!JKhVxݬ<߼y֭[7o޼qƍ7agϞ_wJ +Ғ۷o?Nm6ww[ !|||0veWWW///W^UUUyaDᙘ[xF \\\222AJJ*EQSL13իۛ Blgdd{vܕn:@ EEEiiܻGEE-\Eۭ{)tI&]tIV_|ɉ 1bKn<1R"!̍PB]v3)K.-ZHM=0ڷoY"`jjkĉ%%%]tmѢŁB9r^_~MӇ|9Klk?6ce(%%jEMm6\_ʕ+I{_{xxԍ78vƍonfnVoM??ngkG_[j9snS^R/]4ibbaaQogʔ)NRM_ V~߿\.O4wN+:^[CGӴD"iH<1~g֤&/k!$$ԜzBT iee( Qbϟ?///CH$žkJzRZ.)T#A!p >l% 1H*Atk@^CBBh޼yiiϒ6CXwcL!kf]7⥋έmuN:UY{QĈtP!$L& +B#ֺF^z=ÇOII)..8|G*~׾SN7m$ __j}||z9{찰,CCCQZ(lh&M9xy{0aBFFƑ#G|||f̘1yM6s꯿: ѱzkXz;iҤٳgrvjdA SnHHHaaaDDeеykk[EBܗ/_=iyl$Ӎ/i(wJRd)yfM-}j룶j&x*ao1iΎDrGlK)=M'oU 9ݯ%0<,z;~ro9qٳg sk.^X& Jf ZzիW ,;vBP(do6Jr~577&O-Tڻw(BAthhhjj+W;}T*}˗,H]BmO|hB!H<<Mx!qM6_~!ý00]P 1..n֬Y6l0333008{_Hk6㼾\*Jj!٥a޵v]?B5cXBX_&7f'O,**j֬Q]>}:|ppp_ Ã77rHR+(>|޽{}}}H)b4~AAAdqI&4M?|0::zĉJ+9ISOOº͝[RRvښlBNW>33sʔ);v|INm&ɴ|D1^xuEul3Ks"JwE4bH4vcy$}5bybTػ|*(X ."eeRBIHϏgΜ93ܹgL4)666$$DVYXX >|Μ9${&CϟrJL2ȑ#vvv'N\xqTTCCCjСCJ4h0jԨ &L:500k׮$={|Z8777e˖e˶nڠAG~~WWիWϟ? >|9b{{{Wh ޫW3g_&uҥϏ$) bӳG:tΝK\WCV].]F)_~%&&ޞVZ%\*rɓ'aaa4M׮]lڵ$=ztxxxFz9a„8BD q###?sr?J:rH33Mϟ?sxx;w8ٷo߶mlll{"\u=)W0#u^:z_B(+y`_~[xM|!ʕ+uԡi֭[۷',˲|||ڴiC:^i,xҥiӦ4lPKW؎?zԔ̀Vǯ*s2) ! h5?m. a1Y1w-lbaa!MJJj׮];JC>zUZkך5k433#H,ˮ[.,,dK<^|QFP?z(??7ntrr۷СC* Ǐ׫WVZDqB;ɹ0㬬,kkkjD^PBv6)jOFFƛ7o6l(<$ȓ`stt7o vvv_XXV~}rU%yɓ'?H+K+++oʔ1~!rR? 1SY}- c'#OAȑ#((Hu%PuyHKZjUn&9KOOwppi#i IDATB-cD!(Oysǃ ! Ο?OHKXyBB\.ԩY@${nw ~_d]^z&L Puyȯlw#?hݻ ߿ߧO VuU+K1LS@F>}wnjER_7MVVV:uJMMvZnn0NӴJrtttuu; $Hye?+|zzz\\\fff{~7(wvv֛*KHHpvv( YT^^QFϷ环^_VX1qDR` $H ABաsG㒓SRRj5M<慃ypEvv={;iT귋]U*Ue͑V8pA_|pT)Ƙ<=mgg~MxxJ/ O2@PQh4ݛ4i˲b&222lL-9֭ۚ5k4& $HyU;&%%k{REs!j2@ôi?eӭqzx+ju:90Ea>|պu>ҥK74heb,FEEݺu+00W 2֓[q_qaǎkZ˴NIItw޹sgyyy{YΝ=<<(*O/1Ǝ>>>ڗ~o;׿~kv0 U֪UιtҫWBCC)wf׷]vb=gΜh4nnnbw=;}}};t0k,=ѣ-[~8tPVv֐CLMMĉ/_|瀁ӧO8qݽcǎB{ ==;v|!C-[6x#""ZmF_gϞ= ?8JKr޽zꙚV2{y9s._zeÆ +**0`ڵk"z.]0̱cǺufWXvv jg̘aoopeS^111͚5 wݫWxlXDDDBB>?/,,>|8&'''&&z{JHH@7o^hHN[zuǎmll*Ƹtʔm`##c“nii9`G544 sss+BA<]9 o…iiiΝ55c|>}zTT岤Ǐ`̓O%XP:wn߮ݻwKlʐwss۴i#srrFy1=z;wЭ[6l剝<ϋ8,>>>ǎk^<ϳ<3pKǟ/!6l޼y~ܹ|}e4ѣׯ׭1=ypӦjѣG7u7!##7` t>>Ǐ/Y1677:t(8@ZODyYsU R2 QB 809caw.G*}iAçMF rʜ9s222:v옛kdd-P0 q7u֨Qa>>>sMqEݭ\"Vɏ5חdسg~ƍի7o駟,Ymgg7|1qVZd+W̝; +V6~<cqLLvGKW3gu7nY@~,ZٳNNNG)ӟ$hݶm[XXV--h⋅ ٳarIqi+W,X`ӦM.z9rG-Ydݺu;wU7|sa̙CVK;0cqyZ@,@lܸqΝ/_ܹu*pݾ}7x?Ǐ?z`]&]xq$JԩS=h֭qqq{ݶm=/_CаqK1xsG1˲ih*":Tj5iii:Fhf̘1cƌɓ' sa{1d>|ʕ+{zN:m֭_}8 _l5xacn޼yғ.Kjoo^_Q;L $09U:w,/RSSY#?oo/{7١iVaea*6mrpphٲ%*OMI=|pBBѣ*O233ϝ;w50DFGG_ř62, ((h劕N>!C+UVyyy-\EQQJt:ݧ~ڿ.oǏ?2dݚ2e=c9t:NKKĄnݻK铧nnҬf͞u \>4hJ޽eU*U;99/^XxqJJCnU {!ٳ&M~+~BG]g}vҥu"277o׮@?_vm\\\xxxi=QQQZ6$$z~pww(F'Nɓ'.\277oРrqq!5 M_R Cٳɵ!4hРÇ{]d Y^zo~ uU=ڳgJ^z6mZpaݺuo˗Ye x077G}M6=z<}qY...ӧO;wzJ[TJ\|L&4hze{6lXVVV_xxx,^Wu9vŋ/^H60kkkk֭~m>}6%) ajժE6t_vssBzv# _ *CB~T*r\.'9` .y2kd}'/)[ܓ0h 074@tll\.fOK'SJ:߲`ԓ{70srh;E0118+*+޽ Ku֗/_>޹sgҤId 22Ν;1nذW& B<_z kРŋGhBN222"iiVq@itt /.СC˗/߷o8V,/MGM聲 !P(-[ԵkݻCf͖,Y" (*<=iҤ̘1׷ɒD<{1c\Brcǎz*88;ߒO Y>2;~ o9uŋ sUV͛7@hVrE(߽{7k֬CjZMV'_PP@y~Ϟ=ɓ\.СChhԩSj5MÇ߿bb"ɽ}V0t b94MשS޽{D?Qr L&(ٳgFFF\\0w>gre,XOѣGLL]:uv=`$ ʽoddTNw ׯ'`ggg40PLcgϞ +OKK#.d+Wd !C={O?:|p~~L&{MU IDAT7nLtF>|XXXxɓ''***99gϞQŲw}/OBFqlLw^'''R 2d+++wwwOOOr XZZܹ355uƌž)ěL&KII),,ҥ ʌ#sr={~(}9)++ӧJrРAESŮ#?ۄPP)a4 ;g=!c_re@@@Lr;;'.^8**󡡡jzСb% 45jԄ NصkW={kVqe`ٲe[nmРGBCCKbիCBB\\\ϟ>1b=MPU*Տ?8`DFF z9eʔ}IǏhтM؈O4iѢE۶m;cTTɓCGaaaddJիaÆYZZC ۷m6cc^~#q4swcܡC3fxzzFѣGzzzXXY˞9swӦMM6NV c1@i4u111?Ν;]Oh5$#GUO%^'_uyrJ:uhuV ˲6mde,&&&^tiڴie0j 6455%w2+Yl_zejjJf@BW\Xm|0c,k;[6&%%k׮j=z@Zڵk͚5L ̌L,nݺ0-<{FD*~G>}zƍNNN}:tpSGAAǏիWV?L[[[YUE(yI^ZXXyA -L> 'i=o޼iذT OQC/Tdy䉡o';;[Vb^V *SZ(JoM2fggggg7lؐJ㸜kkkW^!''G?_999-YDo5V$^ Wlْ㸗/_;bSSSwڕ400U'C;%%A666 .b"]vuudtn?~w̘1t {D/TԗMӴUNRSS]+̅4R]]]N'A j8>n"F?xOOO̬9yEQzSe dcGP>y9!LT*\O $H<*C~Y#CXEUE|& $Hyȯ?+200[n4 !$eRI A D%?iI ! $HPݟ? A $HP} $H A * )w A $HPYH'=ߛTp? Ϝ9S`4oݺ+~}JjٲK A 2gKhѢEӦM jC$tdcAAի5j4|qի_~eŊ'N466 A j0Ʋ~䔔ZMS4yqZbb <~OJc;;㞽CƴR*ׯR*o\ZM8 4/J1i;;߿?00PojƆKKp!"v͚㸣GtըsR<|բe2TUy91/ݶQGoT~!\P($ӫ %Fs޽I&,!a"##Ƅ2`nݺYFјJjrI AQcRRRQQQ`Y,U4B@A&1;9L̿m[6 ']v+ju:90Ea>|պu//ow533ܹESɓn1vtt)~о~]߹]]]˴wqHHHZVZEuΥK^z*OMIݽ{70ڵ뉏?sFqss 044/((ؽ{w~Zv箝)ggk׮5nXZYY_uE''Ν;ZKIMiؠa޽իG,Ğhgggr˱gN9y򤭭{ǎ+׷Cfͪd}>|!QF:tO<9p@zzK޽4h ֓yرC).tyʕ6l011 :eje%%%%%?`8~M˖-?=/Մ<ؘܪMq#ƘeY2(AVkgg7{lRy ccrz?_Ă!.< O9wW\2c㡐Yц2e?)Ę0aF۷sƍfoo_ZlСyyyV|ʕ++3ȝ9QƘ0~5޾}O?dʕk֬پ}{:u@6iҤnݺo>W_}':txM?䓆 >|#G; RRRHq0a'|ұCG V]p12 wΝ)׮] 1bD֭oܸann,y.44… 733#Irrݱ۴m +igDDw֭R1ܾb='n۶ {zw5`vwwo֬DzG%U;<,v ÔĠi:??_R!8c9m۶ڱ}$Oy8?ɋ {E,cFB5iE$kމh1c(ENVMc̰;wDFFΟ?_OԩSgϞ}}XX[y|U( `ч]DϡW~ .LKK;wi-ۯӣ@.%%%=~ceIi E%''!D@Ν۷kwݒ26mHA_&Cn~ڰڿseeQرcm+ޘy@>}tŽ!0aÆ͛7ϝ;=zqu0ѣGB11SN Ʀ ?cᇨ(Nm4R+W̙ѱc\###mr/ nݺ5j(bܹsnhѢ_www+++b~ 6늊N!77wK,Yz-Z,_ޞ۶msrrJLLܹ#,\TTD+766P՗.]ѣ0罹֫WWi56>|8((Hl8vȑ͛7eY… ޽+ˇ>}~ {0t:wIsgeύ7lr@ 1bDKӧOI{ٴi 6ܸqcn2119t萕%\qyaicc_: .0 3gΜ!C.66رc0q-Z<|w&'zgϪUvAtHHΝ;#""ݻm6UV#GVW]Rw[81s|8!!}Q'sΝ={v5 @11ѱEEEAAA}wq&u t: Zb'OzȐ rժU^^^ .lѼ|TTN駟ߥKǏ yLbooO!9vZZZ&&&t 0_2OdR 0 '?wo߾ݻ+ EEE:/..gϞ3f>3@HsO?}}v]T1:2<<|W\5k9OziNN7o<؃18at~iN,r߽-/0<ϑacCCCӧO_իW{t?o!B(]ބ """TH{B "K޽{Q𿵵ҥK yfK+ a,-[?nhh(%~ï^e?kVѳc/^={8ӧ99jR_r /Μ93j}թS͛,-7oѡOltbm _xPmE%7yZY!e?C %$w(.H!hPV 'zhŠ;w{-Z1ٳ/v5Uf->|M/y/Z#a@( a@AyÏY|Bo~ЬYyOJJJNNNJJtΝ qڵk{T\.X^Ѽ}6''ݻi҈r 6o1uM6ӛ1 7n!^4o޼SN ӫ#rJ ތAE;Hi=B ۹sW֨C^D!}aΝ:u9s2_enwu>;{|;wP(Iev8E֭[;::?0σeff{aÆ}!!oo{Zn~=7" ӧO駟Ν;뛘Xr?JWXX8w\'''ZOVVVQQKwؑ?Ygy{{1믕Jo}[~LFG[jeΞȨ[\./D7ovwoOIqe֬IMm]t1c˗/{ˉ'SJ\PD/_ H*кu'OԮ]/ť5RN+4%(0.VK_ׯ_/YUzվ}ɓ''$$ZŎ%;w_^Ϙ99rd||ѣG{W_a`ƌ_~%y8>>͛{ee/]VX1s.]5j <;?B UVVV^pEa;B(++`ccOQ{љJx"..M68֭[7jhRw-X^ly56o|…Ç;88J) ,>lccp°0<X"_'N1f̘I&رCԀ+>JiڴiRRjU (n?,y!Y+JLP( \.#˱_)xؒ)Y|-Z7M,˺_vMݻw[F͛7_rezz:0xm;q1Q`iV2Z)rYРIylm:hdc\!c2Z!c2FPrkpѣ---;w.#ZjСC/\cÇgddWq#[|=ҫW/xEt?\??~ٳXdɒiӦd2Y񣅖Pfs|rɽ{666Vj,/}~~>>?H]v988XZZ,6o|ƍS4Mw޽W^Aҋ/D)bY633!h666^;w~ʕ+ ÈJ:t萖wqicvSp3rH###?99,>{ʕ+;vdڵk?qyq%Ko3fjwJ… xxx0 1wڅ1\(WMhh\.?~NNBٳg4M*ΖֳlOFׯqFjzCCCCCCCBBAAA5j5OZꫯJ/碩4M_r!,~BP*Ba|q`sN~~~[n-&Vݞ={ʬ:1_fizӦMVΝ;ŭ^zcǎHGCD=r9ʋCʎ!iii:__(a SSSL >i90l18X|ӀZh1f|E#G5JZJT߽{W)~q}ɕrV2 S7/@|9gMrV)c2V!c,3 RP8q"!!!C`sPF׷k7555 , almm!NNNSop DFFtuу z>;: @s'%%egg/YaÆǏ7޵kƍ_Հ;'O6)|M^7nf?_'Lp߼yk׮~mVV˲G={vvX8p7|ӻwo___ooe޽{GT{?zFfffEvvvb;JS]Bxf˵Z%N/*%+GWړsqW;VV͚5/^⒗tzF# -qj>|Xfwa= P&999 #Do~@ T !h~@ -S@ BՁĎ@ BbG@ BE!'}̞=aKh R=moo?pUVݻ7,,'~DNDNDn"rcG:BBB?sR7,xJrB/>{>WP%B,wdz@.Qh4w3f q^6t0 mvҥDT)@ >qn"rcǛ7o gev^zy!!(d";yD-o#kcG~["GZVYXXzBx񢓓SΟA8NJcOrcGfffEY[[w¢<߼y~A81ʟ /KM2Ө3 !^xri:֞:ӯT#-,qFFF(*#3s޽؆ Z[[ |{s璒ڵkתU+sss{mssVZ5nܘ!PѾzUӵ˗===˴ 8""lɒ%X>w\FF&رC6iD̙3'Nh4^^^=zP*R;v4׫kya;;;oowڵy'OvaeeյkWQ7mڪU+q'N8zh}||Zh jbl"sc=wݵk󞞞AAAr<_~}ϳRyp玝׮]lҤIvJٺug}VV-@tttttK*Rϗ/_1b0&&&ǎnݺsNhHT~֭˗/LML[n]zݯԣG2VݺmkpP;w޽!r" ʋCy?Brcc2cGq'tU̟?_S(W._161A_FQ:A( !fc׬CpfmP ^z d82j(F矟:u믿~𡃃Cd{ѣǒ%KΟ?xb xBX"=ŋgϞdK.ݸqcZN:NNN }Gv~211ٿGiѢ`ۤVZ/_޵kWӦ~y//;wԨQĴnW='Mdllloo߿1ŋۧO/bŊAXܹso>ܶM[եFեKV;qD?){e__z_l(ǙdĻX7jԨ/y^zϻw~֭f͚ϟOQW_}Ui46mZDDNjѢ]yVw{ϞI&ׯ(ۣGhjվ߿uR=}ݢE AE1;t0c P|ΣGEG,;zl9ݺuuСCG9uTz^W.5<˟?o~3f&&&W^ݶm_|QuDNoE^IqB)3:!^P O5#zAրi://ORAi3j߾ԩS}||6l8t PJK20䅜 !@pplqGb(!$ P w3gNBOާOiӦN%0̟?NN!CA]1Əo??tX-::P^y*6޳{XesssW%?ztћ7oJDYXXl߾wXo>bV7nܐ!SN-]2۫"KΝw6w3gH/ׯsRE=nݚ?~E%nwp  9Be`eetRŋ/^wYx%x_=Ts ) B Qby:( ^@H@K *IZjlڴ}U+ժUۼy3ʓȮ* ɊF u$˗/׭[β2FK.fͺuV͚5BF1JB\W^mٲmÉB˖-۰aN 7n@5m||ʕ+LLL,'(E%}?~<… SLIKKkѢENNB| ƆL:5ʕ oVVV2\j4cccLS:ѣBptt7n\NN_|~zm=V_K^yqHהJeAA~ٳ/ؑa ТUJIMٴiš4u<#tz@ɓ'jZjӧO믿vtt̟{cLrA!XT9XW?y;O@XԀFxhE_?# 99G!99ـS;1y U:v,9 hkp-G'Gܬzqz^_A7qttu'Mj%ȧOΚ5yG?Bqc9Ϟ=+,,ѣGbbˌ3p~ϯiwNLz~۹s^/ӟ͛=WCC{[JJʉ'Z-񸼥[X~WƯXk׮2 #曒GH2={[TuZ^W՗.]dQQQ۵ԭOBCCTRIKOׯСC x7qȑ[ZÇ:NV?y=UBDZ\-SIqH{A,!)R)Bhj1Lq1)1i֭oZC\?Tӳg϶i&=#33p_9EA ,G!@ "x Ьn>|0((hժUx ^x!]T*srr ȫ88. (oܱ7ָ:Jӳ6ŋ5+YNy[n޼y7nI 66v۶m7l`YpzFŋ۷o?|/ǝڸqqt 7oҬY3^!]?ϔ}ׯ߲eKB_PPp} >NKRYVl޼YfzBuAl^n0=BH6l֭6deehԩSƍ2eJzFzO=:vtZ,ʬAծ]{ҤIׯ]Bhllbmcm2~аaCgg{v"M?aիW:u*K`||RAAA}5kVZ~.ѭׯM6,zxx^V u)S?vյ$$$$|5*x+VoE'խT*---)f/_VTլnٳzgӳ4q\\\N5,XccEիWTe(Ҳ ii{2 3j(cqF~9*yUW^R5BA404M;"pB"sָ$_kkݺĉSSS;t萚:zh*(HT/C>666@%AQnG`i9KS(!BW@d+K$!268V [z̙ӧO2T^ŋxHy*8!DA(?g]jԨt ognР~Ec_usΕرc~pn۶mŊ۷o oԨQFGnĸ8u#F?~e˖XvB BSRRf͚u]GG"}TQU%|B:uHwaHN@niiyAܹsNNN,,,4iҭ{7@PPЊ+bcc###KٰaVHJJ+ 9ZjժUKqW4Oŋܿ^z˴_N:=YfhvرiӦcǎ oK/_v3g^ cbbfΜYF BM^[/]et/XBؿ:u"##۷oSgg^pƍB ݻ++v'33sРA75k;!%իW?|YΞ='6::_nݏ?عsgiat2B333d;߿K|I? 0PDNC#˲,2 #F 8ÖbP\ҢѱGqqq~-{iFx! Ά4~->GC!1UD.g)_ 琟7R@ q /)Vnh8--]v3f(/p|gϟqƘ1c ȫ8x} BQ EAA(=^ٝ={V- !<~pވtbziFqqq .ܳg8N/M׮] 7 B(˭,XЫW6mXի7giz`"##ׯԩOOOq%JΝ+ѣ8(7Xح[nݺ!VXammstww?rXժU`5;ȑ# Z~yf(jջw>v옉I~pwQ4qS|>X]!!!-9sfHH(Y[nzzz֯_8P%5dȐ>}{pl&m[~z iJCFF}hhE=njذ!T.ϝ;G={5h"""ƌ3i$|R'̦NڡCiӦ-\t۷oݺ/__|W.o'/DB??XlB(;;LZHYUBDZʋCʝV*2LP( \.+F.c %R%S <99ٳ-ZnYuwwv w^n?7orta qGc\~{+XZ!,diVh\4hRa^67[4X!WV3,g=zhKKΝ;㈫V={6??$®],--Ke7o>pqƩjwޫW/D)bY633!h666^;w~ʕ+ ÈJ:t萖wqicvSp3rH###?99,>{ʕ+;vdڵk?qyq%3 3e\>qDe91cƙ3gzr ޠ}v mb9.oVV7=z'`㸟~X3uss9ydXX]>>~~~L@@˖-WX5߹sG۷nZAiL&ŀtɒ%w+(ӧO Epp0EQqi4iՕYՄ3fjaڵkWPP8p`ĈϞ=Nԥ+q@D^=!LKKt&&&%BZ ^|Z ?ig:I@˗/_ڦM1c0x#FxxxtaԨQXjժ1c9991 Qz/RYx` n^ޟ9aq?k q @J#Wj'Ndee?CCCݻ={6i$@5֯_߮];77G߿Ԁò,+( ޹sg``(>RJ:-1~aaaxpB|xz>}ڠA_U&N0 Bhڴi'N3f#""ju^,-- ;e=.c„ ӧO_xq=vxQF7.,,M688ڵj<{yy-X`uֹ?x`#F{zzFEEEDD%''O>ߪߏׯ]x,R/__lٲ/677ر#BXPPPXXꉡaDDD߾}7{쀀P1,((VT;vęyy%''N ; ˲?:t(M666˗/wssfL֧Ojժu6ֆjF d&000&&^zgի8#vw+WԩѥKA J+vAAAχ9211Yp;Lu떛m۶C?ٳ~z[[[ccٳg/*ٳggpc=͛7:tĉ6mRVNNN:t(˞W@/h ꗚ?bbii)yf&M (V}g*O^uj.]g>epxT㸕+W6 GK ?VZvvvzA^^f?wVVV8Y%G~~GjԨaff_55Ёm۶۷SSSJȕJVJ؈))@A! LL^Wad@^"y;;_|~,6lذa^)rHGG&wqq)V({vvv2U<~Ν!C~^r,!fffnXy>99S vvRP0PO$ 7W$;cMU#J۷oK,%qn"2o Accc?獡(.((յPY||t'gUSN+D>\VOB!0 Yi~@ TqtLƎxE>S{~S*8-!gd2CBy@Iv!G B`V!G ۂ*q$@ P&%@ e!@ $v$@ T;@ * yٳMLL vy>//oҤIDݻ9992YJ޽@ >JHРAudwNug~~~TTTZOnkk+322~E=ݘG @b{{5kTw]NNZƟy߷oAė!\j޽{(@ wY\.grΝ;ڵ8N(FIڶm{mF,$!Ļ<]Vtzy^/8x@98KKKZ;@ »]ǎzRXXR ǽu].͋8###x9.-=}޽4y8NT!(;v>Zj:uRKAx믥xAy!S^=z7t}Z\ٳggΜq:thtttddddd (}GFFtqժUW^/%((ٳgXxܹVZ+++ ###W^=~x77\իW7nյK.W^|899yǎs1bӦMjz6t?IDAT={N8ڵk4M`pB''G8tzƍ .1c_բE ''˗nݺx%sN֭WW;wW:u?SL)0%%ELIIYhQffmlll܇jaaqڵCN< Mֱc߿  Q^s`͚5}y[gd2BF qdBH @1z„ իW7n\͞=CׯQƗ_~9dWWF 3wDDD;v -wwM{ڵkwڵkG:ucǎiii7V{رgϞըQCz).5^1>R& l)!( R())  @1;K;vL:jh+++B(~(JnRLMMHIIy1cƌs!ihkk[fFO>ge֭ö;w0==ť3d2\^<5f̘I&>|b A|}}(++K5k,Zݻ#G|+E0 aH; 3|Z ?eJ޽˲Mӱ>gdx4 `ƍb𚚚0h QP\oa0q6mHNNNv:w\DDh@~X:un ~BCΝ;{xxԪUӦM{\6lg3g7o޼yfOOOooW*1bDvZhQ^qկ<{ȑ#ϟwwwoժUݺu---wލ+Wƍ;tФI zjRۨQ#0qԨQ>>>VVV8exxӧ+vذa;wuԩU}v@_KYmٲR|@THBggZlxҥK*J) @ |Aӥ,L:NӉ2 ccccjjZ^=|:CQ(J{# @ ||Xb9EQ7@ E@ @@IENDB`workbench-1.1.1/src/Resources/HelpFiles/Overlay_Toolbox/Vol-Surf_Outline/Vol-Surf_Outline.html000066400000000000000000000045631255417355300324770ustar00rootroot00000000000000 Vol/Surf Outline Vol/Surf Outline
The Volume Surface Outline tab (Vol/Surf Outline for short) in the
Overlay Toolbox allows one to set parameters for viewing brain surface outlines on volume slices in the Volume or All (Whole Brain) Viewing Tabs (inactive in all other Views). 

This is useful for comparing Freesurfer, or otherwise reconstructed, surfaces with volume slices, as a check of the quality of the surface.  It is also useful for viewing Connectivity data (dense connectivity and BOLD dense data series files) on both the cortical ribbon and in subcortical voxels.
  • On checkbox toggles on/off a brain surface outlined on a volume slice view.
  • Color Source sets the coloring of the outline as provided by either (A) the displayed overlays from another Tab, or (B) from a list of default solid colors.
  • Thickness sets the width of the lines.
  • File selects which surface will be used for the outline display.

A


B

workbench-1.1.1/src/Resources/HelpFiles/Overlay_Toolbox/Vol-Surf_Outline/Vol-Surf_Outline.png000066400000000000000000002425341255417355300323210ustar00rootroot00000000000000PNG  IHDRMK ciCCPICC ProfileHPY_Das@ 20q`Beqր (5 X wEA9&Tnz_ wrDXTa8ۍ=<k7M wwшnqV4.Pq4n*g\8aЗ.B0M,1Op|HgdrH̼B@9DaƩfa?SϸeVxA(_nVjJrH)3FQoE)A ~0,dq ^' D>i }8A,oφF,-cOxY^+y@%0HA "=~M˻et l!Ԙaina?f;{ :]Y @ <"f3<@ЀPZ@ {i + (pA"HbցM `/(ap @38 .: #A8Q!%Hҁ K 9C? EAB:h TCeP%T BP?tƠ'a f0f~p(ë8Uq _o<(@(wT *6 P%*T= ՍQX4@>04].CC7;7C W 18`ؘH ǔ`1.mcvXl6 [=mcI39q\:.ww7}xK>/oƗkg) A@$ل#6 a(K#:CIMRb=D"iIKIR.tt4DH#1 vQr; BѥR)%c)['Q\Ij@4AZG%B:GD q GfLL̠̤,UB6P6UHVNNWS''wX0EբS-#. KӣiIB Z/mB^NZ>\~|9y Eץ)S;O ,xm  )*+(6(VPTJVڥԬHlT9Kr""EEN-U9ң2*RݯzIu\檖Gژ:UY]GsSza54iZ Z{:&յhӮӾCa$yUYwTOQWPZJրilp61L4,7a c퍅Uƃ&dIIɐ)tiK3mh]ff_mS̏?lfВkYnyˊbeѪꕵuA6T%6[m:lيmmb*4fycfG[tS88&;:.[a'M'SęEÅRU˕ZeJbgt3w5wwp_( ,|^3Ves5 _;~d2'b%%< 4@vGAzA~Y]|`u!Ԑ!!BBw> Q!4\y=J9J\lﲑ;Y~uVJ< Tq&qq\w> +oo,)8YSBq(߉?X8.p ^%$Jz|4y:%"!*& ;WZ_d$IV;޻zB'NҖӐ`ӓ]Psfy懬kddfo~ZZڎu6ZZ_cƼ#޹67%ouoDliS||qVǭG/wն۾ ~.]'laNN;\v+-)޽dwƞ=o{ĺ>⾌}RҖw\XvܭBb[]R=TxӏVzW6UVrXHV.n|槿+(B3y pHYs   IDATxw\gHQ#(*ذwK4j,Qc5&F#c+*VPP)Wvc$=xvfvfwoog,~z@!BwCyu@!B$u@!B$yu!B!jsB!B>B!zTr='99… '''oooww2yB!BV08p>xgY^zݺu֭[5eL^B!-Lzz͛7\."t:˲* ҵkJ z!B!Tݷv֭CD"cc=z\rOr|o߾C5hРaÆJ1kffVnjaaa5yByyv=nܸZs]|9''K.LBv!355޽Q233.] -!dyyyJ祝v?~|߾}UE!􆋏:uR?C_ 6;_ׯǏy{306ma+W$''RPN<߷![d\.HM666݋H$r<99ҡ h^g``ܹsSΝ{С"z-Z(88Ξ=Gܹs ""hN4/B7ٶmbqPP݁nݺnݺ7nT*&M/ܹ~8y… MLLޣGꫯ6lo߾vў!$++뫯ͥ?nnnk׮)S|G͚53;!D;r̙ j;; :::qFhѢ7|#%ԩSG"(JBHBlٲn:Xn:ƌ-nB@  BB? zB" )r:;;>\S+͋BMv֭֭[v0ڴi2uԡC ͛7q7{HBѹsN:✜k׮믶7n >>iii}>>>G8յgg@[jUE!&suu]v- HLLL=!={ܵkݻwCBBD"Qvv\.#uҥ~01-Z4ydwwBH9{={o^Ǣ}kR~DDZ,[ZZ*HhP,j\XܩSN:Ϟ= ljD'OB]~zAAm=*Ș1c:vxBT**MKK߫iii^M^Bo>}^ѣAAApA<ߥK%Kܼysʕ<yyyдiSf[Ȏ9ңGΝ;]Vҩ{7nliiYǢf͚奧ڞ8q" @&u>STɓ:us}kCqq1}BxꫯFqܤIn*eB<ԦMCbJBM6NNNOn󆇇k4XlllL,[}^Bo25k̜93""a~‚bbbҪUBŏ?8qDeLPn ohh_~y:ߝ;wի?sLLLLnn.988ҲumڴɋB VVV'#+J}nbYyTJ@ <=zA |aaR4 j4VKOսp!!vL|||RSS=b8{zx!;2yB.\T*Ǐ+B]\vu!\T+B] B/e+WdffTVzZGޫ֊!GN:t)aE>ʮ$! Pm`^wBo7e:ԡC x^Yrz"wR4h ̫WHBZsFB/!e'[7݌Ϙ5)vuB ^֥KW8yzerg1oHQWy[+%Rkk #/)[xǃL4ܵ͜=uB/ 9njOp0 e{3x{'W*]"_@'<-K{yo_yOBUU!RR8BޖGtO^~H_Jc+Zb+CNcE"ѫ(Yy~F"z:+_ ǃ%6f:Fq۲AD>|u@JKK{ztx @bbbͤ~A^Wy5ϒ{޽<\n4g[N+W\re/_=Nx#]&zmcܹѣS3SI?k\FF֕yqnn{K#,WgXH^|[04wrsv4WA^Nw^a޾̔vƱ;ysjإX"tF^\8#\(+~v\S޺j߮o˚ y߽v;CH4BBxB@ GX^ ܻwȨBFwDm۶}u@޽u'b 董k>} 3 @}y“f9sEOOO+++Ju#ƣG:t(kx)555H$jϟ;oog/KjTRߟߨQ# ĄDTPUIbjjJLLLkUi޸qaÆ35:0T@$ ^ZߢY?l:\XW֏i_Ov&ˑlYFi-kWSצgvçLskܴYg?rl>zr%BRoݨgdda9wd6=O);  EFGF h_ۊzy'Ajd2}kQ.]u-g5Iy޽{;;;+܇EEDD <^z JG¥U=sLaaar9o깎=T*ik׮ӓN-pK޳|?Cь1bcǎ;v 0ロgC]zM222:tvZyAO ZN#/^ttttppзB8BݻwϞ9SR5kbccbq&Mh0%99)9~~~7n aÆ,m۶I͚5;sJm!.]JHH`'11cǎ{n+jXFsbqƪSvlh ]v ÇqkkR?nddء=Q||k׊e2YHHHzz222RiӶÇ==<"oڵk:ue۷=K.ьwi۶۷ EVh+Μ9#}}}_*-fyl P1:yF|H&į̪T˧\vTZXu4۠Z^_|ۅЈP"s"pTׁm'_5@Fvzu=S<١M݉`> +[}A.rtoe֝eeܝIEGLu3F6i޶s3G$Rϻ/eW|"g7εq'wB&U \t*^~mK^x㈎%E@* A,z=GVW,e !*0㸡C\ȑ#SN۷o 𨨨`XzJڴi͛###Zm>}uޠAϋ[~oߞ7oɓ'mll>|Xn^U)Ujnnn&fYܹs- Ξ=,Yussk߾{]\\D"QQqqff&0:t055޴iS@@@ݺu5Z.((()-Hř߿ٳb7^ھ}{;;&+&>x ![np٨(,,sΝ/_|GEE 4'N۷/$%%9sCшbwwbJUXXhdd񼱱q||?$'' X\\\\XXRe|ɹs4i" Zm|||^4<Sv&0@c`;gܽ%' '.ڦOx7MSx4D8!HzOͼSFԢf2XLqrkzvy?kյ{3KK ݗYgejz|0~sm+-@#mT}:߽r 6=rlV- u_PZ;~.ꉝ+|1iIڝ]DryPG&!w&F+**d`p[T JJJJRRRvm۶ବǏϛ76nj3::hŊt*J_FrttfjS `Tjaaq|+++KKK:֖eقD~!fEߩ*Ji+jRlRRmcFbbbjJ'w>\\\P(^z[GGX}񢢢R++R##7oZYY5iⲓ@(V\>nnnΝkѢ0IIISbaFsΥ޺u¢&0 F Tra`ֽ3S=[u0MD]uL( NBielng8s5Swu2 C!g%ȌN[-0 %ri]ɳ:>V81B)ZaXUi1!)@RnfIcXe^$LC$DuΕ!,}d'y:V O10@(GGG}ݺu~-}hkԴ d2( ж?>// 0R:O3WC/MYi5a+j>_RRR@i9VVV4hjj09ϕi9tðbOOrd6ܟΧOP( 6, f(H\]]SRRn޼ٴiӚ, D B.CW~H8ëMwSS(}`edTIs jL*B2N{īuD/y蝫χ}DBahDP&D#3cÅ& Y9_;<橞df:lI>H%eF$ȤB#XH'T"IE"a@ک!M#L*,;W2=r[&$BD(2 ey=OС>tOkD( @*D󜺸P,d! BjkXyY XXF"0%B;'>k*C$ <>> BݻgnnT*֭dcc|RRhŕ^gsss{{8Ribb²,ƍGGGߺuUBrYnnn?===w- {_PV.ũS7n\Z)T$ \͇v=mĬi@ wp'wwv3ұ2X'D,KPU\۰/-w7ndl,ʺ_&ۻx )`w@&%bH(`ê2 # +V?ЧM#6kojaܰ457/%bQFrQ= rX&ձ򒧳È:S1#;8y6pl26jU ^fd8 x9" E2\. ^ R\.=B7b1OoʪxhN?[[[# _~ٳ.]ڿݻO<СC666C YhQXX?~xujC5k-'ܹsgر0c a*>;TJH 33=ztݺu) -̙3/_vwwoР'ox~;w\BB!Ch!bE OOϒ4ss֭[߻wO*֤5oX,>s̅ h?:z IDATnݺm۶ !!!^~=!"$$Dit?/48!sǎ۰a)˲!!!vvv%%%Ǐ`//AAA w7bKןN5H//SNyyyɫjiȤbL OA!O_ UiÞa;W :HF#.M&|&'9}DŽ\$R$"TxYnyq,ٲtjQAm}FwtE{ؾzN\ÄnRH&EBaR>}?[-)36qhiVjhg& w #RYOfEZÞfl[S|ywI2X$2 CH$U2I 7;fFFumUœmmCx@٠҄'"N@dZ^n,FOrTʜ?ֶwn P9V$0EVsgllLͭ[.ܽ{ի2zeeeTζ7nhԨQ Sɓ'J&t:!>RO*Nt(ݹscǎIhlqqVc7RմJeYe-:vꕔa0oe~d!=04 ?yfz$?סg +-^>j5iʺL-T&ri*ŖX*1xvGNGZmՙ =q\ff&ވzYrk4XCRcY)_588 UBsR+5z.Ho«?J(ּϐyu{{'O$$$x{{TkffVis|JR q^t}F^JJ0\0l&aLUtYZJȥ0kPb.ET4ش\r  }NLjD"_A\?J9::>\?+%"Ricc#@Zooƍ׼cjƚBPJPm:wՔGp@YH}Q.^uB~B襈DׯiF J~O^{{DRRfmll- WY̕ >%jhh2[Kj8PBUw%" nݺկ_-:Fd{ޫ:?5^<Z6Kx|#aPqB/۷Çt<]#ۄV^=z8::b!8A!B;h?O B!5A!BkB!zaB!л"ҿo۶m6!B!2 <{ͅB!B .?9!Bw sB!B B!5A!BkB!z׼e_wb:Ȉ!pF!JTdBHTTT\\N ůÇggg;88kԴĹ111/6;NwcǎDP;w,))ׯߠAO^nÇmll+-;Bt۶mٳBH~NAAȑ#  ~UVmذֶv+7jԨ۷o0#--m۶mmڴ酅=~gϞW^}'9ӹsg *}}} z.]R5kF]lY6m^oBR]?KKK;u޵kӧoذv+`{8qR$\Ҏ=`mmf&oYf]xqŊͲf͚W\)B/+((H\*Bo*),,ܹ1Tum_f͒%K|||tb5(((ذaÔ)S ;90 兇{xxxzzN0b /_ҥs-֬YC;H-Z8uTl^*B=Z5dȐHy֭Æ iӦ5hРvږ-[:884mڔ577w 4hٲ3g^l"]xxN?M4ɩC={cǎ>BȲe˼ݿ{,..OݷmF/^۷o |||rss_uB]B+ #~۷u:]@HJJe˖l۶j/V;w<[Աcfee_{7n\>ݻw\2o޼Emݺt:ݝ;wfϞ=iҤ~0ɓ'Oz`Db}TMZb@ ??ԊעuGk޺u;HԿ疖^\\| '0,,,++k˖-svvUVM2O>1cT*f"лo߾ϩT;azTy=ư466\\\ԩTE$''?o޼ ^)=鏭%?Vϟ?_8 s+ˆ;;;>}zS-[a^/Z؊L6rHNW}! Hk׮ ɓ'Ӕ;wܽ{͛7nXۭAQ;azTs9|i$66vѢEy:ujRڴiӪUhwCݹs>yM6}IThh'OvAIJJ:~x^cJJٳghHԠAjhnݪLaÆ;wnǎ:.///77ٹ]v˗/JKK_vy'۴inݺk׮g$Rcǎ7oN0̍7vq{^2ibB˗}}}EJ9yyy{IJJjְSpWU/zxxԤZٗ0}ttɓ' r.\PXXةs' cggg3vZvZv޽nnnϭgiiiddd4^R\cBP(˲lҞUy666>>>e[iffvqc"ꎙJ}WY!O:+Bt:=]jxB\r%??bccsrrBBBh=zH%8Z OhC-[:uť4+ktÀAaHB٣#ݬuJRP.t]^C&MRT]t9uԄ RSS*&e. I%^/?rٳ:v}v}|w wagke˖9s1BծXm۶G={>-o۵kWDDǏ4iҧO ݢD \U}h|n DaŒ[:tC{G7((~Yn)))4^zL4QFJROvVOnogˬSNݺuV疳e???//+դ81%ꎙEV@7À&WYoH(1t 9Zssfͺx'i/4= <_.n< @貪4..b=^/'W*'Oζ ;*i}V^=uGsBs=zرcOsq>>>?X|Dv7K[q\V1X,NLLHK`~VqE`1cǀn_eGմiSBϔ.;99ݾ~.iS}ܹӪUݻwӪ~[Κ5Ǚ^r|`kcfB|J0rHTzqP/dذaӦM3=Y>|ضmB###T*e9/7vѣifϞ{,--i+]Zmaaa=.]?_|9((hųf͊jڴŋh?sO?mذA,DŽVVhGANw)'?ǏǏOQAAAhhgff֩Sgʕ͛7ydFJ+O?Z… ۷e .̚5͛7_lY:u|ҥK]P(8`iiYTT4k֬3gΰ,;k֬~[,GFF4 IDAT1cǎmhZJ%H?ѿa.z rAJJJJr> w^˄ Zj5l04Z*pVX~EK6 ݾmF}EBg)Ƌb`x62ݐJK8BYCJ]rܹs/@ZZO;;jo~2t8N*su:]@V_}[7jeYt:enk׮aYm5铓bccƌ]Mt:uɓ'WXɲZf˖͑7oh4 ޭKgzcZ- 4hǎ/+~VZ-X7! eht޽K9aaa'Nx׮^X-??>E\\l`P {zz֭k9G .2jUjjӧO>eʔ={2t׮]#Gܱc1cT*UjjjII e#GFDD,]ö́Nj+~jٲշ.j҄U>|xԴsȘUZRʲڭ[͞={̙ *((P(SJKKFҹsgVVSSS?O?8Ӊ}(ѰaÖ/_޲ԩ֯_?qDaϛ߭{gdˍZvӦMNRKu:CعsСC !Ǎ?{NcYg}'tomA3i=zڵkذa 0Ǘj{ >}a@IIIjjJR+!鹹ZVKW˲ݺmKE0@0L/wg7ӻ쯲xHHH#FRڵkŒK8y\9 L8lbŊ'U'@ CF@y0bP,}{RSS{zmVZPP`T\./,,&}j@Us*n~yyyl=)((jg _C`Ϟ=۶m۸aX,>J*((ϿqFTZ~[ySN]vm4y֭[,03i9M4i׮T&mղUii[sfffu,͛7nݚղ xzz>}سg@ ?~̨UV~~~Csܸq[n+VF6?uԩSg͚(žVsaƍcÝ}||SFlҮ]6mʥQF׻9[(-hdw]VJUn6mjڴQ ]/.ʿgf̘ѧOF7n|uؽgKwqU]FHDEcA]"v%&Q,$DQ56Ԁ{ `jr/XWZ|03gΜ=gVNprVJg}֩cG[[[He$˭J;~1cƸZXWge)Oy_{1s)Je9cY+YFZ|^Hq!Bpȑ.B!$NF^^^W\vRѱ}ǏOMKmԨs_yUX`X\[,c$[5 G{}!!!ptt|ϼ<ӷ*Cc $Tn%:gϝupc͚5k&YQم:nRݺu1b۷\2..N\SM-[l&N8u4(*w 7n\ppѣGâ4R&??_1!y楦:;;cJ Xj yB7. -|$BVVVx뭷d0[+WLHH !=$E;Ɔ ˲666Dz]۷/BBbZYYį4fNG1Ç & ڵt!Rn"ѣG3f 8#,Y2}w}w̘1'OV(!!!~~~ͫ_~ФI//PPP92%..nݺu_~\{W[g}֫WO?tD/5N V_K+֭[۷W,^cKQQQ+V _x1qOD=ccc? 6|䴛z r6mڤj۴iSފX<8?~͚5E킲H)OySk/f.ϑd2qF;v,Y[ϲ,n޼ioo/ 0Bɓ&MH1J9Hbܽ@?'0YtAr)ܹsLLLUIh?\2iҤj:2HK^c==='DE!APqj*𰷷?uOJ"=jddDB|ҭO355ݲeX^ZѾ}/^/PMymz2wB) [[E :cǎ*gg~믿f&22iӦ'N(..'cIy""dz===9B t* (((((crJ[[[RÇE{Z[[R;W5 x[/b#Q8M9B)tE^(_-[;v?L6bsQsgggeď_իe˖3gDDD8;;1BPL:5<<|ĉӧO'ǣQ۶mݛطo_ī֬Y#m(w̘1Kxb[IR;Ʃo~h^1hРmK?~[n23gOǏf1t>]vرG;wN<9Z1GHcc6mڄN<åsVt+WRʍ#hѢR$89T^"U_p,kllx "9ڟ'lٲeժU""W$%%qw},˶ifРA8p݀%`mmm۶iӦv8^}=Rٯ_?axv-j( Yf)iӦ~bTGE'8`0̝;711 Ҁyٳgɒ%7n$W)Oy_{1s9LNgbbR." gϞ=zG _ OY%cR(KZ`yvwhii9ucǎ7.668GGEݺuƍd!zsss5uWeJ7G "I޽]tXx-[&Mꫯ6nHJC٘o1c<~xɟ~)|={$ +V92$$dϞ=DLV~͛7O4iʕFFFdΔ)Sjȑ#9߸q}l߾}|ѤI*G33qƍ?Ν;lY%uc2ETEi4X nݺ%K̙?GFF~7U.SڋȲKy o}-[s!p…?bZy󦧧)8+mp^V[XX0 S\\222jz$77Ȉ|<ﻺU#Kp6raVS?^ \E^^Ж4\?o 9Hzj֭_%XZ:*@՞={|=++'^aժU$!??^^^ٗt?۷ k֯_gϞÆ fݣΝ;333'rxʲ*F1666gggڊV"E!d'٫S#%Ogsuu4aeccþF5 iKt?TF!c0 O>%쐓?h2224h@>#*܊rrJJJDO!^Q~}/?~V,P+#\ѣj?\KS|]k6f>sLY͕9㴴۷@0552d*vvv j=۳-2` ƀhFF&vcyЖ3L9/yNNNɓ'=<< Hu)CR~%%%7o>p MbbbLLL9~ڵd=LtoS_cbbڵks )O;"Xpannnf͎?k׮N:P|m+053K*׭y{{O<9;;i$H022JKKsww*2o, 8*] σ\X`02_a@(Jss4sssKKK\G*ȯb7O&EDDDDDz{*ҥKˑUF"ɇ_Eڸգ]vIIIǏOJJjѢſEb!jffVqL|FF=#@5v*P1x((@noO *QFqqqdDL ~>>>ׯʮz xu/g)ewf͚=zxzz kOW:ýޟq?d%Y(B-hZAKfY& --ٳ#˲殮ƻk|NMA7'F`>88ͭDJJx0]$uSNAzr4!B)JWQ!A3/oZ07aKKK;;MfpGAAAAAAAZc>...ε]pj((((((((cxɘX<ſ~o9T+qK7_x_y=0|jӧFٽ{Kclnn޼y}J?JAAAAAAAA95k֤I\o+]&h^^^s̱9?_,Y2qDccI_ ZsxvZjjZfVtGBa^/ kw}РAss#??_Vy߳gѣOAa)jW޽{!CMyGyS<)Z?Q`,E9<;;I$DU$e7n|!+h0ǑT*gΝ!i` 6RUcƮ=w\9XHZݻ{pwwѣ{k! `w_?|)sW cgp:u ;;;)))99aÆ`شiӥKڶm;dȐ2$Ax裏?<*+#޽{/\}||+)))IIIcǎ553SSS;w(S|AAݻ_naaǰLU/''cھ}pܜn Ν;]Í̖-[F55555588>dog *)L%!ٳgCCCG٢E[ZZ֯_ʗ@cWSNw;$Ӯk:ⲙ8Az V i+ETTFٳ'>?ɩbaÆ 4hٲeO^ti|]y's?a #=tzO> {MLL.]bŊ~Zmƍ]\\._<|xQ9}m0z=w\{VS>##:tx-Z4`/\Pzyy;w.//o׮]Ӧ* <<|zYnܸQ^=Sӗtcb^0@|ӧO766vpp9rX̙߇RRRҧO+W G,X ...22R:tSNR999QQQV6mɓ'IUO5֭[ZvrM6L]zΝ;۷'6a]n} VUQF֭%|^^֭[W|q.]>|ЩS׬{ˋä*/)ѶoСCZ{cA zѷo $%%2{l矿׫Dzo^ps\\1>9Ӽ۷۴i޽{af͚11ᣣ4h3ck{Qv~)1<~'m_U* 64o|ٲe 㥭3669juV 1G~=rU;;;RkMv+JeqBGGGGEEDC)gEi"cLzj ܹuqW3FT}v~$cccCSqb*(  eYgHbzT*NVTk4DNŭ>AV+2?/r&ML6z=8>>~9sfI˗/_z5OF#j/#uc BCƯP   <1~4WYR3Z[[y.]T*RM6[l\^:1ek7ũPŏV~Ϟ=۰aChhL&h4{y]vA1.))F#@T"=z2/\P|6Ab Ia~i&N diZFjժ˗/@ <<\Po,ˊBJYfeffv!??H[F}3g̘3g.X@T"HuDŋׯdÇ'vD; W={l˖-s!ӧ'''~W۷/^ҥK&&&F]v۶m= \jU9Z|r\܎QF!vѡCdNhXMOOOOOW^9 +..SU]t;߿0,,,((̎;S|Vfee8q+uh-qqq[l))):th^Tz^СC.Yz_?dJeڶm_6k caӦR!:n{vo:~Qa.]4e'''N/ ecLOkk R:uH}F矶63>qL9lrv+3k?𣹹i>LLL>|x\\ܔ)S|ڵ6x:`>|rrrK0jԥKLF\ϵI ::z„ & ޽{#D鳲2occ,^+ku?}vi&%%s/^bUq9;;'Lڝ??p3W?ÃU*N_=bggg׮]!!!c#N:e0tz]PPPBBB׮]M6|1Xo@>}z'㎓c|||Ν*eIIN'~:Ғ{޽>xnnn$&8|U~KKKQ6m̙=˫cǎ/_;uTNg>uVvv6 `Ye848w/<ϻԫw{ݿΝ;b[ZZŅ|aayyybhu݀aΝ 'S?~'N^)(9>>xnnnfffU>''$99y˖-)%%eǎg}Ԯ]r0SMU*s̩j,gin VVVڹ? !n?)ׯ[׽{rgϞ0 @ǎ[hiiina^N۷wرSNr|w*W8<)OZk/f7Rɲ,q,ˢ䕞 YFZ|^HPq!Bpȑ.B!$NF^^^W\JX[[cMfeeeemsQa+b+eB2%P0 `A#j_TZӧ?:q>1PGV=w]F5k֬fEم:nRݺu1b۷\2..N\2TM-[l&N8uڴ}TNƍ >zh@@>|XTTFz"!>|8o޼TggRyLKMP9!Ϟ=CݿqEѽ/D(44o￟ҲuA} 88xʕ lڴIՆ`mcM4aY‚87Ô6c_(P)B=*((hΝFJJJ:x`'L1iӦ7yڵ*Ƌ^ҴvT{G߯GvXoHHHvvvxx߼y?->hrQZ'O $QDe]>(OySFڋsd2L&8Nt n2t6-B 8qF;v,Y[!,yݼy^a=<<0999[oIi2F)gX!f`P~bm6 ^22Eusp;w*-Z>}Z+W&MT _Af bcc\cOO QQa TZ<<۲e˙3g"""u]vرG;wT*߿m0k֬ m1cS4Q,Y_0 r7o^6mRRR}]AD~^^^UE ^-W!?">}o߾;ۓ-[;vl||?I>pСǏVqb[]Zf:+[KYJOP̜9ѣN"˗-[6{l\.v+ &X *gϞ͘1cذajZV@U/**"g^ k.'''++ʓPF&GGGje{ .ASQaad2Yvv6 ym_XXȲ7|"ljBv횙wؑ0TTOr8K. <ϓ&Ȧy*Ec}8///<<<.]DtÆ L7<{L4ΥKHJY oӦM7oLޗ{5˲X򵟾}9+abbbr~ *ڹbyQ~9^$GM%{DIۿ  t<ϋD}6o޼j*O=M<_&IR]<)Z3sWn"B:Ĥ\E@Ϟ={Q,KQP҉a ۵k׽{@KK˩S;vlܸq9::.Z֭[<7n;Qڵk;tVZ͘18]]ʫ2+ F{N:ix66 IDATΏ `J)Tٱcrrrĵ4$|֬Y$#O>dΜ9K.4hP޽Lr'Λ7oӦMjzذaR!cƌ&Iv:uVyQF-Emذƍ#{{{/_<,,=##cΜ9,n݊1b[d2ssO>[nӧOllEn݈HI^l܏\HKFU5aF[dIӦMɵb/r~6l0aΝ; EppŋIL֧Oȸvډ,+*f͚mۺ[]vSLQfff 69r$q0}@zzzllȑ#8#]]]ׯ_DW&ٳg۶mNNN<Ϸhbʕb&I |7 6qݻw#""X}뭷,,,;www|ٵk &޽܈qv%͛bѣO8QPP`5kt bcc &W/U[t\ߎ1"$$dϞ=ǗBPE;WzFZj~~~;!4{~m۷o'2bgg 6ϟ?ǎ%5ԯHxGf?>x𠛛\Y/I9k)Oyʿ|-c;::,{%#hoi߾}˖-9m… q2cZ}MOOOSSSqV`)V-,,)..FiZ^obbkddRn"2Jᗜm, b!x a1Ƽ -ѫit+++QիW[n]*jo.AUuZٳxYYYd 0 V O򲷷v?۷ k֯_gϞÆ +^j=ܹS^=33"eoo/Fc!kZ+++R^Jv⑽:&&&/OfffnnxbX8+bʕ̴ڥ= >EEEEEEobE#Ƕ4͟NΎI=1wUT'tA *C#zz#sڹ Z-/<_XX("_~9rdzz:5?)Oy|gΜi޼99<c}C .T@355NPQ-6{ueX !mqcî` <~f^֟i<49y򤿿D߷n"eH7ܯdذaMbbbLLL9~ڵC11qܹx2dCZ9>66 .Ÿ>}wޏ?{-_NٟJe~qpphРJ{͑V<ٳc⧠05իW޽{Ȑ!妼X<)OyS-רj0fv< `aaF뙃q ,8/| clGn\wnP*eҢ2LP" LVjFsƍI& ؈#X,ЩS+Vh4Qț~ZɘYs^ZRR:5 a^ q/Y^899@[UVqttd$K"ꠞ<)3#*_|vvv&ALTiܸCxTFsG >{{{___Vݶ}[~ҀUB55z @ XO2.jƘ\bzdFNNJJJNNnذa~+-f06mtҥm2/ 7I?#O <ʈڻw zO߾}er}z>%%%))iرfwwjjjrrrzzzΝr*/((ؽ{-,,_`]]]۷o_z^s缽+oqXXٲeKNN~qxx|Zjڎ;z}^Zn-t1FӨQAT*;vo虓ӠAg6lPk5۷0^N>QƦw"lԹݻw߸qӳ[nEEE;vӧ(n!^ر#G888jժC@jի3^RO?O-aÆ 4hٲeO^ti|] }x's?a #=t/ӧaaa<|CO<8pEʅ} .j^^^Ν۵k;ﴩx<<_^b7nԫW%ݘW ӍF)?sÇgʕ9 ,ԱT~NNNTTTM,-p#-%")_XX7ߴmĤ{4yID%\*.ݿTTT;4mڴg&lTXNNnԄW0tPZݷok׮]p]v1خKFϞ=:bĈ-Z?ͭ(G'O 2(m\H5%KȻȑ#R~7坼;mjK_P/\pa{[{uBySIb*A:=wq5eY{kJ!BrN$oL12%f3ݼa&AqR EeJ99s^?Vm2fF3a}?>>YYzֳ~^ A^_\^ee|JFrD7pE*U[nŹ/2:B0ByF-ۓ'wfct|Aqܹ3888...,,lFeN>}ܹǏ+JGG`77 <ϣ7oKGW-\0%%s`\pW3fؾ};HGyzzb̓T6abs.ڶ{nBܶlL]v <<={Ν1tMڴif_'zo<8w^^MNN^|7jtƎ x(9oy4*֭[DÇ-ZtΝOD8p`ԨQߵkgy.<)1o߮RVKh1116ls玙Y@@@`` ? \~e!#F>}:ի:u311іhhz=cƌ!yzzΝ;7f͚O666Dg]Q={6lذ(KKKVۿXds^ ==c׮]ҥ˲e˔J0zj޼y999mڴԩӢE4^^^^daZ|rϞ=zFaY6)))11VZBHC0999Hr1qF3 +k֬i߾… 5m1}{N' 0K' uSNuttau:ullLɺͭaJJWs:uT*0a0aFv+6@ed[wAϜ=zQ???ݻwGFF z6mĉ'N8rȣGz{{eĉ6lػ7R&]"##wܩ [E\/Qi O>{(wP(zݠAz5k֬Ç3 MU5jؿ=⫬mǏ?cƌWjiǏj5:--JFO>K؞W5dRy̙7fffkb@/_M4idA^'$OG>A__VeÆ ߺuN[`O?/q1_ϝ;vZ9;w|s5B#_Z"--O>={1cD9: ek+k۷oݺ,>|+^V҈O>urr*?}>"bWz=BaÆ.^9M6ܹ\!oѾ#ވJ :t!77 . Ì?DaҾ}{ww7AdvWgpp{o?rڵk10dgg9rM6ϟ6mZhhhFfƟuz9t1cܸqCݸyںN:d ={77ƍO+VR"!,Ю@Æ /D_(^[Ԯ]C} {>|ѣGhѲvڇB۷Wܮ3gnܸ߼#J2UXX87ty5+6>+++;v숍^Rx{{wСɓ'7l?ZZY/PYE5 b?KD5n/.qr6SNݲeK>}JK*&''_q]UMemm]O>Ǐw,H-,,a%wݭ[ݻO?hs2ǁBy IDATS$_y1s) e% ˲H_0onP4D PB)&mmmB0dGmسg^z%G$Hw0/MRV.eT\Ac(YpEl 1cT ltÇYFŸ999 *a>1_WV-~K=A֬Yf%\-[&߻w#G>ݳg###˷jժUV2s֬#GNӧ;wLO$&&2yyyyŋ߿T,*6BFB^|JKKkԨxgb˿ޯ#B;VzO?5kD ciiٶm[A~ &CzH vֽi8;{>sԩ .Bd2aX:,M1~}^xѡCcǎ rR_5a„zƍUU}2eJ@@@LL)B_1 CA:iA c}K1̚5?o޼9B믿4hsiejժٳgwu̘1SL)x7vR><+++((M6/vvv­-և{J0C}1PV )Oyʿ|9RT*AP ln6tA)<ȟ35BE>.1˲NNN,FFF39bE^.e2)yL`c~w  gױv0cx'c9=z,X$Zlyϸɓ'WWqE8ׯ_I /=XիWҥK~)):} I#""⊎9rIN0`رcf P;!r+u֣GprrjҤҥK0aBӦMϟ?_XX*LƒEDȲe֯_ԩS2r_֖X^'O &Kav(}١cǎ;vpvvҥŋ?!1Cɨb f!2Ŧ+_ɲU.\+XVZ=zBFW"---/^۵kW“srr蘖#~L.!|1B[[ۂeׯO6 9r[nr|ٲe}YF+;05kܹs׃v|;'^3g ={޽{2MW0ҧ]vIIIWDNUʳ)OySʋ]T*e2BP(r\V\N-w AR A0$%9PV̓E" 2DlذAI/^'yUXQJYUX\3zvQ~UgW B&QXLIF.aRFZ!!!VVV}g6nܸdyܸqW^%'233Ud>ؖKKlIZ2)r7|sK.Ν;k֬7oL&D6ev˗/aÆjZM*|AA}9::ZYYWXRcǎAAAӦMS,ˎ1$&&UVV s|B~~>˲5jԸwOܼyS"BzꕞMn7AXOw?11ŋ=zDLq W1 #\')eٔ7ob%ɕ+W$IzXW۷y߿xx8~:\|\ҤR۷]T5-[lڴ;/תUwСddZj ۥKϙ3_-oR4&&:t@vN: Nn'/,,tpp֭9~tFzo߾ߒO>Gٴi޽{NJ}'8Uyv<)Oy}bsB:,m/]<_|3C OY~! !6lذqFg۶mmFB!!Ia$@;w Y~ɥKBX?+RTR$K\ܚ |lꮃlǀǘ籄R&++곳gfgg;88? B~ܿ'OfϞ jڶm[=\\\=ztass *TJa?{yyլYJA'!FG&=UƲG,X0zh}iӦdGAOOOBq]raNJJYfMa7o&' YǏoҤIZZZfͶn*s'c#ggϳf*57oo׮]QQQ6m̙C,c4S'lI4pW^M0!22Ch&ɉJruu5jp33˲O<s\'ضm%KHҦH>"}ML痙yqgggr )/V_PW^̌^ZF eo߾ݮ]; ӳUV!Q!66ӧO/]cV/^_cP8[ڵ?/LMMō+R~aaaJJJݺuoR~Q89Ϟ=oS/Çd*R휟/Le7 č47/FzyOyS/1kך7oqܳgsYX5777U`bb"ɔJі<, $ @nU PL0t7qq Rج[&&)Ӱ! JJLLTTd9 q'^!ߊ?~R488888SZnzj#ɩb< 3vvvnٲeLLD"Yd?u)(((yT^\%Pj*~yGT*8.55cKr"@v¸cWxP'ٛjZ@/BTEGGGFFnݚ,$_IɘTڹsgr? -ZZPPyϵj՚4iҿǎʋ˲}ٴiSRRR߾}ׯ_e XDtE3<~vԻƍ3Z H#<_ VE,t911yyy#˲*vڮ666b}GAAAAAAA^b󩩩QQQO0O:u bbbԩ# N@)Pӥ uz4!BDP2Ň bf>ʕ+dws rk2 Mf&39>fx/cf!yCjL&Y&9  hnAݏSGAAAAAAA񱡪CEAAAAAAAAAAAgA  4ϡ@Oy?d33 6q\~~ٳF<06_;8S'p\ ¸_֭RV{swСz=Z8 cLvQspp ڸq>Z<)OyS/6)a,F9?~\ZXX|T{zPHPIpn| clgOrm1~̱*RqQT*!իrTZlFs޽ɓ' qDx{֭h4fff(((((((( .cf1*7ω/**  I?3L2 0B!(e00ƀ1pU9X}?n= wZVYZZza0GvZ͚5y2 ZV|GAAAAAAA^srrrN:U^t1իe8oڴ3 9$e"9!/ Z%f<2VcyEԮoC?^7*L7`0 ` uAqTTT-lmmy3 J`0W}GyS<)^`8̙3Nrpppwwԩ_QF9ۿ?q>>> <}6mv_ӽvZssj?[۶mۣg#9{iٲeŋuԹ~zÆ o|ԩSoݻw޽;d1pMy.]llll! &j勊v5x`Ju +<)A3瘚RHu^1. :MMMs0A"uX|yZhQR_wBy#U/2!c.zx\8uC:t%3q<9 GRb0)&Mh4;_|Xذa^z5t5k\reU$x# cL~O\Aԓ'OvC Yboy/_j4hpƍ}kQq88t,ݫU[11/g655uppτ׮]1bË~>e˖EFFN0Aן8q{5jgggO4Zv֬Y/^$W>嵋/NwwޤIp޽Zjǖ/_<&&A̧~JHFg1jAbcc;t$"W!@o^k׮ytkvήvaKb `bbJxr[[[80aÿtU,$,,l̙VvqqL{w۶mzZ`'O >ѣG$/ raxܥKm޽[[xܶlLqy1={;7c޽6iֿ.Oy^|[ݲHyĉoޘy!}4hٲe.D!>|޼yqܹ$,|7nԬ1;ypiӧh<99ѣxMMqE:g'OL 16l:D0SSSHy.*:tP oܸqԩcǍC̓ ;ҡUfyTӓü`M6@xx8BH'DN||˛6k&P{1cƔoeKyͺuk1իWYf)LT5*֭X?C:th)OyS/3~\.1o0XE8CaMv`Ya`hB7pm۶9;;+Wo޼٣G˗=zE˗/^:\v-444--M6+W,^&*/#uA A&-)~|1cc0p :++t;wST2 ,(LV<1%k7PCTs/_H2FC/NHH[.`@h4AfnT(BϞ=]۔ܹsɯ 6+'1u:]@@i@Ւ5mLL̆ ܹcff ((H.9seYAȈ#ON*zjhhhzzzNLLL%j4^q`7}1cbs΍yYf? IDAT67ѹHh42,//Ϩ1cH68gΜϟo޼]u~Xiҥ'!!!-Z $  $ H$W^;w.:VZ%,y+""ѣGvvv]vy{{ر,=zϨ@|dg5<<֭͛,__߈'''C1QNN΢EΝ;|ر2IV+rm۶k5ѣ.\o>^O.)"V{ lٲ?Gt 6޽Zj|ŋz}hh(YVz=X?yV-(( r6o޼{gϞueÆ &NxqA={888Ut+ ''iӦgPxxx:u߿m6#}J۟<)ʋNT谴Bcx8!/<6 )STsH VRXX4MJJʬYf͚5edoAqqw {;.#3}ĈWj1}/R_c+y>_,7xa0ĭ[\Rz2RSS>9::VWq9%2"qLĔssDS~:wj:n`0z[-[7o*(x?ѣ111cǎ@>Ƙϟ;wnuzQTT?k@]:dőޠtzg~1c!rb͚5۷_pa0o/>u~0qQnv{ԩz^П9Nޣ;` Y5LIIjWNJ&L?n%F6ܽ?T:vvv>|/NСW^ege,8paCW^1޹sg~nݺjumܸ<}Qӭ]֣ǢE6mcVj~i_~9֭[={v߾}cƌȸsN;vzϘ1ʕ+s̱UV䪱c>|~Ν;v:0y//jٲCVV֓'Ot:0 j^q%4~'O,^سGbaOV:thRRR: -hЍo<<աC^jذᅋDNӦM;w,W{/,,|!ˆlRCDDD:=B… * 0Ǐ7QoM}{ۏ9vZ@  GmӺMQ3t޽\rرc*K#;tҥKzj-4hpeO8ѯ_Ov͛נA]ݹsHO K[Y#Qys֭/\PB>>>QQQC_;4s~!::fM b|||<߿^!:tpjժ ƍ1aΏ_tiȑݺuId 4`ju{ _8ϝ;8v؝;wk#?'><((h̙:v*?U*ðf&f,J׮T*kk G#!'O6m:|֭[XH$p/լYPt媕M7177W,X533fF\oԁ(DbbbҲU˒PU ف<)7J1Ư75B`YV",kTcL~fͺ5ҪB%5O@E!EB X[[bϞ={ꕰɒD"(6WHYePq !K$$ 1H*Alsp@Zt,PF\Ϝ}y|0bƘA/ZiU\qݥ lk֬YKJ.u˖-ݻȑ#IٳgDx[jժU+ 9k֑#Gu?ӝ;w&ӧO I<+++h6m,^5j8qŋ/]D1koo_۷Ч?~!Lj]qnnnݯeY#/[3,IJlddT*fa RF!c?.[98]a$Ƙ8Hc%9вe+WM<.Ƹ~_N|ܯ^z.]B>}Ą[ ByqEGYr$ 0`رd(˝BrvŊݺuѣ8995idҥ L0iӦϟ/,,tuu&cIy""dٲezׯ)R@i xڒիwId)DYLrIawH-˲;w>wܙ3g&OYf)J//}J0"?,gXlrkӦM-Zd0Zn}U50Ɩ-j߾}lll׮]U 4hҤ ݝqq>|8::zРA!d~hY&ܾ}Yfe*JGGL“7Z-˲-$$$hBpofΜ9'Nܴi])\.Ϛ5+((($$d{-SORH:annڻw\tի[n?_|q b:XttnT/xxx);\J%؁)QgS_+/f. PP*2LP( \.+\.' b I)G80$% uMV̓D"2dFٰaNJ/^'0O ) )JJg+LrL D!%\ȥ½#BBB훑!lܸqɒ%q^J'NdffTWqb[r.-I%%k)|?L|ӧ/]D;wYf޼y2LVְ"/_ꫯ fjjVj5ydkheeU^y{IҎ;M6MV,;bĈƒ,77WPVYYY;v Y555Qƽ{|"͛DҫW(^whr zI80qD"?555::Z*>y͛{H$۷~ ^(CCCI,J  .\@ٳ]jԨѾ}{8PPI$›ԨQݻ'CƍɁ,zF(C=wСCSRRȱ6ի~amȐ!ΝO$%̤=/H^x ,۠A&%%M2Eu...۷oOHH`Y6--a^pA쟂B9ѣdΤR__ߡC2dȐ!666޽{wbbYu˥S7DrB]ܣL/?b?'5۷_~!ߒƳ?~P(0 9AUl: [j%t,v޽pڴiǎ0aBZZZKS_+/f.{>!̌R.2|ٳgƇ,K 6lܸٶm[D"J~>'Ν;CBB֯_oiiibbr%A2JT.?ɒ&۾ࠆ-ۡ11y,ayIʊٳ–AC !'-߿ɓ'gZjm۶G...=:|y|  <"sm+O*p????2QZ5e=z`ѣGh4M6%; zzz*w KwwRRRNNΚ5kؼyA+7i$--Yf[nd|D17oٳg'OVFJ2=2v̘1cW:tNz1ŋo߾=:::((HV6L,^zcƌ4iҴiuFYطo̙3Z-qnnn>>>+V+V׫W/999((?']600%55u۷9ёeY(U*ڵkaaa{^zM:_~G5kF&d\d|0'OhѢm۶EGG׮]{SLaaa*wB,.JnmmݳgOa\d{pp0˲իWX~=YA'װaì{MfʼntoFVׯ__Վ9\ٿ-[4iiҥ~~~jD~!88o߾Dk׮<)Oybft5j,{v`0|wZH΅ Cll˗O^ XV?x~$,z >33ܜ,UƯ&q }3}3 a1 -ҫ_jnN[kfee%o۶mūKC&''uPy|ՁV~z&M ##aaaA~7 6l&=yyyW\iР-c̙͛7;;;oذa=UVj222%VYYYjHyA+z#x~ŽBylkhܞ4d yߕJ';;[VļZoo]Z0Ѻ7;;;;;~U*V^?fff)BNNGNsvv^t,wyPɿۘڵk͛78ٳge9={z ʇSJ׫WN.emQ-4㿿ֱeX !mac`W0m8s_/vر^z&}ÇI+**ڹscÅח1^`yfrOtoT|^#G @yrziēN ֦MJ{x2T5=)OySC]ca,s]:eʔKeY[[[ssI`bb"ɔJn>XY`U5@ Hݪ@R]ǁLJY`nL~P(**11QRYZZe<ĝ0z>''|+Ripppppp,nzFSx4E,Y̬ ?0p?{lh4}v^^ XR5o|РACB)((((((((>rHH(25kָqcLo+A%$$]AϷ9˪UBBBLMM%})((((((((0ƒއ㸄jay̋3+0Aۛc^}kPϳ988ԭ[WRyyyj|8СC=zpƘqƃMy}GyS<)^?mRØY\][8:۷KjD0Jscx c?{mˍcmWJR\^ ˥b h4{M<`0 @Hׇ f%<ݻw_nF133|GAAAAAAA^w3QyN|||QQQLaQU,BA.11ìrq _Vju:^gc|k׮լYo~y+++jy~y,<'''ԩSYYYEc[[^gv6tcb @Ј V5o,Qk%D ш lX1Emwc`]i1y'{||{3gٙmaaQqYYY-[d9B䍍$)#iRkCQnic 0s<ֱc?3zlJ[ީi,,˱,'{'''iƆ8eU*˲¯xxxxGY@yǽڷkVx"x>>egx8?u?qƍ7Ñ#GZn7#//ssccc2Ra+&9ceʌ:DT,[LgJBccC+='$Bge9';ow\''ȑ#;wCCC/_R<&M6MOO/**ڽ{wǎ5rqqU%n~zÆ MML_3{yII{f̘allloo?l0A_ 2d:k׮  &z,Yc0>ܣ{ ccckn%%˖.?U]brƍ?p'|vӧ-[8@QÉrƍl sR܁bcc?Ν:cZ=`Wfff6\`Ll{FGG_~=77wȡCm… ΂碣嗀sssҚ4,,,,UhKKY͙k|>GPgYi+gHBzT*kN4EQM#~ԇI!0L};;Fc00]:3gLKKYpj3ŗe(DQ!@@x+`(#s<<5\gj> n"{B*a۶mzRTD;!!a5W Wc0@b聆՚6m4|p«W6i0:4 H@T"5j:򙙙]tEM0vvv%%Dc7lٲEGFFN2Z-_fdd[&&&QQQ@zSttBi 2dԩs͚5ǝ;w...622VXH:)ҥK#G$͞=;…VZ^^^j@ltb'P\\ܿŋ^ŋZꫯyƍgffYʕ+:QDDoSV`0h4o޼ݰaV!SDDD4N]>$^%^)͙kNRaU0_{Cy̲lyTT0Əm۶m: z0z0;>޽{ $ٳRt*&&fڴiΝ9s&y_"O>%`93إKdk+j1sd߾}[RׯI^=ΙRB/]lz^в#4۷,Yd7ZY[ }T*q<+~$ԓ'Oz=۶m}ɓ'8#!a`@5k,Jݧe˖]tQ(>>eeenF#,,CBBAo@yxxE;Hi=3f̎;lڒB{! ”=۟:ujʔ)f׺ռywG7O"iiiyA[[ۏ>(-- QE@PPڵkcbb!-$?^Pvqww/(( L rss+W!"Onˈ)}}{(>[~`gӧO;N4)222##e"/v,"ħx?SSS:ԷoO>c ӧO7n9R,55~۽{7#c_mXbƌݺu9rI EMvr >v/SDGGo~…7b{]MT\Vx//on\[#d20`pٷFP*jW>ttt KII"Ǐ)>iV!I&$$RRRꫤ$ a0j(rXTN!Bacc|򈈈ݻ[hxb`0EQ111-[>>ݻw*㘐{:88<|PV[[[BExWWWqaa ږRZ~8gRRRwnggG֭[vƎ+ת %7l0!!ܹscǎuppNbj'1lkkW_J4=}'Θ1c׮]R_ .E/6UlOǎo޼IxZ1*Q]kKKĿ ͙kܷRrRT* B^BA#w AL999gΜܹ0ՠiZ&^x={lڴ ̩SN֭gFӑI>&OyViRhViB4bHHB4bR3*93J9`(C)dֳ#&L`iiٯ_|a}f-"=ܹs$8|'O"##k8z-y/-I#"">Sm)?gΜ!˗WZ5g\.4+"Tϟ?9sAjZ&$_ZZJμy~5ɓ!:u=eZM!CݛI&Ϟ= (J&aɂCII M 4~:O\paAI޽?~l0vuivIՒƏoddD>}Z&^pO> \xq޼yO>%%Nw˗/ϟ?_V@NM())9x```B --mر-ZxaV6n(k '2ccٽ{B ꫯCG9ɓ/^?%K6lн{wP|||xxNk߾̙3Eę( U(_a w^rd~R_% vz5k6l0Uu֭_۷駟;v7JRR͛쌍-ZDu\AI?:tP^^ĉBrvv&E!D$^%^)͙ѹs4h@K׷,tR??vڑHU,6uԪ2cZ}-777SSS2㬶vTsWZZZiKAYYYiiy+_k9MQf@)_41ĀI1ZA\ۥ CL,--YYY:t}`Uh;wWjV{y7oz{{%,ˮ[n̘1d.|qqٳg6mZ~}x߹s7n4H86 Gii{6lX^EqQ,ON"666MjD^PB#C{?~S777A[F7M^M4_7n\RRBFyc\XXX?[V{n&M*<=JEP9{U˳Y.FFFDܖ 7e٢"ϲO? >͛q_&$^%^%9ںuk4s}U+@`jjJ|$q˲d]Bk/;**؈)@Pc011J##c00FG34UT\ζ IDAT5({ ajmmmI#'M$..^v_Exr//j^Mȿ܎Ν;u~aĈ$; xxSLK(}ҤI/mllLMM+*h022vqq*Jvwج  *a=qq 2ߌ>ֱ JYvvC cl0ȷφL&3f̘1cx}ʕ++®wg϶\&%Ti71 #7nܶm[ڢE"""AU  Мc4#իW_3d2q999ZC=cD!(Oys5umR<<IYȢdFFL&ҥJ~ ?ss6mڼm+"⋷mſ-)ڰaطm $H93B_~4ݷo 6ܼy_~k+` OW z(̹FhK#<, %4mmmݥK k4M5jZw<$H A {bTsrrߝ9_EQ rvvLl]őDn*999g$x'Г bFTV3M ? $H Aj3!xyTpԯyKKKeZ8yO $H },<ѣ51666{V㸬-[X!FZ?45E!Ȩ ~ cy9XԱ_}ߟ=m-X4]\YeXXSԓ{b۴icccs˲*eYWxIKKK#},<^^۵k+<Q 8<Py%q\>q\5 Ƙa2+}ER inݺu-WqmKOOOXO AXB 5@0Ƥ0c <ȌTeee5:PPP֬Y`++jXݲe˥K||| s,9_qLT^H`lbyL~(@|%uױ0B@Dᬌ3ԣ|{+Vxs<xGaYVhSN}wupp*6hР/^Zٳ+W {90Ƅٳ܁\믿޺uk @6oCDUG,ϛ7d9rsο{hhCBBj'ݤiӦEEEwѻX88j-ׯ_oذk1q/ )iό3 &!C ;m߾=88&B7z ۷/\ai>ygyoK,IM=]Bsp@@kv8c& 6I3|pBn߾ܜ9;v4++Nl'"11qРA߷oʈO2eԨٳַz_.Y$55U\ ͛7#P(x̳,K4•s!!Q*5i3^)#zď4I"ogh X?CصkסCp̙iii666 .$YA4߭ŗe(DQ!@@x+`(#s<<5\gj= \F++2۶mիJ"2 ϯ\^1{7P՗~ϟ?ߴiSddL&h4> ^zI&1thH@*!רQבҥK-n1+-)!of˖-z>22rʔ)j6##cݺu/_611$;HoV(?3Mӂ!CL:zܹYf=~sFFF I'8^\tiȑ0??ٳgg\ЪU~ښ_:V&LhݺMLLf͚շo_·i&**lٿZڵŋ,jEVdZNKKի^j$?:p@DD]xOHHwInJ/d2M6]p BBBÝZ^pɓ'KJJ7n|СP|N͛73#,X{z ]hQaaҥKuΝŋ[nΝ/b0f͚Z5+xrGZꓟ}ݎ;vn:0ƅ뒓t:tHlgΜ;w.gR8eʔo޼qD͛g]HK+77g;JUVVFf^99++իYYYW\cq$''ܹs-2vyFٳk׮ݽs!`ƍ۷2eJ|||~} `0 5kKjӲe.]( o[n#fffV!!!נ7 <<1zꅇ_߄z.ֆڽ{w#sssex:<رc֬YǏwvv.@ʁ˗/?_G֭]?ǎ)c#cKKKeΝ;fffVVvxmѢŠA===/_^U^FXMII8ΩS +VhѢEƖV4&%%3 k2n׮]]ZOK%^%^w͙kNT40 MHƘfξ5RBںu6}޽{M0T]  ȴm۶߷B%好^)2BB ? Wėx<D׺hŋ} :Ν;׮]H.߮]v >>=%%p7n\PPǻtBGfggbND!УG.\x GGr}T]P%%ϟ?G=|yEnX@yHKK˃~GiiiNNN,,,:t8 ֮]SUϖ-[ZmTT͛7B XYYYYY]rK0+W޾}E_Cppprrr߾}>}{UCO>;v4iRdddFF1f͚wn޽{dgN ,hذ!9 !D&n/2Ts5ËP# аaÚ7o111zzaFf~ ًxʕ'OthN3`aw+_-Ws#Fh߾… 7n\J| >p3gΐ=ʵogӦM_|E~ĕEGBիW쿭W=<ExbMZ~3gf!?nddDYh$Ahjj ȋ JIIꫯ$vy!lFEK !P(lll/_ѽ{wrjc-/^, (*&&e˖N*++swwc<QdqnnnG%/N{h@UL 1^v )ȑ#?VXI&9rD8pjӧ 4ovϞ=ǎ311:t(.+#@pWHHȊ+,XBSbKK˅ z{{gddt֍|޲e/eYOOOҡ*kԨQ?}!rA°vҥVZU'=bJrppx )׬hh/=W^hӦ )TP,YdGZ5{4MQQQ'N1c9 p'ODFFq[^ZfGDD0{)}>S(Ϝ9C˗/ZjΜ9r\hVazW{s?|̙ 266Vj< T|ii)9ݻw;88XZZ$OzL&ԩStt)Sj5MC ٻwoff&={L0(LVPP1&k%%%4M7hD?Qra%{~qrr`صkӧ)OLIN'UKJJ?~џsiL{…>}0 sy=}ԗ8aYf)ӧd2eϟ*… D =zԷʕ+{'`ey>}POOϓ'O 䤧={a777F6}z-[n޺ukҤIB}o˖-׮]c! xj<СC8PPP@tRRRƍI!f*nZueڵ}kuFcoo߭[7k ɏUٓRQ~*`4uD>W ٳl7n\nnvO!&.//_ կ ?~MLL*\󼼼/%|uzJ4?#Af͚5k8;;w}ĉ#G7n[޽ccc ~'z{{;99@ŽaA2-^)dL gţnY-8Yێ|} xy34UrFN8QXXhooO>{{͝1c4lp={tqqwMMMk8d2 ENNNPq xk A-N={͟?Ĉ4M˖-ɉ~~~JڵkU͛EEEV*&w}7`ƎۢEjjƍrp"Ca0s9qĉjuDDN2dȬYHGƎiӦ͝;wʕaaa'O>x𠽽 .\e˖ӧOGGGA9266vʔ)ݻw'޽O?jyxx-_/_i&WW;wDGG7zꨨ(s璮zC:88IL&333[fMo޿8ss>}?(J2NO,Ҙ!Cxxxxxx :tѢE~~~B"TVVgff֧OR(Y^tiNNHd=i[[[ss5kָ \.cǎfffB+=g9sfƍ>/^.H&m!K>n޼Irǎ|͎;"##E_<22{xwssft9ҥK;v$,˲K.k׮T ,6uԪ2cZ}-777SSSTvTsWZZZiKAYYYiiy+_k9MQf@)_41ĀW,kuso. 1feeuС]Ujܹ^jZcn1˲֭3f <_\\|٦M֯yLTWsm`+**VKZAw#.iWŎ֔&KۨƴƺҪ"Z(,3wqwInXgO,ҐZ<oogۛw +_SSc4[n[#87!޼}k׮EDDPXXXw7 &9ru^@] tWc{ZqVK'7֏k9Wc ! CHqss|oE$1!H#D8>)yh4 M bTTTОJZr܋yb i~40`@RRҁΞ=d9lhh/^ܼyt򃃃뻯FtP8E /~ .h4^[7sZh4:^t%?QvΝ;y hA3Yk_OA#F83{lyMmqy}3f̈8ooowww%T*V[po6 K,!00DsDBnI"HQ)9%G8..`BFt:///y!D$z>?ilS* ,X`A?O]"##w=4{젠KYn{~~~/b]srrƎ;oyzzuעJHHxUx5˲,@r},x!F7 \Zc@6[z,7~x0m4wzu4hZ|y]!DE(M ( FS44?7>>-WP׼m}M_ۼ[mn~B3_Z3bFRAaNBx4J+Ѓm:]$͉0m"<4sM7ķnnnNڼAxuHUUUFFƍ7e]$t>HնNCn;<<\RV[_xa2uqF___<~HIIY|kT 9FǎvZv p鐐s?s%Iر'|_gddL6F9RTխv;ZT*qUUUNNyY6d2UWUlD vV!@}'RW{UWW[V/// f;s̵kΜ=cl<^yjPsl6[+ܥ%Qyob5_j/]\\xx^⌌ IN:U\\$۷o}KF#ڵkʔ)!!!XaΝGׯ_))))))qHHիW?ݻw9)<}رc{n7FMM͡C;t0`QF QAXx)AA`൫wޚf[|yݻw-K```S-[_̟??---11111IgΜ1cF.\}zii) &&&&%%?{̾$FGG1cO62jԨ9s8d2ʳ!SLo{7onذ$&&f͚5&&&iii۶m=z4!t<0++kҤIÎ3&33]!'O5k֖-[v_l2ZMW=h#sztҍ7-<=Z*!!ɓ'N:thcǎ%lݺuѢEX,۶m#M:uڵׯ_8&۷O/ܿ7o d4srr{={&%%y{{9s>s(@ *((HII1G>|//ׯϟ?O? 69ݻ Ι3'44Azz̙3[s[J0 CI$&"#~ `<g^SNVjxq#Fxwt2qy~\ںu|0pBhnRRRҿYxر˗/ڵzJj޽Æ LJJEyzzBz==WVV w2oMP,k0ݝ?Aw>bĈׯB߿/9(***77Ǵ?zK.rn0uTy5s65>>>lseYeXeaX2,h#9jzΝϟq`0aceȳ KJJ ***VXbŊs٧4uÇ}J2<|zڴiSjjK흙yʕ!CN﹢GiӦ&}*@\zSNݸq 5Ţ{3IUs5yŋs[ ?cbbhVkBB‰'FcUUǣVTWWgeeՋRTT{T*;wƍI 6F_@vZDD ф$0 EZ]gӨjuzzСCcba_Fe8Tرl6wMh@ ^NŋG ):1;;[Tj[U<$I$yֹ=8`0fee taaaA^#9j-((P*͸֟f2uhZVBqwwիQQ(Fu^/9Fddd+a9ekoAo9Ao-m"~IENDB`workbench-1.1.1/src/Resources/HelpFiles/Scenes_Window/000077500000000000000000000000001255417355300226715ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Scenes_Window/Create_New_Scene_File/000077500000000000000000000000001255417355300267615ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Scenes_Window/Create_New_Scene_File/Create_New_Scene.png000066400000000000000000001465161255417355300326350ustar00rootroot00000000000000PNG  IHDRWa7' ciCCPICC ProfileHPY_Das@ 20q`Beqր (5 X wEA9&Tnz_ wrDXTa8ۍ=<k7M wwшnqV4.Pq4n*g\8aЗ.B0M,1Op|HgdrH̼B@9DaƩfa?SϸeVxA(_nVjJrH)3FQoE)A ~0,dq ^' D>i }8A,oφF,-cOxY^+y@%0HA "=~M˻et l!Ԙaina?f;{ :]Y @ <"f3<@ЀPZ@ {i + (pA"HbցM `/(ap @38 .: #A8Q!%Hҁ K 9C? EAB:h TCeP%T BP?tƠ'a f0f~p(ë8Uq _o<(@(wT *6 P%*T= ՍQX4@>04].CC7;7C W 18`ؘH ǔ`1.mcvXl6 [=mcI39q\:.ww7}xK>/oƗkg) A@$ل#6 a(K#:CIMRb=D"iIKIR.tt4DH#1 vQr; BѥR)%c)['Q\Ij@4AZG%B:GD q GfLL̠̤,UB6P6UHVNNWS''wX0EբS-#. KӣiIB Z/mB^NZ>\~|9y Eץ)S;O ,xm  )*+(6(VPTJVڥԬHlT9Kr""EEN-U9ң2*RݯzIu\檖Gژ:UY]GsSza54iZ Z{:&յhӮӾCa$yUYwTOQWPZJրilp61L4,7a c퍅Uƃ&dIIɐ)tiK3mh]ff_mS̏?lfВkYnyˊbeѪꕵuA6T%6[m:lيmmb*4fycfG[tS88&;:.[a'M'SęEÅRU˕ZeJbgt3w5wwp_( ,|^3Ves5 _;~d2'b%%< 4@vGAzA~Y]|`u!Ԑ!!BBw> Q!4\y=J9J\lﲑ;Y~uVJ< Tq&qq\w> +oo,)8YSBq(߉?X8.p ^%$Jz|4y:%"!*& ;WZ_d$IV;޻zB'NҖӐ`ӓ]Psfy懬kddfo~ZZڎu6ZZ_cƼ#޹67%ouoDliS||qVǭG/wն۾ ~.]'laNN;\v+-)޽dwƞ=o{ĺ>⾌}RҖw\XvܭBb[]R=TxӏVzW6UVrXHV.n|槿+(B3y pHYs   IDATxw\GU$-bo(v$DM%$X  TD&_sq Y"?ǖ乙Xt%wYYYĵkYh4 üI\. ST4M&o0Z(Ajmxh4ekcYVժTsg=(!/|B[|CqȨaaa!9Iuk/,NJ=QH,<d64htFQ(Iw_sʹbQynnIQauMu PrkNӴFfb3PB!WWTWW{xxp\n'o[;ZKhF[ZVXRX\S oIgjS׌JtwX&qh:ǧϤj4w yw`4##C*vСYf^&MGQC^pȡysKkjjB!Ij@ H+K+>/-,,v*I$YPtQFV ,,`_7K|W_={eeeʼnm6LeӝD"Q۰rʀ 6+W5Xyy!CzuUӯ_W,&SЬV,ݚw X*1 'ǯRRRDVK+*VE D377}Ǘ9<{('RCZ%LdሬDBᣲ/ۍ0M69rD(4MQϯh4.SR3fӦM~~~ 9wC 0̫͎ZJBx2hR O|y< qj!F\CP/b ӌht_A$˲ܼlĊr9Ũ"1ţ-ZHKf3.UUUǏ_`V0g͜9sǎfff˗////_z۷6m RRRٓ* cجYB ,r۷o'Irݱƍ3hy&LؼysppD"EAaX58hׯTTSL4hPBaaaӓ B>|y洴4;0aBtoٷoݻwg͚%Bw]~}AA|0o<+++xx< Gq\AB)Uih A("bY W@ 袣-OleinR\C,jf MkZ{gSYQٳg,zyy}Z6''gӦM3fe~pss;{c6lx#G >W^ݳgEicǎ>}zǎZvѡ3J?F:UVX(xq~zhhsrr\3 os̙'N4YqqY:v|r|򨨨3gN81!!!44xܹ+W 7o^llQ^ Q>'Ҝ+R!"!U\N(BO:Fu!w=]B3K_Cٳ[hVsrrnݺq}OObooo$|}} EFFFqL-tĉ |||nܸa-õE=ԩSx;MFOҥCBBǎk.B#[[[{{{NQ֭[׍7^zu޼ygΜQ(_}@ 277"|@F痖ogggccpO>xK ݤB5)6#ܜBBATUUR%-(V"p/QORet{K'Z)px|G@qE>cRiU\B5*A]BdžrPQQBh/geee˖L|]0~!TXXp8 3G>hvrr3f͛7o =?4MbbbLLɓ'߿ڵݻq0LYYɓ{5s5k̟?ՕqVjkk[]]#7̪+W"Hx +-.b,, +KdiIXZff \c(NiAh \.WPXYY?}ALPp\$rX8<Wȣ!XD Dj5X! .2>>^&V_xg^B\V/]ݺu]v '''??oT*+++k0RȭSU\%y\!y ZC0YBݺZ~矯XѱxƍθwFGG BR0H$*//wqq̼tBi$%IjjBhРAk֬ jѢ˲Ip4MQSS3'j7z:A۷߽{ĉ5M۶m-[`ooo0֭[...Չ4Mn߾}푑"hEoGkum۶ 6 8P ؼo^QPβ!]r\E"H i-[SLq,:wF2 #***xYb^-p$KjͰZ-[SUQ,o/ѽ{w.f͚;;;J=~xݨ.D}K.4iB3gTTԚ5km֣GUV\vر6l8v/ҽ{ϟgϞGo~D9;;GDD۷Woߞ-Z(nݺ)J7x|a4-  ?vuʔ)F+2xvڅ :tH,  6_oq; Tw+K@R+*H//u>] je---2Z@ xm7O p\e1W1bDFFnˆ oVoAnL7sD5͌3X5m`(8eʔ;vlqѣ[jձc-[偁׮]9rԩSjY|||""" aW^ ())1m4e&Z6m… sq]~}ܹ/V*!VYf7ȑ#֭_qP~~Ν{onhL_ݬYYfo  ]tyn׊+zٲeKT:xI&yxxmÇ;88x{{]p dI >~>}`Tҿqhgg_{M$>&77r̙\<d^KPi׮ݔ)S[{{{eر[NJJ宮 /ٙ?B`rkoѣGǢ"oooZ}QPEEE:t\._nB@uEA@n:#s}}}ܹO6-??!常ٳgϞ=2, ` ԤT*i633cYTw bH$zM3 j(k׮}^ԛl `-A~AMDAMDAMDAMDAM,Hfz,˲,0nYQjunn.5aH@( Yh4j$w tAtAtAtAtAtAtAtAt jcǎx3 (hz}}ۭsi4+++Rj \.p\(U*UvvI*(@QTf͜Ih4r\T$C$0 ]N*..F 777ooo[[[ǏM($I]Dr$IiLU)gǬ^~#GfΜٲehs{NNN~SE8L_$p(<O7:jJMUֽ{E۶m={СCM[Ç^1'O\dɽ{~ w0555555E1@&S'%ƍקOu^QRR7nڴK.&L[Xݺukppk֭BSNݼyٳ<:uoԩS{-X000pΜ9!SʦN矗늊^jU@@@߾}oݺ. j:?????ZRi4 M>/إK'O4?|s޹s۷#rrr/^{G}]v͛7/22ÇǏdիWǛUTT_;v>}:M-Z={ɓ'ϟ?%** 66ѣ'Of͚;88]^WA4Mo`J$I"T*UFFFbb3g:u.\W*4M`iJMUQZV]XXtjf m۶v}gb o1c'L>u-Z,\֭[ܜ$I;;;sssΟ??ydɓ'9s&''ܹ+|}}{y\;w&:::33jl٢jO8R]O?>3It rҥK+++MX,404M?|؉ޛnq5ϗ;w䯫HNN&(sss+++gΜ9s̸8$ILL֭[۵kwq|JNNNNj%Hꪅ666vvvo߮IϞ=Ciغuk+T5nݺeƍQQQ{?&&&O'&&ҥK& iӾ;BhX-))9uI ~"gṘٽ¦g'OT*SUh4ӧBٙ?S^zݺukƍǏˍ7WciiiIIhÇBS^Г'Oח/_&Iʕ+Ǹ޽{#tqop^dZB< KBMky'#۷mllLUk *{9!4o<СCrupW!rLQZF5>xV-++ͣGRRREEMӻv֭Bã?+ `Ϟ=wnӦ/Л7o˗/<ԩS[Bdd Bϟ?dIIIC 5j\.\x1 vĈϟ?ו_ZZ4vXkw| ¯/_mڴ0`y\\\Bƍ6mڴi|||ڷoߐ~!իW֭[bЇ~سg@ݓIr˖-iii-[lٲeNNNttɧ /^dYիs-++{QQQÇ?#гgpjG0`3g=*,, xԩSSRRt92//Ν{٧~9x BHV߿رc8:ݻ7 -wvv5kk8Vdq9b^p]h;KOg "Lhaeemkg+mUUUxEDiij"X,yQeee/,:u3gNaaD"1]ZZ*|~(l[SRR:z?~|DDDrrˢEx<ޮ]N>֭[HH… >{o}_jի.\X^^/TDD͛322<==ccc:z222WZZzܹ_~%//o rvv>ofs ^'O^N w !IBnekl,VfVqT^\^z⠠WM#-!אCŸ́/gϞ͛w!Աc+WT*|S6|қP{~,nھ}{#~٩S'$=:`O;88 .Pj82H# $b1 xEV*_atN;%44ҥ BSN O7W|қH?$$>]\.FƖ޽V e@1e˰ XFA/g4gǘJee%A`E/ >%TZQSҒ23CffH!Gg0`H$WΎ?vbkJoA@4D0԰AZiXD4x04C"eXe hZK,bHc[4.!ID .E %"8C x\^\fWA @H˥$qX3uԂ(^F!( "B\$ED"M0"~bb"A;v_d]t޽ۃ[lY{;^ .99Eh]v…iӦ3T@,I$AR"K((SQ]_-^8<<هK#Bǎ1bBhl Bv'B Ed)Q!IBBSA{{{hG?;>|pPbbc~g+Ԇ,$2$@$b A8p_))q3gt?F&x^zظὗ.]jݺUxx8NXHLL4hT*Yu={ֳgOKKݻggg>ԆK,6mZxA D$8I"(""HdLo!Jرc3f(..^p!'ٳΝel_uxxxiiŋ2}]paff&ٴiN2%55u…| N-Z諯-))YdI/Ehhh~<"İ,A Cp)$"@8(ںumڴicmm?;::fdd>}zƌ~~~~~~ӧO?uTvvv߾}Ϟ=hΟ?;wa#G9>gkڵkD"d:u]]ݮ]޽{ j 7n۷oHHԩSO:KѱcǞ={6x{K!"4ZF"E,B,B44ZWQboo<}g5k_40`^\&sNaΝT*B&M4iRll,EQkaÆ⎩Z-ZkhrC4˲4jiZEZ-htt`v;͝;7$$Çvvvt ϟ%tyN>}ҥCJ?\*Ru9]"F:thv=zTSS#rfT}Z-BDbb fX|}0%%%wvuum޼Fg|Vҥ'0#G+WrЫ d2Z>pB\ĉ҃>\YOꏬYYVK3 bqcE&QNQTKKKnʩu'Leoo?qDT*ݶmD"qss޽{`EI$7oF5$i_@4]_a hG/A~ANNNfY744U ܴiƍwzϞ=CBB§:{8pnZޱc+6>{//sE;wJ$>m7ʈ#F6?~<**o\999YYY&j ]E$A!C.5o߾ayvkb&Oܹs*/>k޼UL: :ݻhٲe¯Y]lT*H$ ,j ,..2d ?r|`ggW_4ڳgOxx}7o &li̘1xU 6LҥKi JDEFF_~ɒ%nnn:uvBhٲeΝ۶mL&;vBh]vٳ'>!TXX8p@[[۶m.^800o7o޼)&/꒒ntM;v?… 7n|1#=Mffg̘;=''?ܵk7mt9PyyyBB?devݸ8FhF1ׯ_?嶍JD={lEEE'Ottt\bBh:uHHHիBy999D<[DDDeeeRRR\\۷tu~γfJx Sqq]7o UVZ>}ZK/0==̙3ṹAAA)));vifҤI:t8y$.wնm[KKK331c8::_ ޳gBhǎcƌ+W"G[?,,ƍ%|>_(M;wƍIKKpW_}0j(\Z]o1** 7_ xxx՗UUUu}nt }$IPpppZZ^AM?R-pAYYYVGiz…prrza6mQF 8[nʕ(JB>>>x;ϯglle4 (ΌѮ]7x0 Z%54Ri5ZRMZ Yz񭘗Q$88x󰻹m6::qYp!QKrrsVڹsO?t_}ѣ{]uJl`O<2dɓ'{}E!ڷo/ P@E ")ouD}N0իxKyy9|QQQ{|2˲ >˫{+V}h!MЭ[]6rڧKR@pڵ4< ^Oĺx{{߾}ۻٳ!PأG9s޽{7n z㕕.]jȻ! b"˲,eEZAȀ(/sȑ޽{H$`p`3gN޽R5k# tAy͛76mZk;eʔB3fL8qΝ۸q_ 'VOĺ?>''->>cǎkѢEvBBBH@-[ܰaF/E"Q=oݻ!!!xB,]2JY5ZF i5HF42_@~AF5k֬555T*5yIIFQ8Y`IIͫ_6Wb^^#ښ$ɚAO?4..N)Or5aOGvUiЕTBķ4\!=/?A@~hѢv\]]_xX,bA6x\uߏ?XZZpܹG=zT\7xRsvȚoey^êiZT*9OE"Ѫ8Y񬽆<{%((gΜqtt|r':bܜs|R)U(U=l٬DA yۭ4EQGU\%G"AjYJRG⹇9Q;$IWWQ.̱3ʬ 4h!VexrDA\s"Im% 7S *q5MՖfثKRkkk>^DѣG2{JVӧ**>>׷#KKK<"4@G$˕57tP*x;EQff"(.Q=lٲ]6$ ^xx13e߿nnnnpXIIۈ#|}}ccc9NTTTۣ_ ԃ |!pB Ø0D&ʕ+JjժUbbbe˖m9sJ-BGp IifCcK.n*<</?믿_>))I&mٲM:|pLϲѣGe2nՅO>dͨVRCZPyKKK'LbaaѶmۺڌZdɴi^߅#$IuQI۷… 3339ΦMBcǎ3fL۶m>#6oޜ^^^^[_uj:55U/qD4HjhP ;/ŋڌ 54D&a׮]D&w gDfff<^(ֺ̅atlll._cVttuuDwIIq֭ۯs2 SW>}Ҩ ~g;wJ۶mH$nnnݻw} AӪU+]oѢEQ;v@͚5k>>>~a֭ҰڶoֶG6/ 6ZAAA>} B.vP(Ҽ VVV —iWW^2$ r իWy< |>x<.p(K4jZmBB/ +omBh]Pزe-,,ьy1̲ohh.##O>m޼yxx={h4tСONNO}2t0eʔxHJ4O?/zn@\v… ӦM3t3g( O>D$i߾=Y\\67#T]]wށ/7"( oHQ/ 2O8Զm[Rk׮aÆ| 6Q\hT*MIIٸq˞ݻw﵆/ܹS"$&&~g(NWvС<00IIIx+WBBBT*Bѣǁj_͛7o޼߀}=|/L7 qڴi}Bߵxɓ';;;o޼3g"Ǝ>o޼qƭ\!$JSRm=zѣ?毿+ƽ0LTCtTjj%K͛2v̽{-[L?^'&&vJ&_GG0))iȐ!F«NdYvٲeRT",X@׌;w5unO>YYYwyk!~~%Ku '_-[v]L\.0aB˖-ƌT*k)TG:Ƌ/ڎ1ZA۷nکS' 򈈈 65`kxb```nnѽ!ڵk[jemm=b4=ǿtW^ի[QQzΝ;cǎ}"##/7^7M˼y&N8nܸr纥A<!CܹS*Ξ=;!!Yf\.wʔ) .oږ-[O>iҤ|ң#G̜>}c>ݻqqq'Nc,,, xԩS_vմ4''/3::Zkƌݺu2dSRR dggg|B|5!%%e޼yaaa~ᥥ/^ķ Sٳg /ϝ;WPPۅgjux 6Q˖-;rH\\\֭rcF,ڵm۶Eۓ$ٮ]޽{ jvӦMAAA_}T*IsssRRR GGǨQR2 ϟommݿ>}o+W hӦ͍7?KR'n޼944ajժӧBJܹsgϞ=sիWmۆ 3gΖ-[6lذ~s^G֭ ÍDb@1333fm@@oh:ݻwS5w\HGuYXXxΝ;-YdԩowׯZYyyyaaa˗/իW]%|\FٷoM/Hsϟ{я 9uTvv{Cԓ S'&&~fff;vlD[Q0`@JJJ֭{jaÆoʺNq4iӃ&kHfff@@w۵kB&u:{I&MV8˲˗/ Ooߎ:uƍ'Orww }_x޽{Fѥc4iRll,m@onggץK>?rH___<`% ǎqƍ_~Ew9A~NNNGݸqÇӧOť?x;wd{ܹxH~$kժsפY[[nnn }A D鏔oСڵ{QMMX,Ribba… " ^xq|||͚5;p˲OܐY`ֶm[=L&cY6??w_6lذa+V8::5-ʪUvsss>ݛ2 @2Lt{{J[njժ}-?ԩSGqСGUV ><''eD]t:uѣvN>}ҥCJ?ov{n簤e o^x?|Y&xjJڱczd2Z!w'Nxҥ={hҢ"//ݻXjjjp4:վo߾&Lz*R^^CСC+**òlrr_U ԉQQQ{|2˲ ۷o۶oV Lc IDAT=]VV3w\>ڴiS7KݽFϗJڵkiii )rrrbbb ֭[/#&Nɓ8PZZիW?c\}PPX_u'60sN]-YYY]ՓM2fƌ'Nܹsݻ ޔt_|EdduRR~/\r:88!5o|۶mlٲeV~G+֬Y3+++ 077o\J255ã%4PAACb{0xKYJ[v ^(hz(hѢ .oqwwͿ5Yovޝѣ&,͘1c &9 co߾>|a߾}'M! 5jS%,@qv+1.[E)x(Orri6l~N_e'Nܺuۻ{oƲ6t/cǎP 4ͪUBUUUu-LXT=JKK{9uTR|njx ԧr\A֭[wM6M6"Bm&,oݺ{3DrU @}AP'@p{Toߞ3gN~~L&+1+gBڵk}k믿d2ADzK,1ϟoٲo蟤NNNcǎխ$/L:&!!FN<88ݻu]X%܋3@u._[e˖}D"IHHX|9zm999ݻwvړ'OpXJJJt] tjPDzTazvȑyyyϟ?wܳgti={tRww@vMOx/.[ !矷o> k{^u u֘1c:wt9sp8 $V8 ~^/\[LLLuuڵk]\\BxfJ_oHG{Fedd>}:66'>}" ҭj e2D"ٿϞ=KJJnܸ[(%{xA}/^f͚YfMPOAl///A ]"=c+@⇯333kWR8 ,/++G8j.]׮]{Q4 ,A/_۷O!Yalsuu(**-SޠzMWܹ͛spKII/pvֺhzBٳgrʾ}*J 4~tD~~~JJ?B;//YulO~[\\V>}Z(L{EFF0BO>MII18/Y?Xz5 ?~<55ǧ]v۶m*//?>C//5hz;vݻhΜ9\[%_)5(iz:u4iҌ3B:t dF֕M*>|ĉR]T~|}}rʺf4B_~7 L2_lJ޽{SSS]]]]]]ك0hg Ì=zܸq=zHpE}}|L\u˗gffڵEܓ n {uvvvvv -f1_~Ӓ׉g0Żw2\HBӏ=^tiddcǎ}V͜9s̙!\wJe.--yBrp.beeb޽{d2ӝ[}BI&ikkX`:NݻٙNݻw/􋍍utt$eddHyĉ"7 gϞh?MMdBh/^p Ӝӧs&M۷3BM{-XSS3}t---kkׯ y<͛ MLLv7-###mmmqߊ+V^}!Л7ok׮%%% 0_~/_LLL{344ܻwoiiΝ;BH?wwǏ_t o>ṰE3f qBsn!$gʔ)W^MIIAYXX,^Ν;Z8aqZZZšx:Б#G=S Uق*$1bF{ٳgx;veϞ='N߿%KqhBޞJ>8DFYXXh4+_VV Mkm8dȐT$*x𠛛sې?o߾h}b޹8zMs=55ѣ7nk޽9rСh/͆yyyx:S)s>^;[kkk,>@& jjjB敕׮]khhDZ`ƍdr:mXQQPnn{ٕlNYkA33ѣGo۶ ~𡩩 !4q .D[[[7  [/({)_3$>|@,**.**cLxè=//VEE}zRZ D>|߶Rx { &tJPd\ߧ'JI\Z*mGR*RFFF~~~2pРA!!!2j[*R+WՆXp@U| HOhZ!SWD$yyyzƌT*ZAzǎ{ٳgK޿رiӦQx+8gΜ^z߿@@XWWWo}[y8pϞ=֭D3 EV|# O?-Y"+~|򬬬m'tWp.RTTLOOokk_(.޽{#GY~=^sݖ8ƍ|󍱱޽{= knnkmmE&1_nјUVEDDHGq{YlNBb"۾};.K"3޿k. ^z Ǐ?ydMMsܤ$g*z̙#FxyyTTTύ3fp3g mll飄H¹s.[6""޽{"􂂂$WAҚ4iґ#GB_y󦯯/(,11B^ZYYyȑÆ ܼy322r&&&^^^"3JJJ̌-..!yyy;;;bFB'.Hᄅ<{?ON-%qĀXmmm@"{=o<__ߝ;wL2&h!Ӡ [l/_%2wޯ^&buB:.I&I8 ~-..QSSp3 1..b̙Ն"?Μ9sx$u#feeEܳD>@@4ׯ7gEEu԰X/_"f͚u傂. GWQQ`0ݻWXXX__/rA IY__p:=vXq~nANNnܹ=`&NXZZ/PUpBCCtz~ c0gΜx"3/_>~x;;;##իW'&&2L G\UUtҀD{hiiYpaG;+2J}ta)`7ghh܌_eddtO Dmٲxذa8 <<<##ȑ#L&… k***x<"0iҤ:66vԩDvc10;;{rrrjjj̚igΜxVVVp^:s˗/-,,݉it>}>eʔ!Co+N^5>>~ƌ ?~|ӦMT*۷aaas3g}Vϟ?q㆕3rq U__'FGdX&Mze[[իWBJJ@q2 |1!xbٶJ@ssܹs'LpA7 6;;lO}wk֬BDBh͚5߾};B`\Cx:~֭[m۶n:=^z%G) IDATG3}섣G*))O`!>k֬Au2)+?%ڳGP(!DD|&Y{&%%?~Ó&MzEXX,--Ξ6mׯ1Y<`2w;V]]N@:;;{ذaL&s޽:{6}t:>w\&fΜd2l6 g0ڵkͷ0jԨ'N|7**j޽999L&СCcjii˛ßɯHGG'../\&*ݴi#^ޤGkmmmnnƧ},bXƺ:&B: X__ΝSN%''O>=..`XΝ;ϟ? [ZZPGB /__|񕕕3f̘1^_7nbϟ?2eJzzիWSRRB߹sGNNׯ_q^PP*,,TVVr劽=N_`C<;j%%%"?ض7n^^^X,fffܹsWnjj266' O&1|տ;-- ϐ.(immͽp³gD\ ˗ϝ;G\b<<kC􂂂MAa $i'O?~A}v___{{{߿W#;;+W *++[[[322]/o"VZuС}%''oܸ+..VWW5jСCqUJ9sfĈ^^^---yyy O ***~~~zzz* >߼yjlllhh(>>nnnՒ"7Lqqʕ+}Z__…׮]=`0|tj7gᄅao%r}iX֯_rw؁իYfw\Ѭ~qqq?ƍ;EYfϟL|Ç߿Js׼!4p5G|UII TRR"Rd`Cܹu4!9pgϞK>j6mz7ɓ'yyy"'8@1660`-&***3f̘1!m6===kkkdUU;v-^_C~-..QSS#I $Hŝ %c%tRSSӤIƍ4};wcV$cc+Wܺuӓ`|wj*:~xx-[,^7 ƍxy' ?dqx999%%yyy**/////g֖AhO$P@Pee%dX|EPF'LА._%T*wD>bPPбcn߾1 4`?߇ONN&։޾}zj*ƃ=z4>>e˖ ;vBEEPTTw^aa!q+?Q'ݻo޼ا4m >|'0!{ddرc۷l#25G>;v,Q:oحҘ1cVZaÆC@~4!$''GѴ)û  D2 Dmذaƍ{lժUiii?H$Ç;n8fKﯣcffF)lǎ ?Rɟ;JHHݻeRRQ;oݷo_{{e˔ L&){ _P liip8<z P/^hii Dxeee:::Rhiiinn&8Nyy<窪VTTXYY}IIWSSLjiiO > A>×d8^~ИiΜ9uuut.{ WDd2iiiU TAOTA7o Ov7n$na&&&ݯGDDD]]}FFSRRFՍ{.F344$ D0`plmm]=qٙ ax07e%5e899ݾ};==]OO۝ DzW^mnnN)*ۄPF;3ůKW9uʕ+weF*Oď999xjW^ݿ?*W 'tu+rx<qziӧO|G&($///99X[[Ϙ1?'.^[YYibbbnn>z^z}C&k0::+AԹ y .^8{l8_Nll+oቤ:M-$$Ʀ͕~NqRf $e\"i'N<|C ڳgO?$;wnݺuB:D`ڵEEE;(M֠8R%@,EEtb0ÇWZUQQd24уEEEL&xu֬Yǯݻ7rH|xwuuu2LӉ`9AqJHHx],,,p~]x]]]&YQQ1l0"}iСLQd٫W\tRkkkkkPn%)2PWW799y˖-~DDqa<4wpĈDMrgikkh4bQ5 ''–Y8Nrr2~:"??_~!ss*[[YYݻ ,[BsQTT\n]MM zLӧO?~+agnnn޾};yþB/_XJnٲ;wĵϟ?8p#G>7߈hxª*__cǎUWWZJ`'&O|ɈquqOUpBCCtz~ tRА!C&Nd2E,_o!fd0gΜx"v5i$+++:}vq c0ϟ?r䈮11ݻׯ_/~ppԬU2|رϟݻw%%%III"Fhp|800P&)))}tP࣠ Ōsrrn-륦ٴpĄ >}:ӕ>BP7o޴|vxRtW1yG'''&2554h2WDXD6E^x񢾾XrrrDF <ܹs)**jnn.))m@+WCfdf͚{zzR( /geffڵEܓ n {uvvvvv -/ri3ݻwK^$,ellhbbB"\._2< It:+::zҥH;v j̙3gDBcǎNP*siiiGh2 wx~y'B@*VVV,㥧;;;!__}3󲲲&Mbt;qw^ggg:v=?/66ё!'N'={ƆFx65IÇ_xQpOsrrONΝs4iÇoNd{-XSS3}t---kkׯ y<͛ MLLv7A7_06_?TAq8oŊW>tB͛7xkVpppHHȵkג Я_/_&&&ݻTWWwΝx!Ǐ/]ڷo\"3F8ܹs[n@bBVVV{3eʔW ,,,/^|SSSM{-8{l--P<_Bȑ#QQQG})B*1޸577_:(TA IEEň#h4ڋ/Ξ=KLVcwwwٳgĉ711^d#m@yxxSÇ炈/ҏFyxxXԀiZ%nCX!C"1Is犜V[}}}߿ԔSUU3 Z a(==ݽ)??!tqƹ)))-[?}n(r͵l6fs\\l6~!}A I^~gaaa+.!Dd'd_yϟ9GO0nnnU&nC}%.)PJx 7.O\ x툈9.T೒\s\㥦l;;ɓ'whW\ӳma<>}ƍ4557~r[yɓYYY2l IwVZu՛7o#&Nh``/a9sgͲjFֆYVf ƍ%!DR'OZXX 9pu묬 t%Pdd[j{ LMM<B<󳴴 niiGGG1O>nLfUf5+Bٳg{9sfС'((#55o[VV{EEŴi\\\>|iӦǏ#l˗/'Nx̙ׯ"JKK7mڔٳYVЍɬ "Z[[H$Bh̙!!!} {mnn JUTThT*'NH+Vhhh?~ч& 򲱱{BH^^f9s˪_1UAjB)((ZZZ4Çv97ޞ>uuubbb~rt2}ӻqK.?PWWW[[/azzz/^BϞ=>Bhر ,p026lrJFF^~AKK /ihhp8ZZZFǏw8ySNxgϞeddL:UBˋ=<<( ŒU׺J{{ڵk?ݓ,9s&B(((H]]L&֮]600XJ;bĨ"̙koo{#G+[ZZFGG\2,,7$$D_xZf %޽?vu[{+..F\sÆ [jUUU.DߪRGG}bo4'Yuu5Fϗ$S,M[6fSmoR!|fl$T»zzzE.HtWZs1]ᮤ('G&$ !!FQc4v8BB,<fwtf.Q[[+''%G/))fdr/aþr''biUUgUֳ*X[9^" w \Rxiee%|,s#Fx> 2gΜSBn rJgbq8R]3Qr,DHH$9 9h)r&$Gk֬.|VYYYI//11mӦMGO0;)//p8{  \)ȗ7$o2ߍ>ٙ[8 -..VԱ)$ IDAT 줤$33$.%]rrrpꃜ!y%$$tb'+WjԨlϩS333GmooA>>>vvvC%xų޻wo!!!,k喖UUUx,SSSooob !~o߾ pcbb<GGGݻ7}t333{{{O/6⧟~"nـo߆ ={vQQ5|K"{Ѯ!rss'OܧO&xvvvL&s۶m~@OU++T}~Z:RPyWߘr< 6Zx5\yU0..nΜ9Dxɂ CBBڮ_$6[CCCNN۷lٲbŊ#ϟ_^zӦM\/_>gϞ>}zԨQVVV/_~ɓ'B3g\zG(ѣGTYYy "fϞmoo:c M6l0v ./^_ȑ#oߖЀ/^DGG ?**hũ|kGV^^>uT--kܹcǎ\p!&&V/n~]i~ dP<@QSSsLFy膊Z)+Q(2U\4##!뛖?y$B URR6l^ڵkAAA{ן5k&)((2dڴixt{{{WWW*:bĈ>m۶2 @SS<}!cnn^UU$0Bׯ__p… KKKi4Vrrrx5A} Bѣ}_ _Mrc%E]YAQ^NaF pm. t.~Їۏ?xbqMyikkkjj"())ٰaC~~> &Ch4D"d2^ˉ)a xbXc$x4hѢEk֬O2?Nd2q\{OCvM=ZYYB߿?_\_M% UCUIIQ^BvVPںFvTd$$$xyyYXX% /$#reaaA"ohyܡ ܹsf<)((/Ggݻӧϟodd$%IL9+HgA&@ʞ w׵!###{{]v}.LRkiiWYN#kkS4-eZ/\vZ[[ƍkڵ/^3fLyy'Z[[ e:::vvvю;:jsssjhhrCOO={aĐDbp8weC 166&VUUǏzzzk>e'~k'ȕ]R[leX^B>}ݻ<J"MSɠ v Ġ++HKik4Hv̭ 44IZ46i!*E_Yf]`ccobbhѢ>}m4|C>5М9soܸ!eOfddw<"t޽!??POOO;;;kkkɓKTz2# ݎf7G;)#m<Bݻ7}tGdNL&3<<'22'm۶ v)ooѣG} Bѣ8bo߾)Bll!CM3PrryUUӍ7p_?eJueEy9m[fܿv!4+XOOᶵs<й`bbB>|hoo?~ŋmllF(..FInOIIɆ qv!AZZΝ;qu .H!---oǧr֬YNNNSFEE} Zhњ5kTjYY}:++kFFF_?422ߵk4244P(Ν#j9{+]FW[I[MDג"+\t)F֦еiZʴ^*4e]ֶqZv/njS^^~ĉքÇ=zc677gX))));bjjc_555uVcc#ZNNNCCÉ?~Р3j(ExIJJz}CCCbb&weC 166h===T-[jkkY,֫WB~~~O{.rxzz677ٳ!TUU%A$A-WVӑ6&ijY[hhh$-mDB*T\allYHlllMLL-ZԧO2㏗.]\d (lժUVVV=o,YRQQ!M_̙S^^noo GGq 2dԨQC%x5mmc?QUUE)!TTTh4~ROOOx^UUU?*sUo}zffرcwma֭d?[D×ZٚV+,R`m_Дz8dJbs/\[߆9B..n͚5?JMM=ydBBBQQ#!=/iZ$`\Bn޿s/H| H:;sLii&Ƅ-ɟQMM|p \\\H_|1tИLRlll:/BH ߿ȑF)((@۷/,,L# dx 0SBD|bHਿ YYcY[0))I&#Z[[zÇ-Zt___p_)))Yzu~~3Bbcs5N #Xݸqcjjj߾}o޼IJQHm۶m"AAAo߶{ԩSHʚ.]J~4vΝK. Yr,T߾}<;޽{ƍ&M"5kVwR($N )--mii'?{D#H62X3̤e7(}sXrZc,Ų#""<==IJee-ZeL˜'EQd ]'3gyRVFc2_~;v9r$33S&EDDt^*GGGЊ+FmURRb^G0̗_~ٮt񬬬(Je|gմرcܹ?Jj\vDRݫ. #c* +R.r_?Ѽe+g.Yb NW%>_u={Vծ]vCnaaavvqˏ=h>^,:4((g!'22*,,lĈcƌyU`T*Urrrrr1zz{{:TT_",Y~Njjp„ ʕ+2xQQQ/^xWPPiZoGfϞ;uT???>ȎB n.Bƶ a(i眻Tv5=-Hss3EQ2:9W,ܐ6 IDAT,+J1Ƶd$ȣWTT(v]555$Z[[[:)UkkZnjBa,cd#hH#i4Z-ɏմݻ(v1[988&ZoGXtppx"?|vD';c|ƍ#G477wrUDD7lI >yo0߷@Ba;;;+++Γ0FPemmmee1Xy( @1z/z/}zիW766OGA[uȑ/K=]mSQ޼~my| kwɲ#8=fÇ?rӋbGS4MQBa9#q0`k?^d"c`My8@ e/zuj67*u ]EAݥOg84=6.Fi~Go`e˖|1ӿ!j8`\{۶m_?䓴4@]z"eqppx}_|EXXXO[222:_?5=eY,תz Wo[ت~ [ՠ?s=F³u$bQp׮]oVnJw`YvZtiRRR=z8??߸CKK˵kzHOߞOyy97 D,?t탷f]S]8{_Ժ6 Q`0$''0 99|իWBEEEuuu]ד7t:]hhٳEz޽{…=e˖ڵQB ~^CF655~}hWumCuM]%8jGS_ba eʌ"233<<<͛g ymm3<<|8-----M"#***MbŊ\aeo>/ #|~TTԏ?bŊui4 xСԓ'O&$$!H-[҆ gggwtɓ'3g3f䊙?~eeW_}ZVV`^VVe''ӧOKGYQF8q?{lFF|111IIIuuu}o߮RN81jԨX'EYY&.\pĉ'N8|ZZ9w޼yͧONII+..6çLFv֦V^=~'OZ[[wt-ޚJ Z{ _*ӴHXYY=ݼ^Z }yD>C ڂΝ#;Κ5+===0LllX, >|89ٳwrrׯ_'|VVV{~5֭[CBBÅBaAAB/3f̄ lll8;vBhcƌ D[2 cccJRT9rãj.\x2+ʝ;w͚5+88!1cV1 A-BFTx<+++J%Y)=ںawwwkkkggNݻwϟ?`///// 9s>{,Yn~IYdM67.$$$**̙3iӦEGG4h͚5uuu[k֬Egffĸ)iӦ%JBH$R*BZx?x`{ԩS:555-]H&ț;z{{oիWoIcԨ8HbwܣRVVֿfttL@H$Ѿ}vT.o˲7nLMM۷͛7M/&9!]^^^UUEx1+W^ nJcɻZܹsҥAAA111+W /r'Ƽ?o7O@[lBX,fRWm}Cƾ9g9N1ZLbY$-2>A&_EtM3 8y B^^^_Q |嗦\PPЯ_?М9s.]V̏TTL oS{nMMR4&vtŌHoܸA^\~!Djرȑ#2<5WPPM Lt" yZ;99?~<+++**JR͛7E֘y Jfgg7ѣ~!{{{??{644n޼k;wÇI&!ƍ_SSo?R:uZ޶m@uUUUByyy999IZ]]RSSB Fg>~ŋ1Ǒ/n@hVYYiiv ׯtXGẄ HNNniiillLJJ tuuʨT*PsmӋ@}W'O6N_teD\NuI$Y&%%?LHH0_!fݬŋ ȑ#]\\Xh[c f{EQrgg PPr/h6\IH)GR!ӏWf###M MLLtuu}7bbbݯ\bjժSNyyy-^ !DQԒ%K֯_ ~ vڎNiWddd۷o4gϞ!C 2tOdɨZj̙NII!yf@@@``^_~P8~>,!!A&ung^-͏y/\Zz{{:TTπŋ1 ZٱR(]^U9<°%2>B|!4sʮǽv^477See02hں:jBa2L"ejkkRHKFaYV*bkkk;B,VVV:88YѨj\N~diOiBT.3UWW0L>}:91;kkkmmm꼽\]]M//|r# J{{{I]֚,b`y6ݹ5?oϥ;ߴ{0N~ȑ#2trUDD#2{"!!$ -~lO~cC(!!0Ѵ]OittLe{Bch7EQã˶mߧgky=j~=skLƏ+QgP[oݿi!a쬬Hst)LReh@M> } Co )=O#;H,QDADADADADADADA̚]X\ VP{A( ދqy1ْ([zq k׮ݺuv].N$( uȐ!F?~{?h D}x4U(Ÿw7U$wr0U[[ `/R*HR@jO׷.\hhh!DQiCVVǏWTo sgmy%zDk ͭaLP:02?qYHG3h| tZ>sLHHR4iK?w\QQB0`@ppT+W`HLc!qsVVVFo===Riwr8`Js F F4u(0:";c/Zk+ Ot{GWWANz:{ ϯ/UF  B9bTkkiS @8ϗ!NDz,gcoذ!444??LJaCBcL0q^(JR)ׯGveᄑ(c.oq[ ӳ`Xg}吁C䵁aƆѓl[}'m[>k|Uy998eGW},c{->b,2pȀC:״rj=tUJ|eM+mAVqㆳH$"?r'x ~ .OII7nX㥓 `@nb F B''kq\[[{:0BE:Kx!EZT!!=1B,4i4P^r Z#B&拟#n?\ڐwtjveh)`O)DSMlr5-lƠ5`h{OwG0 clB577xrNXZo0ϧ9d1ƈ1hA}n!Z-ɌC(tMd>}f((@z[`YYYqqqVGS4roߎ0aѣGe]UY5m4\~ԩ:MчM9~'W|)aSi&!/hYYYIRVe1FHDRa|y4GhA}f(iQ hD4fPm-5x>nE!!Y_lGXLg) Q4MhP<(>DzXј>1 tt*Z[[||(HZ\nq6B@@yZXXO|'N2dH^^kv!dccJLL eYA.$R$ԮGcH:ϗdcHQ^/ F,y Pi(<#caă!,MҀ%Ӏ!EZ|>9w:yc,qqeC"Gzyy:99PZZRPP@~ljj2@28|(h&{<06rBiy4 /nV*WkY]= c{+( >nаI}ӯ9?DFFXX %v(]cIV+_B)ix(*88ɓkܹ֬sᇶ6GGG9]iJURRR[[kgggO>!t)䘶n 0NyA7+i G79|;Bųҡ(W׳Ӆb@!)dʈeVs1<eƈsLZu}x5 cg<.P%Zi[yAjLy͛@ҋN>0Luu˲v-9cpefBpÆ aǏ_tY[[k)yEQM tJQH$RHih!i!h!(!<'. Ƭ 'e7b㋳lJ(ģ)!#o^ӵ6c!>-"m,Ճ h'B>t\"L+mAP(߿]]]KKB0F111FR-\VڴiSdd+o?,Ȳ&$ x44ݟGDS(B!ޒMI1+?Ki?i[OcqQsFp @ CQQ7| IKִGTccݻw===MyXd㚚;;;ԕr;;;@lEEE~ӱ`҄^^^d! @0ƍϟ6mBD<Qd)VА,GݧO,%ʲlmmmffOwE*EeF%kѴI<]B$4c(M9zC]!!!)O(..(²lYYYZZy8gkkk8{qаw^N7qDGGﯤjKJJ.\P*Sb(t hO..E,[_>Hl ...>uDA.V[\\|ܾbIIDATYftti`&o>;;1cƸ?Ҟd۷ogffWO30G 016{XCa89̽~9mk?ŨQb1i߾};==}Μ90:<;;~ <ȑ#]\\9䏤jݻuV;dȐ#Ft\)ݻ{u_.*P F.)9<' <J$h CCCý{_~Νz^|MwFXDN----***--mjjtܛDӴ@ VTd >OB`QQQFF \ijZʲSRZ"H$L˲Yѐ؞Hx Create New Scene File Create New Scene File
To create a new Scene File, open the Scenes Window by clicking the button in the top right corner of the Workbench Window
  • Click the New... button.  Set the Scene File name and location (default location is the directory where wb_view was launched). 
  • To add to an existing Scene File, select the desired file from the Scene File selection pull-down. If the desired file is not an option here, load the file using File>Open File... and it should appear.
  • Once one has set up the display of data as desired, use the Add... button to add a scene. Enter the Scene Name and any additional Description to the basic information added by default about what is being displayed in the Active Tab
  • Toggling off the Options at the bottom may be particularly useful when many files are loaded, but only a few are displayed, or if one wants to create a scene that displays only the Active Tab, for clarity.
  • Click OK. The new scene will be added to the list of scenes in the Scenes Window, along with a thumbnail image of the scene's Active Tab.


workbench-1.1.1/src/Resources/HelpFiles/Scenes_Window/Create_New_Scene_File/Scenes button.png000066400000000000000000000065351255417355300322140ustar00rootroot00000000000000PNG  IHDR&(ۙ ciCCPICC ProfileHPY_Das@ 20q`Beqր (5 X wEA9&Tnz_ wrDXTa8ۍ=<k7M wwшnqV4.Pq4n*g\8aЗ.B0M,1Op|HgdrH̼B@9DaƩfa?SϸeVxA(_nVjJrH)3FQoE)A ~0,dq ^' D>i }8A,oφF,-cOxY^+y@%0HA "=~M˻et l!Ԙaina?f;{ :]Y @ <"f3<@ЀPZ@ {i + (pA"HbցM `/(ap @38 .: #A8Q!%Hҁ K 9C? EAB:h TCeP%T BP?tƠ'a f0f~p(ë8Uq _o<(@(wT *6 P%*T= ՍQX4@>04].CC7;7C W 18`ؘH ǔ`1.mcvXl6 [=mcI39q\:.ww7}xK>/oƗkg) A@$ل#6 a(K#:CIMRb=D"iIKIR.tt4DH#1 vQr; BѥR)%c)['Q\Ij@4AZG%B:GD q GfLL̠̤,UB6P6UHVNNWS''wX0EբS-#. KӣiIB Z/mB^NZ>\~|9y Eץ)S;O ,xm  )*+(6(VPTJVڥԬHlT9Kr""EEN-U9ң2*RݯzIu\檖Gژ:UY]GsSza54iZ Z{:&յhӮӾCa$yUYwTOQWPZJրilp61L4,7a c퍅Uƃ&dIIɐ)tiK3mh]ff_mS̏?lfВkYnyˊbeѪꕵuA6T%6[m:lيmmb*4fycfG[tS88&;:.[a'M'SęEÅRU˕ZeJbgt3w5wwp_( ,|^3Ves5 _;~d2'b%%< 4@vGAzA~Y]|`u!Ԑ!!BBw> Q!4\y=J9J\lﲑ;Y~uVJ< Tq&qq\w> +oo,)8YSBq(߉?X8.p ^%$Jz|4y:%"!*& ;WZ_d$IV;޻zB'NҖӐ`ӓ]Psfy懬kddfo~ZZڎu6ZZ_cƼ#޹67%ouoDliS||qVǭG/wն۾ ~.]'laNN;\v+-)޽dwƞ=o{ĺ>⾌}RҖw\XvܭBb[]R=TxӏVzW6UVrXHV.n|槿+(B3y pHYs  IDATHUOSAݷIiE`ZILl Z!\8 &p)$\_ y^~z2|댼&Ho@P(>BPj1|HĆ{Og2յSlz:l߉n<վC_9ppXh{iGGFFF>F^!}/"DB[s)FEv Le↣0e'6 3sEpFh<4k1~9o^`(19X3Ι} wAT?>Gow  Root Entry`A۟z256_703a958688dfb18d*8) )KFB )KFBJFIFC  !"$"$C" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?z7wã-@ eKFu +(<,Csgwh OƓ]m5RrULDgFkWpw¶vC(V l4BK{t)1Ii2:ֻEt}@J^s;[n/soߝM=]m4+6?ƶ(z7GFW@l o?i~tm7oλEt}@z7GFW@l o?i~uk@ #`) 9W+6?ƏEt}x^Vo'm3 U;:S^-gKN2E: ^"F2 #A?Et}@;ŔSxz,! XrB?RMoݣ݄7 oH'$Jǫ+6?ƏEt}?<@?❸d,]PcF$+~'?FW@l o?i~tm7oλEt}@z7GFW@l o?i~tm7oλEt}@z7GFW@l o?i~tm7oλEt}@z7Hu]"chW@lmW=Guӣޛ 7Jo#'hN@ֺ7Mum¬Cm+H봑C@ ڡ_/a>ݧnVwocmk$O7+ ֩BOvy{>λvٷǧiKjUQE{=Rkx٨u -ԟYߦͭߓkMJ"!I<@_MQŗo*#}Q<@_MQŗo*#}Q<@_MQŗo*#}Q<@_MQŗo*#}Q<@_MQŗo*#}Q<@_MQŗo*#}Q<@_MQŗo*#}Q<@_MQŗo*#}Q<@_MQŗo*#}Q<@_MQŗo*#}Q<@_MQŗo*#}Q<@Zi^2!ibύT&6s@q#5]bdڽ"J*K{݅Q\gXQEQE6+?WZY,' w,y;;x8 N8 3A?V(>$f˳dey—W"#F@b&<)_;?S$y" XO%b taun1 $#p:Ŷ CbYi>(5[OPvm]eBZW%Tܸ^3kvR[fmy!Rjb0X XXsPi!؇׾Fa7 vqpuK*{4_!fPyY8 m;\<~RH;H!211߹MCq-熥Bc V 98bV|ENGk wX_cIh|2?2p;#0s1W((((((($ú~/jW?u\_A4ӳ3| ueuXIRgf0#oFx">MF,KY|DAJԮexתdsa(:[EW)QEQEi )UEXNdG+Ԏۚ"ޡXXx;+*E1 9Jɏ+ 9v:t͔b ǹRT geVӼM*ig{e\\3$\I[R2ΥuΧ)P]^br@@9Mޟkj)RU<[`o++ V 𧂭%ċmr lMPrH0IDŽY/`{Mit%Hٜ(V~lnx8$V:NQks:Ioss+$;Kxn6]#i.VP4c~39gOQ?U] ]2>X;R1@4\Yk+ h@.ԌcU?6ݺWu }-氵&6WO12Ć \V5 L^SOu4;4l}yOJoPY$qyDUinNR8ʨ269|TS{s\h$0Ǣ]>׮4IfD2Oͼ?7?pqWH|FT-Z𥼸6IYנݒ7~@(7f q>dTYB1* V95+]E|W3B9kePLSȡHخ&Iz^xwP$ t!mbRfehGu8 b0>G[~CID{iObBRh,Hƥ)gPNc^.awkm41*[( ( ( ( ( 6jWu\_A4o {_4e(((OLmqOU6*Gsm>]ӿҸmPMgt& 3ĩV> ۴pyMJC{f$Aya:n @*@9M_q]7.^n!a*#|cc6r1Zn|/\I[ dPKwqqQӬ> Z۬ͥܣȎ_;fU0I$\q0i-~!B vխ$h[jY|M7P19|tF<"v _h, \kMf5Cq_rA>s:嶂zxzze)h;sWoB 5e >%{dii#x JbA)4J@$)gn{h=X>C7ccזFઑHԼsž^mlNp_,!IJ)i=K6"/݆3Ԍ%O8mSK˟YI$VFhmPMrvhr6$d0 6Pm56"HYvw Hk~9C4m۲gl}Bd`7, #/V{hYH5{rF|͌X}e[HVfg"f#%8~@}(RXYh.Z´h_+i\;PR4:O ^3$o/O'(={J;mݔ&d11& FU&k|83<6i"DŽM0\GxX$6Zam"[MUQ +$FME0lnf# +Gg$YvPxZ{0fӪG%ImmIIU?w`(u(`cy4$FYK"ŰŖ$? Jce_F[ d1‚@[EPR$E 2]Q|4𮛪Pif@OX5bG}q5/.eUX~gnq*v{zV?tkAio;Vc''瑂"}C.~W2x\4q'$"]TR+HH@Y;49nZ6l"5oG|t~f㚓*]W?eݷ67r\NolmM7|Lgqr2[1!@bZkrc<# rA{_x$B%FtfLF&7n ep-ltlbIvSO",$󉣘<[Madc1@:KXOwE"6 +$V$o0%Tխ[Y-uio-#V K`rpUnpQ-IxCT-2@ y I}}+]&s5bE1Ur|َёm1>Wv\\i1gy# >`_PsRY#!Hi#lX +qD)n[V(9w,y||4Q\k{y- Xx{eѯ-p qPcEr__M}.Xei:ɸGCrvE; 2FQ3Rx`y9vW!XlEMr!WtT $y8bf ԯ#'(((?W7zЬ_>4-B ( ( (9]MB:~}=FZEIjѹE\J3RV9"#fjޢ15uiǨ?U?(cPWXzC]cآ1NzdYb -2F|lj*VЫO=GơBm?lQ@ lj*Vu+{P :/қ5< LNfZ2Fq[P?ơBm? ձEclj*QЫO=[P?ơBm? ձEclj*QЫO=[P?ơBm? ձEa֡}}bj?%UtIX#nQEQEQEQEQEQEQEQEQEQEQEQEKSa%,Sϗk@s5v!ԫGF*ár sޱiz͵RCokfR4YA2#M{|?xv[dM/\.^4YoY#3~\ Uy[8]}S=G[Gɨߛo5@tI,:~oG2fT 4[H HehOϾiMRe=@#~`J$y$$I@qj WbrR8n%3RrsbxUvkɕnf#y=r=:OIkwsf$bwVI6j_е,yV_xm_JK1!:#|B1oxwX#.^z:Jd(K ğ5`7/Ϳb4Ŗk&i  P*f.ɨ izworkbench-1.1.1/src/Resources/HelpFiles/Scenes_Window/Scenes_Window.html000077500000000000000000000072411255417355300263350ustar00rootroot00000000000000 Scenes Window Scenes Window
The Scenes Window is the interface to save the current Workbench session (loaded files and settings) as a Scene (in a .scene file).  Scenes allow one to save a snapshot of a Workbench session that contains a record of all of the files loaded and view settings, making it easy to get to a specific display.  Using scenes, one can "pick up where they left off" when reopening Workbench.
  • Scene File selection is used to choose among loaded Scene files. Use File>Open File... to load an existing scene file and have it show up in this selector.
  • New... button creates a new Scene File.  A new Scene File is created with no scenes within it. Add any number of scenes with the Add button.
  • Show loads the active (highlighted in blue) scene.  Double clicking on a scene's title/description will also show the scene.
  • Preview... opens a separate window with an enlarged display of the highlighted scene's thumbnail along with the name and description.
  • Add... creates a new scene within the current Scene File and adds it to the bottom of the list of scenes.  The scene needs to be set up before clicking this button.
  • Insert... creates a new scene that is added to the scene list above the currently highlighted scene.
  • Replace... is used to modify an existing scene.  The highlighted scene will be overwritten at the time the Replace button is clicked, therefore make scene changes before clicking this button.   When a scene is replaced, the name/description of the scene can also be edited. 
  • Delete... allows one to delete a highlighted scene from the Scene File.
Note: All additions and modifications to a scene file are not ultimately saved (to disk) until one clicks Save checked files in Save/Manage files (accessed from the File menu).


workbench-1.1.1/src/Resources/HelpFiles/Scenes_Window/Scenes_Window.png000066400000000000000000002266351255417355300261640ustar00rootroot00000000000000PNG  IHDR] ciCCPICC ProfileHPY_Das@ 20q`Beqր (5 X wEA9&Tnz_ wrDXTa8ۍ=<k7M wwшnqV4.Pq4n*g\8aЗ.B0M,1Op|HgdrH̼B@9DaƩfa?SϸeVxA(_nVjJrH)3FQoE)A ~0,dq ^' D>i }8A,oφF,-cOxY^+y@%0HA "=~M˻et l!Ԙaina?f;{ :]Y @ <"f3<@ЀPZ@ {i + (pA"HbցM `/(ap @38 .: #A8Q!%Hҁ K 9C? EAB:h TCeP%T BP?tƠ'a f0f~p(ë8Uq _o<(@(wT *6 P%*T= ՍQX4@>04].CC7;7C W 18`ؘH ǔ`1.mcvXl6 [=mcI39q\:.ww7}xK>/oƗkg) A@$ل#6 a(K#:CIMRb=D"iIKIR.tt4DH#1 vQr; BѥR)%c)['Q\Ij@4AZG%B:GD q GfLL̠̤,UB6P6UHVNNWS''wX0EբS-#. KӣiIB Z/mB^NZ>\~|9y Eץ)S;O ,xm  )*+(6(VPTJVڥԬHlT9Kr""EEN-U9ң2*RݯzIu\檖Gژ:UY]GsSza54iZ Z{:&յhӮӾCa$yUYwTOQWPZJրilp61L4,7a c퍅Uƃ&dIIɐ)tiK3mh]ff_mS̏?lfВkYnyˊbeѪꕵuA6T%6[m:lيmmb*4fycfG[tS88&;:.[a'M'SęEÅRU˕ZeJbgt3w5wwp_( ,|^3Ves5 _;~d2'b%%< 4@vGAzA~Y]|`u!Ԑ!!BBw> Q!4\y=J9J\lﲑ;Y~uVJ< Tq&qq\w> +oo,)8YSBq(߉?X8.p ^%$Jz|4y:%"!*& ;WZ_d$IV;޻zB'NҖӐ`ӓ]Psfy懬kddfo~ZZڎu6ZZ_cƼ#޹67%ouoDliS||qVǭG/wն۾ ~.]'laNN;\v+-)޽dwƞ=o{ĺ>⾌}RҖw\XvܭBb[]R=TxӏVzW6UVrXHV.n|槿+(B3y pHYs   IDATxw|ϙ=7B &z 4AA@ "ݫ 4"RBS%}fuM 27|dH6D 0 EQr\&I%!iLf'p篟&RdXRWVQ5m^Ү#C9f]?}†epO^ sckPԅ ?B۵S>v{IIIqqZZ0oooHTٳN>^j֫}x/nNJnUzf=ǂu@ٳg322ܢtD[} ׶nk&Fᄇ*^`;{ݦqPxm6~~fV&H0vZ EbD*:+jjogTMLyAEMPT9Aab/3g`ݻwNNεktD |Dc"ɌF˜XV\x MBny"uؐ+++]\\<==yqiJ4Mb'LP~qBGK"V<pI!UK%:kxŁ@4V( ^B MDu_,?/B($p1@M| (^ϫ3;EQnnnqUB>0P(ufZoDxy" cr\(iIi(#D"׷o_g{uuUΞ=k6}}}W^q܆ vEQԀ&L0ܹs7o|֭[[bp4:::55UV.^nݺk׮IoF>^&  㙁(tţGx9R_ ^-#X an5 o]8C Vol WZfJ?Wp0.3S&T 2 w̙3g?kժU~po`FsUVkdF [&B F 2*f<8!PK9 %*}nURB%R!˲}._0 vS//ٳg/_ðd k՗i x&+[ma4VF}ԘP!8?K dGvdG^if V;_^-wqŁ8eH%`ͼ e1Y6>s=tP\βl swQ(#Fضm_\\\C4M=z?jٲeo>V3111**^^^waΜ9 f9+Y)F)% KQQL:q4MC MjS@ xzAvdjFk}v?n+,X0 Ȫ VRZ,Cu1ƪգƨdںukxjݴiӘ1c_! gYXBѾ}͛dzk;r.]j2GdBBoTT֭[y} (X`Y1"0 p~ \Wg{~:v8waz+W  WUU}o4i(_ ˁq4M[VpDQQQ.sD:thvNpÇƐ#GΝ{֭6m,_Mkʕ˖-;uTnݾ >ډ `ʔ)Mr0 #InC>|q 40} aÆ Ʋl7޻w|'w̺?ܹ 'H͛w:8p 11٬:tؿb˖Bꋓ,˲̣*dGgnY; p! ˦v|cvhpXX ࡦ$IƸ8 pgY(bpb)_E]vJLp¤.IBZpW_Ïv}츱ovqq#FZ}'L0vXG~4rȆ;U6E8cY!A$y_=1cƌ3M<=iҤI_|Enn7|())>|9sp_nɓ''''o߾ȑ#{/qm233333/][jϒj7^tIb6-j$IG޸q#I$i5k֘1cxѣGǍG$˲v=5552""11qΝ$I:k.;;tN;lڴiذa|±c6 iӦA5k&MzZjGdڵۍ7,kZib/ҵkWޞt嬿O?(%0a„VZkrZn]RR8qիILI8qbVF#I'OݻwPPp֭W\IZ,$IjFaZVXԩS۷9yj`ZkOW_}աC &vŲr6m;vj3&R$yÆ>|k׮+V$I~'111~r<ٟtfTL&Y Ѵ7LpLv'*0&Tf* q 1 !aN`~aoڸi[m۶}u߯+^GQT4 bǏ~0l޼m ?qqn=aA( Lf[ jU_`A˖-g͚u5kִiƍiiiK,ٳ'۲eGhhhYYY6m8o.\د_?@lllVV֐!Cؙ'NW\Y_ wYii!rAAA[Qvj׮]FB[R+wnm޼yҤI?׼II1_l9DiӦTv!|ESvݹÃzzxy˦?s9jdZ*_ĉ;w\`}8tgϞ5 "J7o?oܸÇ{_Hpyk#c7Z&!;a]p~#asRfUR)مf!E"ҥKnڣGގa؏?ةS'a0 kѢ?GG:tL4}977GX}8_;?@.49 ԩS1116ͼ"o9MQQ`9s̙3w>gG8FGbg {ݻwCzyg trIb222O }!)))'N OZUUu|6ݾCܹ7o5jr'OڵkiYi|\\^^V[R2229~qN8}5km7TaDFN?jR\\\y]9~Fs' Ν;oZٳgΝ#yu| RC=<<4Mdd(O hne`Æ&MK徾j֭['Oֵkiii|||^^^EEG(~reV^|jͅnڴ}8qDXG9#;?v ,8898qcd9wC 8g(^ k#q<11qʔ)+**x ㅅǏ뭷z- z:rqԩS=<<Ξ=w<Os`77[FO?4...###$$<;w6dJh9ޕae˖}qO?TyCroxryppp^^^v8z*A#2|80Luu5*'޻wØ;:~};гgϮ]ZjԩgΜ[ou71 OjC}EL,8œn^ ꒟0QYBFCau_P(l5xO}h68Kүo߾nw„ {饗_JwwwL֩S>hΜ98w}ԩ-Z_b IDAT$hѢݻѣCyu+h&IR(޷GڪU~ŊAAAUVi4nݺ4(??7nܸ1<<<...%%n(}y ocD=zlڴǧ]vZv֭s, p˖-k׮8"~p1bo>n8ОЎ;\ F$ lݺ599Y(v?߿P_}ƍ| tĉ7ofYV [.99ǧN?R˼= @"dee ٳgLs>WF]OKK8p /mKV.\qhŋ?~ [nDcƲ,AG>}]83LzYf:uZbEII 6f9 q>c믙|3#c p 0q)LUVcըKyeL,  8~_H$t&[IDN&OL4i֬Yj޼ʕ+y{>} ÀѣlNII,bŊs# QD-͛7Ri eUUa*X^^.˥RiC%~oye\-}"e*_VWGC 4&&fӧO5ƍ;NJJDž ,f[zuNwǟdk׮Yĉ3g9rl6Y]v[,Ç6n8}cǎEFF/ZVad2YJJg}:thIIIzz:eٙ3g?~\.7~׮]dcǎ>裯Z,n߾gϞ3g>رwDƍ[xP( ?qDxx8?%%CNnnѣ"""/]k׮zygϞ3gNZZګʯ/e%K\W_WN>ݹT۶m߯]ɓo>?_tf3f̻[_9#;?mOO w&}?~eđ3>wjf8h%mTY-](GܝN4gϞ#FxyyI$^u:[n- Ôx{{ ~oh2@ 6$''[*9ȡ !X ?mհFH$ʼ`q`XfCi]Ւ$ɏZwz$- o4ı#1?OƷ97;Ί ~ZNh4|OR}FifUVVz{{;7&y=|S'#/o&1 -r 8AɰͰVU\^}*ȝhrW]]}ȑÇbw\6Moܸťk׮*q9RߛTNZ )Arۍ7޳gqSϽ~ZZ=00бW#/^:AAA}CƉȎMc4 @8@X*8`fλ,/r{K¼DYKa{8>aF-aYVׯ]n'''9Rj:~Z^|]oh_ಲjXm nܸ-b8""q@ uW1@^@xg/, 0 62V zT{k >\VV6nܸxQoKvvu!!! TPK|\D_i"` \@ ~doܩsN6{ru<(Mp⌌ .Dv6dx3f}vzzzfffi۶m[j!_gJbX,l63  0qLT*RT*mߚN+de+Mtq55z3@ FjO$BC-qW>*UNōw8 9^xwFw~?]{?n81! 1T H;K!E : HJkZ0C^ 0@ 1@ DS@ /@ M R_@ @45H}hj" E J%M@ MӠun,z`x?~No4BB ٧!{oC h8h@ M R_@ @45H}hj" E A@ DS@ /@ M R_@ @45H}hj3t$?~yyT߈6m<(p'<#<޺/MG?Zl١Cz:@ <^;vlee/]@ 1htRvvv~~hdYeYqr;'P JePPPLLLTTA<|[PPpС/zzz> qŋw)ɺt2zhL& t\L&ŋ=믿8Ժq<~_HqܡCju:ӣvӓGYQQ[?~|XXɓI/1̙3C i֬Y\\È@ 0w޶mox=zI$YD,&%%-X`ƌ[ny|3cƌ3fL6a4iRzz_|7JJJ>gΜ,׭[i:??۷o?r޽{K ۫W~MT>B ewT???a~?0^^^gϾx}my^`?$%%coܸ6iҤ={ p-[<<a??W^y_@<,{enqBCC ۳g'NeFW1Sc9 ~~~7nܸrJBB=pCcly9y$EQcǎѬY;]|ejX-[Ə*++:uv>L[<@ f]v- @,f^陦ire_vmlle˦MuqcvyTvGG3vh4l6E"nnfkDI>ƺo~~㸇Z^bEPP`ժUZn4T*={ƍg̘l2xԩ֗x:NREDD;]@<$i4qeY^ p7rY7vC \z%00a'$xѢE]tO$ jA[Jʤ~Ƀ̛7l5ӧOYomx<Τ_!qX88N{nn.I|HtaY͛Rߪ* T*\.K҆8d2r3>}!moժՔ)S>co/p07''p5P(ҥˡC8;\z7ߔH$Qo<؋aIzxP֋@(%#E P(l6jy>|i>uTDD#?8oڴu lذ!!!!44Bػws熇mܸqHDž;}~I | Cҋ@ #|||t:druu;~'~ ~=%^~ĉ###YmѢźuO>ӦM߿?SN+L< /zǧq;=1O' 5'A <FјL&~^ӰO΅@<J4z3Myyy{1bD"_Gk~PHo֬Y'TVV8Vw^av{YYن CCC >aD"QTTTHHHIIuGZ,OL'ǣvh@vB p ٷob1?Ék# S S}.$I޽;88 ;KҀ@T*FcBBBDD`J3f̘2eʶm6olZtȠe˖cƌ (*++l6w֍oxѣEQ| V2͎ 04Ge57YLNjZ8 PP$&&ݻ0aD"y$RͶi& EZݑ6°+ Fò,A$IZ,贴b ^JFҥ ˲?Óͭw:n+ TUUv̙ J!4 .\v'cfŜ3\۪RvĆH y/K).}܋.fK9E*{M+i>‚f-$r~lZv+߬E>E-ztri44e_2sHe{oߠGqxqOO.]>|xƍTO-F֭sww֭[+o ۶m'Jr9EQ%%%Z׷k׮4Mk4;zyy$)rrreWW׈@$IzzkEEE˖-Bɓ'}||Zlpaj@ l::q#$1W['U[8ؾf*B1o6}wЮ'oܲjUiL[qWWЮ{ʍ _;1e158;P"S4 Fҧw{Lp/ei+~{7iCS'~]>.e/kO:Pܷoߌ5kֈD~gr9vLӶm[c OOOPHdYYfZ Ef͚_PL&NGQT>}P*;w.((طono###SRRu\%I˖- Õ+W6tұck׮F???ooSNUVVZVX,H\]] H$<|pNNΣc/ߝ*:eH,5UWpoӦ 0͛7$Iyqf+))a VdvssζX,2,$$k׮h';..cǎzb0nݺu9Vm֬YV OXqRtqqZE2kӦMzzznn&uڒMNGs/~[qY]C}C"Ne%b_' J7O?c9;yew+_{Dk[9pmYMQC_5yA9l_Y$2Ea&~-'u:yOު(/klUBGߙ·yȮ6voKo N6tN}kl^~p{aSkr{0f:ۏ m{諗<'oߌo7|4|F[WVxjXvwؾ䑯ڹِz>c/gJO:rq/+*X4mG/sݲs!L_Ko?ȕzټ٦Mf#V<+9CU zLyw9_j/e:h1C>ߘQ_#qfχ4c>=_k zєmݵ׸; 젠 l6?=Y2LTJRTڸ 5@艈ի?#_ی"""j ,R+J;t`4t:]aa\.www~zQQL&(*<>>Νkժ˲Z6$$C&?d2~XܣG#GTUU=Lxqmb#]!jꨄ{m3n^pNG_:]IɣSns.yڹŴb#CmײO\E^CF;i>ob}F9g AF'v:GBzwȔw7Lq5+2lR/Mͽx=^tJû C[̆B@|׷y&7 0|>C_ 9 _h򮟖я/ߺ߀~d]tN↓\@EY%y/wozƢbtE7鳁%˹z׿iKn*Mًo2w(T6uob Hs/e~E,,q̴ f ODbitΑq }lՇ3g{mV ,ux_/o>^Y^gIJRUnbBQ:\^ QܽlTz5(ow>㏿=t8巼B!Y wm٩obϰqϓ*Jo{ؽ應]=EbH"#BM(%Ǩ]=j܋geEON9%cB WNy٧H3"m-bwOc6k*/˹7.:ˡN'Ac㖧og?Ӳ#7t8>ڭEB:m'i{7 ~{:{ۚOhr/uOcQl_;f{mmůi0w{HUEiCx׋_2 j飦,}璲,m׸y\ D%toN<︺e{\H3pD@믿e2n7&1nWVVvVK40R}999mF166vРA6-77l6Q]]-R)I /]t֬Yz}͟gϞ ,tn""66q\_RRa?Jtu޽8::8..M6k׮URPx{{lRZͷPG*/} ?G|*3Dԥю g'oXŗ>Ƒbb7?9շ9wf/`E}>=Yd筤yTSu: ;S~} ^iK[&|IHԸ{-_r+G-jg\w%kҡQ*ū~wEiBy}%Sw/y٣ܽ,˘j*ɯ?.={ҭb)wr/ [,+" ;zOONM -ʆ"Ⱥ 8pqpTDx"+gYZJ{qYR~}$|$%'y^.q4xo7o^JJdBQn)8Bh4Ng0 f2L "..p( x$ĉL&S&fgZm6D^M(.\' ^OPbqpph4‚`  Elllnll ɤj;;;j2PјL&fۧN:|mCz_T^_v?V[pl5 o"B]M՗5TY],| cjʊ Q|V% ]OoCJ  ՕsnHqZU#;]N{mnphWi2OTv@\Wq|xZ&ZfF 2hUW,Fx($ MN'+o?Hcٹhw769{xǿ̈́S_ݍIș+jeN'>3wt؁iGsGIkx|/hL/}FඋJ"gŻPU>pmq:lxerʼѿ[-3Ǔ;YFMxmFgdy#i3*iYC1-cS A§Kg&fyL8On_v.oȰOLU%_] ]^;2IlI1eϼ299:_D\Nޠ랕E;s#VϧދNd;K˯=4gD_=emϹySH,(|r 8R`DGGGGGhZŒ]._p!:::,,ҥKnd2D't.[SStر`].JeX:Nm='??ԩP\YV?lmmm]"lݺfddDGG_rJB &/J$k׮cF( |77nxwS|>N nwr}^/0̤N\Na2vՄwK.  erx7=v9:{٨c[wot6hUT:. \9spl5{حfMج& v&ș ZȰa֒[^٠ D=~,2l0Ee2p4\.7,,L"r ]rXXetp8@UUUmmP( CVCu tp#944455>/&[ZZh0(L&3 "h4\.JpζT*=qD{{{xxxnnn]]j5񤤤ׯ怀6<`Ç9b@`ȝ%S~I ȍL"7*G+T:3<J)p['#\AO \o[bF`1SI_5{-kso HyD}A .n`@Q444fs8 acCl6rW^l6E\H$R[[@ 0FO)GTbF5R4D"EGGs\&I$nZUN3>>FaD"D`0* fP `0Gu>8pJ%) UD"i?V’l #=)(MHHzPTNDRI$r<66vڴi/_eL&3L x?55n#bX`66HYd2СCy<^HHHMMVJ$44&޹\.ց"(&&C& n*J,:::`)Ĺ 8t“vy<rABBСCv%TkaaaMMM'N8888RgR)Ht\pzBRM6bĈN`0 l!!!b˰pɓ'_~]]]|6  EQJe62ˍ%DGG1d:NVVT*Tʗ0EO:t:ǎK$^/<(a7d0, `6ҦMU;P fH$ Bx&طX,"jI$aPdnggglB9hTէO.,,RD"Q"1bʔ)"n%%%  + b CvvsT*ߠhIII` FUJRӍ=Z.À 6M&^mۖ.]NڵN'&&R( 8W]]] hii)..h4L&mF(H j㩮lnn0'#ޔ{ďû D%l6;<<<%%%))>Ap{x<N8Nt:}>_\\)ʡC FjjdP(*̙3lcN4)<<eeejb\.`ɴZj:''0Ĥhaaa^"##Y,N|B*a65F8Y;wD" ꩧd2rycccppR  "ٳ4-333++KTb.2L$9fX<`Ο ê`0~iq?#oYnb:{'Orϸ=T*d:Q% %888333""B466><448qpH$:RRRe2ن ]<J5LQQQ999L&St&YVWXXd2- B!=U1 dد_FA<r;(111<<|Ϟ=fɓuuu'O>|xrrrRR҉'.\r ͦT*e2YhhhRRܫP8T2'x#G\v_x_:D"B 6lȐ!k֬imm0axn{CPl=6n+K$$RvZ@RϞ=qARRRaRWZf D7n\zzbQ*&lp1c@$wӢ-:v(J&i4T*MJJ2L.#" )^x<6md?hРcǎ1"//OUWWWTT;vL*\.r. b`Y.geeǎ;w^W*ǎ|rBBBJJJPPPllltt t:ϷZ2L&_reĉBf (z<ħ0rK/9ǵbbgΜgϞ=]]]G&A(Hp$yE***FcUUJII1EEEp|>Æ Ns\0|BP,3L؏K x<^hhZvmmmBT(:"(a2߀L&H$Fsҥ3fL>ٳnURD"T;dȐꚚ&g VRihh(rnDEE555X,R>"fkn@}!aW FXXXvv}溎;&6TA/Xχa>5xs0/v+=F rO"8ԩSv;3i$^/ɴZp\7cLLLZZZtth4pBp8p[djHfT]BAAAXXV ]Q wD"V_DP\\,ɤRiDDDFFd:|0 p8]RR=OBB„ D"NNN|jai4jjj|T*\.C=yU񺺺iӦ}7cΝ;~z 0a> Vچfff"OY#=5b#E'Ja6XVK$ cDFc0FcppY,t@ l6񴶶t bZ;;;}>Thmmu\}.ᄇGDD$$$D"I$0 u:N0,,,,&&&((b577:tn|@ ryad2u:ZV(v p|4mNYyd) v}Ϟ=b8)) N솵#$" [o-/+غuҥKkjj_ū C?8<ဩ;РP(&O C o<˗]nGGGVvڵks>?!ԩz[Rmv ʕ+[lٴi`6VMn0 +oH$P(lll};vFEEE9N8HP A"9^rd0 ED鬯rʅ L&ú.ϧP( ɓ'Y,Vlllpp0OIIX|>_¡LYYYQQQ#Gxdr:&FEFFZ,b`S@@ܺ%%%ϟX׈s8NG$.rbaxf͛aIʕ+.]J@ ^YZ\\w˗>}zС ,^y&m6l0BP(aaa5Y6lm>pd2gI76p! 3 ! H$`0zq{pܹA "JSRR$ BdJl^2DVAB000f]rŋMMMFuc IDATѩ!0d2t:=))999Ph4vuu%$$Z, ;w.222..ðÇ[,\ OLlEa @ J|K/E&cBi* {-rR)dza_No-7n]=m%/^ O:B|W=S/G}s B% l6/ZA0h PRRr@>߻K8pxM]I=}xw yT*lnll"HPqʕ޾|zjʔ)P[b?>%%%%%*::vbz67`z$aH@$M&f߀F/nhEN9/_H0,44tR(vQP=|?_lp\L&8q7߬i4?~c{'y3; n>PWWaf%V]lٟBnw}yf'H\nDDDyy91:a+FuqR_xf}hZUgSM|)3~Tu^omzNtj/5/?w> E[㉽[K/8Ad17߸<GGG6lڵ l6Ot:}ذapYh H9sf[n"j}/.}q@`T}PnCo&>pVҥKZmLLP(p8yl]0&_ @ TWWLښxmFjxBC"?mbuӣE*qu~ޤlo Š& UlY>/VZ`ꯍT:3>m) 5N\CэH Qְ^X)]r٭K\Ƨqڷ:5wGƧ_+P 45sF>ӟ]rԾm4ՔnY VL{f1}}>zʼڲwy(Y2CV꘵܇eNR:b'4%H[n}饗Unn.`֭{P |I$Rxxݻ_~/)--7n'N2%55>/;}5  $}H=uwwCAttL2:npoҥKuMwɽvuq5?dQ(ަPhW~ftRoODY]YPhzad uEcgJڛj:dWIʛWl_X ~[{Vl>,-[{ӗE[~bMKB$tfAAȑ#U*kNx~߽}'igtR~~>~o{; tnwqqqIIɣSWTWW766d+%q~&2%-xG'j6|>^8cx|˚nkN{O>D}pzuWj L D;6|p\q  ]igkY&-whd c땛?~-~d#;3s?ݲfq?1,:0%;oU6`ה xeYWZt} wNgNV(yfжVTSvA$M_ð;:kQF'>'~[UzJsX(K>wA7ZNܾ3gFLߐ}H$ȣGΘ1*6>Y{}6n?tPDDX,}#jY+"9x So[mV@lJv|@e{kVt&ֆJ a;TS ٯ1܍z^Z_,LvðKSbwujyK=̷[fF%jNl> peϸv\~{^yK}~ox.5+wȄ':VK"} ?/8 AX,V~^]ptܹvb)<`999Ǐoiiٳgcccʎ9buA?SG;vllllqqqQQQcc炄E'7Lس|a Wn:mG5 |{c5lN+_=wCCy1-b2 B̗rFO: Jnhd< .[V@%k?g]3G$AׯL(2 %'d mso~ ;4*y˾E$4"<͙~o£?M*ͽ^|63gl/V^9=zʼ aOdH ;}ʕ+QeemۄB#z1Yf 6,66ŋs 0ӦM;qDYYYkk+f4 ߐH$qqqBNDIIIp͛tE$#7f$5CXM5?mwzMWExە sK?^8n8w̆͜842,:"4V{.sP[v0M_Ky_ljV^џщun'x%SJ1"ӭh6$w+< N;cI>}+6FΟ21~˗/oٲBdff4(,,w%H8Nŋ׮]s:))) ͖;DQF|>ҢTj||<6lXKK%ٳg曪GG ` 2o[zzkiiAQ`:n$Jsss •+Wd2F{f͚Em]~bS_Yeύz{;7lmgyiF<>_lםV;yF]MYo Lȋאָ)x|]6%+/?9AGvmӞyIcV7=wm瓷e ;/AQ1 ;L6/$78>P/44b^~J:?lAPx111 \.*{8.733399ŋ$I.Ѩj[[[V+JDbTT2#F3fLyymN:vUXٳg?brl6E`nw:(n6`0nw^^ބ kkkfs_T~$#P)ZO٢Uu\B~\@tЏ47V\8vb3blSW~}uk=+gx{l_]Jg[/{?.x kEǎJc[M'8Wof?Kz A*y[,J W~M.lUqbֆ`iC?8춁#[sN~@H8rV>+4\*dY8$X,Vxxf3LV0-f0l6N5}t:}̙pU(,ԩS&Lh4F9N dN'mkks:l6 [vݺugΜ1#Fx7Rhn{^gZᆇ^phK~jjjTTӷnZ[[/׫&0c\荏B@$~9:G>>} NjWvwo4GDҋ{ko׼9O3|,' 0 HbfKכּ)s?$zREޝ@ &T%"R)Tzo=ɗ֜£?F&Y,,h$<`q:ր@Q|K|f5o_Ύ ,Z,D_pƳ\;od* \/:`rH߬={'D⫯:b\ w g͚EѺzL&fy^E5 aT* /h:N.kZp8U6 ^\.x<0zNbHxJdx!9Έe˖޽qppp޷+Vp8uuux^X ra6`n6Vju\^Fv(Jl6@`2ph =Z`L^dI_9`f͚9stww+ Ng6vq8fh4BW $0A-Db2P0x`E7:(L?ުqpppt޷o@d&PTI"ZLf4CBBi4DB1dZ,H$a, Vkڏ?O_hܹs Egg^ `30h8K( ޜN0 jZRF)A` v^h4pȃk+881p7Λ7oΝP6 bZvZJ|P(l6ݛJ&I&) ^oXl6˅jdŋ?ދ*\xqww\.h4`jf>fԆ)hh9 JTv8d|>tV TVV^t>ʫADai4aPUp㱩7K ~Nb ^Za/1{߾7HKK;w\wwUՑXaNLLd0D"t<l6 N0p8iiiʅ}09mW3fYlPT:fvZh`;2J idZ* EQ9؈ Ng2pOPԜ}.~㚪: W&p (QTVW1j<<޷3fȑ#w܉ Heeerr2 HpwСL&&ZmKKd XfiZA8@ 8p`[[[ss3܂Zh &45jTNN\.t0 cnNv{[[FAQNS(׫ja3BhL&N3k׮q8.T*Uuu5AAA IIId2rgasދHyߜ$W }c(qde]|n.Ο/4mܹF`0VUUd2T E*UՍnNX,HDX,ͦh0|0f)JZT*LfxxP(n%NϟС kY,N%:;;Z-KLL^EBLTd2^b@.fZzRzѡ<^)Jol6[hhСCNJX,uuu}3XT:`1:]Ec}l}fqY{A# ujOwjʊj';*.w5ht8|!7,V%zl^/k_ݻmMsMgHoYƪ|I(y) (z?Az<p{Gz1 D eԨQ%K"""`-koo D/}>@`0111 Kh&xD"d2ddd$%%UUU i4m6.FPTR|>_jjj||qŅ+15s~ y=m53=t7>g\cln[]/sUAF ލJƧBBMūge=%U\> \Ԉ~m喯VҤI^}wQ2UaZ2\tCkzHmڷښEAa/o! ZU^u??=$''GGGwvvY0(tPr19ېO?~on}2:k$7y!)8w ..nҤIb~P* C deeIRXD:rȊ+^P( H$b6  rPR,Cկ\.L3III hTPu\$8F((((22R"p8zf7mtzo~O/[lѢEٳ̙3n;>ǰc>ɠ-<߀ iMKc2Nn5[͆SG'D2sǝػ/  PuL`85x9B ,Zg}n|F%.vhC1_Y_7)I:[I?xl0XSзjKlQhu{z2s;>k j׿ϡ(U+O1),:gFLN!S[OJݹd{g0xG~w`bbٳa@ L6ܹsvbfi._l۳ Ν;333v!CdfV`FPBP(\.bm bEGGGEE=EEE><3˗/mnnVbxޔEb1˅gr`3DRt:aիWdٳg Ō3JJJZZZ$Inn.@.{<X 3 {6՚!J,Yy[;AQG ,nϟb4Jg^zt&a&bf1Y[-l388=0(Ǿ`v{xx)Sl6N7 .ST1BP8δr>p8 bj?%F7Yߦ&Xrr<n@ F"|>_gggdd$BmHP? t:EQ #o`0\.\.w\ O?c|>Ol(vuu=ϟollT*!!!Jo?)]Wx psAh$J޶}{)..={vdddMMMSShkkd&i2|~@@Z2eʦMN:AK-x^DuH>u-nV(f2|>?((H$Bʕ+D"wttH$yk׮%''WWWyrh4{(|>wkuz=>@>}Px^h4;::L&3$$$:::>>Eb"8a„ׯZ$ttt@h@QR(8`0>}jc… ;::T*ÁL&ST8ڵk BՒHnׯttt}fVP(j6\>`84*$".%+~<{]]ڻ҄~Cxaٍ6c<=nי?C#`} ,K]]]]]%IXX^h4-9{V d2B466nڴ)>>>111:: f544;vfݿa&>  .cpރxbuuunn.ɄP`00 ''`TTT> vI#I ׯ{+]7c67| %o=Z^r_3wW i_s/555l69)))22r…ꪬZi4~A[z^~吐dE ĉY,bq 55rTiӦ9Bq̙3g\zjq:l/3w7>z7wQ澯L2IBH I΀ *(xಮ_ѯ ǂ@@@%C IIȝLd{-Bū|':}3nInc | B#ۈһp8N80zh^6n8~fcg&l=} ugaaayRF}gɃ?-mϼ?bi޾CyŒyO ta5WK.*B^|geʠQr Vaԧ]08ā-d'ʫVa/o7{'oaa3^:9b2*\\\r8c2D7w3ce.X;g}1i+|o|q4M'_z\8yTY鶖/~4r O߽E?_o}AI7ni]2eȎ?L}Ĵa'ߵGӆ(5yo}R槍+k?{grO͵B:XWdž+5a,]( V}{f}]|5k<{, )**555JvwlᰚDw;AnetBJa?.ߌdcSm٣'YZRklf#GP?ML}豹} ˯._&Iy|ᘇ)C2Et€ʒ48 mDl3SL⩳_ I I˗Nj3虳3G>g}2O* Dpy pCsE' 8ܤʫ- 'yЈ>Rz2eǞͺYX~߰#6mǎ&66cV'DqkN8>Dj/]4`շ+nku7p8{'k+_}UDb! ۏ ~N:S>..WF|/iuM_Stqwc{7v/0z7m9_FMݾyX,oҥK|L.A: ܀!_O0bX(IV}YXXXX3*H0 c#l߾=111---::zȑ?q1cƌ3F K;wD^ ÇϞ=U__?cƌKJaX0 uMLӂ+ ( afȽg^v`x:%pܹsWZ?gΜ;wfggd6l`^z_|qg7n3 سgOSSӒ%K81/6-,,(6M|rXXP(da,,,,%j߾}fj|>qDbζyEFJ8p8 Ž[]0Kd 06mZ[r_ V=p̙3y<sTWiPz{j=e3o J/ KA?>?v͛78!( w N[pbw2XSS= 2zG~zqru֩ꜜw|  |`R8^+PQ{bi3Ec6mrdD$hKRHԈ~@ykcYXX.'&&fҤIk֬rÆ }OxOII:thXXq;bYddtiG&K~ړR8YoN1Ν0,rRc6ok6QqѺ"m|eq i'(Qd.$lm,36ꛫNokM$I;=A ;;Ul%Koa6~?l|h@ʼneeeW\9q, ry|||~"""0wR&91E%#-h"Gf\lBW $eRiGD*J~e^7V5_(͕p$cDhmV8^#.h  }N pڐ?]c#PYbtTX77.iӦu3,YRZZ 2eJ9~^^?,Hn8yÇߖfy n]Jĉ8ٷU˓O>9lذŋwaÇ?^Vyijiii]䩯_xdzMMMw^Rpn$g_yf-P(H@ `= 2YĤ>\",!Hc_Hvi@g)s ȱ--1Tm.'B-eu,% A*M0r![RaJ7a8*K\#̡OHD"h--Hk)>icޙ;vlEEŖ-[3f̰lG ٸq[o%.]?b M?>WUU?5k͌9v뻯)..޾}^:++cl6ۛoaXaaL&c>o>#=۵.qWWZoyiVZ}o߮k׮"sۺs\L^F{w w @%C.#X9@` y,X: -;"B4zBnt12N ^|C ;;4~X .g)~$tK)4Uk="Yp[ Qe}c@F 6'cr]s(??,;͐m6۞={f̘q;v0Km:^>o޼ↅݹ2cƌdw={̙3'Jf=زeKll֭[_{3؞?uTpO>w߮_ :uΝ;[RR3xc]KIIy'??uĈK.y%%%5]r`շ 99ӓQ/p7dWyD4r U^},QxE`8͐b6(2l?&rF K <Wx8rdxÉfYwb9"9H/(/ &c-_X>:2 s&7MɒAs=v,P° ]~wxddd;mذaСӦMKOOpBYYYӧOx<l۶mҤI 7o޼y_~I?~|V555^d2j4/I~C֯_όo38F{blٲe̘1A֭[ׯ__?~A8^SSCDFi:t͛>|رc7?wcݻzmZnvNs999gYxsP p8Ο?g{ .]'P0fjjj׿l2tŋ$ Ǝ{۷w@ Yf1ٳ˖-ϙQO0a֭'N lڴi֬Yiiiiii7of;}s̙0a_xSNu,nfêUbcc?ڵ놭'Z/^V@bb T*ծ]y+"Ξ=s-[ X& ŀF&`}$dR-*a:WE%k {NwA c @̅wg:0>'EJiKԵJ~D~"O})/d&XUmkq_U<(ۤ-41ٺ8+ `Vt^ַhwͬd*$Z_FI6\?8B0N7cW @SBU IX=ڜ'"BqDajOF!k 5!nDVn:oT* Qqd)NV>DV2SP3L[E U65z3nk} "-dKu˸  aÆW^yp8rOP0T\y33hTL\R 322V\dɒ^x7޸a^OJJ =`'OӇ9( [l k֬n ^zNhׯΆ҆?Yj3 ҩ_W_}żߖ1,w ;HD֤/5W s2mT!w%MY]Ę( \E&#И}u.)%pPbyXIx_0 <O)!! =j`W&`).x< D(Qk^J Q4RvĎb~4%#ւ&26pSyc|A!;u5{Ij*"6Ds/T:uӯ^~25777+++%%o߾Æ s:vjiHO}ѻ,ӧOz}f͚o߾G:CK畉3Jd0j7|ncUTT? =g>uT^^h|:Am۶ڵk׮]np33(b2Z- <<|[nݺukpd8V k׮tB$7oOǧ=:XEG9tЊ`]{Λ77.Ӓr A^^Ą[ d n-E'b"[`_R1}q)>1 _Jbe|QbQ4>  ]F"> B̑ … "-z/ @??};ayĉ<b]v:/_lk4MJJʆ v{QQ_|iaÆ)E͚5c')''&魷 .X,_.t:WXXX[[ptk$***===??'Tjjjbb[oO0G}dZ[[}KzhF: I~F={ !CDFF2/(2eܹs'M׮];v#|_~0^,9Q!:!O}T& E'&dB\"k1J] j2{jҪ@*'G8Fb Xp9|I !4ً WNMGܑøl#q%ȵ2e>ϓrYB\ӅJ/I>)M臷:1-[T Xa/:_/݉6(0Hdj^CZfK?<177wРA+W&D}W_}uyyy_駟%K:t(%%_gf ;AЌ3fSO=O<---99I3gN[[[RRҲefϞm۶;vtԏ>o g>Ptumݺ555ugNQgԩC okHXxJeze˖edd?~„ wƍgΜ~@8---))~=JX?L6->>ߝC=zĉCa3 ###''gĈ~!`</:hKaޔ0E I% ~BĆHPpT,dȡ2(Opv85>Cujlx:@n]="ǽ[g!|yis]N KHc!^Š`9MR+yѲݥݮN̲v?(uIg'N @ k;5JZI*P :qI3mסQB9Re2#n0qzuCrMB+exBR,:W2t } wAW^-..v:E=ݘ_ ?y: 0H$)))ɽ&7;b(x\wihB*/2Y[()I1}Yu6 L+?7ن#y(K *\FiK 3Q D:?!&MFٞLNT$+1!J=q*-JK.y#c5Dpњ(V}YX8h.))ٵkP(=zO?- $} .رcw>}zJJF\`շȤ +?}B1ZY%.E͓QESu>7lt85|o&}V%qŢ~]6xmj[Lew%!n'zFhzMm@8Z{9ɫsmA1$a0I!q@gF,m(bDyK{%2r.vb+&zX_Y`%9pVDԼ&}jQ@\#V_U`iQ^|KO!Ir/^\paLLL/A5jĈ +V2eJGGêoq8M(ﭫ9,W&Wqpԡ^>`NyC?SYa5V>udVUoĄP{!OeT_eU5WO9}\W 4.'E<(Puҷ~[R$ AZ:4 iZ.Y/ax=^PoAD\\=JgFHPLMpQN4גuęLפV"?PY4%t$ub*yG^^B1NnO1] i|NI*Sqmot?u}s6s4u$SaV^ϙ VӡaM6D+W K|EW@}]x$INJP{APҀa9B4p96]4˺<4]\\\\\+&FH,0`mlRT?7`@RۣCNڜ !LR(^D=rx<#GdggT t]:W咒W@DӀiV{ ()XubշX,PS|9LbPDVF6aZRIi(P$zvԄ5()lTRpj@֚:9Ff/ wiaܩx#E>!I:0H}7nwK\uFu DFGET? h8il>C:Hp|h"B8px'!QbvǙ)->J{QBA  vSuXv+daa{PUZZP("##@Anw cw媫f|.`}i4E8APxxx]]]YYYzzzƟY96q.(u>C8-m)oL Z BƸ0NpFK[5W4_'}vW8lH""UT,a+h(]iQ \qLJc.P#(t-i5nZ QJ!ʅ? =+Q9 BVeD("=%_ IDAT}(8'hZktR])DDu+ 8j$ |>VDDDeeerr2אּ`շ4eu<"J1?1 ?vUKn3q_VStW/@ 5n! $JGPo.=2y)ļPqؚ)2z~!/uaq>Rvjkt]PosA!(^DЀ><*4h/sqǡЬȣ/< CpDC'B\Dl 'bDIb !hN3K,pbs72577Z*2"r .TT?ӻK߽ޛi: `̆'`EQX, xe`w嬰7jzS5Bq"U d#xMN`^( OF Pm(drV zTEp*؉Ijvc"Weo՞<2%qzRR}3vkj}$9LBhLh0hl8^>;!X"Q=^lЄy!@MX'I,sw>r nw ʃCTbaaa^% aLWٽy_\iVVG}x衇>S&E)SDEE1ju|||[[>xbɓ_{5X"0|盫7YvD4EQL:a" oU;"Axd @N!~D( <Ҩ>@x##"FX!SjlPk8P"i6QSCq9aoH΄Vw~?S" pjf`+ b6D Diڨz?4wK$p#N VՎHeL{l/po{SsIAQd0 fߊ偁Yr8ݗF* gdZhѐ!C?\.tp=`FpVWADQ ̐{\{4dh4Y4rF"R(jȅi0 h#av_RY TE8gZX"zM%2u˪/67z<B>kѪ(y"j)v[ھ p@q!0ln p@~dCaL LsBVid/R啴_C|.l2& ^XXXzAQ*r ۫X}_s7scǎӧOx#""駟n ɔ Hz뛩-o 0}_Nkz_ol@9B4@"0J@iq. 㕑¨l6Dlvx-eK+m;[##(ꮱr0>&N|qy.W!I?mBI UjAh6[!@  U~6 Ax9(Z*#ӧ<|h&q֬Y+VXj|`jrg0f3sڵv}ѢEرc…!!!폻cH}!"I pz bkXhQV~N sChnu^q{}DXrT"(@|>R&VDp}NEjCHIz KaVn Kr,R*`/s )N}M&L&ׯܹsQ{W\)Kn(5a„{n{ijjZd ;wNk}˃}_Fwm6[XX~ A aPΞ=;dȐ[f CQMӇqB83t}PP߂[$b#=VK}"݁DV-Āi#hCM}0T~ 'hx'XFa=A0=.$IЮ;u[U3ݸW}M)ATUU۷o֬YZ_Nmo9NX,!h4Dw2L"ף)3Kd 06mW_}U"|'ݬ1ȓO>9lذ/]}-u7j9s&cVލ f9oG"""nymXXXoO^޽{cbbZm}=fPan?=lzष=lp]Ha\ @єfÎ ;Vwc~.YD=s KHH?~qSSSUUUfϞo>ɓo1222==ߦ(*++K9997\KMM̜_9=߼y3b8媩Hj; 5m䃬1,g]8!_Z .̈_P(ZZZ~իW>|޹1ǻx˗/0|Ɖxʴi"##+**dggۗ.]z\sl~GͶbŊܜ 60s6lذaüyf͚r7i}aömy̙_>p@~z|?!--F֭S999=gDDZiյ+UeƆߍ2~|M/egRPӢK KKK4i҆ bbb7nl0ޘNQJ3f ^ XtҥKbA~`9$==t6-HBB”)SO8uTFj5166U?MHHxcƌro߾s*}FY^^>4}?;K>sٳg_x ^UN:u#F>|PXX8cƌ~ 0{3gL_2CCC_}7xٳ˖-cCll}{1'aۥR]MMML{ **7---3g=zU*fcjiii{e˖͜9S$7 @ p8ϟ֭ׯ_rsssv᯾jQQ3nݺ#GmΜ9 ,x ϟlllt8̱f k˗-^o׿jjj:qij>{?3w|u?3;{M۰ijH" =P,XTk.ﲣސ4W_򄄄W_}ƍ)))6mdu^r mۖ\SSs̙Vw0l(1"--:xgӻ2;iҤE v5k֠(uV߿t}~{֭W\IMMeûQTD:bYv9H$JBP(lo+ڔ 08Oͨ*Z=!xJVtur@ V4UW_ἛLQ*4\MNrRgn 02| 8#d֖9 n9- RGOMIɕUd#nݺ]/\.ޛ:uO>? 0 UUU111yĈ .|'m6B/V z˗/ >w0RTPx#z衃߿ժU]9ĉ/Ξ=;???-- As/&LS(QQQvIt/JR.8nz~oΖ-[&NQ*>>[>Zf Ikޘ[Ery|`ʔ)[n}衇 7u)))odd$GhפyϹB[7;<O.?Eq{0N`t~P&I *:R`>Ay "q3.Q:̫nn Q I2.">]W6k%6>#37hF*WƮG榸#d- J:۷Ǝ{)))O< >|?N[^.gdd+ŋ۝d2eJR) ٜ;zhv#gȑ ϟ߸qc.>segg3ȑ#~饗nܸkt:Ț-:ݟ쬬,8u>ջQQQ.L;hUTT̟?ƍcƌپ}{e:gΜ9wΝ;z=;XxqfffE4d4ɡ6KF/n|xA#|D y7Ę'3`M`CG>ZBHN} qYF'VHw 2SlFkZ7ߑ"P@T5:SWPzT3їh'eqQӦJJ{tn]ĉ[nΝ_x?pBUUΝ; ,]T$mڴiŹEEE7n|[EW=qqqo`W ^{6mBaېcBGnYlѣ }79sA֔ /<17?&!С4G0J~aB\;ll挴t!ͽO6!|D_H{EӍ7H(npA[-@xfڌV4J06Odɒgyfڵjzխ:K,y&Νkhh?9eʔ#G;vl| 88=zmۦM6nܸ+WLV#?Dw)++{9sW_}xfΜ9t%K#o}}t4aÆ}ׯoLL\.>nܣJ C4(I򚳢X^[pE8Ⱥˇe [匃irUմX1TyUWo*o bRjL < -0tcy@R\ ̵d)f4TcV )iE u@#G\TL%X"OƢR%7] 87 y&E{(!lBBB^y;]aSBN Kd&=MքFSAaH/ ۖj!i\aXPGB*9(딘*2y" <`cmlnE0Ŭ ZnuB+Ю䰸V֕ j&IG42E9؃)CiV2@q6% (X+PaQD#(V#yBC,%{R RܘH<۔N!T߾a`/-88c +‚jlV 0X;b#%VF(.P)7 v.K⩷7 f#;wG{Aw)0Xy1n_NDqG!w("Wfh(f&$tTt!q )c"x'xN IY[kQ-Y͸.9\o~-M0*k@+7l$q[r{$XzxW_Wֿ?͗}@RwH!A`ۉzh\w#5uYjTbʸ@-n` zp$ST)8?e+pIh[$[.kǥJ.~!JT\Y[kա\~Q4"^f0 !0YxJML-UdLp>DY.;l FjB\Cy6p[~AIl|e_lRbkqebYbhi$õٝ6k1(εڭutx@0.?*2<69V1 P(8a@pNfӉ|KtLZ]uw i Ɍ[`wbKp8G@8t$=f:/g̘1cƌI*aE.xSEZcǎuR {8y.ڻ͟LkʬV+;;kЪ wv% p1T*j }2շdV>~qy{ 4A>ý a ibWhHh>'O<QȘqCyx@y]rޓ%X̕(E ǃp ͵t=h> I xOF-B@ p8M0 EQnt:N㢨j9՚xEމ?S}ETt_as5BX]εPQT<p'|'Y;a>쳍7z<{mjG\_vΝ;w+V0L?v?G;8qAw}W&.\lZ7o|xٲes}խvijjZdɩS?iii?#kpO?38{ҥk/l [ZZ+W^ʕ+III׿&YpaRRN۳gH$ZlHOرc;ɏ ì^rD2 _oذh4Ʋ7o>{,֭[CBB(zӧO?ˍF>¶n^pao}'g.]Tӥ}駬AG״7bݺucƌiur֬Ynݺgfee\Ǭe˖@0 SPPYYY>(k' f?~|޽SNMLLe,;ҕ'MϜBkȬ" C.$ ]ƸI-uqek4J% Qe$I$iFKRL篊L9i4*'P(%vQ+{O'\rWF!OC2w=h.@FM悐\.[OaX-??ĔHUllYRs% G)֊Rs-ȋ!Dɡh).O TG˗/7nܵkv͛wرsΝ;wnذm2v εJNzGbcc.\gbx}a,Xаcǎ۷ս >h4nܸ1 ?o[Zo['ҦLgoM6er<77'\ti EѩSz0ܹs̙w9c=vŋU08<~dZzu[>|ŬAGƺ݂OO8ae˖5k,[,77wԨQf2 ?a>;wj_EXmeկ?}ݻwO81&&f߾}UUU lhh;wŋϝ;p]ޜq,\_|qĉ۶meժUO?tk E۷o׮]/Ǐ'?#fffҥK.\cǎ~3vHWkb2T.Q)&7WNٛzwpU֢0p`XXXPPL&cK=r\n7!I(AL&S(a#2{4Un#{*51* $=rH1GZMmFΥ'X4ndĘAA 0 7q\h4 ^*PF2A x%)L=TT !_7ybHLؠvp;V[[hX;M655rMnsmxxD"h4G,(ĉO=TtttttO>yQoˑ#G._<&&fرyyylB}v}&ƍcMdKJJ C7}˗/WVVVWWWTTdeeyJJJxF7ncر#FxǏ=ёϟo+Wܲ0Lnx|ƍ'L ^z%wޝ(O:E'͛oM 6t\-;vlBBBhh)Sx 2sNH^ݱ-ijjѲ[o͘1#>> nww4M3FTMPu T`M=@L@ee**6HdX. Avkv.\T*qNz7a4Q\.,@.ݛUJ<{;-F0iXjhZRp8vSŚw4M$>.0 rm6[xxBrATƈ~㨐bdy!Y(~Q#J.vgzW_}uԨQO?+jjjl6{i@GεZٗ:Z 8ޕow&$$L2wJ-_j6::]^r/;vd3'..N!uQH;eʔsRu}tِC7 snX^}Uo;vNXX/\ɰ00Ҍ3b1M3'?x> sD:k֬]v&''lorKtS}T2 Qdzc]ajjjE"{?4v1 q {1 )S`<2(/ Z Պ4 0hdyM3EFE]0Sd@!!* AvN}cHt4M.tl*.ٱtY1-gM:Ҍ%ˑ7A!nqxnY-[={駟 _}.m t\+<7n``Z2nN6m˖- |W۟~餤7np8^)jVƱmu;bر:UޮM%%%qVXDa6l ;jT 0lǎ]V߿7l;wەvq85j7ƪy'kllhQF h40ŋAl|hfZ"9ĉ=K G{Nwu"nftlǬg^?|?.R+j񧐆f^_\\\\\lBW((F0yfiEQH$ɂB͔1@.br*=fҭ/ n+Sg!8qA!|>Nb`$xpW*eL&cJ^o28b j i,̨IA =| R_ȦR\$ ƞG}tϞ=lhlnvkz=3KDDDRRҶml6bٺu{d-هzJ$n7 A۷k {l޷o߃>AԮqlƺݥ#ߩS8q;v8ɓ'233m6qDEnjG~X+z!ӹvZ hKGbƍ+X,g}+%%%^^@וJ%x0 a棏>Z~ѣG{3g0=>7h&APJUXXسg=' *@4}  p)*NPL8В NpTTr\ x<>F${( .ˈ0cs78 E `6SvmSG DfD&D2P(p>2 aP( D.rT*Ivڅ n#Nk9ua@"r|0ĀfPW RK_xqRRȑ#U*՜9ss}̙6x߭vi@εcǎXbP]n]yyСCZ[[vۗzab]7 /˫VJJJϟYYYƍKMM]bڵkٱVT:t233Vk;lذa۶m?p;:عsg㏍#GLMM}Yu^z ⰰ0>Ͼ"I IL?ϏZnIO.vD/j<Oє"߷Cn{o@M7JEuE2 i 2>%gqUyuNH$VbX.8Nrn[(4͎Bs\v>|ZNj+gD?;:juajkzXSߢT?AAA(z<;p8Hd'tّpEQGss4eee'R3JJM&kNO̷5$z ig IY^l)/kJQ^W:)ֹd2D" fi4u\ED"a `:s8tl]:x<&Κ1 nYjtVX^afBvz&i۶mG q [} EP `~F[o_v÷l>[ss5_~_ ȮȜ>`0̘9cҥ555%%&;vl`K}]=y\쳌𱹯Z捺0g/}fhNill<}iӔJew$9zzXe"dGR3"txOOw~c~~~~~~t/ 2EarXDBASq!^b@iHj25[,5ݕ^@SdBAa (.\i"8x<6R%a#ML&j5 6tX'o4hT ppSA!(d?B:$9˛XGa鈎 [Q4#G֝ޮql_=tdt'qOD"i5_:j {v2 0E1 C|wB~3@1Ǐ?~_|"(`M;vصkASL>mzddb=J~qceff("(?#:K/=:Q>.#:x𠟟_KK /F9?lٲЕv=;ap8D"zsnm ϭ0=|<%?%r"SMpjd(592lC̉3jB80ZVRZ ax8;; Cz.͓5;Q*$qclt47p`m0޲R[S M``0thS!CJW;nB4ǮCsD`4kjjZZZC0(l 5 {1%ٛ68q_N镁@Zh? B+lߗƎA'|r…'Npڐ1h_˿g "mofڴig,\𫯾ZdI||jݽ{c=wQFmݺpҥ)Sfdd|7 Ս3l6\.o7'W9sc 1 f͚?Ғ&bҤIEEE6m…III92ͬ !C-ZXp7|z)Sxl6/^Ξ={=>cmǴXm۶eddm4Lp+T*;n:{t.^xܸqC Yp!%##̙3gώ}ոp#<6ohsrʕ3f%%%\`Ŋ!##7wEζ/l?0%%eҤIv=ztvv7cd+p̙ɓ'{~222[(p{06ľe+A$Ijj*EQ۷oZ显 :뫣9ij޲e Ag1h|0}[WCO*G"B;m頫so a ɹ#UBF1g7 &ѓ%"00߿x񢿿_|n]fFٿH$b\߿~zV)g͚tUVر#88 Ǐrrr<iz֭~7y^{9X&m޼-~e˖3go^sϝ9s棏>*((`;rҟ >|˖-dלZ8r75g<o޽<oܹǎFjժN*ّ0A$IzUb/c]2oy/tG}ޫc0 SYYYA,_ʕ+AQzzUxǜ}]˕/{!*K QYmu͍ eO(j;.t%窴\<{{spu;VҟBp̙ ">>J9/w[I-Zh &׬Y]ڑtm;{ tٸq!C~iX@'M_+7nq]VViӦ_~/8pI{} N+aR 1Y -M^;KeUT"bX*ThZf Q}nO!ɼt:]||7&"))u_7nܢEH򗿤\ռTwY1bA:qLQ'|o߾Ayߺsp[ Vꎻ5&:pt~/#555`_ 1 Hbcc ^Cğr> \.WPDGGi4\UpP};?o#*ӾX-y'߸q1co}yGi6lT*333O8qb.]o3W  NW z|L4Mk@@a,_ iahD2r'NDGG /@ Mׯ_W*aaaǃa ]}G#55rŊ>-w{][[KQ[>Dߏe C3A ZYYYXX҃9T_t]\\ٗ4M80_*.aƍ{ۗ_~Y&͘1!7; PED^.\廸ݻȷI$I\. 8,FSRR ;Xgl*NjjaMA8fpv;9vtP4EwY&Lj*F裏jڴ_vE͹Y3''gӦM){ {f}:n7bۭ6fs:4E!aNg/@2 0E  ( G]ZZ /,xAP?ZlY]^xuOOKK:u?AZC1 0ԛrEPv7PH$Hv{p&B KCR}_VtKnJE1 rrrz K$ |>ߵ&ajp\X,&IQp@ ]]rCWXmžۅ4$555F ֮]mСUUUAAA1 e@GezdiG4(IV%u*Y,áR|(hXҫ v1i$E5 `ɒ%٭ʳ*{db ô-ӑ:r{x+ ðPu:=xG!%qv}g- x`Μ9z ðO?0t:Yac˭ n@ddիWö-p80 tom4766ުھs+!It8ރP}!Ux<^ppdlPq k֬ ,HHH8wRdoBarr!C ]_Z__?hР[btw'mzp&><88/@ ǀD"-,,zjNNA0 \.WPDGGi4\ރH+_B 'p8J%HZX,vMy֗E"T* Bg |[@ =x<\V| 0a @ A6 ŝ @ H@ /@ }nn@w_tY@ G!oB @@  @ H@ /@ T_@7P}!oB @@  @ H@ /@ T_@Ν@ ݀$k׮痕YVifb K/}.ƕJZ6111!!ù"MMMh(@ w ٳG$eee }=V)y?(}D8z[nmllmnnڔa)El"lܷoXUUH98 x*//G% ]׍4~xzii~ HIy'}D7t0TUI+/̥(Tyyfc/ ðX,*eJC5kr8NH$<PcŢ(Jrk.;#v{xxڵk!V+?zhzzE :rH4sq?~|qkk+!$lcǎBjGFF^|ɂee%}ݛ-8k&˲bIIk}d.ZZZnjӧm6[WW˗$Iy< ^pgyyĉΝy˗/999=$qtIDAT ЩS=rEڗn$)--5M3̥iϟL^At]pUU~Ą˝&7o.]͕`0(gRH$߾}kZSWWz~ EQfgg_x+EQvڵw~`0/_iHdvvvrrrfffuu8g !0YYY;vx  Root Entry߰256_9568b9187f71cc0e*,l,& ϻl,& ϻJFIFC  !"$"$C" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?=rė72RK;k{Ư;oAMv|"n w>JAvNmctA:jJp"\VBhU{8.PrώzZ[HbFewxv]9=GzG BC:="rgzΏ=]G=#lt{)m:??uc?IG'/?u}H![c?HG{H06DmXO=  ,/^Qzg=#lt{)eMB* 1$rGl?u}H![c?IG{HlCQ=#lt}H![A"rgzΏ=]G=#lt{)m:??uc?HG'/?ux|=g=$gMNjHºGZA:.{BW8nEkh%V lCft}x-;cs*]cmL϶sJt=1A'mVF?u M=? .??YqU.~˿?⨼;~?)k_OWE]<*÷->? .??YqU.~˿?⨼;~?)k_OWE]<*÷->? .??YqU.~˿?⨼;~?8C=^Xn"Xѹ}FFE;]ž.7\IV~Nm#W˿?XƩ{49_jޤ2]K$Ym|m`H9AtCHT^Inu*xyw?U]~\ga7?x`dxQ4#Qn#?5i\o 7)=$$Ӂǽhyw?U]~@=W*&닉 h<,nA`#'OSHꩴc@֜s-R (7UFz#ko@zd(;@BV6P]A8NFqnx[X5&uM 6]="Yd7WWs:`j?s?.n,x[{tXR9%X?fxMQ1jS$ yZAF򹜜ho4}C7kw-ηrYˁx_#+>'^|@~n@%݅~uT8 Ÿ|~~?oƿ9~jߡkVkdh 3100 1Np^`҄ &6737ņߡh~?o:lڤ wZ}P&In 8<ŞrĚ=mmjG>b7N\{}v}o4}C7ךYoqafV-rB \m*H#6<;="m&7G}s9qBcxGC7խ.yY\I( ;CǏ\y:5W-,EX 9rrwt>oVḵ n0>` u}#ޱ:nIJ8Nj-#.aگ6ge,Wu9 ~$o/-Zghfh' <xJ]ir,q9Q rijexxBؓrsӃ Ě&rmynfyVՁ gw0Db^_쁂$ of?9k8KyBťbCJt.#y Y$QGCRL\(!d@dn"hf-㍸cokK6?,ՈcHaHP@J}Q@Q@sAOM_&_-:}m`#t%=8_n~A|S`iSW< V1?`zb铓%|L~!|\\YIE"8 #}Tqo-[Xl˻pp?&M[KSv!X.6#3Y^bԞܶ D@#8?(=;uKu(RBp98@}94k|g,He*9+©XsEk`v~3xO$'wOj*g!g1XjIvY*QE??bׄuɍ_ӄ2WmX&B80}x,qmd6^%l/OnW AU'tVP.hI0?_W{G._NKim}h0/AqpNjsϨyWQ!eٰ0XI6cG>>0T[Lt,~C<{r g#ڦӤ[ yfVqrGIZ)Fh^l1Xxvm/^c)[w[чNUsm*-.:u܋'1FMo?k}kY\Xil{x Tǧ\$u}o[t:F[K2He#'?xoO״Z+mD9/*W 7N&Q:]q.hgVY?jF8*^P%v%' <4-P_R:3m.U2#'z ^@DOBB⧴㼱7Ɠqhgc. ,2x79<>Z7G#"4;0I?R2[HdX,# d5r@bjfb3 >E'S~a]nm>s.pxd[b6fH`R2ǞIusuN29Q9V9?z佹^Y*'Z[6ڶ4~Q$lre*}"_p9%1ꊥy3ϼ·K[hXy9IR? dvlմp߼,8O?'cɟ}my3ϼº1ww?v]қMՐ9QzGP_s~L7oɟ}m&3]h gkcid'$S+g`c'缙o[>Iyܤgztϝ{5ZO1|r2w`Ǹe]K}t1Fq9cX8XҖ Swncz~j?7QjA1D_RΛq֛{Yg4v3l"pH35v?o">ڏMW gfCaO.ү77,&n]>wi_MpEҳm]hQ uRGyJԱP5n5K)xČ,fh{u ?Vt^ /JGL((?$#ş _ m_\IJ+pSJ D5~ox;Kgs#lrՏTf>IPHI5t;XtA8KJ̡^CC0F:ȭ>>K%@[@C:E-:95E (3b7)393sf'(":;b1rnd'<`٭OX豨A܋()$0pgio;$] U3; _ :L8Na/!Pr c#cg#<Zjڟ5緍CO1P2[1ZKYinL0۟sX:~4oPU#iI1!XJ0bqqFh_Pv6 #2O>X@{ms,p|7՝_Z.yږ+tH*T!%[pܞ8i)K fMK$%UWslg+=uO}o5;(;L}X}oot,(m,'>J c- ѯ5)5 ;ӭ˄pgZ1v>Xf6($a2(۳ݲ*ySm%a[8ܮ$99cH qux;Ff&y<؜@zcbV`sMFo$kaB.D~R^r89''ߍ|G-ZĒHGNA"U:mGw<.Xm$dʖ ya|@bUT #RGxgNX4č8(Ey5}x>uJmU\fB%ځnA=8㝯Zf,~FXF,Nz3@F)෍Unl* {=ȇR V,-g(&B#9ڌrx⥽YE%N2Ҫꖒj _hl Hagh?ͪhbO;r9}J'Oմ۸36A 5/4gYޘ(@vӦ?<yǧm6W ٳ8?z@eki jJ@O95<H^y5IhO1櫴~@ХpGUb$((<gCW_}#FRԮo U.zcM}߮h>"·Y_ZyXdu#=+~ u͊bk}<(յ0-f}h3jWKu[9em9=>Ci#pe7t8lf6vj0(+$km`F*2].!ǔhSV Z0YC{}s_^Oմ-b &LHݼWC {W_0~ dG챌,CR sNZIt>pO jtˍBmZZX 98o½c-oG;k/9wecu8ݜY=@+Os17Aӭ$O|߳D;`HXr:W. wb=()%حmkvZO';XF1~Ⱦ&䜳9̊_t7B]Yъ% S?JE[=8ܬr''f /o ".7Hێ3jmnMŪoT - +zۚڰcwoun,l;v0A,sU2ek@s'$ɠ }sþ'll6]Gi4-Vrq0C"Ʀ>.Q5k 7p2~\Gva{i|wU&i*Z (RhzLMc 4015ʢp'O<1QVIr4a a!nօiֱ%n|R5عܵqXZ >@Pʠv~oYhuY@z(mRV$%[a0aօ[Kac%h2!CJ~B808I\#1!GIIZCOq8)3( 8~S:zMw2w1,rp9v1&Xhr:XǷ̍f!QFI'{zifHp(=r9⹟kغլ沆$YQFfK$'u:V[H2%1L{K_@5SEՍG[ɘ" PU w7H]#^5 [<麕|2K0cߝrh$B.2NG$;_ RͿ!4i7ڤA3*Ol-B+:Fk;:E>%ʹmE?]0>[``p(((+OU-DP48~TSi?O (~GO(J*?)紟Q7 o'yM=?€${ISi?O (~GO(J*?)紟Q7 o'yM=?€${ISi?O (~GO(Jo'](ꎰj7Wyv!X܀7٬iHjZ=Ux?)0=y\$W8RB3اRg;~mX- dBƅ$  z 0=gkM̈́>]k{yf|T**,ɧۥLݎyS(((((((((((((("Ʒ. qCq/t]kvwm_[%?(6. Wdz5ݭҪBl;ʟPz?7~ kUw2;>9h;N@ݽT!R)Ua?sڟgٿs^'u[{hąg(3q`t?workbench-1.1.1/src/Resources/HelpFiles/Splash_Screen/000077500000000000000000000000001255417355300226535ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Splash_Screen/Splash_Screen.html000077500000000000000000000046321255417355300263020ustar00rootroot00000000000000 Splash Screen Splash Screen
The Splash Screen opens by default upon launching wb_view and contains:
  • Workbench version information
  • Shortcut list for opening Specification files (spec files) in the current directory or those that have been recently opened.

    To select and open a Spec file listed, select its title and click the Open button, or double click on its title.

    To turn off the default opening of the Splash Screen at startup, turn off this option in Preferences (File menu or wb_view menu [Mac]).



workbench-1.1.1/src/Resources/HelpFiles/Splash_Screen/Splash_screen.png000066400000000000000000005630251255417355300261650ustar00rootroot00000000000000PNG  IHDRz|C 7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:͏gWp:~!B!B!.L~IJj/[älv]tўL[SMMMyyyEEEaX0 p:EEEd>o?bR:ڸ0F\kvt͝r9W4onu!B!B!.>vUru^iSHHY 'զ+t5MWg1[b* 9-5m۫ManĖuY7B!B!BoA誫ЮNn4T E4UӰF:*%Z[n(TUUUU`Y(eNskԵ/- |MSlfaiNi#UXjooB!B!dpmsvr)..B"DJ<JȑnnVf*N-άʺv[WmMut0iL3ΔʺB!B!B| ]SrrWQ%CZ8'EY7WU0L43ȌZC-2 CUլS;tͥnPu n$5#laƕ@]B!B!Bf\njl5mm)s/m󜜜h4 f&]SBP"YsRlbݦ6nKmf ssm/KB!B!B]=,OnSݺˍÑ .ݢfѣttvכyUfeYhtR>-z7,s6$ հ!Q~\2B!B!Bޢ~/5MӞsZ}]3mD:ݚLE6nUNban;t:F3̸L&CPssfs8d܀O/ѤaSƛ[B뷄W z]!B!B! /̐Rp/1ǃ"Y㛙].nommUndҲ@ L&c؎7ϪOS4-iY)w`ɁrB!B!Bose]Nm6999^Wu JᶶT*I~3RM\.PU0M3Hb10alk.B!B!4e֬Y=:_j8ffӮSdߙnǹ !B!B!7G\2k>jvK-nmurB!B!BorI'vB!B!Gijj2!B!B!<J!B!B!DWB!B!bOHtU!B!B!DWB!B!bOHtU!B!B!DWB!B!bOHtU!B!B!DWB!B!bOHtU!B!B!DWB!B!bOHtU!B!B!Gꩧ"!B!B};M#cW "!B!Bv1t#cW5 :c{B#5cv)B!Q$ɲ.BxI#,B!$*DoJ.BxI#,B!$*Do2 ?^ !B!&y-ᆆmǿg>ۥB!B&*Do2.ækzް^s{^ Yyo,9pozR}/"G4]g>rۄoY^?MO!B'U!zS }ZkYymS ^ѹ2 ?2Mtz"GZ0}zQu꥛xؐ/[/^5V<ϭvz5m/5B!B' ћ~&75B)ѷ`F?ۻy_ B|i_1NjgB?vﯝqf1m_Z; R7Gy_fҤ 4/v3Q=9s'⇢[` |y\Q'LfƗ 3/?zsFϽൃR nx㯙G[B!']7R_=]uCW3X#o}i-hVͼq7ލ_pճuz߻qGG'mm]w >x'݃DO9tR)HNT!;g,/}'WB|m/AsSCkͧwg۬\`<8ggg\xP#R+Ϯl7|GxzPB!CB&EQ׌e~3io]\?ʟ{dV'yQ&ԑʳ ARI&EQ#_VoS*!yGG?x{֌ڦuV\ (. YoY|aPi>B!ёIӺs<Ҧ#CJu#RUS5ǟ2WɮmrvUsOvf k*j6laC&LvBn] nG2M8k| ei!PL# EML*ͧB!?6!@ޤֿW~PUA ?ϣNS ,[<7w8]g4"3wWPho SfJU;V uflTϾ#/_$.2Fh]K%?`ݚ6pխ 7,y.?C\6MU jfj#@O!B!~d$*Do:̩i].}%Wμpt{>g,2M* ~M鿎?x_ﭟ{Kr9f:lTw]aSUM*-s/BncM5.8>ӫu3sϘ?v-?[3v14M4TѢa!B!ze>ƍuB1\\`\{/0ZZfQq׫mDnjpuJl@hѢѣG$zBq44B!BۣFi2d Dou}s]ܯR'eznavvdw˒5w 7):F=7e;J!B!U!zfZ+BwiB7B!BS]7͟? ?^ !B!& ћƎEB!B!ݽ뻒LnB!B!B=!U!B!B!237² 6~yڵ--ͩTLQn~и 'hwl}z՚eC9pвʾjc{j|'B!B!DW>l?BBB^)R`M8lneaӮ}o&XP3$A-(8f#9SN|㞩ol&;~yD֗>^| GT:n5xu|L&| {: yሽB!B!V23ޔ׋.:2  :_4FY<`K:uǩozo~ @%xa=8 P瞉GqG,>PUK7RTO_zֳ̮q;3,d[yWt% 70z%I+(#Q!]ZzE-B!Bo]kb_US\Цc6\b'h$$`ʯ@9)0AZipK&^ӟt]tevݮA'={񗭌~cy5ɧfq6,5*Ϻn0&}CP~54cN{Ӿ܄B!B!{eY>?j5BBԃ#QyĂlap(0$,8 B%ǯ#~:ctLыT(. B$ ^m]~ϔǏ'XTߙ4{N>Ǐ?~HO)W͹qҍs/z6x=hg/YoYWbɗ?V`K͊nZ329NƎ5OX;[j\I;9ɒvt97Rf;3&u˳^!B!bGdeõ lQeÙM!ًc'Fsr Z4Р`_tg aX$:{'փSZK/߼9%o~U[9$ ?laR`-ycR8b rփ;~ܿ == Klڬ5x~1Uwm'W9҇#>4mr;~&G 2,/ۮVW`7?̈́3N勵zvB!B!9 Ån:5niJ.IdB! Th0(|@Aatg̅@?Cb8!x;Wu'=YueK7EnTQo3cgiz>.o9?EIxU|p}Θt[cqwtNGD<~ҕ  -2OS̞ϘtKG͙3ėLp;f?4)W掗njWS&+TyU!%[J8ߞzEG0m|W< aYwG\|!5=q#eU7>iY8gVi} 3Է6,YEuSt@ZHdGBk}|ΒH<^dK5xZᙷ?1Y45֐92<{^]4֭Zh~veẇ56-%Y+gj׼:睚Do!|B!B!;%ힴߴP`R-TL* -N4 luV.Q5s8:`뜼$qx/`:lў[\MgCp{CulZ[/Y7qmW&nsMfw޽GΚR 9WNzD9}֛Jp#OLJ!rqf\jTQ3~NﺞoP;o'?qS?>}y/J1tCa;)jҲ,/x{uٝB!B!`w-Y gX^14TZ0$DQ(&'0 sr@ uCD IP`<80n°@#[p4PX@ug_QkKkpuijgS2@uΜKm6uG!obfGZ)tRňD:ceo_ם϶ dr+#gHI(qptޛ6 N!Lys(J Kf~`wҐ 희 IDATW' c)站iW$MB|,Z/zsA5U 44%1}p/Oe%$㮾_OB!B!:|L2輋PΙU/bjAMՉ)lffWαzLTj:#Χ]923QWs Q3iasN-lI3N:N$L7y5Ӯ ?$'Tv!B!B!eYh4U5&::(C0WaxL(&D6\=l:Lra-4s΁LٮD>:NlNʝ Uji"Qm݆BCNXw_ ;0Wj`_vB!B!DwK$555z[rG&c~=Syw -y_8  !~@_0`6 (V ҝaUHb0b I ߓc#c3[ҠB,*Mb@!ϢЎ'w>7Zh>XdaDCiLc͡oes8}2!B!B#?0k+^*lO>g*ui:Q,J(Be1Nl̍`vθ 29G )0H),"3@ 2Ii Ug/׃$G,JQ4F "W/8_2WgۯB!B!!5\e.%"xѢ4jƤՖbK @5JP ASNi.n 4>Ct@]H@ 2qYx! -*irCA>BPu("6t },tn\({hn4l*H{58+߿+))K[?=Y7רZU 6vAA 9s6nUfm{a9gVi֥_v%PL(n \rݛkʪ;ieԿ7c3}/.aeT{ݫS^5ʝ2 vTnOPtVKpťNƣmΛ]٤)Թ*=s7?ꫠ.ն=5](߈3&VJIŅ &zޖU;4e?yc{9yM%bweε9yONۯakME_M4ǝP86kPEWW%E+hp>{Mp)T[jΜ%o0RWǟ?nZg=xg}g\HwRoζ RWfxwg.ڟ.I6ԁv%7]s鑊&s݋lW!g*=nX@ʴP4ֲlә/DOuܼEݾzȗdYe͗Qf5o Ϛ0ne(Y }d#t){ rVwJF=vHXh _z?LMoin͚fvwV-gեU4/4嵽| !&z-e˿͍4 hwe`BPAY1щNh#p3/û?>pb@;#.* 60! KG@ L>bU($AkqBR!0HDIFĈ$iInWr),Tcit:6ҴDt- T(LO~xѪ~oJ68)'-;G±Tnڽޯ`Ǖ=uVa϶-hhuv .V*>{@B %t[=Y7ӘȮB9һvo7ՑNn\y.϶+}f׽:YRUpvcZϽX7_!>묢s&ؾ0X9vO#{|>uƇgo|ku2XĎor{r+dg]H`w^ {u_Vn۟B/ȵ;M3Kι`RVR p3ԥ 16]֞xD~͗M=nn$;m ErOW',ħ?YjKy7eSzuWÌ]\*E͔f+=ȲW/vcpA=e"7GW=:&x{o{ٻ^> zqpg_x\rLpdS~;)7\g`dVh2l#2|>z0G_!zjKrԒ>SϪn@U"n"F(jPK\%5B3s;b!7 h EMQU}.P4u,ԃ<^!̿GFlת5BQ# t݄F(ZnTiWWU_wyiOo 誹p^lXІ-tG&BƮ~Ųn뫯~@Bh@4XUBE h`(}Aݟh‡PP hE X6'3PI! "\$"8U6[4ذ4L9C_Vl"\LPQ+1.րň񴖶jSNb߄A6"fHaK'Dzņ/R_$luzy#uZڕEBƱ7G'{a"߰~Y.Ly?˩o7Y},}kݔUϋ }\9DZ{ C `cm(;Z馥i6h:_Mtq빰MwdXOEw+n O楄ޞj|P1qۜB>sOn֤q:8nTU㳲,߭ǥW¨/jVYPZl>v-6@*YA~6Σ&;n_ij;궝?XDY hdV $~"U J/^\ZRvL딅[r#jܦ_.4@ O,kt+=+~;jkgi_|klk.-~$+n5׻[C $D"#GI_H%Ձhk z,6H[j0<唊F*uVu$yyrFqS>ʒ2}A]n-2^xuF\]egU.@߳H9م*[t&rߺ3ݪ={k'o=>;QxgUIDθaMXo6Ȏ#y]N_zǟ,QlaHBZaE[p҅}jD%Vp`S1LpMp Ґ  pB3N\CPLt@BhD! NI6}Ȫf@+(4nH6V$5%\G̫;(]oBE@S _:Тpk]k`KI4?+.M jx}~o,pr k- T>o_N]wk+v"WGUWOBIu>_U05@^٢rڄS?޽}Wz1~'MۺG6ZS1 __Jԝ\ =VY;Q$_Ì>D,}9o7ҥیP sT1P6tJ sox޴U743 |zο~̶e>yf>ے!*`z&ۮ0um^ O߾ cZsjUft߼k} #zBl̰%v_,i+'cwk3} T,K<͹r.x~"K6 ]W Yi\s[exe K7e^ⅆ8(Y7"P6n{9xX0s)L>5OG1#onx֋GcU;0pTn W-TtJmڀM-K=*40zzݗpl:mf@ߖVk)oƁy==;$&>9';$(W%1+{cmi%u/xⴢE-*7$bO}n(`/]xRjknR8jM}7?Jky-ඬ(}YY- Q{;6>Է?cbGFiO_itwmaΜXسKs%s^0ZB޽!Kﻣ>`\Mڴ}'Ƌl}ڀ1=wզOwdZ48nۢ] [Z ;EnjxCЮ hOl{wzx`'xZ``ﶍ췯Ç mc;p4=+@swN@Yu;ΚMXhO?dq9r3mZuqaZӧ ^3ۭjv: h1җQ'L93a%2^';:\8@S l޶kݪwJ[NJ"60wjMJguf$KvLl;" rV{@fH[.T6)B4ZĝuRvP;'ZM tjX !l j9D`cw`cY$ aihgu->[g( ) #U@/J0Bb$56 3"@IM+VX5S$jy}iVF'}vv[=BƯmok-|R~+1"ŁHm}}5 }F 4Zx?{ZV/)vN^7!8`G %5RUn9׶0=sWƼŹF˒+۸9+6Fjz{dn~%v2 :aKKG/.>}G𕦶˒Wo +9|O)1긠9_ٶ4k̷WBx:>5+kʆs_\wVk`UH5O؝{5aCX(g>o][|.]#\eM\<ůoZ>"Ͻa5n5w;,TVǡ _tR ?ŊXnC%*%~ߑ} ꀱaι:R>j}p m|T\Z5"`,fìp?wMٰ_n^꾁tz`{|u{[=,G{Ifv*kxjIuso4n2գΪ2B{-[(<ۏJ+}}yejؑS+Pz~{n[l??Ci_n=KBU4*.UrHyac(d@ꦧh0̔ɕVj>tcu 땱'T=R)gEq7VF!c/K.nZ_璣~ȼڭI' qZqY][oܺDNd-isu4~~m*8s-K8Neש?s#|Z(6)_⺛_m*w}iz.[qe;V*Go{d0!L>UK7{T`Q暕-mY-C>z|ChIyq`1B[Astc' wo"wT[ x_ܲwoﱵ?T4[ ]_jiG _i994He+I) jfl 8b;|*e׺U{R٭d:p9|4w;ٲ@ 'W5ֽ7"OƮ+'&َ-p(*#}pxq:(١@srL G50!i@ HG09l &u MMPYV2m*I%rFCP>CK!@%w>"4$iOFXѦxz 679jWjXkFT*'Prt) ~׽J .( +vd`ؼjfslw|w~j@(|3c|Hq]͜,@!#|`|dNVUs(}oPzxln.;4[to,ԒLݭ;H?TVxr8s)s8=2oujH;͏t#Mj+-Ë*anZh(DN<>ݤBKٷvstk\J_o0ǁ;}#T/`W-({U}+UMtP&^1t)>|z?{oUu7̝}BA6%VĖ q(J*UZh J`j@5hK,ew d& bmՆ;9|#kzvgXAłF mzCh}_3n5LgctߺbE8=y#o0Tq8SN LM/s"q]bo8qŃowGk1>#gB!;Sƪ4.sێq Ѻ|ڏ@PwMUOz qSa؄pCL§=[DL׍(;]{ gvKi94S[h"&ǻ7Y.XVe[so=bkg/~[ '~`M:A2bsV2 _<<:H>t|P院=[i%cx bP߆T78'Ut>8SR{NuJyrxpQj)Yߣq۠}ki{]/0Z}mq{W6noYЩn(+MS/]+ڛFXDE@@w(5rc-S *wZBa\ [[ShI*g,Ѥ];33nkFܧg~ D)6gP|2=~t]8|xaFo?/noTP:F("*=J9dth6~.L$*uTuMhLhPɽX6oTtk̐pF>P Hn΄f0B4cwQPD-0ߏ^Ei;Gٓ2c+A \pS+sm?Qo9ecz:AI!nCWwZ>T$H^ NөmEN/XH_-7Mr(  ^;o$X Q6xwo:d_GmdŁG ;g3,> [{ jv/O&|v!?sg_ջ/]zXăO};l9@|Om $Ѳi%өRq.nnl%y4_sI-ڀOJz{μz/iZǹjyedQc{1qO@Y_=$7}ޛ&+>aEѪOi/3ASϕ򝁟?.X #q_ m/‚7|xvB<8_ŪsRDŽv[ZF4kDȂY}ȫ'L2H[̂;0o>3M:pe`뉇ܙMiݚez,*WA#NY(@$ :QiG,_QͱzSDZh[ ) O$p#oMLW _HÉV]fvSjI5Ox٣{n^fyBe5Rr_]Ym8^?L5Iv][:_-F1b[;%  '@IBoDHG1p$Ґ$k~.'y_Fœ6U8>w ˖7@U=A :Hp : #`A$|Q^0z@?HCSg OT(wkK@5tM?Mw6>p6oKkXto{f=[1H?{q}nk_4̺ l~[2^rs|V"(Gڝ;cgǏm[g=+rӨ!vyU:HU<%Wo5RvpvѨ}blln;R 6ufvI -RF/4H)pOhɪy,GDZ% wL\-`-6G;%1-~|pc]~u0vqt L`ǽ}u;B5cͣUk5 9hi=(nLc&bdaL( Gp7 _#*it>dl(8y(9˛> 7 a@32U'T|!e*P[4R訔g9e)o>;i%UlVP8!Qĵp7]:T.H/[ xxҢO.1bĈ߆ݺ˧M3HxHTH>0FoɈ') kH0D!^A(r7TY8\UM NpB34A8A3$C_0A8 ]4YND .x8b(/`"cwAHDVܒ'ډ$$x ?j\Jw%ZZ<}h=gơu W7\_ePTX=gIϪVi՛o 4Z!c7 LNDMT0-O1I[Fi8ڸ >m+L?tk:@qz&>W&I꡻cuS#@ߩC~3TTiAZ"xڏH@Ǐ2FOb&_ AVg&۟NY/$垡 - Gh8-EGl5jnP  mW'+oMZmN}۸,jp;qRc&>vk܈: 9O]bZҫ%G]-_ݟp¡'cw6=|X䤐%;O<oeh>D.oE]y`qIp݄]+6nF<*Zb5aVp% #6֟{?;4gOc#rEg)c2^ pƀc|`9&z}~3V?0I{g)Aeiu_h2FXBSӇU>3qƌ$/qF>rd#1|p]̧o2#6Μd5{p[ܽlkP@?{;3gsŠca: ("# dA$IC͏jNBVg"qiմ<Ҩ$~w$ކ, e!QD$] önPCC#0-Viq4-5nxs| ӓ[i]:A#ƥTsPJN.~[]GsV7_HZR#\]Q4|ߤ䵷[We%’kQVSVˇ*:o)]̰j`j<Ԝj#F\'8N@j0%̭C q=7Of)dD'pr#)e\iC\@4+8 NIXed!JbHk CB" XP`}XU `x0C<j{=kD )xQ|(*|/5^TXԢzWK2 kAgB֣2x͎4vVyM=֫k= MFĩnocARZoUW<?{V8h=~$MY ثjہ%mUz? }/7|7_;4p 3jfdޒ vƱ{Ͼ[ˣ[hvOh%};s_:'x=o\)o>[И{:049ߜoxn9uGp:Vp>ъgs-i+#B'}tTba .5X-+=Zgp s&CJ~$]}7g+yխ V5~x;_AE+o>B/Oy:My{Kzw^ZnDK]S]ԪN2,) t?/se(ESVA1٪9N=gn-yر1bĈPWwU/&/\"`wA QF#Qpcᖹ S㔊ƷH`"x2ЁA2hA&H$8C#TG ,!CpDCេ`\ ,` ^8/ >|]Ñihu8AwbHEH)5GUc{_py!Qwp"_iP✂\' ? o#)g= Lך<:t# *4sZjE'LjIښ:YO0{<9*?zC4#I'Qe@96bvL8cM g1 $L@Ao- t{C\&;¥ȵM^iG&HYYk̪@w1Hӥ2™&%*^V- z')KX~O֥"^IlTSJ2k*_7Ϥvk蝨i077ihrhA?)*?TjCy3ƈ#Wu2h:M dDZ@ W#j.Sku^⏓phՇZ't> 5BT¬H@u+/8BOT0 J4,SV=3tu:ANAnNQT>Ѫi0$5Eh%Sd⻀ɉ~Te8&-= I"ZJ3{;$q@umzVyKӂI_(TxG?e)-W4/XE+/*._K8Q*]xJhy_vNeQ@}ut"1Ӯm; $q#eK ߸lat‰}eNu<}tq Tn] .&%>@LѬwZy Z> n$$UL.4.jP|GK݈^lPCցfs/>j(n%$_FgPUC׾pC` ]>O^¾wZ>hҁ-s+֥!Z*;F;N.bt=߿^Õh1btՊuﮱߜ+U8W ٮ8a]l\F_*otM耆:uzTR*eHUA2F1bpU[We ׃?쿀T* $?p d}5fYj|UV{|QWeti` ӹ"CfXPB6S3r X`fSo0NЀ *: T`8P =$taNh (-57Ct>t>|>P7znjvXRWɒʪE߀A;lZ]%m#'nj Y'|eQthdFW8M9^kHc~h+)^1!F1bĈ#F1bĈ#F[o?Օ׮j3!pF&d HO7xx0hBDM=hzѨ УFC(tQ"'p~rڏl? Ё-*GlB~iă 8܆N%ŀ5irf2}N?'q鴊Fc6e5`PK{lW(#[I4;*Gl§BAFʨA-M1oβff\ʹ1bĈ#F1bĈ#F1WuUQTZhc*1*RDL ơbҚdVJC '_D(~_KLo,4''$]QHݯ ((Vp 9-XzKmuJCLvjQFQ+#auqr#uMu>hi˲ّ7*N^DNZ AZ߇چ_oWnzջ6k 1bĈ#F1bĈ#F1b\Wuر&zu.gH0֊)^$ =kF{Xc<*OWJ* k$|6d]To1 c[C=_5ʫFO$$LZ 43~zU IDAThNan 0%Sc%ICEZT%FmB]4VYeﰜwv֫61պEM#4g{587`ůq$!H& ,.8H2,175rR_T޽y+sMP[#ƿ4_?*3Ko˷֣0n-] `+eCQW4Y ct|Mֱ#U#dWec3cĈql^ZS\3h+1&Tߴ [\#X{ nhdEۃxq`09dSچ$gC&ٖ橹FOiԛiM&?>º&O>䬪PP:2A `xЂVPv*8g@]Q#^FfcGwvtWNOvosvjwJwcb]q'5{퍉FP?)Ílj4` $BO~( 9XOAA;j/w]FZ}.?O\SNѓ[QRʷ-]S-] ,jW78Foivm\`c)EkT,^/q#ؘ4A}?'gpѥ= rrr.{zZuJ`(ReaNΪJ)ۊK['";;f4a,߹oW90UpEr#6NDu}`ܸ-G!0r5ss6ԔrYEȚtYl*;*\M*[59au`M0km۸jAvVΪ}]^sQ@s7nsW5@T;W+mƜ윍:P Mt$h`Gcfzdjٳ&;{i^j// {;~UNmZ|sNk|eJ*2ˇht{#Ug]mn5;~9!.fӞ!lCNgv*d8x̸3:`įA9x)$Hh|-xB7h Z ܡ(T4`AQRqH#PGn7jx \xn9`_|n,yw}J^ܣZVU`scvLLe3]Zk{է~=~VSæ8dYc*Y`KOv?x`ۻ2`ܒo.)ޱnyUݕ2e-99k(WcߙKYVY2{%wcd|M0ש&/ 6~S#%ۗ1xTkmq22R]p_nkp6>$`WM?N]6WUHZGzcSɑ+ϛ:`UtVn/ʯvd}kRQ.\5cɜu^Wl;{2RLeKM7dpW{$T7sj>}222R8֕ѧO0s aHxk7(9rpǓs'g iᆒ#Z>kiC+&5b ~y䓭Jj[vuYc6a.?f.sX0ŕ]V*k5v_^Ȗ^ĺڱ-!l[]LX];^?kI/Zk,ZXP6lD)pa,wOvV#!cΞ&dBF"RRkapXԆV௟EMMiᚍ;Yfo޳ g {bl7؟_X^E͚:7?%oZ>9>?j7WNô0zyy]۾5gQ\RiN{-jwGô]e0yI~MA幮 jwW 3_ASnt$Q:.X2!I*¶7w܂ -KIޜS3W.:ЌÉbɍT^*jGy3#8!8+ϴR5RzO_#ԧ8/UmpwGC;, uFrfCVP6w tGeԘI{Gwpttiql57,uG6pe|l?W0BywK[}" _F$R|GtyDEgh^"3 [f.ٺ1Lv)<6JJe|qx`z~,(xhM5{w+(lTLKJS1ayŮgu˔>V1fiu§4YfsaML3j_j.˦>yIy~篛@+q[:R. ۚ%۫A[py[0GeYa;}8C@;Q}2L! ZňqqYWɜ|thA- !dT4 uZnp Zn8G ZFAT.qA 9!t QЂ8GUZ4C:d@mjcFxݢih9N84ԫqx6#"]w _9޳(6@<|K- SǭSAi+QiCzqy9z/.i2iᜁZ\PPKC=&*W_􋽇 =g,G ǭ[=YYqmƸh-^R~]e-,e?AC{u JQzs {KZ⮀,S b]bs}~ VlpHOπKif~k,Zyshrф6,Nȑ7`]6蛦XQ㷟Zj#}$(Taܺ .X R厹Yz]NyM WoYǬ+O`vڪY#FG `/]5qѶe*}pMhU;'rJk|fP/l,Gɖ ,K5*8Q[APoA܍lAh4Yk*%6.&.j]%,(mhae܉ ,\ ٜbHn-j6~"[h4R媖ً>Nl^01wͶ !ko, Wł26 f371f8q= 1{ihϻ8&זϚ[EBhlGtP$,0`XgRAVjmMB‚{U(KX:"z`&.gM Rӆ}\Ȏ7 ',ᒶy%4L1=Oˮq4%=G'atGXyb*MZ^͹YU k{/iI[^,eOB-|Da斁þ/W(-T VtV΂9+vv:R'z3rn+BˉIj4BVN APl(>C}|&WO`k. Fk[&WRі Y>Yrkۢ+vFM1FUe]bf]]EFlFy: U!juUҤW9LZt5͘PA8 vժy$ =C%FH Eu *|U׆R5!joOH$pZLɠ $`bhAnP .|v\ q<4z T۩q :n+h G&*?g4o{y9rsEH@X!>>? Զ4=ULVt$Vwb-U =9 3;ws0>pABjzz`ZKS&w:?:MCN ӱH6?sƄ4SNXζztsxc )խ % gWphObJϣgD1s EJ^{g?5҅`TW{ Ky*w# <[P5ptpJJ\e.`BM:Z^zc֒;V!~f,`Ke<۫.V1:M)PGDi A2J_ҼR Q`+{^if0S4o ;~`ٌ& {i 8Y(ovʙ<%N}ls%*ۘ5c-O8x v>ctGNxdbe&}lsȥ1a"T. +*lwj 7t~Q6wsMQJ.>XѴY3**޽zf敺EYK̫\ڱzqSvR /xdOfD`3,wJl@ߟcֺ͢.𫻡)aS񑪪#͛*Y+_Ȏ?I/JK}[[Z4oⱛwo^W@]+V9(.~=V3n*:IѩфSpl/ޱ|z/G/:a(T 4 ٙ|JV&/,z,ȏR ʾ"j(ѓ__xYkgf%:tÎݛ*];#(Z%'cE8G> kfOBnK+Q fX2cZ댃}%'**J6M/XA1N;mg#gaߚ Wn?Xqme7~?#yJ-ʛ5~k嬂% \<w uwVsgtPV H5-k{I77̺U1uOn8RUUn =\@z .gjZ6#4>U3_ v-Bҝ {jfP㽩ƛ>G鎆ٱJkN~ѲGx --Z8cqP/\79~?J0d|x쭼 =l/[klpI-(eQG}jlj/eA"ͽΎjӉ+n2?R!3p1UaOL/k}huID-|ְy#̫?ha.12W\#8Q;m?kVVtgˉ~=MgUX=$Ƒ] !G݈ƫ2ۍTks/ V~蔠OW]`#4`8cH+xHzl*L NnՃP Rُ'դ&A}8V*nziPP zHT@, @@YQV7,v_b3BzQ(_Ez'G 7mg+rm-_|?ni^i'b:85p۲عZ2'Ҟ2}|J/H}WZm:MPbs}ɦ'{r[ԥ.ycAw_#`Oۻ*wa鬒mT|cv옔֔&k M!~BۈB^_bv3.`\/,|01>ŝ>|m6̿q(zd_͞7ؾ5=ye7 uo<sttMn%K}~&H˘Q<粌4iXJ2ZH&Pmo5M%%hW4fÿ^7: qȤIB9U7YHŔ߂vka74,3sؘ)^[[yJXn ȋy_x]+K2OSVnX K D7Hi9_y1Sf?2qrǘ[wwwŀ7ҒruT4e0Sj[{1üC+KJ̽Q|8f@BB^&܎@2xr{SF3xҔI횚;%{1awnʄo?f̨_i޼ ˋ1*=}=i}h4=w=3e̤if(zLXm4>ِ7a0[L|' `\R?>{ZV`E&fz`Ҙ&Kꟹg-ӳԪWN HsX9!XцBQ_)$6t˨_,He/-)dyt e_XSjSTݠJÄ>:;̟w}L6d~Y5Q;hDt͝9qI>>_lJ3Z(楥-\\KG㾥Z(X`iVTDqU/Xv )5zH->Bw-=5axYYK6(9飳[N\޶6ђ^86u<0=%PfE{ Q~iMiF; IDATӇJvmy,#mⵕ{ &bZxi 򙑿ǖ]n&H PoϯHflG쮜@P||6]$ q"HsrmdbD&Lq],Կth=P((b,Z~aw %"n ĀkU (D4>4>t>tf%} :go*5;>(EO($  $Q=vpxQ>J'AhY- ax>/`@K!F6_TpABPcP@3P`^8 Os2ZΧ§ /ȧ`܉4o2^%6xO H`03P(A'! KRGJHEuf/̞7kJ:o雛q!_w{4Q\Wء`﯎p,k= Km?wYzin׈pk; s &9VBl,IqZ탆s-nSf髤z'_Z M-"gh x7᡻Sa|S̘Óeꃒ8ZD 1&(ƴLL08)v|[;jaךhxxP i} sΜ c'ɷd#M*=u+!&.7Zp8{mœ3qE4ot3Pyyy]DS:knIӟF+o+Vzq ^Q>D}l%KL?c.i  Tl,o¼ /IcBd)SRofRjIlS26nvMQ [Bcc O, `#M"ζsNx%+،pc&ΏVwpF4uWoe`ٻ=nZ{5tӲ!cg=r!oh"))h'i0l{;^17wUYAH@=PIъĊ|$1Jx7vpVmڡTOnΓmwkLļ7W۝ᷧI8b&$tɠ4P@$\\T%WPOBD,WhNᜆw$]m Ra.9K1  )YMۘ34RRIJ'yjnWH3@#0@,Z8`xIj/'#w8stS^x~nsus5nj#7@nJ[B⸢mVٶokEx&uV~g?g}~SktvXJdQ &nM.,[9RƥYNo*^|&խUgV=M77BfٝN[CYU dʹ677777]T9nO!ly`ߑNx܇`Kg;:;;::::,=[1*aŁN0ϙ4/d\]bR4/(amkڝ?)nld 7Y>].Hd5ʥ[vkwGss(mVkȑI"v? |_Uş GKZnZ~2(y{7OMmGwM,(} lgXzv[buX]]sg8&0NH6>*3J@K7/-i;=EQlN'z-ںvksݎjx]K#bs~w5ۜ}k˛ظ4joE ԮV8nSWWgV>cm֎#*F..>.tt[nW!+Vo?c;溊Ư Ӳ0U2SݖvɋiPtwtw+ɪ Y4l{ni^ RBg\jc=jg^jjY,0.*_n{z~-41f4|\݇ok~ Q[6gnK]CeMG4U%+>y_ʟ#OR/,lf0} %DG:hl(Y0#aVM!5K%X*+M0" @8܉?AhP `zSR䪧`}Io5{nU+]3B2*1}s} }x I@" @)A! RG]~PHt7(A= OCO ebt8@pB$]Ip10Hf1JJ I~Pw A2|?VG3Z?>^@ {zQH8< h,n4jqw\Dԥ^\gi_zN⸄hBGBEeJH!ʵkʳ A Ug/˙S9zqɪД=g3u3&nͰVgW(ղeT2U+_Yt~yΦG/>7$;GLfox;}iz}`rhȟvU=sw9Ձ 5G!0/z=.OR嚜 L5N,_{`}qnO*-{}vvj]uKC\;-8D&t"csew̸{n妧sc)ڐk6UrHL&b\=T|L5B>F {#͔m1g|i=+ڡ`Pb-{pqn{1X ׽tgxpyyubnYv=cȎzyD-W9IU*a?dϫ:sfzm[Wͯª][ˎ}m⬉Wnog-˪ \>lJUJU^ 1فilW={%㧋6+r3`ˮ*sنOI&KfZ=h%pφ-ʗ6Zۗ<tKktYΰćB5j/XDrtl3[N=C+3|Νlq$Ν/ _laYL(صgKYie! 1j핣*1EO64)auJzYxa5srX5-- E{L9鵘l),߼̹ύZẰv-9krw՘G}Hɶ̐`Z&e×V ~ uA ex(O]'~zF5P/ FsEV@DjN\\o|ׅ/-`qhTbQ#JH" h8Bg=K[GxqЭY|+|,[F2Dzcq \v}?0lkjnD߫Oj?Rb]8=64ܾgujZ G i;^8èhGi7Jny:!hN uAw8uNU aQ\WޗVMŢ6E=3 q+*Ctڇ]^]ܕ5}-m8^h sE<]QsmFv;ΰ;3kOsX6kZckݙSoNauŇF$RlEm8xfca lÌzgыDӋZ75V" 1U,láW>wpEm5e:C];&ވ6۰Z3.Iviچ7t{lCjՀOg4kaƳ^?NA b)uclˋ'3 3ID%趢lO^qʧns! a \.F]`RӦ[{{ԈIIIm'|E@466>v3W$T #-hA@PH(%%fHH$UB@ 0k 8Q74Zy7sh}AهK*$ >=^p އp.A\+x4/94PKvY8e>=$aF>I"Zġ!I|O̺B£ݎ~vn&IM@'>5--khƴ!фP ԴkN}è13]a4G5b۲Xe0'I B(I*h3Ov%܎0FH iDoOgX?Rt&?0eE([>1ڜr̻+>4"l'ʝp,LTRn :lؠʘzadո+^M媽mwV^i5 PFaܒ4a7Y9Nja iiiw5ْhO;7Ȝq0&+!W UƳG x3%~AAXYi֊z 5JׄG?JO]sCQ0y}$&D>0GESM*IҎu{↧ă6TVT.~fDf_(K bg"b !&x^cf8;D1n LЇ?\&Rt NN R6  !l /O|%> v2u?r,f+#`m&a0LDaDAw2@ ?cO (\ an 5 Z$TR+\ #Ñ IDATw-HsݧgiAfX+\dv^֍덿թf*ՕQT?w} ڭ68 Fq: 67hܧ {goDE@9Kg|mѸKhT{KkM.fs\(ih=:'YƇmGAWb V6|r|q(ObRL1vS2NZ[ 0K-T#]AJ?q I =$RK ۅ-O/:U=w `>@3H|Sg@2A|UcpѪ"C x sS3sڏ[,?ă<ܗBf8K Np@z\66#`eD<!YVVFBz>Hx%ZP|W9a= I$+X-7w&1i SֿθQ‡ xUƍ&"(*-ժMD7uiSMkd]e0~tRZ~=2,_0\P>_w܀#ʓ(^iM/ %`d (4z #0$qHTD*'^EN"95z^bL6pH`U7!P)dP1*HDp˅Zѣ2HZ؉@#+`@/@͵zp8;8^6`U ISڋ!a򠷏6JM칏_"("("("(bܜUvw,#i(@ 5߁A? sk҇^ Jj/n-~'"߇$ǘjE)DA/!6~֡@% bB~PC3 XAB&d̖aDV@%a P9$V$N@K\0 G t)t2 :j$C">2UK$1:$#Jb( N v^~ (p)TcjA}p9Ƀ >kM-I <("("("("(>nTUI|~rMo-JB@P"){'cT~xHD.p vP))kPzI{xH4xA%$9GPhD)ɉ4@A/1"hI(B4HH(@ ]@+gK=\5Pox.|ea96pV~'Ā0 .bHM0+ܭB'>]Z:X K ɉ+&$O A JJYb6vɗ-2ʛJb"("("("(~7.`|+iZC0'W*d@^U2CчQzj4jՈZR4H~%fH(eWd SNH0J* $0l!#֍F@gXRDA"dA,@.a<|BVՀ(~tC"yu@SRAKХv+,*~IaWW"T>NTh}.Qvy\qG~_8:&*\6rKhh?Y%:O5߻!:[N;,6ՄlN,ȘBw>t׭]Q"v $޻/$r:ޜ XkhU9n}pN(mZNOY?<:ۚ?R`hT]p[gĜ\㢘2bEDI("r(n4vua…|w|sF|y ݣZ?f$s-n޻e0y}_k)\ B0@YUUˠ_B!Nmo#=K^T.I'Q#8 nZjH ֋n肋p C\ 8OѩgX9#_W hC)Q ̍FZۏWNȤA%p N(z)Zx|1 u]}g/V%&@Z*q󇾶rM)g7:o]YG`?t; Ad\Yq)Z7Qi13lyxntc=gGςpjnni{_Ύ#u;7eZ2#s6m/l=y+o--**=5ہM+v8Ƴ-z9Y˜+=9{k:hJ;͞7,恎ko &=uʏ V;@ 'q;cHCS`O_*$qD8$;FFC#!OKOO wqB<)5JRL'14ܞ3gn݌p\rûwŕuvGYZxxwݜTkƍ_yS?ye;"#+ .9gQTV#Dp^xPxX8[>(1yjj!Y--3_|EK֮u|#ۈF<\^.5C멷#o B&3߮(^sjJ&Q3,x߾ B|W7o#&LU5-;.Θpպ;֚~6iÑNo7>5h|ŋ[t)}B]O._dՓ%iUѩ~l%4ibIҺ벙3|qbӼO՘t ~h_|<_h~_"66DLcFSU>%o!? _mS{ 뺗庚^Ԃ0p%ʰ$=yRWx+ 1H EG(0BRnXVلE znAE7|ON2umi-\w\j_7MnTD%Jud܂kx22&=Y]ƽ_kO!G#x?mUٚ]g߉[)|Oy׋_)2|BX%j!Xr p$R72GrߝCz̟S H>`sA$X]0~A$! c F9,%;RORYp:(ރ86l 䒯1p?;QP:q9SѧcЀ;1b@3kjƅ$$e(xtoyJ nWoΎ_lPPSjʭ*^&R3[OqV]FvD6ٮ-;G+#2|m T؈mJAV $ʍRWAlo4Z0%D/O9vDlE77uxQjJO):Ϟ*"t_ࢳc_{la_M YEuvޱT.DPs4m@E^uY.Zޱ/K( >*9P8mFT5;C~4^TpՌ@<(z5yCqp"ivmjY{+Ai#б"@$~%#_1XU A`$|v.ԒCr!UzOpb--$Q.Vp+ 䲪^PB,C)863 C`w +DZ!ܐ r+ zaރM89I;oe|[H0(QKXH@ WZ qbmN(IIarؑ#G9vv͍FzZ_9#^cyP=ٱ/=-/WQ?~r s]]'4T~KJŖblMܹę3'Vl@uZ^F uWj!hgp g+1=Tpyt=lXWRvvzu\V+9ޡ7,}ޡ&z=cbe՝5'mJr:Oo)S/ѽ5n~y]]]gϏȌ~lEGOe4e57 U[=ng~Ohiڰzu剖5lXО>㾇hZo8T]}ٙq ޔٰGw-*?f%YW/[hxW-ٙz,67,Zn[k;)iaw|]M~{K3WWlrbWga^=lAYZFU]';%06^Kӆkb4[T=D[k~*/$ګp۶*~ 8 ]QH_'QMG{6U/]vpvcѪK*-|(LLTbˍ-59q;r{CX^h h~PmI/7,A6UxC5 W4LO0TʆZ.;y`w4mX1q˙*i*_j}m.]{ g-]vx1Y4o<:^+nи`\vWp&("U^!`ďÍ˅4s"3>Y\dcs^fӌ̸/z]1 'y HxeZ%V>aAH!N  1Wn8!N CoxC9tv$pmw~ݭՖ1Q?@-[,AH)h)MILz7Y Ls^i0=څsR~:GN71|GELi8-6CMݷϛ=ocp5_| KyJ`Ke3/i|a<3sV ~ZF᳟+G~Ž[ ˲c6]9Ve§P-yWjL9sJw &fC%ll<./I wr0{٣޻E}ܯ$`I M$$n1F~mH"]j]T#]ݮ趍iXD{Q4&@(x`31 0-b2bq` IDATΜ/}'.f8^;[6{΃aS6 Y8nJjodݶ%/_yǁ*Id1yٻ*~N}EXZ,,Шa_%WԷJ>uHT PR?zϱ t~p/sKi~2chXe˲yم.6%gߚA֭͛ 4u9 Ν47Q0$ܱDF~Ֆˢy…Y t9`k[W,NM RHM|~xU_ ykg&k Nm]l'ʳ͛ ?QyHKBt1s@C͍QC3>5AUق`^jغf{ǔgШ?0G̸6{ˎ*8*/B!~Yi/‰$ sű_U.t9(5룉sސ[Zts.<DA܈ᣫ:=p e 0 ރ~>J * nR ?$@Zw{/ϽnRͤ:<6UX{^wSHGCq=dʕp!aw ^HM$3 vK2i.x #/ *4z *d"D*)-*9 $+ kC=P5 `=(—.p# HzfhIK.~<"_]ȷWUA^hQOUܲ,BY zch_<=U;Nhl&X6.cۉP(3==%=oe߰Yg jGY^ ea:d1c =fH8V0's)JnNr섘ً7+BW]ѹ *_>z@9 &*ɍjS<{O8OztMڑmEG~X3 T=~4X/Ҩ/2@W[%k:vk ^j]K3-&.Oc#+vRUۋrU@ejLw*dɆWX#o6mE~Eƣk=pJYOɢ8ݝ`劚|eI>:6[=t9ܕ'vQ(Ŭ^XlKkP_볶˯\+,PuCa̘Ll5MXT>j5kO71ؖώ? '{zxc+Q~&q9%Rʳ}%8kn) v@́xB!%+#GӑMppe 0tCiC?3L#HU}icBʼL=x?' `xh  Bu]#& iOZkK!͋ nȹ߿+(\Ж*yG3>e?m?qBs2TpQߌwukJC?,*yBT-Ҳ`qڰC2jB^l 밂 o0%ل}0_*ᡧ6z0Bvҧʕ)))9; @e[-DkzxE_U] .Zo7YvV%.] s%" 0nW|(P_p:b}EQ55zjaȼ7pvP /1d檤,v_l|%]r^::\:">c'BN?S qp.:]&II .;t?꿛K?fMdz w<,d:WU9\>PW@&LL41  !pH(8 ZP1KHܪd-PN@T.h@h$c:E5V5ĉh< ^āJB{MC8P`>D"fLA[`xO7#% ֽP.%K.\1n,:!9cBmi'̈UͲwMu*ƃt]D9@mNns`"v_Ar6ŁS,7n,H:Zͪ~Piw傥;pۇZ~39u]>'cN)T >ww!g"tBUpgnAi}zL+ñ7KOG\*6/ʚpԒ n͠q;|hx:쬷޸"edž6\@!U+UwqK_{ k?lG܉kOO6q^ZEo{5{Vm*IM(̔ PV=m/WA "dd)S|6<.üYKAqh@UYnBsX<䎑t&&g5%%^EPgPG"U~"KhQ5[Tc_c,7RV4BAmɢAt uT|`-TX5>aF>ՍGKÄF}R׸f|R¥/^8nZFG?0Ѣ2!3xaJ#Lտ`*V55!/!KpryЉI(wl¾;b÷rWvo;S(ٴ! `F9p݆"d?1hZ\h.JjkEDO;q&R$A$6PU*uHS yPCE@N?(WQi5Kltꌹap{ %+,G{Îꮄ02%Q@j}uT@ _ @㹁OݞכZ[o{IXYaJLnP?ˠ-wb6_j{k{Ue״+c&c=WWwfJ A\!fߵ6}oVK|d #~5"s>?68reDf0=*AM/p\x9m砗:3Ԝi1Hqd )Uxࢆn=}Z\R,Vbt܂-W*/@z~R A @Q @ `o4p8L3(u(D޾;:D51L7 hP +IQ`h4jd@: %ZS_4DT h@Ÿ/`]zu2C IjcԠU$cD́>ŬԲ|ej )m3툝2>(oo?94'=lMY gB?F sbs|s5V'jXh6Ǧ:< e^Ѿ,*rS=ss/k7I4?=ҜeySSr ʡ ;==}Oo礘 Cb&w=?S|$ݬ6$glkbc *z< x.YoZF ) Yn0$ly3psdm~lq7V,N1c3s_H|ڜʖR"pvo^v`0'f?X?r^(sIѠK—.+dG[Z,-N4RrK1S˗ffΙ3gΜGiP}-Ֆs$_/_h6fg:Q)PYb6䔳kb LXT>OWPdd¯/ ߹X[{vـDaV3|uf*Kg.k-ڽ9?d!h&Vx؏Yxpdpe|Fm~#.E'5F=jm*Azӳ7Y{@YF]bP.)l;aUx)LKݽ-E)ϴH#9GdsaEɢylmPS߼om᎒E)fsl✜%G9YGܧٶce^?xm 7AeBju JsL3>W4,%6Xr 1aR6ZK42J 7djrg!qe/0j/ !xDQeIA<+’pY,I1»a&컇ܨn+TElZ6` 8 -|5N\iYNٜ>'oos Te<ոsmo}ľ! &sk7I5 9˫ FW孨Z2z%O~-" $[}Z!r~W$)]Cl:ɴN &Ռ#d*:ETToo RWJ͌DFloP ah@w y4W΃Ľj8rZ *% J#.nȿ>P] T5!0atRq9q>?pS̆/Hdh8gcj =_Q*Pyv޾JHLt5qZ.UڅAvsUAS:Zp"UB?+t>f p :ʎGччZGL"UDHr?*iQңߍ%34{[{X$MYᓖ7xXd'MxkPcboFf:)Tf'&l69F?Pasb1Dt؜Gƃ9zE䘑OH. rxJL6Mlv<whͣ֙Md~_GH^Bٜꠙ2DbOV$'|5G%aw6>@t8KlX`yLlvF'MIT>hص Oxl< D~Yf Y B 'ڶ.| ?RlȾЍ+exФJ>Qk,9lv9F]LX C oF0yV[Q11_mv'TT5Nt؜P\-ҡbwv5Z%^5mx8>"-*G.Ա Zf-lҾ{HbB0PD|B-%IG']k|6axcLWEADűcozA)$=LvY'Knx &^N{qX~c[H+db@S#04ZKbMM;,2@/}GS:8p =0  k{@ N鈕Qs jiuQMJXҌ$/Z^zK>$5Ju8$THN|>4d 8'>k:R |ЅԠ5 "C k( @U aA2n*YwOp$yIV=n%^"Q|2kf'!qKL ![F^V8ocb‡,U/5ӛxCu28Лb0LinI7%Ѓ(}6;Gvs껞; _W~Q>%$wԳzbؕ-$}[NUȜS~D`K'v{(VO?I+(&+}ީ܎bJ8sG}32:qoa=5Iʬ'/ɸG ؙ gEskfk9|-|y}1y7f]_ou㺿O햓z< IDAT׺%.(xb찁ϏFAD"wX/j8V珷i`:SU&FCKׄNNE%($|((h}$*X;q2vaJ@֣.͸%bӸ.+@ 2L0B,Dv[0B"$7kQzqM#U^$:wPߋ(ig ]h$B/_0rYztL/g;}HN4P ĺp+w'xA }t .RCE0  ,#S,S *p]kUYTt 'qއ} mdv]__ALB"2%|*ݸ2LS{jHXأ;"Qʘh'MD,cwwS]wou㺿OY Wd-HˏFADpզ5\  i1FBk¦-v#;h|Nx9WQѨMl =BMd܎ y}0/G'n4@Jɜ>ɎšKo1yBDARA@ i:f[ڐ;$d.ޅR !t/򧩪` ؏H/ ӣFPP#x=EX%HG e%9%;.&%]Gu۫=o/J" " " " "qGWeYWv(qF }e$n4x|n,&=(+4 Nft'`Pġ31Ipf@7/(%_U Jqbv:wS tDiQ1 _,q;P8 8xйwsYte~7dDfQy)n1AKgO[-:p $z( L-.P? xTzzCj O?^7$Aā:Ђ ʁbVj=N<"g h]oOo7z}" " " " n\~C< .j`:&u]q8><}|A[-3+X7Z/" Xr2ȩy迆"Yمۃ.p;4:. Lp'hpnBBFECEW]C9J/x! 7\E#;E J\DT D .Ѥ%˯,|ON# " " " "\Cn 7o\~/j\i̐`0*p%N;җr,+V$%j[bvvՋE_N6 '1 Q̘xq  6pG-1͍փ΃"Nt^aPxu{xox-.\`z7O/VZt&=m*rBD.r#usE@#I.vƣFᯊ=yni!-Y E?^ RcnKHV w $ 8A N|"AB-PѦJ xd3B"Dj8@OE T p#qy[dD;;LIMW ) 5k'MHr Q3b?<-G:R?pl ׋Q)zZ W5o\r3'@|f!μ^?Qݹhi9/F%dTg1^QrfP^LIX"4rBʌ:/ob) (۲]Φnr|d}|_>S-}q4fk̜Y#/7O넣[V>,!V/-4 ZnXm?.R7(s+^>gH5[*gɲ:_=@֔-9J}"thC! Yk6oV֔rc1}y31fmi8zhW] S ՜!HssS9rppSuE^4514ad^aˮ5Mc;Of^o8f-Go våKWOvȻ{@NZlX=eK aha"P ;FWo8bӖej%(X ,~@HLp|弼wƜ8 e'L(KQl5m/[]İ0֚=K&a֦m9e=#~]㺡aFlC`C$6m)^x~]u}7ٲ_ V)>7ptwgv/wx2-x`8 9_4e4MᷱCq{,#:y%ܷ"G:(dG-Rџ+ t|CѠ k< (OqGƕVu֫(z=kAn:z;WOR opVVTX FkݎM%9)hJ֣{vl G+;Z9t@ON S{۩HMMxb%xt>gvFN낶SϽ\?zbYyEUn]mcg\U`͡uS/M)?Xu)Pfۙ;OrJ>&fb8[쨪l #rR}iLyee厕 ٭/QYYqCfMj[Y̤l&Y6̜;,!mjɟN j6b*+DcjX|-c!wmU~XhȊڀ2DvjQo*-țsc.Cb qIzk;6!躣ruUU#ŸPɉGaqo,QUP3"P ;wbBjjjjjZZZIf*v>WE0L 4C&fDAحh!a_ĐMS,':Cj_HǛw׸xN. AB#c1 LsLbVps2ɤӝ _޿=c?fmt)_ 6avϑ4\"\Lؠ.A+tC\&zQ^nVEq^Nd o Gw[R`w賣xU}8n/q^5]p$ @4t@ fb@UHhS X2`&H3(;#{E:Sߒbm,˟((íϿظ*Ok ʆwzt)Y~d}(Iz$}茟1'qSr|8qv v@~C PxKyoaQIVa }fpܹDWqw!K<5s3f[Wc*8 oGHQqNuހgw>;8 g7nܸBX\Mq2֧n7jtJ AO)pz ^}JzEBO 8X"q#۷8 4^b &Y}[M֙m=kvtdy;4*7opPQ.tӃAO7y脎@ M& >t9c0Z@9_󏯃<v4A-2:`P5Ը|pr"DIxg3 6O:h T qHXA NcYs(<x d𕗀}$h`P˖ZwʪK7 I(F6zQk ƎFh=6S(_3-e >4 y jE`Oq'!ܾtAȃ#WU+>2m۹6S:MvAVC8ڠyh #ٙeZ""(bݫbclG_8 W_)j,ӥys#aHu#vG3dXWty2 )!F{?\*t;++9X{ BEF/e[N\3}.[tl_aPxt=~d/2  {#mQthYM 50BK\xT>] ǽeCBu7@9`ZYX6p cR;P +Ce{6k.!Iض7 m5|..[tA񠜄Fz^)~胾@h prZ fP^.d')2R&q y<?# ?%QJq}[QjQy{~cƮ&kzY{ݤ|\۹v|>%:~ֽ*^\KE+`,f#A:3&tIih1Œ$KϩQP);@= :~rA@A@VPHhh@ #`t`x` 3C<̄T׼Th38FCe]qάts쮭=RDe?3K ^{kBcN< 6~VKVb7vvb'ݺʶ޻6OM+wEwnޱrђWҎ՞Vh)]Ժ1`[!Eū kmڕq5+ PilH h,ps4 ĉ-SrWFo~yQ?J4^Z9O6u˖[=uK2G8WaR.R /u[í?!2·AӉ涶VUn*8$2e;{… E؈ư!=A7zm0Xy0z7BĦC9.?xȶ+jdRnrþc'VKa;|񞸪u߯ﳭ׮a.%qPm,˦5I@Uڽnr!eX"D˯Yؼz3;๪b>&KF;֠~o $0AձԜiJ Pg^9˺߄E#/gP n$k IDAT XxƌB2.I.ICͭ$ N߲ ng6FQ_Y"2,T!>PHղLM-{O|'M$GI]@n$|{q"ALd2p!u,>84TR\ Z {lz_ɬ]h].5]h-{wMp2p5 İZ 0(Ȅa0 R@)Nt7O020uWR\:hAPhR2@g껴dwP(2;(y`}$Cμ=?gjvJڽO@͉b<򩉹\Tb -^dlPXУ,\;g|v'?f+$ }js^:V7y'? jo # ʾ*̯R(qFJ, W 2Q}_IS/TBBwI;h۠ ˆTxvO[aGB ƗՔ_V`Qο zq*T39Nl pd V7‚'Ϳu\l=@4BDdXRDM,~ qkXe1/n6JW hX-vۢ;q8 =2( x\tuՅOl&sge}GPWEy]u7KZp+ f#QȆZ&-40 GIR&2`+"'_ v$fᤷ`QUhxbFrL ,aK= ]~pF @>wXɠ=\.c%u|1QЪX8 I,'0PܹaUsssr]J"%]|1nPm.* +x\VQ (7<h&_*%iO.[N-֣;''F;𸭐7:̐?c~ hmػ v/{(X0܁j$`I99d&+g/Vamtʻ(y@)1qM 6.W9d$'>;Ooװ+yE/aկSZH'8%ONGSOejN,Vsֽ`c;7(%6dhPCI`[yl{CP"nFtGnW>l9Ȏg RgHЍ''ѥ=|6Z_]l|a)S\ 5:jOϗ*Dx(`%& SV ^б?t 6nfN dC{ΘzMߠDgŎ b}T2(yhAe˰$ H/t*zK5񥽒9LWk() ^_Zg$/Rs'L%V xwl6: c>AlBFZrdvt0Eg_ Gqtq }A rnO{؜2dUEtvtwcB{' =]xG6=),Q?qԍvs%hEo׆o~4,dYM`t7 M j=6flR7@7d 4Y䤑Da#8I`X7I>z9l?` lR:B' p0~=t@  \ -4vP!vos)Ӓcycyӣ[U~&LP?ۇ`{jˑRid̀Nⶉ_>oPdxͭUM"aq@f-[ʸ:ݷ٪=״I˖T-zl"J~} CfvWS>lsYMG-bŧ$[׎I{@i&k X6sf9KNc3ohxby AEZ=Ԧy~++H<l۸"tdoY ~9JD@7m%UfQ6-M((LLfkX1t{P>cƲ?vZfŎ{s'Gy/WKj%=z9?qodزI]3Ԩ5l &NV@P!WZ%NZ?6L}'C'PkwoYix Xڢ$zb 6.G̑%Ԝv_]r*r &奁fƳ`9/`?ǾTj\7w:㖧OI+18g%XZ?;7mU1h!Cz_ {%;>[RRR?ھ~TA"1n7τ:j |C[{v.!}C`HePzؿ(شsCQp;J9=bSÑ=C%5~AJKA.n)rN)OR> ,MI.i頳> !1 Iy#2Է:#>{-WX6.;t7QFZY`DLW񅟬ZL7핤-\ {H'GTe _U S3RVfzBk'ņpJЃ`wFOހtint-6$;tSa3w:<\rZ&^x;tď}+zÛd>_'{u45q2Nkl%/2>;#>#]V>$_@׊ڌ w7^.NmxjP#E|a(K.)hA{/EȾ2FMh<2 vpkjP ?%(-/@<Y W!p8T`?` ]8 *cp, wplB[[N Zɞ^VPo@7yW}cEg-h4Y:7fG|q5U䰚j+^/~:IjQ Fa\du4j~K2Rq`ɥ@hRaOfޟJw"{K m3+]'buFS$)3$=:WM[lj;xܐ9@䅅Vij]?{I`EEmch4y5lI;s-Pd25odJF}. }Fj2Iqf: OqYe0zy%~ X6sKSSӹt44zov$5^IJ! `SbCj˜e(D$5Vkr8L~\Oϼ/g4K6M[ljk \6lllll߽,g*mY|tή6SӾ kde65<fI -FS >/6 1bEh?BkEʍ-VkK%(~`QLPUUÂvK&jeÕhQ]Loȼ7~4'̙3'<T&86\kO MU|9OS/[>-jfxCXnU !H[ 6pC0554]lN755T-Z_^_CI4EӧO>}ԩ3)ߞ>w;#M}u3\QWdhܽLsC@ Fb iK 0Ơ1r稆= ߜW77g/anƄw=BP ..{t3 NI/dMu+40&pyAu5sxL{%iww|('Cɔ{t2'Iݞ ٢o˫m.+]XM{6wn:䘓$'Ly+RO>'i䴓v>v~N` ͨ-y~ YlQ;-wo>7vs-7oĕ+\ڝ"^kKW䨽aIVx0݉wvm8۱wr:c/"PȘEF2NO:o!9hASć&rl驹9? ᨴ\ ?MP 2pC[ؙ W"\@3uu[/EWh6%VG+[DiE^ޖ6|x,AI 62G~t`uq݌jMsl8??KOϙTRt,[]ZxZ:+TJ%Lw!g;g̝g߸~&_r[l{Eo< o `c4z˄Q) ;oX28U1hu/)`qъϘ'V( rGp C(VHh=ʕI:)򒪹EY9E~aŪ!O tȑ`RXrgڲi9?heGh3] Xc<|detR;inMS[x\gL***....ig*|dN}R6> a,DAhX7(?„ &~x4Oe,sf9EJ砼J $u\:r٤ܬ[8rJgDfeѥGyz% eK\4^}}j*M͟4o}Zkn8jNZ^kF\UFfV upx4~{W/["O.)+e(%taruA/jY3Wl:wK{4NN|nǁM V͞gN(vIO4ҺiQg2=?Y?f̜zn7GP^5aɅMQ> Kֿ6鑍zA4KTGi9wZmwJT..s&cѷ Q?l<`c$Cp󘯟;zĹ r/+Vh$t2#[L ΢+wFNTyX)sEcDG}$iEB'o%g2uu @cOЧ=\<)?ho!헴EùJ`cʏkkUMrzqw+rsrgjRnnSWWoYP5sBR>cY'ޚ_=$T`p}So,71'/BY;#?KM/\WG_0&'z%܇Y R`?E3 t0rͣfl_ C{uC).I q 4Է:sHb/]epIfOϟPFAT!@ ˰$""Een _+I#;_?k-`HUD3b$}!tt<.sV߾˔ƻmhirE?T!Jt|#`C7@r :]&d1d2̏ g.s`NpJЊZQTGG ZPlbdRnN,zRNpY3c4L"k$))IؖMb`ᴌ-!y$L2t  CWA!%ɂ>ljuC_ yy dOpWZ:BY%A>/=,=Xg| k@ xAr*tZm€#`A>hf p=qx2Oe$ LoJ!{3f lQ*:]_鰚t1ga:=>MofEB,f:=Tk\9hO (˴W6=\?pXNR!*V>C!l4 VuNc9P*k38<(S3gׇ(Ϝ IDAT9 B-$u-QJA)>xֳɡk5[<( m@"DSвB@ک"U-lҤE$lHҾ! 0y49%LA-ٗ jKi?-D#8au(G ‴ޅ1%Pa8 oQ?E;;,δ@W7+{5\ë(>B[J!^7Cha5K[!0DZn~deņjuz?D3f:f IDI\4C}3y\6gpф0܁JKY$#^ ڼJeI-'>D b7Xhq1E n23hD%Ðhe6?&xY pV\q؏A'lh4pUE,-;| !\8 :; l'ɇN&HHx'DPn/]P=FGV>A iiʋSez+o.wsW++jݏGO:3Mo=ZA?)rǺ%p%*yC"0K2*:!)d%Cx(,C?FA4ʑMRnCdҲo8 o@x;a.YBr]z'q:y:ep<&DEERY>/[Qݫ6)='Iz*ptgh`b5N-D0q4 Ё+%BKW A .3IaDTjd yZAJ*TS %ժuZ6GeIU2kvhW>>6cl***]6Ǒ$DI-u [k0S0WF>g[p9#Ҡ;=8 Np $ JDv?fMR J%j5II(n7~?n7ՊÆFF+&jx Ұz[|Gk5^"d^B׋qocc nl s[!M1 Bڠ*!eKT,[ VVN7;Z߮1=4kzvL{Xh6 \)~*/4> F[** HC}Sp2g k}u_Nv\:{4$͊s{v#GZ7/QJ8S9]B2\p$w~*D%>>>7wII.Nm;=ni/refɢ1h*Kz4Ol̊.ѥ$[W h.Ngj&{,kɺm%\-^S\Ƨ3U:Ȃ1(9 ~$T0RѡV#gW=v<l=dq*iqzeX]]J22N9pnd=n` lw| bɪNp8Ԡ N%=i)JD~;:H f֢wḪ&@ $@ $@ $@ |nq E/ j{)` ȃd6|bhkA_OWˏˍӉۍǜ֏ɒO\UfN]Z9fJAŕUG^#jgZmLԦ|~^Td~ֶN^";OYZpi|||K݉߂K]Jt8yLzP8gqH@jj.TFI!>Cv&"q[qGk "t(tB;1aA x~74`;?h@JzK8>D<"~\ڣD-0" ;8}|&–@ $@ $@ $@ $@Ϯ9YwV@ SU DHt\i5 :2c#MW]E2pqaiCFbךiMy+J)-ToZ_#|rrK2ݩ(ۆm kNyvnq2ʈZ%[ i2cCTD5* u'==tҍ>L PP^FފaFcw! 8ܨT=8ЅEY蹂M̊2Y?F#S\yg/xFmOv/x K 2!ee. t$5t ֗/ߍKB^>292чNEjj$O H H H H [avЪo_WD_hݫG Rxxe N'm~- .uZI%@ $@ $@ $@ $eע}A!dN.x|pήnrЁ Z4*Gqb""Ɏҁ̎φu[#g-(((쨺HWOn;-۝~ɨA-R SBz-T5ბ#w}TTh|$Ic(иpzFNxm#LǾa?*$[p^Eĝ'IJtzhqN34U'ݤ,4/aqOt@\;r7ZXWJ}NdjoѪ$\s)nu  |(ėZ ܚ) ^ެVe]ޓɩowe2g(o7f஻ I Nx=ed-ڸooz5eTl\t,⦆;hllՎ# $ņ|/Gqȍ#%S3h:cwT4j̍G/8rӯ7/ |anNːѻPZpi444u?p[ =ػPF?kFװ_epކ7-8,{dFd1l4J|29 R1Ը5|2'<]_7/q=)w4,ʳ}ǤJ}E6P֣<ʌMV:Npp#Xv8tt[hjiF'm•࿠~_Smhҡ& ~,Ъr2r8QOK &pp'~.t Bw]ty0-45o򆓛EF:tje( 2@ZЀ1-Mym(![G֥pW~Dww{uVyF .!A4Ԫ % :5_?ƤK@ W,GTNu-\Ƚޑ9ٟ|6(]/9qb<<9zk+->Ow!Q|e?=} _y}y7^\ؼn(Oa>eH2,n 1wTIt7;OlMwv=9yӟ߶ɼ6sg0KUc"Ye ehVI%*׭|q8t,| S2 +cTws,s˞+ x2sciۥ)oe[e;w#ZH+W4Rс n-KLnʐz!(cg#C 6ZgN'?n Bd_z^{K 0XR}pnCzAi=Pu eFbsqj47B8D㨺3(c_v+W\rڵMH潯j{uL:@1 Pb_5Р n4!M_ne%/}"O'}q3?9p|X~isO̶ ?ksEUn 5H52() #!KwHܾIܧwep@w_r-WBje<L&{1gΏǽ/CZ׷^_}o\?ͬ<5$B d8Iy?~.ڃS7O m;q:8{w8̟z ӌ,nM<]d5RhϠ[1E`3n+\uՃكِC EL7YtvȬ66ܝxqR! @ jH4H{N酔# |utys쭗'|8EDVaCN+z0CE188 OO)U*Y'tx.! y+tjGuϬrnNQwj Tom\@}P[yriaNZ]K,el*f_8j+V7 eMpQ L Iza֊&hصTwwnkT:tNټoߊ)RfmnT,J k:wO ֙n3It?q=dgc#PٸfVpѸ fC!. (jvA 7E|)K+Vo. <`腤aY!_z֢@`vP=S6l1vŬ #egjOw+VQixTji5%w/E*0UL 7׶HƊUa\ ,0i߆`fEnnPd/Jƃ[C vNٰ{WSֆvH%p[|?4K YV+ ;pж3S n*lܽp/ZiXXT >w+Kز)(@?Rizb2AT6dpR gb %ԓUg`8 yz׊)JR)n̔gvڍ NU RI e7Tї @Z,~ -u!6 elo沐ə.V - xހ @Gݡ~ K+j2ZV8kzxn5eiFo2z"=>PDG˛k62o%%T-:ԫ2G7JR狉!o-J%0I3AC 6XTłM[@7c$/C0@=K Rm1ׯ0^aG(NiC)+2_Uty( BQDwvm9v#5װG+yd<" z7/ WH50|ɜ%fnJp[1lkES a0bҕ}v9 Gî)”]a\ 7 Q$sS$ IDAT`uYA7W7q*m7XP n=l-+_Z]q6CZ#hk@4d 2IIVGgEuρ~ 4&d04QJۮ@vč@>bW6$`#]hWpj6&E'0ـ  F130; j~4׃?xܹy~sϹk_~??_j[o5ѨVި~ߟؕv듊N?pN'F]UVB2C _4W`3.~141>>4B㝦vsc(c}K?Fz1*e,n!!F Fl&,X/ 9_k'cusC|< s0 Fzź7ݤyI|\wӒ4^dr=$D`Do%B ?ۼꟷI?>=z{U+V#Bb9Q,417eRӟ_z{?d1[}K oc{)K *<ۃ{0_PWd$R77 i/hEAl4_Px%_F&D.J[twɇ᥇zv?\.J =-Y..pE2tO5̤hmgou^b{|ZѡCEk>c4.J POz{Oԕ5tq٦+;kn=*"̥5>\^F=`E1s#cw-N`a{7竊~lu Tj09XADp;M-MMM':uw5Z_ O=9TMQ_~ Ae!D};%hOutw\T}m 47[%Қ5OW?wc?tDV/`˳`w0 rdc236&Ew{.ZlX_]U[TB\SYgVYq{l~MJSBK,z1Os=QRώ]rǏ?~qIc|u*XQ8C1%Ш׮uSY0R9t|?w}V!j`VBDTě H$~917߱b2]{zo]aVh,{| Lc_x}P271??Utų}Xup%C R,`2>8aQFo884.0  z*`x ."X:1wÅ|2q.,0T!~6v׹>2^@Aθ%OJo>2:?(Ϗ3s/5)|0cKcF&\b106吿 O) )d! ukQ 227/`XJJZ!uiʆw\SaFMEJY޼c݊w[Tqง˄VPDfo|`\ȻJ*S=`ط PpkԤoZٿ9)ix=1Y9eo{Z_'K$ ]޿Njߜc$hZzeSz-~)̧sGؤ"۶Ѥѡ@➻06}5jʤI?tPqE^J1۔Gɜ=5zCE׷vD)KP,y()Ia[myV=!?y$dAJvEyc׶?̀Z~bjE.ߵ)5iƽPc|a0|X";Η'@˧@8@^ϥ*bbVWAMÂV)ILV2Ĥ$EН>tE̔lm. Z=\܇템h\T\ʚ/f &H$ydVVf-$A(nߺ2iSi Hc̗J#o 4 uQs"JYaR-Wl۱iEl%%+M,H[k>܍G=`PݹoTCίwm\bݶr:E[-r-N?!'bQ Bw~Roohެ =;-[`""z7xr"R6J%>b١C@zCoݲ%e͎\2 QH,/RHIzi5 ~",zh TlҘ+7tO4[ ȶGVOS:P-7w˖5k*~W GUSH\sBgSFڍqXdSӡk>=9_Uyw $TV*x  }C9v?tTS-g2¡Eyyyy/z) 3U q.AG9Wp>\0|U!%tMDE1fWgD"EE.-}?5Wڱw|w? a P cep7, >ZȻK0A$t'eW`D hNCAYx_2ǜ fFz석~cvuX ry]bOo> t~> ~C_]" ɴ7qoE|b=-|c{,Wʸe?`JN+ Qi{QW`eCWWM[5^՚ؔCqlAq ?cmTcP_qḍnAKړe( @FģEŨ ;Q m w}*2ҵJxy곓E/ `tEiCoW4cfe@ن؄ 2d[t |{Z+N Ge5O ;g5>8w9Pʱc(ݲ*bjsľBDF:KPoh/MdćH&{Q@YM+#+YɈ᷏g%]jղ<5V#9eP.Xy̠Wd7VT䭉ͼWH  +B?+ʛkuE$8$BẍxO',pà55r>՗A"\IKǏTMz(Uԗh (^zF4!Q1G,-/)vy6RE,]x>dg;F~WDIffNEcc{Wp '!79 eGGG&VB3e:ZjK$!a!=}w z,$id,DKk>ܝ۳zr9ۑQwV2y҂B,ﴷ['7. M ԁgE67\CE?1I̖!flmLΑj3KcR@Ρ_?"Fx7hZC_**۰jժlUN%8QP"[lpNe2!X T%z$TQFAEfZds!ceU#'xC3Tq|u8y+8D4_YP>pz"*>7z]ݴpa#<<։瞛|0 y wrǧ..3!p'rtP0X`a.c,lYlaf#3$,~/\Eoy_\^T՜*e[CNՆvu G /M9(N~U׮BL>y t;Nk3D=ؐ[91Ʀ6[ XTPmCY6.8GFť7R*[7[ZHdžӈ6-S(-$>6}[͚VkL~%(P8b?bUH4)Q@ f ΆƆ'z晪mϙJAvAn,fI嫷\>US*IQx>B[6{[)eɿSk΁"1aχ/PØ$]T][(}jCywړ#,TJ%wsc]5O/س }ǍTQKw'2F(Pit:|pիW+Wbb:q/"!I#(>&93uʍm[OKoL>r(nMLj^d>(smML>M 6aG(QOM96v䡎^\nR EFQ< Q?Lהel(T='&xగ*(?RJ%sb\AgMbEA-JRksշΟuQ" 11p`1Dlk+ C1 3_외pVr> ?Ȓb!t~9z7OH.}{wq'?Uۏ[`yZT/ }_[SbOp]ѻ<*XTth5>_,bTg>3 nEhkt*2s@"<JCn'Zz+fCYV)}&Ͷ~*< U~|yz1'>}\g0赚Ε3+͎6Klu}-'N=p1\d:(qSor6g%h8Q[ۤXxg.\50}9CpUhwl3bim5 RKW^zj8OM9%ߘNu<]ai:|B[ysg>j;;;;;5\LLXo;o~jFǵ:SKZ4vWk ),Apc`ѾTA&4!;(qu o<y;5:A oDy?G{xk<aMQ]``C]Gr /6|>Mu.&J \܇LKY%?wGS[@ډ"s{GLLvn1u~U4/o+ *5ekvn ΦN 4hZmgYkKȬ*^8i_sl. ^ n!;:OPF/H0a.HVp޴`&xivd͏&vpSM-.HJ/JMΎfzwyVgP*atE^CdE 2=G7nܸG7n}XAݳoŢ&X kW[;!TkLSK<نnӶ(+uU<3pxW~vWi{Z\RbyGoqY0DF]OO_'@ XXqSg菿PF$B455ՖZ=W(uv~-Wo:Z)KqGS38ĭf|<%#JA~JPkx'~u|áxVs|ς"/XOI..\yϽmwLVv6۷j^rʆ#SRԯ |7A!^*cvlw̖A8,HH!ﱆB̶ ~;Z%97qv;5H"' c0G@n%pc.Ǽ 8&^@,f h-ï=p;a^5WB>$a`t СSES 08A8!Њ8~a H~X")g=rӍ隲5邗nm_ ,l^tb3ȿQT_3/d2@8D;g$Ɔ-Hɮ(?ڱ5)o}谰m-cGSħ䥗6TmOy^ wZO-M ߃~ #ž*!ϟc;( @9e ZTe$[WqO-ą̽Ϳ2iDݗU$/%~A|ʢr%5Oj-HrsvJ|tl2M! k՚3щieNKx߶C!YlJ^NUsjeo30j7'rt*%F6IX,{zV!9>~ٲe˖%$|~ܻ] +VAuB116~AXȼ2ENcC@]~BtXXtbn|!0v&}kl3ȧۛ;6?N\ ^Z}*G ,lABri0]-ГYU|;b* Ζ*H\V=A (~&^y6 %4eiѱq#U.CqqQ~q4:oMllbo+OMpP q丄MCeiOgސdUM):X9LVI/fȻQQ؟OW{{> .0MنeR(:#ET P1EA K<jݻ/`ȕ:*}.+:^wQ ] Y|.mK5/xNlpd4>Yz``xv̀Y /\+`yf ?i=ˉ[=NT X@,R%\+h%G0: s N\ad**j'h^Y/sÿgz;ڵkhB<dArT i4ME=.0 &9(,"xZMjdAarU4V%M&ٴN-{%y4nЧ I8_zr?0&,,B7o-,ٷ`{Ġb1{h2k=.-̆h2! f Gvil[㎡pPumϪb>~ȌlJFÐ `4M6.w*|ΞMXS86]9?+Q b4,ŨכfSx7ltO`܌Me,g1-d[ !z](bh]`8B57v]F <\Bڊ^4+B`1ZR{p,q}a4 VFKpԦ za5 uBg9 >5" د)z5F+K_"NQW+WpbJ@?[EACC?m_K|H$vỌ=ڳ|{ju~j % P@31фqqF1CPK]Cqg@?+7.60<M %V+` ;C9>ieu]0ӿ/~l VQ fCu9,+c͌__heب$`*QVz_ڭX /F`aڸmccPLҩ:9eU#*IO'_nkF%oxgG\kt%4iȘH˽tG\f _ׅTkJ☵Oo!s}2Yx6O!u6c!bwzf%JARb|lZۯ):λiM94@1g"k0A_OԺM.Ӱ3 m]^{,6+ k8*mHBj+%NL(,Yl/8Xrd6c4eu=z٩sseǵfbAo/\5 +> NL ;:111j6 yn 0"LXBB b봳Ye9{3w!â:`ĜИâ9&@$#\O>cj, H`?\A 0( 85L0[IuuG` AV&8/JC2lI D!5"5olO k^o/m¿p F㪷CY2-hJbGz0n65^kMAW Bqm>>> ovK?Ïwh[ n#D? +A0GGFNpbYdr K+`+O? o*)?=qQQ4(g?vY"Ȥaɑ~Z_WS}||||&a/A[>&X cV"&1Co)A{#ЏcBJ8g.3aAbz;`Ðd -)Lw}VM>>>>UovK 4p<ϭR<"cYV kԅ_Qk/.+~p}~ c` )DCc)##J&M fCL`j~ь3^Ԧxɢokj=eџvܣמn:M:>hO7?6pm[Su6=xN>|YlliqQ}(vc×3&H׸WJ89\Ǘ:&}^Ԥ1xIÌԻNMh65h=f7* yӎ7Z,>\?|տ|Xgsn raBŒ 㦿jot\'O1:dAi??Y-D8ŶXC B ~`0 jع3k}&g~ڦWwF;rst}鄥i_ݝN 4LsN AS'?tfeem?,ڻP*9 W0}JJJ%1EL ԉ!.D1d4ۨi:~pwZa4ת\K5 fŧe<8\Nc;s.1 iHME8FNЩg={&'=3k1#X 'CEwpg];ᔄ[j W~s997vݳ}UBn0]Gݗ$V,0sFYY{z,0]w;|: t W/C]@,KR.;ȧ7 =K\0rq٠kXɰǐ 0ÎC0$PuNU\ b8|4p1ؾa&MN+?kSޓu-ZhѢrX4j6LPQ˨hpvJ_TmօFZ*Js=Uݱ}{N݉-tuǮOLȯPnJvYAͩCϵT(gPA;Μ(?yjݺ"KB{vx#-({ؗvÇhFp\s"N ꓽ74" z3,}-ߺP~#ɐ ̠͢kÌzo׆80(["ek.xCs(}ECWTc"{QdQWV=/#bMqYzKP虱溺zҽHcpN'к#BUWt୩ /Pv]8/N-2FSrH8S覩3īJwM0CD;ubu,wqȰ$h} 1SfU4G̠]nyekWGsC#&S/ݚ l}H^<0{ZUrlRt) ib&6xE΂\xԳmV*RZ>9{Y.8Vg4:-iy詎Sq5mg?]֎kLN4Z%/Skt) 7Uk+7> LR(2QEQ~Frl؞!190źa$PJ6ζWaZP1uW]DG 7pޏhc+c<\+ C/t9 !&dr3 ~_0:a 㾙G_,@Usܵ,mdtpmvΝ.V;vرc׮緦ƁiG [PW)#}/w=&N~aͧ?"L2~qsL!,Ʃ΄&%;evuC5J/Zʋ7[ѭ?Isd0Ubάg3_Oر뤵R =Z_AڊP[1akq#/_6CțAw(n!#պ.V"b3KuP,J2xr[َ]Jh9Ld  mnmvzױLcX/(ДeZnxݚ3xKP=c lGlRC[E)͵z 6^<qÐ1SW?Ԟښ,yTM2!lg 4#C;ufwٳw Y ٍGU\r\ʆ[W-MJݼMNw]W P޷Å>EM<\y:Ôeq2C dN*螷Fڷ[TrwPMqC+n$n{zG.W!By܊ 7Pk,7>+?p{ O.pl RϫrL&]o5CusM{c6H]z«ͮ-AG/z9'cl.NNpL򑔑@ƥ\+[ƅ>sA03$$90 $&j C _4'|a4ȩʁꝯ89p揵-d2?Βd2$ESSY ݅M=i_U{7LKmD"I\zzYQ@@]-+5vDCfS5!lù&paBR*=q|O}݉DެKQSk7dZjW9v~f0!d+HMg#΃ kH$ [Ć`(IrUAQټ|d&!1ܾJ&ɜm 7|tv;zBqM̯zl B#kuYek-Baٓy뷷 #/+aؽ~Upd G޵JE<*:pa%=6.щH"ܫ N~.yT]~b !.)LdIvGfs#qU[l۴2)7[ C\ GFK]g]UMf}v\ `9'W]p߷Q{ey}.MStW^ݾ)xl}6%iq<iP -B8NsPHpRyDt0ۯB@`:u^  y \.8 sׯ}ƞc&K܄\[DGp! P'x {!.\XmaD"<ԒՁp1E9rske:TaLܯxOkCNӝS[@S_cD/0-ޗbɮ\yբks0m=W9ⵧ fWge Kh d(1*c̟ @p ahq(aX1S8As ^tt :Nb y CK{!'7g*9Q秬767T6hO ж/kmQ]eC{wW]=;.HphyEvw4x5PsGooGU:dju^b7:Z*ISu4oL>M--MM'ZmMV( 77g+S"'{' .1[}K oȁ=Yk?:zhhӇ{`wW) O4HuEFB@lL.+=Q_lؐ`!86R (kNn{DNΰew4www4$/ C:uu^ZnS?i7uً 4ߌ^3Шt?nhnTcâh&ui5}O~UߒHO7c [)IBزI] $U`Vn{䩪nn~*eΟɣ1 /)%`i;r.[7wwڒlSKa By %ƪ:=3Jn=%҇O'W9WvCkzwϓY)-K_Ev#up4-vKҩ+׬=X_>ao/Vk4dc{)KE{k6i [A`8ꢴ/OXP0_PWdlT}]C0jG'g`[.J =-Y..pElvΨ0+iEu5]O%8,V8 ՙ2i\j;;6vɩ+Ik4G(LWLwqjCsQ1DllHkN>|>]]|ߍ!gVKKSӉmZ-ς!4|^MOtyJ ZlX_]U[TBuS IDATd U ݧ]w%HM7 2> Xb{)D"*@t"Z$C9L!'h1mzG&R/*X cw߀7{Vw\xVӣj=abkgmtJ6]{WSR5|0㺳j{w |i(#;懃l'by'꼴O [u \GcţO(ϭg.7+Vs WDq,j`G/tW/7/s=GvjF^{J$r:3mW%^77;y7Mb!x Zdq ! ~s@"ah t 1Q.1*L`kA׳ɇ/EjeyBJO 6NJ5騏tR7oMU%mW:~jKV\T(mhR|DPYXxeC;[SW)PKv nٲfS&$&+[bRR"J[TP!扚(Sy{HyS`0j*Ud>v|4򶻡 XJJZ!F@|yǺQQK7~ q? \2^ϥ*bbVWA}=0: [W&ܺ684RIs*=XT1@ѮSj9(e椤U dPVWg/жDZ{)kXay]u􀥧,6ȜsgFh Zv pcc@{[z|h8p3e:tƥnݶquԜR)``؄{+ش"6rޒ'\q'ػx'(YAn zKa7=EPmŎ<'(=տkSj{ǒh7tm+ 9Yߎ D~'PF ҳl Ggѻ字WFHW*QPF'o;4ީW>n-[R%xTU<0DXFʥ1KWn<<[ ȶGVOS:P-7w˖5k*~W GUSH\sBgz3|U"S,Ryڇ LZ'K8E?)FiwPhN5ՔgKٱ915`;bZ$ZWX<]k2TBo&竊vm BD<ԱfV1@\g/cqX/!JsDoj26nٸE0y߭-2)՛7u|"տgB jHd)+f뇟A@nmp^#{y3ѓBœ;2d6/m˖[rJG C +&(dK%+6g_h{6a"hx }(pտ6dvz0&a;; k=&:C Mq2\M3 dF7NiOyp9l/Lc/7P֖Px6d-bY#!$ ih4izhzXLX#k T~ÍpyEXǒA 59 vlfK/[bP Txv`,'(eWb,1L~?X\}(xP7z[D_5fC10O3  ˅yĬb0 b{?ì >7qՎӰ]e @y,z /:+;zbw_hS_|I3qT|3 N漢}?ЖWl3^i*io&.&9m5$-GG%M)%-z 9y a; ,Zձ@|#6¬mߖ2` >IZS. )eeu8\hfBARFuP[~HV^qKAj|GZwfyG@xYxͅ}}}}g )Kq} F)Nk d0)%CsjKk==)}P˚GCOI9E;UR"Nj#\sD(pke8j꿷9( -"3[ym`&t3) k55/|RԆ|=|«f4}<QY Ⴢ*% ;{ҵ@Yݻw SGluY>t4V+Tޙ`jI&D]ÍjPY3a'M^%բjM,Mbx* p$g[;[2;P@ڄ;-z"aFj_pPT?=h,H< F9(0]vu@|fѱ-0.P T}ߒ 'oũi5ת0x/ACw/>Q l| I~@T?Ʋ=#Ѥ^!Eg i$;xPGrB]xZXSű;%hQQ#a("LRk,2N4٧g`ڸoI;e:/| <'+O}~=Tǐ8.pkkl5Ea T"ɩž׉Ae,4:`E;"|g'%]7LbF~4<'-hGZH*W/ 76 gOguL%ͮx7^3H`}(h| X'p7pgX <+_;1FËOggӰ~|`7_(Qwͱ,CZ(kٻ{v}FR(B*' Jql%d鼮tkӀX,Β jJ١. K6T\ŷ/l6L&a2[]X}&\ O;I ٻw{}>EYLJ SV$Dc^CN6id0~;;Yb/,*J: Srؼ}=$IOʁ#jF6W?-O>^KGG-e!->.T!NK@lԟSq@.YP$lՏSgWm1bLZU( j!] 5 &9=??w~~ݒ§{r}c_{6)66D6J;Ͷn} zS-_S)Vd?WGr}l]t6 {?c /VQF!^xP%uj #1v oLh.߬T}?{t s"tck.h  Л uHŬ2PPUm$iFgXl$ Kj#&eI8gkR f9:aXۣe{}Mmj@=K@b,[+RdX,'CESEwEFl!`65f_[OʮɬI yϤ{d6Fz[sgi SHbMt3zc6}y[$@3XM9m@"xDlZ_E%Tcܿ(|E\1.zB8hw)-5bϙ- 1'xg$@1Bn MO[S/)ԊM!V&mR@}P OӴc@%_A4=WFwVP{n0xZUug'-B_O>dvL HZo[vi6t=LgYw< @[[-8}=,QLA̎Cݵn t^CӴ[>}- ݔg+uk0_G%cߍrGVfdceqO)9&z-Hހl'z]h_ۤtlMz?|Cm~' 0fub"*0WIc8xܳM{Kf C^.Ȏٱ2Jo:㻋j m2;17N\/5b -FfxZ+@ӳ]mF֌.06,`@V+-N?5x;l!AAhǘKš]?9 #y~6M[^H==wabŘiT_sqkΫ TbW. J;wDgc^!YTxl&PK{[rLXBL}M}RYc%hBsj=ŢzqbjXW)zj4KBX7"|km] _!>1402cU<$prNH}k4:` @{Jk8ϜtDasJ>K _YaTxdc$NQU) RE@[OuӧOG2ûxmOm WxlZ5fcA^{?. 3vq1s pw5`\"n{(((ق\11 v'?.I e;OH_Do{F-a30ҰLiV U?rǸ?XZ3{(ܮedbZ) )72W$9h(\`"Q VXiϹ\b8}yuz㸅km JȐ/ .K/KkT\W+/*Hn,ןz5xwU\//uh->YfTy͎MuJS7 \k${kzww|m]{3o}XZNCga.fW\~{5HxwHVӰր7k>82`Z>o~#f5njfWo&.fbb^\1Lx)! گ}wݎTO~b\AՋOYڵu@".x4[黁$ q3es3Jztc_(8'aVǍSx5ntY[~}a_ʛ8b{tIQX}JF=4M^ͅ3 .[ok`tS;"a֢lJ^G* s=\$˚OǸKk4~zmV2녥YYY#i?/#,F'S.hEݲZZߡudBn۴SCjش)6.-ܲ||~<٪V:"KM82g&V0e>iƠLN:5D}Wf}̑t jRyE#nSaK[Q9884U;DaM"pO, @5ޡ=;3w:jt$xp㑣'10zcAG2ۤf(rhr4옚dlf'ͽUT*{;V" њDUMpt~|`;~$?;{I\2D@ܞ{;E%+Aۖ \^ʤaɩㇲ;kWavxfq;~?k<';]}}~I/3.6»kXnf ȡ+-q_&$mϤTYMpͩ3ޟ^@"/ v s+-rtXYa X>9r؉S%cDc?xV+ c EQ ,sFxx'G/l.2 6+!pBԽ;&5 m;ud}l~Q˹(1Lё#GO/IoE>~OOv,\ZU%E00Z 05HG>aEfODD$#)1>bR:tm9;. nvN'6(o HT{O.I⣿>6}⦴zGv9{Hj4^)*JD<^Uy{ RC20}j  8)@gǖ7ʵf`>aY%*tLN"dEZG2/o+teDvij.nDP{۲*JFFkSē \@D*ϛ"Y۹s|>5I{syݾu 'j'HSg9Oa'86udF4xE")SH>\:|j,(Gh{.O<<"˶kA?㕵ɻ AQ21+&+l3VߞjRD({iӘ̖X~<hwD}S <6v.u,E+.˭jmrBnFt{"4x.yuֻሬ=vkBp?ȡL# f\#U2}C܇U"|i^kGm.̵Q^*n-}*|yu%crJ<mhyTޮԠ^:cw,?Y1uVVu[y<ڵNn/|₩[V eO}>ܪfkod,*s䎼9!0Vor+emL[jD.dzEKA|Ṋ$F݊Xk{mDyDMln8U#m,"&Ui*zjzkDVwVQn+4rpw+W-b440f)+c =DMe<l0m7"Ր%"--/r[f(U&zcܢA9NY6YE˂uģr"YUYQiݞK "qn=@P5$$P$ʭr%0pw2@{7ެly S[ٮ`=H ؗ*:]8q>eܘUQƌ]{cc]mO?O&UR&B 35Z^FM s?Bd&f QH7@!Qg/>Y TV~Ά ^L(%ʺGgW> F/^p^Awa>0sYj+ͪga(}J WҐB@qrr/sb[gD./,z``=5(М$o( (CvIz_hX? )Sz }'~GCiוwݵԮ ZƧZ2smKͱ36[+h_{{}5%vhowSJqߩPU\ l]2wo*B|6jQKl$^/ִ'&?OE6rMyPMMO d|7]m}NV6sLU`;s=IY/ȡ-wEz[Sq)1`.|pE+vt҂B"kXW)m[h,VupӨ}H?9=16|0k =zT({m%R\t:7s%K?>55"Unn8? NÙ@* Y],{M-B@|^W`hޟSX3655QCi"ل_]ua QL1XV(1-b (v&N!!Q&ZyKL l5 0+t⌼W] X~K]YE5p@rBieyw''Ǻf ^pNOptҼS9Q=@ݾ3q*ZeUqrB2,K:>#@rی/Ţc{TRm]D9($e ڧF̀qb¨+&nQ?%XJ0=vSVbclr-}-Js}7!N:7dc|jjG,V{l#\KdoȳmZBҨ~x`GJ & mb9fܿ]&/` -[7] tťm='Lɍo5K>Qg!Ypɿm/˪LO\׿/ml R⭩nݕ)LMr/#2~0v޹\"ko톜Rb`pvHtƿR ;w&{"%z3X\ܹ3'JU@ike%*%Hv ➀yc_'gg>/*gOTWu,WRvfgM Jfa%Pr){j9|dofZRҽЛ# Oޛ]T(E1I;x*];B+Vٙɽ(5顣 e0&`&~zC)V gR.-%$"`& HJvgJ whttlI%*1/j_ "ZH㞘;Bͧ{7{9#(82\+S3we'Pss ^u" &;G^)WXGS7 3w cSH)LYJp=vSF$BW^oPB3B)SRv9Wu/,&%9p wՈ]q< 8_&Uƽf%fм{spm瘔GNNkh,z_I[*2:{RB]_DP6w'.͹;vV[m4?,.ӘQ??u?V\ YC.3ߨCe/ 2?DwJо:dm#$u5 }l"G@Cr M-F} =g-PH EC'=5(;]pVh=kE@ci%,81 v |wKoU U }XSe=/vԻ,zH\3)% lnʝ7u|o|JScֱ>-O?K Ӂ~5MX|6G7%DiNZ=P$?CyYvo2lk =~n;g}sat}_Lɒ@7d$لnydG=`1pf^O>p)§jW8=fnXq cfafǷ' 5U Xɱ~$4i7nܒEȁy*ǁgUSѓBœY9oN޵y)_1gjgGFH{<ν6Toxl<dzQ O^Yuwml3IcZA5=WJʎ|13OIB >-g*+07TAT7e 03ϒ($2fr/SQLn<@'φnؘ6 `nI;dYh"om]j (_ugm "|0a G}C԰JS][Μ]r5unvNoзދom0xAݻK&lj)%-z 9y O@>A1񠔥ZTiC}/ze)d~ tѓR3AҬC4M۫OwdČ/yg|7녨.,T8S ɵS2s*$9|Eg&If۠j >6sv*L@Pr0);)ayr)ܖ)``@eܱhH yNZ|O%!5--v~8|&gB zF+b, k|xd?{#3PQ W"1sC-/1>q_:0jՕZ v)bG X_f "jBu, zhVC|0q]`VbIơ$W?6OZw%&_R{GaK} F)Nkd3;nv=V)M5,6[͆Άv<|" IDATY2+@}TZ h"] +I(al4Ĥ퓂z{g s즑BRi#~ō͝9: ё 3@o@cPA¶m+g?Cd2Lay:l~~(VTwU;lKRDIMXIjPVA|`G2,KU\J(<}HR VDl;{]pAcك`yr}l]t6 bcHf=Y>uo\G`uc~;;E!^XR/Ut,ii?le>&9O|h6&CwlcA-5{m"ӧOo*6) Y|Lvqs^0$}c Pimbc4M-bU4==湯RU/ T4=oqV/29wt/ @w4Mώ(DdꊉYizG1$mFiԘ^?p40;&e}ט3Yqeۤi95=.ii6izߥz{mzfxr_O +&g}Ɩ9.wMbE %>TÓa )_d_^R/g޿Dx[@v="tO1.> a/B1H|3;S,U PKc-P3KkE=5;jEXSfFKPU@a R@償^ןEW@3l_Q􌳉_cҨ׏;|9hvtE!I7-i.څ*;h++'M1&]R?`SJT=LVӁ|vbLoX4yrg7 0m^.9CS}ez4P53Y#,lq<$0TWzͪE%kByO4#LV*[g'[o)cRd8MCauqQ,qMǴs˙ӪɠΩ>! hL *\b !g?TSa JYAia?^4m7o V㜨D-E֫$|nr@PxsgrX([$Kb(4@,cv̿ dKj1"Wӑ.^ y{(((ق,M_q䤕ݥe;8Xiw )In%${vm%sHHTE看,E+RpV%'$G3ty]RQ,Vrv>dHɏKD;:|qQc@s|[6is.$o_Ro잣.t́ @Erf<=x. vBڇܖ*_p ͹<<76Yvl yE:\b{J" I1nՐE HrC̝4ch1,,#}y}VE@֏c6Q=O"Xx( Up{zk-o#N )eMSN񂠨܉. u 쎹VuE|Wz&),VsZ F\zətR6ōk5~E02U"lnXKR\b [AO=;LX.tt'm"G]znnvp̮F##7E)!#Uإ;;YBܾ ,UDߠ˻*:H(!Ct8&wNLtH؁-m*瀹w{ qalM sϷUnv@~}q"t؏4==']R#Pހܲ[jQQBVfǦ:%l:ܾy#nv 6j\11K\Xq}H A`*)?].WL|R˸wU[ Rof_y٩Ɣ ϊt8>u~.|_(<<卸˭hwQ88888888888888888888xpˁ]X*Gt͆٤slR-n1#c٘ό&`6Z!3#g"Pύ,\]pϬLutڄN]ำ, e< #ߏ6H$k6DRiu?r=dfކ2Y~[HNbi-*j0@ n}SQsvo ʚz} \MY?Y#lSxnJH+,>sŷnyZM6jh5XY[s%99HpSgmU'-*j f_VTk .ś<."mV&UTde 31gyEfFNRYYS?iچSXeɚ-.wiM2ESQQٍ&0+'o0aO'4\+*cT%Dё|[{Sy"p/ejjDP:WZYAVoKLVjqrUm 6d.A맏U2:eENJλ-0w+ o*1EijnՈ5ͲEݲ7M(eH$kj3)GpڜW\*߶α_RP}bVS[% b\S+AAEm5BU P' eCkPO Gnan¶G߰0MH5l t+̒ [#I|IB `uuyC8?z_:u M-TE@t!/2ٕmsurR2r: _ *eRMGOFW-Dp_gخ ]O7Ѹ, 8\cFgN̦^ 7&%$m96fE uU-I+V$=vlK|DDϿvנRt:@@or ÆVD_%aEnFuzd%~t ?-HOx +V+E5qxyD}} H6Y, m??WΣ8*Kغ!xa]0{8Ob _L -tsz" ״\ QZ 20`FGa<aa^[Ǜ\W,WsP'})œoi5  'EM);]e.bQgu;ղN?3`-D/7Uj@27ߚuױv{VNIf?(/م9 nݞC&jBWso]1DZdT{rOUQiH>~ =)a}㧓>uqp: RvH/ =ڻe}BX'1jf) ~hզr= g 9UaI[[NF0c7Aj-O_MSi[du^qE%ƈFyG_~@s!Q.29D]3tP A:bOv'DžA^{zWL@ezg~ ?@9ޫ-@"bIz*ElR}ӢΤ`!+RjVI0ꙅۀ ?q PW/hF"u:?DX2o|be`*^>C uzȭ% XaaQ [/ mWmE"Wg[EVn  T$ x!EL x׳Q$Lj/ r8U8ۻ[Skasl98 z&>-Wh{SS `3+j5EEBԢZCK]YR[mDc)Tig)pW&ww Wly 0MoIDFsvyVyM x!T KlN8K,ɐz9Y[I$\O 5!</DX3ԟpy&'=âhjPijngepב??oa~ԐzbNja(&$`/[ Y!!<O_cu($uwCV^Uen}RwZTfΥGؘN  ^CUݲv͐Z]Uy9X_R[$ vf4T_vT(Z+`=Q\:|bjQ=YKbV{lz!{kF ijf6AJ5 2hgBwMnFV d?{Ĉu;61N$g/FE0}GR+RA3 I/fQj볘R""v3Rle%, ֭)?IN[81RI]࠹YKyֽy]K>l2 a]_ ~Ii!(1FN܄:] 3M߭;GβPn.k/Bt[` ` ۵kg!-8a"W?c+zjN\'{k䡤opFuF Jc gOjt,?[;4RmY` L5qIr^dyC`_DOY{(m̏7:ax4^И.gLqA@|Jcr`κq6| <2\X1S''{n\O~1ᗉ)X2pps B"AaÕC82Q\5NS/&$]|[B e>Z~K|p37ŎDnԘHDOKd:e஄$P~\kK "8'z T3t)K:k :lJ06}L^sIk1;p/nkIlOCp0SO&l8cQOR@6)N~Lz삮(vzCZ/{]Zv鮕}G(?ez)Tja˻8nV>XQ`* Mo$NW%?=}Z߾1f' `Rf!e'NZ~(xfg͞{N[Dc2&rl2E8 4*{knHD"2Hnڿ94V\غb57UMb3%źv+?i-zZ> #:H`PQ׻ IDAT)DJbB*9\bSp}9 %ja$+ǓAVe+! 1b(On݋ F( Ѭ u>8k}}7{%/*{q|8)& c=ӳ N>ZMv5i2'-׼LB$~tz\6,ɴc'T/DoX 6槷I`$ݍ +q.cq͏ '$a47F@cQ H85@^\,;+=Bcjy3fTD7hya@a,5@AγG#@T2Ewq6;/* ̮PIb(ޥS="vjJ]Ov'j$yG'sI7|k|(WjyJcgaIݛY^Aj֩À񻔚߫Fi[7F(w'w@;LlsfovuIwyˣ(كc@ ;^_W&LHѕ S$ />BO=:4~}} ?*a>ߞa{cr`^'HeD:7GCfp> xwO%HoOkGFMvoxTf'.Yd0 f!/'.60EP0N!]d[MQHo :*< < o19 %7'IxusGlv &(DϬm$$ {Oql>Q}uZl{z%q&=㱋AF ( Q7hڼ3k܃!do𞥀ӠS>lfգڛ:O}_MWhK$REkou|wD6D`RyG&/H'YlQdȴgl@w}"t@$l7B̭>c9U`т ;Js-,)j 9~` {M#%F'|)řcr.Y2w1oIJgL$46U#F!#~(e}]4[cam1z}Bۏٕŏ{L t!֒{yߌF1ͼ ҵOӗC[ <.S׷wK@YW|8O|啗 \r[y%`Su eŅ }woi**mQM; ߮2KIJ06aCy ie/P@)y{MX;]r3@8.6i{1hn+y rKc&ƹfq sdʟHKb,%==|y} >q k&N|{ܼ>@iZv임Al`ҁꌌbA+|0ٻ5i ei*xn]f领}Yt$ql(̀|$ (&P,:HS bSʜJBspΰ5/5zm|AD}/# 9to%޶rynxJUTbz[ڴqbaw N1ز zȣ3gox11GXmg|Ȕg:$$@|dԝySኞHMߘ>!;9D0ZJwS>l`j_{]Pձc^qFAdZ>C!bolUI̮j"Iu%t@DNYE}77yF gܚ$6P%WwBcVoLOO߸Q 1Y>q&a4DgmYik(^(3J4qGoggrRZ`7h uuvvvvvvl؏5Of7Jv͋8g>KC7~ұ9@o}QHHfc? 5ϋA!Rn+a 7vCjb$eX oGK"WgJuVQm$Cݝ]6'%@YZ~}W6fj񯅳M^-@uzZ W6ҧW&G`{OdT^ ]d{ I.Fzq~9G^us7F;yZ`5W?) l{}"mGgt*wIF$ݳd(|!~ZsHJMOf@$7t)3嫖@ O.QG@3=>RivY{;;{^ˣdj:t Y֩@0Q:ç4S ']݆EFJEBgwI6dl/m\`cR\Pm٬Mqt$ԊnrѴN;EYˌ(c>s6ƪ}bPd//im(-~|rCV~ܥlhڜ'k!;EQOF %EH/y&Fm֖:`L LXZfEOXl=YI1` tN k@uVTx!!oZRPdTz)y5Y.fheZ`Zm$xI.3XI P`EqbR~$QYD/ӧO>}e3֣JwJn,-Pg|괥d`OH%Hiv3S(LjGftdH%Xci՛AQyfl=d4>ӣ@rn( V3/xG=lٷhO[4bឺN or[EHэY $ EqԳցq7{5hULѰd(c1qpܽ2>@a ʸ5{D*A΢{YTS犥gkQe͓L7mb͘Vr\>S> 17ݒhŸ-4r|R,*z4)g⃿$%Rsf aԼ 3,}WYS.`Ur9A6ů 5\cJJ4c.WϯRCV%|8iEo, cƇ^(%rslYp =ժD($@?\iBU}+?qf!]cuR>2;Kәiy4˧ʸn1Dm,`ՁR%i vMHw]}77ۣg\.Bq]tA$s06(@P/`(AӦ:X$~L9RŠKh4a4fO'(,/y3S>-_@18ߛT~DTn 7:WOWLeg*ng?ΌI)[198ʜ>}z6#3k9cotc<^8g//)uS?xq|ˬMIMUWP\O8xUo'HؼEQp1Gy*UaO$B#ݠ\mkO(}bN{EwUߛ,k8q- D$HJLc Ypc48C!K:x`v0UK!F=;)t}|!no:)؄VDO`7ټeHu[#X'i"" nL▅9/O-3cI!㣻s$*KӮ[|PH;t>VDchBܢبYix1l[ݤx11;^'g]rFW}!6Pݣv%}OC:?~}FdȈc+|CBRnrO v%\8mmew`_C פ)B NfB~-uډ&¿=]E" 5׷/!mm0ѫ˿+N߽z| N'3rppw-] C%ۢYu<;*+E%WZE8Jic]<_Kw֟@vsЩ wvv|>h:uh`ߦ=:Xt|قpppppppppppppppppp|uFWWsV_{B֤o@h#I[2N3pnP7/[>N{wgg}Y_[[h-w~+Wg9[5\ջv99:.}Ys˿~t]eb}o }mx=Ɫ~{2v= _N<7Mlͯ2P;+rSC Vi,wSTZFPP|O s)P_U%̭q.0zVt {lKEWƪZ'DwWe sgs>%6yhKEViTFފܖ~9I"7үf*?6{w}nVn}8EUnTqy~fO FVfUwqՐ-B_8mBuԴy=% cA⣏+SrlyvƩ(+ 8g4ƩĔķ._IzK DiVVM)j* )O|I])rsj}Ew:rs+Zf2+md6U/HWK+[:{;4Ր[muҩUVʿsdHy-ڙg67P]Ta>I \6?[RH VR7_1|khTo`L5Rdފܬ*{-a)ܢsZS"L` :#L~uLz8;krzT3$`{=Wp~A"+YA oMmŜNxV>i)vZ3wU/ZRQ6wM~CֳZupi VT e,h2TlT(KY_ﵲSn̸oťvy奧&Y֖%/Ńj'X48/ci剙925vĩ)`+W2Ww0u-CSy]TaV>t{djmaҘ'bYNE]6N{c.]t)>Q+ԟ,]tҸFF/zxVnfRyTa1U#ڲN/yY׎KGy my#ݽNjylB|rN-ֳ v#,RjEeNN[ l _}l9}ѯ+[?Νث>a"y!])ZE}][Z}pn[9N[KMu܆v̦iҘLRC(}GJfje9'SmotEaQ0^X4<=J4LȓU f9sqSΡ^wΰ F\+8[ 2CPZo87~&zi ~Qݳ; s#bcm630ŝXu}u[[~e0҃ӷ5Nv:X 7lz󑑑r?-##m6 lshi{mMUu;󑑑OF߂Uis'ȅSu~>2222w>J+ӧO>RĀ<=0"՘r  =S781 2r\í"@Pw0x7(=t _wXTbTcL)>G#D&{T"Tr *E@>Ieoӈ RgN|PD?֏4h?*_F"@i=\BW蹥/*@aWaKDÁh%~E%v#mcyf;K+S)LxzTR}1,DJ\Rnv IDATuhQZ\.`4iz@zr=F+KU"y)#EvӃE(-snT"bzA,Vr] @e`YTZ9k} #]Y-~^=>QF0hfǯXTphO8=iNUE)Q}/;XT"@doCr%c`(EHk(ngѽ̡ 0Up~K]Qi3m!ͅABDZ@UN\.w]/9as#_sN>=n*0,\`ӯn]C rVmwuٖN 6im9ԙ!:<1 8WXw˫"`zm ?r 1-D7v5^mr(E~Qui8d\@sUٛ [< )ßwbw3aޟ󼿨+k1ЈOO3j:#rXȎ/C1Q>|3p&K*~ ܘ]  'C?}&kȐY)Q5$'=]ˬw{5x(JUk"ntpZTꊷf]4pPGy#*F Pcsl]!NyDm'CBk Rل k"Ceh샫Ve Kݳ`,an _{w0½oB}i*֖RCin%7״ԗwxNTd,-+k kIw%ކ`ǚ(OyJ {=W3)oO-w/?o,zfW5ZhImtRSL&hŦ@cpTH^)򮫭nAey0qPI"aj愳 !r SDrmPBm>3Jalh&1JIX2mHӟ0[?UTo}jHj=1'0[_ZTV[_Oح'̯:[_Z]l$ڽ2:0w;?PMX_%cc"X'^VQJwEPV{w*X_R[$ vf4T_vT(Z+`=Q\oSZ* َF4zjHHnZa0&.k&@PfOTZ1QX"`nnST&(yug(۳, MMiu|r1mގ%cc0E2T;emIy*٘/̧(V5ֻrjQFUCoU( + E[ԐFk C WT =n y ϭhm̭ [ϭ/=bfN{͏Ҍ:ްtyg7!bKȑX5qgLވ`ӆ.AJǧZoo E_wȱch;~t }V)>烃{u{4~cN*kȨ>&\ 㮁Nf0 SO}q3`t% LfX KZ{0j61/?RTlxFZ@;~^px$\\~nVlUH~4_% ۵kg!-8:>a@E eehi"C!7M/.逌e#a 9'Axq},c=PVL/,%2Sealp8A:k@ƚa!*K i*uHאp9Z@<&6#a,αeu:g,-sTV/%/ٌi7ZKU=Ã6tWoM)[-o0wV2qԩE/G)Hs) "պPZhL20УO꒭\T1}˟[/_LyDrI_(0f._,̶6:TRT( ,&l b98e0 $g'FE5z{TR,-tERoiտ :' ,#Ô`+9T@bɴ]5%<8SWBf'.%ل cqf8U.&difz!`M'4>]%>jk$bD@u?Y4Oh"?צJ˓sP 0kĴVS\޻,cU]kGC9.8qξ !G@;uB|tW>i F34z_vyiMW [[LYvƁo3 ĺ}b|jTd]QΜ܌bRo3=fm ~yi;wlblc Q#L9CѬ2uTBRjZ,&vAH &8CY uk tGdR﨓Bzs߾iO2}nE-N5 # Q*sW0\||wXvk߮;aя+ezvY!n,cw`#AȒCd (lrpݴP =_Q~ ޙs:vA_EӏZ46 ޥS="vjJ]Ofɔ"Er رeƟִu^u-\RӁ&ήe澭n |fz#;@P|zF6cG!xtCRR@vO|4c)Yo ,y oۻ>&S/ Yrc~GDÛhԐօ7F@:k۵w.l]7%jrkׯ2ޘ(w`K bͱ["Nk ϫuruyJeZwTB|>](ͯ @Nνp0SO&lj)2/ػ_'Ed^1|gzG t"iwں&W&Y5V$ N"v|cwԀ6 pV`)\ cANIh n 閰Q H85@^\,;+={G 7?Kۤ;17c6M@$~CnLC!xDREdoS+!Ѽ,;I `vj_`[OIcx[K+붖y@GBd'{_LZTڗķ &\Q$ػ9#u'~5(y%>}^"{S6 |}ӫ\+x hNtVfNzauxczo.LF2)d7D[-ߝݺjO\3aNz!^cV=r_йudA?x=&F`0xM\1ޢ~_/"3,{h7+}S| Ǘ mMZG7͓`cD"ݞJ2 >p uc3==Ra֕8Y+*ۿ"h(zCrgOj)m&=I>#^, 0]25Pc~ ԀY TF>).5̋ LMZ lLuo}|3pLRTfڎړ*"V YI?Ew3OQRJTّ&su"Ӻ8< Q rQ mJFfSɊ1+~]CUzI"n#Ur c gڗt@$n`sL/y{-|yf:!#@lTg4 .K DE45pKW +ʋSouMx"ou9Ꞹw ]v|]惩C;"sRIBC>2M=ջ)|'%mY7nd |͖X|Ykˌe<(~<`iЪtqVkah&:2&6>N_uNnuyrY[蟊Q/_b39 4}f8)<^o+/ Q4Ez=%T\u iHs\ (4`_&APXH$pF.od!{yvڜn| 1cq~^߸kJPWzgo-Y&RY'EeD&FK;2IKn3w J|xSą6G-4J56ܣtS7޼>o\>[ۼٓe(o^qhD)&Rғ!ݓ@1鈻;YSts3_mg`6!v#dt ߥ:#XP 7nMZBcrY$Ln3uWya|@t7o]:*w+֧@ht! @Ǯ]msouOm~JiN`VZoiNR!i:]}f~V@*Ii0^-K_Gn4{cG]p^7x:^{tO%3}}JR}qaI%mEyaL]ž50~To`U GA",Td6fx@e4zY MJJJHJJJJH$  _4:Rw7>!4Q?msZږ-[6=B- ?کuY[ GIMɖi[lْ9{gq.PYжH8n QTRzahu@>FW(S*Z$@M/tu61z2\_3>?NjZ,) `3K&ncBÏ_#Og}qe}z1CA~:8hijRF }D:q P|5JFGCQxghЩ{ڕKu֠j#N(*g#;# v7P6PvOF8/zJ̳UM%t'6?*H epeAʑ_]6-Kk }==z/_\z?02 EavZenKm|N}tZ:kYr2K6[zS(E[*)$2p/? K/Z< 3*1r p6VLU&VF6J֧V%3с>0:g?,*t S~|=n4h{2P|Hx]2 )b( ҧթcvg1Ѓ )2EkmjpfE#H\ҧ5ҴaTl+mg</#)"Z7TWV<ic-ZC2ˉՉO5Fѫ[ (v.%hH4 4mU+ f/ Zm]!%@M*Q#M ZuWij*\ ӥ} T=@AʔMQ1 peSڻUzmOaݰT7P4=)*+j4\K۲w%H]ĜZ(j-J xJ(˪8x:/pmܝf∃/8Yey([*sf_l!N1Af3e'`cJ/ TNR=M̼yVF:R7&J)׋VAjOXic5.m b@ ̉8sISn!*ޥYfU`# Lνr^ V39|"VaKzDʭ]U_.Fnʾ!Lhg{fUu%+}:jp,q`W w43snIp3POM84pCb"@5133s.W`c&?G,>A mO`(?q15'Uپ2H4rz}R a3!tgffSiffN33̦f_*yB5On#fa\Kmw k- w]f.ͪVdUmNɥ\Gc}!+^~wᮅ>Q?XKҔXj/y'wйQ 'gfW̥fk-jlj+4395VVs\,ΉH tw~`0{^&m4n Ѕ_>3333mdv[ WՍ։ '3ާ$>Λo鍇366g+ }NYY&>Ir7[;trVِ}Gj{聖ۑvhPU+m=ܿ%@K~v^.\_"־]޺ _e:+[M>+iʑC%Q6^t>[-|iÅ&" 7F9_{=iG/\P/v3Q#a|/O H_fLR33x|ouw} hS& 9=ݶhS&`3MOPG94KL'LmlpgVw-)ƉE\i&N&5Sv04=erӥ77w`)䷩Z1S:4 粚w24=e2iNIc.`6'qr{doF/h)Dqg3c+}5 p} 'jRy l6c6'x<>?)l.V3x^hoiǻ?snId0%QONl[ܛ!a6>"bPV,+sox)VL3m's/x^aȘp?v҉/y' ?ἷqnKK5V>x˯>M?{ffw_x3&BV,+sCw|q |!{#d?vxrOrxWCPNA媙snͫo ՁQ{3/Mӿ 9Y4s9-,ͧ)R켃 `)Kyvnۚ;T움4}n5c,-On9on+Y_znhm"r} Z}|FJ 6XV_r}-[`ja`TkzhdYJwQICoeG7;tM]%nkFξ~5TldI0#Gk}bl7E@pu5xFhϧK;[dX fCoeRr{fЅ)ژ MN1ox6xW ۚ0*@ @ ?^zW @ @ @ A@ @ w+εņNهrz>h Y)]2Y$$$$՛CK~HHH,} e @ @ n-ĻK4;OhG?z =pmryLO`\9L3p1BЩjۻi]{KSSS(7NTHCSНRխ·~M-FR3ް.8f"MxW#hMF8egߝ$䱿^ Ҽ+M AOw \7}KEB|EҶ Y^wptYy] "/+/////ORE1(YYY-tթy2ەT{]"A^]4ue%J;Q^R-If5ͦkUg[ѹ{bAIVi5"^UXfUwe%y:3;J+ڕڛ4uewZ툊zKWdo۟w>RҬR9V[򪛺 kӎ츨'|G ,mKMT_b]Yj7:{ˆvm隲$M JIVV^Eo{OOЍ_#K$/;a++NofS[huY/y4^J#ԙ츸ܰumuyjʪen/ͪhwk$R[Y}lL@^a58yҖ p流OG4[-ۮ;=%6/J{* }]w1Ԕƭzk4vw1ʲ”v1.޸˲; y2=N P㚙"g]>s`o,_ICCqNmu^M \ m_~ n~uToD/B*kJkrޢfe|pT;3.ݚ z < I^vC݇Ჩ-dZnb/g[)}J_SX .#"^8ݨ \oi8 am'[ 7]ŜkTZ}Gڥq|oU;UiY̲-(|7K&/K;5܆-nݷ֖ p覆?xuzoغ8-^4=xlȹñ!=5ekHHHtrM geL,:z^)ɏ].*&&&&e!]g~=-qxw]}ekHH6e=ػHvp:g-}.T.{x]TLLlL! IPro)޶Ͷ"26dfqg =}3#"QΆ7yb ى㻷DlYW+{um/ɑi! 4UY&8r/M4baVBnㇷ$D$k(Օ1ɀgha2\ŇgwV?;VPgnXܽ{g.i*LyNޠx,Ӫu~B\޾{ޙZ=hZPd' b~qϻs(赽_1^+čfVl/ ;r߼a33|QZPWkc%in$:aYjAT06̌ݙKmhv& 4Ko;Qg!lr^V ߶96!m5|)}gw }Ի7)Z1@$/[hK@fw[O.)u?i(e8εD=k6K7EؓY*8)'{b-#N{j v:$"6mȦŜd+:>5-m#ܕ#Y~Б->2Dl%mL"6mݺ pr/f-6YUl`cՒ[ FBPwkH/C6#ov9iX p0|GXقR}^WlCn`0<ՀUwSTW2&w1=m(h5Sg9{!2Ib(04[ZG8<jY2eY&,NYvU;h"U_2YF'{tm{sAin[[r:Yے'jb]e.b LwUsx<Z*s^mUXeoE/i몶FtU.BE<>D@c {>Qx-:kչS.ˋXdu,/^^n3n(Y)Ti=H$o;UZjrn/6p@E12 b[iVN-BkJRm)X"cv_]]VK fu )0Q6ڒLm,|g]8<#[Qꈇ!}'jT!?hZ~ml 2ږxN| Z\vSꞃo^^ ewr8<OwPoE‰/-4r$ w ٶm-[~KzN^Û. `Vrx<%:5}֗2I|&UVN^!L; RqK-Н>s//ys`^s,Exܖ 0wkßHԟԧ xt |6<LC"83v#3Fmߥt\ꙶ]s:~<`%?|#?3q{ƔGN/kMiRC˿=G>y[k^@}3#[`]ۧǔJ=[sFa!Sl  ^U |2:|@걮ec_juil!/Pejts3/ȑC \Y|rnfN4U>'s8Wۤ*Cw%rz6ǔR',5Uzѹ @]XIfxu8y;H) SR Ow:k•^j˄4eiT]> dF+7׿99{lom& T8\oI,@Uȅ}^LY$r"ܿ?esac5Tx*>0.jVu֗+ӟszMR,mT k Y׋EEMF̜.˅Ѷ,7[34Quf ]BsAl< y|RZQ0.\[N`gt_S^D}r\ŏ>?aP$P1'TEEʪt}D}.*3j(a~Hw}tA\RYR\|jPb G s*~d6fdb:5~9U5822X(L|/ \[PE#ľEH) U1bEw j*AT 6QK[;AQڊNUw*L|[ Iw,U=<a<[WMP\A-.aUU {V&G4wh- ^=m\Ck _fxF񗋖wEMNbqJvAT7(KOq80 啻d-b|h(HM>\, w;d<9W@"&=P'dy1Im[*df`45nʹ%@ \#Ļ='h"@@GGSErE_X}߻ 'Gf_qG%-7os:L l.}Z}UiA)uh+(_SgƎGq{G3/(b`xWOI0/oŊͽ7rǡ]l-T ;EAnWYnooVQCU@UV<6F̘𥬏n/$@ۧ3N 9)BoġMGf;f-d>A~0oyO?sOBހaH!ө)zu%y¸t)AzCʑu}nq@QmI}xx[tt_>6nrw˚;UU"E!5'R|`޲2bU_ DDltd8d4g#8Ta [>Vzf=)7 @*3ph nINB8/0= @0Yi͙ne@t⍽ pAtRH*܍9@7ˀ?& VXT\O8 +@nѝI9ϊɣc8Uԛ@~p7ULO8=8|9,26z],6)E@k2$+i杻ӢW߷.* (ڑbŚ9̀r5wEǦ0OG6<lj *͑aak\3@\woRtBJF칸b-0i&ڽ~8Z1n/Au' d.f'DS_ڻ%!V*5Zj(7f]WE{lu{%(E;wgG gc1-P, *Վ$M"G (G`MV@5Ǭ3s/8"8T=o0e{|l`Uηmͳ%@ \Ļ+6OA~p= >ԝ!yaPܰluYv'cOC{DtuSF9Ń04vɦ>riz_b?MƦpzrdXav;_L9f.šq;,{oO+7kff&OdJseC%8.eG/k:pmɣNLŇgU*;b`|Fa{>;3rVZRv{OҎ׏5@ak@j3PiK׼?3cE IDATIf_(eraiΪe˖l_ǁ{i ;) ^ƯeZ]d6Hq-U}bJ(G_8#C"z\.WN!GFUyiX2`} X^`FdNwmnMA@^j̮#*ZW@2GqEg_>Gm*^>?)+,k1 xۖe^=j%V/LZrJa:A]+ٳ_V<|tB@EPhlNOwe7Zŕ׶{jM?:w4*RTƼu+$(4,< Ys63PSL\6YA y/MY7[3(8 g&/h,4~EDW_+&xNV򟯰g֊b>_Ogchq(#N۵1HWP?a>n` Tao֎Ʌ 4$' ʷk8FucbAM̌$a|S)YzE~|@t߷|;cI:a@q{la/ j Cc+_YyeTnk,4Ľ9q+ہ`NۋəYq?qN wY=`UUGCRT*jB (Q=WE 1.pFh0jfBL( |$a]XUTGW{x9NiղeeȬ/ټZ1UX!Siڠ*ZN _`V'> oRFn.T$[~^cP,*i{ҘeuJNҥ3L?q7>NjZJoXRI[@^Sgmp8)2OXJoZLP JPFg9&ft?ȑ+ 6RC] "n呔\@SP zKMV1^A&g:q J~ē@@ ggァd&Wz(zi @45)u{hQl\T<c0_} %NY#ƿlfZҧ3Fzz^6VFA \(nQڠl_<ڭso= \wS(EnG+::ni\b٫ţ0 ȩC! O gC|_*Տ|9!nﺳ  F.CŇ ,W(MKV QؽRdn0nfrqoy6S dL;f,,JvȘ%)^y<[@ u)|9tR~)amtMq#2ȏ{r_{^?=rv4ڴ"%!=:]MTz6Ƅob^CFÒ;> Ҿ;:aMA]d$b=%AM u> dVivZv-NEԺ8TeJe+Ye4ԯ ZNm=;ح9qivBj|UfgS6G PD,D1wE)v䴊ͨ~Aܐ U{q>Όŵm-~F8ڍӣثk@>lsEmmצByC%Ni}$- L w1Z[lWtog$[9y` >#BX(<Ƨ K~gmL'氷J]˚-'.4 JZ Ɖ$j  k8I+(sRipi!+eioyaJZ)YU_YA{I8G,l`C5ET9閚c݈65O@XV nUP^_%*(N\ x**#T"agKoirqraP$nTFJ;ƥ殭8$}' (C |ʍX|fA\z*@\X^SxjxBtxF|A%mǸnzwT2}:ce"\ T|zs=xtf&'fYE噅eOZܣBvaF@I}e]5kNfZ5$s E֐ף[g1WVzӾ#y k=gGkw끞mcCCi>2Al4 6 e 2l>nJZ<l,>D>ۖ}o>]|~Y 99V l4\`Lv̴a)04 =A348vz{)?Tf81^`0uěeL%-̘\FwS =1^ ߪ.`𵭓0FtY.f81"Csi^6N4؊S^S&FؖhĢ.̴qmmk66N%ANP̺ު^'Mĥc4N$M-"ֹ f8ah4zҘ,Ɖ9ch)gG&oL1F /&0>%xw^yl @ 8md.[?7^oV'*7#^H=3Bį~'ٴwn_ no f{!mS>c5vxO"ۓhpol;MNV=g> 4Ϛvnaaaa7zZB9sV{8! +À >b^ r=L\f'/>6^Xi^\6=۩kzz 2r,h:V/{v6s"^$n?8ء.%%5.ߡ, pL$,Ko(> N|ߦkSQ}ܭ _zN ̅ _F3\~X0^`vzjz}XXܪ՛^j}(9w=G5Zp}߼WV ae˜MoށJoGg:|@ m9&15~X@&&x+L{S=N1sy]x5~:̘  [|33&.lN'@ @ d&|]5<ExqbWp?@|@ @ +ȹ@ @ w@ @ @ S+պxxJ%1R|¨S+:oUO[Z\f6goMޠY^wJy7. wEEl@ xW o(:YEjG?k=yY2-e QqQ :u{K$UU7YB+dNYϱ LMtk0}'5dOmZvE{E^V 3{̄Oֵע;]{U'vۼHuZ%̪TO;^Jf$pUJ|SI;8jv 7IAlLu斣m)PeiVVi٠deU\S[CS^Vם7.(nm/EMOĤuz *ʎGenZ-!AY:K[z(ڿ"z 3[n&yzFT^U!^UѮkNɪM*QfYNwcweeձKSMKeiO!t4lE%v4&@7?$ʭ}0aLo?d'ȟw "VEd)Gq2ib`[yxYgE侺Uqeұڶ:yS ς1SM-J?]z+mW3͍5# O˗/_|Ŋ|bbx(kE`Km2\%AM[mnCqʎd|oU;UiY̲6|qm3rJ~)P%mh0z jYox5C[Fׁ0?U)Jkr޲_?\#6􎘮C] Ѿ߹|eҾ9wpo[Wk8!0s QE'՝inLOUJ2 5Bz||&6-aΝl4SRiY8. `FNK^\^컜ss 3CGڥ_;y!_I3G 533;"oo.i1|y' 9Kծ؝iPaIĐJ ʗsh>=94lha.**֬ټVms yuc#w՘&5؂Rr2N,l{|c/ffvύ w޽{{ߑ-n)+C|x_WUogn F$/7(QͪEvC;+w=:s[;c7E+yMqkzs>C*S l V k'iξ.3+!N!^u׎77޾{ޙZ=Ț2 \@7g_rv2+FI5. Ze%J"nip8: MU F,iKՖc Y]7MV \q7c% ߚ [;[ĵMilں<^:۶*Y^DXף#UjIҶkZh@aÑh1D(4k˴u<-px0KZc{S%M;*MڶAuBNVU.-xI [B]d7uU[ʭT6 <הjEVu;cUB>:x7+J:,I7I @#jfTYj&o%aLW5ٷH|u/˸6{u]Sj]UF'{tm{7x`z ?>moZ"{0sOEjA @eq8t=mG4 غ$k.Ke9. o@Kx@E쩷2 b9nK at20"9w&֤isE/CIuޒ%:==ڔxNVftx`f(PXgp4־ O)Jjknmu7ym$^ҤUp8au,<Gr hEuSRKFg-%ausmlP[x=iz>5p3j[$<& [B颋psąY,kpע}|a\7);7ht5,h3T2yڽyP\ӠY^I餮:Ւ>'*"Ԭxϟ@5-*!De)̩`l]jkQwPiz [P_1Nc15suJ{7]%#Ks!I|R%`u]Vb'e|l"( ml;zx\X&\Ѷ,~ˆ Q? )FȰ{pG<Ѐڍ+~U vF `݅EMNb*1U k ח#,^鮏.K1QupxD= $UrE}e2{bij.j|#/8EBb E0'{,S)=>yw!kR hSqE \w0]ɈJ|[Zِ!gerDsrF=1TsD^T]DoFFN4JR:-CUz*,,8%5'O_nU * _g) Rr~DSCq"M;̇YR\|jPbќ5_\dk7"ʞK̹Aم3 ꛊָ"[ $'{O(up!ai pH_}w7j(40{~|2'cS,VvibOb:G. wwGfx"(NXu*Džœln=o0s*~d6fdb۹eŏoiN IDATg0zYL_));UyeQReBA@c*xm5TcCUi;,}_mg Q]:EEMN\'͝'3qF'[T:42T!`Ϋߊe?:hꫩmo+wNq& C ՉL'bunb྿*[U&Re + hߛ8%lQ}&ԹUktEӱR} WH;?fWyi?{}5Tګ.i/tBKz3n*fRׯUʅnVm֡ZNUڥ:[-:tu@ʕ2-ZJ$$' I8Xiz>x!|>s>9W@_G^|MO:_Ei]4^v.mq=뢵ɻKf}g }.kl}j8h*Z3~i;'mCu*61~5٢NČޱ6 XtHЭWH#>h](׿X7=ϕXaO!g/4cZ"Vbt;m_fEj;qL7ںUr%i<8gHW{jmYK[Gv f5cDn[qc|F EIE/9sBU^$W4<Ə:og,$K.G}SE 7㞇F8ɻwO:~|&)jtb|l|"Tcƺuݩ؜wKE<|0 R2(*X%gV|9ϼz oHƪ3f?8(qgcpҦ'%/L%Iǒգ X{ƒyOrw2I]$i{g.nX*iqP oH'<|̸Jk#ߺ$MhŤT@9}&&j,6_LC3~ԤyIc>zrz_EooS!//x4_2{UG/n7Rsk tMrsTgib&n@̑Aa㦯ٗz(sX^]ۀ|n@!Rǎ`6yqIK^=<_~s҈ +󰘤ю Kn(yt$c? P{q k UY+09CxݟXT#I&¦]ϯOϋ{xv4uo;/7c+o/]U\. CO>;O`DF zUL@o,*7R&4m=o㡔z5Q7,0zGuedv>[J W\uy`۞LS5Kv@R/}njhx"1`}״G/TTm]z)lc|ő]~iԱcc/I,Z']y`}J汅n$714qhQ_oM6GPJ2 QSfEM > LZ~ k4D52YЄ׾ӴscJ J~ǣ~>D-*w"HNv `S7nBV yLW!(x|.?1{Vk9L0ņN(b:mUs rME̞e9?系vd%&:t@ =DM! 1$>xim7u/)~`7a܄W!j ;L@򣱞%{Lpy p3%5=l8vT[z\0q[Tk-HܶR|c~7*?kC$?WpK jGBT.L6,lϟs2U|,GA$2YPXجnPcʕ yOlz׍ޤ0B~T{hgX>7Me+6ͮ*ԟX6!Np *Pf㛁z+aۻo Ho>.$/ӌ^w.= ;nYۉ77Gu?]痵L?F;c@S?29<$>1k|hr>z]N$>zU0_-BbsC)5`-˦˂†=Ì* HehmϺ+=ZyƳb|}WYПƔ"$Rҩ({R:bu :ׁ/Ph~( [4ÎOV=a6!1]ٗ-M۽C=\ +Z!GAI^+$s򕧟[snJLgU!]0Dm*6QYG'#kCdE[zZCGߩVsn*;cIښ^[]UGǬ ^pcz6Oȝ㢀W ~GֶwH@%߷h ˣ VH,(9ktۍY u,tnfrw!*_}λS+? Eiҵp}_rg>Y+~:˟%U*?~; vv3w7j&A<+㥳 ֫V>9&}*,]`_g ;J>{Y^j NZ43f1 oOc iNT\ ÃݏtW> ў*豓? .ڕWb0yg߱9#D*WQk%Wm󂾼dppE _v"c8qM+]響7/|X2S8cp͗.\pF4^t҅zqyy[}yzfk.~eB=]9$bSÖR~djTS^Bfz `Ն]U&S}Uj[}]ZEtr&W]2kNT՛̶d[ckʏ_|}&:m(ڵvax\rPXXf8'355gΜ9sL%l^(xv-] "Q*kOq\S "ѣh`iZ-/-ךܫ0_8}@˵}pgL&`:gOqͥ1GҕH&sVitر f>YHٮgΜ).>sl3_(/xu9Jq捍w=q9:5NZ`V3;q(OΈ}LU%sv@xsm8%1'QUmp GY*j]F }W%Rֵ#\/8\So {ʃ+RVmPHռ9nM;0Mw;aшO>9sF?S tL!5~MiG>kY:PTXMZ(tϤm?奪 zpjOR@5]/ FKsc,-Bߪ!rE6O\0J[=GklٳdV>{3_>ς>6!N%yF+R:P}"jˮ '/1yW}RA."tBS~[%?olRQb L5wOq|`"d-^Qnuo8a d%0Z׿^cznϱ*\/>Wb-rvգ;2?"oF">>@*w$mݶkH*B[Y25Aؑme Q9&݉n mw~Oչn6 XO;j-lsֲ<2H>tMW>Z{|[ we}rSrWκ7 R;``Go69!ޛv}Έnms(sdR-WZUӻdʒmeMΎ¹mCnbGU u6ErֶMSԽptolڑ+Y= Vf92$orx>]-s{r{.$vY&H,muyN y[t=#T^_>V;-+Mr_:%O_ \=$wd 1YۨC[e׾V8N۶2pmHǾ}Hlʭ|ei@UHFnǩc9wp9;6%5&DaAGίT yO;+΂_kϹm,hkyF%*t{Wփs/j@C)HþGtj(g54*LDWvݿbzMMJy%|]ѿTߍ}ңSH}[8Ap6gor/-h:EcGH]ڤt TMR̂ Kʵ|Nq)1kUvK]g&p~-Uwn: >Ϭn׺~ _%k8+1ZXJUDm566 'O?.ofS2LڏmͭAA! 6Ղa V+16SV (4|txͭVqYnfQdClj+;pmh;4릋†}d-zVo>^fKPHBzEWm[򑺹5pXx#c_57zljXCg}%357wzrsoE>v՞8l @l2Y:mb8=]GG8A+c?}FJ7vZa,:.='_Jx`Z6n[z1ҷ.7yC =[t{05:JssnϖDO}p_1CGsVm WJm˂Vlk|5_= S=DHT{ {eGO+ gu…y=o~,ރߧd`/EM fnn'gԭzNm5Aru_$3KrzDQ{n*t%FDP1cF;ou;v]]/Fj//{=B_[a(3-BPs~9^ry%:s&J>?fPmݔ홯|H҆;'T{'yxd'=ڿr豒KѥI[Px嶡¡[❥?"M[fx%)kZt(=mەsM`Ḫnj=zW/.Uy%T*lӭ'{ƽu|"|溪jzɿƹ*^d^+;-VAݛ<+?mܴ~]Iw> ~[72]g~П?T~>}QԾSUO8~[](B}?46rMbj~_d mlȭc{@1EGJgL5pm^W@G'ύ^4v:ikƛ<ǫS}8=o<uoeG%}omнdLr}JzT,w+X>}콕/h^w?.^=}@ sjt 77g={w:MXqГ//gY\V?&6Mġ/js TdP'#`6#S^7`*}̗ E>6oxh37I06\5=4_v^k ^6 Hn7ql OL7`+`g)>ٍ%ϋb8|U_7k^R_Rձ?}W[rOhcDŽ?>wܟ 7[ձ³MW~Vrh4(IPlf۸e䡣o5""/~]et;^݄uײk[)FMƽ4W\9.C.Rx'ո\hZ2zڌ1eW9QմoT6a5\[q>->-17UbbѸ$Nbth35F1(<^x@gCBv\E*JR-zY.ҨTr,Oӈ 4ӟ_7TLS9dpb,yv#XNgTpZJT" 3-誄 ><\~T]=-S}&$e7!ks #gˎV:j:W&+K~JyNۿ1ok$sOsĐ-}S Z?Mί竫wn,.s4;Ş⊊c{m\u@/69[L),;uʰ]YQwzS##zwM.m^9%Mҥ颗啾tP z/l){?~V""""""""atU|Ə^>2^.?ݧ`G`H]] 8&D(8Ck\,R$.N?r>C&:ґ6wulj@ʱyPlɞEe[j<Fhkz;[suJx<aճrD|J^#&J~>nB1 dC}cJƥrMH$GYt;?O~9-y~//,~p%_s_ӶYKMl\GN iS>ud 8\PiL[B+h,+[wZ,6yBVł"ZTa3 ]rC ʈQpK`!DtkSKb\i'O0]""""""""vС3fS AT*{IP|/k#WDD+GEtWdT*Q0JDDDDDDDDDDDDDDDDDDD*Q0JDDDDDDDDDDDDDDDDDDD*Q0JDDDDDDDDDDDDDDDDDDD*Q0JDDDDDDDDDDDDDDDDDDD*Q0JDDDDDDDDD ]R4/$>;JR[_Q4jSyZ 4*&eKDDDDDDDDDw}7s?~g e?.~prme3B'&2aLlpP^2eAx'v;٪p:o =,׈c4Oj!@sR"""""""" O2*Hn4JYAHj Hj{隮N.Vh?}p+ K]H"QUFtߩn{#<#h ޙ@f^-'hޣh ڙ6k0cK&{u#U*0NT*|[ >ؿ2(@OO ːk& 7ZZeDwlϸ~]X򺮅j]lδtV @qkmY9Y' hΞ%yC+4@!x-W""""""""ƹşug%!jKq͍57w>0oW4^oXaN/=)e{|E 96I=tQ^1Sz-fXۏ]{/ܖc {Bg67W|mO_XJ'-⚦k]]'O 3ZSi8[ܒr ~8xsqӱNlSccЗ2{N( cJqK^~#j׻=>M6l6H8ay"kηȃkZ~e/_Dzhqz-Ͱ|\Ͻ^ӑvr#zy:ojkDDDDDDDD۲2RRma~2 @c @[Ξ<'10$6 w^4QUZ(LIΛXTо|cI#QgP}&hvJ.罖7b h ^kU+ږt׳'C `?igj vqٷ2wdG^ qc"bnt7Q>7zxl0'I"NCVv/PxWY|O5Ee![v JMhUNLv2Ȼբoe(@INNeP{ _U=՞ٹl)g$!匤UwLsh+=E"hq+M@ye""""""""Nj#";w,Ua ,M3BHoMlq+Z #tKse'ʁA#5Ya|a`⃒<mcDKl϶}iwV AWn [kvh!m p,}D9Ҵo,mz{kqttjPt]lt[P_ &.^ߪ`#xu#a J2뱳PSʚ+sGrS%@>lG^ShdB<&>d-v9FBva/Hj{-ҲT[Z d1*ncFWX}! (H G(kw uAW ;ަ Ri[.]"t`Q`Y]uPM9 hMG{XEيЮj_#2td- T(\a[R斺2A5qk"?{> 40cTW t >vmetWz-[C]ՊwhB7۝D'O0ȋԡCf̘k[ S9PY ̤ Ʀ[+} Xt&<#4ŧW 8m.}+ 享Dغl} ѭ1`sWiKZ(CȀ-KDDDDDDDDYT*&4*J}>MRx'WU*UZTh$ -$W#"l -w/m8;F@{xrC; A/l6fMFXvYVn&VKDDDDDDDD23ˮI+bjpo`fL37%""""""""ctu)&NuEQm?'W\zU'(Ap(r vfm7#""""""""")IIZ(>~}<#x9C sKI-Uv$Ǻ*7w=j5ۛ;m*p+xpâˎ*J7*LsǨT{&?NDDDDDDDDD"FWoRɫ/*@xrr,N@ʼX3@MѪgj&egF@ՊK/8\gpbfy$Pzj1xM6 T^`u{'""""""""""w`?C͜{>.pHx '@+yŧlW@쩔o4WSG ƥ[GvO{y7_[q\l.n<>XDDDDDDDDDDFWoKs 鯇4YSeFM;0ym##ťQ#b=!;g%@^<_Z 䑺M۷zpLBމ  Ӌ sj%O@7n9۩,ooo!C2%(vuwl ϯg@!3vBZ[МĄ%K({'"""""""""HH#:K>=U@8:B@q5F_O%fGX.I6?3[~M6'Qz%9og0/{mDEODDDDDDDDD[箘| $b悄cɁv@w@06p#cjPPaJNp,_*圕k8SO?>v~0,]?ޓ=׏;zI|} Iy@>H1ƭ(~8q+]]G  !~RHv"͢,#q \^3ʩމH-#kfKʌ)]?S:_#/'0df4oj6 uM36UAl1j=e*`Pb,n+-wfu >흈0z+qLydJ')G㧹ѩ5&.6.{y{htiIFH[<O?Uҷܞ/̵.Б*cT}]"#N^MƄx|]$̊z[>yҍ)j%z^o#gV\ !%M 5' Wk1sc@+h0D(xHqdw"""""""""r'klle o-u R!@)K]fS3j Qw"""""""""DDDDDDDDDD*Q0JDDDDDDDDDDDDDDDDDDD*Q0JDDDDDDDDDֳ IDATDDDDDDDDDD*Q0JDDDDDDDDDDDDDDDDDDD*QowHڛo9'?܈fO=\~f޽{@CDDDDDDDDD]m6Ad2 \/^'_tvvޞ}1:xҀHH#XvV`""""""""FW/\.UoF``0:xQLv͆Wk'd2 @Ld\bƒ~! ǛDDDDDDDDD4]f׌MvA2 Xf:T;D+JDDDDDDDDD+ @d&n Z&ꔉ+qv[uWo~m"""""""""%Adg,oʯ]AꌮΠثY""""""""o/FW/L暻*v{{FL_=W/4R!v:;] 0JDDDDDDDDt0:xO8S@H`]#+?^yFspP)6享DDDDDDDDD2S@B'A(d/t DDDDDDDDDkL&wv4]int=ՊaV""""""""[K):\w- Lv #KDDDDDDDDD7N @*?tbawO]Tj~3t+)2ٳsijn?{冀:y'dv @DDDDDDDDt1:u )կKTm]v+9w'NշA\wյ9 ]+6*2W|S_y ?|@pJDDDDDDDDD^2L.6Gb|,&2btUJ֡Cnw] BOJ~ 7dȐ@\@Nwttv8;ۻsoϺ璄$$kP@P(ڪ T[omCڛokO{zVj[ѶJK_ ("p[&s{fE'3Yk=隵91)"w`}L TNL 3E0y޽{wng)ߚ}}?Lvgx1wSԼz:7C[QQ}1W5Mngi1_VZOOiRDB<տ?9i="dj=_Z)97D"H$JKK{e_› v1tQk\l.1"DHgLrbĘRZךHbZUkƑGyGyGyGy{4㚈"OQSL EvRkD"!mC5huUv CP$X,XWZ5s7W5ϟ+L6spX, p8H$8h8o)CVKyB0"4qF%iH0 }vӃm۹Zա%;/}K =ho*IcYYje+aavAH))" N5)"ck*5IGyGyGyG[ \*|J$d,h#//qjncj;RB4uȐ!zo=H$ҡl05sg>3nܸP(ԡK/]5\Q]0{D;묳6mڴvϺD#"i%I #I2"NR瓣)#Wc#<#<#<#<=ל+I)|o;fȠ2;ǦjLRdwC-ǎ|_P7fYJ屦J9BιY|n֜N<]tޏI g`UaI/OkiI#NY/s4%}"FJQ%)哲#<#<#<#<=OxDDZM;"{K-T 0`@(""fbJ)J)u]׍bBÇlIQefjnK6}9S͡ pҘw3 5kY_h24z$ +**+?5hР"!OϹU/#8SYYY\\ܡzvKPlICEuI&x\yi1,..޷o_KKK*nbI3wyyy2ُlSkmJDTZZ[ZM&Uf׮]v:LmC3)H$>d`MMM'{ ?u.O,TWUc*@uX p,P]8 TWUc*@uXȓ=RJZ;"bwkY*p8mܸwݺuk$QJu(e RcvcY*((4hШQF!e>M*pk^ھd5J#?L)vƌsV󬩩|h7lذ`p8.0kq1^?_Y5r1V !>쉨@|_hիoWVVO)ƬUJڵ뮻?~|uua*tq\T2^og5']#MDL5յr[{Џ43V}{ JoatUH)hѢ7Ι3sU'8.i}߿gN:[yfBW&J7kcW}}~DTRu;U>O2tWjO 򭳻\/,nhq~wWX1mڴ}rεJ%oG;!"s̾ZkE߾j^^^YYYꞸ [Y=.+tNIy-YAu甕%"+kt hy|s4y{/VVNTԹ|te_/⋟KJJLA}՞_ѵcg?J]>^[m`\x_(<Ϟ+bʑZ?W^b'*y+3W;__sޅ9+}'Db=3c]tʧO)E)_ZN)u)z]pŗY|o}ӞR:|eҮtJL&X}?}lDb;e (jkk @DTsQ״NjlYh٨~C~\Kv?.;[~ +V<~3gl7~=st.Ek)8fv_J)ucݻw]]]\\w!g?7 ^xWw$Zibɻ~ԓ};^UTCK*k\U9KG5ĵ]ŕ ?)WWx=v_֞fbO)W7 ,9,|y;LJoL& ̷J)۶Zks}cR.Fjr仮ŕ fK.sꟿJտ ؈\n1K>(U__Z|&\n,5m\r>}U0fLEw~wo|wBQф}{4fI4E}ƘBJ٧Oh4OU%ڈhuQEJX,gLXew'LX,VwފEw/̮'67^eJ3WQkBmkI7&R[[;ZS7.^p{X]ݩoku]+Ӣe7O8ͻ;ܛ'<Ǘn;:jXWX<殄hu54%77%MMܸK1)Wi pH2իYgLc~18g\{.M9 x .u9ѦO}oM-.YiΫ8|n,{%D58{+yf 8K3YjTuJ+=tӈO$ ofrƬ}3⁚kN]sƣV>Y߉_̂_ Wv!<֓qƙ9AK)p2<W!+d w-߹88s敏tu?/laR!) !ceEJ-DU%yB邲O'"ι'K)_P3EuBQ5ϴl>DB䚘 "LݖOcaHiRZ+fҜ!*BTt]4Ƙmd2wդ쳙zn(oN~y}K 7ѲO?k?]6Bzъ[~}߫}Lu/k"D˾VmB:KӔU(|IDAT @i߿5]Z !ӫ/qWw!BU=_џv$ iV\{D!ֿ wyqqk}vlY0<ϋmpvj_K$o^USCDF39*ɋL\^Ap֗joOZUϚػzjR@,FnKa[<`Iۖ-m;pla[1_R:%*zeiK2uϒo1]}G@m?k7Mz8L'|Es?yK+6n2ոmƭR G "Rږt%8%m)pla±cɀ-Kؒ;`Wj fp8mmm{S>;s̨#r5w|}r'_%"~~":j:?s?bU@"R Ή .eV[Qߜ}_qUcʉ(ҟLuyU3,w6yn0_x^G"Ex̕2qYk5!G:3D"iӦWYޯc5١jvrHm-" N ;_z`oOEFD( l&]پvXiҚ|}RIo斷;EX !O?ŋ_uU@@!݊kvŝWmJKC]>jЧv[Q.\XYYY^^-J*} 8zŷn㙔eY壆ݰhzA8`r*헮f]M-`(1>t+~%ּ̡eee۷ov7cYiӦQFah;fHY`dF?N4Es{*|c/U5:r6=0:I[i޻i3pKr""K%ɶȱm1bcGw昁<—dŤ$K2K2)ȒLJfs_CE(z9甕e۷o_x3tL&o߾lٲ_=zw_,>}2e駟nQsJ]Ym۶_}czpaZ1ӵ[ 2+g.zASG޶m??k֬P(*tNR{7֯_8رc;!vFXdr+W\zu25jԄ \UIݻ׮][WW =`w=iJ޻y͛7Gݿ.KZinȥν?i[Α蔽[ܾ}-bieYs).\8tP!;5ͳ~Nu_ <4떙e2=Q}fh0ua'U=ܲ@q6]ؒ\4,IEo2m>vS+.xhW{ՃZNDM8f͝YFM< DD#.(հ^p֜qe"o潸6} _1iXRņx&\6kRUY\Vιc{92RA/3(z{z2ykkkQQ8&L&wY^^^PP`z#l'߻iiK1aoNN9mti-*S)϶eoB`V&Me]C6DD풇^<}dy}(5:݂C?ioZܼi);ͫ}yKG\>ULDD޶~I3g"RPϜ60}{SfLϧTm9{Z߮`o'gVv2& +8   !#$%&'()*+,-Root Entry@zz256_983b62bebb424c22*;256_cf2e5ffa83c9aeac*"mM*y0;I'-JFIFC  !"$"$C" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?\ s\D"9n1 3Iu7^m*񖍊?/fH?wn)W^[MH ȅ˞scsϭp~֮5 SV&ߔ{Hp_Qx U*6ؕImJ{;p%%yHwhjr\.pߴ>ٯ/m3[C]nʴ8w>@pwS{cc =\] BL]=;[&;\^ڭǟD<#pnB37m^|`퍅eGh_KgAbrA :kiqK1>?5ws̥EBa"Lce,d0s=*bRnMXyqd96a8?`Cpjmk>] ׋Y$ub7vEAF9u1ݾ _j} y`}e]@#FFk" 4og' p0xs3gV^-"JA{4[yo^Jk]@ͼ6W$1 C$7WR?9ZF$n!H;}"X5+ɼK)9c8ۏlH눇JtG nw {~%xa2[pn2˃ƒ-E*NF (jv8qsP]z]"jVb ͌{VZxsA[ƛ (rI!m#A#n!5W6Izd:[{XH$IH䀾V]~:71+mqN:Y0Z[ h툅d3ǵ3g*A]ę,!4i3#6[s8U {{q ^HX=s colT yo䴍B;)p$9+n$im[r( H@xy<}8!|17ەޛ>m7T]FKz21@nǐ8 ? j/D[G|KoH:qb%wIu/Hԓ}Y!ӚW%)%'dDҹo{orE0^[R?Lіie"C>Js|؟sֺ 1QwANNQNJMZ ^[$HŎ0('`Xo5xQt.K|lM[BI#"+ 8N){#ծh'imvLI'2΄Yd=xVc`nKojC Jp*Ib9w9'% <ʫ{{2YӮ|3ޤw7??!@otĖsۂ(Jbs{WIdgK8A,\ lr: iGcDqnFdV6kY)y , * t γzݤl=9qggZ(*G@}*ilG}wK7N;dW_M.hoE~&>Blmqg?!@piƛX~+B¾ >!ZK$[(UcRF:=*>)( _0A^E}n+yqlTwrU,L>"6R)|Yr2H|IsABrtIܦi+'e[ۦ ajiqcp3@nt%Bjq`IB$A<Ж㟴Ipm,ݴؒA%  g?Ҁ>FkKiI|Q7V>FkKiI|Q7V>R{P`cV9ǥ:Oh;j{V ˸.}Ӛz>9WY]10f=IjF<~˵Be8:\dd?#Ҁ>t럳vw3m )9>d] Chԩ1ӎ>Ui/? ܻ_ I|Q7Vi/? ܻ[iWKiJ|RcI}yy W Qr8P2x¨haMBe)O9&M 5֕$R)WGBz _h?ܟdPۓxAT]Jy-扃ÏE'ی$~$du;o28 l #+QU<JBt I>&b'h3Ҁ>5+Sm~mrw `uG__~?,^hvULZdjBd vG*4h/J}|6iOEB78aJOi/? 2HF ufR&>@A#7?|ys?q4۾M*&lg8i/? ܻ_ I|Q7Vi/? p, c~l{n=9->sK!THW57g(94ɶnӿ_&wJ+j/v-ӿ_&wJ(ŽwѻN7_E_pXN7_7iU(ھ iF;xj{W}b;xhݧMTj/,[ݧMuWQ_e{uWv-=a$!G׃Mq6Ԛ?w@#[T̊n<[{Kde2Mq"vcrֶSaoq.I"۫+#ԣ$soִ%y,SRO;9m{Yx[ɅF7!}B5kol C ?E?wڍrRyj6zS{vw-q3p8$@2+QMB9Ī^j>Z[MkbQ--%w;#?f?_'"Y$U^A ,>ӏWiZm_,O}'c{ևEt g.hG~9}armIvz<ßlW=:8 A u,R'+?5YIs\)jޞtW({yLjV8;Rsb9'u4׊~mMK֔Y5T+[8uG^5qqBPB{#UtŪxG% b$1kGֿksFa,qb9\iGu#>{Y]rs+wrT}5۪) /a+'GXnV&>.OaU9k\OCsr_1×p,fhY;VB?홮^;& G*md*@6>{YK_@6~fU@S>FkvHּYi]HӰ<Ҝ0˭tM{Ѿx .& ~Yy~گ [{J_#M(޾`FAё&, Vu\5*-`ɏ](#kFo! H.Xs!1!I6ZȾLZ<`d֭JcSVW?.{ȿ>ԵWF, VUN -># mdDTQ,-ǀe4d(L{* 9gcOG[J7,ɩ30VU=1Nh35)y/&p:+i~.N.dgPr#.}9'Z7XY>?.KM.u*G*?BUˈUWוjpF-EQ1(\ow?U*֩_{<%Zj6qkבX-o_±z]3lUEow-ܦF( {^_׆|}4r3A3O5Mx}_um(ۋSFޢ $[K0;O N41uX~:ޮ3o#yW1FGhw'VbEي[Z}LhhN%b~k)t (6j2[ "Du1fqKYס5ŵ>R|r85"/RaNqd{~g奎}PS[\OzpE=& }Vy+{sr@4čALq7ymkp*fZk5~'K"Z$JRmd~5YOZ>u"ui@3j̪p;ɏ^-JK&G 7#b]BKZAx@놐K{:cZyB)ԛ~~0Krٶ&A1!\W&bvPDoķBbJdGʜ n/9 +Zrqxj&Tp ?)7R_j~*עCoHvYR$d>ZEu\F7WprECN? i/NUԚ]|iCm#9#_q׿^* k/6&2Jk?o#)jVwRfSqH$gGO:J(R]h/P3+\[Co  jӱ2 ]GseE 'eoO!hC~3/H̱(8edhhOB́K>YQNX Ic݂v!Fs'E"s&c/K}2F*SnB wY;XkJfL &p*&Ӻ-j)EwU >ypɍ/`KXtQb}y[ (#M#[lƌΣ ?*(Il=A*a[e(0i5fixF}QbWˈ3' ͫZ5ّX0d{ ҪIL΄yiEy "Hmzn:ڣѤ/hȗ/ $Nz֨ս'?Gs@jZ/WZO&m6vvcX G m^d!r0Gn:lZLL7s+gTR{b}oJ~ҿ*QVo+jT4AVm&89ӵ=Ig!v_dVo+ORoJ~ҿ-OJ+RQ}nF1;s ZW 9,9iisfUcq^\xn22P\'ޭMYl%\r;w ?DzINmao[>)E[o-r<' 9|NX1wX\U1ZrO}h ST5 bn4.K6fxU=4xˣcHƴO;^uj{5{\fUy)~x(9,G;o1x"cی;E%F¬,X2FOӦ3Ӿr7ta SUooWGgc7w-A#b@<^aG7#=qFzT/ ocႀs`!\pN[IyI8t\R<~ uYMpbXddz gxuFIow;P-[vQۚQ$sxV,ɎOU1u5/E-7hŢ88U$ d'7(7yڋM˓p*px$?y)-ZՅnKdc?JNcz\rpsqyR ST5 ȥ>S֙]dc,q@] W2/?e?^Lw yaZ0`8i<€</?e?G3__^*g4{?Gi<€</?e?G3__^*g4{?Gi<€</?e?G3__^*g4{?Gi<€</?e?G3__^*g4{?Gi>€4DvqBZ($֛T=uVUfPpI&FNV 7)zZfFOQSn@n@(>y,ctJ y#+R[W⭿UҺ𐌥.e}ˋ{?+z E幄8 m/88q g8 J:FohD$o\.j-SHXUJ章d6ܵŜ>[)l*3I'W5OhZTyNnF? Hg=wo2ao{sQgE uich\d@yV,[j xmZXXnȑ n A (xE93 y5*Lʯ 8`$YV~գƶ]Nah{&vm~ny5&}H'v(_cL#p36p(qڴkswgK"uR =I wN 2% ˁ8ti(o$3wr؀A8δҼ+Rfڐ̛N>n([9ZG^xA$~tIs$ n޹ϥ`šxFfT+\I{7#8rAKR0mJ^pE' mHMP;syV#;W~XePy?*Z;QØp@+28w#Y!HK2XA.Ol~f,箠RRHw2)܀i[Bnl-n, 8^~-Ʊym$K8E`7=kA V0۔ntɷ+ͱک)w9,Is5τ|2%KEرn7?7'!G<|;t뤷ծt2S)\  ><ydĿj9.;w<+8m ȑc6sMWԴ ֺƛ*CZ:K?$MBvI2Oe 3B 3 B{6.#d"r0{T3OÐKY-ӠҫW,|8-mO+z+8;Lã(qfg\ .<PGj[(3%ݝ hW#֪c6uYӐdёi$@AV>sid&?:ys;3,+nN8@jndgXvI< (;_8Sipy~EI^"|rǨ]]iPdySG* $dnSGcQ]F5z_qUV."gE6kN_8fEQ GM႖17t_{ ZiZ^fv ^jTC+N"I%.$<ke4kGZ@?2}V4O46 c:$!-p=tjqi@.6M>#ge\($uoiZ{#"v_;*ߑwm`TKJy?_+QǙ?/Ε؏xJR;a ѱHoF}9|I bGCQ@+eҾK1FBԫOSj-:heM;6Ka֤X1F%d(!f??#N3sU~kƋ \L;Ȳ>l lտ[Ot@@6%5C$àNt;ZZ 9OF%eMl-tm&1-%bD "3(,qdk? NkyuR^܈V<y{FV>o=?G-:h~rG$zre\1wR5ᶙg:@%S mQA u9>o=?@Q],24g/kaΫ4>V8ft3O[2o!G2Ɩg?0@rxtA ?A ?b*]^Mk b/.VLq~KM"k]Dn|*?r }BM@<3c5 cj+U^~X_*U=e?"XϷ11ƭy#v}|ݩk㉊Zҗ쭱O C~"m''.t''.uxY 7q0$Z[1z1R o@LWv;:>Kf"rB7G"rB7Y%> Foݭ=SGO?@ya+C^أ~_4?ˌ|@ׯ$ZQ\ۣF$ gn͌asc9h??DnDn\oT<-ky}{E}o>D!/2<XH7X?ͿDnDn|Ux h|@yR.wo˕lgiҭ2?ZSɿ~?/΋9?!w9?!wgh?)?ִl7 pqx(?}3W9?!w9?!w?Xi?E4}JMȇ'>{9?!w>eQPuKQm?[Vt% Ƶ[}Bbd#¦X:qj_Ȏ85|_&ZEkm8IIcsxC=[T`mBA'= y2(շ7Gsyo>4tܭ<6i,aL!pVN>* SZO;,W,"B#k s^ bNLy:zƴ<+}:Pn?,"rJ8Z04}ߙ[wK[̽K O6,uP o?}hiWЍlyBm8$QY4_Orxҥ yJ}z̹e2F9ȒZŚ@ĿvR r硕ZtY.F [~`@@$)3֘[ddh!\Geێs%\L +FUs WI"Zڔ;pJnmy@DY?+n' 籞mQ`bܬZل1wq[4+[s|6d 㑌s"i\Cj31$ q8/$DM(VJ$Ҵ4: ޓ 3m۴mcTWVT5+vbn8ĖwOmʮS7MX?A@[]l Q$p-k "pcpqGQ -EΥcl -H$7Q?6ԭ(?dsLD`v"3ȷd4zp.02>9/,H yUe !aiז&5(- 횑=}qK-Ś`8#41/4$'X18 /R0}p0MDI i6'=^.-͹v]J㌁^Y|rm:0 #[X;YMͫ+!!<r994VX[,*?ո+VVPʁa_j1#29eH.0[Fi%0\櫥O _ۼX˜v#A8;OO I!-j#7c϶r~^kP#j2DـĦs9=Pz IG${ aM剑u|;Zmwgiw*QArr::ռÆϔWh\y%] ٰSH3, uM&6?" m9!N2s]&7si+ndrm%8-y Zhֵ $ 9i?O4Q,mTa@@ۊScd@܁1(k6ĩd_mfn$VנȥbD2x<}Nso-q^Eghk1rFp{f)_-J6rh*eĉ84>p}x>=9KKޭU+$D$99]0Uy.ĕu/ʱ QI~ZղY\FR|v]鑃ӑ.X_jgxcH$Oy,J*xaELb#傐gNU޿\kGL3F]_%xwW2ryҿ5hΓӹG둴,eh}fgA#ԯk>hFyÜA#O^x5QY Fy{royb1uS?$F| \t:(-\:DB\Hrs{֯u~ǧ6r A;:}zqTPcM^O7YϿQpB XT/<k^5Cԓȼz!9~ 4z0%2,HCYdem91s>M2(Қ̃{ g"Ki }8A,oφF,-cOxY^+y@%0HA "=~M˻et l!Ԙaina?f;{ :]Y @ <"f3<@ЀPZ@ {i + (pA"HbցM `/(ap @38 .: #A8Q!%Hҁ K 9C? EAB:h TCeP%T BP?tƠ'a f0f~p(ë8Uq _o<(@(wT *6 P%*T= ՍQX4@>04].CC7;7C W 18`ؘH ǔ`1.mcvXl6 [=mcI39q\:.ww7}xK>/oƗkg) A@$ل#6 a(K#:CIMRb=D"iIKIR.tt4DH#1 vQr; BѥR)%c)['Q\Ij@4AZG%B:GD q GfLL̠̤,UB6P6UHVNNWS''wX0EբS-#. KӣiIB Z/mB^NZ>\~|9y Eץ)S;O ,xm  )*+(6(VPTJVڥԬHlT9Kr""EEN-U9ң2*RݯzIu\檖Gژ:UY]GsSza54iZ Z{:&յhӮӾCa$yUYwTOQWPZJրilp61L4,7a c퍅Uƃ&dIIɐ)tiK3mh]ff_mS̏?lfВkYnyˊbeѪꕵuA6T%6[m:lيmmb*4fycfG[tS88&;:.[a'M'SęEÅRU˕ZeJbgt3w5wwp_( ,|^3Ves5 _;~d2'b%%< 4@vGAzA~Y]|`u!Ԑ!!BBw> Q!4\y=J9J\lﲑ;Y~uVJ< Tq&qq\w> +oo,)8YSBq(߉?X8.p ^%$Jz|4y:%"!*& ;WZ_d$IV;޻zB'NҖӐ`ӓ]Psfy懬kddfo~ZZڎu6ZZ_cƼ#޹67%ouoDliS||qVǭG/wն۾ ~.]'laNN;\v+-)޽dwƞ=o{ĺ>⾌}RҖw\XvܭBb[]R=TxӏVzW6UVrXHV.n|槿+(B3y pHYs   IDATx]wXGٽқ RFE{(*F1"Ď-E&6؍4Q!b56 Ql=>̻3m`zz:x/^?_?~7n 8tgeG TSUu-ԾZ舯|P?1xǏSekkb1zhOó(ڵkWV#ra pE-jRhѢ771c|nvj5AU(((b|D!Becǎ6T.ǂcaQ[u$9Vx׵BP^j#ԻWyDD|̙3?7/5zEAćl5^7o>y$779884o޼N:m! KϪQ$NJnly ǭഃǏ?}477$H1 Aj阾st.ndHT]h-pԠ0ƺF5 U_E=|vvvE BĤsFFF5#xV|lT?T=|K$RL>^z5$vR>}اO.2dH6m~g.Q.GDD#'Ǐi_J ߿ L0 iӦ^z_\ά<ܲOfkJr+q"\gRchh jW{X!y<=<4E3ogҸ37wUJQ}a.u :wy8junwZ,"644^%^pӳ74hd۱ T+W_wE< B !,)d(ƿM\mee[{ə1cFll@T7N-&22lÆ ^^^XS¢գpA{&Mh߰!K711:w0L&M4_g޿?##ƦA~m:u" ߠh>UDܚ:.BGJ?5ʠI#!  B!i`ese O`̸fk XvT*ʪ ih,/((~&uqww۷X,L u@"]hgr.o}N4\F4MgggUyz\\\bbbhfk^z5x`xHԲeKoOB酔ᓐ 3gN#5r[PuX oM6 شiӮ]RA&O\kkC ٵkׂ [n500l=7n ~x/!߾}{S33B!$I!D4EQbsW\Tsssǎٳ7j(999""m۶ḨիW...թ䳠f6IN'p[=<<(W²;kd3 \2'3 @h$`.w] K*>vC ÇOL8;;/Z‚+Ǥ?~<$$dĉzzz^ `FMvaQ#.=iD0 MӬ g% WH$ӧOW*Ç$&&?~R4DlH$}Rҡ(iкw<#;"{ѣGݫEMš5"`A,^;66qƍG;77_vq*Ă~{ mРATTTTTԄ /^5~0ׯ߷oI_n]-iݸ:爍rqrr[.EQr(ӺRDrqquVnD"˗|̙3&&&,SNX֭[լR`oY_rrڵk=z$ СÈ#lllkhM.GGG;v,==!$|||BzmX622Rw;ͯP(ɻ9 e.ΜMoAAAY"agc?vr`!VVVG޲e˱c|||HdLϠ&JuE;RUN\J@@y)J,,Yҽ{wccSN8-|eڋv9G; s>2 ֓ϟ?m JEhݺu߾}.\X~ÇI(Ə0 __&LucJ2 ѱ?T*hѢgϞ͛7oСRt޽;vn߾={VRR?~Bfff؈⒒\.W BJTWnݜBn=yyy 266"m~n߾ܹsC aAAA۷o5kVF]dɒmΙ3}O~G=(--={?{ԩh77q͛7$I\Wg#99yժUgΜ9{ݻ=zԧO۷ok>45L&Cx<BISE4sT:SO8q֭J[CC?%JFN1r ibShצUi/T?)d_O\\\7B1ɓ'oݺ 5ʹ@(|hIIɓ'OtBQRP*2,88T,t9!!S2YS{ţG~'Lye\\\``C@@߼ygiӦDKr`Ν;===_ZZ ڵk.]|ȑ#NbffKeoKFm``.mlllll)..600066 ry;KP! .III |7\YYY!!!hiilٲ۷omݺSSS7l`ccsĉf͚M2ѣs̩еk&M>|]vDɓ}8׶e˖;w>|x<ҥK.],\pÆ ߴi!L 7o4bĈϟWBEa`,gz4C MIc7o|?| $[Ղ7k֬Gho6i҄%:4yƆ@FF[>80EEEbx<> P(466.**^p5kҥ]tw&E xzz޽{-ܫW'6h@ abb48,7޽{XG_OF/as<~իNNNǎKHH?ڴ 22BpӦMUw$ Ż&C3 0 0 ",,aÆaaa JUkO[lѽǎSzhKS4P҈bBIqi]<}TA+iF L.y9mZI!{=kˋ>}U3]9[()o=:r۷ier.EbniYŋ AǞC y)RWsbk.naׅsP߷$HjT|iP__;D'6l A^J{AoЮ(=O#$!Sm}Μ9v튍ڵ? dX>'|2>>뇜fff 6LOOxbnY T2lQMLL?gǎ+ItV999eMP`M\ JJJJ%A=K4R(J.<&k+++ 7,쵧O&anU3 @riBCCڵkLLLLLLϞ=ظqcxx8weֺuÇ[XX\tˋmܸq}]666e#]I(|={)7lзo118;_~~~~6l\~CWA^]]]g͚մiӍ7^xիN7o^sss.\;.\2eJӦM333>>,/@ZZZ.]zQ#w cccv(ظq;~5kb}=z{SN͜9S$jՊ~ ;w.kӦ̈́ ֬Yf͚Ν; OhH$bm,l`uڵÇ?3f0`@"CP*|;lL;&mA RҌ\DIh+Vhٲ7:u8ѩ\[hovsscKZ[[$ . 6o'Knׂƍ?zAsnm=rwwW0;;ҥKu044RkИ3y%o%b_X6}_9{&;+g8p`ƍ]]]/E(h޽{===@ MLLBׯG;88888…4[=ճl)J׮]kooߡc'O;=rL&uvvvulm ۷^d2Yb38]"*I' @"$@ !H T EQ8=.f2":,8E'._ :wy+Wxyyݻ2((h޼y%ɓWnݔL/--mݺmmmΝ;b!bB@L^X_Wڶm[PP`iiٷosilhذatww߰aԩS?X\QUw޽tR uퟰދ'N̝;B˭[:u‚\`ԩw|ٳgǏrʬY.\?/_^t)..s*;|yX,#  qdӦM[po֭gRYQ?&b}=Sc1ZYf2̔01!Lz+Wy<^ B 6}\.*ƊeOimmm۴i{{!h0`@IIIHH‘BEšC!;;ʕ+7oތ􈈈ӧ#Hܽ{ٳgSRRB?Cll,.SRR0(((:::88ŋ$I:===SRRX+Ѽ<#A*1 ܏[!B{uwwJ*U^:88ɩ}2hѢ[n;vٳg֭ ?~z͛wܹ]v˖-RAcbbt)*x0իWϭ[7o^AAիWYW|~EiĂ o߾rW^UtGDE*;&aDDD"ٽ{ŋܹCQT,{ 6+=wۻDA6Z ȩ&M0ݣ 55&e!w5̶ckkkUnYN4K.SM+{+0/HI!H1O <I'B>!B>!B! I>:.6rdk/%̅s'_|!12S챱aÆyi))RRRBCCUaaaG;v}7n}vL߶m[qqqttO?tp;vSuH˗/ҥ'Otios]pǎ۲e bRTgO<[.w4ԩSB:,舝lΜ9!}kBx}Pؼys@bGDD\vM.Ν1VP`ջz #SRRV\y~͛7(uAgfM 42-94khM 05!iIs???WWov޽6l8k׮CE3Z*CBBzO IDAT:tPw}&Mxzz2R7[T7oO֭[`?VQ_zlGH$rvv~Ŋ+fΜikka+VԫWY7--<22-3h vW;v1`{nAx>~ip4,+WzFj`O8uxO-X`СNK"""e˖#F;vcǪvtJ~4M|8&&&Z9*rV{uF"$IٳK,a믿P*^zݻwܹ{nPP,w:O,۷ݽYfM6s!EݿѣGN"4sm߾u~gH n!+,XZR^zݺu KcP"I 999W\),,Ī <|=<<"""7o0LxxxӦM6lW\RgϮSNJJJ 233ɓ"ˈ/ǵqE+<;]'E0{9 nq8 {rvnNѿvHM'^PO~Y޽W@-tiW ܹsՅ fffEQ0j(cqΔ 򁄺압UXXa0|>*Wrr26cX[['''W>kgIFҞ>}# .\`ɞ „PHrP(a„-[&Ti$I?22ȑ#ZIReOCyʶNJ$"hNԘF@G/8ͥ8 Iں[n<شiv9={tqq133#IRY2{zk޽{bYI=n ${{{zZ#BnggעE-[h;4X[[W6swZZnE$ݸqCKȺʻcb@ P ߿ذax8i˖-]]]/<ciUV //=zoh֬Y&Lؾ}#Fղeuuqǎcǎ.7޶mkl 0~,3a:+{ٳ'(((22R(0`͚53fXpܷoӧՉAAA _Aa>o Nqe%x< :u긺R)N"u_z>x' ?~F7o/R7|sb~Mvm#HΝ;zjX,޶m[Eg! \0p! s^DD>@߿?''А ҥ˪U{ؠA7o]v޽gϞe]?V4v M6ǺuEtaa!&oe;vL8C gϮ GV'[nŊ wl:1uqٳgc/2ŝ"s!Ww&V)SֺR\lY~~?ب EqS2!HLMM.QBɓN]Yf|H*M ?x*.*u5444xSӔׯ{ﯲX(L5.prhN-cX{nESaѠAnyX0RfJׯN=$$BJ%<*C A4MH"7_f%)Œ,/_rlp*%Kp/ ggg$)))55n4y2$0~zzz>ĺx}p$JS4iҽ{Ri:u;z ׯw.]4lŋǎɪHR lcŋ[j7*|}v\\LjmMs׸TaJ'"ڦ4M'$$<{,//$H1*A3 mc ܵWnw>),YO*'K.'Oz_LMO}U*O_8u_%JLLV}޽RhizϾVuH d$&`@Q4&oL<>HXqsTMK&Mh_ `bb?t988auYYY*ƾ};YEPM|3P>`7jǨM zw Bkkk??cǎZXYgmmDd}ZIA}uT*mҤIex渓 iӦå%^$UUYFCƃJ}BDOx.# MoĊ_$HP&NwVnnYΜ9(jرcǎ`*#$! D)i{DxDn뉉[nvsss'LW)`?kVM HAh @pA#((pN޻5xT"xȸrJM vh@9Ν;_zRlܸݻ<ͱ[RR2aLV5+--iu'}aGĚc ;;qƥ׊_ A8C*J[OT*JH$J 3a$J2777!!A*jYP!gU.ʪUVK.Gj kE.B ཇbFTb =7ػw/`۶mMݿAzѵNzܣOLuooK^tMzرf͚}iɓ'MA b^?A.f J]6063 2HNV=]%8Gkkk\~M''ulPbDlw. >vSNUg>w\;;; fM(|w6mT!իKFJo߾լGeT4._7 ƧhNIIqIm۶dJDEEիWoر!!!x5B(2=T+r ,Xs*|U=ظ[ndeCBvڳgOVVVF~צM4߯_"""N:sNԩS4innniӦ;w_͝;XB!!! bĈ?SeoYXbddba+pzSL,۠9տ;MR%20Hx}_8ԝ-؃zm(534zohMGW EQvJe⃝U-T allܦMD0TiׯhllLp*KB~L.bX&iLmK zzz{666sIv?dȐqשS'99Ç}A8p`ƍWܱcǰa.\`nn5׾~:??秤ԋ/9Ó'O9rdӦMwڵ{wȃ*A}w 9B:{#xgnGӴ R.}:K\xÌ3o߾i&ĉ!RtΜ9/^m8p:Xڴ̌3c\ *KVao~^(-:B彳 ) '333wqq(kx"sS2 K7n:t޶mY.R^gMSIe @ Ҙ8mܹ:u*..VraGյkW?X'cRY{'OsNPPP-t?Y,tY:D"ޚL=7Di>4mڴqU~#U= 4(<<|QQQ&o޼a4n͛7}Cb '$$jU$Ise{y/~ElySL_x1gn*UoM) mذaXX،3~rQP@ 9Ҭ,v#.-b`+b9]Lt rBJo?,Mx*(JPT8p`HHHbbbddիr9u=<Jm"**ƍ|>"W`%,\0;;{z bpv2ܡJꗨtTiD">*ہc`cEuTAuvf&11QcaTakkkwwUVU*:cG}\R"<#GDEE 4~4i۷/^33QFC_z5bĈGbۄ ڴi3bĈ~Z*88ݻk׮U6F4ܧO+R;H)aN1ft鱇N^K E AAPA [ '/_= a^xQ~} S6ld5kQ!S0 KE͂hTǎ{vEDD_!.plykkk777nEiϖ+IlmaUskSxr'wPn]O5Usgָ-j<==G?ZƊ ]eZup?O.iKGk|BREUA{z+M4YpСCqr@ߟdgg)z^߹sG.GGG޽Ɔ=qZ.]:p-["rssYUYnf7#I z)0nnXˤrrrnܸ{o``̙3E"`2YKKKJ @͛N&! IDAT7YYYJ+V4I,/Zhݺu+`GB>>>..=<x,ɲ"˗˗/5k933sNNNGCCCuAѹ8 ɼu ӱIq۶mA9lXykɎ叴ZREs)׮]KJJ/--3gNxx'lmm.]<|i-M8tϜ9,,,/_ZK?Nɝ)yc+Wx]`-jQj;5aK}-VX僅UN>|$ٯ_Çk)?p ŻGDEEj= ॱiŶ Iīls;W`ddɶnal4Չ'p?D%&&Y$IR' LU<ܜ):xAY>###<<|ܸqJWTi."IpsIwfeC (0_(GabbҮ]/Ι3qݻwwqq<~̙3>tssk׮IEa;p0kYz˖-.../_>s̎;VSrPj7o"SNmӦȑ#d X6a/_eA  sΗ/_VTv$''[YYU9 %0'w.ci4gAHHT*4MM48qÇW B{f'cyPQ* Çd۷ooܸŋ-,,rLzz7o`ĉAAA"h֬Y/^`IIIZuuu]lkl^^^%=t|!W&?bc( 77G7j = 0G2 '+kű:,+㙛[^^^QQ@*JRXEyqjժUV5^憢+Wi|}}S\\|slrȐ!ǏqZIK7TPXS5zhK.]4yʔĤ<>:V^=w޽{Ce2YRɝ?9WYvp_T?.9|6SN9;;Fe˖'O0$OdM jB[4~*+n򋿿.(a:l5TϫUÇq4_#B!XS]F5jԨԜ Bhܸq*C<8--0_:u*N&2aLbԩSׯ__TTcTd&7Kr ˹#],$BIV ӓC' *E3`֗V(AD"311Wl+Z+嘘ggg69ٳyyyNxSNݼy;~Wxx?\XXoVn۶\q=zx_cP(dӧO( Jrcnj%y<a:waÆRDɘ)?X6aM55\Zܶbcc̙#ׯbgg!?6m$ 7no߾gϞ-^XQu5X>꼼\-*4##v^H[ʛQ_J%EQ@(j !doon[ǥ.aÆt- DFFFե A2{Tϣ"==B@$$rezk|*PX: N Z и.444 jժD.gԶpB֝0%%$%%,YD{gwkwADQDX E .vc51Qc-,(ETQ#bFEJ98-?FuppGQ7o7yO5yi~X"44gϞ[liXhjw4.U-޸q{}y葪NfwGGkGeH$ՙ'O^~%8I$IB٫֘4ϵϪjt&SΝ+**B䫯5k۵knݺ˗o۶-##cɒ%'Olݺ5^]ߡU+?MIs˗ *a;z P(|'ǫMŋMm҂27 8o}A@"ߞv(@ Abɹ-E:z45#,Tzݔ"caakjj a,-S nܸ)!vzUfaRx5ӏ>$$$66qРAL;vvˢT;.]x]ݻrDYz݃;ksMKkb&{T}wo߮wg˖-'O'{L5UݮtG>sMouDn?qP8;vGؐ@ &TmCTR\S!7YrM 9{x۷oc֡CFպuk\ݥK֭[ב5i4 BofpS+ѬY3g\.W=ٽ{ t@0[={[j ar<%%ٹ6=@Y5ǂ=0!(PhYZ(]lU" Dck^O>U*g_ HNNpBÆ gΜ ;n߾GQB$ O'W5jϬ,騭>>>Ϟ=SU +mHPPЈ#V\,oܸW*]DWH$k֬ѣGxwD !C˖-&{72 ㏵wuّ?yZ'ދ8N9? gΜI1IWgZa02 vU#i׮]hh(,)++U<  J +58Kj[ H( xW$18~]ΣG>|WU٫u6nX^^ :88TI*++{e}0 ڵkד'OۤI6G]T/8}{ $IR kҷD"888x3N׮],XPiǯ\6^W@144ĉ+V1 gEBCCu@awi|(*77]tQ(Ȼ;zݯ^jgggool2%%e֭ިp֬Yϟ7`kzKp777g{>{, V髵DGP4 RRRRZhp޽N-5ٳ@ puu(>(^U`L?Kp0LFbj\i+<7eϟ:O177СFˡvI&1e;._<>>~ӦM|ԨQRt֭zڵku 4($Nk5:gddt944޾AAAժXM\ P;uOQaKΝ###51cF۶mO< >TJi25Vs"ߦ؏CIsVŋ#R=utvƍ%KdffifժU0ݻO8ҬYyl299_}M;u?O8qÇϜ9}>>>ϟ??~˗E"ѦMۧP(BCCan@DD_~ݹs 6TuPuss -..L/W.mmm\/!m|T(wiܸ#r}T۩7o|)--.%&7rwJϞ=+tӧO\U {ȡ'A2DF׺V-MlWc&A tq|ʕ'N?DoJMMf'$$H͛7?\x1##C  }`}zVVVQQQK.w A@% B 4cz#** &w 0`ĉIII>>>:`̰|ƌ%%%fff/^xQ߾}ܸq+ڶm1zEҿ|ѣ:7ߌ?e˖۷o߽{E0gUsWLymN:u ,+--5jSNNΪUD"4Y,--׬Y2bHSsgR_R*-б-+n2x`-Z}UIuA1R*TwSSS-Ua>9.# ]{zؑ$I{P0ޫVٳ!Cr90-- prrVr̻/A(R)r,K γZoްgPE t|JŇÄ6$i/ ---{]R } 03++K*%ܹs6mD~*fVVVii) [nv޽dZ3>H+rH,++\.ZCҲ%{u4...44ȑ#AAAЙw߾}]v!f͚cǎI&q޽m۶ ]v=t~T$So~Æ sM4ټysqqH$9rȑ#ʚ}E@@@||#Gn߾ӏ?Zs\JJJ oePV>;bh蟪zJ-:TwoM(!*;w :͛=zM6"f @#M:WQ3EEEUڌgfz~V j vnUZ|09 @8#WTTTBBÇç{xJI999(<=ŋiӦܹSNL\ Z'999y{{믬K.m߾}̙W\a/ChS#Etv\ IDATCcޒCoLLU/aؐ!CˣG.^gee :mWffj@⿞PԺuk:5L) VO-5 3Ch8Sc}m28toTkMzX|T*=vXiiiXXX׮]_~mff!BݝEhPEDU9ҧ%B*6ceAcw3i2W噥xfV}^FNզh @g6@ x<>3˜D"edd,_|رR ªF?nذa˖-&Mp#ԠA+WЬ6h 777??ڣ6lx=BgVL"#G\|СC۴iCQTaaUvvvEE[׬Y)p^P{ %|A|~zz:7P7#I8xIus_l۶]twmmm={F֤tޝb>K>lE ؼys=f֩T*\&O]ŪUVZc$\vMKU%;]{Q=v XXX0nĉ!!!X!H wUZRRRCb۶mIIIs"$# ===r8@53,jTO雷x[>eJ[jٲ%R 5a%%%p/A?("""W:xh︃֭[_B̙3~~~AAAzau-((hԨQ1c-Ztmʔ)'OT59rd~~رc-,, EHHȼyRRRkxoqpptv54s2 q \.4:PBcŪ[OO-Z,]4$$0`d2U#4-?~\FZ|!Cǒ~yPY5AM wܩM~BGj;5jyÇOJJqEё#Gr0 Jj̙۷o0a[~СCc˖-EEE0>jM &^sa7nDmڴ[nݼy)W.zx.ud0샤m111;tpż<n޼ EЕmVXXo>!! ֜6mŤ ȬYOjcݱc[nYYYf`z#*fE"Qt5A8sܽ{w6m4:6lŋq&Mի}J$׬Y%-[Ғ]qQ.-4,5PFO5NMZ[vX/X?pttd͚5q wܙCbB&999Aƍ xd}ڵgvwwwvvq|֭ز={<<<^xWiVPvvvu_~p:=s7iD,?~dffBKjg۷tFG-\pԩ0⹮aX~=V!k"a9z:Um]݊vdYN9Gd`5Ρ#Ћ˖- wsscYrssGGƍkri S@pܥK.ZaѣG׺H}} Պz°Zۆ޹sF:t(hZN>?{ّC >|٥tGM B~nnneeeyyynnn[_|E~?^T$( B! N=888--m2 zf=;rsse2V1VC<3qoŋM44ixzzKYݶn'^< D"ر˗Û5kֻwf͚?~Coo;J$M rmmm"˷GeȐ!W,*VO1a„jpP˒=CO}=;ԇ;rM>;;;ѿ"Hl(SRRbkk U7$a.MtIW% ˳jg``$ro5FPkeuJ,kPR_3~Ԟp8NNNAAANNN'rzemmGzSW666]veL.lwѼӕ*EQsi18yAAYYY};r>/ =zdnn.H`zàT* =z$ `XTg_CkX>}ߪBGt֭@/C+; #> fplmmMMM6mZ\\ uদbX ԷY]PhaUȬIVP*+V())i֬3F T yV;=o&=a(ߝwz pʕϟoٙv "33z%{g9{yUO穢Hi^y hjf-M>U5+gΜk3sL=h/GWL Oje U1°Դt럴N-=z-.KgoeEqz{|wj5|kAgZo1 HIIR)cfjjڨQ#KKKn䙦@_'&NÇ4hЮ];hf֯)))l; [A̤?09B0MF|`jh#6@^v KMΝ PNv+簾qjZT*KG3isSQL@4Tj.=0|P(YYYЂf(j(?9tawKL#H;jIᙾ9cƌ#Fܽ{OG;hnn^gU'uyT`#:Q,P'VjSK^2_\|3(*,,;q*ܼr CmP"}Bӻ)=K?X }{4^ԯE`՘$OSh@GyBAժk@P@ *ثMj';=0ZЕkIͳ&sÆ 077U%r>*j)U.Q M#3ڕj5Vx+s`3𙀝Q5AWdŊC5L:7;5ji.\.OLLڵm||\.WzԘcS]ձCwq ;J#y$CPA@@RgS;V? B.@WPUd0~cfffbF j򰠺80*h~ bQfD}['77WƑ ԥS—ib/pߔ pw=S"ܹ&//AAA0͛7۷?uTF5C%ܸqc^G:eʔ o0VKcpj5 (@F(!+[1#qCϴ^1}rA*/[]uWV=Z|wJJJ;fff...;Fa( b]+ZhtiKGqT4HH5j4eQ^5^L#EQ9oVny&j.ƹ2E)p\AIEW=aD.ysssgϞsNfw^HaXFF̙3kǺsK >Ǟ4yP+jZԌKgNQԳgϔJ\.?v)pr%"(м\NIqLb_"?vPSNDttÇgΜݡԢ$QU0gsZaÆ'NЇ7nf3bĈǏ 8ԊsssbqNN }Z3//$ϟ7jwS'EQ PCc7IjPdH: !Jb^Z%|~ ^~\XS浛EQLQ d cp9bDeBt9;;7nzV\innNQK6Z̔d2{{{-jKTªZ>R˨s>'$XZZzA)TSJOF?v.\s,---U~$W^˗/xoj ,͹s:w̌tЬ,d̘1;B-))iӧOjgϞw!oݫv+SF䗑;#PTڢߩya9'#"{kfc\v~;~ԩSƽFJJ_p޽{;w^?zҥKQQQSLYdIZZ駟ӹ\kfϞaٶmӧOY6]sS= C`0cƌ/bر̻_2ULE]l[[[5IeeeELdRYEFqRS #3-m- 9eee&/9tPf-[L0Y?2M6l񴴴J7|||BCCW\yAݩ"E<Դ稯Q?vԼqBDŽq1Dͮۃ%N:uj6mVaDSĵk<==Y&CvY LMMp8.\ի)//J';;ʕ+;v3fLDDqssy&IHH%dYf[Đ[ׯ?zΝwQB4oӧ}#] B˞Y Ϟ=!ŵ07 BPp0Bk&Y l^^|#F jcs|@ hذ!}ĉ7mĜPq<&&J$ک͋׺'0 03!\d{8|zjook.21fIz +?s_~.ܪ5u#>:Tg.p8!vzY ]֭[7hDQ X[^pÇAt͛$Ifdd 0ݻ{㉉|x֬YMݻwiF#>UQ>{LPX[[D"0XqZYRP(0+Mq%7_$1e\NC@͇ vmww[ !88xڵ[n?SSSbqnm۶B˗/CV˴6mDTqIIiYًy bT|J8q^ ml @E @Q"V%ⅅCqWppphذyM7T\\xMٳg6m:qDzI&LزeK||0 5& s- r չsgVB.۽{O0iӦ۷o733ܹݻw۵kp|egD.]\Ҵi7ndgg7h`ذaaÆ1,W;23[HԼy;\,2q́)PAPC8a"I:t(ӡСCϯ^hMpppBB„ .\ذaC[[u=}ٳg۷oéڻwoLL 򡟬*={׶_ű1>۶mj0 {e/na\5PJ|YTDz/Mȸ*UJ/))iڴqGJkEEEEZZڷ~8|-JmۦM8@Iv}ݺu"֘4ϵjN:u077L |@РA^zM>}˖-P܇$I^^^ӦMxêU?ggdXL۪%݈zmj>陑b1?Zۣ(byE{7vwd2l޼Y1::'O%}]˗/?}3|~TTu,>믿’7o>}BϘ1cܸqvv. <CJԞݡ_>}zU'L&GaX>ǎ}-bA40~/Ta8vح[\1kgDT˜'O~Kpp($---IpW'/Iy}VAHq8xѹ"nccc.\ئMӧL<͛7gΜ >}I̙3cƌ>}XXX5̙34͸߹sgF=%%EPXYYH$Zuħ ywK} H>Ih;-}1<'H,2tB.ܹs'DիW3}wnݼtœoݴ1cluSO)Sk [-ԪҨ̓xsrsdkWOK+p|SǻLLӦuppgjJP(qq EsѣG}}}mllHq\ (J摑ZFyf>y `D-o-z5'O*׵GSL:myvvVNN7n#0Duo6]* IDATq.ђb>ˊʢVZ%Ab1˦i|Ԃ>@:\R3{G` I~=,LpPeHlZs7MaUv>FQq'ؿ Ap߿̙3q011?v0 sX^(<4c5tÜ\#|>+0 <6A#𮙙j{ApZdn#>4>v0~X,6773:wvaT۷&&&={dUqk;`vڝ?ŋ 533@SUZYHD 4iԆ>u H}#y$CPA@@RgS;V?Д<22ə5k*7n\xq֬Yffftyff޽{KJJ ֪U+XX\\CDҽ{w E$AmI DsB̰xdd;t ṦmyEF-))KK+RW`ݽs玃)` F_j|DQT$ B+++-!h-y{{{{{!C}5P/J oҮ]jAP|AQ C$FlQ >S+{3c ]^rgg ܽ{wҤI앑ѹsP{{GFFO:ӯ_M65J€I0EvZ@WTB3,5J*>|ݺuW^e%qs@ӶMmJoW喙*z]\\tiv.lVU&P͙?Ihڨiי%*iݻwן'z:p2Œ@w$Ncv(GZ*k*Լ* l~:11~ >.((>R rPIJJNLLܹ;ٷr̻ hH EPU9F;34ZnMtrrJKKJ 3 p8?Mh(4#ø56VM,|Y3g6˭Ǝ۽{ד$֧$11˶-Zh׮]8۱c޽{E"ª/ð8ɍ\|f19룛Hp 25|X(FFFwV;6a„jQTT`𺬬 ??޽{O<Ց,QTDvf՘uhQQ=UAF(X#j^P4>RwΝ.]X( A0 (x{W\UN͘1088ɓ'>E-Zz@Ӵ<|-sujh#"" ;EfO<)++cZDM4ի"W }ܹAAAT6#ؾQ"7iq\NST*)tppNJJ`zfdd$$$\p^AVTT\~>a„ Gc(0/2݄L&۳gO͗-[6/rDDرck9/2o!2 |. zzʮcP?)W()Yg,`չs2ܾ}UVwqqQu<P陜 =>| 6lСR!mۖ===g͞(B0Vk:2wUgRRҜ9stAx2/&fee͛7o׮],aN߷&>R|0E|5m|,---..$I"(xƦB. _xQZZʔx<EQ Ceҟ>}z>}`B٣G;wf NMM=uݻa#G6mtڵ;}tjjל9sGQQѪUKKK]]]ĭ[~W^-_\Hs@ZN5^‰v)sN(!Q"_Yyϝ;׬Y3yϞ=ۿ ng%2Z[ |bC MͬQUȹ:Z `<8x`fd͛7i$::UVf ۷̜9s&Lxׯ:t B\gWWWP ><)) qEG0.V̖-[aaak֬>gΜꫯtyVŚ5kN:?WFaD[ P_%$ILfaaaii`&2 @\MLLL$D"dVOH I$I . R6GE `bbbiiYVV19sZhdɒ+W^ZSi=z4iZ DC8[nc˗/^ޥK 66=//UV/^iً5i5EHsC1r9AE(\0"lKG j)@QPEKSS ҨP(,,,T-GQtǎ2͍.6lXpppjj3~@DP( P_ഴ4x(ɠ3}{y&M 3j1zC;vlРAgF7z\.@q8VR0x rn:<<|ҤI  SCf=Np֭[iEQPa֦M#_J-z7o*iE%UQVt)URj֯IzDs=&&hJ%MFV U hlq"8^t[( {Zj5jY'^*))r oh$W^ڪZfX`@5x֑ 0{GOAm|DQT(޿Eq\T|£$I8־}{ 33EΝ;:u [l 8zMh> AvԂ???s5 à  ԩSQQQt D-WWk׮IRKv8мysoo/^o˖-;v7nˣOYT"1y8gdd2d/r^ZqyoJ۬333b1St]r'W9/2 bX󣓺 |kRRR]\\藊 /_^v$I ȳFQPs"Yfw^`t#!pH(*""Ϗxży0 633[b3 44o߾}.]&NxJY 8p3Νjժ~m!!!ԩS7n\x;`a\\#Ȼg\EE/㓙cǎA)ª IF72al],=u3mS˛3g=<|.YvmQQȾ8:::::>zhݺuJrmll4ibeeUcXz/,Lbth)//5 /_NNNNLL>}ѣcǎ'N\v(88xxA̙sƍɓ';v L듶Y(O۷hтq;:N #4an-~,U:lF2T,1okp8vvvvvv0,h ͛77nܲeTO9rdp3xȑsf͚ՠA 8…  4S˞/}63PSՊbX :N #ԂVa'4sd" O8Z%7nXfMǎ?޼ys#eee m&&&rARgP,7n}6mhοr6]еk׺f#cfee~e|EiӦݻ744!lll,,,^~zju/--o߾ݢE KK\HsС xݻi%d2zi}۷_n]DD'.ͳu̝;Xri:~U>j?ǿvV456K D.-XM6>>>֛7o1cF>}$ȑ#Ϝ98s̢EI AdȑǏOIIٵkȑ#=ڶm6o\GT%*isbݻwڴiM4y⅗ב#G1ԥr' E!DGGڰa H#0B0 ^tggg: fϞ =Bϭ/bȐ!Rvݿ_&$ӧ|>MСCPqvvɁ˖- e蜶HL899;v4;;ã"z€ykݺuI/^4;FaF Eݲ1BxV|jjMl2]UESNHYDتiUx}0W0ן={֡CռW^%}uN>z*=w^xQC-A7oބ,,, {SB%fUVs( 'WE{v\~sي6&&iPk#v¨hxLTJw5Vˤ:B'k_v?cظva^?ܹsRn rvvfE+AQԎ;ƏC!&xKMMEdth.K{lbe %U(@E$veI|ЯFaxC/&*2}Dedhj"wyk8 I2%%BNV^AۡJ>2P `7y6L$JQ$I$%ǑNmwĨjefqko:IFQSNuG ap̟E+h0F803D"q|)v|pUr ަЏAʡPjӑI9oMَPT7g|UۏX:wC$T()yN Jd*_0ˆύx9;;233##0BW.tP(!N舋sR4h)`ׯSNXhm۶mv }+I9N"AP #6ն| IQIqJIPP^^^q0ˆj@c>G#3`WMwufA+ TQQ333CBBbڵkϽ{qt:OEQI5},wHБ@APPH B=x.o@؀CZR,$I$)ID0ތtFс-{FQhQ_}GFj賍aXii)w--[,55/¦L׷y"##M˖-{xlNILyYO`$9N*p!@P(f~`KG邢EQ$$ LNQe+3M0|y)rG0>zkkk[[[;;;;;;b0 EAe8kxd_}+//w>}||?sŋm۶s˖-#I+VDEE988,Y2) IDATk׮|//燇_~ٹwΝ;q№ǎ6mҥK N:~x޽iӦ⯾jRIQTEyh "Z.S"op$Wz H EPTI9QXNrG>?j?ǿv^*ˡeFss~)Ǚ>@}-(({7o$IӦMktg0Iq_@B=y:SٱcѣG..YYY.\4i#GgΜy惇<޽{fọ8~gaY  5.@%,$Q1 **f{Fh<(%DEDE! >]%Aw<VUU"?l)?C```jj+8}@~2yHtƍ7=޲%xw^txv 2 t oT4PA+gLP$jnC,^~͝'<}eK,;w1B44ݻp„ ˗/ 995G"[;;{ D" as>_A6mږ͛OqmwwYBUBEEEYYYK.666N38}.oɓ'ϟC ƌqrrjjjz[y<$IP @$A!P@`/$8l9Xa/f#À-'%DRYY䉑ѦMvޝcbbBy{42caaee׳mGTUU!Wfff<|<EII?p8%%%0vX 属FFFs;;bT}Aqb5*++GJNNC t4 qX<.!sY|O6[-"uz<x\6a:lRCr} Cb`f_U"ظ ill Yvmtth .GOOl6{ʕ,jݺu˖-!IΚӬY\]],Yx'lPXXXii̙3aGEGGGFFļ+V/U4q ׮]y]v̟?gJJ0Hί«p؝}ǚ;LJ}Ϗ3ft˗/ZbI$XZXXNݻwix+ jjjGacPAABh82+zzz<O&\FZPYZ>$$D$ǫ)/~KK EQzzzEUUU#oGҥKiii.]0aBi #C$46 йc%"D=PH$ ⰠY)n| h_RommC1l?UOU } f766ߖ8wwwww~T๘A@`U=GJT{=C>+a`!tGbK^Wѣ !2L.մu?(L:][oc `#30 xvWUG--Zt[2K A-277ר43f%x9sСCJaii\YYYWW((;WP(411)8 |@+tޫUa``P  }eaaannߺPH0@y懁gf( Q )??Ua```````xvaa!^|d}e-3c5Ue@+t @WW?5b```````xqy星ԏ00000000<{K >G(л# ZZZ|~=^붶6X@DQt~/&VӞ^B?=>E!'d8ΤImmm;::<<T_KڵkW^5113fÇtuu}^QIE333KNNFmmm==(Zhhĉ/_l>|0??Μ9jʧEEEfjuFF ǏgggZZZ3"7nׯd{ X>>j&"m BWuuСC'O,z-0E_rܹfpOOOBqqqqqqWx:>^6mo 6:uwo vޭ# ccc TSSÇ$ ~33Ǐw).EQzk*\KKˏ?XSSӽ{]{Pmg3++o^^mmmQQQ ?aX)))'N bbbdM---֭OQ>644>kmm޷o߃ez^/y;wܹs={餽U5Mhtqܹ_uvvipȅ Fmaaaaa奉.+..>r@ K~_~W2gϞ$e}ޢ833̙3&&&݈^XXX[[+V*$&&v;eDkfqq\~kgggZ` .ooo3==EZU4h(Sz^Yz5#::& B  ]]]W:̙3^^^gϮYDkΜ93 ''!tԩ|ҥK 88BҥCǻᑞ^PPڼy/駟Μ9Au\{{{;v !o?G;vlٲeg {B{l޼sܹ[l1;v~ I=7jrBW\ 裏III2tY=|7{O>Qem۶yyy;8ƍAAA3fXfH$*WWW_TT(?9sߏ*++[hQMM B%00ɓ8.!!A:B;"$Ɂcccn߾Ղt.Qf5^2`\b˧PVbqxx;s:LVn 9sLt钫+~:uE/~~~3fXt)psBonݺ:T[tY#MJJ={6D[}]CCCe-8k֬]vI$Z&'''44t+V>.M7P@Qg^< 6lذa=);==oQ__,w x… eee׮]7ojBUy % =?<&&fȐ!3s}֭|G!!!wnjj1boddtP055㏓92ڸqcee}[^^N.:tH$EGGOX8^dITT֭[KKKV.aÆmڴ vڵaY 6ܸqc.\غu+TVV^z ,+>>^^1eUN>}̙{߿ㅅfffYYYpڵⴴ4(*11Q:B;@||c֯_ >OhʬF#̈́;w߿Tle(7onmmꫯ hyZ~mx~ʕ+...W_}U111WNHHxBCCT^x1**j̙"h͚5sIJJϧSV[?2~x33@eeeVoM}.SXXHQԘ1cT4Г'O^~ٳgOPPΝ;kjj~G> B(ڌ2@ p$''t@%%%ٯzeeѣ򪫫srr$IVVV@@@vv6\x/iСҁAbsC0S/gϞښbH4h3Frݻ24OVbpp8t"1)qɓNNNnnn!!!sΩ]]H@pY$y<ޤIƏOL$ɹs禤ٳg=<<,(OTXMakfpn``ХXFFF[l;v,+0k׮uvv8qEinw%? 0*.knb~W__ߔ_2xpa\Ǝ[\\lll䔙;bĈ9s^={hBO`o7oJFюJ fe qW`:tey(xbiԨQ'~/--577zpT7pww8qMΝ;T*<䳌pĉbeee QQQ2|nǏxy֭$33ȑ#ϟuVFF̙3SPhǪ*ڇFQj!**2k`II ۫%oٲeǎ~~~AAA|= t!CГj9}7dGp:::;;;Ne;͋?sƍ[[[qN&m{>ydǒn666үY^QQQUU%s.eDFNd܏hoϧOnjj6mZVGڿlٲSQB~-qwwC[[[kBIi---MLL]pBjFFϷ^ĕ"S2y 'SLIJJ:xt222<|ȨכAE@ Xv… sss|Π!Yex];;;^fff,ᆪ!O@M4㖖BpڴiYYYiiiw12;:::>zλLjk .%CRVxMFK777on޼n:ssE᫗.]Z~=ޘ1c.^d511ÒE`/1e2:##CM_UBGqO|2)lMS,=C)|[ǏMMMedp84hPmm¸.MWJʞʌ_ Cmؼ_}ٳ'*,--'OO?7f>u3gθ$9eʔ^ `aaaW\n@p޽{/02BJ$^ʹihh!))_}W{}<7n/"999, pXYYݽ{̙3j<0˖&M:q]TTDQgKKÇ4-vGu__cq:::&M`ԩN ^斚 v2)d7o^J,Kݖ/_аaѭ陕݀O"\gg犊DE7i ͻzRe )lM 9BQ˗Tε?===KW\ڋ/Κ5KC.eDPiϟ?/7o«۷o2dHSSӎ;&L?nܸf3336mڅ (QsDD@ IR,os#*̔2c:;;,_<33sƍ۷o$}M6y{{K$[[m۶1EEE$Ilٲ|M6yxx8;;oMjܸq uֈ%K'N <رc T]|||D"ѪUBa{{ V\ W^~Ǔ'Onjjpӳ<==}СW^eVS_EEŶm*7ovuuRV|qs >w2[555666=QQQ2dHkkY@@-^X(J$[b7e2+;vToH*eVS֚???{}777g(DOO/,,UBz9;??…uuu#G\f:hw%({@E;!x MMMKN*C Gd2USS]jkkIě=ؤed%I(s(ɓ'<OƑƮj,uuuAȤV]]s(455ՄkFF듓UO*'vQ2)VFϥ=75ߤdR+++G][[[mm6je2466!<'[?Qa&s5hjjVzĉ'N=z5G,ϝ;ѣJccia]EWD[[[GGQmU<  V8/g5ȑ#ݻbV\9{잔*cnCdll,47ʀP~ԨQ~~~Jx|FkW a hqMæd````````4@0\S^(IENDB`workbench-1.1.1/src/Resources/HelpFiles/Toolbar/All_View/All_View.html000077500000000000000000000143551255417355300256310ustar00rootroot00000000000000 All View All View
All View displays the left and right hemisphere surfaces (and cerebellum if loaded) and all 3 volume planes in one Viewing Tab. In All View, the Toolbar looks like this:

  • Orientation contains buttons to set orthogonal and User-defined views of the brain surfaces/volume displayed.
  • Orthogonal view buttons are labeled: L (left), R (right), D (dorsal), V (ventral), A (anterior), and P (posterior). 
  • The Reset button resets the orientation/zoom to the default.
  • Custom Orientation allows one to set and save (or not) a specific transform (pan, rotate, oblique rotate, zoom) for a surface or volume.
  • Surface Viewing contains pull-downs and check-boxes to set the surfaces to view: L (left hemisphere), R (right hemisphere), C (cerebellum).
  • The top pull-down menu changes the type of surface: Anatomical, Inflated or Very Inflated.
  • The check-boxes to the left of the L, R and C turn on/off the display of that structure.
  • To change the structure's surface file, click on the L, R or C button and a list of loaded files will appear for selection.
  • The scroll boxes to the right, control the spacing between left and right hemispheres (relative to anatomical spacing = 0) and the spacing of the cerebellum away from the cortex.
  • Slice Indices/Coords contains toggles to turn off/on volume planes (P = parasagittal, C = coronal, A = axial) and settings for the slice index and stereotaxic (Talairach) coordinate to be viewed. 
  • The vertical Origin button rests the slice indices to the default (centered at the AC).
  • The  button (default on) activates movement of the crosshairs/volume slice to the same plane as the brainordinate selected in any of the Viewing Tabs in the same yoking group as the All tab.
  • The pull-down at the bottom toggles between Orthogonal and Oblique volume viewing.
  • Clipping contains settings for cutting down Surface, Volume, or Features data to be viewed.
  • Tab contains cross-tab functions for yoking the display of two or more Viewing Tabs.
workbench-1.1.1/src/Resources/HelpFiles/Toolbar/All_View/All_view.png000066400000000000000000001631121255417355300255020ustar00rootroot00000000000000PNG  IHDRig7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:"""Xf8[jUr+b`0L>=33z|o*.8 !EQVD0ФIE:N|,LA ~j^@ &Ib| (]a'_o۶̙3dܸq["@Q=;WNg9VPL`j-'&&,SN'')"++Kh`Dī`<6Yk&T\5cR@ ׯ_믿`΍(APL+6H( RCXh~=75HG*svz QX懴#G7BBCC]\\ZF<~5}[,c}3hX-GG,]t fCxW](,O=cqS[ޟ/a…*+f&Jnn={^iMڴiP(ޱE(srb,&\61K6#ts<ϧT/6b1y];ngF= Wt?mx?q`'N MQM!7_;xmԗW\1Ӌac~?;'h%{*9d{>* k(v<~,;%hq4w^B>}zĉw^OQ09S(Jk=vW=wCœ+,8b'%`K_snH:7V2qDm6L:009;y|Zx1Ӂ!CKZISQkLM~QxQw-prPw D```jդRۮH_v5''gŊӧO/Y={,[lСR"Q+ Pا{P$=o8JRMgBīJ\{<{cёyPy@hehiI8_{>Sk:ac7n~u}c`7f@]iN3d):jx=g|ݿ _urݍ21- Oݧs.[̾r譯~ ?W:ܹsUT8N4mB ! 7o.w}JQ(zرiB\$Z\m2\ל5 ,qEDѨ>)ɣ{\ h ĥn===+VVT3{\r~L/`)􌈈wݱcGaޙE%3B+b0MF~QI',MQ"j0D t9k{/Ax;:_9aĽS@4pؾwKT4uO8'@ 8Y]Ғ<Jא+{͹GύGWfo.='܃1 Hx]ǿͮk e \Vj!+glU/N?}}Jq8r|BBMpЫ & ##\LBtz^:`0h://Ԭ,s>7H@،}d/LBuYL&?xH!pzTO#:txƬ&!B_Xpi6σE@B6mڤ}?sLLLTT˲'Oq߲2˼Qt-Ot< zNqsٓc8y #z^sD=O1p$ pXo߾̙3oO߄ϛKRZZ`pqq1  ~̙3`088WWWZhe~(Xˏ^E)W65p#Bm 99999999/'8; 79&ﵜTWj]T"5=3v=ejrJh,-.*3{w?w9kFk w 3:,P_fsNTZywƆߺ m"-sXڲiqP ) Xv֩3FmD}qg!]Wq a0`+MVA4M% D"׾&g+M&7L&suuh4 Xͽs7L3e.T2ZM)%sᦄ. p%lܩ5~A߱y>.]4x5kTRE,0a„={GDD-[;w4&_6lÇ:uAsΝ4iRPPݻ###L2c kn!sn_Ȑj h4ZM9h,~~bVֵf_yӫtn9=+_ i/suPEPW;H,w.Bh! < ht|rfXxC"G x!voZ8spp88BOWBȮ]j֬![sy~w#d\45ZE&n.3!d@bӊYkSE)pjmz>ZMye2()r  "g&J 8=:H% 2rchWLIJGuHs9Axvk.5t~TӕQ×nYƞn16xlDK6oo5UR ٟ.j㊛Y1>G$>HP8Na3tx6g`?C+&7VKq@"nA1vG\@4˲D*J$ p,`Y##tyyX1|qL |#kiE t:]ddMM4k֬B qqqLl⥥K._}pwwwwo;-&7777Wsyv+]ͽ1& vstUj_]>[WksssZmǯ[IGMtVy6]']ڞͭڻk{4tw chu/| KHJçrZ>]g꩎s[?xA~,Ҵ\Z.#dZ!C+ ;ZzVPunw~;w?fWmѯEs.VgCqrAg_n݊C"O\\nχ6R7a#TNtU4M;88;ٰر};V̔;uf]b=t#So z K O4|R0?Hgfp3܃ A& K䓲YS ,O -{~aO%W]KJ??'돟t.ÿ<[c&|q]xy\X93GА{K&}8%Fm`if7<::\8pm|!DСCWaС y=}:u"fD..Ձ=+qh=aC( #]5R.NR>h$HjZV6\H#ccWK0Җ&eQ& ˃xZwgZE#=w_98]RUjmgي~vuR.*Dh&/U{\l$ItVsA@cޯ%݀D}h[ߥ-y.)M*Wh:fx@eչ|sW @HX Ӝ# HvիϹmݿ! G ]Oߓi<2e(JV+pӴ".Z 999J2(l>wrp7}%gV^Ν]Y# 3 h3e?,rƢo0VⓊTJJ v??,z%$$XܘrI///ِ7Xs33}V?,CBoKOl% hW)VEEb ЈduY=~8T}:f񳑥ـܬ. P>.XvUBH.Nn\UxG;kKIoS]m|ϔc'{LRIôI[4;SEQmo~GP/@|>_G|%5WOL%]NӴ@(6nh] 0@"h He/bpg'v"{UE-#G8z[-yV}Fb[~nddQ@AM&nޫCZ$ʊi*APL#u{7HR;Mh[~!n_jՐӽ|ZjJLA!s~א9?V WP92gWh#Rƕ,W-&8})$D(͆\ٝ{Oǹ/mib̺C.rԌ )wxrhzH?ҳJX`B0xpr͊1L)ayR{NQ(B\ZA;88 |;,<ܼ 'M~́c_Э&BEaP~_F98:1gt:)M1^ޱ_ usWP׷$!Dsn*;p)bTG39?l8?"]+kݿ{WRzh\!/5> T,p+&;* מ IDATVҳ+/)dL^j@qK7־?voձjՏ]~eD Dxɖ /miU?{/vi3N[c€+GTj0u>`/)[_> 0m/Vߵ羲V G¾gupy _5.b~5츭tgZz.Ǘ3aO;_fN_R`K(*ؽgZͲ*+S#^oSq St$kFA]Je/;O-ϓ$7WRT@ݪ>U\\(7WPC OL^/пRcGلո^G ΋JMms"7m`N! {Un&)xTE o蹋~)0|>:,kRnk(}Lerݻw̙3nܸrʙBHrr9sJ.k+ ]I,:%[qPT *G޿=PZ4-&8OJs"% XJ)k(Y˶)H˽v5*ݺ\lV #}֢Di-$$8q_-ܸq׮]  qss7Q_cyԋOT&akٳgƌx͛7ZJuԩ˗/_tÇ34M_vڵk_~% VZ5kl׮Xm˖-]z5!!aoj@*(jY (|6,ѰӤ(1katnM0 Ӯ]۷ڵb*J&۶پ}k ]\&11rPs=̌EͧyO-F1 ecZZw9aoo͛{{{˗/C$IRZl!&x x70o`W^=7771_u˗/_|9;'t2ʗZ=ƍeˆ(mݾ z]֭Gխ[W .젰.%X [aJZڤ- Q I.=r%ț넏2oٟR eY04hJeuhAq5;S͉, 0;Ɔu^Cuo4` Ҕ*(BUÕaYVBQ,jD*Ͱ,:u'{-wV~aԎSs>ZEqǼ)˪Abd svRV;Re>QV}1Q (p )#Kk?{57]нǐ*Ry%SaDۃ:窝#TmFC_Wԡy`^4 @َw@s0`^j^D2JUHLh1f͵}Jo|Ҷ,nR^^4z"af3O^33V_\|yfS8 Hdxzcw_/Эo2@Qgno{Z*Jme:~2*W<|iC*9iH1lFb8s統(˖,YɩZjJrvvvttT(p D-[//_>((_~+˗/[#ڵk;~[^5jX;*Çb?hsI)Zd2S&ә-NgN!_8e .զMaTr|~ȝ$h@Z, tt//Rcy9Pk"bөkYR)g4A tptV Y -#c Kw1 a( %zs≎'8dZ2Wǩvl"ntbN r'nҰK.j|Ňό5=>wǤL߰mNUNv\L6< )Jf$N<=U(ʴoz22o{GUxc竲?_eAsn=}_l-ϨT Ů9KG+Y1OCT}o.!EQe޼׫`0̚5+##Zj ȯX1AA=7n8<0MJ] μmÁY|҈#Fe$]`ѣ%2Jz-["Dɂ[f֯_d7w>U.]kbk6QįG Jaأ'ʈ92ÛPظ@8S ŋQY\^^Jz_ryrO՛AwrU MAS(lR՚_ "x5ϰ [Z}jIXimK%& XyziGu=K@suuVGm!Re?ѾМٹ|ʕ+C!+W>bEӔ X;Q ĝ8(*܇v{\J^&pQ0:_[fK85eHz:H.*ySjQb*`˦;PΝ;Ϝ9#Jw)R1񾐯hT)5/g?>{322MuCaڵ=JIIRc(ZPP(_#J#6789 Ppvr HBC9 =>m]=G}ѭj֣ d )\) \ͭo:\Ũ)hON^*fڵ+0U6lxlաELm*lwWm*|1g':X2tծ[E+u_6- ]\L,* EbE;GWYλ?==رc%6i"EnӬ޾ZMF.ڋQ>Čyſ+==eYggJ*ժU鿠DnEj2&ҥK[?Fw"H ۷K.]8 pvhd=q<0d9@TpL ( ..U)ȓ^Vgq{{YqaDjQ.]`9ɩ TSg] )M&OUa!Jdɒ۷ooݺyAl>㶮մU91ɨ#DccW{WUފ}.p6`e זz$|(oǮǏׯߠvZ > \xi?f/;_;gڌjr#Db]g7 Xzw{䅽yƍnJOOghF NAIr"W,мIݪ; EW?*T:t~EիW/=n+S7'!7 [ۚWс9ZM1iiӦWNLLOT_bJN")A?qNݽy=o_۷uri-yDaȝcd1}a2C->⊪rh2s Pr1i0z騪-[޹s]vGtNNiϪi}>Q$^&yX۬PS Y=\אE+sYd=ٌ)×jOnpSSSUCwl󼿿\\qihGhڴΦ[n]47N=}Q3E'N MQyn\y؍D67 PH:~|5mhdi|0C#m<Q;ul_2i͎ f9Nxf/\4~UrNw오RQD*烂kHUq& n2d/% u8@:aEy{{koa1W^ޯiۦܷb*Cq]P(T$dY699933DK.J###M/  ZYGJwZT2یȶP}YrSsD;/Yy=# JR<Ѥ>Ѐ !y@*JMzx7ieZ Sӥdӯfƭޚ0Oohqtqqٳw^/Bx8NP:uTRJx'Rm† ;7nؾ{,| "M#Mbrh]FP}m4ӧ=0?4Ҟ%J zeyT2|ϟ>6`]Rz\RnܸV]\\gb2S 7nT*[T^Wsk!dYڟj&V  TXc͚5Ew Qe4 hMQx!BA0 i~İ\xouHİ Xtz;{Wϧ]i±}UiMUvE-'n5l#R#|'}\e/<'Ze.^JM~QB :B-cן_뉅=u:T%bc+/^4bg7Ǒp4Z ,|Ę {/cE-YD˧|y4ۧ]2)JK~={ i 9|(삞zK*ݾ}^zަg2 ӰaJw,4_/ydpqcv|`:kL&ASTF7˺.`"~w&xxӛ eqڱ01|i37Нm16uE{w94kZ{NwƯ+Ez!D̊W\QϟZ`ƷdoW]m6iA5H"%͇)s|1cNvѳ+1}oTŹP|1=KGt@Qe4@zJm9ƭ'ĀP˄x"RSSMI³RRR4hP(rssAhuZVu鬬, y,;t틈5wx t! ֪&Go->J;:?lFoZ)Ll6-#˟01;Yp R.j0@hc0sx9N-z|k_z7QFf\zE)G0Kԑ׋8nժUPxVط^i Y(0MӮ7oU"\]5Ctv TZ3oЧ5G)5iwOT%$^(-5)?)|]=+QM ]۟m8avaquȮY8UpyLK׺Ы}CaYy?{܂- Ш/'~V(ACШ;9hq! 8^RQ[@ctP-0YgQ3& pr0JPdggO) NNNRT(*Ԣ7iLV-FFCN,|vo#g8K~n1@ҷ~  |[K <(o٩Ư ˮM׿m[4 .F ed9H]oZ:rC҂Z/GF˼ ?,ے+>a&x+GCMrx#ϯ²\rںk4q2;RĀ?VWh2hV5+dMW, c7wexr8;02ӵ<@ yؠ~bfw$@r 'PyQ% P\9G%JP(D<شù7@&fggڤ9O :O ~?&^ʾzl!wjJ{9ZL7"@m6nwqQ;N0 ]|(jK(\ cE>J{9UZ얠:ݾ&u4(q2+Yy:/{(r+dffߺ@VVGY%y(ߎŰ U3&Re2E]4M;8888a΄\@J໅cKH=b_"Y-X\.gF}gq$،^PVm='x&Z$coDžغ C7Ś IDAT >#7P:atzvhG/U,WIČ 1}va4Z6潎 4zВ,Pk#(7.B&uRE Ax|?(bj38f!O3ñMְs5!D Y;**q^d#lsg9F Vx=J{X6#wΐ8J7ɇ t ΃xhw*sӍ5V/9U25,"Yܹٳd̘16 ,z!Q#(M<m8%eWz=ˋU-M˘-, 4zehKA.K$xa6 &"_^7:)P,1DSW$%!}l0dӇqpknbu#q|n:8k6DQnnujɵD' lΤ`5k( LI ӛkp%L{oc{+l3׽cƀ9zESkȹb ,<'N Z!%W;zzz"y-W(7X|uN+F qݻw˖-ka="ie'/&75ɫ'nif}&m6meǺh^o,c n݂g'^Qn*k5;"Im'&& ///Rj9y^";RjyQ*P: [a 'NlWλ `c9Kyn!mGUy!q?;Ve&0dK~f6kDcơ/Nu;&˔)#jz+Vtww7k -77w޽UV^:nݺ˗/Ɯ4=+\_&?&ڜnjiR|M10vyT2 d1 j6%ɭn/ǠHE ;] III5j/Ք8dff>|m X}hMx`˕rTzb_HMKtG6>'|o-5Ve*:ntt1@٣OMќW@6aFÿIW Zb^ufj5{)vp)G>[^Z :ToШD>b'0~ 4M i[)2S)Ĥx @Qezc~RSSMTիoذaܸqq[bE,+ƚ[^z6ӅWJk<3*M? }: NEUa%$,Y޳3uUw]FO2={b}4s]9PW65;kbcF0`VZխ[_w'''¯ TNX\^f͝;wvԩO>p#yL*GlNy373;pYOBRe'Vba/w ƫliS|^¡\p I/lQޞ/˲ϟW*u/^_paCkG{=^YM;Ǟz8/TMO2}eYs({Lsl֖C'WeyewW_Z1H7ooM_= ;^)Z"1_ PdV2i?Wx* MS 0`iGE$^6RF!Y,@jj*YUTwٳǏ_\9 ?ErrٳTbq]W^h˼= VVڸ{jW -Tj%V1־r5^wڷ}?4}0|'52~Nea9)O}'Z no+]g@W>S/.U@Q -*"*Qł-v7b~jXcG%{"(`-S;, ɻs3̽sj/\pʕ| sڟ)J?sسgO8㸫ŋLQTHqJy.4MR*;_x6;bm ;ထhMa{ݷonvvv{zl4Si7ctxsMɡE2EEW`|֌>l 0/ЦyA䃿|P0E1PPc(X(u4o;7(ԭśQ(h ( A"_f[Puttl߾3gO޸q㰰0{'RRR4iҾ}{GG2LJ|->԰܀^}v}0G\M}fZx14dn4n 15/^aC$'~'ˣGLQ3:[HLV&AQLſ5}"yp<?\.K$I &-)))LHӴonXeʘ"U ? .y 1ђOA ?OW0{dͲ\ّv ^i1c~]2$H [WF96vb8 sPl62qj.`b!XI "GN~t3b4DL@MM"Y|_f\w=%%eժULJ3PʈNNN\e¾mk^xgST1e0ڵF۷L$E"+iӦŭ]Bj;w xL&c ^~:cƌof۶m2 Evqg7kfi %ZY_{`)SдiS___PXt ֭[׸q绺r尬^zL6*cҊcKE"hh"B-U[(}5#J2@QT&IRGGGzlsǎnڴhB6m{Gq<66v+W4XjBBEFF:wW_խ,nڵkS+5@7x gЇ]\NE"_όV/ED U>D"cF_Hz~ݺucFph+Vh4LVeLZR)Ip<~oVuq>mlR[%狫[bBǝv& !F핝CG,l۠6|`Pc3d>w_o߾5cTT۷W**/ՋW\$=Ye&ABH[uP n/ER EEEAق-o/JFh(z$c؛BqLʎUaGVWf۷>ܸRׯ_\\\aaL&3S;w.ΨT*333333FwwX`NŦJx;ta;oFx7e<^ &!xpwTĪUĮ?mabܹcc,߹0yT*SÇ_tVZ]h(bd*cZ<[F>Og\vGpi)|tj]}lq Q [:bUw4Q}Y(}v~.sεiآEsS)~ٳg{a 2ai 'WATz c<8*h{{@`THJ{) @SE$@Yפa0ޑ몮Qݸq#==ݻw8+ʺuA"s$I^|TA- B,w… ;w*}0KJJrwwٳ'r۶m/^bL Su^okpX>mF'%Ooyq3q*(rrH KާoЬ6صכ][fͨQ*R}>/KV^+"2qpS -D|?4AR  hzj4M'&&;;;S$ID"aF)<gs120Zhj v"* @z@`X/M<-vW-[<" l#Io߾a:^$Icǎ1ii2VNđb[M 3%-[ғ3(6eRza`jk֬?"8 (EEePDJMI$ Ec(E"PW{z=#8rAdJJʩS&L o߾MJJZ~}Ν}}}byrO-o[֡c&gϞկ_7Xff&A ~~~iii]t9VV SNю˖-7lٳg.9\c_ ~)6, (R ҉ `QƋ+[pwVx;+a9V߰"W0A$#o۷3eؾ} D"Q3+q[-Ӹ`^:ρCD%,8SP"߱B!G@Q>I2($Yi9+J&=EJĊwKl#fZ8.;zӓeߞQVl/-[y잠gdP3Wuiƍݺun+'sw ëGJ=lذv)]ÇbUMdrr &NU8tЕ+WRg~_AdR'c3~<{tА7FWu{, ܖKdeev&瑙ɉZ@O_He<'OIvq.*QVޢɗȾ-1&Ү X^\AxB~>,*# v-==x;rb *=ѕ_c% 8{J^.y_ֿږRX96rium6EyG% $e-]u/#p ($>H=i95cW¢>wcxӈNϞ0ȝ|7U=C>Uf?rwfCQ!Q,((J "lqLWkz6sHf$]ajRO"LبL#xu}̙4,ɇA}РAZtUAÞX;2r yM˗//]\d &44… 5j>27HNA5L~|Ř&y*[^mv| AoP( ҂XX$}O3OAd4oִa=/￿K^j< |G CIau%t`Xc^"\#TRXTx*F{;[9<˼M.EvT_mDƶƑG8:Lsz@_>? &F}#=m$%m& "InJl\o³3*~kvэ D"؉{eV1[.m xфnu<@ޱ`U]VE"ESEeiILk2<9v~۷& ӓ\lƮI\q& mQ `ָaA5μzG}Ǿgffz1Y)82_G5O_6e},EmA G.I-넅h:uF˜fIgB"ۂ#g:u:6lNVeWz=MSnnn>>>whh(Wn:YdL> xOo[CkRhӦѣGlMv999W\Y~}:vP{IZ %[S׭dbmy4ȝ [m߾]ɇ͙zeVdc- SdD\$IZf*cpeb;?ص4D aʛg,աns#fM $aN| #iU"mu󅹋BXv( )b Z|6qh(VmSZjEϖ:g+or$hI޽Ī5XlE!yj%$H xM$ER$eBv$ivϷ.h].gVEғ{Q pyMG}FR@)W]FgR׈1(MZdK8A"vhN̻/֖|Bl;׋Qԙo};[y/ˉ?r)qˈaj]#c#$~tGDBnД^l7ox{{K$BP E>F+J?~CQbf#{~LZ@4h9zj<DJٵ#ٱ4#\ \M3v/1-6*8@S?׼x<&~w@ԍߏ .i` Oٓ&M=zt֭dobbb j_}UnZn (JӍ7)WXs˕J9eߡԯ_رf05f \\RU DQkL= J>r'ld^ a{qbPhY].0;a `kq[@}fY]繷[c6/Ԝ{?! "#Bͼ,bf7K}iڌ½Y(˩6\z3rla;KL)8.]e fi? }hhfI nX|* _76й)MӖ'3W@jf}Ȱgujꢡw4LMJUh.@h4.bHM;j+O^(R;QqF֬j IDAT}AwoQ` ndjxT>TJ<Ɉ}CE( PHQcwdlKL2"HR)mXGо9'wc%E7\ڼl|d'#K{y"MKNEIo/mziC{1lTQ=+6!"o19me䰅gtĕhnݺg}9cƌ͛{|rYYY}L&c\~j?c~S(/]%;,o++yT"BzOVYx}2IQ_Y>îQ[cw}ٗFH{`$IAWND;mŝD\I} -KU1 OLNpk-F\?Q,k pLA;H_RvGC}'3–pФ&?0uϤNM oAd&+s$R`i؆Y'|SO Y;E!*Gad:;NյA0>쳀9!!{{{^:''G&߿lgSj7{t}]vcޜ9Nο<^v3I|xy8Ô4kqbQ|[QcT0aþ .#+Y$@zShT$qD(^gk  &?xJJjVroc5u(}󍉡KX,d݇HvYwdw,OOV}8(O_RӚs{Yx닎S v=u0Dֳ|Hm9AY5Yа%Or`eQCoibm(? eGUMӅ̎:7,^YPH{@w$v-b(R.wrm;.f(;r`Bӂ! ÅhB x#Zlan!C^njRS*ÇkLʤWlܸr[V7ޮPz7VK<ӦVUNlr+3?S O N_u (Pb,a{ʊ@!ϡ܌Yg}{9&,/JUD}rEG3XRD" b1\QŴdZ:vqzj+fxz2;X3~p[Dn6s߽~toD\52ܭYue2\z-#D|og{?xoD΋мu=ٕS?jV۳~=` 'Yggg#"=za?(|Vkx𡇇rm,,0xC*DoB4` ɮRBa|hZlNWwGpF:e{>+gOl%,lk-jH<"`,!ԋíMU VAxxx)Ǣ2TxJw=.wv7@#9V?Ӊ38Mu날S5b[Dkn/>FvgnݺdA'MBP(7I]}_#(|z% A%wq9.ϼtgw4E͠_*3ou;^Ⱦ٬Y3\;C2ܧO֨QÇtg;Os}Ln'ot/%; {La*DߤYԸYg.0+np7[\Y8屑g 2vY].==w[`˙Qqɭ3#BRU†G]]]ryAAA$I &Ȏh4\.7V%ENT׳ cy<ʁüķ'la\z=8mX/Dg?'Cw32EzLigsϲAceP3 ]̛'8$n6^_ +ƮUm\:͛?n2K_=8O˿3*܇{)|OYcg.h.v]pȾDc`y̒sP}&''hq c$!73hO;Vh$ݟw_81f}yA"@$V0Ura˗e2YVL&իW\ `|1NfaIz2m%[e 0"g[F@ "r۽9jLa0g @{#s\ 4ޝj (R&E8tqFY9@,0z}2E5 K6T LG\L&kҤɖ-[Naa LڪU+>4b^Ufi撧J֥jQѣ'»YVV/ޮPCIFy&p¡xiz8cQmN9$ǽzY]I]/b>o7z; NtR?x 221>(ZPP*0y[Qsݙshͽ1Vi<Nj?ZF-j)>J݇5߿4haV>6P=ȸGTm۞={vڴi>>>ܽ{om۶J2c BD( @{ab/{*/01yz=YxcyV111宂JDBB/Kŷlc(ڭkxb[~1ӟ tT**|ٝ7d;{Z,hVVVnn ? Ҡ(YfM^U̔1lp"*qNO_;y; p͛7~qMr՛xX vҽ`u.^g㸇GN<<zEduj`g9<$kX Ve*Ӝlٹ JӴ*kƮUXׯMն0hRΝ; BR1eXG׿}Ν;Rd%*Gxf4EnmgۥmxX 5W(:}?;l#0Ggggx8bgg׸q㜜<S(r\"TY2`,@͏0dɊA֥f 8p&5$.]1"//Fm$go|?Smb*HW[02ϟY&LFӧO/]AAAJvrd{Ctg{x\]{ L|0JZ;Y(Y(}5yL&JZάũJ?pq_Fbu`Vc`eg5։W^LDc0y}G tZkUfy˔J"1988yyy쫎agwʘ",AS,A.LOYfyXf90 F:.33Z&OǙ-5"IV`&b12& PS%玟/li]}% #UBPZ\Bt~~~YYTpA' >L$/`CYaW2|4M}s_.PU9J% \J_xgV .e0njp"_QE8H{ZCOk%ĸk{GZYxMfH$HJ]H$fR=W{Uu)ecD"1iJf;W%(7Ϧmѥ-ʀ~m `.4AvXl,mڶ*0+Wk=U0pYȌy, ت?E3`Cuz֌I]~<Fx4}a'7O>gڷ]ZDNiv =x!Zs粲:trqVۦM5jw.u.5̻݄m7,IzP" )PAPh8~d@׫o$&p94 JNVfbIM*Ά pߨy&d LXc^-gɁ딇ҾDc%P@;"zJl: '/_*$Iy5isgumM""eI90!fGg:>:`@]WG+JWcXΘ;wZ*,,}5k֬W1cj׮TY50YZ$IJ,.h1CIhI ܒFOA@瞈cDrόa71=utyW/cjf&v-77foo_V-5l057ȳFf`&ޑu=?kbaF6%[hv+* <,cq:i~f{AnK ( ÀU#_"Uy{)ݜf ͙/NB4EV\ Wcpiz׮]:|0׵]vgRX=zǎ޿?K+XZ(B x І|Zk<~P$ d>b>O)\?)p͞&R3e"Ǯ`CHTzYM8ewԽSKgaJ%=ES,72mJ'78zVssMwZ0@Z_ƥ-C`N5]^iHm3CI_4]s$CIR6l>Fttݻw8YVj999/_YYYLx_&;`,8%_g34M "@_so[Thľ-MO PJKɕ5׫.w̯wq:uhzVCmc׬P>|xO6Zh7V ?@>c ޵o5z6SFϬV2ׯ )t{1if{MU3`CuyVgRPwg;35BMqx_롿ܕ]Hȧvәrj1B\s"a`9;yd.\0hРLeȐ![nebXB-99yՏ=7n> 5^ʺ{(|ffeffK@"|&_B ]޼M-Mꂚ:kPY@jҶ_*;3$f|VV`5kp7omk"/-mSL@0y;`gǵoo8 ՖI!x#/=qxxN6۶o\̠H+^7ʖهٕ3jޟb| d@"@e10[b&---;;ȎNb/(D PCp `J+D$(xdǜOnX2{~w2%p Sݻw%{VVDGGܫFϝM,ƀ {jժTj`z{lɽ$";1 Ra#}qQK1n. H)** E+Xki ?̼@Ɖ| 8ں0 +!95ߞLÈ-'aPH1LcSg9kĽ IIOyr087㈎3徝J+1? ߾x&\ְ Bz5 I_>yk;'*@@soK[A''P(nYfn_xǬ,&mgb4: qD W8~>WԣkDzv4-,={!l~ xss',uO(V4M B햔$_վ}{=<<|1lm:t:tfPcx# %ꆱ=6kXj'VɄ*T"cg7NA4 R4-w4%j4_j{~Y L+)UףQar $VGɶh4cǎj͡iZ.{{{l*cҊ<[K -wd6Y } #rZ8 8wԭh;}-r ;>=bensO쿽)r>YMNjh`wdPC#kļ;&V6ޭgc{ձC>EA:@ٍ̿=li+Y^܁N*e^|plٜD"q~BHD >q$dJ-A uE4i_.,?:OI}eA63{nmUj fZyU={8q"aFھ};+;H$b|RݻO6͘3*n*Y%C$#&wpH j(t5vr'&H@;Ii?GIm0MZ}1t:]JJ `ƍ ,puuaYYY_d2&s+c[e]êo1c(㸬E0 1=YDq55}֪ri]z3k1LK5knpSy6/>p'W٩R. br@E <ʎ6͆s4N$jH^*rHD98 :&lLƕޓ oVgRV_|ڵkߗ!!!u>P IDATjfٳg܌$P(:'7W񓗯oP*e2;; Jfu R{"5qvp AP!c-uZjPw1-آd׼ٳKܫ999o޼aI7ŋ=:~m۶-_iϞ=K,aZ\rQnSԦL2~aÆ9- %p6:hqƕyr4?Zpu[~1M.Ӕ]*LbFl^*&O\:UhZ T*^GQÇ_tVZ]h(jA^rl!}+/e6X[ENgk5Up81미Er2_uX}f8[sy #,X[|xˆ8Hxs`e;.v*El( 4FAT*UPPM;?Nݦ:|ܽ|}}T*_t8 =j۶Vm?꓾Y/^{ lذgcj2uܤ +gNj \zi쵕w?ի7f̘l3eckYmέr\!d_ :F@;n77W7B5\n6[΀ vGD?www uwwOII9Sz=.]4jѱ&\7i3c EҾ=5\]Nn5=VZGJnp6EsٷN1GW/_z`bnNX(0 n@zrSχFUl;R b n߯Kn{b_QE% 4EBOߋzPd1E!'g[J4- HG߶o'IA o>a$iHT};?#Gr[f,󐲜ۯ>;@jDvx? A5  8 XFl#1G+;;;3}srr1r\PrD?۷mv3<<<<(tb4=GfPرU֚tʴUd^( 5eMMSmc? H?녚iH!""@P4v74 b^$I>_J: I. Y?/\kWߝ_ے{~Ӿs jƮE}TLGGB[uwK3E$Z܋HDLheꌣc{uݒ {fVP}+seɪ+w|f@3YxIWf:XDaʡgg&WJ[| +(d2TȬ(bV J۞/sܫW/ 3jԨ1pRmիv A$%gR4J$Zœ - kt8A$e7W1G FU_mώowΨ3_Oo7C١o vř0i]kk&E\`&1LiQ+nh`kxR;yz|W}(sXr%=m0ޑŋ/ sgxѓL)}.gdV [;VFw,H$9Rm?ʧe;vN.MB?* $Iwoy\s9@?R$inT3_H^t90*)~x{?JU;r/BAhц;Z* yL)B}S 䭙dU0ֶI~zdG. u/?+lEXXmjh30yISFY?roݱF pp0B4w֭8noogggWɊضg:k7=ytC7G :]rwgP(٦4NmL(B_NUẴx4dN(Dv>E AĘ!2FvC`oVo]N+A0P¹0n\ʋ;sl'WHݻwϝ;5i$gggxmRRҖ-[7n\):u^2ӊxH*V=uH/Ԥ2^7o^&X^a-*WC dܛ'7, ^gӌ'SF|kKĆE\' JShsR[O]r$=B5a`1$Q53B/ˌH 72rgl~&]0aƏO[Gd`SmTʎ$I\vm ^펎C ]r%EQ˛̙q2w,ÆxǛw*vʀu$eC! *bb]Ig=ydv= ! J"1w/_\x] wk|;+;$%|&O%=|,A@LbeaD(SQK`'aSm,ő뼦iիWׯ_vrvt`F4Y,44...@<ۡ yf8}=$ZQtzNeDxY(HavH!l^(v^T*V{Mooowww Z-Wi^OӔorrRFk_eڹ:h}0Y\H?lnTܖA$$$X$+ oڴuCt:]JJ ;DAAM4ipBRDtVVftpwAI*P%JDz ,5Sw=Ajzvb=RNMsc녶uʼngsZ>)~Ǯ>̯>PǹEνG M֭̾KNNf4h0q$Ebr%ro2fu~V\$I"5/W<3rGZCXyvzys{+0տj=BsyVsrr|}}%Iaa!EQ( i4T*uwwZ.Pwk js|su /|oRKıA>pָȻ;}:waO;W8wIxc~C3@ѻc_Zd`ḫj1S4COa˭=yyy)h$ɓ'O1ݐ [LL̚5kN8ѣGUM%q:$1``dӤvIAbZEҒ[~M'l(Qh'l⟏]֥ιs k;fo?|;+pgOw h|\H`6;_ުZvwz/8h 0sȲ?:DTP^^#$J,f |]Lce}]nnnƍ b܁ͼ//D$ ENF/ Σ׭[7f ǁH ]|F1_n HmT DB1A@j @@@N Z F=BkG3˸}w@;&GUT;4D6 r!T93!SLثo&1>]/z?s(25֮zڽ{wkժœ3>FӓԏlVɌ<BW h9.?"ۧiYlLfG(*??E"FQ1=H$rppg"rӒO_<7jc7dΔ*R9}]ߝڷm »uK@ȈqV"O @v.Vn й?%3Ԭ.AVKJ{K.ժU+<+MQMZȎN>wS- 2KgJ,\X*os;_{+j<8;gyqB=TR NtRym(ZPP_[$z(SU ^ay;dwjuM{6` VagϴZ-bL AQTAq#@]ک_Xu[+Hin]QjoI= AB<ߙ;\c((HY)Yh.o*ś&h υ(_L% -w *) \23\rt>{9Y3y9O-3 Zm}؁;ʐ:luJpmI.5L]6Vj{R^D@eMegX'&Zpp\ljə~\g}u,˪T*e9%4͛}!$333**ߟ\(ƲlCYi:~oav1F4-[ڻ *=5JZx r4}ڵAdӧO|~<Νk׮8,CalxrAdv 0OINk"2?eY  ֿ ; 35M*?xJQƲOԚyebbb-~+ԾO]+0;] w:$@ɉ~^.oy'r0WYx/fX^^p}ЎsV?.'83/N퐼JHhͩO?A&8q5Y ֵd\[;>Bm3LƲ,q6#8XȘ:u*q^P>a„[n9sH`ã-ȕ ĉF*RVGnL'N(:wɜ[NwXkEDDDj+ FRX8N** ˼;R9+))k.ݰNx|?`,#ʵߚ `CY!SIx{GY9쟈%+-h1o Q1qo+ $y?O}.ٕz`4/sBRH*ĭwt^-(czNErԉ~Jxk:>:Q-(P*9c>:\jϱyEl,)ST8jEQ-NkP(ڵ;pVիWhhSqwʕ<߫W&ɚ(HmqZ0^ԩڵk͛'[Q%Hx'u5<݅K*Nφľ9eɔPLpA2ލ]r]w6v| :0YJ:{qBQX04Cs9/²Wi5yoohoãtFxyycO2_s|Հ(k3!eik/1*/A4m%%#Sy($ 5ǟ+)1CQ1&p0ij>;}u/r _lo^ [uC*qy-..q+1];u͇N/ztyr_:I9sD;}G3*ibU'ƥ rl_ ^3 N*n(OJ z]6}'>gV`kocE3_{Wf/x1%Eߛƿz/v湣MGSni nd*9(J٪kU~aAO[b =4'stt}-Y{؜?BQJc~6q5]ز oyD-y6Qe2`" \q(Gl`0X _ߺ&7Y[̖)j{uQuhYgmO"iٲO<Ѳe˓'O.[d2Jmg6l.'‚ VWw=T5?{ IDAT_MVA)}˳[QpiG kb9>?ԺsFl? >2ɕp}s;: *ee {#o62]qЮ|:^:G0\eyv#}!3xC] ̒" 0@hgۙYYI`޶<8PGk9D`5#+U49գ'O߿/܅V y趞xO=5c֏{ʞ=&+PFLw!Sɤ 1 ɗ~廗 Cd=URjсSF*S6j(d9 EE mTwi7""""ukXZ</jmi4gEWFtlk3WO.oa4Al Ʉ/9EF @ X˱`\)W+$*B*]8p?mȉ,O&'yTo){_~X_{WH!0R/2ʳ:8j52h/!(YklDDDD۵2^jILM*y[c{x:K1M= ҘI#u(eQa3%z>==B<UKm)5UJQdwQURC)_Jp8TTF)J ޚLtV2=Z%8nzyzJT<72Mgݲ˳.C1(jM_VɻA.( M! ,Gt0BjĿo^|JC!$[sz!M?[oyﳎVG( (:tƍ %KjөWƍW5j,˦E묍NqͼZEVF6zJxS0#5 ! t_򸄖@XI*7a(7];̏LD8 DQX3Ǯs?B SEQh4ZJ8p`^^]iNg0\R Iߔ-6;Z_ 늈žի Tӣ&{ S+E&ũ.puL<?d>-=),Dj~}HkZtQk׮bdӧ[;w]׈TWKp,ptѬY  ֿ Ygm4x{Cu&g]ޠ(*<<|nr6GEӴ j"#4 B!O8hK,d***:qpRHfD"ϐt 痖24f%A(&~~~' DDDDTwzh96CXpUM6lt8'g/U)tmuڟ+MbT,m.T-QĽ;ԒE*xU\d-iFkGRoߞ8i]akoz&;˖Vf-}%q.]֭˱K u6y9' S'^ {> fݑ9B,z=u:σf֬_ϳqA>}[fk)00Pӹ7M!Y4MT*J܅:tq,˚,*c}'x'h4 n=h6UYV_xwVSt^tRQm%uH@ ,@Ae4@3F?[|RiЛ&J|p@R]DDDʊ{> H$8c0q,˲w?JR=3y|!\l}Mǟz|pg5<zlKqљxXb```Śܸ~mNjG ,1G,L)т""""p9>yģE6{Uʫs֯C2\C?砮Oގ(,j6p[8hԢxÔa3fI!7n7kf:s{ӟ &PQ!gpهpKJ(AQJX<)*x\cY?ZE\`… A;DODDaβYAAA>7nݺuȑ۷ok4W^yMt&D"(0  W V O'g^ХoVfD{\cJj4Ҟطx쾘雿>WFd<}BN>d'EÆ࠮SϞ] <|)*!K~]7->ǘ| (jyVbѯ C˲wϒQeppxg@Ĥ]!%b1 3uz#E)Zǎ@Q=G"Hg(]T= f4Mik&)Ģ~ @QTčEDD0hmi؜+q͛7]㓔駟>|Ϣ׫ˣhهEqF*S@GESHGu$ `C(bݏpQM8pDo> cicr mRefuY]ib;DE)oE_Go?1gB*Yy •Z!i#h"U̧ BxEG㵧gC 4y0=?R7}>;fRV@:L.B Z#0ֳ^!#~)I$~X{0 ŀ1u3Qh9Cŀ1qŽyX@ʝyy:M)xYGzw,<`[9>¸b !<ϳ +[O-("""buBqqq\\G6, ǃYꦏvٷoߨQohV(ַ<ݻ7d㸢;v\ti֬Y4Mza5֭{%-!]vjZP"RlyAa)&*2C!n__VgYӘC{gEOsE;zxSYq<[xH\Ưa3} CQ(r P K'o=}  ӭ^-ZXl֏p>@`;(ED.4M{{{ܹs,d2(d$-f#Ñ_BTďZ&˽gƾ<'.ʖEÜ/TqUJejB_p/bh4 Eb{zx,JNR6g1}"""" 2\g-ELMvloy + !gϞ`Jƽ-P(Ӿ}{LT*-B68#ܡő~{4$S|h \.#h-T y8r82=#c@(k֏p>@,//`0HzKB"={6$$Ghɱ]lD%8-TCehʖˮ'Y mqyT%DS-˷2-'lOx=WS(BH޶)۴Gwe%,r_#!)G6TZR&fBĩWy^b%aqF^el_<1%о$Jơ5)u SW*軙n~_ٞ,u]~v×9-P)+krXWڊ6%!s)?l]m =f !TJBmZ!C6jow؎B*]0q۽{n8k5ޒSZۖjnuyOxB\;RgիW%ķ5l+6ZLUvSB%و0uh L(鑬i$m?um)SlOѣGczꑐ8}6jU9+t;Tl"nFғewCi&BGؾbn zmJĵ۷ǯ)Gn[=(_-w/^2ܷG^zY5qP( EXXXXX=j|~^4(9; њ-+JX!g9]Nv޾쌳E mIb?(9=C)18#Vց'.]\:3bUIɫ)87c~„V}4#~ת\4ȃ͈=٪QT0mغuÜ!)wvq:V26,r<奸s3'O,m8mnrT?qIDATmz%˧6ywr"ﬡ~l]W:OD$}ռnrGEW23ڰr3^. 5c*GieS./ZdwZ n3C-J.nX+4v3dNh֬u7vfksLM/YYa]K_n YYVaulg87W?5jUU6|y%^].N^˶d[TEMxPA@>< >Zu1sG ȹsΕf$O{%y٪%{MXy?,-sc LRv;k:?p>8m5QUs{VE}M;2mٜ18T]>w|q}yѽqh28zA)4queҽh>dl?Uc(y˜DD@{jShh .oG6d8o7, gJ`Hy#GuИ9-^fd慨Cr^vQUQw P8~U8Vyu0c[ÃzLu@ܜNݥ{lx_n+M߿5yB|n҄A}W朷\l+?ucӥCȺsҁ1˖!(({ gà v%r)?(ҋaZ< }Hj3 9G8ϟ wt~ޅ:$ /%7zQ};u6'9U\8o5[Z?oTQ5r1{U1QaAaQ3Tj-R6rrϟePu=\sq-:O `-fcSiv0!@*gQ ys|fΏɹlWhS-8 .9Oç1 WSjZP$l޿p@.>?[ r&?߻KWV.mES;p("ũJ=) :!bª/S|1+m?H;*QJj74> S3<9Wn{&\/r:dP-+:Әw##gA Jtt,:g;K3٪ƱK!"ZӔxܽV解4wor?`¸ b TF5Һ BA\YSm_ EK/D].T ;ls@iĒ?>_*$, zGș*ܲM[ 8q1kyv@DDeu'[֗OM׸Q=&-.]#bFG,!3np;#;ko0ܼA>M٩lLѳC|=OcBڣ^0? ijژV|%1+6#pXZ9SQ9+,S;\4 ]4{q#\CQX4oJv!fyD/0B{_X,]|-PׇXWG)[2cp:[-!h&%~u"<Rb4MAK3uIÒ+!:]^k&G0U@eTS";_x[]1þncNA*&KsuD9Q15L,TQxF %c6V?ԢLz$F`̙M&9㉳Vc?M=[#eX>_G^X1Zle/2+7 -ՄVކ.!Bm[gD8RϸYS#"b̳URF/;-JǵEA6xP0O 6Wot?gz=:AWt)1~ApV(gG/t:CmƲ;EGLtM*y,hAQAWttmTܣ^,٨igI9,PS9_ZTxdI!H+-<}xi+]gjӧyT+*xE&Xhs=hs.WF.z/<1" }Y9@ҌX$l>\XZh`w81վԢ ;szT./7Xt)gXݳ~g{ )9 ?|ܭ5„E> n>\P!%qine蔀4qbDL}&jx՜=MMƯI~"i"0tN:ڥ+~8Z3ݚD 't]P+<R=8R< ah"8{'hX'^|aO57374iEL{|=#4h$>"#>!}k[=wsVBwcȪ=7&"5i `D/8l)8gjS}i8GK61s+S bĊ濾)L}7[x&%yiJ%k#1e9s-Df:9.mk1otk6`'[ vwG^dUewpM;:U0`Qa*ݙo/G41I˩۝ckXMWo٠Y\ Xԫo{o8i5g0*}l7\͒am>#t1N#Fs{l..P]xOW? JS5 UKɡ(/aLrpq4f/FNwH aƏ24]X}ۦ˃YzBT'mNӪ]4O<cͺ4Um;<&AfZҥmn.R"V "EIENDB`workbench-1.1.1/src/Resources/HelpFiles/Toolbar/All_View/Thumbs.db000077500000000000000000000250001255417355300247770ustar00rootroot00000000000000ࡱ>  Root Entry(4K@ 256_6f2da5d33f4d1d33* 256_e783c429ea2030ac* d  !"#$%&'()*+,-./01234 ).DJFIFC  !"$"$C" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?o^ɦuBBOJ̰=70\]x̳l2s#YNAe95K.5ee.~osPӣ 9f pY&{q3׽Sk%gX>a4dҤ`:;v8v(_ ^3CaXdq1쑌_|yl_^xGc=>ݢ/)`1>~\ 'bI.]E}_&2pG;f}/oW6:{Li`6F;cYlIIq@לU[.:On/oq1#79U8˞:7.اvdc)zi/U9ma 9W1H (BnXv\dazfS5-FpI Ү/ X=v HU<شmm 8_'<F9(GZ-^x#U`2)%?!RTcAިiϪ^3dd"zN>c&.xWu9es5:_&k?+[p=3OI-ϣ$1*Q6#p֗^nE=X%j2ƈNt :vǽUԮ b(n 30H8z]K>sts{U=2m3=Ǖn=1qY[a1_wHx߿W׬(`hq׮3Q\u647Wq>wp7]qWM緺R!8&Jz`~?7^̏ǂeq@GXZn˨kuids 0yc,me*P3nN#2ۻ63H_Y:ծڈtj<㜁܎{ `XdAM~AQ@ʮѹ*RN8zK}>dnT?w|Zy{4qoh$$8i])+\aqxp[Zy>Vѳ0%vrXc=ZŤX$bGH a2D;-=qc=JZX twӊ4y !0uln ~kVklքHVb_oggn$ A $c%^(5 kA1?1yvxq3֣ӵH aؑH:WP$|fm}-䖲yEԹ̏9?8isE=F%F)9x hJ{]>[ES'I|{ީz|1z.}泪Ъ p8䜞z1y1Cf^rךωn-泵Ӣihb1r9,{UIЙMEPxat,1⥙~Y^+dh(X0s=k{@mSJ쵋##>!DY* x=Z|1%6yTrVEm :\{{ӢӮ%ǦNCj 4<~I"^Cy!1FV``P8QtmY`d)#jakh7gGC[Q_71 ܲGjzZYKssr"0nmJFz g},P[-#\DH#Y2%H"{*ƻ B 9$gguɉ(g)p dQCxKY\A# Z+7=xӸ=Ɠ4w$ 쫰11aVQߨ7G$쁁fP(wN@Ng"*I#*ԯ#6r~&duIcVu [zsdS]/f,Ȳ@Y2cosgak}Ɵ [/i)q{15yeY6ki4vV/2Bf?wm^?!K∤bg1KʪJ7>hLp: iMi_lmg Ty%Ug$ӥ1-d1 NH#\6G"F4(FHpq\?Hb:~pU[s;Fu ֆd)X6\l+٦:Z|@DXY[jOF Kg#RQͬu-o[8曈cdQ@~xT"H2߁O~=-_'@{Rrm'A'&Zeͯa}-c|i_w1oR;r1UԽՎ?Zu+WU(@J 1^sG+T`5kmSQph<΄xo==z5uV5pü vS!é<psЌsVi5;|5 8=#8גyesDɋh}Nr\ 8@XX C*;וGqVƽu9>9r$d!#q+s3p{W]s=F̷%IG ,pO'gIY#W, ,ZE4M.Md]j(K[Cja=ךXO;m9 ͎b w/s&÷$ U.m^ddHہpI<3söjk{+?]<^C^ # 3iWG$LnluzrEq>&rȪc'* _Nnoc$Kǟ*y䄑4+ޭsw/#Ʃsj 䃐T{sOh]Fv H9:rx5ww_uj Pp$mP#@Iz$Lʭp̱(8ϥAE3}$'S@UI [5cϮ#P$ /LrBmdm }N]ovhH ن R@ymo >:)Π>*Yg>nb(Ǻ v ;zRE'90CѠfJny\RO&+)1k`P/F ˱#cO7R($B_"y,9hY g'I4nr 3v)!F@=vwIMYAsCg׀k'-s ܲ}.A`yu9$:dz\\[E< R:x8=(a, PN3~3j1e)ָh<~|8z:s*I~3޹+/#-0PI_O$<-mmC ʪem 1?uOG /a2HXA9<⋇fu?䢹 ^jRjsxrB8tBs(qHn%g2f8$  ?oӅXݠJDf b%\ s#1%)|+c$q5֡4o`-*(<a RڈĢ\ UFsT`  7,VX-ҫ; y8<~j׌#y,V^"xp.eH)Ǖ<-o35Wy] ;IiH ,I=>f:ll I cV'ZTux$_TmȚ*MNR)B*+bߞwo\gns8Ojo x#LX {~o8mʼn3Ĝ9$76z}'wH`1Hۘ5zPy:?0c)nE?6ڌiS].A۞:@G3RCk=fԙ,yG^~ ܾ#Hr[X3; PI EG{Q;qq $cyQ]֜ =A}sJ&x lZLC&c+C7P0z==F@workbench-1.1.1/src/Resources/HelpFiles/Toolbar/All_View/Volume_ID_button.png000066400000000000000000000143761255417355300271650ustar00rootroot00000000000000PNG  IHDRK47iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:(7V,^^^(GyXu'f>uo7%ڊyˎMG?}q 9Kut3in:RSYEwr?]*{Yg3 )*F莮5zQ32Q_/<sWכ~-\¤rKLԥ\E))ylxhB.|QaљOG.mKS(.+_whx999Vey'Ox~_}:—?43Ku+$hЙGtppH[|ZEc'2s{OdV4C =tilOٻ|ؼ N(@ԖUY,޽ sCG_z: ~S8ո]%?tޑͪoՇgJɆ׊&+mL&vUULXxku=@ɸoD-}OangSRoe4C"Y2=*Ⱦ޷MȒ?]Ƭ@T((oUPV-gN\2edu''q̣OWOc269i &9,0cW1~eǫ^x^g4h]:4h<k3v^(& ZvFLOwrD?Ϙ`Fx%5umgl᫼=mbkKwѣ ussss£Rt&%晦߳),wυ^ {S*6l j$`L&!07SW'r&QrK\:4X 7gx m;~2ENw#USDuA@J&w-!DX˭FzQkIʽ)?'򈆡kԮiq Qm։3{Gt h֢S߹5?Lb45]=eϹ`DAgĉ['[>t]x`ʌk9[L2}7ed()) m FQS! 0jԠVsnr/f%1cFuB8㕫WsB!spʺ_3Lx\7Uʺu\vAz[n7~m<޸5/\8iN\!D{J4-u,=MHS]Y/: m.q8>7 j4?IR3zܹ[wiԖHHȽ:/ Z49׍]Ms,rF! el`,"F~@Rh4QDdвL]&Y6yȑ<*q5Tv{ˏZe;%!x޾1Bds=y{N3g;ߤͱ;furwt:aGPD<߽{q;*MQb4&.FҒ-:Ia\“}L®AI='%YSu )&)g'.i$򼐇ѳ:M6I2 QPwaq;+@#uO.|}0!3.l ĺNxo 48m/3tgVpoTKqV`Вjuwz`7ΎgWסּ);}=egӆѽ[JjRFO_F-sy5#㗄cLz񵪪6lT<]$#ppd 0N<<|;fU1NyoL2kԺ6_]Iѣh7RNTXT*JU8ӓ㸼Ekɟg8=CN^J@;($5]Ҫ< F=N>V{ CoݻszpҌ;^x-݉14<+k=7@+GwOKz ,0ܹkc^*k"50h̽{?XSE#yڜx߬8/lD ; ;Lb ?0#es͝7xt91똦{]?=[ʶtt'&M63򺭰V")C}K{A",'tf ݉G`+^tr.8Wof@_ƤamU[ 4JQʔRމͳݽ ]x}CO._ש-f Eދ>mX@%^M QhFy"a~ֹ֟ƣPeN YS3)u.ݻw9jt g;L 9D"Z )!MhL dJC V[R=EvMv})|e ๛֎SG D)+iek) k1>Vegg[q\]E#JP%)U.GmУkPԃ> o,y, }Z9B^ фwDj5ЦM ZpE%E-Z\5B};c$HB0_hAG4v _}czLGb_ua 𧷔1-( LG.^kv$!xɨMo~],K܍jhҠw5tp:Ǯ*!D~H[x+|te'?jҸÀm@uJ7$͋g@zmk 31ngCIe\V"Bz ^Ǜ#Sk?JR<~:< e$m΄}z{6.ܣjOÖ QeS|o?ߖV_GWrgߗe48_}aD@m>Pa_.iy㰨4!b6"Bya[zyf=Q w:88888rܝ i{N֭Gk"Du;A)SZHXGm\.J=ia_{~?gQu 댳1W,(HG kND!x^3:#&tXvz r E+m?t_hT~4}>>=:[0t%hGo8C,׍2ڼ߸@D/yf33~;ZET ذQ H}瞍23ȯAy)#wkrgW%,꫞/UZ(.cm>۴s8mƍw ê.bDmBs:11aOOOR-׈Y KE2 h'g0, Z"j%+?Q*j uy@{`JP(5_ AttrRVOs,U9̀%3}| EeD O䛩2O}54"[RPdkxN U,=w:-UR:'R&a>o]u̙="}Lj)}t7qC҃T ?c dKNG;W] (,]|Y2pO/ =R߁w!T*J\BFR5?LO~OR;4Z=JKwe-aEMO+H6ϒo/wiR%1AZHT 骧74.=m+VP*4]7eZn.)((طoߥKrss!B5k֫W/LVJcUq@UܝHPzP᪸WOj߁[mXi#~ J@>╜4<RsGET\h K tR'ybCpRY~>Pkcgi* :@04ZuDh`0iy#|UY=Ʃ̹>rV{IQ&wI V{& s&dlh=I(n>]\\&6/둖Rol鵘Vi4ZDײׯK/gdd%#`4Z>FT(StyYyZk1 .Ogp`!^V3VZaF`y|MUU+5쯿*JGi~XBz˲}h۷o%[ef݌;~f#u:hЦߕ+Q焐k׮1 Svms!EQNNNڵ#_\nڬ]n]I#On_n֬Yv휜();Ք |ŧ[֪ obs# eoq:Oȝ$6c/R\:;2_"D"(((~zxx802֭d'P±׹sUVTN0RI8t R/Ċ9[PXv]zxx\|_H$UV7n"cmaÆ#ACN3FQ BOO{8'''eu:]Uix6XWAx]IwP(tuuoҤIvv6`Rtppd7c)'Ostt|DaÆ @͋wZ)ce5rjQ 0raa@oFٽ{w]\\8eF&YذQB]Vd@P6k֬Yf ui(bիǎ;~ɓ'aaÆТq,Rr}7_"[X'3"ʱu>Ξ֖Bb10 r};˲e1,KrfRV'OK}l@ +v,^|ȑ#u;v 'Oٳ֭[ӦM|N~5?mTV* >g%|rɜM6ٳ9gMV嗕%u3?}e]'G;E,|7чFE?Qr4CQTmC<bsZRpSgܼ-=(D|[.??v{c΢χrDz1c,Dz,ej,R\>}5\\ EQ4MBXSDΝO:UVZjz3OoGf]? KYXY׎& ikU3};fjŞͭ^zUʡh4?Yݿ FQ,gEQnnnÇ{4mUeU'5_kц>ͤ4̋;"@k'5Swwф07?x;V} h.m;>D9 CԖMCC3eVR;@|%Ë,-䷣*{ȄmlY=~:9KJLpGV9mJule٣G,ۿfw;ZH s%ϙ# 0 dz_w;IJ2`5-o:~1cƍ*o8qb\\\BB˲%=|S-4Oxի5z 4pwwgF NF` ssskܸ 'GSMrz4 /CxV*EV7޽;uy0f _~3+WT3t7n bh0UV tFcaIΝ_`ȪB1^MJ5cVR>8w{_跫7w_/"zå([߯i1tYK/I'=$m6΄Bo¾WM# ݛ¹v͛k {I6] tjbГ87noY5<3H (@@QM(J 뷫Jv0̵k|}}a0E2E0FGrtֱѱXM_q9.[]0L|gqww<ӂb4͆/vY*-wZM8 kĤAqyyyNNNb8^+eYD"qttTTyyyNNN3u͂o!knG\owǽ:O ;OkD"2>pOz+⷇En[yגyP7:׎>3aL::\dz)\Bc&Q"6L?d̃U^aUyX#=]֝, >= M;bM͞eKv͘vʼeC᳐ ^żngޠia`u5ڷ>}С58$Pe4H/z I42|Kl_Rf:r UKWGGo`I?ٚ1WrDo>*#/,˞_ヨI62Czًbc,׭gXM1^paoإK(p2]K~עE uM9!.L٢JU%kGm{u [D][&B7=D(0_ټV& bX,J0LJJL& g3'`ĈB0EaS7|P ڲu Eģ 'jW.^5DGģ&gK+PʊvE`JpO G$3}e2-s_cز;#ߥP2iy0ܚ>azjOcʄfcE{ ,ݜ3per*Fn8x|;̉3/M>F{ __'XƄl8^LVEvHZY;wܹs#i9 "ӨĂ$>ÆI[4jԬU~?fkC G$7 0Z^]&01x԰6mCg y{`C6%dд3n1dɸr@;wdWN~n'pi]?Wͫ5 4M?\B,JˆiVk0$e88z"+?߶E}, ꚁ8N"Cch6XvTr=0 10ሁan߆gn502rz9}/|{q!=~h,n&E WBҩ̚a6ڷ> <LBP׫Y's } Xao]6mVBW6aѓGKt%=t:D"Tj!eYt:˲VȆp>$xٴ>ހYyJMvr4:'vS7%xP̥ ag]_~LJ7Z;b%&.aMU@izymհ@oɿ͞w|@o_i,r<&tQ@ޱhA41cPqnXv6/8y4?r{!ыzZ8F$JNڷq t{dowRRT5j673eĢg*'C9#ɮų0r7 .uOw'v~KYz caځZmȟMVZUP95MPTS'5oC1xg`Ӈ-LhBBat;ES.^Xc*0Tǹ!*ɀVm? t<{-%;"k8枻}VOǫÚ@k׮^zyzzmI]ZP0 ͗n_#EQL8vՇ.|wuv/ڮsR|<8P$c]1+ʦMnݺt)_B͛6mTZ(D!޽?Soom8Tot^7ީ)ѠPR:>lY҂}_5@Иi![/ܣj0f>^C$}z1yh0>Uwo^0&fgmXQ1F$\тͱVg4׿1<,^QyË>Z3fd׉X.E^߶A֓&td+P/ʡR}"*bB80|aFh4 t5m0a4Zu󆩁aS,XM.j@gFh4`NՖZ6=Iaw;cahn{Wä\&S>c8@ ;+&%%eddX5\IRX,1!x 3 WxÕ> t[RV^^VҦӀ@tG҂Yyzu@vNhp 2d;wow4N828iM5yyYg&?WT*mРۂ ߿_`Iyܹs]]]4hPlN1;SPf-4zpjڦ @;o]MH~+uO_MH:ۛpG*`ԝÚ"5vp$55upBѵcuY \mBG]n &oza--5_W>2x}=`{<6Ɔ!lUܻ>V1"}t$8;g9G_bfY:.3lJKJS<:ʙ3ioP8.uBsIi(]>0:<\͖qWAF.^^EaBrrr&ryxxR4h4ong"P§D@(K÷| hѢ_e}K )N PEP(E AQ@$͆c}+H|f%,% ~jOgP$"!SM v/xJ~dnj8͟La˷G-_m:sLLLT*%UE1 p[ X<=[I"$Ki(85P6]eBT$ @ZhR@&"TDIh MKP T+,3 ++ H$ ̟??""ۛpQ{xx4hР,A;Rr&]] n$OhOw_ҿR0'H * 1BF`C=<3zm.L 1BR!˓;ղ M]tqwwrʚ5k Xڭ[ƍרQ_E7چ``rcRaK;¦m*jU/w_O2IE%KL* #!|dr9LhiWXXN ez̶I!qxܴh=Cq !D"I*X*:7Խ_t}7ך GF#+^ժ\H*@3P,Cib[,gD:(ʜ"{bileyѪ| 5^8 ˔ke6Knqeeo?[O<Pvmeٴg mVa q B^iK`_!~]p.1 oC4E%h|PIDDKEB +mU[QD+uSHRRh;@Ԯ]ǏGDD4nܘc$&&:tM6m׮]xU&% S}M[7fX7}IRwnBa͚54i ATdiEfr4ɻ9Z pCҴſJXlmB)KQ gł´彇 Q<[كV6;}]Fs?9 -|h}¾M^9rVn'uTrD$a#3.:p E 7 !J|~v*#ES9,nXjyuXΈK. ԯ_@@q\Js>f  lJW.22>J݋:$Jký^RV-=n4M;99mƍ'N1gVi˫~NNNgw~%ﻳH+13~]U!b0rSO/1 (~7!-`VyPeUri |tT7;##2) q@#HU /++jPP(ڵGIUpp0o61۔%e<̩ނ]zTp8EQ4M]XR0P;woN٘U\xoL5t/zAK^  h98T Pf ݉!BY0| ÖoüA^ՕS6`̱y=h˗W'-m<ӻ(a'wqɤiДl3r`XZ (EUq=S*] 4`dw|*4/amPjyuǜ3_ta@W)hMV†cwl3|;}ghwCn4 ]OElЬ wfK=\5k`HOOD8D[4_P(QRlԨQeHB!^LbwҦd.o9b>AÇ-ɒC UpE"2\O&5EVrX(2G4 +:7a<iIixTJJJiYkpWPPj+Jpu;H}:!t:ݷ5нX"( KH0GݬL)Fjjj@@@a5NvS9؇mi_s|fXy<<ׯjN;{7b~9x]zeY_fMWW xqTuɒY|aA[Vf};J}dXyax>Pi͖z^fS!B{9WZW NV6l ozjbqggL-x=^N˂ b̿U&Y,;BHnnŋo޼- ֭ۼys;;AcY޽{NNNJ88{ !JF߯[e`b]s$-UC*q,hR5=ʆj ԫW2e3U»U5(_ѣ=zTZ@ #l^[eJ "11QR9::RFɓ' 䌨*lQBpڵk&p.h!"cZ[<Z5Eڪ`%<,^rѣ;v 'Oٳg]tiҤI*4WkM! CAAH$W|xsǰH$h4|p,R)H`w`1/K}i}% C=Ԑ@1w ۿVH{kQؓ7(N?ON•,0͛{ ;hR8zE@'S.W>w;Mʁ(;;; -TZHRZ]vڶmkZlQ4iӦ={j3D HT֙Z}}=o^:thU̓#,^tƎt2!C:wj*e5kVdl~3?io=n\IY`0H$~rgZH"!@&I$ JK>9Sfku !395lQę{:5Q]Lmܥ>Θtksxߝj ogq4[RZϻ0~Z'o0-SK;tt;~N3x4yK !!3/Fױg=Ń9ȸ|vv=dqԳ ^П~5IPic``7ϒ9#<==ׯ/mUeU*轍9.77A۷oѢE=5k4ȜW`]!_ըQG`:zpq1 |(!eYad2YΝO:ZV-KvOYopP).9nʎ([b{) PȇߙS{U@&MrQ^N+|͞ݛ Ǜa]O쬇OgHN׷űϮ_˧bI*Jk,Z;4],gMV嗕 @ SΨQͭq󏓓eMu~j|dr`Bߥ}AT摔6m"r S+MHx$"P.*i7 ܨٕqo_;b@VE0js@wSu{nX?+ZkJ7{tnlk2\i?e3@.&ʆ O^31U$Բ4<@nnǏjL&+((8N@ q,tz\~Ν"ԝ? 3&I!+8/Nfϗ=:Ɓa̛@ꁹc> F.Z;wQTk^6 Z@ D* " M""p آAUA@ lI6dwl܏w3;3MA3^ցG7-["?oʩ_ ¢p-dJkla0XsƗ:-;Gں}!% 4tvXA' 0v=KR]>}'Nn @bOĸؠAܝYg8NDATD"q`ybBT*. |% Oebنeٳ_dK~Qz̡m+e ?YVu8|7ߏ$ _7wy _#[RɮÝtg^~lⲖ)D\g3_`0p&jKlP>7X%bLϲH{,,)GFMwljMڷ n.%A0 "1?<>̨*ʿGZ̏ȥ"11)'S߾Ԫ(?-BCDl>ֲ '#8z~Æ ^q*K#"ѨT*åRP(Igjb888XTV#8p`î3OJ:e 1Hsq㒽~VD4tkٛzж'_ힺ"d=TFLo4ۛF[1ivO|4|EB 7"v7>Q&Hv={֜鑫cYԩS[i6OD*2+""HHDMor%YDuT #Q_,󓙪=NҨtP"H\V?9Jb(:i:K5 `RRRF5Gԧ&Zu7 oShg}kKqUn&P( jo2 #6mڵVZ"}oq3->h_jW?;s  u̙tۉts;Lx R<3sjNjS $""M63w****((> {N4|!%""O3l0>?ܗj""Suw׳U]`q5Ӕl ,Dxܻb,-I$.[^Ν;˗/l߾ցdg/,dܯyA~kQQUY>W@9єʗ*{l^bƮ1!]٣_/b?;p)N?쳶W UubXXeZ}ҥ9szV֯_?uT@@`4^ZVryքTFo#*"-9wG5m}Yg9믿ܹSe=/88gϞ͚5{ז,YrرǏ/[lܹ={ k+mD_|<0)<00O<3`{g1jZ+ N7t:ݮ]?+}Nz>88h4ꚛzqZy&u<*S^ ~v|i'5VW>p}*$jBpaӾ5nMݝPg8[eUYf}m֬ٹs>#n5PؤIƆҴ7Nr o)]K J/(igL`^tX?`0d2^oYv~?Ȳ;wfx;i p#.$2/ DGD!%:3QTTZZJD~~~~~~R拮}.sw35l]pɖ˯w''wܾVvCZ & 棕b^S .gFH$zA0ߤrz}jj̙3K$.N{?mA}8Ti5CiwOڝNH2e_I_t CaS?@T'K^\ߖLcV3~^N49O)߱4)TP=䕧0ԏm~5v/d;WJT6_\ x<\ަM6mpm\ESJ[SKԗ~eV7Hvmnoior-޸=}{j 4z+964s3R͍0 tp˫ܺ>`kq.~0 _ojqӧϘ1cM3fFTcMO|Ʌ%]ֳn]y25H2pZ1KMla]x`awfͬW~|˙%z'H0LI,i?,X3)s֎ޱ <~/ST̫^A]krxD UPk ڙ^kȘJ;w,6REibDv"͹˃;3?K&=oC:mE] ɡ9MimB[t?HHkΤd;igjC t]ю/<<46,,k5aYСC7o裏֮]W_>|+*-mLFi\^æsGq~|җv,t.S_=0ݸݗm^m8m!GFQ/^/ҝ={vK㗾8祉^"WwHYPfЋN|ͥ?(tQ?vk7(­+5մ2/=VJDt)4/vlmWD~EMbzraJ5tR_νڷ~jYݭ+/ܠJG;aYO>_¡GEʈ"!i<CmĐťɪ@$0 Ca(c`hO=ScLgjk62Ւek 8[s^/JYV^rYfѦM233zZwĩ=)ܝS#G΢fY^-3{eˊ]l6m8-_v6#3u91*J/p^qy_ /KZ}7823E"aa`5aD"ż群~KGt]Vn]bq8p{UUf5dDD#gҎ76:0 GW?J/bFyW)cb(pfAۧS^%WUwAfЎWb:w%iSywRyV=LfiebPgq`0;wfΜ5kV&M(<$$d„ s_ϝ;g5܎Y&gSO[Y3È6$[ߧ~SCZ&<{y`>1Dxxp3Uk.n\ WťG(*5Q~J^p8 ?}yq}ZtGm2 eΪMok׾O|rn>*μxJJsN$ + )Ntu"#W8'A=.LODcx[;^?짺53J=[Wr8\ZxdNвڡD_yP0o=L.7y0/W=نġ1 7hji,<# |ny& bFL\葷ίn'˛ƼɉD0Z>$,(ivC~ ݙq.Az3poj,8q"9994,L2 يJҔGEnjƹi)Mp- D͈Ģ'c̊yꣽ~n~+w''y0{UD6_Z'6"HIE|?wy#"|lH$P32Q*kWz!31K+ ;yNG\E\EX1a"U6VZ]nN[P%w5'wŔ]N4p(Pn!jj񩶤$"Yz.kYgi.`^x(Ǜ/0?]Ri$~~!UiO-Uޚ^mzţK20c-ߠ|RUY ܯoNjϘH^hL ,t:5FDD$$$9s;qږC/6tA8x }4.ҰDDso>9䕯&wХY+RuAEm@ #y.Ib_"$BAHĻG{"zdԈ&z#+,u..KמanD3tD;jr͵NW3~%"qᇥf"+Q̸XFDDÉi-rSò,a S3j)=b"`ը?W#:H ;m8stL~,].X^j߿,2BmUgj;3VożqJZvB"HݽN#ҏVW9Tt8='1ʮN!*Hՙ͊ v긢‘-fgffr۵k71bqIDJO~t`_׽DﲚVW8Ŧ{ 꺡ۛ4Ӷna^&M4i} 5**(((88X$FFQkZVh4ZbBRj~~zr1c'-)x'S+_ ʰMսg &b!ADmSFgxd,JD"ƿyԥKރOkLB/ SHÂLDSW^{!`~X(L! k"sa2p))C:PV@kOo);>8n3-/'酯KH;cEߝ[J>6:~4VJ$xxʜLXL|LVrRkFEDȭ1c*^TQ[+))Ç 0#K}_>K6n]ʱp=j18@.ϹzkZ7FR.JB@ ZUX,V*Fo;$~M?w  8f ݞS 5$$ůX+gk1ƏSG/bL1 ſ&w %|aHeV!2LkP5m֍6r~+m6̒nnِwP+㥓/rURgӮE6>tch|'!7&wmiҤ?u!ChтFVDAZ5cKw).Wqg5֕W.cf)f/ʻe#q@lgռ'$k".Q^p^5ߣ 2 ]Ɋ$ u4vBaϮ;3͛%$ 3j;tdX!+d2w8p`NNΰaøW]x:m|M~x!!!T1 H$+WtUܿi8h999ݺudBf52:H ~GϘ֧5kU~<*Y ߜ6V{\f_dK:,Ji/̎e +..Hl4"""ꔓ]ֳnTWOFhP,66mrRB0? 1  /ܽ{S MM,KҬ@B! M!h¬,TW5 IhlEx͵r<>>??>ô0h4,qD\nCbfmϬ,a>#DE?x>a,=K"kرcݺuk޼"c0_~1euVH{X𸁴oߦZ.]tQQQ!޹sgٲeM6m׮]?^xAgxap|83pV yNd>֭ۥK~bS>x-Zm688fW4NL%</88gϞ뫯0p8"pž={233zE#Mմ2u!m(6^r^pp05mڔnݺU@w=RIDr<((H.KR<5]cirL& :x<>Vgjan><2{0x """(// xGIIə3grrrJJJ˖-;vá:"8 Y!p2Pl\xg`0?ѣmڴ={vXX}Wzk܃Q-6[[,Dxyyyg CVV1k6mۚL0!%%?dY6>>fw\"Qkޟ޻Sr:st[o:<μeа0(KiRiJJѣGš4ib\kso7,:+kұω^ոxYk4zᙰ,{%FP(4ӧcbb"##zFx\ǽ e AAACT߹AD-3(1q:,QRVRv@G!<NFp5}]^^F>hB*i4VMht:ZdU%r+,8ܻx;/ss;bN݉ #Nf;-oK*݄r<^O-ΪXo Q&F43*4r"} B0*JPD"Ѩh4Oxk]Y}V8o6 g|3H[RXĉw_@DōmٿdžIsygoľz( <~xYOѨT*åRP(dx\+-3bqppR4yV[ÿxk{_ҎX';]RO~n:^QKuIr'g #6DtwD)}:i{ zcWN~x7ogM_,˲Jt<O pAiXjZ pT:x;)#~6>d{9[s"2DeY"()>Nct,+""-OZQLL r"Qb b:KCx/e^PPPPPPddddd$xe2'y&ΙԾD[b{nRY|'3k\KD2,..nӦMF72 õ۲,qqq2YnY̛Hr&OXuƪ-QY3hǒ skȢITb#-_xahɄɃ'4Y4'NLJYKmHNyjWxcpHyrB-Dx80q-7o4_h4矟9//oQQQs,{ƍ%K4i$%%%$$z+FUR$ Q)5PU/ɠ*V8@V>X/eb#+LVh{n޽ٳBxogu,V 4-x GG};t0px":={Μ9ߣGBaX[bY@C/, '""W*2 ͛7-V DDD;""|N#"P޷oߘra9jj6 &M4i$995},mQQkNaac7np͵퐐 8(6^B+mf͈ƍDa ^r+K]p q~xT\{uW'##Ðu'#8K>͵Vwu{i[XKۼyshXZc˲/_h4\o<#Ðu'#8KYhE,W^ZμE-ci/_\RR⚤Sz/k׸Z2u'#8KYhmѢ]v\"˭/_.--uqR),DxYFr͵-l`Y6''GrW3Ðu'#8Kٚ-͵ި:rrrJNa^nnZ&4WpCdx)ciٖ-[QnnKSS{i\ ulExZ"jՊ^JD2ODȄz@x/eu,JrqR),DxW^E,zd^e;]2jת 3e?\v]zE4bMGr;8XWׂb"JTVVFkɽ;@Q\Y*i_IIӳtnPp6nO TEb.l=2^5׿q+|b|c -UV\,IRWE4uk2 +hY` ܡM7=&ҋZƵ~0]-)Qd+0m{NX.D4mM#sy..c&w3vK?6VJllTפ֚Nϔ;߻klGNDdt-e|XC6^5`'*ն!nO.dGRkKn܇+M^}%GE<.#_9GP (} ?9;e+esvn%({+IIIIIIcòlc,˲R&r(OͲ,Y5vlZ!kʩn9Q6(r)O$,k4hnݺ([nIcSO,˲S[W%zUSo=jlR?Χ}'(㙇.Jz i^?5i̢WY{U3n' *p'3mjy*>9_o8u`'w1}w9mcJ+Sy~ؤU+O.//fVE yrIgѣJ:V9wb~Leۓ4Wv2[nݒn>Zݫ./_G}+IS(5lP9+cҲbS^SLd4-Y.!d MIݺuVxXO"H$('DDN쏏\QC)zQDd(]|{kMٹf^Sf,Կ HyDT|~jvH -=ⷢx}MVoX3w?iJlزsy3VM U^s [nQ]$Qfo7OD+MCDD׏|СPz/#us/-]a7͟Y"\3乥1K7ٙ:6u;FL*c~G{æ$Rǿ_?9sdFHg)6' ߼9}U;iiS""ե~pöikl(**[ET߉!oO\R/{݆usdzG칢"v|C靃֋kanʦ6QÜ>r !Rƪ)5npJ-59cAD鴏 ӗB"R]uw~0n+%Rd9ND~ @tz:x[YY%|:{ɽ""Y" 8\JDgoD×|}Lα YnCKΫ@KSŒ|"=;M|C[v>rޔql1~ +!TCNCSƾ<˘wg$ߖO{6 0rұ림³ ӣW|64RWACbf-:""/ޢhjL<Ċn%*('h_ӹC1/Mñ܊ =[9#t";2,szdpccX5*!6PxGD$n)~2Rc;M\X狈hD+{7-tDDeVƍS#֠-# N~DDf<1ѦSPDuH8t\GtXDD?\~nM'eLq:4Ϊe,?t"㷯$M.eq-j) SedZ{Ƞ+##MDDak\bz =e=yXU͝`o,ggIS.2b)EYޘj'k% qJ$V[\ n3Ri5j j%*}R{ImGiŒ7g-<  &=akNWmA+R6#Y\c&L73(q^wMDz+Bou\`<;ʘЖu=vNғWǶ-~iݛѥkEGSҍ~C_(%]Ju?>Ͻ7eqg18j[YITD ہPi9yΈϿSW>xr;~5UB, #-\C*if4egSā1NDDIJ4DJe3.P,ۤKh Jٌ5K@ѓjaʩn }蕙o(^rb,HdԱEN}4D2˲mr9_=maf^Ac2fIkQw*PiA0<џe{ ' 2 kpց\_y'WOygNi,[|KOdW6^XDz͘9ٛ?pX,9u*GϲSN]5jID"lk`Ǧp=[>#'v8u-ܪI4XPu!Ŧc$Y+!npJ-ϤD4Oe+_\Qo:mW~zH>F>=?""E°D7{d4Gg>޼drUاO!#',ZT@$% ?hb":rȩaf&Rƌ!]=1MҧώGT98d"V,+#Jo>#HQD$|t>} 0u=\\rż}Pn3?UA.^?w1"'s?_>=uY ϟ>C&HgA%V/'"m)Q4}7/Ϙn|"jʘ?aӳ̙3~Y:#řKܣM3Ώ̪\?~iE*2کT޽{. B/WzT%*_'DE"M %%*H&۝1EOL$0 IDATټvt, {۴ 9Q*)kPRE2 !e6d6ߡARi/n,<x`sPP第ebql68AA?kWk yzd|ixaqqoZ\ޟnߦO>%KܝzC?F;ݝXbw'kݚ)³M"!݉9z= R<=>1Wl;"w_;@رZ}r$磔+H$TVDC?<"D^\؁'}{^_;۷sf"rwGO5nx[Tl,j$^Dxާ)$N ;at Ǐ֭?z8ÐFD8Cu'QQ\q@"%Pgfn[v"(V[XTtTQgm98Aڎ6lf͈UBH P˩**ʬY[x<y:ND48rw"LN2H(tw"FZx<@H(:"<~Lj Nl!l𪋚c|5ԟZMC;Uy]ab<݉9'!s Go*0[ TuTJQNGm|Hx ra:/+ܝ"<݉h<; V߯2\*%q#0~ iQ|z,f#Pc-[Fyyn8PH嵶ַyVr@ }GGTA]!h9ej4:qK$>,5`#bb꼕'-;vvrs܊#?_o@W`n5!5sw"1qJL}O.7̙w ڡoJ덶mS~LII埽Wi\م"j5ɞ|S"\w:rssfcODBխt:~xee*I-\Is rHu?<AGzpIWp (%ȱD6DmP(|싵nRy]}fKqCn$t+8x"g:J$w8mo ux"Qц]3*ׁ[nL.I/+@ߚw}ץ edTY-{0-, Ա9٢Et8ԵJ$TVfkgU$%QtsveM/xEvޘ*d?wDze^`FOVZ~_n}*KK hܯ R` ) 5kk#ެVoN.[OӦV+k;um@N)\S&^筤Z@5pS-8|Qs`ZiBYnd+O7Y=:W޲m?y?2 5T+x̡_<ePH߭u֎ ag klםƄh$mGHj=PXY{_[i'O(wDx7K$ [G*xӼNӹg&ZyduQȥRgd T:gWP%RU:il VXAAts?Ժ+[fCXp+ԣ# r#CCrk!!S{XMEWƃ>۵Z6kݳ#ΚE.mWҨ%C.+'駟+͇נCE pgŤDb6D$"؃Nku+qqU˶Rߜj;NBn."h_[O*- %Ŭ=h;-Pk :ژǫ*c0<@ ciMg,]/9Sw5 [ax#/uJ̀Z}6ԦM-{j<ÍWAAodhHL+ׁA| uxSRq#zәƵp=v sK {ԱݶbiO^-DZv4*9SlëM:Tֱq.뇇 Z1r'$4]3JT#~D#3S8VKmxŸL]EQ(nZ]Uf{5\}Z=?ۓrǮBae^][?n*)4qṠZg/b4:aMsicY7\(烄bJW#3M@]l_n""Hs5MǔSCx&|c:|^{p}CBaeO%{5)=8\R=&QXDګla;wW'gV6JլD$U+UiQ>uD%%eKGfr97r \Kbio\TuJ+;{2=5y͈[ crrF#8=]3:ˍy}Qb[ikȰ\G|7 KyyBaZ[id9┗: ̓20oJJn~ V=֋`/v9 GZֱXwWm x"w"րbbۮ*1H2Yey/mi)uVz64doMե5M7u:j),zTZٽZ"4biۖޮ96MeK7GG8|=J.]>4/">!U;Ol'8{O9Guk˟׵RS@U)ZMM:9z mzLaS Rթ\$^_K)ڷ(5=Q;6bmMU/e˚iZGndY;p5.VZ!ޮSZGnn)k!{[+(Mb Ihyeeič=rʦu-:O<5##0Y_{n1L"0:<;L;6zzpWJ7Zᥥщu>n]k)ux|>9-WWkKibaC5-veuٞ Q*uR[#Sq1Mjy;>c#턫D惵>"Qe|=_ՉhV Obi^+2zE矆=n5Meu=%nltW^2δYܶNզ1;;/qqTXuxee7Dw5Ri*MVYl_vgiÉA=ڴW|I^Nϔܬኢ겆ZvZB^ $U*hjatjffS,lԖdFVD.|nʕ"ԩӈzBTr_mN '&~X5r"{Znjhz߯ GU40`硿?ޓ5c% #KsgvmՅC^~@Ш]Z*eV#[=ـ)2y;<}rtn@JQEc3W׋2Hr| ~^\.XHH!й'~QBw7|p8CJI2=T$e"^.4.ks2+I~Dt++ ۷׶-cL GPcW 5jx,9VB+0^o&ĭyò Ե!3-Q[Bm 4 -wCQ[Q2rW ժۻ;Ͼkd6]+4c>$Ihqtg@2$G\VV[kx+%uBLl^ڋ..90@T08^T!kxoІ Mī)\v . .[^{ Bj-^Hk@IfDɆȘfѣGWwvn8r+N$LdA>Ը}DІ 49[kx*Z$4)h硇{#tq2YCt ]&P$4b }&Ј2^fo E;gA <ɈrY>ڲEUekzhkxl=6Rqoss{]?%c >Q(K;;68c^Zc] >K [x9u+5m3^'gZsZ6o]K/%J l& p eK6B*ᙘiyu;qЗce::6t$wㇼjD]CBAڻm[]kq9 "ٻH:쟰9ˁ`h) x`|DOЙaMB.J>Aą]( '?B/-hJbx'N겎2OP u=M9'JX^fm$5F16y-qɇpZОj뢌O/Ț7tÓqx2i)I<ǜůP[--Q[uwҘKZ8<ĽN:p(_Uf @'\.Jcߚ?c;v8m.ZJe> 79R7Bw; ^ ]ZG=GD7 mVpzS WhD,‘ BT^ )e0H/liɉ%7 V< 9yn22|P? 5P&YB'w`2@n8:ziWW}˙ JifАX^.-z{mi(6IKsAҮW כno'r4䌃Ü>=L/ںf;_$rU*Z\39" U*[tnγb@ Ń:Jr}OtW.mmգG &hy* SJ7I 9\KB< %s G$t7g>01dF| dkhfgohƢghDjhU~p# lr*W!V14#nÓgA!s TV~޵dZ=x5xG Kv*GҨ$ޖ-ڟg1"B:"-P3s-y{#r/g*>9zwU49ikwiDOm\OKKdKϙMVdxekܿUI*OKῪT6KK'K8WɓMf1A)WћDsskW;5Ư@AlޱV\`'dÆ63XWWcJ &p &c::JJ2&C}}֩1;[ZԄ5~]GG$:8 ƼukN MiMS-I*q ܴV~H׏kԚbKO.|z{5UѪ ٻ׺2Bqt?ob44D}}S׿>6y뇕j)MfkuՖ8@r3--f`8/ Z\8u"71ï~G~4 ,\k/5G*DT+L.w޿.,8<),l^3~08h}RgK^8+XhhȚȡRW_Gԩo3EIbujCޮ5fĕ`.st;>;;ZR$"46#]Rj214Ԇ}J6;j)g؅ ЉMP%=lb^SJŦn5U 7G4mؐ `C+q#5$Pv|? Dp}RIgE{  w 7a,t5XXm5G[-,yDmqx49M$f{;%0ߟ3R1Ҙ!  5?#4&z)ڷ/2Tm&n/.ϥ/IϘ[[ jy[>MA|| ML*yxÃƮ+ew47G~5u'R-0<'`أLHEב*z()0|q\38jDPB}]Bbg%-eRZJ!"sQg2d MMy-҇th(X5 15u^{s)dq R^mml P&$.^eW7j6}R?^^{mnsʢi$STRRƻʍ(=^ /$x#@ŘloËwa:i!i|<\*y\ /䋐3q\-.Ro/j DDz,<--3Ҷ sVN-bxDsEi waj酘6_wo n:/VVEl" !w.ȃ Ë|֭4=vtB 2.bڬ}䢰!(䠠,Ènie&5w Aιq~ `IB&2-8r k4︃vGFhƜ\w1w1VaQA (Rv%g?Kl 9%YJU@ O=d[ZJ8q*58B|U0ӢVTleHxxiCqx6;kCw@e'2 4Ga8Zzl^;BX"$Íg=e0Nmm:"wy`dOC`x\@|eٗ[t3e#9qگ)ve~>k$\f(.{kW&lTGܰZR5eᡇgwx{{,HPacxM{Hvye?n&PMk[""ZZ1ih?LɇyBjC25T[$o^‚G܃j%aVM Z^d7QGd}ݱc6&dhȞ2#cn/n4.1 c0iaW$P-#J 1U(ЂRHjѕ˜lYd"4<[~xsd ۶B aKms7Kf/TYki C9>)Nx ʭ4F}Y&FLz,`YHe0дJJ/C2uur9B}r`D&Q2Žw::ph0S Ncɻ$O}~=[9Q|"EHToǎ2t tᅞ 98 -EN]ted ?n[yRv'&rJ' ( ,nUBmAtL YZ"U(RNT m,ܺ՚&'?+p D\J9jY %WE&D:va IӺR-/--l={WvΒShD$S$ J:vYÓ[\ªN>2\L̐pѬnAxʠxt1%:%sA$Jd2n'25<^>fՐ X&I0z͛ ^DD^jΗѩDb|8:e;%{YrE4*1W Sb1+#sJv#G(4Zmxzv#eG,R ǏӍ7eWOOي==44!אvh(7Ŝɯni)&rCdtI2i8,.aGgB{fef>qiRX #t-z*I9}vom^@\P9t錨q^וWs^]07}s\ZPd \?3@q$vBnjp!&l H҆6n&SHW䦛ACa c8,=@{y/`jYF1-ZWyG2AjcwuɀKB OVik,2D:HR(CCC䓴iSVfB:!t ĨҒR/lCj'KE ʛ#> TXW懠' %p%kjrq2Z*]o b+De"ϗ^chV̀0fʨqZl./[/-㦩pRq>jƲA q*=(C8cKPvbwsϞ=t_ˠ.pH. ԝJݷ{̏ߠH^^Xj_qm+y1n=]>^I/mdeM < Rl \#:NR 悢2](;wxṂ*@?r[ eYq0<(BSWfWW~@XÓR #䫥4o41jډke)Gp!yJ7jgG2U (U@/O{ᜏEu4< (qAz~# a6X|@Gɻpg֘hn.'K2Ƥ^B5RϤl\}wy!e?n7P,ʹƒd}WlݚKcJ:y~=,In PNB!"Vhᅿ;r#ozӏ_?;4syI)Ṧ935w[HS ę+CUAuV=Խ`"9K,!xhȨ9OH5ؐ{D̅Rfn 4jd憐'g"&Aڨ.(8Qkl0ҒUEU*[ۯ PhŸx ͽ*"_nKj`610D!]+T*u6EwCkziiӧj%OJ; &OP T4zU""m<لSҟ"=LwĄ[mVdC48`F>ty1Ȋؑʴp_Kiڕwyi/-Jb(~ vNmo~USSV|[N7L򟗄S,%IVgXTiO(%s=:E_ Zh`7B m;{K(b`Ĭ dBcc47GnRgu$h%@A HF>2`勐"`x%ŏgx +d#Bkmbx|/t;jso8"I%8ڔQ;yLJoyglL&LNVb *]s3~媀=oҎۼwMI(D O x]_#o^݇**2y0R[̌s-?MJ2Cd :ED"ڬ'M9'rKzi] s@v4kuURC%rG†HљZ&gPq e {\b1:Rn#hoHta}^$A.J6쬽Ts3{ u5نcl8 ?[0I:OD;wjv db~z;HR,,XvNNJ2!6v61)UG 4Cw.G+P4ӓR/g?(VIbN0$4%4ZޮmdeHl Q蒥gM>K<|B]Ue--( `H~OK{{OKiѡ J?C(5Qp'y=?g7esĪ@8\??Ó{g`%3VVh/ \  hvR.٫{ݾ^$sCx{kW )OFf;:, Q漴2dC$M:vQ/;OHD/I:RNF%NʄhGBr",Z?**4+x8\ҩS6o5ԋVVv2kez kr?9&r,\}I%`xҪSd }7i\!DR+_I{ mNKx䤭PxbrPD IDAT+‡Ib*9|rɿ-xܜM:sy7+0|[Zl=ԅN%:uFdD%L2`IL#5 `E/=def#UeJX{EzY={hꆟ80R&ҝ`pÐPCFHxVSeiZ8*=?äᱚ%G鮻Tf' 8I5i!/;$G yD#1{dS0+Dn I8΅,'lÆ_J"/OJJPyd@I: P3I n|͝wҁ Sq 2nD<> N:@(Z\nn;0x5n.:x0O^ha!g[n?ΥµUTudg^#u6mGZ?D)CiO[}kgf(myBbiex(mzd ` _O'Rx~O|'c^ &'i,aX!W3LZ{)" P_gӄic)KyB5x))B:I3.<$AApr LYi*&yKKx 9RϕIs85E9:jݲXͥ% #HWc:x 2Ũ35?FL $)kbZup?ǤW}o!z{PJz>\1<$-#Z]]6R6[eFi2{O"S#d dÚeRd~7џ(/{6BgK9:J}}n ˹$1N]]tLaժeLxrI::ҍ3i?^G>BNi "^ 4>Nx - N@gཊEK>Jf+_:}3L)McBE࣠lah^#18WGB5T!̃D?qW+d*Ukmllݭy"0I*@7=4:j"噚إA?O{_]01A۶-,Gc%4<SeJ@TV|UϾ axH7Nи+X3uZ~ O%I"ט<Թ9FwWk3OYsu)*xjb[fj%L3-s۽9 38@*MNZ5D5ז5Nɧ|'hyٖ/Ъo/#Ue9,h%[xbYM2ཷcpHCZ-ei"b\y#^ .$42Bgjl,+}d9+qRc'UV/PK#Lpzao/IBL)WƘ`>7voqZ<'p3`K*',jk2$o_^6WX..a?ʵl_Y#^,w6O{Hp;8U Wx0rOSPҥ 26sBwI e,I",=+Fa꿺ZjÃdDdE:/†ʹXP8<7EIy[.O;:4{7:?5pCu^Hyi-l.ePn)>u+IEFwO]$*V$! ,Sj?d))r+*Hr$&㪢XL(LEC$PD.3ߓ^~^z;p AnCW'`+ߟgKjC9ó2iWLg!.رFpa.ǥe!Ҙ,f.I)zI$4$qĆ"<C㺴 _̅qx SIBYE7! _A6ڴ)\ n;ҡ-ayGE(: aKqA(Ǚ$ĉ/d 4_9YVxٝ'wr[9B_ﯙ.WYY8N=IJ8tIUU-el&' 2A 5 FiИ>WË0n+tPSOp'G`'JfR8|pZ eZQk_mWV4g¸[CY4lPbx%aSQ<<$h~LfHѴBUF@*[h`J j$Q忖AL kxmm /k}Ǝ(ʘ+59鐠Z^02'jo>~)&V_|E~FE5NG+v-ȖZ)F`F<BB|ޢ,ɬI!sI%YMJ]VVh^/`a QjHxO#bp}fd &'qDW cĉ" ~Hl,i!_RcswrgoL<A_nwv˅D/fIGm},Hg nq;2q]4b(PцI! h1@B) 1Ր\/dz\g@Bpuue<w2{ 0UQT=v"?Ska&-筤;kpIhx8 E:cd={ `@=r Oi4vx|_=̿?wG žX\>$w6ϝe2j7?qe٦˹&Ǯz!~&sE i [J>Z<]' /dY(qz:Wӻy[p`Ps!楱>6f'D1W*I=80jK~3R =}5KKd*`?0#54D/IDQC~/:ĭ)4_čnd[++F900l:|FCC4=2&s8Hns+9 bI Lj^Z(a=f"O2a03xi: RcIת R)ip&o=#gj)Ǐi0.(v Ǣ-*%"[bDn7˂\Uե$1cxh(EX\03#ĩZjthNַG΢А|r]6Nxxz{i6zAғgm8VCB/򲾹 d~ix'Ֆ"Wo|S<~;;7ހlS:!-^}?kE L]0#145#Е.WjN.)8V5{e ҰTN`b3?: ' J6< }x+Gf:O?X`LzBxrb \Ԥ-RM7Yl#nz35V4i4呃'<L:Ζᬾ1(@!Ixp^|M>7R5MkCss6 si™abO{%QSS V=穓i@4Z? |I*OJpiwhV`K⅖T$SD)ʹrk:;uD'>A[Hq!|[eVj]$teV0`YYA!$ se7>Wx;$%`psi!!٧>B =46454NJE%X>kn04Q"2f(vOF'O^]G B<sS啈lU>;jv9 (cxN)`3ATڤ,oI2cHc`i K?iLz)"Pu s-9 cG~=/Y1UQ?1X.WOX!8*Nb̢=ƮfVW$Շ0uƒ*lJ<$J};||9rv&ܜT$%Hbx.ƩDGO>i?'(WZ+fVW=xe4Pc^kkJ\]]"V"z;V=w ј$dϼ(?8O35RӊIx*|3\ &OнI3-p29؈5~c]<[ݔIܴo~/S?]Y~bn\7Q|r˦[(pEgӕW&RF|̏VWf!ġT7B#%]00b;θ ƕPbϤ0< ysXy.yKfh) F{d́f)I'V*Ke0"V&deH) ]AVJ [Iv';+jKzW:-l<1aAd* B\M,y7NW*L*2Lq>Y_GSm@=\]i"y勼RNo1+W^jwH($,.rQa/OڶǎKh.wnF=aukPYuhS:zO5-.Nvtdf5"?AD_j/:u$Ip%p5"[ᐜVd OHchfrgYCU(D bԤ.-ņri)1Q%--Y1~e<@SSY6+9ܶL-2ɥn/սJ`x[BZ~4]B]5_6R+r==4;e#*r$mޞ 0(4==֮cB P]ݔv5fZH[%qSAbx%h' V(?A yL%I.4_/r1<|M@-$kxi^AE9sUVu4=[LVi)'RJG?v. /̉E+5T^YjLÛxgۭ│^)IaqeR-I2)q:p2ʹP<)Vw"֍P;=񈒄?F ڰ$k=vRҼ06Ոxma[}RنK̀ljoI])< #QM+sޏx\Ŵe ? #(:OJ.KbcX\ (+LAl͊ q6'GDnPΓ!˽pWPՕ xDYwG஧}i]þ H1$mG4*h6?iYB|z`=^ZffI/IBDO[&POBmf[G=qDޫ޶1K'd{T U4cǾ4}yMRl~+~/qXRә\$uzTj l=sBOرc]v(+ ./NtD.EݝEo .UTGC YΧcxSS6gV{irv XWVƺ9;'BWwk<SvG" X6`jrD gabgfvȟRq]ZuE\4Pۻw%z{ۼY7ZZ}_=57׏>ZI;| a֖ jiNj&MzWWWmRNNbf^[^ְgR%D"qJ5䩧hzXCoK":C#~ȳta;ezr@ cX^f]π2rπ pa"lS;IB4%"16;);wkXA(Fq#yS5ݖzP‡%i5;kN)=sպxt啹/Ml+d\n'D1TEjfr!M~>:ɤ=%'M^FI° M`AC^=azݰVL[4A) hX{.R.r࠸xҒYZX[7_ZxL@F*Ӛ0{ S:?EIΈRV13 T=+'?GBi.t_ EJ5 2MZAyNmRKKBTQ^̲½S|T8r$YFiթN=gX+OS'N?9,ØpV{],LR:ّA6ii)U>pp0^ U+6PrD ftwwr+. "J鬳Aڽ;'YRPZQ>7=o˴sg.zO"蠉 Ͷ='i)KaT _*oTa0 3Ћ/`-,>Mp6-\'x`D!!8DajZo:3Y& '(msM>*= dһҚU% fOnN9(~TFYlg'=8=lλҢs xv̪͙Eӝ+`ww[ٻ8149iV3#AJ-s[ :`x(m=ڈLڴ)hܳ'+o]6 H''+bޞa# H[6_SSs\H(ĕՂY;SԌɓLYgZfg=2̗av(в{{46f+CSfR @Iۈ^2 lvݚls$ ?7PJCHݶ rwmE"RS ݑGԠO}~l`! n5= Y+˜`Y͚p%37TRDzpT dxM\6n^8 AVU^Z;+FpphڔCÆ葹zY޲%癤ڊ+$/6$$Nn.-G&T~mۊ$ ))qħ(5-1)RBSVot5t5rnkϓUgr ni|(ᵴD'}}49i2\CMPrw+,yN>ļPM߷} {1<COoH{ZxF81ڊ4"#A<ޑ};S:I1=û*ר::Q#A|43PPq;KN4Nժ 45EǎcKǹj@Gs]5X[\̒9bVdp0jbx۶ee彨RIe1$Lp-;mm#Gr•^ R~~:I*ib"W%4)[bx8ATx\JL_'fn, 53T\SBNeRJsjrM睗U =v{`? ćၒJ(@r$M)|‚?b+.|a3I'-[ϡl YMW=de%CzK@*K=5]lz:=LLډjC$IE78hsL 缤.9WeHkY!obw*1N]3DLHW Plm^[ٵ3y8b$cRo#9QpSy"eÕ%d}O.UxcNMXw@Fd^f6IӅ?0.w//[A>٤JWĥq.09q&_*|8n{QQV!Xx&dyjgv!:$M#l w]x,(AK-!ax+*G[b$2Ոz%vEg ў=GQ~dh֬Jʊ 1Fkxx,ќѓģ7Amo˽ <[@vt!!9H8͔mũW}[Nuw_xvǎQKuȀ\_6O%\` Wc|qi >u^xÍ1̼rO++:l/nmtpoV:~<.?/@r4cyLyoqCɯ!AćPā \J(~| k\6DR{ڲY$iPAFin1{l'i,Bsp(mf$Y=7G==4>v`zܜl`~C]2^e|IC2߈L~~[-DI QԢWePNP3ƹKˁ93fF/C͹[\ε;Dk_# TKa;ߡ0,;/NDdṍyBHR b=mݝi51ѰK{9#AkoO&۱#!F!ie[?I `3FC`xIe NEV `iJk?з}׿~~ou>h# ]q=X=CbꐿQ*NMOg[ s]^|ѳ$K5? ј+.M䆀:~V:q9L sV\ |DX{dwaEVSF7ӓO ![:-!"X,1ɌҒ݌; 5JN ֆ ?0϶%+ED{О=tm-!Gك@gӍ7f =Q*[m FJBC&ݟI ."{ ^U{^Ó{醰D =v[} o$6U_5ygӟMk/Po/E =9]&iyQ_aJO\^NSfP^t D6utd-Hn/ƃTr6LBog7QF{x0$ Z͖ڑaظ~vs-^/-Ln$Uӻwoŭ+iGLzbee:I k <ㄼ9 )qݻ饗(kđ(!9:[P* 3׾6(2e[ǭ0ĢjoSr.IAWTTU8qxSzk -Ąz+9L DcW $ٟ9ˆo i0$E*/qxN+=|+ i"EN0ӂoqk5. ʴl!U$gn[u9 )&ΰm !Rʓ% 49IeTbY LjI--49dZ2I7;ID_D*,BM FF ʣB`4ޮY6ձ7R-V9կZ[il n"9`YFQOv::_@= p!ACun./׌ƍͤ;HBjlTikb敱TÞL CVK C%{G._meN zQÓNUevI(T=?COzABIRJu༼檲B xnHERmԘͥ@xv䤿o7 [u??rZZ5(nH4A8 E4n!ʺ}ԑw$igg*KLrQR@0x3-ïR3JFCq]/7OxFgg BJ䥭L6egzzdҬs Z O!!Zc2y|P^v˙|eb׈]h\FL[ Y槁rg~ׯ 6mzm,ɰ^͸9S28Qv%zs rPZZsziv SŇ' I]!hqj\<IQc^چ0<ɲ4|u:'=pzzhf|2N )?ؕ#]HSF"mw.(c(^$E>7Ғ衇J Y[.m&38|h"GAw8B 0= (ImLմ\YEh-ހ5Pr=={.w=q~":ڿ`$9fҧY.ŕo e+SOy )J27Wܵݕ۷0m)\u-g%Yٻ;bxm.3)*t&D(3Akioc= OnbbBeR$Inz"lEđڑsr=6Qěx_$>Wuw ֈog rRb u;KR;>ghvGSydO 䰖ZH4>P?=h:PJ%1a$.K0J4!I$9 A8ՍKK/CUj-kbxg2ӢRV9u7֚!%ԓ* Jq3FFL9JY,"RnVKenl]^Ó[7Ͻqx<ZXf4zӛ8߇6n矷\i<qSQM7QVf3,,BA3I.{RF(0ٵsKnzu[W떗z@zr.3y%mҐ4z pO=f=I`yiuJnh OrKN6e ]Yi-l"UERC4o( uxYv5kkf?$R$CLr?J%Y v\ѣmoI1j{ ǚm[$-Â[ʽ>Lq\|ÓVTw+>b3ScGY^EUءV녆CE27nL+ jmɥWV>MH!L&yUiEI3^ K+Y3xy)d7(J8]V; S6Vyjsn(%wuG^َ*3ɥ""Gh%>WΦM2g E/X/B$+QTO>38?b.1J*߰9͎4{+ϖ.vGӖTyM {q'MX M1crXB;EZs}ci6htawƍx%-!ĈXyyN4Onכ.>-=5j sj^#NoXzXt q#Τ9%U*I65Fa' Rw RQȺ)Mia s["Z@ @/+-^1XiEW+s%% N~m/i"x YiE U_PQ;Z$&:51:U 2?[ѴBRM2ލ'#uĦr[T$ÓNp /}NXL.U:fe懕u:f䫖fKQb'2WAJ*)D^% $]Ze?%KG}Y| 'x&8w:Gc 8EvIIjѫJKH+Ġ@j 6|ORG)D?w @lb1):<1Ծ\l8gvlm_NrɄvJ``:LҲ%5R5.n-$‘x=T&ܪA^fW_M]Ǐ/pU,PӼ Hח&̠B^*DFzzǛZaԊ2ZoiA99uҚRܙX@>шԶU.[l+|kʺ7(IA2QKj29oF̡:BO;rь JT甧ZsN74{!1Y.`e+uNvĚ9 S8V&z?4QEtvguW«Cao+kkᄒ]N_}Fhi:^EyX7+ biguwQϪ&OO3ш>}`69;zWE{UQ "VLy >%gO=ҲAesV]wV\XDmNwxVtgD;8KgTьm#Eas*y8ш3f'5%uϿ:+AI߻f c{cViNXKB9/kAL&-. ԕ"Lؼii]0I1`倜"]:H_ ʗN1y:ON3hj<`ɓo.Vt^T̋\TM!0-8ۑXy>ӷ11U}D J/>BN}FEKkme ={́=4m20\`UҗM'.\:Ԯ7SY'~%jRjdUV9n[EY@L3UR^ֻI +޽8|}_]Rr30p v{;gꐒ0(> rnyS'Ruɥ8Pn2ϼS2'yQuDrމWSDM&o ϺRP\8lߎU~>:^Yi&^&& {]@q}eVo A';s WgW,46apL݋C]Kb x+E*Tt1wW VpÃ= 9v s".d-1#s.4P1\v̡0C$m\~Iw(z2+8I|[;ߛ壩50dvuY<ʍ%f\{?ՠM 5 VZc6bQ9[X?myI6e+j"zi|zE1er4/<$ZZH =ۺz_⤢Fa01hj+YU"{~-r|8^eL>,ʄ#c " T5g㐝ݥ{=b:nFk,mt4Ɵײ:<^#X4WGOr >kUU0|Ȯ2K]Ӕl;H$TnkQW݉:а2v~)(Ό婻[ĝ:T8{jjX7HuѡtB $ly {qw^_tYeA;,@l1,\?ǵ+qHW\"&;KYDOg>wx,i7 )hg|qy`)?dS>oňr+uIXYe f'>;;4β89>pЮأչo@J5eux5xtxMM A"$9sVvՉXڛmV98b`5I.5`|ɥ],:;leb Q|LL/"Zi;z.(Np+-S!0+-$E ޹ 'jMLZZڹ})E J8 /^ڧOCٳ'U"FvYFawV뎔dK .NFj):A~JxJf z)7q57#6]3@] % /@\ub)&;va:{>ܷQ 5:yUV"'qĩX8XWeIB?k]kXJ=>ytɾe}y>8VZώ8ʟtZc`ňe ^9b_w4$m 3pw '+-6%%9ov(<n;m 6T1!懧Wq?ݸO,Xe:vtxC7:b ऱc'"DӆWkyR*,<6L2y:qwNeli88K%<ֿ*SE{e(3t7uV]iJ}.Vی:Fv@-r01Zux >+C@{7vAHWvWSmFkB:!0oªDJ$Ndst]-w(ROJ[[눴Lٞ`sN8vh=Y[bJ^< ,[F ; \Th)ם2 , "ytxNN(c $?;spKKOўPbTSy4?Eqct"SPDUաEw!EvQj14.I ^w hמ/6H:$1VSJ;o fk'rDiR#?O*olyii!@JxR+t.6w:!Md$e&8lGh"U zu <& ;Ziڎ͖"U*} 0sΝɓXRvns~x!l{hB!+a $wYo3UшNVv3’߆ N~rTVKv=}:pC0p]3aI tTJ,q {gBⵦEsbiX E“[>[R`/iB1ӚҕɄ=J^lw/Sv2ɓThJCl645k&+G f]L Yqpm(DLBL _N~:^.Wi|v>KYiØP=+f]NI"tr:1W?<+B*¯+xv:!Bztmv ]LHOGeepFХ~( qqsj:![Jwfh $ΝOSduEa2Y x(9{ַ@sux"n$0gĄz9JBuA 4'3BK"wh4YiMrEL&]Ey|At\&++ދ9 |FDEyzMn BhUt"5fx%5h"̾]P`(RMBlwZQYV˳^8"w:<_sDNy+(PU:'CݞpIԩR_]Urr0JBI{-:zw! !@_6d J+G9bޟJSʴIȥ*B8L[>ղw}GG`s"*44t  f< /0rQXiᗈoaGüٺ#%`Z"w^>բQoӼXlA:wM.^Unf.!b<0|D;v`?sMQUgiHVaD6zhDDDf5o߹Ud%[|EJZ`@ѣGЄ+bEp :㋋ѷoFnme:q5x|刑#鴌ǡLŜBٹ3 Jl:'@n u ?6o꡴R:e:[ˊCtSVީO"&ѥ'sȱ'Eފ\6^\$w=BǓx]=߸oYRi6!t2. Jo_w <;Y΍~֭X9s}:GJ*t|FJo#HB Gק'A,gM1Ԅvv}~Cs +-:izEKPrZiKP s.r\{5txbv65yҲ4ڽ鍊1 ALɥ_a~xvv9Aq߶Izɓ8y3@N]=PNL m>rKJe8pQrd>)];5O61$S#q(zA֞oFƍ4EEy^7h311l1jԒGYK5j ϫOvk`5CK{.6+-0&OٰC7,!a5YMM%LIN>USNcEN}=@kᢆ ) B}U !(]ӄ._ޑ qJv))3c^;;w;q'NKmi@Z'Gqq:E` "/DJ/~`|" Mԩ25];pΛ.tyVZmܹs[}$Nt)I%<?REwqqCrl;ѕwB;$ptx+Tu&dtXΝ۬B-[ pCs3O%ȭ.r=@:aAN'V\"D14ٖ ΂TGA"~>A^GtA3.';JaIxDp4NAtA!@߾]=."?| )T" AA\LI$+rAA<    dZM:p$<  p$<  p$< 1R6  "   ^8AAA\P㰃.'AADAAA@ui  ⢇a]N  PEXAAADAAAHVGAnGAnGA 4H# 7H# e<+H# GKKWAA!@||W&# 7H# 7H# 7H# 7H# 7H# 7H# 7H# 7H# 7H# 7H# 7H# 7H# 7H# 7H# 7H# 7H# 7H# 7H# 7H# 7H# 7H# 7H# 7H# 7H# 7H# 7H# 7H# 7?ȸ~+T%'OZOӟ߸k'  B6 /6vp~--UQ ?r:]ҨQKx^}G  !Tѣ?:{vcl jjvz ԩܵwAAN8K]>䚜<\Mٳbbw?n  jRRf_?<-522؟Fc9>]\#yBGAA`Ho>bi[y^k? xw a  "dP=k6OJ4>>&v'8NĊ?hXe|)'w<5 K貆%tYaI)]oGM8uddju$QlTUWwPGAA_\{l޻'Ojm0Isr~[ZɶwQAAme[֦;oYYwhqm9zyAADN+-CrqqC];AA"tϦ:r;[L%=8Ν|-zPpAZtY=\v T텅OUtY5z}A0fk.+ĉWYȧ%Ncп$%Ή7k-yݠA زe~]XTtY9|l2K.׃.kwT&ki>zCϫQ.k7EVj%.(xwdȋW^yv"Q W\Q8n3KX ]nZ9{vcZlgRҸ7]nM\ܰy󄨨~ϡC_6miB>]/1he޽zDJ؟>VGP0&!,a7')iUT|?29yr]ݏ?vk@q|L̠S"+ t5SSJk6ٚ3qڵY2䥺5(눮µ&!Ws[ZN)*<ߵ3gڭin.۷ᄆc<~^K۴i]^ ׻+<[g ݏuDju&!*KHjuLddVeURWJ)-]2jju$]nV}Mӧ?++[RE^t5iAtG K/(2oaᓥ.wu<ԯ#Bٚ+vkA? PTbbedeݡ%opfP_h̘GH5jVXH''7֮-)Y;QnlMB*"d/ZVG95rZRvRU*.4sZ/e%libWM ]p§y/1IxS{)2Ol`uQnlMº!{Yٍ̞XP^qV)}J/N;g75yyOsq64 J(m11z ǩ$]p§YU6KLVZ 4SwK׮xׅ\f3fd?rbKFb`6u]IVBVk֭!/+/{qدLJ:f3n6[tY fjZF#[];-:gbutu]GE#k֦;nq^lƪ uu4qgn\%WUѽ^離T))3**V^[__X^er={NxC7l={NZ˿hn.ML&TTKMk sfe%&ٌuu^tY f $< B9sL{s%ڰa$|*)i|L=zib/tČvᆳh55Nr6{{Թ( W_]v6[\Ç9z#G>\:&./_VGuX&MnOڧm#Fc~ !+-A!XY.#c>22/*[ϞoU*Cr͛dZͩ? Nx@کSKm6= JzܣG_HJߧϯ֯ϯ/8~ܸ5QQ O9wn3FՙEE1cÇ! "--ʖgdpEG~DJʌufsJ1fgcjjv56on.c`7gVskvQ'.YkJܯNC Ue:Lmr"" jmX1?%eƱc/KȘ8v͚L䒷Y:uL> *.n'Ti6m2qMlRxtԩCPQJ,L ly߾wEEe9RjA @|90IVgРgkk!>Y\Ytdg|s SOT<]?TP"" A!GE<ƩnY'7sfqKyO?}.\8 ""9Nm^Y~촶vρT-+WveAAii9 *k ۞=O?;7?>AAǩG bɞ""2͛'TKx^Hz}JR8zejuTbء# pG2 BBb X:AAAAAAAAAAAA1AAk2AAၺ  nw  `BAAD&?<  0Yw;wF݃TZ9o樴N'ADk޽{kjjbbb 9s^655Uf޽N]2n„ SLݸqUmKᕦ7<5eF7G? ]e2395>{ۜnغ/K Rwţqo3U :Ǐ VyiӦeff>#zUZZyfVk0?]Eܖ9eʔg}Vg]nm PmqOw讉hڽl3GDl=qy}82 ?.=[hŝ5sn@{[:8C P/ F455w}eeezիWYY}ľ\}g,Ydɒ%Krm uAc+5Ǝ<A+*JREEE  RAPTvu*n_Jl_d ݖ #M?n6ƷMǓ_uozӆ7ō_]>6<#îo__n|,+^n7=濶K}zì׌_wpۙZ~y4vves샍_oɣ/{eԛ_68*Å13n'W_{ϋW~ɰ)g-4u; /55f=bN^z_Sk>ƍm6m 請O~葇]|K ўJqf%h v!v֔qnz%w Yz?n]22] mYq%F}7OF2-Ӭ'M n$]?ˍk/sl6lXzzzqq-6lm6[vvRCֵwf5%H੣tOy1C79B^:5Go ʅf+:bʛ{dE DOxt֓͋>8ǹ׷!`ϗ-o,&@@b *B:a9WN2W\ډ F@RM á}öA! IDATx>`=On+.>gOLVvvMˁ7ꜘĬӣ =cٴi3>{`g:<]ΟkLA;\'*+\{{ | q}7fݺu&::KeT*ոq233]sOKKFdZ,AxFc/躛? ^l `.v~BV 1&g{nSicPj "~u1[rQw}Nތ/lxh٩Zux^V׎~ Y*]\;r TւM wJֹvSޞ3?oᶱȽy KClݺu۶mϟgl6fKJJ3f̸qegJBwOܯ_O8HciA7n,))>}: yDx@:<۳{s<D"#i9d3 gRdxkF\l2 N"qxO4o޼˖޽K/AtH#7vԩSwy' &iyj &;/֏RcV!a D} &N=rHr 3jQGtkl6NllR-*hvj,=sc}U!esi1Zm|Bw]8Yt&O ,1r&פ9*?c_Oq͇L9ڟ [ n N4dDݮ+.FB IxDGכfJee6jZ-P{b5X 3v|ifI@Ӊ_S}]s2#[p_1D3nY3`=W$-j f]Ԍf"E"NK=ٿ_0{bhmv{?|{K~=3"ಷw t *5Ouh/<շovқ'F.z_yGlqz͓od->qpҧ{,XV.{o>/1Z`XVBxȻwtWN| ryO~GwO瀯v{#_ǧ 7_^ oOoiL{?~`Q0'~}Gƿ_n%׏|sg_囑G^X}7ZtÜ 7~ƫ-jō_؝ΆlVr}o*,,;@ў0 'Xֆ=_VHqr,6vfL~2YcZV0-`),M}zj{Ͻydb_YnϚSnkc AZYIdQ Gw.nUW5'Cg?g^}Cv۟Y•oZk~j=nRgKt :$h*['h^yä\y-f$/{85"CW]Z=\w_!1tHl,pWL%GӦ?=";\x∹Bb\w'OEѨeFd߁&dRIyr{3g]m.ۿnǺpްj65[Gl47l{E& JtRe4@K]MՍMFl,߷,ꫲwm>/ɈU[F1sq }cFctcՍ紥R]l5rF O͎|sScH63N\s šz;_uC^}>-*C lVA]YYYRR"DȢIKa\ K%ofE{HLП;z؄܉yK㍈R'N O@fG@KيJ,l>o~b2۠yeݐ+{  6{0of0*1h\J w%14??3vs7ye `yrM}̤@sxq'!fW;7'W~~3Ml{T7m`b.h( A7o-'D7!""O>ꢢ蘘JCtGF#8f^cw6ǵNn_`#kq<9 ?5\ kx(TmP{O 58a_XG>NP͟uٵW T0xFe*У.LLzGi|ҕ=p9< jZnY*gzrfTplbtVëĤ(HZXZQ4*% oԚl?D~= +ml 1Q=x Dkx!ۮq7&Ehy \9yxD u0<yϹ◹ge|t0۾v0e?7<7CimFy2=leW4$+A¨>Q<sS ^$OVzLZBaktxn"{pz`[eBs/sR_qNah8m©n8um!},6 f4jWUv+Ы޺ 8& /Qv "---EEE={ƪjJCtG4͹s0 >/3|%gSP?xJJl/4&K$Tj5;(o-xU[4=߾,gj2^ͷF#} 퀼 O_o*JU*3V ¡{|WQ E7RjkzֳR:ޠQVjڠUwUـ6lәG\(jީ,)iy[{ g 8^ T,-gp*JXPT*dO)Mh=EKt*YxJяބ;~sS<]aԅo7hN3㔜1Fdq+dM5T!6Z*sۛVJ!]kxU*j0p:U-3HDLt_AZ길(H݋R8N Ԝ. f,g5^PS qh3FĬ-j fԠh3|}d}7VT_N q@,EFdLQLƠUR{lzip4[SmѯvLF@PVmP̀zTyrBO6ޠ>Bqljp3SFmj仍~^}ۿw4j%GajMʕ/?=fsŅ /^}쩭b_NxG/7NcSr9QmqڣxM{&F֝=ǠIy S,صOɘuTys"jNҡa‚17\n`k7Da߾نhupj <4+m+'El銂,ә돚9&C83ʟk0)z7ݶ$mPNȦ35@7%8-,' O pgݳ;d?x|ζE?fd'TLqD?Ȳm\,9%ʎh ^kk)۳ e5_'_;-;Ip;=ݭ(w,^7 0%y Bt:Tlj~{+Z[[F̽}쀄 Ƕ.=>%E.'+}FT!S6@ˆ>4j%Tظ}o3 +o?WAA :AfZ.6֤-A2yfUbWaSNz^""# C/61f{r">9;ܐ :2k^]{#OΆ#g{PohޱUt9[>߲;xϫFXwQUg̒J@% M iPIl i%-VAh٬ JZ (*AIJ! $(d&313Yf(AG'9}Vr=FT9O95Cp ._|Qshtv_nOީ?usF:3/,,,<<8`Z9[vb0 `0 yT:???a8ޅ,-O8f`0 FšBB(5MѪT*A!Ie`0  E1fy1 `t]\lW\1T*fEQBDQt 4<vQX($L8|P@.Be܈ e ?*fg`L8x/ҥKuuu&IVgݻw)0 ilfn?ܽG(p8%w-ą?.J(?_)y 팢(,>}z'gV;OÇتW8ٸ{\-ۚehg?E"}UӰcv2K.͐.BZ~nݦN=++//]^r?y0/TQRuKW/!PLc0n_Es3jvE!I{jV>\?˷Y6޽+4B)3&##'?c`ܞ?ySݸAOS>mnǏ7aq"˲`H †w#ڒS [X씺n9w, EQe1-Gϒ/kG8h?Nӈ'W.^x) b9W%4Jh]M6de$jWV=ͯaMߝw1}id\'?*xWֿyˊծ4x{s 靳e>563-S~G?2_/OiΏF%I%IZE79P$IԉoeIoߪ$F}|#`oI,>ykOqu'>'Z0,剀OUzdREehcqqEJVACQ v Tb?]e <ǁk%7K LZPNUR'x-7nGC+k7ͯߩ.5<޼—3^EȐ4yM/}?[5ϝ1KR3MX3?/{ -zbm>FIΝ d0y3?|Mc׮Ym*`J(lV9Iݦ\;I\wv…Y9 KK 7g|ɉK_Fhm~Ɵyg"uo/'5󽻵g o~T_{^2K)U(eix T 'rS FZV Nv^Y7|ssq'B8Y!2u(IRKi^f;ZnY{1_=^3,uԄfdb޳rcb$w[N8NaFMvZD޼jm_}H#:o hyq/ڄ`CvOV|<hz># F ֒7$'( p/!2 h&wbVzaiR^8c=58J)h42  ƭ_޽㎎U *yN5"r6K.={`0 ƭիW9]sjSN#^ӕjVڦ[dE[Z8f-LH=FMu<zyK~zaٯ{tp <84lB8J)&((߿#`0>466={ңGiӪxY!@ |?h***z y VCENiE^qGRơi;P UΧ '>3ޗF8ut jZZ]N O6@J@(hxīT]~xZߨU V9gTmN8o_syޱT83gݥ_-,/Sx{™gxT0G+0hi9!Ms~;Б33V8ϣfJIq L2 T$???Z`#<N! >}@MTQs_^A>_#2'՜K##]²JZ27yG\~( !DhZ??U*8ND"tjc!@D9pCP:jVqSZ+ ZQyppj??j 5kw7W9MƟ  ?OuS_I]s}yO/:G&/>˩EFVxFPi5BԜ/ \CΞ}&rjAҽ(\;,ZhbQu p4::8 Ʊq|'@S@a Z;'QJ-ܵS?|{SPJU0k2*RJ-ZVVw<ȃRPED%@P Jyy9'N>|xaaa`0]_e[& {Z^ุ ͇f6% R -k0t$ M\Wurw$ApZ#^vÝpiUMVo!֚D^q:Zfݮj|km>c_{& xdYe믿VTAAA7u ME{]GU~=CԒ  làGc_'$$h˲Q`Noת4xKkl mӻ rMʇKxb¼y;XZmiyFujkHHp\|*@p-Cby.Y!vLT49QlAବф ~9c0]su:%EyVQ|b  e{\p\̟?sș(c0 %p#9α O!]N RIvu}G}{V(.vpۛV72Nݱ7J|ߕ++>/F85$.۶&pW=t7$TǷZD`8 !9p<' /y^`BO| O&O}e0~O:'XYyc->u" ȩSoT'<\ڀ3 P J)"rρBwgimN\$b]ɺt̶|nmX>.<|\AG ۵|ܢ]9繩w||qE %yR6z|Z2\:9^=i;DH֢'5'k=}G`+w:NȦ/HӒE[M#9}tӑ#gzӧ*ΕUe ]C'={_s=6bߡ d PYV( ˊ$ɲL% 6as~lxǎ/?]%%6& 6XqqmW/nmqᑣFPD?:4:S7 +kٚ .: G*tM8O@@:VL??9]Ę)#-:$qC'5Pk˳F?jԨ1޿`uSg ð)qd16dBp@(`صn-{$mNNT6͉?دUy[[zFY[|c D=FMq@Oyڢt@ 7*z[ԨhU@=\ħ4rnZ}82m>@`;}` BL)"IP H$e?Oc-^LV枭pr$w60֭v 㦣qGӼe9=mQ+OUTc<;{D 8Sڛ*sЁ7&G0KUT=`2Q!Y (m{sъ:{yy%溲c%խ3{p|ysU_:VO[ge0$|V~{7+KeŻ-Q` W].;gM^,u}g/OJIOlޱcuJE] zh:5sjRclh PITQl2Ide I$cM3q//}vRן{#wJ_tj:m|,g{8OvccB~nBi9{`(>f0nAvvi{MbijVA )FmZ8jտӆ.:RghC~?˿ |x={6<<'Oj6$ n=tۓFI|ՃNz]Y бx ƭlbAnwu&^!L>%\Wgj-0ڌ ^O}!m@uu&AP똌b0n?dYׯ_>=:mz/页h#_  ~ڀ:!!A]`0n'#( q(Koi) `0v #A9PJOs̆`0 !'8ST=\Gsq `t J)qz= S2dY$Y$ 5Hk2"c&'6l6aV-4 ,"jNd|3+769k`\@s tۋWQb_@#uIC>;e! _efut2q_ԅ؞_}dh*]esc# *U bױ{dhw7wkW9̋`j 6"ceVo}0 J8%3`”ӷ> 92>s+>6πSOZ>xSwee:}hkcVpEU8pd|Jb؈G?Az. +Һm^ O~'::KTCDͧk&xܹvU2}N*Ozp؈q3Tlox.tK:w_t٘]5?smN 6؋7'ٹff~mY{qً&KU\nx3T1-~~w-—.4";;@T~~ue]f>@\- FҲ)hEV`W(TR`eYe dYSulǬPiڻq#9K$F2:_F,9dcN$+-6>EP~?F JЈ(5<'b\Kk{ k3Μ<Վ+ S-.+;; uac]˕/_1;<5\w}Uz_`LJ@Ԃ7S\k"]9]1N\xn~C:l=s_uȶ=Cj) +`U5vW'EǮ{h>:ELZ6xͻ }XkysTW֧Nr{3~zm~oִHT*Y2~YܚI|n/8)>5}Š¡U9Ƽ+6k6׈\[<t#W|f)>2%pŻ*έm|Ip2ӗ+o17UT|\ɭ\V6dǦ`seCzv^fc1/5>>'z'|rWٛzϯ ϕꅝ'Y^4GG{ػw޽ds>S@¬aJ i>ڽApwqիNl]m|RO^kgH೗՟K;9ExK-&[`ߌN9Pu*"(T ]NK!7.WVm~*(D Ԣ/~ӳGwIPtתNgcˑ-z Sw?"r2C".v>+zG@ 7G>1o0p3;5F /z*=y妥#zŌ×}bFj Pu|+P_LM )|0Ż4I#'k0.yUIIHXXD  Y'MOY& .Gzi1/.5l|{ɪŸ>5 [x~@^?_ HK}@~ zGr .o{0 vzdԈ 7z̈V(zt(7g,WSVz~0O}Gzg[ҺS{^ftJ_SKOUG!f`;[[ܲʫ@ƖhKinɹh+V;K&Ӧ5^vrS1Sǽ(mGBE!BFWz +3FjZW^ Ϝ4LLykifKUTn1UX{s־ }T,E_&MzuM2S ʫLҢiE}޴XoA0i2_8eeϔW@̚`*4w.6<ʷTW:@ ub.U:R} ³%XOEN!aO٢Z!jʝE5&SUyd.lJ2p8@G@]^?|dɠ"KT'2SGӗYJGBaϖ矞=khl(;445=zaehM|i2Z>c Gj=yڇ nTXdk=-K0@`w wLbi<$:g:Wo Y`}@%:ck-[]"EӎeF`ԈIϬusLJwιanld;7CK%gU?hኄ~%kΝUٿyk/Mps M߰a1\)hۺ.lҋW$SĹ^xEBz|t=odფUQq+j}̊qdݺC[Qo/K902& q1ckGw +f =i"gG̒sGQI# #b' ;zbBĨG &W aTq4';.61*XvE 1_|֚.٪=Ro^?qqb 5[qKo_g~GyfMJ?kf-=bkݏWkjZ7-ÊG n_`IZvgh] 퀙vX55 *6BgIDxey0.աFxK# 0paނ绖OH țtL=T*ZDKխ ih9m, G/iKbG}"[߶eSc cȷUJ]P ÖE@9@rO81|v/xRzٝ;w)Sׯ#f|\ u»y<)TR(͍L;ϣ5ptV 0M,a_9Mi EB̶HHf#ʬl,"F`'E-0E11M:D&fUH_h#lyX|XL]NMMMIl0eL&Hff7*&b4t)bVcow7?tbKO^Gm|X}pB;-md4Z5Qܸ1i;һ~oܝ \Нs[LJo%FO)'4öV2"$,+W ? ,1>xp].<011$z{uaY(NSw?RŤ;}^CݍuuJQ=n8+]w84Iݭ]?%W;ںTg.i:ݫ,-2n[i1======555==mvk׸HS [r%9Nnһrqɂ>>UŶ܍vra>C7Ќus3_<|2ex~vr cSA4GF][ce~ZBFoma&Cǿm.Fӎ7{}a6:2anktm󳳋TXuwoI>֟L}ۊ32t6^S촌!UVa8.][J=`a⌴nӵeW|܋CiggggggdyO(򩻶,<ݡ3?쑏Le,Co]Z[imJV-=-pNbr⩋ sAMCsy$Uv5' O%''|GRuiާY _UٞE6}Gg/7Z#=EwwS_zO!##y@ۋ/60 0^_2Ƈr{B^\fffn699yUjZm6l6LFqllf}A733vpkŅw\Tzvm8KD̟VW8 5'TxV]yF/ցal=\r= rUpJT1qezr4 WlMX  Odv vն| wzTX>08ZUSXz-S+{P9WpYr\*//iU#x@ٷXٚWNͶQy F_ºu1jj 2SmFV1F?y<>^?j=R74{QquRyyyymݺuqqfWZ^)o/8 QT`lKpOnMRXI-E]] Y-g?WT`gվ?ZKkrgVx0DU G4s 7[|:4OO>58Uߒgcޭab#ޜYh`Kӥy:!KJ \5ŬKNO޲xGF/tY;Y1'=[Sɶn:aF?q/o݈+Low3Kj*>dju#B7A6%xa!/.,u]6lZcccFq||//FGGYݲeի}O^.-cxx??'''fe׮]3L㯾p0y A < 71*&Dt (kPŌN̾ U*(&FFVUElv,˲6Ho,NTVM"Gulqr˶sԿN]J aPU*@`*_Z5gv.YeTPUqP1eYV[w.&9YMüu9g)ϝF}j@]/*f6PWxWh-J%AG02PZ_be=F7b*˂ o<lĿPiU*A$ҼŵNRR˲*U^_:1і#F = 3,ϜFLa=j@[ 8wK;0a0%AF_.]zK:::N~[n6n h>mWSKWS=T f %oٚ Wm I g2T%ڜG&?\nFV2ysPƮWm;9-ȪjOzHc u5érVВs.EH8u[C][{l4~Mۼ,g*[a󙴲فJ&!6EDy> b}rG5gt gn.̮d.Tz+}3%Q5Gء"<1jfvl'a/NV+nt֥@u2=1go7ȩ=*缏 5u73 P),GZ ԯbϦB@UU>ê<%Wu\t ?hO@ZNj8ͯY;c xPg3T.ɳi[EQvs?>WZ lT ͽz^(\Z zT |;(Hݘ_5[ʌ=FF& RԬ_=-ͽ6\y|c|o;-P=M(h Y93i]=](HR/s1[!=&lz>iB#fI d;C®=5~~C"KMZ,~o OB9#kW4/5w5om%}:w+c;[dUuV =jd0{xCq2pӢ*9WX]ʛ.BKGwY$%@Gね>sL(_jZQ({ޝY;}/t`F➵+,̯۽kGC[u/cneLKG!_p/ri2|vsK=mjm >zۧ-@To\gla~:خ؈XjS<۹S6&-UA#?ۯn} 67a5M/۴hTJv>#?UO072# 4o9v `jhOh޴/m(Jה$ ?<(Z&k@ͯ;]P"N'`jբ4GDz[v(y j{w޿?8|a;];w{N Լ;b]0J%~L[\c>\0shuI-Çg_Yl@EݏwmۼP@T!߁,E\עXJhAt?@5v]X5qɩє];weߓp9hKn\~ƍ7|Xaݍ'DhO3061:ؾ>w˾'f;sr(l8wG\{w{phw*}Jg"|t߮[H$+Wp7c<"oH>n)JzB-Ujhϻ6zv5qT؝/_S4$Pvzɣ@\JWK%(,;N,ںyM'1gٓY  sQ[Ad Kotm"#t;X96phǬfN@cz~4nmrlR K R S:Wy߉Q@p'k~^O",\w@(LCIfrѬV`Y;KHHVz^z0`dD&MMM'^FƽD8bMQ`t^Cg+H[Sl9x,EPl/ˠ-wijwdD&i1J'Mơ35МӚ۳l647QOh <T</LMNuݥksӆ)Ppnɨ?֭K`2Oǡޠ7ڜbv)vϱjƄ\MzmFKU-?[`(+`{`9mWukVBZ] Hjy]?1{-bX1 Ĥ=,9{ [-I+gک?ӂC:tЁ}G_(JJO hsߐnhhhhhH5X"1*.Gzӏr?p?*Q?U6 M@y`^JזN'먮lM6[7D̫PkK7,~;Z,b -%de|+b˿&dHݯk \MKͽzc-P`6EJTo{S+Qf5&hJ{x@6GWR9e5:}os' 0@<5CNo.XzUD3#uzyq 8xw#01I?[6cOkhOnjĞ^h=sFo<қ`3^n&N@~\|2˲1󏂖i)###yyyoH4oV~~~4|  xȽ^W11pY޿Y#VeY۹}@hOCZT*ڻԞvkzv wHO{T<@aUB IDATʳ}}VhEQ:eiZݵTҰZ@s[2}$yPVTUa˯3' ҹSHJe%TS!*jֱ,IxZ~>AíE;]c @̊ \_U*,Ѿu=-+R`@֠<H Ry~uJ,R;h~ϟUGXWk*v,;PoT ixзТ>1qΪ˲`z-*4PV8"kӁt܊Z5Tyk/s ~Xx(m9;4]ڔh\F:+< \"K l)|U&b23#>]p y&I?DXcnRD cn5>u鰧?7{֘7;9;YZ;=Ҿ ~ֿJbf>G>-x׹߱p8?O.]5,G577q?ar-&hɴ"R&T+ܑ Bi+Sl)?h~v#Lf䴘HE̓Z,v@""\#=1  [ Lؘ0~jaj&O6&Y"Gw&NtG*bb\s8XaUdw#vT0ogLU*qf1^841lX,voPs,' u-Z X0y#G"d:.eNh8Np\.}mޔ.]3?^{nK$]vH$e~o۶r-aرcFoڋD"Y&K_u_soEȤ‡YOkz:@xXz?FPY,{Bi<^t>8pm!Tl1 L̕'u)bcVi,"F$',e~K&^ء}SA+lQN_>$)@XDLpmN`nxA5Ф^_+U*_ T@ mӜ@2ԅ9t۸ˢx@[\a^`;b__l6{e7|}NyԩDb;b2ٜyNSA`ǓWQVLq{pϴߛ/ v:a/[r&*/ޜzmG*qsI-/zK<0!ʭT/vn!e >Zf 7ExSSSܹ;l"E𸕉'+W2 )Cʐ2 );&Ùq;*\.d drm]&v\.jH>b  333.kB/5ZXmD"s  Y/5q,  ;   4<  c'''̌lr<<<<222,,[)# XDGGAAiPGAqAAAĝExAAwӂ  bypiߥ{x2AA,io{5kxGFF>AAA҅;!bŊ~O?&AA,]#>#6mp}%&&r "<  %<=J-ZGAY~}VVֆ a=PGA`YdYqAExAAK~t:3ܬAAA, *~2.Zr$ lXX'x AAP \.9aZvD"NQ"~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:O>D#$<7n Hx߯Ej ww'=QxϜ(((xvK흈3C)Wϲ,DDDDDDDDD~{Uv@~~8dQFOv&҉<m#"""R#طZINNNOOw"""""""""R#08g5څ"q)(Ю];:~~҉TnN84 9kmV^P28}K9>b7v-sF &LA܍6nUKe@1!;&("""ta?Q ܹ//HA㘑^61瘮eo,"g7 w>h;Kw->&vOt}#*{Ϯm>VxBHffѣG۶m[l6[,93FQTv!>>>33R܋aǵڵk*q|Xj#}cUynb}lbUm X[}R"""ui rm\&{a |3gǩ\m*LlxKL);vܞRJ޳~9*t8uɒ{G'(9-_~∈<vl̵>>>Fq۞KڞnuL$)b)ԪU`ڶ7yGVmtoYn]+4 ?l B -LeYwӣBՁźPFԩS^^^,˚L&;Bb! :sLN.c7>oΡe/aWMnUM5yyv@˖=b;}koNy *6#-5R1h-!Xv)m>2@zymnNf@HB.ai#_vBxKҥK3!*K~N1lY_q7͊}o4F QR_6 QJrZEDD-E[͵\As3leSӃb^!_K ub`ǘvxa*"ĻTs5:qWoe؄sJB]sLmN{/'/lwi~`0dee(bd2fl6Lh4T*//KQŪ&SxKH6lƷ\g`o,15ה T@KW9/yY %Iml!Ң紟]ǾR2B㽄# %jT >5iRxR`yy{_`K8ՍS.M ; loo1xT`(2˜aVnJޡo."»OުϿO}HxZV-/|aaaq:e neeM,9ws)Z⤖9gI<[;,&*@ڼkGm&Avבg,?ߞl@-Q'aV -r+똰ZE&90>E[!BzʭgO ubѻoD[{\rvhXWCo-Qf71E֎űJ6Ɇ5xP,NɹK+/-!bO42Fjn{QdhWPRk` E{+…XLAB!7}/j@R_Xh#[0iA 8iBR<˦D"X!y)_pG*t:"Rf1l$Dz{u!9iUAW؎ށ22:eɟv=Ύ5!6LqRI߾U_:jQmCJΕU5l;->[?m(血tuٝʢϾC聐2Ϸ"\Ga%-n % pgЯwe/""""O uxq M*ʋS$J2QkvD{FUpΦ*~RW9\-;p)#w{WV_:',ŭ[fd2LPrXP,{rKQ7cO ;.ՌW@4uʡhf> |QV Ѝߞamh'ϸ|ީͻP0]?ݘ14  nvӍ4L`de< Jh-G}xC \ _c޹2+GDDD)/BWYh4uUz#G*T)ŕ,Y"g"Iᤲ\Fc+H<:/(0ω1 iČ%?{kU(ױ44Tє1kЀa]-Z4>+,4ג߯iTz4̮/vøM~u&$$d< wSE v[BƍCBB*.JXXX+G՛:O%L-ݛ2)e\//@0vJd~A~- j+j˔{+ t`Ju~*NRkJ#ϷA-_#$y' k4ԏ{^pAjmŻC.Ydj^K{u.""AeX_[K,yyyiAFN 8g~q׃tk7_ۆw7YϢC?po_A\W N5ơU&Kݔ6hRi'Rͱw!ۯbQT,r,Kh:3+_~!ļnnn<DZ,T*-yΫ""""bG 77wg^r%//a'' 4i._-V;vbe, a2)0R@&-sQbcW7vH+`DZ620D Nm8j[k⊝k2oE~p0 ppլV9qOGxU슽j‹%Ne )0!F/W>?M>e] \: P=BT:ŌxkxVߚ̭S/$߲߰U(xv2)<ܽ[ ݑ!ZŇcnu\CDؤ6dƜA~. U86gV 8!擽ʎvSPO rƬC+Ŵ9Amnn=̟z|ݯmW/2/݅u#M #v2T*R_TD]a)- wk^C½c G<7WX.!nQ@ 1EdXCGRoԫWϧ3y)+&ĴĴ.B:y?U *ArՖM6m^GXq6 A}WkP 傌<#եXeSRRYeY²Epbʳxy}1gFNoݺ7rb7Fo|!:_h8!rPFuٗ1?__|mu§o8Ͻq*z5uq犫:v (EB" (A p`XJĢR߹ؾ'%_÷~+EBYRd!ErCߺ}--dI1K,-(gwmz>=yDDD7%ٚk CvvR,..6L&l6fA3M*+77Wȓg5c3*J>׷i%G=} Y<@93̝wnLyo !eW G}>I3 v|KǸ9:(R'Y)JhFB1J*JUjin%<[5wHezզnMinJg EojR \!`d'YxȐ\S]ѿ05؈P#5kCjbzv$lCjbjsѭ}W22.H5i9+7?حGW[ 6$Y=@׬UӦ!Zֽ׭iέə%+Z!8g0!T5tO^-}?ʝKd'W?Fh(h4EנW\\LaF8eYF49"8}[(8.wFa_-^Gڽeʸ]Y[E&88Xbbada߽͊GL,1G,,)EEgA IDATDDD#HxB8-\+5...2yd2>f3MrY} UƊFn)ĉۡъ  sO84kS֙s F$&f@kOz/ &ݖtđg殉O qāsӿ;qb]b'UJ=ڽ1BHd^ük{dN*C1KX`qleok0yXғ7mǁ_qRNB+uR 5>>LyZ Ȏ3X3[-ZGNQ6wvݳ#[y4whQn㽛kVfYKCu aU<&hD0WS:n_&Kj 93羭;( & k x/T6FU#p'#H?s<e>V/ݛǾ6`tA``ޕ_7 v3܍*f=%F8b扅,obafɹ?ع G93G1’" #g8yN/55bxyy MݕJT*eF"X4-8...AȞ`ppײVыƴ@o_p Đxh0w:55E[#f&5\tv`WSE3 x6mkMNN8P2Ck>Ԁq"~B̍EO;3 [wl֫2ψPE)JOl#L⨔kUo@v2^ 6@)2OQJ(U!qq%lhi84GVWܾ,˰e3cu3{bS~>/;=˩@Bj1Ir܄ݗ:B|nRK|H;-٪A(=G}-S։5s-h7y׌tUkH[#w!᭐W˖mn~{cL",?ZfH]_ί#$QBp A6#6 h`q$BuB6l8)xfoE, B1mK\a-.PI 6X쇅u`/c:{wR{>rE,CQ4 3iPGI.b><OA{ ''iJ.JRTx yeoܸkgWVs9ŕ-`g ۹,%̲ +yg{XkW.㨨u $++?m7 */ MTn<[,BКx?l4Fߒ3./VM8u1z7@ +>{p~?>*'Uo HodF-TܶD"ԩWRR?? @&yxx UD"B}EFqJQjٸ'&Md}y^߿nݺ5i!XiiLVyαN#0/̰NKQTSI$ EIi EIh!8Lbݬ)˥,J}9h6)xY J#(*5 |sb12I+"-xQ$rQy/ ;[smI+a Ft*ރJgO'Eɜ0G(@Hl%\%rDBQ(V Px+g=C%'<@8,Q@ժXZ!/_X,k׶RRJX/Y{^!$33֭[-[trr塡KHH T1%а]ݝQuDj_3`;u UzSR.f9##C0kTCa[!UV ( B)iTldr6M}E=kř?=~u-wTQAӠ)PTIF/ P'4!yHrފBį̣,kQ4veB؞!uUTBgaxya߾}[Vk4Q9͐H73 o_w 1PJq~}y _uVآ+wٔzH 6DY}XMOw00y+BLA{"U^taÆ2isj#h ( <'G_Q+w(IdQ^}"""尗8k ;w.---//a''㸻wﺸh4)y"\`#h4Zjݻw~-:t2O7N/Yj XB&Vuܻ,4_j4hZG dDD Ø洴:u<'Ow8J62KCkAJǁ牄dwqy2T4 ptС 7 @nnΝ;N:5nܸF*0$<4ا?N9G"tKMM}Wu: PJubkf_qz߮ÇA'Hg;~x<Nqx5󳖻6CV8.$$ܓɻmmaNﶭ)(yڻOO_a5!fY. nFBӴRfYPTTm}Z`+f ! 5^9u%U;mα+;^a\۵S҃:5r~KGRkpCPE[к.B__at^X'.ˆ7oV!:KCI^m =o w.׸ݫ ,y 3cP4hݻ,Y,;4>h k*'=#'Cjm۶f6W: հ%롛^&M5o1H.8Ύ,p 4+D ܁sB F%(4^7 I:vGXyI{bJU C> ZZQ4 ki޵CBhhhƍx椤$Ǣo&((h1B]QFlo˾_y{yZ999:NT''G88.66600޲} OOϑ#G~W{k\aY!f޽5;SB׮]DDDgpWXXj]\\d2Oxvs'yrgggV[XXl&3C>96\6~o@&2aұ=ZKW*.B "aŹR 'l9cgվsoE| _h”ׇ/=c[J8϶qB0$W S>g)+IQG <pʁ8mH r,^twgNUe&)Ϧi/(ZE<ԭNێ(̣t2\tDxms[WS˥3)!Lt"J˼<;rTZ5,.NKKo_{jld]HJ |Px4?:a°?[yשW}wym+\='X:ԩa9$t K= 菴F#˲EٓӥKWϨ9ST,r,Kh:3+_~!ļnnn|iu7A6?D6m XES8WRDDϳGZ#==l6K$akHLP(rp,Xqݤ󡯎6zzF6uTeM];:6 pw(6h^V Ѝj){> <Ei4З_~lDž@*W*w%rFdp IDAT묵6V]8U*UxWR TxW~ 1UVP])zl(W9pYKL>J}G@Q[~:LƲ,qo >+!blSg^@ Oמc~߬@7\ٔ|;xB+Z Ȥ]363Pb)wbeRx`Ni$0;Heii,N9yG7~4~m3M% )&w3zmw?%gN&?7"7oz/5Uq%x cǎz>\rE'$R op[K[2ֈ*)[*(ǕZ+kިʀ%p_g5 ![?bʆӍZ =+^С"DrίT瘻tq{s5fYC*%=U{j'#vv{ĩ`WHMa&lG]U(k^pݻw+>߿?om𯩥gpϟtc>?~6+Hm%N&r.|ià#듷y)0YikTΣ3,`}rAyN=٨aTڅ?mz3VW""""^xv45'O'$$,\> wqqBjʭZBjՏ@iXyw>kmjA\B/8Dv5zx'9rȉ.Zu:tջYE` RϖdwF.6#19Rr,, zo֯g[kFWYZ~'Q妬#iK soJg#l?$dEJ~VduxE !V$6CMEn|IJ;  W׍F<~b'""""vT2UۻcǎIIIPM*zxxDDD֪U뿔J bW)L=%0SE6:lKoZ۶oʭ yq<0d%AJ\ >4T[MqJKu-A_TҦ[ %}!&dwǮ<W<ωw ):vn4OڿOWfo2y/?6?d Ps k)\kR֨.Ҫc sF ӗDpq OEd4Y,y^Z<$Lr_0_IL=U!T$LqFG%I2&Y. ϟoZG>y\3-l^ZۿyZ6e˖w^rr2www///www[  sqO!]QX8B2ޮڇ2sdYI<ې5r6AesF2OTձ:b/@1a/-CMeY`{Fz2@DJ7甿jmڅ/}AIިՏ$I3-32)zn=6@W k 9{ 6O F$gI@ y]Ivt_qȭF絟 M7]n0_^:߶qJ|+H}fu{EJ7?woK@ uD̦Xcu50#"޼yS]L1.^Fđ5CfU1bX^Z6Fh]~:7 ֕8S} 6l>󼀈N[]?w#k 'Z Ggҁsgo/&n}迬|Z=>Z=TLeZXdȲ\T,i4)>hla>d>GגN5nЪ}/u,Ie>Jq嗏*哌o`缥J jLN0_ܶy7aS-$c=ː,e+>0 ϖV/Zi*+"[ѼHT p]T|_C<ӅLz1K^_+~Hɩv,Ē~3;wqmǐpbޥۈU/WB]_͆gkܦMǏfFӾ}{VkkZ*vT oøs@F*wvA/itF#h4M1_y :IsHI`Mǒ 4mּ[R3ZtX6H)eٲ?rt}Pe&g(ɲ|̙ [CX,;w(Qb_BBBdddxxxppp@@V)^̑<r=:sLN6Ѝ޾cZW^/}(R N4K3ܼt,Wno:|stJ?}}6juz]ؽ4M,hfZj?_oeY.ӭGkC!c3\ܨO>ڱX(@ŀ=IrlvW{lݥm}z-3A!)oSKsgd-?Ϗ#ɑY@-Z_I&E`XVӋ61`V0XlY^'V͓|b䁆)#YF]C|9u&gws86ܺfI=bl1wW3͜O!a圵X,PV޼ytҠAJ@jwSV踦Q6[{W%^3 [^_eAo5n=]Oؿh>4]u^^+ٽ֟J_]".zZFw2\}|;nX}f$)=====`0B55j׬Y3[*FIIIIOOk,"]: aDMB}c|ͭ߯n[>I50k^Bχt9^$=9X Q(P7p]kZSRRlZm6M_r|Zcھ ǟ:._|6%:Fպ"jٗGCS>> ,ޭϟݽ\rw5;?N{W$0 ~)ֶೡh Udknm.ٯ|A\qQGA0 0][zGx^15=]G9(FeD 3raFBk׮F:IIIǎKJJtEP+㨠J:J.*?yY+ Ytwf"HxexaY#ʥ-2cXN<{%J 4("##WZU^r=QJHw.Z^բ͖yC Y{ɳG@MiVvGd~g#ccf_cӯު:\vwz'2mcܾ!#RW=_>:ag$p5nnKNNw^"E\]]SSSM&)ݔn2222LPBtڃ~Ml/i Rl?:amԆO[<)$ď[~!Ck? }L9ӁO8cc?\]y(&C51tWJtn_6/9iw?oI]|늻ǶLl{ܹK9:t@[?iUcI6kޡmQja Ydf{0 vmxgϞMHHpiZFNsrrrrrjZZt:^c4ꛩ/ѨYM<ʗ8$:HZ:0飚U}Wx^b`Q [g>_W/ K^}T7ϯDͺ5u@ƇqX^1O*y"=QcȦj:bJj x^*UTPSkӷ<:agr=xkr\bLD+yiϋa5Y''>9ahҕd4N$Hp^4U!99M᪃,p-sWVInu[r<aDٳg޽kEl6->NNN^bb[]kWm:tNbΡmT* #v.<ԧt @z;-HJ2J71qӱwn۷ZJo 231f̡cw8ׂyzH$I2xxxxbpe}a\/] `0+W.<<|̘1;$ItVU_4 IDATr!8[13Nq9k?x@3Lnh:t|Wdr'V"TÒhԺy군$4k$&uz7>:`+ƭ[8q"g#gyOݎ;veK1]f, r9e0 |x5jɓ[n=vX@@o7xqZ^ZgWSa#<)ǙiYԢ-s̺YIY_aa(VxW^UKN<_fddprr}˔)7o yZL,e Vu7?TaaYﲻku:] (J S`a{[vw-0 a(T O:&e0 $(xi)ҥKݟaa'CK{ܹ<aa橠s8gaa\Zs6aayYeZȲg2ܞr'c19/a'AT-ݵ 0 0/#*lcayP<aa橠wŴ4p1| 0̓Kks-Zŋaa'C/-kaa^:D+uLˆaI siSRRTaa … eayyQΥ9g3kaaϟOKKsuu}sʝ<ը SǓK-̽‪郸|2էB.lH.:^la틞 0 3r\2w3W=m MOh0_*55+L| oWo9,vANf\vsۗR+RSSnwW)ey~0_2m~R@A++;NQbzI LwΠ 볿ʮIf7@2a\B^blYO݀W/8`Qxg#őtbMP^7'd岅ջĭtUPYSm,덋W^a-XЏlV7Gd*%$SUw}}}}}q1buEo____߂~.9{=0SEYLu_+nr?Faw<}nm6t]ʖִM6[@[, <Ϙje6"rhm/,":lݘ =q?/DooVRI;Bg)ajciګ#kQΥ!1 <34r6l;EO<44mZ۸K<eN-F{KFT^z!ߔe9n˴dY;WTZ,rZܜX㳘L=bZc-sBߓviLj}Ӳ,?yxO\rdlJ+VOxbv/^VZS 0zx......ŋ/^3XVk4yΛ$5$kw^_0d먵[7-U~fA7v L7̉EԶ$ܶ:p@a3jαXԬ^TQy^`c%LIFKnڴvT9k3MIm`׋VZ@qs\QY_8"bgc\XE-wW>KMO)3eM Ȉ4 NnצUU;oyCZyy3x⡍1oP˭S`kNcI4>ȧL?Ց+f$QFny6?|;=0 QK=]+o vyR!trpkP +Gl̕DZUD<,q7-_#E\r&W 8x#^o/ ĕHOOݾZކµ4ذ9e?ȦDq2Zm׊;&gTWuO1 <D1hy-z^V.#uA,3@xXpoW ZcSm<€KB YpGN^:g{9>Fv.~hPlx%O&w>g6n<|)^S??1%MOB{a,ߣV8XG &.gszw.MߘD=C; 5T WFZk'/ަy8U6g퍗 ?ݑ@3{T %|pGɀ?0(%CͮO !:K N}zeM?. oa>?0Ax|JVMl8(Gb2A”0%%;< mdiŔnQ}1ˆD ,Z)II{aۃgkԨs};ߣWr<.ōJ#g{uِ(^!ZBYt0 <tK9cyOEz_qys^SudB8w %Ds5b{=-]E8F`?(Lxb%-$DjA>(ĸ@)n$`,=TB АVt D?ni ` !|O+~t$Dwi߀h 0N|"Duzgl䏉+o˞[hIL_!~GEj@0!Ѫh p#FVM& SiZ`.!ԘV,bъ&-LiPޓѩ0x(Nhŕ@|CI/;!J2@Qj&=/Pѭ!`AgX(JDgZ1IP:_]i; u/ F7LjG 0 0̫ XKlo ¸ !\(@zw yc+~*qÀp0Ӳ@sBcV>aԂ~?K @[jZ !V s& 7؀  ;u} 2INa4 'Rց @/|(Z!BD0 0 0 0 0^b" P Yl t7o \IB`:F3Ec{[iOZqН.H@Obkpfqܭr.й}wb/7у0R^X@ŵoeyh%mrxVŔ z '5 TqdP2kW]?5&ÎyFch:#T됋B<ү^$@?Ke:)@8wb k=4Eb5--`yWC@ri|#E7ْe[vҲy'NDP!=Iv(B^G2&}HI#0q)fdWzO 'tŒv(6g`Y;S_sq@_zzT TMK0 0 @Ն'|G\`a >| Mq)GA&A䏫iCݘftns$&ѳq9,' TFRe>T>{&'D5`ڭ6#8.Ѓ^$DXZDӡį9K|| Ҟ֛U G!nfp㎏7D`#vTy1˻(L_+0{Fm[FU?C.#l3Ѓ  t}ZBCU@ Ac~޷_ xhL!HC$n-*؋(3ʏ l"DMk}}ct- miZQ+2BV#_Q i 8OeْqZ5.aaaaa^T-TZkЖhx~$tW|0`m5yhx.@tP?wmq&L)D!ZS=Y 2LO@ҚuTo5\!%Zb' Lؐ2}L'QyVAJP]-twB3UPCg%{6\p #'1[Ɉς OYCs7'чVЙLҕ:TE@\FzP_6T}XȔ_xF]Lsm@ouRQF4aȮ(\Mr)v"Dfgdŗj"*Paaaaa/-U2'>/i 兣*l%j.-J8.odaǖP}7bmZN-/*;~a҃0#e¿0ęX$eB=l(7d_Aוh(j"fq#hτ2#\Xj>Ղ'6~HQeWgоqRZ1@Z>]ePyo)޻&J3ZѪ3Fz@Q=`Z6Ͻ_z2ዕènU^At-%NT>;y] orCG7:šDe42]QlO{`to2`1 u + CՂ"-?Џt+o'rTUaayQ'~_ŋz3"VBDUK5%H:z xW! !~ӯFXUJ 8Db0n$qOqu1q #ōO 0'D]ɲ$0yH( 2eۣ)#Dz~ ۰z{D4!<-}K :f+U4]XE*oЏf;m"ʄ=\4c( +('N  J6G0L)/~Wh1+B1p2 ($dGHѕ6LX G FdĈ.t9F.!daaaa&ף-I4k?wH;%~AF-ڹhz E > $8v]HO @JX$ӭDl!DA4t K1lݐ3Ta8~BQp,0} Dъ²mӊu_,g**y FWxPZe 0x|P%$tt#g b+DQu:% }n'^}LS V\K|~dZm< t0QžACz~m*kMZq\Ry*G j6xMK>u,h-~D[k-PyQd4r_RMT@C*ʵ3tJ>2! TZc|PLn HA. - aaaaxiG%zD"Wb$ek8`7†/зC (RPU:J*PIG]ˤT* H%|K:J(cDYJOHCE|?R+ȉg=)GrS P,ۉ1\?u ;R` DE$eKa-p P y\RLCe,Å=3h Z m:R-`R@)._}*> IDAT-@A0/LKU<.ѵNϢS|,(*1w7_38V//H5=T2e©,|T$ yq:2 WPu/x{ AGv h-aaDņ'/&.kW :?h2|8D-0aO (;%n +l}}pZT |',dfP#oKg-%H/6A@%ZmQknB/Vqfhc/ mlp@P\\g1 Ѳ-cP !.6ې,W;TۇQԏo;՚OO~e0LCo 7 bW:Y'^ Լ^T¬Tdaaaa&ע⥥%HZo큺NTNvןmE{t='D&n! *n%JW8I b; (pEehftʵn  P ycq0D2[=t'} ݩJT >Jt-|Qs0BQ!vN-RKE܅RjO`(]wÁ1p@J:Wԓ(ra:qn"uG ZqYqKʑBW.utmDTB1 0 0 0 0 Q2&"+>P9WZxt nҦ{jݷ͵7Zn|GPh#N9>s`FSt@FXTE1#ƀv&jI1Jܠ*=h!d{; )Nw-|CB(b!q 468Jңs⟕ ۚ@L2e bә@( Y]F^'WyDAZaT܂̈́9>wtH{Y|1 0 0lxDiiZkSJRutj6rnViqё1 #}?1.]WmxcLV}pwLqDt\quчRs~v2W!„u MTt:m+Ҋ-_B'$Z%@)`BeIZ, 6*:SFDy*IW[Q#q@K=Q6ưācKQWyn PN@oa[B<o؟u0@_vA&aaaaar=1zT v⾼[b!NX(@]! @^u[Q%~zCQ7tt*XC{leV$@iZι!@Hd?>@qWs͚|igM \$q5_ % &h7ܾ }Vs -ZntWD{ +>hq!458Uݙ¥s&Y 2o ! BA_8XMA@+X1PV=/( VR|Ɇä.@ 7h=aaCņGSэZQk@GqfjbDB42.ZHs"@&8]L{/!Ew]CQ_}VIGdqqjټ.Ւtz΀LjGmj,:QB:ҷ/cSzTP V#o#*hqfUyd fR@Bx9͇^'4@p]`5܈"#:}H+Бo`F{0C[@ofc. hҌ&aaaaa\jU`(eTˆngxiG@S"܀jkSE{t/i׶edc>QkNmHP0q{*]+5 2-%D >m V|B@)EW+PO7Ӳ BT(L+^EhO"v,{bIhHjbZ^sMZm7R E=X*)xV 䥥;ڠI⏙qҊB~-Z/@ ӕ٤qw5A$DO*H!KžE&$=$"RFUooKǓ4ePV<!8 1 Q4@28Ύ=@o >Eaaaaar=*^Z@l:&DUq ۓVE : 8(G"!"hg-D8)_:VZdft JB]g8.̠l螑2*`+&!mD!⺢J*8X(JD'h3-tt\3Q&GI ΖKY8,,' p&.tULk2i?tLpSXJ-@iJ/"GMpIBVt<Tӟ$w t߀zc.Fp = sFKOt t&`! _ 2V b 0LAZXdS^tm" W>PI2 0 0TV`L)Dc^t0I`.A+" #; ئ(n޵ɲSy1BÎZMBK0(I,Bz A87:Pq/H9U]V Jĥj!ɢDu.D9j?{:,/ Nyģ"&rZbDX (A+v^2tK^Sk![@v !yLx;q!Fhw*XWR s%3RCq( @Cc&/`@-!詖)I9J_@ 0 0 0 0 zTdeujG+Re hIKͦѝ| OgPIHG|LErGI*Q %S.5Dy]iij+ JsP ݉GI-Ǐ]%=>)2HU uP)+%DJiA$qG꫉ 'ęCT/ n-'ZQƄhJs$:|ByMZOP} D K@*Rgdگ8g}sګ /Z6*bB  HBwVc4yZ f)1\sKZER*U%D)5ACW ӤmuDev0 0 0 0 0L.GK+(&- i䷻QTuK\ WcXLI*v{Wٵ췭62y!M) $ѣz2QAL5/LKO߃֬ʒƀL[D@4_a3:n*UҤ_ em?e/mhuBgZ%l뒨,$iebooZp!Di@U/ 6|֊2~x)Aڝ Lw *≇|"|EȾR A81]7aD%1Y\B DQ}DΚp"DK#! RU%j^D_~ ?qU$6`A{*Ew& '3i@ 0 0 ü"̀Z (տd ӝDrH E(:ۻTs,ʢS]Ab>dei  TTN"ջcÕ#y/s6_ѧdnf3q] q1<PD3*zLS\^,|ȋ@E(qN!G4)5#}fRҦ!Ry^L% { eo|dC? 莑hLZoIm$% #zIlxt8=9ψZ/wɴuXy"-@OMzx&+َRr8!&>t`Џ0 0 0 0 0KVЬ)ש2n'NQ)zZ R8b&NAЉJqA=>~u.(q)^%sD-G]j;Q=NblLDKiCMPS;F/ \QN趕e:I!*K6Xpr^`~0dp0VBq|(߯đ2']R2DJwLDҀZ#d5QnoxM9YAw:qv&Ad߲Kϭ"Dt5-r2ፗ Gn!ELR-TvNZ؛JuX҆q (䉂 Nh @'P$gw+J}QъFaIBD2 0 0 0 0 Q [PR@\LFjZigb ;\?{ee}Eߵ(hXb4QQNǡЇ&4,CQ *"* T E`o.\ZgsνOnP6=pC7ˆ ހ7DI4e3x_fjW1`5͠Ly-#}T10PSR9mJevi]޳k2x+%EZЭC`-529pn}]G% g[%zT{-R]^۫䳫^C7Qg)ݮAhIpn\inmŇ?@ |?gg޴5v ֍BK]as{^n?XZQ_bRXzQjW!pj*gbQ ~gjȶK].l PDo.MwB|: Va*u: o^7{s_fi+J/]:qhrJr$7yih)/a*l)s 6h%)fQK|wE4i# rGw>]kuvuK[%XZYE`70yS.OQEb³#@ >"関?TnVrXZ] ֣ vou(4&X7VHD?Qt U! ?Vp' pn[^#\Q/bj6~w]dxRn6pnv\}c%p؟Vݸ7(F;(UpI4 3+b:G*gIU]e˳@?u̞?tfݠq'RS}>[zSXI:2SgVd!z Ž#.s"E3/9H{eR3o @ @ 4,oB\A7 0`6(#k )zl:WԆSQ?+󄡲Mum62zL,lwVq Ӡt>lXF%]ݬf^n2gK1S_5Ylξ>E@gQ) h2X`R\FZ~iuEÝE櫓΢X*sn|V4~OFmt)*T] A߿ խ’473q[;F,yI{6wZ⺥gߣDBծX^r&jC r@3W8u~w[t[ @ @ ő`i7R[+툆 D_T6w6dbfoݧRl)o.|>,/DreDNPH@egt PsM;Ù\e[!mݱO!l? l?oTTb˴S{e`MϢ2Ejv8BOUpQʹhhs Mlhc:% YD?(*D_'(Cn$06ƶ:7ԕ*3US&@ mhT_7W &榘7nJ{>@e/dhkޝTz| |wD- {Eԟ#ѭ$7t"J7B!-u`JVuoɹjB+{mؕ@ 5ٰJ l kWHc3t"^"`,T/ſ~E'L'H0\W<ʌDVH]K늉8:uqv>l>cø1)[n\kTr ]!iDA2@=?<ΐ\,, )P7>jFjm0L7s~X7^ D0 (b PJXKc0]4Q:fպȆd[aNGA4,VKUp?B#]UΈAͥOe`B^5 xD@ @ HN= -ݔ][ͲMxdm| }V_Qב?_ k7CzE#^rHICinl?_^Mh`y&J?KQw/ҟ!r/%kfxG)P8[J?oMפzq~.R2:6}B@gp",7Q_v0Z-L0:G'>4tth 8@loϫYbO6(e%\Өjz'ڸn{?\`J!1W%RK]:0t$@'r |\]&22ˌ1m*ͼ#)ڃvbBG[ IDAT@ @ vy$XZVRh94ڽf3u@⍀Ny4#Dcng-7|Y˵^M@RH@ ͣrNJ"v"|LgfźCcm电pO*X yF_ JޏN sRtJΊ+W.> 1گ+* tW&Zkfb#uJAu*Ҙ1`*C8E§/stD>$}*TL}a[j`J 7PdoO|ƚM;y*oWxB:Cça¹Q%_ùɟ3d:|17k3`p-tJpXe6JIJ.W.J}S?s+[7nP2PMV. t~z 1%3eVl,uOTYEpF*upQz\&݌QXF;fQzOe$7};(k5t @ @ "\0D$ǯՍ"mr$Rqz8ןt te.%<썡~ּ gY2ps%et% 6jq|8;iK ;I;@Y^| tx4An.6ݶ'e+%"-ſ@^:*5sӱ|Wny4t .hƹdYv8֭Щ O*`?ZKfa0ڻR+O)vabtF76+7zO=J|xݡVggPGЏ;Z233י8]6B}"e In,33@qu셠)5_}}@ @ w x_9 G{:d iGx3h5ȅ_rra XˁFK7R)F_zI*@ե+@ ~ H#߷ʫVn|O?ܬw.0SW/i@Csٓ8V'Vg~?7%W-qH+cMvgct=2 J[xe\|3.iйxs5Xz\ e+ke$CԯƁ ̖P׺>G~mn W3 OTf{ql;%Jom?r8Kb 1ɴZSghXM\6gZi}S3`! 3PGC~l]@kW}zIr%K~.щ2 /./5fqY aJ@j0MHFK/@ @ @ #Ҿ"ώ5f:+tEKn6zYߡ_v6qB뒯bv/ɝoE.fNݸwRTel(~ef(9].,ܾxтS^I˼6{ikjY| 7 H9\D'""h"k#DqU*>͆kŞ}N8ӏd0m~F r xؔD%#Az )\Cgeڤ\"=/ 'c_'eީY'ВǩJ٘+J\MI+Jz_!L%,s~Ͱ~`xZ#L_/1Ar6'z<.o'o}Ӈ|v(MFZv'.ԇIzƓtn!1uQ⩈;h p(}@ J w_sd@sn4?q6nҮ79%Q2=C]j:\%JMO^8%ii)8k&3< ~Xl>R@w}𠘍q^73xA66cx`(^ N4Rɏ[A tf>qUEq{5f2e0_jYßfxm5ƅ}ГtSy;@hB>iQ[$+F4Эwïu}8ME=m,ܓ m,mک . @ @ @ #m+o|S} ҇k%;43k>5LgK n@ /Dhh/CPwo +  zҔ R[^R0Ek K%ݼK:jI#An~E Y~i 8RޚJ-c 8uG:Jir;m^6 R_- #c zXàw"V|ʈ[:nj̭ 1fkJ/km N7A A5:u5?ug/D'+^xDM J8D1K2@ @ @`Gm$wRzI5m?}5qC-FiMCJiq*/4|ܩUNW8o>_s"]M~ܐo~dqG-TPEFQOTtJhFm}We`x^ēӻyia}tqMu"E6IѻVʒNVn u]>HG} NiXlhX ЍQ^%Ş&ܨI9 77.ڸʄ%?ԽRbSZq)Xwb.3]hQ)?q>3/OrSWٕ>(9REԎ ˅B'QzY{˵3@ "釧fӟ%ެ ~P:\Y3mW/,g$ 5kg?Le?,{gB("*Sy1 mnn5hlKr7Ů.ЇU"7t:kTˢ'F7ޟ2K0^2]W/峩 =8knlԐ!~TimSatDf7JJҍoj󜏒kՃ @Z:Tk!XU@>]kEMnj<_,yKN 6k7=_OLQO2J /Ya;+@ @ H Y:ӜX: +=?uϻtpekQn6[k V7G [(tO%ݕ &~?mZYJflKzi[- 1T8Uw-(ASs}Bۨ3qYlw oB4rktt*X qs8HW7]FJtrj;LS)ಎb͛:'(Kw,?r^,mL*WQPٙF~?[ /BrkZ=N[e[;عځպiq&+L @ @ "=MedFk Z^RP>|cՁ:^C0A-E/(-y|ק57۩a^ lXRw& ML}vQq蛃O g[%2^?@R[nmA!* ]ѺSinKGRoEJp݇z9S}pQ޵8D5|#*kIWH.YM\-WӬ1CThEZ#. Y<5"J \AWWן)bQH>ͯkl4yYX1R6~ŧY78o\bO(4?@?!L *tXabmQG+}[KOgZ|A]sG8E3*-fж$h_j E s4Krm7&}vis"@ @ w VLZêa(]m񬨟9=oS$&Gdž]8mF={d!0R&ۀʈL^b:t{kXilɺ  <=ۗjxҝ֒Gs6T=CЀ년I{[D> >NШr%^SOmڔV- Ke&vwh)AYh;)US/< =Z1F`ubUVg@Ks轢y-mFX̻e_'y7Sd{W'mdz*F` *JkuW @ @ ő`i=.fvi%N^d/WQ^Mi7M]b88qVK~M-ogã۱*nNJTRV>cܳZ/Rf%+sډҐk֬JZj %|(<Z1 tOщ4<5'6s6OyVFTX7c`:ZRu[v-S+$ 7`H_PN+e0+Opn3 ` _QZۡ6Jsu~2ĥZPV>C{k@ !1Óˍ)gmխuI RA馣΀yv2 DiNTO؏lӳqh3zq3ROK: {ukmKaW悷-hGQz%e叟+[fMAi_=R4]_'(c?@wXnpnП_/Pw)7yO )Uh_t(-~^ﲿ؇Rf)m9~VDžO?&  R?F{nS琺Jn|%;n: s(U^&uD-~w]f3O 55Uՠ nK@^S,0 _u1y݄}@ @ ] B\Kjԕǭ81{βRjk_9mɗ(UlzaȚ~?azy\~@h|VS~48B4{wS9A i=55VB^vTOe0wbTqTfJyiJ]$\f] k /ܓ  hǻS>{Uln] W?O}-ajEW({dW֍}̺̽bx:.JpC@ZqOH?{΅ B^͹~S!xkD#F:8+5Tz/Zlx\.ژ13M}t;^Qyqem"?}ƝnnlCSZ?@  13{tW/PaKSĞq{p(fXRx> 3d|}ir)w6I!zm qns[p(^-?L&J]ww+0)[)74CZ ~h/@eѬtj,Fu3Ny-h,Q[p̪QQ!2pK5m+8]J##l*սT2+!bEw[z~Ԝ+t۱ >WxT+@ @ @;Kt/t&FQJxg!AJX.m-SϥtSD׀WhcLxJqp֯ʱvx[VqntAkÜnJB0p"gW>αⶸ6p{.B{w P߈6 1]DX8: IDAT@ wHQʽE)j #W1~JHb/ ,P "'x))G@g' FI`ݶj8m3ʫPH*"twa6HJ[^甿A+?1>~dE.P|_] +f񩔕絢4y_ܨ[216rj KF.޿@%@5ݺZ qrluvSv4_OPKzw<`~.$Y7i^MnB@K@KQu`\,$3}]GcQ @ @ "Қ{y 30gjy;5)5"Ȟ)VB3>OJ  ,n1<0O)-LR3)DR 6ڒ^EsOO) A#Nw@ @ ] Vr tO!/drK5'$d 4C [A~l̝mTl-ٻcVT}ials:䵫]*/'W~P&[[$0;i(EBhdL3;pޢ1fLyYk) PѡVu)Yoҋג{yƓS61SŰVMe 9ɝy4/;4yEʠX+pp&AG:s)9-[{Ni,o}duNw; AN- $P, =5jg;NTZx0lBt@.$oӻ;6/;s>ݿ'{V;4W4ݸ[iX*hoI @ |!_wuj'χO t8 +41)]~$nUxpjV \W)l٫ċ6hjgZ?Bo-K"̑ucxJN$(<vȏ*M7U4VzKfnHJ߀ qTKp(>!'~"JCnԍUPC4 U,,L_SH,uL6[\jщ| @P[[ws+HiuAGޏDiPaڠgoV:/ORWJ'5}b|ZR_["@ @ |`ieUPcѴ}ΩڣۧDm3e@)>Ve>Ê]Un 8k#7ӻԐL nx#Qӧp[#բ4%o()3SՋaʤ?]tABryqR'V@ Ol2nZcS*P^٠(oqdYcȖp\2[0uĆ=g褮yOՎst}A~c3暋j(ݩT ˜Ä#`~[q`9G9tjyO{V>nGC_#%SMGu: ga5ݸZm@ @ @ #(AI^ %uS"KibƦ&_QM/S:AiEvE+0Z{QNy*ZWRZ"چ#PpS͵s"V9 F6*V"”UeR}3q8Vp;`R]zqz]A RI=[7< Rx uQ֫ͼ(=P/Qx̧|"럡S (tnh%M ނ2}؎vy.J{ٸRgܥԜ*^ g>V@ "13[u$7]_7^c}6|6@3PMHskQbfcʔ]=Kۆ> .5Ġe"]&2}.U8K8R-U> |z"R'G_PB) 30П8Y[ܶr{~\HfJU#[se?᠛ U0U] udXrGNkĚHP.b}BCYˠu=LHMD S֏Fi%G.R6ߗ}]: +KU@ " mԸ)\tPtݸ ꊙ8:]*@ @ (,m);hM.>NkJ.JBhjĘ1t8@x5m~˥иZifi˷{+Pa@k6OSJW ܏lj/ xҼ\|>}i3ޣPճ ЙSдഗ R85`!s`QZ{ʜۚ4d٭wy:~(7yB6TvѠ3f)rSuۨiRWS!JP7fg_u%^6: ȶN!6+^RRh&.hxfEU0 L -tC5跽HleD6s/'re L˵Uɔ9g*,|k;\ޟW'FH 1 @ @ <,R/V)ep?@V֤>'jW{ 9خEa+@vJ@Ԩ_$~ Sts5N&̟?`-ܩnL RFtK2ö^iWk#>#^5ݸ::eyc)c?؟&mK7,d CskD~q^ڣ`!>|g EXNWU @ Ot?2ϐΑ߾Z R??>] NTm-6$JmOد9*i^?r^VXDŽ@A 8U> #?T3:`U~ׂuXVNpc"XR)~§?AJ(g j8 ~MI\_Uc}M*KS("0 T:(+V}nВ C?(/@ @ v}$XZQ=TH@w>V7b浇^.2: .Jō[7:s h`wj,mj3: `Š8ӏ2>T:gEt2H$,hNpxTu4w O*|~]] ɳLʭgEu E gۃ0?ĿOﺠ{!Fz:g_0Lo<+˓r0Td툋3` @ @ ]$X">yO`"Jr{WOδ(kp6WB|3#sur2(O@2&봃d `Ϩ9F^N'd}>=ǽޖ̞J,aiSPr(us히T)w3dn]^&Ο R# [ZT#jeDtj]0ncmh:Jiw#}y[XVt0L$͠n @ >Ftvz1U7r || G3]:* 5yŢTFR2nuCyV-^D؛Tm+|ʫ] ro!]0\W[!#YXE߉6=]/ E'Jn[ U֜^N6hӗF(~u{ !xԳ$JTV^ D Y(hUթyډz_Umm.?okB{@ @ HV@MqMOnl|Γ & N?L9<L\XʐeqG0R =]jfp(LܽEB{ǻ]( OuI_x QI%$%Tt߈ )ɚSdY#v쁶g):j8AgNLu6X+hWѹdٿM4N\s4:>^H2Pۮ_MT/c@/@ )Cuj 0YRLL6pmk%|^u=${[۠6b @}6yƿ'H'$, :4i#Ҽ%#TZQ}l[G->|(.78:V;t{xNsujXUJygS &t=YLeGaUi>54B8,̡"4(pˮNEfW[=@=|05lSIܟ(_Qg6򕮺\sgۄyܡç;T I+ʨq OWXr 7}+@ @ H GXGO9uߵ0^Wh͕ \w9f[xq:F,&IXޱ st5@fկ ٚPy`aJURRϩ@mVEFR._tK)ƁM^Ehht` J#L*H)tgR'EBwTW IM.+@yYr?2r59}tm h5 oI}cL[6'RrSۃhv6:.il2 s9,\013b֓D+4玴Vn-+ۧޤw{0Qh^IkR c|6@ Ab̒Uwnf_(J5 MB/o價Z@ { 8 fffJգ[~Z[^Lm: G u,8XWכhnQ D-E{cƍSZ}.)7Ra*ω)m?7{ⅸlO.eUmMkvP]GEEdP -uuDe]L6 ?_Kļ|Cϧ<5S)?޺m+kxw>Es9̠ҳL(uѱ/NVBk>Q,_@ @ ,("QLEAFύr2*m۠EL0[U``JMR=( Rtt*JR-DidS,JQ_B'0=>^Dp l2lPݺ֜A Jq:BK D7nYlwM! qW fH XRVF"wTxVT9KICBOӤ{?>8= 8I\`kJꋾ\"tzZ]J'U7>F:]c)Xީ/}L9?TyReCX^>U(#B>\A%tٺ,z1ZXQ7o軿 k^Z|`-N篈64Jm[)@ @ |G`iMT3V떝Q*Qxx"qyާ_tIUf)Eٙt36NN[nэm!MJS-y4x!>p(=Ysh%Sd!\KmQ7BdEE1䔋& 5L/hm9XM`T6&0~c.Jh6 m'r\4 Et{Yⵀ C{ OYzlj*ma`֊IQ]@ @ @`G,PK7~mX6֚l\{<{e!;R"hsMc25-)fm l zIS2,O+V^T(`ІA]]7@M]`YZ6Pk8,e=Fk-\_Ѡ׮j\cgd2韷E4&Fzևt V:}t/߲F8u: Dn %@K)IZ^ȯo{j]MfP)S3]@ wH)9} Rx':'!3j?c޾y.z ?5öTșH5m+fgR#pzflXCF.w{ntOc(R^gﶒߗĆ'; ~;snl _/i [=<~\п'f&KM)EKmז:Q[(UB*P]]=r5kL ؞| 9ʀ2^[b@ @ vy$XZ"Q/;]v=gaS|w78ViX `U6x-#Ŷ˟E2zuwӬ'hC`9LE}0/ 0pGҪ3އ}0oRw$@}xsL}Fi!<RXzß(Fo4Nu:&)fguk_`Rۤ'ol'n;M@76wV @ @ vy$XZcGRwN`ǼzEMTN _?F* PM*)ɞg,AՖcl]0HucG<+y5E)szBӿzxwoݶUVV Zsn]mr59\'}5R~^B% > *2KP˯J~o\K0,ؤ_3n@P]'Pۛ/6{Vz62@ ᩧO@²F2O*—f쐦`kOMy4s(<>0[ՖscuQ1tjh-W}b~*]D԰[/*g8ӜA^#uIYlDq|̺{jMcgRRu%&}KQX0.rF Wilh%);@atLVJgNfź/,Y1Z5VEw@ @ ] VŤXZ@9(XeV7N‚@χ+/jr_g6d_NA&'+9EGxҐhЎ֕d/R(œMyCϽlgn]Z*iEŅB"K+T)\ tR*ήs8P$S,;~/?%h{w23Gźu/V_w12okg0^9ws`%;dJ]y%j^L] @ hDmjuqM9)/` !7}fY&%)7'ngD)?~mP뱳fWy4['ڜr`c"?~#݊:(DkjSLuipuu7HK`B^a/5̖,B*DNRRe*B\nCFD;߉oJֺ>utxE%=)SJR"ᮇ&&xɑ1.s0_NG@ @ ,, 3n`q)S)$Pg]gMzzyפu4J2 YQG%Z.^ErLhoTo^{YY틧P$JC nY|{f(Հg>vCN+^X_^ P\05LʽzЗRFU1Rdq[ߏ]ީ0^@ch2X lF p(uf%=Lndz[-{@ @ .Kk^i.0pnIn*̜a 1NU.`m ^P׺꺫k[+RAD@zo^BmB]7gܹ3ssdiL£+Mls,Aܘ<݇?H$iA^MQce&_saȥREQEQNzxD,48&0av h) .0AE,btXnJ.wsX6\~LRI5VGG*a»Ӷl%gyx~ᕻLhC aq|eFhQHȒH)@3[TpDW%Iy^4 QRoMQl6rBw!;ΏV [R5lY(((((!JK})IhIH|"WVkܐD%L^V x4ERE\ _bYx.zZ6|E`8 Mg@,'ǰPo"NS0z~ <%>.AnCS4J |Lj͆ <+>L)^t_oL~v&Em+]}kɷfY)D^%yHQEQE90D=~ŝf,^f@\( Ld,ZP)Liu.:Kʥ6Sf1#SL^~_*ĽΚ)t(Lvے Ei;H0]b^N!-e)\[.M5c7TF$+j h% 7U#Ln^B+Kͥ)-y\QEQEQEQEQ !JKi:~ٙK=*;I/>xTW>#G יWIL)$va &f34fDm(ST` .`+A*%m!DfZ4VNRi `ʃ6"ӑ=m0՝)‰zQWV1)+ )H.XMjHI&( K KL@ⲇ|T!}7T#% p;VEQEQEQEQaRu(,"Q-SO*]}CN":Qk(Q-I *@yn]Ky2 yC兴Q(+:lyh ITt`//\% U,l#RF>$k'U-`**7Un@[0Ѭ tɣW~Cy0 q5U|"-dÅIhOFfnޖ oᕢp. ',F iHRq)`2`z)[d÷i^((rp4>r'tP"ν<^ŔJoHDH~ :X*?͎M;b3՜뽑^@Dk.-_lBU徿Ύn? zDYr6uo5)DCo0%H1&*9)`+('$^{<¿h7a`:_"9s/8"׏iC֚(((((RA1DiHJk3gk/ ThxZ6}̤w&>ƻIZ+M k:^n(d1]?$wIJ4s ‘Å~#(^0}Qh_x3Iɞ%oEc~\4tmK|XZLRwl0X ( -+¥)ž2L?䆭ѹY@. yI{Mux;Y4C^I`#OPuib'xʆwWdglU8#rg'lzI-)/$+(((((((ļHO:UIIƒLyIaJtQ|*25`f ץx R+  >cx܏*>JE`DL5Ĕv.c%M'T0QfXCxޅWHJ΂fEx96;.[/ʆ`=wi dI_;aHa/W%W=hNoȆJAR{gz".ǟo/FT`OryQ3 y((r` ŋ#bJ>Ki#3T?sћ~; G?ffǔY|Y@.؈_Ů(qA.-ҫW%,D6-^$"K54fC&&OsmڲF`g_]5!90w4Љy񨞎^ޥU>t04T!¦G?DgS%oC\rRSY)((((Ra1FiITeˡg}&vKxV+Ľd֯0W#p |s<$>0M$gIwL|xU6?@@<,=76Vc N<ѳ}T6+`5n h(((((c:aڤ%D"YTIkP3af^bQSR#U#HQԕ©b+Mn\;?DžɤPY@v+Wl2x,jcK?ˆȑj' `mŊI'9r %@|r"S%mK*MtYSL=p5WSʽ(g?Ah BԊ(( чG\H-I>̥τ[ J5$IML RR+YU R,žb%".ʅ\ P0; Q},%w)|0Vj&S6Kk$$ o“@5t<޿ԳGAF#INDL.} 7aw}'K6PͲ;y)=*u0J%yM3((: IDAT((RT{Itd!0oD3Viv,˸X ~+yl.#0βS($vyю мlN:q<I-#no%'D1J zx=ES{:={o㷒Ty $(\1+l80<6. 73mKL;i᷂ :D Ĩ z%TjZZ~b1Iu` ZryИbO_)'4O/!%[?Z((eIBkgei_^y/W\KFJd$ryO^Ay\qd7"P:e=Ht^#؏4Uqҁw̙$L*Ļ ̠S RZqf*)aBNGV<7&b 0#!9EQEQEQEQEQ*,(ԥz8"([͙R/i&S܇#^YP.p%# Hcq70iʓ(%".!# <$/Vroˆ_O COHx :-~ 5@z|ã~r$ `|&*v ( ^(lW/X'?6bƃDdG"ah)=AftVEQEQEQEQb++"u&< 0BސEQEQ$DǤF%ۦ|i,I_|y|< T!}ڳd\#E59h v޿I /{).ԢI9(g.k'9&S̒sd<}-uD.rEJxAS&Y~׉#슥 _uSβ& rS9Kf KP|cٴN?M=6޲闼΀%2ن'L ^ \NUF\ߘj៨(((((1kHgVTJ\""]L ݄Iu4ɨ&WS3 w_4/1I-JѾɒrϕ>UV͞={ɒ%֭P^Mi&''ǸUEQEQ~/͛7رcǎerضFO>={ٓhlEQEQkƍ7nx<bX4 BUEQEQ~_ ? <OJJJ^.\X-Z=: N0~ 6,ⶕ*U*1gb(R6b{v,RJrb/Ez]ڲ{(;M߸gg*?]/l޼W^pwתUq1cڵk 4("cǎ]v-ʧ7 _K?pn:g,`v^88]˾b؜Ύ95٨Ǐ*i@FZ$q.yE>58ہm#7%9WVSZ qdwe|Sqgp\yzE9^)\.vSSS@5`FFvv۶}kmvvu]W}Ȑ!$FKUEO6*>-.ꁏVUͷS7r^4ش[{έflO]o_}߿|vu{\mG\'{eK9 zCorI[7p|I%+Mcۧ?gg@ >.wD@=vܛv4pt&o^<6-gӽ%y9lS3>ӽh)\ڶZڙ}|5y~tNU]sܪ71h,9E) 4ؼys$i֬Yڵ׮]{:thvvvf&N4h'HDUE$ޭsM-eS'e)I>ǵi3 }llr{\tQh9);q0/|/P^8Nz O?o[xybR&-#W8:H4c΁puv^:{mz='6:|'8E%鴩(J(~K.ݽ{#v5i$ 87|sn&NZrO?"۵kEf]ƍ( aO}|LBlMhkvVǫZ=tí-ۮݼ3pۯ{9m[Gq{Ƶ\2k_[Y}硇j3LVmö#5[ eʋ/jgSY+{?(%'Y^{c'|xq6me6]NmQ|^8*S(@czڷo7x &x޴4 )$ݾ}zBYd{뭷aכFVQ>Y_W7oMY8*7{cv88w?|՝<ټ}8|PڜU>2?6j|U9NtsR]WyIի_v7ntY@[zq<>$U7t: @N;>]=L{׷g$<ƵWpο +r\b-[?O>cƌ;v$j#x<^ZvڵoߞhlEQLKO7 ;)1RR]6zsJJINI)ri;QE9J%Kׯ\_ݺmԨ^#~iVn]㾎VQ%ՀVC M#QE/^G(O8?ߓm&E9>hrF.\yP(dY}8ee:˕HB>8 Pݻ3228!pgffhч(ւ cP"N0^z]tZj" _Ěز,wGZ'JOϮgBl_ׯx-8mܹsɹ\p.)sfffrS̓T+Qi#[~ 2"; -c oq nq1s<ؒϲrT/6|ܹs>l(JGWxJƶ|͉#ᴫ E)?$i8u1+.n7$gbEԖP̉J<Rᇡy_k 9:n-tkL+~Ȗʡ^?:zT)mK(؎3_{ό hܫW/V'[{/Z䥍J_p3”m.۾t¨Y^ݧ~loֹ6R›F(؞L O+ `Y#7f5+Xc~OH_VCF|IN^Qz$o(uQ6d^笀ѪĖCP'!H{ݺuP C1(J͵mEwbF8o w(A[r#N4^,Xa-4={,~{wv޷vv<ՐĝhYSbK|O?w~"yGj⨎`?e;퐳Qyo۞]6w<f&Ζo>YQjG7?дOI| ˿CvZǷ.?s7yM{.2;i&|ƒmێlݻíL[uxq@ кuʕ+W +ߟhr\.-|c/H;9"=}<|K t}r;jP~AA~!b; [[} >^BqcK$^`w<4:K4;U7_Egi HaenaOO 0,ϬyK(Xdйy]qEXR'c]Z\ܥE&ӸU_;Es7X8*xmu2+޸,wqvKN߱qUjJ!jxKm/s3E9RHv۶]j~wbXp䭙{P~=[K}k--W IDAT [./XݨI˷tj~=g}>vSN oQ}_O[ [r}IR EpԁU$DZ`ewO@}FA.x|H<kWߖ A^8yK5k x~zkY+[lzg2o5g㦻LE(b1c^o_ϽOfE,~:@k{[w]2ս]tC[:Զdf{Qvؽ?wxa,/ޯ΁/ `cXQ@;+oQ7|Ժ1>לU˿ec6XoτxOb=ze]8M˖h}~X,QBҠS*?Y;wm~NͼyXw-(7_]tE+ߨ?KG4Z[e >m7=; 7u4 /~.ۤ6 >r^ަ]9t\b Gt7 O$|xR[A)= V?c5%߬FUY2}^gT³&/VnQ ɤUVN|ø VwЦ7UnqNkU۽fv4'=mG~-׷s]^:y/U[;N`ۖK?zcG}z?3f}<޲Am;K׬l_5ųmoK?_趛|ƕ'\߬WnOMj<{͊*[WJEn^8 I0@Vk24ivCxuz;f+Ӎ]ۦ9hh^ХC{lsMMz,mzΩpz?7mG?οi 4 Ԫ l]=}) c þ`gkF69מN_bffA(Tp[=8Tv?z r$ICEktFt\Tk : uZ:Ad &|5"qX ?t_"jWͳSsgV;6ϮZ'ѝP»wEh`(M & ̙OrfJh(TTe.x)jԮ] a4gOtM6[{d&lܺZ85q@f{~χ un;yd~/C {ކǯ5m.mtV[jlIݴ~\lgywitY'mIom[a;طaM Yu /]WNRNv֪XXZ|)pH!U&V~j$qӫWVm ׂĎ=\uOqyz 3PR@Қ\qE4kl #cN=nޑVqDNV2PQ*YYYիWRٻwo(JdYV"6hu^ʶCݿ7q>˲\꬚gWZn*O$ ,˅(V*e NaOOMn{I)e8_ ^"هNp\ɀ '$yy|V$K<(4w'n.7PZjᑄ=e,+f+?SF 1t2 8.n} Ҽ.˅R{}~TKX>p^ɇ|``\.WW37˚\% Onp== *jЬɏڲ\Tir![OJ嶊N ކݫ8Wf~yjأ @˅=c@4~xʄ ~縊ٲHuR k^&'0fGˎ\I>xH_?;qN<x=q0w†O&=ųu)4,8e+WNOO/ۙWc۶ի=5jԨTQ"z7^2.H˔&]/o!ΜQ,q\%8¢-Swgo?q 3J w|?<OY"vngڟ>wbώ$x=q`p 6Z_qh(W*'g6[]Q M{w@4vnؿvn NټN/sѢ`n7j+VIn܉aJoZL)uO| {Nز#Nv>p:o_Nm[ǹ3Ztlwn\qPt3v< ]Q]lY|] xCJ| ][n8d{-ѵH/QiQjUT\ljbɞԢGT,p pWx8ԇgY`ܭ)iNbw^ZAeӲQ{Hoݤe:Y)԰y[v~-~^t%ۓeר^*+S ԁo\tՍ?}zx0oO_Zﶞ p%I<'c~y)Ux\+9[Ë{ŋ۝&9)^O-}iI >SoyrN'z=SýW߾~##=25/|ugS*r fY&މGDoS7i_ NjrZGn}M/9-O?{V +t e=V:^ .xmI=YމG_fyjem eZU/To٨V/6 6/S굫X(<3[م*k\gLlҨFE+{ɩ!fSk[<{{ erOhư?{jP f6F;WxzU?Nv|^eY]nM5x /}e'ۖ۬~iܘtoPͻwHw AMTO$r,#G²PU4x,Œ[ZSLZ9}دHjRʮ_&]ʀY [nd>% qsu4k̯UZfp%D;gMdn:t' = /Whr㛣n]>-L ~NSϸ/灷Wz[%;ኞ$OYE [)lmi/'grG}ȁ#Jq%WynE?~F.˲\.\.8/)@vnw>fj_:^_ۤ>,U]}S/hW-/{5~k) UZ|#/K[TM|1UJq? ZW}&:Jg背g,AݦM ~zBNu>m 6q`\%ؖ?' ?̉E׭pEa\3\ݬm%fHoҤ{H eY!0z1W9+SG}`K&'usZgΞp' ٲݩ?Xn٦~?X1[nmPx^ކgz\n_K?i[e ,+SRIke(ʱF^^^8U)D))FO/$IYv;n_Z2#Q~Yowd9XpHnw_rʼnHKI ?RjG EOI 6x!)Ԁ<6wöZIb,cǾgT+UXp(eudwD)ųc`p'=%PdOy^P*+Vhܸ~f)#(܉ pF:ݯIqxA0 ݁Z$z)MPF"HpWN/HA~E QxSƫ^Ă`4^)(rHjnٲRщ㪖Tx \#e .L),CEz^kw WERW)ZGV"SIM$[%l)vN뿻teI;>?:@d ɁD\Yq,5ݝ^5GLJz{/b&_fߨ=+ okTЏrRJ9tmP&镥RJ?!%@<>_9בrL+cYbVEQEQ۶`0t$((ʱKQH$cǎ`0E"כbY-a+_>svDZv/@c~c]ހ 8iii999:t|(Gr7._|۶m{ٿRRR*U5kVzuS8wñ߸I?ܣzJ^˲,X-W 6l~zv5/A [bضsɓ'2Eض~\6#k@djj7X3s՝;wnذaXÞ7wHe. )dVGհcxo--VerU;_>l)S8cǎ1S~2cUJ&< 'ѶG'NP^"+])ʉmc[Q'j],˂e8E<Ʒ 82Eqq:ww-Qd᲍߮H WsU}T'=ٗs'e7ݿl^m*U OQN\xA]J1XWṠ#W_Ӻv%CSaVaHy"2##q]) K89/ժ沒]$IOR^mڹƍ7o\d+r8=%/k;nq a"p!l&bm'j~wEQ* IԀ#kYz VpXRE)ĕٺ/V5#;>q܅Z8 b2t΋na/ OQNp\.Xju-eJZY"mvu('4."ym `@"Q׿7ԲaQOxEmV;ei}>Y^z+XF1~ !'3{l^\)H5+b[US6m_Myώnao\Yu6~7gxr`?^ʖiiPCx %CPY@A -3q펬 Q) 7͝rWnG툇C!ojU=R5;Z9 3H# $"WAC AA9|8"""22RW(u.]DQ;vqFxs nc΂[̈́) i֪-i4jFgF~_Yr[piicv}kk$ h1=+EawT;O p$RꌻS ?#괿k3 HJ) MujDg4B Q/[)ȥ괈 ş&J)t8*U,Ow E4MaBHժUs u;uژ0 vH('"r_j] ԩSUV✗|޽B4/h5RJ)d̓spDp7VvjHͨ8Z5'2C9*ZsqHq p3;Haw8$|ӀxMɐ?88eʅ B}IP(bj{9c?m;QK4 jc68]!CYyp1v"h@Lm:O1a{eI\BQaRfU彌PB(I8hpNQBqCsڨCcv6uԂH8y8(_+E`tz鬷ziڎ߾߶gf'ה8ݰeg|֋S_9Q;miiלhM;#ZtywS׾{85+i\,-pIC q7|g\X:U BbVp8jQuj;kiэ -`ժ~×tDk\; ;4 !t{` Ez9!yݐ qQ(% TAwunw:RU)BlavƝ6ԸB(0(jy41zny2N>*>p81t!4wnÕ; Ln[ 6!gΜl*UP(!N>]^+jn۶W_=n$[ 6:rdxT GD8zt:7o.DM=Bg~d4p=(H+8lQHHxDX$<<**RwGgoXp.,0'ci0tJ,2XtT^ڼr?-[\,];r!<OC şC O ./⛫7y+8ٻ)kcDH׭L{o Dr۷vjZί4"i} 7tT/iD)nI))iܹgϞ? ſ*4M)0a)%`0LfU*}BQ״s6%2wJwo=hT}j .,:Ikcǎ|- WUVBBJ70!L0͒jP(0ny}rOn=j׿A+JgvjժC%%% $) Ҕ!0t!$7 R'-TPy!ԨQcС)))~+XgW>t5jO tSC@&.n%R?5Ҹ#GQ( a )aBBBH*C /P( BP瑔Bl BS98]Yk>BP(? 0"A A#pJJtjOP( C!P&43'Rp( BP(*<PB$HS( B0J8!ֻ{-H%P( BQёDJ(M;<h*ڽLoέo1WBP(0c4J)%  B $LH)aJH|S(gTTr ݔ  K0tG OKͯw] \/y>9.hM`%Wr%Wr%Wr%fO2j G݅*W2x0%)k7Ԯ˯YjǺXt*\g )aBBBH*C p X?뙔Ӱ:ݞo=N/7%mDoHj#81u]'hH))w5U)[bw%;c3/0n98oҲW ;}xK_a=xHXس݂YEsf'I)WϿ /X+Hɡ}_\oO"M0 7{ڹ3?zk% 7ߐ~!B& ,`)cǽF@du6PN*a+U]^A!W JqT>a{ ! $<;Zi SMyȔg&5?>{?yU=QwY߽;O}իbsO?>Zi4=- 5>wdҀu?qG4ί˰;gS6<@ڼ!ӠV_zeBJL9eU=F<}5sϟ3)34r 9zn0J~)|k}hq ;A?gH~jobG D~=sRa8-G֭yvr9x[uRa ]h=IF_N/^gZxur>U7̨ks3}ShO.VBȹf yKnLfL6t7+&Tϔ}Yw,oa2Q+)iҾ^59#$ܐhU/8un}Yllrd.#5Fn)jgU7~\z;wo=v}O#} k]quN Q$GsrKhLgp깞>[{ws[(ܲp-^  e߳'loՂ^ L!(F.*CS˶&a##Tḇ.ycGĖm_=3?A8~r$eT,@U k% ˘x~#z׭$S:7^\y ! jX:@&)uǙ?0љ5o7nys珵__f&z_vb_LWkёrUy~φՋ+O) ۾zOHoL#z1nGn+')uS\^UYE!P&43'Rp(_q@?/wo)ƻy~_Zۋ;s__J + Ss޲B0-yΊ߼,bȴuhc>X7}8ގLi^l f}5*ӟ}񝙫Fm;:&d;;?7+`Yr -oջ<*-?מ?tk)i1KԬUb}lAi`3B&㹗_h_[F.xpr|hj` uvdm-JP.+2LYL4-;ZOޑ0~)<X2q/>Bj@;ww.9CqBpΩRNg fʺh)i^AH'|i{6hO 돖@I*lY͓= 'iJU) [51vӻ|c%&ov9;/々4- \NOkO?7tЎ5L=ƒq3ߙG: ``0;D@'&O^UY4dx&Os{ַKYA2(P޼*xEv6LӼ)8@B{0!HSl\aަu+R&4!_7~$ JdSb`d+tgϟԩ~Sem۶zn_^u [eav^o|>o,% Hu8e3wI^oz7eyY?, >n>%lsZy޳hXzn^t*|QnA$䖟o% ?/11ee}11cbz~y\>j_Xz \6uH0znS<lty"֊sE!;{ -yivj{Ԕ>ϧ .~A^7#u~Ϙ^/o?ys'-˽WsLeД2Ԯ#w]с1ժrz9nQ11_^zʺ:hnHREy>&eLN~=sճAdy>ϐ mр]6ۧ ZjL̠wV\2F>כq5w^e͝nЏzan2`Z3تUČ|輸)~CY揌y/f[-™zu[ySK_Jn/:<|4'> M _FzQ~J7oey^oɃbXj7^zqbc;ۿh\SG ico5 W۲>kx9g|9n 4|RJ _gZq]OTjLVldLawzދ>W֒^}ym' ^{2eñ9&wswgy sY.6לoZzE7>󽠂z0q11YEz\bA 'wsz{땒_Qevq!_xKo#.1ucS?y:.^q!3/A#)aN2NT"#%&&&]qd}>zi-kRZqµ=kڷ3Miz@聀.9J@ պuݗ$ܨpcu]7|_j>`v{Et]d7Q.p/n 6Z$BB]]O?~ҁը֢8=`ɲnzu3uȲIk47@@ڷ@5L]|ZoI2Bq3Ћeǐ(2>2<#-}0iHǾ;q@ GFw0zwiG'hcږQb0t=whic^> /NmƼ[ 4@~ [?Ӽ;`/^;#i<]xsR?zo-X:䵳}do@VUu=L*_и+Tٗ4ieiC%-*gd$/vK>i! "Y`!@@Bvh8˱ @,@z}Ay;޲ɨ!OzNn`VY驓=%i/)@p@@,ټS| 1』ԾFv0}7)wjG]C°Z.2F~wlgLne;~Zz}o8nZ+~u=xIc,bZ5KvNj$=tЖ/[6 '32=z0#3Gݻ/==Ǜ9Y=ѥ܁81@ /Rn]2mI^ vIѯZ>nֶtRK׎Y.-{<@淟[vZ&1تUvnón }K[> Aq9޳KqyzoFt[0++z/>n{_]ӆu$ASs-r>.kL]HY?}ַG:H9lkaEF7ivXV41>}t¢3M ]UӈV ֔hk8bD=k$lspBk5p~q3||W% !HyERv]K<>H;4艩]uB/_+QO[0I5hWRQ:th(܁J֧eӓXn^o==w`Y]?뵠8JDȎ?se/8X(}5Gju-}Soh`@򁤏6<-:ʎ.ӱC͇F޿4,j.HJq[wx|B~]WV<|Vz5m!5Y o(Vl}u寢@HU!p&0H,^5Ss_$o#t8m .eJ5J\ZMYs"t]U0 RI[8. `{.!]$V 3pf `|9= R8ByL%M$4oN0XrAs"pYI ֽ+&5)JJ#Q!=u@|B|zpt)~o== R-XNASzO\uR=8^0yv. ?ʝuRk#Ây]_<]ZJo)򁮝[B .1xP\SP  h)f BTY!hu2y䗓;9uX %!HYU)H@4(ex(yPJ)MCJ A/ Bh0n_?a9g~9n*(vRFF#6ܐu[̸: MM_3Bʡ\~s>cYժ٬ */tsh[-2xuOIǔwY< Ud1F)c % 0~{o:0v*!@to_WA,+o-B _h]nmd̏n@ B%v;cÅ {t7nV?{}fFW'32y 1U-Gv]SvMZ蛣aV® |b c8!qEz@vo{,E-% 9D2F!P)a[r+nTn(`R.e/"o>}V1tX(bںn3}U: K X~121jY19{foZRi;/̜s@n$TCJ)aJec5Jk ol?tƌљƔQ-zP?׿N75u%RJSp c Lj˚شH$РEf6φe .)/~Ҍ1^6BlCPzb3^")XCϡ%Lx+ƀ1"J)F z=oVԂn{]! ?n_QKul5)1g߿AoKFuOT0XY6)3hS4ʜ_wJ^TnטC|v&@׉Oe$LS0 &!/ $> 4\۝ ~8[2 l/s]toT~v,+5(/c1|cg2?q633z1q76 pƧ/G׺6p1fK'(O^ <߻c{w  ?Zm\1v]F"e禥1170of$?1iZ1?%a!80Aez"dauUe6ӗ 9'E̔l?n#Gp}g ]r2Ƙg7<)c'6=ՠW*`Ua<?V=FviҴ>YsF9vr .:̚``n xwм3$]cշo߾z}x<-Dc'N8|`㌇Xѩ*jzH`]n<|><\#tIuуߴ.NU5/FFY+I34N0Ɗř2vY/d~쵀;4ޠ̙yxk}ǧ{wиjξ]vܹ3,8uo }_9sT<٩ߨUk|ָG_t$;g"qHjqv-Gr۠_^^%۝s2mw;2,"ߝ{rcyBdl'/9cy|iP$nK+3`]L7p㿔Տ?`~_h h{ <s=zTwn?xvĎc_3gYkfBq'vJV/l?3`9H78 4 MJK9뿒_FqkmجON k2+ˤiҮQWR+~gvXҲfX]ngr0::t|m.rq޽{Ƒ#GjԨ*h~8|VqeXZK c,Js23Tu3**9 v 8c+9b O pV^%<)yr|pDԿ><@Dl5ɻhմk5r9o\gi;{9i+)eARUu3oK1|whe}£\ 'Ǎru_ZtZrlۣvv#rpE 73 /'#'£“#:Q"<ݡE7%o`c!{=S{ExT$4) Ss.onoFqz]Qs޽ bgUi%[D-&\ ע4Ms:4FdUb}7|s7o.{x>4NH! 0n[xcqqqʒKGz1NgxtLvtՋ)K^6۾xisvN~%tV ٿjQ+:j ǔ/իƗ`1-+O\l΅(rqe[:W{̘٧[Eڕ,.wԼ)L}G\^j.l`ͅt8E_~7j`䝎'GnY+< x}^Zx ;gDY˭Ngxi"p_{wqq|v%)Ē#!u-6ڋ}ھ^zʥ7Hth=P05 +>IDAT;iK0ɆX-kI}?aZQqv?'CU Ģܼ)rD +֊*֥MJZQ-v/>yцJvTRH),ߋĽSzZĄW"U7;i `xpb}x^~$1{{~7zC/$̞#/ﶶw9tE$A.׫)>:ʶg/#8+S\hf''qu9dhpiŻ4iDt/_ }U*حJlx'_2v#6M[RǷ_.yD{qqRA믖Tm"kW<8b\.Ok|r}hzFFT~|ni!|;|s~֩s?{^/b94#TJH)ekzEZѽk6!bu 8jw՝NrPяZ./7  &<_jU'oݿe}+r;6]*JBخ?P\_?;r$<Q[oݾ}ZcPh.^(xw4uO1`(&]r?Bܺuի=c(ZYYzm˦ -p_|t:/_zVP`T>}̙3i~嗾{gy}xۆD֞ƪ%o_;qxㆄ0niP\z~?,˲,t:cLj50 [kzzlzV'4T*^/o]ZqC7$<qC7̴ ʊ+R ON:l6j̙3`]ZyK{[C 6݇=ܹsZkF›o(̲~m(֞8qZ;s8k89I ǻ|{xm;YX5|snmiRJlmcU.qqۍH)媨:4[@qi($RJ%cyNӴHxuo? ?kw myyyG޼ym0`ܐ `ܐ `ܐ< `|;v0&N>}رw}0.Ϛ#ŻK.A077Gh{VWW8<FX98<6ɵ5]g]p3g/]oa877_tqrߊZIENDB`workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Chart_View/Chart_View.html000077500000000000000000000112261255417355300265050ustar00rootroot00000000000000 Chart View Chart View
Chart View displays 2-dimensional graphs or matrices of Workbench-chartable files.

Chart Type, determines which kind of graph is viewed. If one or more Workbench-chartable files are loaded, one or more Chart Types will be available for selection. Chartable files for the selected Chart Type are listed and can be selected for display in the Overlay Toolbox
Charting tab.
 
Chart Types currently available:
Data/Time Series (top image) chartable files are graphed for a brainordinate identified in a non-Chart view Viewing Tab, showing a data value (y-axis) for each Map Index/unit of time (x-axis). The following file types are available for Data/Time Series charting, as long as they contain more than one map:

* CIFTI Data Series (.dtseries.nii)
* CIFTI Parcel Scalar (.pscalar.nii)
* CIFTI Parcel Series (.ptseries.nii)
* CIFTI Scalar (.dscalar.nii)
* Metric (.func.gii, .shape.gii)
* Volume (.nii, .nii.gz)

Matrix (bottom image) chartable files:

* CIFTI Parcel to parcel connectivity (.pconn.nii) -- matrix with parcels as the y-axis rows, parcels as the x-axis columns, and the colored cells indicate the correlation (connectivity) values between the parcel pair.
* CIFTI Parcel Scalar (.pscalar.nii) -- matrix with parcels as the y-axis rows, map indices as the x-axis columns, and the colored cells indicate the scalar data value for a parcel/map pair in a particular parcel (identified in a non-Chart view Viewing Tab). 



 

workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Chart_View/Data-Time_Series_Chart_View/000077500000000000000000000000001255417355300307515ustar00rootroot00000000000000ChartDataSeries.png000066400000000000000000002751101255417355300344140ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Chart_View/Data-Time_Series_Chart_ViewPNG  IHDR$9DŽf7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:}9uoYl,H Qg$_.22^K7#nu.d$%`` a661 \*T?{Sgz9T#U4Q]{5WS,c BP( BP( BP( 6@6lؐJ|<a|.BP( bÅ^ՕH$<>|wAPקYP( r1& Cc TiPcL٬Gw;mڴ;J% 9FZ;vL}, BP(NgP,h\r%\ F 01(-`Vrk_ڜveuwwT*L&$' CZNMM8p?P( BXbC}L&>Zh4/yjZAL&#Ǹ)AOl4jyh6F\.8p`ZN7n,l-.H*A4ZC}[ߚYT( BXXP8شiS6MәLF* zdFRVa(fQfPr@ςVRi߾}OAC/JBP(mb1// ]]]l|,ceA2a5$i`:8rwkGeh ¨f e\ҝJjZT 0ɠ>1DP}"m%S Jl6/U BP(NPΝ;bww7G1'eValNAP qpZ 4KPmh*|@A]**J6M4tqH _e2\.w 7~`VT*J%54cDbłO%Z' r뮻_?T( B/P/xxxP(\.4f 0Ox3hܔ1%* =M~Si[C0J {0QDS>DZ#JC<Ӓ ԫz@gr{,BP( ɇ Y~XX]]].nS .ogPZJVn N)40Jj7؀`URjf% /zT9 6trtJA;|G[򖉉o|]fBP(5.>.%#+Úa|xsHۣ4 4˲Cax G@ޏAi, ^l.>H` *pEA !1-5 ~*z/?3]jBP(y_|ue`VF:fKLfϦQL{/]P( +TlزePOOƇWHAc JFOzJk@Aid~@Cq !%N<C{p1FGi!3QJA!g]tEFcϞ=Ƙ7fY%*[Վ=Ob)N}YxeO^d7n'_xs1 BB) 1Esė :OSOpep^xV <Ž`Ȗ<ܠW~vuw@Y.(66?z9dJ]IT4YPw׮]ncZlyjرcwEq:#]tfjR(Yݻ7 ]'/9^P( *6NA\qpwwww6Eo; Cƭٯ044@iT*z l5ؠdC M?::7trt]`&1Ԋ&PM+i$O3T a*#Pe5DiK/*xjY~Zm|~LW( łS6l `>Lh)k䒉j `%=*j> E/ !C0$ JΜV/ 4j"(===0/A1AT#0 -E<R]( iN 6U BXZ%K46vՃ^txRycory1&s]WP( Tl ؼy`XSuz(+h*Ff*`w1 F\.A@]4&0åx(VwHrQQ A`B4;´T*JՈ DPlW,EK[=?L*r7M*9NgrK^ڏ?دGuOA ay 7DwtU( )۶m|:f`h a 8  18.PU*r\**h <=,:-ɱ0{L5"Ol\F,) CL&`YVDi‚;}r!.nL\oRFqk\.߿_NC{_j n{m*U\{W~$*mT1Z= BXDF*6+o>00elRWsHv.UrPnPK}4JR*YԳA}22:1T.skX$@JR$ɑP`? tPW/:gH֑8^L&ǏԳkL&Ar_]]]zb' WcKvwL$&&\dw1e)+Uw]Q( b!C_!=(35άK)z\*y4;!ޠ2o/z6pqFrHs[ Z'4!Q3@:(M ȉX( ֔Pђbt]kdl?|xn~g#?sϞsn4&׬SOi&3W?mL5JP(N_Zb®]-=4O=K!Յ4G J\.^Bφ<=N :6LTf><}QV dc:9 @oPʙJmJ1ڦ\/4(h4hlꫯ޻w~8hL}{Wz_:{w~\W( \pX___P`gA gɌ]!I\4-iLNNvuuҀ`_Ӏ% d nz4( 0y饗:,uXW*Q-aZlAiĴ4M=.S@1IU%/P%2U #޽}85>u߾{ kG< uBP(P~1 yht#xm`7Ffs:%ѣG}ߧ#q@ŲM0, p˿A9ʢh6ǎ;sJ cK+~qT01.>!,LYI`?h]vzvBP( *6^مdkA .0noq5J|SSS111\/skZ,@)5$뀍 HXbhhrHS~|HV],T TfJ')3E'ߠ̞O?xDI" % n޼Gh+ B8bc~cɌS;;FTP`Al<$g*@ES58Yu) Li0aHƜ tB;kh<#P>{0anx(jGjC1{I7$%$&#bHKԱ}ܺu뷿O BPm۶E*32u!Mpe**v?6+-ǒ=zbH+@mU%K}幃a~s=X,AuG `] 3oJ΍0je{[>m_j4(c"Ƙ-[hI\BP(PqiӦ~P5 Ƀoht S!EBR^5GgH@`=8zh>g 8DBJ񥠲~s({YjTKXֵiȎ<[ߋGNй$kNycz*A*ڴio( B7ڍocMQ8bQAs*X( ]@FҀ5@l;v pԠ4\b.b΍@j?я/_>00gC=  iG bn ؠ2]_=Cԙ(9ɤimэ7>sP( ⴅ/g@z3:P= ) // B.s)=ܵZVMMMol6/dȫt*iMؠEzP( 6ЍFJ=tҨT*?,Y%nshz* :\ʐ^LlC`3ƚI+b#NSK.BP(Tl@\vepgYsw&%cA|=\VN HFooX?3ΠtٳePޡCU*yÑ> t&!qfqx^={,Y$K0Lc'cnņ Ʀx?<ϣaTԳ]0!UBkWBP(!Tl\r%@cTd 2W|#pʕ+Y,T!`M8),'Uղ, ^j^H@F@fX}̙@<~ FC˂ ٳgҥ@ W36!lF16S'ElH*ǽeUDVφ: [D"JAwzBP( +Tl=7@WיV#( ?ӑBb \Τ mi(*9pDRi6aX( W>3 <9iFOln hpڳgh?.D]QmJL]PĆ 6h+LT;}Gz[%q!G>׿=PU BP(N+ؘlذaxxX%$` :feg > %`8d^cdW*BAh$2iPt:E:yh5Ilp5L yᇗ/_N4鴜"vt`2AӉƐn cmekB[ {$*~X71CCC{~L$lIýoW\q? BPP1\zx"8ĀЈKf4Zz5"PðFbEE_uSBH$O#$TTl#\[9ǔ[y1LߣSu<\.r-k֬+ uɉH!ͦAj֐k!ۏ1>R6֑,( 릢b"Z;vؽ{T( Bq@ƴk.Alel Q@-[622穀ҐT MdY^8IFPU-A3hz!1ҨT*7xƍzzz M#e-DA.pttK.yf3BP(*6ph@XhP]6$ JoիW+y*aqY,ܥ4L4oa1B1aH9#/?Bl;g#> P#z)/(#t IDATjzyYXr%gCO=N:0߷ۀ`W\Ȏd>f"* 38CņBP(9Tlt/^KK׶ʌPPF__0t K91âO%6𽵉?MKK4MBJ8/@#jh66{rA.GHB;϶jKi_.!t(310c,nU|[neҥ===r* [fdA)S+d[2l67|}AYlǪ.zP((dJ3Fd q"jn T40 z^TlAlǾoZP( ԀWm6\pTNr(ۢ`g4b4Uᑗmj س :h'* lPZX, ҭ3K֯.~O8|6-NIRf։Uo_r6rXTk BP(N8]v B.8nڮ\pJ w@VjwuA U.-A+>P|E s5n(-Og>;V ™%1+|7¥Hik^4#~9Mu;5;6l>F2y^TrzhPx*:-ZWP(*61kc4 ơAUF=388xn:}Eߔz'e WUl$`S.] itY6ca:r<)uƶ=SDG c:iKuZl-oh5OP( )]llڴill NQ%@ϸ@.RĊniٲe1J=zQ$6HaL[5k 793&Iq$_:Tԧ.BP0oVƊ &*$G SDe:eO4=(0Jl\.=zVark+[o$+ BX8_>44Uع,IË: [n93 Chtִ|FRI+%5䵽Kоh])c EfYsl2X5C4L>ZjO 9R0+%U{ċ ``h˸ /vuuMNNNNNE*6tR`)yFҰ4E䅌}Spc'=KaVm:{&JhH| 'k_Bg5Rmki 4r!Iַ&g^^4b 낲ljmc70 7 xPs`]'x: BP(5NGq^:h=I:Ij_pOOS2-vgDg7EVdf+_ޠwfimQatV[lYX)r) )6dlB_=) Gx7)lh(qdSY t?3A%y^2d2|z{{:]}Wd2CfFP( "i'66oL4^gL):%( j+_ h:ҠjXVEM;..)8Lo0I5MIBQyn^n ne֯B%QaW[lȟaT72@dmf`ND6-? `*F&׾v BP,"^bc###bQtv`eu@hFRoQ +<Dө%ft1p HA;v87kEk PoP( )Hl\qxTqMda4INjro|cpp;X)4+Oщ`fhjBc01Vw;aA rC36:.'ڻAa96lax-aD"׽=6l:t.ܺLr &-ҠUg t?V1ƀˋO&]wݗ% BP(NqWҠmQMҠ42Pj\.?4Ī4hskxBOӒ tFh"}'_SAH$NTTBnmݺu C #Tnj d7<::Z(rܹ ;>c <~ʕַJ_jmųUfORoȫ0 O>e˖.7<ܠcdCP( i!6~ZU 4}rSJR?g Oa/̡11!Y5S !{gmD &ƌH#2na鄰h ^ Aݻ{zz֭[f!"cssb> Ȟ={9rСk:t, "Ru`ࡇzL,TRHB=CP(SXnիtpVN$ xPһ0Vjn[zuXdbF q.\m"b7{2~h: F멦m@l`Faw֟C&f+~~_( gqF.C *s̈YGԽ{9|͛rh@ u\ : AA,{ZN(iUM²To( BqjT\r0| QB)4$ (qtRpЌs4\LixCخ|EY@I̠Qόm|zjժBZ^T(O'>h}* Ed00zO}٘&s tJX B__3<3>>wޫJH=b2i H|߇5wo|VR{{80O% BP,2|C5ccm)+66o޼||>O3(b<$r0 DbFRư.t: u|2Q- ɒcxM}?& <#g{w֬n<44iHm;T :cRi+$Vpy30;zn lwww>/ O>}6mĞJ̛A5t>=Һ=gbC]w N:GDr 7q=P( bA۶msߤ`IG:8=I O=MԡaMh;:1hKq#/$0 Gv>b $n[~=9ЊV,`Et!=\g{$э^lx$-JANlWWW>}g:fCd 7QRUmjqLACCC~޽{:0 /ΆBP(tڐt9%! e@L:8Ch FTzᇇ&iXAa{"혎J+O* !+ TG0h{yKȱ1mXFg.ɤ~ЉA7FRٻwʕ+!GVf9Û >*Ρr.bq|||bbgu CE5 Z@rPLaZX,z+gV\yWw}]P( bT;vXdIoooL)k&r;LJD:x\~GO#y6fUf{LgS^S@x67 tza7޸s\.Gg; z%Fl4ԕb@cT՗^zixxxժUp sK#!d{mh0 q300pرI0LѣY@`p #sr޽{) p۷o7ɾR( bސNfsXX8U]]]2Ig)d/}Y,ːI[q[6-X5 gJUr:`OV;sw7ؐMu2c< ʳRJ^z3:.Z:}P"?B.)˕JR@W USIw`s.# q_g7~C9眣bCP(ESGlܹsɒ%*<({;.]ׇUtY:8eFRMWia+;>]<xo$Ct"3#^4s5~ |&tiƾ` JW*^|+V}===+ 6-G YN8U} TrTծj G0@.0m#sm  MHRF<0|e߾}[r}㶗BP( 3gG6N}v4rhPN&8}J+VkTԡ!CY1JCbxaKqT ٯ5^1  /|aݺu001jKZ | 6AfN(JCCCyk ;KJ f{`94̠7vX+XD$za{qq9/6pL&fqe2oo}+>l6vm.BP(yǩ 6.2=EOַ5IttpiJo}[L9Ui$bB1z/Uk:$$Ytr01RG"0MuWJMY.Qf9ܪyr-uF=%;HَVTVaO`H$nwAtww='?IGd=6꫿կw_3|3~w4l ~Cƍ /|.BP(V,bq 7B KwzLY8ZCR[l$i ҧbViSo1XklRuӯH(5>(b,H '>@4`1y@9Ӡ3QDP]]]PdmZ5^4=# 9A.->eT VPl`%AI=!ӵ0 .]O}|gYbE'*{AEݷjjƘJ-[:#-[vǿod֮^\z}~GXXXy9C'$qb뮻( NY(iX WxNOҐBó* +M3pVAaH†M`6Xi=5݌[J222pB # m'p<@$\>LW Pr36[.$GP̳+wZE+eZ|O~z׻wz뭮UtO?g@ic *z}zp|p֬otvZm|>W,Hy96BP, ,J7Ā7Q*^S|``fs :i-jp>BGBV0he[!a AZvw_`1TVXprL&},[F5Ruh 97M r0{BZ~ArH*ڂ 94AιAʲN+WF=44uoVt'x]9;gbǓ?[ݥ_ry1&>z]ņd29@oy97v54gC`7 EРJ|hh#g2Щ< IJ* ` )A{a9FoXn#9ð^JK*CRD"ZVΏq 9RE2HxnY4f8 7йcirh8ѺvAQ`+. Nk(}7! իWؘ x+V fjGyS: ]oAɥBqRpfZsǿf2XWN NqXdbc֭ÐAN1\lWqXޔ;vgoIL)̐Jq B7ʔv萭-czHPOk{ e}F`]/oذA"` IDAT#-GCx % [Vj-~f1TLiP{smԘPx9Bn"$z)摶 d2i]]]f{ٵkS]q[#.r.sg'|τa`׏RKJcՃw WF!O=C.P'ĔR^Z|yPIWˌgK 3LEXqPRs⺳e]X'2~d*4+$  $lOe2AA͠ vs[.2 G|UDD7s:m/|ksm E[~z۶|F^xr͚&ݍƔ1X<\Uu$nY$P,^,_4&o#8묳| ZJXֆ'=S:ŪNuhX w0iUuIa۬ @lq c~>*pky]t1x"C* 7d^*9ؠ4\f1J{0BF%Hl!t"Q"ND"n4K,d2]]]Zw*]ウ{%^?hLYLfUzژu$[iłINPM"W߷l1w2RD E#6*T R4:eШT*Rirrr߾}2tZr)I%* ;]ӦTx* h7rq9.IwS Cʕ+ ?tkpQECƋ= hHOҠJb17"[&2u3QF(_FO``gvGᓳÖ/D ^)h6ES(x[ᘩVĠC*ճsů}m{Wz_:{w~c)'|Q@O(R)9NgZ=Ni,sb}"})D𩩩j SLVlVa}Ms5ӠDV}>  @٪}oQ~{TZT*q^z)uk 01?}"H^ yoV) &5㕆f`9"$߁8|*b:DA -[ņΖ 2b#T*T\{ٹsgPصk<Ʉ+(~wڽ.V;|Sp]W(zW|јo#f\n-2}-°Ɔ  ^6WHP؋^J!n *ۖJ#GWr4fа406JC5V^4}qԳaZg{nțiXLԭthT*իWE<۷Xk5(qC|߷> jKl L5#m# 2#aڑ`JlgN Y\8u ! d2###LbZ׏g|mljD/6_|q۫ fӤRm‚]v$* PPrرcG9zhXrVνNQȷV~Oa3Bu{è ʌR0WrRi!6^Cኳ2x$7dkWJt?ުu9f?btYj;֋ύ56>`PJH*SOpF5yM>2(3d2mĉbèdRO'ͳ| fFnݺ^L A5zS͘/˽|>&t vC_e(61(@5=EU@5C5X>t͔Ab6RFwЇNx/.<fYƋ/N g@j) j5 Cx J#E Ch\;"+{5Pysfc wM[]M ~+/ %>keVA0#!bb}}.3 nǎ;|/dpkbES <~S3l C1f!{ ;Ǯ Bg*3¨ª:͞Ci)eh)kx؍zjCCCUP'V0pm&,", jĤe2\y'h^rNO?\vl;Z<~!z6z{{VSJ4-tщ@s\i<938h"?ҳ!_*3:J+1TA)w&-TSjzl,jREM cw5n:RU*zBi03섩՗LDilLZ7EV)6Va[+cg+/# .87E-t~,vQG[-Ns3ypƤ-m=6gƍ+E9 NqV,vXpVB581 5@l=*گvnԅ4* ^sF'$4iNԅuht96/çAw:W4bN{{{cbIg?k6b&4?ԧN`s9ltmiͦ)#Wp5T6S*MϞ{? KlҠaTw9 C$Ө41&::Ƨ1< _~`-3-3˖wd3f扈xk}!©@ 6F 5K7a/?n Xq*r13_SOd/RiHae1=Hcllˌn+LlzzB,'827ܜˡ[&yYS,.WqKͩ]Ph }YjW鴙UJA2i ~֙!U[: r6p.xk89lTQF%_.Q˔FT(BCPyFԞ Do5&7)^ fhȌ?0X$[Ki0*0 =K8Η*YrG:߽^WթbhkȮXaI%yu/+F[j.˱KXm4\_Li$Dk\;\)fpf l6T ˖M#U.}SvtgSIԬmr| 7ÉJ$fsr<37 ߟK TsױxNY(VnܸP(B~OF4­!thX.{xf# E/׍:#,REӪxk]Q}0%X,8_>pU B.>"y!,eK+ɦ E:D(JQ̖{)l-73cVOqw9[Ntww>[`ϛ!+:5)k6k?=IuoO;l$&jkf d8ѰJFcVuf1v*#]X9=L /J5R-i{wm̆ m:Z_g4$eTfteV?Z* I"VARk25 _MKi0$HK\qi TJVRȑ#B ǵZJ#n.bd VntXD+o03w 7b̋ l]&cUjppP#(Vtt+4,+\Tsg|~Rɼmx00!m9(x6f3œ 0+@\e;|n_ /6.bH50/u |jj0 콾|A;Z0Vx^^]Cj!S>c  'E̦9uM5FNhF\BAs]rw-c*<`,r}Rlz垑t^MH&lvbb"-@x)OԿ-'G&4gڤu9*,MNw9٠Ahl0m(-=bcӦM===bROz FzaUPI?J2#/MbUlFG/V*p'{b.kRK`H[ҐJrtfVT*SSSR)L[V*r:_fhr\;xS/A -lH87|ʺ^VѡҠ6X͠D?i-!Juuu>|.p(:ډt:Bh֭+xƘtojB|& v~"a:k!rٔ˳%sM` ̐Gj:[ډ tcD#ÙOk) $I4* 5Ҡ915\> )/i&&ѣs+#G̋/mWZZ_OKoXon c8+ gw$ ֫T*5n`m-tPtl-ꐮ W *3Pq oñHR V=֐  K]0U…^K\W
U4JoHVf [k 1/n.4/d|ܡOzыƿnKcF`jZ"@RhNzvZ{B)BÔv#zrJ7.u؎ef{nO&6. Gj$ZT4rZ5ƀ|ȊfqD0QfL [c>c$ [_ѠN$#kIv( ;hUk6l K뜸XHvX_a{V@SMur0@XɽNh /iH>( 4r2az"s3LR)U1ho8rɉ>=&In Z`E^jt^:?2"M|y WL+e:fM cgboT^y!v4ғl¨P!L"q'!'4BlGI\m<7a 0?klxyQ#a;`Eb @@_oU'GŻRœYslhP!CPLQT&Aw!Ç9/)̋/}b2#F!0OX#b'0B3 I^l4ZPvM&jl^."P*a IDATU. M pX1n 4<@m'4b\GպV0Po@3=ps{WԖ.wkmt>$+FgcgttX)8޻D8ϐ &m qlƌ=@]o٢(XHO.<4~#s]+4-o; }\$Nx - ùTs?W57 *#3goYy ̳Avldɔ#6.T ,~JTJR.rV}?C S5ec>k)ÕW~|g3Ϙo|-[$5h+ig/.!u7AwA8OӠ4huc&elD>E?[:Lt.9#ƹ!MT$KI\(l_(?'<rnu?N;rȎ;`1ƘF޽_H^mZ(b۾ɞ=h)W2>IM$Dyfb6 yK/5>sn56臩)8.W03r d& (.\tQİN¨:L2۳PVgY:.=zJ[LIN$%c6oQmΘC TI+iLpr\z6h۽Z)Н9UA'3T}}3O>槔r? w4%J!h L.g W{)Ză_+G&<L,mHy4E.!y[e6,eI*g [v%qׯpҤa*";D+j0{gc ž0*DqЉ{&3:WI\[TݕK9ʼnf2˖prWsAul+6a'5 .0? h|4{(T_߬RG0U2\mI2xıcvʒqφ ͦe+OȌ7K/7.}w OUn3S,x^Dlߝ:ΫQ1EO|tuu e ‘m(JR^g[sf'i}#vȏHNroϐlf/ zl ? F]dϑ JK|Yr&ś=!HC'bCrtcLrYf"@- `VvzQPK:\Lt{BUJcǎݳt`enX~?8yG]k~w1Ѱs_Z\#񾑑{NrbMƵft؈a Lq31Ws֕-#+g P^x2;ojahLlC¨>iV$g.<}ȼflͲp-6!gnP8ǘgC܃y,w%(Dfd!?g0x4 wF{Tx?{eYY ?SꜪ:]}h&chd~3k5Q&NV3LFW2+:$0 ^b  b$Ah@!\Zhr{g?:TxV^s~MrHpnG,05EazkieRq۞lx G2\c-ED{nV]}npWőJBBBRꥂ+?8b 6{E|V&-6ccr%Fuep#y1N'^`SB ^*/5ٸ⋑V底R'^Ȉa "x(ˆQxJb# w/0( yE'/^a!li[W"u.C kP$A]qXc&S'{I}=GHXwj|ۖ\S]Ql+QlXZ}JZPNhi5񉉉jq~%O.e9ϯ U+l|K1J CtV4RdqR>zէP<6KǃeW^IjԆ,~o? -.f_PLIŮ;hsk%֍fRZU# 7)X$k8 6[ML82V\ (%K4OY ֋b'"QI:zlICD S̕dZ;yF:PIYi,..mf-!L@e<AIIN@Q׈T2`p;J%N)PAVeQu9Z 0 )nP`Z8oTZfǷ˹TWjmBko!Lm 6Z Mo*^Wfg7־iJ\cY.Sn7kcN('{>nL?L眳J0lCVC?JP׿nײI'%"O8g6Tlp1M'մ)*}~-,d_h'LCC43s)+^td$3PDPav*~䞲Ha#Ϩo!ۑ khfXU-/. y4 zko~Ƞ!i҅.Ohs-(Xj(4i2\%3UPYW=w;fM6!ںu:6_*d ,xa.0M7ܜ"a^(JBN,9+j{c6ݺZ^)$Z—\ٳVsHU _٥K |E48 |jщ']w{C:Hs<>xݒ= n":t98yJ) h#!SC?#liln͛Vv07һMP9^GjmU70ή'i}ʃcزѿH lKFGԔeZW`/?_\9fbx$r... ui4VuhyApt iq l(KjŬV LC$E?|jBJz(~+ çJ[Ј|7d$la[SlֈHk/ 1'}UD){!=.|d " R@>[l5wi}-$,VIBwA6MM3f9%l}VVhb"cJ;3 WJ3ZȑQE"ݨJUQa"?:$ܷOS>q DK"GVgC__>djp |[cܘ NܨlEԷX%Sb"6x]C2eC \a()(K85n[XxɳFA~˦*ӰPؚ̋i$)B`CkTJ9EF(6X)N{x/1 ûM+I4䚗LP"B)l#d)2c;aqLɊx0>,;g$jjb.U}H~)|8:XUd7x0:Z*jtU%7+l4-4 %rl9<CB0pDRW lۥ!wd]d6l5ێ9֔ˮc܀  CKlJͦ Y\?DZ6?;:JQ<8PGa*2= jGȘ ~X3 u*@pթꍎ~ X,{qG>55O&cLl}^t:ﵵVt`6H;Ɠ( @?~ $d't^n.httWLuj,,,,,,dCkLC#` X8]J^aم` c)Zթ#&=6nHyhH2\}QLQ ?+T\f}+sȟ*m -}ͷ\``5zMerG}jQ7&''{OO?Wr0881>~~c߷}WVVrp j5C$264=ӿGDBͦ Iu)6Z@YxqlTME0;E բ@YŒUp16=LQ[e|61 W[Ol<‡+)3"XY\̊cu1imnTl.жJZ\tc C5Mzĕ(ӇKܨᇳ?:>SJ@;D6fEcUGM)̑_"0[H?ю.pT*Q:5 2Qdj2t5864n}(P1Qʁhfj57КKE:ı# -g*Z^~onSNX(#-f$T1)>>NЗD?= EG8ӈ҇ {{']FZ^Q{մtJ _v^b_*9+&c)0eRF"mR"}O T }YY6^ٸ Ոl9P!3t:ccc T)nJ-ݥ g4bXh5h$"2DҏUG^<)y]Z .S'N jǽ"Z0v ( R+#e`73 &ʏ+2zdz '+-34X,p>TлSK bb'w_)͍6<8vnT*D^/Rlpg/KY6CQ.1IվVVhrVzunT(5Pua%{EjVOb} b[(sInﮩVieekEz<"/{TUl7h׏6i>v>LC6BJĂb&{w7.Cf2S(UydyxHgOzsGT*9X(X9oZ6؍J2dF8=`*{n,y;cۿ-J[6&''!Ԭ*X@sT՘k4ְU5,T4J}< ƑQ*]~↻ܚA$*a$I҉U=^Gr:fJxEDdv5MP!lUF>{1u{z/Mwh@WQD>}`xli0{@l 1v4xar mNʪ${%ـԆ r[s=e/ȅNO߽z!}B a!ƫx-?OQD~֛Hk%͜K9n ]vFFZUhU(q8 d#FFh.JI5,#tj4he*U ŧ^L`x +};8@zUG*)3׹,}$Ʌcl_:]qk%jQ8Dآ(S%׻Sv6|> y$Wn $ivz馛\/&]v{KKM46J r$IQJVi~oPwd}ǯOnTQ=;ܼ)Gl3L@1 #vFq iՈp !|1ҝ+y'Px-N-T#](64db; Z%9fO<`,ww)G!Q,"& 7=V@tlv<08hw)gdm8Ҝ`^l:PyG"9C{ Lb IDAT7\UP1GFF>8j|;o d_\ rB}=JeL)^RBZGe(mNU9Bi.@[ou.=?W[ ̅|-o~@*jEP6xT!/0pllTӤـlAP0UHQT^!ڼt7b0*t(6б@_t`pȢX]|Q,4l8q-D.jt gKũoQ`Cwvg;bo"` cJBCʀ4T9f+|ԗa9|9nT?S?%ҵLB`/fNg||C5mfxW@16 PB>ZXloQwI/a/ʎ^TXȮ\|+k}R(\7=-O(vNG2ۈ>{T,iF%/ {*`H c^uqV{諕K$%I&&9wq?7ݨfYG \.D£|m(5B 嬄Sl@>qvivQC/%Ih`]M.O Xa\>$ŔX@6bi4h~>v(ʙڇN- gSlm˲q@6lp2yTWƽ Z^L\'B R]-Guo#evBvF uI6d<(p8d3\1-, Q.ǼҵrtɆ2kDk,FT(/F_b8E3 FyW~;ߥhd) WEF6.jA=1LOvvTr$xi,2J & L3,+G[7m*"GTܺ0kpʲB Ֆy// ؈GnذA3τJ={|y.=~y}jgO:kZ fgY3>c'ޑU,>Лޔ}d Kx /:%fOn4G"(o."VSOO !7: Rȁ6 70F Pȝ=K7* PsH# UkcepAMOf0>`֜ ROTQy^d cD@o爧{N-I27*DEF(ਰ )NfFJ>>D>_==XqaQ"K*3&>eKfF7ޘ4*7-!9nToxj5kPŇtf>4$<"w(BY  !߷|iV /R7*uKkdT /Jݥ;!6Z|9yKx/gm%<6k4啕4$!>^1Hpu4@.v(|LWF?MQ.K02/&4k2Iرh'ܷ}^qFך(яjx)Q!Tc),,<8Hoz}Mk`QKCāWWҤ1@/9,\.X24DD67}BVVht(^ fQtE/""O^9ymDWt%mf37oHC28a28O9>[_31AwE?#4>t?@#;B?/.' =.gJ49鎱5kl Cu\|tK ŗL -(뫹a\%*!jCOv ?3l&nLZ,EX#9dh*p\LI4@6VVV6mׁJ- YkCHj͕i(wd 8w @¾|^QMy^a/Ki4qχ7$|2k8 :QSjjj腙FF/4^82 >n 4BYf 0ӐLzP,cy=풼U*j裏r)| Odrr ox#$Iwf>sy5k^E) ȗhp0s(i&BW 7$O9V$ : f^^/ !WfgLeӡ$V+.`ِ᪰lx1Yxo׍\Ρ;izVV2IjQd?B ;Ђ>LMQh޽w8f+VК,R<`pr9񧞢K/NY@&&2S0Ȉ;0`t`.4Z6P[MC:<\TP~#NBuvRc_e33I+'rZOIX\~u*^l*H/5خE7z.58$INlaaafff||^MCPLC!$y%\c an:6M:~@^l (5YR* 8fqGOێ\pE*NEy{;a{#f_)`}EQ0P,gCc KKKKKK+++ǁ, PJːsq>NIъx``@1 CUgTG>O]JKa1+XOG?ƍrxel6zRY$IόUi_Oƌmwe O`s°eClqc`=E8Vn=kJ O(}X<9V"6=u:9zfhx҃ k^5L;=jQ^Ҫjzc[2tR_i@1do/SfJ|"&Ľ`Nűa$C'u"\s+O#T|~dE޽p&(-r5X;=[m6*R.Ӯ]DD++vIxز$zH`_-q%t _ڢ^\TCq4 -#DٲE+Pza4'(Ħ iH[Gh` v$ [ d7K + J%BP1 iL3B*CScl6iD->mZVmN-J`i֐4KXoy2A~){(4 vxW^櫚D?U^;_F ԣ"0q΋ "qfӂ t.-x(ws,,UBg !ڼYazy]`mtT Ғy鵯\ML>GwY "z{Y:*nM H{ߣ /tOYXl@EV*{[1V-[6GJ33407XP:vۙn ٤̍s 0x7+-o @3y ÙMU~(Q၈.&&S 3tkw cx7o͛ݟH㠆%l9[6,%?D' \ZjffS+aґnTJ(g:ZFQP4Nl֘ݰa;P% H䶌A{(' `CjcR8|W( >Q`pWk(=u HLT03;nW*I׬&2VURK&P1 vDeI6Ѵ(8jbzawٮ=5q^F"H={lٲaߨoy[ndhh;y[ھZ WaB7}.,k>謺C¸de),1=M扉, >tIm[j&q@oGB˴}SsLHVxYY^υh[FIK\7߬߁} OkYXCSSSXd3#KУKbjhh(ё)c.<< z}0r)$ɬy!O!!AՅQOx bvN8nvt%7oG$xQF`y\&i'cOԊ>L xMvSk)1tx⤅X$wm61Ǯ9oJ2vMt-Di1"O|_D{ _H 83$L#I^>3lVC+l-G!<BTj)n#l j%P:~;ZCI*ZMp U~SgCы)"7ejA9幖f˔Hja?퉀~^ ^RTKdW@͗'fE"b×2s3@lrO 5V+I4Y#N!)2qQf$->*;(o@PTQYVfdɽFhl4S}U.CJ *@Y:ŶDy VcFZ祥f$I9E 3o* 䬗fǬbϑ5SYV}YCdxUg ,[PC*@_e. ҲQ?EOd6=`v G8],/gYh`u_-Hv`rCWvR] {UGZ rACCq)3ߧv ON_`q!8.OqƘ&"ݦ3+ͷ&S*#nQDkiOJAQ&mIW;|M_'M&zW*qLCl&/O4! ~ .(ҷ<5 ;pqwӺunXKnTr0>/eAb: LMY#9*z@#H633366q*T#4ePJո2k̶^RM`$Sxi2 dJn/Գ Dmf¥M`EKc,fEEp IdFyyH4S4YCF$u0BsL|eOe/oΚv^8 T:S\z饷v[hlJ_0׍8n2ƫiU-].~&Q;k-,~%/t4@CQӻE8|" rl J45EK[fĈYI`hҒmI>fҡ$q tNҲ16H0 wy hdpG)z*nT$pUtI?Yl)I/gqG1<4D++΍jd$" ѪX>,ݫ&UF $̼I06"'FRYFz=GrlZnMc5M-b&W@e5՞vfgD/!b 3 }2og\W McȩQ <Л(7N1ƂҶ27}gp!?$ [l6׭[*bfx!!aMpdKҌR>yCzAQT?SVCJbܨ,D_j`K7r}J8#On)P eEb`x74IcyI$5kz=8k@9z[bJm*nUfU0' 30 u$B䡘] ( iٽ{$7|#QU92b'"@RVV8fC} c` PK4~`iɕC&OO^OцZh.7mr'-*B䩘 x0O1YIVRf8B(́.JC!MMQEn@2$+E Z඄z*z0ڳMB\@g/N={m Vϋլ: $ Kk4hP ͶyDPhtLɌֲ7*,U*}7=6O;b[(QβArQΆ2DݭF?^3341n }JyID|/qJjΣ٦L{t^桶OAy{*xC4W C,XMkHrU8YQ5f M2`hZiHnzQE-=OfX;EfXtR  0tY49 oJ1hJcИHTFחe2%*^\?}fh4z^j5d]Po$|)xJq<006 4 @{4)sXTSN.kew6 j酅y2Kiyw=f_M)ۤX&&l4Mq)p!_ [?R?.mW5BM?A+Y~¡>x1C)@6gDi.m&mLGsh^:t\QBN`zS|2 u|vXr92R~' R,{5-&4+KSSD, x %I +~esl&[SdJtlH:ų 6 VѰD4r${KwY$rnUcsGI95zN5K| IDAT[.1lh{=ySV@c_\ DC B֞l&a$166E D^ (VV6^8}1S1WA!C8 5/4@1 DX6lvȗD>(Alr?ϯKV[FGGV-ibRVľ8Zq񱱱Q&AFkeH<%VAȆeUn@R~#++0†?<.疱uoju2^Pѣ͒dU'4+TKD],[BFB=DozCK.q߸$ *wA_bMDzڶ-.s,¸r-c64@<ҴWQ>k"wȪyA$O``<$@ x6JŅ f~>9wPe8 X@ŹQOq@@U㏧]R/$b<|,߰)٬DY}Q>qq,̈BA2Tj>Uڵ׺޴we%a o4 rl5_)\~*cJ}ۏHeFx+siiiyy5y (҃J@R+|I(4 !?$z(!x$/K$R"_S~o![df0|S!a #`o~)Oo$bÚԆz=tfCZ͛77 zbz-{w?V_SgU^:Lڽ {<4mZJ"m\.Ws={-+G^/7g=5CCKgj mð`a@ $B:c* ;:NF )L;~i~޹V ڵ٤8snT3ep)**$ gkHDCCSO@N_kD柵3TVyha!.cctq]ut=5sVpp5| .4?rz_x\׼ ıR$E8d|$YEVhx]` opθ)S+_)F ~j.0/ Ϗz݆ ]H|Iئˇj( 04xFlx(wA(5nrý^ˆ첤d(|ӂTّą/]WVK9{ZXX_ey$1S!ˢ|$}{$ ƀCo߾-[LLLb%':JIdOEQ400 mziBI -9Jp$Q:|C'2Dvʛ0хrLp~Wz1v맧@DCCoT_d#ׁX GRzk/Ț\,Ґ`k"v:~>y :xio}Q٤ upa B37k.Ғ*w]q^ `YgCti6V+zJg\51LЙY2/\3j##7Qكۗ=O)- ֜Rv8RO*r}ٴY9t9Fb )@6ZY7Ѡ9" c8Zª^8g:D%a7*]`w.=)ivY?p~}<ޥ͹ 'jCdvھvל>8kΝZ,e^)5  -/W,o={hVwr%US;HnЏS[(%.hQ5|jjJ9P)tnd(t"m^aIl(zS1'ָQ2nTjL6 T1|P{.`l|% [5[>яZJ[#!Eٻ|u}g!Gyd֭ׯg]煍N(j T}E)a:Tu(_zr+σ(B8rRU*a H\WRj7Vy7n:0="XSOJe]=hk+]̖(ҍ^i$EQ)zYzcW4?RGǹ ~¡t0B{IRrCyom۲Omq0@P"(q<4u+9o"\]Ư*1[6uQ Z^sхGOH<G/`i G$ q ]yn9k`v@iii)Ƹ\n8 76WȶUosIr>2ȪZCh7$qjv;_OW])vԜsE33Tп,% MO rk&Z=jJʽ{qǭ_hŽ'1iMCiit]"nƅ˒|ýcadCF|B}ԁ 2z~˩Q+7ͷ~{hxՎ[^~-sDTNqj6s:ez=K/nmo{@J5t341TQgoe%8DRM3ZbTZ6ʯddVQeD*KF44&i4m,50q|pg/  tI˨֭;\ԏ g`. J $7ڲQAg =V{ˢ7H$J6i3&uؼc{YlppVFvikoyo6ғIiMJ~W3+I$ [_򕆔3Gs#KLPe3i("J4?O㏻w@i!DNd)) <4=_1gS<SZq{6z]N)SSW,L̗6:d/VbDwFa,P.:/ԐgOYiQiL)gbeM^ 7P5jT+ w{b/HXu08饥%1*8=Hˆ|_#2-;w<'''-Ӑ!L 4$4RL#61*q#7ΞRcJ >Aa!/PU/?VIZSNW"-U^!R*U84I%(*6?`\Mk V^F+d îDE޺{.&B䨔9 #6@(aHzAھYM_28P㘶lGq~0 "a0 I ?ʴ|0 DHIߟ <;/O\BQ!H0_@(:mVg2@V +xUPŒc6.Q*eWz;2 Wӛl]tGU*libc2޾{LϼW I"keCf:{J5[(%=R_  ذ瞣*BFf3`žIl [f–r~Cezk?pֿjZtq&'ݦ`\ї$\qIQ̀i&z=Nw+!s& jW5kXAyl$+#RجQq/I%7HqylwܚRۑHر@ fDyMd@%L]{yy+,NЙ\4% %n6wub\LCsf&Lo۠=pf%x-6p?.{`ONWh,|4* Bt}Qjx378lQu V{T:>QQMTvh MLddirmB9'nMy@^6w ܬfB!D\(rdDt1t  ²W9pи,IMZ6k^ϕ05 7}7Y K[hvhry;Qts?8(EҞ`}Ho\F8l{";Lb.'!yBTEWD&R٨:rf>c$ A@mGuG=`[)9;s.:쐕gVrUJn;kR$ɅdH| aKV:}QٸB%L}V\Hp qvSG8HPj磌}(aVEBtk$kI6ժ ~bfsqqpA[L_ mGZ ,`\﮺*9K-) $.fVs;F^g$Ha+WBiW"%H8$QL#[(5Y[,=ђGHv/riGZ"k2evSO@]"WVo6_ho."` 0)7Aڸhݺ<ڸȍ)e}"r^tn qNX(H4it:0 PT`@\U-` iH^%&x,~ #x7"tGy H ;ȑϴX.f܏u <42p &,,FyɌ":T6愰q!2mM]F7(O)|NIˆUpidBo{=ڶA^r"#,n@ \@HÕ\0R5jFhbO)ubQv'lnCR!el{э7RЧ?lҀ;]ܨB<ͺ]O[Y-[rKl(I`PDs( U(pNSYL^_MSrI!?)?ltuF$('׊EH!sdw^Oxx V e\*ufN;-Y&d Il  [jul^l T喵5####@ n8?iVc`[ pRq2,|Paɟ;S3%u.m sb$M ^ K6#K.ɌJ ZRr)kY(]t*-Dhx|)##ѦM||+| @)fᜟw᥷/fl˚ZFk$kF6iH}*+jeeeÆ ҫD4 %L= DBMƽ[5k˗J#$$oXjJܨ$-$}fR )@ǀӹPWN ֜Qu*bb4t:[oSNٰajj5 + /Pu 4b e.C5dljYCZd\,ڞUH߈}C~_Q'u:a.ҒE.#^~%xuI x``2 ֍ $qe6@kk]o7ٳ pchW%Z8 jNH*_7ntI01'"zt)Akx'nU d†}dY!A"/L,,2b!d@ >-l #8Ys6Z矧sCŨdݨ Lyh67~vνyu]w]wEk˶>P x+m ժ9L,PBvяfCp>+X6,X$͚'ߤ;$J &I_vULD ߶݃k ;*A3J@x0Xd*H"i~jPz H}JNMѳ4E[4i R] n\I$jPٳ8vZ&֍Jg䯅%`}T.Qep*4$] !βJ*?`nnTn#T\~d{!Q4dLF [ UVlw0N],=Ii /AiơC>P:4J}ct̶dgկ~} '0h BC&qrңWGh!H0=QoH`mOew-V]؁qױ1D2CĪ']rMQua%3HUU8"*it413 N% d 2Є;u׿N@fPB!}+ۿM7oۋ)auYnvj5m>D_[ -\OQ5n X~k\obƒY 5.GȬ.mq@ɡ^ܨFFtS:fXFo?vz̽MrcmY孳Aif:Ee.s:1%=&}(hz?m۲2)!1=sr%y_#nP"`}4;TDlcP\vY(B/|V1Dr[O~2; )uz+o?SVIYK,-vfdPvPXYf"61,$DAڽLCz/$R=zO"4쐘0)ˋ)زDZQoE"JٍH a Ϙԧ>\N<֚!L|/}Ygu LvYBӐ|]iA0 $Rne)ipe^vgSV hIjrlwķA\Vq9n͚",ôqcκxjŬ5 ahmяd% j>q-p*&"ڷ/{ ƀWƲlhy*ZX @KK|UQSSn?ND8"{ϯ}5Хe|3u k lߣiq1 Ax+\@R&E.(O'hpP*JT.r0Ô)i6_%rlCUʸL/ -FrvJ)MX&&2fͦ^+y֭7oED8{O [,)JK=WJ й/>T[lOKзXܻ/Z{ώ}{'U=$ƍ;h|[a e" ܰm)޽~ӿd*RZkoGߟ06W/HH}DPLW88v f3#K2;K##t+Cy1V^[\P6҈K ~ȒAU`9NZ(l.%THN86ȩ?t{CRd0]Xmַ;8 )ҽz̲!cQH3 %oDͶXT0eY:3ʻi~BZJK"37lذnݺz^ոZ}0rYcP^7'0 !OJn/ 2l q'MdZwЌUࡈ/rk N~z3&)ӈB+4e)=W*qڻ~Ap$ sW"rCD@pdj͹.+_e]KiaI6|+д$97*q/$ Hʂ2,!?K|wПIJPđSض wRٲ`IJ$9Ewiban{ A3) L E￟)C1" aِ٬54/5/eX6yH((gG0<3hoH} SEѮ];uCHиnoV?2<;J4P$>8:²]OI9o8 `MD&nQoKy( ej5"#}/UHWJ}% Qp$1LË-ӐDy(ҧi{gl$ Qw)HVR£8ub:L+IyXXALM=s=H9 $V {iEm_$[˿O?SO]~D^aÂ%¦K1m 1ِpqW/_-d62 K2Ђ'Q&m_qyѕϾYۗ}4r|ܹ3 ?llfͱRݦm'rYBa ~!n#/.|3DQx`2gm/bwc˒RZJ('(X~b6p/-8i, p=2 aez\ gi*2-5 z,j9ԁkrn_޽:;$ͦg}GxʈGKȾ}n+a:?:_MמƐ1{֢4zQ{K}emȆ ؀$i[4jz[m%A‹z%L#伄%4(.3 /`E0| 5cDDKD7B'ʻDyO*^Xd,6<Ӑ`Z4&b>33~T8\) ^%:{ 6mڴnݺaN3@(^n=3 iEp9.dC%#-Ybsy@/]i[ YCQ5y|p3:a9'Q w$q#G.!4Cb0(Y+ #-p@E7Oˋ ĪM(zUIPe u1Rm-fʝRz.vvak%[J`]( P=<'',44 [n[n̜mHdڭT<)w/~8(.vB"y$Wu&~#2+ʬZ@RInYB-y9̂c93>а B|@0af0 ZXj]kenȬV~䉌xŋ{wo& ɓ5"T*"k| ]&5Zt'99OtD"HԞ5>h/++5O3SR#kqL' k>ǖ8-QxfcɆK3?1䦸c6Y.ԁ̑ d6Xu1\Khw/-9<|8`n~BbZZ!67q[{wJ9Eö[0|&rU"#bرc/yK3L&I$XA1@18Fyp~#kWsF48jLX)'>"ҳo۷Y.ۘJP2ipC)$r!t&\7V^pC A1*-l"8PrLLl 0J}EѦgBuk*}P$<4rV6Ix<&F`/'8pGGG3L:l :QQu#4d Zg_w>A2O GU>>22JR1BCh mr 2hڡA5M#t/Py@*N`:.ڔcmwAlGb{LB7~R[ˇݡ4qLjw󞃈1Àu1Ï~"b%c9 ^V)4bLNᄸ 4(6],²?5]P:1NySPR|*9"J1z( 0 Cb5fJTMv\/%(#6lDG0 Mhܒ7I ˆ4R@#zt/}sprK:_j06ˌ V3bZ[C";9tLޏD- ~ >88L&y{:L-3bC4=T\lWt;k:~ eV.w~-9,S qFh!73Vgr׮WolO?Ƿz:Zk/A6[?(B=annS!y5:7m=Iץ0 YAqn@k++Nר( N@ c6(&WJyE0>^@1$#KIt/_P c{"Dφ\K0<,ό 5dJak]X1Xǰ@Epm ݼ[TӵZJ%eC?~F܃;QF|xyixkk5b1P;d1V+0 T9˒XYEFK` FZesl,-mڝeV`RF*fLf P|0&A lA&΀dL%Î5{T kO;˺O*\HG^@#g`h )8I;ChRh,//>|xlllhh($IL֌"W܉ -3ƑqT,x9$QiTUQ$d 9 yaNiDCcx!, 𳝭b K>rz{/G3"AY%+7xOi6SV*/~Q F5bCb¥e^.rF^++0<6 OH>"ƚux@*flAo ?wsli٠c^f A8v nJ i<7@sU0gw* IDAT7r5M{iFN[ 2p3+G2С1lͿE*8e U;d2l}4#Z3H_ӊQ E2tV`p_lGbR Aol#YĂRF6{JbC0Q?¡{FF>ZoDNY`Rb#rlviiirrdHw6:.L9 :ň8t%Bs &&6Ńsb`4˓pO^]Z.r(ȦDD\*& (9Q Ƿ?===wt _!6"ʾg;|G.}ss\ySSuIu=*iP[[]ڑX \urOBH6.H7O~&5+lꍡ܍Gp➞ZFx{<[TB_r -[4ӛǠ_:組eL*Vg'o:`!cc01?lel8T1(e{q_=>|;H4]K͹0SӆWwviEmf 2"|6]YY~Skr~LFjg.ÒQ(DūjéT zm>E\c-M@E!^L8* TPkkk(HiC0 BÈB3!(>'4%S 7 nۤ"`q[A?J!ō>wY={n."ccBa:؛H|' ]Yf@-5ӳ«_ (|.`_E"DB6}i24$8o6 < ۃ7zfIix2D Q2 >svځ\7RL7 [E5>Q(LW*x-Ɇ_l>X\m"1X6a N "wA0S4? B}f7Yam >!9Ayx MA.84{lx^-= {`հ<`k(a.בo~([;0!-d̆HnFҙPLh^7r3 y%7B;eCd33>:gT$pv9#xI.˅wl٬޽pr3/,-ͳ5rzV6a|]\!h?|}Υo MtCB6SX0!+~ݸ+ 4`A2M({2==~S{a=-mluE cm:` ݍq//Eg2_r?ZC>ËK:ĆXw YlKK&uP|=Q5,.FEӳٚ0pG&4^Sx= ~]3cחuZ2 ^O~SP :/۠cuNm;pR*6`_a\Zr`1hO5r`xr6 ⒃vB%P (Ee%p\rAҋNDh#h+)Ȑb|8 Mf(oX_GßGbqCJOF~5<`oa)qjg*B85O^: L#kQ5A|N(9ˈ#8Fb??ecd!E*+p-Xl@$#9^^]0Ⱦ r_@y5H"z5&YmA5d!dj^UZ:bg?PqHe0)F1?ym)_mpG]w|oK\7|d pP(6A?;aPWYYT8snP>CCTnO}8p Nȸu!$ld,|ZF~=E+ƌ ɈpLZD.>DzbP ):_:MYx*(讳w}lw~mm4Jcuuuiiivvɓr9Lb¹瞋2c``y9N$1$o ebcb ؞ T}(4"MH&"˭{hoxB>m$LŵGZ;]?HʪOlSgf.TVW܆_9h#JA/ϦIdcE SQ-ddS-4ȕ`ӟʑ> -,QU>TN Mi:DTt"E,HlP6*mxߎyiwR)\b}u?q>KD2CdEc^0P4񣖕-鵵{Fr޽{IӨ4-Aá4 : "XEX1aJiP$t:N4Hi@5'~vA ݂sZl e]@ **: oqޞMT:WeT yu+_&9@={6iCHv*eh"/4 .Py8F!\B %#4Oݣ5"Z+nŢ U(f##A*⪉Y JX -<g+oR-i۞ uAo/,.ʖA8yRjF"/b)ytبͺkvT\1|xG#++ y2u10 ćmoـAC"gsUjW1n<9 goQrE,(.[]]utbH"wMo!kDߞxT7PTTF{0n<'T]]]]]]=ywִJC $l#']Vwh@[ZZZ\\\^^^__VX,Je24>'T4#r7W?MuH}!"u\?} T;g8}0<`ffr{4 mIw Ҡ,r*6˜c;&%DvZ+c"B])M-7SMs UäԜ$eEFkddB-`z<kk[a8 3Ғ+Dv^ޞίR(G/ x)23r([0YPkl .y9_?x*91~K+ gt>MZN Jf3E.)---&+\zk&d27up[^. ~_Aqĉx8`QUl}}K_R$A+]B Fd&)1lJuEO49xwtZXXX\\\ZZ%L&300JWb$ F6S9@?$ B-Q!UDWolb@|!7~H4*+5ږ+d; /6B8^%V٨`z|Wj:UZ'8åw:ލ~X]ݻU9HqrٚEUHXm#߀rQ,֒ku 9AP~@>@O6oBg3gegt}"  ZDz>qJyxVmU}voMTGn$O5lbC d` L4?????ǿTkSFa;ΡL&5Prr ֳ9(p^j?~m2j@bxwcx:qCK7h*#hAf ȜbT'Ozƶ0??\,{{{S jrN?=Dk -G|d^0?mt !:}+ aO]~'Ox#'m8ҤUB^ÌmY]ZV~|v?!|sor r {X6.;AVV _zkb֘'r- ͆B?V\k'jF!:ʂ)VtzCb qZV!L[l 5~z9/-F!7/{^`֡oUY3$#B]+}>9j*REtm$1bY?+6=sB|.|s$ :N !/,Kv_?)x1- CAZ2hH($ko&9h{ CRk/mR6l6> F__ba ˰^!ep6Mx-EctLG"Qi]ûl sssW V!<43Ym!6ħ>a:d߭7eg 7 h M☓&u4lȹåwFeĶ N'"t5Eяg?3l}mAH|:x(/ҟ:'NEOʅT 3hcB.o#PH^5H2rAl 5c σl6pui5ǟϫo`XZl-í] 2Y6]88-2ņPwCJcffԩSST2 FGkw>1 %{R5cuuٶ<4> ޷o:Dm+L&q/X窊¦4|0A3`2h8 H`>[__b###hR?x>[cJBhr-Og1\[umo(9'-|D1]4o|/B`X.-pͯ-KK00t_l+?!o |n =ػwKd(̈́H CWT`cP!|lb2yG^.i1_~AZ?Bb6B4Uְ~T@2J-kZLdy{6LM| _t-=j)kwSvn4&j$ijqqK7 o@XpMNʌ+w''{E m/~qlh!}###TCuA2Ё̨6NJГVb ~=H]ggg*J* ۋ}'5%Briviޯ ?+:4ۅeL%NXUT Е[}}?TnQFybt*Z-}g&~?l|[p}י?E1Ye}AX1tu, R03(o`X IDAT]5벉OUN) 2F['͆Qhq|Z ?[ؠ-ܨrət?=fCr7Ae HB<@N<׿^<j[7)6\|{C#ЇŸm뢂VNG?/.jAH;P aSo2 p覘HV\E牧{`uuufffzzzzzzyywdddtttddGPg(Յ&$nEtMv@=Z j@[6Hr`!:. עn_>>66×GheF7$8_)\?a:BWԳNi=9zC6tݨ[O?+Y6:Xj'w_Ră[*FC<f  37s{VCeKput-XӀ5w+˺mbQQ7ZSsiB8xg0!BFTB:8;;{A#`qMë34 ꪫ3 *>`)A¦4|~;S6hdƗJ|>6;;;==ONNV*ݻw޽{tttxxSZQAcF!U~ܓAMf P_~r7lb =庖?OKƍFNq S,R#mm:Rl62Vs㍭_PFAX;o+v/ΡpJ$d3Flݲ144զmm\.G '2 iڨ1@$jC\T*\.ϟ8qbKG`|mSJC{9<8882#sG#҃L#)VFSB`A䗢ZiC"ۋ vcc#.//A矟Oߵk0Ƃt9ͼF c|.k_j8[Ff D V\lp\B8Jw׺hx50TC^Fhb/z|4/iy*M;)wUڢOjZމ[EZ hkxIVg|F5,@?眳9##f`6Á[:C[6qщ \&&ĞMRop>uP t*>==}%hOǥCc|ul[ַ×_~ &rpD!Zif5ذuw7#45FaB499yɓ'O={޽cEŨK6m2;QjԂ[ӬAێcd#|ʕƅ MqlݨTwC-RBC҆_lM{nTh ΪIcooSSf4p8 ̯U* ĵ[5vl BC;-ARiccBtbZohᇈpC Kڀ]hg}v߾}‰v]GRaeQÏԧ>uM7al Uiw 񩘙I6 7>4[YYS?\&AEhPd#BC78D;S1ƳK3/W2CE=n֨Q64naV=kԺhSo֓Xۈ-I7DS wXfiaZjYlΉDjEJ\67Ϭng ZgbR= &N ?xb{פp:yFz|[B}a;Eiѵ^{ee2L&CsG~MՠCQBc PN˜@`]p7EJϯaƩS*ݻ0B#NSFF!5FE3lBG[7CP 7w}9٥; 3J$Y?Xۈ֊{GF,얼p֭xon$/ lR EH0Eas]rN<FXR uBa!~$辑'u, @` B6]YY/yW6C#߼׼5wOӼ2! ƯӔo+"l4OaAMQf["N^\\L&sڵ -6U6aڊZ6k_k?gOF[Hybm8ܨ*v:#c(x=[#Y;%< (64$$A!H;z9s9>Na lh|MΆEEW+bddKQXOw_0<>ݬ!0N6eMR)h;MqXSNMNNnllAaF__`3l n)гWLH?ѣ$.d6}6M˭/ճlg9TaӃry.G"Qv갇w 꿺چv.^w]*i²C)4їnCqm]~d2Tu l} 0J # NL=e538pPɓ'1|dddhh(JR)* Ncbn&=S`2n&m{գ'h=_nm~[m~]r&%.J RVPF1a_8mʓ0qEXl<++s3mMlpt*?m ]O/,6b"N4$گ)hӸMH#bTZ[[[ZZr###FD8ndBzS>dϸg>sٳ'LR) zFDFm4P41A.{3ql 'R}h:ut$ٷocF*B#8q@0H7:eT^_14z\rٚh0 ]e}.: UiL6-4Z%GEv`[GZ NHn:) o/}ެMƒri #>~#wD6=`"F#=J_Ç0-n`] n45SVqFu i ErfWWWgffsZ3o {^ + ¿x`?(𕜦VlFH`NϦܨ}xc a9)`zzG2?bi| ްhmn6J{=FOt(U_l=z~(lC oQFק{RnT:Hl ߤfE'__ GQHaeI`^&n35#.--NOOOOO/--Rd S?Λe<Tަ^N+5gM'+1XP 1 X9m ]@ ggQ9yMQm5,ˁ0wbbWݧ^vY_al iXaP.6Б#E~u$ƥX,ICuSWgWppe#תT*h}' F$ͶA-o/X,ƹf nE`\cxA)Zp#BQXofffzzzaa!NLLT(3opѸ0voEie }Gp !.!jV7^xa7SO~(`z*̅h<-pC}yR6}1YVfngqZl SS{wmj` hXaPl5Ρj#bcmm Ws9HbDJtf1|IAKzzyThtUhb4~c/D_Q|߿o扉 ,1ЌMW j{QxQf}'A< 3333330:::22eSP_ EL~ߩ4DF:D\4A\<|>+]vYq_oB4&݀.6QtKC<N"{bfk ]QTzӍ*Yy 7 \\29L@Hb̂U $I3nJh4ƍrF}llNr SNȌf?O4$tMy4WCX3d0 1Dx '_ eEtZXX@kCCC$3v!"!b=]6)$} u_#OJg4wKB4`4:8UBBnT*\vEZUjE3w8-=n y71`DgFOv645:*T*!W6 {<6'%/L72w83~OOO,CG)Nɓ m!8-lMɏ0?L+_ʥ^{D"L&y3@ Ƃi.P15 O2w Ɍ98ģ Z3ȦE ShIi{j;Qi ezNa0vA=n /g{ O\>zS]}rr r˙n6F>R߶٨ t'؞t#9D@Sw?X;j=h0F~WM<#&Kh ~]fUE*Cji0,F1ܑ:BG6khpد\r(֧k-Òp!ނ/383C\.//s)К mt:N&x GBO% m8 IDAT@քYOۀMqY`RCH=8~ݰy=t##ss'*Wzz_+s}ɿƃm_,lplm3CgPn7V͢;* f# ""{q? Mq rPHƋsQT"7p%\.s~)&xKMw|k7%3l#f_qZStS\-3ؠZ42;;ɦ0|ttthhhppp``eF<G,&{@ĕh i\c$P [ 8o;9&N"f|pLlS7b;rD^[3xuW_'/F"'N|lh?n.:j5lT;W4#'—:"4:(6r. w)dx !3 Ȯ8A]ok$FxrܸQ,Pp?A]%߼ѦuK{o}{TICx iat:dlq⭯hX\\¢t3x>2h1 6.dT6#j[iaS2 ƑGUuk=aÿo ܻՋ@_ĉ/.1ٰ2vsE;qb6:(6~^wu|7kUOG ։#xqWVI 7BX,چ0D"bP( QSKe٤`T FH=}  Y$ٳ? Vh @EmW u'bT>zы^300#LR>*BfP7qb14 !:E ~ԃ p`:nS fT* '"?%TP.+Źpw/]+K3]7bgQkY}{luB!l.,lKPFM&4kOAAx+4zQ0a$FnW\CKOO|9IlbRˍWf1#$by`ooE}^A4lx*F!/DƠfB2ciii~~~vvvjjjjj*LsL%T* .l;H3iƛb X'P rki{%Ć֣jbѳ!`}A KJX,~wgbqѣcK/F"[ϟx|O*uq TluZXL7"4Pot}mԷ;O<=tqbG1-6l_}"KLC͹d~= '"b)e4n>rPQ^^Є"mhuaN^-vwp sN&TKnA_{t[fh!Vq Q(VWW&''.,0*52C qĶn+¬25Cs~0g7K):v6C(r)4ux?Wk_ϋD"1~x_xEvw>;w]t7lu:+c&$%4aA3j\@5 ɀjJ̌1h4"x1t+ -N( &Ym;kTbz~4e,,,)ݿ?i ,He8ԾEiЧI htu43i|%Ay^0e0/}QnT߷ U7! g5~h${˩O=ǿry%A(l:p_ڣ\y0_Y3݂.h?:[Oe/#F^IpfV f`3V\]DXr:o@$A˴d FcDo!܀ݸOSBhP>Op S-;jf#P 4?Y(tECΒ]l+`.1|lNC:b !0C,1|g6ahNWrcX<T| gvrL֌Iԧs9C#bo O,B :_=ϫVF`acǎ ^xt:P Tħ v -Y$GH6h.b@EcNς24Jl|^v /R k#?{O_*u휛J%{E7v?Efgg539};Ӎ<]tO=W_LCZ= mhB0C Nz@[d%Ff?99|R;oQNMVjm{W_}={@pri[ m^o ڝl6]YYGw|>{ 2u`[nRțE7 ##nf[_If@0Nc#Mip!gPalF#^ĆP\H Rth4k(p~k~,6873JθSA`g1}[S~8MG}^&Ϟ 15TiASB < [Y&2GT jl$Fɭ48w)ʛOkG)0E¦CaT:ϣX\\DS/~s9gX+¾y 1|uuݯFKhn]UkRDTF(wMwSTO ;0YXʥˇT*?ޗzSOGf78Riied"w`L-s g걱t:MЫSw#aa [חgffN8QT&&&!.8zpV!> slMoϗ(J$9(BiXqȐJ6ED %ذAX{w}(W .d]tM´i~;~nD]pZ\{/B`hCux*PضL ~,ӄ7xX ,Do/ {͘8?< #'0 cK#oy>_]]]\\DSy7::D"eTcpՅoZ]];u暱1-3(ijKFCu9i8':ܙ CeSzQ#=%;1 (,mAQ}<>Ump-,,<m]t4VWafWK't#te4>UW]L&#\:k:wڄëJPOJdo!%+G4e/XK_0BSK_N`5"# w9眃ɚlJCA7x P*כ .6/---//c*l6gϞk 2eYF2܊&:4j~500'|[E]tE]4.pu^sBNEdCv _J%,tJWiFЈ_7TU.<'|_=t0kSgwҦ kM4bh|(3Ѕ^8::Ffc|uoSنȆ GKeZ'V6 G6 4֑AC5HixAW4C,ZP@,Wef ASQf`,xo.袋QmgXl |k+!pfiF( C'ARDzBpO->KhūA𞞞jʉ[~qҗtddkp009,A4X/.//////,,,//L&3:::<<<<bz; sLG4(>Ků55ʢթoҠoK/E*CJtr2øg!"X=m}}=\.#JxD"DZ|oN[Nl۔Qf@pTY&΍S=VI#R/U dadV6#F0-3y䑦.袋.Vؾb0^K+Moxj}(6HUY :lԫ,9D5]ՠXWB \upyޗjhhH C';Eitwu }$2x^.85V^/黐\ Zp5x a5N 6Aص.袋.8Mt v0@ x_H$PrPo0!'>_H'2Sp]8gd32Cw硟=5-};馛ǑʇL?eSFx$HP,1hPT*Ű=h@ -uw z&0^Z4; 4HlT* :ED8a9.[,dD6wB\]]f.袋;@l ~x+^Zb8BD+HHl«{L Fuhg98ex #6n-_.U9rdtt<9lŠaGZƦ:Pfp:2C\E\Jop6 o(/Hp\i9:2mX-VE7m9z)wE]tEgv@?x(9C_D ><'X7ciWMܚPyZ뤅zAqJǺTPH$}..M+mom ~Dkel$Ǩh=>Iz9qLbsL6W!Jr4q)PllllD"n%ϖFF&dƆF0g !/袋.ة}h2z &6wxE2 C9 t0Ò6yn|.QZPt.QSm ~JHJC!ַ.A\ަX¦aS(Ē6j Q,Q<@UU(+eϹY~~lvK;6 TL+ Ѫ%%L o:*n`BϗS2) sL vga$qtn TؠZڱԼ3s QrK;43YZKv;ݗ~n~e;cuuuhotk^vukN7{OA3ܘ$ iZPˉ i b4L3E$>͎Jzi`~.8 So6#N>|J Mŋ k*كs>arߙ% öO/6 >EYK8Ob =fz^|=|ᇭH>䖭OY>U)a0RfnzɟėWxA˻[n?=F3٬AwIceۢU)H&'Nk"3Dl0hn՚5khi:0BgA`]Q{|>qd- na3Ǥ|L)U,PEpv޽~`0H- J-e^`g]]rd$iK&]űT4KAǮgY0X`^f]y21 VDK@d KXvm$a[s^uwzXJz`ue[E@ h57 cɒ%@o?%J !&`Xۙ6`҄%vyKxs9G3BZ,DCX x"`ڌC%k麮(JWWׄ 239L+WduҦXT{ϗRw@tzkg6 fk HczƐe޴P( RW0l,VElbX}_y|ٌAcBk<^f4(J&d2$Aځ2fTX  { cǎիWjr% +Cz:_H\h# ]ir 7ͦ$;+ !t'd2jժh4j7UNQz;ڴ7q. e'PB&4V(4B9;1 V(J:N>h(j Zj4 0'{lv c|z f.d2lڴN`'7%J7Cbp5<'^ bw,)K.nڃn-eFQl1f0CQD"z8g0K2ca2f9uie4 p?,Wef)/9؄5vEU48VMGLi}uGLo1'ӥP&om:j1%H 3h+[EQRp0 B555)$Pcj9x`v!"eVE2\.-Φ:y(BS ږ 1BƞX 0p[>oܹhk1"5?ϒ-pF_"by ;a1 3TUd2xX,ᦦ&17X'+Lb=zr}~((T?EcsyaW2.3AWӴL&sĉ|+UUU4"qD7œk c7.eq7]Ąri)u}1%2 \dfL&L*EV~D!1e3ǝM0@lL˗ӎW$`!%>Ćqil! 2Al1ş&{ؖòZ.`4,q80Qiet,1xRmrL& !4;I㧩k*i0بllh];/g}1; (Sc8%q'vCphƊƠch$]$Nc^f͢E` V޲T<% 6Oz2޹D72ssP;ƵbS)q6=0i ]@h4*r$acFkҏr9EQ;0 6*-[D"BהCaη)K;ˋ^;4+*q ;OQL0 VWWΒ40#ب`np8 ^fLHcL(AJɌaWGDl'%N`JiTҙluСCW 6* 6a~TTbYi/ 6kRJ/QlQ%P($IR  c A]4XQ̛7 ulk&qsXYoX~JN V2X!V{||NƠhTH$bpݚ߿ FR]]T!BHX 9gnN38 ;Q&[&aY7bCiSX,&IR45 cttP(d}M[Jr&A{Ί2a:.ϱ̼)) S( ǠTWWB0i 6*:2M#V7=X,fgSM8D65Fɀ;C=# S(TƠRUUUaDQ*B4M +b" B }i|;$P]d.!~tGK,7pkK!}SsoM2L&x 0}WӴ={8L;JI˰iPA=~ Mev-VʩcIA PeߺӲt:=<<\__zΝf5MKRGu8GUUg@Ň5X$1>].d|b%-b)v+Gflsck$I555y(el0y 6*p8Lgk/YR QokZ7xp{QSJAfݥX(#X,XBSFvYrР,asfYv1b)]*˩j2hllD"4f[pبҌ:|Gp-ب06nЄ7,Ө!s(vrŒ:ySo뚦dɤu\[L&s_\ 6* : ^T\-c)<Ēӊ]9[tP|7[4.#NP:U Tرcjf={~rۿ>g|㏟xyz:`F2xL9TZ ɪ){9uJTk3E[ٲ6St:LʲLe]Ga%]rӺO_]z{}[v,ϟab`s-ņa4jbcCT|4ä1gA i@ 12L"8|SS+nݳoFH7X&|_?Yi= #Ѵ"r BNlXd8 pܜ *聘7źҡT/e2UUʳ̲eO$ bcΜ;t}0?B[cŧm{ 6* M~4 _fMS$Ibp" 8l[C'c>d?OUl6L&nKtWfQf5MM46޷so] j!ך^"vvbK0ب0;FqE{) n-32 0 B-83TUUຮ  4uvآ,{{iyB$v=i)sȆpVbN;f4E32bS :\Qd2ye˥jww51+ŋ muz<Ѷ'%D9O5!DGG널0 V*`2ꆂب_uFcǎ{lttQQyrľr(3n@xzjJh(J"裏N:u  g>s3x᛺v۟jk{w|_l%ߑϧ s 6*EQ^/eF|*Qren2`JÔ:4^c/@ʡC_OxՃo>]udppn&6ςbJK!* W? ǰ {rJQgϞ8qbJm 즧g==?}}mR]"I&a uAtW*{oMMM |v\.XŐ2~RH) r*J ܹs*,6MWL'>_߬cpp{}{t4Iq_3|>\<=n}0--%$ -\s?r:m9 C ߮Y[(.⣏6"8?2Τ-F}~,WkF%"p R^ăLT]h)UUDoo/ h|e˾_Wi72H7"I3g^}UUB֮Z]խ]Ut l ]rӺ J&ۢE~F.ng?88$um;t}K|TOXbyBƙ 6*A%qb* `b&V97,UI~?Q>S-x<߾}p X}BX5k^^Snri6 ux0!ooF"m-+nݳo;kZ+ݡ'p \n"E"0#].{Յ̯ !@SsCON?2ȱI[L.} ^ٷo_<WE4]i?\:/Q(]LEX_gL*p:@#J .@i08v/todeoOO *;8g>{5vӱfٲ'G| XD]FBWbsܞHؽB 'b7B$IPNcXqq٨lvuwrџ b2T)S|94uܹs'O"{%Y0x -]ty~_U{Xa}6{7Bs-i^ v\q㍟cv֘VDZu}`]Ie/CꅣG[_zoxwD4 'i٨x|͡L&fY|7, X!a hЍ*pH$}}}/`"o|ӟ~W{>`EY9a !$ۭO@_~wt4ůJ&Ph~__nzƞkP<^ol//^ Ϲ\Eɑ J,L`ݷrKmmmP`ͩnwP0e/puRCyLu4{N`Jׯ6ԩzz~J⌎&=(;&zJY[P]ݦMXmk{ҥ_J&E0|3g~pTUuP6.ȲG>nk{sӳn}ǵgH9wr/&ǯJЃ#6~>^'Wիٸolg˖+\|oJ,IֿL&OR34nB$,Ua%&d&m1Q4vM뮻 R%c4KRS4´=tz``СC`Bjjx[nD{>@qU 5ct[o}IpݿA(JL8Wb1L]6~v=k Ka)6Lz4 tp6sTUWFwv볓+Ci";:9~|^9xw!jh'[Y(d_O${ߜEDbݺ wrk2yҥǶ$p]tY.[W_]H?ᆻ ҥTv[ۻ~E9L+O$N+7뭍F D8_ +90!Lb3uE@ xhǢp7LmX&iLfhh SCi___Mm$Mƺu?p >_mK>;<V%It334}ˍ47wBVqso\YQhџ/YK~v>}cO}xy`ժܹse: kҨf8_zu,|4a'90) >il6J[Sft_ߎGhiy̙p3={>Mզ|!==?裧 !يp Í|oB-Aso>MΝ+S$oܸ=^H ,_ww18_Wm9UW:?ASh#t:oL7!.ջ\'[}}; mppWU}u֖ l\2T ?ʌ}M۷ӽ[ZmlVKˣn~BGaHS DQWW`/u!֗+o}UOnS6{nO|(&>ؘ;v^fXѢQoP-WUӱ} g6a!تߢo=uW%񭑑IMM\Whџ˗_Z¹s[%ɽ|?TU-ܽfO>_o߻fOvテM`f>u0:;;Y+HCudຢY::/\ł|}gu}(|loZ&!$lK3=09~+6JQz< 64KQ >fͿ}~keSOFȑ?c |}4 IXlUUU έÆDZcNY\ƺ4;nFI|%T j*6{#4 01O. `"amZǧ{ L`ZrIENDB`ChartView_DataSeries_Toolbar.png000066400000000000000000001135741255417355300370750ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Chart_View/Data-Time_Series_Chart_ViewPNG  IHDRJ+7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:(( efǰ wܹsvΙs=D餦HNN>>(GyXw3@^ssqQzcEWgNbB& tzNUÍeVԝG]EL-6zNӹ\{Ἕ?u7[Q2{Y8w]}`Y%;&NLyW2AvQQ?.?5/0IqFz۱:ok2{Y9{n\h FWdhHqzoD=3:!fÄ٩;n2 N݈kUQ%ҁKK^5UV=@=*-I8$KG4 ]ɻ}IzhKL=S󀦭rέ*0BdzFolu),x^,%e!@p'N23nl lSw!kr_wlY<]F7c ߍ#^))2po۔X 0x#)!46aը;MǎNNLW^5 !ګ{̉Z;C;F4x_0p%\)ճFB{#{Gz= tq]:J HN@=qUxh10g jo?Wf8mʦIAG1@7}΃F|=If$'EXF' 6זh?{шZM NRT9t)I!֚pᓿps'vRHݜ*NqAd>f$H;t.-wLMo0hV/ݝCu^QN9m^1mx( [T:,!Pg vS a3wGbNڎ 3kS֐abc.XvD؀@QyJ]I>S"~3R:{vfNs¾m쥄,_ٖ~7}ҤI~ `F^Vo?gao$'@=}zɝ3pmD>Öƕyϳ:8.ܒW; nfy˔@vn6۷4 5>3rY_3;iO:a V*I@BBrekWWhca0ӧg hPqOh$H;*b+zhi-kkV_Ԫ&+by6Μ{#qZ s"gHD 4%)-p "L\M)sjK4Rz`Ωܥ7Ci/Ć<`G"UYmހC-TЋqTEtukL :nV hЬePPVbHg߹jR!AuD'%DGfHNLA1y>}DOZ;#6viWZ:pƽ-+*!H%[:iZj{ImsI5mSQ+.ر|\%O[|-|I.StM,}ٸf:jLKY++<H}To]-Ͼ˱.''ؚj+ Q*Hrkᰄ]?h=򍬀r  8NĻ~ w7@v<lBL-@B/X j֗2c?FE-Z\ 3Br1znT2j$#f2Ĩ'D[uA{ _ecvLH jYOo-ϧcZQQ@[t'$<,8wlE\;=G 2zqo49͆&\s.>C5uz,Bg:ۤ1q͗Kgn 6-oo+*sWN@Ě_oŒQ_RװMBGĪ%w?-Txa )srs&1DNk :nGb}&>F垆=W˦8 f ;w7͋+.ͨ7_8ˆ@E|Ӄ憍fĈ[!l慭q;^4murrrrr r{ +ӎ2sO"VEE{R%Άr\ xڲԣqqFcM".=t5==~gK\Y2Œº!sMIO.yϨS GP[$bDĒ7m:))aoooR+׈Y pK\E2 hW0,FZ" j%+?Q*: iహAoJ/?'QZC0d%71 ӲPj\S*}'" )7d"2fhDd<36A} OgUR:Vu6jW)ww]a̮~&5>RְkQ~ufecr枧.PY">e0g_Ѝ7:j| T*J\BEwciBJӚAS>T)w"0RU.A VOO'%hiJJcҕOX|zXEN /_T*i2o:,j/RRPPgϞK.YV<"888t]&X%bchMHPzP:xeӁڿVFVjpD,z$#^i[xzgP*w Upp-aIB~7m\Tk˱3F4n' JB!6",8pzp~A w)֬kԋv >bV+(HvT[t U@ЇKk'_O=sذ:~_3)t~ g_J@Kę#,Rf_)P#!IxȠ|C뗈OߟG|Fu]zrM5kl~R*-5XhdC+ўn٩z6jH\ӣb0W\|_zukk.33sݫV=z\^1/e|{بRIICOR?_?_P777[Ie?2Pr}ՙ_Y!YM %J]~_S9##/)Qhh4Rş^1X$PJu:-@YͣmH1;XAKPRtvة7#ORR;Cl \ќ;wAXߥRȑ#- BEcY~0L{`cUlNL}V][շ+w/0sBȵkY(֭[r\mкS5[WГۗZnBQHL]n9&;:n٪/C-헏(JC wt,"Uš5kvԩF/_>b WޢK  k(رcHUPM&d{>}ۻK8ŅeY^_Yꝝ"nWK+ =<<5j/ɠT*d29"Xɓ'yyy4QرcN}aJVYͥs\xӳ0raa@AiҤDz d2f;vJ9*ȵkݻWd@Pxxx{xx(7M(aW=z4>>رc'NWĎ;vYpsq{n5 ,q@g?"ƱVBb10 rm˲e۶m1,KrcgO<)@+1WY|Cj׮=vX777O<ٵk׏?عsƍW*¿(/V\Y6T|z[J {!7nܭ[7Kz^\Vּ@Sw18FtQDQ sD]Sռt-@l}\;u䙿6{8aœMEX͗v_Mkr 9cYv̘1uXcY2w5"6TgggO<PF,널 .ܸq#''G(:99թSiӦoՊeǏ3ӵt]]] ‡rXs`ٔ?o|ٳh~tzo_)7mϊĆMdGSE6T%CHz?OaՋq-v*_qM ~lLPװI,J\]Jsjט^+GڲM ]R坁9c﬿tURgYڵk)))999gs5?nR^\!N| h$TN3gKv&1cX֬Y󆘬!YYY mڴf4)iš$:tpիW^ʛyjkt2ysM?w[1 J]7}ׄaZTL;3|ONEYNJPah4Ǐ,ݻ^z#F((OOÇ{ieU0$>?0\}_> 4̋;O{L PvԄ4/`8h̢vW~ h.m=[7_/襶nJX 9[{xVW8c?,^'B4=+_gUIٵ*#!`˾Y5r2r^2JLpG.U%CJUkeÇ,۷o_''7N\/ZH s%ϙ# 0 3_K ﲭ;Oرccnj7n@ jĉ񉉉,˖)O9f*Y4nJV sիWˋa`4z`0LFӳaÆ.\0N3hsOV)oʃd5kVxxu[dm0^qFHH0&d2\R_7 K:tpu^^FVjrr@f}w~mhw p3杞i;6j[h9o62Y=yjri

A,S2/L Ul\Uz>~>@9 XmpB U@(=%Py:HevX-5g233MV&duL5yjktu{g)1F_јlv٘G {,IYJ򲳳e2YAA`0 Fh4^o^^^RZB!Lvz,Bt6!NSno{V}VGoFShoDp e8 IDATPN{G"SRto QQQgΜIIIҥs+ q2!aYħػod21 0|z^JVUoF;pOA8 XnMv F6!G!oe k"m$l%9@ }+k& +!䃛_}ݧ͠š@!r` hҤ]^ؓ|O:,<_V}#KT q;e߂Wx %E ((X,~߮\(UB0׮]gXad2s=s.GrF޺{_g5| r$W9w;5aNNNf`0?deZA1Lŋ;t ,-MV~ڼddΏ45r%8N9::bxQe9YRt:9Ŧ<j~ hvdWy8A:&8?kL^ @m}¶v6ZY &:5N'VV{%`4.$;j$YyϦx,1&m$b#J:MpsX(cz*=F2d oj Ѹ}0V%\IZ8fhƔz"lJBN?t+hbM`*ғ|O .3ڹ;7 f-sW[@U;eYuvv.n[{Y׎>s :u:| oѹe=j (P d?8 Ao"\pWqkJebj/nbU|l*Fws||||||:MYowgG;N]?ftv̚ rb'ȘsfKO!?d_Z>/E}fBwbG_j?޹d2H$ E"H$ii~J$Nq\Fj77-~O1a#Z׶*:}eZ* 4 0A=+ o<*a/W-ԫOB^^/O0aʁTkfϢ)#',Y6'RځOXR&?:a|iţnj5s'&i:x@z== Sz2c*AJ]9p΢b!êFbW#W&ݱmDĔ1m Nj$dX'vl{R>\P)MϬ Xw0kK(J9gyP("/aFs#~ưw \.wu_βlkuy7>8xaܳu؊,-+<X  C 6|߬K ,10K ,30J,?_ ٙ< 4YO;XdULMVa@0" O@"wd/A";4XqIymZhr2V#|b:{6~ }IKcXLnpi"VǝP58~)d]}HNH%T@^֩m~ڟh)aҏj[l\/AA֙q/-5eS5u<29cWcL1;s@\EqٌI}&N߰ΗKp EQ> Ub!73f60pSO|xig(WڷTY1VWO)s-+eK0fj͛7o޼v35E?ܑi2/]|7E^L|6@B2~Фh5Vҥ[N_kjE+<M6U wlBTj/-^;o˷io9"BںX5r ]||mqNjF歵2,--h4.bT*H$6/ aRSSe2U0T7Q#Fl^M CxQ\>`o~f9TA[74x~$@ `ʘg릘!Bt/ ABYѮ pRI(d,-eqLZI_$ȭ=נeW{j~+omjV*-!!\QY)&+&ekؗtlA-\* }7ͤVdü[ v1|9~;o[< x5YW: yڴСC;ܣXXḅ ~}p64m Y>?gkU FB녅N-/֔{I?M 6u@˖ע37d>{sDh9 2d\M ;w2_~gxϷj@4aK@_+}u| .>#!MxֿfQeCӴV5|<˲ rqY?OjڸiiK|sq';B=8 ֪ UpF C,L,a8bdc7&&Y؇PZ+b#&Sq3)j ֎d֜&]a{^1-?!up=nh;|%Ɣ8M*BeiZ Z4 o&@]/ڡy_$IR(!,RYϷGLk!KŪ,>BCC&MNvZ@\ n,MU +Ds}?fPӗ9 `U|gӬ}-sN[haR=M{BǬ^݊㲂?a\@ğQ?~h .aÜN۴쳶 ̏_¦Kzрq^Z1"==S@F6|=컓޸C|JC6TL6rC<9}|`ڂb6XLKevqReqZL۷^x{/?4>T֤+QSZX#m{~;zk4ZPP6jbjJqšjPRf6vFJ{on@ ߖG=}v'.ȥ|Ya7w8X>l;wvn˼Ղ|am\swU.*X(]o†}wW;g/Z&'$ںTGDIƺYCH(ך)J E( T| <8,#z.UR;۱wO@ /n6(33Y),QLH1,PsӊZL@N mif{lJq[l%\xݗiӦƍ+6RR{@szii}9Z 4ou6ܡ)ѠPR>n{i,g1azm\r>ëbO>W[d?F>IZ-f1bg߹~)2,v[?)u KD9?7@'Grnup'Kcnjl[:%('\ޭkd=z| z3CH˰:V$b+@3 JUJ@wVUOHAT*"QT*[DR*|pcw0d!? ~4e?'[w$>%G.]~={E"?u Eqa̩LFdnRhd44k8~!saS_0`ڨ]OLFd2&u;[kh<^.{7јi;掆aw5Lz.c2l}rad%JbČH$mW5YMVWQ`ڒ:]vEӀm3PQuufh 'W*75;;ʷ4N424yՀ{/kt쌔3gR^**J*֫Ws<(f$<|pΜ9+6ik@޸iS/ @Pߎ[WJ0mWn%&&^ #mǰƖ6,O\6q[n*T裏N1 aޫm' ;V|`[?nvЄoBqz-6sX~lbZ̰j/ߦÖ|ۂkH^>-oM-M~F2?L@f~u6jkV29ѫwϧ 5C+a;.a|{.ߒ G{FR"¶.MV0Z {E#,+;Pz,B0+++77WlF.+JKF#ުrE&O煀r91RJB׼iӦA[4+[PHv*\ p"E(@@@q(E>b"!-R;k/k5YA7oR#;5ly~m'gx Er@.0^5ܘmĸ^֭x{o94l[䀥_tl0e[ϿԬM>s֭k֬9u9s:u*77777͛4iW֭]]]mX1P$y/R >zD?xU8S'l_:V@_%u3o]Bf5<N?0_yY߬YݷY9is!4Ӧ~t9krĴCxţ U*B )uET8]uzBy{N!-_ z'$ 33d%2mεIǫNC**ʤZB!h4>|0))IT4TԻmbtHy$EN ("Pq,#k%)l ˄H  $B-R- "LDKER%@(CX2N@vv6OO> H$KMM7o^DD/eMVͫQFzJN=u -wkčE^o`XlAltcQ=tGNe} qi205>qPA-*a$\6؃_To9ߒkPTHIj]i//;zyy]reժUFX,ܹsÆ UƯT?ʿ"B0>՘?b0h)NΥ&jUo__x*ÍLE%K*!|(r%.hPTXN ezVɲ!qpܴhж_"IB\, ]u{@N}pDjb8u-~Brd@ WA9ao((KrWokuV/-8gļ}>M]ϥup+>Ѧ|\vӬUe}D:l=qDJJJk֬i,{޽3gVZUbV q cB^iXs~H`G KN[4MQ4@ h@ %/*R.RP JfzE"E 8c8p%)Dgcl2xݎG nرc 6III|rƍ[n],@+^jIB&t_f-z wYL2Ƭ{ߵ_PШQ<NNNJR&}MSO-2Di%?LZjY(h?[+QkE ʳڝ:c94DxoY!HԟcwmS\ЕYY!vVe{ |ѩGpHwB`bXDQ:k6*aQSR˫:C\t !nݺƏlN8v[p ON|[GtS2v6Y|6vj̗= ˆ[rxeYOٗzo4ҪU7n?~<77גDiGGGu뺸TZ#mV,%b?/$ +{͊ȍ|4 ݄EGBJTue"Z*(TWiĒQPʤ|,ǁa"ҢXp6ջlQBF:uQFIUhh(o2n[3pݛsEQ4M\XR رP0gw.u-)UB 2c5-J CׯI.|14EQ _Bh@zG>lcM1Қ7o^XM{Ca ?3Kk"VK+JϜ9 333+++88X,w٣G^z`Yd2%$${xxX˪_zY{/ןfػBLk|/n[}\ypͫ/}Tedy?g_ƻ x3y)yB_CGf^J__Ȑ_,&+W_Ly2CV%eտx({g2SgXY/tE+rG9(PTZB?r9G_s4XSPCGCzr楅/ɏ,֚!D޻wW.))sQBaڵݻP(Jp #heKk+L,I9nkO{oƕµ<%Mʹ3ev9vaD4M-:K{?CXX닛*|lWMhuwwXnqph233}|lHn^6 lٹ4Y ^UOe;o߹zjݺubegWqt̳+x%QN˂ bryWlw>Yk!$//ŋ7o NNNknҤÛ,{}RqEQG=RYZԮ]:YNFٝkŞV8g2 x}mԩXg yVg* o [PUnѣG?z`m@Gռ FYJ "))IR9;;Q&ɓ'III ꤇,첲SBx͚5k!Ow8 PE։1O-J˂-HVH[)slOe+W>|N:cǎussɓ]v[cǎ5*+&u2oU)dh,((DʀOnDE^eXkնG'Lm?4 t=)qW85ѵQ5 ߏ{G @X):qD'x{n;q#﷭/#&^F~C߹;3&W5Bzxуgoр>ͪib(fG:֟.RTV?;=EQZ,vY٩Dh ٸqcrrrnjuOqs&F$z֙Z|}=o^:thz{#A,^tԩSƎtꢫ!C:trJeɡ!V#MoSzugmlG|ߍO{]YYoqh4J$~*g^H!!@&I$(JK>9sk9 !10gϺoNoN#^Lkر.t]>{X±?] `uW4mS*ׅ/LN8t*@Wj4z[5!!Θ2/Fخ[:Qȸ|vNv:9y1oxϋ~Pa3spp7믒!֭/omՋ˪X;{;p]6ܳg… ߜ{eVEVժU#'9sM6܌F#~GaYaL֡C'OzxxT^Z鉘) 6ӧOvKC|#;B! ;KnפiZ.Y_~c9_x[Fn2w;:6s9QθԨ]CjWxuov諸ryɥ~.-\qN<4ͺȁǷo]$#vl0X:rPt05\n_I}`hQc̙{=s8wY+^ ChS-=܁-A)v- nDEg%>Ͻe3'JeFyEu[y枞-hYUU̽T*U~~D")..6L<>GL&aZT*QTeLw w6'- kڌ]@{ loLX4Kd$.8sv'FO;Od\x^M^u -ҷ uSbW[ bWO -(J(* tWXtΚgW0 SXXX%Mv/7VdhoLƳ~(˹w4{ӡ?kש\z<+4צ}2L 8?jGC'Y\ 0~-#k$Ν;׻w9rMz,˞={ӳYA-@P:=E_'Ov_N|(3}p+5~K9{׫FA%/1g?)EkwCٔ\b.]ۂHA4U:lrV+I[KUq|Ɨ&AeL*e˲q [NFt:B(31kf3A` f˚; eܡV401Ơ 4 >@RXXxر>}888+ѣ...dxax eB^U l1<2̏!JyVH=44/Q ="4R;awNc`B1-䕿"Џ8~~Td7F iZ#ui W& 3JU*NMX:"7<լnr'۸j͜~:P( \ףM5LFڵk͛7Wt}gmP p5!,Һ&j'ۇ(\oQ }X,P fO(;;.]`Y1{+VAHK+hGKhmUf5#yOX_}] )**6m9D&&&^t#":dBn$WѰ&:u|T,J{i L&h4FaP(Qj LfD33=_IJ, 0bhC%lzc5tq`qgmNjdϒpÄã}٧[l[w,uԥKͶnzŋCEGG[̲ݻw-[L4Aye܌D ԲؔX<>hYUv3d2;w~|>߲JQ@ vJ, 糶9տnxIEuu =;'z@9,>n5evb'g㞇v.m-'SѶm[GGY*,,3 s!__I&Y4uxxxL8񫯾:x`XX'yUjTWjٝZ!zU1X=̼xujpVwVcyyy(9 @,w!''gŊ|Wg[rg*h5xQf梲a.};`Zg4v9~3.L,QL]mfDi,>Od>^Z9BQ@䓎W>S*~~~of 2+ =hnjΝk4u֩S&d ]nVd$W pxTuZ: \ȹ_~B\ʮXŅy|e6}&WB{9svvݻw?˗?~\T*'NXb޼y{vvv~ҺX&sttk!%9::QO>=hРo ujZ( a O8?q`0Fd2iڊ?jWрTx|~_ܢ@OuoyPuͩ)ys$c+RppqّxqӜ _95?n+w}e7oޯ_͛_xqaB}6DCb S:{l^~^Ѫ g Vi4eF3gDUsmNXYaRhdFǻ/,ߵkW777Sik$Wрe(ʧ$~đmhI;ZD~z*V_5o:`y5Q F>6ٟ3fXכ{37Uc>lnnnvvvEEE$I\0o?^^9E}E -lS|_䮶;ꋯuòR1_`M 6_MtfkR~*dDF0>.e4f͚ediЩnϫ&uDmI)?ًaҏ#pnX/`ko`Ʀĥc{ xEQ~['E?NzgD]oG<>oØb;sJ`؞s[x3fwb7#iot|W;e!s6p T@ۿrd2BѶm[mrSkK% Z6Cº:pWROıI ÜTh{x>PPɳ:c0zgU;J^֙}k]VAKQEu@+is-]P' ;X:n3gn*5sLdb*=lrQ.SUQց](m|l-E^D}z+kc׋Ev(:{ 9EWTFCAڟ*#CQ*oc$ܞE&\v{%\I#]YG[Z %xR(3fe˷R۸:Rݻg]鰁}sFCHD+v\ȅXݺչa$/6<^Gk42|e0GW)/J/0u1`wJ)=ĭ!?d!Cq:nXcz}Z'pJH|ɼcPNw^iE{_w~ {\}IWRymy^DCZIp}  {{+:SƫlD 셰YoO|rqQ˂t\@6u\WWJ6Lo+F aIþ EIK)wa,;J+T ,+PBa0uњ,T*=}o޼o=vRl,hݦ!#%wƬ=2¦:=yW\':kWj{뇧w {{"[sud2› .|'2ȿS=Ԛj?s]BG3FO~bB,]oŇߎ|wۛcaP{RLNf>o^Ox'miۺMdbMq/Yov&fkJ{s%&[w}pW"R͌#Zz-[Z$)7xD/)t'(PE nZXe E2sUpne ߋ\q˽{T}>[ER%i9H,ݖ{ m7Fd5#Q@FGxEHݫ3uݺuj@JBѪwn~+?XIT\ g>Ya.^xĉٳf͞=ݝDEE͛7ﯿxbc /}o{{/Z1juij%``? _h9yXOwc>@J[uV$!,O.[v%]ϥG()biv!o es/Zv~5՘kG`( ]jE٧7pTzKcqU<2;ոQoWvd*qO5nYy0%݄_(2̽G{߬ (㐩~{7wt_q7x_ɰ9dmI-MPEk XvitN{';L;2@$P%@ʡnNn͚[ousL>mfβ,wԩ`W77^OQgK7H$))) r %$!A +cDCYoH~څ{p??}v_tܫ%s˖/7=+ͬ\uPxm,! 67y;۲WQ_"WGZuZ~ή @S`ٌWKK%3. lVzE vvV۷Fݕ+W ^|||<==FN3wB,k0X^zʰ'p_ RRlBy_ޙ ]xBߥO.@֌C*+\6mE >S׳^n-GH,t?3` &"V3VvE@{=_S܌ֻ0|@Z~[PaL ˲<2QTm,ɩ,IpPe-{yEuZ},RB~9EXdTvXcióݻz˲*cǎd2<C|V'J===_R*~{ _ҥ_t(#hL`Ye<=F2Dm{/235OIJ,`dUܾ$eeˆ=2гmao}Q.0aQ+Xx-t޶AfYVj/Dr; W"&8ޫP e`F-y~'e> <.UnM$〃oN x j$-Ct֭;'e IDAT[Ҹ~mǣL&+I~{u.*oģnJmT|5PbSqאĪYlέV޹s]]]]]]M&FqrrrvvD&I:^u:^x4Mr'''FSa`OK0jĘ1>F&3&xcs%RX?Oϐga}T]TtTvoĜuvѢDB//~nz ?U9x<W{\,pu"#疞+ P3 h(ߐҝ\A SܗFGVNds(M]\臱'^oΟ$ Yi}ɮղ[skt!6˝4WLf!NwNmwByGww^{mCmٲ%UM&ZLѹ{&ºBVduVrj4w̄a]>;zkX+A6yEμDn/sj͉ ^EڴVZV[^UQ/klIJlqq`x_eJWե^ v? ʄcCmw3Q~Ri|j?PG'+>o<|ޫE/O\5[2| 0r/h$OkM[3tAv>$Ξn2r>*?77WT* "}]eTXXQعY͕mMY&66bClVzL7Dff^wqqdjh42 # bQjQT7 _J Q,Z2"(ooiӦݿs<U&v4-HrP(4wps % ٌшׯDUU쥕d~~~;vXp!7|EQd2,m۶Lf >c\.4g>|L(;;r1K,o㙙AAA-Z00 sǏ,TaGC DzR޽{(@ɉ^pW[bEfڷo_3_.U"\ trI1A4uU:4glwvv ߕJ9xx`ZZ޽˼)z֭[\_[]YqD0?vvv2\8?@ӴX,6~^YZqjƖ|>ão߾֭㶦 ...Ne3ɺjjٲh4mvGײerH$I^U?HU#Ŧ1z Yfܹhήcǎ>Td2L&H$"AADêYx2L*:;;s>x<'  lAU;s/mEEqb`  Y{ 77\.G:T*糳U*@ oժUΝz|Pc$FRlfru;K \t)%%m۶sqssPPPӧcǎOe*&'3zQ<ߊTKAQ)+ջ\} ä9sfYm۵3DEE~,U;gNj2M_yԙϺP~~_O% hjqv,奦qUskpJ$Д777www^Zޟ!֖z^nf;M'}AAMwf,feet:\Ν;i4u:p.,kOKKsrrP{N  vn-i2H 7#Q4FU-SEEE-[H$:Nq t:RgQQQQQQ٘M)}Zdxh]l}. Kw%l"-N}<{40 ߜ(SsugK×^@=J\{Qل?% )Rer\n24\.wvvD&I鴏wz4h,z:dg:YP==V=V:{̏nKG>x"UEۛwSNL`]8l[ ޕ7뗝]qǒa먷<,$ %ޙd&IV+ D" 9L&0hvvvV&t%w޲ze;MڟՅ # Yge۷>YK,˲,h \ | 0<eYѨjwʣH/O4tD :?Ĩ24r&`M,,mjg0``Y]&- Ě2"+jT&9:M?@:f$j;''''''OOOOOO<O"\vM|P(D"X,i{̵6+YkJ\^2MyC\sʅ ?wP0rO.|'~AA4VUMCn٢"0P(bX$ѥB!HRdջ?w}LnR?k{shi['W@zs&棽J6:N6Nxe'NjѡH`sj3  w^ZRcǎ&[Ruײ,cǎRi1nOD սGp3z@3w/Ͻ3t@HKIO)lr Gr85)tE<59Ȃ7^Ƥo  = ܣGQQqwH  &JKK+u޾}2d2=xȑ#-\e[n-_=44ť|NSQ9I-`tO4(|QA;HK(F-ҖWP*->6߿… 5"YG6܌D bӨU:sV;::Cx<\.իWJJ{שStϟ?׫W/\ne-Vp|Z.#@g%8X $ xfYݾ}Ꭓ#$$ҥKk׮5 BBׯ@`HAAU1ݮb/@ pwwwwwAAO3g ')AADYY֭[\/-Y$jdAr35@McdsynݺK  h\wfee)zN AAQ{Vw>Y  ѱ>7oެD$2H 7#Q4F9Kzi  w7o49KAA4 Vfζh}d,AADbe˲W^t`n^N+k¾]dFChZe&Syƒ:aŦR:7tJ;S=uohrQXޕٿr3<3|eTwHHy3{639~NEvk];u7hD"Aqq1=l9`>-BM2֕U~cd?=i%M*a͎%K2i6Hb!yO \6y#"J+cZnuH$ut [6< ]sy|{KA{h-c^.|-ykܶyɼ>7. @TUݺ |tqpţY3B!GN\\\\NP4kd wSPe~-qTAQMrgUӓ&#K޲Y3BP '>.>GP(f {"xհ<='b7'}r=SC5=+Wޟϋ۩'@s?<\3 ?ѯc?~!}bisܞ\k->uᏋ{YMC2bIB!˲,6."ZeYmLH9DmvL]Z('X5K#N,˲ƳwwRҚ+wMXqRBħ@#$X}ZX[Fi/gK>fi)۹뽿1)4{i SKRRI;YGTSC1cO>wA8KoL]PT}iMD$.~%l{JiG/)IK2QWlf.sVAvH,/.dv`Ԡ)%>&be1GVwv\:bSQL_KKLpХl *DX_N,b/////:O°)5奣(#Φ[$ vܷiAUS."31{d"9\.奤~-O!50z=[Y}U .fӬ53~Jmi̮}v-O]3U6mYҦU۲$2:n~[qZəq'Ɋn؅ @wЉ>1Blxmj2vЏ@GM @憓to1jֈԸ;*2.sʸ~-\kr>KHrÜd!qq6j~Z7E6Ubi2g9yi˯'. ? el35 %0E5bvG0i?NMnp[^V^B RLtCw5}"שB- @xtcwK[ 8ۮ^G<yc[?lPN=GLZ J=@HWd]#~g껌~ܐ0+^ `{RC#RL6ʼn@&C8N[GOi 23;jJleϢGR%?%IU֝pLN]|$s6qZ( W  :5ѩ6!X^6lf+rdn{y|IkЄ)㶤fYC1%1xa^jF*LY[}ڳJl*Fbyr啼bE&Mɍ2<s7}>;ZM_pMcެX\ɤZ? [83.CvThfjsw$-ss=7( 7 ׮#Bj73}5. o`Ï4P.@y9aƪqI9ܻtxtՃK>QoCǨ sV}q@*F^.LFr OeLstxGDO>۵$uofnx{#1z/-{¦ft/O6C=ӋI6wuJlDR!=ͭsBGp ֓WOQp-a53zsҦ]U+_pMC:p723=ay-eYK5kV+o\IeYֽ[72SY^ALwy<{M??bSl*+!}5{4N `DG|lzҷڷoc'?,| 0@DHO}utaD:oCGD-ٚT~@$$>\L#`VRgf_o,ڷ^UWw{2͵41upSWO^ /נ}[C}5u&Q2Ͳ[k !"jUӻA.awyl ;Sp}NZ0a@_ۚE<%6Tb3Ϊ/W5u׫\ԅQ#9?wBb|eZC{9?ɪVEC%Dd N8iN3|&i4'Oa@|^n[Ѩ4|=Qi Z=Q4|TJW;ctKݾy/ =Tz~ 1*I' j2V5fi4z mĖ;Z0:hJS"}*\`tvnðbS)%1|JX{@CZ[i;y /PRk[&R{|IDATf_ŋd q9hP"@['DE:l=Fśo wSa[7 rgU+~XNbPmbS)%Ւ*24 g\nm}vjWGe>me/zo:xRl/   ܹi   s)IENDB`Data-Time_Series_Chart_View.html000077500000000000000000000050371255417355300367620ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Chart_View/Data-Time_Series_Chart_View Data/Time Series Chart View Data/Time Series Chart View
When Data Series or Time Series is selected as the Chart Type, the Toolbar appears like this:

  • The only difference between a Data Series or Time Series file is that in a Time Series, the Map Index is a unit of time (each map in the series shows data at a particular timepoint).
  • Select a Data/Time Series chartable file in the Overlay Toolbox Charting tab.
  • Charts are viewed for a particular brainordinate, therefore you must click on a grayordinate in a volume or surface Viewing Tab, then click back to the Chart tab to view the graph for that location. The color of the graph should match the color of the brainordinate ID sphere, as shown in the image:


  • Chart Axes:  Allows adjustment of the minimum and maximum values for each axis. The Auto checkboxes indicate that the range for the axis is automatically set to the min and max of the data being displayed.
  • Chart Attributes: To adjust attributes of the chart display, such as the width of the graph line.
  • Tab: These across Tab functions do not affect Chart View.
workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Chart_View/Matrix_Chart_View/000077500000000000000000000000001255417355300271365ustar00rootroot00000000000000ChartView_Matrix_Toolbar.png000066400000000000000000001022171255417355300344720ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Chart_View/Matrix_Chart_ViewPNG  IHDRNHL?7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:(sΝ.uP9>#<IOOOII=z+~/ܹsXX#6 6j$۵KhF_PMr( Qjiє=.ӗ>x򚔖2 S=a^2}ǝ^ i }8!򵂉sFցVxMNڗ>Y'=xvoqF^7 ?1c6jdNwqq\7𺻤.iS&&_~ZwB(6ܵ}V !DCB4M $|Z^^"Jӝ}t*7mw6wc,6'Ep㻋xp@tVtwؾlR[ ""=ke{}sWݯl<_a'~@-ofo?*PxqmN^% r{y˲YQLa. HEFߠ=!3vOcə?3o.dn0") loǃ6T*&SVl/fC|S=-{GS=:_V-]cA6 DR!\7oy:mlO=>;-)fl׹]1 @jzj#-Z]PPcggjzޠ7 AFN/J===6 y$k7id%mtzz3wh9;d3Gw|Nc-Od˩Y%ˇrےfLiٞg48ݴĊz>_uwm%ەYi#w ;Q&sm˫q_,P1=S_2ٲ.Mole@^ãu3SGdž wQ%^.}m9VDBP(L>>>>>>,j4^pF((ǭznSн'S$mQmkC5ykIxŔ—eC71SPߜY$@Cíwe$-Nճ3ѕm2V ˲j_N(: |O s8J ;Q=c=GZ]P=%?LoN=[ѓjX \ ˀk/-{pzMZE7_Cu nnnvvvBP BP|>w,X,vvvUЪsMdMFOبiK%@޺ФefTAx3k'HݐӬL̃kqquoVs%Ҟtf&GP'>ؑ|Tu_YV?0 !,˴zEPpmԟЪ GQwK,+4@ PXn&(CT΄W|Bi R~EyQAҊz5B? 9w)ZhxT4dBek\Sܸq`0p8H$$X,掹Mׯ_lnduÂ=mwQ(&0R}@*Hܛ%|+r5y/8'BS/q#.c^Y瞫q OI'̊  Y3|r@g%=7ۗ DB޾C%ԩ7tٽ{}$ BC (>>>rT%r`(A*vvډwv!`r^)gb\nٿ;.8IPQg'Jh@WVPZR=@TL ]ߨ\&{u<O.t:a <#0 CQNcYJSp\k'1O$"j 2RUIcSL >w7 zs[S>>-2N0kV9Nn#w58ȥuI:"lW<]{ vm:lY6jlyUDK">=t}JMCP,r Z}hj8tsg}V1"$zPB'V$=b2{yy=DzyY @.O}+P @aPzNpRQƗ pp\^Q|rϬ၁͢:]vݥ@ rQQ=|rkwy8%vn_# ¶oβ,Dg>EQ$lٲ%,,zS쑽7>$ooJ,7Mc;Yqɳz7z󌦆<="ff3/S(,ܿ!|/sX2Wxeywņ3d*%%%,$''_1r9m+'W.;inp#︸8WWWkZ;SӀ@pQtԛw 4|~}ytKw:J/]kب%/.Cjt:.%/iii>>>:u˲ D{...c'V*0ǯZsa-CmܱF7|O=^@FuUpZ[Ӭ` 7kCF^U_;Sܣ.;:@ܘ5 -c{|Uaf,/uMfʹg<{tόPE!J$ +؉?~X ?dD]'.LNFRKtIX\pP&ם?J !l1WiC=E__=ey!-{/Օ۪ycZ K6|N|p+\1cXbXa"O]0Çի0nѭӞ/x-WfB쭼o=1r/HղXLz*0 sܹG>8vxxә<+z ݷ@UBAd h >tV{mѫy ـ4k{}>+Q^-<w"kbt4;v@?)7 L9ÀX (X:}v_DW. `QyWt΢c1Ò4{V݉+8rCRrjK^jXgrw΍ %_jynTpA EB^Qwx4x>Nɥ' Q_JaKܭ}ã=՛h/0qsHt?'qokI-{qG,vG1^~!vh QgKF\Tfaw]}|||:|8dxmI\l`.>>Sϩ a=Ǻ}|,9iVOjN}86;^PXz֭[&&&ɉ,^3(Ðj b6,*)NT!鬬ƍ MӴF#Xhh/*)ձ*-[c:Fc޸1*-–R)ձVC㵎?Fcgggs> zӧ-Cos Fj0̡Ϟ=뫔#2TL3OHkM~oǿ X13s~&Z"zU㺤C'OZ/uN;bNe[X> K9~;X@ Z}[&~{cz~ gh#Q [\ٓ;7sZI*^>xROۉ e22~uGN8Bm*͏+~C>NJ+)|{Kau\O~uZFsAd[ uk|Pt9aCYmm]D"@Q($EQu8\:99q6hDOHiRn$ZIܾsw7n*+i8)rKa/uLZ]XXXCoCʼ_*23˛8m sg6{ts&utOlkc9ܵta\/7-we'~ͱM*NH9Q'puM>'W'Y#zψ2~7dGNOpup:WHڼoX[tXtDX { kazl׾=S/(au }-0&6^mNϭSJdg]9 ]ui켔wnOvͪ^:ф.~~o ,(~ydfa Vl٥YDVϮρ1.4q]( Ŏ&2ҁKf Nqxţ(ūCQj !yfB MӴҡCc}PRi>Q4e$9UFVϰziO޾qB.E=M43D #Dvvu//VDg drc~T&oesv6ؚride M JܚsF\~>=a×&%mau&}ִ~[?z˥ ooN+[4h޾Gs@i}nm*E{wwB!FZbмS_ YI0V>׷a``Om, QA5[ h՟ x!MPTne/X;KV-[ŎʆAK4%}ֲ{P%ؖU\OETsWOFee2ȇ;GImX/gyh^zclF9r0Z5ɟ4ߚG}s֕x-trbPuVGa=ĉN 0,C3ik=^ykᮢȗ@9 IDAT?]ܩ/R4fQ%Iפ|ޮw&YSGg%F5Ь&gO=.GS~J;rb`&FYb0V겳F'jzxUe| Q n_o&srePn~8M)A ,׮D\}|aL&!F335JT}i07{ݔD T@P˯\/{ &uU9iq.^N ]?<ⳇa^Coj7@s= č9]7L[|U]`ehPVi*HK7.aёHKSyݳD˗ooa;H4[~u`I>\ ,AϷЭn3|x\8 6 CǣXn7pkiK]>0}ZB|hF\N3&kcrG?մ0}ߜ#$}~r^ ީݎD$>9]fO?bFQY^DN/>olAAJڤ(,ぢM{N\ (3%4a,YRc ж283*Bo&vNNۆSTf@k5@bg~ iu%ѳUwӔs)nJ5y 4xB)3Yū3 j:~2!e}II HvJ1ѫM6<͈DZ2py%>28xw)]oy}4{Z,:#b}')ti,88e..sw9\ʤƶ o^U== hE4 lG[e :LzRZ݈."+rLK/;H9F%TQ3yeRF)s2TUu}C(enR&]F站H<=odd2N)zaGf|޸m _LF=h23!Yׇs>mdFbs*tߐ7i՝+7EIQB)[xz& l0`wc6u3*䚒M}uJ-+|陼Ddʩ)WG\fYwMϕ=@1"7w9ЕCWɞ+oOyB  EA>wFwA!_Z/RK嶪|Wh.!QZ(;<դ-ƧsKoڞ;\="6.{VLm'7evƩAOw/ 99ݻw?t0,˚C Eo.;(FuXTOƧ@ʁ]'tEQg7-vtg=moI}9' <)ԇ-\@a)W%_7 _EZxxgtiiFҹvF63IfDXԯGG.}.w7zX~S2 b'e9WnO/D4Ď㹸nݚ#}5)AhX W_BneA^E \ܬe2œv 6ZtTkge4TkՌF`0o:utڵnݺq#<:ȿRԙ @QC"PsOwS!PW;LCdOrPf9g$ JHQ<(xESR;Gpp垦yg]Pxf'I /})tL$B2Q!木Ia,Ka@lsR +f)... xxx &$$T]PPpB//ϭJ]C^u.ϩHy\|c76-޿hkEN/WdA8LuetLh}Ԑ(xb0t)WՐ9>VhD.ktn{zzv3##b [׫Wۖa[#!jc'w{u w---ussDH$8qy`DzJ{n ,S`x<.Y0#ͩP(|>GQBq (JM|@ wwg@bD$b1-߽ (_stCYq tŽo|g'F(kf;CBD(L~RL[YxʡB,a,X)fR ANo9ܺu#G$$$_ϟ7Me7T[9C4>RmQ272~L/+1fcW/d=Q.#Hd2YffBprr fHBh,..̔d g Y:oď//+_3Ds < (*X8L|ͥbPva͍I4Έ6]4g9.XHFg7?h< 'L3'*1~1d?4Nȗ<'h~gr9OI, YBX`VƊ+,,y^^^;vXvp*Meۊ7wOg*(g]"ձ[////--jRE$qu{/6$RE&YzTD"Tz@ZR$bK%r56&[I栕i vNym*AJɥ|<x(d)}D*!!` ,h/TZ/I!Õ\5i2QBss8!DV߼yAR>OUxygYV ߼yS&rƑ: :`ݒ:XSk$e?gʘGZVǕa" an']@ *on3(… "jW~SWg. Q N9:H @XTD˂a">7_XikqBΞ={esrW_}ehYan߾,˹x%,K\^^;w[pZawY yڽXW9eGŷiȃڽ4 ^f26l<+`|S]%ݮ2np\(X=* 2MY90`YnPܘM<XozKa222>ܰañcZF!߸qcBBB$ *7Z,s`0hZPy, # U*`± runw=xáiXy< 5JJbF!ǖou'ۇ~I jnx+ýұc _F( nTz.7T@{r WL)Pgk% ׳勷dㅂ|7oR|qWgJ`<v1ԁ2N Vݻ-b&==7vlMQ WOڇ.>0*~GEghS!ơg4 n*vÄx<;;;Xl0$Iɮ պ-MɓAmEG.9m[߿:y67CP<ǠN^8zgz-n 8s[Nף8s7S>vnڱ7{׃%;v w.+EkzB̕1M<͆³~=Ӊ}"qk%!m 7-dl 6*G۷d[$xnnn7o~5z^>^pwwP^=C'Qȹ:+M\Ǐׯ_߲s@Ҵe=DUk]94mWʑ5md[3{p0B.'ϗJJ~-]R- i^-uAS%ߺx{I4d]T`oj hKSssPNθu!2>qE׮Ýwћ+sE@x7rsGYeܻSPQn:\"&F2t_pAV[Ν;N<>{JzI7}fGGGWW׺{oii2 ٳg-m18(sҜZپ8v%i.oӢ<#ONoiv`^<0h`qai@bn 4|ygH{OgHԀ&aGO"@I/I˪߱Fu) R"{7 (LJMJ>[&W [?OaY^^q[Zs{ꫯ^F+ܾU///s|1UxٌkσDY HTrQYy< VgB~J;\DŽu}ԭ ̎_t'ϴ/ksKjT35#Ko6*h)@ß{_yFirP&;F( * Le 0*񷓳#LFQ[&هyS]#rxE `%'[6ʥ]]+,ϑ?3N&jGÆ _D_*;f&999((޳qǰaþu>MoML6aeRoaFP(,{y΋y(NNNR0p˃{6膄N3/e٥m]6[˕)]U8'-UYS[|!o.]]yZie[00# _bP(4}Z˗'MD4h4nذa#˰lWZj^Aצm۶%,Oy&beYFS(esx˹Zڽ+9R" ծBDZ/ǨL_U 8 Wʅd-c*K8 ԏ;H?DGGWM!gϞYU%xZ*J<;  :xiǣR< csq?$!h ;K~M={t wW%BW~I#WΦfpH<:a [YVj&/iii>>>:u˲,tu*k5-[?&}UM|ltzK3V[(z-V2vaH1Xi+2o]e*, [*Dc9iM-xMJnʗS^^~{{{L="y7RJD;ヒncz7|ԣ;F/_//X*'|Qo5n/72|D,U#WP4hݦ:>_("5w`^g_"vg '`_Յ3CXZ/X*[>2EU/=rJyɯWXZQfNii}yky\UM#~ϙ^4*bN.wPsXraq##r\?sMVMyS΋?so_@D4M #v34sαc C&uuݪ70Qtssd WH IDAT嵉BVe2Yi;QVS$QBD@crм?5_f`fK5#Yӗ +m$w5חXg?̌?pupһiU<4kFQcM=ʙ_lBgy?úBXH  .j26$yI)Q{#)@XR٨Wrbg<Pʫ%,>rQحk(^=I0|OK?j5\;{q۷4|Р6@ہs/Ygwp+Ř1c,0,0 U!_&㿬zXe<ɔd!!![nMHHx@oE5h]:^1'eÉԯR n#f&Sc_V+gdt+__Oˆf#pwG){:5}UρctC_auV讁﬿Nmnha`,jLulZswO'w+WUq .2M|;{n:2.VΣZ}ʫ]ي SflTpajRYeIzNfVKMQ6!/s Pa @"^~} ~~~Uކw.\300>|i.]Hrh2s/3.0 8SzUL3V^3CbLm} ]=I6bg. ~[^3˪;7kVE4ǿ]4h.S7}ApNU#.6>F:yJ8?L뙐 ĎjLXdVGWʧ4ZjNJ?l^mb4<7ov[,RaC‚6bcSv-.Ω̳gkkjG_K/]먾qdP=amFTKe LCEGrQȹX233SRRΟ?Q%2ك|Lom<]@Y.秤䧬o_ھVUb~?^$5w$xnJEGvxsaӦ~NiB0y mf|OM;1D.{zF̌%&7I U{Gk'_O@kݟr NHDQLܔt+U c۶zlegnU.T*srBaB_)W)MG@Q<L J';~%3&VVݝ`M‡I} F5RXz|\Le`oaO@TCLn4~~j 7QtOܐvF@MK(H R^j+7O\00}tcL-kLߋ |o8?MI @ (UjJEzYUYO~bVmWXKVyVmj⁂BDIH ?BHS ,<<ϩ[1ekU֍~|?pd5W1>p(yÀׂ|%sh}<~y.6T'}{pRe 2CLfh{>RɎv/xBr·w25.k*EٺRz}5 #dd4P(^zΝ|^y$ 9 774w+6P6l!(=:I]jʪ @PE4K2A̯Ō%R_ו'lמ=a{Ž:d5$,r_ظe6~;Ky{ׁUSg{л]΅HG ^_87dރLEIM\R[s*N}mQ ۽kjU;iҤĒ.;uTcvUa$}{VО033cVN\'P@)%|uUq \KkK3>ϡR>pR|>4!_K[dg#(~IO8:[9a4Z\\xKG&%%={K.K,яBZ7G%IWm\zdAU:n~)yW\Y=iR v²N/*,-(S2gߥ_TmjJT&A9cCʔBH:e|eSxz`.NXG[wiQeI򕔙E Uʧv2-6Q1(|򠒫| [L<@,,喖j7cY>a%p{!eRJnjo޼yҥj4Vl3:ho.ZVN_1sXW:&Pv8uKǪߛXpopaޥ=1f OU MdKEEj/C<=>8@^X 䥶M2Up4FD~ȑcƌ)WDqU*iVϯzߩߺ!Xk>۽->Ґ~xz;)\-EQ=閏 X1gti,|,NZ?jԀ`X]b QeYȂ7-C"l6 }Ĭ%4MW<..^?T3/A_=xJ m)nr'#זt6y4sKe]mY>|(D ,[L7?|C&i!F3WIۮOt^,J Q}eA̝RC$bt0ww}s<}Ѳ]҂'ʎ>>޷H5 uLFsUgg꣐]~7dH?Hmo euOסnF3A(D$kkkDhŘyo$Lhb࿰6PbPR]uĄ1"Zչ{~FA)J^^KQ޽P(5)t֭v_~9;PRHf*njk5)%BTzyLٱcGݤj.\@'sF<54+t_nHKeUDy%*Ʀ6QmllT*U(YN꒾vO_8 L%] Lx<e2Yll\.׭9xzᘙedd& yzzg܄dD?u_ %a~f. A7?gZ9Mhhh5A^zSL&h4vvvJ6Q ?̔أ1lS^)d71V@x9EN_iaaXM QKB_ѓsF,qOe͖ӧO 0t(@ Ԋ,0I&33nݺeffF!߰aCuf[s /bvߺU&t8&7f2ޒB]-u)(ϊ+F֭[NJLLwršVKJJPvxXb`gP8q| ݮ@ vx<;88$%%m߾]~ذa=zc5 4򦚆Vu u?jSԥ1U70ן=cu#41:[[[۷ÊD!wqqy‚B$&L 8TWoM~@&VD!DqBv^(tނG~5{R)#;%N'4|V@hkt)6JR鱾P P'XtuH$T~ ׯ_OOOW(EYYY9::իuG!'z*N7OWM/ePA٪rTcjhwGhbd9}Zmrrr\\\.]:thРA...EPeoFWLk4D0?\g bͼ9 }D`5N@ 1Hvv6R&%%]|yK,i׮B| .$%%Uz| Kw6~#~Wdթ(HON9Jί9lGUS_o\VjSTdx]JBm`K$D3Q>YL!~14թ.9@ҎQ`%}Y>`%˶no p9u1p~9{L+u&'%rmם.j 1ޛx{]np=@&WmpoK޾Ej^;meW>lOsz_cu#41lllKd.kǯgs_x1 Y)23W?Nz.1L!4YYY+p8r_4@ $󁩓dD...}Y5Q׷o_H_IR?mxe3, ^' XHR5m){w̷x:{Ȇ߮~&'|W8XUo\d4 ]n5=d0>Ucf"#'11 I'O,--}gG!۵k7d;;;CZR¶HqVR!WkB+yb!Z\ XTO%PB P r"x䉷77nԯVugΜ3BjԾ,#> Dž9xL2'--MX[[R\D"y>裞={9@rrS_kI$EP$g0)rppvppHNN޶m~C:;;R, 6)U%Boz>s<&ا%f=g,ɤ(]vڵk ԰3dG ,s|}FI̽@:9 ]GdU]ܿ BKJrB Bc2@ uuVVV(dfee5u%DsPoH6fDI MuS]VV&@ - ̎;\nI,0 a4}Zh DsPoH6EI@hM;csL@ Z(5X`޹sGP4MS@h y=FI #2l SNݻҲ[.g IDATD B.sNAAA7@ ƀe- @h[`2rK}I&@ -RhNOOWM٦ Quz@ ndP&YþKOOW*M@ ]]fffQQ27 :B= ÆЀ[`2rKGGGM"@ |`I&@ Z:Mu:I&@ -]wD Bsuz@ SR5qS@h Xw%@ Z R_I B EEEM٦IjY ,hpu6_ҥ%=[W[JSf09 'Be~9ȆOn⤒Iϗȕ4M&*iν"Q2ec4VSu-X k8 *RR@~wdox|6M4]rV2c lș&G' &JԅEe?=_~Z7<17[O&`'F/kŮ;Ca1Ճ(ɾq+=[֘J +ѿDQ @qr|OOOOqeJ?wcY{g>,2˞ؗI\%kίdPzoAbuROOOJ333333ܹsΝa~sj3 @[p[V&|{oʈSwi_538<**,X釀6nN ԫmC{+}H:"|`Vx Q@xL2kQ;=Dw@O(fF>%ݗK\=!;{#'D-p,4*5Wn.?R 1ghNb9Ub?گOQ{]kApbuBS =z~݈~=N 7@c 6jX= is;CϱIaRBJ*]L/k~ө}zo+v_Úi[c1jdzDWjm{Y>Bz&:j#s :~t^_͢#`Bg%H%D{c[nbq~ YgSv0FiP yUVf~j 1˿Y)Jqw ZM!hЖǸ#ؙW^U۲—KqOA-NH-[n ;w~u&YH2DJ8BŅNM8QvV e͛iS薺NLO O̬ KeLtQ Œ$'"%ة=*Ee]]M?ϒa\DfA*;nO\ [Q 7*Ί^;!5sa4b~RM_}˫Q@(^u3ŃP7hAoJ@ 1hg z loPT׭3L꼊8$jT͗ˈ6y}n&6,NLYJ pX,LHgis~Χ({E4M),zadҹ>t=Uׂ6o|'WTs%ܻ%:YNW(Mt)JbV+rh eAfvb3(UX2%;HM^VeBig\eѽ+b8w+D}/W$_I̭aJaiQ}yS,ƐÇ;?=ܨO-D¦oO'f *^RKa 丹pWYej,tr27;ʕZ}^֩ !ԁf2l (ܤFHUԠkL$gO0qp',_/94+M6S-HƸҹvWޚ׻{ӷ6ys;ra'\7ڈm\Gc#'or;ޜe$zIfU_Ƭ҅_ SFmSVx?rndDg#[-9h9?Q .mL/l8~$r֠TO4gބ=[<5mgN= 7ǔu 9lc?zC <1h^ -zٜR5ГU9Bp.^h6TZhy"+6T*ooo.]gZU^J VhU Od%AR **@$ֺcԲ5^r|; ;`| BZB D 躆<RDVC(APh^ B*0yRjJe+kݰUkyBT "1j\t/I~#OdeexĖͪlȎ9CRR,GBb:_˜y<RWJXj6zxL6U:BxCuC` sL``޺QZ>N}h%hk]GhjȰ!48 . @ 4"ÇM@ s[6*PIENDB`workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Chart_View/Matrix_Chart_View/Matrix_Chart_View.html000077500000000000000000000105011255417355300334030ustar00rootroot00000000000000 Matrix Chart View Matrix Chart View
When Matrix is selected as the Chart Type, the Toolbar appears like this:

  • Select a Matrix chartable file in the Overlay Toolbox Charting tab.
  • Matrix charts do not dynamically change, instead they show data that is dynamically accessible for viewing on the surface/volume in other viewing tabs.
  • When viewing a CIFTI Parcel to parcel connectivity (.pconn.nii) file matrix, left click on a row to select a parcel (will be outlined with a horizontal box in the matrix). Parcel connectivity for the selected parcel will be displayed on Viewing Tabs that are showing the parcel connectivity file as a layer.
  • A parcel can also be selected by clicking on a surface/volume in another Viewing Tab and the corresponding parcel row will be outlined in the matrix.


  • When viewing a CIFTI Parcel Scalar (.pscalar.nii) file matrix, the y-axis rows are parcels and the x-axis columns are Map indices. When the map index is set in Overlay Toolbox: Layers for any non-Chart Viewing Tab, a vertical box appears in the matrix around the column for the selected map index.


  • Chart Attributes: To adjust attributes of the matrix chart display, such as the cell width and cell height. The default setting fills the Viewing Area with the matrix cells. The Reset button resets the cell size to the default.
  • Tab: These across Tab functions do not affect Chart View.

pconn_matrix_montage.png000066400000000000000000010521261255417355300340070ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Chart_View/Matrix_Chart_ViewPNG  IHDR_7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:Oեp1z6Mҩ)fnCĨ>8Rop͙j_GGbNt륉yi\8)iY'Yۭx]s9x("N7bg7oYof5F3t;vjg?}'$ݳ NG7x?M6U9‡`kӮnW`s38@1oizW}n Sa`5=t@F1 ]?T=_#fxL䎜ȥrN;k<؋mW(擰QptO>OTϗ9Qw ~6z83|x.QpnzCyXo1WhǂLss%|p.z??\ygDnX)7B}#뾳u;E5/{sߡC7XubhQ1 `0 `0 aB;`0 `1`0 `0 `0 dTTݔ 8NUU`BofMZWt l%ãz@<\/[gL?GWgb5 + *q{=uݰlO48E|])`ܮ{Zq|EoEpx;{ Vt 9TzU)G6Y&is+_TQ0o=Y>z=t}{9_.YOk B 鞩nBS 9|!mqKs([Ly#ܦ,J@r_-X[ćS9]u|{7U* 7r+ӊy.ʃu~uWHE\C/ _DNR7!q](A Gbͣ:4z.OS<Uov4ʷJ7j !g5kŗWcUېE^'6{<|U?`0 yLA`0 `0l`0 `0xnb0 `0{뀌$=>"+kg0 `0[g7[#`ð^2?|x 96h"0}b *Q߁պk]5+ mlṪ7?~vb^} }~*}:gSw:o U>~ hH9R\LA̸ ު73{. utݔ Up +OL%!eԯ$;إk pܮlosiS旪6= ٿ6⽀Ƿ}:YY|,'> wZiMO3fhz-rn}gcTn g7xM =-yl7x(zmya,WA^]]kc'KJ>ۿ?koHݏIgS3uS< 7]FW|7rSb?7"rQ1DNs}@) \eeGL2'tWP(LK̭*d `28l7%-pPr#/O}Bo=6ش[H(I1р7=Z௏^ -@ZE@%*Ƕ~챇{Mޛ, zE `w)C ;x`z|hvh Mtjv`0^쳣77EEFġ]9d\T9C2e^_?Iv*P806XdJ b4,s8ScdT `09riHV ̗T$%h ?P맞f  t2'ECM—HJ*)K}9ezxYL^A -q$/!" %W>|d{o ɺd\nE(I&`%%wn yCAN5oj7>hT^Q<뮞GtBTh՗6SGF+3_u*>}Okʡu]B w-z9#&_1w__%Xpb~_=CtύY2z9Gsy3T}2x+稞?s>5Tx')C󈡭^`]k(-Vͤ=4~o(&_s]U*?gtVbnkyu:S]pԋpڜUj$M?t#|Xݾ#y?fgls tϧ>Lgżvyjo[8WW3^]hʵPz|Q0gz;{]$ iA"gشX9 %D%c<]mm\+R .viuAGwvH*A!A|Ti^[m/.LlV|(dll €oЇ~ 0KWrZ` @"·rׅ&,)@2p)ܒ1`0^)w7;M޵ʲ WPLHZ-K $Ѹ;[RE=NJ%_(05؂@/I KS\|&;ӻgOM)h 9H Ґ"u"6f J0  >; q%gz!H%„d&E(L/i7, LLhg0 ̞;xtٴY8MU䐍, efS,w2IP M* - .3eIIG&*a,%%E2n;jH|IIfLy)fT%,FL %B) %8yYc[4y -vzaPC9xduʐ =,,CA2* KNq\2Z EIJhA6ᔴKh U"Y' )A2w+ #b0 ?A;y;ehI  %@G鱣jDD¦*b$nVEJ`lMri?Slm&˒MUn9貣d]T0!#0pfe~ts :{{^Li1]aun]N4 uU%M,t h@]\+-CcR1>YQK%a\/Y#Le_Fle&7DES<5*((Γ'_I8cw}&񕉓wRGu9U8Gn,|IoFMIf6'  =?)pХI8= ්n$P|ޤQ/pA?CvAq?N{[|\: o=+_PrF%+ iixh 1{^0RVHARB-+qm$#!28(² J7:/W2;bGFA0.i'&Ja^z^%k)2V!#5.%Q<"aϻV_ye2ld,I*8FlXFa,BҠ) h=% S1:Ǚ> qD;UNqGF&,Q^(J9A(A#~Ra(C<y90`0 NU#YA%+O+lPYQ<)t,jN7# am2=/RlLBƞ0VQ'DV2em@tWLt\]z9$R 6AYQh6Q>+I(AG\9c 煇g#b0  0 :©aJ]ЮHA @w4OgV 3QWcaYfK~.^EM6 ;BKF|谙2ɡݦ"o\-jun_pvQX2k*ͮYd2!2$Y:_61Piu.C驪d?YkG/a;l:!'b=c,<DR !/vc0 y]&FaF%cP =Dy7mDiC x<,U˜3?wa,9ŜhTWbVdܠEAAv쩘ws$,nv}UCӺy]t߃ w4fuO4YϗUo0ڨG龫Maws4J+4ѨfOV㹵y(z%*_n yyH=G,?W=>(Ju=hޠ ~;1evhLe0m0#~O?w^!K2=i3D3`yBJݭZ[dhƂ@UR hiOJJ| lb-IJIbx*ŀOLPfDH]):*)+kS;yޣocgs̉T>ȸ>=MIRvcB2)RsUvV@M2L%%ht!@ ؒb, aRpTR'߫;ڪRL,k*^dy<$LA`0 ;ڠrާ=KXLDO0ۅ,l,ҕ8Tye[URp([h͊1D_ J$'v,EUΕ83Lua2.[:(3!!5.yv ҸA"r_+|C\Gd`g*t "ȅᮋ,+%h&**hJdv@FC#ȇfy$hb $a[[ur32 J&_fB͗ `0yoW:PRۄvI\W7F(챍*L)EC+,n yvi9Ls d]Ve8;9()$I5h&DA@YdMF`Cڢ$i+gQK]#~SkaZUuU.鏾M?||$bKE6(- [2r0Eb;%lY bP킴M:ɳ *( ]! $ Ԋ{σILhg0 kZI0PihG " +*%HZhZER YQfu2[,mZ,R+<ΖѢEɨTZ$A- JT%GSm2OR8255Ȝ yc7Hݏ{jjëEd( q+%}ebV#lM!uePN-JA_uCKb'QݥރUFqEPN2$j2QLxl)ac?IvF02*`0C'ܸ2OjXZ4t8wxCF" dZ|~5neN[B!S$9,xɮlTddVM҂쐡Ǧ!W@20B!KfE/UENduegYWfc%R3-#`w򼴢'ĢCQu> z`{#dFu${SC<`d Ʌ(@A2IF`H29/-htų&d,_IF؃~$Y&)l-;`@jyմ^ct>1XC8Ln{Q$^ϗS:~&Z̀~'klSvC+ܪp +O=6B8s깪uE1BHBDsWh ,]deAS>W_ u2; Wx uG iKQGPF᫺S 7 3t7v}QM.,sr؝U}=Z o݀}@f IDAT+Rt׹W1.wFD_EohT"b/z|wzy죝!<.<)c`aMboa|Amp=_jri8S%EHzq8N-eyx _S=Wr)fDǶb>?h< %ܑ .|:%%ËvOAdHzPTߜ'% ;RF 7W XZ7L!p$U% 6M2(Y҂UIb?e--cQ)I?ɔ@ M,0ɉv_FB8Z1ƞ9 Y+KuRHHR,r8\J5)-%ë$lG3teŒ):APQʷ]E&j@޻ wK`B;`0^xb2Nģ͔vɦȦȤHebI&D,O3FwU- LM].RLqhyyj*.c'%OJ0`(_&/rX[4.JU^UI m'#(IF|JAT)_Jx@KC,HSGʡZFX9HKchv$>Jv8Ag8о@ 9A@v0IRK,JxX$jHO.KeBHA&>@B;΂g$%YQpN,%L4ǪDH.(?%bC6܄!kDPiq!pe y6&+;y4PoC6TISz6fEV`0 B<yEAt(dd:[̐ːKqp-lBPgd:Awex@>T%ٔJrf4ݚމVKEխ T$I!( Xe]LsZt<[ft+: %BH0_ϰ Cms !A%g YQճ1)%]ZΔ f(W+M˚}J}}u# ,<`Q2t.D8uOIleLzϴ3ʊ H8DsFaZ{ f ZETa@Bɀ <#J0Y0S0IJM27&W %c0*Y* Ja1W0E -< %j%ږ xqjr Dw /5 qb dH M,̼1eo8kX>_mnekWĕU yآxȢXd4GW( 4p|z mp\y쬘7YPD/pb^ 3krXk]ojs: ub‡i/<'RTC#zɲ#N^3Q?41X?H]kUZ>B|4jf{ǯ{SJOf* pB38Qopy31+u1iż p*{ ݺg3lO5U|4{4iЃi]/ <*;@J'y>PR_߃/m`ŕ) DkG*G*r%KO%XBڜu *J/cGӂD3g㒢WN2sL8O$}( RL%݂NcaB^*tڑ% y^9׻c֦f*z#ooO0%mCƵqqvPLA`0 $~B]7g m`<ۦijbf%'>+a?ڒRdžN)0{d[*$vL A,"H 8XB ɍSSM+L/)22˗L%%A-bMNu:eKo?"U0 $-p%`sDw/3lfX:R 2{٪h~Vaa~E]l /Aڢ-uhLrZ0:.G{ ^`0 -{=̅e6$M0o&{̦iK!ecY | CFHYfM' 2Y&Z-B)`8`Uee&98P +W:>B),il^P'Lj]7P`}}?5A˛PVit,֪ѝ|SaH2J$9cJS){ ȷ0椘uX(YPjˆ+,Kʒn-`8`m'<[ y0T$>,-3m *mDѝ( [̌]"S^_`ݠ qơzf]r%(ORp陬uA{ !<,A/ /Y['^ zS 0'zGY69Ȋ|I [T&CBY#?,HLL`@Nʋ'RfrO}p|#J>U`8wMZNI W({x/EFQ}NB̆ ̱2qߩ6:KotSUU!4) "X<ɩ4W77 ߷^ >ˊ|^1 ^C s,g4QOw6?KE9ck=OI|;1)bNU5e tK:Pm7Qi_痐UIo7]x =_*L}?G߆n~z6s hZS#ɚFYI~5Y{,QmtCuzÛ]5͡pZB`x4=yNg¡IwmN,9UPA{Qev4s2~ g?+#8VO;dNeRv=[Vg]XmEy<%s A ā/lV&9yE"* WDA 2]X&AW$ipXX@Ć6 AIb5j!ɅkB)7k 풪7✏ ihyAkd%ɺ)BR{7VfF^OgJGI%$i`L2!(qኢN$q(Y $;W)IJP |ɩEشD@9"zv$J^& pƢERY0X9U6\޼v?ȶe\6 ƵشƖLϦ 璱Y3EIKk&wbB;`03l"c7TmtDsTBc0kz7L@T a(Y;}@JQr]lL!GGث7kx7@V\4ܡ[WI {" dLZVv06dHFLA]Äd?79z8ULoRE &afM )!TS_l~ SA|Hr``zbʩ wr3 &?;'<ߕndXcGvtC +V ϯϯjmᏂE&0)cˆ37ә"璵q7C%8/0ORKa `07C'&1Fēd13lڲV)SʻVO%]40KT nGmti U4]*%&Jӿ-&G؍< 0ad)&MzIY-&|y 2f8.VuM_.!|9 )3rX@He`Чf~^ \eǕd$yKRM F:Ԇ'@|4W*=}7y@ZtO ޖH% 3Fx}{H Rfnh}I݂ɂ6"*" Y", +A db ,dE$ HR!K ʒ2 & ,|nXQ_$sJK5w7Mㅛ]‚Tw^vpE Tƫ \dϼ{+k ϕ @R2^eL ֻّ\6)7|PRf;Mv{(ߋU|x͏^gr 2qrCnwb 4Mi,H)f*0G,׿paaz䬓2nG ~p狪:kՀ+DZ>WUU|c8Urn9sqphsU%|*)/k9>7guz []g|WOQE54(F7=OR+7z[oM>]zQ^)C{MF4(Ͻf={ט cU__Pf<`[t bޮ7c$Q#C־jv-ެ7фWdv՟8CW=WWT9q/9#y{jR#`M,P6zėNZ1qpٳQ+G@S[n}g ٯ f'd%^=?u~ \LH=:]W'liQa `ơiaw孖8qnŮdu$Β]%*\m)-dR% {ooQ5y:9CBa EDᬈ3Ez(** Ae $ d$''g>gÚQ]ڠ 7볳V]}yMHS..MVM=ƹΤy {1D()V+u~o!ɘbDuu)\Phd:l]t2[ \ IDATt [`6`NQWt4MJƖfGYJ j2*>N\tlE!ptsy!a559UY;|%DS]1qQ$)|S9\% S0+ ' L6 S""\բv.H??8XchBnalf,B4551|PڧdWto,QgZZ^mtD w3̈́̈́'6YXxճVSa5nFWLf]o+}umiuXhH5 (Ehc3)I.+2̾6 І6 ,LJ'օQj E :4#uDjD qB%!*E"a@$8{1ڬYXHC„Qp+L`FDq IN sd5S*G;2:wعf@J t4a1hj`>|3u{~Z ߻ 3[}b# }zCѸeWuH|/ XG/ۚ If97ϩ"T(pø²_œ@E( Wi18M>uG*o»C{5!KCuLoj/kg,fQa4>z0BֵzTl$T*hO_?nMNT&zHD|o4;<^?1.:=lX \eI¶3_6vCІ6}Xrn^otܪ%-CPWrzgWd"R(R& Jfh 'e D(%TMHa0"iFsMsp!;?^mJqʅ`K{ޮf3 J`P"~ 8׎/d̉X4+}lfB Ip],t J<8恄* 2ģo aeF0nј>yvO_ݓ}h\U8 j_`^ZyrS!/S=ː)x7&pӌ @B -xcw} k :<ɒ&yX Nelq;CAB׍GN^&-N13QMw'].Gŏ薳`{^]ۼ+$=XwīK_w{b0Y ̘w<= 52&yo>}{x+?l91RلܜO.ug07,ޮ@rMz^D۽LeP/g% ̟{@VuVx)\u +}y_Bv)aDO*WN;+/p<F`5y–k#lKsWy Wntw^Ɓ*jTKӥQ*W3,):+ԚHھ8=?_;ܑV C+Y:\W:(0cu[@ q:TwPʮ@]Ԃ1Cm0l`f@ N3T)?]Ё& -H!2d )ܥ9nX6aJd?$cМ"TLf VDCns6)?<֏l6ٰ;nn̽ıtxcaU? sՎwJZlu$1s[lܼ_܀0 shCІ65O0yZAt̍qJ$@J"cBL#a&ͬiv^?[TRbI,egpo t4F %R$ss K;nl(y2f9]QUyY= D{NQsWW>d;-+2Sk'/A,T$g{ɲ8m"<̈́FTJq o Y^[ZXh?;MȌ0:icU3yR/PX*X 7i{:j3m$6aC!І6 k`Ƌcƅee:?UՊD"e`0u2E k0Ž-j&uuaԐM!h`3/(.2y 8@ΝtJ}uvW!ZᎸ%xبM)N)aV>u|L9q%z0&SA UOsXk Zӆ øI9{^2:ƥݓFg`8VaZ Ѕɉ(C _5h!IG @vP(QiC5Rґ yNlOژWa}QHb+XۭV:KݟA!BJa)GF*mZ:NB%cC5\=E􎲨k: =cU4ܪybV +n@&MZp\v&o9 2D(Ո ճ$mPHbIC4 +ڜmrj'ٳ?OfolujD c 1cƤ:KufY;vn'N;tҮ͑ Oi2Cf *}a֮TzvKR jf ZĠ uSd U ^j^I:\P^0(w'mq0SY l~[Xʫصc[ME|  nlw%|x1ds|;)A RmfPgrپa]~o/vj;}w|栺c-ۍ#{\`o7p[ۿs7ilPg{j7lA@^/韔E8ȷ?x!mJ/`s`D-Vլ>*TIEh%zZ`eTFH-]$!/?႐1E,e/2"tnTNІ:V&I%` 4D%S-LHsrʎ FXNF@Q$5C aZ2T E2g9rr|fqq(gWd"ٳ]I8ޢ1J(J:g[=oW%i&gn3r}Czgmo|95nqh4tq^nܸ􊅻nk\{)J2$H27C*a10|d@"!b ]>Cja-;e8ԮP ͮ)TQѭuZˎZD]-Son2)'GI4]˸B0wE)XP'a!(A]3K*X1"1]Q&s <48` h'iORZxj՚ARA&<͔sz0\ݳzșkRu|例73:8/'%oehټ $YW:!70ZhzU>>tדR&Xd4{vpf%A : w [z!MG$-"35dDP8L;ڄ4&FvE>? =&cԓW46t YuC`#!&d:Os۹7/gi7^7,3SOryk~( 6W-,-Z),ܻ[R{ϬEFŌ %mNoqVβs N> Ԡ \ nAK":c**2mDifn#Ѕ%xmKm`1`2Mt:0q=Ƌ_EKeų/35S^?˪̼"ˁr'p[L4d \F ]fDHl 6Xb}aйTTwo# }'Іǚp Pu&?1!{B aq+r(!*ƦTJgJ@)%2ML&AHE UE^xv[>yCnL mhCІ6/ˠ3T9 2Z'jTFT=!B)uPGa e(:Gm7iК$aJF^dB%<[''T*D8WZ_Wn>w뾛nuN?xM)Uɤb>`g| <}!UuC[245mCס;\ye󼸱r DENnja_d"9 <(q7Bk7>5{m~JThwSit;W''CSK !lk.L a飔tM'[ B b1V;hIi!{d]_ U)ow! :[4>'!B6!q&Ou\` `DPsgbt2[2I6M]ɷ x"*ckEsS,f -hyآbq ]{!j?PrϏ+\^>;*{ây? T^oo)y٠7{>\V-nzͧ#WiO}?W ^w*lp5OFZ*rGYNxjy\_a?+x9\X^^r{ ~hΫϿUbhtxР,d  ||Pb9EbI]nLxUKK IDATy7 |德= Wo.;ge}nߟïx ,+oiwKROx%+U5_wB~\M?k/mRRvO_{^L~h_\`ܳK_Eu:3k4`T\r nUQ0DD *0苽rμe]!#$*J8\Уzqִ!$&k q3NgLZv\F23sȉ8&Tc4Hӏi}$O&w#=mچ-ͦI6"^$kGfR5v^gdXKXM!|R[7+kSvbiR&KE}sV38AJTH)@WE8DdN0jX_X!,ճ30PW2j9x9uKn7wsmq`Q>/Ĝg6S>L+d•_/d2a>Nw"4Bbbufoy%äKC{LTZHTyv]ZݴwvUj EڤIXZȽ|s+Zg/,h9u)ŒT~.0e ɚܽOJCE*84rw=m76$BRHBtT7պid-"d*8s9 mhCWN؛n.>xb?h&sU!PJ(9ẞy=\u݉v tgUӦGAXD `T2Saq() =h'@wwnVjǍY&0Fcu y\ +T*nI';FI "h<&:K,.t0vD]{+ΪG{!EVw738S[0'&bs\eӫV ?؍؉G(MMxg*\_ rNܩG=bT6lN)K噠 @gn7ll=(䳟նt70~h}Y35S j 793i1jP)zXw\߼Hyv5}tkYξ RndA+ӑbҖCo< kCh7 mhCWg9є%3jJ,UEkc~~%KZ7luۭn<i_Bg_=寂59X`9Q;-JR$=Z;4 I9"66h^ iāu&wGƫGeӛې].ASd!i8*&LFbcXQ,”fnv+I^FC0&x[aIbEtY04E9{!l¦ߴrFv*]iFVAn;#zd޴vTE-pn;B#-!4RQa HGKe:B8g0!`g} І6 mh_k40ía2e{+1iL& 1}:k7,2V7J:"a*6{s N |t:yiq|<*J]EV*g~$Iv *8Q.֧7fwvwLijZ:T6TmE;:5FL\iMY@y@jjwRv1ƧʈKQ$dDg s:r|NQ&ƙ}pڕjy?T*虥Қ J(Μ\Z:rrH2}nnv뭫Z)s7OY_Ck4{}W|(;)wal  zjME=&'NWJ}|ϝ;>Ua IG8oYG(nTsJb/'L:dIh2F]gyF¥ ̈.YxǪP ? P;MAIDfQК{]Zaw^eG! 9&/^)|M䯨`k灔$BHXoAC3x+ F[BIMBD&Le:41c 쬞3޿_/1 mhCh<htA릔lL`"a#B"e/SSm0Xy3!5IJ8;\UI8VjvY鰘IjrJ<˟|Ԟ=+vitvth/[[;[gl:uƝ֣pb쎀s\30"Cjں'Ҵ4WPe+"2 @<\ӗTPlg[v1U"PXaahѭ}+IJ edmudZ$a`nı3VkY+ RI33:K̿ZLTg{bVf-,%?h3`‘uHg Tx}]!_Gāuq1&j|hFyvKUKIn:kk,.rH&S2bڱdɛ3a^.xoF/@\S1DGwQ0Ǒ:0QkpB hl &]sjuLU{ Ӝ39fw06JFNJF%"V dˋq3ct! :-lk4ߑ: MuM^A)NJ;LhR݌NF+e3akUTΌϊhcT~WzwIwOc˒ 7p;-O. £=kESU%^\}^-WS*" wWk~ |w/=^ ~=啔U^?T]{/E{zJ$y̷͠LķWgr3YU+W^?1,4Rromw\Yx+7ڈHI77_O8"/#`^rsp0Șݥq_W F34^Y^/,˺Owoirϛx7mFדx=ZI)g)?J>W^B>=7vᗧ)VxzÖ́w /+ xO<ʿ\x;̖{+?=/;gݗ>ڢ _kk4*bqq>ŗVݡTH !>!\1]9_gY\,ک2e''' ) j.xڧowvvr~~>(L,JRT*$INH4%Ȳ_ 8댌,4c[it+N YQZ&pZJQ(;@f`:a,HJRB*1}.3:r򪧮^9(cA.GLM.~њ,#GϬ JJ%2j0&ZI)3!ȴ1*0Wh'c vb FSckRvy5>}IBC ;KSu]*j5wnjafS=eֹ'W#kP%.f3bl<*?Cͮ03cK܂c;=μPQ d^N\ 8eCApi`RE`0&H3Ҕ9.,%U#(dwM0D"@|k=]d3WK֤0HW^cնt^(Q٤ @>_t7 І6 ˲FnW!c!=_ @w ;_Wvh6ξ ' 81rfKM1+nBv"/A .@J '[i Ǧɹp.H@CG[kޙg'wOO"  IiH=_9*@~9}wRLka!!R2 a\ulubRJwy]hM.3(N&mL(qe2M8Fo̜<\3^(Z݄HrT6=œEwm֘H۹]LO11Fh}DsZK$Gwب(^^_tv*)]*ru5%Hם;VF;c%a% kT]qcfwkfGݷcsg @;CJBhYIhy^YZFJ*aUnR)#JDkbX hc"[k7ٻ P3$kWzYxn[tG v})#IdԔcv={رn6 mhC.~״1UQMB^Tۨ:\W$/;<>{pv=]tgeuH#A*۽;M\zDxlwxt_rDzDL|<٢NYLt}ܵkk~~W=>>g̍Yn l(EwY$;%A@BH\o"dIKK*vޔ~^Xd2C*r|S.LM6s\89N(QEJe[UО۵579lLmMd:+M3e'P%׌q.dM&nѓʺlw6rLC~:|/V~S.!!FIe9ƒKE^¢C,w-|+ |FC31ztgޯ=$qLS4:Q$LNT.i+ կWW8&MjUc} `l:PfN0ITOqq>\n~)jvI&SsP2.Y(|h禦,g*ϳEa٧;IQ$#lK7,ӥӥer+icAl( [)QqK*Ү Jn$oy3/X}xGاiBHެ~1J cUU`'d@,u@>._my6 mhCFrx*^mq]QEF%=լU=XQv޴/9egrOk:t\~_pnr9"M8>VY3J.?X .<^|2Z8-[`wKxt_j+w_]VS[lXrOl3;<1@v.lnln4lHc:KK;ep8 */'5twga^t C5( <-}!gF{Ro.zK:+oAM+=?(k%4 ,ϔ{~5O(/pv: h . U.Xy3or^~{r?_įx͠JWpQ{ |.ܳlWc@&=~n~Ŀ7n^2nxk(K{pg~|/ _&+ZrKm+t^+/e6.(kpr| )|H9k /yJ^{ͭn͠"Ȯr)({f:H+^Q47T˫ 7^4l~/Ox.ً%-%x#Gxt(/pv[홁45V&_}朆G47ņJ\ΰFg` 歫oq[$L"B{ٽ9*;aGl9un:8'7ӇߟOųE'=ff̬GX̷m"tl\`\sDNf'c V"wuWp?h!;K%azFK)DۻoKg|A IDATI9Zy%IY\zo7wCC#,訨(. ((3(8(; b oUU]kv{ϖy2-ut9O'OfdF楛[_sC +fO[%33 rA(4ΏUNC;:KgY=[jn{C9f+@]SO>rQc @"&+hgQGs!S 3x?2VO׹4RWx2$嚇gQ/zbrmyܵpjҝhS>#a8/nIAw>":(wXiZDlveA weQ@F6ldffKr|;MEK,\ժ%He*9vLJ'%S:0g"ӡLcaiUmDSWΪ87ȇZ`&k-7d.ӳblxbwKbz\4E H܃㡋_D_#XLAx{cŽhYД.)@g"dηn[k?h&5D4t&Rk€i|^ν)຃emgLȕH_& ';fL'eN: "!wi]˲'<8?K-/M$hJ : zspfН1˾=ݹO+ƀ;+ZeA(8JGcA3>p&td@RZ8:~VƏdeu:+jG֔~tҹ Ĺoxru}gE!IjpDJVwΟ,^yvѤ  jnMHq.1Ȯ%v -QV֯PBwp貋76(]9{7RO>%@},nbP -OQn]_=3y]$ IaJ^!kK t"@d`*R99P2>Lzs.U ˺.?:6- ^O!e}TLLL\sMŏ,.Ekc0̓c6m"[&#y"wm:5{;]D+Z\Xu9pyϞ"@!c0 .viN[D6M18kmP\Z2CfHfe&qKGldbi5tNq_?2WO$ 0 B%f[k1C :!}LXkXZ':|wqv!9Ї.h8~W&ߋ]?323%6/gWMEZV*)~B !Y8Y VJDLڈ |aly ~{I]X\di !\u0q 5+~s">,a6&bϟOY"֊\;[:C[z0fp v&|SXC"E^H 7b2V| L%. S c5Zi6]fO<xAYfl0 `qy ufцd]Q8d6fІ^F'e5H YiBgԐlםI3 9:7fz[OkTIut008H H yaW3"\w&|m[ywOyQk\v;E? X˪=_jOXǿ^=oQM/(& 2/~1RԼ^]%Vݙj,6U >fE?t~|[ITYr!єV7ywn_n{/YkkHWm?~au:}I+_^/ktpw֭|X:~{w4*UUU nWjWöxy|ז׏Wf~&K%9!jL-2s'ޟrԐ履ך\w;Fy(fV]8 0ruӏ>eY/ZiyX;صf%7Hoޕ;kG/d $ȃB׶zx6WaeǛOo0uY+m _K];]ل UG /c,Bez]ay&D'W%"cI2f} dr&]6ZltM>[QisU&eӢ2 QcM kVZw2uL޻z.8LB"BDܹtK ?]h.y+uũV~gq|l5$]˛s#,n9F[\ndS_=K]>^4XOvq5oMZ1+re|&?5 hb`pY.hB),}]K~6K‚e^CCe`0C PD~mĂ9•4%I=KEMMMDR$T"l NČh m3)ALo.$BIBEd䚙y@xt7*ʓ5~Fl|ǡn{pW}\X­Yz+zfГ̠ JX{ZR,рJF6ld_ffabQ[ RNf{f>m]ed#jǒޣ6vզ6B$.ڈјB" !b@>%Ux hw/쾶'{YZvuwduk?,hZ/oװkQD1_vxxzL? 5A]PJ %iJʼn2WssN"\dK'Cgs1d`|,cR/Jrk$^Lg@XkU9 +EaQDiSȑ6t:Jjz$ywk X4CXr&*M;d3՝L%:Eeៈ;;L瓦S!Zit xn#i=VYvu2afA(8nDI51Cg-29\Pry5J-6դE;B hIJ`,ƀޠd L\“Y|ZPZ X-tJq (\HA(&TD-ܝ)f#BPfAcc|_y{/(Â,VSvHiE*5MsY) @`bZ//er\Fnd#F6ff6TIeM (^9W⨻3jEAEr⍵1]2\PWjC7=;9rQivΙvZՐ5D'ι<| g XX'`|T ;tTq1~e<=V|03cFډ-[6mjMOk=ZߛeZM,j IK_JAlu.i t1[^v\%=v1%!غ4iY\vM՝:)FNQ^E8y ݉b<4"0RZm[Ǝ 9x,$ hb$bcb۱@"k/m.<+#7xp{ٰg+~seD5Cq_. ,e`AċC3glN ?5(lӖY'U[&(<гDkt~ ð$XyY_l`,RNYR6%1-WLeyf"XE#]PTml]O?‹aNB).LI! JSkk]DIE!=( *53W}gYc?F6ld8<>(u@ݮH_Q4u~D6ԟa dk K8!FjjYF/E<]|TԻC\1E dqBriNU ȵ4Ecccqv&v֮]NݻǝL7}vعc(9cݝ]Et߿߿߿YZZbyn~,dj$w!Y׋ŗ{\| '.neqI\RV( ﱧEl Ya;ë#Kt|1YdNśe~eYYennh;%Hwjy7SXtFڧj|:0T|t!B:yLCrcL^!b[1ZFdheTVkdSo mrb*+0e^73ӂG28aʬS I(ڶkraHJ6o=nƇ.K)\UChH <'X:z8gu>8_jU>b$+b0 !:y8i̲.7ً3%hWc]5';ǁ YHa‹-[9=,~ngh@XMYNXIXMiS(kvI-j{iw_Us2mLD^ZH- TDiHxf8ߥb~r/ϯRjJcmbk=On}wWOhک_ *\UHxjK/n~>֨lw-NS9@?Nޛ_wv;._/w`g*/=T1*37W7 9x}CPVI0k~dKw=ϯXe>~_/$RzKk2ƪ/wT{>Z~\]x-9n(Vc:~y\HW$ >R4_Vo?|1U4Gm ~~ܾt?;W`]R|*DD$R%3%ъņuf̑ld#.`33_S~QdJn%`'T2Yaאk-Vc՚,Yfu>p,Ei@ Kّ.9*n!!h- qZchc2=SNԆȡ!:Gѣ_t^713[b2?aض-ϸ"`0ӑEQhxi_IncTVe*, a|@M9.2acI/t>,?2 D5/nϾt$WgiyfٹFF9{Tb\]gɬܠZEQr)'\h tdveԒ Ry^֨6wͿ+f}mff \Q_a2% | J^1h3h*D@izjR` l/%\&ߥ(Mz zqePckɲ:b/ NJ!4<(f]={Vв3߬Xk! pi$\؋.8 qf>Qn}v@w<&sϲN K2Tq]b"ENHsd[ $")0Y@[YyOX+PB9twGyJ)m`oH!J*%UkR"%!댽|5?8]rAld#.had JGp^C5-h+uc|#h:Vg6MlU j+lF/4YGwD|K d@}I:M4];ph8jEB{RUGm^tX`GN[4DY<.&'W]5}솇J; :%qT[bi"uRi9+!tD^kZ %*E9y@Jj-!G[IcZ0\P+l]fkJs[Ӕ{l5U eBJMkaZisI$>N a>ѿĐI֛K56}% )iWá;'" ‹ؒDc6N5I\Tr}K"S2aF$t&os,B!QD`8{."2C)r,5y9s,cgxH~f-(R1ksZcIjtU] +PRC7%t,q5Z0T)"ts}feT 7LuJ7QB*f2`9[څuE"M\gE3_ׁj-F6ldy-L\ȦrVWAt[fHO2ԥy%Ll4KN"V4W(j![y!B!T^.O˰,3nZP@< Qիd7Fd'[NggmS{\7uDZp(^GaHE S윇\H JyR%xfg>s?`qԅۃ,ªϩs5hWPrz-NhgWe34zi! w~i~)]Ө(Μgy$zp_b';%ZL g) bW!PouH*DV+L6Jodln;^qq0OȢ[f ۲]Qnj}n"XꑌJ(_@&_+Gc}9X 9^εXdcd T^3%$XҔJlr3 hzWb1 "mΙ9d1/r'v1;.CE,r[g~ǞsCZd:g='Ȝ",/θҫ 4}juسF6 .τ2TA#ojO?s rDJ?G:Sg@x5(WwC G hFGyld#oo-K];r0)t md`Tk5sehh(yu\7l"^Sb-ÒwYjnWѴ"=]Nדíp+ؿ`4x6]{5l՜v2$ef={!`@OO`Hea֣<>[CI `g|6U6y[¶Ulh@r-R;bI PAMly&>zkk36Ͽc;>crNX]'A4aT 8 RRι֛Ƹ&)pt_FJb36&ӦOg $ ":psffPCtعʮ 0 #;[;a;RQ )ʁcG X*!R dy.S%%A(Td=ۼl9qYoĪJcL  g3?-s1LIb/J`gtRq.ݔHjA$K3QR:/k6 `e5Q-RA үZfљ5vЕ*m4"pAE$#Yen3w:kk+W-ewq˫]LP-} |˗/â`ou{#jp7CE4o.P$/V:z/_aþ:T þ kWW*} 9{U+95aܷvzAWU7x3\Hbj|wT=A\&,Bi}֙Xt9_ [{TR.SK J9c+]]ߪƏx?Fu7?΁jnj,v]e;G(rwT7ujWx#yUuV%q{WK͟Rhg;xW%q!Cx{/?w[/VkX)5[FUwr)v/K|R '* ?k\\,jϿ.ͬYo/]\$yQuRSn)vh |헹sk˴%lS~m&Ll`N\+F/` !f_'ڢA͆)vŎjfdgݓ'.8]w!MYZ]kxmb*f>0.繻Zfq wĆ/{1njy d+Eep\ İTBUDf-790l0J -xg1u֕>zÁNɹïhTLaAzg 4ŬuGzeGwe;PkEzΛ=Prff5#U33#+mMb޹6C2pCDel+krO>{QkVȽܤbrdأSq۶7'qWQ  =pywԾ2Ԉ\cŢb gJs\ G}dnp\|}>[ ׹"x?'Iqo}w1z )} i1u;XP<2S\Oq4䏧˕]YhAWpWU)RC?c%]ݽ,inͶ t[RF*4֎DgWN՚qE* THB))0ʫ0m6ZQ"Kd`-RPI.t 6 F6lh33_* \WCw,+ԅ MiR+9&MlCx];s2LEͮ4E=Dm\5D=vÐA]6N>|Zr"USyfn_ΡCnjccؘ Y6\2]@[ع3 ^i^g47_:<EarOx+UNL*1X.&"/M@y͡>"F*#w}n\O'o_M^xaΖD&ҟ4msYR ?n[q֗B2BARhWBJ0Վ+ D8 0Y~%DM8)FKY4Nظ "lv Pr+Ikz7uv^WsL+@O0CrGrU袔0'Qzne;_(5G7 =|T{r):d$4% 8E.J{,^k^/x aCZ0#(ʼnӿ~TYX)<"mh\Ne O " )چqU0?~UoTIaR@ .;,qJJǴ2kǫkeIb7Ui=A,"i/詌F6ldeKR?³YnUW!ΦxP"+CH!QJ3iQJsAv鐦@])MDVD69?<֫^%{p.vK'N46aZؘ/S>+^]ӟzqqキ|sGZ?wfl 5T{yĭo{ )wg>K}Yb\_u_\Кupku&! g$ˌ) c"c?;. MQPmO2OߺgϞ.#ԩ#G-w.J}Oat3,81{`iB1G1w~4cVWRJpD)5KFΪ+wRVvHK($ ހntmbwI˜I, hFb͍Iki(CH)=&Zc:ϣs<)[,Y3Qru|..1Vke:L>{B5<"U϶.|g!,x8*s9do[A(EQ{ڏ֚$\]DAHXJ!/'B"I <7$}N)s) |YT"gQdߙTgNVZٶL4bG;3#Iw}o9w&kgWË=_Y p['0k?[>X7U'~_Aˣ{xsLK^՞ߧ2y!YrXN_/OdOU&xuBc՞wT]?PxO_Zj| XSZ1PXo~X_tjk%S.t+Nӧd.$jF_Z>/U?\7_>{7?17T>~[ wxn1Wy .|U!&ʺm,{?T8z?rn436 ! IDATy~^-޵Fկj -5CXFvV79M`3st:.XKM+,>WFo`ᄀ"=?-կ PVE9 7V{vm]*޳|󿪕?÷?_SNֆYZ\,ago k"y[@bAڂ/O}YOe]f5fUp@mX@Z.:ά*}. tTz` ʲsqvg*Y7\ o@jaxm5@>}ӟ{G,>,k5^zA녢򸋿_w]vs! M[/ w q9krۭjڃW?TC1R,KJ?<5qphI07(?Uk9gy9I<dp8h4+(׉G:pW^PQb@!g{M=Uk{zյzꮮ~  2uM&a"T$m,㲆lrK";e.&4r)e0BQ,\J)T6,*FJ) 53v{[Z'..<Ib~T^x7X0A/J@)vbkcR 2C!S# 7*&ש)ȺF?@HT ºT35awm];Wm/D)E&- 45+q#&m.Ix%)'n,)q3p)Lw@ j5cػUWdK<'H3 [# #FI@L~&P@QY>wpL`m8pyUC;dN!czz`]@dω;cdP j r25Y@neA7N@i+D 0..u.D؞EȷR$U$Um9Mlbσ9D=B;ʧu [hZͤr7s@\{ Ͽ*U?XK 0/h"3SҢA'uW'u݉)DTϖe{ڮa= ^sMaF,^uU…<:t-d""Ç:t萔+t$I1t !Rx9dAo]f퉌b͸ϧ\IQwg7? .>IS]P-ʊ*nG, }@8t_yfhP=scz66XCiv(S|hEs儺.? BT@ɾy]z ywu0p_=\u7<˳{,5YnU[ɹbHՓ@< X?,ܞ aFXTZ&픲c^Թ8":yP /t/zJ([t,jsMlb&LN ]XbaZJDR9N\LKYpHUE_nhyw Vߥ<\'J` eEq5sӺDGa+I/S{+ |睪׫^{^j'sp3XXHfwhͭ~c:477Uzixu^2=/ozg`1]oWy ժyl#C>˼讼šT~8c&1P҄`uЮR_uH29ILoKuڊ0RU@?d߹حL~)vvCT pI%J*! ơ;FinϺFڮOw_\XY">/UͿ+݊ 1ο.%PJ@J%&K 9$(#/!Ѱ2B0aMd#H x9v1蓴gSNz0^=_ Gw_zxO3hå;,bjQ Hk4hll{W^[*ɷaNy:!k[Wk;94$;+!ߊ̬<]XMqYxZeK e") 駄CAt1NCy eQr;|a̒)7WB;i0J*rt\"+](Wg=;Mlb׋-.WBK[`w[UsDf)̹4?N꒳v%gkN$hpO4SBRwr`dcxlᡡl#X/]1ugw[w|G}Γٛon=z/'̓̓eg zˎ;vر]F+tx|/,+R!u"n$d[N]kUyAڡ88*#ݖ9N!?g# 9 ڳfG"./e ;fXbLstB9iLQ&ȅcSdh_NP3( &PԻ͋MKtb9B͎/r%t0RB U VԬP aQ 6wPT[gnReyKًC#')kUYj3޶]o?u~ ?+_ _?߫pj?m%9xMת- o6_]oR|m2Ƴ>P=w oBoV[ՖU~ Ֆt?u#ic-o#|SERy,-wj/.ijv۽%M?ϏtķD92/Z|iVWwv ^ћ`A1?k/+#[~G{ Pai*{7yՖ3$d~7`omSo~ۥjUw{@u3/n_[nxwa[|J?t=w;\ Îj t:Hqկ|xW{M=w?|'?>խWMٔK,h2. SJ4N#,g\-!k NSzT΋˫DT+3ɽu94JJ+-l酚HOspSsTf{$f0x.v|_yͽ{O$ݮV{Z(I~gվ|pDpqJZ0d׍$:Q-QSݠ"x8fp@IT@QOd)Ì$ ((g}:NTVVi q KZ[U/ ؼ:UBQdyygID Bp])W`QagΩTȋ"z).fQ{ 󢟙~F?ӽTr.f#J1z~Tyne 4&G`CQ!PLmƒGR5h-=-ǗϽ4%/D (I/2bԽ{wxz{\O+ * hS䌖%Vz@qחyJe .rxF)A"$Qah5jQx$#1-hdw .pCeH"t<ύ1z}F ʱ̤*Z#6$(b2naᇎqǃ},qLD&Ha]M]u2b>ZS:K?wduɁ2B#  +i:J ubuƊEPTiՕRWyc–NLU;Jyկku>U3aLjx( sxY0Y$ x>t)uvo1V62G&g3p59t:NS ̎y>̇C@%x"W]5ɓe *=>S T+)y>U`ǎg:( E F% # e5bucO4SCqv^ĦV/r!x*3Zځ* !0@NG`x̣bL[ΦeYQ஄|Al&(d3CE itKMܙO9=)>A)Vr4h> ^v؛9freƋP4ꊕ!q?Y͝fR&Y_ˍvARI "l-lm !h"cڵ`3nstE~$h66v&6g; ʩ* +,@&tr#(r-xn>B(Y:7U\jx<+\7_rgu@~g^4;;s0A|ǴNKP[oMfA]X/RVqAwl<2,-r^o]PM/=y YhB8?ř6gm SqAIJl Fdl9j4@ \YEqtYq$a?aI ,[\l :R<,sY@=oԎF}}W0% Upwa @kv )luJ.J+\smpOA))`2b$1*l`l).C-@ BHLh;GxEмx^HsuZkyF߬ܝ5/  6^g3RcVL`']TB&TAZ;wP A(J=,䟏Gm)߉Mlbľ.lqGkW.p^VOLQCІbA۩82>Y"aGa{w`rVFDS#i|UPP0k͋/|cfp5zИC qHJxpxk34替ʨ80wZ%+W~vBHI\ԋ:'?>潞ܩJx 4at-uN%"3mG5k*tm6 ^<.XMv 8#NS9$Ѭӊh1<]j2SNM)RvH4ɳa~^7jQ(AWȨcjctn˫SDtjSonAh;b1{hZ7>WRn9wu&w7՞ $E4`nF40ؘy8~}5KޒRϒN[1v!Rg&Mѹ1eg/;zʻG];{]Z(Ub|JC,•)\ߕYO`bw_A]k[Ev6z&C b2yW_;\'\ߔs7ś#[t"أ.VZk6OXe2HY2du! mz}YչtfXP %*E hs3YKk{/< CȖ4Yp@g#M?~md']mJ~eUewOz+;{##Ǖ9 U͐;;<^Vmy7x ?>^|\4e\XrOկ)TyZsTo|28VfvW[.+?\ rmlu,?[[DW@Uu׌n xCk+>_mz˭?I͏:8wjn~UN3j:.j"_*&/וwZY7UKG'?QR2|LS)lۇ^qE~>uFci6f՘CMg٠g;)y >p-/ pN:׾g0HkJ;fٳ_>q?xVe8/v0/uQ}ݛ_0$ NB(ЖTM>iv \;t)v.[rg:/q( jQ(isˊi!HQ~MX3 s fdH[$ACC9>rUr1))Rb_"zKl1sgzA%4: |pVOmj j-A1D!rj5Z IjkKKxSیVŀ$}¥AG:Z CcBb@6AV1)2/Yᥗ8'T4BL8b=NLs+T7/5\/_ ޻C!T A5Aإ$Qܓ#wh7MlbzM?+kOlGWFEȥ{qjeUT]GNwRO=feE(%L66SZaIkz? (N>aRnӁNz5 7pVٿ _oq˓^]; gqCw}ooij"X޼-5rG3+[HXh4`cG4Cj!D &7à AQ"9ͳ$O<"3A. !C٨}ld, fˍM5^_1t-ƃDx-3[6Sbfht2j*۾׈d s\!@IqޞY?ߧ3g6rg0yuFmRa!e5<a4+ 4[h$3wp#.bUmpշr0BpQ|lvUosM;|O@SӲyB1mx~hbeM5Rh ό<(?&&tV bEwMh:Ww15.҉">|[C~5s|*"TLe,|粧腋 EeYYdяfqI"vi&Wdxc3ݮ~_Rk9}WCv1To/5S?5@w}m0W Vo6R@I"Qh '٣D-ߣ3<#ѱo&@H v, R1u脱̀UͥϣlZ2r۩](Nm{a>Ů=4f>Jx,oqp@SA E-$B=EX;4#N <Yg&F/O.9VF.ԸU. *i[S2S,p,.m-06x ks.O:7Lu;:ߌ6&6MV[\fU*=KӇ>`(w)yzg 4\ gP]^ RHJGڶ\us}RA%򍸑#` /|ᓟũ)An2y~``pHY< Cgj!`L6WE44^ ̧O@y%guv;MWPbzY\W^{c[v-:u桇cǢ#Gtp~`f M!v{}g2eeYp|y?W Cj Fv%0(j9̒$KzW5ZDQJJwϜX+ݮ®xs3ݞJSÏWfrb'Nb{ף;k0+)(;a\l~wT?B=zN>~pѩt=wq<3BҞUU)B)*E#+ynԺn3ٺ @\Q6Mk{%K&E;(5LfvjQkzS<|7s/ҳjv5)h:Z/rKͽ!42ynM Yۧ\cej\ѹΓ<f` ȅp=-aL$O,%ɵh2gŖmFuG__ ~#T^;~'yTQk~xbcV[n?K*#XmmgTaNT[.wPc!@LjW|Kuk-6+ov{8m< ]FObM^Y=KaEMl8fn F|lj/z~W{DSFkЬTEbq,P᭪c_غ=ër۽[rTWdV[?n{>Þz_W~SeDG?x |_;n6nd i|*c `s BF8<YG0GB7|}uƑ7xYu|'5Ry[8bɶt$B7lpfK!#QBhMmI2Rbsv;2gp*^eUn6!"h50I)RvM]v^tԧ:tv}Dmx* \6Is*Fm-#"snhs±vlur2 °,b'v c \Z}xf[~(챿IK9qC|3>>N9I >7Gx4`OSt~}4d(iӽ}id K͠kBiQhhgi*J%,}+b|7=M:+cmr ~ )9+_VX.[y]e¥Y. *D;a\("G8ZVGC 'o[8y'4nJyq9H]=UUقS޺[ZF:F||5!M@ km.CF* ew?qc@`춺J!A4ҵ j7ӹfwv-n룸 dNlbľ\Rhc9:Aix\Ⱥu`tN{ #l޸e:iD^I-BO|XكϿs[fSMya6 9{s{Lו`vϞP) 8>Nߛ>-R ىB=%I6j6SrMnFFSvY#jiXQ 2ߠZ׍/_0#ѭ1zBR|S}Vw0v70t禼vq .x r_tfK`R] 1GudSC%23ed]cW"wWv@*0"A$)^ ag__\a1IBed~~pk/l+9c.BjQHUPјK+<k0N_H"e!&+ܫ8Iwci 4t>wЮ׀[Wi0!-tЃaS<[+#7尷 `A )+6VyMlbľlq`9z yh;_ֲ4a> {'βޒWuuT*%3 .nM-֗զZ]ط/t|;-r m~U:ʴ&˨ ئX~% XN"O !yAI )"[O[][Y=Gߋ{jqw{#E7h=Zك.t\jml hhH{}PIM.@@6FK8I8&M Hf= (4y caa+ 4{~Y_/p`FV )_t'>ӷߞ.o:%݄ A{N~: h6bzv"cy䦛RDy8Ϳqn #P<,N .˜fNS ZdP[:8pV̯J)=L~8y# H)%_f'7ꙕ_Wj+ZufxWRayځ/Nh_1[\|-7sg}y*&Q  ) t7W33RH38VVifg75TusL!&OlK]஦3/f[b`hӡmy!wHzk^3?ŏQ長3x׏T[~^ʯ-GFTjoĸWOgy=|_uwm'jxwW^?[Q z릫_ب3pEus} (o\W=9V -i|z Jkx=aڲ:&sIn: {+?3.j{/ WyPԸhz;%cUiyg~^h ~$ /ϻMTa';&Q}V78ճ^x=vZ'9֫섙*@6?V@z_t6T$sȺ⬬6A A'^W?_gA%|Môʊe!Q٢yNN#6 4X.g>RC%ª'VVށfE Heuе#OzXӘ-.ە8feTl\e+={o1 VpTC o~1Z `Q+֒DQ30?/ 8:{X׮ϩ>u֞^{~OVTOp,dfv| h&h3BozD=ǡ S+U=L$m䌞H%[b/مO_lLNo|޲bŠc·׹MxŦ:٥_XyZnٕa -6X6KF{F6ldYlnEP2ai]F$km|3?XI> 4C<,@8aLކY ĺ4Yf} i~=|ð\Cee6dRsAJ@3DnҔ x7^)k@l((u u"}J$Ver"۷ΝiM@I:K9e&T} Hpr 0&rGA+$Nԉ~$JuA:!ZQ\m_\pd w<I/mu5Ag&fW[˯'67w=T{ 4&FzyIsL[gZFTH?%KIV壼Dte"WĦ&+5UWć#%zRW9u#^rwsgm)gy)I_CcUƫ|loae"xN{s!jݙo]q9{iztQDC? 3.ml a% YbXuXY=6&9'Ћ6kւ3pv#p%'5 HZ-2Zco5 /I#<|.goh8RLR[;ZDn?68zVg&`?j#ȁeaKl)9v#F634־= &tkg^v*oL߹z?n!2ٹ[Pu~r<(?L$jmBܖLrJE)P`Z!1$~"Ox>̟dIoފ'90Á=U)(p\r>CoqkJ=mn|l^apl] y2=AHFtjrJUeQȓQxgn Y;i6$/. ΪjK[C;)%]fz$aY_ U<5a:Jd XߨjSuMRR #SyO>cBK{˗~uo?+Z3m8DXÛ/9b\[AGl g F6?=a$_r-5ň>TJ- 6pA-,LLMpVpdq|H:=ۀʏT 8^ 43VW谺c_br1S2I0'M@~ǟh۳70gt?"RMLU&:v/x%IM~O6ȅ n0vi6`cuZ->!:j)i>$paXǓ:"9[oefٻ(+N_/\hŽΌiB;Ё @FAI=5yGA@LFQBm. gq=JKݫK{U/˝8Ym4ʚ{a1u>4^3jzc/73i'%%߂3ǎ}҃n {tHT" U*/ iLK*beCo{I/M筫쩜h\t[A-N4.Vw4 klکLZ'lGȘ(qD,J HIy!,_)D& H`C#]GWg&!z|hDS.QcYBF7hhv r9r>Nmqj^F@۷19NL.gE"CY^;9{×35N*PP$WL^ӟ͉9 qa5ޥBiC jpLk>Pm&Uc! mŵg+;\܆2(^tիw]ZSW/zam-˭~˭/[x 'nV;}oEw7g"L?6I/[lռ՛-|- 1+>d<+l͇G5~(V5@' &e ޤ+)*7g<M{)Y1x|˱l9z$\u@ޣygاMx̟ejzFg4š3j$>??{3#y{͚|ʙ&47d_zr{&gg6du;i4lGgreO{[\`V|wO6pwcxc5![o62wy}b}lͻSٚe6x1(~d+͛A]d^yBC6Ka->G>Y5 o^`aXGl=|ZJ|qฬe`ov楼7?w`e/CqX(3Ƿ$n|qg٠aO~ l S&?{郐rsMuF_./ˠ;a;"6ED$_'%2 w! JR-PҁvsHh*8ҩY*/87$tYEDz11FG&9Ep]>$_k^#ZǻHL{ TJ!qHaF:6 K39)o$7,3X ͕YHW,X  wEexAWA|dS3=3MZnM^PNvTqKv' )bc1h67v67f.M''$oӰޑz"X` ll9ld#OaXHAӎXS)[lG' ]Oh[يZ,;u[zb!LY*JZll$m Lu2 S!y4^OKq,_zB )͛n r9B&X_O(Xqqٳi+ڰqطٱj\v} ӹOa NCEQP' l?"Bm[.|&8{,P\(KKF~~nbfq]d>񠆢Sg0}kM{쿇l%FcFp+MWtg@f$zI.ub)b+bi?ʏEoڲז'a0}l$B'Li+TJhkڀT 2gqM4!> _",[x$j"J܋8뀛? ^Hi|E"QR,ЩR,Ej yLqpsX~i$/Ԙ: ?N$ͽ&^$RҐ k8Ϙ>V]VI_PփE?r1]X\cn R"2rm8’BXQmnRd 0Gp_ˣ,%HZ[kF6ߎaAH$r9v{L:T,< '%1p-TtP¼<ٜIcp]'>p Fb^@Z->{6gP8WZI2E 1J2"Kgf6RqS#(>q,89>=| g/pxv7t8 6<.,$,띫}IEЇq2{JV<6nsޱaYeYm/׼jy3ngOzONq'5YgYWpDZwGF6vKl9(sgcE7p0\hj-\&Ww!B;g 4U0:30 Èrv}T2'QȯP~Ǐ80iܞ=CUS6CbA@ENŋ$d:Z|\}̓v|lrK )tۈcSEPXa -)EʜH}mM Tu;vnCIuã =x! X0 U##v_7__dD! ܟmpƳ5h*|{i*W#Yz暬xQ[i %\?Dz5?y>|(~?!OB4eXX巌ϑՙ{Q0ܙmp0[󉭴:~(%<]O;_3{AݍpE0k/UN~9Y7]? E KNq#sM5[ c%)يt~n9y{wF1dXdޖ]S/fjzOc7~!z_7r8jli:)LCͧg7ώgp IDAT^ 6{^w_gbәsy{7Yͦ-^aCͶq2#r6<]1D#44B>;\Wಙ69 1Fj4>2"3j& `C&7:0{ bMkńEoطۮda6~T01D)6۴jq4wMM7%4o)dO79xt6<}19=]mf\s*jej{r z;\yҐ Za'lmF0^t$>bAk ߆LZIMu ԩdq)$<6O!2 kGDܥy<0.gK'5f„vKAy}:yعy7;S(8.R=qA\bieR[!)&(B l)%s6LƲld %H<tu6=UvX50ffnnK>k|[,u%pCXlu=Ų uvb˖pBY4p] O&hAI<񸃬ֹ\ƎTq]9yMlr㓓w4fqr8()}Å[XN/GHP|fz|>_B\1=E? KS2Bdv{6Y3pgY^`u59h-ifm;x#G·M0gs$-!f?IaKVC\;thDzcV\K>e:SW<#F6ܬ$~L[ \&mds '@k:WEk5&!t@;_PHq+G~bv.\:*/u={s{wvܝp'|>M2iP~I$')kn./s4w?^t0]Shp I=O s]^b칼\~¶m׭}髵owػ.zKKD_.uaD)&u4LjG+hgUֱoe: hI RMh?$>&Ԙm8GVW)"ҧ%er2 #E(\BȜ;522(Ea|v3 ;FЅŨp1 }ѭXժZFF i<N~P]O/| v1!ifm/x,8y S-qc<0Wa݃ RuRM;v; ˆ >Gm!\DHr9%]'$5dDEE%p, ?NB휻 o|>|k_n;g}^Y,@Tu#?~_J_5_qO;`li(7&5uN Yۘ/b2, 'X#P5tj>:[gOp k")kǎ{24螑15dҫK sDGSUEj\g5)oBCWP Ӌ R@5_[Va4Uk4#m7̃"TxaV-iOQ]p8IX[yΞw'jsn{F.GMCЧYC,+ C fH9&t{4/P*Q*󓥖^fF~%$ZmLOQ.S-8<8Ǔj(P&yVhI_&+0c^{F;6ƇIʰ:?) ø"(CIs[zR&6QXUs;RJ)eDaFݟhӦA H& | >CeaG•D=6~f{|" B%%+2(!bؙԬ/lS\AF5[i?_S Q*?9|\2c:dw:E@mcٚ}ٚ.n(g4#ٚeU8<dVD|y]V䓌WO6ږf$1/}wVxQc_6՗r,䯎km>Fq5+JYbW m!p<C9Gz?Tzޚ'ůH{}v?SImڛl[FZ鳅6Cg Ӆ|>c~?2;ogE~_?LũW o{IL? ܞN24elQ< ΃v8Sz 7ekdՆgQ|{x[(>o|4-J^2:]dHlZ 6d~:NCfU;V#[ȨPF{g ) ,$qL_۔mJZ6(->ɷWnq@T7qa޻\~!΋{5%1t%=@]W3n`/öYRhJDv;&%(#T lO}UW۶٥Tz![ݽ^x|>NQ 5vʉu?cZiKISS UsQ.S.xH^ۣo}j󋋥Չ;{s㖰wQusMvvt[) کXJЗeDX1`l, ~socc;Ǜ_|!Q؆4*;ɲlD_Y~ BYJk=!u.\d~evBϣR߀Xj!2uP:euj幎hqcnS\*T |JG?{vGQ*ժ;>Vގ.{ֳ1Eef ;&%n ?70hPMt>jH3ë^SBd"gWnYPҗkk׳zwJX~rz_Xq,l(ZַV6k4 @A%*I (j2(uzJjbUx 58oNSDp)2sƾ--(u. 94ML_1Oʕ)֩Vpp]JC2]z=9 < taO1txÐlϧ̞ByܧP2:*x6t Vh4aeaI CaY?Ɂ}[~,gJDzmED|lK{"~ߦLVqʥ$0OX66g9s(Q1xŃӭQ2 Ձ[w e.(2 L*ˀvJXdKiJVp{\3$ ?*-aۖ̈́<*]f F6X$iS)YOE__|2I;3n?'/gNjSrJ߆3pA@64{d/s`f<_qB.avBUݦ)SrJJ Q+>MOGzĉIJ&Vi wLCp$?ٳv{ݦI4Nyp^hbjrq~FgoxP zܳ:ժ?1!ѣʱcru`]Cw KNC|hL+0Ky*<4O6X(0bXw0ٹC3USSncǾ>7wiIa\BJ{`q) 6@DwhuFiK`mpzHKGH ZDĽ޶ &Za qB-]Cu |q;x(x/HeHj\Tq-MX5Y8z+c|쟸b4 Tk1.6: '<"X؝,K7o$ q0Kk.dn-l=}wT6YO br3][/b;!Bb8׵Z_nv79ti_ƅ ;-kFA%NW M{)%u~gxSXTzuWu񏟟P81=?]}uV?}'/}z||Wu׿C nk @vOp=[v?hufnwsFZ?;aQ&ɲn& Si iffرrj߹ƗڱڶN>{((zM7G'45~={))O}3Re˹ZJ`F/Z1ӹE0&qi  -]& woJ+ 1s֗~X5U8I| )H?B;%&K)PA6in1:WwY룧bdcS@t 1&4mX؏38~TmCo* <\ڙJ*2>JZzmJO 1.ػ}{)1V N"CjumYZ ?ӞDM_FB!į;s-z}>A*˫LM05A!ϛ3Wh4i4y$G=KM,nbV58!C+p#RoщX/W% X8QgTcz`89wx˧GK),e$(O?CO.eS R= .Olwyv~|Mlk6IODf-h"/zcFH[|g/l+e~֗DV1Ow+6➬2 \D4P /V&!"lqQdb'fr^KmmW?f}/W͚MEBV.xƳ5?˯?Y(bәWˏ<{̚/!?i$^&64@$(~&kQݲiꦼ̽ug/_0gɇ IDATgtbDO}nk-19Ӡc_[|:EOtXe2[ UVpUpO 2];d5[y?:[|'Ycvcǿ?'f>>mܛ8+^xmZO>e9YV?ּQokjhYx2M"IFsoU+Qkx)Y kh2I穏 "",}X@Nff۳sseq}mW^NLDBYi6_PϴZ5MZY,Z5u:jPGsHftl׼]x㘘Z/Gg4;;MN\n|8m׍:4qHirm[oCYzŵ먡hl1 !v4˰^eHoI2f 7Z C&PEg%S'j24-mYAoni  Pv1tNHg +ET^q_ v] Qa{wp6,6WFI.]Wh_ 3F+ LJM')Ib  cfa5N↧Rɓ0 6vTզLj=?y//qJE:]xa6*p<ݤՍƧDssBW )/_Jk͞h1"dvEkİLE[;¸EI(L)d>U6JGu|0 }>ㄏj% VF#F6rn*B:<#6'[%b\c?]r_b"Gv.n!)C"P'ɜӷHfR~J W#*h [\A4"ѣցę|n[̑Z'cQ.|0,(fu=ܘG8&gY]\: ~Wtsh)D/B%:== 4{R);OPǃ!E>Ҁ׾Wjn7,YjYƓՒv)y ReBVkѸI M-]:NsIF]uڍld#ȾM)aL+I.r3Y:;^?{[//a֛(2@ЅP {GLPaQ)@ ZOBu&2'1C.; nWW} bN{Irsٝ٤]mV+ve$&^&$[D[ؾ_$l ~$@Z석<ҹTS#!ý3 ~T (SUš)~B"ַk]CC| @Jo1I&uU̻3d;#Re̢>kQ);Z:A΂,eSkP(f((hk;kfe-*HxB,aeu4iK%j .jD|x[f"Zc݅gDS*Ҥ%sg4gşiB6sv]oսF+hL!c(j.5'.7;u2c38~LءJ^_]nr ׋$Gc'qDx>[ַ0"?&P33]@g ]јB~Y.r/_q…Lp )xaڣC;ùGy@ok[hZUۺp/"zK{An=P|>}8 ?C9\7*rrxGc~ڵ۝r}-seS(P(p5EON󙑑pʏy;??r#B8K?鐑ҶCF4[VaV* >:'w; &6mv|uغo!`z`&.x8h( ڲR a Ɗ ?ʵ|z,c`+CsRd\[9VڡkmICT=M߭^]:|` ^(͞ЮH0ڊBiqo}[i=B:6EIF:ﵟ=97l߱ge rQDt l}1|s>qWRLM"Yv7'0Oӵ[Mg7oߩҞp! vd1MȆe$0-oѣţG߬?Sj]hА56,0V;B,x2:h't+_a߾Kw|Nɶ''KJ)|3 ^0[.;fmoom܎gߑ9뒏i@F Aoe )XaXu׉sIX:.k zk䥗^5mz,0d6 vas2Z!ц00UljQzw漎4G1&ϊ0HG")4Sk #09{Dŵ=I6NFN5)n_gK1!VbXeXU" ep迳0.e }(Ac hz7-O16߻(NJH/0Sj<9jFI!n -_AJC WLi Ԇ⦟Ou67,aw|4*5G[i5歆<|yg~}'NGnc Y'yuZxmALYf2*s C:{6t!;ӔB 7XWr=*i93UK)F"Y[ђ4{f!hNƵ>frPfX2[b.4QlDz è?L ьO »ޝݵ+kax$N 1(^#׳ĹBmpLʻ8ogv.1(gٻK.98>$kv%qvtzB!S*lYʱc} <{72T `d⼙~*U45}wK/ihNJ}0'nxqVK&OWM 䍨0i|e+PR&''_>aB:|5ѝØ:w%Q;6L؄@1쮏`N/3׼}uf^6a5+5kBtIy>DrɊ@rʠD '8207c-G S X37RchUȖQEg+ka `xFf|K1<:;8v7?|e!"[R.jʙ~* W}Ck` c}+ Lz,IY .Քo_03Mk*dr‰+9 ~cf}J- O`k J B1g-}KĢjP5%dgACf-E}[ַ~31EJSףWp qO0{K:r-9B&Zyn=6|J%6ɮі{ Ot#~ݵ uZZ% aWB~߾=ݻvL&l0 VFꈕd+'cǖ}ÇF/caxەW\tQN]>׾<ٌo-4Tz_~* p_Z}GLcqуn{'zn F33L6qcwNRp{LϲU *Q Kf <mrDC `r׿0$d:*BV B(q,+PH dO/9z~L˄4/ Azmr%B92b}̤NgևJT5YLP{~C?OT9RDַo,ǴJATABoDhrd4u(3TNJ=}jaJrϑN$X"vtuFYh(&4Km4\ _:z49٩-Cnaĉlmx`;qpA$9]ѣn}x8UWޭ\[ZZDZ^=?/o}뫫+v3d%P,@ae1RoB`֠}͒mQ /) ̃%J(39(Kb|MKGL i@޺B* fmhR&3C9W7oM,JiuE{9)}=-( Nd3M"iJI ]2i4 ZѧTg+,E_*Mʭ1pJ F_p*-"9"}kD5uvP_fp> q$<&¦Ƕh?rv|7sPHEQDIiAi`WLRzQ)V۶Yss~u+cc u*u9ݻgpap0dpWn'(KTI5X`LRVlS:H2 Ǟh:߮D"DJwOQUufQFwx2<uI-㜰-I2' m"o+m euz'kk~rz֌fu8Ԃrc{p"술JT'y2nÂE#DD{T묮SrqW[{?"B&C)6fuo&68.kq$ 0u $pad1LW=; ƋaQDA7fiҺv(ol䆎eY’B !N]I/?\5!\~>"YK < ;۷<kLyzk7z?<)76Kֺjf}%5,}|6K"Ng^2wt2,ћ:8ͅg7[gNl)Itd!:e77kl˻̷?uͰ y5?U^[ncl. \?}pA*;J(JS^aoSӵԯ̂~7uL~tO|'el~.Op׸G`\Qf¿֍|;NzW~$d-'Ip[GMһLONo ^_-uwyK,*9]~Oc/o[~Hw5>U?˯0wyJz}w>.$ui[7Ii.=/Lmħoڮ~"} U;͖|TWh sG}sHO73.S؆aҹ}/?tz2Ck9}1dV-ƁT&-tFI{?{8\삓%b^,i.\B1ٕTniׇ5xM;ra߾}gr}\mj.0.(+4V|݈.Jn雭8a҈i?Qd !k:zL? N[ra} jH]z뭹\n``8*ǎG X-IV::Qi(jdx%,v.j~+iԙk\WӪ"9].AiƀyFiEώ6C̬CSZ-*Q ۥ eCo+rx.wn;'C[V cG} LGWK^i Icɢ$^: :K]B$-}I1`3+>a`%a1X* Kuo|\(542PP%Bj_2s`NU%`bzse5qs[fz+<>QPEXO[!V``.Yit)aB8eD`]wM%Cٚ5>1\s=>oaJ{R_'S(rC*h9n1v(,tT>鞺5o[B;,rS27fwe" ,f²Xqu9 NmAFjKS9ՙ93% l "– W@ Dľ3d2e|_saaz0?W=al3 >XH[ML^ D$BA4^\ ¦eVh˶%HIR1B\Kfx=.gax\;"t,QrAַo?#XS! `߳%&n lDo OD:BR=Bν);{,MvaV PHG_iƤ+cBx`c K{3>.J(R p<,`pR eGFӮ0|Kq dT`rvΞsΙudK{ 낾* L*.>d*2U*{lԗ 2C"G,[yWz?S07Nk+8dM>"XP:\[ҠhCZ+1h /j\}`yy!O fD@( ͼީ)tIn&kh]\:B@8YЄ{)q D4(XO V7;(eEa,,̜>Lm+NSY.#hwWđ8[ =TDdVKv@޸4Sg`]td㮬nJMDAq@Xd36~fh(MWuאa D` eEy͓ӯ1bOgO+cl(=rYyYO=ml͠ UrRDRJ"FE(b3G-1HGA&l 5룍Ԍ31[NKn%3:^*DQbf8 +}h׷o})7C3az\ S‘ӟLƣE8G*ʿ|4j%?7utlE#bMuzO |éj$6:3,8vq'l"h.Im,H6{(p| l+"JUh,ZbjpǎӧۚHlh23eAH"=XEOu]-!P7tf3%+a֍@JIi "K_ij>\WnQOt4i6MΕThkiW3vLn3.6430]ʡm.HhDƄqK*W*Y۝K Q,dE"+%nDu\zXFzE 9{q|u:QmrV mcc(6RHi!Ir4#y<)8V"ܝ-3?yנFwf|oM|-dp7Gc?lxͬJ2:% (^ щET$HDvDT׫[r[)`7v\v Xúygh?F Hַoq=VOKLED#kJ8{9C "4ap)vDI}{Ȼq ssd] _!|Dh]S#)N5AmXp 6a'쁺ӄv3*:l Ertf18pN EJuBlvg&"׽0 23̬Rxasa176~f3Wrn8oփu;}#˼}wk-V%1xWI2(h[4BPd6P*FD4Fs6۸w2u'O2Zv3b)urA-("j2[DSmjk bHZG[5:a| h%2!MCs֡ũe/\/=`RS!S&X$TKOmc[d]@˂B*Pf~xjQ̞dBEi-.rz\e\2k0_59*TaNփz$>[BDmL 7ӷJW;Lw蕫@OvX0%ͤEE࿥[-K=閃pk(76Q&<,)t":ky閻)W{ o'U|+祻sގANs%ȯNo-/O}W]F??_̖/.'wuĢޏ&jC06C_NLze'(aKlZx?|\R͸K;~c哼 k40b/tw}/ܴD.uV*o +]SGn{6(EH&໐= ƵRIol>ikv҇ ^k+߫C;0n53yEWRk%l(oɧDKN̤'-PъB\lᶅpB8/Gc@E \DV'%FO?1ȇEEFx._td#T:*';q]\meSDńv/IY(mr9<|Lfm<!>+ܓMI9Y 7BQM>juff/AC:ә|~=9F070*۴G;YXY A9faӬM*ofw{.s@/lhٌ.qKҁȨ31rL MԠ DtuqYZqXfCdƛ^H:108˦CZ0\=j($kuf.pWB%L'JߠK!U|#D-űxe{BzC>0c D"EFXd% GbX^%@\2 `U[;# c`X$z3r d86"ۦ IvC2mk?4&1aua]R+#FFĢ9񮉐(t00<}{Px¶q]܂6 c,HI"%Rޚ $=.{c2 N#w]u6Un\ ]ַFi8~&S")E=3זMZ(2D[\Tۼ+zqy=;tA;~0{/i߹mutp ~=?errR:<8Q.2؞u Ql'rLɓՇ9E98hVԝwƲM|~]^$2W]&`"l"3~/EQ"5u{5q]^ ˻. Pi݇(!#B=S€fSmVEfMI5-zLØ(b1O/W"ckt7P@}|";W&-䂄zncaekv Mp&'I{]mj g9tûxЮڶ+;_/7mVd) +ZZ}Q. ll9V͵YɸK ,mrVPz<⥙kYGhWbjgylcdmmP]BN}-0>DdEB m|vS̞`a'p#,ЀHg@<A? =zѣwBH)e۷{`zi3r()R S CשL)ٙ׍Ӓi<(ɦo,7uMd%a}Bַo?v$$+:*&'USޅ[D9@FswJK\@jiƴGsď1C%3iW źt0$t:mKev5{kGnfg ga˺S;6rApa.ı(Z}!FX ޡTM{}OM\N DD.Lvh>}k3%R~{y6 zU?U4Z>g`gZJO O_[^ӵV'T"dLѦ{̻4s~_Y^[M|p$r[X_K|<}3]T1 ䷍x'S }k/ w|Y_/^l?˲K[LdQzCON.Ŗ<|Ȁթ;kUҾGLg|'6G]li8 g>E5u]W 66|˜ǟ@05i!!u=>d Rl`e=/aA3 P]k1{睳{7}k$u8CcX9FϲC׶2pq]SU 鴺D+DY&RJ:i;϶=3nqۥfyp ڱ aDhQC ˳\|k]q?i84pW%] ofWi?nEܤo RYeҥ6L:-vfWhs/ $C= ˬ (B*P"Q3v[E4RX=xz`Z$Gb7?3J %E J( m#5ICS|4'1tG,Nw|f(0<&Y(metm $ñ̒߂d]%,|წq Wn g/"q, IDATᴡӧ/œ3\6٬8gNTyeG( ([\jۙf3kٲǩu*tʎZC񄙺qGLvNfnцєmt Aַo?Ƅ wڕ#>ktyI?= #&'#X^o4n7rwq= #p.QYWɻZXxcryXܡԹ1[XiǎQ߷=DnoY*FywL 8k.0 ى$w?]uMl`yf(sw1C]He2sǰ,Om lEP>yn8\Md|0KJm6A{f2Q N7δF[½{ONmZ]yy*P@jl'DˏڞmEas3Bˇ bdi+mюhv:`av]@e3笷^kÌӟdNI0.3E*.>[ַxJ:) @21]۬WiZVx^˻N(, qq%ª.hlj髚כXoXyvս;Nq-a>KWnZ\./,- u~XJ0Y?]=y FFg9}zWHnO.Ǖ6*ŻuYRy"C: x'S.ejP-:`%sAT9uJNE#jv`x}pxq'@4,?eZx+qBBEhub^Tϼgؖ ўP0;'/+}^,BQzbvAT={ $Lp`I0[2#0ڻBX1Yh.x)Ә0yr,/v*t:(JDBHٳ{9O63AL.4Y-sX9]X\rYބhCQ6ILgvMec5. ( Ss8s.SiX=N})&wagYFX,Nv?Zd*<ލ.!Z7p̀o3e d)^th-2v:D]+k+KW7klQzGWFƀ-A3^b5N.byr$[ַ?]v*A&N:p"H,|NnEQdG:F]+1A$jZ&?HWI+ r#B 4sy`p'5M8hRXRg{=]woE~{z.PdWpAÍ?>i3_꙯~ՙzlnZoeV?At ZM"[d^`S8h@CĬaʆjHF{Xkr2^]\lg!J$^!|- *u*ׄƖ]xCVCp%X9v8Y,-!)y[\[Ɨߛ;3;VaJk!L Hؘ $2HHB(A l@ Il0h BB$Jvvgv'n*]]Aϝ7]u;TWU=:NP1P 4&Unŗ'BvK/{<%]qy`φ$g>t1(9Ne(:*Y";H | #w2Oŵ;@yiBc_ъyxљ. #?ʴ"5(ppE~N@awZ#eF2;C~ /+)~u]~|^&.KE[k0hk|d|u%5_F=_{^ Q|s0f)n5_sXC.ytq_M19AM UJ~!=_#|G?1_Yn|n}5_3|2L~L#Pɩ|ISpQyM&;n+O@X<Ky ^sġV)> oeK#}@kw 4Xuk @}s[mƚ}0 ̛mn_Icpx<#/\}WZ[[rx΃v[n;ǃH4Tjjz<ܔ,Swlo~c[=f85@6[09N持B_=UL,:.驙TZՑ')BXpיlD]='F!P DHtzz?Tebgι2kSTUk ́.";!2k<9|k/amk?8~34c5V_F+}ݧp \aF/uroTK!4f i/Itح+կOnvamZ\f$$d~̓502]a<{\x..6@ ^9shrtqLT+7< ld}Ǭg˨CR:gYu>ylr(JQ+hzj/W2Z$<)Od G&$"&י8 +כkfuRTϸ>ޏkI>m:QH'x~^m(`/SSuu@Oتib&D&2Oj'5 'L.o9!4huq0Ɠ+&D.o(y1{IM;p*M%mw}$7>M7UG>pbG-noo}Cŋ{W6n_ZbzZ>8s|"hom !n;RQ謮 1M "`6KMCcz#Or̡:qOtl5O A2=oCVAţQC?΀;ؚi&+Nv;2KKxԂyNDv KYoD#'{Uwr6#uMnNR[OnPӖ3DL#< (&@ܓ"A `g$D&3WGnNU8 :@&8ѯF\A/=d@Ld"?d?ec&~_Y~imYw~V)r(n[V%e֋Bj1Vl w+7}gTF)77cGS޸>ybʕʙ3Ca}UNS{kkN'tǬvUfϱtI6A!iQBpէMs`sW Kqq"D[7@<:$eE/I e x:LvLj?h#3F{bh=T~Uc&;Utޜ౳zx#Z[{uwޥ #2֘[(Нݧ(߈(Sp" ƫ O#=[ՙFdOd@>EyC )VJ)$j3:vLq_TsqǤHl5gLX,;$.ep[Fvd4[ /QiryfBJ]4uC{xhUKJUVh7[R(OplM 7ͺQmJCUbzgYN?8AeÝ]Lj W1et{*Wٺ@2>p)l1ܦΣWsy#&( }}f2do)b' jTGzj?RuOP~v5>|5Xnw@{o>&NPy6KTLd"D&I/4F,AQ. 3 !Dg" TiE$/mlzH0|g9Hl8Q9YcM4(Aҗ>/o?,qG~t{FY t`0?/jZl0ן!('^Gcp]]wPTIA]J=)CRaEa-@mϋ:jNw|?IMtBTB;u:[? . ؼ+l8ޡ֭q+d=re7tib9Cyb ?6q۰ -ZY.Q][Y\àPɶ|P)-fC54B16kAW|җd;kgF+w> Lg4Jj˪c7vgF 6F=h VB% )JD/elEkơ@yp=eq>AmSv%:Wha/ 9~]9^=t;ܵJ.%8܄=k033-Ad Rb' $UIU hyv_htfnJ+$Co_06(SiC.yB3C&~ 7}i*n, ߓ`BQ9ܚ+|c &7_q?s⟎ik|/rq/K|{@:$WܖQ3Y ;_HS&G"o<}?q޼7[ou( g寀ܚkk7[5Irc =]>8u7Yw^-3?ּFxSG~`Inp2Ϗ#M|_{L>SQ+ý_{q?8h@RfY)ӌ<1dRZHq5$/> 7;37w9;+`WC]|K4*:̫^cgIk2̷mg璛FxS2`ilR;*[3p|.<] |7«_ᚢA O&O*\/Qa2 PZluh@.]i(_0?V!pilWxm!v7p*peloO^o>u󝹚kIx{[B w+6&]͔\ڹ_ uh .g+|%U"+uq]J:K@xH4*bԡu&͐9S V |um0 ޭ ժ'E3 zM&2|{:_i}$lK Ok!(=UUiy NUZL5c>t M~4׍ܺ7S]뚧OH'c=zӫqh辚t , ʿ+vJ>8+ oO{?1ܦEo4*T%I+x]_{NU˷iSԿ:])=5֣XrЁNA$OkӺ*̉Ld"ȧh+i#/wȒ*j]zԔՃvp;tlOvOaU $LC`cb4R[[rkk{jbom`XQ% Qsb3%ÄDƷR00r.#V5 0#2n+t.C˜bo*KḦknpFIB;M26^~Y>S3aUvXDbtLf~DVFW20+%@whaUD68~ql>T7Cx zp;:>Wķ[?x€_|߳) o%_0?~nOsx˸sw\Msy]G[|wsqk~oW-; G~"E˛W~@҅;ȏKq?|Y~|C,w 9_oݯ3o=K{nwȣ|{4ޝy}Tc~?9ޓy* gW9n >F3k ovG(?[eG^ O5<ߐ~7мW=_|3eoqF)Sʾ̴C~)o8{ԅsB?y_|9>*.M_˻72)KrӇ"GGkUakaWqߜd/O8|s7y[Rs~-O8 ?5?/w5E bM ĒHК;{Hmwmf_nNKz(Wg<(}}/,YbPiag]iGyO ;r3-`Z%7ZsPR Å>L?O)]*qO8$vn\D{()W.sIS .pnsFkVJQVR !%B 5LeX Q=>J(JG z\z?;lBIn-~'w^E\djaJ Ϲ 1lg+lLmmy]XnǽAܾr^`Uuy2gwA$N|)Z ?rfiT^utNj~" g1f<~to"'?qzau#hՃݦߪZ =*" &H}n nLpPE_Vdv7_g}64$JQnh b;^CiF4%G4'OgwFfÇ):m?f!s"D&2Oz 5&0+)[դb$WU!fSͷIbRWOVRL!)'B;OHiW LSai8b4 >-L  xG.p~t Ezڹv6wB? ]Yah4(nCS̅Aw%"EL< 3DU.3z%R>ScAL]vA^Jtȩlw6fЪ؝ZsBIA3tA,'|&_\LYݐ].k10hgxl̰xƬtfXj AolH*R2qiJo0 L >) 4aUl6sc55Prp)ȢBeiҍDOC +_D3TLD&2LS\"Ad6QYC^S1OlWA*F'SHc.D3X K5ce(Ȯ .ĨM9ThUFtu딛1nG iDsyE5y㺬iv: w8j6[̟ͽ=:}rSzzn1VJgˌ"\Bh8`Hh"Ib{\Ѱ}6hJ9nM39\·#;TH+mPK0LyҜt~EˠN`) gA&$+fēJ!Μtt%Daܙ]͸-g*) >r{~\^w煽;ZvNS:#yBMlw DdAq\,n خP.3*{%@ItCRrs=v5}xV8SX\cʣ.uv5D7+8UH_;G烩$oL:7$ɮ;iAmStk(2Wt~ 4ÇWwPo uD&2C!9:x4<ş,L <@514BH_q Z-F7\3T?FD@4M|ɜxЯZ|M-J'}6I0+d#oȸAq 8=ܣ2"أ0nRE.v@]yC~":IG9;2f[3ٽ]VyBsـhVõ!iF #F=6S5:L6=B"j; e%rmX;TJ!0˟n1L3lQfZ("|+ vZ+ mu# 􍳧OWJկҗғBJlC4.>ʅg]9=80.U#3nO5nv5gr;SN3iͰrcwN DjEF: [O݀kunq.$& <)IDEY5= "AcC%Lô Sy+*%ҥWy>k^S=q]bl./``ζW M~ioVZAH`_eu,; dJG _Nh:˄5йrڕ[vr--o6MM,sлt{4ߖqQfCyN@%셢y[镆lU D  ]e5 F m{,tY챤@Ƞ"Hĸ{}WuQpp9̒=K\Ď˩4K0M:Ez *Ԑ$(M󁞊D0eܡTk:OO߇b{J7e<(P}hqOd"D&*[w/N-68ixk@!jY ]t m Gί5$qYA,!T8MVv%5U r أ h`D0u)6=v` Nc2mN*^v^U|o=73xQnor*|Asg6!_5kN?3ש<^wܚ}g)P_LX8O9^@"|6lhܵ_ǞP)N;%Zcs91Rdz8P܀:ic{,ܖgF)܏E#4ꢚ%İj)Cw)'< :IAV/LRcp'3JY17|Z0n2zmASj- )w[8κ#D֬4߫C2-r.gln`L!I5'tFP')mL՛ׯ޼zFv5=xiϡlcT!PTRΌܙ9% Zn8A;DC=hdVy ƫҶcv79BjpnBJ* 9$: Gl['LiTc1-Lت2LD_\\Y]hF  ao$6Gj&IRoRxK b{;ufrjћX&2Ld"(ޕ{'3bDD(mt*O*2Ke1{ QOF_Lޞc*JV5#3;*xKô))κiWP .>:sk̖) )CSCAXS&Admp-{/t]+~МNtg;Z,G,N8:zӭ" +);,(B).->S !Ml֌_ ;LWr8ys]Btkq{F[++[/!`yYH橻j ~gnPBq$/?C\ 2kN+ط6״sm(82͉2}PlF![Sͽxܝ;3ʍrgf]6Vr =*/#YqH쀙m| L[60ЮNUx޲q6:L!sSc>>A =#ɾOz3013*'bv"n&$K!q($*Dd>N=N wzĐ# X)T K i@t:և3sNcj ˥~i,x\1-#pXqTTɜ0G l~߂ЄNj f4*0 jEz++x !˟flatX#^P+' tx' ^zoܲ9jD~'zzM`NOk1vQ]\L̵ IDAT0ܜ ?FTlth&"NG<~3_Bz)U9#2vaghƗ$ <Q-i C^*{}f|f!$hElogQBOԹq2f/a/6NJf%Y&zmtW?>"AQ#?}  ҴrC>]%ŅhUzv #OtdM{`^PU=AEv)w뵦D&2Ld"`逺Tb$!=!V!ov&u~S' {L0=3'9 .y˞6RAX)AmV*7DZGO){BL]diD!\j013 Jׅ!8 uQ8Gg`s+!1 JQIb5 ¸Z@4?EjB;+@ pX~T*QcWj1u0 Ah9BZҴJ^+\?%UtA@@`m` =tN! D(E%ZLGGKu9:sn6SX 9tnbmU淙q4E Pot:h5*+J[NIWr!j}Ԝ4(%TIfSi(Zۈ å+q=~Dh%Ġӭ h;}1Y KCW7!a>%ΧoK||#?Œ"h^hBmRJ(7NLXYKf&=xDJa6v~y#o,w)EN!\\9*uʽXkݪDa tgƼlԛ]gs98} ܉UfIf/X:N0|BeD#^/O;/au`_RhJ*!aziyT$ݴX:0#hX#  ~Zg5fӍ-8!w:bRL'QEGi ѵpN,} JB3ĺF`h'=@H*=U=_Rn^SHxSp?Lo4u~K|cv|W闩Z8gNkΓjD^?ߞtSp3_D~EKܑ[_G#B~]|=EyGf._I/N"<;Ãev2Ř+WĽK'K_C(,]U`'=6\(p }C_A-_55bf'م܋WGv/\r˗F(l<<16GL$!KB~ctXL"x\)-"/K=9MooO D6F t40$̀أGmYst昚ߤ?EC.Cc-$S  sVEۃ Mhקn9.)}ܝc2KǠWWN1H/`?so;NY \oODY3K:קfx(nPAM@;$c?(M](P]z,J)IITBTڗu=(0H^,hIEBc'O5 <b A؊yBїT$5AQiDQGU{,NM"hKYIl5*X&`&;~ǒl;zDdbD&2㒘Ϭ$L.4)3뀼pMk.pi&$0H2Jj|lɍ|$P.w Z(d# }Cgn ug6O1hү3l2l0h0LOCsW&3,taV0.G:YE> v \KH-vk8tJ!&N9ӝv6wCRy([9rRis8aK{۟|oee/gLk^ץ.vAxt~y(I/BK'\##0b 9 x6e8ܦ:)7c7ӆ:":ŸwGdAIYyV,riR )V]\s.TI 0zRk D֣: |tE !bM>*dCaӯ&P ,~؞䒡`QOg`b |]JOELD&2L<.z4eYaaeg5-u˜PL祜ךIñ h .zA Y]nu%m 1$$ SsuMuF 5FM L0ej8L0{Gh}[`S]x?\ c-OG9n,9J%jT\nq4Wj/|DUњ\:^gJ^)Sʋf+jƵמڳ~kYP! .sqKwo-6vS~ȉH7_4uxӦJ._oZPUO{P| ֑ͣ;F,K14\kl@ ͙V D2;#VMntƭ2By8;b*~nw/x;gyz?}n>@yc]lߤVL:?)$Kq]ZjKb?Q cT x Fz?w:G/5ElYt="sxU nHJdPW"4 ;nLd"D&NrGUR,09U'RfhLЋgdr?BERTdiz+'yg[2b=b6\C\oms\js]Ii65 d;i9srǸi8ancucs-u^RWutmFN͎zGZGڞIof7*+WWV~-N\AEMn= 4](bإfl'۵j%:Lr;SDa 0R+IH'uSGA+^Iϗ'l:ļ940H%)Fʸf,m4/5￴Ä+hpBDi0鮽jW8uwޥdH2- 8>/Sr^>Gx;˿wkURBϘy>Em) zmtn9b@ɥ|M`[o'趋T8%_b}Jn뜠L}uU%UOLq^QɻV|4c]LǷ_ܜF,8[䓮qnz%qORҼ'}W`/r\6ܒ/-IWi^kxnZ$Ws z@}`Гsu3uԇ{8"}ziJ8%ЃMMoUm|[ ,в topS30hu@)jESK;Q}o^)N=^( 11"P@%a[>_,1*@НlOYJ !n͏kr Ea ~NN o2&:W2c+a`,J.V:ӋmΥ/h^f̙jZvzdD~IО5 $^/N'%wﶛ^,,9s،9c#$D) krnYb}zW¶щ"04ױv@#ѧVz#B̅JӅxNV=+WB]%3B(]`jpGDk[` 1+~'!xg[02hEwI0 ْݸy:GѬ{Dia( 9f9߲!(tDP}tUB\Ig=w"=hHB9) 7S=uO U\aΜ;9kdy̞vּu3f +L}D'-RᮝWX0th5`:V`B 0Y鮤 i& 'ڌÓ¤rZAxe暑p=ҶC{ [͡vk4FO/שwoyPHܤ_dN6ilK ٝgR~8P3KˈXfCׅ8!a-fo3b o4P:< m-%(Jjٱ.L %xL! C>.̇fdwxJ$N6韆tPf&{a⦓V'd݆nΧ 6[Uea y,u,mUXP/T,,wx+՟H~,+NfmaW<ΗUal5DbAȔj $|ӞP[7.R ֩uV&2'M9;Sd@}Ɯsg+ɵUҚrW-(ZʧӡݢdƇih0-[`@qenIY8NJ[S*:o A-S+v]n+'Nk)kP>T{g```*NVOβİhr9 Jd ,$>q7?rL X " ([xy`"o曥Hkqf&E?C !$ \82H;qnM'p96q). ~MvH$&#w{䧷z_ҐL.zCƼvZ|&V"uT|ŧ|-&n9l%<}Mm~Zm5>[>|P>'S}z˧ҩ;W+t˭zV ~o7Vm쓝D_)nWq.>G y0A^Q.vA-[u;2zJ{%3|upo V/^m׼xm7?}]d+~@oKP^'/ʷHi<lWi'>wx)[UO导z<;|2 ^Ts}m#_Wz9zO~5N=GhiGwk?K lZ*g'OV iڸ 'ԓgEō-'o6+}6?rFiYVd6%Yj.g'3w׮{qm!HLH|yt$ObԠ( /D@ԙ`B|+9 +=m8 x"6,s)H=$fkG*K_eIxf&r)⦳r:ugZ3tͮ"ZԉOU NJB#VDTXJ7#Ѡav;h?%dݖZ]x'^%fhv.[Er&iv k ۲pgV;t'䂩 NW8GB-VF2t#cFIA #(p D ji^-&Y, X-Fڌ4 (d?k@,AۦC N8 -¨]x(C:!k+; LxC IDATq,|Gbw`cHDԧ/̣.60TRyJ"떼J4 s) owBћ1{ᙓ#S^]bd.:btU}&$.zYnxvóI묛qV]P%~R:Q]ܾ[ >)nZ3gtӉkI]\ѱ@Rybv;>9ҿ`;Ŝֽy|ĢnӰ#- )[$;4~CpnقrnXľ$ǒ03y*r f֙`fx OU>  h" d,[ZiO:D iT"/z}gO-ل]C g%u^;AZ) IdH(_| әsñ,P` ?t$DBe#9/)xLOL%=*5 k`=̅~%-}UcdBd9L/=_^*uܙguP}`xzhƴrc&'7+S3f͘364P/LYڹpooV[N.bj]5YXuݟnܴkh`V=ZV_ +Qī{S@S)T\8,NSTFAQ# s LԬՃWN|o7O߸մE0pn\wg4rϱ/st^{|>Zjg۠{dET-׹[ԋMSw+is)'j.RE3\=tM0}(KCSX/SE=vDp"6KUJWWOb:Zo#X g8*ּP?8eh΀|?eo_NMiT/1B_lu!8%cTIC?DZngB=[XV'ZصÈ˰(6}@[Mؓ[Ir,ʒBd*ˌ3b2]+g?ȵn2Lt2XVo@Ҕ9@YA*KÍŤ6ᙹ$yRK5)Bl'qiCVnmǵ-IKږ%m;&g=khQ m;>5`0A ?G ޾]9~_kzӻ.on3Zpr ca, .}SS"⇄MdPJUd9qٹ@'zB'JSf@< [(΂&3zZ #Q)&daH5|Juz̻$Ѯ>$2-oG:Nf($ :ahVnI|q" j^R-4%%_=dd4x*v\'EE%KJ8$. ڌ\|]:IHP]yI hmpoH nd ;jBɻKׂqx(UԩtC׵:QKaVN r52n12kʬ.e: pBקS¢<٠}&{Oa+.Z jEkE}ebb\p ݱCs[ڍ3Yr&KRFDZn}.&;LBsA@YڱNu@t nL6,)1S>ōsxK@ l3aCťȧN2O)aVLGT+X3HS),e ']PCKaaO!ىi+$.a![SsH=݊ 5≰dA~s'X(u: ^]K@ x`FHBƊB:*NNRePQ;GJ2s@F0X;,%-h rIi1 6/2y#N&@Zl,@ (pH$"j ctB:!ⷖtWیWeL& ʔDQ 2!fSV Ů8ꗞC0*I$A=c=K)̪2kpJ.=5C صZEt;!"fe.[MɦPp"# )T[4[b{V\6=˽%>ŞU66|5xj,hi2V۽zdRPqb[d0EoJI!h)sKt8Бd}}/ɘ:k_ƼvEe oԕ9~yUJ~&Le8wE>mrz~P%ny^4ox/ rqWf#xy%>ZWUT76 /K^fݎU-__rz>Gჺe~6.7M7RMVrPK2 ou/-G(Wi CÃ#|G|#uyPfUWj{l>g}nYEz(x06U!)v˅z'Oshݔ` ED7y j4U4Qpy57)!=/`ug:6 |U8I} 3#vO}>lwY~sr [>:Pp%[pbpHߜ/[`YmlƸޢdSX,WKDHGҖ,_f$o1B*BRwh=3/uwa 'BPuu a6tR'yq4j]d]eVYmL]x0:?xjU{VzqrzTY i) . CmSN昄@8yزpm 666}m>#);$-BЎX1]?XCȰ$>KRIgvh1Rg yJ8ef!^""%f 5hn)X$F|YK%'pL5\S*eQ Y'iwoMt{`2 }֥/ǎb&(XX0.JE" z@=Q3=W*nRHAv8X2(R+uOs1K&@lIN]Z3*d%ytC=R)-Ě2k,a(w:g9bYrcO\X=ֵYf]8TBEf SȽ8Cjꁂ$ x".&spa[I| RCA7Uͦg Ԅ8W2 B"yfȬK90A\v)T|*E"l>}>r@YˬFԥ(0:vV`q?cr'yxf"3ؾ4yd|#Zl7m&6Yq鋲*6 ע`Z+HD$0h3b]5۔oD%TIL]H[tZ˭kwc1$]s$h܆ DImZLL.]d36NY~d!WTRN>;@-y)|[io;-?-8W\k+zV\Za>W-(*(]h{3dv~-u+h^T25Yuk>VJ)8//ϪDu6r5nP ৃJtx\HnL8ԁ\3ɺ+ 4B^Ght.MhշX[{W\HF^eO~8BqY^x^rDMzihy=薇EJN;P\Źo,Ԇ>Q"/q\|"@/8cW^e^ebS06J'Mё OGueX?#}l?ȶ.} V}ňx.*$H@EObC"("ݑJJ0A@;#cʏ^=ji톌eQMAEPGҊH| 6־ \dlHd.P ()"u()"M RmKJpQFym=s&́᤬y`Ѵ[ \&>}!>P`ZbB!c64Ou`eVX1b5Y4ֳ4oMcjXdSt|:-ܸfӭPqLp&? aFBmmشmZ}:":i{ 30d$pGywm<1Rd$qX3;!D[" lIh&3Nv4eaHU6m#gKŧ⇴C: 4!9f3jg`````a LXP0ςKDA83ץedôoJ"%(nd'  muzKl]mJ6EMN$URI$AH'>cSJj!avKC7Y9C<(r)ٳpߖ7VlkϤrETTzUX[6|`{Qi.GnY *;Ln)?NmוS՗:Ri{n̋{h_=˩;) /w֮Wr giJ;.P;m]NQousW+!VQ[߇/Gt˧uU 3`T%5dd>.Siye?+͗ _M?\"B|oS)2REDy}!G=$ 剩]t`0[, 300000a*:m (*AʹR^v\t(AVx] t^ US"^W7ujT`g!v! iLD%fJ@R)%Dʹ$ m:%fD?lTSyn 0`sJH^\ʬX  $.KFg+đJxt! eV %&ۨԎMuaB:0md0 c,vVYYOl5UD<V Cd>M4x."W%snޡ4Kyݲ]R׮ z&=ؙ9ޡ RO}2d9ormAY4֛7RQoL^9Kv;<;5|2myCM~LO8}@ߥ%[:p ~rܻ8--|(6{=Y uۓ9U!ĹzB| w n-#uwt) z<PS+UWvZ Y0)t& f)ɣCyEh5Qm7'*.B>N(ӽ7-*cTaS̀VP2,oz.7JruVY{-&E?3TFԮP@$s:jꓻ! )g?1 nri|>N2$d\N,cSImliC 6K\--:EK%[UN_ L;I3ס8M PD.}y 4xd=K[ D%}!ȴk:̴y̌;#b`````ZժDHJ >KbXy 63&_ЁMӡҬ0ȼf]!1L*lHe3(y.)ZRf$a2AF * % G xqQ100000hx~Z7K;ωy)q6>: {|UE~П_8i;EirgI<8JmW}p/; G%}'\v8@qmf2&:LXE0]k٩02*[V]9JeZT-Kl%/L&LI&z-s ~30x>"ew2Qe&gv}d^]z̹z0Kz~׼FȰ\KaHLza[ntt/3yn`{PûtO{ 4%3GxnkE>~_9}lYs5;||)pF^wW!3y_|kQoԼewo8%4q{i߸3^z"NvW-Ӕ 0î pjYsY)zjU Z6%_+|ݲ20XqHHi p;>[S'n=D3k[ 2O!u5L]; 3[ۥZeÎ; {n`U]Es1 30000000000xN`蜁3dT"ź厬z}>_WIput)KԂޡy9|ffqm<Ӓ^4ow 8m;7^W@QU.d8H?O镇?-I|iQ<]ެo}\xBd>3u2ZkL =p,hvb(ͫQyU]=apnWErU7廽@8C}N-^%{=k'*ͯ;_9NǓWG5_[.^Cs /yW-ҟaӵy5JZkrղYe>HEJF\^'ssղwA l3\Zp _9Q~ i}Tvj Ɗ^*Vgf^zͺ7NU SJ ?uq}E ܏kg```````````v{jg`````````````````````````dTU0 Wrg:i[LQN՛_]w@pwh. N|@Sx[dNI;ً{j>gO~p~/Kx%zYyquϱ;n Pw{e}P.|K; o[GץómS a`vM  [N>p^2*Ź3/.YJ;f5aQ|*\=4ӣQ(zE BA'.jl/Za 2Qta8W: y\=)~ujӣ2*ѧ3l?b>hr*Q+hg6`0}`0 cR;`0 `X1`0 `0 `0 fe2*JJ2}YQ8}ErzðyA=LJtϿVL`=-YNo3Q ö 1NO*utyǝ\x.^r$I8r*8|V@].|L3D5Bh¡[W|g~_lMK @fxGXLd|́[ڹNz"-w4'鞫ua!UG׀ 7'5UO>cu3|.$[6SR89=1?z,q'G_;K:1 _Bñ_KLxaڍs{ c&W@  GQf9;f7t~sΌ#O>sZZQw Ύj ET`"Dj `0 i0 `0 k>&3 `0 5 `0 `0 `0 kV&"VP`9E ە)+vFc+Mw .:AAtn>7վ깘cWj|R%7qΗSTOWǵÊ?\?NxJlMU؃ۿnL.o!^Ȧ[W۾|_-9[tǟ~"Z/jBA8 v=@n 7ѻ=*sܣOFi{a_݌L=!*wTuU@k/;^ԋIB՜v]+boFE+6W`k=)HQ"Wq~<U| q^"yc}r5gG< \M SgZs<{H)UŌ KuPIfATk{ ф=߸M%OE9T~qJ#i1ռ|39Don]% xd`0 f@`0 `0`0 `0xLjg0 `0 `0 `0YW{ ]`bvhpdF 2_A&S)( K8@]CY\fCU7as{.(FR8{ qŬzD=?NjuK;o{ǩ#`g̿tsT/9\+^q.rGxss[9o;_qG5{3 W $Rr0*-fɃKsp?=S`bkTJsVgvӅf>R[z"umɚm?]sW\?)pƑP_GQCf-Ӭ4ʛPçuϭp9+{dB7hk\i(Ӭfjp;`0`0 cR;`0 `X1L IDAT`0 `0 `0 fe2*{+b9^=<{.{@EwaX1s0)7kg+KsÐ&SpGs&2Qmmz{Є)0Q:tύpb~]Nꞯ]ե8S|ɺ&vO{6'U*u< z=UʏTϋ|VOHqKَMf̩ڥnep/Zك{s'̟>w-T-B\1ͱ@:5y/}mDSuSA=FTF)<4x!Twz)4_;AFD׼/sly x6~8Ӥ:iWm }gi{~_P̟qz/uݳQ1Cti2w*d]}Ɋ`,G]T8g㕪4GxSa#TÚvk=|d]i֡2U1 a 4 `0 5تC `0 ϦNdUX,>P`0 `0wTg  vY,%x+kg0 PcMm3t)fg[Ǚ N.&u6$2Pd1=&{g4{^<1*Ml{^+rR,za8Z􈡬n99Muçmpc|sx?*( +zUl{=֕ 7:8A\ )'szvṠztWr-38e/+_*_(zEps-_T}6l"u=q 56!̇TMhv76)'U ΋H5ݔf*H"AqÊ̾kaz)7u80.w8}wPՀjoۿ랇@aoŕN~ ue8|\J#ݣ1tKeF쥛Á\P3 h蕢[߾B\EiZ"UᯄnӜ(IME\PHɐEɰǢ*̪[fY'N"- ʒdc`0쾩[q Pe$8%;q  $! 1 fK^p7 4 2i) b9$+@bS%=68?yy[8Y/IJd, ')KF=6_ef{5m;}S .HF XOT ɭlTg#AvH *$E.XcGS%cR;`0 M{sg *ar.K`%= 2 `C". [;?Jk}rKg41zliAB *J*cl) lTM`b1^0V m> XXGdf)7u:A.T 1Vh$$U6*|bC-` `8akgi:q6UIQ%UI)'k Y^ef?Qo}c{g86=1"'@7CdP$.]Ty̵O?MSc4d i)(aX$I.}13b dD `T#9]HB/$U'!yh^A`d L'jN]Yf쪳~lDmT{/k-f)pk3n0z)pd]x--s|S1/P7÷ ˊG;Xqn8/~깸rRj25:jst'MӢ3ɯof}tjAz#LB5s!7[;q ۇu@siIy5φzx8J͞7tJtz6f~7|ͺy Եџ^*An<q}bY7Gpt_랫О_>T6ߗ{ȟpfdUK)*SRgqp'cg+p:qƐ0JŸ˟Mql K5AG=F<\TWa^_`x9nG -eJ+2AP Kޔ̑NA*|T0 F F_dcf `d9Mb̀L`0v: zb$IǍ}l'Q <,' X`@mwr6Habe}!MO+)J2Eڢͦ'z.w^+sY1LL16Ad!#H@Va72~{PdE&L, iw"Y,I 2l.JAJ,QAƂm,=gj6äv`0l8aZX7IMMzEN cQ VYPŕ$7A\ ֞1L#%Z &I Z'$jz {,lr6l/B`Zuû^:9tCZ9h 9A XE%8x )XGfHJ˂m6P m_]S 'LvE&1`0 k9N8IO .[rB3F4<UX2%AE /W [2tYL_|E+O"l& ^[ AƢգfإߣ7ƇReQ~cvêqZ$d!q%YCN &YPek S TPLa8I&Y|:]2d 嫪(> G :i\ͻ#b0 Z3`r ztprň`5ϖy A&%3ʼ^"d-0m'Y7AZyA^J^76C="%\+@[QtWu q3Sͷ_,^',GMlŜ軼 n< κW۾ch?u"g|JW^=ߘ \in35`&_h.`R }S]u+8p! }|V'|]1᧺fTd_ɷDLW4A +oGu֥t?]ie-D$ԭҮn_)-?V}k))X$Rh "Xj$)VA.+AkoYs$ y`)΋5 B"D4BLe -yZ%e?߳ʸeF3X OM;gkIK q0b6u6A `?=M:jk Un,m zˡ!y}^xn,Y.yc >8[ki0 Z+"M%20!'YM &(Kʒ l`rE \Y}tIXmɆsRrb`NILi^Y8m GXMiJ\A +[ +V 1qDtxR\%~)A-Z@XXᑱIbJ@,)ݐ\8ɠ\0dœ|u w+vJ#f>BRIF$=0"#.:n5vr7Y!TD>N<. x Ǘ}m`0 dt:WؼZHY$-҂E‚gqL'bhB]),.:&>]4@ .$2V)IJeIUī,+ A_kaa0z2P 5j=rzhmA]|չ;՘S`2QIx2q +6JJPlfMwxOQjYev9`n'VO FxSG5pښڭjO(*z0I]0zF|\:Ӆ6ɨ.1g.#u[Tᏺ 8E/3f\Ioߌc3r qh)D=`.ފT_g˵폟AY Fq\5]=C|n_?q9{Lcuϯ\RQn; V1OhըDOk6mxX5R>jR ?= Lq\R3n^}bh"O#u)qβnӚsҴy *[:N7dB8)"`LE|4E.=Ox,'nx @CʧxV;ƗHB H" Yc}agw2ZtXeW}`E {,_8Xd`0>8_  Z Nq@X 6H-boy%q*< "X8!mϰ>OM I\IUJ\O_b e W; Ȇ ?`auI01g\?<\(A57dC>5g"%  KZC M4Q6=/1s U?0 (=ƊiEx'QllfHH*z,Qɞ4 #ۅQwU~XS?G(|* ?}Lhv.lľH[)pf/`P;#Z9 Lq!Mﵣ.S7LjNW2K@B*-Wy$SPRv)y y?`HR (Bz~Cffҟa @G+ 0 UdX+9ԥ"C^}@&Lba^{?tg+֥!H*2X] i\\M Ô%䢮S  yOii.B<ҦYK02 `()ґ^2h`Z,dR0ɰ&wZjF'IwBi`J%+;PJXI,A>R1F"%5p]<svi cj",UHC+o]Ke`sX\( š;|& ]ЩoQJC쮦{TA- ,&wDz>!dw/qV ,um* G$z F鑪uHaIaXU vD>wULjg0 ǟGRȁ `wq6÷-}ĕVq& +TtYj2j+i%] Ek.-rq1A]$bxj&9[ c4]3Ljg0 ϖδ,n7aiO Bٴ$lE$I? =`KlY3s~khO5W?4+-eW.N.(1qeTuE{k6@ٷIFTbTƒ˰# ,i8|~ unTT=Ȓ*v-ҡ?++j}wm6 p!#1~V*"s'z+A'!| e+S 'ku%Ì.ɢO֣s0ԕ `xqAPeƓփn][ς$Ta9Ƙ]1ZcIE"i2}% IDATMRd, u ڹn ₸UQq?F" H,ϋ$4H6C 0+;(*PN q,a'hWB?a [lX٩ustI]LKX2aU5qր,La+aBДMA` uVECj&穔UaT2 xcCn -K 0kyK5/\ d:.ºWAXOaF9Q]Y]ìvfq R/#TǵNzG}}nw\DT/kFiITK1z-/jS𳅿U#0_/e<v q3Pl~|9U/Ťށ\TmB}0OLB77l@u<t l<)=㑽P o%{^5yq=WRd76}EDE~ _lUuH) ѷMq'] Yp0<ӈDU5{&5Q{!ժ}9'E4v[йt߭y#z)YWL&QOsV].K};j{G+ЧjoW?ry qqTU#[΁>Ubi͹I8??\Q4Zpm! qUXS$.zO*x6Xv>D$q%38IF=,- zv6\*ƌ ]MRWcXicHٲLÌ@A)|_lBÐ6끈#3k+*'EHBzg,nm=Cv֥dY6Ē!de+* ׎U6i$"wd`S;G+~w LQ?t*aD ސl"v"/> 1"'eeK= eLf+,,L,̀L`0 g+h $KDD04 dagiv˰*~hBfLÎPb̭AD/ZpMd*D$NPRO4Pe5-?BE"Ra4IQK#D. jֶA63v&}w%@ a|_2ɤ8I`KnueHɴ P˷N˰H^,yMI ͊UOÂX6`)s[ӫdiY'`0 5 "LSR,me9jvPB?(JfHқ`VrpZx,,AZbe.qAjN {Izavg`AaOa(WYA!)4;9dWe\>y6tae*' y#1öG-;NC5\)! Y8`f>0l-II 6q5Ē>d%e _44eRnGZ]v6(H%K%s%N(04.`d W8ȡZVl/q`1!toE<CΌU2mH@/dS; 90*kon0 ڇl ]$S\` O\dDٸeDXr,Izgz[Kf)oҒQބ6hMrLk;NMEK Eng8dY/uMh̺-/XWXvMBV!%U2)% 2g-e؊?q!L($N43:Pp&Y&a_JI6bXeu,/Eq~vs6q0FYgu Xld7E %{<%bgM+x0*e=wYBwKm>/+uCPX~%tϤ"pL eykrz6U>{b_w!wlT9z?U0~5",WN8[t> Ы8GER6Wɨ(:{H1L m~9'nYs?ws Rmj4N8gf]wj&\z^fm-ENn~xlT:XU4zZ7O=|W8{~ެX)Gga%=V4 /TB{+9]i:N%Q6 +St]8J1ohCcվӞ#R"GA_iW*sWgw~Hw6.׎NTP*8E ihvU*ƽ% q{!@/mou^Š?8{~8ӠK| ;@uTfF/OX48okbOإmr k PJVTDbTƠixsEO"$."'iX;#grh+58^̋fKޭ&~!vBОbP#4/'\h1H!aT+bvmi pNǙƊoxndhKXRR%jbJP!HQB+QYk\:kh*T%U0Sm ;E6πdh (c,{r=Ljg0 م)6'.Sdp| !!*, Z7Y*"C0c-0))Ƥ5PD A8TdBt@6eUGOSPFO4u+ShO2dyj= Oe3D$iZ2&𸖾d`XFcOgk@ U@R3 =Zn7ֽ4>Nz,%W[ najw5P!(7s d0̓̐hQGpa$1 :9"ue(&`+#GƉbkз,[ 1iKO%W398{w/ʻ۸y `0q:ô :9AF(]ZJ %IA\P)=xDfx[Pzke.0̲];$TUY2%-2n9v2!Ng\%4HPY 1e\A |ť$1"USR0H K!^q-}v,]}Y;윂 00ǰ~c86~=~R,,[XBa,]^w"pX&|rz 5C5unA\P36M ]t!ϩУZ1o^L!M1@P.F&B#ڣoV4`$4<:g5oQ}.^orF OnR 0Q5!zG7Ԇu5pb6m8v]6A[%W@z0M$*iyt,Y#tZȅ+ %m6 y{1$؂YPNOC*n 0P{pջU4G\7- Zأw+46hhW^56+*`nrj.'lUJզc2NWZ/8|owrtH$$DZֲZ1!-|yW.I&[. ՋT딄VA`e#,)<xE*Js6K 3<ag^n /g'މIHHj}xqaY0ᣋXg!*U\͝kwf@`0 gK,- 'BKFMR*hdE8jJȴ -R#}9Lŭq[i cbm32Ofjba[Qdx_Aו?4VѥMحqrq21J4({k(U{llm&HvCP)5\NFsb.Q,Uc+{ &RTS:Li^l 6epNӖ=EMQk}N=̔*+ 1xE¶6Y(HqX.y)PQ_BQmnR I$ a]aj7vWZ2.gf)C9%xOu1 ]KrBݴϯ;1`0 o6=AR4$D^'@Ab 2NI@H٠F7.d'Yjթv03*wz5P I7eAE0,ʕUP@DDAE@W ^Q b.Ȟ$Y23=Vu-zR[= $9uK?yߓ4S[ Jdڢ8|OCҰXq導\IQe,o}8N.d1ss1BO߫c *aZσ~O3 楫ƅ,+@{x9#Q@y|\ncKw䷟g] /y:oț{+zܻp#Wʛ#1qܿ9)X͞?'h0 32 .6sjvkӚ性^{)qi*kO.0xCVk>1)=%~9j7)~9#P+~yTY9/z6ks^]6]8xhυypkx,f^;͕ކњ Eͪsz(?ˍ=y~+;\eH;yp)xFxO`:ҚGGWpŞLs߄`IxXIom1{ZZ63%O@8zMW.`ruj8*iZD)|-uIMJ3N75۝wScZI_p-JA4t%.,Qоl< ?bDj۞P$vkQsJYJ{Pi{լyfV}5r;V#C>zL&K%e*Yl(kc=Hkhpg3Rw0WcoS"}s1B ߞ܅1"ERPܬ8 8hԨ(ŖJ/U1mpV"i:RvάH IDATgٱXw̜҇{JPQlgk) b(S,z#BJ4W]/ߩM P &` &˹bdVPW0_.{g!DfR: *d%I&AZ}}442 [dwVY(>_!K3 e%H`=e^LcSZ,gIi*9'L ٕ,\k8<, 1y6A@M:^д?h]4]J(j_N^{Pr+Jg+ؖYp V5ptyLpV\z4KT].Uk۶]@SC)ZבN|'1gaV;5ve*HBYnKP~TN? .LL>>ΚQgIV(N) 1 ;ԟ|z{{u fE4/2Q)(aC, 'x\zܥgeNL0|gze홑oKߐ#W<,Ri)9Ɠw=I;ІbUE*p&Nt8j7 %;BI]` lAt4H- V4$N0L0_)W\ CdH/ɳW#Pd*Q8{ b[q2,"T\qmt;42&vBIU+z1vFę<}6ݔ-@Pi=',w7ʄB&E* U90oljF,Aqx[6Q:v"10P |wbozlJrJvo۞%sFcn疼`:ply MR-Q/QKj։@[Wľ8z7PwyE]~<9R9)OףoBgwɌ$8Z7\!YZ]vFڰXA佒K){. ;id!j(v+=p'*|h+_xo-}8՚ |\!|(3121iu(*ѵ: ='^X3bq.xZ/ɷ<$\b(ME!o7||o=zӰWcQ^^}o{m#3P\aL~)/cX筼> ?fWg:87:3ЃrùZ(uxs-<ɼ?Y<痚97>yO?ȋ͕?wz ixOQ6!~Q ,3KU7ES*'W$YIk^fB`܍sxrhYqּ?湄f(}<|9W1O :<\5)~l~Ws~i/ |N*u~xSYyy%["zdL"1Vkf8"]8ʙi: U  {?xgi-tkP H:ꆇY>%!^>T((=LeIr[@'d3kэH*H!dȓc#-C*.*a!8mIfTFs_ezs+\5\S{9xr -hc3f2o24?)nx{ppxs | 3=>sшV ߀%Hr1ڽ>wԗ 645 s?u=a|Rձ+\GXXޠ#}-fbAI\Nf^/ˠCJGNf(r [u62u9rx0m@C6{ Xp|WӎIҶza!H}C#/EJgW{^V^vkt yBr܁j5?<(1KU*b\_9E8R "L_Q}G%^ۍϮ 1uCٚziĢzAIę_Hl[-)ٺ Xrjg$23Ȫ}JE .>_ၯ@*vY|GGN>M"#>c!<&n &` F<*!i@%>UQf>8hltv,픷ݷ7. }¥k|怀+qgNmVJV22JDF*& T81$2&B=TeM4stѴ̔.1U,2P"54D# d^ Xct9㯳5^di)5 /48Q@g)}: k#܃DŽ$6]\zEN=P &` &LA*;l%Rgs~:<gLvtvM5m8a#[5s˴@? H!D1qfO a);,vߠ׿7Wh Leqast))y JQVv<-I߰hlGFuv' _af0t2sn@i=дP3]6^mA0 (EatzA%D`ǯ8>DV/p{;WqexUsF7a dfc'j`ZO_^~YR<22 ±) ,i ^9\tX^1zhud'vzԚ2?ؕ%R9mqn$ /`vӟ᪬xh<dcbfeK%n+Jم0wIwQ,F GA{yAMPR9Xl%(o95Zm+ Y9^ݙ\Jtԥۧ/뛌W`emnK)4lǨ/au]S> &` &`,.iݯLu gXQǬ2#8__2/T|6֑6bX R )RH)篚#@^<-G8p]YqlaK!c@x'թ3쮿ͬB@:-{VػžVbuV+Լ=`UV'<7vlK&8[N\X,HVb:EYܣ>"RtCMj7r%^#[L=% ` n/^*X\(9_2'eٴ Od uACP}NRm=୯=e*x5^hjćqGi돹bD^rF^b]qRpo~awrߗvVjk'1~j=~^rPGb+5Zw98)d_Y /К6~7=՚ 5TVɨ-V 0Ul|(SD|/jC/ҥI^'S/a6x.F?奏33_%RosuQ;J>̯LEjb\mx81q?u]Q X=zO3y-\?O's ɎAo=5@Qfc KkqRUU$uS6a$AQ=ze_ퟄ'kwI=/JǚhXG^|oy˹o}Q~X>j { o{~jҿ+[ MfU>ok.s%?"/k?ך3{>m*) KZ8\x60U<1Tه+2u"ZU 02sKڍl.^^'f*EcC@ŦbZlJkXcRL؝2F~קMm3=V{lyCYg!uR6 ]  AQnNfӵY)dʫjӵ9K̩4b3٥4B(?G/8Ӛz]f_5#3=`c8>ײm).^'q./l*n04R`C jm]o9L0w%ժ,+tJk1U(IɭwWv bJd.8H={I%i4҉gn- IDATʒDQL BF*BXHZK_[RTϩ\,iOk.]D86y!^CjgK,jDłmKJM- b(ҧrIL>^Z09bao zH^3hw3ۧk3Ty]o,9P+Urvuv:peTu92 bkYD.T-`wYΙfCƕعbJ(QPSPSN2]_TDO[-jFey AvFx5$\=( $K2v9Hn15AqB)EGq8SBpFxWPp>{KJΓ4 AY iDOWTe궊m[H-7\ _mĶ9Y>Qm!k׽ 75jT8yNo岭mkgn>E0EN(ىh+[k-nB&` &.c["96kfhץ^.Bl{ΕGrdd` AiX $MA0P8ql_z[۳K]n*Nq<$By*{i=2̒l{:-P[Nr{֠)G3MD;SIb}Vcw?P &` & pVk5<%>J@q,fAp@3Z.@)H؝ʽx9KE)$R RH@ )XRI5s%R*)1R"#?g_}hZ8Rk`#zW;SW~S!}7Ҍ4Ckz!k={t|!?$9f,q"k+AHͦg?l+r{"E!3b^|BD-D^E T^HGJ %G=uϻ>*ӷN:ۼ1κ \zJ& rLSGpV0ucK><>ꟽ$nXlkne'󧓺\' U9% 7 '$Bȉl\pͳ[?0+WP !{ʉq,yѽ†If`Z| / Vl+<̞[/RZJߍ8=9R)R !BHF;7nw?^S+*qω '[ټvj;yl㸹A'^ O0ie& na?PLnDFTEMskS~Z3 )(le5!ͱ 0:mi>FW1:+Gj(5QewyD@(d.x0]<1)`qz'0{~Lk70{1(iqg1ՔYQZ{y yp 1o>gUޮ/: m.4^50{fM}#RV|4 /Xx x9VRm'*)sWf~>`ޟlr8܏{/jy}.27AAg}~x9-x)609f J3Y\jMAM/J\q湲`x6(j1BCuZ[8EnD$3=YFb?)E8VQ3$B %$QdXā2[K?w3sGEDb r\zKpFҾ#{o}Z;gSt<۷@be'5=\,"RqF@+"J@+PaTNx" B)V%X9pYz;3s V t&v}~(A?qFf<4ɪZ|wPG`t9 ,43GI D5@>^?JM.tz;qG76*8ӧiXbr.AN(V (+ʂ22ʴjFZK$ʆ"r{ݍ Ώ\?5]D&p gSB){1 @Stv[VN); R^iD%|ݽ_Vp3=ŖVyF]~&wsd$ s &` u\ٺd U^ %F`A7IYV^rCB)2vW4@H=1qL)K%ᗙZU8[q/έZ$Ar0z>lе7>ev/r{N?u㝝}+ ͙ qM=!uO+1Ӌ)z1!iK*Ѐr7kk,iD&u+rww֚=eYȃTrW_7z`Xn6ZBgZ-/= k=zC*41,{`QS ۽ p+r~'uⓇƢMh#u7{z? XQ`JPl UPȞEE$қQ뒰M|lSڮݔۇW'#"{fiCYh.I%zUv+bk^UZKkv}\\ײNj~(Dga#ɾOLLSn#?\_DIŅjpUML0|j*P܆Q^ \Sbb–JuZn2rG\X af&S%XŠ"Y$R"R(& E/·6XR(\XSkScT;q[?h d,cWsbE*n3P }0.PwYȼS#S OяiGc"6"#N#18.(E?P:LAPfJ τ"\VA(7-sWN!y_:ݙ>\5ngrhJZ/^^~.K}c9Tc֡ldn427 k<<2H\v;Lt F|sLiIhEz_c: 0 'ٷzU6W.W Ajٻ1cV >Rٮj8B[TJVrHF{s=lOX ~C ,A)d΃@|M{]^SeI+ ͙+dc9<<@s%޿flD1ӧ1I#ZO nB&` &[#[* TrM[@`ZA|U]V5+_/PBKc'BfbGDK K ,eV*T(bXXb)8PQ_@iTqe TB/_TJ)O?LL@h;Tpu'|65MW|9YGt*{;nA ,Aͦb1GЃp0 ͫ/5zls*\zٹ8 aP]og:c[θKM!ziKJɶWrԸ5u{{\6zQ'sni;$k&ץDOe *!Pv}&PfuQg]Jt뚺"?+ҥ[ &It`Ce[VbM WTaRS^\jMDzaKJKZRJ@Ro:+SoH˶>l KOhXH1})|*8UTD'-)-)Nn. iTDY$==}sڨ611!JثexHٻ}sWL0L'x Boe _ SGƖ3}p8^71a4`)NéId!GYZi`^Wk~&z}EaWVߓN $ٶ,@ ʒR |W9S$\}U'523{l bjz3RFcYmlDZ%R8Ljw"2R! 1r/gHq|ҦJО!aCu_KټvKPl|-~~0cyK -x7F.^`#A`JtklɨFk&Mx_5$^>NLVFBxy[|P9ߋ|_0΅Y 5=dS>_i6K Lxa2U~? uJQcT~z#7~#T9#E.O 52ovySXd,|Xə'r㍝}?^Ͽ4I򂢾˯0Oe怷hD4Si! IM.>am4xrDb@i׍2/B2{7eJ5XثlGvDYf\QJ)v2%\'Q4tʦ[tdMp[VO{>Ul(O@UO8N eٖm[vri<.THN%H*TPF+TLUay_%.m*K;#N[mJHVVDžK[:v7 Ȝ` &`oS9a5 8{!.V*(}'Fr4)[: SH 6njbC(R*VB)TBl",GDBH (qmXbYD K -a)2&^Ð"ݹ"N;a3PGÚ$r*Er,TE TI*­@d5={ɉ0hr7x"XeXfa:L5|ŸЀuDFLSFeDzWvGFѯd^蚃 GcfpS|2 C)M1iLv:q>.p+n߉;gs1~Nf{{xKL[u9t7 44nA|"8½"oG"΀x wqnx7UUu#vs~veĎ̌|b-*);eiZ>\H\o/% '1Ҵ8arf0?u#|?D`^CtۻI9_NJ{p{9rB%S*SM-M SR*!JJ!LR5Fs-I{;M"7h73^7mu?0, , <Pc$p | x4p#p 4_e'.> ynALr{YWDfA9MGykX1YL+]Շ pzmΝ{kxs8JBa:B{BqC0v223V \IKǀ۽PJm=vny0s!BBHЁtGƺO`bzsqg#1s?cXy]<$u3RV*"LHs9-c{ռ.:T2e^z^m!.{|8ʇR{^w{RfR: 3s0s DPY{%kkܹ>rH.zi#N EE{zSl@ :'j|ۀ 4-Hs`Ӭ-ݖb(BCF,AN#Kv8k;tuL~>)7?=OmEJQSHy4p`a-c6էSOq( J$%%繝h 8UR$)IVnV*01Xi3qS@Nh9H夼DoH;MԁF&>#i;`mY`h]`)wq2n뷗J YT]S8AsɁ1|ܹhl_Ug Km,%h HDN*I>D6-±-(1ϮY͎0{foS?C< ( qOщ<Oy_Qat||tIXgQF}"s!Gz:W9ՅDTT"U" VcDlAR*!:_mt4%ׇhc8ةn!k (;:@;C?^ VP"۟_ ~x^|bӳ#g%X8$T^'y+_._'xiCMqkn wv_oWxDs7?<3.")qqͫ'{)Ǹ馼jCW0s㚷{3.IO1N}px pg['-e`#Qj73`cr=vf;[tK'iW:IGʵ>w7+IJu ]fE3.~bL;0b؅*qb#h8guBkݹwƃspr+!`9 BFw:#lz\ݷ`ج^A3M9CZVWϱ vHB:^tIg̎:~Xȿ=+][qTwhc-\".sFו>f˟ T  , ,pU}m)>daэbjxh[b\JjC7 S]G gJ@e:kj~W&Lm %ˑl@:MAZоWaȩ0zHfꔑ+FhbJ7Rɍ֔6S;%#{>gx/h/Ffc\ 4m=!u4z`S 7kȽl&4oPiƬŒ٦s7s뼽|on8] `@Bkx sDtwqܿv*  t*I#^>osMt`(ac}9n(qJLq>9CW] ߙ+M"1D))>_}Yr]W*q냠F-wjI _-lk{8F?E%(zًp8)5 _tʄk="<&`2x@ϣ G5 a-j+6^Gg3b27Ƣ)_>l$CT/.RJKMߦ 5 ȕC'p˜R*:VrP_+fRٗԔ|L'bF,njgN{$q`}XPX`*`ɏ,1q1NT,6RU FYL= yj71+w&[ܪpԎBG*T`w0HRkr;W̩=PWuuV,M%-Wv v}poqIJc8_sh` ]`S}|6a` Gp9/'yeJsMnkl_67ܴ͐ʰ={m/l'2,9)N7"TݒhMОfjRiKzxdh/S\+jnN=OaZG? v`e3Ah8d,a<.RR/!P{|Hg/WLsuD{Uyc )$<@2 ~ڦ: Ծ%v'|unujyxt*6Ti>\yX`/_[s$P#`Hov8uȭy#Gʒ@UqzM10u<@$H*RR>ú-h.K. -[24YJYJiBSUpAc3 AG<}&(8\^(.< bM:;.;< cxs]'g: 5Z-mzΌ 1DIEh jX{ <~ K.5D2!vj.L'7p'Ofo9. 13).S} ݭ `!t'8C1Et/ ;Fuq#EKmZTxK$$%yQ ,5ӁCfmG%v¦86FDJ5N$JI!E@y)ϻj-hk1n/wS̙xoC⚷/o'eX/E&~ /#by-^U.ßշ+by%{<3ApGώ"Ke|ļ3yq[G3Ok">2O9q7[M]7MI qsC |w\g)t]t!ͷ߇ ށWx7/7,pW5'W]xW|~6}<ŖywƢ);J+W ^3O%qf?֕IH ŋ VyYS \ x7(?M~|^T1V>Ł>CGhnj fQ;?rA1:!H0j.K[\v2ñބǖMBiKۢ,XHHCRnjLҌHhB#K ŕV;N7K​Kmi<\3FW<-&z}vBaXY(sk <zd)}j̨L}/0DG4[>䬉%RJnڿ1>Di,*s6[-N:g;aGgfP,ާzu8Jd/c]2!D_!)yaZQ=G`Պ/-FV4GKֲ\Q;kJK K-[)d*U J 4RR`mιtN};9g6U54obG'f+|W=Ի45Z1,_zQw kkw=4vp\`X`+ㄷպ.FJmh"dwKtul]y 0%u_/"rNHA:Baca #; }9$$Ld )e[W7yMI*=3kk:wb_ -'x)Ɣхw`H*O@V }1ULaATff c0'kB^ou<3v`wN`| bN'5NjL#54烞]&-I+IEoP+I 듖T*]˟y :vw9Ж2+ޠ3dG$@X>W־^nvU&Zr΁E2!,ZǩunEe^Z/Fc#;QNV`=嚳!6b8*C D[O`dQhtc ,AX˓ N0%^ JEKT;gR_M* rsJJ%UT+I5Έrox0*,Izv'k۝nΒ$QJ cycV\9CnTl O5m|R:`5Y}j]P;} ] os[`X`+`϶y2峖>h̩z.Ն҂0Q5M721PMGM a5Lq͏3%4m^p>fo\-ǷD[TPpC0I$@`h=:𼤼XIA,kx K,'2L<)9/Дk+IREc'v4GTwc Lc9^];hoCC$hm,. `j.aHXpJ=pX79ϸTT(hE{}γad=&RRY)ҙ\0iXm2/ v{ku:Y+U r)OuFWuq]\ u[>=5sIyn;FZ`X`𴵵U!DgBց׶|1-[?~-%ٗ eTh%i* k36pE !$IEDLĎGvgv.MnGtPI(!U*"j ''&yIJ"MRud!$X2 ;)PZ0nwB!͎x&],0< |;A ֮Vo`|s0L'Z.wV+K,iTvݛ&?Fܪ3!a s=z<6\Du8Y h|ǓGBZW pe0 |IJ*eVU>,2@?8l)C&Z[h=Bq -h]#@G̰z'I@ HT,$rqjid.Fz. .H#D(:+YfZcd9'9B㬭4^βG)Ÿl itY+KR%e-&|;4T PT *]쟵;`7)(^8s*^=9wMlC;j`6O8P #7iU\S?  ո]@S$џ+5xO|~)l;Ɗ͓AE(uQ)n#n~qGޠ&n)/ lހ>enѥ=Xi_Nu闼xb0+qqA=@Sfiq_O &n'͛hJh|] pc\w/\Ռ/ 22C:~GM^ES(讠yGuŌ |O\oSi4UB;:oF%x}]x~x[y~X~/w{4B}oIG4/h^{cǾ1.+֞yp]Eq͛ j-|_::O^[[1uSg?ky)0vl*f3ٲU1xd05aUܞ$j\vΤd0Y<Y//&"}d1> Nl[Nx:+غMOԨb,Aa-Ҡf !&RQiF 2fAabkA̙.  x-?m`{>QUC?aNW0E5 Vo6v;J7ZK<=y{o^{ZJ=eK⻵69Jy8$Pv5D: ")ڎ[Ƶ>v>Юۍ{ ]rU`7=w8Lm`5oa^Ku6К@,c|t.Z=^ ei |7Y23ׅ >Dy@e֫t|)Gm{K+ i*e1%d5<*+X+vg54Y'm ooV7k*IUZ1U IDAT 4]&xqOW֖Z&w*8Cjc]튙,yWڿ^OvrX8d. ,8ZEV-K?m8E4zCsCF:[RXQC 8#B~-movw]s q N@;=@w ˚ѭ`<ϹT +5egx>nmGtyǓCL*{IdKg,-gn8赵u !ń.ȃNhg ]`0 (87Ea!70zk~BD-C2V3XmYYJ@MR5 9` +1. F#j4v;˼q` 4Bt)H z^.y:3cMH 9$᛺v@O/#'OIvw|, , 3ֲ@-CzU_}H3g%9܏2m\)rȝ\Rnp9Áٿ\_q:Rgڢ,V"&A+tv^o}CabR(a2n/ШҠEiV% ShKey#u%'j(Jl)G㳎!StZc/i>` {^6M +%LCxs#mj޹f"É)&j ֶ Ba3PL 83h Q=쮾H8=5Q]1B&9%%%_6+7N%Z^-Rof'2T-YpOwlT]@9p0O dE )R֊J։84cV^p[\{v ~4I@ qF;qnW,w$O/όk0*ΒHD~-Oo -nhZ Ε#tBJ$RV)kaf710Ɲ5""!,[=c\[bdq\oR =T!*dp:78h4?vϻK<MSm$ A 0YD.9 e[eó XTafn+QoY\h?>4N- gL m}^`X`z0hXj8W( $Z.(O>{kW).8W^C%Ufk`,;m*8DDJQ"H6%;z{K_./c'SɎ&"P5g7vRp-,H*K#0NBpˎho+KL j,ɀTOƷͽU5ݺZkuYMQ.h'ԇH@^j%E?y?B#pZ B*>:ζlj#`ܹ(lYk߷-hK}Q{;i89(;InKɰa6៑nn"]pmsboZg_ ]|4p!fRI$In;UH:pa>;}IіH2tRdtVm$HE3w@oS4g+t|[؆v/:Jf6,t9*`0 AC , D⵵Nros8v'߲􀑓,+V T}?,v4sR{9E~S rDqJԬK. \jN*2 % %IAo M2€8oNt+![ m:}wa`ta8p}-Ǥ *VZ pe(|›5%G#m gkF77Ƅ<紩fRťMc o>jVV$;yQ\qfi XX *xƛQf<4tp ־ܹㄎ@"x]d ?"u.Q2Yu Kjwm5R7lcr2i[ʳx'S!zYi*"A='JW͆}I嘆Dɸ Y_g׷<|g]ݵ͍#]Tf*'{$B,)o $A HQh= !O]9NC0]pKҲ5֦*TR ,l2v.sZ3? 2:/IY귱{ ׼.M?\AK =f#OFITnX`xJ~Ez1V;`Od/}G7~L%70AT zÁj/AsiaJ:,LtO¬zz6-[ SBfrG#OL9pI-<ܨQ%ԪT X2ۂFl"0B0=lt{"\i:` 3p 1Vka.鉺(Ul4+6 ^O07 D0kmǙQ(mkvO_{@&:M^W,,ؙRg<&[NYˉMSz97C6U`X:Qx5L&s)[nx?py)3`h+\WIDm۟ȁoLD)|d[^+]*6XP ̲aZBIBG<'SRR>MeRHH A܋lu9.z]HY8y#^li&ſ3s݃]v , ,0J`K #pp!pZm[ۢɍ"缰E"S YKɳ;7,L$-Oۤۉt)R)e)7:=L]EM4kڰ#3:Á>8У]ބj$FD)5SjҶ$!ܯ+{ g.c1B$Q5;T\lmfbGfM̐@Y 0C:1nGO'Z 86-}<ȵ=#1@)4ݡ6dUTړ 1^_H hoc79D&uY^ř|<۰ćMg|tM:\$J+xwȥ2VNY[DBI!tQpRHbmeWB&RRԎsPPG-[N.Β!F Mv3ӂssP.y>W ,ed\i0>bԎrjEVW/WhiJx?Td-j aije+ &gPe3=ú'r'R%&mr*j֚˒ c;۱a}G`\޸`!ru՜+Z$uW6qİ3?ew+Cc<%UY8C70w6A᱄e>GX",S#1Ew+i}r7 {q˙OyrϹsy=o ϰ݂lA !ݨ: Sճ7օNbJ휱@'"+>p[(:,˲^ֺ{7?Ѹg0|Ν|i>G?scZ2|mU?l׮e[4Y*B()PHNo,C]h]`2f^V{KT hc =DJJTT tjs(.c޸v_Hv}4Q^臩OyIbqܘOɁ10S#v '_x0ʠ)U붾_(y]z~~#E|#xM -~4Vkx<݂fz]qo5O^<5nJdT\T]7<#s!8]l;W"y ^tkwѭtW+s$%{Qߊ;ƺZGc9_ 7so ḫ*oU~~+y.~.< | _E6+_Q/~;eyҾ !y+~67so7"OxaMqτ)nvw|>IJ< 5Gfe3.6>Y_+OGwk._:_[o4篏k |qw[ñc K6Q\sغWD_~)ź>AvP0zN'&;_6} ]|wIpySTg HҤSHߒ@LMv$,bȑ "EH DlѩqO[rbǚKo`h{N& kEkr;\uE^ph#] 49l=TL(qjA&0fu>6e8?zifY \ȫ| >/3pa.3lLg+L~}@qS^;^unZJD,I )X *:A*R 47Vv+qfC1xwt؃}:c)pB!}o=@WC ,  sBhC@ X` wE8RqqudrZAw2IpC.b!`4:A IJ"@$Kgh03kaSr9 'vY/-Yjk.rpE.wtaÊs7x5_\Bv!xnx*q-iJ+(?hhϙ#`L09g9o C'#Kp{ćt=`im[Ν{Q+X@B9 U|o&Y6w: 0m K S`0V4sYC)eY$Z'Z߁}F~F#`zssj_}>;c1S"Y[ W3}@Cfp6x3LڊI)R֒0d,}J?bbq@ 6n'gڀƇFޅ{:db*(dBK"] ɲ*IO$XUo k93kO($ n^ Y̦B8LkmYGnG8A i[h;iI3z#Ns|a!iʯ<ۻgJۥ.siB{9= !Yjp ȿ$=(;mli`2ulz8TQ}Ϛ׹O`L%tcKWX`X_ %34M|{} }pazLlQ-:ٛ*)˝@kNNB*R FfhaבݔZ?ֲ繝voƥ~%:{Ypgs[>Vsp?NRLZ1g]z!},+[- nAe`,O{wJ}a4zD|' 9Ëps>#u aa< w(4BGMf;̂nvf7`>CCOTw:fآ̷ս+ɱ~y'S#4ǭ4:/IY\%rJRJK֭%TBH!Lu&eQۣu IDATeB/f=R];.PO&t&Q:H? ^s`?/e{[{:E3⺠x?pK}O~}T~yF ?/"8QFdxόk׼D_මx'[8v ?(~)ޙ[ru[gI2YIv "}E"" *PxAC2d$r.gTu{N0I9{TWW~~<Ln|=!R6>~al>13o^x5{wꄀ5߀8Exa8?6lr+>u|o3a7&|mkpa{%|}^?E^CgMvmLP>^ţ|`{l\QOb~?asL~W k>a k`gC"wű c;C2C?7,Ú?t0PUp=h_t˷:y7³>?Wa_F_x]\h~ /#!`ckᙛ4~~wg}iyrR >6s@rF6Xb Xξ3Bn}icKTȆw|!ZkKV~Q  .I9ÎFEY.fə,=U(^#lkv',+w2g[0WtHZk()$*NPjQ򕛁҂$ Dd~e$ JvFa?acw@&ålFe(#e@DV3ΐ dž؉@ .ɺ 009YDQd2,yiZult#tw0VMf'Qk֏ݭ/aٺ=V1HJhC׃slCpW93^4ɛU黽c2I6V79Bk-1$Ve"K!cR:/ȻQ}^":9ieNn>p'.΂ HxCޢ ##|I^FiL^aTmmT7jn037;zU,_'*°D "܌Bn2=t- k3.H\OYZf.e.SL[U`qVN{d&maj657Fn*)0Ę?xiRBٔ0%+fyD;4ϳ"O,݇ĢO78n3=6MÒo*g:t,kx ǚd 2e,wkdA%=dD++#ڕ:۰y{[5q%Q$HFH4IK됩Z#֠ڸbF:DBHQW IQPd*[.GzOc2Z;dWSVC]Bshց(RE9E!\!&)V1Ew-em.ĢTHD9S bT zEAу|2ވ=HKaq6iǴťn(kN5MYutn>o"Lw9.8zŁUh$D=q4gխ>9R0J֕ߌz|F tnlRʸă('%iBr`!("<-^>J292.c] {mq]eq\7 eCwkl7e,c]R.|۵kBfQ =X3m$#5nw3gMFrz* Ε.R+ R UBBh5e+$TZg:tT?3XRx䷿jf3]ѫe7Nik1-+h XZaQPTНa\AvVv= Wa pkǎI=|i3bCD3i܆Dh GuRA"N"bA,%$2DZ%嫠Si23ɶI"YsIJ]ԭEr>n[J$ɹ-fGmO[~5шFJB,yV(4㤙4!D$e$e,#iR(X[ֺYKn߿6 ?˾1hWAt>|*=XE8 Zݑ1f1 vOeN{ '⅏ <$wxW=b*ț|kְ9v5 QRɾ2.|PgxcXf+^ CcVŠI[Ι^t?w¹^)/sqgSš)J;tuҔ:HpĕsՁ_yG(u[Q齞. \/r'.6yƳ/ Lp9^V+>tafw.HO籯k>+g2 ó27 hIH{~/ފi~,IYk-֚66ͤ? Na* {Svu>Q(""SG8L4 FG+0Z΂yl>ow'd *a#Xl+wcmwQDI|SLq "i6+&ؓ9b}gmwPOR(٪YjZ2 І3k Jyll0mL[ ;&(i0+cӎ3  Lv.N^N;˹IJREE6'/&6ɶ)v{#Z:]JM@wvkpvn;}ek7ZJkeZ iB hBHhW`NI ,[Vuq63_>Dqs63|s48^CX2,FATy-y<\'k/xlY gڗ;\&XHn_Wb7EPc()U4ځv'H,tD"R1q$bmǥ2ܘT,vXѲT8nEB+PAtb A'66ϵ8[2%R-__ەT#<Ǯ%'WƘiA"˕mii3!:yգθ֦޾aDNؖ0)IK$.Ő p0JL;)ϱ.Y\dwD%ID\,-wkAw f66MƁ0 yƩS7Q}b,Ij<&{O/m]'(Z!"6 Fi)R~v]CcSz +4ϳ<7ޘ..{Sd tonv$͐s7fKx>疀Ey_ 66¹ޱ?F_1X2ܝe:eK1瓦@]z.,tvmn#O~nj^8uHđ&v#:B`XU#Iy Yf[.b`q]u>;zCiw&:Ӓ1I6f4]Qb. ַJٿܳɶiJW2×=J37T b&fsOfGcK~O,pRkU .;Ʃ2V2Vs@6IHgTc#YN~rcΓy4;AI;D-CQߟ;3k&B8Q_j!M 60+OΊ<3áҿi8:_> p$VR&{ !ּanCva=2vcX2=ZV 锉/cAz ׂ:Emm83fi}8({d~"2 B Rk-KtɄ@DDJDoz *lV qP7twHIw2o=#1)Y,H1ƛV,7h&[#f[.M$J S|(R@L,ò XoS"6,pqfẘe*?JR "aq٭lh0`ڌJ4i0`B;d6l.cY4n6_Q`vj7hPWO*]kRJ0u[UJi*]{5.–⊿ 6*98Z oK;v|zK:F,WFa xBº/r w>C+~c̨Ԗ. |a830QqNCx0r{ŷ/ aX'o k^5imÚOL{KN5K>[9LHUƘRΓ Ok>? ;ͧQ~%|=@_p&͕~g)/wa}Qi~ `}[P!B{,+\q!|3yu ?68{?Tnhȶl[aÌut2"1jX$RI`RNIj|Bj&)˼g"־'.4EOu K+iy? \v/ZRڰ/C ~M=w9֊DEyeRQ5}-Rb+:v T1!,J4Uκ(e9de'PCNГ9M ZlhN NacUj7&UWLueɒC%H ٽs'Ӧl`D&S0gAfA_)H4ƾ{a˔4!Pdh*.?:BE* "#&!6-]d%HDDє4; O)x@:<Ýu6.2Y@4ELOq$4lru4m0meBu{ɑ~X|wBÝt7O5ZiA[)kp PJER*[)+І kp] FmzdTekup|Ё9~ WrhcX2ܕen1p:LC{Cg /6seRYEO}!y:ot  2μ}QC4ِM4z-mZJkEq8\,P{ݙNs>WUtV֬:,dt{e÷GR աGhCT3I=η'v<"YAOrE6ɅMLXg~PgOt$#A͢MHEv4&S IDATt22 \d]¤,S7R uP¹r$1DꄍK1f=INk>f"3i4-h),Kcq'rLΓ`n%g;pl2>$/6=`fng&5&ݍ9rhLDЁ,[?:nĖ;KN'NÏ ^.eazps})ܱKGZm| ?  + aL58z~˵ |sɆ׻u:C%SašG5 /9 xrX18+ ! 7Bxԃ_xKXy2;_zk|=R||W>(a7`E@Eů pzX6W~; ?~_~^7 {ƈ:ϋc"ϴ󊯂:QP1L_)A|ȯy=a/LxTs԰4x^6x;0y/ +]`?y jĭo9XNYz|hؼW x~x+;}a*avx9^m~w~]^8>=3 ϟ#{ioyzBx>R}7f/qA+հX6”_;[ M5v.7>-  tfC;(4bu[i(taITBAx6 SIh+䙷7\'k|pR=TW;Cf ɚ(o~_e%l{\l&Ē&OXgoĔ1%C4@YVh(Y= iM ֐X{2g@&xj„vhШ0 i@{5QsFٕ(~C1fL;a"a1ݠQn*@HZI.;G:ָg¼.P ?]oFްCt6yQ,tVyG`/gٗ|VRJ\5:M@eK"֔|ࢭۍ >Y 6Y;hc(ݏqR8!N3]lܲmh a7>XySѺkȥVL__!A=|ǎn,cXrw´}eZP$ѸΈ[ v4,-JA,$)iIIR|`o}q}Xp =t0;h"= Q[K 1B}΀:UQ⺓SJV6RAwxVD(Ѳg#˘%l*1Đ~+A LMTA4L:TPue z#@6k[{G4':ر5 vYX,6mN8Sx@~ċ7t,˜աl'UNs\;no;6.2 tNDH59i6֔}1SF%]@tEISw&1z[юlĿ F)HenOy8Yי}w9RП`sMa|5bE -ra8rjmf( L0m*ݹÃքDVFjq3M~Q0 3pzWyc|^9g3mxa¾SlwUK`&isKio]2e,c[Sa&lafI=Lq~!c4iA'cϞ o=a( >  LGwQTX8f8$2R6œ҃S+gMSv3IڍB YNky?O;+ + t`6KpڰAR%5h]=PEY|tWbaBc!;v|8jo\~^ܰj~ D=xR;A/࿋焔.ߪW<ó`&EϕsMbVПL$5_?W. \GuM>v=yI <̅5;KWX_vߵ&]*yW}~_~+ϼoH%^83<>v_wco}ܟ&WoϢ#WraW<g⧆l MrɰW'$%t`A#wr^ >mשV W±<7Ai#aoǽ'BSZP\r- cEC !MxIpW ,5ֽ>[^z_~[ya@y P%-HO-}!~^1:pG_~cȑ&ID"J9;b5E/dqӢpjЎ VCz:TeZGQdw6@ ֑ 7ZJu^y~ݞ[Oб)`faZa>:_i6`o+Xφ NgmF6,]69>Ϲ*?7w;!s,cXrL%pn6k׹O5D[{֫Hd^^G )j0ߤ |aBN$21V3۶&1ЋTУ:(+9^Lv&BE#Gf\nmA8Wqޏ-R-$;;.uBQhH+TD[1!hƱYA"i^TvTUV[CLhH~kDǂ$:b{vkU롭hDv6N "Sd+rxe"(AiC\${:X(C|Ɉ0 PV*EQF& nfJHHRm=?֠ Z'T|?LT0'=;\pmt? P{Ж>Z묟g{1ZPDLts3&l5(]n'HM|=qWSdgl(]#AD >+Nx,gɞ1ndkL+ьJuZ ; @!zYPD݂~WL<8quo܀G4T?_iZk|pW;aޘ+ǩ-$4z@yQZ_8o0v|{tb *{K8 zpz{,_W݌z3oxhoL;Cn,cXrw` o`q6uu]יmJ׃#VEJ[EKm1^M0+ )5_:).$цxxƶ3"!DWu׹`v(թ> A^>1!5SP^%Įhq;a3,AW3r\":EE_4γp˙ qtL񜅈I9P`f25e | tvƔ'NS`l>6a)K=H&3$eB3i@EzBpH ;rgu^߮GHgxop-sQE!XXYྐྵ34B)@)ewˋGG'm@2ж}\ԩCzY@Z D_MD>iDd "cЮ^is!s_?ًGOmaw?e$0_{)ϻ7]Wi |O ;;`+Oa š캱c!^^6_  Ok>񊏁a317W|UbgšŐ|l>şC}]XW~[p.+y³}>[Ow> ?EWAW56'=+Q^sp_ v> wyR% $pIXs5 v-.oUkE\Rh|W?# o[Fr|'Wt!snSggނڠ/.g,$VyѰJX3*BWa_ay0,^q#lp3<-巄7q4OP0lLXm4?_?yh<~U/k/U^x烊GN ss?:[&LQ4=4a"+c_~ą맲tGq)l'-Ԑ7i8J|=M#Ӳu XޟS L}mgGax]<hOkB9J(X79-I^Wu &t5&S[@ EJ4bZ,)fIũRU\=k}21-P j7F64Fi,1!v yA|6\ofnb)ٹ~'ګA*-v-F۝EYrnu?B!DuoQJv#N(*uB S0eL~JRVyh5[g>davprFd 2Njh;de,cXҴZК`6jl҂.iKkxaQ};]W5DS^{,=CFEXVUid,ZkUqoX):_]V*ȩ!g?L)!%_7H ֡OtGxGG@e7Kv 2KHА$13)=E+i2{j%3=EN|\2"=,5(Rlhƴ-a,D,ZJnYlԉyQ_ȸk&;'CoJ'6#F3Ӡ;Q)qSC/~\;W[>yA+E?MvÙahX^ ^|~X!oOk>|aݻ>{=<>x۟j[G+-5F_Xp|a<š/7L7cyDXbx? M~O:ɯxOz1ag h~ͷxetSoT9ԏ+e$WwC]j'5R*L5f 0qir&r1i[5eж~ WBM? P6CZ.T I !q#ͤ ?\1"m߂2+םp \@Udg4`CXW븓Ě3"hUtLY@"A!i&פz`9=1j3XikU鼮 pP=I%3:W{,6墓G\hU5ٹWTZѨ npg0ؙţqsi3%XsvG Їv60alCibo{KϹØ1XadDNpd.a9ݱCX2./-K!0i9hׇXfjD'<-2jU]=R8{@.jEϮ.>MR!FŐ!r֤) Np*3o\wvtCf,-TBp{/ B?W*7=7K=|@{-m&smtʭZCn[j"ר@JV( t0~tx%Mj;;srB.F?3Qk 3GΠFiY?O@]}9oεo x9̴a^]Wºf(o_gL,k['CMc2e2OQyIe 2CCe,c]^"-3+`` -|̲ 5UpcӷhmGzpH(, A[n])!A-AG-|:Ih2Lp]%6w'u0 1X2 iPOQq7X?1Z]5kvZK mw&%ِZ"Wwt-OJ- #w '9N-WxԓROHύKXtiuiʜon:6wFJyS sӇ-ݔ]<Nwj~{M!վbDDF!]El\c)+#Lmkd0: (sn`+&孞k awܚРqm XTf7` ͖Sո|Ed3 b>c.iXS6n SԱe,cX.ssox8svŤ̑k hz:#ڡFB´uuBlKOɭ6}x=Whgf΀Sx3Β(#FE$p(Ωb>Ʉ!Bpy5SSqIIzFvA#P=MQDfD[siט$sD2s,(nъJ+ޘa gڄrT.y rds a4Q2PdB vyQ@wCaPwn#J}((2?Aөg;XM[hf[-el7ESs?i;o]6B:0i:W);t2]k'Jb &XWFY6}f}1R?“ÚF1!9 }*98~& _?U'8)^!U{gs$D""0aH,IPQyR (8 ( 2NgSwWګ9ܛ O9Ͻ]ws,ĝ|UӒEkyRk)M0⚿:sCVq>!.׼+7  ok{$7L_uqVWa'xD=y#b{-yQ /)A=\}yQyn~/+䝯>̈w:~Q<{5Xgp(^B|MQ1 q7T2u4w5g3Cb:ů }"?$⎿|g /n h}P>\r]g. σոf|YoҔ: \8M^DqeI~Tʯ=.u|qd(be::?nxu(}'?"R7|OosJ1>Hms)AXM|ҀTdmz\WbB= ˴M/uO~K|WlC7z;+ BAF'=ic$]C /`bDc&v\: 5Ebvcخ_s;L_m%6`-# і.͌7߱IMVi%Ff%FG,^(oڟ9v-Mz!6O˻|G `NØ<J\繛dBn({K췳tDPtu /'Z>}h;axFe"X{Y7pؖX^M;M8ɡ,hlLSl8dNd"Dnlr ]&s0HR,' ciO)*)\)[ ô?N=q qi-.a 0^{*7Mpf%uكM!t<-ZΖxvAcMcKe@R,*RcYB KnKE F0+d6^v`DZ=$C{I[b}|}0rb 6_V0|KFu;]i T<}wO촘T+qbNߐ&@c8'2.mko8d0WY'9wѐNe].1Md"DnbbRdJ௦Q@&I[\h->붠 >#mU 2(>* Z9~X;q<$l dR$X#s~S-@v[v$fZLIq0-PJÝE87Kajdr$`WU#كjREXy:'sOjC)|cʡw3کSBxO)_Tviچ1[EȜvۭ"[n\K03zj&kJ+VRS7 `NR.D~/hX,ӝ .圪&n"D&rTQV-1C{".8W6}x6`f<\0}gr6hzug>i^i.JI+$]Hkc F8v[UTvTs+Cݹxfع>>n2WX&00 z[%W4LO8}Ւ2[rEh6 ^^K^B+0HT&u{6.s 9"7dkl2;)mEI4wyk)VZ[i}6c+ ,64Az;SC}rXYcp8@C7\[7we38^lbC8/QYh~b߀ kC^̓~ =\$;uz.:ao0wIwc_~*q5_T/wᾢ~"n{y͵+Ox~((#y ,xdqW*-}M<1{x%VQΊrE T~`![yA.Ny9x)xf\bKQ|1r>RN3N$U .xȣ~\ҵ|ϯ['j mѪ:ne.jp-pV-Aև/\eƻ:Û-rܱ!E䎝+¿yDd-j= gow|Tg$d dj#w^`Se&3ʿMXh_&RzMֺLN|ɢZd-X0D&2L'KK!\])!_ ּ$ A>=`v gR#)G6ٷN"N._IhüH2QjPCw*u_1œ'V^GVݥ"!<~C6al-ӝ'.''93#axg%]J)]̧F~8-KPaKRX[an`_E$meW5 SMCB"KwDp^BC^1#t\jqn!1ޡXL05d#c3Ӡ2qФZ5'~rܔʍ枭lyCũ@Rn Ӟ@c͆~tRC<7vlD[0օ?穫eV];,d4ʒXҌ$#I2W݂t` ! i?W_eЂ9*qpւm.^ F'k8H}V|*xlp,cp,,0d`4鄶!1޲'Is]zQd1.F*3.8:P1ۡ;Q^nvnbOvݙ\@eO\LCPEّw1%z"IѴ5S-XoKE) IDAT 55v*[d]xCؑ:;:s7$ФTpXWԔh(By84U8~;NtẀ.e;~W*z,ނ rҧLtnMIq01{ܝ:[]H%LdpOsi5zSjufhNysUm4@lús0hHmg :wk, af6[/Xl9,s5o9ABС[FQJ gsdmlB Cì0GƂ:[@:9kft~S"$ndn }#1&I΁()c 3̀ov]GFN%X1k[2M'eO´f40I{JT]rsd S-Sr܉VNFUbؒ1^:ETAIM ţAw{Uyt SJ} 0Y+]>߼g~`k;"uVxc#tNCuJຖ7"Kp!ڛ.)QM)CE)ڜK|qY=|KaYZBNQ_idST\V.|z货tN]NQu-s}X"TQ}PJ;\==Z'2ʎh;ͦ. mI$3o*Y+2 4SM6XHsKƩ}]R3m.  9VZIDȬumgt$S ai cFlfێ\\9,щ <[^sJ)N̼6 Iͺ*n<{c8op7t 99~t fg QvªU J%IҖml~mEZb2e*\_UR %$4l{bZfJhLofzÖwpwrʖg-l0ueY<w T0s NOp76I_k'1{t V~e[]09{]+eX 3 7ؗ9ʕ赧$q.Fl|jc*TXP#̰ug%G;_cCo-zSJDƯL!k !^BԣTqafmsW-2 ʒLofj3כu7]s:v5:M̛/7^X]JR*MSR^?U@ >ͨѩpvVTtuPgt(zުРъpCA hdc7i4 Zbc_T#>*bR%@Ld"ȭ^ּ!tQF},Sҹ-x{h& G }_hGmqWV4a6%>3o&u+ J#{7]++J4ªe; {k0`}}}nnH$,Fm\ZRug:4ۥAeʞ8r _\grV2N)M78x?\uQO7EyfMLaWDY#8 -`oXr@NtN_\六аα؎NN6㺭\usU7KX:-9]cIR6ƴZ67U0i:f!:;+pn,K*JȺ̌ oxiz0`Ϟ *ܘqF&sv-CD-Q4|[lv}z/- su%6d;J\ZvFZNg{r}O`UIUg)A]X3xCs(g6!NC>c1 |`1oH,VN36%Q!.@! @6Ifeϡ-Ld"D&rei p3 aVF8")@P*0#bӚ-ga3+_"9)R4ICa2_ -4KGzUWu]7d M6`˵ K!p]#l1Ns0-Nth)ݼ# GйN}(blx*\@lк ^R!etsz9}M#4 2U L8;ޅ!IWow eFHKw"1ȭ( FKl6㗍ڱ]xg &՝=0D׈u6+]V>S:Y$9SS] Z{S;ZбLrZyIc}bÈ~s=@׳,/7d{~\䯿]O5k)X'>$u~_A5ׁt %q3'"ǵpEZʃ{ " v \sU|KKe'y+Er| UQſ]c$x?3ȨX0{[˗ ?QGP|Olɲ_xRϿ< y%O"i+K{P~V.wy=FOs/嗬?^+Ba<>DyW<ҿ˞v_Ō |1FH೰O3A5ؼ1?<o5yʚwQ·9Ols)L.~R})_W)D-Z.Wv=QZkxpYD:ke OX\u77q?#/8&: WTM]BQPG%OyϊWoxYM<~6o$5>Y8mYE_c"7SFڞB2@-D Dq(Ҍ΀&G+ḦlKR*$'xG͆'lm6s#XA+QyuW5je=DܜNN 9(5[.qSW6M|eSykfFq2~/alf,jMoh6>7@aUg=$ FU<+X̀i29Ae-e8U (] cS8t\) j1k.-!kP*9N {uhwRY81Y*hZr#:PCA6*`5u†sn+m% t96L9U{wׯ:PWnѫIL0CD&2LA֡q; yhxmVg@j |ee_c!gINqx`J\G*I+~Ew\MӠHR;\?_Φ<azK)yi隞.b08;TpN3MĦi~HێUCV v*/HS` +欀n`lʼ"Xo4S#aF1hdBQAk|z߉e}@i4I&0.Tc,FDyax[@ܢE+ ٞqka1tgM΢iu+Rp ,’)H\B)!]:c?s~d >wDwGwnhai#\pEo-yk^Od`e&2Ld"~Y^~zSCz0 3(qj-k1zl:6r!r:_N}D|^vRJnT:w/68Wz]brSmNXod~S Đ2ykk9۝S>t ˙XRt1%CeVe8X7lKF*@H r6rV-Etb= 3e1VyJ?zq]'0`ĶJ~UY U pSaJQX&uLCcFBu-1~P ZaHIV;JYO_z" S4[Mf;tdv@_k 2(X_[3kzc3.M?BI2불9e̤Łدm˪Q=v$Fylt%3l9nzA/a*a6e>eVLI ֭d^9ذ B1 PXǤ4"s=hq(sT`)|JFR>F) 3׭Ag)93&d 7څq;E"1AKRCtt0k/–tJ ΉN)e@{*df4 2b='Dւ7%HVS{l?=L[@fXs\эhmLg8Z::Ld"ȹ_NE'W}n }v Zp .?K |0 XEB1qъ6xQ,Gf 1CY&v NǦ-X*dc59`X0Ƹ x>M4MJysóō{b_ZOig20VK'ب tg2s8cfFB'a:e.e^ҴtS` >hL'* 4U7KaFUa:qilld}4fHwUnh P'tF9H,3JrTFsuY2Cf  :=ږĢ@*#^NrB*Jo 9s9zɎnt]?g Iˢ(1y;yfu\[k^._t;m\*>fV ӯkAW[#{aWkE_`(I2C(tXskz }8|(pd xڕ RqH6xg+⚧%/|2mύsC8_D&2LV/]nۡ>8,crV=+G1ߩ͌Q}FuAi3M.{ j­q 0 -Y@AAOj/a:a:eFBhx+FG֏*εR"۩ S*gsnM#994ڐj TJ`pktb .gYߠUBCwjd r -KS fCК\cyAҧc ,-n*q , 9lb*޲Wt_WXe:Z4}=qŒ63zoxnDWo%T\*d{9jY `nIm$UppK{a<ƶS9\wlOLD&2LF(Qȼc@v` `NWN0!3D*`)6nTbN}C: @uLq߁bZ, ; ][]uw)caP vu`g=&yܬ6IЪnrÎZ8̛r@h VʼI(5b0ޛȞcTYuZUUa)!(ObntaEaHlN0 ֖X])*!\uf:;K%E( @ iJ|Шb Ԁf\ V]tu -fG1JeSY|二 uTI&C  a"H9gĜNfkK ?TqOn8S>N=u)a`·"Ap2KPf"D&2[,/|igD|*0uw̼Nq;ì]2}o'aer߮o/ѥb %A>sb Eږ΀۶J)vٹIr)U3;/Oڶ-7Oj334`II\m d4ECJhkڱ94:b וfC5]K?L Pw΋rtM@Ş>ږ1)H4 6Abh1| w%Cc:tƙw*]4$_z#= h J;}J\xGT7̜eV z3gG#9[G[i4:;X] k$fL*zPpYrYx% (Ίb=ιQW؊?a/,pOK0eܝ\klk؄c7D(Wp[`l"D&2[D~=.xa -X'œ0!};|nm5,I-]9Ыn;ed}{zIb Hmr#hrJ,xWVzL$MS䯼s^{3u E؞ˈAXHbR,(4z"YfJXHBR,j3GgΥ%p. 4!βH_yYѹ<^L'OSU }__~kzץ_KS(HsJQ䘦R2ul MIly<_ ^ KWh.dh!#()!RzYb'Q5SkƦVtu+VB -('S&;k73^n/lf+&Y)|IFhtRH ΓȤ=H0vNΈn#rٻʣTu1MwX|>_dӻ/==6}jt1H3g(r :-bnH/2a ͭidjvFJ23k߁ѶA1qOZ6mJmQ&N8I큿9Ǎ]B,˜N(VWe&rƐæI̦ch'r8WIY?l`Ձpqaw_B1mVeh؇Hi` 1 i†ZmPY۬-άS[gm*ѝ%Q[ =os1~O[: lbM@֤Jeҟ:6uԗ31rc{*f3a VTc&cdH |n Kci$ MPiӎFu7 Co|3qV8MHJCӽ;Md"Dd,? LO8ep]cZ*=݇f֪"t't%K3XDwAvy=BwssjzZ&rk\#xZm`SeZ؆2,6;X5ӛBXvz^+NևUDZf:1&z]j3ʻ_ћ*,򼔷G XqŬ3-O ɉPF]! 5!& BHqJo.GF4RȕҊ@ o_ \pd+[ \S_VLvn"|02~D&2Ld">t/ +({bOE 8Azi6܄+yh.D u `b0*w`Qޕkkk+`9$cU {ȕ 4 {^jv!1{Sid XGUN].If< lsР$*ug~oeڻ!GkXJsHX$:P'-ڢCoiÊ&3[Β3XD\RHhmdt)*&wEzg-_CKehY6 kccFk4!*UƶE8A^EQ8{}ڣg?cΊ 5^SDs/`I%".̷/"ӜzWOnwϥl<"wۇq͗ _k 2`rY++Bq.Wd?|@TOe-~(h#AgM^Ty0/ w?΋k@A.m9bC-q#*L$3(C?ۉT?ormuW5ïY<^|bqS1+ ɸ?=O>Yg֩?{Kz_C˗EJW@ dyޝCċhG+__#wxr#,t_k><$uρWxynrC3-6@ ppd;Xނ2qȜD&2L\DKKwVF IMe N@uB^Ŏ|}uz;M \-: NHua\iƆ,D:̋ xaL\Xfpi0FöF}*ĸZ[{C\*EkmVuuðkO}.9K*yqVd*N0,rX5` CY#АXvzH:`KWdb !Q5QѐS¹Ŋ[u=˖aŰb{)sFC3q^1?I+toccGx\l.<3f9LifCw[hz'0m\IWx,u;^ϙݽKTfQ E,+(E-Fc(bAEcCEY ,vw^.Ι3egwso`E j.8pfh[W  ژB͌ nN~~si#LgTA58{ v'ݥ߻ 7,@:Tk]dDenuuyͩp@ *8?jTZSvNkZ?8WƗ橋8`A h}6=qZ??Ѹ. Xt]4y#J44QMjMp<p(\p\i|* 8.)%UEx<NTT2ե˓x Зi#RHӠY1c.S;O8un,G H^\b0f)ǓʑoxV :UdP||O:fjfps! ;gQP_ NJ`ϥZk"  ,rD,6 P(IбӍ3Ua]qOa"%wεgۆ*m^>fW\LeDЩ(I2gUTT<먔ҁ__j9Mx  (Z]БÎ$贱I6 #T"ALEh4{47٤6L4(5Ҵ3&Ï%o2>7Ѻ%5ޝэ hK h"SEBɩK9_*u\[ .߯s2BZ$a>\Sung-*7ͅq4g m_W~.ͲK.-tE6 o[ADkg/Ӱm`K)r'&Do~{oKÉs]<LUI0pmg=kK=.,HϯYzY5Np][$v4aH I@hWx-wreL[i]vI. Ø# m;syvB'8'u,raWyGKew؍ ((sr'lgrDJ.]p]ᜤߍe&la-Oҕ>R&VA7ǤҎѳ5F%YwhdE N oE>6,'K[bw*;vv]ZfǙ)waf2֣~ t:K8nGKZN!j<~epcJHZGw]$ZV,C:u0w^ڡkhúU:;@ r@?ӿϕ,AWcw&^NTKJ_BS1S\.cu~@ ]8f,Q>T,#~]dY|lgq~_9Ji۵Q(E5)p4Q"qbʆ*X=3ݭkPcqD,bto z44y4%`+LS)j:7O.~K'hW CdكX${wNNJacfh[Xk٪u\WW?)~Xp骥0vG$zwPL߯$a+pJJ|)Vb3&aA.8gEC\;AAX1ݼ _K@54yMq9ѥ)Hn gžQ-qWkhZ9E0q.>4M|3ԃ' 'j֩K>a=i;T^SV5#p/hx&Zt R-Iw`UA(G+R<ۤ܋}Րr N6mԥѧw H#hJژڸt3wW^R4yUq:**x1x_5ug ۷*++NA$22љFTUURP(8xOaUzRrM:6uRdl2\Bq UY3B8޲r7~׷nVAAKm0oNлGoV\wYwGv)"mj XO \A_TiFL-6,*jnhn.|ht7oRC ]S.`biC"H5m,کM]vmQlYjP\WIqui4cbѸIr ؋&UJUz݀ᤅ9H,T tlÖ b*'~m'E}:<(hz2+XEuVjbUJץPSTT$,$\kƮÇ>NH zmqItN[ ̺.М92lfX\K|ośDFZeo,?;'sk\[!"#w3/rI\Vtw["%Xî-<ȓqE,=m5g/cScs._84-_rrQlVꐵmC8~Lϐ[N#l/]g,Fdf,;]v#]T8甸"gvy=5LY}ePG ںN7ṇl v# 0ؖWaOlɊ h?ks.tcF.\a US.S͡aδ|"Y*7plNA5sҁ/m,BC.7̗!2Aae$lݩiL \ptg*^T3hh|Vnࠉ f*:ON:M>e2 _B@Ҳ֮־8cE# UG9T*nwb[qS8T>AAAd'6QD3Q&Mѕ$J< As4]Ӗܯ&8{HC7EUThRUI%`\I o̘cgO0z)bwr#3{,-;i4saS_@#R4x^\% N]uTTx"|ا 'B^.oRѤ_M0?".-(srZFV.ĵA^dwC%59 &9K⤂,zJVIN8d/^ w]>OR@#[^qۣɃ)j~.p]OrޡZQ x*):ADJgg$?-o$F-Nz u&:F_-T*azQ $".́);y+{(fEf#h+_5@TB`@&-$(v  jn0 JM"gQ$ Wj:9r ݻS L\^{hr]U,zC0+/>, ãGuLu3"8=c1;vTCGCU-\SpvtH+24# cM-ۖcAG*GRd.hMKQ ޝzr&OdRY`:Z#F2U(*Ai<;ס+ ? Lk,h5C^ౠȇ~ؔEжynA9HH% jKE7 vYxA|,؆H.ͲP uJ о [bIiAAVLn  *^*kV*󰽻HMt8TTR)Jл5'J]^s\bJU67A1Eб% *լs Ez!Y'C vQ-ڏ*;\X޼.%c`ԥ\z4]. ϥn;s'Ƶ5\h(CGR(j)Hq u~M69uY BEh4kLIcWʞ_?ȷKdɑG .,dv:X\W77Sڸ۝#_di+@eK>Mq^iTe< Ul^뺉<$͈`pH\F%*`Oz}m ×v<#q9XbZ\0i^0nff?[p-#Ei.X7s:cQl}g nhIv["Fq.Tg=ܴ0wFFF֮;Kz ü>t$(2}V6+|-/$?b_N}qQ|4a @$on3BrW cW;ٖW`!"9#R{2+(Eo8/ 9 + lS9&pN;tV[~CZ~{`y]Y# OU;ڶ;OVDFIj0Ms̃4Δ_71uSBdR\OٺBA6 غ) ]abҝ#~96nPpȧߏ%M?c^b*/,wGNt/ \DTHrKS,zۜT 2aw62,39'{_vo SԥjrJq'J*x(E4%c`dE T"j^`h,0W~|?b^erOd:tuhQx6t'qmjLƣ5yMt\O3H%Z?jĎhJa 4<܌5n>ᜤ{j^8Lm_P̈g\BSXrGUAle$IK<8iC7;ZD!ZkGLr]0I2F,ex: ?N)3XJ|']8? ɻ4".KcEzʊBV*E2:DHtjLΟXF6jjh<~w6-@M;EK+OyyHƒe577WIJ8F_q!LХc͢ [>kh{z<;w3NOg',-vJҹ!Mm-ٳr5` lkO/#R_fYE(7w~*8![${>'~PL{=[eglͥ_ loR #6pm'n4&`_Ԟ ٖ_ڞ7&q_x`O $N! 1&YEasc'|{HGm'F9|Kl˥pmӞS:miqzӤ[S%fo| pFw Y{ w7Nh4lc8]l[.3<8af]|Ȥy,^阀F"a'3$gρv kW 7e2w]$4?&Sn"TLF±0ֆ _6b7n]I i8T:TrJݙRRSTS^~Jq&P dPdG[d۹Qm}G8tOCQ@,H@T+vmZB!pު]P?S$:XU_(ԧy̰ 5Z(RZ{AS11TD=Pi (ȃh^P~5>=J)|.uMjdl @+h{,,3!wK#Ͷ2-70Wk Rh)wԔyqQ[: h-EĵA6f+bcV's^:xU~"US)md3xRB&<'CTtvtR=>sɉ;{B?GIdCWEeધ킢Ҥ`)4j)s&j~FtGG)dp3;.Ig/-<ۯ[Xܷ  ?~l&)Y\8K3/ @ڤAaWCC!<߻swy]yLt!=mm79avzdbhxxZ mY+8n+67$;\c<ɜqlivH X"+%vOk\$exOo1{?ĥ-vL)<[}9;U3p[R*-l˻qȑV΁Vx˺J0C>.Hb:+vy`. -B?2zip]Vmy~M  }\8r;8~kۖG~Hn~-tqb\])._?Ϗ9gVxM<7`դ{~-C+NOfm[vmn1>8jbrF|?kábf7 P =1jT;RZ@+26(REIZ0W0&vB΅l- IDATO'4oe˓[dh[иdH\f|˺0. ?gfffް ]ճ IxmdC\p[}^R9.~eb"~NԤ+m."]v-3au2庰xjW ^-;žM w~'380;T8D.[I%sag7r(mOele+*e;unEr5t~ 0 :$[r Aa`-ϜmO[kxř#3*[OEY€Fd/? CVf:֝Vf6rae -w^AA!rJuX:@#4C3cC%={hg""hGBISa%wqz*?aȪp}5/ݐfF`_|!  rfû =UluyB|j\9ʍ-9eߔԱrؾy8umyuG/#u<7FAe~lpqzŅ+'9+ģp^eb;~ESOXW:^bi# F1L[~iX>=,;{C4?nrkKa‰ɟw3 7y;Re >>` hO4EDβg.sR\b7nRϑnc6- K(. |öbJ<58eL?>c؁\.3+2:ڞ&gn XgC-M"xc$²94+,Ҩb.\)mK*IxqAAX(xw>U ͆#v%Ӑ<̅4dztLDc/&:pC簜GdBb{qM3=r~nyGdTAAX>V*w8sCP'ߋoK &4>Dl62u3dI:ZDѱ{2SYO֢vˏ{kZ š/s mkc)f\ ' ClGQQA Q a%:Y/TR>mKXkpԠ 5틦^K%W *D-}V^Puq8>+i(Gjo5-,~ qi l[F -.JQlN3m2ſ7&=`mPmW5 'pڦU6O˞jn݄u*І:DmI!m-WB\GQګ lwV"rI}rYvֈ z-o v:sicӕEk'8{36Om-pm!˶<=(Vn672~D~Imq)%l/ݒt)ߧg@жbri760CXY4a'c,}Tg 1a('1Et:׭|_ݪFVsc npCrr  sX"Ѷ@j"tU:4u0(E{I_y\.q[vZuN#nD 1>4%PQja +xB\=Edyn0SX9 }-ZE>D*V-^G*w!`n+b+Aa\`.l3*4͑q (Ks{ø\nWV&r:gNb5{rb]nEEdTAAX^;A-@18 296ȑaE#|ӖJ}(ezgqd-_į~IR5/IH\=}Z~1" r7~va>ΆGhoW4v;9W+!fEkq5->ΖcFp  wrX5dF6x0q:$,Zmeq ^s_jVn%l|iGƒ>k~}Z`o$@0+.尷$kLFǮd[,Lfig[4x(`Dؖ QWI}8ҫ'Pla[˰׬*;T6v/…F 8î7xI6J\4emy?$xjE(> iO||J/-<;1h`7!~Ej{p[߽[w7Df2vyδ-7mFiW{u<=jg&2?{a ڜ?,8Ŷtʲ(M; X۶LeO,&٬ Cs&f0AޓN،s"׭<ȀLAAV&r~ }K/\C"3"uor ru"Su+ B1!dŘoFXJXX}%){_8̕QAaEcb.Q6˔-< q.Q;!̋`R)v"r"2*  lV~Aד9XZsmĩ[ii͵3 >!ab{uP؞=K_-음=.?;h(8Ƕ\͗0S2*9*ZJ0u׫OVf8r߭ w?)UY Oq[ 1=eRQ\ gؖvcdXzqC4)dȹ_8N[{ pa8]mv=8(^q klp(^lk?@ |l[6SM0ͮ3D/I\m{ŶgOζ3mKS\4%.b+ <3;8u+92 SAALϭw6ZdVta%A\;AAAXΐG2AAAAAAaiӚ)ðeai.?k 8(z-PkWޮ:j[0 [U>Ӗ{G`3…#l%{7͞2 .lpGe%%En;>a{'{A߫N;P^炎Fq.ldǰmyþ'f탨 ToU:.ZGnXE.9Eo;2 we 3Wۮ1ٖxD>eb\8tmKĤVgIpvͦvCxAAAaPZ}   3g>AAAaH{AAAAX"ĵAAAXI\;AAA+j7eʔwygĉӦMzٷo-r 7\dCK   $ӧK<3<3cƌL&8y^PXm8pVZYmAAA%D}7>{}ՙL&NbP(466v؞{Ēl+   ,9i>ѣGA?wɪSO׏=zu]#ۛv!v޼yl+ 8йCOnIy=?h~r ?'vC[+SAXIkٳgo~6q[AA(ǔGMqCvZSǏGu+'rL?~}4ch |ͣWxa'uaay| u:{ :ӷ@MuбCZFo8*hM'\wށW5xZkгꫬo MǬfʓj_s 7/? G5=cFҥm} ynCy[^߸g<dž=ej>袋.Gyңm?\q}mnQolEMAaHk[oӧM6٤G_~QGu]zdM^|Eu[opx|>_n[AA(CM]ٻ} sTߣF֚ba]*另 =ӧG}4w|p/R&=dȐ]w_l׮]Nn_0/oZVAʱ!ݟVR\׎Zy;cĕ\<Ǧ/杳oWX_ﺞ4h/yU׀-x'9=7[{ƒCU\@W$<sKkx铽ڠ.Ѯzq']Am+.axӞs= _ajLJj$yuW'*'߻ZW MZkݳgd2Gd2uuu2Jmv={D :thsss&- o+ Huoh#6_׬Zi`ܴ;Zuw`#;UўR Ұ!9dbQGm8 m{7 KC]np}< +p>gNWr쎛:6JZo|7~%CN>y_ +jҤIooΚ5>\u]K.lvmJ+K  i/w ڭ׺ɮZlhIADߵ=wi  ,j„ KAaАY< uA_y8qyEg62Y,Z{;ouZ;wnǎQ,OZWUUd2KpA,cA)׎׻!ojrJEnz}zuy|뮻2uH˸E/g1r֞cƌ:u{!ޝ ;ayaCL\ƺnm3>x"s;vX]]n4/\1c>AŦ^}"֎~o_1uΥ`t,EiPƜ@?u%?_~k25}3'{oo6}7H `;.۳{~;q-u\Nܪ'zb}NAw?mרfg~z S}oڽg=iCMǚIMf j1qk5-)ЁO|Ql^ȭ"~Fy]Z6mZMM -~i b3gUUSq)RP>8k_,MK!ӌ[_s3[اk̯'}8>7un}WMd*Smi'b;}wܨK@j :>D[Y鴑Nܪy{].?ҫnS+JsN|s^[tlx澛V_dVN)|y*=oBuv+Ns EK'lYԆ m4tK'oyFdnǝo}p͟roڗoپKu= M3YUVXk\ZWWWo:uj 2NzUAIT&RSxJGR4 Zt:KӒw:7̄;z^NMMp{uI- 5o'fzwk= ^ZQ,w:z5}66?1'u^cߚwla;_+:guxe{V @WUvs u"MHuZ7?{gUϽsgd\\P ܗ"-\RDK%Z+IM(=3A-EdP@QAg3wqaA=?x1=9s-fs{~{㱪_tLV,ۇoww R+[_嘂 H[e*ʿ@  HhG Uh;vxQ!-3( h2(JE Tq >v zͷ^*=g()Qug{qVƒf>hi"yքJ ܄sϟyL`$JT\ %$a"qx?"[܄8K9Cs8p?LSWC;.oG ZiyG~ÞJ,Q-!:u7}owx أ2?|Am{0-B;!meаys6v!5Q>2&65HU!-2rphs|^t~c]8G;> F'~[7M Îv~vryxDlFT~q?M&wN%9+|v˙#yMp%aG'K:^G(@lz]4K&+.O7@%k#@rgF'-*Q-++G 'OCE8S: `Hڎͻ*w252l4֝zsa>n,k(M?B+4j-oN/RVx.=KsUOѲW;uuώIaȲ(1vM|g.)Uw~f'SN+x=:&pF-RIâe+]=&>s$"S#˲PVR|lˊ*Uۭ;6qfL,ktJ1V-jblE,TVZj** ]*ji=Ƴ, &ilMzksءGh[nWۡ=\\'V`X((Zahk</槖ä8saO L-Z1g F 8^ZI[ffy{+J!jMwN]00{n@Y,<& [B]ց8`Z; VnG3iR[g#ߎS^F,s.loXLԮ/b=nh}:-D SZH1WoZ2qP[)(JKmgRh;%yMMzi6r̊}遁Wh@YJ^* ibw]JM3}?pJLJ*  .iF}>Zv@AiYōrnȟVq9I@m:}ы)74 tE5W*0B50} ֟-(u׸* %x‡ `dsh#pĞ' M߲4 DQ(T*]]]jW!BYYYnn.0 @x]\Fi\mb񝨔B;%f(̦w"olt ubJQuuFxcQƥDbUEeVT0[KVxyZ2e}|\ײ:Dвl{и.|x1 hLkYO~pkCVd2)rU W(A,)eh0yIl()x k jXoR+2OSٝ kp_Fd2Lr,`PO&Z'%2 A5) *N& TUNt7y~;vo+kskYbM4Ev_xUdʹ 24 z^Pqs.ZwoRr2_&+AKh:SyIبT*kg_{**TpOW8ҍ22N LiU W(GY7}U @npTGz>Ǘ.@v*9 4kg*8&ϵTvGRk:v>ko*tͼ6|HEQ@yM@%&XZwTh vjFri +%%VѤj_wb  k;hM2.sGtew8}_k覸v>-sRQTMrGfa޼[~צvw_6oL׵Þ_Ϲz^14 ~zzY﫢(T'Q'µK0]G0,r۷w~ht^#M;zq@gor hDATq .íWcj;Gүz4tu-<)Rt)z2 JT*i׆m}G%ڹ; v)wRgo/NrZRteTM!'gr/,0:7W鹳v PT*U*@xV(yLB OE)TrF`TrFd4E[);879z銰kaʊLK~Z)>4ƺh9)! S*puʹ7obʿ V䖙 #%͂'9lK; <_4è MR)90rWnϧ't+|_&aTVItMs QҔZh` LfS-M'*#DA&4M23\>=~<Ɓw==E/|˙}ih~Qv2 muh(,M]:d7k/v[~0+/ lgjRD'r!nCf}WK+OW9X8+lʰ#Lk_T/1/:}-_h4UW~NۗxTͻ~ɟA7Z[sа|13]Ӝ81W4gi^,;A^R)Rn^/YfS^wܶ5pv: Y.ǎixvlsOd2޳kKR-:xL&+̵f9 @2EaGm?xaf.2+DRPGQ4kGF 3Jmcᱹwlh 14hwvGTnb?[wpAviۼ`a!B|k_PޜSs Sv(4-©SBCCmoAB;L0VMڍ)дyY-7'`m\\oا_8OP<{;ͺo?X?ɓ'z=ÐN@x!@x&d}]v큼34i*Wؚ* 18zjjj'NX7` ߃lB UA~zJJʭ[ 6H[bzxx6mڔDᙆy@ ³ d@ @ yHhG @ <$#@ g@ 3 ))6@ @ QFA Znݵk@ @ <ǎ! 2 @ ᙇv@ CB;@ @x!@ @ <Ў@ @ yHhG JܿmĹy߶@ <5v+iƒ"g865-ܸsuk[DKusP!Dٽ,2Lx-fnpS J?JhD˕5V.I@鍤#5,reデ-2PpeI b_MKJj&67ԯ+^ivҩ"<=8OFACӗh"Lmy֘MuƘop fdas5ńyZ/U5STȫihsx/|{ j\'`h$/h*ҤGyڽ !+ @?$ '3?}Rӓhfǘ{Ofg}W IDATm_c6)uQc‹BRƆ!fd\-`(yvqӣ5S/Y{43@. ŗҏИ8?`䘽{'<ۡ34QkKze7 -.g_ε+yyW\' X?dy{xk23-'8@O B˜W_ \y˚u^bsZf͝NFwo<(X68xiO "^N>ol Bb,EPdLr V~JE FKغws\dꊉלsA XS6**a RF@\ m^ߎ(}N(YcȐNxw͙k%^:y;5rV2m]Ў݆ B1ž$`s¼yuʐe'88@YٲaZ Dn.Z1[nSX?xpЎ-\\4R8qM 4ѩYEP/n~fK0./vңckdx$.Pu|K]g@x|Ь) SN^VDaں1Y `]!%<ݸ] hRܿ=O؄8`%.%F/Մ6G !;64H' us1AX p(Mғ?y;!,c Az)-)Vhg2вεE Jԧ4YfFa'g ŠDaVLT'Ӵisꆞi*gb>j<9S)yӧ m@˜u!ob,EP"58p&@rD'>oh\b> .okr3N,31j{WB"N-?BSjy8 yI+0{j m~>?m6B;G ($o4@HFqTlq޷d^˱ZwG^qYD]AH?q]s7O0rU@QwT~Qo~u>I X9϶9 g [v0fZA[qnTR 7Ik䌼0c[{8 DWMV[rEsJShF$ej |4}GOZqa z<2i 9es_vwKdrW`3 +?F"l\ \l.Vpkg$!9.߲@ٵkW9mAҥ?Ϡ-j9*GJ b%6hK T8s }ظu8VkXKMK 8LeDڶ >%%%ZEɀӖhtqTF騴eA[b$6h= bl`#)L JG5@x6%ZK-G'-(ZzFL8,x˨mQRb`*ze¿ȱcxhqlTm']iJje5l6 )ߚy,EO jLGz?`\\\P.!:?IL:³~4 * Ws Fd,Fzpq!N:jck%q+74׿+h͞nNzs@ ZtmK'N @ @ O15~@ @  $#@ g@ 3ϓFCm3@ @ (OpI&1[}w?GGGL7WyV;gem۶effR% EQtvv6lJs3pE\nqOQM&s=gL[n JG.3O0+++8(E A /++Z`4-?u:ݗ_~ӰaC;vXb̙3@ <%?څ ݳ4LnnnzrppD:… G $@ -O0{HIHU*XyCG-K<}VZM<ѥ@(I&}۶m5jԓB$ӀӓOh,++E󮮮:tH,.C'N8|6myE@_௄v&imڴ6mZ ?ErojeeٜYfqG(7߄O 1 D>}\eYGJiy>;;2Z&uJQMADND6))JjK9 bvJR;w,kX` fj)7 k(a==={.)*3Lw֭ RaxN:I(>vd|ODDND+]RRѣG EttC4-9+1,?5͡ _i  "- T5[n=zdu:w:QmVk0Fd<Ν;;֤IA nnnI}ˉ<޽h4+...d@ id)J Ct" DqO3JeAQ/^|֭eVKYqFNN?Ord2 T*yɥK󝜜DQTۋX%#@ S&6B/;{.ؘ}L kvwK2޿;vɵ쏫y\]>KOK8*o?\ʋZ99Nwn۶Mŭ[k׮~sRğΝ+++7v\!/rKw'M l46FD%S뛑},ڳ;YTݾ}d2kPIsqWNcI`3DQJjٲevvv^^^ӦM봴: 0 # 4MtQϟ?߸qcJ% " Ty5x"_Oz">^q4!JC4mn\Eqvt#\N_ip4MKřYFQFۯ͛W _={ׯ_d96#㍑*b=pE낳qe\{[oQŘۼyOnLSW_V$&7FKAEBq EiEqiӦӧsU'`]8_)A (*JADH*9)B;P:hB#YQIM)4hBz4g6\^߸qkf&`gg'x5{dSGO93޻?yo,,wԹJ_GEZBޮ/7-Bo*^oאO2sm+8JWxdqEu𠰡ؙnW!}踑=ZZElVZ:|/j5da>>r{Xc]DL9^p_ܐ4c娥3{AE %~>oVFZШaNj\pFa؝ =۽vR^>sTE}fpĽ$ ogܯ"9JE%2|Ք'!sk5`W-;?p q(x&m9Wxdq<Ǝ(V%s;/9{ާs1]x_\ڊbIyi-#tp'/LJy类NcMLuhm`G@Y3Pʄo8/s7ߛtыMInq?S7|0(q5BXhbOosJ`üIoǧ~˶lzoܓOޘX 9q[/7z 'ӏ8%l_<_}4{{}Q3sn3IVͦ\ox( 5ݹu>_h[v:~LƾA>XpfF x?0x)`Ȋrgna3}5=ڤ1қk~):3jzl^ү߇nX[~3,7.4}T{{{}{K82߿Z:;@qK5+_Wt+f~=z7=**/˻og~-@lO_,8Aw:RkOgWx( E|f~>A 1gK}Q;;V짲ѓYqO_{,5daw iw*{{zɆV<2O}=QMT]:<'畚Ν;x3=VbJ w]zA>PRSWoŔgmv߬?z6 X (I.㤵</s|ѱ'nyp߶ձ.ğ񼐷;iwy㝫I*O/;'^݄Kxt|QtQq7}ɡݣ~(?hV|J҇Ϸ^O\7yP%iRcdyU.vzz9hw%VǺw&Vȍ3I){.9YInIwpW蜴zU=v"GqC%uyIwUn3N*z~|F˖YݗO\c6E{/nJZmlR؃ۗPWsv횮ztIII'tB^SRe/\eN#?NܹsSH͞wygKJJZެ9[OJJfY~ݞUR&[_Țy^pS/8{rƫ~|)Sޟǯw-y=XpqۺMiJ]~rrJ|hL,/^hb_ǻ_q۴)3rM2GeH/~\.[/^߮ɳS4Z׽Sߞ+r݋IIǯNLn.?#%%~МM-IXΥOJ뭁x(&N0rB#'>${;t FN4hDIc8'$g!( cg(>wHաK!=o~w7/d'L̙Aα:iԙ6iN!yyygϞh\";)ά18]q/%Q= jOL$xx]JL[|ZZ6㽛;;8iq+?LL=Q$%-Yr3iZ#{nrvw'}~D uSn<|U[QV!⬤Dt)>>>1)1O[2zJόuO}qRʞn_ΎfsyyTvM_Z;7߸㽣wp_7;!~1!bH6ϱ0룓]yf9"mOsU6Ǧ6_Sզ'I>eڵh^o-Oش=k+o-Ϝqi J9$LDM^#kuek? ptEs6 l  XyozgC9]IsR "4|z?+{c%^}aKfo}V8*yJD~׏U {z,_wt$|<yz"pr@-{;5(.vVI"p[/Y*AEQe==zkG9p@Ç_fMS<惿-7ZÙ363UV;Zoq)+z[Jع<ʼnRh'Eh(hGh4ͲzN eY;F 7˲fIM,e2 LV`0{:!+30eYvFs5խK0o"5%m&`*cYCI֞6MM`0 teU4 ҹlr^̗coj8UzLfbYCN[b0 9eYgST'S;XV`*/S6'eԽg Y^u5kju۷U7rrѵ# 6TܞS(C7gG}s~6g~Ϟ`fU7%FW߶u2ُٚ!oQe֤pOgh2 eeZH_%GJw#[ Nҿ %rpk@ a_V!؂ӱcj:p +5"e3tƚ퉳#l֦ف~,d3 MT:"}MY6Kק"%c /`0#jZ|WayoNɝò¬Mg m݅h+lO/#R6.!-MŎґ]?3&aD{xٹZVM|̋=57+9gfsN3Ia֚٣oڌEfU媲6e CIB.1tt ֺqW%6RTXaKIQc-gd"^ݺ*QQ!M5ca Ut_VR]nQ+\g,$11)*=*,|P)Qs:D69khB3zFJ96A` rJЮJ(%5::tN踸nݺuAi rVm4?2FwQU9wf6A\pAQD%T%SQ\RLTR4,$554Jr/25PswP3ǝa@}{}9ܳLfP!x@Uy|RD,d`Y@zϲ[{x}tՍ{ ! {0 C1EwB4eFܐ8)ϒ g2$I][Wu9rOAU@%<'Hs4<ωD yiPlذ =e|q/?nq\YY΍K1?A(ahQ4`Mos Ȑҫ9@K'@;-1q\9k8{`Q{qUf(,*+-*u+A2p&L&8)H9(|Y _Vth;tbW[4|u2/ܼMЦi/ܽ%Zd;%̎shج {Mjovx.exk4eudLZ q]9XT |4sv/<4%o.9SF y~۴;wEBϛ^k=~&>dH{6x!i! p6WSݔ{ƈ@y8 &$SҸE+}j{D쾗:Kvuis .?znE}eiVӵ,kw Yiko?^yXձq(3rf7]78՜nrRwSדxn5E̕&52;T;fD^.nr-R>q.]#j/L5{$%)ӗ3' _HқKƻ>NɘM)kRdr'.v_m}9T&ăOڧ>+ҝ Xp2*.yVHΘdmuo9wpQxڇ@4C(ݻjϡNi< m|,2~k0]aQiYѣ_w~DVQ//¡ÙLȸ]f`;5!ʰ%6eRvmNs<Gdǭv`V{/mXÛެ{xo%m 0 s5'Z^T92pSyRVZصP" Ezx=M̽V 2 -[#*㸃~սRK 15?`5FBAכ Uv/-Nep9L&¬5Ɓ~ob?,?=^*WPjdJ];+.k8MЭ[{ؗS)mn|0!fiȜ7"NM[w6kSʧ'v%ҵ/Eͪ 4_b;߲oݡ7AˇxszR\f o ~͝ޡeq8rzlEIꖑЍotpyܛ%Dܺ w(:To$ݘ:`TC|I32c~'1SV$k_=m@P@C^[ 4.aCI돿6W`D!t'1dlB֥ UoMA :$L=tFF5۞lZ:Xwb*,ӿ5p ]LQQZ.,""xݖ/R#W_\86s-u@6"8aa4h pޚOEf.9T?7z5n8QJ/'#м^gGd[-_ wx(lM^æi 38W+8sZKb{ u)K4aR̲5DP#դcElXOթ$d~|^8ytߝ~|;Rw.* 7 G]XEQY$"!"!"(J1 "(,0 bZVyޯVHFSb%xd|`|4iҤQp@[5i/EQÖ)  X5t5kIN -+}ѭ4N[6g'!_ S8םۑ23+ԱnpbɅ7.=z:@CI^~gf'\!^cPCSwF-KmUr9'9.^Pt[KcBK^Fx%DJo ȥ9!.slEB)zl>s>($^5uK7!#2W 1bԽ͙SDB7N}{lw{[mG[w|gpo1> {gO׋fv !7XD2}o,rְ^JXMÃaB4"̡:5X(YLڼultOG?/ׄD-;v QI=#ʡHZ̡1;67]9H5 [!hW(tZΔc~_ƀ@T#:l5ngۅwDv|̡<@3kUL%PT~~M?m=⤃|X@樷j BQ@}u<)R aHXHE?1>`] 4hԨQHOݛpM?58P|1 $D-!'LNZ=⺵;X~@נY{TwѠOZKD@;a"{Ҵ_@YXJ^MeiZQ1'u:#4 ` L"y)Bh@.Y4[UUcBM罴/z"B(oegf(hHc@#4@h r9IDATϯڎ>.BS!;&O"h(H)h%!4#@7DoY|[JM+eoy%EUjm#xʖN=Oz{%Ҕt4+sS>;:iب{DӒ( g9>i څ6X{f}enRcu[Qnn4hʭmJhB~:,~ WX1'Ӄ(kn Vk3SSS;釄)?B;,Ub,twD: ?r,=Kҷy Pj}L,D͢f 3!2nǨFb `qʅnA ?e[&ePnJ+8Fӿ{ƔVNU1{ro{{0 ~mhGi۶m^^pm۶!"@ Gc"Ư~Ӑw?~sS_mtø0Te>3>̽C+mUX jd^/7ܻl %}mkSG4tR*HHt?H|o⩇>մ\]E2Z0H&ޤ]V77Truc!3 !G3N:t{m~+fsR J匯AÆt@={43.I9zr(O7/*]v#GUR F54`p|@hPPШ (xH ht}߼ECC QFMݻĝkue9˲4M40A!kԨ0Ҝ(:ZY`wތĞoWX^nȿ}TLK\%J-`Y֢Bje'"`#k7"ÉC2dȐ&LKbϻg û';${Iin5m\X֥kl`9ٷnegggg_͹S!mI}aK|Iin̸d`Q }P^ng_>tKFZPYqR> 0壐H=oBb\I0 Mrssoq''a^0t)[o )9WaIZʶ#Rҿ) '?}?-|+%_ Xm@c^C=_^ocړYp<3  =/xiažΒMAM #|m bQȑ=@ow)dfg&G04}uʤq2d`I.6h4cQMіjuj۷]>>m;QhMhd~ N^//7ܺz᪞i^Yj}{k5 {62d-{\lB&.{wF}n?aZF 6|ŹbϽozZ& 3{ٳgOSB9iҾi +Ys߹ ܼsssoG켂B)Yn$7"/w>m=ւi:0j Z(& è=3H{f;׳ 㦮=F^i!f>ĨD:$ gYVݺg"ܨ Qr-g(K+eR(_/7<2'R2F.2F.c -gC%| ql;2v~}//\)F;26H4wܹЊΝ;vߵlCAb[XJ.zac>u /̺TR>/ww6mhٲZq)Y膢Xm3m{!B%)}hڳkBdAMOmHXuh+/$s\m4 K+Oӣ}hCTOF_nU[-yq0`Camrmhbնss~1`JA1 w_.PH=X:ڡMG)l|;V ˪KҞy|Xrz,.o}YJ sieY$G^fD.k1{C^|}m\=@#cbbcbbbb 2g- i4#|mr{&*!4gimN#[y=#1{߅[YycSU4MS/_}F,^-QX [ds㸒~V(0 ]~g@ƈ].‡yޒ!KJAȻ|L0KTVYzNc_c ]˾=mⶄ_5PJLMm g֋0 8sVkAS윜knC"Nc I='Z^LSx4iPxD˖@0od5o~~F#Jme˦ɓ'K*j9h%jJUn&\|TV4 ŠU_9s^h4ӦMFvSTYW`>qݟ_[{zr)ق pT*7;;{ (Td4MKmƻңnSwk2ybj]q;G{P[`kB&jux'%P($-|Uyyw .cٚ 'X;n׺v iɱ]13{ݦ:pC?KT,P Q6_Ux v_wJ"MKˆ< L&->`ׇݪ`Ȣq*V/ .CV,0zY/MJS^Ɩep?sH׷uH'F51j@T,J$Wx[YK|~=F?xӳ{}, uWfōH:daK0,6;s`gpYhVƲޙWfLrg [+,+cـ)OcT,! FZ)Xͬ]jndvr˲*JeYpyQʚK=Z$귊щ=IOOٿċ=[JO;uUq¤0#RDv2s5>YwAh3Q%C14M2Y+# ԩٳgCCC5jd۶I5/tzPMR1 #eyZc50E_ڦL6,]Eq CƁ}{iVG?4 kr}ppak `hc"57t(-lN<W%;C`ۨ̾=zO6/zR40.4MJT6:F]lE>Eaٕ gJ+j4 sK?Ӿ>[paOF c+ۛ&05_L9gԩcVv6z% ð,mg-Q/S 23v=1VnQkѴY=qKFcDF}a~CX냢l\sv0`ο #cYG~=.g='Z9s΅ """lt;vtҾõZmu#>矧sf9''g- 4dYٿի/^K f֭[GQfۣO43 GJ,fQ3rd" ̝;744:.^إK~!$$ÇO:խ[`[\l|u)cZPPpڵ-ZxxxH@Uxj3\ U  (2r?5^cC}@hTrO/_Px3F7@ F0rIniB(ƵfY_FUg`4苋Fa%FQiTt7R1@g*wݾ7E]J`Ջn_Z[eGFUYFѠ7]Jɠ7`jw[4!nAyYɒIW~R!hD d,|k@oooBHiiiAA]n&㣢2G@aaMMgyy; "Mr*A޽{JeFr???ʗ? 4\g]r{y]1*#33}&BZtVxVy3½kj;#ow;pyjz;WW$*%MQ(Pұrw_rR:3]a)Q D34]PXHYxTII0N(:mfRLLLFMRlJ80 斟P((L2ȳLTǓ'x'weo+Oײ%:mv'oY E@5k+oi~_(y{rrO rɥk_B{GhQ=יR^sory2S8#xzzzxfm0 K<==O>]{ma[U'(}s@fBhݺ'vhTtsα,ekR7o. Emq/fAPx v (aӁϿ7}qC6V=%d׃'d^URD3$-2#-!{p^L<˲^^^XUTǒ5ȥN8D%3T8+(( zl 5`Y͛KU*p=7nl[2eN@+k<HOfizn3YMӴoTTN mS4Mꠠ Vk?ʲdnذO._-Fu /??*WGo:zB^4J"݅AJZ|hX.T fHSf*jh4m6zgp`KUHRU %yBB{ƍ n߾q(d^^^\L O \,:#lI62tsUݹs8Rw\~~~}||d(B)***\\\STT@.&/#fEET*777???9Mʆ.&rPWLTD)a-{;>ë [եæݫY ! 6LJJڻwozzi\pIEQIII 6١CN !*CU*JA"M&UF:\p)2Quv7ovi~R@QTf͒ssszS]pzrŕ~b"xyyu`0ƺۀjX.K;p= +&r \=\pqT]_W+?.&r\6LT.6 . .Yp1 .*\p\p\p' k#IENDB`pscalar_matrix_surface.png000066400000000000000000007357371255417355300343340ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Chart_View/Matrix_Chart_ViewPNG  IHDRb-n7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:{pz%[IIĺb.'`3|v`&$+ab3a3(ҞCEpH1#Vv8&NfIHx #f,yx/]g9x󜼔aΓo^ y#ery `{p0\>V5A32IgЩ5#YKD ?\hv!"gaot>sfXM!*\|n"m1Dۙn%][g>V2*nZ9lX,3|H; .:3t8uiIY"|s:Q$3of]L9،/QEQE-JV+/~PEQEQ z]ҵ(]}TEQEQ%%>}ns D$j5g%In%{Sa93 цQMیD0R/\a4wNuvcn*Bonf[cTr b=mGxyMDpH^kv>"2hDpe%C693a-.~:k_,^JvZ/ 8O Y`F8oׁy)Y7cFbC ~[kϠRs/!d"x=kS= ]`5>Δ^"m.l/efd @\ 1#t;g<`_:' .aX|ɱ 8wEQE#*x((9.9q LjΙk\9e;r[q^`B,Gf1@a7g5ab2$B׸NϮJ\\}sF(uAAn;׼x߮hLa<[igq 4u0Qe{|:>i {c[6Ef- UkcoK"Lb3b,r6Nfo#H/w]6i@?s@Ew80 >"2Zr)I`׼Cpa+3ʈ.iN .foxNf`zXC i&+("*x((9.ݱ]Ԃq8voްPG;˲ n0Қ.4!: ͈4REQ_-j((( Aޔh*((JEȽnɛȧ"2'i1jLk 甫Sa@-_tEz`k8^y)3{0 "*-f&'/.7Xu݊; KcFXK8",7&:"C8ա$KG\lkU1T٢iY2lSacYNx<ϩE(XXgV"E4nFhinwmFGDED3H~'|ˈ329;ANL鰃yc8mC;be3<-b*3<.c*i_ȉbݿL3 r\STWc4<|i0F F􃿈ZGjhqw X(B,!gGX8Q0*w;7_-fad'3// f8v@yq[߲E <`-S^BKf"R=Q3u ϋvr"czm%!.aOxQEQ_'j((( Aޔh*((J瘺ih6{f{lg.>/;nE1>?m":Im;AM rA0c>;X ׀HSfKLSEڙ; LpnЍ :FvsaP8 nn![ >7Wٸ]^dͻyk.5#8GŅ؄XnsZX"cx;ߌ쇘j% Áϯ.+Ɉ lOpya2,[qgp8fՉC#z!TیsF&Λ xxK7وx5XF7 yWff?=J8XY(cǘQ# *N{[FDKe}3,fFw((N|O< ((tviW3:g/"|a4p^$!=4ίygF8I$_X a{pz3KD:DRp[fW"^wjPlH 7ʫ!." |:A+Vϭ}np m" 7#9Ǝx/B)ԑ,s'8m$i#5jd0i-\Zagvc|3C" WmV,wUa_~ wlYliNx)/F6'n.B^&?)!",&6~XǞHS"9?H ]LxیlL2[l%]]kn١WyqV.%](()] UEQEQsLݵ])Ü4-Ņ=rL_cڠ7sm:0)/?ă2.릏C"ITʌ 9F\s=NJkg5*Np:;nߣ"-=+oH%3uaw,F8+gFq_ [84F ĵ78&/cXDȑ >^̛8+C\A\>:As\_SVxRD}E߃sHP&ɰ>+ǍN.KS䵼Px> s(_ts`.o2z=vjp"gMKe\vw%+F >yk}`uBNĿWԨqu}'Y\@h"GQEQ~EQEQEQ_t-ESQEQEQ:G-cEp4Z#p|qw4u|]ԛvw;q{bNAW젷Kpw/ /e43 5eҮxz1o`A_w&2A+ z am~w-S >B^ޝ/mq7=۵޶3EQW POQEQEQ:&2h.?L\,ozFd` /-!h0\#gZIq@v^p$LoJ0Ca;-3 ]4 Ѡx7il1 i}!E9 E*^7#-oZ^a8'sHbdHtZ&"0p;60Sw>lELg+vr=ol!FGe5 Bu3c[DZuCsT_8N^kJD~y)3#vXuw"Ÿ;'Yb& 1bzVƭfF=rtN04s![M8g "iGzyEH5lDt-4c,EQ~<-*u%:J+BHtfxTz&qCf1Ei!JWCBwHUs5-eٮֱ"Ṣe|v[4'g_U'~upb+wC,a g>pqϵ_ʱ- hZěN CL;t(s_i e}[A4DSܸ~8X m rkcE ΌI_b`7 E|Y;u{$xR &g/3I~̀ڟ;3ndf4׍IΠ؇uχ#sea C|;s[v,͠RKrho_P;cp_(/hȵaY._~*.f֜˜4^j#/(yb\ %(ZZ8!W櫾Hcd9ف}UeQT)(BIÏKCa\*,/k_ܕ^ ui3R+ݨ`y30bIA/ Z%XZ ^4Ds}/yRdj ޾ƢU/!^:d]aݮ-7.3v*dcڶ|?q]'>fd`RݿS$΄'Dpg rߕV?\'@(KOf`+PΪ@؂wQr=ƊӶ%bG;E.ʌ§"nz،H{`_#j9DߊЧН;ħDb'vg6,+Ac̏9%P3_dc!bgnd?#~_H1+7q~O3u֨Oh$o`E@݊ XËA\~F\z]uۉ򋲒KjrLϹG4B/sb X_.-LN{}Q#9z/TS.(] UEQ_6ĺWQ-QEgѾj(ry:@ R:&R+/$,0CY[is0|uIyC2OtYF" !;6c/9%"8 S:&sGZC.ƾvVN #Ě˜ V˭9T~NP|7GVϑ.9ǁ]0cӡH9]D q9G&򎙱`Pqqh$Ow(]|]\0h܄&U0*(U݅lsߺR^/q|O^3?A@u!s@Rū3SЂWXaL+V yTRy7HKPI"OEQEQeE"%;ed*Z?D/ VwH3W3][^=5j'juir>/:d拺J|wBيW$G{Dj_wCMQ]tӆ7-(/^'[/d?p(,`;u[6gZڕ͙vKϱ+.x:t4!X2y LBKab{## K%hxCI|H{SҾwx0t9 wfRp 8+ mHp̠mҮy>f޳L/ g܍f?|(8A/ـ2RgZ^͙Q ?!5߇"x1#iT32i7"bF\p>O\W>p:p ®cv0#)+ Z;\8-z[hvCHgh;Do6RsFp:j>5 ۊ b ѸL8ϴ\̱#E uNx݌l&"s똑]Tx `53E`V;iXS$oP :tJORDbAUxQ888(>! W?9/*2e0#벲]s}߀ok:_0o{c>X=䡹eIJ[DbY-sqcvVt(ȕ/ECۛg. -,l+xiu0syy+B.8rsӡh7]bs,,+by|' &pLmu+!x aa5H:yIIڟWB5d`QGryJ9hÛҵMEQ+.vk0pfH3r#l.뵐J"RflJ"ېmy8W!7^DeF:wo-ۄO.Gr+{__}̡3;#+ssȟoƼ4Z1|dMYq=b`#FsZH >(K>/GC}{ѧq*4g薦9TUP1@`9b2g[cbˢ G&Ԇxy褌Y^uV4^3j< kj/  Z\S-tb!JB9e5H9O'pxFp1x?d)hkQbPxa7TS^;+VteDbR:};嬢PEQEQDbc(v;:P qnxD<^5TǨQ2FMU.eQ :ud,jyG,{%=*Y#"|"PQ*r u /+ܯE s)"黥 CDv6)] mTEQ~BM5[A@!TBc]O*TP(}{i,H ɂV2?G&!>:Gi߃n_h,oa^++{˗vY2Y2iI[iLҘ%ISddTL|<ɪr._uܫWnjXf< bZsiѴ<;KӐ)}FOgȟӝ+?'őEBMX/hHg7fSLS$=2.fˌ+8D DttA_!{ 3 N)זǙby:>ϲ9^i[u̳'ogSֵ"80 ;x.8Gse.>䨴]? n3\Ruo4d^U;2J{I!s5c/8ӎ5%?D@T[o:RWgHT9ihW ݜfjR>u0ZlBg戵 > bdVíVR3[wsk|"G&[iΘ1;9[<%siy  q9/"r{(KJ"nn̜\-vO**-BLirrTVX8QQ*"ĢTDme~dx$u`uѝM}t 6؍t>N}llO.GP#!!%L4h.밺+p*uKJ o!^ߪJmfugUEx=KS2pa^KBteE!焢,+TSEQ~L bObu Xf9exMTT"^CU-U to{5CDbIԵRMJ}<\˒͒ɒΒIʐJLLBcf'i 6fQuoqXWn ͯ-KK8T"ïƯ(~Nn)$K?-[$ϓG/UQwh|(y _?{w[P۰tqE)DbC vQSʙzGlj*kj`Wu!l% $^ĪWQэ^(-*u9ۅz/W29|fɤh)=ߕXR^V$ ?8y+ Y|7ْVwa;f3x+I~jF :MWw&J4B4RȶZ,K$2g(("بXUN mWfp׻M3>sqPJCLl:"Ua"j*X|(vOXdIgHeH51'_Qm껰ťDӉrЄׄW_q($݌< yDexY,~x5~ u;W.*k(.CX.JVV3?[*RڴsBޔh*(ˎDbTK밴jE/_5н;jW7KIa],FZi]LK#-JBjҿ9`sf:ߜ5EKEl9K6RhVw/;ޯ!,9Xݵzej#^78W46{F%;I/fެ!+~Wzg{5bvlf! ЁɊ=a#"Xi1HLdMpF|#x۫0sBpX]p iLQk ,C~ΕLo|Xs|oaN9Dg)Üy3]<չ ~qrF"$ϙc7/8=~\Ӻ! 1OvJg k%6 81Bv3_qBpTvf4ً(z90#As:!!1c;rvӰD>!"e8SPѽssu߉K|w _q+aηfX݌}@ˬ)&9muaby8 3*C%.&T5y@6B4TSYCe!X%ZOMHbu%h߸LVz2/WZd ]:C:IS?K+G ?Nuǒx}p6.[ZGmb>L㙥VUY9r=r>9H_zƩ)iR)RE5/D<}|W(] UEQHP2]Yݘ'j*&FklY؟} C.Z܄VQ ^|+f&SR|ג62/>~em~. e.I^];G~Yjo PAeXX-{zK6Lkt tlq^u@0N `aQp d+ocq`/a#˭c8]D:~*rN{y*G~ˌ͵<vװ\QQ=DZ(@Ү}YuظTjbDkΗv5P[KmUwiWt[Vw%TE\I%/ߥJ] 55^px+*x6N׹KvH`'~C@+ ڇJ/߀OOv&cn]3(奧O}<µCN|vJpKFla2ۡRe^06)&UUsq>‚A&9#K8eA;.jXMb!288${3 ۊ$0hO!(D"ө9'OX.8#Á;:HxéVBqgʯDb=f,sYv;HJIQ&.f4 jkҎ$ Cd3b0/EJ)^k5@:Q:o wܕ???J"Ddxr>~'=rxG$" " XJVߐV:&AQEQ=Ć=f,D vJ ̇:'6T\]dH˼Zj`ev dRl,\ߍf2cQ̷4~o򋏩uWeYRP,:zw%cijd+8b] <p; Tl!9)RQ"Jy] mxSڢ(,K^Er;`uWh,(UCM-^Zj?5t2#[mmsҮ \$IYa!fx4^+:AitfZ,NO9mo\Xq%V_#D},EO'$5Ư6:63)KZ4{cQ0N$ ^YA NJ1;gCltɣטg(>#wIO84aHwծ7Jx-rnf18PyOiAX:##'7Y ے}-'<ΰy2,u_ 3#;]I (MO)"휐\T:L3`,ׇv&T2 bfL+d( <ٶ\zVmRhu-o'$ pkF}CSdî VTo4:ͺ SfaU%kRFi Ttˢi*5jKt<՚kkgvǨ0K;Cq/Z32/ ^e[j?SHbXViS?ZiRY1j['WHԊ y>Mϫ/>|ͬ4iS&OOD< (!fi*K̖]iGG]j{UmvFkgv&cUb]Wa5 ._ԥiMҚ|dc|"*|_vtNs^q&-!R<*jz9VbpAf4=[hu9J(˞+jWMlov[|s7z% ΁fd~gDp#"mXMs|N7X'J\Aeʺ{olqE wKlO:YbqTh?7OˆXe\w)-x)<4ѾRMM8U],mk2//fٮ%O.T~ MZh(bHl?`)29%iwn4}VV{c^^*MW9,[|͐:qajTKMSKI%cٗKڐ''Vg%-~x4v\5KWz} k&['aϙE]35}hQ1hN3Dްy6}| f"gI_zuAZqÓO}Qf>0#vhi8=Bn0cG;- c{;n=y+ouN"x)#E1oxWQLZmϳ^O6Gyn`|eF~т'<7|޼E$"Jp89iȁm]COh'ͳ; eo;ώ=`6~;B>{>(xrpƈ 9cy+_*E]ݘ.d:Kn@}fDBcꋺzs^ {Gkw2&9Rgf:(tUɐΒNݥhi.^C5[DBV",{Nx:ykub+? p61*q*@GWJ tA55DTLCbsJ'(x)] UEQJ;kdy2OiQeB$}_UhՓ=ٓc~E[s.WewLZєO@OJbĒȚbVܙ$$V̏)1"Ţ/we--iWg'¦+nUc֬CNjw!bIsdKRl|ÜfZ:^INʏ]EmwP/_*! mΫ/w4\1 zyYR{igh0㮺+]u՟HQ$ ⪘Ӫs\*J}QsSwzaܩΩSޮ<\4{/>Mx9ߌBvo;XK{V-&/vI=:#V|K |RǏ}.&^ol `̎!oRڑ{5jo1gF+F?Gfyr >)g{|YE;҂ٕDU/b.-nu%|k> e^m uN+#֪ZnZ&/~$;<ۧvPxܻt|l¹n㨳2#o\nFcr>)]LnDVqZa4v):ݍ9ݙEn>X-w3At5! .ի#v;r-Tֳwzjj'tm+UF fJWzA/zy'[`iVȇ[G{s伕^kG^ Y.VyhzoLّɔ 98Z?:fQp EZdRw!mXT+=DOi_՞ !v/INw忼YNUOp-[c9ό|))5rڻ!QpM`Di7H‡\cuf gD9 ~s\{¶Fm?x&=޾>)6w[4; ~*,Wt:D1|,sFDg!K^IO~IvVy 'R~Mk"tț8$ar~ZvmRs~D ]:M*eVTV)W 1ljZz\/oyJѥj~`FΙ]:[ƶsDnu5B*Z:1Kb 3sfLdIvw<ݮWrfe7j'7ҥӢ٦٣R QԦvW A] =Ywx 2=$ !C wJۥviم43?n)C 1<0݅}gR~XZlW4Jv٪+J~Xgjql5 -.-of afaFaF!C=yTv%+/AMV . .49N^o s^;vnG{?ڗJT|]V̠'a+ήmfX0o›6ڎ E6~ܱy/v /S; 7U{sύr?:A^껷HWuE8}ϭHaU*8B? าaCQچOEʎhD3R0RŸAƈ(y\GWATwugo}_ ѓxP?n3W^z ow}Ӟr;_{6]HDž 0*u2-jJ߹6V~ ;%b Ǥ8/p4}p)RL2ML\Ԏ5jaNfz>"7`xJFFF=h,;3 4GUڽfۜڮ5C+tK3ZY&˅|8WGGFf[UVa`!^r2Ge(A ` ӨAE'IĄr w$L%sj(═yRGVi"Y/z;l]/&P@)T: ("-ZX*b1ned%C 2dpa79 8ۻ9lD$M-QРQ\'XX?=ˋ{,_ -W2`h;ͦ^.66WVK4 1[U$3BP"EZKgaAa.V@ꌲr<;ۛtw;󘩧JqI/ ݝh 9F\.xħH&\+dd9 09v­,- Yf 2#vRR;ڭIG^M/R.Q+QˑDvVGܩm*7v62eCMGa(9}meu܌5 +K٘!K仄慯"/QHaSƟ9/ABLJʵ#ikJ\&K." Ǎ۰0^4V@^wȾ^cxs-Wܪgűkӹg#1yͬ 3dEf=y wY" xQ1^歚ȑeֻ0o|_ᯢAr>ıp{{- _D({aЅ Ѕ%9 oh8tjG[6-`4fuuU]bIJ[˸ͳHdԒ0;:m֮Ѐj|Kĥws2Se}%旙_bBm&gf# F.1?eU5UUjjؐ`=0T!Լ4^Y|R޵l|!O':Ԕ3?.a;&+'%{}OR=*y([epzl_5.ڵ>ǂBf?D5%TA=A>cpO=#x8io^`[7{Py YD\pcHqه[R?5UZcs y|ضpv1 ĀّK)^L4;wIu@<#]LѨ]j IDAT"6na3%pQ=gsVDuq:z8e}u-+ُ?ڹ^Y^Q=M; DuMnLc{Ssjl.\(P1ReHRlZ,32s+\C0c::JX>-Xef̸r)-K J x]B̄LSw@֏~ B8h. PTgļ)^[]ofMS;]$:7~d +Yvޓy5,j𞣡c󂔑߽SYNp/'+%*L|?9jx*É_"KL/26eHkl:c漇Mڱ14pfg`X+,:icWr-maCf6F쪣;v5*j }? FAo\bn<166k\c([ٻLeM+G`tEnKV@"^!_XS<+ tob&=GV}B2dȐ!C v"'s@rݱ4N2dCA!&bvjwAUl5P{Eon,@:P0]_[Y]C \q%dX+]`z%k@>p_9x%o0@+76 300h/};;gXK15F 罸O+]-bCf#3QfD,R.9@1O<c%9ȦR23!N&PAX[P؎%ၑJfI+C*fvTᎆ,E3C br`(1'vy~o a cv뱬Y,7YakqJI {W2:ױ9IzgҼh]1 gxM ?4\ ע ;c)oܪ4JbζW:v+@RHY7SvdR$@6yj@΢#>K]w$n*Т:ގlN|_}k8q D>#&^$lr!ˇfF4w,~Phyҧ<=<3 vBox>?B:<כv r6Kے0Saj"Y_Y챤cvYαRa)uBinn0Qy9heڐKLgP>JRW]̽9p%ݺׂX Zܬ wveS". C! u ܜܔJz1ZC3DLV.?u%46ߒ1,Uj<Jd;Bdc!S2dȐb3)vc`.pR; )[ ANEcQ ^sdwke#KK(u셮L3mCRb-hA^N%Uȱx^]‫? X&X&ф5¹o௼ӓB\=a$a E]:5bluU|63Ec|:)xiRF4==kwfnגLO&ffYZ9BMw(55ɰ]7 g^uͳw<^8#OA { =1Wg#\.{"j|?ŎlGrJྮĥC2ƅ瞢MOnÿ}ޫ8H4kʩfpOq]8|++±|/HAX1Ui_O5X9^+^h=g{w'~omNfS"eqL4I)'kNjSM /Λ7jmc**\v[z,je^I[ י3DSn]S9M]]|Z.r]} 4o9y&uӮЮ\ȁWsw6_g%WðA ȓd;S09^{YR&í3/w`J5ݙ Z., eڙ<%JТ<4Vblb[4+}.*AO])mWkZ4$'8cfodz+ցTCw*WahV؂ff^NA. vog:N#$x%JN@{R'1@H|"JN!@PkQ!j uc?`7)_ v!C wXݎVA RɏR#!^2\ZdJfqm]EKV)32C+T<]uloo*V݀^Xޱ۱S[aMuSJp_-9a [co L k-4xĎiVƔ i>@M0эg)!!OZ^ܣSƁ$=qabTHP҉ENpPD eV:g1^`n͗dvQqhyaDxDATX!5)^xӬgʯMs{BN? 6\%w#O/9'9h'h~g.' o|]<߄W_JUX#h PFʸXDpw;)Mt[\)ꐧZc&-d-9h;Xxm6 ƃLÑ"q@~^U걼 }WK$[&u&}[I|DT'w#o(DWN-O_Iu nx ԃelVVO]ϋD}狾*e͈gy2wDS;wpLX% ] `w>I(]iXmQlQ 02"Xpu_q:,BK1rz}~[S[Sbt {nbMy{b0zmDaXa(C} lI9 *[[6_ӊ*yr^}N@Өv^YKA{d-Wݧ}kw_{x}&jzVVd 2dȐ!C>&)vzrJe,SX2A]淫\>tJ((5iD E;hc\`k@ r+{u nvVN=obMl}Dmjgv7 A=ބZ>Mv:u+ݺlxTcg"ȗ))T(&K[(x_T J/ii:݉ E?P=bҘeJ(M8\x;mtȈq; 2d`rXsTwVŚ7e)YWg]:*ԺlrsM=yLޓWc11ˮD&2;Rc<'O2{} V0眭E¬R 'Ep+HoG̹poI+~3-0.<o'|?߾_?x)=_G?GS\+E^j+E-?`Hk[FN w`HpFQXu3ͼfAvh<-e/;fK /2 5C34O<ыAm^Awmmkg/QF&]gu%?mc/F]=;R܁cQQ!+G*;өG .Ou<{l;)xX1ދ7ES'>]К@WH>MIR!:gp"S2dȐv佡bϸ mĤvyƎʛw}_G47WQg]u|W@Mn격ǬK{E801cUyR)r6|zVn-j' uɹ 7.#W;u|w;VKiĎeuNU gd(`X ;Sxq%9\xG#Z *8]ޡݥݥۣ Q)ȇsNM/;Zϧ~4p27RǏNi= ;Aa?/'[Wmda}u^mu$ŵnF>i:HKEnn1kMH'pOU9yg ה‡.0><~[mk=x=ξ6kf)dt/@/">4.ح;r]TpFt_gTG\ Nc-vltmɎ6j97sQbݏSMPԑxɗy]=9  .«!pF\Uθ?͑Q/[po|b7|ڎ=LpnsJW Gf'9ӛ\8 Fye؍Q!ﻫWƂC+Lj@ԇ8C/Q 4v|¡ ]K /c m/][{-BFFAwvN 7ZR+ߥ"Gۅaبn v9 ##_aK fΌ6[kt8Mɧ3+x:d8/4s LlM"^^٤;k'˩r>2 2dȐ!C̸ v;:N* U0T45Kem=[fҧ@"9LczAM?/~ Zc41P,ALn$1Y1J!LļJ9C0y(c/U?HDDDD!yɈ5Mfš6ދzA$':VUk˱JD˜!Z"WT?KA429[Tᎆ,E3C nH!lK´? H  {GJ"u0EޞOvW2"4)me yOEKkvXwenlݞhT)Nu}E\Ew/x>%o44ClBRS] 4J+wdUL⛡|_OԆ2Jikt.{(i{ U3%V+Iv'luhi ]=t"WT'jDMDnvf)a!_0d1 O| ."d;fO/P/Rϵ#MfӰ>"BLjA$?8\3}3?oG pXjQ*o݄8>v{uv5cL?.Y IDAT^$]BHӜ0/rA`D!0Tۧ竼Q,tQ%mXx<6~jG~kp \i^'afqX6€Nh$^6kDW%'[/% auy#E 2c:\oYL1&Z+{9-5q90 g;Kʐ7MNIGfu"f} EE짲Ӆ0w_K=GP** *,ə![ Qe &j+ԖYXo$!c4G2(xpѾUa_t fPӰeב)Z+=#rE  (晉5`թngx{AUL\#VI.0?T4J̭鵣|8VaBz~3;v/C]BeȐ!mny[vݥ wl?-3|f{Gt;Mӻ.zN'4~ BsD<[dمhy{0sw݃f%KW )ϼ&u lCWƛG(党w!mWK#`x=C 3dXVfr{gkg񤂗l= gc`Dbx}.b XkwhiiiivJEJJR-wXYaa^sgs&|;{,«=щ륛tJX4̵2sA>u_x)p*E.Et9G *Y;*x:3\ r=d$}B&5֒g!|z,AhyWPÜDHّ+ OGh"G F^](߃EO3%^(;"x4Et]a@mC?mW)߈U)JH)cKtsIHքByJ`HC+27XWg'g*EWY,51$ j` UAgK& i^y+խ:MeLC$x ^R=p}q zsRv~^?S2vKvGH@.O.ONWsWE:M4׮mbZLN:3^.!Rzfo1hENA׷F~t{1GVvhts?7Fm??M믦~Sb7~2d  2dm09@!mwݎ;/KzpJw r ƓWDPpfwkWƭf}72HSm8}Pk`Ifb:VYgϡdOf8S3keI?zЁ,g ǵ0#U?GeEE E e J 7( NlD )O w/z;{u.9r\,S,QHq$@u-8s$"v;nD^/DtLwch;ɫ1>5_KhPaE.GG' "6H,=t_L]~wFde2dȐ!C;&'=n̝XV\6R%GDB PODGJ/9GЀaAurFV44;҉HC&jU㬌/ eod^^t!usmhag;_C;49Wo;"1xU%}\Q pJѽxIYQcuhg|\ѡÚ |gKα6zdRbBO::tztz/K&V̤͇O>gS?1yU2d؝R43dȐy.;I]%uW/(!M4bc9&ezȘwxC9N^_vt%~ÙqV^OAd 9s^Q}|r/eel$J433t(1LtIRnrR=: aLY.\ׅ ^+LOfs*.|_b/Wd4OX$CK'bu%f/Dਁ ~vk9B> y۱ǿٳ]ɉ,uc#r Ok@oM}>nǎDT5W<P9i^uX_jϓwiy#o81R?pX8MDَO>mA)5jG*5[EX^kx 9sɽrPWu3ypn(VbNURTf9yś*(cj7U6 cԡX0适Rѡۡ!ץی?~Fhk-K5OR@Q@oz%驳.q7~=k7w ݨyr% 4B3"y?P ᎾR'YO ?aE'ԫUϷfj۵|s(lC˶m$$^vz 3+a=.ȠEUئbjJN9tp[Iꪛ/6z'\4-QydȐ!C x}&7U/-j;N wC(Pb"̜Es|"Z ԍbT!  "\|e!%6ߏf()FUUCCKKr"I$ySeT).R\>/̠G/6%]\Q~s(4̼x3|'1 h(½ȟ#ov)wsɊIJ"x /^pO;r7ש'Ôz)v;9!NX1k{]POgXYoA_0w"UR\ki'd~sϪ#œ=S8<4snyjJmUutm.8L&J+vy 32[SͪFiFEQK:dGSA|^nܒ;4(_$EU{_ W{ bv5g׫ӫH~u0~u$;Ѿ*:"gLw3%˦'rr]]=yʫ,uu#0v+zk-Oܶh-Z@{%2\q2i]=#x=|fgn+gZ`_Χ&++Y3Aq.ޥU4D<;t:PeKSI}C|bA["e=>{GrW4џ aEzڄשSs_|kNsBB$љo`3{uobplno[mG 8 rg#2`i-5Էp>ĩpX!gmϒ?WUty/繮,g9+{` -\X 5o>[Ȥ*^,t"Ug:Wovź)#fw@=NaP\| RB[9SVᴽ5#ou\z{uoCyWܕI55uCdȐ!C x}&74bIԮZө]s ﹵зr\~ MnNzh(j[ݐ;ubeC>Hjb&-**}7zv3Ϡ;GkrPAJ6E Isk;Β;]~L{SuuO= 񊏢0*I Swo/8"JHR2RKK^>C{I/Lza v ,a tY7rs`Sٗ;>zY| L֮aV 2 rҀLrOu(fZb<Ώg+TR&7H+Omje6 ќԦ6f?uaǼnj`FB,2`YFKyc&xe]asl x YJd 2\]Dbl*UUlh0xj8o03$MfCgE`7%$i|nAGxt.;!xm9:!~U ń5nF Lt:?,v rƨLF#F#VW9|8xv8Б1i5XKv|o1;p̿*iƤ;5>rr3z$ nD2b1uԮVB/4=Ȭc52Vs4%x|ƟOb% oꞇVcAd:1fZW2s7"/'#?{ z=Lŗzz,kE HK@f)>_,Ń IDAT-ty1λϳbՇ_OsNZ|̒<5%4u 0GAK-eV>[\ tfp'] 8s}YBN\/RgϽX|g,UReFG?`2__Oj RiK gߟZvGgS YqZ+x9^H|=Wܝr\jTx)9«lԗ8~l^Ѧ|Ԏ.2xSԦv졧,-`ǜ`ƣ+Jt'2`UOkGUNuЮt;1\wNcnE2_eȔ,!K򒎺/Fg茎".lV0CҚ[4*ߠ욢g1'kgj8pSސ&{qZd%>uftOy<ivaNwfca9+ۿ103h%`- !d$=yȖw[Lεҹ8Iv8(wN3"MISuVV8x.13{3{V]zu&h0pΈ|FTNf&3$#?"PZ)^*U1^0*R▽\sx$Mom$8YjR}'6[R8 >^g8ߝ2W"!z8$jɂI=+80xYp=\vӭC-/z]/0o-p&OoYju0C~UX;n5+Ͷ`-k__ɩ& #ŻJSx>ܛP=?/Xo鏱DF>ştѨ5ujC??'X~ 577bUx\sPo\&agns|*; @ZKE|WZ<0rQP.=_a4$ 0>̙k gVV=<y59'Y+W;8l~8դ A\B&[_t"}gawx/L%^M=AWt-ADF4iݐzG|#u5w*a3yHkM@wbc0f_­%/Juc,xy9sޠztٴccԦ6MmjK{Խ3_ fvA,4>k_qzj'sԍ^4N(]]yw&$Yw͆k?0}.w O-J0ۯpv /[Gt-*ef@g0):OLuS tWH9aa&?iљ!E^)R-o3dQQ)yy{F- < ڡ/&h9W׬|:\^F;oa.fm4%M"㣟`y|0ls=g2 zbM)0ftb*eW'.ג? C<'\QvΎHr)2% #`IZ1FizZ|,_eȌ$$U kojSϴiԦ6m}>9n%!ܜ.Mf/<-2  IKWrO{̲}zX mN".t*E3 Qk#-TM^÷hZpSrxō x yЇXU*^ՍaOγ~uB9dRDά6iBzy:m&*НΙ5L~{7ӝܘķ$'m~ݤU\g: Ln9r@6 퓮zY$dx)ِ-m6\v_X>FaZnri+}k6wF.a!S,(jo?j͒uk"L2xOkag^~F %ڮ]v ]5ٶ4<>h^ZΫ_'/S Xž@p}=5k˯xP=5 s 9ƞf>hy;Jk|l9?⺑O}.49gZx)m%d9L!EVR8w1~͛:z9t3U!?wX N ~UL3x M jj51@-\0Oa{2|4oW^_FT*jt#ɳ n@GcPW{I`B9TT.D.1ZfTidCJ,J.^4tUKWƤ N_gດ|]uyѿ+x 6dH2X[VGC,`q?>DVF kwn8:|$r/|Ӛ0lH >k{|-&/68P ʅuo鬿Ωp)%r647¬\vi;~W[5?koȷxS-"&SqN"nW CuUC@}TO1a֢}yż4t8΅YjQﳏ} o9[u l;Nm?=μ{vVwafL̐';EG\1vy)AYHO~Y_0yz#tqKKdkˁZ[k~c=?{&ῙfB$>j9/EdʹNAjyCkO8uaGNEw-ݭ`o{؅&,^?u1&|αc3l)RW)Ttg4;QJDͺ4]R3v~wR񫰰tfcqz&3Vb!x-fUЮxM}T:2E%L $ i_!3G;&8LBKG+& Y+nCpEM{]&!*^ sݕ ßz9Fܳw7Y$@}|n bXr* v2H`a!ox/*0z' z?/š")uRKXWdfd}~T@^|e_h0OGbdUDr`˧/Y|,;$ kRqwHoʇL)7MmjMSzeXf1. Jz&?Nfwl*8]rwU6akݵ-27BҬpx:׀IL qwYjZJ3GBR:S0CVI,ϦT^}W&{ǬtY C!APBv@x: r^O%AFp6^Gn&W=@Ȧ5`~`uKfxZ.z(&l-Ȫ 7rt 5 nnuRQ/NqDžyܻnоqz%e);26cEVpd\,IbzlZ1I A=6XO{{z7,;=AY!C|4~-:_Iq;1s>vk")H2`8øìgR1DL`v}wbߞ Iv1#u':Uh.sZ5~{@"/oF P2:ir&g찊O95 *:= Nvgj0;乳!/98VwxVO-;[,{ڷMlŸZ_Lek&Оd9w h0kM]iƻ gM‘Νs,"+LoOY{hC-$W fw;Gxoӌ.rwW'Zt ."%Uĝn3.:Dzdgt7 픫ЂC00t@X E*"IWvXY hWl *(t}8Txlkk-lD md|ksl-1]\C!6љ!"lzz"h D~UQ2UŖǍ$r4N?A;!A00T뒌sgQ|xztUX@PnT8)B;7Zk]9B1(WpW=U+h/ dsھ a<'KduqTФyT\7FwVdרwSUH$TBW2)><ʰxT8H.,acЪ]_k>!F) ,#nk#wYe9c~AV1u\W1F7MROCZY4ă6z|u{/kᝯg΃GX'uyM{uX+_ѤR%r%ϕt` am@>:ǁ꾿rz4D>Ge;g˗gz M|ՙr?X)&@-?V_Ar9gXiNbHo4bK8{iG^4g67.]`D5ޝIS~_ p*?Q<g5m]a@}1kg27pu{:ȎP,l dA3V,h L2H(\th~)>#Q@xШYa۾ ;~Z~Yy3CqQE2R讨o,/*ͦ.cv.`< U*wW#DR4#HF Cw Y]5u2L]%ղPr6&u^)ЮDRfibR~5ߎcA;8apwMW*b5s%y(dH[:-f1o{D;To<9-aavYaC!S;lMmjSZm C0#CQ[GwAYڗ&?Nf~i8 :;\U;걹@w՜f)Sdw F;ك iե.SnŪ|]f->!|a[Ok1|F><7pоFXE[ū2*X[5BfC7*W/cS_UQ2p`+D)Iʨv#oosKl{W.\ rJ5;fruG*?̞y0Ek^*<+3-zQKq-2ZojѝQ*9+VO n:޿ypXJ`_[X%59]=v;,Uuid{B/*&RGcĮgez7v(s, i4 v48?hiC]]Y" ȟ.gIw̹`d9#0=s7/ę;^tq 7koIs7k@/x"qgx#u`C/b@Co ]{/ETDnoV;wYBw"Hח,ޗjS;g̼vͱc5;uv^ԍ])YɍwMN:~eO$CCi\WZO1%Sd`lLŢ2` 0q&Z>foH7%24`f5f')H#ʼ;LYb&e`a>WׄsGJKVUiVͲ4n00a [ZWOYjYK '9D,M]ŵX苲:y`;1"JF  FX6~Oc*-HFߗDi$g S Dhg *Q;_6k?7vS!fhy~*yKqwϱ<py;Yz)d_3Dan߶pZF9y*%Y"̶mLrMޓ \'Wǁdh'e;zl'5;dz*i3 tGWY1c UdŢL3aZec*p敕Ã^B"E#€@2Z7Q(f'SZ)шH# KLXUfX$ln` In>Rٷ_b(tg @١ $ mjuŰ [$-Dy"AF`XmŔG Ia4ey.Ls;"!KHSz$QWFd`D*0*$޸Ai iA⍐-aLE ep`3hʶUM#dkN΄V= [a l7 -}Wb a!dhI]50qS,hU:aZtt[t[>a#!Q)thWSv`5ʮZ5]T+kxb|Y#Wkh#㥴;޸ .GGȿq'7mVbF1X$H@d^9Uh!a%,&NM#/!sV`/uupx.C v`G`,Nb)`[ܠn?)Ԇ5sk!Ɏ'+jQ}}Ǡ,}|64䜺,>0tNVʠଽW[WEWӉsISsvҘFLrsY!|IZ;Io'Z~.f&8J#U ]N0Ne[Ⱦ^g8XE$Gr8ј .ΖZ=;_tZq-njc|!)*c/oy|~R{>a,mBD,jβlM2%=Y纬ۗQ'53Sѫ BZm:sl'eyΑAUUh)YV&pNZ$и"jî+=>huuӠHC%L)t ൈb>9Bf y1.`T|ĬRZ>2 OZHh6_iUB33wnM@C1q!hgO+qWh$ F<"nnюЮy>́7}Xf&Z\(co\Qڴ&H2؁zzGTe \Vܝąl' $#I*H!dA 2Óý;1Ũ(i < !G):HW4' K,W]Tx<~Ԧ6Mmjmaᾕ[IQ1Cd ĝf]RӪҷivʯn_6H(ʊ a53x9T}Ks2A& *O* yCSzR f|9k5L95).U).rZuCnӀe6 ќԦKh AR(lJ& anE/҉i#L5&ӈ]a5瞀+.j@UP7*PIVL` X 1հ@%P!^C_' CB&\&=ĨϾ{r #Y./ˈC< ㊚1ځRfvr8.t"^ۜN1;4i#$˴/p]Z'+ge8*q]ho\ei>v@[6Gff6W4y 5k$k WecLjN~9LnJ\waGG%z91g!ѵnM@9[nq`z^ Ƹ9ݫ\ {9U)N5q'1xWߋRpF.5D.>*pwg2\ O#Aۼp}?5~攅iP$ZN?4Tj\2$f> X[VO,ppFr:l؉3s9 M#v {N ]R^(g?螿(}4{Sg>{g}eJ0',G`u~c1cf?+Ștxu+8"4E7 Cx'{8˒c_FB|Eᬞxph7x|YH]yKv.U^­&IE YؕjKo<:4džwѬ\VR2*=XY.g|ckG/[X?owQ뎚Ϩy[`e5jӊiZf-;B;wr=sLx&J(d31s8 I *aPHT]mcbe@f1Nܔ@  GSOY}1UuW쵭w۔eSojS/-,m}]c[MW}~t4tpD' +tWwus"|eƝ[OEi@"IBEk 4n4w*WswfJhꪋ**,>cu^纜 WO]ԆQ(ˡ-%JӐWQ71't8ѧSU&ZULfn)d:>y['KDWNS}͋9 }|Z>O u8Χgv:٩R"m M?i{j?az췰nVQn2nQsxb<p''gzޚeH$&JYJ*j`}X#NVk׿[ .1bзq6xCfxU 2/=o[tWuzvppU: 8U` 8@hYΡKEn6C:7?ŏyVۖ+d9sS+?Eo\oUvd,\Šr16]jgP kZbZ-"k4fsӜ*=W =]찪>6H$^\U(Aa0Ml&c=e-cFwN]4SnjE[q?p>ꝵ1RZA:+G U +f:8?uI~ֈMA"LZw<2-ǥ8=[]~:ɰMqSo:@pw2ayrUUbbF ojNF$a-1dZ(H*3YxKrb*2MmjSsT3*NS#^ݩac6t|VVDdJ2LQ?P5vh .p@AWEI}aBR 3hW~ 7( % Ozm$X+0qIK`NW^7`NVa뻍☘[jz5\eS $/W!w>R4O"XՂ_CR,*V#6\;5YU05hJV[Oc^mƄm!Nd^nj}bqOfpn#"S0c3J-M#&պ/հZp? \Xx]r]h-1߲iԦ6_SOTUqvE(g͵T,?HU;Sh`9OhJ~St 9TYz*잉,!KI2At\Fc u T\'sBI(aHPg8>Dzlgs]&gֱ7> |% $Q f 'pϐMMe-`'5qVxX}VԱ9|g=J :찳Έ9A Vs4W:ko..7#)$X_?$9lJMuRro۽|mb?uԪzk-2Tܲ53%PTYp\v&}o>ߠC5^c*hHdz*-rAzUNK W } xW?*Y+˕hz!ЙyUp pjdgx[h鼕q#d8ӎYsB<}X_}ʌ(S ]_@ o93;&l`W34TaZwYO(ys~z }W8lp :&:TמypFΈ'}G*imx|;ٺ}*d9gU_$˹ࡺ[%3m?=-25klv&b1$_-94عi 偿Com䭏~".MR̦V9$ 5#&ٔ]Sq*WVswL+$fì2Cڜu!SXMU~ź8oM-hZZZPm6ob!6h6sVH&kuU"!Wr2P-%|^@ D PL+6,YT22 rġaH/W<|&qy[|u)q JmEiU=Qg" wLإbd,o$UxUm@zMfD9m9?j#^lcOcţ_I $ 9ܯzYnGݺ7Ԧۦ Ԧ6;-,'Eh<۽b?DHN>1ûl퍖x=XCqrtvlD#"a(0'ݍS&˨ڼEUMP$71SН lS:Ⱥe\*5]!"%yEXמ@Tj }v ^D}} OT'Y੗DwFdmq=!30Yp{pQgUQ8UZN%ZVxQyioŃ\wNا?^\l2B]rH;3y<mnU~!}5Fkdkdq son=="nPdm{2ʍX?X-U$^!$v2 K);bFw]R*_tN pid<*)X!ppbo5 |Qsr摝#H`KZnƼY3>T>.nS̨ \O!ΰBZY GYj xk|*S6$_4BCD"xq.~"7e[y깅篌b܎CSNׅx='z_38I#8Ry!+yx VwNE6 M8 rڸN`5!K8 OeùtBN0֧Y+߸Xn7pJ6S,qsN 2Jb_}s|bڃYձ IDAT]I`# [Du6*\TaBq6<#K}uvǗ#;npg·:'luؘtNGXĄT &^XV*5 4mXLZ|f ʮ *Gܦrn*S$*.KT1ӫ%jjR1$ ׊[R"c1HY)] /Wd+)kV)jj]u}&.ra5*Ur2xRTω&o_E~0Gk|_t}:>*y c_! ?F#WhS>djGMMmjwlSUIh {$=F=ɖ8[1 ";n8^0a-cz1>O"BB_Isli]CjMJth (%ǦV4]PvCe*bȥ)Rkk\qV ^h]/ W̾rc}2G<5,ζU7(،SqwX Q$HpGyO)y+yS&kOVdy^ 圚=Y-`dӳwԆT&ǿCgtcr?ZUs#iᱹM'-bpfvM6yy5fU]0/W>I3VkVX Xsn$A,Xt>Yx4og:r,c;Z{f|9l{bgHčlp~5aa+c1pI[Ƞ>^t \9k9? vFEtN3[ÌM+uǫRG-,ϕHlImHڤBy+Y._uoyC% it|"sVR3e0=[AwSsԴEwMm1jY\;ǫ x>z%.VVW9ÌՔՌUtl^3~*P,hVR}L&i=f{v!JvWs_Em׸&Mꪐ&5OUzcO]91duʀ妊 BfBfpVqjɆ:Oĝ+ =''뤢L$ r@#mqIԫ3yֆKsC-``JyGXS.QX$^ݜoa#d,qiSAT$ў2AGԦ6MmjSϷ\I`1˨G2SQQV+z? YY>_y@^us4T'NDE+&j274nc)ƗfQ) h礣Ԟ6SJ*b}4h[)t]Ϩb!a&h<[iNO*б1n^T f{̶rP* DJ,q^ڍ1 d !+CVSF]kC֤v^H/DJ7Z5'tn7z-c)B%Đ|H> hfN9z?N\7~ym^?Pc uUѝ,%=>-DtN,.Oyt,[.,<('6P6*hNmjSC½!E;'-?GgHf͐Ŕ3R;"c~+yw㪿w`E(gQ<3mfbJbnHeNNBh{T]v%ŷofpĝD-C *p5TuL*%Rd uu*!}qs}^%ieMeA1z"8-úoWu8i{@Yj,K52F5~,rÈ!k#VW8ơ*Bfcvt!ԻlR_I屙jSp퉠Gh̳,^!!ِ<+tؠ^6Y}Gvf<cΎ.x`!9ԙ2Դ&@(kGsNw=iW?dG υX> 'Y`8}a8 {> Vv}DT\3/\sSH}kh -FMΜp29Rֹ8dN?uI8+V`,1#Mα)|a%x I!V8 vID<4=' o%]"ںhq]"f]=Pp3ji|z~ ZbɭCfxzk|^eSg+}?O'V3g~s3h|e؎XNk~ZϻWAɸX>|9ۡNgذDz Ke !OTUM$x)~L:[WuGE7% Ck^_ /y5]ŸEGM˷&&MFw…XMu$tW]Xftgt EA,o8w<+kKI%Y:LLV:'+E*}\+,e9a9ax kSrjܠjSu({ӥ~Mde6q܈uʀU*/43GocmTavC*  Ճ6Yi1giM,,]̕~W;&6+vkڀϦO'h;(a:Q+xR7%}r(L0jwQQEOg<]讓v6bN&AwI.Vѹ>M|Q#V]B\TR2d$kif7UH$whr(g*In4ʝ9~;L1IcBDuKQߝr %!)U~U@%Q6e'R>aӞ]t@UgvJRPu{y`\Qb) il"Ht4JAWeRENcֵ(&a@AKr2~ty37`W0O*$WX-zw8 k!F##0d9d)dɧFoYxr M_\`.q( xb TVݥZt>^ / .*Ge ыhl^NOs~Xjy&st* Yҗ VFY9#X=L6yd rjsvX{v8T ='V:tXܱSx:L0Tpc0ʯV6^;vp*𖳁6 l`(.R#B35Lg0p39JEEq `;RJJ:e9ip&hP92/K %ֳZ6cM]>#u܃vںS6FwiEw*΃3AnМpNGf-; (}7w즍2S6E0˯LwdFa\2%R% v26oBmm-MƲ4ݹO-p ªuk#e0ʁYSIyRnem\6k,30(+c,nzkGPB} k >ڝxwnvzljȃ3=s^jQJEw8H:1e$v*VzMin161}befT8DwKx) ](}%q(c1\rfj*W&]HGDel􅤊 KOrGrg2Š.'$%2*YΤVz*>~ mpm -\X9;0ʼn&Z߰OƱ5rlsm+8Nֵ>ks4k"{d3e{ ngjlrL?ŐO{*%FsO8,i19:WgjIXbqItY|iL`d'GsǤh% :a}w `b2Kug:+E74 Ĝ,C4+Nߎ&> ,;v|wuKۻ X-;#݉B\pG"&`XpTt<Yte9YOf3$p&y:O|Wθ>g·R-|jz(Hd 9K{OAj)+3 lSv"kz~,5pyEY\Ayg9{]{шO3?Fpp}`z/ gχ*-.D踼3aΖvS.z&Ce^Y.:=HvǮ$3p{B?8qF'.THDfA}>%;?X$E]ޗmio.*l̖ ^&v2$^/7@~mډ]{ ɴ2R6v?P,MRX!0Yf"yᏲ}_}*9n v;fVpabo#L bA=}DKTİȯߟ+)_\l q;4=*+λ8ny$8N.ὖuKscN^ '\z/QWYx dY7~gY)A.Mh2?aј$T?ݨE$8|<9IQiGb`Bȋ+*יc Dc+S](1:׹b7 oivwZ"^8/1QYp#v4 ^h+ͪ+ ?KM2޲%0F?DLD&mIʅܝNՅ̼6=M ݹW@wz/3!Gܥa̯Iw1W^XUUo ؤUuX\ZoxGp;][ŢV|je6 ^ +ʥixE<^oAHtqAP!FܼϐOէZu*HQIeu-4(+1(k%: x#Uh,21DӧcAXK dZ]yD'!X>>X:_vVE; ݙW)6Crita ;vG DV6 l`m_%JLU{{]!aKx&&I! (XR} '2*Vg:.vup59[괮3%rb*J^pv'7՞קHWA0f7;4tS0=Z%Cwipf֯qHzn_횷*ygu^(Ǜvrb+|R-p9NbHDik#{Ih/\T0^./i< 7Yj N[D!͝py ~|^%!Gio3& #űXN7g߉bw klב 1">?ΓYPzahCGɻ.Ĭ}uǼӷvD8W(t$_8Sfuŋx5 لD(Ҩ3ݼ|87;Ǣ}Ż Vc*0Ẽh <|7# IDATBCw@RrНr;9]t.!e9S]݌ԫ~&:zy4Iqg[wL"̓31kSC8.mHVY,-1ڥfx2օ(2r c xҟ #9qbbU)|tQGVsE(.jR S&T6Z!1u-J>S7GD`Y!X/du/39ћO?tk)Xsєg*j-yLordJSNuF55|*kpLj;v0l`  l`'%(gk~Hm>D5?O2w;cL21dҐF3,hJte32xttg=':*۔x0V}} )vQ.]ѵU߰BnJڕKs4;K׹6e>4(鮻^uhׄP0˙ ʫVy94Ph~鸡[eZH}iEmy@؞UsԐrye1|MO1I'`($(՟Sg%]i\X g|/ck6u6@P*xHe=Xig]`wq5ӎiŬEmK\ ^>CuJЄqc?,[acn>~/jz@(p/R AEpp xN䩦 Q. < s*B~Dh=nn M"Z/|}NytSC\&NM5B,}H8OK"ygYw.l?r2# $ɻ%bYyU_ݰR '8t\[B4\.24^|SIs ^I+/pX/b 2W< .W.($cM$BG=$7]G ~cEPJߡgQvlQ/I=W[B $bj9gZ&{[,uz)_Gx-҇ЄcR.shb"!5bj8Nj|" <|D<gSReg9,v"Z{鱄Ir{#)zu\V,$dT1nzdsSDƒgʹwy̋]ﲒ,)B>nN/Bw0НUva\c*Mr{,~%{9}jߪX:Y|q._x'rnuHLo LQ)BwjelJITg"pz$At6 m|y[ӆwEfgz;?}WВ"N̵h|̈PkRq"Y;Н5*_/DwE+Ap8hNMd=VP ez z4X1;U$}l%+1weJd`10xe;}P<7}899E7}w,.zL19T o6܌'4g <]ywGVeT$=_@.N"h ^'Ӝ*5J 9=Z-:.]='H4yOyΞ:{\a6δ$:Qie4aLww*ǤSӸPn8NȺVuSo666PaOէ3P󩖙r Z((cZ`f0ٔht;Jtb1u9xyЁ>F1z`xZ5W_6@.JF٪!7Z bozdTdxs)[_[wgfQ[ -X-Di7c^4b:v/}U+u9c_l{u9J$ϳES? {<r+kgY('$!㥉Y8hp\պ :;ՙNO0mfIux2iH]/U>j9<} iᴿpME~)}~?v-_3=,m.\rgŔ"/WoNK *U&f:}ɦJ⩅sJU J=B{ ߸PӴft<7a&="'*^eh/ 8g 8|Tn<\#C Ys;wquۉQ~/+QVLO4\';/oNhGZI]q;tuCtfi.2]N{:;Ws ?]kNu#SA !hcՉlD"]S.wQ) 6n*Q:W}0u=b„0!"qu)QD9Jڏ[uѝq´athG#l*1ԦަѦ&3rkQi~I媡}*GYee,58?Eay~|ץ670^й= X>~02Cm~j֨}UܽJs9+}ü.Ih*rih.o1c"ypJzl&a`6i,y-3e#f)7:f2*Skxix麢zΚwtԹНdttoޝճՅ̩ioDU?j9tJ1&QDW#j;>Bu3) LD/d>HCwmPkOk =ŌϐǐO3֜GGΩ6A .@N ȝ^lkfMl:sb˜2eE@L۪7ԟ'KTVFXau1V41!xbj6y1 t4K9Dmuj1Ո׈znLN*޻4{`A vdf3P`DJZL+l;K0KIx~'6$[3͋ t44xOIEYPSa% YJ59P{ŻxI"wPqw,`ZMC?[j7JsG-{SoP$Z`*R̡YvͲNOTmU8:>Ӎ^5@ Vf?Ha%JR ^i"U QmpTDUj S{g]wi6o e79f-QYXi%lf.xX8\r'UEv`ʌq g)"Sᇼg*v.>+P[xM^~3,t7pp}e^u{wxE+4/7aωL^gKLMqxUlg;BMNmn^/ ] Βz">i~Y<3%JpkNǙĩd%vxe\թy<+3*ss^ksMB5=oퟱvﻲ| 7E?{z:N$i-Cq4ud*)};VɧY΍^@f%)1_0n)cHqn^꬘04jϱ Y(ӈhHj벮5T$4R9yfڈuGuXQ׬6Z>LJ/Q(Adg(gsI2OQ]m:ܥVFY4UꋌDX7yYIOUVP5BP {i:+qi% O*@w[i0/gL_Gӣ'i-wR V+AC(6`6Q0e 9喏/"8D麋;PCƙ`j2C}НSLX3xQrdEНMh;aDMlbXc"]gJgG&'bBM͡+㿝/;Jɠ*Jkp]JJ%!uxyZ^uL'Yg9}RbС]: &;_˒g56đUئe9yf,.K ~Zb03QJcezw÷cwG2#f$*b$w65l tp<ǑKwGp%q 9L@=rn< ִJ4cn(tHxџKē` BhqƮe0x2 Egp(v'B~N{A>/\S`~PLJ?YK3w ppez~Yyc ;,XTD6a7N]~$$@}l^s)zp7g赗Y{Wtӟ W]b1,v gh'&[S?#Y1W<ܙY)փ&'"D! T3LV6𥂴'UO2}.Hxݬ%|Il +K8Fpxq p%#S[H.qֳ+#-j.g6uNW>DEGwS`:KTp@)O%M,V&'9wvש *h6VA1vL>: a >7N߭[XxEi-jz`_:`DewhGZw7o L{@`y}9kj} @O%x*GMrYofj+WYl/(_rB m=g8u=i!8ezwo`aZNIhojQtN*^ n|C!ZOϦ~iy=)Oܱ7o)"+6KCwNh癊8pBz:f=PA/#$Ƴe3Mt)H1]\ mS޷Kkrbw4nRs]I¤JsԜ Q?vRZMJOm5 :oeQM?uLx^ǕC|)w! :q6PНѫ*x9qsNa]Jep\^AkfkOV~YRt4SynfCu*h\abEzS8oO&BY|( -G(Lk}'<*UWٖ$@Νϊm6 5v[{g9îy3J–*GU9ʑf ގ=4oX;M{9O1mT (xP/B)Θ2׷<Q0;cp ;JO}p~Yrƌ(R *fd&B8 }'Sw#0W")'J,dѳǙ=ΘJ7M ÙqѾ1x(^xp~ SD0ɘ@gCpP"y;DoЛf>:Ou?oXv*9Ylx(뀝H=.bJ{~(|xp5s$9_|/S0Ou&pqRjg'OO9rh4>34IMr+e/.xaM&Kl4NWHOMzĕay?vƋ rw֦|큌C;~|~;ߗ?ߝgosMp-%.(`DCoPF' O s(ǩDb_'I(&,=EpR=Qn o|gwwq_ x9~Hfp;%>h~hk"1f 54:u9Қ{ ̈́vB+ΒL";zZoeJtt'bBܡ7K<&gڡN'8jt稒 {Z툶ugD~Rzw]yŧSMa߀j&9[%!Qh'Ê&۱r{G3'/s )gQکvh:=&;"JX bCwwgMs}Bo{`ORٓT<{cYJ9z;IWcm 2 l`؆"8wu.#D=hU->I9] w!pЩ4]h-~_GpDƴ"o^sR :]C<eڨL(1YbRݩb"t 坏#:aHYb(&䨡"GwŔ!!dJ|ljń a: ITҴx>UjL3tX-a5`4+@y$$N*&|X>5cٺ8BHR~$|SɦŮڷ-Ylw'mt讻<8!5]ORtVRt}D[ߙ Ds`m0==gVNmRKxfc؀y )Ly-Ì(g:2v=tnjӕ(J' pZϭw.O㞟.=SҘ$!˜*n\܀'f=ۆuVx+4q咗}_M&M^o~?bC_㋯o*s0=L *@™ONEp[wə\#_rXt C9d ,59Ӛx^Jڐhѕ\q~ߊǖ2%(1._ ʼkׅl3dWqC"C)oU"z ;0}v|acg?9}'u Uϡ@k٪2'sGi8X[-׽zX;".n.g?Uw?gy]>dpS]9( SE+ $/C \izcbeG W,Rp*ȇ+/^& >mn> SF7,/ӄr}K +kʟ}~2c۫8ӳ%XI8U.,&ǺMu~Vא2)gDU}_p=?pPCv9u$).j;^$T$DA:Tʤ|]3=Qӷ?o=@s9:ρZ&>x_ ̀ 糥3Ui{*I!*$';? &mb5)'ˮ&'5K uu'?\! V8h7‘F8Z#itvVP3XGꭑH5UiML& IyU^lyQbNbtL*6N3'&($P& L<+(3^b?0Iw4|R3<=U";P9tV:gZW,g"ӃeV/QVFi<ѹkwg6 T7IOӄxi}wɍehʄo/e^ R=^8O#\,YgAKRBNFU>Y7\}r*9Cb+D_By-RjDg89NΟ3w'QGEwߺ:j "/QqcNac1cy3~HQ<\8iE]Y q3f֡SL8C'kѕђX{BgIҢ=[z3 `vB"! q&⻨:<d&X\ff 2 U iI0L_U;d#q V6b,_\Y;vO}f l`7 6ێ2== ,J"@.0?!pg(;fnbfVar,F{xDZ-VjjròJd.!F]{h/?It$a4:#pu[ᰆ;!&QI53x[Wo(OPʫ<^>ҧ ,yε G[b^v`szlgNFT'EfW86wsZ1e&btefiQP+3\aP@9PYb{س<.LR͐-GA[ uٲ; 0de%b0ߧ=ݧqf3I444Bj&Y ,Zg}VfB'S5y1f@0g=9+1;q/A _.omLzGd[-E R7 UU) Et96c!}|PlTWl3;b.bAܘC9]y⠒$7"c6ndw5ۏ5hyـ5U'#.8-5*i!mߒa625KKt1=vСoCn%oླྀy>_zkrY!^W&Wp+%3KmIN&BXg`I~<;;y鬈İ/9U˷gU-tFL!$7sq"o:^o+DsJ8ǝ>d%> :h~._DǁcDu۷ϋy@[\@I}o$(N2B,F/xϼʊ~QpS{}(r]|E>ӼEAe_{_q pNtђt4*aoe}Τvl`co^1/.s,Uae`\G3VzH$."18~Qܷ]dx愩wfDț]!j0ʈ Нgi\4r9/֕6 Qz)[x:sâߋnw#H}ځqcBoJ aCd쨹>}-+8v/ww/EAЭ݊ߩ݊yv`[mmkj=\+]ZL/jp얙x$~-laV1R>sTUdƇ[ Vr&wp ބ <)qZKҀN%sr]¨jU0]6jڰuH,PO4a$]/aC[WI ;;˱SgqfrН晾'T<s*[4䦡FJ7o,yI&oU.7.wȴbSԦ6G,,X|pieOynAj̳LtXz*9k]hnٕA7= Q StW=#37,KrԌRv.XoځN^9~?{AQorwrwro`t*A+Av=N^e4*p^YGAE3a72ɺXD?󤾼V9K16ox$˗Laʼn6 nMXޘ&VrݠuL|'D1QΫ(ѝr_*% Gg&8bL4MKG#nrs_K(gɒ,*W| MF"bpaCvNSwA+JhNmjS~ Ml9XΙCYuʻEwv{{ww;;%K*僳+ͥL]^(lGa{Q37,|{)GwQځS@¸>Lځ O" ? (0?Rt B^?H{k9yߩjЮߩv[^NjUL? ! ͺ1KS'E::?H_<4FB&bh8. ^Q6u"%3\Dt36Ł6>t32<́yиU,ksȞ!9xku)K؏q<lH03L)2H&2OS(=Hoye3Q(>{j&oswÀZ urh0=N רU5/ 2GM"᥹AСon1YdŖwF|wؕݫps9V9IWT븏v E,ݕ5(!uhSS6[uVWr.)=]@w΅E6="rpn9xFQ[w~ 3^azZ.8 0&决 ('xcnpSojm6e69';S 9#2@Mb.vffZT V-_!/ ?_Z~_~l%?7S>Ev|{ٲP!֟.Yyp-5n_c (RBS̈2xY ec7Ds\@2n}'SEĚ@x4&q|DS86! jV t7Ldw W+d\/f$+6.x<:d[5eZhdQM .,|ߡC2]`iSgSojS=e[X;Z?粺C%H!\ZkA-?d'ݴ!A.?de{9ONDwYt\Wժ9I#hKAxz;U pn'J^xfvle7N%"+ V.%,ΰ. :%ht2]y %t665NQ&UIضI;7t0aC^ԉ4^ܐ1.$1n}VbbLma 2.'"eg!]ES$?%>jY6vUZ]cqĵM(;4۴44ԻK7RgE%cœkllCs@dEFy>o>AOo.:+8 ]J+<8E_UT}$ѳ#ͼYć i5BweKKdR~xNcO}eL#g\N!%b{s>CLd Gb}nmH<Bd$‡d؉/uH$͕H<ԓТ?bYϤlZ "o_/ u.wĵuaqWķ袆&kv&f1<ۭkXͥbN,31,tƛNjB02 ;^}uo3G 5faNsΉᖩ"AP\5.ICX//Eu =TR1J4,zKyUW|?%R*;5~48jC˙N^[M:W lg9C;]lGҜoVInяmKM@=$F *BL7:0RhnphC))S#x4hT )97]kB#y) :t}LksԦ6Mmj졆x}ga91g>$Ι)KsǭXWCߋO24|"q-\7NNӄv;K9;e5h4(?*6͐(z nz_GPZUWa~s&S7̹sFߥfvnT"3~TţSQ7*7RMf%ʋ| q}z\|`n$zjgn[I AE3R >A}1gKs39Qo?DfZ;|ai`jO\ڙsv']j9mXגZ̐139S*2N>_s>tgK/[>ipˬ^fP^2joŢb㎄] MAivf:sRL6';uKG}e \4e6\PL2d_ 9vpŴٮxH~Yy8xθuh8%ZSԛ#|_ 5%!"R}e~{<پ< ׋c88;AE嚯r# 'NdK/奊 ɿe;h ySI67loVr8Z 7P;nPB_*pl`S pII-N[ve+0ňvN'>PG"A ',se1–촿fiﺅ*N?y0dwŀjZ :sCA٤;mwj>-fţ3Мq"D0ׅH[?<šJiO%2#^?P^74GhfJ2)eT9Pc n]fjtj2_o֛l"N6"yu=Q^%NP&[vW*9; pΔP8dU0*NfZ}+ k.]47L))ͩd"]RK4BY8Kv쐼 ;tHN:'!Sn)7MG.,*WXΨH gQzR/Ffaf@@qC]:d/N oO6a4uxMO+Ccإx6$V^AZ?nqMvԕ;{ĆG1w-ջxiARtl M %]eOȞ sMoLo3mJ=`g1'"v 9t$DV6{ަ.Sf42R{T[p\ᦜu=leq&+Pظy<$Y9"G9K3C:)>6d}-dg6`o=f֒Rk9<#Ldո}WȮGwYsʨJKH4pgsV>3 jgok^Jf%4.+;àǥlj\mGUXڵ Iץ'ZEҒfAtf$˻؞xL(۵^>V43wr:hULan^{5e2mZT3)ߐܹN7p-bMrZe3tXYO5oyi ̻ifTv7]T^I8B̂.zB PrFzH|/(>-=X$E@ُ+;o^$< >^LGZܩ!V ÇSm)(7?yo+~G^\Yw|!DmpQnN/mx(/I|t` ?j'FŔ$}pN SgSN/%FsZŗGD4N><g"?AB?#GZTyJqW !(]pSP|rk^h/ b7[*<_glNJ8x wy*V&Ha[Mܵr< ٟ0<sqp0Lm(`-=8= T]U\*~Gm7bwkY~!S>Zn!XDYfyO\ja+eGtmkjW/x6=59an5ЂS-e_)$}9qKWhm~⌣D]W4/$H:|;N "@+IZ)I~bQ>͛J8+@k%lsi0"K+' lӚ cg>~yQ1%MW(h '+Ŕ8Xu\2"@2*$';~z ,ǟz3ye__կ.3"ӏ9-iyEWMUlRo[L{hK_G+mcoi;::LvEJVsV-}7P{f `9#dtvM]ݧhD39J%f=ThU1 TLPINJ.R[HmDEep>Z333gfӧwf%s˜)@Q;AE#dDSەn7 SJWZվO$Qá7Qjx*c(SY*sׂ579\y iKCc6q5*lF 0ޥՋܤp:Vf6n|Oẻ;13E q;/ *oqh2Equ=Ϡ]ewq< KpB( C%〓KufhejΥ+,UY \c婊)1V؋0m9MG,,*f(v:S[lupѬ&[; .YSm-P2c,nP"lS?M`;E}NX^ZnQEd#+ϻx#R_̯̾Rڡ!슏^[s^1(1+[)Pd;,/qVF!F% e*'|~8ٌ.]UjqUyTg$ѝE%ݕuP SO^)<հC4q[lj6x)KcpT80Y1 :Q *~vD"%'hLjԠ:kTCЀeHUq Hwٻ3z[tI6'M4'L[̬&̀eL;y5ֽ '?Ie`/4R24nqHH?&b{2 1n3'/zq}y?wS$4N 6k k5.;$Z^00<= -`OmjhSojS`Xnt}fu#*ְ^%03NkEuYuɢ`e`A; e[&(d.%ԖX&:Ӟ]϶k37֢/oS,^㵨PiR 9VcNaR/fUQU% /uPQpۜ:ay1v*bS! 8p ptrw re0-`ԙ0 sNs2_Mep׺O2=hj#͕z[ĩŜ 2Hx۩׈M0i@F=h0H1!]ZPb9Z/Ar?X6dJqچ5!K黳icGs~L-=@?GhЫ +{WWzoO*;?ZL!v' ÏTKf?EK7h8\hoYKo(xpCNyWDƯ Z﷜x{Lw@ g(\ 8\IK\$=>j3f"HL'`q&+JEeP gHy֜%м<GgWK~T$sAD"KW$]-.lXRqA%9b{ opƀpx`Y ǁ 6egE]Ŕgqm8?/.=g^mK).1"sV븺龬f{lhMtmb+-ap-re,xfܜ<z>Xv40 ݙ&;΍gECI]gG:Dޮ~mޘhOeg/I4t!OfY ޒz j( ^VWPY^yhJɘ@oj%n Y=̮2,hsK|˜! ;(TȺ 'Ǚ߼g ~@κy3+M߅"T.KR;Jvꧩ졛 4nS y[J(#;܍:=Hܝ\/]6}ĆO{){q' U4cqe7b= U5zc69YԦ6jJ Ӣq@a5>vލoPJp&˭^@/PNHsLI3;'@g.(4~ ֟Svb+Z:kPn zH2ZyB)Ag0OabOLW cJY3eJe T0f2 /D:cy .(aqB()[ n\DzՄ15-9Jzy)nz(mTM0E4_zÞBUiViViThT| IDAT>,vYdWzK= !Yzwp[1 ! F˽YA{韤z1jGI-cF4Glzf2̜ (_O6&87&}6mXQj'uk+p!>)eZB%[VYV UISu'^=]6xMb, )9N7Kx}a"Sd +8qS''H&e7x<͈IF KG[[^#J? u_S _:oJK\4K[L{nB6,2R}Xxh; 2%N//V\Y "%";%1,O9U\JxsǮٮrJbB4<-  F ,qE0x\@*| WȪfs08lM8dK2J&|kEhjbZ-+"x71ƻ386e6} sP7LCAߥІӨd}WYPlnr|h;.ZR.Q@͹3*ΣҦy1ip Oev h}.1TQoXB3$dF1q)(?Y Dz9{X #f޼P{.y=t+wnWXnO0- ` ViA՘3[gJ+ڍiiY2Hc)b K56kC+o.!uY]ޗ3g.^c`em{p۾mwp۶8oB{3U̵}L!pŎs¾8e \Qd;k~Z]}7JmaU_puh]QAeNZnJR̿VYb u'djEwQ͠; Y)eJHyaـ8$4ƨtZ j:H$sVB`=swr~,y@#o] {{KYy( ՅKeu`Y5hJw7-s1k,(#gI=f̮1̎hǠx[6N7Zş:CuS5aYIJXRTgENtY$z 5C^:)@ZFmpKM;3g^dqL[eMLU-v4b{ل8! I6ўGd 2"p0뮊qHt7*('Hnmh ]h9uac>̵ײ?v3'Oz*ܷ?:o7ܿ07W+xӶnv7Wc7Nt33-bѯD#xvֻg,喙Uu=ӳI@# 2`817oĎN78x/Kp d,, Bfigowߵ?j9uǟ>ܮzԭsN9ct~y )3`^6lVՔ*zn (Vo6w` QӇ&j~r7!FC]>J +mb+/beD_ۜ~(#SYL|uEL=M |&bY5[`KbSQo(/&z(avʒ/)kahz]I(S C2ymP1PZCyWoa3[M{R RwP>`>GZ]am>Pq?T$$4Jho2I=H[S}wE C"X^-Lrx5(eC Pv('I*2=).O ׇ+Z/WjpG#:N(=+O]n N>Oޤߧc{y .ɡC,-]h4''NZ-ZMMzz${G`zb+b;7dFU1ԔWh}*[8\,g;y =}MB߽sߗy&H#8pqǾFZZۆ%B2+1!NWQZ bV}oG+'k2 ?}:]K1=vmnZ]1aZ}yXӾ@;+ƳvDzfWdg@KStV9S({<%yWΑQG)SFNߥWӫd:;c!9Z]rwu" ]z lUyR=\b H&%BT7N,Ffngxc4  twr"G¡efoe%ii6i5i4ǫyuo;;w~sbfm]q8댎0Xtq xI#>e0feFLcG 9>H2eZ1g>U'(Esbȥ0f v!!q6{? QKDAYO. +s\2,‏RQMLuK8I f2e\֘jL7u+Б]G!^\2Hi~kaq`V1UC+)ۤxqw\>ѣpKKt:,,rw͔3έ<Ξmzҭ.zRi06]=4strT@Iڠ`E]y: -bp TLhSeTB\1k;]r| 3Cz^0{m;Hj>^m{ٳ7"7pP@ޖ!Ɉxgxoy÷U@9 b7_}^_ieҝ4TW5U"VTD"F~}YhZ7l(v4bWW/P1 />|>0,#fCYAJF(~^ k}2<^0T{` z(BATTr"ػ _V5_U^lhim020^dQE> P}N˚SV;Y+^ n}.u7⊻fO)?˭GF{3 Pbf2./Qy?mD^S;S_ wqn/ ef㮦,փeד@cc|%'팬:ŎZgиD1ZztwUgP@WD6[3/P3)3lLQ"ӳÐွt ɠQ1]hW!Xݜī#"p*w!8YmV۬<~]Rwb窠~WyThd:gPyjmO&8DFb<& CF#o8~Ç92 9kd&ܝx;:v3[SEw^ UL/ߩWqwU9UFK 8O<x`&ΈLZI7m [;5XY ئq=a!9O /a4b0  ]blC< 89>'G\C$ʬY:1H$[+|iUs$ZZ*.;`zXY^1Y#!#!Qߪ<-p\ɲa=DFƸP>-XEt=xUea (Vj$F7LFְ AxP}haQ72NkG 1[5ZY*ۀr6e@JHBFMx'5 V`4߉*jr<'I \U? ! "c{_pdÇY>qw mZm  u|o@8F·>n>(7KlUccG/ຖt$*w]M E5"_%8,u€Q:XrQ1gc0\[u֙Ju D, 8;gsYY"z)vKqwfi׭G4CK#M1^>N(kmL/Ó #dH''>R"$:7YS*35iV֑]"ȓjp}~КʚRfI oU$^h_4VE%+vlq+CAEʁ꿳wƖg`l ՎXdޚe* ; ZkJ5JW!zb?+be,uX5:>KPAY/FPDȒn}XߗUuF_4zguA <09U<eNWXmYsLԩAU@ ~cpjbL62:N gJ 54ԃ y^d#wts+lDkWYcƸ*)B=iVzxa@ h}t< b8{a*ʘ4B ӋWT|'d:>g~921͐F4A~="U:H dy:aif2ϰ]42|6ԩ cD  K$aх 4a1wԖ`iς18por&j4NY:gX8C݌VOq]N|@.,/7t3%ĒX5p)9c=vF0ns*^ݦ%B>NC5sծ)b9#K| d7"xONc$;Ud5:ӐAmÄшw'X_"6 mMu |N(m-vwczx \ϞY,S_[@6sC9#|€koVh#qrt cl s. UWkđ!Gr|YttGKݐx 0i17wYD1FD="˞y7`XH dD2&^xiʇ_ma{a nn/3<*\4_TּVA(t3F6?R~kYfZد;CWe%הFF |Ϳ)SFevnx?>`$ (cҊX߬Js[ݗje?䈯'g+MXa5GؿF%zY GZ#Yr3GEK7h]?7mj~7?_ִ+/יCS&x[0CY_>X&YsVfF_U$0=F)SjXPaaX~-t̋]ٶdRDtFX93fi^z?MigOB85t,iR9*[$Kȓyڐި2V(qV&0i4ưeX9qW)|ҷAj+[ZG0QvO Muھk{5ܔۊP9́VĈoH23wsul|Ct:[><]w,ygGv+ftLM8 iV ,?Y$Z;Uř#:r(`0dX`" u^OǬǴ=v|.!Gql03+JeU,H<3f7|o]k)uif ^ J!'/ϒ''UNK=>97!N Pd#eĎbK0jmi5RwS8w.Ky L3td['tWu{)x[_"X"e$쾒&!Kt.I+ ;?'m4kc%y%ѹCh(|cA/#;kC4]țrhG=8MY;PݼuD3ό!\:O$C~G9uGX[gy)Kv4}yq\Bwom^x5LMgн5]N܉s,/0n3vYG4BArC;)2S̕-bmXBKcǬ'4R9:cؙ ru HI]nfXYD .7w$b &=,C{Q K & F7}TOAn7BRh$~ekaSaae,<6ֲߖZrhaY5P*6Ѻs*▛2:fzXS9-ž*Js-d&#_9l^ZC3^k.V 4&Ka I曭J]fT1ޚ[:Vat"sP7}qN-[Է4-g)&3Yg22+),tjW5j|)Z6<3ؘӡQa9X4^-C{k쪐۔UgPמJu@'xU["/!q&K8 of}= L5AwA@.B8w_#YG>]ju4 YiVgupk]5 SXPj܈! 0f!'4BVBY&#:q =-*+ǣv] ٭nFW<1ΕDJbx2}ujR! +s\2e2#"D1jժ*gy SjynƨſEwf=V9jj^9o rۧq9xnGwli{n=AOb:\L0^ p\j SḋNUx 0OSف=e8|k /aW 0vXU)y&ptx;֕*V|?NƖڌhygIc_'9̸˂$NGD}X}#eY`22h4юَYY Y Y NRDWsDUGK(>!ݩN;_:?%Hc?!DLNR1kSCթ sS7=Wֹ]42|ԩ! +Ah2 2x>trvE)g X8k ]ek :yY8; =t7d~s'SDLo+V i6qѽ#[5M,ss (eUU;p>.䄛!}m(h+K] h  ȟ)Wլa}e_ӘHb $⼪^m c$vDsXx6c8! 7qbiEZ-ZMMjݥS!)+̙]箻}}XY'=? jME&qh`Vh6SёU]qQ~,,h%;>>x"шA#a5TfI(;f{VLoKU'$͈?y>2gh5N/w-B/'9NH7S!.`tzT/iށ8y15Ry,U]>eFKp%ީbxQG|FߴE1Qk1ЧǚpH_0CQy'XVX9o4l H|M o+4VCt?3B4 U`'w:JqTYs/F?5c[{k+y|C>3߅ۂDXPFRzAYZXVoH C[jK-̕ WzQ֔ZLGb+*=nwi|[?ʟ#.+kG pW>n+tT\Go2/pwjOJcߜq/K,d.X ~, k6%W_~EwӡQ'ՅejߵM.RDQKf)!dqa8f^?πwts(^(,4b_ Ŏ65+>̹RRf;ΈudkHlHl%EdLHl-$ %!N85?7p >t:,vXШhPodA2WxeeZ[]VWk[[˗kΊ>:O{2kfw ҲJlWJ̎,"DHr`F TYF p|4T!A\*$=tHgD 9;272e.s^ tH4. ñP3J셩br3T#DD L3`q>d~jyll&с7w(jjItsd'bZOlD+41l ,~On!f_LoیOYH> \sk'p4֨eHd'5.%$E4^p%S= EWYCMn[Tk P+aiͣ'!~~haj"e{C4y=8$2[<ȆT$1? VWYYa?Aw63$&I8s~}o>8}q1s VFɼ^{w6hgu e46G.=8RLv &*O]SӔ uҟK3bod2bQЈj/8i2t)Kr/͐dH^t|e`?dsoNWdA2YrgeW IDATCF)O ^b|˿,ÏyW{4Y1fMi'W rxlWa'z7:%=şKPJe ~I=|o.r]]xIYqW唡﯈2Kd_5|AنvQF6 cT6C&-`ihE?ʺFy7x`CͻuF n_nm~j?VY &~N+t7cvoa\\5o%FKЪ˜eʦwguͣbr T;0S\ qht2sfp3Z8nJqm<2A-@l2.ɴ}N a"]؆+0TVaJRQjV/xCР]a 2to2e|'խol0xNp j'p;$2i0/sT aivv CwImWWW];&&Fi}neouC f@wE7W9&WTM99W= ?g*}"5Z2y3*9rB51fOП7Yf14.LGc$ԍ5o?4sˬ2g2|ԩga:ySX@i4y5)L3" yYxBfAw(?RD3G8dWAwQ%bUlvv%6/yRxdXј`ɮ)')w'P+7f<86h3Mx< SǍ&j%yx"J17anߕ0GwW*c4ݩWlu6h~ tWm$.ͧ|V;^ w-deeC,.njlfλz$$1q}>8P̙ࡇ6tW:tޞZ-rKog}ܳ<QU>Ԟɜ-@zJ+Bʩ|0jn?;Ȕ0"YB-1;!;cvb&rHnOf9O;ҧ1Q\px=d#cd$Q-ƔVNZ( Xy8+Weͫ+*5xzo3dA iDSkk\ mHk(~|&?ipPQ TpfXKk|/5vxKY-֨&F Qc(mDE_gxo8W ]}>&e5bDח5jWYrG3ػ$_b֌F F|Z%wTKH773y =>O\l~1 6فP*0byȡ84;u+zԷ3OEwWOfu/>c7I&P2%YYن#F[9=x4uqő8ΧP^̙n/whŧFEeteRZ"1}Y7ɡeHEEPYfnUH2hD1qxy][sz='֖me"F#rvݧ=}ea!Ovwrrg=ho^؜ i<iT}8[<+r0q"\$﹈&?擪"bbDb2 }//Q&RwBt7=ݷ<\2!B}Cbhfpʲ^ze&`$0d 0CYq>84P/rjm\ѱ6 ;OXDvtK,O'j8 #75PZak&;ڹ4r'̈́i(>/Ãp&GwW?"4 慀 EZ}JNF:04̡QEFk]KFr)gGX^fyvff @5o& Ӗ$ qNqt!Y]:ݮF9Kkk'oiԧznWKdaap-++?~V@w欩Mb6 @iۗy up ]QED|E }z.%0f(')'P9dnG1, }S)wɈx'IrDR>͆7σ7p2wќ\ES:"q xp!>Ng6xV`C(UtP:Yl<k=c/Ŋ)},>@?E^vYKk]m%fH݆!-6/qGŦdlU_K RgI~<8*&o XT^Є43pa_q"Gw>P*>RRdV33ݙx ZQ`Qy+ u4[K?? YZbqvz@IP)­*Xȇ' ?/<=`ܹ#sak5וO}Պ 3ԆCԩA0>[o8}zx)9R; wB[|gjkf*.V/MmG=&U.SoZ2!fƋ闩NlkܸM{,3~ᚣr"ٹ2A9&Wa Me+,7pȗۈ\}kYHa(ANxV4"؟5Gn__jUO*M(Kk.)˚Κ: %`Kwl?jI#j8g*{kE1Kɲ"O?1cKf B?2@TO64 |zqfBvy[EXB>G V-FNȋpQ!C垊kԞnEYplRe\Q'Oa3?Dsn5nlF#5xw M~b<twQpx4(d=.Jn7'ObوE;bZw+WeYѝ*g[!{BJ2!"IHb9}W_Ayž,N38j062' ~k T*ό0P*K f*\C")[Q݅Fgړ=ٙS^of}U: 9+!a]ǥ-RH(by)<|  D 0kkGǎ O=: HySh$c}yռtYFw40T|N8Ƨ9Oa 5aoSwrҿ$j0u\_x~L@@JIMR$ȩV\Ĉj77bBӍ Ƃ0R܀5a Sg%bFOl#=#m&}Av_ՆͤD B8BdBJ%ıHnbsL" H^UAV<9fo3]]ٱ6rSZSHA8ku$y6sN朗[ :GD"E~Xx&P Vf#v^[aa >^1f,RP$D I+5G#q\p0Gw3uOt] @w޺nvZ5S5^9ݔhR[j{h|ZnM^ejNRN=̆tN7@*VbI0N!*sm+r¡1.dy w9>!;fEVwK=LEJ%#!LjZѿ4 &5WKq/,3GiyQEX$N*[b"XxX:m"r}֠#/6u (Ͷ Xc|5[$ l`^.kwx|}$ ςq׀sFvĈP٪ņZLߩ` w+8 :b XC^[+%,C[xQ)42,%*Yk5 5-֎p{zZcl~z<(J+~Pޭݡ?٩3" 1FxY>/"O!pJj$˕]X&)wW0x*;%l=1'j Е N]8wG;ܑ?<¸5FlOiS;EɔUf}Q 5Oxp]^><$ı dē+ҬcJ⵨J ,KAV\ Feʠa/+Q &i)]OclOw2KLK596PI)O!F@t7gVW'ЮVÛIE"3 Eȇ++ш(" yғzG67G#dr+Нfx.5[[ Qwg;՛Պ WRϕ_5\סT0BUZZ$fHP+ j)ԔKH˓ 10r&N!UB5}Ǟ#GLR'e(.q22g2|%g& 9|uÄM$R"NOKPZ‚Z\LHM/5b\,.w8!qP"MW@8. wCyI=2]&ӶG"yQS)(Lg&dڑ,|L<#l-n ~ON$z]7+xpȇ&2 n3n??3$XɈ-k~ژ>%b]ek+CrUKD^o(_.6HICmOE`Z!_oDI%`I b46.kvknhC%M*$X4R30U\bP^4k=W[ ^mx2[Ӎÿ* XvG+VY(~۹]F3w7U$Iĝ`K@b!-5xu#~m=BCfww&Kbal/Ujjg%łbj%DcD&fSQ57ԩq:Uh=`tHgˣV>ԩ98nJC<¤5NaWWVш) i=~|p]2.+!l LDaMkq_P ԚԔx03Z`ĕ~07Ӂv(n&kIڼ! a Q4goU4Wbx#ݥ[d٭ʖH9óő#>KY̌(sکΙSba ³*Ns,h <9WCo?}zSG W(T4/n~݉ɏt &CSi۱^ŠZ!AFՔTR}`<)$ `_{0:2:n;Ii&a.s\MglwCeVccFLI@jfJZZr>^{e_/Zm&͂O:9sq+|'eI8iDI#r(+ N4#4LäS98A?/j'BfʌfB !H!pQsBJ4jIDsb{TClV}f1t\g0ǫv$QEWz bGT m"w ȵ,OrX*si%u ƮN]$BPbfN*ixvlF×/ׇZkML5!|R~ \* =VS)iރ{re Z; Hl#l/=0e c1`L46fN&Kc Ȓlw^kuGUU>Gaӻvzfc6*b}E+Gvʶʰxz=lY! )4ȥ5! n⪫X[2 Az̎)*tWwWXphfUa49,}2Ktg/M|6>گT̢&T=6=n kl8- aM=888] PH]nppB IDAT8Yk*לVd.u1Gܻ P2x DD +aԻr+}ATC5n'})b} 2]hwI]Z`KX[ d>z*ꩤ iZJBJ.!fi0@k[kIeMy;])Uݹ.lN K>jtGlhwm碌Qa0dlj~r)~" ;y;%/r#<7$bgZUq6ürc}'1aNjS|OOmyQP[<^|O<=z/{aN^)^U'xB\ U@EݽvrM b;/uZ k_70;w]zףꉦ% /p2OA播B:F c<#N`MqT1k7F7;o>C}UઢxaK0c!*JRBT-h%Ɏd[#+}?qgxBru;3i<4-'MJ I><{d;RBlٜ|V]f쵁}pcDw؃Ŷem̤&210׳Ic R$FUE/1c}U#!A^ olEyA]ݹCQPf]Vi2γgrEwi{])~?b.n).3;n6J]w%?햺Z(7:`|Ly5ۚa[VOóq<8/K r!-la ?<K$0 ˆ`ǐ1cM̊@XU);wv3o[rKDwpO ǁ)D(UgRMeiIh_\g#Xu٫kJf04Aglf6HnJiBAHA3T;5M5kTU ֊J >YCwsܲ% vL3ӞwWBz)nDcI(V-9q[o嶧qayUFKD1qD! YHLu5:嶭wm-x:s&>s& dsKڥԻl֮>޻=EHy^Yyh9bܚ3jfoiRfVlkn۩QxRSrv{AͿѩ-. [®q]59)lop.#j2kpQ^Ӗe@Zh6^E0@^iw̾淥m +u<FƲr>9Sq+t2 kO0xJPr0a]7SU)עZפ]3Sr[J)DQ[ s[#1@Ѝ攍mNm>~.]MTRMX7KNwwGլA~1ȋ ڕɍ7r9‘#,-2QL+ WRR).A7ι*Of hޠ;{]UfGFsͣTdّGhݕ|ꚵوg 1D F6kgZm]4E;dГh6=K#bj i9:ʐU ॆ۷y6**FN8'y\qJqZelT}W|PsnoqDOc/xE񋝤 |9p;;tW#/97 u |^/[xsX_F9G57V7G':x uysuxvT))N{ =ӝLwFcs}=>> G3]:<uJqD8y?_Gy5|n/u2Ie9پ_zW@^a(m< =w'?{U[|A4]v+y+c6}Wn.nv;Q߱&nD,1^eo*RNkvkQX[g8 Th5kVry}/|3<]YB;gf?ZWP{}UGg/ c@;maFjEtﶝM $FwZ+[Na1xm }$݇v/ yR+ͺ mb'Rd[,@ i;.I%5ggT}l7@G%N>EN߀$~CEFA'۫{??R{"G7xmxOxQG@Wq2p8GrGn+~2)pɁp}sN嫾ё#x%oS+;D3u7{EIz^̏ػۿ/S~~=}ΫVlM;_Ny :k`(^oor~2!K?^^yJ#Ot2="z3n;Ox7ԁ4Ms!:MoگsyFqW9~xWVxm^ =|wtk\+9a""\U7HpE]w܅\sbjlgf>wrMJ+qMޑ͜]V !5L cN*to{҈U6d(΋T]~'4T& -)4%3R##D( 19` Z9fD#1d (SKtv)zgX}1Dwڡ98dw;lj6 `0 .\OgUVVX^""wPtW7n.zcΫ@;7( εs}4~ĖIyw6 ]}wxXT%k*>h=h}CrMa_9:Vd#m,P+Kg†)R41*( vü*ZP m|R#8+I.Y,la [4!vD*@XPh !:ќnMѵn߫a\Oq ѝ bH r)VKѝWxH)T(0/13 Z4g#3$ʉwfS1$;nvխޣ) 5ˋq| W|W ͺyꁿϻ2B0DV7ےx+bFׁq|Sk:Z0@wd .[ n C3l,m_Vfz,%JSjO>S*ޖbK%,NM5]vˡ02ZYYϻ.)*k4)ˠZ@+%VvRJR*)T) c4&fm$cKOvFuyÊdsr6s˥h;WZCFmy2Hme 6l.%N:XU 250"s1h"}讙tיwTw]#ⲑEA$wG4Lj̼~Ve Nlmll)('ڕ-DsFJ|#vJS{-4Ž`{l֍xHw Ymc> Ļ4{򵼰]a[0x [® @/1 2҄{l2d)f1719voJkJadhz>lfVzCJ#AHAwNjCqFsRsBqR7G ;_REYY$Ukne6մj@@@()<ێ" y].icd[smR¡/9[͒Y\p%dॖR]9 'RFD(ntS?nk8|u@@Tkvd$+ JQ%\֮lI`fCu]#Z4˘LC}>5RywQJR,J^ٷѻmo&`0b`L=\Nl9AB#n&:ŴI&X15 A^{G}ď\7i_X1-\w8寖)=^WӞu u_d{'|D&xܑNL'9j8:. }$96/ywAįv|úK)9?lNw@ev<N8hx^dzK~LrRFom}7}I;K?GL |n[*}^ߣߡ?أ}}ONe>Mu=J;>٫&)_?d&//u2IUxA[]^,ᾗWy~8*.Z'}N~3 b2hkDLCYn 6rʱŘVm#27١K/KU)?f fZHI|ҡd-ILѝ5`c3l̈́Rvlnc-@\QWSJ`o첻1~ ʋn$5m`[OrNqNλ-. [®qQHbP18jY5e2ao {A26<vnEVI */M%*٦lkuM+:=_smv?,zK>2<?<~x08>W)ӕ YzԽ+{ZY?gZ7NUYwNY|BZJ)rINv050ڶTrQ-H6iO.s/ S.؈؊.ג)[cv7a 6a/ `T;g,/MnAwM9WкzshJCF#F#t@Lj)ͳnKu}]i5 Q:s""xcc8wI+NP4]`ՀXJB2HT!шHVO8@BOJh7%KHdiN&RDXr#7b%p'i<^>_g];P2P fĿ^rFo_Uv'r]gG{PZKX]].YT?SUbAwY.=>Ώ l^\o) MN*E ʂ4itWfacIxdsY6BgHAAX]gUiBE$ R#%S D( 1ELITJĊ^ U\"P2͊iBYU&i 73` \&֜',#0#"˽8.g0M']X'f%yI~a `]qJ"Ũv":XR 1cvS!q3C&'_xqq6P[+SuDAOpbA ?2>8T{GMɮ愴zݤvwPk T*tm>z;Kv@&t%h:j8,ޙ j[L!((/zp:orI.*)^{F5e7rH<{mN߶͹ZXWChˆᐥ%VV*IrY^&c1J!d,V9ut7_Lusn kTlhנF-S^=ΟyΜNzD@$bF 4Q`)-ƨ3ɶinSLɦuUԴ"݌ RQWa4 L7$4 ,0Gߵvl})8?yy[9jde,r3p~"33);n]<ٓ6%e:GR%UN⦏0rx aA0=*ޯn`O="Ww3}gܽgPi0W8#ehQ7+G釷{u F=+Gp͍ qnR>C)holS[.=0Cy̍; %xް'v7zx:O|/ȭ¶6 N^^?lH2r88-1pհZs^ ^T'͇ye˾z3G]{+@Ird JPwR9 8Tjޝ75⽃[1i:·@KV{ZiRnW$=t7}3RZ*)t\*BK\%AwI@$dɔ`9ԓiCa?=AFjfanV]rrc=2 F+$+$U*jmHރkwߧtWnfU[B "KK[Zb4b4dy0$е К0;]x^tw@LJCyTP`tj(%qw9㏋U!Xf9`0d)d0PD@*@KdV*r}%A"bZL(Xd]`c:[CH H#Ye4 "O<ɂ$i}#Rn=ogwYjmmNt2P,*HH##`x<:,`h Hv$0 >da]`]q;^()CAp"HSS&v'r^ "fvnI}ιߞH(IE#=gbz2].qAwwҝ+)Uiwvx J\Op|Nn/\$ 4&ݹOS24SI*JPW;iݛUadi$Nf ɴs98oAZMxiM3>onx'N(c'6xgqs;_ A͑EW[23w}#^c|;)/B[S> @uN؀? |Z~]ZݽϾӝں׿ Z$ϯIe _dp~'3>ٻ~Y;z/CxdofaN_\o-yiϻMNN\u3]yýGO L儶 'ۋy譖NE|jRdFuαx)LB~׼$w'{gbv.y/hs$D*!{,OwN& vצRD7tSͿ讵$R@MHS}';Sr,1vOX*92̝lTUfnz.,C;+3]{f6_v5lS,ATD#g+0sMRa`qDC."#!>C䈽9.z熂c6้?p#kk<}`@UKED1q`@320Vx/#" ZU>pw&.//]t;A;ዅЙtg<QSi+SijΝݕ+t;?et(ȗEs5Z \&(%A&KrdYI%y֮YJ٥۝y:6k_Uv`5ԨTLB1 4di\5'E+ov) D/a-la­x@\;E@N +Uݣ9zM/*݀CGql{Н7LXwRwQAwJSAzn3KO+PJ7NV1ځZڕa.UQ*r)l{;!]V%Y2:$ߦؤ>HY*\s k5Kt1i'cM2'^Џ:쾨ニbyۖU$ VbFQL"%R^ހTMWL ЮPeT1u,.\XSx .^dkKzKA EqWmu60ekT,(UTHdUx޷0;5m t6YZiIM3mY{L[Ftn**S7s]5&HJd ЌucsΉ`"X*X. ̇y \]A[h.la ;b B0 \41F #b'_d|]J, ETZ`#5ކ eZ> 2%ސď G Ps*5kSq7-xN%""!MAw!L=$Lƃd0&:WVKf#T1}38ܒ*.eiLfϻ+ѝ9-\`XVW+f9sԻ`p(,-1Zj W!qDļ3^"tʮIeX8 @rݠ>~vwxӧ+tŅ3WjtWwB7认vXH9s(L%QDqIl0fحnQC1亄vik 5?o޻nz3Uf:붋flؾxzS+NV.fQ;sKST0L]GHޜ7Uw>.''<&0u<^p?ϾIq] ':IW+?@x̝:~zt_NGz$+|zy#پěۻ;)?;:vx<0n,{xO'ƽ="+7~noh}FwG^! o'hȇzjCG7FGOԆ2ǭ\rש>v>I^oNԅ'zx"NytT3{sԓv4JEp5 P~@`jV+TDO1rm%G:z_eMb_f/kqzlT&֛Jfېuz軬x؁㙁5ΎtVcnh*oI$AIoT&t[8n9xו%[cdD2Lr1E e̯o|Gj]{قv!aݠ.'jvy|}2 WLE1NBƨj|3b'uYQ$z4IL 7 "ptoJ2v٢2M0*()DxrC++7;mVE\>l-la ;>S2DT0# M222NJT0f1 B a}qSWWL320Dk<0KєtJ,:psI=e棻@;,n6[Le_=μ GYF1plr$'Np4;;<~.fA$ sN+);dABMToT蹇xvt,P9a2mILҰ۴mw}?^qSʚib' rE.E2&00,NK6%cO)gm!-la newQdMI1=5e2c456id@fݪj\VJ NL'6FEіLSXriwz{)8%oל\ѝ9d 4J0MŠī`^Lü]I2!3A.EeF֘VЮZ IZ;KRc7(׼O9:kU ᰞnW-'ɃuVGwͮ=S~:p3ݮ3&i=ÄWw[[;Ǚ3;qg9(z!K s]ZV;Æya A>6!0>v֖}ۏՅA2$Ipf4d4a%ǝw><7#E(( suVL]*ռp(_ƶ˴+nweA, @ Do3IɔIA"e8 ھuzy[N xEYV<Ά.}E<j-AYg8Lhhv,#"lo3At xR()Kp;AНgZ o^iHSsphrs ;SltWdis%gswh<n`}Y̺`&R"F.E ̙Sc(Ru2[}TDl3:]锭-8S8y).^G\r$tz]^MR謥bm-4Һoeљ1GM-+yZ "-4 &Y8Iq$QZhW;Q4EYcmFa(#NLn?a?ě &5s8٨~<7)ԣQ^yl:)?D6su R^WIyuL)x]zP\yO#'ӮC ՚Ǚ8AOnz=U[kķWS~֫q8}9=aǺZ"/o\p_dJ(|?[N⛾cu|տ;;o>BN ISo`A?~:idP{^7Ks~^>H'k^iW{׼n+,'۽_$AOqz]/l3 Y 0LB)Y@cY:nƉ+LSw3jMf ]e,hˡdpb=f۔TR b٣qyuII&alËEqcjRIi_Wokҙ?!0t]"Ύ&;ku]oUB .y6("E:'^|)2'T]=SoL',?8)$)L`SAlv0OCe oa [gɚu@6"+!YL2I&#!qHGy["&Lռ93K9m.`pz|-hwp z(bVa(h(]gJ)x|Lf(Q2t7#턝_$8 h7,rwXx}\l ;]wW6C[N%Lo;YTUd"B"H%Lx,=xcЁf\.'S~Vl$0\tC~krp?[t^E8Lȷ|˟q_3)sINw/k2۝/*s ^݆Qw|##yӣ㭫S𲻎ϟyx M^> {[u_;znX3^5[|N'C*Y0ASNoywS`vr+i4:==NBYdFq[8~ɑg9XAL3pc0)&|"`]&25]:$AL8ݪ>oI'4 Nw`+6p#U6*cTļT k7SAwuu8ytaLEܥI=/ $L vAv3rÞ`K:&#[eƴVU9+}.| aQX!rs̞V }]+8GL&g;dvΌ;WOnW.gr OW) Zs}(d5QIyەjF(U蚾Ӻx;Sm:6u/p6L޴5#E,y@*b7Lھͮ:vlir֮HQLEl<*ؚ-Bdea [/uǧcad >-=Y}&e*]JҵNФ*/D%',KL[4y/(í,t]F93=Kkݪ!\(ݹ5i&AI $ѡ.mooY6ńBZaifךOsn暫Y_:!RL%&K$uo{眱l$vpnNxwtdz*6sT3ns`~D;yp源a&BT&׭}f6۝ h=΃ }j,gJ(f,5<4U} $Ty @̵,v(;AJZ_47x!GSHEΖl^rٯd- Esa [?uǿ,I"n !3d1iH9GfN7O/gi_wN=bq3+8=5s|fWëJD|ц'Oҩ ux͹:=^<@chcpvf'|ů<_$OUN8NQTG=}5yCeBިnȞ`)~=69^wG'WFz$ u^nl{R]7}K[~K=&m5ؤX:%CIh}jm,_͚&o/cJ8YI5o  V)3ׯ>6q%m#jJ1fׇ⠻9QEi.mmY&&Q IW0]e<[LVj|ǧi7qkk11]T< A?ݮ9/=Ϙ'SC :nMxyJLG} !bEscեTTA9WX4mɥu.::Auɪ3v4Vm^beyy.ɆaUeQ(av]3煚z|d0ifvBC();:/.[0x [9w|bM ܐI%\"4aݕ +{77;7Oo{@KN9nUWtWolC#xX\y J+(# ˌ:.(Ҍ 긠>h^ֽu׳ěxDfddDVwy><ˍ_} oxYp4 L❝iFߩ?**å[oYu2ّ*"bhtCC?C\<ΑN wB`xuԜ .Sϔ'QُvvzcVLOwzIADfhj!JARv89a `< o^~*=r2=rK[RV*=%ݗqjE0;=Yg]˖E (fߝȁQDM|_RAFÌmvP?{!-ko1}oza :0m$Z,vz?Р KAD/4.^lVKtАK g1r KK=1x5ؒ:r|Gk/3zX*㋭f"@bh!W Oؒ}Q ueƓ )8;q֐TSȚ>z}QgT3F Pd}*zo0*|{|7^o}sJ;ͨӀ5E?7}׷mov3lP1-qq ~ux]w]}-•4U5nhB3AY_]Jgx:G' #Y-'֕O8WPOg݉]a#K#K=/uV<ج|vnV nV.B2[vLKfǢ;ԻiVo&a0 phw0;Yxb"(4TEܙSݚНgƣ;U\gdʲdUtgn']ck (2=cg X-|D/x0v-Tv#T-"39.FT:P GI[]19#޿ 'Bp^_'R)aBIَx\^FHFQd9ANHNuFrI/Q́?'9 ƿa)2.2xSΑ]s.(2S`As -< % ݭki&cSDVd~a2k̊0aQ /ݺxҭ;/;nta0A+<Y)S$:ޮ7\[osUx+!r guakcP D3q :t:yp=>rT_}ܘh/12*ZTLSIEvڤ;Α2z[[ÉxQ<.-tfpE|.< Q@rp.?s <tg::  2ܜނVM,U4uF4HLL|o*qU!eN7<^ԍ8|I$2JF(rF($:1.c)1βFnJݱccX Ta|awrVxpbV*פ}w[oN 9DD357AhLD2@g0I/1 \ϫIrTkتoga,V9:7:_6%F Xҍ$ϭxuw;=Gne[ v!=|_3tJ[`u?j65ciF |Ege2yLh G8e7Xs8#w`K3Oǵ*]4JCwƪ  z4MGcU% K"s#pQt9)g9H[MXs̫-X#.|Aϙ*#B/tw&_?a["t:e'Cv#a dxg(u&_jURNdm*N*2#ZXő %qs L*2 tWBA]L<۬ׯ? aé#Srrٿ[WN{TL_%T;ͣ͆CB8 f8b~vdS8$qH쑡lab^ۣ+_*7(y=o5дɄ<;p&ajSԦo8>u~=mW_x0JG|2&ӕz*z1:!jYb[PDU *D7P%o{!Dxj+eN$N/pe@yUkPټr[q9}`~@x؁[%r_M? V8R m':9jS>dj_]6e6S. < f>sOtvkO=nA\"S%p\LW0ă]i+k :; ftt;#^ F+1p09[rwq:Nnb^FaI$^:Ux:Y)̬bfVQhlʻ;*ԧA,-b~A Dw uHp*c iשI:NPvrCa0Y< D!::>ZB|W(Ў*v@]$*R0 Zlf R$riLΆn2k]ʇ2 *W5x}:eWjȲB_Ҩ{8^0ܽ`{ph6-~#vIj2+ =AJpcžNf\ol(>cؓ2pέ65FlGM Gt9` |K=9vp&(? ]pQF+i ~F}g3c^&:}Ϫ8ajWl7 3_9~K5$VU?{^ސDo>ƽվ 0S9[RFhP6p3 uQ]Qa^h$Gy3? ]=R|ȚjfJxi۶l \VO@ ^Z.O U:$6o8B~c-LN~o5f7^Y>p750i0NU Kr 7l]Ǿt8\9u99d'M/6sRjrTD_"4XFq'a4aN:W=d"s9/1g)㜖NBwv(cnye t΃C&ƣaQh&3d9)ΓGFDm=qSig'n X\D GX' *y4IP'Z49کN ~EH\. lni|KZX.@#w'2K֎?҈ʋj.S0PܩWA.|i9W $+ R_Wj*AW96䲱\_Mb V "irN8Ea33>&M˦!Sε]}.<N,¥p吼 QKO?w8\,4!މNi Ԭ( VNuou7On-}bkn&N2/Hʞ8xQˡԥqRt= ڤ"PRJYR&ye/i!aGI%(Iűڙ$“*O?O3s]&Sѝ&kcTHe&qgdNNV6-M1Em>ӧ ImnငvZy)\i%MJ)JЕ Cg1,f%PRCe҉#nY.A/3(2e%!fZ1+yzŽCqP;=<hvֱ30+tNj/AMwHxNlןVVn,ޞy̩7unz8!UBiT{}Cjmjhw4uܚxaSced{p?`<`Mf 2I3^ʌL#  '.1]f6{~W~֠&e}FZ/=A`ZH[Wu~?o*`Yc'bK |hb?5ZF:>er*AqH|.p뷓o<-;|4WqjԒ=  8Ș5ÓIt2"+ɨ.g#eM^wpccPN8HAɩw+~)VǣΊă80, ^ Nĝ (q'n^Vpzs9FJ $(zDuI9:q*IGT;;qɥlqYL1쇻MکF-H325g ȴ;9δ?&-l0۬ $uѕ 5ҚĔrp3lyFS7li1c-oռ0a CÑ)xVR.z97ֺ?8K(Z''vk]1L7S4-dLֶk3B2($^N(%z+b>MUi1tM9,wX. b&^c3eOTɫ:\{ο33AV!6~=ȟc4eFc:q7+L*3X_/TVWqߗ󭂵khrNwG P>^ZQkHe,xe$a^1ήН+ԧi]+~$s  eBx?4WQ$q9N[:ex:ڡ) O=[--ynس`y-/KKl8wLmjS ]nMY]z0XEQXIQ!f@͏Wx5efpOrL$PW6<Y?(z`A1 T\!Dh"\% '6=E2祮9NN)#@k8W L~xqŸZA1;+0RpK1:iF)D5MvXMĭItM0Xh@v- |vP:Н,OMhyL0r nF\Rqs89 W:y&eǔY+@ő c^e0$;Y*VBy^CPM-:$“ro曻fa`NSc8OSھm9Mc}:EDxEeXЬ3u"8,3EgE (2HU>NSEUs%dS˟^Զӯ 4*yFHpܹR7t\eZ/pyLd΄ǘ>(峫PUW y`06ꯎ)-b{Q7HOz+L)͆?{ADެԐ5,ef@1w2:.ϯaoԂZ_R@j̪C[R>K!"nѴvߐhΚ Я5rUK;X/Ƌ>FO g)l7(]a8$ep Y3jڂR8Kn4_>%\?o>Y7H!Y{(DZdynW6m%|Q&Bd a[f32SبHCn-#Pۦ{؝>wgqx^޴qk3#%YV$C9P.IʮKx ^~0CZD#\MIpz$lDI9*ᖎ|srFaF$gJ ::5/)Eݍم$~P,xAk8qZ;O,o}o}>/@AHvcTM6&Rk*Z4کE4Mf; >"1'/XڹNڡ(mSN|J(tϾ-s(1\R3VkWA;VjrxNʹvR]JRx+qf4W6ޮc[@w*@ "8M$^|ԾlMmjSٵGo3Pk+};s(I'Yiu/KQ`xfg/ɖn^egt!@/œ tP>!RPj+ D+GUHS8tȕ(F][a{8h騞fԟ>xq0B! 5LT6I)H+Dٙa*)i;F)B!S,OaGXjјv>E]^vJھ ,73knFa0&ARu<[qrDw3՟o#WĻdBU])\L+qQ9*k0](I< fW_=b=u ~<htƘcUiXUiz%j4 Τnx&Xu 5HhVv^&ur/QQi0lbR9&}a[.3HQkͫS9@VYA-lZ!g*dXmIhxx-fR7VК|4hXT&Y0"b>FƿZz[D Qo׆YB(ƶ1N6SFf*eJ I=Jt/n\ۼ^C&)-P"Ұ*T/FY;f '\rɕMШ]V[E;⻮xTjYԀ0p7?HhXjzYjh%Ӿx3DQAߍᬪ41L7B&c*]AwDWL5SVp-V}!d4) "eTJ{cE$ fyǏӟzMS;{:7kD5i/3<bUIXa{!KZʲ50),tZ5$ e~p&YrQp%Jfm+ =)8Ob@A3ܹh87 ;kHJubNS8%d 4$̳!AWV u[ J$zIk̫U$DK 7z2ŧnH"a|ѥFI6КD-WӇK.^u?4lx.Mq?491c3K4>kUar! H0Fsi2e}9uHPr T+A) XE;heS"+*hDB8w`BQe 9#kfr@0x) *v|" ܧy_4,.ba<ߨo~НZj>D<);YEuc(;6]90SDX6Nwqj k&3!+( h'b2rJh'cbG`z)hγd!غ<~ nu3Dw%ZkDw*%EOqp;/$>"JNE^d.whoYm=m" GËJyshU1xt4m^ۼye/ c:vn V/=oјE.Ws:qW~RPCIj+Нw^;<0@@sBlSgYT'X.ӒKw<.*"[\ŧH!軵5|cgMV7=?ĨAJF>?V`Z~p V=VycxneFLG1X]}.kRRS+nm+SxQ @FarV"F5+eԐ`@hx~jp'Y!|A*[Mw/ h0*r j k4~w#$}񳀛J[ѪJd4™3pi^kMo ՞a?kX0QڐpcB#;H'*G ͂Qirx̨/=LH%`a&:"7V;P v;'Q+K|8KG'J*O4e(.dN)IJR)@[)I' 8rq@H9%23qNDW@!M3g'IUpF8cjcqvv8휢N\s 1?V [yh n_)nSI!z-7fuY1\{ѽǰ}װ l[oaXuk.Nv<2K&enuQaˑmu_6Km""l)sVE{hL}@Y'<5צ>[V.Ȝh27c 3E=h `,nGl`4u_:bۨZvxuxe2oضM~Qc04:Fy&iKĽQgx9@s!rTrwP:bWG,(ˢ|x`TOw>-) qr9쟿#}o\9Hi&@S '7IR|\I`,gHz.I<¥&W$uy1R.N\-'U#GNssŰGwe ,LӸ ;dG0xvu:=!|DO먒u1x)7 I'8nco=]u39l Vp&hYlm׶ݕ$+I9INxlR}(ż:h.^w/^8^4ӚTQ6Pljl(U!gJe*\T#r4vGXidYwMN4MԦ6MmjH&!r4*3tG U)8M#a湗3%)RxBKlN /#6 DH[_#Ѽ`#mHh'kN,9#A?"{x9c9J#9ay<ۻ8 aQT<oH8ra1b @nmLW/`f zA+FeqέO4Ac!Os+v>п"6pXN-,=Pβ\y*h'ȓFPy6Н78V҈xtx9j.pOmWګ(]JLzL+]9O໒'!d!>yE݋L@Z8:cH^ Rxxk.fCZ[f2=m6t؁ȋe ٦6iԦ6]{_pzUpߣRox`}EH ɫJsUf ΍RnBʔxix~$nF%ѣqф*yQkOUABB\긮;ﺾWh"e@BgE>bY]7L p:A{8p!4+gfLǍ|˷KpmŤVm'u\YJVjߪ qgMr0C8`kI cǜ.%6!#:D*ߣۡ~vbqK/m\4쉵ps7۽`ݞӏKIB'JN):ZB;Jt,B,f\f|4u/\Gw*`sm$boa{N;`}=*g1e{!#WQ h7`g8nM3ۍ:mH|A/fXܾp0Uume]gֈ)S=*"aMCu{Q[[s)w0MJ+#*$~gR`>ۨvo5l`Suở_(b }-Ç%9?kxn#֖ 5{j96[8猱9#yN r8'bˤM _k/H0!OyЊ:p:SA9S[o?f2{p%DQNjp1pFcKǎ/;t%,Bwrf9BKIJ; v/\6 | R]\83Uj[^VG݉xW{%<9;mp\:I‡?<Þ 6sȊhY?: VĪ]?o`(-wFśb[yt=`Vwd)"pA47~Ȉ YKRJRzéUi%j=@{ 7`hy,۾i7j~757=gK5?o&ki+i*Xwo{ڐAF|dTqGpZ;kh*&@F_fMnel<ӚKLu`}*su]F?L*_d )YMS:PmvϊrlpB@0urPӌztfÙQa*'iMQU(rQ(6pR#Hݭ͜[_nl/OxR)p-!1, tN,B)bgj$z#L O9#+3;ZdIG#>1RK11FcV0[-+kf%+eB 9&%ܦYn:Jbs?</:\z),aX'** *֌L Ccڧv+nil*p8㲃XB4Oga:|Fp6.F!^'mwu&bh3mT':wa x).skk:s2F!W]=`Ih'^ab6<8^ tǽsKtwM*lmj33u[;_g?XR1eβYvEw'A }'`jS*iԦ6]sߕZD<Q  K݋ NI $ RU;zwf[xm*z\$@ uy%]>*X-䑍`lع%%6l -6N a7p $v ؀74ز43w=[^wKLsN吏~z~ _Nw:(u ؾR_3sɖhj΍E:_ :zx(/{QkbT@wFk*wj;)$`rb>u>;ώEcQt(]2wYLUc(eMfnHVA]!Ic}!;[oM.+|Wzoݽ?? SVtG=(Vó[NѡUr/V?ŋ̹VjNr~GYɿCt:7-8U [˭Fo%e񖖽8yww_Tڶh-|w)t~jtq|kE}ظ9 pjv]J]_S¬f)8Ev%;g4h=sEzVj檠6R^ѹcɭWY: Aن"?j.sD4CrU^ۧTA\>?Gu7h-ۨ|A? i002uGĀ‘G};.?~a6:,|ٹ.cD LwR 5kҾ^+ڠ8>ȣ}|@%3`q4ύLZyF#Ӎ.3x}'p<9M txΫ?Su LXhu]" Ӿc2x+` ] ט_dti>d; ke,㦈%e,&~Y鶒G4F%'DQR9^K;OiKڕ] Wj^1-vD+o2|t9tTS8jhH M찵ť;.w*Gt{vV,2,[ڠNgn/3@AnvF}*LBuM}Ƙ&+ʗ@;5δI.{,;Ai);ϟo OU6tW/vg_e[ UJl}>3{}}0wC_h|M:f8S|r QGv˱gI|Ħ .|]aΪ.e}Acplmm48۸d5VkU^o9w䏻yuY;ZaS\8LVLΈ|^O"=bBznoL ЮuUtWB>QɈk>_ڊN ,\UgdRhN3ZkjZk)1Zk%e_TGCUX/}!: RV-UJ֮o{xȪdNk|ds-οGp*LJsJ<M/J{Щ Ez G] uP]TxAq:AQp \χ|Jb3b+3nt*2Aht,\j66:E9x̔BLnv7ٹ{:=1_L^bt[)ę դ6y0CLY9MgY4eT^"̦[W~Oskky~> {$:we.n.^%~7wmڭuXa(xrEqJ%|"?78*j{~3|ƭb-/g~yЙwgb7ls9j0]9azUrUn8Ri2x6[c)blKuw>P^ogd^+ҫθRy^#t^&!/Px_'t>wNҫ3t5!(Dyd3`nU!}H{Iځvk/垻CCʧ# ! Z-$[GAFCѽ5$J(NQAXPympܵ( }x2~y9u^)QYr C0pv>%;pr .'3]y?zIhN[fN2'>jJJ 5C*+YU|&],x[8IƳi&&Tr&0Z8N`_?xN!\g]C/13tylo6O>ŋ\xpb}r5j@?W t֘xiO~ҞdZ I!IHtP d1Gk2MRAi]pw&Cw<5F"BDF}tD@w%+]s'f↠Z $^ڎLd<! C31zptxe,b)\2qǽR5@6ephk9cl+! o5mR1JG^>UdϠ#a"STb/jv̪lSIB()̴RIU4EUըWjJil:MG1x .(7SxsV}Ux=-5_Sg94jqhns{_s]A^9ЮwhK~AtNtuZ7΃kjI92$ H: .4l6qw6g:AKD\H`@P|C t7:5~dS%u W*kRu9qAw%Aiຆ Z] ۉİ ;-&mSx{aKoXƳ"}}x F76Q@{ '}WnRhAeS5Tk1ּ>y '\bx o^NCq<|D )2#ʼ۶ rpz7 εv20+/n{-8mǠg5w]Iݝr}+ˁp94>r<-^\)9|fNyc5{n*_ N$߳1f%A|A \+yq)8횜æh{|lX޶28alfBp-8p?%ظAuv4,wqVQp9oSg_SסlQ/g:zWǺ$Vlz˛>!.^XUm~gAgJOk<aDٶ)uLƠ'oo `]0xD0tzeL#hMdcbSL>~Fw )_y3M'yX-p0߆Ļ BߝWa#?-namNN'GwcLMNf{g0Oܶ>GrJg^݉vʮND+t?FhǺ;yd3#<^?>]&>V tU)]6bsY͆emJ2/C'^Ƨt'OjVvۗA]`kL[0^jẴR mlv!Q]g|N*ĒYKoXƳ+NKXP1m720OU2;}f|1kz D颧 *l?$LXᖫ!aZ%Dg|_!=/#<)Rzar- Eb ^h:ٛN&$k}~E wpWͶ ռ9ttt:9i y?|]&ܾY6a.{cZ7vU@;Oȶѿwb[kFIO#o{u[N(כ>t=Ul#l:Pve dԈ;c* $t=.Dw%Bdy)HVuGx#-afSٌ}){PTK+-@]ZAzimװ ojrph,odSC>*OJYc[@|$oրwWZ7^_>k=~Y\)9'Gaj) oJfkZS-d~J tt = Wō89;oul9]c%,V9rRd." uV8|+o|Q}kz)45#~|j=̷5ŭϴph#k+?*Z/2ZjsZ7Nx5,k#|s ޫ_X~]{ibӏ:JIXqnqD1I&⼧ّ<s5>+ Bt!Deu+ LqEQ(;3]?3~qP2tw3˺C+ƌ"Fs9Yv.0S|š^>mtsh |:J.FNS#py+0?{boW$>Cz qB '` IDATla}Տ]:rPgCgfR/v&)'G<Dž?RLya? ݙ.Ay[x4>:߻s !n!`*G׸9t-T,*L-eZx%K?i {W=؅g5bY&aX2*μӯ| CEinMeΕv 0zx٢-M !xU?0 4E0$LbF1Y>9 1đ`P8Iй7١ٛhFQEqI)'q2Wn+߿7w7̈́"aÅL>Cw]& 9{ {coqDI;vrPa ]G|L]v.Afl4Z `-+p=pݕ xc Mfn*[܊#JPW¼3K-W"%&Sv{N "ˠaF9GtX ޖqRe,?#XJ'PPO]ɮbó^Q1zH #B)rM!#x85Ԙ S kNKy6a>>ZȿdTRJ!2C&_K4K$G;h:/}.2Y _t.@wX5` iLM|L/3x(e/ewq$"hCcnW-{pyjLqI}`{G׳;t'FBk5!u>:\aC}]᭒A0T*”xXagWq'TB˝';wOGx-}[xX$^e*".Yf+_F)U[K IZ6K 1 [[qJ e?R (-r뙳.I - ltU=s?8ul~\>g| ی9tF!||Sz~*s;>p7V{-_ᔡ[<4ᰌp>JYh1Egg~>h9Jv8nw6 xb*eا?Wî,q7X9rmq_pjtZùe%Lѯ9`+f\IȋHgY ֱ;Et?9N՗հaӸR/ЭBQ7[f,oy__ėt^9i$:ixcPoHfaՈjAЉX2љbRA"0pk@2K cD$~x5aFV+y`DŅŘl5ǞKjT`ny[R*>_o reƭt+\b%AlS#ZD ̃QCqsŒ[2 N +`+)=ŮdWlH+AJ?^B?iخTz9' YKo'4]޻\YQAe*'3Okn[]eFOxxZ;q+A3]L 'qFxM@\$0 M[wLc1q4YDK:}?T>X t]qǵp]ފR#+<ʣ;ʍ! ?zO`%Ӳd2ik2wzAi]z.ęIJNDž9{o}B"JH)ebt衉WMӁř~1/]m{E#=[i[_1.SkbC\xs !Јc<`\#NRVgmOxK |R͚NA zIùQWgwh ;$B[~M8\i,;ZB5#ey_R[5s,8=}IiUglq2z.l!LNVn6fFi$RȮL5 A0E8 /J['8upA}ѝmlUҔtt^!0e&Lb&Q2k P ]NV=Boh]ۼqWh?G$'7Iݧ"s,bJ"*Yd_o_pdu/O:]yqfJ1qqESk709$B +*go{Mãqo5Mq]ۏl⮄sU]|a\Ŗ!02r_9,MVe,cxZ[ 8 1T@b|L!Ё[RNhBQ<ɮ`P*gռ1{b#dg*]o6Ru"{ qF;;SVVX]>DLLeS̸6I4qJ]9e.L5II:=K_ЗNfK2oBuqGWY9rvgxvgpҫk Tuꢆ䣮lrls}m8|2E-F5s]w)G%hc"jN e5'īiƫi *3c/MbRBJZ,]&F9.?}jB(x=d)$k(~?"g%:-ի^ew߾jmΕiJZvaAn5z@4i5>+sǙw=~Gpȧ|/0#*K3S$qv6٠lEKf׌6BB!MD>1آ9`Gh, &p&MHRW2wLYUl2l: z l^c>;_#;FGR5@wS34y9(%`,&LItuW_6a?/~:Y;\|X?8$Zvp&jYDQdAT<Qpe-l뢔yk;afy bw9}i0<AjD'}t&( ݥLwxI=""֊ ڔ >@g. 2q^̚G_If6'`SqIqIstڀz/^_ V5P%}'5~iqwJIcMЙbKo-<^N3]Gx3GpfpǍp.ؼ'C^ N}C9]LW+I7OQqCv+d]5@{ DW_mS0ӵ*c_hW#*讪L4It0<5ij.T%q'UId&+:YZxt[$M⎒݄U|H^G+(I债B{9+M}Oa7q_jǖi3wkVZmSf -NMyOW՗w>9ory뉆a=[-ď`k;*X,\EZ=؃0i_g>_99j{8/H#F1Ɛj& ӸI=JYWaC('lN'Ow׶=9nNe va6a I2qf뢔 $Pwn&y/B\(k+?\׋\V+Wi{'''˓II[bC1=1^^̀!)ٸ 3YG>2P3[t*> Adr\Z^co !Q=?v.vgwVLV#s}l|]렼}PW_*ҫm\Rv8*?.KRk/LVqW; Aw@wJHY;'pP&[Zlۨ8wIU Pr%+GٕtP@t٘ab3o]tK2nXJ4e|ł+a‚EbEE%ED8kj/]Eh(cgBMOA?Ǜ3=g~{[!}_om_4j>chw{FQc6 r-ll/ęp.ELXx,V]sf9.. Α@w``'Zgǣuys) uvgxnwxeɻr]'YsCo2\g3Rw7*^za*Tg.&ٙA %eek'd8u^k'+d]JQTʎkjRA /ij3n0!Ѵ%IN73?-/GW8LaKVxOs_"{9ګF×[v7Z ʀ*?p1[ro2kdW;[Nˬd{Ƙsږ\piWkl ~b=-:/? UjK%*zn16ϯkkJK<1+M`v!\ .9khRvxHJ{Z)1*ѝ'jN׭OUzݵXcЩvwFc,f %jy2*iB0(~:\`mnW:+-UtQ|N橊7OESviZrw٣ SI)T2ГD8?#E~Jegm۷AU9^3lLݎ>'K%+ 28Q/H@)T^irIqQrIUYf%נjZ/eu(S "P(xh^(6uo *]_Td5n:en,Y IE|'p]=NN)E6خ@%LR tL]uQWA< vvP;< rN׭w:jAzҸ~wxnw1xvwmwID˦^p▇+g*|]ZFD!($^cO%!ĤN2hȜ* 7?OnO'Ӿ^vR"`.ijub3":HM8ymajg)k0x =-3"([WTkA/b -éū8*g n?-7Z7)Imڦ#2a|1Ie8=[aS2Pt9be:/pMl節2"N6=0wߖRݟ]o˫XaX,&淶- ^=);ܔts`>ys=%g~\ +9D:lu'Zڷ+'}bvKUd׭~ǙUI?>]#lS+GB K,CT^B ; f`~|%LeRR/$i $I"XGNybPVEI<<ݵō 3d'_2Nbu'riFpv*CZ0;ip J" ܓg*?G$Xpwpt. W*Zc,J#xa5O&)yѭA\;2Uq_ʎtkۘdD8b>&*i*45i$NwdJ`wrrb@' {1i2H1R|PQ=rAVmSV]R*2 M4Ke,cX2n8NY(IG|x'njsvͪJТy&G]_'$Cz #1B4d(JepNcr5#Ƞ4 'ak}pB~=]ݨx0wU LderFmP8@<~q RS ƪlSx͖6Bs?Er IDAT c*LT(T턶#Xi*W),vtW+`CaD Zc^44ML>NC+TDI!P;Xi/Jz!rxf13v`V/3P76K3K2nXJ4e,ÎӟpEO-ѕHo$QlKv$cYGqh~$U-+Akz?x: ٌٌɸ&T\S毬Jϓ''=O,lVzl>m*mf~3?y˹v66X]]gV3.}Vr&*]j |SԷ6mb],A t'|l J/Ь߅O&Ɖ*hnp:  <7b)K&O|3|g=-EpJscjc.בo>ju+8|F-{F盜%?d5}ը5k5g뙳Jq)3̈}!hS϶4N›^[_Zk9y.# )~JgaF|i=_-V;]߁X>~J~%-eg%$WX%RH;OGO[|V~Ͽn=?hݓ~uɼo3t툺O-7m%Z_l5 Uy|j=|'.pzB5[܆࣭ۭRf) z'᛬f R+Nv~,_F6ũm)ظ~E1Vmq ~ 9όXDnhnMy ,((#ͭ2\'ʈa{0*]iQafSeX.r)ѝ)rAЮxTj*0X'TFo&Yy$zɬɪ#tQݼ^>_)p \Wm`k1hN\:$s{ZT4I㌸2J dV!ZMrhw4খ+fuDty% u!L4K>d7W,e,c˸fqȧ'*+,7wY'BzB@dJ'ъLmM1=ATh +QjE$l*OTpnJ h:ٝw{ۣTkVI⵽y;9y5VW :|Q\=Upw}W몃 $vr1GW5@Źq5UB' 1c'F 酩 |,Qaf7nQ[x|B E єhB4!2':gK|dI-E IWNb:%'&0ӣ|v4kd]V\ .Ⱥr) M 8D8|m'4$]XnSϼ^a5 x[kaX4eihF4+,#8os:8m3l^Xu7>bq4nVZE0X%C~rp+lI9+x]=3U }ȲR:2jvy տc5{X5\~:灷4~j%NN_ϵ"8I2ۙy_rZxGu(ǖr+8yI?|bN_1(+_N1E+ik%-FQ7:Yo-o)[} -lyngk Z6"1K+l+;aע|iM7{"k$9dҴlH$跏붎EmhɁoJE9H5> ;h,xO؛[dX}!rʜ IWdIDЂYLE GAE q \@z[~#4~-%rEzrd"'6-e.eH<2`W;2l=HDSfM MY&YHDũxXաBD"3R YR 7HDnJV*8_\b`p/2 ֹ"8'm<wW1顎ŒmbGgtbv/MdOdR`Ɤ#˽9..͛[n?m"/ȳbCosZEIR$IcӦN7zrx;4<;+ާ>ݎca&6mw:!}4U)jMfcOe11HA`azSaSݓd~*bnfltݒg1Y~\V˚E1ٹj_%'92JkBKNoK2&lO~Ao+6Sʹ9 K$%m˖鴏fLlG嫚b#d]qH]]p0lv؀( Ip8Hٕf~.+|ZӋ~?*}{o79M7* ^fY)4K;rn-[կV<-b81`JLDRV4B-Ұ %ƇLuTZ;* `M++\?Jt#0b{w|-n5fsEě:Mu[c}]C?Vv. jk&4)Kzݬ$D:11J.I9_9>>M q0,_U̺Qx].fr96\p8*333ͺr2Y+jQ6~iw2\ʒa?9 -:K^Q$kk6JO^Bt'N8-[we]2gE0|ehE'!OJZm,FIfPMNw⎹-n[qpq6:X|Bٕ>A΁PjAxF@9bG5kk˧Q^stPuFr8\f<.)Go^g1 qgV'9ۤMJdr]x{r5/瘁?0GA_''1Q adq:*+ `'f_S0P4)'MUYƲYO @'_^OW>.] LQ~ Dr&B㭆k!Su2S|)x&<^. TWH!T@_4o#ko4+@,c7":kW"[>0ÞH9:D`VZX/.ɮN|dȹQ*K W6S](Mԕ_K(=먷K^[ujm ^c.u F/+: f:1ݘn$emykߜHWND>iT/zGWK7u]Rn;X9/BF@͝a {Qc̰3$6’&?0~ۑx;5@d<rfNA `rw刁v?6#kS>ךHIg > 5J<+i{IDBa }hd3Hq)u\/77$vSZ2"8K^ $' DDLGM335U(V]H0g֞Aܳ t/3Rz`wYapLcZ*X*f-Ag[>={%ʻz$p};۷d W AiVy=ڰYW}k_J>qaIEFAѮNokv{n7旎RM*w̯+t}]k .ЃN3N5 zyciK>Ύq*[[=0yRWI5t)ٳ]{yۅ8X=ffЬtk%>)-;P5i/<Wξ3?l aݡ7{53lQI0"WLw,e'U++&?MrAvOh7ݩ~ot[io"NQo6x}P9| (ڌ7GtӤ5Nwfs;4QR;GY _gb9%h:!\0۟'9\YoPO>yqcA]l211*qܡF>Kk uʢN*}z|2G[ײ>e;uqe-+[.lY\˚T:gE5b+b¨J1 ɂqנ&FKzW74uX:G٪_G4CFd ;2vG^s?fE~W_8 ^iOoAn&땏-I ovo^2amo2~9{[k bᄣ0s0gv)$|j@ä+Kꕷaz\)ڼһJyK#_)I=q:l{K}Ÿk,Lʵr+#[4$ƷA9 .5]lc$tI8D((U[2;{__pϤˍ8BIyufOJ ظ"N}f0ݚ<^X_l|x6sDYW*MW7쿮h zjX4{ԩpVʌ[R_bܝXmXnm^\޺u|l]X޺eieː*"L)#nUeF}L@㨣={1~dЀn'0֗%Y)g:jp>ı p8P7ZD(x/gΜr ;wr1u|3běBD'o{vweG[IEwŭۍK݉`ay۠[twe+#˯ƒ!XuqF+:̒kD{lzEՑ5Ktw$mX ] -ʵ#fH )fucX;t9ݺG c_I>z wiq:_wݝ7îe)/6N"ze r}60-͋aVK^]LԜ]/AWF'" b:r)_aטy 3"WoQ$& DHϒ> E{8eƐ*y"<} 3(هQ9Spd=n)1]Ud4-3[԰Rp7ycHЍ~oYׂQm*??W%\猻RnIi_Em5- :mU]]g/f7'vw9kZt ۷9&ߺuX>"],lԍ sb^:np86g=kv.n4x]m^wtVz)Iezdi6/"\kx3)՘ֲ[X޶uai֥.tK<)Wdt3|?6LuU)fSSENM۳JSHX$Y$],Ţԍ\R:*fsqx7ƂK4q|~;8䛗w8}3TiFS- >U\Y(3AڂVqvoM]a~Q ZX>jano.I.ILtH˦2낖+ô|]i&۷wmOSz $:$6I0u5ss~! &g~} {j6֙Cjޚ2x)ZWsx]~a%T_Z|7R@ԉ$)'c4)%hPOO1?*uP3gl\$OP [̠ N2S.rڇj-bNRvhˡy] DD)~sg0fM J*Rؓ\ 4Tg$/b=(83S CTw+#:]>ceMi*Nʽʼn\2Qq3G=n+ ęm%)aC1Ww8J}U_fyH121q]v#[X.,{y~`QsI ɘ1c԰1Ȭv'_W>:4ƶ&$.IN&bH2!W)X*f/vV!gq5]9og&۫rVgz7zt)wϮV3vhj.Ne+2K,4z]m&Lj1=|2'urr]%tf9s$e`0IO-;r*]c ?w{ dkKRl6!]~?Y37RK-!!Ƶ͘! P&II=pz 3:z-`/@`dQzzXZF5r?`73rd] FB)\47GdoL;{BWGA̠E2-]CW g2bo=eވL|{E G-}AĄ(͛2W}WR6O7Q ,",f Q_?lcgIeuu IDAThQg%斘[5x+ V!V ?E 4 MIZ0)K؞IVi*WEJ +9~7Yq8q,۷39daV_A7$mA}4 sh1dȿ׭BO:5>a-O}]F t{Sm(XT̙d AҠhP$㽵五VAiiM,L%Cnѵ=7ƂK4qkO9$~g8(:IiU83M!D4)a(ݝj'S˫呍}S|~ cֿ]Ksm#p 2}fS""rH9k "Q2?3O$զ5FnRQ/yahMo$;8^c!os֞$3PNض\*un$\gBt@vWETVkVUWg q$4 ^TuJBB'M| 9sXvBQ(;mY閙%L!_KY RtԝWuPx-峗݊!gqGϜ|2w۷m0xtǃ1x2(n E X]i?.VJnլqt[7.%7,%7.7,%74`[tA~f,%I>AѠ 6LlC^w`9NLjL+XtTo؁@]S"&se":,gß+Y~K7IGرwDy)xZwg3c| Tw_>)~ѐkA%}" '9&b8^9"86`' ,|3k3ڬd-1Eԕq=J4kd!M$.4fJ#- yJQdEIޤHٜ5e+lht,Zf̯[$j/f NA@>{t#·86s8a_1ǰu+7|VfotjI }hmh2,Qz;cjg_NT~ʞP?KY樻 ɍKɍKޥ&EHi$E3aU,*=4SLLgL.Ӄ,e9`{J_6uv1{ϦsiXsN8 ^Eذ_~I,ة)/A~g/Y(. 7zՓ^q<aNxUf \W^$N#|NN6;SHFKW~x~XUfHR2?3GpƦI=.u q3<`!r5-c*EfTS{Dn9 "#y|쬿!r#q6A䡛J/3:"71,&y݌MҤ`b8M n\~鸍H\`pp8#~3'?NٺMB}LGU83' {ImhAZuewTnѯڤaewv|э~Av -nZV6kl3aɽ"%'ㄺ|H y̰CDFzrE-) < "Bk )Li7@SO>feLRKPe5՗u7yJ^yqYw?6DJC̙ n}N5AX]Mw[u8b8F̀ ęy]XFo/ .EK1O2mM`[Eyh}], @Tu3 >?FٍԤvn$,ey>9YN27-w ܴ4`$]&hvȠRHȀC:99 ^}6 C 90/ٹݎ-[غ &' Vd4Bcyÿvvk:᪈ ̫m^+z{zz,Xhw,`X!@:/eCF/OhE:c-/0|"6K BDDa{?2jj^,]fxsPe=<ټcre.#E/[w/ȇDHx)#`;r;Mi 'B BBX>fعo< @$Kϛ"zld--j&Oݤ QTɤI#eWJ3o#sp_Ͱ߆gy*6^fR`SnH;#tQ!>/i Fg+Up8b(˶:ݮ|$cNw79EO'o࿱|DXz<;ҮcFV*s a֮Je9qqY:]6Q0 h\GCܣ0l ]X*f/( 7Yq8qd Oi64HZ)eSWʞĨKrQHzvwA$#2;\[&X ϭܝdk{!eWZ\?u\7 z,wi-p,$7.M''vQ_70Ä2x_phͱMp.<93?Oe 輔hJ6|G>)Uڰde':ux+pBx|e7s<7.o}W$6VK4n9O؆J_Wv]x_p5rϗ3xc FN3jƾ))KÅRmk/7@} |VSZg+x5JWR1e2P;lH@Ujh87s!"BA+Bl4<?cSԧ 4Mf&%"~ ΪK#"U' cf#PzA(J=Ɍ@ZXI=`VUiUENzt^6S+E&=~vn!rOϙprOdqQ}f٨T^rwEHrrW"#铔?.Iw̘%gMVԭshXЍhƵ?aU@e:3pRvFlO{Lt; ue_׃_qvq[!gqWiBCYvdrnһjnq /7,a/r`|_fgVᒑQLO ܣ0`YҎF[;a|]S+98c@a,]j(%MIu0ix3er [:v^"\M@BAFORx) r'̠'F.՝Wd.5,a&S4a.BD?W(orr>!x)3K>A1I4HHH4' *ݰKw]lP0&.VV8:qIfSo֮*3z^N?`;~!\r\-i+IvPvI'd9[7pD&+p8@l4\WaCѤ /)y2j2Hr(H (}]N?m? ތEɊ5iNFQ W-shש0,x@OxYΊh_P 3 ؔz,a X xN^TY˓ lJe `ހ 5*f#k#? AJ ^-,u6QnT1OJyRp~.wvc[22`At谒2hF ^RmF?š1v_pkRvB(ˬF5Mթ'ˠlŸm> .'U/VuzM3zk7q7d)Qw຃\wV-TtitI$Ґˠd|{8EQɊp8#iFBH$e]J^vh˪b%nTU_1o*i"tL5cZ,jz/O1x)g`q6Dı/4wڍ;\vTFhgSW <h Ԕi$ݐQ3)ЛyqDCN@*EK`te/ Z AGh>owDS|u鑊rf=yF3QIAVqc6& Q_W'7zeVD7@IU$U}fwvE1S*]B-,s`;ⅆ7\VwK·86s8aLrtw1Eh=v5Mf1lrz٠]:|:ݴsCo.r_ <}^GoC bÕ h^]Lj&r hS:W.a$awhI+_yt.4N$z{ 3|@=ߓͻ3ůn|8 !B>[Q׺MH$Y? 6 ^QPGpB5׵KP.XH.ȾWf؛*i(S,@s_3Bw3Eu8` %%Iz\ftT(YB %ih_Y@:WZTUWb$Dfl3ǝXoԩUNza~*Y*32CҤ1E:l̮ˮ="]6XXoykpx|cc386mML f:I%%0HxxK*s0r%g' >YVݸʤ,3wecٻw?4)Iz nXVaz}+\d,{1/FL)^<ɆiK/@2X^#n*`u-sGY3#2u#Hrrk?3P(c *g"lodeB26SW$ɦ ` p#0g.p1gLEό̈}gܖ~3?%O ;%2^"C%h,.NS coa$_ wi nNd"r)K2wT._gu/!)S"0tE27}|]'W,J~,$QAV}g!'ݍ aYXa~n%Ҝ&b%c2[HեhKs86<&p8ǑVnF/PvI), g؀ŐTC Qi&.)vUo6CBӓe1DPYvw[bn~ciLN2a_ؼT6_ñ7ƂK4qy;ۧ:tsҬ4a ^%nkh mT cL> &ݍ3rz9ޠˠ1E7g|B㜋s86֔h>QR`Ғ#VRuD,Ӧ#eX"G<7"6f?F0BqhD[A:1f eV) -vnP%V#wWo/ gUD`rZ#Wa'=-dwjWv #^R)ߧbDmJ=3l*#N$R*noD:Bʿ)A )} kˊF@MMxS 6yORfؗNYfͭ)⸭cMg+݌^N#4r# R9V+EB:d[7d/EW9lׄ IDAT *qWS&LoeB6V>R s860ql,8p8ÈVvL1xt0Zx#n8W԰տq 5n\>߼:ccM/&LUHט}\"N$-H:[a;"NK9wG`o#>r+"{ۛ7E]l8 LC38kHoF K"uLrzPr*< }t%R"TCk󌥛$%*wGL8"+'1{? SLʉDPFwG_wAۿc9X{Ɇ"bV lq aF8#ܼ¶))iV3,,N+H˪ Pw[y 0Jxv]OC/FE ) ,^FgݭXeþ+@A&+p8X|S23&W!n20l.G1WrB|5kO*9Kauzu}裪Yfn;+lB/oxkpoh:'y;;6)J V35Bg`*U-[KIw/tW+[Pc=Eywpͯz /ϏMWvG¦vJ` O4OxrI&i_;>$W>+_3c6O \O0aW"*P7h&28(fjeY:S"kTzi7Dr!W~fcyzoS6ZJIOGRkM3"뇙W-yV൧a tIw-KJ$IܸzkpƂ3x88ٹ<&0ƞ+ C*/V~U2|`) !炯{qz庈%Dr~`v!#Hiv"9eu@Xԁ#rd#8 /_C '#pZ#}I?,d,SSM( gY[yd:HK͑=w1pRJt\@a8ιxvf&AV<^,jn+kqz#*/a~f܍a݈2~*0->qvO*:`LVB1QABd!$la/_ww58Ռ9ݰ_sͰH[pOL w̰4o_k$ALjfew+/K|e> oPfrlD+MSmS"1E1U|"f'0/ӧ!.3l7osψ,90  ~ l8W ɪȦY^]0 ; S'~  #H&9=TaSY^fuxE ! 8n ;{ fNGG?`MdyA /h4+mހ+ȇ^@?< bsdR{*k7"F3(:*Iwvs/r W_xwppp89|GdM Kh*/xyQ])f߫IOبz!Ĥ;^t'r Z6`~?O{kppcc%_C~eldT͔RY5UDaedO5 5KX}渻v/LIe9YAzMM Z)6 x#_ww5%YfnNj=yfoyvx^arnDwSl[$Kz-XC!7jׄ :7zZ1:-"USy r<q2TFv%9GY\$Uݨ+黄"%O)㭝q㭯\yw7cR_JRMGC|+|Ӊa(LBAXOxUؕ!XuYYCv0|_yZN磼g${)c@ ʉEf;L+뵷 8|YzzHޅ^_̠ d7̰C# $*(A+ xyu |:O3c>, yr"0D&EN?{/D -*߹N"ceLwfJ(ƞ+eWh6y%./t2r,Iixҏd3/Qk^JףۥL Wݝq[!gc)9fӘ'b+aQkp]򥰙d3 ^0ahP@ eYF7xt:Z,.27Ws91W/-Lb 2C2i*[X w|7x!#ym\5.fǵ@R˨|)3FS"Ѧ(8U_& Z@;SKyߙڕԜ_\O3S+55QN|CS|bg X~ ȼpn[_dLAP,1/N !N+9mB@det#7^"2 "sA΋V FK: g^8 <줙lj25KfJ#eNK*j5xEE9S)#s<#viiYY2= cp8j#7mL5f =6ӄ47bkLLCs3l")<o׍ZQ7JYf3 zSq8npcc%PxI3GMYo+fRJ$ɦAk7 ]Ȳq7"z=2sq򗽵s8nX3&!U^,gM[jq`Oi? <P6B:x{SIwa{`} R3mQV ;Iu!zYN2!2B"Z!1ϼ[̫굿' ?B+~BטEİ#SU\W·A<֔> W^I9k `}q){m4OD$2KDsdV6  2K_eyy0d+Aɟ3ee񟑣&_Qu ḵPyћn2]l6Yyc6/p^xnddj@%eUw$ww弻R鰴"g᭝q[!gña>ŖIL5h6*sЂeB͔d $[H60t=v0n ^?x~*++,.rM|9uى"W%>4/kmvĠE~l` _@,,dFBp-bw_=HD"O=ib C;îsR֐]O%+K.9W7Ty8Ɋl#u' kL;Mi9lH ~d˖E ;UR{ˑ sELD4=v##&"[_g|4p8~<%lbiLhtz <)H2)$ ۼM͸[ZA<[;18p2fCef͛f*4 ļ$#H2>ILDkSgHYqāsv96\p8DrA7`r4=j9RlNV*B͠wewI s8!֔hB䩭D5`A5adT/]n}eX"DRMa q͉$;0&bcåJDAJe9 S<:%m7'TPWHy(Չ B K#u!& {4Db0lI O\g]"?)"~ZlbcC5M#3[_DN"RuIbZbWͰo / {_LsI6 ř66\8DɊp8y7oV  .t8p8 kJ4)PL{0dgaA<7c'&w s} dvky6cƜR8w1 ܰHVur [ly)48^{'< m5>EҼ^T#\qK-b]p!gp8CÛ_Dr2iG "׮^^OՑynX|!` 9 \ky}rC<2(! Xy* y326^WҍX.lqnG AǕ fN"e+)AdQ,D1Z kON? V($:l}y-pza_ΛxUBmO!"(΍_h {?EnB8m- ŧ=28\QMׇKK՞iR+BV+ʈJL.BZl'8tQos8 C 9p85+2%L֎P6]0~lN1e"3Z#a'Jr:f0σ%&kU|xyXo /̰?L{klG8PfA>]rcMzc4?vM]Dvg`dpxN^xIye M#ܴȜ6&i򼲿"|Ap#Rd!?p8icp8  .t8p8 oZ]iш}/1"6!̹h~ZNSޣyBz)! R#qRڨd\no9BLD%i֩""ɒO!֓G9d4=`O@y R*9eĂg1NEK"!~Tgh+"Ux x!E/Wqd(Ayywokfۃ(/Bd yv^%$|&OuA]7<\,p8<3xp8ǡaE 4c/F$e83S=,xebx #N.D!\g"&N\Xe'bjCĠE`@|;`*@$' !U$ Z;]{+d% $e_4:W+q#8 l; }.$ʫW 34h ^o"r=KFT\.< LB/">=&H XYn\SRn?/l+^#I wp8H7Yq8p8#y7ƂK4p8CÚߪH?+ҝDHCX F+tmO# fe'7M}J* %XVA)$fj5BRiձr5ᫀ[9D[2"B2i}!l(an"6QNהTj9K1Bʋ-ȭOט643׳232N2STjѕQ[ʋwH9?+͟䵡tȓ>2cƂ3xp8ǡa /J7/-Öߜ[@2 ҵ 0$et7S>X /\tluא&P&G<-;$;%~"m$M)^=2!w {8@S:taRג ܾL,H'ay^ IDATLId;%re=Ҕ?z7DlGbW, `{o#|z985:BG`)5k\yohB$R] 38k3-jp8mnp8p8G\Xpp8qhXdqm1cg(+"%) R* sjDTΝTP48jVI'9J/kf#}Fr !"ʺ)ޤԃ_ PZ: kRgdDfŢ͐Y+ZrQf؂Rm+SedDtх=~ ;8)] xs). X@/3? @'?Ojcpfն""F`5>/DeΡݥE+(p8Ƃ3xp8ǡaMVԭ]$$(ao! $.B|MdS>:}DNJ߁$$$5g.|zZ3 $6?%n"~*찜Pʝ;f=?K GY?W@-Gm+G\DU RAҾ7 1'bXr'SֺGNG K3r0n"K`g'>DKBpMD<'-':7Yq8p8#.xsl,Dp884) d>= Ld4 ș4"a3qoJ+sԌEO_`.>`βF?e& .)goy[ P\ 0Pڎk`!YTEV)]oBE0^{%IpqXuwFrJ;Ͱ=dG fir;^9b}YjfО:OH*(\.;2q8 cıpp8g*?l̰ɦh殅kt5I2%5oaVJVaW-DQɀsED@Aa9ð)+<&??_+.....֭[r\"y^;886qB!By|wV۷oߖ-[rLeY^_\\yfA^{zo{ A'}XSKKPXV,M }f70_ngx +>N AxAбm;mz}AD|8){dAB_+)%`FD"JR\R簲J HR~qi([oiqie !%%-k]=xzMmVM5nJK|, _6°VWyN_{ +ڝs>u֭ >;46nO߄]g KZx`$㍉mOoȿ7IW>%}3iqƤ>6%fUaޫ:XQ=NB!Iڵ}6qVVVNNNg}Wbcl!!!5TBi+ }t~ޒ ܶ,o?{弓BAІ_w[ey .1[] X :^5|eoE^aƆ:PYVLO>׉kʜ_LWצ8ʬTɿ3xRqzw|ߏv;KXf^/@ٻGHAX2_6~6&u$_g|_0kÑmd By.1.\JII9zhaax[qggg7`ooF !gϑ_~ѽO!-^?qĵk4 avDѴmi/R\\leekђPeAP*={O:33ߟyA`3p&/=ZEXfA-0|%$W^矽j]@!-]Ii8H$<3 "u=W}rkgg /@P`qkYV=^v̍r.=^tju4wFA+++Jq\ړBH] nݺA!M< 8ӫ:,Ƒ5jٲ)q _3k7?Z:M.ڛb6mmMwc_Vr4{odu)TȤ5h? J|-LxmЫzmK>Sgy^VOS7kT'Y :{קWp6-?^NWˆ#BeyڂJ=ϛ-KUgU*U>}O!Ͼ[ !LLLxΌ4GAX0|=*So5x7R ۞ .juk?8҇lr\.m04QF5͢r`-#O:U נi|i\O$c% S)*.UQ4{smo07`qAPcCng[G3A(/ѣUqCz:nࡺWB*[+<z 1z]L8tmGkRʷ~C<ϋT*B!4#T&&&:00x^UBY>ESLف׿HEU~0xW|ςYs~kcrϒƒYe& O)k>Y5$,r n^:3ٷpJʞK`goO3Vꎽymu,9pܭcz|z%7wgØy;Gwc8Zuic~M mNyޯ[隲ڬ2ڷhcer, -(82{@j{M[g81^4LiJUz8}F?ܗT%9lOIc+Y8_3ǙǢƹ*@{{eya~7|d\n+𷿽55jhUmMl1@˝z|@ Y'@J`Y~=G^5M@9c٪8 iE^"gYgM5ָ;br*nutvw8*uλp@LXV>6>qw2"*gI'_8ފuц^:n,~&ɨsV5. >yܷ}וG1حSsOW ! M!-8/6]$hu,mFctP|Vѡ菈+?IWF%:@[@+# W~[WҊYEU+:u묭#}޹}_>7XmU5HV?8_EQ`EY_ NsdnXML>ݥuAC\ږd]ee{֖=ʵOsK\W^כiM<]yf'm5SoTvH:gK4e\MyyS6L c}VuAvAoXx%zR P7lT Fw}ǴM{!VA3ĎKz6mob:t;8pSI 5RS:*T7;kW'9bsR7D+|%Tjknh^0RC+3O(-@BP ^UK8ՖH koTK۲TF0dʪ-O!AX555 hq& BQyhvg70*VpS߹oCVV* J! -^"tycUGtܷ={NNyv< HAtӎZpwj߹FQk*BJi^׼ϐcqzpGk^[v+\M c</#U3#a^\}NaUx \L.p.NXEY$+8u\Ҥ0zJacV~6`wВ]e;7RvlӍ\7ӁWn6n_10}5#>zz,4nXt?\~o;7j[{h>sџYڷ_ܸxyd_;v|7\\̔2(jдxuCT sK Y͂z,vMzכbUzq.ЦXY璏]YE-:h`~h ޅvʵMڬ,wv0gbF7-M- bUIz*o欃(]젽}loL#1 gi rֵ\;\0L{)TT\30q⾽mƏp/,еd!gw (JJE#Dh4C$L`FR{0ojo͐,F[àuM6բk\~H +Oι[2geYnҺb<B\G7̋DR}!ŬL?ؘ;OTep>J&d*'pR3]m]J| =ܽad*q1S.f$Fd$6]D TZoXD2/|"HMJtOGZw43Mٽ1 A.s5AabD!J!@R5W(+%K$?W6ѿMF՛}4iG}ҫ:#|/aElz.vt%Oi+n\ٮԕq:k~Lk F[Lԡ{wՙ#ҽ+|*v$8N}!Tт ܺtm9(*W&g2*; =$8y9w uvãczzA^S),robfԬ>\fg~Vm8_]/T*:byD쥳TV^.J^*Ud2Tf̉qw7@;ZI$v ÈgnBH 04n|B!5POjfqu&㿿w$+.(]lL&{ZuwRK[03KۺoҲvz= 1cc[!RwX{v\jJjf0UYn(ʬ~^5pWVVP<4B)4#H*ի:u d)Uwyө\ )r\.??]_tRVٻK}^caL6["ukzE-&w8OSƎF!CT*w6YPeX~z\\ܥKXBy&+7n޺uxBi 񴿃رc;t@jBH&dh B!BHfїUB! !B!<4#B!M!B! 2QQQO"B!B#HdvڷoߧZ%B!B!/MB!ByNB!B4#B!M!B!4#B!0sBȳ->z*W>zee?ZByN48Sg'c+`OZ_PS[uѢh¦R7oݚmσ>ߓՌ y^RPP{j|xWܕ,~ iD%[ R|./+ꓯ!-6>,傀g?+N5aUsĄ)C=}a {x[\Ѐo4b3D&=>'ݪh-XK;'L+=2}{'+3yL0?J򰺿lvO+Lx;,IË !OFё?=kHasWEE_(GH%([xoK0{kUN":]5-lUOl ޾ `wgc~yi=I=V\4;OOif_ս=o^DIWس`-ɇKnsP:iU/^uxQODj- ck$:Kh/.zQ+cuyrXe4hɂA,ƇU]$/kJ.Rv`S#V]ӹ.8S_W]igf9'akD>~ݡXT!y5^xkP'[P ?LWmgӻEh{uֺ+dz%:_VBT8e;˴b:@`x0Pc%D:^9*}3E  8U_ Uꢔ'Lk-awoF-K2sA@K"2 ~g*Xf0 a4_ ozЧgdűP_9--q}IȔe{<&;١X;m$ ~ݱpdmiiǶy )@ׯ͢J@1rڄ&j+? 8uơU^9(1.JY4rʅ-[8sݩ4rEɍLJtx7,3f^>F5˰t]q;}b }<@ |{Zjg;2; N=zno^e@@xrq퍻Q@79;You.υ96XcZ,Vjv^f{/U|le^Df osurnWYS2+W8/ثiD^`{`$,eFSBƪ@/OO///lp/"vx?ld2ێȸ$:%6@F w:NAB:,tmr_zW"xJY#W{²RC4VU۲ ;oΆ#:y{Oa3"%گT K=ސYa[;˳R@/OOO' B]΢@UX< @ ][WPus>!OQg_tR"i;|u|6,g+j9EUS+G5JzYC+r↘柈cgqAq+mLk)^ZUac@+ӶLX6vjV]e8m~<7K>.:5X.Lݻ@-.;QVgg(2s{dD,|0a@dܲ%||mnBV R~TV[}pG%,C}\P\Z'I,$)֊s@)s,^4.$-}noƪ _I+V sRSh(%-`Wjua_.UKa~.Ǩ@d-مjuqAZafsznL_!i?{d,|KjZ8M@WKx!Ѧiq!)Aa[P5YJs;ekTיcJ'Gi#:tXArm|TݸAP^KAr@feeU@UJ_/y{7#㗯ݯ쎴dOI_֎ L=`z]i++ȳH7f-mg(o{m0}:>ج>OO#By ]F|%M3ߝs8aOF܇{b79B!0w7ȳhB!BihG!B!-  !B!| xb;|3F{饗ZtdB!B 1Ĥ0 #& ЪU+OOǫTf!gǓtE#!<#':cYY&KGݻ{xx( ^NKOO7,++]]]CBBڶmk<޾}{޽Vz̚&BOnЩD Nt:0 ՠh`#;88tܹUV|4%%%w8nϞ=...3g4T 3 oO1117BbOnЩDx ^?QRRK/ eH$O-1\.711y211/^Xe0^矃fIe2].3 C*!q2FrD"{׷nݚ4l0C'l=9D;W=G:;;3F*_(),,m۶VVVڵk<wÆAAAvvv k֬0lUS"~l`h8l΍™qyۍoފ0߸H#ou #&OD\az9]|d ( eY1/[r0 ˲[n}Xo[9;K/IJaLbJtJ93Ν;mڴux] z:x=}fsU謹))`W/uq=deb-裏9kdtCZjunN IJJ]v5nP}_6=m۬{]*.0^l݆ 1Zsz#T~)c$Gߟ]˼Ls&<0k.x8n޼yƋ88Sq,Z[[_6ԼBwtJ?3&^eރt=0?߻v}>=:K;=qٮ[%u 'Meooo.t: g޹`{ĈқzĥSnypOc-M|4U9VKQdO>-,٣-_㋡<tڙ?JZسQ:5jk8ԔyC8qDzN駟?SN=`{]nݝ;wvZTTTVVfȍW㡊h IDAT7'I@`PxW7\ P(0 b.ۑQ>MU{Uk13]2Nm Tqc. lZ_UyEh׳ dE^jC|XRruF,keCzVF3Шi;*ӐΊnй-Z4wފl u۳_б6>T²EFI.-fYmAg ݆8~X+~3/ Og..W[#6 ,ˆ;w{Q ^#g5ΪQ?w]ckXlVCkφڹ} XM 7ke>xۅڟF[#xxlQNlGQSmSWw{Yj6GXXX~~ʕ+Ŕ}fUJR4y,Ыr9r:Etzydসo?t)-|;jsU{c> u lܺK?'0/_65w1v.755533_ƻp t$\{}uh1q`YF,G:{+s-Z47Oh)n䐄s{Wr8sVleņUiםʭhsz[uxaLF/:\5oaGmWS.zYz1.{{ eIWS,pYZȕ[T 55/v7`]l禤0|LXms~NH{3~S 9Sgž=mmR\qd,n~u+X6)0r˲ln/:cyXFk 0CxnQؒ8%˲;? z1?[:6)E7p]M%jg=l9wU'7V{'.\HNN߿mڴ4ir6ԝH$w y&>iii[6wxjz-[XZ|?Y;+T2OZna>Dgs? eݘun_7o֙ZEߛ x[ZO#G ݺ}5v͉2ss8ݹ&*ҲC;>b͚+Jw̚QyWE\ƙ01&bM̱\/?XlKR^=U5a3GX\`s u^j3[DMe=UGSת*\΍u޷Ѐ߮qdhY|̨بg}7K? oQWx+ӸzY f+(O ƚXV]]#ǣC}Wvc)Z 앝]f?#<%?:PC"SF%P>r9?n0#q䒇Xob~U_SZݩ¦ZXY~۴U_ [osc{̩bNqwyQeM? 8*[s~8'7ޣs՟_vssׯ߁֭[!¦MA5k\.8NT6e/_kd8Yo߾**)..hSSXѳgN:=l&j>;wX7)M\ݥǏwTH1_yw7&N䊘k uA#I7a*@;c/9i* a0 ޗ4qm$$laPi]k .<wUQq*V.*VVѶJ.uu_Vibu*TEAE4Bd~L2H[xC8ss=sgfUEg T-M X#jU޶ h/?y @Hhxd&S1/83R&oO{LPWtin^]c~/[Qf6cFJ" =ax?etջ>h:m*`Ֆ㺌q?cb.G5;̋sZtD&i?~_F;zj)ۈ7&Wo/y>/s_T_Kz}7ΏC޹1s}Bɺ}֟ Ph>i K֮O1Zо+dʡCͪШwnUkhuFp{9q4@aQ= Ȥ^fWQݙ~C@F9#q]}E4k-F.8}+Wd5{҉Wnܗfg)Y νQ<92}Uʵ鱯0f&fT(L&{1aھ7B.j7:<ܲBo1Ǩ'b Q3{[sX o3cS@8/xdBہ?n ~-=zdGDq&ȃs2|_?f6p&E#VgfB.b1Spbf72&8#q00I+:{hv;"!~t`|M "=uN|c.&d3kҏ?7_=\Iƴe*2Ū]ق_U x d2E*G{*ůsw'Ύ)_4jc_ygt)2Bux_9\.2|˗/?~x4Mt9$| <̤(Nݺ%%%O ƏgϞM8qҤIYlOKm& Wt*wC]79\:zY=z5u<'NVYI%fGG"(BLEiZ(躻e"ꃕ=^Zj`kZ`k1iZSu"Ś)Vk nQk|ޡe_ZUwczԥ{)G^,JH &4ttZqW/ \B7V_g"*)鵄ۓe~T4ZٕkZ-+kb ]^O'o[u&$_AAEp$תQ V ЁpeV1H1 %V%%wbƬV|q>3?󣺣 zg*P7Ι aRxC_v? jJ.K9y};)2? :ze݉8&yySG1$,Lqr|I:ʞ &3r4Bp2^}pz}|+.Vz Nzͭy& jQAZ` 6bv ;u8NxZ=ʤG=' on>飋o}(˼(buZΦ=uz ?ĪbS|nKo\;oG~ |܁uxgRQw`ޞ2N1ghB`IՐ.VB=5 ^h"԰<6&Uak4˽~nQ&+X@$n4_7[ CG)O 0d}wIi}>yDA A ŋ <ًBpE<0{ь=Ĺ{9iҨHc=JI9#d7kcrGvuMP$t< ~w/m @1uZTHj^uިjfi*"YG8 V1 WQ"VP@jmgL1r3i{3}`2G{㿅2>aHr^+1+DӺ]hzݨf"I];O|6Q&S0Nb 635PZԪtBPݟ: &Ncb]ѮS=KYIJǑ3Q|g"/"Ao0kubfH$$-u[_Zs2 4Xc/"&ӠE==+a[ړ+'^ ppߚȴj0 |Uڒ S˷ܵ&2Z? \RݫomSrsnGv Ž)@V~]Hz t:VY (FBa?L٢Lj5Ht/ K&Nغda_d7UU#Rr o^:+ԯ`g[*r'wE+,V$tcYU? iKO[eLI/oXeƮO mϛ߶_%p{ݩk+S!S=}4,˪JIaD =YkE`4ѪYZΟrҮ=yɓ'O<}lժ'g.ٗ]H@>ignX_0Pr5%9lGX.Y!o.J3fQp-뙝]$v[Z8d}`0NZc} ddāj Jr~~j0YkBSo_>%`j#F5)ggs=Z޸TBPo#uL&cF*JA x„ NNNRT"<ͥNqq;;th ǿO>DxBe\}.\VG(ذac?eOtquy'D2{ 1_bee3'>}-H@"vg1bl,Kfax5EM}]\|q=(lm-LB&}DZM =Dg|WNr IDAT=oW|s}]޴ݸ,>1yO}OLaws&;J /}-[lܶm jصZիװ?N>Kzť hCLDD)KgE2 Pru'DJI9li2f Q.!{GFnP%|@G `HjӲe= Rx3{48hk}+ʭkXXPP,KwKMr80P 5b k˴k/rr?j2wt$f~L~:Pѯfk>`J<χ۷oT[nݺv뱆 U@FDpyIWM3V&[pןYPa8{@nT\jOHS ,ȏҿySݣ_l!0yk1mZMZ IPanB*nMv=sJɨp΃"6-),mqL= f4Sآ)ɤR0E?~~~={4_xѣG5kɲ,g2[7իS)T*\Ƀ ޾d3gH^3`[Zwoٔ;=}7N~]^"t? 8П9sgM6]vMP^6}:(b2UP.^E8^L1#+Df6l9ݜ5=z!quz{-+9`˩KSl;շdWԤjMP%\NpXĵ{c(KG*P~>9tџxe*~ٿ?썂K{,NN+;e=+7B9shL4(!*Vr(Q,:@DQ oE&0toopc UjnDިTVC{jgX %t\8R(P(<Kے"TXmnwP d=ܢmʉCnV1}Π2zDBw)<|e+'H{Mɯ>4mRR$ck9FR?q[Ľc.3x޸KNȒ%NM }6(\$x|w^Zf@ع\)}rQ2|tRtñN?4Uk0nNaf}\ |QF8 8ܾT }l-UQy`¡Orsh¦Z; ^s6cwK(։\4' )|g3 41S&LH!@Eg D^م{V}Wd"z3 9aB$RWN.,xz:8׉ Y!^:'&u.l <ΫQu81,1q"?Qxr8!rBxYP 4 y>aNUm;N*anRSbJ(K2"@9uCW'Pcu"OdK%S%tvN~͌)FNqkm0@ĶMQ$\ױC,F⍔XʘR[kI$~!.ŷ^4n威#؀P)!kCDTnU(gSz{WQТJAD=!tSoU]+JRk(FQ XCֺ-`E"Z2EJȕ g{6IZcףGf19QԣT,)DX6>%IV˧LhQ2E7 :Y&s"GP>3Rf>;\(w^OгbڪO^_ ElsvvY Jr{'d2\.Y9ʩƃHVl^[|5=T.*xQFqԨrU~[ѸzoZۮlӗuŸ+z0B߿_aUM޻wpۙ]M] L q-.k)M)M4M4HCehZX##HpWINj)ʚ.Р4 -T)?Er&5=k:5>[y-,u9op|6ӑ,#EQr??AgmCB*# r*O:i5wS ZFW_}=4w@v(`+QN/Qv.wžY~#EQׁKy36B<)nH0GIhV^;h Bg?_;c/X!|mj -^ ܄VG{FNRΊ}L +[;<܂R?Ѯmnslxxxx +k)țHm/*cN1_+7j(YLӄ_#S7NŞ+~4džF\CV_KoA(24+njC) h?0 W3L]Njy} EGo޻wOEQ΁I6َӖyJǗ:2'5OrezEiok-\cߩM}j?o ?Yz'ms}˟aqg̘ѥKΝ;ww!<)µra^tTel63-'Ti0(+s9O7v9H+}To&!!LwnL" W}ic(䞀{3/kOOt@`" &c?$f^V 3fee#j(JYܤTIӴز"6(<|mEfXmg"]\IiK1ڵkhNūi)MӴsw>)MD|EevYaD<pbúAب&4[v޽{YLi\9)J"!ѫkv_Z`u& 6,eb%|ab%_^;T+g=3+rBACݳ@0^) E%|UFx1ٳgz>-55%%eڵU/qFvv'TNX#Gpr/)_\%WGԫۺRtfq4S`7{xAqwh}^҇%@~~~'W^uځ!Ñ8s[B~5|{i@8&Y4M_cD"a3f:MM]úĘ~bbbb8~4%קZ \|| 0»DVs[ou'97[ba(@4;+''++++v`"Y9O??{ӫ_LL=M: CW ,n~j ((( _EaA8~7pl;y?_o4eؚe@/f(0OY6P~[_=ێ]X׫卵H{86)\8H$Ӏ7>:w@+ʹ}H$Ӕ)_O4Fv[͖=~#Gz0qϝɍW!|d  0"gxݿP~#H$|.Oov*Qn }Ǐ_y7hqi94 ]e~5#)JgΜYXXXXXVg͚%DKR!f4]qg3fҾ|<_|ި/7S0~Jiq{jQn8׀αG\lN>74M쓼'Yy<+02 -U ^賷#д4P%nL0ƌmEi\MQh`,U24M6 a)<#1mZ2+?E+l+=y _p0 ҟYqaZ™6q(HA[H~UoLÈIfNz%iTc4 TiFO6XtQUVeݾzv^iUP K3 M{Y Ą_ȸ_8@ RD"oBS]P/@X_34}eP·9tҕ+WwD<}c_FtTn۶e]tyϕ\(YX؁kVΌ&[V}ʺ+?t:LVU\mmg![4S֭VOyUޣLQ={feeۗaErZ{ՕG;p*` zX`aoDbd(P40\. gRT,q&& 1 \.("E>3rD'X7wڵlԊfw{FF8i9*H dqqwόw'L+ ɡnB"[2ngf!*znЂeG.|48Hj;ו53(Z0 ӽL9|Andm璣S>%z&$;%-pŝymFvzȵ7x'~C1gʑ b"zz؛Ĵ\ymm^L8uw#G`f܄\xn,?\_B6%;B(E3 #!ah@!H%v(Jah)RHa!D?3_(?ko={oȝ̍(N5i+3f%)3{ ##$&o={om^0e/"&]E€)M;K$BZYrIIŃ7<₸1.. ud.u,Bw:O8NЊÇĺV9Kyw:M8n+N̥PHyMYm#(JD.'ø80&>чޢ3x>, ަT x1禌x,vTN`@G{L<~wj-9`^+!!!'OްaèQڴi#^\FQB*^ʷQS>43OX00޴D"""Qr豕Y\DMd"mz iL6@C]2^4$#/-n }ܿF-M~7 `sw$thg"|a+V|qsz Y08pAбӭ] sf,x߸xֽq79+0 #HrpNƀÿ9Va~62,!:wic=Kςj˳&aDbW윽ZU'srk#]y1i5 ĒO;/Iw,3䝞qn)t1"v;6{d;_;n3.|L@̩a^ECć!bJuePtIyi;nI`YzpF?cd"K0r)o,{O,n>5,ȍ1WY*FB (-xsy$rg\C$za{HM;bƒkI$͘o95{="ҝFF /7SLdA+SG^n>5ҍ:MqqՕ0jrBUkUڵIi:<\dU6%MdnnݿDW錐H2&"Q.H䮮5W  BW,]/6e\-XgEWq+c,u/& 38SȽbƞN)eNE?y=|𻟯GQ*3g|~AtZ\]]lsj֭[{i!ۿ_ާ4Eo~!__nݺ$Me2BPT S"<ϳ,3hy 1Kty)x^ZAd @/]S=eW<|ldC4/؆}d>6W⯡,gyD._Z.|<Q-]Se~û^|yWXҳZNe_HNNq9B@eeؓ/׷OYd|e2YUJ*=~wvvϥiZMJ+J˭70ixIDATԴ}t:]zBl^s/IͨǔTZn+BBU>s*o`s֭P˫znM_D6gG 9iiOGV U|ˍ^s ۤ~A_Kc7ۄ߇xwNЯRUX ;Zd#B, 0[ppZjk (;iܴiӝ;w4ib:8\HR߄@q\VVU*k(fVzj.h0p๨PoĊoѬFX/{DJE+?*UVETC!Zr튯8Kccc7$1Ӧzj4؄#9* 6akr'$!4h0uԼX R G&r/C=Am, HW^y% 8Kcf%C2Xp‹zFh:O8@3k:e<^U9888P8x888gRIENDB`workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Chart_View/Montage_toolbar.png000077500000000000000000002067371255417355300274230ustar00rootroot00000000000000PNG  IHDRN 7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:O֥ nT^Z#BRRҹsn߾2HE篌0 (d#o aHmZTػZ@bF2Y=WnPzb<<8xng۷8VYK)mѢ}Y.4c m?ֱ֨o  zj 6 >rľZ<ɹm"6MyuxR˹%CVygA*;$9#ŸPrÇ}W\ٮ]5jQX4|㙞ƚ "N$qTRTsvJq-w}7-->jzc[i7:xa%r(#,t7I;eG?8t# q}}}x㍖-[.[LZjuS~x{ &£1{j`ao;1^)0tnCsŤ<>}fWT>^v`7;'nݚ. PA&w52 J!0)=tjOB+O:/'O&|6%o|Fq_W%z̘ZjL0eOc~o]W>>ָzLsB&?Cddd??JaYR*e˖.j~ӰӀYokPvđUӖ=E½{]2n&Kt9DIĐp7)Œ)9RW͚5y'wx0+~Ib&,XYC#>>@xYrMNZ4bdbbڨ-g<ݫMǞ&|6^}^3f̍=:-_>y=&p.)!|fرcUV-WaGfQ*?~UVjXa4:ԨӸcPSM4뽦%v#aɘMVJ0oU+w?+322&ƍ˗h4bXVjXl6ljʕudr|dDZ*9uv8:χ\>nT *kّTtHT]'Kv^cSslE-<c̑$ZxjEy(Qj4999zɔsǴӿI_t!M?.rY/A8xQV0;K;vk_0:RD*& bñ][4l{p`1ÜfZqB65j>eD^iuP VzSGd@FFE1;;`0(JQ:) #raT*`q(`hw<8_bĨ mI;. P #c@č7%~9RxLHMVE=xmCFc7ߛ27BFv]\`'u3/cr׌?o=i32yڼӄCr)@M7*c;S=E`-]w/'LecDw]Pz:F]B]-85RWMnE% !NofG7j]q y *4&"T29wT]/3h=3MQbQ.3árk8 IuqnkIcA۫]ꩊX_WCH,Hr o28~ȴ;q%PhL4uno4k,_#,fEEANW``v=,aHS{"N߫wWW5yU"?qֆmj9{͛|\c4GvPй_.kZNmkZ`|9GaU gI_D2i"^id3N*?O(Ô:(Lej4BqXgF^PT>>>&It*8}m̎O7{\Nf87M:.fWKkŹլ[$5|yr<4E' @o ٿY|4};o3gZFQ'eM&`[)@ TP+ BJ88)r{WWMxԨ۸g~WKT>7_nRK]z`r /ZyJ(VTma#&,7{)Z=A}Q{f* X ;ex)@)0k@xOWZdMI.]>ᑃѱ{-Ҁ0lJ@WTR]HTrcۆ6j"j} - JR2%Vx5zw.qиS?3!5Cg^:Q{ٜ c^Z3jž)]F îM.u@â.l/aB8j] c'g^~!:D1ɗo,W5km wbx7u`RnJJ38Q`}]zz:_0X$͵l p'vSw)ZiXDQ[ -N(J}J)`RJ1l^}5Sm?W6$=/dNYyvڿ`M~+8?!H{ӯ)hpt}yHߋUy^,5{Q.j]N17dY2բxy[7m`glC!]?SHWG=oeÚk)%H%n򠑟&~ӠXcO埑7SW8Ŭ.S>D+0[E6sn.] fssm@fN1׻1 TwaOuא.hk^zyG|>hKF̺k5!pWr i9gBIݏ<>/?$2\2ݢxQo L<>@˺ !igv.Fi v: zc[Ķ?7G~ 7WvbW'kqPY/ CAE/§fI aB\p4UᲄNAjz(jeY-<(YR1gs97 /.q>TQy9VVp5lZfY^j/R(u*Yc?RP}?yPТm@EWjfv±gמҥסkV͖q)qzbQ=_Xȑ+mTX>~ro6|&._Z֜ɉsFz-4"[[3G;l6& `+snsWiRX~#G{|zsj_J8e6/X.X! D~W3}{j{X> c۬Y?l@ZmVw4Lx$fW2˙tRJ)_.g 3L!5K_7 e "OxxEfɺ[GX&~H򌛉ggoOn5}Vkf׵ZQaj(Xi?x-[L-O\m̤_xe񃭿5EXn{SY/YH޺8gO@oocDu$0`0 lHBȹx1C2I=`047v,H9q`04p s=rnPg|>:0U%h5t 69_ KB8H$DuM`vIz>^Jv錟s4=t~/܋U6%$NkMrtF={.9}~Ʃ]Bq È0=90)|IPP+P*RFJU)_"Pzڧ 8dt5Z{y6s~_/_V$7{וK Ṡmt/4D@)Mڻ h P Ԭߛ%UoZ Հ4(Jgm8S#}lڼT̠Ϋ=0y^uWY/A 87Ҧ |z{f~rIkx2MN^9~ 3ýUh֬ӣh\;X?ҳʞY┆Veqڕ+9WaCFf/HQQ%Vu {"lx_k5z2/C[Qe1ZO_V Ţd[{6O~ F4$;v&C>F0}avhYQ}yz[c_X3Ct!95^ݻ*oA:/fxpCeTҷ}IIvdDhe=\'n7qi,TtƁ<4?1 z}56l0qDe0 !IS֭{zKU }+`[#:=O (X:*Ø`w"bfwx}j}ke\8]]ө w~kTUY=j v/g8PRc46vK&Q-oFp_M@ WڥFR*԰V5=! Tmrmy@aX&i:+fnk?&hTl^7.5u#mCj0TS]ז+tJoWWH [$9%;?ɹI5uSb:{kĥ]&pZ0~!RGy8gPJ( (RJEp*yѓMQaYKX_h7{^]WZr3&PA-70 ˲,aZy 6Q C w>u`}~fA{Z1hft7@s`X(Dy"!J(d (n)A,mЁ{CUQK`mð HIcXn,a%O.v~3zmpvUෛ6|^#=9DԯS5dq~P bYJ T8+xJ_K *'5♥pd U?yu y[ `lw JB0F:| :)fL8:^zEVǏ8q"22!U6~njP8)"HO,r\Mlj=Omy;R*{w]bQMutp֌*uTFϩ:Ԃ_v0qJ{"ݡ2u]r>妹\[fk\퉼 ' B)=s !!ǧiӦ:ujXXX6m9sfϞ=NQFӦM}||0ZZG+w rWӳd?Z'8>_5] ף\' ;sݾ}SD7<Hli%}Ȗk;Ø1C웗~mi5GGaFl9UkPA}?~|UR_PPJ>tVTԴז=E~B*68M ƽV>js[M7YW<>0P֓c29=4˜1C9ӍK?njlj-`ƔE,|".> ЫE5eY1j 9jV_YQmV W_ /Vasj40Ϫ7|>/>jMH ݭ\`JصA1tר}K*xV;qgի˕^Mdx(9'yܼq3[>w,PhugX^]ox z2˜,|Nu>ȥN#yoΒލixSHIsg}, tq֭qqq\G.]]Ku0Ln]kuG%ČJ멺[ӮH#\Y{73>!:zr(pN,Pゃ[n_XfP(m۶ +*ԣ>(;{PGƮcocD4uv^&d8J%d}KF v봼Th?tᔚJ*8~ _;-zHۅRKa c4sߺe8J C"Ji=jbOۘpF 3}j]98N4qA)uͽ=~Azw|ߧy( p E`W _8m]`8vZ(GS o[aj\\rm]QrҼ!}BjFV:Z"6̳ @JV%Szj HpHyEU;݈7N:]T;<ˀa Z@Q(MVͭ=i@ߏWO:Oicg_SMϑ۫Ä[g@)-޵˻j/Ds'wݺ{}ݾ#ZnWp?]a4Z%Uuߕ j"3HQqpe Nn?@;kV&z = rϛ9@yyջOJ#[LxbP)_C­UV5jԍ7" y/ nݺN:U=z8LsG{=f\Bש1EP9lʅKǹϯN7z/tM7of pj2^λ'&(EԖnrt)s*}U}U,KaIڄ`xK|)((PA/ f--3דF)_'gv9o//tK(t;צ{9$ͦrޜnt`NKIt];0&lvlS{TY/b~m߾}Ӊf5E4g ^S L0gtf\ /*)z4Ӻf3gNFF??xڍly>a+0Njeʔ^5,m@,ÀaH[Bӽ[V J )C)Dr,{+#7Xc!Rt6@]PtݩgJ.fVpXI=IZF֋gBPM-AvbxU17?R8q̓Vn߾rg?W<)r>Tw,/Gr']}'ZT*_C@婥%U PY% qd|2y"3䃯WP<5^< |A.[Mc1JxHOO?tЭ[|||5k&_^Ϛ nq%y)5*uʕ{j⩹ǁOsooooRRzv7%^F60x{랅#XÓpr^!Gn+$ptԍ<| jpBppa5>{0 C`vͣ$"e7+UU2O"Ozr~-v|zlR,,jj./|I6|n:E)('N8|+R:KμFΘVr*]p Pſ;=?Q`O9wݼpO#)FVbpxh·.t;gw$Kp֭[?.Yen*Bxxx|| mh2cFw߼w.od@Pǯ ?sL,(Y"BDJ%ړ@Az5qʓέY2z:?Ӻ-'zyf}ouCK [n^^*yr,2R.Pͥx^75} -NKx:/]as?<NZU5[4jsbwGM~Y2y?8-c点q1 U bڵ %'Mt}SudBHPPP߾}wر`;}:`߿o߾AAA¯ӊY/t%J.,ܞRAӉ(õN)tT9{VkiFԼfС+-SrNj]To抔9a!?hتD4g[/ z޺q[i9o(-F|fP*\͞V栌'ÃaFhq(DPɵc4wgǯrzU+WÔ2)^9Sz{feYPj\pL_uZwP9?/Oq=VCH>7QR]ŸC G -竔"1nKX$N3~eduM (Pjby_|r,W˦[X0rnr,4ge*>yX/O8@ʠ8H: -U<|);_'cǎD)\©Nj4jjxN~xFB OR\8YZIDħ Y=^<<8CRTyeYB]puAƭx՞~;}J hjx7~RtOzFуC`obΡ['G/k _,:Ah5|]9cfv3vtIzZ?pn<O}f #ܹ  j mvFAAGkm,̑ [jQ6lӨ]YjB%wi7ƽ|!-B_aQﲆE~sBd:p2$E۹ҤӵlV2Nk2+' P~?%c?}~nJ`:>F0mc78̖SĎkxΕ~0M4OsyLƆL쵓Cո7`dիW*3ФUTde<l<)#hrrrܾTQ ۺF, ^5JrMQ^3 jҼ?u_f6ӔR|4i.~MCϹ8ws>ٳY?sh7b\9L ['$>O;0ٶuO;]ZM?W36룄moGVnn.8A\$@s'-|bNu@AJI#;>Fw< BjA w<&Z f{]5CPPi)R^f6JMNN`oE)%d}x ^:Ɇ c>,F3W bgmFL~r1z~(;lXߖaG~+O#ض)飞=jĦ/x%TX2>ڶ ]rJݝ. E:WGW}NaX,?lh)(uzcx7X\iZ( ɺa .<5u!7;%T*Brf}-HT*?G6iR{SwDU*XFd FXΥ?ywTrf"FںoX껓j湈ZV046>b궷ZyGWyΡ5\O/xlY@eI :~Q?Y;eSB` 8BE`Sw/TiyH XS8oVצnݯoq6كv]w&  Ͳ:qi0}U=u}78Q:[8QA96 ⤤\iKNNlS'yzh<5 /a pT`)~&aۿ$t0n7)";џ5| @؝":nj35>_+'> ciLr56j֩c@)Z9,SY MTu PÄU|?GNȴ<(++Kry02m ^ڊ訰^znrybvFFFFFFF7z_tYbKW<Ѱ R^ #8:aaϩ ?u_}MtwqD}d ԧ7oEFFFFFF9 ž@(KQER8^(ZP( d9W(Uo\zuJoVRp,ۡ*R{JaF){iVX>>nݺ0V HôJS2hm \(k x>V^U8# 8\G'ۏ@<ʲOtu2%ҭm9u ' 91l6 Ts::xa1 #9`'*|ѮO?-MxJ{Vxۨȝ%.^&'#######$6AQFUa%I8_t/BQy_?޸qBu\}fnsGF:J=Xk>q3.^QPiҖ#<TymH.v;՘2a07{˝:cGt_5-sjNuV9u "ͽh C0a `%6t c O؁!RGI\~H( (RJEpyM 0PzUUTUVMIIkK}XB*0Ku݌炪VR\^i'թ+%O ?/;&CuX˰ޝKSd$xDE)ޮ+q]>̦&Ba޵M2222222O;.ƃs~ 9rC,`),caCΉQn :|7qqq9ULa_曯ڵj@1&z*Tj=oX\M0Ѐgp5[I>7!>O6 aY ð 0d#v\` a$Z7sХӿ"V6J>!"DB,Q7y闒S'0OӦM)=|IjhX }zM6uy wΐ䟛)c̆GDTN~ɶdo9b}[HçslajNK>:Հ#gH,![uZ:_)(Ŀ.4sVpyPFFFFFFnӚaIlBH*UFq͢,[L5Gヴ)q(t~1~>/O~x ^r% p "Ʊ  zLV @; Y4TFMJ(ByRX|iiiÔnqPPP֭N:ӗ^gT*GE)SrFodV~uVE!B޶^bsxmִkG4ÿ/z;N˾v.%46mܹ, gϞpBFF˰"] ÀBrYHoTL#lYMfV>e}q_}/ R( )[ 5PbDKYO|Z˔厭lyzQ*R*.W6oϲ~saܔ~M5X|YB Dh gkҥK ,YұyNj=== be6}yNCp) )*Q\ kj7rVxQglR{`7}5}yʳQ??0Ե+O8.aEtvZe527?/^(IRE(?{.ƈ`m=!d2Iq+1dlscLZΪދP/` к@kÏhw&%5XNlZ88 :߃5[#$NcC9iiiGyI[͚5swwϓ0x)~XRADQTTj~JNzMy/䵋|ϕ0`0 # ~vOJ!SR2DXq&RW;ϼKD޺}%vd;eBJc57sۜ>Uj;_NW\2[řM̜[Ca9e'@Ae +?_fJ p?wl~$I|rڵEI$"mn0Q,*/*/*ff22f Y7<~ϓeR>wE^T^T,{/y7XN)U?[ ֚zs~>ŏ1}Y sf^򗄞5ݟ<0w;&uj"?O}?ԩӳ0|.OTR^dW/7Z%&܋(@e(C[ $ URNt>G\~SRq̙+Wq^B:u긺gc/اH@aHM7%}xHTn輬GME'gk40JMWGB^4Y߲I߷ؿnÔ$)^TofI22/߷ъID/I$IŽ=uɱ_/9~zop/ǓUUUx.xDħV䞓3qOݪש:c{Y/?B-"@~QG m^%A~TDfzx Yq]Ji**z8Kig_~^Y]8{KۨQE.~bj<G90Y`@D[g~ lQ須據 \߻A(UC<rtr,.$IO}榿s.k/ޙU(1v]ΰLAHE  ZDEYF)%ZDjEEZTdW?nӔhCwN %!Wyl]pcHڦi)k7qÀ6-.bϊX~i^![YΆ~$5ȴA}wہ£v*},:Nt ʳm[ {)vEmQ0oDNӽ,{N~0B:;ཐ߰V09NhT90DyN+,+UVoz>-[dIk֬YqƔPR6nf*Ydɒ%}}K=A.aG}%`Yu;vꫯ.\ >í(I8VTqXp,8J <+޽r>@?uJӪ;vlȑ+e/xyy 0 00>$VZN8? }Z]);Ly/{>sm<²p5C!a/uW2<~ZAo|( ܩQ$<;]w=iךE=Կ~3~s39ۧf`LIT$R* :e39 -7ܴexA`w@'Y%љGqw`Y4;=Zˏs\|=:E}xi6>ׁC~o+rS۬k;Em<SI=G&@VIfџrh]6ֆ?ڴkN`5K)z|u;̲,Ic7ϬSv|ɧbmOŲ$('Lc$Ywݏ`yOYI[$?yfh1Nj*g!ʍwBLݫ5mg!I/۽5-ْ+Y.~=p]K$NEO349bmY l:'ÑA(Lrs^-ܣi|'2$zVp)߼wzԞ}FZ$J%p;bٷqnurXL3Y APֶ=Zh^ûz=":z+/Ol읏< wu1N.dI $Ipkѳq>Ȓ(UX9e1)`m7iel~2yQfotXC>G-gqjqL v.. WlB$4yfO0tU_qܳ'wz\xϱRȼLdCZx{vl o7 I۫{Ҿ5G#&.:IA k7O. t6_N= bTj۸_:D-UT߾}w+?.E a|||ko\)wK(@jjBǏ7oܻxqJU0Zm``ѣGK(QdIG[*S #׺Iq櫿D[Ts}M.Lp>[~^U*b!#͐a~pQSm)޺G _{Jo[/RڸP3DR eYFަ~>h·%8\iUx[ͷ˒`YQk|w)[iޮ^ٷB~j}~J)*}>kuO-͇?u:a`}\sI .椗e#Ye)9挋 =Կ!(dAdzmWsàKB̗Gj6sVGLh£S:7F1KϏ~ݹJ#!@)jŞp a%Yf|G <ieYWY2wW3w (4nXfCԳ'ǭePB@ C{UXsx 3 K)A% ,Ly8^W}Шa1tmU'x]w(E ,ɰ3r_t,/˲h=8p9ڕ)*ɲ;L]Ɏע٢%#(x%vH;]1;Vm7Hn2MKƷ/Bgm-T|u}]bg~Y^_r ZmNZ/ h>~4{wȎ=C8`N̳pjLeQY%Թh WD.)g~#"@@/tĆ!Sx`r?vE{(suƞ9y~=gNCԻ(b SWo:;@PDaWQCl|9RQ Ô/_AzzЎag)0ŋyi@)a(Qj9sjժ(Z,wAT.Uٳg===ڤmܮhZX(4Fy9;uv8ww%$M&NӘvhU҉5]KmgnfAM0\^z0[`ř47av?{Ȭ7ma8+v[MbRh6}G4Z̆ďN"F&ߓd qc:Oڍ_dl[7/dge> #'H_Mo! O?qJIAEiuQ@;VN9*MF棚BH&DH 7ߏ_:e$<,_c!RJc$I@r_2tk}XCv-Z9}uldɻkvbvjz]djXe@Z&r__=[(ĒP)0BT.>9bKW6z8;"CLE*Ce*Y~dY3 i&K)d2;a^'7}.,*ho\ͰF.3)t#W˫ Vt9fxÛD&3;X@j¾%s#w~QO]avV.Z0}8-O'9K~nΩl& ^,8 XiLc0Ka˱>`Id\\j sɢj333<T%!Q IDATYCGcjBYgRoݸ ARKq[6saybz8н *hzf,WDY=$9>/+IBҟo( wcDŽL_ӠLJN_Hm.=21)v ڼjLb!DR.]7"ؗN2ddd<~O՚L&Yau ٢|}}o޼`.W.M ^ '~\bǔ5ؓ߂Ad7ya}@7./ȗХ& UI9N(8v}˙t*e*Vûpʩ7wukuJřde8B,S%=eYB&3딱Z φj5AA)  UE)]ϵkWyŃ- J|IVksWoBxVA&GJ_țֳ=2/QDjZ>J3|}d@ֲCBȹN?2W)z]BCi;79p@%*Y H{A w5) 1PDH:]+73o#MN @A'*,.$*z&7/$jV݇mq&%%2QQfDi[>sD*X\E?pj"{V,I$Ev7#: C»5j:Td|txY#W,T;vM,Ab!ui4)|ߧ-URVE2Xטm7e~a˶~;tT܆>zڢH)2:GIɜ;Z3<6x=\}=~وȨ[,ޚ8aE6ykTl MS'EA`+jCVձnieAs+od[*s7FZh2$ZVmsaj.нK)`ʸsVkդSU2VARiӝd]Z9ǹx7/8*}YAr>{?^4r}D9*6ɝm*SHfZeQ9+i22O~QsQj|ܪ֬6j릔{MU%U᥸̂`5kB/_m,֒}tؠyTz[׵aaA+~k-A Ěz)xE ?n3w,M\Md!޳q#&d@x_NhrmVuޥ=qbOSlOIJ݈jͼ3,j­1_!H՚se>wr2gK:kZ9J% ( |ug6~ϻ;Ɖ|Ӯ͏ JZse{x:;Y׹5W\'j5!~m͛)PuķW&!&%xawCo=]}}fFx˽=5a|܅t d]IQ!x;Job1û.&i^Q&Z/tqĞ={ k:X9_$h4yzzT*Y:g,3 V=<<܌F 6= t/WN?w ~VnL}c)6~ݢ].ws%Զ`HhR (I5?l;9lh@[~x1 ZƇN__[/Ud?^bsNS8)N*_8oq:Wy.>'TUj87Js^ꥶ-V(S *ZTnΔRW}:8%Pǜv;=!x5C{V䣷A<krЧ맃g$~wn-ޮ"'Y=*NoR ogC<'Qظ?nJRa%ްbqլ~9~մ0]0qyly f:Z;(oC1aI$ 3]a2qdCߥnwwd@>SM'T]F遾 [I橇L loA.Q@7>jR `OiS7sn/ r@hilRP^@=? #>W]홙VB򘒾A !ʺKv*@{[ &߂H<jS ;/eˊl hx审suC:Ѵ`x$if9.n vB7 5G_o)x5<+'8mmS}8qܯL tbȧVXҺ@ ww(v%2Kl0s;ܻs٧6~Ӥ #S0P83@An R0<ǪԜ?dz4xփ,;qnL-} aZ%-1kgLٸה;(gatƛUŒL=V~;ăM_}ԠʗF@)wEfsoM/EN@P/zn`;~[ .ff[!Ĥ:7Tq@V,r=rqFyxO^x#z}֕|n0,um4)|S2wڞ+utJ5S—-;=Pgk7;(-WHy; ;(Wag6`J~R NMxy }Mh4e9HF)eY!sVE@ w ӆ5xJC[u8PJ3n\:u b B&=樣ZUmo JL#ZmU@ƻ'N ]3AcxTݹ,ƠACիWNaOTeh4hZ畁nߛVATcZi4Hг^P0WQ OnZ<KҚ"j]@񓎮ܷȵΕj:SЂ:X5SKKZI vԘXd=8N,S7}O kBy;=sxU.:X>C&q.Ѥu'J!E(.e:yY  hrǘ$m*zbi.?h_ wN᫱S]{ӗo '݇Nq!zb:~6o!KOl=b C!n>//AJ$̈́!Ěr;=#3vYeiŞk3we 0ůϲUL_0̛Hxw1o?ԦmOr2@7+gʔaaメX>y뾄m&,j ?ep*1$VLRX]++rlFL&;W^y[?x{؃Fu Cl,B(8LZRBIfeR*9S;_Wd {.߷ec@@j.IG@R:2"ĖB!V<zWF+Y , Y]I%D`" b}(J&[?_<ȐRYz`H:6f<"7!J `exYjvQM͐ 3.Uб>-r:\Go:Ykd?jY~#6*!mH~Lc3WP~z` !!z)!$O{ m-m5E؆:@H݇F / FӠh4(*q]zsnWP];yJ:u͂ݧ{<>0"S)ǤI򬥔<7vD8mu$DŢ֕{ yG򳌱 !)9:KdžM&  pvo%IRB;>WگKcnA5I3/8c38u1,jU м$l]e!qЈ~l \X78`xL_a Jyh]u=Z X,hε+%bQ1-UPe){Tfm?56($$`r0eYNW7?ˢwl(W{mGn-(_{onʹv 7a<]us1Z;/ʪZz` ,iV3Fj̲7mL ;0R0:lbMYzvWl0 ,#Z})exX&ë#c?0Q0=l S2mږn|\?x{Er!DͳclνYCn&d)U/l:A!T,ʐ Q6ëG iQ+]+r,i6l60 ‚eUۻJ)HQ^F(]deu>gX2ly3t;#j q`XZ2}qP-a_ջ9-O94l{~Xbz|kWQ(1: ߸\Q[WW =wSݕ`5V6I2|RqSJ9tju{ȝd6OrrY9P]jU*FQf3oeYVuσ_+?M"8@P_R.[1"cqp]zuČTӴ7.}0HбU/?XEYX Հ/lzP١{W^J(֨vr+P9^ H 4qe>ci-ܽq*(QD%LaZ7V+˲<ϫT*eīj峲!brrrwÿq8fTh}nWE4GnF1ey^JMIv.7߿P`Yeò, v&N:uة[͟>WR,hIݲ@FՊGM<_~3Dj .iyb KzJSq,`Xg VCZ_^ d2Xo k*L`ZSOJV9dG߿eN8(i?>nQ3_0CğC0E 5[1f,qAIʩ0EM5onyc Wo߾~.O.,[dd=S̲r,V};͘[hz~;W <~5(,9xN;l5r*8`u=0ON: ɲ,%dv7J33&>oyvYJ}Dc[*Y?°,vNMHl=ulkW9zGQLIؿ.=Zճ_؆fX.ݷ (c]|*8x"+]QC0#NbCMifYe<(BLIعvPN) GR7ԩSZըX hyVt*N d1<1g3w:EѪ8ը8p#j5q]']0+ωW-;y'=peld9S)SW}օ+[G X)$EmKL8NQ @`aٮ<ǎ]1H&58;rX,yIO@9έVQ]zho_r4f81cӠWQ<{e{nvA(3& WrbIeXq[M|܈ (>!zivrnjqq'O_{vW3(˔mI޻@y,ObEKZ.n2/ _ң#,U(_ɲF)88T0!wܱZ\^Aebܾ}ۑIr2$ѱc=zڣG놿mѩw)PW|t5 kժ׸kuwEށ {nH1]תc:rlvs =&jX~z]ժ?0ڱa-=/\߃8%<NLx1y P( >>>z>33Sy%XJD1͒$ ^<@UD?,L.5TR@Uh'M s{V5,o7IR XKۭpJ) V8-(xNg{r5b&myr9n(`=, ÈWv)_ (vl` Ǚ/`֒2T*%5K1}*4 vRvJ +~ l4ۛqRjM7[Vtp"lЮ7\UwԪeh#D1jNR{iyB-sgɭys W85[dpR{׽_MRXuŒ+kAQѢCɵ=&ߜ8ps軣mb̗=shN2Eyxyw?VC2y{bltb^u-oRϗ6jxEi9.-´ѓ L\}wu&N h9că5gQn\,9_4gOάs {i6` 4yeKœ'O?~QFU{/{36 wJ\+SRT}Ұta1s/n ڪv% N7pj=G&3Xic[E]59"rjJR'EgEt~nysr3q)}^+WŃdff>}&M*^x]VǏ4hP^=i7F\3g>Yv.p~mm聛? ')cدAۿP@N?6txٕ5GNV.bg|#1oݺղeKW^<_ڵ{.R9[`6O8_+6 whd% Yn62"8R5o6^sӛip O2˵.}&{dxbxww#<0]D/S=z3 fc`9;(q W`+'oy"8R]g eWɸ ?2xbcc{eU Y+ ٹ)LdY޴iFŋx#,˩}Iʗ/欄;wDEEi3F(]4lIfcւ l^ExN^ݑ;7/UFzCe6sGֹ/Z$N!3 =/ٗeY///vĿ?ׅ)XdtszU&{`=#7{\q]a*(`ӳiӦG4iv$&&۷5jhڴsuѻBV #w`. Ak$WbS]Zo.hަ}˖ڿ,KA/.\PJJ绗N}*u2Ӑ;(j=;yˍ+c@is/a'Id̬{N^Ex[Uѿ nQBORp Ai_6)tՈ&N:p0ˁN^#_7+hw'_#6e6 Yٯy<Çq\ҥ[n]t .| %J V+z}ȝ<ɺYGB9) yM^pá&d ? W\)S8ş"?R>p!`HmOm/x(S$A)Abq.>{e{hٲ6 Ȳo=#;En:_~0yP j:獼6?"{zy"ocu!XDb Lrctc;g.ݹIKfUȧ|nbeŋTV-==]uqqqsssqqQf eW4OeҠVݗɑ|tj>DaZjvڋ/v>/Bs(I8A ܨrDxR=;\>{'"{/B^,{w0---w.>㷎9PҜ'?K-x"?}扠<1RiY8m 7Ex{fk@BOþT08WƘMjlOj^|`(_x{c~`眆'* ̭օQ1cB"pl{/| [IT< zN/َ*;7m̺,+%%Ն돆۷oݻwϟ?~E(a}:P{<`\r%ϔ6G>9|{NmxF(;ޯhڏk>l]jZ²c L:?Ic:d,h2\;g !ytE!wM}}塤³&OّztSb}nKA Jɀ.+w]1;].MxzA Jc+_Ip̥T֮+U&ܛ;a0Lr pb-B5݋/ӜVdE(돗eÂ_*YPJFJxj MqNĹ Y}{76 6$B3t%ⵟV`/+pmYv,̋J,JushoblW~sXx77.zB.M9c%n\AsfR9.EItܩS@)?]q[rj׮!DZ_ z2q )RT'EQTK.B*P^cxE|FUrk>TU5\f}jA΂73 8/w+8_C޵ڀ[X롛yY|)LZe[P CBa[- YwG{D"@RG2mm Y$HN%wD9(^:J˗Av$)!!+V5j=ydǎ-[WL^ӑ;N jϹe;TiEE[QآXc/M(Q7{y-(!vbPi˶[wwY|9s̝9{uX7w\%l\\p^ if󗐻{|cNm( ^Ý:65;䮓knUBRHtvwU]֌;z AыiWkgy! BQ(T0^YD K2S3s)iROq>P;`˥p?:dHmtoo޽{7o|ɒ%(յKkd!{ɍwᦄ50ial;9d97 _NS 7EQᢻ.[^Nh*D__EFz} 9xw]L/x ;͵\+XW9tzj|zy3ȇwx+q9b4O{ R Ad-qxIucmZ&=Jz j<;qr_cB|ˋUƋf7Nd C#1o ]g7 /Dk8ΏI3u 2EQ|iBBBƍ}|}M&N[xj$ ?1LZӁhS<ϟ8q@ddVw`]p]D%h(BX(aX8؟6/ٿ#xw"B| _cw#s}`=:"iˈGqsu z7ajrux^u7Ϋ/> >'hNح]O)!-%Fі2uǐ{_$_ Zt~J9&-4LΝ (QqF戢(fQCBB.\ФI',~Mg&m!o_-Z[Y#w_4%x#>}>eA- G^=On0v%%!cTOq90LЩKMM=ydZZZ^(j5Q{Xۘ .Pt0 >(AjdX:'EO(#[̜w햫D8 1AAV*G۽R5-Ck|Pp2Y!:MR13=ܔܳN[yz|]^P9HDQ$ʍ}y8,w_Ixd/^)J^/ES`TT%J}vFFFP$D̘Qqڴ6KI,t(9 nDfȾ ƔX <ӦD Ձ'f %|<<=5uS:?oPfh^ gΜ9yd֭2<ƍ5jp2Mvx+-`k٨Żrc|:HvKZQKJs6k˹uNBoIr@\/t.鯆G쪮h$vnɉaޯEQT-n_Mkr+V Ǐa])BVVVd Hu_qA(垞Z6++3}nv]'A;Q5eHQBm,ReDbppp06.8e%SgcDq"*'Aᗊy>---$$X <ݻy!QUt?yW),O,\gQ,="a߾+όn橿t5V܋\zfi SgT'S~{.>ު~S|3(5,M= Qqw1h]%`0~w2 xML"!#/QyT) @^}r.E6sqA5*xoK(h4)pu?i /Š~-ߠ3A6e;W\{q/Sa%0jn~ߊj&L^]߿C2S)ug^r.woG >xNyFw~Wمv8o&o5,s iUO6wwu2a]bSNT*YeeYeɥga^^^:a;X`NT?rNWoݑ86\q;gc$.vIbG-+a#yZ-Mrg699y^b\ "0'g3qvД}Vћ'y{cҪveoԎ\~jQ*oS2=x{ͯKtӥ4z~ԍ=6.8x|tφc#kGŦO=JR>=ĥmTRykG鐯1QSG9jsbjzhWQCDDĀIT^[[ HsA߾}c6_mAddiq\o(%Өj/yyãSuCTTtԜT켚I"n},f&R$}DŜi7<]ˢfW-̤[R b1tn׺.3ΆGĜhaaD#K=y`71ȋFN4rh`Lx L>E*atS6&od?L{?"S \GEgG7<::o_)x9.RqT q}޳/x{~mȘ깫~DI*!yIT+:+]qD4(Xz>lhDŠ0 miFEzfS}f+;p殭;M+3 CZF~|3Umyj^/ѥ.>̺u\ }EJY8. طΈ_<=OT2 q{xxxx3BȎaΘ8 Yu>YG!dB;19k 0ֻ<%ltBlwSfsF935rxm=DQf([MJ)sН uy yui3!63'd;3Uwn~YBݳjAx^7vn,3z=[c֑,rГ{tbq9roPunu[*o~.qD&]U^Z 4^suOmܸ K~QP) =e);Q4W&o5]g !f9`F`c|V}q֝6Fx+Vԓ0 3?ϛY}޺jd%|xBw=q&? 9 繗9_ȋ@̉;68oGB4yA2 @9q4w'v.`clpAYl߯h=xDOɫ\ztAl'd(!r$ܙFvi*BXʩRv,l cۿh?1wYRuw<`n¯8ˀ]tc6o=نhy~w{~c_+>=;hʭDtK"6Dl\=ymPD/~"]b^F[`m/ל\O EbXиeآu_ͪWfd%o?}Kk>UZvD3gR9퐔o=bbͫl3om3xFۑ瞧 +ی۩˹+7oJgsY7XBQ]k]N~ܩ>BH+z̖>^Qy{{zǏ ! "99Zj :iCjK#@ pw֭u\| CgtՙlA̠f@:W{.zxV &t9N T*UB;djr\h&*R Q(NW8H333=<vNj02ҕ{'TjMy;?fe_!;yfzKϋ_m"QbmQy4ztN_s 9A'YG @x =jPiS'zr@0 uzS_6Q  .}CU>y''o샔ߞ\޾\+^}A( l}!۟=qfrKxU.o:m;/XJs<'X-{ixTwD`yӎw .X46`y, _vXpM!PBډTb:) '󴏍.d"@`s4_ny6xcZiή?/AH>w&MFcY#'8D!"!A5Im kLQ(D^,dY`p;}N VءYOczy (|4|A 7Ơ k(8i"Kv{,& G"<+0GmO^v}1~}^dJKݺ<- DYaoaϮ3kcfgtÏ _oIF]bF|xӄP Dјr;8UXxMbcc I"WIׯ_7LjZI,* |ԯ# 322j}-IYK&}{ fDZ[oy3C=ٚO¢R%usk("//䚤)^%maD3כ ! _=dMg&e2Ѡ:,0"!r@LrEQ$Dxx8V~AD8Wt'V͈@I j`4 hl4v$4z-=Z5N&h11y΄n~tЩvF@hzlIO]YR!|^43[:gv08aMW?8dَ׷Mx̶G'G^8כ&rtr4 MDdЛev$;!yק f&MT6ãhR.6oqNKvh9_@|i0`A헷s>nƻ=kڕzAҏ3T>F),>B( DȯW"2DNtL/9=wYzf^opî ]Ϝ1P4\nZ)Ƶ=cPoq;=5P7JX737۠chQ;ӂFӮ,`/9 /=c>n^z`{,f6 ^ ј}a8p^V;5+xWcǼjOv{ܩ;S?|v*`Aw:^lz,6T/|VŤM#Fv5]ky'֎ )@Rdڞ_'ӌŠ|lʽ?YJIϰ,xq640HVo_4ሢ{{VX: k4GdkЯTgF#@;u })*=^d'!@ !śEQ|XKEI-E$_To!~.[r#,yd2/Z|ϻٽyeB.{\?gDzz ?. 4 -f2&hVnz)jx:FM[FhzqgTֹ<^?[ȝ&ʯpb7̜<;$brf՘йfٟ:5vÛ+;^2K;frtz-xy2O_]߁R=riԲ3~͢@~]00b3X^1Q@Ԅ-[z6.Ϗ_}2u?k8*xNHy! (-V㖾EQVigN:uoB4EQe[paN(e9.LkCln&ЛeJ\=wEdZm@Gri(=*!N?߀X.=r@ZՊ9e bŊIGbd*ʕ+}>̽lɓ'ӧO\MW3Gۯ\YwJde?mMq iAHs d2LZayYd։`D3( H3"4mmu3)իIIIW\r`wG3E8aYEi/l?$ z iF 4085jٹZ"`}9ċeK6Y%)פ/^%,"x7J[a@@:5z,B,Uq Ckn_η\&]6D2SUml8tC 7!}tnDzՓ#ogOBl' v Y@x}&VUY*W'Zr4jI/mjY% {՗nZET/fjuWu0$XLVdҜx5hp,_9"Kx~p,Zi࡛YV*Y=" 3}ݭqIyo/9*ߌYeJkf7@K+6S?lـ-BzmHR?xN(~>2xVHm kT– " PSNQE?Ԓ(ЦHzRMj<eXnk)p>ub7( aB9mZnZ ֧A\[-g[Sy "q<0,ײ7-91@vQߍ/BClN"x;ju]?jI=VOB+LB%C)X"E(x`VE"@ (yuxW}O?Uzb4ԫ^P p,yV Ɋ[U@@@Ȩl2j /5Kg@cEV/9%J ^J9N1a}n΂8ڈVy[*b>Ku\6matӋRn3Tiss&:qS#J@$ #~Nty_Wtyڢ(*JLP(ɝ ]Run(EQ4Mۏ fl;"OI@^6_b hJnPU_rgπA7=o~nMuDziojUS[Ohxk}W]vTX˯ \  e9i(LM86sɃi\_Ei LFӴMȁ 4?Ӵ;  ~ 4Eh[;ϪW3!Ӈ# 9/YiJVFvDQ =9iD(TNY-iB(}zζ$"KoPm f;]YH.6*A0e4( Pf^6m ` hVPׂ G0Q%h^k9C{F ) ._34o=y$-_w[BӴ}T>gfK׬&A9?MQh޵&U5h4AAA:ZjjZT+!"x-Dmk?ah߷$/$$ܻwzi)޽+S0d/s/iΧ0\mIi{N{O>}_h_n3%mZO}x>jW4X`LM}t~ҽ@ v'HŎôꪒ׼{]r`…?քaЧ{fg m) >}*`̦&䀼ŧ_awO($Ԗ1q>anUˎQe CoӸ̀٬+ϝZXoP4M3"}Np 4M F4`Lu~8 :D ?foKV%@ֺ'b [=?Fwʽj^mK?l!Кf!ɿHu! ~L UڟS6[_ٳo+KHE;e$2͉ wuUԫۿU3jr<[yq a{To;N^xzSa{HTIv${?I009&'pDagVc.nzVkjf !fFEլP! )%K+d4e,a/|Ji? R!( '(ghVcJsag9fSCn-/Ok0NV;,ˊRӒ_6 ,up{׿%ljϪTԸW}(M{fCJ˞޹i Ҧ5FNW5{[w:H/Ok0мlToeb wjͺ!MUWw tR3 MD.LsM=`j@Ɓ ‚K4=Cܟ#4WlКb{mz`'-Exr[afgן>*[m8U[!%1$odN4Ū* @UÉV-LL.8MkH޵'9QXU€8}ʝb}&>fFX_-C( ]/0q)GM"ңs0EIuzyyIKA7F߶ N0,\l(O:V˜<Κ8.!!!>>f͚yɑd[A*0-=WE{ڍ@waHX\À{R[ck/J8="hڞC|6Գ?w5ӆ.=?CYMnOԟn^ĽEр%.K+y P*TG]:Sc>ڎ=]y/dظL4%xOڃ~BO6V!j7i{=|gS ò,|xna v" IDATqm#xv4[LLd6c YUmb0gBR qzX6iSmŴ=ׇzi7Ͱ+FJV8x/߷w[g4)@W"˲,K8Cn oeY|ۄ{ʱ,@1 R os.f*,<= 02(dɣ? >`% lgvzz,h$Yg| eeSϭ8fU09:דd)xسFyҬcET{y(Y |u%\K:"!ahu*{|䣾E8D+Xxw]T, sbL޿3N*ֳ)aFd?X;0 BxaTX ߱x`i ZsRÅeڏݺ0zgSUmܽz&xϕ 1_4;gÝ$ V=eY!F&H`YB> P(롒RS2)dWmƛ.S;4&fXVfgMgw{zvqr |U6mNy|r,!\!?m~*ڞ_H籝jLay=E2+H{mX&sLT^l/}U櫥z[ȭߝ+ͭ .@._as`[ãG6m ʕ+o[8t +wؑOrFVTiݔŋʕ+I >tR3F#ad&t]jfx]*4㽨K#ܴ\ hj&jV!jEW} jӇ|"1 VR>^>>jg~tN OO TCann955ejzBU0yݐi0r-S~A"31 )=jBp4#hwf#> ;-AiQ$PU4M[B`Fw"Dyg?/ܝjVp4*3_dBUKVt";.$Khj6[955x!35#Co ނ1R238n>5v&RI&O915h4jyŇ?g0pPh}쟳A3-SBw[N[J *>m:to߾tg2ӾG-\,ªUּEofCEQȸp­[222qss+SLj|4>&5yM4k,71̞bŊ}YJJJ^3{EH[䶒7Ld2\* EqhqJW@BVӗ֖8JȯTh,%;ihT4E@I.@F]RA(Q RA M?OӗtpŻ+ޭEtVKzoc 'G,vJ׼\)Ri>[;B'zZI%9ΝP E/UM  f#DPx JKT" خvϚC .Q%<|d<'&&>}z!*Vѽ{ݼy%KkY'H!}4K3U6kݯz<h8Z,!!|I(濕.N 0`vZRށuk o \5T~ЪEP$KQx Ҕ =sLƍ}|}M&!D:yJe|}}q/xn1dgFuY7N&s{eyFFwrhie;W`%"WAtӦM׭[w> 00 E ^1sl#9NI}Ǔk{isŻ .}V E{%x i4ϟ?PD Fm˄(fY/jZ؋KuFYw?*X-ul6tHݯw6l6/X ===00O?}+:<.a\΅7rwZ\/D8.jZD%J@QRLNN6L4M,+d2Bҳ4qܭ[HHL~f!Yb<z>~>4st;D{ZJB;GJ۞O\p?9X/2h܊46PdFA?-[ dΜt~5kO p9 o.s r.BywǶ J 4iM0 !  ?RƧ$Ιܿ|CO~d@M:: z^0_O.]ee<_=e>O99L ]z*_Tws~{~b@ն.}ۮl泝#o)?UL Wt9.=5M^Tm:ފI<\.;뤵<@E\^R;w̘1cҤIʕQ'O̜9Xb*UuvY:WE[J7xOuFZmUaOjw9: wE3hت9FVijV] =]0rJV/M{#M5"(`- ej}|.ٿE0JGs2|yw Ms69veqc+8tՠ k6IT=\^6£LNne4ED@kڿome]8~Ȓ kk4a+%D oտoy [zQ]kAA1m`y sxkbޒ üKsG6د{G&ቋQ.o{.r\-;oIEV02]{Ũ EdqЫV_ 87nHKKAQ3fOXb׷ r*ZrwT*ww9m}ReHUrU̹w}sBQEQYz ꩡw[~{nRrLJ!'`{ hJ$ָZ R*e.+* HVWh u-nUlBui(Z*Rb% *-@ a@B@.x~_ę33g&<99gR=@ jqEjAhHMMM}IEMYEMQ_\}Qվ]W9n,KuO_ٔ:gzʕ9dwH6;DO&/#HN˶s,&QZrRSNb):+4ml;W}+f*lݣ\tڵ6nٹ$I>3VYle|HiwFa{UUUB?~|ii'|` lٲd񾾾/f_M|zX,KSą;^ޏ!cÇbݹ^>w֭>H˻occ׻?2eJŲĪϾU3gQ ^ֱ 8hvv^yo;{&aeJi "'w c}mF-DMۊ=WQ1y%x?ׇ"k!5!g'l;OX,`sƊGuja7/y~@@Œe,?zއv|hW'yIcT;bٱLkWt>,PW2٣ǃ?JJާf1C; iW/'/XiL /)QFxP֧!bVx?WFژ%GSo.v<}zX<ĬH>0#yt闕(lnM>ϐ !umDҎ_>_s?^G6M>M`o][BN۷3 Wjx#y96}ݦ-\*NT}؛\CBiq;df;}W-(({B=I)]pK|]()ygצ)J߿Q{U.'MS=ai˟ms\R3vNe)_`\2%@55vMh͎CCJ`#犁?뉗ߍxwک+.i+ ʫ]H?9v?ؿ~f nvR[{9Jy碝ߧLVV>u:ZrrxOb_;>[ E8c 2%3}g䈨ϕ@oHRW~5M&)Mֵ&˕OLqb25e~|}Nl[o9g4fѷa{BHٚx]mLˈ&puRe'9 M Bn:|>Yڵݐ\;) p; ö/ xqj0c[Tkx<{0z%a Jׇͅ oG2Ra3 ɫ+B1·M.C|@߂:O0ݻw8:R&>Ѥfu}lJ4}?g<޶cogëd;Y9kju ౷\TwˀEJb` u/QWT[g,<,o[H!CjxqF5`1iŞc&[eYE>B~_g6lkB^ٚ5lcbAA{Հ=žزf٦[@Z<^(#tκY0x 4??mM6'GNnשn{6-TҢ_K?I*P .^>q C!&fֿܒž}p!|{Z L4Y)[Wךbȧs~x;f~2L-SW|[o!`M6/IDATEL B;dvL:w4Tӛ5l1ou\Y\}b  c0`5L JgM0x";WL[xUVΉ  Hxn3#j/ t]FyJ >iVngI'y%(xH;#BԘ O1b֪|@߂QKҦmbSnLB!B!0 RAשV:B!`Օ[PLB!B >B!BU0_P(:*B!BZh|۷+++A]]uѭvB!gՍ|@߂L~ P#B!B!a)B!b%_:dB!B 4[|PW t%BHYu+ 3T*RB!B!-`t]w-~I!B!yu;dB!Blje:n[,!Mh]cSӗNsܱMI^3wRl=[Geҳ]ht4)cGbBrZZ\ͽkv'g]/i4Ҭc?HWxRds⤷>,$)aEv8v[?5B<$=[t_gzH\l$;"W+HZR/mԨe=ҴhY tvvrrrr"'''''NB{m9;t 4Rv"ƒ#ݴSrFpE.bTvקi/K݈u h=gF 92-!9@gg g{j;s9 B}9X:Htq=&$G鏤+u0G7Ec.{0 .է{nϯQd'n8S/x=j9]&Js {|R ^HG BZ-MẓޣG^\Q`O\%TL HAک6ڄZScJ* N`+gvs ]+R* {.чJ.QivY&0 8?no_zmҍ-OJԨL0MG8~={ѣ|(t|[˯5y1Nr3!< 7 Aq]xم2$MK^@uDrO$J]۠4*{֝9_ E91++E>fh$F-N5ns? 5.rh>TrpGX=-G^+cI]&~U{P>I[oإRIwp! +x$@ 卟+zs8 ӹߦ⠠uvMr^\ƴR*ٓSPk_&11%%al۲ONzOY $/3g^XѻI\=V%70[@Up?hm2cJܵy5B&H97d :]cqտIre7cwm]gگJsFl훓8bs˚45\*|$y#) CΐUSw~!Ġ?7ߞ/.l|at'9}_DG{Auiv}nʽR iexduFzG kkYc/lЦwƸ9N\ ln$J20)`#s+m!3y!.z1 {tLCƋ-y6r`qܹZT72`O7g7Om:'NWA~?}LɸjhG.;6yH@ܵ{zkjn7^٭.\;hCX;)EpO_,^w >5^&el6&E<[LEąk ]YyH҄ۤUWPT,t*:4G ^κ/݉EXL" f\ζ9xPo:mNdzSTuwP'KU9xMǶ {xsHj鬩j}!h0P=CqՖ2r_91+^eE2 'lF~!wĢ2W@U vFHwЂX?6I4-qu.PVѬc2K',;};fdKz/k Evh:1fȇұ}i&G\ĺ7dR 5K ts%@ђ暚@aH-;jk5t 4 bHOj5 `'~)l^B!w%a~ώC8iT2iPH\:!XT^L))៾Ecڻ9[&Gvx#J{>sگRۅ߸c;/9.jٺqS <08u۳ffnMz3F}cȖcte GO'&40Y+fۛάu[mq¿+BgOxot7Ж`iJH'-{ |ұXΝ:Rʫ4lQ_Jfh%eJ\aasҜ=J6Rd\64J![JRPB:X3OgR^}1=4*T$/G-i @+* + gkr%ʉ hTrY_w[Lk6NvR^fC!(ʔmMJ-}< s+˫؆F g_1r%`hZ;'oLsivopJ没5u?7뢩M?~ԨQS>}ZҰhBaeZ^ ;K`<M!g?9עSǤeQ[7leIG `s"'gY6BZ48N-m2vdˇ51!  R(<>SrfkI΁Ӳv8[Bgbep%&MQYB+n/+w/{T#fƃUT~ ntrג9?kA{>=_Qn)S}!j ߑͬY.BM72IENDB`workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Chart_View/Thumbs.db000077500000000000000000000130001255417355300253250ustar00rootroot00000000000000ࡱ>  Root EntrylZW@ 256_6f2da5d33f4d1d33*   !"#$%&'()*+,-./01234 &֋~JFIFC  !"$"$C" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?o^ɦuBBOJ̰=70\]x̳l2s#YNAe95K.5ee.~osPӣ 9f pY&{q3׽Sk%gX>a4dҤ`:;v8v(_ ^3CaXdq1쑌_|yl_^xGc=>ݢ/)`1>~\ 'bI.]E}_&2pG;f}/oW6:{Li`6F;cYlIIq@לU[.:On/oq1#79U8˞:7.اvdc)zi/U9ma 9W1H (BnXv\dazfS5-FpI Ү/ X=v HU<شmm 8_'<F9(GZ-^x#U`2)%?!RTcAިiϪ^3dd"zN>c&.xWu9es5:_&k?+[p=3OI-ϣ$1*Q6#p֗^nE=X%j2ƈNt :vǽUԮ b(n 30H8z]K>sts{U=2m3=Ǖn=1qY[a1_wHx߿W׬(`hq׮3Q\u647Wq>wp7]qWM緺R!8&Jz`~?7^̏ǂeq@GXZn˨kuids 0yc,me*P3nN#2ۻ63H_Y:ծڈtj<㜁܎{ `XdAM~AQ@ʮѹ*RN8zK}>dnT?w|Zy{4qoh$$8i])+\aqxp[Zy>Vѳ0%vrXc=ZŤX$bGH a2D;-=qc=JZX twӊ4y !0uln ~kVklքHVb_oggn$ A $c% Clipping Clipping
Clipping contains controls for clipping the view in the X, Y, and Z planes for Surface, Volume, and Features structures in
Montage, Volume, All, and Surface Views.
  • Checkbox toggles turn the clipping off/on in each plane and for Surface, Volume, or Features structures.
  • The Setup button opens the Clipping Planes settings box that contains options for panning, rotating, and setting the thickness for the clipping box. The Show Clipping Box Outline toggle is useful for seeing exactly what will be clipped when clipping planes are turned on.


workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Clipping/Clipping_Planes.png000066400000000000000000000716241255417355300270600ustar00rootroot00000000000000PNG  IHDRMJ7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:p(`Q3g9sޜy9s>75~x<OBӴT*% ?r!ΑH$nfBy ,kZjj~>xJib +G#:[ P8,˚L:x Ri]qu%*O[9 rSFFzupup COfໟvjAWLq6nR)J8nT*OR 276L &iذaRT"g cOoA4,\\]Y`S]9cnJ%'<{vK}ֻj-;{z9s1 Vs'(:եlK.UTTh4`0TUU]RQQAtPP\.wy.LAC.ߍi6|s.ZL3w.[b'o۶/>m9?=Eߟ~|b緿g:cGk-me qGa=ǧ7|u=ny\kV]uϟFȥc$EUvyUٹۭVo\wܡn_*((OTm(hZ# eUUˉRݚ πtuKzf's~Zϻqܿ&t o<7A&xUQS&x^NJ Rfkjj|}}DIbYb`YByyy/^x:_"o=S%)Ps񬅩HR(Rx:?o^Ϛ?SЭ`m!wrS]J= pq,-._5wfm Ԫ߮|nM%[ M,0,5ZASLC=ug2M.R4٥g]~~~]vlE4-0^^^._1_2/W.J CvDy,6ĕ 7ىAKߣZcTY 0tlWͬe/IlmtTSB%|ILg4bPGFWj`hlleyZݹ___oZ5˫]^ u!!RZ*e % Hi^Fsvp)e1v3y;9%{[ݿ TĬ'o1_s?JxKM=w4Eg8~羁PkklYM 9@;s<ك^YYu;Nw5\fm W=[r8@SE/^}wZZ_S8N>ާ7OUn}bTpgmj/sd:?P]oc5A{*tT\e/44^aȈs9y^PFLRĭw#A BjzHjI,hBzCil62jy|wi{8'P8|-&`Ë 7g^[[NP:}pޜeEYY?<Q},}ݾ}xbuO*RM,Dz8.l֫wdܢ`/<u)ꍛ8hl e?+++zO/_ͯIFYg}>yoooɕ+|IaZR(RkORv; @*FJSBR%#O?<ϋNjJOvd4ELsDaFTr!qVl6;P:+H9Ci@>W:uȬ%KlG-=oL '|3>TKuZnQu3L5:(1J㧲^mYBJo7jjɹ(BSlMn:pMR{r͵kQB'Ps6BWjjjz U66@O" Y?A4@ 'f,П8+..>}tmmH ˒St!0sVer2H}r>q\nnf={F%a$4MQPkA/^dsjÎ*$-@ֆ}'OZօ jZZE[@[@Yx6 Yhe(@Y(ZhLm@ 4sC4T/(,kllYQAQF\@}7+݀㸓'O~,Dz,kgYβfv{U"Yx7X8_3Zs^2Z8YL,|Eu6Ŕ> qI;4kֆrǛ””]v4!fٿwzNkJnZn5r~_LLdɍ-v(@.Ajz`ehY.Y.4څKW/|F)̬`aal|e%5KYQ99QesjV*EW[Y"n̆n5ww/7_qC7f–8k5s,˚N|ck1CtV,hoe++++XYXXlkÕK_+'XY VNr]dJecc% 5kڢd#c5?KGz)NzZH!VӮ.Y554]x &FTs589X;61j7xEz`9[8 6^,lph?Çloc'8 vVh,O1reYGh֬w酿@O3L$>8LZVF&f6-&Jj# Y`2kKgtkc[9 ZKR]JlN_j MUB1QhZZ/ܑ F6/ӻR]JdNnoblJVU6kw,q,Yk@.12!KlvuAF6>Ҕ,TjNuH$&fquT_ށls㭼`MVeMCB_Y4#&ߞHݚ tނL76(0tjryEEGvO:_7q^5s|MgF _c|/b8FDE +s+R >Wz+-4`G3#/[xWk$)^b3kD|]ԇߜ$Lc0dx@OG3-{'91[7@ƒ~Yq9^4!P Yj.z~J 3 ,ws"R0+@ܚWݔ7gGG c]ޫcoDž|a 8ѝ(D"@q@T8fJpBMJ*M%:nE{b<.^'6@I,SBOf$}Ws1O ++7جxj(e@؈<lYQDxX P"?yN>SZgo6 lZɑU2"b=9` Do{?ŪYPtһQMh8cf ܨ+Ŧe.Aj~Ajș?DꡉqE5!6}AOo>6Ji^hӔмAE 8-P-e$22"%Z29 LNˏ (GA)~7k6_k_U2s%b6NR\ʵ̹9"b5;կY.9wIL{ml,uWТUMO PSact`tW֚EӴSh>ڦȯdhV0TJ2Z!RBJ+RJ&%9C,BfO36 S ㆫBIodMjI@P7߽2\qlW~ӂ%FF~{ --9Ѫ%Q;4 eB} ecKHޑWi.+-h.aǽP $m,*+/ܻ!4&1S-\b.z'X}>0`ώ>_;w/B\4-%R$m:݄D"K% )IRF!cdR\Jnٛ 0 ڹfB&Uʤ QhL"JdJJ4Зh=W7:] bi\E9CLdh2%dЖL _Ykl%\6f%9Eс lleHǖƼa~`dj@&eL\eU iNfm͛5udJLMǽ$veZWv9Qd >}%'r(`hT ˕RyӚ(qGQR*Q$iǢq^ܸl瑥/ C`Y^9#5<#͚ &FӥI  ¨4ֲb1,ldw0YFч\`1,Biida]t P[[{ɑ#Gik=Gӱo.K$Us 5IQVe EEGyq${/q`yVVIP_G<.rs?eb]4h4rdTyzc-yHj ,)D.Eyxx1B&)JF?Z #kn/x`q82D&긇G էJǕ_`ƃiPhQR rqx^<]S:PiJ6<@4O#HxYfq3bTQٖBg.p`9e$5\@}ʲ+++o+̙3lذ= Ѭ~ v`0\l44MHRES7p3X9rș3gy @yVEe+-=]0.vSIv;pC@ 7fzuӑ𻇭I}C!aD᝭teRoYQAa4QSAq7CO&8Jű68Sw9^4.mǿ:p=s]•YMap78޸LebDǧ,jF ҍ^c^[zݐY` IDAT6+ߪ׀\! _-መS=%n͚+fn.7pWLewVm;?xcbT,R*Zj[yp9VI_K~P1tRLvC,'bC/u+W{@a*ԝVM+E&-LXJS'eou(DyMP戹=SXhTV0a#~Ņ_~QFI[jvh?`ʔe>^3rd lf(O_,hJ5nu]WZ$b5sU]gmlLӏl 'AzBw".)yQ^|dz͡CR;θ?+J+յ_}s6pRe5苜3fur$UT׭ksKȄCюXGEKT=bֿ5SgF/TJdGbS>%2v^]c9RPi(؛;̓qV1dljGk* nD&$&ls[ EVeb#cSV*JԦU{W/Ņ4(ffE)4粔kcjZOɚ{QZs ޢYJYȍ>__k>ͬ35+<_n䥭5K:=H7)6ey&H먮CM 8kS^J` v$量 lG,k1~ًz}~dnjRQ:j;&ϛ]oz?LYcV 1Xqћ"3'_hеWΗf٣&z+a^~*?}S .)*]za2]>GĢZE[ӌy<8WiXS#,[WJ3 <1 *C[G[]Zs ޢ"m͈y55rt+F~Ub!q^ 㶾N]X\5:k7wO1wt%戹BЬY^ OWzK uEz{>*T:J"pw(:SHjmTW͘cd"n AO K\&Y@`7eMLˁC5F"1>nUf VbuYҀ0Vl,l?QԌ&ү3n?zw27s ޠY|~^||?_*(P__\rV+ Vk=Z/;#CWP^m p Wb;ꪚ}>zr0T##|=ztBJYoF@bhBFQyd,+mH*h6g+MʢRh_G89ɵYY[cfrUr 7|譆+2K:G6 6z.R^ɩ @vLpkV7Xw1@8~{)! ((plVX-`"E;/%HJnzzy´64uuÐ9l4 ?'>W?SVP?K-fJH㢇g]PTa6X;g|vd&[ ]FoWG%Ef6IOMډyk[V673|BSȬgbDMb֯OLNh*tysrrz%2u\pcYlS ]h?.%ϭN\YL&Bإxյe&`"ZbE=r~pLh.kQ4Փ0(ʼ1/sqX__VPYmqw`q/`Bs1@q8FD";'O>9u ^^^?xۈ [sQ][صY0VuLE9,>qLPiz< Foe4Jݢ!o{8S4VN$ɀL:o"Kӟ7͚L6 B[{gjE n9?S/@ Z2n8魖E_)!ֺym2@ |ii@;Xa?A4@ 'f,П E DBh@O" Y?A< ܐ?gIik"~nHQۼ"7J]|kҽh^{Y0 ֞pKW*󌤿ӞaS{} kᛐXT&lnJ5fLMl=9Xf* GĭO̻FfY sv6&? 9/e[z]>Mٳ4322j̙3fΜl_W B̳Bh@O" Y?A4@ 'f,П O30oJH$ݐ8/:Sf={F#EQb j:<<<66VTXXq 9S߿2clzH|аe˖~8 |}JJJVZ٦bbZ%DzǏO4IVvCz xر &8n ,] 8G!q ?x Lw-HR\ۭIrT*_SNEGG,kv{cc-[,fHfs[22ݵKf< vzݱX,6ngwbn,˲,Z,.&!eC4qNzo ZveYe9XRe={AήqDz,*Ju҄XXi[ݐ(5+5! d2eY>8(e?#Y\~M:X=nUm5SW,_spBEQE}IwME(yl齋GEk{eO }퐋LE?sy6{1>?yξjkOOyw]r/`<|*~'M>?.``xђ6tӝ~0 w Y齏L&( % w@pnFPo&;e[͒ JG̮#B5k 'h?0]ղ)⟍Lf Ep ?!÷6>v Qu#`_2,11yX[[+ǎ!qLnP(juIIIUUfOVUUURRV֝bwe< JQ>{'8X㸊BT:mڴ5 qG[hV# ho7{$H$~~~ӦM+))9zh8Z:thhh_{'22ݵKf06ŋR]AV6f92 5f񎵘NQ0 BT:*C,C,-˸İ  O DBh@O" Y?A:0oJI~q&_t={FoAjuxxxllÁ22ݵKYfGcf+..~衇ķ [l yZ˗/۷dժUm!!e\B49~I[Ŵh?.vq`xђ6tӝ~0lw Yblaդ{`37QXu(  z3t,2;z2-pY253ڀ% nӀ>}#ؠ W|o?qİ@k4Jߟ7MM@bo M ѯo\6poi(9QRfV0} :W m}Im>1?E~.؛ko 84qihi)J INk^ܥ:2~˴ĭfٌ[@o(e@؈0Ҭ MoMy;RڀAY8klad6->: i$ګˁ j/l۶ĉA;V\I4^Z!q[%]9z0$\WE9eɑCF%dQPL_H 8a^bb&N ژ9Zƶ&xټ1"G:bṚWXIIo^ ;׉r쒔ϼ*N7>-iӳLν`gT8{fd6̽ Ī9oWuOL0 rq ND|EUaָU,`ѐ-7S"H${ I'`$qL%t poueiy=5o!ռ.) &F7qq8* ЬsyQi ظE&(4 * {!f`aF%6E5jYMͲjUfLL)/LC]).ԙ@9rə>{h͇/>2DАW/0(zgLuKFNIRq40w\N3x`<|>,pyYezbwe_4=ɓ''O2Պ 3?Pz\t:cMUv}m?w,X,G3f )Sh4˲nBB #Gǥen-s!qGOY%}Bom|t.%jĨ0Lmmm]]8d2Ybb/8jkku=pk^X=knP(juIIIDATIUUfOVUUURRV[~ ˸-CYnT*GuݻwO8qNǒUTTJiӦuݽ;z2DzAA{!=#D"6mZIIѣGF#qΏjСCCCC;%!e\B4l/^JTyqUf9N0^^^cƌXE1 P(JsM=22ݲK}CП {?AֆPo-`go-kC rJn)7 Y|j<\)/9=S1^r5@4pB^a7~%) Hf]D7FoV^)ZoYN&+hUy\Dz7xCRuI8/:Sf={F j:<<<66y8.\5nVv?[eJץ jlW_OēcSf0ƍ=ztk}l6[qqmCCÖ-[BBBRSSZ˗/۷oӦMV`gy +4 +ˈqyGXr>9~Ijuo70ǎ0av0ҥKApB{=AqDRzZZmv 2 x?r~Y4_2fZN+'F*vb@.;}SNEGG,kv{cc-[,fHfsX@2eGx'q7f\B4łbl>>>v8ݾnveYeYb(]25 d̸3fuR%[.M]2G; {|'EZveYe9XRe={AήqDz,*JLbRYhbwc=5kkBBB ebm_$s11Jxh67q;5L%M)f\kvAAAAA8yN488H,rw+}u\ap 3г{BUYЅ۷4cܲr}-38$e4(7]4(WFيbq[r[~н&:%nK:9[|;M,_y.|3g?_hE^ӝVZɽen=bo;fٖϯv̘qZE濰F|Qu26//,v:< Ԋ1@Ym uq^0vE= [^LZƼ6[1)2&7etlsZ5l֨٫nݙ-jX%`*Xts64uڠS#[1;v݃ގc+'}mc,o?3jcgz9-q4MatzM=xqms.T&SgS.uZ#b*{7^5 >[9Θq~k3h#ݾ w$hy{bt9L;R_ՅYȌJ{cSf$hZmS^FX>~Aqb,X)&&d|ey;boz{U߾ f""(X@--J#O'=<ٱ^O˱W>'/Dd[JH\HI4}GTk/Yǵ׬%Sí~Olۣ_ڽF5;˜YZo_u 3]djddk5?(;Ą%}yQMOlϊ$wd=4\ 8?92yϻVc,.Ub{KN&lرu֭[w:T K=spg^hzȘݐ]5uZN}J(YtƮӪVϒn7%vȐ)bOm[0?*{'l' UWb֓1~,zqa0TU׷MPRYu٠MN R55\hjmySfb9Oij|guvhlN]ȡ3!O'=6GLIѶ|[o]ݕ\aâz'@?k'jl4MMESx& 8_Td?JָG^rۆk<ӆH}long$M/}(xا< 'B$^4̄Q>̴>s0Gה:Cb[,Rs-z)V$56 i'UJV  `c8__P]MI+..^+$ ȫ>;#/9m%O`ΖϗwY&}F֔ήrz =w@FeKCvŕSca~31 5wb ePaq7궮u1= 3++`%#:U(&deX-q>UdA{3,hX ]1NI;#6S#Ї/\ά0'z=tgy[:*쐕 >`櫼hRHRMZ}ncyO wdfZBa9U.?0#!"tpXrNԼ&7qļYxzZ7T;Sc=@tյ ujkk37cSQq˱&TU<|=-Z~w*௳2{4hG%g^FŔ%Wh(1:hΖCץ,XV=|å|gnglf+g8MA1rO{\h؃9PhXrXZwD\ͱIE/m=pҥ[5?QT#Y%o($9 ~D1߽d ^}p)Dm򞛲jw,N2 'N5j_~&LJw9}tf#G >|РAuf zSb_Wg Kܘg~ߺhҡ:L3I:I}glh-_t\~g4{h~#+uNXHxWoY75 q`5 FNh_nWIn/8lR[! tpپ]tLLL󹹹 .XZZZ=>p@G1KtMMMz _^r3$fg4{)Yq鑵ا i2ph vP7c.ek׮O*fggE_cc#k?e`gHpNg$8c*{qf*ng {\.nhh0L@ `2o%k8'~{p(#G64jT"twyϜ5wb|vGR-V TWW8ql6.y{{FDDܼI.~3>!$ftEDBn7o-&d],TQForX 6U!),e(hC$,mC  l6_|=ziӜOe/_~'H 1A(///))9sibz@ͿK.h=zҮ)| 4 \\\T* |$NX2Op;t:_=qĀ$ݍіXh~5k|R 1c׿CSH=% (J 6DhY,AT &|{6+œ5j<~cdi܂Xց^X,*??C0Lfr Ah:|re}C|KA)HJ\wkK7lX8(jȐ!gΜqr+W&+iJ+q }PT{VŒ<^CF"JY ه /qG&̞rX yq02,$$ٳΜ} m꿌doX:kNݢXg ^:`eGuNתhNtZV:bߪ*<; :bt:d7u?eyEWxxv^Nt}읽 zNuZVZUӪhzl(aeZ^ :`Y|@8os;u>:kuuO5[ϼ4 X w[K,{a4J704M3 CsPL@9x.MцFO}aBe=Nkg=泜£g/p23^i|dY=zɷ :CQh4a(AJ4!Fbgf)}QMj7O-`oGS4s$"ӇD"SN; E4}x0'MW'yek3ՔDJJMӦͯ|YOJl^FQEٌ3 2,˲,+HJeS]*)p$}dɯ|6kwջ:_OLHޒXg 0n΁Gi3 `p}#Mi iJJ`F`Fcf~ /ޟf-YEh]1k, M>l6 ۫qh48.>>^R9~&?22@p;h>>>555{0`@PPuWI8ɝ 6dI&H$bڳgy6$# L&Z}ر L6vLJ:9ƺ#GƘDg/Y溺3g\pIEI$__aÆ92$$Vvb,nt:Nw5Vk6,~IÙY]HmW߻dq9ˍ7={5P%~/N7v'v|}}޷mq,pI$1@ Et,CgEShIENDB`workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Clipping/Thumbs.db000077500000000000000000000320001255417355300250400ustar00rootroot00000000000000ࡱ>  Root Entry Ӣ256_5908b4bde6ff1b31*((er(erJFIFC  !"$"$C" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?oxV22m 8YI %B0%A> n\t>|,-<678kT꺪J^c֥*Rz5ĭԐŸ*rN:MZ\X28Ve* a@ /vb$Iq4F2Lexwםƺ~4m qO?O87ZGZTԽʔ%;~?G"&k_Q?jYxoު}#G"&k_PQ?+or|Cm{T}.(N Yb[;X^41H} =aӵ0V 2 [hqOoIfvyKݷ `zz ӵڝ儖mƒnY u#בO@)9h|"uwA~mFV s&%wgFnoGcc~X%u'Ͼ3 ni? ];EI[h$I2y`l*~F7ص\3i?)\F AԥKnm/IGr͜zKxMԮxm7#FAYs9S=jԿ hfѴcp8@RVIm}=LEYy9?3ry4>?U7ZGZE|M>ֿ֠ ?oޣ[_ϵWkj>?U7ZGZE|M>ֿ֠ ?oޣ[_ϵWkj>?U7ZGZE|M>ֿ֠ ?oޠ_~[_ϵWkj~#i7C&knX`["?6<55=\CiVEid 7*W-l VeTڹc8r2OeIe8FlaYsZ2\xc\ }%2\f]tt\pD]pŭ}fZXi|U΄H?)һ콯U%J7 ((((((((]:.feLJfd ItXO^ k [x"yi"R_ !ԃ09eX&5~Paq<@c9鎕6:[J/jRCs[cBm 0q2IG]^Uoty5yXA ̸-if npHQzvEarc%A\6ёn@<ZD> 𵭞oobRKk.#[py' Jyzqpo*q:d"08掿o-A<b\]fd+/$<3 B[9R8Fʲ0Qm؉x#Zr[ivPlxBR[Ř#TmxSDHum` vHMO$':Q:63ޯm[j5SHʫn1i;VUM6jݱG s3O@F~:A ͫ,>qu'HgVUd~Y~Ln;WwjI)5Ȭ Ei`wH[jӖ^37{K-7MzLU$Gn' AۑKĚ-~5Lkci<*N|$\2O^wͮ-Il4R2-PSǽGk2Yick{$|kl 6PV7 ~H!KI"x- lTcvLH85u.d"gD݉ڠgbsϵ9[[sF/>8]5>u[n6dʦ$ .$C0S^sxZf7k#[E(V1(Y-9p k??^`/T?ףH;P?z@_ijGv~~ߟGڇ*v~ij@Gڇߟ_ijGv~~ߟGڇ*v~nS4BC goʀ$((((((HNjMO$qLO|o2FG5\<6GkR3ofu8>^spG=uQ@Q@gk֟Me7/%hb FzX~9ݧ<76{y2 ̋_ _]i{XQ0%O##֭P0QEQEVxL_y#Q)&u#۹g+<3XxWֻg{oUYEDv,ybPuQEQEc^ewzcfDo!?;(lmު>7𮥥GCYE Yxi6)gyG=ȫ~'H->D#yne`Ar~W='}>_OMPư>‘Ш\z[ZݎKմRM-#K#C p6F=v?Ǣ463I7,f8֥ ( (!ا2-S8GJU|Nkh]D3y^w~o?b_ؠ[1U-ḎfA5ܲ1@MBWxZىnSC, `1קZNkx%wE 9\qsPU 5 e9 M[}KZ}2 KVa8cܢTa"5߄DžӭS7لBn<'ٿ;ߓYt֬'dvsZ[LRhfPn]222 u1x>? 6&GF''bg1!| >Nh^`Эn,I@MEs՞fcleG@u=7z26A4_S473s'pn%K03*e $@>Λ=u?OΛ=u?O֧m/Qw%]5&X|ZIdX1"W,<,\$B)$i^'a uXcGoy>5ո[kحv>nj͡iAjE̡mހzPB«dkBB$7>Iyc~=?* ӵ{:ZArno /)mb6[Dkm췿d*0f mA,>U}s7ı 6In.6By{ ` ~8m_kfiy7]],O\RՇ gM ꧃4]j͞jWBX!"đ(D. r$i@S]ex=J4o v 2f#~"cxm2J(UaP5{3\CEkd&}NY (%[f8Z ng{Z,0i`PE)f2lt]2{P7vQ o.Pާ##\__G+q Km201ךM&=2cN!!N71 .v+mQWy,6v̒jܻZf7* U4G$֎_ghheO 1ġpe3_:KH(]D`LH:P|7?so^sxoxI)a路Pm-U|e#[li\kMkM]#I֬!/Cu6T2.l!6YF~̯-ѳXh/yKңQbF[.n6;=S''9'O,Jsdu=6uI{YouQI#oWwVT"ky-彰K(o&<1g 4)n5Iy4:lY%g\@9=iSP1YPf3n$SM7zޥKKvVɮmdl n88X:+o)=AWUcFtӨ$@b鴄jEs=4=4ˬ<+%ɠ݉ٴYH-RYc?7jѡNv€0>M7z>M7zߢ8MW_ϊ&\^\HfUTQI(:vZ},3\C4$sF኷M7zZ{am*?Jh$ЧheS~5]i[%[uÞ3 ttak^M˦XoKq\Qַ-PRuj}ŤFE1G< 0un>OSvoYӮR4o4i-\VLJ(Y/t⊥ <SMMXB_sӚ{FT8tEs.yf`DOn ij[X )]kg^1yb4.uY!eh,~A=s _bwUtEWgo]4v]M! 6QN#!!dŞ-?tnVcuʊ7FO]qKmo'u_OQ]?X~nxΒt٬VC1۬V}cxsa's:܂9ָVlZcJȇxVci 0ztnC_߶8s:tVef<ԭo/TMyiu[{ 2$hcv:OK/\޷u6(a$fUn/=] [XYyOx7j諸h#u蛌eʺwsJQCsk:Q v@S(55֕qLnByP21 N1sޣS4^^b"MfNF194O2}C#kfŤ&1,t߈Q/R#?At-AAa停`k֡^c.6Oeo.0ky}W<]R}YJ"FĖ ''*=1@{s/`ɀ riGHs:9Pcݻg~cz}H-\Hڠn>֟EWKkKh?v(  q?(v ,Uq,fq&u&}Q!0ӈ 1rjiI%qp11߭XSu/ W-feP'+€Ҭ@ I[\BU! 8KGASWΥ"yhUO#qYdU`;R&5wcnEqi.:Z ڵTpGCWV0Ĭqjӥ|-vHh1E0LWEElŻbhshey >`]$[˒\W$qҵ=7I}//c-k6VryO 8?kX?z U^S,j,jDFv=b Su@=|_=SXwoIam w{P( ( ( ( ( ( ( ( ( (2<irй>/:_+.>!ZgºYKDITr 80n]h|o//4y-$ܤo}VUp:@5kkK}J Fi ;2c9B ,%9MŎ7ћ{|b]"g &{٬F",][*[#pwר}VUp>ëѪ߸?;Ƥ^N`1o ƛ(s/GB7-ƑuvM\O&5RGqZpE0ƱƊUF3 ?,Ő>z} ŢDŽ"ҡ3&e.}skt]J$YxtR!HXr*a( (@2Ǔ@<“|RԴ-!༳ҭK$견pFi2O-qvJ\d0F4 4eʒ`>HCF~t}VUpwا/V=bTd7¨ea篢 Custom Orientation Custom Orientation
The Custom button in the Orientation/Slice Plane sections of the Toolbar allows the user to set, save, edit, or access custom orientations for Montage, Volume, All, and Surface Views. 
  • Click Custom and Create and Edit... to open the Custom Orientation settings box:
 
  • Workbench Window sets the Workbench Window to which the orientation transform will be applied. Note: All tabs will have the transform applied.
  • Pan (X, Y) sets the amount of X, Y, Z offset from the center.
  • Rotate (X, Y, Z) sets the rotation about the x-, y-, and z-axes.
  • Oblique Rotate (X, Y, Z) sets the rotation about the oblique x-, y-, and z-axes in Volume or All views-- only active when Oblique is selected in Slice Indices/Coords.
  • Zoom sets zoom factor relative to the default of 1.
  • Copy copies the settings from the Transform fields to the selected (highlighted) Custom Orientation file listed on the right.
  • Load takes the settings from the selected (highlighted) Custom Orientation file listed on the right and applies them to the structures in the selected Window.
  • New.. and Delete... allow one to create or delete Custom Orientations from the list. The listed saved orientations are saved locally and will be available every time Workbench is opened on the same system.
workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Custom_Orientation/Custom_Orientation.png000066400000000000000000001243251255417355300317130ustar00rootroot00000000000000PNG  IHDR`7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:|^^$EQt,ʓnySw<l(n}wuAQmH"G>>>^rrr^v+W4]V-JUI;<= m3I/òĦAD2NՆ5v4ҥsFYƿ ۿ>{]5fsui͜Ctk0ECRnv]:(`*EuލJnX]VV-///FCS zJR5׮](Ĕ{m_k u:w6_z oᆇ^-SZ]>7 AT'}m!?=iN;gos'݊B;5O=ۦNk7;gixI]Wg4;zEv7! ׽uϖ5+nEDEٳ]J* 5E%gn tu{"ҫ_pi___BAQ0,y^I8EnݺW߲?uTJJ2|aͲ9Njv*sNkDՑz-ھSJZ͊j̗!tӿOwոNmxx"5vg۞;oƼOw̝9l~x=?_xyS+"9bw^x}+8j̗5_{'> .DUnqGPBY 0O|l7,(Z0 0T'pKDQ$IjuaSy V,E34-AD%/(Ut9/+Du%Щᆵho(^ YEQ!\Sh}%〘#b[*1u݌_BԇV͛ZĻ6jhkɲ'W9s&`ecG_ztذoEДN$'9u,!*{'__K#RάR(K/9?}N0>< (O^v䒟6wo]N}+?[_iES]ӖVWb'8T"^ݨ9[On8v97h&/i3g> ; PJ)Z4h4(8Jmn6WR%*DQou$I^Tn*7FU)Z2*ZdX@r]9AEӨ4՟ u(6A_rމPA+(Ip JIN}/Iɖo!MzMk':nO̵ac Z닰:8!8};`Fԁ׆cVA&i 2 Wu$ W_tZ̹<ϯ@x׸&aھ 澍5 #k8No%0 Ƚ, ?aa2W vUQ*`,M6DN`qXrމ(EdIEg^2m/ros8C׳n3ِXEKߺdFN5<{COsHXVRq틎e<t pl3P efpU(r-822"hDQ|H43஢DΖk ߗEPa#TYyng2MvC9QdSg\8,An@ ǃ41]AEIV+jT"ÚYBlh/ Wd6MPz`Tӟ jF*XU)Fl[W r!+Wc`&t_$rN`UZ^ W$"D3z~w]1'M eDQͺ팺8WOB D1Gr8S,w9Eեs^W򻤔φ M$Fh(NVS DQT@k7gD fXX#IC4EG?x {R:OmCL|4語"4Q;ֲyٳ(xzRz<ܡq -pkZV z>''j!KSh ;PfI'R^5BA+YJ!ARТ8Y4Juc3F^&@TOw&6@gނ[N=sm_Q) 5ctA]j8rN#)-5֩m܏wLL;'>NA$r) &M'6nwُە߾,&.lp:Z7Nj%At| yc_LdGcrWp3"H |pf3O p r?h5ˣdnnf^ ;ZEvidǬy1@Z8VQEX$=)7Tr%*ud2/JDQTչJRlE$Y֜Zq<*:lYhI@ҵlsO;NrIDT[캃gw{IAjECY/4zgۿ GZ%1e匍^Xb A@no|/~v//x \&?VXMm?xoWn~|7&+k)%vC+>|7Q,bmCDIBx1ͺ`(A֑Ӱe愗 rp#.^6Yyk*ܖ鄅7c~^mG~DP՜+i &*!!(zxx0Wg|3<4j5@y.\DO^^j۷ڜgaYc/ˠt)=_O<5ϯo0Z8460kx"ybEը魥,Zk/)^MQ5Qׯ/saps=4Z-(fjG9V<h4J0: G0tl[k2qX)I920Vks['jbJ$wr7$JP2,KS8xB  }x^9'˚F6Ƨt6z]eky_J# tWtyO3 5i=;i%FqѱE]\]/fiG/߻qh470m-狎7)N>f gB}`%O2ˌuMZ}gFc֥l/J<IA<9ٙ9f{evls%Vy`Wx? {uױ lO^4Rgy0k;Xv%yѸ聜G:XDѕ+,򀄟 Sf'5/"'pOUϫfN!j`x1' ,w^ ]?[.Xڿ<-h\rn>85GL&)ؼZ}pTĢmo_iFct,Zu_u=:i]Qr}\p<[mC(X#T\AB>?oMs;oO8h}Fkʂ4] ǁ9el`͚i-:d?i9˺.O1H`V]&+u3F{?6YhX<0:k6b ꀡswuU{2Iы6nd4vYt~ e=g۾w|;ˢ~qR| EI%oX=+<,ؓex(3ۯlۊC7rL=yC!8d||zZiҲn4im* v .jSd|.%u]|=e>ˣb\udʘL@kҚ`⚃gp*Hpc[r{:_FG`QI]hӋ#OZbq-WѾh5rVDK\$ 8, Fca݈EB(7?+U(P<ADѾ,+!jȜC0>^ :|BMC^=BBbwRfPN,yyV+ -t-ëf\b`m5b]yo,-xyyj{,ؚOk/_tͺú2//G/_~]q:|ꊌ1ftjwu:L{]E8dilYuC޼ݺрqf;z,@ӡ|b<ADDg\=We9.y3#M<5-@3T`BY0C :!X:iW i~tcj?-P-cTKߜs:',\HP9OpO P/]ؚhs߲eƁl|.jM FkkbfoR+,PSff V A#ͭnݺ5k=A<4lSw&v2ߕ2A=G3Ng y;DRا +\ӾsӲ}?4ba.>ojO۷Lչ_d{4b@y@S9[f/+ZE&T]^YO}z A!!#CˠkZތˢw6{2 $TYYYgΜt*+ 2QlnܸoeWqgɶ`պ9xَvg:vVJE19P{y㶛\=f޵_l^|GIW=f0X(!T "I999Vu֕]*nQji& AD $HQfTsdrBurGWT*Fa+AQEQFQSE4NA&'Ќ($.! /WQT% 3|H3#j nիW?wd%Rhk $&& i0K6i'ޕHDI(d,Ш)-V Q3~Wٵ 5۽+SN<RB*Fw?+oAȽW-]an|HP-xx E'{<SӯY\"ȁE"׮Z Iv{6ݸjk/5g *_?|2\_LGc_q(5h٧--c#o5JoD߭pے+w, %FDV|L===y88xS%/9!HvD)5|Lx阵ᇷJͳ IDAT,YOI//dM*odhG;Q wt EtJk -qٯ]RA/are-5\1"CJFԻ$IA-EDA^1ySb􂠡dn$ 9EE'/:x8yC7׍D'/9)HN^xʉH*V}FDr27^P6'"Up BXPxz}`:B׳ X~?5߿[b#ع:<}Y{ffGwfEWb]ӜMM8}huwwrۏ&̗W Swϝل1 rq[FƮ>rn<``%;|~hJߒlp6ajj} ;vr6?>(1 c6N ( ( (rH+nt/:y /JN߻-I~`w % '5+ϒUJzi=¹ $/0Mj[j 7 H}}g?WFc,}l/HgxÕH +e<H: UI4`vdGPf@]]xОUm޵ ̗cNDIXP`H~l;fz'aSŒpdn>W7{O5DIqoG|*a;~ ߘlڒ=mKX֛)(yhFUL:}К;;擩ͦI(0k~Ӿs/KshOB3 O~lĤESo;ztϗ̣H8kK] ޕScӡtɛR붬e5#~ah٬jvJDhnيfN2YV!؅^8{̧+|r+o|7 p*,B]̵& _!G&DŽȳү UɚK=1ۣgƟWjnȚ`Ywa_p0nUFLvMX7~Ϲ^E-д B|܎F=`BWZF{uf M߁ޟ6dg'/6ܑa諽+E˿f,׮: |#O7}GM{AҢ{E5sw=kzX ՀX찻J!<$0  I@NNQ,$I(D`2͂|OSa. I$IAN@ xTw~lW`j EG6^IM:nZ Z R&A:RXfP+pf +g{Է1n_@uuIo36Vݔ@P_W½ C[4)4}Pi/+ңP%ʰJF]J&E>Bwm5"8@n-KG耶rX;̀{ޕTV͉2 $1ވ?p2!Ɓa;0-q.M6cڶs*5_$Z{:jn >C- DE X@|-Ng/M#?֘7 euR.3ѢsLL7c|F>e.]Jt:2y(RP¹,S;4vGbԬ)qg$DoLp!C<Ţͧ\S7c4MQQ߻5,Vд4EIRaD.$IDI 2(IbiVE;=w'Ԟ;[ 漛YJprATKo] Hk~~ăsm9@m#6:lib:`Hw/i1Iǥ.t[;b>r4n7zW68`zݧ,܋6.:xX+d.&yZYےow:CW%_8e~~u)AI'$$g-C
o$`ԤԠ) X%QW<DӴRPPtJLW)U+jVJRЫ?5'gC̉jrK jBTYVUJF` A<$ZGe00R `ۛ&v0,lԳf;PQ=vD3E3*~W:h=W\ݠ@_?uAw2~t(-@5bP;:=c,AQQc7-ڙayv32LJjbxTk3FZEtJ@;3(>~س]&PᄒVum`͌v]?)o:.8q헎ouUe=#ZDT(Qh$ǽ7Df;&fզWeDz,0,ʁKᨐ$I=3#]Px z-(*|lAQ0j%Cޘtj̼Q0Q4 M;R aq EE\;]SeU(Ȕ[V,@k۶XW_ cҥgΜ۷oƍvJuAPܪE_H׎-N>ak .=J'(?::zӦM}(]i__h2D!cz3&+++''{lX*GP` BEQJ2 eX x5(s}l7 ]~xܥ] s`Ą0-ƶn[dAA#ٻptgQRGo% OHB;'| p۩g}ɘQ'|Lo^8wi .V=xUSߍeϕrO܅ $^! נϫ/5FnztgOeYlO9Wniӧ{O w o$_*#! +ܴ JAU8v}q[ {? v)ϵߜ[h_G\x3Uc|Rf_͙3gJ'A<>J9V):'k 5){sOd:?ۻFwpOm/^̸_ xJԕ+W*ĭ0h|'Xҁpƕ[uonXu6Dy>^   ,0i|ڪ>zr|mt}o7gtA/yǢO5kF77=˽ЀT[iɅAQ9/ƭ䤾%C}]Dd\_OmѨ1~s?&pg#CB  )g6[g@Ԝæyl:"| ώ\8^%th@NXx$<Бx V]+ǒAkW8(<|ugB]ђ4d)6+$iè/q+$^! ?Λ |EV6>;tj|sU@$Rv<626 x椃v]BcVo:i,ѭ6H]74KQ8sçOY̮Fc1uG|zIwhvth\M=@50hOOa,ocDYl"KNjxIsKf2nws %ڡuI&v+ M%$)5LyH#IN8aVjӽEqg6O8{_~k޼쑲{E\דG&rl1E 2$N'@߲mo@B8"%\gb#cNZJJ-Bgwg@qH͊ESc!-Jvܨ|$f y1dˑ:`ل˽ƺO?ȍ_yōAsL6~#7o~m@܊?"ҩT*($$/ݵ2i򐖹' l޼yƍ裏zhjJVƮ]oڵk7m$½dl8ONZg{@#{WJ)@=_=׬_iѣ{fI>h<7_O{#<ۨU g 1w%fXN&i[q1o,JmjXYO9`zYԈ}'MY3aA&l4Oe&Ƅ[mײ;Pjyڕk]OM>/ʧT*)Eƒ$|,g(6eCZm۶p*o5ƹf͚SN=qĶm />KY+$-y w;bFF瘉Zkj0m֮gԫ(V}. HO6(o7lHMBGu06vN؈" û Wֻs9@.UH;_~z=E4MMES-@S=eCZI2P@Zi򐖹;(ԨQ#00J+\DyC_65sugk~o>{ܦgڥ*Oޑ=r-Q`4O:Fju8 3/t1I|mE^0d`̢#$aQFlQNY:XHvFY:gځ7dݲ:kFqk_vI<-`iO,Pm$^)=.o@O,7s'eqqqǏ IRFƍO4%;IZO0+W<fFRծ];---$$<~V/ ELDhB4uEXW;T :=I>i%{z3:?ٙpSq:W,Ly&M'ya;/a>h4_LZ78u۾/^W>ZY=!22$,3nsLZRe3)iFqXi֚g֑# G-vw~ag7^n8'{3Ht B>4nݺ~ծ]_)ip8t:^70L!'GH<{F4U 3}^sRشtmwSR|LRKmlM4q得g ,>Iu}Ml Lwo9]MV~ATxBG|[bh Q 0y IDAT$`Y]:2Bсh槊 ( ljoꕐk:Ӱ]"-I~.J>~ޅ=l^)A\+9wo?O+EQ9N777Ύ>}?~5iZ8-K"-S2wF P(ras3X))ꂻ$>sFxVȑ- \>YvС¤=W=?p3C'Nvǔ>#uv;݊g(r[96Q#9=R>7S=4ee.S}ECB7rE.vM fP(t:܉uwJ<LQ)FlYƲ[1"~w9;D^YXJrĉ}Qsss6lxH˔r'G*r\~gkZ\ SrR6&/Rд9N>UM c|niϟo?x= Ư+7}~֎Y%R&ڲ.>qfۻ߶'l_0)KQ(4Mk'֭*cMvv]랺"j }kzȑ#=z$+i+ BShӒ(J5`c0,)18a{פv%&]v,;`JE:B/@ a(eWZ?,+Z=~t{'$ޣǏW|P/`"BS +#&&~Oe2Yxx>ߒ͉:2F͛999gϞ5 nfׄ'B;@Q՟J .,,#G7NXX!2IG*v2L*`0\xqȑDZ&˲:nӦMe%#F(..6 u!!ie£+ζ~us ˲...,r<ϲѣGǣGYe98WWW{eek$ _YVrGr߬qq<W nȦԽxrL攭a]{DzμJ!}M\jN7̔xf^LwϞԴi!۶NY6-(RE̴7o5̛77yjtEsrSbeY7%W-q8EQ5آ,C ZEW28]r1]ϕ7$ZhKMOE`x|ge6o4ɘ_+i{>CS#W#UfRrRbbbRQD= R];Ӊ(=2lŠY. 9r~LyozJ/F0^b@OҁN4>tg+0ǯ>ܫ-:bJ}tB{ Z3H}i#vG& bgSޕEz7FPsRW~ -JOMH[;峗pÉel !<\֖+^}?0e'@>p;O9a<|Lz-ɑ^R𩼼Sk,xukLQ9g̀?g׍hGhJ|欉FjS2F563j MYdϒ (YfKQsc׵ u=wγʁ,,z"~#:/C\{Ȗ(YΦ^A!BQޕy@ s74-.0ocW%m-آ,Cx4tDoH-WT(^3| kG16d 2#9*' jkc+>aDͷ_xxxPSu-hNnDS}`TW֪riğggz)l#*eY4sn`[+:us1fmOSGL9Ӽdi]ŏc3ſQ3(k>ʜKWl(ih(h4E79WXޓJBn|u mäub[eB+Ҥ耭X AKvz#hMbYˣ:#DQMoޱvbR*dJ-Fb`՗:ԩv Phz*_6nP(k>ՙS@Nj`ʉQ/A.Xr $Euu9<6*o_~E~꟥( ԃr}/˜rg& |\hKآ%y Ο?媪*qɺ@=VcE EQMI%RGGݻ͢I_Fb5na\>u`f: P| Z]rbR&;]ߝXS_i̸83̆_8D,,-?>qϨӗ;ح eؐXw"9CU f5kfjc&QybyWFo 4E۷[_c#yh/c HϡN \Ж#EK, A c*,/o/>9̟~}=43Ϻ֒w(JYG|Ww0aAOc'o$Wj8H帅OS_~pJ{cAQ:$8E?SV9IрpwFؤVZ-xZbCuV<3uc\i6ջ;̑~jI̦`{>+/r~H߈UMu_-9t3`Lػq~FT*;=<< ylWN|f9x;!۲^, >_Ρ-nXb!?????J\K'VqUiEYNAr4iu֯_N^"Gף0+ ҮӠQI[ٳƂ-@~F+l8=eS UXBRs; n\Z}C+VJ△UsJ+XEQ,˚L&qfѣ^:f1׏iNDzl yUӳR{ZmkbOCxeEɖe$#vUV۽ _nݮ]VXXn12PN "U*UQQT* ˲w)**RTry#!N-2FO,h4f;;; àbYnq L=oTDzls$Ab`hMu:Znݽ!>Q\jc+b~3`ܒvaq4nҁhN,^F3 bRD"qwwt^' #&u|E10nnnC xbNNNee4M;99՟QI,C,\4ѨT*Ĕ^`!PGݼciKgvꚉ쭲1bkp\M3oKs}谏OVi#M]m#\-7\;%߿̏@TuyMzT(РхaJH$JP(Fc3 Ix ʓdRbлʬ"gh2Lķ7JqD.G͉eek&V8GGGT*W _1={|,Aߥwy??ҥ;Vu,^*a-Y+h5; 7*L};3rѥʜ_:.(_TM4Prbʥc=}c +)j5MܐI,GJRR)"؂+O:!!!]VF"iM*X2-D ,+z*b|EK{/ngjEOmJg-vn Q#1)'vLg'4m-x-SeDBOk~J[::!;ز92٣Z[еʳZle>3EQfiq,&<"B m 0tnnnt bݩ%޹5~{nKy)N(V(*|kEE=qb+v4Md*`HWIya&;P Jpi(FNvJ׾7M;V:^S'r`RiZ߽{rKh2y@ Fu|Ų٥_.'Jbu/ W(8N׋ ߇q!@hSd2wEEFvW>NM>s8yŵdZ 1fE%;C%-x xseEܒ&^wĸ{ݜ;oټ>i͗-S{wlDPTx񢷷7@ O4^ 9rȸq<1eek4iiggףG~O?d>g<57o9{h 4hwg} 2#J;Q&IR`0\x1118q5eSS CT IDATS_#@0f#6md0lgO,C,\4 ĭT*UpppaasN>Ͳlg iS%J]\\zݧO___ggVɑZE: _!t>{k]pw@0ͮ<742E"Tݻt{iZ;* GGGq]p'#WV: _/ȭR=UOީі3nv[mYd}88.u뫯www7Vx8eB-?4pUEª]5m73/cNZɓj$%t\~roZW( a:1 qK(]xܵk9LY{[آ-#HV"E3*wFNܼ=3o6;&|qx.v "Y]7ahIAii 7EԔ7UyQ֎FlNKr7MOΘȰmɪgo>Th?ũYegN>wϞԴ97{yRw5vu9oW}wlh.FQE OO)@y"uTyf3TN؂X@ -q%;R<k/f 0f o _<զTOkA @o'3Iw'\?0P|~1lGaS ^D}ֶ雧xk&-3)w@6O6Mb,5)vIQeOFN1n̞8OtPzle؄>;8 e'򗏨aZ@D˳C}8b.)q^EelA,Cxr8ڵkF\Nu>9 Lּ=sl9Qu IO?^,o9@иY43>-̪ qYʢ#S29kZG4z`W^C 7mXf(=2)@L/qoř0nUdtfiwbzg)_}oT̘p^,ؾa`д<X@O^\Ԍ;_Y>~9 ~(̪T{}ˇ>:Yi ݙ'fmK{?:d2+5`kweb[ApBzzRzW c,h =~ڇ@eZ<*ooٔ ܫ[WGś'Oϯj%%uBCQ//yxqNL۲ ^]HO0"@K$~$eb a"u%CYa&" _cGؔ%dTz ݽ{Қf޻y1Oؔ?bHjjjBBBBB‚ 6mhs'elA,CĘ.]0L|=gE,@>tkw,2Gu+!bmk]--,Rխ[7ww9*Ԣi3.\==<0W`XYk\L%FLU@E6=%ggcKӒ&FD'DUD|8+(mq|İu[EV'ǀIpbItԑ၁}#GƝU/Q~ވ[=K7Oݚ[>8q Du^p'nca~Lx`ELUo8Lؒ_YKy))cE l]cqz/5''{ޱ=99d*_ _(71#cbb hZ2 ;>(kz$xQgys t %{=Ma}s wgӼ'?k[bل p'ɓ% #f$<]X2΋h,))GVk2 qDZVX(1L~OoT8WZ_Qb崊ܒbh2 ÈZ]rhlnB7<ƚ/z{{ۜknkȝ2` Um/--3%;o߾^xaرxgY6''ӺbbZviœ@qqO,RQQq{S24]v) Pq2-82I<^'/q zzpmVĘqC]un`Wbp"9>MӂmX^ EQ ,!:b2nݺӖqZ zpgkG#LLSUkL}%Dj.i6r '''+΃+߫a_9}Qz6iYPݙ,gn\ZeW X)[V)`U.#E,k2ľgW^3fȣiZӉFK" hKL&J0 M dB(Z+HS0.MC'L\յ})f4-* ?vx%Ͻн̱b`g:?M ="~'aMz8@Da(A HT*R4UjMv%Wm݊^f|XI*?v:1o*.!(͐ڵ;KLLfsee۷w! !qRT"05AX9m }EӴ0Lfgu |r ?OG7@N% 221rWN lRTT#o"&&xplt'\R\\\R)EQb#˲w)**RT5O 2heiIxT8˲"WoB;r2>1K?cvGM #Za_r_P445LOoO>n=N|4 T\f3Mǡ~t4'@h"r<((ᄏr}||xWgΜafȐ!O`L, - yZvhtnnnt b#MSd4M;~xǨ@<%27`a֗; ﻀ7?z*B2 xg,R_Ned^NJ"_΁"a77!C\x1''Rܘz;r42N8e][WbKgb_=iKоrt6Uo{=D"6`P|cwㄫ;D"y,lj//Pkg&iΈ+W;vlʔ)bxz<4w DT6cVz87LeeeytttϞ=۬o @ ԅa]OB`{rBecbSE6j 1y^_>%nn!ZN}^5x.]XkjWC\'])}ȥIjCQ @l6 YREFFfggt_y@P4l~1$N-OO&ywFa(yĄO_3ǯW/e$cqOl%(V" pזCĬ^Rw|_ڈ&⭌NKXᄾZd2 l6s]S$Bh-:Grl.,,ҥ$' ?ANo}YoSV/|)6*by 7+ɈvD ,KQTn݋,:-6Ny/ %bfSyyۗ.i~#rTQ|1;\<"0Xsq?} q7}QzKSМ{o CD/r6帜o`ü<_|U҆K% o[("PXRR'ŏZd28cYeYތ!c.YX`۫OgjÛa<#'Ιqܖ~crsja2F{o+[n.Ƭ =DɅSiQs/Y`S@ڞvZTsl?vZxxdg*0Jʭ%]`xtʶ\wu q#xh(Mi4E@ AkZD0tUF:Ni:Ntz Vu:=UKpOt:OvqqqqqϬ/ ⱮV|miz:N[kZmMZmJz3ϋJ$www^׷e_r"iTMf"QqPzx[r|}k}<\ؑ)5g;irҚKw_pZlL&J0 MhP~h)54EӔ3Lt?LQ{RNy%hL| ;uӠN3cQIDAThZ3 0բqtx%JR*&} -W 1u'#95McKn|:IUG4>Yy5Xஉ-Te`۰ y-+W5 p(J05]{T+V7ڎnƄ; n޳A#3<⥲UHDE0u&RTTr't^eo-荛y:sկ1@R'Ls1c,,G 9 6o!txצ>]aBaj5ǽ.6{i`_eV6P#B̷IMM=wAz !il&b:4ػ=lgU4DMD&xl˨gvQ\bCA`itlY[Ȃݗ_?D}_ %UMU_-9t3`Lػq~F;7|F|1@;ܖmJG||}bcc}}}-#d2^XTl]E\WJV''~!ߟ0+|s|_/黆"5 85 sӂ:Qr/י=7PtY,NZֲ D%2r''y3䄎rз ^+n9[5U{?X.zm8PŲdrppF}1cƈ4ĵڲb#ɼ===מ .|?lۖ{={Y_ qA\R\\\R%cNeܹSTTR:F Z0]vOOOs`xꩧ""":_y i*2Nhvpp;8[n**((bYc.D(Jνz ֭SUӡ+G 5%H$WWWR骪t:".Rw}ۃې+@xҁw /6/,Za_u!}A i揙d⟴5VAZz1{P(h ,C=XhV(-XJ $ۀ6<8(H5čKI)ɟGN_~;1[q):<Mb$IdȲFE|<ϗP(Do Pgikk LLL(:4O̕qI6Wd( AGRN88Z,ReY{zz;mT0t:y^*b*d2HlFQ#+ PXu\<{Bf4Mzd1n7NflWfffbVBG}麮iZ-YaÊ HĨP(2.DSV`ԂD"qfX @ 0==}fX yt@xMj߿q(p8IENDB`workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Mode/000077500000000000000000000000001255417355300224105ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Mode/Border_Drawing/000077500000000000000000000000001255417355300253005ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Mode/Border_Drawing/Border_Drawing.html000077500000000000000000000105531255417355300310650ustar00rootroot00000000000000 Border Drawing Border drawing
When Border Mode is activated, Draw tools are selected by default and displayed at the bottom of the Toolbar. Border drawing is currently only active on surface structures in Montage, All, or Surface views.
  • By default, any loaded border files are automatically displayed on the surface. To turn borders off before drawing a new border, toggle off Display Borders in the Features Toolbox Borders tab. 
  • Draw by holding down the alt/option key and the left mouse button while dragging the mouse. Red border points will appear where the border is drawn.
  • Draw a single, continuous line or draw multiple short sections or points (all need to be drawn in the same direction).
  • The Undo button removes the last section drawn, the Reset button erases all the drawing that has not been saved/finished.
  • Mouse and key strokes used to draw a new border, or erase, extend, or replace a border already drawn are available as Tooltips.
  • When done drawing the border, click the Finish button and a popup box will appear to save and classify the new border in a new or an existing loaded border file.

  • A Name must be created for each border (click Add/Edit button), the color may be set with the sliders or RGB code, the default is black.
  • A Class for the border may be similarly set, otherwise the default is '???'.
  • If you click the Closed Border checkbox, the first and last border point will be joined.
  • The finished border will change from red to the border color set and the new border file and class will show up in the Features Toolbox. The start point and end point of the border are marked as green points on the border for reference.
workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Mode/Border_Drawing/Border_Editing/000077500000000000000000000000001255417355300301605ustar00rootroot00000000000000Border_Editing.html000077500000000000000000000053061255417355300336560ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Mode/Border_Drawing/Border_Editing Border Editing Border editing
In Border Mode, select Edit from the pulldown displayed at the left bottom of the Toolbar. Border editing is only active on surface structures in Montage, All, or Surface views.

  • Any Borders to be edited must be displayed. If they are off, toggle Display Borders on in the Features Toolbox Borders tab and make sure the specific borders that will be edited are toggled on in Borders: Selection.
  • By default, the Properties button is activated. If a displayed border is clicked, the Edit Border Properties box will pop up and a border's Name, Class, or Point Order can be edited. 

  • If the Delete button is clicked, each border clicked on in the display will be deleted.
workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Mode/Border_Drawing/Border_Editing/Border_edit.png000066400000000000000000000266541255417355300331250ustar00rootroot00000000000000PNG  IHDR/37iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:|xm|rKE£L$z( & rks0#G\.ץW"BJ7juP¬9W<AETcDnE;wU7z`Æ [GnԢbV9r䢨u pȤB"M*ģ˭#\ܪ6wU&!*|G!RRB~o>}(-zUU 5Gr%Kh5+sR\U䠄L 9矿7]8(#fqSKfF; $IeJ)to*$쐠Y8z^Q^^4?=pP=Ša,^jWb*t+sH={\8.FdWrۥkM1J(>ε-%}놎qfMZUUY`ҀU[ڼ](|С?g?DîSW u"LuLɽ\p+ -*;㎛;4uLw%t9 OEJ)% v #C\Vk㱓Nh#: l1l h!F?<ыwF-A\ʂC&Z!AvTtlaiacF+ś{_%U gOjԸ s[;MzꐹQ(ԖipYTf-g`04y.<4Ei K}w\hucKVJYMQ=![ {~7XX*K2@d{+7isTZXsģI[۔)9?f uh*CB] +:a` Yg^A>6jQ10e ??Cln^86>;r>^Ŋ² WtPܽ#p:tI\ j~ZS/3onŢ;<!YyNُ\ιrJQ!G͘@lɉB @m (D?KDuAT=ՠtwn>2+VkY߉5XZ Z-dR ,r-N=I29@oneOeȥWh?FMNм~ .)P[- eZ?ې?m \Qã**[à j9rƾ))9:r{smMq--aQFFMg{19;/T@mgq4i)k}oƦ/>UՀ(`& 9NM>s@Ǟy=ټzz,  @QUCupsww]w`[J3!  l}<`oHP\[ 73(WdO<}WK?FgXCDi^knZQF1 .U޼v/m_1mZ0vA-v{֪~7n˞5mW3jtnt=,;{ݶYv{wNH{>#P/9騩q8.­BN:Q)/̙1:'um/ے>c^L: 6><#k֊o6=5f[߸8N)\k˻)߮ uErC_L{تeYe1F!Q|}U=N"[FJߐ0){dǣ?~0y#:b7gnZWe7^) % …" qF*¹í*>[8A1(lݰ섗˴voUZ>|橝&/Ja 'פM>'ZŔly]^d潦,#OGB8&S ,)Z;%6,zqoOc|2#/eM|QΓM\:zÑ#'-KZ3%54{j͟:4_/7֌OLpt?`]Y{8BŎ٘l-3hnﺦio0;上 5PBѐhšoN^qԻ[!zw~AV#(Xx`]`{Fk i;Ϩ;}RnHwg`ƦhȘFP<. 5r=fk_6 gBp܏a,^k-hbQ~u<Ă{S[I7ҭ\7N)U9N%}@:WhJo~Ht]yȻE"Ƅ&ѩ5 nxHY=k:J?UIˡ 4jjhgH;#7v=C5hNr|GDK::߄<Ծ@C(=ucuN-Ew0MWJY;QhUB.&ͼfv~*+|h0]*}k(;wԓR@a'->X5'ADjWS?8/@Uϕ@@FrΡݓǜ1'fΜ֣V tsk-! |aڇ5IIM:ަG!Ą-k^(PH78¹o.Tsש}Ē 2(p}=/6pNd~wF NOC/6Rz]{@}d!LmL4pیE1t xpCBbEp&Qi3Nl(8@ncܯDQUkʢ Pi:(}~O"srA۾ys>YHmـVKY5<SY/F{#r١tS[o+tƘ(}L36/e{rr[c)))oᨹ.ε)g+s8*O]2=yy{fFvvZ@cgJ?^TTTTt3]n&ђo7-6dA2ƘH_}[-˚ /t :fQd&` &8xԳRuUu6} :f hA ">uHYZ9u֓|l6ۆ>@'h -:.U!x־6X,K.HP_$0 .y?N`|}{2ШN 4?kSz屔梘8.YQ=zN 80t={h|Ϭ?,EyejR>>ߧۆs7YmXڵcefs1ƏLmZ:yJ4oo(9 ޽ϷeO.+!k뤂yM}dMMGnϪׇuH[>y=ıjvF(@#ZսM܍-cDQ<0!?`L9޶kj>n)Zł Ӿ} maNw9жL[Lۻ)_޴j>2w,sr9qv f%JݻGx2.r6Sr,`##8eN-Y"Q,;] pL;5zjO>lZP7a{;cڡRbVS`8ޤw!9$!"9$%pp( QU577wРAodYo֢R5RYY#z#䨑jEG6q[t:?Yxj;-ŝ.kΧj%c-HrJy.^!Z 8Y37;㉅Yl\@V9vV7>{clW#֊ZC 5:Ur] |H hu>_1FDOP)8#ȪGQ zvت`0U .>n5QQ JA qఘ3V`2s9TNTF %ffDyZ6c EdMCxqķ{e,^Ukwbvq8(3crgXNEPd۸ J 0|2jWU~:[3=(~mpzPcjQ, 9SYjҀ5o4 "V=fA" #4h&B)%mq?Hn.יgIQ[DTϩ^FVt8NT+)O |n|'|pHlg@(bҰJ`/E)?t' P'o@:} Dqh PĈêS[<}lo*=J-4Qfcʕr ůY_> θJ7-ur g$s5 U/F&GA(wNb#j/Uu V=30I!2=1:nQ+BIIw *Pqu ȜmIENDB`Edit_Border_Properties.png000066400000000000000000000546431255417355300352210ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Mode/Border_Drawing/Border_EditingPNG  IHDRu7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:՞j`?ms%eEF5#:וpܓ#TRvtq7ehy՚׬ U[FD8֭v>g;f DtⲮl7niZ H .S?q#z5h}Vcک-z߸VU>+1_Ş"(fsiXWF w{Ͽ`k)ONJupe>Y}m+^\7./k9x]5dϾ}n#'ʊ+v yWT_!%BtEsvQMΥVk'3~H(.vh4jZ m6f#R .**2.,,,,,Y>Pxby^o#y-9vn:{y7/A*כz)2?s&Wcy zH_-R|8DN3)r^ZFd)K4_OhB<_~v{cںy^ӤgG\ۣ5=PPP<%c[r~=[*J_޽xmѣuy/:mB{=k=F%=z|\,㯋'yţ{}zNPt06G=~(yxţp١ ޿;Y4z\+9(rqkj)sZ~kOEG/ƟpeK(i\w$\Uee2SP10$MA8cIUWZ2:A (J@8P(DY4zx0 P$MI 8^r{@ZeJA22Nn݄BI+#ǮI]vl@oAy8g!yw;3;|3>j) rNvQ FIfr]7叉1?.x;i+ Sx) $lĒ>Llue7cw; -W$:w'rO%uJB-)Ƿ bM9mwAZ._:K\"X +18t҉c_oXkD E[>=%qښ'eS'/{bf^~cJҢ]Z:U/zR yO'--yOgnB@s΁wnKW +\.Pyid %TJ%JVd",f"'r^pؘy^GA2JR*jH9MIFFр`X 9N1 R1 e'""~H^ LhTɦ;sN8BbZ7 -F#4~ll-gO~Lr3շ'u"g<@索V䑭Ic(-힚с o)Vdo=`0_6<`'Gfhw //K9F~Д( F`=mيoZ LKy*{{O ;3feЮ.zvȻ#p]XmDx|qxrV; Xn)Ny&C em7!4L&B' z`Yl@)bYV4m 'bWN(r+XLJQr96ZH;I-z<#3ݸQjˁP\pz=K**jN?Iɗ˝|rWQZPP`ɧczcÉ+Ǜ  ʋDA 0YʬXT}=<TWXn-IS kOW  H+ @d0M'phʮ[$*5IÒD>fx~zXr<LfKe|yyo߹C3' ::`[p^Z ^^j !LP( ]RPhetafVun[P٪fa\F:W;ZAULE+ IP(6kWW^ 6bkxSE_ @ ŒES_zUѶt빫o5Sj* EĖ* n2^zk@8*|ؔ^cϞh߄3ULW︺MT&8O6 xQx⸆aBU*F\.teM43: \&i4q4/^d2t:Ba]o1 6UXcP9RyPt]+6to3oUh5.ʀ/b cMe)0pW%'B{^1uHFA7$NM %Q#c2uQl¢vp+;wѫ/y7x᝾Z#>j`4[JJ* KQ~=HR Q; W (m`Rrn4tI5=K(%W TN?+ 9a-‚@$a-V=cQ~?_e˽RX sF+K{()3)Z! =} #MU*jwRM4!~j_58?+H0_kqn jiYО N{l}V{`4A~VXYZUպ{Z -i\,E?̇6 ڜWl7!~ڡSRgd2RAFVlNg2UTTk#R)ɪ h7`>\/#ܘNh+sႯ>آ7|q\M{7%'pfز7KzG[kd=%;7έ$sùnV;mεԗ\J\ᖩ.6XUk^KW?\^3:t9<9u嫍C1/4r6eAJUC_ԲVjIV t 4MQ}}(%]p}sUҟU(g&$Iޕ 8nq"Ibo 7EQ>> R謪*{Ug7{0ǹ}g[72W$ijɚT lwdr!D@_ߤ9O1s @NVv+Gk'7N앵v,E<>~Z%Člnp@Gb^4ՑA?^3 n/^K.uB L{ԓ;>!2uѨ%+0݋duE=AsDfl2y/=21g%:*!="j=wիW7r<8yԤ[[>lX<X:ΓfMGON]<2e_΋d:lXvm:56<+,صkm"R=r>%︘ΝN_Xm>-V$|/Uu~ץW__~%#eԜq%^S%.=3|U3ౡ/)PZ _eOW`͘5( }ԋ{I>wK{ 8v 3\=l6iy{.r+Ε^Z71ME篒ڥW{ 6'E DU 0iR&NUrpx///. 'zhE*1sC$A=$LjZ,wJ~x@NJoX-9?tz5_ 62{t9B8`fG9@m>c%k< k]8Oq@ĵ.աC\Gi;ZiHx=[VEꥌo6W^.$m;~o3[ɥܦ ;*6ܻenMzjb"K>*FbBowx'厒4G:wkJ_Hݦ]sp0MljK8J]Ylc'[e3up oo` ?1{=ѳ~hRhسxKd'mtDEA!H'3 _@{+j卨tN # $t,+Z03#}TSt{yi&0h淔ƛÑw~SЫ b\ov.̧fcF@sʡ)]>80)c_?7F?i1~ӺM4PsL n^c-F-zhӬ WW6D$$9c1k@2k^q`Ȼxp=S$wt V//:xh49rdM'ltoco3 TUKbc\ǬF0UjO_lfh5yz[6kn(j,ղO#4T>4sw1D53qa1Q;LI܁3thaONAL5 f~\%;1anJ霵{wmZ<籜ԥ ,dqy_` k0gYvجQ}6{X9 >=vS)6~8zf8|Of枯JMiƺzGQHC1WO D8{lα;=q3f̘1cG-|_$ɳg,\m-l'q},\-ôk;Wˇcg{Pv:dEkGn۶kꉌn=rWD{gf!a"}7679Ywcbxu⤣%JEIpWpkgYMź>0Mby6n6OѾSk_ FNJ \>8sȻЋkqr-[l*`|B}gs\$]]"ԞF]9,N yY) 8{1>HܺEPʋH5tY}?,x4o3r餁1#"ӧ$dK7>9.xsQA$Ҵg2X]o1MUTާ+×gfyڞX'|XrG]=qa]c߷ϓ$zǂ#XWjC+7{6PzlbkUɸg'[;imۥsݺ p' z. Jw@@'WDVP}SQE3տa3S]/)IO_r/M؅%׳S':CFO^Urܡ9YSq {CM%rsh',ꉴ {! n/6pIh-EƿЈ I$+XzdX?'WW&HV+^<*޵_^}grETNjd1M8 OJ-6t9)˨uH OONڏY:{xszV1h @0[w8'DF[p',OXJ rfM[ӀM5 'u-:G &|RdfY٭x~!守:q))S{FGt477зGCGcUߘ..ϙUIO.΀QMcNX @1i`Y3Nl߇Ll 5>?ݴ-ʁM떭2==׼Tt_nK쳈2?o{< 05ȿk(.6i]Up֠3@t VA`h<:^< 5i⯩^0MM3\9g"5~A覃!.ChM& :έ{1'WD)iiib-p"ky($o5Ѻ(Ho`0ܡWJ몦+"7%'S\5+"ߍ@rEt#"̓pT$$B2 LEB-$Sp T$$B2 LEB-$Sp T$$B2 LEB-$Sp T$$B2 LEB-$Sp T$$B2 LEB-$Sp T$$B2 LEB->]lEEE*裏z{~%$: ʞ={:vԾCu/ 4o=kn9S8(&&Z<1EGGܹ8iȎy7ހE'9!->K)}=u:G_0f3 5֒qSO}\ٚ0sוmpVy8eYβeYD1K7Kjqfhȟ߇z”UGM`_Ӫ O\[pֲogJi+s]K46i Mߵ s%OLcg0j66hωv"[i3v]4~i?_йL8~KKmf^Ӓ5~.)պ{Gpq!KK-/6è&EcURMaY2%4 πr^YxmBb]Y2_y8i#WF*>?Ņ(Դu7_| (ޱ8IQmHO[n杀8cs.i;˙i;cL=%xQ:lά^YԤG}}'fTK/W)nbdeO
+YX?8eD___d]Ƽ#ڙx9UwMETL&dtd2M&ɥ܌ߦ#;rgEU.8t'% J/FX6HTip! 5_:.at KEx^ɯziEzF39c,uϧD?qӧt\ tVVՈ k$~rh`3w\맲01?焵m$-`/?3i⵩z*?/ܜ282sUeJ_S/VBEQSL$AR$E\.Q慬}}}c{.MZ ~YLLxӂF։+:Ed9ј,Y\I@B깥}:Gi`8u2XK֗SS8MRlX&% ; sf<Ƞ~v͌?u3!c>|50-=n~[Z^ .Z]1i!Y;s92$sbr1rsQw ()=& nUl刞AH8x&D2 G^萊UҀIS}rtAlHBD*.%#6OiM8iIH @ʸ)18x iqC+*==[tzUrv PXs kqk(`cѭNjĝ-SaaItI)²KS jtÔInjll0g!qC/ IN?va4/aܰNe )}z|֥?8KO!-R.қ5Fs!cjIk"$"ifrKKb2u?tV+Um@Vv)DT:[w *2LP( \.D.!_JU>oMN7X&D`jd cYľ41:u9G:%c=9=U4  Es9Cg0?|-ǰOK\wXgf>u?&{:,\gIR:X,tIFyCm5vqE2rrm0Wl, ;G A7nܰlˋkXImRz}?y}tWX`TRUm4z8kĥjĎr8\B+],hwtC۹w풺RF^UJ4QoLI֭[iڵ], Dj)#** +R8}_4> dfffff&&&j4HJزgyF?›&9cUyxx2 u=dY߲;ΞZ2GrOd'9ԕc6i1LKnAx3'~8ΐ2R{}-qsύs<ϋ'sXyV(ѵk~d2y}IĻG;ń2aqv __3gԨ1uqoڠf~F+_h8 ,'Xvv,+`yp +Vx򩍩_zV>jsפ%6)0XAgZꇻDSS?wcv6N(6qF+i8wE52VܹsaaaɴVyLFSdA V?M[::{<=h*9._ zcΕxbE .(u\ fy߉a&;󜕳b'X7Lw&oh{l4aC}@JMlNʫ_aOj멩"@A `z{Md`Kߎ_.c/FL1z>66V<]|H,+`!@on@[ }d2U7 &S'<{Q/3/tkUf۲37vA__:M_՞j حfQ&.^!@l\D4M_pl67h*L&Ӆ"!x`-jH&@VŪ\"ڎtZ1nO.`sķ ?i`/^[K @ I<8 p<8^ٗ`24DŽJFjj4///tӻI Ha*;<6`qc0>]ƚR$U I0|QڂKXJ$I^ܷyŴGZ`9콝H\W&c:အj@Tz4+Aq"3 hDO ΃vJQJ)&A$IMghdr2%i9MT/k]?B5x@ (^J#sC2FG"*MAP []]v7qY W; (`/'M2*'3jz;UfXXȟ I{3݂ l$i)rH@BA j-@Tbq,8-)KnM}U Con|l;i!uK?A /p$!MrVNQkܸkd(0LTD4($`/)vrJ73S KKlYLx/C Hl&8]f DOTZɁe#<48y_3IfN=EQ>f24%' C1 )IM)ZNQ E)RFrd>ʏ)Pl)x*;4 wYNd*Ϻ4Lfټu͛9Va&((AS;IDATPe2 AV q ӞE'½joWu7.8[ǂXf333W\߻w-Zҋhbstҥ\QQQr  Root Entry}#256_eaa41a638dce0b75*3((73_([(73_([JFIFC  !"$"$C" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?5 mKwV[[Khڟ* -&k槦b=..K Uhx64= 4eGмe6dfpN0GPkZ64}&Eq03MYgi{lB4.4m׫ymhO;rr*s\GtѣM̜Es"c(#wIZ'` >åtє)+3(ҡ:2SіN[],:;JYAz,lt.[S Z=lhis SX *ڋhnȎqX鱋HHrVWq[Rwڡyum#zfaK| + ՍЮ$ @׋c׬5FW/{c{^*m!K fvj}/Pdb'Ck)3J{TOX&fx**=@`kh_iR:?&;i6:MTa7rW1^=SKu ,U0H;#ҵ.uK# .-;;xk~#Xȩk-cp%ۜdr;t7B꿮Χ?|Ih֓ 27`ޭ;lHu3IH%{Y9%|fAmmuz][iLpD0S8-_][H-fAw 󃵆FG^+={毩鍤2W|F}kEE|5[tЭ" [+Ϙa;YH`1*Hoϥ>/깏I|?qg]^[B. nHVr:U6|i7FB񥤓ox$zbǚww{ZB/tN0;ppݸ@bMEEtvV|M[CVWZ[uȶ8''z:V.Z>$ѴsJMe[y$4 iQ`Ӻm~/ꏷ=uM;X zOiZՆ-osk4r\̳dRl 5aMuh|:qդ?ۍ="؏r\ESJzs(!Qu,K\G]ߍJ@VPXM gk.t~!Gx~m6x-%d !D?&wq1P] oBQg{itfF-uvVWT}_xV&bm|&.٭偼I'5lb·g|G=|>/{z~/꣹[ M2I+pk }ހ,h/]|/#YLǘ7\Yщ=Iо rE?>y7Dă?TB|YeuP|;7bF s1fF$&_7^E$ o_ WLnB`y!^-2? o_ רQ@_WLnEzx/+ſ&_7^Ey!^-2? o_ רQ@_WLnm襞FV^Ey!^-2? o_ רQ@_WLnB[euP/{Ezx/+ſ&_7^Ey!^-2? o_ רQ@_WLnB[euPYW>"mOmVk-ۆ58b;zuZgxo֚T4Qqj{.rBІ^i_UcZFa-X\ Ԗ`T8eK^)|#]8\mlP!#;J]4oĚ5"[ /~n2.^G\WM_4+y+KB|8Zk_v pgF: CusC}ͨ_$FkN_E41JѮe=hZŬ7Xk+G9P-2*9uW-Y? 'Ii&K$A8ɵ{URN Ih<+$f<:zuw%4~VmIp8 "WQ&,FC I?]ɢ$w0UpIxŘMu-ֱa[HXiR\o:T]Thӝ'9Fsԩ8F2KN=<{g'6ܩ<>7M}j]Z/:,l_1]|Hp /oj\FJ}lqoy\r݄nz񍟉s0^fH4ЬH&!NzBͨu3Uj.oCו h%r2W?p{`~"I$kc(mݏ?J͏ÿⷾN?.5GBݭM;L*ѷcq]v-8Z;{t}$a'cU5 4u7c1C<;-φVݧ ns=Վ6i4n:tlҡ8o/=3gF}~ i-i6zTq)&2zÂ=k:ς5_ jZ=0IW39E+v;F2z^ڣ_k}q62nた? q#|: XJ22Iܽvkf!V^"q:"_v^#o=ׅ)'iCᛁ&ݼq)=_Mz?P]?mKj2F0LyCe%heֻg27FWANA#D5FX-fmŻ`3X.v+ %w}l 98pE+_QѯRUd΃+I!Z"[ڢbl "gxK[҄2+kK;~Qp^g v0Dcb 2&s.M4V4q6^[Uf?nkvas^*+E?/WpOGJG448IVY(2끊mVƣW,?;Xj+į žz|%T TvW~+|M[]Kφ{'(*NRg{z~'Ė4`AhPz1'FOqzכWTҬ|ao멚+9ui!iJ$kT]WSE?x֗VW7Zv״EPVM(k6%$?[7t?4dX48՝UcqdgS.0 .AEsWB /.`έ7 Xdaev| gZ`n5łB䳁CgD0 Na/!gZllA`ֶv1Bx^[UOT]j!Q@Q@Q@ -e .r@#Ge h1]Cvn:qמ֬DsG? jMl{]p`qr:t.Й˕bd7O7Xg?PG,*= }YX-A#,?]UT @ ܓAyVRXv·,(eA}Ò=TC<| +_Arĭݜu3KǦq1}~i>F4-gv\z{zqҒ? (P; 1z7g?PG,(g/41`(^oIP|sԓJX0#P#q%uq? >g?PGpbx_Bff+}I8cs`tUk- Jk(Sq4g#u5c}~`gl-, =ď n?I? tw68H"v=$ԔKYM֟fmd+47kGQZUM]-+5e8e3 u wTSлvv)c 4li?}P΁fΣ" bOڟSƓAK_cI/Sлvv^?;Pli??4 XB2Yo{9wi)& {VV,.dm|glr}4W)/iլtEMgZFN!Y&1/ #eD@Hf< GߋG7zƙǧl48;IVߎP(꿮ћ')4-kLԒZ  wwTG9+2$ ֯VNcF,axߴÞՏF'. ]wpMcVZq(m,W>kߊuꚐ1miLYfo3CɷY/x[;?'Ӓ-#u$`cޜ]ڿKл_oLA}M&VVY噘@9 [O|@:mSamNOݙI`#_h+xpjkZ[0,R$BXu+'WQyjO׾ͧ&-KE7!pIqT%7,(~X]F斤eoXϜQ EV~Gqaeqk(*Gq _"|ƠFGI Qߚ<i)l ^ ]B5\*M냷zm6poAoopB(v ՛c1>ZiUo .t5wh5%MhLlY<  nH2if nbKeF #1ljkvoX;s H񼚬!6ڊystZդg>HBTl (k'&uqK=W1=պMQgidQwI*ˌ#COlȱ,i$R#2 |aqI{6?xZ)n.K[l XpӚ|;Zjucܻ[ #,ɻh~PD[חvqo֕Sn7ZHAEPEPEP(~a]W~ޓ_P7(czOª@ޓ_?5 EZczO7*k?5 >ޓ_P7(czOª@ޓ_&a2h zg֩mm,e2 #;-Vv&dQ,oI Gqִh :l?=:l?=X+60}(QҾL&q~o,dV(((((((z3KUhX OA*bj 3/d,N8iK1^kVbt(u!G@~y]*0o*RH2趌ړWVv>]x~UBZ{ˡcME]\[&d*kvzᱩ}VauNc`ܧՅ{S VIAӶ/*8U}n&X_ԼAef`֮%. hIئX##wO5V%ѷdVi&D1ɂQr0eLItݴgxѓAC{|x e]ͭ@Ye(m.I]~s4wsZGt;4d=ГǵzT"$uP@]v⇪hV][떚 8W<QGnX]†sz)5-:?\麔 ^-6$ܡ ׶ȉ" 2(XUUTP@ izxO65jW:}3VAǞ<2Ir-;ԃ˸YpHs޽8*a@N:RGqE\ d|%t>E лP,UPe+j..cjOP425 t|!}G{y13OuӴ|7fTּ^u?|i6^JJzVޗ/͟{Vw-SXUQQ^Wǃ<;Jǫv?Ry4x++93_cr=EĚ#WG0 469 劌(%`jkKE힙}kZjV\C?QR,y2g#9T}Fr=EsA|0jvӣuluWW :?'Ug/w>ǫQ^G{T y^'gb>WWuxv;cXv5<,DZb+ F TΝѵT*˕ntTQEq!EPEPEPEPEP\ԁTZ fܣk7=jLE,m m5?f6 ƿo'S\HV1FYKdm?7sֻh_ y1j-%՛1ʟ߈ 7K]sJkFK$ف[Nò6wCWQҮ,u"23Ukxzk SȷHx.,R-!6#;3שLo_߈ 79ɶ>~oL1@+ ?qD_/qi6V iz]d-6N^F=Xe,5_I8}Ԩ+ثV;k,B{7}\^b)O+cOfᮉtġƁ8%/|qR+ցeyiz|c^;MP4 vVH1 /7_iqKGP "c)@>8s}.8s}.~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:ʶnWC\~BӴ"'(q@BWR(ۊQv)(ކU IAx<*h4QP`q/|F02@"I0()~*R.>>,ʋoy$AnwQAݻsϜ].Zٰan݌fZfY6sm6[ZZZQUɄ(Xn],MEWrZ̞n)Kհmîƈ r'nMӴ$IE4MF=BUT,TN&Bмxw:i~]Ao6Ո ˥KWrm[mFJ#"YYYiiiAoZP6nݺ1 S^ZQ_-Z)RĂ+9yv=]myW8ywt'+b֙v}ؐ;]*˲uU}@ T9~vJ^^02*GZ(f㙝d{Wz/QQQ"#L&X,ܼlcbbju܏=a5F޾Q-] H'}KpyΫ{ft!\{Ǎ˟>wgJ@,<51O6?O}zߜd?eңR;QnC@ T z+WFT~~k0.!? I n#ÜTsgPAMllv=///&&&""Bx(hSIRk4ڼ<yS`qd)FUGoaܴnD1T#!G׎n,ͨvP}oSm2k j(IP|hǡOKDwik Hѕn^7as8屷eœෳvCu+)x~ !tol7kmV\oG/ ~JSS߈YEQ,oWB%JIIb*% IdSyܙ3X6zAAAxxxTT"nAt96F 8rz?uW*5e $ث:BZ͙GN\~ۈ'(]\XEaXJdIEQ=xK5R>췲eGV)#~gKT$,Ujڜ0*˟+_OwnaRWywM`U$ChepT|@ T-|>/{y^'TQ,Ʋ!4/U;! rQR|HH2Y߭@4˲!!!<̬02"*n 5uO_XFDl, rss NI$e:uڵSi=$I& /Z䈳Yc;u$I56[îsK'vڽ{i^4_)%4Q9qikf9Yt{㍧?v?5ѻ=Sw*N:uꫦ2]K'vڵkѫfI$I͟t%L>=LF$Is˦)qok/-i:W RDNJ%SP=EQ,@[X:ΠWrP†9Sd҅uHZ{0ht4 SϨh|(A劬f 1 FިՇhFڨuCHY=L\;á Ǐ''?~xr/I_]u֕Rm(i-s-澯Ί?6euեSdڭ_wK$Y 3_ו ?]$I]~2}ҵKɣN3k}kӒ$w ?鼵dn3.I;}q`ۼ_/K$I8w֮X#`W}sYrm7$I2gvZv>ܒ)w%\9WIӊ g\T$}:l~zVr$I_[㵻۱q6r;(|B?JwI~uبj ]AZ-BE9]jX-ASK|PM94Nj5zch5˨iNŰ(3\תv8'dM}?.N; 6{T=#/wifI%=&JmܨPQ|n[d 7hgY'#H(vps5(2!1Kh&.\[>SYVɥc;n.ƌ}^qR>=Y^W8(Y[ELYg#C&"6k*veK[M|uUǾ[ԡ(^X el{o.ӓx!?(JerVN׎L{ƆFGF" x PA?)4/,t{쨁jtVZ/ d,ghĠI4jUTAeepX,F|8k<-`ĪEZLׇ y-58<~X=.̲Cq>2k+Ǿ7%s揟:9EDQMwy{?ޚH E΀E?$Zc&~i# {x(kzc "DYSe tZN]ycڈcpG7\_v6Y-'d 8IqϠ6RurWj&$w1Ѐ9w|S^ַ{v<&.|f;sRcTqc~9hmJ&.^W N[ȉÈ"_ ;VoȌ;P_ٳ'h$F&7W:Ydj4tZ3PYTTtmEx<YCCCz=˲APEQ*rh4ڤW7uȲ*((ʽjBG{ lzEld祌+)*Z+^;DC[uX`#kjW:,F e ꂬ^#S3T-d],hStupCg=,`MYb{<Q"t|N Tq?DBUǿ,`vȷ}]΁Dܿ;PIo ~rCbA=@֭c'=2ݽ\C7@ |9߲* p/`Þ={x"[Gy'hg)&@ Uۋ@{i9pK)~| _ ė/_~*H qq^= +[@ 7Ae8똠n]`v% w.iʆmre7![JG.¡v(RI.Y|,!M߮%pQn a$I%v\ '@@ U?u 76Yس=w\m>(|Vy^V?BG.=<T}[Zaq@|;S>_3i֥Wj{e(gz/[;~6*K?ۍվh8qoҐ1{=CFBxީzP3!1 b+  A9|QQӧ|!6lh4qvAԩSy$i:""QF.ˊLP)O,^yr!Aʆo0Э+9 \pрz<ѕc,p͌C[1 Ss`eه[6m+;dPS_h3w#~I:<'OSv-Ç2ĸjiȟ3rdE~/? ⭫;ߋF<(#.\p dYv:nڴ]wݥV ɓ'z),LՂjݽ{w&M*=99YׇZ (,,ܳgOƍ+N/!de8ѱfmY^7F}{#ߏ\h[(@) =Uݶ^>Kw9p5UqYk 8FchɞAqը[4œ ePS@,~e% ؚ&2#6EO8\ EE&wkd vSSS"#####M&,%777///55:u(ˋĭa2(b^7zFFٳg#T 5Jl\/;{l Uw?B#Wk ݑiYr+ 9zOxS`GTF-¸['^M"7 %|!w[Uq^6A7ɞk_BCW@C,4yĢ_ &~-LX^dҍ(Nŀ,^Zb@$w m233WLwhj:.333""B @L)LhDh4E{nIMa:.?? U{ǩ=W :Egڙ):Q8:W~%uM:uB~?outԡeX(.ry풠 M GѮmԨcYx^+(!)o2,8sa5җ_9 ]mf; ^b $vd2)⃢(E1 $bX`YU&GWV#eY6$$0hta2T*,Jt 4m4˝P%$pwr.X3r|9W'W?%<;G- !F(JyK Šm$:1}ǎP,6Lab*I`tǏV$׫U]PJb|OoיּKhg2>hde€3g w&ydm۲bL:Vyىj݆%A~S? `}}Fl(  ~]O_]Q˾Y+&Ď{8nO/p{^ժ[V@_ņfZ^XpxC]|~Ȼ"|{ݑq,k4a|ǫB34MVOQ<@Wͬ IDAT^8wE-O UGqAE Q3hˋT˖5?b_MǴkaKi[/t&Źֿ_y_m:10gp9rʱ/~E1-M2ܹd}|ѯ%/sOudG{)*C7>]K/lMo|a۸,:b gn遖{)&*5.u`[7+Ö$UT"_fw,2kRN4:ȩސ.=p-v<*?Zpy :Z$@Bѵ|'P,ʆz=h tio)2EӴO<)e)J 19{7vi+,Idt{"<"߸>+CK0DSȾ}]c HYFGQy-ig'C^-Ye9{df5µ$]:w; ~iG|+U$⌂$.Џ<ڹC2h{G%Irfݛ06A'e_i셷T=vP |ʳOOPKyY/_v.>8\fFD ׅ B;>᤾ á냦jNdMөjFt#44lt$UP%dȲ***q$7Cv>{?WjVdȲ˥X$BIf+*b{][X$2֗]HAEM%IJI<IHXx'86GtF??[ K7~ln޷l|HL 9pT){H:^[TB*m[Fqm fq` [V;Nj6yòdZ*`0Ȳ,˲X,ʜL&²`Pb:]E|?$eRI`)({l\޴;Id=9LKT*Y}$PLlAOniT)O}%b5=NOlqE{Z~Q$x{2vފd^4cro-ror{P'\f܏,#*yȨW^[fL&NDz,EQ<;j0]Rާ*XV})ѵZmhhhv{qqqhhh@EEEAחP%!!,v~6.$D+8\j?5;4VtUk)p ɲZƗ~sZetZd[ְJ`:=-:UeAVhgq1/4k%ShV-:j :N,.rBy/_\"eI233UFtZZژ1c:tW^wb۶mmڴw5jԈiV+; tVf ˲ʪ?M.P&)$$X)(4]A*,[Z$Z)zl;签 dyGq<(:Ew٭n(|Z*U\v+EYYr@؊<ES[+࣒G٣KBPX )NT07vN+ѣNΝrF)?X% |ܨXPi BSK\2;U3:@ 7!҇@(VRWT87tPiuBe|bK/ u~|o4՟@^ 5p;]@oQS֑Svo$@oÇ+'xuA x'B @oA@ t\@ @ D@oA@ D@oA@ D@oA@ .@ VN8qGmڴɗ@ E4}<ߒ$=zeG  Q?AE199Ӆ4M+_T(Jyn;-geeQh46k֬s,{ǚ YܶBe 'Qox `2nL >W\QT TeYݻwWG8((YoWڍ"@jHrr1bqEC ~DQiZ*}N##""y5kݻ[nK@#BUN#\uM"4lvxĉM >e\;?by^E/Xnnnddd3Y[lk׮Ν;3 @jܰދ_ܺ#tKdֶ1s25xNq85{/7o/}եY; &dEQ!,,L~s# ǒB].K*26ې~E-J6+y0Ը]awMΣ]Ѡ[ GZ#׉PxS+]>e0`ݺuvEx$I?TZ2=ۊ `6cŒw96U7יdž+昘5kFYaP%"Q} :$,+PeczdAg*ΖglLu2{[l9Y=]݂[tz^ӵ1 ҷ1T+_m_[75h:[;F3Og7gWԏϣX`AvvܹsT/ Fp8{ zTf%MleWOh6wY$I$;q"(oáߝٷ7ʴ2-GZ~@(#[/mázM;dN"Ҋݒ$IR1r6M"i>Xbʴ'k BU ȨP&}s'U ieӑ$Iin^3ODDDDDr:N헥SmwJk  \.W H1kNg9#f~/JDGȻ]. n_.fsE?r:G< g:DD05Gt~7yʲ_ k),ھ}oIY6%x,MdžRYl6i슴ey֬YtٳgK}v>y鰅{sU}ቫW {!?w7EQԥy}2@Rpϗ*) =0%!u"4p,5qWz60p-/5@gKFJߺ@Qs>޳bݫe h=d$&|6+0rI ^/cT*ϖB-.풀2,#ʝ ƲV+XVOMZ=Of曏%F|o=,ڸAC<6 8I_LJ[tjCfmk,Yi}o| 0e^(J!lj1,ڸԞOmF?Cu<&~yfFq%o3f-kG5=3I_=vT-g?w2y%.}(i;E A4W#~Uڼd*aTxy {EV(B`O7Wـ|yI$;m@짓\Xb]'K2po9w3{>Ͳ_QT}W0>=^hsܼϤ6SW ' Ns#G%ˉG~<΋'Xy~nTW>_IHZ=tu-)ϟ˟lΙ˶uTrܲϤ~Q[{z"Ězz.i{__;/MyM|oZn){/u/?pye㒣9=)ϼU{)V|Vkg\xg\9띭XՏNJK~`:8y9U-~oaLX?69`Eb&Q 87ʕ~pXʑ>Nͯ7aL_ptׅ^CY[QOo _r'Ym4া^[ie8iGN@6C壛m}6^Ԯӫ7 qAsOb<̴֜7?;}L2?Y].{L;Zhڲ k5k"^u;omqʞo>;( mΠxrwkS۾='2yeY6a,꧓o}d\3d\Rk]3CɅVU۱$E֍g~/Rf_ם/]GmЮ쥣K5(*s)3ll{@(FGv8uR6:0տ<(o)i&?Ϛ}iN,fnީ1zMyMM+0Óϟ͔:5eyo꥜S=obPcX r?בe v&$)5/ (6$)X\QE!ͦo7g6REmH\9-oRKzxbE)+~O{EGM8*MbێagjHc[0++KDzSgǪ5b -''cu'`5 hS-Dfhhr$^0lW@||<()MSwx=9 g.\ӞivL*&{\(J@ͧ- z ^r$b)+Z4M@Qeh:xa3zzż/=)Ѱ/~1ݷvZLGk.!)R"IQ 6Qޮ8V~'R,ɓn'>̝G 8yzMm^ܝB=/WJ,2hZE`xϋmzzI+mTEh wuzl (i/P4SwEj*4ʗ> iРw Cynom8Oa84N@u?:*4) >x˖p%c4̝l_VVi&zn`im6KG2oC_v}mwJfIK9 <|iV6(FiJE& mK0lW vSJ@S ,TI"ڈ|ޗ#fm=uϏ&((y3`ha%;Odfr\($.2=Zb\@W3 C3%H31ɹ'6b1Slr%,}jvbau=bX0kQ|_7 t=rOSC΋.;q ǬK.^JV15~aX4Ӗ4lʕYC2)dɊ]tXEkzs%@ Mk#%ŋ572r Rq,˲,8qb.]:wܥKcR 0ʌz1?{M~`ngh;v$?+^_޽e͸O#_ ~ U(Rnת]S1pcYt|%/?ݵy',ؖ&Ԋd=[> IDAT{Ť7)Єo=}R5Dڥg mv1 0?gmhd&c2ȑ#'J;@X N|V?TWaOXE$~EՍ@j+geXxI`Y7w &ty=g[=kMrNID2yځ4 m1`5˒ܩa$͚寴}M4q\Á5cܐeq DZP߷+(GRVjZ,^zI+SM\n5n(n3k0d2Exfzw |[n=@pװÂ#ᡉ6 h &MIqÖmʊK-.5 +n9쇂e5w~ר FS nz?^YQXPq^õ@}aOXX-L&&~j{=7=폜:]u0,q, rH~t{hrύo@ӻ {-W u]?ozhN8(Ͱռw/)g&N~c=~קcM?܄V&_j-LZlX_?;=+ȐP@rGQ,Cf8$>A/(H|A-M2C+| <Kk| @X璪ʐkħU*h>C#{6W{ӷo}IZ=R g/l$l= zDl9ڣ+#ex}|t L:c8(<7qm,%Hܞ}5P@\L֏{p~aGw˖i\(0jYeԠiMl=l`{X؇>.)S?uc;_swmrfV=3EV_ ZgZJx؏԰5} F!彮 k|8$NŲۅgzN0pwVh :튦JPM6?~ŋGѺuk_`e;MJJ~0Ǥk{ѯ$Y^nkڍ(^{0ᣒMJNNy>--~Ȳ|sկ_?$$iDb &{jv14,ɠA~ {wU>l3,*K.cne*X.mfhgZiZ˛d!di$ M@0W\@!@F;3?e`Qy?̙s=}s*ZZ, kbj]6,[* ` %JT̳;U(ϓ :?U)H$g:8]Q^e2ʦSO:ՂP# --&U)UZ @=kRZl>o|P&BIN,eexRrwU@Kڮ|D,NwKW(b= j?۷pHөTN:(uCle2rrr2X?rS`')h4fgg$yW_}N:5rHDuKMM߿?߿G:nН 2}˔ P(ZB' c#A@mH$;t!ߚb$֑]]0҅BIMy6fun&rC몿҅B ZOM(G( W}9J6w߽@`Ѳ%[t!]*I)G$\w;](]81 xyy۷ÃIQ(PRoPy9ued6P O b :wE"Y{m§Rz?ꏐ'L0510iDӎm c @|Y G믿`a]:BE/Zh6۷'e1Au+EKdN"5(ބB!6،]vSV\uhb? z)WB- F?P5|[^tgڵ+EQ])7 |`0]pW^ipCǾW& S)!Լ0 O6-99yF\GIӦMk߾}-gϞ}8BL"IcǎofIIBm5+L& nP^! F?󃃃۷ouA9~ B:)B!*5e/BGF?!r/ BȽ`B!B! F?!r/ BȽ~l6wBMsΝz2=4[:eh4rOwlEǁB)$3ٞx l=яMc~r ~ܹssW!gV*Nd摈~ؗF~ Zg;kUTj603@a/61 .-B PKW<& ILt~{P0iq':˚" Z;ef*BI?ikgZ{jt]rڴBWV}l/vxb5uL /S0vn$iZ2uI^=5@ȝj!B@?K%璒L,&礃Vs+ɝW;W+֝1BA#wPv 9#_ʨTwT,ýkK@VMQ9i>݌)IKun~a\8wf5V5HݲlDTԤ~{Z%K^sw_)=zAs2M͟{b v ’ž[|l[$y;c 5q>5_ ?3j/5I$i{6v B8 _fsb,9eZ`SlIK[97];-Eʒ߀_YiJn1cV(ZWX^ZXU}06u-9̠넱yUW0gry|Q]XM =Wb“ ~M<ļic f'-\ru#בeߕRs`+g\3R.Y%^btG `љ«W{Kj@ OY_j3;~⚭BGWn+?w؞oܷfɽ|BFw9oES  {ɿsok&h0Q+eb¼ toD7/GnQV"Wjۿ  bT*Jhmɷ#}|||FO | nFo͟4UI 0ߦ 7z(lʾѤQqc*VՀW }ϹVgBpCm3W/?E?@Diƺy?/isg dZ\r>ސղk~ᎌb(]':만?`cNU!$MHi@2˽ ;˧tbovϽQUc d |e ;;S+n;iٷⓋc`^_))Vط]H^/Aغg{TTpN:R_Uۏ> <<=Xbܑb?.e5{ДV$.$zzcC=$(}^ˠ ;\`>x;:P,G<yGLAڧIDDW)3aU 0hPX)9)ߟ:PH >l%fBvG_!*MF~;!eEI>Ҋ PfB9"wu6% |+XZ j;ࣽpBAUgWY;-VSgUV` _gMda#wo XsLo27& @L eKL-τ!p[w>`;NXtG{g$U5cPAY>|o &ʄȁ# S Z{[yih8d]g>c_)S*+o*{Lm_2+N('N`UYc#3~[Ъ H:񃹙>sR}kc:̪2-UT*$o~*`űme7D9m;y@ \)OG@jy qk&x7b8X1G|@mBJ®o I믾*:ujȑi2BDjjj`=z IiO/JO=-Jk6~M$9#fl`9 N ZmjDijDS|%pB$շSD{B.J.BV./~q" MxAOq*gQ!!r)QH:xW~c ')P#`D")--mZ @"y URlǤV9٤>CpM#B؊i* ,v2M=)M{ g=O)ύ*!$~l9su Bv殂MC9{F? F?!Uw臻hѼuFhJ!B"~B!^0A!{!BB~B!^0A!{!BB׳Bbbb_&//UA]h4v޽٪Pkя]PS8&O ~ӃBÅS]ٽWGD!#|Sяhlƪ"Ԛ4,Z;kYT|#EZ/O>WupXlu#mm/$]6%Go8;_!tZHOc Vџ%-!}c$Y;䍫)L|T H@%I͑L2gtysئϧnz{/&P7cs|㛼jXȧ6"pZR*r>UdSTyNځ6O7cJimkU!۷ȷeQf.(-~+Glhôâ^cx`ëυu>wa[QÆN8߰'?6HRo KnӏmN)l"WlָB,~~qg$_Rwk[6$Inkfßl!Aȅq^<bW1& m1E[Kث46β$&zmI(6mVb}5*Z|WVZW'nK_GV}WJMQr|N "{F<=<#嵺/|1#NLQQaaի%5}N Hv',ˉ/] cBҸat04{hUwp4;'ؔ eO'm>>>o>I7(U_?p!8>'$ f_BiJW*ƒ[:8WҮQQlON DtmugʬzLMoEʤi! `Mq"[Gڮ}Փd[S >Rۖg^6`ӗ( Ҍu*~<_t;e(9ɨH}E#!ek׮QN{ueOpW+녡?,,8~<0BI*4 oTQqVo@\^ :~󼸞!k b fm%o:e:͟a~<.XuK2i*Xqr !=$(}^ˠ ;\`>x;:P,G<yGLAڧIDDW)3aU 0hPX)9)ߟ:PH >l%fBvG!*~r[?C7mE򤨓#yʋ$j憊+)gmh|ΊNW(Go-6m&:fS9Ypf<껈 IDATu:Bq8呐4;d֖rRQRvqbV>h3ʄ̜(@FA9# @(3 KhirQ+b?I[".ǗxJ5`B[kBb BqĉJ>}l`do ZU >|Z'~07G`SJ/xmLǚoȬ*2ZJRAXQQ&x KѶW*t0BU=gn^ OZ44[HH}ZS[낥6a.z89빍9:-.[HE&l~>\@W1;ovo(X{^uSQ/<^ӟf8fBaO-6~sR>~MB=Vl0@kVk׮]m,3-xa,y ۋAFl(˕ -I,RT B( iFTjH$5ReZ`\4 uOD"i0^^^xxx#QBI~M%[UO!U) T,+]h@-sainnnU/ #0쀀$_ꫯSF٘&#JߣG$injAK%gZ.rh.TrB5(-|YmRb/t|U8!۩P"p !k-!Tti? &~ӧ8tvkajJepp(a$av6 Wތ&)Z9+x햬BwBqя0 a3 `0l-!}c$Y;䍫)L|T H\,NmqjH}uE};R¸v!jqf{w:=$Az6զ%߾@/  <"jw x' 4M,{Y.|eʾ7\)V l[@!P ָkdd`4 FOBVڋG@/nj B,cc䴍DKؒ`NxK7/n<]GHfM2gVSy[mNbul}//G6 U9{ e[ ;33 2\.1ױ]hˮȌbN_lBq}?Bx<>/ ]G(XP^Ay^W  JY)k&''$WXdlF^n}cÞtE<| 6bVtOGBY[rʕJEIaNʼn99z,ZmQNқ2!a\ڢ0[W Bc 6ĭ=P*ˋ.fe]d$GBeI/2<ᶢ /$m8 6P(@ͤ"<+SMT{= 3~qbaR(:]B!@5b ۷ot:Db3"I {ݺu[no ѠOߜhh"KZIB+4哻N< 2aQfy"6?._%{qӆaI>=|p aytueô ~%S5ͺfX%uBu\aݬqG7DB"z}aaatttYO"##CQ<̙3Ǐwh(˕ -I*Z%R mTjH$*ZJ$5W3 vsiJ-%RX.BIBB5d2?j>%;h>n5=5$I^~W_SN9YۊPHMM߿?߿G$IQ5vؽ{8plǏwfB}`D %%ɴT%Ü [}!BAFu떐PVVf(B!]ㆇH!B-YS&`|BGWxG!zaB!B! F?!r/ BȽ`B!B! F?!r/ BȽ`B!B! F?!r/MY!*1 {ʕ*h4ٷL&Xp̾4E{zzѣk׮$?\/ d2;w.99Y,LdKc!Ԓa[3 .3 u ˴&09D |w $Gp'~ЦM7xc۶mkPBd27N"ƺ4K>}:Hh +ƅ^&L۷o|GHix-L&``H$cƌ9x````׮]=pBKV믃cO՚3IƴKGuaF1p"h2=== →p'ܽtdKc3"K_ZT`ɗk^˦lK$}feeiBF?)XPPӡCt{5&7zLYe%[7(qO\{7 FQѾ}{__sjNs[@X=7ۮiǶO~ ۇf3:SuAAA2˦c#GrS.\СP(d_F>OSB2L?M<-0=dlJ\>Iz k 1 jz=;:88ҥK={Dmu`QG˿9 27iޟǬz|`볿Az5d}d~tFӑ$) t⪋rh4UUU4MS(*RR*JVU*Z}sBwN|LV4jF֨ Ҩ֨ o8@&ɤ2ٌ~;[yf؍(deEUV7{#֜_6cwF֔2L&M\osZ#ZRTjT*UJTj4@EQ4MT*FꆫԖZ7ߝ܃ ֙1`g2F3QH>E)l91a96kZmymnJj\)h48Pca􃐛H$E$>$AʋLy:0ks<{6yÔ׽~YG$I0w.X6yؚ3'6N>O򏽆L9 (&I Hs[ƃt'H1L_ո;҄''Iz$`f|h/%W,[? di`0xEyhw>ecGZ-%/jBM m޹sGTr'TQ^/[:eR7>?,/Z9!vnw>Y:a,NND!MII4MK<X9{]]]6`m6a#𢢢?{}gy'..9ګ asss\RUUŭ !9k&Flr:  ٳxyh𸹪8! (7o?ڵkzvd2Yhhh=R|㷐zy{{w%((c{ w6qFPPPf=Ls%''☘޽{|^W*ΝKOOW(qqqaaaM %4qִgP-4Mzzz{T*vEZ viOX ~ ޑ$)$@  4ML&ק0##ׯ_?~q㜯`8tӧ~;6}(C qƺu]ۨMh1yh1A!T* hi[܅➰}]Ԃ}zjffqx<޵k;h4,\`04ϟ&0:00w/H5j[Ps=np~BA1, w֑իW322^|E>_XX٨y?&)77777wܸqY&},RƵk„ }Ý vvs?Oxw W{d |[ճtGʅ w/8j.]tر|gر<m۶uZ믿<Ϗjs@s^4r2rtQl5  ZҌFd4hcb7j} Ks4VO[k՗DN^u_ÆMJ/N'u&;SvUX.߰*&?߅OJx]fʚ{l7}Q}6LbiGgygGࠞ”ޝTJpH͵ 'W+Bɵk233'L nܸqر^$ݼyƂ>:I g2LsY9哔ۇxTl\b0F^'}ϟo7 Q_;v-=WF}w֞;igkή۳TG۶m۶mARklH:M&IIOSVN_rnڳ}{o[_/;-GWVO)=3e۞ Bk׮=ztĉ9~1c)… :t KiZ?M&w6eX7gvc@=vI&^g_tgϞ"Q}]2MhCh=#n04M`p&?0 3.&rhDF ?VV)^>g~͙,q3b-_҃On1j'n\9(\X+6gy?BShh/S:L8}WF `8c=*RR*JVU*ZRkjZg>3<*6FF֨jce2L&]u,oٌ_nV4juygZSGgl>ҨU*ʲ JUR*h0iWT`Щ4jju>}L&&~m6,e LR,jSfHcί)US IDAT_6X_6`ƛR:FsL]u~Ycd7?n sWaW >pnZV>D0 ؜^h}ǡvF}'o๙1A3igVچMXoz^3 0 { }d;,!LpZ?1Mje0Y. cV+ H$E7ob~ ϬI7)a[c^/!H WJ&v?xtDGMںw?9_6H3/W^`uI+٧j=jBk?yv3l=9Ÿ=$A$B@g_sd҂>㾡B$Iu{I$X>{кNw$P9z'<.1La З=oq)0^^^<LUDIطkqk- $v#߄)0NjftCHԟa{OJGdYӃMxa&sHR{k$ 5N7ZG]ef 3U1lvLٰPzв#JDŽV# غ`q9@A|m|.0wGtzw͹k2|i!CܷкwH07gIR4~`>:B?Fnދn#%,ZsQNON'TKuX ΢ tt0A!r2^uX?Q~M&^+?f"I jkW{z߰Ȩ@$ETx*"xY0>ۏ 9v*zڔO`Z*uta4Id} *#ww@QԽ{j/iAXѸS S0S&)Ի;7T؁$Iz ?}糎=2fT|x4? `o}}9<}]$Ij4B |.S>N*@yK;H ;=KGH|$/h+$Y;db /Cm5Iz*0XuDP(:[?nl%HݵMoD>ѵX1+IҨkГ@IsY<66h'@~g`B.TcLq=l{s󫫫7<p`{%c?>3z)c_3.8/ ]uf xXHHȏGRTĘys=#{ڟ+a0h4/iҴtpƃy)c@n ͞B) 닖﷡/2^( }'l/pE0`X-"Ջ7$z=z^AP^G:n+Ў)T?QN7~A)ѫ_}Six֑!cޑ bU5Bv^g&v3[aB.Ԩ臛̍~<_/0/_NII0aB``H$b署6.P(iZ(Rwwtw5jT.]_*nWj?HB Dj*ZoKThZ*vZB Bia7'ZTρpQ(P" jTkn6 DX!\d2`qS@k&X~#N:?^(K mh,*cFO?u100Й+MlMW*Xw6+˼O7k0>nW[Gl3p`XG?`qb'@aBe=ׇKl{L>6Xd'!3""СC{6mH$sKuz}DDgs8Zls=nMk8܀oc=ۂB qq̀r/\ޫ =ZZZ߻w}<8\csssw?tN:|'KhQyh5#_۷ [i!pow~ ]|^g:֭[YYYyyyO> qfި UWW߼yĉO 0`W9-95V[>&[:¸!\臋ly?67!yҥ0By]VYYw>A<OXX*?c.-95v3I\3{Yh{Bȭp6vX_}csSY ցi___OOPZ}=J~-BEQbÙԥٛиqsIi"/:/F!~l|T w!T* x<|f},yиq~L;eV&r]B9Xw|E X?w؈cp&acBm /{B'p32q;\OM [c z@~~ [@ɍV<֪=4x`?G!D?F8֣]S~ ?!{~jC Bȵ~lr@t MX v?AeBQXb~ 6}~W>F!&v2!\Ǿk[ھZ^|D1Azs=l2kPUc!j!~lm ߾U<0A觞 B! F?!r/ BȽ gA!\?H$E5wEPd0J… BfЫW={jt:]~~~]bBuI(6wEPjF?!+Z'zg=#jxk"F?!r/ I[;kʚeYk F-B_YYW*!Hɹ5>K+I9{X%8̩SgJLZ2s8ބ)X2u&+gWncU6 a/0yiT>^j÷]> *g  /+.tf訁E˧[޼ P^5U욟7|ûsg&=8CF8֤KxE񷇬;ar3RvˮSJr{ SOrpӓN1d{/_,ZKoSN=V>L?!e60ۆ,5/d s_:nj``1kzEH wp:'0g,cnONNHΜs 'L:}y,][= S~>>v\0;?:bp>Gi;M Bycl5x)HHQ5S+Zk.wRt`}AQGb9E]/$Bѫ`FQtӍ&@IGRtBޥ{1yex0~=''%M$8T&wUL\@ @Q ;+E W>{hU8>5Oc'^'J#%'{ectӗ$7}s6k9o<n\F^5ɝ^ɣX56kuIZ]vp9,!ȴi/RfVUTYyqr;Hf.oMUS6y{sK_5qZ+8ik_[Sv``vش{SY:XOuL|{zsy;,Xrf#W$_2?7^!3bߘS4Ǝ>??RπB^JfEQŽ&˲$IOZr_&pȑ;S*ik?%mZ׵\lj#UWhԵr0]駟ߟa0"I綌zxrG:[tF6;tփ!N243L`A! z9Xvۯ^L?!x"uM/l{L?!AJJʵB.!BB! ByL?!.~B!]0 BȻ`A!w~B[^.UV˱!BB! ByL?!.~B!]0 BȻ`A!wB!!BB! ByL?!.~B!]0 BȻ`A!wB!!BB! ByL?!.~B!]0 BȻ`A!wB!u "U=G@覀u?!.~B!] !B/Fev,[g}]b:T[[KQ )&)99t\B!:')))!!A&s|LAp&,.\aѶ/6~[W_+\jd|<7Pier6DAߨ5'F<.fCDz+? A!"{Z]4" +6_;k?[3O[mx;u/k}]t\a9$ 8a a  L8֠Zhx~nZR_Kkw;&/^c+)n3G-FHe0~B8k!>{a؜w 3==uT| `Ϗ2I촾1ɰ\O_zQ4gu/p CR2u p;qUE:8B嵻/d{ !@B!~AaWnN0LlVXd +^T Mq`7UU.W4BG4ZcJ1޾ޱ IDATDQS( {?+YPאds Ad}7u?b!J)C-_!Z 6N^x;)3ς _ٝ}W\-$!>(&#,|17-L~~#Go_x\l5} _^2,(22@@eƬ_ 9nbM$ۅ|CzVZ@)ӧϜs E!Z2^|wV<,'ϗ\3axBTTY0N˶Y{f; /jtF1͓~,U,FCP A\^4^#YܕG7{yZތ[YzbœOB⛿wcZò,˲ǏI(?0Dz 0 A!dZ~4 mߥkKnJY|AԲi31L)EDeuwGM)+fْC'ǥJ)L&|`{FRB?`{ck~xW0cǎ̌YEq*L?!\F?m2ϯK[4#Bxx $N68^hja꣣*x;Ecl͖NS׫Y9##t0${Bj{vjr-˲F1(ݚ8RWWAܵZ,5 ~.yTB^hx7$)0 q  ^Zd*..6L<ϻ'X]]j[Te6B0 jZ{mAk׮ 6f,zn?oxQwIy^oyDѺ:H\>ػ?FQ{)B<:!J>,֪=+drCa|Bdn½sf0Yi\kFk՞sa[ޱ7,ntՂ?ɭZ$&uٌ^BQ*12>zd2HFe DAe"ѠocF{vUf/hZjÇRڧO9s!~g!9X kӣG!A|NxCa+;Wvy(;uܹ2wϬelߚҩ;u?,˲,;~x$Rs,0 `BWhG/]TeUjF?MwʪIW2)jBMFiUӚjyt1 c2RE≂-YN 1vRVZ͎%NKR3Lץs[Ԧf}\#XMF'Ert*8;:i)K#<ϫчaбcfffFDD,ˊWEm>1([ױBԥA|_Tqkv_3">A}ttkAwPA;lc^,>fNȩZ8N^ѣGs 466JN?oL?!Эj{vjr-˲F1(#;q.$$DM? deeEX,\~B[!D55an~B =btmeRSS_[[^eY+~B!9NŋjoZt:Dž 0@뮖B8j:V?L?!꜔k/gq}xd`0 va(Bv`A!wB!~B!9/hlgP%˲j}g%6-??СCҔRɔ+BuNRRRBB^Ct=z᭷۷ŋCCC=OyyMrrr͛NhL?!BDR_|[m2v,={X7F 1co?qĶj0 B݌R*I=*;yVtʂ17qxh4]v;uAuf?~|$ DUV͚98(+VlFckaA!܅_C҆?`IWxdgvz3Emc}/t:EQdRyo6"""#cU% @$V1_!jJJމRz2,Y2rCFw( ˎ@ކ;sҕi#2yK$^/I,I$xR~~>4//ҥeY%I$N'Im~1 J֬EZ{vMn~4/eٳؔHH XazNc{7J $IPeY&H~zEQ$Y-_!E.}&f999kV|@޼>}dY噢bDB*@mH 1AEJ|yfLF.dڻW745l|eS2 r|Q;ᵫ ;@r(,˳g%+,ˤ阶!˰O;_#YVxrh|L?!e"@P 4Qྻ_]:/,-9Ps앹×@OvX>]ܙ3l'Vh :!@d Nبp$ĸ/-xZۅ#ZE=8mĕ"m/7.3 ##Cm̊={K6. si4'(3C~By\>{?쁩=gwm=v\Sq-(ߴ|LÃq[}nƚ(j= 2Td$2dl\2h7j1F=MRceFs0X/}oLy(#)um;)(9; BJ)@^j B rLp,rq{i[p`=E@Vmv9"|fx_t վI(R]]׀u3,!BAT! S)!4Q+ ߿>>:eDzyĦW2FN7gPӏ:!$p}, a0 rq°WO^trf%imɗș*]#:&)(~Z±lO .3hX^1eꐕ>vwTQQu#^Ȧ}o><@o2vKM6jj sSˆ.:y7ZATWB!dgg&>w\8ڪJO썍/W4BG4ZcJ1DQS( {?+wL,5`k*V c{jo<V)ٹ`g҈ҵs&uh1!D/B,Ϟ,Yw.[ +T {سw_N?uz@&1,X0wEj+ΕB[k5d9Nn}6-VCנ 55,Â"!q,T)?k Zf̺;..˻̑#GW7?֎l1۲Ap XV#yyaYjժÇO>s% C{=#rLp,ga _ۓ+~\O/ϗ0|z_3&Fc6'JDpPc@f8V_u?<25]VgUx<:l#:=`Dj.xxFijv,K?~Ç%IbXVQ~?!LZS5EaGVV=SV;Ls`M\k]~F.1L)EDeuwGM)+fْC'ǥJ)L&QCv(_6X= i3m);~ ygYeYaBCCǎ,+^UF!tNkk='!<<|]|zQU'Fu4}5Έ0ۯN_1kMf͗7j(--=z4q0Lcc$ItBuBVѣoXإJ*˵,Ơ sXXkqb Qӏ YYY1EQjkk-ZH=,B!ZCh4<HIԞ+ MMZd*..6L<ϻ'X]]j۬Bu?aZVu/\=tߵk׆ RRRf{2qY<|jjj;0 B7["*ҕSY JMM-..޿mm,]&)***...((%V0 BsNŋVuM5ߴzAt^Y㸐HY[qVt7bA!P礤tZa`0~հ#B! ByL?!.~B!]0 BȻ`A!wB! Buٜ5Vky:h~B6ItѢzEQwp|pCBBa񾾾zJLLLHHhgaNŭ;luUUZӢGFdj!jȑ#yyy!--mA~DQZGٹsg~~1c(dY޺lExG`^&YP6%KU?~wv!jN͛7lPP,˄ uEJiXX3<׿a￿bN/H68$@ *BZ]s汸O߽΁3]B!thܸqFQQïT?$4rM|||'N'399V$mU}}u}P PWծrB\UBX>mOU˴?wB/ R8˲\M?l-}AZA_i(12uÑZEQ$Iv?REQ(U|||W_566v]+R_ާ##kD"JIE2e*JTE?WןCH0=09>~oo)v_Io)n:WTd%pJSU4B1||Qi6BEQcǎDFFl}ԄAB6{_277G+4= UsȊHٳg``n,L/WͩT4:"*T S)H HD " R۞(HjO {?>/[#>y^=b熃EjЏ uZ䦽%j hE%(*ЪjYyB:|IDAT8HVTEe&$J)sd@3vjC]Eq,?\dI$ <ë$InEQ?$$$tߵ(6C "8pHJ KKL2PFSU)x7!8nvȑ{e0|A#ՍROcܧ]Mh5磅Ob~KO,Bċk&ss6|fP U _~aoࡿ\1 _x ׮x*v :l :ݏG PP!,r?lkhllޱwȻs&pfՎ|U&! è ˲,xLyȨQQw0 q>>>p\]SJv Zcr,2ϱ,`!,ð 2CKHB ~8iz{ӓٽ]ojt[iaCa1 DZ<,#D3zVvXB45Z3C(j9FQ$J5G~h ZY# a}r'=jsHc(Z= r?a^"pwB)P$J% gc+["B?OjbQò,!D7V0I/XL-0 ˲,L=z, @EeEK7Y&+Qrݕ*4ucq]sw&(0`NG7s0 xg N}@ !@Z@ȳOPDI9J#@#1J%?wKy/h:m]_Y()pTdt8Bȅeٺ+M^0 3~ O/yy *geg̭_\xy0. ÀdaW]0>$|wLtAT徯{0 cjjj:83˲ p`0Mʼ˭&Bߦg  s`#N98=Rs3:-ϸ*\} BAS@DV((SŲ_BBAp8u?lSOwCXSI#`Ϙ~:mD`kY_]][Գ*Xe3)\<%Ԑۗ3ymӑzd߾SeYNM?Fmk"If^VXchxV+p8FóxNsqcu\%MMA=6<;vYsasQ7 23C೧+rZ8Vó:Չu?j1#o\E*>ꥠ>K_]%>y4-P ~:ܶn}%sТ{;U(*+eh}Uah[G[B]w~6Ѯtò$IOj_w󛼥Htԩ‰'t:fEyRMEMiJ;H5vhzh%N{ua\sʲ,//_v'O,|ߝzhcHXw_!kZ$Y- aRkSP((*2uRoH߾}I>eXB,e˖ &hZuv BzDRwނm*N6mӦM{ `bCm WYvi ;)(TEؐ0Ǘ ~B禭tM͛7oذa:+w9EQLII'eY4`ʝvaݣ\SAGԚtZFxǺp]À)M[lyꩧԗo5|Y ,cǎ?SN8p`;nvҢ?888==?*f0RBԡo(=%zl\8xP9}֭[MW:`A[{53!B޽Go߾w}W 4hȐ!QQQDܕ曃:fA:uat:]tt4~lE5=RqGhϞZڗB(?wdGrpW׹^߼B2Ӭv\wuP~?~ûvE&&<ߧOGDD~jݻottS.Nkw%aAM.k}N:m65`C5z=k6q\``O^dY`z#aFj2zU_____o3!t:ѨhvQWa^J FzEԞIjt 7gݏ{.JϞѧ?W3B~ZU+\834x41A:Yٺu WFB{xVu@[4f'Nh5Q;0 -H2`B^dF[տ~Bpͤr"wi:.I˸l!m{.^дrR\R.Wqq~n|<6m.]r+ueB!/Բ{z O`Lf+\*\+B]e ~>|!M`iK-ӧO[{Fw4U;5K?*L?5KWahszAL?!tS -;50 Ӭr((ꑔRF˺^[u?-?۞/'zbAnӲ={m/f7jC-czV'#-B][,4@f.9 ESIENDB`workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Mode/Border_Drawing/Create_ROI/000077500000000000000000000000001255417355300272145ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Mode/Border_Drawing/Create_ROI/Border_ROI.png000066400000000000000000000210021255417355300316430ustar00rootroot00000000000000PNG  IHDR7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:gs1~Y,R!zT.\x1`"_~.\pynJbqݑĠ^n=1!߿?ƻmCt+YZ$N$3beYv[ZZC_PgddB©1'Np8= $fBH^^^LLL׽vqe !]VeQm6ۡCl6ۤITб?~444t ϟ텅1 #nll݉4eYeY$g2Bꇠf戈nnC Av]F=u}>($)Eʕ+z 8$/rVV322@ wf=ݬꟖaEQ+p,}%7lSnݺ511_N E1..N$ID0ru͆ M̈L|RKp~r,RнvKRDQ ev6C H~ϦOHRʷ~ gl~i.}4˞?0-W*렼OFrt:߀2=oCM9It<€zV  "}hrkԧO\}e咒)EQ5o3[s` WQAhxMq\ys*};vo߾j)4]qG;$Mj:@A2sLїL8'wnÕFBXwÃnKhX \a ZƧ_]&&z cM1`\Eo|y8ɢfDwʞ4wQFџUUUO=˲JEy vH`Z6"rCcXT}&e^_hz!vjLȾ1"Gͫ "U92u5Geezo CBXu\ 盶K.K>e߲/'}.a!/8sA~#$'duҍ{ Yl Y`Ih8.+B4vò ]mZ#;oF )nUQyuqɏiNl{‰+ VPd]dOӡܙ╻&^4o=3 ++4U _M-hm2_ }yXkZЪ<*aol>u]@AzsƤ'y^J0q#MO}\!%*;Sѵe(AdYVt߹t7n\dLR?Wgb,3@NGr\@cm@I@ҔW<uGvcO#ɇ׮)iZڀ5s ,ӓ:}L0 E+H^َsF• `V]遊aJY'nF\mZZ=m?NMv^-+ν쩮x:J'9~ ?J-+-fd>{CO@dՖ6;%SCdVM^=X!>(nH]|G0,0-/u?cq.gCBԿ39˲4&͘eY㺺j-D$f8xޛ2hV_ ˃n~nΥ|&&uly|#%^u׬{ߙVQngL4E[ c3(mґK8+CX*'^1zaS6},LaZQdw"͛wItJJny>#:9&)Ut0ڮ6[my HJ~Xo08{V[e2y#rҥ.3:H;ښkNg7gkO_umڠuw* !fCyeQꁥyn%nTl5w?l౟a*' !,r8rj6q<`n[[[KKK5I/""8DU@fn[]dݺu#GTY̱c;tzASxEĻEXzIvBv ZV 5$ |$>prFC1t۷oǴ;!D9&B劈EVO0Zé Create ROI Create ROI
Create ROI creates a region of interest map from the vertices within or outside a border. Currently, one can only create an ROI map containing a single region of interest.

In Border Mode, select ROI from the pulldown displayed at the left bottom of the Toolbar. Create ROI is only active on surface structures in Montage, All, or Surface views. (wb_command also has operations for creating ROIs both on the surface and in volumes).

  • Any Borders used to Create an ROI must be displayed. If they are off, toggle Display Borders on in the Features Toolbox Borders tab, specific borders can be toggled on/off in Borders: Selection.
  • When a displayed border is clicked, the Create Region of Interest box will pop up for creating and saving an ROI consisting of all the vertices within the selected border. An ROI can be saved as a Dense Label or Label, with a label name and color, or as a Dense Scalar or Metric, with a numeric map value.
  • Create ROI assumes that a border you choose to create an ROI from is a closed border. Border points are moved to the vertices nearest to them and these vertices are NOT considered to be part of the ROI to be created inside the border. Note: If an open border is selected, the program will automatically "close" the border by drawing a line from the first point in the border to the last point in the border.
  • Fields of the Create Region of Interest box are automatically filled to create label or scalar/metric ROIs with loaded file and map names. Use the New buttons to create new Maps/Files. If you do not change the File and Map names, the Map listed will be overwritten, redrawn with the one new ROI for the selected structure. 
  • Invert Selected Nodes option selects all vertices WITHOUT (not inside) the selected border to be part of the ROI. In this case, vertices nearest the border points are included in the inverted nodes.
  • When you are done creating ROIs, remember to save your changes using Save/Manage Files.


Create_Region_of_Interest.png000066400000000000000000001403661255417355300347240ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Mode/Border_Drawing/Create_ROIPNG  IHDR*9/ 7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:PK~.%p< E}s~XcVVVPPPXX]dYq<dY`0T3?RɋCu*-z'dn!dShHN|gxH;>̍o D#a^LըWQ]9IBBB6BT3Bq1A233K-:׋ l1)~L3Y={C,9None`8PI[]/Ϝ>~i+>|j3G`Yǒ̟+ 5fp+?' *vj]ֽh##L3cfP8l3̈́|))emcPO1S5/ߘ~NF_hAzr(#Oq$v`SGEF'(Ɓ=y+s?tIX:ZV^~&-o/Rz3iy Cj4QJo{kۏj>t_,ycwzҷ3'zkM:x+*[ K[f2v-86 z¾ NW5HOSΎ[}?<,3gPw@\tQiN}Ƥ_]G4~x?~FډB)%|ZjUB)ŵOn-?}AaK|mD˿=F>1ljV?} M A)P*MVMg Ji]#,Ƶ7>5y7:י]o,n[ZWp Q&7g/RD]ɰ4ٔuLhB]&[O8jRfoZܤ7$^y٭ȒHgz-%4 jA.%%9G5tyoX P{!#vZXsZn=?KٙymjaD֩] Z;:9~NYl>Zw=QC'4m绽޼U.۵?3(Xᚭ#㝷N0L0%O`Շ z o߯Q‚UڗRC-ʳ:<;B>ѨbϺ:WfnWN^/̼K+YTQ7`ԼQuӋW$( ec4 ۨ;Z*g cJEYkY";k^`yOQn*oX)7sa ʼnO4Wb4ڵ_)m?h~8%2(JHiC#F@SJq0C-Xd&jeÙ.mRq`0t:Ţ8Pb9Nf0$I~?5xJLW8@],KL{ʏ{^3~K>]fq?w% < ~FۿO{jژͪѸ%IQ/>bKWGv~IOQύ0zQE|a^3w5NI.~ai<(Rznϧl?s' <߮,3/l-6>_yQLQBo'pʰ1@́6tR9'ſUJ^L7{=]˓'Pnkl9n!oU+qe䅗6tBt!ze}6|6=Z5o4s2ΞCJhh^^vcqzcfY,n͊i 4M0pҳ!y@Bƒ-!?_Ƚz)kɼn/'Z"CȂɼnFl2cx)'#_ dPy` r<,&}bN;brB+ 坿MKq*C!ֈ?1܎<{WBd{\ pa| O]bsS@4x_`Yo8R1\Y֛#jgUuB~ | hE3<圌k:N&(0IPyATުMJ: &Qno54_ިRJՇX,$\tH>"h7EQ<Rop'khWt/1>ԨMagܥ;hhhR(:gCn=s ̊.;]C%(y&9rk446<Rݢԍf%Ux(#̝c; ;ݻn?u۠C;q=@ ?-hhhhh𣡡q=@ ?-hhhhh𣡡q=@ ?-hhhhh𣡡qu3$ydnI2x ?=?8OYQOSq)6uILS444:G"@#d 2!),C3<%!g;̴GCC/@H1 nX?=D - 2L)=_:lΑ'tMtM/U?0_1b@v/jlr5_Iϗ2ӯչQ ~"m(78M<# Sg^^MebW$Kسӣw[>ؙL&0r5:+wOr;e撘[M$R61Mr5&^9˱ͪ k}{GSN-+cs=}>8,(Z|)$ 9q-Qܲo\/)_kZԺ:h5oY<52P)zdeXu^`) :^77̛$ʔye U' 8gvb{h?tnBH!c_{Nv׾M**QJ m'n rʑ+u aq@ei5!o.@Ͽm"w~}Ţq}.$Hߖq9)x;BK0rrJrZNyiOҊM_P:+IɗT9.ڸ"+kE.KD ܽkQ! $]?|`֯[vݰu)9%P\vRd+{>66]b̾c^+tOXdLҗd @O+FǴ@ްW$5K|k @"y @l/pbm\˟[K{g8s\^ 'c?oQFm熁Rp\ϘptϞlWc:23AJz=Gw;z,.;_]M(G=S z=eʲ oiOJWO!`QeTvlPi;cLaHQl{v<;d;s1ѯrdhJC\𲑽s8H:0cU/Y6oPV ]3πkY);95[+kD:֣Yqqf &صoTmC Fxjq,<_<:#Ke\.)rI'z73NV 't3jHϧgmZk y&/N[DHZ9u%=`B-[=y_ }jĿ*>=&O>ưQ~ܔ:3941䑭'L~"au_ j5Ͱ%wFm.?' 1g8!a3yFK;k{NhK*މ_4!1)Wq]uɯmI>9kauN<:^"~LZv٫< HK=mG%d9e!w[j| ɌBa))s<= @ʂMb1=%A W,]=%i[}k p\M[^s]& 05چXv>SzV&xC)eY@]nmFz^uzލnO/vGMCGg<5L]'-^7LZ_ubH]2w#W[tO虱vKzk- os`rdӷ'/hx'cfazZ ѓs r< VDrҮ$oX'CC~Ƅi}kA=pnZEI^_`tIadI=3ӶݟLu?Gt-y>Vus`F"r/M~u{?hKv I6{^B̯yIrm;P-G zz~V^^oQ=DžOOH'%j7u3? -hC׸Gw׽´n?,^YY$oYA5WW QBr"cAa f?\ 1M5ʸ᭗ň[f=!fζZ # 5t O.]PR)CX8q#1&қ g} R86 ;7G@Y0ٹל?_r5_i 0k}?LE#ȹVMߊ1TD۰9Ң5j.hZiјA IigbfeV 76& ^2nTHzTFjp_K'/_A 5_q(&fhEq?k뵭@tԂ "}A1N4vD;h>~h՝MF3G ^s0Vd@c(1FE NK>ܝc9{^mޱl|"UFgƏGM;1xy1{wep͜!XS<ʐq+NlCOwgWxx́O)sZy@\Ȟ <Dszི3ʩ<h1'hz5Oxؤ`聓Ohtj|T@3?(߂^~ گ/8Ϙ}ۇ e{|X]s=!#fX߶N*u:.m "1~mTRIۮ==7xpqp5Iʵ\Ǟu1e30OL:x`` 5p8^4?>H>kL~o'm2o13>ߛiϽg@Gmn u 7x~Ltk]@RgoHAߪK-n> EQs.?s…G'Xⲷ >>'2^œ̕ z< gڵ.djDF?A},0OdN>hG,#)g/={3'Of9m,vAx.sKֿ}m,u~~ 6ב\xEޠ gyN0^{; :ޠ : <6YCJHzd - pGK}.*Dpd-0ϒU sCfj^5vʬW6,^ TGM~\0楩Zhm@ѯ¼g{t;bG?45_b`4A D9pcA ~FSۍc=iZgE蚿xIO}{N/ǺWS~hE@K1jE;*v06.-2[HD;U;w@XuZAV~h4V/jsM&Q6`dZcyhcph0W??zmZӇ4nfOn^Nu2:nnHW[$Dw$Y~WZ}&8龺Q'!@S 4~bxhȩc^^ 444ʏ]&ߘʗΦf" H(2ԉU0nO۴ig[_mV}{4]azyd05D 1>Zޓp BpK,04RJqGCc}Қ4by7`&ڮo?93yW΃Fb (::g,=cVN@lMi 7n'+m꧿}k ^Jm=bSlXMtMG!z7KrÅ=00ɔeHFr ּ3SТE2@1qs݁Yqv tmc, 7! mvb|m辟ǿ+u(vȾD䤔v}[F;thWf>j{DZm9?n}l NW5]oCw+̣0B\;H~: έ_һa>S(sJTD ,fW/m@3/)ďnGg$v sTrI'z73NV 't3lL3VcƆq_nvя j0۱q]naP.ϱOx:0agB<^ q}{|ξ5n&!Mfŝ/~t,,^(}ǿ```ȗ@(2<7rQ<=1Ԛ}hwvi;D6hi5h8p#>7屪E傊բ`?`/w Y @#ͪnk`u%WT {ʊ 05چX f'v*pؔ2YD@@жiU)H p owO/=}tSc+I=rU#bcQX)=;.Kz.׶lQ6 80nPb/^n۵WJ  yxu:/5s믝ҷ,g@Ծͭu^džϨX!28?V:lNJ^ 隮wPW |MȍtS͠0RڽT;qu RI.&M.-ŷR.]IE ٥C;.(x36xpZp2:E:O i>c*pؐ!5Ꮿv@asEm0 ԯ#Ш]4k`mw Ow3?Gb-=`d~FґsUj߶)p%y:z7l6Pj=@5]5OQF87v}Be!(hS!,wTb+b/ub|&u,nV`[$ l)׏>Nu/ŅwQܾ ! Q%L,Ѥ<UVP#@ W z4$t깤 >f-_7?#q60,:BG!%G& P hkq(pHkƊ Pc12ƨ^?ϙV|x|o4` GJBգwL{_Nt6_1x;X<>s{Muz*N5m){Ukj5 Qlp)eEKVqjG2Οv։ 1WJwpq99<<=e3 ^Uqڷ^ < ơW|)zό9_U;2/^y1`Z|bRDʷ⁁bx\}@Rg?LZfkcߵMlO?w[;ؐ]_|̚'7<=T&T٠`H$RLK>E2/qU  0SRxi/ok)(wU zh`cRP0Y1ηrmϴepPLaZJfnwj-eqnɂbCma(ʣv,,eܲp6IcGX,(rw} `Lm݂1/K^ۚCt\lO7bG_=mi1&5e[ث%8ȼu~j_lwuӎ>k˗0.7:[=O}Bη!"[%pve]#dשӢ ok)}bt.r\ȿ?𖣔R2GG*n9Z&sɚ5kt"2 C:d.A /=\A=\5߬%/ *b0[֣隮wJk_+X󄀨t @<.ܑ/e`*nۅw{0σ/@QYu:^/چk!| Ru`ӍFˠ `Zn^^YL<ǁul @{)ٹ&- BdJMM^N3Eלjhhh@@ Rj(PEBs:&S ~ʎ(9s&22R=MD([pOQ)+w{SRs:Qd~ >eN8cլYSݴQ.*AEV? 1IN?۬Ç?O[\PJϜ9aÆWRJus444)pޫWU>0/Z)1I^jٴq{pWDQ|0hv`IqA;{q;?a:U>.x<YQxY+)_Z% %t;x[ ?@QdۧOժihh ȫ <[1=d^d[/C^vŻ :q>3]MnLW 㐢(RO98r4p?'B9p +~(D0ӌ[}v&_^utGÏlmq&U`TSȑ#u֕D=PC1CQ隮^᤹nsqGH B˜[F.W5o P@zr }wʼny_ tfU>ebW$Kسӣw;վHo;ÊRvh_ |~#y: ~{Z봨(u.lhh/l稣A2̟sj7j-Ki7kP͇a L:eeLxǼE`ޡ2[bglRZ"HS5jJt㩝 DfZꮕkӜ+GZ"d-k@A <@+b (z"$EeY}ߺM/tm&˲,ojչԲ׳q7J{8kY+F>|VRd~ךLfd25{'$Ev_Б#G5jȑ#zoON{>d{|:mO<է ]ۦkn7Mf6L&u謟/U=pU&>lO޵T[Ymޭ|o?{*al쾺gK)VΩC mS?~뱟^ߵk#6Y.uS8lzs2ϟfjq)R2Gyxxdᖱ|Ѽ+?\8KU< #3< sːN@-KhFY6^%k{!|.\u܁&aɲn}€5%kmFKSR2R/%'%zS?4z!]ۅߎ\W>P쿏hF IDAT>y͟Lױ;Tq?8?iElnNM@섵?Ldۧ MtoEzS>X| 葕RmFBo~CW2 ͹l<MWv;=gIlwsCrw|$z,gb8UN_뷷Ɨ U0lLULEDu肳,YbIoONlrz(opTmFT?E3S)_7cLUQS}VGaS ;z`_'ع]z7l'a eNʔzAb!<*jrlُ5ƘE#lRSϓJ+$L7F[oj(/?I㕊texe.0SeS{ů=a͒1,q6E;׾&;xpS2TVӚn޻9?nzmgLmR_-AlB* V}-R1]-SJ^1o~Y9|H:uÕ(_+a~8$L?8|:sBx2h/O<PdEVG&%U *<>/* C9 QUOoU PB@ UC6hZ;A MR-6N̕:l[{"o^37 GÐRݠn){ŗcG~yffffsRɨ+b']]}&pnjjݰb9W@;bN Vkaa(7$t jxyArpEd2-AOtp\)TQ9). 9(dJd2L+fivdEQ*TLebSԲ}A8xbMxVMcn8yS|{T|Dew/ҍJHiķI! (F=nj%m/oBI}S* |Us/_s" df-ʬ#]CB z~cBTʕ+">vT`ˀlKer($6ǝv6Qn&E"9 o ?"( fQzIʹ=MSYE9r(܋MOscL+W6b>'n+?ˆwsoBjŕ%r~nE%)٧hm,vi7:MPPPPЀ۳| Z~76e4yY͢b{v{G־=/*pW J^kU̢/ʗGN\lb~ekhch^w2>s".un!2C9\Nxޏ鋇\ Krxdk v+p\!ŦCa2pGQU ,Kk~R6núuM3.۲y/0׮m"TI]9>uez5ԴXǮէ*r=+QѳvzT? nׁ E$;5l\9yr`Ix-}^ǻIK9S%J]iK n+ގCR0'#uԎ˶ ?;fN4\1tyXv5KV%h8y8 €7jͧvG9"@n/&O"I Yv_5bN8>L̓y@Q$IJ$IgH3*o7[Ɯjڌu㞮RdKo|̵}$&'쩓b@l kK]RKۛF.y]d~uyqP>9srJ¥kb˲,%.xT~O8cV`RI;484}1y' tvwkFo3Y:nΒyLf l _]=;Y2WL"LDV_I]+CL3FmCMjdG~E>˶(DI8EW(@)(*Bi])/] d{~Ԓئuz oF籦s+_Z¤psB$;;7oӒWV9G\ B E?G ?|D&vdߕ)&ǟa ]:`hMs)M|%J mn9TmN]/@!  @7Oh@5(ڠob(^Q _}{ (ZXѬjȪޤYP| 6?Э@Gn?s+֦;oW^ ?5[/{> Ju05ū;gLC%̓e'qɋrm͎@u).,<-játGk;kr/9Q(]֊3:;^U~&6~JhI##X_5h+lf o?KTa:y=E΃٫L|Č}jVե l/YnSJִE(8-ؤ^|£ۢl/YvlkwC|Z~)h4eg!t$*^ZhǁK>T߯^a0MT0 9VWV3\"6>vܙ$w/6_xLRؐIǯ,v|UZP?:@ظm%p?Ŏptx cզB S/  !! Gx )8sVُq5XN厔C+đ 0 qxe-#^U޻ ע‘|sT!Yp1fSqc^/a#!04gwַ6]!kغoH8gF޽ n1} O׼@ԯ(@Q/}'\pe[=c;hxgn:c=QV2ho*p G^ e 4,gͥ8ب"4ꜥK!Lq5Wo]`T$8=W/Oo=5cͿ .8RJ)J)U8_,fHTv$-D?{C {F׫n`rJj ^盛4oea]G~lܼxE;)iɛJ>7#/m0u`^%}SU~}#zD!,ĩķnXp!_r)cU `7/̏eYBXaa2 ˲Z* SҮ_ϑEft{W yN^~||XeXj ,ּ6-x7.ywU1k-CXe}'IK߳INғs$91+{S,]q Vs^ZESkWʗ]c築,2-uZxa{ bp]%T~,qՋ_o\>yÓmqR @gti iG\2]E2ݣo/XR~딸́ׯi,z/?U/B3_~ugN,aꪣ`\'+>T^<9^x֋g\0 Z;e @ :^qcuH08cY+eHpOmY?b٥ݒ@'l "8eYy㸆>\q.lξx9}q_ʵ,7.hXDK,jւ'?(R c|buvV|";&$"Coe 2p<*{_'>g^ޡ/O EFBz73j=sMesFeWo޼y3+}-~^\]oɹ}_0ɾmoܾ YfQHhlY6J) :֭_ !޵{Doj_'b(0td7hx!)A雬w}~ cۑs227;nW׋>.j`w?wf@@O'|`Z3xnًxJi@>?'ļ>YeiVǘcRL^U< yR_9aFF, r\|o"rDB|;Py@o01>Em?-kapQ}1{+ɌM]N2ヾ#:mvf䨊~:sv]a]?sS/~w[ HR$n "hS[-Dܬ Y㪈:>c ^K(<&xe'Go֞vP]tnݺZrw淹ѝ`]n'Y [VzzY4[DG_N`2g 0坆d-OdVc_h7ȕm,Z-"xȗS=elQ}\>(Bl9czDR * vh||| ^$1Yj b6/j%A4E7XDY\Y>O# ɋe qNEq7MT@V&Nqw *h7n8x`mϷ- Y5@r(W_}9nICOyyyeOtUzcp:{k]l, tԀג+/S^7нVlo}ggz=B H^`Cr^WŗXqێײŜ!OZE_|k2d7'58{f4p=<ïcd0 V7mk T@ aCk?sN~61-OyX(rƍˊLd)ZrB=ew |J$Vb%m78;@*2^u}R.^AkꝜynLYA=CQIeYFO=̽ {Z5r{~ s= ݟo]W4 Li~s#>͘f!JU7v.wyy5eӺ:ӽ? ??էU%ܾ;Q,Iгk-03NnuH/尧Ș_/="3Ùƴ .O?mS9y >EIuGo lGPRm h,PX~rɜ>TQY PXX_,s?쎻ULJ-#x8#e>Lb=G)e9,,˲((*Je!M;IK.K|ej')Noܕc>2کEI?B*&-_VT [ Wa>(D$_ޖ󫍇zpW;-G{HDr( aj<{ziv3ہbXrRCUč4a/{EVxÝ6+^~IIl=hv~َ:'[Kv9eZ5f[izʗn3cocؖv˅!!UBBBVf2.A IDAT'V pS7lYv/۱hP~ep=~p9}s+;^Ăw}|Xx;mʍ~l{e% m=7Q6U*ɪ]Vm22=ÑۓI~ejW]vJ2-TY%'rX'v}2O3CK/h?̟exQcsl1~'@ǒu 2qےmki?5׎}:nSj>zڢcXX,Ex=^3fhJT]=F# /B'uzK J)dQ w?o-'}l{kMGauފ2$k{8}U٩R( 6\B~ 7ܖK FQ{(PETWUQZ;qdEEQUNaT?Æ܇1GM܄ܕ~ ݭK1hx3M,Fz=F 0vʧ;vUՕ/fJ#!ơUt]OXS") D0$U @d6$Y?vܦu?yC~k>ԯYv_TG_u{YEPﺜ>ȏ^;qg]N@UԲ2z0ZXELCS&oU PB@ UC6hZ;A MR-6NKXX"b/˚ Gg kB3JWfgL1>ǣ2{>"M Lq%}bD~.悀)Gϝ;hIdK/Fusm),sзVyj( wvOOG:<AD$ -&nYZvϩewL\ZP̲9YIqʕ+L÷l[}CBd2">u?'mYF"▥a k2%Q0(hb Bچ ii=[pG `w MO8TreSTBMKIyG# %-$PfA UjM. A?+9;ʂ B@#sA$#?d u4PQ'a m釽Nާz }SwEA$RQRZEA-::%#ީ,b1cӵܺr$ߧ<cX3MPP9 +9 Z/Lm:0ix;)!m6c7j~oײ&djle5b7EQ4sArҖTRvnm;6abqs51&d Y7fFl۝}> ohnW,*fQg'}yk1o wEV,+|~4:(E py릟s8!D:)?2i f5Dev8eGJ[ܥ?\^xg#7M&sJ[ƣk94vEl5 `ԉskr@ۆXydj.j_q!DU6Ʋ, h۪n4 /KC_^?\!/3>jപG;Wq_T n}jg@vMuҽ|o۬*aJ]=ue0el)˗E5n]ˣgo{>+&* HvUH$g}wzVwfw8apxti谹1T ۳22|۴%)ųۺvo!ԕᄎ2tdn1s.o6(Q_v%hЎmyKϘ[:nxQ1߮zpKD^ K|~Yn;O]1`%ƭ^Q߽z2x[K ~.^ZKoM _nۣ;lKY/:kK?ފB]ulz+n'TQy9 ΧJUG(PHvIK[7i0V}2xn"c3ײ3}tN*O5;mmr"2 NPpbcIsIo4ڳi?V:ԕSW&mY{ 6v::^3RƭާoܒiݫМ|~{+WY꿲RU8ZV߄o9մ#+KVGOd$//G贤.Q풝l>eL*߳qE. .P JJPZhWe KWT@mAC_7oL^ݢfacힽbM+By ~E~ǴKRzfOݷ0}w{\]lrh947z@XX|C( [^~ n/O`=%@Ž/N[2gC2)<8]u ~WYXtDR>KZ_xl[Ԇ#3S^oN_2'ˇ{/9 5h$N{ja?*\X53X^y& X-uj7L;MU9PD u+KCۅ93Sq^]jU YvhbТm(6a't}Wh6 APTm,WiҼy}J)lEo{{uJQyԖI{XwG@!t$*^Zh JK ƐQڕ<h B]s{^ ?i8}nm4cL+UCx)}Dsba}/=k1› q.]/598-@VmRvNJN 0 NS`ەf޼?2Ǿ!?OoG:7-,;H~+ mKj(=r>iڜ(.̞ax^6uJ7E6kެ GqΉ/bL^rut0w3R'mݾ1(? 3mܶg46/i-Vq!C,J[5Xxt[ԆCYk7r6eXSI_ }Tϵě-3+waq,z# ne(+d`(U)@sT~ƽF80Š'ɋ߿׭A%^e6gCĬ ,njmA첾a>`0D |z}/>=|%&, sqBY)gӡ5Oѧ^kSuQ {Z \ο0 RԨ |/0+9tZr U[5a-5mζOF?WًF Jamao7!8p gLcLUCCkL^hڕ_~@ XN+8g! ?㋄щ2 tu* rֲ0nGx>!]J $[W30X!O]}gs;H=(!eM (1zaTBXB%?SV#go_z[!GqOƊP.91C^ntFzh kBﲼܓ+br3VN8Պ{ń7@G_}(1a#<䡍ܳFl5|Xخ8qLsR@ZW0lI`ޜ} zЙR8YYaHJ>N*0 C-"^ CT8~O$Ҳ#ek(Jh}B1s,B8c/_z-|B H ɩc=BJ]p"J)P@QYU^yqcYDHpQZ:!={+6 9Z!^Dn<]6Z)^+(Ss9~yC$ ޫ[ ~nNO6,ڏH?sylcN'+/:7a8], $'0;ϲ,;4,@dq1下!\y‚{9y @OT dܺGIUҢߦY'S^h ,l*:լ~M.p˯e )r"1kЖ]CMđgkh-͢wOߧq˲\)thJo0nM|͒{3}ҙ%(R͖iGԯ2 .*C98/R'-u:[?O؃7h7pE(/&}4 خoqYlڒ-e9{Ac`O;v365SgN/EsvYnl YoX1a+P`q磝eyoꄭ?k18wtlFwuRfyEh,)g( 󳎭s,Dz:dz>.see:m+Wm"˷wLHHŘZ~֭YeeYxUnN))_PdxwJ9]F8V NAo?D|VVF3}4_7oh֔=ζq̤'!tVPoF 5_g-={v--m?˲U ?eeRؖg`QļRz_NZL,k-+tpFLChoC]v\ u|{{ރ,pHG>"&~U,rcwc#'O:"1W[Λ:Ż|3Џ8v@TS l֟8}t:JJ_# f]z+8աyݥs˩ˇbcFh qg}|Ɔ-|ss`߆x#:-n4+6e7qWko\҉="&,L0O^2s6Zy7kul0!_,<$TўOZ~t}9s6r'ϏU;P\v#bNb\cN˚NԵB/@/Ŀ`0Tq<(%:Gtˠn1ړ,70,^Jщcͯ(ou X9y}w'{v_ #eaԁ |i;0[Ki` ̺kΖw<_3~vxw^ߣWx 7t紁|ז44x잯sXf܏娪2[z ?Ci Eyk tot/ _J@Wy D@rss4NhnR+qeNd-@)˻tRݺu}}}8T{֜ AVEh,&Di7հGzno e"Q. ]h2}f 79/Z̢y-=N^-` }Pb7zEh5d`\l|1@ρR jF1X1z U6.R=?m b+{ќoS.(8 һl)V`1R1?DYW(Ioݻ.lͷ.=ouBf g aklv*nXz[^# l=˒9$q7MT@V&Nqw *hҚ6m]wJgњ;_>}os\ V{ 6>]t^^^l~(W3Pd>X&]܏WHWE7Kk:Kz7z?=.^Gl|`,W 7ڠ"yr~G?zyóϖW+6"i g]&޶9Z`ye>8TL2&Lz?NK%ڋ[BzGz18ΗN`08xKgy3Foa0`0LV J P)C=cټ|o+P,;%fh07sJ)$ j>tDB322j׮ J?׮\C"/ΌIʣS =3 "oK?cîh- }Bn~~`i_\?o PձM=ENdu,twNy{݀p傿7 xW%33ZjO&hܹw9tq=xJ+lX?emY!MƱx;֞U,2Q5JO wƽVfw\y~,viŊ/^ܹsݺu^<+N=CI?|RϽ>rHeIRZN wdFcI,=xjjffm۲%<a5kp{2(hJm6=`K'm?uKoM_{K})nw._=jyQ6F~"Oݿ=;hUTZ(J8/NeYwF^7?6ٙddo]⭘!zWgoYlw?%\1I'm7^rʦt5IDz2}N[*O^ZwgoIt6X.n2Ɵ/Jq,kXv(*"*QٸN!ã_fujƳgxQ6. qOuBѠQU=d.C]PYvY>kk歵SvER Ԯ6Z%bSʜyڰ{4Үm7_}ڵk eOD&ıu} E2N'q v'4ЙQߎl @#Ӈ3eҙO2̳k]{ьη=@{6~sqxnMג{䋸#_3c h ^3{m|02mG ?<Pe@UDAۧMDJ)!n c?EBnljs8Jqi7PPPHPLU(Je\A3EA6DV;'#2*j{W- Z؝0/>Wf8>,Z°IqNiW:˧6+akA\;fen6  \Fr)`S<"X-PiX}ƒJFT*HDB ^9.dvޯ}%ggw$~g3ʗ>n3 ,z98B9'Z_eJv> <VU8Gl~۹m?k1B`&tc` c.2PܨLǀbGk/,ͱϸC֝Uf?w,u۾Q9;$ZX#`yێmvۛrX `3.q)pV)=O7P~BɎrÊϐ~_*BއS;d+Y4nvn)"U$JI=%Pyc0@]g&s?zHzŨnqd} $&4PwB| \5ҶKEVT* ]稭R {13JW6%UҭTsvWNtwL'*:)s?RP^kmPkvUr{nKn9ȫA خm$C,Xd2{W;wmv?۹v$(H8 휒67 Aי]ա" D$?oFzkOz4%V|wvݲO+#s5U@e9ӆǭ`_K3W`ļ^yF,`Y &@#2Wo>lO`JXذDoq r 2 b`6>ax,9c֬7M-EQQ  Kcj%I&*q9٬( p;s, ,PbYF[{YacLס3$-(8xwkw5wԡiq)>_>YKOq_>.öUÉ8+áXEq8<5P%iȚsp Ovvzx63s;-&l4 @|p;7OMdb``Dg:4 Ucg:"p-%:1tlX"mО7lwh,&)^W nvn ZJA)(!0rQQ,b1tF&pwkD1 ҅D ~ \~Z!b8pW^,+9Ibe11FoP\LڣitA beh.j .?G$MӪp8M玈<'T ۛkt T*= 4(^].;A[neҥƍꪫ9O DRIO=|ukN*>}zSJ(6d 7*_m_̷t]*** É4))i„ )));s%qi3߸\1_SSp8q84>>SN$] 1v8)9?'Qp8+ p8Np8m\~8pp8Np8m\~8pp8Np8m\~8pp8Np8mx̙?ɓ=A1֡Co9>> 7p8?U 6 5fOai/|})@|-p8Tuoku$\V2#w'<'Xߍ6lS|RL}쬼}UU(<Ѝ)O/ H03+ CM&tUU5] (ҬO/ݰaIfnVTn}uE0pʹe,wKgΖlpyXRrf-le,yglO6(/ a~G"v[5b6j@^62'Xy-p_e5lQSN6[Ϋkˀ5_YϕMN֖ Vh˨S'[f/9o:YYG8Ώ j6].媏r].%TyiޤIY 4*) AU[%}@lC-\VqyZ]0/wmF59u릎^5mt.0o۟\^~~z~̅-ZoE?ًW- R´1jflۻw fdO^ZS:=kFe_bpK_YjEE:p8v5LA(R!*Pb24lrl^1A~>n$N@%;WMbL/Am=ҁ:rb cK0d$z4h=aB{ L,-u~a9g|`f1\w;/XwM>0sߧHN963I˸u# ʈwӛ@u f'ϰK7/FE{X<lr^;ORRƸ,[^_rm)I)c_$;8 *='<֌L{b3.EAEA- 1Z~p9;@!0''k+I&XuwPaP8cpaB9CA49i ~3ܬd{D@1A6sX\ 6}@<'9 N\l\ };.l:va]53cR[2bda#'w2G'Ԁ=W88rdS8#g+=(@G($I$H) )P(ZU ÉCW"v&H ,<7N{[MI4Ui `3"Q 8˜ܮ!Kz):'9_O ;jfs $,'ڼb {v$ }j$(FQZ I`LSj䎙rK8@f,ˊ(b2 &ɰ?-;WFM]XM Ђ V9+"E5 -uzڰ|iZY 5}L oSkNGU ƅ/Q娭ٱys'rl,H>`EΓ+jNGM*=fzEi #ǍCuPR̜jWīg .\]]ɟ* 8|Յj=q=zL1p86~>OňPJEQzǏonW# ?ܖHΘJт#-cx/<<(/ Ƥx,DY^nvFZ=Ҳgl> 乕dޣGYjE, &=qܬҳ&vJi.ΛЫ;PLܻnIBi=zJ k5ۂesX`Xn,˸ fd5cLn̂4ɷd5!##Uܚ;Ii=̷=4fp8N;̞={Ĉsתܹs֭gnEq:^eLeKjD?3Z1tU5KwmΨ/j8N8p{ou]eY4ba`ZXȩP9dj~UUpLƘ `2u_Zu۽}ϩN:@:|~f,(Aӡ'ް@{ĺ7wASl\XE+~ƭUbp5ؿ"PB(eƀk ]CI`q^ @#k=8zֈB _?o>QgpQSŗ?}0/'HG6V@ t4Ơ8) )IDAT3h:ae&|s\.1ix^k|q6~  FH!3-SoLg_s=mvO}7QB)QO)S>WnٺdA>J?}mWc7浍S)>N HE#  [{J - JEQzkƘQ(bj(H T/RRI K !6Nt@|+=0:&9?T $PA@$$jY$QQ&Ey?w9#1i{7kip9eΏB) ش1KlθGp|=5~(2i: Ҙ½g|C,*}j2d7. ׼P8Rp8Z ѤJVY@)Ԩ:P+|>&k’jA}L p%.4FUֵiLgL׉3BYwf\q8N"˲ ~lJh,.hIW }{g9㟭[՝9s|?usU@p&R \}?P{9}l'G{ /|9srLѐ)JqFBTUuݲ,_p(MtD*,RH5ID"YK"E @Kcs+zuE?Ke@5E!xg#i_,E"H2EH$QY$I0ѡ(JiL.]>}tlPWc;NM!o_ZOΞ{OsuN~wXJͼf"" P *PQcطGG FQ~'zb]Qh(E?8.]LܸlNjJ,@*_qWC Gܙ8{&ߨ"=&WIsS"I_76LtH j=5]-Mo'Od68>~I Q4GwSV5*Bu:Qu{NXJXAQ!T|>߉'~ݻ5`x<|˨_u0r]}؊$=x@p5SuV/dܐGg4|ccz7ӧx+~8NEԏ>hҤIkhZܻSصGkBǘБurGKL^.LhS$%%rG${j_?%b%!t4f=h5M:u{vLJJ^WUUk4M;vؚ5kA6mZ|||xGup>o]vmc^#G6mt5m r140EܽPckf4D~ݻw:VUU}G=zUUU7n55l6yСbpp8Ƙ;~ۿ+4pÇѣ3W{ѭ[رoذaO:PGڵk߾}tf`:wjn8}xNwݺu=ǂѣoΜ9[JB$)>>gϞ}֭[\\\k "blswԩVWW;wr+ T嘘޽{|Kdh׭[ghp8?20r\sk֮`4***&&bX,, y<:clsI|I h6V'b㏧Mfhp8?VcSUl`(1;ui=7zݥu [`p\vkG~vUh\8tp8GԞJpp8堲rÆ \~8si7[.mV<IENDB`workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Mode/Border_Drawing/Create_ROI/Thumbs.db000077500000000000000000000110001255417355300307600ustar00rootroot00000000000000ࡱ> Root Entryצ} 256_bfbd10c45e9b15f2*  !"#q XJFIFC  !"$"$C" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?Uvp$}xJa^ RAc'6/7d[YA)2WT˸H(qo@3!hſ,?MwT״MbE[-V9-'6apV w 6G9yMjya6we S$E Py7G<.N?&[;Lٴk}],d)!OH!! pB۵exGV{vi6{IZiUJiv_=?&[;L<}M/NژoGUERMݐF2 [^} +;罷%fi)6 ono;L+i̷#$ 8e3.xPKGn+5-$q*7Oubз'ƫg^k_ІHnfL(Ǧk/e7D& ȗwh<~xvwyBܟNOukWxx>ǧfLfHKv;WMқ%;-<nO'[W{ҏEy췖>족 kBLk ?3\BOdZn X+BVD` w&%9mL և+1<@횻iV*[]m=Kk)呮X?1"j 䞿(Ϥfjl|_ևG^<Cir㷜c uBT ?sķV&MVO\j kXehm%0 0F3FjnNOwi]s:}ׅ[-?OI7=W]C,g$lr ,|+[N::d%& Sl~e 9 LĝN;њOYsˍvO7\\W> е}4xu3^#@Yby`A nR@?`2 Ova(BSwFxCjCjJ_6oF<w_ދDEb]d6F:0O &\CjCjؒIY4shL_k k !K_ xW,t7ċL %0j7|Z^6/T}>_PQ4E^!Ta}xcaA?uojk*Ե+@ʆE,=ZYworkbench-1.1.1/src/Resources/HelpFiles/Toolbar/Mode/Border_Drawing/Thumbs.db000077500000000000000000000250001255417355300270510ustar00rootroot00000000000000ࡱ>  Root Entry -{ٕ256_3fbac5e297ded89e*g wg wJFIFC  !"$"$C" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?.¾6w'\6oerP l?v_Zw J+/Wʏ_*Ԣw J+^pʑH>HQylX 3zҀ7m] {nʳBz=r/ prqhe7iڿTEejQ@V_ڿTEejS_UO}J[ח_uh5UK4b[8 +>V Q:k[[E3NroG WfkS[C,Աm嶇9cK_:!)w-לi_Z[L i Gt*.Hz䁏~ԓͨt&H"\לu烎x$$vja ~2LʻrrG=}R|Yc т$ 9#9S0O"Դ4*X{ +2L1Yæf?p6K%, goW\BNn<nzwkoM- RlrѺ&dUgB݃*}_>?6}_;Q\090 p23{c_\xMYL3h@$3B<25-]'DU 7x' p$g?Cϗ~#m%^IvD3qm/ݟLᶵjPvL JcBc!9fۑG^kUfc,1L<3t [9jhV_cpEhtB{iq M2 b @ ԐIgZk*%ªAӿ~_7K;I4I\ #h$X3nR?1( [KI$WR$ /# s:߿zcjPRDRᔲgL]=Kq-F[&6zFuk_/\ndkSo#K<3mM*?.*D0 lqȠ5ڑzǷRDS6 K'G; c98 GIdc vEg#ᱎ6cֽAХYt]6I bG$rk?L/N)Z_GZ_GEW'DZ$G#jQGg_#Z_GEڧ?={Z_G;i V{jqe;i Vg}"JyyA 9&[w(2I2*$EaC. ?s'pt +ep[\,M*.!*Fmm>2~>N$q?- @Qr sL1/@f\M٫?WPxO&?UOq4f\MA?_* T?j9q5L?c<'P٫?WGj91/@\0 :2DRb,7G|d(r qԷK;mMD̆8`Sp}rxJR^t0&c+  _|O>ϩ:ޏ`s:8RXte?,@g}YO/0#v㓞+Kz( ( ( ( xVcx@r(%5idYCUr;X:HL0~_}>_dtVRl#;؄bn}g:3 i@?}s?zvy7zfOEPEPEPEPX2 ٬oOQfmO$8Vt[BP#KÒI>IK%>5$f)eҭ[h_GEc tdZ\ߦ 9[4Q@Q@Q@Q@Q@Ksm(3ofvZjkMӇYPGޭv :o\ѳųss =ע4$` +n!Y 0^)mH r WP4;P8\ n\‰dlU02rXJF.!+ Dea|n- 뻌z@ \rHJat[XW <t5yyBG\(.#xYIsS-yܟOQULPd](.-B\nF<OX2 _j%nw ܗm3<$AYprס@z%ຳƪ?5tP?4?5tP?4?5tP?4?5tP?4?5tP?4?5tP?4?5tP?4?5tP?4?5tP?43^Hts< CEu4A 1oǴȽeY_i7=!V9ܼ F_\ o/thvvkS/H(DyR 8:| f"I4ڮV^\8}o06m''V,v$ <ۇqk襘 *Dž7.up+6:< fqw;OKMb64QEqQ@Q@Q@Q@Q@Q@Q@Q@#KHt/Ywn5DԵky.$7F+8 2XOǴϼ֭$kI,ˤAb@u ^xE<6\ h$߇`#7Mqsuq<ѻ"cq5%uĚF1l(h,NUk-K5d:qrS?hSIo-K`ũB4E4zdѢ,r֟yC} |+Kgqoҗ RgII98Ÿ_>_Kiizvz[OYU ~njZKѼEj=A*{>![Q9b*9w)$sIo ̒RvwmNV{iSɻ \4H큰(+WJmΧ%޹ڲbfIs2# ՈBX`W Ȫ"1-x C=(((((((((饤h{I鬦Z0jd{\3;cxckW_'(&A顄sP}a[POA_}a[^]Eg]ğd!R(v9 f^}*x5q1!22v$dgߵE-]3P$ۡHUk$+x$$-, oT98<jIK~k6hG8 9^# Ujm?7Ms3k¦XqC%i8X_?LJ]|7 !ƣ{m_Վ1BfRk,*Œkj8:Ғ;&-N mJ-bp]F,@ X#,@Vв[˘_.A8m^W~3\gLkDᜌ8rݲ5 {cvgh#g׵ORuL1>EQEqAEPEPEPEPEPEPEPEPHtEr^-<.3mylE cEKG,+f({@ym49xndvh CxgRY*Ы1}h's[ͤI bH"P??Eka8x햣ڶb7t%PؔRworkbench-1.1.1/src/Resources/HelpFiles/Toolbar/Mode/Border_Tools.png000066400000000000000000000361721255417355300255240ustar00rootroot00000000000000PNG  IHDRC7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:LJn B";ͯ[o 1 Ʋ;E8_W‚ <_+ߌ~k\ 7u$CjpjppwA4 ŘA8)Wꇢ6ۆmU&QKX'nj!Ž~'aF6*}a; jpu"sU!PtR'#N/7Eܱ]ni:W\w75\BH@،( ł'p ";)^ 6wL $=E88Iu9#7jT T''k ^\q\QQQbb"˱%M#<#ȴqyU)gp׈f!81s`eT~͏ ryN^/R% V !ˡAR([7@2< Pֻ.Y'#ת~j%CWdF2@qLˎ-4 Vܻtb{Cx>]Ɛ^el,@1tp@Ey?uYѱau"#0Ldžbk1D1BZj,4*k}{j"  )o݃1Dg[Aփm  ˧Nn`d/IBkz+~UW+d-+w{OxY=K7zgL8a*0y \M~OQ)5sfox@OZ||0e' !`eTX,aaa؇@]"s(\L kg{,3WO 228)PvrAs+I3D ; R,R#xG ܼ2oF@M&e'wt" |+Uk/w []\SBpԯq nP?( BѲe˖-[-=}E6dy}=E~Z)/xgYV| eYe98x9Mͱ ~Mm՟ qnaO;n'yvOGrn s9 vNs`v,Oò,d/cET_uI*JRnH]{W&撆Ĉ]0)BEDDDDDRe&DLeS'#$_D|zʙq?[@ e2eRdd;@Z!` @IIe^^jv+N:8dXJMJm ]޽uA4`.lS. 6'93E˻TweRRҨ5ҿ4V\g!rHw|s@tTRҨS9Y|bhM&lZVvhX-tm(XO-?}G|=***2bܿH'd9cZ?X,FwbZoY=uyrkM7:r3{UX +Wa*dԿ-\pݤkGʁVeM\7Xr / N 0 T дr0 CS4E1 0ݟׯ_Λʓ3}rM0 s曥3Wanmѿf_bl9 yN؞>ӵnMjy7)痽hډ'M nwjn]=߷Pf-Pu:N%gSSBL[ig34-ZekZWoNvΖZVx%B!6XSFH D=fQkw*'E3oߕ)ok@@(iZFbOy|ډ'M- pp_, !dk]4FjG/c!ffEQ˒5V;zIB@틴ZV;& uO;{hr,oo̵ r?o~)sC֮):HcYx f@9{(Z5bc '"G!'/!.ykǿ;R_,;T_x]Nq W 859]lMM:lYn]v1<{Ux֌w@[UBJrޔUXQUZ4 Lp!3i)Q-&/thwGnm'bM$ n P)VL~b%Jr=*-7VDzMΛ]\0o?R德0|F/\*Xd⫫&'cɻ'x k9upff`yU xBG0oyJ*A|Z bc?#qF\pϙ8_pgx '!1ϯ'!''pV1F ৫&<2`2[[`zlމ}+6oŒ&˭%(^ kH  @%ah+B;,ܹh%VUQ鳓>?m0 E3 >kիp52Xs~G|j_J⠾Y5_#v8#ݻvX nD @ݡ̬W(e"4)/@@s(o)"ZP@G3;RB..<`$UW,!Y@Tl(r@HY;vP6peX-"!goX*?"Ij`i ʶ1k@sQ TZWBmz1 TĀ}eU˯`9r.`1I:BP3USN885WKke "__km<Ǝv/ m# 6i==PG#X $;$a޾1jCa)p3j@Eo%}q3m5 v=xDy`a;:&rrO&6ķ$E `s=ca$t U &pf$wa KUxbWUKd1\<#U l_n%{qyr eׂGzi>Ͻz?b$olhksIߴ_0s5 N3lT"6:1VZ|#b/nW'&:%wA?↉ţM2o,_4#^!%g3\A#8Bhbl:Ba>Ւ3Wm۶EEw V~Qx{t_sÔx%H"N Apg qAp< 2bdu8!CQ H[7k7ѺDԓ&OŪ$\W0>q\4yjF%?slУwϞ;eNl&$5J?oh;\ǭͿgب1pT)Wt q Z7׉bl2>;hT2,MS)E =P:eSRN$e(4 Z7dlNgNe4E"@ٝXO S1]!G%`Uԃ@Ё,+EX#= !^{^"ruԲ2+g5L&u#P'ʋ#RڳgŞn(*, 0vƲe=#Ӂ]t0]ɗ -PZVY&dЬNԧ`!DlU8R&z=fm.]køTNb`|{K9D<:`=6u􄴮&e@o-rvG%@X+'(/!_l;b*+1qʮ_~E#ꌖ*`wU"w9ٳz\i#APૢnFv cB3J!EЇœy6itsd D-(0Sv+#FWVvHEwJi,3z Sj]R KSmbm&BQf&E^ s.Zi 4OŴ huҥK !DrvgڸZu6c ᨰ֩O,L|;_a0 O>:mbJJ.?LC=H\Yz&m?'7Қx@0 MӾ{4Mׯ]||W#:B󟋠i3[ʊc+gU& F}=L%xaȣ~^OPZ. J)2Jdwl֪ kխԗQ+ )#r)#J -g%uy/zhw״xhaS2ߊ|oʣ>c~/|f  #Sѽ"Eg*z_i, FEGGGG?(JςYVV2I_qqZ3bȎ83>ZJ@hKoe<"cx\9OYo>=5Q}T*6zY!hݥP@!G.#kf1/T|HYሄ^Ϯ1?scjUVSY3l1txNO@F3:/7=Z>b? N1Ϝ2tO@T**q̅uFS%gp6Qѝ{@|< ]Uju̘ /,Yk- 0I ׳]pܟ>ZRuM/1DG !lRM<BX M2˛FThM@ڊGML:b*S/sG5洓Ikg#l{<>c h4kNVK$SFgWˏ}ӆI A`ϸO> jB>\xr9GK>zl-ػ楇WZgO/EkMT6R"> <~s|O_ _Q^e;~yvP~cǎ!fq)upHw$0q* snػ%aa@|SB'H2#"@(TNDa$FX԰gAD^&nKVQ'qUH@_%&^Ԛ.U;1띮QRj%MQ(P@|wCR]\A ^28[K{1DAyowIU꺒 ]~چ%) ,a֝BH88Q>_CnйՉX-jזjT]w%7a{\~mJRBq4P Q_pRv@p8R+-!\;$gk q ߔkk~2>~K ]UT+*+NhXЍ+'PFU"&/!0 c4Ϟ=ۢE{%JBQ N?wbhgO ~/ 3W|WJÇi{w(1E@b<;NSJϣ.*oN$)l%H NMk-: "\k  R2Mp>|ns]k[ZZ_E5k' }eΝ7+ׯcǎݕҽ裏ѐ {'DتsApTjPG9t{AeKKK @K5MTTAߕXЄ&4 ^V BD"\~/xm6 MhBpo@IENDB`workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Mode/Foci_Creation/000077500000000000000000000000001255417355300251145ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Mode/Foci_Creation/Create_Focus.png000066400000000000000000002137001255417355300301670ustar00rootroot00000000000000PNG  IHDRWUG7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki: 9(R%$I($YVax w2&ܕG?9UIEeK +)ɹ{wfyn@5!;w T4M{%y7r&AFEE I,-{nA=M%52SoP+(.$IQ .Iҍ7  pA_?9Øf5/l6[#֭1CŒU@ڵfA~Q-[hZa< q\ZZZrr`:aZfl`r?tx??}U!(ȲݛkݲˏP(Zw'/k֏<{`GD%\4UEU Ο? +*y!G?w\vv(JH [n˹K*#ʓI'Ʋyw8GY^ZKȧCR5qt̪U ݪW IJCCCCBBCF#LYYY$IVREV{Û޿odTX:=?_t{hkX/lw-7!9o;XWgNǾ4doI7Z`%@&O4}lī=ث}iRZ %%Z;#?~ur)sdT$<p\|9!!`0t:Il6dk֬V:O>E~~ݻׯ_gΜ:VdXS^2+((( I$ɀVRSS4)))IqG$I5jVKII)OtFC$p"HiuepcK?$w>ĽZ`h> ULZaɿ4}aV,P)zI.:ɫ,˖1ރRҤ"5$A[ XVM 㠺5もy$$I4Mdgg{iK PhR\tJ%Xd椖J%sVVV sX~@cQE׎6zmB7]>9Kq_m_ǥ-a egˉD$E3͟ٮf7 Lj'W INyg5.|gͪ5cW;_jح&?|x;uz:wIofn˵\趰ɻ.xoq{$ _9@O;Kf4s$ /?ɂQTrGeHׯʽr5MXXAׯ_ 􌞞l^! jrt3224MS%B$IE9s]>ϿRdĐoH',/w`$ xEtdDu2Hr ((K JrQȋ2x Q1!i POBlSnWCF((4$f4SZF "I xQDQB¥2%$I8r3yCSYcĊ:Wmy?؇'$IEq"NR`g>IԮ9RoEkvKr"(cszY=jْLjלw _N1Ӣ(B8qO{)9V;?}?ѣ_} ToߧM'յ+8q~W6gɪUډ" 1Y4H)~pvs& \kHqHU l(e$׿fn91sCN T#(PLiuX,FQV0ni6nټhYVѨRn>Tb*("I׷k 2ίA@͟m$營m?-y@gGm 膟:swL82jZVU;lYz݆>hۀU;,VkZ¯:hȮ͢dϘ9#aUG/(5z3O,[#o n ۰34~~ C.(׿eOSy/IVH *j Z-avfWj{YnDIK^:VTӔdT H <'h8Z[f|vB.!zGuB+)?0Ymܥ I*^ݪ@TݘjboWJ$3%WADq 4/\f+jXU  ?tl\7.\` RĄW:6!6-Uk4ʒz>ՌY(qjJϷiocY9vB`ҳ67;⦿wds;OuAP('`x,-`ݕ |tHHȍ2~X$>p5MZU9%}Qe.?T(;w Fh.+eǺ+8r|hY=ڼzv|tyqXq )HUug7 il?hl f0l껑B0Wѳ0yY&F-3|>[uȴ>ziam'[ud*BvVzQT)!IiHHrxo;No,$**0JBla:$k E{Q&.uz|v^_͜0D? xQM{QxD?| [|Ǚ'| \PP08%3f2‰Ņ Q |*d%:=,-*azFoY3䎼~[|=:'=R^S(GKکϹFB=x$I"xN=VD;a;]hzhE>^ 򭆈UɃ鹖(6 m{BbÉv  I!ժP-DY| SIOkꈒ$W~K_}UK(M'}j{ُ7u3ܥcY!OT%Y(-vERIIWskWoXN)O/^9OW)3[7'gTA jH9725u0f3 &dl6gȀĜ# h *Q*$AI RPm jP q7o .g;We sT(B "j6zmx&D#G}gণTWg(QB[L) }^gۗ|ݳ % 2%Ayo[wZk6Ә=|,zn>M$H09KjEY;rGk-d]Gř2k3x{qs,-*r?P$x9rUS8ܲQcUIl_XHϯd蕣{.d $Qj4=\S|e|4O Un\sGʮ}tNB8rRo (pMꂫ\Ahbo\a'|"#if^\HZXsXUiDV^C2ҳY:jqjdȵ^E͚R\UWQִoHP|^Vr=5kE:2E\jG44A% 2n:*t=-5T%3cG:GvZY,"*v˗˖P< ׎:s @u:F9xj ;(VZM 6Rj a(Zh1ЂZKpV٫P\OpA9k 55e$G2 g0u93.9MZֹaf#,Ca ZSqE\RV-G ͹Ӥ|'rVPFQI.^%-&Ôf! $I $JQI{%:*ADF$- A^ z?EN$Iҕ+WX .;w=*VyE;9{:w#K(i)#,~E#\~eD :wI7]@7-%+.yn"Pη \8X<?W9Q_ĽʉЮONi7$Wz IDAT;JJJ]AAAAttVIv H6Dz[Sm.i&υ \|T3Ş={PDQܻw]$Oi_PQ\T@@^UPp"?7;_IAAAAr!y̿<˧աDQyq?Be`(nP $o#.*7cn*]v((T.m+Zf]~( #5k_OA#?RPPPx(Nyu8ٽFTPxȹgCAAAnP9o')"(3D~.}7ȵA8~-dWEj._6 FK.NU'oDP<J3gΜ?>//$IA}bW뷻o :t9,nM-yUNnܴݥǯ)kg:q_SrK?X{Q!P l߾e޽{Fe$WnܸR(*l dgg޽{ٲeC 7u²jdr[$Isk:ᑑ9-PU4~ʔBJ3g8A1 lNf^qJ $}~a߾};wv LP+禟IqKr~[}qP śN=,x?0Ayw6Q]?v$233CBB$sʦMڵCEƭrFtu=ܝz'N5TA2 ? :t5';`Ѕg.Q{ҘLz |yQgswnϻ|\oQLۓO۸&mz ǡնKk([qk6@Jx?3dvvv޽׮]j% ,ˊ&滜9}fٗO}i7{g6ny**yWqO<ݗ;DŜY6}wt7?IcS0٭%h ➎; G6*}6k_ޚLW?я`#!7kYQy//~nt~'?;%rp ϟ?…% jy|8^/bZb%fq SܹeHɃ2Rs|[:FSSCYpm{RecNrt$yyʌ.NJxqrS?Zbɴ-Wd4I NO PtgUi=}7TϾ+;C ߤͯ_ßiO^ 6,( ?Iuo_¤ĽF6E[:]-}˹=K{wjd׶<ꝒA|bŊi ?dy.3 dm]òwڀi K[/R^~CSÇ_O?nJ2 `rd\3pvȶ{ $r;Z-۩rC_ft-X(9xaZr*(T'L/˕8Ү.}#00000Ow{^TPe6Yۚ 8Ք]巋+B1otNY=O$Ig/ژA?~5GQP6i?,&Lⵤ?ѓ7rR>m|xWTPF }ԫM|ض8\9WҾ+VlWm-[Dtϐжq mg1@۞L0ucΙm?s=ґk-;}/vm /ֹr*.+76mԢ͛7:5YUTn/JundKy*075]7tSJ9]*(Tf$T9{ǻ;tt<لgLӪ\#^($I|Ċ-f&I긦O`Jyog5\0sʉ0fF6M:5>K>Gh[3E(;+B`6MWd9K^j\[;a=bOc\/l\#=[c +`UHF^8X@tm6 ( lsikw8fSrHNB6M$&)@!gT9_4v̡ojگlvqO!nOrK?_x5}8b``lS7.[kҲYS9 f $I5is'K b3be~AW"p fΜ @MN!"<ϻh3_=qa$AY&u=ٮGUwqӑs2z4I ~Aڮ.g _=={7ܖo~uTo#4)rrVuqolvwT*~VS,,?ej$d>Zlf1DO:;O?8-64 ]>%%@+lF80w_[}% `|zܞ3lZ]^]G>\R;)]K^Cƺڅ|ļ=1GS5_@ D ҫ!Z9_h}P3jJ_tslV/~IӮSl9uI1IQ흃ѺTK[5ϷG1V.vt}漲_`̹:^S-W!H+'+/swsA=;EKihֲN]pSs}K|RhݠmǶ1Ee w뗼vl7IRALEQ I>Zc|t$-]qAI( iW}l=n:1g7[<U\O;A!/(fWo6pبo6v˗oE$-@bzDSxGd%[8R t5z~egk0IƎ{IYKծ]{ҥ^x5K93G7q~t@Q۶ o1>>Vom;oAkByJ.9-_JA4no\=bh×l!WiتyMEPqyK.\`zt雿hHEQE1 SS02.wFU8`fwp.`({M4ͧmgQ)B횯\(*I}~6\af^!ӿs2r3ddddd 9s/4MOTUTJd\NKrʕ3L<]X).2 C#Ա&GW 4.oѣG=A0 MPHxfE~f+fR_IE!>mۦ}WM\@P/7^INKKNNNN|5`E4]+cI0W64i$E1 WS`?\49 +Mw QION:Q@єVN^ 0 Rx|4~xF(o'n9#ωkf#NC> mƆƠc;M^/4ЃEaOT)DKp7)[ƜLkBn5yZc0h* \w$I`E )i҈h^&~86w"6{b;67߻u.߹OZȉJ0F?L7я+喟_ ;߰'E!>4MKAvśF<4Xַ$ 6r?́ۯD'u˫ߔ/$S~iL_BԳIRT,CX>>=_2vޜWHdߠ^pvZcЫo-&[vOf~~ ;\eOb<1[ls0U_l2=/f5z.iQj(7 /,_< @*HTQ,Z,5k6lH5w{VGĉ}pǮ _ 9<;T+Z '0Y}~Vh6.-UQPHzW322L&}QPP6?n r+<^ԑ}mN#qlR^2O=`#8){ vJnwQpr6>8`-ÁN3=fogWO塓PPTժUˢp9PI2n7p4)=vqfC;BLCi8 S흂 ڀJk7y((Qz #pJb_cƋ-j[G 9 ?qVxe}~=ǘbej |Np.N:UPPPPP\\g Pty sQMeMԹftt  h6t :yܓ9:ǩ}ڢ "m/Mi]*1/o\ڛl0'}y wRP))* wW UW$9} yP$O=577bJT8k^foHCXiooשh$,b},3O6<hP:%T* wlL^6xqE_Vko{?Dp.*񡠠pW(CB9okZAaZZSAAAAAAASPPPPwx.%N: L6TPPGtuDBBBEgG& ˫Yf98r|nZ NBV] ˠƍW؎NU3$yO>]1VPy% [DAA>ƍl6y$J>:#u-(y ByxH%- -O{fYVq^쳓/MBd/;g"6/ ҋ,_92NuK#z:WGa/zy>-Li 34`>[|,cyAʇ1Fhk+ (>'ׯ?i7-;oXP!?ut{9>lG۾x΀ ƭOSouԪ)ݷ>^n/#ɼ嵚0 Ǧ*((o*yi9c4Ont8(";~5q+< l)Y&|yI"PCF H?M˵WY*0&з}aOxaiɂy; o J(#zр)ǜ˕(o-S(TFO3RًS~ɖM=g_8֖e3R '.ӽ/½F%(7Hy|P`OsW0@$d9$ έq+̗d?IXZ gܽ/^t Ckݷ, I{XL&׹]2 8;sY/NgO85]6N͸wN\(`-cW&%%] mk#n3g|~ k|.Zsܸ-VܹX}z]^3ĢNW=HU*@s=$YYbL 999מcB~hm{L?@-p^QLAqp.Ʒd | F@ SAGW-9|unzlNTS?_y8k ZRM.Ƈ3*e,I%TAAcC#)1p64a==oETE,m>wK[`fgTTiW`mDk+rڀ.zsGqo}KMs&ޕ@MCmKF ր Sij^X>/5XM%(ƒf]_f'3_| Ar6KemfFܹ]Êa3>n5QӗnNMOi _s6k?T_܍9.coH<+X +H7MՄAg㢪?32**R @-ki֊*d"IbTjP[MJHCMe[1$$Ge;3?8ug=s0;{{O~i 镺՟0мl#<;a܅~%uySV9^qa޺kikf$ޒ27o[`r`8;t`ڟLLTXpYkN{)f#;4 =ۢo ܼ5G&-io>Sk~(Uon:zKb`Rs >onk2lp\?S‘1jm:Fkf1z}} Ôv&‹9y]`d ? HޓQ1kāvOj?iꞐS'Kodx82n³k$84:j)5 @QS2-UZKAs7uXûh1r$ASq÷wΫX'_YmDHjHwS^OO @TWWG37 fB)"l ,#Q "|!ÎF҉ȫ*>I]\XSkjlEA I#c%__p-?DNΏ9P6[ @AxӲL0͵ ֆU1Ϳ'̶+Hy[]঍DcBEK È݈Q3գV{?{zuMܵF&u~@djn ͏2=Gl(Cwm005A} ƈpRv[Y9iޫhNԤ{٥}Bv,k5|(Ϡc`/ 'anJQuXǔ]&<"gk'cf~0+ONИ%BM}fgk?~!.eH;3xXgfat'/՚V {*k:.^Ĝ?^T^|׷)M -&)ئ&@ &;99ƛJd _>z<#T򗖖pDmiBA-E3n||y\F 3?1j\*W ML 6=fXV?~ĉ\"˲gΜN>ԓF2HdeeM6 #G\\\.[hrb ->Hl+DK!wAH sǣ.9l3Y?uV8#[}p[fBZ6\Ճ&2Ѡ!s%tK"L鸋,sN}ĥs6 :VH}BBRt^ш0v!Kc}:\$X!X,~zׂ@{a^b3xp]~:`t0qk.HMC.*B nhOw:.COK&`PݹBz?g2O+/ .л}]Ptǐ !oԠC_s30eYMHWB!PB!=F!!BH P%%%]B/}y]NR&Nؗ'dr!^]!2z{.3.$h4,˲,{ܹ6!P c !Qn+J,PێWqoZ :;i*ް`ɯ2vo{r 63J>n}'KrVFq/IGK˳VnUE%'Ygl p|+L\tjVR_vE՝>ZX.ӵT׿jEZXoځUkIJ"=|DwJw ݒ‹FڜlWg_n{41qĮC2qq "K6>uy8֫aK0K[*)O[ncccccckk;jԟxZx)Vx):lXthn2xrck$TPn[W?F5P;JEkopiQie)jam7$xVaOBamLwv8s;vM~l2#_̇{KL>l @WkN`m/TVV^.=hfp;<'<~ p!a{.^Ӆ dssS A6izVl*)BEN?!RFOߑlZ;\h! wC+,'")DN2>bP?L5ch1{l"$y G0ae6lcg5Œ߃jB H{w:{tyy{qCHX2swTf|؁M5Tl -[ᬢr>[?wxCTr(~바FwC.&=zS_562cQ{VIM.MOK;&bި|-kՅGXC'0楥^&4^ǰF&#l,DSpP!C]LjDq|Wmjjo|lAtW5)Z.nɷ2 4LR6ܹ^ig&^d9͵ V"#y x9Oy[22hI4!Tt-QEHwtkŧ'JOvtt_=ںue`)-6f> O!>N;fϟŀ+=4?lՓi9 9G+Y3o͉_nXwuOs13?WH'y'xhuk&qs< J56ڏ?~pKY{ #L,s3me^BJ˾FҺ:) 6V$@SSPl!B*U@ oEbRf?%%%ǏwrņG3OX.ii{Gtؖ&)dĖc7wG >Q;+->`-3sFV.AnEb\NN=1.0.&v_9=o0`ZQ EHQE!FH{9F#~,킧p c`Bc(Pb] BM,r CSlnKOg&=nbŀ)BiȅP~~~]BP t6u?CgeiɄ ;BHXS5wEzˣr!! WW׾<tkv,R 8]=fB!E!!BHQE!cBB!P\Nob>0T*T*}W)r??{7 oN3rHwwgyF$u,zKon؃zKoAz4!*j⢢ r^<Zz1!*<*F+...&*E#M]aӔ}w!8012}j!Fcjjʲ,R5?HR1 òG}VYJѰG/POg'_'uyEħ?0wx5m¿G qYM(ڳg: PnK/^*π®șrQmѱJ9 s?Yitc EjD.J8V @^y,Rh _>ឥ//jJTci:]-\Xswm,޼ȍ۲963:  6Ֆkf +ȫN~c?y^:}'o>(=k{#wc& qNF;cm;⃳H[nKtx/-'KN9e,g\!ZcǎԬZ*Z\.J>]V=]Vkj]?_}*~k$ ǾURsCj򣛜6s|Ƹ;%Y R)楺,T^nX=T9찈-/?sَr&gBnj \V7aj~{>#61$쥛h).<= jb/n̏: bzns?d5=y~]>?o#buRŒ܃[}i"yoW{i۾K *},W}]u,qΉ<pݙv`@RDV3w7 Ǖ+EpM˒SUbKIvʈ}% WWE'| RٻqWt`WC}z:0A{0^EdLLL&<BvvvڮQV. "2%U(6snНNx x cu;B@nIًwXs.L j@T<[}yǮ}Rnc&tm?+Ąq[B(V~!sI>>Giںش+{?PŒ9۽WFVҳ7yrauo`[Ǹ7s/J/Hj5lg鹿3*my{%q(k1@O?ײV^4Z?8o%J#:TQ:neׅy]oȫrPr&nrjBߜ4܁3ƊUXpP;?}Dv;7 `嵵?@FjO5}2(ݚc]\MN=oy.pߑwRobKеI~q(lٻ)ܝsfUuRtaJ)O`% GH9t'=`z !Q}/!x{1?:(A~Ⱥ5TV@Y,Yf5kJX?bCK _u'Ou{_WwKirUy.A\[[W\('fs1nit%~n+7?o<,j4{NsC gU7rlmqZevEe g-Ѿ/]iwSq6B"׈8׌]es;y@yPg0xߛX"hnk[^q^yBe|T)HL ċ?_l^xO=w<U{B}f_O `Omp҂ouK4;`͝kejXK"ȚrR)e_Q;=>3N7Qh(K[aeaÆq"^|yΜ9Ǔd,]x5h72^lKcRow^۞8xS{[3g7v<``gsY(WFZzm?S64@4vj|Zv=Od"xM55UX{CR$! V40?n 7o5jw:155^P7nhhh.w`W Ėx,tAwT 12B(gg砠!d*BMB!PB!=F!!BHQE!cBB!ؐ"U{dMy)vdI?2yqvfɯ6Oq /h yd΍ɮq>?}ҎJSF?漠̟cU 1n^9wWv}<6/f [oD߉m,޼;>k! eBp}7<[Tpbٕ;QSlwI6 6^qj?۩J4Fƨz4rNdLB7YOQufGFjMyϤ,:)ͩlii';FEOغMX'lk U{Lq K斗nrXpz$-r$zkNi~]JM-ڱcGrjVy- Zim-5[<7(/?i<'kBF 5ɑрӡ"^/*ފnyҒeʲfl57nZ ¤{WC=hr{FbGSM\?.̤,73>t%uJKw8c++w6d}ݕP'?v7ڇVnW}]u,qΉ<&`+]I^७N"߷TF/?g!dP _Ɩ"dg ?i D"izĸ1[h,^>]odAgi䶈 mpվW)DM[9R7 4!Bq:D,;ؼwV ߏgS5 GXN Ե( NǟTǬ OHweouw |~}~: //W'KR`K["fx9ov`!lX`Ul/ Tonky =ly{U%$ }\ǏN!r~jv<6s/p$/7h!L//'ɳnOxvQqȉ h,,~HK4g[;L0%rztFK 55(L Z_O-+77Wϛ(rjBߜwBa:MB}7B@m ~ʝ&fܪjkn0K2W?#I& @KN) ڴvAmJ4nn&<݉onNroMF5Zs ~ljØBfV)sC~zOHWzM{17-Ok|Zi V)ڪ=-)Y KK_~,>|g{S^b@[lKJ[UڝBPڍubtljiYU*KsMɩC!a|bj}-\Nh{9ee|hU*^V)jPeO 'vɎWMŀ葺%p". lձpJcwgWݷ4I\'?iW]4G//a'~+1?VձH<$m l==h>۲tDYLPr J`?x?.hWqUTXSY9&]N{HRviTXYZXZ+ MMVKؒ4h_Q.uΐҭӋڪ yyX 6l|dUUiJ̌9FwM, X>V*-ݽv9l.n|`p> '`ܴ RimMUޱ]E\s#ҊڼR_cKd@~e9+KGWaCc~8WYy… JK/퀓ǀh ܾ@)l6|1s츧aw276~A&Uyyyy'*5U5e?RUUU qmU5rVIfrlmqZevEe g/!h CQ0>*s{8+S~'^|v NZt(Bۼx>hz8=!BFWSlg|=@"m۳}##'CD,8@S >@ |ϕP10{V+u Ѷ3,e_'1P`fJ+M>6>tqB<.^.+ l(S$3/ZqښU+2 4`@>h !Nՙ_x( }jm,c 3 'x=W˫yEgO"B!d4j*>ؽxw8x\\D,zuA$D & LL ~`B@P;'%@B(B! M)rSHzcQ_O  v)DkDެ[RvA!ҷ: ~n_l%mm5}rԴ=iEk'(Y.#+vJkf1 /.[OA'B f<)* ;^X졋g^O)T ֮)/vR>jDMuu?|=z w8!BȀd|-ԉtI2G>'VG#S]%je^l>S<}f} n ͏2=Gl(Cw9 !2XZW}ژ5ykl_Rڤ`b 1Wϊ=W#[`DClAPB$zmPl!,E BBKJJ*++jZ6i4<{w 11⡇ruu2e@poNs)CʛBHh4Ν077 377755:(JTzܹ?<$$յ]MBs̐{i8PBzJ:~xqqU&Lpwg{̞x ~=*iNv/ r!Fh4DBgKעߨꬬW^yFR1 ?4@@ь3_׿z)r5 ~ի&&&֥@*p[5X4MIIIIIIhhX,V}M2 cxP#}O?tܸqus3FJM3rmwMD;Xe7GL{tT4|ȅP]BdeeU2믿~mll۝{tg+ mK>]kӖfnXYWO 0 р;yQVk4ÇϘ1#YiUY*J 7FA]ox0e R ?2eH]4U|Vy,+`T`!gYPXRi&\5,1F~S׹ 婗̌c R麪/=hMz̽p"rryssѣdfT"Zd-2YE&2\֪=ei\jV.d2Oߤ[ZZZZZ/" xYI^&=߷e----C4KR\.WT |@`kk"{9V5T-rL\ (r\VrpSk~eiiiiܖj3/|q2ᥖZkӠ/]@}oR--mm-LU&ϬL|e+K,-_,XѤGzI 髒ZL[kOeîC -rYCi/~v^KSj2ٯ߿ѡ^lz7bqO$T*T+wEڭ[bȑ#|>&x2>-tALg{ee!:q?q?}~+߾g5mcx Z<+g4`n0 gx-aS4|di/\~aɁao|z _W\-F̐ɿ=/ӥn01 ʢ?()eJѸnY(~wO? N >Ǟp`U< 'nɆN~H1~x kݣ&yTM2MIο>y.t*=mcF2 Ĵ51 gjuM4|ȅPnnnSLy`EZ[[ϟ?ߵkLj#LLLn8afa棇S-#0<W zieg"bL,:Q9\֗}Q Q.D,,h:5j}ӽ0 >mS_=#^֧~\T`ab_`O<ځ<8cFIrޝhuD ps&.޲3'5 ʘZ<4Gy.m&qiQ+N~71'Aj/7}GY01q泡e݄h)h!!BM8%ޢP([T*&7=swrcoGxP̣7c+~i˴,0{͏ N4rO~SV| QL)v èjDzl{9#Q֢nF<x9MWު~KR˯l[x?7VWx=P/մ8*vW%/ڌ2@jx*%*~7ۭSԔojV`?n؈͸ fP+[4|>ņ̬+BTjIע7od2kkkw4w^ _U Es7jя%e~3Țxf5ܬ&aeY\njj ]5g>@櫻*JE~~|`#WV;aV> |dI{>>ݬT֜;|^jp>@O*+++/]Twq%|MMMǯF2>/B(|'4+&cy.TUUUqԖE3GG^ TEB\U}EҰ?;fj 6߫ 齆Qg/>a6B333{{g㢨?fw,/QBtQZ+(8PK;,T1Ϩ4`zYm?s_lzkbp waccc`` t|OmB`7,z K*yeeweuok_] /cY1&Hg㿋g/>KL`MkڷľX=㹂eM˘&`xrٓdreyШ,IK&/1>ŝ]԰MM5ؐj><~ߔV~vOV&6x|m58SYYi۫ccc;K_}鏫 ˤI4=b)"`Ϟv.%J>|]wQ.!Z5jTo @Zo :|=H;:Tyw>y/X, .uyoJJJOh?륽WpolXV^gl6ٳg7oC 6e/x]Xo?FVwikY],+hYr\j]olOoWqFy#\59VնGWDҭwGeBgM%5;w7IPzASPһrСCo>m4Z-Y]i]Qv_.ghX>U\(}tk]RֽڎaWlOثy[pM'չykQ6`ش铢"J,!xG@!0FQwI.[nF)k7~]É>=]9f$sN}V+ݐz}myQ&0W&9&/ܒ{si7VKgLxsGዓz^oZqƱ-&OzG#pp~Gm4闚9Bqr_\\nW_viW\xVk\\LHn1}w61.VlݴIARK6J]S/Ή{"9L[a천ͻŽ5OeF4kd3奕e\>T kcnνw ŽUљI.3SV>icH(ˌoXYP2ئ3j~9f={EBiNzEaB!ׁR 0a¾}^uJuwDGGp?OyyykkѣUQ9o߷r&hhy%H^?ڛe pjvIL =Yn[%Ql.)IO0M-@$/x-lM@FAݲq0ǒ" SPθ 5dr:1pqBzWfVzH">aά#F8rȡCc{xaB5|#Ge'7~8K$#bbi;G @a¶= -@YSK{;_V͈eMTE!/P护<`0\4_WClB̔{sۻMZ=-{ϙ25L">eY^qm?… קtXww]~wB7|@@ `(*]|f c{rµ@g[^Jmzhؒ zKFV: *x:' Io(3lwBu!CT*N_җTֻcSNu%$=e% թ'Gm21>;i]|oNalaV!)»"TUy{BωIILTd`7V.9вJOeTr- !0 ð,ݕV;ӷ߷.**]랚ilfְ"S_-į6'?fZ^iҶ{S:Q雋<^{hWiyJE6-#&73ql&&'&3')gs9%Y ]>f)AƼHJ$'BȍqsΕ=z rZݏxgቫ62ɛ9VSFjVd{SJkl䮲RTW䄐V''Bu6ǿVwIri53-Y1ѭ2$ŻZ#[NwoF̚_B!䧺˓>cgo ߷?X+Z [MLnMJF"!BnB>a a za$C^ܤIjB!Wo&_3!BSYG~߅P6FP\s:ܡflk$_ Fco7y{{z{{1c8BOtDMo^c3O)" qqq=Bn BB醎ҟz{B!8 K/iZ\eqܒ%K[,˶m***/9ydFK%BzB ƌT*{{ VYY|z…uօu Ξ=.\ݽK!p1 :tgo~xذae`Qa& `ܹڶmۦONS}BB) JۣT*B!=X,GYh 7nLKM,Dpǯ_bhtBB G䆺4EC( &WeZm6nd(~ᇟ&yjREAnN,N4w:]<ϻh,5>:>rrGƣNNt+|qҒ/q]PJO g-z晤g6kZDdO%ϝ;x&yݚO6?xꊍEo(>D8w7 -Ƚ) _Um &.C([bV敖֕h|f?0ٺ @IȈж|&vd;;lvmMo]S*.o#k`(~-(bG LϡO[Sj^jaǎ' ҧl8Eo IDATŝْvTsc/M9Z,ivg8>7]~>+`˗/_|rv/X(.1w\(j7,J \\*W=0H9P')ˍtN\J4bbCnsUI@cۙtosB@ ah!vk ֧y3IF΅E*+K֧Qi^g.˞9nJ΂bΝQ8sJQxuኒ >Q)wWSxc$չG?26:*L}ǣ[8-cB yB9Z]}ѣGGKWO.KUSz$i,Zsީ<"~(tKu-5W^-ɱ=L3YYN}QmŭԷ;u"3.}R:dƍ ,X`Azzu0L0 4aߗ9/KMO6hu#n-mv mCOrg1|q*$d\TՔlmDU~; m.^ 4N}QJչ=Qqcǎ}}߱SO-|@W>m|sp9C~+5\.ʜ;'zhStJ}MZe^k]+K2ǿYRy+aɕ;{祏 OPsIl*5ez.#\W47{Ixb@aRC W,|]oY(\.˧N yQ~aV.2h~f6/@s?o||K^?(xE0wh6d v  M,(ˤtnXUqXߟ>8Cްk[¼Gh[yX1ulpaF> @΍ÌƉ۽0k ;7q{CQ GHzN?3f\.ؘW'=1ήInB7V7cYVgmuۏ ӱ=,jqߌ,v-:P@plR͈97lҘ{j&a/S(R$LRXX8x` tUȕ'{zSJ 1V,ޔSr6=}E5nuə ت~Ix>vČ{"og@ FV;(#B(=fȑ{)((0r\%ӧ EllFo!Pkum.RHD|\z 4FWhG?.oO6%khF9v5bbўlëC54[j0ⱷǮmC(Vrp<\.񉍍ڿSS ]>>>ЊBn^̼UV2Q?!fVWRW;l6ۏWΰ,1j(aejF!fcgy==r!%Kd2wwwww!8ukmA_> "Q !%@;-8QE!Fk F#B!nB!BK/jz{B%qK,BzH ƌT- !דfQBzN J!\_V!}E  RTT=BHp8 ŕBN~KJB>Xo,![C Guecu{ 0_<2mO{[ &e Y"ێz!m']B!<\;Y~>-|XfؤE;wܔR?'>ڎwX{dB!rP1S-^l[]PS@LA3LHRܙkJ7\@'B[.Nns3׼0ο}UO9]B!$.BՃSů)cY @]LHʾ?IF̹w8+Wom~(tB!MXzZit?F.93Krv)Z:D!B.#f;V֭h?$r⺳r:!BkfUVV(!=߅PCUWnJ!Wj(!=߅P BTT!Oq8 GA9.u-G!R__C yB!eu|KI/IYzm[V|;^H{赽!BHqǟ8b|: ^m8NZI+oQ`&Gumxg]o'"Bȭ ]Zbl۶ɹ"/gddɓ5ˮ\Pֳ(q>@TTr*L\!Emc]fsKfwݚB!}LT… ֭ B={>]p]uNn`7.̼%;@Sl)o$ O>]X;W/1BX` 䖘$/͛a898s/I \9eyB!p8DQdF&B*63Cjjjrxذae2s 0 0w^{m۶mӧOtu Kr(mNOA'^UfP '[C:.BmJ6;__bnIΊ뾩lha) 1E3Pm8B!Qy^Uv=~d J<,3IJ򎱔BJC*Yb9rdѢE<3noܸ1-5Mβ~zŢvbJlz.mU)/3#tYAt1ۍFYӀtB0WjeTTo@- Io!Bz('O5.V X8FiZ}ogCt(Q{rxVfyyyvL&~<8!a :oooV!TE Xy5;3bfz m LzA1}6oqiV9=q iCiese@-BH7VW8'\uR6:rFe- qtC,xwssy^y?sf۶m(9 [+XR'tA6r= e?[:(8<13?kӮiaj@ȋX8vzGG/=2+iWtjP*Xm"=ql i͙aj\I\QPiJ%M̔y/VW2(_CXUvȭSv_Zluo[筎zކFAz:yjH|U6.4w]]hsb9ǝj {7nܸҎs V.K*DC|fk[g$7ZiY(=;N0-uZmi 5)JjNCX*(jvه9+^4CE!&M5YyͫV0)(i9R2JN}]ݻ[<~\}亱?jb.@^g@1`ZQv59YOZ ukks-G#v F_c\R(zDQ Sku5Vӵ=9uj\nt}BzY=x?U@XCMkV%`URЪR*ޜ?-deACC-/? 9n1=VÚt8N8y) j-^XBC"0JR֢ֆhn>߱ =t'6*R0 FE!J'a7 E!\l0m:"8G@FXvuѤ̒ RWRhkqCG[[GCC JQ advd 0ZRDu뒹E[{ ph2I~1=yɓL nX#PRO ~4Ad WB!.ٚ`*w7Ɨw/O+t%n²fȑ{)((0 >}\Pj4.n :?!]1rzKrت755 BטNn۟{ѣG? !Bة˲Fy˲jZtx!TAA޽{Jevv@!;pdu\VVV[[[oe񠠠$!Bȭu'$$Tl9&MpYOHo \{mʡDPB!rB P]]}u46<. ș6v7gߖKゥ|htdz+9%̏VF΅E*+K֧Q+ Vr6URt/ z1ԴJfoUeњSKk]?!Bn (>9_9sw.}rn;R BRؤH-ˑ#Gƍf---֭Zv{ۖxlX.Y(L&=<<,XO?={6˲r\ N(L6"""2>0hpe@EohQP]8Xy5;3bfz|jg'?`gΔoI{werGDFDak)!һDQ<~~ݟ~\`TVC.Ш~!;~c(ӓZ<ϻw7{!4o r6Wܽ  [A?$Q;6^3VspgO'%uepu˟]Xt-^`r9C-`M~vyiꅫi;ڂiZ>[RJ'tA6r1 0o٤IZ1bD`` ӎeӧO_i{yi񑃂r6M5v.jʔUsC#7ڰ䊒5œHӴUPǥÃ7VtJ!x:leEIJu)<88ܔSVTS OيA቙YvM Sx,fFGݴ5Y1hr}^,qOtNr٪[YXјnsNr~fRDh=6hٱҬiб^1B gZ 6wHiFҳ5 "pZ)`kbh:η 筎VGQoC =wjm}B$o>p`YN\.l|{9bN58'kƽ7n?iǹ 9AEDC|SYYi۫cccM<Ͽ111wqGWAg=ΙnrZsj0TĶHYF+Ϫ/FzF+hVu1λxt]VxZEqVtyT<8 贤su}r%g}K۾:|UH:-].=y/X, .tA+wF^:KSsVrvϔoI^ &cߖDX.7gLw㸷eE[SMAuunrqQtqm9IX[[?8j=<`ט>n#@*=o;^o9r&Ӫaa#뮻 h\Súbm' :h9sKKnclY0uKZ?j(}IIɬYX\&3@:no޼911Y ;rHr|ʔ)ŗǴiӮZu&`Ve3׻X!>jq{\g]V{k\+BHyP{ļ伂CDhah :m6jb2 v&e'mT_,lX k]8VP(.xR.c 8` b(J[Z2"|N'jpPW* ÀaDQ}bY(!TT3##ܹs.kIj%8|mYd!q X|, XRo~V)H.6H\S?SY |^ztthhhpSW)4JV!3! BQrwBV*b`P(]|]2qkr`-O?io6GZ1w1/yܙIQ]ɑB(&m?` 2F֍ 0J.jqv)$jRzԤ!W11|ҍ: 1SFMl=w(K[0cYXwt]ҡxa6YaX!z>JKnXЄ*Jmx)rC:+4ղBĬ--!]pӪXbJ`eCռCls\K^ߩ;VOȚ;WXP)0%6wuov?ql㲖ITw򖝳P$EQ0n-B!%$=e% թ'W#~(rRޙIY5]nq:ݸ s"?b Yu굳8w_wJ.!ʳrDz~nڽC'd-7ɮK*j@ג<%XqCEq 02ptUԀB(B!"*]랚ilfְXEwP%WOHUjcUj\eߤTmg#p>r-d beY[݉ښ8y``ॡ1s;[pq p+'l(Go8v+>[,+Y(uSN=t2fr!u3Buťgr\TT7sLww zn8ûw޶m[RRѣJ<ы'~NΊ䭭flǏ AaJP(Z!Cx2XU*JޢV===<== @TA+OB(Bu&‡~X^^hѢyC.Td2LDh6y?2졇ʽ~ݚr!HO}}}M6znĤb8o @.VUU߿yS\* !׍(gPPPdd|3ՄP( wo0,V1qĒ??ÇI?gjjjEgN/gٜOY5jXU?("\7---}ѽ̡oa9k^/f!v߮5 UsǍwy"$cp8Dq=|ÇwwwʾwWv/>w;NY]K秄wx~JC(Es:HC?<((e!7hrۻSL~?xDl2d&TD[?3rֲ>'enZ'ׇ\ (m3hqn3 9r+ ;N;-쓷4+fs%'·[wܒnpĥ{yy;vǖT,!;(?U}=;!$! BD".GA1(_p]`QZp_uYE~"* DX <8p$( !$sQ?*L`+yuWwWWM?' ;f ZVhX 5!?]|8SdI;?_3 IRGZO$8}<عs`aV(6xju8t:@ɽ#nkp]9snx~iUO1*N)1oaɝKg>|3zy}kL8ӠɄ7)׸p>FLyӶƤFtd !0`:(q"xl%B%q[Z %Cà#?T޻AYٳvjZ}" hpG#LAa㨫,d`C=OEEŰadY~K/y=^Ij*:thyyyhN %BHEϧ7hg8㸛@~mUs׽qߓ$N#Fg= M(ÜWv9YW %Kϻ)*_0UޓGnO8㩄j6-:yd&Գ8֟(hk^[0 ^y+{̽ 8 H8ԅ-kB*Qk$m!PM7;xһoˏ{߿PM=SQ?$Ɋ"IիkVK$ɲ,qqqtnhȖ`0ZWWt:59a_&̽xy<1hnO;'KkgX<1`<Ȼb?*1YPpϢᢩOz'z0(6v6ԟߌϪPp9n\͔<6cw 6]8cŖ}$&&"˲dP`` fs%%%;wE-_B^xdKawN1Vy.%,k/|d_\^h=-fx+7Z >Ž[l=zlnGō|f|Lbt}['=Bg"c`$}w0;`Sî~Xw=R\u➘px<އ.d8%.YU~R%]Ȩ<{Qh69 !DEYeE`EQzɒ%w}(`0b0mq)))P@ IDATk֬)..6\ Ĥ”%ٵHd(&rfgժUYYY)))\ıPg:u;nJBi֒$yקUV9l~gL},Xl{{qYyvz(fÁǤ^}'$ p@2qȥb焝l80~daײ{7GΧfioRTUQ' `0f+((Peҥ^W!ϷdI l6[΅@~+$IϷX,ZkkkNs ϝĞ~[VFJW/9QOFSjWu~W89նr} zqdn6UlZ~|}{-&e ޚkY Fq\rr!C֯_}ԩ}9FܹsʼnCED;N߼˗,Y׿D1in޼y̘1/$@pm깧&vꕏNw͂V/{?[y/|4[[w'K[YM@/GT;$`H)G:Q0ooCg B(#3k'=7}M+wv om6 s.iyOZB1 `0QFuַz`0w:u#>|lǎ>gϞOMM=xK555[n^on.첄 fcǎ"Sw޽ŏԒq=TU=3΃L|aKȣ_tcʯ'}1[Rm6 .~( VZ,qAy9L&e0/.;psgcCvn I!U B@!>>feff˥(JT(:bfl6G\ĎO'/dZ`0pG͛7?^j?~|3O^_1-YE=~\16yLɏ9>iLɼ$ "1BSc56/ϙYh*IA?Z߮m5;Bb0j1 111Ѧ(TEѠ6tyFAED겲 &pcig>=h#V:ih O?xG r-e8p#jmFn1eHU_Vb{c9U/$#+3;ڮmЬPR#`\,B<ϟww4I Ф7 t9XjS]Ƨ)lFVnfpӏN_P`1z6^瞹ٶ`QfbS.\k. sc,`0Ztĉ&СC7oL&SMMMk(_0}<*48Ck,@˧.~Ap U_=TN:u|9&y8׸ZsZ- q\r2$'@R$vRݼa\{׳CCNpwB䶯V(j@eBɓMZ Əx㍢(VWWXnٲe„ a7v75-g|hLbbLv{:2oFn;ْ5ۜG){фHɲ~;P.p8X %G.?lT'cu @m &,[(==GڴAL s 6xJڲeرcϧrhiG!bğٹ{]>`6^~9;;C38i!15t.o69rduuq cveYki@t`̜6m *Ș2e %--XQBh4vn믿:NE9qjMHH p%"PxkS %ٳ5N kQT6l$I$YeY5aO}B`DI r햮m:LGai:qr(:Q՚AXcEQEb4GeeልAKE$ɓtkعb~CO͓0 `0JؼPڀK(!DH8СCScl4FIKԽ{M6-] 55UIQ#GرC°C;y*O#T-1 `0QKJUTij&#Dok($jƈ`%$$VVV~7N f&ѩSnݺ%$$4O4GkP6& #:PB]uT?`U'^6Eb\ZDk:5t~_[y>))ftu3&508;=kI(ZN/(-]Svk[:qq-&o[,rN3AQ+v`7& #: +!P4I -CV'wi^z1 `0YI(-fEyz{дLPZ v0 `0%UBywځ}P FX%O4 #ېP7D?!Ӈkcɖ x}!47c*`0%>\XzۧB1Aڕ4Otڵ$`D'Pڦ&dj!Ҳ\1 - cMWQL<1 F4sz ٖ4o>*7gmD6B1A rZҿ\2 uwi^MB[er>ePD?ZB)x}.-?1[`D3g%\ 4 JQM-jdY޳gΝ;5 dzڶkys/^홙={ѣY=>\fN5:%@'ljj ]:\2QymFbd 2 `0ΑH$T~RDCceY@(- B'ݻw/_b 2dʔ)Eŋݮ`$Ir:w޸q+Ə߳gQϊlZ&]qE֘I&Gp59z>\Sed⮅;OO/Zڽ3 8WB%T!UGljm\W$9(իw%+++y zz8PTTtF1_^mӮD$)@X-Ƥ1 sv_wMx:sO'5;1 `0QYI(-m EBǁ]hFU5k֔? D%MBHJJʜ9s^yѣVCgr󝅙 I |ۢ6@]rS4x{ڤ #d!A\3 U _@r/ unBvܹsΉ'ZVUm#r{^X^KJ~96}ҤIqZZZ޽#Ѳ,~}򘛯L~uf1t_P{j*@xո~ׇ dy d]  PPZt9hF/򪫮JHHAߤMЧ-±{j8WmÆݼ+c (_UUBT6p?999%UUu:6vy"^BWD;F<7w7y]J=>*{_F<5cTnzZԏWIr@*BTѐzy(3n}൷֔R D"Z,ȷm r1"AU򸸸 ǵ:ѳ~oujg;gW8(hCǚBIBiii۷o$HEQ~[[od15iBAHd3@7Ə `ܜw_6լacc޼ͪ?__?kJNu7D7zG-6BJnÕqe^;KJEe$`D3H(}}h~+w|ʌ HWUUE8h!_/ POH>WOry%IkjInPdla"De+I 0OOO߷o_=L&{drfW’d5ьM" @%PHSpT C(qI$۷.'z#n;eWW9ZOpBCM nI=R}/.pz BA+*wz/`0H$'Jõ0-:j):=w<4rK$Yi&'@-n<<9v7[G0=~}^ p~ߵ8Լ3sspyw_7 pm~k~`^9^ <.|#J&"8y>11q߾} nw)>+aN!WJalpZvSO Cҁw*j 39_ynÎg su iw?!O/<l4m~@" @!pV IWW njkaͼ`0QYI(ܢ*'= &$H=T|VpPk5} p 0k; :BrGӆc$U-R6b’;|zgװ0pG'A] oSJ$pXoi;qGu>?f pˋ_;z 0xw|~1 ފ>/E93O7ق1 \*r$bZ$I0<88cD0 vAH0 rS63@ ??f]^fMsOϟ>`0t>$(>AC9qD/n/90F1&&jIIIZDn]xΝ;_|Ep5פ-1gcl2,˱C_cMLg2 G<ߴ|xچޝ-Hddd 駟֮];m4A~DŽc0;H7WؤsFŬQFmݺ2 }0`@N"ξ|Çرٳ(U=TBegg oYjdž=^!e\ExޟO?@~_~}IOO7Lp'ٓ$`A!ӶBNkrss+**vڵi&IF ӽ{HBC**++nggg߿O~3hs2t钖/O_nOX(& wۢ]>>feff˥(JT(qnfI"Ӏ16 #33҉bڰ BydZl4nݻw_|1}t֬Y3sLzE0 `0f"uxj+9`0$''G~PEWUTL8&#:P4^+맪?|ڴiV& ù9̄TBmtu~Zv-]qid0 97&C ^ڷ F?OUUU>LB1 PĄwHUUտiڵP [%`5 )h"  qh741&f=ά&Bb~UVV3[սIDAT"vVZխ[K)`0CI[\RSWASk* Zv -'Bf0.$vVN!"b086)h-ы$Bf߂,HBQb\x^!D!BU$`O G$Af**ݓ-Z OkVП~YH ?k:H_8IENDB`workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Mode/Foci_Creation/Foci_Creation.html000077500000000000000000000076671255417355300305310ustar00rootroot00000000000000 Foci creation Foci creation
When Foci Mode is activated, Create tools are selected by default and displayed at the bottom of the Toolbar.

  • Loaded foci files are NOT automatically displayed. To turn Foci on before creating a new focus, toggle on Display Foci in the Features Toolbox Foci tab. Display of specific foci can be selected in Foci: Selection.
  • There are 3 ways to identify a location for focus creation:
    • Left click on the surface or volume. The coordinates for the location selected will populate the Create Focus box.
    • Click the New button and enter XYZ coordinates.
    • Click on the Last ID button and the coordinates for the location last identified in View mode will populate the Create Focus box.
  • If a new foci file is not created, newly created foci will be added to a loaded foci file.
  • Use the Add/Edit buttons to select a Name, Color, and Class (optional) for the created focus. A Name must be created for each focus, the color may be set with the sliders, otherwise the default is black. The default for Name and Class is the name/class one last used or '???'. Creation of 2 or more foci with the same Name, Color, and Class is allowed. 
  • The Project to Surface toggle will project the focus at any location to the nearest location on the surface being viewed.
  • The finished focus will appear on the surface/volume and the new focus name and class will show up in the Features Toolbox Foci:Selection tab.
workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Mode/Foci_Creation/Foci_Editing/000077500000000000000000000000001255417355300274375ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Mode/Foci_Creation/Foci_Editing/Edit_Focus_box.png000066400000000000000000001204571255417355300330520ustar00rootroot00000000000000PNG  IHDR7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:^2DBigs"E)n ú0aQOZv@-))#֜iQLPrq_ߝmڻqNw|//NGM-.)F>yFB_C}Ъom;xfK}7?=w+S^̟jKd)A‰aPTTI%rD,HD4 1 ˊYF07+VErX,v&Џ9Lŧs "޼󺎝|KV9@)2b_”oD'cㆳ^Vvw0B<>yo/BŕRJq".RN[UfE)/b9|混iZOu^KUƒWL&⡔"R(P`4RՔA7(E(8NpipB!wH"-$R1 F1ebIܬ)'<%{?vJ ~Jpĕ5Rsb.OOCw7(\@*XċD*+ҵs3gZlu}uYFJ+@s8cD"Ҕ/qRk[|J!l֛05,ˮ\)0eb{m07si߿>]eYeYβ,L,iY]vTJT-y='>W}^[Zd2>1_?eiWAEUc,|dʴiM=Zu<<] 'AnqO8}J)H.Ʉza|] Z16;qrR**JaY|uuZfq7|5-`p֍]JD>Nxx}]mWkW* \]լҍ.+ahցrcY~y :#LyQ0շkuY MaWTUI닮x{5:#-su WKXG@K*Օ50.2]IAlV댌՝וIM/؉ť2=Cb+\]AQzݻtQWWWUUo< B*ZTT*$@pwww녢4my0fuhǴR' .,P(n@ ?cm&r"'GSn*3j<B|ق֭[۪BsKi50N_Tt \bSHoUȉ59˲<^ֶz^P^?>((ȶvjN7 B0bŠִ2k@{W.1-n.!'*9fG?]W߸|Z֟r]QjE-D7祟ZiPw mE|/.4(r#{,8eYknթްa/l;8L&XaikJM]2Bƙ3!sYt2]P GRO@[+-9m@;qj3${~Gj߾տ yqg_-c,O;R#4zMŅyY5%雷=rӵܜygsJ;ymUŗs/ t;t٪-1Ewl:tQ(Xб2=wȳ VI((c3=е喳 Cǖ(;ve+ʋOGNVZxvRxm&бkuд 7܊n r 8rZNII8p j/5-Nn1Ͷ6N*$[}[5PvIu:$ر :JnhnKYXOn:\E mZqC7\...ȾXڄ8emharD*y7).HWW>;4ze23}?%lBp>]Sռbw@k>r)&ƇӤ5YGSwLV}Fa@͹oE{Z.Ux eE6CC}KR'ܬӘ2w =7\V%EhdNޯZ%c0gx`3}Ӎ: ꪲr6bƤˌ[xR<σ4sԮCٚо/>+k!=]dZn@Q|@Uq> $اn6. h/#Z=(`<= 5L)%>[aKӚ 69+O"<А3} i7`Ơ|arڱ׬= 9EQCPFɿg86z骼mKB-W2v4vטgTn@k|BzFHA2c-h( ̐*Em|bZ ߯FEQt폌I!zf<$p)`9mޘW'm[ ''+9_yT=Y;.T=Zbv1D\[Pu] @{w(K@ryۡhQP ! !Q<&llk*|3aO ko{Ku@3bPա-f;cM`4 M|+pk$q, /+88Z e@Q|eLQ-^Ix?+כѲ#oq1IZRF1я*w֓W֬ݳw1Ccu|m֣}ͺ6PEHDXUr٢ύt-3qKY,RZs`>{XD{v1hѢgϞiii]t  a/W^]y?3Bp4[|XZ39[~xɯ@h4MjN(HT>D5FXrjIJZ4ߓ]bgϖʤ?SʿϿL+Kz!R*ߤ_;^1]??yE«gjm x'4\6P2~ߖZʽhk8Z |9fIk ^{.>{Ӎ>?Gظ+v4R闝t %IN-c4>~}:B3҄=b\hb^1ߖp-啉GKekã'yƊ6^Dk)%H$3LXX,^*kQY'|   [גiWwx /<DPCJ~S=!>XElűD%԰]TdݚM[^Psu KG$m*ޑ taa~~~~~bw%H$1q+(:~GzIT%KZj;.Wk{MON;{vI=/Dn.:1e#$~m"~={֏DҘ'E |OeW9{O"dp`uB{.\bu/:èLL_N߭͝~}wCO^`ukLUf;]^~zP}9͔ Kƿ2)x3&bčE <8kNf*K4{ԑV"hvU}O62Rl8PX=> {<X =ztp8{p-?^#[r2iAzOͩq.ۑ @ag"0_/#Zv *l9U$'ïeXט~2ϐ'[m?֥bn6CVk:ZƼ52gC.9vQQqLM,5_~#$?WleGذqbz*CV%hUp1X`ί{y3ߝ.(8eer w}6 ƢXth˶^&}ķH8LQ`] F_Y: k/'KGUQ<-uBkӕt-ie_kM5$z]. dމYħ/J4(f;դAa'2InIbd/TL(S~ xgW-7 c%vc zWKpLQoJƵ!%X])|ڸ85Y6 qSD+4$tvj2Uu5q)|5 *ե. `  qOgo+jE'KoYێ7+%g SH+EQQP~!ryGSAno!Tܧ16wӢڟ,4 juyU =s]ʏ?]}\*x2Y'FӮ~[@iź:ĉ[ɧM2󧳕]ޡyS|d(P{IZ5uxR4zFt:i!HqY<xHjDK`ditΖY44 h'R 7Ȣ1]7T5w]H%ރi/oR;\*leK2Yzΰ^TDWۢsY=GZǒ6Q#`Nh.Q2h(UJV5FҴ\tnur$VbYxRԽcu+u`hBlazU]a FFpд\qMWҸqĸV+U vZ^sWꮀ5r'ktS'QVB=e׮]{{%ɕ*KN+UuUiޢL9Kx6Щ~3siA Ok㸋ei 4 jכi4Q m;#&77J68u%M0܄}{@e5k1s IXfMsg xpKx7p{aniV4J0 neU4YnzEt:@h$Q;x`}~F#T)7q^ |Sd]*1 P.i$[[ia`24na]5p?#c=nm (Աlڼ ü-Ď?{_07[wqن4&[7nGI SWb 5mzrmH`qe2> ª]<ʸms$%[[)Xwi@帍v:MԮSC}rE?Yc䥟~>r z^_-0$Oo eoM-,mkavoZ`h(#־>C;t;zҿ ZKK3i=]+bcGeTppwbiH߹ ]C4blh3?Rb|+I =6afѵW&lqfGO0쭌i[zs}alhS6.51&}^Fe*fT9;Yz e!o` V_O ` 5 1~<>V%lȺyq>(8iٕ邗(7XҖxvL /x;11X%8U|?Ysf`Ӗ\bRAb Bw䥧>8]X]i %(k bF8J7ίa=a`~17o2?-=}_~,m1I;~8rG;&l՘} 8xÇNeeYBXYX;`T#n_f\[y'Yi߶Á9L{䏒Rs {ӷ5Dcì"6jGtGdB5m< [BZ"(}[+*JRҀ^;O)(ZFCϽ)tNgKԩ-i3Gw Q?+Fh68YUخBT +@ӲkTt& XɓN?Eؗk=՛]\>ؐiUV( 6<66l T ߤ[CY|Y = s|:o>ߥURE,4l[NaX-vK~>D/1RYhEC]D]@%CQE]?anNNnv٣Npa2j!u҄Xo6^O/wxDw~jYY{xǮ821 >cDrxkc,˴xFU ~ٶmB̟dԵf<n~2266ߐ+2͏;8kn0̈́`nC6=Xuty;{If΢_3~IGˁZRϗ/6 ''|2kdz&&(X|W%`81mEyPn22iaȯ83}қkNf1nRcK6˷sK' jf⪋FN+_0䄴ƽrmN0ٵ@xpmSc^X[[&xTgs~;;K}P /RBT#kWϙ5t#H>;R_.3DQE9!s>^:;JT\rV3Z*Z+Ur` ZRVk`, GFFFPP]]U-Vpk;(,@׶G(қ!qodS_p(..o^IBT*7m4n8-1n;UkJVhA{+`]+T*ITCP8feٵki`}D+U5o-W:XgWq5gVBQJ Di >صBF7bF.n^ ZD}$@x9x8 Ҁy:|@ j*e ;qSV àT*ol&[:V? ;e;]/7 qqq͝qH$jx7=D h:y1BYfMsg pJ GlR@ Ua Ba Ba BYI>SR)l`Bh ,j{*[zzzeei]6Ll ]R )梩JsΈ'L&ٳg,t|Mǎ{9?۷z IDATL>.R )梩J0 NٳGsg䁡2--{XMLLi_.y²ex饗욨oRES#1 Hd25d2r^?w\1Lfo1 fEү_,^o)[|sT%pH4b)92 &l63,k6wyvf30 0WR) K!^uQ)ssaP6|,u=܍ blaaXazQo|rrrQueYaFP0LJ %\4a?X þ3'|lsv℄6NH&'(О_0>aٳgH:4uqpĪ۪̝Yfv,SxJ 0,Y(a7rǰ,d2DmZH7-U{zC;f%U_M]- вQwUeɆaF͹ܑa Sc&}uzז޵D[q˲SNr˲Tέ6}6XU!J4H٩u|6&g&} #33}3ə[5D(m%^LtuuՕ2o:\ŋ; rժ(0]/G%)҂g%W" ?}9E|f<:9 yȊ_:n'WmQW ל0 M;.OG5%_<,¨Hd 6N^w|q̭$M 2sʣ  AZ[=к ¬Gey^Ht底yh.[ڴNуmŅ?⇯Jy'b>/ewkvw # .:}h Ҷ@uuRGff&20X]ǾUנ7?@y;@`c,HtUҏoH|" yr|uyG'd!~7L޺yG֏O=lvR53{{XS4VG>pwpoE'$!wcVVzq%fZ?G~X5ӿ0MVd8Gt{'eD3~Mƾ7.Rek73~6ҊEO xeۙ'&))-sG2㐼h r ko69va#4X00*7~X58/$y*j$0oǼ OOb|̋/tM̾_ 8[-mihˎ,O7|=)!6먎˓wӻ[Ϊt#o{6@w&cBmnG ]8?d+0Z=u]|JR)EQ(< -'A;n=fɌ\0B*[*쭸ҖxBU~rH$%TZr7^]CjpZaXowάOt F:m.0,$Y+' SտFmtl>`9#c?nAn5iEgmƬ言FRx̸~xi6(5zH(%(JDQ""Jt$ړ+_\U|YjNB}o}LӖ>{Ǥdčxɘ`bdWy❿m h7|0 pd WKbX[{M ~0܁ :T*\TU?ם{gQAEmA 3mp,ZiIXZ ׊Z7].%%AI&*cJ*& Wrw1 G2{psϏ{@D zhܛ<@:B8#Eko[qۿXe^Ν0GO@vRGҊx}]=~Z:yo5s_c o=,4BF#J~ƃ=dJ5k֬Yf}p^m`NL80cg'S|[f>eWB#Osrez{1cz:>Vxںg> zIe/g_ߙ|D7}¿ vV.Cuz OxѼF+j@(|ٳg̙3GYuRZZZ$I-/쮏S~sFtO!K< Cʋ;=vuO|m~7.bg}}UX_} qO:tGhSr[~}ItWJ\oV>kiڀ'O,QBdYEɓUUUZEL =3-t}3ܑ>Ə駟nڴ)..n포r:Ǐ///WӧOGzyo3MW.j& ˲ӧOڵkWSSl|3_(:Wg7\Acǎ>)R>CZ((8nС0a2iOIg8Vڻ_CgЙV}u}1-Bȵg#!CB Bvqvmޮ!mݺUԕD!B!(0B@B B-#̦&ΘVhf,Zpmױn۶I0/3P:Ѿ-6KM2-9qe]2S^ jW5ѭYUYnrz]% )d٘dYhK5-+@|&˫'BȵƣG]5@Vf@XV|SʥK`R6~avP#́o)Iz`Яsv{!R AoKgi?#9l3v ~SŻ3ô.>h#f,HB=S+BC[`p@]&Ns7ܠMM@! 9@DXR!hҴǜX}ZQޞ4$d :-? so@[ihW']Ia!F 3oٽG-RB7=U|da{ihPy~&k;0p!sjxJ^Em=j*kWTQ3<_o}cu:<#**Mw/oO2ny[Z9#O,U<=BH=5šy,%#іiȬ I4Ee1H- Ш%>F!y:XYfoWRו6ӄtɪWwפ$DY+@BpLCCGil.0 M8}DsZ^{_|m#O[n馛]wVCm9}`~;{=ψ B5nG!B!(0B@B B(B*u[\ @d {KHٸ{S"%@ZDL7m"f.MM2M4)0BU};^_ d[@lhG qid/Ivh48ugZc B!WPbqA(jneB5ϞK!)%; X}G1BYMS9T'] k !'owH|qm}BhKƻWOc}iHxzlɊFiO9hmԢ V__?Y mB61+Ԓc0"<hmW\ت%+盢N6}ԓIkEG4ŭHšy9JBBg sSsxΛh߾}SLB v0a+.=pK!B@B+aɲ,˲vE!>Ei[]~Acǎ>|i@*Z.PEQ&WD!'!!x@!ă>zeYoWSN'O=+|?0DGGk4oWSDQv-1cн!}KFoע_~`P!˥V]~qM >rBQpr[MEYEjB |Mڔ7%jKLv5!u[y^yk̍- snm;6$$2is;BegZRgdž.Sn|@ݫΞ~v9qc|y HԐtjԥ/;~SGKgNH<mY}} jKϯ/BzNA_FE`N uM-mCoLDii%Nw%tz&`!oPa0lĈ_4?46!Οb,KCiƧu|ſx1BHw`Bd}1 E~ƴ†6% ZԖ~@|{5 jn%* lh(8#}1@@L#WS/!?U2$'Xr1'BHw`f[2aNEkM#GþZ#Yyk7WxZ]a,x\Q+e<Z#񥙉)k7lI =TnOהjNMJ_\^+EdX`v@k73+'D!kSǬϩؒtS~z[1H]Z!$asxji{Sb̍;pKooz#a~s[qF~ǐg'NNVˌOϜ'YKY!Nq1й7fZ`, ;B%; 9kz}cZS/HVؑiA}ڨ5w퐰xN 6@v\{w8f㹮5螭qVWϡ$BH|,˲,\.oWS۵DQ] 8x`!9c˖-_|Ŗ[C$I $IsIZ\+_ :.I%IrJS~{eY.,,t:%I$N'I9$\-:f%M{ZZ&Z>'["WY5$I1?0$I?{1???,#7t\1}o>:rdXu{I֢sYE7ga;o>C`}1 0-i%ǥ6er9G}6>r:L[BX{kQjzVq԰ Kٷ/z>g|^w`mLBL|}V8=My7Ӧ8%ot_u9*eAi60!>s|yzs,dH)23 +  0wu=zsܕ%R `+p)gזƙgPwtp_ eN}fVw?GSF\.JDW PWYyyYYIYy#="({ɴ V|ٯ3"1s/H)m۽ÅjsmU%NJsSͷܴ03њ1>\{;įb ]U]""K ,zL}a0ry 2 +W Ja芁ѕ nVsFn_T.tc<gPm apdW/C*q00rvRc< "UU*^y}$=+vT C]IꚚjkuMdJZ2Ȇr5݊z^}}@~ʂzU.e{w'k,bF̥= (;t<}o~pO|V˖-[lYjj+aT*ah4f/6yiӦM3MK|yk_"pj궬$K 6@;c: 7!* 62r[NPn yj9q{9qQ@F9?/1NvKC<p?Ky {2%<,ǻ+eY뮻H$wͱJRhq wWx}mBx ߷۷oʔ)ZR|'v7f a7CJ7>ݜg_2t#®]&L~52\TTd6yp:? r !울g`SǏ7C=dsN U/ꪫNI^7iΝ!!!^:!]Op;;6里_@8CUFIKKk698cjN<)M ,xɪd}#Ə駟nڴ)..nis:Ǐ///WӧO>ry"W[`Y688xUUUvjjjr:ã鹞k"0x1Z݇QM_-PEQ츥"qC8p J:0iZN>@]!}#!!x@!ăJzz=ͤ$-SO== 111tOBHE۵BCCnjCwk -A]~AVk4z !o\.7{M >rbBQprMB!ѕT6s̟ܶ)yŕf+'~ŜXXO5)9քBMCgC 5LLf[ņ9ۊO._ֽGoԜBHJWP|gK7U7'pq2h]7jB/c X BL+=?3_Ibcu%rxFHT[M3<9$-$D냗B.gM `LY4]V;p:P'Bb`\rxjݱ!N| BJ iS+1d[92T +8u*!ˡn!31hɘW^`ӫ뒐聜: l=oA!נnwn3oL˘Dzc8hl’ELu+(,BObzQyF2e ]ҷAصkׄ ]>$Dߟ5!rEB@BBeYeB|Ҷx(ǎS}T @RT\(w$B!c !!x_|Q׳,B|yvEhFB|(ޮE:fW!o Z  jZy"rjk/>\C! nX@!:C]yAɠH^[$\\yB)^f/z>VB;0HuۣgZ"VlXr-@jYgICswD!?p(MY>sԤ ?ҮE){}3%<>] \\Ps@!w%5m/9ւt jHy)s:VI4 ԕnJ)v؜>7ݼ_yz洶 )FBSdΟkES~S%Fܩ/S'G6v~5sczggnL33bbM^)]YB!3% }{ M>Px@=ͦjt" jcc^!\uQcck܃AAAޮ !\}|20|!\ޮcp8>lSSӄ ~~!/w}wΝ/ٯ"]+u]Y'9=;*kO?vykH!rw*-˩T*{ k9T!~]3܋u5EE;E͘i?xr877԰*aT d%h6=Oq:,qUQ617NWz<g-# ?j@⊍yg_@]saL4Z!S` jSvW}|G^:;䥺&p!.a.cNu!4а;73uQ llž)Y eGJDdf%[>;rA do/i_{!Bȕ$ɱo߾?>e;?/ . G~4.@._*`N`)> uԸI=Q|ԩIKk9qc/! 瞕BFMztӖNX{H<8*F 4-qs64 rPV^O6 +@—)9sz洹 2dWi%,g׉Q+Icז/JWhy5\+6WVM/I2W${d^ơ%2?Pm2-GZa%6kndK۶1>ڵ2l*عiikk=Q3R`FjM= Ҍ__VZ.pcy$+њ=o VUK@ԭs*jk۷W!f[Q ]zlGH_̷|@(iG57jm__n @lD]յ5W2?5C$4krYiƥY0dl(ToO2ny[ZAd !q97aH04Xa0x8,C!9ej 0-/WuJ(sΥq].}O[Fysxj>2ߓ`Ĝ3cg,]szBTn涃h=7EfUXpO)*)tYI8QrZE@Rù4ACdfҼEk"ɼf^`W6OWdT㪃%e|eu%vJ !0 $pۍ?i5Yvon/~(/z2w1} jy#Lm;-ex qZ^-$ owS$K{z$Czܫcl{sՐx^8a)QB0 ,WUU:p DEE1)]_O*A.`}IgB`[뿌wք뺌tUBkiiӟ+CNT~Nn7̺+;g9S̙jr\lRCY].,x7kii9\.Wss˛$Yd '.8$pp.uL?]i;c s%?*w~+/tpɢR%nmZ.Fvw47PP` \$eZF@ųʲKnX{Wu·^ ]eXu8Q@[ed%$8]Np8 3 >t~~!cZD ^j\K +K2$?.H.4Y7?w{[#R'|;j<.ɐdߛ6YtEv[NmURN| tZw}*u%B.RkkkUUըQZri4e'#2*^Kb@;ϜG@2t\BCjC$x NeIAp8,9ヒt>jeK+N$e. Qj5 PS ,é àtG8iٱn3zh{c;u!j9 T*F0,J׏ed0 ?7fY}!"XXUKP1.CjMzΨbo9ջ /fg9hoy;gof6<2cApV3e2]\80 ò,qC vy7bU*̈.Wsȱ ˸WMȲ,{?Ƥڭuvc_9!A}^zy+J b1׋ nIDAT=@%Yvɒ .2èYV+`\---z}#+rZ[[z}@@eS{,0L˩j{ʸvi=}f7ϋwUJHU?hїSްv9YUJ<~̥x_;w[ 6վ7S?_~ͫ=o9bT*2̰,˲NTJP)T*8`,˂ 2Z?jXǪ9ZͲ*Tw`YDz˴,êNtNX~b91pҭQ8m]1ލ?S9=XaTJũT,˪9c4,Ǫ8V' t+BPzc V+wD`n 1n1@8#|4*?Tޗw{< ~3rJ:;͛ׯ)1o~T*Jվ٣2ؠj^/I,睛p85: 5Dz X=M0WeiY i-^4XUMh _o!Մ F,*KvʲUNFc5;@rє\áeQm7*Gkvn1hT*eYfthe١BJŲ|t*UN0`Tm]*T}|a˥R0wsmeYw.Zc*aT0`]܋,p:jNՑ7՜ 0>ɶu j攰 0 ,` N2V8u%B.˲OniiQA{oql@Q܋Ľ+f/kδ'Jl8]*e9RT=iim>{A%DKhgT*nze`3SiXFԜʏSi4֏SOR锨aw.u?'Ic*!1,3nbݖsvOjԬFj4ΏjXT5TZJh \c@ h4W mGHpݳ5t^}i_l96O:a_,@3Z hp,˲~}'&>b5{ޟ?,,+; ,|Wu B*0$vFs~$aZPSi8VQi5֏i8-ҩYժ9q:qj|awVa;/ołLj՜Z\:.pzy7 ݔsvjN˱V˱:5fujV~p*egZm$BE>|xcc#m W[}y6zA⪻^/ӪciT8UbUٿz|/_?1;..- Tbr~bQ+qV-T4(!f >ϯ7O5JbTJ(=Im7InP{Jsy ?YoNJ˼_L *Ow; |ᬡ+}ec+pd˥U.yX9j@Xt$BE$e!!!:N>lmﲓI mKx}xs`Nk;ex uM{)'6nx_w]כCtK­|OHeVթ 4u5? @3(t_7Փe%RS_̻He1!eِ1clݺuZ4CGw4tPoxofK9v> Z 9}[?_QUm# z^WГP 0꾳CrZnVwc!t;vl˖-,.Yd+lo(~#z#GرsdЀa=p)ƒC_6v"p:m/sˌ3FއVSSuV KZSSS\\|ĉI&cJ>WuϞ=6l2dHBB˜1c.BʥơC>C?4NJ\3^Ǹ\,ѮnAGvHr  7|1ct:rwСŋS` \YEQ<~xYY޽{niӦr,rjmm=z^^^:qĩS~ع3%6=zoٿi!*nБ#t:d]U+NƏ?iҤ#G)0B$I6ѣO:p8z^rbFV-b/z9T*F3hР뮻NZVmۦDc ҡgΜq:WTlPn7`Afy9NAΜ9,rk>s`8N땻B)3}ђ%K@c '˲<+qT(Aے<o t]֢w /^LU !}L~z3Wޮ߯c W۷ Foci editing Foci editing
When Foci Mode is activated, select Edit tools in the dropdown displayed at the bottom of the Toolbar.

  • By default, the Properties button is activated. If a displayed focus is clicked, the Edit Foci box will pop up and a focus's Name, Class, or XYZ location can be edited.
  • When the Delete button is activated, clicking on a focus will delete it from display and from its foci file.
  • When you are done editing/deleting foci, remember to save your changes using Save/Manage Files.


workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Mode/Foci_Creation/Foci_Editing/Foci_edit.png000066400000000000000000000256631255417355300320460ustar00rootroot00000000000000PNG  IHDR 7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:}lܸZ r1lذ;]JMMp(VP9*)W- s r |f0"d`?$EO>TU_Gswՠ!.U8rʼp+nJؕKvӿ9A:MZ0{4Mh\QUU2&gXSm?$I!a!8;ΰك3F-*:S2tuiм:[UU 2/*ef릦 q2iy]U%Zꥌ\O>w; VK|XYY+MQ!BO[/ڬWhks ?';u8Q;ԋU6m(**`XDZik/[fgDRJ (B?PBp!?[x>}c3d0R*HtD_1hhé u^C;+!DUUU,"wnZW n\.̒~MCPp#ܼ+]ڮ.^8ߤi3!B h\h(I8fx#u,ճR]k tP7::1j&q]G<1+KQQ58Q4 " !gw(_#[@TDF f<'Rd)rwEׯR]Ș-ӂ@D~,If!%I ZW!ahVoXl#w^+4D e >?-Kؤmc2< M "4(_[_30<Ҏ=7sM: 3*zٰI_uüNO$U\8@%V8>+  B} ?'kBPc8bʼnP}ٸ})L*@mcG1]u , bV BĒbT>fnXX|{E},J0J-H:981j](94bYsMÙm9r*Ԓmk,ڬ 7JнP4A(ζwعq5mL8 ]B'h;ltBca%5!# |;jُeVwķ6D-2ƨ2KԶSO#0=; ]Wa* 򮆎B=D k@NuK6C_;84ʘЂ| /;(*յ)UU%I4sdY] PkMKOpغgaSRX7.!n!C¢*.>w/?wWle0/Yzkóm 0k 655~v6ml6ٸO #977R;L4 /vOF񸫪nzUx<Ml޼ybK?>񬙞5=sٓ$%>~| n[szTWsz ShEcZe^̣]t*^^p酪r9hlKHmBs;GU-!D;|Zy{⓴؛ܑp!ʾ9#}ByC!I$Ir܀IӸvwκl}>\w#=333333<%I}Ke9s$I/?4'3333gU,߿433g&|t)I`H$ݞt1]³_)FJi芄нVtT\hܳ=&dTO-fL*}wtgp1ϢDŽ󆵀djY4 q.TUUU1Fc.5|yd?*~[ޖ% ySc~To%@{ϟ8h?mܬoNLf>zvHEB]ѷ@; +†\@iiD !Z k?zQ߷KgN7kY}23qؔixM|ǜ.ӬuV9;6}s+6p?OLIM+ӓlvteriu6ӑȪF gЅmC[_t}'7yGVn~!_/v'ӛ*|k.}q/^3ػcfX;[ {﷾8 e/+$:cׂ勻K-8t"HR13\-^pݷ۲loYazBy94X"H4f(qG|y n ?z^[[8u@etк%:kCe8Pі:ܫ9C7 TxBo۳ Щ;DȱnSJ V %!KD9rP!ҟ0g`dl]6`6VKC(V@Ɩ 6a 1'5.Z&ś=@Fy(TgdGFVJ,icY_X?.# xcɔ.vfZL~{XMCw-i1~gN.f9JίQs^ƒL @|@G)+> [Vs$i,}fN':60 <2L z+1c;;^!=&,gZup>0&\8]PӺ@-G疬)عRSffB ؿ^_+'Ă $j$,QLi%D`kثE"3΅\fpn]~ o71v,@uYe;~vJʫ,cz=|K[ 5FY#[6,Y p،, AC{u),soǪ2o K>`pTuS]Wa\wjܬyW"V; = hҮCuCΥvB{B@AQ%B*KFc3cLjnGU~//(3,Q{&< ܮӄ\pp"Sм+4 @EyI*fO`a;%e>)BqS䁝p9]K8\"$>Ewt2.?¢" ) 0%T.ueY`}Y/kwzslc۞Nwy}5Җk>PtW *2(#ztg&Neh׽0wz߮I<Uҹod}_[QaH} P|`D@ËV-.++~߾J̠'Rc(-9QxLaaaaKz'Sn_}b I$Ie1Jv}KUu>:yjef20D2$AaV\eJb֤mf dL$1DdFiCNp$j{GқgB*ؒAju-  @8۴fk1vH32 vZ?8\_e/o_ݿ~x HsrQgK]/O$mHm5ņƍ UwUE9$x\.UP-XTy\Wfj[iii6o]7MRMzHoS9f5)!zPjQNH M5?}Vvu=[W"[kibŊu92,,o4VvR?pk(4M[fM+irA qqD.@mDPFW_FQASc2LW4nPQ JA PY/,bDpAjJLjDZv5BD"[ejUe]=JV/OUj@ #;=f/FC ^]9MK~!]bp02a'M#.5 e|z:ZwK\h,1iº,n!)k"jd!o!PR9xKT%OVy$Fa?Aird8TjIؕ0&)w B"N1H~"ՆHˉ-l uT"&.:YRR~k_K1^ 2A8$(t}>-k`B,|RU"u_M1\GzKFWUׅJq2Wp U e~+8 xƟ7l%$jnhbTdKe~W\zJ\' U%^Tpѭ)\AibaF$4N>q].ߒ v#M01E#gȌLMq2П:k-C8,U}p_kYnmIENDB`workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Mode/Foci_Creation/Foci_Editing/Thumbs.db000077500000000000000000000330001255417355300312070ustar00rootroot00000000000000ࡱ>  Root Entry!>256_40526dcee322db51*E+-+L(#iC-+L(#iCJFIFC  !"$"$C" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?oj(WK=G;Bْ Xm-cVlA,\>!ax seIMNm\M)J4Ғ]Y-匓E-Bqp+&OԄ\1EB6`}u 9mgW'6$ccWuT+%-ӱ~tkߝi $ߺ?렳3\F?ѿ:Z/ k?n3?oΏs=?@,%p?3\F?ѿ:Z / k?n3?oΏs=?@l%p??`tO:UOyq$[p"+"`Aի/5i<7-ԿbV W5Tmrk>:>h `g 7Mg-?qix>V ^y`m=iė9˴K9:+ ӏttiAMs_~u7G _8fkߝz7ZYK[teA-og~u7G _8fkߝz7ZYK[teA-og~u|b'Syd.mڶa΀3oΏs=@)p?}ƨ;\F?u m\mOcWt%ׄIbKeluQ, =8iKM4[MF[dHQq gگ4+$PYu;R_:誟bo(@!efc 85ξ[Kwk4[ (:rs+WST{vPS^$TL[о%-݌QXuf +% +:c>VYQbӢ79أ p3}1PE3I,qjH6QC/KKK̑VHEȪTj+-Ιqy{qpdS;e.%Q07\r{}w0\Oqc+r~݃m}тjC]]r}?R.[R2?H_]Z~-7^8㒤7Q75;Bv%\4R8R{rx$Uti?(4y?ʧ&jms?#4>hE L$o(cTQ @9O?w~_Sa?7_?cS_/ij]m8nV"iWcodAҵ]B%?7nmxԟlj5KWrH-uZF˙آMۏװ[CsOy~$T:w_ɨifZwiwwXjk媫!l Fqt&Ҭuom`Abb`p0g<[eRmʙn G93췿n-IfM|ybqw>jVֵ OGYAhu+#M8 %~ow72\цX$F[adȭJI6Q_ @/6vf;ؔǞISmfPQCېwO'V@.x/KѼ9mj:Σsbev1kf,I"gkaA\Z; VCxRyG8K_n>i"Sٮ&|-\-n()π^vcimu2C3dr>bO6e }oVѥۖv# ,vҤr}|Y6*:iVTYUT*YWjOKno5 _AnV4l6b3@?3Ժ3Rg;Y#Yg[wFo4F:HNߍc-ԯm!eو P|l zu7w0Y5iOcΎXEeO U]\s}/UL·qy7vq‘ l#yܒFR2;fdv=d䳱,ǞI9+Ӽ7k:v6.$H̥umN$*ɩi=kqnDѶ1k*Eqԛ''TbiZmMkH$HxewfZ˳.| ,fY<9 pti7֖ZmI^-r[sTĺ,5[d3N%R&āsu5 `/nT~(ogN2Zo3ae*G[8kiy[nt&4Wg=)c2o4WJl~A\?$񞕡3ޭ˖/.|ÂyݻSs$pKy.-yQǽ¯r{ /\^\xz=Cñy{ Y̱[~ѱ22?(IfyH{4GS2ߟ+قqm 1Ф $*FƼHɭ//ϼ?춿(AidltI'(-Ny+X8gKeU)v9P76VX'/FqogCq}oqtE*琒('g+4)Ӣ6]ObnK3~H 6)۴wۖ_r~5VPIlI/ܥi.-Ÿ-H=eH#.}tۗ[6qmp2@Y?ya?P?,?Wk>ϼ? IYKY'MbhhUꁁaf[4t%`A8.cmP[tf`6[޸PAp\@Vh^a֓r~5xD-a=\<0Dz(mf-g]k'\<794E}L yl0poa?P?,?\5O_h(#GrACNS춿(?ۖ_r~5{춿(-| ʻ\Ey[~xDny#h8ldRx=K12 MⲴ6k{}FPrq08UB]Gr(ƹhռ?iw7#Y^Cpwr*  h\ 7HKnJ䁂q[P>|)wc#ḼPwoIۃ,`ZQYoa+;5#sٰ/Iz(EUU APH$GCKE2F%YJeAJZn[?ֽcXn>ի6(Zn>ի6(Zn:mȊU9]8(j413ի6(Zn֬u}C__?]ju}Cs&q&vW ?11Cq$]eq{rO/sի6(Zn>ի6(Zn>ի6(" Q*r qޙk؎!9eWgq\(bf|L8mjdN}H$,durwb6(|FqQNBK?ֽdY?VFQEQEQEQEQEQEQEQEQEQEVv_hv_U((8Ep2x3^o:(InI#d)STpx>X66xiyz-ӽʌp&H!I[= ({#kKFPwNƦeKY(v5(????}a?PO:_i]YFJeOC ֣jAh7Z!Ç/Z8oQjj?֩xG}8Ç/Zo? <֣֠jAh7Z!Ç/Z8oQjj?֩xG}8 V22FUh7ZL# 0q@EQE痞!n}tL"GU*n*tbbxٷTh$#I0sʑsĹ_'Wf5X _Jz`~r |EjWKi(!md@K*ĂF9 z(^YMw}f"W “ĺ伐ʷ3ȾWYZe˸2G@ͣhIc Bbv2$#ANkGYi7NIͺ@@ W-Q@Q@i}z4=ܛ;c>fT%7x:oZ}ͬC UtXTv(=v_ɬ\זVٶP#i$$1A)Xrco %6a6LW$o&WY\'.dX8̍i(^$cn\"n9mYdC(td`*}P̩y ;<ƴo=ce^IRğ-ʓ*KP-_أ/VKQk?7(Ek?7aZh}{}>o=gaZV}-@hb[xYV}-GKP-_أ/VKQk?7(Ek?7aZh}{}>o=gaZV}-@hb[xYV}-GKP-_أ/VKQk?7(Ek?7aZh}{}Iȏ|9+_&㲹#dI)ay((gSм3u5_7/1>k3#k\K ~vH4? z3syk̼moMVRҤ4dwe7nIY_d; r:)_߷#u{Eo39*l&e$' fG[ƕeRZƀм.hSJ`m@G0Pp+b#85 Uk3ܑ57<_"ƌjKL~DʻW% ({酓^kCO}:;=,`kq 0vq)*& Le<`E(;IJ;ck]{6AU mLu{ĺVKUYi&ȁ0Xug\9Qp=;OGzk !L |$ |a,!g{uK FЗg.ݙ1srЁ ύKQ ύKQE'4E'4EP ύKQ ύKQE'4E'4EP ύKQ ύKQE'4E'4EP ύKQ ύKQE'4E'4EP ύKQ ύKQE'4E1}2x?"uq=)PEPEP`g803sEPEPEPEPEPEPEPEPEPEP nQ{ Y!y& 'd$1\u[$׬bŊ\K:[F)*di ARe3}+]S:Vivt[[Jm ş悇xr\hF]n0$Vo=r=ib{ԳFs+H0̿껊'N;,VC-O"gaPT[9}9Z躔^D!>a fg(\O*%Y892|]O웻m:w*L $dAbOdt'<`Gqqql-G$G8 Nx&}o[·EHK,*O<g=k|7c_{71&?5OHy~+^Fgge!x=4.տiirGW.#{u #%U^-}>tiq7Ɔ23ɑIw%)퐢1Pf#9r^Bwkm͎4-<隴zfxY @g*. ,g:Zu?workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Mode/Foci_Creation/Foci_Tools.png000077500000000000000000000244741255417355300277000ustar00rootroot00000000000000PNG  IHDR&lVW7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:Q?& ''z{÷: o95|o z5,AXbE!9 G0IżWZVa%*y$0 ?|:! =5wԷF*.!C}zG.,IR@`dB_`$kU#!R$G ([J"!Q 9.!ГGd| ]Cu!R@tJD8 '$I5*B˲Vm`i#{ۙ%˔5e:<gGTpyl&;Gss? dog6fs 4蛶ϲNJ<|ɏn`[3}JeYj2\wxVI+JmpOrh绚Wߝ:7Rn$PB@ };J~HVڿVmSJ")YbaiUF:䱪oq%m`ќ^! CJ'T ݗB2Rce0v$mZsaưl.Rfk~ q1ͤ,yZ\aѠWG>3+YGLK>Vx֑鄧g.ݾ>Ͼsٽ"[̮*rSؔA)(HiC(Zq,>g0!=_6**e}|K޾4;M7~ݻi_~NI@2'ucK1;6N/C3BռH`䘞[S8qP#kV;S^s' I婵%&5'/60UY}մ4*BCVSL\6}/ޖq>눱 G V&ۛϦnvx͠)Kyh NmaC ^ iՉf){]k&|:w8jФ4uY$A6~D[`HƱcdž }*؎T?lV^hTU#rQ׮]t('f=թ5hY@ZyVkyE`6M炜׸s8j^VagԳ2;755Mth"""BV{bK7n Jmo=6k1T [[KxMBEr[54&ީE\g2kX¸vn77Mf J$*T!H"owَ}E Mn`r[v(Y&)$߾BQR' 9E05? $CjV5'R]Mh=V#j:1Rhq\1YÀ! ĕ4 3W}AA)e\w,EcٛxuT+ V#:Bn֪BV=x`bbZtu l`8pgPdËSJloQg,;|iP uE;NmJ!(o[upgZ5j7w JO!N Lo)!$2p(zS3>LG,}b3ܡ; (<`)O]w՝9 uXܹ͛Ϟ=ۯ_VZQ+4 SPz{V _o-qРAU)IF0tEڑE  9l GA˗/gdd֑1 3ڵrW?U@  Root Entry2> 256_64bc1d728cfea11f* 256_3434bcb1cb700553* r&  !"#$%&'()*+ < JFIFC  !"$"$C" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?` ]`4U !Gy8pHϽv&܆9\ř&O]k4d2yl΀ld:0;o G֯*ֵmKM Aӭ4yZT8 *jRLeq ?=]kZ&g]-6aANq^ Qsi={۷qʪ?hZKդR~V2;sZwח^ԭXY-]I'*koxfHЌV X9.4o BxU!HFSI%?kQjo/ Gj:E~5Ƨ"L&TC"sđpr/.lw:Gu5?¼^(mcMд]6FJ]E$tdȧQw(W-`?uoxC AV[9J6W˫AW9mXԥ&e`61P* ;BUw#C$bAP:kO cEfxhn4#fУ[:Gu5?°{EEi+|&yQH I ]|Woj:][&~aXbLY Iκ}'QuAcO3ž05xUtãjz=t.bJF !r[ j/ [ėN2W=EkkZ&IiVZ0,dpi/H٦YHEt6VֶV+xC3@bp;I>ՍđhR[:5N=CAj t}kRMKWsuzIrKg75Ռ 䵼b0I,7a!Eofm,CTn':՘5@<>FWK|'=+:{] M%f!XV, "u@MƁ,sEڹRºm:CNQ[ԧXjZue-z宭Ka蚢M8پ%4'q'+n15\W2)iVTnwTpߑnv~+ %5S'9#V^NM8|(MLohL-&>n))qO1q^cEw^&֭HmaCEPñ=Ȫz=zv,B #>PJԏhMD 8ڙ J.~nNZ(WBۦ/I>n)$6("]29;C^gEzzxLD ʣhǫaq{upi,p}q\1Et5Q@Z&n%9Z&n%9JFIFC  !"$"$C" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?fUm- }(S> se9`zW}K"8Jyp {EEmuaou&4K#C,k2FJ;+E_ f9t9=}F_ϬW=+#n/f1Ds80~\Forg y$ʣDn2|˙H43\3C4h|BF(p9,q@7Fosy?slI q2Oii7o&}VzF k;媻A :w:~֣mܧ%I/3sgu2)>cUxZK}?P[Y 9p}]kj-ؚhz֯k ;MF@퓊M]Xs䚕nc5߲/O񓷌`qۭrlq|9AOzh~(k~%ҡfuBp p+z-.NPM _:bρyずtvYI7+]K]9V[vB>Wn$O*'W#o.}7B7w7B'`+oG}[ߥ G1פ?YE._~ggyQ؅)&@N~U:rA?/Qa>V€>}rY; @'oNo~-捎 6JNr3v^a>VKK<\z}[ߥ ;H[#ce(=6Ml[],𒋴>G'Oմ}FDOE۹`X}2*^5uGMy0 E_tH-7aXÎh@^?#ןuVǙ>_Ϸ<.0?{A ?Ely-e@BG#VǙ>_Ϸ<.0?{A ?Ely-e@BUum;/OPu <#@fbzǖjR\FD^\_w'O|@vqYHS fFW\ 61ϠY5m>M^mgr߸|6JtK"S+2@h!") O` p8ަ(]7*HJA,{ ' zWO?_qkWfi.mT3n8r09'wmhpӭ쌡Aы;zq K~6eĶ?v2n7`JiIEٴ[;;4~znz"9,溳cޣr+VM@־&cw<$RX4gnp{d {ρ<u1öJq=L FO iK34@}IS [N#ZI&kj줭wwnby}+&H$h䌩gC qB_OW_KW_K3'BЫ"h[bW¼? _¼? _;_wn?UG4- q+?^x}/ ?^x}/ >;BЫ"izxv;aw2x'83ֽ3Wڧ'XxO G,q}A (PiM^ypRNU?χFZ)X\DU[Ec;xϥOxwH.Oht(fx'xfkGNnծ$õF6AH$7UcoP0qbIL}ķSڼ:u J b/ ct3M*˴gNp=*׈]2MV4Se%YqA Oha9?jϝI5.\[Imuy엋X`%[ H<7.,R2bnp:t?ϝI77>ڬlUk#_FhFяchM5) H]=3ZwϝITcRKc, ڱM,|9w_ 'Yk Oз T3e瞼{kSfz@\k[{@'/ Q2ikQ0*q'n8sTu{Md {tסPZ4If`:,I-Isƀ6?[|O&V; b2k g_6$]ň _s叟XT >'ϝI-nMPvSh+ !=1ߥ_ٱ}j#X$&Eހ)Z0RbYF6d cV]?kq žHxC .6AFVlḡ:Nz8ټ'hܻmڛ[_ AR赩u-Q0}Z쨢(((+;uY& s }6ԥڠXIL`6` zm94!An :`z56;b$tb/31Ȧ (R[=:4}ؑϚx903J(((.>* >* ?ԵG-jMcKT >_kր;*( ( ( ( 7o?hwy\OvV}Ge,?6ON} "4BQ*tVEc"ffHl^5+Td#n]frF8Xn7=L|1M<c@Tx6V6đn1A5IjM!`'Sr:ճ}E-gNUb O Y əԑqǸjh]XH v1'k_ $!UdmRޙ}|7Qo\'x^MSO1FԤG\<UY5t;KGBƒ&}sn3Go[} 瑞F䯘Ssaw{DZ+}q~55)c9W} 짔xF|7Qo^} ׉m[܉"[7HJ1}0PX 'ׅd/sh'O'ܠ9? G8m(uoX]M8˱/~g#5YE*욭6d[Qfp$GI!1@ۭgtKFb3qx.O4yTee'Œ;~OsQ&ncl7gC)v9'# x@kToXc8v2@q9<Y#Gmx*s x˯ ռ3j(t *'j;?xrqqȹ $8q=&Ok@w3@t5è_07 HzudjmMqΠ"r 2dp5ug«.h[NXf@bvO/.mifs$,r't>7 /@5_}G*qbdryZ?k3YKW6Q-C=JShUTpW֟#eT $qg(jOI##I>?%hۧ?Gۧ?T>?%h@{ n?n?P@{}_&/H#4}H#5C}_&I,$FO"HUF&@od!vw-cKW-~zpVrZ^tR[*0AA#zBnu6 jAk8hm i~+ҰnC/2  ݁0zɭh[c}Ra͵rHO= {A=3I4f9D=UH?V DU'I/ufXзss#;rs]. [ye$s1>4{O?gj#L@GB4?º'4hE?O=3Oys:jls Q'WO$?Ə'4sGm? $ tOh#1?H;Ix+Ћ~T5뤛IEVs?i.qzmuaX6\y{AǨ̤ }Wgınc⃥-@/_f#eAI4>|Q y -0XIkAnpEYDiow%󑴹R2qq⵬ka* EOmijEVggl77!F?>ijEekz#ҮDJo`3 Hy+0Xo݌r T 9dsZ/ۃC̒/uz\_A5'#nO߹m'mf3Iw\}ϕ#lsOsc)b~~IFVZv;9㹢˿4:idfat U;pA!kɥx[VE?;6MurO*~pƝc bmuv"C;Y ?{|͓N||jڥdVBU8I#䮦Iq͜S#`L)(HP]V8k됛훫yՊy1vrKdAEQEQEQEQE]ﬣryn: SI,+Fʍ`5kO7َ}{b[IBַ"ȍ%13@7,?԰O fP:`qW,06#dymĝP)61aK<뫉-縚˂>pޜuiOqyGzo`3_mWjg:ܗiA1S[P6kjKHT(0 =6Ꮨ>jꗖr!Hv= 5jWZ|R{).mKl<@pOŒxRgېe*vu}I(@Фk=l=;8~R]x#R O uƛjb÷DgfA4?zYشn',ѐK}A57gžh얛捪[~3㧡=HRKȝHt55oQзu_ ף_ ׬տ[GƭBJ_H/^H/^)k~ , \r7J/<^c\iIU`>$?ֿQckvW\%$M~l$ ֊qq׹澝^l\2hxnW#pRViI' <.%I$6_ _~ic_z`\J 9 ,]GNZ;{rVORTb<=K~Q?w?ұF(Կ5_s+b<=K~Q?w?ұF(Կ5_s+b<=K~Q?w?ұF(Կ5_s+b<=K~Q?w?ұF(Կ5_s+b<=K~Q?w?ұF(ɵ_ #y k8k&?j'J5-r6zΙ %bylm>:Z[[t-e7鞸 伿MxW//@%C=4P/,*I'#N+AO^3i,`-%te8 {s@qk6r$۪#2yA*@b @$ 3J5(tey]*-X$`5_xue(VfE{{© B2⥲<e&mqk%6WLU~oeF; оS]( f6=⨏[ ]0s3ʃ 9+nCy̶Ҩ%8NN.immZX 8m:HҺ(?i{Vu^#Ɵ~;K,dem}'U9ېH8N޸:_/skYd.Y'ɭiL (Ȣ;i# `982z}[3.dworkbench-1.1.1/src/Resources/HelpFiles/Toolbar/Mode/Foci_Tools.png000066400000000000000000000244741255417355300251710ustar00rootroot00000000000000PNG  IHDR&lVW7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:Q?& ''z{÷: o95|o z5,AXbE!9 G0IżWZVa%*y$0 ?|:! =5wԷF*.!C}zG.,IR@`dB_`$kU#!R$G ([J"!Q 9.!ГGd| ]Cu!R@tJD8 '$I5*B˲Vm`i#{ۙ%˔5e:<gGTpyl&;Gss? dog6fs 4蛶ϲNJ<|ɏn`[3}JeYj2\wxVI+JmpOrh绚Wߝ:7Rn$PB@ };J~HVڿVmSJ")YbaiUF:䱪oq%m`ќ^! CJ'T ݗB2Rce0v$mZsaưl.Rfk~ q1ͤ,yZ\aѠWG>3+YGLK>Vx֑鄧g.ݾ>Ͼsٽ"[̮*rSؔA)(HiC(Zq,>g0!=_6**e}|K޾4;M7~ݻi_~NI@2'ucK1;6N/C3BռH`䘞[S8qP#kV;S^s' I婵%&5'/60UY}մ4*BCVSL\6}/ޖq>눱 G V&ۛϦnvx͠)Kyh NmaC ^ iՉf){]k&|:w8jФ4uY$A6~D[`HƱcdž }*؎T?lV^hTU#rQ׮]t('f=թ5hY@ZyVkyE`6M炜׸s8j^VagԳ2;755Mth"""BV{bK7n Jmo=6k1T [[KxMBEr[54&ީE\g2kX¸vn77Mf J$*T!H"owَ}E Mn`r[v(Y&)$߾BQR' 9E05? $CjV5'R]Mh=V#j:1Rhq\1YÀ! ĕ4 3W}AA)e\w,EcٛxuT+ V#:Bn֪BV=x`bbZtu l`8pgPdËSJloQg,;|iP uE;NmJ!(o[upgZ5j7w JO!N Lo)!$2p(zS3>LG,}b3ܡ; (<`)O]w՝9 uXܹ͛Ϟ=ۯ_VZQ+4 SPz{V _o-qРAU)IF0tEڑE  9l GA˗/gdd֑1 3ڵrW?U@ Mode Mode
Mode contains buttons for switching between Mouse Controls used for viewing mode (View, on by default) and those for performing actions including Border drawing and Foci creation.  The mouse controls for rotating, panning and zooming a structure in the Viewing Area are still active in Border and Foci modes. Information on mouse and key strokes for using the mode tools are available as Tooltips.
Border button opens the border drawing, editing, and ROI tools at the bottom of the Toolbar:
  • Border tools are only active for surface structures in Montage, All, or Surface views.
  • When Border mode is activated, any loaded border files are automatically displayed on the surface. To turn them off before Border drawing, toggle off Display Borders in the Features Toolbox Borders tab.
  • Draw tools are selected by default. Draw by holding down the alt/option key and the left mouse button while dragging your mouse where you want the border.
Foci button opens the focus creation and editing tools at the bottom of the Toolbar:
  • Foci tools are active for surfaces and in volumes (all views but Chart).
  • When Foci mode is activated, foci from any loaded foci files are NOT automatically displayed. If you want to turn them on before creating/editing a focus, toggle on Display Foci in the Features Toolbox Foci tab.
workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Mode/Thumbs.db000077500000000000000000000120001255417355300241550ustar00rootroot00000000000000ࡱ> Root Entry֕ 256_64bc1d728cfea11f*   !"#$%&'()*+ !5&'bJFIFC  !"$"$C" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?` ]`4U !Gy8pHϽv&܆9\ř&O]k4d2yl΀ld:0;o G֯*ֵmKM Aӭ4yZT8 *jRLeq ?=]kZ&g]-6aANq^ Qsi={۷qʪ?hZKդR~V2;sZwח^ԭXY-]I'*koxfHЌV X9.4o BxU!HFSI%?kQjo/ Gj:E~5Ƨ"L&TC"sđpr/.lw:Gu5?¼^(mcMд]6FJ]E$tdȧQw(W-`?uoxC AV[9J6W˫AW9mXԥ&e`61P* ;BUw#C$bAP:kO cEfxhn4#fУ[:Gu5?°{EEi+|&yQH I ]|Woj:][&~aXbLY Iκ}'QuAcO3ž05xUtãjz=t.bJF !r[ j/ [ėN2W=EkkZ&IiVZ0,dpi/H٦YHEt6VֶV+xC3@bp;I>ՍđhR[:5N=CAj t}kRMKWsuzIrKg75Ռ 䵼b0I,7a!Eofm,CTn':՘5@<>FWK|'=+:{] M%f!XV, "u@MƁ,sEڹRºm:CNQ[ԧXjZue-z宭Ka蚢M8پ%4'q'+n15\W2)iVTnwTpߑnv~+ %5S'9#V^NM8|(MLohL-&>n))qO1q^cEw^&֭HmaCEPñ=Ȫz=zv,B #>PJԏhMD 8ڙ J.~nNZ(WBۦ/I>n)$6("]29;C^gEzzxLD ʣhǫaq{upi,p}q\1Et5Q@workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Montage_View/000077500000000000000000000000001255417355300241105ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Montage_View/Montage_View.html000077500000000000000000000102321255417355300273630ustar00rootroot00000000000000 Montage View Montage View
Montage View displays the medial and lateral sides of left and right hemisphere surfaces in one
Viewing Tab. In Montage View the Toolbar looks like this:

  • Orientation contains buttons to set orthogonal and User-defined views of the brain surface displayed.
    • Orthogonal view buttons are labeled: LM (lateral-medial), DV (dorsal-ventral), AP (anterior-posterior). 
    • Using the Mouse Controls, in Montage view, rotation is mirrored between hemisphere lateral/medial sides and panning moves hemisphere sides together or away from each other.
    • The Reset button resets the orientation/zoom to the default.
    • Custom Orientation allows one to set and save (or not) a specific transform (pan, rotate, oblique rotate, zoom) for a surface or volume.
  • Montage Selection contains pull-downs and checkboxes to set the surface montage to view.
    • Top Left pull-down sets the montage structure: Cerebral Cortex, Cerebellar Cortex, or Flat. A structure is only viewable if the appropriate surface is loaded.
    • Top Right pull-down sets the page orientation of the surfaces displayed. Pull-downs in the middle set the surface file for each hemisphere from the list of loaded files. Up to 2 left and 2 right surfaces can be displayed at once. The checkboxes indicate if the set surfaces are being displayed.
    • Left, Lateral, Medial, and Right checkboxes indicate if these structures are being displayed.
  • Clipping contains settings for cutting down Surface, Volume, or Features data to be viewed.
  • Tab contains cross-tab functions for yoking the display of two or more Viewing Tabs.
workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Montage_View/Montage_toolbar.png000066400000000000000000002067371255417355300277510ustar00rootroot00000000000000PNG  IHDRN 7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:O֥ nT^Z#BRRҹsn߾2HE篌0 (d#o aHmZTػZ@bF2Y=WnPzb<<8xng۷8VYK)mѢ}Y.4c m?ֱ֨o  zj 6 >rľZ<ɹm"6MyuxR˹%CVygA*;$9#ŸPrÇ}W\ٮ]5jQX4|㙞ƚ "N$qTRTsvJq-w}7-->jzc[i7:xa%r(#,t7I;eG?8t# q}}}x㍖-[.[LZjuS~x{ &£1{j`ao;1^)0tnCsŤ<>}fWT>^v`7;'nݚ. PA&w52 J!0)=tjOB+O:/'O&|6%o|Fq_W%z̘ZjL0eOc~o]W>>ָzLsB&?Cddd??JaYR*e˖.j~ӰӀYokPvđUӖ=E½{]2n&Kt9DIĐp7)Œ)9RW͚5y'wx0+~Ib&,XYC#>>@xYrMNZ4bdbbڨ-g<ݫMǞ&|6^}^3f̍=:-_>y=&p.)!|fرcUV-WaGfQ*?~UVjXa4:ԨӸcPSM4뽦%v#aɘMVJ0oU+w?+322&ƍ˗h4bXVjXl6ljʕudr|dDZ*9uv8:χ\>nT *kّTtHT]'Kv^cSslE-<c̑$ZxjEy(Qj4999zɔsǴӿI_t!M?.rY/A8xQV0;K;vk_0:RD*& bñ][4l{p`1ÜfZqB65j>eD^iuP VzSGd@FFE1;;`0(JQ:) #raT*`q(`hw<8_bĨ mI;. P #c@č7%~9RxLHMVE=xmCFc7ߛ27BFv]\`'u3/cr׌?o=i32yڼӄCr)@M7*c;S=E`-]w/'LecDw]Pz:F]B]-85RWMnE% !NofG7j]q y *4&"T29wT]/3h=3MQbQ.3árk8 IuqnkIcA۫]ꩊX_WCH,Hr o28~ȴ;q%PhL4uno4k,_#,fEEANW``v=,aHS{"N߫wWW5yU"?qֆmj9{͛|\c4GvPй_.kZNmkZ`|9GaU gI_D2i"^id3N*?O(Ô:(Lej4BqXgF^PT>>>&It*8}m̎O7{\Nf87M:.fWKkŹլ[$5|yr<4E' @o ٿY|4};o3gZFQ'eM&`[)@ TP+ BJ88)r{WWMxԨ۸g~WKT>7_nRK]z`r /ZyJ(VTma#&,7{)Z=A}Q{f* X ;ex)@)0k@xOWZdMI.]>ᑃѱ{-Ҁ0lJ@WTR]HTrcۆ6j"j} - JR2%Vx5zw.qиS?3!5Cg^:Q{ٜ c^Z3jž)]F îM.u@â.l/aB8j] c'g^~!:D1ɗo,W5km wbx7u`RnJJ38Q`}]zz:_0X$͵l p'vSw)ZiXDQ[ -N(J}J)`RJ1l^}5Sm?W6$=/dNYyvڿ`M~+8?!H{ӯ)hpt}yHߋUy^,5{Q.j]N17dY2բxy[7m`glC!]?SHWG=oeÚk)%H%n򠑟&~ӠXcO埑7SW8Ŭ.S>D+0[E6sn.] fssm@fN1׻1 TwaOuא.hk^zyG|>hKF̺k5!pWr i9gBIݏ<>/?$2\2ݢxQo L<>@˺ !igv.Fi v: zc[Ķ?7G~ 7WvbW'kqPY/ CAE/§fI aB\p4UᲄNAjz(jeY-<(YR1gs97 /.q>TQy9VVp5lZfY^j/R(u*Yc?RP}?yPТm@EWjfv±gמҥסkV͖q)qzbQ=_Xȑ+mTX>~ro6|&._Z֜ɉsFz-4"[[3G;l6& `+snsWiRX~#G{|zsj_J8e6/X.X! D~W3}{j{X> c۬Y?l@ZmVw4Lx$fW2˙tRJ)_.g 3L!5K_7 e "OxxEfɺ[GX&~H򌛉ggoOn5}Vkf׵ZQaj(Xi?x-[L-O\m̤_xe񃭿5EXn{SY/YH޺8gO@oocDu$0`0 lHBȹx1C2I=`047v,H9q`04p s=rnPg|>:0U%h5t 69_ KB8H$DuM`vIz>^Jv錟s4=t~/܋U6%$NkMrtF={.9}~Ʃ]Bq È0=90)|IPP+P*RFJU)_"Pzڧ 8dt5Z{y6s~_/_V$7{וK Ṡmt/4D@)Mڻ h P Ԭߛ%UoZ Հ4(Jgm8S#}lڼT̠Ϋ=0y^uWY/A 87Ҧ |z{f~rIkx2MN^9~ 3ýUh֬ӣh\;X?ҳʞY┆Veqڕ+9WaCFf/HQQ%Vu {"lx_k5z2/C[Qe1ZO_V Ţd[{6O~ F4$;v&C>F0}avhYQ}yz[c_X3Ct!95^ݻ*oA:/fxpCeTҷ}IIvdDhe=\'n7qi,TtƁ<4?1 z}56l0qDe0 !IS֭{zKU }+`[#:=O (X:*Ø`w"bfwx}j}ke\8]]ө w~kTUY=j v/g8PRc46vK&Q-oFp_M@ WڥFR*԰V5=! Tmrmy@aX&i:+fnk?&hTl^7.5u#mCj0TS]ז+tJoWWH [$9%;?ɹI5uSb:{kĥ]&pZ0~!RGy8gPJ( (RJEp*yѓMQaYKX_h7{^]WZr3&PA-70 ˲,aZy 6Q C w>u`}~fA{Z1hft7@s`X(Dy"!J(d (n)A,mЁ{CUQK`mð HIcXn,a%O.v~3zmpvUෛ6|^#=9DԯS5dq~P bYJ T8+xJ_K *'5♥pd U?yu y[ `lw JB0F:| :)fL8:^zEVǏ8q"22!U6~njP8)"HO,r\Mlj=Omy;R*{w]bQMutp֌*uTFϩ:Ԃ_v0qJ{"ݡ2u]r>妹\[fk\퉼 ' B)=s !!ǧiӦ:ujXXX6m9sfϞ=NQFӦM}||0ZZG+w rWӳd?Z'8>_5] ף\' ;sݾ}SD7<Hli%}Ȗk;Ø1C웗~mi5GGaFl9UkPA}?~|UR_PPJ>tVTԴז=E~B*68M ƽV>js[M7YW<>0P֓c29=4˜1C9ӍK?njlj-`ƔE,|".> ЫE5eY1j 9jV_YQmV W_ /Vasj40Ϫ7|>/>jMH ݭ\`JصA1tר}K*xV;qgի˕^Mdx(9'yܼq3[>w,PhugX^]ox z2˜,|Nu>ȥN#yoΒލixSHIsg}, tq֭qqq\G.]]Ku0Ln]kuG%ČJ멺[ӮH#\Y{73>!:zr(pN,Pゃ[n_XfP(m۶ +*ԣ>(;{PGƮcocD4uv^&d8J%d}KF v봼Th?tᔚJ*8~ _;-zHۅRKa c4sߺe8J C"Ji=jbOۘpF 3}j]98N4qA)uͽ=~Azw|ߧy( p E`W _8m]`8vZ(GS o[aj\\rm]QrҼ!}BjFV:Z"6̳ @JV%Szj HpHyEU;݈7N:]T;<ˀa Z@Q(MVͭ=i@ߏWO:Oicg_SMϑ۫Ä[g@)-޵˻j/Ds'wݺ{}ݾ#ZnWp?]a4Z%Uuߕ j"3HQqpe Nn?@;kV&z = rϛ9@yyջOJ#[LxbP)_C­UV5jԍ7" y/ nݺN:U=z8LsG{=f\Bש1EP9lʅKǹϯN7z/tM7of pj2^λ'&(EԖnrt)s*}U}U,KaIڄ`xK|)((PA/ f--3דF)_'gv9o//tK(t;צ{9$ͦrޜnt`NKIt];0&lvlS{TY/b~m߾}Ӊf5E4g ^S L0gtf\ /*)z4Ӻf3gNFF??xڍly>a+0Njeʔ^5,m@,ÀaH[Bӽ[V J )C)Dr,{+#7Xc!Rt6@]PtݩgJ.fVpXI=IZF֋gBPM-AvbxU17?R8q̓Vn߾rg?W<)r>Tw,/Gr']}'ZT*_C@婥%U PY% qd|2y"3䃯WP<5^< |A.[Mc1JxHOO?tЭ[|||5k&_^Ϛ nq%y)5*uʕ{j⩹ǁOsooooRRzv7%^F60x{랅#XÓpr^!Gn+$ptԍ<| jpBppa5>{0 C`vͣ$"e7+UU2O"Ozr~-v|zlR,,jj./|I6|n:E)('N8|+R:KμFΘVr*]p Pſ;=?Q`O9wݼpO#)FVbpxh·.t;gw$Kp֭[?.Yen*Bxxx|| mh2cFw߼w.od@Pǯ ?sL,(Y"BDJ%ړ@Az5qʓέY2z:?Ӻ-'zyf}ouCK [n^^*yr,2R.Pͥx^75} -NKx:/]as?<NZU5[4jsbwGM~Y2y?8-c点q1 U bڵ %'Mt}SudBHPPP߾}wر`;}:`߿o߾AAA¯ӊY/t%J.,ܞRAӉ(õN)tT9{VkiFԼfС+-SrNj]To抔9a!?hتD4g[/ z޺q[i9o(-F|fP*\͞V栌'ÃaFhq(DPɵc4wgǯrzU+WÔ2)^9Sz{feYPj\pL_uZwP9?/Oq=VCH>7QR]ŸC G -竔"1nKX$N3~eduM (Pjby_|r,W˦[X0rnr,4ge*>yX/O8@ʠ8H: -U<|);_'cǎD)\©Nj4jjxN~xFB OR\8YZIDħ Y=^<<8CRTyeYB]puAƭx՞~;}J hjx7~RtOzFуC`obΡ['G/k _,:Ah5|]9cfv3vtIzZ?pn<O}f #ܹ  j mvFAAGkm,̑ [jQ6lӨ]YjB%wi7ƽ|!-B_aQﲆE~sBd:p2$E۹ҤӵlV2Nk2+' P~?%c?}~nJ`:>F0mc78̖SĎkxΕ~0M4OsyLƆL쵓Cո7`dիW*3ФUTde<l<)#hrrrܾTQ ۺF, ^5JrMQ^3 jҼ?u_f6ӔR|4i.~MCϹ8ws>ٳY?sh7b\9L ['$>O;0ٶuO;]ZM?W36룄moGVnn.8A\$@s'-|bNu@AJI#;>Fw< BjA w<&Z f{]5CPPi)R^f6JMNN`oE)%d}x ^:Ɇ c>,F3W bgmFL~r1z~(;lXߖaG~+O#ض)飞=jĦ/x%TX2>ڶ ]rJݝ. E:WGW}NaX,?lh)(uzcx7X\iZ( ɺa .<5u!7;%T*Brf}-HT*?G6iR{SwDU*XFd FXΥ?ywTrf"FںoX껓j湈ZV046>b궷ZyGWyΡ5\O/xlY@eI :~Q?Y;eSB` 8BE`Sw/TiyH XS8oVצnݯoq6كv]w&  Ͳ:qi0}U=u}78Q:[8QA96 ⤤\iKNNlS'yzh<5 /a pT`)~&aۿ$t0n7)";џ5| @؝":nj35>_+'> ciLr56j֩c@)Z9,SY MTu PÄU|?GNȴ<(++Kry02m ^ڊ訰^znrybvFFFFFFF7z_tYbKW<Ѱ R^ #8:aaϩ ?u_}MtwqD}d ԧ7oEFFFFFF9 ž@(KQER8^(ZP( d9W(Uo\zuJoVRp,ۡ*R{JaF){iVX>>nݺ0V HôJS2hm \(k x>V^U8# 8\G'ۏ@<ʲOtu2%ҭm9u ' 91l6 Ts::xa1 #9`'*|ѮO?-MxJ{Vxۨȝ%.^&'#######$6AQFUa%I8_t/BQy_?޸qBu\}fnsGF:J=Xk>q3.^QPiҖ#<TymH.v;՘2a07{˝:cGt_5-sjNuV9u "ͽh C0a `%6t c O؁!RGI\~H( (RJEpyM 0PzUUTUVMIIkK}XB*0Ku݌炪VR\^i'թ+%O ?/;&CuX˰ޝKSd$xDE)ޮ+q]>̦&Ba޵M2222222O;.ƃs~ 9rC,`),caCΉQn :|7qqq9ULa_曯ڵj@1&z*Tj=oX\M0Ѐgp5[I>7!>O6 aY ð 0d#v\` a$Z7sХӿ"V6J>!"DB,Q7y闒S'0OӦM)=|IjhX }zM6uy wΐ䟛)c̆GDTN~ɶdo9b}[HçslajNK>:Հ#gH,![uZ:_)(Ŀ.4sVpyPFFFFFFnӚaIlBH*UFq͢,[L5Gヴ)q(t~1~>/O~x ^r% p "Ʊ  zLV @; Y4TFMJ(ByRX|iiiÔnqPPP֭N:ӗ^gT*GE)SrFodV~uVE!B޶^bsxmִkG4ÿ/z;N˾v.%46mܹ, gϞpBFF˰"] ÀBrYHoTL#lYMfV>e}q_}/ R( )[ 5PbDKYO|Z˔厭lyzQ*R*.W6oϲ~saܔ~M5X|YB Dh gkҥK ,YұyNj=== be6}yNCp) )*Q\ kj7rVxQglR{`7}5}yʳQ??0Ե+O8.aEtvZe527?/^(IRE(?{.ƈ`m=!d2Iq+1dlscLZΪދP/` к@kÏhw&%5XNlZ88 :߃5[#$NcC9iiiGyI[͚5swwϓ0x)~XRADQTTj~JNzMy/䵋|ϕ0`0 # ~vOJ!SR2DXq&RW;ϼKD޺}%vd;eBJc57sۜ>Uj;_NW\2[řM̜[Ca9e'@Ae +?_fJ p?wl~$I|rڵEI$"mn0Q,*/*/*ff22f Y7<~ϓeR>wE^T^T,{/y7XN)U?[ ֚zs~>ŏ1}Y sf^򗄞5ݟ<0w;&uj"?O}?ԩӳ0|.OTR^dW/7Z%&܋(@e(C[ $ URNt>G\~SRq̙+Wq^B:u긺gc/اH@aHM7%}xHTn輬GME'gk40JMWGB^4Y߲I߷ؿnÔ$)^TofI22/߷ъID/I$IŽ=uɱ_/9~zop/ǓUUUx.xDħV䞓3qOݪש:c{Y/?B-"@~QG m^%A~TDfzx Yq]Ji**z8Kig_~^Y]8{KۨQE.~bj<G90Y`@D[g~ lQ須據 \߻A(UC<rtr,.$IO}榿s.k/ޙU(1v]ΰLAHE  ZDEYF)%ZDjEEZTdW?nӔhCwN %!Wyl]pcHڦi)k7qÀ6-.bϊX~i^![YΆ~$5ȴA}wہ£v*},:Nt ʳm[ {)vEmQ0oDNӽ,{N~0B:;ཐ߰V09NhT90DyN+,+UVoz>-[dIk֬YqƔPR6nf*Ydɒ%}}K=A.aG}%`Yu;vꫯ.\ >í(I8VTqXp,8J <+޽r>@?uJӪ;vlȑ+e/xyy 0 00>$VZN8? }Z]);Ly/{>sm<²p5C!a/uW2<~ZAo|( ܩQ$<;]w=iךE=Կ~3~s39ۧf`LIT$R* :e39 -7ܴexA`w@'Y%љGqw`Y4;=Zˏs\|=:E}xi6>ׁC~o+rS۬k;Em<SI=G&@VIfџrh]6ֆ?ڴkN`5K)z|u;̲,Ic7ϬSv|ɧbmOŲ$('Lc$Ywݏ`yOYI[$?yfh1Nj*g!ʍwBLݫ5mg!I/۽5-ْ+Y.~=p]K$NEO349bmY l:'ÑA(Lrs^-ܣi|'2$zVp)߼wzԞ}FZ$J%p;bٷqnurXL3Y APֶ=Zh^ûz=":z+/Ol읏< wu1N.dI $Ipkѳq>Ȓ(UX9e1)`m7iel~2yQfotXC>G-gqjqL v.. WlB$4yfO0tU_qܳ'wz\xϱRȼLdCZx{vl o7 I۫{Ҿ5G#&.:IA k7O. t6_N= bTj۸_:D-UT߾}w+?.E a|||ko\)wK(@jjBǏ7oܻxqJU0Zm``ѣGK(QdIG[*S #׺Iq櫿D[Ts}M.Lp>[~^U*b!#͐a~pQSm)޺G _{Jo[/RڸP3DR eYFަ~>h·%8\iUx[ͷ˒`YQk|w)[iޮ^ٷB~j}~J)*}>kuO-͇?u:a`}\sI .椗e#Ye)9挋 =Կ!(dAdzmWsàKB̗Gj6sVGLh£S:7F1KϏ~ݹJ#!@)jŞp a%Yf|G <ieYWY2wW3w (4nXfCԳ'ǭePB@ C{UXsx 3 K)A% ,Ly8^W}Шa1tmU'x]w(E ,ɰ3r_t,/˲h=8p9ڕ)*ɲ;L]Ɏע٢%#(x%vH;]1;Vm7Hn2MKƷ/Bgm-T|u}]bg~Y^_r ZmNZ/ h>~4{wȎ=C8`N̳pjLeQY%Թh WD.)g~#"@@/tĆ!Sx`r?vE{(suƞ9y~=gNCԻ(b SWo:;@PDaWQCl|9RQ Ô/_AzzЎag)0ŋyi@)a(Qj9sjժ(Z,wAT.Uٳg===ڤmܮhZX(4Fy9;uv8ww%$M&NӘvhU҉5]KmgnfAM0\^z0[`ř47av?{Ȭ7ma8+v[MbRh6}G4Z̆ďN"F&ߓd qc:Oڍ_dl[7/dge> #'H_Mo! O?qJIAEiuQ@;VN9*MF棚BH&DH 7ߏ_:e$<,_c!RJc$I@r_2tk}XCv-Z9}uldɻkvbvjz]djXe@Z&r__=[(ĒP)0BT.>9bKW6z8;"CLE*Ce*Y~dY3 i&K)d2;a^'7}.,*ho\ͰF.3)t#W˫ Vt9fxÛD&3;X@j¾%s#w~QO]avV.Z0}8-O'9K~nΩl& ^,8 XiLc0Ka˱>`Id\\j sɢj333<T%!Q IDATYCGcjBYgRoݸ ARKq[6saybz8н *hzf,WDY=$9>/+IBҟo( wcDŽL_ӠLJN_Hm.=21)v ڼjLb!DR.]7"ؗN2ddd<~O՚L&Yau ٢|}}o޼`.W.M ^ '~\bǔ5ؓ߂Ad7ya}@7./ȗХ& UI9N(8v}˙t*e*Vûpʩ7wukuJřde8B,S%=eYB&3딱Z φj5AA)  UE)]ϵkWyŃ- J|IVksWoBxVA&GJ_țֳ=2/QDjZ>J3|}d@ֲCBȹN?2W)z]BCi;79p@%*Y H{A w5) 1PDH:]+73o#MN @A'*,.$*z&7/$jV݇mq&%%2QQfDi[>sD*X\E?pj"{V,I$Ev7#: C»5j:Td|txY#W,T;vM,Ab!ui4)|ߧ-URVE2Xטm7e~a˶~;tT܆>zڢH)2:GIɜ;Z3<6x=\}=~وȨ[,ޚ8aE6ykTl MS'EA`+jCVձnieAs+od[*s7FZh2$ZVmsaj.нK)`ʸsVkդSU2VARiӝd]Z9ǹx7/8*}YAr>{?^4r}D9*6ɝm*SHfZeQ9+i22O~QsQj|ܪ֬6j릔{MU%U᥸̂`5kB/_m,֒}tؠyTz[׵aaA+~k-A Ěz)xE ?n3w,M\Md!޳q#&d@x_NhrmVuޥ=qbOSlOIJ݈jͼ3,j­1_!H՚se>wr2gK:kZ9J% ( |ug6~ϻ;Ɖ|Ӯ͏ JZse{x:;Y׹5W\'j5!~m͛)PuķW&!&%xawCo=]}}fFx˽=5a|܅t d]IQ!x;Job1û.&i^Q&Z/tqĞ={ k:X9_$h4yzzT*Y:g,3 V=<<܌F 6= t/WN?w ~VnL}c)6~ݢ].ws%Զ`HhR (I5?l;9lh@[~x1 ZƇN__[/Ud?^bsNS8)N*_8oq:Wy.>'TUj87Js^ꥶ-V(S *ZTnΔRW}:8%Pǜv;=!x5C{V䣷A<krЧ맃g$~wn-ޮ"'Y=*NoR ogC<'Qظ?nJRa%ްbqլ~9~մ0]0qyly f:Z;(oC1aI$ 3]a2qdCߥnwwd@>SM'T]F遾 [I橇L loA.Q@7>jR `OiS7sn/ r@hilRP^@=? #>W]홙VB򘒾A !ʺKv*@{[ &߂H<jS ;/eˊl hx审suC:Ѵ`x$if9.n vB7 5G_o)x5<+'8mmS}8qܯL tbȧVXҺ@ ww(v%2Kl0s;ܻs٧6~Ӥ #S0P83@An R0<ǪԜ?dz4xփ,;qnL-} aZ%-1kgLٸה;(gatƛUŒL=V~;ăM_}ԠʗF@)wEfsoM/EN@P/zn`;~[ .ff[!Ĥ:7Tq@V,r=rqFyxO^x#z}֕|n0,um4)|S2wڞ+utJ5S—-;=Pgk7;(-WHy; ;(Wag6`J~R NMxy }Mh4e9HF)eY!sVE@ w ӆ5xJC[u8PJ3n\:u b B&=樣ZUmo JL#ZmU@ƻ'N ]3AcxTݹ,ƠACիWNaOTeh4hZ畁nߛVATcZi4Hг^P0WQ OnZ<KҚ"j]@񓎮ܷȵΕj:SЂ:X5SKKZI vԘXd=8N,S7}O kBy;=sxU.:X>C&q.Ѥu'J!E(.e:yY  hrǘ$m*zbi.?h_ wN᫱S]{ӗo '݇Nq!zb:~6o!KOl=b C!n>//AJ$̈́!Ěr;=#3vYeiŞk3we 0ůϲUL_0̛Hxw1o?ԦmOr2@7+gʔaaメX>y뾄m&,j ?ep*1$VLRX]++rlFL&;W^y[?x{؃Fu Cl,B(8LZRBIfeR*9S;_Wd {.߷ec@@j.IG@R:2"ĖB!V<zWF+Y , Y]I%D`" b}(J&[?_<ȐRYz`H:6f<"7!J `exYjvQM͐ 3.Uб>-r:\Go:Ykd?jY~#6*!mH~Lc3WP~z` !!z)!$O{ m-m5E؆:@H݇F / FӠh4(*q]zsnWP];yJ:u͂ݧ{<>0"S)ǤI򬥔<7vD8mu$DŢ֕{ yG򳌱 !)9:KdžM&  pvo%IRB;>WگKcnA5I3/8c38u1,jU м$l]e!qЈ~l \X78`xL_a Jyh]u=Z X,hε+%bQ1-UPe){Tfm?56($$`r0eYNW7?ˢwl(W{mGn-(_{onʹv 7a<]us1Z;/ʪZz` ,iV3Fj̲7mL ;0R0:lbMYzvWl0 ,#Z})exX&ë#c?0Q0=l S2mږn|\?x{Er!DͳclνYCn&d)U/l:A!T,ʐ Q6ëG iQ+]+r,i6l60 ‚eUۻJ)HQ^F(]deu>gX2ly3t;#j q`XZ2}qP-a_ջ9-O94l{~Xbz|kWQ(1: ߸\Q[WW =wSݕ`5V6I2|RqSJ9tju{ȝd6OrrY9P]jU*FQf3oeYVuσ_+?M"8@P_R.[1"cqp]zuČTӴ7.}0HбU/?XEYX Հ/lzP١{W^J(֨vr+P9^ H 4qe>ci-ܽq*(QD%LaZ7V+˲<ϫT*eīj峲!brrrwÿq8fTh}nWE4GnF1ey^JMIv.7߿P`Yeò, v&N:uة[͟>WR,hIݲ@FՊGM<_~3Dj .iyb KzJSq,`Xg VCZ_^ d2Xo k*L`ZSOJV9dG߿eN8(i?>nQ3_0CğC0E 5[1f,qAIʩ0EM5onyc Wo߾~.O.,[dd=S̲r,V};͘[hz~;W <~5(,9xN;l5r*8`u=0ON: ɲ,%dv7J33&>oyvYJ}Dc[*Y?°,vNMHl=ulkW9zGQLIؿ.=Zճ_؆fX.ݷ (c]|*8x"+]QC0#NbCMifYe<(BLIعvPN) GR7ԩSZըX hyVt*N d1<1g3w:EѪ8ը8p#j5q]']0+ωW-;y'=peld9S)SW}օ+[G X)$EmKL8NQ @`aٮ<ǎ]1H&58;rX,yIO@9έVQ]zho_r4f81cӠWQ<{e{nvA(3& WrbIeXq[M|܈ (>!zivrnjqq'O_{vW3(˔mI޻@y,ObEKZ.n2/ _ң#,U(_ɲF)88T0!wܱZ\^Aebܾ}ۑIr2$ѱc=zڣG놿mѩw)PW|t5 kժ׸kuwEށ {nH1]תc:rlvs =&jX~z]ժ?0ڱa-=/\߃8%<NLx1y P( >>>z>33Sy%XJD1͒$ ^<@UD?,L.5TR@Uh'M s{V5,o7IR XKۭpJ) V8-(xNg{r5b&myr9n(`=, ÈWv)_ (vl` Ǚ/`֒2T*%5K1}*4 vRvJ +~ l4ۛqRjM7[Vtp"lЮ7\UwԪeh#D1jNR{iyB-sgɭys W85[dpR{׽_MRXuŒ+kAQѢCɵ=&ߜ8ps軣mb̗=shN2Eyxyw?VC2y{bltb^u-oRϗ6jxEi9.-´ѓ L\}wu&N h9că5gQn\,9_4gOάs {i6` 4yeKœ'O?~QFU{/{36 wJ\+SRT}Ұta1s/n ڪv% N7pj=G&3Xic[E]59"rjJR'EgEt~nysr3q)}^+WŃdff>}&M*^x]VǏ4hP^=i7F\3g>Yv.p~mm聛? ')cدAۿP@N?6txٕ5GNV.bg|#1oݺղeKW^<_ڵ{.R9[`6O8_+6 whd% Yn62"8R5o6^sӛip O2˵.}&{dxbxww#<0]D/S=z3 fc`9;(q W`+'oy"8R]g eWɸ ?2xbcc{eU Y+ ٹ)LdY޴iFŋx#,˩}Iʗ/欄;wDEEi3F(]4lIfcւ l^ExN^ݑ;7/UFzCe6sGֹ/Z$N!3 =/ٗeY///vĿ?ׅ)XdtszU&{`=#7{\q]a*(`ӳiӦG4iv$&&۷5jhڴsuѻBV #w`. Ak$WbS]Zo.hަ}˖ڿ,KA/.\PJJ绗N}*u2Ӑ;(j=;yˍ+c@is/a'Id̬{N^Ex[Uѿ nQBORp Ai_6)tՈ&N:p0ˁN^#_7+hw'_#6e6 Yٯy<Çq\ҥ[n]t .| %J V+z}ȝ<ɺYGB9) yM^pá&d ? W\)S8ş"?R>p!`HmOm/x(S$A)Abq.>{e{hٲ6 Ȳo=#;En:_~0yP j:獼6?"{zy"ocu!XDb Lrctc;g.ݹIKfUȧ|nbeŋTV-==]uqqqsssqqQf eW4OeҠVݗɑ|tj>DaZjvڋ/v>/Bs(I8A ܨrDxR=;\>{'"{/B^,{w0---w.>㷎9PҜ'?K-x"?}扠<1RiY8m 7Ex{fk@BOþT08WƘMjlOj^|`(_x{c~`眆'* ̭օQ1cB"pl{/| [IT< zN/َ*;7m̺,+%%Ն돆۷oݻwϟ?~E(a}:P{<`\r%ϔ6G>9|{NmxF(;ޯhڏk>l]jZ²c L:?Ic:d,h2\;g !ytE!wM}}塤³&OّztSb}nKA Jɀ.+w]1;].MxzA Jc+_Ip̥T֮+U&ܛ;a0Lr pb-B5݋/ӜVdE(돗eÂ_*YPJFJxj MqNĹ Y}{76 6$B3t%ⵟV`/+pmYv,̋J,JushoblW~sXx77.zB.M9c%n\AsfR9.EItܩS@)?]q[rj׮!DZ_ z2q )RT'EQTK.B*P^cxE|FUrk>TU5\f}jA΂73 8/w+8_C޵ڀ[X롛yY|)LZe[P CBa[- YwG{D"@RG2mm Y$HN%wD9(^:J˗Av$)!!+V5j=ydǎ-[WL^ӑ;N jϹe;TiEE[QآXc/M(Q7{y-(!vbPi˶[wwY|9s̝9{uX7w\%l\\p^ if󗐻{|cNm( ^Ý:65;䮓knUBRHtvwU]֌;z AыiWkgy! BQ(T0^YD K2S3s)iROq>P;`˥p?:dHmtoo޽{7o|ɒ%(յKkd!{ɍwᦄ50ial;9d97 _NS 7EQᢻ.[^Nh*D__EFz} 9xw]L/x ;͵\+XW9tzj|zy3ȇwx+q9b4O{ R Ad-qxIucmZ&=Jz j<;qr_cB|ˋUƋf7Nd C#1o ]g7 /Dk8ΏI3u 2EQ|iBBBƍ}|}M&N[xj$ ?1LZӁhS<ϟ8q@ddVw`]p]D%h(BX(aX8؟6/ٿ#xw"B| _cw#s}`=:"iˈGqsu z7ajrux^u7Ϋ/> >'hNح]O)!-%Fі2uǐ{_$_ Zt~J9&-4LΝ (QqF戢(fQCBB.\ФI',~Mg&m!o_-Z[Y#w_4%x#>}>eA- G^=On0v%%!cTOq90LЩKMM=ydZZZ^(j5Q{Xۘ .Pt0 >(AjdX:'EO(#[̜w햫D8 1AAV*G۽R5-Ck|Pp2Y!:MR13=ܔܳN[yz|]^P9HDQ$ʍ}y8,w_Ixd/^)J^/ES`TT%J}vFFFP$D̘Qqڴ6KI,t(9 nDfȾ ƔX <ӦD Ձ'f %|<<=5uS:?oPfh^ gΜ9yd֭2<ƍ5jp2Mvx+-`k٨Żrc|:HvKZQKJs6k˹uNBoIr@\/t.鯆G쪮h$vnɉaޯEQT-n_Mkr+V Ǐa])BVVVd Hu_qA(垞Z6++3}nv]'A;Q5eHQBm,ReDbppp06.8e%SgcDq"*'Aᗊy>---$$X <ݻy!QUt?yW),O,\gQ,="a߾+όn橿t5V܋\zfi SgT'S~{.>ު~S|3(5,M= Qqw1h]%`0~w2 xML"!#/QyT) @^}r.E6sqA5*xoK(h4)pu?i /Š~-ߠ3A6e;W\{q/Sa%0jn~ߊj&L^]߿C2S)ug^r.woG >xNyFw~Wمv8o&o5,s iUO6wwu2a]bSNT*YeeYeɥga^^^:a;X`NT?rNWoݑ86\q;gc$.vIbG-+a#yZ-Mrg699y^b\ "0'g3qvД}Vћ'y{cҪveoԎ\~jQ*oS2=x{ͯKtӥ4z~ԍ=6.8x|tφc#kGŦO=JR>=ĥmTRykG鐯1QSG9jsbjzhWQCDDĀIT^[[ HsA߾}c6_mAddiq\o(%Өj/yyãSuCTTtԜT켚I"n},f&R$}DŜi7<]ˢfW-̤[R b1tn׺.3ΆGĜhaaD#K=y`71ȋFN4rh`Lx L>E*atS6&od?L{?"S \GEgG7<::o_)x9.RqT q}޳/x{~mȘ깫~DI*!yIT+:+]qD4(Xz>lhDŠ0 miFEzfS}f+;p殭;M+3 CZF~|3Umyj^/ѥ.>̺u\ }EJY8. طΈ_<=OT2 q{xxxx3BȎaΘ8 Yu>YG!dB;19k 0ֻ<%ltBlwSfsF935rxm=DQf([MJ)sН uy yui3!63'd;3Uwn~YBݳjAx^7vn,3z=[c֑,rГ{tbq9roPunu[*o~.qD&]U^Z 4^suOmܸ K~QP) =e);Q4W&o5]g !f9`F`c|V}q֝6Fx+Vԓ0 3?ϛY}޺jd%|xBw=q&? 9 繗9_ȋ@̉;68oGB4yA2 @9q4w'v.`clpAYl߯h=xDOɫ\ztAl'd(!r$ܙFvi*BXʩRv,l cۿh?1wYRuw<`n¯8ˀ]tc6o=نhy~w{~c_+>=;hʭDtK"6Dl\=ymPD/~"]b^F[`m/ל\O EbXиeآu_ͪWfd%o?}Kk>UZvD3gR9퐔o=bbͫl3om3xFۑ瞧 +ی۩˹+7oJgsY7XBQ]k]N~ܩ>BH+z̖>^Qy{{zǏ ! "99Zj :iCjK#@ pw֭u\| CgtՙlA̠f@:W{.zxV &t9N T*UB;djr\h&*R Q(NW8H333=<vNj02ҕ{'TjMy;?fe_!;yfzKϋ_m"QbmQy4ztN_s 9A'YG @x =jPiS'zr@0 uzS_6Q  .}CU>y''o샔ߞ\޾\+^}A( l}!۟=qfrKxU.o:m;/XJs<'X-{ixTwD`yӎw .X46`y, _vXpM!PBډTb:) '󴏍.d"@`s4_ny6xcZiή?/AH>w&MFcY#'8D!"!A5Im kLQ(D^,dY`p;}N VءYOczy (|4|A 7Ơ k(8i"Kv{,& G"<+0GmO^v}1~}^dJKݺ<- DYaoaϮ3kcfgtÏ _oIF]bF|xӄP Dјr;8UXxMbcc I"WIׯ_7LjZI,* |ԯ# 322j}-IYK&}{ fDZ[oy3C=ٚO¢R%usk("//䚤)^%maD3כ ! _=dMg&e2Ѡ:,0"!r@LrEQ$Dxx8V~AD8Wt'V͈@I j`4 hl4v$4z-=Z5N&h11y΄n~tЩvF@hzlIO]YR!|^43[:gv08aMW?8dَ׷Mx̶G'G^8כ&rtr4 MDdЛev$;!yק f&MT6ãhR.6oqNKvh9_@|i0`A헷s>nƻ=kڕzAҏ3T>F),>B( DȯW"2DNtL/9=wYzf^opî ]Ϝ1P4\nZ)Ƶ=cPoq;=5P7JX737۠chQ;ӂFӮ,`/9 /=c>n^z`{,f6 ^ ј}a8p^V;5+xWcǼjOv{ܩ;S?|v*`Aw:^lz,6T/|VŤM#Fv5]ky'֎ )@Rdڞ_'ӌŠ|lʽ?YJIϰ,xq640HVo_4ሢ{{VX: k4GdkЯTgF#@;u })*=^d'!@ !śEQ|XKEI-E$_To!~.[r#,yd2/Z|ϻٽyeB.{\?gDzz ?. 4 -f2&hVnz)jx:FM[FhzqgTֹ<^?[ȝ&ʯpb7̜<;$brf՘йfٟ:5vÛ+;^2K;frtz-xy2O_]߁R=riԲ3~͢@~]00b3X^1Q@Ԅ-[z6.Ϗ_}2u?k8*xNHy! (-V㖾EQVigN:uoB4EQe[paN(e9.LkCln&ЛeJ\=wEdZm@Gri(=*!N?߀X.=r@ZՊ9e bŊIGbd*ʕ+}>̽lɓ'ӧO\MW3Gۯ\YwJde?mMq iAHs d2LZayYd։`D3( H3"4mmu3)իIIIW\r`wG3E8aYEi/l?$ z iF 4085jٹZ"`}9ċeK6Y%)פ/^%,"x7J[a@@:5z,B,Uq Ckn_η\&]6D2SUml8tC 7!}tnDzՓ#ogOBl' v Y@x}&VUY*W'Zr4jI/mjY% {՗nZET/fjuWu0$XLVdҜx5hp,_9"Kx~p,Zi࡛YV*Y=" 3}ݭqIyo/9*ߌYeJkf7@K+6S?lـ-BzmHR?xN(~>2xVHm kT– " PSNQE?Ԓ(ЦHzRMj<eXnk)p>ub7( aB9mZnZ ֧A\[-g[Sy "q<0,ײ7-91@vQߍ/BClN"x;ju]?jI=VOB+LB%C)X"E(x`VE"@ (yuxW}O?Uzb4ԫ^P p,yV Ɋ[U@@@Ȩl2j /5Kg@cEV/9%J ^J9N1a}n΂8ڈVy[*b>Ku\6matӋRn3Tiss&:qS#J@$ #~Nty_Wtyڢ(*JLP(ɝ ]Run(EQ4Mۏ fl;"OI@^6_b hJnPU_rgπA7=o~nMuDziojUS[Ohxk}W]vTX˯ \  e9i(LM86sɃi\_Ei LFӴMȁ 4?Ӵ;  ~ 4Eh[;ϪW3!Ӈ# 9/YiJVFvDQ =9iD(TNY-iB(}zζ$"KoPm f;]YH.6*A0e4( Pf^6m ` hVPׂ G0Q%h^k9C{F ) ._34o=y$-_w[BӴ}T>gfK׬&A9?MQh޵&U5h4AAA:ZjjZT+!"x-Dmk?ah߷$/$$ܻwzi)޽+S0d/s/iΧ0\mIi{N{O>}_h_n3%mZO}x>jW4X`LM}t~ҽ@ v'HŎôꪒ׼{]r`…?քaЧ{fg m) >}*`̦&䀼ŧ_awO($Ԗ1q>anUˎQe CoӸ̀٬+ϝZXoP4M3"}Np 4M F4`Lu~8 :D ?foKV%@ֺ'b [=?Fwʽj^mK?l!Кf!ɿHu! ~L UڟS6[_ٳo+KHE;e$2͉ wuUԫۿU3jr<[yq a{To;N^xzSa{HTIv${?I009&'pDagVc.nzVkjf !fFEլP! )%K+d4e,a/|Ji? R!( '(ghVcJsag9fSCn-/Ok0NV;,ˊRӒ_6 ,up{׿%ljϪTԸW}(M{fCJ˞޹i Ҧ5FNW5{[w:H/Ok0мlToeb wjͺ!MUWw tR3 MD.LsM=`j@Ɓ ‚K4=Cܟ#4WlКb{mz`'-Exr[afgן>*[m8U[!%1$odN4Ū* @UÉV-LL.8MkH޵'9QXU€8}ʝb}&>fFX_-C( ]/0q)GM"ңs0EIuzyyIKA7F߶ N0,\l(O:V˜<Κ8.!!!>>f͚yɑd[A*0-=WE{ڍ@waHX\À{R[ck/J8="hڞC|6Գ?w5ӆ.=?CYMnOԟn^ĽEр%.K+y P*TG]:Sc>ڎ=]y/dظL4%xOڃ~BO6V!j7i{=|gS ò,|xna v" IDATqm#xv4[LLd6c YUmb0gBR qzX6iSmŴ=ׇzi7Ͱ+FJV8x/߷w[g4)@W"˲,K8Cn oeY|ۄ{ʱ,@1 R os.f*,<= 02(dɣ? >`% lgvzz,h$Yg| eeSϭ8fU09:דd)xسFyҬcET{y(Y |u%\K:"!ahu*{|䣾E8D+Xxw]T, sbL޿3N*ֳ)aFd?X;0 BxaTX ߱x`i ZsRÅeڏݺ0zgSUmܽz&xϕ 1_4;gÝ$ V=eY!F&H`YB> P(롒RS2)dWmƛ.S;4&fXVfgMgw{zvqr |U6mNy|r,!\!?m~*ڞ_H籝jLay=E2+H{mX&sLT^l/}U櫥z[ȭߝ+ͭ .@._as`[ãG6m ʕ+o[8t +wؑOrFVTiݔŋʕ+I >tR3F#ad&t]jfx]*4㽨K#ܴ\ hj&jV!jEW} jӇ|"1 VR>^>>jg~tN OO TCann955ejzBU0yݐi0r-S~A"31 )=jBp4#hwf#> ;-AiQ$PU4M[B`Fw"Dyg?/ܝjVp4*3_dBUKVt";.$Khj6[955x!35#Co ނ1R238n>5v&RI&O915h4jyŇ?g0pPh}쟳A3-SBw[N[J *>m:to߾tg2ӾG-\,ªUּEofCEQȸp­[222qss+SLj|4>&5yM4k,71̞bŊ}YJJJ^3{EH[䶒7Ld2\* EqhqJW@BVӗ֖8JȯTh,%;ihT4E@I.@F]RA(Q RA M?OӗtpŻ+ޭEtVKzoc 'G,vJ׼\)Ri>[;B'zZI%9ΝP E/UM  f#DPx JKT" خvϚC .Q%<|d<'&&>}z!*Vѽ{ݼy%KkY'H!}4K3U6kݯz<h8Z,!!|I(濕.N 0`vZRށuk o \5T~ЪEP$KQx Ҕ =sLƍ}|}M&!D:yJe|}}q/xn1dgFuY7N&s{eyFFwrhie;W`%"WAtӦM׭[w> 00 E ^1sl#9NI}Ǔk{isŻ .}V E{%x i4ϟ?PD Fm˄(fY/jZ؋KuFYw?*X-ul6tHݯw6l6/X ===00O?}+:<.a\΅7rwZ\/D8.jZD%J@QRLNN6L4M,+d2Bҳ4qܭ[HHL~f!Yb<z>~>4st;D{ZJB;GJ۞O\p?9X/2h܊46PdFA?-[ dΜt~5kO p9 o.s r.BywǶ J 4iM0 !  ?RƧ$Ιܿ|CO~d@M:: z^0_O.]ee<_=e>O99L ]z*_Tws~{~b@ն.}ۮl泝#o)?UL Wt9.=5M^Tm:ފI<\.;뤵<@E\^R;w̘1cҤIʕQ'O̜9Xb*UuvY:WE[J7xOuFZmUaOjw9: wE3hت9FVijV] =]0rJV/M{#M5"(`- ej}|.ٿE0JGs2|yw Ms69veqc+8tՠ k6IT=\^6£LNne4ED@kڿome]8~Ȓ kk4a+%D oտoy [zQ]kAA1m`y sxkbޒ üKsG6د{G&ቋQ.o{.r\-;oIEV02]{Ũ EdqЫV_ 87nHKKAQ3fOXb׷ r*ZrwT*ww9m}ReHUrU̹w}sBQEQYz ꩡw[~{nRrLJ!'`{ hJ$ָZ R*e.+* HVWh u-nUlBui(Z*Rb% *-@ a@B@.x~_ę33g&<99gR=@ jqEjAhHMMM}IEMYEMQ_\}Qվ]W9n,KuO_ٔ:gzʕ9dwH6;DO&/#HN˶s,&QZrRSNb):+4ml;W}+f*lݣ\tڵ6nٹ$I>3VYle|HiwFa{UUUB?~|ii'|` lٲd񾾾/f_M|zX,KSą;^ޏ!cÇbݹ^>w֭>H˻occ׻?2eJŲĪϾU3gQ ^ֱ 8hvv^yo;{&aeJi "'w c}mF-DMۊ=WQ1y%x?ׇ"k!5!g'l;OX,`sƊGuja7/y~@@Œe,?zއv|hW'yIcT;bٱLkWt>,PW2٣ǃ?JJާf1C; iW/'/XiL /)QFxP֧!bVx?WFژ%GSo.v<}zX<ĬH>0#yt闕(lnM>ϐ !umDҎ_>_s?^G6M>M`o][BN۷3 Wjx#y96}ݦ-\*NT}؛\CBiq;df;}W-(({B=I)]pK|]()ygצ)J߿Q{U.'MS=ai˟ms\R3vNe)_`\2%@55vMh͎CCJ`#犁?뉗ߍxwک+.i+ ʫ]H?9v?ؿ~f nvR[{9Jy碝ߧLVV>u:ZrrxOb_;>[ E8c 2%3}g䈨ϕ@oHRW~5M&)Mֵ&˕OLqb25e~|}Nl[o9g4fѷa{BHٚx]mLˈ&puRe'9 M Bn:|>Yڵݐ\;) p; ö/ xqj0c[Tkx<{0z%a Jׇͅ oG2Ra3 ɫ+B1·M.C|@߂:O0ݻw8:R&>Ѥfu}lJ4}?g<޶cogëd;Y9kju ౷\TwˀEJb` u/QWT[g,<,o[H!CjxqF5`1iŞc&[eYE>B~_g6lkB^ٚ5lcbAA{Հ=žزf٦[@Z<^(#tκY0x 4??mM6'GNnשn{6-TҢ_K?I*P .^>q C!&fֿܒž}p!|{Z L4Y)[Wךbȧs~x;f~2L-SW|[o!`M6/IDATEL B;dvL:w4Tӛ5l1ou\Y\}b  c0`5L JgM0x";WL[xUVΉ  Hxn3#j/ t]FyJ >iVngI'y%(xH;#BԘ O1b֪|@߂QKҦmbSnLB!B!0 RAשV:B!`Օ[PLB!B >B!BU0_P(:*B!BZh|۷+++A]]uѭvB!gՍ|@߂L~ P#B!B!a)B!b%_:dB!B 4[|PW t%BHYu+ 3T*RB!B!-`t]w-~I!B!yu;dB!Blje:n[,!Mh]cSӗNsܱMI^3wRl=[Geҳ]ht4)cGbBrZZ\ͽkv'g]/i4Ҭc?HWxRds⤷>,$)aEv8v[?5B<$=[t_gzH\l$;"W+HZR/mԨe=ҴhY tvvrrrr"'''''NB{m9;t 4Rv"ƒ#ݴSrFpE.bTvקi/K݈u h=gF 92-!9@gg g{j;s9 B}9X:Htq=&$G鏤+u0G7Ec.{0 .է{nϯQd'n8S/x=j9]&Js {|R ^HG BZ-MẓޣG^\Q`O\%TL HAک6ڄZScJ* N`+gvs ]+R* {.чJ.QivY&0 8?no_zmҍ-OJԨL0MG8~={ѣ|(t|[˯5y1Nr3!< 7 Aq]xم2$MK^@uDrO$J]۠4*{֝9_ E91++E>fh$F-N5ns? 5.rh>TrpGX=-G^+cI]&~U{P>I[oإRIwp! +x$@ 卟+zs8 ӹߦ⠠uvMr^\ƴR*ٓSPk_&11%%al۲ONzOY $/3g^XѻI\=V%70[@Up?hm2cJܵy5B&H97d :]cqտIre7cwm]gگJsFl훓8bs˚45\*|$y#) CΐUSw~!Ġ?7ߞ/.l|at'9}_DG{Auiv}nʽR iexduFzG kkYc/lЦwƸ9N\ ln$J20)`#s+m!3y!.z1 {tLCƋ-y6r`qܹZT72`O7g7Om:'NWA~?}LɸjhG.;6yH@ܵ{zkjn7^٭.\;hCX;)EpO_,^w >5^&el6&E<[LEąk ]YyH҄ۤUWPT,t*:4G ^κ/݉EXL" f\ζ9xPo:mNdzSTuwP'KU9xMǶ {xsHj鬩j}!h0P=CqՖ2r_91+^eE2 'lF~!wĢ2W@U vFHwЂX?6I4-qu.PVѬc2K',;};fdKz/k Evh:1fȇұ}i&G\ĺ7dR 5K ts%@ђ暚@aH-;jk5t 4 bHOj5 `'~)l^B!w%a~ώC8iT2iPH\:!XT^L))៾Ecڻ9[&Gvx#J{>sگRۅ߸c;/9.jٺqS <08u۳ffnMz3F}cȖcte GO'&40Y+fۛάu[mq¿+BgOxot7Ж`iJH'-{ |ұXΝ:Rʫ4lQ_Jfh%eJ\aasҜ=J6Rd\64J![JRPB:X3OgR^}1=4*T$/G-i @+* + gkr%ʉ hTrY_w[Lk6NvR^fC!(ʔmMJ-}< s+˫؆F g_1r%`hZ;'oLsivopJ没5u?7뢩M?~ԨQS>}ZҰhBaeZ^ ;K`<M!g?9עSǤeQ[7leIG `s"'gY6BZ48N-m2vdˇ51!  R(<>SrfkI΁Ӳv8[Bgbep%&MQYB+n/+w/{T#fƃUT~ ntrג9?kA{>=_Qn)S}!j ߑͬY.BM72IENDB`workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Montage_View/Thumbs.db000077500000000000000000000130001255417355300256560ustar00rootroot00000000000000ࡱ>  Root Entry1Ӣ@ 256_6f2da5d33f4d1d33*   !"#$%&'()*+,-./01234 EhJFIFC  !"$"$C" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?o^ɦuBBOJ̰=70\]x̳l2s#YNAe95K.5ee.~osPӣ 9f pY&{q3׽Sk%gX>a4dҤ`:;v8v(_ ^3CaXdq1쑌_|yl_^xGc=>ݢ/)`1>~\ 'bI.]E}_&2pG;f}/oW6:{Li`6F;cYlIIq@לU[.:On/oq1#79U8˞:7.اvdc)zi/U9ma 9W1H (BnXv\dazfS5-FpI Ү/ X=v HU<شmm 8_'<F9(GZ-^x#U`2)%?!RTcAިiϪ^3dd"zN>c&.xWu9es5:_&k?+[p=3OI-ϣ$1*Q6#p֗^nE=X%j2ƈNt :vǽUԮ b(n 30H8z]K>sts{U=2m3=Ǖn=1qY[a1_wHx߿W׬(`hq׮3Q\u647Wq>wp7]qWM緺R!8&Jz`~?7^̏ǂeq@GXZn˨kuids 0yc,me*P3nN#2ۻ63H_Y:ծڈtj<㜁܎{ `XdAM~AQ@ʮѹ*RN8zK}>dnT?w|Zy{4qoh$$8i])+\aqxp[Zy>Vѳ0%vrXc=ZŤX$bGH a2D;-=qc=JZX twӊ4y !0uln ~kVklքHVb_oggn$ A $c%~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:>>;;!ift;;;ŠO>ymTUy\C*h?95kDBBFw^nnZp8"K( Dc'ÁE%{ki4y,MLMi75.M尖-{^vz`mdjzV)ӧ'O͵A$(;:;;Wy#?,<.+}P65_$aG&n`2ʍ>?,_՘6ԱnbcO*GX1z'}~Kaa-BSB>|xMgg-ZH$j:33ŋժU{Ay}TU<5^\.UH351-vh KҎ7:ʥLGe3*>qKBѴM*n..LLLyLUƒHeYe0OnԖnN ‚;{^_p8=5 9tp=zW:r\FX-}X "x„i;vOΏ\Zc [!ABiuxE̒WַPXXh> =,_:ce,0c<(jFPš@=!7ү=r:c+x3%@-~ܭΚj=AƍzaSLMGW}de4L9H߿ VÊ Qf͚5k:::S!ٵjՒH$ ^O4ǫ]_U\\ln?!{vи~5u:<D0GWjٍXx<ge%)oC_O)`VeHU:u0n@m O;~3'G~|*U(,=U]Qie/{5~-B;1x|>_ L4L@ 0mJ7f3 #Jz=04Mt:4M1 òZM6Jzڷ''F.o1,3aHTT֤*|L@HߑxAAewIY չ#dCE@p jh᧱Îmy0U(:U\1RltE!;@)kRlZ΢b~.)^ٌ|DzN(p( “Sӱ>ޒ7qC~t''kрʨ=K^ݥii)t߬ Kʒt XiwX:7~r!MXk*Z4Aא!6BbȎ_񯯈η<|!vZ9ڮBB^w--NKDW~B;j>"}onxHlʬ͇k#+De6j p~B+\o;lFOBժT*'''>ox ;88hZS97tpߙ e MKOggXo1׳ԇ7o޳-CQp8b訨}$hvVa*ϡ8|9+YưRzd^^ϥ.HWIU/gN2S'7ת+Ǘ۷5qQAK3#߈-Bv\<4cnC#C5bSBf%LX;kBʸE.B޷\.U5jԨQ!DՊD"Hr !64)bHjMmcPj\>7|Kڶkzӗ@Ww;hP߆n^ӣn}Q`ΝC*Ұ]tiܸqBBB۶m^ @$ݻӧO/Ki~ BCCZCPL6m̘1W* +v_GwnL0?6Cf奬6;ּPS{ᔋ랴jabYD̔ءC/Xw0auU Dfb`gF5>_(Bg/VCF>xB)VlN8^e+aKe^b'sk>k/ٳ3\`tE)H=kI9;/->s5ufaɹ.(n1yOJjjŠ!KG.뢉þ#a#n\wr3jW+%ZiS^V%ÃkpG*n h IM=xHJr7zOk㳰ǘ엀7=EP}f^qY+;/i JU^%%%%%%*k())))-)))0 D|IW/<(f0u; ;~[ K7n/(WX7 ~#CCٚYźRTwRvF':McqE*H^嬙m W"u sfdz"e_=2z{B6U]1R3e2>R)yFQ`["t:ˀ zשߠ p?O @ة[:#j`t[ﵯ[A|}kyd)Y|/_+(F VTkRʺ8nqrr&p1[exH;޵ ALnt}2E1|?^z܏W?[$Kȳ:fR0ƩL,Z@q.EQBвo@ne \Xutt4d2;'gvZ o's3U dJ̿kl>߼n2~bȚdǼ!4L6P.@-&|a0)7˜0́`8k:aلB!1MDc#JQ]!`%!;[L@:.Uh4ӦM۾}{:ҥKRRRRRR=ljժ={32=zgVEq4Z7xUV5jn%_)jbgׇח Tًg jyzF1Rk7b{/_[?Ǧ>A\eH,]Xܬaԋ[D/ O@ayuSܜrgڶ!BNnZ{D͕T+뙪tޜm#ĝiiiisNĵ.4T _y"'''€M99ÕRVnZEsr TFzxƽ'Nl- )*G6Gq@ pwwz*;lo !wwwɷӰA>?ڲeéY*G9\>MkpD®"O,tiXûl9WDBT&%U*㱉&VM1xΝz걔2m_-_NVeIi);wJve[Of;ҬVFƶi6b6£.LOA-Wbg͋ Ώ^g>/)q\@71{u{!c XbqQ>72u=C:e(2rӎGu9sH>aRS U8*KR`Ŕ@2>CzZѪ1?54o[0wٷ݅j9Mjz,JEݻwOxVY+3O_)gΊbVФH eQ(qlP(-#8 ߕ8K+/JX`Y4J 9J<==7.]7F6β*6;*X3Y).{v%LȊAH8Wٔ^ C6еv_wޝ~JV m]_L(]U`Tfx ӧO|||BBBαvHˀ6pBS.l+ Q]64`:u888/dٽ%IKi]tپ}9իWA۷gUʎN6RKɩzDY9vOH-%y^ X0KYms)~+<@ 1cl EūhBm۶p;w Vh[lw^nnV몠UShvܼSr{.UJx”鼼<[b !...۷g}[7x<+Kh]qɣ5_ o#>} .!055ƍ<ɩN:M65[o&(51JVzuU>­J;Ru]U9Sy5?lNPPS[/z>55̙3}cKA!s7<@3XjSM+wjdEd94 v0LFFe Yd_C.EqwwPkdDChe5??^v'x׭[׭[.bXJiry̢׶mў26GoP'K9vaokVgH*0LoU*L5Za6g~6a$a_}s'@9s bq|Qޕ2 sz<(jFPš@=!7ү=r:c+]1|9ɑl{>4q->{e Üԩʕ+z}``]xtŧDuerK&Tr?d@3UeOi &z[_4[t*`o9]hK^6vNQԷ;Ux.؃;W[1, HR^0.|fBT*55'MػCO{@q,;Zy]/=)p9 `foVG.R J|ݴHxɠ.I573iՄն PʪrŪ>l8_+qѳ{3 - Ѓv!Cqcu iӞ76S兲<2!Ç)))ڵsusjEq\B04MN:={ãZjfv&a f)t tq y~=Wc/YQX%V4(@R)*LN!0NO9avaf}~4bKi͛7Wi۫ -p( B B^tih,XI'fئw>}tex&&n|9ah>nR@tН,{@% -֮4Pgne)rf[ {e0mhܸ1MF1k"}p9z (h`=~b(n0 z5ws䐆F"C?33i///Fs%???///5q[QBN#Douo,<5&؇$=Kb_QhDժf>#yu9[#xz?axhږ]}y%Z  DO4˥zdmAf_I{̮#?:b8kH(fwu> Aф4!"U C1I:Ea~ a+(@}aؐa١f&~:bq|d%EioI?)Ko6u gV׸]L\iF/E}涯Ƶaٙ4HUIG'/܆g]r:}зeV~N/$5Ӌ4DCamA]>A/6$(<<RAgwyv!zBywyG,zá8(jF"xyy;wbX}"}ܨFr ͍fJ[;{0 I Kq6j흱gN ]2[<ŽcحBv-{Iό=u 6+BY2&?jYWs0cik[`&33v*F!KJJ\,FB=g_IEq!F@&"iwbbQD0~#-,.۬F\P8 1WTp.(wpuqI7$Fn^2>|}6S~Q5ߪQ)foOݮ)avݻ=/ՍS#9Dwj]RRBccG?,]ɲifĶ3mZ Eͧ|`왳Z}zƿO4 YX~xʛFP oG}S(BzZO4t%%%C1*Z}#zJ"z=)d_o6ZpB\.WT...n"LzaOG5j8!+7t< #bۑe+Ԡ޼!2q۾]k#"ޙZuxv\W h ;ǖw8q ,kNLUx~SнCpԨQ/Z%M7nriQ6]k*ыo'&&?Q RͷNzzF#*Ɓ*F(P((݆TC 8 zbXuJAȮOB\ߡӐ̺{2#bJiXl^Y0,_v/D:㻸vmߧ]|/" be~RqD1':R#liCgYظ١v0 U5HQ70l:bxw]QS;7Lv{p :251 0ѳ<r-wH>Uװ;ѓSL} c Bb΂0p 8e>ibM<!WO.c)U wu>bd\ uCwIOik6m@|8`4bw1KDxjVf])Bn'3KBؗ&C09󇠱4qFd*U7#rV-P#豚vc4 m_cZPRR<'7 };ߧOKTOc-[>y1MVÿ4gG5y%*#:jVj#"\.4BXͲ e-t$8X %hǀKGt;'"'H C+,0]BJ2jAݱlZ@a;8[6/1hپBve'E+%R7a.[\\,)BL_ɖ Ӂ6{m.>ZG|9 ?K CS^^Vymkh"( @̙C Z~>Ava4:}y\BD|`t:3"!DVt:n0 ZiVkslc(N>|1}_+'3@u ~$"-mκ p?HUʎ37J4}J&Cв29a[]~0*\ֆrid3767m}\'okwگW,8yFGriv[U9*|.}*j]@m,*|vWa40uakiwǒ,nWASJ,D"g(3Ȥ 4MF- Nl8gD,2srJt4!2+7='ؘǗ-XrӓYJfAN?kM̉|XB (':I{^`V%lfqR}`\/,DN(oʦhg]>>z=̔1mccN%Zk[B(ETiY"&z0zRݧp8$;;݆lGC]iʪYMRVJ,=u4ܡKU@ݧ4ܹ҇ [,쉘#W*{Q:y;G] EwKA={exYlԺQ^l8aL7=ҧ@@P>VCTLhELxщZzU]OfhsiV[0 IN#FE`0oajD N3bb@ x<\׳tA,KGψ88(4h[]ava63X İvX8]7ZlQ[{@\p|뭎W+\rL\иGMމye| f|HLfoN%_}uY[zZ1صqTWPV@QMuAƊ]L%C| `R7W/[RTJ8vVѫ0\T\dBdNNNƍe2>dcw޵}ь٪0Ÿq5k~HԤIq#Fؿ EQe3f3&Ege'E9'̹Z+ V42/o]ɓ̔>Jdn=G`gы{+ڬ][pbׇG RC^>,㱲Oi2iu jug~ٰ˚~ \(aCjzSs>*f[uEL9jQzboV}{?;N^UE# Kgu>/l_Ryr"i_/]Ժukҡ{>۵?O=tsފჀ!?!=vVw$RB^s甧)dO*5Mdraǿn21jRáS tgQ]g*f?# 8{HldRaO75 Sh43c@tP UpCz=@h)#Ԡ|kbGp /'8ES.)0z^O y\|B zBB_VV]V-W?O(;kI6qcHd-%ݚUPL'KQ>+"kK!Y!K g5' bB F^1cCSÐ*i5bQVĬgC8Œ9wܭ[~gZgϞ~VZ ,XrelllY >}Z"L6 @||۷=<<<.\d[^f=ayIyN(80@l{W#|`w:@Q-qfM@ 0kvJNa)hѯnu:hY'Ø^ތk ;h iNdywh2՚M`Uvd4U [Q:|oVfL5G0@LHHd3B&{lp8C{?m~$.XVXqXcl+L?U(.}Űܒ LkYR8 e@z8]<}e L[玹8#npYW衧SK.4A٬1kp8...m۶=sLttn=z?h۶f4/4+JϢʖ*تlu 6K4%O-'LZXAeM yxК0x`.@3aHVKLX.!g @ľߌ깲Yu֋Qr(Kqj9VПvd4/lmH=eLm'f8^{,FsZliG(rppFeSIi/֮-ZTXX?zWԌ7 @/M׀7qG}4҄mńqPp|>Db1DQzQ3XcMʺ ?5iWUiK eGPzDVllt㹻;884l0??L&srrdb P PV*weizƽR^ghbK.MOOoРiA[VZIEE4M5 @rrrrr)S>q6f;a=*q{qz,:qUOJAeڠ`N̲Kĝcapq+ E ݽox݋'{< z-ENV$̉ 5EOͿB |*"N*zF# Shal)03>{J+uHV^%Rgu_V[RM.\ O::! y<gFygN}I ƑDEQ# G9[B9b˥(P|Pԟ=@R -_}l"\~]UVʹD,iv݉GNJJR*3r<::@~~>Nvڕ+WVСQ@¢}~QgϞڵj~du*Vti.Pz?F_2uoeF$TIb{ǖg#p(?L( jeSRGh~5~Zӄdm&RMDQ{4s]soG7WPg˖3???99ӧF!mڴqrr2xxKo>ؠuřo >d]BNiZ B ")6_(d. 6'X&"P@O8=mܼRoQ!F6g{m#,ol %2(BȮ]Fi흓=p%KJRSܪ8cʠSNNBa^^ϟj Q~-(?k^F *E|vv'Oh/vvu lbΆ o}Ai3B>2')GϾ/RiI%pPV(A! ߌƟ}_*_;8o8`^|5rgXv.r*\E `J'Ha^m6g&H4aۅ=-4zi>*N g2ng<9"AξpBZZZVV0jժ 4pww/h o;̲([n}ԩ:z04ݦMgils?NQj^w +f_IX&a*t C.GW(.KUked3y0AX;v &CcC1}ߧqEPI" 9O-̙gϞթ v.㸀rp^bg' +N4\{1C+3pR"P µ+:$ާ̗:p?>XZ̪&U/zү1Liii+Vh*@f J٢**3$b 4 HtA\X WEpwZjƍٳ={_Cu)bx¡u9U Sk ~7]w˲ ^z׮]ر#\86io=}^^^}=f͚wP BIϴiޯUiV*i>IX)JQ RkD0-YރFZ]?50R]E~8NTN4x`Sq؁q\RRӧǏWzkРAڵ9W^x) ZaƬasG Qf@A.LZ~j322V\iO,s)[ӧOm,{C7y杻/x[Gt^}rځ5U@SlݡΥih4&a(E)P2k7mηF=l[y7 *1_DMʡM2EF7 y ۪%~b7rJ>.h6P"lU.D"0\qR B~ MB+kz׃|YJ 41"P XkrEqI=cj`nY9{# xnW6\!l/ݶ}5[4{u>VےJ qacՇ^_ۓ*sN-Z 46nŨ%db񈥻 ti罋k}(J*#G|VdEah4ġyʕ3L.\Y/˲F6l6 _B?S&kpz#jۯכwmlĄU6mz~[,KV{׿PZ۶mEi鐿7VξvH7ؒ6PJ݌[: +E)JQج/`scFvU* h3YJR3<_%B _R[;.??V$ CWFN闚tG{@Qs]nFmo4 ĕRέ0!Qz4;dn&u훴OgGvc_Xp&xiܔ*)g7Ϝ׋b ϒS.Zq%.D-~xU:$7'n9MyBRFW gdQ*P)#~B>>>8Q՞RyR(.Ed2Zap&kk6Wu6H[T7"z3PRnAL8rL ٙg6~CF ¿ݣ̞2\&&j29*KNw䉋AAAG޽̙36lvѣ}edd,_\T<==WZ٧ZsZڙ'uٺf-Zp/?w)!]B8]D"? (E)P+=rK0)[`߅r1-=S(n^Z^¼/ 2]Ψݤr U/IoбϨ |X^Y^T)sHݬE[յ2dD=0!֕Q7@͹nھմzJ 2b<[sSfGmI_~8 m/U[y:t'?(e7luRcB!HĶn3GIB&yzzjMg`h8VogA_Ձ1eCǷ [Sm=q[)lA77̀Ѝ%Ϯ.Q1wvA=g=l}rv^n()TѣG]vÆ ôj>jJO>}12/7o~С;w?oye9Vr׋1k C$T_z5k׮kZyoD %^J/E&.6||ZUY[&-2˵ׂn/޹R@/\.H$XWF˲R9E_JM!04vS.hʖk޾ rFY ݷd^> Tj/5{W.6WM,\C)`h4s(DA\{v'}bG6lȺS(un`.{!-%ؒ ME%\sT Wf(|3{a(ۺhT*ie}&Y}zŶDp\LT>0 rT 퐯e׫i R[YH;YHa?Fmܸўm.w4h b^]uR)qǏ!""Bєw.E)J@n}i(M >O4aYo~7ŭdbclS8 wI{Q+':)rjB!JrxP]E[ѩ/&oΥz zTWY]uGT,qD"}&DQ)$ t:R$N՚MDz^G* s~ձ,)qF`NPe˜3_n33 B|ʖO:Q<'4|aj3nC`P-{Zl:; i” 8/U[snǥկ_XQ3-8=XJ/?Z@S<Ď:#Oo1N!B9A0h1`_񡑵 z8YRuRٸqX=wo֤Iۃ5,O볼ش˪TlPX2J&o~p*>8%]pV02RMٜ2JUj91"t)APԜ/eVTJA(n\ʥ!:#j%08hTM.N5LT뙖5GgR*TA l{$ q\-aJ.YRxqQ=ǶudRYNM6M>i09׸qcye6Fuo#5*ӄߴ/ j/2q9i 'oiUw ''VI랽*ʑDi}dOR;CpI*:==q׎*Ggo+3k [h9k IDATqZ(ܸx&g#?w^lXMn{3XM^@E,.ex}=DrǨ.{`҄c4V=KF--fӧ8ɿtNCKJc겁/\NMU>zN[˹oDؾO8˦_aעCŸ'\ziKD,/LX01L|YG֬Z"YkNl\^Z;_(w:/>Ѳ[)?9 " `e_z񤅟D .V򂁅Y RBK$? BH5ƌYkM^^^/{S¦gA?ݴVA)P [7[cF(OO-Z8qb:u pʕC]tN:-Z,*W* tN9y 5klVV(#2v[ [-D^bcfY;#b]Lثm3M'Vh-uWwu-i79;ad9N':fJ-[R6e_\hU+ٷ*=~x"@IXswsHLșbT%Fw w[ 3+ \uhOWwR@V|ys=W =馜 {7,)YМ8NgشMqHUx¡X~Wl xp`M/ã˚F-vj7 .z7N;a`oe ޗ19+9G'TU@Bǃ}|`6C*O(7dM\2+i U{+9NYg g/w~^^m՞kJr@U jyvh䗽ynN0r! B \ף{8Y [ /y'wٴi y) 0pB7ۯmR m5_V7OΑ>o^: 2xfJ녃|Rk{\:|ސ摚[Y[F?K!/Y V8*ϯ}~~~/_OD6DR\Zj-[Sx%0B\ [>йMu:Z&6wF,y4K"k<'oY-k!^ö <Λ !YԸ-=5y&hc-ߜȵy q.98w.W>m} 5 PoQB9yFlq[*@?FU|N<.@X/`JP.[9sNu|` _09h xߺ7N%c?3z:YV/mB M.'5ZvvtF|949AT.^vK ή'O@[ϲ,CD<<)3җ_KY cIz'ݞ| -=Z},ޜ2jƷF1RGl6SUk-9unswN$C-(&4@@;ve3CjP-`4:#8eųiuy_~)"?x}ł͹F!wwګ {䮧•' V#IpyצY?n\F%3ߚޅtkpwoJlHygQ%: #ioooJUV-VT*ZR EAV.Fnhi| DzYov>X̂ nnS0@x'tZu f2?U`;11ýb8m{93#3e("D!&h m/N ,/<7z^"clpPv[7hun0ED:/|ʖ=EGCDhː3^Id |u0{Mk+}W.q^r" F}5aDDQ}?ih@|پ SzMKF@ :F2%Cި6q]Ä]OѸDN^|(JTyyyVqg^ȡ[]Wbl :VZpf.;[щ/F ˲C- MpcG~y(̈́Hs&PcY@hdkD b2rO\BLfN>Gd2Ig&{JMOyn\B.oʲ3FOoZ/#cK~= q"ٍr/Dȃu: _t\nj+f5fs8dr 97R:djӟL "Uzwnrl2t8db''ǜ\csFnxc d>e-}n>m?^3{~'>d2gjlѬڣ}1mH/:kuQɟpCv/ՖClʺIRoCSof^yA|b=m>9~\UIƎ^|D};L?l\2>jqs 3fe`Z)Sgi6#}C~ܫLXKq 2ܿ%Qfw>auJUiYds޸uĂ?%GTWnn-fȚkmeXHʏC\>k̜d֖,U;ᯟ=g _t*X~@1,PI ki67A DҠ \>|[e:eϵ÷zl6:Z  BgP2i4+qSqdpN~ ;q>Ǐ_Rw; 7&Oh1'yƄG‹7d:" ,Қ<7}F%$3鄑!ُMXn]e'0kϕ0-!S9usfvflf _GgZYuȓ",G\GuV(m׫VߠzMl^8,@ lM,SțT@y7!- )),ۻ{~gfM' @p܉~r"xr# OFYJnT!!'u@tǷ G;ϛ{$mvn rLb_^%փ :,B!;/9tArn0kk.-$Br_gE(!Gb5Mc\hhT( ƫ?eu<%Y<䬰^?9Ke-=͗=C"|C:|Md>փ]ya_?N\ |DžU 50K]^W xP[& YgR >+ u[Vj֒ ai6[)+`"D>BW˖"B,ҥo:eȞE+`mr/ؕ7( v_ȣQϾ75?kpʽ5[~ VltݚU\ G]w)_!-WƟgPa\,6jNcInPӠ p9 hTٶ,E(K'3Ƀ^$ --NjJ| /# 7K)pk]ڐn䢭S;mc0oQL]Tם7v? .{إ-C6Y֥,B2(sǨeIN_aL59B`}d2Sa_S#!nV1 ϯe]ݜ*EŗHqjlo#!BvvŋoܸjQՕ+W_A;thTyZi¼CAB>NoaRFV!.I)X3*xU+FH%'[2UԮ"ZӬ԰̕2.0'C @(դufmrHR ݭc=I3,*l!W%ъOΫ,|+"t3(BiNcsTjt(J`4D);ݝPNip&B)lB !2g9^S"PVb%\T@F8(uWțX^lSD6fB 0sZ[mfc&p&`g ?g"DA(ٸQ[2ե(s@h V%g5rk4BQoc;=6PFQz[,N^j"ȶNTFiF{T[)2d^(|W䣗'VWZ)3墽Y䛹\@+o$6m]ND~C%e1"Dllqy \vl6'؃ǏWVmٳ={ŵi& xXmk *|ׯ^_5##u>>>2M6Yo I s{:)?uS(.l>{!@hf.O2p).fNdZx9{Na֖ΌolߞNYDiJ@(f熿&pmDvHi=|}gTfZ}$j_ju@4MР]He|&,&uH`m:6 p[h%qs!ys=M: s(k݊(|tДpl|ie[HF[QBh@.4m2ӚSiq恞X@1fiZ2cwbK׭[=]zU:CF!`E%y/Qs8ԏ~i"QMӖBQm?E4MyeyhZdVlh4MӴLvM5EՖ}N7xt>gG(G hͧĝL =c74m2וN< 遭9s6 iŕz+iJLGj4c_ҷ4|_4eNQ{#3Ӿ_NI=_&Ll,R?v U|Ű"D&)O3q(\nT#do?~fqu?^``q1%3$]vڱȍZ!! H>U#ˬ$VXrdZ&h#<>7pΐu˱Dw7AĖK׉c{*h՘˗/9sfqǏ/WhM4hPLL̯zB {FO{YG2w0=5)@QԶm:utiطoA>}:33sԨQ&LΏ'_3Ni($xahʣhڳ״Uɾ Gh|xh\첣ff(0qyrDɱrtq(f@g0f?zWt9:1޶ܝRroVT u"0ˢc ;i9aBW.it3tG>Ա,r⨖~̇~'>~+9D" h <wm;~oH [&NtڭΨR>dј0ti@3 Cjee=<ڃ@.["0$Z+:Qqt0o+Wn>}a;%i*dy'?RaD1v , i:}ς7v0 MTKwSSͽ +U~.~ib)%6~X޻fN89oў?]91vI@N p04hKEQ6mv_n?gSqUO|7O)K&iH7RSe؂mw Ԃa?pZVM F0۷)*/LEK$ t0 ä;8,3i'P[)wZ lYvRX:X'2Nڵk/ؾS go?̫|JbNl -rRHh7)&edçu YFLW) )#r)#J -g%}첓qOzLK6"PC~-C_]RϺ[M`R,45~eD5b8wR;lT1~r2BXe-˖![!P+k&ШJe@ H"]5c}Pc{$pamȂ <~ٳ!!!e4M փ EvN/~c-CH9iCˏݬ[aeKR1]!][>%otyA؊"qASOR}yq̈́3 Y(o9؂r yc*raAh4wGv-$<( F0u^wjamՒ6^iaszמp1Tz{VQ##j f$)09<6ROzͭ#VGMSA m=ֳ &Qk`ɻAQdHTWPjF,.'QD0 .Q Xs껝Ǿ`G[7c$u'N Q{N@b/j/`ɿKf0\Gi<;=*w|X Ǻ0(&+,^@wk+,5psH$Kѝ'4Jlv `o='}+Pz]Y]pdJbq/1ᄵ\.~/}N-孰ԏBB1la}u{%Nߗ8 IDAT vH$";_N(#+h)${z: tխZa0фY7)-+R(^c{4b/K[}SvL5\)9]+¿Ч?(~{]ڥ ^Ph~3О;#1gNE~ WPTqVDgJhDK01'Q39 9?qU*q:pCeKIMMuyǶl4gJՠAʕ+K$[[l6{_zٳg[v=glx sjS3/1y@ P~mp޽6mvÇǦ`Ym۶z٠ qݻw}֭[.M&%%QFr^(mCaQ*:*{g)¼iӦ;:A={YF wwwQ;t*܉f=ybUe5󗈮=3@Q2`stpP1kerF7fFQ2Ej32 ynsܴNfy9/uQ] 9YDᮒ3*̺,lE6h kTr (DR)$6"MQ=?Yf._P9+Kd,ٜ#@˔ IK#XBt:23 \.尺gϲ ,eQ/Ag 9:Y!~#6^2& >gitUߩ^x@8|x+_Di6/R\)a2`\ٳڵk+ ^y8{֭lzt?y_l?ҴOM _?reB9ҪU+{ԩS Dg͚%qJKJJϺtjժNji.= \Ťn feRL&N\>^J +}7)}Tx6k?_|=}ӦM۲e{'RUƲlzzzjՊ+^zo֬E(h@ʪ=9,d2OOϜ?K*D.8|y O%B\P(3Nj###=j԰a֭[.M˲;vhܸ D.ᜢu0hLKKQy{{{C+L0WgKͮ\79yAPLz3ͷ9uX A,є,Y&TˁQ֬aNnVfgB&7+lcf̥R㻎ܜC!dF׉{llV?Z gڿJ]*\ ̆.r+^m[5 zl6S8ѧeY(?m7[z{]*]DR P(F"axtlߥ>;E*(%n[|p#rntyKvS)s/~ 5fv4ev5/~<8 DG6}i[أ ՅO322!r͛ճɓhQ7 9?H+ )QBŋ/(kBOddd 6_(ȧm16x68.[QMRK$ Qv g e\s304m>5ۤ*-0nNIwŰm~Ԍz(*==B'D[  y瓾(E)"8XJ)@Ekxί3/ hN25~L}^bn'2kwQ}ݻn>w/l=4~WbKo,b)nVm<==[h믿N6N:;w]6WΝ;9MlѣG\x1>>^d9v~uE?fK@˰:q \%}No//.>]}|7l` o6?_. v o/?)߸/ )F8& -#6ht$Vj x{{sNmPGGPcݿ_T2nk1bDwߝ>}:|}}g.ϚVM0aС\R7X \^:~e 9̿YƋ3.t]=[hSn@Pӵj xPw^vsql3[VMNNnѮsvY),,h4Gs=zuj#?e0O=re+ JW.oz|4;{ha=w~0>fsv/Vl]o~:y_>|لj+ 8:y@um_ U(}Y#xyKlFbzU;c-fu,[ZabL![̋JU*=UT^eQ鑟K(VX+rp==$EJ%HCb'g?<Na(/޿uwvlrt'{=a+3~KʲrSw?C Vsܴ_o/?ȕ ͵=+fK -4Y)SL_2ߨ_0f>;Cl~a|w'!B+gVcJE8|a;Ksd9,WT*j |\ݰjU _Sw OUUa2';}@H@ѨC@ 0-|ݾmZYrz~=T)ul^a⧽EP,_:ynLl%w?qP>_v_T*^<{rfaե/#dZ{޻@վAȲ0DzryC"Шm !Bl5#o>r; B5:B G}:q>N=]ŵ+Vwl =zkū'&,Y$^}z4'csIg=X4xlO~9۱ljk1O v𻪨V)IpS>ur/{DccB/}zqMb8\_fd _T2[ېB!ڧP$7"BڞL^m6KJ` Q՟)$5:iב7!{ j4jGn 3[{}6vl!bBaAAǤ^ɫ^mFKfk- sfZ h-M^M!; ~~~i5:†vppUZk5ՅvvH!̼O!0׮]h4"ȈN޶הH>]6:]&d^SHᩧj(B!<+} uȄB!c7ڵkeee-B!Zl͛vdjGg !-]6:]&d|ܭ[77o  [!FB!Yi>vZyyyGB!B՘B!tVF[Mۑ !BHfs 0yyyF pڠُs5!ܦ]G߄lNICȄB!TGH!IX)S*-B!ZjP#}]jtγ&v۴wtVc-#B!ڑ !B: Bc;2!Bv7|Pk=z:YBZ^mu;McJQ!B!żPx j5&Bl>ַ#B!޻_Vy<^ ǩ ]:YK.-+j툐%s]Y\u@iS=LVu|'Lm7BYr~~֎HRLLґT.ݽfwnH^HET`́KŶBI Hڂ SeG^x5T^O^s:ݞIZHZv`1IuU:۞oƊI1 78|r;6>Jx[bbbj>,7Wk:*b68züPؽ{w}1Մ>Hxvi^$U?6A٥C&/ܔզ>ܱOT~}'J0@W||3ɲInU 'N#PzOݩu3:@ѩo|SZfƕQY;N]>drDbb,i릯b*LP\ Ӥ()RkIy{{zzzz"?-5-_)rI ބVԬґ/J0!Kro Jg51VUk/2>~xx+BflZ0"8 !O)kע~܆ycND^m'kOL<8xĆ2+uczJV;Kctw]0LumR5IqufinoHrj,|{ O3wV*jۚ{)lG|Ti5 Zk0 k.<|&_Jkҧ9 Q˜VpNyiN@W~YnhWqlx"/j=_r.]<WJhNɑ\!ET_?~3D @ӅCB? V11Ɨ[IЕN]>?Kg&.u 牥AV'·L)Z}ϞQeK0uMTY|jzdSC%^e?̅#m p aKo}gn׋Ԅ;Cƥ,ߒ}c_Ď1oEIISJN~+\^)oٳg{]WFZhkBھJf/@k b/lg}EߌX0;q{g.$wx#ekSӷU$,3(ؿ)N,ɍݘ5;3%: ܕcCSm-e%I yvi]Cl/{ְiIp.=I+90s /pà ?rq8K"'jHEmq2ex *^Obf@P|WG(pĽf{f`7͓-,˜ү-0bYlyU@hԾF8$dxԟm+i܀w?>;5.z6PR}!1nުЉk圔^lޥ5[X M** Pw.7-B}Uj?[iSJ1)UPZѨj9 ͸=HqSй&ŹwWVw[TiH5qRjΗҺưGYH\T9oaVVX%~u W GWCz [qn%seox7UU:)PfR }@q)=2.ue ?z)2űZxhw묶=iq=FOR6nc%%J*\AnšK噐.a؝@HU:R$,[85UU 6MYm !-|S $/Ш^S5 M9c1Q H:[ZJ6@PYU<Bl=l})|IpX 5v0 Ó>=EJWK x=1\ y60)_w8ȳ҈ݛ$vQ㐺,j"R,9{LY&i/hIU0 ٫7yZRԔT<(yfKl(Sj9VI@W(%WH& E@uUCΚt87~ZYr!zۺKk():Uf-x avB4[zoNZYrfT'it`m(P0OO  ]<֗ad}}]97 H7?2 8|ui<%2@B,);s&O[O>M}AȈ ]}NaR(7icWצjx@LR}ϱ7)H0 ]WʢTY"^jT3|qPvI;wn4tJOmQevl=|)j+%NKaږ_6ʐp6KGm;$/AӰ>-~/}V{ KEmx!VG{RNu*JǑ @?t4)fVtRL"%, [Xij XX`s2NӢnN<mf$?.߰_JIz\Z2 e*4Qӡfnҳ< icZtgyTY}|4:6m6f-koTqY~2cEaIGKzR,7;L.HZU366>=lZ3d% {i.DgP=Fl(>iGNIƸ[DNUVl~L7GI%ov:YBZ^mE=Ze, Surface View Surface View
Surface View displays the medial and lateral sides of left and right hemisphere surfaces in one
Viewing Tab. In Surface View the Toolbar looks like this:

  • Orientation contains buttons to set orthogonal and User-defined views of the brain surface displayed: L (lateral), M (medial), D (dorsal), V (ventral), A (anterior), and P (posterior). 
    • The Reset button resets the orientation/zoom to the default.
    • Custom Orientation allows one to set and save (or not) a specific transform (pan, rotate, oblique rotate, zoom) for a surface or volume.
  • Selection contains pull-downs to set the brain structure (top) and surface (bottom) to view.
  • Clipping contains settings for cutting down Surface, Volume, or Features data to be viewed.
  • Tab contains cross-tab functions for yoking the display of two or more Viewing Tabs.
workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Surface_View/Thumbs.db000077500000000000000000000140001255417355300256550ustar00rootroot00000000000000ࡱ>  Root EntryV256_ec683a118059255c*  !"#$%&'()*+,-./0123456789:;<=>vU81JFIFC  !"$"$C)" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?bv01]˩_]Z9lR7G^rOQ4ߴ{b;>gg_*F8._*9o?;-G.%pI#1[zUcԸ7xg٘qjq]g>g_*ڤpk q@ ]}*O ֿC\@ \/Q*O?.Ze?xL1KtHJ`|squ}Ioږޞk[kv8n=w.o8=b$,:5CPfF/ɕvj9?l{a"rM"l7@= V qNNNpRjZTm. 6Tl08n؀T =[NAY ՂF٫rc SK[(:uܿZr]L[ E [ʹ/}5cRNE>ZȱvɌgνvIs5eޚɍv]#LŌdc->о$`Kyx'XIy V&_r)ܼOՇwڴַ s3F p) r E1:lri-m~$9!m;<;03\k3$InEAF2>ҟ65DUj6c@ﷸPpCts{4W]Qh(O*,zdbѾ7md劍`~vuNw显Ryڒ£*В~pI<8o 1iQc-- ]L)QQHe6B횧/f &:dI |^So (&K)&WY$t$TX)WdbeՌ9ic2X.ܿ.V%.2y90Fw:L I$ndh.V wѾ%̷&pS,)iI` Ӧ+|-Twpw /G GONrB Q-~ա6X 3fֻ{HҦ}IDųNN6;aw--h1p6AUwc$O@n4ad1vco"-% 1Lͅgћ0{χLé`y%O|qOF]'J LC"BURe8QM6ЂF(9SFr>xNXǕ 㓃~ud}]u?:w/v<UGPOg]^5ژ,@9#@)@+p3C 61+А==3Bíp-C ?0ҵDzF+h(i;ؕ I+_}VJ%F}*(/M+cͥ^K<DY"P˂2pwqg"EWM:n.?ͻ4l'80rx鑜V]|U7 g^!xs"€@ yaATOx}Mw<9E?sڵfY*пOFK@Z.6\i>|Qbfˆ9j?\aIaq@m8YDX<X9<A5r/4Yk6XΑL># r=jjJworkbench-1.1.1/src/Resources/HelpFiles/Toolbar/Tab_Functions/000077500000000000000000000000001255417355300242625ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Tab_Functions/Tab_Functions.html000077500000000000000000000042171255417355300277150ustar00rootroot00000000000000 Tab Functions Tab Functions
The Tab section of the Toolbar contains controls for functions that are active across Viewing Tabs. Tab Functions are not active in Chart View.
Yoking allows one to define groups of tabs or windows that will have linked rotation, panning, and zooming.
  • Yoking Groups are defined between Surface, Montage, and All Views OR between Volume and All views (yoking is not applied between surfaces and volumes).
  • Surfaces are yoked so that the corresponding lateral or medial contralateral hemispheres can be viewed at the same time (e.g. superior temporal gyrus on the lateral surface can be viewed on the right and left hemisphere simultaneously). 

workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Tab_Functions/Tab_Functions.png000066400000000000000000000237171255417355300275400ustar00rootroot00000000000000PNG  IHDRp;7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:IDATxix՚NUoI'C $l* hP@# ", @E1  ^Ql1 (xEo"q K ,UPݡ"Gp!~So=YHQQ !w71匘C !1}o*U# \0}C,S'NO+Tlhw>-n Vl4eWr֌wgs.7OMoՅ8X|+_Y7gh찙3F)e_`f:g=;}دu J&<ڻǯBΩ;isN錟vfovNOX@[% x悄ߏw:N]H78'5\7LV-oRrV=4Y5K@ajL mړwSCIbR/V~hξKgXNN@jCCWڴu]rw86nt҆u;d|ӌhm8E}??wj1"G& d!n܁fc^$>[.1uݹ<'q1 Zh. ncI}}"1py̛:)+̧vv<ϯM-,JW=Ruxli5x@fѢD֒7'vfV n}-- O2E -W73_]S\zK>kߝ.cz[_ފN aJ"%0o฾RXޣy6斸%_V/-j{дS_Z2lƊz>tʹCo?\zZbdg>96s[Xa#^?4]ҰO(Ж$IzL[e W֚5Ͷph\gΜTwKcT?,9jaq`k/`um^'넽3YJ^ߔ74Z5k3'jI N`zor6Ao0]^cl0s3sڤj^^^AAAii)ƴ:K 7t ! ,z-!(_XXXM/BU7i҄kLceO^v*RA Dڤecc9]v݁%omWO=щRZDEE)d0CCcbH @c@S³-{/YSW35(|~'o6և}gWT&Zrc{f_v_aw|0$C1_S4"EUU}D)u\@8n|}Ztz'i@^Պ--'?MڗR% 0B4uevz~WvK(cLSd E\N:(v_98-z!Az~l]s:av*9S2S3a|?7-l{Y64D?;h3i&g>Gt'X~{9+翱m+kb5k(("!B T```Cc Ӡ2(S4VJ29(H4J)a>ZUVV@!g=6tڵzL[NL8Z }?Zoc~=YiS-[qs;cf%g:3Ĉ#05?zn73Ԍ'Mrs|ʗԬĘƠ27zVS6UUD-+.q9{^ڷݛJ3$yO͘c?ʀh hI}0{9{w9dцiʣguG>[,K vFo}v<$TF #F 0 0 ~1 ^L $jf/ 6!*m[1HA(C!߀|p?z w#%/D"DR"B@2 f%[7.޲ŇP:E[0꿗Q_* I) ƪ|G?=;عqVKtvD-&jDm&fmfiy뒯WqqPpj-&j1dE$fJ5Gjd{rem stli$j(wOڷsߘaf@ܬɝ$MϷv:{&`kl~r$I*d$g$@ǧv\"~* =HUHw}cҲ$o DVg( ]sޏ?$dsD4 "U J͑'OV]ҥKgϞ+j7 kD|›v˥RsX`<蕐}P~BM!JжRB@"/@e;eٯٌiSU(%ރߝZO~EQL&lB okY^bTWeOV{%Zk/{ۨ @ -^ Ơ1!WH2oKKS]PBf͍5LVjVcomh*If,"H '41i^WSUj0B,f7ʲ\PPЪU?NMb.w%N$\1U.B4v>! j6QJ׮]裏kN+iޔ~;GJ'~ &.0(8q"LMN<'w= #Btt[n]Օkc?_DSA p8$I TjzePdW)N{_:5u3~CPr9#g !(g A9cPCPr3Ơ#C9cCP53Fr31j(g !(g A9ccP⌑1!(g#C9cCPr8cd(g A9crPr3F 匑1!(g A9c J1231QC9cd(g A9crPr31%CPrƨ1231!(gA3Fr31j(g !(g A9cPCPr3xP(޹}+zu? fR5O(1uq?2ʫр|--Z8?oMsp8Z8vK)1 (#4 8M?8⇭MJ|@UUT= d(kH(r9)!`Jz) -rNֺn{ P֍\ᶿzKh]i{VZԡo8wf٩~cm7PeQ6^`Gf=I&"5F!`;Rҁr4P:m`}84}B0y^SyW”^k$8w$PLz*<0mf@UjTTT?BRv3 f75ݻwG5L IENDB`workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Thumbs.db000077500000000000000000000140001255417355300232730ustar00rootroot00000000000000ࡱ>  Root EntryͲӢ256_8feb0b64bbccac36*  !"#$%&'()*+,-./0123456789:;<=>j6{tJFIFC  !"$"$C'" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?SPhe8vRџ+ 8993s{癬,T7NsF54˨\)oʙGݯa.%ѵdcpyA@6aqX4$wDY s!ˌk7z/uhњ7N $ ts\mCYĶoq.zߒzIn]#49k^5𭎣F{FVmsjLA<z|}x_HyGL!?{ׅ?3vhf[XaK))ѥD|PlFC&2:la@#).m.4低E"|t7=98VtFNq!zuǽZaD:!UE=zԺ-LȂ/]j~{,da xb|d|FY~S"m"avdV̏H} GURrXI6Sd ,GMXO8E;67ɭ2̑|bI#p3ҦohN#2A+qc=*XYgK2v݂K 0V/5cgFL^1'v "BO9c@_H#Dq FN0c+]è{ŵ!bN3xMG[Yv?h.HN1ߧ=K뷓5A)˿ODE,۰\ 7Jϱuk-`&`n&~`nN #9wu/Dz(xHq;@'(qp2u~s2d9`HRsNOlp ?ӻ' |[,w{ۋ)OTXῇcK%}R.I9GoWfb @9(0n<]p{i`"8$`v=NztS.Sbᣏt 8cxJ(xٚun-+Dh 9~Ś+[ y07e(~/֖̬h\ ӧSƝk56[ 0!rv| c ~з4/O_{W/q⋕i!]>u/&26 FNz`u=;g(>&\eEUozRNT89qv8H#gYxPlo[!y*0܎9hQ@xC)w!0t>ՙɋ6wM[۟lQEf?ʑ۝c<` Toolbar Toolbar
Toolbar is the section at the top of the Workbench Window that contains viewing settings for the structure you are displaying in the Active Tab and access to Workbench functional modes (border drawing, region of interest definition, etc.).
    • The Toolbar can be hidden or shown using the button at the top right of the Workbench Window or by clicking on Toolbar in the View Menu.
    • Tooltips (available by hovering mouse over buttons/elements) are an easy way to see information on the buttons and functions on the Toolbar (or throughout wb_view).
     

workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Toolbar_icon.png000066400000000000000000000150201255417355300246420ustar00rootroot00000000000000PNG  IHDRjx{7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:i }8A,oφF,-cOxY^+y@%0HA "=~M˻et l!Ԙaina?f;{ :]Y @ <"f3<@ЀPZ@ {i + (pA"HbցM `/(ap @38 .: #A8Q!%Hҁ K 9C? EAB:h TCeP%T BP?tƠ'a f0f~p(ë8Uq _o<(@(wT *6 P%*T= ՍQX4@>04].CC7;7C W 18`ؘH ǔ`1.mcvXl6 [=mcI39q\:.ww7}xK>/oƗkg) A@$ل#6 a(K#:CIMRb=D"iIKIR.tt4DH#1 vQr; BѥR)%c)['Q\Ij@4AZG%B:GD q GfLL̠̤,UB6P6UHVNNWS''wX0EբS-#. KӣiIB Z/mB^NZ>\~|9y Eץ)S;O ,xm  )*+(6(VPTJVڥԬHlT9Kr""EEN-U9ң2*RݯzIu\檖Gژ:UY]GsSza54iZ Z{:&յhӮӾCa$yUYwTOQWPZJրilp61L4,7a c퍅Uƃ&dIIɐ)tiK3mh]ff_mS̏?lfВkYnyˊbeѪꕵuA6T%6[m:lيmmb*4fycfG[tS88&;:.[a'M'SęEÅRU˕ZeJbgt3w5wwp_( ,|^3Ves5 _;~d2'b%%< 4@vGAzA~Y]|`u!Ԑ!!BBw> Q!4\y=J9J\lﲑ;Y~uVJ< Tq&qq\w> +oo,)8YSBq(߉?X8.p ^%$Jz|4y:%"!*& ;WZ_d$IV;޻zB'NҖӐ`ӓ]Psfy懬kddfo~ZZڎu6ZZ_cƼ#޹67%ouoDliS||qVǭG/wն۾ ~.]'laNN;\v+-)޽dwƞ=o{ĺ>⾌}RҖw\XvܭBb[]R=TxӏVzW6UVrXHV.n|槿+(B3y pHYs   IDATx]wXG RDiv)TlX#Xbر&{/!X@{(*jl(EQ1uxgygn;o,''TCOOPZZ Ѐ4 h@8/_} JЀ4 h@|BHi@Ѐ4 Vegg+JJJꝞ/_D...ǏQr!77Wk4& :O \U/^|I]}Rcĉ\A۷OujٚXmM׀M6r8-IdYYټy蔊3g<}0!0`H$" h@\rʒwwI&}ir @6YRRF!0 $v -e2!at'z̀(1  U*X9RN[hѢbUUUϟ?,//߹sʕ+-,,]s۷O>]GGIN(Y4!}ر{xiZm?A8G,S(;tO?1e2ٱc ݅A_|I-+IO>|r&MMfffH$OޱcG>}<<@$rq 1  E!8bKH4dVVV/k`~zfr<((hʔ)LhHm۶ [|4KJJJ H|I\\܌6MLLƍ6Zl^rӧ666M4ԩ}||X QYK*@kH[P(T:91 KOO@LEQ5 *TѦ4^J%Y`EE\.722Hx{RellL" Yyf) ;w.RQQqYUa-H-ҬfY0rMz4a~ΤMIRb:3Q13Inn.ҥK$IcݻÆ ?yP(l߾MF%O )%c@AyLeQ~kӿAܸqmڴСvرo>]]!C̜9Fj}/_ؽ{#jZa||g OL޸y󦯯YUUaBH$A"Ν;\D"צ:=IAq<'/ԩS“'Oǰ8F`J J-BCCO.H-I|Xrj H{۷o]\\FiddV)-22ru|@(m𫻻۫dX~_-يq\E۷OۇǏoܼޢ@ xr*=i)IxVjxbAAG!A$Ű#$IbFDDDČ3zV}#0`Y={agΜAaFhij$uS3J<45ʯqN*90cHqr1R+իWxӧOwttdXtfF ?w!#=ļEסR xSSS]`aaQ^~-H*vooX~L:(d۷hѢiӦ!I\. ttt8p 7or@II+W,YdẺܻwoHHoDDDJJJEErԩ_۹v21!,--˳D2LV%drR&5jTXXXZZ,t-0`@BBz_ 4￧ٳ`߽͛{wչ;v\p&t"ߢ !!!m۶e᢫2r"{{oHpPfTbiQQQWtqpvqo1@ncg/G4>SC\\8;7wmbbbR)+?}gp09c"J;u_uƍs纸ߔEYYY{쩪tզ$IN>}g5ONRIX5 O>>RЖaŠW^uޝ yU\.J!!!r??Ċ$R[4jpzV|JaNTph0[榚 ZOffeS_)A$EQ$E2& bńB!+~g==Kڵ Sbsuu=|JJF[P_x!MR\xb+4O]*mرQQQѣǏG̭Hr֯_ bw+,,3gN֭{ݻ;;;=:??U4,Yŋy桔f͚5k,00ڵk޽9sB5kU|>8p`eeebb"0,,Gݻwxcǎꖚ6+G''gcccccCQTyy1ϧ(J&*BUUU8. ˙Jׯ_SզMUVXXX~~~XXyhh(J\vmBBݻQmfccs֭[Ϛ5ԩS .TU֡|#hG gWs ]]^gΝyG4֬Y۲u.ظ[nNNH*}V3g+ɓ'tqƮɉ'RJ`5kxb:[BB˗yyyêWJT??]vݿ#HQ5jԨI&!k1$EB) BHgz{1k-2r\NA ˰$R4's3aXNNNhh٪+8@$IRTZ RTZ!JbxݺuNڼy-[jgsrw$_\RTr_g*-ڶ*vT<)1cƼy̙C)o߾O{=3*GGǭ[:۔)S޽qϟ}0;;~駟9k^5mݺu޽Z5ТE g˖-D@\\gΜJ\jlllhh>^rssPrGkE"rH\PqYYBԳJfggݻ?y%Օ{zz>~ܧOӧ7i҄8nbbF 6fJJJ;X144\dӦNM:sZ*ʕ+L޶onтvv͝;;D;;Z*?FDDD3G'xgg>oieuR)zzhcx9>Q$U25_adH$3glٲeVfϞ]\\  F-Z4qD|= #Fpqqi߾}~~>zw]rEU֭ӜBM Pgb"6SZ-HΦ-òH$y HD+HҬiii52$p)F}RR-[R 3@̬8%ׄr4/ @spaJF jy8.sz [cu :/s\ .\o߾ׯުx<`llN[zz͛7'L5NNN(33Mxzz޸qvMV,Cjdgbb"6ڵkPPPPPP򢃤~G1!DF8#I08YE4/_߿޽ÙN9߆ Bq+--#uڴ=gǏ ծ]lQ^_ޫ׭pҩ3g>{utt?yXS;p˥:tgΜS_)X\`6m.\`Q\㱱ÇG)ӦMΎ:vOppp@rԩS111:y$r/XjU=^xM ƎxqΝݻwk1 $ɭQ ô%tRxxÇϟ?2Ȱ dYQ,@[4 >K6m߯'P=P%K7nȜ(H񆙙RbjtF hAg6 ?&J׭[^QQ믿jM "o#hѺCa\`O47\wqqqÇZV!񕏏3[fffIIO?O?HNipMТE#G9rUVLnuʕ#G>^z={vΝһw~V?[&bzS\\X,~;-vBD1ppp`G;!AAAAaaaGݼy2ǏV̖_O/\.ףeE]x*Fx񱱱tp7o޹syL/={y]vGPme2L޽ +)):t* :#B3 ww#GwneeջwoPH3.̕۾}޽{:uڴiH$:tЋ/$ɩS.^8|P8_mܸ122~ȕCӦM۴iӦMH! is-K*Pw=qĵk׮]?4n"=PW$xN2XtRPNR*9PG_ 000X~}u~L(ݬ tNkkkUo58ӧOo׮I͛7߹s' bРAQ< NŎE7nǏmmm/]Rh<322ڵkaX||ŋ{եKx##MBmFQT֬YSYYL4iԨQhK;*PO*=}ܹs1I ^d 9̜9qqq5D镕׮][xѣk S$2##cǎ%%%zҊFyQm۶͞=[K[4*Ƿn?~<1OŧՌ b#F())Yx1awx{0 +))  Dg޿۷Q!)))gϞs΂ VXFlzz[TKT$^$|ڄ@ X,f~%J̙b ss#Fx{{OrSӂ Xdf56Ƭ?2f,3SĄcf3542;wn޼r4i!lڴiBBL&D曪_):t'O@_~MA***BCCR)0''U5h?~i!?wܝ;wZٽ޾{gnnٳn]|t=}o*3i+"w8p`ppIXXVUӔZ|>t@;9 )) eu\.իW\ݻw׮]o޼yyƍGDD=zѣj*9~˗/G=o޼۷oGDD?RUk˰uu}ݺu3g\f͜9s\z566ĉgΜٵkm~QݚիW5bwիWJ3k2G4 Pbbb0 ;ydn5j$h#~uðO m|takҥǎ{L&C-^Xi(j 0UUUJ#L&c6lprrB#{jjImb! kѮy 0cSǒY7oƌ֩Sn۶ &Lp]ggK.䔪jw 522ӧ ޿ѣG[lרu?x`޼y[lY|?9H쾋@F  ֯_?|;;;_qJu#F1bDVVVYYYfϑaȐ!e޽A)fF8p@aaa}wfB&)ZUVVA$2x0P0UlSn^ÄGrPFC{zW, J(Q,S !T*P\ˎB|>Cϻ?~|ɒ% PfA?ho8q9s\tGmժEQL|ݗ/_hnnnhh{ѓ'OBݻ׶m[///[[[E vvvLٙf %~5 IDAT ;oee96`={<}!ttt 9qnNtQ*ɌadMLLGhs [[[WWד'O 8TiD>*b(>L 0 00ӧ<=z@ ooÇ^Z´E3sÏ3V`Y [S 0''{xxL4ᆱ 7 Npq<;;-C3hN9}𡎎N۶mUÄGXr%3ׯ5Y;667|X @8g+Ko;))۷"|||Y[ -*v7 "icǎOnn.7mLHHr .4E{|  r|[hK^P0Ęϟ{xx1Dꇎej ҢEp7s3ی IAޜ9s~G6thcN..\011OOO$mEȏS,f ݱg6nڂ8-JK8aX+>ưj߿„ t3gN\\+?kWPlz,\ڵkg߹s3ڵkGQ;P9ݺu7o)>>>͛7G%̛7iԨQ(100pŊ9lkkKg 6lVIhF St##EQ85[(ٳg/_Ԗ-[hZ!=z?XiffF@QRRMY痖"KD{ JMMm۶-ujjj ک 2͛7JJJB>Vu$eHD(ILL8}ZXX뻹[ !4wvZMLLsYRotuu 7_jS\-\qF9Axt?';Mvq׮dgyyymٲMox<3q„ &L404R.)qԬѠZLg]sB,Tkk]ώa\.h^86RT.kvvvLw:")){+-4i7o>x \B[,0+!a2E^Fc"I%1W*D,ݻw]tLLL_厧t:&lٲ~*4iٳLQ^hѢ痔ВlPg`~zjkRԢv\vb773рTA4f8#MMM?߈:wd[7lll߾x(!66񴴴tkkk@iiyɭZ2u--, MLtޱc_41E oġiUUw*++ll˒_ba 6f?$s,͝:?啔s̡ hZGzzL&Cjޯ^56(ׯ߿5A,bϑ$ٳg\.͍uLO).4`ffYRRRPP`ooW_YG?(B,#7Uyx(%XPCkYPS{tt{|?\$5ۅrV[r-\ӧ:ծ횕+hSwFzD>eY[[={V ٓU^}( 77&'' `mm}Ռ SSӥKwEÖy&sVB^bb^r\"$&&)5 ݛMiYf#P(D2i l۶CȁK2lƌ{8q" ݁aabb2n8??ZhE]|9??L#ׯggg#oKQ-}9J/xQue~+OTZYk:TUUeff ^fUU'533suuEqQ:_Uh]]]t{e2T&~BPL֨BߍԈtS+ J?@B3@o!IM$ɂ bbb5AL!BTann;w|}}M̪ O$?d?rt|ՊiӦIڱPYYY$I*+^V&j4X[UMRvE>uj]BV8#3^mA(~2ˢ4cnLvBhiiieeUZZfHI)/ں8'yc1 | H6,#E:tWZ—矆5 M$˳Ғ߈?G.d:4WJAY{@@5knݺEGB PBj2 *9!ޅ5p_|СCccc5k] WG\g#4w|AN&I).>ںwW^E֯;vd1c̝;WU00`&k*B[l?kWc 5 }|U0+ ˰:$''U#>ʲ 7"E& [[OVR3IH$JՁbN")B,COOϾ}ZfԨQεSNꓘWO$2i6݄ XRItڿ)S맦>_~ˆ۷o{9r$kÔǿ}=gff߸qcر'N9sرc[jo߾oݺ]|~ᨨG!C|wl$VAT .\xq~~C]t06PYԛ:\pofZ)BN  @۷ܬ{j;CQPNXT^^NWGR(( dll)BR(Ĕ!,\qYi߾}llS&MӠp688$H%KfΜy@VV֭[wM6ܹ?D7;͝;XZZZYf?={Pӧcvi]]݅ """>"?&N=8@7?a2+eXBԘ=?~ܹs[&ǭϊOP-*)sPRPiŋw֭|Ν#Ḑpzut8C ڻt颧wѣGGGG>aaa]tٳ'`֬Y{9yI4O>fxzzmݺ]=ze СZpqqqF]6,d{v 2F+,,8qbrrÝSSSuuu+ 򵕾]6oz… RU$0S(aW܌9ݡg@`: I"(#$F޳_zEP"e Uk|&S,񀀀'NL4ĉg۷ooܸo>d'8qĠLd.֭AGG BX)}F.<wEy;" LƼ]i >^pW[JV1'bۿ*[ύ۷jׁ$ɲ2fs=鹹ׯV5ZҕE 2ވd.4… = n׮&'e[JL(r8(2h!*666Zrqq/ tE}1_x{؃O4'IJ&'):l2$22~=~СC<...޽ӜzqАv3dff'&&?o78NYY TarժUiiiׯ_311EVb{j3 M8׽|AUAa E]:']8BH@A eTA9U4ȃVVVrǹ\.y$I$~'JYjQQQ}kEO;B.j ezDXw"|-Bd@?y6Y)33':|?-Zi_@UU?Rݻ]\\VZeaaQrrrΞ=s)S4+ ֧O C j'> BN㬛Oe_S:C]Hq8GNLzdhbѱ{?HA9IɪB}%6l]vEIII虢d),,kR$"4z)..>tІ ttt+**X|!Clܸqƌ#FHHHXjիWƏn=zSFkڴi:t=ztqq~אǏhb͊ .]"I2$$_~.]B]JaTt$0 `gn@9 *$!AB’ RRAqk CG1dE"Qzz: F?w4yEQA*QFдiSwwիWѺuk$"#$h 17H4M:ywرc[nzxxDTfAAݝ9"5׏!ZS"@lq=\|) +++5ZGϟ?Hjĉv:w\@@KM[4@<==ǎ?;wN4v-LK襴(5\ :B(*n~(%a:Un@-LD&zbScXp雘$ HS)qp[lbŊÇ#t>k{{(TOwppxL&;}mllJ78FZf۷o!H$F gvaA|`RREQH}`6,,,Gٿ}1bĈ={܏?|P8l0͛7UrwUʊ7oެ_~F Pr-[_.  :եEa:v@,zVrs( ˫2")XQI8B,̯^z= $y<2j IJ:::X@I&%%%-[d^{{gϞgݾ}  ;unݺ'O[Q]jԨQOG&]}%@fHj^1Ql\}vM\YYҽ{w UUr\*TVTR+++KFPL+/.((شi"PS x|>9y|:QUQJYhByfffUUR 2F0јN,䚙c`bc-5oּflSc#Xo~>lذJu&Mڵk^6llsss|ԩSkDQL2fvݻMVmP?lFFU(B͛N8 67ٳg.]NqC:}ժUݺtbccgeeݼysĈ#G\)/dʯa:c CU!)AQPCFQSR(t]]]]ww磭ΆaEzzz{СIq߾}Y(rrr Q|'O:wܽE IDAT{ݻΝ;766vƌ6lXn;teQnjcddTUU5| z-VSZ21 QuTڥ_P3{"ŷRCϟ?=>@I.% 0K ‡**ՃvQPJ ,J4ʯNU&\tttڵkʖ <|cǎoQrYv6>Pm߿2X˜1cX/xk._ҒN;vرcѳ;wW`(0o5{z0l֬Y3fɱЊ&60*}U#+XVV~СΝ;cOtuu-Z}6*]~83K@TT S^^NDI&11_t!#qp  st@Q q>cm@(mQ. ׭[`;;;>n:kkk'''ESЃ!my흚4iriD#, cccsrr?H$8Shi̙-N2ՂR 7>AD],j^v" xN^ީS 'OlժUV `Aq ||R|j)df+`g'NPMOOoZ_9΀vkLIi̭p8v\w  ™f͚>}ӓOLVVVϟHNNF) 1 QQ(?577g3AG2d_FFFN2L)t98s{߫_>Y;6 ǹ!!A #'xUG\.g`8tƍ .tqqի+˗111ϟ?wwwܹIJsSoiy׮]111,vGU|g-gttk:88vKtNNd 3f-eBxڵgϞ5n޾cǎ-((hӦ͕+WA$ÎDD(,ADDD̘1 I֕Dhh]'Θ1N<*ľ}ҧz#R43U8Pc)-P559`8}|U+TWWsr`_]R2Y(3K"Lf Z1 8p֭[O>DmmmE"ѣG7}ff&vZ͋.\0p@҃-66V&X)!Z۷GڷogӅ<A69(pPlP\(9AA7"+ :D2cƌ7o899?~\__uu'ŋ111)))=zPuMMAB~Т{HM?pET HE*ٳG1i$t7"H*(Bկ(RzbVA"Ϟ=C>>>!!!J۱c˗/CCCjjSJ?+QWWWEkׯ/))quu ?X|>LȠ Xreffښ 2B!^??5k ƌ3m4g``0}`P`4 k///DvZZ( 1bX,i߾[DDZIH@aK>cf_8k歽() r9P D< [ @ -$ V)#tkȣ4zB@yb Bj?,O(UqȳgϢykモk׮} W=zI||-[:?o޼w7n܀ 405Kȑ#k׮eO+$ի?˖-S\{__PӧOgΜyOk̙a„ ׮]8qb0(GqsEpET brrܹs @\.߳gI9\.IQ~~~۶mCFhd7nPuBjL_pBTjkkjooaΝ;w!\\\:j*VJ4͛D@ȨsZ9ɰ]qÃׯ%\5\^>*xfbȢ r9Aȧ^M;BtLoڴb:~ Ќ FFF/IZI:b 80ڿO bRIp9Xj).@= r)cXirFiP%#{R)(޽{:u ظq\D ,@端 ʢRSS)jݺҷ3gΤ(ȑ#e˖͚5u׫WZXXwYZZ5999^| 0:w[݌aXK@}(O(O"4?j[tԨ+Wa6f33{zzzn۶mݺu{Xb۶mV 7-`oiӦE%$$ܺuX#lڒвͮ"! R#$QbŸ~0L,|ёקpٲeVVV)>al {( H}X!H$8<r}l%B1wŀB(9rvVNt<ۛH?K炮`yyyC ۷]nnΐ!C Fw8qG^tq㲳ڥKC|ހC@o޼ys޽O4)33>@ӧ'LиqcTȑ#5lQttիWH77|мн (~}Bҡ05OTjS?5SU%#G鷊JC`I >?|@Gٶm[L۶m[5? }So ED"qssErI.]BQ+LWI|T>>8# @\\ܸqzyy\QiiiewaQ\[3le  `,;jШ+X5Ә=bAQƂ(jJ4"Re̼?n7Xvܹsfؙ9S6կ5#IKK {T!-- j]v-__]zuHHHU(((˃Y݂C9|q" U/@ҐĆ̔BoOgu AΝ;2*8J211aTMjU t8.`E $Y!p^(jѣG8 ϟ??w\UU(/ȓ#8JaZ޷SC95۷o~ UVVݻua|W^ 0uëpT* `*{gy>i$O7oYՒ۶)@( Obr.Dr\???z˗[hU#G:tmz`z$\[tǎT*׬YӪU~oݺA˗O>ѣC Zvm߾}iѢT*=ydǎ#T! a> HPPPllݻܹΔ)SLRZZlccdɒL hmBu!$k׮Q)!7o'%Jw#""ykd2~=zO vZ}С'O3p OԺoZZÇGաC}-X-\niBWw;gg˗/[,nٲ%99EVS/!Wo* "@|0IAT*ؓ$\EEE!!!Æ 􈌣7HsC3n7.X-#HfԞ={.[JG|w}G/qpp p¦M>UEQ[[۰0m -=$gmRԥ"2,Rse$EITr%GDnvtw3whfM?^bb]MAAADDğin|>kee/˗/$IX 'MMMCv޽Y_ZuqumFEEoǏ8;;S{\ޢEoٲJKKq CBB\b Cv3Innns"(^z͈#`hD?%Dyy9T=z4uT յkW??oܸ?3l7n\IIѣ;wP5*񉩩ݻw 4hСC5U[ڐt?\ްaC]\\rss:hժ՜9s:u!H~wH*ᘘ$oi6;TXA?VV\ HV8""{m]:.ހ$.:&næ\A7{V^ֱcF.\HLLlӦ͢Eڵkŋ?AAAݺu[v)S c9O<ㄾ⋶m۾ynD?#GJeXX؂ qaLfffaL@QEӧO.**H(E ?SE% ˭GӈG)^fR'B0RVJd:+N_ggaذa7n޽;l9|pC1;(ʰc[ZZ2555e4z,BXXݴi͛d2Y6mVXm͛x֭5y&""!0i=f̘J333mmmQȸp´iӌrF UG3Zرc9FzW/%OOOOJJbƖرUW^~=55U  6Yf3f4Dj'nܸ188xƍX`O2D0c\ۧng2OnjSPPc!W^dBtDDDm$d2@0I88Kp¹s:Td\!/ 7^={X}^唔Zu7.('W姘g|>&Mںuߗ_~yȑ5k\]]={㏭[߱cGTTҊEEEsff7MviPxѓ'Oo**44tС-[o\B*xf͚5 Ý[8ZT[-$RBAuG ;ouHv))Y={Z\L-̓O8ѭ[7rԩP8;pCWXѮ];jG(ԩS Di/___~OnnnTTm?~m6.dX$;vP*}5Ef9HYk F=zߗRR5o?09)) ~xANNLر#A ?SSSCCCWZU3"I& [[ÇGEE 8 }A`};vlRTs~)77w՚;"Eeaa#G.YZZEFF[ΐa`|Ո0ޱ<-T}$P\0j9z||?y'vf?N_K_~3f̀###`k7osM4umj  cX *!Ioacb. ss)..633{mBB€Ǐ߾}M:uo߾_zaVѣߺuk'NX`ɓ۵kgϞ_u۶mPTxXNxȐ!5L<]him7m8nJMUZOǎ2V JRT B8fHHHrr!C&2LTSBP d2Y3|pBAKNN[f͚Qe.+D^%%%ښ&%%1TftҥK&tn[ӣɂǃ+&&& LLL` Qy055uƍ'Ntqq)@W`=Z ,MVk boϟhɄyPKKڒke!= 2D.ܹ *3͛7ɹ}vII @˖-?~T*O}˖-Л;&&&++k„ T+>1<<|Ϟ=9۶mۈ#`;w56d 4:88eż!LaD;vԕQV_ߧGcǎ;vl͏|{5󥸹?qޔ$ IH;+N7::Zs@ׯұqǎfff(<{حK.eee0>f֬YOdd$}|A"""fϞmcc]v믿^wL_D1/7q$`j 0!Idy9NAHjǚ5jʕCZN1`TVl٢vQں'UI&Uxwm>VVӧ}MRnHΏ*  J,{zzRT|+Å c=?S/ܿVV؉L&k֬\ qww0Fz֭suqqqttTջv킡 vuu}s O0f`|O}iU=+XPŕa6?(K}yBȣ "77?HxuJv"Sp(@^F~U[:ʪJ/RYPPǙqJ[8U*ګW/ 7".] "_@xY5\5Ns?k6^rM)!4IKKܹ!Xeee999%r7o* !ݻ322AVرcƌЊlll4ΖdTIڐÇǏ܃Ȋl8nܸׯnARYRRAOÇZS Odiiinn.0paM9zӨ</\V""JvJbwּm}=|<~|g^^^]vJurmll삢(\(Y/ZrII V Jb(IdKkTDQ{K"&5&Tha/H$jQWb賲*OҕƐRhU̸2 r\zS]G7L.2NJ,Zh=zДΥ-''G?i֬Ypppf^xqT\m߾}=<<Tַ`t[:GOLHHH$RR* !!(Ƣz2_|uҫW&7;ׯ XXLNRkNNq8666EEE0\T"b@nPAu?_ڇ# &ca |>'yFW|" APƒ~qdjZURᄕ111-¿/6DM(KKK-YP`t"/1tOߋq/bظ66KKK \~ҥ~ayHHHk @3x zF LMM[jE'֙Fnnn.\2e EN<ټyΝ;޿J&&&2 ZOAew:}=ϟE{NS akETQXzZZNMM555"/ q6ծJO{Sxyy/DTx شifݺut*++w2e #fPDr]'֙ j"3uΜ9cƌy葯/㖯T=H$jP o.$IUUZEP h>Bhjz|\/wrhsV˛$ɒǏ'%%q8DҼyvڙ6- |kr ڶmݻ3g/IԔ#TIȆS_3W{{-[J$#YTTOxxxL2zRZΝ;/]4d1V n20^* չ.dPmuѧO͛75U"$$$PQU |2ҿgZ7=ZCv*FNOfKmH д^#O3 7olٲeDDPPPpiӦ+#j2? 0J4P6o|ƍw<%%%Rizٳ;uPon2pI?rA,ɓ'O81-- 0dr p8w1}Tvwkjwizaq>>eggk8[[[X8rĉ_~[?hQNNαc===?*Z *I?رcASSSPHL&C$66vpv5s^zuСŋ/^ܼy3$̙Ӯ];XAy۷oذa;~o޽322 E*FjZ*T*5Tݻ_]RjZmaaA\.7, A?0 0d/j_jw * >) K=$߿ߣG+kkRI-B8VAPP۷mllT:cǎϛ7OPhzzL&ӧcI)))pe<33A[nx"..߿޽v:e@ttt^^^|||aaaHH]]]ww :tݛ6mڙ3g̙Ð1thc68 BZ$f9s$ӧOoښqQ*jvvܹsO-D>|X$a^2?zBv{AGn5' 3&$ׯ_T*+++B֭[;88jBAT*$>}*J5ooǣZhqp* -?WZJ&ߟ 999&MZ~ԩSG B000ԩS .DDD4oΖVX*kOQFq<..͛666ڵyw}ψ V8A8 j~ W8 T5G`itQʵnh/!GGGwwwwI$$K .3\hSd2j¯}S hhw?蕖xzz r P E@rB(:88jP׮]#b#GuV=jI(^pC}`eeu޽-[t]ʨd]&&& ZүͬY ϟC8qC*޽{:UJ(...!!ߟȕx0,|8A8fK`HUJ]wn֐X@yy9}ݩ Y*LkׯNP\.p\9 ]LLL,,,ӑ[n=0f̘W5pz òe۱cǐۇ̙ӿ^zQHcbb  رc###رc;uݽ{wooojMVV111!IƍƝ;w޹s'::o߾'C OM-^7n ӧϕ+W`?vZC>h_d v) ǎ[א `7VDUob wfhtg={?pիԽPp8Pصk׵k`0 HMM}ڮ];>Ow<:l*ȇ!2{˗Kԩѣ?~ ȦM޿gAWqI&c׮]?ޥK^zL# a*++ &"I\R|γgHD"2LV:ujʔ)/V|~aÆGGG*eΝkjj ^M#G>|xII ߨsOcƌ5khfuFA@?B1)xZٳiٵs"\.yf֭[׏+ԙFX  2d͚5΂IR\n~~ ^۷/СÊ+޽ۡCR:8S7@_(!;:u6mZ``ŋϟׯϟ?իW={vٲe?x My=3%%ӧׯ_yY5 utzh0p{BQT }m۶ <V:8t$6lX˖-aKVVVTT\My<c*)0sssz.K\ 5jHcm_knʕqqq[nСP(x|>V?s挥KNN'|r5B 2i$ŋO>^P@pO9sc;2.:_V*VVV"LV8r|>=G6 58D" P(=jԨ-*+dߺ|wiѢE'Os͔QRRzך[ZZ>{̈SFFʕ+/\PUJAԃ/Tme ?8xzz~cVgBK:r _-:thddd]'\.7((斚nݺKhт I2;;{nnn ;vh̙3ëW GNϸ hnՕp/RW5߼yסCs Zhpvv 4޽{ɓANNbHt ssnݺRSSJeVj"أG:tҥj !w~$I77sE^,ҏ\jwrr IծP(^V@-s݋ʘc$PgϞ]bEǎgϞ4m4ĘO>}SN?> 1777n\LL 5smm۶!cwr$J%U򀀀ŋ{yyxzz^|3//T4j`HޣJbV]>8rrr_~aD"oI9rdxxx^6lcǎ+V޽["YpZ^^T*lYYY?cMlllzF;9t=zgj5cǎ`׽;9G<]#WTjWTxjL]Z7im4ܝT:ݠӦM\\\)e/^\TTDO駟¥˗àPYx%Rsz?zi~A'!((ŋ?|pp\>}xxxSI9p@}P~aZZ@ GdggCߕ+WrssE" ޱcKQo#kRd2Yf] @LLLhhhUTEmmm5p@'''PPMOC*jo8Lׇ\$MȇqZkUW*dS[ZP>ϰOUujV333ƟOwӣ:4aJ|WX[[bRX,H$bJbU2l| (uLJxAat3PXPPuSfffDDT*JP!Ci2 !49t/a.ZӲ>\\\ztًj* J%߁Çn=W!ȁH$iٲw˖-mllQx,,uưa:uTR0p8ż B b1RTTi,--Z606.""bŊTf@jjj׮] tU؞rVZ;vp\$3JVT \Kƍ;vX۶m.Xm\7hTjѥ7ig/z:;NJé'KG{3rlAuWn4/j~anqX& IDATf D*DJ*((HHHJsP~n޽;l >}zϞ=5wiٓ7n ޸q#@,/XӧǏ>7n!W|B8}]йzSSSaÚ5k6c Y !14(_(!;7DNR蛴vFԚL4CdIIӧO9s۶m,,,05>> iiiw܁.2Jo}Ç2QQQ 5f] i###7Lٳm_0z9rw=/22rݺug\k*hLjeӹb1"u?8ŵlrܹٳѣg-(z`afaa͛? 0(Lfff\\G^vm^^tj2dH-))i֬jmmrٳݻW[\HNNO͚5KNNISfW=k֬@cykXP_x𖮮TĉmF+񸸸[ntܹJݻ"hȐ!ӧOҾ,,ZQ*p}͈ccee/j5ݼkr|cyj kZ|~QQ\VVW^oa]0n>Sm_Ǐ H,T"|0PD_#I2''Ç=zaDAPP۷mll4mEs)(( yϫB8gϞ_~ǧ'RZVFEQPX5p?yaè'OVOrpp 6aaaÇ $IAmbw 4T*Lw4f,̞={…T݀21AŊT*U(O!C-ZtN˵YTF-|LjUI + R- JB拢GgϞ}9%K:f??W^͘10vXL}z||<| deeM0Jl\5ٲe˗/^XsXXX,(Ad2sss kB( }`eEMLLRT*dRr򰳳6m_ Rb8##~~[&Mtرf͚mܸqҤI;w666+VpqqYbŁ6o /֬Y ʚ>}w}׹s%K;vl֬Y~!Y׮]ku}vSN7oԵ,Ο?_r%,,})))TCvv]DGG-Y|`WW׷oߞ;w+]_o'( KG{DQVVfcc#\.,,@-( LLL,,,ʠRE 8|իW߾}{ͺ S{ԩD"HR:O xݝ;w,YңG+ٳVVV...999:t~:B}T|QkHV۷Ҳ(wlL=CFԢE z*JAj]0/_w9r߼yӺukk"VƏĉ3g :Ɒ4>ȀǩF-`r(τ8̽Vr9Ѭ',Ydԩ-=fArڵ]vA/`W;vYRR_{^®%ta #A>G8(բgːcZ0G,W) 666Px ,&ZAeddh=}C|M7jP ~'_stZ&{k ڟ=U ø\.oC #BV'''wEk_ݻwv @eʡG!%%e޼y;w 8}?LSz2b{{{ o2, P=m`_,K^x'W/|~}X2;w!9;;gggׅ,,,uN7o T* jq.)+T8SvoHRRR^^/É0 Z[hSRyH*{ࢃsnnn|||II ɩsGzwܹwI&*cFZr'&&999s{.>> #j9QfG W"M6eˠ% $$}D"8o߾]haVVVfff6mrtt}W]tѣǔ)S.]T۷ 2dD"3gNll… ofšD \x~+Wܻw/lX>p44雌Gz?c&J+.J_΄40@v`xGѿjM|Teai|'SA#sssPUVk׮]lY-uڶjՊ(x޽t>MApkǎ㏲2;3uTؾ~@{=jٺukaa /**BQFdΝ/]+ 0 K/M.=kF?hMrHR7ECkuHˮA_lFbaa! q 0 r:AMdZciD=##Q `´B;?APJo^hWHHH6mwLL̳g<== 36SՈ=UE)`++ZMjQ(޽stt`*0rHqppP(EjxRo=Z kc际@"8@~AR\uppp֭---k#P*R\vٳg===_za/QGK#frH Cֺ_daa9Ly+zplmmmmma& e޻wݻ@P_~T7tJca8p@}RTG_caiDkB~uu>?~ٳg))) ,7nw}gggo>, ,, zY"N3rl~̰=wra4JOO/--Y@J[RyΝ;ϝ;ĉ_|6d`l,4_E ^Z+}N6 Kޤ8`ի+..ŨQ8 CmT ZJ/' qaBpE/==~j`/Z.,#/… ;uԦM/_gժU@[ϵ'U/hg/oרo׿VT*Fr֬YLm׮LXxqzH$XVVVCIpONNNKK+))P FE > K6n8W# K#C ή4RtY |>ݝ_g:㫴Çᇚ+8߿Eѱcǚ)tyP(@yvA@$I5Nd{Œ7u41a4&>c5k*XK#EOW]U%z:r҄$ɽ{N<^/ճb$%%!2>l<ǭ/F{b)pAPgtAH$I@'1t  /GO mT  fd': je)Q{9 !PK0p8D""11Q.D"K5q<%%W5/%T#?GܞWF ^"B쐟mamH@@$N8A*H6w3VQ?M*Xꥺ' Ѭ h$~+GU1C,ӳjJV.k`-TV#M{_[q&cvIzg)Q 5@Dy9'~)KPV#H@ǁ\EJm%Y(Al,,, ;?9::A"Y6#i~,K k|Y+&ztr[8t2Ν;CJվ}Cry\@jGzutRFھ{ݻw߻w&㔫@$\۷̌[7|G I jREPP^^n,ŗFhJ5_1 c׳/eq@[ЭP@H 'O*n?>|&FKKKRRRIq%v=8QqjI( B>w1@g HV,@" 8ƺ4PK#[n-1p8zI&|4ZPW_ S1듒nܸnjj>c-Zt! yp?}OM$At uj8 5T ! D@čm|aOE1$ (D+9|~gjj XO4{j j¸&QAy/_LMMMKKrǏ0t)+++kkk[[[[[[h:"i ͫ'IPrZ_u„ \.h7n|yڶm͛g߿ʕ:u޽ P՛6m_zu;p@nnǪU`{nn%K޽د_+W\pAO3gDGGϚ5kݺuyyy3gΜ}*'L0ydJ"IR^^yZe*$LAGC0f&؏$T$N$A5N$Y\>L?V5v F-| P(RpO"pE{{{WWWWWWZMw0`@vvv^^ޣGR00 HZ ( !_zVvT*Jk@BBSzzkצNzf;v8~8˃>|۲?;H<~?7:uw1c\zz֬Y>}J,6]l۷o ߿lŊIIoV^3l0P7v֭[^sѢE#Gj5I* Ё 1 i/sK ߢ Ԁ NJ':`]caiP4&NҾ}{KKK Hܿ<%%eǎ澾G>}~J߷bP0B$4w[#_~3gl҂ᚚ"b)~~~Wrww+ӧp#GٳwpoD2w\$Ϟ=?u--- *x;vtiРrx{{Äg/_'kBj;" HB ( ( P $@! r0.Wicai@|[[XX78aaarVP5%dP wk+W긹$I:;;HP0 lv!G ۹\GzzzNNm۶HE R P\OKK۸q˗/჎aVb&\ E݌"IR-*AA$ UXXԚpEO,ױ@ v-`/&ZSkj ޿/JCBB\nII%jԾ5b(aRUVqqqaaa? W^Dڵ:SZԥ T(.PN okk3ӧ}|>?11!OSWEYfܸqC&nݚpƈ 3݄yJO@MM$}6FQN7=&< X_(^ZEݻw K5 ] Ri^^߰q ֬YCmkp0 h`bb|WOnٲeܹVVV(R]]]\. >|7RRR.//CEpūVڲeѣ _\xWv ׄ˩0!Wv'̿xhk{vA$ `1pЙk?eʔh3ƗAD" rssazҾ.Պ(JB'|K,H@"B8AqBP?znvBsssjF}4j>Bw,, hѵں)HzkZ ucT*&&&t! $===a>C|DBo߼ys~~ϵk~'OB=LW= ٹ>OMHb(@P"$E $ H! $8q0Hf+(,,, ;9U WKV5r&„ {{T@+3,d8|>?66ݝ z) v횝]llo}I sRbpP*b`(DH@$ q1NwҨo׿VFP(FR/@WejZּ)++QٳgϞ(@3"EjyJJU sC(~Dz+z,, jLϊV5GGMHH2dׅ%TQ)/>cǎ5+ K3"?4wvvvF|ڵku Q'caaPr՟Z0Ɂji* g}S\\+ fҵcZ&{kPrDaaa3dȐIvvv-K!qi'X @= 䲲pJJ \cQ{j j^MQ `B^)))PX?4x y2^DaIA5IDATaaaaaaai,|BQw,,,,,,,,,z`F; $I}V.Ԫ;j1Z0+r~4.HqaAwTTG]\:i DJ@4 =sPLHBAxHPE  R%"XD$4!T)c~>Kfݽsgޔ;oKr4s?w( %ѫb:::͛7677L~eEE:t~~9??oll|KKKsqq YZZoڴ?f(++?Cyyyoo *Q,_---sLɓǏZZZ455צ6!!!uuuFB@@{] li5JA ۷o'lŷ!EEEr+++4YYYKb񸼼<} 5$$ $)00pݺu>|>{oi%&&JHHܻwϟ\~eeܹ8}9. bv28ŋ}zz:227$%%yxx455)((pssdddv ۛjD"y{{={vjjjW\#uο?߽{7>>̬jvjIשּׂ/\PUU%jjjKqSD%yyyB'\]]~pp03.gۻڢ6bKi8A/^0 ###...mmmP(tWWW{"PgA^^~ff}WiiiP-_]]aooqww755dE{ׯ_kaa8<<jeeemmAдk׮{zz{AꜜO"v oooA???ss&&&:t)JrreBB-ʝaBV__fnçOݹs?~ޞ={fll ?߸qcǎR(;w8;;>|ՓYTT윝 ׯonn?22Zna^u92227 (0XO//lnn,6ZchjjJHH88#**z ebb;?snn۷a؆v(A^^˗ݻ!XBCCRSSϝ;ڊn쁁555ŧN/66OHH  x~nmmݺu+C=srrRRR|||uuuYGGGkkkKJJ"##}}}---sss]\\ZZZВY,i0)to|BNFKKKý333_zwwwNNN˰JKK E&֠a ل%uP\\ 766q:::˾>55恁~~j2\YYZUU())YEǎ8p@]]YYYjjj|||D"Y3{yyYXXEEEP_UUUp3FEEiiiYXXLMM577EDD455ᘕC`0Bsss|_ nnn𪎎ч|||8GXXbjjᴴ޿hoodeemllPM߿ SEEEα285驨pssojddbf RRRŢ444¯CCC 400011DJ  1DKf򳀦8ֽ' %""m۶r҉' G-Gay>xù ¢R8 K7j涵}𡓓SAAO?D>n޼ n۶m|||>>> ɓ'KsNmm퐐Ǐ1.'''j 3d$D"QVVvuu666JJJm!!!PMtwwEFF¯QaccҒW00|˖-+\"999(bO^~>6`8PUUO5>>azvttTTTX7֦2t3g8;;=z]<4EKك@ yHGG2!3V6 X_ kݛ!gܰasQwn pArӦM4'vh8o!,*`^(8]] {uփ&&&jA9r@144x"ݹs2 K$5-@ 8qa D"F$p<G}ؓ'OONNڵ4%$$/]Ϊn JLL쬚ZMM # &&Fog'O&%%!BDUUU틴*r6CcX999..N;eee4 3#o޼'W={^RRiknn))Jkk+b&qyy2+fW,i2)R SRkB4$..N׭[',,L"N%ԉ/ PM *ef ,0*((lݺܹsk222۷oqNNN;v噘`0==W- щ;={}MQQL&HCCC2/`9< #Ҙaee7oHQTTTМ풓SUU͝}֭[Y###?ᑌ@uuGOO/..իLѣG0B+'8Jff]FFFFFGG'##9okkP(L&kiiIII/FyyyA`ϟ?XYYUVVp|8`!а`jj*''zWn+ȼLfosrrn߾ +B<E~ 񇕕zŋTRR{9nə<~8555'''<<*0o!,*X;;(j#""&&&Μ9_khhLNNJHHWQ T[[[pp0 c\wvv=}  566fhypdkkkSSӈ6H"##SSSx|ZZڼnٳG@@:DDDۓ͛7>}E9'Otpp 366vss*// w`^GG^PPpfff޽ǎ[<78G ***ڴiSBB}SN:tH@@7##EDD +((888ԁ H))) WWW{wwdSkivuu ?s挭-CV6 f5řggg,w"899g^'---###JJJsUOOOiiwEEMX -`Æ *I"LLL( ]_< 3{[ 333$&"255EPxyy~u 30!$ """pI"1 <JCBBH$҅ P B_p?pe022ESYèi .9994D?LN8}`ngZ æY;11!//zqa^^^[޽+--=>>>!eeeN9,+nCNOOT Y1,KUUZx0cX"͂";q e|;,jSyQSH$%%O G?s1۷999ZZZ===999<~}ڜYpL@[3YuUSSMHH{UgVW lL$oJJJ߿>v옅ŊaUUYY:kk2֡o\c5Xc5XcbxB8q?IENDB`workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Volume_View/000077500000000000000000000000001255417355300237655ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Volume_View/Thumbs.db000077500000000000000000000140001255417355300255340ustar00rootroot00000000000000ࡱ>  Root EntryWV256_8feb0b64bbccac36*  !"#$%&'()*+,-./0123456789:;<=>j>.(~"+JFIFC  !"$"$C'" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?SPhe8vRџ+ 8993s{癬,T7NsF54˨\)oʙGݯa.%ѵdcpyA@6aqX4$wDY s!ˌk7z/uhњ7N $ ts\mCYĶoq.zߒzIn]#49k^5𭎣F{FVmsjLA<z|}x_HyGL!?{ׅ?3vhf[XaK))ѥD|PlFC&2:la@#).m.4低E"|t7=98VtFNq!zuǽZaD:!UE=zԺ-LȂ/]j~{,da xb|d|FY~S"m"avdV̏H} GURrXI6Sd ,GMXO8E;67ɭ2̑|bI#p3ҦohN#2A+qc=*XYgK2v݂K 0V/5cgFL^1'v "BO9c@_H#Dq FN0c+]è{ŵ!bN3xMG[Yv?h.HN1ߧ=K뷓5A)˿ODE,۰\ 7Jϱuk-`&`n&~`nN #9wu/Dz(xHq;@'(qp2u~s2d9`HRsNOlp ?ӻ' |[,w{ۋ)OTXῇcK%}R.I9GoWfb @9(0n<]p{i`"8$`v=NztS.Sbᣏt 8cxJ(xٚun-+Dh 9~Ś+[ y07e(~/֖̬h\ ӧSƝk56[ 0!rv| c ~з4/O_{W/q⋕i!]>u/&26 FNz`u=;g(>&\eEUozRNT89qv8H#gYxPlo[!y*0܎9hQ@xC)w!0t>ՙɋ6wM[۟lQEf?ʑ۝c<`i }8A,oφF,-cOxY^+y@%0HA "=~M˻et l!Ԙaina?f;{ :]Y @ <"f3<@ЀPZ@ {i + (pA"HbցM `/(ap @38 .: #A8Q!%Hҁ K 9C? EAB:h TCeP%T BP?tƠ'a f0f~p(ë8Uq _o<(@(wT *6 P%*T= ՍQX4@>04].CC7;7C W 18`ؘH ǔ`1.mcvXl6 [=mcI39q\:.ww7}xK>/oƗkg) A@$ل#6 a(K#:CIMRb=D"iIKIR.tt4DH#1 vQr; BѥR)%c)['Q\Ij@4AZG%B:GD q GfLL̠̤,UB6P6UHVNNWS''wX0EբS-#. KӣiIB Z/mB^NZ>\~|9y Eץ)S;O ,xm  )*+(6(VPTJVڥԬHlT9Kr""EEN-U9ң2*RݯzIu\檖Gژ:UY]GsSza54iZ Z{:&յhӮӾCa$yUYwTOQWPZJրilp61L4,7a c퍅Uƃ&dIIɐ)tiK3mh]ff_mS̏?lfВkYnyˊbeѪꕵuA6T%6[m:lيmmb*4fycfG[tS88&;:.[a'M'SęEÅRU˕ZeJbgt3w5wwp_( ,|^3Ves5 _;~d2'b%%< 4@vGAzA~Y]|`u!Ԑ!!BBw> Q!4\y=J9J\lﲑ;Y~uVJ< Tq&qq\w> +oo,)8YSBq(߉?X8.p ^%$Jz|4y:%"!*& ;WZ_d$IV;޻zB'NҖӐ`ӓ]Psfy懬kddfo~ZZڎu6ZZ_cƼ#޹67%ouoDliS||qVǭG/wն۾ ~.]'laNN;\v+-)޽dwƞ=o{ĺ>⾌}RҖw\XvܭBb[]R=TxӏVzW6UVrXHV.n|槿+(B3y pHYs   IDATx]wXG RDiv)TlX#Xbر&{/!X@{(*jl(EQ1uxgygn;o,''TCOOPZZ Ѐ4 h@8/_} JЀ4 h@|BHi@Ѐ4 Vegg+JJJꝞ/_D...ǏQr!77Wk4& :O \U/^|I]}Rcĉ\A۷OujٚXmM׀M6r8-IdYYټy蔊3g<}0!0`H$" h@\rʒwwI&}ir @6YRRF!0 $v -e2!at'z̀(1  U*X9RN[hѢbUUUϟ?,//߹sʕ+-,,]s۷O>]GGIN(Y4!}ر{xiZm?A8G,S(;tO?1e2ٱc ݅A_|I-+IO>|r&MMfffH$OޱcG>}<<@$rq 1  E!8bKH4dVVV/k`~zfr<((hʔ)LhHm۶ [|4KJJJ H|I\\܌6MLLƍ6Zl^rӧ666M4ԩ}||X QYK*@kH[P(T:91 KOO@LEQ5 *TѦ4^J%Y`EE\.722Hx{RellL" Yyf) ;w.RQQqYUa-H-ҬfY0rMz4a~ΤMIRb:3Q13Inn.ҥK$IcݻÆ ?yP(l߾MF%O )%c@AyLeQ~kӿAܸqmڴСvرo>]]!C̜9Fj}/_ؽ{#jZa||g OL޸y󦯯YUUaBH$A"Ν;\D"צ:=IAq<'/ԩS“'Oǰ8F`J J-BCCO.H-I|Xrj H{۷o]\\FiddV)-22ru|@(m𫻻۫dX~_-يq\E۷OۇǏoܼޢ@ xr*=i)IxVjxbAAG!A$Ű#$IbFDDDČ3zV}#0`Y={agΜAaFhij$uS3J<45ʯqN*90cHqr1R+իWxӧOwttdXtfF ?w!#=ļEסR xSSS]`aaQ^~-H*vooX~L:(d۷hѢiӦ!I\. ttt8p 7or@II+W,YdẺܻwoHHoDDDJJJEErԩ_۹v21!,--˳D2LV%drR&5jTXXXZZ,t-0`@BBz_ 4￧ٳ`߽͛{wչ;v\p&t"ߢ !!!m۶e᢫2r"{{oHpPfTbiQQQWtqpvqo1@ncg/G4>SC\\8;7wmbbbR)+?}gp09c"J;u_uƍs纸ߔEYYY{쩪tզ$IN>}g5ONRIX5 O>>RЖaŠW^uޝ yU\.J!!!r??Ċ$R[4jpzV|JaNTph0[榚 ZOffeS_)A$EQ$E2& bńB!+~g==Kڵ Sbsuu=|JJF[P_x!MR\xb+4O]*mرQQQѣǏG̭Hr֯_ bw+,,3gN֭{ݻ;;;=:??U4,Yŋy桔f͚5k,00ڵk޽9sB5kU|>8p`eeebb"0,,Gݻwxcǎꖚ6+G''gcccccCQTyy1ϧ(J&*BUUU8. ˙Jׯ_SզMUVXXX~~~XXyhh(J\vmBBݻQmfccs֭[Ϛ5ԩS .TU֡|#hG gWs ]]^gΝyG4֬Y۲u.ظ[nNNH*}V3g+ɓ'tqƮɉ'RJ`5kxb:[BB˗yyyêWJT??]vݿ#HQ5jԨI&!k1$EB) BHgz{1k-2r\NA ˰$R4's3aXNNNhh٪+8@$IRTZ RTZ!JbxݺuNڼy-[jgsrw$_\RTr_g*-ڶ*vT<)1cƼy̙C)o߾O{=3*GGǭ[:۔)S޽qϟ}0;;~駟9k^5mݺu޽Z5ТE g˖-D@\\gΜJ\jlllhh>^rssPrGkE"rH\PqYYBԳJfggݻ?y%Օ{zz>~ܧOӧ7i҄8nbbF 6fJJJ;X144\dӦNM:sZ*ʕ+L޶onтvv͝;;D;;Z*?FDDD3G'xgg>oieuR)zzhcx9>Q$U25_adH$3glٲeVfϞ]\\  F-Z4qD|= #Fpqqi߾}~~>zw]rEU֭ӜBM Pgb"6SZ-HΦ-òH$y HD+HҬiii52$p)F}RR-[R 3@̬8%ׄr4/ @spaJF jy8.sz [cu :/s\ .\o߾ׯުx<`llN[zz͛7'L5NNN(33Mxzz޸qvMV,Cjdgbb"6ڵkPPPPPP򢃤~G1!DF8#I08YE4/_߿޽ÙN9߆ Bq+--#uڴ=gǏ ծ]lQ^_ޫ׭pҩ3g>{utt?yXS;p˥:tgΜS_)X\`6m.\`Q\㱱ÇG)ӦMΎ:vOppp@rԩS111:y$r/XjU=^xM ƎxqΝݻwk1 $ɭQ ô%tRxxÇϟ?2Ȱ dYQ,@[4 >K6m߯'P=P%K7nȜ(H񆙙RbjtF hAg6 ?&J׭[^QQ믿jM "o#hѺCa\`O47\wqqqÇZV!񕏏3[fffIIO?O?HNipMТE#G9rUVLnuʕ#G>^z={vΝһw~V?[&bzS\\X,~;-vBD1ppp`G;!AAAAaaaGݼy2ǏV̖_O/\.ףeE]x*Fx񱱱tp7o޹syL/={y]vGPme2L޽ +)):t* :#B3 ww#GwneeջwoPH3.̕۾}޽{:uڴiH$:tЋ/$ɩS.^8|P8_mܸ122~ȕCӦM۴iӦMH! is-K*Pw=qĵk׮]?4n"=PW$xN2XtRPNR*9PG_ 000X~}u~L(ݬ tNkkkUo58ӧOo׮I͛7߹s' bРAQ< NŎE7nǏmmm/]Rh<322ڵkaX||ŋ{եKx##MBmFQT֬YSYYL4iԨQhK;*PO*=}ܹs1I ^d 9̜9qqq5D镕׮][xѣk S$2##cǎ%%%zҊFyQm۶͞=[K[4*Ƿn?~<1OŧՌ b#F())Yx1awx{0 +))  Dg޿۷Q!)))gϞs΂ VXFlzz[TKT$^$|ڄ@ X,f~%J̙b ss#Fx{{OrSӂ Xdf56Ƭ?2f,3SĄcf3542;wn޼r4i!lڴiBBL&D曪_):t'O@_~MA***BCCR)0''U5h?~i!?wܝ;wZٽ޾{gnnٳn]|t=}o*3i+"w8p`ppIXXVUӔZ|>t@;9 )) eu\.իW\ݻw׮]o޼yyƍGDD=zѣj*9~˗/G=o޼۷oGDD?RUk˰uu}ݺu3g\f͜9s\z566ĉgΜٵkm~QݚիW5bwիWJ3k2G4 Pbbb0 ;ydn5j$h#~uðO m|takҥǎ{L&C-^Xi(j 0UUUJ#L&c6lprrB#{jjImb! kѮy 0cSǒY7oƌ֩Sn۶ &Lp]ggK.䔪jw 522ӧ ޿ѣG[lרu?x`޼y[lY|?9H쾋@F  ֯_?|;;;_qJu#F1bDVVVYYYfϑaȐ!e޽A)fF8p@aaa}wfB&)ZUVVA$2x0P0UlSn^ÄGrPFC{zW, J(Q,S !T*P\ˎB|>Cϻ?~|ɒ% PfA?ho8q9s\tGmժEQL|ݗ/_hnnnhh{ѓ'OBݻ׶m[///[[[E vvvLٙf %~5 IDAT ;oee96`={<}!ttt 9qnNtQ*ɌadMLLGhs [[[WWד'O 8TiD>*b(>L 0 00ӧ<=z@ ooÇ^Z´E3sÏ3V`Y [S 0''{xxL4ᆱ 7 Npq<;;-C3hN9}𡎎N۶mUÄGXr%3ׯ5Y;667|X @8g+Ko;))۷"|||Y[ -*v7 "icǎOnn.7mLHHr .4E{|  r|[hK^P0Ęϟ{xx1Dꇎej ҢEp7s3ی IAޜ9s~G6thcN..\011OOO$mEȏS,f ݱg6nڂ8-JK8aX+>ưj߿„ t3gN\\+?kWPlz,\ڵkg߹s3ڵkGQ;P9ݺu7o)>>>͛7G%̛7iԨQ(100pŊ9lkkKg 6lVIhF St##EQ85[(ٳg/_Ԗ-[hZ!=z?XiffF@QRRMY痖"KD{ JMMm۶-ujjj ک 2͛7JJJB>Vu$eHD(ILL8}ZXX뻹[ !4wvZMLLsYRotuu 7_jS\-\qF9Axt?';Mvq׮dgyyymٲMox<3q„ &L404R.)qԬѠZLg]sB,Tkk]ώa\.h^86RT.kvvvLw:")){+-4i7o>x \B[,0+!a2E^Fc"I%1W*D,ݻw]tLLL_厧t:&lٲ~*4iٳLQ^hѢ痔ВlPg`~zjkRԢv\vb773рTA4f8#MMM?߈:wd[7lll߾x(!66񴴴tkkk@iiyɭZ2u--, MLtޱc_41E oġiUUw*++ll˒_ba 6f?$s,͝:?啔s̡ hZGzzL&Cjޯ^56(ׯ߿5A,bϑ$ٳg\.͍uLO).4`ffYRRRPP`ooW_YG?(B,#7Uyx(%XPCkYPS{tt{|?\$5ۅrV[r-\ӧ:ծ횕+hSwFzD>eY[[={V ٓU^}( 77&'' `mm}Ռ SSӥKwEÖy&sVB^bb^r\"$&&)5 ݛMiYf#P(D2i l۶CȁK2lƌ{8q" ݁aabb2n8??ZhE]|9??L#ׯggg#oKQ-}9J/xQue~+OTZYk:TUUeff ^fUU'533suuEqQ:_Uh]]]t{e2T&~BPL֨BߍԈtS+ J?@B3@o!IM$ɂ bbb5AL!BTann;w|}}M̪ O$?d?rt|ՊiӦIڱPYYY$I*+^V&j4X[UMRvE>uj]BV8#3^mA(~2ˢ4cnLvBhiiieeUZZfHI)/ں8'yc1 | H6,#E:tWZ—矆5 M$˳Ғ߈?G.d:4WJAY{@@5knݺEGB PBj2 *9!ޅ5p_|СCccc5k] WG\g#4w|AN&I).>ںwW^E֯;vd1c̝;WU00`&k*B[l?kWc 5 }|U0+ ˰:$''U#>ʲ 7"E& [[OVR3IH$JՁbN")B,COOϾ}ZfԨQεSNꓘWO$2i6݄ XRItڿ)S맦>_~ˆ۷o{9r$kÔǿ}=gff߸qcر'N9sرc[jo߾oݺ]|~ᨨG!C|wl$VAT .\xq~~C]t06PYԛ:\pofZ)BN  @۷ܬ{j;CQPNXT^^NWGR(( dll)BR(Ĕ!,\qYi߾}llS&MӠp688$H%KfΜy@VV֭[wM6ܹ?D7;͝;XZZZYf?={Pӧcvi]]݅ """>"?&N=8@7?a2+eXBԘ=?~ܹs[&ǭϊOP-*)sPRPiŋw֭|Ν#Ḑpzut8C ڻt颧wѣGGGG>aaa]tٳ'`֬Y{9yI4O>fxzzmݺ]=ze СZpqqqF]6,d{v 2F+,,8qbrrÝSSSuuu+ 򵕾]6oz… RU$0S(aW܌9ݡg@`: I"(#$F޳_zEP"e Uk|&S,񀀀'NL4ĉg۷ooܸo>d'8qĠLd.֭AGG BX)}F.<wEy;" LƼ]i >^pW[JV1'bۿ*[ύ۷jׁ$ɲ2fs=鹹ׯV5ZҕE 2ވd.4… = n׮&'e[JL(r8(2h!*666Zrqq/ tE}1_x{؃O4'IJ&'):l2$22~=~СC<...޽ӜzqАv3dff'&&?o78NYY TarժUiiiׯ_311EVb{j3 M8׽|AUAa E]:']8BH@A eTA9U4ȃVVVrǹ\.y$I$~'JYjQQQ}kEO;B.j ezDXw"|-Bd@?y6Y)33':|?-Zi_@UU?Rݻ]\\VZeaaQrrrΞ=s)S4+ ֧O C j'> BN㬛Oe_S:C]Hq8GNLzdhbѱ{?HA9IɪB}%6l]vEIII虢d),,kR$"4z)..>tІ ttt+**X|!Clܸqƌ#FHHHXjիWƏn=zSFkڴi:t=ztqq~אǏhb͊ .]"I2$$_~.]B]JaTt$0 `gn@9 *$!AB’ RRAqk CG1dE"Qzz: F?w4yEQA*QFдiSwwիWѺuk$"#$h 17H4M:ywرc[nzxxDTfAAݝ9"5׏!ZS"@lq=\|) +++5ZGϟ?Hjĉv:w\@@KM[4@<==ǎ?;wN4v-LK襴(5\ :B(*n~(%a:Un@-LD&zbScXp雘$ HS)qp[lbŊÇ#t>k{{(TOwppxL&;}mllJ78FZf۷o!H$F gvaA|`RREQH}`6,,,Gٿ}1bĈ={܏?|P8l0͛7UrwUʊ7oެ_~F Pr-[_.  :եEa:v@,zVrs( ˫2")XQI8B,̯^z= $y<2j IJ:::X@I&%%%-[d^{{gϞgݾ}  ;unݺ'O[Q]jԨQOG&]}%@fHj^1Ql\}vM\YYҽ{w UUr\*TVTR+++KFPL+/.((شi"PS x|>9y|:QUQJYhByfffUUR 2F0јN,䚙c`bc-5oּflSc#Xo~>lذJu&Mڵk^6llsss|ԩSkDQL2fvݻMVmP?lFFU(B͛N8 67ٳg.]NqC:}ժUݺtbccgeeݼysĈ#G\)/dʯa:c CU!)AQPCFQSR(t]]]]ww磭ΆaEzzz{СIq߾}Y(rrr Q|'O:wܽE IDAT{ݻΝ;766vƌ6lXn;teQnjcddTUU5| z-VSZ21 QuTڥ_P3{"ŷRCϟ?=>@I.% 0K ‡**ՃvQPJ ,J4ʯNU&\tttڵkʖ <|cǎoQrYv6>Pm߿2X˜1cX/xk._ҒN;vرcѳ;wW`(0o5{z0l֬Y3fɱЊ&60*}U#+XVV~СΝ;cOtuu-Z}6*]~83K@TT S^^NDI&11_t!#qp  st@Q q>cm@(mQ. ׭[`;;;>n:kkk'''ESЃ!my흚4iriD#, cccsrr?H$8Shi̙-N2ՂR 7>AD],j^v" xN^ީS 'OlժUV `Aq ||R|j)df+`g'NPMOOoZ_9΀vkLIi̭p8v\w  ™f͚>}ӓOLVVVϟHNNF) 1 QQ(?577g3AG2d_FFFN2L)t98s{߫_>Y;6 ǹ!!A #'xUG\.g`8tƍ .tqqի+˗111ϟ?wwwܹIJsSoiy׮]111,vGU|g-gttk:88vKtNNd 3f-eBxڵgϞ5n޾cǎ-((hӦ͕+WA$ÎDD(,ADDD̘1 I֕Dhh]'Θ1N<*ľ}ҧz#R43U8Pc)-P559`8}|U+TWWsr`_]R2Y(3K"Lf Z1 8p֭[O>DmmmE"ѣG7}ff&vZ͋.\0p@҃-66V&X)!Z۷GڷogӅ<A69(pPlP\(9AA7"+ :D2cƌ7o899?~\__uu'ŋ111)))=zPuMMAB~Т{HM?pET HE*ٳG1i$t7"H*(Bկ(RzbVA"Ϟ=C>>>!!!J۱c˗/CCCjjSJ?+QWWWEkׯ/))quu ?X|>LȠ Xreffښ 2B!^??5k ƌ3m4g``0}`P`4 k///DvZZ( 1bX,i߾[DDZIH@aK>cf_8k歽() r9P D< [ @ -$ V)#tkȣ4zB@yb Bj?,O(UqȳgϢykモk׮} W=zI||-[:?o޼w7n܀ 405Kȑ#k׮eO+$ի?˖-S\{__PӧOgΜyOk̙a„ ׮]8qb0(GqsEpET brrܹs @\.߳gI9\.IQ~~~۶mCFhd7nPuBjL_pBTjkkjooaΝ;w!\\\:j*VJ4͛D@ȨsZ9ɰ]qÃׯ%\5\^>*xfbȢ r9Aȧ^M;BtLoڴb:~ Ќ FFF/IZI:b 80ڿO bRIp9Xj).@= r)cXirFiP%#{R)(޽{:u ظq\D ,@端 ʢRSS)jݺҷ3gΤ(ȑ#e˖͚5u׫WZXXwYZZ5999^| 0:w[݌aXK@}(O(O"4?j[tԨ+Wa6f33{zzzn۶mݺu{Xb۶mV 7-`oiӦE%$$ܺuX#lڒвͮ"! R#$QbŸ~0L,|ёקpٲeVVV)>al {( H}X!H$8<r}l%B1wŀB(9rvVNt<ۛH?K炮`yyyC ۷]nnΐ!C Fw8qG^tq㲳ڥKC|ހC@o޼ys޽O4)33>@ӧ'LиqcTȑ#5lQttիWH77|мн (~}Bҡ05OTjS?5SU%#G鷊JC`I >?|@Gٶm[L۶m[5? }So ED"qssErI.]BQ+LWI|T>>8# @\\ܸqzyy\QiiiewaQ\[3le  `,;jШ+X5Ә=bAQƂ(jJ4"Re̼?n7Xvܹsfؙ9S6կ5#IKK {T!-- j]v-__]zuHHHU(((˃Y݂C9|q" U/@ҐĆ̔BoOgu AΝ;2*8J211aTMjU t8.`E $Y!p^(jѣG8 ϟ??w\UU(/ȓ#8JaZ޷SC95۷o~ UVVݻua|W^ 0uëpT* `*{gy>i$O7oYՒ۶)@( Obr.Dr\???z˗[hU#G:tmz`z$\[tǎT*׬YӪU~oݺA˗O>ѣC Zvm߾}iѢT*=ydǎ#T! a> HPPPllݻܹΔ)SLRZZlccdɒL hmBu!$k׮Q)!7o'%Jw#""ykd2~=zO vZ}С'O3p OԺoZZÇGաC}-X-\niBWw;gg˗/[,nٲ%99EVS/!Wo* "@|0IAT*ؓ$\EEE!!!Æ 􈌣7HsC3n7.X-#HfԞ={.[JG|w}G/qpp p¦M>UEQ[[۰0m -=$gmRԥ"2,Rse$EITr%GDnvtw3whfM?^bb]MAAADDğin|>kee/˗/$IX 'MMMCv޽Y_ZuqumFEEoǏ8;;S{\ޢEoٲJKKq CBB\b Cv3Innns"(^z͈#`hD?%Dyy9T=z4uT յkW??oܸ?3l7n\IIѣ;wP5*񉩩ݻw 4hСC5U[ڐt?\ްaC]\\rss:hժ՜9s:u!H~wH*ᘘ$oi6;TXA?VV\ HV8""{m]:.ހ$.:&næ\A7{V^ֱcF.\HLLlӦ͢Eڵkŋ?AAAݺu[v)S c9O<ㄾ⋶m۾ynD?#GJeXX؂ qaLfffaL@QEӧO.**H(E ?SE% ˭GӈG)^fR'B0RVJd:+N_ggaذa7n޽;l9|pC1;(ʰc[ZZ2555e4z,BXXݴi͛d2Y6mVXm͛x֭5y&""!0i=f̘J333mmmQȸp´iӌrF UG3Zرc9FzW/%OOOOJJbƖرUW^~=55U  6Yf3f4Dj'nܸ188xƍX`O2D0c\ۧng2OnjSPPc!W^dBtDDDm$d2@0I88Kp¹s:Td\!/ 7^={X}^唔Zu7.('W姘g|>&Mںuߗ_~yȑ5k\]]={㏭[߱cGTTҊEEEsff7MviPxѓ'Oo**44tС-[o\B*xf͚5 Ý[8ZT[-$RBAuG ;ouHv))Y={Z\L-̓O8ѭ[7rԩP8;pCWXѮ];jG(ԩS Di/___~OnnnTTm?~m6.dX$;vP*}5Ef9HYk F=zߗRR5o?09)) ~xANNLر#A ?SSSCCCWZU3"I& [[ÇGEE 8 }A`};vlRTs~)77w՚;"Eeaa#G.YZZEFF[ΐa`|Ո0ޱ<-T}$P\0j9z||?y'vf?N_K_~3f̀###`k7osM4umj  cX *!Ioacb. ss)..633{mBB€Ǐ߾}M:uo߾_zaVѣߺuk'NX`ɓ۵kgϞ_u۶mPTxXNxȐ!5L<]him7m8nJMUZOǎ2V JRT B8fHHHrr!C&2LTSBP d2Y3|pBAKNN[f͚Qe.+D^%%%ښ&%%1TftҥK&tn[ӣɂǃ+&&& LLL` Qy055uƍ'Ntqq)@W`=Z ,MVk boϟhɄyPKKڒke!= 2D.ܹ *3͛7ɹ}vII @˖-?~T*O}˖-Л;&&&++k„ T+>1<<|Ϟ=9۶mۈ#`;w56d 4:88eż!LaD;vԕQV_ߧGcǎ;vl͏|{5󥸹?qޔ$ IH;+N7::Zs@ׯұqǎfff(<{حK.eee0>f֬YOdd$}|A"""fϞmcc]v믿^wL_D1/7q$`j 0!Idy9NAHjǚ5jʕCZN1`TVl٢vQں'UI&Uxwm>VVӧ}MRnHΏ*  J,{zzRT|+Å c=?S/ܿVV؉L&k֬\ qww0Fz֭suqqqttTջv킡 vuu}s O0f`|O}iU=+XPŕa6?(K}yBȣ "77?HxuJv"Sp(@^F~U[:ʪJ/RYPPǙqJ[8U*ګW/ 7".] "_@xY5\5Ns?k6^rM)!4IKKܹ!Xeee999%r7o* !ݻ322AVرcƌЊlll4ΖdTIڐÇǏ܃Ȋl8nܸׯnARYRRAOÇZS Odiiinn.0paM9zӨ</\V""JvJbwּm}=|<~|g^^^]vJurmll삢(\(Y/ZrII V Jb(IdKkTDQ{K"&5&Tha/H$jQWb賲*OҕƐRhU̸2 r\zS]G7L.2NJ,Zh=zДΥ-''G?i֬Ypppf^xqT\m߾}=<<Tַ`t[:GOLHHH$RR* !!(Ƣz2_|uҫW&7;ׯ XXLNRkNNq8666EEE0\T"b@nPAu?_ڇ# &ca |>'yFW|" APƒ~qdjZURᄕ111-¿/6DM(KKK-YP`t"/1tOߋq/bظ66KKK \~ҥ~ayHHHk @3x zF LMM[jE'֙Fnnn.\2e EN<ټyΝ;޿J&&&2 ZOAew:}=ϟE{NS akETQXzZZNMM555"/ q6ծJO{Sxyy/DTx شifݺut*++w2e #fPDr]'֙ j"3uΜ9cƌy葯/㖯T=H$jP o.$IUUZEP h>Bhjz|\/wrhsV˛$ɒǏ'%%q8DҼyvڙ6- |kr ڶmݻ3g/IԔ#TIȆS_3W{{-[J$#YTTOxxxL2zRZΝ;/]4d1V n20^* չ.dPmuѧO͛75U"$$$PQU |2ҿgZ7=ZCv*FNOfKmH д^#O3 7olٲeDDPPPpiӦ+#j2? 0J4P6o|ƍw<%%%Rizٳ;uPon2pI?rA,ɓ'O81-- 0dr p8w1}Tvwkjwizaq>>eggk8[[[X8rĉ_~[?hQNNαc===?*Z *I?رcASSSPHL&C$66vpv5s^zuСŋ/^ܼy3$̙Ӯ];XAy۷oذa;~o޽322 E*FjZ*T*5Tݻ_]RjZmaaA\.7, A?0 0d/j_jw * >) K=$߿ߣG+kkRI-B8VAPP۷mllT:cǎϛ7OPhzzL&ӧcI)))pe<33A[nx"..߿޽v:e@ttt^^^|||aaaHH]]]ww :tݛ6mڙ3g̙Ð1thc68 BZ$f9s$ӧOoښqQ*jvvܹsO-D>|X$a^2?zBv{AGn5' 3&$ׯ_T*+++B֭[;88jBAT*$>}*J5ooǣZhqp* -?WZJ&ߟ 999&MZ~ԩSG B000ԩS .DDD4oΖVX*kOQFq<..͛666ڵyw}ψ V8A8 j~ W8 T5G`itQʵnh/!GGGwwwwI$$K .3\hSd2j¯}S hhw?蕖xzz r P E@rB(:88jP׮]#b#GuV=jI(^pC}`eeu޽-[t]ʨd]&&& ZүͬY ϟC8qC*޽{:UJ(...!!ߟȕx0,|8A8fK`HUJ]wn֐X@yy9}ݩ Y*LkׯNP\.p\9 ]LLL,,,ӑ[n=0f̘W5pz òe۱cǐۇ̙ӿ^zQHcbb  رc###رc;uݽ{wooojMVV111!IƍƝ;w޹s'::o߾'C OM-^7n ӧϕ+W`?vZC>h_d v) ǎ[א `7VDUob wfhtg={?pիԽPp8Pصk׵k`0 HMM}ڮ];>Ow<:l*ȇ!2{˗Kԩѣ?~ ȦM޿gAWqI&c׮]?ޥK^zL# a*++ &"I\R|γgHD"2LV:ujʔ)/V|~aÆGGG*eΝkjj ^M#G>|xII ߨsOcƌ5khfuFA@?B1)xZٳiٵs"\.yf֭[׏+ԙFX  2d͚5΂IR\n~~ ^۷/СÊ+޽ۡCR:8S7@_(!;:u6mZ``ŋϟׯϟ?իW={vٲe?x My=3%%ӧׯ_yY5 utzh0p{BQT }m۶ <V:8t$6lX˖-aKVVVTT\My<c*)0sssz.K\ 5jHcm_knʕqqq[nСP(x|>V?s挥KNN'|r5B 2i$ŋO>^P@pO9sc;2.:_V*VVV"LV8r|>=G6 58D" P(=jԨ-*+dߺ|wiѢE'Os͔QRRzך[ZZ>{̈SFFʕ+/\PUJAԃ/Tme ?8xzz~cVgBK:r _-:thddd]'\.7((斚nݺKhт I2;;{nnn ;vh̙3ëW GNϸ hnՕp/RW5߼yסCs Zhpvv 4޽{ɓANNbHt ssnݺRSSJeVj"أG:tҥj !w~$I77sE^,ҏ\jwrr IծP(^V@-s݋ʘc$PgϞ]bEǎgϞ4m4ĘO>}SN?> 1777n\LL 5smm۶!cwr$J%U򀀀ŋ{yyxzz^|3//T4j`HޣJbV]>8rrr_~aD"oI9rdxxx^6lcǎ+V޽["YpZ^^T*lYYY?cMlllzF;9t=zgj5cǎ`׽;9G<]#WTjWTxjL]Z7im4ܝT:ݠӦM\\\)e/^\TTDO駟¥˗àPYx%Rsz?zi~A'!((ŋ?|pp\>}xxxSI9p@}P~aZZ@ GdggCߕ+WrssE" ޱcKQo#kRd2Yf] @LLLhhhUTEmmm5p@'''PPMOC*jo8Lׇ\$MȇqZkUW*dS[ZP>ϰOUujV333ƟOwӣ:4aJ|WX[[bRX,H$bJbU2l| (uLJxAat3PXPPuSfffDDT*JP!Ci2 !49t/a.ZӲ>\\\ztًj* J%߁Çn=W!ȁH$iٲw˖-mllQx,,uưa:uTR0p8ż B b1RTTi,--Z606.""bŊTf@jjj׮] tU؞rVZ;vp\$3JVT \Kƍ;vX۶m.Xm\7hTjѥ7ig/z:;NJé'KG{3rlAuWn4/j~anqX& IDATf D*DJ*((HHHJsP~n޽;l >}zϞ=5wiٓ7n ޸q#@,/XӧǏ>7n!W|B8}]йzSSSaÚ5k6c Y !14(_(!;7DNR蛴vFԚL4CdIIӧO9s۶m,,,05>> iiiw܁.2Jo}Ç2QQQ 5f] i###7Lٳm_0z9rw=/22rݺug\k*hLjeӹb1"u?8ŵlrܹٳѣg-(z`afaa͛? 0(Lfff\\G^vm^^tj2dH-))i֬jmmrٳݻW[\HNNO͚5KNNISfW=k֬@cykXP_x𖮮TĉmF+񸸸[ntܹJݻ"hȐ!ӧOҾ,,ZQ*p}͈ccee/j5ݼkr|cyj kZ|~QQ\VVW^oa]0n>Sm_Ǐ H,T"|0PD_#I2''Ç=zaDAPP۷mll4mEs)(( yϫB8gϞ_~ǧ'RZVFEQPX5p?yaè'OVOrpp 6aaaÇ $IAmbw 4T*Lw4f,̞={…T݀21AŊT*U(O!C-ZtN˵YTF-|LjUI + R- JB拢GgϞ}9%K:f??W^͘10vXL}z||<| deeM0Jl\5ٲe˗/^XsXXX,(Ad2sss kB( }`eEMLLRT*dRr򰳳6m_ Rb8##~~[&Mtرf͚mܸqҤI;w666+VpqqYbŁ6o /֬Y ʚ>}w}׹s%K;vl֬Y~!Y׮]ku}vSN7oԵ,Ο?_r%,,})))TCvv]DGG-Y|`WW׷oߞ;w+]_o'( KG{DQVVfcc#\.,,@-( LLL,,,ʠRE 8|իW߾}{ͺ S{ԩD"HR:O xݝ;w,YңG+ٳVVV...999:t~:B}T|QkHV۷Ҳ(wlL=CFԢE z*JAj]0/_w9r߼yӺukk"VƏĉ3g :Ɒ4>ȀǩF-`r(τ8̽Vr9Ѭ',Ydԩ-=fArڵ]vA/`W;vYRR_{^®%ta #A>G8(բgːcZ0G,W) 666Px ,&ZAeddh=}C|M7jP ~'_stZ&{k ڟ=U ø\.oC #BV'''wEk_ݻwv @eʡG!%%e޼y;w 8}?LSz2b{{{ o2, P=m`_,K^x'W/|~}X2;w!9;;gggׅ,,,uN7o T* jq.)+T8SvoHRRR^^/É0 Z[hSRyH*{ࢃsnnn|||II ɩsGzwܹwI&*cFZr'&&999s{.>> #j9QfG W"M6eˠ% $$}D"8o߾]haVVVfff6mrtt}W]tѣǔ)S.]T۷ 2dD"3gNll… ofšD \x~+Wܻw/lX>p44雌Gz?c&J+.J_΄40@v`xGѿjM|Teai|'SA#sssPUVk׮]lY-uڶjՊ(x޽t>MApkǎ㏲2;3uTؾ~@{=jٺukaa /**BQFdΝ/]+ 0 K/M.=kF?hMrHR7ECkuHˮA_lFbaa! q 0 r:AMdZciD=##Q `´B;?APJo^hWHHH6mwLL̳g<== 36SՈ=UE)`++ZMjQ(޽stt`*0rHqppP(EjxRo=Z kc际@"8@~AR\uppp֭---k#P*R\vٳg===_za/QGK#frH Cֺ_daa9Ly+zplmmmmma& e޻wݻ@P_~T7tJca8p@}RTG_caiDkB~uu>?~ٳg))) ,7nw}gggo>, ,, zY"N3rl~̰=wra4JOO/--Y@J[RyΝ;ϝ;ĉ_|6d`l,4_E ^Z+}N6 Kޤ8`ի+..ŨQ8 CmT ZJ/' qaBpE/==~j`/Z.,#/… ;uԦM/_gժU@[ϵ'U/hg/oרo׿VT*Fr֬YLm׮LXxqzH$XVVVCIpONNNKK+))P FE > K6n8W# K#C ή4RtY |>ݝ_g:㫴Çᇚ+8߿Eѱcǚ)tyP(@yvA@$I5Nd{Œ7u41a4&>c5k*XK#EOW]U%z:r҄$ɽ{N<^/ճb$%%!2>l<ǭ/F{b)pAPgtAH$I@'1t  /GO mT  fd': je)Q{9 !PK0p8D""11Q.D"K5q<%%W5/%T#?GܞWF ^"B쐟mamH@@$N8A*H6w3VQ?M*Xꥺ' Ѭ h$~+GU1C,ӳjJV.k`-TV#M{_[q&cvIzg)Q 5@Dy9'~)KPV#H@ǁ\EJm%Y(Al,,, ;?9::A"Y6#i~,K k|Y+&ztr[8t2Ν;CJվ}Cry\@jGzutRFھ{ݻw߻w&㔫@$\۷̌[7|G I jREPP^^n,ŗFhJ5_1 c׳/eq@[ЭP@H 'O*n?>|&FKKKRRRIq%v=8QqjI( B>w1@g HV,@" 8ƺ4PK#[n-1p8zI&|4ZPW_ S1듒nܸnjj>c-Zt! yp?}OM$At uj8 5T ! D@čm|aOE1$ (D+9|~gjj XO4{j j¸&QAy/_LMMMKKrǏ0t)+++kkk[[[[[[h:"i ͫ'IPrZ_u„ \.h7n|yڶm͛g߿ʕ:u޽ P՛6m_zu;p@nnǪU`{nn%K޽د_+W\pAO3gDGGϚ5kݺuyyy3gΜ}*'L0ydJ"IR^^yZe*$LAGC0f&؏$T$N$A5N$Y\>L?V5v F-| P(RpO"pE{{{WWWWWWZMw0`@vvv^^ޣGR00 HZ ( !_zVvT*Jk@BBSzzkצNzf;v8~8˃>|۲?;H<~?7:uw1c\zz֬Y>}J,6]l۷o ߿lŊIIoV^3l0P7v֭[^sѢE#Gj5I* Ё 1 i/sK ߢ Ԁ NJ':`]caiP4&NҾ}{KKK Hܿ<%%eǎ澾G>}~J߷bP0B$4w[#_~3gl҂ᚚ"b)~~~Wrww+ӧp#GٳwpoD2w\$Ϟ=?u--- *x;vtiРrx{{Äg/_'kBj;" HB ( ( P $@! r0.Wicai@|[[XX78aaarVP5%dP wk+W긹$I:;;HP0 lv!G ۹\GzzzNNm۶HE R P\OKK۸q˗/჎aVb&\ E݌"IR-*AA$ UXXԚpEO,ױ@ v-`/&ZSkj ޿/JCBB\nII%jԾ5b(aRUVqqqaaa? W^Dڵ:SZԥ T(.PN okk3ӧ}|>?11!OSWEYfܸqC&nݚpƈ 3݄yJO@MM$}6FQN7=&< X_(^ZEݻw K5 ] Ri^^߰q ֬YCmkp0 h`bb|WOnٲeܹVVV(R]]]\. >|7RRR.//CEpūVڲeѣ _\xWv ׄ˩0!Wv'̿xhk{vA$ `1pЙk?eʔh3ƗAD" rssazҾ.Պ(JB'|K,H@"B8AqBP?znvBsssjF}4j>Bw,, hѵں)HzkZ ucT*&&&t! $===a>C|DBo߼ys~~ϵk~'OB=LW= ٹ>OMHb(@P"$E $ H! $8q0Hf+(,,, ;9U WKV5r&„ {{T@+3,d8|>?66ݝ z) v횝]llo}I sRbpP*b`(DH@$ q1NwҨo׿VFP(FR/@WejZּ)++QٳgϞ(@3"EjyJJU sC(~Dz+z,, jLϊV5GGMHH2dׅ%TQ)/>cǎ5+ K3"?4wvvvF|ڵku Q'caaPr՟Z0Ɂji* g}S\\+ fҵcZ&{kPrDaaa3dȐIvvv-K!qi'X @= 䲲pJJ \cQ{j j^MQ `B^)))PX?4x y2^DaIA5IDATaaaaaaai,|BQw,,,,,,,,,z`F; $I}V.Ԫ;j1Z0+r~4.HqaAwTTG]\:i DJ@4 =sPLHBAxHPE  R%"XD$4!T)c~>Kfݽsgޔ;oKr4s?w( %ѫb:::͛7677L~eEE:t~~9??oll|KKKsqq YZZoڴ?f(++?Cyyyoo *Q,_---sLɓǏZZZ455צ6!!!uuuFB@@{] li5JA ۷o'lŷ!EEEr+++4YYYKb񸼼<} 5$$ $)00pݺu>|>{oi%&&JHHܻwϟ\~eeܹ8}9. bv28ŋ}zz:227$%%yxx455)((pssdddv ۛjD"y{{={vjjjW\#uο?߽{7>>̬jvjIשּׂ/\PUU%jjjKqSD%yyyB'\]]~pp03.gۻڢ6bKi8A/^0 ###...mmmP(tWWW{"PgA^^~ff}WiiiP-_]]aooqww755dE{ׯ_kaa8<<jeeemmAдk׮{zz{AꜜO"v oooA???ss&&&:t)JrreBB-ʝaBV__fnçOݹs?~ޞ={fll ?߸qcǎR(;w8;;>|ՓYTT윝 ׯonn?22Zna^u92227 (0XO//lnn,6ZchjjJHH88#**z ebb;?snn۷a؆v(A^^˗ݻ!XBCCRSSϝ;ڊn쁁555ŧN/66OHH  x~nmmݺu+C=srrRRR|||uuuYGGGkkkKJJ"##}}}---sss]\\ZZZВY,i0)to|BNFKKKý333_zwwwNNN˰JKK E&֠a ل%uP\\ 766q:::˾>55恁~~j2\YYZUU())YEǎ8p@]]YYYjjj|||D"Y3{yyYXXEEEP_UUUp3FEEiiiYXXLMM577EDD455ᘕC`0Bsss|_ nnn𪎎ч|||8GXXbjjᴴ޿hoodeemllPM߿ SEEEα285驨pssojddbf RRRŢ444¯CCC 400011DJ  1DKf򳀦8ֽ' %""m۶r҉' G-Gay>xù ¢R8 K7j涵}𡓓SAAO?D>n޼ n۶m|||>>> ɓ'KsNmm퐐Ǐ1.'''j 3d$D"QVVvuu666JJJm!!!PMtwwEFF¯QaccҒW00|˖-+\"999(bO^~>6`8PUUO5>>azvttTTTX7֦2t3g8;;=z]<4EKك@ yHGG2!3V6 X_ kݛ!gܰasQwn pArӦM4'vh8o!,*`^(8]] {uփ&&&jA9r@144x"ݹs2 K$5-@ 8qa D"F$p<G}ؓ'OONNڵ4%$$/]Ϊn JLL쬚ZMM # &&Fog'O&%%!BDUUU틴*r6CcX999..N;eee4 3#o޼'W={^RRiknn))Jkk+b&qyy2+fW,i2)R SRkB4$..N׭[',,L"N%ԉ/ PM *ef ,0*((lݺܹsk222۷oqNNN;v噘`0==W- щ;={}MQQL&HCCC2/`9< #Ҙaee7oHQTTTМ풓SUU͝}֭[Y###?ᑌ@uuGOO/..իLѣG0B+'8Jff]FFFFFGG'##9okkP(L&kiiIII/FyyyA`ϟ?XYYUVVp|8`!а`jj*''zWn+ȼLfosrrn߾ +B<E~ 񇕕zŋTRR{9nə<~8555'''<<*0o!,*X;;(j#""&&&Μ9_khhLNNJHHWQ T[[[pp0 c\wvv=}  566fhypdkkkSSӈ6H"##SSSx|ZZڼnٳG@@:DDDۓ͛7>}E9'Otpp 366vss*// w`^GG^PPpfff޽ǎ[<78G ***ڴiSBB}SN:tH@@7##EDD +((888ԁ H))) WWW{wwdSkivuu ?s挭-CV6 f5řggg,w"899g^'---###JJJsUOOOiiwEEMX -`Æ *I"LLL( ]_< 3{[ 333$&"255EPxyy~u 30!$ """pI"1 <JCBBH$҅ P B_p?pe022ESYèi .9994D?LN8}`ngZ æY;11!//zqa^^^[޽+--=>>>!eeeN9,+nCNOOT Y1,KUUZx0cX"͂";q e|;,jSyQSH$%%O G?s1۷999ZZZ===999<~}ڜYpL@[3YuUSSMHH{UgVW lL$oJJJ߿>v옅ŊaUUYY:kk2֡o\c5Xc5XcbxB8q?IENDB`workbench-1.1.1/src/Resources/HelpFiles/Toolbar/Volume_View/Volume_ID_button.png000066400000000000000000000143761255417355300277240ustar00rootroot00000000000000PNG  IHDRK47iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki: Volume View Volume View
Volume View displays brain volume slices. In Volume View the Toolbar looks like this:


  • Slice Plane contains buttons to set which volume slice planes are displayed: P (parasagittal), C (coronal), A (axial), All (all 3 planes). 
  • The Reset button resets the orientation/zoom to the default.
  • Custom Orientation allows one to set and save (or not) a specific transform (pan, rotate, oblique rotate, zoom) for a surface or volume.
  • Montage contains settings for viewing multiple volume slices from a single slice plane at once. Rows/Cols set the number of Rows or Columns of slices to display. Step sets the index spacing between slices in the volume slice montage. 

  • Slice Indices/Coords contains settings for the slice index and stereotaxic (Talairach) coordinate to be viewed for each slice plane
  • The vertical Origin button rests the slice indices to the default (centered at the AC).
  • The  button (default on) activates movement of the crosshairs/volume slice to the same plane as the brainordinate selected in any of the Viewing Tabs in the same yoking group as the Volume tab.
  • The pull-down at the bottom toggles between Orthogonal and Oblique volume viewing.
  • Clipping contains settings for cutting down Surface, Volume, or Features data to be viewed.
  • Tab contains cross-tab functions for yoking the display of two or more Viewing Tabs.
workbench-1.1.1/src/Resources/HelpFiles/Workbench_Window/000077500000000000000000000000001255417355300233735ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Workbench_Window/Active_Tab/000077500000000000000000000000001255417355300253745ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Workbench_Window/Active_Tab/Active_Tab.html000077500000000000000000000044271255417355300302750ustar00rootroot00000000000000 Active Tab Active Tab
Active tab is the currently selected Viewing Tab at the top of the Workbench Window. The title of the Active tab is shown in bold, unitalicized font, e.g., (1) Montage in the image.
  • To select a tab to be the Active Tab, click on the tab title.
  • To set tab-specific display options, first select a particular tab as the Active tab, then make your selections.
  • To rename the Active tab, select the Rename Selected Tab... option in the Window Menu. Tab numbers can not be edited by the user.
  • To reorder the tabs, click and drag the tab title to the desired position. The tab numbers will not change.


workbench-1.1.1/src/Resources/HelpFiles/Workbench_Window/Active_Tab/Thumbs.db000077500000000000000000000100001255417355300271370ustar00rootroot00000000000000ࡱ> Root Entry8Q256_b77c91c9bd4c3d5d*t \.+?JFIFC  !"$"$C " }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?;Yi)-l`i.J#_h *6z CGuKJVQ%0,2ㅸlNQkБW`GJvSqr5o4bn##]#rd `zTtxRy$(c~BXԜs^(s˸rDZδן%~}&~_sӓQO @j*AQg&cԔrov $q&ZK;k)Z2J`#yqzR_k4gQcbBT:wdhأ얿p^0֧)7uo̍s-q6z#"ĺCKig#qøo𣓜Zϴ?QK_mL\K7kW얿}}b8[k ȕxhBb2X׈5BI+'pV&c=}C]O-(%C~sucsak-ҁ۫6~z(h:қkWWn0ɵi$r0I얿}}ab2&|^A9U$*cr/;h@auT` e&1;Fw_dhأ얿ƞ"o 4,Jvb?/$C\-#3X%Nrkc얿}}b9 Ut5{4ۥ`,r$:_Qvw0ǦrHp89#udhأ얿\xWOsJPu@.p ^3֍E/n|g,0sT`,O^⻿Zϴ?QK_Y`Qskus+UFʄ<mc]-(%C~po=u☹tblrAArN+Kz ѥד<(H-v/-(%C~pnUpG3,ynOLz]t{]eR%rARI=+_얿xA(workbench-1.1.1/src/Resources/HelpFiles/Workbench_Window/Active_Tab/Viewing_Tabs.png000066400000000000000000000364211255417355300304710ustar00rootroot00000000000000PNG  IHDRJ ?7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:yܧs=(.. ͿIBHotI*4ͲaJW@CCCCCC9ѣۏ?ʆ)Q.+<`y{]fC,>]ML>@hhhW'BZMVӝ|vmTxygggWRRISiPwwN|||On``Չ4Vmtgh;>>>ϗoT̳wMKhN͛wy%%N1n ;C7o޹s~FϞ=FNRzۨ)tJym7gt1_]~VCitg;*w7OZZKN !׿SE;;?R.TS"l',y->Zl^| eMx$*CϦJ9냧?NT'yj7m{ːPUU555ݽ{ÇOgێB~: {Qܜ(J8yϛ'.b6;`4-iW۩m23uzMF%(=eSןOʛiܵj\ 3d2.\8<$'SQSjxxCːg涶 )C03ijLu fpȦ+iKh7w9nƌ!!QonSɽw,l] =e#GE!jm(ow}ۢ\^BK̰[9+v=Q<դ;*xsd># & ]i.= ;~Ǽ8w-X޽{kFF!رcm6lv2+o݇AcSSEfc_t5! IK:F)y= Inݙ0фA RV!H.lu-4u@U2IuLf=M)5!̺zioa8\GwǏURRzѥKtl;N;i(؊_y$)ߚD;%e"PARp6j)o[\yidJkFYB݉ H^c2Az%{iIem>w{q)'[,y~SX*L(9Hw$Ai^aL^e Ցٓkwj/&:.W贓{' !ȋ9b~ p O]wV7uv]L\*UGL-qj}һW/f%TT>q.Hy`~F{8*=:Е]PĬlitj Yq.n"-uH xg7< Z@p<~a0_7KERi~4yY9B  ו2E 2($@(HJ/Z {߹bE]:+*WoeƊ+1zԍD,5An|kPmM debGGAx@޹#@:@ lǠ[} aq%7yqm۶effB v=`nh$֔>0 [5QXaٳ**850h KOw&JcvBCuӣ4PR(0XY9EiwWtEC/ vomBޓޕ )q o:NWɋvv_MJHa#.ϖW*ZxWL6{gk iAngcF3GVO6k1u鮧|kĈ3됲zR1EQY"!} pSEQY#}(*I,-\A#.l0UVl8{r9{\oرcL&9p@~:m`e76jNmA"Upf)`g"gʫyX}uO碦ĵcwlgL]4hO0`z4;a,@yIL8EEU}1=("!111SVQuKqq[m0,.$$&7i&zB5 ׎憢ɃzN M?4s?8r;fFq<Xm ;p]V OLLX3V%3n 8eEQ* Ϭx=:Ԫ6=)* qdtXdӰևWSyg ddk;;k+rv*2"Rd4hP39ߝ m 2T˔ %Y 0{SEBgh v|Pbbbb+Vy.'%*&Juw9& 4TW[(稹Y9s]0{+|׿4K VvIg׿e;~wC 8իW^8wZ*,,b3??֭yyym5m i KMf2!ޑSXu(nYqaLАme nK!ǟY](O9O6$(6R~BH8 ¼Ӏ!ćqa7?Œ@. @ x؏S(|6GC_> \pd2w-kjj߿_SSӱPh4b`G pr\Nmq ;m~ݡyzD~IL 4d|.jXzd%zDFӐI} (H6I~#x&gsv]("Jtx?&!W_yƣ{nbjceL2a8LG R`F'25¬߸U&}j G8Ɔ_*Ӕ?WyTRWc9 Nd7  4HtD 2P[l97EQ(M{lRUǰӕ5]H>\{dYߐȄ7ƨI׃Fc>bl%!c^Z->VI}k[3T 6߸`^Xu?ߝc:M}O⺡d7 ߱ߴRRgmg k 6S=m3ֆ9ψ^ʛgwd_w+JJJܮ^p$$$.^ 77722˻6 @Y^6ZUS^ud6"`a=+8jm̹T& !I!\vHBׁ)= !ψF= !5)GWQ:Lqt!)//y=<< >, cbb֮]1;N{{C()*tE(Hwo4*8p0<󍐨uB8 eY'gy/~S-@Fx{8_&^\YDTNmٸg +)ؤןE[n0?Q5:} SZ5L.0O 9`j9`,oWQvL\8㏂j,+0J݃4dpSsNxp-z3=Y5Gg}w@KSM:m.PMEv*ōsٮ?$4Xjө)zjC9;cڵ;|C{jByee{zz̙3zfΜiiibɽw[J(=EOcnUYl{ fq·4Ņ*pO>83ǾI˄(`N?iB* MF֞`/GlB!{GY임:6'aFב;y%%yyy-EuJKK7o|5OOO##ɓ'O2E5//3zs܉MWVf?,&ߛiҒĜy|Ѥ/*0'0HD|~jY`5-d$#,B9js]Yc۹8_R{M4<#?( *wg;`,ݨ2!DXQ_i+r5}48`+ȉŒBwt٢--L\} pp،~I^vꝿp~]!~~:ǯ$H+p•’¼BB Dܴn|/!qG^DdAq0E46%NC5F&Z(\8fB \ڞs5Dj Nrk+~.2* !q ! FS^g2*IJ!ko1+=Dْ!~-.RE%-IYS$׭$\2Lqr_8>Oϱkfi/|ܞc])noqv9xFJ~/W0dzI֥9xμrrkf?c`0$g `HXo# 1刼_b@^Ff~i*lֲJ@JF^NF1"\zuܸq...***222ҒB"..o߾FtҝlfǸҏym]?T474;WQ0fd O0c_EQOocnEԍqآopqajk?tIDATZ*ϋF ( (y-S%ϾD˕.st@?>yy)))011 O{ga.+n19tYGZzLV^ Jϓ2 }] 4X`~r1\ nWuc3ŧLzg  ~R@c`{0}6|? _2H)7+ˍN+)Y<d;d(*7ll.66EAk5=}9fz㷲|$%e[|B$ޡEKKLYَɋVVVfhhثW/iii[L+EQO#JJkLlEME)*J+7TU?\~Q~iPh_ &PSQZkLs\|ME~~)Lb9Kܾ`M_5 @ȬW#BNMW#J97T, ɿھVOͻЂFmij w/G JKL&UUق J(++k7gpcmWZtNsjtRjVy5XXM1wt goM_%&MiPBAA*͒nBH+(|Hs5-$HHHHHHl6ݱPi(QГ 5|j<CTi *jñT%X j,V Y=;mϻ_C!ҀKAEtd}KTXu%n߰4KAwAĶ#4N}5Xu޾uqW~jj3⬉!ʩoc VQEUxʚң:BCCrrrZi@),, }ߵ<'#""=t[h5N-qxI  }uFYh]fffWOR3n Bv,tFɦ_v }mt[h5gGӕvYYYWD.5wwmt[h5ο_VPPHJJ )))vvv];JJJBCCԺ:۷o/P(+b2Ã?QUUWUUDhhhhhhh>zvu444444444ċ/:N9KCCCCCCCCZ:Y"tIENDB`workbench-1.1.1/src/Resources/HelpFiles/Workbench_Window/Thumbs.db000077500000000000000000000470001255417355300251500ustar00rootroot00000000000000ࡱ>   !"#$%Root Entry256_8f263a31a19d7635*CC'zjˢϤC'zjˢϤJFIFC  !"$"$C" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?.+qx[uXMFt8@kVTm.u*g(s;h}KE|\" ҋT#,d0ew^6I$'|Y7g;;c޲O}Z_Euk4b1%$rdr? s{‖_cfw%^li{ 3rISqmϩu]OzE+Uxd5)\W5$SQn)T$)˖n]ı8w}Tt7TfQ,lgNoCgz xqVeX"K2 y{Z㭘]8?f˸2+ eJJ ۺt OwCΝ)5Xo-坹{8'2˅BN *OOp@ ֠D4K o\=cJ:W`Z~ Ͽt? GA;Sk8Iw idDdl[#hZۯm./10 R.~O`\*swCΝ)4nӿ%?ƾ_Kxn{ag[1F c1sӚE'QYPs0o8uQ,ΤU?(6 )»_1gXޖw%~*>5:wu: مYZؤL5q5?n ^Ob%S/EHeqsz^nenogK]Za3B>RZ'}?Jèk,sz{M3.֕8'?,ξN*SO!&kݹx Jc?>9'p>=dՉ_O9ew7q䢓qWGKC6 2IOs95~|ԟ,]R%ȅ7G;0rj[_Lxbnyۻ;VsX6ݗ"yAa[n:}8t g*W;rˌP/y95\OGz>Ϩ"~TqRVGG#U=[jۼ2&zǮIڤ#:s QԖq]C6Q3ӨOIi즴}_ŪG,ij4̊RPiԧ!YB0;<8F~BC"*Maܴ! k|dk>IF {9- 拕k-gLbvD < G1Tst"G2du{vme{iˣvy:TbYDO1RT#/z >kݚk[!a&Td93NV-ΩE ;`F9 wgk-]vnHd yƛ 6^…S2A=9G=}M}%[BF + ʁ+cW/\0MԔc'UᣅN.ۚ2o-.zfhwD́Qmb2,h>R3xOf躤߽y1b6<1rzk+Y%!Bꀵf8Rj΋I%o3C"3ڒ˩8I%/@2yqFq 1`pG"%\=|Uwb$]|Ő(E?}qs=j+ec.o"?`Syd3?Z5h4-<D$.r=q]cBSgbqSƫF饶!~ D4O]Kyk'vC~?\? gul~$e[*R(#uo?+r1xsu{uOuOkCH/y;Iȟ k\fqۜ F[0Vt1ʞ9a* 6 /yL_8GɏV9?.?$U}7CхyGVSTu뚥ޕo#4#mU;AOYy}Oҹ*HOKh!X_$Nc.BFu@#_ MՈlyCћ5N'FAƴm|d&XYf.xԁDr Wȗb܊xCē>-d̽i}פhZz$oC3C">ZOW0C[BA>Wj;xXJ-`:̾Z+J\9R5"ӾbAMYQE8QEC}uoci%ԫ17o>WlrNnZ5Z4k/e1u>vsn5 B0+6dX$9N9'>p\[ IVces2>=*;O S@qO ǖm}%G!}?,4XL/hy 9?ZIuV#4$mYNjyivY[O0;X2@>`=OW?p5Kψ . '<-cQ’Q3ǟȃ괻~,մMZ$ =21?i󳼆Xc3{P_J|Efay2e0\`qN*]Ho3kVb12׸5Q\֕(R4VgWj\jEK.Áǿ\ԴH?9Tc#?v+ X|dqDTkff܄ml`i~I>^_r"(/k.'C)$1GoZ@Fŭ](U6y8ݣ~| qs&WƫoFLg ܿ=j=ܿȯY]Z"ugȘ]Za@oOoǵ_[ n#1.1ggH/$HcZs!vXGTC,*o \aGܣЕԕ> a'zZk/)‡OSQ[cV(alF y؅?Aw'upD- m~*6.mN61IƄ"7 AǢG)ckJZrTS^_HYSubpHǾ~R~)5!%1WxH'Nz+M7GEB-4K!ߜsXZ?Sڜ܁J,ao*({)Ta 6K$0 AfVHbɨ*GZ?5[GFUb3s+ ɓVa]EFO>?{?4|_}#g|ֳnPIx *yWsN_ZOſ#7&nab0GoPK1=lt`01 R-ŶzO^PEkʻpnK٠YφUk{?4+u ǚ^n0Ofyx"O,6HϾkik{i O^2k>-6vmJ~h4{?4+N/r:($\SdXHAy`4bZPE9>Pb?蚁9GB=n ?%?:<tw5`iĹ9یlq"ԇz~h=_?`  [_%p$>Ҿ}e0U,$$=l䏠4ˆK9Wϓi WsNj9zst_HH͞Oӌ_ik{}vsr.cUOzGhեX}292,M!UTǎ9UijK(h_SJ7*;N Ӝf욿 KO]s$ҡMNı2;p gcҺ:NlUAETxOZMiiCj d[9 <}k飜_SQ] GC:PZ/$|  rx~iS? pz?Zm,ycWxh-Ud}=y}mOgoK%H?Q\ (o5bKMFz(O?kzY٧'sQ}M{W4-wQ)7*O}+S^Ӵ1i#OAq\n[>8!9ϥs ݲG2`U"].y V~ʵ|So4CHu~}gB@1_6+{?#'o!>wQÕqz:  1$ӯb76dOb>uIGݡimNs<*9 A iڃ[Hw~h__=궷QVFy؎*=jۚ?SA&%#[FThiABb؏u]ǍoVMXh(a\B nMCTc1% 힕+N@o;Nv>ϥT/Y?tR&F=}o.Ji$ C7zGG hX9xgkӅ*->=ǭg꫻LC5޿ȸƤcRnK!,)󮇘aFg|5ở|GSRXJ7vYYᖰlo=i#nL}ԧtF=W6:ng=ާqDGjQ).x-7m\W.HHtaOƾ^e*XA}+i߶2_0BĒxex?@v7s&n"ؑ'ok̀u<c1 ́W]ZWq~Rtt &1@<3qk$K9U=|}vO,\^[Y.UYJp=k-w77>/"Iп bw{G]>!hU]jV0.a^{{"fR\c0ZZ"ZZrCx]F7m Qe FkY闭rYuؠ M[cf#ԉFicf(Nj_8WT@?O?m\Y_E++h^moN-c2 ,?趓xIiH l~iסO<$*Rpxd= |@hi?g'+l#fΧi&?G`OQT(υ> `I~:a@>|9|4HMzV<|?ۥ%ޣ^9>ރV^AСKK e1=؞y^jSlՊց-$W|4׳wU|?ܟ)Km%ϥUoY@]ݫ55̡Ht4Vo3Sot"?~P9,4k~?! ۅVɹAmG3_k%fFQ; 0ݰ4ڀ>!<|?Ծ9$pYxe}F^[x:hEO ]Er [+_TfG~AETQ@Q@߷o액R&1VG߀57%_A=z?Hė,B?ֶ睇>oc>9in LH]G-gดV콫n⋻I/뫍9T Okj (ncs#uvBX#)&})Vb{ q]1c168#i4?-[&H^AGeRʍJԜ1aǿ]:GisԴ;#(?2{ڼ#H"JLٿyf5C) #◆_>;4#*@nTG}3f("W"?YVMclw_¨/dPv#"_ξEoF~6P&:chI%ƿ"jDtqoξ%Wb)X7D5oaP _ξXk]M,DtB)l~|5nBWQ\kÖCx-tiS8gQ_-m/S$ǪԴѰ(Q@r]]257F=į(A:HV"ǣshXK$ABԊgGr77u^__{/I_ؠQ1\"~,S?i]Rl 6 k{,X'&= p7M1^4r Ot]>FS2݃OӃ] ZeaŊyeHfff@><@~kFI>;-?Iѡ6My^Vfs^a odUT  1MFx)&B.G@p dIN_'\W&^?Եȷv{D/4x'hkڴ+oG_ƹ:=v[;1:cL[ ?3 >A6hW]FrHhv/%y* buD d9'yDxPKo 9"GxH.an}a@NL#FUZNs[jZ+,|mk2uFQiZsn6C%j[ uHTNŝoс\`?C] R {Kk` {J6q묺fm啥'Vz-61֧`eBr^6HbxU]X8xI$XBrjZDl!y:ĩ>g}Vs 4^E9v{_xB!]>]c+—{;-|F=KN&hliPFq 72L]}857tbca&veQ|.y&yyKt꫙Dz!=< X{ =s oI>ѮGmCYm0^Nr~ENؿco?O^/TLGio#L:D}%EW⇢QE|/s7 gyE mӅeSױ z x?UtyrԨ9Iv=^q swx;6: p? Zv0[b(P*\iғM}h{>Oω7+. :g*_(BVV Ԡ3&A| WIsw/uOG*_)jB?a&A| /K_~>1|l[h|;n"#zKK;wԏHk Qõ2zX ׹d?LHY(4^{'W>R&NdvƃZ] SPL"ti6}[pZZq1|z_sw/uOG*_(AԲ©`Hrt].Q<W9u ҋWd!Уߚ?W|BGT5ޛ 6MԄ|ϭY<;GϞ,"oON>!Уߚ@~k㞿]:қ+p>x8##ҽ/O 4K~:OJ?{,Q+.}+Zl k_? n4xW ξm) i8< g/L?܃p?s+3ėF, ;'#?Gco*ɇ+ׇBd+D_%3T|Yfx.ه_pd?GL?܃>a?ċXjK&-n?JƼdckM rH1|dwWp4#-oqHE"t``zOg޿!o}N"oPNHՈ0 ᚼ3AWOEi4ٟdC"=O4d'ܚ랧fЛ%O^'Wo᎙k4BJ LW{ErbMүU/*4t(((<3nhzS˫v1 VL ޫ;|Bf$<g-D^_>s8/=~9탫 Nn'ܫ@rp=x5׋Gdei:qũGP*A }֦ԙcֵ(Fg@TawhfY5! ۼQ0:Ր^hA*0۾~~{9lW%'RIOׯ*}NMۮ&$17$NpN6n35CP 21?Q\ɨo4|)(|~} Vף)iUvv?pk`-6wUC!"96 Xv`RE-,'Uvk+-%$WP99ϵG-/LmP N%#]Z!"E ]ohPg3FW66;Nszqޑ/e71/q8Oh!/3mA~^9eDc*F3Z=e̴%FXwc6t_jd35(R[x VXEe?jJlI+װn5 eFdͷd*2q~$>`R7l9ʌ|?:a3#:2\fuk[Vjy%YMh[0IU|g\h0 m<4 4&nIp Dgdx^*f͎]|, q֨wyU$8i2cFΙ91-ѻͧƗFͯ!S'LFxHhߵ‹WSF Y7]$Ԭf$>[1rd{S e,,K?AhgV pTn\{^\3ѻ~g~t?":s|?@7/5Ybi!Lw|sOcL? rJڐTin9@6W/?Y=vU6W/?Y=vT?ƟlQEb0((ͼ+cƋ`&i$p =+a4&vy\گVRuX=y|AV=pz53 r8U6~Dp[Zy6]Ir>oUhh>@o9]w>3 r?gD?cT'%%w@Z";&${J`=`}1QO"]OUeɨ~or㷥]tOQ? '/PǙ$Wt(GT@ G^;G/o"j+&Npg tǧs?O9G3 rGA)C\EWUbv~] ~?^iRc}SR[1c? '?O9G,~?W9cmJ s9S1MmƱNqUs?O9G3 rX2ӥ[{Bn݀(ܟaK "wrLNa:'(tOQ_ Ǖ&ێr͜qڱמ١ ^_)OtOQ? '?̿E75A @F,9vbn]X]7_1=y?gD?~3Ή29c >h^24_R#(g;J'-./CF?p$;@vEvƤ>^&}vVۏE^}̾kG,?S8W6yN``=AۓHc!#DmGS (?workbench-1.1.1/src/Resources/HelpFiles/Workbench_Window/Tooltip/000077500000000000000000000000001255417355300250255ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Workbench_Window/Tooltip/Thumbs.db000077500000000000000000001020001255417355300265720ustar00rootroot00000000000000ࡱ>   !"#$%&'()*+,-./012356789:;<=>?@Root Entryb c256_44874de50d40b7a1*'256_b476b7dbe98662b2*6256_11d555545a603fd3*4Lf>v'4*8JFIFC  !"$"$C" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?ʊ(ɯBCk a;Xz > :W+o#b쫮UE~^gE)ڜe|xWO}*o ?wхG==+<[G÷o{?++3 cRP˅U$WΚoni,>i"ITVs0Z> &uKіHڳyYHPH"Rcy6rװxOᶃ95_ȍ0tCz֣lSd#wp<xt-6hx``0?^eaC&٣۫i{mw$Slq ֻ?i6- 6:UR2L*e%2c1#ou xjqVᇋ#9OHߕuAuխi/#/yk7oxvi|KG}*I^OeM[ ;q߁K A*rB{]<Pl_N6W)%kZ<|C5z/ kCXqZQZ:6%Tg0U8w4(,TIu^]d p]#͌ [4G{0{gEKXɪѶ>@GZTjR#c lU'sY^c[#L!wNyՑJ2}Qk^q[đIqn0W Op<=.;P/ݔVPl奿:i{ek=u5Q^ӝr9`D̓V&}ΙN13X>avRop9H=kG oΟC1KG& 79^3T}2me%)-cE^j-\DF!bCBLjxUdW}ݺ饯8++Skwn|{kse9 !sJZ f8T1^#izs`?+vΔ#Ҍ'N PrIKx&<9cƷiH8 cΌτ<9f@#8Ú ~}qesW;B S \ 8#]?ݭ-{Zot5HeGd@>wmP>u!&zlZ6K;o&B2Fpk7< ](ČJ0r:W^>"ZV"XxBc;/b܆e<1ǥuڸu[}Jĺ4lef%Mmt*Fy%:cF9F;=}YxK֑m3J2AzWK:țh  lq^-7xQ>5^X3*Q܂y^^nxe ֑712/L~ sU&WgxSqk/ GjG.A'_ GO 4A|a<0#|x&::#cQW?%xcS&mч85?.'i)qϾMTd}7Z8[t@t}$0ø;bwe8M;~]6Q]WvQoݲu /Ӥa%p齲F= 0=ޣkWfQ[ Zcuz3^ci&X!:mԾ|)CϠ>kìWkmO,@bA IT*jQ#p# Ѧ?J<qqN9.{b#|I bU.Um_bʶW]VѦQz3<'fA%ן3x#Iň*]3K;Υxjf<*]HւQ~w-0jKXgi߭s$ӭgx VgHfiAC99 sҼ4酭)??_z$y&R)M4v$7Qͷm+> |B4+84=Yw>.9jz(BOf:V'|}E>gvA(ђ78j[ [kRc@18J&t!W3g= WGsK 3Q'@r$OcֱMN2*Gaҡ{zWϗķQcq->'kT+2#@M>O;VHcXԮuKssT:_}mwM.#NFi/KY^emX.*-l҃Nq26)EsU|3[_i?H?Lcʷ9aUksQYծ԰C+c~U|Vio#EJ-(>CG+pG#5%nu]ʱ5NOe?[FŬ}+d1U84qiO.־ռSlk5`<|,O#Eyd'rـu#nA z⾐hA|uWP/c[ekgaBYpO+'xRt]ZQR(1aZa OR=JX=ygm.h?WV+Sl] d}m2IݲHdh[Tdp O_w/\[7CiJX 9^w6IkfN#۹ #cfsI' ]HU:37Jec$ |^"ݷgWy [xǮW*}jX#C69*XtYs$Je\FL-n6O 2MekhR*ao}6q? $>ˊI \DcRUe!GSc㤊zP' 4CEs"$Q \8m\2[NOZ @> fv(N _ʶ. K<PI{Ӯn÷q V{6' 2Ht^&O;E8xv̑c rH&T'I;˚|7ccom,0'$U%YVM&;n9U{jn׊ \D : _9G`U/;Ysi\c~^- UÏ-kes\3ߟsH|RT>|RU/|E V%g,0l#=b[2E$󺟝׮ю7=P_W|%onzՌLðھr?m$V~hh%U1 \|g _xvONM2U:9(zNy^-&I_EF7*R݃5i=đܻH#/Hq\ӵ{ğEE:~*"uD`1GqT"4!MvKOJNI[^Kvχ(m}lυA 0yW;$GKgo>%]Nra$tv7R54^SS#* +Bϫ.|-ᯈjg73rԏU`}kڄ[pJMv?V#UCʸqaq^SR_i_^6o-+8EI'p~8㏇>HuE{dZgo8_zDhLe;ŒwsӾ02Hioe=:mY[lqUse#/?")<~qT_u_YvvWZn)a"@$ 5VMg,=bBuWXcAԮH]="U>%|gmt5itƱJa#3g_'ڀ>?#(ݎ:N7MƖX̎lǠ?io:mld#h˴8LT*t{ο6tڧd7(>-Ks-#؈5K.fNo!0<OOJ0*oB[<׈Z_}Kqp 붾C>>!x>`=jsӶa:(^ԵtBe1U•,c9> xf7Qe׿J;E~^i&Ӓ[wnCҽ_*To V!Hy8w(=7}E|E}!\xo\k[Z=hR[胺{#Hgtzp>\_㺾@>ď,6 >U1?|Kuf{mT֗7 qm+E*e<4PWPMW-"p0>J((ŕՓ RZ<%O=OީQ@\((+_Z&pe~~= G>$wZl1Az^mdHcLaҷA.OdgV[gZ|6H侒V Zc9yS%zAw.7ʎĎz Mt]-I #egQߊoHY.g8w_+W0_{}o!hilSq*9q6Iio3>|lv%_Ʊޱ3{)gi17/ٜ"BP_ p%xDpezo= -{d\ ^eQ8ҋI>ETQEQEQEQEQE'BΑwC:U^5Nys(Xc[TRkS7RMXi:xJܜTt b zvDݍ~b3rP+Zju18Z.m~CxJSFە-[w1K}&ҭXOy޵v§'湬x6c8y$g^u'_,~xa` 13cdԜ_[VW_=[ۻ>W-ѝlEeMYr$5ϕ]vd+:F=)_F:դw!i.;b8ֻ-:;նD 1a(QO h^SIվI֍WʛWz$އZ,F< >Q 3h~""'|@S9}Y]Mon5pJ TnAdhB7OЁj_7WȰ\^n RvQK:W>pxN^TRɴODמoxxK4;IP"Y=C" njG0{t-V4/'K F[Zxn9^6y}>fo[yk]@v|y:q.->GWzږ[[ZܱY@|*5hkG39^9GkWLlqGXtfl<8gZvW(F8%։ǵ[gxfn}%CHXdJOtxK0c&y5]J[7UXvLGk55}³kN#ֵJ.jw!FO^74/4N_R]fՠ&ohK0?s_ʇ"i^qTSGQ]}|A x nĖ"h[=y8<:bx\crOy~oo.G##I#1'ׁhaNO)4,uTfYn7n(+>Oa%d.qok早^i-eۜnSʷPO]٥B^Fy"s0ͤroUofh4Ml!c7-oa?d?)?ZٞH yVN6 ӗ,_NixJ|M w#$gWJմY?u\J*KhċQW_65Lq֢|;sݚך7gfQVzv{Zyaϭ,Js{_̥l4/)+KiřRMuQ^8jGgp _4Rt_R:(8B((((((i:|Cr(diՏ\\es՘TSR0)QEQEQEkז)C{VM I_[ydo.}#sTQ@(((((((6*1JFIFC  !"$"$C" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?PΠtIK3P;h[&-KK35f$a 1 s{<z[CU8ɻlqf`ZÞ V",@@ov4&&Roh"-eءr\WbZ=Ě|;xkWljNy*_`x3KEGLJl6`;W%|3u ԫJ98׊#ZswIѵ ,ahCz_FP//&_3WnZHF?TeAa2rqI^cNjn0iv4ҷ |5}c_Xl,=פ71xv]%[k Ja.{צZޥX6Mrs +s?,šZٳ䐑146&GA𭯇.|;j@̐ޣ=>ۋ>%`V }bƷkeiXg*Y>N{~u&Awc 7 CqxMaE9MA=LW[;s{1SiU`$?Cּg}OA 77C^x;N7S-kS5뚏(r-1l|4o 7Ru^B|?F]ї~x7/!Ou5uh4>۲&CW>n?|pMz7ɨiOk-e:eYD8e 5MVvi$wUk/Ϧ6)VuFMŞ<3 ҵ$ c;`ް+94Y5="c$ܤyd2Gׁx_7xMGo}o3"Z42rnћ{[]ogXgkqypI&yuZ&Uͻ ^*IN?UDRvTW{i]ϲ>}5})e ݒE]ԧ8>Z-I63N9 Κ^TrQ=AkJIʔg{U'ߧ3guU_蕯N1xW%kөQ@Q@ڇVu?~}&9P:G'AjqdA7̌90ss^&ZԒKU5 MglRV{'-$8-]<䃻gbBӠ6#UiuVv89H$TeQ +i=cm1Ԑ}W;gMxo6KmE nQׅQrn.~G: [+|&oꐈƬJvG MmCA?Xx.H*@':W__i6{{hgCz+`>\ xº6ׇ5;X͙"Ȑ>e_ aAq^N"V:I%&MfA)\+Ckۣ) |Q+o뗖Ǘ3/_|KD<1.1}R;V;@$x2@2-Ɵ'YeEho9 ijU@k&Jӭ<b+;̱4f!~W~]Q-e6'ʰ s zfHK닍\jB {inVӪ}oc>Tf! tϩ~ Qs;<Ii }=*6,wWShA=exS5[%O[iqUPgp'z;m$mtm|*0xj%DʓC7N'>ìX0\a&fs`{E6.,3,rșaO{Zδ m_z4c aQOp;{יC Rj6WG&8Su-+2 Y"QAoG ''sT5+;VڌJl=zj|Ay 3gI4oCDoa<>s `J𔰪sXi+&W;~SB[<5o>k uMn$o?|ڹ~b1+I.MĮRaPs ~Z8WƗ+ɬ'TrSSf3m(ٲ*^. d;s{^R<d'#N720pE'ԕʛm?S?=[3G0ȑn/`P.~|Ež_ih%Ԭ{'NkuxoLJFEufv7/z YMH`@{QR't|_#%/oxHA ;  kSƧ_p$m ;U|%oaxDdO޻kZ] ԮZ$۶j#56c>)GZkd &l}U_ Mj?*fǰ}Mtku%6 H W]]q+yܚf'C^ߋ/ﳈC,Юq߭znuBr3)y%Y#nf-+#P1 =k'l-rr 8?u~"}|-b6\M]:%(14WG^i!偿gAntq+fG$Bʰ<,v(x%>R_. : q]`ڧuSSԌ[P,pgS R拎5uq>ߡv-lK}&K"$AŒ[' OZśB𷄦Eѵ_j]lg`)1~4uQ׵;H.vqf*º?IoaG 3}Y$o!Z+rU:>onuoV)e]꾭{qۥ,R$*o%ir6Jqr\jbY-9#ŊɎH8l|R961v7:̠LzjsRqkєSRV9OϠ#meݰaiEi7)gN0,e99]ׂi^*גtפO!}A,rk薂'K1l1O$r}kT$㚍(҄idq}& smgq hdS1^p: s4|?ƛ} I6-춠2?|w\WM-{L4W#@C/?t\ljxI{#]Iã~Xj剩*tZӗzuFU*K_3õ[ᮣurqXuA׳nԬ$76 ?^eKcm͔eLQJ>n)Sh;oxCnTW=U$45酑8\mN1 EuiY٣|e~u~uAh p?:_0҂Z[+~'sz5ѯ0AQ̣pĞks]I `K U##O hy$78_ BH-$}=꼒E33ei[?&mK&[`ݍՍIo:}#b?[~2/$کQZ{{'_[MGԤ $ ,z 㫫۲1.'ϯ񑍾DC-rw3g!u JI[>KycrZmִ堺N~OySМ^WEg:Qӳ;F&۔ ^pW!-^\k,;#"@ ~WO-ah /#C}=x~$Ki$?ұ]1;m)#n,WNiRңOo/EϮ6sPa.o-,zχ.fpdW^&-2[R58bZ78_=xC5Y,n9AWtkʭJUiu]?feQEfE?Oyqms{-a !qJi73W ﯬ_ŦiCQN 8< X46/ol@EO28`[6vzl3㐽+֣ZvmM|")e|ܐ$}Ό1[x|] n48`b62K8Ԏ#QjJc.dg7>[R%ErALH#'$q @_,tv >'P@|îO{`Їm?rfς?!eY4~ofς?!evs+w'٠'l#B\?>Їm?rcofς?!evs+w'٠ ivni[KXRcYE(V"{GKh=[4l}?wf տQܟ&?xԟf6}lm@VuSi-1kiHYOei??zJPդiNh;3:vZ;G{ݷ#zV-ֻo|@uI_ V?C0T#"i,G-\܃}z6Liip=Ӥue[,Ip#nGͣ1ֺ7VbQX`%?bAnoA+>iW}!xI6ȓW~t 㑛jR>U]#-ݢR)1-<:-osR qu0pxu=>G_hua.lmux UpH/>}rKd ӣIdq  í[Ж f1۔r)N7vQ_kL"(mpFpK1H 49nl![#Eck0+3K{WfD%42PÅʲ2*@{u K弋v8 ~apFy[}Bņ:t]<ȉ#P7u ۺ3jh<9O+2bW\A+ug[S}%-Dr}LVr6 czcXJ"oep<^(UKHt})h(5Sxk٫%&Tm<۲0ʸe[Wk:|9 xV{K-N3:$nb/ [ɺ L23zg֊.>U<(c^CDz0cԣXpO_ֲ&m6)$ռ_̜<ڑd$6_F^%y  OaW&mWm3K+qd9@Cp:w8m>1&?;d7QּWRro*W bt`bOܷkG"/@%:6\~闐GwVQIma{h^ieUcvDS.>~V鯧烜,2rr>c xr]A, _)Qwܦ'ˌ?#XL#;YA蚯a6ʪibrCa>.:_ݨ4U[q3JIZF-3C#Re#[&7s&v搆͖I-g6 |Wr󑞜ԎHIu˵8'܁}+S۽K-{| Koɒ^8=DrgO\I~ux[kC.'H 1ɐn:$ Ċ1<ѬSv}{c\II'Od'p;# -ga=Fy\2TWdewYxd!JluԤdMG(~Ϲ`]c/RjG/B9R1hvIC3x)?u.U]Y\*y$qߠ=2K&/ ,e6K%21q< | ḺV'oAJ؇LV0|PG بdw$Z 'WI6]5ęPv%sc89蜜S'S75 j0Z r3uΝͻe$o>tk]xH1iL"DX#P@*:|t~$sXx66)1>RG6mOV-UI Dw1/vM,p:+1rM'ViKaxQyG*/Cӎ&}xecn] ɠ :dbX/A\c1\Y8,ۈtaV?%TV*bb` <6[hZ:PD[r@p0=3ƝwjW^7,i4$tz@Z\]_GwBFXw4ËkŐL' xpJ8 GcƳ2|jGvG^:!ɢSV]܃NP忊|7q4P` ~, ТXЈʼ_3B}5>WeHDxSI4|CX6\Kg(9'֥`;"a{¢h`P@=:tu$vW&K]ܓܓZ4Tק{YdӔnUUGS^TpXc&y+{g]G?" ɼMGMdMYYGU)@Vgx$@XXUx 9B+#@:q,x'M'fs ݳ|`hyuV7J:=m u1XMރ#:Bvy;."y;j]CQ$q5ݬ)2m*$ +8P Ǟ` A,wjg,V&}J Y/A# 4wUс1[ }* _ ]kʶ|0O*YzSRzeRS>I3 (8x犹iO ݅6Iq$)Ċ?|3vh@ b} O:Rw.q$8w)s>ķ2w }cxd\7 @##oѭZB/1ml߶k[?"P"rpP?Agj8v'08"+ĺE&W###okVCkA*H'{wc稦4j8 9~3\Y 'b; 0$k|ӛFEmT)pA/;q4ʖYm$3(/OP*Ģ+5ƊJbAjc#~Hw;vqǷ>_^ o@0܎2d1\1ɬ/t)dKPY 0@steduڭ `c18'?$M$y[3JBm"J⵼4(F(q-"O4mȻ@dz_SK[kB(V2:.Pz(Ưg o8V+K. ɯK ?}p<}p2[d3?pEՖ778oR0u0KvEqw,W{|Ė'URu:ڰu/-G޸UFU&븓NQ^^0P) r,o9=*MRԁ.mp':Q2a>cYiQc빗>80bX`EԌF)Ȍ@O[k)PDXsGPh!P`ڱH@ ;W^g~PVnjw-G0hY26lt:a8CʁF_GB;:hn]@FIp \& [[<[7cԊƠ(E']Fư4@s|m[[dy .#;eQ@3ooZ-Q2 M;>]g#'JUR]Bٱ`tĝ(.m̢S-ёsdIQE'^sj bbVGٳb;yzuKɊWuEraEPEPLf>vJFIFC  !"$"$C7" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?O[ẋ`{ʹK}CMgpZ.wGmpN{{ؚu^;{-6}a׿oՍMH;f9  T(o ֺmk,bFɅFR1:sI#[5[ZRD)lYVos>#(.AF㻠v3/'hۺǘ: ㎴Rl٬m7͑ǖ3g2¶4[MY<ﱩ5!%Im=1g`"99iUNgpT?C]޲0g d(NO54: ڢc俖~ӷ|7yɦZA \=zc9حZݾC#rg }.V줴iyˏ䑝ŷcn?vJX Ib-܇8'4,NAlm|;}]E{A`8<ȱOE.oOZ&L лr y3_k g>]Nwg8sOK ]nLA/ԧ/'|.UؚQ>)D> UE2r,6gϴeO< wjx~uZvTC9?xu98MNJQ3jyeI!?˖'Y/ur<nێ4do&M/2ٰ@ r3`1 2mV{>N2; ; fl,`m?vQcuU[RnXLbDV I+KǗ@-p-;Y1GֿFBa6?x?/9aKWZJmbqi",~qZ5?3I&[,MXgO\m^}ke-Z aXg'qש[E6fA4&o$Wh~O?Q+5>$[$y$i)ӁS?7~M's aS4X{rv8s܉ϖǠ}{}=6IO+.v€73CzX/"/hHb 6Ls8}EmfY٫iWKf#&Hl N#ox:uKk|H@aHmLγ UqI$rAJ.[!6Vca{xQB?qOcק8cu`\Zk FwyI:8HEXɀn^=0yk CJuBiQg/wtEux=sIɵf$KkqGy ӖfQ09Zmc+41)$@^xG&XvC"R>fTO %0Xf'<2do*;xZKdE2V`X`R>s'w % C==Gx 2YAf[F7fNrqի;6.a{]0)3i<3$HLGpvJ`$9W]FRkwY~wžwO .Xx~j2c8QԐ:`8憩j٭3>o.y>q!,qIg2y̚LH. c=ꎡ%-ĉȫ0O&G88y}oωo4FxuY$La>glGꚬ1ڛ-I$Us %A 9űS~)q\RT~899ϭ}NRAkz"],k:DIlёCXKv7®^u -, t[~"IRK֓iFiD^@JdsԷS&.5 }JmGNHNr_q-TԴɢIih66\"vy'=F F$_ " U`''cׇOVW |?j_r8WVOQe:#-8= GʰTRo JԿ'.`7VNM.E11݀[+$Vk$=7^7VEc]z##OO]^_9{.}$&ۈ%09ns] OMVo8$$>AҸZ\I4wCNW`O o0yJ58=SYev6n ')ұuuYg=p, .PA*>R$rKqʊM4Z8R9"9?1:xr[_D_L XLS!ƶ/b~Сg@sFpB ƶRYȱ^Iy0" ߟڗ̨,[`ԂqrYT^JydBGcyy=:v]bM1uAt||;d}t[M?v5ҮOM1HJY#vJsg5-"9%Mb^'h_.BQ =={Ud]sYO]jna9'=24Xd PX+czqF;v*.yb—1"Wv9+ C >ų GBE\B-!z56q umqYϜtW¨$)ROSnuiz1k 8__}777S ; N_] R&i\Qnնu +VhF`s:R5'i-bȾ^~cTuP.5vI%G餬e(]ʏ{V>TZ-n5kX t~`+?r'"jʌ϶*C?XwQwT-5\ѠY]H#**Hkn-X1nl٦ iգul b]q_\8Q ƴv)R8|ghaKOjSYN8Hp0`,[b$#e $5\kUsּ|R|E|mI~!Y9.UaGhz}I `coC~ ӕ5,F 4ZmB q[/22/ 7\||6O3iX.{veex5+98G+(q͏N?]ho47 {ğjg^K?f_>CSԿh[l(tmN1"=̣̯пh,1bvltۡCy ho\:(D$Ԝ xCELr0[{d{z(QoH<Fe 3c_.Rk[*٭2.$ 9u(I]3zu$+goeǤexI)"}W<W^O2fI[Iy>QT`qtQEQE^4fXqɯZgR4XNPx7A3pyp./d+7=k5m>s>EFDPơX|'dc)UxSS:ƍiKmEP-GĬ7Fk/B:~Sفh_EgjA g5cב.$hc[)BpNYNz0>+W6RGflěwR@#}Wi<9ku Iof@BA{(֕IN2:~2$tjJj)?M/B P2@MWiz-;x [ۜHT|Ǐέx᎒6wwDTmy$ӽW,]__IkH EJ3%/<:%vJ:6C?̢( LTu)FRݟworkbench-1.1.1/src/Resources/HelpFiles/Workbench_Window/Tooltip/Tooltip.html000077500000000000000000000021471255417355300273540ustar00rootroot00000000000000 Tooltip Tooltip
A Tooltip is pop-up information on Workbench functions that appears as a yellow box of text when you hover your cursor over a button or pull-down menu.  These are found throughout wb_view for easy access to functional explanations of buttons and options. The image below shows a tooltip for the Custom Orientation button in the Toolbar.


 



workbench-1.1.1/src/Resources/HelpFiles/Workbench_Window/Tooltip/Tooltip.png000066400000000000000000003067501255417355300272000ustar00rootroot00000000000000PNG  IHDR_h7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:_ >SCB[>6 >رcBѡ`f׷ ]$%%,Qe1 !* "Bm***b04&u-sR.$ųCt˖-U[[ϟWLCAc5 ~AC(VtX2X{xék9?k%%%Zuk?0t qB/:P}p8VVVXիVf5 j_L0[`oo_Q*,rVQw&%~⹮ųt5tC:|BкR~+~AfWԁl6NŨP(bcc>}ZIׯ~q###- … rf|7 1''',,Xp!TXUUuٳgTիWeT1ꈉa"Ҫ5x.mNߙܲsMfg>{znc>g[zN@񝯛{h-6B8+rkš:(..>}~Nu֖-[Ǝ۫Wm N}rЭ?Qp܊fe.],,,P5B2#I&mڴDw1UʈTwDF^ɤ"QCGL6յ[rrr6mԳ('7o`[7m@XX_|qAccc333rjIas8SRH.غEf(iݓgΜYRRXW^]b:7u4bwyJsC<M{15q5EIIIhhՉwݱc_~Y JmQ2ssCj;Ts2vkej.ڜ;<вw52$$L1n4&ӊL8`!5Bjǡ:000=zٳzrީ; C@OOOg1 -}}7onccSRRտA7P*nJJJJJzڳgSN <ʕKkq]vRq`BΘvYYY=|0))qrĈ>}*JWͺWY߻᰿z`ƍBCÒ>svqmܘ+֝1ag̘b &T*ׯٳixCN񿆆e˖Hx=ҥKh' g`{wW̘~y)&I .д!%''gׯ_Z7x zcxsUzܮw<2א''߲(Bȫ;.DmNއUx9?joްs17T32ePbU TUI>ٳQe4iŋk&͵k״]i JR4nK+h k}Mw;݈oɔuy`kema a#5/[HR [oI"&6f̘ w֍ 6P ~׭yc~JqdݺAM^xXGZ:>---6olffvΝ @fz]ag4Mi3 D"ofNӴ}Q}09kn)F=!CΖOZ<ne(H<}OyMf@e=Zv<|jd;So)UzHn7&0|}s;Y>„MU헴[!O5}QLǏ=v`[@:n_UUQ:ﴨ0P/C^mǧeZi0G=0F]*ʠ~>o|>)dTOΓTyy`;88hffgϞ1K6ennޥKUr߿j*SS/^XYY5nɓ'Νs玭< ;GLLL$iqqT*%wpl{?VQ;v0Fuyfwwu|5T4mdC=l !fӑq=&HJ;ycgb|]Lgǐ_Fw4#P/hԪms Ả~UfeH Xr c$K ~քg1O:}f^ /۶WʬD"Ν;BN< ãv@]".[bC~>zkWSCCCOO]OOmnd;y&rԬQO- Myu,gТA&C+ .0_SQ{kȊ2%\ xdQ;/-rjSY[E)ry5P =zݻw=zNuGMų*PlllmOcgA#MYr/6Gfv/#}v%6\&EZAۛ)V8&&͏vGZlɖ駷/,X4MK:4M)3o\3“c˓tVXߊu7;dB&ہ|ƍe7N&w1*S)OF jUJw@En!|]I <5uԻW!h:oE]9ZO\!4爚vi݋yI\ʿS_",#C< 9ytӂ(xNZ۫A)-kׂ4%6**Q޴Z^.\P(\\.zGKc?qlߍp牽[YAaJ.7Z *Xxk"G,h)}xaqژ>CE,ap_7Zk:ܔ+ 1GySE>0w)MgD:OժpZ"OחZu[՗ R*x ~Ge5wqD|~c;7^ښ:!o%7qkŻv#u{'u锰k<ţ9 cW ٘GSc˸}ZoD&iڪ̹A:vsLJ2<OeBrMLL$%%;u22.BQciw4bOrs~C++!,6V94уNJJZl٬Y|||;6p@he SgmwB^ VGWs,E_ 2Sggk8;wM S,3Ap ʫ) Νnggghh5k\~}޼y9v#¸58Q9Ig4N9rgS5օɱ/x]ith'tvx\BH`x+>O׶v P0vs[ύЀOw~[u3* un!-WhUmU?*|޹u-3=LPraE"vv}o x￲р1 uڹAA޳PO{{B^lFWtj'$5n '@FF]VokďJ.`\*P,aBХM'OZVы~WOX4!ji}Axa//'Oh"99СCӦM1bĮ]6vy6o̾Ǐ8^} ݻsYյeddRI6mRkHFQdz,[^Pcb`x&.FkoY'^:dnU{o}݁,mE=g <}jA )iI]>JRnD';-v? h:'40s3eZeͣiZƤa/7{#e/\H/\ z>F9ކ+V\|EFFF999[nsĉE"QN:\ MTEl\ڠk׆Sft2mU6I`gފE9w`NXwymΠy@l!WP7D|~yinyQ˅f;EB8ּEEE , i޼u^~P(.^vZ''P0n <Љ?lX|_~ 9y*))ߎ!ݻCzUe{&=Mj߾ʕ+ lYWƳ"kGźke7oNѴ(gO#L!.>+C\L3* L2:ya}>!"rr]l >!w`!DvYxOQIC {y^+ _> w֧dH7թX,4idɒ;vPpŠ{+!*{ 7ҫPD$(8C <8цKQr K.+!%/b"@a@fuUhwDN_`É_bgz1;g.o>!2o ]Ƌm lwObcGO]um* h3}[_Fu2$zOQ0wDV`޼yΝsrrwuue\7̫Wح|ɕ@Gґr ӻMgjxZܳ=+3+K.mfL8xv;0|)Ee0duwg^e'7K8Nxص+}xZQhx ( O;>nLSBH%K޽c#E|H/9d#}7[0JQ z(_8Z*MmN !/^&85dWц֬[2HYׯ_~i߲;`Eegg3TY ~T²X,&o3vPUpjIyADJY IDATDW_}EQG:dt钻{NNǍW<74yɓZv/rrr"^-.)❜ml?~(44컑~\nu@__}pӨgW,i F B kMn^Pt7Ȼwr}~^%6T]K[7t1i$YGc=œC&2R/O^6!ʗo@~,<~dnI쭳XiGB0Ô*LL|mߥ,Xжm[ٳsʺu떝][Aݩ-M]aglGQ%^vlʀʽzhLѝZ{G%zܶΪE߶N v#$Io-U:jszfsNl<2n߄$/͞zvOCn@ҰmcYGX^)@D]k=!6sO_XbCJ_|8Vc^P+ϡp Cgo+7Q2lIII_}E"W_)PDRTTT֫fq /9ڀJߓ\cw->ɦ--Z}D{;d?vf>xkϦk򳘽g)!o3I@RbZV}vuQS\oo8f_G݀+2^3kXAc/Tkԯ */-y ̕/ԨQU\\r%ɫWx6+ %0swl-k0xY7[E-}zO,Z ZrH+ayZl}rk,S brB}//0 gdZ";w[nfͲzzz\.R5oaMoJiƣMg@u{: ˍg.];̴ȭ/|FSp0a}ѿ{iᾛM'ZTͮQhVT_в߀ y@fDj/ ^.:ϋEӟ %z?̈xk΄9XSg'Lk`՛q&n cNJҢkǢiC|a^VsRLz:bGDDp8KKKSSSBX,a 6V~bфUkYV9e2Y ;wZsEWQw{ӪU+ug~Zk###&ɓ'~~~wܩfI]zguJ/|kdɡoZkOP9d%2 |C+fJYakbCoRVR,|C~e^TTbnnXGT4# ݻH/zzzN 罯’b囘Vީk]x'?GʖN>`;ppC;V>n%2kbnVʢ6v= +{v%_s* T7%5P}uQ)Ƿs,TD #oBߤP=}2%lġ+6 e\Ig|v٘1cjE~oxPTefffggKRjVV" P(Bɓ'O *`ذa##U?Y]AXA80lذr4}Ç066fUd[Xk+mll\ aH]hvcPAkeP-}K2t>1hR+0`*?]@X#2;lf뛚J$c|>____ 0+4 Y~mşq({4p8ݺuSb+y͚5+SFFmڸmۦWWb5RTTYfi -z}ߢ\U~WYFnTYC#q ^u}'OnI\zL):73uѡV Ƭ4N԰2+FА.xkeKWݒC]`ㄮ_=>5kBSKw5<$ijD/[~`>0tCㄮ_tСP}2Mly14j%6Տ,'b*Hk9?k2>]x,]n:Ez,4M36RTZu'g]|u5BH\\EQ<5u++OַQ PSAΟ?@"Է,! 4طo⩚t<#O֫W*K >>}-["ԿB: A7k|*4v5tAtAtӅs:蠃:蠃:|Ќ-,,dtu:蠃:蠃:|`effjabbq!..ptt_I*É'ޝ>ꠃ:|u'-t_Gr i M6`|x/e`aܸq\nUX(ڳgOꠃ:CCP\yҤI-#*5T Z m'5w,FNLmZ-UtH4zh//_mjSn>DOnx6VѶ.eըf=?E@*9bcc H?>SDF ,LlM33X( ˜5J[hm6aNUxژ'{n|V+AMvո0ŠdQ*999}XY)44TT沋O_ԦP,>qy}jP*Lj)Rb,&$1.BX m^:WwZ`Nh,y_.ev]wS,n5k_|XTدb1Ȉfr"#ءeQt!^zj-4,BJ%EQ^^^QQQ 6xvpZػrw\<+!;'<aY2X^&à nӦ XiDF7@G%tp4 `8Uj2o` RԦi%!R rÑt2y`ԝ֨Nc*JCCCJ}?If^.}5"OّL <9b] ,N@TA&lasJvI(Jyh ]z~5c'Nخd{!y~ʼnWvCOOhl7_ )j.ފS{Ţ8\NJ  qMjٓe_MsvzXkSk_6}МX[6⋻W.=`sQ=~;0Ѱ8]ַH&ЌU`kk+J[jըQ#d2V !ӝ;wz)s;`'ҾѳRLFj͔u뷅жo65ztjU+*j'VUU,ᓋ٥((333fP_ߕ̿w+Nع|ݏ+4qmۜWr~4"Q0LY缞;{DV)۳Aѐ*0(ʏ,>>݈)nbT7;?504tV_oM%H_ű*"EQ4M+ l l$c! MEx&BoM3ߓS7Kyh[h(^(2 dGLtΥԔϫ%S/] p\йT6=.ڷu-p1\ }]@VnW:׆ %$cSW~'Њ& 9!ܨ]Sv9>|١1KGl|F7!lErB ‰~Z^P(((Pmpׯ_K$L&r\.d B*4j(77W,&]Gտ0n)Aa+]<0Q&MynXިCLaRY>dc=5XEQ@T|r~4-S2Ȕ)!geܽ%ȔDFEdJ"S)a뗔(=K.;YT],Vy`0&6]Eh8Գ5馚+>O(%MJZƧKQ~.smj{l^*,C ٙ{!'+yl[π_>@0̿3,?\Y9c?30b710$(N:|l}~2Rwt@|܎/uWz:yo_ Wwx>qp;2޳K&)q{Ftjj6 mڌZݭ,s޽X Blmm4]\\, hf< l6盙 bsss#Tx6?&m3[:XsBV aSgos-c~)pqwr9ֆLq5b)WuRvÞ/$SS%{t ( o!YL̕Ls w6EPkDVX[UfTctW[-_RRpT{+i%+iwN tөGxzb7ѽܬa9⒖=l]rt7v [}9-bS96,-;7%cI]|uhj1KlȊ& 9!ӌ iҩLvfGWc{`4=μֺ]TJu4T%[#Rml%EpզeJ*P&8*=t4----y<Ul6Yb1y+Q"h {O PR!BVnK ڗC}%v1KTjP@z@ `ѡ]X,߻)hήת@Gg+ZbPȸ7Ue2Z 8m/}ĸyR;.A4+_%],1t\.F|h T= btQ?Ɩ+Zի2SX,B+rЕIp5Gl6B( d Z,S. * 0Mwlgf4%菢J/ÓL8 ߽=`H; fra^1p 4&|ޮ pk x:3qzwfRD04vzWRJ(Z_|w0-pQ]flw׹Q:4 /] Q&<=qxφllܽ|ӫ8sNL"3%b >-BC.J書 Y'IܝP 7E?Y_< j&呟,AӴD"Q(l6x<===~p(d4M,՜BB&9"ImFPYSC+HN [̗PReTY(g vʺCS~ol,R OJI%tN2_JH s?qXDNVʵ8ioQiJ3 l Dn“JgZ+|=s0ˤR$/r_7̈́;U IDAT}fM8-DCXs:t8_m-bRz/oJ"{L;ЪKQW>_iIk]MT6A0 *_>lbKܯ~ rT@C׊QÖ%U{@OhڥC׾Cl5bЀoj' 99Vn 7l@^+ n`HCM3 4Q$;5u;@3N +։X㞁}w_`CqbqvܞEow{&EĶM>aLB-fΙ~r*`:|ld\./ js!.Z)r?qlBl$OOS : !w(|`@-81wh1chpխGsC3#}3#X&â?x4 $efzh97okvD. o DV"7*ADz:?A`)ߔe=ڴjզV>\lpϥ=WZ1yʾkg9܇٧-<8(522JL4:3U{BJ%ŒJ4M?H^kr?! 4劳G<)1gx-S5ya~܊JjZ6] Z424pw4,HsCy0/&JhƜ*|bݑ l,6,MMr>eUD44p45bIێ@izw.♦/74D _B.M/ػSf,B]1Ք4m1YԵJ,},zVg pBw[bҧGzhk%e7ñtBo{ڏ+"2h/s8gbe\Xsؗ,wqr"k?JoyŸt2I٫bgt|2W#z_d狻nAZIͩ/]i\tjXݪaw_yb \DRk̵߯t$ 5}K=et(u[ PG`0>=}{̇Dg`\\\>`]y|LL`jk"BZEԒRvUJТ(GiV"U Yf2{xd2 Z|?>1s޹ޙ{={w#p'.|W/Vu񐯓> ZFD|8KD`gh8]~~ )>U^m~5 䳝j(-sXsF#6>:rl+s}3O15g @ ,q%z5P! -630 ðL6QKuzUp1ڏTWx` ^R=N8T-0|e9 Ouc$mky䧁 |B ! v;+/6a3&vF҈\28F)cbB'EJNtfxDW>s}2mͩ둊6B @aDz7=0Fx;-C?mͧU?r?=n'קnW`H6,2,Pv@C9!P0ܶV ú][}۲w67 iﷇ%c=w</uYۖfNG@J E6i+[ !VUպ_𜜜ʕ+;+Gšwcj\q=oM[ܥ m!Պ:~c !)Y jbf*k9 n9uD1nՔZM;5nb @sx\;lL36\Ӑj]o8:ieV+3'w82k-J YYYߍR/_={+Wl;w̞=bŊzzLuy{o733 <)u:;iQm7WGdXNbk= PXh45':$)@FVxG.[Թ&1 Rf1vr9!4OX=-O^ R A9~ FUܬ%@3jk8er  MGD ( igs?Wٛ!XKIO~3a'{]1y ˲,X:]|UwѰp/H-y@l Bx ò,KXVa`,Т;wL =:n^p]!c2a${~5}Ք0AUIV Q"D^H%ߧ)>!p]s< ))景r̂ ;az%6 fXs30 'l ʼnKz&5>YD}&=ΐ!,. x%Q,K% ˜-^ e/ܪed'qÀsV_Q~v|h ؀||/ae@N@б{t0EwO4M]qܱc4MF cǎ?~<**AM0 z#vXjp?RE},DۭK?qsbOi4<sÕ[}5M:m;PVIw=5}>7}nݺ͚5)vȨ})SQ{zJܱI'=n͹/GJjMۢA=^1kH}*3^9Wuʽ{zQ(++qWx/lp9#Sabtko-ގLK0э+)3d 髐"c2R mm;EP!FZ:6q˵G^ i_L[wpOwX xcn D(1`oRZ釉ռ9Ýn혚/ƴI5zeI@ϗj,,L|V]`ޥ wjTTk zm)/_g6og3q_ wZњ ]%crOڍӀl#t8QRRR*Ay^*8VIocBI)%@fƥZl 2_hs.]<P弁-O7yZ/cֽ-qm\xzUZخuz&3dMr_keYO"Xb֭CBBN:tR@&k.,,A@[g8pÑ|୏Py?Ո^/]~.H1`XqRh')]l7ObRDM#cNA{\D.'QJi;*>5a޴ȱ{4|ȍJM0r'sQJݪoΉmWǗm}J-N&b:jiTؼaInh4M.:Y9Qaԑy]T^}g\)eL5<]%!Bk=Q;Z~5RTrkDe|)h*R?|3|ְC.l lPn VX +NR*=T22r٦]mxNJr/?L5mZwm]hK]LuF\yhv҄nxAp=`Ô ,O~26[M>Zx xX;/FqyRfv le 2s D; 5?Nc7GI+ZO6դo}@KtJ/5j1bĝ;wb& (g_r5 ʒNJj^^^*):ű8|-زeoUt;_uvAꫯ:w@nٲ%&&9BEJ1kJB]n޽{W=<<$M32ry@ॄSk6fm2o-̟{=f=MjX͆\^΅ޘe`^* Y(uƌ\NUKo2D-VOR[V*!fC>Nl _B{!sFZ/%"*Ele明WR[u>a{RjT͆{&Rx7rysV؃٘ej4Q]\,^Z\>;qJVv_0 m-[㜔r`6VUi6f VUhV0Mq'~gժUN A ?Rt6k֬ڵk8tOJ g`F:B z')C8A+ҝ)]};C:T>*+j}.QD tK>JeW!Jek;ӥOn/DWVGpWR(Dr#SMja0`(@hfܢTAA)"e((Xef* 7f>]2Hk>}:v t˲UjV:sN<0CwˠAG) ܗRپ7nAR!`nԾZAA(Rql(%>x*k٭ lp R)4__lذAq,שSRYz^zaDiV+YNJPPQ+@# ʾr*5ףQYU gϞMKKbVKaPk蟬U޻-*Vٓ ^^( KS?<9oZ0}vZ4}y %DQ,ڴۯ1z 6#p"}2y ˲ZZbEjj+RF'~>% ZỌOuKRn%lEc%(P|mIWX{] wޭ];Lܪ*BXX؎;ApGEi=] )ks={+̂e!0Iӻv -]]܆iD 2}psWF_{Fu_mK A~GAwz>ȿw `9a:-fyzz//) gumq .a>LIܮ^ Jv安RPj6.ZFV/`¶K8fF>7iSz߮S+W*و yF/P)3BBBBm۶sn aT7YXKy("1NA4( 8R HW5YjĈ1!-Oh^Pe{tQ09a!rDL|9gτ`Ә1onk[.fLJC'K&^dL(d}dr 7P0#JC8H8" yx3wX2zε Fn R>5gz/Y^4Y;T=_}gkz[tjjpc§2x$Js쮍guM=HOc>yNy/bkR'ʕ+4֭[aJ^vU{*g OldW[qmV0P.TE&G fINkqT []@/Q^&mq)uyhF|e]:-xYe=b;]MS"+ɋS@tQ{l(Æ ={^z~@"E5;ybHKwRDQa[W2gmkIcT烃-7?Uϲ`(Yh6V'͎!==]Qy/h\3?I癪*pF IDAT?+a۶m[CE>mҤժ (:Y !rbŊESn4i7HgYaCAPTyeYB]߅(r~jȀ.lW䰃#q.W+@*H_O+T JwmZSڤ@ c W&m4 F.=5/†]B]>>C]_'~j.oOhHӻ'"طӟ'D]>#6q- n 4pUExкu(έ?ް(iRjB%YwF| gEm_w%(ug0 ?T^7f?˖#F{U|(Vy } w'X~uu@WϘ}lGSUva}M>q~MCή6 @։MMr3MZH\;_*̓K&vX@ۄj?̕)Vد_ǔ~˴lDj%'cZwt>/JsJ }'F,H,RQϺ +SoO% mwras yZȟis̢مYшMs!G5eBY5'薸ccS\9wHVȺrWIl:zxwIvs'oo6i-bs?y1x45LRAp[xi߼ץ@dܪ AA=Jۛnd¾Q)8~ Uy6"!_}ڊ%ݾqF!@(UJ[I1H (;C 6e*ImVQxxi~wFsڻ-&%~{͍gZ_' }a jj܅Cs.ߪ"ڼGt:Ꙧ]cxs;US\6}>C竃}>A]wdgE99GBؑe;ZtC{,\V*sy?"="--l)G9Q7BCC7L >4uV((.)P*Eb%{kwp/jeTrV)hseC״s\Ι`EUov]1uIh/`>y⎈ͣZ=ᝃKw|bt ??[.h v)aV'NZ3O׏.j{s_2vS;x.T)% Rt+ tXeQuq}V^{rJ~~H?=~0iHxG=.rqYFgݳu;6iݥAӗ"TiME)8)anrvSSSm6J`0p~޾*OK-cXeU$**e !*`08*{$oJxy|ԭ-ȪwNH>%JMŽ3qutVmi%iǯ醷?0+=נh5-@X *ǮڏK:C5@;j"S?O܁.;Y) O)77WM޼ۈ)_ +p]:ĎլV2iuJ_>덺]zJ .(G9Q*իM >4Œsv)JeY㊞!ٕj+Ic|y|/RJ*өJ[aFvmAAH6^k|jZ [DA)mylS>g`öX{0_T@@=ukn~›*(!Y~46j] =<ݸfY`0t=4o"ˡh \ً-ľ58(Ө'D4fKDQ,C<)t /ZyJ(KTa/;7z!aXJ*PXl"9\xw޵l.DJiU"RөdZ_l@}BC,SR7cͳQ^HW/-MٮJ8,w~^]6qVcgˁϺfБi/)SWh[jum;$.=yU>f54j, ""gYP YVUr /S33^FRx T+|)JE@kˇِa0T:MIԹ-(ǣå-uDht2\rQt;O]7nܴiSQ6ZkVq a^ƍߜJ^Qj(cjO#trgJ5)A/9w=+7\+i B9ݗ,a!2V)Fؙ>j @U + !"gLqj^<] 4k֌}?~Lu?W@ک?~s:u4keӱ=| +()+}"T o͑ m8Dŧ_:2L=V~cu]}UYB.,%rxžs(݋6E9wC)'(At>8bq9%KBW>lذr|YPBqyVi+ $`thNv r^0i;0p+,1Tc08fZ>!tUYthio8;U))@)E,7uJBdϴ/^ڠwf#t]5kD5dexS&`qdzڠŕcqÇѣG#?,b=ȿw]Gr=Xeԩyzz/Ӳ͕Nm\ᕣ(C+.݇x:[M`JNWQ6g88FsBf }ӦM톞v( d2vew}'TA^JNu@ k F,! DJfkƜ?K! %"*E& ٍ8Rz9謄ZVT>>>҄˲]O@qI…H)!иz| { [>C7ηQߩZuƭ$]@ uzac/87)͐ :bx|pppqʟ={V>Ҵ/F*x2<`D{dXıB<0 ob`RtLLo&?>{,jX…}/ K0%eѡ5wq4d'9)z4l߇}nG'58Zr }8_uJ ի <= NHA"U{.wl3eV {+޽siw;DQa߭e@l饧Օ 8) 0Z}_wcK3 lWczJnwbKMVmȹot/Hg8M6lP(Ǐ_;tP  = ꔔի'mWG==Hc^j PiK @!7rܭcj+:g/8t%PJsssO8q…,㼼VZ~}Q9"6su]qQ:38l|x8?#G=i̠ ?C-"z6e|0ACsz ~W9B ?szΝ$vԸms/ ·zKBrz{>Yޜ )ٺ 'dyj8ϵP *yʼ3o ?,aQoݕpv N=|IVޮj5>PҎ񒌶 ,+/ܶ$&& ,6[w?+i Q^e)V|EATQAw^aqkn=iAvQ#0-%V*{fi {>J7gqw7q)j#ۇ^V *VtKm?>!pԂ E2"}_fpYB橝;w͛E x'{?hj GItAxpЎNiУYwʉn%1j;z4%Ù߰oZRjWϹm 6tСCk"r ?7k[V}nG̬C LݐNf *ݘ2k,W!u@VRy[5ݔ]~ncRۊr7m9!=&ZS O-<cyr82 ǁ A.\_e˖.Oל݌ ){.yۈß -۷e˖/nݺ.,Ftju7޹~꠨`_{}⠲@p᯶q#R[rFciZ27}2jKԪ}KHr*/]˶ݸ}<y}`XH~Jz]o \&BQL#i;ؤNJ-FlYTGu\=9`<|DQxH.gGC[[ymKw}~u ptk$y ?ۛvO*֭aJWXrY~Iڳ)+ܜ ulDQaUeps{M~cIg/'MQ.meș}4]Gg|vPT8Fy|Uk^QEAP~sΑ^Y+D$0zޏnE%6B@OuuڤuM_rQAy6 l6*7R7>|útCtQSDAy&f)y'[?&2ߕV0 +B<(Lh _Itv1Ka9VNN٣WgaNfr<C  W_w| Uj]V62("%Z̲`tđ"fی/"ןe4Xpe/*\>Sg)o]-j(N?|vzjgGyyBScH{twΌmrQ@"~´0;= j6b3ke@$QH֌k]eApo24)+\W09yrv~U-DQVρᯄWEprZVC5 B",Z\q(x)Gͺ"Hݐuѭߌ:[?UG5c&O]='.9>z {)B\I~wzߍ:aڼ +drʙgf4Yn6sO~AOfJ*o۷#.0Sad2YQ#/ɥvcZ'NQFpp09ofQ*>>> E[vӣEtǃOn_#i Z#t:}^^Z)1=`;n1 g洉J(9=U5QǤmǬXvkpasCΎSwm F;X}W[_ۜ{-û Fts@ձ@殏gmS[&$djIno)ѢȵA<!P"Ec3MW3dLT@ܯc{I=Ϙ]Rzw@[d>nXDLJmv 냙v÷}4/0nz5!$S9$hM`3L_OO'VS+MjYջPm:pL;goX'<_W$:wjLMƾ-De1{,(|o3xgZ>8JvRADA,-z 5](PV_WfL˧mmuB Kߎjipɵ=5n0{4]+w}~Խߟ>[qh]랯cz^1fV?}1un1*vpBryHHHpYr8ƓEQԑ 77ݻz^RL&Qa!lQW^uvvpH4F}N SoLX^u|]H.n,5)h!w %SV Ѕ[vp;zTsԕ*]\;`Fg*Li[upâpQ ((ƋrB5SJ_S7"~`j'Rr| g W\?Aji^ v~p#'|* _tȕ\Hap53A @j* (W2aG"feDg:W,*u-tρ8>$ǩP Ϸ}%&Mucv 􄐸=@٨2oԸAM(< |C*|f*u B ]1c'̝=RH/Ƚf+o;96T J VpuFPWл4{mΞUㅈfpa0ɰ D0 q\9Jaxj`gL}uGޫr?ijJmo 5m6r᧯Wrϩv|6!Ck˕6!0< YU8k}+%T/5}z֩H9=jZ B1eyXlǺSWs"=~ðֵt3]\>R3;߾q+&ۙ1fSҌM>g1 WP,HUz;Y󙂣Qe~R24냯giҺ(fUEg,!0CU@*S\riqSܺ4!\)i"B4׀WA-}KC?"@7͹iwSABb4~&e,ݙ?bYbƲ\ 靫8mcB##@e v4 %qyW&@2H``'B@y |.@x+!*B\DҸlmPF"@\zJI>NJc{)JP&X]u]"ehvلIWLBܨeh%#F]kw,Eقlm^#"^AEAP5g^%#r/n{Pq@[W=V`oqBfI׭@Q rtI}O$3S!&hT8 *B@DЁ2%I]تf*|b7:I +#WyXC}_/>XQP(X5v8݂ϙwx%0o@tCy@̊Nܓ3yt^~L|nsdoߺ+>fӬ K\.hڲ:VvlQDw~)>X|u^p.h4ZV]]%OJUݒaFv$@z#?ldR8]sEtLxl+yUl\$< Z=0zH_n`sSJh4Q|w?pW!%[)9MQ= @4hyViQhB4MS4 N&>I6:-QdRSRFiE` )V6F8wUmu9CFU;M8I\BSvi=WfW*P/\͟v;(whi_t:~(`ЕP!\M,D(@ht;J  *Wլ] OA Pl{Eٞl 4h(V C 4hBQ%gMI&mEf %:(JX p4J {!n._[}rC`2 |B"{J['FtU%fu^>1-åD[.(Gg*JLP(w ]*>4)KO3r`ίrY-nHBx1{'v ^e>ȹwS<=Z#*U4*ʒGηP[7$i yX^ϢZ?SϹq"rrٷQJU c yyyʼnXdW.E,η-b^mEQJҶbN HC:i9 ]t\rb!?rXO l@G3;6k}X @S۷/)QO|`_7t@ ;`J{{ _\7m')O=4-!x*e趴e< 4am)iߜ৉›i\ a/vmHejJt.2hJlBNn{[|&"lz\%c.u`˕+շb;?It/WR !89a@[};Gչ]$~kQ:Bif 9qat:4eBvYcrGWzeVz5Tl:20,Eb E4]]o$' hkշw]mYJa{FP2G_ˠ ފ~΃'h EQ @8AE9C WgP"nuƀ &v5 =pgRPbYVNzahQ$(ea/؀}KL#,27ɛ2zըU)( M4M̤.Уn6n^&%R9Y}b+ _݋aڴNkרpm;.jUr(`?Q4d钲:/81("TQ8@vZݺu5G[W?xիgQݿt 2tA[#YRݐy6Gvb< z'F/+v20rɳ4̊u|1o3÷&ǥguE~smé'I}8sb1?R .婡xkή3|`Z/mͲ#H.!d2-:eyP*T,3n^0jfcX˲>C)=§D츴G Ͱ,"h)UI~Ho5{}ƿ&5[{:5ci D O^5ܡ5Bf{~DF|5:z#6k t,L#ˆsqfU_lȴU[:fh){R GSr+>,# VàLč|uN²gÕL  8xV=aDQ h}XV0(a|֌gkkǿlt<ƶYCkXaai d2QX0Oxc[ /ma:tXpUR !,[Ӕ˩z'XPheYM9EBR;sE?nOb`bd2RƔչN zWtZݰa@9;|5jxFYPnڬ^n6+>7W0rZ"^vh4t\Kms\Eء'j^CmJQFrVr#ר4DUeev2`4j5]&(XXU{Ph)Kp⒒ YYYݻ}6#Gtq_|u6_^WW~^7oSxumK]fm;mm. vڛגj;>_s?ֆ w˖-?@ZZ<$h׮c񧥥v0~=zI]ufsp[ W u,Qϝ;hWq wܩ^|0n1K90xb([tSNv lʬSR2L7ܼLrJ{L`@?#Ga\M3:y0 .>,8L F~81j5 iBRh?4"D" <87wRó1<0rMi\͆4: ]t)'ĨjS$q.d0Mx:DKE':ٖQ"Ɠ֣GGڌD:'PtiA֯_R.nPP;w8A޵kիW'LPR%6vZrrrhhh6m+xn2'h]y>z" X wg[(-6|bg׽O.=Bf~VBTTy4B<{qv%6^2fh(lzKrv_^F2er;5:f+7Z}zΝ'OUVӦM7MDV{_BI+@Q y#qʷb1S.ϊ[lX"M-&G5׋LBJ:uTe2R|yU; ӠRPlQӤ~}sp(dΎ-:hE__P qI·W%`|/@fe4蛇UA^):/Xz?]'F߾}3#(&mR1-svmBc*&;?I"ow]jZQ8!㮳#ϯk#k%6d1= yVpΝ;1`\r[.WܩS-Z$,[L5kubLl6uA=yw\oCw0 c6ʗ/=kJQ!ЩlYQ63mz; & *5R%euѲeA_lRw֕ D?_н D“/+d9:RPrtY&PEyʡ^Q:عs/#yԠy$.>wNB5?ՀZ{\?f׳׼}Hzw_ύ{&7ue5Gw2L tݱNLf ?F|FKRnCt^/ m)!)AQZVTR$3=_ز /DA~? !$444!!5kyvF)ՠ(*888!!!449Pjx#++㌰3&oӆSy3?KZ? (GvI~E/d,!!®)5590WN9B@gxBHޝmz]\'󯻫v]y_3OtvMJڄ75&@$?45Ke] E 5۷o{g=єܝzpqqNE. G\>7lAQTŊ tZ 9ޙu:]PP˲./Wᕢ()VݥH/EyDE*p9_sqf بk?4U[R0vFm]3Lݯ g7Y>cWY"Y"\Ę[ $ػx* ̂k lnȫbl ^ut֪z7 Ti]U&b^^ޱc.\0VXbݺu}||?x=)x\Y4!2\rORe]jxBQGܳc==H=,wg<8oΈi*?Ք Cj2۰MN("Ϧ() S10l @xB8HFsFk7upPnϫ* p⚳+sVt3xػwo*U &߼yʕ+[lx\pi׫e \?p[ptI:F.l"|.Ѷo{,J=o39.0"*GRHsax֮#.! Kp?Eg~?~IP|\6[t6{LFĶٵ jzǢO|VH2}oyD:ʼnRiLW'o}}\ d>0&ޔs`_w($sv ^yԩS6thU􀀀7|366v…(֪U07Ԗ"iJZm'$tYhp=my0RB Z`-DHt5`Җ#24Ov(Q;={t[_ba>4CS._8>yMߴR+EQVrp 5Lj;^S2-2ۇ&Y{ϾS#[RH\ j4jy˟M4_7Z#G8psx?|z8#Kz Kux;BI)[BwP?a6^xQ4qxm{VC-5Rz%eu.pJή79Ӧ;G^W'o#tF/Tv]z,rqPlQ#{.p/Zrv=v Ϫ/&6f{o~p$NZ=r=sW Pk?z2iw\#eʔp-At2L)QA(J.t|??|c]y^~rѽF<LRWX:Ȍ}z%3~982EW MssskՊ>wSO}Uc dzOG./plj8jr']s'Vv-&IEa$N V8.)J7*y85L<99gQNt{lҌlg~ST*RRT[&y&/=tUV~Ǽ~[rgi6$yC'3&L\zRx ؐV_g/_.~ٖ+qqq7ez<6mN/go 6m1DoDڴٔSXV fN400sÊsn߸l~D1͜hE3/8XE"S*FcqE5ymxwW=@K2%5p/ֱM唐qg5dnfSf+rvQ|N.9;5mtS{%eu0 )&]3a:GsBiC #dd}|]F`kw{i=Oȿz>)RD,OD53޿ ܽ˃Qרc/jkK<&j0q#73F4q<`:[k B&vyNJZVq=oG!J3♸y;G5pQ>Pq5tG3DQ4V(Ǜ6qlV(6>y3_$o>ܻVR=uIߘ3[,BŠsv}9G\fg} A "A `Z%Nw UHS)F@DsZQhV][޶_ߘ?1uy0n* <:sp{t}e>`{b[˹ 峟~8Jy|wy'90\#eNCFWcʕK~K`n<)+ŏι$aGnCcsIU.Dw{q1.۲2w7 nO2Ot'9֛܃Y^}_Vt. OHto\,@h Ќxϻ'cXՍՇ\][̻fO>:6E8}\HC rMhEB@Ivnnܶkbl"/V#g8Ws-nኂ`6=%`'vqK:pf^~ԙGvw"KyfhPr =c.@Qo>R$ ں-Y#e,P(e1m[w sv=!jV"mzBρ ;_8N꒰Iڲ,DמÜ]^Zv#kV/FJ9Ծ,?}aT{ʜ]emXٵÆC#T!g.d\6 \BM{Ԛ]c6յʹHScŹl^*z }7[nB ťKԩJBݑȂ pwŗ^z8;d= [5je[5 0b Gj/GjtP`oXņPuG~"WTߥPg(pG4=|(nCPx(4Mz w%&q4lc0@O.FOڭOk02䙫UIxE9};_f-# u6vo{'72u_Y;g@ju: a"I ߾|=gլwP)+(6RRK7o? }yF=^LJi@S@AyA< Rp_7xoɾ@FAN x=i̬ݯF֞:q ˲@ "Gx#怔[o);&ȇAȅm \3'X8D!"!~Mᑍ%;Sãb(EQȋ0[1 IR+u0mRk߉ӳ:gyVjv62>RxB!7?z ulVWya(9l6SݨcW}*񏛼m8wQZ[)cfǮ×^ 7&DS5lp1}Pzm9GjYC06c!nb| 90ƫ&5lA-z}r &\s O BU&n񇯛r\=Wزh:rw;"(*TNQ0 ;\D~b} Đc03?=DPLV\YBr@FeΤ`&3/uZ|GRw@ۿzIv&jǯIO}l2>+VYitutdd4Myi .㐢n>BQ Ҏ G}$&B<+"8AIk؂;@^Q Oc4_b/a}Mg>d=L~)}Ӏ&C|nS. U5!zzM>L3i%SVJ1Ė :=42?f^Y](bbMDMFjKFVQi饿-h4G =P}ª m%ju^l:NbMdhPeCbF7u? Wu[1u|Pf IDAT]'H-Rsٕϻw~y@|Jj3윔v]:e_ǷGչ\72 UFv0 wpkaS~J眞#8jX]?cnyu+vZ,__2 яXhn1gk@|`fny"v1KE(E1"p";u^ /1W׫(Z- Oh1,g4Z,ن/{w~+ LF$@Ɵ"C{nxᝀ2SAz앣T`-fj$>y[i {wKx&8>;޶ROqKj7n־r.6?j]5? XYFa3w}ڒZ tIYqJ*9[(oߞ1cFٲeWÙA'p& M˛PT-8IaR{ť]Q-.;u#2BJnX3;geT er@pOX.[Q倝Qܿfw r.9J*`g5ϭ aԑk5%\-i5*+g/ݼq#W}VU Zah5Z6Ƭju# o26nruGB:ׄ?Z-cP՞!!?UUM[=/>̹t(7ea׮g_k6?3ń_ \;&%.[v`})DBQ Ec×@ P,Cn볷QS"Sft 4)b&rmFԭ7eT^GbnHϏhԨ3kq/lu.?hv Q7wD$p{KܿrP3r~Jw&~KJ ..8?c[V.a|}@EPgӧΎa;()f!QA:|w:]}Mgt[߽:lhk冠oGO e>*[^pP(oڴ>nܸZjo><<3gN8QVM7 #w9u8l_`MtV`{NBpl2ږ (P!0i@*lksX.-r[&@{"Cj֙@\Dưj 小S|)w=|؏R$9?mnj>C@ Ə0ѤE;Ӂ6_V,KX #sm"PhdUl A{LB(*ѯ4.$([SJDX2G$k8 ! {}Z2Ba5˨Os_Rw}>jJc#ħFԈRKMFz@%C)X@"k9 ;$@ (yR{(P2B @U "ҁcۛZN;/@XX!|Gw2KlkXb6m[y-Źݘ2n̚xpEtFJC7lXWj8e!H/堗yܥOr{z#D.e6r/1U*Ns}_/Jm#\\} 7y8ثe.Py, g:M!!!Z X`jlppp6mj֬ 4+tਔ+<ҝ,BGFT[ER) 4#"AR~Q%i0o0?| cȉdBДM.re[kiG(NN`ϣi_+T 0j@R9ͻRHXǮQ~ի 8eVN\`є2jZY ĊMiXFĭo ̫28)JNST$ i"4rH4 i62EР if%kW596|;(BhB#vV ^sever ȼ7 a !V}6@S4MӅ-:>vg7Om7̲*g~2;`h ȑe(i3U q*rX)AMX9R+Ui5+uWj8'EB 4E/RPmp 4MO|y?|-1c%1 &~<37QOФ(.lT/bK-g.f ~1xuGkPv,={Ȉ4+[Vjn!oӓG~/QgՕ{`U>~*#(SPOJ師qYlN ܛ\MՕب}fXi S)Euu,&g& :%-~s4Ux65+!N )޺LM΅^F^ c@HM^;{oZ]rDM S~L,}"```V`1J "c 0 MA$h [Ҕ4MKH'j9R*3\MzUUm3Oorx Fuj^lL2N?IʶzoUCȭ#\TT]9N5 2 9n^/lj"~ G~ƞXNNu4? B,є>mVeЄyoI O(" @q(rfbl(JY]MӺ0aڄV.r 6cYVEZi,+J0lVkY_41hs/fݩQW[0LqFc^ݹr־FMKQ}v .Ų5ȟ*U(c)6#j0 c[~4}KXQNJ:FEQ'o3ݬ6(eAeDQ,AToy {ʥGlH 3ݟ='nn) }*xKK?CVGEE8qJOO,LL&saMZ`⟃ft{ߕ"jfllǥM Զ8i0|F6nf/s܏f<tzhAW Q``YeIHMwC_ |OSb(ֶAY%`h'DUs;eY`(3cY R6 \l,F(*eP +-TXU(hVƲ,E,FZ#E3uoNR5 ;x.6m_竩;6xv#5 e}qfWka peY̼,˲7|}XBx`ÔlC0K3Z@RlV+ ߳+~v+ؚQ)c!)tjj~WԬMQ -Q% CJ g+aPiq:'aY3J& aD1 # Ce@)%2Z Vh0٢ ]L}~w?|{n~ZQsG3~հ5RSsŲ>?~{N1F͝l{+cY?~ݸ,`F;)0( yOW.%hu1fY#o}5|ܠua+}|Yӧ w=.pxNn޼ٲeKgsw.ɉw(YYY7o^h4]tV/TNNW#9}3:`ըhE" {Z*D"E"%p<\c(=Y=kP*y>!D(Aw,(_PKOyK\Lٳ;sG*4/]LQĢH(3W?Bwc<]g<WReĈAAA߿yk6o<<<Yj_oUhмAߒ_l 0=a/_OB4<^n)ʓ'O֨QC&% i{2lQӤ~}s8c~2k0l>FMޙ5qqG9QA,E*Bx[z+Vz+UZU**EQ-RVEC6IHB@T.~?԰;;3>; CLBa'R)@BbQPIMc05ɓ)q%Mcu4OCٷ*P wYv]*M0a[l!Iɩ$+&Y냽^b@"o.$Ʃ"H*@B}({okLW5\.d4+ F &e|#+Z, IIʝLF22Yq9B@҆G(t ,, 3h^ih^-(5@ӧO^ina!uttzJ\.wڵS'ge}oW(wv_,ױN/NJTVVR3>ChDkͳ4`k-d2o߾=dȐݻ0ѕH2U+9ws/:$رc/۴4:f̮$߿/LLLD"QzzcRH$RF$)HHR־}{gg7on+| KzLpsmK6\h1a^ ;n\VVVF?VZfbysa0F"LMQթF]¿oZ}-,,D着w}d2adA"}}:o08w}!_N^SU;b:t@ M4 X M[Cx2zS& KKKj]qjS"{G Sl6T P<;ݢ.;7,ضrxʠuqs3 +$ui+(ߴ_^@Q79kr0 -W{fcYh\,D@a#Խk6oW)w+py<}jL_x1 o 2xihã/[PP@E/(SKVWWK$AO*T* &:j2}g1 oonģa5.p:ƏhU[z6:Ms H6lPQQѽ{Ƿ o4[ ZBWvF @iiظC:t`0\b1cX,l75+Jsss[ҒcWv|L~H>U0ImW="3nJXGRo݊8_~T* QcjFE{ }]*M9D:߀Ђ?+.nUB +xyR?TkV϶|ǭ- ւ|@44mg066xUUUR ===:*IP($:ycRʻp0aK3:XT 옐wؙOXY@\YV,pHya)+l~5AvE>>!k]7}l+x]d=f^wHա$I[kȜ{w=m ꣟''nv5fE1TemP&0*`'?qaIइQċOr+ 7eC_oNzIjFoo-}///WK`۷}|͊+.\P^^^^^~Ű]v}U]t(&[%|>_l|KlN:hm{xkŚaT`0 BB:~4l{#mW&ZbQWu3 C]ڸޖQB}d2SR#{^#+&ē*iqJZT.!ur4V|wƔqGoۍ8{8Ry*Ҵq'6L=k@O rju^Kcc'f(2l:rٺlú`t @Uɣ>g(/ڂ5}]Knݺj挿R& s yL,PZCHYz;qp؆7P?l[]մh5ZD볗/,kMKu4gi)g?EK<`;7Ǿ ps"o&-t.V>;zw^Xs*O-Ӧ5hhhZ-#FFFFFFk/\|yXXXXX˿KOOvg=ݺ>ߺˌx<#s%RaL}:p_?!w J~'<!?-+lm```7dє\%,}6ttm\+*OɳOJJ:-%IAB`5n$鍸......ܮ dǭ{a]\VI2{]\Vܮn溵M@0z\._mR0kpqYw2ڔ uW>nˈxz#8[ь h5^[eQ)+Zx>UgWWW')\7⻓-jEE;+^lӫrd݊&/{IښHsKvkмY44in<Kb1bCqV*qZlЉ aeݧ}mvJ[7+k^R P}~nmIC{Mv9>xzxG<}en;>~joC:gܴϥsLX5\Ii)!gOO[;:FK*IМT<%Fl*Ѕl0 =CKCK 5j|L}7EkAgl j$IYzL3'>bY^yt1!B#3_w u\^wzb[i/AE=q2$VN=ܐڔq%ⷾ@b" {}hBdAeGPs7kX[} w22dz9jN=#6#CaIn!_EڤȔ]1W]~n8Cb"$쿏˂}6'J$IjĀg7}f O\ =[ ~-ӢxбcG?G^jꑗhL4Vs\!GH5sU2/۷HD*#p`RT96ՏkoOJϑRI%x+uR@E 7{{S.rM͗&M֫dnNʉOoĭػk1^G>ظ(m)F7BTH{G%ySh7vI%샷E~3j$uDOA>Y=޷n\MRI `H444oZl߿/iOtַ@ @ߐccszL׌#IHH"!IjAo pƷ^T(K#o:ƀ'K;bƩ)Ųj>OOmPbS_jlhe/&b"w{ާ 4/;c9ؓKQ jʢ4@HZY7OZ/RE'5ۃg,Zbo եئnN/ 28= p.s!>2s@TF/4>jl=`uT:tw,.[hM؉Np^!)]kW2TBvB6 NFL>2 1mʚ'.IK8N6꤈.5$IdXNTe_77va.[:I$dYU_?T&h555vqkq9@HKyd~|}ÓNqJ2 $YFc?{MF @iIOOz"PZZƍ9| ׯS+#J޹܏?j%9y@ $x~~~ kJ}h 1kz+^^ \3JT(UyLW Jz,jUgk0{;0`ϋ<]l}LCEٵ"z<ۦPBלOsvM2ٳg{{*a0fff' ւ|ЌjڅE]PU4lÜhhh@3f$ɜHdhuś(kۄd2RߣrT,x}Phw>h[W{=9xL\F-[Esd^h#hd՘ִ7I6F*V`kAkUhX wNCCLh٥tUhZ _ɓ'#M-//!¦ʓQqUѱ@yKCv V:UtׁM\ & @׮] M+rsK44o=Z٥x@C *.i) ʱfw-[EsdSN=z@4͆1]j- ۇL& =KC֣e ٜxAPZ\R5]WtJVaG5ʃ%e%%eUU-gu_I粞\;ZVhɵԸ'-&&gy NHԋ1.-5+ %*ٴg=^p^Zb(Pv1M*hZ4#V W]+{~F?$6V^yxkpT!UYiAO:ۍ١/D˼re{fհGB'>vϺ/3VJK:ힾɇDWgLM%~ԃo Ik}R[l@b뻩UZirUVz8-#:uBhZIT@P% r"#Όtkkŏ^q4o gOGd;j쵺&]v"G o64ς,snҬ|~hƫzRڙ:'=8AM 1>{r_QU8~A~eWKzE`Gh= `b{g}~h}<^ u ߟV1F3[lM݂7h8S_̔ݬ8м0H֣TSK ST*%IPF&<3AC?`ox\Q>gӵ*Rdi|=r AJ r}H$Nr>{I'+%$`_D¹3|>gөIS>|yfG*%!IąL>xN"}|5iTqTq^K,1vV[b7v Tt&7c$<&&hޞY;=ZHM=54/@G;Q$):$b"foOY9q_KD' N&ߎ*! QԢi|?0Z AjGU  >?<;+}yQOlF:j ov}|ǭ>@y0P5ǝkxeLlIR|N(R\&+P2U>4:5{MJKs><Iɥ?'?%IHۓN"enyQdDm D臾U$ /Z(t4nO8?r1Q/!H2)ֶӢ.iG/>Q(I"fv֨sTCL ;;J]cw|BBiqkB|~࡬z @F2ܡsCMPPzQ~]!kQmz*G4͛VzZ_=sdU)W_!HRT1[T¿o&R @$A1F@yy G0ݎO_Sz.ecBxK/,}NBd!եO"Gr;[yYV'-2Ki/ R%8>#4>ȹ_u|NȄx?^̞%'ؕ)Rm8U^]8sVtjŌYs51iUm<^)/XZ@ػˋufi ~`wG%_Z R @RYoaw\_m^6ЀKeSw3GO'3q\y?g8hUU3hIGCCVF/x<WGR/a QeJAc:8¾/(OLq6n\\'T*N>ٹ/"X{,&OFg *ˁCwx9,=/q 2m|zp̍mKs c 8{vr/]!ވ^q9L,c @uxLƀaN<W̱~&bm\N;7ָ{굯+SSօlƆ|C%R)if<.n݊=y`Z{ 脣SJ>xwk!n݊q.&~4ۚ(]b8W+cyrU z[?;it#bw rчuRYwS X' ck'(Bñ}pѤ)T<6BKU=>~mܔ*6p17Z 4\ȅ\1 &omf؈? Yr8\+kc&\̋_kc?dFڳaG,pL^SMd2 zՍrr>R zff{LJ86ӊ1CVT([@Fؚ98c-Tn[f]Q=ܸswg3-ILƠ{A;44oZFvsssA4-ە)P|JމʟR?p/\1>]z&kؖSboog'_sk{>(3yfHu=?b\#Tx9`PsvMp/˿c 86[δ.,HBUB|5@ 8uR&Gͦ~iSG K$PO))DT=*PGR#TuZ*'Ȟf:| xinCwߒ~fZY{ufi7 x V'oxuJ*W>$VTPR%xCQ ߉T?bgמ8`.D:qUYqU0u.);=*z@ IDAT׍g;1Āj_hud&`R-4 3*L g)o[-zVz[_3sÀ⻻fve ծUͥy+ь 1TS \#Hfǡ8\dB 8Nn , k@TeeU: ܲ+RPݏٵwf Ey,1՘ukU=lg]]\]  K)eɉ򕪒ogeeY%>c+7sa|@1cGqWbxg@UJ[kG] L8_.>YBc%Z8mKUɻR!*iM#kg AC;Rm}GevAo6e*yx5 0TR9Lɴ w\xF'C2m\vyH KYk%gf[la& Qxʬu:<{۱ `)r, )L={2ưcgS*kPâkw`e^H ؆~K :ɾ;9jrpq~aBfX{ېtS%D7`uТX rj14J m.J8y,[2HJ1^3V-3ed^;i^HnWcd`\ڟLohȧBϮǨ]Q3z@GQFF><6Z;f%?hyve àQFcN V@3edcdu^Wi7ܷV:hdӮgJE'gg $O9x4= k=ly=:xa]&R˪xdOVYm}?ؙt]{G]& b]VNۂ#sfX6cE %N *i?mÍ|?av7&ޔV>Sl{42h7%EnV>SXk<Ǵú;BmUVp^]Sgߙ;RWNKF22Z;3tVdfY@2;ξ\ j[B/6fB,uTmڦmM /l}egucdr{(;WvnYҳk%,@CCj=acc+ a^^^^ڬQoA\BT*%S"DBA &OGkDLκZL&S)@qTE urfqB1X -EB6b!/Y%]π%5BhHKb OPʯAɄ04*&b!ZD}š2dZJ*B\8@ !<jĉ}fFX Q#1B\VM9q@j$u2l a(yVF(#8Pb-3]*%E!l K9 L` /up^ ';d)0m yOd5ilfyW @%2VHkjJTN-WS^J &*KgRτYKіՊcr255UN6oC?' NF5,ƠyV[_lc#Q&ѠhLqC[7/e̍Vh &U&мj|ܹ3=/@+A#Duƴ_\WV)ʸ4-^ƯtXP־J)]}+6`vXZV-°Y*o/ `0d2r(i;7F @i2KE/P.Mˣ1KopxVƍhZy137nr L+QG1] j>(^^c-V,!:%m!֥inYI%4nu 73gOr~WiKȴwT{WzGj ;wBhZ:i|iʇe&]9q)ϛ26߻i5d{}ZUdv>P}C|zh>So"MJCݺ4ͿҪ *U~ܔCo6R;5xJzgր Mˣή*Ս_C)18H9)@LJY|"L&ϑ[F#W" ES寂Ym7쳁m_6RgX,{a^+ndgRiţkײ_*74ε;-F T<}#ik-#ϐ+4tWvWyRjȦ&JDhŗܩwjD1G56+x‡{"R B_n=':FniijcpܤAʺvj=ckC|oCYO<R%iљj+JKC䯾.04YERҧNcN c!Ns{ID0?*yъʋ"OKX;/WBi{|o>oih9;R"x=.>Q(xRsoS#1mo>{4MوMӦQωI~ >S B\{0P*z2j7EuplNc)shPY ]PFiRa*0+iԤ1W N@Xts7{FcDqS4w:I3ʖJdi;B^"J;T]-6шdR̬թ])OpF P럥h5Ustj=^<揉^B E3&|BOmB^;fF> CG0Gfi49;R4dh3Pil{߆cǎޛW(3M:¬1ⓏE|~hBnSB*c钺8Ya~se4րiigՅTZPsx,lN&aF/٦ssČ ֡F]3jܢq#RnsWQYkZorc!H ŨQQGVÿhPL'&]]]=Ҧ(q^ ޞcҮ+93Xш3O"+R`IQFcs>M'2O<uN R @ZY˂[sWRWIn6f&Tϊ,gGe]\zbծLl'..8wf/svإpfMNa#gHH\Kf0xbڧGxrlhg%4qQ:7Р@Q;VQe5-Eiݼ}|"TB*m=ӳu6UQ;x6=yq\@rƶ1Eggz^Axg6z"/߮=y+S@\;Ӛ p'u}!C4u Qi(;t-Ö\<3 BAq8046W](> aglyLz.Xnpz"uߍ飾1NYr͉u℆}"ƍZ1bƔk-גi=;0ULR`b|97V\ §?q.&}ү?kJ+{ZH><׾K闿>uGrO۠E-6F$R0X,]~gw+tGΥIw&c0?;%mbqs]b r{?]spR&Dn֏@k<35+!Sדf!8m]8XJ/l} gT[ssirDWs(8 aUmeu d6 qru(> ZȿBFX !˩O?>5 &UiEw4-~FPWHԀmO_Z3 ;1~AL&6]Rrt~_t}ϦT}?b s` )A5J|f.Eߝ0 +yo$Gxv͗$sXz$I,v m~r$IRx9US=x-n1hD B$/W|<<-5"I2/w޽{pwJ9I :>VVJe2VmAHI%UbTD&aZPS#%1!^4Rvt!ؒ% EjUOzl}s;L, C=L*V BTyobXJR>{7L* TW T\]Gs ,gIP"HED(Rquu"@FёZts:h|;hP_Ʉ55ƠGH&˨JdЕdz"oݹq9u=IIP,dBV}G`+y֐%KIֈIz4PH1E*n}qm9ZlpU _HOEu|ΦTtH(xP;LP-LmρCg[$5B.3ҭx^h{SG\g5$IX%@qW~{|7www7n|yzRv`~ԗz:4t^_̪=`$IdJiDO}jnK;{_K#\j~PJ.)HUmծ]_}n*p8N.]tR'ʰ{OýTN. E>; cNXi_]y#3{ ]>~g TNO^Ռ<ՅWfhЎQUvinǐAvOy(b[ ׫6|֋ qp\KW&$I9=6JY%Uur%Uj8VmhMgk3}[) . }dTWWmS'0son;K!>@l\<P.^ƭ2"?g}["?+p!px]jq=" 07#>ayf,8)Psz&qkm ^ʰqlT4`̄a.rSLH)Wfv1 ".F->zbU-s8Dg=>Vj>T;:8v6UJ'[. 52d>]Y`Lo[Uv=ݻ[+!sjaWflB\S-$e*a+J 0CO2HR8nk怴? ѽ{fǂo><0]"1^eRaMļ0g]= ߾_W1.z`|0jBr9zr [vt!)%,.c5̌/;1 E 28}.͐+pJ]6CXZLDTaxg@UJL,fZ+6TkD7d۔:E|GWWΖujI-DfDMQI  9Sjgil[S8 ԫp\J޵n8Z?SWH@FrNQcmPmqzM5OP}_ _^l>pxwotw}kk2;W>p[tF{]AluyQB{*R(]Dx[Iٽ[/g/j6pځwnzٝ=ufm~'I >BT)0\sdWÃ\\ QZz'AE|48x@b<<]^ '^r)3 p&ډ]3Dhr +ߝ]2}&P||(#{쎽~ ڹk :d0HeCD`0=0c8\uwٓe9`u h)[er'¦π!ޓQY>8y>_'#tVdf uI7UV}3OqTw¢ IDAT{͙BDOO֖Z+&SO5F}j1ʷLۿꛨݟٲ*Y]<}y}yS}{ * ላr #I(#ƯA'FScgz]. <Cz{ߪz]g+6Mh@œw jΈ JnJl{#~YUcEMt7xvR)_vKۧPϾlP'ͮ1k=`Z؂ $ #dUzΉٰNOiE(IGN^xۮ3˳ԑ7h@2YQ`!juϻ $*@UG_?B)WÃ/+?!DT0Fۿy9^Aq/ל&mo Wv:OU/դO; -j(]$G{䔫jyon%o.ϟ|5gvwy(rrU=[)E褳f %ȥ_٫I=[<%'Yk8%3i C/tvwzm[ڱ7wޱQغnwSYעeμxb:}ӊ'[ԧsQ>Cg.>琢bqWVl{r˼/_W鄡Ӻ|_{[.||LWJܴo(}to|W=]d3ׂ̽=R}`z`xܲʆf~@!f@5Q )Fd6)G5~ 8B2q{+ZckqK qY#QCyIh:f8B!M(!X܈CdjA]miIN~T( HnT}i m䨷&{m/m#|AL/cѺݗ^pHtlS6@Pn< EklI VFL7@Pvβ,Ĭ۳o4 Q`+]=8ѨAD6߹#Qzpv1"i>nM}m;,iE;M Zͽ(Xbv g)͉şQ.]dY$I$QEQ\.yA:?̀wzkg_~l[?eߒce"kFz˼Go xkbּ叟uw ˿JIМOѠCr8IިV< 22t 1 h:b %ئA!=$0$55+fw$ciC6c) { f-?IC=̛Nڢsҋ$֟>gmiItIS@S', /+یMp?PZ͸a*>Xi;N0i46Y@t$bkPG^HDRf.MrȮ(6|1 6cqut BJ&[H~씎v NImMR8s=iJzh:Saέ_475bţ]u& Df|HظΤdWwWE9t4yJڎuߗn))peFp>;z*N;g(6~5 Ss(zhT(p%hi8%]nZh+0L`9Ai;c' KdpPQbQvqB_!۶9-QxqniO.ڽϷdm+VQ,*ҙ1tt8f= hڅ<~zd{M*`}d>3'/c ;FVScz}jfGMُhh%9?v.Z"IIjYd0`BɳnC.6Y#׌@P!( =J0(0+(?xtPRqfm6*AR`q͔Ye!Ap8omX #a 7lYF\oI *ImL CzLHTt ܛq3 ܹ$_#!}Hm؝2I -Wˇ==;_\`oc'1qW>Iz{}:f9ϵ{iq(qt$|Lxr,{eqgҼ}xsZ8Ǟ+qt?=;cfy}Ys4QZ?nm7,1 VX,FgV:ɏ޸J8ۅeYe;,o7pCCde_~~C *4+/z׺dl)/{7vguH/4 g`Vnx=s̥>+Nd`7_qҘMQ'_f\r^vXϦ2b-2IHܨ$oALq1_1cSsh /g^rik_g?Ν;%&wY#d֪ K FҜrM  GGÊUW,@ErU_򷆯 u E]1,aW=5O>x9oj?XDX'tK[Gih~E+DZRopb0????И.Db84n(8|\-pe]sU]_7K1l˯v7藋TuQ ɍ2v]{y28"7WL^hL FIFҡMcԁ'C> :'Iq4}Z[epߌ^o7,nj\qa&F"sۄܫ?|}r#0T=߿k2"}8CD7 У1EHS?ɯ<-d}s`ۯͻq{ZJ銲!|W4߯3HE}=Gq%w<:ET)v7=`Qaȟgu{^p֌95MGDshӖVG ێǬ,Xe-q֊>AG{aŮsQwj+*MƲrU-?}{掕SU\Uo}p+ͬ~`vL-WrX}_j=S=w?ݸ`)[|.Y|U-W{1[+}@UU|K}핳&jY+^<<|Iݳvքkh&O}傧{َ8}jʬ{U\h=1^rrcë_leF_~U֕Qr㽏F j^޻}cŻrU-06~Yl=cxgZ^_yeU-tUKzWށSE -W-n.w&58~]VΟf6o}fSYZKmx鿾ڑFo4OzK7cIX \f}Q hy_ˆ.0|{tGyՂ /7}s\Zs7^jܕVK-/y8 NJ}~zf4Ĝ?ᰖŜλg'OXcҞ=mlx]q,˲m4M˲Duu5MpRɦm{<3:sɶ?;{o/8~=/Z(Iw58Wkzu~.Pz{OgvKn4顒)WiusǼ}sMqa86XT9M}]OxzjMUofYqcUoW4gE/^Oϕun{ʿ\vxxƃR:>w(~9ǯzQK|5gn_L'y=p:gn,+ V=5x\axq4eE7e_W2w?_nԎ;v~1rBk: ɲ,$I H9">dA[4z֖!}O}gKjߊa~HL䘦iRLӴ5K1g}U]k֟X xW(-<]ԥz9onڠiNzpۻg+flTp {6hVWih|58hKg8ZRYi/}bjմk˭NFE.=J MK2/lT4Mm4ѮW &i1vG]h-L-fjڼl!ho,ZPrIտЮOWz>i{M} VtsǪZ;?+4#ߕ<6M{{6U?\Շ>4pBB"M>i07Jj<=geey4;Kt4Z/7$!yC6#bvVo{>`}Z{x[F0|btۈm{Qc¼ }dQ1eWA9nh*h K7uK9seA?d"K?x'w|=gKIɔNpϷ<~zvw~z1PalZ7ھwNO;,`; '-z> &Ioz`}%_5&%MG)3̑#/.t^4Ikoe,K^aߖ\츧)~;' e Wo{_Ff6.Fte4JƬN)V#lNN}Qӵ,ܓL6jYR']4ZK"1jЯ ;y_#вwӾG=Ov= wO R\/"hܷb<7y f3ķqO!fA?xd|v)4n}}ܨ{zrmΖsdjD:z tybֳo}KکSܵ7Ko玥?\vgZYjN,|lVY!wF2j߾TzZ%V7Y7_-R+Ϩ=1 |N.={Ik*f<~ 3ZS~;^][G1nHϴ èi;cflwe 6|[AO`܄T6`J-Ҿo=; |C_0 ՒJr$zl\tv&G ϯ vjc 'H(65Z>)irIŬٻ28gW< Xc}ۘ>ҳ:%XKԞtܨxlƒ55:,mOo'"jX!CMǻɘ sl! _\t6?#⮣{(9BQDT`keKKGAUd)ۗMݘG!f mBqt'۷G !!I:\8sg.][k社7 6!7}D IDATYR?Q3?yꤑLg3 c{l!ܾAH/L;rnc{eW,|ZOt{~0srU=ם7sޥ=Ǧ=@gN̸WgϽx޵S{zuerorNeo[4oHƘ@YLcXU-WsG_e-u@褳g %ȥƕ[ځjIJw/6:ůտ%c^Zw=um;ˊUuM?|pt(@r߼]u@9奝wie {r /[oU--~V@/Z/=bȒ&$bJ0MM:_iAiՖ ꤩ иj@4BB%ImpM=[/FP(XѨ 5*aB|zM`E:/t[l27Z4f P@dPcj1݆/l -!_J-DZ!5M7[P +5 *!QC@qMG ̤4^ģ$y֠Y/ טAd ~ɢѸ$>viI511; G5 b8|il.qMRhɝt_: 28\-NfnG2ZzB(E2f,I^3E$>#+Ifm:ԀG#4 ű[|[[ *RAF邭ԧ}^:^5?7)xA7צ-g 6NsSMIFp)  Y٥R(8E5HO 兢 =p{;8A h BH0m%f2 ġ&IVO7̝<@MDi6gIArO?ٙ Ae͘g<\v|(~?,})e^FiV$?TUUٶmY /R) m$Fueoa傆]IawD^zᨣJYCHCFNi *a3*;ͧY[K]i$IYYYk+!4M'v- ٤wdbfF.R/:Zx~w:)56x~e& /EjSҴ8` 8ږejcZVSxGI e,ӟ3n <٥mi'Fv ҋD"t3]4 !>}H(5 ήX,O8DmA:mݱ4ke&Xjb['˲|HDQwqEaڔplۦ2)})@CݟZrSL^ HLA2eY4MK$용CJy3OGD/꒤}ަx%{d>EHRںqF'aa`гh${OD8˜IEK W g22*LX)c@ӮM;5ՅB!3 `۶(trN=^Vs!dwG"fTJ=x7\YC /:F !vr 70XZ= c7p5JeY$I  PYKe|AT#ZzW[F=L +-pI0ap8,IR<!0_# Qy1TgId}XhY-yL1d3z2.Dy ma=v_QSZ1ާ"R0C5qY%M$2LѶ{נirVaFvi۾6aW apF4Kv\A=)KEQG/#aPKʌ&f:w>.tՃTqz.F@W0gbiM^[{< Ac+ԅ0 ]Au]e05R-ȬJ(<|(p*<>uxy [dWFD(qH|'osR K}rrr%<2tZ:&o9?][0f`FbV.!pͺٶM_FvOyٴFͽB1.O-mQ-4RN2 ђi=Osc[Ш^d\Jv)ͥ|7kLg_cQlY6Hn 6]Rp`>WJvnvVπU Fy+)&^^ԦR n3}? 28$nS%\LFpgBVVR']G.n=ê¶ПAϒ~7+cnmm-o YeǼok|LP(u>Ox.T\[G#Ϩ:.͓6*j+}Ig2=܄Oq{.O>y7Fj5sNxꩧ~0783IY (xS%Rɡw]c+04OV`ư=x>`̙`2K^ԻQaC3Ș.!mT&I3'3 += kjoޱ~gukKA8k萜*ܬ";sU+߃֖p̐FScb! !qTRkgG2^oYԞx_8*Xu]\zws.Hd5َgGբn{#Vܦn.|P.R0>*GM#t#Ѳ}!oC (T^ 40ၿ`<V[1xp^$}fز,qF9 #o#) ?q  |k?L;tGv=?` \~'p_6)&PT_gΜù4OR)|R.P_8pNJ,%CvIv፾Ŋ p/\H .VElV|>D޺a70^=nUN3- 5dWOөKPo[N0k+zvc祉oAI?IImu*t㏗v9~\2d|!/PN$Rϊ(+չ vx֘a['*)R >@OTzr_xk 1a7`U蹔0k.uef$Fh:D`1xmJ|,H.x` 8 cpBS ׀na>P˲cDH$DQwT#F%x*2ݦ2l+HӠ xʷI~@L)f1;eQ BrMx3$[Igϲe1R/dO .˪'g@2[ZaxpC4Qx.t1 #}Q6L $}rOa7g܀vJBϷ; Tbc.8G~;U5-ov*iׯGOw^Z^@$Z 'J݃욚= V;lX]/ZL? 4*'C %/`ò$;]o-k팜H? ێ( 3ּ߭]#c=ԷC<Q\tܥ j24婯g[ è*0i+_v$ CȔb}8K7vbQݱIJwˁN d&E~gGǯ՚vk}U+ms.~ݗ[l? 4JQ ʲ? "TGvd )1uv;̼xTL+ۺA#Zu$^_rY:M?!M~G3fr^__jkk1Q&U]4sݻ;8imfӀofM\Y>FG@ @ A"PU_B%P.ݬƴ/d`:ܲ/~̪ȃT4趼=N.È,#<.R`d/7HBS^P(fJ~vc;9RAvvFYα/\]1 :E# 2+/al."^Sn[ r٫q4X`)eEejߥ MKmƦi;qDW ù'tbFy9y%^Glq݊Yr Tԣ|E];:}+u Y[az89}.ÆZ,+[e4Ft,%Lu{vۙR>p^kٻwUÖd!Ӵ!߇7l߿-$DB>RzUlN1s!$^ՒQgQM8jʥd ar"G={Pِ4O T?믿>J;㙾g8,]{ 7x>:"b1۶#Ulto; $U.-oۣkԺg譌JE3G~nI&ϟuty#o卻3]Sºm\X+M[@*F8BK.(e1U #|L[Ficrr 3<긻2cC{n6oY (|>6Ol*&xYnPO*FF]h S.},5mǰs|A~Ivό'J8D& pb!cstϞ=U=E Ô)LbQYo"P0T 0ݲ]![1UwV̪*EE@~W\9jwlS/TW4xp84uÈjZ}@VV)^g K1R;>v`ꉘfV:n@wu 8@w4c{k*W :@3??ϗ4v.U;"VWW߿nO O-5gJ @aa!!p[nˎ87x# rYQQQUUUUUŶQҥKVVVAAANN?)*fv1kS,3 C4j޿?Uۂ PFynJonݺeggӝ| Wl50?fIªVGIE#dmEAU/!=W|c$.|sEVtw pPx̄lEwY%nSWS.Ȃ ezaٯR\u= C= !pPy8buuumwӹ(u7L9͖\zb1:;% ITpVtFzqxFK`hIDAT‰ Qdph'@AL7Vl-ɋ|H*]wF߆׹~ى;QI$ٕ1X,Vk1wʿ)ZP\d9m˞3=ַti( m}UU]Щg"m$EףD8v_].ůuP >]0>`Nⶽ;m@vc3 d0KÁ@w}Loc6meHKp <FMu ulݶm[EEEuu53R -1K50p83O;Z$SxlQjR;v`Թs?҂_om;3X,qQ&d,h]vt{a#)'=t}XǧGGi-pMtpD.RcBN}u:t!lL$ F,˚2eJڒ UU!G( lbF![g oL!@[`7yFZܰYblz'??ͨw&$alg,'󥓒MB 6~O{\b:TNfit)@Aj!"rE$q6dQbo]U8ߖD ?OV }|O>o +䪠[]}˖ cKO_x>_8꯮W'KvMv^RKnSZr" vuo5`'K$___iuV 3V_Ua mb@vv;;&z.gSYrTXN" QڵMlMkWAЦnZKAKy\.0F[}}޽{7mDCk4t$I>r\"sbOM]DQ˂,Ʋ# ,~HenY/3Re/r]]]]]]CCCuu5U] ݻwG,JpS3Fv鿔[PMWKmǮ"xK9 f\97eY˖Cn!L]|p8x гɉh999pXŪ*.6HkZǔ10TΪWvN9MZ$1:\P6PUgiaV.s'zC>mO6R"!R[̝`.|MuU`>&xIx׳i3sqسO D|~\T:.c竈. {uMMR-)?qtHo(#D X@w ~?XpCHh4@DsN8$]Q6aVPl80*ZmMb6ɧYߎyD%udIGJ Ԓ8ADZM3YZeԊ&" ݣK?n}[F\rN; #úy ׉EZiv]}JH|e5iVkbml_] y4gSSVUbn߱*THi#5B> HDjl q@wÁ4^,/Xk_lm_eE4w1SlR]nݺsκ:=lQ^Z05WB!o{+,ˢj#++T3X7 nI1S Sp(W83F~;JGp ԡ m,3rjbqi 3>ŏ}HViFK2AuBR@F™xy/00j,%3#(vK\!*O*  ;1v ۆ ⚱yQŔn) ٪ 'My;#c",t23^6fpٿce9dUḇ$3?~YR5=H?"{PN `1zU "1F{ ,(ev8+흻|O8N$znݱIU6`¬P6+] n9ZDBDܬ H qTHYE쁟#A|<~暄+!"5TXDCM\:|9QBP[i@#Մ4&d7 ne눒uIiSمrV~wod&2* @kl"!2YFK D&P ?o͵ڜ/o%O='Y3y6ͩ(OmPc_$;ypl7qCQ%IkkT9Zvs}0??_}џϧ5_O1猷1}lsA- bTx<^UUE-^l{]lYAAAΝ;v옓#IVp] ٪4viPdrF1zHRIxz3ܱgm` lcOa}[g-dڴiM *J]5M۽{7]CpwZe<8o]pFwH/ wmݡ`ܗ K b,ЛaH2+/Al5:Fw=3A﫪FkgN4䆾a-G|{,q,hd5Q"HB8 @l AƆ={]} uv9[ю5ֱݰ-:$D@?y(AF{Q+q^r̟ΪN-H[EM7ysCXO}VkM ѩaJ15AÌ/$ KȍN:A^=QxUb[;Q!1BO^v"x޿csִl :tb6יqи,HABB>H8DB@&$f''(PZpm˫]6').$ݚAQlxT?fF~+** 70У(ڷo,6Ãt=/w"ZV5"Q-("M(_g0)CvPOWrA$}rX_ !ڵ*Ν;oߞњd SJ>ӕ9N e#Wr]dG2H-k#=|;~kg܌'|a)R3Bfggө ub`=S@)1G=6o=y2K{,*3M] EݙF_*~\, ?JрV2eâ(lK-H\(isܓ̓E>uLւ˻*\rU]y( $DF FbzlF,`ĚbH $ 4,DIwkjTvپōܮD#H wQ]-|;2>҂S$I11q!L G#P/Kv&v[asGt/\/8N87"$RAIi4Sk }t] nP&CKeoQ>w ^iv+[py:ō\ KB.~$8F h_=OwDLMMUrjg͚NwrƧy?%= B#wN$Dp AyS+q]!**g^{g@ vg<.Rk-O^QRMoS`?BvżrcQ^U4- (QiHJh4\A{&;[PV- & <#=IY7̫4t{+i=YrSZ*w5؝Dkfy.l&rњOԫ]Ղ \Mj`3bR믠uPvS3Ԅ:D1סe»qooo(fHA)"u*EFQ~Pr'IMjU YV' P!&"Q,'(4Sn>xjdfj$" ԫ#y=i/YJ{T'Դ.@eyo%zwy*U<ЫHAs(0O;gĄ2֎<;Gn6-X*DI5|dBNr 8x++7ySԧ־[ݼ֚#~Lk#Ѭ pˉ"J)4{(2 G I2c^ظ.W5Ͼu&Ә?eZP+;toqo|l0`Je6c!|,0Aukl"Aׯ3A>`ìƏnL.ĎcSо9;wlmm-KU0 Eiz.XX*<],Z>AqP]lqW]B eYfi} P   !"#$%&'(Root EntryQv\256_b77c91c9bd4c3d5d*t256_8f263a31a19d7635*C \{y0+4JFIFC  !"$"$C " }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?;Yi)-l`i.J#_h *6z CGuKJVQ%0,2ㅸlNQkБW`GJvSqr5o4bn##]#rd `zTtxRy$(c~BXԜs^(s˸rDZδן%~}&~_sӓQO @j*AQg&cԔrov $q&ZK;k)Z2J`#yqzR_k4gQcbBT:wdhأ얿p^0֧)7uo̍s-q6z#"ĺCKig#qøo𣓜Zϴ?QK_mL\K7kW얿}}b8[k ȕxhBb2X׈5BI+'pV&c=}C]O-(%C~sucsak-ҁ۫6~z(h:қkWWn0ɵi$r0I얿}}ab2&|^A9U$*cr/;h@auT` e&1;Fw_dhأ얿ƞ"o 4,Jvb?/$C\-#3X%Nrkc얿}}b9 Ut5{4ۥ`,r$:_Qvw0ǦrHp89#udhأ얿\xWOsJPu@.p ^3֍E/n|g,0sT`,O^⻿Zϴ?QK_Y`Qskus+UFʄ<mc]-(%C~po=u☹tblrAArN+Kz ѥד<(H-v/-(%C~pnUpG3,ynOLz]t{]eR%rARI=+_얿xA(CGoZeRCGoZeRJFIFC  !"$"$C" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?.+qx[uXMFt8@kVTm.u*g(s;h}KE|\" ҋT#,d0ew^6I$'|Y7g;;c޲O}Z_Euk4b1%$rdr? s{‖_cfw%^li{ 3rISqmϩu]OzE+Uxd5)\W5$SQn)T$)˖n]ı8w}Tt7TfQ,lgNoCgz xqVeX"K2 y{Z㭘]8?f˸2+ eJJ ۺt OwCΝ)5Xo-坹{8'2˅BN *OOp@ ֠D4K o\=cJ:W`Z~ Ͽt? GA;Sk8Iw idDdl[#hZۯm./10 R.~O`\*swCΝ)4nӿ%?ƾ_Kxn{ag[1F c1sӚE'QYPs0o8uQ,ΤU?(6 )»_1gXޖw%~*>5:wu: مYZؤL5q5?n ^Ob%S/EHeqsz^nenogK]Za3B>RZ'}?Jèk,sz{M3.֕8'?,ξN*SO!&kݹx Jc?>9'p>=dՉ_O9ew7q䢓qWGKC6 2IOs95~|ԟ,]R%ȅ7G;0rj[_Lxbnyۻ;VsX6ݗ"yAa[n:}8t g*W;rˌP/y95\OGz>Ϩ"~TqRVGG#U=[jۼ2&zǮIڤ#:s QԖq]C6Q3ӨOIi즴}_ŪG,ij4̊RPiԧ!YB0;<8F~BC"*Maܴ! k|dk>IF {9- 拕k-gLbvD < G1Tst"G2du{vme{iˣvy:TbYDO1RT#/z >kݚk[!a&Td93NV-ΩE ;`F9 wgk-]vnHd yƛ 6^…S2A=9G=}M}%[BF + ʁ+cW/\0MԔc'UᣅN.ۚ2o-.zfhwD́Qmb2,h>R3xOf躤߽y1b6<1rzk+Y%!Bꀵf8Rj΋I%o3C"3ڒ˩8I%/@2yqFq 1`pG"%\=|Uwb$]|Ő(E?}qs=j+ec.o"?`Syd3?Z5h4-<D$.r=q]cBSgbqSƫF饶!~ D4O]Kyk'vC~?\? gul~$e[*R(#uo?+r1xsu{uOuOkCH/y;Iȟ k\fqۜ F[0Vt1ʞ9a* 6 /yL_8GɏV9?.?$U}7CхyGVSTu뚥ޕo#4#mU;AOYy}Oҹ*HOKh!X_$Nc.BFu@#_ MՈlyCћ5N'FAƴm|d&XYf.xԁDr Wȗb܊xCē>-d̽i}פhZz$oC3C">ZOW0C[BA>Wj;xXJ-`:̾Z+J\9R5"ӾbAMYQE8QEC}uoci%ԫ17o>WlrNnZ5Z4k/e1u>vsn5 B0+6dX$9N9'>p\[ IVces2>=*;O S@qO ǖm}%G!}?,4XL/hy 9?ZIuV#4$mYNjyivY[O0;X2@>`=OW?p5Kψ . '<-cQ’Q3ǟȃ괻~,մMZ$ =21?i󳼆Xc3{P_J|Efay2e0\`qN*]Ho3kVb12׸5Q\֕(R4VgWj\jEK.Áǿ\ԴH?9Tc#?v+ X|dqDTkff܄ml`i~I>^_r"(/k.'C)$1GoZ@Fŭ](U6y8ݣ~| qs&WƫoFLg ܿ=j=ܿȯY]Z"ugȘ]Za@oOoǵ_[ n#1.1ggH/$HcZs!vXGTC,*o \aGܣЕԕ> a'zZk/)‡OSQ[cV(alF y؅?Aw'upD- m~*6.mN61IƄ"7 AǢG)ckJZrTS^_HYSubpHǾ~R~)5!%1WxH'Nz+M7GEB-4K!ߜsXZ?Sڜ܁J,ao*({)Ta 6K$0 AfVHbɨ*GZ?5[GFUb3s+ ɓVa]EFO>?{?4|_}#g|ֳnPIx *yWsN_ZOſ#7&nab0GoPK1=lt`01 R-ŶzO^PEkʻpnK٠YφUk{?4+u ǚ^n0Ofyx"O,6HϾkik{i O^2k>-6vmJ~h4{?4+N/r:($\SdXHAy`4bZPE9>Pb?蚁9GB=n ?%?:<tw5`iĹ9یlq"ԇz~h=_?`  [_%p$>Ҿ}e0U,$$=l䏠4ˆK9Wϓi WsNj9zst_HH͞Oӌ_ik{}vsr.cUOzGhեX}292,M!UTǎ9UijK(h_SJ7*;N Ӝf욿 KO]s$ҡMNı2;p gcҺ:NlUAETxOZMiiCj d[9 <}k飜_SQ] GC:PZ/$|  rx~iS? pz?Zm,ycWxh-Ud}=y}mOgoK%H?Q\ (o5bKMFz(O?kzY٧'sQ}M{W4-wQ)7*O}+S^Ӵ1i#OAq\n[>8!9ϥs ݲG2`U"].y V~ʵ|So4CHu~}gB@1_6+{?#'o!>wQÕqz:  1$ӯb76dOb>uIGݡimNs<*9 A iڃ[Hw~h__=궷QVFy؎*=jۚ?SA&%#[FThiABb؏u]ǍoVMXh(a\B nMCTc1% 힕+N@o;Nv>ϥT/Y?tR&F=}o.Ji$ C7zGG hX9xgkӅ*->=ǭg꫻LC5޿ȸƤcRnK!,)󮇘aFg|5ở|GSRXJ7vYYᖰlo=i#nL}ԧtF=W6:ng=ާqDGjQ).x-7m\W.HHtaOƾ^e*XA}+i߶2_0BĒxex?@v7s&n"ؑ'ok̀u<c1 ́W]ZWq~Rtt &1@<3qk$K9U=|}vO,\^[Y.UYJp=k-w77>/"Iп bw{G]>!hU]jV0.a^{{"fR\c0ZZ"ZZrCx]F7m Qe FkY闭rYuؠ M[cf#ԉFicf(Nj_8WT@?O?m\Y_E++h^moN-c2 ,?趓xIiH l~iסO<$*Rpxd= |@hi?g'+l#fΧi&?G`OQT(υ> `I~:a@>|9|4HMzV<|?ۥ%ޣ^9>ރV^AСKK e1=؞y^jSlՊց-$W|4׳wU|?ܟ)Km%ϥUoY@]ݫ55̡Ht4Vo3Sot"?~P9,4k~?! ۅVɹAmG3_k%fFQ; 0ݰ4ڀ>!<|?Ծ9$pYxe}F^[x:hEO ]Er [+_TfG~AETQ@Q@߷o액R&1VG߀57%_A=z?Hė,B?ֶ睇>oc>9in LH]G-gดV콫n⋻I/뫍9T Okj (ncs#uvBX#)&})Vb{ q]1c168#i4?-[&H^AGeRʍJԜ1aǿ]:GisԴ;#(?2{ڼ#H"JLٿyf5C) #◆_>;4#*@nTG}3f("W"?YVMclw_¨/dPv#"_ξEoF~6P&:chI%ƿ"jDtqoξ%Wb)X7D5oaP _ξXk]M,DtB)l~|5nBWQ\kÖCx-tiS8gQ_-m/S$ǪԴѰ(Q@r]]257F=į(A:HV"ǣshXK$ABԊgGr77u^__{/I_ؠQ1\"~,S?i]Rl 6 k{,X'&= p7M1^4r Ot]>FS2݃OӃ] ZeaŊyeHfff@><@~kFI>;-?Iѡ6My^Vfs^a odUT  1MFx)&B.G@p dIN_'\W&^?Եȷv{D/4x'hkڴ+oG_ƹ:=v[;1:cL[ ?3 >A6hW]FrHhv/%y* buD d9'yDxPKo 9"GxH.an}a@NL#FUZNs[jZ+,|mk2uFQiZsn6C%j[ uHTNŝoс\`?C] R {Kk` {J6q묺fm啥'Vz-61֧`eBr^6HbxU]X8xI$XBrjZDl!y:ĩ>g}Vs 4^E9v{_xB!]>]c+—{;-|F=KN&hliPFq 72L]}857tbca&veQ|.y&yyKt꫙Dz!=< X{ =s oI>ѮGmCYm0^Nr~ENؿco?O^/TLGio#L:D}%EW⇢QE|/s7 gyE mӅeSױ z x?UtyrԨ9Iv=^q swx;6: p? Zv0[b(P*\iғM}h{>Oω7+. :g*_(BVV Ԡ3&A| WIsw/uOG*_)jB?a&A| /K_~>1|l[h|;n"#zKK;wԏHk Qõ2zX ׹d?LHY(4^{'W>R&NdvƃZ] SPL"ti6}[pZZq1|z_sw/uOG*_(AԲ©`Hrt].Q<W9u ҋWd!Уߚ?W|BGT5ޛ 6MԄ|ϭY<;GϞ,"oON>!Уߚ@~k㞿]:қ+p>x8##ҽ/O 4K~:OJ?{,Q+.}+Zl k_? n4xW ξm) i8< g/L?܃p?s+3ėF, ;'#?Gco*ɇ+ׇBd+D_%3T|Yfx.ه_pd?GL?܃>a?ċXjK&-n?JƼdckM rH1|dwWp4#-oqHE"t``zOg޿!o}N"oPNHՈ0 ᚼ3AWOEi4ٟdC"=O4d'ܚ랧fЛ%O^'Wo᎙k4BJ LW{ErbMүU/*4t(((<3nhzS˫v1 VL ޫ;|Bf$<g-D^_>s8/=~9탫 Nn'ܫ@rp=x5׋Gdei:qũGP*A }֦ԙcֵ(Fg@TawhfY5! ۼQ0:Ր^hA*0۾~~{9lW%'RIOׯ*}NMۮ&$17$NpN6n35CP 21?Q\ɨo4|)(|~} Vף)iUvv?pk`-6wUC!"96 Xv`RE-,'Uvk+-%$WP99ϵG-/LmP N%#]Z!"E ]ohPg3FW66;Nszqޑ/e71/q8Oh!/3mA~^9eDc*F3Z=e̴%FXwc6t_jd35(R[x VXEe?jJlI+װn5 eFdͷd*2q~$>`R7l9ʌ|?:a3#:2\fuk[Vjy%YMh[0IU|g\h0 m<4 4&nIp Dgdx^*f͎]|, q֨wyU$8i2cFΙ91-ѻͧƗFͯ!S'LFxHhߵ‹WSF Y7]$Ԭf$>[1rd{S e,,K?AhgV pTn\{^\3ѻ~g~t?":s|?@7/5Ybi!Lw|sOcL? rJڐTin9@6W/?Y=vU6W/?Y=vT?ƟlQEb0((ͼ+cƋ`&i$p =+a4&vy\گVRuX=y|AV=pz53 r8U6~Dp[Zy6]Ir>oUhh>@o9]w>3 r?gD?cT'%%w@Z";&${J`=`}1QO"]OUeɨ~or㷥]tOQ? '/PǙ$Wt(GT@ G^;G/o"j+&Npg tǧs?O9G3 rGA)C\EWUbv~] ~?^iRc}SR[1c? '?O9G,~?W9cmJ s9S1MmƱNqUs?O9G3 rX2ӥ[{Bn݀(ܟaK "wrLNa:'(tOQ_ Ǖ&ێr͜qڱמ١ ^_)OtOQ? '?̿E75A @F,9vbn]X]7_1=y?gD?~3Ή29c >h^24_R#(g;J'-./CF?p$;@vEvƤ>^&}vVۏE^}̾kG,?S8W6yN``=AۓHc!#DmGS (?workbench-1.1.1/src/Resources/HelpFiles/Workbench_Window/Viewing_Area/Viewing_Area.html000077500000000000000000000070661255417355300311750ustar00rootroot00000000000000 Viewing Area Viewing Area
Viewing Area is the field of the
Workbench Window where images of the surface, volume or whole brain are displayed, or in Chart View, where chart data is displayed.  
  • The image or chart displayed is set for each Viewing Tab, unless Tile Tabs view is on (see View Menu), which displays images/charts for all tabs within a single viewing area.
  • The default placement of the surface or volume is in the center of the viewing area and, in Surface View, rotation is about the center of gravity of the surface.
  • Use the Reset button in the Toolbar to return to the default zoom, pan, and orientation. 
  • The default background is set to black, but this can be changed to any color in the Preferences: Color tab.
    • Click on the tab names or press Command/Ctrl + left and right arrow buttons to switch the Toolbar and Overlay Toolbox to control each tab's display options. A box will appear briefly around the Active Tab "tile" in the viewing area.
    • Mouse controls operate independently for each "tile", unless Viewing tabs are yoked (see Tab Functions).


workbench-1.1.1/src/Resources/HelpFiles/Workbench_Window/Viewing_Area/WBwindow_Horiz_Labels.png000077500000000000000000025723301255417355300326550ustar00rootroot00000000000000PNG  IHDRX$6 pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_F IDATxyUřLReٶmB% E,r9eٵZ3@068B($H$((\_]MӴ, ,!1 #i'ݸ,aYs.@ ,\!0\Yƶ` qlN`vZ G @ L(8cED9!4MWq,@Pp.#E @ LL%W׊9_!@ @ 8w@ @ !"@ @ s@ B(@ @ 8@ @ !"@ @ s@ B(@ @ 8@ @ !"@ @ s&xL_|zspc?_V]l#@ BprxxG|s1}`cZ ~CDE @ DU<1:UjJgƺHNKTU!m՗-y?&q  Ͻ;WzƧOc99 :"@ @ &" 9ԫe[._]6 NOHn%Z7e{~$zwИV;PrR'L眧@@ ""4[ûT@cg:Г0 85U^;P#;9( ,&m:ŶncLu@ 8א|}~Uԩi-!D@  dyPy%/_tOó1нㆷm(ւCg|][z8 #k<طωj"]ODB]균%{`8t SpYpwr2G/)a3"xegD FE2NQ0 q!,D@0F.<&"O^?ΥSF`X2tr 0D«8,dz6@dhhl6!K K.@ [RX, ]߶mJ,^qW) ZI Fnjpo|G>"@0q~xSj+|%6 I=|'KkU*r9p0:?"^l0r*L2X3}I^S%{aTֆ`P-|.aJ~jdd h%9E)) 9pZ|k/oLȿ=EWN-#}9_x_ɜ fKo +nܤ*2n7:wC_lLO؊v%9|]'޲ 4׍J+ǡ 1B%*H)|CVěooc>ykejYN|um]㿇J!'ιm۶mBAlvJBFiɇ~ =ր8wEqd*{z^v&C;T_c28y x`C fC̄ t{)sh4zjn.p_<<{iĘO}*W% S ^l~b{?=GDoT?1+!S]]2'k}cT&,SfO^=lEio?sgUWOcPpC'8=V1KF޷VHN-|[I)s_}b Sg;c1փ#ose(g?r츴p7OW} JX0TЪ1K"$4MN Cf\j}LEtETgi>^4NEAS;EB]@u]Pz0|u`xо})S̲3C DWA<ħ+`*%Kg}`]ן+.ˢb*yy(0D x oB~/zR p O.ϊTAIOg+ ݣr2 ҙa,)k; a.7޼wn~;h^K5b1V/= H vȦڲߊwˡd(ToG; Py]6nID6ᜲ}mFo2O97,ǴsSƁD4YXDn¯OO~S[Zz͔YgY47r hT|GBk?V3 TxK?)|i<;]9sɻsݬE m}aa̯۰o.<-:ZwrAL`ؠ[PGvD@g(S S&ezG 0D`{5\S1o)J@n#TVzv+k_}/,}\:?'Qe鐍Óvoq7'(L~Xߑ Q;P ل朌M7UAɣxYs9Tt&]T$]&9񵸧*cMFдsxD+qwG},0b|[z D\DsmrV$宰D ;Un;fU9R1c)=lT$a@lpԒ3xfo;^˄\/Z#I#@%E?Oήá&GLjT6m5:bF5& R#L7WJw㺧_ rn8.K L/=X^lI/O"ງHn(G#0?KY;))ͷ 4e\ __ǟJ'O{7n{fyӡ7  S> ^~]ɼoG+$>d2EpgjWHB_  (@¼T:@LOGʧ+˦B8^ bR &0]BY+gMc,eSʹ˶OMڑ]aY1[qмka`ǣ7-%9pFP*41ٴW8_}lA'`r< S+TJOmol촷G[~|lmG%J.)w!#_a̅Ve@R΅9'}zp߮ruP8TvrƎjƝ)4Ou&&][+ j𔍚# TzAm<}*A0@Қ]U1xe`%*0WJk(Я+uE%3s>~ƿקּt2q(A.DBu_bHJP( ,E3U6|œ} h GW 2AF pMgw"uv慕7!iP*À*v~O_r[N` PeĒ dv=; zW\|'0Cm{$!RRrӁXF@cPP(Ra!PA 䟧$Rt6>C|E%5L4  \Yy6 u,@s3,"G6:=1 V E=ɨDljsd0&9Lc@ Tr 2P/s}U\yݿ3܊@016<c-gsOuN|S/=q␙ ,АY,u 5&3Dizj-nX/0S/.4}*'ݢ0R|X@ \ǿ,c3T?XI~wȔlirebäUB ruPdRhzձʹ< Vh_+  $l{T^8p2:b5ܾrGӘ' L`]~7 #d2=So ]OO~DP%ReLEȉh/%UɤR6$2!mG~'f5H8|EW;Ʊz9JXX6M9zcVG,3 pT3Dغd&B1~w>0sت<Hڍ_R KTХ A&Jti@"AA(j <`DLS\;`Vd h,CE ;t x[: .Sp 9n҄!J#a6`1tvF~ dPg4w e/k-/~ۍ?XCGϮp&M e~p;AdU#s"& xmQ$}yv3GŽGqr|MfoS&\}(??37wſC(,/>κ̏ї!'XN&6{rj{3^{y'3K^փ=x]Š&B@?[uM ԳFrO_0d{LpJdVuj f̹/]pgiy^J; Ow;~v-G>eXs%^¹wO%9*<R~ǹ(+-ԙT_d\6+qƤZTb[- Nv\מ2=>NEze5YY~$3M\EثR L4BT$Sp0OeλlclYsYfz /,'Mnڜsq6U =Jׄr(DWCX+l Di\V4iKkssGٺ;}9k AmPţ]B6Gpb:Њx@'/`|[ا)Һ,Zy|^]2^-;Vc:q>ƙHr!·Z  {YIXUS`,l;#kfiC( z"=N~P{^¯~O̲r]?8hbzih ~Xj0־'y2=bwwF2ԛ .9О2ecD'})6-Fӎw69luiMCi$ЪuCi2Co,OBU8& Nv30~w5':¦i=I;.So\ bAo!&RJsO/Z|wذ̻2CM"RPZY 6.Vcn·,l>ߞ29#cθzzq\UnX 8[ ]~֋KjfF ?53 )ʴ']-0&O٨󫂳s~y~0^=pԶˢ@A_ ( *@  B5_Zc^`G{rZZag53CyMU<ܭh^*)ŶѼ"iHDjgGR}~ϙQHs.MU7uܔw%HE@y1M9缨s_+2+v{ l3iz/ͻ79jf-<}^|Ԅ$].7brΎJ54TbN>CI] jZӻm~o_ UVOWZc;@GxU"QV)U+DWh̝5Ƶ`]92(O!=%(ixkz-OvΎ ě1VϢ5C5Aj~E* k3%FK0a~˜x/-zݾQNO`7כd-BpehVQdVd#Dz'lSO!]q7%|VSapZ- =?IQp '|$S@%ea99W2Ꭶ~uǗ[0,c{(&2dPqۖѾէsa;|k̔($Rdu٥ݛ;/ y9L'ˠ]Do>]VwSaMPB;!QIy}}4]y6:br4iviZ(/`/}J,{ ,@"ǣ'uwA-@Ggg끎HOs53ίm *bF_@ 8khUJSոpߦ,9K+L4˵wKas nJM&~u++6So=-O=Fy? ZiÑ~*(2P2+?%FcxF?!L٠b5 QU+EOqW1Xԙ*փ0㯲y-/ q( ڂZt ˰dWd+F|uØ(0XSdvG`|n26 (][;wGS :_^w_ܚ|_xK%oߚPyuY:. J`8UqL5z(&>N+4󕖙M"T6wVPgjQO)2ƽYgw &RWL'04o +@Kg@WnĨC[~ Af4UYssfDB@iU@ n4qy"g\P涟vEMOC7t07]x; x*>Pvx꒚ٰф'& ΃/0==P(HtA %*jJ\W:/z=] #@0dT\@ { jL>ώ^JM&C E@fy]+cFSN ;Ж?5ZJ},΢řu*i +C:s̀[JR]0й׵W-lh'3oHM.S̈p랒,p߷݀NѶ fSN4Xgr\tǿ{_RU)޶CHԙ*ϋ?no=Z֕`q z =lzZW"2%hx?qQMa*(TZ6V3يC[_|#r?@"q;:V1(%,ĠF D1Ӝ )[~jUP(:LRv$iK sz7]vsjڢ5ԙ4!pWY)-X=Ssѧc&Lm~AoA ;.\̚qч_Jvˤqn*q>2)&TԎ׭fULxfίfѦl;K BO "2\LPC6p^Ӷ )7󫏭R%RjBrPgDq8[3I5K#jy ypp sNɛ/"yLVQ*ͻ7Q^zUFsvݍOuv 4:@Zyb3,M:C.ч˗gW J/Ɍ8Fpc Bp(3wȉ(^iʕ0eӳ{=I pTΦ]Gw:)Ce(qzesy&+T)W4ru5!XjꇿQ5늂Q@5\͞~dQn\vv)st}'LV~ql:Pt&)`=U-YP/J@"0ًW_PQ  ‡Vο}USȑw,v*+vWq2 ȥK>1W6~';dY)S;\ziZ W?@P=YϦ50Ù\[~MeQֹGO~7W<ڊ!a@=~*:X2' ``{J8|ߴ9GDJ̫tE a*z&tQ DoU0m0ŜO2u{[XF2)S.8S9wlWfɣjI:;ߞ٧R_FԴ)aPX6OeYU7$D*RL vÍQŸX@$R2}՞rRFF}ėn*P?ZL1Ʒ-twK?YHį кEiK1NYrŬ]]Ii$\sd0D]_5i {K_x߫0:'M㱪4+r>AT0j-\7=T*KO+7=}ꓺL(%J}')Rq5.SbAB "z>XK &TZ:^ދ_z<'tbʱ#aL^$p(9 P% Uӳ 3 ڦG!♵s7rRӦxx Uqd0A蕆9Ǿo̾.G_~K^Onݻ8?O#|{?>a3P)(/ב( L_ ssnR-y/=_Pa3@aw@ IUL=FD+45:UjZY!ff ާ8k_#89GGsThg|LSO۲!t $zf ?㼾9s^-+_8[٬ODMf6O? VOZuqxתٯQiκbPgJd Rc)$[V1#='WcSF1; T6^Z8m4:*mbOVzaxjܓ3N˲ |/]_B: HU>foȌ#>L_s@b&(nResЂUfe@TKA1v *'u8mK?|v章0)AH:'-O,819;ħ8v%"n̂ ,J#V5F5}l\sAA8yq-#7p6m*dai/c`|˙y= 4֎CGpsxmOXȷw?+ 9hacl!E!]}{o;cY;Н T'S@HY#3ohbm7ټ&_U:t[gttDq2yWEb8?ij1λpRrXI`$z0vmμe5ګohV8P4dR{tt)ҩh#r>~`9|]yTkr|'87QAfC4#Ӵu2QIyod^zݻNVzSgNUEPr4Z7I79~({1"a9c?BM 9:So<<y ;{䖩if[fv eq'f*^ >O ɛq3b8Պ5ӗ Kqp߮l3/W̓ #OXG( -Ȏ7lpjSo[:jνGG)ݛ/p5!X߼ǝ- w~ʠ {7_@PLDΛ*ٹxy$!UY'u3k׭8 "h['u]zdP(שH@)?S}Or;ka/| hIν o.;F49WOͳkޓY̍ROflj:#Unfڜ^Tnj[x9p5 êBAc<깳XмuWgo_?umgp\1͸ o7|,rDٝLLߋ GE@>dozP7q^Z?FPe)o#Djצw ʓ)2rʴwX}M]{ROoB +)S *s__fogS1Zwsek_<o 9}lqȓ}|᷆շ7~Á,[@k*KӛD}_oo7T;KgADr ҙ2}6`}}(5Zti_@o 07ڞyw޾ν{}"=|ϑM=^9|pġDGޢ_񾜶+|l7V%[OA%ɧֳ'&is&i g,[lcnD*tfܴfuu海gm1s%^zx[hZu_Q\5ӝ8)XpE">9ӾwxU奈SYh9\O/&ܾ!-G~V˼2Rţ)۰.,G s͔:if"GˏL35ϗ<;`U rv9sѭ_e熵ps!ݲ)A<*a5<>.~a'F^q-<4 PCՇ_7+?0d0eO|5n yHY7OSn U$:9@<Vzny[НXq'5Txk۳Xx$JPWβJAklew_P5ƌގ#˞.PB-ʻ+j>D/^"CӦx+xsF=B@4]?_#<-弃s[BH,ykɒÜrBr\/߿aú+!@E ;{fw@f=;@&ͻ7M&RqAbYQk~a7'6dj'+9p Δ@Pps?"J= tBvm[U`߼|f2ÜԌeDAW~97y!"Cd֟B)lg>= n DT})@}2hС!-g+rs*QcPΆ;vT1O-cvppqe Ae|\F+/vtlZ6HPsecD92@D̉} ps(ҼS2&$o=gފXDEBzx??@7ŀOYx=&Z1wbY,r`yLmz;W!U٫Ҵ)-"PhS}lO:DwAMyW.׽93$$C*7u>z e5a\8%:`uP>ŎI>3Cæ°H$BJxDuO'fcމvܻ [;} JA+x'sw+(AM ~ݯ>)/s2Nkp7:*D/,Q(klLpG4=L x^tx 08PXfHtRq<_TeT[v[7jҥ`x:psݺ*@s;{ @9b9@EL۞TXt[FsxUmmaիGu[zxn:- '"8-<|UC:&76W0yD`ٱWL*RUPr[^IW_OW*/5!]1ƣJO#J0mz}yjԮZ{ͽg&׬V%a+;4Ҁt{sEA1n9n0^ir&۸*( ݲzUZ 5NTh C7zrx-J" W4oxjn!/(;p_2vd˽cxv^q ',dRM|>_iiieee0z1ت'ɧoW\dFܹWx/{-)hYZ)d qA$#voذǙWIUwșª0 ^eh]ssÆQ1nexTc#WP%55}w/Vy6wֹs!?S֯~##I^QZ.Xy9!De[RRRZZZQQxdY?2>Kޘdܰ3XaOҧTGCtu$9Z2+dj:wP$MpgjۑXOr8'Ay4oUp`>b֎cx]!+չuzPgn%MDɒ䫙9R7? "H4Z["9ؑXjmVSl(mGHWJWw+ͧҪ\_T%])`RnXXOjXGibͯ4+nm}[c}C;!`ͭӧUE(9"曇M݆i VxkC2%F}hN^ur05D_ۡW[&<^Kn>cӁhk)7[7fm;;ԙL|~se)LJ%><#^r6$@kkN2E]&>VkBO]뽭ЕL%\"ȔthOvG bf૪'M'8`:lkB1 %?=gH$IU򲲲`0(2[ ww 2pOXfCyϚ<;@Y c1`%Vd]xئ: #X0#'7q^BQ2…mW z WX/ ɛky"8(`g>x*9$gxtP=}F$a}*k6$ׄdw?n< $&O eOͯ~3l& Ǵp yRkCrPgTQ :(t%,9i:(;R-VcrѡCKٴy<5rhJܝk^R5OM7 8U(!(QT%Ra!ɯwc.%A6 #J`-[OAUG 3Rw`O7f%M9w+2sЇѫˤ'Նr?OHc;^4N$OJbt[P1M1^So]V%m;:+xG.|D'!@@cImywV2C*Re@ Η$gS̲mҐ$JFUG_^#t1>~PA*( #£iV|00 ʼn@ L` z0EG޾>w@'/ 6@2 S,9/+=ǰ]|ٲ%U+VYZϓФB"wƌvs5;Vg:{ xyج".eW_gO{1ks;Rq"fD*>-[ תkll|=-]GX*h!"E"( kBGqN خo)Ngu%9F xPFfj qw?^[[U"JJݯuyrƔz̰bvt, = 4aD,ϪJdE)١ (A2 ԗ*O9I1m _n6[hcZ wn(_NT̰t!.)J%MgD5)*Ta$Ӻ9r (DW.QQWFgVk%p=3km`~ά֪pDPoLΫ8@xmOM1^So,]ϲ< 2)ɖ NU"UA9XJ&Xv[dFT2 ]5n8H5geǕ]ëpy`)HŽrdF< y_DLϬJd bf\6gON| x-B  ;K6$9# ~a1 g%s4\v>/]z͚}kMtF_!󤫯~gg)Qcl3ouFn|ަ&k0( ÎMXO~кu4=Æps376θ:6q{_SSrJ$h _pkk$M=[+#s֯߳d Cdzk+畈A%Kwۀy#74{mmŬYfm({vtݚDleAJ^=g+a6(]D\t AT~uz"yZs,sQb(ӁJRa|uhITn9̲ù{9#(ё!D+K0Ӫe(qFNEBqe>ZwǕ9dA?g^W(;AJ0Dړ]b$ D" àN-{@"E 2B5x7-C!Q,~qZZa@+)wꍢ-0o)7m_+*X;IJ= )p no‚3tqq׽)Vr3> )qǧ+3(*fƍtӸ q\("@ 8B V=3g./煠9Joj O.[sѕƏ-_^ܭdڴID; ][8t S=ˍry䜒oMdavTe7@e_Z ;nh֬. 1y{VOf1P{s8Ϛ ynR^xs^P>| SEO<-HJ|w~ŋ)2h,%/P2m-+b( F[F!0 UXW͹|.Y?2"|/%aL酜£m";z Et3X|mSL?0kꍢJ "z%B<=|t-? ]sNp RQ$]̸>n3^ Pg?@+^md?LZ_Px/,瓽/ҪU2 ]М93>̒uǂ 'A=4D"@(C@ ^Ғ_62{G䌦_t8`pn &#P|{W˲+N;`s﹧{:@ 3?9nAXfM7Z p  Q}R1؜ҥi ڵ9zwCs!sS<\ sݚ5jc#|Uv] PFAWtvM_,Qnۢo~O&E  3RB6l 'a  P /lsc"s~IW]< O2-kV  A Бi |)w!/D<.S| C㎁DxvDlfd4&r޼(IsOв}_,n,[qYb7{,Zj d~^} g>H pw-ۿwE2@1t48?\?Ѷsg#Vl5Tϟ?jk?4c1Ȉ>@Vjմ+zk٬YlOo_,'ʃ@ 0BM"LL׭sBQ~xe$_Z?pکZfՏP7Vzpw0W(!E\jزu+T!4D[EP }ͧ.8on͚V*iRbDFDpwF9n@ gG@ ي7_J'5xܶmNmjWw]_WWaZN2ҠH rۼkݺ<ر×1=_o׭#y1 *5j/4k bՓV _?)0>슦@\{ 6dW偨,. @)b%?r/V0߼k]de;AqG~ݻL 2^m_ɠIqs=X,O`pn ,)@(6E,6q;nPF&QgyqFrfQJ)!ĕN':9!RMt]4MeJ^f(/w_ْ%9oݲ啋/ jŊЭ3gvG// ZnҪUЧIp={x EKstf756DN1v__'ފoZV)]='L ޳:;k׺uǖ/_sͫnwgo٢COټy67m˗35kJ#Ȉz+{,YI;ԯXݤ'#֠@ P"RJ%I4,˦i HNrIItZ-pLt^➅*$Y%I$1&t'KF*xt]O+N[}8Ha|7n f@Fc{XG{Ht]'ON+ j9,]C'vt)ݻWiͺIՕ_JÛgeVjUs\4XpNk+_F:H)?%OIoS- x>1Vzm|$gOlf.}T{- @ g""˲k(J*rq 8CJ۷&mVG)~JUdUQݓ.@4׸ky*4MS*`rLAFncǔo7̓IKrޝ;v q_nm{D pikW,>} 0<^if OS{饵^ =4TMџ׹DQ !7|D@ E@"@$Te˲9ozn6;#'<{+4iqMօ0dYfItz]_@sq `c]{S>$X0T{S$i:2sݺ {RQ0кztyhUرy8{oacrq=rlDۏڂe ,[x"StDeb@ .u;eٶm8Zm]_C>柦0KiX$AIE4Mt;_]`"$;78y c;;1UTkk/߹38p+%ՇJ:N+VT.^驨6z%cwaE" sxӀ621"iohsL:Γ!@pv*91@A9,Jr*_k-h馮9ԍlO) A뀉`vѵn]ɝw #4}tfI ni)`~ s4<@7mjj}G/fR1>2yr viS/ٳ}&# j5k/G;:~}:@QXIΟZh5 솭\&-C1Y򪫴 f,/ZE4JRDOFJ(_U\9{{ćT^vY]:[RRRZZZQQxhdO퟈EU Xaۓ)¸AYQHg.T*vUǞx><ڭdG juESiTbU?@;T86RyF@.$ VT扅ny y?@?@s 2!d *Cht;1@C,Dd\RݜD82ʫeUyvTmLn[œ@10r1W@   r 55:u*b5 ڴs+`֣Q*I>{[}4@ y@sP3mλR#6(s~λœ+ cvsN H; IE@ 3E 9S~Ý@?$uvDY!Mv v8gRn@C4*D? @T@`/ p`eQ ̬{ؽnA!0^iP_` j24 @h yWxA?5DʰH@ PP%Nu8pz~rn1x6L6gxLx'J*$V"}a*@GS~T;6!fN75+K˗?au)yATwq@_|B )K :x^.5;Q7Ȼ@ "@0I˲|gf;E$?Om.x }HkQŹ8H^t `{U-[/W̟_1o.CvΑORD@ g1D4aBcYu'K.xuBSvE"X,JLtGă9o_̲e*@ b9.κ&JX1`/FC,HN(x]1yE=Jto=x9J龰ֈwcP([իYG28y2S [T_|e XP(DQlfTɜ7:{ބ0At#SF¼#&-!ކ`M Mc)G*CpHy/s< -4Yٻ3eFB$I$ɲ,IRZ\(dc0X(t ;;Gh]'J:X,0A㘶=7Ҿ })s0``fc"* o< jYw44MIRD@ $xB]7&^)1: `WcUpw0v`u/)wW# |EsmבVYA8tKo7~ۿߎΡQy&!i&DiL 0 At2-D}3fxcSp8#m%B$apV.ă=RvN(Apv۲,W0 ۶@ qR}g{*_K4fu"nohuNm4z 567B\ Jkjz EY!`&5x*xOox5KR* O>UE.@; pu"@ n8m[\m,{RTX9R"|ozT$eF)Żri g"I4iEݯD>{}*׉&pTN6d0hn0x'`TW%9ީ,R/p#/: Qr7A]dD)Jk"`Dd\zAĈ3z:lp8g]kDv{UUPtb(&D9O% @ גȲ,EQH`I]o֒XGEz3%+ )O+לp:UueCl3PmL)eC:t$ew>nlc 7"@ +Yh ґ\hVdPl;M)VI`;ੋT_c- |I"@poV|]FeA*SI˘Ne<*VM${wkvu U J&@ 8w`g~֭]&GN$X2)6I$("$ $}&^@p$I$1IQmU)qR4~۹QKҀe96'@h=E g*!bpm۶m7Cև_ؙjq#=?$Ͽ?|6bqGm]6#"@ ٳՙ33Xe% X0~ :,ki=8iaF"HRTY ҦT DXoD>Η2P*!]@cZgVZ׮/VrSx0eW'3X@ ̵7gHڶMN&0H4ʺ=CAli;!J4MK6%\}elA2" B }~o{hz]]d'/|~_4IrC5ښsUUPmc=##F@ H'Ik\s\˲rILràᰴokjbGt%BPA$y.TfV.X 9y @ H+6.aƏ jOZ\5DZDh@0 :|-DR<(t9vbs3ko* ezRڂx9Dg +wH.HMovm%El_$ D@(qwuc؞A]t'Y){`0TU58ZG Y0(F@ 8ȳ)rb &<q#}}M:z57 OAVZjkFmin>3bvs]~KV~{=>1 T 5i{LE@fA˲dqJA4#55܌Di0"B;W:UilTf̠9Ŗ] 8Lu4 #H»}>ϺR|S2t(h^@(tAw]Ro:_ϖOUWfUUK}G׬1\UU/Ӯȴvg wS>P2~^z{vtcHOPu -+++YEFA:gk] 8T4D"a$b<%Gh#屝Ȋ&Hw*֧.CәtiB 8u ,}8wa t}[`0iubx/Vr㍕T~_)Q FKefB 8Eq lu|3^>E/v`u]UմD> @E Ni@X܄msvY_`0ǎa{;9zt:UTrV^.WVJS(&i3g* }@pNiwJ&aRIn؁@<\6)I V @@~ntp7W:؀MeA4ڰ{{iG0^`HuuRjjɓjVQ!+2 ri pH/EFs yP:)N\_/rha@('wShXfWûhM^yML] `uINMQԧJnQ@ {qPX0⠻Sumc,F47ӎGc1}}aJ3fze$e VQ!UU`P*/pps/ٷ Zȴ\ L&1OIՍv\5y ߌD%<0SP$IrcT)w?:v\SKjaӧϜ9FӴ{ _^ )F}Xӟ`9&LlYVfzgW91MZ[iW%uu~OILe, kk)Z4 ʰ@s_+b/eꀴ0 d":KG٢9}%)āZN!7 Q+E B Ԧi%z O՞;vk w-㥗R.Dda 'vnio'bsUikS[Zv-%^gL8y2ΜʸߏB~xPQDAlZs$ [kMQy3iD֮W(|u8@)m{֬Y}Nxiw}zJvuIo ϸLJ^M4Me]s7j? jU{zqe믗> 2D.j @p. ۉuH=qr~u?aYmoWcx$,@ˢ9^:TWÌδi*)A}>@ /$0M3JYō$kwU\iWst4P$@`r2`0sڻksX'H"$$!P9h9O義Z‹?ѣCuuսUu{Y{TA R]X*Օ\*40Ƃ `qa&l\.0n3-O;#uS\rE$qv@PQ9'>A"!K'_u4H޲@gD tCmC0tҤ)%Y2-64K=j:Ț0sjy_`\t [v`s&AB<WS;^A-;C9)`M;un e!( íXtƜhӖgwrn֜G ߽}PLZ:GƑ(˧TJ/u$':b]-0j,2EzchY~y v5ly $O3ȉ(Ý#vqyd٬ٹ9Xطp2PR3i!.HHHm74~E5.@!D shpkg_a@SK8]GÊrB9@ѡΡh܌9! w^a 9+ͮ-Xڂc&>QY|7=УO8.bZ?te!`8 z<.ediڙQatgmfgNDjkyM b2ؼ  FBhR[gi `UdY*ٰa6.Ue0qmpuA)`ѽKJ7K/n7@0uћ԰ -1?{T\Z2  $YxxkOtlX{[2̺{$zK+ilk:֮XsXû4Ω/?pՄLF7oz+#,`݌*p'8rsr09RRF $$2@Otm2yy\6y2\4࠯SFF|1aiFiFWZZJkjhu56KJ^M ذA7<])0 `-M`EceÆMx_ˌ Ryx%rr8,@Yv=:WW_"M[f0".CYn`Y"A1Ԑ$quN(e Q@%7.QBBpʹar HVѱ3aN)ak aL%y+0ղۮ~GPg=h%u9O i pg~$ykݺ%1-ڴ>9O|GL~h'e'O ?_<'$سeٟڜS_.= PV|AD?w+Zn>l?g}&(Hjjl̫g~ɯS[/¶C( V>O,7㤱~kkG5oҿ;:\`y1}u v?y)w78욏yc #|5ZOz2IlnLFY'^T[4x5?kO-(er$U莧~u.ў 㳩+?{GGLhdžv>z_ǍC/h2sgp r V i4hNO"`q:xtdTv9y_4|no1In7+)!PYUrl^!*cXXIMjÆMx ҈=ԫBt.5#9ӦtnHNJ,.\4gZfƐ(#SD;d$ Pƀ1NLh v@6)L"Pv.ECHXAJAM8CrD,J`6l(qtM+c x a Lm\;<&Ms⥷f;1JRV՟*iݩ \{Gp2M6m[?5Kw^1SbV9tg9r =-;;[[ ˯j'Dt>< jl<7kR~LKyŝee ۽)~7Oz*' @DMfY9ytǖhZ2Hap 7O^tkwCwRN$I>]ID0DڳɕouiVG㈳HLok>Lq%ܶok}C=o6&p8>5̧DVwN7Nu7l֓E x4 Rh4iHES>3ΣuMFƇ1  `(/SclYcB@!) $`I4%p{=!goI#@ׁcx8޽(IѲ2ZZ㊃nrذϷE1.b~V a&l_Hj ,"qU_h ò s7eyfiUu59E}T7@QzRT?s Y\(KP(g 6˜1I;ARzrDqHTY, "D z &Sʦm"&!d`  5X h& II +$d % r8#:R^Sc(Rc$㲄0v"G38̩#W%J@@F Qb&I&F|KDĈdCAэ8ׇ%$39PXdpԋ@h:i# yu~ݬy4teV~eEacWP0htQkF Ł OPȝHDPR;6cE+V : N"̟q qI1ܹw.UάJĤ*Gh<3 pAf2N@ԗOET-?4UV!`pXOW78}lz+F(i^ -.V^2))- Mf798A4U " \I̊ o\tUaoAhÆ qaYs֚ELjFnHX2 |KdK,v"L$x"ހ_@MD$bp@h0")T$5\trrR#&7ݒ?[)@CpȂаJ5Kԓ)0nW a㟻`ʭv\4`~y6FsN ՠpߨ >Cwq8F4cts(0͔jNX2Ѽ[ߞ(sR}}C{_wn& #7 #'MN,rpXvHMh)Sqn )cRJTW>wI9ՄjH%.u[o.(t(8V5"̾Z2\%3ștTL&a1e;/W aUU '|hxdy `ngo^4(@iI=TTMQ((TrM9$$eb99+,.g4-I0x ƒ@f FѲ29#w8v:! E0noWvdB!3?"k|16`?Y6l" K5Ԗ1j` xHt $F@M{u9Q@4-t55B~n'J@$L4᰻9]"` @M ))DtRSlrD#28%Q(@ .a8L#[pgIa3gGoԫGcsKLw ͩ+jQoaXJ#c0J)·anIqm=?.3M$qwVո\Z#+w6U,toYfSU={x11#UM&aQeP+g1(3)!yÊj*192QşT`eB[T,uBEAwB;p-rSЛ_1gI֟W=P.>pº$0Ӡ2F$Fq}O_kDc&kf?-nݶ~(:isϿ/-A.p2k ?g78@gnū+dTK8BNH)'&19J(%Jn>D\ODc}aUMu7^_ی?5qƘ%(=@$+R@4YPu(º}AȈH BN4 !) wIfQ)/' 8Iηa}%̆  @ZM Shhf F1"(ayT:90+*qEf,úJJ rkPe88`}bd]>BLNm3rKӆy%'K2Aب:5E%މfJfm| 9ȟ_^uf;,3ʩ/mo?nR\wro ?Iq41vf2-S%U^WP #Ef}$sL# 4q'/ (έKsjqU͹au$5>a~Q!%K^ -J 9GN3?/oهvXZET۶;: nG/c*|ᡝ/cR3o\Y!r FRJ)@X 7 ٳW?\-#3;0х,Ag-;-<ֶ^ҖL*5o~j^O$ƻ(u:/` y+R , .,4ML$ɤ#p;QG8${!J&A2 A~px$$+(0 5lذaMcil'VXiY,iaF yI#@;0Caer!!"e c2` ऌ ARD c qBnK\`Cu4wMPMl^}]]nB~Xt ꊥ(vxʊK HM@ 0 dR)!!sAD$1E`ׁRוR$[Z_gwL3Qeb!<$K& KKBJ:HY2Ą"S."=\gsUhto`(A|!U Ŝ"0")qf0h/)).rbL FI9\dAGR\t(>7N ` 7P4IaX x\f'0M9f nN(B#'zG*ll2CHO.Q20f'  iN1c괸@ZV $:gc1!TUN&EUu$wtT93!.qy3 H(d沢"1k\uLm[3z`e6lD ^z24jGX%GsTK:Bp.ZHDR;qcZ)b6l[v;hՔCqCMcIDvOd,"(zT"IEc*Ţz&uơ܂'U@8!rb&cqRDBv*"5dbuh,iR$h&c1"%TPƓI1@jjXT"`8'UqQx.hc$pt{ݎ>SMĒ)rAry<24S ux$ )^)D$1qHR Z2OVFddH1:^SшFe#hR'c 7SxB.Sj81x.&uexsΐpN ՌT$$B}Ȥ,?,pJ´!\ǥd;tJ4qDJ,ٙf(>s`,(${>Bktp7=h FكNjÆ  /nlYfjjϹyRȴ^4.*( q c$bxw9޶^ԇFOd@1Ƃ c6>@l@pF)@WDqҖ󱗬d*Afs1 D'BKBQhc='L9Y2 _na,N[<ǎ9# d@tI/isf/N56cxg" du22If/.@r.&hTN$hT4 wr4͜zV?;I0KDڏ'o= 6`;i3 ɗG9`C/ϕ8e&q(@vʒml>moGpb4f XbQ-(I(i:61m|~;+@3>{ܕp=0?C`(ζ@u5/Sj)zP`*CUHęL:4K3ņq>0Ib.`z$;xVԀص Oe2(E@7{+$l0'4/Ow X&  qEl(fTB?0LbH % " NZgZ-wtxi_2M%\!l[+ cK4K{#aÆwg  JBP(cLTUb")r#c1W4*:8 mm(GAErrH(}zĿ2^?O"sӤ: or_ǾM !V|7FkgK8EQ҅Elذa6 6nxC1F!.G0@-Fv/ J ۇs>BmpI,^ aÆ2ɱ,9(^SQ|s:}_ܺ.GeӔ3I& 8Bxt7ŜΤkJU0dm-WlĆRJ/p$JE~7y2`P5SDB$K?t8N0p$م1 32.`ˊCz&-ˊLj" {HcxrE,=,Z e !u]u0^lذ=!@B/(Anuz|@ PNx`"$ !t:kkk/H0Ƹ(FXQICiqgg',WUU)rAZ%,JbEy -R)QeY={`$3ғh Ks~XqGwWk4a$AA"2~ڋ 6`B²N5!gq{Dc& Mx6C`֬YYY١Pr>۶mۻwgΜi㣪oQUUuWʬ5k]uUI' 3ҢǙ2!tuIRGىGF!0!4KRhK/NBGټy  =+!IH=~i5`,b #q"!D)cM#e++oQ&J:aNw5!40 +oredEkB3|1O0< sʜj+,{͆ 6`=m {%j1 g10d#0 vu]4ekSa„ ۷o?x͌l^ilƌv yyy*S QMw^Rg$2<%-_r%I:@ `TU喇TUP:;&%`4yy>A6미 1G|,p9dfbF8=EKE WE0 hfoo;Y0~>;؉DY9D1'?|ZN&999h|ΧT* jc>-Mq4)؇I ȸl: >\@]]]3xZ=C'8H0O#EhBl.KX,J ðECp\qks%BiZ"p\]p^bڒ[OHBu8ձ*rŜsghoGDhfU4-%IY!}> >#~&$^-Ox3t<#u鍤=ӫ! 4MiEQk&-ٙBH̱Egg 4OW|HS8'> #_9bǬ2}6O=4FH]Zb 0M0 QW9hDF#H*[,]]]yyy&MgHyXRx |>$I(QJx__\q3L߹$|SA;v(!c\ZZZ]]mݻ;vlddĢH҅N8G\U9eLR|ٚL9# GkDBh4>a&lp݋K(˒  Szbt؀0(s -?slU Q~?6;HL(CnFzMDn^K=B pdWWWee8[;vl#3g ?~d*랙8?#X~`gcglƊ5M3ӞK>j{W?ZVYԮ%(@ ?:JKKX[qURB^Ρ %29ǫ`(b{:U~'-a gmw*/>OYX>iV,R:Rr!dmgQSӬ?ϰWMBF}Cba#h2f ( H\)P$;;KDF`1N .8O'1vڃcX,HPBd0v.J}[CrxZİ@( H$ `GGG^^';vtxx$??ߪZUUe /:M:00888`+de)eg$29>Q@B_N$Bt]_hW\.Ki clmT6lDs7+ $~|0o݋[Ñs]y0hGY{kg?<#H5rgлu 0oiNL@d?>R}rq]0C3LDI9bvf̜) !T;v/A}>_n^.7ٷ8OJˉT4L.+ӡSSWUդ1@5D8;eG,KM5X.,0C7Lƌx8K߳sM)2 #߿} &(&T$Qe,nC!CTݠҔ 1\EдцFd,n^CDfjTӠ%|ңnll,..|a߿c̙8E)mZ B$Idi35ͤQ.+2N)?)H,+!QbfXX "ARdDu B am+¥GY BjGGGii1Ʀi޽.}W&W9ԉ Dp(Sɔa$?m=IVE*QJLM=rM$E'ab )g bPXLXv9.hkH$ƍ&rآ֭[}>ߔ)SN%hMPx+4" `( \Qd+_Ns,Fa;vѣhRYY9y`0DMMM NAϟ(L!:Qswl*1u{޲l+BkךyA ӱϘDK# 7GёaUrrs !Cm]&Mq:`e~֊,lL?H$A rdY***uc<889cƌ5k("˲} Ƙ26<<9S%wwu ~p>e1X^zI%KX?jpppڵ㫮qM4 s,BaÆMgǜ[7\׍S7LL s=޷G\o)!`4ʅ+nS%:xaNx%ssѾM/:8t(HC0t "!N "ѡ0S4jھU۳.%.3`Q$Inj躦 X߂DѲS3M, L]#3(ʌJӝpƹ(--ݼy$SNu8M+VTKiw6a%%WO}9/Yzk on<2$_Z4%x?Ȼ52D<ϪvCCc7 Օy.z25qt㟞\?]5! y{%Ijooollt:yyyVT*SO -*(((..~D nkںٷ"_ך+w$r/ZpAmYNP;XsC{6qɥ~d B G?[M_{r57߾rORm۶uww/ZH$4%I/900}k6@K[)7Sckh82w\4& difiIdzYHVLpvNt>\"A1`4黎 Lr`_7*#p0F,ڳ푇٭_I!n޼yppp(Zo߾X_ҥK Qsvv=h=+v흓C__zC{b;aW OZd*' AQ$gFuݬQbw=k hRjeU>qbI-߼h,2P9P2{~ni' DX1'&a|kزe,%%%VxcW^y&MTXXX]]maӿ(X"+lBp8DQ+v pB!Bᑎx,nUU\SSvGFFFGGcǏ%1 ӇǃO PPPũ577#-zE墢"UUST OwBhtxxXC+ #yeeXRsI>Oeh$73)v\H$++DEK쳹PQxJao-b=c}5P8Hvvu&q_VVV^^&R*jjjiim CE$RҋghzwD_뫫}㫟iˊU_ɟĴL_l~#~8օch&Zx|8 !q m߳w^KlYHN cAӀLQ0=}pv /y睁@0u=CCCCCC3uXqD˦~۷>v1-{Nٱbͺ?>v,޵i}1O`&G[ņJݯ?챥1&o?֊^ $+?lJT^^~zQ/bۻ|[\.˼>^q%2h W~2/JvOv0dHN\0)kM,9OܱHRuxg^޹x#6gv]`m9.a9gAźzb e{  q9g b -;Q꩗~39)G{䕃L\ f(ZѠ9pgװ9yw# ̌uD`v)3HpsrrB=G>|-[x|( C:M rnt pgY !Fi[[["2#B---H;++k$p8l EkkkaaaNNpLv{<(Ir"_WW^^{tZ5PAcxZղŶ ml2L8x777moN&S$rO4%H@ ;ݫ ! Aػg{{{²,)uקtGZWĉ)'6lذF|AhkkWOYfJp@S02=:Q3R0X9q7qJ F+yGZC2@dܽn횵^08 ̽+t~ۈ 7ǿsLغ R%~ꓷ]&p~n8Oo) Dt%q5.7yGWݘSmԑ+Co=+]pIaxWK.yWR~0kԊ}O>cǎ=CCCszѣw$]*5+_xKƀYwWWWƯ{vp\S{eG>5od|B(ȬyrQ 5Q1!=6rТdۋ\ϭNs?ݻ_}լ+V`0XYYn3Ć/$f껟$3v|kD5 &JЬ;C:/[Uh_٭ŗ'.4EȰM5\t.QOXv@O6ZV,l4% n0u4rw I(&S pdR`kƦ;nI”0 AqJ@K%FSr:$\Ft͗۟}k\G*qˇꀑ]Ֆ… w/7䔗w9"L7??oDᣟHy~^N{ֆ `L$XϑͿ/V@KWHpߑ\ة@=%u.;]%t!AAtH\K|#sni X%hDBI)<TJ$%j&"GvkՃ^{s%‰/^8qBÄi|@%hd%f0 =E-C= (LEdt{vNKr0isk|###_[ZZ@ s:Y2`E}[bE 맀^H,KmmmMMM]`Aak)㜇x< O.z||2^ `F)cahhᆆYQB99'Lam42nV.)-|uuuEENBQRR<00hrYAJ ~BYYYUTT(H$LӴdvvv$)**r\ۢ2眃 V;D ˫9窪F11e9/7/}7׮%UU99t4u2D%1nmmeppMDhM",CdCCCggӁam۰a6Lalyj;pFT0ܤ._[y}(NӚb:,G:5άoط?_6:y.螇 ]2lo[%bhutT`jMYihN(rw5t%'V<7A ʰ?ꪥQƢI%1I tՏEN);MCc%K<+Vؽ{s= 70qw+,Pמ3G}7Mv=o}YMtbPFMY\޹>> KN9Pp-?/Mi2*d&D ֿg]{:П3nEsk20#-o;O y("9J2Tmش}w}O\(xɢ:?hسl+aݴ?Kn/ʼ;N(ʕ9k(^q<Ȋ+ /` p uuuyqw"!&~BO~f~ߊM{~DN VpN%Kۇzf LPpM_ǷL&! xMԀ{ڼgѮ$ 䏟y"c-o;5疰/s8͟m~kÎ־]`Ue獍 )wO|G /7G.īܼoDDνCq3sJܼnWgEJs ݹW79^p;y`CWY; 񤔖͞={͛7fΜtR+ex0Df/(;sRad~+!9c, N6 ,;Oძ''0T~}m-rg{k|ч} 9h| ~'9~-v.鋃FOzL#Ӵjf_o S]}oYvkɜΆ-Eg `:4,1RgSΙ3_ Õ_o)K+՟ &ws%81y},N_ӳ_08ˁ1\zJt"-u`o:%j#MD=ae;h뵵ï>=A6zw+@/c- ([v8P;eFzx)(ᕛ}~ji8wtӓ?#5ϼ+K{u']vztW+ ]"k~T,$д|lC.Ζig[9(vlitv EC3&Nv^C Xnvj0 {e544<cΜ9g'NVJ#W88;Bh9Z+]r_N)Sj뚚$I|t=8;θ"d~Ƹ|nF~~~QQё񅅅,#M|m9ᮃ[[$ .GdR+s*xnn>c,oh%djdgf-Ydtt9z@ ` E"4p2V@I&K"x"*8߿$;;{ppq`?i 41&r:N 360Fsrr&Nl)"QJsssLn˰R:;39~ 6` H&N;Dmi{+.qIFL/WM-&txQ\yQmmqgWgQ]wBYsi;9cLTI.G  c`PbraUnڼyƎaHHBrr*4`T!C9tQ]|3-[  !y{M'ړ/캳֠d]_ ڶPYA[0ޖ=|~E>rMͼ3[ۿ~k?k'7ۓE!rG7{y|/W5\l B{:q*4߃n "ڻiOOMpȍDާұ%+ˏteJ죑#zY`z2d'sO_/FզTUZ\Tֶom4Ce^PAA(Bk[nQ8>\wAk^֌Ist=Y,(z/2)_Y~њbNA?pZ_sѝ#% >}Y@a"Hy>?_|呎?Pɮ/dg~ɫn-鏿3='moa<˿7G8K,==lqՓY~o~!iԿs{wu2αz旿~L^_{?/?t܂&?~,S뙱=D1΢("tɹoQ->@7^1̹&亨stK[O`|Փt05?܌ۻT2+ ʥ7] Jyz DGX?3g-Zu@..ɤ<ue~irǎ_|eWOEr'7tF)_wi՗l_?7/Eۻ;~#ݫ^i>gT{װ9C\=\踟N\3C.ؽc}<<~jᱟo5/.۶gɷ|'O9vڵKoʔ)V9C!jR)999CC]I-c` I3pr9犢47{<XH+!]7 )-1 eH.t >Lb8E_呑^ɓ;::^%K̚5{ǎzE9cN,"RJ+,(:uj4M&UBD Ǭ Ng8 WRRL&-!jnwiiU"k6lg%7lC`nU\ٌ,'UZ֡ށ!T4bjɤ ~UXh ? sR C`0  b?I1(cViL P%hx\o'*p0BA YT5F"/g.^lن NgyY$I{}o{W.ljiFrT88-&P jO}~p}bR8!jx!^ =50jW(0z4s nuAwȆXMW]˒.PIX@G]]puqnEnG`@$()Q#-] bo/jnL»^ݴPʁƭ/̓#nљ PUuϞ=v"H&lF)K|8as(HmG{{ԛ&L( ;r&z|e? Dhg$#{NKRъD |OwHBpӱ$t20E{x.#qc}' j˼f"BӦMmmm(`Ahmmݶm[$IoCdbS֔ɹ(SJUw}kٲe疔4  8`[ovq6"Cm-{ڤ\8=q1 º^y5w3?\qd Dzw>u͜R<ߐ: NSMwozG]V-Ge qU]}ߘ'Twc^21#b6d5=g3+N=pCs$H^Ss $lWenܸK$,MMM۷obYQwK͹+ƅvmkW~=O۷#^n(܈Ԡ:0!*` vyr(`tZ~u>O4d !% wݚ蓿ĕ ܀XD[?./Qqέ}׫\D8ǵo6o?n׾4GhHw3beC_oZP*{ĢrYɌriuw9f%uYSRU 'bHX|k aC>"Irh9/+ʒ#a޶m$cǎF4J/Maaq{84͡޾x,nǓ$IQyZ*8T-:[і7 P:i0siέu? ai>{>[tCOOۭ׫;$I3gnOO%qJֲ n `KKMJJjƍ(edYlHssyyyyyyPHQ˓e1iS\p;? ;"&d9eYeUU E1}Ɵ~$bd2b:'#34#ם3MeͮV4n8V7eZndbnj:=& $9=rb`&R;n- 5:ES>v G_C>@}'5/w2E 8r @3Y;YwwO<~nlʕwyWoּvhf;[jђG YqhC{h/̪OK')L=ѻ&+Hh'Sq |aj͹bk%n |G/Oio>[L&7t1e>dv5t dM]EFl0ӦVze<5tL̫Ch ,P}('DN=]ۊްaAGF^Hc74a„S(#ҧNF{Ju6r\L>ow/# (YTDrs10GpT;VW7RKzB8'.\"c[ϝ_s@S.fnb>>((O&S{|⫯: 3GwC!D3PAC7T ̙yŗm)pKȱKʧ{5CccãXwIpfU^!!n12?i\`h wwBB#]ZEVb ֎28ڏ :wq+yGZZsizpL4˪\IP!ELȖH)ҞkP͔Aȩ=}j[[&)"Դd)gaaA4B644#K4Hص++=7>ݹߎ3H1ƅr|iDk4Vk/^|!yUUU:DZ;ePU5[[[;22v{jjSF`vwww[,AH ~Kq&I>u!TEuV ')ذa&lG" gz|#D{UUmUml=ڼ=)+]\qF)froiUq]+-5{J@ 7oz~{^OYcB2 %`EQ<t6,ꥳ\jE85l]oc]ܷ kfؾqC_ `0d(Nb|Q[{8t'شic0.\P{{{\ӚڬQ`Ŝ0bhZ'Vǿ.}mv95M 1T{w |,I䉹}9U9~?ۿtj H\WUFn) ѣ;>| UGV9^C6ܶk[1{|MȣGz<>V.sލߓw kT *z4O/p% 8$~26m655͛7}FGGws_|%VNho<:PBN13 XL`S]vpdvdֆd.-Ɔa2Ĉie@O}#iȲl&T]m-붊 $46 +!D}!e˞~iJm6{իWn/{)--1!fJ'|,a0tYP1`ٽ>nܤ,Q"9\`1(; cLDKPvr8q禦@LоcѲlMB844?;cʔ)+Wܰqc_=qwY l|Gq A舸@ee )x]%x3>\S[!֤F򺲂T{{xD `(%ZD,FG,?'ۻu$\Og4ztUpN+˓MBLeWمSJ9d=/^ּEy-;[`kinq8<];_zu%+Br#CYr"EuL5X$*? 2? l@\\6Q^Z0% #kyw~;qď㪚Zٮ}}}|ҥKO~9>xg%,TTT9r$&9$QQE!L|^c| PUUE( rNZ[WWQQp:s0u~¼L1+k4"B 4Mr8 vhnB׷w^+y9LVUU9bm۶555?ȑ#n<ϧ\{znkyG:\ZZRYY G:Lb:P(tF[+*ǏJX Z=lss .lqmm],F]v٦(DLUl331  B Ĭoڃ)G;j\Wf}ߛ/t~lbei'y3,8d = ^dH(E C.z;vcg@U=Kc[WJwKOkLE EW/kM.. 4<[0mFn__1wx7FRಙ^ x#feкwrP0gB64{G;^!M:ENnNhw?a߾}SN̙cfOOU"HOw={LRSSs+ջv?EgϞǻ{9bXkkҗ0Ƣ(RJ}>_~~(HB𰪪(''$2XtkXܪhr)PBوP?M`e' &ttt ϟ(( ,رcGc㑪744Ak5XT?b% BQQQeU a$ɆEQƏ+w\X=t8WZm۶s#Qyp;^.6lذ!NRp1Gsr'ބp%o~JƱ )Ooՙ=;dڻsnꥋ?I!7> @v1GsdEBqEw|hRwv DDӸ` ݹAi{ү~o"79KSrK.[>]0FUb([[NlѸ;ʻD[6{>,4(n2!Pt-q4ihh͝>}z~~~(E 3TVV644D@ p6g^x (rMˑfM~L&)@o5zdHͣ9sJI.zG_S,'%.YFLeܬ[nٲedkꫳ;;;7 cΝH$j/#()lG\걁q2ojqGsɌ Şx 7\yŕs49\y!+99Xb jVA KPth(n+kJ{k]}۟ 6/\ i>6\O _|sTh]xɷܰco<=`:|?n\:˧vm:}&A*?>?eUUȕYwf{$,/.-ԽԽ[qJn3sǝJBI2o5νs9uɶedE[tGӾ~esJ 'Ԯ=F};sݽ?cE;_}ܷ߽n␲)]I}-'pWe /}鲳ϬVj)u.^O>}z<߶m[^^ 'p͛7[ӓLO/A=:bvP/u.՚ fo'|c2ܳH+FYpQWSxW^ԩdisOətGſ^^d`Ⴏ&z7n߶L9-׫b!'Yω+jپOEu-֚_aʛ`ZU}O,+[4jB[Kc0(x_スf17vTNP'_}5o;n U5U,_PGNd1yNdTMn޼q],eIUUuٲew}Wk[cr)ŻwSF"e@Wρ#$ FH&¢£>wݵk0m۶麎1vͣh0,++Xùp.iv[[8>/##öm۶ qz}>_`%zgqo,p!˦9~6'h!D__[ƍxXzFFFMM͒%?pC{{K\SA?m!IyY ;w6x 'ղjT_<޴a+( HiGݦ qTo8\Z="S6I)ݶm{gYVUUUIIINNNjhOOϦM6n ϟ_RRoQeÆ &|#Rw͵uՑ֝?YQcjKsyqW|5n8.&&܊7׭kV.QaBΝU]6655c=޶1P\Hm'bŊ䔖rΛ֮]6f̘9s\̳&;;#$/\peiްj&S͛[Xmrd2ްuM;M=W:zܤE寫KN. AT%"ްa]m([>1UoVI٘xq{O`A $b[5wJF9*:Vۮm7Z\cKu[Z3ǏaھmK ;vC#F!)Mv+ٕmuYPRU h箦NNp|kWcFeΆM;쒂?f=QVڰ~öTLjJiSS;魪*..sG͵۲eˆ E7o^uuT[lmN6A/huI+{C== CZR-[YR rrr (TE]zu$5kV]]իM6(IJifOOOwww<w'PȥԽ6mmNرcNJ+NjJ+#@(I0#eƌn(EO/777++麾􄴤%>6fb,#5"|m :V9MH aL wƑzVWv౨9T gv2ۈ{;gvtRR[C55uť⯚~y3.Ok#pqnN1y|ফkG]@aӳ Qg\d ;L..[84#@؊.=hgD)[MpX#iNuy$maP8U\S\M#A_40mӌ:`DMF̴9`={Q&\-rlیǁk)eeU46bpJNұL˲e#' 7N¬9RJ̙ @fgg\@ǂ\?cu#l?ēGr.$T!n(ٲ[+*TX^^^YYu]wT*ѣGb1Lq,]DHyyb}VW:ffX)ܺS옦mFz[6Uz̩K;n!V3f̫b1Sie;Cz酣`$aB W6vV4cf /GޜI; k旎0&XX1ÁsF 4͘!lx Ω`103nQ驙tĨIR&c&fU/"w#(0۰TVV92xjkkKKK Bb}Sw2KUOq$U(AvvNS'klHv78s*(t,0!R"môb[0ffX)%X 4q!7KfJV^#1pƘ;j٥U0ƈqWΞ3Z؆i UcLj\-5xd OYmðy)DrPkY m=梟N* `@dL☖e༚%l#8 jqp؀3g`0८T^7++k̘1n@WAbACA3.@T8@'v4 cJ{{#nW(iZ uO:" ?lڝ\_!~ǣ갆erc.J'QEEy^^!WуP4uDeee ,pA\RJG]VVNd:@r>a>]Ŋ\qtn>hz7'H.cNvNnNN ƘL;!wz z\xss󺺺Rs 5 UiOTHņaU぀Sʬ50ĤUx?a)>R~~_g)!B 8Qݹ>+^ڎsXDuS_X D^6(PB 3U 9_; 4+1"!gj#[`J)E W'hoO_UBh4x!ML`)eQG_N=t9b^׍٘RB 4(!U y, &`twwDQ4_ 33C#Tϕ㍩[]΅QmEFs4ӫ 3y*R)R(& 9Hr GJ bdZeONɔ 3aMB۶F &ˀ,AqE]@U)cwՠ;ۜ@ Fw#oץ!a^R&d )* %3g諟 f *;6;HvcwY7tvv閇eff|>!HKZnt哈Vjmm|y53^ڵrۊD:0?Iú|1fՁĮ-bȔˆ{4AC>졈DP]0qDHc7!=>?"xsgi#km؅98)".'0asys/B !eRva1;@_ѳ:‘,r][Y5>_TQ?&au\> 3{EnsoC>0cnmR%-üwҀ짴 32 #t>$^=n2 jG m<2:i!cq ϯ2ЈFiG!71mrZcQK˧+nE(U?HLp[qЖ}g3ۈ0u?9 yɻ@0>ԗs3!>/)Y쐖|$Ou@m& }ӗ GѾWE46jdSG mR:mgmUDTC#NBeI)]t !infңOq`0xLrƦyiMޢ6ILwn8fHU]1#(Q\R7@u׫j  6<'X$NOm t:B>,[8'-y01|^T 0\i9C m5%R]|+վYi>]qgZ} I7' 455Iqдb!EFFfQQQ4mjj\;x<  )PIIeY a΁@qqY?.(F4OdKp0YcE5+)rWgffP(z݄; -iI;rL}*/(-*TAphN|ki2Fe[.I ` AByj0&233^銢~IRZҒsRrz#݆iiL O1#?&pY#@{gςxZGl}\ppw"Ygk=1B`]T]]̬P(5Msa*PZҒti@Z`3N/.˧2:[v8ЧwF  P$s0g-N Џ*#rYӻ`ZҒs # UUu]t91!NOѽyugW?tD~ bsΪ>UUCڧ6߷RnѸR7O%n0%-iG@Z>ЇzqqQnNH̞{X;feY ( Rn rvEv>AA0J`*@)MngD]*1NKZX=/2AWT!KWۺk1E:@z F8("H)VncU״}r}Kmef38>ϥ pL889O%HǗ O^43aƘ[&Elp̌0$Pjn%!1ry\ i ~Wdb'$,L0!J?eNR= u!s+171s>Ӓ0\ m(G/V p1H)]d4P"z$ABdEE}]6cfDsJA0|> 9H)ce)c HKZҎ|m-yPq+G$i 2gm3X/n8zņ'twwwwuK)}>_AA ~dPqƺ:M>>$6mZvvvccc__2333773PuimmRz'IhTJ[QOV}j 2/'?sӬKJz 1 lŒBgI9˲,kYan #BJn;>FvdZҒv.Ƃ`~84ǚN#NĎ80MUռ<׫i 65+i~auP"}+W`#iqǏ_XX88N>c뗄0-Z,U2Y$$ZoBkIns|6-?<J݊EU7l1c̛7OQa64:)%&3VK`*j#LNR)-H Bg L$&RE{vݽI)띅z鴛abX,fY<dRF:A#-iI;q䍫 rc8iR {RxTUBJ%UjϮ# $8̙3}>A(.^0 y!^oVV=nn egg3l>3! 51RTtdi?#@uW̊ )iRJB|===e#A&L엪[b(q%hVT7X𵊊N:)6羶/ᶑݽ>7'ܝfZ !Hr&I20eBײ{`w.09Z"ґlNj;8٪&!M[F: .HKZҎZY z:#վvT]Ͻc }>SU5Ԛ֕*1Ayoo/BHx@;z= !BXj6Mu ܸ7Dc4x<@ `eYqb;vhoow-.7)#1ՉqI* >w;D RYUڍmDU !sf͜V8?J0!$777y@ 5y +W)ۛЁ1KLc`RJEQ@&ꪨ(,,l`;::6n.x)}>_ SGGdž aYwq.++V%{?FZ;B#h[#EZ׭_QgΘU]])Mڀ>˜@ ~XԽ^aYVr_z|e:턣WfrA..]tYqY3+)>o? Q_wQ]0<ޱg{͝4(@8uH@)YzNW:/DyZr'(ZiX,&gTiIKZ٥{2('SfV|J&XRմ۷7H,@PGQ DDQUMU0Yir`U'ᦝ1.)- y>LZGꎙGno_zrj/^v? x Ah, O B !d4p٣kG'ϋ&~>1$HO?ȪvS+wZ#aY8aPJIԀz# -i_Uoxal9ϴzƢ}Q0<U]a&}d+R J)*ځ9JJ=`xs,d<Rpq:pm۶Sfggϗ|YJJZ&Ça!7gk( oiz%;KK3šan ӦM+,,L྅e3!"|Auߵ7ή19!^P$q-nORwqV\?EzʪRolH˟^-ROud`OSB *?#FMdAW{)>'`@T(aͤdh, #&$+X=Y/wC%^pV<*0.<1esǫI۲ fYp^%,y07 PqE/ԑrӟ^W9wi&DZ, cii.AmJKZҎoʒ?^jBH$DL{<ߗ(ެz^Ŷmq((,(--4&+\bJ)&8eGJڣw&&cT(~եؾ'9sHT:ڱcmSNBHWĀ԰y=Q!S]}%D81B@ԀmN݀i4#h1wت޽!L%f R*Y[/U?nTvF~xMMM'N,,, O{R FU@ ]]VXꕺ?Y$H_o mїY[mҾY"?Trs4˳r\E2PXۊ_x-yO߶hRY)ɗ9n\`0EmcMu`#PE#˗}˦G~Gү`ձ_#.WMvfUTUE0m{0-fB|*B`EnDm_hN&X]1A^#i$ BǣRߌF &+UqðAC`Ru-}E'־G\reR8V%q۫[yyvgٱ+8Ə$-W?>.STmPt/.-g%#zdڴΏomYrxlS62_]kqJjkDڥ:SN>wIsRŁ yۻg<;ӍhzceIiiqߔ/ -iG@ZҲO':{x3$HFlvn-!wڥIŎr֯_m۶ &==kڵkׯ_lٲɓ'`SQ夔Pl]jd?$f,8Ju`Ճ ;LJc3!姘"憕0*}@`)ս~]i37(OLjFm9jZz B.{uwY6 TNUsǝR"!q!UKj;6+\,HL9Į;wxsU 3hR:9s.ZpΉR"IYXՂc3W>[5se3oB +J|mf}ƗK~3e[w Bh70ycKVƗK9wlAF׮;x~?VjG({:,}Aݱz9rt^߻Kxo;f=lٴzc6VΜ2qT&P5s:)ǯخ{c]gd5wژ# $ Yזemi.7([L`uMvz-<B?WeLwg-TPg=p97^{ayv8xU.{%!dMu D3w>?n:QkqW^Z" x9Rw$H ";oo)5a9wm޻瞭}񥣅 1DW{GgɺN>|ovVJA~܈-^0{.96'?NK 0XٶGmUN?cą8T:9=Nj100U_=gSlwN|d?} BX}ý<@k2v$&KL2vHh@ +<@ 8ڹ'lS?bd.R XƥA"Ķm)u)A\ɦxAL H-7i8ږ9Tavwو> =DܩR0aWT7aTrA)! ?K-X˕;ϞWEp %abrgT\U3JGlK:[';;T9+!`V +{fܑDDwyN ê޽?Vݛ+/l|{w>mE\d҅;0 y3p[[v6ؼNkWh {f5q\;Kn۶ϙI,iyVGMmv_A4=²,˲lNqLKZҎe/ZoqFa˶4۶-zgvjmmήqsc(7j'Xb۶m`T{?e7/Puox5*#qZP'{YtDTգ!Xe;\Hh %PEݽ{{5Ac]@>W]o,-xg۷Wp9iD lx}?mVό.y9¯~sW\מ-s܄`["m;QSKM-kM?:r~~1csc-[_ʅ#UUS))c %< pDQwLp R8i9I)y<TU3fۦ9 EUUB,x!z4! m۶, rw*v;J N55fzA}]݊7V/y} u T_ܲ)p)I)R&\tIwo's GMPph[\|j `Uso3ZOZr߸itNЯp_嚊G/'-~q3%ow]xWrŪggi֮O/tևqm?_>؎7Qb^IID~yhw{Z#mO>iܬ,4L٣bL XFcΒ8BR!%30.R =aLz\p ZX # -iٗ5fQ D9X% t]ӵX, ^N.~۶mTUU)bc˷cN01bDcccss#Ig[Z8GUggo;H!z8Q5nں1 T( ,%k]űQTQS01{ZLhZk/=ȋT͚;00 ,[3 BG)P+sNunٶ>ޤӎ)Qk(DW1hF )PpؒGYWN00{0R1v\sӜ"~$0fsݶ۝֝1I 4n}ķbRLd䨬qR,2qY -.o6*U &qO݊Y~ڷqU>]ގj9ov/71S5 ʙ.`IU@ Z~ROtWLPT  |ܔR/0S/lxopx<q4MӴm-nM{Ғv%-KWc' p"Bhj0/LӌD">/33SJiuf)$ܶEQQUUJ Ryus/ljm3a$Pg,{Ʒ_{ow|rbE7,|w)_~õLӸ衧X*}n/{y3'ų.gN,OIqQ@#jXdXm~jτ\-]Gc$Lr4<I"7뗿XWC;tf)2t}30}!Xva˪{|.adF]x(]>.ٺW/mo#Vy 0'K=EaT3/*s<{_g^wsKYغ ,oWc_{Vy7U\}絘#XL;9ybnZs)m4] `k4Z']| glE:ꗾz׽OSE>r%_~⌒ >>*~٪ڣ&EOf~q]/0z>^oOΚ3mzn{cw'"~cUk/6ު+)\[B]q.eLaS.Kλfhk$.KɄ5Db|sOiKC>B 8-4iq8k޼h@c|+SE߿-`.ڸuWuƇyO>h {ɨ̾8ٶZ?wWH_ hW҅?蓛ۼ {e dwm7 X~7]zʴ߽{PĎ{Ɯu8!?;6Ŀ^)g|K?bڽ@PA=4&w|ȓuY􏯔A[$.%H\pcb8L " G@w'e_^:z|M٬|ھcsWo sǢ=<*- FMdC!ǫ MZ7ă%cW]ͭ$S F4(`;'A( ؆v3Ko,.\3Ԏ)2؁  (AjYV>SF;v31Sgz<Ź_WMIJ riW|:#1DRq07+768\Lb5ݷ=L$1ըϣHaIǙKZ/ { r:UUznbqΓ5~?K)MӤJ)\vmlMF1!SbODH#ֽ}ſĿ}Rۋ߿ܙS0H앋ޜs5_l}/~:[֮|={޷n4_+J뷷?Z93\ 860!%32%UA/1q&(eDηRO5N_ڗ^8{_rHټvԵ9\YE@DV>|o=\xL?cGycO8rc}I!%=ȺƘ=q5SF_=|cNn"0g/?qgKn5Y+boxw1oKe/Jw<?y~$59t@gt6E{ȝ(r\t"QGJBpAnC (Lz(Q2Þ%!<ڣg=7'מ~^_w~~j[s=̩*w?No3TC UW}kq9a><蠋(TQD^ / UMUU]6^alxyͯ<fTԱ~<)$>5h*Q(zёݙYg]VVhĹN5_Z31+fYDսxuɇmmk*d6w$]>ܺ˰9peL0k}?]uL?|urSJLIۼ fvVYy/7ΞS\ @~i6;B5K*p)I͹[@dy--8q1uϬ=}MWRZ`WV\d}H0! sʊ&ν'^Xq;xM7,ßa2 D׭-N:hϛ]iWJܔ#=^=0Ɗ( G@& -iG@ZҲ/ .A%\`BP?  n0 P0% {:[YQ@ ࢹH2 fFJgz-W-kxMm1V1nQqtBat@܈ Ɵz.TQϘmתN?6Ζ9%=}' ϙxgj*ƍعivџ?ԡp Z!'&]>o̔cռGVaCԭxFmMs~{Ub_+"u%PyJ9B"A‰R ,/ %~݀1UU=F\q m[Ͽ.|0u\YrUw^|\1Ȳ;~UON=9qy7ܿLk',jVV}.b Pڛ:X&8O:–iB \,)P !Q7:,q:1 wFG 2 `3e01.,JFX*(P[@aB5tz/zwg;xCeԌձt?]yTBT S@H 駆şwW7w@A);_>x1ƞK~W_!DnSHD(F TdKwC9Vd=$T:zDQnb4o~O׿a£b$!T[Q`Ҍ@}<$௨,R$AHkûOzϫ]@vC$ DM58R]l,Da˪+ P~;B"!E^i, G8Bq'& "ȢțO%|E=5oᅭQW|)9X,9!4}J~@1)(r_JCm;(1$%)Palw%gkI%@>PLDX~{Ah" JjkkK3ҁK',)$ 0A p^&AhNw#g}S+>9KWqoui\ dV Ɣ0b1SCc$"/=lC/W)N<_}(-.FO;Go{amN+mkJ'5=HLOa͜'ܝx~|ѳoԃIc[pFq?u'~oz*ӯQ8y 7R0BVEl)0 7H0ac tlKΖa\ cRR}AFBboXҽ{eμ ~gs//i?s2pV6qy DhP!\G@Cˠ *h2s3Ғv%-Ty|aRzn1vu= 2  n2RiZOOi!J)RO`3mkk дp&}5'QDR {)Qa\p!$š*/GF|7?n n3.B:RJSd7wC4l7!cY-I&7/+-ɚZl$wsa'A HtBHxOvcNڥ/'mS GZeI`0ѴPfVrci*rta9nH0ARh$PXs~8%"'[@A`-T׮e _ +DqTH !L,c}EE!R\b] /Go1IzOPq1c!%`1ffbhSMI!DDT8mQr)[=\[!PR"AD AqhI]~j /p/.k-vkF`]A:%REtm->)@/1묫LMS DƑ`L 1c=.0FP'Գ5T7{JMF.K+c0$&xK0 $XuE9u{_Wt9PX~1klIg?zb"m O6kU:*vsg{E_~E{Ge@̰da!w;`aqr 2y_\-_j%~>!`oEWwOqG~ħny7/;aFOۖW/ ?t+ϾS~0cvtF%x \ %ȔYM+ߚV@#T|S+C?ԆFe}rB౎ -=*j&]Ύnc .${ _|ƨ?]{Yȉ6Y^EgQ]TU{}ϮbѸZJGUeH J$lF \>HMGmq_ݸH $q!@2 ڝӏG:c&f.W8Rtڍd:wrsss_}Յ bm; N>=/ T+BܲlIl/T ܲɋggdfVY\ʗ `Lie!gIR!ݒQE5Rp!1!cYp0tr^R0rm9+2di Ќ0 Kp H,S&a9VK;f`=bl^[Feܱ֎/JQDzAYB#/(8#3 y7rt]@ZҎe%ڶmfoo$jĠTM6yn^VE;b X~~~2tl,z7~L{M 2=[FOguXe1 `ĹPJuiaE.,3u,gD >3~' M̰bgdc¶q |l.#nr@HVT7D}YRm]u,G AW3 RDpԩұm˶ ᭨5X<aSS O` -@g|- Ybi׌;ko{컗)SU~U^L Tppsncٯ\4/@, }?nS ѳpſXѮ>Ϛ8XV5J !⚙U>ڽw(q )TD#@ȗeonz,/v(x7Y0n97O%^ݗqׯ?mv XpB-쒉Ӧ<_ /Ϲ؟oQJw!?7_pb)T;~^?& G&c%>Xj ).:B &yB_*z3ӦBJRWUaR&Q\~7„MQڃ\ƝAyI53M?_rSpayu/U%GuJj?lȪ&w[\rT'{|1;s+;wYpy￲ɑ#YYKnroN3zV<"FSk`_>iZ _ڟu=wL[~xi60Rq)QLKM!#t"ddL9+7.}|̔iǝXAT&$H]&ueOSv~t .3~Dy^U{ɠ8󛗎SE\qG }[=Ye&:#f͹m_yAJYG/_ͧq)}oPg@ 0cƌ쮮.wXx<ť{()3>E8xOrν^ߔ)S23lWT!'SYq@{<)t!e6ܶ$wɝ͝±Lgp^rnj;|[g[Q zC9 3v p"ITK:# -iG@ZҲ~ׁ*cRx27\YYo+rQG c;MƏ_PP4܍Q5n,?}e]{_0T:q9_j UjqңZqRf^_O\j=siݎHyrqtks!0DonĈ?%xRD_Q3fj00G!fL 5!8j儱2 F>P1N~.wZ,a>CJ jWoO(.&0!R=%s܋n(tΨͰ LTLZ2k;52u%#b=\m병ʪ% W8ˏ[Z:; -k9iqOO~9}NG(yg_۶3./!RJ,th ì̶_G,GΨ. %tUWkmiP#G.ZR`pLU|yo5'{JJ3m;j΂Bo͖J({lp].ՙc)SX;Jp($ M4ov{4}+VXlyaai(Z=zKb[/YRp&8)Ei(A.JKZ>/HH˾ڢl%Cty':WrMx`(^[x4Mmkk,!{~ڑ#Ghnn޴iӦMkjjN9֋ińDTM4 !N0cqG h]i6DHǶn4&JS[-x4e嘖#$V=:H)% D5uUaDS c @+ &Rp ,29 BGؖ#D(1M.1 )-m9(NG᪮"n9s, Ѐ~ ,).)ězhY()c WtlL )q5D[.O sB!BxYQ"O)ńxӆT 8pl&a6kePMUS8!vu2L!aU̶8V5:tA3?xoyi!!T[[8N{{R[dɤI+*q}nH@^LGH G"f$TS,mKhA؆ж Htl% HJiK.>}뮵k׮򜜜H$D+H',c1v^JSJphI<Ѓٗ]vi\,ϻDioo5ʶ펎>yƶo[Uvxfƹ8x#!^o^^^fff04 'LҒ/3r ˭Lx93 #F)|*"Ƹ訣z׬YSWWmmmǏ;66 24htn1LģxJqfA*mDQ5>'RaJt߇k;ݻkހG{ʟ\E)NA F$ȩBD3HL)v~9yXQUgRDd[i[4!G* )@!ڲ!aDO%QE FfR=GKp{BJ¸Fq{9t#t_ķY8KK R"!@H*fB!3zhFQ(//4R c 1JH& bB~QU1E]Dt'lbah{~JL RtH,JNq1VG5CT+IUc k8F! >zsA*Iw|9*Ԭ <(ы cpbD "Oa@?&~`nNV?Zc&{aR^DcPPA+Do`/h 8o D)5~9꺇yGO{̱:ݩiZWWclܸq_ƕrAgG?eۓKhS{iG>vʱk~:K>$'AHHĊ  &Z]8S"BUU#H,?~)Kph߿2t3y'7j|Kqo|8+@mZ^Qja/ŀJ@sGzRE{),Gm۶E1߷e566J)] U"i&"QQOT]Wp ÖP{4hM'4u~ruӮI)Hѽ eLw@MU kxDZ$l )+~LA)bBk|TJPx$uj?0ԛmFHr[)1a| E#ErrKjƁ/)2!u?[ KFT N☸IY}ӧϗf NTKmh9]mM;V*5!yyyk֬yEGqDiinQ@Na^~6(q$tZ5VptM{uh+S=T[~Ub]I $rpNbȹxwJJJB`ΎuɌʚ0GQӵmӮ9H %=ሩ%>#7lkhWo#3F)FHwUK 3}$*wuܪ,mhS5 ^p| 8I!۷owGQ)DR qr] }8zK| {IҪ~ Fa]A&$n'$m;{[2"di_@ZҒvS!DWWWss4m(k@/!ѵ߃p_AB܍x|M>12Bf===wԂ3W/?)_8XeNvvv.]& _ϑtt ʁm{I%Kfڽcg]inƸqG9YbESSSmmmff3w? @ g޺aҢ1clWJfO</effN;,}7θWhH!BEײܒ{Oٌ- bƍӦM+++b. %S-*:qG7 PSD<|Dk/`%H˧YKqau5 t7;0F>*3. BMӲwj3B3<qֶeǎ^{0J)UU5kV0\~… !O<~/ŀه1=q#+'MȀÕU8M4^zѢERb|2~섉~af555 )8,. $>91',ߔB*bp!^#M-.\G]K~e#3gL@0cGE`Tk:moFHfx!L0oXAɈqnk"UqCBHqIuamj)#0XXK/]ڑ{aqĪU\%ۖe;RJSe"TQpL 0Ni9\Hy(BUi2jB@J2,&ܴ LU]U0FR0۴lIqBB;wZuԑJʆ} cupTIq.Dxq)$^;.%MXXzOKZҎ.+/еc=>/۶My1 |xL0G>L9aLn(PDQqxJ9B`N?`ilVVV~nwvJ^D% }݃۲Hָ#f9XMVoT3Z uSL5jTooeY%c(ҡP( pIE&VTT U%~v:ؠUp8sƆϨY&  !úE] )#,q2U[H)dO=LP%o)MZx˯y̬Icy[>=fCnSFwݽcgΛqYyE[,^r1zzNm`W;wzWavQ94/_޴=V5圚L9ko`.KLs*K3H)@raD{:#Qg}In7̀G!і8xCyŁ[뻉_RVPQ*m74DoNQiqAXB!˘#h]`KSJ c{~;9xrMmG]. 9B R<#m`p!#7/akr $ϯ͛oydI#/5:SD71Q#',V0\4kV.nؾerGg0XI*NWYcqܵni]K{`}u7{*oW}3K={[׳gՙkfPe{o^`jExKyyӶʵ'oy__)-ל5-%h^#x͎)|eW9 j 1ι2ƄLlayq@HA`M&xkuqܨr $yDz"^{oH`2,fKH Iyk²*w ĄTTOi=iIKZҎvč;`  wG0lnM7( 8B'3p9SGombOHS7VUU>[ N9̓8?ЍYJlOy az1C*AK)! k'1kfeB.8794xl_7~hms?Jд.!I)GY'd'pù(u=GB3V1ܛ/c1y#. Š8G'[6n޵q]2w,YQ$isQN+|k/?iƘ%ox_߽8_?:v-W_l£]M[{Bcvom-)fϭi8K |s~򳩫:Ӽb w~7 _-O.OOέ18#1)*W47}=wBܲ3ry'Q@g7)Kp03&mt >0?z?3T6`_a)$ 00auj݆|[6]}q])Kp8BDm# lZҒv+vRӴ`0hYaҜȁEcO-ܹUsb,c3a튂'5T>JiRCDѮeЖQ|^q8H#>N\eLn?Џ0.,!&%NaU5[7Y]8BH_b 7f]&NB~tS75wѠ1 @h|[ygbvE7x-6%-i9Ԃ14mSBիWbCOmH]קN(ޒ܌UU1`~=,nX֥Du>G{i{wqǞ{Dc8; UM)pgz:v|ӴU 1س+FOa b*{-)%G>CV($$1H`Q'Z1ŋzIŵpO3ƙŌ7^C4ǫyڴf gV#ؾ")r=S17<*վ;&}G 4l|mpsۼYJD -$"@A.\#$0I.9$HwHι=Z|m•cF-3;$H$I$'Xb3a1isB". JeoZ~w_<{#1,  P,8$i9΍ŚUU BCL)ʵֶ04_ Bd6`LGt_Α\[qgc:6#CB_q BR0fLHB F $\cX\ B)4q+kAM5K I̶@*"a[6SbDaP엾>!c DAޢRzۗ-'M3g v<'6(_4|T3kW:=tLTJ .KVk4'%-<ׇ1 TR aգSVK5{24-ϭY9ʛ>wEelתu=spoO?SK]<})$g9^{NnZ}LI=kNv!ZЭ_[C\yÍs2}=+_GĞ`:HDT}>J 3;#\"b0#@ܓN幮|t BcxR Ǭiqп>S:/jD4hcL4B(eVVZTS?^S  )~#56s;ڣ<|@V9MS?m|5ABF ZLGPJ .Tf\,|Zqƅs"Z\0>l9*\(@u"ͱ@C|hE#Pw#yxRU5i1%?=ưYW7=]ot@ksT"cG~}u/>||i#m?뀆r} _q;+q@Tݧ<ݥr8FlJH$^WKA_HE )R\) T2HI)ђQLRcjllt]7 uq0ԀЙw;[`;VJg224;Ss,[z.-le_8Bמ3"uo-6vwp"cL z7wYB{?mv-:J8g˜bb;`}u~_%+uO~ō% a gW ~_«i"3A%eٶuđ`11 bujWru?Ż#Ĝ^1k87I]e}+0Q*Ez tRD AB! =0/:wb?ƃ/#|٤r#r$|7_]6xt \jRI`RRRJ1ο决eV\ݑz@wZlz@˴Ǎ1~r[WG@"H@I@ҭNRD]a] @K`L* vEu1BM $%)I )!!%Xv]70ƼJ)%a qS2h_ǁW޵Y7}FRF@Y"?DL*^Mw+(M8rG|1{T1摕$Pbٚo35nkIO#bRY3fڂEG6ԥRᜲni:Sb`wV'tx,EdČ:Zz9#}/ R6X+`iC2@Z[u >×HTʱ-۲B2[J1 HLp A(4PVG'ZRГk]]Lbr=)II|"cR1^=&$mOx-#;1ѱaÆ5EĎΎ7644λ; c)wR@D }-I* ŅKs%oVflFpuG(-'4|e30ʥR`.KW:R ;C$> FF^C+X/e }ڲ!0V>ea$q_[i(ˊK_sxe.m=\ff R:dCU*LT%Q!$ EBuog.8;]0ohkc}p}9ǏzɕuuQXA2@$RS#&t0F!g~|edߡ6B܂Lvu]k?ܪ^uڻjo:I;xHzC]:%GM-!1$ T vT  D*DŕuJ($A@R1SP($m۫jz6iy) $u;w W ddZTR04Aue(bʘG_K~52rllTtJRz]kRC8dJX1vEµ9T)D?/CJJ"B :"QJҋG+J{2 {z JE4oYk-]F myWΝ>“4(\/<5K HDCTq;n̝$%݌*>o񀀓=7dϔJgFǩ% cPzKܩ@a zmAxO@="Ŵ/R+_sʉr7HQ QR*iŽYHE*ΞM%1Y\5uiB(V]FsKQVr,*?eV$9UX$w`ie .TRwbQ 0!Pkzmc#}WVWzEZ`4#X UW>:C+}KA> ()RI3|&Aj?2GhnܡCR޾/C}#~QV3>2Ƙ4X>2g~Y!tڛZ::ct_$PW)@в%?c΢`5׾pպ:}߬:3ωk<.jq" (I> "Du] ] AQ\QIIJH! !iΑi>(@Uj~0 Z &CCe̴"?!m=+/* X<q 8F9oqwYWNZv^L*1PюZDD%3r2r y?:}^^Q:&>ovpTw{/|wK}DԷK+$Sva۝/N(Ms3Htֹ;5)ΟRi~v7]6MCsotŪ.0q֞}PuA'{P n{ۓodȆ:8-(t]okk۱c{R^~ܔWBţb\蝖ۛA?s>C;q~փh!,<"Scq{W^$%ց> ̤< D0{;"Z׬q9 Ac:oJ߳o2Sԯz}ǶCi /JQJhPJy'c-1???'''K)wiQ$gyɚ}N,;0chf͚5)))'03qG~cvIwu"J0/]/g?ΈLzaA eˈ-F47t;hF}Ʌ3_dЪ|?vT;2C={wSl| MO%M 3V۴v sV*mW"_3s]ګk37_vQw,WqwCOSs%@Q54xI) mط|) IIIBJy.Tsn0’@@Re !|`۶kDV|%Ϛ94cYs̎p."_\}a&јqם?kpnAr _hU_uյclSg@p4ѸuC+~+/((<#* y_ۚVEӎz^QEF  32t.{ϺƜP!=PA*!T $%)I )QLjaf ,18322WwXq`c3\AƍV]l߿Ig R(^sn&@ P W_؋{5̘T@Jĥ :tuMO'WZ,1'VBONjڮ^ܧ> Yf#-nU/U$$ __|t,s@D~kٻECyUʫGș0yLp@!=twMG3X՞eY:AD/ %%%--Z-HvXCv/.>\[)3RgO)%2<81 T`NSΏw4Mc٢`MOC-N JQ%hS;\AHu  E0w!D 6{_,8Hn02\Tv~DŽc9. @q(__,Bؖ+4tkW/@e E)IgP2A!]ZNnڔyee:D P.AIJB -$$ #nz2)ջn{Tr]][EgA#F!J ׎wwdݴJؖQ{%"fD&-D%QTs%G؈]nGlx^[5iR>qcܥNfJ.@ 'et0=go' $OFI\vifQ77y}sBN ('Ϫwӟ&YS 5 uu>s U>wȡvUuAi!G8pرcSnB FB>pt`Eye\9fҳO i +=5-O%Kvvʪ{g_Pi4=kRG-Qƶܓ%aztN&bY>Qc{K+++tb()8āsag]Na'Z<ߵ=򊊎5iv@ (9SQJh0dI%b %2:T@490hO_*uݳ:ځ@ )P1;2K jzߘRB" T@Ax<~"(A?("&LBdu{ uk\ aju HR&@5vgQ/;q(@$TH" %+{`"-H Fw^~ўx=4qGUR3eܵSb 6RRʆo${/#ٕgM-]yO2}^^_.;;Z]᣽]{NB 9y>|x)<ٹz]veffz\{$Jg!(INN`îW& R}a7%K | "Çu]cHaEX=A 5L@zP@IP S=kY*TqRs8NbX1𼏍}>_AAAzzzUΎ˶! MH K-6x zpfUu'"@Hsk6C>}璼790\G]vrau  :RVљΝʔn]NͧVB{=ԡdu j[D#@ ezo/[w޻uihbCnz݉5*ѩYr۱RDIHUБ&WaZV~v@*0|5FJVnn.WJӳ2J!ʔ=nkFZN~_K>XgRE(QRM3|F!T`Gۣ M]#=.͖zF *qw q 펍Z`G0yȬ>,۔ Ppo>6rܠ)L xJj ;7^Js+ ! YػO)㊘,D&8/=EfuF '@@Kh6Q(%pNny0k@ dQ@RՉQWWiZjjP1 =a/@}뻔$H(j<6EҕכO)гSe?n^4$s Lsn&8)-x8Q ehK@PjVJ!Rib,F0}Q@dZjeYe#5:Y5s.%pu]UK޵vݞ)*kdžu[7{CϹ[QjnC?^9/v?YZ9~ ({"8.gekO, _|fe ƍ5t Eە,)Tpl3{/hlތM- <3"!/[̱_#% mx_S3`>pƬo]_G0Bw;S9 ucIЕ I7uD ;K_xrkeCLȑ܋E`.QLC8NB%8HzBd{[6YoL'橄v'@,/~4B1"[pyxE=g\[]T؝{z 5r'/Wњ /.yU\/]=wxǔ[^Sv7)O%zFyW-.RJ!J8u럾wƆN¦U̙6j@*傟J(ibѵ_=0DM#˼c u(HC7~_νTz 5 {}}yziA nYcwo~|/.y:ᘥD.ŕ$G o¦5˘ ;y~|u9YE.f.YtLϔ.Hэæ]tn5s5/ʥ*("Ƥ@a@ >uq<. pI3aQ0T+}iQi\4] Sjk^{˛:@?ذ/[c R;W㔎,kvD*-ï Id()DBzzRHDx-nmϚuU $ԃFB"yׂRJRi$A*BA)~ןz6, B=|Ww5 F$~7Mn+qo@/cs]yK-/?/~3s3 U? څ/ 㪱؝u8aPCCXքR('rx;)1}|K=w@D$VW*)Pr`̓H]n4egԓ⳧W@ !"$kx&D Ţ̈sRJ H z+)PRB3#$v 5w.#bòx JJJH5&AI)hJxшJ<[ﶵ+t]̝߻1W~W4 n~ӇT_l?~J¶2.K]{~2_lCY]?2kW0Wx=OfoXc[UX#u-/Z%%HgIe/ۻ7=/ƥD %^ "DIH p) zBHMAm%JH .l;뽵s&ϪBJ_4][tI) h`[c-K*ZQ @(.i`ZE0 p1K+܉;iD{埏lWUR4 nKO@rە@(@B`t"Q[HE54(HNJ[@t`9eʔuֽkoG}MiI.(!enj Jv HJR@@R>&B躞RRR8睝Iq]nϞ=͖e1Ƃ`nnniii8<~ xL,XC9QO$ҝOQ P8'=W][ y(˅%zRJP4R1ftDP2 7geuʁ\=5+^y61thʆ+vm^7j\iȼZJ뺱XL ("($D>ӶxC)`4\ҕlWZk{+}nEbϯjq7>waZ?֮. R.mfZߤS b/;霗<«_ipnpfnch_t9_y#MtG $%%utb#k,.@R-DZ-4|Έt\Hƈ/^y`hy~fnEҲf'e qK))) ---t . +>/#333+'$% $6B4MBչ-Z[[7lpyAiko{JUUUEEa}ĚK *wp# MӵO`ܓ۹OГ (fǕ.KǽaFXk]]|(REuGawz"j%&1,7H;֬|}_=7@zI>ӎJ vuRZVR՚C++p@vq8jm{vUCϹqL&%޼@SoԑChV 8qe~͝T eR-@AJ@ :߹5pƴqYn\+ U :4Omj *`GcNWBBBm  z4ÛW-}dʀP}K|_i~ֻrͱQ\så WU2o<{_pΘPo׹%<ڶvq7L>/]-/xw>Ʈ‰\EewjkSr(ݯ<]~S WK97Ŝoim[Wjcɬ3w?nSdo\?l߿xq~mQC@TX:rZqPJpbR)Jq \-[,))a(˺qI),ˢA!#-`p۱kۺ[ͺx\Ї^ݺgO}dt|;;;t'*G֫˟}sѩ& fAk MiA8|tܴss]0{Yscxz'g).,NGێtd{U#Ry,ޕ_C$`/-W0bxQ~a#qqH.k|^;Д5/^3^oO:>hŷ|eTqdۏ yA>;hQGnӋu۽|Ƀ=;oaJ\7r4ox۹fkJ/itQAlZ0n Rprn㏿^~.6"ߵFyW6F{5W5ȇiײ>ǵYR'KFM/SZчHBht&E))k(%\̩ߩ;*_ʀaR5pUxlȌÍ0K*RDꤋ.2_˔Jل8/ώ1Ͳl'ZJRW z])%PxU>˯9" :q0!ϰ745hw6769cr^|\6vэS^oUa'^Xn{gz2X=y*6̩$reorM4<{˻ۂr u'mY2reGվ?ɜ|mͷ}4[f|ǟY{oG;X#DLMMMIIq]UXIJR@@RCҧrrDqbXgg%477[NI9޼]87'wΝ֭s]4iF{Ͷ+D&͛Tb $< K]FKHu󗿾\}۷O\Zvk<4 E&]*P}Q ]iGc 6踥ԱqldP&(pvi c7߻-i?zb"!%ZV~\[ 8ޟ9&vDW8k@ẙ%=PԷw;0 (1MCѭջt:THn>ϿhΌ,iivD)AZ83XPK$j9~txwÛ,qcQ!J.@ Ḯ#@'A}޷_8l1[=w#Y=2âvy&U3Tdϊ{~eټgkrt8` )'1;R5az啻I |m|yoz +_~Alά1%Mp1B"3ه [Tƭh=tߋdn{?f\0|7g?ϕkY0| U{u#sB*ƹRv5UV;g_ia޲<^M95ݧ̾`XÑCZpЄòЊpDE::it/OI[<'j*)III*a!D,Rmoٲu#Fv\<7=''4͆ݻwB#Ӡs{FDsc 2ݏx[+ɧW#Q>?cfط)Abx@х7y։D|Z !߷uECFpvvfV,Ո07 lj=,(P[BIt*ױhD3r$l_S{Gցd3SK4HռAJoڴ,ΥfPW @ %4vmD uCɚ{~fwF箙[G*jv3BIOIR n93mҕWxPoq,u?hܾtָ \SJն{oȜvvUV}6ĢQL>jV+nؠi.6ª_߻RYՙӶ{ku?ef* }׋O1pv^poW.3ΗFӁ8߬[n~л[]Pv &Nx T+dEB>*5ҞθeG+VJLC-P4+ݗBsbJ&W֭}]{9sstD(srQ멄ڻ_A˾ƅԊY=rqb0z~3iQ]*ƥ=C*!ʫU#;yY8cs TI;xz}gx٤Nc%CϜ .`xFPc2RrF=K;,)B5XVw~Iqn?o G7ͮ֔k *'\8܋>wĂ;6~}h7mzǗ P>p3DhРH$rᴴ,H-8違+]S<,CTI޸wR),/L%!_c@Iac@?3 A5| wW(3"yv2T5!H+s|N9ehp1"!5=>/'@̪e W3BLiWb\tW_4xvUy14E@)Dտ(dI5d;+)*%A0&tkEO@D1B E"!% C>Д#Ɓ0ό0HW* 捜0zYqᛮt\DH!)][ZNGNpnD eG;oɓJMQtsĜEF 4`=kTaT_8[#\!PNmmT.)ܷɕ ca"%>g4tlL=waK Yz)H'|ƭZhZuy(3LiZ=?n*u_vaK Yxġ!/ݟL;BiIQ)ңAfo@AWwFr& ē !a04%Bl-h y H0;ywJή a,)ɱ ]}QÞ2=z}%3/#(f0r9#uW +NG|?>vY%_HH׉Gm *-Ae5Ma`Y+L/6>97%zHF2+&.JJ[VZww<4CC!'F0$":F|>ipڳ8 xܢ#R%¶7p[666{5Jȫ=j |uyčܚ#V4S/|f]pF+^>Q\QUn8ͻ8+3f@yC[?6 \}AuzC/r06pٳPPo=9sFPq.cD"+V0 CɏƓ@MMMiiig"96A-[_|{[D yLΩ==-hLCC0+-o9G0.3ðuYf&~X)c)24SYّ]qE(<_vf禦Ra'wt&Q3nR͗6P3hRLz`MnB>b0`G#.*d !m )ӂLCwpy\c)!1*J*)(ιp RBA%HA^JRŸû?]*PByJpPBTVOj=4&ܹպ}ۻۃV*=MrW*PJID%@ctg+D>*C5FTcuJOɏ"0vf䄳B2ZmfN 2_vI "S GN,GWm^p?5mw` +h9FRM`=l]t]'x=R'۳DE*󰱶ɕAO!UhaO7,+a3RkQvUapvaZaQ~5kƔT9b P}Ͼϴ';Q#L4g%Z&Z`u=Eg}5ge\6)(r*缶p7WƺEgSk[SOnO+ $%)III?%H) Jx<3xxsSRRRe3F=B!iiiiiz~4:+fd|uUv,nvk{ҭm/QuWXlAĚw$2laveenoȼ| Q?aKsƥvXD֬^΂.A7?^JMq={O~Y(YTTdm;nt8R*77׫8 #Avy+0rTsOP`RQTi!_F1 3&W0Q[$+d&qji`I:AiR))a*+F.}UtQO \IiV^qsyu[g iT ;wSaMxu,u AD-DJ &h#`am{H{~֜9ҤBF(7~]E@( %#aK~ͮӖ#V 3Qe}+W鯥n 1\IY%08(65jjZ^~8rӇ|~|}Y'/McвmC78q{nR隮ǻTeѦ_[.ơٻB*W=鼋z Iv_°gt3쪫vF{&)eS-Ts_cR8H!eK^~;6ukֽ?s~<_\UQ0th3//:E-J׎uvF@I,P0kw/}"ZM`ʹ|陧V4gGl5{xaJ*\NJR_n|mmmx\RXeddPʣ}+2339e]_/bԗ:=yo4 dC=Kܝg׿ /z`"]ԃi,#r1p#Aް){֬ko;+{߮Xx7>?*/z _⡿=~+~yWrlYuO.97 cذa5{@ 9۾{3S@q'$z cvc| h󶷗3ϝ>@yt~lZkri% y7ꡣsM`P֭ۅ5aLy}O\hvzD³`2TcL2.acHU =JFBt-uZ;BOwl Ylݐu5'n F8?$E˔Q]q}~7֬-T2J$X҃pٲ!:A (E$#Lh:"T "Ŧ}lƷWmj w+,aΦ?_{S`ʈDYe/>UjVXuuW) *\cF0`YVoҕg/4-`;.۹A_YFV>{ LP _(wŋԯ<:<˯흖-,qw=wW\įpEv Th\im94:m Zh+,czرs`QH22WtADG;Θ'Dmc̟]yfo;gEUhgM+}h75fE;~G~s;|&&$Mgh$5GN?c_F!QGl^^;Sfv =}q3Z[[ˌΝ;w0?ؾ&??J_ZXB&i^m)(;jҳ|'6-{{v%%JY8eQ6 J)P<Q|ӏ5(u57_~cĬ&ζWf75iy٘7^}uOgC}6n/y̸|޼zן"Q_/B PJIw)OH-R#g2Ft%㊂Tq!OJ@jt ?4x${BZD\40 [V!\pFq8=;!Pa/[ʌ!V , WM?k~)o\q;_\;!  q9hѭ߻|z6a%Jמ{vVߔ[u/b58wo>{HkH|8r!8SWW8Nt3c"sꝊݽzsfRrrs2T u{W-qK| z]t5u4Ua2J4k͘X%@7J5EN _Hxۗtnm(;ٷY|gr-]cLt6>s.W.YXT\x+_9• 4%9_3kљ1`c%2i]s{^x #208fԀH̴̫(0@I 4K NhЉ^P6dYc/Г+~TC@W6koܹ&_X=t2fy3Kӫ&%F>8*T\}/O؎^mjNg0-U2|_ՔQ^d BI 9C厹e]0,(bq[˻ۉc/\&)7pۗ)@4CE[is斧hLN>s'??rZ-5K_)DZp娲r<SHN i_pQ噆t_y!jf>rٗ 5S g^xIb@")#gk *LUο~MpfٜK.)nRߐw5yu;@.I~ gɆ=;ElL[tY*c-7\NsFοHUdLLk_OW@@OR+&δK3S-1t?cN P H /Rv޽>҄#~Խ\9ظq,--hA͇o˟~Ҵ,})2l+kRŗ.}mٵEEEyyymmmMMM^fU-{J)FiqiqSSS{uXkcXSSa1&*F$m{V2'a) D(EB%Sicqa~*PX1~Jp"Qj\pqT% 8Th% t)Dz]P ]qRL3tHq9}Qm9\"aK.WHutl2M4"#.B!t&j2tm )cbT D3M: T3tۮiV,L@r) wlA Z6W24((ǶDg*r]!4çS]Ȗf,Ico=?{<~~Qd\i@1Akmm5 cȐ!>|5 Ó~N- eee------E߿͚5';֫+~C=YXqII8poqbW[{GKӺiWBw]eC=xUVVڶ][[b)7mWŖg ucX{{{SSSGGo?vrb/_CidF@R>WafJJJgg=hR뺦iH>B.555yt=c]33pܬ?/oX +f澴 3EZt޽o>:"%J]6A C[7WwH/%]-t]UUmBb_7R _[qXUx[R> '3"}m + p-h `-}咽^q˲ѨmYJ)CvdvLJRP= dhMH)Oވu0jkkFii{E!B F322c].h Ɨ4Ƕ!}S+w?CkDw=;7Tf3l\Y6ԺxUM~gӅp` mPv&=?g͒Ѷz7c˜44*,b ! ǟz.x0<=pJtGWJ_$.5qԈl%o:={nAD4'"~immme 24}:ӁHɏ;g> :E}TBiݩ(xV"Xzu$|̤0Yfy,Aڳ#YS0[YPE@ Nf9.>.g_RN,a{]mVj)P A:*P7jEH'%5pE+g{vCO$|k 1/4|Ŋk֬: PJ`[]oaFaa1Jp9fO(Ip\vԟ~AoN/1Բ4r6 ;~%+ՔD7S Cץ JB QBLWvd }ׅ~?kȸʇ."(ЪR*!缻ɼ"c3{]@ޛ:nۢfZVnnNdOqjkkwBP;I y9j%K]bvs:v}@z#mmmv YKIH0ytuE-05@8 RJ}䩏F(555'R~xzJb6zn")>҆߬U857KQtx|I9)nuk?,z|CWGJ@@R>H)lnoocyyyeee^y%"v{!^O>F5 sGٷ5&cW3:"hԔ`An41+˦14SR(P+G]W 7z4A+̗ 옋h Zpק,jtL_0%%ۧNpbT[}t6REK#N̗Գۿ|Lj|øB򦦦fܸq'#*nK}@BGJb\Gge!RB666⋞J~HR>}৬͏xP{1>Lhbuc?ut^ó, Nqν6۷oojj񜜜O&-Ѳ\,H'B۸P`jz h3 ^E|J$3PB\ǩB }>_UU뺛6mڱcG{ׯ_gϞ@ 0jԨc=sQC4b9)9r$"nٲL |njևOdث7z6yct_m[>R&ތ͛7&?OWrǸ;I` )H![fv]P4;n$ [-hE943’@@R04۶#B-x1WUU666q`ffffff8rNo<3:7Aͻ\@!.]5 iERJ DW" pT=&c%\K=#Di/)))((8||&Sd2I)VJ|⼼JׇyX&}X0:scyƞuޣ[4MbIM@@E&mG)庮+,s1I )hjjRJ/7x6}>_~~~vv뺞/:c̣?{eR)g=RU=roTe$خ z5-].{Z0T$񙴇u⟌ONjRN)ctI*%B@%uCNR@@) ($-hO2(GWo8aHxppx$%)^uQэ$ߍ]mZs|>s:AoC>ޏc AßA@G[X?/.>o4B֭~sϕ[yAj%a @Ir&q_?XRjvmmmccm=ؤqRJ:]M=?6Dˉc@4ǬiPH׿!*DT'!G▚1eBGuuu e<J|:I )?:iY?E}DHMՇ˘2!o2礙KB(>I:H2]'JI TqW䊴#?;QSՆ+]Tt7ǿ 4$MJRrƥ9Z?wr:AwRB!N K)hQZ8p c@ 770SML8PR  Y[xt'"%a]jFVltdov[˗tT]L*Q48ڨYM/׵"οsKZ_^cmPTTD)Sӫ>^ (8٫ j2@F<|LPEQy$e"8wݻwWUUB~!aSڎ+WO܉FmI ԩtxԖS Dr0b{7lڼ÷ׯYWsVQN@~M}mA)$CdڤB# eOJXm~S;X4##r#GJx4眙?~G}A uSkEǭp8-룻EEEs[[[.--M, xϲb OmUt] ON9Nu̧=yh]]0<=zqq5غb JQ ( %b72bXRZ[[\5dH)?P%=-8:SvwP3&>06|B +:vުKR_f驏l>oLdxy;[%kfIhY;bnR].48Ulں M]Hm5[ۂdtrj>Y D}ˠv/;um6 .]8zX!-))fQ)` #G"h۠\|Ow! s),,,K@]G*+ 1Q(AuWٿnOYLE!@Z%쏘_!(]==EJfZ/7Ta `yP`@^t-qAJq!ٽR-U#w ! #ǡ-Diw878 (q݁*u4YmcL);x{JtYYT!#ϙ H<ݴ۷ˏL,DNA bH|T}p-|t}H]hwmS]!_F"D\gj嚝;6Fn e,Y6kKJDr퍶9EO%C[f.6F ]>W[%%oT.{5sRU0@!qG[DQIt{ 4#SP1H+6j+V6}ʹ>d,PaZ&DujXpYPEj"% # qG@@'8p8\nWsSeYRHMMѳg{a#%LJٯ_+W߿ؑovJ!Բ37tcze1 ZRRU=1f +7޽<&6xYtr4!F icϖ7C.0!Cw^|OfXSbXRjiw Zpx7\WQQポ;+c\'Q{#aaϒ>_t/,P>GEgOȞMmbpz ^FI,!=Sfp{%mܳvgCó@XH;dm JGY~{\qzi)>ͨ۲@}UCj57[Ns5,9p+⁀8#x sjkj5MslcLӴ +8\_:O^wf$5YD@]6~?g9~6C_x搜dcE'ε/}b)w;nuWvXwiVn~|%'H x,!Qݧ> KHyev+݇}0DԾ8;XY?sbi_cƈPtMSuJY_Y=/: cdcgH@؊YcL/>=kBr)@J&#USd1,N n3 0DCΆV-#W>K%cŪ6̛+xP<,}κ X&\>3,"fKMEKSeCTj"ڴm3l.ȃƞ#Suar )ͳ5yIbA*|gF^z Fݾw]!n6-nFzˏ=Z{qZ0AUbʶ,9mS\l >Qkoشxi^sd}HTޮpڶndw[;0R/kW/V5Jܪp~Fo\'W4*L/),lc|QߩdI GW{,tJ5?N}y9F?{MfHqigVߺt3Av >7۴g7 >rgޕk_ 4guP?INଃֶs"_}8k"88qa„EQ4];&0F"Ǔ baikiZ$&&*D;QHjK[R_}ͅ69ƨ#uBXkqW Liw{3cmm{&9@pٹi~:2کEF-g ! g $)NGho9g \Υ$[?~7ן{3׽=!wf,X~fs$ q.=Bpɹ\O<)z|~`ϧ/~4o[5yZhbф)"M6__PT\ж˿?l aBPv-٧_lm\J)YI8Z>?=Eu1bX3}e=oo;?xvԆ1Ad0-[7lٸhd;&% LTU5UaEt]sE EÒq.QE5 ӥ9TUvuxh[YSfhnPJT`I8mYj͢V,bC+|}z1`K1&JOnuQ !$ $׼^;>Ã0s_ ,ܺ._eOmCCUSVx7-gg_yƧyW?{䥵RQIj5W/?<2NɃVtb_#~sV \Ⱦ? ܰ/Ą.$PbV/yO7w^uS JڍӫQٯDq ZmYcoKEYN * 꺪 Rb*A 䌩Tu8\bKp.$EQ*䍏mlU T P%ʥ)}hU϶bX8r;H Rj769.L=^-|qHpz%`OiJN:;N()Խm_^>`Ɲc&x\FMCQ@w:/zi'ͬACO$!&gI]MaV[?wxVD$%3#8Ait:}~_4 `!aAagRJq継( vK#QUSkޜn:4}_i^y._.[ Qx彅W"L CHn~v@(Ki2E7M `[)% 46M]Fma؊T| yˇ{=_}Oy[.O>B~4;nB n{v4Ԋ6d߫',Tg~^gݢ/V)ƳK}T,2RB d=˯O-u=lgm|M^9@*R8 @ W|o4xl78kXz#0~w˶8cy%HŠ!Ƹ䢝1ABXSe' %FSUM̜th(\(4GRZzb&=1S.2>!Lp5"@ a]/I BAmM{nk6dɀ֒ QY^#9XU?w%L!9AtfX!1t]SC%FO_xeovO~| (vO[S!\?4>i *]\ ;tGٲwcשּ]znie?>O'>|ODG[SlXBpJe2Uׇs&3~ƲUz;@|  sXgv^eG2&Dm^tUElt2pkrۘbJH3̄|gV謁9?֌(Hcёe/DKműP&zs2Jہ kM\"miS1QneT@]늬k "Vמl?3!=Le9i! ]򲴜)9=@yFRP}!AﶇMhKHE$@Xf|?c؄1ycB$388}ML][^VV%Iu %e0Ǭ5 BC.o޾悋z&:t$@]Hv@g vcܙ\|o^?ӇnaF7x?RѸ%;+;]IACE@Hl蔪I-1gQ{<=zsԷa+"wOb8#8⁀8G!*T !<O6o,u[߶Ozv &MÈ Cp=]ī8մyk[pxk|k)06?yT~?f?s%53}Y[[ӂ)X^-;5{t,M :[s=OyP&&e1)0C c,bI^-ʨKN[0[m=scۧrS ~7 +J!b1֡ ]ڳrN$ϻy`JxL2jg\΋|U{wU&Drfq8t'si7a)n 1M3_~åS/ꨘUeE"VBc;ݫbP%9T{<- { /m G#0zէoo_W֎:ߤJA%,0̞#ΰA1?Ll} tɿF>B=^GbvTEH\@BЁ#׈noz뉷=$-)oNSSFL챥`|X2rnx @ndTS}ݺqt=FuZr鰋i3A $bupīSH2@ҌpˋF@< 8#ㄸBvX,-˲}#0:tvAŠi.#V̎14/U&sָ~Is6Iߥ<9 RFFwNf3;h|HlC#5 #~/  Q1F;buu40$;p ah">(,CED, pɥѧwQwUNHG8ĢQK.cIfم@lvm]/N/MsB8$<$gekklশݧ2g.3`yt^AE0Y&0.$g|]->5rarh^/[zōRnq[3s1^~٫h MUe5S#`1PŘRXi K~ڥ8CVng_vpM[=~qgxməJdڹ20m4yY_=89R :•O}ǜ>~숁>Z\8nTGX^E,e B)J6:BZe;Ta G,ٳ %%G¡vS;' !1 18XiϖՖRI~U"'hk+as/?xa,z WL(N$BbE% )h(`\NOӰ m5d;&~/H.3-[ȓor+<}3@j_*iNb1E,&- &3 Ȍ5W-=g5oB,f2U UF_~j\x%K{O Mwy>+3-<(9TmAU{S!޲?33 c%L.)tr"ZbNXoXUa)c]W'?:+02pcIɃι΍hn߇a6GHLP,pGZ duQLcS|_5Q&BXDCuw@bfa8]^$MBN;򪪊&oDz`{W[+JSvz Iv{(;'BG|of)G@@J;` FBmz!t]onnv!LHpXGfMM6рQem:sňDL]…[Nhߢ]6fD~Aԩ71] aBZ:zc B0l#uAd@WA ; Mq)\ɬAyƚںikxS @"d!WN, e:djv(-קЯ>5W'ƢtRεӮ+TSC+ɭ9wbd$^]͸ɲf-+Ʊ {?y;]qB{?jpaY̒\pH!8 R"QKC2E<=>C1m=s._׶3qڍ?~Je5 J$B6>o㮔Q*'mCGē7p` GLӁ#s=?RlL !)$N":= ۤ0!*C:"*.U@a$v fuu5M]TS5WD4O޳!<[C{UC?>_ u%aSvϔRE5Vw=F[KEw}YEâfkEmon`1.*"3y|#t߅gYT-˲B0  @NYc:DHꕟ21(%^3L=^5.rA,E|A@X][5Շ-B&ڕVw'L)^g~-Dzr>Ə %G ̥%X"Jq!RJ݉X nY\b@ f1!R],o@H2q.$Ar䠹}.?(=1GM疶A߈%C'^z?z=?~FɘA)y(@U]v}OmT'fijd< 8#$)1#VW[r:QMiHD14Ra c9BP(zAFU~5cn*Dyl];mr%HtRB "t$0˃v*?8ӄvIar]mCr1Fxi#t]W( Ƅ- }s_YG{dRpÒ%*F[ء'{0B#@&)ǚϼ;㆐@[sKͣWb\PaEV5ZGm ޤ9T-Qav88.t&b&sy/z\l?__=))~Cƶ0g}osyպ%5{OwWzgo).z{քjxﹲFuT'Ⱦ()DQ@3FX5NBx22{xF{rz9Zd7ŘHmLpRԕ`'` "b D6`VN),U1R)$ 1-$ HtF❾Xu:rF%B&TX[J$ ƀ# 0`z練gkĞq7nʶɈqLJ׆.GCԆ#:.SnVm5)6$b;r0B0iNB>lә;5au3W4Orcݖ79Қ׾SVfSkѶu j]DSWd`f'玽⢴=Ց(7-0 '&,P_4ch!BX8Tl q i)QJ5U  1:*Xo]@3skmH !0UaP= iv!L(&6e Qtv @@wWU͐#g߂D)q(_ĉ3!%c4!{͜V=y̫~a ofnPQ7".WԋMm@⾉8#8⁀8NP4 BHDhm۶+EQ1.]jfJJJ3Jծ=[7Li~/)8g6_w%eabɑOwV8N};SI"\;`rBKusOMu ֬E.vd HB3'{{XHpF{l(xtI_1ckczCD R\=2~2&,%h#c_97N.M3N74݉Zkl/)LQUz^p]f֝T흗Դ~EpViJ zu;(p QjQ;CJ0T-qBH d1.-!A):1F#JE90&#ι:ch(BtS^`|{[g%)0(Q",Й-~ )i|Cp|͊XDwݻ7/UIzƶJBB)A9NZrA!zb òT+ʐӃڼ}e:07) Zc$#g_Όa,h!ef MI{a)4d굷:hrv}y%.*sokRp85ހp%#C3iYݻbS ,|`N*H1XpFg\u0履㱏ȴ|j84"e' h'ՔR2iq')@bB Aa͉ כB0Fa1H)eL Gia]aoYHv};rjrJ}8F]voݚh@Uf19oNJGa=FX$3MF@{ڎ@ePgAv2M:='Gq{]lmv9)999Se„ >m۶$''weο~:7 .D"g˼&W.)PO9ehb 8u9 ]Ιꉚ4)d]]烯S}n~L݌׮q5ۡљ؊D!EQZQ P(zx!vr>i}`&~ް_}W)\XUo{_[ λzhN B0b܈:)yTqERpӈ1厺f}GS]~ZI+w>Q]Zu N z- Xc޵$edg41] 1 Gr7ߵ?rR~tABbꀚd^SU[>ʳ&T R)T,^T6霳%opIϗ} OώlY{d'ix_X.peԅ-> Qq@DՕ-3i9GRL0B\JSbJ0P"O(F4˟0Y,__0U=Ci!k~YBX4Ԯ B# ʷ@F᠔R  ,$ " 2CF0\q ϼ 9w]90cg^Xܜu#r*EB p aDcX )^%NP 3~y3>~i^5 JvYV}3o d\yŰ`Bҟד3W~J.jr_&|0@ ξ-K_IOb1¡v>hX7/S'_9mx! / :,1ǝ{$J%fP=|=z~~  ` &]y]?}ٿ ͥSs>`OT1jOק@\H! bkI#s"PL>Zμͥ2>k[sC#_||hmtp_"@Wy]!;oG5o>Sҳ59P)*mΈUnwkFnYp滭G{,wb^p]tDS& %% 7tֵ`ç(^!~93^uw1O>)WhfȨYZ10FyX@@qtm69NJiSSK8̘1cǎ۷M6mٲW^vf=2,grfdF$C E#N"5 ip6F8bZ906K`^B]{0 DVAAg}1|VK)ED"W}9Z^.8 _~ݙTEtSogw>{ݻnjm _gZ,\wͿ~;+j_~nk=/sNcXؒR %BXg '?'ϼ_F}۷3٭fsnosrVӶ otbI:e4RsX$t70 &O]{X0jd2'.E[\x}yտ/f}٦5хuL8ӓPcߊ~qkp.3z}/qϢ>8g䱥!&U* e*@VSbneIg^n3o$ a%VTwƈ ?۹캻~:%5h4%THDUE0T*^21C^{nrH;9 Sj fg4; M%G_z/2/?vrٽ'If+7>KmS+>.8.6"H7wJ=?L›uCw^G=v5{ݳjYqgɡ0 P,8"D`^}w\4 & S:>CAV4;zGT̹˒.ϭFǞ{S޿xMQu~tˤYBu9sz5Ub̶pYB v->~Ui>hsU)EET E# RP! %NJ0XHԌ>`-f%qǽq-OӤ#aBUEdw/?1&C^H*W/ViکYF)RNQM6Ay?%"w~neʣ]Fq4$ӽգ& 4^yGw&6MNNMM6llٲ{%Kx}>yScc}}=O>~!~23#m?kE,p܌d$%==1Ci1Ƅ_ejĕ˖/; .8HȲRJۭ('|hѢc來p8cRwR(J{Y7ng/Ao֗ Z溽 Wds]I%G ]ĈMK"y]c .q83{^6"pYe{k6x/3tذsǛVA rDsÚp7I'cOnDN&naK_ ˰zh!LAG7}L8qd9\ɟ//7Hv%AMr %kx1HJ n}$oRM4fe'SF\ߕnpߤkdjiw=0 Kc|-읢3vO" =S!e?G 7ĂYJ{mߓazi7܍eI^͕9[r-HXW]#@1sǜuQ5=dYB, cBHJ$BԈSW1BM0iI]TBi=rզayQW\ه~d{%{ wVX0:„R.*!$B ݑŻ^YG/>ˁgw;]Rh"ǠK\8e` zPJN\\! gy>ZM! :TDӮssc<_uu̓LRJ\#%8cIYCNrS=•?Y4b~FZ2e\oư;ޫwgx~oz$3Cϻ6}I.hjy.|.6L#wU7IJ ]S=;[0dHvlٻGY\rN\q75AEj mB09L.޿m;>FN["[^!׬YSVVvj#Bik!uΏIq/1@tD;roS޹;щ[cܪbO :[(Pql~m麻$*A͑y5pr|>#D"s˲0vezH$1x<))){e777={F"E_{TWWw@nz}}}NNNZZZcccMM)lhhذa1.YJjNNNQQiGh4ԤzffR Ep&UU0U-͙5狥+]}ŝb1KdJhA`Eר! g9$NDH0K d>y%[tUwI:ʻGˌq)t@(렀XT )1đDTW)E @*1eX\'4)=*!ɱJda2AN XFILu]nF-J@2˴8r=aKbա+p &i0.%"b;b1ATXܴ5e4O@cƥTgiit0IB"ƅi=S8:hccinH*!1Ѵ7|=jEMr.*a}fsr*<uV A@2śo[Vvt&N~L3c\i(L#}" 0 kFV aDcN'R9Ø"-bBDԡ+,#ʑXK J2Mj*03j0;uͨ)Q5#f`@2# X%U4UfA[fš* ,SUxo= rd}X:JJCmKdϞ -rhvH(r8}1 %o߾S%xrss !RԣGĺ:;k-[ƍo޿ommuݽ{bۓ̶޻j.3%sadPEWUB楺` oOR|Gt/?7=U*N$.rDUUǓvmb2R<# oR lhh,KQ{z].Wnnn,y뺮z׺ǸT;Mj0ӿ_xvUU!m2&LFtP791ҶX,i]#\վp!B˚p5Θfop~@U\RGTt+6vLГ{3^NPRHTBJD744tK)͇.1U=}.B EӺ>8TMB`J)H)%&FR TJ)8jJ%H"FڿNlN2*?v7x:v#NaKnFi<qq*5@vH$ 999*]%T œ1+FO>}u;C{$h*:L%pW[%ԄQ6j9o)% CJ`ŽPlƢ ];LB*JM\JM% (*alմ{y>*_![BJ)XT!N:&S TѨi*A=>(!ЀcңGCZ)iG\ߙ޻bxw aw84ΆUZrWG-/@rp!wqX?lq| DZ/]&pmGfz$3&L8J"??? \nݮ] 8?8`b'CzNhnnb,4CTS2ځ#ח3贜rZ΍Hq;>]5H ۝ no} @ LҴ]Ta JKKN^oԤ~be?)aGT n邛n:JJ|-ȧtLL6`B]Av99(s8J1;B~|/B$%%57ܹToi'X:_:`aKǺT,8Nl\$/;5@JŎv08555B!Ƙ]D~?Dݝ^pnF!7F"4>}?.^h[G}[!nݺtaÆeddğҡ^(Zs ['#WQQtÇ4q! oad uz322233O47fG'fYֈ!͘4dR8^@@PEUUpgn}B=Q,ET{[anW4bE"-&͡Pd'\DQ;M_׾9&|q|m|81,{t8yyyMMMK.ݿiIIrJJa!J:!W=cQ_o:7=&1)NQ䴴X|ԴS>VW߸ԇ8NNMz8DΆ`Qw2Tv 8⁀8a"mmmUUUBs{;vlWz755-_|˖-} v`] 0 $' :Hٖe+W/):$Ea?cL) mބnQ:}"꺾iƏ>H|}f@_zhgQ&&&7>;;kc\TTg̘$&C\q?Θu ׶p~{Իu;~?a^@"P lqqleUUP,f#.B9***6nܘ{i=N=vi}7lпCXr7^{"{On;iǵ㏿2kT@xJ:;Y0DV_}ldwuwݤҺ*F'w.aUUh#d)t]?(999Jw/\-/rwJݑ]QG !+ )(" Eأ! ߄tp8Ҋ1rݜ=Pkk]\.WqqRH!1 .x<%%%k֬)//w:Y>Omkj6MN9!\Hm<@0ARn)[P[wޏF@XlRvN!&s# >@Tb%T!NMC.jxWNog=&a`qE,k"iI A<i3-&Xvx0Hnq!ahfPBUݥtrJ۳:ld2 MMM֘)t.]HҔ(?#C& :CXy;1t:$G; G/9aҎtJ D1f | CqHA"m]8zFVnr!e-t?y4@mw76lYxۧvGN=;}.gbcL`g%2=D;51뫿m{#5 #pծy}n'; i/JA$ 8Q!ivᆗ&v@4#PRr. TU5--%55ΖcĀHC+du?LSL~Җ~O뉕9m 0v⭝ޒ_,N_l۷#C8z}ҋ-܊u_|eST2Vf'/}5 3\?Bo޷w^}s^r{S{Td!1!Dvy'vC˦/^3頋H@g]~wcBH1 p !%B D 3`!0 F8!)ОpHShJCU+WΟ 8dȐc;]H$'TTHMK0~Bzzew~Т觟ؿjj222E)njjO곲Ǐ1#S!p9X}zN/vuA_Lٕ[A9|fx`2lAyK񄎴#z]cc-}t}x{yk|w9deT] +66P-ec- ?Yٿw~鈞!,(Fͬ^zM#/L{=[Ʀ{M#,:|A(Z}ݔ2_^GF}>V ,Ap˜`)%;s!R P/kߙ3j2B #Ҟ=s1 B x6 g7ϛ7Ѣ _}ꀎ:%ccWY`acS_a?q㻦#5k,X0.'FhǚƷ?䈋Ɩ/_`|XiiNe/Ie 㿩x;^~j)>qtq;Lr }pwp-Xŀ蠞a]gIc(Hdͯ;t^4(kx;}l3utk{;`}(JVn bڍ!x vkF;əHv~UUm&91H)圫CP,s8=xꝿR˺/rϻGiXHG$\8{WL-LvP~iyrg=mўS.OվUo^:<״pˮ~<b7,]SMɷrM`CR|^',bzcJDa;p׍we#\u7ZasteWB!>{˫[s/KDž?>ީؿm܄9>VLQ7~~ߚcMמ6S//B ew`CsO+ L/W@B-۹uQRRpȐis[I+'Ji֬q f 63f`.nN2;_El!,]Tf]=?Ь6T=`@ Ȏ}"0n//8YIkX_;ykGĈmXaiQEEŚ3f&o78a='_R濭t!['ho t"G/_Uoy[ :{_ǸQ7OGeHlHS]p$#}a܌1[ Ks!D9GaKq&7o{/fy! *=1zz tZD"P5'* b`6.2ˌ4p E4W~5VPoFj#vXk3b 'e7D+w-Ⱥ V,lr@hա۶.0b5kJ35#M-Nv]JJKCLWk:_?6/c S˞xy\Xg]>{҄+7e 2~dt#Kб; Q;*ھeZORbM!p PuM;wP]Sq~0AJ$cCFu{*CL g&] '9,b}z&1EQ)HtDdJ0c,:dD(D2tg#nn29f{էAj1$%hl/ ;}IB#TѰde<LW `4t)%H0(iG6FM8u,>kQey#tb8D67Λ֏PAqĘRE!cO"JGuάf\^l8#ޡG2fPth;t& 6Oа' x#bGvXk]KsuS[cT;N.{g=u'7Av"'9ʅ;N!R{0x Bukm !GpEQlJ)!R ig9s uuuv@cɥJF!k +M JTq"fu!aI ťs.5_x[rcVoVoP dn0a9}}O N$?0Q}c7AЉ먤عu{¼lJw$B%yՠ1 :hsu\!"-[9Z#; 8G@ [{喬r^<-OF͓Pc L8߭Pvͮ1w`si%VN[-vUi93v&`ojaFmSGX'KprẆu ]HP{ ߕ[rڞ#9A1q;0p2& !۷z5ODfGzⰪezjso/ !B0i=" qߧf'Mz7 OG ks_FFo_S{w;u3n~@NG a9vUx֐ÝpES.{w|AC ƏGlhkd-X=/ @]{?YḢn%JiNФ-78HK,Êq$躮iZKKiټ/<BH5MTiOkm\QFYY0aᦆڨiL"B Y&X.ܚrRJn2`k@Rdr%V/nnfϸY6\OCXC}KtSZv'XCo LoA*@07E%Qc# 1 R4W#HHޞgyUϺ&Ț֦){99p +RkmC #F[B r$6N嬋(*iHQ_S;_ӳdHޠ[+, R-;~Χ|Ź}{.!2Iߙffɣ ]}F%J~+2q\84$?T4b{5\!$"@ʁf79k;r R%eT0^7-sT@])mLZQ4ݡ)b)FDfg_s$#R%2k3VCB 6z熦ʔܬ}N77̝6YDqzƜi:,R!|4BPiuƊn_2df45u+|ݟ51Q."Ҋc(J TM+tN1bјԔ?B3QEmK͎3s{u0wmڲz*s&s&[!Xj>p`DzwO+qy%7YvJ8 ŒDb;ޘjSP,ZSިF(ɐBuݩQh̰z)#'CRFDyuÁmBMuy=2zkF6]~r_bj3"Q*Ն1贋3󥟿,94#IzOt^bLqw1Oy?;Q'rϋ0  JٽID؅Q w\Hqcȁm~{u;8!31&caq#Ϸ_FQȡ"UG 9ء!Ѵ>m!A oIfH{ Y΢_W4'rhH HՐgq( "BH4M~"v^WU`…sz{6$^zѢEv:Hӗ#{֖FI9C:o gvyX[yoeSݑF" z']JqP`v#kٓ}98ƴ.V@F_ѰԾ^{[= MDM8Bθ?pZL4o|*fvզ?,oZ;GeKuDŽ;3'׃?gT,ߴ)G"*6|}l3sr:$:3i2⡿{﷿(Mdk׺θ}L}s)F#iF"`  &dDŽFN[$ZΙnm7bsV-k`f~Vĕ`7vDí)&]o4VujUn\dŖfM*6yPbu;}}+m҂CrwHqrШVSؾeOIic]U2jJMŮsgDcFa9&aId&4 *I޻j-{ ߿oj@_l'3E?{kB9^V>[ \4zti~P Z ڱf9fRi{dTia[`|/"[ȟ6+JGO=g"H  &%`Ɔk(JԤѶkX (Hbݥ4[6oƲ&o؁Aj D+[=eoX,Ĕ>JB!i6oٰvF޳tt $!uVJ)[7ݺbNJf޸3xl.7ܟYXKt𑴩 y}MVëlG]n%лDmyw싹3*r&IFR`Pdk7fn گW޸wՒ/?_iu-8;E_.` #-H$]EN97}ˏ l8щ2('bTh\<șvylßA%@@nHא"#;eu*AX8|EHw2; B (@p<# D;]RJ|v0`xoqrssSJ;w?~BBB^\.W& _?tL!p!fXD|Ff8U#/Xj/`n%3b1@Q3& facx${MpŅ4-aD~3,$DQU :tDv,Rp)|/|f\.{T<}{\CVTyL&q]Z2Zy=S4q*Ir``VR!W)0%@c=wްfq@N_bڦK#hѠIzj֨jmd! ;{wp$í+F-s/ZբжֈBV]HHɸNO;BU҉U{-^l8kӮm2 OnZ4Z>Ve"%֬;Φ2Dus7qmWZ,cӒE-Cr{|/wJKے ӯ6olǢOf,p$}JǶ-9kqUD-g.zm/s!!&BcbĝV2jEk/lٵm?5'k†E5aHdSmӋI9sr*.bX'+~k!l<3kzej{ D_]Rvn.+6͟9ke_u7)ㆈX,͟5贺ښ֬]v[Y{[V᠌[ cRHZ+뭰WS Fhݛv qgjJv*#P ϼgW`B) URQ`[np} BCrNux 8'w'F>;SAFNڅ#Ժp=$אMafxkm3' }ī!,@7=zL4a;W NhBfXHog%#i|ԏ`҃ğH.,A< ]xSSsuu>duH)׬Y+-*JJJb߷o֭MMM 0!XA=nECAʃݼ$gf퐖hEeYg퉶RvVG,iS Ha1f"fGV19+ʤ܊aW<:a2IN#f 4=s p%b`U*fqD)HfEMCI9yp)>sMPf13>X fv5;{i0D]!_y]'?IJI5MӑV}@7‘ ߂hcqۙu#)EgHD+ #$`edI4@JD%H ݴ#251`AJf :@"tJdaI HACq(XRKp~rD $ mPRʕc`BI}֖F/Qj^m${wdvBrF_K &f& Dg8vZH)J*ѩY2lZ;0FMt%f2bA1J)Օ'[nOT"@t_J&)VΪm FE6 2lX#/1Kػ@8ﭚDPMUTFD!FSͺYo\))gbSS&H+tP&ޱu]%4,8\uax`AZ1>ZF1n3Vϯ~=}8xƅC?^^i#`DsL)XS3#AB&'q!cXW1-K>ݾ(Jϡza:E`u7Uoտ__ ?;"!c@1To[N<ѽ)O=j؆w%LL;Z͆NL$e@fF`nGqqQR:5D!#a0 Q*r#UmGuLr%5.Npl8+sT@C]q|wK\֖X,Fq:G;X`0ػwp[[4;Nt:E8^s0Ƹw}tg|;v!?Sr| 1§B,1CWz;ko­ƞѣꊊ¹ˡ`aQg&H3€6ԭyeT 2bƅR" .,v˓ѨP<:uPн וo[9/%ЉƹcBUW,Bc1Xe-5")ד" Ka I)8cf8ʬ% ̒d"'!3\wym#X3r)Bs /ߺ|vrv RshP~d^wnARmч}Gg{&>;+_LJ@Ej˾etcfHTDŽaC~E>=7-U^hˡxtA =9X{CƟ-R ꖢkХ(B"V8Ml(+_;GC<;p!` FDJ)$%43/ӧRiB87:88QأM t;_83s%4W׊>ZXa^4(QD4Q(@<Bݣp8@gAP 2w̅jm=%@(usjJE:vJ󁝱hdؘ`,uݺ=#@J>>gw BJ!,@Rrn73'ɔ.5rh QUлk 8_!,Q.ǣSv@/})"ZޙSx=vؔ͆h+0cdI(%b%2$C8a1.@ @jUYڳ3Ņ?\涿I }z#Tti)TUpB|:-R۫%eZ#RJ[;ܼ~Ͽƛj = H֚P0?" !$ XX2\H$w !]e{7.|y~Pӆmeߕ@w~wJT$OV[鈱=2SZ &x4nW 3cȐsy:%e#Q`){zkM+ؾ-5`o_~$$`ƥRڹQc Q8#fq+ 0`5(K 0LAH!:̾*Pt >h぀8'еQJ BRJ9N{_C51vY(RLDpyŮt!G8*1OGa8zWʹUCzㄔ4S% &\HBy 7vmݒʽ`^e;V^7\ñӡUXf]Fq&duSB@Uߠ;|I p+pOSis!w3T{:@X#0Hni;}g7ʛ\ nڰ9=}FgꄞAY{љHc )% Dj_x-`1!$w) E\>G;B+X~7!΁1ŴaU\Qe OB1%7:wHWz+fqƘ_u6;9bs2WfE5V=E RJɸ\HRpi(_xk95KĘ`BHq%DR&ii UA &%},ĹbQOĒ⠸̲X4*Yæ\tև~/>vN_7Hƙ\$ko[ݣ~yKJR4)cRpf1j$猙\0YSJƧ{|ՋOUNq_v;:ۇ)1|ѣmw2oxlـ·=pЈ)GֵTlZThR4$_n\぀8~B4N39)9664vut( Y1Y? +Ta}6".hJr'$F8P"lރ(qxȒϞ8G uE7]s79>+ /i,LpaxA( 0ođ_^1›*y!j8 z@ >/=yLai@֤ooH)Ua.oK)=n?󒢕#$gPY}\}YEc{Ϳab/G}X^SL Ut]S0ļ)S>ӏ,t;|Xz`LM!d$Z%{}!<޴,oи-4%ȪB@Tˡ %gOAG 5 ǐo5W?zHd_O}̟?S`L ^ݙ)4G)˞_;|ȡe_>~zY>Г a$C3~8:NK%8#HUT4N!0U5]X1ӓUt'g r[Á%BTsj*rH5ݫ?{G7^yǃc2B_5qlj6{tUp@Q#0> 8]Aʘݷ kS3^0A"JckQ<v(04MCӴ;wVWWB'P[Q)Uiػ9ן\ 8Xۺ}Qm#$CH,^c1} NHM\k*\6$fhB%mu-!Aˈ1!QH-nhsӼ'! &~sK-71 p)n#18k*ۻ'C$YZhh5,=/jZL3!+ř]r!Č4O[M(JƤn/YjFŊw9e?;4ܐ24 Top5)-5@'O )\'S&oiF&%(4 k޷5w1N,iK0ezQq3Iɽ' &o>/j KOsHTYD)`x{yjZ ffhDtf0dq!C NG}&WG&U)ނ3+{1A5#f&NJBRJ)D@ѼwoI V4>ZLqRt&'{sy<2b v3.qZę=V4뙵C\ ꕟ v&uzd.C fZۣ80\@RbR[&oBj09 )8ԕ^/]d̲LC''`LKR"N(MʔA0)10IRB {Zac VĄBS)"aq)RH Hr˲,fZ: S3bLԄIBaB$2M#SORVl 8L),.I LSH)(E=sWoX9KU]A]E1|3=ƨ]@ęڳOX;?(J8q1kŌK!8>RrD`fO,Lq.%q&Ă1d RfbJsqljԄyp۳zӳ }nGZ#vRD 'noyt8y>[˰>(}e`'{5QD0`)q!nB] hiiR~8>4nCcSEtS|s/0hĠU5EX:YY)i\tu$4՝߁[F"4::|'MrrUU:p ^ s.7[Hf<ct[iuy%b:G2yd@r20b9b=VƘ(6_`mdx4>!_I )  ~4RJ)BĎP%'7/=# v#?*td(9yiߩЎW%~p4-G̜\qLqi&wu'^U'͋ @d@(hYɒe|}w|w?',+X(sADa#ygwtve%~Y-3=5]Z0ƨ\ζ \)Dm]2yfcu0G߷pNh?[zA! qG_V0XVL~_o/nݳ5K߷HO̤O~7^5s^s;ohXZHG\W8/1T*r9+B`h 87y$:ݹJ)X99^zբڿh0T,c,W]EHҌt}#ƨ9ߋ -s^md-.j|D=_X3p@*;A&6̉_tXO >jYG;N(Cl^ݸmk pNx`&Zq`X+s՘|Ţۊze8{o}?5R/ k¯d[%aToa78hu۝LtHH*F8gF՝&:";!!cbK;0p8L)uGb˓d X0 :DvmO IZym_,@9 ϣG()`A s]"u !f2 Ƙa"*xQiR=h[E@@͵Ho+$,_v2 cg=g?_s:m+F(I$ut?%k/~ҀOֵ+c9H EM?ڵViUX{".Y"7"@൬1b.r=F)Ȑ6Ӥ"aeeYBA  Y` P'+LFkXmOL{@whm2 hJHgwflapͯ*ZK˿ ELrn{g>i} Xecw65qMH1ė`^jnUK zP{'0_/߿i2c% I"8 DF)YA\LH{QUL&399)IL XGcOCEV(B $fvl`}>Vbe(bQxM%L4$Ft =] ͖h%.E*#$"zILD)2@q@-YrGsPl`=LBa `M:FHd7bs#8g ;CxzM0r\1!C p݊^B'"BO !F譻sH oF1Ms64z{m\'_cH=r5 .ش5]qiY!I'v>zOXzՋk (’<ۇ\|c-DVݱAK+I|KK !8뙇oP 6^{Ylc=o@.Z#.{H^93w\e*9wv,[3_.cUV1rfuMJ0&~}{a`7~ńu8 b N+>&GeV#MRsLvl8lu]hxz0H2G,I#a"kN湭ܵmw=>^T_o"nOѶuW벮J_ 0X-O|CÖJe/&:xd(B~=p`h(2$B'vo}6{94SEI.r|`UK*N_y;z//[ 9vбќnS+n",8?=ۓ6^n~BڲxbscZ,X#)k!S7?zl׾ޜJDˇ޻cμIu.*I[:]v\*1Q̓w=|oyౌ[-~;Uo+MO L_;l&~/}5oO?p_ABlQjzajfCCc xs?a$Kl [6o~*6cƘi|P8w>cH,RAKٹr%2Ux@TSc%Ijy߂ou+gt!eٶj*oxÈ&J˒呛8ܷ~uWmvr#l[6B\"ؖ0k.DRUUFi!LTMC8.;KD 7ݙ?Ow}Kwޞu"1I ^ޯ'Ԇe¥j,(um(EH`R$+D0۶Ovt5_ F1[jumv)u<_l П-n y3Ծ= 40C(HƈQϱmǣ1,LQT0F]۲]QIx[N\GīL}n< 64EDض1ư$ˊ˰Ƴ7вdx姻t2*K!%Hb"+"Aϳ-O*ܖԵ-Bfw>AuGjq6/(3CȪH`F]ǶtM"/v1ǞE1eU%Bb1dw>G_[>KOZe!03Lb}BHRUQʰ$|yx]w= `ew+QCHr.!,)Q@\#V)*BΩN TrkoUcDZvXU0D"A)F `(#Jiȷ:t|Kd|nLF_q)r0ZºH4T=6.Ʒ>k,ru۶lnsw,Sk_8t?ލSqS/GOw<5.[c=Sk=9S5H zbC[w#7^sͪ'#;+z]>T>7?c40sbp嗿}B)[FJH$'} wW,k2MyĸlIOIiԇSVS.j硊#ns!z]V!Z MoC:j# ^N̢l(>'_ۢ{#\ِpg`KI:̳'[VDE-):7??zD_oDԳsceںi¶$3KCr+!f5W׿ԯ7.Xol(IE¨G 5 B<7 ƚ4ox<>k.S?C/]fRO?6{؍7lXMnguBKʐ;]>lc{e}1f&H:ZfZeӬm%N@t wϋ~y5 Fa[zSD,#'? yb3G,_etȳ&=fEqr<6>8>^i?pH8}u=qR:V&7zg/kI=\lAO'reM sf*1 H.,f=oʄٙ|oӟۣ*r n"нt;?_\-}"Y҉=}OÇQӥ-J6>qŲ4lz~ȥxt'fp;6.mw?ыxE -K^//}7Oڲ8S17}Ec۶ eV:vkVr&W+4F=ϙ<Ӻ>~_ˏ_<{,Y3w"/wm|7~S7,~ʌkꟼkuL*~]ڪ?w IRQQv`߽?Oկ~3K3GgۑHmiWq|GO DPأ<]_'k'?Qs CxܲkOn=Tzo? k{2C'9;vVlf+;q|K_?{]~_n=>n1d}7~i&w>'$=O9V]6mxD}hwqۯl#mOw׎W~3aOs}c}ω_~+V74ǵdM Wr؁~|ʬ8F*AuQItD G]Db0s$o_~}5R܎ x2DƬ\M+>7~OzOu{yf?WOEM)zcowW!\ۑu7ͯS.߶s``dDY5ڮ׿+ڳ{|!& pCT. I,>}[{yIzY#.Jd6HTa)uҦ34089Xk/Q0&2Br1s<m?܉L>:aYWjGK=Ǧ. zmg5%mw1:0U3t0f2ZH^ftvH" AUblxoa=|¥T#,y>5!f5ӈ9N7S#!1n8XBjÎѺF#@8j!Ldv)7o.l4.{*MYrpi?+ɾt,. Ρmgek#jz jG;=u#]cf1' ޾a$3-]e@}/oZB>ڔCJz$ 6 Ģ'T% u#8 -KS|ê2qәioS88&"95޹/$-gv,Nv#C(;Dٰ!PZ`يߏŃti)d'{OA[lJ=-؁%W/os*uXHB & 0@&@@]]=Ms1ѧڊ!)Ȅ^P5S^v]'%\2xbv+3ɬK;/zم#DKuw_ctቇUֆb_:*;K\ڞHopb 5Bn2Tb+b!LF1/_6gLqɦ]1QF-1Y-?J˖YM#}賿mrh:hrHƈZ.\SJO)DR`YH5B[Ƕm 19 * n둛o@Ë߿V;#cE' QJ%zLZ:AEm$cخg2L1eȸxl'tc8XŘ)2%,)Z)KFdڷ]cT5SJFw~r5~xnxۼG003i;vZr/JR.(5뗴FA,IRc:VȨ( z!,)D1 3cͮ bY& JU:`Cb[sbEx[Њw/js!1&$+Qf IS$$Yˆ!62TMN0vΕmq~n;;XM HHUHj@,iYV#[#Pzq 0Q1LѬ/Fa*Rbȶm(<!,aY""V+iA.|x7aZ Lty6AQAIJB$IL"@,˚E'>9rӱI:GCSf1vhhM"M;DEź.YcTk:ȵt#"(dYɮk?+N+V\l_:?yѻa5|󍡃 []9:CG%-ְpeأlL]5"OJ]~::cq|ӮJ8O5KC1Kr٥@e:caLKcLR4&Y"?ȁ޿L&&d{E,)*v2,). ppd}O{GM5yI4!<ó uhP-g|,j0, GmǛ D,o^&xrɦ=?O\^'3/Ulv콈d͈5ڬ꽟c+rOzܾcpZU*eӕ1!게˨R!G1YIfy}SRerJ! E !8FQOm3,:C(|淥~E T֣Ys ۽}pmъcZ1-)Z/ُ5_xdӼP!մ5j, PE C1]ŨG}?)ZU}tåWAU).rPcKtceC<kzt;ko"zbyd̲kkZZZȑÏE`8?tg|Ϫ8_8#^d~wgTy+ޱ[2uA=Ľyek o[U[:k߾VKDCw}vnr gwuES7uɑjiuˮ|տ}|fΎ۵i U%wh槷ekO7v\_5^rgR^ȅ/ٶ[6=וpmm?Q]GLV\ӳzŻOmqe;}eImO&6^Ѧ+ز<Ӷ@ˊo_[LxnXZKm+Rf̳,bz/*.3BmgWcDU0?^t]kSNjVoXTV1IR`X,r9ye3rǺ 2r#}Ջ#`l<߻kVlG۾w`+c3cYm˖n`ylɑQxeÚjngyfΝ{gKzڛRkz۾];w=2Acoԗ_Ձ_]ex*'Y‹AՈ5Lvmۺ-׶/sAKc 珿m}K5F<ѿc]%JC[xa^[[w;<_Ͻsa\'3p`&^v<q0ɤ8Sdfb4صnic,Z;g K;jC //lmnik$/<L?kjuL8||}Gvu4ͨmSO={׮e=mQ;1i]|P8c7I۵c]kmح''FFQWkH+ό|psU&y`TY}કM-O 4uiRMV6UőѮ :m]])E1`F]R&kY.(oy3gdN!0s{EqkR)q&&&fOaiI"a=NaD0,ƈ8$Y e>#;Fse fԵmIHȱmdE$ 3j;2YQo`DgLV4E^vr SAlN&hX,`9&Go܋7bc;KeEU`Fݖ|aeץDQU h/tÞ]*[.Ú"s|ߎm{ǥ),anmjjj(c(M3(MͮK_M\2-[ ]@Ce` aIDT F ax$J B*Җ೫ x&f[xo5rJ|+/ky9)τ Fg,fAcD™7ϭAN>F!dYum#J0ưe1Ɛ"E(Eֈ $hV !(!Zuf/B@@ E0D"$PJ X pI&q<.mm55!UA+бc쩧ضmz}Wo "@AcIt]X,AMӠ:@8(}\{^_!$P!g2αcАkYueQӤCmϣW7 A  -\PQ P6+# pa`F@ @\7 z9ZH4H$ƴ*#Ke,dρ$̀`4 @@*VUHQ[^ uu*y7͛Y.w^=#2m_;@ @UU0`(uZA"\>i=1$ Dy3<쌌8'O٬73 yC=RQJ=2ILH&uuu,ZT*%L=oxddϞ=##bQ,X4Jihllhhê隦*˜z㺔Rq9b뺧yp8 ;: P$QJO8qR';Ƴ<!c'Bϛ ֆG4 tylt9O޽[(B$)'A :oM V#/|AC)ٞge2Ԕ=9ٷwc,~#iqGGGkkk,bX, JL=ϭގ㌏=|~`pxp/ z>ݸZV_ (np= {X&s^& 3nX @D# pCIW^miI!Tb-Q3]#Gܱ1u=s]s]Rc<(]]]uuuRC&9v8m𦦦^ב};[Zl d2hȲ1v]׬T@͛3y|>`555ә˥ HBD2YSSc,ˊ"KGy!EQTE$)_(LLLxkk B5bkmؘr\8$vYA4]~}$f뺪(8y1-[6o.W*p3W?r1nX@GGiOP( ===dN8q7ϟjhl\b޽{<(]]Ju]۶ VYJ¡P,B!Ea.ԣTu.+ |̫VkRիWᚚd2L&(Cu,ˌ1qhfP?3334Cf_[8h5qy]6HS x+B x5oB,˔R۶ư,s]"I@LMMܹst: D"]%Bp])f>ϟ<98mۢ@Jjl$.w ]wB 6iٷ7e J<,DԺd0B ! !Asfk^krs2xtq] 69֯_H$ cχl!.؀YA:iV*b'Gt:D\b[h\.wީh4jdy[T2SS۶m;zixk ؜5kEQFQ_Xn\. j 񸪪r||ܲ9T*|:qF#몪o6g6@YTJRPd2####C^̆U4EvSJ "I$©jh@H)^r]/#?~΅A DɟZ[J!DLosА8JtAG@]]]8Fp8J~BMirJeYRxWufdY"Rk׮aqcp/ 2IټyhBnFF~ #b γ[?koŸB*;8RpxSiںu -:#njD"\ c\*dYVU!d68 *irVk0>'^  !؎mV>wR za0Do ,PUu||RFx8@ ' 0n*UwrX,0dat ./FіeAA>L/\NQ@@_h!xdd!TN[.JA09qɭDJ\.,4TQdUQCnZ` NƘeK/>rHwP(B18%ccd^t d J8&ᶶ{˾[ "@@ *U.P Y6ܲwLھؙRq#G\@$YxÔ@ 1cL$XMMMmm-x'RpX.ZMMM JbÇv5o޼ /Y^SgG"Ygff"MPqBmA[[[__8t]#(ǛyA>&q!FR< Q7:Bfq\мyRT0O$NYn']299911DQdMy3X8 X$:t:Ny tdb<<޷]r Gr8K7IK( 3tN7"TFD͚cuom; ]o6t] eY'<ϋFx< }O$R)CAY:#r .;p]o``i͚5=;}eUUcBH44!ڇM8G* Lym+ pa޼]S` ? AޖiȎrD漚bum?p`mHu]U58O>b@Q8I$M6m۶C1&P R,GO=ٟ"I8 ^{-^@?ǻ.x}!}7YE`.mS>{bžYqfE@U iq0bq͕Jw8immmhTp8 p[TJ!$NNNڶeYe,i1ɁeITTx,,+󹩩}-X@$_(J s "17)\y-≉[p7mmm===Hc:@ \\"L +@_y %JPP,=ϫT*bzTuMSQ,O8h"0;'@IeW,5Mt(X*\B)5M,RYkjjݻwJB:::(Aa)*)0/1=; 7O}JJ$p]~e޿ "@@wW~XQR*W*4~weZ B/ իWGQH%֮]0͛7g^x<>o޼d2B|$$wj!!۶hmݻwΝ]]]w p4MYD2SU52,˞-]TӴJU @ƘeY.5Hr8$ H q NPUu``  ;veqG )v$Yݻ{zzjjj(t:H8C à  <&)\܌@mی1cJ\.J%#lH,IDUJ rъLLLH$E1 9'@3 4MSUUyt,[< @ _P0B \MMT*z5Xܚo'ҷ}X4Ao}3iUm@(]TFF*7h$Id2L0 )ȓ\Y'xB4X,J@!jggg\{M"@Sp0 ^8n"@߭L TGq?!2FVC!y==A^mxm.#!mW7oܺ*\(9M,joou@ 5k8p5/X  =x\1u]Dt?pX,=z5G> ICn(< H$|~||<Yv,˂_R * s9j/pP!\,cuu]]SSB055%n63p8]WWFCiO4MuC!4|.*M5Gh7 C$(S0`\%d6(rs*222d`Sѩ('/bT$IP(Èy^<FUA%$ض=22H$c, &<0mЗ׮c1{--g>Cg& nh<`A# T,;v=8)`۶-授FP 744@dityRiӯXܼiӦD"ӶO |.:wy!''٠qX,611d_˟PpOA[Q& # Ih 'bൻD{"PUT*U*b@ֶ  "@@eYj}🊢XUfM@7(?>i.s>8v, .^:`$MOLWʕzk7@y(\ pllرc@|*W3}8v2 B`9_WB4]d p!]x+Ԕ浴;55ur*OS:<8Er_:;a YW]EK'! YHW_*фP!ʑ#~`=jە 8C= @:Ì%KB&2|Hj{~WrZZZX,>eut ?+JTQpO!泲Q2Tiy(M$$yh{tzzz\.C>P.ijR9yd.S%A+/|yP( SK_ @10ba"D`A6w| I8\β,0c*8D`Oxu28HE骫zl+& ߽x  DL׭oKe BeY333x "˲|L&x)1tE**,?ʯ JFɤaB ^}́69 K40_( JH$t.;x gMOO \^p|8Ǐ?SD[-}CDByԶ6 #dNOW{>F;g ,隮j:njj!ՐmcRWWK@)T4Іđa}}} ZMʲf˲m ::: qmPy!e:]rV~w%-f Ż = ן>mcccD*##ycC/ 'O~`;;;u]Fx<1=pa0pO!p+4'``v` ÿr֯PX.[n'MEQgffjkv.*qszC3ΨT*x<)>@mGyAP\l5B1:66^PXhb[#ܹ+2 _7 Ppo'tzR[ǽNL7,K@@w7*ZX%<3ئyZ8s{^뼦x, !;C_Q`0DA@, .K,f0aÆ qq~M|P>!G'8ļyB΂;.P/JP@ `6,(333OV8m. 8չ!qA 83 c$) C@ ʲ 6H:NP|=΃vNnKy,^ecbqllLu'HVKT*nX[#$!T 2p.5Mt=C tA/r2b^ j yUUꮼh4 ,|8 QKhtCup87ׂa\mjj* Jw?!">O'TUP(GMLLRX,]b( }ZwJxՒ4'C<|P!t]#ۚz{{|"aAA `kۦiz[*naYy]*n,RT4l2Ms%u]YW)˃}}}`EŽ)@yoI)B,/YN$~W rwr-BuMw[,e (3Hoз\.Cf[Ӵp8 Q:'EuAб|UUGGG(jBmm^~ypMY|D򊢀S N$| o$hZHTB& p&\+WUj^|>OƓl U|:܂+`MS_HZ=X.;|0nx.@$hwi~hzX4!$,]0 ~>Yo)eAal6;44(m;`J=b\c UBsu]B,KGsJڀ> !2M4(۶.\L&J prl>t׮],0m~.Ҍ$ |>m{@Y)Xmxxs*5&wFhF)'?)/J@@`7߷]u\f|)kztDʕ+Z[[A 4͡!H;q,AG`0]0/PӴH$fs\0eZdY󊢬Y&Lr  _(Ӆr\, W(é򗩪jYQpҸ 1-*c!'I}D]]%er\Dqjj\ŋ744H #:di>s4fYq[e8fff:+l\6;80jZSSS0FyÇ쪕+ki`[6BXH` ;GQ䪪 C(0#i:m!?åR0 ۶x˝#hnd16[_Nֲ~vtt1@7ϟ?m޼y@,'$z/J/$g tB0qMӠ LFp!C ^?Yi:2G?*۶ D _Hr !f֑#wX;vXǎٖ5+ X|gx1BK.1:'Nm}XVS|5Ƹ\.3Ƃ|Z@p`( _hhhm۶vd21|q;5\_)xq4ת+T$1 1x$:@np(k: ZPݻ!peX*Bں:n*ae߾=ϯ]4M!Ńmۆ^ajjjjj*+e۱eL>B2qŢ\sUPJ](Pm,ia'^ ;CXjkk!ކkVSS_玎K7,Bn9]eh i@Inf<A-rybbun`3LPm[g !<6z0FK/"FG$MMy7$J@@ 7HvZU1~&&*XB,]QU*ƇN@US\USXC$A2P(ض yb1 {[ZZyxjI(<0l ap 'xFs V,#atX,jڬ J%8,˄HB!14BSSSe)O?UAs,߁a|kxq2B4[=qvkd( { ~ vL#]:۶_?PsUBDA!G16 B !C@ ʍ.>{`ˆi⹞(H% r*244dMS] P UZ2SɶRX.W^*)TU5,ZNCQ]Ӛkkkn0 - 2R~TTUKTWW" l6d-Z433355D eZ?kU.J'!ףhz?K%B "@ ijԴB !+5ǺV+?8Ck׮]x1L"_MNL@Q%Վ㘦 ~RwsMGWPX$b1c( Bj6LO )n R"C΢8cccqs"@} -nuq!WJR)UwyJ%I y!VZSs'ϧi^)(ѣG HJZ kk&x|g|0dY^vmww78PHUp8 k_ޮu=#- +V&d2 ! 9A0r:GfBBB荇L&o>˲ }RNCHFUU(J0b8$ùFOC`0P1VeQb 2lW薂, #8z²,jYV", 333&NdH:qU` D5Txpp EE>p_iR)ض],[ZZx,rpP(J%q5.F J?0r\WGVf8e ~i 䜽/   .pg&UP0 Bee`R ł4Ԝ\[Od&&&:],a #`@(p+/Fz0TʴڛuX@@Dlf*d!ds=Rޛ CCCtff)ܐvg~h.耙^0!Buݶ6ݻžxKBt?55>ׯ_W'I>U14j &8n(`pxP(DFFFBP [,shnnCmm4dwk_ % C$BBv6k7Νm[Վ*,(`0][[[WWW,fH5wX ͅp@^A| ax@MӄPTmS^ ¦e:tȣ699Y* ކ|Z]]|.DΩh_Dw!?W*3)%P )cY%+YP(y^\rgz3yd&IT*\.LqQ.]@( >w7 8p@?cT)XjpY]MvqA@Yx1K.>Jڡ!u'&&/M6577iN_./⚚(֌['aZBf2oDkCZ.@D`>+z7)7iE2+ӲP(X6a]p!ygCt]NN``=ϛ^/su]Pć) LLwC4MWj?/?hc`E! ^?Ǝ=ܜfitz||c\TIөXRGpӻPś2`=(i&\!40Blt<mh${Eҧ?뿊UD i* _r!};;v8ضJ;RSSSSSaÆX,z*;vr$̖{^kjj ] >(䣪 씴tw@?OOq!(! Sz[?o Cp ^HYū#~'| hJ4~!+z  &Ic:X G$vu-X~} 8ƻegf"$x<d%pNq`Ix}ϟCة:V" 955\xR.;摳1MS(l$ FBh6ۏs؞0?PugQGЍ?00033|rX^0gF,p|a8"'RA19;ە1>k 0!M\4ݻ%IZt)t 聀 ݞQs7C__X&)4} D?ALdUDC.Ir\4 xe>v. 𠝏q;}RDS +'0~U&5] ' yQQ;vYVD"kt-R*8 /g @It(MMs#{ұ1J8/lެvuR)曝瞛@8 S x}ǎK/6LB|J(~˲@~?σө( L~޷o_CCCss3dfb%<9o&5l6OPc333@:$|~(Pf j!+zTnq!U|QT`B f(C^(9. eB7!Γz~(@RyLp$* 0H.[`?$pPo[ip@aq/k 'V\,YU*lj<|+WTUu׮]iE (wwep"-Z$}Ofb p^w @y)gr=!`~wEQ@{թT?$R1̞遁X,V,O8`e˖%ɚh4 -[>'9ϩORVZA^ebb6 qAA>Ŋ'^x>呶w;'Ϋ@>BY&ᰦi|8:C\ax,K*b/ʕ+$`w(6 bsg5*J2>>.r}}~rE9pO Ǭ^WK28s?Pp':ǎ]bE$VցR \a\n ,x*،g ?14K&+wD8 X>ᐪFBُ=rs[`~ ,+VXxqCCCmm-c{5/皦A*uZqr]]W\1o<).aE)5+B[C+N#K\ϲ,ʆCq{۶8099 bǂ_:_tV NWa%"Y.MӤAS<筰It( pE!%e@~```dddzz0 o&y? M$IֆB!^ X,gpRC)sj}[bE86o?~%y1¸Rd2J ^)@Ǒ \0Oѳm穧N &AAOB!~)w.^9ɪmՂ8MH /pժUW!@ d\R4ǁGN5A7l(g{yQJmv)~qYBH* r]z;b>/׳D[T?Gk@J "@Mb$4;33:+NT0h$4o諿J(@@@o}0B`ms7oDu! pzhii隚ƦT* h4@Y._ϵs/F/Ib񧦦c---PFWU &>=| _T[DVrXEI˃T +ZAч_ ?l#+ǡ/`4]jU F0 %x<8βe˖/_~?~\P M`BRaD"g^:@AvU > Z e>~!nԔ`UAYBclvxdeRlgnQUoo&s%|쫿̡5 ɲzj` ^},E^KE)u\1ǻr ̏0(JggaGLXKh4;::(6mĆ='G8f4?o^?B@@@gCZ4x=X. @>x #)!iRՐ@s]d 8v'OڶD2`/ZJz~HXy7>>n:fZ(ޯA+>~WGUU-p]ٔ#Uf0 >ր_S \||9C@‚߻AzgYv0 B@: e:(+R)~rK/ttt4 sPvH- IeSd2CzIỌ)Jsgczݜ:p @Q.Bix<Lr3R4M Be,( p*  ; 0|!/+"8O!U=Y87> Ł0 rq(F3@e˲ۆ/x0IU+]**،1Ӕ-,$m/OUU=mhhp'r9OO(+ b(PRtW?bϞT*YO޸ޒ%{ރ~Wy*ϿDaAm{xlSJd2 555XLt:xZ.EQH4A;Ňfy >(o6>>D@h("N'/σS^ q5,˞ EScU*@ PWWL&A;bqF<ZV<%y!k0ٹs'lێbp-py{utiGI 22J;\? }pY8>/% \8~'bXcccSStxA@CWWוW^900s~qؿoEM* !0_,[5<< { X,*BRd)aBLG񽃪=JR(eQiA쐿EӴP(-ˬnY.cP8|2pUJ~p8 |>%"xFrEQGJeYbz0As z>v θ C?XTEQDL@=DFXTbBX,VWW`9+ J.sPw l\3Χ P(OL؟ޖh4q:thppPl…8c8gc^r/!$A=~W??F罇qEBG)P(pd2F4`[FGG;;;!1 %O2ZE Y&9z(ChQO$ơ‚OU5ϭm`0HZ J0v0d2 ,T7| w:00P4<7|}!-[$Id2@p -]^ΡRX> wErƒO,5i!"\םwDz]|12Ҷ6o}^"|j` p -_N$1x}QPt]gM,;No+f3%6gQRhQ4-imԲ7Ѷ[f#lKaII, 52+o~8NuEj,2߻wN2^kcc\.?ӍF##ѡ^8<@RBjS>^ blR&,e@ (ˍG7o>|Y7nܨT*W\p!gYL&zn}uuit:;::bJdW>___GgYبߠlB780 ðh%b#^"bgCCYhE/>ߑ i%_OUJYA϶mKiGGG*E Ha("ZLu|)J~>RqyNJV (@gp8}+WJ"ʲl>>F8#deb@KRtX,BS $W4M˲xghcީdPub>w:/ɡ Rn B 7Z:lvccVYuxxm`0H_zmo+ߊ˗Z%?wY˲d%@@1|^% F |2ѥjөV/_Pכ&\7~m[1&H_z饿뿞ϝz^.W Z % rYdKBMeLu*T*Q 6=I7!d2E&% 8l6\r\VjR,phƄe-w82@-nYQL`x[}c`iC(<˩я~zg{^ /^,DX6 ,t]4ib1[nnW^Ǫ#uNV~% \rLCB%ޅlFӷ, $Ku!A]* j*j&ENsl O4."uVt*0xQaDۍ.Or9Tx١Q "T-<{'$;09Mxk.$|^AE!0ٻN{f3TE>:T%?dv"E7wzXg8T%BTZ@ݻw4Af3J<|>Pafiwb'k d`#kY믿^*iV Ё^DUZ-p:ve6J%2xO^_z+ߍQׯk O IHJV$wE쟥R&*]- A] t:O=T*PV+ غ `0?gyZbXFC4, -x|=˲~򓟀(O[l"1E=ekv/+dd}߶SeZ*4#bBA,FIYfx9(y7ndoWl1k"6K|T,+&?da*^HXxw畗o>~ط~{RҒ|Mb8)Mr¶(0 6#GBxxCm<[8VX, ik.M9L|߇R ߋ \.W*d^@EaHD*|eG3eD<# V0iVV_~ׯCD|iF#4FL۶mYx;P&\P@b^ \UHbOPd Ud BW_}e0>}lЖBF .\ۦimSQY !~/n7JR% {bK!Sd-&0{8&Bc vaAp…b6Ju[W/ek 6N|a̒YIH Z.zQԢ[D^`g@<ʏ)Px1 qnՠQ;mo8 š~u4d+IQHa4m8@K"\.AC]]dr>rXX__zF]U my 9#La _ c]\@X<<<|x TĂeJka:}UUw+ql `|}HXđy19[~Y6G@,ƍa^r9LZ?,:*iWL&*8rVcL׳(XPwB81R4Lbp8TI;#gLΆQxDQT*xMęY!qH%/wj_~N?Or۷oc7Y{/T*urrGUvx)+PIAba!@Iq4UUͤӊ EH |_U`AKaL6B1`Jw@rq0aiEpf^~7f3`^f$@2rLOF{ iwVUאA9<2f~?apֶmpzdLvsqz.3M=쀲!|0pa_.N l6yΎeYgz~*9u0ɹ t:=&I6ňG?UL0;.EP|G>"\?GQE뚦 %X@Gx?W|2:N?4 EQ,˄@! ='Dմ^occhJ%o3P6h蘃O`ii ֕JV1#s_HY"Vd9a-xꪢD F*ɣ(b躈mۅB{p2]H*Q0R`0/[X_B!DZӔd!2E"7kר+1 ! 7NNll&k8y^Fa:μP(b_cEV,S.!xظ{J. /~8ye3jCRA|BP0M>N^{ %?3ۿ}3PϟWEo? ݓU, YJdW^3Eܺ9QHӟ07% c.ed2?3UU˕F^/JH!A!x{^ϗef$#42%.Ǭ3e͢im{J9N!";YyiDP}@<9?>>K.aj=W*fIW!p8wNkzR)hf&'f?+zȋX /PGNAV=VW35,:Jn9|w';RNgaB`BLJr('<z_eIi1T28c I<>c WÂ7@ɤ킍!6>,\"eY9 1N<ޅB mԷ(ekub!\ o4ZiCΘ@d2B4z}vΝ;>fzƊ"2~s!'S+++cX?4diZT*J\y_sӜo1p/@BfNΜz] #U <}θɌ-~,pÝ "eYF〢(jYA|\z)UU|߿ꫯ&e GD~+ʷE_|>i I_==_~=(IGG>Vl _իWsp8RiuuuXq 8>>~W766<4 g*1WPS͢\.tJY,Y@2$۶eRlB,>r JF,jr'4j[WbN{~]QlV ,f) $ #I\Ā+PPX z_ S ]?J_*54&MH1D;/8yTHV_L+J _zzYB\1lggߪ yK;{dwh}J<\.w=n6[`3y-`0pwq]=\ˮGR,:x1j준q ǀ cz#b莝\KȮ2sjjψ.2xIc&e@FÐ< y^R)JIL//|߀!Z6oܸqy`bClV*D-C%*|Ru]ס{$ )f۶¶mpcI o%1gN6!7=/ h# ;2X$!ѣu${İUI%—j5:é{HJX$ q6 ̊y^l">w----//dR˂,ïK4 :\a`n'"4M6q%- 'bJ@ErL'''F;ͦ\β7HNſWџDX'NӐ"Fq(IYH?`i<<b: nD2X%~ಂA``ABL6.IMy%! ^Y]Ru g*|)jZO޼yůzu]Qjs6dPiYLF^/J>e<>[p èjmCEJ(j +?_A~0_@2}x !EizT\V * *a߷,Ӷ ^e)|/qY\M[ERIS>oRl6 /:8je<+<-ORA [%J8sʕcH:CA&'eLS\".NXa8`0# {J7|QNןoףR)Ї}U@ r D) YJd*R~ߕXZ _O<k_}4~筬J%RYyRoh{n\VUʂ(e= ~~ Ģ,WW*lsrqbc`B[zv`w4yXP(uEadi>#G?Q 3qcX rGdž<r5H|0fJ΃Fa額$[u41ܸ(XH$3Բ,yB!#q||8N\jnlv:4D:GӴ|> dJRׯ_u3MRl~l4 ʾJe}}~.ãldrժV|e"20foxkȇBIqBc13NJ 899W^m) 7}eMS۶5Mk4l64S"Rt~P֠H$(f߿>"(n,E(~iZMUcǠNJ"ʃDUO0r\RA<<<}HD锡-;Iav>cu;}ZC FDQRw<GQ$GU,Wg(F*S;l6Lb124_EQߎFly b@d߻wl6fQTaJe%͆ahY. \,!e !#o a?|aT #_s'ݮkZ}:Ze2ᲆd 䬪+JE~,yөs_~Ԥu, b"P/TӴxlv\nV %er)+Q L&3Lz`ddC’"s2FfpU "{Nڕ 8Yw|>OSV{kklf20E1MSUՕ `s=иb}Tl CH22 #@N{VKd21MhPMS4G / b40 TMuC4!DPX]]-#ii(Pçi줘@,.j=wp|7rB )t @Xձms6kь\.G@UUx X^^.Jܼysoo!~qXYrZr9`1@ءIgDJ//|A<񄾐 w(4˗/?+ N˼T ,~y7(#ynP"rr93~P<@% sg 0=N?M094q4 CבK%QˎJh~}XfN\dxBThPw:0 h4z;;;@=MeGNpq&h4my'tų,(Нð^뚦jRYZZTUU]666_"'F\.kk !f#EM;_HӞ#a- bRRdvč  +$߸DqP5/ɒWTzomtz<|#FC<*YWm1MrVR.aF{e2}vbӳ(@,ٶa6teJr4-Us$R Y\h, eYᅮ777av]ׅ5\أ6v*Έ>Agx  G!D.wŠ3""CRAPUUQUp+P۶ y۶dj Gt: n`"b %P u]HJB@,  P+ Q8xO}S^ziwM&G?G" ִ}H|RHQ'h8Dd%+]ɜ>=o޸.:@өVTk$A{{{T*<4C,*XYIh4ݽ{w:^|,klrV<XH"ylp&\V oB;N~B6|W@=̷a?CGfi/qc nH2 AˎwHiyev=>>`8-+ϬƔ¶t: eVx<RiZV.ўz駑zMj 1 '=6&@_T* p\+d^t Ac{iŴ:F!|˲jh4.\@ M;wt$d&KQ,?}cK0C8ޅ\.oOK."+4% E\]]8X4G*L&ov*zA&B%F8! Ĝ;P4ZE.s뺹B!kw]nE0| J---f"R{Q曮jj*J*:::"dix::[U ;L$@=z^*eFDD)PG"LpFFp8$8PX@M\.ZrX8p<}*"NS! '#ĘkB8>+\J8&A&-c"ˌ.vZټqƋ/d"^}U|k"bO#R a3a%+Y RM _@4[oE'' \~/^Ą?RTB۷o߹s<@7ĎawygggH8RWAGZfŶkAݑ9؛hd2Q(@$lVHCT*™z\/F@$xee .c&Ů$:@O! at*Bvg |㘦zy;b&1Ml.] #rnBÐT*`#B;wnmm(`:h&o4nڶ(TԧMVdR .@dY_3p||\V+ :^|9Ew~w/܅2<*\^_xq2c̞wqj&0n4+C~l4[m?sѲ<)DBIFUa&jZ wxժidp)HSV G(J8 G}>a4Mi{{{0 qxgё_f~z/$vb%ByIa 3T*M3z^oܸGJR*@b_4=MlcǟіD g3Z ِ?lPpY/:hvtpTjp(@U!R=(*( iH.Hz !הrȁ& 뺦iv0 f`󕕕%}ơ$8>2 iҭF Cbe&NE۶=; sj`0b6j6!t:f`t !л\>C$\I R_ZVon\u`0?LxјNh4Z 3; zrK60n߾u޽gyHiٱy3 /1'fЁYx榦i~Z0g2*j  c(@΀TcHG!H+pMӪj&Gq T%ESU0@EQF)A#/1G"*]XBLSyxLZun%?"h/xݡB )~ Sie!L`嫫N_u||,Jr%fÕ#ě9*JV$+Y ?Ϙ PmV|>T?rΊwyg0!B!+0>wV2 d?< MQ!${٢ l. 5\VVVFrh@@ex,bPHA΂였B B ZYb/YYY)hHniFP0CTDU8*$/ \t:t[C+\۶AIu]EeYHʵt:zćA0L014v]1AqZ\|\۷o'/// 5ԫ%Lܿkkʕ+8f|z4(JbXVYڃϊ}WeY1&kd|>_GQ>_GƁj lVTi\~ܹs`pZx6!paDXyPX ѾX(PU"8Pۅ#J`<L(X0v=uWVV;X/"֧Rl63NP# f3!W?? (Sĭ[HjP>qPЎiG0YJd%q=ϋ^x!2S&Jx QɱOCg?ٷrɤAG^(&XBc@`CR%@W*WvɎ(wڒUxYHEL{Hg0A]''rJ p)7̌{\ngY` bD+5p0qztX,BHdN@9}!t5?}uKC7٪i~VkHTd1f3Rq`zJXff7WNp8(ry0Yc f^o<#gBċJd2va>@h"s{. 0(Z|׾vM娜s `@hs2~p'|xUձR F& bяEWQy~5Db2d#\.٬T*BA"nCիW*揀lƹ Jh&Ă^@39>>Vh~6tͶmD~R0F\@L&2vYMx i(TEQ8P5~|% `9_KKKGZ,{{W/9*'_#M d1GFY/YJd%7 /x fj @h4 2-VXC:l=x2KBǖeb^q!cU)(HlB@ E;N9B1QUHb=0 ݠȺpgXHj&8l |~@=Ple,ˬˠ  h$"h0v]www4Bp﫪/|\<eRcп{.U_Lf35FKofЇ>&N\r AZ 1C>T8Xp5 t) KKK !zvEјȯù9|>Dž-Jaݣ(8hde?Ji?˵I(BwŽ{|.٨޿XlN$~œd%+˭tZL:@PrJVz !慰;jZ>xRPHeN| *"0\.eY/d2.'''a 퀟'{{{ `CX,'n޼y-~]T_vM. ;,zeÎT*j0ݻwxx|+ QYw"r.ђ)Ò.(eB> bܹs/_MdD#4hP(cڧgTՎܿ"ؿ* `xR-b\ l6 X~{\^[[}_.aa~2n"3)dz`V"3hN&_g?׿ZkNd2r|7nxB"!l&b}]?pPj9BKH; HV$+Y6ee4Z0sLnZZ,s # ";<<ކ" ORs1Y&2H&bX#O%ݝٶAP(T12$Q˩3|SfώEp郸w""j"lĤPDQ U1_c?/ŒF#˲%SXЦd2PBu<[뚺` {y$"80ǥRq Dֲhm9cNR^ǟKmQMӣC˲L .Rt:u}:oҙo|7v>>!sfAl6$˽ F~~"x٣8q $<[[[nկeYj6.Hf f,d .,}iST*pF76A+NU]UU??SQf  =îwDZ3 !uAaCp4(\d>! `^ooo///sv4%5 }|;\0͋?R >ԶR*0KaEa5ʊ6r,՟ξ r*!($yBIzχh גj\0MG–`|>>?ƨWUQh@E.JFC|~xx88Dלx ̝Asσjt_0ps?9i @*qMxl(pppn۶纥b1ifF58ovag?mll%r*Drx] 41^O0V~l­ TꕫZ-"nv]X(d/xސAnFx\;1\pUĬỰqtUi@ <;̞v@SS!;wD+u͆O>ׄ=MsYHV Y^RsY?OޏT*mmmVUT_ J8@_fLRؾeT̓B}dqSz1S)lI3ziߢ\W<;88 /@%uŢzOn1 %ޢ]c1?FQ(9*+Fh燩~(>xqatM,q|>fhT,GG1F }Ƕ=߷,K%N{W(/\PT< $ݑv.MH(k& Nx /d0 URÛ7oKSӁqVbENVkZB,5=i^MRK˕JtQ*:t:~/dV>(G>uD*Y&(x<ݾ}[Qjo<~QEFDHp)=?dQ} " b/UdV67?e<y^ P.SDR>J5^ns"JV5s,k[q இ 10.>EUCijU TŞLƽ~?>[[[[dž[s\|8Gf_ iC N<:ETj=qM@sΎ6wBp9yƁO*R8 !n^`F YJd]lKT$m>BŋNeʦt?6l<[<؅U(&c: KBlKf. laa8Os\Td2vS ?J۶a#eRtǝǙΙfNl6:<-|;dW*">`:T*8cɌ $dN`:8NRiZI21Ѽz_j!L&Pt( @=;nzf~\mc W%?TU<ܹsKKK `83@YӴno|S+WNaPխR#< < B!a eN@lLs>w|?4}}}h F|u&\.c{D7&(fy||Ztt] lѣ8 a,&o ꯭ |4PAJ5nEQ$>9Ia/dg,#F;U"@E<#OFTSk׮]p,*B V2OqwSE p06G(wy䄇>YyVd!LÜ!rrݿWzUli%!~ݻb0P4-d?l7W֪LV Y[gӞe^.@@Rz*hLK^Z00pzu[*f B>_GX,2C;Jj5 D^2PbqX4M80B&ǴIcr~ gIDDnslTN@A.EXa6L*BX %HaT|۲lۆ1*2 j]pR9:{q(΀2]#<" CӁM d, FaV ,F$GGG8jY,GXH3veaVt`i\ʐUx/im^r\EUե%tYTݻfAP ?O]+W0/~aY xZOj 5{VS.cxPI, ]bgfq,EQ@ xׯ8 @!ٔf?CΨX>^|>OIb2BDcdSM&`v47xզeYx7ǹphf1BPU!(:(} 3 nCATv{fsO>˗* !N w  VLccmm- C|-3t9 A."ĩ3u߾}R\~14# w(v!mrGA,d%@@w[Q6{gd.(b瞻~pX+=;ؘ.o0dR)u(CF:rL&fiYpl6!ԄM0a@AQ@!A:y _eEk" d24@ #s夙*Y0[TY&2Њ'5 #M{AQ G\xl6_A$(0(4ӞXY^u6R+D$^ʽGWF}2qf3!d2n۫&Z v`|8S͛7с<88(JO? j3|>P@`^C VK>cujZ(Ce z=#_3.\3 cyy.W\QdӕJ"7]" 080;&\.7NܹUT8-@W<08U[)>tz2N1[@+P<0Abh LE&H `u9OhYGgS!Dټx"Kh=EQvX='p^ UP?DPcA%sND Xf-' N&`,˪T*/^r hY1뢥/S `cY >nE NJ[Tye Y#tBc"Va.?W^w @ǘ,/ ~E*ٿ)iGGGnݚf/^OSLyV$zIU~,* +++LnS=riw\fw lsҚKBNi"2]Tj2looS?")BaX8qܹa\.JJS 7f =z,!FO5'!F :lNViJ%H̠ƴ'xs(tCQU4\X5u^\l6UAe2Y|%Ysl[?ԙiڶXdx4F<]p' dWTGB?)|>1ec8jRQ2Eh[ b8SЇ>yޭ[ ⳿/Vm7M]חKRZLzx~,j\GG`#FF>LiLV Yl\@q^U`~ߠ?&,*:R2[" HLj'2 "n8EYKy4=0 "B'-ӲrR $s-SQogơdёm'''ΝCkaN)hd2f8rP!"qt :3Th`&V""DU8o)uz޽{B&D5d2 ðl ӗ,97Nj,juuq0n4 #Nk(\+T˲"E7o~LhX C  ) J߶T*J<@rLCд!\rK_6Cr'CsS gF Q7vdtJ9Buh4_]])r#PF!-1/{ MnaBWGk皦~@03se+J'7ggR ?SՎ\wj{.> r@;[,QE_(2܄A Hf4c8H>0dTU4N)Y\|2N1+18nTjZxpŐuQ:B\3^ #"&BWV?UKpr, wan4Jjqs²LJ{*Se wx@>:6'łDz,C_2(#ptCd>u˰%TJ[ R 6UU0RTi-BRfl6(|>ϪRh)4j͍ Ne;?7^7 4QuvǙyf8 \7UL aQ ,Yp7}i Axrd!Fwí.N  ZEkeYxŴ<{#Hܥ`<(Qꃃ $Ob-7XGF(N6r$4}U{KQ5 t(7|HHI`+(350" 0""8Er |*8a Û7o*SO.//#L&.]}xt%pZ(X}01)$irjjTXRI{"[.pe(PQ!KQZ\;l>88T*NPC%)BR-dYn hL&l4Taɡ%Y~RT*JBGGGPX`lB1p4k4tE(Q$0`k3?T!EӴl&ee|D~2p4=ff9kكS@UOJ#E"H0p) VSFFJ?vd&j@04_ rD d2<|Y |OƮiZTf6YB Cvww >!H"RUΝcM@J/>1ɾ0J{!$.B(?x߀Z.˲52K2G ]C| <ք$Azŀb* `(X.08<8w&3"}XS0dɅ0 m˚NBs {oommZzz~o6H@9 Hz1/ib-˂ؼmA< }4XH+,ֶT*kZ$Dav* `kp pcC r6 2 "4t=dT|;n^P2TcR#cUL&ׯ_7Mt{, (@X,B"n`zjXt@Dyt2  dMvu_0O}S0&x)_U.]DPF Mg߼;V u8p8g/_,k'xwP@jxN?=;;;\x|>' G"Cg җdvww… Z-JAD㯐GfvVkZdm|V E[,T2ͅ;)}6VCHcx\R`/W*+WAaDԢ a;E(Φ AAJx/qMV*K.{rEةjKKK+++RI;3wԴaEQ*a$4>Q|6P`t+(V åem9gQvC}B(Vb&>R4l6+"\6ph&p62rS ҚM?侘Ŋ$i6!"'L!*ϟv'''JH. 4\z6׊|uWZGdGo/^tt}}s 8 \PpcVsvscb=$}hł M?L&AfY`X&~=ax*pfiᡪR0 m샦yu]?2F!>Fx'I1!2 `>w״g/&~X|-xl<,]סS.l69 BVUJEƃUTH ά%2Jd%=N Li㳑;wӟ4vMlt۶M˄\gŢO+K),v؛ݙaFƃi ^`8 }MSQRJa**Jf"YO'Tt9:40l ^XR ]WR 5D# stLSL's50tz1]qf?H`9<<%E\(aLCv|d_}-.qHBaf361}'$!5gc2T.Ś5(谀^,zZ-< ImQBի< C[^L@ZIhd~y NS4iE.;H0^BZzwyG?Wtp8\O>'?IG|**~mHp@d};?}߇-(TaGs>0 ٩lpc 耬3 Fh4hAQYэE> JjdҾb10H::em̗p`+T*8li[O<0eY1*;v( ; #fi s*"@6B*bjQ9 H2 Gjj E|랜X0Z"𪪊'p 4o߾\(C6qGf%@W @#X"i)4M@x1q.̈45|ŋm? (UUuQpu4BJV$+Yf͞+x\ '(;3f3`,==̓[L.]JR$WsCRbqc)CryAg56oc6'-v8)r$*uR}`óqdVWW6:ӓ0%12p8PHr&c(ej.+ \"y |#3" B! O0 gZ ]q^^; \:>p[nxau8FQ. Yl2p8t=/ Дg<<0͉V<eY8 BX(!eȮ@a blooҗD*u6|/ qEg?t:xdaT*ax-MVWWGUF2"I5MԓmYxQ.EiD뭭zv=.Mg|?A#hxrm4ϑٖl6 0fY&Cg(Grҁ!0 B*l;r~xxkU*Ջ/8mi+h(J,?TH\2֋@fy vm۶m9EQ(A\Z^^69*s=vˋY;)+'`\.G* fd ?# 2MS(B4yVr|#0'N.ΦRU,`Mpe7~x $PSV2v;w7s$5MC]Ӵn_,0):2<L&;;;j_߻wa&u].I TBEU"% HPQD כ#-okdY2oI٣`0K?"RƁ)r9g|>f0&mT?R ~weuŶhB,*Ɉ3<=ϊ$+Y d=v\7N]yc\YYy0H&d_ζms^,l~eE40WY(l\."2݀8HDт ,&߲+4Qm۾y2/`'d=:FTU=<<{n\K֤- !x!6;yڲ 1׃$syO)Т0^zicc~dř)j0k  DRH ic+2@IvD-֥VzhE/Sx 4%Kйg9FM"Ri&ࠍF];+nSXWQ0xmfoiۥR00UJ;)L'0e ^,bD:K~A'xn]ϝNiDyEQT54J=p n@0ݓd%@@JTlnnэNELڿP8O(MBXp9xZ3/RF!HjFqA$vȃeuyOBO8淃 FH(i L0\@B8<4Ci^", BtrQ.rV$"P,VU8ɿm5tcZ6}: ^ChG%,㎀ a g2H\07#YbxBQi`/h3(a3@^^^.h saX5҂ؒ*tJQN ,0b0OXZ*F5 |ESd_nǐC"u8'3y"Y5LLTcF,vd2PЫm; x1 ~FdԕZª^t4ͣC*DZGUal[XjXF@ YzI*GdQVWW_nc_0mpOy5JDT pfryɧ{{A ~_Tw:rd '''t:)U@-$O r I3A]@PMg͂瑷fQ`N%19~;.Hݲl*mE*2ՙ`vqcc͛eV,1CS+,0>cbt4 ^b7n3}vww?R?'d]|2+R !N#XfJ''өcۙlV B$p .L&S,+ H9\vN^=jclʧ=l6{nժV$SG0۟i{pp*6,fel<OSHk|jS"25XmfZeHB?=m8 L ǗpP hdY(6 @$2+nt$A,H;( & 808dl!+^v,z۷lEUM;T"Q<%+\ێs:b6 euϯw:k׮UUlؐء2DE\VeڌHpB!=ЯFEiaFFq* f<j$ Rvt]o6{{{nj!rp<àв)3<ڲ'1ec %eI1Sj1ɟbaA-fCp)RT>_Hӽ'?d0 e2N0->;̎m۷nݺt钢(;om; mׅ 677AKr0zẏ}olRn\$<$9l2E @&E!L L1_[ԁh2Lx<-B ¥Cx?V* PSւ%4&@X#ۂ '@051fË/Z-NwڵL6*<LӜNn7Ms2jʪ[U5Q)UexAܲ,4X0‡]{H@NG)\6a_- G` rqDX3s=D]y]6>.-TB|>_]]?=O@aZQa<__Y"?%X@ Yz^@QmԽG@JH!V`0F R|0;@}/2/ɤJB .`0888v`mm ^1zpp *Z f)ޡՉ9gN#C -#vu _He!D^gou"d@mDpbs\"IRdr\PHSd52Sy>8BIaGmq jqMqR)˲xׯW*??ud2Il6?Onx<m(dYu||BE`RV,ǙfG$d яyĥuKTG$ǎ&9r8֊amJAt:]*ǗJ˪o^o0w l:Ǻai H .wa ! [߄BE.\8 :0kNٜ?ޱ*٬z=}CR)lԲBYXAh6{*0 S$ HH(5p_B7TEIG#ty^T;9|ܹs˶ Tn5]O E(ٴTָ Ȟ/8}!D:) MGiQfy@,*]vͶ_K{LιDQD*% /֘L$+3q@kv$zqxeYhe}v襠g!$7vPOn LO Y+HlҰ~||h4NNNt] Rx42,r!acccggg41"TTt]4 +8R$01Jʱ[0 LJ1gvǔ>f&iZJրezxϟ?jp  MdFaF. ~HɺQ!$rwɑV[8 ` *p2ɨl{&Ȫiܝt: DVp* N7G^wlis*d۷o_~=o|A|4Lo(O<<~(\!*a*ι(2(* %(1ά"QD= |.kedUooo lҰGP̗+0eTl::牥LD>뺶mM&{)saԯ],J;+\RYp-Fqy۶nTqm+V6A~<g2z,! =CQ5pEUHtp;pQ)"" E("2#9-UEUTMSUMVH;U -k2iG-d+ Y;в=00L‰ r=of<+_}ttdYv>n giFlN=!ގgyƶ~qSUUa<8:Z_^fZK98}'+Y d !áxL代(rJ^^AE5(!!\YYR7~lB+r| [;;;aV&THA9`z=Në 9JsCJ~,UxZMUU۶1jbdT.|>'. nut!fQ"ՠ"L{1al6Ep6V(gSVWVVzޞeٹ\v[r^8%VF#TPHT*ad6>3mb#-Y(,(i% evE?cu(ŋ AO55ѭe' r6EVӴBz| P&1M//mЇ>(WU /絴Թzj /Jz۷oC !%N8.(Pc*}H>dJh')mYe'\,GJ*2>'&"Ka_) @#w@x mq>jt:,DOQWH :CBV+j\䁝a ;F {i|~8ra{j=זZSS󼧞zj4}{{|UEA #@ )bJV$+Yg5 |?iZ^VE_w0xlFgYO< b!#QMR@dErZ&g،&ih[3,+b/Ia\vg6,0B5UMRٶI,ȢLex.Wnq]džm4iHucccY'Pvh,ZԱ~q=2dfjuii S`7B\.2E0JDW4E$&MHPW#g" \)$> U0Iάߋ(yBc/XdYfr>N2Si!bh> b|;(K'r\6/--]x1o|a~ +'{-//?ӚZzJFэ7RTՂ%{~*UmCIrnyyYq-JcGq|1 ,]؈Y/ P&t\lV(IDKLV谭 >Ά!R w7gGh"\b(b-E#:*XH1Yt6r@Ot+8Uy5Q@]rggǶ+ 2+FX$CtCWEUUՄP0'v''''2ai:UyF8jJdrF`0t0 -XA++xAxITP,WWWp8| EQɃ>@HJV$+YkBXߜ1g2~0(.0t:5MQd2Uϡ@{3R%ţCt3d2QUumm(j3  aۥآkt:]Ճ p]!繶pl'!pt@nZ'''d1~@ ( ehd.l6 uu < ,$@'Ne68>cH|[rY~ 4`"''@cEG-a=zdz 9.rHY8DݞΒPOZfYV^j))dNc˹9ۡ,B؃BBg dr>SfrW X!Sd+V\.ȹ;FupTt~okC{9 /~xY >e#ec4^EtT*i҆%NDK  o mᑖbdu7Y6,VR꺞I) <:J`qԈe>'A_wJCƫjzbATӴ|?L>ŸD~q@DFcooo0Z-Y4 ~ ZhO7X8NR%R)pB|ӿDbuFB=jBxlbN2t= 2tV8ix>&G=6݃&:dI;N*h 4|W~{, wxr@4}3_/B:+BzӗHV$+Yu;WEQcu^aj58 3PU-V|TȲٞ$%Ji9Hs͛7s}܀!L38:2AbHBT*<*4>ai%mۆ3ȍ9LЁe@Ӱ(QsHX,6GCm^FY')[oiJZ@^N$hĀP\.*d: 0JR .)нxh4BjIjA; m߁ȉ/-jA(,ۚsKCn$r#=xxx}d6@Z1 ?Woい~wbvm HAzի|(K/?&J瞃&ȝ;wɣ][]\o:y GL~A0O9sPg//'թGa(_+W2Ys SH&#,(_"xml pBk^JC\"[<v@Ĝ/ߕ 7ܙNY`[%HqvI瘕\j6OA`ڇ-GoH?"%"E(D҄͂)-bzX euKz* @sN׻tR(J*ž ',ZA㽰:8θ!(DB`w~4_|_EPHţpHa=j?P.1XT*,~j4cǓ-$999)r@;~*4;`KUe3!dFD8#E`|2q%SmۥRI,4C2b& Ͽ_pa<{l6>iY;_Ę jϣujzJOeMǠGgD[(ŸePNK)S'^ɓ`ď⏑SE$ %uCF |(,@bQX҄e4AP0m$$bdO(˅Bgbx.:8Ǔ7_>?b C<0Ppg7]G0qk2t0 !駟tݻF;KXb9몪%R8߹sGUgKKKj\OWwv kXzOTZ@d: ɷE͛ D0 Mjȿ ,氊Ţv;zca;WF*eIOV$+Y= ٶyjOQhwwqNϡXD&allL+wygѨ4Pvd[+TCZCu}ϲ[A@'_Ȗ>%(aAEPa͙ hPB2S8A}19㟩I\/p'jm LL:Q.}8_sx6RTQlp]7=o0#L`FZ=>>I|K:fmV0'|)3E+W: {&2b ^ JXI#`G{*2eG;NMFrAs^MR2L(?kI+_Xl6Qc#h&dMqfssj~晓 IXUUu| e4CAȚ?m? xu2L<#ĂCFb`3[Ԝxc놪(ڂs!M9>>fFju:J" {bHCR;NNN0,M+ley&BCXA,?x@Q L xZVZލD$"N;v\/7e<`x5MtmQX+EF>qp}UqFe> B,ʽi1zTcq=ˆ4P !TM ೫f[nJ/"-#*{^_JѨĭdX昰 %@6iZр܊`vqp_V67 +(:<<G?ѥ%z͜whsa Gl| {o @qMV YzEsR驧um8b,:J%Ƅj?`Ucެ^arc$O "SY08yL&Ja<4Mhq8v4( DDdYj@tל[9M VTOɎ< R?>OZC|8Jrnl. |3?ϖÔ콼ȟHVU,zR8 VV6itHbźEP)ɚ:ܹ'+X=#cU3',_fƨ, Rt6u]ZmI &HP_ ٽ' :#~# {~蓣Ư#ׄY Q6,XSWP(wTbQQgVqA]*pDY*Aܺukuuu}}]03Ah4*rk|pjD:xP 8 ۃ,n */X#D! #KBww0 777ѢV]xq/r+ o4눜!z>G'0jATUU/_ y`<7z}20׃ Kd21Msww//J U y-jLAE~s>õP( a`<1ҊEۜLbe6N&EP58L'''|fYqǝb#`2i8tVA4MLiL*tlFT$eL0 NdKڨ!;E$4aka(]cmm 9۷'C(Rt}#R@5dЯ~(@y`)[B HqH VpHu?>v9w2|zrY!Dx߰S$'+umnn^zUZ"p:oRF!,d*92$ʐ"J"ix(@ibq%ju˒ 4M 79t]/BX,%Dc2 ^ e`2FFf|NuUQg>GQߟNgavA&EN2aN3=rDnhD2R0CM^ߛJ)@Y/E1y|Ze,9dyep ۉFi.y8 ,]dtdG8+`6f"!iI:Ixu7Fy]G;7l6l.3lႮ:x2Y6x<_ħ!= (58e5Ⱥ9W_}P(?s Nn(o5*`ij`pxx&9*@BhA[˲Ì4'ϛ9N@1@IG=ti.-hH1,&/;ۊW >GX$7Tp`g vJg1op8t}sssss(" b(:F¡>xZu RR>J@[`kkkeeE.; Znw0QH UT|+*1l2Ng Y4&G:-d(0 5RFJA3Q:ʨ"ǚ&X'7MbP 7B,f.dR,zq-Cbang rPEFnqj`/b*:TZdPUhA߷,Q"H}( qbƜ- C֖8uc@G[ZV*d2FP.CB_d:2͛7R2HARE!M)M4UQ#cv9Cw&1T'kS:r\&5RKm4$d~( ~gYw>#-ϰb 2dwzaH1 V@Wx 0_ںz* x%ŭ-$T*JzO`& ~!D՚L&߯4 xIݻJQ%?%J---z=EQ\뺎iڡ󥆒l6N)@=:+$r!ɗ@Vn20qZĊ\J{45ny$]hCA vt٫g{ o(ߓ++dBE"qS|u]˲,˪V{{{ɤZʸx& 4S>H(cY/w]l6b1A |!D<@ީV,P1@|CFpJeyyYu˲vww9Gs ^<*UEx2B\xqiiT*3o=.,Nm[ڵkĢY ˃hd@", S|18\<7h#Nxxg{YEʼn&s/BJvшӔM49"C|FW #!0x8 VϟaV#4/3AHOY ?5Mܔ1;pgcc'|q3HV??xhe2o6t]W:f Dd('}ٟD/e#T RRii,/euzTM{ _ٶsCe6S(~R"NBz`M?PzQ$H!4p|UVfaUoHDOUi{R8[Na8z뭻wCNn[k &'hԟ}IlIV? -vzz! eZTH8 DZSqzZas v~)>4j}a,yey^TRoFy{||ƌUJöCq\߹s2z-*|x231|蕥Z20buEpƢg8p@ZZ/^oodT؏iTUJxh2,Sn{k֍ܦFispDcEp8DX2-H}|HtJ1]n,S/VJyHܵvhf;Gp8f2do\ػl d5;l<0{'*8agg^H1HZ Vy^r􀉀<<ZY2@b|~tt>@" Bm3و["eܜ"ǕifN#Lb&s"c8??P&w=n(;O& M d H@^tL8)^=I6rq׍"*XA U_՜ff زֺApyy;N*G vl vGZI؎ոL,˴\yu4=;;aQZ$R)*zA kAX,YNVk`w%:Izuud4: Jh5$Zx 7pH-<1- >srh" [ժT+f ^8T/Rc(VUA% jUп@˯n ݻw,{ dt Al ؏W;<T i &t5)N(jM ]Li?d˫ZoSJF#64OFhqBVJ6Ab2rYQJ)GQdbL#E@5])i0;௠;P2% n[&S(;ҥ+2`:<<$lyto-86 } ܠ*E#o KAvR(|T+ĪjOov{zw5H<,̔-l$[#dAZaI0 e^6 5NBCj2Cُi4Jf,ln+W.C $]2ِE{R!+Hq OI}0Zv#J K$8*Js bb*eeH; @4M'ӧO×?L%6JJO("ʽWzmJ+<( ϲC<Uձ+&|LPa'æn\E?\:dxc@ӹeY0P&74ǿ!"!B<3Z-uۻGN{*hkp]H%g%n4 BSx6 b|n$?!ЊpǏy-9OdL,,fy||ɓ> Alvof;~؏4<|]Mmᮀ (m]T&{t:$l6d Z uVOƸߣ;@J,wGiG1jt74qkg\u"tbfeѨ6OV_UXdCf.[eEw}w8O ?8 G :>tiZf/Zjl6s+:SV,RJ&Lr`l$Ijڽ{;] mZ-/,3L#,QjWM1a9)~i+SAL쩢ϙl'-i u<Xpxyy|~_mzf&VeY뒾.e%ۢZi rXQa4MtZmP/kLJǾfCr4vJp 3aNҊF9 c>cfa,OVGA\ʈ 0Y=Yg jvp0\#RVc "h CW*;-}G3*ՈnPXCPSal2`m޽{VRt]& $II61pQhpvd]o4{Lp, pZAؽ ){jϗK\ee1=2|,*aƁOh|c  gϟ?tjm^o6LuU8 s ?_UA 8\4+0bO&fsrrBQ}Tt0`FEys*.΋W0 fH 2%a_fdY^|q$wpIjccw6r,˥M^=pIb:ڻs `lRR6N5!lyN=$#\0ƃGL*`/nO>{.NgJF 3z `&_^^acvwa^l6q z 9@|T:+eZ-ܱ3(*z@HMY8?~?^j7@?~%ؑ.`4櫊c?~j_ZteooɟzsΝ;I4 T4 8yE]I'|>l6nw:Q/+ Dڶm2,Z+Nt>|E6M\dizj%VH5AgE/iCEma~{ Z,y ,Z+aj5Qi4tQd#t۱*iN:ۃjfxjw@n2ud2@@ժq#d^׸/OHUp]wj5IfIBvG\FtVZWT$P 7W2OOկk?wQ#˺p2+aΒ5ܻʩ'IaEaTl274IɴV``b 9d6v]taNTٲN jIBT_Ed2i4GG:nJYgyiyl>v *\sJV*#9U&8Eln@Ф *CtjR*=B4bKd3w :{( 6X4CvFvV9[X,\x GH,*Y}!Ҷ^{ #:|>GG( EgYNz|l$N CԵ%_RB%v&fOK :~!#@bF}vaxtt$g[?\^^֞fJ&8Nv`y ^i4 vЫԝid}SQtl6i_vfP<5xke?FR SZ~%xKG#NdF!ұ2Sey`Qϳ,c2㺮yqBqP! # Τ֓t:v+5v ]1O2kZإi^ %ODŽ+)PȮ4l> F@d}|<kmT*UlIdY ]8kp8vbs`hv"e I daHfӴVȓ#K,aed2Y,>dSQn8&8s?c~} d#,2Q7{MS8S齇fuuB|g*R  y86t(0xȢnGv˼ZІiykCwGF.g? R$6U. cR8 O,JJ5-6WlIp$ib0cu1X$۹am]"2DO5Aڀ\K@~\.]A9wa\\\t:7|+ϟBNW[bucb z3˓|wl?mO>ɓ'>s`\.KHP@ܯjpjs}p@"Ryq=!3)6Gk89ڔd ZiPT ]^^]”>-_ʊrZku]]אK 4`S SEzIpl)z@f36Bz6$IR `6҅yϟo7؏=25 y•XO6Ay?PŗiҢBSa I.(svH3/3ڶ /Nz}yy9N%xrAQԕY+)qCDnhFbF] 5Y}KcjD! P JL(#CQݶ=}ͦl0M3uRql1Z2Z p*NZ"YA*wwM ٰ$Ԅ T1xlۦ#y\~ޯ*qňPPmXq|=޷}wa$Ixy0 ={F˒Y@ )qvbF\ lZ4ck:Nk\)`\L[V4p t8SG?(ӳnK E0ʀ7lEh4;~؏i"@uT/s<,{ 8::J4!lfӑLzHG\}4)QN!|g? D-g8g+;$88N4ͳ٬VZk?0Q*y 艠YkIsNqgq_D dQ=ɎbF ((!s٠(IE4DǶm˶6i(U S j :2nHїM|,2ƍ5Zҡ (P/ tn\EQLgG^ iX!JY|8}agt!"~Pif;D2|, |b$"Hk5z }7R@+ᄏZ~chݒ$y,Ȉ({2Y^ϩVvr*a^^^FUsXDAFBLeI6)A }($p ޕA Tp~sa&HF$W&I'4/{)[+~4N%p$k(ƾM]K`1,pYm}?T(aD gERĤx 81!!0 ?~|}`ǀK@ }vvv||l6B@Ee2gfˉOlt p1F,oM'IDbh6|_)D g C}`j)J^+jl,թΕ2ou$exW&ضaF_\\l6 :<%Ԇ2...h  C1[@3F]oM H>> 5JC8S0gy+ 1%z\}Y'''~" u+dȍg ;X,R?8=Od*32sIj R+aA"B@)̂iZycXu]# Or&QPbiΎ,όve[mgiF6{(u"$yf٣?%J|KRt:gfq,(pA{6ɂuQ| 7`QJ-xJKűIDP׀O.1 x׼0dHf38W~#p888|:VA܇UGvK ,IG YE%.wyZv=zcT0HTpӘg@q (eVir#[f}/V%IUGMj<-R{p8Dۚl/ )gx) M#E&O>y>qCXw~쁀؏_s46\DGF:ud-|镥* (os j`0@ '̄[@_=wC9fQ'],P"x% x} CGCTfNjq8Pld#" jhԻ.Ȑ fB~n]ץ*?i+R4L(!F-xR/^PJ |DŽV+ `fDk h` ՘cX$k(+%IymGq'1~=ҹR%k-'n09v!G 46iN/^cNYV֕Je4z~/_ocŤ)&`0Hd2ju@NHe#|W9@ j; ;7aY(mj RT걕S Y*M(dj~7M;881ϫC 1Tmj~~E7M$Fc0ZpvYмS np28&y|>_,s ##|qAnDPIٕ^~6 YXcI>Tr,4Mn S(y5cx:s$gYn@^72*) U7if)cL]1֔!U+pʞn|nvl62VFt<>>Fy{O9<L6R8 *NPtũPU %ĨYe1Il;}i.%$_zT$X2dqF@X,,:ـc7??}~;?BΨVnw<OgS[CVU!i̟qi!y_koil$Itl$ Gj'*^Bd/KXJnq4NGoL&8t!CW9=d>N(NN*)MӭEh4z: ȗ1ՈB "\3%Ue8NLc@A<87 t᢭\y:i6N h3^ĻW -˪Vaj_tWNJPJey%IIQlQ'Ib;vOP҇I*nxe1rl AiHv⡡󼣣#B.nI$IoaB׃V#fZBd jfh)8Qe!pHrd;`E8n1I`FL&$I׾ZZ.7ټJd޽{w:ba 2M)(!R-vuRuFaډt:,Sॣ4 +nEV-+&(>ĔƩ!{&)vpJK#tn JQa[Zܻwm`/Htxڸ<ֆeYƨ<71;6LQgO[ Q@Qaru{Calh*mkZ%OJ)e$#\4 q?(+r 2 -~&ϲ\LL㤸 /@ZPQ~WǛk |+6\^a@@le0 c8Z ,΋88l^רyUիϟoݻw~)AaO?, 2bo{ `??8ALw"Qɲݻ`u#IK'(5N_*gh4CN_ᛦ3h$/"Mlo:>{zݚYרN3b}?;vV>mC5d$~Ȋ1P)z|vN& W(  !VyPMrR.n2.2"‚alGaaa*3XgSE $QcJjsUa`"ۤR\.W!Rf<wvYiHP3_lCD*;${.8R@ 1ԖkaW Pv5^諫ry^e^___7U9`{fhޞͦn7I(4=}}(PYHtdj&2_XŲN;w i5$ ?ؖR-|1L#R j)L*D6ٕP@e̍/Tv+J`%j{ĀB}bf5:t"dW΂KiiY: zuʹm2AaR(Xl,x7/B@@XR-28pR'uݣS2aNS ac|xx,`Z-lNg:~駯zѨj~؏ F#2li͆|<;>>N;%pd.<ĶV.~L!ZE3rEԷPߡ}q,"z>iVpM˲m2Sp (#J Bn[ߪj MSHpEiP`W*xE0]d!zT._M$#aR K0 tlgݮk0RbǑϘ-@63Yf+LԢl!%B8CeYR0sh*RL22^ׯ\e+*8%j!> Y-˝=U">6MS]p_8RX X{I%IZ$9;;#l,+`x7 F=1IV J\W_.//{ifz_zR꣏>^.iN'.66 h 7)Ix˲~i_Cyw7A ؾ__`TV;vzZ]^^>|p:b ,u`ۈs\Jj XXHaRN^mvVaA`.*%%S~g(;QѰ(I΅if|6<=j5@iZjb zwQI>hɷ‡aY=f w o'2"@fIs#$m@%8U;88pi_M_RU8AA1׷qR8i*dY癡q)[Tnz~klxc|ɍ@gy #@ )xMwC$%N؏=sHAF/TF+zsif/EדCf&eO|>  68tHIfAƂ[.WWW9jJ*mLPZL$ E_7x$v臂U wbGN'? VڀQו)}Ki$dtBOزvyrEH FOl6KDFvjt:e*+:Z&(\:#ҦܴLdxH@\{anMMJx(Jvp|x;%r $'k@OfYl6er(a4f]\(͍gS4T LR-CQ9I$Btd^%Twʑen4MJeY0R_|eŖo}HknMFZh<78]x:+ `*B+ -Eky^Ra {eY`.x;$shCK ?@3yFhL5}{kŎdž0/7Zz ܍ `2l6M&v۶qF#Uy,v:S\RFԶmjSyfi&<HV&BnUr3D!O4{b*›l6ϕh1c ԕc7rW U!˳z.?SJiU8 wa?0Ms^f('̀Y3^UGl6ggg4[,#;]C۶oLɪX׭V6X5:iZ zO~o?A:}\~~쁀؏А6#!b5{XN4"ǥS4Zf5eJcy Yx21D}B>\fI3HV!Q TwRHVn4*3}*![F1rtG)ػ0MSjm*ϲptCҧyZ3yj-K3eM4s3$NLÜL'nRNpwƀC|2yr{.%=#e t[ 4`K~Dt*ˡ/_߿_T)Љc y۶e/ap5 sg;z0ln;>a؊I:G!j:mvnEmoQe**\*(ǔOFK7md vVq@j""NL3 n p*G/ӈГ:Xd@U47~#I>/|>w`8,5 3M5zv/f8::RҬ䬮q4~ )`Jj =?#tPϡRL&o6T-eo>PmN[A v[V2-E%7:^+Xb^C(vizcI`BEL mh˲ t7nՖRdt:w}c+m!79Iw:zMf>/XEd(N`Y ҇?<|) IA~ȑg93L k\吓EG}tొ0QdݤUmJVpyhڽ`06: %^ZcQ@ T /ynZfVxtzrr"K8\Z "Z6\}+Z)ny$d'1A8 o*(Q,g4\J!GyVjzCk$))q:!ؙL7} Dr_&,\w8N.,&uDa* Kg}/ni`=b^qdNȁ_q(F/^pV#d`'vqq[^{c!D ȺMΝ Bc.c[m2D%䑥e R@= KeRfuppt_q,BSuhFXm6yݹj]?LݻReYif4iip84M3MRU*WYeYremQ»;cj5X%d%VR_ŃFտ?C~؏[oAl6Qz 7fRk+DŹu~~j44T.Rt>/ u,qO]eheꮔC/AZ\///zK 1;{$FN ?-S,bH;wVJ5gi! kd_l).9ކa6c1y@5U*;(BnyuVF%(@ۮԈQPoZΝ;;<.qXN'F̴^Ó SYst AEt̴NSD+@Ny޳gφ!եe|# CX)A؟&}H8;4GLnPbH  2`JkOc,(lhtxyyZ>V֚ l#CI,g!HE%jy% =Y#x>^@fv _v|SQo;~'Vu&,Tq|!IARD *ua!, Z:NV_JH`bM}#-٤+%]Uڣ0Pℼkw؎ TJE'YK4WGDTa.KIB4(l6<9 $APL JӴjO?m',[z 'sR'Ńb lNS6лv@rh4(\Z-j5.B"N |$=|;l~쁀؏mܿw﫿'''"jzj5mz>rfZ41J8m۞L&~2mD ~2Xi2irX, գHm^myI@X!/DkB@n6BciםRCziX QgY%iPdR(۝<82˕@<{Z?L///<4(J6^w  rN *Ăp4^;4LJ tif̪Y&I0RT}ՓE"isD xn;йhp-%IEX,rDSf6%D58FYa{X %tb@X tdn#+e0- ໟɲjvrrrxx{0( ܶBIز8tgMghJ߁@_\8m;P<,MRTRVډ3k й.i@n_gϞs6=24M]׽X,^O7|4!~.&J)r ɮ$N4KІiUС{^cT:}\|^zPT*(` } شl`H}A X,?x`8aZ%9d ^HR-9c}UTFv,q*hCs~ @/#>|lA8a|v&U /K,ӆn6A ߅} }I2+/QʕfHU+HZ>o`B^OԶ7|s`l6AKv}|| ݖef-٧&7yPpyIr/4.%|ӧ `l4. ؊RʸhjT#JBr?D yORIRTwmi)yJ+@vQٶc%IGIvۗM" c'IZmR33nSry}}X, p*H .޽{}Z _LT UM9AE=(Ԙz`!@07:,3/q\]]]i&5 qZd~쁀؏q||t]J#Ciq^29L/9(ofYvuu):t͑ ߘ) L 8oV|5tw_DVLNd裵(n5R_M6PYhf5BD;Aۅ)fH2QIgv"m PXhǺ,$3-òvްk6iyfhC[:ϲ4̀4Ax_J4\d(#s`CW * \SnBkJ&;Bl6ˌt45d2sNEDjI/dAX~rDe"Nsppk,ǩ$e/DqgYbY˲0?6Y_ZyK 9) 0i!xLWJA0ͬAZ7AO` lV޽{Q l6g˗/Q2 r0M^)Ls4AQ!*oZ7猢'PEQT>-U3Qs]w6ݽ{vQfyIVͰ4ݻw_|(Dcam`ke7fcjL Un3uݫ άR?$]6 cDJd17"es JZbZ)0r$F^b {♥YiA.֠oHz[U^Qo!%W){N&ӗ/^sT!!XVJ@moFeR6b~Z^}.%N. paCf2dYvxxϦVѽ| ,~Wbw$!U o|?{ `?gGGG9 $~̤Pt6]VAlmRm!:qtȵxR*zfmPga.H~0 @@mޓ$l6WWWN"`BʷoY- 1[04p[aXwǤ̗Km 8+ۤq(c@bn)MIF㭷z7l6}裏zHՂ DFa8L0MöP'\wH*|gC^:>Q6D),RmhCn#$}>O&wC`|v}ppprrt`rU=BZ-f8R/)NK|&w]ZhzUW)Md z"4q wgIZ;+a%Q埦!Q֯kefq`Em;4^a! ǷS(\ϿV'!G@[i}^|,ϫ*O!k3w]矇Qtxx@$`ӃKU̷|n& *qIZ  ;$dOA]NjłMp;,SG><N9<nVZkԋ:vV,.ӲJ$Z'XbVD>D<4M1$҂np%ȥ Z!8C+=QlK m0E-,P4[9~u_J8D6{e|$ٝ7|Ƣ a<}?oJ2;P2ioyE<zX\0d0KR@N% CQE0 8 x- jc?::j6RnS_\Rρ1+aVM H VAF_Hqm" 9?%?ACV\>y$'I2LNq$!ih?v={`0t:X`/sG@ =1t=T*nyL{)&w6>,;RI̫e$RJ%!c?@سrr\, 0Rۿ(/X Z :a\5!o*M6M+J4 jZ-; @;dS3Y1b@ Eٳjt` OpxK CQHF߇ =`Ym-8'VD#ɦ-8s6ӟEh<*%Ql,@8n{a|{g/}|c?o?xC8QEOm;a $S]RC+EiYVgqQdt>Ďk/+Kg2FeD-tIQ#Ph4Z݊(QjiRH i0~%L, :ykS+CRr ,RSH' )M/P0 ö,MJs-˴LKk橁z!"Erxd>o6|5:(?4f(,6 $FĐ ҬTl!4=owV.:BA\tFĔ3#*5\hl6!87AU8&.eꂿĬ`ʭ+\R5c24 dA;e?q__>x>m*@ ZTыA'~_>ݏwstttF,nyRE3< !,` y~xx^:(Mg#faШc @r$IEaQjm`Fĵ,#7<|VwE'raN/f-EXTx8,ife[e0Ъ軗Q20/Xa FR\e \'JWJUV8w)H˲LӰ,S ;0_8dz7ϡqhaAV+FtZVVC)*`5*"̹@B>I5ylÇ___Y>GQAIRWWWwyt:e<[`Z{~S5Z7ҕJMXk@I)g"ʊ2CݻX tZmVmbL8hIJI8u|駶mC ˿/^~~쁀؏x7FHeOܽwBRJu a uE۽vݵeeKc3t".a!?_9Ĭ3M\{W4Fz^G:=6I"B?! `=q N4/Z%nRXM%*Zq*m1"06ɐ7-&Ӵ}_$Iu!fjh|2ȸYjHfrNV*pH??C#R 3[ @*R={_yBD@y;^)b}T ($~81-S̓ҁdgm4HL:W:W:7uHD&u!J#C@vQNAPNaKv2-0 _|y"'ٰ,xOm!#v,KgH|ӧ/_,R2hjP [~5`SRkHШx|qqZ.} s $p`AAs \V\n1 ǏΰE6fm.́e"h@g{޽O?t>w]訃 R8EV:Qa,Q)M0CYzW(ѥRXbuaC~Hd&,iln^,Q%~ brav ze^ۅg 5#U들24aj[{$yt2 Fa2PJuRUbU+dʎ 6oڸfiLK#TNcI0 [jS>A$q 5g {ӌ(s0f\Q(Vo#+CKAĆmL-P`3r%xAFR m/~pw0inf3_,fht9a0f1)gO /ϋjeY4ILiY$2'FX< <7f,{O?ςԆ,6 P].{ރ48|z>yc:<#`:> vpR=x,RtJ)Q1gYk}wm6v-8mA d:bJq$ZݠŮeq`.3\ Yw]E9zq.B.8Dz*C_ Cd.Z*,ϱ^VĬ#GZLFIeN4l'gY^'Rmءlc+J!NaϕR R(E$K7@(|7HF2D' #Y+ ml_@Siӌ ZM`19  X|?ǂ Hj sgggDG xxtb\.KꩤPNajlW< #p("n𠒉AO< ðl/ (M(j>V<y !KG8v,` !}0@ @yG˱f$`0 j߅XV_{5'OO=29T*}ѷmR+A"=}@Q9-KFwE34a'k =PV)<#mY٭jgr6p Jbt0T*rDQ46' g* >g>D4%e!1Czġ8fY*JW 52MSR0GL&mJ,0aZRyqe`Fjnβ̶,lDn CRfNMz:>Rh80Un\i۽;[@,ʼnϟ#'lArXT>~/ql<^>ffa]HF  {# U…tG$r駟b7&9 p8{ `?&Һ>KT6E+`K56t*n}_kb>(L!E*KyPF(c0kBFWպN݁>_7X6Qns4>-%EB_A4XH?m>kI|2kZ|fzl)d)?Eg,mP*Jey#uZeam4I,K%nӡz"Vl ݕH,iy58n$Z(oqt:RvCD ;\E2j ےYi$),j y*7 V@0JayOS%/DZ2Q\hhGIږ ?Zjm۲/vdRl]uWTn"?IoFƬ#8WQY,vl۲a^fYjym۝Nl?H b^SP|u,yeҹVJey]&l30XN;:V|lnTJ,TyYYlݻo~GGG̟TAh46,qlG֬dEKo6Q*,6< 6B:q(^  dtMe4 0pAͿfv$I-Uneٶe[ ȅrO %t e%Kҏy~Ē:a0;`/4K&(Le 0JEmvoB-I*X(#P(hHii٬Vk۶a<";>q#Qbs$#m[荗I!0(LX#Tm,7,<ߣU|ALX(%1JRԍڇAdd-2ֹBs"Q PiM #0G7 Odۆr^A9"240sfuǧ_ofEo6-۶ }5(©bڦSbcUj&np{B|$d2L'J4M3mnZ *Hf9??gY'IVdjl Y%a0VĤXt(PJ6BE(1ht:fWyh4@$:@rg8?8)&'\"BL; "J_q 2)-1"i;vթF<4DEfu;!&0 )£J $A3PĔy V8mێ_7yEYI2]Fy RAr`I;"lGb6]r:}V@@<u]v}}}MB˥Ԑo0j5i,V\M]Zw cr9]qO&lhxl>|c0(M4I۶LЙ3lPdU f;99n4M^G}>~lZn}@KA/GqFv( 98K$HR-Tce 2,r]׿zkXhZw?c~5-㏟>} YJ_M# glЪ$tNLbQbAZyRj= rsplӢǪK:#]4+^__{j6QdY*P/\{? K 9  +x\v'''GGG38 >{d7Zec(#Sl}TJ$NFβ->B*"/خ..rXq'\]]Mx\TO0.t°V5 YPf/s}`H(M:JE53yTZU,5 -Y2Z伀0 fɓK۶اsO.}8`!j5|,>}fI'ITTtd;0vݲaiY,f펝#1)qSy֠$#kjph:C:e0"##0 qNNNQm?xn;CxIt7MQ#$HGeVPTL`C=GG?Ꮇ*ᅑ$ql7 M+dnqdٮ%hIc SffiK  $kw] (^yGY $`hp8D>\'''<888H;NmU*L,3\7z,%tLg~<P!tU,;==|9NFy>JF@p Kq) n `}5ML+h4L{ `?C)@؈P@8 IJf^T˗&v2Q3p¿P 3(Ͳ,f FjRmD$GE X [Kp"FWWWJn3}8 J䂆s4"M7x>K)Ot:^]]5˲<ϻH7"YZ`v]ʖO&F\dzy>K30c)bZ˗.N<Qu8ih#Y$yny4 Ѩu8\8'bce* AWYן|ɳg,w*hݵiB_4,x/%I)auA7Lx+@j,˶-mPL2BUaWDo6dnHdwaFQ|bz)l !b_j ῅iGP ģdq^x?]ab-gVc2H; sF?LӼ8;;C痬@ SjZ$>4)07ʔ&&`7 e`fCַA L!!+d!݉=($ʕMeƱe|p~~o޿86<0-4MEV,%U 4C2^h8yrfXa6JkG`_VWWW}^n nj=QN/+6ã#ȊdZVmabhZ(*QgwqHzI$3n%ZԂeE~tYѴ4*iD d>)Б[.ϟ?ED08VkaC[ӧO/// VpRBam[2)@)eFŞKBVUcAl6s]wݾ|ǐgpSĤ,Shq桬z1ME E;a^lIym??!H+,b6 *g<y VtzC)G;wZB>3i['rDf8 nn/d~O"\i=egrC 9r: HBBr}߇tagЩ')X`A.11಑+qxxd2|u~t:_"o qlxt:H!s0={ݻw||t.>mQJYvov =)ֹ\ɓ'O<Z{ >e;i^r]7lF@[x<(0 a8 ¿[I*eZZi ܴ!@/ЍvK]UHvŢѨ‡).j]\\W+r)m SLR1Pz?w{vv^`o|rO ؏ 47 zj:,Ď1c$qzV"3.^NbB{tY0 Zi @jJYvpp6܋/0t:78r'/@e5 eT3b?:Vhc>==Mŋ& #np`c"!GC;vKp؅3 ɉȩ8adwmop/s j8Zp,gRҥ@f3c4?lf<\bkh旾tX,SDT ;Bֶbj?m\ܵF/>c8#@%b@Ku]vH 2IUrU؀Yo׿pn]בYлfpY-H%XG@yuuuLC =0"&3fyYǞfJőivQ6*zZCȖri ;Ⱦ]E6)Zn& @D0;V1LEd xM+A)}s &j=Z?l PV%[ʕP#B*(TN HHg;nV3 O/..x ٍ/7gy|e[Vff~{6 :`eo4(8^ igkZ@~#`/e^ I)`<>&g϶-xx2-QAlCPUⲑcޖ67l6NgřHcvR4n\@ܒ$:?????O}ݷz 8J!CR2MCsU\9;81oj, _;#eq &h?1 ͠ Q㋋|~F:y#lx Y_bvi$iei*kP*әZ&z=L-%"[YaDL$%>O-ɗ/_~2I=K8M}K$r#xsGO`Gj;jh8̺c?^Dq,Au0x'BC.|WWWj/>eUYb@`}{0Vlv)(w]P(%䁇FSC/ق\,O>?r1Mj '~i+op}iyyR]t(@prro|c8"$B<&.l}H񠡀lFf!wOl2ٟzf}^& 2,˂VXҮ˦Z 0!eTSِZ*ij%%b?~;FRVz^BTTȀ)̜mB}f`b@T3Y >}^z4?>???;l6AT$ RsmZa((z!nPCp18'C,˲ _i^ UA3m 0y~% ȉć,4RbL8FNTU4Q?O=?\l^xˌ]`aGGGh̑2wku%7ԅm(XVa|/@VR-r-xSlLsP{ZRv}/Ě  YgI&iz޽ZfT 4]QJ?!)Ho+BkƓ'O={.2(%SJXKP@HW0XNwǁ$I<ߣ |&\ibɥ$Si*KH;VIҩNMôl[FFl 0G9lpJzq_{5RƸ$g2 Ԑi쇱bt]5'OI t GQj4-Cq+ )*Nű-+-6R6aiE`Ra2i5fL@1xPӧ_җD$ dHqƆϤemV L&ZZn,KӬR~;N a~؏_:=C,y~-pX'aLM&:=99 >GYi󔌖a+LQBl6nP"tgB$%_Qt}5ŀ8DEc'1j"gKETˇӑHSF`]UvdQ~Ē-<. O)"ig"iqm<'|Gq#a:$iPAGٜ9$QaٻL6rVR _6;qA%#DŀrwZTv_=sL]7 KbTPL0jʝ Dѐi,S 'h;;} .d(_JR 1rsEaxuu$`0`Qk^917k*3"}];*Ie{ Z/$MT# Gٳ?)Fӑh&a,&-`Env:BNKJ(v$HJ,3y}v8ОJ 7Ƅ̰L3.j1+KB^1u,t:%D Вdo#>sz`O(89˗/+ /^Y>*d2.&Y p/tض]֘@oۨvhTc'|?h4jۀUArCI ـ'eQ6"jHy\.AƯq*sCIG% _&TEL&=>>AZ‚6رϺ  O5h(TV;8 A!Yt,L_G" ]A\.BB~*qQyMU |-az nz! eXYR[c'(i$I4&u,,z{^ĖiUk5`fYy^?pqJQ<iLYIErE40pTJA޲M@&cS@bg?Ƞmd^%qB}" k }&Ib"uܥzw1KqL,K<@V0L&~Rt{].%0`I)$3ї[&@~,־;EZ{_[Hb<7&MN h7 B>pd5x;K12BLtrfݶl+Mvɉ*K3ԵQcEmWk54dK'YhCBԀox)(K-)'I^tn'NdJˋ<ϳxa4ǡo}:}piij0釴`%o=LlC ),h4 nNVױsfZ' X@ЛM˗~i&t*۴mVO`d;Q/b˺!Y^JSP՚x<fU A)ӲIKw©}|| ϟOL?9bJG#+B={Z^to}[V+fyrrO}<WolRPA^ZE/‹ e)[HIm-36K(~>;yy!%# q\vPGd+Te2sUIXU*:_v v6A0fH?#\ RYYyFbpH ͦ^/mەSV }h»l6˿˿zﯶmR-"^ee)vfVڗD,ۢ#JAy׮r#5W*wQ]k$؆ifY1bm,MEEA$I>T >%p) sJ )PHh̲L820  RqVVh4 F/ @)i$ {h4F$Wxr3JG4[6>y(Pkly?fG/ هB)޻F_1j ;P'+;Μ8t8N 0%LP;$`(TZ",Kyf:X. t0 *4Oj^ qJrDqLezr9@9+=Lӄehb#ܦC ( ;ijEv?O_~~,PRMi +]7,5-t=Ibpx<dkێeZz{8@1DZl;M4K4 p2z !NzaaSe>JR۽lZ_£FO<ɲwG^HS>CaiZɄ R P.d9PPNt:g *ظȭT*/bM x4`#n׏=b#%a$#j$  ɥg%_"M|^:r(@jH濬[cFDi$Yvy谥;7#$@DYLn6z=.d)im1\lɓw+1J[OǒsSR|>{ɳ &irCtMOD%=!D|لܶ*N{7TKӴjݥ&<#Ͳ8Q4-(QSd+o5_̓4XtTpF\D6FuM7L5 <A^:PNLӔ!OҲ,K4͒$EE(j)6\.#Ynaa:ajiT$J8)KH)]םw wç'H:J70 (tG+FW %8Crlq(t[i78R*Tak6I0U (S"TbR~",Kuݔv +#py# 4.C\"߻w^__okVbQݿwLsan쀀5J/K]]^^uԬ ,]\\\ A4v;he(LF7 t##\M? ˊʬf[ۊ#4髫VuxxX7"wa[@5FTn>uM*NȐ%* iۡ<5=Pu]VG/eyB"3˶ժm[I1[=YFi'eZVu&F ]U0&Tp {{QA~P*9S5MCmYF%DnC_~%P4M!4ddijnj[2rdahH ]`1~ߓDZG$ZaJiai<SB. m3h"dh5Ms(*/KxяqZ?99=؍$FO~rxx)h40Β]^xH ,3M[7 "%IC+Mo[Т@$R3<.Hd^~`ٖcۺnhy^FbooH{_Rs(W*حRe鎠"H^d 9~ ;C4#٣ iyt~~QBk18tCO$RE t2{G4Mbǿl49$Pqx`S7v^g9)ggo۱UdY`0h u]4,G,H4a5-BR8&t0RI?}[hԏP⣬];f'P20>}t:|]pXp] 208{`047.cq?ެ5Mqj(DhsI&WJN0 z^_.dۖ8kQ%qR*ikF%$M]M /VS+)R8&܁Y+ؖRZQA. CX`aw}?0NX,n`QalPSYw /:JbΓ'O$ETVMӨkqaqU$r|;o4@v ' d!Bt:e]6❝{rrr||\!+N$uϣV.e)Kp+ T.PRD_~R Cbl²_6u}v&i|>fAi ê@〝8s||jՃ#:b؍$Pgt][nty_WzYZׯ_V~rZQ)-j$fp)13b,2Y4:u[A}˴JN =(ka^j(;zdH*~P+ćCW)M 8B-Ŵ,GS`Ʀn,/+ fbb# ȸK"XYh;Ç%4FaB`6-ǏfNJxn$2q^SHM4s;Ipjtڝ~lXevn5[Lֱ0 C54yk|z`7Fb=ժ\*#imvӬ {m,2-2 `M 8n6i+(g mA@tuuu3 L Mi,N)eٖx:6L ɱӦt5o05`hbؖu=$IP[!a_7jµsx]8Ni߶Mu kx,?>;;W2"u~&v/^< @Ėޢl `֡$'fM4Mu2Qf3xUok%b×pv:B"±8R $`,.?ϲL74NӴF%^meq/ #}" yl6Fi? Kl,xǨ@4MM,Ӳ,kheڎ6۸]uCHxwqqq~~^.˥-}іAZ}Пq\7j4Mt@ wԯk_}ӧO+ $HiyXp(Jرal OJr$60cJLZ@spmZ{{{f$,IJ2~E(2$c"h43n(,N- `Y<]ݯT)]9;rdɰ*3($kz)%R|\k4Rz_$Xp;H"b6XT...Jҝ;w:iږkZanfYK'ONOO͆uC3]8UJAj^?y21eѰl+K0R:RcofEhtKO 2 l7B|ԓ4Mom4ƓR J{>&;qB;42>jhVrJj>gJf]x3d\ԛ0 CeiFۂ4-a,s6r%b\:ǁJ $ֱfi8GQ4L -ݼI\5MwVZ[Ԇ %%2ax>i&iz||k)Wr< FFDRek ɐ SWWW<<< E=0M"~Rii#l6Ѩ!/]H*d$d''''''G)%H@R Cb[9M%͛7٬V22 #Tl...8AAB88ρVz ANj\|YiZ_ciZ vԴzWU͛z8BްtKQs۶K%/}:z>2َS4,0@.;ia<E %t:V8I@=#X,_Sy @ ?c~JnZ' wҹ]PC: ýpȾ v`SyHehJOFQ#a$s$08TV%;$M 2f)8F0 R{X `Y~ID8o)4=b D]וڶE\V;MJ=JV8$T@N]בS(pD^zr䓷,3/kizZ( ÂJ9׸ Z+1%zqO&(l]`ZFmۍF@,,tԲGƢsp|ro4Vv\'Z贒-ȻG %V?%2]aAl< \.gt,4M}{||93"><":acOW$4#ʥ/~ Z*躾Zʥfkki C>._:G{wxx/ A W9ۃCd-M)&eYqtֶm˴PrJebg'"zfi,4DeF8eo6k̟Su1duVN,ˎyb ;'2]'1L ݱm7MQ!8??L&Ŕ߿{./q/o_^]*#TD @(JcỸ` 6zV)׊ȄT唐 Op%*oPl6ǝN5wncBHĔn=r]|ߣ; `7V?~4`+8l(98FŒ=TBI Ν;^4 jd׫bHjkX#B:CN&Z-ɡ\ %rɓ'Ѝ 6 v PBM*ˀ)8r$'YYFQ`GH'B"@SŜ0 r2*2FQ8O~ 3D(pU@!"Ѕ1ͦٙar9c]7$Q*]uQ9۹^ݻwC(!r*˵ZE9e*Sx"h%u6Zw.l6 p8yvwHBH:v5MQ*1 Q7[:+>`]OStx _zn쀀.; P6igbR垙E eY~lCI,zuz:L8n8𽬀ᨃCju:nߍzfիx"ƵL@s+(QU*6H *}Xa4EgY2Asq%ZYp8BVD{-4`1R<ai CLdG->88Tϟp.yqI7o#r~YƁ|xV1"KpzA? b`es*ҲA MӴL34tMSAFa:MR$ϰnCN eG+rƹ#>.//RIA[NY{d(7|a~#6Hs2Ds Ayn}gi{ǑD;dFPU ]כl6O0L۶)F"zL&W[(,6ڠN~0Z):eYeanҜvڶ=̀F\ͦCTL LM`spxooqsrAvj BAVv16ꫫszЀb*QhRX3/Ւ:@@)YR CBxxyv[_0EL5qzPu^GQ%ʑ*t-h& ša4U*0 xi^ٍ_[2N\űQ$pXK|M{(:ia^]]%qlPbM`i!t o+9g9tkٛ7߼l6*#%jʊjChCmGHz=#'] Pcec;FS溺; bK 9BPj!EjmۺEI yjI0< vz}/..>#?h[+KSrl۴,YdΓYFC 㣣^aJ$$Ujg-MDxD(TT[ 'I0,PEQɱ4-6p7ZPBl0ˉ&`DVYt%^;ƣJ`H5Ұ=5]Jػ^[: zD0أf7 ctGj5ҰY̮An_.N DZ hJslFGvY2) 96bT*g5"ޮ$(k(0 )Q|0xϧhAիsH0"-H}\ aq>K^4t&2f;SƍہTK[ rSlL4횦xҊ ;<.z(x` mp8RQ aBW\ugX|ZvAn؍;sSfHvq@44FqrrbǏ///u@Qd220ܹd&i`0(JFbQP;R96|>GՂ~4ߒ:*oO%rkQ=zvCn6-{W 4!rN`3L4D@Z&8F)S)Mi[-G;P˥iZvL!_ v0u]K4Hqpxppx?n[('M{}'#6ME(:N:,p麖rEJdABk 3V&3=#ݻ/a2-BsĬhūGLL'TDC뫕.Ev] Q0 {i9:A\zFwcCa>u2 wlN/^g?C=Z w`\ Ss|@<,M?gwwvvmW;Ν;wsr> @ʻrٹÖ+uE>99}~c\-tFTFcDeՂn+m+rO yyy <mY H򶿿4Mab<LUR (Ҏj17bҔfiTQQ4L\[dޛx<>?;S[A>5.N|gTi&De7U*rr@.1>HJʩ*]#`[\<1H9!kL|ޱ.l=zv}73d6gYYU+JR7CoWyII 3\6KPNիW?]Sn؍*n|Ī屇!Cykdٗeȕ!TV3MO?fV 4mPR}ǹ[պ.՘hf`y d/..a_ mѭG :u]T+qi|ᇏ=j KXw@M<]5M3?DPZ@!ȥg۶;d2cA$<[$eVQ0Vy Hlf5H,SӴ4I쨶cJ ,>̲ Nf޽{Nx4{+*$L>' ڶ/^ |cheTxf]B C |]eJeSzh畣  P9ЧQPd Ju wMWTÛ6zz J.b0!őt˦ 'S`;䙬oT|ԵZV+? WTIfy+[b`=<9| nЮLY})3Fך! G/&)4/KoN]__xݻCyNuGά4[ABA6I-eW(VcDZj>z ;k88 s,v~w;0pͲ,I((f7+z`%p[cd sB;rQġT-&4-q.//' >ŋVRtFp=R'D)~@@'Ix,u\|⮅^]]//?~yyn$#eZ=}SeeN $!FԍMm "LeqZ$I.@f7vc7v@n|cw,LE1^wb"H"zYT:::2L%鈁(I!x@T$?Vv׻O+oka;??dyiWl6OM@S`ZeV:zL;j`+R%"텄 |>L!8h4N_?{?{JrBBqk癦9_xP Z!iN0 Ig! p"cyl]z_PN5YAy; źMYP/K>F+Ѐ@5<8\]]1v쮇QG}_۸up"U@);2%*WSQg3_:ʼnn?zh+ J]wjyLj' N\x4XTQ!$疋~?/`7vc?Q&<\#^7MzXCãS:==EO#(([;tB)YmZr4( GWmUV˥q7#RV8틑$IQI?LyK:ǐHL>j<7M-1 (}Ȓ`zC"=ǫժht{=DrIYfiZ,*f*D/V 0|xwvH aN -R_/%򫻱; `7v##ÒXXa $e= Гz6nr???nZ8g.G;)i$IR.kZt:h<S6!x\5R@7z?4#7ySemUZ\.O&8F rwԫczLhZVh6NOOOa)iJ($ @"sTR jZG1g @9&%!4M'aft КƷ) vhJkvfrzIJ,? >Sz!X (W TFD ^ҷDxP{DC[cWWW y# -x(j5\Y-qMZV*9(}bOX p]v;>?8쾓{$x ȭ U, j{IF# :$ǐ1eBLq)hr+lyQnGb>ݡjB +VM4\*BIxr[['7եJ)ӴXm$:eaʔ&7x<>;;u8fbJJ0<B(GJe4L&Fc޽$IvA 4{{{w܁=vt '/"Hϡ~ـ!, Os@lB%dڰ-lTK'Lf uq:| н6W{ku8n6PE$av:]p0dNׯ_cn٨<6u@IhϢe s\.:T[՚fR@ob ^%=01譀[vJi3-O?NI)8й x"J5R٬TUZo^ J`r5фf}3i2-2 qs[Azp*.0Х,QF Y6Dn ZZ*ن۶m0mh~$.cޢcufZ$ʱ[J*5;ie|񵮧4-PE[\6{t4s0 + VYg̮y$N<R.iԛ7oRpe `EQˋ#:Jn^͛7ӟ>8v"# }\mCs3n쀀?^Ǩ/9I X19L4ȑ \VJJ)mGzF:D "OK1a$rS j,/rNI%1*0/HDel!w08JLV@~iY\.GGGt&50 7zFX,߼ysJxxQA\Ӵ,Si't7$im;\,iZ-A? (0'&~҉ I;l- f|rgggz>zFDK5˲)鬔ʻZmLY`;BSLeT¿]DCO4^$VsϞFaT/T*t%+ \.%iЅ#Iu.Q`9g=z뱒X);z(l D|Vu݀^ c>t]w<#)8ڦxwMavJ?m;7yݻw[D(x>yTF9&Zqt]/vN`fa?r g8phZt%>4O )2`G?-ۦ NEEV [<71ʲe@~bnZ-QHOBKm \^^J%,1i)-PTVu]8b}R 8f4M? .hƒ-J].qGÁ;,`7vcwpכ>RڣG܊<6 7oޠ@͘r' HG%w,rJaBvNlȦ z[nPR9ruYO?pb]ՐgǾT*r6yd6ݹsx;*O FlkT8kX@ Z]Fυ2tRo˞5Ї l)&*X, 5]KAΫ:ٲ,K5m;/4ՔL)M{''?яqHlUF`/ʋ-9T5cRafP"hZ  NndF뛺k70n2QIT*wEV Tv _6ZM$^EoH^l\ D<{L!(,6rmЭl_D?::J%2@P+nw2LSw#0z*7 E \Ӷ]'Ie[lTR/~ j~AjVk<V0 u݈8MPI]$ jմRߞR9\&M _Q!2`3sݦ" b^'TV%/8#D~????GY.s@te='#ݞU!DG16}n<QQꫯ?~<y Aԅ7sAMݻW*NNN>DJpw6&gu]e& RQs m3uTreLӶw0FƻSJ$3@(=I-֔J4Q,I#uèV^{qדJfNP@I*@,.Ä6ДH) ۡg~ovj!#2jҿ 6hs@=GC00ؠO0"r&{aɳl6t$!K`2axppdNT |Ç|/f\",cv#%ߎ2\ ])٢L-L+Q̩䇐tuueYVt:H.rz3't%(\.0$qSl@Z9s?SJY3d`~eh\!3%~ri)Cvp8_Lǥ6[5?!>Lim#kvj&.,t(JNTTrJS}%oi$Oljm۱J\.W/]"}Q/^h4xVܩti ?f? l57b"U dSH* Pu0HBd e l\N3kFI\_"h4S 4` )mv]w VqFaRmNL F2h#X#:kH [Ki-ɻ; `7㼀?t~i[-V%$o"FqZ& ^j 3i9pTC&)R `4нlA2]*#`GEzXS*Mӄ?W_}l\zzjcL DoCynZ Ba/ xOz7 G)ԵKn/..lۆmۿ/>d!F (Dnc:tFm!% itH{7V땿$K?%%R4 0-SiJrM4J˥Rz0 |CLxVT*o 0(K @0fv;.+na3GԤl4s!j-:JdN2S2K@hw'*ٶ,˶0y勃 8E$GGGe^g6߰Z9v^ߔLzۯxx)lŸvMRэt$՜N8f>X,ܹs^v-kŦMabKD :lb~Ξb'|VGPJiK8D*岓O* `T*n( |>5LuVV~׳m{<6N,c3 ܮ$M4[L;5 }~]nʣ$X@.$ls6 )r%TʎhZz2,LQa_C2~Eb_VHWbL^Vbzv'޳J\*'_,TDH*aA7j۶[$WAr$uBrIZ}4f3l ʲ 7k$ePaS;xG>dn/5A]ؠnDbt:"ʻhy7vcwva?Cu7Cc6\ömr+HI),VeIiGqݻu}ZV  8P`2hyzXTUpݣֶm:Qx;K H1' M~޽/^|B,v{,!K$-JV tU. (P dVCd|i.e>xzzl6Otz~~{_R2ۿx<|/K<6!eCx}bz8 dDG@@:d_%7QZaƒ V{Y/˽nV pKnjnCW vQQ R);e&e#'#$l I(2ϡ6~RHCJ/AkChpC'T{'땥'|lRDtѷjzxxF (/˞-b^U d/sbfz%-ȑ Ve%H7"["Mi\,&#^1%R%=nY  `OLs^sr$zabo$#YA (X~_'KF#MS5A`YҶaroQ4A 7e(MtR)/q j4 <˲JVvfi󼣣zV%1nFyyKxz6V+Áj˽=44Jf\ZD)s!ǹA=Y+*A, C)ej= NH xд%L\. Gg: jW+q+aҤSv%FB9&J$ːzpcc0p8|wvqn؍m?p0<888>>^/^]f86Z Ð5.V(r=dVpfZ/~yϑ#B:PǓd6- wXzfr<88z GyiMOn Ԡ=}XZ*4i:ZLZ. ;VHDiUYǝNfUÑ\__1'?B;ߐ) ~% =,<.jOÓleǸԥ#b ug7,SMmP{RRf܄qGa n__mΌ,S7 #B+DT:sl٬,+ʶmW*U2JX̭CPEiv Fտ 403;~AKz ب@eȥRzZ` ٗH zUʏwJ2 &oQuE0\,| uXdftrr`bae!^܀v L8r][T?8VX\#X 6INC6X8V@@ppkYHRYۊv~^X/x܈n0ˡvw] d5Mol÷luC^Lqcf0%Z1rAa R D-Z(t۲?knUǡpvvWp]_}`62xVׯ_O&V= { HVAI bxP(fUz^,0RH,MgajnD?TV2hT lPv/..^|`0 qkxg.QQ#ێl!A ᬟf7dOƣ.wAn؍@h ,j/rXl6k`iL'np(VF2J2|a ^GW_}l҅G|>lzzX,^xݻR)r\MӴ-̑ @K%\lGd0 t޽{WWWI o37^Y(H:e4MDdzAO^zc xi?ݻw}0 ɨÀ3 `k'laMYp 0 X.Xa2k_SE$FK[IE"6Sog&"$˲~򓟼~m+M%qPH@_~;"-h5s yGi rh ("xYCɒ?@Q cB CGer\Rh  PlH5>X FS+_\\iz||3}E0WCY.z >s]'ɓ'OH@#˼\\+b)%]T<Df3Jhr~)#Z z %9@so X#)|En-#'[Y윗ޜUC@*Oɬ 6:2I-Q,SkvVmڒG|@ p鹋.6:J!f*=4pT <2|ǩVlq]/ Wtl^,JeZMgxryyjm xq]__Wo-5:Y$@ 3fY. lkiVU jf%닋'O7}T>\E`#;nV߮ yv(Am?4< ^~MgVSc-I|WϞ=ɻ; `7g$$L .//F`6AɼT*~ X> :V~#3qagC .g5#!5**wE&Qɗ_YF ]|@Au7vcnN/~= ]*=*ƑӋak$=ue-5J=h¿`\ˇV=DbxLBlٜٺ+bApj"3lfe%mi אeRRǽ^*,HEH3*](̢XšJDM&zvwT<It}ERS ,no& AK57PJD~RnN{qAS}V 9UR)z2 ¯k2 =zj*TorIp oY"4,C B\5$A YF ,urM/( 4{, #rYor.?~_|{DIJF2fa肚|%jZU*UЧmSB@a%4MGf>6]L0iֆ,]|d#) AaW>nTHjĔ D[%xDp Q'5Z7 dz w;8}b>/W*fVE?$H,@@6 9@8&/&&u݈#`?}UOH:mL . I./ܽ{ /w2͛efh*ouT*kfd<z_$) 27(dbХG2 ӴL|Ru@*JZ=|>NKԋTTw'}& P"Pdlr2'Pr{|ɦF`; W_}U*=z(gZ-zeZ z,CYu݉ hFgA7\.@yt:V+0mۆ B_K JU2: TKJR!Eq&˥$i@xGsV9X!40̐)xR 2RRT*Fж%wy습Ph9:pMRZ}e}}@u 4~ _fn\}j(s!W, +`1 E۽p@wyg</dP0 [VmXĄbmXAY. 9C,J%Dp& B+!2v۔mAA@<jdh4dӣT0#}2`@Wr`\04gٛ7o={ٳ}3Oxh h8\b?EgȞl4ni+4Zn_Tݻ$Q׃q؇~xvv6[n쀀؍c0C<Y,ɿPφdWf[CA@H g{GeF>v!LA8X2 8;;~@a{6%Jrl۲4Ml6-l-0НB \ _8Aͅ2"hdZVegY8`0D"ŝ;weCv"iaZ2;v7X tZ&`(Ш|\.|P-  ,$]d+r\V,K0"7˔oS>5 -lU"C@*Z-$<(ZUVq p"Z LAԝdxmGPlLPTVW`hJ隦_"YRosVp׀fj %h/4gRj8-{`mQ5MK Lr7p"Tl.M;f f3ZlbBп@R'TaEX҉5~l61B~vz̒pXT*saZaz\ǔiSiY(.3DM1&。'q4]WhW h+ x}}6`obړ)e42:@!ݻw/rblnmVhm˽/_^\^nhG҃XזezfO>\2[(HB3߾ `Z8~˗/6J417.^6B@9V+xu݂-*p`!3~KNZ)1[ ` v:˲RxLdɻ; `7ilRT6@hv#eşq?}t^(-#B%qnɃ-Ijgu6Q_ S5?o6bQ"[EQV;::3͖Q9PV9~Kb(C@VrJ,ݦ3NB\)0QtiqAlD*\0X,NduRj\ 3Ƌ}`0uG2W,2Biv۶kcZjnh|kRƼ`U XzXOñA+Y+]|[Q.CpZ Jńњ@6[ eYp^?~ŋ͚m8VĈ2% +ϒ!O"2zf5M . Gnu_\@$MTvS9l6, f>z$ຮ`0vjqRzlxͲwϟZp:PJ%+/z?`%if5H+~FSȬRiD")ȭ%?-: +ʿ1ѕK| FZ:!;7F/_dqa#oh!RIH Q0k0S*<61̤/$& 5 G`/l-m4SqzZi$eJAk)}%dIKl_}z|qtҪ&Kq$+뺟|{ݻw7^nrJi(`[p{1ږ>h4\GD4u0mQJ7T pA=mG~w}} I< G6e"0=%Z]dĘnRq5޲ ;$ңVu]1ai,\6a7(Qv dV!x޲ּ TG aǠL e Wx:F#rv;8p8`ZyK4} GR;w(>䓗/_B#j8jJ,R~c2AA :R @O>AjJM˶, 4QH [z=O?Omۆ 6 5V 1`V[VLz=^~MgJ'NҤABDHM'6<fAEQ^{;w Ő @9q9P(b8,$ȶ40+.wz=l N3э?]X(S"Ley筼l6aѨc`: Hj`1S\j(HhdJ`L 4D j5vA-/kM%@zJISHS /C[*fYQz+cLTLQTBzrr236|c>'md¥:80^%#U"&^7/Z "_ G|>G y.mj#¼ZVh$.e|>3 VbA^Bzyv1aH{?9uAx7["!EK jp8;w n$7Z4moodKX9BL8ضm#5kI$bh=P398V #dj4M7M·Rw7 h^C*32xŽz$vnf۶2;=CֶvӇ2+Q K T*I d&|> Z€g}B"[(UǞAa>fxLT/=5 lf:Ogu2؍A}lm%Z `"_\]]u:Dpiϟ?DY^!x:f 헠ᐦ l(>_CQj.lq\._xO.//MlZXC3m6%u]2-H /iAC ,jۈ{|>_V(/- ,Kg?̆agd%0SbpvEN8(BbC2B.M|^ØSݤ|wfSAr[Dlc,&LZn2fFk78g}6 `!q 0 Fdw&xzX4jX(;w^~=Ukh=Y:eLېyTsxÖaX@G4MYΕf.tD>\Z '`AY6"0ؓ<ְvc(]aݕD04WaRmaR#ׯ^ 躞H\[zj|1Joe 'kIT rar)ׯ_C$Ȳ;w f DQ.dM(VIx $@1$$Yqn:v{ h2^%Qj/@ `_Z1_FhZ]^^FN 4N)΁%3'x{lZ3^E-Aw]h4|idRzt#}PV B{^^2/$ l6,~ɖ3EiN_eRYY$ }WSҤ:Dy~ժT*TfD `l6p<yqJqPJu](`ijYfFQ<7rvpFZ|$VaVat:P(@ǩVNUm>1iF ߻1jldʤBuVHى-}8l6($60Npg) 笽KHB 攂E[$ߋ|TBbn?jeh%4Iʺ4 ݦiZ :pG3%=.aY5.Qh-3MBIo6o׺A0LqivyyX,mY|;@˳3V+e0R2!1SHA"χgU#E̜D8E+f@fch 6l~U'%(Rī`bAiP4>hܨT/wy#$d~bYVF/_\.ktF d0}߿sN.DP&U,Ov NO;;PTOnD @˃b: >F FR"'Js qQ m/Fr&'*? L/W{wUN$o:N wX 75fԧg\F#4;`}1ͽz% ABt;U24Az`X]H.yErӾ/[2|fWT, nbJ^ 2?";[ǭƔ $KT6if4t}R ؜mi*1ѲL)ˢ7W~i ⣠lF^ZI+J.ST2[LYa ñm]ӣ$=JEn薞eYaéVZ5M(ܪ eb2L&$I-Iz)cYݻw'+`{vZ. ]'chJ9"\7.Ui>{RS/~ȷ1T.8r|EE^݆6&~"L0V'Z VffՊN@1gv:yT4}F$Qf2 OYh4jvg<2q KYrl۬9 ZmooOu?>}tF ,2a0p}}͔Z\afm6>@g%ZV@G䱴Y?E>p U\$@Pv{^Iz r`ACS0TЕ`/oejOPIͿUJYRBVx(@vaąLJХJMs/޿b&($PӛNiĔp"C1GXJz1(fM)AIHbd"c=JU6t:l6H|Ig;\lj(myx q4XtEaL6ǟ:N$bMe ~qqZ56LANVgϞ _L)'"]6AB! -j(M| kPRPqB@ TAOqI$fJ:Eɻ; `7Vժ롟׍FSҴ"hvO9S\`o!)5ٌR?VJm! XBJh` _= &n"XA7r EI"S\\zBʆLozXsLF}^|k~,q혦 Hp(d!T =AV0c*$X iz]qf)$]hgh4ʹ,Uy\ZVnn)04uͱ#44ai  ۦ}2Y*W ;IYП/؜I˒S klB6s0P4PhRfM2[cSVel)(i~65?Հ_\.'yb{0 h=`o]8zann쀀n90z3,&sem[uO[5r cҸhG%sD|Y( &(SP2hA]{DdXppnϙ'i+fYvppʪdZu:NSױ@L 6 cDxCKal1OS?:4F7^%6AMFBe=N0z^oXt:  }| h9{OX`ii1YX@y(:NŰhN/_w}6JH~vpO*wH?fV-;˔)籱А&s0 H]ݻn@M&,mPkAxC0 2#[B oQ2,<8#GFu(\wc7vcwvfO?_8(Q/(0v/ӟ^-$YIKY.#{X>?pooD(8FQT(|>w]״,MӬ@fDm3Ըl4Jx^kJeU+($EgKgu0K̇$Y.ggg%T.N-&#zw !ֿ*Ѵl)],pӨT D 2HUm=jzz!Băݞ`N"eYNG+a`fC^ 'V>쳧O?/r·yJ8yrHjZy˥e7{†Ʃ^ 5ĝS\RC9W9@ 5޽+%D`!*+Vy}H6D$*;YzeY>b[Jq4__GK aNb+FJsƩ(l&|ݒ& ?DZX)`$9ޗ4I dKA@l><` ffYh@F$ edm 2O%:@VwpxxEd:ITe vC74MS:IY%qƁ' ]_vlB>m躑Ym-ͦifZfųlZH*ۚh46v谎F#?H ]sTom\YI>~r\.RM I͓.q$bcۦ DmohY6h!uG*ro`'&(򄕀,"xZxӓ 2`\zt^aW_M]v;>8^.ʣ/jA%3 f -t;nCI:8ly90'HD.ftFD悮؞9NbqTlFZ5 c:in`km*̇%@E;l%EXWޚ$1I i}2G-|j(+iQEmR--m&fh,JHQ%HN/(C" 5!sCXrdKAPAg) h|(uAa}K]zR KΎnOdC14,5ȥ*,` 4JǓh\¥̶m8Kh3nE¿KEr s,onQjb29VǏRO* a VS}'\pAB=@rtTBN"q(@ n&rA#ƶ,R7UoV/JIJ%He7v/^,aAN*6DeW)jj̻.8IRh0B`!gª/iJj ut eZ4M ~iI;N)2X.Dj av:Cש;N9z@Ӵ6M3#I9aIÑsA,BTNdIM/!GYo%ݩWjm;ܗ@P{W%}F <D0~>}Ν;PSWBT*\*oO2Mu$TH BJ.cA%0 b"ˌl(X *mDw&N@o!d^ RT:접ۋG(ҭ&ԶE_,˹$Jj 2f,*JIl4&R*K Pq!5Mq,~J0qn6 q5ԉj 0Hq` 1 8{.T*u:}?ZJdq@4-fIl,V ;U|88R*5MX,0H DY֘K@as^{05MË}?F>JSM; `7#:z F4+w1g%3*#Edܻw%#IJt$.ûJ>Xmx W!)8to6~Db]$e1At^ˀ}REp5 r 4AѲ[``8G}ڳKRǵZ PG 9)H0"]L2MKFxSC|>GzYu:F^ 1F!7dG;"Q@ϡt%r+`\,YbW9T͑qZ'!M#³IZV; pjBcvۖ r,ġb7ɏ 7n8::Z^^|rZT*Nh,Y%rDȤ1!{a:`$0_D,)KnsZ\JVTpW-#Buf0mS(Ȳ7sqˢG׷P1N1ұeY詧¹{RUUp'sf`Ol4P4ݻi rT(1R_`~p 4t5Y2y*8r3< ˽syu;2չ>x୷Z[[׆0 ~XQs7-% 'vx49"jc=O gGW'?>>ԢDc) x1bNt:şcƵ"6cNM$>+bTgwW>L-|kKKKh&4.,OIZ9mTHD0Y`#'RI'ҝFB I ?g*=\:f l $숙&XGQ Tb0SwPQ H9r2!#)%' 0ExMo0ڂA^6 A,XFuݿP5)!jDp P!WHHӜ/l>-:+HXHjl1/P((J@!c|HؑgXE(B󚦁S&AG}%OR`TM!G#vl@(qO&^\.'& ޺uk<gqYd@@>,;KEccpT*s*v#tq R\.wXb獘i\E#s5Olt\JYVaZ,|qfknEtjyjT.ȼg^Y |RZ (s9].y#q_\\ av]uϿpZVCBA p< 2-0.]&d2 |(d5r w4$ϣth>A=8vO'p|Ir:r)G2 N") U\ |q;wX'n0'EтyA=Z J|Jө9U;퐈A ` |ewgŕ`Ţ>ӕ dvtt&2pˣ0<88}o R:TdI%Nʀ\-t୨(;u{Tn9[;Lp{{{{LnoiT6E &'E{h0\xWö8<3V R `e:CX|z]~Z x"ajD?&$/K>Fpw;w'B{ /Iʼn8*"D"DĨgH^(iR{DzZk:G M +cɑSPiD8ODUTB?8ՆW @pxxmt УNUn{m˲aVXtah8=Ç_z%nWUb2;xFn`F 0 #8?`KQVѣS oFAU속F.K8CUEY/8Qu.#MaP,kʝ"@̷z+ " qG;]y<tjNȉ8$ Ë?_ݿ_/3ȻCQ2 VBAS5NiA@BR=]j5HijM t{ QFb92JF@$Iߘ%xMbP8<<_X謯7͍ gVpfbVAy6&O4* @N(qAy)rAdHdѶ<91>n/--tX}B]ד%$ Q(0K(V </JUQD.P0r2><'KlU0ނsrCp?-1CoޙdQ2HuA FY`M.ph4h2AP*q x@'mp'EE>3bŤ NKKKH-ˢ!ŜXV,hN&~ƍdRm۝ZVa `j:2tC0ݵm;w'@Npy'"#Ȏ 0bE\N@!B(IV ,5DZAZ;~\F'd'{/p.9DZ1 m(?k<ʀF0["!p CgL&iZt2ޟ>I|x` }NJCQ {j$߷mVj0 D,#J#=k4쫒ǸL&`Ì<jG_<IMduuueeeŠzBav#9)\<GWw.b;ȧ3a='eaRK/4d8,2 b˗/˛: $=C y1E}*w1#__7yttYhXh&oeY5MqDZ(L o6fa EUUEӵ8e{m4=1=R$8RT4fi'B8 `EcVPeC>Iqwz _^^NP/ \ZZ* ˕- ?1C@j-(v/SAD>62!t hP!(B%Tfq;u) 1N'gLl3.,ܻv^/(Ґ"-|:>m19Ay)`\RΎB1cPi&q\o4H4n4[AWe' X@%-A haacD)T& #Ey9>>~#\oy~9Jt:-TVtEJY2ۢRjCe- jb |pp`f, hH0EYd@@ϰmOOͿ7 zHѹm*dR76O/qQ@?q6a- z^oZzD]QTEQUSDKD<_NFjsBP B2'˲P`*4vf۾zJxTS`8vǝ"VVVȢG&D!%~W6IfiJ&!뺎3NC8!>Ncŋ_&!;pC$'PQA[ jf[X,bt4-"e *\L5?booϱM9ęэt{Q)IwlAF!?[<*8 #`  UP$1c`ɲO+)j< mcT1 X8v+G)SO=1_re<ST47n<(1@`rk_Zׯ_>Œ zV<諑hw@cbtlʕ!z2Q+ WIzKQ(\r8M-us:e:M(S# "9o4MT/f>b)eYIEq0LDc ZP(B lZ-ذ# cHpq(X lٶY{iԓQ@]!_h[zT0-2B(/"'4/8IjjXz&.,aDӴ$;z{ %|qL95v7T*|TnJb?+xB7Ͳ$6R cʚ9Y_@J_BdB"Qvs6o^!Y[3e/4C߂lp8\^^GñjTXpRT)1@4]FTUE ᗸ`H)$|+uғ2 aY@ \@!G įr$rnq(9 =e?iRV& uGժ,)Lp'E:O[C'.kf3GE d@3auaN^fԇdGg2n`5d:5{T~|@| j(Cߥ%(oВ*1eWYH-m+eYzL_ljP(XZl6 [41.:Lpټक़)Z?$>4zt#IЫXS䃑 F/n~啕.נk !nEYd@@@ύmIk4]dh԰tM- | 5֖8jFZ]XX888$o ;YG.g܊ 9f 3!$z`l+({Sؒ\bRS섧xyCr.jFcyyYV܄Ç{!&{'$ @c㴱|Ԕzz5_Zx<6Mk("ý۶:T2t`kH O7},5Yʏ!np~Rj(V=\.W*Y(TSXdtȴ$Nӧ>|q04USU0Z}@P,3-2 uYsxdn<׆# _!!EzQ^ yҳX%R[[[~"v܂{sЇ\$55g ^R@?x7tt$ ۶]}T4-|Tv$נc!S#n.⥠RNի/} [jf+ض}uI)8@\"ZAEԺͩ<}~O< 1TUL !|{dE4|O?o6$߅=W5zJGMӤ lRMHbq4#3$U܏8"=eU麪jqt6RO3{) \.*jEq{빞CyK ƞ 4)qq4Ձ!DVޠ\*jSt1H-VtWЇ>4N= ZSYbg9R,,,95-5 @nqJA嬈7LphZ(X_F7 ٕ9I )jg}T" ([,p6N#. x% Pu]ZQT"ye{9y޽~իZQq\.W%_^\\ yUU8Cw4\% ]zHr[ *vXt_@zuv!xZuvYzR&ESu]a^P@Ɵ2R-D'=(Y1S>2"̢_{lؓO>EQ|Ͽ[("8-A w)/V zÂ_,s0ض8Jj?9KA!0S /Md E/dqe͛O=w1~8jo;n^J%yRJxgh5*TFQMUH|OsszX,F1,vt`QI.j{2N٠c㗝_wϫj'Բ_m=&j VUV S;;x٣fjBK·OMu' #rGG E͓TUƓejZgn)barHAYq+4:^0z}m)mllDZTmv3Yퟯ?dZ! (nVV>/ ZK.kTyiħ70Q'jUzdo.)rUI;IXMMq\._.jǍ70N8f"0Յ$'$*|)O_l|y'X Z}SlU$8o( x4L8`DIm3o+ D.cb)MF.ٱ!;+v캮eې#bEle %l*S H`R}M0 Z.\ O@86 RفspK]U@2Oxc=č>$ )̹sRrJEYd@@Pk_//ollK45*r||yhMBC=HߥsrΝ$ȹ#%E? 4QyPN{dL(JnnE,U@5Xl!FANI0>KsT"BIy1\vˠy^fgyYx~wqqqaa!QrSgD9Sw5q!7.5'Ҙ >TTe-DP bKƶRoue$e۶ F^:[3klM !t:gz2X7m- ;b*5#ӖO.ISZ+Ƽ^\K]ey}IqV<>>}H)eZ|u(CO JbELEQ666Jf$˃E܀QjG*`0|g?`.zhg$ -X@śDӴdR/\V5v :\TYو,;0lݒ)](ܝL&`>0,#eK%#өz[dM($RsIfj|wFq lb[*._lfC2G)ijeҥK@PiQ_ H t]Vztit2+I792OA&#;8|#<.TUUDඒ"?V510u]a߇A ڢ(z駷q[}۶2DnϦ΁#n۲eA 66E.)={{mH8;v6MQU[[NX8a~3./#52eiFddZ%Bcv L-g8d!3q}pae-3grR//7y?ԏ#'1OA@qs LKI9X X0f$mM%blT2BV;::?|ãŁNXt:tzU*8;aD(>pv3G>`f4jJ " "nwyy{zѱBˆW";fYVb/5 :b2$YnLp/FFiCwf*1B ߈r7BVCKӔpa핕5.XM?LS228A?u:nmm*O䧕8d@XEHNV٤&)*P>T*Q@yr^:C\PZmRB%.NNa)Z2}2G@nζ-5MǝN& \cS2 @ad;꘦t=d!yPAt'vɫ$<q9B{Y05(C6h-n#ZxTT8 ^x 8DP8\+_(!AR0 ,KO) @(r%z`\,'3T> (H9ַܽ` i4(PHm0~~]\\mv~Hig P Rד]\fխBbE J `sEG?z||AP8S HQI( s8ܶe ]֖x?5vZLW,Nە$ݵ*JT*჉ :QC0llyd65JHs/ HN*Ry&@< Rnϛ\wɷ  M!…{^ܜwՙN۶;2oky#QEI 7$Sƒ*P&5z<# 煤J@"Բ,l(ԋq˂aN&h ޥѲĜL&Rik:#7,2 ,N7Ld}}]u۶æۭxmw) |XA`ss^Xݵm`Ƚ0闒Г),8JFnY1AD EaGd8j0F4`x$|hD\Nmkoo hgEc:4| Tdəl`0rmmmmmrk*|uMU5rk)rBI@:ճr(pRqe͢+)\tfoLH)ijU/'N~ DF.@I#uei& za 5EQjȼ 86υG%b5:7%(x޽ ؅oUj/_,kkk;/~g؀Pɷ /,)۶!JUs@p0 ~>ƉY⮆“A>>>RP(d7;|:{]'drԱ$Ju}}3,ϙ}}8Ʋn(ݻwR٤G.XA!dRMFY@WVkBp;~yD ~?6Nȫęa\x03<;H-tHj!N"ve---u:|>/4L?q< ü) b.k|$~DvXT5PPUcM W8v0}0V+5$ C;rCM3T5B*J%r(8IryǦs9Cʅ>S#N8K8 p#D6|tUeJX5M3a~7BZEO*c}eE]~2+`p%Bׅa8qxXY,FQ 9]5WJgSUڌ%M̀,q]hڣ§cF.\mmmn;ێIS{$@0*HՔ*Ag8NiEQpOrjk}{s]ŋz)WR04M3ϣ>MҞiHHGGG2 a/zqFD0<;.]2M~\Y{'OڕqĊ z1q]eUNsk<8t [iZ`Eѽ{{ ep9n6cX(,n,W"I$N88EMX&1rßjJTg4Qlyj }td#(b [H߿[b H2FIX*rYG&o[JedP4&Lh]rX܊Heٶ MNCw .\n<,)EqZA &17$@X}`V9L n0۟@7Kq$IMӼ}*oDQsϵmȉ\.w5G>+'\XXS&ya1Od28Osk˨ivf~%#?hYډNdy3xB"NOl O24F&mCkT. ['|:7Aߺuqr,0,W5 O3S H0JZQAL M@uX,;O s)"Bn5mYj`1ݻ^ՄJ3)\jT_;F6h8rK"2e }#L@ymRdu(uݻ7WVW N5(TÛø ' 3CGhp S=Fr T{wf3&PHL&~z,k`4av:Buh #)A2"+Pp&;\_ʮo$DQ>iE>bA_<_Q%A @&aSQrd0b&-q"QE{{{Riii (Ml4K6l$th$ P L.,938(RN-/^'ki3B!gsǥR 8bq 6lx55QOd.RGGGIT5X#;rj8h@*ř~N((QxI̅dZ^s*۷ozb" X13_|"m&̌AoBloon[6Z[VkR(>|BPp"H@=cPӏ\z0Ó+Q+b@ʙ&*)c'x;ЈʬP5MlxO#ǣ3'IH,," \HV +?1D 8! m dLd+8پ{612v3K" MLܨ !&de#a)V' LTUղ,|K!wlu l(fʋ5X!8@Zvvv$ ޥ7G(asjz:x)Po[M!Qp ˲Pc*:YdJޏ&EIdLVЬZ9mG; # רT*@eB֊y[/) NZ4Uh=aAܿw/N8>@SW*FYRJB/~)G/Q}w qxw=T&M4;8O+<O&x4N&r"[.DN ul\7zѭp͔+"N{.TU-J tM[^Zzghm۟'766@E՜Bk05t|I]ף(u412(*@q%9@$Na"6l=s8 P(\p61"|.p ei1BJ Q0ce1ht||<ͩO~jssx|ƍ{WW#?AAׯs9]\.WJtK/YC\x'\YYYYY,u+mT eم菠)8># P&aɌ!t&/I?ifZ<)B}+kr ><[/N 20~_fkv0SdPY.158Fl&T@7 hjIRum|W^"/NOa= CUQ\gAC”8H繮5yUjFZm;,@^@$d",xw @.m"e@@G]wl-w܅  R/.TwooL~2(R*\1/ C<#ܺu ˗q ( EQ Pĉa.Bx2&+`0rG> a8`-`AEo;PISd=͛YTÃ,)mKyTy6P,--5JBBy$~Bȣdl;@ 13; J'45 +٧t:EѵkS93WLJkM4ܔ8sJ%3J\E1bјL&aC`oo.\x'[Ƣ1;007-{tE=anqI$Nn8JX4.-qG ,cxe@@v?L>!cJR/Q ~[oBD^6fpƍՕa빃7n!4UZ<uA9r]ҥK+++DL&^<w_zgF4߻woooګh˹sh46 駟nv۶vkfKRXhA5VX9DzpKZ T ,zx*a AYis4Ԡ^V qh.ȧ[b%5muNs…r$ 4m 2X(CgDU X1M``+zF20!kǒ/$$](sxf5cێm[$ǏP 4!&:6o5M;>>~!dH)`*;H܋ڛW|b?SǁD8I~e#YDQ^}ULɦyYTI\T1j@ ]JHzέzڞNtϏ5|Χ6_rT* Cp$uǣhwHʙb!4[n ˗.UuT.Q FUQl><:JD(BUrS6F#tG5n?q]۶N!!fsΕJ$I^z%EjR.@cL XNUӴzYTx`9M̺p4g,~lT)K%PCQO 88'xڵkԗsb"bVd ? #p 2яbV]~42W^=KYO~d4)0ɂzV[^^ I E#cM'4LaSEYd@@Y( ]aW&Aj#9 *:6lr u уb纖FqfPdz87:>ݽ{]!.EPxդGmY$^ԧ>- ,!d )?d(rAZ^܎]JH9eFd E#@!%Fukgꙥ1Fߒ^t:F}w3dra~fVcjp]}~LCa A$^XXi# Cdb7 A(॥%UU& oݺzi}j5yO<>ZƝ;woQ"יG*p?;^wm,+++>,JbJTcZKV.} @L\#+IhdYeuqEN9=#e<^Kdx~]\\\\\jđ_e ~bH6YZ&rMN;PD"a2OwZ|rH?>˗/A2b(˲j8 PTBQLFcuu_?::>WBro=YdEdŏL.qd4M3}rj( @.f2VBuu @u"h ϱifQ>3k~޽{,cXGDbZh4,V8f$kH((\)PTG9=:t9Xn8 !aµ4N6D"8[+lMFg4wvv<,I.$%T*V D8.5Rfj|t iv]T¹\gqxGwڂjOaY֝;w,ޯ>Zes <+[]]|r٬j # Zm-#OXP5<Xa5-$Ii@MӄFfaM,ׁ=8pk^3[VӁ]V& `YVEèTW*%e2)d[S| uss9]B!EQŲ6<_VVd2pp8|WիW/-m)/Q "'S* /|gww+\;w>c:#",Ȁ,!"t*c>uFd%$sBM(1N߾دzԩ#af.7tqnؙAz!as6;Je IAb G\iG~/ѨV]pŋ$ً4GI E +2ekny3rJ%'V<Lt2E_\h41;Nwwwe$I0Ihjv  aj[ {/P2/C~)0h4Y R@t_~e\^7J<l=Q4b0Z/,,,t: 3V $U]̱e?9>OɍDӴK.J% '3du"W_eTZYY\x<@0"4-V'DÈKt5,"9Ӝ<47u&$*"<&de`YlBk6E)h`Ғ5m]&>I~eeX4%\>EɄEQ\\XX(˝Ngyy.uL< ($= r%d6>+++aTIH}s1K},Ȁy`s@C>Ga8Q\i6W^]XXC],nCT# ũC-|.?^,xiشz.I-B!N3bC.T*\J !X2DO[ B 5@l.T3RYN'(J"h`k_ ZLӜLƾ*?l)7p|ygIm7?WU !Ĺs玏|M\FR`,(r]73 ,Ȁ,[nWUb0@RU|/E rm[vHbB\a޲},W*P*7q⴪9MhV@ N2$(ixgStPpK;Jpjsq1sINZ^W/b % 'apTdooo8mYAHG1) dH>hP}>nZ<ꪬ aLa1, !iNc"NPwYaL`a~WV8ȝp, ĶVVPFeZld1f2@G=j&"A eil@A `az~W\VE,v}ǀBQ(ժa`^i\jj f 0 MsMdEE?zq| MƇ?aEحZr %!W(nMI COhڟMR&Nۼ3c'H$ ::Tuxii STdBv:V.8, ZW6"|rbΛM̤pAoG[  5Pu1KE8r1,S9~@YV[0}deVlFG'V^=/ sYo%utZ,5MFBIy”;#k~$;78?"oH?=ټm,x<, ~Ydţ /Ege7PL)r'H*X>X%s?#N+pPT*Ft3 y:HvHJl|  `/N˲LB48&j 5pLJxP\@A`f$cZQWV" lիW]v4",Ȁ,ΎگC\ufO|ⓟa/=`[^nlm/O?DPzMvSLtLișvX7jfǁ :Nth_,ז)> i Qlcؒ)$Ҳ,J !&DA̬ju,/n;0GqVX,;UUUu%˩U D_hI$䫊WYKNpStqL11S#OĶm00fGt:}v汗E?~ܹsΝ;׮]g?n9Z+@IJYIU騙)R%Dd+9wOr4x2*n ZiYG@ӁbL8N&@{GBJ/ch4 `:5GaZ+yxmIGd y^|!˲,r'pNG ܿ]#Ť?Ŧ X 7X5 ϝ;GA!9_nIfEE?@a`o /6rZoY,'{уup$=MEzcSVLj?޲4,pɐ5SsʢJPBA^ŸnwP/NzB@8 n$BQζ®xөjW<7BYcsfʕ/5a 08 +3Emf<[{2!EE*OpW 4ZZ]R+]1LZ!rRSDI̘Y{9g_;Mв,]zRy{y>qUUs|?Nzq%i27 !8})*mw-ZP,`T*VNGnZ9>ō ٢PX0pq@#z+fX @&c'50 ʈ j 7_뗷Zv>e,"gi)Wo۶/H0{nAYKǘ9rSH_p~3IBDRfue=9 rȨ0N| mۍFCU5Ps^̘H? ʧ2"v BDǯD8륒QTa7 )oԌ9Pe0fN<V l'p17M& $BJ_z Fp M-ϓyCgd8\NT*AﳿSOZ-t,XSSdփ,O#N_Yq`r8/.t:̽J]dP^ $ 3r }D1$SI؅B6EYd@@Y<6²b LR>BvwwKRX[P(izyLr.H0)N R|!_OOgJ.QK`%ԇ@Bvl۫VH_mNȢ,N](91Ns\Ղ"T #mT*5Z,I Km"ąF~F#ug [n(ʕb)(rhTׁ0ƐJAP*&ɷm*d233,FB|_/=B|_!/$]Y)t gְVcڟ@5MeIWyC ZAg ڶ`Gp0QMd, e˭ZqI+ jZׁG r\eX;aqpv",2 ,<[V}gJk#PXۛ9 P/ƶ!<3 N6R$f%~~V-A ~\L1`@HƔąeqLӄ10q2+.-}^\X,˭V 8`$I*`<jekWYdYdƝ;wP+!cXu}4ٶ 0eDSq$"`~)+;N^uARt'2@w] MP %MӠ\*f$IJTXf|W<}<߫TJRLZfl5M]\\\^^^XXX^^) @:UQ>R@|9a')pXup8t]wyy9c(N&f$K< '!'F#7ZzdfY\hAnEF… \8 u!\׵,k0F#˲8xbpPrܝ1Ifdbp+PWl9/`lB9@ز7|󋋋3dIBzH*YVr}m` ϏFO 'b~#^CX>j0I՜NG?q ðMjRi=*YdEdcGGGeQzz꩝x q(D:Z|Bو>$'I`pppp2#ۥV e`DBdխb4AF&Rg _Pq>5 Btm>q"/2 (q} CLPl" #/0m{2x8N E.(P|Zg4bnJ%M&c*WrNVEmY8z(7MN"M$I"O@DUեjEf4FAB PY`T:LXWr8-trpOUSPb%S6N'fe;|aZi;q)yBnX88x,"qw6 *p`,reeZ5@Lju,GjZV*bζ ch* cV }?Ubf yՋŢy۶"mwSZ:8bz͡$`[ =w:yX $|.Pf`! WΝ;8~Je:tjn[\dEEx饗^z饟ԻJ’zil@)pP1?//-r$ C/H.hr\vff>'+ ˮ;t8 Mȉ`OJzʕK.I0 F?b`qRjfRQjl0l6v `0nw0L&O18믿ܹEݭjr9Iq^} "T@^D0ujA(-X,u,Vi>VBEQ] à\Z-ÀK_TB}+lSydŗJ%O V_AǼ ipeYV^|ȍ&,""XhO#q}YZ9Lbdbd$L ȜLUx+@ꀜ& +wX dCaFק\(B,,tVVVPc v.DEh ṉ0ɞI0:!l<;::,'qo=`: E(DUe[{%… O&P'cH {{tt Rl56(+ ˜aHNIJ,ekL _٭EYd@@YdӋ|> b kfD#&?'kl< ȖhꫯYHd%4@]l__#I: JliФ Mr*DuCw%z{^~ef[Yd~<tg}v:q\Ֆ:bu/X1&(ʍ758yM 2^xeɍҽ^t8NRh<4jel!s^8Z`Y 13F maak@ -,Ȁ,q__uhSdN -@‡s7kڕ+W CXi(Œ2r'q`H1a N!uVk63(!d@@"qA"&Ƀvvvn߾y^veE?A:_WҥKX = #PZ%$\.7Ml1U }Fo߲,갂/ $xWnȳNKt}ntjYV$B^sIfƗ"m}3qO 9˲o~][[K M",""R$Ib&Pӈ)˗// Q˥ҵk76bQ复|C| >e?/AdRkZz/T"plA|m+(cS8<<|{߿,7ܹs]xѶm46 hByyA`V )W:<mdkp_cc0f xX4):@V"Q( ĉZWYdEdE?7QЬ^ 守!rG$7Q7 FΝtR^O3 H rh? ݐQrѨVjhT*89) #>Jy*ZEqx}V6 EYݻW(._lf[!ؔ2 (5sag[ `m9@j,f)X^:x10FEH rRD[Tsp$2A6!tr E[B~vYdYd6__oF%i1EfÉt3?׾rZ… Z(u]7 C2G!@]קi'|8K8ׄIB!l!$j$D;IBB~MnEowQ*|^6eyɾ{$Y(b$D_[$,Yd>L<>>nەJ32f8>ș(駟><DgNa-AhrSr&< m-VX__<_(BUT]ǘC[ !Xm !$dYP#z_$#up֛|O"Ԅ_s?`o-]B?<ϋ' ",~qpp_Ւ$Ӓ<I#5xɝy4@u͜7~C w Ñ22 01SVHC =) tk ,"f߽{:žxbT*;{@ZT&Nsn%bN&\3Ov3,;(ٳjn4T^iYp8?踞kjBW5-t8x@7{:UN'u _6&oL&ё(j34erYr=w3sGt:0~`vm7Ƕ'$RcW_U.^|_rh:RI<|(άDdE]v|zYJ#}Bhx<FH@@/f-v; yxmT@,k8Nilb#8_x >?Ō,CP.áax1c۶۝jPx̎X qO~Y.8,^w}ϟ}H(BAhdB\Vբax{4gWV;Ձ}T<^pVabJP̀,1]ѱA2:LP} ! À> r4M0JRZ'- F ;2M4M󼵵g (+Fﺮmkkk|N$ s w`8>.2EA-α3;w_;mQ,z]4}q^ BFCiD% Um(}D#7صkk|Yd@@YdOǡtXBLSh,uh@A.Ng8N&b$I\/J^t:^xq}}]'ۑ kkk/^\\\ 3'Phh4{t:._tx^ y透ضM,ҷPP$Q 8BaTD~>8'[b/qeɀ,q 8d2 q^o6ZjL8={;H1HD RO$ܹsL#EdAkPh ( Pf>)ttt(7",~`8o,,,$IΝ;W,1d !Pc_(Yk8&  @4401@^c7 0 q8 @ :2&'ry$yj>ϔJ$I4]STEU8"`0UjO\dO,9+R.4{UUښtN<{4W(%!~_ǷtNy!_K?Y^1$B䇍͛R V(,Z BU?~hq%IpV]!p?HFd}ﺮ۶mY`08::L&mxA !+|Źm|~:)XL25 .t`)?UUrRT*zL MqAI\!9o#EYdŻiNoo6(ZZZrh8UZ:raOyZP\aԩŪ^*2(GÄrP(`v02*J`LHHTEUEU <"q]=^'=h?w (P}YQ~IK%Q* !&III_◒AQ7Ir+wxS+$JÛ̽BA^*ħQ5!>$d=&^|+I>7{$ċ$ߢq Zm~^ t|-I,'mxu*Cƍ_t7NB?>V]u(5E2IqϹ[}(IqaO0I${͖Ydﻮv~2嵵NS*@-yHܺwDz,4r\30i* eQh!mlgXjZ j?$$jS /3MAP,"6\5MS4`޴} h | vl4);P,+ |> 0. yD #N 7sm ObA$Տ}cD̎S ? ^'X,?t^9>h۷=j:Cs9c.@_k0!j Q$&Q*|~E!{!j⑭ڂ<(w!U}*+__SOENE$rԋ+(DŽBaZ(}_X(E> Y:'7)(Q$F#Q.Y,^yD(V>+Ϝ.SnB 0D͏tt$ůFMyP;]O!*'t/Iky_S($_ d2Yds[[[wEqpvMl4.\t:zVU*2{FyeY4"( ucR=YHL @jj5F JhzdyiPwvvKdEY__<EUUx<ƪ>5b>ie$sadC (˥R <| +G9~"cw$ #⼿GwO |3"%9Ӻz=,gɌ ̣'qYyo"{] /޿ "C67Aq0xQqIqf0efH.z=dZm){,#b3'Y95rz.+JZj0Q@} *֞繮8ޞmo֭[",⇨"d8+; 5i<,+D -4)<ٓjxMEq]SiB q㨏h~r -#o!TEUEU(fFz~y^" KzxYܺ%el|.ƯZg~YZ\|TANТ׺c+sdYd~hv1i(MƸ&-!58%-%^I2'S%R!b3He2)A r9h4* @XD1RtB$ocޔEYds;yG]*,kېD).#(Vs>Wr4e|TǮE(8*̑h @0?nO'sEiXӴ[n%Um.wϛv_ꪨO~^J I H{k<|_XJq~Xm>&Vl\>hΎXXmOB+pXR^c" Gɞ~ouK y/}mcwwϲWQxְc¶8HU!5)_h6Ž{'gu@ԽuqF.w٬ t˅¿LNt2 ,x #BIخ'Gɲ2*#}T*9#˹\0 JE0%Dm'*EB>&B\F%Srk 9Ohl[ m\jjIE0nHdch= 4Mscccss3 DQbH͛7M|6E9;|,&qݟ<+ZMT~9|ڤ_^{Qy?wE.'Eˉ7ݵk3ϤI:?͜j0G]AӾx. 䟛{뽞EH깜vNG)E>/:wxFCtD!r?jk4)ݛ orYi4D)S ~Y|?uK0Dt(g/v|_]u{~/ϣDѷW"h6a(q(_-,s9ݚ&]<Yd6&swwIPBOFG,DDV$ ,JZ"Kv|pZ]z?\ M(>nZq^tZ7,bPԇ P``Y,^174>O_r(PES8Ùmi{{{w}WS$?{:hk+Ũݫw?W"eeuUln*I;;|qyƲEW^\{%|Gz]LuEDoCs{ޟܺ%VWErRűm t]jJ%MQ,xJ%}.d"mKc)O.KiKfLbuUN ߤ#Ua8:Vb{_!U&eqQj_V0oP hZMBUE'w?vX2d2IOEY<^yh4j4asG-}T#5șAOOdA1`IԡMrq0H2h;qEBlmme(@YdO<Xqu7;)r9l emD}PQD+Nl8<Rwh^+ZM PG   A(ម{͍PrJJ?f"&֔FCWxQ$lD̳fS()&sSŢд:] =>/d di,Ki45|_(EzrQ$DTνɏy/ ER'‚Ta2MFǻiHE2 ,'߿3ϰ|=#ב>L'!'Z:nBx吟\2_f$I)],A%55؉cNY4GXf'YdE nMӄ@ X]Rl޻wu=H BJ3 P\@uO\]#5r>~l k6kkkw<')odw8888>>vZөj@p"6BLS(B 4Բ,uwww~~oc<4 D'=ИͽXJt:Tz\4Q,οCYU)UUrsr^xBU+ !߃iB>/e}bZG#.uesS;*gűw$O? P*˝|#򉹃7 IH1=2D40|իQQu]p dc"a.@@JquѽgƦ= {G;x+ 5#)d8am*)UM`t:,4M]ד$F~/Nk}YdEﯯfF^MsqeՊ6#UF1KKW^ؼrVSU… V X8-d8BΠT*a/îbd3PqƋ/ۖsm=b:ZM GE?qV?3ߝGCVu/ٶCi)?|NZ3\*}Qyya?~byYT?YXTƆ󆏪ϓH$xKtv?46t"Ly ":O~dWV)yM!%|q@Q(W(%[L,N 2F@Yd؆i7olۛ|zpѴM=QˌEsYl~i0/e?W|=@tǟMs'' @4 ٳdu}yr<\*P(C%;ZdadHrV`[-٢6GQ"@1BU{oΙg>edP 쏌{3O)Y[|BnG$HSm`?jek!4e6g?>wonrIN;ZB?:xwGiwVy_[K1!췸.2f."w?(J>`Tby.*0DijjA$I=w>OluݷF̙ r-4N&baZ tbx ?ApY˲&ښmۛ[[[XkF?QIX, $IͦyԭWzy8_4Қ_qFܜZ)d2v_z pC!f\3rxj z~0MbM9p8ϝ;OFQ|oˍ7vwYT}^&]nJ~5IN;"  m?5WɿTA{6kXEy'af1%Ηw?_JˬcɴiSa~SϽc 8~u36660.]jǡOH6I(DuiB2 Y"x< vww{^\Vh& s~e3rRS4MXu]I<2lffl>6"d9" x }^Hn S 3!I~U@^tT3߿wnݻ }+W0 c1櫫7cwil]O&mk3*x l ? ;dJdOL9'twS6,N_Ϯl*"@AAC~x˗e?ٳǗK24,P@S!G뺘5Ȥف`F@amooO&jJ,KTK}Y5/PAAAg/SkazyK5xA 0tݘB!Ũ5*4MǁMԦ4`sܦ;`` &Z__} V(L6/Xz2翛edTV laU*o&ɺDglxNKδ/umϿ\/~G?b"_X9'¯wxW*X^*,ދ{ss| 'hڷ>R">r@27Ʀ߷m~666:?Kj̛fNl61f64I :[9>bld!t;NgeeŜqv)^Ƌ/~Ƿ((((|#{?l6 ðmP(`}°v0;"? _uL; |'>q3h6 \qc9it]7&F׮]{oxt?9}˹_}1̲R `9/YfOWϺ10]+_2Y`ml˓wS]|Os??fg竏=6b~7 &^*F=ɯǮ]c1e2TX8 E(((|a|mmR8CHIy« F EF@zQ6qy{kr)"/ I0 ?;L&" $IZlh9Fs!2Ν;vrV<}0t:~u>k@z c0Cif3;gJ$IС?A|mT*lN!p~=Ms[PNUo:3=M{!I 'H,V,rםC< [Zb0>?p]’% ik6eqckxn,n˗}ϧO??瞯 Vɻ{n%yxfw9_\dkk^3}*"@AA=>?|e'j@Ya뺠 P6N "5lL&{{4-Txk5UAAA.JUJ0F~_zzuB.cl<^; l YXX`F#".ٔ$ /x0L&ɅMwJeӾj]HGL?9+I>}o6j~q68չddEu$, Y1`./Nן ܟ3MfoM }vcWaJ6]wxoZ*5>u71 5x<G?Jȑ#BAuL; ݡ@0M~AoYVǓ8vNs\.zG>R41iW\t钺: wSachi c̶mEh_O&vVEHPÇ/// xUF܌n 2 !^08t]c=of!(uw@Ӿwf̍;Z.?}>r-?cc_M ̓A20g w̶Yޫ]($_Q4- 0v5~78ZouK0` oY'(oGe'x說 νkkFM8"!*"@AAAa0<ϟuѣ<lV'Kg*㓤z2s'(h4ݍX,zMrBl7?BW^y(h2˲4MK4MqAv]^O&/^~mM?@hHل#s@U3\.10c!2?Zަ>9J?d+xQ-8fr-ߍd?9q+!;꽾B: k4~d%O{4=A `2clwwڵkZ{Ē*20 B$)1!y5cX@, `L&B7uE6lmm=̳>]o4|Gk4b_x7da{vM^0ԆF,L1<{W$q]4 ˴t]gia'B]׆х $f}s?dD-rKӴ/L&W4=ٚvZNJxM*yQB9MZ__c1MӜL&(C 9? `'g8" ,#υ/vޞ w'~SN-..bv<,˲,K'be4MM\__}4eh4c(bmllDQ<>0JԶ^ /|u}3W*+Ͽ~}asD4eu\N--+kڅS 58gۿGMï ;6v}w~Kܦ1rx%Ιa+o?aم mi*"@AAAa8~9q[$psǙ0qh4M;p8D- C(MSei4MӫW•ZAAAAo.^I@EeYh_β5<R>h @!xi&8LhGamɚ; x]vKMcN?-xs8|~w׿MfY|uiߌ MWef7-goM+eLޥ-`^<Bw}Mes_oM6N+sQM;;4ݘ';!93Ǣs;3 0d:cw~ޯ{I׿;[GuV\sx?~3O QD»^uX*BQՇj2eDvak;;;xWh4 x!: 8V~ ^{R,,a4x QT`0v4q1('}m!CfGx:8{,~8p@Vu}aaaaab8ĜW^ɲ,7nųgllΜ2 !OY*0tQ`goo4pzf8.i:bC#iZV+˓Y ^T_u4Ph"3b\;&'{2?p@Z<>hm^(05|}])LӘiB `9a9_ZcLיR x݀amݼFYƒauN < ǝ˗Dȃ 1%w5m``9ΔJPyof9igםxVJB;p>+y<tiھMӛ}Qi4[{u^.,{+wI\tIӴ4M!EGAAAAN㏻{}%I!1u]`2P@ikhnaia۶iaaAA0J@!G!ϋH foG[$mPs23ls?ѡCk/1>0IﰕfYoO'?-m'y޿{Ilv>NUPPP4M|M9DSA'u4m4%IA׮]c,Kr ?,1qj;4';Ϩ6!Wt}9]*f7,ܶp|x^KwS"Q0Mg<_!㘩/w>O$>ϝg7`z1QAA ?+aN툀 s.oɟWU*|i/,("@A "Y?x?j4>UoJavsW{62[_grĝ">t4V*߽v=+?zj+Buq_嗧:Xyo#8g 8{=10dIҔ 14  VcVY PPPPPPPPPPPPq*^(zae{f-9s)XE(((X\\FH aiih$I]ՙQ0s&+y$aI² 8g>1M^Q PPPkv!4,[YYۿ`ΌZ isyW^?6WWVͦ8O^ZZu4$Iٳg/]{ u>tcS cLbLe PPPZnll| _ Rt…,._N(gΜY__w]qMcBx/={ƍwF\h?O8x\.۶m뺮GQK|ׯ 'T毠yO|8M\ZZVKKKzo~ﲼS(\B0Pu}2 CuCR|ee\.JR(4MK$I,666~g}wB}رcǏ/ˎ㸮9ϲ,20t]/JI,..>|gy._"~JT*Frh4|GiR<{8{ajn4=~aNzn6jPGqϸf֍7^}(zd :tGi6ZVU*l۶,0 !DQah<~ҥ~oXU.}ߟL&i2ƄgY&BDQij޻z7եQPPPPD9 >ij|Gm//>Ν;wQ5u0 04M]5MSӴ,ˢ(ʲu,NeA0 |B(L)\E $ՠJ)P8y`0wOY✫XSAA]ÇAj624b8ybѲ,۶I1eQ>麮3з%x=˿˫Wһɓ'Z @ "3X0VU* hZ8pAǯ3u^EѣGyx!~;AvX PPPP~xyyyee~2iUERiyyyqq^ !]mxkJ{gY8JM^x_|l6ͅ!GI~m2Q@躎u]9ǎ}=MӼ{N:v𫠠zz^*1MY)IƲOZPnih\~5By8cիW;{{T*4XMӄ rؾ)rr0^y .:thuu(_g|(mJO6(:44Z۶Ɓlz9Qrӟiiw{ M w-ޟoۖeAO":=XuaTx=YhPCeY˗':e8_,XLA X\[[[^^z^Bb1۶:T.F~/ommCAAA Ʌ0Μ9S.9R.Mӄb)۶p86E٭}7ݪfc (12@ "Ja@Zax^w J|3v{<m؋/"onnokkkO@TU%HԱ `a &g#s mMuqo&IZ@5 #i1DX, !@[ia< 5`x`޸~h4zG,߷DZiR) SN> jr˲VVW۩T*MA{[X__衇Zl5^#gݴ,BEIˎriZzP/JXӧ1@I۶=8jZ^}n.6Muހ<4rRkg:u l_zhVAAAA ?T*B``1}n{СFQW9Y挩7g_q.ǯlfC NcժmYO?o8O"XD{Bdv.bu)(( F8΁N8*ʱ èT*/bo[朣-eN=%r͐s؜v rc ? ;vt: CcB4WWW>j*/h4Ο?$I\y l&| Mdk1-Q.eZR:t詧:bggGݨ PPP`ssɓzZڶʈx(fB8ST2QB}x<0ضM U P76jVzª Q(taPxǮ_,:#/Z\׭q?mou;ETj6بj$w]QBim{W.mێ~VWW~7n`SeFGgɓ \׹|ʥKt]L&TPPxk,..{'NT*#I. 0`l=À9+84 C7-S+Bdif3XH /9.4Yl2 rxEp4< lZ5˶4-0e---]pyc^e\[˲%9 eھŋUPPPDgΜ(J)!>Ops<e@Z9} W^0 ٬3Lx<,R4MsLi;¼5MD.`<ctQ*If|;9@4  @b9L&$Me4ȃ bu/t ?hSp1xhzX <˲(zO1M-L[L4M2M~B0\<иtfY؜d &#i/9wJI׸ie 8g &8mSk& l6:OEEq\1 Iq_{`0T7"jlnn:t;/$Hŋ ,<9 NR( 0 FZ<I$/[X.ʤ8& T*׮^~ g>ǻx8>裏> ÀO9;A 4Mq΍ 8a'> @@_(\Ah*h<裵ZVR b:UQĎfPuu:{ի8t0 b e5\dl3 eg#(+P >ZI# Ba:e !gaɔEt3e4-۶a1a\P n0D%+Y }ce\H$MVuĉB PPP{^P4ʚ|9N.Y\.Z-۶$,c۶d2A8vB" ] ˲Ǐ\.C׊Pvc@vʕr?пBaees5̏y7>ڈR3g~3NS)I1Mt]7t]u{ O%;=f3e)/7X^h41@><"an$Xဋ53 KAAA ? PNq2'?YT|߇_4D= Qn1lϲ,2фIeYQԒ` HT*u~2*Qkp;v7|?Z嫠ۇ>p@Xf k!4MLT!_,@˲5sJضvfrr.YM_q#ҹ9S) 2@j\8\p:4t y J^zҥK'N CcB,fd 7\8(UZAAA ?R'NZMӴ/|…ylVvM nT<r]Ƕ,S0fYB۵m;C˶5P8nNWG:[o-_֒F>HBpر$I]NPu8,rСCeYyzwA@2$ neia )s7M)!֟{wsslbkZ-˕J|"ȑW^ygE(ގsD Me3'Іn$)gPp!%xamնvbWP@tM)C(ICdP;B m I\"şB+k4MZ-0Y@&/OAqs=cɝ  躾j"$j3D  4i|J0ƴ,LL˗y ͍MxdccR@[42}BPʙ`15|fYcU'FӴ ^FxKtΜsXAH6kda< .5M0aV{-W^ z^W*-5KIJ I崙n75N/T% C 8J`aEE/#?ޞ7`І :U(J??g&2fh4`gJ55MuMu_J%Y&"i%kիWK^^e4/_,e[!H$cƧ#8$ʦ95$tSO\-J,䱧̀cUZ58eo9R.BLstLpx&%p3̞.=8-IAEL D_[XDW]0FJ —3g,--jR$:c JѕS'O=vZ~{C|3ɷ q+d21M3 é7"t]G^p>0L /˖e\ E$ Rҷ逜_(X1˲uye CYAXDW-HIYv+O,6kbYA _MӖ>OZ_|G?xQџ$"O(fqش*K3")r7hZ6%al7+Az/>,--zׯ{%ZYƷD2Vہ3V D 7Q,?{オZZ @ 0~*1=_l:C ڜ8F4ͨuݘu յZO|f+5n|b<21f=Liʱ)G' e=h4Cfsb ȔT5>5s?__8C ,//Zf !-_]utLFL$"M\( Xp\3 X,b}^<ϟL&sӃtͱKYdHTd1 %7Zr}L#,'?SN[*2?rfj7Mۿ]զ3;d#x1?jLZo4}Zcq0-Rg8$(E<9RNr;xZ ݳ(v$_@qGPh42M4O[(U$!"<- 4ZR~4g}^pWZ=zt}}}ii^U*f.t aA<&lJ R$IhPMC2k0dz`-[dLE8 =˲tOUj<pZI" )zˉIG]igH5F?b28LpE,^o<,~aO2>Z?P< (Li$Z!Pɓ']-˥R ȊYsgeee2L&uǡ@2U9ai I$iif8c)e18ϲT6"4E,>H.9%@ٜ sVr/GWaEh+Li$ I!MK8zeZEe.Y__G|y(W t:;BV;xf\n6vX,bѻӧbsWْ>HUqc"m\{ZΝA|ߧr%`{4pA/6L:ǏZ-ЧqlƁ[V ମFpJYsrNSfLV(YV.oi dm۞yW(p"@g U-/j>ݜPP *$GR,7!P*0n^u=#4L5-յ8'A fe麦A)(brԈ .9j?9$U3g:DUh Eh4,d&▬~\SIJS 1 q,@ax0677: /0{ ˲* #D{766Fh6*l{<8R9c\d SiyExȦ4 (@ڶM.fYV(kp8׀60-9"LLJYO4a1uB$37l}AՕ,csDUM,"j&fq~x0CpA,7Y`ggg4FӧO/,,VU< " MYiZ08~d4xv`iZ^/)m9<7j=13FZT*R s=p7(A@DqPPmB`y 1QUT*aTrz߇,}'"@Q(2B7kzM&^6×ݤP":c0 9Q'l9d+PeelEex?:4@%]1Dy;.G>. M:p@YgåW,MSt]wmmm4]|Yv]:γ>:n.nRj"X*y Vn/..b+\ӄHْ5GJ{1d<qEu"Xac>_zpGe;hšə<[ȍJ!>; iVy4!nFHs$Vr]dAxIAZY ׯommurj87 C_jM[,Z z=TAGҽ)˚V1M2Mc{{E!6KIJ˲ geO*iRq˗/#jj{ܹג4Z >~&D\ƗqfFfYaJ,]cx`RV$X<\V<[>M0K djg(yid3Ph.#?9`KUz,_#?mfsCgU-$zBB? ,~C4^khT.W*JX qn9aEQ4=aeYRJ_xKr x7Bp^o4 ILe+ YVsD)wYVj\VD]hِoIy((|p}}S5HŔ]S 5ɵ&f VTN1 L}]sxfWD48,QDU*U!?G;LG}s\/ ȳy/ea%roӹTt2HȞS&*o6YzɡL„BqTB¢㸯d2AMLAA)>cǎM&uiiryqqn7 嚦a Exd'sRk 8h4 .V5 # C˲0m`nۖ^y뺐кMșozO2A̷% !(ЖRq|:n'dC& r>( F#c!5У$"Rxaig4z=uACI=! ~ ڨuØL&s49fyrJP,I²M&̶,7(ax *"@.䵋[)"@A#R?@n uDpRНSN-E*je_DR]sr>Ipm?h6c͔!?&Rȵ4M CG3xL3@H!șzBtr~%".YJބ{Ge;db//8h'&-2;#)4/...//u9Vr4 L:zә4ommF#8JbѶ, T5^F|Sc*zI<σ~#\2TPJWvrGeSV @_g<"B$PHIaI0h4`W`c ӷ*lEXL@Y<&y8A %-9}5nz"o` $m?H$3&<ʙ;MiӈZ\>Qx  av}qqqmmmuu\.y8IRM@-{MJd2looI6uV+I7n@ 6LpFô0>O3~ajE((|j' f"QʟU\RPL`a^Iv} l ( Yei&2Am;E|y,'ϻ")gRA1nVZ~ C3EQX,\1fTEdp5?c!iZ#U5{Eh4Lȋ3^.eYm#|G0<i޼ĸ.d[*Wf#y;;;ϟG)|QVw~رc㐛MbL yư&S1@&r!LT74Y "y,dA*"yƪl@x܄1vh()c}r{%֘hy񇖊O&)9OY& YiՒ?:rXE{Aڐe#h4B^eY7zp%n: s?|{ J_|9}s?? s<l;*ZlfTy㸐Sy_gY  Vbb8M"`P#5ĝdŅ>ŋ(Vpo?JjO:׹<2 xD/~i|! ; 8J(,:彥]=qeY8-i\rʕWnmm;wnwwW}7>:hۿ[U.>N[hƘ ! ]&==D"{]8' 49Q:Y l(#LE3\@O(6Bml?7}`3!%4PIB}:}p;.-coq9XXlJ(dYt@. XXX#m۴$0vX˲4 kf|!D&d{<L^sٺ3b2jY3uǚFэ79MrGNi0LS2\ExXRIĄ4pol fpeh9;}9&)NZJ%vO-0ǕJŶ,MC/~v\ƉBM˼}Cb>FQ]A /lƙ3g666PX,"~:wҽ{aҩR*pQYd25DgNHRu5 (eg& BTʲ18cR27Rn0*bjI%*?IJʮݽq+ f熯~P M.6N5*6z=4`qEh4-t)N'-"MK4MRbC qZ%3p>?so*|ǎJ$drOM4- YeiF-}sLcH,B(mq rۖa\jȲ4Ih*!9ꑁ+,wKM`v妬`%yB*t@eYtB%C,B K|x< {{{1qI$/,[0^"x2@F7҅3R>aH afk:6tIJR,ā:TTbRY[[u}ii VL+BÇudčTFL>"%B0d̤z!x<ݭb\撝YJ:dgdMQuNR@9?O"E=YFWڶ][_I>M7yη i&Ls(Fa < ˲$M(,i%TPqb3GFK 8NDw44 .!}Q7͵m[BL (iV,ko 2,//:ujyyȺ)Y%_ToX42!u=Ms!vȚ8'<0 Rh4,Zbc@ ;ѩ2H^$ᅵ_*N)ML'Vl-wCOwr-r54e2&f0 iJ>6UwBP(2}ѯG ^ (xɍiԲSgzFkpnRc/r PPĉрPӴʡa^o0Bnyc HL3.1i Y̎JZ Yӑ`Q CPZUE/LR(5*\*R0\`%@!6c }$% @Ql.ό1۶wvv&IZ,GHzµ1M4e&"0lYvTz6PÙIP~ʙ;B~ qpei"lˊ,VUח^}՗^zI}>X\\lۖe9rĉi969.4Mü,K3l DZhf ecF#0Luf8b!m; @E˲dkvF Ly;q$g.ҳ#&I„Sb27Q& Zd@.25, ^ja83EB.:r\,4$Ds .]vH*BBJp;.3x9GqbZE Iض},ϟ}_}pWCӴjZ-Cy777:׸-&I  \s!ET77kZ[ YqRL1h4SBNuYVJao@1-Ĭ;2ޛ uc doo} ~K\ -Lk׮ 'BEHn$5 0KSaLj%ׄQu" ˃ pp$i,kaXJ,C7 1YiZEhHT=~>{իWZm۾{8PVm"0)s&3v.WR+geBYCfD@2۱WH%0CCvO,8Dȇ@m\9PoZf|YZ嬞16L֘/iLuPjzjzIs:E((0Msaaf9Ѱ],0/j4|X^hz &2PǗlLJ*vG 4wVs9`ɲm{8шLx"4#v<cD/$ d/+KFQ X xBa /nJ| -vzmqE!O6Kwa-,,gMI8N0Hg=Kb0cKp8LhNh43 `B,ӵi];t?⧩RJFDs䟔C".˗A(`4ij~% z5y{lCL+ PA[uOT-h4 ~TmonnZ-ᣚ&d@l M/L $%AXz }GQE1ҕ`aal񄺮2':jZ8vF(TFQ8G]]]}WxW^yEYX+|Pp=,..[.*l8 "i04kmg_՛y]N#kpnEܜlܩLp>U 9if;ͪHT'1jz%/\rĞct4D$5>H\K|qM CK,AXGǡe ;tEt$r#<kd2;;;n7y9g& P$ni6$ЬƘ:FHs0TQ PP˾QV( ܀!>L&^1y(FV'wY$4|D(fH5g9DNMtMCI6fLw#>aCe ɶ{9܈{"`Y/ RJ A얇v`Fyr5"fhP4-ׯ_zC?oСC0Շbԩ$ ʂ[jN&+m A\ڢYa~$},fg6keUKfsmm[օ hpbiiimm WPT*zu]Z$MdJLEI&YOlU:IR Ȃs|7Qdž %e5}+2-/ps$ɾр}j4pg2VЉA,"(q' C-]| X'' A +|V BV;z( Y@BhzBz^IT&?, ^I&Y~3 cu<"(-fZ|=(5E((m,q?CB8ϟ?9'O 3jpҮQLRS,s%h9BLCmfŐXd4}&ϕ!U:TА@D4H/ι&|"Sٶy2lO&d2 ȌHl3(BK|o4a:tP(dDGA wIt@;,e $+PeXP4z8(,sV!Wl3>)4M8\2'w\ײmsv͝ۿ}s w!VVV~~ieepy4-)->d2"'93zpE ʽExdDcig_k>x@[TJܠphi4W*0j^cLlW ǶmvH@d2) r=įdcƥAL0 qLӰ``m[*<{衇}pX,}sa3<[&)5;JAgM677?Obj6jP(8ŊDeʊٯd#r Ȅ)cJToZSt]m˶4|7 òm̞,ϭ9>d^uR=NX`U=k4 EddRȆI5KjӠ/-. RP(ámt, &Hi%/ i> |G<5$KZ⠉oN sa@:A@S34Kyi\uu/˜gy"pKR@DZeYzUCvNPKR,ϵ9@9?8R.S;%d2β>fI:H/n/0JRXԡN)NE8q)fRBGݡ\nhtQo*QEY& ;v^z T! r^d)ma^2J'.˂Uy| "tTXA]׃ h';Jxڵ`΂y]8+Rqh(%n&9C\@PUP(,,,' ^$ и(8P*^B|ϋʕ+J#{ Ԇ' bs]WVjۿI<+{/TY-zJ## K.f'Rd*PΪVx1&9QO峁n;+J'[[[IWUy@H R,KŴ[Ѵ<H+M(4TALaP4-[d@9M #k1+|ƈK¿s^L,JM>J(sOٰ,37 Pp1rk-).@g9sv]h "WD<eIxJsP"a?6++sy"ٶE),N +++ȖiQ"d.6t\=8R'5ZlF#̝r+hᇒ;4g ~?a\;ry}}\.#'G >)8pJq[KРV67?(zҥKϟnkZRALG. nDBu++((("@AB\^[[+JOS&7}Ԛ(+`o/ %H$dTEI1ymzk׮~"2uA>m_AsSA聤|r*(F{ccǕ#Jdk Pc750Y d <2I@NT]?pYtI_vDRȖ.ES4SӧOuXOW2g:gav&2_g喢\rɅ99xp@yeYSQW3BBNtZ;пa eahѦ='STZ^^F׮]D&Yʙ̱N Z-j0$Ib w gO ?>Hi2 D/8S,G fk)hQi6f,)\[n)Dc~GMZ:!.e'Y+sI)Á #Fp&'*a6< 9@NO{կ42%$Kvd(_h: 0o e%n۶ @& c M dFF& lO[$tm1& ,  @- +bT:|5#̓ɳrSQHtC{|H;I@y?J)9$˜mbexdY%4tz4MǓDz(z^ٴ, fmRXEB'T*7'par˗~EVZoXT^| J+ǣ0 $A߿` ]Qh^ ÐsACtJkGCMuW^L&c8% @E( n4MMH QZ-JPEFTV2# #iqq&`醮qGTMo*.@ժ\XXjō41(P(ˤq!]'w}sЂC_՛f4H^'1G6d2' KQC$Qea` a+Å0ib|̏?jT*`N(Jhe_n1z>P:w0׺f syA% /q$GjD̹JMv01ȣO&<\*  6Kb C i"!~׮/..nnn8pw)<?yZWYO]o7` uc/<箠IOmStExqu_zA=7-X@>1A*@ #iA}Xl4iv:Rg-8w{{n7M$D(#Edv%Ss!M~Xa*&L&M g{,@G-Dߎ!_z/e>z&"3"w)2A\F!*7- BE!nH2|h\ c+dTYIǖO1Ms\T?=2(K>|0FfV.! Cl* ̨iWGJ~y0*-Y奜Hݼ6J.} 3BE&5NB~by'ds@K4zhYUz䡆p Ī8,hqZ%y(,Cv;^iZ8T*q0J% 8Ӊ$ 2eYeqA@6\<ѣGk*l'DۧP)ǓhzBhFbHZ, 8NEɥ~YQ0^,6 /F8X*^Lj`YlYO/i<ѻ 1 O9ⴠbFQ&Q3äP>2@P}/7RN]\꺮:c\r {$I8t%h"l4fYpP=@)`s ôlieA|$'?|peeٳ/_VVZJѠvZH ɵq6wKr54ql6̱\PX08@_(a8Q%; Cٜ9jz=¥$̮m 012Jl/!'Vt@Sޕb*]HgnH4KS6[xKeX,*Ilmmz=JgsX`i6N&(Z.{BZA}C 4MlE1ZJYriNJnME9 j m 4ށN:EK/?q"/4v=L h4B!0 CmL& J{!iVVzۡcVy7(o'.o rY& ˮ84 01:C/M;Ga LGN!F U/<# aAX DZl@t U J`O$e,fL0LòMK440i 5#<$n7nCvXZZGfY2-uB0<۶e"MSkB7T5g |Qr.G%`oOĕfeDܕSH[b r&)7[Df(P 6O+JDi_ddP&7'4ރӱCCeMt8.ya\~}4qbѮx}XdaKF^bE/4M86M ǢiZ'"-vx}}}}}: gI:7pssMtZ4#u@2gvK[N+++ׯ_/˥RiqqZ_`0P "/>|{ V`0t:ׯ_! p݆1X0jۄRʤBJn||p[ HI- [Ls 11Ps/:"4 ʃe~y|x8wU-kkG)Jzh`4vb1h&24-L@Y-g9;{'ͳ9?*ʾwԞ z,hg[NL2k P6va>0!d,X> ds?d$;{DjX:h9%2Is.D^h8EeygFvB k8L(=mZ-{4 v0x5M482]τNl)hۇ<_eo*.@AAA ƈ{<(vP!\XX8|mP;SVHQvnX8{eL'WS+%D0"@7u]a Z(Q"I1d٫VSę\l*jQ*y Z.T5^#v-a(!tDЎRM5r#F |?ISr:X2**{ٜ9B@yė<0MS)#cX, @2D ќss877m iC\FLYo4Z=F~:dYV\|Ct]Qs\i7Pe,a< @)q,+ƣYVouSΌ(䍠YQ?'̇Q0t:`ݐ?ri|='G{(K {Nds8(36pDQ%I,㜧q<̶ЦAF#2QM%Fʍ 8!^w<m:VNFcgTO7 8>z(z/L ,Y,K;uCc"Ӆ &D33$E.eY?>˲j* /}ɲZocumuqq"b<>Mx ,JTJ}4jlF#D!z}WV^i )LN6~dmjRd0'RzqI֬.ne9 {:yΤҊLaP]Z%)9k4ENʾ@ ܔl}?1IASPpc3Kpb4䪔6 dbbjobcI'ItIbVd  YKD$D-18cdfв-۶BX,JZ<#ͻxO<9\C# y\jMJ㺮sM3t#RƘ.t8cL$eL6]'A6ufubf@olJ9q Z7$K3&nsh%=3T*axEjӼL04#; BnIj&}}/{9$oLއ`g!2m?lJD,3I^l&*n>IUYFSD[N4S]Ǟs@& Yx%:h姫'xXlqٶE۲tà0SŸtOdJΌIZ-$NҬ3e; C]x <٬[M?t)*(((("@A'g*J۝L&?ϴmŐBP Bzdn, u^eӧrE`4bh4:{p .Y$ 2"eQj5P&JdG 0˦ efkF0٠/Dbd$H߱)u)g3!(ޅ-_qTT'" QK4Qm-G 4el} mr3ʽN~Xc+ىIuIs=y޿B PP aYglۺ_z5 fuT|y!@*qE%ߟA`|!' EQh,r[{N' v&H{B:| iv$Q?rNNy/bb'X=S@,7u]Oo r@2_˲sMTKLsNg@ZCǘw !APC 89،32krfۅs8׹ƘnۖW!@S}ꩧ\# yrѣo:-\YZ 2{|A|@:sn)#SJ^ b jLJDl,Zc$?6̹&˔kBdilβ,#,1:{AL=/7Zj1wq f~1H بT*dH}^X?INE\/E;ri n^ek99?=qvvv}Yߟ|c`ɓ'?7^WAAAA w 49nFǞ筮~s@CT+Ivw á8硏sFA~LZEc)pd]tI}c)*'c0@˲ CGwƍe˲K4[DfLTF(J$ۣ /#\ȜI5R="m(35J1;Ĩ9Q<!\?7E~pY@P(pqiCEeQ=J~KW"KZRЀN Ƙ\N&gBM7L#1d2 ٳBӴfYAD&RjFM4IgȉhODqR SP>In9lnX3w(hš,6/Iz,˗?Z^&ҲCS2/ 7&PNP$a$.KRe$`Ku]c@sI=' }ԀNXKKiRi2\v ޽4=AC,32۶חdy25/?YB ,o>#tsCn:yt}nðP(aH;?_+.@AAA =z͏F?bDEPN%d˲@ЎADRסMd(;8;O2O&sLàX$ \߻],K9gpNE۶9fM1FctCQ|d,GϹhIVPIn~E M/ItJ٘`#Mcsf0m5[M ds`̟GybOd3d0ڇ ieT#.&?w *s tt >4]ih;r&ͼp\Nɍ닋` p\qQt`YR]XXJu &/ҝ3V[A8|spH7;s񏯯cBTEэ7vvv& O*CSv(;ދ2Ž(|jxٴ_Ԩ+b4W s?ݧZMDJ^N8hӬseYEtr%eӀzL c i\7z8!CG;F0YE)ƙ^ ]\(EbYlM0#۶$޲u"S?7$ kB` 8c3mMM|ߟLƃWFVA_yƇ/MBd"BpۙsИoA.Dȫ%}yQ򤾑<0q<oꢗVo:Sɚ>t:#G3M& C{sLB :DρwWs:(L:L&8 *1%B5 u8~ea;O-%q<^y>" ĹBիW+JX{X,iZy r8$#NJ-.'&ͪӄ5 l"rVצTu4oLāʕ+ds?S<Vp]#_.W^Y_8śP<#h>zl6_ *hx0E1|:A纮lGl`0@6 ͦ-MFx fۜvX(!nܸ1L:By" 2F3 B2]vGQ$nd"m%+yݻ2ƹB\0!yƋS=&6VZ+& hO8h4qfT!ƺf^oXeeYiqQ10%%VlX'!uZB95 8)sٶ(jTb,+8ĔX#:j6IR@dǙC"-&XS Y1zι$@3 94I8'1c,cԭ'IZ(X (GD_Vqvwwbx<BKu':'4;TC#w]rȥ:DBC78$ ,M{Sgkr C\l7n<˕׆Aөen@Cbq2F( `Ln[-JrMYL6'sl6*;R,.: 6iqvK@sRMl<4sJ j<БtɬYMbq=$%X[#Kn*gn4^Μ^;h42,4EOrdl奐#v]T#R/$%ˑAa z&w\./,,4 Lz>*ʕ+W '0 9i":P9/(J^}tvRl ;Ulp\?IӌS1! peeZ⼁V *G"C+R12^̘7*P,q'iG4M7͛ɢ <”^lXˁӭ!^)OpDj1 %R!GޛȒ]'y{Mnaπ$XOM >IC IS;ԘsfdF=UNdmʲY]]És^{ TT&:F&ET);l{www6MSzH3Y|NTTUٳ.|[(Z>"\.ł 4+8 \:pKhq6& $4M}'kF pQ-j(dڶ. ]ūG[Tכ=grρR6[AZ~0q\Tܹ$ɳgϢ(*J8V8umAprrR<NXb: p0j g"r"{i[ |> D'LLr\+8v\ױmᮐ$iB8I6 cX@SRߓ'OXrQ(F1qmT*7|ѣGF //޿\."L&'''''' GJ k])"\rZe8l(=_-6 ;PY.g $NЇ~]du0.CL2(B@*`ڴfLk#LP 6LٜDn^,IIDx<&/,2+RJP)V(|'orN4p\V`KwTB'8iRH*yzbnV:3p GòWTa`A`Vt:G 9b8;;[,ro45Vm޶vr=MtK+IyA hf )4VK 7km f3`|o7< 9% H$IdV L&ł84 (@l(ڲQ(F1Ɲ;Ǐ?A:<<{0(...^x`>S %D`ZѼ Ҩu#|z?q-ɜ23VYs@("covX YkR!iRdT{ٶ8N%uW*%%&2(eFFqe'|ǖezh4,*j ,40\O@p |^fnIifYR =ynA0ϽXtPJO)JXfJt> 0<d\f"˪)'2%7+3dLWJJNRF;;;N\b-tnl6߿=*j{{{w܁0HK )* A4sN~fl:j)492|x~~)rbPb8PjܓC+ZNS|Fc L&}ire"uR3*QP`rC`Y Gȥ X=U_&DGDڳZ+ٖ5Za6[4ISYWdW* ;KN&4M4y~R(EdfKmB_E'%D怟`` UDiiIXZ ),YRN&QPFnyJ4F? ;Vŷa\Hy4n\3`,۲$IL446im%/rϖe!S4@B&08J%_xux|RyL;BxH4B_QM :Ilxڲ,t\.WA-۶.\"۷$Id8IF^T9a6\өHIGQ/i 2eiŽJS۲qRk2͞&$[Q L8v.l+^_ VZ\(! |^u2`|h^ZF9hHUtRv,lKJph `Ygp= ضJ  ^lj@*a(8:$eYx<ͦP=$$1 ӭ(F>=zT.޽tj5Ǜ 3x\M1-H)3˥[*jZn4ؼ H9#,k<q\.IpYadYI~>#E":QHY֝$j5b ӼdBz,|)uěTs9}lu.#SZ6FMjnj54!aZ2)$IzE:Y.rcj RޜɒʌWzsD$"B<DC\Lm'iiZʈ4Qmdi8?GoZ,rbx</ܖzQ2Ni̟OPtCŞc\#+l\CuYXo?K8Dwj8xGXZ;2%۶]փ=햅6kI_kۖ4yD67)qTP٘ıJSpEZ [)4MRCqt:Hq{XGh^{R>\ǩ+ӧ=yq69d88ܰ4rK>2RRˬXM8뺝nj$ PM\.7tJ<ЎffR{`qdU5N_| %Qzx)Phu]Yyf.-4J\.vVC'p8Q 0 #5MZE d7?44YD>[iUQvP׀8Fb2VlL2*Q`:ZmggLbaf|Or+VHe6*!h:XApv,) #@,1h IFPk۶JS$MMϨm츮?99)Ooփ,ˎbᇘpJv8PʄXe1p$@'152aX,IӤx^ZPĞ>ig|+]EJQoxg u@ DZ$$IR y#eWt*scjIX؜vyqVՎ#W0\+laUR;Z6SE ϚzN]|oz7o |T۶(NhT*z=h\. 7Y#BJ{GzIB c\cA0 /..0u F%I1#%68>"j\[SzmVٳg{6t:"pT~~6?cPb|VeY`n.ZXƇ&zjկEQ )M-_Mu0 A}@dH=o#nCp'MHI: EJ w(۶Q!3L0 (F`/(jmoxӭ._DZx}Gx$Qⷵd4M{z=*MONOGa$QY%yiFQL%$po( OT*U*9q㻲dKiض̪p({XzX6'$.@-)LB%%鍀mIatUJ6m#I0%O`./Y}2aZݻߗFqO&hKfGK$'.hȫ%E]Vh#ZhFxow^^\!r)ҕi6m?t:cY2$\ 3k&>h'sZyBʈ7#@Ͽ߈ q20A#ЫUKV!Yo# >Yh$@VVz0pn[)eٶy?XJ.W*Z=HLd_ Q+!7vD 8sK`0 +6La* K`SC\P(Q(F1>+wl>jrjB_ ߷m`ooV*Z5A"&rD %:ZaR l* 4Ue+HuV6I4K%g:[-(u^;GrEW_W,p;Ke@}#Netuz^.Ħ 4k?#$4B4d }JX)V4(z\EQY?FF4@X,jGJIhg-Ab9նbZп "$k6 hGo#/ |m㱍Tż@qjZ*`B)u>*֮Oh4oagJϥmRxBj Ag$WwMQ6n,8VHM808,Lqj8-rЗYG xݤ {aX~Ӈp8<;;}eDZgVEQRe5D@2 <<+V%a |86M@RpD&68ٝA<,ӸFHhpTםGFI PKϓ'O>):4Nb )*qVGGG`/*劼˲ǮTlt:cf`P.wvv*rR-wy`VCeS%%s%(:3n޽{~~W+"p GI+_d2Wf(F-A[\4hnLB䙌>g#g;::B=5:y;P 3b85kl&tV*9ٖR1lێxZU*#Y#(`aQJ@xr2P` p#AV  ݘTS2;u{*$͡>o&yQ3w.Ko@^?Lb!ğpE8jb$/-]*_9Yg5 xi&o@`&W0@o8g(/I6cPiɦ36lPrdrV;::B''~a|}FV{Aߗȑˋ>i΅K f@5 JHg؂'jŋtztt@[qJ :.xӧLYҥ)E6 ^ެW08dB2AVR7>i7Y_l٬xq2P7D^dt^ X)KM;fRl@!y7?qqH*a:NZǓo> 5;/..JGIM; }جG@+_%%5Rz[rǙNa/`0 (*L%zF_s82Qb@@1Wk cklGLiԷ=u>j!*;*JK ZYhEdyeqMscnWsˬf #2WⳤDO#Z^7X?DN-:ƣ VZ0, ƏF#IZ6MC#- *Q&ȓol! CF3 tcOb8 ٴ^FdkgcPc, j^_AJI9q&db PҌBj8aڮBQE~b$fѣFҨ*FrtJh8/tmN`hV+Jggg\0`<6Ż[UM\T^92|"E14e78.=ֆh4f裏*epJd0|˲ x뭷bM+F1 (NOOpX,JCNeiVwUUV ?Q @7:1~eK%=Հ8 F@)|Rʶ0 gzζvi6T>' 4(usD.x2G4/dfPZhPJ Vݼ,{{{x:0Ib){m'ip4V bzΣr}"H,$ eY9[."v(s4^̻ l63TZTHfJODe[Ȁ?7ړY @!S ۶ 4ĉHǭ&\n\bn4 +~eZѣ^2e f9u>1RhOG\1/K_x.eD)\.///{ШEww2H(85SJa_L&u(^Z?+ok^%> U׉ @PuK4%/j)sE[)Agw~~zd ڰ"VyX9ƒiTꍢ(718a, O所ݶlqZIb m4-x^RŲL > nB +x:_ UI\"$px>n}OϡlّtG^{r6k1Q(gb FP_V˗":m4*kt\ZEdQk%rOugJ&#Zl(C-l6OZza08o6[J)t.w]B&Jeoo dU_j+J;bp^RYIG kS7pC>2R Hj$e(i em>nƧ54XABBgE뺐FT5ZT2K%/i.2n5>m0aqO(Gt@Lr,6MӰ /Vz.KX `$GGGV mU?FӴtܑQX IjԑO Vo X*M|> (v^pNs%a p:om[i֢4۸]{) l%RJ v2{iTDTE,HB$ !5 #lTa ,}rEj9HvyARd(#N6\A(#2|A|cX9*ܒ tgӎV…ryc91E1ah ,0׆.v Z/^Fv=&mہb['v?~dR,n(Fh4~glNS|Z|Q-KR e 4˥ao"cp4"&eJ}Z%52uJ]ߐTO$:Pm4I#ݸ) iiV(s@o RϏq!_%}YcrZ,2JnS=??[RRifX.K$@-OKBfmL$h4 Vd:$ U־!&v`5.//|t:Г[~KR!K7F~ V6 ք %{JP—J*c ,˰ S \%< _ "'aZ 0i]I)8go52M#4Dh)cT w@GKxˆΝ;^Or^kϠv@p6˺Lv6Q(@/{&W4ޝ f Tl$<0 cf%RȮdeiNr)?y34Il%D>lYaGR\0PE@܁{R:}PGV~N ðTrK7L< X,_MHS$i*tUdꌼXxcŮ:)%pHW&*u3Bft paFaQTn ?(jZCXߊQ(F1>߶f3Dr@*q\[(PJukƏض=di@Ծf)W9&pCڎuA-Tel H$^xٳ$r,4/2dPՁu8;=/n}%ZGic\b/:N݁Fyȱ55ˆDx+ћU ӀZ3a[RMmfb0MӴL4SeI$iZ$¬0yEKRiUG8@z ðlJ- +?ء Q.5f slp(ƀDGjgg-$jX4TfB6'qac=C3>?k(@&X毃KqXؤ>Bc\.cu=ed$*g/OqRA6}@2T$&N)j2k~Xj5̇hHJiOJX.1;nJ" }#o@6L& j`aCpRJMbxqjl/|(J*3+: ( ?~ f__[1Q(Ƨs8믳x<ͦ/Jalݻw)#O͡ϟCxawδ&Pt ?`gJzIdy g(!DuVGR4B0&''Tn߹sZʦ\Y@I0D!/5tjaxdn/LefΤu$f}9f8m"r8NzRRshuj CĈL5fRT,qJ,XjZy|כٕMjl^eMp||NA+p4hmj5I L֩/BJ fa8fdx(6/|a>qZvpp]Mu}h Xؿf{YF8 :ˑK \M;;;v$h H)Fd]25^A"#OT){rf96R72Me*I Qtyy <βr\=߇DL6ua@J" T *eYX$Kw Ln%]ȁ"xEJ0/ȃ@l6qFu}Xe; t ,uݯ|+??.hl?_,ttC#aC?z 2ACyHQUkMߨԚj|$Y!i@Z:Z}$cVIĊ )dRVK$ .C LJ'";~fC(߈ghœZv0>CFRnEQ^ku< íxt^7L@޳mYfk>F X%I 1Ď[-䔻Z(nr3ʄ^9$dYa \mYa(#1lNJ$!l*_z(? <<{{ QJEWabH'(>17 3y4m>-VTt:Ku=&dla>":;IH0dk$VWtvcQNM<^֥93:࠻1@<>8 xLHI}>.fp Ea[D@ ,|m .<\۶LA !?L Rp!rgpgp嫲^`08x:-|HJIĊSLSahf Ij \=_u(FQVz뭷z XbRðޜ$%ɗJZ{w} RdnaV74S] ( U۶R:rY!#t}$(GT)@uOQ>͚'S *۶L;h2@[?rB{d8~:\V+˴$ZiZiwu9gr"+ƙlj iߔ˞aj5N CLN7G^;;Q@O NuVpr9(Kzx0EU׷|Z...VeVk@<*VUK l& u aIn|.Q%iFW !fjzoi68?b+F1 Tx~o9rz0LS"US BϕRVF1 !=R3E[SY*TĖj V+ur04 ZZMh53\؈ ׫0L\םL&`tn4M9I='&<:"GV.*B*˽5b[jIDj B^qww4͗'/m05!Aa~hC^[VZfK\Y5IDN咝8 EgLck Bt@lpV20.M 7ʆqFږrl[vk$*`w?FO49SH%v/ wv#f/}XAxF @X[Lx‚*`)b\*g,Xnju]T:I#7۶XMJɡ즑Ln d\S^+6";P/XqrV ^ DC+|5.]-Z.ǣuHY !X#~@Km_{5bc$*yT?}43{˵eYrZ5M@K78rYbʎ}<o~ŢWb@@1S<\-ݣ :뺃ٳgRj\`<#†͘Z۰_.Fl}E)1H/=^:KYxvun:W`^+pJMqDQlvVCPC)E^+rr$I-Ki %z=NIp,!I_`Z@Ag%Il6":b^n w꺑U R$: e.ua^EJaJyR@ֵ<+9:QJ!E/Q9_.tdTY!ô@FDpANκy&l6SaNogҀZT!qfRq ˴8FIrxX,X72Wi(Y ljr6ٶqGelZ~ww}/VC߼~VKEZG]$(ҠT+ÒN S^G?$BdFh4!HẮeۆhV@[Q.+*uCiD@2 ŧ3vY$rh&/iҭTEa2Yhm0yi-?4zFL: 6&Hn mUβIj`DFLygJt@<#-)c^ge\Gt:%@dV+s1@c9CX /`//裏5(blXɓ'y^Cۏi(?c(*VQBMxnQ;>Œ~bcH  zکpFUSb #pR4Gݲ4M@ڬx*nY[4 mm,pnj4JhT/FSX"AL@94+!Ё=L(j4|4`iaHqϟ?/(W(F?547xA4͇}G`XD mI}dt9hIjn-|,e XOJkZۅJLRmXZ*9iL3x2MjV(l #d44LaszJeZ4Ie@+;xEbcNcJ(k O8k_x,/ ّSRɈc#-X0?fӺTAп X?Gn7AR㶠\.7VxZ%6 eR :WqPKRQz^ɤ^l6/uNv6DRّxri>3t1g#7ѲxR?/>Ьި2*J,l6L2,b^iJ[<@+&W~H 8Oc{\ []le#KMiOJ1<hkD$A77k"l lsqns{L{{{V+ CG~\>\vY.gl63A$8aa ( 0MẔH -sc)#1K#Tw!&By*bp}_,P>tܔ'S n FY{%L@,J< ~2nrZSJ95ed̐NSe)4lT*l6rV#u%1o_.'''@B )^(0\,LyX̤UxRp@ g)uΈڔLup蛰m;1$M4 &eZXYyyP:@ء OcM_* ?j*V FRt-YҫLױ+R4dff($VJt6ib(U*0@! 24UK%X- i5y0sb2g% wt:fhE\ M FflQlYL1`J >|/qvnR(9d1@dP r|<-heDAO|鵖4@9Ske9]*l2-3t(]t29 `0 jZ1e'E>Ф\ 40TbFMˣ(u7[jmݚlk% pYˍ)Y?FOA"4AWby9D.IFaF\Gz~xxx|| <{hn ˠ="&F, VK޹L2b;j:_8FϞ};@|a>+!O;ţG~b,F1 $4/VUݻw^C q d /b*ċFdj /oΨNߞn- FS4 ]E 4/ E 2mB]>MjT}*uOd}ijY6P^I ' `.΄A!|#t"Qu]]\eYTG%9krl?c5L \cwi۾CD2^ 2d^zO&06,H8w kZ12:CĜqy0)tI^fԯ dW {%EEQzQw ^h6?s?W*&U8zr*םr~4\.Nr 0<==> .a2h2 lj|hBKV*u^era zҧڙTԒ@y4}oIeonJoI5G.jk;=q$ @Pxey_x[V!,J\Cpn) &Y:\Q<<<<::t:U`I8Qa%QbIGjKS x0Nl6˕ԲMZ-u@ٳl{ݹs7lFJDh2aTz@@1Q('nXuΝݣ#zJu4Md(ҴTO0T&ǖrp^S_M@%;C si> )4 |>)Ry%MEe[;8IJ-ۊ:Q*5 0:D2?N;Ƶ0 @~vITKH@>bqzz: JRTƅz|A0XYzk0|X'Q(F1>);~jRMi~((*۶mYV''' ANOOAVf?<ɛX2Cz,; YMBr=,BC2ƒ|1_Vx01$I'2(:Ui= 26G|>uY6d&G *yE@r,ii-·;'g+tMP )z'duHsD@Y*4NUN"H4 77!Ihpi2I§W9&11FpV-KCXz=PECZYEiR&fbfR +rb?8<Z4=??OtvKM+pU$0/!8o@s///_en Wӄ*rM~DZe;1V1 )eye϶l4@aPgDQAE VV*+3|jzLƝRL|Od, vƳ2Ǧ4%dΜ !I0FY ðV;;N86yK&.B+x 6.OFr7 P>h4Q+9)!bb"t |#% %l$O513hh+LC|# VA<裻wG_7|3Iݻk+狭@# 6 LfZIxN{If>$Ymf F4V`YSZ_ÎXmۥRjUK NanZܒsuR y 3TF" N?%L"TF7F 1I@^ogg('"2Pš1M8IQ^(F:E26]*k1Sw[:z(T*f35@nFC)>UԷdA~$j[4RW⑵Z pU4DtœLk\)7JFx4V0,QMT0J$# _mV@ʲ߶Zث !A\^^*!8`! m;N,#IqTfxEQ??{yHr4ͷ~Pt// w@CExO>Ǐ;Irj]f_<`$eYa*ct\En6R\.'gDKprd*p;(o.ڶvi`v#kڔ r|:Z]n>APT^&:6Cv)'3~Eaf֛vѧ+Ԕ]&`QJD|q|vvvzz ,K`cYГ5N'I^ 4b*ݹs@@1Q('hX)* 6ܪժ㔢L@y&6G}wg8spppqqQ.KJa蛕YޯNJG4siء @#2X,~8N=+9e]#%Zv 7N(64Lcg_d$&[J0xXL& k().ISDƫeIko"q[նh R웰 $8]\[P`>ON&Z]QDe+uaQq(p&[ %c øw bm>h H7V F݀8[yJs881̘9):A؎*W*?99)j\4Ӑz%kŒɿ\., `H0 ///!s,?[krR݃p:q%uM3gB/Ry]/R86L0;;;FCҳj[}OHa燚M-@6uoǠVyO$oGR~y[ bu]6U?j &KolۦpiըNyj*$b28F8S*9veE)|My=7o@}Jizrr2Y;wvvO _L_VXд t29==M6 |?2&!Ex~7~6b믿}' eQ I17ޚH38!eL#/sfR)co9 t%`4]\\y^:mi}&<,s\hȃUd$_o8@D`BSUA`.)i\waԸ9Z ;`+6)M*5ITtzzr6®e%qɩ8<p 8F=V[ 5A &U4IÇ+JIt6VCK"@da0FaϨ%PK2j rY(F;wzVjZ\_)0 ?M؛øHef@G?j4b8:N)OՙiXҍjbȻdc-drzz0C#t2 #B4M%ITJ)H)CP*7q'qE` z tâY߬@֎3FlcZzr[gm *[1j򌁤=sZv Cċ/0;)k2'RO!:/MR46Reٖq5 "y?}݅xN02G^ wҶ0>IEFy@^A !\m>995d 6V70 qS]0 !ҏZi/_@@1Q('cDQtyyIuV-$N31-nkwݒa(H[uxxS=MJ(kZIqd2?LjZ&VdJYmlR$Q*UYyH$VJ%*1Mlqۖgz br/ d' doM!n*^im[:[ȏ RUFc4,*+l).M(uILLqa|4&"{qeVVGHeQ8K뤃!Rh0 HinR"NtGP:)"LjeO-.9a$hBnhU F062W(;;;NuB52-}p L&`<c$U[4N0z~v:Yذ ҹb뺶mYDn^EחĜjh4&N 7 ! on?U JM$5Γ7Ok 7up'*I^Yk"[JBdҍ7rzgPJW*ѓ#r&Vk&2ee 7|>'K>ju~~#z1*X=n!aӱm@=_N̹ dX,&Bu(b3zoof 3'WiY!b pJѶ$IgyaS+IJoi'?x%nN[3Ɨw}@ֶa-:8{1+@_zPlbx2L&;;;z^LJGG0/M_r) 9Enz}vP3+oeG.\Vдzl6A tQelJ).Ke({Ӫo3d~YEyWh4zhFD*8INW7Xy NiH E6t~mv-|[m>uIS+}FV4XbҊ9@RXVff ".sauO-¸ UÙuXL`pyy g:&u=&V2C / 0 @c3qy(b4ǁP*NTm һiLS6\CSH4GFK@Ie,9%3ZDLdxGa! JmQJ%t: -@C\9m[Yf !I+`MYɞd=C~ЂKIT|FކN2\֢|f)H65:N b8z=8,`Ir򶨬GyroȎDJ9/\S 9gʭ݊r|{o8mWh7_ KW9R0lF7 1&7p8{qqqyPR_o|hpŰ,ka2p֩f|vԆ@>8N'+ipVXNrHoAM58(q8|#]?i [8ʕJv`Iö[Nkӄ`5M]K- Ey1\Дǃs9v׷6<@ӸC \T`&1(ѯ2{n3"GV*3#Q^΄ؼлv­ F7dCG7b:Vtz~~3;4>t:no4ఉXyAJ ,`ʅa`bZ QbQOĀ?uYJTZ2L31TrdceI&wܙL& 9Ko3y1QV|x0̻wVd{\ZV5Z-Z($I2p8F4^;yžAW*0c5_Ç嚓Ӗ, b>Xey캗0+>b, \Tqhɔ&@-%:.d94x&X9Q(F1>)?_qU&7:aʴTT2 r/vk0Vh+x<^,*cv i2 oHM&( C=\0`3u-rld2Eoo8&贔 RSH%]d&U)Ya}83?bY[~"[sȜ2FeLϗ >zh4!///G-3S$cI ̦i$kTRqC3# 1Qz-YiC*_g6 ȹugAdžaz=Tq)ZIB_W3>ܹ,B>|b+ 4d^BZ2 BCӧOwvv>Rn,@!h= |AS NM"vHJŋzz]ZxjE|)HeJڴ@1iA=:}{YP<ay>W~hx m>d| sufGsЄt4~B m"7M[~VotfZت0bNQLIN!I3e+c^ njihkFv=;q u<*cTa\h4C1Mć 0Zvxxhd:9==˛凌řk1ޒKFY/^899éw^O|\.]/حe}_$d7IՒ5RZ@N}Q?>>(+<=bXǂ f:H\ |kYiu)(Èj}+_?/mޕ(V,uZi(@R m`e2ņaMj$`!cvD])QTjfV!~* (\fnvN]!,TN\VᴥNۦ쯔1HQOa !~$IlGggg;;;<ۭ=،'Z毮[je<OOOض { Y$|ܓ~>ˤ4޽YbJx+ZF-ER`I$fgϟAْd P>hX ɡ,h;J1M<8 /hqlmVbh4>??_,^s5:( ƔR6t\ךRI!Me[<^S.;|2|2 ã#+"<[>Wdf8;;NH8f24 @fZ>y М)jx\ & (4 d&iEw79(|rXb y.i]郚;0 cyzݻGGGx(B OIt=ټ7zY+E8IYاabйWI M& [rr ۶aL#Ylkg^$vQd40!"W^eyMT*(@=_>'57³6Cٝp3(Њh8q]]@iŒ§\.0uN0M;w, yHHZX#O[(F1Q(ƿ̨_A?U( /0ILӰ0M2a$q+]( b/ršd 4]ggg*9C6YVry6urrdmN8z:H:PCAR RZvIͤ<% Qv@Pl,)- @nF@ БDLZl` z^;|>et:t&ĭH-N VN$^uU|[PVz%~;ST>?f/O^CBKxdhaH1$FpX,*nZ%e(@i4xi|>F믣o2Lu]^k}uN Īf~-/DfWnAJƊeZɣ[θvm0;d^ZJ6WT- ]/2np}#n4I,X(4UՐeV" Jmd/vɮUkJ^rpsaeWIVGD=_y),g@ȇr vn%"h4dAAn%T |?m$^rTʰķJif65"[F܈$Tդ(i0⶘zQb@@1/0믿~||\V!,T*hh-cZ RؕA c,fګ2w{,\/F։ʹ_v6J%*M-FJk+!CsQ>)*ʴS2aJƱ62CˠfE2-&,K9/ p xx.Jj7Z;ݎh^keAZmX frh4`j)0W+n5M"FM  @ ljtV#ph<\= [$F@ h4N~_.9 0X,0 ]B-z AJ195SMTI;RhqVnhx%BNTx<m![jwZ6OaoNނk?6riKvcݓ~0P@py 7ǛV /K:Si• Yd fLTƣd2, VTr&Ei#_Ǯ~l6]E p8D^jwww+.\.)r) q=Iz#Yo{-Kql>\ #Dj<-d4 jϛSKPT2>WnT*@(O< $7_H^odٗf3kж@FKjmMSp JJ)4Pbn\t =hRJ9N dzEt纩RR Pll@8j;@I ɐzµEb*B.AFq}: V+ _ae=仆q:r3MT Xqb{(6U3Ij:G&;Rq`0$MEk@1Q(ƿvv^}!GBaCFc$54M&}u.PceDXh:jZ.AGk+`L׵Vy/VnpVq[h>CH!0Z: ӬvQAIqtT~jJ'ȶO40Y}tkVz%| v!~zzZ.(4!-,&e+_^^Y ;B*iFu}8fญ^VhT*_̗%y6Qm,=4H'|4rf\VkZV aLlvvvvyy$IRvnx#ᳬ}m&#_gN壑Jc%j9gólu%(RJ XQbPD*Uf.P@l>`A -Hu]CM 4#FٌMS)2dΏd(U£D FEH [Xd` %]nRw^Ia^}Rlھ c6<EQ&''' ;y9LJ}>h櫮%HA>$Xڐ!6Rx8,D,$I81ifl~ݽFn&Jys aE,ف}s&~ZieYV2  oPbfqJ_WaYzY/ 6v\Ao(6J$I4I$F3".)V 0#2mb>Cl2oGD/YR<ʛB:9Xl 孓4UckZL̘2T)pӴFb0 f={h4߿ D*oqH3_BA#/tbG {%hoX!Ԋ[58 tTahxi/^ggg9/QZŒw"vuhtrr1Q|74VFa$QZy( 9jn$i#2q|D}6rZs]T‹;0 |??? `tݝn -@ @cY.e q^{ 5! 2)m@kZF,wtXw`-}1dVbqۊ "M&J( ,0zdEqҭ< YIЄH.jXn<1xYMDʿIG=, ٿ rBcEUN{n[@0۟ 8 0KR.&u{۶ql6$! ?>(e1gq!ۂ:G.eC3!ڊxAhNb1Ng-JU]5IJt@m< Lyi,KN* aqL_\.fo}[SQ(F1^;ѼRBnZR#@v4U8 C%:QB  *Ϯ*k W)+]3QWi|[ȫ`Lス=!9UyLMT#jC"7HNNO߻_V]zcڣ'Z gE1SJ7"Fsz5ȇ򤉏3nRQJY?D }޽z}zz:8`/ ,I,h>==ElZU$IS*ZH, L30i9Z&i³ <$3J4|>Lb"PZ^*J.p0/\=88ف6>83 'm >P%zȕK*"rAO|)`><{l2ܻw(27xl rc;pcI$=+S7j@@z}h7S6b֊%:x[ WKiBS.nMoRUDXPf}lSn@ #G); JNO|IvL9jmpz@_hpcOXC!@#`ߔ%OL&& I(JhZAlUhd30RAr988{.VW7TBߎRt)V ZNk_X`ݽ{ jPbQafa'R+NѨjMB$IM/4Pjs(C0\[^f:JO5D!$㔸3 Peq!}kV}Zp!R+[ifuR+25Ml6Kt{]*|^Ea DV9T{3cAL}4⿺Y D0HfImuEj53c1Ievy>RZ޻wiڎ C]/cr*!dIxF>+Ÿ4MXqu"x<^i5 '#W`!0˥ya߯jg+ ZJQK.˰.,k>Dewz擲NJzT2AViOGj6bhZՎ QlۂmJt J.PDTA62-d:)@pXg[f&t:E?_p윹da,ٳg0!d˔&ttK"0t Df&@I ^5. wض=' q!d2L&õF?<|Y A\QR`コ.,BʵAn _wC|ju~~^޽SmqQ3*\ nʍ=5>3[de3ʣ[lp<T*{{{&'b2:^)xl6ݽf\.'9NmWSI0Cd58SyPTGFܾ`-}VK,CO4MJ({3(u) 'P K~VnX`dF:kȶ͔RaI T$MDQt6`2RD#|4/lׇmNuGSF!AL񂰣/B[ o/ QυJ;.a#xp>r6&jö'iʞ2oZxid7 #M 83k5/CƑTj5HdkSr&ijeO W$pڳ逦*P$fI 4MѸZ....//' QeYf:z!K[ERT`{,yV|߯T*#2mW*<ݻo6'%XjGTPQ(F1yeYF])c20|d,;e;yH&11"T '3GrY5 aB N0 B:hVK*lkHjvjhs!$I !Zbd߈J"pxrrrppZDSJF#(N6RY?wf&4 e/{f efUz;Jz{ll>a-p}tƁpAɃCr"gq"~`I# C&I''v_2Żݮ뺿?)J^bQ }Y*iN&ZuNK~ V8I҄QcD ^$@qļgϞA08L&S JRnd@"dᨄ.>JFK= !d In-cnbgg\. Q;v(fuf R,"u< #iyy֮޿2ov2<@ m#brKz]T) R}l nq>|8eՒiLjUǴF&3 w@ʤQ4M!. TL/^QˍFcgggCZ 7H8,fuPigX)H&+἟Rc>K̔9l5mwX`#րO4 C)òk@8N (:c6DE˴92mdd;81\ jv͒MO C[I8SO));D$Bfj($[2 eXʖ2IdZ%qepZe*'a%I*)^G%Ir~~~vv.hVͧ ;&m]5Ǒ^eM&h4L4U\)0af G#;wrH0hhYh"A|cB%^ vs Ç=|yI L A^_bE?NNN (b>Lڶ]rK 0Ro_sf?P~Vh}1% ֎C #c0!~鍐.k(aq;! I U}ih%Cd[kDӕGd2Inz=ܥ4MaFYi8hivU] []em^mMkhbcVoHQabo,V˃ވ9Y.ݻWtlAmL!Li^j|0=۶)A VT5H\lʾt2Jv8(,9%;#83 ɓ'p8Ǩ@JLUV$(t~n&y4ӧqCp2N0e>O$ILeFlV݆4`Mn  pXHR p[Y`6{Ye|>/vei?\AkZϟ?/"bQ] rk1喬33*|df Ipa"`4jF~GZ^x^TQ^G2읈r0Z !X"1oΧR^r^Z-M!jAo!DGMWo!1h2[yMK[akI?KbzB y+dhϓLΌVf 8m?l2leZuY2-[#Eӹ=e6 H-RA\\\TXש v4Dbfmޤc`t:/~?я`YXW2w22MLb@>X,w@~KMNKhNfI& #.Spa氐.5Djpmhȋj:Yf%Tף!f~ P9Mȗ&n\!%d_䮪F {+]!()q| ֛ydm&jG|^?{% 7GfS~5JK4ײ,|IdbbDb(կXFV#*Ӏ$0DgIR{^(J%( :NL&ʂIy~?ryRl;}kcQ(F1# m{)R牆y2^rf2xЄ|ߧQ8,"]QwO=u! X*:^+aOŦ3C2v"?5o`%a<(勴D9R+d*XZ5,IpIؚ3o-oU%&j#A~[ yL0@q Z`>^AD@;n fQٜN& ߟfiJzN&M%+7a# ӕhƧVJ ZJaVFq|2Lx>$ dQhۛ~ukB,sEt8[1Fbx4 A̲ yDlVPG'%KKP{NS%a*M04MI5j\5[x-Ni>}EO/i*'57&E: f] |⢰OkcS-ŝz(˞R*ɤ+4U*;w>{ ʵZ n\J[WT4r:)ᨷե5/# ]G:ŋjk*kll"n of p%="XL$d0AW|5r B81, L9mxi}d7 W#0MJ$ių 8˕ oj U`eFAF06mIJJq>v/Hn`0lVD(F?#[Խ{+Z*<ܮD S(jGPRͱ"8N Z5Y qbd2VQ!\F2^q_^^Htd e;WRYZgD I&Ȉdq8X2>J(F`XQʵJz {)oI}ey-ruM\|T7|Y}; i81IUj'YyQjn 8RJ;@3krTM1%2zzTFfq]apgY{~d{0\{fc@sQ84  vvv/ +K޽{ڧz_נ] 10b Ytk~/^e|۶+yfHT qӑDk|/t|RdhQVRPC)uX8j93_VleK<5HYV~K,Y_MT_)Ay%Fp˂y_iX&,JXb K9Οr|,KӴөa QJ_ՎN_| !<4xZ34ee8)2Ud<7 #URj^[o>&@L4FU$qԋxMbTݿn@Z/@@1Q(?xUT%C ?jmRЈ+v`{CƎv}4EcDu]1<f"Hq+Jd<7Va(XIGQ|_hfxMЎK#ُVtVR1VWQ`/( p8zGQkt9J0HynN-Z.U{M^2{N^&㦎hrsqϪzO0EA&_%ʹx]Zr "_>@d2 Rnw]h @6KT<pdu &Hu&4#$(7^\s" J˦44&d\.& @F5"#[.u. T]mecq⪕64k4kҪ+ @[>9,U"jju{j5ٴ" )jhm[`376@ui]~ToEBDnHT4ͥlڿ+ :YCپxkF rhۜ]$q  AQV+˴8)JQ݆3: ^߻w1I (F1 o}[o6I˲~gϞ-`5Pv8;+*RD]8roZ%^-vϞ=؎IEJ$UJ CZ{QYi2wb{p> niKh!&.,MӝJ^-H)(H,Nћt #unR'vMX @?o}.pK柗 :'T*Z-LWG#c>Iח+ZwC l. 4pI-^+pUpJ^z zW ]&nda9]VqOVu]HnkRBғ";@wZ,Lq%DLì+Z'FrE dnq-Qq`{bFmmܚ1t;J9 a"5r) kTعE5[%wOxC90bpVL& i$hEQ,Xqضq^k*3 sbyx53P-%xzjmMckL8٨7d2)bQ`ۆ]  _^^"D@=NiPgV.o) 2"jSyZ߿oɟ,KGzfo}[vw! A5ceE]2|A(q#]_$q,KЭ]+J]c`hDF14)PI~u޺Obb}\_^R-+A[[H[u{N}̢ʄ!yOP4|E r" _i* eГS+i`H$ D@Lt 8{뭷>M@??KKJjC{\OEZ|c^dRz:ݓs+Ҕ<8]3! MKz@4)KTkZG>Byj+$Jach/-Ky$&Hwi"ˋD|I Tyh:)y8UwL۶qǁMx: _@XR= (!೫ (.^IF4蹛fXuo~WW5J^a#ב1?zî"(*JO>=?x7  Wm(9MZe3q\+(b# ×/_޹sd?=3`XiZh֨U·dt{_.*”j}ww _·m*1m//Ɠ'OTFa&%#7:uNUFFz z(90`v8sl6:|$ U7W T9(`kڟ/I/uW>uv4!O5 f;t/ h@ޞ $jtBCaZHog͙Ҫ-XQ=fO,3^GaF^Vo|%T*׿u(Ńtp 1Kj0cNl p:>{ZB):)')߲,LԀ4TIT*˶,۶sqbqqq. juurc y Iiggg'XT+Dv8*Jŭi=jRv%}Q~t"ͫ C'kpnHnfJyGGG|APb|Rj:ٟooUULm/dR.f7`{MdCȪ>r ?`g2}x| :5^\\|mZ{{{2'AZSR_eIb\^Z2FGxn3+cL[TՀ ~V˓oe>[)RsKvZM0$ o *G@d&x0%|W t:'BL$_ w:R` 6r4k. a:q"t`33nŝ;wցe~JRw 1:6qPG8zfxtJ%di\8^,l6MgSB̨h}(wHW[y8)(a/%.˲H|0NCnf 5SKRY@!!ؼ3B![|Qb@@1>O?t:/K8B)P?jl6 IQA(tr"HAFh4Ad r>#B.,{$pH oio~r/ KBВ΅|Ș Ⴄ`O1aS)u%@P73T>v{'^̨oUKEl&04 4Uja*J.Qyzmc &Օyp`q-8\qە'H܁=x8N{*yH_{~WS<|Ν;nf%(Riijj5L)022`%ҵRa3]l0 .ˮZ*u:=K^! 'TeÄ}|6ɞp! ((ߴ✺T7SnonE/66%@U>xߴe\xX $]׭hἲ, vbhZyz\~HmGQt>泹~ްm{2xw||j$I١#  zEX>} P{ qd,-(c@=ENr޽s>,bQ e9nZft¢;$^*p:qTw-sQN^V`"<lL.kfE߷m i>}// )̨5s RF[Ce iZr er :d BqC-04:R7j࿲  oir>mV  z)ENmJ+AþHlk *ܽ^O)51UM4rRP@3}:"za ^JO mY_d5Pb iZ-RTdc-h2fzy^rAUh߿S (*JzF:\)_?i$Cm2ovU ( @")h$3LM5 jLM$?"# #rgL&q8 - [.q͡.AVZ,4;w4F X;@_UdxU0[u]x<}4MӃ!'rɾ]qss7l&YըFT7h4vGYMo\2ވZ6$I>+` (03 IS4-eSi( b$$ 5 +8=;==SyfYV9bz=T!|-(CÇϟ?kwh4lF-ƽ_?` !:Yٶ]17[8nk DQ4̓ 8::Fet-PkW9&@Fj.᠒hiym&}sm߆l=e )Bjw[_2$RKzw=<< 58 <8 `*XoVO6 jI-G쁜T:NސABdiS$*Y l30+E8`0x^xQըFTzX~Z1cԌ 4G\G|. h`."RA[,p@5 j4תL2ђ@{jUX.n4l:A}wߧ92qQ"5 0l;0 Gѿ]Kuᆥth/>YiU)˰FAA|fވ[J3XM{UܫsCjƍmgd^,H5U!eS]pA%6!) g91lWX,+ O>'l6R H99 y?zѣGPCpR=[qi@tei@$CeB>j!_*2l|۪Myo֨7H˗Z[fCu[@M҅"~(C+MS $ j̵tR%*~K7鈒|P%~%(وa ,B6,eoC6!gi*6{&̙IcjG{l6{9DJv<<:No\4H08h@y$I ݀eYO|4NԭKli&IeV݀1Nh48yWucH9pٶ .7n ykT~b87PWLӤg PL&wChG d2U5a HwTR`0<ӧ?w]uAJ4yTM}]4`ϊQe.ZQ!( A, XۖUo4@'fJqyD~Llwn7@rxX)/F&Jli4Mƽ{,=;;K;6yJvUa,t:=== DNjAh48VzǏǞَc^4̳c ¯}{c!D\b$!cz#qXt-:|:?ַۖ P -1f,VF@+}ȨaPW=jGW8,#ʑ ^4} Yi(/d]TecF 7qVSzfY *Z.뺏Yvg4PM'J=c$InQ#IHs Z- FnٶyǫCZ^-Ɠ@V+_h4#Ebt]D*1er _quH6l7ۍ;{KMz^lܠʊ'eA2ɤGlUAR}I(3HXzrg*u0o[ٸ« V?$-0 F4 jɳ&mگHtgE\MjZZ)DrN O|2g9bQ 0# Pi@.^t:{{{4^jTk?yނ5xyRu׵-;MVЁJ0]"@DJKxf>Hz=ϛg0nۣT `T ,iu j5ֽyf?-t8s۶}GX"Q,E8BwCY yOJmOz"҄ "̈Y],0"/%JFl"AD)!m%#YZZ'IB%rDaVku~I{EIe/HJ O~҅M< М!?fF-$e^+6iywV= |> Y a*jcY.r>adF`ZwAoFl^qb= 'IȮ VVF9G  xJNn} 3bōvyCUw@5Qm)E2Ʀ[[U,Y ^ѣGxo1:{I]F$l<w:}ߟL&FΝ;o;wж0 ՌӲPVf̠jH],:<<ǶޔD&0"e+P[3G%Ƃф+H<ϛHH0Š VYHN/,J YGd19({U0+q>4TWv{B8"`Ϟ= A${p4"GVE<$I@0F.Da(ERpyme=~qg@O@xo!cٜx@*AF1u XxhJ!-dIP6A%)hG,JY>`CPZ6X^S?ˌ `9QEؗpDT"/Pa6 CrlZǥց0̕@exOR `T^$)Tdfe;g ѶqwbL&t2fFvE1\Zм=]]]]^^,&j5 4zg*WʖtH)jxg>4cbŀG VKhB?'*F5* @w*Dyaa4c%_C\U"3 BE@DP87{N>b_Rd1S85*k ޻kze~g;|A^vR@z]Z!8YMHƪ)kɤ^] CȕzEg2tT%(>2|A% cmt_XJTec 4mHA%q9ll$8ow}WvB?~eZmĠ'OEsuN8Q`zDZ2뺮50 6Lô [bJiz~~nYf z,-X_4ͽG.b Y 1Ѥ^Fe -Ku:`ZQʩ᜻Z'hJJiY)du^c6A0N?Y?Lrw}Y5 Qng@yᗏ@, kW_o*^veb,Ie30|:D"1G,LEt8NI[0>,yI߈lt+r +Nʵ߯jؗRt7rؖ5NZٺjw: Yi*!$F,lp授%$qRp.  e )˚} gt\UPjׯEhd>I-kU+RF {vvvss`D_ÇBtzWؑٸ42YiGj!-ȔR0T裏Lxk]I#/H?ZM3pbPB5E9|ZOl/5@@|)?DZ]84+X.c|-2x=16>9)R&&hݴ29E.eFnQM{ӧWWWZabK(f"c6 8<ϋ"˳4I$N$IWz~LDȊLtZK{ݕRqtC@efv2m4uNa!!i832}E@,-< _.8ϰ*tѓ=3/@,*$[ڃ,!mQB5,\ hi(Qҹ5mށ;mٻ(Yہ |-$/LܓJ'"d@mJK=2 (Tb -۲Kn}h |> Vl,R!\ REqKwjݿnsFQ.~t x:'''StooVG0D!K#em[UrVBXS೰XY$%Px."A@Zi6^o::jVPjzfzbYt;H MI]\\pHK!0z>c)yE8ߑR"}b1_,в.jݮLBizٳO}{!#*:iUq@6Ib@E\2-Rh Y+'RAr9L~ӟc1l۲La72(_%  M?`"`*5.ï |#wp;|Wd>LmWg$DD@١J}Tv^\\LӃq@-Mڊ>)a1dҌ e;uDZeBE^Y8Sl罽nBxpE@ H;$!hZaZF#:a_b"MuA\.TGEya&ijVt:{ְQ)6⯘ _ŕa2Ryoˊ4őh0lr\zB95-Qi5Xvhpoc&ZZMC.jxCI8 V8C#wFsī(( Ch(H,]+DZkrX}fp\];\JԢ=u$: aЖp8LKP,#DInk}? XyaY&q l$"p9Ey)74Mj{R)E@Bj=pd$I~ĀV+ o5%Axێboh=EKN Fo\__GQjF>=#!,>kW ARUfh5 CY*n*BB%8h4 h6m{5D ZbF^ƀ}8;Q0Mg`0orѣpv(`)V:|Z' d>^$OS(ե:ђ\.!+C@% P$>´Іt@Z-7 (Taw5)o U0g{je/^Ȳ 7\~YQLQUTa_h'Orw@PeD@;TW,mx-^wss3Ns h"t@R-( j C"mUTsUY^[-dr. C{(Z}ee4nA s24h6_1Oz_wmF#;߯6HAjbrSiϚ:EO@EQ+rƄYbbb&I  Q1ki#mDŽ>eeE(\@]%*]bcz#W:`ƁKM@ 8ʼZxZmZxn6gJs럆a(eԢP)%0@_ &?.!va9}-p  _I@^PΦ@YO͕h~ r9) _>Mv{e4ď(y^Uu`hܿ6}[ߊQ o(PZO61tPƢD,tHBqgYnYv^3 b6z8 9b=4էUJûضAݦ; MV45r$KjO p _U6_?RPX)ۍe.32k2}Jgm'N﮹b(kp@*Ia\؁H3kӏz0FFhR *I8knͶ-Y-%U jC8L@ bhא:J;VaVtzA  MӴլ. Tҗ˿/8&z\*k4dT[1޵CQv]qS8ši&6WIe䔆mb8Qa>@Va[`k 5ˎNlSFNGi/l CZ:0 N$&u@A,V&FQdNanu]_@w.ܰX-;] %Bea]<"/TQPFv+die(SGT&7;۩JbkRj;aD-eݟxNmz`Rol6=c=u]u)톏p]fy8ϲ̢P{(^`I`RKIWNHkn@XࠢPڐSn!?:Sǹw?\. Xf_*-Lpa].Eʥu,8@'dΈ ]E6`n3߃Di 6amKLڸAxSsf>)SUdvh;mg?|>K DXt$4 4 affxUOޞhGsl`k+MrPx:hEvj\>QJ 8fyaULWIx˺^ b6- *$ZaG^sxW΀ɋF*Ɂhrö;w@ Q o(TeUE*D6^_5)dvA-_į777޲,k:FQT;k{^ȥ`MōFj CI#"Pex<>==5M˴6f 8L&(Kf0YtJ@Ya<চ+>M(”ă#Gݏ9VrVe@&1-{&Dmf{q@+a6 2C~-W?nFfb>z=w!`ǾnPuc %< WiP* k$WkŋN*4)&-Jp`nj-gAo/^v!Cf(777Pk]ӣ4X9Cor/ײƈ=HV5,M$vlDZGibR(^)C.vT XHWn*QdaYm ض{l#sfUi7Pv*١%Fܾ&>quLeaLbkJ*Ix7N/..nnn_r:aj-0aCmu[X; 3\bM4g~B\Pe?xooE1Q4xkA?W YBC_l*D LUhR0 * 9c8Lk زYu(+a&OO ('}P3Ȇ(Ux)Z9ϮJJfhrtjŮfyvs}s ,P-+3tb}6ZiGz(luQt(?'w'􋶣]h9n}ߏ0JEahI:$:I VqgTgF-OVW5ϓ4MFgz),ےuMZm!E696=+ZsYB'%F҄TO,iV,QF*v*!]qm*OAM̫o LZXrnGȮ70V4ԅQ?ۃlp8$G$.bQ9OOO,KJP10 ^ӁI]t:LRPp聳s}} @ԝ|4 (KE'MFhj(e %-tpC;UV@@5QYEB e)d3'g]-EQ<}t:/tVtZ؉/ Jonn0u$I2 lv~~lܹ3`0ϓQR 58-B.gggqY0M-FӁ ðu:U:󎏏_xqqq!yjŚI7D.Y-s-@4+zf.TBkDJ&z]EpOraөbjT[1y!j2%*Ă xކNn Gkʼ;///?n6777zpC= {''Qu, "h4 JƵƁzPF|Z^Lۃ>j2 RuOoRI։,'v>7LcdDZ8od8fP1Al6-#5I?+g)-nLE2 #x%5%1pMiRnt )0c=ar-}u'IE`7+"/TqK@@1P<4ˊ"m]$ 6?ׅʤQA $7YN&/v XHQPp#'-{8y1C4viۖc;f&ƹ*5vqc54M$LX$ /AIaUj.']_n82ZLȚ~\z֧oj}y-Br*,o.JJ0&sBJo>8D26lY7 @x70>|I=H\6@mPer'xn#2y^{Qr]e 暆ޫ>?/~Oaj T0MeyQJJeEha[`FS&%j 豢y ef$ă38nEdF5* F_p8d-e(ƎAiNvU*٬^nlOƒT4P!tlۦ|>?==;44"ǮZkeAhA owtZB93Fgp& WɌV "&db3Bu燇`΃Jk<lt(%Z&-I5rxAs]!c@#a(0|5C^rsQJ;xfff |?;;FQ ~xcebh4+ƺk4vn9gYEh;?K~az@ܑe4>VQpf˵Jp]Tœ?ճHR٬\.={K}/6ZXgo\7"i>{ '!o8Hb|c=;ft:ͮ$]LYM}:pȲeR?/r¸S]B`U_4  n&tLtwmg|m-e[6*P44 U:kA@H*uLl4sPM4h~oun_JC/Y6k͢<۳^‹̓{(ܜe}E'''~׾-:Tv ( a^Ւ$m;+oPrqqqnvߗӞBd:;>;Isl_חHB[Ij4 nfWPigۣQ4.Ɖ2~Ht%XDCg:^__c˲ $R,Y5/_EE>8Duj^,|I )D+n \t:ٌঔI^4]N?O4-R~YrS]Jǵ@ɤƂրcP6"Pm6(Bis#'Cۮf[4u}4U2۸mlApd4@ϓD t\^l%, E z\RK>ǏM|J)OVWn2`2h( I>&w}}=L<~A fySֲZFNxRcP킟kl~q{\b^t憩=,<С<, e )^RkJXaTjT6Xt9Mx?~edb1N(}˲...% V*xwzzjHWQ߻wo4!-@,'/_mo|ׂ34 ˗?O͆eY+ KL)[F`45ڶ %_RҏkD/uƂ?wr5؀˾ :/O~_~/@Bp\vI_/y (P)ɉ]b[/ !IεZ $eh9(E`9#@rvfEe)KSkTe*6os!A"ejߛGCP/\ݸ5ɖ{ѨMT,Jo1#|oƦdN #Hq FKl/E 0lZ^r8`7넝 =2!ɝYaBpsssuueYփ߿tt$Qh#nAh^lCii/˿4o~rK%#6vh0jl;IhZ8OI}||< PF5QMŋPvA}Έf׿GioBb+`0l2t:{@rFnYvg61" f>h(4M ۣ Pq?x9 wܑVi  B%/5SŇz>$N1v.@|:󓓓AN&Da:+0BM#J2śPPPu9k _HeIP,_RS2+H/%CtTóe Ft84BډvG?p故)zYat nRp68 &GߢM~#S\8jM~&$Pv {K0r ~ot:]mY,\ Hm 9-ffe>Iɷx-I@8I[RHLQR4G ظVo#"qUl5 M(#9B[^D&P ,2잍Ƅ_յPnO#$L@& h\%qp,k$ |4l8 :L j|*p58`%I Ç>z@nbI@![50PJi?k'8 r4IlǾ;R P8lGI\KNCTnK"T*HF5* `6riԄ5Yx-ǝ;w/u:dD0gM(@vf$Fqxx<bEeY'{Lnvk]\\dY1N @u޽{d?|A`&IzDTwZL{˗/sYx2K |V~1AXVJS2ؕ!&@Xfٞ>dy(4CJh6-d|47Ms6ҡ K2Rq9|~i{[U(7CTc7pɐ B y,s˲[Z;b4 oh+kQa`y5!i:k[qfyȲ44K]A28&mB5]bz6x4%˚T멵h9F`ۺ+#q<27n冣jHTm꟒]qA@l]Eٌf`QJ{#&``i ,ZVߧ,6P1ޘxXifYej.бv=>|x=6`6R*i\պeH@vP$\ݘom˱unrLe#45 2 mUC^{XNDa``iB˄-caz"/R lw-RMm!̬[D-Wm`xL Z}0|y2ϋȲ{lre1O"הbv]7 ɜ؎H8yA^!22ʳܵQ(8Hq2M+}#!8@$Bh"bVQz@m:. 겑Jzj'&SRQTpr|J5\j&Oxy+F| ?@klAlV@:l6}7較/^^QAlR*SiHGGG@.=%ďy Z x,b@,rqq$IӁCϟ}Žmf3,ϊHdo4IGQ0XI"ZF2 F$<˲|wpVvQR,F#(  @)X,:\M-|\.ь`Y!Cov[iXa->rzHQ~#v;;}M_ 2{IVk<bCm jFMA6nćiXu]='k\"iWkDKrY2*Cuxw}}}qqqrV^OGx ¤:Ll;`0zب_җܹGGMi⮇L@5GLrU8#3B8$v4M4]/,KSuǩ0AЍ`'2e1 N>9BPkG66ku7n۰7ؘӖC ,v&IN ۼ9dL (C?*8UUA'?eF :&?%N˘ŋa#%Zk;qA|$u] 7+S#C0ple& \vTac+CGl._ Q**+Jm8ң,kD4M/Y8D\Uqr5QGG30#*EQGZ h,Ke;ˍ(Ay[ZjFu}*R:5r(,I,yOd|LA1ߝlTh< tgi$4@4siáAn-B;5M狫O>V}_k4JoZEHmpE@]M<<K!hB aڞ I7BlAizWZڔFz%<[ffell6ݻCx u]p 4(Z,hk0cєp$CCTt-X$nZPj5۶g?闾^l^a4s~8AH# cq8A oUjT@@5|k_{!J:5MH:D$ppԈfYǣѨnEM󋋋`-!O.0iE^^b^7 NOO(M:<`0888PJ~_dl:}My/ D2E@x7$}cC")l6vЄgObD(}DB-DZ,R!&p-ͮxhcZ:65ro`55"S/M\ S[ڌCp6e͇<[f Kuh>:QŅ(2M:ϟbEUy>N!b@HB3(T(;g< vCojaz{xhٓE(cR脭>sbb V625moO^f3u: f`M,ͺƺ1ӧ+%2HH_kybooCR,!r Tըjn[ou]U(Nnc.utZ/..^|yuu-ٺT ’$i[wh*N =[,`+F]Tnf+Pz]׭\qݚm;m1FUjtX <.%KHflAt=22Z,m?|Mdx-qy^%I e9~-Mj'wF1 ,P l+`n$oMˏ8d|(KvHaYJm@hS@i8.M,Ndd&/l?ϕR_YXf""Ul6c;" ?縣z@4"Hb-p=-oXê&Vdd}m[a ,aJ)5Lӟg@Fo! |"[@*A4F.K|~~~˗o-xク]nTnOX{\`/ !Ӈ,Dҷ Ka*(֐l6L.[驵:Fu]zQY' kAs!)›Ԗq w㵐\ 粃 =D%4z6EXd߻*IJ3ժmI2q26]ި5I.>F"?EQCR%nTy~bo7´,ojt]V4+p/!d# yo%ĖXXJ>PiGEw\w[ϟf3X 8M2$*RB9v0#;ʃ1b@~2 jtQӶ,)(,z=W$%ifX-Q~d HLA+ h>S)&V ZQt4bqH=0 F ~mBHgLJ %$ɯYTyLDPeZGL$KEyjlǕ ahzl};. J??~X P4].Sev]Ef =DR! 8WIrgHqmˇE~pXAXEOxQaMfIdR)fq FPFve8pe#n#ȭIr kmS'CA5IIvdߡ|J@=|v#0,aa 5 o+(( cBJ=5v.ovgHz9aHF1^%O3U(,,URظdm+uS_Wzxlj>o}||nѵĆ(݌ 0 0J^jS MiL&h}Gp .DB)`J4MǏ=z^/HEp8Dy P iG8MKUK &lYG} Rq<ϒ$ r 2E8CH1B4Qߑ )Z;`[,'1>q\,,ukI5ۍ` T>dZ.HA:-yT Se2fr+ W6ok_V_[kx2@^S8.hɜPa-۬.&X*rL={Nn*fG: V} ~UT4NA0bя~|gyo~W.~nIV&(nc& _{6 qj5ɢVOQMC d.\.;˱f1'7[mZ4jC֓ٱ IsЛ8h}&jg6a{$ As'\5gyr&s(oQ|xf:Nb>gY[Miݲ,ݼojoT%>oF&ICoJCI-tMK{I⵽ZMEu˓'n  D|R8˶m =Ԏ SnVv\Rl@CqZ_b}:?ۿPJM&ȅ*=#l4*#q<{ٓ'Oς l6%g `&Ju὚e|p}I irk 8Yf8ȅ2aa b.I{ IƐWAz-,vJ!Jtۅ6n<jחo057GAHZ; 8 ]\g~ׇ߬Q-FA,SӛBYUTVjT7rs:?Z>DA9P_:uqxxiwP- ۱ h4TBáWUĀ tw3djM!\[.~/ovv+PjYp8$Ay>HyȊ~t1ȚH?e'6c=F#d±B\.iezNXBtH"jlL-(;mapY\h\irppz%+|bm:fx9泭}!?I223JFP 2&⯤&U7Qm"Jotj @`K } KP}7 琿4,Ӣ==%$R\ ÇF# C%(^lt:t{몵mL&/_OO$/¦n,˺zc=R?l>فfykqeeES(r<ӋDkҪs\2 : ޓ0 LKwrQl9tEmT)kO(npjֲ4Jx<ˋ0 0T~+V(0\0?,IlQE5P(ifh4*J)6l3~ Aņd51C. lyH!d777iض:кv֢N;XWWWaxj5{>4H!uc#i%aidɫR0G2XtT&P"#sk12VK z˷d6M~ҕM uR[4w Xݜp1c嚿6iL/wPa7=@ < EaY3,r({I(6 nnn&BrAzfD;q\&HØ"'QYk P;EQRdvk hIɏ4E&gikH륲&{;u]v؏\"c|h4@7F^xUD;|>7|AAb@?DIbECe=Al6)le-)P\N P* 8Džʉc?e |:6R 06%#2J!qH/gSwmb+'1M {;hnC'uIDٸY|b\.2$ ҃J<1O3$i^V@4򉸎h4ySUhWaPjT`Ae>Q;lpj*ĕ`kCʴG2֔>#7 4gCaf(+'?DFJlPy*;jRYZIiFž[@18b\ж@M ,޽{onA9+,, `+;mJT,t:IIe0+"B똦fyقl!R_9CpKk.j@ (l4S[P6B ʫpgfɌiYvFh@*W dm8+,:5___caԉT9!=>j4`^aMaxqqy^ףጕhinvn5MSʲ _=ϟٟ٣G>d @/~QAxa8"󿼼N㟐 >eY~ _^^)A BƍesTa(F{{d:JaXj:y 0U<_.lEQ2o!D8QhV}=,IcT%->uN\dpV;ym7\Ѥ]ƭ@C̢| IPJ5 v _$Iru F5QߤDZa$vG$mj*d< WYO^YIjG@G`4.Տ$!b#2i'T)Atz}} xBŋ/O^`0ܲmsVjY6epyu*V_D%d24lҎyEz=/<ɋ0L#bC CcNWl| P%Fr'OөeI*PCČfjZZ,~/ta[eنa^||eXA/@cpV)`OUKd2tmAN<(iaY+yҺTJd>ah[20w@Fu*>@ ݧ0 UEvwwøl~w{}}oYhDOI]fq?x\+" ImZ-֘C!wn Yfv8 (VJG0IġJ%iml:v4+ ݞC[lա/" Z $ʎ$lk":"l(v$9??~ \o۬4nmmcSnYHf+||Z߆a\uJ뺭Vk4mbŜ Y[VBQj6 ƯzDQqx0F~(0 ۴Mʋ\ʱ=(T>䓗/_"PIy^yid:IBi Z-%:j4;DMj[FgDHPY z'? X[$GkLՐf3)ƚxssiN3/@\.ʭe]\\\\\PJƂl8 (jA4m횦֑.MDxӐvnƈSrv¶8Yx>, $l8CZ|DJݪԙRVc>nm@X \$ yjC0MӲ-=QK@ pRY|Gjv a$p8{TnZ,̀yje$ZʹNCf>Ga`]t:{϶'Of3ɬj6ʅԶmt1`.@T%nSo^T uJEXydx>,nj#L3_F$Z;VUFMO"6pv?(U%SZALF\CRE +f0 ww]C. Ji)(^wFQi h4T'"c-F 6\uKJ :/_ EFoC䜕ODzj X.0GBd=_̦iA3-/ T(&Pf99r<% kP(( rkdTD[,wc ?8,)*l  (@eH)Y#J2sYt ACmSP[(l]k@Yu 888F 1fig$z__Mhؖizqqo^Z"ǍFO?1n4jaI| `S `XA0 pdl0ūPaQ* ,KJm!Qv8kl+Mħd24Sk6Su4!?x+>|) txV2ؖoOmuKQhrM&тl: OxX~ 64*aFc8Ci)Z.''/$`%ըF5QըX@зKpBnFaVYYY7X_kXZEmv8lqI351d 0oJRAjlՕ۩Ƅh>߻wi g%8۶!ng.*"f2.b~zzJ" JʮR N;8 ˲ e X cjrs2,'(lGhǵqk²yV%D ggh! @AN Ҳ .o$5~D;_LTRdK@)e(e|Ӳ5q@DxdǾu:ãfI2F߿wQAfS»Qc6ǏɓkUl> 2?QTb9::j$|lZX䞠vxA,Xdh+WS,W'圓=tK]UQ9 NIR--;Rw-v Z]ފ2ύKǢ(& x5Ib"*TgF`oo^Vj)fՕ^jUըFT (߿oY~b,^O"صZ0|ˌN_\\ a08luxc|ttE0$Fe@ e,,MU@. dAxJLSs ՏvM 4f=ݮatvyqQ9m*^jZxFQZͲ̫+(06-3_Qҭ-ʲHRQo[lFvg DZyz,WV)=ZmaL@n] 4FjQ&|ʋ*甩5RM])\.N1a*A)7[<ˎ#ĠM',3 Ce[Z77QQ;ǏY%"Fպ~׻) EQ2uG V z8 ϢV#!l-sX[BXR$X_ ZC?L龉IZ֕P oE25P`c_^!c)ȥ%cYVHktU)/iW:%Ith;d$q'8<nО)$$@e{tYVLPa0=<<GWQծ={fێضeJy*(ըF5* 8<<|뭷Зl6 ; FlUidadL8Ss/;wEӟǏ~@`{veYy>N0ĵ(kjI,zgnF HlqgYe-wghuV [Fp(R4M2gF9,țАče.)??{L)uppl6}Y,^!EE(:eA:88 o*X`eQb$*YEnN2jCN%KUb "x$BR_#ll6Iį `nQ8ϩhY|Ȑ!D H Q-~rwdQ֔uK|`UP38`Y4Q}9d|R_ ~jC 7P)5NIAEmRV%NlUɎǔyKGA2~!X/٤E6X? كp8|>o4pc#=z30\e0 vu>"@QGGG'''sQ~ݾzVK^byqOS(*u * (5cqPGfE1B VB"M3CE؝fB (ef~j^N$vߞ\:zzЂkҕO>߿w^%-eUB4$Gv6"2s>ȟW|S)[@ޢzPh6x!J뫫( 62CDaTըF5* եc;( jADQX5 R!D6<ϯ`~WfAL&uFjBAz8>z^v{_1zM@`[t"f,% Q"I#xB|P@Q l,1d0 Xvi.< jZј4Wr"1P,ZVagJmj 0- wHtrxw(rh"t]JofزLVۡM;Z׮-˛0FRGTb90,c̔mYraL h[Gh|,J=0HZb/YUBP7j"nW\.o&($tZ1P]׽s`0^*4M \2r#5M'26x|ΝpWWWO>0LծXPnaB7MJiYʜueVD^!|:pO*52%7^veIF01H ءOd7_$Ǒ8F@mi7 xAw~8bԕ1h"e,@$f|+)k+C RDBܠ(JrmJi+Sm\.y>*IVn>6/`2[p]˲ (J$/fgYVUX5QjT@@5~Y8߇$ d²E J2C@|nRazi;3&|j!e@CMQLݥiԺ_eDJݡ(I'$ RXvK jJ4Dtgknde9(OPU!SZ.i*{nRv[yQyN0l6m-n'?7N |jVi+/疋ijڠD=l&'}MaZuq\XJ'_Ё"ʆf(X^~Vc/m s%{C=26}ܪ%7 C" ۲!1 $zEQ\\\h:1>0 qb[!@^Z,׾h`!0&%ee0ʋ"[O 38=HPhrHaP2MbK5Xwp ώs&#g8)5MhB*Ǖq=MZ!`08>>N,I[ 5)ikGsNٸ7o8L7O.rArrJ6e&)ChZ~jIZkf|¢(8n6wIL)pŪQjTnÇwt]PB܀d;:/1*s<;==i40TɘYG9a3*3 I8Im )fGV wp>XAIRػ|(4MuœeYz40|ߟLl`9XzA@XW~kfJȉ|X,Z`0 {B.ײלYml.n,Kh4zaFh> NM82Pi& d2UۘhS};iWɪo[-0 sP"b@l7jV!-T!q%:7Y@q֐qǸ<6X1PLLxƔ~7AdV F< QaUA妀@Z]*h%k$q%z 6AP2 4,A1[x<zzH]{VP8U$VjTP@5!w/_PybbQidX V2TmY @(l:5- h2ꛛXUG:0KV}\Ow],ki$%?unBz#'ty: _IFK'FfHuQJ2:h6՚@3Tَ;vd3Tق @ɌARc*$̟R԰47AвQ_HF:uؘtv$%-(.M1xiK$4Zzt:X96'@ńՃVR*%ckt>ftJĴ\~-@mM$M,3ʄ#@d'%rͦZ4p`HϨd2 GJ */H;O5QjT@@5~r<[p5B{P?;;;;;NCJiJB4QYF$Uw%Ȑ=A_ .k,a$>k0 _$6F`[_v(G[ID9DʆL 1 NǓiE;cz(wم޽{A'FUVjTP_~Χ~tT}j0 WVF(PUh@w|m8NrXܠLte2ӲTԱ/W"5vi]Lh8TJ![2z5m*E=NԚ.#Xi6w܁I^vdhD.ғ0 A72pnf!|4S@Z%Xa>īV5yp7(B}HHXN2c_<`vbګkQϧ*;+nڶnyyE27~͞PvR(0%}ߏj[4I<4Cp<<<<::B2@!RC2R<3,Ӳ0NqC,@|SC)H0K4X;@.X֠2,F@F[krk}j-иz!*"iYڤK^Gl\^^^9c(#s[ l5nzフldžacg V{XjTPjq/ˬc Rr4$_k6 ðVݽ{:ac jLQdp]nв8ǗLH5'wT(Vօ.I?ma\q\׏?  cY ʲ ]"%fYi!%/䫵eYna<Tsl4~z7PM5]P(;2Eڠ}ȭVҏ[N[umPxO+J,@ 4Mhϱ?8XmopdKSkD;+fL<7-+c۶4KJ 1;Q`p 9=b} ܽJa9{5|9/,ml!vvppt:Hi};`#an8V i.(pg"Uдŋ,y0{[vӊD*̫f #/Z MJ9*W|Lj~.y5kMtTACZRD։M,yo峐Dn T㬳aXrVeiYVb2,O۾ "˲V0I0 m wЯLPƚ*#N&zhQLo ZKX!o0CuN9Vl&uk>򴗚|Lnj֟"/R/Zը|VC6' ,+ ~ppp||t0! зl$ϳ45 2W$)ꉬ,9(R*v\Zȋb7 Hd5tm̍Kh4ρ]\.0X=hRfmn; }1PE( C% Q折Ul9AfN,K 0FnwRRk늦fO>9#x%k)=+QFnQF-6=}Ç4[vs`Y_:(TQdX$}ad||']|ɓ3*r`, FC8S47> 1bAqW@@5QjT@@5>X3 rя~eEF GpzADGQۭ2ZfoZvI?Ͳ4 G-ָ Lm-V Tj܏@>yᤝo5 li} 99K}aCFT]E$*5CkHYN"-MJJF 1`.Zܶ^ln xx]յm[d_z" 2!Uҧ֧@gm}1ωfi@Qv ոR`FH9vbLӿ^,_mc{q' HZzԊ uz777b>f3cQ-$ O3 xy^&iB 'ÝdZr9͊ߧ4dBYS$B>N N( %$Ib~eSMSBed9) 繞%}ÁOáeYfooܲVe.UcLW1s'ujZ݆ n,EytL.>0Mۤ<0X{Wψ 径75<в@3F5QjT@@5to՚]hzEϞ=;>>FZ >IrȞITY>xR_+E.k2*QV̈́92- C~Ȟ,˂ \.|>N;{]G4LTNY_ he L/fȪDs%FfIFeLV*'Yu:۶///ÇٹuVS@ qL 1xe;_Qk,C>X'g;<|>?===;?[YbXHjRjooo8u:v gH췔 ӏ=QjTP7iþ'4#/~>7}v;(PK;tM9=m5)|._]]aooVA5i!jQJa8nn'^vy$C('L@Y.K(H%WfL{lp-:dYL+ .5^8J36(mjX_pe;>>Fs,x$㽽(^hN劲йR&~)@&MPݓK^砮@>˛oDd^;=~-)=3DjK ]2ex(&RePR =$i17#4;Th.$a,g4hD2mk/1H ô,寚`\~{߃vmmziP^4-fJ:  s(,)s!5GH nˎd$j۫TDID# heق/M h _yI| ER*AooVnt:lP#_|m6o@0­R&6MXTmk#p6]]]]__lju]4 EYZݹsg4Dhx40%k_DjTըj|Ƒ$* TKߧhӧOzfLx ɢƸ Q,UɊ8OOOtzNert glF +afYV pJktδ[gc8i(=cey-mUx{mi!b>:I?Df)ѡ_sDzt xFݶmn;sD~aPvm)ANWseT*/r>Y1T;!g&LFD$D)h4Mznm0* oK1&2zMk0gYkI:(N\%#Jfv]y^Ght:c$<{\___]]AXEKqiPaՆa=Gjs??O_;?ejL<$ئiFnai(cEۆXF777qx< m!"΢.!E3  l(Mx oL$j=AP6^)a: 8XT&Z[aRMT,TūqgʀZ"1˲~PrSWHKW4M˴V`eN@iضSyf 0 Rq h:ro6m/0-ӊ(hA wC($lxk5QjT@@5jEs4M_xh///{xm6[h}}୷roC6 @ Zyޟ1 8t($I-+Wy+V> \Z` vdRb0f(Ɋ+$+$bN`_A ^/vPG,k$iC0\* l*Q}駍FaF#/2dt|WZLµ4O![.777b2aϐT'\fT`QZ.cquu,.8`[o&4W=*ǣ/B#ւѯT~5z6_-2:{-[a=>~4leO;uݻj5$W%+6쇌}x2z^q3h)Epz5P&+ e-?m #ll!4U7r_\\qclY*s\4TfP ycL+_5Z+NaE3#m e7@#'k|`Muj 'ᐲaȜeb5TdžYFVs`0(w/jZd\L(Qҗ¤32$V@4D^v2tlVD S`X_@@9mikYvga(C$K@`Ԁŋ;Fl6FӶ"B[A~ ⡘:9XeYYEey-@o4lñynGg /Jƻf2~m9K1BD50QڬnS@|Hp#&/`BCmV.5|\JF1_@PZB)&qYy/YU"˳<˓$)Pv:~4x‚#`V,oըF5Q*}fYȟ7׮81&Pن-06KrS/T=ϛAt)CJ|p䦲oY WBFZ'͠9 !ϋ?v˱:ckЖ||P4gH_1Tf~Ŗut;S.Eqyy_hmۦay&U2UݫjzwKp8D|>ǎ{0ed2iA bA$MM#Ygʲl #mh4D9m9HKVݴ|Xs vFQe樎g}Ƈnsj2.|@e&+ӍJeL0Ve[Y`M͗l4jZY2˲,h0)nfKS1eNݮ\QjT # VYVXVgضFNR DJL{@IL|KU}OIg!! "pap@xK{"ePx1{5*( BNOYk9|UI; ԫ;T}z}!9qtN9OI`](=kZv{bbbϞ=(=5W@) 'b(HHbp3'?E1&>iLfnwKhV"VqZm6BHΘe"Ytt}WJzIkhCGc(H!ڶm˲ oJ);e۩ur۶ Ţpv:ؘa)c},9<yᡀ_&b[/1ARc!c_ـL0)aKS-e9KBGm+( #Ls155e˖R5Hܦta֍~ :#Jv9rM7XMC1(q.H-3VFdcEJ266~SdӦ @`'BpWp.=)>y)*p~d2'u!X( @(IxTqWERjTq)gLr(cO~,ktӊpC4דY]X"0nt:mqHdy^+zF.\Jl HR5 DpPpJb$: CVzF!|)`]lES"R%EQE!*VS` lp΃y0qmmL6AnژbM%srrr֭P^᫁+"|L }(qu BX.°*DF4'jGWstG@(Eu0dm8U=U)e>/@&A y$J4)E02E(Z4uz,ģ9`3Fk<*,˚lÇ;fh daǎccc?O<JOM-6 9X Όv iAN'j,#af#b2L. 7:hR&xrCP~3o4fGzfa;vlbbʶUU0ϪxLLp@=_$@Tu "H6*t' `Pb@   M%u!3 =x *@+m>f]Qq޽KDҎ>Tb5nr qaA#sy.(.FǵZSSSx8D4J0x̣80 BJh֍$K".'2 @;XpQd20KL<,31NsJJX=av ik3 O`pVqAO2}zDl> F-A!9I9~ܐ1.= d 86/}cAh4Μ9Ө]&%kzzz||>bhȞ$+SPDQJvyw}q8噙 p]e@n㨥I3U8:5nOsrAeb-d8Y>W"ㅅZ>p3P* n Ijœ`*]t[*-@e^?EmHS@Tq+G@n&7 @Ft5' C:ynVof1^dy8MRDb mt}az^ @3b0 P.cH{Z{;􂞾Z466e˖|>w!u&RĤA:@+.5 n_3[^w,j$#f77jrQwsncBd&vI.:[˫qG@(ÁLbǂ)r959WW/ƥR >}~zzLJ\=ɟD* Ca HCQ '(Z2ZrzzqΘ4 yTLKyy㕱2LѠ @jtteЊCL ]Rf#vt$rSJ =s]cBz,ъSt0M Z( !ŧPL=$vz=ƙ}nשjl6GQtԩr\*rb|O+`58o9ްҙA }D  {J?nczsI8Sj{iAV|C%t]f))џu# -˚T*mMct.*ՀFQƜ~v-!޲eYi dJ RG˩G-u r AVx;0a#8v /IG[եNF49O?o߯}9$?,e[dBt*.)@ GRBh8 @R4?11;hQOmk[]z=϶,9gN@N:S,R*Y>7ׇ:iyCA;7i 5LNϨlƹJ =-`JvS,$g3)i^>IP P*tj N! +Vm6N:cǎr\,!_cFB\C7V9Y(cfWOӈ^l`^`. N QbP䧄;-g+!$P-CI)bQK+VWWnX䜻SSSSr)NmOtǧhy+PMiR̓4 +rH)ȧ4ÇXD(j*&0][ 7:LO:6sP_@cXKq3AEi%Gu8n; > =.ɡd> ͠p+EQO6jZ(fffH_ :qab:aHCr(zQ#ًyJFngiiqyet@zJ1@! .'%e@Tc?}TK|;ٞ=O񽹜x;'Dsi=l;jY[Hsn?H}5G}♎9!X8R= Rt8iB-PdQ>dՀ уN5Z n* ;@i0 Z!@hJ%y8 mcQ#CڐThLt̐v( aXaXV-˂Zʖ-[f4tu;6LR{<6G`Vx0mxT5@w ݳMTP3NlO  )Z5x[֭t655I49R F>x&:n UͰAL9:8&v:Aj^ 0$B@ SqqGqUtRq!bpYXXVkBb*O1d6ՅoRTXq1͋/#Zu撰 ZNJSC2x[ϮJ_q(?wRFhԥڔu!2Jn(F@(.cp維0qzuؤ| 4cP@)rUaiG? 0 Dn^w5β>˱nbV{\TZqh9?< °yEKsqiq5D/Qp14:=%uӄҶTʶlNq70 -T !mt:(Q`M;\.GTR)s ?`۝vRHa0 *J˸p뚖 1^4ayf%^աlUC6 #σQ9u]@TAvw<#fk651],F4m.m[@b=赆›(ʉ- ° |߇cǾo?Gj}WTJjHJŴEDZXgϰ(fӸ($hAGEn-ZsRJyp$%<}]^%h8 A np)0h'<5nj|T*U**r6["#_RX)c57BuT*JJ\,Wٶmܫv*vFQdVGt:]?Xb\F`YVek'؃w(sɘWųo,_܂mK)sQj\W𼣞w, [;7%+7$^g88 Ϸlam)X DQnffR p]Zfl6[(&)J|G7===66u *JQruEL&ja/0E^(‘p|n٩T6[c۔LS#To$75J}Ke҈\iR^G43lY&cٳ߳m[)9f34„Ԛӧu0@')} /@3f`a%D{0jZr=cnT(FAzN]Ap H1Lc]xamG[q.zV @Z:$`}.+sFQbACK8$ $GҶ$62mxa/K$fni`'H`hiAB18 fts EER u}?~#G+vE0( Rꪫ) 6A#m۶l)$\EJpsOw'n9*ڱ$@4%lWinɒu9 QS>9+A3>3AH v9ѿt;DۭjZj(0wcY8tA?CǠ ÐB28 = z+kg6}`ģX,RJR "9cѮ#U!6拇@Ei;B|P.'r zWb?/WʜY!R[q_nJ򒮺3?﫜BFٴ21Ӝ"R6y"#esspS9)Dzbb _`w(▥n?\/,,sMOOSApSYy^\YY1MR`P {UW] u9j֗#LJȅX#^Fh*Qhv۶m3 cqqs8v'Dv9WLX€ 9Pt]>ORJɬ"E:Az.{\$>}s<3 )^`hYuB~ӫ"tB].Xy D!:B:FJ'L BdCW>aYiÆ2i!S .(cPh>|q#G%BX3c,ę)7(MB* OZB݊:8z?#'Q*z{B?1heM7zCufZVNØ2Ƙ²|>_(lۢEݒ`P'1&dA@dܨ< g|} cWl^ ͉jU(`T*r9NhHHd=:{t޺.~z JiPFeYao> /|;ȹ!e4'Ms0JB# ޟigzL[˟I)[WRKcY;Xߟfo8R)ff.w~ouwgt…(!2&Ke='e>\ 2G߷oT2m^Ƕn]ߍg2z;,90  N=2 sK%ͨƃ  TE{H`fR Ӕan6t{HP.P]@  5 ׀Xt>;w}l@A&btBIf#Jڲ,XK|R .zA|Z)P5^wE{~~~~~~ii1v-@dn&W(Jg2Ó |--O 4 !!^N.}Ho@¸PA>ɀ8ߺ@a[[Zͦ㺜s0 E8vKbBYXHQk$).HPg B`}HFX1Q+k˲*8MɚS9qgH'za^/do>755|.EBo]I [ܹ[M) 5kLs\4cR[8=ngf+iNJ_oxӟzU?؏|]wy#NG}+@ #xM71#8c즛cWOb?SS3J|ؾ}lzZY~M9#67u$ o'+v1Rf$$a olnn_bϱ'e}#6`@|Wn Ԁdu]z󅅅ӧOW3g|8v}ȑv;vfO1\.`1aGgz`iSPP&}p@+Tz\q"n ubP0 Ȟ0 "J#I",iuEq b+Ŋ} B <t5K,hː=p_h=u1@/#iͮo\)0 oj|R'$uҎM(Nò b0LӜ) \'h0bb PAӌ !2%aRҮv%PƩF[1cssC ~!2BXCuPPr|fö!R2vײlv,w/׻k^fuɲl+cv.FQʒşꍵ٦i.o>!ι8ql'?"{qlxcޑ2+} bXI΍-v{k؉`nNͱn?ޝw'D[.fZ߽][oozSGvRQOBd'13?`$g0Fq>,UI| `7.!BŒztܨ-1V]Xj4x"(WǾ{n}&q kd7305Wxqt:(8;Nil ꌘ $t4 (g;(0hDԫA.@z L %ωF^N Bӆ/Bjѱn jGY\\|GϜ9}t>|ݻgff8t-S, 0 !HF4 GyF@*e|A$$-Iy%+! @s,Iu u(ua, !7G0x8vZ|䜧>)^TꪫRT( À-"IC'/SJQPfYױ\.G/EUϔD 0 0jZMRSSScccܱiEx7Q{|׉ 3Etfi$zCLajjjzz:ϓI0Ɏn}/}?߽G0ʖ5;7և>ԙ8v̿rPw̲f c\+Qi}C6x9[9y1Wbk?OD[̰2l͔R/K4χ2ͩTj7畋TC?Tq3_ KA,Ds{K_|Νu6N}1ԩ;kY[Msb-)nWQ.޹}{zB6k_>moB˶o7~gQZ`_#z;=ݛˉ3i)uo}ϟv9i 8Fa'@۵Ed_̾j@[fg%@1( e]gN8D4ΊzYt:ϯ3 ðLS 8c 2l6gY6u<7dYTՒBds9 |֋+4KŒiZV !el4LLL%LFh$T.Q-Jxnnd ;$]\2|4*^wx1pc C@{luRjeeYyi8Fc,QSOw4Аi\jn֭[ gjyNo}7`]711q̙={>dthiNM8:/b fY֧fMG7OTY5sڌq!RY1 ֺ\ki)lu˿ fm-gfҾ?Ϲ)edE4c}de)QFt(@j?+5)-[,KlR0&,kEO4g}MO'J;_2}nņ14èH3c~ ?>vl;{RiO-*Erv?G]I9d[P§j<S!J4OF@(. `ӧ `zw( h -,Į@O9u73 #t]*#28aJ)0j]GhE:%) !F㡇 `ǎeg2B0ɡ.%Bd2IȮOבғ]2>$q/WTתQRO\.J%ҙBlݺUJ.zEA0LB _'p \.Gd~BRؓVb}TBXuDBgR)mJ) 2k6Ѽ㎚aT2,q/U*T*@t:MQꬬ8K={dY@UM)ӏ<܊|?MsJ|4]4^_ZuN߬_(|زJܸ}q朗urʥ=7~\n-[׽mg,pnrpc{1Ź-e^F(k~w:S߱CmORtַ 78ԑqǎf2!$F4Ϝ0I.үVn=t9疽:QJazi|:gfNQW<]C4ei'N@ncbꫯr㐢XaFan˹5 vM8!߬Rҧi!BurOIA,j9㹮]ǩhE> , vb-,JJ*Qu`q=wZ;wY"!ai朚hQ"el4=pxq9D ab||q??k.d 8ϝ;c`mm vgcж䜣yj ?آLx 'kz(08 b C*[BoE` q`gq3k4$Ibqq4կ.///8ۙL&ϗe<>>^|>O ,aqlaĒ?3GUn+7xq ,+3J}0@3]9!jI$@F m?7$q{>8LOŀ(hnX p$>(%ٳ+++\[AOU8|WV6b4uB.aSDZcXs*8ZmyyYJf@ Bgv0pBbD x? Ԟ={JR&)JHu %\=3L.kh~8)7[!_SSY8v ׾$p5(z8Jw zh1!DJ G>iߏ>qw9B؜_& _[.l{۲%OSv;9w=' 1F_^-7ڕ z{_'8DQ++ROx!/D~#C}K/+19|Ν({̹)eiO?|?ϜSSSȟ8dZlUP,M@f1Ar^״Kӎ8ݮifseeL& !7\VG-,8^ZZRJ>}zqq1iz}kC!vɶv}YQԡ93ȹ0&p]waaӟؾRr98q\Tv޽"Dp`R[㘦nl (k͊;6Cb+?{q&,'B1FIi7ޗ䌋ybS _oS׆5ԩSbG~G`nIxUhNtM z0''tų0LXP$W aD$X PF `(0 *?6 uaC?Cbd8޹s'X]]}8;{l)rKe:#ڎ6+ShUUưBGrD#p!M0F׳,kee6zfswY鴌EN^w]XZ==CG;ivw}Ƣ+7tΝ8v]P{ !KB_w|WKʂaT@˿ O^uU[(eb_򩷾3r\Wsnrn0&/Yo ~;?\FAt]uUR/,zG aqn]>ͨV[nc\{n`>1i|e8"067>vv=;'.4'x# `Ok5ϱ;YlVEK_b_ZRs箻믿~eeeiisa;lْfDހ LBQ9#Eh6Po:~8ҽ{btمsgϑ1zŌxA8-= w F<~]۲r\:AnՊ8rYlyCϺ35F Z5|Cs(e<?D:[;-kumuyyP,9VBT**$j  A Ξ={>Cs nD./////z=Y}0’ K#UJ8G_I/yK݋SDT*ڲeݻ{1ߥzq8㌏ !.t$khJ%XOס޳Ջ܊XN_ 4((@Gf0ǧ=zތ1 $oO?:ѣGxmݶyLNٞ9]'d()/"w]Wč8kH Emѡ駔3H!|4;1ï3F=t3° kGO< lѣ:t܁l6y^>G7޲,!! θϋ8} *4Y4LTKP4:|ȶ~fs׮]B0Nݻu݇~xv]R"T}/ =s]w۶mtHvCB|vhKh3\0| NBI D,-//ZqUb(-E:;B[]]rB E5<=)-uP `} ?&%YG()g Xu}i\߿TDjرcNj8v| 0Pqp@2ZT(+7-c9ҥ)f49B \Ǐg9Hy]A ',h^kl.֘f3;;K(v[FFnTl3>˹¼R*b,f{Ϳ(z<)CoeC (rnjiSMs"PBh0<ǮRСwBXR,kiNJ \ȹ%eɜ"y4&0"`MR^m[*ıI?S?޵E0lFm )?cBd,pn]j1 )KݶIY_8+N%>8L}zAG MsB4c~r?iN ޷O^#wo9طLTΫr98nM(Z֔ŧyNF@(Ɉj4>r95=_ v]j.@n{IFgjC#ܜ 0[9rfffrrlHd=;~x?+TlϟGSe˖-b1f[6ȟ7%>9\0tҥ;G,\hS,͢ $\64ʁ$ǵ@' g1}6;}Za5v\) v:}tZ},rY$d2axᵵՙ4֫p]W^={|Vnya,`4 ,otпX=[3hз@][^FP$6.7ЛLG13FZ][[{\Z:(ˁ#p[o8 7 9 Bifh5X\KtWġeGum>.IĭK`q HOO;9f NCTJgt>WVVr޽{ǃ u]S0O :D{:'8diџzиȏ^WՎ=q'"p@uWK8b1Jkbnܶ-[mS5P۶ә ?ƌ=]Auc1 ;vE-1)s!SK)s9!eI_]sahcW)Oh3Gv'ma91\bΪ)Ӝ4.8 sGJ ȹ8u*N@*-?8vp._!s]ṀJ%}kzTʬi[֖d`Xc1y6nLvYI`r!2'Nr9\NFQ+]JE\#\~ߝ|e>җNp.ƄaT=t0'jessY}^?߂lԻFqҨC@ Dl&8ۺmkE޺2PSH'PKWF othR j+Q e&_RmnZTFuxԺiaHzn0X>ƽߘwj̔bHO{ι0L) o0@ (Η+zkYV& ðlrs>ZX,U*XXD%Ƀh%u]8 8l/|aǹ\nuuțFL0zu3%7lszWgpmJdWt 9ܲϛ)ⱊþEǝNVj5}n8o߾gϞjL&~9}u]W. $&2!{2Cgi,]ctTc۷/͂sA0 ] V880tXJ&''!E0 p!hQ ar΋"f& IP>hnV3MsfffzzT*fMŠ=m@7Xw^"%Pͦ~K֐ۖ/ZqIes`3c%/v?Q*c_p3 v4-kk4z꧅s)e4cO֭f@cc1ϳAn ^0GFxűc5֯\cCvs!D0*Q"kk}ps64y@Q4Q;s^"097cO6k^#~wAhwBRLsoC,0'E?Er_w`pgS<#b(cccBZu׏OLۼtJ>છ?<; jN&Njݥ.:鄁O}BD(> 7[HSU1԰#`P2GIQ\bjxGxݻwo߾P(BtdzG䬮_B 0di( Dzlz|\T+J:!r-G=vh0,sjj DݻwB,--󦫻fϼH Qa_$}2pb =$zL/,!)|jal?8^'EF$*Ftğ(V (L&fou 42x>QGP`g`MX@%z'pZ-tuA: @%PDtZBT*4&@&333ccckkk+++P0R[-]nrRQ |Gbit:xvptv8NZ=ydQtKHp0o])Ữ8ST^)w W@ \\.G+ sgI_LJ1ju{+cG&sЮ:@&2gS14Q^g)_Ij1/H\8FQSnK{9͹!e0XZ:Հy·7kAxr32mIR_|M>=^ro8)GA5I(_ɑ0re%da-S8lNط:TbV/xfNVfuQ?2(9UAE"4~4(F&J! &pu ͠sȔQ^"ԍ^aA8ta ?OA@PBtB]ǎzBNxA eZ%`}8R&q]5LOOOϟ={6S6 \.|e2^ . , *(=#U?]#8T*%) 5/gmbzEi{^ѨjMNNsNlU|Ri6Ν;{0꫱4[A&8tQJ1bS8,NgYHv:b. _k|ˬkFm&mHBN>q@y^XtFQJ&'', A"$\YvqBN]3ӆRRRd=&&uC8Cnwɮ\Ob_KI$u¦#ŷޚ|w~t;W_c.i/2n}/nwm.f/V@@vFT0.qέ%57~t:aAFF@(.;ٖ-g8Fqyzȑ#Gр2aP:j)CdZV,> !;N8}8%7)PAiiրi k.={&zIdǏ0/f|ˇƋkUB ̟T)ۖ@ܐR|>nՂ X[[CK6mΜY8{l:n4HǙFogffpK /!L@mJJ VVOL*10XSRjmC'b20?,0 Sq*QQG$>r}3 T*%/uևz(NOLL\B1'!U(tFn# M[Dp'DF_ rbDbHa! X“ommZ]k6aMMMr9X2J:ޱcGӑRb)^\\lZ[lV-=mJKև{=ꙫXQp, !L%8¥kUgffoߎ Ph CPMLjă۩p[y*$Wa@& l6K&2Bvgϲ/w(2+^<#`ll/Olp.8EL֯޿˖R޾}IKsc1qlϞKu{߫k)GQԾ暩ğF0\@)b/9/P3+u:UOkWZJV|9N]]ay֧2? xW*:T*B4<ؿM_'۷+8u]OJ11oL?TzheN<VXЅ&_^=Uh ܖ-[:rYo?zEgΜiQ]0`I' 4jURR)[O^/NoݺT*tFs_JrL&:B5ۥΝCuv@Ʀ.0APէ[|_R[D O0n$>(Ʀgžm^w1 F[Bhl??/,,? []]]]]m\.wL&C]uUB0!R= 4Il,/c{ٱc]FqeZ=zmB0==I j̘ѽbٳ BH\ҐL4Nu/`#,hKH"AEI slFbץuK*sMdeMLLvnzӁZF8m 9HQ R~@VjJ)'Y@NLtǎV {Eq(* {i6 Ha}'J4ѷ'͙x)qrLĄiOm`m 8*^(>nU0xto&܌ԭojՁ (&8VKKKjsowyw-_ɹӧO?c۷oL:-)9SL)%&F '7D3%^RmжEaI5 ?i@@oF `˖-[nK<%k̖->j+Ν;i[.]BvH^c{ z4Ba83Ƙ...g lt@ĥOl!5YfVH(soHc MtNݲ,; \.W**JT".D<p tN_F\ꕯqJvCC:ε9[Bd8?s6t:yϛ ?Mm!O8>`Q6K%:Dhpeoι\2&g.n!woðc\@l "ތ nR)3r#v }cSSO?{9Je# `P8cM7T<߯A B#J9&*⠵[|#5-e-,,jUNA`\D3P(`_f}j3te˶B4|ߧd1R;"+9U4 V3 #e2vt {A/0 p2L z>N͖e:cfT\6,Զ CAnPXZZZ]]o4HyqB߱cRj~~KKKe堰@j L/uAj'TcZ3?SM|:?+hx" N2@.bRƲ'OZ^J`eeV/}ibwȑ#a 'O\OLL@As(Jp Л ū8VKhbKD5o-g.,#6O s*،u@U~3cnh49ڱcǖ-[KB]k.]l6RSSS@ ػoTۥI2`T)(s8`XEq"?NRl޻w/4QWkA t@L hD0iZ Ef`W3z=֟b0ZM7-S*rw_dY{˷EуQ6ogo,crXe>d:RɼM[H o67lar2˹ac5+e R\,þs97RHp?`xEˉɺ11p%A[J%>f|JG8gy6tbЍO]&cs1yYhKСխ[ 8}h߿kR4Uށ# `O#~X"d _?rR3VFfoV:F M<08FDzqHԲ,keeSQu) @2M~tt"RTFQ43=3??_'''#wDL FjN A=LV$m^cCeY0Qwnj;縮10_&D8hP˝XJ3Q +Az=A/KR;vH)jT@Rjǎ;w4 cyyG!JHH;\-i7HeHTvq:]Yo27\HċqWf"dmmVI)!'Od]wu^{S;6Z"zsiY[ggm_YYy B.UJu]خ](KD< 5;R<0 $wЍɺΥu既LS&ٿ?+z%1/q La kV6^`c(8:/0Zc3燃HWTJ6EOf% ^ђKs IݳFgRп:j5bll B|'u0 AvsU+ư,z~ ~V5w7aLpnoƟ6Fmw)so7J ;֒Jo擟liV{Bz3tvnm/{ls0ղfCdnSgRv st'?wR*`,-?f"/f`3ox՗ k~x]Ms dwD?^~*I)y˦a92Fq":|X߳_eV(;jr0ճP )>~˲\׳mkzz&NK)+ÇϜ9X,Jm:Wg!sv+鵟>Yp p\^ 1NwzzAaȃo4@"H{-t4LF=v vөժkFX,73ƙUB lte%,˂*aȩT* #v;}E!D>zzX,i!q333H{ީS `YcdCAnNLAeNַ3Xt3,ۛP!w}u!qQ&x1h4xG}Qd,~B)j6RhXLD aA4SHYy@d2L`r9۶a ~3Z*`yՌ+do~U _uϯJ<5.~;{뇼1կ\ 11! C^m o 80~问'90O\J_3 t!lӜ9cWg0 Q+CnuRDQ')702s }vX<ث߽o^㞔1WzȋO3&878.Y:z]==mo;#eQ|d_xRR?җTHxt݇zȲnיΝ;zCg~T:W\ףFb ri=AЫj( V4`ڷo4J:E(d(E |⁃#z>_k ,JL41K) i[\D,Rbs^:/$ǖex^oii9 C0]׍{^.5M<׃mmt1V*cj4L8|:@|"϶,몫V-'~{v;5qaj54"}`h> ~|{G1mDP8ؖ=959mX,B+.YcLL% b!Jq1t:P` A3:_>?c>QOWZ c)-e:QCQΥak^ 0}wgK4F#>t3.X>2?%وLQz؉'oB!elR) b@,/af966妦&^Ezi )o'6H _%r1+<`}*vM Eء76&ph4P p߾}+e@bs RtK}H8"WI}|z~E_3<JBcq*\d3-[S蒍DЫzz$c`1T (l&]Z }5mGGRȐƮ]l^YY,u}Wi:M**On\W .Ǐw\'>CS1띺ul)aPmc&"i5n[(8>T PSz=111155a$N'ˍDaEƹϜ9sqxhe2|>_(H(afh<,o%*Ӹ!C^s>H)oS % RJWVVcjZtP(jԐΤuW΄'n n!𲊣#OTݛ*L T;O az>S9eR68I>.Ἑ @ܫ^/{8?4\6733S*pǒi,PcG4"&PRI N3 .|pu𰻮K招 0K: ?YyMbdÏ~ 0)Ӝ23:^A²ľ}}F];n91tǑ#즛ᆉn{l—UU?+_mzcQ._ժ \0*2m$ }(熔y41yɏ~a~-ܔ`QW7^}m0 ߈EϳrYdApa@L&CTna Ew*5r11Y5%lHpNvH1FjU|3ַfhK?GC;!4zv > zc P2h4 8"!(eGK`u+z[[:Nղm{nnN)~5u{X_UWVV ޽{1dF\1,V8@"b0l6|D0Iʦ[{~$t:I)r,JD J FBy, hZNԱ'ҎUJ@3hl(nZ3atmؿSB\K_/NO6Zu>ouA)39i۳Rmθ2SO?Ll>]";ùa%Ʈ};ޡ>uav&ݡ8h.?syM>+U:1%%Q<2m?b^|g>{FK{D·yU,B4M,D;+^&H_ÆQ|f&G@(.]|/Um?ٳ#R3\׽;w2R4cLulRy%f>=={̠ErJ#{C쬔އ- I'9ZHBwbڬ2]AnRT*t:}CaPӘmzӶ|>zNP(yQ*fs[]@gA/y\s w B)(#A^4mr\6v_bBa\uounu$s]L‰ rr0Pޣ"zOA?4u^o6V !PF׃E8gΜmk}߸m6(ޑY{EHQUI@*:۶HLxu:F"TE {uu5N΂-aFNJ{l, XDڂ~5V<4d\c;wœ No\Ń3P-lCgl[ĘՕsllspjr\.c\z_`Ovqq@ 3NR^s͵ifY0P;v}1nYXDY$d>:xcTѝcu^B y\A"ClVfɔzGy~yfXN>fggg7z=ŅBwQiJiQTZFBx6ō}ѽ"a% 0*Xa UӧOONN2(g hRg@ ZdBHb  e{a2>) (Jb.dBB#RL&C*=ٵ$p@G׫V"uvfPg=/g)i4M@.mݺk^?~lkF\ᧄ 2_^|d׏=a_Zu|po,BYӜm5w_kε/8@i7a_^ڹp=w}[YΥa9߀=$\^(3qGCqCѻz|n^:ށ?E۷s] `K;T=p~yA7<ĉ?;ܔ2gYӶ0˲ً ͦϹ|{<|8|ӛ]uUiiӬVӜ(yɎu㦙4 0&ƸaQq`߾$ abs1_g{Gqf?c϶og2o6ٛ(t@KE/zQ6Eo ">!G yʕWVV 03Rj_K): S )SDڛWP9L2M*Uɓ'F>OF^փ`9V°ۿ}Lsp NlߋĊsSʌӜ0)(_D٩oN"70*ԞTjq{A ;wim,/w?o"m.B0fFme2fGtoTqߜcX_3&F0~7gW97-kK6{iNi[oM_>7tmoL)cϼ<~̧R}Qdtzwk߁cn~ %)(in1 )3xy5° ?5eM6Ÿ曳岅=qࡇ?1&Șem-\%?"׾֝iFQɵz1~˚FE(r|x.(>ݸ뮃[9\eY[/yͲ׾[Vc}jղr\R72 덀Q\ 1f|3lve2ϖFHs4ك|>h4Ll9IS*WѪB6=_VME}3 LX,HAC=1i%6K4@XXF_XXX[[m4!|N!|:Ft[zBb˴QzAAudbR& =x.Hf{Ho|qq̙3KKKVZq \.} )+-[LNNL$n[V:NXbյtpSZFN}۶NrzzzllZ&7*)S⁊رcN{npdP" 'N8s̮]nT*y(>h@Sjݔ<IqNg0 @ժV`J)q.rSSSccc8tҤ8;( ?/ dV+۷ook;.=ގCvaFQzDL&gKD|߷m\!ONN)IFwc ȉF°E8 P7綔9(I9fE!2BXI$V* z7-DZi+!R1ns9DnqQjEQ3]b͂s.3F0Ms\VQ3Bu8B-km0~#pZkQQTĘӬ攔0lzg° 4S݆QG|SjPL~_\:}97/] 5z `Y,kVK+ 0\  *F)Y1y]_TȫGQ{QeL 2iƹчҪqծ"eeӜ6I)sQ].a NzKJy"kY3='eQ ð`9]nDeƘmog0 %Z>al۶Meaϵ@B8\'''cqv*Ч0p(2T] 0"/5gffc^|D,H 4c(bЇ&h$z6 narrѣv*b|.GhlceRJB0RکT&@jev0:]R(t.&N 8wҶmj-[FGCx[UTr*fBU7V'  oœ'O\w}f&Jvu 7[,u%yRx'I|k_H%(&gzi/~c+++gU*,2Xh 'UUjdY<(D06Qn{'NLOOJEl&(vk>C*4T,i{}&vXW__"UExX O.KB3u@}2Kw`'OX:jZ\@,N ]&%qfQ*oUGlvϞ= 5t:v &'ǺѣG^lN< g.? D0؍c/8vfg i<Ź9Q|aL(婾o" KzE"kqDQWZ?${K;FB-kVB; 0%ezF !rQ4]Ub疔9) R D*ehuC,h.(/]" csiJI!R6/q !9E8ƱFBdH Nj.f/jr!2UB#HVCܢ!dxemo38 2/e.q#q. NӜc 0}B1Fqi"W[o orV 1H5 2W*neB)$F[ ZT*=>>()s^בR@\ Z>tt:M<^G[N׿AbԴ1ض]T@`B9sf~t_@䐗f+ `գO1 s!< =mbКsig2f_m&lrlCg*p9yeY{ay )}w^nJJenn Hxh;h6s֭[_,t\\\tlScػw^"|V_0"qH툕CZ<`m2xHL%4QP35Xbۻw/i2+bJ(C*~b"ԇjIXV8New^$єv]}lypN妧'&&fgguY'{O@Y[ZvZEfkqqѶ*G3BXB =v/OZP2T5<1qѰtc%e}Fmn0 Rf ˘|xRoY*pnrnHBZVOƩ^5CBrs)KOTsM) !҆Q〱PR 's/f/jP6 RA 2ʋ8D\b7,I1^ގQ_@ F5'QS/N#IMA@u5~Jlr5|_wx3!d fKm* Q1vvu]۶ nttPPL5ZT$?J9s̙3߯ݻjt{w󝋿Q"c\.j믿1yXK.̙3wwȑ'vS?۾}nbbryffP>OzUW]EK(Qi`f/9@BP )}S<`Σ<6 YmZPuS_,`Ms>>>>>>~ 7SD4E\:^CA^!~pb|nX, ,xA [y 5m6OJd,5O?i) P]T13, 7o+Уl~'{ptR{/2]^QBqO=#Jb+>p=J0Kѣʿ βYU//V)zF>rd޻nۉuyt)ZoT;wmX*Z%`ٖt4LBT* p]ϲ :NA56S ֵQI $NfXڽ{OVl-FO84VVV!b-T0 ,";LQJa[ftȉ[mJ;w---:us=3zFqIV_u>ӛޔJvmFqӎ|[ߺOϟ>=OMM]uUCNHolllbbd<cRA5K'xBQ/YԱQq!(\hbقWoUBݻw_s5R s _/ujuZEIs 4cD@}ovvv=sl@\8B( `cuBdz^Z'NI9-/IM{˯/Iy }̲\%=]Y|2~nl~I퇎Q\R,7~jjn7SKK`z_}W{ߵ{W6ea0@ܼa* 4-t:fݎ06OczO,JyY(e}Dw][4B:.̺jq9y0ިjkYV?/O\+`}Qn#GX:f}:VZhtVs܋^t:/|aԙų ^?s4_B`}m4Ci 1~zȸ'9@&zo'*[H#QKƘu|4|>>>. p;I[F~twrrrm )%qOMMYez`|é(!%5M:u 'yTquC~y y.`x?*g:??)뱑jv8~'|&%0dR0LZC2$ǤHi^ P4VHaIH^ b!C-zP1@:  x 15萠2Ї&T뮳m _eգ3AdgK)[P.ItC/Go'=Ofqыt=+-d8 i<^ 1t A,@ P}P.~y)rwvSJ0 %4 (,=zÇsηmˤ3aBf}9G9}׽MM ;ž (F@(?|eɰlVW=)GX(;sJrchwDZ:oѳnV}DbGz0NZ،A@|B F/O)0pXI1D\ssۣ(lZϫt\E<ϻ11K*O_8YY_C~q!RR^Qb7U̶Տ;s; `#ۣ_*@d2탢B00`7:#nܹsq^QԷʨ`^YPճAzh6 ve8Xx/dN0t>‡TjeespEQTTlh Hj<*j'"yN^ zJL u!ML[S_ aKY4d>QQ|?D;[lMM7Y}k_SA6bhpZ~4ͽr9۲qR; t:s 蝞\7Τi})D uFrn$ڴzEz}aaann.J[8jdL1˴(s\>NAbGfl۩[WWWϞ;硉E4ZDVMBH}PSL :$pw !q|/ۤy 8R&LiF Q@nnEC???PQ!Q,|:JhB+ϨaDZ8Vn>}nKT 1?=ѭ5b 0JJ !,!Q2? ( heYf߯{=۷<F1$\=~4r4 ۶SaJA`f atzjjX,dN8a\h0azd("=5˂ez‡ )o1YV*BR_l[`խK$0n1`Ri cnZfRiy߳gRG}ᇱnQ<s!DβLƕ&&b( G;mfl6k>B ,b0|g>;qٳg&-Nٶ!DT߈ֺas({UZN0ll_TidZmyyyjjʶm;ĝi J)4IX*BK Nc]ĉ4TV9ډP1 XE?#7G(FqG4G1uFe||]S]G2]*(>{lTBw5ޅ ~{:q@s]׍\.à FhwUi\.g6:(-Gh6낝 .0}:j)~A텅ѽ4Q\8Cl |TAQr:n4덍߿_d|m}c^T*MNNҐT*e 4S2' Ŗ4 n3t*qu mx0|2~;T841P@v<lNRp8Hf:=@$:(D* V:3F1 gA,kee< T*'>\%}' @2 `zzzvv2^es}@EQEL/u/] (ը7lŒz^ ߴ~|Gw(F1QQb=G?mi?c'GgC4'|ȑ#eٽ{0NZ04`\Mh6B|"7A2 ĿcN<)eP@ 2M?7| ԩKRLptuBf?0 #cǎnQ"<VYv^'Rd㑧E >g"DYmP,2gff łio B@FPVV˶T*EzuB~4zch(F@(Fa_煷ʤd/o{?f'OLF񤰀lyP2lь8nƣh%X8#q S)`V u]HĈ F1RyrwHjalxd/@n?EQbB)NP0vVh4Mz$ rW]u!=1O-}= dI^HP"i|2,bEQ?8c:#jUNN3tf8NPغukXܷo^n]9utB(F1F1d13r΄`kޠ=S#,`/C=T{n?>zphll<]׍`'=Ⱦ%2Y; @r4}"d`@]cAo6{U}Y:toqVWWc,[l6~л!\}G(F1QQBh|뼀)w/2C#,`w/<o)2%$Ӻ@,KV`\jB rRuH_~H8Ra50!2w YWcrBPe/ȍBc+a\>(߮ Y);dl6+#{eYugϞ]SS8y|^P{BP(!@8}!?DCX56B.'28f+x):t0%Kjؾ_U6 ÷XY,9,\uuuӦMu]!dο/BUÅ_p!\!1AuĄ_&@ܹsǎjH(I8/g'TfTj~\/q1@qPYt}!O4_DSZZ(yUUU2)/J .Ca$E*mi<FBP(!@xZ?Τ҂sqP*I[ *)Ŷ]v%K|/-X3f---f3ms`ƌ3gv.GcXkR7BY&@: r;9B \p1Bi~n ]F2Yk2Av#S(l)h4ZYjdJ:+z~kQ9c,8̓fu= "$򖯜X.CdҾߒ($?]>HӺ֚ T*/,ZIr"'Om}}}WVI BO | Ơ ?ab7Eo/LFJ P$S~ΝBK?l^ft_{a]]]($H$,=Mӄb1&k`߱,&m4[N%B}Ap &O ~n` Bev1H? BP(B~.Fe9e_ѕ *<0˜J!<\6>`y|Zfg^i~1Q9J%?11fSBH 777<" "RR B  řoڄ>IO0ƀ1kX >9l?%(Nuw܉Z1/&oW6Y[1*GtO)ᵲw , KvI-@F!@p1ynT* yX"!X,J_^zҥKxTB!s/@L`P92@`>>11*7CZ,8R B  _׿8/kVX|Fi۷Z@T+ZK!'e)A( R_ ˠY,!Wdį+>< ew <1YlN.A -cRIv8m;g2ՙq˲,:|ѣG0P(^###6l?~4K<;~$}Y, '[`eATg* U(cdlh7#(Oض],L&H&PXlkkkii3<߿m65H B  ř'>Aq55x1r TU瞃AB P4[… *mˌ_iKkGʥu6ȅ16M3Ha%ʗ\WA% ~@VE.y]r\*rOV 2 B}}cccj (/T*uӧKNNBdSq^2PHzY# B0@!\.P)CQ/og5*THQ[e\%,$3׉|>LrP(455P姐GJG9r! BLk~Lp]k3A0>^ *4@qڃ;vtuui&m2_J䚿@ Ȉac۶I+r;@ P^Z"8s8~?fĄT2Ç c" \Hj*/t:g!Dmm8aE?\ȟdte>BHjX`y  A0'0 d"f /U>[wo۶dXQ⅜[L4ZUUF R@dt:-twwR B  şop뭼T:6 >9XwoJ P̙3G.I[9˖0 I~_[2-߶푑hii !(x:ǧcc@TJlvddD.uww Lt]W$\8w) LV} ˦P(ٳ'VUUiʞ#e~t/+idqECF\d PR$d2YY&{"?b8>>y^Pضm BOGmCU\uX~c_C.'s8l޸q#ƸC4E/3r@\9qepy;<<,h4J)]z!rNi0.^/rtzddDV+ ei:\ SeC]e}}}B'R,m-Zd8y^6- iB!0d"evp'ojBBM9!0H+WR }?s$ف϶퉉T*%KVcY&+2dST}}}gK 1Ɔe/(s^ѶF T M==P*A] ]G?xQ֯__]]DdD@>ۥ[ a\`0({ cA~|dx,[&&&ljFrQi4EiZ0H`0o\J @E+#@KKؘ788Djkk+w$X#{f;11166 eizc널cwd2V(###>l$)JaR،mmmH$2<<|H$(+JNc圀Q9!1R B0Θ@0Fap *JQ@,2Lfllq1I8„i,'BRۛJ֭[wDk.8Pq 4` 3TdWTum(O4 -G]qk"¯nOH&fnYjj렣{L[@*%@RPrt%7n(X047^-WiX&k lݺG&P__&Cp;={g'qddUJ!Ķm۲?|X㛐)38<D"H"ljj@#=}L&t:ePᜋ\.0D`J50`1Rw5Ff%P cLlvbb"R˚~5 t=ϻd[#8P1V)z17U86<(EniMM +Q| _Pߎ⼽/M3|*^9+!@qiw݅ׯǟw]Cm-jmڲ۶A.Ţ/<+5@Bڵk1ϧfY0dV 墙 u+{_.\(+x;i~p'R,|>l67wBJAdcis~`h~KBq_z y677jrQݯ*"Wb6M$נy~~eP9` `BX+(g D8 Gvo/_5&qjtX]+~u+X2OE%ƤpB4#H8{i˹ҏqV~)o?J!,keYo]e9sRC]P(2_ y_(֮]{EIs0 ysmBr2GNʀ Bk4%]M :8MI&K×.}&!kS:yz~\8B(ϧѧzʲs+ ]u0S'~k9O2Ty? DQr5xJ ۆLfÞ^^5PP}? DJՁ@ a;8 }?˿+WL֢dRZ.W_6n,?'(ຮ.|>iLs~-i(W=+ڕe)e]gZ_b1Ɍ`,X044t=s"W(% 5k.b d t][H@Q,+ʕy oI8]REe9@/ !4111<<秔w~«{fT8Y`8p)ƭloW<פ\{-YbTp>0<7jij})iia`ЏbS˿r-[DYT[ ,[jjbZ xlNӹ\. 'ct_-L),8NooѣGDccc4,+Fccc2:cIxjk֬u}…Ͳ,> 'qDHG] r)w3u=M^H~)&JB H4 ɯQ꫙YEu:#g @}^<|!<;Es,e_+!RL.%iH}=)`x8Wd]] _A2L;@ v]p]#oZ\S#,AgÇŦM [~R <-|>i"Wc+*%vsi͛7gsXBqn`;v9s3d'Y_&_F,2Dh|||||& 0~Yi2"mۏ,kbbX,J|>dАkkk[[[e𧜲4M0Dž & c _V;]k'w |q+l*eB<*.[F::hS>7uv﹇NF}w EŬt PsH/ŊNZKъb.H$`:12e_TL]UU][[gY%R 'PمE.CoySJfdcxxP(u|>,KT*ٳfQ( BP(ݍ@ v 93Ⱥ͕%"Lf||:=%)CYNƄLfXLX,VYVln?cW3儦 g 2T; (ls? PߡB;:ϓJP3^*T]qxq{/wBDܷiRPyK>_HrT*̤w+Cj' M,x˵5i+}&@I$xX, 3gliiBgyfΝmR)(g#dңXkÇRe@ ͜2~&!/VWWq_2[H `ddX,vtth4ZWWW__ +g-MTdQ ###[l9ۿPy1&Xnx[c<0.1@'*&c|9};ȪUT׵R ܁gh~={1b7B8`wwAъܶ c[G<qO>UPḙ Ðykb(.[d8<82춵>:sܹ3NB8)Mҙ4 BEI^~ʀ"SFoYO7@x<^__뺌NӔL~e@@#B/1W^fY}u*1XEɅUwc\7=;RNWBB!x1jzd 1Z(akLiP(N(!@q}+@?)XaTSI…Ś5v8r_ EŹh۶ H!@~-) dnkYV8rq`0(.dzl{{{,d2;vx'΁\BQ}}BN?2۩V*r7IaTUU,$9S~ʒD?B_#Pv*u\.m6qWP8pMV(t1i]Gٶ2MSp1V*!v"[B%9Y%()Jy, X(zzzl2<<|67 b8Ʋ,ە "/&]]d,R_OZ[i2I1&<tw6n[EO)D"דD$d2oih0&vGFqܡ!k_{tP(JP(^~+r}}8CŲebJ`L'o}ľ}bdDXx '*m 6l-<ϓE8ȺuO)`0nOOD 0 u]i؁q2naB  l=ʿ=%.t8ƄD̜ s18[mb:Q*m>Wgxdbi2_. !2ƍ{zz c.%FFFLӜ?>0 JNlppTjl||СCR+3֭Yf͝;7 RJ)%ˎP(` }B! $0:)Ȓl"%Cɜ]K4Ts$x50ne }qƧ@,L d"<{6^wt`J1hlKa7e;jAνX ]o!i MPCni h A G\Y(x&gS|f~6 8EXBPBB SO~A8G 8EpYmQ*  ay1>##s!-pPWW7wX,  wOb*aL&eI?߷Ge/R$_Bt]dSR5!"5Y phhhllgMR~Af5\]ުfїlzkxB@`|h(@z`bP0M:6 D!XdX&òYO zG8O=u.UTNB^mqehbPGǑm#Faa!@-RyN5ilD@ F9z|^ݪ+P(L~BH$ȲH/W\d" _,M+F$W_hgv - "t H>eosFpE1B%4X SC!EËEz,fF{ٶmbp܋WNB΀(LutD, !(E`1"lr)( Bq0Ҹ!%3`1_;Z, (*Ή'2@R!d"0 aa_9ϫ+FPBl a4UW,WFP( F8dtD(F l/)[3$+@ ki0Q!2KwǶ{g@8ΐW.4?xFcۑb3]X-cՔRJd9$P( `r9߲qʿBZ0_pnp!aBBF0bl d3s('s$.c`p_ 7 Bł|QY,1BX%)N?IݱeEeKyJ%Se!E&t]mm,ϲz/oD/Baka~<*5Q(JPyRc }Xh֒:H&rTh<)x?N|̜g#scq\{<38s΅@ @Fxʅ?mtQS( 3F=9NX۶yB44)Ja焗Mzq'Xn)#h|L%N)%PB1Ƅ1sg"'T x.mc",#?LLThp. %ǵٳ^zX2AB]u]@@i<%)JPyFGG󩅐[Q!g^YF&e @%^[ !!1m!8p3T~ $U-SbMIk BP(΄98eY !0. f@p~=bc"/P䪻C:O~86ώ/ -B ǡc ޽{-`/h嗛iSJe8@04M4M) PJ}IHP(!@qR9d2mcwz9P] cv۝`0y瞜m.bap$A1=B?P !L(c BP,!y8ڌqT\P.SVgO)'UeeY[M,K߻7yspxJ%bYqe޼ݗ\Ũ40ƚaf0 a쇇P(!@qƨ@)u]7nܵkѣG鴦4?Р77C, c1o; m18!B8766MM]]] ,hii!J{K*[x* BP(^ <ϕZ%1kK8>N WI=c>eL+H.LV!@"448oxp3u]C(!ĜoGɲ,˲aZ[[ s)!đ#ݻwu= JaX !$ˎz{{׮][WWWUU xP#F6!܄h*1K1!^xx- fMSN{. '"#q +TJ5:><}T庌1C!$XZצ _ʔrULJNH8y-=_Z@)L700`YV8 .4АKnڴqppUW]5o=LLL.[25U(g=) ~~;+n?-?m޿}fW! <&aF3 r#Ͱ{Y> pF 8\ n\u܈},0lS!q o49 r@\x&!8fߗko^7UƏ2+y$ΙxR ( YD9ŋ ; BT Vmm%̐ ,˲msnYVe')B ?"'QuJִ i4 7GGFFf h/ieYLRLV|M3f̨oll(lw]. Æiض56Ƨ7 9}WOw^3' ;?Ͻk_gϻAc şƎXX,ao 4͕E~P(T*l.JƧ dg(JPd}XTh2Fs0 vŀxUUK/ݶmm۔j`HLV<]עшmۄ֬Y344iy+W|ӛt۷`4ߺO} 3gS88A-ri wm%Wkzh~r+5„a-7t\>e}+:mʼnXz`/U{qCÆ]~q^mdNE{'߳{{.}sۗ~xW%sl{C/ j9L!޹ov}};>etV{_oV]O)|o{͙oM] eW+?% ,YÔV80_Lb1۶mY֔fJ P(xBƍW0#⺫;jjj/X,޽qH$9B9#5r]wJ}q.[n], ʢ/JP( 1d&DDcakǵZ:Wn_7_h܏8^siUʹfuC׬:q'<_==,5Dtsm9xosvm5a9ks=9D\&hIΊ%j}DRS;-hwE?eY>+uջd3_0.U]K5oxLw.\ FP(V-@:Ư6Nri7EFeh¶m۶ Le(JPݻ%4O?N=6lEDGGyE"Kcuh4:С|^Ӵ=BAnmmozP(9inn+Wic Q-1#w{W_Rx7w$XUe={Z]VwX.ҶXDŽ %BF슿~5~7G'{jyv=/p雾օr.*'d!! "Pvm[qĜlk҇-ƳOY7F ^n`AjEM}'wm2|Kk0srW]W?w=-] {]vOO>zM& jBg6A7i|tTۼY"o}t]!ι4e):!]T($䳟 !J !>(!5` ӛbG{{{뺇wtt\xՌ1_xBN)IFK,"ӷm۶aqqҥf͚%5u ūfrl7M;lؾ{߶H_?_&8kZ})٣Ju!yJQs.sQMu]*!@Poۻ V8cU@ѨYB;NRcRsP SVɚmEjCV E0`|dzg tEv>@14 |$?^)!t 2 !aD"YM:+7Tg Z~Vۮi`Y8 &d2MTA Ep-#Du΅MӤ*-^)%վ#W(^S`oݹ\.p!@IWJ=`?qMFs:cYwl+FϞsl &W p7644BTyH(-_9t wvBc,Umvu]ƘmۥRɲ,Y/@)uXPBմ:]GB@((JMdJCeJ*^=ٿso,m u:[>]/u4 FtX0e㩷*%3\.܄y¯inr广?HDbsV+Mرc׮]㴵|C0MɪFFt6N 0M38cYc1fYVT2M1&V)JP(^9!: =/8.cLvf Ze َMS[N_q]A#FSr[nǟˆYHi  x!:Ew;ͣxg0q]p(_'|w;#g+`0 B8= @UIiO*!P0'G`}}jVWBH$V{tT8hs߿7J!@m4Mu9ZeY kBah0sADZd!@0Yl˥8@Hl3G"/(Z  ?Ȱ- ʼn?#(=aTTm /#1)"g'M)@E6N"=&:پBm;ضwNur]i&@YBr&/Og71> {v;`egBhgE>B;-?&/Z9N: ܈dqp/ʹ:Bs^[tПLziO  Xha#8p؛5+}{ ~;`F (JRɶmY>q8PBBq*А !4 cTsC bQDx28Qd`Zt@X`Rca &]&*-RYތ:VO֛Ct"U8L9 nPvI8a#@Z3<3y'|*(@4Y}g+@Jvrj T^4Ę)L*Uhu2Dwܚ=aYεE9#T5Mk SĂQ.q wci1㋗4Nr| E,D"scL<xN&APҟ1ƔR}Y)@ p1[ aCt3ccCe )rT*JTUsBKqBL ," @+>!Ӵq, wHg UDpݻwߛ<@-2k5v֥_wFH} ˒i5v\&0 >qd]~;l7-\wyżbL߳>3y'  8dhckvD_whe0ƅOݾ8}ъkW3cKzg+\pq,;ijE<׷st-5:w2}J,jOk0䅗\O c6m ;!ٻyC ϛYeȯ6+c={?;m!ӌoW`\~pix-;i1oᢙQq).B!9Ds ky-bxCDž^x;x!A Bl6;44*(OqirPІ W|-@40d5+Ƙ241RTCGq>nm*ŔN) ^8kjk& @Fd@e&N8)vsB:IDqin{[{ݕKj'mګ4X8č# @ԡ=HKssML#Xlz;WW]u]0~";B3o=oX86@FO7-/z%+7v^/0o !4T>$8F{6nO5sC'2 j3?ݛ<_s`͚7.$fnY>sU`:tp5/rv- 1y#۶nZ%p_sό'BvM@Ƿ'"(zR'QB|ECu(!L~PT$&d@(P~'@B`)vN9{'PB0{|,+ @`18 +~p l&f_efVxŢZ0Gݻup<]}OGg80ulyW|3[zpuM`tC;cn~GXzG[nzXr&8py Q6 YӐq2\NٜJ[-\ j0D j. 0@!wwǁ=}0 3fϬ iJ:xdpum3Z,c@@OGjG :MϚR-xo__[־%B53fw4Ƃ`O=x` DA$@gG0S+Aq9c3?{bMwSk=U77Nyk[=\˲lg~zΐU7ƕ3 ׵ݏޕY4 n~es 2sÞo?Yxӻo\!ZiߺCzuž L E{: OѳeW_7ZT>sqB`ǿg%Z^ |{|)}nٴ֕W6ۮjAsHLxd7꘿tYgs=tO/?ٷ;ܸ iԎĮ;]ЁT1$c]0&0ayyײmBe9Z!(R*z/|urr& *mw6[oo!_>~׻J~~渳{?#Cp{nZ/Ӧ'h&7֕F1 [POx{~u{5/o aktC>3[ ^]xu:eFѱޞ1қ}ƚ#_k|/^9 eٶe;kvG֦z6vՒa#m=8hWFJI.`fCMe \Dzmrq;9f5˓̪ߵ~ /͙\0 wp͘V=1tp=IL&0xp疾"AY#0 Z*Y!+JE׌Yr<`V Ţpx_i%ƅ@E͙qekpj~ol>;_߳\l則wҝ``DvcX!͵;_vaEBw>/wX+dyc]J%[%8K)"Θk{\#$.wJWAr*\QOUm88_ãn=yƛ5#R(/l !uqөQO Hў3"|ph=Ɏv޴Zy:s7n @\s]N& <Z<շsG9ncŪ+u#Y'1Fbs*\xZsҙoԴr =-Dt6q e·=ܖLcKhgp[mi@W4o3 ښƼhU'4` L0QJR܉ʕCcv)B)Pu͛=}u)B4M` &1AA0B0WƸʹma!CR;%w/D]u2Bc:$G@H$|c qFP G(ct"wB;V.7J66U1 xl# OZJiuuu"PB<|>?<y_ؕqb(YtβSV벷^xÜ+BP /y;]5U-.pö :97}nٛ?=xmu5 2RY,'a|*o6pm[g΃iAb&@L0U&#4؎]Q]5!C.r4O#]cAH @4neRtR鞾G:P@Β`{7EmxFM$cMHk!^GGDvܪXH牲o)?R\a tBbw~_>ruRrR5&J~ɞ+1#;b5 qG3<聀,(5XB $o``o/.ho "o0 1 +ﳐ–6-m wOۚu՚N1'* qω65Wnٳ̓%m՘! GNX(DAhHI# vsΖ*Q #Tf bTOl=zHWr&HxLy!@cdXd:ւ!\:jC7Gpî3 ;wӽ}tj 1A:QȓHB %Sz,yӒBEƲPI) Bx\ y(s' )O$5T(!@xqti`T0fR9A0#F91ulhX“C ng0<kmtu0d?;#phmzIWvo~dlfsBӢV~Ih4(2n Lʜ"(``޻nbd.MUţAbN h9HpÌu-]Svpvf̶&Rxsײu3Θ= dL Nh_ߐHc'd6 j:io`yD%=7F=31m85=!Mh`$:!\.Y"A埧@b֪?޵~_gVVzhtŭ7i-+.tu37c}(wqw `vѳ<m0gn!ZH N_ޥKx7DRq+:/o{ 7,LJU^ٔSNh]5}<,0/R׹_|c#O/n 3 9@=7'{tnN:W}Zݗv]m1pE [1FB ߄>5lc)Xhz[Mwߡ&^ӚԘǴPM޸qYrGqnsu"%ycS‹׬nJ ,6sh}s &I.ں4;-^"`. 0)ٔ3*nf̱ڭ2g$={>yICwU&HHOW"IMdeU- UsQ]/)//gܿ溮Y8_H41;pժj({GSs]=05,]:|6JP(Ee]1*"@q'qڰ4dZ1`8{:f0$\.f 'Td'2.F"::ǯ~}AVV+ SSǂADzc!b!qH85<饽 AfLM$fJh/ّk&%D`0 R <DӤ7ZU]_}` <jO ̱K끀0e< DfogWuGhw.ð !zYe Lq{B0p&<9hO<#gNP(-tE"Nd Vr SJ {UJHw]JF@)2$8,(<)Y%fD3t5Jб{8|llqX,R!ҙ߷q1u B6MR\1 d45 t **"@P(N޴ƘMS 0ɟe (/- iD]Lz7u@swC=՛΂V?ڠ\fEhI H8Y0DgBNh, jF0Trd%jB Ѝ@0Zv\ ?i"M3@lҌ 5ݔ;˃  wdu;x}NMk]sfTȪƄB/V, F#f.Kb,㹹Ӓ\m8Du="1#Წ u=\spYˁyBcUD({Ҟ9 mJgsр<,4Ө ǀ"!{H2\p!4llb]4ZN- D)!LNj86 F(2!!EcZ%kq2Ca)1inF47s!jQMޠhFŖ|XJ%Dy ('#M7ɻq@ G,G8'F@& @S #dyc91۶=SnB2DuJP( !t¶kI!C B'mwlc' 0c;.o}EG qBr[|&#x).C9l5_ʿ @Xo!tl);# NL^pE-={FS !&3 瓗D L(N q&(41@N~D ӡʇ\K#c%Θ˜%8@ߣj  F[?͊&b: J)y"D C>ahqZf/&<6QQ E':@l~MIBE <9 @19&6w.t!ߖʏB Ƭ' 577+FP(JP(3ZZQlωƓ']\GS|ɍg.oe>1 8nrUB<0 RDN%Zf/mo;  5|Zk:!K]m0uҗ>eeb)}Ldry(Fat𑾾J8( B  BqflY=}pIyyZ+腷 6k$K×M=@1L-6T42ض헏VcS=@_Uq0 eT_ڱ ![e2-[]K6( BBx8յ.[7?xg7CAu(EeY]c&b1777/Z(H(!@P(JP(3DwޣGRImϢ  pyc-B+gOD.Gۯ/"N>=WT( B  Bq&h!BMTBxLMx.BP(P(.[R( BP`u  BP( B8P>h(4M4]u]4 c¥ BP( S(xHl&ˆB@ iB*oZP( B! $ u- %( z{{=M$@0 BXN BP( +`JX,rJP(!@gGŚۂp(T BP(WԇthhxppP*JP d!c,_+!@P( B@!XB Wι jV( BPb4A CT( BP( |[JX(_W12Dto~xB(_۷YRN;#G*"ܳNY kh_~?>M?X6}??ܾk-ԗ~a3&n>ka@n%e:=.ߺGlغeu79w~5S}n$o 9FOK?a1)f|PN&:锣1+;x9[kc;Oy`P mկ޻(UXP(@E(tko#:W- _4F(8:E Rxf05>1.~puƸ@D:) G'x{>/̌sLv~w^:.M$./4ˀ2bt Br@hPHc clfR`쌵WšM?~k?OcE>t_oC/f[P 0uRd\`@F f ƾnqTbz@sE8 w &cgQ[I/q!&bf14ݬ &5pbd{<[j֜K{.(Ow?_ݟW-H# bv 0#2g,S# h@/*Dj0\mk:|D {xvai@53ȵJł25C80{c\uHV(JP(N2 ^\OyW)=u,ᒖo}iˮ~es?nB]i3C^]K+xӴ=݇V˯|ռg{6NlW>}}F'V]KڦM6 jx=;vjfF<\wMέ R@pF_>sϫm\65k k|ϳSw^3)g"r s$Y8v?|O׎yS[>[/ KcL{@o hmު+]M %"Lw-moLo*ۻ;'۷7\"|`/ikS1>^߾`~c@׸Y\rqy꺖`0Q?óCiaU^%-@SE׿Ƈl++?W/0N_{cimkdv5<X#nI0V7kn:(jjz` O),y"q{m={d ZEJ݉GD>Stp׊X9+n`VBPBB1LHooޖz^r9cSrs޻w`JoDtVޝ'O~p9׏p5(>_ɻnvQ{+`VT疙y}ށ.X{b11Fcbo(VbA{])"$DCfݹss?{C+7oW_j۱v:P=(Y={&|;mќ`=r>P.b_n^'*274%g^uIucԻȽgܗ_zkO`!sܠ?o0bc{aW-#! 3,:q }vdr7ǗNnGSݡ+};Vl]99L#g\?rǥ n 77y3:wnGfN|`9w7`cLkVKx;Wo ի/E{)~~f$ naH nIsw_y]v8D1⇳=|ڝ=oPh!~ЀY r~[\Y[L91ݸtUj)RN}~n^:g7,7D_2n]wed RD&c̙le~sE:rƢt9.;8{f[.nǪk{5s ʦߵ8kXb2.1$@WOv[HGzQOw|cy}M@1{^[/7:3$4Nrtf}ɄIz?=g MAz͖OkRj~g)}w=0+ߝ~?}c.?粻^tES])6t@W93oMGl*i|~fS˞p{dR[zR'a۪_mjN|̍#"7r+C[ئQ0!1cJ.|*LUW}xsI}л_kҳ֕7ݏ>o>xv%3?ph7 >[7Z :gh[~/ѵgn`gkAGrn߱{81Wy'| @эHle ^e7W]J˾|}͚c4iRowfvTl'093OO]c}+|6wmS %%)U-S_xx-W߷]Bl[/z>ןrsgUql4}E]uVFa}fE[;Í'eRpWF{WgMEٌ 7UReI^[gsgl8PT%ֵ1C!B`E 6$KXD{OG/[5}.R6R B/ytyó&O/kJ+(;k v"HTs۫qIY^;)5zSӲͶqYny /СCqaͲ-,7xؾ= ].}ϿYQ.͡bI W a/ NjXעA_=$=cfѡgbT,jeM<$ժЫX"?^fxOBZVv׉A X΄D3=7?ɟY8摉Z"hT͙XvwXyu%ƄJ ϛ`=GkM$Ƹu4h.[={Ň.6m־}GT %&+.Csmfwa 5xe:/duBTQ^W2xؾ~Go viWd}JCd$b_4ne 6޳P$RJ(UK~9scJU$#sftI7 վc1w/U r#b!mBXh24XJ[B B 6K6<-%9'#4M29ͫ#n7Љ{w}OݱbZ&9x/M:c 8R )wV손أ&'MfYda*P9Kaf溮$}~~}^wvY=Hr1n'KKx W nqvEkoOڹjF~gjfþ[Wx.䧊/gns䕏~W-YgKC}ńkT(sVñ o}4;zf>Q\?8˻_Za_ض/>`GǾ^i˴^z}͘ZSyԯ;,%TdҎ(=+6⼄ ;}:%s#8ueYg3!90n1 lG -ؑXtM:OF#Ǭܼ΅܊Be(/.Ԋ?U%vvtH :θxdR_{Y,Gt&}u)G,&2u?h**ar.@0]F辍X!gʱ#a?SUg\Su (3Ƌa%=M{z`zzQO>1s, ?z1g8ORx/i3r^݇?zܹ>.=?>yARtz9> \J_g_7IwMvᝈ9'`,\ש||KHph(5]Q׉>U}ǟ/#$)xK#v*7Xj&$0B39ͭQB074rҒ<~G7ÙHñ#uIHIKt . 6΄DKSPUQUS]kJ˫BSҲ4 P7cc&`͗0 1BBpKU$I5 %fe \;Ľb[KvRZYYUQQQPPwxm?3CÇl=snYV(jjjmnn朻\8M01|b]O@' 3'e2.a ²,KH)\IY>Mq%dĥ`aD̟`nTwj^|BBcaY؝P9KYaqKLLRrdB׾sB [ye#Ob?q]g\ ŕ ?>uf޽f_otۨ.3YBDLΝR0 vz !c e\jdl܋j.Nث %3!cR$R$J,Ⓒ2&e CM˴ );Ia&8;f9peS׵}4)%"J|fa*sKˎGH$d&CLbWB.I$B$caɘ B͓iZUYL뜔'%  i2!$\~,L)E)4L4-N隖#`@rC7cgcsGB#a馐!BRe+)Sr^TQjJ=/,ŪC 1" ~ĵLÂVF<7B e.!@ &3#nTs@Xa1Z>܊2okշ'Wy`VHGnjY̒?s@p+aB#G7\#a#K._"Bx_@wRp:_tG>|2cQ22"}f!|bu#FGNh !)1[2 1?e-SJ:&%"-e 5:a 26c!C :CY B˅F^vm@9}~"]^wAJ_o7Lsԙ7`n:vl+RJLpu:!_"sg 1C!@"ex31Yfl[ph|S? C 1kq"b1C!N4fmL/$`I!w3$gVL<b!;:E_,0)bD@ ^(1i-\ cOfι=H))*?i̲m 1C 1C 1#bPiii]]OxMMSsnc ڽ|>)655=ztBii)))skk_xi2Z B('בRr.39?|5@1bIao}2ʯK퇬0(B9/B؜ll1~ndb!F4ݻD"$˲ y9ㆆƸ*~ cvm"z9?d*//D6@u}ǎRJ OJ)ϏWU@ PRRRWWϘZh JbbR~~崬@ `Brq*=b_ň h)Ǡ^ BRJIcǓC 1#b'wnG]tI3ĉ 'J߷o_[svYgTbŊp(tu4/_t:ΟEYUWWz8犢nذ1ƹh%XQ^z\$ ["h%,!%4B* yP@/Fߠ'$k.ZDV={n޼Q!%؆NH@!ٳg C 1C!9 G"H4M\A2q$B|'TLn?A>B@0(RP%|ѶBpUUsss~n!88 Xȑ#{衪 G"6mZxeYC4&Yɰ%CAM91p0BWCqJth)1!䧍T=@p.%F;pp8lREs99t ,p8pRB{s b!'PHLX,C 1" ?0¡aВuBI)BR˲Z70ƺn, ,V;l-P0(lwOT!Z 4-TzϡČx""ƆEQm۶WVV=O>ANsu%''KJS-h4xMWEm5x` 1_ Q4j_ Z2Inţ!J6!lz(2 !+B jl|eCWWWۓHSS5k333ǏJIDFQ q͘1cg}md1<Dg@'{tN7;V'u`@c1[*&Gĉu $ CHj?B{wYvM[޷?fgQ=a-?t6²,]540B-]~z=vNsrE1 30-AQij󩲸SFrW#b8uvKVƤD  90]xHc8jGTJT 6={feg!@-E # )r^P i+PXd҇۫jB_XtbznNmlZ4 ιR2ƨرsʕffeuh-(0M%,Z8v=l߾gA)=zTW7}kN](^JIСCN5M>'۶m[BBR$Yn]߾}233CBQf3Mp Du,#iC% 6?/R[[n(d >RqzLm+cB eK),ڳw歹+eڵkWTTRpUUEq1!KGx]jnx)ZܮCVAfhjUc8 /4PnE&P q)$ťara:84O~\RRңGSw޻wo^N3v1Qg8c?B DYYYZzy7UU9u]"ȝwIUu;jdI(7;v~d<> @χ\Ki, [0m/ƿlMH@-8nܸС7zӭNe튕r6lBza$ .$"OhH))c,JU+WjZ(ZlYrJJBB<R R2.hecPf +Ós@nwC+X~ղ2rWRHd'$8>~5鷹+ 0hUBL-)/-jMGщa%{@Hq\?C~P -?G}\mW+e{`˖#[ -z?apKI)$ Zn;=rB4fs`ivR.N \!9y|q%_JRӡgĕֆU3R] 9|8)99>!X6Q#;e7ѡg?#  Kni$"eq1Ѡ!9HLN}r:PvS~&쩪*cL6np`~EU`Ϟݛ7o4x # EU,gk |oSCv[h4ɶ# @!!1m] ZdH/5m>{)l VbxS;{@J@ہd-`0Am R"v jD @@ ѷW%_E=,hȞ/ \{6%mN- ji*Y7{[>0WTMu8-)`k#k}G$c^w 1Ĉ~A@ !vBO癚ZZZZYYeO_?ce}%m$K G5W^{ Í ܈zX EZDbj@P f @J3noyIt1$-%ԫלn„(0-_eT0Zg3~[͕%ovƁC/r 忽́Ƅug9O %8ng5,PJ5M3tC ( @2 K-MF'x*Zݳ [p~~SdWW-;HrXff5Z`%&Ĺ0oY@!ow NSB0 BI!6O")%gBH0%!R_ FF%L9L0!欦pħĻT,G&Kι?&\@H- c{d$IzSZ-1'9NSp4{V*ߵe:߭sAG\iSE>!`)EԣR A#5Gͤt[k,?QSiIo7lEy&Uk}1>1E8ѝv&NJ!G,6b10ℐaȓ;0& | *w>Tm$v)Wk,ټqw{9 - KѤ-ȊevnY{;֡bLѩV^oSIA Q !;sԩSVvV>0LNЧVLJ]Ni.ڌ1mI$[B9\ɏ]#BdLʖh/Y`}G҂߲?t „Ejk"?'-Q2T3 B \ri_2.55ޣ xR|Tyi B\J"l"B 6)zGaL1[$~$DU+oAWB<1FGEߑ aJ(-OM ΅Bh+%P[}+gt %[g7囕[$(1-57cF M*ڳ{Qf KijKvoٱi}]ƅOLwfC}m9g>+j,{Om0zM{D'lBD!CKy1 ˮi,%o(ݲbG_/w,{ ݏgyO^qF%w:x)Q Ҹ}1+Or=]80ð8HC2J~9ˠ25Τ+ܷc-Mq;c!;e8df?=?{n.y܋u'4(y~WA$ap;vtiM*8Zܒ" $X# K4VchU])WWH8zuo.)8B^}ؐ>}νzѺPa7=.9qdK;@Ju3߹%]\"$M&&R4ܲL9w*SSȫ-1 SQRqnV}6MSN#,*c1?8oƴYU2%tuS(;}GOicUV--=n!ӆS<Fᥳ;ZT$ߵߞyIr)PMԯ'k|q|z]\5"-xH;+\J:2o"(ڷO;6#P`Ճ~viMO^ ]/n ~h ָ2=,^w$-άdM% ĞnƠںq͔3yz5^8W^Hrѐould+l"B뇗 op=~sn'\~Np^vՕ}#3TGk$Tqs||oy"/->:-Ӵ$d+ xŒ<|p_fAp;4pL@LƄF!bD@ m*saRUiJp2C^޵Sk cXY{̄;.zo>?n]~n|h1gHƸG)w,{gUX(PzAƤ)\-1X0}j~ީ9R u`0 ,Kbzjؠ6R%,i`mc|RbB$HD)2,{II5WtYoWJҽg4xa :bN 5K-:!]EmSK$I,[Bȩ;VM}s湃ƃ@^~VWI=d#yrUD !w}<{l~Y7o}h&G'іbv :!j?:J@"i4VT R]l θhE Ho,+םys4Uc_j܍{wNae&jLhz>zgIŕ5pT3wj, Sj)wʕfI) BqnՅ4@XC7X[ )@p6Up )\BK}ɜξegY;\^RR}y~#$yGޚ?oMu5CEikf 5 H -./,=2Zvl܌T6X B",A*<뜫&^E_Lznأsj"m7"9 x(R$I!"é)itΥN`Ӓ@rd߶+jN3" g%y| X"dpK!'&`laXa[Rq)NnӲiOP͗v񳚭?`Pqɕ{}5cR nX-"(߾a iYh֙߬(Ιn8DLlp aL)mU?d~."@BB @ۆ/6{Զ.cL B(!H$Ul>?+{渍#6rҰa19*LH@Duis %"fXu8U"n0 SV5 ,Rp"ݰMs953##5;>>4ozS#%p9 A uCK2@"ʲ^tZ)/NZ0}N*#cv#jN !P "SMs84ŞXP`\"jnF03#n .Ըx .8\w* Tq8*L]MN]>~ܗ>v&uԬPؙw_j)N&rhTRp=iD\*`16z=-9 Q%j3'NuI R:N˭8(K馐R?m[W\t#m u=odaƁ2 5 o~żv#.Syk_ykϷ='տg֤nx[Od3|hʹ>/vΡn_QC !0Kz蹏{w`U#O}]sj3uyC/nDLq CF]|P<ɮ¦U$ekiNHRXK߮}0+bL 4bC 1" _TQB6K6+B)Rۋ:)?]54cT]]sСvI.,tCJ+ܹu]iZޏ2?PJ-{{o{`0Kٸ<{[9кВتg'.ݘ=8gfU KD_ֹv3nɔYÛ:kY]sy=|`aU";[.:3d;}:m憠${y㇏)N(ŌKv֐C/\/,f1ee)9e2*̘7gŖCsŰ>tx͗o[l+3l94gҘ37%vòSgm72խ&Wပes`!O>€twڥhQi@Ĉ'g~izQ 5USr?Cg~{#`^z 3 w3C1 :CǿϞ33}?g eb`2+Ɲ} fҀ  d3$rdG79qû'=£z`XdY\:i1'ƧX"  쁽Bz@7c} *;8cՒ SME$qxHox'xzUqq4-S0-I@ջ7Λ=si#v'f ]b&3dA.!Bx9= ΨZLf'resI$c4u >doQsǵ}w|8k%sXX'"5Mkz ;IÖ B  uu٠) Ĺt"4%KȈ)j\@UzB a~r2[F ]4Vvb)uz!Db]&V>cF_r-0+puf-Ǥm?|%cびp/Bv n^1[+.r,9Hy}Ӽ*7",s˒jP 砳ϻ0mJnYƊ5|g•G;3vkW뙫wTs:!.`Ѽ@(yy8{۶/m2 j.c{%HZ5OH [y#.lofү 7[is^p;qz&Sg3!i4-˒g̲XpuLoس}>ycME.^9`ˈJ(kG3Wn>Pr^]+n?`f\sɴ[k¹}?_N,ޖ`eIƘi4- ʍ\#ξdH'Dh!Mޚkxˬ5,!{=EYHִQ;;۰t}G!Y\x]1]\MSnUKnMx9#z3ryje5ӓ: uwy ô %[GT_3{}5ꁻ3Qt%sµwWi39!{葆@r!`۩cqs%{N^ZUd(hƙLt aLe7>h0vP!`r0D]Db<b1$B0ƦiBk?>YhK$ BH(?ZثEESC8 m޼9>>'زe/lF߾} :vv [!@4O_ee+`D B@ޝL@D2&:X50&-j NK-}/&:Ұq:׶5a>F 2- D+F@Rg~/pEŲsT>pj`׊%w8\`[jcT[sR6%J7M}鋶gx9TR;w%jݝ0}hLYBHf\p,ô-߳uU.}G}X5P]MF@ܰj/OSR:;W-Syw_;'լu/ +Vv=ۉ}ᕷ+Iv kGeg=08>ph'~>*\\paa\k[pF/p !%a0Be 4:Ô4˨',[Uw{&8k"Y2M]G%kl=(e,iZ\br/w[qpUw]9v &$Bpa ,#:6 a~6 @m( 7۳ 3p0  ؕTٿgS#}Dt0DeoOigޘ?]XCl6J&-(h] RW:J>`-kVmk%$,=i0`iXB2nH=&4 p8foٸ}7^hb^HÁa0&8iq+ovJhk&2MʠaV$#ތ!J삋#" [K)$L4!P(:cb$!Vvhoy9}ۻs5Ս5rdGI"FE0ME@Z4}fhF=uF&hSͺ`*Csc(Ԟ0mnL'Z"dNĐQSl6זƽ/. .Pq3ޙ{iy9w]WU% `BJaSri l\tG.Y }x\lWɲOY\?pt^{R=);GpmYCK:=͝_'[ߛXYcbKn0,A00 3Xvͻ8pݷi,Y33US%Wn #ן=-Z(enX\2tS7 ӣ:oY<+߽jr2`LcוV Ih Ghpb31&&c8rpmY)zd52´j+m>tN,=OV52ŧx}]%;Tu-L޷7c%ÊK|׿WN"6L!!!* UY69wU# lL9_.ݳ ,fF)Q3&8H.@zW}M#묊Gkjam!&4ư%p)A]DCGPJUU%`:GmS(@HIdTm\.[V%Bh8ڸisD7 ʪɓmhhxG/,Ӳ֐Se׷i!j 8 ?5O`ǿ+g붪 Lhc#E۵'-Sz;/yOd~@f-DBcYRa؝}r͈hh۪@>ZS]?얫<7q;O>J^q[vg|k=&!+ʎ+??ա_+|m\xVWo>{z4䣯O<#$ գܮ.`AxW|}NjUMݲ>'o.㮹v\TwCan&./H>>]jh;,XS@w~ͨgOժŇn(l[5oSoO|pU]UUd1t?CI_}2{eq3qzgU:hl)̔t\)bM.s!D'uL#!UΑg-8خ%,N 9 dr&mտ0 fѓ?/ &M;PsܾԀUD% n2cOA %r\%1=#,θRA e+ޟ a(q]ϼ7/68GtHT*>{y,[56nbReg # \H=\p.Tћr\@ #{d!b2xUEj{=?qCrl'yӱ-:RHf`M'7+w[J0 PQ4KlAo0P8^M -%44cCٛRFW!8$Jt bpRH|N'շ6zֵ [`+Rˏ/~[{5 (lfI14O7tT!% l@RL/.7oW9RQ4E̲ !XH:R K$̀sn'PaZG DXuE׽7~ϼZO%S_p-m|?|+#K_z9Kw:0; So_wݗ״>Ńk ^~!Ў/^-UõutߘOB:Ẍ!i|ft|+%Nw^dgÃ7>{?uA.BA-ݶImz3~#/u4*)W\7`Oqp mVm|wwO9ҹΫG mNr!Rm-j?auC{˳k֬u3ZgT"XSe!Am]ĝ2[W[=ЍR4QiZLHUq}1L-ƥ7J)-Ԛ- eԄcRi)C[:惛VK뇣 S Ld#LR5'`(܂N9Xp[Yh4DєzdLubBzė۟1T$CU8ɉ3sصOv"~jvɛI:78&d!$pc@-EXmQH@\D3C 1Ĉ{ J(!cB0j=/.v[G /B8gv>|Hn$@87wիQFRUQ6f?ؾA!3涵kMs#G$e`GF*$;p@[~1+*D/ֱ]w{[vWQ"@= G;*49˲,UU5??tr!EBHyvρVsD!>)% $4T'-sw4jy~U!bD0eJj5D9,="q) H{ - >tI!PIkir0BB0 q|Oە@\͉iqj}+ ju0UՁpT12"RH%k )U`"f~>" Zz#GֶeONN)N&$H1i1{E0`Lѱ0=Mz64r5xYʁ+ož5{33[~x~]YUlI$&^0:[Q#ڲطSرw@+.ys3*nG ^w٭c:EB`0u&u,RMBRi޳HnY[V:j6=)PH.[BJvm_{)㮾ҁY'|^s}]YN5d\"ZWp5lظcǁ>8ܙ`ԕmVܻrʤZSy}#!I RgR~˧-XW4?K'9.)}I)5 L0ܿ4S~>3@{ٚM,XqC6CWpwlj9dR *JV,\f_#&Rw e:1n z֛FsٝRjh!!i6.8)1bR" p Uke_M_ڃ6u$0a&͟-ݴ4w\q"KDE#Q5tl[EuV+XaكڧRbq`- 0%TX(%^WQ Ns0&X#1~W_xwC`$Dqԙeزa$$4BJc<@ 1Ĉ~qBګDUU14~zLJp:vqva 8+u/?ֳgϋ/F,ZՏ(8Mw?% )Ž{2;֬JHP3*ЬŸ;([l4GVVf\_$ D[f +;\vM֨}dCʡ~9ppRBBP4͵k׮[eegrӢ ! k*0  S\dCe {fOygnvۤ. !, y] Y~0pnC n ,*R߱m~Ka5-"h<9RT^|ڛ8J0IØ:B\ZZ;a$-/rApGRbK&h'6er$r n*v/~Nʇxw޷+FtL.(',BA0՟ng;{M+\n1Ì Fݱf?Oi a#p`OW;;Iv*B1CBe!}_E 1FPBE `kiD)%#P@0i(L J$@I^V*%b.)ªbiAJM8n5k7Ny߷{ٽ\fxɏEmOeg\q]WJ& c#d6BRbqDS Y+1^b-_ݹf(7TdL'Q}*CJ6u>zZ"͌+j4yFS%Dl`+vDR"BHjCpaRL`/ڬK[z EqРﯳF;t"TCCDda!L!I !Ln b8TPn*m>zH4_ I)B#KdS"*N˞I֠[#ʯ^YY _pA^~g8hV0D َ cB U+u{ Hua @00f)0-! \G%b T,*Eqx4cP0H$u=wWbISCvaTՁ-zf yLΙ]&KiF̀b1"RJBvn + lݺ]Q4LiYMM_~9cرcĈDCd㵳NL8ew N$BHA2?oE^+%%;@ը%K-[-_ !4jԨ/8>رb]sg|>#ض̄eNW~~S'&kbλtBt( Ƅ :bĈڔ1c$%&2!B-x@x uPl@5BHv{mK=64B@H"#;sqxuʋ "6-P`|F[E=ݶt) G[`DUM l^o~QqW{:TPhc1`vZ|5 pdC^0kٛ-ngymuM~DyYJD'NvxvyiQq]:{!Rz's>|@[y.'jq: )%XLoܽhk >8"(Ɣj*U欕 MӰ$&`E+"BY͇*Dl8RQǜIɘ/~J0B LF]KNwY8E*IiiTj~r(73ӛ$ % 8n߄,ыsnX'|ah6=dݓX?kC{V0! &PJjcH[|A)/9<; qAa/?RC"ue%#Vo %*! 9\O- yO.f |B")"TA*EJ4)NGmI^.ݵbeY7>rqI)H.!b"q{8h٣ȓѹC7~5srDϧm1\쐛CJHMv8ӯTt:Bgv&RJLBQV)E|>O^E _G^hwBئLQV 5##& `W_~QIakC8$T.F.ˣ)!@\ b.B#J0F%Ӄ pyuS+"tYue',J='9ƒ-&rHs| T9;8s`Q(uXRbМ|}c'#z6D&H @Ԝ]{+MK8#}:c[fX7ز@ST3$%&Xgrj W-_^1dBW3#F8kIGp]lL#9aR"lgl,{n7I׼7-󕯬 cPUJᆣ!Űl0BZ#vףT,Sh5OHh@ r.0˜ pjz߫-nኺl!B&"\P%fsc-Co$^#a!bcz;suˎ/f<%)*ňs \$YޖK}ZVo>x߽s'rjqץ \~W7l:Zj<1:E; :˻`o^=}K+ظ ,1HHf Rp!1!@0`խc9)b1c qRZ ,qp|lA)erf`EvNi Jh4UGػ b!F_!]Bz@iG"۟#ģښC%UEqukl߾[o4x,[x0-&Hb3 %q v6in. /ܼRRK ge+Yv4ۿl[^!LPqqqӹkM܉q؛q';.:'籉o! 2~mɧ" | vưK;㌜BRJAՕ8ОKL|dӿ;?{`e;7U윶ȱ%e,nXPPORssf7i~ɼJ5go+q)"2{Q2br1TZfQiȪc[W:cʵ%&Nؾ4Xy]i#2 ˰7K!f|:;fwۥ0)2]g\bPd4,D(r9;y<.L~1i Rw5 \"H3<ʪ>{5]ڴwc&) Ra7]jz-3@t-7(t"Cd}e%6W.QdY>GthG*+R gϧόpSF-~&~Roݗ\6RL+P>ߞsWlkwҽnלwKѦ 9;?<ӏ"a^4`D/0a0/u^~,e9T[<4LQDU[u)!OJOrm._L$ls>na_VNiH$:u͓W,7H~k#=B>ŝdLwK׾iתק.k,RR0,9 ꡊ+KW:l[FR CA)X}G_T{iN0+D )@r!Abg(+U @I}NЮ+rE^Ѽs}[xG-NR*)MP": L)Upġ`" -s`ՁPZAq%R&\"bM%-o*1Rhm<.˴$@grk y!݉CexŤ?iV\yv~i8fmל3s>3"뿞$9E>GPPdYaJPtK DQ5 f̌Or:MݸthXiOy y7ϏO={Xκ,;C8#>9#ދ(ER0+}DeΦr9kuV=gMݒXMuۍOL 3u] 1(uZ ;,Ddެz n] DA0bۖgPmT7)o9Iv"J/=4`΅$n0(q;,Mwsygƥ&4isz" 1I.޳y?YϦϩH\UDPaaaqL)%q'*aJ1u.+ 2*DAX ÌSJ7ce~mcz%1X3`$BLnwܺ&ޥ>+6-9c[poy3Q׍~1y6.ݟdf.랣zߩ}G$Kq\^M 3^tQWR AMqH@Pt=b`1&RA%!.;uung D ) R EN!bD@ ?$%UeY;wTU%;;{ ȵX͛7gd+ry|a4 4-k ݻx5jyx# l}'"1F/@xMycZJD|K!9X] R.0@'={E:tmwyc޽iii&L/7MCuxE@ @9}o{u͠j wON8Av: A|rvz0h9с˜؛dn``@Pxׇ)ܟUxcf9ϼf)3,HmqjǗ)/'R0%ԬpW8s-mkz0`m͠w=Ϋ^S(;͉OM` N9^[!xVLA:/׍䲘%TJt%fd{|. eS&} $Ͻ ^M@d)ބ!W^ѵia|Hkķ?0B Irk;!/?d7,Fҕu-/׳] a]Fծ5?fxؿ×/Hs!Qvh\u >w={U71?oB>xMd**,E*%gfgqTsΠBj=|{AAz!B¢es?xq}.9vIrkZJ[ºgL:upDHhK霟(&+f.X}#в{\y-'mE 8ޜ¢;?xaO܁vS$#LzS_+l< #C]~OCooO~zF)!L!VJ Ckz߼bx3rN&HմڰcAg_YإиńY& A92l{e*ьw %v, irzO9Sz7a2o|\~j1833 Xj B5gBrVaH:( '%J4I!3✲ݛ%M8;n0L|>H^7 閬测l݀Ь^{۠,n_BA^nGOfȫ}7uG/;OݏOn5TUB"BT&HyF^p禽MW|ܯ7 ] tӓ=&ޟi}蒗z䝙><@0NIPJHp4g;G|q7;b /ŀ?ҫo<{Pه"I/;/q'wNricWVV3-٣)DmZ1B9iYg]^R N_BZFsJ7Rwo&5uL&@R) Lͧ >'\|M5FuH85RU4gsxOgVnBJbRJjkvTlI)qJ0d0x Hiwuu5a's B u ͺĚy|*̌ rkp!94 753>"ƯkMgbRz0€.b7"8"끐t( 3 4J7ׇ# ;>%1έMdۡK5@Bp#2T[ShTaںFKEsy}nA%3!YB :=KC !H1Q,9Bq LB%)!@G"DQX)fĝRA fDC*8Ov='I3N Eu%KJKI)HH bL.FxIH(BnCXMMͺHHDŽb#Bq84`&N?!hjl!MB+TI4#lpˣLB55 Nn#@T0ƘfSj$G,ph ádEtK'kجkR)ir8;&lV!$1:D:hR+*g~MS *DHsm]mmC0qRR$CT8)i).B^Z@z]_r矝^-T.e6apGwnpw;@pL FS}M].MHLw)F  "%$ƻ5"`#`cUm3orQpc]]CsDqN5|@X́0RNF0 @c]}x56jRZաIQDqP"3Pp<.fD7-x=*fa8]Akk"X$&zPcSɩ NdƺR*2aМb 2POMsQ3#E.RT4 v z0`"P,`P`vQ!{`IQ}*t9L;3;;;ٜ`U@b=?CEAI%gv9С|w 肨fowUWWW;Iy;iPW_z^^MTC!Sg0S|^]P؈ H e Q"0Vw(򋋊^|أ}=IT_^Aa~Í`,aؐ1 G=3>=0mdha^~įQBf0M[@1(ݣr@mN6vـ1f2uoi\˅$R""A'Ŵ!Dʴt]DWF-Κ孷ml}cNBXLJFFF^(B!M2}pzpqNR+W W|pp2 p=/thѣ_ᆙ3g [BNk@Pt8. D`|tfbƞx~se ֜eV RΟ?? UTT(bkCQx 8r䈪>1dY!D,;ri@1NpJJ@HQʀKrGғ%G+-"BlӒeFV F ۲%r5$_ئ@*-K _%$PO?TPh@ڶ@BKR!l)%Iz@) gH۶-9_ (e#!ԓe мp^qn'Цqkd/רh a"e wz~e(ZX a5O 4&m!Zipt(,[HDpxs,!3hۖ`g{#0!iI T":1v8 =ѲpT KJDBCEaݿo~`8om%)"ťc'_ }ԷxG/Z^$l%6mB P8T % bۖDCJCU)h~a)H["i!)<x `0o⚆pA!`Z&˦Id'sB(it,I) OC 6E@<Ei$j@-BPr: JrJ}+rP59KYfX1›Ƕ$x<"M#{ZHuvtvt{^]sΤei,HFC`Q2%@^i:* a k@Q˖@tE R;VTUA%mK" LӋ+"cz9jo(0-5m!$R+ e_%M+ĉf"SJym35t)#EY"yy%R` EEDB-.^)gꋖr/lYh `aB+ J!]43WNP ۖ׽h!HH0m y%%eKJi98@|i[Y60n 0eI$ sD%{a 23i V騭TaUzaF@`a mˍ뗶ppP=zftUaYqz 2>7_7.B48_ca[B2 2Fe$A?!im!i'B kyw~ק{$ADH[PBBZJT%]poB@ζlOkAPј:˥J`B%`N5ﴢ+Λ7;CQ G5kh47?iL)+//b[l1cF8Ϊr```555JPīP9K 0E0ݶ譗^7gt,L2Ef@)8-rU9¸2<9N@f^&L9 LaڔsulJU5{PEB!/eiC-J g0gqnD` g+03q79󙢰̏AqW, (>"eZ@N3I(U,(>Y/(τ!j6OgCRӹ&NZJf̵͌r*PG ̢"C@gǹr앖NBKOO9!W R""`dTPƥE; TUu==[c2#b\a@D >53Ԝ!PP)A 6 UIT?~jΝ=+++5MfKRG=xiz&H 5/O>]E F2+4S7U(e44Bo:-c@/83bp M:)g!\Q'emT ܉h<$4c<.7DI@}3 wLîƨ(S+Cp?Z`yWm욘veNzBB_p&+YB 'm-&j|p^6EU4x*2LbdOHH0WR8C?4&fw$TR ڠYz 9uǭDtrܘs<@S)0jTzvC'sM}d:愜].f' F139x3pd{$hn\\x"'8'|y)#& @@}C? ΰ(Pn]poB6lr/z )%ZBNTrʘq(3fkcɤY 9;NL7"9((~t،r⋆a; ^GiYV"DҖBH4upxx0Rz8z=?°w(ҝJ%*Ku]2(*4$E>3XqrT!?ui tRs'd|LEx$c8| iH# E544+VԜѪ@h `hhڱsߺO}"2+v,EqppQ*t] BN|VzĹˋa>}ٳ}|Ooll4 [?k8pGA:&8r#W:{pϪsB"N'˭ بG-}o8+ A$Hc&mE 6P(8F9DSgΜ544cǎÇGQEQNt! $Rrl7"&F|`,?|iEť#&[-IB`.(J~~^^^t2%A2pQVTT.\pݺu> /x܅E.RLd_W)#N͛Y?gy} .ś۷oy_!t!ũFcߟ-qMc]]]O=49 /ѣeeecA|)j= BK]'T|&8 BKLQK}C#C).\L+&AQ`8X F|a2'= |+W555 &SE[xBȡnEKo@5*SrSR:{p$S5.\ۀ B$&cjڴuu~us%\3fE\˿]ol ÔRDR۶UPP`)%%%:aKdY@WH+WB!wbjm5%ϣ {XxiǼ e,]^ς f6$ aJ %?gZ57FM)sG?ēʊx4 w=p1Y`gy+µ"p.ݹi{ܲLC)Mf; !l˲rB*)̴Ѵ\8{My gDDT儑qKR PNq!R|"Jinё~jvTD@6]W.\",8)asݽ~1f|71B3(4J2]pqԈNI8>~ n.\p.\=ˑsةJYsOa˲N)Ƙ(۶EEũ)u… .\p%\ZBzΕh4i16222<<| uJi$zNDrp:BBP 't;U>e@)mn:_.\}Rx RJ)2YP… .\D\R}`0k-=YsFIIi88h&pj$Yp̙ 7 .NTfSFG?ٶ(JYYiF\H}hh4 'FKKKsźp… .)!Ҷy1˶a|=JDOɿg 2rL)w!i7s^WWW^^rSypR DZWܹs%wK)ul%e˖:& \_.`I(QHrjB0-^סה;-&KDdA߯9L$-DDB'8̙3C 4 jiiٿ̙3^ϔR΁]C!p p.3@RaMՄ-WV̍RfOsNR))ebfld4McKu"=68SĠfvSf/97 sQh4:=x -p%\p|oOR8Ǽi?MBHwW)e{!.ʪJ۶d,:#R`!_'h @ $(HQ wCa$MQ>tu7{-\8'L!szH(|2Ӻ9 lیGTq/GZZQiZPHUD"!4c#7oN&s BHz{0iaL<05_oxaOR| NҔә D aym;[+CBl!:FGGKJJNd@LR1>0~&"X=44ޞrJMKz%HP:00~?[M x^1YA{yi5&0yj)T϶j>qtj+жEG m J}}bnl .⟀]v%c%|E;63k l޼Y='/((piD@rHB` H L"D6d-5mw>cƽgڻi~l9)#[ p('qoζmJ)g\8hniy}JB :+/rp1%*0U0H.$!D  A"%" D%5Jl!rxh(sRsO@a2w$G(e241sN-^hk_k>MeCDq(@آ~?JDBc,RTVZd@ g.&Nq:>?0BQ$r-QC|#W\\ jp.< ΃L"D)5keYNbRhރ=`Yg9`p渷XlB+pIq̅|Fqn+]Pݾ%׮̧T)I{;noɒA]I[K̵msB˽))sN),[QxOwO=ىG}ꩧ.p8dWXvᖖʊRJI8- M$m(8qrMPg}c9cOb&cƍ*M59M5c>ݐǘc&RG<ɵArX5ym3')eNC@ Z>)HF!)@ݒ"հgڇzfK˲~]׃c4#U&d-=5Α]B%*~DiYck̰ 2=N`QU#`PoRRDDv;$JD&۶k!A$i8Ҍ\}'bb0qURqp1 I59͇3S 8%u)ܥN Č~3镳&%M(sBꀀ#DHQ<}೯=}Yi$%\po0ƺS4xٞ;zY  P(cs:.өoWQQ!%nڴ3N?t) խ)uQ*9B)Ji; @=$h8B `Ag6¡}}u->r&mg"'O RQMvn޾u3d)eOn؈_t>c-lAJɄSB[=ԑ\0صz_~Pt%׾E'19B|s])iIDsXnG*2 #gXt,$C qQ@0s PES2FGirFB!˴$D\aN5@(ԬK7@`LA2," P:D "!\ӶWĤYȰ%WUpS@3e,g3 eB-ÒLݟ*rҲ2qeI˰0'F"Fhi t1j8mDhjd =Ks1,r++,/+)S7w1UQLSfwi++m6z]b# xC3Pє IS l\%DQ;6W} J(ڈDEG|?"ҲP(-[ɚFDB(qN!m! S330jO D D 4*%J[%[`=ӳ`BHv2@Ucjh_wMX5)ADH~߬Y33 B:,###^W=)#dLLSi=DUTR Æ#^ tK" 2 %p,{\No@iztd5!PEUXZh`=4;ơ1O2¶l9IsEazH0 $& aZwNkiL)FLL,sQ"x6>m6,`KaH(كQ.\D7!hfvvز:焑&f6 $%|p(lH4cBhm;"1/).A(!H(&BHtx!`ORBx'@P{v7oۺWd] ZMI!>a#㋗,Tivf{}2ǟ\tH/~,"ލ_wkƼ& _ NL 84MdFB$S)tc1:J!LӤ**T o̞k=eSӰ=ҕLc}djenPȴ.fD4Ez;ԣ]Ϭ҉`ts/SUrR(,[HlYhmڝ{XsEKHB(XGw`%YxS8* kzw_~њD`gJaOՄasHY8{v}YBCvt%~5;>&egB̤#9.}/[J TΘX m[rOƻ7=vlIkYpiA Fv=ÏkOP_٬3/źLxṿy( EQ{k1.!HP؃ᗚl;3+S<ӺuDEQJ$m[ij7%͘5{FyHYkfu\Bu5ҟxY''4fH!!iKŢJ@UC/>` /'’N(F2-cw'ߪ0DBdi߁#1K S/B e}{m:2bln۰ּ髗G|%8?Te$6{h4Z]]-$rPJQql.? ڏwoE8\!["H^eYNFL4J(u̧'IsW{V.;\&"ūyG^%s.{+B"!\CO7+._ZE 1ֽG}zk@^eyM4w?{C ~EӃ2fgߺMR9%5Fjg4̬L츥1|WYEKV7G5$7,mLz/r #?yzɜ27u#,77N7Ht[PZ6oICyH*0歭f姕 Sfy76x($ڻq$*]_[HkӓO=ޘAXa9wyʫp M9cNq|yw4>i6N6g%R6@o#B$!Su VWIlAH4 LF'3… pGM1 #\H`yw=:+ $G"PW_F6mڮݻZUU5MbOicg} Ȕ wl:2ݴyogܿl`oIQ{*$T7/䆍@'/>MU,'F_Xh`K0>ġ'q˖ЅWq;~;83U'؅8 Jan9I9` iY"JHv/d/W͆bpbJ/k>W_FFQtUjl, L ء~gwmc~JOY;g kº?҃v♵Tǟ|q ~rf)Bdxߞx#iţ{~C]g4>gϣ_?>UDСW7v- S"U^꽳U =7qo?;pwfUEBHB)MYeY\53,2uIM!@eV۶7ӈvc9;$o AN@bi 3`„!__40r@rll4lqN Tu %p"إ0&׿}P/*dm݃5*߳@L-kW (;uxkuq-}R۶y^g39 %lEU!dnU Ǜ7qHGodZ'G-ۆsVqJ:5@P \Ov>t?n?M1SryϷ-{*R;y/QJ_n.$mOʋde3>~ =|?[vbigϝ?8~pAE=/>u7oG嚯6.(󢔎 ftlzw7?q3-LG}3/<=:?'~W\So y{7~S7~ieزĉ9+& O 0I9/k"4LhH$}nn=2U?o9RdE-!e@y[B7ِ .Mh: 3hS$fyUӧNls\=Z PTĕٍڵ/.,,,**(S*ҙ5Y4MDu"45l/]6cq6Y#ӥ2!6 3!$,G? cW~79z  ?. fuzk>O=<[QJIH<(E( Jie9R #)/\3L8~τTﶇv57/~觟v测gn K~nٜg^[~//~{귟H4x]~s_Aiy͙+>,KxDמxoʠR@)$Jj+.b)8ڹosD}( H;}[paE3}ws߶fe%ͫZ u*ݟ̘^C=U5r–*+0,*Qq.K>3|#$Gc*O:!5L fwlm#})[Jt D 9<ȣ \v>~?}хZNA 7&Qռ<QFh^elaIףa91:j+Ҍ'R-  )GlxQ3½]mk[ [Li” ]J)pB mJ_|qW[pƂUAi޶y֝$Kj DrTzݜYb`ˏ |2n&Fb-9wiŸa0z?g\ΙH{p_o2r~/{kaܜ/2m!as(''la,\R$RD3Umw޳/r=?l/gghgFݟ^;rњ/׫LKW~箿R>p]7߶KW=OG~JSf,bAOG_z-pmP@!GS_5gV]@%7Km&Lhh&HH0 AuW L%zH1XT-ruVm QBRţ*h%DJ67oUfAh >*U^݉Ԑf22m@5N(d?ܴy,-<-!2nʄ3$nu@.\"śp q97MStX<҈B 9TQNX+|dhhxݕU[6o۶i֭+9krOl_>{njW4edX}{ӦL\ @i ^zI R[[%D$XôIX]sQV=n[(p]s o(qai "c 2I ˆwɵW>?bZI%">x6U,z>k{{>weyֿid?u~MU||۫;l,οw-GUAޜ+?ݜ06 V^TA;c #_>te4k7@U&bQ]ؖP'Zc?̆5է_|5g/ҶleJ"3C6E yv?Y*EdzI0tzv^+kJ;pp)DHiۣ-{nsd^T;KŠ5vs`T/O<gƂ*Q@ږec,K Df.\Ϋuz~`Gp0GZQugO'=5_NL%-3 @})y͟+;چGmÓR+x%\旟8z{iIۖ-(m{G78) M(7wަ6YZ:oR-־g7(bXǽ0-<A ۴( ֠%Kּu% 덁egǀ%BJNB0MKt}jD->Jв$P6n=/)8%Ӌ}S=dmnm! V,X l޲y{s?P%pڊ馄-S&@HZF )S %%&-91 qԩr A @(X-MYK-_BsWH|Gykh-ػ~ÝGelU V1 RH%Ym;s.-8ڒ8QJHZ7`f$|"MdQ/LR1y:%!ohkEl[;ֽ|d t>4_|󯛏^Z |Cʐ^|WQ,;H4ꌷo~epd0iT+dDVW, 9cˎ>ׯ$_ʗ^5 *wkOsre41*2d=,(CLb5+jSW7>j{JUcݻwųg)oްO^_">+{Zae AX%l l6-S`!x.-Pa Y*Dl)W{O_Z?3SGgo ΉmKm| j145xP=]zlUsӒLiaBZmY^[W_CCy`琑л6vK~_da119ad2H9dnon"Y4Cn2S۱8V,H”tPؖ (ֶ-S4-PH0m9b27C .ĴOx*ʘYP!J :nj3H)B Tjǎ>?"ӟ>|lW^yi-Vx&0uJfH-50 'n% S #uУW^1i !S)OSfуe &ӣCC`vt8$`љ2:$3'h83D$a;.;0)i !)7$ 7 #%Qa@m䴋#f $50J57uH  q +ߺkIϗJm];j-[.=M%beKgmMD>zn䝷_UD\d3`$\Db/<뢏-;o߳FKʼf{ӟw|s`؞O]Ӌv INo8oQDz%Ja[eC?ch[3xx#ࢰղqiO?FM-?2Z{z)Z|Ze<RTRw߽Iw#=^ܴMƷ}`fIj&OmYvrhϞ}iAIzAyUg췜\{e!ZX°law||{lZ=4DbRHrg{pbZa !Tnx~~賳QM9$v<}Z=wss+dӣާ"4FJ #ĴA 3emHR[Sϲаl[VGKHXdRRb{ O_*]L5mo񻍝+(f[Uu%QB_xGh M|dna|?/XYƒą7mɥבL98jñ$"A Q L 1]iUJ۶3,DPgyMZIAwoNV LrFFaaaIIɘ7B˴pBN* 8 &9`r}ےҶ̔۷J*ʀE+#}M]ӰixJ-ĭO_.m83͔92`$La ugyl߿59L[\T}}@ص5vG@$FcPOy OSG !4R%sZۼ}kBG>ԟQ<$PǞG~.-+v}ޮ.<}NsGs ð ˶MÐMw|kε,)sE3TӔ))(1s% v<("TҔ!Trktt`6[8':ٸwuWϯЭၔ@uY5:3gmdDX_¥lTVmn4@ 4SAAÆiZx-[wv/GM۴%4 Ӳ,Ez~be Ŷm$COm QضY7GFm &K,Mp˦]Ͽu̎=;.m{ 鍍^|nӹW}dL9ro]~{s R !ݖ$׾ /;7{w?y5!k$7U=ڞYc-M/z聾v0S7^][_<җ>9o9Bg>h~[7;kŪaع@j@랼'饧Y ʃb#ڼ9%ۈcD@f6 عuu5?KhעԳ,?٦s>!RN`d۽/>sHx疗kU7Ac0zm~w`~CW.ՆpR_}'V؇+B˪=21 0XT8EDD@Cr>AMد (DG uN!/jn`YU$r`ˎnVոtny84$pRN4 Bބf=\e^J)t ;_ɞK){/!,!¶m&nJmx4 i'hrM'mĨZb$k78unY9FFM!mle)Z[X(i )wf}TlKGs#P0M (]xj}w߶vsW܀\q5#{6$idUgZ_«~ݔM7PW7~ޟRgW9 R aKb$l0zp݋3mOܾ򷗗Yܷ^^{ouiOGBoOW#~3UJߵ ϛÕBY-h^Ï;7>(؝QH}RL@ ؤMɔ41"DM)5IHNr2'nBv$` 3q !3E5HRLqz#6bFI)U]p.Nk9gZ(:Ec̎g;NRÇo1{v#e,H꫏>%ضdld-Jǘl !OEIeg|S~ƙ).Nc3Iyns-v!idog{},bM;b&*Wɼƙ2h^TDTs~`\Ȥ  4mi@ΐJE DQN©9:2]VX;V2ʩNQ'S!0PtQz=Zgψ8 _MY肨ټsmWwW9eOއ2ṗv6{F~ϣղZm-;[#[6-WIB0 mE_}-e$8082[ -lޣ/oHl>[DX<іۆXJU*ʄ- SfxڜwWY_hN2=]5:PQ{M[/cɔT4 Tr)҉ srh#>ҟ}v+^lj@#O_<9/]sNcK<*Krw6nYyh YdLeZ8$/^QU_UJGJ/{rh|@THʽ*6mzOw YfF&!$;_ypUs?#+{/mۖC}\4kzmԫ2u߳#}E~࿮~[CHBOSXs]^)ꤘ#9Ifi!0  H8\La;d2Ωjl{2[8^J$Pрߧ|y%Ņ$ F}%yQR?Λ\:=AP.@(6h elL$eg-z4={ُs4WcӳBR`ʴ23R¸4DUB( Xom%*{:'XW0R'̉ e:'4o2ѿ?~wמcI'}V|$dbۧ~{Sˎû=/wWm8^ض'd+]xy뮁+ZލeBH,lU_Bp[կ+eZ0FL_Kj[7$0ʖg 7j>32:dK%f-̪eiǟ8JR)qBI=TT;G8TBf[J ޓ~R>4wr,q:IFɩ3$ MeH HJ_Mi[NNBJPRPQEe…Kx1MUIDR 9ifWJ=m':tiG{])e)VVVW& eҝ6풓U^}iZO=%usQQfъ So~MMWk_=<cfmcI zTcom6^R~2ϗs'G(裏ٳǙf]v|iZs)gwώ,wQ2H(cـ2E^IS)%T!$a,?D!>iVN ׼H-U @[^~Y]Ҍې 1 Da3J2cx^::VW)` 1Έ$Rg367<*<ա-K^n_d޾+/h(W{!ֽO?X9mQCqXx8_0*ƚ -LFgS$Tqs[6?Wzg7uӣwǗ %:%b|L+S a4։$-@Y T$M, uï>s[#5Ejϡ+)@[ aLDضSWW^ɓn[jJ $^x߼}-5yeZcv۲znuQ*H! II <3?\|Li Hq,Q9!D>X-_PSgO¨pj `)" [1]5%s#:m?xo|^"_UeS UE큦wZ#x8hweTTWԀ `3<)2 QJ"0i6Lias );>-5yI DD)mBxno !eU !gw8A8 M8VqdСCd<OU(#2Rs@`<+PB:dcb=QQ RXtM%hLmJ@1HIKe1$gq*(NEJ+v[~/<-/ىĸS Aq/}/r]҅+䏾L Ty}~Ǽغvѹ%r(C[zsS!V̚PqBi+@J(*S сJ %D"pwTr;q+ˣ7&(bֻ;dF[x0DԼz瑘@ \hI8PxGO;=?a~tc{~ RREZ(8q:'g=Єs# NI `HN_rJtN5ae )]2)M 9AJHQ*?HI.\D7@JipEl,x꤬ܙ47;wM-N$_ھկ.Z(NSb:$kv.G$SGM1e_Jd}ֹO#~|I}#j+UrAdn ) /^wX,/_ )uu>NR\B@Q8j>sdej۷u(YVJAYT%VQ?C*_QucIq䙦1oC=3)&;L5D[> zBM(cV"6"ǣi2`J -kx_ҙ390W_T~otXOKz,) C-W|]@ѡJR QF)#)Iz:)ra`3msWzteAd ܲm_{ΙG>;iSUSJPv,iR@8aS*21F # EWS[v /i"﫯nxr9gMiea3OW8# QuMQu_qMciqցE տW>w 1,2Fdv;X&_~mz/]X3QBc֧ j׼e4o}TϮ{YCL(%JUpoFG C?e&GR*i(2f $  M;;sg^\1X =|_O@衿|Os y&H@xzfK}ߗ|aN{hP8˭%=(j=zFS8!N<Kes!&IhD!IelH([nuVVV.^XQ)%2`NՀTw@ߑ}])޹sOsE[XZmqqqoo0B&":6)7 rFF PW8JW]߲@gFGwij$/X(x`g_eGgϯ$ T=f>8^l1>f+{O\2_13Rƀ:WjyU%Wآw^tΊ7)4"d0o.m]|큁GΜ7(m{ >/LZ@(e 0`80@B)cSbP0^!q(%]zؔ:k޺|˦Dw72Bض^k>Ͽ_~æ]5cf(lSyfO^QyzwOBQ¼ .{𶖎ûw4Θi` u|]M`|q1)\WBDe̺c]G:Yܕo G#4k 999QN3;,, m)+[Pj4m}~kh|iK/Hf( %Y9SO."HoooOOmUUUs݂N$zAປyaQXTMD"HEew~'ic$AHu=K{;= 9f#Qp3zGzGVXƽcsN) K$dEť6w٥)lJ ByUsOPo=3*}fLqÓmh#]U(lΕ( 3J$J9W8sCD[J fBJgϮZaI) T!eIcyӟ&niLlvS/wvnygPB:ܸGMP՞g =n?{~C1+|³b2Oti2yxmMU8988Usy+g w<\Y?g 5issqs ]p=\Eb;w ξpJݸgӦMfBq7-0xf( %}\џZX;Yȕ3Gl-fIѲ,dFO_m5ksne9ZȪ%'~ojI?lڹ7Ks zuO>/xk>w/ҬhxU'7v,˸ ?zNJQzo>ri}7,?o||z\+-٦D{S_2s~ӍZߜ^ 0 @959U#[xu"XrUg+ JQ#U=@l=tz~xӇ|~USۆ՗};~0bw$Ls~wk4n4@j}O~T.E*q?H!ۿM?#O[|Ǿ+οO^wڲe+r?Phˋ,:ֽmyyٗ5.9CyeI$OA!@γfl ,@DVQyӮ]4)m۴٫N;)WW~o0.ʝ=:RrC8PJOo^TT0M[ߨϙQB )ŅQ"$tE 4VJecop3Cڇ*so{'FF]x5K}10}k?|P  \Q[ ac|wQa);¯|ƛn. /8/\w4ei)NӞ4{¸TE χk h()?ᅱ}A)!sd\5K჏KKxfJݫ?VSqҷ_e> m,yxic5Ater6ءc5Ig9^1g "ʩGa0tz_]?.=R3erej&Uꊛ#]qGMi.\x{OOSyyYmm k׮m:|L,*))9ӫNJ3u뮞ϞPaȈ{TF}3^L hOE*+"~Պu7@~ym(Aiuv{ʪ!Ͼ={7~n'rvdCr+=@7zwa ZأmqFk+fl M+DrSTDC}/0()e N41k"X\]]`4u]w㗟*[J`ӊ4]-}GFс֦Að%aJ@ f >hgw, \#ejJ=*"e!/Oٵw4i'P\^S_[P I;Z!N/-zb]Gz“_YHsޮQSW)D;z$@)ZwhMjjJS#ӊ3>=l@(SGJ,K cFyKMoȷ+g}m4&OAht=ݲ`[H+R E)≄ij2P-[ZZZ>`0{Y"Qd+ضêx<1:uFFbyyQJ\f*8E'(,,8::t8e +9|xA 4 ˲m^\USQ"͡v>mj*(D{vh鵕54b= lV ,[ܿuDDk̝U짶S%k;f啖Lb N|V26p9^XQ ꌠPB9񁎾}O~K E<%oiw}()tO j2h*IunpQ~QiHRo ]áiuaM %рWBA쁶VR!5{d˯^[FKf+w\]m1$ !mҙsъuv% "'PX^S^S-rEz[o_(W^1<8*/:$u1tx,6)$K*!H/' &cY1gH N6OIb:* pNbwYz#\D N&S` 2[̯#Us7Κ孷ml}ӄeι!uBiJ]p=\H~! O?J>d2 p#3Muk |dɒb"L+!@%0)APΡxTDmVv{ۖhgu޺u߼̚7U. R+V|gl{<6l)H${T=HD"Q٤ATETJ85 ["7mJDJmiT){Ai@(곖n@J˴hh6DPؖ-Q-5QQWؖa -[ VDBHWm{k?wnCζg/1?ؖ%CO+a h,V2eKEa$V1sIHaZ̟>@چ)SJhQ9۴%Jk9z>XG6}}07{y 3VJh}Kߺ}/_C_3NPrI<Ѳd HӴ@+@%PqR4mѹ+',3a%ՍE@)[rOn:0[^?s=M^}]E`R6!4PX9jµg۶난~,(?Ja_^Uh& / -Hu /ڔ %M__۴l'QHqEyv2XcCm @:YroYC9N@%4Y$p.\ EEEa' B x=6XVg8穔!|rc[ivBEtcJ`>QLũ7!g깢 r>eD Lͮ!aHTX˄_rm:,@־B $5[>RԒlQ8㜓)8w w teG,pN3oӰ]`1 a)%tl~%"Jeqxn77L@0חL&`nR@۶c~ǝAQլTu``)i:|dOse?PƢ26Mcf b|0+8Bf̞ ;jY x U0U-ϭy3T8wRqeNҲMqR""ɤ>%^ǯs B"HMMeYͭ?555P($EXe4?6vxm^/O_:?144s;6 4xՍ,:WZuGWn>]: Y{ 2,P?b:uI =vDQ Jd2̋Unz(q7ځ3_*:`0/]_WQ"!R2 9`Z5>Pu;.\D O Q[TZ=;MY 3 !4k@]!c\~, .E\aRBD!LK.q덣: $J 8 N5ax}?ʕ+Ol 7<+Ul(CB4SyJ7:c[غ`zI((t%\ sPi5gwJw6ES4Ww,yL&^w+<=FJi8 ӧOӑȔ2F'x ! |gyfŧpf\AS ''LN:{*V|53%D򢢢hu``+6mۖeVݲX,F)V sÜiRF[$9~d5[ q̔3f߸qc{{{UUU *.\V83ew:kˊ͟Oa…KiאPJRT,#$' Hkkm]oo_AA~֒G۴y3!och^UAJJyy{PHcC§oprM-D(--⋶nvho1LdLJ*y箜1s[x<===}}}PV8nJVH$ sTxRJS&LSfj-9_lY$ٹ}#G,r Kl| "4 [8H\.\"ſ1ѣ8ضKJJ|>ŔRRS?EѨ?uém>?;K&rLy)=P(,(x[΋bXM3܅7FqOCAMӤ1G,))1MԾwǩ{}纮QЉZ'򦼑zwRQܹsg] rCG@t7.\Dog͚9{v㩲_9m;jeYӦUTJ!N}Z‚Sۿ@d_\=.!_sB8-*7A3oGDaۊvK.Nsr(!J7 .?O 7']8Tȼz# B΅S)\p1`\/B"J)=V)I|ujR.\p… .\pH$x`&=5SJu];۶=:?uz;RJUUUUuD.4 "N8م .NDߓ:$).\p…KpqR{AJ֭ۺtT=RڶzꢢB4Eٵk<ϩv*ZZֶM6jTld]]i-0M3{SD x":PJmO7su]wqW .^ހSa/821p#s… .)F<ohh5k"c!dsJ#8zcSrL:dg5M#ɋ%+/w:{?쳉D'!$L> 2F L?cW^GSr<^ \puJ^~}WWW* eee˗/r.\<(avvvRB }dEa===a8YNCR]׋JJJbs… ."oyKƨ ifc28c0dǀ(J'!D״@0h x'Uڸ@x'c1//QBi͌1DjժMp}\8K=:ySzH9oR)!qA۶+++"qhxkddX\qˢp8.\D8I<4 #G{ 88KRB\RJ&M̺8,ˢ4he b֠4B8 @/?pΓc3≄(RR<{s9'L>mдiնmMId.!!t^vBǪUzqY𵷛@Ji Ú`|t4;3-òk B)F=ᦎr>)FX\Pfpj"!\2 pqDUM6eU6EǢH͖GyvhBRiJM;t0>Y0Ov9'NRʹJ#!g0+Suz^0p߾}R646905qtoxJ9x&?I7j1JԽ|ƌgWϜg6N7"`"'+9gɇmUU)'/w&Dh4wqgz/l7lml0B H@M{or !BޛbJ[N^ɖ 7O=;g<0|C5!c^xdVGGGYYY^n>p]coo3Ƹs1Rc@JIOOn߾}eS=U|ob;MS=tfEGsA(**ϙY`Nו$Hk*ER/U=/K/4kİ\E0!(kE{t>pOO͛{͟?30³t6c4sxO~VxmUnn/;vX,YWɜE)=pr8~ߦHO:BDQoZ 1->x7)SNJ)-^XXiZ4u]A7n쪬,(GA=Sŕӱ$ySpƼ9Sl@j둶#7M=: !:<RSޭJp\]pVi Leho b_31\ʜi|:G3c8n<oii)((D^>ǎ m; Q__]]3`(q_ @7oF w!W>摴YU9a#[eoOl+ƄúH-@"B䓦P}g7tw%4}wVz- ͛{Rmm؇"oQ*cś&5k$MKUԿ}<0cd\xp^@PaVlyft!n}iOwYOMӄ{-;~}׎WO$3Lwp!PsC7m~765-ycIEeEaa!LpK`XDZ7y1c??So>3n AϛRsZB{O_䫻+*#s\=Wb]2jfJ{H;;3[m%8O&MM:R1ǚ:;TUu+B0& !3bH\d=Xcav#qL>NU^ciLGnp%B@p!H!8ig{坆 ϕBD"'U!lh_fDp*{.7ED'au*^v: qvp!@(^#Xd'N?vKج"993B>F`̈հ{0 ` ?qⅰRX ;#6h#Ɲ;+;hx` c5w\ RL2kV/!O^-@\7f0ڮ7ha8܌Mv6E/sSnZ $Q7#p9\9L`uu֬wysμ/Zg\%pavz'p;0D ũtu]xj”0/4( ,c#"8gS'BۻkGՂPZsBsϧ#ÄjپM/3BTn.0@YyYIYI4Bp!.]~ ˶2H+ „R̶븀`iH'0`msh>:B@nIgr!LA*K0@8\"kĂ!N#O9'_mBIe Q0!08SosAۻBQb}H2)0BOW9n{GLf&ϭ鷈)H!@B޸*2{ça19s!ʆ[{Jۺb␥SO'j 1VUƨQ55[Z{zjjFܱ''7Ka@BAZ/`4=AA'Batz4xA+SNi(ij1;81*S D;f&e؟x֬L|7F )6VϽj-ߺa G {U9c#> 3MӶ,#Z<k8Ǯ+<.nŻ;vl13Ƅ0oL5c33^R;cޜ)%Ad@DSEe%on!Q'3B>]kM?M"='VrTA`\lT0'%8{s Xh WH:`b{{Dy]<Ұ8)Xn{#`LUF$Oj۳vk O1k+kV}]~"}  [V=k|vwNPG srw^ zaCu׾ƦN )H~yհLS^ ) ټ|>X'XS˗<]vbcS8]mѧMb={JD4 F kyu_=9qt LB,WěNs 27ic=i%Bg(o aBpZw7&\!5"ťeEaq"N_Cݶ[tXFS7kQ#faAwٴ(Jƍ`IIHK"{ .PkzK^}+nOપKF0\(*..)+\ Feqqsu 27yvTƯ-vl)=20@uܶrْ囅/w3c+D5?l7*'ϟ76Inڰ5Z@z9Xp&]?u}\&XƎ=׎S(0U-5T3]a eBA T6C=i5<˴R`!jIPl$ !8hs:QTQƉw|yoÔg掘o@0X^ͺ&F(f Y5?0Q0å%eW^uuccþ}mFݻg: 嗆RG$2#CD !D"TilXo?u:MlXt>!``|Q]‡@;3mw(|asiտU<|R6/<!Qs%݄9ӳ2f۶c^zt&4% Ͻ@?ydlH `wl}_AMÒW+SS~?{_Y^JSm_ Ƭk?ٕN$m3o׮*F<~ ;ݗws/5\Ά:`Bi3ԈuE_]a0_r[2>替x~c;6g~iJ+w+[{Yem3\.NrEcbqu HeSiy?=mN?K#!/7BDlۚxtIYӆhP֋O`Wwsjo:YZ)D5܎5ꥏĮ=/>??jzyAhO=Z&~t f8x(ưŅ EC~sOmx.>*e(] BiڪNRYmJ oe՛72#B&N<"קbXj͞d8'm [vt8Bxq B߱cG,[~}8T5=e tCt':uFIeOD \6p$ wXFms A w]heV)_{ /qaǷ,}g]~o?&m~;~M] n͟KIcM${ ݯ_>",ڹ'6obd C9Λ͍c|>Qk AcrTP#0f, <<`h5aܳ/v9b<҄ p0]`i.BX$)@pۄvc׮>Ϝxꅪ[6 ʪ&͡⸻O8LݦmHΘ]]\]U:mi,ںu%)?[yJ4SЁfsZ'n޵lr|->s/ F3ET!gGr д*+J ÑU.= ߹S*\Mύ:lnA)G8)B)l\!:u];W M9g߿= ]>p.9{|iϔwWո_]2RJnŌ}‰'?-Z@ĹSO[W-7??Pѻ俿_"O+TWWVJqus@(c̱-r9DQU\a LU.ڌ BURk&sS1BsL;/Ϲጙ%;^|~yR/*9kZ8{3Ⴑ ΄ **m; V#EѨhi魝'0"Kpݧ!ܶLryvR;@s=[vј9 $\|Kx?rU1喅5a{"u LTUU(qg|a*\LEUBl˲m jB1N-DT]Qe[6c E:STS4oֵLq  X$ W,@*RT`p,˶]u (/`V=QK~NmnL$`{H=U; vE r TQ[6xFEQlߴ{]zxtycxR<1FdF$T6;'L22,zw~zeoL.X}]f;e kcW gzIeԵ}q Dj!PBHWgۣ(Ƹqݵ3Hy E&}!ŗ_U~{ǫkOx @c:\ǥ\ZVnjoj/0]@&&w.Y߿?ݖob^2"_5- g|ݔz?c#tcH-y/?c~o1}ZIB_#}v T9/!th( Nyet4r~k @1wp9p!R91qgwY}+zf\E(^, a"i3 "w H$R|/A(A[*ǗO>|wqN#Y읾'_-#孫deQ,[vmyyEaa7^ookkrrr8gG.H](C Ӣ4O9/ h;| 7~63W Jɜ9|mn(,Zh@!;/(s'?W6nýV-LM E8i#\1~.8O/Klu0$Mo?1k]0jX^0.oʥ+ӯU'쳯cKl5hX tP>j߹m'O0< .g5O:uiŸ?z$f8-+E_ӦΪmK{ۛ{L VU+B:AŠp$\\Wp kPa@i^ʼy*du9 /XpWl۶"|?L1uNzqsm{gq9G3 D|=n8ϯaro `LDg=No`qNimmQDkIJBj[;. 8B8r]s2$+Dqu͙]{VyI_nI-b\/gi:zvm7~E.ssoCCs_XYYRTRvhh@}FɈ1J7mlv8RrKJ>6s]&o޸_ T `۽*z[ZZް#0mi}9ohԢjǸ6zIb(Rw'(O4'H xIG#ghok)=-gtqBUq(Y.'#ٱcB !Լܳ|rUUoyznSGd@CNUSABBM3u_{A S ݮbD\/#B񜓢DWf*ԑ&PQB _~#㽽G򩽨TH3 +50&!۶c\a m& u{խɧ)-0 UsFu;mZZ+vjNgGeվp@6o4kG 㞶-[ĉUJ;P:lJ~{yL ywjwŷpZmj%!xۦמoq?o |+瞧>-:o}9%iz1;i;8s-4t8`A,1!c8w3Gܲ$R+@ް'۬ŠpZNʟ7N_tvlm\?↶|D w6XF__4jی3mbhX>_1tmXoښոC>rSiYNۖn1 ~WXN6-7O6w௶N3&a{-mzj_/A81|t.q{7ǒ]@ Ʉk^ d)80LΑ$8 zT7}Mݡ54DZ0Wmo[lصso[᫭kfBpعtIGASfD;sYaaaaaO:78d l>(wfzgQN#$ƘsO 5+w6-a9}}I;oL^,ڧ -X:LomoS}pZ@(wزW>XxٮG4-Wppkjqa-[Wu+2H$k+u\΄!:M%>hvLN!.$Muhg)MM ąOJGq hs\Eɣ@ ~D O 0Ŋ( /^̢0ƪ?jqR;c߆1ci]Ǐ@%?SΕW^yu_Jx«l ]d#1CT0?@a9'/~fGT5.~r\ ;osw@瓷O?zdw$Iθ7_}~kNY<%;ƪSθLVh2@e̊}Uʘ| v^~qԉV[g&: $Z7m*'l58lSW>3_ICVET<ۅDۖz*.do8D),.O(tpM;H;3 뺎r!p,7k╕*[Z:;Å#F+G=uƔp-Em bi S7E\<7;G@j'"'v8y?%1v`==zY(|OS[_<`@)B¸8Wnkݞo<Åju'?)H>@ض[DuͶ-H8h&Ruu@X.5qGAH-d Dy\!w!EApL&DD cLf(!uZpxo:g7Q& ~583 ! -_>fiinٲ|rW\qE~~8!<}~R(€<3>x@L? 3N4r$=4ю=?)-q x^}rܸ/㜥TerCDaޘE:@`}P1ƒ^7B'Û44YSw.X !6 (/#̲{DŽ"#}Ϯ+lnGWoSJF >Hi ܻ;&nYkDK qyg@oO_s[;LTSR#2E XQ۶l_"X˶^qd2"+uh"Qё_s…⋋.ptpZ]m>ACs귾ŒqsEؕvq8O&G@S[fZ|#D?/ё:wD._ro&GҲW^illUTV~۶LEz-#_ ^HLF`B%@ #Jc܈QǺ[9.*pHfvޔORS\s#%\! pk$b@( k_yk)51x*o"\p*} _L>#OW~0Ƅx.cL)# #(QlPp&F<ʚ]`Jru\tsoQ b랽/ϝwwt|]RC>H6kuù3'1Ǽ췦"@&sG~~?ny5?{Ϫpl0oaRг:pجOTL F9TUi! Aa Ԝ0eB1PRLIꓧ܆0Bp?6+1B  F3.&# پD"1!0gsۻP@ $8BX8B E[( x L̾IΈPEA#$"Pp* cxd𬢰[#'>ԗ`(RU]UQQ!xjLK[wdD)\(*Zv-`/#_)~y ?隗?k 1\-|Y\̃Uλ`$u &Tc# [th?oiXXF<t碑%ŵ9A:8Gk~W\!" Eihhزemލ LB)sL98O涾i]qА0wTnn޽{{{;$7'7cѧ-d(A &0Z6-WmA"zz6#qYi-G|}m]U 3[M,"ŵ%:  E'otedGySY?5$6+اRc.}=V'{ͨYVRp3 @qeH%!p$}f0䣼\Ɂ<pZ{hٶmsggװa8Y]_/-{7}L߷y_|d /ݯS/uex7' TE!AL>= N>N0 $}~b ,ެB ٽ~3\%`Ϩ++6bm}U;|cړFiEN;yBuAx}߾>XMo9);y>dn_<ʙXTN[x9Ơh_GkyRو^nilIӧVm-;{L T TQױoo/r{t^B1`Bf}[ZtnQ!`TQ*]!DMPJP~pA{v\_V'Mec(X˶'O&%>*!:!:*ҪHnAwֿݲsMٰQ`HSRM:%::A %X%b{?F4`35lxm8ָ}m`٦nZ=4?GG<5eT!D*qAH$;wܵkWGGGNNWٙ=Gnپ|綇x epzذP( +p*mKX|1"HvG+̄k Z_yxI+֬o?|oι' / }׿Իo$+?3=MIq]uoM$sC!Fϙ]c#$e cL2*9{!v NB]*EWߵwgw׿jIuFP*>JuJT4E$J$ !Վ6Z}Fe_-ގ>q%Ø2N8#lN$=|r%/Jqq>>12GGf H6'q% L𡮟ڟul;//p.^~ߙgm :8[o|ӧ<,~sQU*̾Yg>k&xaxR>?~4u\Hpee vzYsN[q*~%SN9ŶS.DB:~g[ ~oC/zM-q_ZWY91>yq]1jp5'yNʯ6wzCݺgʓ'{~a0hK<77>lsZ=YotIŒ/sDxisKa ANv6'v3o႓BasM@M5˯]X/-3sKxYڡ6(#ײ-a kμ򆖶[d GkJ^^ֽ^^$wnZHi6HOSƟoBS 03g\vM_wѬq>pHD Qq BC-?q qYpg!U%з{gSaw~N# OX.R6}~ ]ky꽉cgF/oS1jĶ:U}wb*^vcGvj^mQU/w<>kBcUUM}~ֽgՋ5W)J7]Id(*:AQTqTQE E'~vN/?]Xݿw+/.usUi?9:'`ĢXxz5Juzv~kfhw󳍳Ϭ P]Btz^\V:˚k޹~ܳ.L0dCF"d~w>h )5sVoX};DXh;0r0<w8`RA H;vh4:fx͈o^7ENhԉ_GI&ja(--F c?YTzˤLezEX<ֺo❆u-lZT_WV=«>Ǻ:&~L ,r'\[|]/'sͷ9DZm ){Z9sO>ܙQ O(%^ LJJ`Y G5ʤ_Py?P)TW09xŒSθg :\ۦ T!8U $4Ң]ֿH%(nҶ1hdSN,3TVVbŲm['?zTqKUCޑ ?P>r0BԔ7&8Ld*7_:\H˲ ~+_WUAO{d \Wws=7{s>2-EU?n9ԬGeoQgvӎ[.\49ƸvTm0,//'pɤF c7P ^/ >sy>M%<>B ~uV1Ι|9u'dXp7xygTkr@y3N?C5-+GO7{fyY ~rphO$ܲ wN*"E'7̽ǿ鯜RtMv*v]6zIj- *OmYؓf'Z}#sdoFLF+jO-ISW鷰Z&^c{Fys?vr.œIcT8bb6Ͽ;evu!mI_as66,΍D3f]֨@8T9sWX}'̘5}l_Qiv̻/3׋F>=ڄ\v޷L۔( i+W r'$lZ55VXy'5U]]6 \zI&*=tMM1E*rSoolׇsϞw>,/1;Tj`I cGU!@UVU5aIs D*>cSG~Ee9cB 1!* ;cvvBQM<É*#.( *L72-7nLOX૘}ws >sp +<㲛iص[[\嬉'͝7s'q]yqSܴ99E>uT@hL}ޤDroyD0!ۓBEQU#] e 1Ue8YK  |yB CWNK{>~ar 8B`'rDrl@!]m$䩧#iBN7}򲚚aT-{nڰ.phر5#)z7xzĉ^F替JKʺV .oZ恫3C,ad&^{mmktRBys,>u3.L ` l'Tư4wE&L,KӴM6۷Oe9|>_{{[o=p)-Ͽn޼˫]tvtӧO oEU}۶ΝcYDssѣqm޼ysnnN^^^2ܸqSOOϔ)S vP^خ]6lP\\2fhE`vMS1h#GOd|r!F\)e:KϏÐBpr\!@*pS#m hp s,q<L:"!@X4 I g(ٟ1mWTՔn5MS3U-;wl[P]-X@p1!]rYq)N1p˴9 k*}qϓSc]OL+FQvw򷧞}cYc swg^Y/dZ+w=f[t ]r ֚m3V5-;mL(>p) ܱi 2иs+j+Y-0Uۖr@T)YI[sIUc[.S?;"HT]'Lof!ɶ^^?f\dY"@g2!(tuo0a|Lg T@ĸp8˔x< A) 0%#nBSc" %Hpq8 LB 0fXLBhJhgggooaҹQz! XwW7缠@U#9"-ObzQRR^@1}i!t]///7 *4o0Af6O sH0c,f:EA9I"TU)!coSA^M TW~ q3 \K9LfCV5k8KS>76>"!Ft@3ۉbaPJLRB!۲ɤj{esp(ʌf!x_5O:Qnȟп@'ڸַނ7Jf-z}u*ӛj(LCdk2C\qA뤂euK$R|C,Yn8ח/ wtvbPd?`cBf12'.`ZC[)iAVuc;#0M3Lr>755EL1<B EB`pŊ圳QFyd`ӿw4z[VA-]- kkao M)5 !TXTh>FVBӴCJp76551Bh,{wΤItMK9 @ Y;1|Ç/,*$d<|" {Р xi2(wЭϛ7/_~ɒ%+p6|vi/ 6x_ j\Ǝ_{ E"1PliŌg|>EQIx3jނu}԰OQRRR(H mnn?$ٛLn ]=\7iY55#srtJtݝ]] *xݤIg n(J¶_ٹsڵ@A|C|^Ӽs-xOLB0!L  J$)Hk~iD"q8?EM|m1 Y=}zvn &_[_}wU3JK]67#疔\s(o920`ʕ3_|NM͡?ʝd>Gk'l%2d?@D D"H$ɱ`0{}R{8,Ah^;ujk{t3~baPg*+MMrs#(BiyGh8Y̯,<0u'keyqC/^ r lMQ6mѲeHQ@WTd.M$+1h*<~mv] ($"iӲ[GkÓBp!\!$ǀ2 *rN*H$= @g/?ba\n9ӦH{)+}} {J!QA2 _Ddo-e}pb T7TK.q-ɑeHyɤ\$D"Xb/|gs/ rrP^ Xښ6 0Mż K~C0Bp9̚<(@EE~2// xI4g>bS1r9.- H 9!T62=D"H$NC{nMC0`gG?B@!큟7\poظ*} {]r]ݝ+W@i"G(BqFmZoyaP$xզJ_ֺeckWa G ,8oj'91P=+$ܻ,Xj$}k39_y9m+==%c<3J$D"!֯etMQƫ}w.3&s'#u&+M֭KܪG-0;FqC4c3[3 PhMa%^{eO]G2qck2f$@tOVn`eTB$)HH S\KZ_?f赌z U"H$͕YϜ2Ew$֬eƌ̮ʂfɎ_]t99#sskssزgB㛧MͽnҤls /.+ 2wiFA^qxm|hݺ;/_nGk"jw۷֑-o- ɜńBHFp]uSVO9}H!@"9 S|$ƾΒm\a4D"H_|ڌ6.^+~y꩙]_;{)Pс"tJ`bQĢ.Xp\_abž&''ad0쎾>dR8umXukJ8w[O.er`)ҌG$Nd@aO,⼃i%9㺮+$D"b][[S'LEݱt庇&#LHq~@uH,oY*AEQD"H$7Wbe~8]twmBR_`YrsQa!*):־D▿%l(B){ϜYMsGK-> {aO! h8EAyyTLS>`li@)p~M qI ~ c/yC6^K0*X:l*'blE"aD z_|]C0AMPQ*T#/zf ݸm ɒ;:ֵ ~(g{jD ʉɳF]7АS_WZem+*KJ$D"IO012>Fב)Rluvj^5c !(!Rd {@>ߡ7Ӎ_{isWAPs}!0 PBB@!9j9̍ϙB??q~|[~p؋D"ɑ .?tc @TU48!)@"H$ ^SPΘ܃tAqAC]4ͧi !c8{Z"D"m ]/۳/̓qonNAm^+/H!@"9 '6mrooGHI&֭3g&}>c1$D"H>B8>1`{P7|>uUQ4J~1q#ZrM*+˷m[@ ຮeYK S{uн:˫xE!dO-`@)UUkPQp63ϔdSXD"H$'q=p֊q~nęg;~Db1/BP(Á@0 ME%DWŋYXe5͝[:bD.K(i>ivJa<8bxВM*>`ffHR(Oa`V[ r(҄|`0|>Q0&#!sx}Nky{"37׽319{D4MUUEQ0ƞ| $!OaUEӴyV]xJLD"H$\N3yvЯׯQ`@@ %-?A #TU@ =Rc:Z] !'r)EɎF4}=I뺦iRBj)H$!>ϲϷ8_xWP}{'`|]V&EtV9J$D"$\,+G  c?gL0v;OAa 0 @ŘxCo/yelHnzsƘ8d2'D"!40/F"7o7F$ ]UUH$RHL^eu;/G6-d @]d3UMU"H$Ay梋 Jm_{o0OM~P0_AA&؛!1B(UK b[Aevz\1m2N AS|<<۾u$=|3U!SLL$X,H0@u/%ܙ݀zz>L& 8(#@"JsI'.b¾Y0UH9@"H$ ;܂vCÝ˗g I.5;8>Pto76޹|7搦yg-z}u"maVVJE{;D 4T@(XG( "DQTEhs"L;|C"l̅08H~!Dc(i{eP4J^z v}}YVS,V_XOg PeYH$^\i~?~ވniڶi,%(BDrx yeW͋K8+JQKK{ë|D2rH$Ͽt̘?k;/F:>9{W^9@ӀA;N>9cׄw( (Jf#h줓n]偢||==iO8\Dpp %zӼq8gC!;aEQ !B1\6ޕlaLdcfOg6WTJ!+[@K+ø߲DZӓ'Ld+ ;vX~YxX|>0|'m۲,۶cGD aD,f!N;-y s+J.@;5kpm-8֊`"x±XD"H>!4UBuE"g1pX#]H\sH$P8 ;kssS7T$k: t]&x^~›Az\*I/8Xcx*0`0 =UVG/DqǶmq\,$#@UU/_K6mZt06(J^xvZEaRRƍ%%3H-h٫D"%'[-)t=cޏ?o002|Б<0h07mZ;z |>0 D~ÃyIgTQ3Y@3B@ ]9|KB] EK$ qH4 kO$Mwܸn \yK.5kVUUUkk+PJ=G`0hxFq08Q\ $#āɳBp5e~}`N j.uvjuutR"WYAa"H$Q D qnl\PU_(-3QO_Q!l J*hn@QUD,8**JQ &h&w/E.QMU5]74-@%c~υ22Ι ؆: .(Tב Jv̲z,+:vlM7M**2DBwygMӦMTMӼRMMŁu]RH!@"Zic!8?ߜ7g*Pfc 0vlW:P'0<DZ9kkkKJJo>cƌ@ `F&/@FPX5B,/=dhD GRybRBxo2iڶ5{vӜ9x&m[NcBJH$Hra2\ꩋV<HƼ\cP >332E+Wz}^˺iƌl2QVvjeER r]uՌ-++YpݤI Ⱦ邪KF-_}L}Au~iдLsjj v.ZT5ee"Pz`c"ąp@`!BXBX C0B@]E &P0PZ>o^رiEE\xx8<_tٶm;N=>*,,PQU53;M7  BDrDBeC)SJWt]דd"0Muj`Tٻv]]`vv&preJ$Dr>3+;ePD̮Ǝʂ?4GZsǓO~1(7wI?0N;D`&ve? 3~O̺~"}C͚u̙rkO8!5?Y{zTXפ Y]g+|>1yy B C@ێdL@qB0RIS?9!7J bNX WT(E=v_bŮ]L2~޽{ĉ .B^REQ ≪{L$RH缻8g .ޟu˲Ld !ƌ!SEcJE!FH$Dr$4rSnվ}rpaۈs sxrٲo~柃ejkhoO%8B7"C>_ʛ sL9DSB y'{U ڢ"4 I+,ݵ q0Q"R4a>bRCH0uMb߽su|`|4N($|$SBc2zfr-ƍ޲e-[Ǎw&I]}>RzЖヶ tLC9H ⥍ B]LӴ,˫8N&qJ2@K"H$#0(M@ $$۬?{ITeA>d]!RiF4xuβ fȶ~1<$ hxp;zo}kЉ&OVvFOϮ-)xҤ[z oOooFhp]  KUA8$ **y3` pP!Gg`,8M<[Ą?4 fC4BeY۷oomm=餓 nݺ]v3 /ظq'fDbЖC>$@KRVVMιW(//̨}%D"~Q<i1s`T2 N˶! ;M6ݼ(Ȅ9_"` ]6}zfoa($ ,iM\h|(@Gk3dGco(lYysO\+6mHU(+˜{֔)`'hrຠ({{5S)BP04MQc(xzP1O/N`mܹ3Hzcǎ:u޾bd2 Q`d<əD ɐD"a۶^!RC*?^V/p1~eD"H$G;_֖&஫瀱lv};w.;61&`\Z _{ 8GD/|B0x/g eV_]0@([d2dعf cPA 5*s̔JXB[z)S2Gmi)ضLIaqw*8ꬠ6i:nB'x8EC!oy&87K̘!/4BySNmoo߹sGMME]6mdUW]D!{;m)UՌk$>wX5cicB뺔Ҍkn H$DrT|);aÄmfnmBx9'cgd/t{"E9Zwh<|볬L\9.Qpމl:0乡h9Y ]]Uພ&"Hӷ))˺nΜ/~C*< $YC!`]]ct(Jun V<`ԨQG|P\\|%n۶( ,P% QPd1|A'|wBD #"haߠI$cI| 4 BD"H$GH(+q>{gVۣQ ΞImg$ U!!Jo6+~8p / 𡨮{l\U~߹{c^ljdsiIM@EilZ@l*hFeOT+B ]%؄`8z3s}gv?Bνsg.dw~wT. ѦK7_я߹3g1,v(&NJ1fyxڱnX]dµtXe |3 nmmUȅ:~,&s) pp8ףqyRI  ذ|?<>;WY8@NI~hracG/^ٸt+!>5@0@?"kL)j^xI$}ggylo5 ~_eˑA m?/9cO^ 6?Жު٧j>Bex_ ;zRD͕ @w݉~z6DV8)Pibp<,,k+(s箚7Uo?A?)MV3\uϞ)οfzҁ~n+LS"v5VND/9췿lΟ HiQtE--ql42\n7nEU,2SY`cz_:z,}jX{(F8n̙ s\$l+Z$T_h~MI t{ť)?RͪU?ccY"ZZȶGJQ..^TU{㬹\])Vݪ`,ƼK?RY54DJlXWJ%l[nI: ~GӯBIBx ĺ==xo>cns\2 ܲ,q\׍X,Ssu+Lizu@ιBJGR~ixx\i}ԉlPݩq)Ica8:BѺ4 ,?Ua'pƘRfŠhE@T8G fRTS)a/D`fDWfth"#@3)QU Xs fd"A>3p f5 6|O(F7͔a$׮qp[wwTK)cs ^[9,$|0;[[ӖީSW3pP[ cJ4MYҏi޾\.W, z&czz0\J),Ka& @'eJ+9/( byRJ]@XA|GtaiYyR4MqU9Bbms1;A̘,@J,+ z u])mzj.A眈ti@ Yp΅e,Ao:P2p, \5,ǿ7h!>Uf`p!xIENDB`workbench-1.1.1/src/Resources/HelpFiles/Workbench_Window/Viewing_Tabs/000077500000000000000000000000001255417355300257545ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Workbench_Window/Viewing_Tabs/Thumbs.db000077500000000000000000000100001255417355300275170ustar00rootroot00000000000000ࡱ> Root Entry29R256_b77c91c9bd4c3d5d*t \AR4JFIFC  !"$"$C " }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?;Yi)-l`i.J#_h *6z CGuKJVQ%0,2ㅸlNQkБW`GJvSqr5o4bn##]#rd `zTtxRy$(c~BXԜs^(s˸rDZδן%~}&~_sӓQO @j*AQg&cԔrov $q&ZK;k)Z2J`#yqzR_k4gQcbBT:wdhأ얿p^0֧)7uo̍s-q6z#"ĺCKig#qøo𣓜Zϴ?QK_mL\K7kW얿}}b8[k ȕxhBb2X׈5BI+'pV&c=}C]O-(%C~sucsak-ҁ۫6~z(h:қkWWn0ɵi$r0I얿}}ab2&|^A9U$*cr/;h@auT` e&1;Fw_dhأ얿ƞ"o 4,Jvb?/$C\-#3X%Nrkc얿}}b9 Ut5{4ۥ`,r$:_Qvw0ǦrHp89#udhأ얿\xWOsJPu@.p ^3֍E/n|g,0sT`,O^⻿Zϴ?QK_Y`Qskus+UFʄ<mc]-(%C~po=u☹tblrAArN+Kz ѥד<(H-v/-(%C~pnUpG3,ynOLz]t{]eR%rARI=+_얿xA(workbench-1.1.1/src/Resources/HelpFiles/Workbench_Window/Viewing_Tabs/Viewing_Tabs.html000077500000000000000000000041731255417355300312330ustar00rootroot00000000000000 Viewing Tabs Viewing Tabs
Viewing Tabs refer to the set of viewing workspaces within a
Workbench Window.
  • The Active Tab is the workspace currently being displayed.
  • To navigate between Viewing Tabs, click on one of the other tab names or press Command/Ctrl + left and right arrow buttons.
  • To remove a tab, click on the X button to the right of the tab name. Further tab options are available in the Window menu.
  • To reorder the tabs, click and drag the tab title to the desired position. The tab numbers will not change.


workbench-1.1.1/src/Resources/HelpFiles/Workbench_Window/Viewing_Tabs/Viewing_Tabs.png000066400000000000000000000364211255417355300310510ustar00rootroot00000000000000PNG  IHDRJ ?7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:yܧs=(.. ͿIBHotI*4ͲaJW@CCCCCC9ѣۏ?ʆ)Q.+<`y{]fC,>]ML>@hhhW'BZMVӝ|vmTxygggWRRISiPwwN|||On``Չ4Vmtgh;>>>ϗoT̳wMKhN͛wy%%N1n ;C7o޹s~FϞ=FNRzۨ)tJym7gt1_]~VCitg;*w7OZZKN !׿SE;;?R.TS"l',y->Zl^| eMx$*CϦJ9냧?NT'yj7m{ːPUU555ݽ{ÇOgێB~: {Qܜ(J8yϛ'.b6;`4-iW۩m23uzMF%(=eSןOʛiܵj\ 3d2.\8<$'SQSjxxCːg涶 )C03ijLu fpȦ+iKh7w9nƌ!!QonSɽw,l] =e#GE!jm(ow}ۢ\^BK̰[9+v=Q<դ;*xsd># & ]i.= ;~Ǽ8w-X޽{kFF!رcm6lv2+o݇AcSSEfc_t5! IK:F)y= Inݙ0фA RV!H.lu-4u@U2IuLf=M)5!̺zioa8\GwǏURRzѥKtl;N;i(؊_y$)ߚD;%e"PARp6j)o[\yidJkFYB݉ H^c2Az%{iIem>w{q)'[,y~SX*L(9Hw$Ai^aL^e Ցٓkwj/&:.W贓{' !ȋ9b~ p O]wV7uv]L\*UGL-qj}һW/f%TT>q.Hy`~F{8*=:Е]PĬlitj Yq.n"-uH xg7< Z@p<~a0_7KERi~4yY9B  ו2E 2($@(HJ/Z {߹bE]:+*WoeƊ+1zԍD,5An|kPmM debGGAx@޹#@:@ lǠ[} aq%7yqm۶effB v=`nh$֔>0 [5QXaٳ**850h KOw&JcvBCuӣ4PR(0XY9EiwWtEC/ vomBޓޕ )q o:NWɋvv_MJHa#.ϖW*ZxWL6{gk iAngcF3GVO6k1u鮧|kĈ3됲zR1EQY"!} pSEQY#}(*I,-\A#.l0UVl8{r9{\oرcL&9p@~:m`e76jNmA"Upf)`g"gʫyX}uO碦ĵcwlgL]4hO0`z4;a,@yIL8EEU}1=("!111SVQuKqq[m0,.$$&7i&zB5 ׎憢ɃzN M?4s?8r;fFq<Xm ;p]V OLLX3V%3n 8eEQ* Ϭx=:Ԫ6=)* qdtXdӰևWSyg ddk;;k+rv*2"Rd4hP39ߝ m 2T˔ %Y 0{SEBgh v|Pbbbb+Vy.'%*&Juw9& 4TW[(稹Y9s]0{+|׿4K VvIg׿e;~wC 8իW^8wZ*,,b3??֭yyym5m i KMf2!ޑSXu(nYqaLАme nK!ǟY](O9O6$(6R~BH8 ¼Ӏ!ćqa7?Œ@. @ x؏S(|6GC_> \pd2w-kjj߿_SSӱPh4b`G pr\Nmq ;m~ݡyzD~IL 4d|.jXzd%zDFӐI} (H6I~#x&gsv]("Jtx?&!W_yƣ{nbjceL2a8LG R`F'25¬߸U&}j G8Ɔ_*Ӕ?WyTRWc9 Nd7  4HtD 2P[l97EQ(M{lRUǰӕ5]H>\{dYߐȄ7ƨI׃Fc>bl%!c^Z->VI}k[3T 6߸`^Xu?ߝc:M}O⺡d7 ߱ߴRRgmg k 6S=m3ֆ9ψ^ʛgwd_w+JJJܮ^p$$$.^ 77722˻6 @Y^6ZUS^ud6"`a=+8jm̹T& !I!\vHBׁ)= !ψF= !5)GWQ:Lqt!)//y=<< >, cbb֮]1;N{{C()*tE(Hwo4*8p0<󍐨uB8 eY'gy/~S-@Fx{8_&^\YDTNmٸg +)ؤןE[n0?Q5:} SZ5L.0O 9`j9`,oWQvL\8㏂j,+0J݃4dpSsNxp-z3=Y5Gg}w@KSM:m.PMEv*ōsٮ?$4Xjө)zjC9;cڵ;|C{jByee{zz̙3zfΜiiibɽw[J(=EOcnUYl{ fq·4Ņ*pO>83ǾI˄(`N?iB* MF֞`/GlB!{GY임:6'aFב;y%%yyy-EuJKK7o|5OOO##ɓ'O2E5//3zs܉MWVf?,&ߛiҒĜy|Ѥ/*0'0HD|~jY`5-d$#,B9js]Yc۹8_R{M4<#?( *wg;`,ݨ2!DXQ_i+r5}48`+ȉŒBwt٢--L\} pp،~I^vꝿp~]!~~:ǯ$H+p•’¼BB Dܴn|/!qG^DdAq0E46%NC5F&Z(\8fB \ڞs5Dj Nrk+~.2* !q ! FS^g2*IJ!ko1+=Dْ!~-.RE%-IYS$׭$\2Lqr_8>Oϱkfi/|ܞc])noqv9xFJ~/W0dzI֥9xμrrkf?c`0$g `HXo# 1刼_b@^Ff~i*lֲJ@JF^NF1"\zuܸq...***222ҒB"..o߾FtҝlfǸҏym]?T474;WQ0fd O0c_EQOocnEԍqآopqajk?tIDATZ*ϋF ( (y-S%ϾD˕.st@?>yy)))011 O{ga.+n19tYGZzLV^ Jϓ2 }] 4X`~r1\ nWuc3ŧLzg  ~R@c`{0}6|? _2H)7+ˍN+)Y<d;d(*7ll.66EAk5=}9fz㷲|$%e[|B$ޡEKKLYَɋVVVfhhثW/iii[L+EQO#JJkLlEME)*J+7TU?\~Q~iPh_ &PSQZkLs\|ME~~)Lb9Kܾ`M_5 @ȬW#BNMW#J97T, ɿھVOͻЂFmij w/G JKL&UUق J(++k7gpcmWZtNsjtRjVy5XXM1wt goM_%&MiPBAA*͒nBH+(|Hs5-$HHHHHHl6ݱPi(QГ 5|j<CTi *jñT%X j,V Y=;mϻ_C!ҀKAEtd}KTXu%n߰4KAwAĶ#4N}5Xu޾uqW~jj3⬉!ʩoc VQEUxʚң:BCCrrrZi@),, }ߵ<'#""=t[h5N-qxI  }uFYh]fffWOR3n Bv,tFɦ_v }mt[h5gGӕvYYYWD.5wwmt[h5ο_VPPHJJ )))vvv];JJJBCCԺ:۷o/P(+b2Ã?QUUWUUDhhhhhhh>zvu444444444ċ/:N9KCCCCCCCCZ:Y"tIENDB`workbench-1.1.1/src/Resources/HelpFiles/Workbench_Window/WBwindow_Horiz_Labels.png000077500000000000000000025723301255417355300303150ustar00rootroot00000000000000PNG  IHDRX$6 pHYs   OiCCPPhotoshop ICC profilexڝSgTS=BKKoR RB&*! J!QEEȠQ, !{kּ> H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_F IDATxyUřLReٶmB% E,r9eٵZ3@068B($H$((\_]MӴ, ,!1 #i'ݸ,aYs.@ ,\!0\Yƶ` qlN`vZ G @ L(8cED9!4MWq,@Pp.#E @ LL%W׊9_!@ @ 8w@ @ !"@ @ s@ B(@ @ 8@ @ !"@ @ s@ B(@ @ 8@ @ !"@ @ s&xL_|zspc?_V]l#@ BprxxG|s1}`cZ ~CDE @ DU<1:UjJgƺHNKTU!m՗-y?&q  Ͻ;WzƧOc99 :"@ @ &" 9ԫe[._]6 NOHn%Z7e{~$zwИV;PrR'L眧@@ ""4[ûT@cg:Г0 85U^;P#;9( ,&m:ŶncLu@ 8א|}~Uԩi-!D@  dyPy%/_tOó1нㆷm(ւCg|][z8 #k<طωj"]ODB]균%{`8t SpYpwr2G/)a3"xegD FE2NQ0 q!,D@0F.<&"O^?ΥSF`X2tr 0D«8,dz6@dhhl6!K K.@ [RX, ]߶mJ,^qW) ZI Fnjpo|G>"@0q~xSj+|%6 I=|'KkU*r9p0:?"^l0r*L2X3}I^S%{aTֆ`P-|.aJ~jdd h%9E)) 9pZ|k/oLȿ=EWN-#}9_x_ɜ fKo +nܤ*2n7:wC_lLO؊v%9|]'޲ 4׍J+ǡ 1B%*H)|CVěooc>ykejYN|um]㿇J!'ιm۶mBAlvJBFiɇ~ =ր8wEqd*{z^v&C;T_c28y x`C fC̄ t{)sh4zjn.p_<<{iĘO}*W% S ^l~b{?=GDoT?1+!S]]2'k}cT&,SfO^=lEio?sgUWOcPpC'8=V1KF޷VHN-|[I)s_}b Sg;c1փ#ose(g?r츴p7OW} JX0TЪ1K"$4MN Cf\j}LEtETgi>^4NEAS;EB]@u]Pz0|u`xо})S̲3C DWA<ħ+`*%Kg}`]ן+.ˢb*yy(0D x oB~/zR p O.ϊTAIOg+ ݣr2 ҙa,)k; a.7޼wn~;h^K5b1V/= H vȦڲߊwˡd(ToG; Py]6nID6ᜲ}mFo2O97,ǴsSƁD4YXDn¯OO~S[Zz͔YgY47r hT|GBk?V3 TxK?)|i<;]9sɻsݬE m}aa̯۰o.<-:ZwrAL`ؠ[PGvD@g(S S&ezG 0D`{5\S1o)J@n#TVzv+k_}/,}\:?'Qe鐍Óvoq7'(L~Xߑ Q;P ل朌M7UAɣxYs9Tt&]T$]&9񵸧*cMFдsxD+qwG},0b|[z D\DsmrV$宰D ;Un;fU9R1c)=lT$a@lpԒ3xfo;^˄\/Z#I#@%E?Oήá&GLjT6m5:bF5& R#L7WJw㺧_ rn8.K L/=X^lI/O"ງHn(G#0?KY;))ͷ 4e\ __ǟJ'O{7n{fyӡ7  S> ^~]ɼoG+$>d2EpgjWHB_  (@¼T:@LOGʧ+˦B8^ bR &0]BY+gMc,eSʹ˶OMڑ]aY1[qмka`ǣ7-%9pFP*41ٴW8_}lA'`r< S+TJOmol촷G[~|lmG%J.)w!#_a̅Ve@R΅9'}zp߮ruP8TvrƎjƝ)4Ou&&][+ j𔍚# TzAm<}*A0@Қ]U1xe`%*0WJk(Я+uE%3s>~ƿקּt2q(A.DBu_bHJP( ,E3U6|œ} h GW 2AF pMgw"uv慕7!iP*À*v~O_r[N` PeĒ dv=; zW\|'0Cm{$!RRrӁXF@cPP(Ra!PA 䟧$Rt6>C|E%5L4  \Yy6 u,@s3,"G6:=1 V E=ɨDljsd0&9Lc@ Tr 2P/s}U\yݿ3܊@016<c-gsOuN|S/=q␙ ,АY,u 5&3Dizj-nX/0S/.4}*'ݢ0R|X@ \ǿ,c3T?XI~wȔlirebäUB ruPdRhzձʹ< Vh_+  $l{T^8p2:b5ܾrGӘ' L`]~7 #d2=So ]OO~DP%ReLEȉh/%UɤR6$2!mG~'f5H8|EW;Ʊz9JXX6M9zcVG,3 pT3Dغd&B1~w>0sت<Hڍ_R KTХ A&Jti@"AA(j <`DLS\;`Vd h,CE ;t x[: .Sp 9n҄!J#a6`1tvF~ dPg4w e/k-/~ۍ?XCGϮp&M e~p;AdU#s"& xmQ$}yv3GŽGqr|MfoS&\}(??37wſC(,/>κ̏ї!'XN&6{rj{3^{y'3K^փ=x]Š&B@?[uM ԳFrO_0d{LpJdVuj f̹/]pgiy^J; Ow;~v-G>eXs%^¹wO%9*<R~ǹ(+-ԙT_d\6+qƤZTb[- Nv\מ2=>NEze5YY~$3M\EثR L4BT$Sp0OeλlclYsYfz /,'Mnڜsq6U =Jׄr(DWCX+l Di\V4iKkssGٺ;}9k AmPţ]B6Gpb:Њx@'/`|[ا)Һ,Zy|^]2^-;Vc:q>ƙHr!·Z  {YIXUS`,l;#kfiC( z"=N~P{^¯~O̲r]?8hbzih ~Xj0־'y2=bwwF2ԛ .9О2ecD'})6-Fӎw69luiMCi$ЪuCi2Co,OBU8& Nv30~w5':¦i=I;.So\ bAo!&RJsO/Z|wذ̻2CM"RPZY 6.Vcn·,l>ߞ29#cθzzq\UnX 8[ ]~֋KjfF ?53 )ʴ']-0&O٨󫂳s~y~0^=pԶˢ@A_ ( *@  B5_Zc^`G{rZZag53CyMU<ܭh^*)ŶѼ"iHDjgGR}~ϙQHs.MU7uܔw%HE@y1M9缨s_+2+v{ l3iz/ͻ79jf-<}^|Ԅ$].7brΎJ54TbN>CI] jZӻm~o_ UVOWZc;@GxU"QV)U+DWh̝5Ƶ`]92(O!=%(ixkz-OvΎ ě1VϢ5C5Aj~E* k3%FK0a~˜x/-zݾQNO`7כd-BpehVQdVd#Dz'lSO!]q7%|VSapZ- =?IQp '|$S@%ea99W2Ꭶ~uǗ[0,c{(&2dPqۖѾէsa;|k̔($Rdu٥ݛ;/ y9L'ˠ]Do>]VwSaMPB;!QIy}}4]y6:br4iviZ(/`/}J,{ ,@"ǣ'uwA-@Ggg끎HOs53ίm *bF_@ 8khUJSոpߦ,9K+L4˵wKas nJM&~u++6So=-O=Fy? ZiÑ~*(2P2+?%FcxF?!L٠b5 QU+EOqW1Xԙ*փ0㯲y-/ q( ڂZt ˰dWd+F|uØ(0XSdvG`|n26 (][;wGS :_^w_ܚ|_xK%oߚPyuY:. J`8UqL5z(&>N+4󕖙M"T6wVPgjQO)2ƽYgw &RWL'04o +@Kg@WnĨC[~ Af4UYssfDB@iU@ n4qy"g\P涟vEMOC7t07]x; x*>Pvx꒚ٰф'& ΃/0==P(HtA %*jJ\W:/z=] #@0dT\@ { jL>ώ^JM&C E@fy]+cFSN ;Ж?5ZJ},΢řu*i +C:s̀[JR]0й׵W-lh'3oHM.S̈p랒,p߷݀NѶ fSN4Xgr\tǿ{_RU)޶CHԙ*ϋ?no=Z֕`q z =lzZW"2%hx?qQMa*(TZ6V3يC[_|#r?@"q;:V1(%,ĠF D1Ӝ )[~jUP(:LRv$iK sz7]vsjڢ5ԙ4!pWY)-X=Ssѧc&Lm~AoA ;.\̚qч_Jvˤqn*q>2)&TԎ׭fULxfίfѦl;K BO "2\LPC6p^Ӷ )7󫏭R%RjBrPgDq8[3I5K#jy ypp sNɛ/"yLVQ*ͻ7Q^zUFsvݍOuv 4:@Zyb3,M:C.ч˗gW J/Ɍ8Fpc Bp(3wȉ(^iʕ0eӳ{=I pTΦ]Gw:)Ce(qzesy&+T)W4ru5!XjꇿQ5늂Q@5\͞~dQn\vv)st}'LV~ql:Pt&)`=U-YP/J@"0ًW_PQ  ‡Vο}USȑw,v*+vWq2 ȥK>1W6~';dY)S;\ziZ W?@P=YϦ50Ù\[~MeQֹGO~7W<ڊ!a@=~*:X2' ``{J8|ߴ9GDJ̫tE a*z&tQ DoU0m0ŜO2u{[XF2)S.8S9wlWfɣjI:;ߞ٧R_FԴ)aPX6OeYU7$D*RL vÍQŸX@$R2}՞rRFF}ėn*P?ZL1Ʒ-twK?YHį кEiK1NYrŬ]]Ii$\sd0D]_5i {K_x߫0:'M㱪4+r>AT0j-\7=T*KO+7=}ꓺL(%J}')Rq5.SbAB "z>XK &TZ:^ދ_z<'tbʱ#aL^$p(9 P% Uӳ 3 ڦG!♵s7rRӦxx Uqd0A蕆9Ǿo̾.G_~K^Onݻ8?O#|{?>a3P)(/ב( L_ ssnR-y/=_Pa3@aw@ IUL=FD+45:UjZY!ff ާ8k_#89GGsThg|LSO۲!t $zf ?㼾9s^-+_8[٬ODMf6O? VOZuqxתٯQiκbPgJd Rc)$[V1#='WcSF1; T6^Z8m4:*mbOVzaxjܓ3N˲ |/]_B: HU>foȌ#>L_s@b&(nResЂUfe@TKA1v *'u8mK?|v章0)AH:'-O,819;ħ8v%"n̂ ,J#V5F5}l\sAA8yq-#7p6m*dai/c`|˙y= 4֎CGpsxmOXȷw?+ 9hacl!E!]}{o;cY;Н T'S@HY#3ohbm7ټ&_U:t[gttDq2yWEb8?ij1λpRrXI`$z0vmμe5ګohV8P4dR{tt)ҩh#r>~`9|]yTkr|'87QAfC4#Ӵu2QIyod^zݻNVzSgNUEPr4Z7I79~({1"a9c?BM 9:So<<y ;{䖩if[fv eq'f*^ >O ɛq3b8Պ5ӗ Kqp߮l3/W̓ #OXG( -Ȏ7lpjSo[:jνGG)ݛ/p5!X߼ǝ- w~ʠ {7_@PLDΛ*ٹxy$!UY'u3k׭8 "h['u]zdP(שH@)?S}Or;ka/| hIν o.;F49WOͳkޓY̍ROflj:#Unfڜ^Tnj[x9p5 êBAc<깳XмuWgo_?umgp\1͸ o7|,rDٝLLߋ GE@>dozP7q^Z?FPe)o#Djצw ʓ)2rʴwX}M]{ROoB +)S *s__fogS1Zwsek_<o 9}lqȓ}|᷆շ7~Á,[@k*KӛD}_oo7T;KgADr ҙ2}6`}}(5Zti_@o 07ڞyw޾ν{}"=|ϑM=^9|pġDGޢ_񾜶+|l7V%[OA%ɧֳ'&is&i g,[lcnD*tfܴfuu海gm1s%^zx[hZu_Q\5ӝ8)XpE">9ӾwxU奈SYh9\O/&ܾ!-G~V˼2Rţ)۰.,G s͔:if"GˏL35ϗ<;`U rv9sѭ_e熵ps!ݲ)A<*a5<>.~a'F^q-<4 PCՇ_7+?0d0eO|5n yHY7OSn U$:9@<Vzny[НXq'5Txk۳Xx$JPWβJAklew_P5ƌގ#˞.PB-ʻ+j>D/^"CӦx+xsF=B@4]?_#<-弃s[BH,ykɒÜrBr\/߿aú+!@E ;{fw@f=;@&ͻ7M&RqAbYQk~a7'6dj'+9p Δ@Pps?"J= tBvm[U`߼|f2ÜԌeDAW~97y!"Cd֟B)lg>= n DT})@}2hС!-g+rs*QcPΆ;vT1O-cvppqe Ae|\F+/vtlZ6HPsecD92@D̉} ps(ҼS2&$o=gފXDEBzx??@7ŀOYx=&Z1wbY,r`yLmz;W!U٫Ҵ)-"PhS}lO:DwAMyW.׽93$$C*7u>z e5a\8%:`uP>ŎI>3Cæ°H$BJxDuO'fcމvܻ [;} JA+x'sw+(AM ~ݯ>)/s2Nkp7:*D/,Q(klLpG4=L x^tx 08PXfHtRq<_TeT[v[7jҥ`x:psݺ*@s;{ @9b9@EL۞TXt[FsxUmmaիGu[zxn:- '"8-<|UC:&76W0yD`ٱWL*RUPr[^IW_OW*/5!]1ƣJO#J0mz}yjԮZ{ͽg&׬V%a+;4Ҁt{sEA1n9n0^ir&۸*( ݲzUZ 5NTh C7zrx-J" W4oxjn!/(;p_2vd˽cxv^q ',dRM|>_iiieee0z1ت'ɧoW\dFܹWx/{-)hYZ)d qA$#voذǙWIUwșª0 ^eh]ssÆQ1nexTc#WP%55}w/Vy6wֹs!?S֯~##I^QZ.Xy9!De[RRRZZZQQxdY?2>Kޘdܰ3XaOҧTGCtu$9Z2+dj:wP$MpgjۑXOr8'Ay4oUp`>b֎cx]!+չuzPgn%MDɒ䫙9R7? "H4Z["9ؑXjmVSl(mGHWJWw+ͧҪ\_T%])`RnXXOjXGibͯ4+nm}[c}C;!`ͭӧUE(9"曇M݆i VxkC2%F}hN^ur05D_ۡW[&<^Kn>cӁhk)7[7fm;;ԙL|~se)LJ%><#^r6$@kkN2E]&>VkBO]뽭ЕL%\"ȔthOvG bf૪'M'8`:lkB1 %?=gH$IU򲲲`0(2[ ww 2pOXfCyϚ<;@Y c1`%Vd]xئ: #X0#'7q^BQ2…mW z WX/ ɛky"8(`g>x*9$gxtP=}F$a}*k6$ׄdw?n< $&O eOͯ~3l& Ǵp yRkCrPgTQ :(t%,9i:(;R-VcrѡCKٴy<5rhJܝk^R5OM7 8U(!(QT%Ra!ɯwc.%A6 #J`-[OAUG 3Rw`O7f%M9w+2sЇѫˤ'Նr?OHc;^4N$OJbt[P1M1^So]V%m;:+xG.|D'!@@cImywV2C*Re@ Η$gS̲mҐ$JFUG_^#t1>~PA*( #£iV|00 ʼn@ L` z0EG޾>w@'/ 6@2 S,9/+=ǰ]|ٲ%U+VYZϓФB"wƌvs5;Vg:{ xyج".eW_gO{1ks;Rq"fD*>-[ תkll|=-]GX*h!"E"( kBGqN خo)Ngu%9F xPFfj qw?^[[U"JJݯuyrƔz̰bvt, = 4aD,ϪJdE)١ (A2 ԗ*O9I1m _n6[hcZ wn(_NT̰t!.)J%MgD5)*Ta$Ӻ9r (DW.QQWFgVk%p=3km`~ά֪pDPoLΫ8@xmOM1^So,]ϲ< 2)ɖ NU"UA9XJ&Xv[dFT2 ]5n8H5geǕ]ëpy`)HŽrdF< y_DLϬJd bf\6gON| x-B  ;K6$9# ~a1 g%s4\v>/]z͚}kMtF_!󤫯~gg)Qcl3ouFn|ަ&k0( ÎMXO~кu4=Æps376θ:6q{_SSrJ$h _pkk$M=[+#s֯߳d Cdzk+畈A%Kwۀy#74{mmŬYfm({vtݚDleAJ^=g+a6(]D\t AT~uz"yZs,sQb(ӁJRa|uhITn9̲ù{9#(ё!D+K0Ӫe(qFNEBqe>ZwǕ9dA?g^W(;AJ0Dړ]b$ D" àN-{@"E 2B5x7-C!Q,~qZZa@+)wꍢ-0o)7m_+*X;IJ= )p no‚3tqq׽)Vr3> )qǧ+3(*fƍtӸ q\("@ 8B V=3g./煠9Joj O.[sѕƏ-_^ܭdڴID; ][8t S=ˍry䜒oMdavTe7@e_Z ;nh֬. 1y{VOf1P{s8Ϛ ynR^xs^P>| SEO<-HJ|w~ŋ)2h,%/P2m-+b( F[F!0 UXW͹|.Y?2"|/%aL酜£m";z Et3X|mSL?0kꍢJ "z%B<=|t-? ]sNp RQ$]̸>n3^ Pg?@+^md?LZ_Px/,瓽/ҪU2 ]М93>̒uǂ 'A=4D"@(C@ ^Ғ_62{G䌦_t8`pn &#P|{W˲+N;`s﹧{:@ 3?9nAXfM7Z p  Q}R1؜ҥi ڵ9zwCs!sS<\ sݚ5jc#|Uv] PFAWtvM_,Qnۢo~O&E  3RB6l 'a  P /lsc"s~IW]< O2-kV  A Бi |)w!/D<.S| C㎁DxvDlfd4&r޼(IsOв}_,n,[qYb7{,Zj d~^} g>H pw-ۿwE2@1t48?\?Ѷsg#Vl5Tϟ?jk?4c1Ȉ>@Vjմ+zk٬YlOo_,'ʃ@ 0BM"LL׭sBQ~xe$_Z?pکZfՏP7Vzpw0W(!E\jزu+T!4D[EP }ͧ.8on͚V*iRbDFDpwF9n@ gG@ ي7_J'5xܶmNmjWw]_WWaZN2ҠH rۼkݺ<ر×1=_o׭#y1 *5j/4k bՓV _?)0>슦@\{ 6dW偨,. @)b%?r/V0߼k]de;AqG~ݻL 2^m_ɠIqs=X,O`pn ,)@(6E,6q;nPF&QgyqFrfQJ)!ĕN':9!RMt]4MeJ^f(/w_ْ%9oݲ啋/ jŊЭ3gvG// ZnҪUЧIp={x EKstf756DN1v__'ފoZV)]='L ޳:;k׺uǖ/_sͫnwgo٢COټy67m˗35kJ#Ȉz+{,YI;ԯXݤ'#֠@ P"RJ%I4,˦i HNrIItZ-pLt^➅*$Y%I$1&t'KF*xt]O+N[}8Ha|7n f@Fc{XG{Ht]'ON+ j9,]C'vt)ݻWiͺIՕ_JÛgeVjUs\4XpNk+_F:H)?%OIoS- x>1Vzm|$gOlf.}T{- @ g""˲k(J*rq 8CJ۷&mVG)~JUdUQݓ.@4׸ky*4MS*`rLAFncǔo7̓IKrޝ;v q_nm{D pikW,>} 0<^if OS{饵^ =4TMџ׹DQ !7|D@ E@"@$Te˲9ozn6;#'<{+4iqMօ0dYfItz]_@sq `c]{S>$X0T{S$i:2sݺ {RQ0кztyhUرy8{oacrq=rlDۏڂe ,[x"StDeb@ .u;eٶm8Zm]_C>柦0KiX$AIE4Mt;_]`"$;78y c;;1UTkk/߹38p+%ՇJ:N+VT.^驨6z%cwaE" sxӀ621"iohsL:Γ!@pv*91@A9,Jr*_k-h馮9ԍlO) A뀉`vѵn]ɝw #4}tfI ni)`~ s4<@7mjj}G/fR1>2yr viS/ٳ}&# j5k/G;:~}:@QXIΟZh5 솭\&-C1Y򪫴 f,/ZE4JRDOFJ(_U\9{{ćT^vY]:[RRRZZZQQxhdO퟈EU Xaۓ)¸AYQHg.T*vUǞx><ڭdG juESiTbU?@;T86RyF@.$ VT扅ny y?@?@s 2!d *Cht;1@C,Dd\RݜD82ʫeUyvTmLn[œ@10r1W@   r 55:u*b5 ڴs+`֣Q*I>{[}4@ y@sP3mλR#6(s~λœ+ cvsN H; IE@ 3E 9S~Ý@?$uvDY!Mv v8gRn@C4*D? @T@`/ p`eQ ̬{ؽnA!0^iP_` j24 @h yWxA?5DʰH@ PP%Nu8pz~rn1x6L6gxLx'J*$V"}a*@GS~T;6!fN75+K˗?au)yATwq@_|B )K :x^.5;Q7Ȼ@ "@0I˲|gf;E$?Om.x }HkQŹ8H^t `{U-[/W̟_1o.CvΑORD@ g1D4aBcYu'K.xuBSvE"X,JLtGă9o_̲e*@ b9.κ&JX1`/FC,HN(x]1yE=Jto=x9J龰ֈwcP([իYG28y2S [T_|e XP(DQlfTɜ7:{ބ0At#SF¼#&-!ކ`M Mc)G*CpHy/s< -4Yٻ3eFB$I$ɲ,IRZ\(dc0X(t ;;Gh]'J:X,0A㘶=7Ҿ })s0``fc"* o< jYw44MIRD@ $xB]7&^)1: `WcUpw0v`u/)wW# |EsmבVYA8tKo7~ۿߎΡQy&!i&DiL 0 At2-D}3fxcSp8#m%B$apV.ă=RvN(Apv۲,W0 ۶@ qR}g{*_K4fu"nohuNm4z 567B\ Jkjz EY!`&5x*xOox5KR* O>UE.@; pu"@ n8m[\m,{RTX9R"|ozT$eF)Żri g"I4iEݯD>{}*׉&pTN6d0hn0x'`TW%9ީ,R/p#/: Qr7A]dD)Jk"`Dd\zAĈ3z:lp8g]kDv{UUPtb(&D9O% @ גȲ,EQH`I]o֒XGEz3%+ )O+לp:UueCl3PmL)eC:t$ew>nlc 7"@ +Yh ґ\hVdPl;M)VI`;ੋT_c- |I"@poV|]FeA*SI˘Ne<*VM${wkvu U J&@ 8w`g~֭]&GN$X2)6I$("$ $}&^@p$I$1IQmU)qR4~۹QKҀe96'@h=E g*!bpm۶m7Cև_ؙjq#=?$Ͽ?|6bqGm]6#"@ ٳՙ33Xe% X0~ :,ki=8iaF"HRTY ҦT DXoD>Η2P*!]@cZgVZ׮/VrSx0eW'3X@ ̵7gHڶMN&0H4ʺ=CAli;!J4MK6%\}elA2" B }~o{hz]]d'/|~_4IrC5ښsUUPmc=##F@ H'Ik\s\˲rILràᰴokjbGt%BPA$y.TfV.X 9y @ H+6.aƏ jOZ\5DZDh@0 :|-DR<(t9vbs3ko* ezRڂx9Dg +wH.HMovm%El_$ D@(qwuc؞A]t'Y){`0TU58ZG Y0(F@ 8ȳ)rb &<q#}}M:z57 OAVZjkFmin>3bvs]~KV~{=>1 T 5i{LE@fA˲dqJA4#55܌Di0"B;W:UilTf̠9Ŗ] 8Lu4 #H»}>ϺR|S2t(h^@(tAw]Ro:_ϖOUWfUUK}G׬1\UU/Ӯȴvg wS>P2~^z{vtcHOPu -+++YEFA:gk] 8T4D"a$b<%Gh#屝Ȋ&Hw*֧.CәtiB 8u ,}8wa t}[`0iubx/Vr㍕T~_)Q FKefB 8Eq lu|3^>E/v`u]UմD> @E Ni@X܄msvY_`0ǎa{;9zt:UTrV^.WVJS(&i3g* }@pNiwJ&aRIn؁@<\6)I V @@~ntp7W:؀MeA4ڰ{{iG0^`HuuRjjɓjVQ!+2 ri pH/EFs yP:)N\_/rha@('wShXfWûhM^yML] `uINMQԧJnQ@ {qPX0⠻Sumc,F47ӎGc1}}aJ3fze$e VQ!UU`P*/pps/ٷ Zȴ\ L&1OIՍv\5y ߌD%<0SP$IrcT)w?:v\SKjaӧϜ9FӴ{ _^ )F}Xӟ`9&LlYVfzgW91MZ[iW%uu~OILe, kk)Z4 ʰ@s_+b/eꀴ0 d":KG٢9}%)āZN!7 Q+E B Ԧi%z O՞;vk w-㥗R.Dda 'vnio'bsUikS[Zv-%^gL8y2ΜʸߏB~xPQDAlZs$ [kMQy3iD֮W(|u8@)m{֬Y}Nxiw}zJvuIo ϸLJ^M4Me]s7j? jU{zqe믗> 2D.j @p. ۉuH=qr~u?aYmoWcx$,@ˢ9^:TWÌδi*)A}>@ /$0M3JYō$kwU\iWst4P$@`r2`0sڻksX'H"$$!P9h9O義Z‹?ѣCuuսUu{Y{TA R]X*Օ\*40Ƃ `qa&l\.0n3-O;#uS\rE$qv@PQ9'>A"!K'_u4H޲@gD tCmC0tҤ)%Y2-64K=j:Ț0sjy_`\t [v`s&AB<WS;^A-;C9)`M;un e!( íXtƜhӖgwrn֜G ߽}PLZ:GƑ(˧TJ/u$':b]-0j,2EzchY~y v5ly $O3ȉ(Ý#vqyd٬ٹ9Xطp2PR3i!.HHHm74~E5.@!D shpkg_a@SK8]GÊrB9@ѡΡh܌9! w^a 9+ͮ-Xڂc&>QY|7=УO8.bZ?te!`8 z<.ediڙQatgmfgNDjkyM b2ؼ  FBhR[gi `UdY*ٰa6.Ue0qmpuA)`ѽKJ7K/n7@0uћ԰ -1?{T\Z2  $YxxkOtlX{[2̺{$zK+ilk:֮XsXû4Ω/?pՄLF7oz+#,`݌*p'8rsr09RRF $$2@Otm2yy\6y2\4࠯SFF|1aiFiFWZZJkjhu56KJ^M ذA7<])0 `-M`EceÆMx_ˌ Ryx%rr8,@Yv=:WW_"M[f0".CYn`Y"A1Ԑ$quN(e Q@%7.QBBpʹar HVѱ3aN)ak aL%y+0ղۮ~GPg=h%u9O i pg~$ykݺ%1-ڴ>9O|GL~h'e'O ?_<'$سeٟڜS_.= PV|AD?w+Zn>l?g}&(Hjjl̫g~ɯS[/¶C( V>O,7㤱~kkG5oҿ;:\`y1}u v?y)w78욏yc #|5ZOz2IlnLFY'^T[4x5?kO-(er$U莧~u.ў 㳩+?{GGLhdžv>z_ǍC/h2sgp r V i4hNO"`q:xtdTv9y_4|no1In7+)!PYUrl^!*cXXIMjÆMx ҈=ԫBt.5#9ӦtnHNJ,.\4gZfƐ(#SD;d$ Pƀ1NLh v@6)L"Pv.ECHXAJAM8CrD,J`6l(qtM+c x a Lm\;<&Ms⥷f;1JRV՟*iݩ \{Gp2M6m[?5Kw^1SbV9tg9r =-;;[[ ˯j'Dt>< jl<7kR~LKyŝee ۽)~7Oz*' @DMfY9ytǖhZ2Hap 7O^tkwCwRN$I>]ID0DڳɕouiVG㈳HLok>Lq%ܶok}C=o6&p8>5̧DVwN7Nu7l֓E x4 Rh4iHES>3ΣuMFƇ1  `(/SclYcB@!) $`I4%p{=!goI#@ׁcx8޽(IѲ2ZZ㊃nrذϷE1.b~V a&l_Hj ,"qU_h ò s7eyfiUu59E}T7@QzRT?s Y\(KP(g 6˜1I;ARzrDqHTY, "D z &Sʦm"&!d`  5X h& II +$d % r8#:R^Sc(Rc$㲄0v"G38̩#W%J@@F Qb&I&F|KDĈdCAэ8ׇ%$39PXdpԋ@h:i# yu~ݬy4teV~eEacWP0htQkF Ł OPȝHDPR;6cE+V : N"̟q qI1ܹw.UάJĤ*Gh<3 pAf2N@ԗOET-?4UV!`pXOW78}lz+F(i^ -.V^2))- Mf798A4U " \I̊ o\tUaoAhÆ qaYs֚ELjFnHX2 |KdK,v"L$x"ހ_@MD$bp@h0")T$5\trrR#&7ݒ?[)@CpȂаJ5Kԓ)0nW a㟻`ʭv\4`~y6FsN ՠpߨ >Cwq8F4cts(0͔jNX2Ѽ[ߞ(sR}}C{_wn& #7 #'MN,rpXvHMh)Sqn )cRJTW>wI9ՄjH%.u[o.(t(8V5"̾Z2\%3ștTL&a1e;/W aUU '|hxdy `ngo^4(@iI=TTMQ((TrM9$$eb99+,.g4-I0x ƒ@f FѲ29#w8v:! E0noWvdB!3?"k|16`?Y6l" K5Ԗ1j` xHt $F@M{u9Q@4-t55B~n'J@$L4᰻9]"` @M ))DtRSlrD#28%Q(@ .a8L#[pgIa3gGoԫGcsKLw ͩ+jQoaXJ#c0J)·anIqm=?.3M$qwVո\Z#+w6U,toYfSU={x11#UM&aQeP+g1(3)!yÊj*192QşT`eB[T,uBEAwB;p-rSЛ_1gI֟W=P.>pº$0Ӡ2F$Fq}O_kDc&kf?-nݶ~(:isϿ/-A.p2k ?g78@gnū+dTK8BNH)'&19J(%Jn>D\ODc}aUMu7^_ی?5qƘ%(=@$+R@4YPu(º}AȈH BN4 !) wIfQ)/' 8Iηa}%̆  @ZM Shhf F1"(ayT:90+*qEf,úJJ rkPe88`}bd]>BLNm3rKӆy%'K2Aب:5E%މfJfm| 9ȟ_^uf;,3ʩ/mo?nR\wro ?Iq41vf2-S%U^WP #Ef}$sL# 4q'/ (έKsjqU͹au$5>a~Q!%K^ -J 9GN3?/oهvXZET۶;: nG/c*|ᡝ/cR3o\Y!r FRJ)@X 7 ٳW?\-#3;0х,Ag-;-<ֶ^ҖL*5o~j^O$ƻ(u:/` y+R , .,4ML$ɤ#p;QG8${!J&A2 A~px$$+(0 5lذaMcil'VXiY,iaF yI#@;0Caer!!"e c2` ऌ ARD c qBnK\`Cu4wMPMl^}]]nB~Xt ꊥ(vxʊK HM@ 0 dR)!!sAD$1E`ׁRוR$[Z_gwL3Qeb!<$K& KKBJ:HY2Ą"S."=\gsUhto`(A|!U Ŝ"0")qf0h/)).rbL FI9\dAGR\t(>7N ` 7P4IaX x\f'0M9f nN(B#'zG*ll2CHO.Q20f'  iN1c괸@ZV $:gc1!TUN&EUu$wtT93!.qy3 H(d沢"1k\uLm[3z`e6lD ^z24jGX%GsTK:Bp.ZHDR;qcZ)b6l[v;hՔCqCMcIDvOd,"(zT"IEc*Ţz&uơ܂'U@8!rb&cqRDBv*"5dbuh,iR$h&c1"%TPƓI1@jjXT"`8'UqQx.hc$pt{ݎ>SMĒ)rAry<24S ux$ )^)D$1qHR Z2OVFddH1:^SшFe#hR'c 7SxB.Sj81x.&uexsΐpN ՌT$$B}Ȥ,?,pJ´!\ǥd;tJ4qDJ,ٙf(>s`,(${>Bktp7=h FكNjÆ  /nlYfjjϹyRȴ^4.*( q c$bxw9޶^ԇFOd@1Ƃ c6>@l@pF)@WDqҖ󱗬d*Afs1 D'BKBQhc='L9Y2 _na,N[<ǎ9# d@tI/isf/N56cxg" du22If/.@r.&hTN$hT4 wr4͜zV?;I0KDڏ'o= 6`;i3 ɗG9`C/ϕ8e&q(@vʒml>moGpb4f XbQ-(I(i:61m|~;+@3>{ܕp=0?C`(ζ@u5/Sj)zP`*CUHęL:4K3ņq>0Ib.`z$;xVԀص Oe2(E@7{+$l0'4/Ow X&  qEl(fTB?0LbH % " NZgZ-wtxi_2M%\!l[+ cK4K{#aÆwg  JBP(cLTUb")r#c1W4*:8 mm(GAErrH(}zĿ2^?O"sӤ: or_ǾM !V|7FkgK8EQ҅Elذa6 6nxC1F!.G0@-Fv/ J ۇs>BmpI,^ aÆ2ɱ,9(^SQ|s:}_ܺ.GeӔ3I& 8Bxt7ŜΤkJU0dm-WlĆRJ/p$JE~7y2`P5SDB$K?t8N0p$م1 32.`ˊCz&-ˊLj" {HcxrE,=,Z e !u]u0^lذ=!@B/(Anuz|@ PNx`"$ !t:kkk/H0Ƹ(FXQICiqgg',WUU)rAZ%,JbEy -R)QeY={`$3ғh Ks~XqGwWk4a$AA"2~ڋ 6`B²N5!gq{Dc& Mx6C`֬YYY١Pr>۶mۻwgΜi㣪oQUUuWʬ5k]uUI' 3ҢǙ2!tuIRGىGF!0!4KRhK/NBGټy  =+!IH=~i5`,b #q"!D)cM#e++oQ&J:aNw5!40 +oredEkB3|1O0< sʜj+,{͆ 6`=m {%j1 g10d#0 vu]4ekSa„ ۷o?x͌l^ilƌv yyy*S QMw^Rg$2<%-_r%I:@ `TU喇TUP:;&%`4yy>A6미 1G|,p9dfbF8=EKE WE0 hfoo;Y0~>;؉DY9D1'?|ZN&999h|ΧT* jc>-Mq4)؇I ȸl: >\@]]]3xZ=C'8H0O#EhBl.KX,J ðECp\qks%BiZ"p\]p^bڒ[OHBu8ձ*rŜsghoGDhfU4-%IY!}> >#~&$^-Ox3t<#u鍤=ӫ! 4MiEQk&-ٙBH̱Egg 4OW|HS8'> #_9bǬ2}6O=4FH]Zb 0M0 QW9hDF#H*[,]]]yyy&MgHyXRx |>$I(QJx__\q3L߹$|SA;v(!c\ZZZ]]mݻ;vlddĢH҅N8G\U9eLR|ٚL9# GkDBh4>a&lp݋K(˒  Szbt؀0(s -?slU Q~?6;HL(CnFzMDn^K=B pdWWWee8[;vl#3g ?~d*랙8?#X~`gcglƊ5M3ӞK>j{W?ZVYԮ%(@ ?:JKKX[qURB^Ρ %29ǫ`(b{:U~'-a gmw*/>OYX>iV,R:Rr!dmgQSӬ?ϰWMBF}Cba#h2f ( H\)P$;;KDF`1N .8O'1vڃcX,HPBd0v.J}[CrxZİ@( H$ `GGG^^';vtxx$??ߪZUUe /:M:00888`+de)eg$29>Q@B_N$Bt]_hW\.Ki clmT6lDs7+ $~|0o݋[Ñs]y0hGY{kg?<#H5rgлu 0oiNL@d?>R}rq]0C3LDI9bvf̜) !T;v/A}>_n^.7ٷ8OJˉT4L.+ӡSSWUդ1@5D8;eG,KM5X.,0C7Lƌx8K߳sM)2 #߿} &(&T$Qe,nC!CTݠҔ 1\EдцFd,n^CDfjTӠ%|ңnll,..|a߿c̙8E)mZ B$Idi35ͤQ.+2N)?)H,+!QbfXX "ARdDu B am+¥GY BjGGGii1Ʀi޽.}W&W9ԉ Dp(Sɔa$?m=IVE*QJLM=rM$E'ab )g bPXLXv9.hkH$ƍ&rآ֭[}>ߔ)SN%hMPx+4" `( \Qd+_Ns,Fa;vѣhRYY9y`0DMMM NAϟ(L!:Qswl*1u{޲l+BkךyA ӱϘDK# 7GёaUrrs !Cm]&Mq:`e~֊,lL?H$A rdY***uc<889cƌ5k("˲} Ƙ26<<9S%wwu ~p>e1X^zI%KX?jpppڵ㫮qM4 s,BaÆMgǜ[7\׍S7LL s=޷G\o)!`4ʅ+nS%:xaNx%ssѾM/:8t(HC0t "!N "ѡ0S4jھU۳.%.3`Q$Inj躦 X߂DѲS3M, L]#3(ʌJӝpƹ(--ݼy$SNu8M+VTKiw6a%%WO}9/Yzk on<2$_Z4%x?Ȼ52D<ϪvCCc7 Օy.z25qt㟞\?]5! y{%Ijooollt:yyyVT*SO -*(((..~D nkںٷ"_ך+w$r/ZpAmYNP;XsC{6qɥ~d B G?[M_{r57߾rORm۶uww/ZH$4%I/900}k6@K[)7Sckh82w\4& difiIdzYHVLpvNt>\"A1`4黎 Lr`_7*#p0F,ڳ푇٭_I!n޼yppp(Zo߾X_ҥK Qsvv=h=+v흓C__zC{b;aW OZd*' AQ$gFuݬQbw=k hRjeU>qbI-߼h,2P9P2{~ni' DX1'&a|kزe,%%%VxcW^y&MTXXX]]maӿ(X"+lBp8DQ+v pB!Bᑎx,nUU\SSvGFFFGGcǏ%1 ӇǃO PPPũ577#-zE墢"UUST OwBhtxxXC+ #yeeXRsI>Oeh$73)v\H$++DEK쳹PQxJao-b=c}5P8Hvvu&q_VVV^^&R*jjjiim CE$RҋghzwD_뫫}㫟iˊU_ɟĴL_l~#~8օch&Zx|8 !q m߳w^KlYHN cAӀLQ0=}pv /y睁@0u=CCCCCC3uXqD˦~۷>v1-{Nٱbͺ?>v,޵i}1O`&G[ņJݯ?챥1&o?֊^ $+?lJT^^~zQ/bۻ|[\.˼>^q%2h W~2/JvOv0dHN\0)kM,9OܱHRuxg^޹x#6gv]`m9.a9gAźzb e{  q9g b -;Q꩗~39)G{䕃L\ f(ZѠ9pgװ9yw# ̌uD`v)3HpsrrB=G>|-[x|( C:M rnt pgY !Fi[[["2#B---H;++k$p8l EkkkaaaNNpLv{<(Ir"_WW^^{tZ5PAcxZղŶ ml2L8x777moN&S$rO4%H@ ;ݫ ! Aػg{{{²,)uקtGZWĉ)'6lذF|AhkkWOYfJp@S02=:Q3R0X9q7qJ F+yGZC2@dܽn횵^08 ̽+t~ۈ 7ǿsLغ R%~ꓷ]&p~n8Oo) Dt%q5.7yGWݘSmԑ+Co=+]pIaxWK.yWR~0kԊ}O>cǎ=CCCszѣw$]*5+_xKƀYwWWWƯ{vp\S{eG>5od|B(ȬyrQ 5Q1!=6rТdۋ\ϭNs?ݻ_}լ+V`0XYYn3Ć/$f껟$3v|kD5 &JЬ;C:/[Uh_٭ŗ'.4EȰM5\t.QOXv@O6ZV,l4% n0u4rw I(&S pdR`kƦ;nI”0 AqJ@K%FSr:$\Ft͗۟}k\G*qˇꀑ]Ֆ… w/7䔗w9"L7??oDᣟHy~^N{ֆ `L$XϑͿ/V@KWHpߑ\ة@=%u.;]%t!AAtH\K|#sni X%hDBI)<TJ$%j&"GvkՃ^{s%‰/^8qBÄi|@%hd%f0 =E-C= (LEdt{vNKr0isk|###_[ZZ@ s:Y2`E}[bE 맀^H,KmmmMMM]`Aak)㜇x< O.z||2^ `F)cahhᆆYQB99'Lam42nV.)-|uuuEENBQRR<00hrYAJ ~BYYYUTT(H$LӴdvvv$)**r\ۢ2眃 V;D ˫9窪F11e9/7/}7׮%UU99t4u2D%1nmmeppMDhM",CdCCCggӁam۰a6Lalyj;pFT0ܤ._[y}(NӚb:,G:5άoط?_6:y.螇 ]2lo[%bhutT`jMYihN(rw5t%'V<7A ʰ?ꪥQƢI%1I tՏEN);MCc%K<+Vؽ{s= 70qw+,Pמ3G}7Mv=o}YMtbPFMY\޹>> KN9Pp-?/Mi2*d&D ֿg]{:П3nEsk20#-o;O y("9J2Tmش}w}O\(xɢ:?hسl+aݴ?Kn/ʼ;N(ʕ9k(^q<Ȋ+ /` p uuuyqw"!&~BO~f~ߊM{~DN VpN%Kۇzf LPpM_ǷL&! xMԀ{ڼgѮ$ 䏟y"c-o;5疰/s8͟m~kÎ־]`Ue獍 )wO|G /7G.īܼoDDνCq3sJܼnWgEJs ݹW79^p;y`CWY; 񤔖͞={͛7fΜtR+ex0Df/(;sRad~+!9c, N6 ,;Oძ''0T~}m-rg{k|ч} 9h| ~'9~-v.鋃FOzL#Ӵjf_o S]}oYvkɜΆ-Eg `:4,1RgSΙ3_ Õ_o)K+՟ &ws%81y},N_ӳ_08ˁ1\zJt"-u`o:%j#MD=ae;h뵵ï>=A6zw+@/c- ([v8P;eFzx)(ᕛ}~ji8wtӓ?#5ϼ+K{u']vztW+ ]"k~T,$д|lC.Ζig[9(vlitv EC3&Nv^C Xnvj0 {e544<cΜ9g'NVJ#W88;Bh9Z+]r_N)Sj뚚$I|t=8;θ"d~Ƹ|nF~~~QQё񅅅,#M|m9ᮃ[[$ .GdR+s*xnn>c,oh%djdgf-Ydtt9z@ ` E"4p2V@I&K"x"*8߿$;;{ppq`?i 41&r:N 360Fsrr&Nl)"QJsssLn˰R:;39~ 6` H&N;Dmi{+.qIFL/WM-&txQ\yQmmqgWgQ]wBYsi;9cLTI.G  c`PbraUnڼyƎaHHBrr*4`T!C9tQ]|3-[  !y{M'ړ/캳֠d]_ ڶPYA[0ޖ=|~E>rMͼ3[ۿ~k?k'7ۓE!rG7{y|/W5\l B{:q*4߃n "ڻiOOMpȍDާұ%+ˏteJ죑#zY`z2d'sO_/FզTUZ\Tֶom4Ce^PAA(Bk[nQ8>\wAk^֌Ist=Y,(z/2)_Y~њbNA?pZ_sѝ#% >}Y@a"Hy>?_|呎?Pɮ/dg~ɫn-鏿3='moa<˿7G8K,==lqՓY~o~!iԿs{wu2αz旿~L^_{?/?t܂&?~,S뙱=D1΢("tɹoQ->@7^1̹&亨stK[O`|Փt05?܌ۻT2+ ʥ7] Jyz DGX?3g-Zu@..ɤ<ue~irǎ_|eWOEr'7tF)_wi՗l_?7/Eۻ;~#ݫ^i>gT{װ9C\=\踟N\3C.ؽc}<<~jᱟo5/.۶gɷ|'O9vڵKoʔ)V9C!jR)999CC]I-c` I3pr9犢47{<XH+!]7 )-1 eH.t >Lb8E_呑^ɓ;::^%K̚5{ǎzE9cN,"RJ+,(:uj4M&UBD Ǭ Ng8 WRRL&-!jnwiiU"k6lg%7lC`nU\ٌ,'UZ֡ށ!T4bjɤ ~UXh ? sR C`0  b?I1(cViL P%hx\o'*p0BA YT5F"/g.^lن NgyY$I{}o{W.ljiFrT88-&P jO}~p}bR8!jx!^ =50jW(0z4s nuAwȆXMW]˒.PIX@G]]puqnEnG`@$()Q#-] bo/jnL»^ݴPʁƭ/̓#nљ PUuϞ=v"H&lF)K|8as(HmG{{ԛ&L( ;r&z|e? Dhg$#{NKRъD |OwHBpӱ$t20E{x.#qc}' j˼f"BӦMmmm(`Ahmmݶm[$IoCdbS֔ɹ(SJUw}kٲe疔4  8`[ovq6"Cm-{ڤ\8=q1 º^y5w3?\qd Dzw>u͜R<ߐ: NSMwozG]V-Ge qU]}ߘ'Twc^21#b6d5=g3+N=pCs$H^Ss $lWenܸK$,MMM۷obYQwK͹+ƅvmkW~=O۷#^n(܈Ԡ:0!*` vyr(`tZ~u>O4d !% wݚ蓿ĕ ܀XD[?./Qqέ}׫\D8ǵo6o?n׾4GhHw3beC_oZP*{ĢrYɌriuw9f%uYSRU 'bHX|k aC>"Irh9/+ʒ#a޶m$cǎF4J/Maaq{84͡޾x,nǓ$IQyZ*8T-:[і7 P:i0siέu? ai>{>[tCOOۭ׫;$I3gnOO%qJֲ n `KKMJJjƍ(edYlHssyyyyyyPHQ˓e1iS\p;? ;"&d9eYeUU E1}Ɵ~$bd2b:'#34#ם3MeͮV4n8V7eZndbnj:=& $9=rb`&R;n- 5:ES>v G_C>@}'5/w2E 8r @3Y;YwwO<~nlʕwyWoּvhf;[jђG YqhC{h/̪OK')L=ѻ&+Hh'Sq |aj͹bk%n |G/Oio>[L&7t1e>dv5t dM]EFl0ӦVze<5tL̫Ch ,P}('DN=]ۊްaAGF^Hc74a„S(#ҧNF{Ju6r\L>ow/# (YTDrs10GpT;VW7RKzB8'.\"c[ϝ_s@S.fnb>>((O&S{|⫯: 3GwC!D3PAC7T ̙yŗm)pKȱKʧ{5CccãXwIpfU^!!n12?i\`h wwBB#]ZEVb ֎28ڏ :wq+yGZZsizpL4˪\IP!ELȖH)ҞkP͔Aȩ=}j[[&)"Դd)gaaA4B644#K4Hص++=7>ݹߎ3H1ƅr|iDk4Vk/^|!yUUU:DZ;ePU5[[[;22v{jjSF`vwww[,AH ~Kq&I>u!TEuV ')ذa&lG" gz|#D{UUmUml=ڼ=)+]\qF)froiUq]+-5{J@ 7oz~{^OYcB2 %`EQ<t6,ꥳ\jE85l]oc]ܷ kfؾqC_ `0d(Nb|Q[{8t'شic0.\P{{{\ӚڬQ`Ŝ0bhZ'Vǿ.}mv95M 1T{w |,I䉹}9U9~?ۿtj H\WUFn) ѣ;>| UGV9^C6ܶk[1{|MȣGz<>V.sލߓw kT *z4O/p% 8$~26m655͛7}FGGws_|%VNho<:PBN13 XL`S]vpdvdֆd.-Ɔa2Ĉie@O}#iȲl&T]m-붊 $46 +!D}!e˞~iJm6{իWn/{)--1!fJ'|,a0tYP1`ٽ>nܤ,Q"9\`1(; cLDKPvr8q禦@LоcѲlMB844?;cʔ)+Wܰqc_=qwY l|Gq A舸@ee )x]%x3>\S[!֤F򺲂T{{xD `(%ZD,FG,?'ۻu$\Og4ztUpN+˓MBLeWمSJ9d=/^ּEy-;[`kinq8<];_zu%+Br#CYr"EuL5X$*? 2? l@\\6Q^Z0% #kyw~;qď㪚Zٮ}}}|ҥKO~9>xg%,TTT9r$&9$QQE!L|^c| PUUE( rNZ[WWQQp:s0u~¼L1+k4"B 4Mr8 vhnB׷w^+y9LVUU9bm۶555?ȑ#n<ϧ\{znkyG:\ZZRYY G:Lb:P(tF[+*ǏJX Z=lss .lqmm],F]v٦(DLUl331  B Ĭoڃ)G;j\Wf}ߛ/t~lbei'y3,8d = ^dH(E C.z;vcg@U=Kc[WJwKOkLE EW/kM.. 4<[0mFn__1wx7FRಙ^ x#feкwrP0gB64{G;^!M:ENnNhw?a߾}SN̙cfOOU"HOw={LRSSs+ջv?EgϞǻ{9bXkkҗ0Ƣ(RJ}>_~~(HB𰪪(''$2XtkXܪhr)PBوP?M`e' &ttt ϟ(( ,رcGc㑪744Ak5XT?b% BQQQeU a$ɆEQƏ+w\X=t8WZm۶s#Qyp;^.6lذ!NRp1Gsr'ބp%o~JƱ )Ooՙ=;dڻsnꥋ?I!7> @v1GsdEBqEw|hRwv DDӸ` ݹAi{ү~o"79KSrK.[>]0FUb([[NlѸ;ʻD[6{>,4(n2!Pt-q4ihh͝>}z~~~(E 3TVV644D@ p6g^x (rMˑfM~L&)@o5zdHͣ9sJI.zG_S,'%.YFLeܬ[nٲedkꫳ;;;7 cΝH$j/#()lG\걁q2ojqGsɌ Şx 7\yŕs49\y!+99Xb jVA KPth(n+kJ{k]}۟ 6/\ i>6\O _|sTh]xɷܰco<=`:|?n\:˧vm:}&A*?>?eUUȕYwf{$,/.-ԽԽ[qJn3sǝJBI2o5νs9uɶedE[tGӾ~esJ 'Ԯ=F};sݽ?cE;_}ܷ߽n␲)]I}-'pWe /}鲳ϬVj)u.^O>}z<߶m[^^ 'p͛7[ӓLO/A=:bvP/u.՚ fo'|c2ܳH+FYpQWSxW^ԩdisOətGſ^^d`Ⴏ&z7n߶L9-׫b!'Yω+jپOEu-֚_aʛ`ZU}O,+[4jB[Kc0(x_スf17vTNP'_}5o;n U5U,_PGNd1yNdTMn޼q],eIUUuٲew}Wk[cr)ŻwSF"e@Wρ#$ FH&¢£>wݵk0m۶麎1vͣh0,++Xùp.iv[[8>/##öm۶ qz}>_`%zgqo,p!˦9~6'h!D__[ƍxXzFFFMM͒%?pC{{K\SA?m!IyY ;w6x 'ղjT_<޴a+( HiGݦ qTo8\Z="S6I)ݶm{gYVUUUIIINNNjhOOϦM6n ϟ_RRoQeÆ &|#Rw͵uՑ֝?YQcjKsyqW|5n8.&&܊7׭kV.QaBΝU]6655c=޶1P\Hm'bŊ䔖rΛ֮]6f̘9s\̳&;;#$/\peiްj&S͛[Xmrd2ްuM;M=W:zܤE寫KN. AT%"ްa]m([>1UoVI٘xq{O`A $b[5wJF9*:Vۮm7Z\cKu[Z3ǏaھmK ;vC#F!)Mv+ٕmuYPRU h箦NNp|kWcFeΆM;쒂?f=QVڰ~öTLjJiSS;魪*..sG͵۲eˆ E7o^uuT[lmN6A/huI+{C== CZR-[YR rrr (TE]zu$5kV]]իM6(IJifOOOwww<w'PȥԽ6mmNرcNJ+NjJ+#@(I0#eƌn(EO/777++麾􄴤%>6fb,#5"|m :V9MH aL wƑzVWv౨9T gv2ۈ{;gvtRR[C55uť⯚~y3.Ok#pqnN1y|ফkG]@aӳ Qg\d ;L..[84#@؊.=hgD)[MpX#iNuy$maP8U\S\M#A_40mӌ:`DMF̴9`={Q&\-rlیǁk)eeU46bpJNұL˲e#' 7N¬9RJ̙ @fgg\@ǂ\?cu#l?ēGr.$T!n(ٲ[+*TX^^^YYu]wT*ѣGb1Lq,]DHyyb}VW:ffX)ܺS옦mFz[6Uz̩K;n!V3f̫b1Sie;Cz酣`$aB W6vV4cf /GޜI; k旎0&XX1ÁsF 4͘!lx Ω`103nQ驙tĨIR&c&fU/"w#(0۰TVV92xjkkKKK Bb}Sw2KUOq$U(AvvNS'klHv78s*(t,0!R"môb[0ffX)%X 4q!7KfJV^#1pƘ;j٥U0ƈqWΞ3Z؆i UcLj\-5xd OYmðy)DrPkY m=梟N* `@dL☖e༚%l#8 jqp؀3g`0८T^7++k̘1n@WAbACA3.@T8@'v4 cJ{{#nW(iZ uO:" ?lڝ\_!~ǣ갆erc.J'QEEy^^!WуP4uDeee ,pA\RJG]VVNd:@r>a>]Ŋ\qtn>hz7'H.cNvNnNN ƘL;!wz z\xss󺺺Rs 5 UiOTHņaU぀Sʬ50ĤUx?a)>R~~_g)!B 8Qݹ>+^ڎsXDuS_X D^6(PB 3U 9_; 4+1"!gj#[`J)E W'hoO_UBh4x!ML`)eQG_N=t9b^׍٘RB 4(!U y, &`twwDQ4_ 33C#Tϕ㍩[]΅QmEFs4ӫ 3y*R)R(& 9Hr GJ bdZeONɔ 3aMB۶F &ˀ,AqE]@U)cwՠ;ۜ@ Fw#oץ!a^R&d )* %3g諟 f *;6;HvcwY7tvv閇eff|>!HKZnt哈Vjmm|y53^ڵrۊD:0?Iú|1fՁĮ-bȔˆ{4AC>졈DP]0qDHc7!=>?"xsgi#km؅98)".'0asys/B !eRva1;@_ѳ:‘,r][Y5>_TQ?&au\> 3{EnsoC>0cnmR%-üwҀ짴 32 #t>$^=n2 jG m<2:i!cq ϯ2ЈFiG!71mrZcQK˧+nE(U?HLp[qЖ}g3ۈ0u?9 yɻ@0>ԗs3!>/)Y쐖|$Ou@m& }ӗ GѾWE46jdSG mR:mgmUDTC#NBeI)]t !infңOq`0xLrƦyiMޢ6ILwn8fHU]1#(Q\R7@u׫j  6<'X$NOm t:B>,[8'-y01|^T 0\i9C m5%R]|+վYi>]qgZ} I7' 455Iqдb!EFFfQQQ4mjj\;x<  )PIIeY a΁@qqY?.(F4OdKp0YcE5+)rWgffP(z݄; -iI;rL}*/(-*TAphN|ki2Fe[.I ` AByj0&233^銢~IRZҒsRrz#݆iiL O1#?&pY#@{gςxZGl}\ppw"Ygk=1B`]T]]̬P(5Msa*PZҒti@Z`3N/.˧2:[v8ЧwF  P$s0g-N Џ*#rYӻ`ZҒs # UUu]t91!NOѽyugW?tD~ bsΪ>UUCڧ6߷RnѸR7O%n0%-iG@Z>ЇzqqQnNH̞{X;feY ( Rn rvEv>AA0J`*@)MngD]*1NKZX=/2AWT!KWۺk1E:@z F8("H)VncU״}r}Kmef38>ϥ pL889O%HǗ O^43aƘ[&Elp̌0$Pjn%!1ry\ i ~Wdb'$,L0!J?eNR= u!s+171s>Ӓ0\ m(G/V p1H)]d4P"z$ABdEE}]6cfDsJA0|> 9H)ce)c HKZҎ|m-yPq+G$i 2gm3X/n8zņ'twwwwuK)}>_AA ~dPqƺ:M>>$6mZvvvccc__2333773PuimmRz'IhTJ[QOV}j 2/'?sӬKJz 1 lŒBgI9˲,kYan #BJn;>FvdZҒv.Ƃ`~84ǚN#NĎ80MUռ<׫i 65+i~auP"}+W`#iqǏ_XX88N>c뗄0-Z,U2Y$$ZoBkIns|6-?<J݊EU7l1c̛7OQa64:)%&3VK`*j#LNR)-H Bg L$&RE{vݽI)띅z鴛abX,fY<dRF:A#-iI;q䍫 rc8iR {RxTUBJ%UjϮ# $8̙3}>A(.^0 y!^oVV=nn egg3l>3! 51RTtdi?#@uW̊ )iRJB|===e#A&L엪[b(q%hVT7X𵊊N:)6羶/ᶑݽ>7'ܝfZ !Hr&I20eBײ{`w.09Z"ґlNj;8٪&!M[F: .HKZҎZY z:#վvT]Ͻc }>SU5Ԛ֕*1Ayoo/BHx@;z= !BXj6Mu ܸ7Dc4x<@ `eYqb;vhoow-.7)#1ՉqI* >w;D RYUڍmDU !sf͜V8?J0!$777y@ 5y +W)ۛЁ1KLc`RJEQ@&ꪨ(,,l`;::6n.x)}>_ SGGdž aYwq.++V%{?FZ;B#h[#EZ׭_QgΘU]])Mڀ>˜@ ~XԽ^aYVr_z|e:턣WfrA..]tYqY3+)>o? Q_wQ]0<ޱg{͝4(@8uH@)YzNW:/DyZr'(ZiX,&gTiIKZ٥{2('SfV|J&XRմ۷7H,@PGQ DDQUMU0Yir`U'ᦝ1.)- y>LZGꎙGno_zrj/^v? x Ah, O B !d4p٣kG'ϋ&~>1$HO?ȪvS+wZ#aY8aPJIԀz# -i_Uoxal9ϴzƢ}Q0<U]a&}d+R J)*ځ9JJ=`xs,d<Rpq:pm۶Sfggϗ|YJJZ&Ça!7gk( oiz%;KK3šan ӦM+,,L྅e3!"|Auߵ7ή19!^P$q-nORwqV\?EzʪRolH˟^-ROud`OSB *?#FMdAW{)>'`@T(aͤdh, #&$+X=Y/wC%^pV<*0.<1esǫI۲ fYp^%,y07 PqE/ԑrӟ^W9wi&DZ, cii.AmJKZҎoʒ?^jBH$DL{<ߗ(ެz^Ŷmq((,(--4&+\bJ)&8eGJڣw&&cT(~եؾ'9sHT:ڱcmSNBHWĀ԰y=Q!S]}%D81B@ԀmN݀i4#h1wت޽!L%f R*Y[/U?nTvF~xMMM'N,,, O{R FU@ ]]VXꕺ?Y$H_o mїY[mҾY"?Trs4˳r\E2PXۊ_x-yO߶hRY)ɗ9n\`0EmcMu`#PE#˗}˦G~Gү`ձ_#.WMvfUTUE0m{0-fB|*B`EnDm_hN&X]1A^#i$ BǣRߌF &+UqðAC`Ru-}E'־G\reR8V%q۫[yyvgٱ+8Ə$-W?>.STmPt/.-g%#zdڴΏomYrxlS62_]kqJjkDڥ:SN>wIsRŁ yۻg<;ӍhzceIiiqߔ/ -iG@ZҲO':{x3$HFlvn-!wڥIŎr֯_m۶ &==kڵkׯ_lٲɓ'`SQ夔Pl]jd?$f,8Ju`Ճ ;LJc3!姘"憕0*}@`)ս~]i37(OLjFm9jZz B.{uwY6 TNUsǝR"!q!UKj;6+\,HL9Į;wxsU 3hR:9s.ZpΉR"IYXՂc3W>[5se3oB +J|mf}ƗK~3e[w Bh70ycKVƗK9wlAF׮;x~?VjG({:,}Aݱz9rt^߻Kxo;f=lٴzc6VΜ2qT&P5s:)ǯخ{c]gd5wژ# $ Yזemi.7([L`uMvz-<B?WeLwg-TPg=p97^{ayv8xU.{%!dMu D3w>?n:QkqW^Z" x9Rw$H ";oo)5a9wm޻瞭}񥣅 1DW{GgɺN>|ovVJA~܈-^0{.96'?NK 0XٶGmUN?cą8T:9=Nj100U_=gSlwN|d?} BX}ý<@k2v$&KL2vHh@ +<@ 8ڹ'lS?bd.R XƥA"Ķm)u)A\ɦxAL H-7i8ږ9Tavwو> =DܩR0aWT7aTrA)! ?K-X˕;ϞWEp %abrgT\U3JGlK:[';;T9+!`V +{fܑDDwyN ê޽?Vݛ+/l|{w>mE\d҅;0 y3p[[v6ؼNkWh {f5q\;Kn۶ϙI,iyVGMmv_A4=²,˲lNqLKZҎe/ZoqFa˶4۶-zgvjmmήqsc(7j'Xb۶m`T{?e7/Puox5*#qZP'{YtDTգ!Xe;\Hh %PEݽ{{5Ac]@>W]o,-xg۷Wp9iD lx}?mVό.y9¯~sW\מ-s܄`["m;QSKM-kM?:r~~1csc-[_ʅ#UUS))c %< pDQwLp R8i9I)y<TU3fۦ9 EUUB,x!z4! m۶, rw*v;J N55fzA}]݊7V/y} u T_ܲ)p)I)R&\tIwo's GMPph[\|j `Uso3ZOZr߸itNЯp_嚊G/'-~q3%ow]xWrŪggi֮O/tևqm?_>؎7Qb^IID~yhw{Z#mO>iܬ,4L٣bL XFcΒ8BR!%30.R =aLz\p ZX # -iٗ5fQ D9X% t]ӵX, ^N.~۶mTUU)bc˷cN01bDcccss#Ig[Z8GUggo;H!z8Q5nں1 T( ,%k]űQTQS01{ZLhZk/=ȋT͚;00 ,[3 BG)P+sNunٶ>ޤӎ)Qk(DW1hF )PpؒGYWN00{0R1v\sӜ"~$0fsݶ۝֝1I 4n}ķbRLd䨬qR,2qY -.o6*U &qO݊Y~ڷqU>]ގj9ov/71S5 ʙ.`IU@ Z~ROtWLPT  |ܔR/0S/lxopx<q4MӴm-nM{Ғv%-KWc' p"Bhj0/LӌD">/33SJiuf)$ܶEQQUUJ Ryus/ljm3a$Pg,{Ʒ_{ow|rbE7,|w)_~õLӸ衧X*}n/{y3'ų.gN,OIqQ@#jXdXm~jτ\-]Gc$Lr4<I"7뗿XWC;tf)2t}30}!Xva˪{|.adF]x(]>.ٺW/mo#Vy 0'K=EaT3/*s<{_g^wsKYغ ,oWc_{Vy7U\}絘#XL;9ybnZs)m4] `k4Z']| glE:ꗾz׽OSE>r%_~⌒ >>*~٪ڣ&EOf~q]/0z>^oOΚ3mzn{cw'"~cUk/6ު+)\[B]q.eLaS.Kλfhk$.KɄ5Db|sOiKC>B 8-4iq8k޼h@c|+SE߿-`.ڸuWuƇyO>h {ɨ̾8ٶZ?wWH_ hW҅?蓛ۼ {e dwm7 X~7]zʴ߽{PĎ{Ɯu8!?;6Ŀ^)g|K?bڽ@PA=4&w|ȓuY􏯔A[$.%H\pcb8L " G@w'e_^:z|M٬|ھcsWo sǢ=<*- FMdC!ǫ MZ7ă%cW]ͭ$S F4(`;'A( ؆v3Ko,.\3Ԏ)2؁  (AjYV>SF;v31Sgz<Ź_WMIJ riW|:#1DRq07+768\Lb5ݷ=L$1ըϣHaIǙKZ/ { r:UUznbqΓ5~?K)MӤJ)\vmlMF1!SbODH#ֽ}ſĿ}Rۋ߿ܙS0H앋ޜs5_l}/~:[֮|={޷n4_+J뷷?Z93\ 860!%32%UA/1q&(eDηRO5N_ڗ^8{_rHټvԵ9\YE@DV>|o=\xL?cGycO8rc}I!%=ȺƘ=q5SF_=|cNn"0g/?qgKn5Y+boxw1oKe/Jw<?y~$59t@gt6E{ȝ(r\t"QGJBpAnC (Lz(Q2Þ%!<ڣg=7'מ~^_w~~j[s=̩*w?No3TC UW}kq9a><蠋(TQD^ / UMUU]6^alxyͯ<fTԱ~<)$>5h*Q(zёݙYg]VVhĹN5_Z31+fYDսxuɇmmk*d6w$]>ܺ˰9peL0k}?]uL?|urSJLIۼ fvVYy/7ΞS\ @~i6;B5K*p)I͹[@dy--8q1uϬ=}MWRZ`WV\d}H0! sʊ&ν'^Xq;xM7,ßa2 D׭-N:hϛ]iWJܔ#=^=0Ɗ( G@& -iG@ZҲ/ .A%\`BP?  n0 P0% {:[YQ@ ࢹH2 fFJgz-W-kxMm1V1nQqtBat@܈ Ɵz.TQϘmתN?6Ζ9%=}' ϙxgj*ƍعivџ?ԡp Z!'&]>o̔cռGVaCԭxFmMs~{Ub_+"u%PyJ9B"A‰R ,/ %~݀1UU=F\q m[Ͽ.|0u\YrUw^|\1Ȳ;~UON=9qy7ܿLk',jVV}.b Pڛ:X&8O:–iB \,)P !Q7:,q:1 wFG 2 `3e01.,JFX*(P[@aB5tz/zwg;xCeԌձt?]yTBT S@H 駆şwW7w@A);_>x1ƞK~W_!DnSHD(F TdKwC9Vd=$T:zDQnb4o~O׿a£b$!T[Q`Ҍ@}<$௨,R$AHkûOzϫ]@vC$ DM58R]l,Da˪+ P~;B"!E^i, G8Bq'& "ȢțO%|E=5oᅭQW|)9X,9!4}J~@1)(r_JCm;(1$%)Palw%gkI%@>PLDX~{Ah" JjkkK3ҁK',)$ 0A p^&AhNw#g}S+>9KWqoui\ dV Ɣ0b1SCc$"/=lC/W)N<_}(-.FO;Go{amN+mkJ'5=HLOa͜'ܝx~|ѳoԃIc[pFq?u'~oz*ӯQ8y 7R0BVEl)0 7H0ac tlKΖa\ cRR}AFBboXҽ{eμ ~gs//i?s2pV6qy DhP!\G@Cˠ *h2s3Ғv%-Ty|aRzn1vu= 2  n2RiZOOi!J)RO`3mkk дp&}5'QDR {)Qa\p!$š*/GF|7?n n3.B:RJSd7wC4l7!cY-I&7/+-ɚZl$wsa'A HtBHxOvcNڥ/'mS GZeI`0ѴPfVrci*rta9nH0ARh$PXs~8%"'[@A`-T׮e _ +DqTH !L,c}EE!R\b] /Go1IzOPq1c!%`1ffbhSMI!DDT8mQr)[=\[!PR"AD AqhI]~j /p/.k-vkF`]A:%REtm->)@/1묫LMS DƑ`L 1c=.0FP'Գ5T7{JMF.K+c0$&xK0 $XuE9u{_Wt9PX~1klIg?zb"m O6kU:*vsg{E_~E{Ge@̰da!w;`aqr 2y_\-_j%~>!`oEWwOqG~ħny7/;aFOۖW/ ?t+ϾS~0cvtF%x \ %ȔYM+ߚV@#T|S+C?ԆFe}rB౎ -=*j&]Ύnc .${ _|ƨ?]{Yȉ6Y^EgQ]TU{}ϮbѸZJGUeH J$lF \>HMGmq_ݸH $q!@2 ڝӏG:c&f.W8Rtڍd:wrsss_}Յ bm; N>=/ T+BܲlIl/T ܲɋggdfVY\ʗ `Lie!gIR!ݒQE5Rp!1!cYp0tr^R0rm9+2di Ќ0 Kp H,S&a9VK;f`=bl^[Feܱ֎/JQDzAYB#/(8#3 y7rt]@ZҎe%ڶmfoo$jĠTM6yn^VE;b X~~~2tl,z7~L{M 2=[FOguXe1 `ĹPJuiaE.,3u,gD >3~' M̰bgdc¶q |l.#nr@HVT7D}YRm]u,G AW3 RDpԩұm˶ ᭨5X<aSS O` -@g|- Ybi׌;ko{컗)SU~U^L Tppsncٯ\4/@, }?nS ѳpſXѮ>Ϛ8XV5J !⚙U>ڽw(q )TD#@ȗeonz,/v(x7Y0n97O%^ݗqׯ?mv XpB-쒉Ӧ<_ /Ϲ؟oQJw!?7_pb)T;~^?& G&c%>Xj ).:B &yB_*z3ӦBJRWUaR&Q\~7„MQڃ\ƝAyI53M?_rSpayu/U%GuJj?lȪ&w[\rT'{|1;s+;wYpy￲ɑ#YYKnroN3zV<"FSk`_>iZ _ڟu=wL[~xi60Rq)QLKM!#t"ddL9+7.}|̔iǝXAT&$H]&ueOSv~t .3~Dy^U{ɠ8󛗎SE\qG }[=Ye&:#f͹m_yAJYG/_ͧq)}oPg@ 0cƌ쮮.wXx<ť{()3>E8xOrν^ߔ)S23lWT!'SYq@{<)t!e6ܶ$wɝ͝±Lgp^rnj;|[g[Q zC9 3v p"ITK:# -iG@ZҲ~ׁ*cRx27\YYo+rQG c;MƏ_PP4܍Q5n,?}e]{_0T:q9_j UjqңZqRf^_O\j=siݎHyrqtks!0DonĈ?%xRD_Q3fj00G!fL 5!8j儱2 F>P1N~.wZ,a>CJ jWoO(.&0!R=%s܋n(tΨͰ LTLZ2k;52u%#b=\m병ʪ% W8ˏ[Z:; -k9iqOO~9}NG(yg_۶3./!RJ,th ì̶_G,GΨ. %tUWkmiP#G.ZR`pLU|yo5'{JJ3m;j΂Bo͖J({lp].ՙc)SX;Jp($ M4ov{4}+VXlyaai(Z=zKb[/YRp&8)Ei(A.JKZ>/HH˾ڢl%Cty':WrMx`(^[x4Mmkk,!{~ڑ#Ghnn޴iӦMkjjN9֋ińDTM4 !N0cqG h]i6DHǶn4&JS[-x4e嘖#$V=:H)% D5uUaDS c @+ &Rp ,29 BGؖ#D(1M.1 )-m9(NG᪮"n9s, Ѐ~ ,).)ězhY()c WtlL )q5D[.O sB!BxYQ"O)ńxӆT 8pl&a6kePMUS8!vu2L!aU̶8V5:tA3?xoyi!!T[[8N{{R[dɤI+*q}nH@^LGH G"f$TS,mKhA؆ж Htl% HJiK.>}뮵k׮򜜜H$D+H',c1v^JSJphI<Ѓٗ]vi\,ϻDioo5ʶ펎>yƶo[Uvxfƹ8x#!^o^^^fff04 'LҒ/3r ˭Lx93 #F)|*"Ƹ訣z׬YSWWmmmǏ;66 24htn1LģxJqfA*mDQ5>'RaJt߇k;ݻkހG{ʟ\E)NA F$ȩBD3HL)v~9yXQUgRDd[i[4!G* )@!ڲ!aDO%QE FfR=GKp{BJ¸Fq{9t#t_ķY8KK R"!@H*fB!3zhFQ(//4R c 1JH& bB~QU1E]Dt'lbah{~JL RtH,JNq1VG5CT+IUc k8F! >zsA*Iw|9*Ԭ <(ы cpbD "Oa@?&~`nNV?Zc&{aR^DcPPA+Do`/h 8o D)5~9꺇yGO{̱:ݩiZWWclܸq_ƕrAgG?eۓKhS{iG>vʱk~:K>$'AHHĊ  &Z]8S"BUU#H,?~)Kph߿2t3y'7j|Kqo|8+@mZ^Qja/ŀJ@sGzRE{),Gm۶E1߷e566J)] U"i&"QQOT]Wp ÖP{4hM'4u~ruӮI)Hѽ eLw@MU kxDZ$l )+~LA)bBk|TJPx$uj?0ԛmFHr[)1a| E#ErrKjƁ/)2!u?[ KFT N☸IY}ӧϗf NTKmh9]mM;V*5!yyyk֬yEGqDiinQ@Na^~6(q$tZ5VptM{uh+S=T[~Ub]I $rpNbȹxwJJJB`ΎuɌʚ0GQӵmӮ9H %=ሩ%>#7lkhWo#3F)FHwUK 3}$*wuܪ,mhS5 ^p| 8I!۷owGQ)DR qr] }8zK| {IҪ~ Fa]A&$n'$m;{[2"di_@ZҒvS!DWWWss4m(k@/!ѵ߃p_AB܍x|M>12Bf===wԂ3W/?)_8XeNvvv.]& _ϑtt ʁm{I%Kfڽcg]inƸqG9YbESSSmmmff3w? @ g޺aҢ1clWJfO</effN;,}7θWhH!BEײܒ{Oٌ- bƍӦM+++b. %S-*:qG7 PSD<|Dk/`%H˧YKqau5 t7;0F>*3. BMӲwj3B3<qֶeǎ^{0J)UU5kV0\~… !O<~/ŀه1=q#+'MȀÕU8M4^zѢERb|2~섉~af555 )8,. $>91',ߔB*bp!^#M-.\G]K~e#3gL@0cGE`Tk:moFHfx!L0oXAɈqnk"UqCBHqIuamj)#0XXK/]ڑ{aqĪU\%ۖe;RJSe"TQpL 0Ni9\Hy(BUi2jB@J2,&ܴ LU]U0FR0۴lIqBB;wZuԑJʆ} cupTIq.Dxq)$^;.%MXXzOKZҎ.+/еc=>/۶My1 |xL0G>L9aLn(PDQqxJ9B`N?`ilVVV~nwvJ^D% }݃۲Hָ#f9XMVoT3Z uSL5jTooeY%c(ҡP( pIE&VTT U%~v:ؠUp8sƆϨY&  !úE] )#,q2U[H)dO=LP%o)MZx˯y̬Icy[>=fCnSFwݽcgΛqYyE[,^r1zzNm`W;wzWavQ94/_޴=V5圚L9ko`.KLs*K3H)@raD{:#Qg}In7̀G!і8xCyŁ[뻉_RVPQ*m74DoNQiqAXB!˘#h]`KSJ c{~;9xrMmG]. 9B R<#m`p!#7/akr $ϯ͛oydI#/5:SD71Q#',V0\4kV.nؾerGg0XI*NWYcqܵni]K{`}u7{*oW}3K={[׳gՙkfPe{o^`jExKyyӶʵ'oy__)-ל5-%h^#x͎)|eW9 j 1ι2ƄLlayq@HA`M&xkuqܨr $yDz"^{oH`2,fKH Iyk²*w ĄTTOi=iIKZҎvč;`  wG0lnM7( 8B'3p9SGombOHS7VUU>[ N9̓8?ЍYJlOy az1C*AK)! k'1kfeB.8794xl_7~hms?Jд.!I)GY'd'pù(u=GB3V1ܛ/c1y#. Š8G'[6n޵q]2w,YQ$isQN+|k/?iƘ%ox_߽8_?:v-W_l£]M[{Bcvom-)fϭi8K |s~򳩫:Ӽb w~7 _-O.OOέ18#1)*W47}=wBܲ3ry'Q@g7)Kp03&mt >0?z?3T6`_a)$ 00auj݆|[6]}q])Kp8BDm# lZҒv+vRӴ`0hYaҜȁEcO-ܹUsb,c3a튂'5T>JiRCDѮeЖQ|^q8H#>N\eLn?Џ0.,!&%NaU5[7Y]8BH_b 7f]&NB~tS75wѠ1 @h|[ygbvE7x-6%-i9Ԃ14mSBիWbCOmH]קN(ޒ܌UU1`~=,nX֥Du>G{i{wqǞ{Dc8; UM)pgz:v|ӴU 1س+FOa b*{-)%G>CV($$1H`Q'Z1ŋzIŵpO3ƙŌ7^C4ǫyڴf gV#ؾ")r=S17<*վ;&}G 4l|mpsۼYJD -$"@A.\#$0I.9$HwHι=Z|m•cF-3;$H$I$'Xb3a1isB". JeoZ~w_<{#1,  P,8$i9΍ŚUU BCL)ʵֶ04_ Bd6`LGt_Α\[qgc:6#CB_q BR0fLHB F $\cX\ B)4q+kAM5K I̶@*"a[6SbDaP엾>!c DAޢRzۗ-'M3g v<'6(_4|T3kW:=tLTJ .KVk4'%-<ׇ1 TR aգSVK5{24-ϭY9ʛ>wEelתu=spoO?SK]<})$g9^{NnZ}LI=kNv!ZЭ_[C\yÍs2}=+_GĞ`:HDT}>J 3;#\"b0#@ܓN幮|t BcxR Ǭiqп>S:/jD4hcL4B(eVVZTS?^S  )~#56s;ڣ<|@V9MS?m|5ABF ZLGPJ .Tf\,|Zqƅs"Z\0>l9*\(@u"ͱ@C|hE#Pw#yxRU5i1%?=ưYW7=]ot@ksT"cG~}u/>||i#m?뀆r} _q;+q@Tݧ<ݥr8FlJH$^WKA_HE )R\) T2HI)ђQLRcjllt]7 uq0ԀЙw;[`;VJg224;Ss,[z.-le_8Bמ3"uo-6vwp"cL z7wYB{?mv-:J8g˜bb;`}u~_%+uO~ō% a gW ~_«i"3A%eٶuđ`11 bujWru?Ż#Ĝ^1k87I]e}+0Q*Ez tRD AB! =0/:wb?ƃ/#|٤r#r$|7_]6xt \jRI`RRRJ1ο决eV\ݑz@wZlz@˴Ǎ1~r[WG@"H@I@ҭNRD]a] @K`L* vEu1BM $%)I )!!%Xv]70ƼJ)%a qS2h_ǁW޵Y7}FRF@Y"?DL*^Mw+(M8rG|1{T1摕$Pbٚo35nkIO#bRY3fڂEG6ԥRᜲni:Sb`wV'tx,EdČ:Zz9#}/ R6X+`iC2@Z[u >×HTʱ-۲B2[J1 HLp A(4PVG'ZRГk]]Lbr=)II|"cR1^=&$mOx-#;1ѱaÆ5EĎΎ7644λ; c)wR@D }-I* ŅKs%oVflFpuG(-'4|e30ʥR`.KW:R ;C$> FF^C+X/e }ڲ!0V>ea$q_[i(ˊK_sxe.m=\ff R:dCU*LT%Q!$ EBuog.8;]0ohkc}p}9ǏzɕuuQXA2@$RS#&t0F!g~|edߡ6B܂Lvu]k?ܪ^uڻjo:I;xHzC]:%GM-!1$ T vT  D*DŕuJ($A@R1SP($m۫jz6iy) $u;w W ddZTR04Aue(bʘG_K~52rllTtJRz]kRC8dJX1vEµ9T)D?/CJJ"B :"QJҋG+J{2 {z JE4oYk-]F myWΝ>“4(\/<5K HDCTq;n̝$%݌*>o񀀓=7dϔJgFǩ% cPzKܩ@a zmAxO@="Ŵ/R+_sʉr7HQ QR*iŽYHE*ΞM%1Y\5uiB(V]FsKQVr,*?eV$9UX$w`ie .TRwbQ 0!Pkzmc#}WVWzEZ`4#X UW>:C+}KA> ()RI3|&Aj?2GhnܡCR޾/C}#~QV3>2Ƙ4X>2g~Y!tڛZ::ct_$PW)@в%?c΢`5׾pպ:}߬:3ωk<.jq" (I> "Du] ] AQ\QIIJH! !iΑi>(@Uj~0 Z &CCe̴"?!m=+/* X<q 8F9oqwYWNZv^L*1PюZDD%3r2r y?:}^^Q:&>ovpTw{/|wK}DԷK+$Sva۝/N(Ms3Htֹ;5)ΟRi~v7]6MCsotŪ.0q֞}PuA'{P n{ۓodȆ:8-(t]okk۱c{R^~ܔWBţb\蝖ۛA?s>C;q~փh!,<"Scq{W^$%ց> ̤< D0{;"Z׬q9 Ac:oJ߳o2Sԯz}ǶCi /JQJhPJy'c-1???'''K)wiQ$gyɚ}N,;0chf͚5)))'03qG~cvIwu"J0/]/g?ΈLzaA eˈ-F47t;hF}Ʌ3_dЪ|?vT;2C={wSl| MO%M 3V۴v sV*mW"_3s]ګk37_vQw,WqwCOSs%@Q54xI) mط|) IIIBJy.Tsn0’@@Re !|`۶kDV|%Ϛ94cYs̎p."_\}a&јqם?kpnAr _hU_uյclSg@p4ѸuC+~+/((<#* y_ۚVEӎz^QEF  32t.{ϺƜP!=PA*!T $%)I )QLjaf ,18322WwXq`c3\AƍV]l߿Ig R(^sn&@ P W_؋{5̘T@Jĥ :tuMO'WZ,1'VBONjڮ^ܧ> Yf#-nU/U$$ __|t,s@D~kٻECyUʫGș0yLp@!=twMG3X՞eY:AD/ %%%--Z-HvXCv/.>\[)3RgO)%2<81 T`NSΏw4Mc٢`MOC-N JQ%hS;\AHu  E0w!D 6{_,8Hn02\Tv~DŽc9. @q(__,Bؖ+4tkW/@e E)IgP2A!]ZNnڔyee:D P.AIJB -$$ #nz2)ջn{Tr]][EgA#F!J ׎wwdݴJؖQ{%"fD&-D%QTs%G؈]nGlx^[5iR>qcܥNfJ.@ 'et0=go' $OFI\vifQ77y}sBN ('Ϫwӟ&YS 5 uu>s U>wȡvUuAi!G8pرcSnB FB>pt`Eye\9fҳO i +=5-O%Kvvʪ{g_Pi4=kRG-Qƶܓ%aztN&bY>Qc{K+++tb()8āsag]Na'Z<ߵ=򊊎5iv@ (9SQJh0dI%b %2:T@490hO_*uݳ:ځ@ )P1;2K jzߘRB" T@Ax<~"(A?("&LBdu{ uk\ aju HR&@5vgQ/;q(@$TH" %+{`"-H Fw^~ўx=4qGUR3eܵSb 6RRʆo${/#ٕgM-]yO2}^^_.;;Z]᣽]{NB 9y>|x)<ٹz]veffz\{$Jg!(INN`îW& R}a7%K | "Çu]cHaEX=A 5L@zP@IP S=kY*TqRs8NbX1𼏍}>_AAAzzzUΎ˶! MH K-6x zpfUu'"@Hsk6C>}璼790\G]vrau  :RVљΝʔn]NͧVB{=ԡdu j[D#@ ezo/[w޻uihbCnz݉5*ѩYr۱RDIHUБ&WaZV~v@*0|5FJVnn.WJӳ2J!ʔ=nkFZN~_K>XgRE(QRM3|F!T`Gۣ M]#=.͖zF *qw q 펍Z`G0yȬ>,۔ Ppo>6rܠ)L xJj ;7^Js+ ! YػO)㊘,D&8/=EfuF '@@Kh6Q(%pNny0k@ dQ@RՉQWWiZjjP1 =a/@}뻔$H(j<6EҕכO)гSe?n^4$s Lsn&8)-x8Q ehK@PjVJ!Rib,F0}Q@dZjeYe#5:Y5s.%pu]UK޵vݞ)*kdžu[7{CϹ[QjnC?^9/v?YZ9~ ({"8.gekO, _|fe ƍ5t Eە,)Tpl3{/hlތM- <3"!/[̱_#% mx_S3`>pƬo]_G0Bw;S9 ucIЕ I7uD ;K_xrkeCLȑ܋E`.QLC8NB%8HzBd{[6YoL'橄v'@,/~4B1"[pyxE=g\[]T؝{z 5r'/Wњ /.yU\/]=wxǔ[^Sv7)O%zFyW-.RJ!J8u럾wƆN¦U̙6j@*傟J(ibѵ_=0DM#˼c u(HC7~_νTz 5 {}}yziA nYcwo~|/.y:ᘥD.ŕ$G o¦5˘ ;y~|u9YE.f.YtLϔ.Hэæ]tn5s5/ʥ*("Ƥ@a@ >uq<. pI3aQ0T+}iQi\4] Sjk^{˛:@?ذ/[c R;W㔎,kvD*-ï Id()DBzzRHDx-nmϚuU $ԃFB"yׂRJRi$A*BA)~ןz6, B=|Ww5 F$~7Mn+qo@/cs]yK-/?/~3s3 U? څ/ 㪱؝u8aPCCXքR('rx;)1}|K=w@D$VW*)Pr`̓H]n4egԓ⳧W@ !"$kx&D Ţ̈sRJ H z+)PRB3#$v 5w.#bòx JJJH5&AI)hJxшJ<[ﶵ+t]̝߻1W~W4 n~ӇT_l?~J¶2.K]{~2_lCY]?2kW0Wx=OfoXc[UX#u-/Z%%HgIe/ۻ7=/ƥD %^ "DIH p) zBHMAm%JH .l;뽵s&ϪBJ_4][tI) h`[c-K*ZQ @(.i`ZE0 p1K+܉;iD{埏lWUR4 nKO@rە@(@B`t"Q[HE54(HNJ[@t`9eʔuֽkoG}MiI.(!enj Jv HJR@@R>&B躞RRR8睝Iq]nϞ=͖e1Ƃ`nnniii8<~ xL,XC9QO$ҝOQ P8'=W][ y(˅%zRJP4R1ftDP2 7geuʁ\=5+^y61thʆ+vm^7j\iȼZJ뺱XL ("($D>ӶxC)`4\ҕlWZk{+}nEbϯjq7>waZ?֮. R.mfZߤS b/;霗<«_ipnpfnch_t9_y#MtG $%%utb#k,.@R-DZ-4|Έt\Hƈ/^y`hy~fnEҲf'e qK))) ---t . +>/#333+'$% $6B4MBչ-Z[[7lpyAiko{JUUUEEa}ĚK *wp# MӵO`ܓ۹OГ (fǕ.KǽaFXk]]|(REuGawz"j%&1,7H;֬|}_=7@zI>ӎJ vuRZVR՚C++p@vq8jm{vUCϹqL&%޼@SoԑChV 8qe~͝T eR-@AJ@ :߹5pƴqYn\+ U :4Omj *`GcNWBBBm  z4ÛW-}dʀP}K|_i~ֻrͱQ\så WU2o<{_pΘPo׹%<ڶvq7L>/]-/xw>Ʈ‰\EewjkSr(ݯ<]~S WK97Ŝoim[Wjcɬ3w?nSdo\?l߿xq~mQC@TX:rZqPJpbR)Jq \-[,))a(˺qI),ˢA!#-`p۱kۺ[ͺx\Ї^ݺgO}dt|;;;t'*G֫˟}sѩ& fAk MiA8|tܴss]0{Yscxz'g).,NGێtd{U#Ry,ޕ_C$`/-W0bxQ~a#qqH.k|^;Д5/^3^oO:>hŷ|eTqdۏ yA>;hQGnӋu۽|Ƀ=;oaJ\7r4ox۹fkJ/itQAlZ0n Rprn㏿^~.6"ߵFyW6F{5W5ȇiײ>ǵYR'KFM/SZчHBht&E))k(%\̩ߩ;*_ʀaR5pUxlȌÍ0K*RDꤋ.2_˔Jل8/ώ1Ͳl'ZJRW z])%PxU>˯9" :q0!ϰ745hw6769cr^|\6vэS^oUa'^Xn{gz2X=y*6̩$reorM4<{˻ۂr u'mY2reGվ?ɜ|mͷ}4[f|ǟY{oG;X#DLMMMIIq]UXIJR@@RCҧrrDqbXgg%477[NI9޼]87'wΝ֭s]4iF{Ͷ+D&͛Tb $< K]FKHu󗿾\}۷O\Zvk<4 E&]*P}Q ]iGc 6踥ԱqldP&(pvi c7߻-i?zb"!%ZV~\[ 8ޟ9&vDW8k@ẙ%=PԷw;0 (1MCѭջt:THn>ϿhΌ,iivD)AZ83XPK$j9~txwÛ,qcQ!J.@ Ḯ#@'A}޷_8l1[=w#Y=2âvy&U3Tdϊ{~eټgkrt8` )'1;R5az啻I |m|yoz +_~Alά1%Mp1B"3ه [Tƭh=tߋdn{?f\0|7g?ϕkY0| U{u#sB*ƹRv5UV;g_ia޲<^M95ݧ̾`XÑCZpЄòЊpDE::it/OI[<'j*)III*a!D,Rmoٲu#Fv\<7=''4͆ݻwB#Ӡs{FDsc 2ݏx[+ɧW#Q>?cfط)Abx@х7y։D|Z !߷uECFpvvfV,Ո07 lj=,(P[BIt*ױhD3r$l_S{Gցd3SK4HռAJoڴ,ΥfPW @ %4vmD uCɚ{~fwF箙[G*jv3BIOIR n93mҕWxPoq,u?hܾtָ \SJն{oȜvvUV}6ĢQL>jV+nؠi.6ª_߻RYՙӶ{ku?ef* }׋O1pv^poW.3ΗFӁ8߬[n~л[]Pv &Nx T+dEB>*5ҞθeG+VJLC-P4+ݗBsbJ&W֭}]{9sstD(srQ멄ڻ_A˾ƅԊY=rqb0z~3iQ]*ƥ=C*!ʫU#;yY8cs TI;xz}gx٤Nc%CϜ .`xFPc2RrF=K;,)B5XVw~Iqn?o G7ͮ֔k *'\8܋>wĂ;6~}h7mzǗ P>p3DhРH$rᴴ,H-8違+]S<,CTI޸wR),/L%!_c@Iac@?3 A5| wW(3"yv2T5!H+s|N9ehp1"!5=>/'@̪e W3BLiWb\tW_4xvUy14E@)Dտ(dI5d;+)*%A0&tkEO@D1B E"!% C>Д#Ɓ0ό0HW* 捜0zYqᛮt\DH!)][ZNGNpnD eG;oɓJMQtsĜEF 4`=kTaT_8[#\!PNmmT.)ܷɕ ca"%>g4tlL=waK Yz)H'|ƭZhZuy(3LiZ=?n*u_vaK Yxġ!/ݟL;BiIQ)ңAfo@AWwFr& ē !a04%Bl-h y H0;ywJή a,)ɱ ]}QÞ2=z}%3/#(f0r9#uW +NG|?>vY%_HH׉Gm *-Ae5Ma`Y+L/6>97%zHF2+&.JJ[VZww<4CC!'F0$":F|>ipڳ8 xܢ#R%¶7p[666{5Jȫ=j |uyčܚ#V4S/|f]pF+^>Q\QUn8ͻ8+3f@yC[?6 \}AuzC/r06pٳPPo=9sFPq.cD"+V0 CɏƓ@MMMiiig"96A-[_|{[D yLΩ==-hLCC0+-o9G0.3ðuYf&~X)c)24SYّ]qE(<_vf禦Ra'wt&Q3nR͗6P3hRLz`MnB>b0`G#.*d !m )ӂLCwpy\c)!1*J*)(ιp RBA%HA^JRŸû?]*PByJpPBTVOj=4&ܹպ}ۻۃV*=MrW*PJID%@ctg+D>*C5FTcuJOɏ"0vf䄳B2ZmfN 2_vI "S GN,GWm^p?5mw` +h9FRM`=l]t]'x=R'۳DE*󰱶ɕAO!UhaO7,+a3RkQvUapvaZaQ~5kƔT9b P}Ͼϴ';Q#L4g%Z&Z`u=Eg}5ge\6)(r*缶p7WƺEgSk[SOnO+ $%)III?%H) Jx<3xxsSRRRe3F=B!iiiiiz~4:+fd|uUv,nvk{ҭm/QuWXlAĚw$2laveenoȼ| Q?aKsƥvXD֬^΂.A7?^JMq={O~Y(YTTdm;nt8R*77׫8 #Avy+0rTsOP`RQTi!_F1 3&W0Q[$+d&qji`I:AiR))a*+F.}UtQO \IiV^qsyu[g iT ;wSaMxu,u AD-DJ &h#`am{H{~֜9ҤBF(7~]E@( %#aK~ͮӖ#V 3Qe}+W鯥n 1\IY%08(65jjZ^~8rӇ|~|}Y'/McвmC78q{nR隮ǻTeѦ_[.ơٻB*W=鼋z Iv_°gt3쪫vF{&)eS-Ts_cR8H!eK^~;6ukֽ?s~<_\UQ0th3//:E-J׎uvF@I,P0kw/}"ZM`ʹ|陧V4gGl5{xaJ*\NJR_n|mmmx\RXeddPʣ}+2339e]_/bԗ:=yo4 dC=Kܝg׿ /z`"]ԃi,#r1p#Aް){֬ko;+{߮Xx7>?*/z _⡿=~+~yWrlYuO.97 cذa5{@ 9۾{3S@q'$z cvc| h󶷗3ϝ>@yt~lZkri% y7ꡣsM`P֭ۅ5aLy}O\hvzD³`2TcL2.acHU =JFBt-uZ;BOwl Ylݐu5'n F8?$E˔Q]q}~7֬-T2J$X҃pٲ!:A (E$#Lh:"T "Ŧ}lƷWmj w+,aΦ?_{S`ʈDYe/>UjVXuuW) *\cF0`YVoҕg/4-`;.۹A_YFV>{ LP _(wŋԯ<:<˯흖-,qw=wW\įpEv Th\im94:m Zh+,czرs`QH22WtADG;Θ'Dmc̟]yfo;gEUhgM+}h75fE;~G~s;|&&$Mgh$5GN?c_F!QGl^^;Sfv =}q3Z[[ˌΝ;w0?ؾ&??J_ZXB&i^m)(;jҳ|'6-{{v%%JY8eQ6 J)P<Q|ӏ5(u57_~cĬ&ζWf75iy٘7^}uOgC}6n/y̸|޼zן"Q_/B PJIw)OH-R#g2Ft%㊂Tq!OJ@jt ?4x${BZD\40 [V!\pFq8=;!Pa/[ʌ!V , WM?k~)o\q;_\;!  q9hѭ߻|z6a%Jמ{vVߔ[u/b58wo>{HkH|8r!8SWW8Nt3c"sꝊݽzsfRrrs2T u{W-qK| z]t5u4Ua2J4k͘X%@7J5EN _Hxۗtnm(;ٷY|gr-]cLt6>s.W.YXT\x+_9• 4%9_3kљ1`c%2i]s{^x #208fԀH̴̫(0@I 4K NhЉ^P6dYc/Г+~TC@W6koܹ&_X=t2fy3Kӫ&%F>8*T\}/O؎^mjNg0-U2|_ՔQ^d BI 9C厹e]0,(bq[˻ۉc/\&)7pۗ)@4CE[is斧hLN>s'??rZ-5K_)DZp娲r<SHN i_pQ噆t_y!jf>rٗ 5S g^xIb@")#gk *LUο~MpfٜK.)nRߐw5yu;@.I~ gɆ=;ElL[tY*c-7\NsFοHUdLLk_OW@@OR+&δK3S-1t?cN P H /Rv޽>҄#~Խ\9ظq,--hA͇o˟~Ҵ,})2l+kRŗ.}mٵEEEyyymmmMMM^fU-{J)FiqiqSSS{uXkcXSSa1&*F$m{V2'a) D(EB%Sicqa~*PX1~Jp"Qj\pqT% 8Th% t)Dz]P ]qRL3tHq9}Qm9\"aK.WHutl2M4"#.B!t&j2tm )cbT D3M: T3tۮiV,L@r) wlA Z6W24((ǶDg*r]!4çS]Ȗf,Ico=?{<~~Qd\i@1Akmm5 cȐ!>|5 Ó~N- eee------E߿͚5';֫+~C=YXqII8poqbW[{GKӺiWBw]eC=xUVVڶ][[b)7mWŖg ucX{{{SSSGGo?vrb/_CidF@R>WafJJJgg=hR뺦iH>B.555yt=c]33pܬ?/oX +f澴 3EZt޽o>:"%J]6A C[7WwH/%]-t]UUmBb_7R _[qXUx[R> '3"}m + p-h `-}咽^q˲ѨmYJ)CvdvLJRP= dhMH)Oވu0jkkFii{E!B F322c].h Ɨ4Ƕ!}S+w?CkDw=;7Tf3l\Y6ԺxUM~gӅp` mPv&=?g͒Ѷz7c˜44*,b ! ǟz.x0<=pJtGWJ_$.5qԈl%o:={nAD4'"~immme 24}:ӁHɏ;g> :E}TBiݩ(xV"Xzu$|̤0Yfy,Aڳ#YS0[YPE@ Nf9.>.g_RN,a{]mVj)P A:*P7jEH'%5pE+g{vCO$|k 1/4|Ŋk֬: PJ`[]oaFaa1Jp9fO(Ip\vԟ~AoN/1Բ4r6 ;~%+ՔD7S Cץ JB QBLWvd }ׅ~?kȸʇ."(ЪR*!缻ɼ"c3{]@ޛ:nۢfZVnnNdOqjkkwBP;I y9j%K]bvs:v}@z#mmmv YKIH0ytuE-05@8 RJ}䩏F(555'R~xzJb6zn")>҆߬U857KQtx|I9)nuk?,z|CWGJ@@R>H)lnoocyyyeee^y%"v{!^O>F5 sGٷ5&cW3:"hԔ`An41+˦14SR(P+G]W 7z4A+̗ 옋h Zpק,jtL_0%%ۧNpbT[}t6REK#N̗Գۿ|Lj|øB򦦦fܸq'#*nK}@BGJb\Gge!RB666⋞J~HR>}৬͏xP{1>Lhbuc?ut^ó, Nqν6۷oojj񜜜O&-Ѳ\,H'B۸P`jz h3 ^E|J$3PB\ǩB }>_UU뺛6mڱcG{ׯ_gϞ@ 0jԨc=sQC4b9)9r$"nٲL |njևOdث7z6yct_m[>R&ތ͛7&?OWrǸ;I` )H![fv]P4;n$ [-hE943’@@R04۶#B-x1WUU666q`ffffff8rNo<3:7Aͻ\@!.]5 iERJ DW" pT=&c%\K=#Di/)))((8||&Sd2I)VJ|⼼JׇyX&}X0:scyƞuޣ[4MbIM@@E&mG)庮+,s1I )hjjRJ/7x6}>_~~~vv뺞/:c̣?{eR)g=RU=roTe$خ z5-].{Z0T$񙴇u⟌ONjRN)ctI*%B@%uCNR@@) ($-hO2(GWo8aHxppx$%)^uQэ$ߍ]mZs|>s:AoC>ޏc AßA@G[X?/.>o4B֭~sϕ[yAj%a @Ir&q_?XRjvmmmccm=ؤqRJ:]M=?6Dˉc@4ǬiPH׿!*DT'!G▚1eBGuuu e<J|:I )?:iY?E}DHMՇ˘2!o2礙KB(>I:H2]'JI TqW䊴#?;QSՆ+]Tt7ǿ 4$MJRrƥ9Z?wr:AwRB!N K)hQZ8p c@ 770SML8PR  Y[xt'"%a]jFVltdov[˗tT]L*Q48ڨYM/׵"οsKZ_^cmPTTD)Sӫ>^ (8٫ j2@F<|LPEQy$e"8wݻwWUUB~!aSڎ+WO܉FmI ԩtxԖS Dr0b{7lڼ÷ׯYWsVQN@~M}mA)$CdڤB# eOJXm~S;X4##r#GJx4眙?~G}A uSkEǭp8-룻EEEs[[[.--M, xϲb OmUt] ON9Nu̧=yh]]0<=zqq5غb JQ ( %b72bXRZ[[\5dH)?P%=-8:SvwP3&>06|B +:vުKR_f驏l>oLdxy;[%kfIhY;bnR].48Ulں M]Hm5[ۂdtrj>Y D}ˠv/;um6 .]8zX!-))fQ)` #G"h۠\|Ow! s),,,K@]G*+ 1Q(AuWٿnOYLE!@Z%쏘_!(]==EJfZ/7Ta `yP`@^t-qAJq!ٽR-U#w ! #ǡ-Diw878 (q݁*u4YmcL);x{JtYYT!#ϙ H<ݴ۷ˏL,DNA bH|T}p-|t}H]hwmS]!_F"D\gj嚝;6Fn e,Y6kKJDr퍶9EO%C[f.6F ]>W[%%oT.{5sRU0@!qG[DQIt{ 4#SP1H+6j+V6}ʹ>d,PaZ&DujXpYPEj"% # qG@@'8p8\nWsSeYRHMMѳg{a#%LJٯ_+W߿ؑovJ!Բ37tcze1 ZRRU=1f +7޽<&6xYtr4!F icϖ7C.0!Cw^|OfXSbXRjiw Zpx7\WQQポ;+c\'Q{#aaϒ>_t/,P>GEgOȞMmbpz ^FI,!=Sfp{%mܳvgCó@XH;dm JGY~{\qzi)>ͨ۲@}UCj57[Ns5,9p+⁀8#x sjkj5MslcLӴ +8\_:O^wf$5YD@]6~?g9~6C_x搜dcE'ε/}b)w;nuWvXwiVn~|%'H x,!Qݧ> KHyev+݇}0DԾ8;XY?sbi_cƈPtMSuJY_Y=/: cdcgH@؊YcL/>=kBr)@J&#USd1,N n3 0DCΆV-#W>K%cŪ6̛+xP<,}κ X&\>3,"fKMEKSeCTj"ڴm3l.ȃƞ#Suar )ͳ5yIbA*|gF^z Fݾw]!n6-nFzˏ=Z{qZ0AUbʶ,9mS\l >Qkoشxi^sd}HTޮpڶndw[;0R/kW/V5Jܪp~Fo\'W4*L/),lc|QߩdI GW{,tJ5?N}y9F?{MfHqigVߺt3Av >7۴g7 >rgޕk_ 4guP?INଃֶs"_}8k"88qa„EQ4];&0F"Ǔ baikiZ$&&*D;QHjK[R_}ͅ69ƨ#uBXkqW Liw{3cmm{&9@pٹi~:2کEF-g ! g $)NGho9g \Υ$[?~7ן{3׽=!wf,X~fs$ q.=Bpɹ\O<)z|~`ϧ/~4o[5yZhbф)"M6__PT\ж˿?l aBPv-٧_lm\J)YI8Z>?=Eu1bX3}e=oo;?xvԆ1Ad0-[7lٸhd;&% LTU5UaEt]sE EÒq.QE5 ӥ9TUvuxh[YSfhnPJT`I8mYj͢V,bC+|}z1`K1&JOnuQ !$ $׼^;>Ã0s_ ,ܺ._eOmCCUSVx7-gg_yƧyW?{䥵RQIj5W/?<2NɃVtb_#~sV \Ⱦ? ܰ/Ą.$PbV/yO7w^uS JڍӫQٯDq ZmYcoKEYN * 꺪 Rb*A 䌩Tu8\bKp.$EQ*䍏mlU T P%ʥ)}hU϶bX8r;H Rj769.L=^-|qHpz%`OiJN:;N()Խm_^>`Ɲc&x\FMCQ@w:/zi'ͬACO$!&gI]MaV[?wxVD$%3#8Ait:}~_4 `!aAagRJq継( vK#QUSkޜn:4}_i^y._.[ Qx彅W"L CHn~v@(Ki2E7M `[)% 46M]Fma؊T| yˇ{=_}Oy[.O>B~4;nB n{v4Ԋ6d߫',Tg~^gݢ/V)ƳK}T,2RB d=˯O-u=lgm|M^9@*R8 @ W|o4xl78kXz#0~w˶8cy%HŠ!Ƹ䢝1ABXSe' %FSUM̜th(\(4GRZzb&=1S.2>!Lp5"@ a]/I BAmM{nk6dɀ֒ QY^#9XU?w%L!9AtfX!1t]SC%FO_xeovO~| (vO[S!\?4>i *]\ ;tGٲwcשּ]znie?>O'>|ODG[SlXBpJe2Uׇs&3~ƲUz;@|  sXgv^eG2&Dm^tUElt2pkrۘbJH3̄|gV謁9?֌(Hcёe/DKműP&zs2Jہ kM\"miS1QneT@]늬k "Vמl?3!=Le9i! ]򲴜)9=@yFRP}!AﶇMhKHE$@Xf|?c؄1ycB$388}ML][^VV%Iu %e0Ǭ5 BC.o޾悋z&:t$@]Hv@g vcܙ\|o^?ӇnaF7x?RѸ%;+;]IACE@Hl蔪I-1gQ{<=zsԷa+"wOb8#8⁀8G!*T !<O6o,u[߶Ozv &MÈ Cp=]ī8մyk[pxk|k)06?yT~?f?s%53}Y[[ӂ)X^-;5{t,M :[s=OyP&&e1)0C c,bI^-ʨKN[0[m=scۧrS ~7 +J!b1֡ ]ڳrN$ϻy`JxL2jg\΋|U{wU&Drfq8t'si7a)n 1M3_~åS/ꨘUeE"VBc;ݫbP%9T{<- { /m G#0zէoo_W֎:ߤJA%,0̞#ΰA1?Ll} tɿF>B=^GbvTEH\@BЁ#׈noz뉷=$-)oNSSFL챥`|X2rnx @ndTS}ݺqt=FuZr鰋i3A $bupīSH2@ҌpˋF@< 8#ㄸBvX,-˲}#0:tvAŠi.#V̎14/U&sָ~Is6Iߥ<9 RFFwNf3;h|HlC#5 #~/  Q1F;buu40$;p ah">(,CED, pɥѧwQwUNHG8ĢQK.cIfم@lvm]/N/MsB8$<$gekklশݧ2g.3`yt^AE0Y&0.$g|]->5rarh^/[zōRnq[3s1^~٫h MUe5S#`1PŘRXi K~ڥ8CVng_vpM[=~qgxməJdڹ20m4yY_=89R :•O}ǜ>~숁>Z\8nTGX^E,e B)J6:BZe;Ta G,ٳ %%G¡vS;' !1 18XiϖՖRI~U"'hk+as/?xa,z WL(N$BbE% )h(`\NOӰ m5d;&~/H.3-[ȓor+<}3@j_*iNb1E,&- &3 Ȍ5W-=g5oB,f2U UF_~j\x%K{O Mwy>+3-<(9TmAU{S!޲?33 c%L.)tr"ZbNXoXUa)c]W'?:+02pcIɃι΍hn߇a6GHLP,pGZ duQLcS|_5Q&BXDCuw@bfa8]^$MBN;򪪊&oDz`{W[+JSvz Iv{(;'BG|of)G@@J;` FBmz!t]onnv!LHpXGfMM6рQem:sňDL]…[Nhߢ]6fD~Aԩ71] aBZ:zc B0l#uAd@WA ; Mq)\ɬAyƚںikxS @"d!WN, e:djv(-קЯ>5W'ƢtRεӮ+TSC+ɭ9wbd$^]͸ɲf-+Ʊ {?y;]qB{?jpaY̒\pH!8 R"QKC2E<=>C1m=s._׶3qڍ?~Je5 J$B6>o㮔Q*'mCGē7p` GLӁ#s=?RlL !)$N":= ۤ0!*C:"*.U@a$v fuu5M]TS5WD4O޳!<[C{UC?>_ u%aSvϔRE5Vw=F[KEw}YEâfkEmon`1.*"3y|#t߅gYT-˲B0  @NYc:DHꕟ21(%^3L=^5.rA,E|A@X][5Շ-B&ڕVw'L)^g~-Dzr>Ə %G ̥%X"Jq!RJ݉X nY\b@ f1!R],o@H2q.$Ar䠹}.?(=1GM疶A߈%C'^z?z=?~FɘA)y(@U]v}OmT'fijd< 8#$)1#VW[r:QMiHD14Ra c9BP(zAFU~5cn*Dyl];mr%HtRB "t$0˃v*?8ӄvIar]mCr1Fxi#t]W( Ƅ- }s_YG{dRpÒ%*F[ء'{0B#@&)ǚϼ;㆐@[sKͣWb\PaEV5ZGm ޤ9T-Qav88.t&b&sy/z\l?__=))~Cƶ0g}osyպ%5{OwWzgo).z{քjxﹲFuT'Ⱦ()DQ@3FX5NBx22{xF{rz9Zd7ŘHmLpRԕ`'` "b D6`VN),U1R)$ 1-$ HtF❾Xu:rF%B&TX[J$ ƀ# 0`z練gkĞq7nʶɈqLJ׆.GCԆ#:.SnVm5)6$b;r0B0iNB>lә;5au3W4Orcݖ79Қ׾SVfSkѶu j]DSWd`f'玽⢴=Ց(7-0 '&,P_4ch!BX8Tl q i)QJ5U  1:*Xo]@3skmH !0UaP= iv!L(&6e Qtv @@wWU͐#g߂D)q(_ĉ3!%c4!{͜V=y̫~a ofnPQ7".WԋMm@⾉8#8⁀8NP4 BHDhm۶+EQ1.]jfJJJ3Jծ=[7Li~/)8g6_w%eabɑOwV8N};SI"\;`rBKusOMu ֬E.vd HB3'{{XHpF{l(xtI_1ckczCD R\=2~2&,%h#c_97N.M3N74݉Zkl/)LQUz^p]f֝T흗Դ~EpViJ zu;(p QjQ;CJ0T-qBH d1.-!A):1F#JE90&#ι:ch(BtS^`|{[g%)0(Q",Й-~ )i|Cp|͊XDwݻ7/UIzƶJBB)A9NZrA!zb òT+ʐӃڼ}e:07) Zc$#g_Όa,h!ef MI{a)4d굷:hrv}y%.*sokRp85ހp%#C3iYݻbS ,|`N*H1XpFg\u0履㱏ȴ|j84"e' h'ՔR2iq')@bB Aa͉ כB0Fa1H)eL Gia]aoYHv};rjrJ}8F]voݚh@Uf19oNJGa=FX$3MF@{ڎ@ePgAv2M:='Gq{]lmv9)999Se„ >m۶$''weο~:7 .D"g˼&W.)PO9ehb 8u9 ]Ιꉚ4)d]]烯S}n~L݌׮q5ۡљ؊D!EQZQ P(zx!vr>i}`&~ް_}W)\XUo{_[ λzhN B0b܈:)yTqERpӈ1厺f}GS]~ZI+w>Q]Zu N z- Xc޵$edg41] 1 Gr7ߵ?rR~tABbꀚd^SU[>ʳ&T R)T,^T6霳%opIϗ} OώlY{d'ix_X.peԅ-> Qq@DՕ-3i9GRL0B\JSbJ0P"O(F4˟0Y,__0U=Ci!k~YBX4Ԯ B# ʷ@F᠔R  ,$ " 2CF0\q ϼ 9w]90cg^Xܜu#r*EB p aDcX )^%NP 3~y3>~i^5 JvYV}3o d\yŰ`Bҟד3W~J.jr_&|0@ ξ-K_IOb1¡v>hX7/S'_9mx! / :,1ǝ{$J%fP=|=z~~  ` &]y]?}ٿ ͥSs>`OT1jOק@\H! bkI#s"PL>Zμͥ2>k[sC#_||hmtp_"@Wy]!;oG5o>Sҳ59P)*mΈUnwkFnYp滭G{,wb^p]tDS& %% 7tֵ`ç(^!~93^uw1O>)WhfȨYZ10FyX@@qtm69NJiSSK8̘1cǎ۷M6mٲW^vf=2,grfdF$C E#N"5 ip6F8bZ906K`^B]{0 DVAAg}1|VK)ED"W}9Z^.8 _~ݙTEtSogw>{ݻnjm _gZ,\wͿ~;+j_~nk=/sNcXؒR %BXg '?'ϼ_F}۷3٭fsnosrVӶ otbI:e4RsX$t70 &O]{X0jd2'.E[\x}yտ/f}٦5хuL8ӓPcߊ~qkp.3z}/qϢ>8g䱥!&U* e*@VSbneIg^n3o$ a%VTwƈ ?۹캻~:%5h4%THDUE0T*^21C^{nrH;9 Sj fg4; M%G_z/2/?vrٽ'If+7>KmS+>.8.6"H7wJ=?L›uCw^G=v5{ݳjYqgɡ0 P,8"D`^}w\4 & S:>CAV4;zGT̹˒.ϭFǞ{S޿xMQu~tˤYBu9sz5Ub̶pYB v->~Ui>hsU)EET E# RP! %NJ0XHԌ>`-f%qǽq-OӤ#aBUEdw/?1&C^H*W/ViکYF)RNQM6Ay?%"w~neʣ]Fq4$ӽգ& 4^yGw&6MNNMM6llٲ{%Kx}>yScc}}=O>~!~23#m?kE,p܌d$%==1Ci1Ƅ_ejĕ˖/; .8HȲRJۭ('|hѢc來p8cRwR(J{Y7ng/Ao֗ Z溽 Wds]I%G ]ĈMK"y]c .q83{^6"pYe{k6x/3tذsǛVA rDsÚp7I'cOnDN&naK_ ˰zh!LAG7}L8qd9\ɟ//7Hv%AMr %kx1HJ n}$oRM4fe'SF\ߕnpߤkdjiw=0 Kc|-읢3vO" =S!e?G 7ĂYJ{mߓazi7܍eI^͕9[r-HXW]#@1sǜuQ5=dYB, cBHJ$BԈSW1BM0iI]TBi=rզayQW\ه~d{%{ wVX0:„R.*!$B ݑŻ^YG/>ˁgw;]Rh"ǠK\8e` zPJN\\! gy>ZM! :TDӮssc<_uu̓LRJ\#%8cIYCNrS=•?Y4b~FZ2e\oư;ޫwgx~oz$3Cϻ6}I.hjy.|.6L#wU7IJ ]S=;[0dHvlٻGY\rN\q75AEj mB09L.޿m;>FN["[^!׬YSVVvj#Bik!uΏIq/1@tD;roS޹;щ[cܪbO :[(Pql~m麻$*A͑y5pr|>#D"s˲0vezH$1x<))){e777={F"E_{TWWw@nz}}}NNNZZZcccMM)lhhذa1.YJjNNNQQiGh4ԤzffR Ep&UU0U-͙5狥+]}ŝb1KdJhA`Eר! g9$NDH0K d>y%[tUwI:ʻGˌq)t@(렀XT )1đDTW)E @*1eX\'4)=*!ɱJda2AN XFILu]nF-J@2˴8r=aKbա+p &i0.%"b;b1ATXܴ5e4O@cƥTgiit0IB"ƅi=S8:hccinH*!1Ѵ7|=jEMr.*a}fsr*<uV A@2śo[Vvt&N~L3c\i(L#}" 0 kFV aDcN'R9Ø"-bBDԡ+,#ʑXK J2Mj*03j0;uͨ)Q5#f`@2# X%U4UfA[fš* ,SUxo= rd}X:JJCmKdϞ -rhvH(r8}1 %o߾S%xrss !RԣGĺ:;k-[ƍo޿ommuݽ{bۓ̶޻j.3%sadPEWUB楺` oOR|Gt/?7=U*N$.rDUUǓvmb2R<# oR lhh,KQ{z].Wnnn,y뺮z׺ǸT;Mj0ӿ_xvUU!m2&LFtP791ҶX,i]#\վp!B˚p5Θfop~@U\RGTt+6vLГ{3^NPRHTBJD744tK)͇.1U=}.B EӺ>8TMB`J)H)%&FR TJ)8jJ%H"FڿNlN2*?v7x:v#NaKnFi<qq*5@vH$ 999*]%T œ1+FO>}u;C{$h*:L%pW[%ԄQ6j9o)% CJ`ŽPlƢ ];LB*JM\JM% (*alմ{y>*_![BJ)XT!N:&S TѨi*A=>(!ЀcңGCZ)iG\ߙ޻bxw aw84ΆUZrWG-/@rp!wqX?lq| DZ/]&pmGfz$3&L8J"??? \nݮ] 8?8`b'CzNhnnb,4CTS2ځ#ח3贜rZ΍Hq;>]5H ۝ no} @ LҴ]Ta JKKN^oԤ~be?)aGT n邛n:JJ|-ȧtLL6`B]Av99(s8J1;B~|/B$%%57ܹToi'X:_:`aKǺT,8Nl\$/;5@JŎv08555B!Ƙ]D~?Dݝ^pnF!7F"4>}?.^h[G}[!nݺtaÆeddğҡ^(Zs ['#WQQtÇ4q! oad uz322233O47fG'fYֈ!͘4dR8^@@PEUUpgn}B=Q,ET{[anW4bE"-&͡Pd'\DQ;M_׾9&|q|m|81,{t8yyyMMMK.ݿiIIrJJa!J:!W=cQ_o:7=&1)NQ䴴X|ԴS>VW߸ԇ8NNMz8DΆ`Qw2Tv 8⁀8a"mmmUUUBs{;vlWz755-_|˖-} v`] 0 $' :Hٖe+W/):$Ea?cL) mބnQ:}"꺾iƏ>H|}f@_zhgQ&&&7>;;kc\TTg̘$&C\q?Θu ׶p~{Իu;~?a^@"P lqqleUUP,f#.B9***6nܘ{i=N=vi}7lпCXr7^{"{On;iǵ㏿2kT@xJ:;Y0DV_}ldwuwݤҺ*F'w.aUUh#d)t]?(999Jw/\-/rwJݑ]QG !+ )(" Eأ! ߄tp8Ҋ1rݜ=Pkk]\.WqqRH!1 .x<%%%k֬)//w:Y>Omkj6MN9!\Hm<@0ARn)[P[wޏF@XlRvN!&s# >@Tb%T!NMC.jxWNog=&a`qE,k"iI A<i3-&Xvx0Hnq!ahfPBUݥtrJ۳:ld2 MMM֘)t.]HҔ(?#C& :CXy;1t:$G; G/9aҎtJ D1f | CqHA"m]8zFVnr!e-t?y4@mw76lYxۧvGN=;}.gbcL`g%2=D;51뫿m{#5 #pծy}n'; i/JA$ 8Q!ivᆗ&v@4#PRr. TU5--%55ΖcĀHC+du?LSL~Җ~O뉕9m 0v⭝ޒ_,N_l۷#C8z}ҋ-܊u_|eST2Vf'/}5 3\?Bo޷w^}s^r{S{Td!1!Dvy'vC˦/^3頋H@g]~wcBH1 p !%B D 3`!0 F8!)ОpHShJCU+WΟ 8dȐc;]H$'TTHMK0~Bzzew~Т觟ؿjj222E)njjO곲Ǐ1#S!p9X}zN/vuA_Lٕ[A9|fx`2lAyK񄎴#z]cc-}t}x{yk|w9deT] +66P-ec- ?Yٿw~鈞!,(Fͬ^zM#/L{=[Ʀ{M#,:|A(Z}ݔ2_^GF}>V ,Ap˜`)%;s!R P/kߙ3j2B #Ҟ=s1 B x6 g7ϛ7Ѣ _}ꀎ:%ccWY`acS_a?q㻦#5k,X0.'FhǚƷ?䈋Ɩ/_`|XiiNe/Ie 㿩x;^~j)>qtq;Lr }pwp-Xŀ蠞a]gIc(Hdͯ;t^4(kx;}l3utk{;`}(JVn bڍ!x vkF;əHv~UUm&91H)圫CP,s8=xꝿR˺/rϻGiXHG$\8{WL-LvP~iyrg=mўS.OվUo^:<״pˮ~<b7,]SMɷrM`CR|^',bzcJDa;p׍we#\u7ZasteWB!>{˫[s/KDž?>ީؿm܄9>VLQ7~~ߚcMמ6S//B ew`CsO+ L/W@B-۹uQRRpȐis[I+'Ji֬q f 63f`.nN2;_El!,]Tf]=?Ь6T=`@ Ȏ}"0n//8YIkX_;ykGĈmXaiQEEŚ3f&o78a='_R濭t!['ho t"G/_Uoy[ :{_ǸQ7OGeHlHS]p$#}a܌1[ Ks!D9GaKq&7o{/fy! *=1zz tZD"P5'* b`6.2ˌ4p E4W~5VPoFj#vXk3b 'e7D+w-Ⱥ V,lr@hա۶.0b5kJ35#M-Nv]JJKCLWk:_?6/c S˞xy\Xg]>{҄+7e 2~dt#Kб; Q;*ھeZORbM!p PuM;wP]Sq~0AJ$cCFu{*CL g&] '9,b}z&1EQ)HtDdJ0c,:dD(D2tg#nn29f{էAj1$%hl/ ;}IB#TѰde<LW `4t)%H0(iG6FM8u,>kQey#tb8D67Λ֏PAqĘRE!cO"JGuάf\^l8#ޡG2fPth;t& 6Oа' x#bGvXk]KsuS[cT;N.{g=u'7Av"'9ʅ;N!R{0x Bukm !GpEQlJ)!R ig9s uuuv@cɥJF!k +M JTq"fu!aI ťs.5_x[rcVoVoP dn0a9}}O N$?0Q}c7AЉ먤عu{¼lJw$B%yՠ1 :hsu\!"-[9Z#; 8G@ [{喬r^<-OF͓Pc L8߭Pvͮ1w`si%VN[-vUi93v&`ojaFmSGX'KprẆu ]HP{ ߕ[rڞ#9A1q;0p2& !۷z5ODfGzⰪezjso/ !B0i=" qߧf'Mz7 OG ks_FFo_S{w;u3n~@NG a9vUx֐ÝpES.{w|AC ƏGlhkd-X=/ @]{?YḢn%JiNФ-78HK,Êq$躮iZKKiټ/<BH5MTiOkm\QFYY0aᦆڨiL"B Y&X.ܚrRJn2`k@Rdr%V/nnfϸY6\OCXC}KtSZv'XCo LoA*@07E%Qc# 1 R4W#HHޞgyUϺ&Ț֦){99p +RkmC #F[B r$6N嬋(*iHQ_S;_ӳdHޠ[+, R-;~Χ|Ź}{.!2Iߙffɣ ]}F%J~+2q\84$?T4b{5\!$"@ʁf79k;r R%eT0^7-sT@])mLZQ4ݡ)b)FDfg_s$#R%2k3VCB 6z熦ʔܬ}N77̝6YDqzƜi:,R!|4BPiuƊn_2df45u+|ݟ51Q."Ҋc(J TM+tN1bјԔ?B3QEmK͎3s{u0wmڲz*s&s&[!Xj>p`DzwO+qy%7YvJ8 ŒDb;ޘjSP,ZSިF(ɐBuݩQh̰z)#'CRFDyuÁmBMuy=2zkF6]~r_bj3"Q*Ն1贋3󥟿,94#IzOt^bLqw1Oy?;Q'rϋ0  JٽID؅Q w\Hqcȁm~{u;8!31&caq#Ϸ_FQȡ"UG 9ء!Ѵ>m!A oIfH{ Y΢_W4'rhH HՐgq( "BH4M~"v^WU`…sz{6$^zѢEv:Hӗ#{֖FI9C:o gvyX[yoeSݑF" z']JqP`v#kٓ}98ƴ.V@F_ѰԾ^{[= MDM8Bθ?pZL4o|*fvզ?,oZ;GeKuDŽ;3'׃?gT,ߴ)G"*6|}l3sr:$:3i2⡿{﷿(Mdk׺θ}L}s)F#iF"`  &dDŽFN[$ZΙnm7bsV-k`f~Vĕ`7vDí)&]o4VujUn\dŖfM*6yPbu;}}+m҂CrwHqrШVSؾeOIic]U2jJMŮsgDcFa9&aId&4 *I޻j-{ ߿oj@_l'3E?{kB9^V>[ \4zti~P Z ڱf9fRi{dTia[`|/"[ȟ6+JGO=g"H  &%`Ɔk(JԤѶkX (Hbݥ4[6oƲ&o؁Aj D+[=eoX,Ĕ>JB!i6oٰvF޳tt $!uVJ)[7ݺbNJf޸3xl.7ܟYXKt𑴩 y}MVëlG]n%лDmyw싹3*r&IFR`Pdk7fn گW޸wՒ/?_iu-8;E_.` #-H$]EN97}ˏ l8щ2('bTh\<șvylßA%@@nHא"#;eu*AX8|EHw2; B (@p<# D;]RJ|v0`xoqrssSJ;w?~BBB^\.W& _?tL!p!fXD|Ff8U#/Xj/`n%3b1@Q3& facx${MpŅ4-aD~3,$DQU :tDv,Rp)|/|f\.{T<}{\CVTyL&q]Z2Zy=S4q*Ir``VR!W)0%@c=wްfq@N_bڦK#hѠIzj֨jmd! ;{wp$í+F-s/ZբжֈBV]HHɸNO;BU҉U{-^l8kӮm2 OnZ4Z>Ve"%֬;Φ2Dus7qmWZ,cӒE-Cr{|/wJKے ӯ6olǢOf,p$}JǶ-9kqUD-g.zm/s!!&BcbĝV2jEk/lٵm?5'k†E5aHdSmӋI9sr*.bX'+~k!l<3kzej{ D_]Rvn.+6͟9ke_u7)ㆈX,͟5贺ښ֬]v[Y{[V᠌[ cRHZ+뭰WS Fhݛv qgjJv*#P ϼgW`B) URQ`[np} BCrNux 8'w'F>;SAFNڅ#Ժp=$אMafxkm3' }ī!,@7=zL4a;W NhBfXHog%#i|ԏ`҃ğH.,A< ]xSSsuu>duH)׬Y+-*JJJb߷o֭MMM 0!XA=nECAʃݼ$gf퐖hEeYg퉶RvVG,iS Ha1f"fGV19+ʤ܊aW<:a2IN#f 4=s p%b`U*fqD)HfEMCI9yp)>sMPf13>X fv5;{i0D]!_y]'?IJI5MӑV}@7‘ ߂hcqۙu#)EgHD+ #$`edI4@JD%H ݴ#251`AJf :@"tJdaI HACq(XRKp~rD $ mPRʕc`BI}֖F/Qj^m${wdvBrF_K &f& Dg8vZH)J*ѩY2lZ;0FMt%f2bA1J)Օ'[nOT"@t_J&)VΪm FE6 2lX#/1Kػ@8ﭚDPMUTFD!FSͺYo\))gbSS&H+tP&ޱu]%4,8\uax`AZ1>ZF1n3Vϯ~=}8xƅC?^^i#`DsL)XS3#AB&'q!cXW1-K>ݾ(Jϡza:E`u7Uoտ__ ?;"!c@1To[N<ѽ)O=j؆w%LL;Z͆NL$e@fF`nGqqQR:5D!#a0 Q*r#UmGuLr%5.Npl8+sT@C]q|wK\֖X,Fq:G;X`0ػwp[[4;Nt:E8^s0Ƹw}tg|;v!?Sr| 1§B,1CWz;ko­ƞѣꊊ¹ˡ`aQg&H3€6ԭyeT 2bƅR" .,v˓ѨP<:uPн וo[9/%ЉƹcBUW,Bc1Xe-5")ד" Ka I)8cf8ʬ% ̒d"'!3\wym#X3r)Bs /ߺ|vrv RshP~d^wnARmч}Gg{&>;+_LJ@Ej˾etcfHTDŽaC~E>=7-U^hˡxtA =9X{CƟ-R ꖢkХ(B"V8Ml(+_;GC<;p!` FDJ)$%43/ӧRiB87:88QأM t;_83s%4W׊>ZXa^4(QD4Q(@<Bݣp8@gAP 2w̅jm=%@(usjJE:vJ󁝱hdؘ`,uݺ=#@J>>gw BJ!,@Rrn73'ɔ.5rh QUлk 8_!,Q.ǣSv@/})"ZޙSx=vؔ͆h+0cdI(%b%2$C8a1.@ @jUYڳ3Ņ?\涿I }z#Tti)TUpB|:-R۫%eZ#RJ[;ܼ~Ͽƛj = H֚P0?" !$ XX2\H$w !]e{7.|y~Pӆmeߕ@w~wJT$OV[鈱=2SZ &x4nW 3cȐsy:%e#Q`){zkM+ؾ-5`o_~$$`ƥRڹQc Q8#fq+ 0`5(K 0LAH!:̾*Pt >h぀8'еQJ BRJ9N{_C51vY(RLDpyŮt!G8*1OGa8zWʹUCzㄔ4S% &\HBy 7vmݒʽ`^e;V^7\ñӡUXf]Fq&duSB@Uߠ;|I p+pOSis!w3T{:@X#0Hni;}g7ʛ\ nڰ9=}FgꄞAY{љHc )% Dj_x-`1!$w) E\>G;B+X~7!΁1ŴaU\Qe OB1%7:wHWz+fqƘ_u6;9bs2WfE5V=E RJɸ\HRpi(_xk95KĘ`BHq%DR&ii UA &%},ĹbQOĒ⠸̲X4*Yæ\tև~/>vN_7Hƙ\$ko[ݣ~yKJR4)cRpf1j$猙\0YSJƧ{|ՋOUNq_v;:ۇ)1|ѣmw2oxlـ·=pЈ)GֵTlZThR4$_n\぀8~B4N39)9664vut( Y1Y? +Ta}6".hJr'$F8P"lރ(qxȒϞ8G uE7]s79>+ /i,LpaxA( 0ođ_^1›*y!j8 z@ >/=yLai@֤ooH)Ua.oK)=n?󒢕#$gPY}\}YEc{Ϳab/G}X^SL Ut]S0ļ)S>ӏ,t;|Xz`LM!d$Z%{}!<޴,oи-4%ȪB@Tˡ %gOAG 5 ǐo5W?zHd_O}̟?S`L ^ݙ)4G)˞_;|ȡe_>~zY>Г a$C3~8:NK%8#HUT4N!0U5]X1ӓUt'g r[Á%BTsj*rH5ݫ?{G7^yǃc2B_5qlj6{tUp@Q#0> 8]Aʘݷ kS3^0A"JckQ<v(04MCӴ;wVWWB'P[Q)Uiػ9ן\ 8Xۺ}Qm#$CH,^c1} NHM\k*\6$fhB%mu-!Aˈ1!QH-nhsӼ'! &~sK-71 p)n#18k*ۻ'C$YZhh5,=/jZL3!+ř]r!Č4O[M(JƤn/YjFŊw9e?;4ܐ24 Top5)-5@'O )\'S&oiF&%(4 k޷5w1N,iK0ezQq3Iɽ' &o>/j KOsHTYD)`x{yjZ ffhDtf0dq!C NG}&WG&U)ނ3+{1A5#f&NJBRJ)D@ѼwoI V4>ZLqRt&'{sy<2b v3.qZę=V4뙵C\ ꕟ v&uzd.C fZۣ80\@RbR[&oBj09 )8ԕ^/]d̲LC''`LKR"N(MʔA0)10IRB {Zac VĄBS)"aq)RH Hr˲,fZ: S3bLԄIBaB$2M#SORVl 8L),.I LSH)(E=sWoX9KU]A]E1|3=ƨ]@ęڳOX;?(J8q1kŌK!8>RrD`fO,Lq.%q&Ă1d RfbJsqljԄyp۳zӳ }nGZ#vRD 'noyt8y>[˰>(}e`'{5QD0`)q!nB] hiiR~8>4nCcSEtS|s/0hĠU5EX:YY)i\tu$4՝߁[F"4::|'MrrUU:p ^ s.7[Hf<ct[iuy%b:G2yd@r20b9b=VƘ(6_`mdx4>!_I )  ~4RJ)BĎP%'7/=# v#?*td(9yiߩЎW%~p4-G̜\qLqi&wu'^U'͋ @d@(hYɒe|}w|w?',+X(sADa#ygwtve%~Y-3=5]Z0ƨ\ζ \)Dm]2yfcu0G߷pNh?[zA! qG_V0XVL~_o/nݳ5K߷HO̤O~7^5s^s;ohXZHG\W8/1T*r9+B`h 87y$:ݹJ)X99^zբڿh0T,c,W]EHҌt}#ƨ9ߋ -s^md-.j|D=_X3p@*;A&6̉_tXO >jYG;N(Cl^ݸmk pNx`&Zq`X+s՘|Ţۊze8{o}?5R/ k¯d[%aToa78hu۝LtHH*F8gF՝&:";!!cbK;0p8L)uGb˓d X0 :DvmO IZym_,@9 ϣG()`A s]"u !f2 Ƙa"*xQiR=h[E@@͵Ho+$,_v2 cg=g?_s:m+F(I$ut?%k/~ҀOֵ+c9H EM?ڵViUX{".Y"7"@൬1b.r=F)Ȑ6Ӥ"aeeYBA  Y` P'+LFkXmOL{@whm2 hJHgwflapͯ*ZK˿ ELrn{g>i} Xecw65qMH1ė`^jnUK zP{'0_/߿i2c% I"8 DF)YA\LH{QUL&399)IL XGcOCEV(B $fvl`}>Vbe(bQxM%L4$Ft =] ͖h%.E*#$"zILD)2@q@-YrGsPl`=LBa `M:FHd7bs#8g ;CxzM0r\1!C p݊^B'"BO !F譻sH oF1Ms64z{m\'_cH=r5 .ش5]qiY!I'v>zOXzՋk (’<ۇ\|c-DVݱAK+I|KK !8뙇oP 6^{Ylc=o@.Z#.{H^93w\e*9wv,[3_.cUV1rfuMJ0&~}{a`7~ńu8 b N+>&GeV#MRsLvl8lu]hxz0H2G,I#a"kN湭ܵmw=>^T_o"nOѶuW벮J_ 0X-O|CÖJe/&:xd(B~=p`h(2$B'vo}6{94SEI.r|`UK*N_y;z//[ 9vбќnS+n",8?=ۓ6^n~BڲxbscZ,X#)k!S7?zl׾ޜJDˇ޻cμIu.*I[:]v\*1Q̓w=|oyౌ[-~;Uo+MO L_;l&~/}5oO?p_ABlQjzajfCCc xs?a$Kl [6o~*6cƘi|P8w>cH,RAKٹr%2Ux@TSc%Ijy߂ou+gt!eٶj*oxÈ&J˒呛8ܷ~uWmvr#l[6B\"ؖ0k.DRUUFi!LTMC8.;KD 7ݙ?Ow}Kwޞu"1I ^ޯ'Ԇe¥j,(um(EH`R$+D0۶Ovt5_ F1[jumv)u<_l П-n y3Ծ= 40C(HƈQϱmǣ1,LQT0F]۲]QIx[N\GīL}n< 64EDض1ư$ˊ˰Ƴ7вdx姻t2*K!%Hb"+"Aϳ-O*ܖԵ-Bfw>AuGjq6/(3CȪH`F]ǶtM"/v1ǞE1eU%Bb1dw>G_[>KOZe!03Lb}BHRUQʰ$|yx]w= `ew+QCHr.!,)Q@\#V)*BΩN TrkoUcDZvXU0D"A)F `(#Jiȷ:t|Kd|nLF_q)r0ZºH4T=6.Ʒ>k,ru۶lnsw,Sk_8t?ލSqS/GOw<5.[c=Sk=9S5H zbC[w#7^sͪ'#;+z]>T>7?c40sbp嗿}B)[FJH$'} wW,k2MyĸlIOIiԇSVS.j硊#ns!z]V!Z MoC:j# ^N̢l(>'_ۢ{#\ِpg`KI:̳'[VDE-):7??zD_oDԳsceںi¶$3KCr+!f5W׿ԯ7.Xol(IE¨G 5 B<7 ƚ4ox<>k.S?C/]fRO?6{؍7lXMnguBKʐ;]>lc{e}1f&H:ZfZeӬm%N@t wϋ~y5 Fa[zSD,#'? yb3G,_etȳ&=fEqr<6>8>^i?pH8}u=qR:V&7zg/kI=\lAO'reM sf*1 H.,f=oʄٙ|oӟۣ*r n"нt;?_\-}"Y҉=}OÇQӥ-J6>qŲ4lz~ȥxt'fp;6.mw?ыxE -K^//}7Oڲ8S17}Ec۶ eV:vkVr&W+4F=ϙ<Ӻ>~_ˏ_<{,Y3w"/wm|7~S7,~ʌkꟼkuL*~]ڪ?w IRQQv`߽?Oկ~3K3GgۑHmiWq|GO DPأ<]_'k'?Qs CxܲkOn=Tzo? k{2C'9;vVlf+;q|K_?{]~_n=>n1d}7~i&w>'$=O9V]6mxD}hwqۯl#mOw׎W~3aOs}c}ω_~+V74ǵdM Wr؁~|ʬ8F*AuQItD G]Db0s$o_~}5R܎ x2DƬ\M+>7~OzOu{yf?WOEM)zcowW!\ۑu7ͯS.߶s``dDY5ڮ׿+ڳ{|!& pCT. I,>}[{yIzY#.Jd6HTa)uҦ34089Xk/Q0&2Br1s<m?܉L>:aYWjGK=Ǧ. zmg5%mw1:0U3t0f2ZH^ftvH" AUblxoa=|¥T#,y>5!f5ӈ9N7S#!1n8XBjÎѺF#@8j!Ldv)7o.l4.{*MYrpi?+ɾt,. Ρmgek#jz jG;=u#]cf1' ޾a$3-]e@}/oZB>ڔCJz$ 6 Ģ'T% u#8 -KS|ê2qәioS88&"95޹/$-gv,Nv#C(;Dٰ!PZ`يߏŃti)d'{OA[lJ=-؁%W/os*uXHB & 0@&@@]]=Ms1ѧڊ!)Ȅ^P5S^v]'%\2xbv+3ɬK;/zم#DKuw_ctቇUֆb_:*;K\ڞHopb 5Bn2Tb+b!LF1/_6gLqɦ]1QF-1Y-?J˖YM#}賿mrh:hrHƈZ.\SJO)DR`YH5B[Ƕm 19 * n둛o@Ë߿V;#cE' QJ%zLZ:AEm$cخg2L1eȸxl'tc8XŘ)2%,)Z)KFdڷ]cT5SJFw~r5~xnxۼG003i;vZr/JR.(5뗴FA,IRc:VȨ( z!,)D1 3cͮ bY& JU:`Cb[sbEx[Њw/js!1&$+Qf IS$$Yˆ!62TMN0vΕmq~n;;XM HHUHj@,iYV#[#Pzq 0Q1LѬ/Fa*Rbȶm(<!,aY""V+iA.|x7aZ Lty6AQAIJB$IL"@,˚E'>9rӱI:GCSf1vhhM"M;DEź.YcTk:ȵt#"(dYɮk?+N+V\l_:?yѻa5|󍡃 []9:CG%-ְpeأlL]5"OJ]~::cq|ӮJ8O5KC1Kr٥@e:caLKcLR4&Y"?ȁ޿L&&d{E,)*v2,). ppd}O{GM5yI4!<ó uhP-g|,j0, GmǛ D,o^&xrɦ=?O\^'3/Ulv콈d͈5ڬ꽟c+rOzܾcpZU*eӕ1!게˨R!G1YIfy}SRerJ! E !8FQOm3,:C(|淥~E T֣Ys ۽}pmъcZ1-)Z/ُ5_xdӼP!մ5j, PE C1]ŨG}?)ZU}tåWAU).rPcKtceC<kzt;ko"zbyd̲kkZZZȑÏE`8?tg|Ϫ8_8#^d~wgTy+ޱ[2uA=Ľyek o[U[:k߾VKDCw}vnr gwuES7uɑjiuˮ|տ}|fΎ۵i U%wh槷ekO7v\_5^rgR^ȅ/ٶ[6=וpmm?Q]GLV\ӳzŻOmqe;}eImO&6^Ѧ+ز<Ӷ@ˊo_[LxnXZKm+Rf̳,bz/*.3BmgWcDU0?^t]kSNjVoXTV1IR`X,r9ye3rǺ 2r#}Ջ#`l<߻kVlG۾w`+c3cYm˖n`ylɑQxeÚjngyfΝ{gKzڛRkz۾];w=2Acoԗ_Ձ_]ex*'Y‹AՈ5Lvmۺ-׶/sAKc 珿m}K5F<ѿc]%JC[xa^[[w;<_Ͻsa\'3p`&^v<q0ɤ8Sdfb4صnic,Z;g K;jC //lmnik$/<L?kjuL8||}Gvu4ͨmSO={׮e=mQ;1i]|P8c7I۵c]kmح''FFQWkH+ό|psU&y`TY}કM-O 4uiRMV6UőѮ :m]])E1`F]R&kY.(oy3gdN!0s{EqkR)q&&&fOaiI"a=NaD0,ƈ8$Y e>#;Fse fԵmIHȱmdE$ 3j;2YQo`DgLV4E^vr SAlN&hX,`9&Go܋7bc;KeEU`Fݖ|aeץDQU h/tÞ]*[.Ú"s|ߎm{ǥ),anmjjj(c(M3(MͮK_M\2-[ ]@Ce` aIDT F ax$J B*Җ೫ x&f[xo5rJ|+/ky9)τ Fg,fAcD™7ϭAN>F!dYum#J0ưe1Ɛ"E(Eֈ $hV !(!Zuf/B@@ E0D"$PJ X pI&q<.mm55!UA+бc쩧ضmz}Wo "@AcIt]X,AMӠ:@8(}\{^_!$P!g2αcАkYueQӤCmϣW7 A  -\PQ P6+# pa`F@ @\7 z9ZH4H$ƴ*#Ke,dρ$̀`4 @@*VUHQ[^ uu*y7͛Y.w^=#2m_;@ @UU0`(uZA"\>i=1$ Dy3<쌌8'O٬73 yC=RQJ=2ILH&uuu,ZT*%L=oxddϞ=##bQ,X4Jihllhhê隦*˜z㺔Rq9b뺧yp8 ;: P$QJO8qR';Ƴ<!c'Bϛ ֆG4 tylt9O޽[(B$)'A :oM V#/|AC)ٞge2Ԕ=9ٷwc,~#iqGGGkkk,bX, JL=ϭގ㌏=|~`pxp/ z>ݸZV_ (np= {X&s^& 3nX @D# pCIW^miI!Tb-Q3]#Gܱ1u=s]s]Rc<(]]]uuuRC&9v8m𦦦^ב};[Zl d2hȲ1v]׬T@͛3y|>`555ә˥ HBD2YSSc,ˊ"KGy!EQTE$)_(LLLxkk B5bkmؘr\8$vYA4]~}$f뺪(8y1-[6o.W*p3W?r1nX@GGiOP( ===dN8q7ϟjhl\b޽{<(]]Ju]۶ VYJ¡P,B!Ea.ԣTu.+ |̫VkRիWᚚd2L&(Cu,ˌ1qhfP?3334Cf_[8h5qy]6HS x+B x5oB,˔R۶ư,s]"I@LMMܹst: D"]%Bp])f>ϟ<98mۢ@Jjl$.w ]wB 6iٷ7e J<,DԺd0B ! !Asfk^krs2xtq] 69֯_H$ cχl!.؀YA:iV*b'Gt:D\b[h\.wީh4jdy[T2SS۶m;zixk ؜5kEQFQ_Xn\. j 񸪪r||ܲ9T*|:qF#몪o6g6@YTJRPd2####C^̆U4EvSJ "I$©jh@H)^r]/#?~΅A DɟZ[J!DLosА8JtAG@]]]8Fp8J~BMirJeYRxWufdY"Rk׮aqcp/ 2IټyhBnFF~ #b γ[?koŸB*;8RpxSiںu -:#njD"\ c\*dYVU!d68 *irVk0>'^  !؎mV>wR za0Do ,PUu||RFx8@ ' 0n*UwrX,0dat ./FіeAA>L/\NQ@@_h!xdd!TN[.JA09qɭDJ\.,4TQdUQCnZ` NƘeK/>rHwP(B18%ccd^t d J8&ᶶ{˾[ "@@ *U.P Y6ܲwLھؙRq#G\@$YxÔ@ 1cL$XMMMmm-x'RpX.ZMMM JbÇv5o޼ /Y^SgG"Ygff"MPqBmA[[[__8t]#(ǛyA>&q!FR< Q7:Bfq\мyRT0O$NYn']299911DQdMy3X8 X$:t:Ny tdb<<޷]r Gr8K7IK( 3tN7"TFD͚cuom; ]o6t] eY'<ϋFx< }O$R)CAY:#r .;p]o``i͚5=;}eUUcBH44!ڇM8G* Lym+ pa޼]S` ? AޖiȎrD漚bum?p`mHu]U58O>b@Q8I$M6m۶C1&P R,GO=ٟ"I8 ^{-^@?ǻ.x}!}7YE`.mS>{bžYqfE@U iq0bq͕Jw8immmhTp8 p[TJ!$NNNڶeYe,i1ɁeITTx,,+󹩩}-X@$_(J s "17)\y-≉[p7mmm===Hc:@ \\"L +@_y %JPP,=ϫT*bzTuMSQ,O8h"0;'@IeW,5Mt(X*\B)5M,RYkjjݻwJB:::(Aa)*)0/1=; 7O}JJ$p]~e޿ "@@wW~XQR*W*4~weZ B/ իWGQH%֮]0͛7g^x<>o޼d2B|$$wj!!۶hmݻwΝ]]]w p4MYD2SU52,˞-]TӴJU @ƘeY.5Hr8$ H q NPUu``  ;veqG )v$Yݻ{zzjjj(t:H8C à  <&)\܌@mی1cJ\.J%#lH,IDUJ rъLLLH$E1 9'@3 4MSUUyt,[< @ _P0B \MMT*z5Xܚo'ҷ}X4Ao}3iUm@(]TFF*7h$Id2L0 )ȓ\Y'xB4X,J@!jggg\{M"@Sp0 ^8n"@߭L TGq?!2FVC!y==A^mxm.#!mW7oܺ*\(9M,joou@ 5k8p5/X  =x\1u]Dt?pX,=z5G> ICn(< H$|~||<Yv,˂_R * s9j/pP!\,cuu]]SSB055%n63p8]WWFCiO4MuC!4|.*M5Gh7 C$(S0`\%d6(rs*222d`Sѩ('/bT$IP(Èy^<FUA%$ض=22H$c, &<0mЗ׮c1{--g>Cg& nh<`A# T,;v=8)`۶-授FP 744@dityRiӯXܼiӦD"ӶO |.:wy!''٠qX,611d_˟PpOA[Q& # Ih 'bൻD{"PUT*U*b@ֶ  "@@eYj}🊢XUfM@7(?>i.s>8v, .^:`$MOLWʕzk7@y(\ pllرc@|*W3}8v2 B`9_WB4]d p!]x+Ԕ浴;55ur*OS:<8Er_:;a YW]EK'! YHW_*фP!ʑ#~`=jە 8C= @:Ì%KB&2|Hj{~WrZZZX,>eut ?+JTQpO!泲Q2Tiy(M$$yh{tzzz\.C>P.ijR9yd.S%A+/|yP( SK_ @10ba"D`A6w| I8\β,0c*8D`Oxu28HE骫zl+& ߽x  DL׭oKe BeY333x "˲|L&x)1tE**,?ʯ JFɤaB ^}́69 K40_( JH$t.;x gMOO \^p|8Ǐ?SD[-}CDByԶ6 #dNOW{>F;g ,隮j:njj!ՐmcRWWK@)T4Іđa}}} ZMʲf˲m ::: qmPy!e:]rV~w%-f Ż = ן>mcccD*##ycC/ 'O~`;;;u]Fx<1=pa0pO!p+4'``v` ÿr֯PX.[n'MEQgffjkv.*qszC3ΨT*x<)>@mGyAP\l5B1:66^PXhb[#ܹ+2 _7 Ppo'tzR[ǽNL7,K@@w7*ZX%<3ئyZ8s{^뼦x, !;C_Q`0DA@, .K,f0aÆ qq~M|P>!G'8ļyB΂;.P/JP@ `6,(333OV8m. 8չ!qA 83 c$) C@ ʲ 6H:NP|=΃vNnKy,^ecbqllLu'HVKT*nX[#$!T 2p.5Mt=C tA/r2b^ j yUUꮼh4 ,|8 QKhtCup87ׂa\mjj* Jw?!">O'TUP(GMLLRX,]b( }ZwJxՒ4'C<|P!t]#ۚz{{|"aAA `kۦiz[*naYy]*n,RT4l2Ms%u]YW)˃}}}`EŽ)@yoI)B,/YN$~W rwr-BuMw[,e (3Hoз\.Cf[Ӵp8 Q:'EuAб|UUGGG(jBmm^~ypMY|D򊢀S N$| o$hZHTB& p&\+WUj^|>OƓl U|:܂+`MS_HZ=X.;|0nx.@$hwi~hzX4!$,]0 ~>Yo)eAal6;44(m;`J=b\c UBsu]B,KGsJڀ> !2M4(۶.\L&J prl>t׮],0m~.Ҍ$ |>m{@Y)Xmxxs*5&wFhF)'?)/J@@`7߷]u\f|)kztDʕ+Z[[A 4͡!H;q,AG`0]0/PӴH$fs\0eZdY󊢬Y&Lr  _(Ӆr\, W(é򗩪jYQpҸ 1-*c!'I}D]]%er\Dqjj\ŋ744H #:di>s4fYq[e8fff:+l\6;80jZSSS0FyÇ쪕+ki`[6BXH` ;GQ䪪 C(0#i:m!?åR0 ۶x˝#hnd16[_Nֲ~vtt1@7ϟ?m޼y@,'$z/J/$g tB0qMӠ LFp!C ^?Yi:2G?*۶ D _Hr !f֑#wX;vXǎٖ5+ X|gx1BK.1:'Nm}XVS|5Ƹ\.3Ƃ|Z@p`( _hhhm۶vd21|q;5\_)xq4ת+T$1 1x$:@np(k: ZPݻ!peX*Bں:n*ae߾=ϯ]4M!Ńmۆ^ajjjjj*+e۱eL>B2qŢ\sUPJ](Pm,ia'^ ;CXjkk!ކkVSS_玎K7,Bn9]eh i@Inf<A-rybbun`3LPm[g !<6z0FK/"FG$MMy7$J@@ 7HvZU1~&&*XB,]QU*ƇN@US\USXC$A2P(ض yb1 {[ZZyxjI(<0l ap 'xFs V,#atX,jڬ J%8,˄HB!14BSSSe)O?UAs,߁a|kxq2B4[=qvkd( { ~ vL#]:۶_?PsUBDA!G16 B !C@ ʍ.>{`ˆi⹞(H% r*244dMS] P UZ2SɶRX.W^*)TU5,ZNCQ]Ӛkkkn0 - 2R~TTUKTWW" l6d-Z433355D eZ?kU.J'!ףhz?K%B "@ ijԴB !+5ǺV+?8Ck׮]x1L"_MNL@Q%Վ㘦 ~RwsMGWPX$b1c( Bj6LO )n R"C΢8cccqs"@} -nuq!WJR)UwyJ%I y!VZSs'ϧi^)(ѣG HJZ kk&x|g|0dY^vmww78PHUp8 k_ޮu=#- +V&d2 ! 9A0r:GfBBB荇L&o>˲ }RNCHFUU(J0b8$ùFOC`0P1VeQb 2lW薂, #8z²,jYV", 333&NdH:qU` D5Txpp EE>p_iR)ض],[ZZx,rpP(J%q5.F J?0r\WGVf8e ~i 䜽/   .pg&UP0 Bee`R ł4Ԝ\[Od&&&:],a #`@(p+/Fz0TʴڛuX@@Dlf*d!ds=Rޛ CCCtff)ܐvg~h.耙^0!Buݶ6ݻžxKBt?55>ׯ_W'I>U14j &8n(`pxP(DFFFBP [,shnnCmm4dwk_ % C$BBv6k7Νm[Վ*,(`0][[[WWW,fH5wX ͅp@^A| ax@MӄPTmS^ ¦e:tȣ699Y* ކ|Z]]|.DΩh_Dw!?W*3)%P )cY%+YP(y^\rgz3yd&IT*\.LqQ.]@( >w7 8p@?cT)XjpY]MvqA@Yx1K.>Jڡ!u'&&/M6577iN_./⚚(֌['aZBf2oDkCZ.@D`>+z7)7iE2+ӲP(X6a]p!ygCt]NN``=ϛ^/su]Pć) LLwC4MWj?/?hc`E! ^?Ǝ=ܜfitz||c\TIөXRGpӻPś2`=(i&\!40Blt<mh${Eҧ?뿊UD i* _r!};;v8ضJ;RSSSSSaÆX,z*;vr$̖{^kjj ] >(䣪 씴tw@?OOq!(! Sz[?o Cp ^HYū#~'| hJ4~!+z  &Ic:X G$vu-X~} 8ƻegf"$x<d%pNq`Ix}ϟCة:V" 955\xR.;摳1MS(l$ FBh6ۏs؞0?PugQGЍ?00033|rX^0gF,p|a8"'RA19;ە1>k 0!M\4ݻ%IZt)t 聀 ݞQs7C__X&)4} D?ALdUDC.Ir\4 xe>v. 𠝏q;}RDS +'0~U&5] ' yQQ;vYVD"kt-R*8 /g @It(MMs#{ұ1J8/lެvuR)曝瞛@8 S x}ǎK/6LB|J(~˲@~?σө( L~޷o_CCCss3dfb%<9o&5l6OPc333@:$|~(Pf j!+zTnq!U|QT`B f(C^(9. eB7!Γz~(@RyLp$* 0H.[`?$pPo[ip@aq/k 'V\,YU*lj<|+WTUu׮]iE (wwep"-Z$}Ofb p^w @y)gr=!`~wEQ@{թT?$R1̞遁X,V,O8`e˖%ɚh4 -[>'9ϩORVZA^ebb6 qAA>Ŋ'^x>呶w;'Ϋ@>BY&ᰦi|8:C\ax,K*b/ʕ+$`w(6 bsg5*J2>>.r}}~rE9pO Ǭ^WK28s?Pp':ǎ]bE$VցR \a\n ,x*،g ?14K&+wD8 X>ᐪFBُ=rs[`~ ,+VXxqCCCmm-c{5/皦A*uZqr]]W\1o<).aE)5+B[C+N#K\ϲ,ʆCq{۶8099 bǂ_:_tV NWa%"Y.MӤAS<筰It( pE!%e@~```dddzz0 o&y? M$IֆB!^ X,gpRC)sj}[bE86o?~%y1¸Rd2J ^)@Ǒ \0Oѳm穧N &AAOB!~)w.^9ɪmՂ8MH /pժUW!@ d\R4ǁGN5A7l(g{yQJmv)~qYBH* r]z;b>/׳D[T?Gk@J "@Mb$4;33:+NT0h$4o諿J(@@@o}0B`ms7oDu! pzhii隚ƦT* h4@Y._ϵs/F/Ib񧦦c---PFWU &>=| _T[DVrXEI˃T +ZAч_ ?l#+ǡ/`4]jU F0 %x<8βe˖/_~?~\P M`BRaD"g^:@AvU > Z e>~!nԔ`UAYBclvxdeRlgnQUoo&s%|쫿̡5 ɲzj` ^},E^KE)u\1ǻr ̏0(JggaGLXKh4;::(6mĆ='G8f4?o^?B@@@gCZ4x=X. @>x #)!iRՐ@s]d 8v'OڶD2`/ZJz~HXy7>>n:fZ(ޯA+>~WGUU-p]ٔ#Uf0 >ր_S \||9C@‚߻AzgYv0 B@: e:(+R)~rK/ttt4 sPvH- IeSd2CzIỌ)Jsgczݜ:p @Q.Bix<Lr3R4M Be,( p*  ; 0|!/+"8O!U=Y87> Ł0 rq(F3@e˲ۆ/x0IU+]**،1Ӕ-,$m/OUU=mhhp'r9OO(+ b(PRtW?bϞT*YO޸ޒ%{ރ~Wy*ϿDaAm{xlSJd2 555XLt:xZ.EQH4A;Ňfy >(o6>>D@h("N'/σS^ q5,˞ EScU*@ PWWL&A;bqF<ZV<%y!k0ٹs'lێbp-py{utiGI 22J;\? }pY8>/% \8~'bXcccSStxA@CWWוW^900s~qؿoEM* !0_,[5<< { X,*BRd)aBLG񽃪=JR(eQiA쐿EӴP(-ˬnY.cP8|2pUJ~p8 |>%"xFrEQGJeYbz0As z>v θ C?XTEQDL@=DFXTbBX,VWW`9+ J.sPw l\3Χ P(OL؟ޖh4q:thppPl…8c8gc^r/!$A=~W??F罇qEBG)P(pd2F4`[FGG;;;!1 %O2ZE Y&9z(ChQO$ơ‚OU5ϭm`0HZ J0v0d2 ,T7| w:00P4<7|}!-[$Id2@p -]^ΡRX> wErƒO,5i!"\םwDz]|12Ҷ6o}^"|j` p -_N$1x}QPt]gM,;No+f3%6gQRhQ4-imԲ7Ѷ[f#lKaII, 52+o~8NuEj,2߻wN2^kcc\.?ӍF##ѡ^8<@RBjS>^ blR&,e@ (ˍG7o>|Y7nܨT*W\p!gYL&zn}uuit:;::bJdW>___GgYبߠlB780 ðh%b#^"bgCCYhE/>ߑ i%_OUJYA϶mKiGGG*E Ha("ZLu|)J~>RqyNJV (@gp8}+WJ"ʲl>>F8#deb@KRtX,BS $W4M˲xghcީdPub>w:/ɡ Rn B 7Z:lvccVYuxxm`0H_zmo+ߊ˗Z%?wY˲d%@@1|^% F |2ѥjөV/_Pכ&\7~m[1&H_z饿뿞ϝz^.W Z % rYdKBMeLu*T*Q 6=I7!d2E&% 8l6\r\VjR,phƄe-w82@-nYQL`x[}c`iC(<˩я~zg{^ /^,DX6 ,t]4ib1[nnW^Ǫ#uNV~% \rLCB%ޅlFӷ, $Ku!A]* j*j&ENsl O4."uVt*0xQaDۍ.Or9Tx١Q "T-<{'$;09Mxk.$|^AE!0ٻN{f3TE>:T%?dv"E7wzXg8T%BTZ@ݻw4Af3J<|>Pafiwb'k d`#kY믿^*iV Ё^DUZ-p:ve6J%2xO^_z+ߍQׯk O IHJV$wE쟥R&*]- A] t:O=T*PV+ غ `0?gyZbXFC4, -x|=˲~򓟀(O[l"1E=ekv/+dd}߶SeZ*4#bBA,FIYfx9(y7ndoWl1k"6K|T,+&?da*^HXxw畗o>~ط~{RҒ|Mb8)Mr¶(0 6#GBxxCm<[8VX, ik.M9L|߇R ߋ \.W*d^@EaHD*|eG3eD<# V0iVV_~ׯCD|iF#4FL۶mYx;P&\P@b^ \UHbOPd Ud BW_}e0>}lЖBF .\ۦimSQY !~/n7JR% {bK!Sd-&0{8&Bc vaAp…b6Ju[W/ek 6N|a̒YIH Z.zQԢ[D^`g@<ʏ)Px1 qnՠQ;mo8 š~u4d+IQHa4m8@K"\.AC]]dr>rXX__zF]U my 9#La _ c]\@X<<<|x TĂeJka:}UUw+ql `|}HXđy19[~Y6G@,ƍa^r9LZ?,:*iWL&*8rVcL׳(XPwB81R4Lbp8TI;#gLΆQxDQT*xMęY!qH%/wj_~N?Or۷oc7Y{/T*urrGUvx)+PIAba!@Iq4UUͤӊ EH |_U`AKaL6B1`Jw@rq0aiEpf^~7f3`^f$@2rLOF{ iwVUאA9<2f~?apֶmpzdLvsqz.3M=쀲!|0pa_.N l6yΎeYgz~*9u0ɹ t:=&I6ňG?UL0;.EP|G>"\?GQE뚦 %X@Gx?W|2:N?4 EQ,˄@! ='Dմ^occhJ%o3P6h蘃O`ii ֕JV1#s_HY"Vd9a-xꪢD F*ɣ(b躈mۅB{p2]H*Q0R`0/[X_B!DZӔd!2E"7kר+1 ! 7NNll&k8y^Fa:μP(b_cEV,S.!xظ{J. /~8ye3jCRA|BP0M>N^{ %?3ۿ}3PϟWEo? ݓU, YJdW^3Eܺ9QHӟ07% c.ed2?3UU˕F^/JH!A!x{^ϗef$#42%.Ǭ3e͢im{J9N!";YyiDP}@<9?>>K.aj=W*fIW!p8wNkzR)hf&'f?+zȋX /PGNAV=VW35,:Jn9|w';RNgaB`BLJr('<z_eIi1T28c I<>c WÂ7@ɤ킍!6>,\"eY9 1N<ޅB mԷ(ekub!\ o4ZiCΘ@d2B4z}vΝ;>fzƊ"2~s!'S+++cX?4diZT*J\y_sӜo1p/@BfNΜz] #U <}θɌ-~,pÝ "eYF〢(jYA|\z)UU|߿ꫯ&e GD~+ʷE_|>i I_==_~=(IGG>Vl _իWsp8RiuuuXq 8>>~W766<4 g*1WPS͢\.tJY,Y@2$۶eRlB,>r JF,jr'4j[WbN{~]QlV ,f) $ #I\Ā+PPX z_ S ]?J_*54&MH1D;/8yTHV_L+J _zzYB\1lggߪ yK;{dwh}J<\.w=n6[`3y-`0pwq]=\ˮGR,:x1j준q ǀ cz#b莝\KȮ2sjjψ.2xIc&e@FÐ< y^R)JIL//|߀!Z6oܸqy`bClV*D-C%*|Ru]ס{$ )f۶¶mpcI o%1gN6!7=/ h# ;2X$!ѣu${İUI%—j5:é{HJX$ q6 ̊y^l">w----//dR˂,ïK4 :\a`n'"4M6q%- 'bJ@ErL'''F;ͦ\β7HNſWџDX'NӐ"Fq(IYH?`i<<b: nD2X%~ಂA``ABL6.IMy%! ^Y]Ru g*|)jZO޼yůzu]Qjs6dPiYLF^/J>e<>[p èjmCEJ(j +?_A~0_@2}x !EizT\V * *a߷,Ӷ ^e)|/qY\M[ERIS>oRl6 /:8je<+<-ORA [%J8sʕcH:CA&'eLS\".NXa8`0# {J7|QNןoףR)Ї}U@ r D) YJd*R~ߕXZ _O<k_}4~筬J%RYyRoh{n\VUʂ(e= ~~ Ģ,WW*lsrqbc`B[zv`w4yXP(uEadi>#G?Q 3qcX rGdž<r5H|0fJ΃Fa額$[u41ܸ(XH$3Բ,yB!#q||8N\jnlv:4D:GӴ|> dJRׯ_u3MRl~l4 ʾJe}}~.ãldrժV|e"20foxkȇBIqBc13NJ 899W^m) 7}eMS۶5Mk4l64S"Rt~P֠H$(f߿>"(n,E(~iZMUcǠNJ"ʃDUO0r\RA<<<}HD锡-;Iav>cu;}ZC FDQRw<GQ$GU,Wg(F*S;l6Lb124_EQߎFly b@d߻wl6fQTaJe%͆ahY. \,!e !#o a?|aT #_s'ݮkZ}:Ze2ᲆd 䬪+JE~,yөs_~Ԥu, b"P/TӴxlv\nV %er)+Q L&3Lz`ddC’"s2FfpU "{Nڕ 8Yw|>OSV{kklf20E1MSUՕ `s=иb}Tl CH22 #@N{VKd21MhPMS4G / b40 TMuC4!DPX]]-#ii(Pçi줘@,.j=wp|7rB )t @Xձms6kь\.G@UUx X^^.Jܼysoo!~qXYrZr9`1@ءIgDJ//|A<񄾐 w(4˗/?+ N˼T ,~y7(#ynP"rr93~P<@% sg 0=N?M094q4 CבK%QˎJh~}XfN\dxBThPw:0 h4z;;;@=MeGNpq&h4my'tų,(Нð^뚦jRYZZTUU]666_"'F\.kk !f#EM;_HӞ#a- bRRdvč  +$߸DqP5/ɒWTzomtz<|#FC<*YWm1MrVR.aF{e2}vbӳ(@,ٶa6teJr4-Us$R Y\h, eYᅮ777av]ׅ5\أ6v*Έ>Agx  G!D.wŠ3""CRAPUUQUp+P۶ y۶dj Gt: n`"b %P u]HJB@,  P+ Q8xO}S^ziwM&G?G" ִ}H|RHQ'h8Dd%+]ɜ>=o޸.:@өVTk$A{{{T*<4C,*XYIh4ݽ{w:^|,klrV<XH"ylp&\V oB;N~B6|W@=̷a?CGfi/qc nH2 AˎwHiyev=>>`8-+ϬƔ¶t: eVx<RiZV.ўz駑zMj 1 '=6&@_T* p\+d^t Ac{iŴ:F!|˲jh4.\@ M;wt$d&KQ,?}cK0C8ޅ\.oOK."+4% E\]]8X4G*L&ov*zA&B%F8! Ĝ;P4ZE.s뺹B!kw]nE0| J---f"R{Q曮jj*J*:::"dix::[U ;L$@=z^*eFDD)PG"LpFFp8$8PX@M\.ZrX8p<}*"NS! '#ĘkB8>+\J8&A&-c"ˌ.vZټqƋ/d"^}U|k"bO#R a3a%+Y RM _@4[oE'' \~/^Ą?RTB۷o߹s<@7ĎawygggH8RWAGZfŶkAݑ9؛hd2Q(@$lVHCT*™z\/F@$xee .c&Ů$:@O! at*Bvg |㘦zy;b&1Ml.] #rnBÐT*`#B;wnmm(`:h&o4nڶ(TԧMVdR .@dY_3p||\V+ :^|9Ew~w/܅2<*\^_xq2c̞wqj&0n4+C~l4[m?sѲ<)DBIFUa&jZ wxժidp)HSV G(J8 G}>a4Mi{{{0 qxgё_f~z/$vb%ByIa 3T*M3z^oܸGJR*@b_4=MlcǟіD g3Z ِ?lPpY/:hvtpTjp(@U!R=(*( iH.Hz !הrȁ& 뺦iv0 f`󕕕%}ơ$8>2 iҭF Cbe&NE۶=; sj`0b6j6!t:f`t !л\>C$\I R_ZVon\u`0?LxјNh4Z 3; zrK60n߾u޽gyHiٱy3 /1'fЁYx榦i~Z0g2*j  c(@΀TcHG!H+pMӪj&Gq T%ESU0@EQF)A#/1G"*]XBLSyxLZun%?"h/xݡB )~ Sie!L`嫫N_u||,Jr%fÕ#ě9*JV$+Y ?Ϙ PmV|>T?rΊwyg0!B!+0>wV2 d?< MQ!${٢ l. 5\VVVFrh@@ex,bPHA΂였B B ZYb/YYY)hHniFP0CTDU8*$/ \t:t[C+\۶AIu]EeYHʵt:zćA0L014v]1AqZ\|\۷o'/// 5ԫ%Lܿkkʕ+8f|z4(JbXVYڃϊ}WeY1&kd|>_GQ>_GƁj lVTi\~ܹs`pZx6!paDXyPX ѾX(PU"8Pۅ#J`<L(X0v=uWVV;X/"֧Rl63NP# f3!W?? (Sĭ[HjP>qPЎiG0YJd%q=ϋ^x!2S&Jx QɱOCg?ٷrɤAG^(&XBc@`CR%@W*WvɎ(wڒUxYHEL{Hg0A]''rJ p)7̌{\ngY` bD+5p0qztX,BHdN@9}!t5?}uKC7٪i~VkHTd1f3Rq`zJXff7WNp8(ry0Yc f^o<#gBċJd2va>@h"s{. 0(Z|׾vM娜s `@hs2~p'|xUձR F& bяEWQy~5Db2d#\.٬T*BA"nCիW*揀lƹ Jh&Ă^@39>>Vh~6tͶmD~R0F\@L&2vYMx i(TEQ8P5~|% `9_KKKGZ,{{W/9*'_#M d1GFY/YJd%7 /x fj @h4 2-VXC:l=x2KBǖeb^q!cU)(HlB@ E;N9B1QUHb=0 ݠȺpgXHj&8l |~@=Ple,ˬˠ  h$"h0v]www4Bp﫪/|\<eRcп{.U_Lf35FKofЇ>&N\r AZ 1C>T8Xp5 t) KKK !zvEјȯù9|>Dž-Jaݣ(8hde?Ji?˵I(BwŽ{|.٨޿XlN$~œd%+˭tZL:@PrJVz !慰;jZ>xRPHeN| *"0\.eY/d2.'''a 퀟'{{{ `CX,'n޼y-~]T_vM. ;,zeÎT*j0ݻwxx|+ QYw"r.ђ)Ò.(eB> bܹs/_MdD#4hP(cڧgTՎܿ"ؿ* `xR-b\ l6 X~{\^[[}_.aa~2n"3)dz`V"3hN&_g?׿ZkNd2r|7nxB"!l&b}]?pPj9BKH; HV$+Y6ee4Z0sLnZZ,s # ";<<ކ" ORs1Y&2H&bX#O%ݝٶAP(T12$Q˩3|SfώEp郸w""j"lĤPDQ U1_c?/ŒF#˲%SXЦd2PBu<[뚺` {y$"80ǥRq Dֲhm9cNR^ǟKmQMӣC˲L .Rt:u}:oҙo|7v>>!sfAl6$˽ F~~"x٣8q $<[[[nկeYj6.Hf f,d .,}iST*pF76A+NU]UU??SQf  =îwDZ3 !uAaCp4(\d>! `^ooo///sv4%5 }|;\0͋?R >ԶR*0KaEa5ʊ6r,՟ξ r*!($yBIzχh גj\0MG–`|>>?ƨWUQh@E.JFC|~xx88Dלx ̝Asσjt_0ps?9i @*qMxl(pppn۶纥b1ifF58ovag?mll%r*Drx] 41^O0V~l­ TꕫZ-"nv]X(d/xސAnFx\;1\pUĬỰqtUi@ <;̞v@SS!;wD+u͆O>ׄ=MsYHV Y^RsY?OޏT*mmmVUT_ J8@_fLRؾeT̓B}dqSz1S)lI3ziߢ\W<;88 /@%uŢzOn1 %ޢ]c1?FQ(9*+Fh燩~(>xqatM,q|>fhT,GG1F }Ƕ=߷,K%N{W(/\PT< $ݑv.MH(k& Nx /d0 URÛ7oKSӁqVbENVkZB,5=i^MRK˕JtQ*:t:~/dV>(G>uD*Y&(x<ݾ}[Qjo<~QEFDHp)=?dQ} " b/UdV67?e<y^ P.SDR>J5^ns"JV5s,k[q இ 10.>EUCijU TŞLƽ~?>[[[[dž[s\|8Gf_ iC N<:ETj=qM@sΎ6wBp9yƁO*R8 !n^`F YJd]lKT$m>BŋNeʦt?6l<[<؅U(&c: KBlKf. laa8Os\Td2vS ?J۶a#eRtǝǙΙfNl6:<-|;dW*">`:T*8cɌ $dN`:8NRiZI21Ѽz_j!L&Pt( @=;nzf~\mc W%?TU<ܹsKKK `83@YӴno|S+WNaPխR#< < B!a eN@lLs>w|?4}}}h F|u&\.c{D7&(fy||Ztt] lѣ8 a,&o ꯭ |4PAJ5nEQ$>9Ia/dg,#F;U"@E<#OFTSk׮]p,*B V2OqwSE p06G(wy䄇>YyVd!LÜ!rrݿWzUli%!~ݻb0P4-d?l7W֪LV Y[gӞe^.@@Rz*hLK^Z00pzu[*f B>_GX,2C;Jj5 D^2PbqX4M80B&ǴIcr~ gIDDnslTN@A.EXa6L*BX %HaT|۲lۆ1*2 j]pR9:{q(΀2]#<" CӁM d, FaV ,F$GGG8jY,GXH3veaVt`i\ʐUx/im^r\EUե%tYTݻfAP ?O]+W0/~aY xZOj 5{VS.cxPI, ]bgfq,EQ@ xׯ8 @!ٔf?CΨX>^|>OIb2BDcdSM&`v47xզeYx7ǹphf1BPU!(:(} 3 nCATv{fsO>˗* !N w  VLccmm- C|-3t9 A."ĩ3u߾}R\~14# w(v!mrGA,d%@@w[Q6{gd.(b瞻~pX+=;ؘ.o0dR)u(CF:rL&fiYpl6!ԄM0a@AQ@!A:y _eEk" d24@ #s夙*Y0[TY&2Њ'5 #M{AQ G\xl6_A$(0(4ӞXY^u6R+D$^ʽGWF}2qf3!d2n۫&Z v`|8S͛7с<88(JO? j3|>P@`^C VK>cujZ(Ce z=#_3.\3 cyy.W\QdӕJ"7]" 080;&\.7NܹUT8-@W<08U[)>tz2N1[@+P<0Abh LE&H `u9OhYGgS!Dټx"Kh=EQvX='p^ UP?DPcA%sND Xf-' N&`,˪T*/^r hY1뢥/S `cY >nE NJ[Tye Y#tBc"Va.?W^w @ǘ,/ ~E*ٿ)iGGGnݚf/^OSLyV$zIU~,* +++LnS=riw\fw lsҚKBNi"2]Tj2looS?")BaX8qܹa\.JJS 7f =z,!FO5'!F :lNViJ%H̠ƴ'xs(tCQU4\X5u^\l6UAe2Y|%Ysl[?ԙiڶXdx4F<]p' dWTGB?)|>1ec8jRQ2Eh[ b8SЇ>yޭ[ ⳿/Vm7M]חKRZLzx~,j\GG`#FF>LiLV Yl\@q^U`~ߠ?&,*:R2[" HLj'2 "n8EYKy4=0 "B'-ӲrR $s-SQogơdёm'''ΝCkaN)hd2f8rP!"qt :3Th`&V""DU8o)uz޽{B&D5d2 ðl ӗ,97Nj,juuq0n4 #Nk(\+T˲"E7o~LhX C  ) J߶T*J<@rLCд!\rK_6Cr'CsS gF Q7vdtJ9Buh4_]])r#PF!-1/{ MnaBWGk皦~@03se+J'7ggR ?SՎ\wj{.> r@;[,QE_(2܄A Hf4c8H>0dTU4N)Y\|2N1+18nTjZxpŐuQ:B\3^ #"&BWV?UKpr, wan4Jjqs²LJ{*Se wx@>:6'łDz,C_2(#ptCd>u˰%TJ[ R 6UU0RTi-BRfl6(|>ϪRh)4j͍ Ne;?7^7 4QuvǙyf8 \7UL aQ ,Yp7}i Axrd!Fwí.N  ZEkeYxŴ<{#Hܥ`<(Qꃃ $Ob-7XGF(N6r$4}U{KQ5 t(7|HHI`+(350" 0""8Er |*8a Û7o*SO.//#L&.]}xt%pZ(X}01)$irjjTXRI{"[.pe(PQ!KQZ\;l>88T*NPC%)BR-dYn hL&l4Taɡ%Y~RT*JBGGGPX`lB1p4k4tE(Q$0`k3?T!EӴl&ee|D~2p4=ff9kكS@UOJ#E"H0p) VSFFJ?vd&j@04_ rD d2<|Y |OƮiZTf6YB Cvww >!H"RUΝcM@J/>1ɾ0J{!$.B(?x߀Z.˲52K2G ]C| <ք$Azŀb* `(X.08<8w&3"}XS0dɅ0 m˚NBs {oommZzz~o6H@9 Hz1/ib-˂ؼmA< }4XH+,ֶT*kZ$Dav* `kp pcC r6 2 "4t=dT|;n^P2TcR#cUL&ׯ_7Mt{, (@X,B"n`zjXt@Dyt2  dMvu_0O}S0&x)_U.]DPF Mg߼;V u8p8g/_,k'xwP@jxN?=;;;\x|>' G"Cg җdvww… Z-JAD㯐GfvVkZdm|V E[,T2ͅ;)}6VCHcx\R`/W*+WAaDԢ a;E(Φ AAJx/qMV*K.{rEةjKKK+++RI;3wԴaEQ*a$4>Q|6P`t+(V åem9gQvC}B(Vb&>R4l6+"\6ph&p62rS ҚM?侘Ŋ$i6!"'L!*ϟv'''JH. 4\z6׊|uWZGdGo/^tt}}s 8 \PpcVsvscb=$}hł M?L&AfY`X&~=ax*pfiᡪR0 m샦yu]?2F!>Fx'I1!2 `>w״g/&~X|-xl<,]סS.l69 BVUJEƃUTH ά%2Jd%=N Li㳑;wӟ4vMlt۶M˄\gŢO+K),v؛ݙaFƃi ^`8 }MSQRJa**Jf"YO'Tt9:40l ^XR ]WR 5D# stLSL's50tz1]qf?H`9<<%E\(aLCv|d_}-.qHBaf361}'$!5gc2T.Ś5(谀^,zZ-< ImQBի< C[^L@ZIhd~y NS4iE.;H0^BZzwyG?Wtp8\O>'?IG|**~mHp@d};?}߇-(TaGs>0 ٩lpc 耬3 Fh4hAQYэE> JjdҾb10H::em̗p`+T*8li[O<0eY1*;v( ; #fi s*"@6B*bjQ9 H2 Gjj E|랜X0Z"𪪊'p 4o߾\(C6qGf%@W @#X"i)4M@x1q.̈45|ŋm? (UUuQpu4BJV$+Yf͞+x\ '(;3f3`,==̓[L.]JR$WsCRbqc)CryAg56oc6'-v8)r$*uR}`óqdVWW6:ӓ0%12p8PHr&c(ej.+ \"y |#3" B! O0 gZ ]q^^; \:>p[nxau8FQ. Yl2p8t=/ Дg<<0͉V<eY8 BX(!eȮ@a blooҗD*u6|/ qEg?t:xdaT*ax-MVWWGUF2"I5MԓmYxQ.EiD뭭zv=.Mg|?A#hxrm4ϑٖl6 0fY&Cg(Grҁ!0 B*l;r~xxkU*Ջ/8mi+h(J,?TH\2֋@fy vm۶m9EQ(A\Z^^69*s=vˋY;)+'`\.G* fd ?# 2MS(B4yVr|#0'N.ΦRU,`Mpe7~x $PSV2v;w7s$5MC]Ӵn_,0):2<L&;;;j_߻wa&u].I TBEU"% HPQD כ#-okdY2oI٣`0K?"RƁ)r9g|>f0&mT?R ~weuŶhB,*Ɉ3<=ϊ$+Y d=v\7N]yc\YYy0H&d_ζms^,l~eE40WY(l\."2݀8HDт ,&߲+4Qm۾y2/`'d=:FTU=<<{n\K֤- !x!6;yڲ 1׃$syO)Т0^zicc~dř)j0k  DRH ic+2@IvD-֥VzhE/Sx 4%Kйg9FM"Ri&ࠍF];+nSXWQ0xmfoiۥR00UJ;)L'0e ^,bD:K~A'xn]ϝNiDyEQT54J=p n@0ݓd%@@JTlnnэNELڿP8O(MBXp9xZ3/RF!HjFqA$vȃeuyOBO8淃 FH(i L0\@B8<4Ci^", BtrQ.rV$"P,VU8ɿm5tcZ6}: ^ChG%,㎀ a g2H\07#YbxBQi`/h3(a3@^^^.h saX5҂ؒ*tJQN ,0b0OXZ*F5 |ESd_nǐC"u8'3y"Y5LLTcF,vd2PЫm; x1 ~FdԕZª^t4ͣC*DZGUal[XjXF@ YzI*GdQVWW_nc_0mpOy5JDT pfryɧ{{A ~_Tw:rd '''t:)U@-$O r I3A]@PMg͂瑷fQ`N%19~;.Hݲl*mE*2ՙ`vqcc͛eV,1CS+,0>cbt4 ^b7n3}vww?R?'d]|2+R !N#XfJ''өcۙlV B$p .L&S,+ H9\vN^=jclʧ=l6{nժV$SG0۟i{pp*6,fel<OSHk|jS"25XmfZeHB?=m8 L ǗpP hdY(6 @$2+nt$A,H;( & 808dl!+^v,z۷lEUM;T"Q<%+\ێs:b6 euϯw:k׮UUlؐء2DE\VeڌHpB!=ЯFEiaFFq* f<j$ Rvt]o6{{{nj!rp<àв)3<ڲ'1ec %eI1Sj1ɟbaA-fCp)RT>_Hӽ'?d0 e2N0->;̎m۷nݺt钢(;om; mׅ 677AKr0zẏ}olRn\$<$9l2E @&E!L L1_[ԁh2Lx<-B ¥Cx?V* PSւ%4&@X#ۂ '@051fË/Z-NwڵL6*<LӜNn7Ms2jʪ[U5Q)UexAܲ,4X0‡]{H@NG)\6a_- G` rqDX3s=D]y]6>.-TB|>_]]?=O@aZQa<__Y"?%X@ Yz^@QmԽG@JH!V`0F R|0;@}/2/ɤJB .`0888v`mm ^1zpp *Z f)ޡՉ9gN#C -#vu _He!D^gou"d@mDpbs\"IRdr\PHSd52Sy>8BIaGmq jqMqR)˲xׯW*??ud2Il6?Onx<m(dYu||BE`RV,ǙfG$d яyĥuKTG$ǎ&9r8֊amJAt:]*ǗJ˪o^o0w l:Ǻai H .wa ! [߄BE.\8 :0kNٜ?ޱ*٬z=}CR)lԲBYXAh6{*0 S$ HH(5p_B7TEIG#ty^T;9|ܹs˶ Tn5]O E(ٴTָ Ȟ/8}!D:) MGiQfy@,*]vͶ_K{LιDQD*% /֘L$+3q@kv$zqxeYhe}v襠g!$7vPOn LO Y+HlҰ~||h4NNNt] Rx42,r!acccggg41"TTt]4 +8R$01Jʱ[0 LJ1gvǔ>f&iZJրezxϟ?jp  MdFaF. ~HɺQ!$rwɑV[8 ` *p2ɨl{&Ȫiܝt: DVp* N7G^wlis*d۷o_~=o|A|4Lo(O<<~(\!*a*ι(2(* %(1ά"QD= |.kedUooo lҰGP̗+0eTl::牥LD>뺶mM&{)saԯ],J;+\RYp-Fqy۶nTqm+V6A~<g2z,! =CQ5pEUHtp;pQ)"" E("2#9-UEUTMSUMVH;U -k2iG-d+ Y;в=00L‰ r=of<+_}ttdYv>n giFlN=!ގgyƶ~qSUUa<8:Z_^fZK98}'+Y d !áxL代(rJ^^AE5(!!\YYR7~lB+r| [;;;aV&THA9`z=Në 9JsCJ~,UxZMUU۶1jbdT.|>'. nut!fQ"ՠ"L{1al6Ep6V(gSVWVVzޞeٹ\v[r^8%VF#TPHT*ad6>3mb#-Y(,(i% evE?cu(ŋ AO55ѭe' r6EVӴBz| P&1M//mЇ>(WU /絴Թzj /Jz۷oC !%N8.(Pc*}H>dJh')mYe'\,GJ*2>'&"Ka_) @#w@x mq>jt:,DOQWH :CBV+j\䁝a ;F {i|~8ra{j=זZSS󼧞zj4}{{|UEA #@ )bJV$+Yg5 |?iZ^VE_w0xlFgYO< b!#QMR@dErZ&g،&ih[3,+b/Ia\vg6,0B5UMRٶI,ȢLex.Wnq]džm4iHucccY'Pvh,ZԱ~q=2dfjuii S`7B\.2E0JDW4E$&MHPW#g" \)$> U0Iάߋ(yBc/XdYfr>N2Si!bh> b|;(K'r\6/--]x1o|a~ +'{-//?ӚZzJFэ7RTՂ%{~*UmCIrnyyYq-JcGq|1 ,]؈Y/ P&t\lV(IDKLV谭 >Ά!R w7gGh"\b(b-E#:*XH1Yt6r@Ot+8Uy5Q@]rggǶ+ 2+FX$CtCWEUUՄP0'v''''2ai:UyF8jJdrF`0t0 -XA++xAxITP,WWWp8| EQɃ>@HJV$+YkBXߜ1g2~0(.0t:5MQd2Uϡ@{3R%ţCt3d2QUumm(j3  aۥآkt:]Ճ p]!繶pl'!pt@nZ'''d1~@ ( ehd.l6 uu < ,$@'Ne68>cH|[rY~ 4`"''@cEG-a=zdz 9.rHY8DݞΒPOZfYV^j))dNc˹9ۡ,B؃BBg dr>SfrW X!Sd+V\.ȹ;FupTt~okC{9 /~xY >e#ec4^EtT*i҆%NDK  o mᑖbdu7Y6,VR꺞I) <:J`qԈe>'A_wJCƫjzbATӴ|?L>ŸD~q@DFcooo0Z-Y4 ~ ZhO7X8NR%R)pB|ӿDbuFB=jBxlbN2t= 2tV8ix>&G=6݃&:dI;N*h 4|W~{, wxr@4}3_/B:+BzӗHV$+Yu;WEQcu^aj58 3PU-V|TȲٞ$%Ji9Hs͛7s}܀!L38:2AbHBT*<*4>ai%mۆ3ȍ9LЁe@Ӱ(QsHX,6GCm^FY')[oiJZ@^N$hĀP\.*d: 0JR .)нxh4BjIjA; m߁ȉ/-jA(,ۚsKCn$r#=xxx}d6@Z1 ?Woい~wbvm HAzի|(K/?&J瞃&ȝ;wɣ][]\o:y GL~A0O9sPg//'թGa(_+W2Ys SH&#,(_"xml pBk^JC\"[<v@Ĝ/ߕ 7ܙNY`[%HqvI瘕\j6OA`ڇ-GoH?"%"E(D҄͂)-bzX euKz* @sN׻tR(J*ž ',ZA㽰:8θ!(DB`w~4_|_EPHţpHa=j?P.1XT*,~j4cǓ-$999)r@;~*4;`KUe3!dFD8#E`|2q%SmۥRI,4C2b& Ͽ_pa<{l6>iY;_Ę jϣujzJOeMǠGgD[(ŸePNK)S'^ɓ`ď⏑SE$ %uCF |(,@bQX҄e4AP0m$$bdO(˅Bgbx.:8Ǔ7_>?b C<0Ppg7]G0qk2t0 !駟tݻF;KXb9몪%R8߹sGUgKKKj\OWwv kXzOTZ@d: ɷE͛ D0 Mjȿ ,氊Ţv;zca;WF*eIOV$+Y= ٶyjOQhwwqNϡXD&allL+wygѨ4Pvd[+TCZCu}ϲ[A@'_Ȗ>%(aAEPa͙ hPB2S8A}19㟩I\/p'jm LL:Q.}8_sx6RTQlp]7=o0#L`FZ=>>I|K:fmV0'|)3E+W: {&2b ^ JXI#`G{*2eG;NMFrAs^MR2L(?kI+_Xl6Qc#h&dMqfssj~晓 IXUUu| e4CAȚ?m? xu2L<#ĂCFb`3[Ԝxc놪(ڂs!M9>>fFju:J" {bHCR;NNN0,M+ley&BCXA,?x@Q L xZVZލD$"N;v\/7e<`x5MtmQX+EF>qp}UqFe> B,ʽi1zTcq=ˆ4P !TM ೫f[nJ/"-#*{^_JѨĭdX昰 %@6iZр܊`vqp_V67 +(:<<G?ѥ%z͜whsa Gl| {o @qMV YzEsR驧um8b,:J%Ƅj?`Ucެ^arc$O "SY08yL&Ja<4Mhq8v4( DDdYj@tל[9M VTOɎ< R?>OZC|8Jrnl. |3?ϖÔ콼ȟHVU,zR8 VV6itHbźEP)ɚ:ܹ'+X=#cU3',_fƨ, Rt6u]ZmI &HP_ ٽ' :#~# {~蓣Ư#ׄY Q6,XSWP(wTbQQgVqA]*pDY*Aܺukuuu}}]03Ah4*rk|pjD:xP 8 ۃ,n */X#D! #KBww0 777ѢV]xq/r+ o4눜!z>G'0jATUU/_ y`<7z}20׃ Kd21Msww//J U y-jLAE~s>õP( a`<1ҊEۜLbe6N&EP58L'''|fYqǝb#`2i8tVA4MLiL*tlFT$eL0 NdKڨ!;E$4aka(]cmm 9۷'C(Rt}#R@5dЯ~(@y`)[B HqH VpHu?>v9w2|zrY!Dx߰S$'+umnn^zUZ"p:oRF!,d*92$ʐ"J"ix(@ibq%ju˒ 4M 79t]/BX,%Dc2 ^ e`2FFf|NuUQg>GQߟNgavA&EN2aN3=rDnhD2R0CM^ߛJ)@Y/E1y|Ze,9dyep ۉFi.y8 ,]dtdG8+`6f"!iI:Ixu7Fy]G;7l6l.3lႮ:x2Y6x<_ħ!= (58e5Ⱥ9W_}P(?s Nn(o5*`ij`pxx&9*@BhA[˲Ì4'ϛ9N@1@IG=ti.-hH1,&/;ۊW >GX$7Tp`g vJg1op8t}sssss(" b(:F¡>xZu RR>J@[`kkkeeE.; Znw0QH UT|+*1l2Ng Y4&G:-d(0 5RFJA3Q:ʨ"ǚ&X'7MbP 7B,f.dR,zq-Cbang rPEFnqj`/b*:TZdPUhA߷,Q"H}( qbƜ- C֖8uc@G[ZV*d2FP.CB_d:2͛7R2HARE!M)M4UQ#cv9Cw&1T'kS:r\&5RKm4$d~( ~gYw>#-ϰb 2dwzaH1 V@Wx 0_ںz* x%ŭ-$T*JzO`& ~!D՚L&߯4 xIݻJQ%?%J---z=EQ\뺎iڡ󥆒l6N)@=:+$r!ɗ@Vn20qZĊ\J{45ny$]hCA vt٫g{ o(ߓ++dBE"qS|u]˲,˪V{{{ɤZʸx& 4S>H(cY/w]l6b1A |!D<@ީV,P1@|CFpJeyyYu˲vww9Gs ^<*UEx2B\xqiiT*3o=.,Nm[ڵkĢY ˃hd@", S|18\<7h#Nxxg{YEʼn&s/BJvшӔM49"C|FW #!0x8 VϟaV#4/3AHOY ?5Mܔ1;pgcc'|q3HV??xhe2o6t]W:f Dd('}ٟD/e#T RRii,/euzTM{ _ٶsCe6S(~R"NBz`M?PzQ$H!4p|UVfaUoHDOUi{R8[Na8z뭻wCNn[k &'hԟ}IlIV? -vzz! eZTH8 DZSqzZas v~)>4j}a,yey^TRoFy{||ƌUJöCq\߹s2z-*|x231|蕥Z20buEpƢg8p@ZZ/^oodT؏iTUJxh2,Sn{k֍ܦFispDcEp8DX2-H}|HtJ1]n,S/VJyHܵvhf;Gp8f2do\ػl d5;l<0{'*8agg^H1HZ Vy^r􀉀<<ZY2@b|~tt>@" Bm3و["eܜ"ǕifN#Lb&s"c8??P&w=n(;O& M d H@^tL8)^=I6rq׍"*XA U_՜ff زֺApyy;N*G vl vGZI؎ոL,˴\yu4=;;aQZ$R)*zA kAX,YNVk`w%:Izuud4: Jh5$Zx 7pH-<1- >srh" [ժT+f ^8T/Rc(VUA% jUп@˯n ݻw,{ dt Al ؏W;<T i &t5)N(jM ]Li?d˫ZoSJF#64OFhqBVJ6Ab2rYQJ)GQdbL#E@5])i0;௠;P2% n[&S(;ҥ+2`:<<$lyto-86 } ܠ*E#o KAvR(|T+ĪjOov{zw5H<,̔-l$[#dAZaI0 e^6 5NBCj2Cُi4Jf,ln+W.C $]2ِE{R!+Hq OI}0Zv#J K$8*Js bb*eeH; @4M'ӧO×?L%6JJO("ʽWzmJ+<( ϲC<Uձ+&|LPa'æn\E?\:dxc@ӹeY0P&74ǿ!"!B<3Z-uۻGN{*hkp]H%g%n4 BSx6 b|n$?!ЊpǏy-9OdL,,fy||ɓ> Alvof;~؏4<|]Mmᮀ (m]T&{t:$l6d Z uVOƸߣ;@J,wGiG1jt74qkg\u"tbfeѨ6OV_UXdCf.[eEw}w8O ?8 G :>tiZf/Zjl6s+:SV,RJ&Lr`l$Ijڽ{;] mZ-/,3L#,QjWM1a9)~i+SAL쩢ϙl'-i u<Xpxyy|~_mzf&VeY뒾.e%ۢZi rXQa4MtZmP/kLJǾfCr4vJp 3aNҊF9 c>cfa,OVGA\ʈ 0Y=Yg jvp0\#RVc "h CW*;-}G3*ՈnPXCPSal2`m޽{VRt]& $II61pQhpvd]o4{Lp, pZAؽ ){jϗK\ee1=2|,*aƁOh|c  gϟ?tjm^o6LuU8 s ?_UA 8\4+0bO&fsrrBQ}Tt0`FEys*.΋W0 fH 2%a_fdY^|q$wpIjccw6r,˥M^=pIb:ڻs `lRR6N5!lyN=$#\0ƃGL*`/nO>{.NgJF 3z `&_^^acvwa^l6q z 9@|T:+eZ-ܱ3(*z@HMY8?~?^j7@?~%ؑ.`4櫊c?~j_ZteooɟzsΝ;I4 T4 8yE]I'|>l6nw:Q/+ Dڶm2,Z+Nt>|E6M\dizj%VH5AgE/iCEma~{ Z,y ,Z+aj5Qi4tQd#t۱*iN:ۃjfxjw@n2ud2@@ժq#d^׸/OHUp]wj5IfIBvG\FtVZWT$P 7W2OOկk?wQ#˺p2+aΒ5ܻʩ'IaEaTl274IɴV``b 9d6v]taNTٲN jIBT_Ed2i4GG:nJYgyiyl>v *\sJV*#9U&8Eln@Ф *CtjR*=B4bKd3w :{( 6X4CvFvV9[X,\x GH,*Y}!Ҷ^{ #:|>GG( EgYNz|l$N CԵ%_RB%v&fOK :~!#@bF}vaxtt$g[?\^^֞fJ&8Nv`y ^i4 vЫԝid}SQtl6i_vfP<5xke?FR SZ~%xKG#NdF!ұ2Sey`Qϳ,c2㺮yqBqP! # Τ֓t:v+5v ]1O2kZإi^ %ODŽ+)PȮ4l> F@d}|<kmT*UlIdY ]8kp8vbs`hv"e I daHfӴVȓ#K,aed2Y,>dSQn8&8s?c~} d#,2Q7{MS8S齇fuuB|g*R  y86t(0xȢnGv˼ZІiykCwGF.g? R$6U. cR8 O,JJ5-6WlIp$ib0cu1X$۹am]"2DO5Aڀ\K@~\.]A9wa\\\t:7|+ϟBNW[bucb z3˓|wl?mO>ɓ'>s`\.KHP@ܯjpjs}p@"Ryq=!3)6Gk89ڔd ZiPT ]^^]”>-_ʊrZku]]אK 4`S SEzIpl)z@f36Bz6$IR `6҅yϟo7؏=25 y•XO6Ay?PŗiҢBSa I.(svH3/3ڶ /Nz}yy9N%xrAQԕY+)qCDnhFbF] 5Y}KcjD! P JL(#CQݶ=}ͦl0M3uRql1Z2Z p*NZ"YA*wwM ٰ$Ԅ T1xlۦ#y\~ޯ*qňPPmXq|=޷}wa$Ixy0 ={F˒Y@ )qvbF\ lZ4ck:Nk\)`\L[V4p t8SG?(ӳnK E0ʀ7lEh4;~؏i"@uT/s<,{ 8::J4!lfӑLzHG\}4)QN!|g? D-g8g+;$88N4ͳ٬VZk?0Q*y 艠YkIsNqgq_D dQ=ɎbF ((!s٠(IE4DǶm˶6i(U S j :2nHїM|,2ƍ5Zҡ (P/ tn\EQLgG^ iX!JY|8}agt!"~Pif;D2|, |b$"Hk5z }7R@+ᄏZ~chݒ$y,Ȉ({2Y^ϩVvr*a^^^FUsXDAFBLeI6)A }($p ޕA Tp~sa&HF$W&I'4/{)[+~4N%p$k(ƾM]K`1,pYm}?T(aD gERĤx 81!!0 ?~|}`ǀK@ }vvv||l6B@Ee2gfˉOlt p1F,oM'IDbh6|_)D g C}`j)J^+jl,թΕ2ou$exW&ضaF_\\l6 :<%Ԇ2...h  C1[@3F]oM H>> 5JC8S0gy+ 1%z\}Y'''~" u+dȍg ;X,R?8=Od*32sIj R+aA"B@)̂iZycXu]# Or&QPbiΎ,όve[mgiF6{(u"$yf٣?%J|KRt:gfq,(pA{6ɂuQ| 7`QJ-xJKűIDP׀O.1 x׼0dHf38W~#p888|:VA܇UGvK ,IG YE%.wyZv=zcT0HTpӘg@q (eVir#[f}/V%IUGMj<-R{p8Dۚl/ )gx) M#E&O>y>qCXw~쁀؏_s46\DGF:ud-|镥* (os j`0@ '̄[@_=wC9fQ'],P"x% x} CGCTfNjq8Pld#" jhԻ.Ȑ fB~n]ץ*?i+R4L(!F-xR/^PJ |DŽV+ `fDk h` ՘cX$k(+%IymGq'1~=ҹR%k-'n09v!G 46iN/^cNYV֕Je4z~/_ocŤ)&`0Hd2ju@NHe#|W9@ j; ;7aY(mj RT걕S Y*M(dj~7M;881ϫC 1Tmj~~E7M$Fc0ZpvYмS np28&y|>_,s ##|qAnDPIٕ^~6 YXcI>Tr,4Mn S(y5cx:s$gYn@^72*) U7if)cL]1֔!U+pʞn|nvl62VFt<>>Fy{O9<L6R8 *NPtũPU %ĨYe1Il;}i.%$_zT$X2dqF@X,,:ـc7??}~;?BΨVnw<OgS[CVU!i̟qi!y_koil$Itl$ Gj'*^Bd/KXJnq4NGoL&8t!CW9=d>N(NN*)MӭEh4z: ȗ1ՈB "\3%Ue8NLc@A<87 t᢭\y:i6N h3^ĻW -˪Vaj_tWNJPJey%IIQlQ'Ib;vOP҇I*nxe1rl AiHv⡡󼣣#B.nI$IoaB׃V#fZBd jfh)8Qe!pHrd;`E8n1I`FL&$I׾ZZ.7ټJd޽{w:ba 2M)(!R-vuRuFaډt:,Sॣ4 +nEV-+&(>ĔƩ!{&)vpJK#tn JQa[Zܻwm`/Htxڸ<ֆeYƨ<71;6LQgO[ Q@Qaru{Calh*mkZ%OJ)e$#\4 q?(+r 2 -~&ϲ\LL㤸 /@ZPQ~WǛk |+6\^a@@le0 c8Z ,΋88l^רyUիϟoݻw~)AaO?, 2bo{ `??8ALw"Qɲݻ`u#IK'(5N_*gh4CN_ᛦ3h$/"Mlo:>{zݚYרN3b}?;vV>mC5d$~Ȋ1P)z|vN& W(  !VyPMrR.n2.2"‚alGaaa*3XgSE $QcJjsUa`"ۤR\.W!Rf<wvYiHP3_lCD*;${.8R@ 1ԖkaW Pv5^諫ry^e^___7U9`{fhޞͦn7I(4=}}(PYHtdj&2_XŲN;w i5$ ?ؖR-|1L#R j)L*D6ٕP@e̍/Tv+J`%j{ĀB}bf5:t"dW΂KiiY: zuʹm2AaR(Xl,x7/B@@XR-28pR'uݣS2aNS ac|xx,`Z-lNg:~駯zѨj~؏ F#2li͆|<;>>N;%pd.<ĶV.~L!ZE3rEԷPߡ}q,"z>iVpM˲m2Sp (#J Bn[ߪj MSHpEiP`W*xE0]d!zT._M$#aR K0 tlgݮk0RbǑϘ-@63Yf+LԢl!%B8CeYR0sh*RL22^ׯ\e+*8%j!> Y-˝=U">6MS]p_8RX X{I%IZ$9;;#l,+`x7 F=1IV J\W_.//{ifz_zR꣏>^.iN'.66 h 7)Ix˲~i_Cyw7A ؾ__`TV;vzZ]^^>|p:b ,u`ۈs\Jj XXHaRN^mvVaA`.*%%S~g(;QѰ(I΅if|6<=j5@iZjb zwQI>hɷ‡aY=f w o'2"@fIs#$m@%8U;88pi_M_RU8AA1׷qR8i*dY癡q)[Tnz~klxc|ɍ@gy #@ )xMwC$%N؏=sHAF/TF+zsif/EדCf&eO|>  68tHIfAƂ[.WWW9jJ*mLPZL$ E_7x$v臂U wbGN'? VڀQו)}Ki$dtBOزvyrEH FOl6KDFvjt:e*+:Z&(\:#ҦܴLdxH@\{anMMJx(Jvp|x;%r $'k@OfYl6er(a4f]\(͍gS4T LR-CQ9I$Btd^%Twʑen4MJeY0R_|eŖo}HknMFZh<78]x:+ `*B+ -Eky^Ra {eY`.x;$shCK ?@3yFhL5}{kŎdž0/7Zz ܍ `2l6M&v۶qF#Uy,v:S\RFԶmjSyfi&<HV&BnUr3D!O4{b*›l6ϕh1c ԕc7rW U!˳z.?SJiU8 wa?0Ms^f('̀Y3^UGl6ggg4[,#;]C۶oLɪX׭V6X5:iZ zO~o?A:}\~~쁀؏А6#!b5{XN4"ǥS4Zf5eJcy Yx21D}B>\fI3HV!Q TwRHVn4*3}*![F1rtG)ػ0MSjm*ϲptCҧyZ3yj-K3eM4s3$NLÜL'nRNpwƀC|2yr{.%=#e t[ 4`K~Dt*ˡ/_߿_T)Љc y۶e/ap5 sg;z0ln;>a؊I:G!j:mvnEmoQe**\*(ǔOFK7md vVq@j""NL3 n p*G/ӈГ:Xd@U47~#I>/|>w`8,5 3M5zv/f8::RҬ䬮q4~ )`Jj =?#tPϡRL&o6T-eo>PmN[A v[V2-E%7:^+Xb^C(vizcI`BEL mh˲ t7nՖRdt:w}c+m!79Iw:zMf>/XEd(N`Y ҇?<|) IA~ȑg93L k\吓EG}tొ0QdݤUmJVpyhڽ`06: %^ZcQ@ T /ynZfVxtzrr"K8\Z "Z6\}+Z)ny$d'1A8 o*(Q,g4\J!GyVjzCk$))q:!ؙL7} Dr_&,\w8N.,&uDa* Kg}/ni`=b^qdNȁ_q(F/^pV#d`'vqq[^{c!D ȺMΝ Bc.c[m2D%䑥e R@= KeRfuppt_q,BSuhFXm6yݹj]?LݻReYif4iip84M3MRU*WYeYremQ»;cj5X%d%VR_ŃFտ?C~؏[oAl6Qz 7fRk+DŹu~~j44T.Rt>/ u,qO]eheꮔC/AZ\///zK 1;{$FN ?-S,bH;wVJ5gi! kd_l).9ކa6c1y@5U*;(BnyuVF%(@ۮԈQPoZΝ;;<.qXN'F̴^Ó SYst AEt̴NSD+@Ny޳gφ!եe|# CX)A؟&}H8;4GLnPbH  2`JkOc,(lhtxyyZ>V֚ l#CI,g!HE%jy% =Y#x>^@fv _v|SQo;~'Vu&,Tq|!IARD *ua!, Z:NV_JH`bM}#-٤+%]Uڣ0Pℼkw؎ TJE'YK4WGDTa.KIB4(l6<9 $APL JӴjO?m',[z 'sR'Ńb lNS6лv@rh4(\Z-j5.B"N |$=|;l~쁀؏mܿw﫿'''"jzj5mz>rfZ41J8m۞L&~2mD ~2Xi2irX, գHm^myI@X!/DkB@n6BciםRCziX QgY%iPdR(۝<82˕@<{Z?L///<4(J6^w  rN *Ăp4^;4LJ tif̪Y&I0RT}ՓE"isD xn;йhp-%IEX,rDSf6%D58FYa{X %tb@X tdn#+e0- ໟɲjvrrrxx{0( ܶBIز8tgMghJ߁@_\8m;P<,MRTRVډ3k й.i@n_gϞs6=24M]׽X,^O7|4!~.&J)r ɮ$N4KІiUС{^cT:}\|^zPT*(` } شl`H}A X,?x`8aZ%9d ^HR-9c}UTFv,q*hCs~ @/#>|lA8a|v&U /K,ӆn6A ߅} }I2+/QʕfHU+HZ>o`B^OԶ7|s`l6AKv}|| ݖef-٧&7yPpyIr/4.%|ӧ `l4. ؊RʸhjT#JBr?D yORIRTwmi)yJ+@vQٶc%IGIvۗM" c'IZmR33nSry}}X, p*H .޽{}Z _LT UM9AE=(Ԙz`!@07:,3/q\]]]i&5 qZd~쁀؏q||t]J#Ciq^29L/9(ofYvuu):t͑ ߘ) L 8oV|5tw_DVLNd裵(n5R_M6PYhf5BD;Aۅ)fH2QIgv"m PXhǺ,$3-òvްk6iyfhC[:ϲ4̀4Ax_J4\d(#s`CW * \SnBkJ&;Bl6ˌt45d2sNEDjI/dAX~rDe"Nsppk,ǩ$e/DqgYbY˲0?6Y_ZyK 9) 0i!xLWJA0ͬAZ7AO` lV޽{Q l6g˗/Q2 r0M^)Ls4AQ!*oZ7猢'PEQT>-U3Qs]w6ݽ{vQfyIVͰ4ݻw_|(Dcam`ke7fcjL Un3uݫ άR?$]6 cDJd17"es JZbZ)0r$F^b {♥YiA.֠oHz[U^Qo!%W){N&ӗ/^sT!!XVJ@moFeR6b~Z^}.%N. paCf2dYvxxϦVѽ| ,~Wbw$!U o|?{ `?gGGG9 $~̤Pt6]VAlmRm!:qtȵxR*zfmPga.H~0 @@mޓ$l6WWWN"`BʷoY- 1[04p[aXwǤ̗Km 8+ۤq(c@bn)MIF㭷z7l6}裏zHՂ DFa8L0MöP'\wH*|gC^:>Q6D),RmhCn#$}>O&wC`|v}ppprrt`rU=BZ-f8R/)NK|&w]ZhzUW)Md z"4q wgIZ;+a%Q埦!Q֯kefq`Em;4^a! ǷS(\ϿV'!G@[i}^|,ϫ*O!k3w]矇Qtxx@$`ӃKU̷|n& *qIZ  ;$dOA]NjłMp;,SG><N9<nVZkԋ:vV,.ӲJ$Z'XbVD>D<4M1$҂np%ȥ Z!8C+=QlK m0E-,P4[9~u_J8D6{e|$ٝ7|Ƣ a<}?oJ2;P2ioyE<zX\0d0KR@N% CQE0 8 x- jc?::j6RnS_\Rρ1+aVM H VAF_Hqm" 9?%?ACV\>y$'I2LNq$!ih?v={`0t:X`/sG@ =1t=T*nyL{)&w6>,;RI̫e$RJ%!c?@سrr\, 0Rۿ(/X Z :a\5!o*M6M+J4 jZ-; @;dS3Y1b@ Eٳjt` OpxK CQHF߇ =`Ym-8'VD#ɦ-8s6ӟEh<*%Ql,@8n{a|{g/}|c?o?xC8QEOm;a $S]RC+EiYVgqQdt>Ďk/+Kg2FeD-tIQ#Ph4Z݊(QjiRH i0~%L, :ykS+CRr ,RSH' )M/P0 ö,MJs-˴LKk橁z!"Erxd>o6|5:(?4f(,6 $FĐ ҬTl!4=owV.:BA\tFĔ3#*5\hl6!87AU8&.eꂿĬ`ʭ+\R5c24 dA;e?q__>x>m*@ ZTыA'~_>ݏwstttF,nyRE3< !,` y~xx^:(Mg#faШc @r$IEaQjm`Fĵ,#7<|VwE'raN/f-EXTx8,ife[e0Ъ軗Q20/Xa FR\e \'JWJUV8w)H˲LӰ,S ;0_8dz7ϡqhaAV+FtZVVC)*`5*"̹@B>I5ylÇ___Y>GQAIRWWWwyt:e<[`Z{~S5Z7ҕJMXk@I)g"ʊ2CݻX tZmVmbL8hIJI8u|駶mC ˿/^~~쁀؏x7FHeOܽwBRJu a uE۽vݵeeKc3t".a!?_9Ĭ3M\{W4Fz^G:=6I"B?! `=q N4/Z%nRXM%*Zq*m1"06ɐ7-&Ӵ}_$Iu!fjh|2ȸYjHfrNV*pH??C#R 3[ @*R={_yBD@y;^)b}T ($~81-S̓ҁdgm4HL:W:W:7uHD&u!J#C@vQNAPNaKv2-0 _|y"'ٰ,xOm!#v,KgH|ӧ/_,R2hjP [~5`SRkHШx|qqZ.} s $p`AAs \V\n1 ǏΰE6fm.́e"h@g{޽O?t>w]訃 R8EV:Qa,Q)M0CYzW(ѥRXbuaC~Hd&,iln^,Q%~ brav ze^ۅg 5#U들24aj[{$yt2 Fa2PJuRUbU+dʎ 6oڸfiLK#TNcI0 [jS>A$q 5g {ӌ(s0f\Q(Vo#+CKAĆmL-P`3r%xAFR m/~pw0inf3_,fht9a0f1)gO /ϋjeY4ILiY$2'FX< <7f,{O?ςԆ,6 P].{ރ48|z>yc:<#`:> vpR=x,RtJ)Q1gYk}wm6v-8mA d:bJq$ZݠŮeq`.3\ Yw]E9zq.B.8Dz*C_ Cd.Z*,ϱ^VĬ#GZLFIeN4l'gY^'Rmءlc+J!NaϕR R(E$K7@(|7HF2D' #Y+ ml_@Siӌ ZM`19  X|?ǂ Hj sgggDG xxtb\.KꩤPNajlW< #p("n𠒉AO< ðl/ (M(j>V<y !KG8v,` !}0@ @yG˱f$`0 j߅XV_{5'OO=29T*}ѷmR+A"=}@Q9-KFwE34a'k =PV)<#mY٭jgr6p Jbt0T*rDQ46' g* >g>D4%e!1Czġ8fY*JW 52MSR0GL&mJ,0aZRyqe`Fjnβ̶,lDn CRfNMz:>Rh80Un\i۽;[@,ʼnϟ#'lArXT>~/ql<^>ffa]HF  {# U…tG$r駟b7&9 p8{ `?&Һ>KT6E+`K56t*n}_kb>(L!E*KyPF(c0kBFWպN݁>_7X6Qns4>-%EB_A4XH?m>kI|2kZ|fzl)d)?Eg,mP*Jey#uZeam4I,K%nӡz"Vl ݕH,iy58n$Z(oqt:RvCD ;\E2j ےYi$),j y*7 V@0JayOS%/DZ2Q\hhGIږ ?Zjm۲/vdRl]uWTn"?IoFƬ#8WQY,vl۲a^fYjym۝Nl?H b^SP|u,yeҹVJey]&l30XN;:V|lnTJ,TyYYlݻo~GGG̟TAh46,qlG֬dEKo6Q*,6< 6B:q(^  dtMe4 0pAͿfv$I-Uneٶe[ ȅrO %t e%Kҏy~Ē:a0;`/4K&(Le 0JEmvoB-I*X(#P(hHii٬Vk۶a<";>q#Qbs$#m[荗I!0(LX#Tm,7,<ߣU|ALX(%1JRԍڇAdd-2ֹBs"Q PiM #0G7 Odۆr^A9"240sfuǧ_ofEo6-۶ }5(©bڦSbcUj&np{B|$d2L'J4M3mnZ *Hf9??gY'IVdjl Y%a0VĤXt(PJ6BE(1ht:fWyh4@$:@rg8?8)&'\"BL; "J_q 2)-1"i;vթF<4DEfu;!&0 )£J $A3PĔy V8mێ_7yEYI2]Fy RAr`I;"lGb6]r:}V@@<u]v}}}MB˥Ԑo0j5i,V\M]Zw cr9]qO&lhxl>|c0(M4I۶LЙ3lPdU f;99n4M^G}>~lZn}@KA/GqFv( 98K$HR-Tce 2,r]׿zkXhZw?c~5-㏟>} YJ_M# glЪ$tNLbQbAZyRj= rsplӢǪK:#]4+^__{j6QdY*P/\{? K 9  +x\v'''GGG38 >{d7Zec(#Sl}TJ$NFβ->B*"/خ..rXq'\]]Mx\TO0.t°V5 YPf/s}`H(M:JE53yTZU,5 -Y2Z伀0 fɓK۶اsO.}8`!j5|,>}fI'ITTtd;0vݲaiY,f펝#1)qSy֠$#kjph:C:e0"##0 qNNNQm?xn;CxIt7MQ#$HGeVPTL`C=GG?Ꮇ*ᅑ$ql7 M+dnqdٮ%hIc SffiK  $kw] (^yGY $`hp8D>\'''<888H;NmU*L,3\7z,%tLg~<P!tU,;==|9NFy>JF@p Kq) n `}5ML+h4L{ `?C)@؈P@8 IJf^T˗&v2Q3p¿P 3(Ͳ,f FjRmD$GE X [Kp"FWWWJn3}8 J䂆s4"M7x>K)Ot:^]]5˲<ϻH7"YZ`v]ʖO&F\dzy>K30c)bZ˗.N<Qu8ih#Y$yny4 Ѩu8\8'bce* AWYן|ɳg,w*hݵiB_4,x/%I)auA7Lx+@j,˶-mPL2BUaWDo6dnHdwaFQ|bz)l !b_j ῅iGP ģdq^x?]ab-gVc2H; sF?LӼ8;;C痬@ SjZ$>4)07ʔ&&`7 e`fCַA L!!+d!݉=($ʕMeƱe|p~~o޿86<0-4MEV,%U 4C2^h8yrfXa6JkG`_VWWW}^n nj=QN/+6ã#ȊdZVmabhZ(*QgwqHzI$3n%ZԂeE~tYѴ4*iD d>)Б[.ϟ?ED08VkaC[ӧO/// VpRBam[2)@)eFŞKBVUcAl6s]wݾ|ǐgpSĤ,Shq桬z1ME E;a^lIym??!H+,b6 *g<y VtzC)G;wZB>3i['rDf8 nn/d~O"\i=egrC 9r: HBBr}߇tagЩ')X`A.11಑+qxxd2|u~t:_"o qlxt:H!s0={ݻw||t.>mQJYvov =)ֹ\ɓ'O<Z{ >e;i^r]7lF@[x<(0 a8 ¿[I*eZZi ܴ!@/ЍvK]UHvŢѨ‡).j]\\W+r)m SLR1Pz?w{vv^`o|rO ؏ 47 zj:,Ď1c$qzV"3.^NbB{tY0 Zi @jJYvpp6܋/0t:78r'/@e5 eT3b?:Vhc>==Mŋ& #np`c"!GC;vKp؅3 ɉȩ8adwmop/s j8Zp,gRҥ@f3c4?lf<\bkh旾tX,SDT ;Bֶbj?m\ܵF/>c8#@%b@Ku]vH 2IUrU؀Yo׿pn]בYлfpY-H%XG@yuuuLC =0"&3fyYǞfJőivQ6*zZCȖri ;Ⱦ]E6)Zn& @D0;V1LEd xM+A)}s &j=Z?l PV%[ʕP#B*(TN HHg;nV3 O/..x ٍ/7gy|e[Vff~{6 :`eo4(8^ igkZ@~#`/e^ I)`<>&g϶-xx2-QAlCPUⲑcޖ67l6NgřHcvR4n\@ܒ$:?????O}ݷz 8J!CR2MCsU\9;81oj, _;#eq &h?1 ͠ Q㋋|~F:y#lx Y_bvi$iei*kP*әZ&z=L-%"[YaDL$%>O-ɗ/_~2I=K8M}K$r#xsGO`Gj;jh8̺c?^Dq,Au0x'BC.|WWWj/>eUYb@`}{0Vlv)(w]P(%䁇FSC/ق\,O>?r1Mj '~i+op}iyyR]t(@prro|c8"$B<&.l}H񠡀lFf!wOl2ٟzf}^& 2,˂VXҮ˦Z 0!eTSِZ*ij%%b?~;FRVz^BTTȀ)̜mB}f`b@T3Y >}^z4?>???;l6AT$ RsmZa((z!nPCp18'C,˲ _i^ UA3m 0y~% ȉć,4RbL8FNTU4Q?O=?\l^xˌ]`aGGGh̑2wku%7ԅm(XVa|/@VR-r-xSlLsP{ZRv}/Ě  YgI&iz޽ZfT 4]QJ?!)Ho+BkƓ'O={.2(%SJXKP@HW0XNwǁ$I<ߣ |&\ibɥ$Si*KH;VIҩNMôl[FFl 0G9lpJzq_{5RƸ$g2 Ԑi쇱bt]5'OI t GQj4-Cq+ )*Nű-+-6R6aiE`Ra2i5fL@1xPӧ_җD$ dHqƆϤemV L&ZZn,KӬR~;N a~؏_:=C,y~-pX'aLM&:=99 >GYi󔌖a+LQBl6nP"tgB$%_Qt}5ŀ8DEc'1j"gKETˇӑHSF`]UvdQ~Ē-<. O)"ig"iqm<'|Gq#a:$iPAGٜ9$QaٻL6rVR _6;qA%#DŀrwZTv_=sL]7 KbTPL0jʝ Dѐi,S 'h;;} .d(_JR 1rsEaxuu$`0`Qk^917k*3"}];*Ie{ Z/$MT# Gٳ?)Fӑh&a,&-`Env:BNKJ(v$HJ,3y}v8ОJ 7Ƅ̰L3.j1+KB^1u,t:%D Вdo#>sz`O(89˗/+ /^Y>*d2.&Y p/tض]֘@oۨvhTc'|?h4jۀUArCI ـ'eQ6"jHy\.AƯq*sCIG% _&TEL&=>>AZ‚6رϺ  O5h(TV;8 A!Yt,L_G" ]A\.BB~*qQyMU |-az nz! eXYR[c'(i$I4&u,,z{^ĖiUk5`fYy^?pqJQ<iLYIErE40pTJA޲M@&cS@bg?Ƞmd^%qB}" k }&Ib"uܥzw1KqL,K<@V0L&~Rt{].%0`I)$3ї[&@~,־;EZ{_[Hb<7&MN h7 B>pd5x;K12BLtrfݶl+Mvɉ*K3ԵQcEmWk54dK'YhCBԀox)(K-)'I^tn'NdJˋ<ϳxa4ǡo}:}piij0釴`%o=LlC ),h4 nNVױsfZ' X@ЛM˗~i&t*۴mVO`d;Q/b˺!Y^JSP՚x<fU A)ӲIKw©}|| ϟOL?9bJG#+B={Z^to}[V+fyrrO}<WolRPA^ZE/‹ e)[HIm-36K(~>;yy!%# q\vPGd+Te2sUIXU*:_v v6A0fH?#\ RYYyFbpH ͦ^/mەSV }h»l6˿˿zﯶmR-"^ee)vfVڗD,ۢ#JAy׮r#5W*wQ]k$؆ifY1bm,MEEA$I>T >%p) sJ )PHh̲L820  RqVVh4 F/ @)i$ {h4F$Wxr3JG4[6>y(Pkly?fG/ هB)޻F_1j ;P'+;Μ8t8N 0%LP;$`(TZ",Kyf:X. t0 *4Oj^ qJrDqLezr9@9+=Lӄehb#ܦC ( ;ijEv?O_~~,PRMi +]7,5-t=Ibpx<dkێeZz{8@1DZl;M4K4 p2z !NzaaSe>JR۽lZ_£FO<ɲwG^HS>CaiZɄ R P.d9PPNt:g *ظȭT*/bM x4`#n׏=b#%a$#j$  ɥg%_"M|^:r(@jH濬[cFDi$Yvy谥;7#$@DYLn6z=.d)im1\lɓw+1J[OǒsSR|>{ɳ &irCtMOD%=!D|لܶ*N{7TKӴjݥ&<#Ͳ8Q4-(QSd+o5_̓4XtTpF\D6FuM7L5 <A^:PNLӔ!OҲ,K4͒$EE(j)6\.#Ynaa:ajiT$J8)KH)]םw wç'H:J70 (tG+FW %8Crlq(t[i78R*Tak6I0U (S"TbR~",Kuݔv +#py# 4.C\"߻w^__okVbQݿwLsan쀀5J/K]]^^uԬ ,]\\\ A4v;he(LF7 t##\M? ˊʬf[ۊ#4髫VuxxX7"wa[@5FTn>uM*NȐ%* iۡ<5=Pu]VG/eyB"3˶ժm[I1[=YFi'eZVu&F ]U0&Tp {{QA~P*9S5MCmYF%DnC_~%P4M!4ddijnj[2rdahH ]`1~ߓDZG$ZaJiai<SB. m3h"dh5Ms(*/KxяqZ?99=؍$FO~rxx)h40Β]^xH ,3M[7 "%IC+Mo[Т@$R3<.Hd^~`ٖcۺnhy^FbooH{_Rs(W*حRe鎠"H^d 9~ ;C4#٣ iyt~~QBk18tCO$RE t2{G4Mbǿl49$Pqx`S7v^g9)ggo۱UdY`0h u]4,G,H4a5-BR8&t0RI?}[hԏP⣬];f'P20>}t:|]pXp] 208{`047.cq?ެ5Mqj(DhsI&WJN0 z^_.dۖ8kQ%qR*ikF%$M]M /VS+)R8&܁Y+ؖRZQA. CX`aw}?0NX,n`QalPSYw /:JbΓ'O$ETVMӨkqaqU$r|;o4@v ' d!Bt:e]6❝{rrr||\!+N$uϣV.e)Kp+ T.PRD_~R Cbl²_6u}v&i|>fAi ê@〝8s||jՃ#:b؍$Pgt][nty_WzYZׯ_V~rZQ)-j$fp)13b,2Y4:u[A}˴JN =(ka^j(;zdH*~P+ćCW)M 8B-Ŵ,GS`Ʀn,/+ fbb# ȸK"XYh;Ç%4FaB`6-ǏfNJxn$2q^SHM4s;Ipjtڝ~lXevn5[Lֱ0 C54yk|z`7Fb=ժ\*#imvӬ {m,2-2 `M 8n6i+(g mA@tuuu3 L Mi,N)eٖx:6L ɱӦt5o05`hbؖu=$IP[!a_7jµsx]8Ni߶Mu kx,?>;;W2"u~&v/^< @Ėޢl `֡$'fM4Mu2Qf3xUok%b×pv:B"±8R $`,.?ϲL74NӴF%^meq/ #}" yl6Fi? Kl,xǨ@4MM,Ӳ,kheڎ6۸]uCHxwqqq~~^.˥-}іAZ}Пq\7j4Mt@ wԯk_}ӧO+ $HiyXp(Jرal OJr$60cJLZ@spmZ{{{f$,IJ2~E(2$c"h43n(,N- `Y<]ݯT)]9;rdɰ*3($kz)%R|\k4Rz_$Xp;H"b6XT...Jҝ;w:iږkZanfYK'ONOO͆uC3]8UJAj^?y21eѰl+K0R:RcofEhtKO 2 l7B|ԓ4Mom4ƓR J{>&;qB;42>jhVrJj>gJf]x3d\ԛ0 CeiFۂ4-a,s6r%b\:ǁJ $ֱfi8GQ4L -ݼI\5MwVZ[Ԇ %%2ax>i&iz||k)Wr< FFDRek ɐ SWWW<<< E=0M"~Rii#l6Ѩ!/]H*d$d''''''G)%H@R Cb[9M%͛7٬V22 #Tl...8AAB88ρVz ANj\|YiZ_ciZ vԴzWU͛z8BްtKQs۶K%/}:z>2َS4,0@.;ia<E %t:V8I@=#X,_Sy @ ?c~JnZ' wҹ]PC: ýpȾ v`SyHehJOFQ#a$s$08TV%;$M 2f)8F0 R{X `Y~ID8o)4=b D]וڶE\V;MJ=JV8$T@N]בS(pD^zr䓷,3/kizZ( ÂJ9׸ Z+1%zqO&(l]`ZFmۍF@,,tԲGƢsp|ro4Vv\'Z贒-ȻG %V?%2]aAl< \.gt,4M}{||93"><":acOW$4#ʥ/~ Z*躾Zʥfkki C>._:G{wxx/ A W9ۃCd-M)&eYqtֶm˴PrJebg'"zfi,4DeF8eo6k̟Su1duVN,ˎyb ;'2]'1L ݱm7MQ!8??L&Ŕ߿{./q/o_^]*#TD @(JcỸ` 6zV)׊ȄT唐 Op%*oPl6ǝN5wncBHĔn=r]|ߣ; `7V?~4`+8l(98FŒ=TBI Ν;^4 jd׫bHjkX#B:CN&Z-ɡ\ %rɓ'Ѝ 6 v PBM*ˀ)8r$'YYFQ`GH'B"@SŜ0 r2*2FQ8O~ 3D(pU@!"Ѕ1ͦٙar9c]7$Q*]uQ9۹^ݻwC(!r*˵ZE9e*Sx"h%u6Zw.l6 p8yvwHBH:v5MQ*1 Q7[:+>`]OStx _zn쀀.; P6igbR垙E eY~lCI,zuz:L8n8𽬀ᨃCju:nߍzfիx"ƵL@s+(QU*6H *}Xa4EgY2Asq%ZYp8BVD{-4`1R<ai CLdG->88Tϟp.yqI7o#r~YƁ|xV1"KpzA? b`es*ҲA MӴL34tMSAFa:MR$ϰnCN eG+rƹ#>.//RIA[NY{d(7|a~#6Hs2Ds Ayn}gi{ǑD;dFPU ]כl6O0L۶)F"zL&W[(,6ڠN~0Z):eYeanҜvڶ=̀F\ͦCTL LM`spxooqsrAvj BAVv16ꫫszЀb*QhRX3/Ւ:@@)YR CBxxyv[_0EL5qzPu^GQ%ʑ*t-h& ša4U*0 xi^ٍ_[2N\űQ$pXK|M{(:ia^]]%qlPbM`i!t o+9g9tkٛ7߼l6*#%jʊjChCmGHz=#'] Pcec;FS溺; bK 9BPj!EjmۺEI yjI0< vz}/..>#?h[+KSrl۴,YdΓYFC 㣣^aJ$$Ujg-MDxD(TT[ 'I0,PEQɱ4-6p7ZPBl0ˉ&`DVYt%^;ƣJ`H5Ұ=5]Jػ^[: zD0أf7 ctGj5ҰY̮An_.N DZ hJslFGvY2) 96bT*g5"ޮ$(k(0 )Q|0xϧhAիsH0"-H}\ aq>K^4t&2f;SƍہTK[ rSlL4횦xҊ ;<.z(x` mp8RQ aBW\ugX|ZvAn؍;sSfHvq@44FqrrbǏ///u@Qd220ܹd&i`0(JFbQP;R96|>GՂ~4ߒ:*oO%rkQ=zvCn6-{W 4!rN`3L4D@Z&8F)S)Mi[-G;P˥iZvL!_ v0u]K4Hqpxppx?n[('M{}'#6ME(:N:,p麖rEJdABk 3V&3=#ݻ/a2-BsĬhūGLL'TDC뫕.Ev] Q0 {i9:A\zFwcCa>u2 wlN/^g?C=Z w`\ Ss|@<,M?gwwvvmW;Ν;wsr> @ʻrٹÖ+uE>99}~c\-tFTFcDeՂn+m+rO yyy <mY H򶿿4Mab<LUR (Ҏj17bҔfiTQQ4L\[dޛx<>?;S[A>5.N|gTi&De7U*rr@.1>HJʩ*]#`[\<1H9!kL|ޱ.l=zv}73d6gYYU+JR7CoWyII 3\6KPNիW?]Sn؍*n|Ī屇!Cykdٗeȕ!TV3MO?fV 4mPR}ǹ[պ.՘hf`y d/..a_ mѭG :u]T+qi|ᇏ=j KXw@M<]5M3?DPZ@!ȥg۶;d2cA$<[$eVQ0Vy Hlf5H,SӴ4I쨶cJ ,>̲ Nf޽{Nx4{+*$L>' ڶ/^ |cheTxf]B C |]eJeSzh畣  P9ЧQPd Ju wMWTÛ6zz J.b0!őt˦ 'S`;䙬oT|ԵZV+? WTIfy+[b`=<9| nЮLY})3Fך! G/&)4/KoN]__xݻCyNuGά4[ABA6I-eW(VcDZj>z ;k88 s,v~w;0pͲ,I((f7+z`%p[cd sB;rQġT-&4-q.//' >ŋVRtFp=R'D)~@@'Ix,u\|⮅^]]//?~yyn$#eZ=}SeeN $!FԍMm "LeqZ$I.@f7vc7v@n|cw,LE1^wb"H"zYT:::2L%鈁(I!x@T$?Vv׻O+oka;??dyiWl6OM@S`ZeV:zL;j`+R%"텄 |>L!8h4N_?{?{JrBBqk癦9_xP Z!iN0 Ig! p"cyl]z_PN5YAy; źMYP/K>F+Ѐ@5<8\]]1v쮇QG}_۸up"U@);2%*WSQg3_:ʼnn?zh+ J]wjyLj' N\x4XTQ!$疋~?/`7vc?Q&<\#^7MzXCãS:==EO#(([;tB)YmZr4( GWmUV˥q7#RV8틑$IQI?LyK:ǐHL>j<7M-1 (}Ȓ`zC"=ǫժht{=DrIYfiZ,*f*D/V 0|xwvH aN -R_/%򫻱; `7v##ÒXXa $e= Гz6nr???nZ8g.G;)i$IR.kZt:h<S6!x\5R@7z?4#7ySemUZ\.O&8F rwԫczLhZVh6NOOOa)iJ($ @"sTR jZG1g @9&%!4M'aft КƷ) vhJkvfrzIJ,? >Sz!X (W TFD ^ҷDxP{DC[cWWW y# -x(j5\Y-qMZV*9(}bOX p]v;>?8쾓{$x ȭ U, j{IF# :$ǐ1eBLq)hr+lyQnGb>ݡjB +VM4\*BIxr[['7եJ)ӴXm$:eaʔ&7x<>;;u8fbJJ0<B(GJe4L&Fc޽$IvA 4{{{w܁=vt '/"Hϡ~ـ!, Os@lB%dڰ-lTK'Lf uq:| н6W{ku8n6PE$av:]p0dNׯ_cn٨<6u@IhϢe s\.:T[՚fR@ob ^%=01譀[vJi3-O?NI)8й x"J5R٬TUZo^ J`r5фf}3i2-2 qs[Azp*.0Х,QF Y6Dn ZZ*ن۶m0mh~$.cޢcufZ$ʱ[J*5;ie|񵮧4-PE[\6{t4s0 + VYg̮y$N<R.iԛ7oRpe `EQˋ#:Jn^͛7ӟ>8v"# }\mCs3n쀀?^Ǩ/9I X19L4ȑ \VJJ)mGzF:D "OK1a$rS j,/rNI%1*0/HDel!w08JLV@~iY\.GGGt&50 7zFX,߼ysJxxQA\Ӵ,Si't7$im;\,iZ-A? (0'&~҉ I;l- f|rgggz>zFDK5˲)鬔ʻZmLY`;BSLeT¿]DCO4^$VsϞFaT/T*t%+ \.%iЅ#Iu.Q`9g=z뱒X);z(l D|Vu݀^ c>t]w<#)8ڦxwMavJ?m;7yݻw[D(x>yTF9&Zqt]/vN`fa?r g8phZt%>4O )2`G?-ۦ NEEV [<71ʲe@~bnZ-QHOBKm \^^J%,1i)-PTVu]8b}R 8f4M? .hƒ-J].qGÁ;,`7vcwpכ>RڣG܊<6 7oޠ@͘r' HG%w,rJaBvNlȦ z[nPR9ruYO?pb]ՐgǾT*r6yd6ݹsx;*O FlkT8kX@ Z]Fυ2tRo˞5Ї l)&*X, 5]KAΫ:ٲ,K5m;/4ՔL)M{''?яqHlUF`/ʋ-9T5cRafP"hZ  NndF뛺k70n2QIT*wEV Tv _6ZM$^EoH^l\ D<{L!(,6rmЭl_D?::J%2@P+nw2LSw#0z*7 E \Ӷ]'Ie[lTR/~ j~AjVk<V0 u݈8MPI]$ jմRߞR9\&M _Q!2`3sݦ" b^'TV%/8#D~????GY.s@te='#ݞU!DG16}n<QQꫯ?~<y Aԅ7sAMݻW*NNN>DJpw6&gu]e& RQs m3uTreLӶw0FƻSJ$3@(=I-֔J4Q,I#uèV^{qדJfNP@I*@,.Ä6ДH) ۡg~ovj!#2jҿ 6hs@=GC00ؠO0"r&{aɳl6t$!K`2axppdNT |Ç|/f\",cv#%ߎ2\ ])٢L-L+Q̩䇐tuueYVt:H.rz3't%(\.0$qSl@Z9s?SJY3d`~eh\!3%~ri)Cvp8_Lǥ6[5?!>Lim#kvj&.,t(JNTTrJS}%oi$Oljm۱J\.W/]"}Q/^h4xVܩti ?f? l57b"U dSH* Pu0HBd e l\N3kFI\_"h4S 4` )mv]w VqFaRmNL F2h#X#:kH [Ki-ɻ; `7㼀?t~i[-V%$o"FqZ& ^j 3i9pTC&)R `4нlA2]*#`GEzXS*Mӄ?W_}l\zzjcL DoCynZ Ba/ xOz7 G)ԵKn/..lۆmۿ/>d!F (Dnc:tFm!% itH{7V땿$K?%%R4 0-SiJrM4J˥Rz0 |CLxVT*o 0(K @0fv;.+na3GԤl4s!j-:JdN2S2K@hw'*ٶ,˶0y勃 8E$GGGe^g6߰Z9v^ߔLzۯxx)lŸvMRэt$՜N8f>X,ܹs^v-kŦMabKD :lb~Ξb'|VGPJiK8D*岓O* `T*n( |>5LuVV~׳m{<6N,c3 ܮ$M4[L;5 }~]nʣ$X@.$ls6 )r%TʎhZz2,LQa_C2~Eb_VHWbL^Vbzv'޳J\*'_,TDH*aA7j۶[$WAr$uBrIZ}4f3l ʲ 7k$ePaS;xG>dn/5A]ؠnDbt:"ʻhy7vcwva?Cu7Cc6\ömr+HI),VeIiGqݻu}ZV  8P`2hyzXTUpݣֶm:Qx;K H1' M~޽/^|B,v{,!K$-JV tU. (P dVCd|i.e>xzzl6Otz~~{_R2ۿx<|/K<6!eCx}bz8 dDG@@:d_%7QZaƒ V{Y/˽nV pKnjnCW vQQ R);e&e#'#$l I(2ϡ6~RHCJ/AkChpC'T{'땥'|lRDtѷjzxxF (/˞-b^U d/sbfz%-ȑ Ve%H7"["Mi\,&#^1%R%=nY  `OLs^sr$zabo$#YA (X~_'KF#MS5A`YҶaroQ4A 7e(MtR)/q j4 <˲JVvfi󼣣zV%1nFyyKxz6V+Áj˽=44Jf\ZD)s!ǹA=Y+*A, C)ej= NH xд%L\. Gg: jW+q+aҤSv%FB9&J$ːzpcc0p8|wvqn؍m?p0<888>>^/^]f86Z Ð5.V(r=dVpfZ/~yϑ#B:PǓd6- wXzfr<88z GyiMOn Ԡ=}XZ*4i:ZLZ. ;VHDiUYǝNfUÑ\__1'?B;ߐ) ~% =,<.jOÓleǸԥ#b ug7,SMmP{RRf܄qGa n__mΌ,S7 #B+DT:sl٬,+ʶmW*U2JX̭CPEiv Fտ 403;~AKz ب@eȥRzZ` ٗH zUʏwJ2 &oQuE0\,| uXdftrr`bae!^܀v L8r][T?8VX\#X 6INC6X8V@@ppkYHRYۊv~^X/x܈n0ˡvw] d5Mol÷luC^Lqcf0%Z1rAa R D-Z(t۲?knUǡpvvWp]_}`62xVׯ_O&V= { HVAI bxP(fUz^,0RH,MgajnD?TV2hT lPv/..^|`0 qkxg.QQ#ێl!A ᬟf7dOƣ.wAn؍@h ,j/rXl6k`iL'np(VF2J2|a ^GW_}l҅G|>lzzX,^xݻR)r\MӴ-̑ @K%\lGd0 t޽{WWWI o37^Y(H:e4MDdzAO^zc xi?ݻw}0 ɨÀ3 `k'laMYp 0 X.Xa2k_SE$FK[IE"6Sog&"$˲~򓟼~m+M%qPH@_~;"-h5s yGi rh ("xYCɒ?@Q cB CGer\Rh  PlH5>X FS+_\\iz||3}E0WCY.z >s]'ɓ'OH@#˼\\+b)%]T<Df3Jhr~)#Z z %9@so X#)|En-#'[Y윗ޜUC@*Oɬ 6:2I-Q,SkvVmڒG|@ p鹋.6:J!f*=4pT <2|ǩVlq]/ Wtl^,JeZMgxryyjm xq]__Wo-5:Y$@ 3fY. lkiVU jf%닋'O7}T>\E`#;nV߮ yv(Am?4< ^~MgVSc-I|WϞ=ɻ; `7g$$L .//F`6AɼT*~ X> :V~#3qagC .g5#!5**wE&Qɗ_YF ]|@Au7vcnN/~= ]*=*ƑӋak$=ue-5J=h¿`\ˇV=DbxLBlٜٺ+bApj"3lfe%mi אeRRǽ^*,HEH3*](̢XšJDM&zvwT<It}ERS ,no& AK57PJD~RnN{qAS}V 9UR)z2 ¯k2 =zj*TorIp oY"4,C B\5$A YF ,urM/( 4{, #rYor.?~_|{DIJF2fa肚|%jZU*UЧmSB@a%4MGf>6]L0iֆ,]|d#) AaW>nTHjĔ D[%xDp Q'5Z7 dz w;8}b>/W*fVE?$H,@@6 9@8&/&&u݈#`?}UOH:mL . I./ܽ{ /w2͛efh*ouT*kfd<z_$) 27(dbХG2 ӴL|Ru@*JZ=|>NKԋTTw'}& P"Pdlr2'Pr{|ɦF`; W_}U*=z(gZ-zeZ z,CYu݉ hFgA7\.@yt:V+0mۆ B_K JU2: TKJR!Eq&˥$i@xGsV9X!40̐)xR 2RRT*Fж%wy습Ph9:pMRZ}e}}@u 4~ _fn\}j(s!W, +`1 E۽p@wyg</dP0 [VmXĄbmXAY. 9C,J%Dp& B+!2v۔mAA@<jdh4dӣT0#}2`@Wr`\04gٛ7o={ٳ}3Oxh h8\b?EgȞl4ni+4Zn_Tݻ$Q׃q؇~xvv6[n쀀؍c0C<Y,ɿPφdWf[CA@H g{GeF>v!LA8X2 8;;~@a{6%Jrl۲4Ml6-l-0НB \ _8Aͅ2"hdZVegY8`0D"ŝ;weCv"iaZ2;v7X tZ&`(Ш|\.|P-  ,$]d+r\V,K0"7˔oS>5 -lU"C@*Z-$<(ZUVq p"Z LAԝdxmGPlLPTVW`hJ隦_"YRosVp׀fj %h/4gRj8-{`mQ5MK Lr7p"Tl.M;f f3ZlbBп@R'TaEX҉5~l61B~vz̒pXT*saZaz\ǔiSiY(.3DM1&。'q4]WhW h+ x}}6`obړ)e42:@!ݻw/rblnmVhm˽/_^\^nhG҃XזezfO>\2[(HB3߾ `Z8~˗/6J417.^6B@9V+xu݂-*p`!3~KNZ)1[ ` v:˲RxLdɻ; `7ilRT6@hv#eşq?}t^(-#B%qnɃ-Ijgu6Q_ S5?o6bQ"[EQV;::3͖Q9PV9~Kb(C@VrJ,ݦ3NB\)0QtiqAlD*\0X,NduRj\ 3Ƌ}`0uG2W,2Biv۶kcZjnh|kRƼ`U XzXOñA+Y+]|[Q.CpZ Jńњ@6[ eYp^?~ŋ͚m8VĈ2% +ϒ!O"2zf5M . Gnu_\@$MTvS9l6, f>z$ຮ`0vjqRzlxͲwϟZp:PJ%+/z?`%if5H+~FSȬRiD")ȭ%?-: +ʿ1ѕK| FZ:!;7F/_dqa#oh!RIH Q0k0S*<61̤/$& 5 G`/l-m4SqzZi$eJAk)}%dIKl_}z|qtҪ&Kq$+뺟|{ݻw7^nrJi(`[p{1ږ>h4\GD4u0mQJ7T pA=mG~w}} I< G6e"0=%Z]dĘnRq5޲ ;$ңVu]1ai,\6a7(Qv dV!x޲ּ TG aǠL e Wx:F#rv;8p8`ZyK4} GR;w(>䓗/_B#j8jJ,R~c2AA :R @O>AjJM˶, 4QH [z=O?Omۆ 6 5V 1`V[VLz=^~MgJ'NҤABDHM'6<fAEQ^{;w Ő @9q9P(b8,$ȶ40+.wz=l N3э?]X(S"Ley筼l6aѨc`: Hj`1S\j(HhdJ`L 4D j5vA-/kM%@zJISHS /C[*fYQz+cLTLQTBzrr236|c>'md¥:80^%#U"&^7/Z "_ G|>G y.mj#¼ZVh$.e|>3 VbA^Bzyv1aH{?9uAx7["!EK jp8;w n$7Z4moodKX9BL8ضm#5kI$bh=P398V #dj4M7M·Rw7 h^C*32xŽz$vnf۶2;=CֶvӇ2+Q K T*I d&|> Z€g}B"[(UǞAa>fxLT/=5 lf:Ogu2؍A}lm%Z `"_\]]u:Dpiϟ?DY^!x:f 헠ᐦ l(>_CQj.lq\._xO.//MlZXC3m6%u]2-H /iAC ,jۈ{|>_V(/- ,Kg?̆agd%0SbpvEN8(BbC2B.M|^ØSݤ|wfSAr[Dlc,&LZn2fFk78g}6 `!q 0 Fdw&xzX4jX(;w^~=Ukh=Y:eLېyTsxÖaX@G4MYΕf.tD>\Z '`AY6"0ؓ<ְvc(]aݕD04WaRmaR#ׯ^ 躞H\[zj|1Joe 'kIT rar)ׯ_C$Ȳ;w f DQ.dM(VIx $@1$$Yqn:v{ h2^%Qj/@ `_Z1_FhZ]^^FN 4N)΁%3'x{lZ3^E-Aw]h4|idRzt#}PV B{^^2/$ l6,~ɖ3EiN_eRYY$ }WSҤ:Dy~ժT*TfD `l6p<yqJqPJu](`ijYfFQ<7rvpFZ|$VaVat:P(@ǩVNUm>1iF ߻1jldʤBuVHى-}8l6($60Npg) 笽KHB 攂E[$ߋ|TBbn?jeh%4Iʺ4 ݦiZ :pG3%=.aY5.Qh-3MBIo6o׺A0LqivyyX,mY|;@˳3V+e0R2!1SHA"χgU#E̜D8E+f@fch 6l~U'%(Rī`bAiP4>hܨT/wy#$d~bYVF/_\.ktF d0}߿sN.DP&U,Ov NO;;PTOnD @˃b: >F FR"'Js qQ m/Fr&'*? L/W{wUN$o:N wX 75fԧg\F#4;`}1ͽz% ABt;U24Az`X]H.yErӾ/[2|fWT, nbJ^ 2?";[ǭƔ $KT6if4t}R ؜mi*1ѲL)ˢ7W~i ⣠lF^ZI+J.ST2[LYa ñm]ӣ$=JEn薞eYaéVZ5M(ܪ eb2L&$I-Iz)cYݻw'+`{vZ. ]'chJ9"\7.Ui>{RS/~ȷ1T.8r|EE^݆6&~"L0V'Z VffՊN@1gv:yT4}F$Qf2 OYh4jvg<2q KYrl۬9 ZmooOu?>}tF ,2a0p}}͔Z\afm6>@g%ZV@G䱴Y?E>p U\$@Pv{^Iz r`ACS0TЕ`/oejOPIͿUJYRBVx(@vaąLJХJMs/޿b&($PӛNiĔp"C1GXJz1(fM)AIHbd"c=JU6t:l6H|Ig;\lj(myx q4XtEaL6ǟ:N$bMe ~qqZ56LANVgϞ _L)'"]6AB! -j(M| kPRPqB@ TAOqI$fJ:Eɻ; `7Vժ롟׍FSҴ"hvO9S\`o!)5ٌR?VJm! XBJh` _= &n"XA7r EI"S\\zBʆLozXsLF}^|k~,q혦 Hp(d!T =AV0c*$X iz]qf)$]hgh4ʹ,Uy\ZVnn)04uͱ#44ai  ۦ}2Y*W ;IYП/؜I˒S klB6s0P4PhRfM2[cSVel)(i~65?Հ_\.'yb{0 h=`o]8zann쀀n90z3,&sem[uO[5r cҸhG%sD|Y( &(SP2hA]{DdXppnϙ'i+fYvppʪdZu:NSױ@L 6 cDxCKal1OS?:4F7^%6AMFBe=N0z^oXt:  }| h9{OX`ii1YX@y(:NŰhN/_w}6JH~vpO*wH?fV-;˔)籱А&s0 H]ݻn@M&,mPkAxC0 2#[B oQ2,<8#GFu(\wc7vcwvfO?_8(Q/(0v/ӟ^-$YIKY.#{X>?pooD(8FQT(|>w]״,MӬ@fDm3Ըl4Jx^kJeU+($EgKgu0K̇$Y.ggg%T.N-&#zw !ֿ*Ѵl)],pӨT D 2HUm=jzz!Băݞ`N"eYNG+a`fC^ 'V>쳧O?/r·yJ8yrHjZy˥e7{†Ʃ^ 5ĝS\RC9W9@ 5޽+%D`!*+Vy}H6D$*;YzeY>b[Jq4__GK aNb+FJsƩ(l&|ݒ& ?DZX)`$9ޗ4I dKA@l><` ffYh@F$ edm 2O%:@VwpxxEd:ITe vC74MS:IY%qƁ' ]_vlB>m躑Ym-ͦifZfųlZH*ۚh46v谎F#?H ]sTom\YI>~r\.RM I͓.q$bcۦ DmohY6h!uG*ro`'&(򄕀,"xZxӓ 2`\zt^aW_M]v;>8^.ʣ/jA%3 f -t;nCI:8ly90'HD.ftFD悮؞9NbqTlFZ5 c:in`km*̇%@E;l%EXWޚ$1I i}2G-|j(+iQEmR--m&fh,JHQ%HN/(C" 5!sCXrdKAPAg) h|(uAa}K]zR KΎnOdC14,5ȥ*,` 4JǓh\¥̶m8Kh3nE¿KEr s,onQjb29VǏRO* a VS}'\pAB=@rtTBN"q(@ n&rA#ƶ,R7UoV/JIJ%He7v/^,aAN*6DeW)jj̻.8IRh0B`!gª/iJj ut eZ4M ~iI;N)2X.Dj av:Cש;N9z@Ӵ6M3#I9aIÑsA,BTNdIM/!GYo%ݩWjm;ܗ@P{W%}F <D0~>}Ν;PSWBT*\*oO2Mu$TH BJ.cA%0 b"ˌl(X *mDw&N@o!d^ RT:접ۋG(ҭ&ԶE_,˹$Jj 2f,*JIl4&R*K Pq!5Mq,~J0qn6 q5ԉj 0Hq` 1 8{.T*u:}?ZJdq@4-fIl,V ;U|88R*5MX,0H DY֘K@as^{05MË}?F>JSM; `7#:z F4+w1g%3*#Edܻw%#IJt$.ûJ>Xmx W!)8to6~Db]$e1At^ˀ}REp5 r 4AѲ[``8G}ڳKRǵZ PG 9)H0"]L2MKFxSC|>GzYu:F^ 1F!7dG;"Q@ϡt%r+`\,YbW9T͑qZ'!M#³IZV; pjBcvۖ r,ġb7ɏ 7n8::Z^^|rZT*Nh,Y%rDȤ1!{a:`$0_D,)KnsZ\JVTpW-#Buf0mS(Ȳ7sqˢG׷P1N1ұeY詧¹{RUUp'sf`Ol4P4ݻi rT(1R_`~p 4t5Y2y*8r3< ˽syu;2չ>x୷Z[[׆0 ~XQs7-% 'vx49"jc=O gGW'?>>ԢDc) x1bNt:şcƵ"6cNM$>+bTgwW>L-|kKKKh&4.,OIZ9mTHD0Y`#'RI'ҝFB I ?g*=\:f l $숙&XGQ Tb0SwPQ H9r2!#)%' 0ExMo0ڂA^6 A,XFuݿP5)!jDp P!WHHӜ/l>-:+HXHjl1/P((J@!c|HؑgXE(B󚦁S&AG}%OR`TM!G#vl@(qO&^\.'& ޺uk<gqYd@@>,;KEccpT*s*v#tq R\.wXb獘i\E#s5Olt\JYVaZ,|qfknEtjyjT.ȼg^Y |RZ (s9].y#q_\\ av]uϿpZVCBA p< 2-0.]&d2 |(d5r w4$ϣth>A=8vO'p|Ir:r)G2 N") U\ |q;wX'n0'EтyA=Z J|Jө9U;퐈A ` |ewgŕ`Ţ>ӕ dvtt&2pˣ0<88}o R:TdI%Nʀ\-t୨(;u{Tn9[;Lp{{{{LnoiT6E &'E{h0\xWö8<3V R `e:CX|z]~Z x"ajD?&$/K>Fpw;w'B{ /Iʼn8*"D"DĨgH^(iR{DzZk:G M +cɑSPiD8ODUTB?8ՆW @pxxmt УNUn{m˲aVXtah8=Ç_z%nWUb2;xFn`F 0 #8?`KQVѣS oFAU속F.K8CUEY/8Qu.#MaP,kʝ"@̷z+ " qG;]y<tjNȉ8$ Ë?_ݿ_/3ȻCQ2 VBAS5NiA@BR=]j5HijM t{ QFb92JF@$Iߘ%xMbP8<<_X謯7͍ gVpfbVAy6&O4* @N(qAy)rAdHdѶ<91>n/--tX}B]ד%$ Q(0K(V </JUQD.P0r2><'KlU0ނsrCp?-1CoޙdQ2HuA FY`M.ph4h2AP*q x@'mp'EE>3bŤ NKKKH-ˢ!ŜXV,hN&~ƍdRm۝ZVa `j:2tC0ݵm;w'@Npy'"#Ȏ 0bE\N@!B(IV ,5DZAZ;~\F'd'{/p.9DZ1 m(?k<ʀF0["!p CgL&iZt2ޟ>I|x` }NJCQ {j$߷mVj0 D,#J#=k4쫒ǸL&`Ì<jG_<IMduuueeeŠzBav#9)\<GWw.b;ȧ3a='eaRK/4d8,2 b˗/˛: $=C y1E}*w1#__7yttYhXh&oeY5MqDZ(L o6fa EUUEӵ8e{m4=1=R$8RT4fi'B8 `EcVPeC>Iqwz _^^NP/ \ZZ* ˕- ?1C@j-(v/SAD>62!t hP!(B%Tfq;u) 1N'gLl3.,ܻv^/(Ґ"-|:>m19Ay)`\RΎB1cPi&q\o4H4n4[AWe' X@%-A haacD)T& #Ey9>>~#\oy~9Jt:-TVtEJY2ۢRjCe- jb |pp`f, hH0EYd@@ϰmOOͿ7 zHѹm*dR76O/qQ@?q6a- z^oZzD]QTEQUSDKD<_NFjsBP B2'˲P`*4vf۾zJxTS`8vǝ"VVVȢG&D!%~W6IfiJ&!뺎3NC8!>Ncŋ_&!;pC$'PQA[ jf[X,bt4-"e *\L5?booϱM9ęэt{Q)IwlAF!?[<*8 #`  UP$1c`ɲO+)j< mcT1 X8v+G)SO=1_re<ST47n<(1@`rk_Zׯ_>Œ zV<諑hw@cbtlʕ!z2Q+ WIzKQ(\r8M-us:e:M(S# "9o4MT/f>b)eYIEq0LDc ZP(B lZ-ذ# cHpq(X lٶY{iԓQ@]!_h[zT0-2B(/"'4/8IjjXz&.,aDӴ$;z{ %|qL95v7T*|TnJb?+xB7Ͳ$6R cʚ9Y_@J_BdB"Qvs6o^!Y[3e/4C߂lp8\^^GñjTXpRT)1@4]FTUE ᗸ`H)$|+uғ2 aY@ \@!G įr$rnq(9 =e?iRV& uGժ,)Lp'E:O[C'.kf3GE d@3auaN^fԇdGg2n`5d:5{T~|@| j(Cߥ%(oВ*1eWYH-m+eYzL_ljP(XZl6 [41.:Lpټक़)Z?$>4zt#IЫXS䃑 F/n~啕.נk !nEYd@@@ύmIk4]dh԰tM- | 5֖8jFZ]XX888$o ;YG.g܊ 9f 3!$z`l+({Sؒ\bRS섧xyCr.jFcyyYV܄Ç{!&{'$ @c㴱|Ԕzz5_Zx<6Mk("ý۶:T2t`kH O7},5Yʏ!np~Rj(V=\.W*Y(TSXdtȴ$Nӧ>|q04USU0Z}@P,3-2 uYsxdn<׆# _!!EzQ^ yҳX%R[[[~"v܂{sЇ\$55g ^R@?x7tt$ ۶]}T4-|Tv$נc!S#n.⥠RNի/} [jf+ض}uI)8@\"ZAEԺͩ<}~O< 1TUL !|{dE4|O?o6$߅=W5zJGMӤ lRMHbq4#3$U܏8"=eU麪jqt6RO3{) \.*jEq{빞CyK ƞ 4)qq4Ձ!DVޠ\*jSt1H-VtWЇ>4N= ZSYbg9R,,,95-5 @nqJA嬈7LphZ(X_F7 ٕ9I )jg}T" ([,p6N#. x% Pu]ZQT"ye{9y޽~իZQq\.W%_^\\ yUU8Cw4\% ]zHr[ *vXt_@zuv!xZuvYzR&ESu]a^P@Ɵ2R-D'=(Y1S>2"̢_{lؓO>EQ|Ͽ[("8-A w)/V zÂ_,s0ض8Jj?9KA!0S /Md E/dqe͛O=w1~8jo;n^J%yRJxgh5*TFQMUH|OsszX,F1,vt`QI.j{2N٠c㗝_wϫj'Բ_m=&j VUV S;;x٣fjBK·OMu' #rGG E͓TUƓejZgn)barHAYq+4:^0z}m)mllDZTmv3Yퟯ?dZ! (nVV>/ ZK.kTyiħ70Q'jUzdo.)rUI;IXMMq\._.jǍ70N8f"0Յ$'$*|)O_l|y'X Z}SlU$8o( x4L8`DIm3o+ D.cb)MF.ٱ!;+v캮eې#bEle %l*S H`R}M0 Z.\ O@86 RفspK]U@2Oxc=č>$ )̹sRrJEYd@@Pk_//ollK45*r||yhMBC=HߥsrΝ$ȹ#%E? 4QyPN{dL(JnnE,U@5Xl!FANI0>KsT"BIy1\vˠy^fgyYx~wqqqaa!QrSgD9Sw5q!7.5'Ҙ >TTe-DP bKƶRoue$e۶ F^:[3klM !t:gz2X7m- ;b*5#ӖO.ISZ+Ƽ^\K]ey}IqV<>>}H)eZ|u(CO JbELEQ666Jf$˃E܀QjG*`0|g?`.zhg$ -X@śDӴdR/\V5v :\TYو,;0lݒ)](ܝL&`>0,#eK%#өz[dM($RsIfj|wFq lb[*._lfC2G)ijeҥK@PiQ_ H t]Vztit2+I792OA&#;8|#<.TUUDඒ"?V510u]a߇A ڢ(z駷q[}۶2DnϦ΁#n۲eA 66E.)={{mH8;v6MQU[[NX8a~3./#52eiFddZ%Bcv L-g8d!3q}pae-3grR//7y?ԏ#'1OA@qs LKI9X X0f$mM%blT2BV;::?|ãŁNXt:tzU*8;aD(>pv3G>`f4jJ " "nwyy{zѱBˆW";fYVb/5 :b2$YnLp/FFiCwf*1B ߈r7BVCKӔpa핕5.XM?LS228A?u:nmm*O䧕8d@XEHNV٤&)*P>T*Q@yr^:C\PZmRB%.NNa)Z2}2G@nζ-5MǝN& \cS2 @ad;꘦t=d!yPAt'vɫ$<q9B{Y05(C6h-n#ZxTT8 ^x 8DP8\+_(!AR0 ,KO) @(r%z`\,'3T> (H9ַܽ` i4(PHm0~~]\\mv~Hig P Rד]\fխBbE J `sEG?z||AP8S HQI( s8ܶe ]֖x?5vZLW,Nە$ݵ*JT*჉ :QC0llyd65JHs/ HN*Ry&@< Rnϛ\wɷ  M!…{^ܜwՙN۶;2oky#QEI 7$Sƒ*P&5z<# 煤J@"Բ,l(ԋq˂aN&h ޥѲĜL&Rik:#7,2 ,N7Ld}}]u۶æۭxmw) |XA`ss^Xݵm`Ƚ0闒Г),8JFnY1AD EaGd8j0F4`x$|hD\Nmkoo hgEc:4| Tdəl`0rmmmmmrk*|uMU5rk)rBI@:ճr(pRqe͢+)\tfoLH)ijU/'N~ DF.@I#uei& za 5EQjȼ 86υG%b5:7%(x޽ ؅oUj/_,kkk;/~g؀Pɷ /,)۶!JUs@p0 ~>ƉY⮆“A>>>RP(d7;|:{]'drԱ$Ju}}3,ϙ}}8Ʋn(ݻwR٤G.XA!dRMFY@WVkBp;~yD ~?6Nȫęa\x03<;H-tHj!N"ve---u:|>/4L?q< ü) b.k|$~DvXT5PPUcM W8v0}0V+5$ C;rCM3T5B*J%r(8IryǦs9Cʅ>S#N8K8 p#D6|tUeJX5M3a~7BZEO*c}eE]~2+`p%Bׅa8qxXY,FQ 9]5WJgSUڌ%M̀,q]hڣ§cF.\mmmn;ێIS{$@0*HՔ*Ag8NiEQpOrjk}{s]ŋz)WR04M3ϣ>MҞiHHGGG2 a/zqFD0<;.]2M~\Y{'OڕqĊ z1q]eUNsk<8t [iZ`Eѽ{{ ep9n6cX(,n,W"I$N88EMX&1rßjJTg4Qlyj }td#(b [H߿[b H2FIX*rYG&o[JedP4&Lh]rX܊Heٶ MNCw .\n<,)EqZA &17$@X}`V9L n0۟@7Kq$IMӼ}*oDQsϵmȉ\.w5G>+'\XXS&ya1Od28Osk˨ivf~%#?hYډNdy3xB"NOl O24F&mCkT. ['|:7Aߺuqr,0,W5 O3S H0JZQAL M@uX,;O s)"Bn5mYj`1ݻ^ՄJ3)\jT_;F6h8rK"2e }#L@ymRdu(uݻ7WVW N5(TÛø ' 3CGhp S=Fr T{wf3&PHL&~z,k`4av:Buh #)A2"+Pp&;\_ʮo$DQ>iE>bA_<_Q%A @&aSQrd0b&-q"QE{{{Riii (Ml4K6l$th$ P L.,938(RN-/^'ki3B!gsǥR 8bq 6lx55QOd.RGGGIT5X#;rj8h@*ř~N((QxI̅dZ^s*۷ozb" X13_|"m&̌AoBloon[6Z[VkR(>|BPp"H@=cPӏ\z0Ó+Q+b@ʙ&*)c'x;ЈʬP5MlxO#ǣ3'IH,," \HV +?1D 8! m dLd+8پ{612v3K" MLܨ !&de#a)V' LTUղ,|K!wlu l(fʋ5X!8@Zvvv$ ޥ7G(asjz:x)Po[M!Qp ˲Pc*:YdJޏ&EIdLVЬZ9mG; # רT*@eB֊y[/) NZ4Uh=aAܿw/N8>@SW*FYRJB/~)G/Q}w qxw=T&M4;8O+<O&x4N&r"[.DN ul\7zѭp͔+"N{.TU-J tM[^Zzghm۟'766@E՜Bk05t|I]ף(u412(*@q%9@$Na"6l=s8 P(\p61"|.p ei1BJ Q0ce1ht||<ͩO~jssx|ƍ{WW#?AAׯs9]\.WJtK/YC\x'\YYYYY,u+mT eم菠)8># P&aɌ!t&/I?ifZ<)B}+kr ><[/N 20~_fkv0SdPY.158Fl&T@7 hjIRum|W^"/NOa= CUQ\gAC”8H繮5yUjFZm;,@^@$d",xw @.m"e@@G]wl-w܅  R/.TwooL~2(R*\1/ C<#ܺu ˗q ( EQ Pĉa.Bx2&+`0rG> a8`-`AEo;PISd=͛YTÃ,)mKyTy6P,--5JBBy$~Bȣdl;@ 13; J'45 +٧t:EѵkS93WLJkM4ܔ8sJ%3J\E1bјL&aC`oo.\x'[Ƣ1;007-{tE=anqI$Nn8JX4.-qG ,cxe@@v?L>!cJR/Q ~[oBD^6fpƍՕa빃7n!4UZ<uA9r]ҥK+++DL&^<w_zgF4߻woooګh˹sh46 駟nv۶vkfKRXhA5VX9DzpKZ T ,zx*a AYis4Ԡ^V qh.ȧ[b%5muNs…r$ 4m 2X(CgDU X1M``+zF20!kǒ/$$](sxf5cێm[$ǏP 4!&:6o5M;>>~!dH)`*;H܋ڛW|b?SǁD8I~e#YDQ^}ULɦyYTI\T1j@ ]JHzέzڞNtϏ5|Χ6_rT* Cp$uǣhwHʙb!4[n ˗.UuT.Q FUQl><:JD(BUrS6F#tG5n?q]۶N!!fsΕJ$I^z%EjR.@cL XNUӴzYTx`9M̺p4g,~lT)K%PCQO 88'xڵkԗsb"bVd ? #p 2яbV]~42W^=KYO~d4)0ɂzV[^^ I E#cM'4LaSEYd@@Y( ]aW&Aj#9 *:6lr u уb纖FqfPdz87:>ݽ{]!.EPxդGmY$^ԧ>- ,!d )?d(rAZ^܎]JH9eFd E#@!%Fukgꙥ1Fߒ^t:F}w3dra~fVcjp]}~LCa A$^XXi# Cdb7 A(॥%UU& oݺzi}j5yO<>ZƝ;woQ"יG*p?;^wm,+++>,JbJTcZKV.} @L\#+IhdYeuqEN9=#e<^Kdx~]\\\\\jđ_e ~bH6YZ&rMN;PD"a2OwZ|rH?>˗/A2b(˲j8 PTBQLFcuu_?::>WBro=YdEdŏL.qd4M3}rj( @.f2VBuu @u"h ϱifQ>3k~޽{,cXGDbZh4,V8f$kH((\)PTG9=:t9Xn8 !aµ4N6D"8[+lMFg4wvv<,I.$%T*V D8.5Rfj|t iv]T¹\gqxGwڂjOaY֝;w,ޯ>Zes <+[]]|r٬j # Zm-#OXP5<Xa5-$Ii@MӄFfaM,ׁ=8pk^3[VӁ]V& `YVEèTW*%e2)d[S| uss9]B!EQŲ6<_VVd2pp8|WիW/-m)/Q "'S* /|gww+\;w>c:#",Ȁ,!"t*c>uFd%$sBM(1N߾دzԩ#af.7tqnؙAz!as6;Je IAb G\iG~/ѨV]pŋ$ً4GI E +2ekny3rJ%'V<Lt2E_\h41;Nwwwe$I0Ihjv  aj[ {/P2/C~)0h4Y R@t_~e\^7J<l=Q4b0Z/,,,t: 3V $U]̱e?9>OɍDӴK.J% '3du"W_eTZYY\x<@0"4-V'DÈKt5,"9Ӝ<47u&$*"<&de`YlBk6E)h`Ғ5m]&>I~eeX4%\>EɄEQ\\XX(˝Ngyy.uL< ($= r%d6>+++aTIH}s1K},Ȁy`s@C>Ga8Q\i6W^]XXC],nCT# ũC-|.?^,xiشz.I-B!N3bC.T*\J !X2DO[ B 5@l.T3RYN'(J"h`k_ ZLӜLƾ*?l)7p|ygIm7?WU !Ĺs玏|M\FR`,(r]73 ,Ȁ,[nWUb0@RU|/E rm[vHbB\a޲},W*P*7q⴪9MhV@ N2$(ixgStPpK;Jpjsq1sINZ^W/b % 'apTdooo8mYAHG1) dH>hP}>nZ<ꪬ aLa1, !iNc"NPwYaL`a~WV8ȝp, ĶVVPFeZld1f2@G=j&"A eil@A `az~W\VE,v}ǀBQ(ժa`^i\jj f 0 MsMdEE?zq| MƇ?aEحZr %!W(nMI COhڟMR&Nۼ3c'H$ ::Tuxii STdBv:V.8, ZW6"|rbΛM̤pAoG[  5Pu1KE8r1,S9~@YV[0}deVlFG'V^=/ sYo%utZ,5MFBIy”;#k~$;78?"oH?=ټm,x<, ~Ydţ /Ege7PL)r'H*X>X%s?#N+pPT*Ft3 y:HvHJl|  `/N˲LB48&j 5pLJxP\@A`f$cZQWV" lիW]v4",Ȁ,ΎگC\ufO|ⓟa/=`[^nlm/O?DPzMvSLtLișvX7jfǁ :Nth_,ז)> i Qlcؒ)$Ҳ,J !&DA̬ju,/n;0GqVX,;UUUu%˩U D_hI$䫊WYKNpStqL11S#OĶm00fGt:}v汗E?~ܹsΝ;׮]g?n9Z+@IJYIU騙)R%Dd+9wOr4x2*n ZiYG@ӁbL8N&@{GBJ/ch4 `:5GaZ+yxmIGd y^|!˲,r'pNG ܿ]#Ť?Ŧ X 7X5 ϝ;GA!9_nIfEE?@a`o /6rZoY,'{уup$=MEzcSVLj?޲4,pɐ5SsʢJPBA^ŸnwP/NzB@8 n$BQζ®xөjW<7BYcsfʕ/5a 08 +3Emf<[{2!EE*OpW 4ZZ]R+]1LZ!rRSDI̘Y{9g_;Mв,]zRy{y>qUUs|?Nzq%i27 !8})*mw-ZP,`T*VNGnZ9>ō ٢PX0pq@#z+fX @&c'50 ʈ j 7_뗷Zv>e,"gi)Wo۶/H0{nAYKǘ9rSH_p~3IBDRfue=9 rȨ0N| mۍFCU5Ps^̘H? ʧ2"v BDǯD8륒QTa7 )oԌ9Pe0fN<V l'p17M& $BJ_z Fp M-ϓyCgd8\NT*AﳿSOZ-t,XSSdփ,O#N_Yq`r8/.t:̽J]dP^ $ 3r }D1$SI؅B6EYd@@Y<6²b LR>BvwwKRX[P(izyLr.H0)N R|!_OOgJ.QK`%ԇ@Bvl۫VH_mNȢ,N](91Ns\Ղ"T #mT*5Z,I Km"ąF~F#ug [n(ʕb)(rhTׁ0ƐJAP*&ɷm*d233,FB|_/=B|_!/$]Y)t gְVcڟ@5MeIWyC ZAg ڶ`Gp0QMd, e˭ZqI+ jZׁG r\eX;aqpv",2 ,<[V}gJk#PXۛ9 P/ƶ!<3 N6R$f%~~V-A ~\L1`@HƔąeqLӄ10q2+.-}^\X,˭V 8`$I*`<jekWYdYdƝ;wP+!cXu}4ٶ 0eDSq$"`~)+;N^uARt'2@w] MP %MӠ\*f$IJTXf|W<}<߫TJRLZfl5M]\\\^^^XXX^^) @:UQ>R@|9a')pXup8t]wyy9c(N&f$K< '!'F#7ZzdfY\hAnEF… \8 u!\׵,k0F#˲8xbpPrܝ1Ifdbp+PWl9/`lB9@ز7|󋋋3dIBzH*YVr}m` ϏFO 'b~#^CX>j0I՜NG?q ðMjRi=*YdEdcGGGeQzz꩝x q(D:Z|Bو>$'I`pppp2#ۥV e`DBdխb4AF&Rg _Pq>5 Btm>q"/2 (q} CLPl" #/0m{2x8N E.(P|Zg4bnJ%M&c*WrNVEmY8z(7MN"M$I"O@DUեjEf4FAB PY`T:LXWr8-trpOUSPb%S6N'fe;|aZi;q)yBnX88x,"qw6 *p`,reeZ5@Lju,GjZV*bζ ch* cV }?Ubf yՋŢy۶"mwSZ:8bz͡$`[ =w:yX $|.Pf`! WΝ;8~Je:tjn[\dEEx饗^z饟ԻJ’zil@)pP1?//-r$ C/H.hr\vff>'+ ˮ;t8 Mȉ`OJzʕK.I0 F?b`qRjfRQjl0l6v `0nw0L&O18믿ܹEݭjr9Iq^} "T@^D0ujA(-X,u,Vi>VBEQ] à\Z-ÀK_TB}+lSydŗJ%O V_AǼ ipeYV^|ȍ&,""XhO#q}YZ9Lbdbd$L ȜLUx+@ꀜ& +wX dCaFק\(B,,tVVVPc v.DEh ṉ0ɞI0:!l<;::,'qo=`: E(DUe[{%… O&P'cH {{tt Rl56(+ ˜aHNIJ,ekL _٭EYd@@YdӋ|> b kfD#&?'kl< ȖhꫯYHd%4@]l__#I: JliФ Mr*DuCw%z{^~ef[Yd~<tg}v:q\Ֆ:bu/X1&(ʍ758yM 2^xeɍҽ^t8NRh<4jel!s^8Z`Y 13F maak@ -,Ȁ,q__uhSdN -@‡s7kڕ+W CXi(Œ2r'q`H1a N!uVk63(!d@@"qA"&Ƀvvvn߾y^veE?A:_WҥKX = #PZ%$\.7Ml1U }Fo߲,갂/ $xWnȳNKt}ntjYV$B^sIfƗ"m}3qO 9˲o~][[K M",""R$Ib&Pӈ)˗// Q˥ҵk76bQ复|C| >e?/AdRkZz/T"plA|m+(cS8<<|{߿,7ܹs]xѶm46 hByyA`V )W:<mdkp_cc0f xX4):@V"Q( ĉZWYdEdE?7QЬ^ 守!rG$7Q7 FΝtR^O3 H rh? ݐQrѨVjhT*89) #>Jy*ZEqx}V6 EYݻW(._lf[!ؔ2 (5sag[ `m9@j,f)X^:x10FEH rRD[Tsp$2A6!tr E[B~vYdYd6__oF%i1EfÉt3?׾rZ… Z(u]7 C2G!@]קi'|8K8ׄIB!l!$j$D;IBB~MnEowQ*|^6eyɾ{$Y(b$D_[$,Yd>L<>>nەJ32f8>ș(駟><DgNa-AhrSr&< m-VX__<_(BUT]ǘC[ !Xm !$dYP#z_$#up֛|O"Ԅ_s?`o-]B?<ϋ' ",~qpp_Ւ$Ӓ<I#5xɝy4@u͜7~C w Ñ22 01SVHC =) tk ,"f߽{:žxbT*;{@ZT&Nsn%bN&\3Ov3,;(ٳjn4T^iYp8?踞kjBW5-t8x@7{:UN'u _6&oL&ё(j34erYr=w3sGt:0~`vm7Ƕ'$RcW_U.^|_rh:RI<|(άDdE]v|zYJ#}Bhx<FH@@/f-v; yxmT@,k8Nilb#8_x >?Ō,CP.áax1c۶۝jPx̎X qO~Y.8,^w}ϟ}H(BAhdB\Vբax{4gWV;Ձ}T<^pVabJP̀,1]ѱA2:LP} ! À> r4M0JRZ'- F ;2M4M󼵵g (+Fﺮmkkk|N$ s w`8>.2EA-α3;w_;mQ,z]4}q^ BFCiD% Um(}D#7صkk|Yd@@YdOǡtXBLSh,uh@A.Ng8N&b$I\/J^t:^xq}}]'ۑ kkk/^\\\ 3'Phh4{t:._tx^ y透ضM,ҷPP$Q 8BaTD~>8'[b/qeɀ,q 8d2 q^o6ZjL8={;H1HD RO$ܹsL#EdAkPh ( Pf>)ttt(7",~`8o,,,$IΝ;W,1d !Pc_(Yk8&  @4401@^c7 0 q8 @ :2&'ry$yj>ϔJ$I4]STEU8"`0UjO\dO,9+R.4{UUښtN<{4W(%!~_ǷtNy!_K?Y^1$B䇍͛R V(,Z BU?~hq%IpV]!p?HFd}ﺮ۶mY`08::L&mxA !+|Źm|~:)XL25 .t`)?UUrRT*zL MqAI\!9o#EYdŻiNoo6(ZZZrh8UZ:raOyZP\aԩŪ^*2(GÄrP(`v02*J`LHHTEUEU <"q]=^'=h?w (P}YQ~IK%Q* !&III_◒AQ7Ir+wxS+$JÛ̽BA^*ħQ5!>$d=&^|+I>7{$ċ$ߢq Zm~^ t|-I,'mxu*Cƍ_t7NB?>V]u(5E2IqϹ[}(IqaO0I${͖Ydﻮv~2嵵NS*@-yHܺwDz,4r\30i* eQh!mlgXjZ j?$$jS /3MAP,"6\5MS4`޴} h | vl4);P,+ |> 0. yD #N 7sm ObA$Տ}cD̎S ? ^'X,?t^9>h۷=j:Cs9c.@_k0!j Q$&Q*|~E!{!j⑭ڂ<(w!U}*+__SOENE$rԋ+(DŽBaZ(}_X(E> Y:'7)(Q$F#Q.Y,^yD(V>+Ϝ.SnB 0D͏tt$ůFMyP;]O!*'t/Iky_S($_ d2Yds[[[wEqpvMl4.\t:zVU*2{FyeY4"( ucR=YHL @jj5F JhzdyiPwvvKdEY__<EUUx<ƪ>5b>ie$sadC (˥R <| +G9~"cw$ #⼿GwO |3"%9Ӻz=,gɌ ̣'qYyo"{] /޿ "C67Aq0xQqIqf0efH.z=dZm){,#b3'Y95rz.+JZj0Q@} *֞繮8ޞmo֭[",⇨"d8+; 5i<,+D -4)<ٓjxMEq]SiB q㨏h~r -#o!TEUEU(fFz~y^" KzxYܺ%el|.ƯZg~YZ\|TANТ׺c+sdYd~hv1i(MƸ&-!58%-%^I2'S%R!b3He2)A r9h4* @XD1RtB$ocޔEYds;yG]*,kېD).#(Vs>Wr4e|TǮE(8*̑h @0?nO'sEiXӴ[n%Um.wϛv_ꪨO~^J I H{k<|_XJq~Xm>&Vl\>hΎXXmOB+pXR^c" Gɞ~ouK y/}mcwwϲWQxְc¶8HU!5)_h6Ž{'gu@ԽuqF.w٬ t˅¿LNt2 ,x #BIخ'Gɲ2*#}T*9#˹\0 JE0%Dm'*EB>&B\F%Srk 9Ohl[ m\jjIE0nHdch= 4Mscccss3 DQbH͛7M|6E9;|,&qݟ<+ZMT~9|ڤ_^{Qy?wE.'Eˉ7ݵk3ϤI:?͜j0G]AӾx. 䟛{뽞EH깜vNG)E>/:wxFCtD!r?jk4)ݛ orYi4D)S ~Y|?uK0Dt(g/v|_]u{~/ϣDѷW"h6a(q(_-,s9ݚ&]<Yd6&swwIPBOFG,DDV$ ,JZ"Kv|pZ]z?\ M(>nZq^tZ7,bPԇ P``Y,^174>O_r(PES8Ùmi{{{w}WS$?{:hk+Ũݫw?W"eeuUln*I;;|qyƲEW^\{%|Gz]LuEDoCs{ޟܺ%VWErRűm t]jJ%MQ,xJ%}.d"mKc)O.KiKfLbuUN ߤ#Ua8:Vb{_!U&eqQj_V0oP hZMBUE'w?vX2d2IOEY<^yh4j4asG-}T#5șAOOdA1`IԡMrq0H2h;qEBlmme(@YdO<Xqu7;)r9l emD}PQD+Nl8<Rwh^+ZM PG   A(ម{͍PrJJ?f"&֔FCWxQ$lD̳fS()&sSŢд:] =>/d di,Ki45|_(EzrQ$DTνɏy/ ER'‚Ta2MFǻiHE2 ,'߿3ϰ|=#ב>L'!'Z:nBx吟\2_f$I)],A%55؉cNY4GXf'YdE nMӄ@ X]Rl޻wu=H BJ3 P\@uO\]#5r>~l k6kkkw<')odw8888>>vZөj@p"6BLS(B 4Բ,uwww~~oc<4 D'=ИͽXJt:Tz\4Q,οCYU)UUrsr^xBU+ !߃iB>/e}bZG#.uesS;*gűw$O? P*˝|#򉹃7 IH1=2D40|իQQu]p dc"a.@@JquѽgƦ= {G;x+ 5#)d8am*)UM`t:,4M]ד$F~/Nk}YdEﯯfF^MsqeՊ6#UF1KKW^ؼrVSU… V X8-d8BΠT*a/îbd3PqƋ/ۖsm=b:ZM GE?qV?3ߝGCVu/ٶCi)?|NZ3\*}Qyya?~byYT?YXTƆ󆏪ϓH$xKtv?46t"Ly ":O~dWV)yM!%|q@Q(W(%[L,N 2F@Yd؆i7olۛ|zpѴM=QˌEsYl~i0/e?W|=@tǟMs'' @4 ٳdu}yr<\*P(C%;ZdadHrV`[-٢6GQ"@1BU{oΙg>edP 쏌{3O)Y[|BnG$HSm`?jek!4e6g?>wonrIN;ZB?:xwGiwVy_[K1!췸.2f."w?(J>`Tby.*0DijjA$I=w>OluݷF̙ r-4N&baZ tbx ?ApY˲&ښmۛ[[[XkF?QIX, $IͦyԭWzy8_4Қ_qFܜZ)d2v_z pC!f\3rxj z~0MbM9p8ϝ;OFQ|oˍ7vwYT}^&]nJ~5IN;"  m?5WɿTA{6kXEy'af1%Ηw?_JˬcɴiSa~SϽc 8~u36660.]jǡOH6I(DuiB2 Y"x< vww{^\Vh& s~e3rRS4MXu]I<2lffl>6"d9" x }^Hn S 3!I~U@^tT3߿wnݻ }+W0 c1櫫7cwil]O&mk3*x l ? ;dJdOL9'twS6,N_Ϯl*"@AAC~x˗e?ٳǗK24,P@S!G뺘5Ȥف`F@amooO&jJ,KTK}Y5/PAAAg/SkazyK5xA 0tݘB!Ũ5*4MǁMԦ4`sܦ;`` &Z__} V(L6/Xz2翛edTV laU*o&ɺDglxNKδ/umϿ\/~G?b"_X9'¯wxW*X^*,ދ{ss| 'hڷ>R">r@27Ʀ߷m~666:?Kj̛fNl61f64I :[9>bld!t;NgeeŜqv)^Ƌ/~Ƿ((((|#{?l6 ðmP(`}°v0;"? _uL; |'>q3h6 \qc9it]7&F׮]{oxt?9}˹_}1̲R `9/YfOWϺ10]+_2Y`ml˓wS]|Os??fg竏=6b~7 &^*F=ɯǮ]c1e2TX8 E(((|a|mmR8CHIy« F EF@zQ6qy{kr)"/ I0 ?;L&" $IZlh9Fs!2Ν;vrV<}0t:~u>k@z c0Cif3;gJ$IС?A|mT*lN!p~=Ms[PNUo:3=M{!I 'H,V,rםC< [Zb0>?p]’% ik6eqckxn,n˗}ϧO??瞯 Vɻ{n%yxfw9_\dkk^3}*"@AA=>?|e'j@Ya뺠 P6N "5lL&{{4-Txk5UAAA.JUJ0F~_zzuB.cl<^; l YXX`F#".ٔ$ /x0L&ɅMwJeӾj]HGL?9+I>}o6j~q68չddEu$, Y1`./Nן ܟ3MfoM }vcWaJ6]wxoZ*5>u71 5x<G?Jȑ#BAuL; ݡ@0M~AoYVǓ8vNs\.zG>R41iW\t钺: wSachi c̶mEh_O&vVEHPÇ/// xUF܌n 2 !^08t]c=of!(uw@Ӿwf̍;Z.?}>r-?cc_M ̓A20g w̶Yޫ]($_Q4- 0v5~78ZouK0` oY'(oGe'x說 νkkFM8"!*"@AAAa0<ϟuѣ<lV'Kg*㓤z2s'(h4ݍX,zMrBl7?BW^y(h2˲4MK4MqAv]^O&/^~mM?@hHل#s@U3\.10c!2?Zަ>9J?d+xQ-8fr-ߍd?9q+!;꽾B: k4~d%O{4=A `2clwwڵkZ{Ē*20 B$)1!y5cX@, `L&B7uE6lmm=̳>]o4|Gk4b_x7da{vM^0ԆF,L1<{W$q]4 ˴t]gia'B]׆х $f}s?dD-rKӴ/L&W4=ٚvZNJxM*yQB9MZ__c1MӜL&(C 9? `'g8" ,#υ/vޞ w'~SN-..bv<,˲,K'be4MM\__}4eh4c(bmllDQ<>0JԶ^ /|u}3W*+Ͽ~}asD4eu\N--+kڅS 58gۿGMï ;6v}w~Kܦ1rx%Ιa+o?aم mi*"@AAAa8~9q[$psǙ0qh4M;p8D- C(MSei4MӫW•ZAAAAo.^I@EeYh_β5<R>h @!xi&8LhGamɚ; x]vKMcN?-xs8|~w׿MfY|uiߌ MWef7-goM+eLޥ-`^<Bw}Mes_oM6N+sQM;;4ݘ';!93Ǣs;3 0d:cw~ޯ{I׿;[GuV\sx?~3O QD»^uX*BQՇj2eDvak;;;xWh4 x!: 8V~ ^{R,,a4x QT`0v4q1('}m!CfGx:8{,~8p@Vu}aaaaab8ĜW^ɲ,7nųgllΜ2 !OY*0tQ`goo4pzf8.i:bC#iZV+˓Y ^T_u4Ph"3b\;&'{2?p@Z<>hm^(05|}])LӘiB `9a9_ZcLיR x݀amݼFYƒauN < ǝ˗Dȃ 1%w5m``9ΔJPyof9igםxVJB;p>+y<tiھMӛ}Qi4[{u^.,{+wI\tIӴ4M!EGAAAAN㏻{}%I!1u]`2P@ikhnaia۶iaaAA0J@!G!ϋH foG[$mPs23ls?ѡCk/1>0IﰕfYoO'?-m'y޿{Ilv>NUPPP4M|M9DSA'u4m4%IA׮]c,Kr ?,1qj;4';Ϩ6!Wt}9]*f7,ܶp|x^KwS"Q0Mg<_!㘩/w>O$>ϝg7`z1QAA ?+aN툀 s.oɟWU*|i/,("@A "Y?x?j4>UoJavsW{62[_grĝ">t4V*߽v=+?zj+Buq_嗧:Xyo#8g 8{=10dIҔ 14  VcVY PPPPPPPPPPPPq*^(zae{f-9s)XE(((X\\FH aiih$I]ՙQ0s&+y$aI² 8g>1M^Q PPPkv!4,[YYۿ`ΌZ isyW^?6WWVͦ8O^ZZu4$Iٳg/]{ u>tcS cLbLe PPPZnll| _ Rt…,._N(gΜY__w]qMcBx/={ƍwF\h?O8x\.۶m뺮GQK|ׯ 'T毠yO|8M\ZZVKKKzo~ﲼS(\B0Pu}2 CuCR|ee\.JR(4MK$I,666~g}wB}رcǏ/ˎ㸮9ϲ,20t]/JI,..>|gy._"~JT*Frh4|GiR<{8{ajn4=~aNzn6jPGqϸf֍7^}(zd :tGi6ZVU*l۶,0 !DQah<~ҥ~oXU.}ߟL&i2ƄgY&BDQij޻z7եQPPPPD9 >ij|Gm//>Ν;wQ5u0 04M]5MSӴ,ˢ(ʲu,NeA0 |B(L)\E $ՠJ)P8y`0wOY✫XSAA]ÇAj624b8ybѲ,۶I1eQ>麮3з%x=˿˫Wһɓ'Z @ "3X0VU* hZ8pAǯ3u^EѣGyx!~;AvX PPPP~xyyyee~2iUERiyyyqq^ !]mxkJ{gY8JM^x_|l6ͅ!GI~m2Q@躎u]9ǎ}=MӼ{N:v𫠠zz^*1MY)IƲOZPnih\~5By8cիW;{{T*4XMӄ rؾ)rr0^y .:thuu(_g|(mJO6(:44Z۶Ɓlz9Qrӟiiw{ M w-ޟoۖeAO":=XuaTx=YhPCeY˗':e8_,XLA X\[[[^^z^Bb1۶:T.F~/ommCAAA Ʌ0Μ9S.9R.Mӄb)۶p86E٭}7ݪfc (12@ "Ja@Zax^w J|3v{<m؋/"onnokkkO@TU%HԱ `a &g#s mMuqo&IZ@5 #i1DX, !@[ia< 5`x`޸~h4zG,߷DZiR) SN> jr˲VVW۩T*MA{[X__衇Zl5^#gݴ,BEIˎriZzP/JXӧ1@I۶=8jZ^}n.6Muހ<4rRkg:u l_zhVAAAA ?T*B``1}n{СFQW9Y挩7g_q.ǯlfC NcժmYO?o8O"XD{Bdv.bu)(( F8΁N8*ʱ èT*/bo[朣-eN=%r͐s؜v rc ? ;vt: CcB4WWW>j*/h4Ο?$I\y l&| Mdk1-Q.eZR:t詧:bggGݨ PPP`ssɓzZڶʈx(fB8ST2QB}x<0ضM U P76jVzª Q(taPxǮ_,:#/Z\׭q?mou;ETj6بj$w]QBim{W.mێ~VWW~7n`SeFGgɓ \׹|ʥKt]L&TPPxk,..{'NT*#I. 0`l=À9+84 C7-S+Bdif3XH /9.4Yl2 rxEp4< lZ5˶4-0e---]pyc^e\[˲%9 eھŋUPPPDgΜ(J)!>Ops<e@Z9} W^0 ٬3Lx<,R4MsLi;¼5MD.`<ctQ*If|;9@4  @b9L&$Me4ȃ bu/t ?hSp1xhzX <˲(zO1M-L[L4M2M~B0\<иtfY؜d &#i/9wJI׸ie 8g &8mSk& l6:OEEq\1 Iq_{`0T7"jlnn:t;/$Hŋ ,<9 NR( 0 FZ<I$/[X.ʤ8& T*׮^~ g>ǻx8>裏> ÀO9;A 4Mq΍ 8a'> @@_(\Ah*h<裵ZVR b:UQĎfPuu:{ի8t0 b e5\dl3 eg#(+P >ZI# Ba:e !gaɔEt3e4-۶a1a\P n0D%+Y }ce\H$MVuĉB PPP{^P4ʚ|9N.Y\.Z-۶$,c۶d2A8vB" ] ˲Ǐ\.C׊Pvc@vʕr?пBaees5̏y7>ڈR3g~3NS)I1Mt]7t]u{ O%;=f3e)/7X^h41@><"an$Xဋ53 KAAA ? PNq2'?YT|߇_4D= Qn1lϲ,2фIeYQԒ` HT*u~2*Qkp;v7|?Z嫠ۇ>p@Xf k!4MLT!_,@˲5sJضvfrr.YM_q#ҹ9S) 2@j\8\p:4t y J^zҥK'N CcB,fd 7\8(UZAAA ?R'NZMӴ/|…ylVvM nT<r]Ƕ,S0fYB۵m;C˶5P8nNWG:[o-_֒F>HBpر$I]NPu8,rСCeYyzwA@2$ neia )s7M)!֟{wsslbkZ-˕J|"ȑW^ygE(ގsD Me3'Іn$)gPp!%xamնvbWP@tM)C(ICdP;B m I\"şB+k4MZ-0Y@&/OAqs=cɝ  躾j"$j3D  4i|J0ƴ,LL˗y ͍MxdccR@[42}BPʙ`15|fYcU'FӴ ^FxKtΜsXAH6kda< .5M0aV{-W^ z^W*-5KIJ I崙n75N/T% C 8J`aEE/#?ޞ7`І :U(J??g&2fh4`gJ55MuMu_J%Y&"i%kիWK^^e4/_,e[!H$cƧ#8$ʦ95$tSO\-J,䱧̀cUZ58eo9R.BLstLpx&%p3̞.=8-IAEL D_[XDW]0FJ —3g,--jR$:c JѕS'O=vZ~{C|3ɷ q+d21M3 é7"t]G^p>0L /˖e\ E$ Rҷ逜_(X1˲uye CYAXDW-HIYv+O,6kbYA _MӖ>OZ_|G?xQџ$"O(fqش*K3")r7hZ6%al7+Az/>,--zׯ{%ZYƷD2Vہ3V D 7Q,?{オZZ @ 0~*1=_l:C ڜ8F4ͨuݘu յZO|f+5n|b<21f=Liʱ)G' e=h4Cfsb ȔT5>5s?__8C ,//Zf !-_]utLFL$"M\( Xp\3 X,b}^<ϟL&sӃtͱKYdHTd1 %7Zr}L#,'?SN[*2?rfj7Mۿ]զ3;d#x1?jLZo4}Zcq0-Rg8$(E<9RNr;xZ ݳ(v$_@qGPh42M4O[(U$!"<- 4ZR~4g}^pWZ=zt}}}ii^U*f.t aA<&lJ R$IhPMC2k0dz`-[dLE8 =˲tOUj<pZI" )zˉIG]igH5F?b28LpE,^o<,~aO2>Z?P< (Li$Z!Pɓ']-˥R ȊYsgeee2L&uǡ@2U9ai I$iif8c)e18ϲT6"4E,>H.9%@ٜ sVr/GWaEh+Li$ I!MK8zeZEe.Y__G|y(W t:;BV;xf\n6vX,bѻӧbsWْ>HUqc"m\{ZΝA|ߧr%`{4pA/6L:ǏZ-ЧqlƁ[V ମFpJYsrNSfLV(YV.oi dm۞yW(p"@g U-/j>ݜPP *$GR,7!P*0n^u=#4L5-յ8'A fe麦A)(brԈ .9j?9$U3g:DUh Eh4,d&▬~\SIJS 1 q,@ax0677: /0{ ˲* #D{766Fh6*l{<8R9c\d SiyExȦ4 (@ڶM.fYV(kp8׀60-9"LLJYO4a1uB$37l}AՕ,csDUM,"j&fq~x0CpA,7Y`ggg4FӧO/,,VU< " MYiZ08~d4xv`iZ^/)m9<7j=13FZT*R s=p7(A@DqPPmB`y 1QUT*aTrz߇,}'"@Q(2B7kzM&^6×ݤP":c0 9Q'l9d+PeelEex?:4@%]1Dy;.G>. M:p@YgåW,MSt]wmmm4]|Yv]:γ>:n.nRj"X*y Vn/..b+\ӄHْ5GJ{1d<qEu"Xac>_zpGe;hšə<[ȍJ!>; iVy4!nFHs$Vr]dAxIAZY ׯommurj87 C_jM[,Z z=TAGҽ)˚V1M2Mc{{E!6KIJ˲ geO*iRq˗/#jj{ܹג4Z >~&D\ƗqfFfYaJ,]cx`RV$X<\V<[>M0K djg(yid3Ph.#?9`KUz,_#?mfsCgU-$zBB? ,~C4^khT.W*JX qn9aEQ4=aeYRJ_xKr x7Bp^o4 ILe+ YVsD)wYVj\VD]hِoIy((|p}}S5HŔ]S 5ɵ&f VTN1 L}]sxfWD48,QDU*U!?G;LG}s\/ ȳy/ea%roӹTt2HȞS&*o6YzɡL„BqTB¢㸯d2AMLAA)>cǎM&uiiryqqn7 嚦a Exd'sRk 8h4 .V5 # C˲0m`nۖ^y뺐кMșozO2A̷% !(ЖRq|:n'dC& r>( F#c!5У$"Rxaig4z=uACI=! ~ ڨuØL&s49fyrJP,I²M&̶,7(ax *"@.䵋[)"@A#R?@n uDpRНSN-E*je_DR]sr>Ipm?h6c͔!?&Rȵ4M CG3xL3@H!șzBtr~%".YJބ{Ge;db//8h'&-2;#)4/...//u9Vr4 L:zә4ommF#8JbѶ, T5^F|Sc*zI<σ~#\2TPJWvrGeSV @_g<"B$PHIaI0h4`W`c ӷ*lEXL@Y<&y8A %-9}5nz"o` $m?H$3&<ʙ;MiӈZ\>Qx  av}qqqmmmuu\.y8IRM@-{MJd2looI6uV+I7n@ 6LpFô0>O3~ajE((|j' f"QʟU\RPL`a^Iv} l ( Yei&2Am;E|y,'ϻ")gRA1nVZ~ C3EQX,\1fTEdp5?c!iZ#U5{Eh4Lȋ3^.eYm#|G0<i޼ĸ.d[*Wf#y;;;ϟG)|QVw~رc㐛MbL yư&S1@&r!LT74Y "y,dA*"yƪl@x܄1vh()c}r{%֘hy񇖊O&)9OY& YiՒ?:rXE{Aڐe#h4B^eY7zp%n: s?|{ J_|9}s?? s<l;*ZlfTy㸐Sy_gY  Vbb8M"`P#5ĝdŅ>ŋ(Vpo?JjO:׹<2 xD/~i|! ; 8J(,:彥]=qeY8-i\rʕWnmm;wnwwW}7>:hۿ[U.>N[hƘ ! ]&==D"{]8' 49Q:Y l(#LE3\@O(6Bml?7}`3!%4PIB}:}p;.-coq9XXlJ(dYt@. XXX#m۴$0vX˲4 kf|!D&d{<L^sٺ3b2jY3uǚFэ79MrGNi0LS2\ExXRIĄ4pol fpeh9;}9&)NZJ%vO-0ǕJŶ,MC/~v\ƉBM˼}Cb>FQ]A /lƙ3g666PX,"~:wҽ{aҩR*pQYd25DgNHRu5 (eg& BTʲ18cR27Rn0*bjI%*?IJʮݽq+ f熯~P M.6N5*6z=4`qEh4-t)N'-"MK4MRbC qZ%3p>?so*|ǎJ$drOM4- YeiF-}sLcH,B(mq rۖa\jȲ4Ih*!9ꑁ+,wKM`v妬`%yB*t@eYtB%C,B K|x< {{{1qI$/,[0^"x2@F7҅3R>aH afk:6tIJR,ā:TTbRY[[u}ii VL+BÇudčTFL>"%B0d̤z!x<ݭb\撝YJ:dgdMQuNR@9?O"E=YFWڶ][_I>M7yη i&Ls(Fa < ˲$M(,i%TPqb3GFK 8NDw44 .!}Q7͵m[BL (iV,ko 2,//:ujyyȺ)Y%_ToX42!u=Ms!vȚ8'<0 Rh4,Zbc@ ;ѩ2H^$ᅵ_*N)ML'Vl-wCOwr-r54e2&f0 iJ>6UwBP(2}ѯG ^ (xɍiԲSgzFkpnRc/r PPĉрPӴʡa^o0Bnyc HL3.1i Y̎JZ Yӑ`Q CPZUE/LR(5*\*R0\`%@!6c }$% @Ql.ό1۶wvv&IZ,GHzµ1M4e&"0lYvTz6PÙIP~ʙ;B~ qpei"lˊ,VUח^}՗^zI}>X\\lۖe9rĉi969.4Mü,K3l DZhf ecF#0Luf8b!m; @E˲dkvF Ly;q$g.ҳ#&I„Sb27Q& Zd@.25, ^ja83EB.:r\,4$Ds .]vH*BBJp;.3x9GqbZE Iض},ϟ}_}pWCӴjZ-Cy777:׸-&I  \s!ET77kZ[ YqRL1h4SBNuYVJao@1-Ĭ;2ޛ uc doo} ~K\ -Lk׮ 'BEHn$5 0KSaLj%ׄQu" ˃ pp$i,kaXJ,C7 1YiZEhHT=~>{իWZm۾{8PVm"0)s&3v.WR+geBYCfD@2۱WH%0CCvO,8Dȇ@m\9PoZf|YZ嬞16L֘/iLuPjzjzIs:E((0Msaaf9Ѱ],0/j4|X^hz &2PǗlLJ*vG 4wVs9`ɲm{8шLx"4#v<cD/$ d/+KFQ X xBa /nJ| -vzmqE!O6Kwa-,,gMI8N0Hg=Kb0cKp8LhNh43 `B,ӵi];t?⧩RJFDs䟔C".˗A(`4ij~% z5y{lCL+ PA[uOT-h4 ~TmonnZ-ᣚ&d@l M/L $%AXz }GQE1ҕ`aal񄺮2':jZ8vF(TFQ8G]]]}WxW^yEYX+|Pp=,..[.*l8 "i04kmg_՛y]N#kpnEܜlܩLp>U 9if;ͪHT'1jz%/\rĞct4D$5>H\K|qM CK,AXGǡe ;tEt$r#<kd2;;;n7y9g& P$ni6$ЬƘ:FHs0TQ PP˾QV( ܀!>L&^1y(FV'wY$4|D(fH5g9DNMtMCI6fLw#>aCe ɶ{9܈{"`Y/ RJ A얇v`Fyr5"fhP4-ׯ_zC?oСC0Շbԩ$ ʂ[jN&+m A\ڢYa~$},fg6keUKfsmm[օ hpbiiimm WPT*zu]Z$MdJLEI&YOlU:IR Ȃs|7Qdž %e5}+2-/ps$ɾр}j4pg2VЉA,"(q' C-]| X'' A +|V BV;z( Y@BhzBz^IT&?, ^I&Y~3 cu<"(-fZ|=(5E((m,q?CB8ϟ?9'O 3jpҮQLRS,s%h9BLCmfŐXd4}&ϕ!U:TА@D4H/ι&|"Sٶy2lO&d2 ȌHl3(BK|o4a:tP(dDGA wIt@;,e $+PeXP4z8(,sV!Wl3>)4M8\2'w\ײmsv͝ۿ}s w!VVV~~ieepy4-)->d2"'93zpE ʽExdDcig_k>x@[TJܠphi4W*0j^cLlW ǶmvH@d2) r=įdcƥAL0 qLӰ``m[*<{衇}pX,}sa3<[&)5;JAgM677?Obj6jP(8ŊDeʊٯd#r Ȅ)cJToZSt]m˶4|7 òm̞,ϭ9>d^uR=NX`U=k4 EddRȆI5KjӠ/-. RP(ámt, &Hi%/ i> |G<5$KZ⠉oN sa@:A@S34Kyi\uu/˜gy"pKR@DZeYzUCvNPKR,ϵ9@9?8R.S;%d2β>fI:H/n/0JRXԡN)NE8q)fRBGݡ\nhtQo*QEY& ;v^z T! r^d)ma^2J'.˂Uy| "tTXA]׃ h';Jxڵ`΂y]8+Rqh(%n&9C\@PUP(,,,' ^$ и(8P*^B|ϋʕ+J#{ Ԇ' bs]WVjۿI<+{/TY-zJ## K.f'Rd*PΪVx1&9QO峁n;+J'[[[IWUy@H R,KŴ[Ѵ<H+M(4TALaP4-[d@9M #k1+|ƈK¿s^L,JM>J(sOٰ,37 Pp1rk-).@g9sv]h "WD<eIxJsP"a?6++sy"ٶE),N +++ȖiQ"d.6t\=8R'5ZlF#̝r+hᇒ;4g ~?a\;ry}}\.#'G >)8pJq[KРV67?(zҥKϟnkZRALG. nDBu++((("@AB\^[[+JOS&7}Ԛ(+`o/ %H$dTEI1ymzk׮~"2uA>m_AsSA聤|r*(F{ccǕ#Jdk Pc750Y d <2I@NT]?pYtI_vDRȖ.ES4SӧOuXOW2g:gav&2_g喢\rɅ99xp@yeYSQW3BBNtZ;пa eahѦ='STZ^^F׮]D&Yʙ̱N Z-j0$Ib w gO ?>Hi2 D/8S,G fk)hQi6f,)\[n)Dc~GMZ:!.e'Y+sI)Á #Fp&'*a6< 9@NO{կ42%$Kvd(_h: 0o e%n۶ @& c M dFF& lO[$tm1& ,  @- +bT:|5#̓ɳrSQHtC{|H;I@y?J)9$˜mbexdY%4tz4MǓDz(z^ٴ, fmRXEB'T*7'par˗~EVZoXT^| J+ǣ0 $A߿` ]Qh^ ÐsACtJkGCMuW^L&c8% @E( n4MMH QZ-JPEFTV2# #iqq&`醮qGTMo*.@ժ\XXjō41(P(ˤq!]'w}sЂC_՛f4H^'1G6d2' KQC$Qea` a+Å0ib|̏?jT*`N(Jhe_n1z>P:w0׺f syA% /q$GjD̹JMv01ȣO&<\*  6Kb C i"!~׮/..nnn8pw)<?yZWYO]o7` uc/<箠IOmStExqu_zA=7-X@>1A*@ #iA}Xl4iv:Rg-8w{{n7M$D(#Edv%Ss!M~Xa*&L&M g{,@G-Dߎ!_z/e>z&"3"w)2A\F!*7- BE!nH2|h\ c+dTYIǖO1Ms\T?=2(K>|0FfV.! Cl* ̨iWGJ~y0*-Y奜Hݼ6J.} 3BE&5NB~by'ds@K4zhYUz䡆p Ī8,hqZ%y(,Cv;^iZ8T*q0J% 8Ӊ$ 2eYeqA@6\<ѣGk*l'DۧP)ǓhzBhFbHZ, 8NEɥ~YQ0^,6 /F8X*^Lj`YlYO/i<ѻ 1 O9ⴠbFQ&Q3äP>2@P}/7RN]\꺮:c\r {$I8t%h"l4fYpP=@)`s ôlieA|$'?|peeٳ/_VVZJѠvZH ɵq6wKr54ql6̱\PX08@_(a8Q%; Cٜ9jz=¥$̮m 012Jl/!'Vt@Sޕb*]HgnH4KS6[xKeX,*Ilmmz=JgsX`i6N&(Z.{BZA}C 4MlE1ZJYriNJnME9 j m 4ށN:EK/?q"/4v=L h4B!0 CmL& J{!iVVzۡcVy7(o'.o rY& ˮ84 01:C/M;Ga LGN!F U/<# aAX DZl@t U J`O$e,fL0LòMK440i 5#<$n7nCvXZZGfY2-uB0<۶e"MSkB7T5g |Qr.G%`oOĕfeDܕSH[b r&)7[Df(P 6O+JDi_ddP&7'4ރӱCCeMt8.ya\~}4qbѮx}XdaKF^bE/4M86M ǢiZ'"-vx}}}}}: gI:7pssMtZ4#u@2gvK[N+++ׯ_/˥RiqqZ_`0P "/>|{ V`0t:ׯ_! p݆1X0jۄRʤBJn||p[ HI- [Ls 11Ps/:"4 ʃe~y|x8wU-kkG)Jzh`4vb1h&24-L@Y-g9;{'ͳ9?*ʾwԞ z,hg[NL2k P6va>0!d,X> ds?d$;{DjX:h9%2Is.D^h8EeygFvB k8L(=mZ-{4 v0x5M482]τNl)hۇ<_eo*.@AAA ƈ{<(vP!\XX8|mP;SVHQvnX8{eL'WS+%D0"@7u]a Z(Q"I1d٫VSę\l*jQ*y Z.T5^#v-a(!tDЎRM5r#F |?ISr:X2**{ٜ9B@yė<0MS)#cX, @2D ќss877m iC\FLYo4Z=F~:dYV\|Ct]Qs\i7Pe,a< @)q,+ƣYVouSΌ(䍠YQ?'̇Q0t:`ݐ?ri|='G{(K {Nds8(36pDQ%I,㜧q<̶ЦAF#2QM%Fʍ 8!^w<m:VNFcgTO7 8>z(z/L ,Y,K;uCc"Ӆ &D33$E.eY?>˲j* /}ɲZocumuqq"b<>Mx ,JTJ}4jlF#D!z}WV^i )LN6~dmjRd0'RzqI֬.ne9 {:yΤҊLaP]Z%)9k4ENʾ@ ܔl}?1IASPpc3Kpb4䪔6 dbbjobcI'ItIbVd  YKD$D-18cdfв-۶BX,JZ<#ͻxO<9\C# y\jMJ㺮sM3t#RƘ.t8cL$eL6]'A6ufubf@olJ9q Z7$K3&nsh%=3T*axEjӼL04#; BnIj&}}/{9$oLއ`g!2m?lJD,3I^l&*n>IUYFSD[N4S]Ǟs@& Yx%:h姫'xXlqٶE۲tà0SŸtOdJΌIZ-$NҬ3e; C]x <٬[M?t)*(((("@A'g*J۝L&?ϴmŐBP Bzdn, u^eӧrE`4bh4:{p .Y$ 2"eQj5P&JdG 0˦ efkF0٠/Dbd$H߱)u)g3!(ޅ-_qTT'" QK4Qm-G 4el} mr3ʽN~Xc+ىIuIs=y޿B PP aYglۺ_z5 fuT|y!@*qE%ߟA`|!' EQh,r[{N' v&H{B:| iv$Q?rNNy/bb'X=S@,7u]Oo r@2_˲sMTKLsNg@ZCǘw !APC 89،32krfۅs8׹ƘnۖW!@S}ꩧ\# yrѣo:-\YZ 2{|A|@:sn)#SJ^ b jLJDl,Zc$?6̹&˔kBdilβ,#,1:{AL=/7Zj1wq f~1H بT*dH}^X?INE\/E;ri n^ek99?=qvvv}Yߟ|c`ɓ'?7^WAAAA w 49nFǞ筮~s@CT+Ivw á8硏sFA~LZEc)pd]tI}c)*'c0@˲ CGwƍe˲K4[DfLTF(J$ۣ /#\ȜI5R="m(35J1;Ĩ9Q<!\?7E~pY@P(pqiCEeQ=J~KW"KZRЀN Ƙ\N&gBM7L#1d2 ٳBӴfYAD&RjFM4IgȉhODqR SP>In9lnX3w(hš,6/Iz,˗?Z^&ҲCS2/ 7&PNP$a$.KRe$`Ku]c@sI=' }ԀNXKKiRi2\v ޽4=AC,32۶חdy25/?YB ,o>#tsCn:yt}nðP(aH;?_+.@AAA =z͏F?bDEPN%d˲@ЎADRסMd(;8;O2O&sLàX$ \߻],K9gpNE۶9fM1FctCQ|d,GϹhIVPIn~E M/ItJ٘`#Mcsf0m5[M ds`̟GybOd3d0ڇ ieT#.&?w *s tt >4]ih;r&ͼp\Nɍ닋` p\qQt`YR]XXJu &/ҝ3V[A8|spH7;s񏯯cBTEэ7vvv& O*CSv(;ދ2Ž(|jxٴ_Ԩ+b4W s?ݧZMDJ^N8hӬseYEtr%eӀzL c i\7z8!CG;F0YE)ƙ^ ]\(EbYlM0#۶$޲u"S?7$ kB` 8c3mMM|ߟLƃWFVA_yƇ/MBd"BpۙsИoA.Dȫ%}yQ򤾑<0q<oꢗVo:Sɚ>t:#G3M& C{sLB :DρwWs:(L:L&8 *1%B5 u8~ea;O-%q<^y>" ĹBիW+JX{X,iZy r8$#NJ-.'&ͪӄ5 l"rVצTu4oLāʕ+ds?S<Vp]#_.W^Y_8śP<#h>zl6_ *hx0E1|:A纮lGl`0@6 ͦ-MFx fۜvX(!nܸ1L:By" 2F3 B2]vGQ$nd"m%+yݻ2ƹB\0!yƋS=&6VZ+& hO8h4qfT!ƺf^oXeeYiqQ10%%VlX'!uZB95 8)sٶ(jTb,+8ĔX#:j6IR@dǙC"-&XS Y1zι$@3 94I8'1c,cԭ'IZ(X (GD_Vqvwwbx<BKu':'4;TC#w]rȥ:DBC78$ ,M{Sgkr C\l7n<˕׆Aөen@Cbq2F( `Ln[-JrMYL6'sl6*;R,.: 6iqvK@sRMl<4sJ j<БtɬYMbq=$%X[#Kn*gn4^Μ^;h42,4EOrdl奐#v]T#R/$%ˑAa z&w\./,,4 Lz>*ʕ+W '0 9i":P9/(J^}tvRl ;Ulp\?IӌS1! peeZ⼁V *G"C+R12^̘7*P,q'iG4M7͛ɢ <”^lXˁӭ!^)OpDj1 %R!GޛȒ]'y{Mnaπ$XOM >IC IS;ԘsfdF=UNdmʲY]]És^{ TT&:F&ET);l{www6MSzH3Y|NTTUٳ.|[(Z>"\.ł 4+8 \:pKhq6& $4M}'kF pQ-j(dڶ. ]ūG[Tכ=grρR6[AZ~0q\Tܹ$ɳgϢ(*J8V8umAprrR<NXb: p0j g"r"{i[ |> D'LLr\+8v\ױmᮐ$iB8I6 cX@SRߓ'OXrQ(F1qmT*7|ѣGF //޿\."L&'''''' GJ k])"\rZe8l(=_-6 ;PY.g $NЇ~]du0.CL2(B@*`ڴfLk#LP 6LٜDn^,IIDx<&/,2+RJP)V(|'orN4p\V`KwTB'8iRH*yzbnV:3p GòWTa`A`Vt:G 9b8;;[,ro45Vm޶vr=MtK+IyA hf )4VK 7km f3`|o7< 9% H$IdV L&ł84 (@l(ڲQ(F1Ɲ;Ǐ?A:<<{0(...^x`>S %D`ZѼ Ҩu#|z?q-ɜ23VYs@("covX YkR!iRdT{ٶ8N%uW*%%&2(eFFqe'|ǖezh4,*j ,40\O@p |^fnIifYR =ynA0ϽXtPJO)JXfJt> 0<d\f"˪)'2%7+3dLWJJNRF;;;N\b-tnl6߿=*j{{{w܁0HK )* A4sN~fl:j)492|x~~)rbPb8PjܓC+ZNS|Fc L&}ire"uR3*QP`rC`Y Gȥ X=U_&DGDڳZ+ٖ5Za6[4ISYWdW* ;KN&4M4y~R(EdfKmB_E'%D怟`` UDiiIXZ ),YRN&QPFnyJ4F? ;Vŷa\Hy4n\3`,۲$IL446im%/rϖe!S4@B&08J%_xux|RyL;BxH4B_QM :Ilxڲ,t\.WA-۶.\"۷$Id8IF^T9a6\өHIGQ/i 2eiŽJS۲qRk2͞&$[Q L8v.l+^_ VZ\(! |^u2`|h^ZF9hHUtRv,lKJph `Ygp= ضJ  ^lj@*a(8:$eYx<ͦP=$$1 ӭ(F>=zT.޽tj5Ǜ 3x\M1-H)3˥[*jZn4ؼ H9#,k<q\.IpYadYI~>#E":QHY֝$j5b ӼdBz,|)uěTs9}lu.#SZ6FMjnj54!aZ2)$IzE:Y.rcj RޜɒʌWzsD$"B<DC\Lm'iiZʈ4Qmdi8?GoZ,rbx</ܖzQ2Ni̟OPtCŞc\#+l\CuYXo?K8Dwj8xGXZ;2%۶]փ=햅6kI_kۖ4yD67)qTP٘ıJSpEZ [)4MRCqt:Hq{XGh^{R>\ǩ+ӧ=yq69d88ܰ4rK>2RRˬXM8뺝nj$ PM\.7tJ<ЎffR{`qdU5N_| %Qzx)Phu]Yyf.-4J\.vVC'p8Q 0 #5MZE d7?44YD>[iUQvP׀8Fb2VlL2*Q`:ZmggLbaf|Or+VHe6*!h:XApv,) #@,1h IFPk۶JS$MMϨm츮?99)Ooփ,ˎbᇘpJv8PʄXe1p$@'152aX,IӤx^ZPĞ>ig|+]EJQoxg u@ DZ$$IR y#eWt*scjIX؜vyqVՎ#W0\+laUR;Z6SE ϚzN]|oz7o |T۶(NhT*z=h\. 7Y#BJ{GzIB c\cA0 /..0u F%I1#%68>"j\[SzmVٳg{6t:"pT~~6?cPb|VeY`n.ZXƇ&zjկEQ )M-_Mu0 A}@dH=o#nCp'MHI: EJ w(۶Q!3L0 (F`/(jmoxӭ._DZx}Gx$Qⷵd4M{z=*MONOGa$QY%yiFQL%$po( OT*U*9q㻲dKiض̪p({XzX6'$.@-)LB%%鍀mIatUJ6m#I0%O`./Y}2aZݻߗFqO&hKfGK$'.hȫ%E]Vh#ZhFxow^^\!r)ҕi6m?t:cY2$\ 3k&>h'sZyBʈ7#@Ͽ߈ q20A#ЫUKV!Yo# >Yh$@VVz0pn[)eٶy?XJ.W*Z=HLd_ Q+!7vD 8sK`0 +6La* K`SC\P(Q(F1>+wl>jrjB_ ߷m`ooV*Z5A"&rD %:ZaR l* 4Ue+HuV6I4K%g:[-(u^;GrEW_W,p;Ke@}#Netuz^.Ħ 4k?#$4B4d }JX)V4(z\EQY?FF4@X,jGJIhg-Ab9նbZп "$k6 hGo#/ |m㱍Tż@qjZ*`B)u>*֮Oh4oagJϥmRxBj Ag$WwMQ6n,8VHM808,Lqj8-rЗYG xݤ {aX~Ӈp8<;;}eDZgVEQRe5D@2 <<+V%a |86M@RpD&68ٝA<,ӸFHhpTםGFI PKϓ'O>):4Nb )*qVGGG`/*劼˲ǮTlt:cf`P.wvv*rR-wy`VCeS%%s%(:3n޽{~~W+"p GI+_d2Wf(F-A[\4hnLB䙌>g#g;::B=5:y;P 3b85kl&tV*9ٖR1lێxZU*#Y#(`aQJ@xr2P` p#AV  ݘTS2;u{*$͡>o&yQ3w.Ko@^?Lb!ğpE8jb$/-]*_9Yg5 xi&o@`&W0@o8g(/I6cPiɦ36lPrdrV;::B''~a|}FV{Aߗȑˋ>i΅K f@5 JHg؂'jŋtztt@[qJ :.xӧLYҥ)E6 ^ެW08dB2AVR7>i7Y_l٬xq2P7D^dt^ X)KM;fRl@!y7?qqH*a:NZǓo> 5;/..JGIM; }جG@+_%%5Rz[rǙNa/`0 (*L%zF_s82Qb@@1Wk cklGLiԷ=u>j!*;*JK ZYhEdyeqMscnWsˬf #2WⳤDO#Z^7X?DN-:ƣ VZ0, ƏF#IZ6MC#- *Q&ȓol! CF3 tcOb8 ٴ^FdkgcPc, j^_AJI9q&db PҌBj8aڮBQE~b$fѣFҨ*FrtJh8/tmN`hV+Jggg\0`<6Ż[UM\T^92|"E14e78.=ֆh4f裏*epJd0|˲ x뭷bM+F1 (NOOpX,JCNeiVwUUV ?Q @7:1~eK%=Հ8 F@)|Rʶ0 gzζvi6T>' 4(usD.x2G4/dfPZhPJ Vݼ,{{{x:0Ib){m'ip4V bzΣr}"H,$ eY9[."v(s4^̻ l63TZTHfJODe[Ȁ?7ړY @!S ۶ 4ĉHǭ&\n\bn4 +~eZѣ^2e f9u>1RhOG\1/K_x.eD)\.///{ШEww2H(85SJa_L&u(^Z?+ok^%> U׉ @PuK4%/j)sE[)Agw~~zd ڰ"VyX9ƒiTꍢ(718a, O所ݶlqZIb m4-x^RŲL > nB +x:_ UI\"$px>n}OϡlّtG^{r6k1Q(gb FP_V˗":m4*kt\ZEdQk%rOugJ&#Zl(C-l6OZza08o6[J)t.w]B&Jeoo dU_j+J;bp^RYIG kS7pC>2R Hj$e(i em>nƧ54XABBgE뺐FT5ZT2K%/i.2n5>m0aqO(Gt@Lr,6MӰ /Vz.KX `$GGGV mU?FӴtܑQX IjԑO Vo X*M|> (v^pNs%a p:om[i֢4۸]{) l%RJ v2{iTDTE,HB$ !5 #lTa ,}rEj9HvyARd(#N6\A(#2|A|cX9*ܒ tgӎV…ryc91E1ah ,0׆.v Z/^Fv=&mہb['v?~dR,n(Fh4~glNS|Z|Q-KR e 4˥ao"cp4"&eJ}Z%52uJ]ߐTO$:Pm4I#ݸ) iiV(s@o RϏq!_%}YcrZ,2JnS=??[RRifX.K$@-OKBfmL$h4 Vd:$ U־!&v`5.//|t:Г[~KR!K7F~ V6 ք %{JP—J*c ,˰ S \%< _ "'aZ 0i]I)8go52M#4Dh)cT w@GKxˆΝ;^Or^kϠv@p6˺Lv6Q(@/{&W4ޝ f Tl$<0 cf%RȮdeiNr)?y34Il%D>lYaGR\0PE@܁{R:}PGV~N ðTrK7L< X,_MHS$i*tUdꌼXxcŮ:)%pHW&*u3Bft paFaQTn ?(jZCXߊQ(F1>߶f3Dr@*q\[(PJukƏض=di@Ծf)W9&pCڎuA-Tel H$^xٳ$r,4/2dPՁu8;=/n}%ZGic\b/:N݁Fyȱ55ˆDx+ћU ӀZ3a[RMmfb0MӴL4SeI$iZ$¬0yEKRiUG8@z ðlJ- +?ء Q.5f slp(ƀDGjgg-$jX4TfB6'qac=C3>?k(@&X毃KqXؤ>Bc\.cu=ed$*g/OqRA6}@2T$&N)j2k~Xj5̇hHJiOJX.1;nJ" }#o@6L& j`aCpRJMbxqjl/|(J*3+: ( ?~ f__[1Q(Ƨs8믳x<ͦ/Jalݻw)#O͡ϟCxawδ&Pt ?`gJzIdy g(!DuVGR4B0&''Tn߹sZʦ\Y@I0D!/5tjaxdn/LefΤu$f}9f8m"r8NzRRshuj CĈL5fRT,qJ,XjZy|כٕMjl^eMp||NA+p4hmj5I L֩/BJ fa8fdx(6/|a>qZvpp]Mu}h Xؿf{YF8 :ˑK \M;;;v$h H)Fd]25^A"#OT){rf96R72Me*I Qtyy <βr\=߇DL6ua@J" T *eYX$Kw Ln%]ȁ"xEJ0/ȃ@l6qFu}Xe; t ,uݯ|+??.hl?_,ttC#aC?z 2ACyHQUkMߨԚj|$Y!i@Z:Z}$cVIĊ )dRVK$ .C LJ'";~fC(߈ghœZv0>CFRnEQ^ku< íxt^7L@޳mYfk>F X%I 1Ď[-䔻Z(nr3ʄ^9$dYa \mYa(#1lNJ$!l*_z(? <<{{ QJEWabH'(>17 3y4m>-VTt:Ku=&dla>":;IH0dk$VWtvcQNM<^֥93:࠻1@<>8 xLHI}>.fp Ea[D@ ,|m .<\۶LA !?L Rp!rgpgp嫲^`08x:-|HJIĊSLSahf Ij \=_u(FQVz뭷z XbRðޜ$%ɗJZ{w} RdnaV74S] ( U۶R:rY!#t}$(GT)@uOQ>͚'S *۶L;h2@[?rB{d8~:\V+˴$ZiZiwu9gr"+ƙlj iߔ˞aj5N CLN7G^;;Q@O NuVpr9(Kzx0EU׷|Z...VeVk@<*VUK l& u aIn|.Q%iFW !fjzoi68?b+F1 Tx~o9rz0LS"US BϕRVF1 !=R3E[SY*TĖj V+ur04 ZZMh53\؈ ׫0L\םL&`tn4M9I='&<:"GV.*B*˽5b[jIDj B^qww4͗'/m05!Aa~hC^[VZfK\Y5IDN咝8 EgLck Bt@lpV20.M 7ʆqFږrl[vk$*`w?FO49SH%v/ wv#f/}XAxF @X[Lx‚*`)b\*g,Xnju]T:I#7۶XMJɡ즑Ln d\S^+6";P/XqrV ^ DC+|5.]-Z.ǣuHY !X#~@Km_{5bc$*yT?}43{˵eYrZ5M@K78rYbʎ}<o~ŢWb@@1S<\-ݣ :뺃ٳgRj\`<#†͘Z۰_.Fl}E)1H/=^:KYxvun:W`^+pJMqDQlvVCPC)E^+rr$I-Ki %z=NIp,!I_`Z@Ag%Il6":b^n w꺑U R$: e.ua^EJaJyR@ֵ<+9:QJ!E/Q9_.tdTY!ô@FDpANκy&l6SaNogҀZT!qfRq ˴8FIrxX,X72Wi(Y ljr6ٶqGelZ~ww}/VC߼~VKEZG]$(ҠT+ÒN S^G?$BdFh4!HẮeۆhV@[Q.+*uCiD@2 ŧ3vY$rh&/iҭTEa2Yhm0yi-?4zFL: 6&Hn mUβIj`DFLygJt@<#-)c^ge\Gt:%@dV+s1@c9CX /`//裏5(blXɓ'y^Cۏi(?c(*VQBMxnQ;>Œ~bcH  zکpFUSb #pR4Gݲ4M@ڬx*nY[4 mm,pnj4JhT/FSX"AL@94+!Ё=L(j4|4`iaHqϟ?/(W(F?547xA4͇}G`XD mI}dt9hIjn-|,e XOJkZۅJLRmXZ*9iL3x2MjV(l #d44LaszJeZ4Ie@+;xEbcNcJ(k O8k_x,/ ّSRɈc#-X0?fӺTAп X?Gn7AR㶠\.7VxZ%6 eR :WqPKRQz^ɤ^l6/uNv6DRّxri>3t1g#7ѲxR?/>Ьި2*J,l6L2,b^iJ[<@+&W~H 8Oc{\ []le#KMiOJ1<hkD$A77k"l lsqns{L{{{V+ CG~\>\vY.gl63A$8aa ( 0MẔH -sc)#1K#Tw!&By*bp}_,P>tܔ'S n FY{%L@,J< ~2nrZSJ95ed̐NSe)4lT*l6rV#u%1o_.'''@B )^(0\,LyX̤UxRp@ g)uΈڔLup蛰m;1$M4 &eZXYyyP:@ء OcM_* ?j*V FRt-YҫLױ+R4dff($VJt6ib(U*0@! 24UK%X- i5y0sb2g% wt:fhE\ M FflQlYL1`J >|/qvnR(9d1@dP r|<-heDAO|鵖4@9Ske9]*l2-3t(]t29 `0 jZ1e'E>Ф\ 40TbFMˣ(u7[jmݚlk% pYˍ)Y?FOA"4AWby9D.IFaF\Gz~xxx|| <{hn ˠ="&F, VK޹L2b;j:_8FϞ};@|a>+!O;ţG~b,F1 $4/VUݻw^C q d /b*ċFdj /oΨNߞn- FS4 ]E 4/ E 2mB]>MjT}*uOd}ijY6P^I ' `.΄A!|#t"Qu]]\eYTG%9krl?c5L \cwi۾CD2^ 2d^zO&06,H8w kZ12:CĜqy0)tI^fԯ dW {%EEQzQw ^h6?s?W*&U8zr*םr~4\.Nr 0<==> .a2h2 lj|hBKV*u^era zҧڙTԒ@y4}oIeonJoI5G.jk;=q$ @Pxey_x[V!,J\Cpn) &Y:\Q<<<<::t:U`I8Qa%QbIGjKS x0Nl6˕ԲMZ-u@ٳl{ݹs7lFJDh2aTz@@1Q('nXuΝݣ#zJu4Md(ҴTO0T&ǖrp^S_M@%;C si> )4 |>)Ry%MEe[;8IJ-ۊ:Q*5 0:D2?N;Ƶ0 @~vITKH@>bqzz: JRTƅz|A0XYzk0|X'Q(F1>);~jRMi~((*۶mYV''' ANOOAVf?<ɛX2Cz,; YMBr=,BC2ƒ|1_Vx01$I'2(:Ui= 26G|>uY6d&G *yE@r,ii-·;'g+tMP )z'duHsD@Y*4NUN"H4 77!Ihpi2I§W9&11FpV-KCXz=PECZYEiR&fbfR +rb?8<Z4=??OtvKM+pU$0/!8o@s///_en Wӄ*rM~DZe;1V1 )eye϶l4@aPgDQAE VV*+3|jzLƝRL|Od, vƳ2Ǧ4%dΜ !I0FY ðV;;N86yK&.B+x 6.OFr7 P>h4Q+9)!bb"t |#% %l$O513hh+LC|# VA<裻wG_7|3Iݻk+狭@# 6 LfZIxN{If>$Ymf F4V`YSZ_ÎXmۥRjUK NanZܒsuR y 3TF" N?%L"TF7F 1I@^ogg('"2Pš1M8IQ^(F:E26]*k1Sw[:z(T*f35@nFC)>UԷdA~$j[4RW⑵Z pU4DtœLk\)7JFx4V0,QMT0J$# _mV@ʲ߶Zث !A\^^*!8`! m;N,#IqTfxEQ??{yHr4ͷ~Pt// w@CExO>Ǐ;Irj]f_<`$eYa*ct\En6R\.'gDKprd*p;(o.ڶvi`v#kڔ r|:Z]n>APT^&:6Cv)'3~Eaf֛vѧ+Ԕ]&`QJD|q|vvvzz ,K`cYГ5N'I^ 4b*ݹs@@1Q('hX)* 6ܪժ㔢L@y&6G}wg8spppqqQ.KJa蛕YޯNJG4siء @#2X,~8N=+9e]#%Zv 7N(64Lcg_d$&[J0xXL& k().ISDƫeIko"q[նh R웰 $8]\[P`>ON&Z]QDe+uaQq(p&[ %c øw bm>h H7V F݀8[yJs881̘9):A؎*W*?99)j\4Ӑz%kŒɿ\., `H0 ///!s,?[krR݃p:q%uM3gB/Ry]/R86L0;;;FCҳj[}OHa燚M-@6uoǠVyO$oGR~y[ bu]6U?j &KolۦpiըNyj*$b28F8S*9veE)|My=7o@}Jizrr2Y;wvvO _L_VXд t29==M6 |?2&!Ex~7~6b믿}' eQ I17ޚH38!eL#/sfR)co9 t%`4]\\y^:mi}&<,s\hȃUd$_o8@D`BSUA`.)i\waԸ9Z ;`+6)M*5ITtzzr6®e%qɩ8<p 8F=V[ 5A &U4IÇ+JIt6VCK"@da0FaϨ%PK2j rY(F;wzVjZ\_)0 ?M؛øHef@G?j4b8:N)OՙiXҍjbȻdc-drzz0C#t2 #B4M%ITJ)H)CP*7q'qE` z tâY߬@֎3FlcZzr[gm *[1j򌁤=sZv Cċ/0;)k2'RO!:/MR46Reٖq5 "y?}݅xN02G^ wҶ0>IEFy@^A !\m>995d 6V70 qS]0 !ҏZi/_@@1Q('cDQtyyIuV-$N31-nkwݒa(H[uxxS=MJ(kZIqd2?LjZ&VdJYmlR$Q*UYyH$VJ%*1Mlqۖgz br/ d' doM!n*^im[:[ȏ RUFc4,*+l).M(uILLqa|4&"{qeVVGHeQ8K뤃!Rh0 HinR"NtGP:)"LjeO-.9a$hBnhU F062W(;;;NuB52-}p L&`<c$U[4N0z~v:Yذ ҹb뺶mYDn^EחĜjh4&N 7 ! on?U JM$5Γ7Ok 7up'*I^Yk"[JBdҍ7rzgPJW*ѓ#r&Vk&2ee 7|>'K>ju~~#z1*X=n!aӱm@=_N̹ dX,&Bu(b3zoof 3'WiY!b pJѶ$IgyaS+IJoi'?x%nN[3Ɨw}@ֶa-:8{1+@_zPlbx2L&;;;z^LJGG0/M_r) 9Enz}vP3+oeG.\Vдzl6A tQelJ).Ke({Ӫo3d~YEyWh4zhFD*8INW7Xy NiH E6t~mv-|[m>uIS+}FV4XbҊ9@RXVff ".sauO-¸ UÙuXL`pyy g:&u=&V2C / 0 @c3qy(b4ǁP*NTm һiLS6\CSH4GFK@Ie,9%3ZDLdxGa! JmQJ%t: -@C\9m[Yf !I+`MYɞd=C~ЂKIT|FކN2\֢|f)H65:N b8z=8,`Ir򶨬GyroȎDJ9/\S 9gʭ݊r|{o8mWh7_ KW9R0lF7 1&7p8{qqqyPR_o|hpŰ,ka2p֩f|vԆ@>8N'+ipVXNrHoAM58(q8|#]?i [8ʕJv`Iö[Nkӄ`5M]K- Ey1\Дǃs9v׷6<@ӸC \T`&1(ѯ2{n3"GV*3#Q^΄ؼлv­ F7dCG7b:Vtz~~3;4>t:no4ఉXyAJ ,`ʅa`bZ QbQOĀ?uYJTZ2L31TrdceI&wܙL& 9Ko3y1QV|x0̻wVd{\ZV5Z-Z($I2p8F4^;yžAW*0c5_Ç嚓Ӗ, b>Xey캗0+>b, \Tqhɔ&@-%:.d94x&X9Q(F1>)?_qU&7:aʴTT2 r/vk0Vh+x<^,*cv i2 oHM&( C=\0`3u-rld2Eoo8&贔 RSH%]d&U)Ya}83?bY[~"[sȜ2FeLϗ >zh4!///G-3S$cI ̦i$kTRqC3# 1Qz-YiC*_g6 ȹugAdžaz=Tq)ZIB_W3>ܹ,B>|b+ 4d^BZ2 BCӧOwvv>Rn,@!h= |AS NM"vHJŋzz]ZxjE|)HeJڴ@1iA=:}{YP<ay>W~hx m>d| sufGsЄt4~B m"7M[~VotfZت0bNQLIN!I3e+c^ njihkFv=;q u<*cTa\h4C1Mć 0Zvxxhd:9==˛凌řk1ޒKFY/^899éw^O|\.]/حe}_$d7IՒ5RZ@N}Q?>>(+<=bXǂ f:H\ |kYiu)(Èj}+_?/mޕ(V,uZi(@R m`e2ņaMj$`!cvD])QTjfV!~* (\fnvN]!,TN\VᴥNۦ쯔1HQOa !~$IlGggg;;;<ۭ=،'Z毮[je<OOOض { Y$|ܓ~>ˤ4޽YbJx+ZF-ER`I$fgϟAْd P>hX ɡ,h;J1M<8 /hqlmVbh4>??_,^s5:( ƔR6t\ךRI!Me[<^S.;|2|2 ã#+"<[>Wdf8;;NH8f24 @fZ>y М)jx\ & (4 d&iEw79(|rXb y.i]郚;0 cyzݻGGGx(B OIt=ټ7zY+E8IYاabйWI M& [rr ۶aL#Ylkg^$vQd40!"W^eyMT*(@=_>'57³6Cٝp3(Њh8q]]@iŒ§\.0uN0M;w, yHHZX#O[(F1Q(ƿ̨_A?U( /0ILӰ0M2a$q+]( b/ršd 4]ggg*9C6YVry6urrdmN8z:H:PCAR RZvIͤ<% Qv@Pl,)- @nF@ БDLZl` z^;|>et:t&ĭH-N VN$^uU|[PVz%~;ST>?f/O^CBKxdhaH1$FpX,*nZ%e(@i4xi|>F믣o2Lu]^k}uN Īf~-/DfWnAJƊeZɣ[θvm0;d^ZJ6WT- ]/2np}#n4I,X(4UՐeV" Jmd/vɮUkJ^rpsaeWIVGD=_y),g@ȇr vn%"h4dAAn%T |?m$^rTʰķJif65"[F܈$Tդ(i0⶘zQb@@1/0믿~||\V!,T*hh-cZ RؕA c,fګ2w{,\/F։ʹ_v6J%*M-FJk+!CsQ>)*ʴS2aJƱ62CˠfE2-&,K9/ p xx.Jj7Z;ݎh^keAZmX frh4`j)0W+n5M"FM  @ ljtV#ph<\= [$F@ h4N~_.9 0X,0 ]B-z AJ195SMTI;RhqVnhx%BNTx<m![jwZ6OaoNނk?6riKvcݓ~0P@py 7ǛV /K:Si• Yd fLTƣd2, VTr&Ei#_Ǯ~l6]E p8D^jwww+.\.)r) q=Iz#Yo{-Kql>\ #Dj<-d4 jϛSKPT2>WnT*@(O< $7_H^odٗf3kж@FKjmMSp JJ)4Pbn\t =hRJ9N dzEt纩RR Pll@8j;@I ɐzµEb*B.AFq}: V+ _ae=仆q:r3MT Xqb{(6U3Ij:G&;Rq`0$MEk@1Q(ƿvv^}!GBaCFc$54M&}u.PceDXh:jZ.AGk+`L׵Vy/VnpVq[h>CH!0Z: ӬvQAIqtT~jJ'ȶO40Y}tkVz%| v!~zzZ.(4!-,&e+_^^Y ;B*iFu}8fญ^VhT*_̗%y6Qm,=4H'|4rf\VkZV aLlvvvvyy$IRvnx#ᳬ}m&#_gN壑Jc%j9gólu%(RJ XQbPD*Uf.P@l>`A -Hu]CM 4#FٌMS)2dΏd(U£D FEH [Xd` %]nRw^Ia^}Rlھ c6<EQ&''' ;y9LJ}>h櫮%HA>$Xڐ!6Rx8,D,$I81ifl~ݽFn&Jys aE,ف}s&~ZieYV2  oPbfqJ_WaYzY/ 6v\Ao(6J$I4I$F3".)V 0#2mb>Cl2oGD/YR<ʛB:9Xl 孓4UckZL̘2T)pӴFb0 f={h4߿ D*oqH3_BA#/tbG {%hoX!Ԋ[58 tTahxi/^ggg9/QZŒw"vuhtrr1Q|74VFa$QZy( 9jn$i#2q|D}6rZs]T‹;0 |??? `tݝn -@ @cY.e q^{ 5! 2)m@kZF,wtXw`-}1dVbqۊ "M&J( ,0zdEqҭ< YIЄH.jXn<1xYMDʿIG=, ٿ rBcEUN{n[@0۟ 8 0KR.&u{۶ql6$! ?>(e1gq!ۂ:G.eC3!ڊxAhNb1Ng-JU]5IJt@m< Lyi,KN* aqL_\.fo}[SQ(F1^;ѼRBnZR#@v4U8 C%:QB  *Ϯ*k W)+]3QWi|[ȫ`Lス=!9UyLMT#jC"7HNNO߻_V]zcڣ'Z gE1SJ7"Fsz5ȇ򤉏3nRQJY?D }޽z}zz:8`/ ,I,h>==ElZU$IS*ZH, L30i9Z&i³ <$3J4|>Lb"PZ^*J.p0/\=88ف6>83 'm >P%zȕK*"rAO|)`><{l2ܻw(27xl rc;pcI$=+S7j@@z}h7S6b֊%:x[ WKiBS.nMoRUDXPf}lSn@ #G); JNO|IvL9jmpz@_hpcOXC!@#`ߔ%OL&& I(JhZAlUhd30RAr988{.VW7TBߎRt)V ZNk_X`ݽ{ jPbQafa'R+NѨjMB$IM/4Pjs(C0\[^f:JO5D!$㔸3 Peq!}kV}Zp!R+[ifuR+25Ml6Kt{]*|^Ea DV9T{3cAL}4⿺Y D0HfImuEj53c1Ievy>RZ޻wiڎ C]/cr*!dIxF>+Ÿ4MXqu"x<^i5 '#W`!0˥ya߯jg+ ZJQK.˰.,k>Dewz擲NJzT2AViOGj6bhZՎ QlۂmJt J.PDTA62-d:)@pXg[f&t:E?_p윹da,ٳg0!d˔&ttK"0t Df&@I ^5. wض=' q!d2L&õF?<|Y A\QR`コ.,BʵAn _wC|ju~~^޽SmqQ3*\ nʍ=5>3[de3ʣ[lp<T*{{{&'b2:^)xl6ݽf\.'9NmWSI0Cd58SyPTGFܾ`-}VK,CO4MJ({3(u) 'P K~VnX`dF:kȶ͔RaI T$MDQt6`2RD#|4/lׇmNuGSF!AL񂰣/B[ o/ QυJ;.a#xp>r6&jö'iʞ2oZxid7 #M 83k5/CƑTj5HdkSr&ijeO W$pڳ逦*P$fI 4MѸZ....//' QeYf:z!K[ERT`{,yV|߯T*#2mW*<ݻo6'%XjGTPQ(F1yeYF])c20|d,;e;yH&11"T '3GrY5 aB N0 B:hVK*lkHjvjhs!$I !Zbd߈J"pxrrrppZDSJF#(N6RY?wf&4 e/{f efUz;Jz{ll>a-p}tƁpAɃCr"gq"~`I# C&I''v_2Żݮ뺿?)J^bQ }Y*iN&ZuNK~ V8I҄QcD ^$@qļgϞA08L&S JRnd@"dᨄ.>JFK= !d In-cnbgg\. Q;v(fuf R,"u< #iyy֮޿2ov2<@ m#brKz]T) R}l nq>|8eՒiLjUǴF&3 w@ʤQ4M!. TL/^QˍFcgggCZ 7H8,fuPigX)H&+἟Rc>K̔9l5mwX`#րO4 C)òk@8N (:c6DE˴92mdd;81\ jv͒MO C[I8SO));D$Bfj($[2 eXʖ2IdZ%qepZe*'a%I*)^G%Ir~~~vv.hVͧ ;&m]5Ǒ^eM&h4L4U\)0af G#;wrH0hhYh"A|cB%^ vs Ç=|yI L A^_bE?NNN (b>Lڶ]rK 0Ro_sf?P~Vh}1% ֎C #c0!~鍐.k(aq;! I U}ih%Cd[kDӕGd2Inz=ܥ4MaFYi8hivU] []em^mMkhbcVoHQabo,V˃ވ9Y.ݻWtlAmL!Li^j|0=۶)A VT5H\lʾt2Jv8(,9%;#83 ɓ'p8Ǩ@JLUV$(t~n&y4ӧqCp2N0e>O$ILeFlV݆4`Mn  pXHR p[Y`6{Ye|>/vei?\AkZϟ?/"bQ] rk1喬33*|df Ipa"`4jF~GZ^x^TQ^G2읈r0Z !X"1oΧR^r^Z-M!jAo!DGMWo!1h2[yMK[akI?KbzB y+dhϓLΌVf 8m?l2leZuY2-[#Eӹ=e6 H-RA\\\TXש v4Dbfmޤc`t:/~?я`YXW2w22MLb@>X,w@~KMNKhNfI& #.Spa氐.5Djpmhȋj:Yf%Tף!f~ P9Mȗ&n\!%d_䮪F {+]!()q| ֛ydm&jG|^?{% 7GfS~5JK4ײ,|IdbbDb(կXFV#*Ӏ$0DgIR{^(J%( :NL&ʂIy~?ryRl;}kcQ(F1# m{)R牆y2^rf2xЄ|ߧQ8,"]QwO=u! X*:^+aOŦ3C2v"?5o`%a<(勴D9R+d*XZ5,IpIؚ3o-oU%&j#A~[ yL0@q Z`>^AD@;n fQٜN& ߟfiJzN&M%+7a# ӕhƧVJ ZJaVFq|2Lx>$ dQhۛ~ukB,sEt8[1Fbx4 A̲ yDlVPG'%KKP{NS%a*M04MI5j\5[x-Ni>}EO/i*'57&E: f] |⢰OkcS-ŝz(˞R*ɤ+4U*;w>{ ʵZ n\J[WT4r:)ᨷե5/# ]G:ŋjk*kll"n of p%="XL$d0AW|5r B81, L9mxi}d7 W#0MJ$ių 8˕ oj U`eFAF06mIJJq>v/Hn`0lVD(F?#[Խ{+Z*<ܮD S(jGPRͱ"8N Z5Y qbd2VQ!\F2^q_^^Htd e;WRYZgD I&Ȉdq8X2>J(F`XQʵJz {)oI}ey-ruM\|T7|Y}; i81IUj'YyQjn 8RJ;@3krTM1%2zzTFfq]apgY{~d{0\{fc@sQ84  vvv/ +K޽{ڧz_נ] 10b Ytk~/^e|۶+yfHT qӑDk|/t|RdhQVRPC)uX8j93_VleK<5HYV~K,Y_MT_)Ay%Fp˂y_iX&,JXb K9Οr|,KӴөa QJ_ՎN_| !<4xZ34ee8)2Ud<7 #URj^[o>&@L4FU$qԋxMbTݿn@Z/@@1Q(?xUT%C ?jmRЈ+v`{CƎv}4EcDu]1<f"Hq+Jd<7Va(XIGQ|_hfxMЎK#ُVtVR1VWQ`/( p8zGQkt9J0HynN-Z.U{M^2{N^&㦎hrsqϪzO0EA&_%ʹx]Zr "_>@d2 Rnw]h @6KT<pdu &Hu&4#$(7^\s" J˦44&d\.& @F5"#[.u. T]mecq⪕64k4kҪ+ @[>9,U"jju{j5ٴ" )jhm[`376@ui]~ToEBDnHT4ͥlڿ+ :YCپxkF rhۜ]$q  AQV+˴8)JQ݆3: ^߻w1I (F1 o}[o6I˲~gϞ-`5Pv8;+*RD]8roZ%^-vϞ=؎IEJ$UJ CZ{QYi2wb{p> niKh!&.,MӝJ^-H)(H,Nћt #unR'vMX @?o}.pK柗 :'T*Z-LWG#c>Iח+ZwC l. 4pI-^+pUpJ^z zW ]&nda9]VqOVu]HnkRBғ";@wZ,Lq%DLì+Z'FrE dnq-Qq`{bFmmܚ1t;J9 a"5r) kTعE5[%wOxC90bpVL& i$hEQ,Xqضq^k*3 sbyx53P-%xzjmMckL8٨7d2)bQ`ۆ]  _^^"D@=NiPgV.o) 2"jSyZ߿oɟ,KGzfo}[vw! A5ceE]2|A(q#]_$q,KЭ]+J]c`hDF14)PI~u޺Obb}\_^R-+A[[H[u{N}̢ʄ!yOP4|E r" _i* eГS+i`H$ D@Lt 8{뭷>M@??KKJjC{\OEZ|c^dRz:ݓs+Ҕ<8]3! MKz@4)KTkZG>Byj+$Jach/-Ky$&Hwi"ˋD|I Tyh:)y8UwL۶qǁMx: _@XR= (!೫ (.^IF4蹛fXuo~WW5J^a#ב1?zî"(*JO>=?x7  Wm(9MZe3q\+(b# ×/_޹sd?=3`XiZh֨U·dt{_.*”j}ww _·m*1m//Ɠ'OTFa&%#7:uNUFFz z(90`v8sl6:|$ U7W T9(`kڟ/I/uW>uv4!O5 f;t/ h@ޞ $jtBCaZHog͙Ҫ-XQ=fO,3^GaF^Vo|%T*׿u(Ńtp 1Kj0cNl p:>{ZB):)')߲,LԀ4TIT*˶,۶sqbqqq. juurc y Iiggg'XT+Dv8*Jŭi=jRv%}Q~t"ͫ C'kpnHnfJyGGG|APb|Rj:ٟooUULm/dR.f7`{MdCȪ>r ?`g2}x| :5^\\|mZ{{{2'AZSR_eIb\^Z2FGxn3+cL[TՀ ~V˓oe>[)RsKvZM0$ o *G@d&x0%|W t:'BL$_ w:R` 6r4k. a:q"t`33nŝ;wցe~JRw 1:6qPG8zfxtJ%di\8^,l6MgSB̨h}(wHW[y8)(a/%.˲H|0NCnf 5SKRY@!!ؼ3B![|Qb@@1>O?t:/K8B)P?jl6 IQA(tr"HAFh4Ad r>#B.,{$pH oio~r/ KBВ΅|Ș Ⴄ`O1aS)u%@P73T>v{'^̨oUKEl&04 4Uja*J.Qyzmc &Օyp`q-8\qە'H܁=x8N{*yH_{~WS<|Ν;nf%(Riijj5L)022`%ҵRa3]l0 .ˮZ*u:=K^! 'TeÄ}|6ɞp! ((ߴ✺T7SnonE/66%@U>xߴe\xX $]׭hἲ, vbhZyz\~HmGQt>泹~ްm{2xw||j$I١#  zEX>} P{ qd,-(c@=ENr޽s>,bQ e9nZft¢;$^*p:qTw-sQN^V`"<lL.kfE߷m i>}// )̨5s RF[Ce iZr er :d BqC-04:R7j࿲  oir>mV  z)ENmJ+AþHlk *ܽ^O)51UM4rRP@3}:"za ^JO mY_d5Pb iZ-RTdc-h2fzy^rAUh߿S (*JzF:\)_?i$Cm2ovU ( @")h$3LM5 jLM$?"# #rgL&q8 - [.q͡.AVZ,4;w4F X;@_UdxU0[u]x<}4MӃ!'rɾ]qss7l&YըFT7h4vGYMo\2ވZ6$I>+` (03 IS4-eSi( b$$ 5 +8=;==SyfYV9bz=T!|-(CÇϟ?kwh4lF-ƽ_?` !:Yٶ]17[8nk DQ4̓ 8::Fet-PkW9&@Fj.᠒hiym&}sm߆l=e )Bjw[_2$RKzw=<< 58 <8 `*XoVO6 jI-G쁜T:NސABdiS$*Y l30+E8`0x^xQըFTzX~Z1cԌ 4G\G|. h`."RA[,p@5 j4תL2ђ@{jUX.n4l:A}wߧ92qQ"5 0l;0 Gѿ]Kuᆥth/>YiU)˰FAA|fވ[J3XM{UܫsCjƍmgd^,H5U!eS]pA%6!) g91lWX,+ O>'l6R H99 y?zѣGPCpR=[qi@tei@$CeB>j!_*2l|۪Myo֨7H˗Z[fCu[@M҅"~(C+MS $ j̵tR%*~K7鈒|P%~%(وa ,B6,eoC6!gi*6{&̙IcjG{l6{9DJv<<:No\4H08h@y$I ݀eYO|4NԭKli&IeV݀1Nh48yWucH9pٶ .7n ykT~b87PWLӤg PL&wChG d2U5a HwTR`0<ӧ?w]uAJ4yTM}]4`ϊQe.ZQ!( A, XۖUo4@'fJqyD~Llwn7@rxX)/F&Jli4Mƽ{,=;;K;6yJvUa,t:=== DNjAh48VzǏǞَc^4̳c ¯}{c!D\b$!cz#qXt-:|:?ַۖ P -1f,VF@+}ȨaPW=jGW8,#ʑ ^4} Yi(/d]TecF 7qVSzfY *Z.뺏Yvg4PM'J=c$InQ#IHs Z- FnٶyǫCZ^-Ɠ@V+_h4#Ebt]D*1er _quH6l7ۍ;{KMz^lܠʊ'eA2ɤGlUAR}I(3HXzrg*u0o[ٸ« V?$-0 F4 jɳ&mگHtgE\MjZZ)DrN O|2g9bQ 0# Pi@.^t:{{{4^jTk?yނ5xyRu׵-;MVЁJ0]"@DJKxf>Hz=ϛg0nۣT `T ,iu j5ֽyf?-t8s۶}GX"Q,E8BwCY yOJmOz"҄ "̈Y],0"/%JFl"AD)!m%#YZZ'IB%rDaVku~I{EIe/HJ O~҅M< М!?fF-$e^+6iywV= |> Y a*jcY.r>adF`ZwAoFl^qb= 'IȮ VVF9G  xJNn} 3bōvyCUw@5Qm)E2Ʀ[[U,Y ^ѣGxo1:{I]F$l<w:}ߟL&FΝ;o;wж0 ՌӲPVf̠jH],:<<ǶޔD&0"e+P[3G%Ƃф+H<ϛHH0Š VYHN/,J YGd19({U0+q>4TWv{B8"`Ϟ= A${p4"GVE<$I@0F.Da(ERpyme=~qg@O@xo!cٜx@*AF1u XxhJ!-dIP6A%)hG,JY>`CPZ6X^S?ˌ `9QEؗpDT"/Pa6 CrlZǥց0̕@exOR `T^$)Tdfe;g ѶqwbL&t2fFvE1\Zм=]]]]^^,&j5 4zg*WʖtH)jxg>4cbŀG VKhB?'*F5* @w*Dyaa4c%_C\U"3 BE@DP87{N>b_Rd1S85*k ޻kze~g;|A^vR@z]Z!8YMHƪ)kɤ^] CȕzEg2tT%(>2|A% cmt_XJTec 4mHA%q9ll$8ow}WvB?~eZmĠ'OEsuN8Q`zDZ2뺮50 6Lô [bJiz~~nYf z,-X_4ͽG.b Y 1Ѥ^Fe -Ku:`ZQʩ᜻Z'hJJiY)du^c6A0N?Y?Lrw}Y5 Qng@yᗏ@, kW_o*^veb,Ie30|:D"1G,LEt8NI[0>,yI߈lt+r +Nʵ߯jؗRt7rؖ5NZٺjw: Yi*!$F,lp授%$qRp.  e )˚} gt\UPjׯEhd>I-kU+RF {vvvss`D_ÇBtzWؑٸ42YiGj!-ȔR0T裏Lxk]I#/H?ZM3pbPB5E9|ZOl/5@@|)?DZ]84+X.c|-2x=16>9)R&&hݴ29E.eFnQM{ӧWWWZabK(f"c6 8<ϋ"˳4I$N$IWz~LDȊLtZK{ݕRqtC@efv2m4uNa!!i832}E@,-< _.8ϰ*tѓ=3/@,*$[ڃ,!mQB5,\ hi(Qҹ5mށ;mٻ(Yہ |-$/LܓJ'"d@mJK=2 (Tb -۲Kn}h |> Vl,R!\ REqKwjݿnsFQ.~t x:'''StooVG0D!K#em[UrVBXS೰XY$%Px."A@Zi6^o::jVPjzfzbYt;H MI]\\pHK!0z>c)yE8ߑR"}b1_,в.jݮLBizٳO}{!#*:iUq@6Ib@E\2-Rh Y+'RAr9L~ӟc1l۲La72(_%  M?`"`*5.ï |#wp;|Wd>LmWg$DD@١J}Tv^\\LӃq@-Mڊ>)a1dҌ e;uDZeBE^Y8Sl罽nBxpE@ H;$!hZaZF#:a_b"MuA\.TGEya&ijVt:{ְQ)6⯘ _ŕa2Ryoˊ4őh0lr\zB95-Qi5Xvhpoc&ZZMC.jxCI8 V8C#wFsī(( Ch(H,]+DZkrX}fp\];\JԢ=u$: aЖp8LKP,#DInk}? XyaY&q l$"p9Ey)74Mj{R)E@Bj=pd$I~ĀV+ o5%Axێboh=EKN Fo\__GQjF>=#!,>kW ARUfh5 CY*n*BB%8h4 h6m{5D ZbF^ƀ}8;Q0Mg`0orѣpv(`)V:|Z' d>^$OS(ե:ђ\.!+C@% P$>´Іt@Z-7 (Taw5)o U0g{je/^Ȳ 7\~YQLQUTa_h'Orw@PeD@;TW,mx-^wss3Ns h"t@R-( j C"mUTsUY^[-dr. C{(Z}ee4nA s24h6_1Oz_wmF#;߯6HAjbrSiϚ:EO@EQ+rƄYbbb&I  Q1ki#mDŽ>eeE(\@]%*]bcz#W:`ƁKM@ 8ʼZxZmZxn6gJs럆a(eԢP)%0@_ &?.!va9}-p  _I@^PΦ@YO͕h~ r9) _>Mv{e4ď(y^Uu`hܿ6}[ߊQ o(PZO61tPƢD,tHBqgYnYv^3 b6z8 9b=4էUJûضAݦ; MV45r$KjO p _U6_?RPX)ۍe.32k2}Jgm'N﮹b(kp@*Ia\؁H3kӏz0FFhR *I8knͶ-Y-%U jC8L@ bhא:J;VaVtzA  MӴլ. Tҗ˿/8&z\*k4dT[1޵CQv]qS8ši&6WIe䔆mb8Qa>@Va[`k 5ˎNlSFNGi/l CZ:0 N$&u@A,V&FQdNanu]_@w.ܰX-;] %Bea]<"/TQPFv+die(SGT&7;۩JbkRj;aD-eݟxNmz`Rol6=c=u]u)톏p]fy8ϲ̢P{(^`I`RKIWNHkn@XࠢPڐSn!?:Sǹw?\. Xf_*-Lpa].Eʥu,8@'dΈ ]E6`n3߃Di 6amKLڸAxSsf>)SUdvh;mg?|>K DXt$4 4 affxUOޞhGsl`k+MrPx:hEvj\>QJ 8fyaULWIx˺^ b6- *$ZaG^sxW΀ɋF*Ɂhrö;w@ Q o(TeUE*D6^_5)dvA-_į777޲,k:FQT;k{^ȥ`MōFj CI#"Pex<>==5M˴6f 8L&(Kf0YtJ@Ya<চ+>M(”ă#Gݏ9VrVe@&1-{&Dmf{q@+a6 2C~-W?nFfb>z=w!`ǾnPuc %< WiP* k$WkŋN*4)&-Jp`nj-gAo/^v!Cf(777Pk]ӣ4X9Cor/ײƈ=HV5,M$vlDZGibR(^)C.vT XHWn*QdaYm ض{l#sfUi7Pv*١%Fܾ&>quLeaLbkJ*Ix7N/..nnn_r:aj-0aCmu[X; 3\bM4g~B\Pe?xooE1Q4xkA?W YBC_l*D LUhR0 * 9c8Lk زYu(+a&OO ('}P3Ȇ(Ux)Z9ϮJJfhrtjŮfyvs}s ,P-+3tb}6ZiGz(luQt(?'w'􋶣]h9n}ߏ0JEahI:$:I VqgTgF-OVW5ϓ4MFgz),ےuMZm!E696=+ZsYB'%F҄TO,iV,QF*v*!]qm*OAM̫o LZXrnGȮ70V4ԅQ?ۃlp8$G$.bQ9OOO,KJP10 ^ӁI]t:LRPp聳s}} @ԝ|4 (KE'MFhj(e %-tpC;UV@@5QYEB e)d3'g]-EQ<}t:/tVtZ؉/ Jonn0u$I2 lv~~lܹ3`0ϓQR 58-B.gggqY0M-FӁ ðu:U:󎏏_xqqq!yjŚI7D.Y-s-@4+zf.TBkDJ&z]EpOraөbjT[1y!j2%*Ă xކNn Gkʼ;///?n6777zpC= {''Qu, "h4 JƵƁzPF|Z^Lۃ>j2 RuOoRI։,'v>7LcdDZ8od8fP1Al6-#5I?+g)-nLE2 #x%5%1pMiRnt )0c=ar-}u'IE`7+"/TqK@@1P<4ˊ"m]$ 6?ׅʤQA $7YN&/v XHQPp#'-{8y1C4viۖc;f&ƹ*5vqc54M$LX$ /AIaUj.']_n82ZLȚ~\z֧oj}y-Br*,o.JJ0&sBJo>8D26lY7 @x70>|I=H\6@mPer'xn#2y^{Qr]e 暆ޫ>?/~Oaj T0MeyQJJeEha[`FS&%j 豢y ef$ă38nEdF5* F_p8d-e(ƎAiNvU*٬^nlOƒT4P!tlۦ|>?==;44"ǮZkeAhA owtZB93Fgp& WɌV "&db3Bu燇`΃Jk<lt(%Z&-I5rxAs]!c@#a(0|5C^rsQJ;xfff |?;;FQ ~xcebh4+ƺk4vn9gYEh;?K~az@ܑe4>VQpf˵Jp]Tœ?ճHR٬\.={K}/6ZXgo\7"i>{ '!o8Hb|c=;ft:ͮ$]LYM}:pȲeR?/r¸S]B`U_4  n&tLtwmg|m-e[6*P44 U:kA@H*uLl4sPM4h~oun_JC/Y6k͢<۳^‹̓{(ܜe}E'''~׾-:Tv ( a^Ւ$m;+oPrqqqnvߗӞBd:;>;Isl_חHB[Ij4 nfWPigۣQ4.Ɖ2~Ht%XDCg:^__c˲ $R,Y5/_EE>8Duj^,|I )D+n \t:ٌঔI^4]N?O4-R~YrS]Jǵ@ɤƂրcP6"Pm6(Bis#'Cۮf[4u}4U2۸mlApd4@ϓD t\^l%, E z\RK>ǏM|J)OVWn2`2h( I>&w}}=L<~A fySֲZFNxRcP킟kl~q{\b^t憩=,<С<, e )^RkJXaTjT6Xt9Mx?~edb1N(}˲...% V*xwzzjHWQ߻wo4!-@,'/_mo|ׂ34 ˗?O͆eY+ KL)[F`45ڶ %_RҏkD/uƂ?wr5؀˾ :/O~_~/@Bp\vI_/y (P)ɉ]b[/ !IεZ $eh9(E`9#@rvfEe)KSkTe*6os!A"ejߛGCP/\ݸ5ɖ{ѨMT,Jo1#|oƦdN #Hq FKl/E 0lZ^r8`7넝 =2!ɝYaBpsssuueYփ߿tt$Qh#nAh^lCii/˿4o~rK%#6vh0jl;IhZ8OI}||< PF5QMŋPvA}Έf׿GioBb+`0l2t:{@rFnYvg61" f>h(4M ۣ Pq?x9 wܑVi  B%/5SŇz>$N1v.@|:󓓓AN&Da:+0BM#J2śPPPu9k _HeIP,_RS2+H/%CtTóe Ft84BډvG?p故)zYat nRp68 &GߢM~#S\8jM~&$Pv {K0r ~ot:]mY,\ Hm 9-ffe>Iɷx-I@8I[RHLQR4G ظVo#"qUl5 M(#9B[^D&P ,2잍Ƅ_յPnO#$L@& h\%qp,k$ |4l8 :L j|*p58`%I Ç>z@nbI@![50PJi?k'8 r4IlǾ;R P8lGI\KNCTnK"T*HF5* `6riԄ5Yx-ǝ;w/u:dD0gM(@vf$Fqxx<bEeY'{Lnvk]\\dY1N @u޽{d?|A`&IzDTwZL{˗/sYx2K |V~1AXVJS2ؕ!&@Xfٞ>dy(4CJh6-d|47Ms6ҡ K2Rq9|~i{[U(7CTc7pɐ B y,s˲[Z;b4 oh+kQa`y5!i:k[qfyȲ44K]A28&mB5]bz6x4%˚T멵h9F`ۺ+#q<27n冣jHTm꟒]qA@l]Eٌf`QJ{#&``i ,ZVߧ,6P1ޘxXifYej.бv=>|x=6`6R*i\պeH@vP$\ݘom˱unrLe#45 2 mUC^{XNDa``iB˄-caz"/R lw-RMm!̬[D-Wm`xL Z}0|y2ϋȲ{lre1O"הbv]7 ɜ؎H8yA^!22ʳܵQ(8Hq2M+}#!8@$Bh"bVQz@m:. 겑Jzj'&SRQTpr|J5\j&Oxy+F| ?@klAlV@:l6}7較/^^QAlR*SiHGGG@.=%ďy Z x,b@,rqq$IӁCϟ}Žmf3,ϊHdo4IGQ0XI"ZF2 F$<˲|wpVvQR,F#(  @)X,:\M-|\.ь`Y!Cov[iXa->rzHQ~#v;;}M_ 2{IVk<bCm jFMA6nćiXu]='k\"iWkDKrY2*Cuxw}}}qqqrV^OGx ¤:Ll;`0zب_җܹGGMi⮇L@5GLrU8#3B8$v4M4]/,KSuǩ0AЍ`'2e1 N>9BPkG66ku7n۰7ؘӖC ,v&IN ۼ9dL (C?*8UUA'?eF :&?%N˘ŋa#%Zk;qA|$u] 7+S#C0ple& \vTac+CGl._ Q**+Jm8ң,kD4M/Y8D\Uqr5QGG30#*EQGZ h,Ke;ˍ(Ay[ZjFu}*R:5r(,I,yOd|LA1ߝlTh< tgi$4@4siáAn-B;5M狫O>V}_k4JoZEHmpE@]M<<K!hB aڞ I7BlAizWZڔFz%<[ffell6ݻCx u]p 4(Z,hk0cєp$CCTt-X$nZPj5۶g?闾^l^a4s~8AH# cq8A oUjT@@5|k_{!J:5MH:D$ppԈfYǣѨnEM󋋋`-!O.0iE^^b^7 NOO(M:<`0888PJ~_dl:}My/ D2E@x7$}cC")l6vЄgObD(}DB-DZ,R!&p-ͮxhcZ:65ro`55"S/M\ S[ڌCp6e͇<[f Kuh>:QŅ(2M:ϟbEUy>N!b@HB3(T(;g< vCojaz{xhٓE(cR脭>sbb V625moO^f3u: f`M,ͺƺ1ӧ+%2HH_kybooCR,!r Tըjn[ou]U(Nnc.utZ/..^|yuu-ٺT ’$i[wh*N =[,`+F]Tnf+Pz]׭\qݚm;m1FUjtX <.%KHflAt=22Z,m?|Mdx-qy^%I e9~-Mj'wF1 ,P l+`n$oMˏ8d|(KvHaYJm@hS@i8.M,Ndd&/l?ϕR_YXf""Ul6c;" ?縣z@4"Hb-p=-oXê&Vdd}m[a ,aJ)5Lӟg@Fo! |"[@*A4F.K|~~~˗o-xク]nTnOX{\`/ !Ӈ,Dҷ Ka*(֐l6L.[驵:Fu]zQY' kAs!)›Ԗq w㵐\ 粃 =D%4z6EXd߻*IJ3ժmI2q26]ި5I.>F"?EQCR%nTy~bo7´,ojt]V4+p/!d# yo%ĖXXJ>PiGEw\w[ϟf3X 8M2$*RB9v0#;ʃ1b@~2 jtQӶ,)(,z=W$%ifX-Q~d HLA+ h>S)&V ZQt4bqH=0 F ~mBHgLJ %$ɯYTyLDPeZGL$KEyjlǕ ahzl};. J??~X P4].Sev]Ef =DR! 8WIrgHqmˇE~pXAXEOxQaMfIdR)fq FPFve8pe#n#ȭIr kmS'CA5IIvdߡ|J@=|v#0,aa 5 o+(( cBJ=5v.ovgHz9aHF1^%O3U(,,URظdm+uS_Wzxlj>o}||nѵĆ(݌ 0 0J^jS MiL&h}Gp .DB)`J4MǏ=z^/HEp8Dy P iG8MKUK &lYG} Rq<ϒ$ r 2E8CH1B4Qߑ )Z;`[,'1>q\,,ukI5ۍ` T>dZ.HA:-yT Se2fr+ W6ok_V_[kx2@^S8.hɜPa-۬.&X*rL={Nn*fG: V} ~UT4NA0bя~|gyo~W.~nIV&(nc& _{6 qj5ɢVOQMC d.\.;˱f1'7[mZ4jC֓ٱ IsЛ8h}&jg6a{$ As'\5gyr&s(oQ|xf:Nb>gY[Miݲ,ݼojoT%>oF&ICoJCI-tMK{I⵽ZMEu˓'n  D|R8˶m =Ԏ SnVv\Rl@CqZ_b}:?ۿPJM&ȅ*=#l4*#q<{ٓ'Oς l6%g `&Ju὚e|p}I irk 8Yf8ȅ2aa b.I{ IƐWAz-,vJ!Jtۅ6n<jחo057GAHZ; 8 ]\g~ׇ߬Q-FA,SӛBYUTVjT7rs:?Z>DA9P_:uqxxiwP- ۱ h4TBáWUĀ tw3djM!\[.~/ovv+PjYp8$Ay>HyȊ~t1ȚH?e'6c=F#d±B\.iezNXBtH"jlL-(;mapY\h\irppz%+|bm:fx9泭}!?I223JFP 2&⯤&U7Qm"Jotj @`K } KP}7 琿4,Ӣ==%$R\ ÇF# C%(^lt:t{몵mL&/_OO$/¦n,˺zc=R?l>فfykqeeES(r<ӋDkҪs\2 : ޓ0 LKwrQl9tEmT)kO(npjֲ4Jx<ˋ0 0T~+V(0\0?,IlQE5P(ifh4*J)6l3~ Aņd51C. lyH!d777iض:кv֢N;XWWWaxj5{>4H!uc#i%aidɫR0G2XtT&P"#sk12VK z˷d6M~ҕM uR[4w Xݜp1c嚿6iL/wPa7=@ < EaY3,r({I(6 nnn&BrAzfD;q\&HØ"'QYk P;EQRdvk hIɏ4E&gikH륲&{;u]v؏\"c|h4@7F^xUD;|>7|AAb@?DIbECe=Al6)le-)P\N P* 8Džʉc?e |:6R 06%#2J!qH/gSwmb+'1M {;hnC'uIDٸY|b\.2$ ҃J<1O3$i^V@4򉸎h4ySUhWaPjT`Ae>Q;lpj*ĕ`kCʴG2֔>#7 4gCaf(+'?DFJlPy*;jRYZIiFž[@18b\ж@M ,޽{onA9+,, `+;mJT,t:IIe0+"B똦fyقl!R_9CpKk.j@ (l4S[P6B ʫpgfɌiYvFh@*W dm8+,:5___caԉT9!=>j4`^aMaxqqy^ףጕhinvn5MSʲ _=ϟٟ٣G>d @/~QAxa8"󿼼N㟐 >eY~ _^^)A BƍesTa(F{{d:JaXj:y 0U<_.lEQ2o!D8QhV}=,IcT%->uN\dpV;ym7\Ѥ]ƭ@C̢| IPJ5 v _$Iru F5QߤDZa$vG$mj*d< WYO^YIjG@G`4.Տ$!b#2i'T)Atz}} xBŋ/O^`0ܲmsVjY6epyu*V_D%d24lҎyEz=/<ɋ0L#bC CcNWl| P%Fr'OөeI*PCČfjZZ,~/ta[eنa^||eXA/@cpV)`OUKd2tmAN<(iaY+yҺTJd>ah[20w@Fu*>@ ݧ0 UEvwwøl~w{}}oYhDOI]fq?x\+" ImZ-֘C!wn Yfv8 (VJG0IġJ%iml:v4+ ݞC[lա/" Z $ʎ$lk":"l(v$9??~ \o۬4nmmcSnYHf+||Z߆a\uJ뺭Vk4mbŜ Y[VBQj6 ƯzDQqx0F~(0 ۴Mʋ\ʱ=(T>䓗/_"PIy^yid:IBi Z-%:j4;DMj[FgDHPY z'? X[$GkLՐf3)ƚxssiN3/@\.ʭe]\\\\\PJƂl8 (jA4m횦֑.MDxӐvnƈSrv¶8Yx>, $l8CZ|DJݪԙRVc>nm@X \$ yjC0MӲ-=QK@ pRY|Gjv a$p8{TnZ,̀yje$ZʹNCf>Ga`]t:{϶'Of3ɬj6ʅԶmt1`.@T%nSo^T uJEXydx>,nj#L3_F$Z;VUFMO"6pv?(U%SZALF\CRE +f0 ww]C. Ji)(^wFQi h4T'"c-F 6\uKJ :/_ EFoC䜕ODzj X.0GBd=_̦iA3-/ T(&Pf99r<% kP(( rkdTD[,wc ?8,)*l  (@eH)Y#J2sYt ACmSP[(l]k@Yu 888F 1fig$z__Mhؖizqqo^Z"ǍFO?1n4jaI| `S `XA0 pdl0ūPaQ* ,KJm!Qv8kl+Mħd24Sk6Su4!?x+>|) txV2ؖoOmuKQhrM&тl: OxX~ 64*aFc8Ci)Z.''/$`%ըF5QըX@зKpBnFaVYYY7X_kXZEmv8lqI351d 0oJRAjlՕ۩Ƅh>߻wi g%8۶!ng.*"f2.b~zzJ" JʮR N;8 ˲ e X cjrs2,'(lGhǵqk²yV%D ggh! @AN Ҳ .o$5~D;_LTRdK@)e(e|Ӳ5q@DxdǾu:ãfI2F߿wQAfS»Qc6ǏɓkUl> 2?QTb9::j$|lZX䞠vxA,Xdh+WS,W'圓=tK]UQ9 NIR--;Rw-v Z]ފ2ύKǢ(& x5Ib"*TgF`oo^Vj)fՕ^jUըFT (߿oY~b,^O"صZ0|ˌN_\\ a08luxc|ttE0$Fe@ e,,MU@. dAxJLSs ՏvM 4f=ݮatvyqQ9m*^jZxFQZͲ̫+(06-3_Qҭ-ʲHRQo[lFvg DZyz,WV)=ZmaL@n] 4FjQ&|ʋ*甩5RM])\.N1a*A)7[<ˎ#ĠM',3 Ce[Z77QQ;ǏY%"Fպ~׻) EQ2uG V z8 ϢV#!l-sX[BXR$X_ ZC?L龉IZ֕P oE25P`c_^!c)ȥ%cYVHktU)/iW:%Ith;d$q'8<nО)$$@e{tYVLPa0=<<GWQծ={fێضeJy*(ըF5* 8<<|뭷Зl6 ; FlUidadL8Ss/;wEӟǏ~@`{veYy>N0ĵ(kjI,zgnF HlqgYe-wghuV [Fp(R4M2gF9,țАče.)??{L)uppl6}Y,^!EE(:eA:88 o*X`eQb$*YEnN2jCN%KUb "x$BR_#ll6Iį `nQ8ϩhY|Ȑ!D H Q-~rwdQ֔uK|`UP38`Y4Q}9d|R_ ~jC 7P)5NIAEmRV%NlUɎǔyKGA2~!X/٤E6X? كp8|>o4pc#=z30\e0 vu>"@QGGG'''sQ~ݾzVK^byqOS(*u * (5cqPGfE1B VB"M3CE؝fB (ef~j^N$vߞ\:zzЂkҕO>߿w^%-eUB4$Gv6"2s>ȟW|S)[@ޢzPh6x!J뫫( 62CDaTըF5* եc;( jADQX5 R!D6<ϯ`~WfAL&uFjBAz8>z^v{_1zM@`[t"f,% Q"I#xB|P@Q l,1d0 Xvi.< jZј4Wr"1P,ZVagJmj 0- wHtrxw(rh"t]JofزLVۡM;Z׮-˛0FRGTb90,c̔mYraL h[Gh|,J=0HZb/YUBP7j"nW\.o&($tZ1P]׽s`0^*4M \2r#5M'26x|ΝpWWWO>0LծXPnaB7MJiYʜueVD^!|:pO*52%7^veIF01H ءOd7_$Ǒ8F@mi7 xAw~8bԕ1h"e,@$f|+)k+C RDBܠ(JrmJi+Sm\.y>*IVn>6/`2[p]˲ (J$/fgYVUX5QjT@@5~Y8߇$ d²E J2C@|nRazi;3&|j!e@CMQLݥiԺ_eDJݡ(I'$ RXvK jJ4Dtgknde9(OPU!SZ.i*{nRv[yQyN0l6m-n'?7N |jVi+/疋ijڠD=l&'}MaZuq\XJ'_Ё"ʆf(X^~Vc/m s%{C=26}ܪ%7 C" ۲!1 $zEQ\\\h:1>0 qb[!@^Z,׾h`!0&%ee0ʋ"[O 38=HPhrHaP2MbK5Xwp ώs&#g8)5MhB*Ǖq=MZ!`08>>N,I[ 5)ikGsNٸ7o8L7O.rArrJ6e&)ChZ~jIZkf|¢(8n6wIL)pŪQjTnÇwt]PB܀d;:/1*s<;==i40TɘYG9a3*3 I8Im )fGV wp>XAIRػ|(4MuœeYz40|ߟLl`9XzA@XW~kfJȉ|X,Z`0 {B.ײלYml.n,Kh4zaFh> NM82Pi& d2UۘhS};iWɪo[-0 sP"b@l7jV!-T!q%:7Y@q֐qǸ<6X1PLLxƔ~7AdV F< QaUA妀@Z]*h%k$q%z 6AP2 4,A1[x<zzH]{VP8U$VjTP@5!w/_PybbQidX V2TmY @(l:5- h2ꛛXUG:0KV}\Ow],ki$%?unBz#'ty: _IFK'FfHuQJ2:h6՚@3Tَ;vd3Tق @ɌARc*$̟R԰47AвQ_HF:uؘtv$%-(.M1xiK$4Zzt:X96'@ńՃVR*%ckt>ftJĴ\~-@mM$M,3ʄ#@d'%rͦZ4p`HϨd2 GJ */H;O5QjT@@5~r<[p5B{P?;;;;;NCJiJB4QYF$Uw%Ȑ=A_ .k,a$>k0 _$6F`[_v(G[ID9DʆL 1 NǓiE;cz(wم޽{A'FUVjTP_~Χ~tT}j0 WVF(PUh@w|m8NrXܠLte2ӲTԱ/W"5vi]Lh8TJ![2z5m*E=NԚ.#Xi6w܁I^vdhD.ғ0 A72pnf!|4S@Z%Xa>īV5yp7(B}HHXN2c_<`vbګkQϧ*;+nڶnyyE27~͞PvR(0%}ߏj[4I<4Cp<<<<::B2@!RC2R<3,Ӳ0NqC,@|SC)H0K4X;@.X֠2,F@F[krk}j-иz!*"iYڤK^Gl\^^^9c(#s[ l5nzフldžacg V{XjTPjq/ˬc Rr4$_k6 ðVݽ{:ac jLQdp]nв8ǗLH5'wT(Vօ.I?ma\q\׏?  cY ʲ ]"%fYi!%/䫵eYna<Tsl4~z7PM5]P(;2Eڠ}ȭVҏ[N[umPxO+J,@ 4Mhϱ?8XmopdKSkD;+fL<7-+c۶4KJ 1;Q`p 9=b} ܽJa9{5|9/,ml!vvppt:Hi};`#an8V i.(pg"Uдŋ,y0{[vӊD*̫f #/Z MJ9*W|Lj~.y5kMtTACZRD։M,yo峐Dn T㬳aXrVeiYVb2,O۾ "˲V0I0 m wЯLPƚ*#N&zhQLo ZKX!o0CuN9Vl&uk>򴗚|Lnj֟"/R/Zը|VC6' ,+ ~ppp||t0! зl$ϳ45 2W$)ꉬ,9(R*v\Zȋb7 Hd5tm̍Kh4ρ]\.0X=hRfmn; }1PE( C% Q折Ul9AfN,K 0FnwRRk늦fO>9#x%k)=+QFnQF-6=}Ç4[vs`Y_:(TQdX$}ad||']|ɓ3*r`, FC8S47> 1bAqW@@5QjT@@5>X3 rя~eEF GpzADGQۭ2ZfoZvI?Ͳ4 G-ָ Lm-V Tj܏@>yᤝo5 li} 99K}aCFT]E$*5CkHYN"-MJJF 1`.Zܶ^ln xx]յm[d_z" 2!Uҧ֧@gm}1ωfi@Qv ոR`FH9vbLӿ^,_mc{q' HZzԊ uz777b>f3cQ-$ O3 xy^&iB 'ÝdZr9͊ߧ4dBYS$B>N N( %$Ib~eSMSBed9) 繞%}ÁOáeYfooܲVe.UcLW1s'ujZ݆ n,EytL.>0Mۤ<0X{Wψ 径75<в@3F5QjT@@5to՚]hzEϞ=;>>FZ >IrȞITY>xR_+E.k2*QV̈́92- C~Ȟ,˂ \.|>N;{]G4LTNY_ he L/fȪDs%FfIFeLV*'Yu:۶///ÇٹuVS@ qL 1xe;_Qk,C>X'g;<|>?===;?[YbXHjRjooo8u:v gH췔 ӏ=QjTP7iþ'4#/~>7}v;(PK;tM9=m5)|._]]aooVA5i!jQJa8nn'^vy$C('L@Y.K(H%WfL{lp-:dYL+ .5^8J36(mjX_pe;>>Fs,x$㽽(^hN劲йR&~)@&MPݓK^砮@>˛oDd^;=~-)=3DjK ]2ex(&RePR =$i17#4;Th.$a,g4hD2mk/1H ô,寚`\~{߃vmmziP^4-fJ:  s(,)s!5GH nˎd$j۫TDID# heق/M h _yI| ER*AooVnt:lP#_|m6o@0­R&6MXTmk#p6]]]]__lju]4 EYZݹsg4Dhx40%k_DjTըj|Ƒ$* TKߧhӧOzfLx ɢƸ Q,UɊ8OOOtzNert glF +afYV pJktδ[gc8i(=cey-mUx{mi!b>:I?Df)ѡ_sDzt xFݶmn;sD~aPvm)ANWseT*/r>Y1T;!g&LFD$D)h4Mznm0* oK1&2zMk0gYkI:(N\%#Jfv]y^Ght:c$<{\___]]AXEKqiPaՆa=Gjs??O_;?ejL<$ئiFnai(cEۆXF777qx< m!"΢.!E3  l(Mx oL$j=AP6^)a: 8XT&Z[aRMT,TūqgʀZ"1˲~PrSWHKW4M˴V`eN@iضSyf 0 Rq h:ro6m/0-ӊ(hA wC($lxk5QjT@@5jEs4M_xh///{xm6[h}}୷roC6 @ Zyޟ1 8t($I-+Wy+V> \Z` vdRb0f(Ɋ+$+$bN`_A ^/vPG,k$iC0\* l*Q}駍FaF#/2dt|WZLµ4O![.777b2aϐT'\fT`QZ.cquu,.8`[o&4W=*ǣ/B#ւѯT~5z6_-2:{-[a=>~4leO;uݻj5$W%+6쇌}x2z^q3h)Epz5P&+ e-?m #ll!4U7r_\\qclY*s\4TfP ycL+_5Z+NaE3#m e7@#'k|`Muj 'ᐲaȜeb5TdžYFVs`0(w/jZd\L(Qҗ¤32$V@4D^v2tlVD S`X_@@9mikYvga(C$K@`Ԁŋ;Fl6FӶ"B[A~ ⡘:9XeYYEey-@o4lñynGg /Jƻf2~m9K1BD50QڬnS@|Hp#&/`BCmV.5|\JF1_@PZB)&qYy/YU"˳<˓$)Pv:~4x‚#`V,oըF5Q*}fYȟ7׮81&Pن-06KrS/T=ϛAt)CJ|p䦲oY WBFZ'͠9 !ϋ?v˱:ckЖ||P4gH_1Tf~Ŗut;S.Eqyy_hmۦay&U2UݫjzwKp8D|>ǎ{0ed2iA bA$MM#Ygʲl #mh4D9m9HKVݴ|Xs vFQe樎g}Ƈnsj2.|@e&+ӍJeL0Ve[Y`M͗l4jZY2˲,h0)nfKS1eNݮ\QjT # VYVXVgضFNR DJL{@IL|KU}OIg!! "pap@xK{"ePx1{5*( BNOYk9|UI; ԫ;T}z}!9qtN9OI`](=kZv{bbbϞ=(=5W@) 'b(HHbp3'?E1&>iLfnwKhV"VqZm6BHΘe"Ytt}WJzIkhCGc(H!ڶm˲ oJ);e۩ur۶ Ţpv:ؘa)c},9<yᡀ_&b[/1ARc!c_ـL0)aKS-e9KBGm+( #Ls155e˖R5Hܦta֍~ :#Jv9rM7XMC1(q.H-3VFdcEJ266~SdӦ @`'BpWp.=)>y)*p~d2'u!X( @(IxTqWERjTq)gLr(cO~,ktӊpC4דY]X"0nt:mqHdy^+zF.\Jl HR5 DpPpJb$: CVzF!|)`]lES"R%EQE!*VS` lp΃y0qmmL6AnژbM%srrr֭P^᫁+"|L }(qu BX.°*DF4'jGWstG@(Eu0dm8U=U)e>/@&A y$J4)E02E(Z4uz,ģ9`3Fk<*,˚lÇ;fh daǎccc?O<JOM-6 9X Όv iAN'j,#af#b2L. 7:hR&xrCP~3o4fGzfa;vlbbʶUU0ϪxLLp@=_$@Tu "H6*t' `Pb@   M%u!3 =x *@+m>f]Qq޽KDҎ>Tb5nr qaA#sy.(.FǵZSSSx8D4J0x̣80 BJh֍$K".'2 @;XpQd20KL<,31NsJJX=av ik3 O`pVqAO2}zDl> F-A!9I9~ܐ1.= d 86/}cAh4Μ9Ө]&%kzzz||>bhȞ$+SPDQJvyw}q8噙 p]e@n㨥I3U8:5nOsrAeb-d8Y>W"ㅅZ>p3P* n Ijœ`*]t[*-@e^?EmHS@Tq+G@n&7 @Ft5' C:ynVof1^dy8MRDb mt}az^ @3b0 P.cH{Z{;􂞾Z466e˖|>w!u&RĤA:@+.5 n_3[^w,j$#f77jrQwsncBd&vI.:[˫qG@(ÁLbǂ)r959WW/ƥR >}~zzLJ\=ɟD* Ca HCQ '(Z2ZrzzqΘ4 yTLKyy㕱2LѠ @jtteЊCL ]Rf#vt$rSJ =s]cBz,ъSt0M Z( !ŧPL=$vz=ƙ}nשjl6GQtԩr\*rb|O+`58o9ްҙA }D  {J?nczsI8Sj{iAV|C%t]f))џu# -˚T*mMct.*ՀFQƜ~v-!޲eYi dJ RG˩G-u r AVx;0a#8v /IG[եNF49O?o߯}9$?,e[dBt*.)@ GRBh8 @R4?11;hQOmk[]z=϶,9gN@N:S,R*Y>7ׇ:iyCA;7i 5LNϨlƹJ =-`JvS,$g3)i^>IP P*tj N! +Vm6N:cǎr\,!_cFB\C7V9Y(cfWOӈ^l`^`. N QbP䧄;-g+!$P-CI)bQK+VWWnX䜻SSSSr)NmOtǧhy+PMiR̓4 +rH)ȧ4ÇXD(j*&0][ 7:LO:6sP_@cXKq3AEi%Gu8n; > =.ɡd> ͠p+EQO6jZ(fffH_ :qab:aHCr(zQ#ًyJFngiiqyet@zJ1@! .'%e@Tc?}TK|;ٞ=O񽹜x;'Dsi=l;jY[Hsn?H}5G}♎9!X8R= Rt8iB-PdQ>dՀ уN5Z n* ;@i0 Z!@hJ%y8 mcQ#CڐThLt̐v( aXaXV-˂Zʖ-[f4tu;6LR{<6G`Vx0mxT5@w ݳMTP3NlO  )Z5x[֭t655I49R F>x&:n UͰAL9:8&v:Aj^ 0$B@ SqqGqUtRq!bpYXXVkBb*O1d6ՅoRTXq1͋/#Zu撰 ZNJSC2x[ϮJ_q(?wRFhԥڔu!2Jn(F@(.cp維0qzuؤ| 4cP@)rUaiG? 0 Dn^w5β>˱nbV{\TZqh9?< °yEKsqiq5D/Qp14:=%uӄҶTʶlNq70 -T !mt:(Q`M;\.GTR)s ?`۝vRHa0 *J˸p뚖 1^4ayf%^աlUC6 #σQ9u]@TAvw<#fk651],F4m.m[@b=赆›(ʉ- ° |߇cǾo?Gj}WTJjHJŴEDZXgϰ(fӸ($hAGEn-ZsRJyp$%<}]^%h8 A np)0h'<5nj|T*U**r6["#_RX)c57BuT*JJ\,Wٶmܫv*vFQdVGt:]?Xb\F`YVek'؃w(sɘWųo,_܂mK)sQj\W𼣞w, [;7%+7$^g88 Ϸlam)X DQnffR p]Zfl6[(&)J|G7===66u *JQruEL&ja/0E^(‘p|n٩T6[c۔LS#To$75J}Ke҈\iR^G43lY&cٳ߳m[)9f34„Ԛӧu0@')} /@3f`a%D{0jZr=cnT(FAzN]Ap H1Lc]xamG[q.zV @Z:$`}.+sFQbACK8$ $GҶ$62mxa/K$fni`'H`hiAB18 fts EER u}?~#G+vE0( Rꪫ) 6A#m۶l)$\EJpsOw'n9*ڱ$@4%lWinɒu9 QS>9+A3>3AH v9ѿt;DۭjZj(0wcY8tA?CǠ ÐB28 = z+kg6}`ģX,RJR "9cѮ#U!6拇@Ei;B|P.'r zWb?/WʜY!R[q_nJ򒮺3?﫜BFٴ21Ӝ"R6y"#esspS9)Dzbb _`w(▥n?\/,,sMOOSApSYy^\YY1MR`P {UW] u9j֗#LJȅX#^Fh*Qhv۶m3 cqqs8v'Dv9WLX€ 9Pt]>ORJɬ"E:Az.{\$>}s<3 )^`hYuB~ӫ"tB].Xy D!:B:FJ'L BdCW>aYiÆ2i!S .(cPh>|q#G%BX3c,ę)7(MB* OZB݊:8z?#'Q*z{B?1heM7zCufZVNØ2Ƙ²|>_(lۢEݒ`P'1&dA@dܨ< g|} cWl^ ͉jU(`T*r9NhHHd=:{t޺.~z JiPFeYao> /|;ȹ!e4'Ms0JB# ޟigzL[˟I)[WRKcY;Xߟfo8R)ff.w~ouwgt…(!2&Ke='e>\ 2G߷oT2m^Ƕn]ߍg2z;,90  N=2 sK%ͨƃ  TE{H`fR Ӕan6t{HP.P]@  5 ׀Xt>;w}l@A&btBIf#Jڲ,XK|R .zA|Z)P5^wE{~~~~~~ii1v-@dn&W(Jg2Ó |--O 4 !!^N.}Ho@¸PA>ɀ8ߺ@a[[Zͦ㺜s0 E8vKbBYXHQk$).HPg B`}HFX1Q+k˲*8MɚS9qgH'za^/do>755|.EBo]I [ܹ[M) 5kLs\4cR[8=ngf+iNJ_oxӟzU?؏|]wy#NG}+@ #xM71#8c즛cWOb?SS3J|ؾ}lzZY~M9#67u$ o'+v1Rf$$a olnn_bϱ'e}#6`@|Wn Ԁdu]z󅅅ӧOW3g|8v}ȑv;vfO1\.`1aGgz`iSPP&}p@+Tz\q"n ubP0 Ȟ0 "J#I",iuEq b+Ŋ} B <t5K,hː=p_h=u1@/#iͮo\)0 oj|R'$uҎM(Nò b0LӜ) \'h0bb PAӌ !2%aRҮv%PƩF[1cssC ~!2BXCuPPr|fö!R2vײlv,w/׻k^fuɲl+cv.FQʒşꍵ٦i.o>!ι8ql'?"{qlxcޑ2+} bXI΍-v{k؉`nNͱn?ޝw'D[.fZ߽][oozSGvRQOBd'13?`$g0Fq>,UI| `7.!BŒztܨ-1V]Xj4x"(WǾ{n}&q kd7305Wxqt:(8;Nil ꌘ $t4 (g;(0hDԫA.@z L %ωF^N Bӆ/Bjѱn jGY\\|GϜ9}t>|ݻgff8t-S, 0 !HF4 GyF@*e|A$$-Iy%+! @s,Iu u(ua, !7G0x8vZ|䜧>)^TꪫRT( À-"IC'/SJQPfYױ\.G/EUϔD 0 0jZMRSSScccܱiEx7Q{|׉ 3Etfi$zCLajjjzz:ϓI0Ɏn}/}?߽G0ʖ5;7և>ԙ8v̿rPw̲f c\+Qi}C6x9[9y1Wbk?OD[̰2l͔R/K4χ2ͩTj7畋TC?Tq3_ KA,Ds{K_|Νu6N}1ԩ;kY[Msb-)nWQ.޹}{zB6k_>moB˶o7~gQZ`_#z;=ݛˉ3i)uo}ϟv9i 8Fa'@۵Ed_̾j@[fg%@1( e]gN8D4ΊzYt:ϯ3 ðLS 8c 2l6gY6u<7dYTՒBds9 |֋+4KŒiZV !el4LLL%LFh$T.Q-Jxnnd ;$]\2|4*^wx1pc C@{luRjeeYyi8Fc,QSOw4Аi\jn֭[ gjyNo}7`]711q̙={>dthiNM8:/b fY֧fMG7OTY5sڌq!RY1 ֺ\ki)lu˿ fm-gfҾ?Ϲ)edE4c}de)QFt(@j?+5)-[,KlR0&,kEO4g}MO'J;_2}nņ14èH3c~ ?>vl;{RiO-*Erv?G]I9d[P§j<S!J4OF@(. `ӧ `zw( h -,Į@O9u73 #t]*#28aJ)0j]GhE:%) !F㡇 `ǎeg2B0ɡ.%Bd2IȮOבғ]2>$q/WTתQRO\.J%ҙBlݺUJ.zEA0LB _'p \.Gd~BRؓVb}TBXuDBgR)mJ) 2k6Ѽ㎚aT2,q/U*T*@t:MQꬬ8K={dY@UM)ӏ<܊|?MsJ|4]4^_ZuN߬_(|زJܸ}q朗urʥ=7~\n-[׽mg,pnrpc{1Ź-e^F(k~w:S߱CmORtַ 78ԑqǎf2!$F4Ϝ0I.үVn=t9疽:QJazi|:gfNQW<]C4ei'N@ncbꫯr㐢XaFan˹5 vM8!߬Rҧi!BurOIA,j9㹮]ǩhE> , vb-,JJ*Qu`q=wZ;wY"!ai朚hQ"el4=pxq9D ab||q??k.d 8ϝ;c`mm vgcж䜣yj ?آLx 'kz(08 b C*[BoE` q`gq3k4$Ibqq4կ.///8ۙL&ϗe<>>^|>O ,aqlaĒ?3GUn+7xq ,+3J}0@3]9!jI$@F m?7$q{>8LOŀ(hnX p$>(%ٳ+++\[AOU8|WV6b4uB.aSDZcXs*8ZmyyYJf@ Bgv0pBbD x? Ԟ={JR&)JHu %\=3L.kh~8)7[!_SSY8v ׾$p5(z8Jw zh1!DJ G>iߏ>qw9B؜_& _[.l{۲%OSv;9w=' 1F_^-7ڕ z{_'8DQ++ROx!/D~#C}K/+19|Ν({̹)eiO?|?ϜSSSȟ8dZlUP,M@f1Ar^״Kӎ8ݮifseeL& !7\VG-,8^ZZRJ>}zqq1iz}kC!vɶv}YQԡ93ȹ0&p]waaӟؾRr98q\Tv޽"Dp`R[㘦nl (k͊;6Cb+?{q&,'B1FIi7ޗ䌋ybS _oS׆5ԩSbG~G`nIxUhNtM z0''tų0LXP$W aD$X PF `(0 *?6 uaC?Cbd8޹s'X]]}8;{l)rKe:#ڎ6+ShUUưBGrD#p!M0F׳,kee6zfswY鴌EN^w]XZ==CG;ivw}Ƣ+7tΝ8v]P{ !KB_w|WKʂaT@˿ O^uU[(eb_򩷾3r\Wsnrn0&/Yo ~;?\FAt]uUR/,zG aqn]>ͨV[nc\{n`>1i|e8"067>vv=;'.4'x# `Ok5ϱ;YlVEK_b_ZRs箻믿~eeeiisa;lْfDހ LBQ9#Eh6Po:~8ҽ{btمsgϑ1zŌxA8-= w F<~]۲r\:AnՊ8rYlyCϺ35F Z5|Cs(e<?D:[;-kumuyyP,9VBT**$j  A Ξ={>Cs nD./////z=Y}0’ K#UJ8G_I/yK݋SDT*ڲeݻ{1ߥzq8㌏ !.t$khJ%XOס޳Ջ܊XN_ 4((@Gf0ǧ=zތ1 $oO?:ѣGxmݶyLNٞ9]'d()/"w]Wč8kH Emѡ駔3H!|4;1ï3F=t3° kGO< lѣ:t܁l6y^>G7޲,!! θϋ8} *4Y4LTKP4:|ȶ~fs׮]B0Nݻu݇~xv]R"T}/ =s]w۶mtHvCB|vhKh3\0| NBI D,-//ZqUb(-E:;B[]]rB E5<=)-uP `} ?&%YG()g Xu}i\߿TDjرcNj8v| 0Pqp@2ZT(+7-c9ҥ)f49B \Ǐg9Hy]A ',h^kl.֘f3;;K(v[FFnTl3>˹¼R*b,f{Ϳ(z<)CoeC (rnjiSMs"PBh0<ǮRСwBXR,kiNJ \ȹ%eɜ"y4&0"`MR^m[*ıI?S?޵E0lFm )?cBd,pn]j1 )KݶIY_8+N%>8L}zAG MsB4c~r?iN ޷O^#wo9طLTΫr98nM(Z֔ŧyNF@(Ɉj4>r95=_ v]j.@n{IFgjC#ܜ 0[9rfffrrlHd=;~x?+TlϟGSe˖-b1f[6ȟ7%>9\0tҥ;G,\hS,͢ $\64ʁ$ǵ@' g1}6;}Za5v\) v:}tZ},rY$d2axᵵՙ4֫p]W^={|Vnya,`4 ,otпX=[3hз@][^FP$6.7ЛLG13FZ][[{\Z:(ˁ#p[o8 7 9 Bifh5X\KtWġeGum>.IĭK`q HOO;9f NCTJgt>WVVr޽{ǃ u]S0O :D{:'8diџzиȏ^WՎ=q'"p@uWK8b1Jkbnܶ-[mS5P۶ә ?ƌ=]Auc1 ;vE-1)s!SK)s9!eI_]sahcW)Oh3Gv'ma91\bΪ)Ӝ4.8 sGJ ȹ8u*N@*-?8vp._!s]ṀJ%}kzTʬi[֖d`Xc1y6nLvYI`r!2'Nr9\NFQ+]JE\#\~ߝ|e>җNp.ƄaT=t0'jessY}^?߂lԻFqҨC@ Dl&8ۺmkE޺2PSH'PKWF othR j+Q e&_RmnZTFuxԺiaHzn0X>ƽߘwj̔bHO{ι0L) o0@ (Η+zkYV& ðlrs>ZX,U*XXD%Ƀh%u]8 8l/|aǹ\nuuțFL0zu3%7lszWgpmJdWt 9ܲϛ)ⱊþEǝNVj5}n8o߾gϞjL&~9}u]W. $&2!{2Cgi,]ctTc۷/͂sA0 ] V880tXJ&''!E0 p!hQ ar΋"f& IP>hnV3MsfffzzT*fMŠ=m@7Xw^"%Pͦ~K֐ۖ/ZqIes`3c%/v?Q*c_p3 v4-kk4z꧅s)e4cO֭f@cc1ϳAn ^0GFxűc5֯\cCvs!D0*Q"kk}ps64y@Q4Q;s^"097cO6k^#~wAhwBRLsoC,0'E?Er_w`pgS<#b(cccBZu׏OLۼtJ>છ?<; jN&Njݥ.:鄁O}BD(> 7[HSU1԰#`P2GIQ\bjxGxݻwo߾P(BtdzG䬮_B 0di( Dzlz|\T+J:!r-G=vh0,sjj DݻwB,--󦫻fϼH Qa_$}2pb =$zL/,!)|jal?8^'EF$*Ftğ(V (L&fou 42x>QGP`g`MX@%z'pZ-tuA: @%PDtZBT*4&@&333ccckkk+++P0R[-]nrRQ |Gbit:xvptv8NZ=ydQtKHp0o])Ữ8ST^)w W@ \\.G+ sgI_LJ1ju{+cG&sЮ:@&2gS14Q^g)_Ij1/H\8FQSnK{9͹!e0XZ:Հy·7kAxr32mIR_|M>=^ro8)GA5I(_ɑ0re%da-S8lNط:TbV/xfNVfuQ?2(9UAE"4~4(F&J! &pu ͠sȔQ^"ԍ^aA8ta ?OA@PBtB]ǎzBNxA eZ%`}8R&q]5LOOOϟ={6S6 \.|e2^ . , *(=#U?]#8T*%) 5/gmbzEi{^ѨjMNNsNlU|Ri6Ν;{0꫱4[A&8tQJ1bS8,NgYHv:b. _k|ˬkFm&mHBN>q@y^XtFQJ&'', A"$\YvqBN]3ӆRRRd=&&uC8Cnwɮ\Ob_KI$u¦#ŷޚ|w~t;W_c.i/2n}/nwm.f/V@@vFT0.qέ%57~t:aAFF@(.;ٖ-g8Fqyzȑ#Gр2aP:j)CdZV,> !;N8}8%7)PAiiրi k.={&zIdǏ0/f|ˇƋkUB ̟T)ۖ@ܐR|>nՂ X[[CK6mΜY8{l:n4HǙFogffpK /!L@mJJ VVOL*10XSRjmC'b20?,0 Sq*QQG$>r}3 T*%/uևz(NOLL\B1'!U(tFn# M[Dp'DF_ rbDbHa! X“ommZ]k6aMMMr9X2J:ޱcGӑRb)^\\lZ[lV-=mJKև{=ꙫXQp, !L%8¥kUgffoߎ Ph CPMLjă۩p[y*$Wa@& l6K&2Bvgϲ/w(2+^<#`ll/Olp.8EL֯޿˖R޾}IKsc1qlϞKu{߫k)GQԾ暩ğF0\@)b/9/P3+u:UOkWZJV|9N]]ay֧2? xW*:T*B4<ؿM_'۷+8u]OJ11oL?TzheN<VXЅ&_^=Uh ܖ-[:rYo?zEgΜiQ]0`I' 4jURR)[O^/NoݺT*tFs_JrL&:B5ۥΝCuv@Ʀ.0APէ[|_R[D O0n$>(Ʀgžm^w1 F[Bhl??/,,? []]]]]m\.wL&C]uUB0!R= 4Il,/c{ٱc]FqeZ=zmB0==I j̘ѽbٳ BH\ҐL4Nu/`#,hKH"AEI slFbץuK*sMdeMLLvnzӁZF8m 9HQ R~@VjJ)'Y@NLtǎV {Eq(* {i6 Ha}'J4ѷ'͙x)qrLĄiOm`m 8*^(>nU0xto&܌ԭojՁ (&8VKKKjsowyw-_ɹӧO?c۷oL:-)9SL)%&F '7D3%^RmжEaI5 ?i@@oF `˖-[nK<%k̖->j+Ν;i[.]BvH^c{ z4Ba83Ƙ...g lt@ĥOl!5YfVH(soHc MtNݲ,; \.W**JT".D<p tN_F\ꕯqJvCC:ε9[Bd8?s6t:yϛ ?Mm!O8>`Q6K%:Dhpeoι\2&g.n!woðc\@l "ތ nR)3r#v }cSSO?{9Je# `P8cM7T<߯A B#J9&*⠵[|#5-e-,,jUNA`\D3P(`_f}j3te˶B4|ߧd1R;"+9U4 V3 #e2vt {A/0 p2L z>N͖e:cfT\6,Զ CAnPXZZZ]]o4HyqB߱cRj~~KKKe堰@j L/uAj'TcZ3?SM|:?+hx" N2@.bRƲ'OZ^J`eeV/}ibwȑ#a 'O\OLL@As(Jp Л ū8VKhbKD5o-g.,#6O s*،u@U~3cnh49ڱcǖ-[KB]k.]l6RSSS@ ػoTۥI2`T)(s8`XEq"?NRl޻w/4QWkA t@L hD0iZ Ef`W3z=֟b0ZM7-S*rw_dY{˷EуQ6ogo,crXe>d:RɼM[H o67lar2˹ac5+e R\,þs97RHp?`xEˉɺ11p%A[J%>f|JG8gy6tbЍO]&cs1yYhKСխ[ 8}h߿kR4Uށ# `O#~X"d _?rR3VFfoV:F M<08FDzqHԲ,keeSQu) @2M~tt"RTFQ43=3??_'''#wDL FjN A=LV$m^cCeY0Qwnj;縮10_&D8hP˝XJ3Q +Az=A/KR;vH)jT@Rjǎ;w4 cyyG!JHH;\-i7HeHTvq:]Yo27\HċqWf"dmmVI)!'Od]wu^{S;6Z"zsiY[ggm_YYy B.UJu]خ](KD< 5;R<0 $wЍɺΥu既LS&ٿ?+z%1/q La kV6^`c(8:/0Zc3燃HWTJ6EOf% ^ђKs IݳFgRп:j5bll B|'u0 AvsU+ư,z~ ~V5w7aLpnoƟ6Fmw)so7J ;֒Jo擟liV{Bz3tvnm/{ls0ղfCdnSgRv st'?wR*`,-?f"/f`3ox՗ k~x]Ms dwD?^~*I)y˦a92Fq":|X߳_eV(;jr0ճP )>~˲\׳mkzz&NK)+ÇϜ9X,Jm:Wg!sv+鵟>Yp p\^ 1NwzzAaȃo4@"H{-t4LF=v vөժkFX,73ƙUB lte%,˂*aȩT* #v;}E!D>zzX,i!q333H{ީS `YcdCAnNLAeNַ3Xt3,ۛP!w}u!qQ&x1h4xG}Qd,~B)j6RhXLD aA4SHYy@d2L`r9۶a ~3Z*`yՌ+do~U _uϯJ<5.~;{뇼1կ\ 11! C^m o 80~问'90O\J_3 t!lӜ9cWg0 Q+CnuRDQ')702s }vX<ث߽o^㞔1WzȋO3&878.Y:z]==mo;#eQ|d_xRR?җTHxt݇zȲnיΝ;zCg~T:W\ףFb ri=AЫj( V4`ڷo4J:E(d(E |⁃#z>_k ,JL41K) i[\D,Rbs^:/$ǖex^oii9 C0]׍{^.5M<׃mmt1V*cj4L8|:@|"϶,몫V-'~{v;5qaj54"}`h> ~|{G1mDP8ؖ=959mX,B+.YcLL% b!Jq1t:P` A3:_>?c>QOWZ c)-e:QCQΥak^ 0}wgK4F#>t3.X>2?%وLQz؉'oB!elR) b@,/af966妦&^Ezi )o'6H _%r1+<`}*vM Eء76&ph4P p߾}+e@bs RtK}H8"WI}|z~E_3<JBcq*\d3-[S蒍DЫzz$c`1T (l&]Z }5mGGRȐƮ]l^YY,u}Wi:M**On\W .Ǐw\'>CS1띺ul)aPmc&"i5n[(8>T PSz=111155a$N'ˍDaEƹϜ9sqxhe2|>_(H(afh<,o%*Ӹ!C^s>H)oS % RJWVVcjZtP(jԐΤuW΄'n n!𲊣#OTݛ*L T;O az>S9eR68I>.Ἑ @ܫ^/{8?4\6733S*pǒi,PcG4"&PRI N3 .|pu𰻮K招 0K: ?YyMbdÏ~ 0)Ӝ23:^A²ľ}}F];n91tǑ#즛ᆉn{l—UU?+_mzcQ._ժ \0*2m$ }(熔y41yɏ~a~-ܔ`QW7^}m0 ߈EϳrYdApa@L&CTna Ew*5r11Y5%lHpNvH1FjU|3ַfhK?GC;!4zv > zc P2h4 8"!(eGK`u+z[[:Nղm{nnN)~5u{X_UWVV ޽{1dF\1,V8@"b0l6|D0Iʦ[{~$t:I)r,JD J FBy, hZNԱ'ҎUJ@3hl(nZ3atmؿSB\K_/NO6Zu>ouA)39i۳Rmθ2SO?Ll>]";ùa%Ʈ};ޡ>uav&ݡ8h.?syM>+U:1%%Q<2m?b^|g>{FK{D·yU,B4M,D;+^&H_ÆQ|f&G@(.]|/Um?ٳ#R3\׽;w2R4cLulRy%f>=={̠ErJ#{C쬔އ- I'9ZHBwbڬ2]AnRT*t:}CaPӘmzӶ|>zNP(yQ*fs[]@gA/y\s w B)(#A^4mr\6v_bBa\uounu$s]L‰ rr0Pޣ"zOA?4u^o6V !PF׃E8gΜmk}߸m6(ޑY{EHQUI@*:۶HLxu:F"TE {uu5N΂-aFNJ{l, XDڂ~5V<4d\c;wœ No\Ń3P-lCgl[ĘՕsllspjr\.c\z_`Ovqq@ 3NR^s͵ifY0P;v}1nYXDY$d>:xcTѝcu^B y\A"ClVfɔzGy~yfXN>fggg7z=ŅBwQiJiQTZFBx6ō}ѽ"a% 0*Xa UӧOONN2(g hRg@ ZdBHb  e{a2>) (Jb.dBB#RL&C*=ٵ$p@G׫V"uvfPg=/g)i4M@.mݺk^?~lkF\ᧄ 2_^|d׏=a_Zu|po,BYӜm5w_kε/8@i7a_^ڹp=w}[YΥa9߀=$\^(3qGCqCѻz|n^:ށ?E۷s] `K;T=p~yA7<ĉ?;ܔ2gYӶ0˲ً ͦϹ|{<|8|ӛ]uUiiӬVӜ(yɎu㦙4 0&ƸaQq`߾$ abs1_g{Gqf?c϶og2o6ٛ(t@KE/zQ6Eo ">!G yʕWVV 03Rj_K): S )SDڛWP9L2M*Uɓ'F>OF^փ`9V°ۿ}Lsp NlߋĊsSʌӜ0)(_D٩oN"70*ԞTjq{A ;wim,/w?o"m.B0fFme2fGtoTqߜcX_3&F0~7gW97-kK6{iNi[oM_>7tmoL)cϼ<~̧R}Qdtzwk߁cn~ %)(in1 )3xy5° ?5eM6Ÿ曳岅=qࡇ?1&Șem-\%?"׾֝iFQɵz1~˚FE(r|x.(>ݸ뮃[9\eY[/yͲ׾[Vc}jղr\R72 덀Q\ 1f|3lve2ϖFHs4ك|>h4Ll9IS*WѪB6=_VME}3 LX,HAC=1i%6K4@XXF_XXX[[m4!|N!|:Ft[zBb˴QzAAudbR& =x.Hf{Ho|qq̙3KKKVZq \.} )+-[LNNL$n[V:NXbյtpSZFN}۶NrzzzllZ&7*)S⁊رcN{npdP" 'N8s̮]nT*y(>h@Sjݔ<IqNg0 @ժV`J)q.rSSSccc8tҤ8;( ?/ dV+۷ook;.=ގCvaFQzDL&gKD|߷m\!ONN)IFwc ȉF°E8 P7綔9(I9fE!2BXI$V* z7-DZi+!R1ns9DnqQjEQ3]b͂s.3F0Ms\VQ3Bu8B-km0~#pZkQQTĘӬ攔0lzg° 4S݆QG|SjPL~_\:}97/] 5z `Y,kVK+ 0\  *F)Y1y]_TȫGQ{QeL 2iƹчҪqծ"eeӜ6I)sQ].a NzKJy"kY3='eQ ð`9]nDeƘmog0 %Z>al۶Meaϵ@B8\'''cqv*Ч0p(2T] 0"/5gffc^|D,H 4c(bЇ&h$z6 narrѣv*b|.GhlceRJB0RکT&@jev0:]R(t.&N 8wҶmj-[FGCx[UTr*fBU7V'  oœ'O\w}f&Jvu 7[,u%yRx'I|k_H%(&gzi/~c+++gU*,2Xh 'UUjdY<(D06Qn{'NLOOJEl&(vk>C*4T,i{}&vXW__"UExX O.KB3u@}2Kw`'OX:jZ\@,N ]&%qfQ*oUGlvϞ= 5t:v &'ǺѣG^lN< g.? D0؍c/8vfg i<Ź9Q|aL(婾o" KzE"kqDQWZ?${K;FB-kVB; 0%ezF !rQ4]Ub疔9) R D*ehuC,h.(/]" csiJI!R6/q !9E8ƱFBdH Nj.f/jr!2UB#HVCܢ!dxemo38 2/e.q#q. NӜc 0}B1Fqi"W[o orV 1H5 2W*neB)$F[ ZT*=>>()s^בR@\ Z>tt:M<^G[N׿AbԴ1ض]T@`B9sf~t_@䐗f+ `գO1 s!< =mbКsig2f_m&lrlCg*p9yeY{ay )}w^nJJenn Hxh;h6s֭[_,t\\\tlScػw^"|V_0"qH툕CZ<`m2xHL%4QP35Xbۻw/i2+bJ(C*~b"ԇjIXV8New^$єv]}lypN妧'&&fgguY'{O@Y[ZvZEfkqqѶ*G3BXB =v/OZP2T5<1qѰtc%e}Fmn0 Rf ˘|xRoY*pnrnHBZVOƩ^5CBrs)KOTsM) !҆Q〱PR 's/f/jP6 RA 2ʋ8D\b7,I1^ގQ_@ F5'QS/N#IMA@u5~Jlr5|_wx3!d fKm* Q1vvu]۶ nttPPL5ZT$?J9s̙3߯ݻjt{w󝋿Q"c\.j믿1yXK.̙3wwȑ'vS?۾}nbbryffP>OzUW]EK(Qi`f/9@BP )}S<`Σ<6 YmZPuS_,`Ms>>>>>>~ 7SD4E\:^CA^!~pb|nX, ,xA [y 5m6OJd,5O?i) P]T13, 7o+Уl~'{ptR{/2]^QBqO=#Jb+>p=J0Kѣʿ βYU//V)zF>rd޻nۉuyt)ZoT;wmX*Z%`ٖt4LBT* p]ϲ :NA56S ֵQI $NfXڽ{OVl-FO84VVV!b-T0 ,";LQJa[ftȉ[mJ;w---:us=3zFqIV_u>ӛޔJvmFqӎ|[ߺOϟ>=OMM]uUCNHolllbbd<cRA5K'xBQ/YԱQq!(\hbقWoUBݻw_s5R s _/ujuZEIs 4cD@}ovvv=sl@\8B( `cuBdz^Z'NI9-/IM{˯/Iy }̲\%=]Y|2~nl~I퇎Q\R,7~jjn7SKK`z_}W{ߵ{W6ea0@ܼa* 4-t:fݎ06OczO,JyY(e}Dw][4B:.̺jq9y0ިjkYV?/O\+`}Qn#GX:f}:VZhtVs܋^t:/|aԙų ^?s4_B`}m4Ci 1~zȸ'9@&zo'*[H#QKƘu|4|>>>. p;I[F~twrrrm )%qOMMYez`|é(!%5M:u 'yTquC~y y.`x?*g:??)뱑jv8~'|&%0dR0LZC2$ǤHi^ P4VHaIH^ b!C-zP1@:  x 15萠2Ї&T뮳m _eգ3AdgK)[P.ItC/Go'=Ofqыt=+-d8 i<^ 1t A,@ P}P.~y)rwvSJ0 %4 (,=zÇsηmˤ3aBf}9G9}׽MM ;ž (F@(?|eɰlVW=)GX(;sJrchwDZ:oѳnV}DbGz0NZ،A@|B F/O)0pXI1D\ssۣ(lZϫt\E<ϻ11K*O_8YY_C~q!RR^Qb7U̶Տ;s; `#ۣ_*@d2탢B00`7:#nܹsq^QԷʨ`^YPճAzh6 ve8Xx/dN0t>‡TjeespEQTTlh Hj<*j'"yN^ zJL u!ML[S_ aKY4d>QQ|?D;[lMM7Y}k_SA6bhpZ~4ͽr9۲qR; t:s 蝞\7Τi})D uFrn$ڴzEz}aaann.J[8jdL1˴(s\>NAbGfl۩[WWWϞ;硉E4ZDVMBH}PSL :$pw !q|/ۤy 8R&LiF Q@nnEC???PQ!Q,|:JhB+ϨaDZ8Vn>}nKT 1?=ѭ5b 0JJ !,!Q2? ( heYf߯{=۷<F1$\=~4r4 ۶SaJA`f atzjjX,dN8a\h0azd("=5˂ez‡ )o1YV*BR_l[`խK$0n1`Ri cnZfRiy߳gRG}ᇱnQ<s!DβLƕ&&b( G;mfl6k>B ,b0|g>;qٳg&-Nٶ!DT߈ֺas({UZN0ll_TidZmyyyjjʶm;ĝi J)4IX*BK Nc]ĉ4TV9ډP1 XE?#7G(FqG4G1uFe||]S]G2]*(>{lTBw5ޅ ~{:q@s]׍\.à FhwUi\.g6:(-Gh6낝 .0}:j)~A텅ѽ4Q\8Cl |TAQr:n4덍߿_d|m}c^T*MNNҐT*e 4S2' Ŗ4 n3t*qu mx0|2~;T841P@v<lNRp8Hf:=@$:(D* V:3F1 gA,kee< T*'>\%}' @2 `zzzvv2^es}@EQEL/u/] (ը7lŒz^ ߴ~|Gw(F1QQb=G?mi?c'GgC4'|ȑ#eٽ{0NZ04`\Mh6B|"7A2 ĿcN<)eP@ 2M?7| ԩKRLptuBf?0 #cǎnQ"<VYv^'Rd㑧E >g"DYmP,2gff łio B@FPVV˶T*EzuB~4zch(F@(Fa_煷ʤd/o{?f'OLF񤰀lyP2lь8nƣh%X8#q S)`V u]HĈ F1RyrwHjalxd/@n?EQbB)NP0vVh4Mz$ rW]u!=1O-}= dI^HP"i|2,bEQ?8c:#jUNN3tf8NPغukXܷo^n]9utB(F1F1d13r΄`kޠ=S#,`/C=T{n?>zphll<]׍`'=Ⱦ%2Y; @r4}"d`@]cAo6{U}Y:toqVWWc,[l6~л!\}G(F1QQBh|뼀)w/2C#,`w/<o)2%$Ӻ@,KV`\jB rRuH_~H8Ra50!2w YWcrBPe/ȍBc+a\>(߮ Y);dl6+#{eYugϞ]SS8y|^P{BP(!@8}!?DCX56B.'28f+x):t0%Kjؾ_U6 ÷XY,9,\uuuӦMu]!dο/BUÅ_p!\!1AuĄ_&@ܹsǎjH(I8/g'TfTj~\/q1@qPYt}!O4_DSZZ(yUUU2)/J .Ca$E*mi<FBP(!@xZ?Τ҂sqP*I[ *)Ŷ]v%K|/-X3f---f3ms`ƌ3gv.GcXkR7BY&@: r;9B \p1Bi~n ]F2Yk2Av#S(l)h4ZYjdJ:+z~kQ9c,8̓fu= "$򖯜X.CdҾߒ($?]>HӺ֚ T*/,ZIr"'Om}}}WVI BO | Ơ ?ab7Eo/LFJ P$S~ΝBK?l^ft_{a]]]($H$,=Mӄb1&k`߱,&m4[N%B}Ap &O ~n` Bev1H? BP(B~.Fe9e_ѕ *<0˜J!<\6>`y|Zfg^i~1Q9J%?11fSBH 777<" "RR B  řoڄ>IO0ƀ1kX >9l?%(Nuw܉Z1/&oW6Y[1*GtO)ᵲw , KvI-@F!@p1ynT* yX"!X,J_^zҥKxTB!s/@L`P92@`>>11*7CZ,8R B  _׿8/kVX|Fi۷Z@T+ZK!'e)A( R_ ˠY,!Wdį+>< ew <1YlN.A -cRIv8m;g2ՙq˲,:|ѣG0P(^###6l?~4K<;~$}Y, '[`eATg* U(cdlh7#(Oض],L&H&PXlkkkii3<߿m65H B  ř'>Aq55x1r TU瞃AB P4[… *mˌ_iKkGʥu6ȅ16M3Ha%ʗ\WA% ~@VE.y]r\*rOV 2 B}}cccj (/T*uӧKNNBdSq^2PHzY# B0@!\.P)CQ/og5*THQ[e\%,$3׉|>LrP(455P姐GJG9r! BLk~Lp]k3A0>^ *4@qڃ;vtuui&m2_J䚿@ Ȉac۶I+r;@ P^Z"8s8~?fĄT2Ç c" \Hj*/t:g!Dmm8aE?\ȟdte>BHjX`y  A0'0 d"f /U>[wo۶dXQ⅜[L4ZUUF R@dt:-twwR B  şop뭼T:6 >9XwoJ P̙3G.I[9˖0 I~_[2-߶푑hii !(x:ǧcc@TJlvddD.uww Lt]W$\8w) LV} ˦P(ٳ'VUUiʞ#e~t/+idqECF\d PR$d2YY&{"?b8>>y^Pضm BOGmCU\uX~c_C.'s8l޸q#ƸC4E/3r@\9qepy;<<,h4J)]z!rNi0.^/rtzddDV+ ei:\ SeC]e}}}B'R,m-Zd8y^6- iB!0d"evp'ojBBM9!0H+WR }?s$ف϶퉉T*%KVcY&+2dST}}}gK 1Ɔe/(s^ѶF T M==P*A] ]G?xQ֯__]]DdD@>ۥ[ a\`0({ cA~|dx,[&&&ljFrQi4EiZ0H`0o\J @E+#@KKؘ788Djkk+w$X#{f;11166 eizc널cwd2V(###>l$)JaR،mmmH$2<<|H$(+JNc圀Q9!1R B0Θ@0Fap *JQ@,2Lfllq1I8„i,'BRۛJ֭[wDk.8Pq 4` 3TdWTum(O4 -G]qk"¯nOH&fnYjj렣{L[@*%@RPrt%7n(X047^-WiX&k lݺG&P__&Cp;={g'qddUJ!Ķm۲?|X㛐)38<D"H"ljj@#=}L&t:ePᜋ\.0D`J50`1Rw5Ff%P cLlvbb"R˚~5 t=ϻd[#8P1V)z17U86<(EniMM +Q| _Pߎ⼽/M3|*^9+!@qiw݅ׯǟw]Cm-jmڲ۶A.Ţ/<+5@Bڵk1ϧfY0dV 墙 u+{_.\(+x;i~p'R,|>l67wBJAdcis~`h~KBq_z y677jrQݯ*"Wb6M$נy~~eP9` `BX+(g D8 Gvo/_5&qjtX]+~u+X2OE%ƤpB4#H8{i˹ҏqV~)o?J!,keYo]e9sRC]P(2_ y_(֮]{EIs0 ysmBr2GNʀ Bk4%]M :8MI&K×.}&!kS:yz~\8B(ϧѧzʲs+ ]u0S'~k9O2Ty? DQr5xJ ۆLfÞ^^5PP}? DJՁ@ a;8 }?˿+WL֢dRZ.W_6n,?'(ຮ.|>iLs~-i(W=+ڕe)e]gZ_b1Ɍ`,X044t=s"W(% 5k.b d t][H@Q,+ʕy oI8]REe9@/ !4111<<秔w~«{fT8Y`8p)ƭloW<פ\{-YbTp>0<7jij})iia`ЏbS˿r-[DYT[ ,[jjbZ xlNӹ\. 'ct_-L),8NooѣGDccc4,+Fccc2:cIxjk֬u}…Ͳ,> 'qDHG] r)w3u=M^H~)&JB H4 ɯQ꫙YEu:#g @}^<|!<;Es,e_+!RL.%iH}=)`x8Wd]] _A2L;@ v]p]#oZ\S#,AgÇŦM [~R <-|>i"Wc+*%vsi͛7gsXBqn`;v9s3d'Y_&_F,2Dh|||||& 0~Yi2"mۏ,kbbX,J|>dАkkk[[[e𧜲4M0Dž & c _V;]k'w |q+l*eB<*.[F::hS>7uv﹇NF}w EŬt PsH/ŊNZKъb.H$`:12e_TL]UU][[gY%R 'PمE.CoySJfdcxxP(u|>,KT*ٳfQ( BP(ݍ@ v 93Ⱥ͕%"Lf||:=%)CYNƄLfXLX,VYVln?cW3儦 g 2T; (ls? PߡB;:ϓJP3^*T]qxq{/wBDܷiRPyK>_HrT*̤w+Cj' M,x˵5i+}&@I$xX, 3gliiBgyfΝmR)(g#dңXkÇRe@ ͜2~&!/VWWq_2[H `ddX,vtth4ZWWW__ +g-MTdQ ###[l9ۿPy1&Xnx[c<0.1@'*&c|9};ȪUT׵R ܁gh~={1b7B8`wwAъܶ c[G<qO>UPḙ Ðykb(.[d8<82춵>:sܹ3NB8)Mҙ4 BEI^~ʀ"SFoYO7@x<^__뺌NӔL~e@@#B/1W^fY}u*1XEɅUwc\7=;RNWBB!x1jzd 1Z(akLiP(N(!@q}+@?)XaTSI…Ś5v8r_ EŹh۶ H!@~-) dnkYV8rq`0(.dzl{{{,d2;vx'΁\BQ}}BN?2۩V*r7IaTUU,$9S~ʒD?B_#Pv*u\.m6qWP8pMV(t1i]Gٶ2MSp1V*!v"[B%9Y%()Jy, X(zzzl2<<|67 b8Ʋ,ە "/&]]d,R_OZ[i2I1&<tw6n[EO)D"דD$d2oih0&vGFqܡ!k_{tP(JP(^~+r}}8CŲebJ`L'o}ľ}bdDXx '*m 6l-<ϓE8ȺuO)`0nOOD 0 u]i؁q2naB  l=ʿ=%.t8ƄD̜ s18[mb:Q*m>Wgxdbi2_. !2ƍ{zz c.%FFFLӜ?>0 JNlppTjl||СCR+3֭Yf͝;7 RJ)%ˎP(` }B! $0:)Ȓl"%Cɜ]K4Ts$x50ne }qƧ@,L d"<{6^wt`J1hlKa7e;jAνX ]o!i MPCni h A G\Y(x&gS|f~6 8EXBPBB SO~A8G 8EpYmQ*  ay1>##s!-pPWW7wX,  wOb*aL&eI?߷Ge/R$_Bt]dSR5!"5Y phhhllgMR~Af5\]ުfїlzkxB@`|h(@z`bP0M:6 D!XdX&òYO zG8O=u.UTNB^mqehbPGǑm#Faa!@-RyN5ilD@ F9z|^ݪ+P(L~BH$ȲH/W\d" _,M+F$W_hgv - "t H>eosFpE1B%4X SC!EËEz,fF{ٶmbp܋WNB΀(LutD, !(E`1"lr)( Bq0Ҹ!%3`1_;Z, (*Ή'2@R!d"0 aa_9ϫ+FPBl a4UW,WFP( F8dtD(F l/)[3$+@ ki0Q!2KwǶ{g@8ΐW.4?xFcۑb3]X-cՔRJd9$P( `r9߲qʿBZ0_pnp!aBBF0bl d3s('s$.c`p_ 7 Bł|QY,1BX%)N?IݱeEeKyJ%Se!E&t]mm,ϲz/oD/Baka~<*5Q(JPyRc }Xh֒:H&rTh<)x?N|̜g#scq\{<38s΅@ @Fxʅ?mtQS( 3F=9NX۶yB44)Ja焗Mzq'Xn)#h|L%N)%PB1Ƅ1sg"'T x.mc",#?LLThp. %ǵٳ^zX2AB]u]@@i<%)JPyFGG󩅐[Q!g^YF&e @%^[ !!1m!8p3T~ $U-SbMIk BP(΄98eY !0. f@p~=bc"/P䪻C:O~86ώ/ -B ǡc ޽{-`/h嗛iSJe8@04M4M) PJ}IHP(!@qR9d2mcwz9P] cv۝`0y瞜m.bap$A1=B?P !L(c BP,!y8ڌqT\P.SVgO)'UeeY[M,K߻7yspxJ%bYqe޼ݗ\Ũ40ƚaf0 a쇇P(!@qƨ@)u]7nܵkѣG鴦4?Р77C, c1o; m18!B8766MM]]] ,hii!J{K*[x* BP(^ <ϕZ%1kK8>N WI=c>eL+H.LV!@"448oxp3u]C(!ĜoGɲ,˲aZ[[ s)!đ#ݻwu= JaX !$ˎz{{׮][WWWUU xP#F6!܄h*1K1!^xx- fMSN{. '"#q +TJ5:><}T庌1C!$XZצ _ʔrULJNH8y-=_Z@)L700`YV8 .4АKnڴqppUW]5o=LLL.[25U(g=) ~~;+n?-?m޿}fW! <&aF3 r#Ͱ{Y> pF 8\ n\u܈},0lS!q o49 r@\x&!8fߗko^7UƏ2+y$ΙxR ( YD9ŋ ; BT Vmm%̐ ,˲msnYVe')B ?"'QuJִ i4 7GGFFf h/ieYLRLV|M3f̨oll(lw]. Æiض56Ƨ7 9}WOw^3' ;?Ͻk_gϻAc şƎXX,ao 4͕E~P(T*l.JƧ dg(JPd}XTh2Fs0 vŀxUUK/ݶmm۔j`HLV<]עшmۄ֬Y344iy+W|ӛt۷`4ߺO} 3gS88A-ri wm%Wkzh~r+5„a-7t\>e}+:mʼnXz`/U{qCÆ]~q^mdNE{'߳{{.}sۗ~xW%sl{C/ j9L!޹ov}};>etV{_oV]O)|o{͙oM] eW+?% ,YÔV80_Lb1۶mY֔fJ P(xBƍW0#⺫;jjj/X,޽qH$9B9#5r]wJ}q.[n], ʢ/JP( 1d&DDcakǵZ:Wn_7_h܏8^siUʹfuC׬:q'<_==,5Dtsm9xosvm5a9ks=9D\&hIΊ%j}DRS;-hwE?eY>+uջd3_0.U]K5oxLw.\ FP(V-@:Ư6Nri7EFeh¶m۶ Le(JPݻ%4O?N=6lEDGGyE"Kcuh4:С|^Ӵ=BAnmmozP(9inn+Wic Q-1#w{W_Rx7w$XUe={Z]VwX.ҶXDŽ %BF슿~5~7G'{jyv=/p雾օr.*'d!! "Pvm[qĜlk҇-ƳOY7F ^n`AjEM}'wm2|Kk0srW]W?w=-] {]vOO>zM& jBg6A7i|tTۼY"o}t]!ι4e):!]T($䳟 !J !>(!5` ӛbG{{{뺇wtt\xՌ1_xBN)IFK,"ӷm۶aqqҥf͚%5u ūfrl7M;lؾ{߶H_?_&8kZ})٣Ju!yJQs.sQMu]*!@Poۻ V8cU@ѨYB;NRcRsP SVɚmEjCV E0`|dzg tEv>@14 |$?^)!t 2 !aD"YM:+7Tg Z~Vۮi`Y8 &d2MTA Ep-#Du΅MӤ*-^)%վ#W(^S`oݹ\.p!@IWJ=`?qMFs:cYwl+FϞsl &W p7644BTyH(-_9t wvBc,Umvu]ƘmۥRɲ,Y/@)uXPBմ:]GB@((JMdJCeJ*^=ٿso,m u:[>]/u4 FtX0e㩷*%3\.܄y¯inr广?HDbsV+Mرc׮]㴵|C0MɪFFt6N 0M38cYc1fYVT2M1&V)JP(^9!: =/8.cLvf Ze َMS[N_q]A#FSr[nǟˆYHi  x!:Ew;ͣxg0q]p(_'|w;#g+`0 B8= @UIiO*!P0'G`}}jVWBH$V{tT8hs߿7J!@m4Mu9ZeY kBah0sADZd!@0Yl˥8@Hl3G"/(Z  ?Ȱ- ʼn?#(=aTTm /#1)"g'M)@E6N"=&:پBm;ضwNur]i&@YBr&/Og71> {v;`egBhgE>B;-?&/Z9N: ܈dqp/ʹ:Bs^[tПLziO  Xha#8p؛5+}{ ~;`F (JRɶmY>q8PBBq*А !4 cTsC bQDx28Qd`Zt@X`Rca &]&*-RYތ:VO֛Ct"U8L9 nPvI8a#@Z3<3y'|*(@4Y}g+@Jvrj T^4Ę)L*Uhu2Dwܚ=aYεE9#T5Mk SĂQ.q wci1㋗4Nr| E,D"scL<xN&APҟ1ƔR}Y)@ p1[ aCt3ccCe )rT*JTUsBKqBL ," @+>!Ӵq, wHg UDpݻwߛ<@-2k5v֥_wFH} ˒i5v\&0 >qd]~;l7-\wyżbL߳>3y'  8dhckvD_whe0ƅOݾ8}ъkW3cKzg+\pq,;ijE<׷st-5:w2}J,jOk0䅗\O c6m ;!ٻyC ϛYeȯ6+c={?;m!ӌoW`\~pix-;i1oᢙQq).B!9Ds ky-bxCDž^x;x!A Bl6;44*(OqirPІ W|-@40d5+Ƙ241RTCGq>nm*ŔN) ^8kjk& @Fd@e&N8)vsB:IDqin{[{ݕKj'mګ4X8č# @ԡ=HKssML#Xlz;WW]u]0~";B3o=oX86@FO7-/z%+7v^/0o !4T>$8F{6nO5sC'2 j3?ݛ<_s`͚7.$fnY>sU`:tp5/rv- 1y#۶nZ%p_sό'BvM@Ƿ'"(zR'QB|ECu(!L~PT$&d@(P~'@B`)vN9{'PB0{|,+ @`18 +~p l&f_efVxŢZ0Gݻup<]}OGg80ulyW|3[zpuM`tC;cn~GXzG[nzXr&8py Q6 YӐq2\NٜJ[-\ j0D j. 0@!wwǁ=}0 3fϬ iJ:xdpum3Z,c@@OGjG :MϚR-xo__[־%B53fw4Ƃ`O=x` DA$@gG0S+Aq9c3?{bMwSk=U77Nyk[=\˲lg~zΐU7ƕ3 ׵ݏޕY4 n~es 2sÞo?Yxӻo\!ZiߺCzuž L E{: OѳeW_7ZT>sqB`ǿg%Z^ |{|)}nٴ֕W6ۮjAsHLxd7꘿tYgs=tO/?ٷ;ܸ iԎĮ;]ЁT1$c]0&0ayyײmBe9Z!(R*z/|urr& *mw6[oo!_>~׻J~~渳{?#Cp{nZ/Ӧ'h&7֕F1 [POx{~u{5/o aktC>3[ ^]xu:eFѱޞ1қ}ƚ#_k|/^9 eٶe;kvG֦z6vՒa#m=8hWFJI.`fCMe \Dzmrq;9f5˓̪ߵ~ /͙\0 wp͘V=1tp=IL&0xp疾"AY#0 Z*Y!+JE׌Yr<`V Ţpx_i%ƅ@E͙qekpj~ol>;_߳\l則wҝ``DvcX!͵;_vaEBw>/wX+dyc]J%[%8K)"Θk{\#$.wJWAr*\QOUm88_ãn=yƛ5#R(/l !uqөQO Hў3"|ph=Ɏv޴Zy:s7n @\s]N& <Z<շsG9ncŪ+u#Y'1Fbs*\xZsҙoԴr =-Dt6q e·=ܖLcKhgp[mi@W4o3 ښƼhU'4` L0QJR܉ʕCcv)B)Pu͛=}u)B4M` &1AA0B0WƸʹma!CR;%w/D]u2Bc:$G@H$|c qFP G(ct"wB;V.7J66U1 xl# OZJiuuu"PB<|>?<y_ؕqb(YtβSV벷^xÜ+BP /y;]5U-.pö :97}nٛ?=xmu5 2RY,'a|*o6pm[g΃iAb&@L0U&#4؎]Q]5!C.r4O#]cAH @4neRtR鞾G:P@Β`{7EmxFM$cMHk!^GGDvܪXH牲o)?R\a tBbw~_>ruRrR5&J~ɞ+1#;b5 qG3<聀,(5XB $o``o/.ho "o0 1 +ﳐ–6-m wOۚu՚N1'* qω65Wnٳ̓%m՘! GNX(DAhHI# vsΖ*Q #Tf bTOl=zHWr&HxLy!@cdXd:ւ!\:jC7Gpî3 ;wӽ}tj 1A:QȓHB %Sz,yӒBEƲPI) Bx\ y(s' )O$5T(!@xqti`T0fR9A0#F91ulhX“C ng0<kmtu0d?;#phmzIWvo~dlfsBӢV~Ih4(2n Lʜ"(``޻nbd.MUţAbN h9HpÌu-]Svpvf̶&Rxsײu3Θ= dL Nh_ߐHc'd6 j:io`yD%=7F=31m85=!Mh`$:!\.Y"A埧@b֪?޵~_gVVzhtŭ7i-+.tu37c}(wqw `vѳ<m0gn!ZH N_ޥKx7DRq+:/o{ 7,LJU^ٔSNh]5}<,0/R׹_|c#O/n 3 9@=7'{tnN:W}Zݗv]m1pE [1FB ߄>5lc)Xhz[Mwߡ&^ӚԘǴPM޸qYrGqnsu"%ycS‹׬nJ ,6sh}s &I.ں4;-^"`. 0)ٔ3*nf̱ڭ2g$={>yICwU&HHOW"IMdeU- UsQ]/)//gܿ溮Y8_H41;pժj({GSs]=05,]:|6JP(Ee]1*"@q'qڰ4dZ1`8{:f0$\.f 'Td'2.F"::ǯ~}AVV+ SSǂADzc!b!qH85<饽 AfLM$fJh/ّk&%D`0 R <DӤ7ZU]_}` <jO ̱K끀0e< DfogWuGhw.ð !zYe Lq{B0p&<9hO<#gNP(-tE"Nd Vr SJ {UJHw]JF@)2$8,(<)Y%fD3t5Jб{8|llqX,R!ҙ߷q1u B6MR\1 d45 t **"@P(N޴ƘMS 0ɟe (/- iD]Lz7u@swC=՛΂V?ڠ\fEhI H8Y0DgBNh, jF0Trd%jB Ѝ@0Zv\ ?i"M3@lҌ 5ݔ;˃  wdu;x}NMk]sfTȪƄB/V, F#f.Kb,㹹Ӓ\m8Du="1#Წ u=\spYˁyBcUD({Ҟ9 mJgsр<,4Ө ǀ"!{H2\p!4llb]4ZN- D)!LNj86 F(2!!EcZ%kq2Ca)1inF47s!jQMޠhFŖ|XJ%Dy ('#M7ɻq@ G,G8'F@& @S #dyc91۶=SnB2DuJP( !t¶kI!C B'mwlc' 0c;.o}EG qBr[|&#x).C9l5_ʿ @Xo!tl);# NL^pE-={FS !&3 瓗D L(N q&(41@N~D ӡʇ\K#c%Θ˜%8@ߣj  F[?͊&b: J)y"D C>ahqZf/&<6QQ E':@l~MIBE <9 @19&6w.t!ߖʏB Ƭ' 577+FP(JP(3ZZQlωƓ']\GS|ɍg.oe>1 8nrUB<0 RDN%Zf/mo;  5|Zk:!K]m0uҗ>eeb)}Ldry(Fat𑾾J8( B  BqflY=}pIyyZ+腷 6k$K×M=@1L-6T42ض헏VcS=@_Uq0 eT_ڱ ![e2-[]K6( BBx8յ.[7?xg7CAu(EeY]c&b1777/Z(H(!@P(JP(3DwޣGRImϢ  pyc-B+gOD.Gۯ/"N>=WT( B  Bq&h!BMTBxLMx.BP(P(.[R( BP`u  BP( B8P>h(4M4]u]4 c¥ BP( S(xHl&ˆB@ iB*oZP( B! $ u- %( z{{=M$@0 BXN BP( +`JX,rJP(!@gGŚۂp(T BP(WԇthhxppP*JP d!c,_+!@P( B@!XB Wι jV( BPb4A CT( BP( |[JX(_W12Dto~xB(_۷YRN;#G*"ܳNY kh_~?>M?X6}??ܾk-ԗ~a3&n>ka@n%e:=.ߺGlغeu79w~5S}n$o 9FOK?a1)f|PN&:锣1+;x9[kc;Oy`P mկ޻(UXP(@E(tko#:W- _4F(8:E Rxf05>1.~puƸ@D:) G'x{>/̌sLv~w^:.M$./4ˀ2bt Br@hPHc clfR`쌵WšM?~k?OcE>t_oC/f[P 0uRd\`@F f ƾnqTbz@sE8 w &cgQ[I/q!&bf14ݬ &5pbd{<[j֜K{.(Ow?_ݟW-H# bv 0#2g,S# h@/*Dj0\mk:|D {xvai@53ȵJł25C80{c\uHV(JP(N2 ^\OyW)=u,ᒖo}iˮ~es?nB]i3C^]K+xӴ=݇V˯|ռg{6NlW>}}F'V]KڦM6 jx=;vjfF<\wMέ R@pF_>sϫm\65k k|ϳSw^3)g"r s$Y8v?|O׎yS[>[/ KcL{@o hmު+]M %"Lw-moLo*ۻ;'۷7\"|`/ikS1>^߾`~c@׸Y\rqy꺖`0Q?óCiaU^%-@SE׿Ƈl++?W/0N_{cimkdv5<X#nI0V7kn:(jjz` O),y"q{m={d ZEJ݉GD>Stp׊X9+n`VBPBB1LHooޖz^r9cSrs޻w`JoDtVޝ'O~p9׏p5(>_ɻnvQ{+`VT疙y}ށ.X{b11Fcbo(VbA{])"$DCfݹss?{C+7oW_j۱v:P=(Y={&|;mќ`=r>P.b_n^'*274%g^uIucԻȽgܗ_zkO`!sܠ?o0bc{aW-#! 3,:q }vdr7ǗNnGSݡ+};Vl]99L#g\?rǥ n 77y3:wnGfN|`9w7`cLkVKx;Wo ի/E{)~~f$ naH nIsw_y]v8D1⇳=|ڝ=oPh!~ЀY r~[\Y[L91ݸtUj)RN}~n^:g7,7D_2n]wed RD&c̙le~sE:rƢt9.;8{f[.nǪk{5s ʦߵ8kXb2.1$@WOv[HGzQOw|cy}M@1{^[/7:3$4Nrtf}ɄIz?=g MAz͖OkRj~g)}w=0+ߝ~?}c.?粻^tES])6t@W93oMGl*i|~fS˞p{dR[zR'a۪_mjN|̍#"7r+C[ئQ0!1cJ.|*LUW}xsI}л_kҳ֕7ݏ>o>xv%3?ph7 >[7Z :gh[~/ѵgn`gkAGrn߱{81Wy'| @эHle ^e7W]J˾|}͚c4iRowfvTl'093OO]c}+|6wmS %%)U-S_xx-W߷]Bl[/z>ןrsgUql4}E]uVFa}fE[;Í'eRpWF{WgMEٌ 7UReI^[gsgl8PT%ֵ1C!B`E 6$KXD{OG/[5}.R6R B/ytyó&O/kJ+(;k v"HTs۫qIY^;)5zSӲͶqYny /СCqaͲ-,7xؾ= ].}ϿYQ.͡bI W a/ NjXעA_=$=cfѡgbT,jeM<$ժЫX"?^fxOBZVv׉A X΄D3=7?ɟY8摉Z"hT͙XvwXyu%ƄJ ϛ`=GkM$Ƹu4h.[={Ň.6m־}GT %&+.Csmfwa 5xe:/duBTQ^W2xؾ~Go viWd}JCd$b_4ne 6޳P$RJ(UK~9scJU$#sftI7 վc1w/U r#b!mBXh24XJ[B B 6K6<-%9'#4M29ͫ#n7Љ{w}OݱbZ&9x/M:c 8R )wV손أ&'MfYda*P9Kaf溮$}~~}^wvY=Hr1n'KKx W nqvEkoOڹjF~gjfþ[Wx.䧊/gns䕏~W-YgKC}ńkT(sVñ o}4;zf>Q\?8˻_Za_ض/>`GǾ^i˴^z}͘ZSyԯ;,%TdҎ(=+6⼄ ;}:%s#8ueYg3!90n1 lG -ؑXtM:OF#Ǭܼ΅܊Be(/.Ԋ?U%vvtH :θxdR_{Y,Gt&}u)G,&2u?h**ar.@0]F辍X!gʱ#a?SUg\Su (3Ƌa%=M{z`zzQO>1s, ?z1g8ORx/i3r^݇?zܹ>.=?>yARtz9> \J_g_7IwMvᝈ9'`,\ש||KHph(5]Q׉>U}ǟ/#$)xK#v*7Xj&$0B39ͭQB074rҒ<~G7ÙHñ#uIHIKt . 6΄DKSPUQUS]kJ˫BSҲ4 P7cc&`͗0 1BBpKU$I5 %fe \;Ľb[KvRZYYUQQQPPwxm?3CÇl=snYV(jjjmnn朻\8M01|b]O@' 3'e2.a ²,KH)\IY>Mq%dĥ`aD̟`nTwj^|BBcaY؝P9KYaqKLLRrdB׾sB [ye#Ob?q]g\ ŕ ?>uf޽f_otۨ.3YBDLΝR0 vz !c e\jdl܋j.Nث %3!cR$R$J,Ⓒ2&e CM˴ );Ia&8;f9peS׵}4)%"J|fa*sKˎGH$d&CLbWB.I$B$caɘ B͓iZUYL뜔'%  i2!$\~,L)E)4L4-N隖#`@rC7cgcsGB#a馐!BRe+)Sr^TQjJ=/,ŪC 1" ~ĵLÂVF<7B e.!@ &3#nTs@Xa1Z>܊2okշ'Wy`VHGnjY̒?s@p+aB#G7\#a#K._"Bx_@wRp:_tG>|2cQ22"}f!|bu#FGNh !)1[2 1?e-SJ:&%"-e 5:a 26c!C :CY B˅F^vm@9}~"]^wAJ_o7Lsԙ7`n:vl+RJLpu:!_"sg 1C!@"ex31Yfl[ph|S? C 1kq"b1C!N4fmL/$`I!w3$gVL<b!;:E_,0)bD@ ^(1i-\ cOfι=H))*?i̲m 1C 1C 1#bPiii]]OxMMSsnc ڽ|>)655=ztBii)))skk_xi2Z B('בRr.39?|5@1bIao}2ʯK퇬0(B9/B؜ll1~ndb!F4ݻD"$˲ y9ㆆƸ*~ cvm"z9?d*//D6@u}ǎRJ OJ)ϏWU@ PRRRWWϘZh JbbR~~崬@ `Brq*=b_ň h)Ǡ^ BRJIcǓC 1#b'wnG]tI3ĉ 'J߷o_[svYgTbŊp(tu4/_t:ΟEYUWWz8犢nذ1ƹh%XQ^z\$ ["h%,!%4B* yP@/Fߠ'$k.ZDV={n޼Q!%؆NH@!ٳg C 1C!9 G"H4M\A2q$B|'TLn?A>B@0(RP%|ѶBpUUsss~n!88 Xȑ#{衪 G"6mZxeYC4&Yɰ%CAM91p0BWCqJth)1!䧍T=@p.%F;pp8lREs99t ,p8pRB{s b!'PHLX,C 1" ?0¡aВuBI)BR˲Z70ƺn, ,V;l-P0(lwOT!Z 4-TzϡČx""ƆEQm۶WVV=O>ANsu%''KJS-h4xMWEm5x` 1_ Q4j_ Z2Inţ!J6!lz(2 !+B jl|eCWWWۓHSS5k333ǏJIDFQ q͘1cg}md1<Dg@'{tN7;V'u`@c1[*&Gĉu $ CHj?B{wYvM[޷?fgQ=a-?t6²,]540B-]~z=vNsrE1 30-AQij󩲸SFrW#b8uvKVƤD  90]xHc8jGTJT 6={feg!@-E # )r^P i+PXd҇۫jB_XtbznNmlZ4 ιR2ƨرsʕffeuh-(0M%,Z8v=l߾gA)=zTW7}kN](^JIСCN5M>'۶m[BBR$Yn]߾}233CBQf3Mp Du,#iC% 6?/R[[n(d >RqzLm+cB eK),ڳw歹+eڵkWTTRpUUEq1!KGx]jnx)ZܮCVAfhjUc8 /4PnE&P q)$ťara:84O~\RRңGSw޻wo^N3v1Qg8c?B DYYYZzy7UU9u]"ȝwIUu;jdI(7;v~d<> @χ\Ki, [0m/ƿlMH@-8nܸС7zӭNe튕r6lBza$ .$"OhH))c,JU+WjZ(ZlYrJJBB<R R2.hecPf +Ós@nwC+X~ղ2rWRHd'$8>~5鷹+ 0hUBL-)/-jMGщa%{@Hq\?C~P -?G}\mW+e{`˖#[ -z?apKI)$ Zn;=rB4fs`ivR.N \!9y|q%_JRӡgĕֆU3R] 9|8)99>!X6Q#;e7ѡg?#  Kni$"eq1Ѡ!9HLN}r:PvS~&쩪*cL6np`~EU`Ϟݛ7o4x # EU,gk |oSCv[h4ɶ# @!!1m] ZdH/5m>{)l VbxS;{@J@ہd-`0Am R"v jD @@ ѷW%_E=,hȞ/ \{6%mN- ji*Y7{[>0WTMu8-)`k#k}G$c^w 1Ĉ~A@ !vBO癚ZZZZYYeO_?ce}%m$K G5W^{ Í ܈zX EZDbj@P f @J3noyIt1$-%ԫלn„(0-_eT0Zg3~[͕%ovƁC/r 忽́Ƅug9O %8ng5,PJ5M3tC ( @2 K-MF'x*Zݳ [p~~SdWW-;HrXff5Z`%&Ĺ0oY@!ow NSB0 BI!6O")%gBH0%!R_ FF%L9L0!欦pħĻT,G&Kι?&\@H- c{d$IzSZ-1'9NSp4{V*ߵe:߭sAG\iSE>!`)EԣR A#5Gͤt[k,?QSiIo7lEy&Uk}1>1E8ѝv&NJ!G,6b10ℐaȓ;0& | *w>Tm$v)Wk,ټqw{9 - KѤ-ȊevnY{;֡bLѩV^oSIA Q !;sԩSVvV>0LNЧVLJ]Ni.ڌ1mI$[B9\ɏ]#BdLʖh/Y`}G҂߲?t „Ejk"?'-Q2T3 B \ri_2.55ޣ xR|Tyi B\J"l"B 6)zGaL1[$~$DU+oAWB<1FGEߑ aJ(-OM ΅Bh+%P[}+gt %[g7囕[$(1-57cF M*ڳ{Qf KijKvoٱi}]ƅOLwfC}m9g>+j,{Om0zM{D'lBD!CKy1 ˮi,%o(ݲbG_/w,{ ݏgyO^qF%w:x)Q Ҹ}1+Or=]80ð8HC2J~9ˠ25Τ+ܷc-Mq;c!;e8df?=?{n.y܋u'4(y~WA$ap;vtiM*8Zܒ" $X# K4VchU])WWH8zuo.)8B^}ؐ>}νzѺPa7=.9qdK;@Ju3߹%]\"$M&&R4ܲL9w*SSȫ-1 SQRqnV}6MSN#,*c1?8oƴYU2%tuS(;}GOicUV--=n!ӆS<Fᥳ;ZT$ߵߞyIr)PMԯ'k|q|z]\5"-xH;+\J:2o"(ڷO;6#P`Ճ~viMO^ ]/n ~h ָ2=,^w$-άdM% ĞnƠںq͔3yz5^8W^Hrѐould+l"B뇗 op=~sn'\~Np^vՕ}#3TGk$Tqs||oy"/->:-Ӵ$d+ xŒ<|p_fAp;4pL@LƄF!bD@ m*saRUiJp2C^޵Sk cXY{̄;.zo>?n]~n|h1gHƸG)w,{gUX(PzAƤ)\-1X0}j~ީ9R u`0 ,Kbzjؠ6R%,i`mc|RbB$HD)2,{II5WtYoWJҽg4xa :bN 5K-:!]EmSK$I,[Bȩ;VM}s湃ƃ@^~VWI=d#yrUD !w}<{l~Y7o}h&G'іbv :!j?:J@"i4VT R]l θhE Ho,+םys4Uc_j܍{wNae&jLhz>zgIŕ5pT3wj, Sj)wʕfI) BqnՅ4@XC7X[ )@p6Up )\BK}ɜξegY;\^RR}y~#$yGޚ?oMu5CEikf 5 H -./,=2Zvl܌T6X B",A*<뜫&^E_Lznأsj"m7"9 x(R$I!"é)itΥN`Ӓ@rd߶+jN3" g%y| X"dpK!'&`laXa[Rq)NnӲiOP͗v񳚭?`Pqɕ{}5cR nX-"(߾a iYh֙߬(Ιn8DLlp aL)mU?d~."@BB @ۆ/6{Զ.cL B(!H$Ul>?+{渍#6rҰa19*LH@Duis %"fXu8U"n0 SV5 ,Rp"ݰMs953##5;>>4ozS#%p9 A uCK2@"ʲ^tZ)/NZ0}N*#cv#jN !P "SMs84ŞXP`\"jnF03#n .Ըx .8\w* Tq8*L]MN]>~ܗ>v&uԬPؙw_j)N&rhTRp=iD\*`16z=-9 Q%j3'NuI R:N˭8(K馐R?m[W\t#m u=odaƁ2 5 o~żv#.Syk_ykϷ='տg֤nx[Od3|hʹ>/vΡn_QC !0Kz蹏{w`U#O}]sj3uyC/nDLq CF]|P<ɮ¦U$ekiNHRXK߮}0+bL 4bC 1" _TQB6K6+B)Rۋ:)?]54cT]]sСvI.,tCJ+ܹu]iZޏ2?PJ-{{o{`0Kٸ<{[9кВتg'.ݘ=8gfU KD_ֹv3nɔYÛ:kY]sy=|`aU";[.:3d;}:m憠${y㇏)N(ŌKv֐C/\/,f1ee)9e2*̘7gŖCsŰ>tx͗o[l+3l94gҘ37%vòSgm72խ&Wပes`!O>€twڥhQi@Ĉ'g~izQ 5USr?Cg~{#`^z 3 w3C1 :CǿϞ33}?g eb`2+Ɲ} fҀ  d3$rdG79qû'=£z`XdY\:i1'ƧX"  쁽Bz@7c} *;8cՒ SME$qxHox'xzUqq4-S0-I@ջ7Λ=si#v'f ]b&3dA.!Bx9= ΨZLf'resI$c4u >doQsǵ}w|8k%sXX'"5Mkz ;IÖ B  uu٠) Ĺt"4%KȈ)j\@UzB a~r2[F ]4Vvb)uz!Db]&V>cF_r-0+puf-Ǥm?|%cびp/Bv n^1[+.r,9Hy}Ӽ*7",s˒jP 砳ϻ0mJnYƊ5|g•G;3vkW뙫wTs:!.`Ѽ@(yy8{۶/m2 j.c{%HZ5OH [y#.lofү 7[is^p;qz&Sg3!i4-˒g̲XpuLoس}>ycME.^9`ˈJ(kG3Wn>Pr^]+n?`f\sɴ[k¹}?_N,ޖ`eIƘi4- ʍ\#ξdH'Dh!Mޚkxˬ5,!{=EYHִQ;;۰t}G!Y\x]1]\MSnUKnMx9#z3ryje5ӓ: uwy ô %[GT_3{}5ꁻ3Qt%sµwWi39!{葆@r!`۩cqs%{N^ZUd(hƙLt aLe7>h0vP!`r0D]Db<b1$B0ƦiBk?>YhK$ BH(?ZثEESC8 m޼9>>'زe/lF߾} :vv [!@4O_ee+`D B@ޝL@D2&:X50&-j NK-}/&:Ұq:׶5a>F 2- D+F@Rg~/pEŲsT>pj`׊%w8\`[jcT[sR6%J7M}鋶gx9TR;w%jݝ0}hLYBHf\p,ô-߳uU.}G}X5P]MF@ܰj/OSR:;W-Syw_;'լu/ +Vv=ۉ}ᕷ+Iv kGeg=08>ph'~>*\\paa\k[pF/p !%a0Be 4:Ô4˨',[Uw{&8k"Y2M]G%kl=(e,iZ\br/w[qpUw]9v &$Bpa ,#:6 a~6 @m( 7۳ 3p0  ؕTٿgS#}Dt0DeoOigޘ?]XCl6J&-(h] RW:J>`-kVmk%$,=i0`iXB2nH=&4 p8foٸ}7^hb^HÁa0&8iq+ovJhk&2MʠaV$#ތ!J삋#" [K)$L4!P(:cb$!Vvhoy9}ۻs5Ս5rdGI"FE0ME@Z4}fhF=uF&hSͺ`*Csc(Ԟ0mnL'Z"dNĐQSl6זƽ/. .Pq3ޙ{iy9w]WU% `BJaSri l\tG.Y }x\lWɲOY\?pt^{R=);GpmYCK:=͝_'[ߛXYcbKn0,A00 3Xvͻ8pݷi,Y33US%Wn #ן=-Z(enX\2tS7 ӣ:oY<+߽jr2`LcוV Ih Ghpb31&&c8rpmY)zd52´j+m>tN,=OV52ŧx}]%;Tu-L޷7c%ÊK|׿WN"6L!!!* UY69wU# lL9_.ݳ ,fF)Q3&8H.@zW}M#묊Gkjam!&4ư%p)A]DCGPJUU%`:GmS(@HIdTm\.[V%Bh8ڸisD7 ʪɓmhhxG/,Ӳ֐Se׷i!j 8 ?5O`ǿ+g붪 Lhc#E۵'-Sz;/yOd~@f-DBcYRa؝}r͈hh۪@>ZS]?얫<7q;O>J^q[vg|k=&!+ʎ+??ա_+|m\xVWo>{z4䣯O<#$ գܮ.`AxW|}NjUMݲ>'o.㮹v\TwCan&./H>>]jh;,XS@w~ͨgOժŇn(l[5oSoO|pU]UUd1t?CI_}2{eq3qzgU:hl)̔t\)bM.s!D'uL#!UΑg-8خ%,N 9 dr&mտ0 fѓ?/ &M;PsܾԀUD% n2cOA %r\%1=#,θRA e+ޟ a(q]ϼ7/68GtHT*>{y,[56nbReg # \H=\p.Tћr\@ #{d!b2xUEj{=?qCrl'yӱ-:RHf`M'7+w[J0 PQ4KlAo0P8^M -%44cCٛRFW!8$Jt bpRH|N'շ6zֵ [`+Rˏ/~[{5 (lfI14O7tT!% l@RL/.7oW9RQ4E̲ !XH:R K$̀sn'PaZG DXuE׽7~ϼZO%S_p-m|?|+#K_z9Kw:0; So_wݗ״>Ńk ^~!Ў/^-UõutߘOB:Ẍ!i|ft|+%Nw^dgÃ7>{?uA.BA-ݶImz3~#/u4*)W\7`Oqp mVm|wwO9ҹΫG mNr!Rm-j?auC{˳k֬u3ZgT"XSe!Am]ĝ2[W[=ЍR4QiZLHUq}1L-ƥ7J)-Ԛ- eԄcRi)C[:惛VK뇣 S Ld#LR5'`(܂N9Xp[Yh4DєzdLubBzė۟1T$CU8ɉ3sصOv"~jvɛI:78&d!$pc@-EXmQH@\D3C 1Ĉ{ J(!cB0j=/.v[G /B8gv>|Hn$@87wիQFRUQ6f?ؾA!3涵kMs#G$e`GF*$;p@[~1+*D/ֱ]w{[vWQ"@= G;*49˲,UU5??tr!EBHyvρVsD!>)% $4T'-sw4jy~U!bD0eJj5D9,="q) H{ - >tI!PIkir0BB0 q|Oە@\͉iqj}+ ju0UՁpT12"RH%k )U`"f~>" Zz#GֶeONN)N&$H1i1{E0`Lѱ0=Mz64r5xYʁ+ož5{33[~x~]YUlI$&^0:[Q#ڲطSرw@+.ys3*nG ^w٭c:EB`0u&u,RMBRi޳HnY[V:j6=)PH.[BJvm_{)㮾ҁY'|^s}]YN5d\"ZWp5lظcǁ>8ܙ`ԕmVܻrʤZSy}#!I RgR~˧-XW4?K'9.)}I)5 L0ܿ4S~>3@{ٚM,XqC6CWpwlj9dR *JV,\f_#&Rw e:1n z֛FsٝRjh!!i6.8)1bR" p Uke_M_ڃ6u$0a&͟-ݴ4w\q"KDE#Q5tl[EuV+XaكڧRbq`- 0%TX(%^WQ Ns0&X#1~W_xwC`$Dqԙeزa$$4BJc<@ 1Ĉ~qBګDUU14~zLJp:vqva 8+u/?ֳgϋ/F,ZՏ(8Mw?% )Ž{2;֬JHP3*ЬŸ;([l4GVVf\_$ D[f +;\vM֨}dCʡ~9ppRBBP4͵k׮[eegrӢ ! k*0  S\dCe {fOygnvۤ. !, y] Y~0pnC n ,*R߱m~Ka5-"h<9RT^|ڛ8J0IØ:B\ZZ;a$-/rApGRbK&h'6er$r n*v/~Nʇxw޷+FtL.(',BA0՟ng;{M+\n1Ì Fݱf?Oi a#p`OW;;Iv*B1CBe!}_E 1FPBE `kiD)%#P@0i(L J$@I^V*%b.)ªbiAJM8n5k7Ny߷{ٽ\fxɏEmOeg\q]WJ& c#d6BRbqDS Y+1^b-_ݹf(7TdL'Q}*CJ6u>zZ"͌+j4yFS%Dl`+vDR"BHjCpaRL`/ڬK[z EqРﯳF;t"TCCDda!L!I !Ln b8TPn*m>zH4_ I)B#KdS"*N˞I֠[#ʯ^YY _pA^~g8hV0D َ cB U+u{ Hua @00f)0-! \G%b T,*Eqx4cP0H$u=wWbISCvaTՁ-zf yLΙ]&KiF̀b1"RJBvn + lݺ]Q4LiYMM_~9cرcĈDCd㵳NL8ew N$BHA2?oE^+%%;@ը%K-[-_ !4jԨ/8>رb]sg|>#ض̄eNW~~S'&kbλtBt( Ƅ :bĈڔ1c$%&2!B-x@x uPl@5BHv{mK=64B@H"#;sqxuʋ "6-P`|F[E=ݶt) G[`DUM l^o~QqW{:TPhc1`vZ|5 pdC^0kٛ-ngymuM~DyYJD'NvxvyiQq]:{!Rz's>|@[y.'jq: )%XLoܽhk >8"(Ɣj*U欕 MӰ$&`E+"BY͇*Dl8RQǜIɘ/~J0B LF]KNwY8E*IiiTj~r(73ӛ$ % 8n߄,ыsnX'|ah6=dݓX?kC{V0! &PJjcH[|A)/9<; qAa/?RC"ue%#Vo %*! 9\O- yO.f |B")"TA*EJ4)NGmI^.ݵbeY7>rqI)H.!b"q{8h٣ȓѹC7~5srDϧm1\쐛CJHMv8ӯTt:Bgv&RJLBQV)E|>O^E _G^hwBئLQV 5##& `W_~QIakC8$T.F.ˣ)!@\ b.B#J0F%Ӄ pyuS+"tYue',J='9ƒ-&rHs| T9;8s`Q(uXRbМ|}c'#z6D&H @Ԝ]{+MK8#}:c[fX7ز@ST3$%&Xgrj W-_^1dBW3#F8kIGp]lL#9aR"lgl,{n7I׼7-󕯬 cPUJᆣ!Űl0BZ#vףT,Sh5OHh@ r.0˜ pjz߫-nኺl!B&"\P%fsc-Co$^#a!bcz;suˎ/f<%)*ňs \$YޖK}ZVo>x߽s'rjqץ \~W7l:Zj<1:E; :˻`o^=}K+ظ ,1HHf Rp!1!@0`խc9)b1c qRZ ,qp|lA)erf`EvNi Jh4UGػ b!F_!]Bz@iG"۟#ģښC%UEqukl߾[o4x,[x0-&Hb3 %q v6in. /ܼRRK ge+Yv4ۿl[^!LPqqqӹkM܉q؛q';.:'籉o! 2~mɧ" | vưK;㌜BRJAՕ8ОKL|dӿ;?{`e;7U윶ȱ%e,nXPPORssf7i~ɼJ5go+q)"2{Q2br1TZfQiȪc[W:cʵ%&Nؾ4Xy]i#2 ˰7K!f|:;fwۥ0)2]g\bPd4,D(r9;y<.L~1i Rw5 \"H3<ʪ>{5]ڴwc&) Ra7]jz-3@t-7(t"Cd}e%6W.QdY>GthG*+R gϧόpSF-~&~Roݗ\6RL+P>ߞsWlkwҽnלwKѦ 9;?<ӏ"a^4`D/0a0/u^~,e9T[<4LQDU[u)!OJOrm._L$ls>na_VNiH$:u͓W,7H~k#=B>ŝdLwK׾iתק.k,RR0,9 ꡊ+KW:l[FR CA)X}G_T{iN0+D )@r!Abg(+U @I}NЮ+rE^Ѽs}[xG-NR*)MP": L)Upġ`" -s`ՁPZAq%R&\"bM%-o*1Rhm<.˴$@grk y!݉CexŤ?iV\yv~i8fmל3s>3"뿞$9E>GPPdYaJPtK DQ5 f̌Or:MݸthXiOy y7ϏO={Xκ,;C8#>9#ދ(ER0+}DeΦr9kuV=gMݒXMuۍOL 3u] 1(uZ ;,Ddެz n] DA0bۖgPmT7)o9Iv"J/=4`΅$n0(q;,Mwsygƥ&4isz" 1I.޳y?YϦϩH\UDPaaaqL)%q'*aJ1u.+ 2*DAX ÌSJ7ce~mcz%1X3`$BLnwܺ&ޥ>+6-9c[poy3Q׍~1y6.ݟdf.랣zߩ}G$Kq\^M 3^tQWR AMqH@Pt=b`1&RA%!.;uung D ) R EN!bD@ ?$%UeY;wTU%;;{ ȵX͛7gd+ry|a4 4-k ݻx5jyx# l}'"1F/@xMycZJD|K!9X] R.0@'={E:tmwyc޽iii&L/7MCuxE@ @9}o{u͠j wON8Av: A|rvz0h9с˜؛dn``@Pxׇ)ܟUxcf9ϼf)3,HmqjǗ)/'R0%ԬpW8s-mkz0`m͠w=Ϋ^S(;͉OM` N9^[!xVLA:/׍䲘%TJt%fd{|. eS&} $Ͻ ^M@d)ބ!W^ѵia|Hkķ?0B Irk;!/?d7,Fҕu-/׳] a]Fծ5?fxؿ×/Hs!Qvh\u >w={U71?oB>xMd**,E*%gfgqTsΠBj=|{AAz!B¢es?xq}.9vIrkZJ[ºgL:upDHhK霟(&+f.X}#в{\y-'mE 8ޜ¢;?xaO܁vS$#LzS_+l< #C]~OCooO~zF)!L!VJ Ckz߼bx3rN&HմڰcAg_YإиńY& A92l{e*ьw %v, irzO9Sz7a2o|\~j1833 Xj B5gBrVaH:( '%J4I!3✲ݛ%M8;n0L|>H^7 閬测l݀Ь^{۠,n_BA^nGOfȫ}7uG/;OݏOn5TUB"BT&HyF^p禽MW|ܯ7 ] tӓ=&ޟi}蒗z䝙><@0NIPJHp4g;G|q7;b /ŀ?ҫo<{Pه"I/;/q'wNricWVV3-٣)DmZ1B9iYg]^R N_BZFsJ7Rwo&5uL&@R) Lͧ >'\|M5FuH85RU4gsxOgVnBJbRJjkvTlI)qJ0d0x Hiwuu5a's B u ͺĚy|*̌ rkp!94 753>"ƯkMgbRz0€.b7"8"끐t( 3 4J7ׇ# ;>%1έMdۡK5@Bp#2T[ShTaںFKEsy}nA%3!YB :=KC !H1Q,9Bq LB%)!@G"DQX)fĝRA fDC*8Ov='I3N Eu%KJKI)HH bL.FxIH(BnCXMMͺHHDŽb#Bq84`&N?!hjl!MB+TI4#lpˣLB55 Nn#@T0ƘfSj$G,ph ádEtK'kجkR)ir8;&lV!$1:D:hR+*g~MS *DHsm]mmC0qRR$CT8)i).B^Z@z]_r矝^-T.e6apGwnpw;@pL FS}M].MHLw)F  "%$ƻ5"`#`cUm3orQpc]]CsDqN5|@X́0RNF0 @c]}x56jRZաIQDqP"3Pp<.fD7-x=*fa8]Akk"X$&zPcSɩ NdƺR*2aМb 2POMsQ3#E.RT4 v z0`"P,`P`vQ!{`IQ}*t9L;3;;;ٜ`U@b=?CEAI%gv9С|w 肨fowUWWW;Iy;iPW_z^^MTC!Sg0S|^]P؈ H e Q"0Vw(򋋊^|أ}=IT_^Aa~Í`,aؐ1 G=3>=0mdha^~įQBf0M[@1(ݣr@mN6vـ1f2uoi\˅$R""A'Ŵ!Dʴt]DWF-Κ孷ml}cNBXLJFFF^(B!M2}pzpqNR+W W|pp2 p=/thѣ_ᆙ3g [BNk@Pt8. D`|tfbƞx~se ֜eV RΟ?? UTT(bkCQx 8r䈪>1dY!D,;ri@1NpJJ@HQʀKrGғ%G+-"BlӒeFV F ۲%r5$_ئ@*-K _%$PO?TPh@ڶ@BKR!l)%Iz@) gH۶-9_ (e#!ԓe мp^qn'Цqkd/רh a"e wz~e(ZX a5O 4&m!Zipt(,[HDpxs,!3hۖ`g{#0!iI T":1v8 =ѲpT KJDBCEaݿo~`8om%)"ťc'_ }ԷxG/Z^$l%6mB P8T % bۖDCJCU)h~a)H["i!)<x `0o⚆pA!`Z&˦Id'sB(it,I) OC 6E@<Ei$j@-BPr: JrJ}+rP59KYfX1›Ƕ$x<"M#{ZHuvtvt{^]sΤei,HFC`Q2%@^i:* a k@Q˖@tE R;VTUA%mK" LӋ+"cz9jo(0-5m!$R+ e_%M+ĉf"SJym35t)#EY"yy%R` EEDB-.^)gꋖr/lYh `aB+ J!]43WNP ۖ׽h!HH0m y%%eKJi98@|i[Y60n 0eI$ sD%{a 23i V騭TaUzaF@`a mˍ뗶ppP=zftUaYqz 2>7_7.B48_ca[B2 2Fe$A?!im!i'B kyw~ק{$ADH[PBBZJT%]poB@ζlOkAPј:˥J`B%`N5ﴢ+Λ7;CQ G5kh47?iL)+//b[l1cF8Ϊr```555JPīP9K 0E0ݶ譗^7gt,L2Ef@)8-rU9¸2<9N@f^&L9 LaڔsulJU5{PEB!/eiC-J g0gqnD` g+03q79󙢰̏AqW, (>"eZ@N3I(U,(>Y/(τ!j6OgCRӹ&NZJf̵͌r*PG ̢"C@gǹr앖NBKOO9!W R""`dTPƥE; TUu==[c2#b\a@D >53Ԝ!PP)A 6 UIT?~jΝ=+++5MfKRG=xiz&H 5/O>]E F2+4S7U(e44Bo:-c@/83bp M:)g!\Q'emT ܉h<$4c<.7DI@}3 wLîƨ(S+Cp?Z`yWm욘veNzBB_p&+YB 'm-&j|p^6EU4x*2LbdOHH0WR8C?4&fw$TR ڠYz 9uǭDtrܘs<@S)0jTzvC'sM}d:愜].f' F139x3pd{$hn\\x"'8'|y)#& @@}C? ΰ(Pn]poB6lr/z )%ZBNTrʘq(3fkcɤY 9;NL7"9((~t،r⋆a; ^GiYV"DҖBH4upxx0Rz8z=?°w(ҝJ%*Ku]2(*4$E>3XqrT!?ui tRs'd|LEx$c8| iH# E544+VԜѪ@h `hhڱsߺO}"2+v,EqppQ*t] BN|VzĹˋa>}ٳ}|Ooll4 [?k8pGA:&8r#W:{pϪsB"N'˭ بG-}o8+ A$Hc&mE 6P(8F9DSgΜ544cǎÇGQEQNt! $Rrl7"&F|`,?|iEť#&[-IB`.(J~~^^^t2%A2pQVTT.\pݺu> /x܅E.RLd_W)#N͛Y?gy} .ś۷oy_!t!ũFcߟ-qMc]]]O=49 /ѣeeecA|)j= BK]'T|&8 BKLQK}C#C).\L+&AQ`8X F|a2'= |+W555 &SE[xBȡnEKo@5*SrSR:{p$S5.\ۀ B$&cjڴuu~us%\3fE\˿]ol ÔRDR۶UPP`)%%%:aKdY@WH+WB!wbjm5%ϣ {XxiǼ e,]^ς f6$ aJ %?gZ57FM)sG?ēʊx4 w=p1Y`gy+µ"p.ݹi{ܲLC)Mf; !l˲rB*)̴Ѵ\8{My gDDT儑qKR PNq!R|"Jinё~jvTD@6]W.\",8)asݽ~1f|71B3(4J2]pqԈNI8>~ n.\p.\=ˑsةJYsOa˲N)Ƙ(۶EEũ)u… .\p%\ZBzΕh4i16222<<| uJi$zNDrp:BBP 't;U>e@)mn:_.\}Rx RJ)2YP… .\D\R}`0k-=YsFIIi88h&pj$Yp̙ 7 .NTfSFG?ٶ(JYYiF\H}hh4 'FKKKsźp… .)!Ҷy1˶a|=JDOɿg 2rL)w!i7s^WWW^^rSypR DZWܹs%wK)ul%e˖:& \_.`I(QHrjB0-^סה;-&KDdA߯9L$-DDB'8̙3C 4 jiiٿ̙3^ϔR΁]C!p p.3@RaMՄ-WV̍RfOsNR))ebfld4McKu"=68SĠfvSf/97 sQh4:=x -p%\p|oOR8Ǽi?MBHwW)e{!.ʪJ۶d,:#R`!_'h @ $(HQ wCa$MQ>tu7{-\8'L!szH(|2Ӻ9 lیGTq/GZZQiZPHUD"!4c#7oN&s BHz{0iaL<05_oxaOR| NҔә D aym;[+CBl!:FGGKJJNd@LR1>0~&"X=44ޞrJMKz%HP:00~?[M x^1YA{yi5&0yj)T϶j>qtj+жEG m J}}bnl .⟀]v%c%|E;63k l޼Y='/((piD@rHB` H L"D6d-5mw>cƽgڻi~l9)#[ p('qoζmJ)g\8hniy}JB :+/rp1%*0U0H.$!D  A"%" D%5Jl!rxh(sRsO@a2w$G(e241sN-^hk_k>MeCDq(@آ~?JDBc,RTVZd@ g.&Nq:>?0BQ$r-QC|#W\\ jp.< ΃L"D)5keYNbRhރ=`Yg9`p渷XlB+pIq̅|Fqn+]Pݾ%׮̧T)I{;noɒA]I[K̵msB˽))sN),[QxOwO=ىG}ꩧ.p8dWXvᖖʊRJI8- M$m(8qrMPg}c9cOb&cƍ*M59M5c>ݐǘc&RG<ɵArX5ym3')eNC@ Z>)HF!)@ݒ"հgڇzfK˲~]׃c4#U&d-=5Α]B%*~DiYck̰ 2=N`QU#`PoRRDDv;$JD&۶k!A$i8Ҍ\}'bb0qURqp1 I59͇3S 8%u)ܥN Č~3镳&%M(sBꀀ#DHQ<}೯=}Yi$%\po0ƺS4xٞ;zY  P(cs:.өoWQQ!%nڴ3N?t) խ)uQ*9B)Ji; @=$h8B `Ag6¡}}u->r&mg"'O RQMvn޾u3d)eOn؈_t>c-lAJɄSB[=ԑ\0صz_~Pt%׾E'19B|s])iIDsXnG*2 #gXt,$C qQ@0s PES2FGirFB!˴$D\aN5@(ԬK7@`LA2," P:D "!\ӶWĤYȰ%WUpS@3e,g3 eB-ÒLݟ*rҲ2qeI˰0'F"Fhi t1j8mDhjd =Ks1,r++,/+)S7w1UQLSfwi++m6z]b# xC3Pє IS l\%DQ;6W} J(ڈDEG|?"ҲP(-[ɚFDB(qN!m! S330jO D D 4*%J[%[`=ӳ`BHv2@Ucjh_wMX5)ADH~߬Y33 B:,###^W=)#dLLSi=DUTR Æ#^ tK" 2 %p,{\No@iztd5!PEUXZh`=4;ơ1O2¶l9IsEazH0 $& aZwNkiL)FLL,sQ"x6>m6,`KaH(كQ.\D7!hfvvز:焑&f6 $%|p(lH4cBhm;"1/).A(!H(&BHtx!`ORBx'@P{v7oۺWd] ZMI!>a#㋗,Tivf{}2ǟ\tH/~,"ލ_wkƼ& _ NL 84MdFB$S)tc1:J!LӤ**T o̞k=eSӰ=ҕLc}djenPȴ.fD4Ez;ԣ]Ϭ҉`ts/SUrR(,[HlYhmڝ{XsEKHB(XGw`%YxS8* kzw_~њD`gJaOՄasHY8{v}YBCvt%~5;>&egB̤#9.}/[J TΘX m[rOƻ7=vlIkYpiA Fv=ÏkOP_٬3/źLxṿy( EQ{k1.!HP؃ᗚl;3+S<ӺuDEQJ$m[ij7%͘5{FyHYkfu\Bu5ҟxY''4fH!!iKŢJ@UC/>` /'’N(F2-cw'ߪ0DBdi߁#1K S/B e}{m:2bln۰ּ髗G|%8?Te$6{h4Z]]-$rPJQql.? ڏwoE8\!["H^eYNFL4J(u̧'IsW{V.;\&"ūyG^%s.{+B"!\CO7+._ZE 1ֽG}zk@^eyM4w?{C ~EӃ2fgߺMR9%5Fjg4̬L츥1|WYEKV7G5$7,mLz/r #?yzɜ27u#,77N7Ht[PZ6oICyH*0歭f姕 Sfy76x($ڻq$*]_[HkӓO=ޘAXa9wyʫp M9cNq|yw4>i6N6g%R6@o#B$!Su VWIlAH4 LF'3… pGM1 #\H`yw=:+ $G"PW_F6mڮݻZUU5MbOicg} Ȕ wl:2ݴyogܿl`oIQ{*$T7/䆍@'/>MU,'F_Xh`K0>ġ'q˖ЅWq;~;83U'؅8 Jan9I9` iY"JHv/d/W͆bpbJ/k>W_FFQtUjl, L ء~gwmc~JOY;g kº?҃v♵Tǟ|q ~rf)Bdxߞx#iţ{~C]g4>gϣ_?>UDСW7v- S"U^꽳U =7qo?;pwfUEBHB)MYeY\53,2uIM!@eV۶7ӈvc9;$o AN@bi 3`„!__40r@rll4lqN Tu %p"إ0&׿}P/*dm݃5*߳@L-kW (;uxkuq-}R۶y^g39 %lEU!dnU Ǜ7qHGodZ'G-ۆsVqJ:5@P \Ov>t?n?M1SryϷ-{*R;y/QJ_n.$mOʋde3>~ =|?[vbigϝ?8~pAE=/>u7oG嚯6.(󢔎 ftlzw7?q3-LG}3/<=:?'~W\So y{7~S7~ieزĉ9+& O 0I9/k"4LhH$}nn=2U?o9RdE-!e@y[B7ِ .Mh: 3hS$fyUӧNls\=Z PTĕٍڵ/.,,,**(S*ҙ5Y4MDu"45l/]6cq6Y#ӥ2!6 3!$,G? cW~79z  ?. fuzk>O=<[QJIH<(E( Jie9R #)/\3L8~τTﶇv57/~觟v测gn K~nٜg^[~//~{귟H4x]~s_Aiy͙+>,KxDמxoʠR@)$Jj+.b)8ڹosD}( H;}[paE3}ws߶fe%ͫZ u*ݟ̘^C=U5r–*+0,*Qq.K>3|#$Gc*O:!5L fwlm#})[Jt D 9<ȣ \v>~?}хZNA 7&Qռ<QFh^elaIףa91:j+Ҍ'R-  )GlxQ3½]mk[ [Li” ]J)pB mJ_|qW[pƂUAi޶y֝$Kj DrTzݜYb`ˏ |2n&Fb-9wiŸa0z?g\ΙH{p_o2r~/{kaܜ/2m!as(''la,\R$RD3Umw޳/r=?l/gghgFݟ^;rњ/׫LKW~箿R>p]7߶KW=OG~JSf,bAOG_z-pmP@!GS_5gV]@%7Km&Lhh&HH0 AuW L%zH1XT-ruVm QBRţ*h%DJ67oUfAh >*U^݉Ԑf22m@5N(d?ܴy,-<-!2nʄ3$nu@.\"śp q97MStX<҈B 9TQNX+|dhhxݕU[6o۶i֭+9krOl_>{njW4edX}{ӦL\ @i ^zI R[[%D$XôIX]sQV=n[(p]s o(qai "c 2I ˆwɵW>?bZI%">x6U,z>k{{>weyֿid?u~MU||۫;l,οw-GUAޜ+?ݜ06 V^TA;c #_>te4k7@U&bQ]ؖP'Zc?̆5է_|5g/ҶleJ"3C6E yv?Y*EdzI0tzv^+kJ;pp)DHiۣ-{nsd^T;KŠ5vs`T/O<gƂ*Q@ږec,K Df.\Ϋuz~`Gp0GZQugO'=5_NL%-3 @})y͟+;چGmÓR+x%\旟8z{iIۖ-(m{G78) M(7wަ6YZ:oR-־g7(bXǽ0-<A ۴( ֠%Kּu% 덁egǀ%BJNB0MKt}jD->Jв$P6n=/)8%Ӌ}S=dmnm! V,X l޲y{s?P%pڊ馄-S&@HZF )S %%&-91 qԩr A @(X-MYK-_BsWH|Gykh-ػ~ÝGelU V1 RH%Ym;s.-8ڒ8QJHZ7`f$|"MdQ/LR1y:%!ohkEl[;ֽ|d t>4_|󯛏^Z |Cʐ^|WQ,;H4ꌷo~epd0iT+dDVW, 9cˎ>ׯ$_ʗ^5 *wkOsre41*2d=,(CLb5+jSW7>j{JUcݻwųg)oްO^_">+{Zae AX%l l6-S`!x.-Pa Y*Dl)W{O_Z?3SGgo ΉmKm| j145xP=]zlUsӒLiaBZmY^[W_CCy`琑л6vK~_da119ad2H9dnon"Y4Cn2S۱8V,H”tPؖ (ֶ-S4-PH0m9b27C .ĴOx*ʘYP!J :nj3H)B Tjǎ>?"ӟ>|lW^yi-Vx&0uJfH-50 'n% S #uУW^1i !S)OSfуe &ӣCC`vt8$`љ2:$3'h83D$a;.;0)i !)7$ 7 #%Qa@m䴋#f $50J57uH  q +ߺkIϗJm];j-[.=M%beKgmMD>zn䝷_UD\d3`$\Db/<뢏-;o߳FKʼf{ӟw|s`؞O]Ӌv INo8oQDz%Ja[eC?ch[3xx#ࢰղqiO?FM-?2Z{z)Z|Ze<RTRw߽Iw#=^ܴMƷ}`fIj&OmYvrhϞ}iAIzAyUg췜\{e!ZX°law||{lZ=4DbRHrg{pbZa !Tnx~~賳QM9$v<}Z=wss+dӣާ"4FJ #ĴA 3emHR[Sϲаl[VGKHXdRRb{ O_*]L5mo񻍝+(f[Uu%QB_xGh M|dna|?/XYƒą7mɥבL98jñ$"A Q L 1]iUJ۶3,DPgyMZIAwoNV LrFFaaaIIɘ7B˴pBN* 8 &9`r}ےҶ̔۷J*ʀE+#}M]ӰixJ-ĭO_.m83͔92`$La ugyl߿59L[\T}}@ص5vG@$FcPOy OSG !4R%sZۼ}kBG>ԟQ<$PǞG~.-+v}ޮ.<}NsGs ð ˶MÐMw|kε,)sE3TӔ))(1s% v<("TҔ!Trktt`6[8':ٸwuWϯЭၔ@uY5:3gmdDX_¥lTVmn4@ 4SAAÆiZx-[wv/GM۴%4 Ӳ,Ez~be Ŷm$COm QضY7GFm &K,Mp˦]Ͽu̎=;.m{ 鍍^|nӹW}dL9ro]~{s R !ݖ$׾ /;7{w?y5!k$7U=ڞYc-M/z聾v0S7^][_<җ>9o9Bg>h~[7;kŪaع@j@랼'饧Y ʃb#ڼ9%ۈcD@f6 عuu5?KhעԳ,?٦s>!RN`d۽/>sHx疗kU7Ac0zm~w`~CW.ՆpR_}'V؇+B˪=21 0XT8EDD@Cr>AMد (DG uN!/jn`YU$r`ˎnVոtny84$pRN4 Bބf=\e^J)t ;_ɞK){/!,!¶m&nJmx4 i'hrM'mĨZb$k78unY9FFM!mle)Z[X(i )wf}TlKGs#P0M (]xj}w߶vsW܀\q5#{6$idUgZ_«~ݔM7PW7~ޟRgW9 R aKb$l0zp݋3mOܾ򷗗Yܷ^^{ouiOGBoOW#~3UJߵ ϛÕBY-h^Ï;7>(؝QH}RL@ ؤMɔ41"DM)5IHNr2'nBv$` 3q !3E5HRLqz#6bFI)U]p.Nk9gZ(:Ec̎g;NRÇo1{v#e,H꫏>%ضdld-Jǘl !OEIeg|S~ƙ).Nc3Iyns-v!idog{},bM;b&*Wɼƙ2h^TDTs~`\Ȥ  4mi@ΐJE DQN©9:2]VX;V2ʩNQ'S!0PtQz=Zgψ8 _MY肨ټsmWwW9eOއ2ṗv6{F~ϣղZm-;[#[6-WIB0 mE_}-e$8082[ -lޣ/oHl>[DX<іۆXJU*ʄ- SfxڜwWY_hN2=]5:PQ{M[/cɔT4 Tr)҉ srh#>ҟ}v+^lj@#O_<9/]sNcK<*Krw6nYyh YdLeZ8$/^QU_UJGJ/{rh|@THʽ*6mzOw YfF&!$;_ypUs?#+{/mۖC}\4kzmԫ2u߳#}E~࿮~[CHBOSXs]^)ꤘ#9Ifi!0  H8\La;d2Ωjl{2[8^J$Pрߧ|y%Ņ$ F}%yQR?Λ\:=AP.@(6h elL$eg-z4={ُs4WcӳBR`ʴ23R¸4DUB( Xom%*{:'XW0R'̉ e:'4o2ѿ?~wמcI'}V|$dbۧ~{Sˎû=/wWm8^ض'd+]xy뮁+ZލeBH,lU_Bp[կ+eZ0FL_Kj[7$0ʖg 7j>32:dK%f-̪eiǟ8JR)qBI=TT;G8TBf[J ޓ~R>4wr,q:IFɩ3$ MeH HJ_Mi[NNBJPRPQEe…Kx1MUIDR 9ifWJ=m':tiG{])e)VVVW& eҝ6풓U^}iZO=%usQQfъ So~MMWk_=<cfmcI zTcom6^R~2ϗs'G(裏ٳǙf]v|iZs)gwώ,wQ2H(cـ2E^IS)%T!$a,?D!>iVN ׼H-U @[^~Y]Ҍې 1 Da3J2cx^::VW)` 1Έ$Rg367<*<ա-K^n_d޾+/h(W{!ֽO?X9mQCqXx8_0*ƚ -LFgS$Tqs[6?Wzg7uӣwǗ %:%b|L+S a4։$-@Y T$M, uï>s[#5Ejϡ+)@[ aLDضSWW^ɓn[jJ $^x߼}-5yeZcv۲znuQ*H! II <3?\|Li Hq,Q9!D>X-_PSgO¨pj `)" [1]5%s#:m?xo|^"_UeS UE큦wZ#x8hweTTWԀ `3<)2 QJ"0i6Lias );>-5yI DD)mBxno !eU !gw8A8 M8VqdСCd<OU(#2Rs@`<+PB:dcb=QQ RXtM%hLmJ@1HIKe1$gq*(NEJ+v[~/<-/ىĸS Aq/}/r]҅+䏾L Ty}~Ǽغvѹ%r(C[zsS!V̚PqBi+@J(*S сJ %D"pwTr;q+ˣ7&(bֻ;dF[x0DԼz瑘@ \hI8PxGO;=?a~tc{~ RREZ(8q:'g=Єs# NI `HN_rJtN5ae )]2)M 9AJHQ*?HI.\D7@JipEl,x꤬ܙ47;wM-N$_ھկ.Z(NSb:$kv.G$SGM1e_Jd}ֹO#~|I}#j+UrAdn ) /^wX,/_ )uu>NR\B@Q8j>sdej۷u(YVJAYT%VQ?C*_QucIq䙦1oC=3)&;L5D[> zBM(cV"6"ǣi2`J -kx_ҙ390W_T~otXOKz,) C-W|]@ѡJR QF)#)Iz:)ra`3msWzteAd ܲm_{ΙG>;iSUSJPv,iR@8aS*21F # EWS[v /i"﫯nxr9gMiea3OW8# QuMQu_qMciqցE տW>w 1,2Fdv;X&_~mz/]X3QBc֧ j׼e4o}TϮ{YCL(%JUpoFG C?e&GR*i(2f $  M;;sg^\1X =|_O@衿|Os y&H@xzfK}ߗ|aN{hP8˭%=(j=zFS8!N<Kes!&IhD!IelH([nuVVV.^XQ)%2`NՀTw@ߑ}])޹sOsE[XZmqqqoo0B&":6)7 rFF PW8JW]߲@gFGwij$/X(x`g_eGgϯ$ T=f>8^l1>f+{O\2_13Rƀ:WjyU%Wآw^tΊ7)4"d0o.m]|큁GΜ7(m{ >/LZ@(e 0`80@B)cSbP0^!q(%]zؔ:k޺|˦Dw72Bض^k>Ͽ_~æ]5cf(lSyfO^QyzwOBQ¼ .{𶖎ûw4Θi` u|]M`|q1)\WBDe̺c]G:Yܕo G#4k 999QN3;,, m)+[Pj4m}~kh|iK/Hf( %Y9SO."HoooOOmUUUs݂N$zAປyaQXTMD"HEew~'ic$AHu=K{;= 9f#Qp3zGzGVXƽcsN) K$dEť6w٥)lJ ByUsOPo=3*}fLqÓmh#]U(lΕ( 3J$J9W8sCD[J fBJgϮZaI) T!eIcyӟ&niLlvS/wvnygPB:ܸGMP՞g =n?{~C1+|³b2Oti2yxmMU8988Usy+g w<\Y?g 5issqs ]p=\Eb;w ξpJݸgӦMfBq7-0xf( %}\џZX;Yȕ3Gl-fIѲ,dFO_m5ksne9ZȪ%'~ojI?lڹ7Ks zuO>/xk>w/ҬhxU'7v,˸ ?zNJQzo>ri}7,?o||z\+-٦D{S_2s~ӍZߜ^ 0 @959U#[xu"XrUg+ JQ#U=@l=tz~xӇ|~USۆ՗};~0bw$Ls~wk4n4@j}O~T.E*q?H!ۿM?#O[|Ǿ+οO^wڲe+r?Phˋ,:ֽmyyٗ5.9CyeI$OA!@γfl ,@DVQyӮ]4)m۴٫N;)WW~o0.ʝ=:RrC8PJOo^TT0M[ߨϙQB )ŅQ"$tE 4VJecop3Cڇ*so{'FF]x5K}10}k?|P  \Q[ ac|wQa);¯|ƛn. /8/\w4ei)NӞ4{¸TE χk h()?ᅱ}A)!sd\5K჏KKxfJݫ?VSqҷ_e> m,yxic5Ater6ءc5Ig9^1g "ʩGa0tz_]?.=R3erej&Uꊛ#]qGMi.\x{OOSyyYmm k׮m:|L,*))9ӫNJ3u뮞ϞPaȈ{TF}3^L hOE*+"~Պu7@~ym(Aiuv{ʪ!Ͼ={7~n'rvdCr+=@7zwa ZأmqFk+fl M+DrSTDC}/0()e N41k"X\]]`4u]w㗟*[J`ӊ4]-}GFс֦Að%aJ@ f >hgw, \#ejJ=*"e!/Oٵw4i'P\^S_[P I;Z!N/-zb]Gz“_YHsޮQSW)D;z$@)ZwhMjjJS#ӊ3>=l@(SGJ,K cFyKMoȷ+g}m4&OAht=ݲ`[H+R E)≄ij2P-[ZZZ>`0{Y"Qd+ضêx<1:uFFbyyQJ\f*8E'(,,8::t8e +9|xA 4 ˲m^\USQ"͡v>mj*(D{vh鵕54b= lV ,[ܿuDDk̝U짶S%k;f啖Lb N|V26p9^XQ ꌠPB9񁎾}O~K E<%oiw}()tO j2h*IunpQ~QiHRo ]áiuaM %рWBA쁶VR!5{d˯^[FKf+w\]m1$ !mҙsъuv% "'PX^S^S-rEz[o_(W^1<8*/:$u1tx,6)$K*!H/' &cY1gH N6OIb:* pNbwYz#\D N&S` 2[̯#Us7Κ孷ml}ӄeι!uBiJ]p=\H~! O?J>d2 p#3Muk |dɒb"L+!@%0)APΡxTDmVv{ۖhgu޺u߼̚7U. R+V|gl{<6l)H${T=HD"Q٤ATETJ85 ["7mJDJmiT){Ai@(곖n@J˴hh6DPؖ-Q-5QQWؖa -[ VDBHWm{k?wnCζg/1?ؖ%CO+a h,V2eKEa$V1sIHaZ̟>@چ)SJhQ9۴%Jk9z>XG6}}07{y 3VJh}Kߺ}/_C_3NPrI<Ѳd HӴ@+@%PqR4mѹ+',3a%ՍE@)[rOn:0[^?s=M^}]E`R6!4PX9jµg۶난~,(?Ja_^Uh& / -Hu /ڔ %M__۴l'QHqEyv2XcCm @:YroYC9N@%4Y$p.\ EEEa' B x=6XVg8穔!|rc[ivBEtcJ`>QLũ7!g깢 r>eD Lͮ!aHTX˄_rm:,@־B $5[>RԒlQ8㜓)8w w teG,pN3oӰ]`1 a)%tl~%"Jeqxn77L@0חL&`nR@۶c~ǝAQլTu``)i:|dOse?PƢ26Mcf b|0+8Bf̞ ;jY x U0U-ϭy3T8wRqeNҲMqR""ɤ>%^ǯs B"HMMeYͭ?555P($EXe4?6vxm^/O_:?144s;6 4xՍ,:WZuGWn>]: Y{ 2,P?b:uI =vDQ Jd2̋Unz(q7ځ3_*:`0/]_WQ"!R2 9`Z5>Pu;.\D O Q[TZ=;MY 3 !4k@]!c\~, .E\aRBD!LK.q덣: $J 8 N5ax}?ʕ+Ol 7<+Ul(CB4SyJ7:c[غ`zI((t%\ sPi5gwJw6ES4Ww,yL&^w+<=FJi8 ӧOӑȔ2F'x ! |gyfŧpf\AS ''LN:{*V|53%D򢢢hu``+6mۖeVݲX,F)V sÜiRF[$9~d5[ q̔3f߸qc{{{UUU *.\V83ew:kˊ͟Oa…KiאPJRT,#$' Hkkm]oo_AA~֒G۴y3!och^UAJJyy{PHcC§oprM-D(--⋶nvho1LdLJ*y箜1s[x<===}}}PV8nJVH$ sTxRJS&LSfj-9_lY$ٹ}#G,r Kl| "4 [8H\.\"ſ1ѣ8ضKJJ|>ŔRRS?EѨ?uém>?;K&rLy)=P(,(x[΋bXM3܅7FqOCAMӤ1G,))1MԾwǩ{}纮QЉZ'򦼑zwRQܹsg] rCG@t7.\Dog͚9{v㩲_9m;jeYӦUTJ!N}Z‚Sۿ@d_\=.!_sB8-*7A3oGDaۊvK.Nsr(!J7 .?O 7']8Tȼz# B΅S)\p1`\/B"J)=V)I|ujR.\p… .\pH$x`&=5SJu];۶=:?uz;RJUUUUuD.4 "N8م .NDߓ:$).\p…KpqR{AJ֭ۺtT=RڶzꢢB4Eٵk<ϩv*ZZֶM6jTld]]i-0M3{SD x":PJmO7su]wqW .^ހSa/821p#s… .)F<ohh5k"c!dsJ#8zcSrL:dg5M#ɋ%+/w:{?쳉D'!$L> 2F L?cW^GSr<^ \puJ^~}WWW* eee˗/r.\<(avvvRB }dEa===a8YNCR]׋JJJbs… ."oyKƨ ifc28c0dǀ(J'!D״@0h x'Uڸ@x'c1//QBi͌1DjժMp}\8K=:ySzH9oR)!qA۶+++"qhxkddX\qˢp8.\D8I<4 #G{ 88KRB\RJ&M̺8,ˢ4he b֠4B8 @/?pΓc3≄(RR<{s9'L>mдiնmMId.!!t^vBǪUzqY𵷛@Ji Ú`|t4;3-òk B)F=ᦎr>)FX\Pfpj"!\2 pqDUM6eU6EǢH͖GyvhBRiJM;t0>Y0Ov9'NRʹJ#!g0+Suz^0p߾}R646905qtoxJ9x&?I7j1JԽ|ƌgWϜg6N7"`"'+9gɇmUU)'/w&Dh4wqgz/l7lml0B H@M{or !BޛbJ[N^ɖ 7O=;g<0|C5!c^xdVGGGYYY^n>p]coo3Ƹs1Rc@JIOOn߾}eS=U|ob;MS=tfEGsA(**ϙY`Nו$Hk*ER/U=/K/4kİ\E0!(kE{t>pOO͛{͟?30³t6c4sxO~VxmUnn/;vX,YWɜE)=pr8~ߦHO:BDQoZ 1->x7)SNJ)-^XXiZ4u]A7n쪬,(GA=Sŕӱ$ySpƼ9Sl@j둶#7M=: !:<RSޭJp\]pVi Leho b_31\ʜi|:G3c8n<oii)((D^>ǎ m; Q__]]3`(q_ @7oF w!W>摴YU9a#[eoOl+ƄúH-@"B䓦P}g7tw%4}wVz- ͛{Rmm؇"oQ*cś&5k$MKUԿ}<0cd\xp^@PaVlyft!n}iOwYOMӄ{-;~}׎WO$3Lwp!PsC7m~765-ycIEeEaa!LpK`XDZ7y1c??So>3n AϛRsZB{O_䫻+*#s\=Wb]2jfJ{H;;3[m%8O&MM:R1ǚ:;TUu+B0& !3bH\d=Xcav#qL>NU^ciLGnp%B@p!H!8ig{坆 ϕBD"'U!lh_fDp*{.7ED'au*^v: qvp!@(^#Xd'N?vKج"993B>F`̈հ{0 ` ?qⅰRX ;#6h#Ɲ;+;hx` c5w\ RL2kV/!O^-@\7f0ڮ7ha8܌Mv6E/sSnZ $Q7#p9\9L`uu֬wysμ/Zg\%pavz'p;0D ũtu]xj”0/4( ,c#"8gS'BۻkGՂPZsBsϧ#ÄjپM/3BTn.0@YyYIYI4Bp!.]~ ˶2H+ „R̶븀`iH'0`msh>:B@nIgr!LA*K0@8\"kĂ!N#O9'_mBIe Q0!08SosAۻBQb}H2)0BOW9n{GLf&ϭ鷈)H!@B޸*2{ça19s!ʆ[{Jۺb␥SO'j 1VUƨQ55[Z{zjjFܱ''7Ka@BAZ/`4=AA'Batz4xA+SNi(ij1;81*S D;f&e؟x֬L|7F )6VϽj-ߺa G {U9c#> 3MӶ,#Z<k8Ǯ+<.nŻ;vl13Ƅ0oL5c33^R;cޜ)%Ad@DSEe%on!Q'3B>]kM?M"='VrTA`\lT0'%8{s Xh WH:`b{{Dy]<Ұ8)Xn{#`LUF$Oj۳vk O1k+kV}]~"}  [V=k|vwNPG srw^ zaCu׾ƦN )H~yհLS^ ) ټ|>X'XS˗<]vbcS8]mѧMb={JD4 F kyu_=9qt LB,WěNs 27ic=i%Bg(o aBpZw7&\!5"ťeEaq"N_Cݶ[tXFS7kQ#faAwٴ(Jƍ`IIHK"{ .PkzK^}+nOપKF0\(*..)+\ Feqqsu 27yvTƯ-vl)=20@uܶrْ囅/w3c+D5?l7*'ϟ76Inڰ5Z@z9Xp&]?u}\&XƎ=׎S(0U-5T3]a eBA T6C=i5<˴R`!jIPl$ !8hs:QTQƉw|yoÔg掘o@0X^ͺ&F(f Y5?0Q0å%eW^uuccþ}mFݻg: 嗆RG$2#CD !D"TilXo?u:MlXt>!``|Q]‡@;3mw(|asiտU<|R6/<!Qs%݄9ӳ2f۶c^zt&4% Ͻ@?ydlH `wl}_AMÒW+SS~?{_Y^JSm_ Ƭk?ٕN$m3o׮*F<~ ;ݗws/5\Ά:`Bi3ԈuE_]a0_r[2>替x~c;6g~iJ+w+[{Yem3\.NrEcbqu HeSiy?=mN?K#!/7BDlۚxtIYӆhP֋O`Wwsjo:YZ)D5܎5ꥏĮ=/>??jzyAhO=Z&~t f8x(ưŅ EC~sOmx.>*e(] BiڪNRYmJ oe՛72#B&N<"קbXj͞d8'm [vt8Bxq B߱cG,[~}8T5=e tCt':uFIeOD \6p$ wXFms A w]heV)_{ /qaǷ,}g]~o?&m~;~M] n͟KIcM${ ݯ_>",ڹ'6obd C9Λ͍c|>Qk AcrTP#0f, <<`h5aܳ/v9b<҄ p0]`i.BX$)@pۄvc׮>Ϝxꅪ[6 ʪ&͡⸻O8LݦmHΘ]]\]U:mi,ںu%)?[yJ4SЁfsZ'n޵lr|->s/ F3ET!gGr д*+J ÑU.= ߹S*\Mύ:lnA)G8)B)l\!:u];W M9g߿= ]>p.9{|iϔwWո_]2RJnŌ}‰'?-Z@ĹSO[W-7??Pѻ俿_"O+TWWVJqus@(c̱-r9DQU\a LU.ڌ BURk&sS1BsL;/Ϲጙ%;^|~yR/*9kZ8{3Ⴑ ΄ **m; V#EѨhi魝'0"Kpݧ!ܶLryvR;@s=[vј9 $\|Kx?rU1喅5a{"u LTUU(qg|a*\LEUBl˲m jB1N-DT]Qe[6c E:STS4oֵLq  X$ W,@*RT`p,˶]u (/`V=QK~NmnL$`{H=U; vE r TQ[6xFEQlߴ{]zxtycxR<1FdF$T6;'L22,zw~zeoL.X}]f;e kcW gzIeԵ}q Dj!PBHWgۣ(Ƹqݵ3Hy E&}!ŗ_U~{ǫkOx @c:\ǥ\ZVnjoj/0]@&&w.Y߿?ݖob^2"_5- g|ݔz?c#tcH-y/?c~o1}ZIB_#}v T9/!th( Nyet4r~k @1wp9p!R91qgwY}+zf\E(^, a"i3 "w H$R|/A(A[*ǗO>|wqN#Y읾'_-#孫deQ,[vmyyEaa7^ookkrrr8gG.H](C Ӣ4O9/ h;| 7~63W Jɜ9|mn(,Zh@!;/(s'?W6nýV-LM E8i#\1~.8O/Klu0$Mo?1k]0jX^0.oʥ+ӯU'쳯cKl5hX tP>j߹m'O0< .g5O:uiŸ?z$f8-+E_ӦΪmK{ۛ{L VU+B:AŠp$\\Wp kPa@i^ʼy*du9 /XpWl۶"|?L1uNzqsm{gq9G3 D|=n8ϯaro `LDg=No`qNimmQDkIJBj[;. 8B8r]s2$+Dqu͙]{VyI_nI-b\/gi:zvm7~E.ssoCCs_XYYRTRvhh@}FɈ1J7mlv8RrKJ>6s]&o޸_ T `۽*z[ZZް#0mi}9ohԢjǸ6zIb(Rw'(O4'H xIG#ghok)=-gtqBUq(Y.'#ٱcB !Լܳ|rUUoyznSGd@CNUSABBM3u_{A S ݮbD\/#B񜓢DWf*ԑ&PQB _~#㽽G򩽨TH3 +50&!۶c\a m& u{խɧ)-0 UsFu;mZZ+vjNgGeվp@6o4kG 㞶-[ĉUJ;P:lJ~{yL ywjwŷpZmj%!xۦמoq?o |+瞧>-:o}9%iz1;i;8s-4t8`A,1!c8w3Gܲ$R+@ް'۬ŠpZNʟ7N_tvlm\?↶|D w6XF__4jی3mbhX>_1tmXoښոC>rSiYNۖn1 ~WXN6-7O6w௶N3&a{-mzj_/A81|t.q{7ǒ]@ Ʉk^ d)80LΑ$8 zT7}Mݡ54DZ0Wmo[lصso[᫭kfBpعtIGASfD;sYaaaaaO:78d l>(wfzgQN#$ƘsO 5+w6-a9}}I;oL^,ڧ -X:LomoS}pZ@(wزW>XxٮG4-Wppkjqa-[Wu+2H$k+u\΄!:M%>hvLN!.$Muhg)MM ąOJGq hs\Eɣ@ ~D O 0Ŋ( /^̢0ƪ?jqR;c߆1ci]Ǐ@%?SΕW^yu_Jx«l ]d#1CT0?@a9'/~fGT5.~r\ ;osw@瓷O?zdw$Iθ7_}~kNY<%;ƪSθLVh2@e̊}Uʘ| v^~qԉV[g&: $Z7m*'l58lSW>3_ICVET<ۅDۖz*.do8D),.O(tpM;H;3 뺎r!p,7k╕*[Z:;Å#F+G=uƔp-Em bi S7E\<7;G@j'"'v8y?%1v`==zY(|OS[_<`@)B¸8Wnkݞo<Åju'?)H>@ض[DuͶ-H8h&Ruu@X.5qGAH-d Dy\!w!EApL&DD cLf(!uZpxo:g7Q& ~583 ! -_>fiinٲ|rW\qE~~8!<}~R(€<3>x@L? 3N4r$=4ю=?)-q x^}rܸ/㜥TerCDaޘE:@`}P1ƒ^7B'Û44YSw.X !6 (/#̲{DŽ"#}Ϯ+lnGWoSJF >Hi ܻ;&nYkDK qyg@oO_s[;LTSR#2E XQ۶l_"X˶^qd2"+uh"Qё_s…⋋.ptpZ]m>ACs귾ŒqsEؕvq8O&G@S[fZ|#D?/ё:wD._ro&GҲW^illUTV~۶LEz-#_ ^HLF`B%@ #Jc܈QǺ[9.*pHfvޔORS\s#%\! pk$b@( k_yk)51x*o"\p*} _L>#OW~0Ƅx.cL)# #(QlPp&F<ʚ]`Jru\tsoQ b랽/ϝwwt|]RC>H6kuù3'1Ǽ췦"@&sG~~?ny5?{Ϫpl0oaRг:pجOTL F9TUi! Aa Ԝ0eB1PRLIꓧ܆0Bp?6+1B  F3.&# پD"1!0gsۻP@ $8BX8B E[( x L̾IΈPEA#$"Pp* cxd𬢰[#'>ԗ`(RU]UQQ!xjLK[wdD)\(*Zv-`/#_)~y ?隗?k 1\-|Y\̃Uλ`$u &Tc# [th?oiXXF<t碑%ŵ9A:8Gk~W\!" Eihhزemލ LB)sL98O涾i]qА0wTnn޽{{{;$7'7cѧ-d(A &0Z6-WmA"zz6#qYi-G|}m]U 3[M,"ŵ%:  E'otedGySY?5$6+اRc.}=V'{ͨYVRp3 @qeH%!p$}f0䣼\Ɂ<pZ{hٶmsggװa8Y]_/-{7}L߷y_|d /ݯS/uex7' TE!AL>= N>N0 $}~b ,ެB ٽ~3\%`Ϩ++6bm}U;|cړFiEN;yBuAx}߾>XMo9);y>dn_<ʙXTN[x9Ơh_GkyRو^nilIӧVm-;{L T TQױoo/r{t^B1`Bf}[ZtnQ!`TQ*]!DMPJP~pA{v\_V'Mec(X˶'O&%>*!:!:*ҪHnAwֿݲsMٰQ`HSRM:%::A %X%b{?F4`35lxm8ָ}m`٦nZ=4?GG<5eT!D*qAH$;wܵkWGGGNNWٙ=Gnپ|綇x epzذP( +p*mKX|1"HvG+̄k Z_yxI+֬o?|oι' / }׿Իo$+?3=MIq]uoM$sC!Fϙ]c#$e cL2*9{!v NB]*EWߵwgw׿jIuFP*>JuJT4E$J$ !Վ6Z}Fe_-ގ>q%Ø2N8#lN$=|r%/Jqq>>12GGf H6'q% L𡮟ڟul;//p.^~ߙgm :8[o|ӧ<,~sQU*̾Yg>k&xaxR>?~4u\Hpee vzYsN[q*~%SN9ŶS.DB:~g[ ~oC/zM-q_ZWY91>yq]1jp5'yNʯ6wzCݺgʓ'{~a0hK<77>lsZ=YotIŒ/sDxisKa ANv6'v3o႓BasM@M5˯]X/-3sKxYڡ6(#ײ-a kμ򆖶[d GkJ^^ֽ^^$wnZHi6HOSƟoBS 03g\vM_wѬq>pHD Qq BC-?q qYpg!U%з{gSaw~N# OX.R6}~ ]ky꽉cgF/oS1jĶ:U}wb*^vcGvj^mQU/w<>kBcUUM}~ֽgՋ5W)J7]Id(*:AQTqTQE E'~vN/?]Xݿw+/.usUi?9:'`ĢXxz5Juzv~kfhw󳍳Ϭ P]Btz^\V:˚k޹~ܳ.L0dCF"d~w>h )5sVoX};DXh;0r0<w8`RA H;vh4:fx͈o^7ENhԉ_GI&ja(--F c?YTzˤLezEX<ֺo❆u-lZT_WV=«>Ǻ:&~L ,r'\[|]/'sͷ9DZm ){Z9sO>ܙQ O(%^ LJJ`Y G5ʤ_Py?P)TW09xŒSθg :\ۦ T!8U $4Ң]ֿH%(nҶ1hdSN,3TVVbŲm['?zTqKUCޑ ?P>r0BԔ7&8Ld*7_:\H˲ ~+_WUAO{d \Wws=7{s>2-EU?n9ԬGeoQgvӎ[.\49ƸvTm0,//'pɤF c7P ^/ >sy>M%<>B ~uV1Ι|9u'dXp7xygTkr@y3N?C5-+GO7{fyY ~rphO$ܲ wN*"E'7̽ǿ鯜RtMv*v]6zIj- *OmYؓf'Z}#sdoFLF+jO-ISW鷰Z&^c{Fys?vr.œIcT8bb6Ͽ;evu!mI_as66,΍D3f]֨@8T9sWX}'̘5}l_Qiv̻/3׋F>=ڄ\v޷L۔( i+W r'$lZ55VXy'5U]]6 \zI&*=tMM1E*rSoolׇsϞw>,/1;Tj`I cGU!@UVU5aIs D*>cSG~Ee9cB 1!* ;cvvBQM<É*#.( *L72-7nLOX૘}ws >sp +<㲛iص[[\嬉'͝7s'q]yqSܴ99E>uT@hL}ޤDroyD0!ۓBEQU#] e 1Ue8YK  |yB CWNK{>~ar 8B`'rDrl@!]m$䩧#iBN7}򲚚aT-{nڰ.phر5#)z7xzĉ^F替JKʺV .oZ恫3C,ad&^{mmktRBys,>u3.L ` l'Tư4wE&L,KӴM6۷Oe9|>_{{[o=p)-Ͽn޼˫]tvtӧO oEU}۶ΝcYDssѣqm޼ysnnN^^^2ܸqSOOϔ)S vP^خ]6lP\\2fhE`vMS1h#GOd|r!F\)e:KϏÐBpr\!@*pS#m hp s,q<L:"!@X4 I g(ٟ1mWTՔn5MS3U-;wl[P]-X@p1!]rYq)N1p˴9 k*}qϓSc]OL+FQvw򷧞}cYc swg^Y/dZ+w=f[t ]r ֚m3V5-;mL(>p) ܱi 2иs+j+Y-0Uۖr@T)YI[sIUc[.S?;"HT]'Lof!ɶ^^?f\dY"@g2!(tuo0a|Lg T@ĸp8˔x< A) 0%#nBSc" %Hpq8 LB 0fXLBhJhgggooaҹQz! XwW7缠@U#9"-ObzQRR^@1}i!t]///7 *4o0Af6O sH0c,f:EA9I"TU)!coSA^M TW~ q3 \K9LfCV5k8KS>76>"!Ft@3ۉbaPJLRB!۲ɤj{esp(ʌf!x_5O:Qnȟп@'ڸַނ7Jf-z}u*ӛj(LCdk2C\qA뤂euK$R|C,Yn8ח/ wtvbPd?`cBf12'.`ZC[)iAVuc;#0M3Lr>755EL1<B EB`pŊ圳QFyd`ӿw4z[VA-]- kkao M)5 !TXTh>FVBӴCJp76551Bh,{wΤItMK9 @ Y;1|Ç/,*$d<|" {Р xi2(wЭϛ7/_~ɒ%+p6|vi/ 6x_ j\Ǝ_{ E"1PliŌg|>EQIx3jނu}԰OQRRR(H mnn?$ٛLn ]=\7iY55#srtJtݝ]] *xݤIg n(J¶_ٹsڵ@A|C|^Ӽs-xOLB0!L  J$)Hk~iD"q8?EM|m1 Y=}zvn &_[_}wU3JK]67#疔\s(o920`ʕ3_|NM͡?ʝd>Gk'l%2d?@D D"H$ɱ`0{}R{8,Ah^;ujk{t3~baPg*+MMrs#(BiyGh8Y̯,<0u'keyqC/^ r lMQ6mѲeHQ@WTd.M$+1h*<~mv] ($"iӲ[GkÓBp!\!$ǀ2 *rN*H$= @g/?ba\n9ӦH{)+}} {J!QA2 _Ddo-e}pb T7TK.q-ɑeHyɤ\$D"Xb/|gs/ rrP^ Xښ6 0Mż K~C0Bp9̚<(@EE~2// xI4g>bS1r9.- H 9!T62=D"H$NC{nMC0`gG?B@!큟7\poظ*} {]r]ݝ+W@i"G(BqFmZoyaP$xզJ_ֺeckWa G ,8oj'91P=+$ܻ,Xj$}k39_y9m+==%c<3J$D"!֯etMQƫ}w.3&s'#u&+M֭KܪG-0;FqC4c3[3 PhMa%^{eO]G2qck2f$@tOVn`eTB$)HH S\KZ_?f赌z U"H$͕YϜ2Ew$֬eƌ̮ʂfɎ_]t99#sskssزgB㛧MͽnҤls /.+ 2wiFA^qxm|hݺ;/_nGk"jw۷֑-o- ɜńBHFp]uSVO9}H!@"9 S|$ƾΒm\a4D"H_|ڌ6.^+~y꩙]_;{)Pс"tJ`bQĢ.Xp\_abž&''ad0쎾>dR8umXukJ8w[O.er`)ҌG$Nd@aO,⼃i%9㺮+$D"b][[S'LEݱt庇&#LHq~@uH,oY*AEQD"H$7Wbe~8]twmBR_`YrsQa!*):־D▿%l(B){ϜYMsGK-> {aO! h8EAyyTLS>`li@)p~M qI ~ c/yC6^K0*X:l*'blE"aD z_|]C0AMPQ*T#/zf ݸm ɒ;:ֵ ~(g{jD ʉɳF]7АS_WZem+*KJ$D"IO012>Fב)Rluvj^5c !(!Rd {@>ߡ7Ӎ_{isWAPs}!0 PBB@!9j9̍ϙB??q~|[~p؋D"ɑ .?tc @TU48!)@"H$ ^SPΘ܃tAqAC]4ͧi !c8{Z"D"m ]/۳/̓qonNAm^+/H!@"9 '6mrooGHI&֭3g&}>c1$D"H>B8>1`{P7|>uUQ4J~1q#ZrM*+˷m[@ ຮeYK S{uн:˫xE!dO-`@)UUkPQp63ϔdSXD"H$'q=p֊q~nęg;~Db1/BP(Á@0 ME%DWŋYXe5͝[:bD.K(i>ivJa<8bxВM*>`ffHR(Oa`V[ r(҄|`0|>Q0&#!sx}Nky{"37׽319{D4MUUEQ0ƞ| $!OaUEӴyV]xJLD"H$\N3yvЯׯQ`@@ %-?A #TU@ =Rc:Z] !'r)EɎF4}=I뺦iRBj)H$!>ϲϷ8_xWP}{'`|]V&EtV9J$D"$\,+G  c?gL0v;OAa 0 @ŘxCo/yelHnzsƘ8d2'D"!40/F"7o7F$ ]UUH$RHL^eu;/G6-d @]d3UMU"H$Ay梋 Jm_{o0OM~P0_AA&؛!1B(UK b[Aevz\1m2N AS|<<۾u$=|3U!SLL$X,H0@u/%ܙ݀zz>L& 8(#@"JsI'.b¾Y0UH9@"H$ ;܂vCÝ˗g I.5;8>Pto76޹|7搦yg-z}u"maVVJE{;D 4T@(XG( "DQTEhs"L;|C"l̅08H~!Dc(i{eP4J^z v}}YVS,V_XOg PeYH$^\i~?~ވniڶi,%(BDrx yeW͋K8+JQKK{ë|D2rH$Ͽt̘?k;/F:>9{W^9@ӀA;N>9cׄw( (Jf#h줓n]偢||==iO8\Dpp %zӼq8gC!;aEQ !B1\6ޕlaLdcfOg6WTJ!+[@K+ø߲DZӓ'Ld+ ;vX~YxX|>0|'m۲,۶cGD aD,f!N;-y s+J.@;5kpm-8֊`"x±XD"H>!4UBuE"g1pX#]H\sH$P8 ;kssS7T$k: t]&x^~›Az\*I/8Xcx*0`0 =UVG/DqǶmq\,$#@UU/_K6mZt06(J^xvZEaRRƍ%%3H-h٫D"%'[-)t=cޏ?o002|Б<0h07mZ;z |>0 D~ÃyIgTQ3Y@3B@ ]9|KB] EK$ qH4 kO$Mwܸn \yK.5kVUUUkk+PJ=G`0hxFq08Q\ $#āɳBp5e~}`N j.uvjuutR"WYAa"H$Q D qnl\PU_(-3QO_Q!l J*hn@QUD,8**JQ &h&w/E.QMU5]74-@%c~υ22Ι ؆: .(Tב Jv̲z,+:vlM7M**2DBwygMӦMTMӼRMMŁu]RH!@"Zic!8?ߜ7g*Pfc 0vlW:P'0<DZ9kkkKJJo>cƌ@ `F&/@FPX5B,/=dhD GRybRBxo2iڶ5{vӜ9x&m[NcBJH$Hra2\ꩋV<HƼ\cP >332E+Wz}^˺iƌl2QVvjeER r]uՌ-++YpݤI Ⱦ邪KF-_}L}Au~iдLsjj v.ZT5ee"Pz`c"ąp@`!BXBX C0B@]E &P0PZ>o^رiEE\xx8<_tٶm;N=>*,,PQU53;M7  BDrDBeC)SJWt]דd"0Muj`Tٻv]]`vv&preJ$Dr>3+;ePD̮Ǝʂ?4GZsǓO~1(7wI?0N;D`&ve? 3~O̺~"}C͚u̙rkO8!5?Y{zTXפ Y]g+|>1yy B C@ێdL@qB0RIS?9!7J bNX WT(E=v_bŮ]L2~޽{ĉ .B^REQ ≪{L$RH缻8g .ޟu˲Ld !ƌ!SEcJE!FH$Dr$4rSnվ}rpaۈs sxrٲo~柃ejkhoO%8B7"C>_ʛ sL9DSB y'{U ڢ"4 I+,ݵ q0Q"R4a>bRCH0uMb߽su|`|4N($|$SBc2zfr-ƍ޲e-[Ǎw&I]}>RzЖヶ tLC9H ⥍ B]LӴ,˫8N&qJ2@K"H$#0(M@ $$۬?{ITeA>d]!RiF4xuβ fȶ~1<$ hxp;zo}kЉ&OVvFOϮ-)xҤ[z oOooFhp]  KUA8$ **y3` pP!Gg`,8M<[Ą?4 fC4BeY۷oomm=餓 nݺ]v3 /ظq'fDbЖC>$@KRVVMιW(//̨}%D"~Q<i1s`T2 N˶! ;M6ݼ(Ȅ9_"` ]6}zfoa($ ,iM\h|(@Gk3dGco(lYysO\+6mHU(+˜{֔)`'hrຠ({{5S)BP04MQc(xzP1O/N`mܹ3Hzcǎ:u޾bd2 Q`d<əD ɐD"a۶^!RC*?^V/p1~eD"H$G;_֖&஫瀱lv};w.;61&`\Z _{ 8GD/|B0x/g eV_]0@([d2dعf cPA 5*s̔JXB[z)S2Gmi)ضLIaqw*8ꬠ6i:nB'x8EC!oy&87K̘!/4BySNmoo߹sGMME]6mdUW]D!{;m)UՌk$>wX5cicB뺔Ҍkn H$DrT|);aÄmfnmBx9'cgd/t{"E9Zwh<|볬L\9.Qpމl:0乡h9Y ]]Uພ&"Hӷ))˺nΜ/~C*< $YC!`]]ct(Jun V<`ԨQG|P\\|%n۶( ,P% QPd1|A'|wBD #"haߠI$cI| 4 BD"H$GH(+q>{gVۣQ ΞImg$ U!!Jo6+~8p / 𡨮{l\U~߹{c^ljdsiIM@EilZ@l*hFeOT+B ]%؄`8z3s}gv?Bνsg.dw~wT. ѦK7_я߹3g1,v(&NJ1fyxڱnX]dµtXe |3 nmmUȅ:~,&s) pp8ףqyRI  ذ|?<>;WY8@NI~hracG/^ٸt+!>5@0@?"kL)j^xI$}ggylo5 ~_eˑA m?/9cO^ 6?Жު٧j>Bex_ ;zRD͕ @w݉~z6DV8)Pibp<,,k+(s箚7Uo?A?)MV3\uϞ)οfzҁ~n+LS"v5VND/9췿lΟ HiQtE--ql42\n7nEU,2SY`cz_:z,}jX{(F8n̙ s\$l+Z$T_h~MI t{ť)?RͪU?ccY"ZZȶGJQ..^TU{㬹\])Vݪ`,ƼK?RY54DJlXWJ%l[nI: ~GӯBIBx ĺ==xo>cns\2 ܲ,q\׍X,Ssu+Lizu@ιBJGR~ixx\i}ԉlPݩq)Ica8:BѺ4 ,?Ua'pƘRfŠhE@T8G fRTS)a/D`fDWfth"#@3)QU Xs fd"A>3p f5 6|O(F7͔a$׮qp[wwTK)cs ^[9,$|0;[[ӖީSW3pP[ cJ4MYҏi޾\.W, z&czz0\J),Ka& @'eJ+9/( byRJ]@XA|GtaiYyR4MqU9Bbms1;A̘,@J,+ z u])mzj.A眈ti@ Yp΅e,Ao:P2p, \5,ǿ7h!>Uf`p!xIENDB`workbench-1.1.1/src/Resources/HelpFiles/Workbench_Window/Window_Elements_Hide-Show_Buttons/000077500000000000000000000000001255417355300320635ustar00rootroot00000000000000Features_TB_icon.png000066400000000000000000000142061255417355300356700ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Workbench_Window/Window_Elements_Hide-Show_ButtonsPNG  IHDRc7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:ϓtt:/YaRJ9r`a."6GWAyÁ92QASؐkr)ۍMTAHi$yy@V^*|_,="czHUxLc&$Lj:Y}q,r}Rl6k4nRײbf5ji)>%q\F#A}LܙV/nZARض>5&n`^w] QhX$ڶ ~>w]P(u!g@X.ŴaFTk'ĈNh4V5B( CV,+%ur9ciHl6cu}avCRQBp>B`/YDV"Q~?3p 'OUuaWҔ |O N_-=±q\IENDB`Help_button.png000066400000000000000000000147661255417355300350130ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Workbench_Window/Window_Elements_Hide-Show_ButtonsPNG  IHDR갧7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:'$(`66+&87&:#IENDB`Information_icon.png000066400000000000000000000145611255417355300360160ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Workbench_Window/Window_Elements_Hide-Show_ButtonsPNG  IHDRc7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:8_!$I?okk Q+TqB]K_Y4G|0K$>hkcR> 祊*Bx7 CӴ$I\׵m;Ygg9"핕i0 ^7 f*SZFFaxwj,ZXX8??Qp[ZiIIXr{{{kLl9$IYiij500t{{a{<[,4iq7 ߶eYN4hn#U 777Vn+JlXV`s;wvvlۮFgu5f@2u-"!N-b\>;;__?t-nE pr(,\eRt,%yy3Q}gy<4M&p]sD(xxh6x_66p#(eb q~ eYvXbA}߶mLLt~FNNNxj>==@ѐem=FRh4FEQ7UUE1G6s@\VUZ!!DeUUE䊐.ܡ(i`ٷf Wi0)ҥ?}MY:5eۼhIENDB`Overlay_TB_icon.png000066400000000000000000000147761255417355300355470ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Workbench_Window/Window_Elements_Hide-Show_ButtonsPNG  IHDRc7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki:}*\oX>bQ߽w/-`J_H]j*CL~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki: &  !"#$%'Root EntrypS>K@256_8f263a31a19d7635*C256_d288a5fe5c9386a8*  /\JFIFC  !"$"$C" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4C9ӊvP(JFIFC  !"$"$C" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?.+qx[uXMFt8@kVTm.u*g(s;h}KE|\" ҋT#,d0ew^6I$'|Y7g;;c޲O}Z_Euk4b1%$rdr? s{‖_cfw%^li{ 3rISqmϩu]OzE+Uxd5)\W5$SQn)T$)˖n]ı8w}Tt7TfQ,lgNoCgz xqVeX"K2 y{Z㭘]8?f˸2+ eJJ ۺt OwCΝ)5Xo-坹{8'2˅BN *OOp@ ֠D4K o\=cJ:W`Z~ Ͽt? GA;Sk8Iw idDdl[#hZۯm./10 R.~O`\*swCΝ)4nӿ%?ƾ_Kxn{ag[1F c1sӚE'QYPs0o8uQ,ΤU?(6 )»_1gXޖw%~*>5:wu: مYZؤL5q5?n ^Ob%S/EHeqsz^nenogK]Za3B>RZ'}?Jèk,sz{M3.֕8'?,ξN*SO!&kݹx Jc?>9'p>=dՉ_O9ew7q䢓qWGKC6 2IOs95~|ԟ,]R%ȅ7G;0rj[_Lxbnyۻ;VsX6ݗ"yAa[n:}8t g*W;rˌP/y95\OGz>Ϩ"~TqRVGG#U=[jۼ2&zǮIڤ#:s QԖq]C6Q3ӨOIi즴}_ŪG,ij4̊RPiԧ!YB0;<8F~BC"*Maܴ! k|dk>IF {9- 拕k-gLbvD < G1Tst"G2du{vme{iˣvy:TbYDO1RT#/z >kݚk[!a&Td93NV-ΩE ;`F9 wgk-]vnHd yƛ 6^…S2A=9G=}M}%[BF + ʁ+cW/\0MԔc'UᣅN.ۚ2o-.zfhwD́Qmb2,h>R3xOf躤߽y1b6<1rzk+Y%!Bꀵf8Rj΋I%o3C"3ڒ˩8I%/@2yqFq 1`pG"%\=|Uwb$]|Ő(E?}qs=j+ec.o"?`Syd3?Z5h4-<D$.r=q]cBSgbqSƫF饶!~ D4O]Kyk'vC~?\? gul~$e[*R(#uo?+r1xsu{uOuOkCH/y;Iȟ k\fqۜ F[0Vt1ʞ9a* 6 /yL_8GɏV9?.?$U}7CхyGVSTu뚥ޕo#4#mU;AOYy}Oҹ*HOKh!X_$Nc.BFu@#_ MՈlyCћ5N'FAƴm|d&XYf.xԁDr Wȗb܊xCē>-d̽i}פhZz$oC3C">ZOW0C[BA>Wj;xXJ-`:̾Z+J\9R5"ӾbAMYQE8QEC}uoci%ԫ17o>WlrNnZ5Z4k/e1u>vsn5 B0+6dX$9N9'>p\[ IVces2>=*;O S@qO ǖm}%G!}?,4XL/hy 9?ZIuV#4$mYNjyivY[O0;X2@>`=OW?p5Kψ . '<-cQ’Q3ǟȃ괻~,մMZ$ =21?i󳼆Xc3{P_J|Efay2e0\`qN*]Ho3kVb12׸5Q\֕(R4VgWj\jEK.Áǿ\ԴH?9Tc#?v+ X|dqDTkff܄ml`i~I>^_r"(/k.'C)$1GoZ@Fŭ](U6y8ݣ~| qs&WƫoFLg ܿ=j=ܿȯY]Z"ugȘ]Za@oOoǵ_[ n#1.1ggH/$HcZs!vXGTC,*o \aGܣЕԕ> a'zZk/)‡OSQ[cV(alF y؅?Aw'upD- m~*6.mN61IƄ"7 AǢG)ckJZrTS^_HYSubpHǾ~R~)5!%1WxH'Nz+M7GEB-4K!ߜsXZ?Sڜ܁J,ao*({)Ta 6K$0 AfVHbɨ*GZ?5[GFUb3s+ ɓVa]EFO>?{?4|_}#g|ֳnPIx *yWsN_ZOſ#7&nab0GoPK1=lt`01 R-ŶzO^PEkʻpnK٠YφUk{?4+u ǚ^n0Ofyx"O,6HϾkik{i O^2k>-6vmJ~h4{?4+N/r:($\SdXHAy`4bZPE9>Pb?蚁9GB=n ?%?:<tw5`iĹ9یlq"ԇz~h=_?`  [_%p$>Ҿ}e0U,$$=l䏠4ˆK9Wϓi WsNj9zst_HH͞Oӌ_ik{}vsr.cUOzGhեX}292,M!UTǎ9UijK(h_SJ7*;N Ӝf욿 KO]s$ҡMNı2;p gcҺ:NlUAETxOZMiiCj d[9 <}k飜_SQ] GC:PZ/$|  rx~iS? pz?Zm,ycWxh-Ud}=y}mOgoK%H?Q\ (o5bKMFz(O?kzY٧'sQ}M{W4-wQ)7*O}+S^Ӵ1i#OAq\n[>8!9ϥs ݲG2`U"].y V~ʵ|So4CHu~}gB@1_6+{?#'o!>wQÕqz:  1$ӯb76dOb>uIGݡimNs<*9 A iڃ[Hw~h__=궷QVFy؎*=jۚ?SA&%#[FThiABb؏u]ǍoVMXh(a\B nMCTc1% 힕+N@o;Nv>ϥT/Y?tR&F=}o.Ji$ C7zGG hX9xgkӅ*->=ǭg꫻LC5޿ȸƤcRnK!,)󮇘aFg|5ở|GSRXJ7vYYᖰlo=i#nL}ԧtF=W6:ng=ާqDGjQ).x-7m\W.HHtaOƾ^e*XA}+i߶2_0BĒxex?@v7s&n"ؑ'ok̀u<c1 ́W]ZWq~Rtt &1@<3qk$K9U=|}vO,\^[Y.UYJp=k-w77>/"Iп bw{G]>!hU]jV0.a^{{"fR\c0ZZ"ZZrCx]F7m Qe FkY闭rYuؠ M[cf#ԉFicf(Nj_8WT@?O?m\Y_E++h^moN-c2 ,?趓xIiH l~iסO<$*Rpxd= |@hi?g'+l#fΧi&?G`OQT(υ> `I~:a@>|9|4HMzV<|?ۥ%ޣ^9>ރV^AСKK e1=؞y^jSlՊց-$W|4׳wU|?ܟ)Km%ϥUoY@]ݫ55̡Ht4Vo3Sot"?~P9,4k~?! ۅVɹAmG3_k%fFQ; 0ݰ4ڀ>!<|?Ծ9$pYxe}F^[x:hEO ]Er [+_TfG~AETQ@Q@߷o액R&1VG߀57%_A=z?Hė,B?ֶ睇>oc>9in LH]G-gดV콫n⋻I/뫍9T Okj (ncs#uvBX#)&})Vb{ q]1c168#i4?-[&H^AGeRʍJԜ1aǿ]:GisԴ;#(?2{ڼ#H"JLٿyf5C) #◆_>;4#*@nTG}3f("W"?YVMclw_¨/dPv#"_ξEoF~6P&:chI%ƿ"jDtqoξ%Wb)X7D5oaP _ξXk]M,DtB)l~|5nBWQ\kÖCx-tiS8gQ_-m/S$ǪԴѰ(Q@r]]257F=į(A:HV"ǣshXK$ABԊgGr77u^__{/I_ؠQ1\"~,S?i]Rl 6 k{,X'&= p7M1^4r Ot]>FS2݃OӃ] ZeaŊyeHfff@><@~kFI>;-?Iѡ6My^Vfs^a odUT  1MFx)&B.G@p dIN_'\W&^?Եȷv{D/4x'hkڴ+oG_ƹ:=v[;1:cL[ ?3 >A6hW]FrHhv/%y* buD d9'yDxPKo 9"GxH.an}a@NL#FUZNs[jZ+,|mk2uFQiZsn6C%j[ uHTNŝoс\`?C] R {Kk` {J6q묺fm啥'Vz-61֧`eBr^6HbxU]X8xI$XBrjZDl!y:ĩ>g}Vs 4^E9v{_xB!]>]c+—{;-|F=KN&hliPFq 72L]}857tbca&veQ|.y&yyKt꫙Dz!=< X{ =s oI>ѮGmCYm0^Nr~ENؿco?O^/TLGio#L:D}%EW⇢QE|/s7 gyE mӅeSױ z x?UtyrԨ9Iv=^q swx;6: p? Zv0[b(P*\iғM}h{>Oω7+. :g*_(BVV Ԡ3&A| WIsw/uOG*_)jB?a&A| /K_~>1|l[h|;n"#zKK;wԏHk Qõ2zX ׹d?LHY(4^{'W>R&NdvƃZ] SPL"ti6}[pZZq1|z_sw/uOG*_(AԲ©`Hrt].Q<W9u ҋWd!Уߚ?W|BGT5ޛ 6MԄ|ϭY<;GϞ,"oON>!Уߚ@~k㞿]:қ+p>x8##ҽ/O 4K~:OJ?{,Q+.}+Zl k_? n4xW ξm) i8< g/L?܃p?s+3ėF, ;'#?Gco*ɇ+ׇBd+D_%3T|Yfx.ه_pd?GL?܃>a?ċXjK&-n?JƼdckM rH1|dwWp4#-oqHE"t``zOg޿!o}N"oPNHՈ0 ᚼ3AWOEi4ٟdC"=O4d'ܚ랧fЛ%O^'Wo᎙k4BJ LW{ErbMүU/*4t(((<3nhzS˫v1 VL ޫ;|Bf$<g-D^_>s8/=~9탫 Nn'ܫ@rp=x5׋Gdei:qũGP*A }֦ԙcֵ(Fg@TawhfY5! ۼQ0:Ր^hA*0۾~~{9lW%'RIOׯ*}NMۮ&$17$NpN6n35CP 21?Q\ɨo4|)(|~} Vף)iUvv?pk`-6wUC!"96 Xv`RE-,'Uvk+-%$WP99ϵG-/LmP N%#]Z!"E ]ohPg3FW66;Nszqޑ/e71/q8Oh!/3mA~^9eDc*F3Z=e̴%FXwc6t_jd35(R[x VXEe?jJlI+װn5 eFdͷd*2q~$>`R7l9ʌ|?:a3#:2\fuk[Vjy%YMh[0IU|g\h0 m<4 4&nIp Dgdx^*f͎]|, q֨wyU$8i2cFΙ91-ѻͧƗFͯ!S'LFxHhߵ‹WSF Y7]$Ԭf$>[1rd{S e,,K?AhgV pTn\{^\3ѻ~g~t?":s|?@7/5Ybi!Lw|sOcL? rJڐTin9@6W/?Y=vU6W/?Y=vT?ƟlQEb0((ͼ+cƋ`&i$p =+a4&vy\گVRuX=y|AV=pz53 r8U6~Dp[Zy6]Ir>oUhh>@o9]w>3 r?gD?cT'%%w@Z";&${J`=`}1QO"]OUeɨ~or㷥]tOQ? '/PǙ$Wt(GT@ G^;G/o"j+&Npg tǧs?O9G3 rGA)C\EWUbv~] ~?^iRc}SR[1c? '?O9G,~?W9cmJ s9S1MmƱNqUs?O9G3 rX2ӥ[{Bn݀(ܟaK "wrLNa:'(tOQ_ Ǖ&ێr͜qڱמ١ ^_)OtOQ? '?̿E75A @F,9vbn]X]7_1=y?gD?~3Ή29c >h^24_R#(g;J'-./CF?p$;@vEvƤ>^&}vVۏE^}̾kG,?S8W6yN``=AۓHc!#DmGS (?%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?uxٵQ й&iTJ_ZJSxImN\zpjƋ3ߊm>Kk$Nwbu3rk0#a\gĘ 赯o{Oڧ .P'Oyfc_=(Toolbar_icon.png000066400000000000000000000150201255417355300351220ustar00rootroot00000000000000workbench-1.1.1/src/Resources/HelpFiles/Workbench_Window/Window_Elements_Hide-Show_ButtonsPNG  IHDRjx{7iCCPICC ProfileXPTAmfs9sX.9IVDT@H ( * ATQD0ܪ]Ulfz`)!`FG񸸺 +7*\!y6H/?Ȇ>~Q!dB5h` dNf8yH O&抟/`O!0B$}'֗H[E =Z?X s$BB~r8E|)_bI 0AQ$BqBs z %YC~?Nuá>V8 hb?o7N83?cb [oddM^`{Ao b`4.9%xLC}$xddϟW@ ?|^\m &96~7&2IZX"@\J@C`p @82@g@U Y02xW A V!9H҂ ! r!" @IQ( *ʠZj FYhM5FgKѭ[i2a0nPLӌƬ`v)h))()()r)(z(&))vtXa&=-aOq8Ng ƕ.p/p۔b19M})?SQQ QPQESPZF@-EmJGJ]NE=MFFƓ&f=->-6vvNΚ..nnM/DoHGN_O?Hggge80̰̈bf4e fb8DϤt"›I\|;!~Yٟ$s4 ;?K&EYVVCփWY!lت؆޳3g?qqq$rs4 ,|υ *[;[&]O / o o-.0#_EgX~B5nK$'*ł[BBBDž  '?E/S ++WOI %T%B%$$)%u%c%[$_H,ҤJ}vΗ!(Ci5Mݐ+7O& PHAR➒RReeo 9Fl1Ujj궚ZZ'uI K|ZE--oEm^mvK~?F]Q`Vݏz2zzz[j}pcL CzCG2#>#Qњqq $dΔ隙Yِ9yK 1HKi˧VVVWig666mQ6嶯dF:9:;8888y8wr6p.p^tvIvz n~}C#{yl|쑃Gɤ}9|'3p1c-sՏW@:1qRd~dgf>%{~N@DRnU*/4avӖ y 3 :3^PT]-)^,(V*PW,l\bGɊJ*jꝚGƵ]uBuE N gUΞodkjk mZTybq_T˲ X%]o|QG]t}NùO6cy[[._#xĉY.r,s, O/+(,}PTQ UVךyj(88ܷfV .ma/^nm1qY燮װ,=BzuoX H@Їܨؘ;w{8>biC9GVx@z"eR嫖#+V|x#'5u}I޴*u{]RA5\'?[ܦѢ%Uҏ12,SlHv50j{ܛ|.$x#‡EDIbf*R8-E۲d(-d7UTj8i*h1jmj]ѿhjxΨ޸ڤ̴yEe*A? |`9.|Q[1q4$$%%H)L<\s:hAc)Qlj'NZefg b~(zz L\BRJڠƢ֩Χ>!lnceS˹ͳ-/[?^عDwClrUkF={nG?4p|tuodztym&,'OLG>};(qž/FH|yl)UrQ{v>>~bY\g`eus%rd}!S ? @C'9!0@䵃*T,dLO @ X/@/R=e`|p9BP6V`0q5,V }&wtv 72"ч@: (T ݀c1)(*Xl".҅>1U/y>JZvr:.zzqN=)F:Sn3OdE6ePXZ.y[g̷*#&8+T l+ 2+ZJf%&)=qbY<-JA*.j&FƚZ:DXw8%,4-ll;88i;v-x Hz:{{w}Yl3FAB8B݌`$EĦǭ[$t$q%g>Y O3cޝy?@Rx4\b*FvW[ڴ/\~ՙ}Э|wf__@iu] J3NC KͯoD߭}e#GGV$*0D40 ^] /(* epmr _F 67GEGa1-XlK T{_h"hi#iR1޴ьq)Ooe6b~NI9-לw?H 9 Td%HvʗʒtY=,0GU]5YuQ !MQ-muS]W=Q kFsۦfVM}6Ol*NG[]ܕ<y{}!8L :$z#!?>1y Up]h1(#nʧsN8[FV]iH*Z o$]hۿ4ܞsŵKf^$ƆJGB nsٽ7?ysAmo-i-Z}!|bqdNٞٿB_ҟ"@8 A7^mt!(*ڡ)hS`Ͱ8 "1#(KT j mn0!) Uz,6 #^QzRQYQ SkP_imצ1,^ ?̂bi`5`]a;ŮȾȑéŹUȃ% qlr,^K7K:*(#& >/TVtVRQX zMn-im}7h|&֦f5+VkQK(rQM'!g7$r`K0HƨgFq EIR4p1\F ֓UY<ٵ9<5|MRg<( (߭,Ki: Window Elements Hide/Show Buttons Window Elements Hide/Show Buttons
The Hide/Show buttons for the
Workbench Window elements (Help Window, Information Window, Scenes Window, Toolbar, Overlay Toolbox, and Features Toolbox) are located in the upper right corner of the Workbench Window. These buttons turn the display of these elements on and off. The buttons are shaded gray (as in the images below) to indicate that the element is on, and are unshaded when the element is off.
   Help Window button

    Information Window button

    Scenes Window button

    Toolbar button

    Overlay Toolbox button

    Features Toolbox button
workbench-1.1.1/src/Resources/HelpFiles/Workbench_Window/Workbench_Window.html000077500000000000000000000033711255417355300275410ustar00rootroot00000000000000 Workbench Window Workbench Window
Workbench Window refers to the main interface of wb_view, containing the Viewing Tabs, Toolbar, Viewing Area, Overlay Toolbox, Features Toolbox and Window Elements Hide/Show buttons as shown.
  • In PC/Linux, the Workbench Window also includes the Menu bar.
  • More than one Workbench Window can be opened at one time, either by selecting New Window from the File menu or from selections made from the Window menu.

workbench-1.1.1/src/Resources/LayersPanel/000077500000000000000000000000001255417355300204665ustar00rootroot00000000000000workbench-1.1.1/src/Resources/LayersPanel/colorbar.png000066400000000000000000000054271255417355300230070ustar00rootroot00000000000000PNG  IHDR szz 1iCCPICC ProfileHwTYK/$D: RB/ҫ$@(B]QD) 8PdbaP} 88 Idό;;Ϲ{@ 3`%҅bQ#&60  2y2'7A׷f:7",S\8-W)Oʘ,U0J"e*Y3nr y29Γs7H2Fe\# $Ǧ9HrMI0#%}˾`1X~)vFfH,fpM6,/?7/3C8T`ggry(2dEqgZ|Q%, 32`Mj۲J:7z>e}}yI3rss-|?:}=Ky'r$ibn܌ ̿'aOBYDl$YK~D~@UTS)QVP\h许D1_\ %[ZZm)errVfc,ňEQ ()g)TOeS 9 C3RhŴohI<(**'TtnDKGhqßeNksިjUTToWcyPT{R7SUU߫~N}B(8qO4 \__sJK[G+SRք6]M;ELUg@LS Ýƨ`2&u5u}u%uzzzYe=:A+ Z  YɆ Em243V537n1~`B6q527a1e1jٙ%U]1{-BzLӝlaY--[vZ>2ag:ͺ fMͯf\jss箙5~a=KtcA‚} znn [N _:#$~I$'J1*>)Mgti4*fUXXAlW6.*1njע]F o-6^|%KҖXh:!:9'SϙZ淬f$}s.RhKbiXKΤd [P%xR&58@LZtZ[:.=!"Lfhg,4,̔f9eʚ]bg_b"(YS67*ryfy[F^Z]ѳRw底CWխV/[ݳFM>k#K]z_m]U`xƖBBQMΛj76 6lr˧"^ѥb[[/}eUW3 ؗݎ.~k뎃ʥ;vv1ʊ^Zb݄ݒҊJnV{Thly^Zŵ ݩ7/ߏٟICTC׬? ;Ԭ\HZ7]ֺ6z[apXr :p(hwմSۋ:NiWl1c=[~*'JNN9jt3Ig{?sFohs{?w˅.ĺyrG]v?t\quj\y o.9x+֝wxw}q/*=,GۤCC?)#OOGuFlǎ{_}ȳg?+\w?39Bb׭/^x5UTԣ鯧U{{]ӹ*>~Lo~ pHYs  IDATX׻ Qг"42 3$1]3 pMRm?*KbS*zĵG46>Y[wQX7w]@ @o7o{;s-IENDB`workbench-1.1.1/src/Resources/LayersPanel/construction.png000066400000000000000000000104501255417355300237260ustar00rootroot00000000000000PNG  IHDR szz 1iCCPICC ProfileHwTSϽ7PkhRH H.*1 J"6DTpDQ2(C"QDqpId߼y͛~kg}ֺLX Xňg` lpBF|،l *?Y"1P\8=W%Oɘ4M0J"Y2Vs,[|e92<se'9`2&ctI@o|N6(.sSdl-c(2-yH_/XZ.$&\SM07#1ؙYrfYym";8980m-m(]v^DW~ emi]P`/u}q|^R,g+\Kk)/C_|Rax8t1C^7nfzDp 柇u$/ED˦L L[B@ٹЖX!@~(* {d+} G͋љς}WL$cGD2QZ4 E@@A(q`1D `'u46ptc48.`R0) @Rt CXCP%CBH@Rf[(t CQhz#0 Zl`O828.p|O×X ?:0FBx$ !i@ڐH[EE1PL ⢖V6QP>U(j MFkt,:.FW8c1L&ӎ9ƌaX: rbl1 {{{;}#tp8_\8"Ey.,X%%Gщ1-9ҀKl.oo/O$&'=JvMޞxǥ{=Vs\x ‰N柜>ucKz=s/ol|ϝ?y ^d]ps~:;/;]7|WpQoH!ɻVsnYs}ҽ~4] =>=:`;cܱ'?e~!ańD#G&}'/?^xI֓?+\wx20;5\ӯ_etWf^Qs-mw3+?~O~mk pHYs  IDATXV[p=+@fJX1iC.Ki&k?ux.i' -ҧо4cAiW`BLqlE&صaW-dٙof;?"LqL{@ln9D"AɚWDDDuέ0zF aD~Pϵ"H#EtplՕ#—Ixe"!ZK:%V"!^jtHY285"AK20sC &vsJ<Xe xê$& X]]aLS$@+-ꥹm p"(ωoCF/ ."~(X}a8+QuwW01VJϙd}>+5s% +HlN75E`\Sgu䅷;HGWJrtxWa8*vr?_1 Öfp 4t UU0; ܺa- "_`aQ"c @$PQT0B 'Ҍx@e z^?B+#mc_5%㣬Rvb/TMh^ln)^ ̳p65('3 qy.ў@"16t8/o7LPY.y7s80hNoꜛq?~.&w9?n23-{k&͓prJ鹁#3-5SnNc XA_{V,_Rle3rً?h't{pCDfj/X5o.˗hmg]R=YX! {bx&dzRK%%1U(q~v۲(|- vbv~YԩͩmܻYهJYcc#ln>뀇D|"'IENDB`workbench-1.1.1/src/Resources/LayersPanel/wrench.png000066400000000000000000000106621255417355300224670ustar00rootroot00000000000000PNG  IHDR szz 0iCCPICC ProfileHwTTϽwz0)C 7Da`(34!EDA"""` `QQy3Vt彗g}k=g}ֺtX 4Jc `23B=ÀH>nL"7w+7tI؂dPĩق }F1(1E";cX| v[="ޚ%qQ-["LqEVaf"+IĦ"&BD)+Rn|nbң2ޜT@`d0l[zZ ?KF\[fFf_nM{H? }_z=YQmv|c34 )[W%I Ȱ316rX7(ݝ ⺱SӅ|zfšyq_0sxpєqyv\7GSa؟8"Q>j1>s@7|8ՉŹ,߳e%9-$H*P*@#`l=p0VHiA>@ vjP @h'@8 .:n``a!2D UH 2!y@PAB&*: :]B=h~L2 p"΃ p\ u6<?g! DCJiA^&2L#PEGQި(j5jU:jGnFQ3Oh2Z mC#щlt݈nC_BF`0FcDa1k0Vy f 3bXl `{ǰCq[3yq<\ww7Zx;| ŗ]8~ M!8Ʉ*B !HT'\b8 q$C'bHBvay=+2Mv&G&Ec[ [bDDĐ I* Zc0&8(&iYH~Ho(%46h0װu wKDŽ7EGGDDōFG7FϮX{xULQ̝:+sV^]*uՙXXf8t\DѸ@f=s6'~_ ˍ̮`Oq8圉D]SINII\7n5ewrm\J`ᔅԈ4\Z\) /ד>aQ1n3|?~c&2S@L uYY5YoóOHrrsNy};_-cZuuk/\?kÑ)*0-(/x)bSWr±^$E[nEmnfmOk%%%JY׾1ꛅ ˬir]+wZiYYGgʿs{?T'U߮qiݧo۾C*זԾ?=xΫ^P֡ 2mjTl,ixwxHȑ&JG˚faԱc7sŨZr}wN>8(mP{nLGRHgT)S]]m?x3g]8wn| ƺc\x'ߥ+=/_u=wvWO]c\n}Ϫ'l:o\:xviMoܺ~{;˾;y/Ylx~XHQc?:b=rf}Icda)iDӤ)ϩV<|~W_}oοDΌ\«ï-_w>~f~#zGPQc'Oj pHYs  (IDATX_LT?; $+3c]T@u. >4mj>TmLjfn$FƠ+.O.(ν3sa$9siZRæ&ԩSA>/O9pyyym iEWWWݻdV^C Du**gddh4zسgϊٳPX ' s9Μ9eYDѽ/OW /U4X,JkK+غukͷ`(S777/rv( }Ԝ}ڰa6nhz^ӈmBҶ:Ν0 )**: D xhYٿR"(he@ݔ# y0r95\.'N EQpTUU w<{g` '+Vi!a)R(!!SCUU͟1 h4 X#8.s#@AңGp87?\HNNPM6TQwkEdee{4m[keے"cݺ_}jjkYYXĢEBA 龍aDp8*C<~j*Q%mdfe27c.iaB(X 7q:m(ʭ) b9}4Wf޼,, XE$$$GmM --.h4'~Jbbb8/+ۖ z8v;Jyy9qĉoڵk޽ͿE4 CGEh ###8@GMg||m۶>Kc_xzfp.ddd}9W^ saڵg ܫNGG6n(;wdOo}w=90xO~_ŋ%#5Mn[~y'OP8ܡzK\pqxS8| R:u__~(13F4&essP\\\ 4:NH$bEAPUUy)))}:Y@mP]]Dfd}i@Q`AOw7 .n |Rx?xwK-'##T?^zhlh޽{TTTzfk577eƌ4fΚEZ m[ }ZڊlٲzS,Xct]'k<ϸu---DQ صkztH):15)eۥ`PP0(=x3,bGϰ@3ҴN[L_$1sq3ϰdYŸsf<`yƑ 7gE()JK$@g~o :h&N$ZIgt$Hӧ4U74R(FCO[ @ p?9ywxD"bx#7o`6V {[;G OroM]Ce}t䈗͕J e4.0fgXp du-`{pA 8.ˠwC CA8 MH2,!{ yBP( CP $P*ʠJh?T.@W>>4Bo0 &jlρ7GËx9  >7n,_("(}5EP( j UA5P][(%hk;:梗נKЕft'z=`17 Y)Ĕcj100w0CX,5ź`T*l v7 ێbq8& qpYB.yM# x~=_?OT7B8GXI(%$DU)уML%#VoI$ɕ4$$*HHWHOd*قK^D7o) EIdQ6S()O(hJ6Jl%Z*fJ Ks˕O(_W~BP1QUᨬQR9ү2JSS WP-QW:BQMTzz:HC i4.m mH fVKU+V;֫6NUwTUQR?.&t6=^J?NK|>mCSc+4: lm g밹:XKrHTHePPIh[-}OtiiLGrغq~qeqs^/oM%&&/_c"E.6]%K.U^Yz"XΩ'Ƹܝ<o;o/'{$%xlKx /JԠԽMǥ7e33N4Q2e9ĖBl$!L(sqfkRHͤ?H=?]q"G5Gӳb妕ù?B[7{5К5k ?.momח!nC[NA~?4*J 7o#Gᏽ6WtضK Ov?U49ysos---wzm=\Z[6-l[vv,qܱ|NNNYEhE.][v}Tީj֮Ta7o=={uO5&5<;{guZŵ_Gs׮/m Gqhku&zS1pLz/=rDIէhc-Yk|kmm~3Ugϖ#+87y>x兔 K;^\pvK!\|˻+g]=}y۹ǩoNumr덶}nzݼpws^~=޽_?~00QcOn{YvvoiӇdeYp݈șр>z!~1O?_:믞cC%'ߔ|{㻎'3O|('槮q'V|}j[ȷGb3U NN!(n@Twt0w5|!v|NDfAU2" I[Fęrrerk-R?t-wcLΕv>ϟzoJl#< pHYs  IDAT8ݒ 0 ED|;qB]w'L* Tq(imP)wu&ݣmӗ XUAP]{\ֲ,1ù`6wa`]Wz b6Ǒ<ϱ( MQ4M12k&ض ('IBhȎ5(Ry eY4ʾB t60Zyx(>n1IܰIENDB`workbench-1.1.1/src/Resources/SpecFileDialog/000077500000000000000000000000001255417355300210615ustar00rootroot00000000000000workbench-1.1.1/src/Resources/SpecFileDialog/delete_icon.png000066400000000000000000000040271255417355300240440ustar00rootroot00000000000000PNG  IHDR szz$iCCPICC Profile8UoT>oR? XGůUS[IJ*$:7鶪O{7@Hkk?<kktq݋m6nƶد-mR;`zv x#=\% oYRڱ#&?>ҹЪn_;j;$}*}+(}'}/LtY"$].9⦅%{_a݊]hk5'SN{<_ t jM{-4%TńtY۟R6#v\喊x:'HO3^&0::m,L%3:qVE t]~Iv6Wٯ) |ʸ2]G4(6w‹$"AEv m[D;Vh[}چN|3HS:KtxU'D;77;_"e?Yqxl+ pHYs  IDATX KlLQgPxVHCY4մ$Db!$ HHDb'6D%e!bceAbCHX( V(E zOs;;${=gwd3Ōl6r $VI>@7[)FEKɶf5^a|pܽSMbX{bx~(嗽PZY1()W%bC0%Z6WК1'JADNp~A,^gi/bP J$7v%׬B$L5ƺX_6,kf} [9<9}#lOkv6p`ܢ?9LL;s7Nt`¹L&uՔnMvqph?n1a1q9[Cx 3gJvW͹Y z s_ ^[`f:_~ zEIENDB`workbench-1.1.1/src/Resources/SpecFileDialog/load_icon.png000066400000000000000000000026441255417355300235240ustar00rootroot00000000000000PNG  IHDR s pHYs  iCCPPhotoshop ICC profilexc``$PPTR~!11 !/?/020|pYɕ4\PTp(%8 CzyIA c HRvA cHvH3c OIjE s~AeQfzFcJ~RBpeqIjng^r~QA~QbIj ^<#U*( >1H.-*%CC"C= o]KW0cc btY9y!K[z,٦}cg͡3#nM4}2?~ 4] cHRMz%u0`:o_FIDATxڴ@3X]  JPm$!c+i xZ`(%5/@9> x,3=9k,ߕSTXb)9N]^AxX,: & 9 #'g"WQdhr*m ʀ$@F%s()9!Kߑdh<0ؿuow8 !a!*W- (^|Eszv H(ŗ;֎vt1uB:4XN_54iBAg2,,haJylu?ջ9.] z\R߱蜿.+Z?jy.# a X2ߠ @'sܱzIENDB`workbench-1.1.1/src/Resources/SpecFileDialog/options_icon.png000066400000000000000000000041471255417355300243000ustar00rootroot00000000000000PNG  IHDR s pHYs11(RiCCPPhotoshop ICC profilexc``$PPTR~!11 !/?/020|pYɕ4\PTp(%8 CzyIA c HRvA cHvH3c OIjE s~AeQfzFcJ~RBpeqIjng^r~QA~QbIj ^<#U*( >1H.-*%CC"C= o]KW0cc btY9y!K[z,٦}cg͡3#nM4}2?~ 4] cHRMz%u0`:o_FIDATx|ohU?`@3?Dhև*U량X׀-PQ@?#@ۥ;p[ŠB/5T\ԍpSwLET|[*DoۓoW$+_g,F M*2 _Vu0'/A ! 򦽓h;@YZt)r3Q[U#/m6D~qFϺqI`3wmzO^/+c@} SfWi( ' ޡ_۶Dz?Zs{>;-tVoH'7;\)<.ZEҙiCvwMQϪǑ-J+jn.¯UŠ:Zm閣ќk/t ^.p<QEU"hhMiEOy"D UTQVm  {]ɩ}aᯮyċ(pvǣ}&|0Df0,ɜ4$O Ƴ%\.G.fc8Fa[34abLb1F"Uggp?ȏ'i \n%qCiO#C7QLJjŦ+i5٠PD<( iPRG)Δ d-mD!chRs7'yaǦ+{W&g2 &G[e%JT"Zr83 C Q@JT7G|hJߡjzd| |9p:BZsޟvh7JLk!TsNUop.O V*ޅЌ.ӫNoT9"7b*W~ڬRm*V|U(}2rQ-93R'*IENDB`workbench-1.1.1/src/Resources/SpecFileDialog/reload_icon.png000066400000000000000000000110351255417355300240450ustar00rootroot00000000000000PNG  IHDR szz pHYs^ MiCCPPhotoshop ICC profilexڝSwX>eVBl"#Ya@Ņ VHUĂ H(gAZU\8ܧ}zy&j9R<:OHɽH gyx~t?op.$P&W " R.TSd ly|B" I>ةآ(G$@`UR,@".Y2GvX@`B, 8C L0ҿ_pH˕͗K3w!lBa)f "#HL 8?flŢko">!N_puk[Vh]3 Z zy8@P< %b0>3o~@zq@qanvRB1n#Dž)4\,XP"MyRD!ɕ2 w ONl~Xv@~- g42y@+͗\LD*A aD@ $<B AT:18 \p` Aa!:b""aH4 Q"rBj]H#-r9\@ 2G1Qu@Ơst4]k=Kut}c1fa\E`X&cX5V5cX7va$^lGXLXC%#W 1'"O%zxb:XF&!!%^'_H$ɒN !%2I IkHH-S>iL&m O:ňL $RJ5e?2BQͩ:ZImvP/S4u%͛Cˤ-Кigih/t ݃EЗkw Hb(k{/LӗT02goUX**|:V~TUsU?y TU^V}FUP թU6RwRPQ__c FHTc!2eXBrV,kMb[Lvv/{LSCsfffqƱ9ٜJ! {--?-jf~7zھbrup@,:m:u 6Qu>cy Gm7046l18c̐ckihhI'&g5x>fob4ekVyVV׬I\,mWlPW :˶vm))Sn1 9a%m;t;|rtuvlp4éĩWggs5KvSmnz˕ҵܭm=}M.]=AXq㝧/^v^Y^O&0m[{`:>=e>>z"=#~~~;yN`k5/ >B Yroc3g,Z0&L~oL̶Gli})*2.QStqt,֬Yg񏩌;jrvgjlRlc웸xEt$ =sl3Ttcܢ˞w|/%ҟ3 cHRMz%u0`:o_FJIDATxڜW]LT~Ù 30EF]"UQ!MkѻzI55Yc_ij6^E{^lPi,(v.?= .%Ŝ9}y}$*m$[X (jnnVD*l!ɦAw ܹcII] a  =omJ)lZ){3?I~?Mt-b4]1$!$!"˞ݻw{񛳳t5)I$5LF8;66#U__o f $ިtqڶ͂m6}>~?mv-b41@V`x wv Ё@@~@>-((`$ k9$a.\D4Iҟ̈h4sF^*_0Dkd2pzW$C (~ 8[XXHqr[; u0d>`!b2yf%yy(%SBJ)hQLX|֭7nȒf.%6G L<UUU _]iu@j% Lr 'H$?==l6lTD;vt>|E TzԙX,T*\Ւh4z >& Ass&&&, Z"f͚|ZVV`~~^?zzdd9oH$p]ӓ⩇PDMZ\ s,t־^T*V*<ɓ':LVT 5d0??]וEa6.g H3Q5 B.@ $pd!s#~<<+"x M0B\t8K@zB@F&S`cbP-`'{[! eDh;VEX0fK9-0IWfH  0Q){`##xFW<+*x<$9E[-qWW.(I+6aa@.y24x6_-"bbϫp@t~,/;m%h^ uf@Wp~<5j>{-]cK'Xto(hw?G%fIq^D$.Tʳ?D*A, `6B$BB dr`)B(Ͱ*`/@4Qhp.U=pa( Aa!ڈbX#!H$ ɈQ"K5H1RT UH=r9\F;2G1Q= C7F dt1r=6Ыhڏ>C03l0.B8, c˱" VcϱwE 6wB aAHXLXNH $4 7 Q'"K&b21XH,#/{C7$C2'ITFnR#,4H#dk9, +ȅ3![ b@qS(RjJ4e2AURݨT5ZBRQ4u9̓IKhhitݕNWGw Ljg(gwLӋT071oUX**| J&*/Tު UUT^S}FU3S ԖUPSSg;goT?~YYLOCQ_ cx,!k u5&|v*=9C3J3WRf?qtN (~))4L1e\kXHQG6EYAJ'\'GgSSݧ M=:.kDwn^Loy}/TmG X $ <5qo</QC]@Caaᄑ.ȽJtq]zۯ6iܟ4)Y3sCQ? 0k߬~OCOg#/c/Wװwa>>r><72Y_7ȷOo_C#dz%gA[z|!?:eAAA!h쐭!ΑiP~aa~ 'W?pX15wCsDDDޛg1O9-J5*>.j<74?.fYXXIlK9.*6nl {/]py.,:@LN8A*%w% yg"/6шC\*NH*Mz쑼5y$3,幄'L Lݛ:v m2=:1qB!Mggfvˬen/kY- BTZ(*geWf͉9+̳ې7ᒶKW-X潬j9(xoʿܔĹdff-[n ڴ VE/(ۻCɾUUMfeI?m]Nmq#׹=TR+Gw- 6 U#pDy  :v{vg/jBFS[b[O>zG499?rCd&ˮ/~јѡ򗓿m|x31^VwwO| (hSЧc3- cHRMz%u0`:o_F3IDATxwmUv>/'$=%Bla䌡p vvٴMlaWc`,I I _N7'_ǺAWOc(qǾ^kセG @d .8 c1f1??lsD=GĢ(9SeUU9=mweY )ZNZo]`9\ !sNwgY$i 9_ѝ\08H9кL&ڔDdW_{e}m*!ZѿukK~ʕK?x3iuzSvwRxso;o9( e){yEXDdGp" iGׯ]kƉ~uc菒$(* DD)%#8"E'cn_xᣳfy|?Md{OIlUNÍ76FWЇv4goܸqx8lrn:􋻖FѾ}l"4 "g1+keLL&g}~i~Ky>ַ:}Z׫$8Aj G}haa4ee;|ЗGyß|w~G~g1*,CMMƏȿ7fZN/2ʲGIa}B(,Kdjj*˲˗/_vm8a8L0I(8eYN~O9ks<# 9פ1z^$ٹux<:7sg\WU3VD2FnwO߿955#ƩS^N<\zM c 1p3Ӛ v;Z:V#oIEmn;rJkh68$N0ED" v9css3 sa LFI"gs4vv^~UG=8)rKȱlB8}vmmٳkkk}t߁W^`"xe*$qF#}ꉷ v-.q\[8@(+rst hTUՠ!7ϟ?г`]^YYY(0A%1sH8GcL)#:5sNDZO(I(MsLeoG|zjlןR 9>TRJݙ#A\4`ɫRjc, yA c"I82!yrUU)eS2Ƽ{q~0qv{;]kmfJ@{}yXɘ 9G8e !F;n3Vk]i\i=b܍ؾ}]'ΙPQW I2 =0ivBN8Nn,ju{o@lY `sm]JۭF>I,qeP7'[ pav0yj<}ol'\|ڵgi^xs+JDB.\^~=ąPE+:ϖozg|2"g3s3)8rk "c[gq8NTj=r~4qyLsg˽^7xⱭ{ \!.A*5:B2P*b<7P3.92/Js{L5-` !q:㪪(h6EQXk^J[MDA!9W)DT%VGA T1B N'"c 9/9c@kEVUUJ*+[("/ zl?>ę^NFa?,TkL31ҘS'_:ۆYgsSanooW`ټ<33{O+=9~לs%eQ˲\^^ZE|ȑ .LOO:u;9ww߽^X `ρ=vIiβ1vƍ{>o?ܻw >|Ç@wJȪ((D䩨{yo_?}jw {e, z7$bLTUs.Pa* )!GDx"!wTC2&c(1)%!2h=! qƍ2 ̃0tז,#qz4cb2 xo$U}/}Y[Vo~H&QE>Fr\deUM/tzv{r͙K._ I# uBF:P{Е7sF{R:7$rL'?_~vBkio5Ħ>q߃opʵk?ѿ^};053/o8Z_d'O\vZ+h4iFQTeEZkP1???L8{677q09㱵+UUyg_֗ڻjf}}Gz<|T???`PYƳ3>ҫׯ>g>ิ}0B9yg9M2SuFpFCyYyKZqNuv@#{cFu$;7w{Ag>8V3.NLq)읾usfi? i[kOJz٬Kι1;'byqcff_z饬ȳ,[]]=~xuSJ=snt:nWqf4elQSJhtMƍY7{ѕ0 8OOϔe*ۛ+Gʖ*Fg%cKFPT @)A^jj7 UJ)99:s֗ܛVvr'gGCNk6Zőg*RȐ"x!tq[}Y,i%(JfEBJaq+$IRJ)G>c"35 TX8qo3֓;gsκ"ƛ,w-u]8gjF#F,J4"Q_FQq\|2N˲, mTJqe#1fyyy~~իo\n5QVp<K)k<7I@Dyw?h4k2B80`,(<vlJ;KF(J8y6&(]bfqL66V2D5խsQ6 U䀢^?׮ M^|ERPҹ3޸6H>83ifyJ3!'y155Et2 "b5o4eWWW8T?W_w`hΜz_{u[>wO4謌>x$`v6Ξ>rke|ǘrxvlf߮KhqmNg+nʣׯ,_ o6I'舩G}h4:b a#zl7:60 $L&~ի=>|PJ}W^z4Mv4'~^oƍƸnD&#Dcg73=ީ<˲:֞:yf77&#WRr 2`xO1i"yȨ4 FȈGD8Y끌 '=_D{F䔪NH6DLI!s%0R<sgÉ5{!P0IQvyDy?7]]Oc׾6/.qεwZ*ΞS?ͫΝtwj&yeو owݻ{|ҋϿtJ{j55@+/./Z_MMI@Voo+g/;߉g_=yסHtʕW^Ɩ+UU}3W~/%q%o޺?/◾͵A8͢(GNGցay֟ +]reuuCzo;tکg^7lК qP{NJ[DyQܸyG$aiVۋIvˀ0@@@bYIAH@O c2aa4c  Qĝ ioՐ#ki^5hnf̫,n.;M((j&Q\wse^/\?X| r{8"//S^_P*nwQѝ3"S(8Qcgff=WWW(nmmf9Tݩk׮?Rt굥ݻ͆1-//C~߼u֑ONo^wnz“QN;00,&r:k` R9xB1 ǭRdyURqSų{2)u|/ѦD{4}[+oo |+_w`R(.\x큻|wl_ztR%[3?O+ !&CWF߉r'B0ƼښPE5x'@風!Ci6ڎcDnkZu2(0ƔeY?Iyuc*\[ػw˗hzvnei2fq9m"xN# $3c> xUt2G;tUz9M;o"QMHsb:tC{vە1tꍫ=]zz2Iqr̙鹞&{qu ^4<^[[k6Es= /q'Ok^r89brdxjaO9s̮Gj$Z ~u|RڵG__?6ϾF؈JzGGgzGO˯>Ү]O?vܸxᏯpqljμqNzyN#CX{}.?w\~eq !:th}?ol߶]qӯ|G/>ׯҫiiju)(H<73g\tQk?lsss0hh8,*X_ TfzTapUAj'K%XֻpkekAHZ&6M3` H"(nrq:ޒ1ڹ#BWQI!L)T1@U:.ȃ‰x1b#eI rƪdIʲLlRZk=zB.K`ȹ􄥮cfl&NBEW-!؞{^]?TJ'E+.4〽vgkks@ 3դ,:rq LOO> /033#%m7===55%*:-̹yU9; sNnu\ v{?sf1a]zy=n۷ϟ{[7DtނhBH"JM&#A ɘ`?ɇ*__G?Onq =ݜz}s>H/'J(0hƋ{v_t#V.;ӧ^?rHS'|??jn,__xŋ?ۘo{*+k]ӿ,Mǟz3'&S :v}人tj 囍N ~ F|ƲS?7??KKKSFUU'Od ǚ?.8_V19B&5q9uԉ'677GR*@;!>y$\ :6jceU ZqWʖޑ`9I"ȫ*ҾTFaU0I4h WTއDPBZ"BQK%*r^"J(ƹ{_&r9ET7ߪo=xl6ZKX܌aa>{_h<7vSnZIcyey[_[_oZιMTѨ# 0˲(Z#:_B[[[MFbH@)']B`fI6$dec%O3x(gtYIPR58 lUUiqFZ[c59ϳ, C"SUUE?p%O|ƘӧO$I^|knF#Gɤfggo߾-ƞ=(UUYՏ~GѸhۃ`<3ƒ$qΗCGʲ (+QȘve^q̻Ra 8b\[U py:s@D=!T\R8Tc d}BBFjNd#b= 9cH2.!>ͽ U5d azk8?_ p =O_w=or_|G/ol$Q ǟg>ϿxW_|~бH&A"ш?7~{e>a:y';W_p+ѹS[^{W^?3?'(|3<?<>)K͸͛+++o/޳gzg~굫WGQQx;?=~ӧ`85h4WV'd2\9n=ݎ-+c¼5sY'A #c*tRe(@Ƙ&")2 80D=x$o/GLDq8RY91^釱RToogjNxd=F5FckSpC&M+68 bػޝӧO^zjo&MY|>񉹹Yį:x׿_WJT'׹^cc}[\صIDgϞ}ocgjL/M-̽k8~O;W^|7oHɵY:~Gojiג *w [Rk\bx,ҌZ|k'qvK$q"$JJ);j{ZaڛDӥko9unѣv{yVjfzk{cnvsg=rLz?aȦYfCIJ(BI.P"Zm#Y7o.izw/-./ŋWמz;8ǍJ몪8vO;t#r!9eYrEOhmeXP^7XoLHr ,xIJوP}_$I|em3iZvh98`UdRvhMM̈́A<84ZVU{q%d74v==ַػ? UYd7nΡcr wvuuu2@QS K37:j$|)=pWo_V7Vw^owfg:҄,D80Z|Bt9O(pyQqwZuk ! ^?<(*j_F"x 9ګ2 $xoeXO1D5fZvRk @Bx6I9cBlS{k6i6jZQ2 I^Go~yn4ѻ$P+[{Dy-*["ϓ$t4zg{.߼TUMfϿrI"xksڥ8Ksss֣qDPKd~ |CRZeg8z$G1@IeBcV̘G$_ӯ6T03ƫԖ,K1,e΀CF]0$GL06&: ZL%8q{IJ2JC%'#S܇8`q"B$2Z::8@:͖-GFj="r`vea`+ *< uo:A{6B"K U?S>[[~sшoݸy#1&83^5( ,uQTɚ1FW{l6SS]HѡRJ90Rֽ^o޽uŋ}avv333@wvvWVff秧nݸ.9g{]ݺu#˲4ٳkmmȁQܸpyq?lyZUE#N^z]x@Dm 4M9kh7 6k.A`Ev{:rZs΅`H*m͗'W|p8t./+M<87^UU ԙV݉\Wι4M}NEQӹm#* ʼӲ 1rF!dTҖn^g̶{Ox<RI@J﬒=lRmpzq#`0LTX 1fu%hqUNfQU`K]͗~U9oPMBfiGw`'p\`Uӫ+ˁ#!sPsϱH01L<υUY0Ӭxv:@;t2;=B9g, Ð!zoJO֔bLs)e ˫^Rm1LGeaj] D K;ɝ'ZctECv c0i,s¡=pBL %`֤! 1VSxksS3{3_zs̥驹Kq}M4Pbĭvlĭ#*o/_Ra` ڊE!}DIG`I*P"sF$K(2rP쳡H k`QF3<33;UU%I2 &4M禧$uQ޽;ӧh+"4͉\:F&|eOYak.c`.,&[(~skf>ooy׷_yy5uhiSg?7>I?hLlYu-| $V~޽f.vZy 6SSSVUQ߻{=ғX(VEDZ*ʺϽfAmE7n\O3gWمcǎ=uWR-3nZ!g?|;;8K<3+5$e`L&IH)?j]Ǐ[m6׮q~wlZ=g{y̥'^x]^ҎpUk-0tJ[a$q:gfff/_4k=Ssф:< kZz4ݻ7+rc6 Ü`11K@IT eC@x0D$Vp##y$ \F(ϻ?Jb3 {(`mnoč9ϊnTQkR2UUbBCd9g2+xk+VK Aw8A0:sG}cgΟ3p! X?t:IOJ4͉2!@rʵ5ʴZ2!ǃT;|0N'ns>#"\\#k\a4Gfd Xex[GI+K)E*0I=7hB4r)eM=c"2=QUJMu(*uh4,JB_yA2.Q0.\8:+fguUEl$ZDx~qa{{j72`kkksssaaa߾}??x~b$"&E-+˲.Ṷ֭[VUu_җr֭Nw~~|(w]gΜ9qǎpፍMԉ'կJ}|[߼qZ眥گ3]U?6t+yN"į^|$"1YWT%G;y}{#,bB(]UAWb c0.yhEyI Ƈ\^PpBd#gdǣD!2'@^WׁTഩ!`)eFAD(xLrcI0R8 vvѣjR:$A85țBLJc"h5sɈ 8%Na]՘%kl+&yg G⦅1>̋̄JOw1Ɣ1مZI0Bi RxrٙZKaLUy$C9.?kιukijk'{|{{kU@z'ޥ] _skΞy/,f}=wa."w}q UD̓#b{u*&9";kGFYaa#or=ܑUA0;=3&E(b2q@u#9 yR0F̺#3TE7UQIUxkv~ 1Ģ*C2dxVY~cys噅V5JS8m8J2u͛7z4ρ<2djuD< -$CEHrVVâ=>{>ڡA>?i8y|Ͼ vܕWc.s_ɯ?ykko?oվǿ{>go/~d?O~/iu+WR%Qj{s贌s ({X1@֒mc Dq,ٞG_ZXZX9JZOcLN!$.s~3ʺg8i:m,ͯ~ƖA,JSđw0 'y%q9h{gX[j4yF4#~'_;q7ΥXȱ}{@1ƥB)O@C(ET6~gRk/ܡcGf9A0rӌqD F\pUC6o!`M3xFnMByeLݝ vmE"H;I[FӎJॡT<կx'(Fn33 n[W1gC2*^;ɰ 8@ %9IltI33Y ʡGPU.+V>NE{ȍJDJ yVP8 282-yTW$Su:gYGpdu B08_D$q 'Vڂ`j~ƙbFX$jh[;)ھqWVPOt{稒J.P s9c1Ȫ "+`{.;#c߱*ӌlE2clU4'—繩4 ! Br3B'uXUtX%x1\7z!TFFv٥مՍPLФ|gaf$\(v\rv.-_y{uҮ/O+o\Rn53A DD30Zolo).v}zz:G7:̣f;;WjDyT5ZA=_@wzBZ BewqL{{g_~47msss$gΝ,Vj׸ـPaPUP2-&o^ By[+ˬ`<?h\@͙q{BBXѓ,K5zμqʥ͛/xՓQ.sw{vJ˾o~l eJsV{@FCJɑEIXV#Gn\~E:΅*Y퍵P1$w8ډxq>K Oz}&\zqg2,ӓ4LQF*R0$OKhCTB:*L1!X75f^ggkt:$KwYQ705,v /xY߱R(%g?0(R a<Gd3=YGАgAhhpzBX,0\ޚݽKvRʥ]sOzHQIjkF# ?7MHdi3gu" 2(+X|QRa+VW;Ott.+ }*%JKU]sю%_ڒ VT9Wt?;V0J͠tgIat C-'扻JOȋ0?tϮlEDz  ;.h<Otnz>+` x:Ej.G/}2+)q$ȑ7q5$ t >Ί,MPZܣƘtSI<uA:ђ*jlٷsgI63pccu?v_V +nv$LMu;=Dą jrLeMNn#+RͼԎ&"NunƛRW ސw@LwtW`]']Nƃv{3.\^YYnGGa>eF $WAv{{>{{, ntfgsY߱R+()pڍ_/^ UR޾;zX\IdXV0OZ6 9XZ*0JvdmYLe!07_ aC㸱v{iǀ "T",- d4Zv>83;֭n8IUUz]IP"86sݞz3u_/t{q?3Y6[cgq"7NqU g,1+rR)kmgSVqJ#c`ъfk{s\ڵ&3+#,ٷH( 8$N򛿱D?#׮\=qyQiș!x߳|6#cLpfqF2"rl(333Qwo7]u=;æ"*SWߊ"k̷\[˂c;Cyb[k^P!G"=(Zk 0z(Zy'L$s)A*i9Oy:d3v=$`F9Qrw;2B IƈU|1Vйk~jo0J9팳O4ڲ:Ckݝ6U }G @$1 ڲYPX6-cfHlH8yeqذIJDJ(J" Rh @ߺuf5sSD Sd-`qsvsߞ7[wo~W_PFyr~y4M1̌W )kزmyٿ{]q}>tۗ R^X$FRoY_W]8tmfTS2#Z@DD&@d%.xqQh1瘻x7}" 3{ Vkyy,UJh:>kN'K u!Bv.4~3!V(xuKs3}kq!v@(P憗 $$&U^<X@ T" #{!߭<ɭg֟'Ow 'Y_|SS:7h|~0>G~_xY;`Ӣ1PZHd˳ 6wKNQjZܸ_՟3cǎҩgX3gIab#ImJko}O>p9Dgx޻vmRMJECw/X4(_/jum 6vf!Hݔ:,>73GB^m?m8:d۟i=9r;rZRʲU5ABcL-Y"5{ .VJy3]1 #DA} E4/MF&2Ep DaDBFᘙg&z)Ofzɤ,2ZDW(x]|1L3no. U+""jK@^<_t,ejR5Ryaǟ}࣏_ O.x1YE4_|*QǟzRyal'Oy'QS^@yat'OFxⓟfzqU׵dp4eY$M/ܹsMUvhզUy䉃n;2-ޡG?5e~yAi'Cf" :"PUw7;coQPk H0P$0D)k{Ξ`> zR, ۻ;ZԔDP&IQ~Y^]sn:4%EHXh3)"70efgfNld ́C}DM&a`"Rg׉sֆ$ˁEw9\n=>xig_g>usOrO_x~+>On '0F͹OF8oOM?uuw5Mu :QjR! iakeֺj$ՄvQu.to߭&%@C'(*md4j5!T ɣ,{G 8y9W+ӯX՚T. 1dcRY+M䇂B@Bplc'"<M$k`!xFB JLN'/7-tR7z[;{"z'o'EpZЊQ! jpVɇUoNR&,Bɼ^^@%@Qt61sDO\Hs.xXk=B"PT7MeR4CE&/\RDIJOuLĈ4|8Uy xUQ)3wJ!*Lю}`!R)Jj<)p-ˋ_P,./,ƕ#VƋX^[ӧ~t]xhwE'ZTFi筭'h<ymQ.+Mj<[V1{%uSh@ !ڄ D8[dy &Mq8su45E A$N @0i&Dv084Uw R DmF Q@1J_|GfZ~Ge0>{ololŏ/|^j2!e%ipuŲPѻ{ ۿ yp^ǧPk!"!$`B"RZiEC`ODJi"!"m$F @dBgu]whvoS:ݮX1HKTU'(8|mtؚD8J)Bq<Zеux`Q#$a iR ^F$  Nw<Ts`ۛۻǎNƷn߬ytϼ^|c)Vn=~]vawkxqf/(Ji#Gi-ZuUERF׾.b4Jw>ˋogԋ+5aFYR]B =2u;W "(r쀘0*$ aBEq4Hv7;rpciN6/ͤ~⽏j<n{rҕ/}Ճνq($GvZk샍֛yVvh"1v9'?ǎKz kc㝟E [1:Mu*η0 !} PE˗/7.^]Fㄓ_Ttgzt@G,A` EϲuJ `IB9'Y*F"˗^~X.WXa|s1H<đXmun^Ќk5zw;` ֚P@ =Ddo*kƻ[Fs-SEh}UסQG)Jɜ_gڰ@D 5 5`@P@~MPw\"΋7^?~˧^?,"VϾ?v4-Gr ^ ;y_iSP:i6y""F<}ގNƎ;Nޢ}C1W6vg yx"g"q@]ĺgSGjsc 1AI_~ժ"(Nw ⇝3/IGRM (9?BaVae ɞ %*@'0~@(EvHT5vw{sλ~ _sihYG?yk><^)j!]?W*NJQ"D$OTv롇^gI=nWtٯ~+}r2%,l49x<$Y% 賀#-hA@$AE Y= K`/@$E#J}+/¸nl.^}<οq|o}]{[ۓյL`~5nnǴ2I[nA!Dy\f&@fY(nGv&DK"Z$",(3onn_|x[lsZ[UU6ƸxqfZiE'y$&Y]Y?fxڤLUTt:6.|ikӌ{Kd<>n{hPF-r;BNv?믾l,3_2ܯ]6T@'I*M/|A]",jrMRlH$D|y2*;)"IxAOǓ4MiiWWRt;]TgEQĀ,vRRO(kfֆm+ _qF5!;Nmy(Ctۏ=viuꦙ9//q~rv;~]ǎ޾]V܈ֹyYNivuΉJ64Z|;ˮnq]6t5i;Y_nZ~Vo_s?R`08;{yzvo~J/^ ̊4HODmp.˒TdK] @b1F Q(QiE2ƠWGy8 EJY%غJӴMkuK+Y^^6x4M$IZVvEyСpvPk]YYiFa"ZcsLc*V!Cj L@Q@(o;#g?ԧ>cOo=ܹ~[pw7'ǯCEAj"J88ѴtEIեM:heisH'|`Joy~۷#Y砈c;E0&I$Jh\82i53vV1(D`.Xk:_4 v)>pyqmҥK[#/? HW.I^vIߵeD3m_dÇ_ 'fD$@`," ΋L,*`RP,k4j 2depFf hcm-"h(I*1i:aQCj'A'q6o(jDzmʉ؇I2"Y2 *lIUE"z}HY'0u6Rb4zu! Qd1WIфāه`䉢{~;=2wutx)\rr^~ɓ7wR.01 š t9@,ppz2nLLĻ}+[ۗ7Ͽ5& _j["6J7M"±wiy{K]uJ$51i,YQ,[Y*J"esg^y78v`;]7^>yggKKK++Nulpփ&7:@TbξynX Nιy`4MSc )YKņS:*RŧiJDi!$H 55LHZ88D2:R,K!"mHkD#XDD !l>. Мش?d#>t`ABZE%$D$ADLT/D"I3@ U׹<,BPucRZ' lXy: ڡ _{~{??'S/_uuryݨgOy;rYY*r56 JYvPOSr׉^ۗmnʲĝ'\9wkK\c$ĘNe)&y-J3(h爨$+%~E\xwjIN4t:>xNJ9#ԩ lxo*ڭV+ Cu2I=hPE!fDYi$[VQzeUU4Mu4A##+E!&B(\!!Fr2/w.>PJi:'$B@I֝^|JH!$KUy8S!0)Bi5}N"JQj*0-beL7."GvPPZX߼w흸˯>q~ޤŭ>~s yQ/0~>ǟ;,99+;u_W[7VRi;+gVş?}˗/^\YYki[\K/+w8GsqTjD$KjxJ)/HCiba2Iju1.:#ҌFS PD$Iξyf:{g{8B GNG͠nv 53XRIt]=jc8LSDv\^^EaqUUeU!b]U^ع:,OAM0Q*U,iV?zY>n ^ڹNZ푟y[| ,pܨB7',"wQbjEYSW% )%,7G?P{q9}w}SE')&ӑ6p. P5 B9?i^s45qND$Q7ϲ,45hm GHiᰣPF2#H!aLG6[~2tڽi:4hKiOgGq8ԽC̵ܑ1ढ़OKszya"qMw5 w1{ksTfXBYYZ^8x鯮ln_W;UDݰ5@i#Zc[FQjf]4MÞ`'؏Ԥ9WHi/zc4)E{ Ip:gk<J44M)4X+FRhpcQ4U56B <˘ꀪRQ 䏵v6D2c^2YWT\Y}.(I<~oon?x7c|s߅p(fcI4C"r[M'{Y nVms B݈2:KR:ktc(jR h4꺪a m,&5Zͳl޻vnfh{kiJ\|sNiXhY++<oͪls"5(9̓_D cKc9=,0bdT'%_;u0DP3wcaZQ$h"D08HT/ cPnwgo_٭z[W]׋li.9ϳN'M7/Ku#GdiPN 3N@j~tLҤyκ{CF)3 il(|لcVT(Be}sbD "4WAD*$(TynL\`>8dy@NRͳ|T1, j s2ڛvcټ98{2`hJu-Y^I49W$u˿r֕NyYSWA@Jn@KWE@EfGD0k̍#^WFߞp}3ad?>-!],Qn[[!R|z2=pp3~cΞ WN2sԍRfALn@jj1VP! A }6,kw;BYġt1Zu *zd 0TVHD|7Ӭ8.> nɮl^.nɋ"2m3K9(1Y0MӄKYo"y(1fnl0Ax9B 0 O!2鹡#c!(I 7F#wV8۝ ^bdk5 3WK}8%$( Ӳn4Lѭz֏?OEɀ%4X$."g~랻z6._>{ #8 8Ik-W=~{Ld4?v#nO[JSJt` 8v udKg/]9s*hMz<[ߴ{H45Jhk1H 5I",OE ޵yB@)Cn?~b DـpMSGS3.IE" hfH˯6e+/|Eu"f3YHy^W%ysi2hRA Pc1RhP&@0" Q CD٣38>8E>ukɸstދ Vf^xv4ສ&(cT^[[퍍_>EQfZ't~Rj]Y*'Of*OWaS\jxV< Nv^:yzp8|oM]UF A!4 TF`&3WAe4}5(AEB ss!ȁkA%(\kg$`WbV y>B*Yh4E!kml23PWmx<8>E j,%0Q*rӬȋI豵jZu}}V&M<+;{jk媪,mmEDIru|r>S#ԕs+˫+H#8$UOY+ HJf^6IT D! A5 .?uo'",Οc$̇?qGn|k33]y$N[W #ۦ4J7.^`" P0F1i.W{UUZ3ζZ$Ii^Y~k֕X҆%Xya>fWMu)*_o+ME3<lJ"" #$i:=|d\zҊ76~;wvɒ Rvλ|U':ʗ|[I~"KVZbst|N^>}[ʹ.GU$T#+Q #VZi@(%r}L Y(>21^^ټ<TRf("cv̲՞{9~'zgowwo̜d< jfZ罈ZG&9_W!03B(l;SZ뺮E)y[7 I@ cA3QYf+utn13B&KkgE qd`0ΟkE4ԭ4U eYjN6YynLIZ2{Hp#+{'I4v'*_'s=ZuO= u}0Ck̋EKw{\Ga::Eof:|3UK1KYQUͲ1Xb 4E# ]@jN|l*'-mUQeS{˵o@+T.cCEo鯮!\OGAyrei^B*`cv^H#;ip!QB`"#! (+04MReYw'< ' h ,Z}GUU{{{,˙0&{kG5uHt8^r{?A [}PJ1>̾kDzmA1(>s;LfBZ.\(J!Ht 3inqދHE4˚"t2ʭ]f`AR $I;vQifv]EjLVصImԶ췣}2^ǟ/OwG<9c]X-/gc (bkd039mQr_`H`47gao&B1&"F:=g<̌GT$Q2` ΦY |MU;cveZ'Y$I Q `&>XjcTDouϲҊֺj:톕sL^_V;xi{/Oo3x꾛of$m @te1Q{~iw4)DbqN9o\WR jUS[/"4z׌j+zV]5ٽ-GjT ;֤<2QA1A  BVfmU,"'Tko,"˯`KD 2<>}h_ ,--CPfxcRD$p|yޚBsWXQ7㯱0fde nDo(Mt1&8UkkFfR,fi\R{ @EI,AUwu4t8\|%Io7c'O~3Yß̘ןm#O9=6Yle1J7j^YsPzya4ރs4_|g>I7jǿxßP#V^n-t9Ϸ|]H;0~࣏>^*啥^w0ܭ{7,I:8/IYiUY<3lBR(EPޱu?=~)[DZDB@:as3`oӧ˛W<$f{VH*uoL;!s|gɓn00~ß?2CM""ATKj5 @)]T#-P*+LFdiwLե ZeJZY{,?TB@+&DÝa(g_;kgTI?V{",S6>iaq2|itӈ_k,y3^w |pXuųk\z;x! cE(%H۝*>C=riiCW\9p?XxaW?)AJ7oP%;g(-xc A$/xLAvcTk}JӂBX{pyk'^z.KS "qgL TU9 CFH9x}bb{?;i!GhE bf$E ^zߋ5ukHysVj9Y=fPP&DxO[Vk:yʝ*N_?4lm圈م@ySq5ClEQa@UK&z9"F j<<ȌĎ<>:[NƆb#IIIgIYr&よR UWY(H (Bd*<3x { /#Z_E\.s;ޟU"RE٪6h"YB<\]W///ϼ7O'Iɳb_^^UӪ.`oϲOT⃭(#GXYͲl;N&"=ollm vy}L? e0I}oqjf0!mՕp#KbCSpQLi`P=#110123/ADD1jA`Q>06oCMp8p$". Pf>iB6 0R@1ƍUѠ 3s Xw?"pG:'ca24Թcx|!XD Fd`mE@k3UvԨE;'J+0y^@kmכLF )O3Ԏ_;}g?BMFւ5yzlj?ًهB5CDOʪz;$TUXٺs#|hyݕ/C_x᭸2؊m >uy=?p5?yfnF_}׿,bM[ ـڤXB=ed?~IcͭƖۗ& N?~UUu+kkkHW~[_[WzRF446Zijv'z%ZOjǙV (D8tɕ;o7no>9ΛrAj2;Cn@i2UPiW0kJ0PqLB@h%4113p@dILob&_ 2ƯD"vq)799@Gݍb2lܬJX|FE,M  k3sU"J+kd")DWN<gFBzɤi>ɴR QFz[Ϭ\H !{D>OǔGn^ܨy-&57ƶǟo~ouZ7B?ų[_ktS03 z?c>?^9+˩+ YNJ޼|ec=ϒv,/ɓg/o !#VP"1NBҕ^pQL+Z:_͎\QV格0x߅~>oCa&&"b Jl:!U4#ߠ㜝N+EhF"5p. D[EQ bUUEQd3sdq'%/џ`$]F8;ɻūVz緯m{ո߸#Ooy<QR)=B{S8t=r;NۏSNC>DTx:!w_]^\V"}FʹLe:-na\1¶q[//ϒdIYWR凵1XIQ4 ]$)X  !(E@!(+@ !k Jl4fق B huuI@C7RUvтu3i鋍ņiuuwxvv`) \#%HP6( -cBEBt:MN֔Iub<Za6x툨7˒$"NvEQL&o먱Uhyz~GNziq['>ǟ=}C&)-vPX<~}~wvp'UP/ubIw.>6In߰8)ʊ'zff@Ԥʲ_w=?~ӧ{1.}g] ,1ll˫i3ݻrykss&ʳ^5x2m7 oouWM]NPu \j^Դ,1@8®uY#$Djp-!+ 5H!_,J8H8 B@JѤ.o\R $bRV.@>ziE8~ߴ4M5-dN "s>wh("xl^Pd/0fb1 c4<[GWiiRf̍>v_if0 Zk4MT{3 |oOK {}/`w~;;x<>s# ݶND1&My`www}}}V4&7b^pQFn&"F^#9)ލ(~20s.,$@L@.F,Ļ#KT#⁵Cvk"A2!$ys,CiE(WXT 59X[h@B `n[m];wpwϞ=|KΟX?G[Ի|{eո0_0]ٸ|k]iE(`_7.ghs :\NW&u٢U54 l9_2qphU`Ab$x|bK!1BR@@",,Oi%lKKj<ڸpV@iY9vQ#坈H4MeY//ujNjจpBܵ)<ܴQ^].ưbt\pS0U *dN?J7YZ|'@#˫@4MP5Y\`mc]iu1c(3yrU3NZE[k)xF^D[;r*_}Cɇ*ٳ~[ݽ/_ި7OZ$ɺ{17z"VCc ,LQ;-MUW:TҕT*ISeUNDV(r$vҌ/nMA}톓M'5J%DeHBd$AD`f\ " L!eHk RlGêHD(&T LEP0i3g湬ᡵu7m؊n~u- VXykIE@E@B[1~ֺ:˲~l\=x)//}?~ĉ'+ѬBʲ"xIo2Fyw~7wfʙgv_n+^c0tlzx/XG8ΒUز.Ӧ>U;K\,MU"ZFM~׊>DK qRRbp.`, w @SB7':Egow"KݴV)RHf>Fi~<"2453,B.F0__ܼv9ؒ+`c՛󠒃Pf˕RQrqAjvdǖA2-8DjWW֮r @B@ @00GK,H$ؐib](&v2à$ FXEh־vE865MZ) 5*mcyגoUSCs9*}(}i~; he .hF8e'u]+u:ݦi%@HDc$I;XޞsPuQy̲LiZt+붱E6Ze*?,b|kZ]e}`u8 ͳh@q/h8W #}!L\! ̀X4Xt-C-/y'U):Vhy?1($4ͽ.g_Jjg[rRƆt]2&ED Gn[[&fgDj;&>\pS;vp~oy*h;dQ;؝j7Ycc"$|W>J{SYm\lSolyvrX=z\"mot:\cn=c: qtk 5 - kt-,@qF e_$=X Dyx_)hiH7\ Jk)XlJFQ)7v~;/25EN%Hu]+$9"p"IQ&3Fq>Zu'Ib)2o3[x!HA}p4D Bu)T *ދ\%UU$i{DLcPMkbK24\I iS Ku{er|pg0m:.\\ZY{3_ҏ\[]˒.u;n[[.V+ޯZzƤҥK;/|aGϞůy!;,;}k윟j[~FgϪfOjƈe`QJ[ \ 8 HKe%*H ΡrծW>ݻ'q}|hlVZ{T*McRV5Edi4Tb $fk"_ZZ&JH)"H l 3 Bc!jc9" QF@>R-Q.Pj  f̟lMm/2}!djjjEX׃S9%15* X!gL+KZk)Vi 4B5S9צZ"DBR+c @|4a܁V:\L!B(,1Q9ncV AL"`R.wةkY #kt;Ҫ(>0UUZ-'4h-델 rF= 2UUBw~_eeY_. kUU * 4M, NMOÍfl)(1Cd{[-A5eRka5J*$ѾFC m%l!3 t{_h힛߷d<"pm*hhJ F $<+ȰΐΑ.`nkA/]kFSLZ!AfLubL`ઃ}/>V#Q`cʦF"D;&6qj5 B\YdTFZF.""b <_kVTeYJ[k-A0 /QG$Hb1"Щ;s :![_c D⪪ 1Xe,w]WzP7r.^Xdy2 3peD?ݡ SU)1y2e!Ϲ6r~W6ԬnffF1&Fc8r/p]ι(FQYZ# zݙƄP=l)"Z&6%nkMoDRli]9YbJM5H6I |nkvnmLv۽+2J龥"/OMY\Rip0I*6o@`h1!![5hiVe ܼԨZ,5P h!\T>rzXl@+!Z}vv!rL kcygJ_98k,D 0#`N$˲aPT%8#9 cV}y1\2ԀC)PJI);a5:b&9ёV;`0#JifU*)FeՈC`(jk$ c Zf2@4u#7ed-`"< .E{^#\ @06 ,JsYnBWfڴYUU6LByCw6۔rF , (}aw/xyZyBF4Jb!H; TJji6*)dqRk!d 4`EF~1(PTBXhZ=iW={&RE5@7`04'ZƀMcLۥ.,,,,,pW Ͻpl!/~ٟ7^wñ^(5aPU^G^PVPkbެu@*" 3 a^(M[(4ϋBhuLC&ǵFTY6 _ZxzjjճWuZ#ϋ,wEѨ=SgDKU!0qS!@Z! sv0" [,-˲**dvS cMLW?p=pM7_W]uՉ677߿K$a͆b/\'?? |ƛѫ]>ss JI!2RT9 +rcXw|~Uj-TTERjM^TeYZ*E%J0&Zpm=I D/K B 0aF…@1hh2r 0 ÔJ D,LҚ7,"cB($RJd42N*k\obȕRy( C?|WUaQeVT@q FJpЪdUC}{Rp1ߜyEQhm)ft\#}*eԗZҁnRJB{w{dQNM67׿/~(X[ۨaiZBnҮ0wۻW[2A*:J?QV42@W.lo|+ pa"A^%M7W7V aWOת7'Z!&{~# TT!BbNCJ|s0yBvgHEƅ*/|[IV׍Q")1E0ə_|yg{n8|WVennn~k__]]]Xg>>w^]fy, AcGgkBHI=t5@`)U9//˲7tK^V;m/^iE~M)ղJnSL7O+bb Q袪 H(Bq~55EѮb4kk{?OOO QZ=]*0a2躣7 JYHjqcrzj~곿{7[O>) =SJ[kzg<80d5v5B?# ! 1F*-(<_X8lvhY ZgFy^l_X=0OW^|]Y&-i _̋Ͽwv{|bu Z{~ZJTLskI( aYxS]w' J!claaq},/1jȹB`1|W^9:8瘒R%V:\4[8_Zv [H0F*c 4FFK!aRJa2 1f ?!$l'Ł~/SMוJAXZ)" PRW Z=MSBD!h#2R))n]GS̈d8RXBqб؆cLU`{=BAhAyVI)76V8 arCN<{B܈k,CLq@12aC垏R$y#B}̇;I9KDv;72Ag"c+7|Giލ"0BQB*] `,)esJZ=qX E[DJ5RZjRQ'Z:E6RU`(‚vy%jZT(4m4Zk)DV8nXZZ|駅>mnG˵Z(*9cDkM)s.t >v`1ɽ>я&iBXJC) \32iKƔoDTZ-om'yZE sqV$Y-JjEP*ePs!TJpYv-..XO_?0 R~|3/tlޥ"d11<;~DYV8 l4qb-DM':Āp VTlZk>#T%PH@9Z_sv}l ~Ȟ:sO/?O({vG_ ۝`tv&ڍG:fUw}#( QB1F^ m$*aI/_ RB@<[3( {U+^=^\F,-*l#,! #Y@FWZ` b,G8+b5JUE'$V1Q`DQFf>H Yoy4I (4˲lv~hPo4w:JFiƔTRe<(A@eY>,yŏ<q137yWw;r5F o<He1.A,2JcQm $0Y0B驉c-$mMP1}~Cxo}f;I2dd1ۼA/<{ę̹(Rh"M2PfYCAd.zyY *V{¾}wLܕr=ұng`!akS## JVRPpEw ORTMӔB,+ݖk`N#I~䚫zTEqjc3Vk"x`G"1Ffͭ흞DŽPA<[Y_#TU5;5]lU.=+! 2ZcpH? (}ߟ ZcHAe(=|4MxsOZ-7k !FKq1@kmv%>؏BYUz*iJJk "-"L e",bnjƵf#- DhXɲ\+a.b>!15?S!6Ea 䘻X%~gϞ_|(/pۭwϜ>qrhi~};L1Fa;LR1)e*K]BYvmmQnCAogcvgBxݞnN,_Nᵇ>|:v-:Ō8fffzO\$VTmأa&S$Ld)f  V3_UZ b5Hp4|@{4Yz<2T%,5_`UUh2Zm1 $0"}_RY: J)!5Qm֌!L)6Di-K={LOJ =JqJ))JkA$NzPƘ0/*y[; +e^ zAȢ(@l9*l4]{2b*tssvypxWk}GR9s cyaOȏ:s!32!,(Vjk,4  k!004䙭 Ea/O Jd W($y\' ƌtƕ!z.=F)&im͉߃꯾S̉n4Mn(o?@爳NWo8ˆX8URygR .K)%2bZ|^xI0v-d*-rmeE+hϯ0ivq-X[aej1 Uc|nN=񤨚~d4"7>IJtW\AR9llN.qӭ{'Ǐkk$)kp^d B5@6ka} Н˚qB !J)%ˡ{Y(-V 0 xvvvcc c0sRˍ𼨴J &qB.t)|P" mZyf¸b޽ªy)`I;BeiR6 bΎ201%@D(Ӭͭ-I>,N^F1}^cS"[=wg{/cP=Bȼ"Xh Ž:,FC >[R<ȋ'ڻO"Ƨ(r1ͳZ2Ae I* +RRRԧM}i4jw}Bh03%Ed$Ij04)+{^u~}_RcZ7BmbRBLk#yǟzIBXgF Y7BmnƘ 8B?q׍Z*LZ]tmmbg4[YY܋&,`Izuuu9ܶ{n fo d\Q {nS~v"@ؗ *v AHt{8SV:UenP ժ2 x@11PRJJ v]\z JSF1cڨ4όRjk fZ4_^^q㨨pXkyQFl1;;;Rg ™Oj)B`[$U{qVHR:C=u,!jwi?֨(8#m?'[+Y*<wyL2ȲB8j @"[ch B.X+0(VViAVc,~{ߌj4/f7w9c ~}7|贷iRgy i,kη,?N=nI Kqs'(  ТTJ)6J)A'AUj@ `UaXk6v)jZ011qܹJ 1ō"StU !d8ij9 .CkECaLE 3i C4 2<"c5h`ItaJ%(ukM~@g;; cadڄa  8og|ރy-uppVg'<j!ܳ4A翐eY[Op??3?v)4CL~#:l`B}ߟ(!z8V}BB)~1! !X_X:s&)r~ӽ?ٹYj PVNrY)YJn/?/]9 ,\[[[\%VRF8g a>gkQzܹsEUmM0dI,fgZfވt0J3=ߓ*o4U%sҲʶz/7ddŬ,^ywɩ;CK;}SIUH|)ºJc>EZC)6/[[Y 0Qq^ fWBaeUa(bFDE}Sày^,+7.2 d / JJi2$95H @066Bk<`Yɗ_<(c@k.Ejb^ t2Jv!mӯ@,,˲;zݯp|caRIhB''WVVζsKc,ˬ,F"" _~ojJ ǬB+/Y2 t` B dAokC1|3/ka~~[ns;4z" $ISX+ʹ+9jjUR*6ӼHBK[av-Ve1Fc0e !~TcʒRT)B0 ֲ8^x}[[[vMjhCKñ6v*+)AgJQJ!&0kqRk BItw1)ep( qU )RV<b0Lp-F*l~aWP 0VUUBJkE%EQloloSL^B/ .IJ)5]O v< Y y)*|n]^B|#}ߵ^M )W~W7o~z{~a$vI)@ !e- `!(h5L}O8kϮnΠrWH@YFP[nc(>!4W['O:{lL<7u̹};mΜ_WOk$Knhi{ 'f|Fq2S/2`1|8H1B[[ZS'@w|_z!HiYVV,˼*Y1hNLLߵwkusucCm$ @{^Z~( 5ĔSSz0.l3RiGrX`X"Hh#I11٨MdTyrp v[3iRJÇ?y}ϵTeYB*!\R.=S?S?{,8#:7~}OW$]p.-ڸ&114mJ~WZKߏ 3FݛTUBkkkV 0=yӧA2L3 `  *S8GkZcc-s7|KwtdR~s|9˲_~7a+X\ivw9*B1vrrrkPa# ccf$SS3s_?iQ^?13^ڽ +Rpbt41|hv/rjΆ5O֠i ˒lcmlll.^4lYkӲY!Q 4Y lN;ZQUs3YFmnn"[MFyzK[ ,eH*_󚻔н~WVW7633W_sufq)D;ݭ.TJR )4@=B8e2 (Ȍ&i "E6{v UmktDY28lNOO75! .xXd~H)}}e4=&Ӈh: I/՚<&+ة$a Xe]ڬ*bƘH)5JCD;Bh.EXk1F!/.ԋ("cv R/oj(֍Fcrr}@7ߕyt:&fJd` L ׻$//j`0|+#{煄aa=ٳg!kn Z0_IRΥW^y7ٰ^p +J)Y+mZk㔸q 鴍TGyzp/6~~Sz J1#fZY ܨck׮ζ#~''?+VB!/ſ$}jj i2 'M0 r~beB?MH{͘/L.,.n4kӭRy׿}S,/AFFӜl)4LL5U7Z!EY XT7Z-h2*[kBn{? "XK(rBTkpY?CDf疖RQu$Z-^ 9SoȴT(-qd%`δV)bDn)Pk4ڀ31%%WRv75>ػϧ\k=ٚNMM-,,[^zi}}HlXiQYkp4kMoAZxufJֺS1rX!ɜ(s}<BUfgg/>^&=ynmUU/?^prresqAw<]w]^眻~Ш[^w!B3RehjoPJe ް_m>qx "T; fc.i:ڢƛo7V7y3|S7Bdj@RhW(ƕTJM ڟ?jkrJm3+w<(*h6 ޠ/de cǞѭ +7v=[#RN2ofa[o+k=dNOO9rdff.IBT}U  a{>v'^=199EOaUUVKNֲު%^yjm0 yB`ֶhbN:53SEQH-h{Aݱ0D0@@(G|Vk~#`&h!a(fY1bF]7^w`0xgágϞ>sRiDA10&y,0FAkN1QVzkmW_yJ9Aaq?zKɱIah󢄄B0fY0xlqVkq@ZL5!L)U@%b GZ0q)yKQ Dj5y䑋+/?1w]߳$I4}K!Qj󤬌8٩{7/=%WMWhFץNj\}L'ddol B=.JIz9@PXzړ'_-˒+gN[R:(4MwlT؜!qHZєzs,K(r}7!^٢(\x0 ǬIgb2X]+owc2'=- wm<%krmp jn78 Ơ!D J{]n{ܹ4 Y#9eWYwsye7dKMBy^Q^$<`sgO4SO=3Ʒ_xk2Z]yO5<-WZS7Mu-A/ɀ805%Be}F)a%󄐥,O1qg!4j)BJD@ , B[N0*eq4A7bs.w|i Φڌ%B qKF>774Ke)jQ1γrXe,+w( (z.JC R#ծz8u%"BxVTN3ӥǸ_O}9RByg á+RkW9wN)t8:!dee%zjyJogdy&ܐpiWca=\w -a(FǵK/>x*W\Mt<:PEmrJ[*I)Qcy?Ӌ Yyw p8E4%kB( yLD Tzׂ7̏~S|ށy^Ds6z(IH (?#}'կ~뭩f #UFU Z/lm:}FVzfvk+xǶw"dP5tEKUER eŒ*%O>㏯}Pe-R\s<ըOZ(kaͫL@s|I-xލ!ao} cԛ[[~iJ /UUQHKCBPEQPNGB/+8AIUUnl6tԔ  qꘈc"ws=Ч[#ac R'sT8q\(R=yGA877WǵzdY|yQʬ,SnobE0}3) le!aCB}$rc:Bd,[zq)%{/cG܉@`t@"cyQ D)WRx$ c}q/ƾk{Ύ["S!>g\N].#&`JiYeY...lnm|Ba\}լ;srl'!ΠR@teZJTa,P%F6MGkP+_ys2"-ExL3Xc!Fgr;wP++ֳ" VJynR!1DuH`$18 3=5 fOFBU1`gWKgBeqpsLЕp#z;Ye-oZ=Bc J Bw4qt<qUUIFo]Q-̜8q Rb4uVw6{eycE5:q~y6(!z!!2~z?3UUB*[80ѕKvJ_ҍ=˲]+v̙3va*"IAoqyl4a|*F9F!v}ԩV}K[Mt #h &H 2@dEG/{IJyUWٻŗ^W|k?fErN"tib[c|fiҕՋ v :;Ѳ*,?|P^dlL.Cd ŝN{: BW!0YX&@bA` < CGaW*eh#VJhIUZG}ĉӃAokk[o/B#RBHc˲$E: [\\C_?sdZ s4M9~G"۝PrO ;sK_{u>{Z_ڻkZm&ȏ|믿`zqYw^qeEQāϾtp Ӳ,گ|ӟK_F֠k iRʷvjZ 'Np8& Ùٳg`J c[bqSyOpK7^^玽ַu;0B)i^ƚ8 +`zjr=1QUGm$ !8"XjA0^k4z;]OVyRFCh R Y"|g򼚘X߸[߿K/_{JB+y,À!Ƶ'~O8_T)+h5A7~'9xgg.;?Yo;c/˪Z[0/sSSSy~}zbjnbf`3T2㤳֢:LgeYveYƵxᆬI{Y^T $LJScBJ!(rWL;K͊(mڐљ) ݵZ*i$ ?!@(i!1xa c hw4*74Z c"WZZcXk(ZcKBSJh{{{յ(Fo}[ !UU63JԴ*5j 4J(zRJq5#L\ZUUdЍE:tYBURhy G4!H(H091hfqsu pc:8jxܖ$CkHm2InJ3\t%yjԔ1]W^\\qUU_^{MOe,0B E%[SӝNgc !l4UU9bÉǟ|Z֬Ja[o]R+tKފ6/#3'^^}W-2A7z&ik=ԜOQOҙ%Fqj0rX0u1@UB%* ?B{D45~3I8 P"$뜅JQ\tセjm`AU)!S!5-yƨ4puڍf3|06Un<;nJ"l8|jFW_}ɓn YO{߶ra(+HSIĭՕVq]qBdq~ev{P3R5=SSBGaZ,,Bk/ 13}V2D1UQyN߮)B)rЕRI".0UoQ`ԛ57/Ue5JAd9QRAJ%c:0"MJ?D! c_/aGZkկEшb)%( ׾{|AR"x)MbGzog}fUW]ɓZ*"xPW c<11DZ[.p +++nq#n qEUL]48HǏF+>aV0RBHcFYDDw0c(rM%y9`a<ݏ{vclccI$'&&ƶ -] ð^qvI{455E#kQJx̙8=7VUUֹLe//?'&on ??=w6˲/fY(bjjj{{" Fve|?˭1HJJz+AՙḬ"ى(31'? s ^вra%!HK[X%vEB[kR f&ϲ "ad4ae{l}}"nwԍDLj\\_YY}!l e+U$4k( y晵՛0h5?癐5Ѻq$VZ =|gG,GC`=Ыj9jv _$<!t]ZFTڗ nwyǹ,w''s>*lB!A `]. aG0Jk-ZqF1]Z!#MBTAo\XXW|2ϭB e8dݥ"_]]ufeי !D*OKKK&0 R?&Q?vЮ:tI*vk}D%aͮ}nzf*5}y^jڸ cݎEQNdgj.RuCw7 ׷:VQA"y$YF#Yx_|' h/}kc__^햛d%?ST%᧗ s/"xvzVTt}= Rr=gz}U^k#JvR b%iZ~+_9ﮥ Ud2D9ˢ`EE 'kxU@Ra=G>/R1U00DcF#Gz諏RS;Z7ڭv{t=K@u+ `,hYsǞ#}h3y`n,bﭷ_t2J7OtbzjFySS™9Ngϟ/t[]gzܯW}O[=OhxAufg 0D5 Uml!əɩ ZkqwshmRk60Fh<`a#b/Y QBP L%92x&\ZmqL n30ŊY[Z I(NX "-8w/r·y>!`cҢB2h4<h15@*-+\jk-%$SNw?i0P[m:$DN[dEak 0ZZ (ouzzGCB^}[o @ jCkn-;7;Е(RJ#T":ʊV[W (ϜJ)7q8i\Q\qEQEt{0 ݏvB̺o}o喵6iCyۭw_Y4  0]'ͩɩFU7fu"/Z#H]R*ϊ>p' fgg76ɲ,!vRղ"8"bh`P2O| ~Jjte^z3Lzb;fI*!,K84K;`ƒ_~;W{0å,dU4nu7wv[KZ1&MSJ1͋eR$u/>{lgg{vzJY1ZAQ馔Bi !bB Ph-0a !Pk „y Y%EZh<߇(-:"eX+2"̥Ԍ1)M)JDECL<-4AUV^è(f'>4EQ8;s۝>oa* /@670h[YHƐX cqy"ZF[_|q}y2rBdf(2Jq>LjG[fkt:ZkgF`lk~_P|brE(9<>??-h Q=NXHFL=z'6<:,_>yҧ<SJ8sQA(P" f=|o>ᕕ*;Rkj {<ĢR"%N{ X;7;+zRVk2+?Пmoǘ0Fcq>Yg#Y5YBSF[\.)]. gdѷ5D9"kPRҨŵz!!PkY3R66<(,+8.FBj]T% BX`!7iq}\x*iRF@JkﱝK?=#H$ 0@F83`ŋP?i:a@0\___\\uͲkY81ƀv{sfffhZYV;$ٟɟ4<ϵ>eY:X2>oӷ%d@pDbxnnc.}fMLL)j;\tD9z 8n6'NpueElnnZk3RWVVZiьQs+/iniow֮ݵ0B zv)< T%7VWΞ@F00aiN80]kvcB)@"fXBZբY1%fGk#W^y%/KG>򑲬YlZkK8ǼqeDqC!b-2@L@bh@jDGV-N_aDPA @kӁHأ0Mey_X\۵y֣8²,ZFSe7SHY 뫫S3UQ^ GZ \UZk(z׻j~ܹsRʽ{:tuc|Mcx!G':D;yP=KKKnW[s0t;~_5W.@r3Ɔzfff*I!DΤ_,^AP˲\]]viٳ'MS7ݏV?RI|曝-"d…秦|?C{oo Z tNy*SRƌ$IZkFJR2I 67SSS++˓SB_A~h4kFn_Z{0 +yoooWUF^>trrҍ$@Ca^BƮ]?˘{ಔ$#G/8p`nn."bwsssccc}}#e渱uqeIW]ucΏ<)^w~l0A}1&<`ѕ>FfZkt62P!MՊDRۛi &z`{^)%d-D{1q<AB.ɮ>`N`%1RUU)Z6rOSO~xֺﯮZISslp[[[r Mw׃o.rӀE޽{RnYVn?Ѩ,K"z=a؃ùv oY t'O\^^Q$IRάȿu߿?2~JۛK,͊Pnv| R'Y-dQo7;EIԪ+׮9'ɉ\Ղ6;AZ[c #58sm$8N9>B UY(*R[b1 ڢ(G(};/ /3?JU~{q4*U纥.xV|6 CEP1767,F7oO7wf;|F$/sꝵ[ۗ^~>b28l5 1j;߳0i֚ڬ(WWL@<p.AeZt8 a˲le^L~@3J$d2?˂140v}ӧό'p1dy;R#{6c1HkeLX)EEQ9v?~kD۷`Fs?sJUo-}Jk-C i\*I|2gYV؛SJƀsιEOD04Ms{J~;ץ|g_~zs>60++w$.2M3bяnnnOWW$EA玘L&@|Dpr '<| r*SUMjT ͛7VVqN:u'O9rxa9T;JpxU>Od[{n~~y(ߗ.]2l3~imTAmW1#ʘHlƳXxO9}խNw x<:},J8McR V 1xO+W8aJ2YYcX+X$(CHBVV\O"k44M"ge}k?x3˒Gaj׶VϜ9sȱ-GtQ#G`kk+, F|ryO3#Ksz#̲ըGl2lM'h@892A 2/&f2=q}YXK_Ɔj[KK ]O=CzD[sԩt.( dýฎ0(BRC`Jv!T&gmr_\nܜ' ι# E; 5ؚCxiE1Ve)V,sTIAH(Nv5%sA9?obLY¹1N< (Nٟ|g`0;Cl R)겹Lʍ 9q[Aظ|&_RJ!w,%wYOg"Q <|\YHpJTY?vɓP*Oꆬ%4@ Bb ~ Ix$I~mXgCf`!n=}ÇIyE<r ({w2ܾ}ĉn裏~n6i}eIT~@qL^]`O.'@u<|<̴"Vzqł;{0lllc`x/|7~O(GV+2MSo$io `l;ιBsRBUnq,sYA4 0ѶNw`B4/ l2 &(eXF:,!1&"zLhGÅD.4R,tqoFlm:R$QΐUa_[clj7_޳haPkM <|?zƍ?9.VR掠\Ӎ(c˼̇k7۝^|2ܹte8i c-:0&1LRHi. Ei˼;=Y^NyIKFmyaF1 c-["t0p, E`O\nuuuss!Tqz^q<RvlZ-AIyttI'3g۷nAVĻx|ʕSNϟ={ƍRK.---JL4 6w[A۽=Zfڧ V8IFQeZ ~78q}>ǯ1d izڵhe*\e1Hoָ,zYv3G+m5"‘YNכ8JA"MD8ǖj)A+j"KOpߙV=UB,T .)$Iղ&ya褔jcC 2‚C\2A 0(8277wG>9΋x}~깏>,VY%d06kMRʢE>",ʳ…#( ώ8r}rBhzfoV}OL9#XDQy晇zHJG*(K)&,gf ,; F*e9&ķBkT::v-Q(cGOڑ,ZHTj;a+ڢ8Ͷ,K676<DŽ=Gyw}., bV*P:Q%FP zݮj@ e_v}jsa7_o*dK)+y8Ze)sFS㹾{tj:kysA'N6܅?񧦳 #cN]A &ڍ\*dq:}DDFM&ZfڂQCEyYN&x<fJ)d= up?g~Ym4JͦwϿͦVk4M+w֐V{O:z}sfIqńsO pQ*BH-+c61%~zUQj=>믽)9~{|K_8ۡZL(ht Va=?ˋxmkt4kI2#2}( nQ)ƺդR+`hK,%(m*AHk8l;1RZYXku8.e]GpR:㖌BB]KHGYy9VIDݻG+w={_km06K eYֹ8-˲leiieE9SJ,cZFIK{;oJ)eY(Bx ( 5Hж]R0^fr{y lU׺.{fA|c[\\tL*Z_NB}ZJ`XZY _޽{:L+gU+ןϞ= -MjPׁ=DN0v{jx҄QQ6ВCr 8^0iٳnW.ŠVkZq{VCߺ3ʐRnߴ֞}}뭷[l4y^3&:ힵhkkorQi:՚Y:tcdDb׭1ʒhRyQ<>eQ2cJ V6fII5,u\n_z;ʭ- kz=tclll@&˯xR:@)&aVo(#Y6[4I# #BZŜ9W\k.^¦jY)S@,%,H#A LmsY*JxPc6I.[W6[Ņ>alyy ~O)ElJpcH͎R3JQ2QDUw+( ASlQdj&ՇRJF(!P2BT+\ hBdLYX#-! gyA BnYa)51B)KP88C_ "c[̤ cG._z里5)e~m:#;{9M,@9opΓ8BcA=ܞ Pl(JiX-8g AV$Ab6>'jڍ786C6 n D(&TRH*AǏ((U| _³,L&@p*~^8|h4 6X k+&fHnC˂dHN5q՚L&чaXgw}:1O>T` @}LBZiHIi>3w߾{ =ܥKVWbQ)Z)<12Fm N-ϯ//}¡Q6x41SQ:+U9wW%*pkkiz~j(dQzxh8^Z#e}Ȳ̧뵐 (4 TYj#)#q<#6ZJ"]t6:^ C(cd-AK'+ҢdyQaaުosV**._~8'ܝ9S *л{VԭH?l65IPjʃ}=a& +W,,, ,gvxypx @WjMNVRݳʃ}.#4 j$q*#RKf0FQJ9[U*lEQFУjUn 6k({z晏,{ZӜVeXGQJJXa1]y˯rw'?WiMXZ\E ,.ZCC=w~g7߼9 {~{nr ݼ}/g?2A4!Lz'bSr)KgR^eF#BH^! okmSa:(Zd F=ܸEj5$/v]Zd)!eSfv+Dc9.!uRF˭NZ뺘qK(Y2Ra1i0L(ZK(Fan}; d-6P&`4iF,"BPl,(&aBf8Z8"KMgF'eIB>w#daPFC0$MrF?$Tv:,j͛7$J%{XN ;tcLET1???77t*|W^]6(B=+76Hvl6`v#GTAXȏyWNՌ2ƅ>xn|ZBTLr1WT@UIgoݸ~ki#YM)kZAZKMES\RimyY֟0F0Aɚ23Eʪ2+A;827a;?6VL|cO<5^|V6V"[FLj빡R"bCRKw0s ~C޸y1F8*]'t_|Ͽp3E^2nu|.c')%"53,By$%Dln=-U.۩ȔD5(!RV<k>ane©p1X]*MqKI-QRZŵ S*-PJʬ,< 3YcKU*3#L)&YPN(334Fbg"x[xcR ˷jl;eYOrmw|߿qFH9X,sˌҔ<͌QVkँJVX_x';R,Ҽ`9, +LӜ G8$fb!'|0}BEIJ O.-/ !$Rs>#'WGկ phscl rE__EDh_[Y-riyy^DD00>q~IcLXp?-ݿ q[wn cLJńk1R ?J)k˼2-10&@]mж[dְI&cMrX q@k-U Yۮ;srt2r85šr6Y]?s.cvB"6N2zsY27V0-#Z]1t7.HJ)#"l1Ž`*vR.;ޡJd9\zS'N8p'85cwPju5NXl P0&bkEL0+ D,Xbp1EG_0 }R#]]XɘPv):j52+#WbU$I m6e=~=4ͤVFIi)Gy{N<_xşO>oo^߾iFZ=TJk&G}ڵk^j]4No߾]!(zXp_<2`bN ZVϟ**'NZ-x7Fm`A³>vҥS0l6p8fЃaQ| b`2 v*뺭V{z*[qʕJ6#Ç[r7{s !nZ8 1FRRnmG! J15eY2͊<7֛)g++ܗ,F`bAFfBxp>zhe0 'il~打ǃ5NgSA@# k*u]lH1pð1ͺݎ8y dEZ+k &A6W..%rk5(\-DBT06Q &e )4aeiGK|ߍ^a[dTa1cERmq 0ۊb`BpF9`]-iRj "L!4 9APL`@.!Sb<!r]w6`}tO?quu5FiPeIV7[+g)C m\/t|l6[]+?X>xpk4Nj&$QJnw m!u(#-jc8<D3JXb6]?J^ʷϽrgEC&6MSqa^Ǐj5((#GaE^,ˍltA9_(Zbfdo~wr1ƿ+|Ðq^$я~Ƭ"-$tp]<~sss$5`q(fYa81~Bф~p<8p MSSdTR½ ;/[_Y\"wz'&J)Dr#EsƈҥwY]ۺ}vɲB)9Hd=ykm qRV W{}H,ڳgߟ7={ֶ݆^ondiÐ2 'F8ʦiJĈ}/J'iϔ)ZG}c't:M}'T(UZkRJV!A0 2j5ەbmA R#l#RA BUZ[c cs\-eYp\Ďǣ0 ^om};y"˥-)Y POÊ"߼q5+Rh(GV<q=0Ƙm 2>9ўt :sĕ뗧qV)N@ DO>r~~c\vɶwlvy `4A >oBak1^]]ru
|0,aJsҥVvkk?Zk("4ԡ²eYCz~f7(WP8 .tM-a RJdXV ΃F#,RҔ Yϝ{͛ӧ('n'Mb0r<aLwQ׽v:KiPC0F#BPs֘e,)B@(B.RJpP>^z֛o;~gg_JspccX|Z%V)YVW5ιH[#"L0 !%ņ@k5Dw(B"KlN#GnΝ[kwo{xÏ-8e:(]"c6ZL!XH#1<)Rrdtm(㱖*յ78kZwy1u,s2a-'& kmZ+wnؿյ5o'kqAF5[A`8WRKiM&.uFbQQy6EIecB-j4cDJw}eX؟C iL(JcZJXkСCpj8{*&t LZ q ?a**u^F91 ={ܺukccsO...F#[]G>]0C}yy 0u@/="JJ5ڵkΝB>ĉ`q!B4s0a{y8<%X߹sԩSp'wNJ m;#֥KǏ9o|ʕkeY AbJ!h0T;͎:ϒ /ݹh ޾}(fS/u:6.@ +p :!$- 1!?t/_~{O_;#| 3!g_nQfCje%'>kexUFZcBx^z?zY_ڍZ[&8!,K--k%&ֺ;q$(8 a!8qE WX+nmlf3ϝ%InpqG)Iku-t=pϽ&qyd89s֭dj5777gyTk0KcΝRkMP`D֘2LhcŘ` 6`I* tkk cLd({Q?8#Anly.$Xxƨh6b!\8,V,j8 8ՔP.23!XZa1b8jl`}A˲$W8Ry&0|++Ї{1~^qsݵ7x#R{,vZӗR"fj`Fkk މ1"w~f[-h:6 :lYHс}ɂ[w,\P&UY;2ԙ v\̋^z3.fg!N^OW_{SAĎǓ(hmYI(5<#jlwkszaì{<# |?oKDȥ$I* g[rC;wg>@r {?+;}ԅQ /^?>VVV`#)CM@)P`%2+%j"/n۷oyy͛pSJ!]J{=՝Pct:7h #fYF3+ (R+&x!s)8RJW_{g~)J`$ 9SH^ }LHوf3?Pnf'򯹞hhbP4`jaͦZK'}VE ﭭofZn2C4ZYrJ0e,e,nvC|$PJ+<-tmmm%Im4W0&1!R a.\+Lfy!)QLɸ8V`ʕ,=7X˼:l/?_yW_8={8EE2vedkma<%amS%BL~>2F !8RJ;GI uFyEt0jAlp8Ty#TBaS7'3JRFQλYaz]ȳs7&>cL^9yd2a3Mh/p)42s}1|)eEQqj-BL0jY c6ai$T"p<\HY ̔.Dcj8#(|cm=ZǃVQN/VDh)qq B[B" <:g%\rH!! $MFݦ\}%,}Hǐ: 7sv@M,@[1qTm*(e2Is/h;g)n$ts}֭[6Lm1Xܽ{w8:t虧z/] w\^qYo\!nݼtZ~nܺuw.]c9*ϨѲ,40ƺZ.s4]⻎k0kוRҢ֐'JLbmm흫wؚQ2*qrvX«k\"RYܱ1ZK)u]\&zd8anpq׿{Ş={|C?GyPU2|J鷾ڠ؝1 N} B媢e{5S:o}K_n#!O؀7+Y)NeY**$ * p;O_X/o77~&&swDp G@Jܹ370^l鴢xZ\ 'CS0aNu4[SO??lfE)z-V㸮zQk0EqÇߙ5u߯!ua^QLʲ$a0/W7gL%lB%ER;C?~)wUkfS*!chE!Eh=͂EsEYEB &2JYk0BTt 2>S`?nʾ>ti€udEQLJJÀK{pͪ~`9G|߇u9L+nZշVm@TW#YDsED*NxaUjʧ,+L$p`#zFo߾(Ν;wY0Zan4Ӳ;DE0(f'c$$Jf+++I =SnR,R!VZSʘ$IÇ(ܸq |AJ!E)ur$#ǎ%T+&BF0uP1MU04.4c󛄇HCL]muY`՚+Ry E}R#e,ǡ1D`E>^b5To)s q&A^=yx|ر0$CEQ+{BDp)!la7 ٜo|{nqCcMY@8]i{v[aXuw0POqݻw\*Rt:UJZ-0(1L&:̅ĵݹUNl7oF#uq$j4sss_~GwA/deZ?pٿOՌ j5˹.dr-kM$>}O<n&lQV ͍Z(KeJˋs=s[8w܎[^JiZI`YZou+W7Z-k_}CnO*`ަ.[cAc1چZϦbDԥWN; [&)ښ4N4R$66Q;eW͖n I[k^|Wz}sn"Hu]7KҫW%-5cL-(hsSJ)%00c #&1:4MЄfq!Bq ~"^}n$E.[k-w8xE)fav=p IRq tEӡ\r:qsVu^~ů*„ >Eq4-RUhŮbeI--WB ˸eb<{9e4-V$ xl\rW9"Jc-TZw`O.1vF[Ji:'N!o"V$Pcg^<Ͽ/?@ee%@2> P8*jYZXICdX)%&bcW < 믃t 4fFL?sY:"'Z֔ڏ~'WW~W9B)Ef_)!1R kƨZ c̕ײy}?wyXɨLW̐Ѯy O;[[OeFbb:t(%ߡ#FJB!)KHhck2eYigK/6u lmO^{x\B?4M뵀 "]PCE|8_oJj:,F"K(e,1n QJ1%91iwa?ܨC$p8<|pEN\C6a P! 𪲉|Fa@叨w*<8 z!XT;n8WY<Ф`2L,kQ2RƯ/rQz r8XbmQFm`mmm<C$Ok;_p0ܹ3LT i)PAQI837!@k-1&zeRݩeR-ghEi1!vvW1NkAH0ǎç|w4JnO&#!\T[SR0gf F)B(\;Ѡ ##0ȀX-c5SBB(jw;3088+Z"Qh'DhB:QgQ[QݻWk,[X: QVynRYw9+/1x[X˂s+L \&N5 \ emj7a;rBܶ BN/0ƮL(2)#H8{Ng4>Ctĉe:}p) "N˗ ڍq BxIZ( NQ߽| 6رcYV7z~'|qq.o\Zqb *l%^)wQbrJQ[+\kùc .4t^Xc)ak ˜QvTRL8F ]bޥ OV˲l:BѣGoݺu̙/BN(`.e2dZl&*i?;!K/FeYjv'w])?0^р dmWiP`,;:h -..V|e=,6m1`v1?pWWWſ1; נ.[kvTy/y^U.;|ګvh=޹1 .6˸VY)i2e&p0q[,<SR,Vi(6Leg0)أaRF)b^PJ Fvy)$Y]]edYfw-p8MdM4֢VkE~ c shTBxh̉AB# VSw)&0o/}^}3?={n\K6ybP#K5jkM-fpGxn8.c>vky WױQ4X~1'ϊVɲb<{Ra=f,*d:t}7o\;vZP,}]ݵ,JwU CJ wv]WJSn#,0%I=#;Y|-lѥ 8%JG0d)TL # Idy6EQ.--}G^{\fBl Y"dK)ZAYl]v{WRƒzC8^d0͛pD*ZQﺵ7ΝK_Ҧ~sև:.U\z!!gc,$WeV,cV&+ AaN13`bA"AC-V }yQJ1խbl*(c+w*d`` &_ Bضr(uYJ, ! ![l9gS,/.Y^"SኣWFlnn`T-+4Z*o=zLXq<Ν;7b¢ VY_|q߾}UQ?Sm cmmĉP++CՃkޝ P~w~ѣ=,' ۖh|'[dWhS*5 Nk?8#W*RJ(Еpǀ79;Pkm竫>`2aS:8N[1T8ޕuZmmY4Ay?\]]L?|vz/>Sc̻^xfPf,c}? Z4i"c"Y*(KKy˲$2q<BU,2\fsk̼ZFad]k1ު܃F$vKlw.P4ZJ ab'!*r4ԃyeN&\y[7>O?rp2<`yK,a cjӧOwQN0 ;x4EISNM j˷[Gk/.-T`D BQ%B ~`dA"be1ak2Z{vLB{Rݍ5?,ҞA`FRJFyg֦IY%ҔpR%,rFW kpժh,.CQz2 Ra>t:Oqge;,ᶩr[Y A`c9%XH@\7ϽḞ#BywnC1V0Ƴ,K ?6kޝ@Y !=PRVb f@ ktBy 1PgVqZ4͵iYkay֭'O}O… v굫Ơ#G役ߺyZT$Vj52]Ji=tG>°9uMJ$׋0La8j++w1Ʈӧ0/W2bkwq4`j~`FS "+RJFKK=b.ÜRF)ZAt!Ī=CV_}1mIQ1q!g0j͊bo6v;/:r <`?>A0 붯|+=#<=_HH(%2ǁ[.'S}cp TL!//O<b{G|}}ի?3?Jx @=p޽{FO=Л*J W>+LJifuK!e>OS&ǹooo^$/ ^|vh0}j /f KI(e׮|߾}^G>:̒4Os_tz{ð۝Y?08لRRyB @0O|:$IAXZ TiJ1Ƙ εbmS8eޝ;wN9s<2IpX\ fFrfYPD\KӼhoAkt JYH0 12[ `zP#ksaRNxyqo넺Qi2}`3W&yL̋TaB.S2υðz@={绞rUQ䱄4RIl <5^^ƺƱ1s:IlmϦ~-h6ZFk Q)s , %K- `0`lbL%6+ LX + !6HZ y}m&1J/î{ 珶n9h(P2D*i Ž0J-iz#GP s69+15oEugy<\6 ؘCx<Fׯ__XXt:fs8~Sˊ*GU>O@;j#SQ`ҽVdr ^Ղ`C}_Wa\ g%NU,gY~Ȟ6W;QH씐VY^$4P[JIy~zussmvge;湷]7Sʿ=ޝc%QٓR"ˎ>aM쁳cjAO%_!~~g aIPPN ]HNO4LF !$$9ԉݝjOu+^z3I^op-\0o7OTk .}ǟs\p"(e0[-AY^dRfl֣(sGpR ;;;x7r}/uU2%$)tna3'N(B,ZRkz9wH$֖zU=''{{1*P2L8*˩`U3p]-S/A%tT 9X>qʱ k {JBVx~II;E !YgtպzrzRkqiD@  PwG2:T c'R< 0ω'4?^*ǎ55 ƕf2~GNa!6G\ Ƅ* qfw=W~e8F~}Ci4v)^ JY.pB/k7%;<Y=#K^tVU¢NYRKD|~D4Q)wo4(~PՎ=׾M@+Sfeh x(%_Xs8ݸ}uBFϯq D$1`GL)Ǔh|p+Ɠ=ڑcoV{nngFY-,,M$. ͙+\VX!QԂX+U[vZ *Qm|i(+r4*qSOVW/>p#n`0n묨գF5'j8͚RxX9dHɲR g $Bp NF Q7|pYyZkg! ~i$A;eSG.#.wᑇ.__=cju63*J3u&K) #Z'+w$FñܖAsQ]}ߏ2|Fa<굶1| eR̝E=I)]^eyR.ڋA_0ZTRVudG~˫$ }ўK'zůAp ~)r*az< JnC.WnooogYVfTXk[h%8w\R)7'r…'NfN AQ'O8LaQ}B[[G*3ffeK6OXP_R&njcx-~`R۹s/؏XYyeD_$ |rqUL9EKvvv"W:(EM]טmzkW͵>...HGPJ@t#fl*g.s5jتxMJTT*VΒ2ͲޠnomnP_ͽnۗp/6G$ a앗^tJ񗏾}ϟ-1VLitZM-q=EWFi +cyĆ ùØB 1q >A~ס@(T*E->mL JBl>8 kyRTa(Kb=5qè6R;8RF'2#+4֛*c_Zј[YYZY]j6G;y.b#{(9\o UEKΝ9{x9Y2:h1uQs2 3. S jJSEX J'KSR)ŹdRxrj{`3//,#f X PkA {wwБO9oq\t+d2''~'?yWO)cGO|ʵDz,sl8Kj++pvZeyB20+yIHS'vʌ EHyv1c!>4}۲,پ7t)P.8N9eDZ`aƜ 4sYkolP@A BZU܉+rTa50 Bjk(R4#Ho߾|v֨޾}+p~n#~3)IAY<)\eC)ʩg0˲Jfyl5-C@)vBIs%s؟W u]C,Kr΀Q1QT<ﻭVc08/C83a`f:},M,OQIRpXKb`.yittj**rUXcl5cL=x_oo.GPGzaHǩV+Ci7VK0͛F>>p+jA/h"V\|Rk(Nh4Ξ=e_iyΞ=]pƲ$%%O0 !Py|׉4:W&TeD&,c11 % RD`K좌 Pɟ'> <\??+(Vv/^UYb1h,--n[y AP1u"qns.7 Tй 2cl~ӜIe?_ n?.\ ܸ}ɓ_9+++R2)vz}¦~Eׯѣ;{~zGV^~qZT[oͭm+wD(eq3sΉBZ,O&cFZP)ZH)u4uclnu$Xk=PoU+8AonVYnt:o HKq}H&_ 8C!oom[+[89{.) ja =(8v鹕4bxj6O=7(K&(IFt:;aTŎ)ݭaԮ5Jjrx~A"gEe9R:2F(ƁR¦ xh H*@4ɸ`IÄ:~[<)1a^~.c*2 0I j)B-!`,00Tr9ϲdOգq: %<]1ڂ+'c؅sǣO}Ci:]]/~}c_ënܸqkw GjPJ0V-hWQZh[\+Kk(//9 (:v0dN1=ɓ'*b$٤P^i1(/su(GR8YF~?rlyy=5f DիWϞ={e/‡ŨK,..ie$;;;I2koGΫ5s-gumO_x-]PZC'Jų֌J qN 1~;zyBŖ$˲~w4M25qڢ#YYP TFr+ 1RbAgDZ/Rc-q68P~%lZB{{{i⎹뺃˯jZ{'|֭[.]ԧ> /;LΜ9Ez/tB x%TUlcEcf(x{{9W zTBs]|i^*d'e4!DHI12kQ8NZչEe\{Sѣ`oog~~lՖw<7(Ο=)HO*_\]Ph0a}룍v#I•F1jI*(s`6nE6GN γno} VAgWo6:{^~x;[ʋ,ꨧ)WQQX+KV 5R,YΔOQ.#Z%$GAq.WB7N#v x8pOvtt26Im5v^U(JFҨ5 QkAFBqL%iO]ƀHp$'\^8$jX9~UomוJ4M8ET=(Gbfܙ?T~G=W[jͭY̲(lq~tRkdq.T+)bMbi2q*d}Ꮿ\T|gO7aaa( f8Yղhm\Y !kmǣXvumsZT41ENCE3"p)#!vy{&ɫH`0v^/-\ $fёHA}vMrYsrxssI:u<+ (N(X__GQ-*=NUZn{2I//u7n\tl~q=\QOnZb\YYIx<8|!uƒo=#Z-2΅t#,R2Ό6J(eTHrJН|:*OOt Gۻ۷vww,_4ַy, y~T` x)FQA`PNm|xkVxB8Q$0Fv)q8fBVs.Ak@)J QB p?SBuRqIgXaKd{)uNqCw qa/ cl}7m&hAi[o>s 9X-ʎςoɓcAԗEeEI[aA6y{~~nW޼yƍVWWW\YYvړO> /<|}qRbS>cb};绎`@4BPgLIZu R֔XF0Ji*.\0L Bpܵ#+Phjespomn]v=p³~ۂ%¨RSJ%r0QHpr)?nAJEu\1Fcj$N`DѢ8Ngu81 R@ :1BI^[nͶW}x(OyP+D~iWrhc^Fһ21"lZH=9//^xa>칰<B4[˛7ompqP:kX3Йq?_FejhyyE?v]|tNCjៀ3|߯V(KQk3dSpGRє)1kZT^XKBOZk SAh+$*qBjm$60 Bq4%It<6H1U°l6 כay.&F 7h[h)!* \ID2){y,hЊMjaDl+[n=׮]SNUp.1rV^b/www.q-If2zPCjG>gϾ+.] (b0wTV>C?zov;z(;}1*I GnoouɧYl lsaJlZ- 0J%L hʭwY.V[1R:!DX+cjƎ=[)<Ű7N΍7^VBE㸌ݴH UL&y^=b*7W_|[{{; ?~?B0N=/~VPMTg$s)EmЕbq~~2 Y' `Y’(:8C5Z[ @iL߹zjF|7;v~/_m4F P 4Ro \0!Ӽpu}qhN(3'x!XB0PƵ$x9]L ǫTjYQ˜p=Α#kF3,ˇal yq,>[9rP< / RRJRkj黎#X%upyv]na., S8,-εZQ5+W j6:05JVkMX8R*׾#}=#6VS+Q8lnnGY]][n---;v ;8,ػ_?#85 zSr2!+8onnk_ן|2YV@I@a?;QЃ5o??8.v-]>N:㏗u-Y~!jF@MLq8"E^ڿP YB1#b,0IXF &*pKLܦ*EXiSͦ:1ѭV 5XFU $IؼY UNgww;HѸvvR\z8%\ 4EJvytu9` ;IckmTk5C pARH.v7oܸ12㜢d(R:l*5rt61UjJi!yss핕$V[o T*pj9r|I]J)`s*Uc@=(E> KOr.)#YFQR5#L0 @:2ifY哢PBB]\kZaTLSc=GVh'~28!O<뺮>J)Lav_yV!8SN16FK) > ޳~H!k^ĨZ[++kajAԙ!˒<ϋ"k6QO:/%uk`h%\G”ĕFFkm҆ %2=W}?YNK/mm&`0H e1RT.w/>}sjX{dppvtڭ{sKsdi#`Kk+cRXa VF)cL4.NqJը^Y0 7oz^rq\S (0Nb0 Bp PO~an>}E&{jtN8>F~s=+++a>Ch2R6ĭ2jYfa\|P-T5WUQ-a_[[Î]0vvvKh4v~?2Ѓ2Isέ}@+1fuuĉ/_W++#g_aC {a)*yg3֧BT}s?3?&&oooonnJ)zZwg8HLz<y:}R5FS)%Fw !PXLJ)c؇2kR9AQA xk~Q}s %ґb:5\cՇQn㇂[` @&uнvڭׇg+aSkh tPD1fQKBIs6uBs e$q\]p0EQBZkjJDRdqlTUcRI:хI *c \cp.熮C=%/\oqqu])۷w1PVx;;{q2MTk=ejU2I(ԓB|1Ji|B(UIJ)[9Y ߏzZ޾z7{^%qZ{{… mjifRJHQ;Fà]\jYv;F4r;9i2_Zl/,i]}rs"DNJSX]h } <)2hǔ^`ٸ\yȑ# tQ"鈨RDa(qi>8=@&Vu8d$Ir 'Wܴ-?~r+$չ1r6J R/U-/,,|sɟIpNG4/Hx@l!2N3B,eP.!302- 0!6&-pN#!Z جJ`9LAPJ~%RC$Bh0JﻌSBm$Qs)E<`QZzn}z%: 2ɨs/%(VAjzt} m͖?X{o_~.0p7^GC4"&_ ;z^zAШR`3˽˰5_fa|#2X\(6ҋH={^8qի؏=Q23fzˉ1ԩSv_79_㖦ԥPYl^9Ia9rCVr #9q7%xTJVQ*9iNQJ,™HN9~P'(*(%P4bjd eEwEF8:eZb-`L+C9* JN(ų)| !ˊ2D5 s*t X;qGgkiE|ωH@X2 W=J%R8H5מ:J'p8Iɰqff<҂+y$Yz \0I`Y ʥt ,7 /U$o^8N^բ#k ѹjyISPkVШ$Mo<8ܯV"^_e (V*{. #\u]Nj8Y{׋YLRf7*BZ˅]1)p[[[Cyt gHk?V:xfp[,elJ)7GRuTBwc+rX^^~WVV.\ݥĵ$K,uݼyl.{x+3,<ǁpQMn~@PVUlJ|ReZO]ddC0P L+P q@,%1C `%B0 Z"`( 8|J [%ZXJ+X q)NL9Q 5p:0!  ɨd}p:|N":QV= 47,mllܾE) TQF$P"W==Ȅ`љgӕ,JY4g0z5;;;A,--amBv4(PfafY =6筯ӟg>?"|%ex;%Lƣ`NR1;LBq 0ktܩ17;J3FQփd#XjckO`@x~?x"w0lmm(#V.,.ַxr?ggI;䓇 )Q"y\A8q/jK<~oV]P|.u9r,Uz)!@QZChsssdV2x 6~~GW_}uee"t~VePJ ;r]ޥN)eSϬެ:I]TZC1!"ѩW Uˊ;ӟA)^( k)QJN,PJ7\0ve+UjͯnO8ҭ??حW8"8yK5PhcPǕm?H's&qnͭ* - Uo6>aH $8P҉(z!hxgQT @,a94@pΕs=J3ˣE Nd|,aGΘqnY 7tE֋0( [k- w@ 1XCSr:E}T6 ׎GQ'SQ(KDI!T*lf]m\2Y2=~`{{[)Ux Խۄe7*rlٷۄVk{{{wwr"bmBV椗ey-+欶L:vثh9wvg( en>8S@`%]\^9`px[ \U4\z޻&+в #d eӞQeOy^::Mloo9s&"t).7ʳ,zޢ&t6{r1/-"7e-=;pRXڂU,e,d6ŧҜ$ Y6 77e%dQBHH)|x` \ROIX=RH'^NZ/0$ww8@`oHHx( ?#N (HD#D%s=GHP?OR'Y:YZַz{>V yoktH'斚S˸$MtG~FްbO8n]2 )̑*yso=j໓dF!h'Eo2 ֫{?QFeq)yE(A(J)1(c @)q7;$YYeBJ)=ZkBZJ_Mp; 2N(%}R[4pF=[Y 1za3~.R5>g2|Zu`% ! (!DJnUJ !Zs^ҥK[[[/_Fq<#3%@:rnc| jq?#Jl\) rTlG Kúq߿uRIeLM5jIvc#+W [h[^h\T.!q)uvV"SfE%GG/^̲ X|L1խn\㔷|Aگ?L??vؗ}c%KVu`g|h4F=a5$i5Fim2Rim-<ϻ}6X\=;}wau-.=2B]Lw})HrxT˟S&>_RO!*ɳ P'jPYZCw;csL`5GR8QǁcZ6$ϾnUTABs`X(Cs=9/[7\0 z-xNX .7jqO)A-,,eZJNYg2@7#D\?ß&2I9{ճ4F nJK]|4M;6hJ iK^Ԝڢ!#<2[KU~ ]S2X-&E&JٳQY)yOjR*/L0uՈJT6ZF8Z75#वպMqW0 n^wvѣ'3ƶQ w<,ć7wUrS-p2!H*=l:0 B-%T +ҢH.gP_a(O݃Le'OJ,b"xr2E>!D2yckaPbQ1Ix♧_qӛ/?:0)cWOO+/ׄ'$wOΝ\]_]^"W:N_p0p+R8B2 Z l~x#Q$2CZCYj),ʹ!T 5CtB0 bE+O-+ChJJ`jHpRo9uc~'ChFQtU ιB0I1ڨRBܼy^{W6za0ƢF)ϕr&1Y^p. s!m$hg#Gp0G)MSԓϪP;a lN]B꣏>z-av.Ѱ<6z*iP`)虆lOw͞x͚irn߾U@sq3YS`ڹs|?j8F,4KSkP(&&IRvM]k@UjK sZz߉BY\#b-{^ҕݲn3RJn\*{C*k̵6 UW `wj&h= ˴p;1Zq'Ă[q"&*{}ދYl.p)530tmJRJ )R)PclQ9'M;2ZS;/ L CL.Ɓ(TZcQ;g RoD$VVPdK$d C)^ N74ƓZn <8ܯV);w_{卷zwq/zcxJhB 1ZkA߀cP*_QSy\ާ~^Ƭ//`zW%2X̰?]> c}퓟$c-ip$K՗Nv,CPyaEt%XzjOFI0|:1=}qD.[ygy+ s~gWZ{@`KSʄt$kmp P΄8ƭW޹Np&)\[О4z8? |6FC%b> RJ (YѻIÑ}cX.B4K(̚6r&p ,PM+4M~5/+VvP) ű\bFZڵkOF6h[Wbj%GlCYgͧɤⴎ̥N.ϨJrĉ7o^t… F# ógo//LhK)VYoiC`)F%B[nY)%GהΕ\ &6`bg?p<`9E^diznjЖ(t^0 *Q5ZtUa5$ԜS B 0e8SP w00ƒ{%SȖb3dIZkP kLK18F TMq(RS!l7i%Ʀ @)ӭvNBW^|ۛn7Wrf5(4Lx F^ G+M)cDxTkb)RbG8)H 8eq) ӟoJqZ#knD6.DzRpu'Iht766ZѣGK тqpxCDKg"qr/Y\۝O}1W۷ow:LȲ #ggvchzqbTA=c4њ 11bA{,.;* \w\pAB)CU Ż8Ns{g wwwo|ckf.cx].KZٵ -jۤ3QJ-,LlRQ O^עjZ;r* \۹8@Ѥv6F =;W=3?Tg[@+-N YYZM'zNy#!Ph'[jtm  Q 0w< C[b9 \\^YZB ) áRƀC*B!YXZ`ȍRZN?$ ,b?8݃aAKe-(S!%1LQk X (9  +w,<ir&)k%؄Q >3 x7^}"-˵"i&24'JyVqaUn1W mdZ_嚄.TvVw7Ā\(,XIKKK7aiSTJ1*]3{zHk&IvfLKo6oc |8^\\jZlcΈ0K>Hwń*)/L%zx.~ y4Mwww?B $I,6F28e|:0ɤ+U:&;09f^Y(Ѷ0Eg:(+jZIm`0qJ-ᔓr\:xz"YPBjʹ@a3e-= J ӖQb)Sk9pPq3(x{>gk^/Vdܼvd$.lp(sٗ?qpեU4Q9&O\twxw^ugE٧>;<~1awkt4c4 Z\BS!4N8a`p\M$s9L0c 0#\r¨EbR0@0qJb &97 S}`0 F-gK8AYeT9wwҔ}3FVZ0LP F[ gSckIJFs'qV']S?S~M&VիRr3Jw,!2K)ƨpЧ s,mI'!/`1K(Tȭ\2E=)gXXͩehk ik18e48Ӫ9cFx`UQo~_{[/N?/?/>}SqAպ~~Ʃt /'>^hpIڭȭC:[oy^qc sދp։G2 t:Ņc'5h*+M TPKi`52+Rߎ*յ#G{݁ 3B9$!9#p-F(%Θ QH)2F 8[O5npTw<)bv$\8‘X7olu]E텅N~_8zd?ZhT {{{ V$f r'f֘W^xۮE76n~}=&{_k6޸!t}S3NVU^eh%Xk똴0E^#xf3uw0BXDs Gx\@s `q1SY"@Cb,৩[+}KhQoR΁24bCk!`*\ϖ  ^])J,+Z{{I3@,dŪRTVL(ff"&fZ"uDϨG*u[%T"$@2f>@bzb"Z707󺳾eߟ(8 OG0T g愈Y^~]8wݷ\U+r)g ʚA(9q4k9ʱuUA1E@ 8䝟/ & _|3-[p~֭OɄ{k׮mnnzJQ? ^\\%"wf9=(]CwީSR> V!8\,fHY/ajާ4S<]&̉sr릦ܻȿ=\1DH( sRDĄDgTf#l^[\\GZkIBk-CاYggnFg-{~Ǟ;_] h ZѐP6p|pV3Qt{*oD #]Z;SJ"VZ`PTSHIYf{w`;z8HjF}곩0Y8]5\r$Cßɟ?__OQ3w7{vQ0 A̒1uM"KOѓ٨sgypùSA^$cEeÇ}O.'O|?Ghr_o߾-|MOxʩS}`G0z=g=T)󹹹vʓ1&Ij/_CcݍFlzM}3?:q"=Ov^ZVtgu@y$]0r"0nolq(:w~7[vQPįVJ"E/:C!}3ipɞ>}z…NMM /X&jp8orOdd39w/H)l1 szcp1,u6*SUƫ7f FN1&}* ?F 9zZ<۪tB8ꓟq{\[F$Lm*0v8ҰׂA5kS]٪v/2˲w;[ۅ+ jjjVMzZkMMMKAЪ(vQQ(hbA*'[Q쬶Qt"wKWj`7$QK굸N_y??6tؙRʛ|xN<~䩩Y`4 h}"eZQ> UbZsa" QNȂ(!#( f,'B\YxH4ۍgν+Ϝ=HllQeY4`q\z"߃O$>-Ypб VD'6엀JW`_Q@q,tƕ5N; j @7R 4ʆA g$qȲ_굫UU<_{ֶ=HӴ՞8IIG3nJY,eE ^I+,? AOe?677ϼRLO>[[[㭭۷o?kJ6? 7Ikm[[[ީՎl6~W\vDF|~k뾣l~xT4'pwpp?4}x(eB#APTyg63)6\:ýao>VD&C2#@@8@"fsȌÛ>xGc'[o}RLZ!*h֛RQ :gBQT%C E&Zk` w~6;F[)f9;;{Z\(j|P WZ D ,u"وO>[[YTǎ-ٲRPZks3&/3/DHX7!_Ĺ ˡX]5_~eeo_Ο?ĉW^9455? %}qG}t}?z+Wx0?{{{^:߫moo{8y^ϗwzK`0{%heUUnۍ>ܹsjfff|}^ +++nO3N ;d e@A0 $ef(@xeџw}Xk/~Ӌ[+Fh 8  4pueJSRUUHFJ)@@8 ;tnߊf@hSLJϺݮԴdU m(eqȨdJE̍fnV? #uٹ_mnl#8DQn+@E!~{/ |8F#k~;N//͇AȎF,PA" SD,*S[YOw Q֯LǏ$D"(ʳ2  fyi$ѹq' vuk/_=qK|eUڔְCVR}m&IJ&<c ЂHH/T҃:89 @!cA`RB2Ca#[N#kL*B P !o#찲p8̲l=x{Y0 #9v:ls0#3h Dv~;&%hjjJ1ֈMMM}_]XXF1!]O&\Ǐ?y*|{MdWkFQN^ٳg9.9w߁,nesssz_{^l!'kFe~_ŕC?`0ںyfeGk}ii:9C/n8L7c""c[>tٲ J ueY(W_}/FUUFi?BuZF#:*#`_R8( %5Fk[[7?X}^g髯 Rގ@^ΪIȪ;vl Z B`JkM%F%f:ӞW v55;dnrqq>\Vtemokek;k:Ӯܬxh0555=3UStpyiѨ2ЖPlvq:]d0N›7o8qe!n..,lmmFCh!RZyFVkpA{GIHĵd0--J`D"PTK眱,IH@=_~W^vԩ8 <`a礐PEC) a!q΂`-"REd^F23cXA(2h`{*@rgJDt"AI5 skt !GP4V/_REUUO>}~f0ljLj4veU9 Vj"S8T($ Ik'b|l6333SSSam&oiZssseYzb}s0vH;s~Çϟ?z_!3gx;׿i_ y_zO$>(Onmm"z͢(k0=!~JOAcmr v1;@Udyjʯ"/MS_Gװ,KoGaǪȟ4t:I,,,g<I e_W=x."cښ'k@tcǎ `ppp5}tSNy[S_NU~" ֚Cn9` e@8ʇI-bE10iQV4:ϟ?ASfyђHB(|XƜsR@TzjjWI_&|k^NJ:z$7?Dq?}FD_$zo~7/_h4o+QfffͦLOO>.c୷ު9 j 8vT7%op߿qF 109!WgϞ}߸t?L"'Yln#\ܿ~|(*fG YUٲ2SlM5GYS~|… TLa$D` b}خ}!if#FA$IҲ |:ʵa`mmr߼aQZXW,[c5 E%2ef(HRuNDq2SAV` 82GN=Ydcg8rad*7EEoY:TlKg M+Hj6\03ʙ鹶"MQ\;HX ʂE]ExI20|Ǘg-/`٨}[?,Bol\~}ӱ ]V7v:kI5,o?1:2B'Tz 䤊1 , ˎ9?^EF%se%@0A꣧78iOM-,,LzcfvتePS#˺tlv4IZ"1FFz G*Ag~Έ@\H86Xrd [(B(*EYoRzZmme5~Dli_^||`0Ʀ6"oPi8 *3=~o*'z榯^Ϗ~m_F|4=}h\phF#;̻/^Uۇ{@]F>mffG xo4swϟ?~7Ye.333W\y_֏ &>zZ5q>IюRdAZh0FW^Wzs3kkI~?=ਖ%[WZ jד chٝ^_z򵓕ص!c"2HXp-pյ"Tj0f(("EE$"k:+)jF~ҩL#Xgfnz`CQktI-b $8YWHqy?NTłu:h$qROuWjF$#ftB:Dkpwvkcmߩ5PRg<{;MO:s{w>*DT[^^㭍_$e2y>38f?6`F 2"hmeH?~7.9m¸" ݙ$fYvE/tJpL#0;;[Ύ:aff>?R!( f`:}&n7^pA A% TA&BBçr,,\ʣKUYpC"I {(+}goղdkqWϺQV;"QiGdFԦ& 5A7ˇ33iKmFIAVz wᝏMQUY8mFyɷ32:5j3Sr'bcϔa !+]H0q"E/?YU#1u)H1[;Ϟ=#Z=Z|)!(ˌ8  zsO.S'PlavYdrlsƓ[PE;a?zuK`{ j/_ d||iHqv7W勞?R9bcpG%uyK+0e $t@ޟ`<{ԉΞ>K/_6 v˪\y>s7aa b`Հtǖo4}z4gW 6{!Jdag6 e㴵бp5B&)(UĉZW)tyUq$Z,(P cT83;|şƏWCX1;m3ZS"쮭?{?vT~zRf;;bl%3"(NF ZPa)q xhsѺ5"Kk9/MY:F1ʇ{>a6IXT @ :UAEsII,_ܞj '% XUzU@QVeYVuBJzc`g33 QK[;3m46ZJI3' AɭN'wwwFYykuE٪n}`iii=ݨ5|') @y>(3һaL=  wD%A#slI&yA( d% $;^[ߢ"NΞ>ڵki{JaEEQW TCD1[=QIYrxzZO'e$I `,A DFrD (e B,iR3՘sn-v }\FQ֘(zCzY)ܙ3/WUwww8Mt$^-:ZsDFqjL4 9iáb\]]W?</;{W/EQK/G?^$ʯx5=q?ķαa%&egtUА2WHB*)4 QK}G3D%1;@1x ѹ9~177w̙o}[ſݼ(}-?xQM}wvӮ2ږ H aU i͙pvvV9ͺe>+ZRZmq\k)ʪ4'zR2/%@ ;޾ᬌkI°;wdR*JpUZͩ֫׮J<{߻q^\(u7$,a,"7zII^a%l[xlOjD!?m5WEQhSj7U>xB)fg/\p˗. ۪*sgugUY:;1ARٿG❐+ǡ"Q4 Pk]* C$4BZtLD 3h BlCf'"pOS$Κ* y%55i|R*Pz,GyAB %Xtaɟ{{?F5ٙ T'xv _boF@p~zph=~'iEQI7Y D?죻IА?RM?#<3z/܋`JZ[ʰeTtJvlou4 $p6"BHD6$@)g-*ct2 * ;ihDҁPI<߽{oml=>~r/S=1bHjj\B4~!:GWVֲQ%H(\]/Z؅ݝ@4i Au97\V&ee4C0S !2 _^[<: Ʌ;cf~ݧO֞mKRQ՞eOG Ћ'El%pngUȖ;?~ޝK%/FsfaXQNI|'._YyKiTZU5hga3LT^/0z\r9>˿>u\vIȇ]*qf~4K! Za oyss.Ymbкҥ=4ت (*"I H I%'%1zAsNz[qƈM@  8LjZf-q/FR a]9v>2C-^tbgg~>_v{cccnn28y_ze?~ĉZ?󵵵(Y>}&km߯jnn+}땕}\,()T ͘\zx^8,I-q)VoTʊ7momVYoNt'7?gNRa6hkQVT;Q-d?#)bRk>^HlN&:/ƘOZ!xQ>yb?2p耜v;J$s~&hґ.ā~~}0Hs( ֝hxeejIt׮]}رcQϵR[G$T)IOh+Ҙ**Jc3&d0NRze~Rc"8ivDc$aly^r3*>Y o'B LC$' o-;v3zIcFo~ _;{;{Q=ydjjj4x֯ t( @{c/dCO0?? >ѣG ϟT:_^y[n?~h/?ソnG?Nce/ۏZ o'@^9 -StRiG__lS/*2Ơe! cQ|4{q=v4(!=+E [һͤ1?w͝ϟz_wÔַV3c5s`tu>zcg7=z_ןolλ,mf-N/UJ1Z٬7NY4O6VnlwFY(^`G؎V46HWQ,f0ZXx핗gkkLePAJzV tVӧBz0<(|䩩&46w1ay.H,QWEWW<~ YʹaGUVg5#tֺvZsGt6Ew`yk9s P*+kA"kID : b9Z[#3o/b?􏎄"8E)D8S@RIdtU奔43=353u҅K.8q,2+|GC$RT9 ,\UE 6T q}/f+=i$!EQ 1X!8E<)c~-p0!UBa%C&"̞LxWkmӀ_0s=ExmmE/˿oO&dyU|p3ycc#IǏx<55uٓ'Oϟ߼yɓ'NvܜE]_x;sY\r&H?Vl M1777;;['q_<w] 7/*O׊Qn\e*WTZ*Unx7o|㭅Ff+A@ )Մ}=[4_DQ[Tҡlp֌E}ۘ W\<uڳ0aa)Pq%Ϟ-׾I2zO?n16$ H*%\Ob%f;GVV?|\(e5N@3lS/'`[&q7Ww _W ߉bFb0j@xo֙r3)677}^iuӱ6[ GĎua ~W}|l~e ,E :`t ;WHRK(dvi);0: H'l7[E8Ύa3ss .}./,У>8yM11% e! UIٜ_x믾vF-FN(sI"$0L\FJRkonӂ_!XIlyfƕ0aNG2/"LL9t RY(@4>_ ":f̲l}}ɓdi˓Z-v1;;;=== =VnX[[sݻw/\|[ Wz/~4,//Z-NO k'}R.yVhgpGo+Bp8IUUQRګ+rҥ8(2O>|ꞵ0^2Xr_ L>.KklT$ [|odwos+_^<{y!&1pΌ6@S JbY7v:.\8sޠxO64fC*ꪖ(! @`A,>=_ڷǟ*: Ud:6;oA7 KbPd{;;;Z¥Q* ͶTjiinESδZ3IpwL6,fgW6ڵƠ;v;RQi KvXdrpꥴ]8KZҹL5?[ՎC@ S03_A7'wY;i>}_zf{*.2 :d ءр9DDGe|vJcY2;,C‹wg+]1hG{:HHHB s6R(;b=L΁!N73333nl{{,˹9B/Z'Iݐh8z@gE>9畞z}nn߸,yɝ;w^y3gVݗ‰F?gggݻw}ǟWL_m$>OtWzYt<Ib?J7~~K_N( 猱ny>=T?ۍ|e` :mP$4]k֍~8KSd$t:0%]]yh-p ӹ=s.+OoGYsKsA\(ITR@ &c.܌ɫ ֟olw;|ig;;,UXB[` +gƩ0vve0 7vd*ٯ+U sjAB}{sg_{lTCYV_>vٛ7>lpNtѹ Rt"7qyQlz;AGʓcfگn{qeaES6XߏH tޚnJKmN& ID a&TBk2+Ulag>Ti+_7c܋z(ڭVQ9{+rsw1se1~'pv蓼2P\D%"b͗>u- %v-8N5c2@DPRJ0{,{|~nKEv]dfgoӞޫh4v$ڝ޸q7޸|r^M_V2s|w/ռ(~^mb".7wmo-7w?dѓ/(3NyN ~? N?w5 UC"F'k>}ȬSn|,lFXaieH pe'߻'7x/\7Fz-MS"bKW6liXߘVfdIiTaiV76⹳gN?u؍?-km$Tn}euAYvfլ/-ǑW?Ӭ[ӭfa ݓZS{_>S4vG&ڣ838q:wW$Yee$ ,XO3)m{nܹ{$3W\yΟ?ߎ(p8b. |x/:w+5if=q(@>?رN88[oD "3!E@-xBKmPuɓ'"|;D< $?|OMMyo<}n_k׮yQs/|u^zm7cQ937{MzOfNF~zƍ>Ɇ9Rg㱮L΂ d4 {~}_ի/x JDIiHأ<3/Q "BV e06>'wݽ17_[3Nmz2 jbSGOn?P+W۳?O~kI$a܌rFSs5](Uiu5vkgu+_3c Ѩ,5v 0IY5F;cf333g/ϟ8qz2X,7$(՝TjT Y;Lc࠻OO-S- ֞jo<{kKl}u -jf[ff3k}׿t{wQݼ{67wfARkkQUiv˚13fkg2tHΥMi[ >o[:¹-lꓽw~Q5&D<ԃϘzd48;'eG'Gx#rNܜ/";M%D$LRD^?|…cKΟn6l0e`dfi7IPh R$AeHCDg,3+RRJ,(^|y|cD7<>"Ya?xJ?TZ1>F *')fn?L,^潿tZ֥K<>cSl޽iyO}_E8$%|Rv4[0Ga^fqZZ˽5<2GDI"|҅|;o|VU兔$q/^à^' &DIwaeQ*ʼR"غ(J0n=OW|1NA>v9 ~h¨֜[}gjn1H0VBALزHd*kiqr8t:ܿtjZk.?|gYf^Vik D&0Ldmn첦d$e\TE >;$z4ΟtR[<13O[W}@5(=__D}vF7$n9M^yJHE؜v\`@ʧ;WwbΪ.fnUgZ-NOoY!~7~3goZfUeFEjfzk_dNٹ9Mߤ\zE~\QP}?V>1YvA<3t:$,ϐCxuD Ua %P80J^WRi -1 ݥDoFEOgc$Rֺq[k=rM+7D)AGr,sFs$Ǣ(fyL9v}:iG7ZytW> <|aav1Ǘxs~g0 ٘BiSe[9fDSyUϧ I- CG't)(υNVӶ"̲a^USv҅7|K_<aS˥0^>'T|'sNSD It&OG+`bV11TIZ* a3mQlg{mS]ɏWN^|{˲'Ϥ 0(uRU(F* 0Q,2+W>{镫Op*wn?x'0~ֈ5\s1(**1ֺ* _~ً̩^zpg_ϭTЈ` %͟Ջ::a?F(ɸ(l@tb8 Al^΁@cva$TԴI* tXTCw:H"Z_]⨞$!Qjavu Q2n|q~\.޵ˣhOo ~jɐ4q?};I`𢩜M}WRBqs֒aFUΞ?} f3~ͳoG~q1e{#0 8g]ya,K򤉪2ry(tRDr0+ZZKB@Voi}}DQj=\yRKg"-9gq"|././zBRF28QxyiJQ4n.8s[}y+!;2_fT1X|]u:d^$@ZAG$ǵAUݸs{_˨L>}ŋϞ[\oTgPҧ# ֺt(d7ȝZkf4{"v[`r>ȳgrOGiex|"F䃋uD`0z]GD蜳qHlⰵ $ 3ChC eH0O:vWT^{{w}}uuu}}}}{{{oYIRK#Y:>o,k㵆sZkcIHERԄUe?mXF Ev믿ou<8^P>VB}1d@sl@( 4t5'?|tOYY/_7 DAhPH$W6Z%ցtkOt޿|wS|&.kQS B2F;ejste$s;(ʌ*.*=w<,Jde;EIleYG"(STRDB'O/+kA[3fo T#:^뀱EͥV^O-//Wy]Y䝭bnfvν'>ham\|jyr|zfQU$ $ v$N61‚SQ4"kY`m\;N7a:_A:7'Xh̓A1`8zeL;SC?f2}n]qO׻O-g͟OwHPaTUDJZ;kξ|奅9`βQ~@HVWUE'ωY4^tGCOD<`S'F|,no`MuG9%(HBAL(Hg^m:_f`BwFK$I" DrbǏ>{y K )Qch4&TJYUEƸ f<Ϝ׾/Z4I|HZDpKzĈ$*/-;lgdcgAQl(6ֳ[} 33Oy۟ {'Μ^8dEf!s%l2CAI urEY m$9$ YHyVTA UDJ h͉i: h@Oa/.?yB6ٝO#U#:[_҅ ODc5EV(q3$%Q\2e](޸~}޲L6D0,r"#Ljǎ,{?F`JSiVݿw޿ENW[ۻ[EU:4VU_+>==ɍVoݵ]]\v_Unˆ噟G0q ia>, ?\>/q^dj,;gZ+NJ72Z vG'o||gBX3SgOy˗.\^Zk7g۲,lX 35{X_'_(}[K(| [BL KLGw^˂ql|(KH٢xXR )vnc ]9m9"I1@GcEA-,~xܹ/~[`۫;{$IjIZjm *āRQ>(t|;o|8^yI:β(& >G_!" "bƓ'A]{?|_}/_Uw7T(JHg3?(vf믽vKipdcy(h!j؍Ze :P*  3]ic+2=XWJkZ8\.I8єŰߩ4A 4MM,KDe) v7(Sg'εkW_Wp(DmzhABkY^s?*r#S" .z3”A{v:sv8K4eeNg{mӕg{fGeDFs4u:" z婩ZDA0P 5M!h00/C鰵6u8"}*(G03au^ox?8uNJJCUU& *7Ɖ?{|sug~v܋뭋^^\}ѓW4a`(M&eT*lQ)?`h* +ّ v9˪ Nkm-F0 TJg i*.]m7(&\b(LES΄퓣v=b~dvؙtfG~ꂠ?R+$IF0H*\?|sR\TPUD%5gg~￿3gEk$QU"F?^{)>b0Hs[%9Ls/ͼ(̌%Ҍ 0ξxI_(_ϽDk ϕ?\a|R4rI;n\}33YCcHoX 0TBI@A΀\ BPHQDSS3gNK/.WryakڪQm2:s}JV$Pb/?j'E6΂s&O>:~x|6d7Cxi_@?$qQRTCYG|*-y>!H|[Sf UYzDRJê*Phx9613X @ݟ$ " !s$P+O>yxoWo'Y10 CDZ3h7fL߾q7cAمKWO_yɽk׮d~z: eY G~Q90Q-ԅ( ?ɏw;d%eT`KWCQX%XgL*$ [;_70 D$(\V2LSɫav՗Z8 K/]Ji]5e9;v:~ǣ@)jScDB0+r (J_=|pssW[_Y?]ol*5Uĕvc`ΨY5iS6ڵF1)Ս\@KSkϭatP$";ٷ5RJ % Ƙhc51u_G[k}&O۵5`3?{݉~z1~D"'H= Na1@ $$p333_ B:lY3SJ@H)}q9k؋X : I @DrOU~,`_U1x=$|B)+T@4 >h!BNtCB Dqɕ*," ~QY<_ ݢsp|AV!IDRW,Hk  A^5(R U qJK\8WV6v?gwo~ęo}xo*RHpHA<]mʪP˗k^G?u0tbiLmܘl]I4"DA'"l(?ɻEY F5]*DP̠- uf lDj"Țoibw81y(Bd 18]|G) 'k·[@vOn?xp3 qflLq=T- eU^N%aƣQ/ 1$!_/{f`%tg}ՑR1Ԝi@8dl]5[un++kFgm#?P$0Vf_R1-'^ZkwZVs:Bft?mi T>}1'Nh~/p,X08 8B)/^xΟ8(TUUU1YM˓4T19:;Q>D057cH㵕Fֵ$;sԕ_(ʬ;(v:QRSf+C✜(NBwģ-°51[*ɩV!P m͆bf) \b>ߨvE^JnjI=?Ucg7wY [) sc!@zispYr'%!3Bw҄?(hO0 9\8MXV,1h~ Ѷh IJB0F[IA*njza!;~3+OVkKgO947[} s\YUFdz|:oHs:>˫g&;O'qрQ)>}TDc 2{v$!2!"BIqNKDD0jȘ@*ػlx4>HZO@g, D()Ӈnݼ3=S7N^ʞŢުY2AܖU5S':XZ>1e҉NHko|tМn ˪ B!KoP F>9,Zٺ(Lc*7>e-SYViHI*t+6\#fVvkvv5yAfRyhsQr(A@Q`0,HaJC+(*Q12( M%[HJ2T΅3!!BOs2z2`41V-!eA(I0seoxQ?*4M$~qJ~DSc.]rʱaI˪pY֖'dFOD dY5,x8K 0cz$)oi1';PA - r9 D(hk ((  ADY^Y #0$aJI7 :tBH:`fDT 5T$\i;}v~ \w5a-`tlQ֎p4ua%EDJwGEqN{Ν^z5IBT,8@…7R%eΆQC2 gW֜p a?~FDEkc 4 ZZ%R8MPPUBWBz 3.7 ;+e ]5-rl$t%.J" *\YRC^dC8BS7?` JCӤ0 ]lhq]wYZ &k#Tq\@ְ軼?ҨR?+JѠgMǔ#"sF vFv0 Ceό~Z,y_O|-89/gp_AH~Fy1/H WGtJ)`9WFA qP3Wv0ƍr7byyϟ?qıťz΂!I## JD^X0XJ_,ٍ'TI1L 9FU;jb; XC}!PJ g- 1eX[9 me ;*3眶 Fs|SBD)H"`J[ 1} b|&DHj f A c`U*EPg}|gϟ}t3| `!c 7۞##6ֵZ ԥOڭ|x<;s񥷾>xƍ i4 .aܨ7jsn8̇!7|jz֭GyQhS:g@Rʫ.\`h Z[tL(uQFI&6fzبZG@EMY:afԖKCh$9"Da%!vEpb9vnd Y-[2dV "z}Ў@R頨tG*߻|Xvs|~nnqCp@sZ֢8P0vZirmgV`Py@}2N 6e1눀 2&q*tpX%YZxO!^')9,rq})ৃI@Ry E1 (+!H4N"B>dٹSO=}ر/| Q0MYiB% :DI41lЧ2;\ <`z grM|K::= J!  ` "&\2B(;f 0CvKF&cfW ΆR! k*,u+ܻɓg^zܷF[ϓ(f( REV8Mx\i7>кzl9=}̅N~ލ,e?AHUQF=5dUǗ9 pﯬi̙3=](MS)(Pss3{sa5IW)4UQm9DAeQ{:Y\6iMIaGQ;' p쁊DXmLRW^V}e" U(Xa*kGjM€6FYR ʂsankWwwjRWS ?a0G68C?0F#F8܎ys۔QEADTLE)%v(+W: Bg~XoRܹ.\pǏgZ|..!dlxe⓺hg1:+m^h\n!>S }! !A$9`gcY(a!3p@8FvTڲ,1D zRI"!9 9}&p`;RxӕB!HGO=}7k{#d2TDJ[y(Gfkihz@F.\8~hݹƭWקff6Jh4 +8 &%4uf}aQNfA9uPWD{nz" D@|fB[)plSfʲ# tUm)9 4eimBc00HA$DFE'.2*A.0N\e WBB$9–J" Id( <[]y$x~ *0d\neSSE1҄ Y!Y@Kl!E1F @J$!ѱs rsX 8Td>iB@>(( FbIDJID PJ >9qW\"?ߝ/1qBOw?\TݿJ H1tuzv -C<Oav{vvjN8|l1#BVUeR DզeD֕H$Qieֺ(ӔBz$(N+Җ ˌ̈$pEƸʎB@ '~ $ 1a=E+%#WUV1(BEBvZ+T2f#D"eFK)YǍ1FRn߽ykYaklw7\FϪʬiI qD"z$^Az 2"Hd rvsZ䨻LVto{#Ԩg(k!Ȍq9{{?ڿ_><ECE*ȂxOr4y>-ƭn|wG?z'ը~}8>F"r+ bsctztJh e┑{NN[7/FJ7YQ+lem֕p|;n;_׍"yVj5=hwnpekw#r' Z Ka/pYhQ2ngw*luXNf"6M7=%S%sAe|= 6Ō*WEǸj$JH8r{J1 5Q>N؝[, # 6E.8PyF@bnE$жzJZyBdS). si^|pT3I_/>)-)r`~4 I&wD"@,YP@# T&X&(2e8w{êomm]ܵ[nm]wm׶u:@δ)l:LgUλ">AK!YfY]`>\SOH" @(WXJΝ} #D% !) BnU@D$CBsV[M"g}{w?xǏ\濮rg>yIC8pieVMw?ZeYN{W^>{F;ۨ)uiƶ[FWVY:vhؐ)Jyp  Dj[X,ڶMy{sQz!QAn6T A:׺tx1+E^,7+W*%~7Ɵ믿nx?>|0,e'XJb0*Ve2bKs 5h + X2 QaAEVi I wQ!x^P ))JPA4 ltT I)$Q%6˴w |NgzB/CcZ.uD,"޻^ c }'SzO4oYһ&P 8pr !sL )U#R.i{YFBIv@FYux6=-އ@\ׯ_߿//ll xs7yE.,ˀNY:@)!B uK 9ed$ $=g B 1#Q1#jeA$6tGO5 j$Y2ƣч~p|{/_淿>k팃2k  +^M}Ýݍv߸uzh{ㅗ^2˲w% D c z88s{ha,ȌkUlXx'[yw:Y.7X a9KòYIl  `3m3]s" "!jjU5>ԫvAz#}w~x|t]|c}ڵ?/3ғy{{"U,=ke2DЪ i\tֹLBLA>8ULJY i4Q.v5eIqN3$y ,P }M)Q tK"<;{V!,ZkLy祵^۶ [u]׬{ةd"")s矱]?¶9~ߒ(55K!DM"*B)!g$9W«B *r{e1_MG,`{kׯ\qck{e"ZfBkwN v>0kmU,!79&bL{NP!cLFO3!DJ&9E=cI@¹) js+y)@TeYZkD%)yVmS,+]W7wÃO'?_[o6U9*3Aylˢ H[W ??}g0~{QhRV3F!T6ͺ'Ӳ6;e7zE= 6|8u]2xgyj5\-g,PZsQRpK`sɵ]@JXdQĮlnQ]/Bll@gC|o7ww.iiIŪ d콻7[|W'=>;w{eesMPwnj'vY.Xz.[xdʒFdQ " @8JWC8[*5 R[P$&)#3OAi DADk̲L33bXE6b"$m>DŽEUUڶ=n6iC. E*B9+9C@( NP03D"NT$rMSD\L!*1Cfn^VQUU ëoݺuK.CjChV8'=dI! G>P ?PAD(VFZ\w3ȂIȌq.rS"d0څcضk#K"kQaRw{띟4]_W~vчw^\`Bg9 y'=NVbS㭫ׯ-o_WƘi/%X [9>\vm6?u=k$kTcΨUUE.Dm>-@Yu ֙A1TBB @da˞ޘ zﻮ*ZiTbŖ].`co 7_z&BDtf0n㪞 ^ǦFijTz~ʷ_}NOaVh,|MuѶfԑQŨ#("È:Fmac*4X+4R M:"֧kr 1&֭`vq&Iړ>?swjBgƔ"˳ OCe~.ODTJ"|J鿢4# 8yFAvy'MX)J*Ҧ\늨Hu!(lRƪ,WDd'~7Z,&҆系~GUzxپy/tލ7]J.: G*)r9"Im~:$4Ep=M'(R@:[ ;% nk^լb>}r6=!Gaõ2 HPZkgGγdxnsg{oӷ~a7ػtekkfAӵ[; t5^A4`Z׭cK%F s\en=jѠCmYٝK+ŪWeI"~QYa{2HN'Lsg׾}lYtyz:Y->tž 1YWkC]BԴH!EC(Jf2v6Nz^UXHCܹ ZH[N (022DxhLp\D NH9Dv4Ve*G!ƘJg|9DAD"KzAYz=m۔|qP&rk/FO3sϠ.&.RȌ~g,dWJVm*" JkR b&OSCF%c"H@.d?5 ŕ+_y_|qwww8(D|>1"  $w6I 5qՅZT@`@>nJt<$"=H("޻`-i(J?zܯ-WϪtĩL69QGڸ]XrWܹ{Wѕ/\6mG^.2V(Q5<?|pXe%t`I[z8\I`DAmܶί 1;HxZ#wM2UJI!VEkm# pqy //t֋M.= c1jWe}7ikB:ǝ"V rcu57-mw@ jxOܽ`r2=9zڗ(H z#*BAAT+ccD!JX|1Zc6]Z,Lu:"Z({ZPN3j:\ 2Ƙo|b1mS75~!|XcHq *5&y_ի.\DTCF1Wͬj$g۴]QT -.u݂GὦزFD9!D$h01Vghrr4~zfsvT])s# ' D+zVg^rz}7_{B&uݔekBb6nǣMRVee8:.{};j0iA00.fjU (B1)zUӤoT<$6i MׅqTpSyzJL''GO{x>N|޶m6(rc-vF竬@CF6SdXeԑ2hCjY®*zEQE[[r\C6WJR& hڦ}Be cdkm(BDD>kT@=(Y?ݮO|HH87?%3>K:0%Q|N6* #va\g"bBI7Iǃ5c! !t'g~ۙ5077W\yyK{4],F1ȩ1bK,U$m =30M$~h}\#~6:헼(3aWKDݯzª^6Ya>퟽^jM?xf_u;GyI`5]㬪VK O=!` ;7(֖y!^߷23YQ(0όr53DZ̻;w5[\e^eT#jM>J횼*S= (@*D|@ ZU~^f.ޠ/!V_W_Ɵ_ }'4mnk{g-f>:֟dY&"H˛U1똙͋1e1c$K8Ƥ]Ka(munX6M3ͼZո_v/̩8[OOOzjޣ.yϒCEdZu]Y6,M]5!@IkeHL9+ qppp|rw\]l۶뼈(JRd1 (41kYUfHO|O Kc籃jRƿ$H系s'^ϝP:I}J~i$@#gE80\l}o{޸?ꕯK׮]ۿ70k C.!+Q" s[c)ԀQ!\=A(eں^Ϸݵk[GG_DW/WjfVn-Ib8[ᣟ ,/]nwdUwW_sm, ˒Dsdd0oXSpz j0㣃3Ds6g'՜4 ~H!FYR*D !zmg4g]].&!d2p6njr o5˽r8[,'󙶪7mՏ~,ˮ_n4]u]t !e,ͦ,]hQH$6RkCƪ,,C("E$u]t ֪֓iu1ܟ>y5֖^ػq?e:޿s>>:8lzXF{&Yڶ }i3AAYn$mdʞefZ@Z$>wꫯkɃܻjYcwKɲb0,| pNBK*q#?b+V{5QQ4i!CD5`r"]grY׿ggpRҗ(zEI)S 6]GD .BzQHe~iwŗ_x饗/ݸv wMJCcih^ BH4VLkG5fQw1BZr?g67ۗ6ng'mojFǏʢo`tn>~66||e P2h4%=z,f~>{Q{pݸvc{c 9WLO|]Q UiB伌L0DQZ`2mB1v~5WWnmln^Qd93h1jKB`spo|׺?ݻwLSK?RZkTh [ _y㫃^gbz2}ѣWyk p$+U\5  k2/j<3Vm" U_y_zy2=|RE"rqcDI(i<1 $DO4ع?w\?SD$q:0-b#v- oY_=. ~-6=j胐J>2NGN7&@S6l[[;>ݝn=w677aYfEQDBu!=E1Hytd%PV sdhe1(K'/_z!y3gYl[On/wiw/wk''?&PeSm;M&"֎j|>p\[@r9'ߍ?;;ۮ&擃{]W:畵a8QfH9N EA}Lu빛/( VDQG(D;|ݪ^ҫ~ō`܏Wm/xOw~g0yY^kj0jG{VYKZ `XiclZ"(E.軮.Hy\vMB@67նm[TCwvrZBmNS53ig}tc+ܿ}g ]YdSUoڮ#QFuADBT!txޥK[,>1I,/ +e Le2]?1!M/fO>,8aJ}@Fx`R D xZ4ߔ^E!Aua&<8=ccs˭ X1B:5RGĠ@y?.NX'oYmml^tiscml^~<Ͻrնu\ՇNɐIp!n1d6:#W~_k_ye2<:y{yjUF9IG]@"}}xvo^0 NAغ.x6I ݽA j ácL3;Ct6|1f:2R^]-#wJAo|[;nbp>tn@@*;юT =;}vHd@i*NNή߼/6ƭ΢ÇG~t`Kk[9 VjպNkm\D LgZkv.-_)Uu2%U {)D|?<,h t_J)k gY#ϥ\ BP*ў X82`紫NLu?l8SOH izv]Qy 2r!aDTD4 G"=(J+.U7?.n{?x* ^|ekk^x+Wj0V(żkjC[?Ǐ=⍿/+]h&3,{1Dٲv?|;{kWpLe|} B]E^i27od-˲̓wm[$Lk- t]3ww޷R[kM.TwwXM~?#׹FZ`>'/kWoټ<;;;>:ɝ릧Ӻ+ڵk)Ct:_0n꺆5Ѥ߄\Ӷm罵*"4кijZ+L+eZR@Fy,s@BJC]JD6psA,r;CFbz<n߸Q7U=]R7OV]HgUY6¯"!Hs2/Eis$qc+Wmd6L&buZkҔY,VJEE'>ܡ%|.t?TE1B׎h(̌RH0 i Qym2ey Bh]H:)ARyZ"@ Q"$4go$a@RkVi HE! a(@@: Z2ŀɡ|΅s˶;->y>eUUIJ?O<~ޕ[\2SG8z1?-?|7_NsetiKmUk"3-˶m6ۿ|>_.{{{63IbXJ"YV:MT/"xׯo|jM|@hB>H[#!6ݼt%R7,u,uTX̧hᝣ\WO=Ɋlsc#ۗ^{7oiYSy4ۻrUږUt:sEuejۺi{OH!Dhk5YfBY+#,mEMjE"RJ)׵o8҈6|6΍,X#GmMܶQƭZRafm5z5YT0Xzm5DkS 6}H 2xiB?~<THB,,88S\k IK$%pr\4$(QR z>EM+p6?~8WW]7|膣뷾ԶD.#ر=kccw<)7z 2tu@k[U}aBD;Y|[oOz/g_ƥkFJ"{%ʫ/Zarzv|tzF#@n6+t%1fp3t?PȄ ل2mM4M=R 'gg{kE4&"El RWES?:=MFQTyU/~MDGC'la?+ D,fn{@kjN_ZY?.jM!pCl-sr\KSKxno Ŭ#uM9S&%pt97SDD( oIjʵPHY>QZ& y-#|ogojwQoEl潼 o|1cʢt!BrhV6p8̲;Í1yp1P ,^G!B]e4;9:^K?a;9V1ƮH)1Jo7,cHie6hQ%%"EDZk]J$zՋSV:ƠPց#"1lq7b{{"k/!Lj.Z<όyn,E\ɻw?fE=C\׮ QD֨E,htڵmclfڶ1QjEZBpmwNFBZV\.QQͪa:׽ 0s׹]"EDEgXcUZkms>ᣮm77{j\u"dzz:oV4-׵7ڵQ2Zd0+]"jT  x+V?h|]lVzZۢf$Y_z N:yZV|9a~r.|Q>Z_W66ΎQJmmm.VC $` &~ /n$ED1Aj{L55ٓP>WZ .Kױզwr}89=Z.wv;!+p4lTYʢQ\t⤞u]]=Yu3=D8;~e,(Ɔ@QM'eշYC2bQE^ͷ>࣮k_~_hP@ZGDHTȨQRZ,QD!DsKK&sbۼᆱ眿ڗ_(^ܭШ uB]pXsمBTQ0˗/G!-Y8s!f!4}mJN3i4W(!֭Ƶ6)"&]um;ri ͪ~b>eg3mA_LP~pjch"Gtje(i4l3F QaqhՓ'~[7O+"/-V&"L/Bށ3g˖ܳ/s3ٮ5[lc<ee;ۛs6nVA2y .n7^ݭv;wVuSK/xP}߹ޠO .( .SyQW&co>Oyz buY 2}Jr\a]~Lz(E8E ('J7-˲כՠWVV9ZqyPVI]΅v]íƆL.ʢ*HŪbv#F 3dwR4fujAPl0O&Bӓ"A [w{4dX}/!rѤ9ffRjMsk~8UE(-MQ~ z۳vsxNMG.]ٺz˯7suFαAb􈢴9]۝LbDe!JkdX={\bs9d6IIЮIKTG$"V<7yi"0^1/JđYeQ\yY!Mwxp09=ͭĵ9<9hf#9P ]hCMn3 w]Uey^*g3[VW/WOYV679ZY-2dN?6,(=y.6zfz^t]lsqE=4?3,Gzmgk{ONz^Y(rR,{u-|ooX%9ѥK{EyOU:,B8<<fM*Kf^V`hXf`-|Z~Ƌz ` H "!ui:v?\daˆ(QLb#O&wyn{K1!rj5FQ>Fi)o;*EUeȲ?(qD&ׁѷa. O| 7oܺkQ+R4X{'RںVZl pP@#CHFZҲ3Vs$zPUHd^&Ӄ{gIZh#7~+:nk!NbGDLM1$4]|:&1Fp8(ʇ.0-r4M۶L w{2k 1:߶m\=z,pP#2Gx"Q(!fEy ]-3M6ۤ'uB$+s&m[wO;9X]xnWE_}HmYc/J=TPV+ݶz>=#ڿ9U,G"1[f`0z4pLBtb$+H\ H|uQu3i1?>Oa'l& uk45y6޺|wk׮Txާ0-@sg "gr՟JXR7 ƶy3ũHzE3k%>B5oU.:9xo@gYF'G/q㍼vǗ&x8>8> uܮwMieI JF}پ w>wztzrpo[67VB7l6+ŠmҠ].Jz &0kUm?8x`-P5k0h*Ҁ  L9ll٪@٬gTk/**=7|kGJ'庹1u"i$mBhn(Efs7o޼~*g?wXd2٤q5'jh[;$6kUL&GGuÍ-l6`jeq;ZVV `t 0rxw:6/Vur1;==ۺ߯z~T@hgSZsD:7n=_ǷǛY>ysx/ONOɪi"{_zSpx!8*Hfγ\5 @b@-0(EIxRDD}L J@zWRۆ)-XDi):]1us"h(&z1r\jMLIy.teVUUWV5ƾm}H3;WR@ *fJ[e̊Ao_s/Ұ+ͲZIAYBZ,\wnAdq<΁1p8>=0Vl6?\~>/M'bYrU|5A<3e^[dC-)*Η>uvzUU R{Ŕ^Fl:_L'y]/EW_C>ѣl~mS{=.ezzFa?ptrxUWF;d5٠Jb輆eQ!e*EҰJ::BԆ$|!ܼq>y49ǫM,M׬uAF 4BugR Yb!:*ܥ.%K)۵u H*%H͗^zUaOs 2D1F}4]J@MabM'ZHSÝ7%0i}x?:9z~;ӳ^^xܬ("+L^Κbwآ*ƨuYM1PeH%!yukmM67$3BeYeyڕd>ͳtz๑,˴y%ᦔ(btC۬s2cZ$DC_/":ضr޴k" -(!V\>GmN3ҔgE11]'x!7 H)繈Ĉ"F}~oQJծHU:!bL(c b$P2#F9gS2J%zs**sg @LͭөZd,*o$v'wnt٬i>믿nlλϏ'bjڍۇee=?qUfۋB+sK|׮_ G}K_W~g1_YZ+"^<8>ZD|`IKD=~NkM]k>C,10;VMKA( ‘'r8xma\mS j׵mY(! [0FSRkB .'\C/Zk)mǵsJ)qM*df*2M*%D )"D[s9,LbQ%NP6i& )OZ{?QfGm4*ˬ,휋c yV֚Qig)ޏfMӹ7_|tzx{ϖW^}w'b~iݷ7/m泇7?A otO>{{^[mm=Z=z@LJd.!ݕRD8*$DA=kc39,+x06ȹ "z(O*Y13 tq;@[k$yufLYa,9I~RlM^f=[5M{|[[=UmY,LfȲXm"C+!CeMտe&bW0 {]sLbB ֡D̐Z[ZT_jx|pwi QbtqnJcLe>"K""!.$YMfsd U/SJBh.s x<."ݹ1ƛ7o.߿\.!RR9$eO,H&mcU/58L&'ԥݝl6NEuaZ*geX7v4], d=y/w"10~<3!a F>PVBʕ#B1F BR zF{#h֚/pFCP *RkmLiK;5P \W qs~? GY7W(}?=ˀ__9~l)Woo޸vߺKmo(v#Kw!HuUUM`Sk}i׵ ,BY(P$7y24Hnm`]\@.)j 35Ɏn=OO2*} A{%D$”@|QkBDZKmHC,z`4 FDhUsv*{=ѝ dYW-lnzj7_6 ;2D~bT H :' /lFԐc l)|Ct"q9O>*zW_@Ѓ^O)oU!eY2DfrŴsmݬP$BD瑈!h2 ]Hwe%% ㍍dr|r7apЯFDeE_{aFZ)Dݹ.46p%߹yv69xX!GC߹'u릳t:.5༯f5.$埳(o>E6LBB*>1Q/ {͝s{{{{8]4 VSڮ&&q 8HBxf:g-moc>Ƨɀk7(J%C(1F=>z2B4ыTbATYu.z;G7OrsTްq"lOOfGW͍EgM|^<99Y NӼWzYm۶-"/sh9mH)%ZEa`׼4g @ ffIQ}?BL(0⧒R'LbL&9JdpBW{RHZ Cc#cʔ2[(C+c-Є9]VHPcoohc0mahg]7ru3"E@$("!hm 7G`rQ^YQVY 6+.˲( !%6  Isd$`%iĴ!d*%Q,bn,ҴrJȬ͋b\4˕PYj׭iBÄMS5bM}ރ`tɩ\rݾd2އr9LRQ!.UӺUӹSU 1g}|EE9^/ou+%LVEje9<: '3!TwJNcoR(@u,_K I80s1}))' ^SԷ=BCu~Umn [׹eB^$ǓiUZֶo\[Szn]W^zjN]7w南_۟h4gk|ɿo7kv9~ooF|[>^m|o0Fm[:נs95$L MOoX8m IkH 8id!J"$[c<0*QߒQ"߀7E$덐"MD 8qFAH)]֖sۦmYN?|,Fxcʕ+Ҷ aX$!Wê6MN*CyJr۫Vun|tH85 ޅH@J@:Tm}Q%16˲4m2201 *BE:.KpiCc11.˲6]l:WO?L?(mWU9(W/B$c6~jyYXk]QtDc )PR1D8uء$:S1\1 2׼Ѣ'I{ш4"LP^ƠlF=c+lWv6Ia\ Pi}-FcȈpں1S?1.:!̚7og~||z'V9cMX-E`<OmQ^?< NO1Q+r3_LR(BZkvHFc$]jC5[Vk2$̜NO`aaMJ2B>ic)WbtPJ( iKm@!#Ȍ*eIU-((Φs|HYMJb"( R&=ʚpPp {EEґe`mM56F;fcdY=|݃?=c7K1Ht\$pO.&lJ5ܻtiQ%1&X.c h?`Tc,!2K iO+RPEA=1&Ƶ,gZ%`:7{F I!V"'Ja^B\LgggeYm︺}Y6U=_!MU?>/]M+"zk?ۉASg"B]%R@m}<9;kV ˗߿j: Etە"1%jMi !蝏!*q-Y{5~` Rrg"~A .!Qg|#mw%Qbb]+S(.*/e6FM]Yվ[ @*mlV>vYg&/R[?_|ժ*ML=ȫrwz{ks({ch$߿swM!2B4S  DįxD5YU^{eʊ|kwK2ZsBHP1" {f՗Gް!b]GSnWu߬j٬ꮭ"(ui5͋7F*v5L^Kc\AiKDg %49x1~9D3iZp$.φQ@!Eatoz:xr0ǃr8|p> `IF[676_]4Cq),Q#&[Tꜛ/z5 d(eu)VbHMzuynr Ҫ26:uXsK]CQVk.2+aB>\4\QIk RjR`/R>eDPA JL!TDJ8M (chZQfDo/K .,EjX1ޣ 7I5eYUJ7roW.\rm\V*ˌ.tzrRǛg̬ڇWeAku̽^(P@OYeXKY$u3Rˆ"Lgzxz~M ֔Y$*i)u19%ŐҨ K-I4Pa`@dШc~ 3Tf\W5FcUUVUUUVT`467y1qI60 *d~)E9 ʫ7%h:Z6UӺnY7M7fsx(n+{xccc17QUUy'޻̊4E妤Bu.(YN.- G\Yמ ESOON`ss37b|o;G 5uNx\H畢f1,$9ze {z2zX2Į5^oX=~kkM׆tA/ϲ^ۅYu%K@n48P")M=W.㭟ÑgLkT\/_SC^&4pjϙQtQ$BDho eBV)}/*y$%d  Qlg'˺f#!jmFch0^SÃիE,2"of:^rGF~\0Xk"\fI[YtZ1sp!BX38bE4JƳλQ(z!˚\'OiMZTǵ /-XozS&@bPp*:EPp̊AJPP!!J-* E 7mp'"!Ey^&SYfѠdYVyr2;,Y"xv|LQ^FIdfE&AnE/Bm3]U3wȵ2U[*IQcbUkZC}y i+Y `uE1-MCy׶;;6g'?^.z`FR!ֹ;MӴKߤkAJgfNZu΅إHk}&It%͸RZ+2ZٮGGGwU&CktOR"B@Z)t2ܿ:y`ҎxYF18U31,LCalJOGb:["Q&~J=; Nǥ:ħ>_4eb\`@-)ŀ( Dkc0;B<cNaT$`ږ2E`E[AV{[C;7O/;ϑvvױ7?7/DU=SJ!WX^ "ks{ODh%i+5TZᠵm[ ! ;f4TR Bh@a#)粳L$.f&.7 E@%09SnT2}F`.)#m^x J<1ADܪBzB`%"( &E`8t+e%A)a_'Y-1*m*Yc)D :7^f\!gynmidVoP4^o ͥ"̮ ]ۺ.NNzGOۨT,x^W*ibW7iC|q^ TJ39r"*Z{n>|x||4MZ/QeFi(sMb Fa *"kb5q)SݴRB̌$XX$v]J۶]ׁ`Y z(e>yrx|zUU- kjYw] R"#xv7u>̗S痶ƣ^uv:[ xq.iօ &7"6${y͛7S9 ӨDunwS/z=>;9=Y!fEgSG1U0O]\|t7߿e?ƬY[NN1N9"X9$(gEP(9 03 {G)齮|:@byHIc׹My`Nmw/]V5WW/_rx\p{5SCC4p KCcFO$bb1pH)9wb ) b.8Q b`0'UBq㷿_~'U6E<^zU}kǻ]]WU f`}]~MfK5z !sX(ZonnlU]3OuUMy_jv1S&bpQAf,LS):6#YaD 1  )"B*<&`6flL4T(+JFy8AGNCb}D s|5tdbɈT;#,.`Jْ&khgx`0Dc03`+ sw9_`f GbRn+fF4_Rhxv\5 yiw)[%d|}]yyu>==;>>e欚bXz{tZ˻Yx@v/^_F"IMd ppݮa=)/Й*379CSSMaJJ T׆ETM9\m6ru۶9IB|_]]nw؛"iSF瀩SFe&#ڍngw3|ØMGv8 eE09в!uRV( £D@AC@),@0Ԉ Ηڽۜ33Ź)-F`4(i=|%PgdO%$@2e4=sI {PTJgDb\#SMd<#ɢ9;^cA9d&Iq1۱լffxX¹PUjjKbl7oGzc'bI@|U)nRJYS)eГ6 vѭ3rJb#*99~DR)usΐi2t>0Ulg^Fmq9D%own#i\ۘBA׭W*B > 9\2jqݮOB૛1$<9/_3\; ݰE) "ڬ,)"r^˳Z< c c"$J-{ġH)fO_|6LqFu9*GtU4gC*I2(Y8\;5Ȣ|yÈ{4j}D4Eډ$A =x}OgCE-ڦi,ͬ>TgT$FB;$`Z׫#is ~JzfeDT5 nvTQ[>0*oPUiot~7;Xm]=:>mQ/=y*~GϞ>կO?>?;Ґн"0;% JsTtLUMÖ@f @T i ~:: Ǹ\eL)3jʔs9* +%sOBC@T(<7&  du1(CFä|'=Y􃋕k㷂f&sN[JfvIC篯(bAH޹p$KĻ:TeYInyv&m1|w4 $eM68oNY0bd %Lt?z`ͿN.C8oT>B*'C̐P"4PBD$٤|~C, A]WѡCc@"p.x5RT0jZV.b νQUUb:iJY$M)n]~~~}JRլ 8&/dYxA|ӷn>فafHJ 0S!jm?{//nnnΞE,\"2NcuȀFO!4m{^CNf!xލ'ub\Dwz.~7!HʲnRJWWm2?o=.#F)}n!Zj5+`FS08㦭1Sd4]?/=:7oU&f1/+U,p}e랽j*z|zrz|lj#CÜr)O9TO.Ɯ-Yd5&LSEӢa1-/A3Uc4D( i9aJ("Tȫ??\Eͮ3BcbJ5?R1T]9bV#m#DEӴmUUFs G*UUWWf2pa!(v(j"Y἟"fz_l31<NC]|/x LP7NGq:= !*G5 x Af?@*̊3A6@5UP2Yd<zb‰ &>QlRQѯ׶u] $M:m.EG%9\ŸaEkw[8MtCD<:7CkdZX{r??󨯮_bxqۛ陁TX(iFD`Ί_.r_qVǧO(QXDnA0bRpC??]-'GGm $d9%e0y Ә~\.f$v)VOCf ;PY,ѾR'*lVyWo|J]Qh E?@?lf~s'Gڗo(~BU=ekr9R-3C.,$PU #;5V3Բ/`B *fT{Rf'bD+U 2eQRID,) Bؓ Ԁ2u%2Φ!iL5Bp-F0vvZY1 1Sc1),ND]^PfE%gfE`3L{{_5)}//on6ol=>9-Hs3bɦn[9gY9THXsbƿ7o}pUO?CPT0B3jBwʟ:MysE+ 3VMQ@Ӥy<9>?h6Si1;BfU43MTm`NWI~ _DMJt7nR:=YwL%1I1bdfO/^ߏ۾n!|{vVCYׄTdڻnj #(9!y[ ߿~ƪmqR)=hZ$~x[V3Ǫi)=dnr[.4 {$CLj ۻ`V}30U`}6*Te>6ar})šos&r!#|@f ]Ɩxw&9GKTa@߻*"A+QH4â(:, .q%;ǎ-땬$ԣCˀ;@f ɰ )4a}yT4\4 b \~t ,D,J w礵~|~l@D?=z3"gI~jzB{<'|%e! 5I'HzzIr %H|SүgA?9H)HցhrX R Ё 6SZ!/>!1g--y]̞_e&De]-4Wd[z-37 rJ)pBJ:A yBP@AY6*ʠ#P- t݄z 4( ê.]a?8 ^L8 U ߆`1 @ ECiLPv(wT *6Q%*TՉ^>h*6A;}h6zz3]>nBw Fcq011/Jt%O$JJOʆ)_S~X~bŧ?RU UBT6U2*P=zUMWژ:UYWKݕL/w5T4|4DG44445s45jjkkk/Ү~CбIٯө3]YwTOQWDNJ A!lhm`Xnx6141󍫌L&&&&4Sf7KD/ٽsw3kdjr9, --)^[,[,ZYqY=Z/nnFh`3fmc[a;`'odWhwcfg498&99.[]ZtIӉtILwq>,vpaT%&<_X6cmH'sXoݸOX JmIG;"}tO#֟ɐg`adžL̟777gidmfhs-Z[r g{gJܚ"fC}T0o`?~a[f%_ م~2q;lxlqfОe{o;%V%Eť-:,ܭBbGACC _?8}J(h՝?\[\SPxZ:z^T?vb'[L4 NSS/3vgꜭ8G=5mhoNhD=zW_]иP~Qb%Kӗ3/O ^_2ԾWwwt]vo8ݸp[vonc}oֿjk{۾giϥ^+<]ϼ/?΃ч>J48 IS%TUn{F|qc珇CHp ʋQ c^c/W~%x5:O?+9;LJ N+|A>|gϝ_"LZ[wOS,!kv@!(YfYs3p EE"ĩȫ a [ZJgi1EjFFȌ3TG}g 0tE6wZ pHYs   IDATxyW}5;^ْ,d!C$BMF`t $1 //18a2S0cm,۲Yӹgc}t' 6iҹTթSRJ99'JsHs8'd99' 9$qN&9srN69@sI✜MrlG}?J9p|gQ]!OxV؇42;W |=nrh߾}'?x'XP8p#S+x}׼o<'ߟWƾ} ',fcOJJź]w}|ݿ]ϸ@5ݶ~@矺ޞc\sM_;@~}[7w2|lP%A=2@%,P9H7Bw  1^]7v, zQ d$j&HQ < t34P>B X@ŀxݙ>'>Ɯ" u3l2{jgՈ1Roʋ_.Y@lޫ)?q.. Np 2,rVk(TP T_3)6s>fD3R{o%;OP7:]fYcSah6OI۫tYYyL֗O4991J$)H8}&P#x(E(_aAyp҆](=/(0gۀ(F(P94d%ah8y/OɕWO?q,Ǭ<q}kHyk\O fU2 S0ER P L(j JA3 p`ՇH91 p唷P>9QnΙT[> $H3Qyt[ȱ(]z~+n~l~U*s(-)@=Vv]oaAAH=7LSkPJn1D5 uRgR&  "CD5(a@Qџ%`XCA9 DDY}tNSO'Sw8ow_L?Gפ̡i>_WQa:ӛ5i4ghs9[mxPlIu0ӟr#)<1 GF CYcbeP6?kFL㡔\ 6BxfVݴMmNo 0(fiĀ2ki}$)SV/q8{uRO `¥ǿ6Sxcm_1$F)qA JQzۂRH(@, N6L(@o>le}}Jd $̩>Z8B1̏p08ܟ&BMiTE_ӟ=A2 :ҍ G[16rd^Hƻ92U &B #k33b 8l#"@hn2w x){ΩẄ<&.7^p_|W?6 [/zʒ⢃_5No2PE) n VDXCPӾI2ZPFF(N 鰮-"\2T_ٌSn|5n+234mTb*Ԩ2&aI!B3 0b]D]g{~z*@8<yg.ߩ -xVHJEqj4(%vkq~_ |fT 8d$\ LDHLE ()*`:mA˜PU}(UO$De>d۩2A-LbS'%'&'fv jjp? %D~/l=Gj&QlB1e(@oh" 9k$+g~P섗 fzј&6Tȩ"%#ã !ty%E4[W銗 *~?%kwlvn'a{;#óݗ?ٲ:_גm "E6 fletkSywFS!9*T\h4&`jym2y/oL՗KAGĄX&O2viΞ&ZXĝi >GY9&zN>h)W9.w<xG^?~YO_9s.hv۾ (ˆQAe,䆒PQmlb/ṗcs7o[gBrHfa݂B{j6<'\׺ba|å2  _JPZa%R5F*0`H 6Fz FJ 1tG *⹐4 q3Ds!-}wқaw,܉goDh$#9]V}c*yow)nnU.pae\K^/я>,rYz ad*"s0[*;C8Hyݟ]߼6x M¢(! X.ݥoeK`\n(%7qX)ฮGN s 2rbV7Q9)!vrg 1CIfw()j,P \3&b>[T$!>=7pzK++xIS@6 # ɉw߾gaP"@8<.ċ UF%:@<&a k6ɭsv1:% P)x*ZwCn+3G\CM:U_,!}̹zgb87PyiQʬ:%Gdx(@چp4q*rjp:"h/C~%$3 ]EnNY)L' !v8' nl{/_!YG >wQ2@UlbĦ=fa>Ð \HT&MƒNf+|B_V}` }w3)1pSiU&( 3'@)I}׿Ar c2 %8|>m 02=}9}X)'¢V 3.w-{b9681w19t {Ug*)7"8ATjOv)^ťm7ΝۻYҽ?œ%y_{tn8N)[BJ6ZT#'@6#E HKC]_@X̌gq͈n34.B+Sv3&ɂwq`Mђ)D2$+B'r )MZi+Pcz V M)zO^5dvXeXnFHFnbG0EyX\,odG7k9y6u\ .~HfMAJ@AFv-i%66 l̠pyL[W62}Sd&pxjl(P0+Q ??'bzUvMb`m.yf XJխ߳1R%Z,R#%$'DhU2=4-nVY^s7g?k27ݯՇD<"Lf6Æ2b!M q|Q3GفS78.Sǝ=kC<ݥ^!:̞^e 'ӣ<\ǰѪ B3SͨZg(GJNN#sjZbur,ڠ݂SH=ضOx׫OCP,?r@!/yp8d ijtM^-jDͣ lzGͮPL-'*3ATqSL,OrrJhL@juɵI`jP#5-07w6 IUA+&+9D%L7+@؆q[' A~ .; |!QW!Nvq&eڰ{Aj qdEރ*594ϐØBG,2|llS= 51E vd '?ͻ?o?><ˏoz.mˍ)by2x41C[V3=; [x"K fv!xGi7,G-KV4H>DxD#s&P&)0qZzSƌ @=}(rߥԦ` >, DjyiC6ɥC5)F C갊TE)y'/=@ku +o2^YGVg 9P#Ci1gJ$5@Z'TfCOΒnᆵy]7=ʏ<]-v {!!)(4a6E(09Ic=3\oBs(fb8gq #C#UeGyCi'C0i2VP&L>t |!pRnmCĤc% |uS@ y pyt_^{ 0IYdxQ!T811#w:Y͇&śF+2fDpN/قdDp1K hDa'H*9Gnb=Upuⳟ+^wvMŏ/tU 柶3Of*E\qw!{u>vN4z# VrQ#}z R"AW6 ITjj3N1on\ ͽIPiL; J|KV- IDATUN8faz @m5GSbhY`&B616C Tu%A iAndk> {,]"y,jB CrX,1BȘjYi:a{d0Z'оZ?ݻܫ@Y: ~rϞ_垼=MP㬁E5k {ʙ܈!YZPA$"ċ )<4Fadjv5pm`im9o:R@o}XW"2 $–PͿ~ZVhV c@T8;rOb wQ& I +F`eLYu QupӑйM8dX졔޴i1#(375ue6&5F N @f>Xc(HmHĚDM"1$貂B8|{H8B:e5Bb!Xg]$!VA`l3"$M-9>VR~xAܵC?<o!*?T@G,4rp'(c@e Zҷ֘MQY'~ ȇ&6m)O=,ѣugL^E<>R IMa(E55ڛޭDPfURcC:pFܶK o) 9c VY#H1dDEJQRIhGߵ9c\LbMጷ0!l\ ݂!o܉w4튵1[_򑳰fYBvϞPUb.pgON^cSEpd|6 ΀q*+b d@65d.S.NN@0 $%nE-;RoF= %ȄTMc2Y@`U׹ZSj̻J`IH5>"J}]c ? )UP_dIH,Հ!<#[W6_,85&^fcbǼj gV`!h*r6c3P4 (MbI͑,d'Ok4jG|N0k#5/e>}+F 8`zӞLlhe:âC)l|"bxT UemQcǜj ?HBR{1`!)Ǒz]lcJWҥT5&Gm4 25V %jD_[|8\o:m hX*T*$ rM!.Կ +K0[t#\|Cb2 +Pўg(m<AB-0E @hpC,:aJ\ )X}Ѓ@dpH:B<C=E9+^|!0:0)A.`2*Gb"`wV7ѧҔ#/r*s 랩1 ]̺f Sj\)KzLz-abra7ŸYɫ2iJul8$:jQ%9s2#cK3He[^q!wú5m"5ԄV hr:DRT/Q#!49j/[07ZmeABZ+lY:B!S  wJJLBPeFF| 8vn%FYTpt}D>6K,c錏CHW#FKUBl3kAMy"]v` :)FV} "p*x s*HlrڦEJL@OAvʱZ666.2|R$UԨgC I74Mk/OSZc:* =NKh !W塎+!MaB] q=]E=魰! 5 Q*_H q(e1dQrTMyF6)-C TJoX@ьMIAIZ* #?ޖL {eOuH h& Cnm,ٗo(m*LTH~G $/+QNEses6J%HL49y0" .u\uO#T]J)bgD (2 g, AFRTI*ʱN>!)n~mHʡ3 q}pvmqFʍnٳ7D&2a"3(Qleg>ۢM1L1OAJ=e.ҭP Ѱ]:O_s5|~[b [87b"Urshf2E5tjC3A!Iqh`1AI謳.>7ır<˥Y `WbF1͛-) msŸ|GzN B#"&S#褧`%ǀܺDL]vR76L#ۈ6DÌdͣk=n+-5}QEIauL()&\ɥ$sBDdDIu1ƌ`MYmYi‹^!|EU LH"U(Q͡bV1?ԌXtٸ16crz, 8Jn/ӯSF=FC3t݄eёMfESG r–mT~haEJƮѳWH<\CȀ2$=]$c|$sYPͬw7O)sDtjBl3pP\3&I1f^Sr86 64V1}K(*5s6׹9(`:p%1C"l )NR2t>= /a5Rݫ^狈< -+ sL0!K@u/ˑ06G0&å*9G7.|?w|s*UTRUH`\ mJtuxmYKH7 ”Ĥn"r&5}ӧ1Vfu΀0a9t$` ͦ`NNs2wFVD+w&Ǡ`_j7M) Xq8jnk;X:'?|+`.6#x\L@ àػϠJmJ^NUk 5"KL( E>+ѕٺI'HHe @<TB=4'0Gvs:)jdǝv>t2m~ {(jJdhz2qz]J#%ϟG+hyާ{@ک6EI*%8&'@ ,TO.SB4<,"6&  ^҃(%dK:#DglZd]X6Fu5]DQE3aD!Uͺ DĕpP9w@GI: Q8 r07+Pֺs-hBc( c2acSq^̈́ \֧"ӟX ;4 nGQ2#DMSS!l\ys쐦(*R_0B42`'R+> y`Ɔ.TJ] .r:Q`)Q.ci'?b3HX[FcmADŽ^$BTR6.DĸqWgɬ]Go&T0r5^'w\(, K MBWj䒻 +rbcfwe'%ٽiXePzq"E/þ:ц(ym5o`uEr"\ߢCA + IDATJtAav )Yۛn¦J !xfp1<.4#g}4f Dj9 Qۏ#_ VXd۶ _8R|$AAqo{0&%aJ S'G0AХ:12OH`MQs6UB$>{B,Q,bsN\8.%n.x׉뮻w'?rJtn$0ѶjC6ɃK*0[w@"$={0؄\lp*kR4ց,7]pPz]x0x\JY k,.˅!w VWt 9hh0{'^^1] zB1]sB4! `1Q-`#+8JJ1R ^J#3HRS󍐓C,Gg!<0>RSEKL9_` V2\GN,Z6Dd94_'STz5]ꀨ+&QpҡΕvwΑK9) XC,72ん4-1ֺ (q9&|Wit/C>_cǦ1#e>ٵKu%.`׌vX/ Hp C p1xp` kC83}}20C~+B (8pzI" hwaI4׽,{0 SG'gE_l1sa%ֺ!Y'qO7ծmQ|0h_x >+r @[!7wxJ 8dA]þc[Uk_o('F6L(~]$mjL%!K/!iaVvXt ɒm i^mkDq='GL-&Ϥ )uL?f+ BvH)Wd[JYԠOY?AkH\u@(_g4T6(1zJ70,+`W [%|Tt4An}}upRS4!A: +ǰ{?Gx)?ity *PT`EXgESfeI@؅0_O ;a𑆲@{ПI[MM+PԲ[u|s{o1``8 _o) `7493'ȕ Dɮ}Ic:iG1s@1e& EAc0/I,#ȶŢԆ;aL'^&ׁ?>ၷ˓*"> 2֡ZFj2ߓBBeyzrp&Hd8MoERrybx.$JU0&O,i0(BF0wXfH~H߆"&=\tX `/T݁@1(1uo' 󬮫 vG-VqhTBEKLOM!|8{%Pn޾=[,;ۂv[( xyV7l](066=l^ n  05 O ϫg r>%{Pm&`Ǡ"{r~heVI^KfW?/ oFi#-5Cns!y/J r*(ZqdLb'Ev[n0 !֓θޔ`'%<ƈ'p1;YG[%2xٟ'yլ l fEMʉ|& "[1y "m]xtO%A#6D]Uo,NY3SD0*iT*c  VʢP4]FP/Hj?j/=.F ̅!" +uv! UXj0NF]}t/8s D?^_U@|?_$qӥ  v q ˯ڂr'+_s nA p  Pmp@N1–>rɣ)`%f22K Y!y&Q>1=Z$#t( ^*#q;(tuF4_ Bx LMg:'7[&,4!_ɷ+PHbhZ@:"oKdLQGz{ &I#ARdBMM =>Je8a4@ 56sޛlA& rkvL{v!+EwrUĄeCJ 'ᶘ^yeIaFC}5I}0[[й$7O#,[yhi.lr*S' 1T.1yBY}'m{ ughx3(j+]$ 3hXph_/=` p`iev6"ȱ7Cρmn@\7pl ̷Ek#(%'cii8=4\Yp 'JYPCuqzKxAXσHm2{^Ayx_{.7!-cڗבhiF-h^&idz $߮719=J^HJ84ZnO!w3QN:Y:Al [""KaP.u7<"Pې,X{ ,"X kUa 8߇_@ G?vuNᳱ3ZI7QF||TȡMu,3i0|8$\I4xZ<2L]#7c#*ǫ ,l;_{oʯ" :DQ<ŋM}ҟ`pe*D.BlIE}1TD842ؑ}N\Nj&E wy=pc:eŰ7ion^y[cdY?bZM -_?}_&q0['a(*K:ʠ=3<d!SN~j›sxK}9y$qt]Lǐ+-n $m@%l; 0v+ic@qF7F bvz晟1&IAmRlUd9'9װ29 MqbC6BŐkȜkHkJG ;5¶m7H+)S(̩Mԁ̀ӧ!jhFUy !<#f%HHe<ϯ_/l}R](Bjbr%јܹƧNJ[|72YDEJT&VꄃRJed72B #&OH'VW :%4ű[[Rf@ιDR&%/3)Šfg8NL0Xb$U`2w/Bo`"V X҂_},0ZKP;_-p+H ;Ek:Z7J.r p.ï®_qqE a_({#8Yl o+B0hd+`eq0SFc,N@`*:x0b(|fS4M6bjYM;/%eWF%#7P =z3c§k487MSM%l]W/3SZ$g:7&O dWoTmt@͂UK(hۂ8q\._ ڰAuF0C!Ahro$qx)xPjxw؁uD\1^9CO&eDWKK\)Pgtg|iy&|R}_]؂u؜zhKfkBy 6GgD! vdd$ׯ[P%o; "XXvaCWYhJcHx %FbEfV\R@Iv%kRѝ t )d`V5vMcmTq7%1̘򟘞D]ʟ}vYD *e΄Q`a#K3]dMM/i{¯q'9Vt[P=*䟄ӎLӉG$a( ⴆ0|k^dq4": gXYE&cɎѥ56:X{VgE++ ]^@gw vi)e(n ɯu{ A$DXscg9:ik-y.dVWۃs*{ DfoyҨs |c> !?6y涅?"-HE%d A:)f߹=m»1㒯t&>.^gQۘW S_۸w5$g`-THPD =g؜BۅO34ag1֑rnyWe,e`xG7!@ԇ8}*D#tpa"m~m7H-K͉lC W9dEj ]9aL Sw!,%ä>ibwi joUﺆ` /B9l\Tn? hc$4r,edݿي0ksnv;&$.+YxaxÒ׿mq(2̅qD /.OF HOG24)xƤ0Y 6 \Nl#r8p$2*Uog>(|~").=],z(|\?MkLffpt y4%4ԧM(9©;N .e Č8TRݽlhC^'}q5ߡ@{ʋ7#\kHz?6AҌ#) Dv:W4~_ż@I~e%JsL شqpQVA̍%hd,]:2%cX lÓ`+{#GP]$w#D.A0 ≫}]1āl dJܓ+IDAT]5593"2 "`\~q(R *TQ$OGMH .dmXfFf cf" 'ff$HB""2XX(J&D*R`W4߯RYuoM߽4ID p.#\Ī(_:B(y裃F}*G  Y(#EkmڊZsCB=GQ9RconnM?Z;A9\8p0b:%2(DRb2@IV# n4\KQ#LZP 5`TzV`2<̖aц]Y8ex@ß΢WEfQ8(z_w\s.|"i4ja<vW׻IQ1 b}ߵ{ d2E;N zO}]z{|>47ى8 ,;Fn'M9dTbɃ0 腍$RP|lP;ǏU(ҧDqr{(U#UȀ"rX [}{^7&X4'r1#q12lY<@_QLVeFgkM.g:Ϟ n`Y|ON 1\JnҡP#F.t*p2읇p' -L0@ -nBAQ cED;㌟K:{rg5N32N)4LAnzKi"ؑ\8 *ddLJX6yͅa64S_:СC~SoYAf2#X`|UC4 ̊-c%iKD$䡬NK,ۂIճBv"sC}V#]ehRK.Jfve1d#qb޹9(2D 0bH&b̡3fxDl4yۙu~iާCdi>/%`MLv0,a nil65p7PIZx!y}XX`Z?{ ھH4e~p3cfxbO]`0iń3 EqǤ^]RĪfbHleȷj6|ǮeB.(SG<{`fMS<7V$hR'd-^l|p'Kjڜи-mGUA] WLN6xz7;q) y}4 H5RD:alɽ%Y `HLY0KMRF}79 { ߽_X{8JF܃U <;n õNQiIɲ$ԴB`9h4 ~"yhƔp11D#B0"lj$G g2q&:{_lQxE(%=u24qh <. LP`N*0_VzMuG] *bV-@Cn<-@˩D)z*ĄlMfF[(E~ؤv…˸^DpTcGX,7.<[{DZ4^_e #L1c xE,l/M&1]bPēWu{yx O¨kJ˞ҥxò9KWM`#qqqn+ozt{@(?VҢOŐhNu("^&=ax~H'׳8?b !hj%DSMAg.zj9 h!~ ^Jn3H26^D1H6 +ò0)VMtar%b\ w} MQY1`fA}/0),1r-LKc8@%ŽYAf8xL~ׁͯq.O+\'c&>yCL[ +-K8As"7^߃!V&]JRJ7FY˅NtDzխJi1$De,&¹,Bn!Bd%_IΐâE|ΐcLeg(;F|2Nmb"lr: i#|FxֳXIC7 +ǃ43 Y!s>!vkVԲq[A'8FL%*A!s䱨29$G5xra=06 k0X",īVcy<jdp8NZixȗ-z'5Ԓi|;}Pc-?q37t*yl:M&l2a39L~״(<!]XeU:ƷpG & t __9\~. n q@PKضdhQI571Hn 8Gi2Za16d8@^vAU&|;E'Ҹ9V ,XC|Bl!\*nXfτ 1=7X(wZS>X#xoxy=WE[nԥm{>6Is klH0day.\Ƅ=Ms gT& izqPM 3ca:}drI'Z 2w<Ȟ/xr32؅,,ѧט9l/A u$(i\D}$<,bas<'b<,Pe~\ YAYb;$RX3]b<1g9(F8 \) ?|/x^a\h\GF=~\o& c,t 3_3T2'FHO\B=<: 8L,{Sc˜igAo~u:.^ܱ>Am,L@LṮu2HFgX0K{LI\B\u}*QdRƶ&Nv|{bimN݉=?'2ja.8/.27`vxS8faTI,˧3,.Z\!@sFn&pd7Z'PL"Pb#1tmQZr-s7| B2G,!.N L"L8ʀ45CkŚVD1'K[0w+^sx[Sz@ԋ,-QُON2̜ۍ{L1&'bDĀ]TPHX^d ]2Y׃-2;̀.$Kܜ_c^|)IIBUbFGy| %"GHO>0 `H(S%>!C"z0J6 p_?Cp-B\եԑ#wٓz5c^Qe#\VhRLg3 ғƤS#\"nJgI.\@ob"vT< Xeq#T(T J܏KҴiL]04qeH׼qKaEE1k\c*8{9sl֭\J/4ljx fEP#BM5`# j0H/*jS XTje"N23g̜9s^?sfD̗~yyQgI#My>$Dqg__ N_Qxzx(KjmXa#mUbwYZ.nOa-9ɬ8+/,I'%XjOu 3]{pGxSLs"YsjPQsnΟ ^Mc@T 60tjoM?<܀ }" $E0ER$B"E4tZ acPƠ"!JF```r.C /˯Ha/fҳɡcltL;+2fm|㲓XI昆;I{[&4CyB)N39ؽ^)Nru΂AJ"łb.]_:׺^!ec-mw<`qִMtW0W1#!L*88T!7>gKya$zѣ B -`Ϟ{ah%L+ < }luu,o{hefsNi9{*IIƌ)t_ EJ$|%(s=ĩ5X'@gnzB!WA -dd'"OB- =Ʋ$6$K# W99,Ȭ*s%LgU4ߪK19 &0ȠQ/I]0I#12x𲹼oD bu?ai%hy_Ld1\FT#^^0vW2u.Fft9$CYlN{[8O2.( S! g$0[ lAoV;רpߝRaAX9#@IR$!aZq2Q -5q)cK"nIi F00.G1 "h 7*PB'*Sm Q x[1Gܻ|}t"*O4eZJ:9(#Xf0Ʒ /0*x/8POv, bI&Jė zL?e/VqD[z5C Bq_7oZ{4>׋I-sc tFb ETh*s5a>PK7cd2i_wbɢxbi0riD}N!Ŭl9A̓wYqxIy7V`S؄rUAV%ą"F`K5h^+*`Bl}:9^j 1D$H +8Z]aX]ÆjS8= XQ).sZ0/-/2TNօF0*=w 8@6"Rˇec:~ٸlh m{Ʌ0ja8xwWhi {l5b=Nb9w"<ό˰[ 40z1d<1!neBYj DN)1%l 1-+EV[\V+La (" 2ᅛ@!i'0AacuSk>lU "։R!ÿ}_Z:fD!CJn`[qC1s\Xy&98-At&8=[Lg "E*҅@_ch ` y?Lc@0؉  n  d67$3e@,p4⹩Lxx R/0*#]J bմ?(j76h@:MLW?>qnY"!"RFj ,Z0j(̀ 27%)ڬPiBx<^8}÷9</UL;v7@6sӃ.];G0G s/aaVoOLc:>=T :þ=tLHHb@4M= Ah) G1  Jp0oAwEv>j%ӱ4k񛵸q,| yj.d>UJU6WY:53O>Sy\_f
ؓTSK IM[t|#Z1ln]ޯZ3 I @POƪhkУ8Wz{x#!)y5L?DV&3g>mh`m{*d>ff5@WI`]5;V! A'@{N\..ƅG_!7suV$BlW=}i; F)A<.&Xax,38S(b׵*%31l #O-zQvaXOP5o6Zz&⻏^wkI/#&\%x/@{7S މjPh͔&mry>k7=ߪB#@fs_{덱п0-LZ~%Gpˈ{YXf^+_s-T>D@קƸ-9!r[2]3BcnCs?>*ԮeD|%4` :X2 pQSLPRaeyqĘ י5Fit )CdL$o$rpӶb>4+̹F_{Яki+x.+B.{L<۩ =UHcnf<>F ^e||pyv%b:iX'%8Iߔ? rw[vITVQN^dpYI"|#\cz[2M^S0[zIJ/HHȟ- IcV>ܼgy s8ͅH |>4dž?Vˮs/aibb{+Рڽ\Ej ?|+W:rȱK.o}^^ުi7ni{ier:;/Ƃ\.5\ǕP((>6z׳_NHpfϚHii Up'PZ.thkiM5O8ڰaڼy9uVUUQ;v<t:EqL{>PMMͺ@`Etw ܼm {1 R?TLJ>]А>(ض}`n+|҄qB7VǓ6Rٶ}CӴ[BCPm۶&4M ĸRRQJRŬ s ɓ5xv>vml:OùFTwl#..z.\hgto/C>MWW99 ))قmK*+O˲eK(.~zJKK^8֬Y`?ODmmali>]|V `ɒ\Auu]\rsshzzIMKfۛ`0X$XUVC?4DJhkk3o^:XEKK####dffO8`(2p3@|<+ ˍCu&gRƝoid1˚5Wڹ|௉XIwt]wO!iJRʊJY7޺ϒ,`IENDB`workbench-1.1.1/src/Resources/ToolBar/features_toolbox.png000066400000000000000000000062111255417355300237030ustar00rootroot00000000000000PNG  IHDR szz pHYs   MiCCPPhotoshop ICC profilexڝSwX>eVBl"#Ya@Ņ VHUĂ H(gAZU\8ܧ}zy&j9R<:OHɽH gyx~t?op.$P&W " R.TSd ly|B" I>ةآ(G$@`UR,@".Y2GvX@`B, 8C L0ҿ_pH˕͗K3w!lBa)f "#HL 8?flŢko">!N_puk[Vh]3 Z zy8@P< %b0>3o~@zq@qanvRB1n#Dž)4\,XP"MyRD!ɕ2 w ONl~Xv@~- g42y@+͗\LD*A aD@ $<B AT:18 \p` Aa!:b""aH4 Q"rBj]H#-r9\@ 2G1Qu@Ơst4]k=Kut}c1fa\E`X&cX5V5cX7va$^lGXLXC%#W 1'"O%zxb:XF&!!%^'_H$ɒN !%2I IkHH-S>iL&m O:ňL $RJ5e?2BQͩ:ZImvP/S4u%͛Cˤ-Кigih/t ݃EЗkw Hb(k{/LӗT02goUX**|:V~TUsU?y TU^V}FUP թU6RwRPQ__c FHTc!2eXBrV,kMb[Lvv/{LSCsfffqƱ9ٜJ! {--?-jf~7zھbrup@,:m:u 6Qu>cy Gm7046l18c̐ckihhI'&g5x>fob4ekVyVV׬I\,mWlPW :˶vm))Sn1 9a%m;t;|rtuvlp4éĩWggs5KvSmnz˕ҵܭm=}M.]=AXq㝧/^v^Y^O&0m[{`:>=e>>z"=#~~~;yN`k5/ >B Yroc3g,Z0&L~oL̶Gli})*2.QStqt,֬Yg񏩌;jrvgjlRlc웸xEt$ =sl3Ttcܢ˞w|/%ҟ3 cHRMz%u0`:o_FIDATxWk@} :"q)t_d2m:0] &WU1 WhEQ6 RKEj9Cv}-ЅBNs"0 .ݮBm4|>Wd Fzn6m4Ϳ #X{T~?%-(tp]}XM$/`Âl62,(kA>4^@)]$n>.䣠\PE1vIENDB`workbench-1.1.1/src/Resources/ToolBar/help.png000066400000000000000000000107631255417355300212560ustar00rootroot00000000000000PNG  IHDR szz iCCPICC ProfileH wTSIFI RBH'@"Hl$Pb QQY\EEl袀kd-(*, Yu`C廁%η}sw9/8V C% a,O`<`7S vZ_m{BE ILn'^K@hEX<$Αs49iMy#}Op$)MAdۊxBdd+ 5Xed,sfIp19Oy0SY99dK|M5]d$gE O*.?j|?Y>3,bGϰ@3ҴN[L_$1sq3ϰdYŸsf<`yƑ 7gE()JK$@g~o :h&N$ZIgt$Hӧ4U74R(FCO[ @ p?9ywxD"bx#7o`6V {[;G OroM]Ce}t䈗͕J e4.0fgXp du-`{pA 8.ˠwC CA8 MH2,!{ yBP( CP $P*ʠJh?T.@W>>4Bo0 &jlρ7GËx9  >7n,_("(}5EP( j UA5P][(%hk;:梗נKЕft'z=`17 Y)Ĕcj100w0CX,5ź`T*l v7 ێbq8& qpYB.yM# x~=_?OT7B8GXI(%$DU)уML%#VoI$ɕ4$$*HHWHOd*قK^D7o) EIdQ6S()O(hJ6Jl%Z*fJ Ks˕O(_W~BP1QUᨬQR9ү2JSS WP-QW:BQMTzz:HC i4.m mH fVKU+V;֫6NUwTUQR?.&t6=^J?NK|>mCSc+4: lm g밹:XKrHTHePPIh[-}OtiiLGrغq~qeqs^/oM%&&/_c"E.6]%K.U^Yz"XΩ'Ƹܝ<o;o/'{$%xlKx /JԠԽMǥ7e33N4Q2e9ĖBl$!L(sqfkRHͤ?H=?]q"G5Gӳb妕ù?B[7{5К5k ?.momח!nC[NA~?4*J 7o#Gᏽ6WtضK Ov?U49ysos---wzm=\Z[6-l[vv,qܱ|NNNYEhE.][v}Tީj֮Ta7o=={uO5&5<;{guZŵ_Gs׮/m Gqhku&zS1pLz/=rDIէhc-Yk|kmm~3Ugϖ#+87y>x兔 K;^\pvK!\|˻+g]=}y۹ǩoNumr덶}nzݼpws^~=޽_?~00QcOn{YvvoiӇdeYp݈șр>z!~1O?_:믞cC%'ߔ|{㻎'3O|('槮q'V|}j[ȷGb3U NN!(n@Twt0w5|!v|NDfAU2" I[Fęrrerk-R?t-wcLΕv>ϟzoJl#< pHYs  IDATX OW3c`xy.jee F7TL4ݘ1t?aј.&PY$b$P!y oϝ9Gk/w={ܔ'u_]FFFU80ߌn7{ֺn߾ݢ7PWWW~pϲ&K˲!@@8@͛7Ptiio=V^hGuuu Y' 嗝- =sssׯ_vS"i8qݻ} Et475K$cp {{.,y%>D Lx)G~^+# Q~$_4w2Fp~)Ç]iKYV9|RPP9tP CItww28,ׯ_s fn[CCܹsjc8)..J~U)-=EƍRRRR0#_eH*T.sAgF+H={xGW{d 7rh@O }w|#1N8gЮKW%/7/7g48 }@ɡLERyF]RD`œd3,Ϭz l V*8(񐚥w>:LФ?È| 5-f5p 2G-0KDFHtL|ᭂ b,aƒDUs[3A96{|.xMNNr=SrWSDt9iď ¯(Foo+R&1"rS.*BIuOs3Aty ,rXX2!\[4zV=p~@jn'@ >uz޽peKKKпc|ݿq^i .^i } -Hf0X˳gsIz&Ā8dU]Lr!cf֟?./&^$J|6Lk @EEɓ'fV-2dz0~N" i&1`iJKJ]~[1c2_Z2.-@{DPI3YYRTXD#NpR:qѯ43"d_Y tիW284N@p'O}2ȣ`lg31-6ݻ 8|>qdLpe+FLrTEOik+2VkCX6Nw{Ug"NjY_v}N;މt1i*'Pπ֔>oPiIENDB`workbench-1.1.1/src/Resources/ToolBar/identify.png000066400000000000000000000045211255417355300221340ustar00rootroot00000000000000PNG  IHDR szz$iCCPICC Profile8UoT>oR? XGůUS[IJ*$:7鶪O{7@Hkk?<kktq݋m6nƶد-mR;`zv x#=\% oYRڱ#&?>ҹЪn_;j;$}*}+(}'}/LtY"$].9⦅%{_a݊]hk5'SN{<_ t jM{-4%TńtY۟R6#v\喊x:'HO3^&0::m,L%3:qVE t]~Iv6Wٯ) |ʸ2]G4(6w‹$"AEv m[D;Vh[}چN|3HS:KtxU'D;77;_"e?Yqxl+ pHYs  IDATX [UgvÌJ00-lR I,|!zB=D !]0#"2$0"肁S >t]![߷g3']^{Z{{3='*38g':޵eҙ '`9̅$CdX9u)]q2noXG^&Fy+ dFD<0!8w8L,iR\BWy],dcF`SӶ (?=VOs&CS 9GOva{FxvU r }jwXmӁv?G_  ^ lJ{)NoSNet͍o{~`a;삿MxDFK.O{UY$g6u0y#y[RG0<,lIY<|h\蟎~>#?Jμ@1BWAEvSL~cGqLx69 d7K_{XJө]@ c'wkWvAQw,+&hw=zJJ]/;Lrr. A) )z*\I2c>,Ioѷ(܍*LnN} ó`ޖlDb&y`ú>z@ 6W`d D\y^5̴ @L#r {yBҶ歯~0cN[}اVROSSJ7DZnүe![?,v-IhWdS,!Э|;~M:~?չmL9@oB-u!:"yI_['j~uE ;qo1M⏑2{)ń:t,nNQzxj#0G{4P < =/JUz?Wo͂s9;w'w8lREulyeΪo '0Z\30X'ce<_Y=0xcG7\%~o!˃!l}*|1giXt[>NxanႃNxbax~P1āfQR,i9Ṙ 39U1 M͎~#Nnmg082cn_jO,U$]lYr9ǹ_c-x0<+}|w)M9zD(1cS_h,Z:i-/pXOErOuԴڈFfVO014J3N9|6\IicTtL6lΗU[:9v~2Nd^QhkK^`ե!Ma*&]£b*b;Ư$%\M6NqKNOox}u%![8G=Z7ZnGW ]hz;NNQSh @J9"jPQx~|}u&d3>i[xڡә={//^ ::l&mhȘ&H'N~0̑ɹO:/c.?Y[ݳZ69rB0#FHВ 2?o/xZWv)M$T ET$ a S ?z9#4=f8v)MOtOr|=>u m1L,kC 7?s˷ pbҨ*j_j74j6y5g?m&׎t[iFo[3/|_ R&j(iQъw'D':'ǦSͲ훫T7ﲀ]YlYXrdUtg i{`JX~#&)#U&Ez;QP") IKi,Q{Q]h)ʎ :;+v]`fdޝ1 2J<]W |M[[$-w4>֭Noϥ^8DBOa7#etX05ʬ\ЧOK5}Y6\PM @t8Tޠv<4h9 r13 AЛz0 d0 DLb^g"^C0#!r$"$~$7H:6# ]N2~H)r-* 6pJs>*ZEj!49(vT6-'m>$jN3tS;w/5X[&I)ifO:K^#VZ98:98*rcWyxf:%+(,D'4''R-%"$a|XQ1eޟ 7)?HPPWe?%&a]Ӣ;Gk ihj==gWMLpECtV >6ea}.b~mvy|J."tz)B,B#"F)DyǏ$j$^qIMIKNicw]FgZ;ECwmJf|+*S h@5lnlôt=&t+=P{E2 }6^=%6S=/ԵfQ?`ȍw'r/%mF{7eOGaNE枪E>kמbл M{9!][|ɿ~v9_oؾ6lWCv|?3+?;w% ߼z'ߛQcÜkoMi?y"`vt^󙗁鯤 o(px;C(px =lրwc:8z9XYIC+K 6%< ϵ K©6},򮭋IDATX VIL$enmpDxb 7ILPa4%0b22Aà aaH06TWWTwW/|K_/Ekk`;%p 'v͊7apyVV g)ʲ#| .,zFOGGGt>Rfl6t?>>xDIdp8|d2# 6ݽ{u+999$$e  A_тDzg2ƾ"Dp?wP&R<}wNc 2SHCo\~ZW_/:iN걸b1744hoozST,ꪩ^;( DX Sp}`$G8nhhc Rͥ0l#c-q W~y-2(QSRYx9sss=cD4v89"'d$wRWWN30mWH{zzެ޷aVVtC_1}soCOHaJ\F0Eg ."EpP xt^vKiq077Sr hv9Y`sآ70N3ن:;T$HGU5@WI`]5;V! A'@{N\..ƅG_!7suV$BlW=}i; F)A<.&Xax,38S(b׵*%31l #O-zQvaXOP5o6Zz&⻏^wkI/#&\%x/@{7S މjPh͔&mry>k7=ߪB#@fs_{덱п0-LZ~%Gpˈ{YXf^+_s-T>D@קƸ-9!r[2]3BcnCs?>*ԮeD|%4` :X2 pQSLPRaeyqĘ י5Fit )CdL$o$rpӶb>4+̹F_{Яki+x.+B.{L<۩ =UHcnf<>F ^e||pyv%b:iX'%8Iߔ? rw[vITVQN^dpYI"|#\cz[2M^S0[zIJ/HHȟ- Icv|8bksϷ P,Wkk4{xOv_.+A)if}3S3/G"CbJ$Jt:,676i$eFqq|rȑ_ɯqbbf+}"/  JbB{zfB{axtnnTT8ZI?kki'ϛJׯx+}oɷ`XZ\D.v;>AۡjQ,A)q( XY ;% ˖mrgPm2j(> OGGkKwb؏VVB[[0eÁNfERiejST*jق$\֟KBuՉ BAa ?zuptϜ&"V "R<$l6L&B^K%[5e,kСC;###}L!D+At%8466B@&E*F^ʃSP $Q(u*Q,  b}}ͦGXw_cmIugϞ- !"IZTThqæGA!2=de:Jb w&'?.\PܼywDQj٩J"|>!\V:pHRtJ)j%xRJe@X)llûP(Tv8`9[\Kdlfs f )d{ըChP(H$F&&n!He^Ŵn)-2 ÕJ%Dۘ@0Fww7[ d\N*a2iJ%rIdYdYE$A, R J)aY>vY_oUS)2<<(J$qDۈvL&Ԩl eHw ss! CtD"NOJ]e¿.۱e'), C__RSa0 0!~?`7ZI:Nm^e@ F<;NloGN01qx P(XDb1\8~%h<$~ 559 F\SW)f:"@BOO/ m띇Ϸ`0A0;"Ess<$Iz@455C| ܹbQjɓ'qQb1:99IF/~Z֙3ǝ?Ꮵ|/~N?tz, YWBQ[[ 6& x~1gfޅԩWp!bZQUUJ)pk|uZ`Yp8X PK-?o@̒9w\ ե!nn&c,5띧K]4(XxGu 6 vK=A@PFGGi*}i٩}nk}7IlmD Hvw[qƥ0{p…+(|!{EܨtWyV_ιs-.t ) s~d\VNNMH.2t0,A&>>xOs` ] *S 4>s@@z&@ڌ;C nD nXNV53 u9@' uV#MJ@?XJjxz7 fDF/9u&g򇚞"Β8xdI?Q_2`9R{OWǏlա|3k.ų0F8GFG2JM!/yo($GYm9H~l_ٯ/f=̂9Q@aZlsߥ M\k01ib1vr/S[D>fPbG Q &[ڽv=jlZ[-n%+$Fŀ؁{^[ SNLq!r[%~ksWsʂ=Oߌɩitnvk[`֚Ŷ]oA[Kps[7Mk˺'WFrmoA^YZRBiT̼*l]V_m4>]i4\p[Eiݎ cHRMms{n3'IIDATxڔW}Lg~/X]X eQZ15wzQ.]4,9v^FjKb. 3zgzXZ k6Y^._ "[a?7@d>0c|nhhPV9\yy9DVQx<` ***89}Q284Ax!A!nb5uuu2i(ؾ};RRRz%RWWGE׮D2 N^_#99RUUJpqԩLxd?r1U2Jx^MZZgΜuQSctt{QJw^%'')-8QnE1D"<^xtLreرwh4k׮U\.W PJ #d}>߂HHH`Vs\EI~6??=z4f Eq|Hpwwqq1Եezdgg[GyJBf {<K/.]DbI|%&&UYNNnGrqrrk>2$IlhVTTp>/$~g 333(Yjj*=11V(p8Fٳ_uy^x&ێiffFvOV}f͚ .ݺuQPP:p;w ~>wA]< "’$ӬZJn:5qwi:%%-[1׻룏>ʶf͚͵W"7o%K?Q #lOV1:uݽ{71by>!!l{sA@ooĉR? "d|I)^/tV|8PCˁ?%(yTAJ΢ZW\zc>/@ؤsFu.Vdwls]TD N J\¾VWW_hܸ9ؿbH555UUU hii >bt>-CKMMMF]GGqTIf H(cF4<ӴeFQbW?44dXS$lQH) 2{8Šuݔo )3 [R*RtVTdpбmTb_M@`8+.pXxep;@^an!%l<+gYő~:̅`b+6*++0}]]]^TBkk<ʻE cyo?JA+˹zA2::z]6z,ҝC56*-U@ii:}R#2j%˩|3Ŗ,N,LFlo?a:9yxPNBw?sGpos`Q9>gUʰ9Clun/)D#گ٬0;SZzIENDB`workbench-1.1.1/src/Resources/ToolBar/view-dorsal.png000066400000000000000000000016111255417355300225520ustar00rootroot00000000000000PNG  IHDR szztEXtSoftwareAdobe ImageReadyqe<+IDATxWSZAT"_ q2`!%N;9TQLf6f&) @!4Un{k3;pox]5:Zg=cm[fXjvN[]M17l6O'X,=???Yёi6qzZ֝sDfݏAc\i2|̌ ߀l...BssCH$!l|||L}FAW{CN/ niaay8kfḁQ\.G`IZDdJG@ @NjC~Uˡ'{i~\Q2mllP&UvSN*R/b{!<#G'''Ol@|KVVVmx>%CEfS?%b c%NSTRϸɐ߬P~zxcqqdC=w(96geCs62驰,')]|=ߎDlMw~rO)Ӱ+]yIENDB`workbench-1.1.1/src/Resources/ToolBar/view-left-lateral.png000066400000000000000000000014651255417355300236510ustar00rootroot00000000000000PNG  IHDR szztEXtSoftwareAdobe ImageReadyqe<IDATxW=Q+NHb $Ҙ͝Bu Oa4R@ g]l,{ Z$|opm׶ ^t _U6"t@xPՄdr \NZL|U=LEj2G Z0 bTzwej<֓Fz~xΕJl6ۅEjU|b`~aN5BphŢ榸!Z^%'0Q TTj!.-&9Rt<!N\.@  ϯ% h=ǺəÉ &ZðR2"gt`݌r8m4u`báT6i,d2 R8d2`R[(`4itl6ρee֌cb5 n`,[πFȖةaDd5f]ݖQ) OI6 =imxv^V >¯Z8Ӊ r)@X,pvOfvKr+C{t~gٲm:xBl6a@뚊)XFڢTF쓧Qxf޾:qz 8vsx D}v_>:<[W]:"*L.3;,Ox͛aFgIENDB`workbench-1.1.1/src/Resources/ToolBar/view-left-medial.png000066400000000000000000000021771255417355300234610ustar00rootroot00000000000000PNG  IHDR szztEXtSoftwareAdobe ImageReadyqe<!IDATxWKHTQ}`(ꌍ64ڨ3.zC*aP qHE4JPe$V#JQ2F(8`%w8zgIMïs ю;L3u0 {v7 uL2dj@ff&PVVbӻ[{i ~wݛΙ?! wtthRLNNV&uK||ЧQS֬PoOpq#PՄdr \NZL|U=LEj2G Z0 bTzwej<֓Fz~xΕJl6ۅEjU|b`~aN5BphŢ榸!Z^%'0Q TTj!.-&9Rt<!N\.@  ϯ% h=ǺəÉ &ZðR2"gt`݌r8m4u`báT6i,d2 R8d2`R[(`4itl6ρee֌cb5 n`,[πFȖةaDd5f]ݖQ) OI6 =imxv^V >¯Z8Ӊ r)@X,pvOfvKr+C{t~gٲm:xBl6a@뚊)XFڢTF쓧Qxf޾:qz 8vsx D}v_>:<[W]:"*L.3;,Ox͛aFgIENDB`workbench-1.1.1/src/Resources/ToolBar/view-plane-axial.png000066400000000000000000000020671255417355300234670ustar00rootroot00000000000000PNG  IHDR szztEXtSoftwareAdobe ImageReadyqe<IDATxWOQDZlKh EbH%xSo/DQED/ cb!R%JG( Ej ԙǾ,mqv;o~3flQ[Q*ؘ *v) -:HlsfUqױN:@|%^&)U[\8e@}G@p 60t^ ]fZ`0{)+ v>u݋rr ¯ZJH2P-*.bGL=VVV@K <@ju NĐUS(QǤ6 Rb>ٻo?IORa䅚TVVo%!1aXcjN'n\kWVdA@'$"`YN2=F 3'OHtw9G<dlhIHv9tdv,(Tv`aGvl,'Ԅ&Q?e$>~ʩ9F R.l=m8yx0 [1Å8sT⻢ED]OөGqͅ(^%B\ [5oR - P}>}G˗.PiXTusLY/'TTmm{X4ZxdljT?}ͨT#_ Eц#Qf/'Nv: \C Z\!Js`.{l -@-ۜr|4 . ۍM@tdianM&C7S>L1[ K97H݂!sm(4~CNdSܟGXPTS{JT<8 C]A0wyP6,(GR6&mˣGcmi۷n,2Tk T; *}U.!;ikϜqV y5US#uwvcvv6 W*tEMh}p%ܫ& f\ߖhB 5F!X>:ZǬ0QWi HnӬ^FRTؿM?^f(.IENDB`workbench-1.1.1/src/Resources/ToolBar/view-plane-parasagittal.png000066400000000000000000000017171255417355300250460ustar00rootroot00000000000000PNG  IHDR szztEXtSoftwareAdobe ImageReadyqe<qIDATxWMLQb9-) jPc!<8zØQ &&$ћ?pBL["c1 8@-v ݒA'ݷoͼYߛVj/07L@ \`0P%-ha~1%=([>oSd4~_&'l*@e?S3 I;NFHp+MQ;-Z`T8Gfmuq&hV.cF#]\MqV+]yFh}}=n'r^J eR\TDmhv6䐍jnj iMH$f>40;;;أwzz~OryG8zIܧ7oͷh,@ܑqܕqg_f%Wy.bh"4UQRg)شPzkw33{Ñf!eTFz'$"kHΘ{e8|=}Qi*Qay&^Ũ#2 eVBxma^:Ea ± ,"Dcjx mwܜ݈kݤe~gJ@0 VVy)x^7хXFΒhJ>9v"@ J, `,D:&4o5Za|xhP,TP!O~w6ٞ0VVUU.Ў@<@qbΦ3g#v+~:VIago%9##IENDB`workbench-1.1.1/src/Resources/ToolBar/view-posterior.png000066400000000000000000000017511255417355300233210ustar00rootroot00000000000000PNG  IHDR szztEXtSoftwareAdobe ImageReadyqe<IDATxWMLSA $pm" -xi8' $⑟$z7=a5A)xQB@jBƀX$^(M!Iń $ufݭ{1q췳/KNvVD#yzue 9A f9nؓԮb"'6NH$}:bpL)+ sccc"# r4Ҿ^/ vvve]=Ĺ X,ZvQ; pZ1R]] )3+++SR#"N3jjjX28p@ `Sѡi/P "G>BI#455LzzzLtV RKfى!K(`xxAHY ST{јxji xO5"]˂@j3i2pa߻S!: iv$t`1,-  jooD~?q{T烁qD1kM߶6YD馋J1-/`;fVV=jDž.Ĩr7̷cFl`z-/.`#ќu'M򭲎gi6$;~/ j dvX>T~>B쨭6'-p4IB?t=`@\Qs;d7gl_.T:} UAnY|N b0zz9,Ԟ9X^`#ߏʇl\+.&)^b:v7"fץ߀f\N0o#X&IENDB`workbench-1.1.1/src/Resources/ToolBar/view-right-lateral.png000066400000000000000000000015051255417355300240270ustar00rootroot00000000000000PNG  IHDR szztEXtSoftwareAdobe ImageReadyqe<IDATxW=oQ>E&>BW?RtX:@:TƄ8 1&aĄ`TӲXӏ. 81/"`bzx{sss9s;3"CG[.67 ,xt:p"efTT0Tgxb5]zc3$O2[ jA~D~~iH@e A% M=B] $Ǵw/ ~ ŴbeHPA$^ܺ J~iHb(\h4J,3pq):َLjq  )w~ 0NIENDB`workbench-1.1.1/src/Resources/ToolBar/view-right-medial.png000066400000000000000000000021741255417355300236410ustar00rootroot00000000000000PNG  IHDR szztEXtSoftwareAdobe ImageReadyqe<IDATxWMLA~XVkڂh#JIĠ^ENM/EPiAPFT/&$1PZEHZ~#yL]+'K>/%~x" ٺ5%ĩ2>??JJJ@^?u^- v=2<< B1E113x(rWk)V+!74Hjj*joI,~0`2`bbN&s~/󣪪 q`H+ѨkjjYVsgzH]2 l tww+2D+FFF4TH7zV10zhiiQU@<˦>UVV*aسmuRf6<ѡb{ <^𽞆ٹB  qX?h?Z76ՒV+My45bbM#jkk̜]ٮ$hVV^>Q*=yΣ`rzZV'|P*mmm5~U9EDX)'Od.-c z<kHD~WFLn'aBiRj IENDB`workbench-1.1.1/src/Resources/ToolBar/view-right.png000066400000000000000000000016341255417355300224100ustar00rootroot00000000000000PNG  IHDR szztEXtSoftwareAdobe ImageReadyqe<>IDATxW=OSQ~iiMY ZC;bbˀFBuI['M P#M 99{{{m] 'yq>8f]e(Ign(M Ši.1g!qQ ivQP uA@&HW"[hivlK3 *Px7f'9;{w~D:ҒbJLL;ɚ?66V1X=~]<߮rlR5̗hL\)SoX=?2ւdX" T4XSC[̅ꕂHjÀ B??^OW @7n@+A@X`<5xb:mo5假D"߆-?߼^:hI$:r0!dQ,J?f4ԣ >5Np5-'''(Ei/=]_` ߠcK ^{92 CG2Izn rw&91:nbmbzxW=Ӱ5_#*./ ýU^Y Ѱt%|[Kii)9965 ^[?V3"UUUR],rzFMϸnLS -80xsǿ{>+Gnc}]O0*u\xdԫ(8 Wh4!aIENDB`workbench-1.1.1/src/Resources/ToolBar/volume-crosshair-pointer.png000066400000000000000000000040141255417355300252760ustar00rootroot00000000000000PNG  IHDR szz$iCCPICC Profile8UoT>oR? XGůUS[IJ*$:7鶪O{7@Hkk?<kktq݋m6nƶد-mR;`zv x#=\% oYRڱ#&?>ҹЪn_;j;$}*}+(}'}/LtY"$].9⦅%{_a݊]hk5'SN{<_ t jM{-4%TńtY۟R6#v\喊x:'HO3^&0::m,L%3:qVE t]~Iv6Wٯ) |ʸ2]G4(6w‹$"AEv m[D;Vh[}چN|3HS:KtxU'D;77;_"e?Yqxl+ pHYs  IDATX ;KA/FMbRXV n'vZkg)X@,AML&,Fͬamd̜9Gi uj+gMNNJgű*|zzBII PTTʆWV pww'/¾k N?  cFFF VB @yy9?cq R9!?+V\888FD0<>.y?44:|h @+++ ׇd* x`+{OiUUU0m%jsss֖8/a8ߗ 5&z%Szzz^ÿ4/S! #iN˱1~pxR~ @#I x~~{{{əV7d[e@ tii XYYy5 dK6dC 3][[Ύ 4[fTB&#OV 1PbzIENDB`workbench-1.1.1/src/Resources/resources.qrc000066400000000000000000000324671255417355300210040ustar00rootroot00000000000000 ./About/hcp-logo.png ./Cursor/pen_eraser.png ./FtglFonts/VeraBd.ttf ./FtglFonts/VeraSe.ttf ./HelpFiles/Features_Toolbox/Borders/Borders.html ./HelpFiles/Features_Toolbox/Borders/Borders_Attributes.png ./HelpFiles/Features_Toolbox/Borders/Borders_Selection.png ./HelpFiles/Features_Toolbox/Borders_Attributes.png ./HelpFiles/Features_Toolbox/Features_TB_icon.png ./HelpFiles/Features_Toolbox/Features_Toolbox.html ./HelpFiles/Features_Toolbox/Fibers/Fibers.html ./HelpFiles/Features_Toolbox/Foci/Foci.html ./HelpFiles/Features_Toolbox/Foci/Foci_Attributes.png ./HelpFiles/Features_Toolbox/Foci/Foci_Selection.png ./HelpFiles/Features_Toolbox/Labels/Labels.html ./HelpFiles/Features_Toolbox/Labels/Labels_Attributes.png ./HelpFiles/Features_Toolbox/Labels/Labels_Selection.png ./HelpFiles/Glossary/Brainordinate/Brainordinate.html ./HelpFiles/Glossary/CIFTI/CIFTI.html ./HelpFiles/Glossary/ConnectomeDB/ConnectomeDB.html ./HelpFiles/Glossary/ConnectomeDB/ConnectomeDB_login.png ./HelpFiles/Glossary/GIFTI/GIFTI.html ./HelpFiles/Glossary/Grayordinate/Grayordinate.html ./HelpFiles/Glossary/NIFTI/NIFTI.html ./HelpFiles/Glossary/Scene/Scene.html ./HelpFiles/Glossary/Specification_File/Spec_File.png ./HelpFiles/Glossary/Specification_File/Specification_File.html ./HelpFiles/Glossary/Whiteordinate/Whiteordinate.html ./HelpFiles/Information_Window/Information_Window.html ./HelpFiles/Information_Window/Information_Window.png ./HelpFiles/Information_Window/Properties/Information_Properties.png ./HelpFiles/Information_Window/Properties/Properties.html ./HelpFiles/Information_Window/Select_Brainordinate/CiftiFileRow.png ./HelpFiles/Information_Window/Select_Brainordinate/Indentify_Brainordinate.png ./HelpFiles/Information_Window/Select_Brainordinate/Select_Brainordinate.html ./HelpFiles/Menus/Connect_Menu/Connect_Menu.html ./HelpFiles/Menus/Connect_Menu/Connect_Menu.jpg ./HelpFiles/Menus/Data_Menu/Data_Menu.html ./HelpFiles/Menus/Data_Menu/Project_Foci_Dialog.png ./HelpFiles/Menus/Develop_Menu/Develop_Menu.html ./HelpFiles/Menus/File_Menu/Animation_Control/Animation_Control.html ./HelpFiles/Menus/File_Menu/Animation_Control/Animation_Control_dialog.png ./HelpFiles/Menus/File_Menu/Capture_Image/Capture_Image.html ./HelpFiles/Menus/File_Menu/Capture_Image/Capture_Image_dialog.png ./HelpFiles/Menus/File_Menu/File_Menu.html ./HelpFiles/Menus/File_Menu/File_menu.png ./HelpFiles/Menus/File_Menu/Preferences/Preferences.html ./HelpFiles/Menus/File_Menu/Preferences/Preferences_Colors.png ./HelpFiles/Menus/File_Menu/Preferences/Preferences_Misc.png ./HelpFiles/Menus/File_Menu/Preferences/Preferences_OpenGL.png ./HelpFiles/Menus/File_Menu/Preferences/Preferences_Vol.png ./HelpFiles/Menus/File_Menu/Save-Manage_Files/Gear_symbol.png ./HelpFiles/Menus/File_Menu/Save-Manage_Files/Reload_symbol.png ./HelpFiles/Menus/File_Menu/Save-Manage_Files/Remove_symbol.png ./HelpFiles/Menus/File_Menu/Save-Manage_Files/Save-Manage_Files.html ./HelpFiles/Menus/File_Menu/Save-Manage_Files/Save-Manage_Files_dialog.png ./HelpFiles/Menus/File_Menu/Save-Manage_Files/Upload_symbol.png ./HelpFiles/Menus/Help_Menu/Bug_Report_box.png ./HelpFiles/Menus/Help_Menu/Help_box.png ./HelpFiles/Menus/Help_Menu/Help_button.png ./HelpFiles/Menus/Help_Menu/Help_Menu.html ./HelpFiles/Menus/Help_Menu/Help_Menu.png ./HelpFiles/Menus/Help_Menu/Help_Search.png ./HelpFiles/Menus/Help_Menu/Workbench_Help.png ./HelpFiles/Menus/Surface_Menu/Surface_Menu.html ./HelpFiles/Menus/Surface_Menu/Surface_Properties.png ./HelpFiles/Menus/Surface_Menu/Volume_Interaction_Surfaces.png ./HelpFiles/Menus/View_Menu/Tile_Tabs_Configuration/Tile_Tabs_Configuration.html ./HelpFiles/Menus/View_Menu/Tile_Tabs_Configuration/Tile_Tabs_Configuration.png ./HelpFiles/Menus/View_Menu/View_Menu.html ./HelpFiles/Menus/View_Menu/View_Menu.png ./HelpFiles/Menus/wb_view_Menu/wb_view_Menu.html ./HelpFiles/Menus/wb_view_Menu/wb_view_Menu.png ./HelpFiles/Menus/Window_Menu/Rename_tab.png ./HelpFiles/Menus/Window_Menu/Window_Menu.html ./HelpFiles/Menus/Window_Menu/Window_Menu.png ./HelpFiles/Mouse_Controls/Mouse_Controls.html ./HelpFiles/Mouse_Controls/RightClick_Surface.png ./HelpFiles/Mouse_Controls/RightClick_Volume.png ./HelpFiles/Open_Spec_File_Dialog/gear-button.png ./HelpFiles/Open_Spec_File_Dialog/Open_Spec.png ./HelpFiles/Open_Spec_File_Dialog/Open_Spec_File_Dialog.html ./HelpFiles/Overlay_Toolbox/Charting/Charting.html ./HelpFiles/Overlay_Toolbox/Charting/Charting_History.png ./HelpFiles/Overlay_Toolbox/Charting/Charting_Loading.png ./HelpFiles/Overlay_Toolbox/Connectivity/Connectivity.html ./HelpFiles/Overlay_Toolbox/Connectivity/Connectivity.png ./HelpFiles/Overlay_Toolbox/Layers/Add_Layer.png ./HelpFiles/Overlay_Toolbox/Layers/Colorbar.png ./HelpFiles/Overlay_Toolbox/Layers/Layers.html ./HelpFiles/Overlay_Toolbox/Layers/Overlay_and_Map_Settings/Overlay_and_Map_Settings.html ./HelpFiles/Overlay_Toolbox/Layers/Overlay_and_Map_Settings/Palette_Settings.png ./HelpFiles/Overlay_Toolbox/Layers/Overlay_and_Map_Settings/Settings_icon.png ./HelpFiles/Overlay_Toolbox/Layers/Overlay_Toolbox_horiz.png ./HelpFiles/Overlay_Toolbox/Layers/Settings_icon.png ./HelpFiles/Overlay_Toolbox/Overlay_Toolbox.html ./HelpFiles/Overlay_Toolbox/Overlay_Toolbox_horiz.png ./HelpFiles/Overlay_Toolbox/Overlay_Toolbox_vert.png ./HelpFiles/Overlay_Toolbox/Vol-Surf_Outline/Color-Vol-Surf_Outline.png ./HelpFiles/Overlay_Toolbox/Vol-Surf_Outline/Tab-Vol-Surf_Outline.png ./HelpFiles/Overlay_Toolbox/Vol-Surf_Outline/Vol-Surf_Outline.html ./HelpFiles/Overlay_Toolbox/Vol-Surf_Outline/Vol-Surf_Outline.png ./HelpFiles/Scenes_Window/Create_New_Scene_File/Create_New_Scene.png ./HelpFiles/Scenes_Window/Create_New_Scene_File/Create_New_Scene_File.html ./HelpFiles/Scenes_Window/Create_New_Scene_File/Scenes button.png ./HelpFiles/Scenes_Window/Scenes_Window.html ./HelpFiles/Scenes_Window/Scenes_Window.png ./HelpFiles/Splash_Screen/Splash_Screen.html ./HelpFiles/Splash_Screen/Splash_screen.png ./HelpFiles/Toolbar/All_View/All_Toolbar.png ./HelpFiles/Toolbar/All_View/All_View.html ./HelpFiles/Toolbar/All_View/All_view.png ./HelpFiles/Toolbar/All_View/Volume_ID_button.png ./HelpFiles/Toolbar/Chart_View/Chart_View.html ./HelpFiles/Toolbar/Chart_View/ChartView_dtseries_DataSeries.png ./HelpFiles/Toolbar/Chart_View/ChartView_pscalar_Matrix.png ./HelpFiles/Toolbar/Chart_View/Data-Time_Series_Chart_View/ChartDataSeries.png ./HelpFiles/Toolbar/Chart_View/Data-Time_Series_Chart_View/ChartView_DataSeries_Toolbar.png ./HelpFiles/Toolbar/Chart_View/Data-Time_Series_Chart_View/Data-Time_Series_Chart_View.html ./HelpFiles/Toolbar/Chart_View/Matrix_Chart_View/ChartView_Matrix_Toolbar.png ./HelpFiles/Toolbar/Chart_View/Matrix_Chart_View/Matrix_Chart_View.html ./HelpFiles/Toolbar/Chart_View/Matrix_Chart_View/pconn_matrix_montage.png ./HelpFiles/Toolbar/Chart_View/Matrix_Chart_View/pscalar_matrix_surface.png ./HelpFiles/Toolbar/Chart_View/Montage_toolbar.png ./HelpFiles/Toolbar/Clipping/Clipping.html ./HelpFiles/Toolbar/Clipping/Clipping_Planes.png ./HelpFiles/Toolbar/Custom_Orientation/Custom_Orientation.html ./HelpFiles/Toolbar/Custom_Orientation/Custom_Orientation.png ./HelpFiles/Toolbar/Mode/Border_Drawing/Border_Drawing.html ./HelpFiles/Toolbar/Mode/Border_Drawing/Border_drawing.png ./HelpFiles/Toolbar/Mode/Border_Drawing/Border_Editing/Border_edit.png ./HelpFiles/Toolbar/Mode/Border_Drawing/Border_Editing/Border_Editing.html ./HelpFiles/Toolbar/Mode/Border_Drawing/Border_Editing/Edit_Border_Properties.png ./HelpFiles/Toolbar/Mode/Border_Drawing/Create_ROI/Border_ROI.png ./HelpFiles/Toolbar/Mode/Border_Drawing/Create_ROI/Create_Region_of_Interest.png ./HelpFiles/Toolbar/Mode/Border_Drawing/Create_ROI/Create_ROI.html ./HelpFiles/Toolbar/Mode/Border_Tools.png ./HelpFiles/Toolbar/Mode/Foci_Creation/Create_Focus.png ./HelpFiles/Toolbar/Mode/Foci_Creation/Foci_Creation.html ./HelpFiles/Toolbar/Mode/Foci_Creation/Foci_Editing/Edit_Focus_box.png ./HelpFiles/Toolbar/Mode/Foci_Creation/Foci_Editing/Foci_edit.png ./HelpFiles/Toolbar/Mode/Foci_Creation/Foci_Editing/Foci_Editing.html ./HelpFiles/Toolbar/Mode/Foci_Creation/Foci_Tools.png ./HelpFiles/Toolbar/Mode/Foci_Tools.png ./HelpFiles/Toolbar/Mode/Mode.html ./HelpFiles/Toolbar/Montage_View/Montage_toolbar.png ./HelpFiles/Toolbar/Montage_View/Montage_View.html ./HelpFiles/Toolbar/Surface_View/Surface_View.html ./HelpFiles/Toolbar/Surface_View/SurfaceView.png ./HelpFiles/Toolbar/Tab_Functions/Tab_Functions.html ./HelpFiles/Toolbar/Tab_Functions/Tab_Functions.png ./HelpFiles/Toolbar/Toolbar.html ./HelpFiles/Toolbar/Toolbar_icon.png ./HelpFiles/Toolbar/Volume_View/Volume_ID_button.png ./HelpFiles/Toolbar/Volume_View/Volume_View.html ./HelpFiles/Toolbar/Volume_View/VolumeView.png ./HelpFiles/Toolbar/VolumeView.png ./HelpFiles/Workbench_Window/Active_Tab/Active_Tab.html ./HelpFiles/Workbench_Window/Active_Tab/Viewing_Tabs.png ./HelpFiles/Workbench_Window/Tooltip/Tooltip.html ./HelpFiles/Workbench_Window/Tooltip/Tooltip.png ./HelpFiles/Workbench_Window/Viewing_Area/Viewing_Area.html ./HelpFiles/Workbench_Window/Viewing_Area/WBwindow_Horiz_Labels.png ./HelpFiles/Workbench_Window/Viewing_Tabs/Viewing_Tabs.html ./HelpFiles/Workbench_Window/Viewing_Tabs/Viewing_Tabs.png ./HelpFiles/Workbench_Window/WBwindow_Horiz_Labels.png ./HelpFiles/Workbench_Window/Window_Elements_Hide-Show_Buttons/Features_TB_icon.png ./HelpFiles/Workbench_Window/Window_Elements_Hide-Show_Buttons/Help_button.png ./HelpFiles/Workbench_Window/Window_Elements_Hide-Show_Buttons/Information_icon.png ./HelpFiles/Workbench_Window/Window_Elements_Hide-Show_Buttons/Overlay_TB_icon.png ./HelpFiles/Workbench_Window/Window_Elements_Hide-Show_Buttons/Scenes_icon.png ./HelpFiles/Workbench_Window/Window_Elements_Hide-Show_Buttons/Toolbar_icon.png ./HelpFiles/Workbench_Window/Window_Elements_Hide-Show_Buttons/Window_Elements_Hide-Show_Buttons.html ./HelpFiles/Workbench_Window/Workbench_Window.html ./LayersPanel/colorbar.png ./LayersPanel/construction.png ./LayersPanel/wrench.png ./PaletteSettings/chain_link_icon.png ./resources.qrc ./SpecFileDialog/delete_icon.png ./SpecFileDialog/load_icon.png ./SpecFileDialog/options_icon.png ./SpecFileDialog/reload_icon.png ./Splash/hcp.png ./Splash/startup_image.png ./Splash/twitter.png ./ToolBar/clapboard.png ./ToolBar/features_toolbox.png ./ToolBar/help.png ./ToolBar/identify.png ./ToolBar/info.png ./ToolBar/overlay_toolbox.png ./ToolBar/toolbar.png ./ToolBar/view-anterior.png ./ToolBar/view-dorsal.png ./ToolBar/view-left-lateral.png ./ToolBar/view-left-medial.png ./ToolBar/view-left.png ./ToolBar/view-plane-axial.png ./ToolBar/view-plane-coronal.png ./ToolBar/view-plane-parasagittal.png ./ToolBar/view-posterior.png ./ToolBar/view-right-lateral.png ./ToolBar/view-right-medial.png ./ToolBar/view-right.png ./ToolBar/view-ventral.png ./ToolBar/volume-crosshair-pointer.png ./update_resources.sh workbench-1.1.1/src/Resources/update_resources.sh000077500000000000000000000005461255417355300221670ustar00rootroot00000000000000#!/bin/sh mv HelpFiles OLD_HelpFiles cp -r /mnt/myelin/shared/WB_Tutorial/WB_1.0_Help ./HelpFiles rm -rf OLD_HelpFiles rcc -project -o resources.qrc echo "" echo "You now need to:" echo " 1) Run CMake in the build directory" echo " 2) Compile" echo " 3) Commit changes to GIT repository" echo " 4) Push source code to master repository" echo "" workbench-1.1.1/src/Scenes/000077500000000000000000000000001255417355300155155ustar00rootroot00000000000000workbench-1.1.1/src/Scenes/CMakeLists.txt000066400000000000000000000036271255417355300202650ustar00rootroot00000000000000# # Name of Project # PROJECT (Scenes) # # SceneObject is derived from CaretObject. When a scene is created many # instances of SceneObject may be created via its subclasses. # When compiled with debugging on, CaretObject maintains a call stack # and new/delete is tracked for all CaretObjects. As a result, the many # instances of SceneObject will cause reading of scenes to be slow and # memory usage to become excessive. So, do not derive SceneObject from # CaretObject unless CARET_SCENE_DEBUG is defined. # #ADD_DEFINITIONS(-DCARET_SCENE_DEBUG) # # Use XML from Qt but not GUI. # SET(QT_DONT_USE_QTGUI TRUE) # # QT include files # INCLUDE(${QT_USE_FILE}) # # Create a Library # ADD_LIBRARY(Scenes Scene.h SceneAttributes.h SceneBoolean.h SceneBooleanArray.h SceneClass.h SceneClassArray.h SceneClassAssistant.h SceneEnumeratedTypeArray.h SceneEnumeratedType.h SceneFloat.h SceneFloatArray.h SceneInfo.h SceneInfoSaxReader.h SceneInteger.h SceneIntegerArray.h SceneObject.h SceneObjectArray.h SceneObjectDataTypeEnum.h SceneObjectMapIntegerKey.h ScenePathName.h ScenePathNameArray.h ScenePrimitive.h ScenePrimitiveArray.h SceneSaxReader.h SceneString.h SceneStringArray.h SceneTypeEnum.h SceneWriterInterface.h SceneWriterXml.h SceneXmlElements.h SceneableInterface.h Scene.cxx SceneAttributes.cxx SceneBoolean.cxx SceneBooleanArray.cxx SceneClass.cxx SceneClassArray.cxx SceneClassAssistant.cxx SceneEnumeratedTypeArray.cxx SceneEnumeratedType.cxx SceneFloat.cxx SceneFloatArray.cxx SceneInfo.cxx SceneInfoSaxReader.cxx SceneInteger.cxx SceneIntegerArray.cxx SceneObject.cxx SceneObjectArray.cxx SceneObjectDataTypeEnum.cxx SceneObjectMapIntegerKey.cxx ScenePathName.cxx ScenePathNameArray.cxx ScenePrimitive.cxx ScenePrimitiveArray.cxx SceneSaxReader.cxx SceneString.cxx SceneStringArray.cxx SceneTypeEnum.cxx SceneWriterXml.cxx ) # # Find headers # INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/Common ${CMAKE_SOURCE_DIR}/Xml ) workbench-1.1.1/src/Scenes/Scene.cxx000066400000000000000000000305671255417355300173110ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SCENE_DECLARE__ #include "Scene.h" #undef __SCENE_DECLARE__ #include "CaretAssert.h" #include "SceneAttributes.h" #include "SceneClass.h" #include "SceneInfo.h" using namespace caret; /** * \defgroup Scene */ /** * \class caret::Scene * \brief Contains a scene which is the saved state of workbench. * \ingroup Scene * * Contains the state of workbench that can be restored. This is * similar to Memento design pattern or Java's Serialization. * * Each class that is to be saved to a scene must implement the * SceneableInterface which contains two virtual methods, one for * saving to the scene, saveScene(), and one for restoring from * the scene, restoreFromScene(). * * The saveToScene() method returns a SceneClass object. In the saveToScene() * method, a SceneClass object is created and it's 'add' methods are used to * add the values of members to the SceneClass object. Add methods exist * for primitive data types (int, float, string, etc), arrays, * the Caret enumerated types, and child classes. Conversely, the restoreFromScene() * method receives a SceneClass object, and uses the SceneClass object's * 'get' methods to restore the values of the class' members. * * When restoring a class, most of the SceneClass' 'get' methods contain * a default value parameter. In some cases a 'get' method may be called * for a member that is not in the SceneClass object (perhaps a member was * added to the class since the scene was saved). In these cases, the * default value will be returned by the 'get' method. * * To simplify this process, a SceneClassAssistant can be used for * members of a class that are not dynamically allocated. An instance * of the SceneClassAssistant is created in the class' constructor. * Members of the class (actually the addresses of the members) are * added to the SceneClassAssistant in the constructor's code. When saving the * scene (in saveToScene), the SceneClassAssistant's saveMembers() method * is called and it will add the class's members to the SceneClass. * In restoreFromScene(), the SceneClassAssistant's restoreMembers will * restore the class' members. * * Some scene data involves saving paths to data files. When a filename * is "in memory" an absolute path is used. However, absolute paths * become a problem when data is transferred to other computers. So, * for this special case, a ScenePathName object can be used. The * ScenePathName object contains a pathname that will be an absolute * pathname when in memory. When written to a SceneFile, the pathname * will be converted to a relative path, relative to the SceneFile so * that the Scene will still function correctly when moved to a different * computer. * * Example of saving and restoring an instance of a class using * a SceneClassAssistant with four members, a boolean, a * Caret enumerated type, a float, and a child class. * *

* \code SomeClass::SomeClass() : SceneableInterface() { m_sceneAssistant = new SceneClassAssistant(); m_childClass = new ChildClass(); m_contralateralIdentificationEnabled = false; m_identificationSymbolColor = CaretColorEnum::WHITE; m_identifcationSymbolSize = 3.0; m_sceneAssistant->add("m_contralateralIdentificationEnabled", &m_contralateralIdentificationEnabled); m_sceneAssistant->add("m_identifcationSymbolSize", &m_identifcationSymbolSize); m_sceneAssistant->add("m_identificationSymbolColor", &m_identificationSymbolColor); m_sceneAssistant->add("m_childClass" "ChildClass", m_childClass); } SceneClass* SomeClass::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { switch (sceneAttributes->getSceneType()) { case SceneTypeEnum::SCENE_TYPE_FULL: break; case SceneTypeEnum::SCENE_TYPE_GENERIC: break; } SceneClass* sceneClass = new SceneClass(instanceName, "SomeClass", 1); m_sceneAssistant->saveMembers(sceneAttributes, sceneClass); return sceneClass; } SomeClass::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } m_sceneAssistant->restoreMembers(sceneAttributes, sceneClass); } * \endcode * * * Example of saving and restoring an instance of a class without a * SceneAssistant. Note that a class may save/restore members using a * SceneClassAssistant for some members and explicitly saving/restoring * members in both saveToScene() and restoreFromScene(). * * \code SceneClass* SomeClass::saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) { switch (sceneAttributes->getSceneType()) { case SceneTypeEnum::SCENE_TYPE_FULL: break; case SceneTypeEnum::SCENE_TYPE_GENERIC: break; } SceneClass* sceneClass = new SceneClass(instanceName, "SomeClass", 1); sceneClass->addBoolean("m_contralateralIdentificationEnabled", m_contralateralIdentificationEnabled); sceneClass->addFloat("m_identifcationSymbolSize", m_identifcationSymbolSize); sceneClass->addEnumeratedType("m_identificationSymbolColor", m_identificationSymbolColor); sceneClass->addClass(m_childClass->saveToScene(sceneAttributes, "m_childClass")); return sceneClass; } SomeClass::restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { if (sceneClass == NULL) { return; } m_contralateralIdentificationEnabled = sceneClass->getBooleanValue("m_contralateralIdentificationEnabled", false); m_identifcationSymbolSize = sceneClass->getFloatValue("m_identifcationSymbolSize", 2.0); m_identificationSymbolColor = sceneClass->getEnumeratedTypeValue("m_identificationSymbolColor", CaretColorEnum::WHITE); m_childClass->restoreFromScene(sceneAttributes, scene->getClass("m_childClass")); } * \endcode * * * Approximate XML produced by above code serializing its members * including the child class. * * \code false 3 32492 3096 * \endcode */ /** * Constructor. * @param sceneType * Type of scene. */ Scene::Scene(const SceneTypeEnum::Enum sceneType) : CaretObject() { m_sceneAttributes = new SceneAttributes(sceneType); m_hasFilesWithRemotePaths = false; m_sceneInfo = new SceneInfo(); } /** * Destructor. */ Scene::~Scene() { delete m_sceneAttributes; const int32_t numberOfSceneClasses = this->getNumberOfClasses(); for (int32_t i = 0; i < numberOfSceneClasses; i++) { delete m_sceneClasses[i]; } m_sceneClasses.clear(); delete m_sceneInfo; } /** * @return Attributes of the scene */ const SceneAttributes* Scene::getAttributes() const { return m_sceneAttributes; } /** * @return Attributes of the scene */ SceneAttributes* Scene::getAttributes() { return m_sceneAttributes; } void Scene::addClass(SceneClass* sceneClass) { if (sceneClass != NULL) { m_sceneClasses.push_back(sceneClass); } } /** * @return Number of classes contained in the scene */ int32_t Scene::getNumberOfClasses() const { return m_sceneClasses.size(); } /** * Get the scene class at the given index. * @param indx * Index of the scene class. * @return Scene class at the given index. */ const SceneClass* Scene::getClassAtIndex(const int32_t indx) const { CaretAssertVectorIndex(m_sceneClasses, indx); return m_sceneClasses[indx]; } /** * Get the scene class with the given name. * @param sceneClassName * Name of the scene class. * @return Scene class with the given name or NULL if not found. */ const SceneClass* Scene::getClassWithName(const AString& sceneClassName) const { const int32_t numberOfSceneClasses = this->getNumberOfClasses(); for (int32_t i = 0; i < numberOfSceneClasses; i++) { if (m_sceneClasses[i]->getName() == sceneClassName) { return m_sceneClasses[i]; } } return NULL; } /** * @return name of scene */ AString Scene::getName() const { return m_sceneInfo->getName(); } /** * Set name of scene * @param sceneName * New value for name of scene */ void Scene::setName(const AString& sceneName) { m_sceneInfo->setName(sceneName); } /** * @return description of scene */ AString Scene::getDescription() const { return m_sceneInfo->getDescription(); } /** * Set description of scene * @param sceneDescription * New value for description of scene */ void Scene::setDescription(const AString& sceneDescription) { m_sceneInfo->setDescription(sceneDescription); } /** * @return true if there are files with remote paths in the scene. */ bool Scene::hasFilesWithRemotePaths() const { return m_hasFilesWithRemotePaths; } /** * Set status of having files with remote paths. * * @param hasFilesWithRemotePaths * New status. */ void Scene::setHasFilesWithRemotePaths(const bool hasFilesWithRemotePaths) { m_hasFilesWithRemotePaths = hasFilesWithRemotePaths; } /** * Set a static value for the scene that is being created. */ void Scene::setSceneBeingCreated(Scene* scene) { s_sceneBeingCreated = scene; } /** * Set the scene being created to have files with remote paths. */ void Scene::setSceneBeingCreatedHasFilesWithRemotePaths() { if (s_sceneBeingCreated != NULL) { s_sceneBeingCreated->m_hasFilesWithRemotePaths = true; } } /** * @return The scene info (name, description, etc.) for this scene. */ SceneInfo* Scene::getSceneInfo() { return m_sceneInfo; } /** * @return The scene info (name, description, etc.) for this scene. * Const method. */ const SceneInfo* Scene::getSceneInfo() const { return m_sceneInfo; } /** * Set the scene info. Ownership of the pointer to SceneInfo is taken * by this Scene object and the Scene object will delete the pointer * at the appropriate time (replaced or Scene is deleted). */ void Scene::setSceneInfo(SceneInfo* sceneInfo) { CaretAssert(sceneInfo); if (m_sceneInfo != NULL) { delete m_sceneInfo; } m_sceneInfo = sceneInfo; } workbench-1.1.1/src/Scenes/Scene.h000066400000000000000000000056741255417355300167370ustar00rootroot00000000000000#ifndef __SCENE__H_ #define __SCENE__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "SceneTypeEnum.h" namespace caret { class SceneAttributes; class SceneClass; class SceneInfo; class Scene : public CaretObject { public: Scene(const SceneTypeEnum::Enum sceneType); virtual ~Scene(); private: Scene(const Scene&); Scene& operator=(const Scene&); public: const SceneAttributes* getAttributes() const; SceneAttributes* getAttributes(); void addClass(SceneClass* sceneClass); int32_t getNumberOfClasses() const; const SceneClass* getClassAtIndex(const int32_t indx) const; const SceneClass* getClassWithName(const AString& sceneClassName) const; AString getName() const; void setName(const AString& sceneName); AString getDescription() const; void setDescription(const AString& description); SceneInfo* getSceneInfo(); const SceneInfo* getSceneInfo() const; void setSceneInfo(SceneInfo* sceneInfo); bool hasFilesWithRemotePaths() const; void setHasFilesWithRemotePaths(const bool hasFilesWithRemotePaths); // ADD_NEW_METHODS_HERE static void setSceneBeingCreated(Scene* scene); static void setSceneBeingCreatedHasFilesWithRemotePaths(); private: /** Attributes of the scene*/ SceneAttributes* m_sceneAttributes; /** Classes contained in the scene*/ std::vector m_sceneClasses; /** Info about scene */ SceneInfo* m_sceneInfo; /** True if it found a ScenePathName with a remote file */ bool m_hasFilesWithRemotePaths; /** When a scene is being created, this will be set */ static Scene* s_sceneBeingCreated; // ADD_NEW_MEMBERS_HERE }; #ifdef __SCENE_DECLARE__ Scene* Scene::s_sceneBeingCreated = NULL; #endif // __SCENE_DECLARE__ } // namespace #endif //__SCENE__H_ workbench-1.1.1/src/Scenes/SceneAttributes.cxx000066400000000000000000000145641255417355300213570ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SCENE_ATTRIBUTES_DECLARE__ #include "SceneAttributes.h" #undef __SCENE_ATTRIBUTES_DECLARE__ using namespace caret; /** * \class caret::SceneAttributes * \brief Attributes of a scene. * \ingroup Scene * * See the documentation in the class Scene for how to use the Scene system. */ /** * Constructor. */ SceneAttributes::SceneAttributes(const SceneTypeEnum::Enum sceneType) : CaretObject(), m_sceneType(sceneType) { m_restoreWindowBehaviorInSceneDisplay = RESTORE_WINDOW_POSITION_RELATIVE_TO_FIRST_AND_USE_SIZES; m_specFileNameSavedToScene = true; m_allLoadedFilesSavedToScene = true; } /** * Destructor. */ SceneAttributes::~SceneAttributes() { } /** * @return The type of scene. */ SceneTypeEnum::Enum SceneAttributes::getSceneType() const { return m_sceneType; } /** * Set the indices of the tabs that are to be saved to the scene. * @param tabIndices * Indices of tabs that are saved to the scene. */ void SceneAttributes::setIndicesOfTabsForSavingToScene(const std::vector& tabIndices) { m_indicesOfTabsForSavingToScene = tabIndices; } /** * @return Indices of tabs for saving to the scene. */ std::vector SceneAttributes::getIndicesOfTabsForSavingToScene() const { return m_indicesOfTabsForSavingToScene; } /** * Is the given tab index for saving to the scene? * * @param tabIndex * The tab index. * @return * True if saved to scene, else false. */ bool SceneAttributes::isTabIndexSavedToScene(const int32_t tabIndex) const { if (std::find(m_indicesOfTabsForSavingToScene.begin(), m_indicesOfTabsForSavingToScene.end(), tabIndex) != m_indicesOfTabsForSavingToScene.end()) { return true; } return false; } /** * @return Name of the scene file. * May only have a value when restoring. */ AString SceneAttributes::getSceneFileName() const { return m_sceneFileName; } /** * Set the name of the scene file containing the scene * that is being restored. * @param sceneFileName * Name of scene file. */ void SceneAttributes::setSceneFileName(const AString& sceneFileName) { m_sceneFileName = sceneFileName; } /** * @return Name of the scene. * May only have a value when restoring. */ AString SceneAttributes::getSceneName() const { return m_sceneName; } /** * Set the name of the scene that is being restored. * @param sceneName * Name of scene. */ void SceneAttributes::setSceneName(const AString& sceneName) { m_sceneName = sceneName; } /** * @return The window restoration behavior when displaying a scene. */ SceneAttributes::RestoreWindowBehavior SceneAttributes::getRestoreWindowBehaviorInSceneDisplay() const { return m_restoreWindowBehaviorInSceneDisplay; } /** * Set the window restoration behavior when displaying a scene. * @param rwb * New value for window restoration behavior. */ void SceneAttributes::setWindowRestoreBehaviorInSceneDisplay(const RestoreWindowBehavior rwb) { m_restoreWindowBehaviorInSceneDisplay = rwb; } /** * @return true if spec file name is included in scene creation. */ bool SceneAttributes::isSpecFileNameSavedToScene() const { return m_specFileNameSavedToScene; } /** * Set spec file name included in scene creation. * @param status * New status of spec file name included in scene creation. */ void SceneAttributes::setSpecFileNameSavedToScene(const bool status) { m_specFileNameSavedToScene = status; } /** * @return Are all loaded files saved to the scene when creating a scene, even those * that do not affect the scene? Including all loaded files will increase * the time required to display the scene. */ bool SceneAttributes::isAllLoadedFilesSavedToScene() const { return m_allLoadedFilesSavedToScene; } /** * When creating a scene, should all loadedfiles be included in the scene, even * those that do not affect the scene? Including all loaded files will increase * the time required to display the scene. * * @param status * New status. */ void SceneAttributes::setAllLoadedFilesSavedToScene(const bool status) { m_allLoadedFilesSavedToScene = status; } /** * @return Save modified palette settings to the scene. * * There are times the user wants to modify the palette settings * for a map but not save the file or the file may not be savable. * Thus, this option allows the user to save the palette settings * to the scene. */ bool SceneAttributes::isModifiedPaletteSettingsSavedToScene() const { return m_modifiedPaletteSettingsSavedToScene; } /** * Set modified palettes saved to scene. * * There are times the user wants to modify the palette settings * for a map but not save the file or the file may not be savable. * Thus, this option allows the user to save the palette settings * to the scene. * * @param status * New status. */ void SceneAttributes::setModifiedPaletteSettingsSavedToScene(const bool status) { m_modifiedPaletteSettingsSavedToScene = status; } /** * Add a new message to the error message. Each message is * separated by a newline. * * @param message * New message added to the error message. */ void SceneAttributes::addToErrorMessage(const AString& message) const { if (message.isEmpty()) { return; } if (m_errorMessage.isEmpty() == false) { m_errorMessage += "\n"; } m_errorMessage += message; } /** * @return The error message. */ AString SceneAttributes::getErrorMessage() const { return m_errorMessage; } /** * Clear the error message. */ void SceneAttributes::clearErrorMessage() { m_errorMessage.clear(); } workbench-1.1.1/src/Scenes/SceneAttributes.h000066400000000000000000000067421255417355300210030ustar00rootroot00000000000000#ifndef __SCENE_ATTRIBUTES__H_ #define __SCENE_ATTRIBUTES__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "SceneTypeEnum.h" namespace caret { class SceneAttributes : public CaretObject { public: enum RestoreWindowBehavior { RESTORE_WINDOW_USE_ALL_POSITIONS_AND_SIZES, RESTORE_WINDOW_POSITION_RELATIVE_TO_FIRST_AND_USE_SIZES, RESTORE_WINDOW_IGNORE_ALL_POSITIONS_AND_SIZES }; SceneAttributes(const SceneTypeEnum::Enum sceneType); virtual ~SceneAttributes(); SceneTypeEnum::Enum getSceneType() const; void setIndicesOfTabsForSavingToScene(const std::vector& tabIndices); std::vector getIndicesOfTabsForSavingToScene() const; bool isTabIndexSavedToScene(const int32_t tabIndex) const; AString getSceneFileName() const; void setSceneFileName(const AString& sceneFileName); AString getSceneName() const; void setSceneName(const AString& name); RestoreWindowBehavior getRestoreWindowBehaviorInSceneDisplay() const; void setWindowRestoreBehaviorInSceneDisplay(const RestoreWindowBehavior rwb); bool isSpecFileNameSavedToScene() const; void setSpecFileNameSavedToScene(const bool status); bool isAllLoadedFilesSavedToScene() const; void setAllLoadedFilesSavedToScene(const bool status); bool isModifiedPaletteSettingsSavedToScene() const; void setModifiedPaletteSettingsSavedToScene(const bool status); void addToErrorMessage(const AString& message) const; AString getErrorMessage() const; void clearErrorMessage(); private: SceneAttributes(const SceneAttributes&); SceneAttributes& operator=(const SceneAttributes&); public: // ADD_NEW_METHODS_HERE private: const SceneTypeEnum::Enum m_sceneType; std::vector m_indicesOfTabsForSavingToScene; AString m_sceneFileName; AString m_sceneName; RestoreWindowBehavior m_restoreWindowBehaviorInSceneDisplay; bool m_specFileNameSavedToScene; bool m_allLoadedFilesSavedToScene; bool m_modifiedPaletteSettingsSavedToScene; mutable AString m_errorMessage; // ADD_NEW_MEMBERS_HERE }; #ifdef __SCENE_ATTRIBUTES_DECLARE__ #endif // __SCENE_ATTRIBUTES_DECLARE__ } // namespace #endif //__SCENE_ATTRIBUTES__H_ workbench-1.1.1/src/Scenes/SceneBoolean.cxx000066400000000000000000000043051255417355300206000ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SCENE_BOOLEAN_DECLARE__ #include "SceneBoolean.h" #undef __SCENE_BOOLEAN_DECLARE__ using namespace caret; /** * \class caret::SceneBoolean * \brief For storage of a boolean value in a scene. * \ingroup Scene * * See the documentation in the class Scene for how to use the Scene system. */ /** * Constructor. * * @param name * Name of object. * @param value * Value of object. */ SceneBoolean::SceneBoolean(const AString& name, const bool value) : ScenePrimitive(name, SceneObjectDataTypeEnum::SCENE_BOOLEAN) { m_value = value; } /** * Destructor. */ SceneBoolean::~SceneBoolean() { } /** * Set the value. * @param value * The new value. */ void SceneBoolean::setValue(const bool value) { m_value = value; } /** * @return The value as a boolean data type. */ bool SceneBoolean::booleanValue() const { return m_value; } /** * @return The value as a float data type. */ float SceneBoolean::floatValue() const { const float f = (m_value ? 1.0 : 0.0); return f; } /** * @return The value as a integer data type. */ int32_t SceneBoolean::integerValue() const { const float i = (m_value ? 1 : 0); return i; } /** * @return The value as a string data type. */ AString SceneBoolean::stringValue() const { const AString s = (m_value ? "true" : "false"); return s; } workbench-1.1.1/src/Scenes/SceneBoolean.h000066400000000000000000000034471255417355300202330ustar00rootroot00000000000000#ifndef __SCENE_BOOLEAN__H_ #define __SCENE_BOOLEAN__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "ScenePrimitive.h" namespace caret { class SceneBoolean : public ScenePrimitive { public: SceneBoolean(const AString& name, const bool value); void setValue(const bool value); virtual ~SceneBoolean(); virtual bool booleanValue() const; virtual float floatValue() const; virtual int32_t integerValue() const; virtual AString stringValue() const; private: SceneBoolean(const SceneBoolean&); SceneBoolean& operator=(const SceneBoolean&); public: // ADD_NEW_METHODS_HERE private: bool m_value; // ADD_NEW_MEMBERS_HERE }; #ifdef __SCENE_BOOLEAN_DECLARE__ // #endif // __SCENE_BOOLEAN_DECLARE__ } // namespace #endif //__SCENE_BOOLEAN__H_ workbench-1.1.1/src/Scenes/SceneBooleanArray.cxx000066400000000000000000000104511255417355300215760ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __SCENE_BOOLEAN_ARRAY_DECLARE__ #include "SceneBooleanArray.h" #undef __SCENE_BOOLEAN_ARRAY_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::SceneBooleanArray * \brief For storage of a boolean value in a scene. * \ingroup Scene * * See the documentation in the class Scene for how to use the Scene system. */ /** * Constructor. * * @param name * Name of object. * @param values * Value in the array. * @param numberOfArrayElements * Number of values in the array. */ SceneBooleanArray::SceneBooleanArray(const AString& name, const bool values[], const int32_t numberOfArrayElements) : ScenePrimitiveArray(name, SceneObjectDataTypeEnum::SCENE_BOOLEAN, numberOfArrayElements) { m_values.resize(numberOfArrayElements); for (int32_t i = 0; i < numberOfArrayElements; i++) { m_values[i] = values[i]; } } /** * Constructor. * * @param name * Name of object. * @param values * Value in the array. */ SceneBooleanArray::SceneBooleanArray(const AString& name, const std::vector& values) : ScenePrimitiveArray(name, SceneObjectDataTypeEnum::SCENE_BOOLEAN, values.size()) { m_values = values; } /** * Constructor that initializes all values to false. * * @param name * Name of object. * @param numberOfArrayElements * Number of values in the array. */ SceneBooleanArray::SceneBooleanArray(const AString& name, const int numberOfArrayElements) : ScenePrimitiveArray(name, SceneObjectDataTypeEnum::SCENE_BOOLEAN, numberOfArrayElements) { m_values.resize(numberOfArrayElements); std::fill(m_values.begin(), m_values.end(), false); } /** * Destructor. */ SceneBooleanArray::~SceneBooleanArray() { } /** * Set a value. * @param arrayIndex * Index of the element. * @param value * Value of element. */ void SceneBooleanArray::setValue(const int32_t arrayIndex, const bool value) { CaretAssertVectorIndex(m_values, arrayIndex); m_values[arrayIndex] = value; } /** * Get the values as a boolean. * @param arrayIndex * Index of element. * @return The value. */ bool SceneBooleanArray::booleanValue(const int32_t arrayIndex) const { CaretAssertVectorIndex(m_values, arrayIndex); return m_values[arrayIndex]; } /** * Get the values as a float. * @param arrayIndex * Index of element. * @return The value. */ float SceneBooleanArray::floatValue(const int32_t arrayIndex) const { CaretAssertVectorIndex(m_values, arrayIndex); const float f = (m_values[arrayIndex] ? 1.0 : 0.0); return f; } /** * Get the values as a integer. * @param arrayIndex * Index of element. * @return The value. */ int32_t SceneBooleanArray::integerValue(const int32_t arrayIndex) const { CaretAssertVectorIndex(m_values, arrayIndex); const float i = (m_values[arrayIndex] ? 1 : 0); return i; } /** * Get the values as a string. * @param arrayIndex * Index of element. * @return The value. */ AString SceneBooleanArray::stringValue(const int32_t arrayIndex) const { CaretAssertVectorIndex(m_values, arrayIndex); const AString s = (m_values[arrayIndex] ? "true" : "false"); return s; } workbench-1.1.1/src/Scenes/SceneBooleanArray.h000066400000000000000000000045111255417355300212230ustar00rootroot00000000000000#ifndef __SCENE_BOOLEAN_ARRAY_H__ #define __SCENE_BOOLEAN_ARRAY_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "ScenePrimitiveArray.h" namespace caret { class SceneBooleanArray : public ScenePrimitiveArray { public: SceneBooleanArray(const AString& name, const bool values[], const int32_t numberOfArrayElements); SceneBooleanArray(const AString& name, const std::vector& values); SceneBooleanArray(const AString& name, const int numberOfArrayElements); virtual ~SceneBooleanArray(); void setValue(const int32_t arrayIndex, const bool value); virtual bool booleanValue(const int32_t arrayIndex) const; virtual float floatValue(const int32_t arrayIndex) const; virtual int32_t integerValue(const int32_t arrayIndex) const; virtual AString stringValue(const int32_t arrayIndex) const; private: SceneBooleanArray(const SceneBooleanArray&); SceneBooleanArray& operator=(const SceneBooleanArray&); public: // ADD_NEW_METHODS_HERE private: std::vector m_values; // ADD_NEW_MEMBERS_HERE }; #ifdef __SCENE_BOOLEAN_ARRAY_DECLARE__ // #endif // __SCENE_BOOLEAN_ARRAY_DECLARE__ } // namespace #endif //__SCENE_BOOLEAN_ARRAY_H__ workbench-1.1.1/src/Scenes/SceneClass.cxx000066400000000000000000000664011255417355300202730ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SCENE_CLASS_DECLARE__ #include "SceneClass.h" #undef __SCENE_CLASS_DECLARE__ #include "CaretAssert.h" #include "CaretLogger.h" #include "SceneBoolean.h" #include "SceneBooleanArray.h" #include "SceneClassArray.h" #include "SceneEnumeratedType.h" #include "SceneEnumeratedTypeArray.h" #include "SceneFloat.h" #include "SceneFloatArray.h" #include "SceneInteger.h" #include "SceneIntegerArray.h" #include "SceneObjectMapIntegerKey.h" #include "ScenePathName.h" #include "ScenePathNameArray.h" #include "SceneString.h" #include "SceneStringArray.h" using namespace caret; /** * \class caret::SceneClass * \brief For storage of a class instance in a scene. * \ingroup Scene * * See the documentation in the class Scene for how to use the Scene system. */ /** * Constructor. * * @param name * Name of the instance saved to this scene class. * @param className * Name of the class, NOT instance. * @param versionNumber * Version number of the class that is saved to this * scene class. Since a class may change over time, * this version number can be used to handle cases * of scenes saved prior to changes made to a class. */ SceneClass::SceneClass(const AString& name, const AString& className, const int32_t versionNumber) : SceneObject(name, SceneObjectDataTypeEnum::SCENE_CLASS), m_className(className), m_versionNumber(versionNumber) { } /** * Destructor. */ SceneClass::~SceneClass() { for (std::vector::iterator iter = m_childObjects.begin(); iter != m_childObjects.end(); iter++) { delete *iter; } m_childObjects.clear(); } /** * @return Name of the class (NOT the instance). */ AString SceneClass::getClassName() const { return m_className; } /** * @return The version number of this scene class instance. */ int32_t SceneClass::getVersionNumber() const { return m_versionNumber; } /** * Add a child to this class. * @param sceneObject * New child. */ void SceneClass::addChild(SceneObject* sceneObject) { CaretAssert(sceneObject); m_childObjects.push_back(sceneObject); } /** * Add a child boolean value to the class. * * @param name * Name associated with value. * @param value * The value. */ void SceneClass::addBoolean(const AString& name, const bool value) { addChild(new SceneBoolean(name, value)); } /** * Add a child boolean array values to the class. * * @param name * Name associated with value. * @param values * The array containing the values. * @param arrayNumberOfElements * Number of elements in the array. */ void SceneClass::addBooleanArray(const AString& name, const bool values[], const int32_t arrayNumberOfElements) { addChild(new SceneBooleanArray(name, values, arrayNumberOfElements)); } //void //SceneClass::addBooleanVector(const AString& name, // const std::vector& values) //{ // addChild(new SceneBooleanArray(name, // values)); //} /** * Add a child class to the class. NOTE: The given * scene class is not copied and this instance will * take ownership of the child class and delete when * this instance is destroyed. * * @param sceneClass * Class that is added. */ void SceneClass::addClass(SceneClass* sceneClass) { if (sceneClass != NULL) { addChild(sceneClass); } } /** * Add a child class array values to the class. * * @param name * Name associated with value. * @param values * The array containing the values. * @param arrayNumberOfElements * Number of elements in the array. */ //void //SceneClass::addClassArray(const AString& name, // SceneClass* values[], // const int32_t arrayNumberOfElements) //{ // addChild(new SceneClassArray(name, // values, // arrayNumberOfElements)); //} /** * Add a child enumerated type value to the class. * @param name * Name associated with the value. * @param value * String representation of the enumerated type's value. */ void SceneClass::addEnumeratedType(const AString& name, const AString& value) { addChild(new SceneEnumeratedType(name, value)); } /** * Add a child enumerated type array values to the class. * * @param name * Name associated with value. * @param values * The array containing the values. * @param arrayNumberOfElements * Number of elements in the array. */ //void //SceneClass::addEnumeratedTypeArray(const AString& name, // const AString values[], // const int32_t arrayNumberOfElements) //{ // addChild(new SceneEnumeratedTypeArray(name, // values, // arrayNumberOfElements)); //} /** * Add a child enumerated type array values to the class. * * @param name * Name associated with value. * @param values * The vector containing the values. */ void SceneClass::addEnumeratedTypeVector(const AString& name, const std::vector& values) { addChild(new SceneEnumeratedTypeArray(name, values)); } /** * Add a child float value to the class. * * @param name * Name associated with value. * @param value * The value. */ void SceneClass::addFloat(const AString& name, const float value) { addChild(new SceneFloat(name, value)); } /** * Add a child float array values to the class. * * @param name * Name associated with value. * @param values * The array containing the values. * @param arrayNumberOfElements * Number of elements in the array. */ void SceneClass::addFloatArray(const AString& name, const float values[], const int32_t arrayNumberOfElements) { addChild(new SceneFloatArray(name, values, arrayNumberOfElements)); } /** * Add a child integer value to the class. * * @param name * Name associated with value. * @param value * The value. */ void SceneClass::addInteger(const AString& name, const int32_t value) { addChild(new SceneInteger(name, value)); } /** * Add a child integer array values to the class. * * @param name * Name associated with value. * @param values * The array containing the values. * @param arrayNumberOfElements * Number of elements in the array. */ void SceneClass::addIntegerArray(const AString& name, const int32_t values[], const int32_t arrayNumberOfElements) { addChild(new SceneIntegerArray(name, values, arrayNumberOfElements)); } /** * Add a child string value to the class. * * @param name * Name associated with value. * @param value * The value. */ void SceneClass::addString(const AString& name, const AString& value) { addChild(new SceneString(name, value)); } /** * Add a child path name value to the class. * * @param name * Name associated with value. * @param value * The value. */ void SceneClass::addPathName(const AString& name, const AString& value) { addChild(new ScenePathName(name, value)); } /** * Add a child string type array values to the class. * * @param name * Name associated with value. * @param values * The array containing the values. * @param arrayNumberOfElements * Number of elements in the array. */ void SceneClass::addStringArray(const AString& name, const AString values[], const int32_t arrayNumberOfElements) { addChild(new SceneStringArray(name, values, arrayNumberOfElements)); } /** * Find and return the child boolean value with the given name. * If no primitive matches the name, the given default * value is returned. * @param name * Name of the value. * @param defaultValue * Value returned if the primitive with the given * name is not found. * @return * The boolean value. */ bool SceneClass::getBooleanValue(const AString& name, const bool defaultValue) const { const ScenePrimitive* primitive = getPrimitive(name); if (primitive != NULL) { return primitive->booleanValue(); } return defaultValue; } /** * Get the values for the boolean array. If no array is * found with the given name, all values are set to the * default value. * @param name * Name of the value. * @param values * Output array into which values are loaded. * @param arrayNumberOfElements * Number of elements in the array. * @param defaultValue * Value used for missing elements. * @return Number of elements actually read form scene class. */ int32_t SceneClass::getBooleanArrayValue(const AString& name, bool values[], const int32_t arrayNumberOfElements, const bool defaultValue) const { const ScenePrimitiveArray* primitiveArray = getPrimitiveArray(name); if (primitiveArray != NULL) { primitiveArray->booleanValues(values, arrayNumberOfElements, defaultValue); return primitiveArray->getNumberOfArrayElements(); } for (int32_t i = 0; i < arrayNumberOfElements; i++) { values[i] = defaultValue; } return 0; } /** * Find and return the child enumerated type value with the given name. * If no enumerated type value matches the name, the given default * value is returned. * @param name * Name of the value. * @param defaultValue * Value returned if the enumerated type value with the given * name is not found. * @return * The string value representation of the enumerated type. */ AString SceneClass::getEnumeratedTypeValueAsString(const AString& name, const AString& defaultValue) const { for (std::vector::const_iterator iter = m_childObjects.begin(); iter != m_childObjects.end(); iter++) { const SceneObject* so = *iter; const SceneEnumeratedType* st = dynamic_cast(so); if (st != NULL) { if (st->getName() == name) { return st->stringValue(); } } } logMissing("Scene Enumerated Type not found: " + name); return defaultValue; } /** * Get the values for the boolean array. If no array is * found with the given name, all values are set to the * default value. * @param name * Name of the value. * @param values * Output array into which values are loaded. * @param arrayNumberOfElements * Number of elements in the array. * @param defaultValue * Value used for missing elements. * @return Number of elements actually read form scene class. */ int32_t SceneClass::getEnumeratedTypeArrayValue(const AString& name, AString values[], const int32_t arrayNumberOfElements, const AString& defaultValue) const { for (std::vector::const_iterator iter = m_childObjects.begin(); iter != m_childObjects.end(); iter++) { const SceneObject* so = *iter; const SceneEnumeratedTypeArray* enumArray = dynamic_cast(so); if (enumArray != NULL) { if (enumArray->getName() == name) { enumArray->stringValues(values, arrayNumberOfElements, defaultValue); return enumArray->getNumberOfArrayElements(); } } } for (int32_t i = 0; i < arrayNumberOfElements; i++) { values[i] = defaultValue; } logMissing("Scene Enumerated Array Type not found: " + name); return 0; } /** * Find and return the child float value with the given name. * If no primitive matches the name, the given default * value is returned. * @param name * Name of the value. * @param defaultValue * Value returned if the primitive with the given * name is not found. * @return * The float value. */ float SceneClass::getFloatValue(const AString& name, const float defaultValue) const { const ScenePrimitive* primitive = getPrimitive(name); if (primitive != NULL) { return primitive->floatValue(); } return defaultValue; } /** * Get the values for the float array. If no array is * found with the given name, all values are set to the * default value. * @param name * Name of the value. * @param values * Output array into which values are loaded. * @param arrayNumberOfElements * Number of elements in the array. * @param defaultValue * Value used for missing elements. * @return Number of elements actually read form scene class. */ int32_t SceneClass::getFloatArrayValue(const AString& name, float values[], const int32_t arrayNumberOfElements, const float defaultValue) const { const ScenePrimitiveArray* primitiveArray = getPrimitiveArray(name); if (primitiveArray != NULL) { primitiveArray->floatValues(values, arrayNumberOfElements, defaultValue); return primitiveArray->getNumberOfArrayElements(); } for (int32_t i = 0; i < arrayNumberOfElements; i++) { values[i] = defaultValue; } return 0; } /** * Find and return the child integer value with the given name. * If no primitive matches the name, the given default * value is returned. * @param name * Name of the value. * @param defaultValue * Value returned if the primitive with the given * name is not found. * @return * The integer value. */ int32_t SceneClass::getIntegerValue(const AString& name, const int32_t defaultValue) const { const ScenePrimitive* primitive = getPrimitive(name); if (primitive != NULL) { return primitive->integerValue(); } return defaultValue; } /** * Get the values for the integer array. If no array is * found with the given name, all values are set to the * default value. * @param name * Name of the value. * @param values * Output array into which values are loaded. * @param arrayNumberOfElements * Number of elements in the array. * @param defaultValue * Value used for missing elements. * @return Number of elements actually read form scene class. */ int32_t SceneClass::getIntegerArrayValue(const AString& name, int32_t values[], const int32_t arrayNumberOfElements, const int32_t defaultValue) const { const ScenePrimitiveArray* primitiveArray = getPrimitiveArray(name); if (primitiveArray != NULL) { primitiveArray->integerValues(values, arrayNumberOfElements, defaultValue); return primitiveArray->getNumberOfArrayElements(); } for (int32_t i = 0; i < arrayNumberOfElements; i++) { values[i] = defaultValue; } return 0; } /** * Find and return the child string value with the given name. * If no primitive matches the name, the given default * value is returned. * @param name * Name of the value. * @param defaultValue * Value returned if the primitive with the given * name is not found. * @return * The string value. */ AString SceneClass::getStringValue(const AString& name, const AString& defaultValue) const { const ScenePrimitive* primitive = getPrimitive(name); if (primitive != NULL) { return primitive->stringValue(); } return defaultValue; } /** * Find and return the child path name value with the given name. * If no path name matches the name, the given default * value is returned. * @param name * Name of the value. * @param defaultValue * Value returned if the path name with the given * name is not found. * @return * The string value. */ AString SceneClass::getPathNameValue(const AString& name, const AString& defaultValue) const { const ScenePathName* pathName = getPathName(name); if (pathName != NULL) { return pathName->stringValue(); } return defaultValue; } /** * Get the values for the string array. If no array is * found with the given name, all values are set to the * default value. * @param name * Name of the value. * @param values * Output array into which values are loaded. * @param arrayNumberOfElements * Number of elements in the array. * @param defaultValue * Value used for missing elements. * @return Number of elements actually read form scene class. */ int32_t SceneClass::getStringArrayValue(const AString& name, AString values[], const int32_t arrayNumberOfElements, const AString& defaultValue) const { const ScenePrimitiveArray* primitiveArray = getPrimitiveArray(name); if (primitiveArray != NULL) { primitiveArray->stringValues(values, arrayNumberOfElements, defaultValue); return primitiveArray->getNumberOfArrayElements(); } for (int32_t i = 0; i < arrayNumberOfElements; i++) { values[i] = defaultValue; } return 0; } /** * Find and return the scene's child primitive with the given name. * * @param name * Name of the child primitive. * @return * Pointer to the primitive with the given name or NULL if * no primitive exists with the given name. */ const ScenePrimitive* SceneClass::getPrimitive(const AString& name) const { for (std::vector::const_iterator iter = m_childObjects.begin(); iter != m_childObjects.end(); iter++) { const SceneObject* so = *iter; const ScenePrimitive* sp = dynamic_cast(so); if (sp != NULL) { if (sp->getName() == name) { return sp; } } } logMissing("Scene Primitive Type not found: " + name); return NULL; } /** * Find and return the scene's child path name with the given name. * * @param name * Name of the child path name. * @return * Pointer to the path name with the given name or NULL if * no path name exists with the given name. */ const ScenePathName* SceneClass::getPathName(const AString& name) const { const SceneObject* so = getObjectWithName(name); if (so != NULL) { const ScenePathName* sp = dynamic_cast(so); if (sp != NULL) { if (sp->getName() == name) { return sp; } } } logMissing("Scene Path Name not found: " + name); return NULL; } /** * Find and return the scene's child primitive array with the given name. * * @param name * Name of the child primitive. * @return * Pointer to the primitive with the given name or NULL if * no primitive exists with the given name. */ const ScenePrimitiveArray* SceneClass::getPrimitiveArray(const AString& name) const { for (std::vector::const_iterator iter = m_childObjects.begin(); iter != m_childObjects.end(); iter++) { const SceneObject* so = *iter; const ScenePrimitiveArray* spa = dynamic_cast(so); if (spa != NULL) { if (spa->getName() == name) { return spa; } } } logMissing("Scene Primitive Array not found: " + name); return NULL; } /** * Find and return the scene's child primitive array with the given name. * * @param name * Name of the child primitive. * @return * Pointer to the primitive with the given name or NULL if * no primitive exists with the given name. */ const ScenePathNameArray* SceneClass::getPathNameArray(const AString& name) const { for (std::vector::const_iterator iter = m_childObjects.begin(); iter != m_childObjects.end(); iter++) { const SceneObject* so = *iter; if (so->getName() == name) { const ScenePathNameArray* spa = dynamic_cast(so); if (spa != NULL) { return spa; } } } logMissing("Scene Path Name Array not found: " + name); return NULL; } /** * Find and return the scene's child class with the given name. * * @param name * Name of the child class. * @return * Pointer to the class with the given name or NULL if * no child class exists with the given name. */ const SceneClass* SceneClass::getClass(const AString& name) const { for (std::vector::const_iterator iter = m_childObjects.begin(); iter != m_childObjects.end(); iter++) { const SceneObject* so = *iter; const SceneClass* sc = dynamic_cast(so); if (sc != NULL) { if (sc->getName() == name) { return sc; } } } logMissing("Scene Class not found: " + name); return NULL; } /** * Find and return the scene's child class with the given name. * * @param name * Name of the child class. * @return * Pointer to the class with the given name or NULL if * no child class exists with the given name. */ SceneClass* SceneClass::getClass(const AString& name) { for (std::vector::iterator iter = m_childObjects.begin(); iter != m_childObjects.end(); iter++) { SceneObject* so = *iter; SceneClass* sc = dynamic_cast(so); if (sc != NULL) { if (sc->getName() == name) { return sc; } } } logMissing("Scene Class not found: " + name); return NULL; } /** * Find and return the scene's child map with integer key with the given name. * * @param name * Name of the child class. * @return * Pointer to the map with integer key with the given name or NULL if * no child map with integer key exists with the given name. */ const SceneObjectMapIntegerKey* SceneClass::getMapIntegerKey(const AString& name) const { const SceneObject* sceneObject = getObjectWithName(name); if (sceneObject != NULL) { const SceneObjectMapIntegerKey* smik = dynamic_cast(sceneObject); if (smik == NULL) { logMissing("SceneObjectMapIntegerKey not found: " + name); } return smik; } logMissing("SceneObjectMapIntegerKey not found: " + name); return NULL; } /** * Find and return the scene's child class array with the given name. * * @param name * Name of the child class array. * @return * Pointer to the class array with the given name or NULL if * no child class array exists with the given name. */ SceneClassArray* SceneClass::getClassArray(const AString& name) { for (std::vector::iterator iter = m_childObjects.begin(); iter != m_childObjects.end(); iter++) { SceneObject* so = *iter; SceneClassArray* sca = dynamic_cast(so); if (sca != NULL) { if (sca->getName() == name) { return sca; } } } logMissing("Scene Class not found: " + name); return NULL; } /** * Find and return the scene's child class array with the given name. * * @param name * Name of the child class array. * @return * Pointer to the class array with the given name or NULL if * no child class array exists with the given name. */ const SceneClassArray* SceneClass::getClassArray(const AString& name) const { for (std::vector::const_iterator iter = m_childObjects.begin(); iter != m_childObjects.end(); iter++) { const SceneObject* so = *iter; const SceneClassArray* sca = dynamic_cast(so); if (sca != NULL) { if (sca->getName() == name) { return sca; } } } logMissing("Scene Class Array not found: " + name); return NULL; } /** * @return Number of objects in the class. */ int32_t SceneClass::getNumberOfObjects() const { return m_childObjects.size(); } /** * @return Object at the given index. * @param indx * Index of the object. */ const SceneObject* SceneClass::getObjectAtIndex(const int32_t indx) const { CaretAssertVectorIndex(m_childObjects, indx); return m_childObjects[indx]; } /** * @return Object with the given name. Returns NULL * if an object with the given name is not found. * @param name * Name of object. */ const SceneObject* SceneClass::getObjectWithName(const AString& name) const { for (std::vector::const_iterator iter = m_childObjects.begin(); iter != m_childObjects.end(); iter++) { const SceneObject* so = *iter; if (so->getName() == name) { return so; } } return NULL; } /** * Log a missing object message to the Caret Logger. * This is done through a method so that the level * can easily be changed. * @param missionInfo * Information about missing object. */ void SceneClass::logMissing(const AString& missingInfo) const { CaretLogFine(missingInfo); } workbench-1.1.1/src/Scenes/SceneClass.h000066400000000000000000000315061255417355300177160ustar00rootroot00000000000000#ifndef __SCENE_CLASS__H_ #define __SCENE_CLASS__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "SceneObjectMapIntegerKey.h" #include "SceneObject.h" namespace caret { class SceneClassArray; class SceneEnumeratedType; class SceneObjectMapIntegerKey; class ScenePathName; class ScenePathNameArray; class ScenePrimitive; class ScenePrimitiveArray; class SceneClass : public SceneObject { public: SceneClass(const AString& name, const AString& className, const int32_t versionNumber); virtual ~SceneClass(); AString getClassName() const; int32_t getVersionNumber() const; void addBoolean(const AString& name, const bool value); void addBooleanArray(const AString& name, const bool values[], const int32_t arrayNumberOfElements); void addClass(SceneClass* sceneClass); AString getEnumeratedTypeValueAsString(const AString& name, const AString& defaultValue = "") const; /** * Add an enumerated type. * @param name * Name of enumerated type. * @param value * Enumerated value. */ template void addEnumeratedType(const AString& name, const ET value) { addEnumeratedType(name, T::toName(value)); } /** * Get an enumerated type value * @param name * Name of enumerated type. * @param defaultValue * Enumerated value returned if named value is not found. */ template ET getEnumeratedTypeValue(const AString& name, const ET defaultValue) const { const AString stringValue = getEnumeratedTypeValueAsString(name, T::toName(defaultValue)); bool valid = false; ET value = T::fromName(stringValue, &valid); return value; } /** * Add all elements in an enumerated type array. * @param name * Name of enumerated type array. * @param value * Array values. * @param arrayNumberOfElements * Number of values in the array. */ template void addEnumeratedTypeArray(const AString& name, const ET value[], const int32_t arrayNumberOfElements) { std::vector stringVector; for (int32_t i = 0; i < arrayNumberOfElements; i++) { const AString stringValue = T::toName(value[i]); stringVector.push_back(stringValue); } addEnumeratedTypeVector(name, stringVector); } /** * Restore all elements in an enumerated type array. * @param name * Name of enumerated type array. * @param value * Output array values. * @param arrayNumberOfElements * Number of values in the array. * @param defaultValue * Value used for missing array elements. */ template int32_t getEnumerateTypeArray(const AString& name, ET value[], const int32_t arrayNumberOfElements, const ET defaultValue) const { std::vector stringValues; stringValues.resize(arrayNumberOfElements); const int32_t numRestored = getEnumeratedTypeArrayValue(name, &stringValues[0], arrayNumberOfElements, T::toName(defaultValue)); for (int32_t i = 0; i < numRestored; i++) { bool valid = false; value[i] = T::fromName(stringValues[i], &valid); } return numRestored; } /** * Add elements from an enumerated type array but * only for the given tab indices using an * integer keyed map. * * @param name * Name of enumerated type array. * @param value * Array values. * @param tabIndices * Indices of the tabs for which the values are saved. */ template void addEnumeratedTypeArrayForTabIndices(const AString& name, const ET value[], const std::vector& tabIndices) { SceneObjectMapIntegerKey* sceneMap = new SceneObjectMapIntegerKey(name, SceneObjectDataTypeEnum::SCENE_ENUMERATED_TYPE); const int32_t numTabs = static_cast(tabIndices.size()); for (int32_t i = 0; i < numTabs; i++) { const int32_t tabIndex = tabIndices[i]; const AString stringValue = T::toName(value[tabIndex]); sceneMap->addEnumeratedType(tabIndex, stringValue); } addChild(sceneMap); } /** * Add elements from an enumerated type array but * only for the given tab indices using an * integer keyed map. * * @param name * Name of enumerated type array. * @param value * Array values. * @param tabIndices * Indices of the tabs for which the values are saved. */ template void getEnumerateTypeArrayForTabIndices(const AString& name, ET value[]) const { const SceneObjectMapIntegerKey* sceneMap = getMapIntegerKey(name); if (sceneMap == NULL) { return; } const std::vector mapKeys = sceneMap->getKeys(); const int numKeys = static_cast(mapKeys.size()); for (int32_t i = 0; i < numKeys; i++) { const int32_t key = mapKeys[i]; const AString valueString = sceneMap->enumeratedTypeValue(key); bool valid = false; value[key] = T::fromName(valueString, &valid); } } void addFloat(const AString& name, const float value); void addFloatArray(const AString& name, const float values[], const int32_t arrayNumberOfElements); void addInteger(const AString& name, const int32_t value); void addIntegerArray(const AString& name, const int32_t values[], const int32_t arrayNumberOfElements); void addString(const AString& name, const AString& value); void addPathName(const AString& name, const AString& value); void addStringArray(const AString& name, const AString values[], const int32_t arrayNumberOfElements); void addChild(SceneObject* sceneObject); bool getBooleanValue(const AString& name, const bool defaultValue = false) const; int32_t getBooleanArrayValue(const AString& name, bool values[], const int32_t arrayNumberOfElements, const bool defaultValue = false) const; const SceneClass* getClass(const AString& name) const; SceneClass* getClass(const AString& name); const SceneClassArray* getClassArray(const AString& name) const; SceneClassArray* getClassArray(const AString& name); float getFloatValue(const AString& name, const float defaultValue = 0.0) const; int32_t getFloatArrayValue(const AString& name, float values[], const int32_t arrayNumberOfElements, const float defaultValue = 0.0) const; int32_t getIntegerValue(const AString& name, const int32_t defaultValue = 0) const; int32_t getIntegerArrayValue(const AString& name, int32_t values[], const int32_t arrayNumberOfElements, const int32_t defaultValue = 0.0) const; AString getPathNameValue(const AString& name, const AString& defaultValue = "") const; AString getStringValue(const AString& name, const AString& defaultValue = "") const; int32_t getStringArrayValue(const AString& name, AString values[], const int32_t arrayNumberOfElements, const AString& defaultValue = "") const; const ScenePathNameArray* getPathNameArray(const AString& name) const; const ScenePrimitive* getPrimitive(const AString& name) const; const ScenePrimitiveArray* getPrimitiveArray(const AString& name) const; const ScenePathName* getPathName(const AString& name) const; const SceneObjectMapIntegerKey* getMapIntegerKey(const AString& name) const; int32_t getNumberOfObjects() const; const SceneObject* getObjectAtIndex(const int32_t indx) const; const SceneObject* getObjectWithName(const AString& name) const; // ADD_NEW_METHODS_HERE // private: // and are not used // void addBooleanVector(const AString& name, // const std::vector& values); // // void addClassArray(const AString& name, // SceneClass* values[], // const int32_t arrayNumberOfElements); // // void addEnumeratedTypeArray(const AString& name, // const AString value[], // const int32_t arrayNumberOfElements); private: SceneClass(const SceneClass&); SceneClass& operator=(const SceneClass&); void addEnumeratedType(const AString& name, const AString& value); int32_t getEnumeratedTypeArrayValue(const AString& name, AString values[], const int32_t arrayNumberOfElements, const AString& defaultValue) const; void addEnumeratedTypeVector(const AString& name, const std::vector& value); void logMissing(const AString& missingInfo) const; AString m_className; const int32_t m_versionNumber; std::vector m_childObjects; // ADD_NEW_MEMBERS_HERE }; #ifdef __SCENE_CLASS_DECLARE__ // #endif // __SCENE_CLASS_DECLARE__ } // namespace #endif //__SCENE_CLASS__H_ workbench-1.1.1/src/Scenes/SceneClassArray.cxx000066400000000000000000000076161255417355300212750ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SCENE_CLASS_ARRAY_DECLARE__ #include "SceneClassArray.h" #undef __SCENE_CLASS_ARRAY_DECLARE__ #include "CaretAssert.h" #include "SceneClass.h" using namespace caret; /** * \class caret::SceneClassArray * \brief Stores an array of scene classes. * \ingroup Scene * * See the documentation in the class Scene for how to use the Scene system. */ /** * Constructor. * * @param name * Name of the array. * @param values * Values in the array. * @param numberOfArrayElements * Number of elements in the array. */ SceneClassArray::SceneClassArray(const AString& name, SceneClass* values[], const int32_t numberOfArrayElements) : SceneObjectArray(name, SceneObjectDataTypeEnum::SCENE_CLASS, numberOfArrayElements) { m_values.resize(numberOfArrayElements); for (int32_t i = 0; i < numberOfArrayElements; i++) { m_values[i] = values[i]; } } /** * Constructor. * * @param name * Name of the array. * @param values * Vector containing values for the array. */ SceneClassArray::SceneClassArray(const AString& name, const std::vector& values) : SceneObjectArray(name, SceneObjectDataTypeEnum::SCENE_CLASS, values.size()) { m_values = values; } /** * Constructor that creates an array of NULL values. * * @param name * Name of the array. * @param numberOfArrayElements * Number of elements in the array. */ SceneClassArray::SceneClassArray(const AString& name, const int numberOfArrayElements) : SceneObjectArray(name, SceneObjectDataTypeEnum::SCENE_CLASS, numberOfArrayElements) { m_values.resize(numberOfArrayElements); std::fill(m_values.begin(), m_values.end(), (SceneClass*)NULL); } /** * Destructor. */ SceneClassArray::~SceneClassArray() { for (std::vector::iterator iter = m_values.begin(); iter != m_values.end(); iter++) { delete *iter; } m_values.clear(); } /** * Set the class for an array index. * @param arrayIndex * Index of element. * @param sceneClass * New value for array element. */ void SceneClassArray::setClassAtIndex(const int32_t arrayIndex, SceneClass* sceneClass) { CaretAssertVectorIndex(m_values, arrayIndex); m_values[arrayIndex] = sceneClass; } ///** // * Get the element for a given array index. // * @param arrayIndex // * Index of array element. // * @return // * Class at the given array index. // */ //SceneClass* //SceneClassArray::getValue(const int32_t arrayIndex) //{ // CaretAssertVectorIndex(m_values, arrayIndex); // return m_values[arrayIndex]; //} /** * Get the element for a given array index. * @param arrayIndex * Index of array element. * @return * Class at the given array index. */ const SceneClass* SceneClassArray::getClassAtIndex(const int32_t arrayIndex) const { CaretAssertVectorIndex(m_values, arrayIndex); return m_values[arrayIndex]; } workbench-1.1.1/src/Scenes/SceneClassArray.h000066400000000000000000000041561255417355300207160ustar00rootroot00000000000000#ifndef __SCENE_CLASS_ARRAY__H_ #define __SCENE_CLASS_ARRAY__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "SceneObjectArray.h" namespace caret { class SceneClass; class SceneClassArray : public SceneObjectArray { public: SceneClassArray(const AString& name, SceneClass* values[], const int32_t numberOfArrayElements); SceneClassArray(const AString& name, const std::vector& values); SceneClassArray(const AString& name, const int numberOfArrayElements); virtual ~SceneClassArray(); void setClassAtIndex(const int32_t arrayIndex, SceneClass* sceneClass); const SceneClass* getClassAtIndex(const int32_t arrayIndex) const; private: SceneClassArray(const SceneClassArray&); SceneClassArray& operator=(const SceneClassArray&); public: // ADD_NEW_METHODS_HERE private: // ADD_NEW_MEMBERS_HERE std::vector m_values; }; #ifdef __SCENE_CLASS_ARRAY_DECLARE__ // #endif // __SCENE_CLASS_ARRAY_DECLARE__ } // namespace #endif //__SCENE_CLASS_ARRAY__H_ workbench-1.1.1/src/Scenes/SceneClassAssistant.cxx000066400000000000000000001301701255417355300221600ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SCENE_CLASS_ASSISTANT_DECLARE__ #include "SceneClassAssistant.h" #undef __SCENE_CLASS_ASSISTANT_DECLARE__ #include "CaretAssert.h" #include "SceneAttributes.h" #include "SceneClass.h" #include "SceneClassArray.h" #include "SceneObjectMapIntegerKey.h" #include "ScenePrimitive.h" #include "SceneableInterface.h" using namespace caret; /** * \class caret::SceneClassAssistant * \brief Assists a class with restoring and saving its data to a scene. * \ingroup Scene * * See the documentation in the class Scene for how to use the Scene system. * * Assists with the restoration and saving of data for a SceneClass. * Rather than the scene class making explicit calls to save and restore * each of its members, an assistant can be created and setup with the * members of the class. The assistant will then handle restoration * and saving of the data. */ /** * Constructor. */ SceneClassAssistant::SceneClassAssistant() : CaretObject() { } /** * Destructor. */ SceneClassAssistant::~SceneClassAssistant() { for (DataStorageIterator iter = m_dataStorage.begin(); iter != m_dataStorage.end(); iter++) { delete *iter; } } /** * Add a float member. * * Note: If restoring and the member is not found, the default * value used will be the value at the pointer. * * @param name * Name of member. * @param floatAddress * Address of the member. */ void SceneClassAssistant::add(const AString& name, float* floatAddress) { CaretAssert(name.isEmpty() == false); CaretAssert(floatAddress); FloatData* fd = new FloatData(name, floatAddress, *floatAddress); m_dataStorage.push_back(fd); } /** * Add an integer member. * * Note: If restoring and the member is not found, the default * value used will be the value at the pointer. * * @param name * Name of member. * @param integerAddress * Address of the member. */ void SceneClassAssistant::add(const AString& name, int32_t* integerAddress) { CaretAssert(name.isEmpty() == false); CaretAssert(integerAddress); IntegerData* id = new IntegerData(name, integerAddress, *integerAddress); m_dataStorage.push_back(id); } /** * Add a boolean member. * * Note: If restoring and the member is not found, the default * value used will be the value at the pointer. * * @param name * Name of member. * @param booleanAddress * Address of the member. */ void SceneClassAssistant::add(const AString& name, bool* booleanAddress) { CaretAssert(name.isEmpty() == false); CaretAssert(booleanAddress); BooleanData* bd = new BooleanData(name, booleanAddress, *booleanAddress); m_dataStorage.push_back(bd); } /** * Add a string member. * * Note: If restoring and the member is not found, the default * value used will be the value at the pointer. * * @param name * Name of member. * @param stringAddress * Address of the member. */ void SceneClassAssistant::add(const AString& name, AString* stringAddress) { CaretAssert(name.isEmpty() == false); CaretAssert(stringAddress); StringData* sd = new StringData(name, stringAddress, *stringAddress); m_dataStorage.push_back(sd); } /** * Add a scene class. * @param name * Name of class instance. * @param className * Name of the class. * @param sceneClass * Handle (pointer to the pointer) that points to the class. * If the value of the pointer (*sceneClass) is NULL, then * the scene class is NOT added to the scene. This method * is best used when a member of a class is a pointer to * a class that implementes the SceneableInterface. */ //void //SceneClassAssistant::add(const AString& name, // const AString& className, // SceneableInterface** sceneClass) //{ // CaretAssert(sceneClass); // // ClassData* cd = new ClassData(name, // className, // sceneClass); // m_dataStorage.push_back(cd); //} /** * Add a scene class. * @param name * Name of class instance. * @param className * Name of the class. * @param sceneClass * Pointer to the class. This method is best used when * a member of the class is not a pointer and implements * the SceneableInterface. */ void SceneClassAssistant::add(const AString& name, const AString& className, SceneableInterface* sceneClass) { CaretAssert(name.isEmpty() == false); CaretAssert(sceneClass); ClassData* cd = new ClassData(name, className, sceneClass); m_dataStorage.push_back(cd); } /** * Add a boolean array member. * @param name * Name of member. * @param booleanArray * The array (pointer to first element) * @param numberOfArrayElements * Number of elements in the array. * @param defaultValue * Value used if the member is not found when restoring scene. */ void SceneClassAssistant::addArray(const AString& name, bool* booleanArray, const int32_t numberOfElements, const bool defaultValue) { CaretAssert(name.isEmpty() == false); CaretAssert(booleanArray); CaretAssert(numberOfElements >= 0); BooleanArrayData* bad = new BooleanArrayData(name, booleanArray, numberOfElements, defaultValue); m_dataStorage.push_back(bad); } /** * Add a float array member. * @param name * Name of member. * @param floatArray * The array (pointer to first element) * @param numberOfArrayElements * Number of elements in the array. * @param defaultValue * Value used if the member is not found when restoring scene. */ void SceneClassAssistant::addArray(const AString& name, float* floatArray, const int32_t numberOfElements, const float defaultValue) { CaretAssert(name.isEmpty() == false); CaretAssert(floatArray); CaretAssert(numberOfElements >= 0); FloatArrayData* fad = new FloatArrayData(name, floatArray, numberOfElements, defaultValue); m_dataStorage.push_back(fad); } /** * Add an integer array member. * @param name * Name of member. * @param integerArray * The array (pointer to first element) * @param numberOfArrayElements * Number of elements in the array. * @param defaultValue * Value used if the member is not found when restoring scene. */ void SceneClassAssistant::addArray(const AString& name, int32_t* integerArray, const int32_t numberOfElements, const int32_t defaultValue) { CaretAssert(name.isEmpty() == false); CaretAssert(integerArray); CaretAssert(numberOfElements >= 0); IntegerArrayData* iad = new IntegerArrayData(name, integerArray, numberOfElements, defaultValue); m_dataStorage.push_back(iad); } /** * Add a string array member. * @param name * Name of member. * @param stringArray * The array (pointer to first element) * @param numberOfArrayElements * Number of elements in the array. * @param defaultValue * Value used if the member is not found when restoring scene. */ void SceneClassAssistant::addArray(const AString& name, AString* stringArray, const int32_t numberOfElements, const AString& defaultValue) { CaretAssert(name.isEmpty() == false); CaretAssert(stringArray); CaretAssert(numberOfElements >= 0); StringArrayData* sad = new StringArrayData(name, stringArray, numberOfElements, defaultValue); m_dataStorage.push_back(sad); } /** * Add a sceneable interface (classes) array member. * @param name * Name of member. * @param sceneableInterfaceArray * The array (pointer to first element) * @param numberOfArrayElements * Number of elements in the array. */ //void //SceneClassAssistant::addArray(const AString& name, // SceneableInterface* sceneableInterfaceArray[], // const int32_t numberOfElements) //{ // ClassArrayData* cad = new ClassArrayData(name, // sceneableInterfaceArray, // numberOfElements); // m_dataStorage.push_back(cad); //} /** * Add a tab-indexed boolean array. The array must * contain BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS elements. * Depending upon how he scene is saved, all array elements or * just those for specific tabs will be saved to the scene. * @param name * Name of member. * @param booleanArray * The array (pointer to first element. */ void SceneClassAssistant::addTabIndexedBooleanArray(const AString& name, bool* booleanArray) { CaretAssert(name.isEmpty() == false); CaretAssert(booleanArray); BooleanTabIndexArrayMapData* bad = new BooleanTabIndexArrayMapData(name, booleanArray); m_dataStorage.push_back(bad); } /** * Add a tab-indexed integer array. The array must * contain BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS elements. * Depending upon how he scene is saved, all array elements or * just those for specific tabs will be saved to the scene. * @param name * Name of member. * @param integerArray * The array (pointer to first element. */ void SceneClassAssistant::addTabIndexedIntegerArray(const AString& name, int32_t* integerArray) { CaretAssert(name.isEmpty() == false); CaretAssert(integerArray); IntegerTabIndexArrayMapData* bad = new IntegerTabIndexArrayMapData(name, integerArray); m_dataStorage.push_back(bad); } /** * Add a tab-indexed float array. The array must * contain BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS elements. * Depending upon how he scene is saved, all array elements or * just those for specific tabs will be saved to the scene. * @param name * Name of member. * @param floatArray * The array (pointer to first element. */ void SceneClassAssistant::addTabIndexedFloatArray(const AString& name, float* floatArray) { CaretAssert(name.isEmpty() == false); CaretAssert(floatArray); FloatTabIndexArrayMapData* bad = new FloatTabIndexArrayMapData(name, floatArray); m_dataStorage.push_back(bad); } /** * Restore the members of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * SceneClass containing the state that was previously * saved and should be restored. */ void SceneClassAssistant::restoreMembers(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) { for (DataStorageIterator iter = m_dataStorage.begin(); iter != m_dataStorage.end(); iter++) { Data* data = *iter; data->restore(*sceneAttributes, *sceneClass); } } /** * Save the members of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ void SceneClassAssistant::saveMembers(const SceneAttributes* sceneAttributes, SceneClass* sceneClass) { for (DataStorageIterator iter = m_dataStorage.begin(); iter != m_dataStorage.end(); iter++) { Data* data = *iter; data->save(*sceneAttributes, *sceneClass); } } /* ========================================================================= */ /** * \class caret::SceneClassAssistant::Data * \brief Base class for data added to a scene class. * \ingroup Scene * * See the documentation in the class Scene for how to use the Scene system. */ SceneClassAssistant::Data::Data(const AString& name) : m_name(name) { } /* ========================================================================= */ /** * \class caret::SceneClassAssistant::FloatData * \brief Float data added to a scene class. */ /** * Constructor. * @param name * Name of data. * @param dataPointer * Pointer to data. * @param defaultValue * Default value used when restoring and data with name not found. */ SceneClassAssistant::FloatData::FloatData(const AString& name, float* dataPointer, const float defaultValue) : Data(name), m_dataPointer(dataPointer), m_defaultValue(defaultValue) { } /** * Restore the data from the scene. * @param sceneAttributes * Attributes of the scene. * @param sceneClass * Class from which data is restored. */ void SceneClassAssistant::FloatData::restore(const SceneAttributes& /*sceneAttributes*/, const SceneClass& sceneClass) { *m_dataPointer = sceneClass.getFloatValue(m_name, m_defaultValue); } /** * Save the data to the scene. * @param sceneAttributes * Attributes for the scene. * @param sceneClass * Class to which data is saved. */ void SceneClassAssistant::FloatData::save(const SceneAttributes& /*sceneAttributes*/, SceneClass& sceneClass) { sceneClass.addFloat(m_name, *m_dataPointer); } /* ========================================================================= */ /** * \class caret::SceneClassAssistant::IntegerData * \brief Integer data added to a scene class. * \ingroup Scene * * See the documentation in the class Scene for how to use the Scene system. */ /** * Constructor. * @param name * Name of data. * @param dataPointer * Pointer to data. * @param defaultValue * Default value used when restoring and data with name not found. */ SceneClassAssistant::IntegerData::IntegerData(const AString& name, int32_t* dataPointer, const int32_t defaultValue) : Data(name), m_dataPointer(dataPointer), m_defaultValue(defaultValue) { } /** * Restore the data from the scene. * @param sceneAttributes * Attributes for the scene. * @param sceneClass * Class from which data is restored. */ void SceneClassAssistant::IntegerData::restore(const SceneAttributes& /*sceneAttributes*/, const SceneClass& sceneClass) { *m_dataPointer = sceneClass.getIntegerValue(m_name, m_defaultValue); } /** * Save the data to the scene. * @param sceneAttributes * Attributes for the scene. * @param sceneClass * Class to which data is saved. */ void SceneClassAssistant::IntegerData::save(const SceneAttributes& /*sceneAttributes*/, SceneClass& sceneClass) { sceneClass.addInteger(m_name, *m_dataPointer); } /* ========================================================================= */ /** * \class caret::SceneClassAssistant::BooleanData * \brief Boolean data added to a scene class. * \ingroup Scene * * See the documentation in the class Scene for how to use the Scene system. */ /** * Constructor. * @param name * Name of data. * @param dataPointer * Pointer to data. * @param defaultValue * Default value used when restoring and data with name not found. */ SceneClassAssistant::BooleanData::BooleanData(const AString& name, bool* dataPointer, const bool defaultValue) : Data(name), m_dataPointer(dataPointer), m_defaultValue(defaultValue) { } /** * Restore the data from the scene. * @param sceneAttributes * Attributes for the scene. * @param sceneClass * Class from which data is restored. */ void SceneClassAssistant::BooleanData::restore(const SceneAttributes& /*sceneAttributes*/, const SceneClass& sceneClass) { *m_dataPointer = sceneClass.getBooleanValue(m_name, m_defaultValue); } /** * Save the data to the scene. * @param sceneAttributes * Attributes for the scene. * @param sceneClass * Class to which data is saved. */ void SceneClassAssistant::BooleanData::save(const SceneAttributes& /*sceneAttributes*/, SceneClass& sceneClass) { sceneClass.addBoolean(m_name, *m_dataPointer); } /* ========================================================================= */ /** * \class caret::SceneClassAssistant::StringData * \brief String data added to a scene class. * \ingroup Scene * * See the documentation in the class Scene for how to use the Scene system. */ /** * Constructor. * @param name * Name of data. * @param dataPointer * Pointer to data. * @param defaultValue * Default value used when restoring and data with name not found. */ SceneClassAssistant::StringData::StringData(const AString& name, AString* dataPointer, const AString& defaultValue) : Data(name), m_dataPointer(dataPointer), m_defaultValue(defaultValue) { } /** * Restore the data from the scene. * @param sceneAttributes * Attributes for the scene. * @param sceneClass * Class from which data is restored. */ void SceneClassAssistant::StringData::restore(const SceneAttributes& /*sceneAttributes*/, const SceneClass& sceneClass) { *m_dataPointer = sceneClass.getStringValue(m_name, m_defaultValue); } /** * Save the data to the scene. * @param sceneAttributes * Attributes for the scene. * @param sceneClass * Class to which data is saved. */ void SceneClassAssistant::StringData::save(const SceneAttributes& /*sceneAttributes*/, SceneClass& sceneClass) { sceneClass.addString(m_name, *m_dataPointer); } /* ========================================================================= */ /** * \class caret::SceneClassAssistant::TabIndexArrayMapData * \brief Base class for arrays that are index by a tab index. * \ingroup Scene * * See the documentation in the class Scene for how to use the Scene system. */ SceneClassAssistant::TabIndexArrayMapData::TabIndexArrayMapData(const AString& name) : Data(name) { } /* ========================================================================= */ /** * \class caret::SceneClassAssistant::BooleanTabIndexArrayMapData * \brief Boolean array that is indexed by a tab index. */ /** * Constructor. * @param name * Name of boolean array. * @param booleanArray * Address fo the array that is indexed by tab and contains * BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS elemnts. */ SceneClassAssistant::BooleanTabIndexArrayMapData::BooleanTabIndexArrayMapData(const AString& name, bool* booleanArray) : TabIndexArrayMapData(name), m_booleanArray(booleanArray) { } /** * Restore the data from the scene. * @param sceneAttributes * Attributes for the scene. * @param sceneClass * Class from which data is restored. */ void SceneClassAssistant::BooleanTabIndexArrayMapData::restore(const SceneAttributes& /*sceneAttributes*/, const SceneClass& sceneClass) { const SceneObjectMapIntegerKey* sceneMap = sceneClass.getMapIntegerKey(m_name); if (sceneMap != NULL) { const std::map& dataMap = sceneMap->getMap(); for (std::map::const_iterator iter = dataMap.begin(); iter != dataMap.end(); iter++) { const int32_t tabIndex = iter->first; const ScenePrimitive* primitive = dynamic_cast(iter->second); m_booleanArray[tabIndex] = primitive->booleanValue(); } // const std::vector keys = sceneMap->getKeys(); // const int32_t numKeys = static_cast(keys.size()); // for (int32_t i = 0; i < numKeys; i++) { // const int32_t tabIndex = keys[i]; // m_booleanArray[tabIndex] = sceneMap->booleanValue(tabIndex); // } } } /** * Save the data to the scene. * @param sceneAttributes * Attributes for the scene. * @param sceneClass * Class into which data is stored. */ void SceneClassAssistant::BooleanTabIndexArrayMapData::save(const SceneAttributes& sceneAttributes, SceneClass& sceneClass) { const std::vector tabIndices = sceneAttributes.getIndicesOfTabsForSavingToScene(); const int32_t numTabIndices = static_cast(tabIndices.size()); SceneObjectMapIntegerKey* sceneMap = new SceneObjectMapIntegerKey(m_name, SceneObjectDataTypeEnum::SCENE_BOOLEAN); for (int32_t i = 0; i < numTabIndices; i++) { const int32_t tabIndex = tabIndices[i]; sceneMap->addBoolean(tabIndex, m_booleanArray[tabIndex]); } sceneClass.addChild(sceneMap); } /* ========================================================================= */ /** * \class caret::SceneClassAssistant::IntegerTabIndexArrayMapData * \brief Integer array that is indexed by a tab index. * \ingroup Scene * * See the documentation in the class Scene for how to use the Scene system. */ /** * Constructor. * @param name * Name of integer array. * @param integerArray * Address of the array that is indexed by tab and contains * BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS elemnts. */ SceneClassAssistant::IntegerTabIndexArrayMapData::IntegerTabIndexArrayMapData(const AString& name, int32_t* integerArray) : TabIndexArrayMapData(name), m_integerArray(integerArray) { } /** * Restore the data from the scene. * @param sceneAttributes * Attributes for the scene. * @param sceneClass * Class from which data is restored. */ void SceneClassAssistant::IntegerTabIndexArrayMapData::restore(const SceneAttributes& /*sceneAttributes*/, const SceneClass& sceneClass) { const SceneObjectMapIntegerKey* sceneMap = sceneClass.getMapIntegerKey(m_name); if (sceneMap != NULL) { const std::map& dataMap = sceneMap->getMap(); for (std::map::const_iterator iter = dataMap.begin(); iter != dataMap.end(); iter++) { const int32_t tabIndex = iter->first; const ScenePrimitive* primitive = dynamic_cast(iter->second); m_integerArray[tabIndex] = primitive->integerValue(); } } } /** * Save the data to the scene. * @param sceneAttributes * Attributes for the scene. * @param sceneClass * Class into which data is stored. */ void SceneClassAssistant::IntegerTabIndexArrayMapData::save(const SceneAttributes& sceneAttributes, SceneClass& sceneClass) { const std::vector tabIndices = sceneAttributes.getIndicesOfTabsForSavingToScene(); const int32_t numTabIndices = static_cast(tabIndices.size()); SceneObjectMapIntegerKey* sceneMap = new SceneObjectMapIntegerKey(m_name, SceneObjectDataTypeEnum::SCENE_INTEGER); for (int32_t i = 0; i < numTabIndices; i++) { const int32_t tabIndex = tabIndices[i]; sceneMap->addInteger(tabIndex, m_integerArray[tabIndex]); } sceneClass.addChild(sceneMap); } /* ========================================================================= */ /** * \class caret::SceneClassAssistant::FloatTabIndexArrayMapData * \brief Float array that is indexed by a tab index. * \ingroup Scene * * See the documentation in the class Scene for how to use the Scene system. */ /** * Constructor. * @param name * Name of float array. * @param floatArray * Address of the array that is indexed by tab and contains * BrainConstants::MAXIMUM_NUMBER_OF_BROWSER_TABS elemnts. */ SceneClassAssistant::FloatTabIndexArrayMapData::FloatTabIndexArrayMapData(const AString& name, float* floatArray) : TabIndexArrayMapData(name), m_floatArray(floatArray) { } /** * Restore the data from the scene. * @param sceneAttributes * Attributes for the scene. * @param sceneClass * Class from which data is restored. */ void SceneClassAssistant::FloatTabIndexArrayMapData::restore(const SceneAttributes& /*sceneAttributes*/, const SceneClass& sceneClass) { const SceneObjectMapIntegerKey* sceneMap = sceneClass.getMapIntegerKey(m_name); if (sceneMap != NULL) { const std::map& dataMap = sceneMap->getMap(); for (std::map::const_iterator iter = dataMap.begin(); iter != dataMap.end(); iter++) { const int32_t tabIndex = iter->first; const ScenePrimitive* primitive = dynamic_cast(iter->second); m_floatArray[tabIndex] = primitive->floatValue(); } } } /** * Save the data to the scene. * @param sceneAttributes * Attributes for the scene. * @param sceneClass * Class into which data is stored. */ void SceneClassAssistant::FloatTabIndexArrayMapData::save(const SceneAttributes& sceneAttributes, SceneClass& sceneClass) { const std::vector tabIndices = sceneAttributes.getIndicesOfTabsForSavingToScene(); const int32_t numTabIndices = static_cast(tabIndices.size()); SceneObjectMapIntegerKey* sceneMap = new SceneObjectMapIntegerKey(m_name, SceneObjectDataTypeEnum::SCENE_FLOAT); for (int32_t i = 0; i < numTabIndices; i++) { const int32_t tabIndex = tabIndices[i]; sceneMap->addFloat(tabIndex, m_floatArray[tabIndex]); } sceneClass.addChild(sceneMap); } /* ========================================================================= */ /** * \class caret::SceneClassAssistant::BooleanVectorData * \brief Boolean vector added to a scene class. * \ingroup Scene * * See the documentation in the class Scene for how to use the Scene system. */ SceneClassAssistant::ArrayData::ArrayData(const AString& name, const int32_t numberOfArrayElements) : Data(name), m_numberOfArrayElements(numberOfArrayElements) { } /* ========================================================================= */ /** * \class caret::SceneClassAssistant::BooleanArrayData * \brief Boolean array added to a scene class. */ SceneClassAssistant::BooleanArrayData::BooleanArrayData(const AString& name, bool* booleanArray, const int32_t numberOfArrayElements, const bool defaultValue) : ArrayData(name, numberOfArrayElements), m_booleanArray(booleanArray), m_defaultValue(defaultValue) { } /** * Restore the data from the scene. * @param sceneAttributes * Attributes for the scene. * @param sceneClass * Class from which data is restored. */ void SceneClassAssistant::BooleanArrayData::restore(const SceneAttributes& /*sceneAttributes*/, const SceneClass& sceneClass) { if (m_numberOfArrayElements > 0) { sceneClass.getBooleanArrayValue(m_name, m_booleanArray, m_numberOfArrayElements, m_defaultValue); } } /** * Restore the data from the scene. * @param sceneAttributes * Attributes for the scene. * @param sceneClass * Class from which data is restored. */ void SceneClassAssistant::BooleanArrayData::save(const SceneAttributes& /*sceneAttributes*/, SceneClass& sceneClass) { if (m_numberOfArrayElements > 0) { sceneClass.addBooleanArray(m_name, m_booleanArray, m_numberOfArrayElements); } } /* ========================================================================= */ /** * \class caret::SceneClassAssistant::IntegerArrayData * \brief Boolean array added to a scene class. * \ingroup Scene * * See the documentation in the class Scene for how to use the Scene system. */ SceneClassAssistant::IntegerArrayData::IntegerArrayData(const AString& name, int32_t* integerArray, const int32_t numberOfArrayElements, const int32_t defaultValue) : ArrayData(name, numberOfArrayElements), m_integerArray(integerArray), m_defaultValue(defaultValue) { } /** * Restore the data from the scene. * @param sceneAttributes * Attributes for the scene. * @param sceneClass * Class from which data is restored. */ void SceneClassAssistant::IntegerArrayData::restore(const SceneAttributes& /*sceneAttributes*/, const SceneClass& sceneClass) { if (m_numberOfArrayElements > 0) { sceneClass.getIntegerArrayValue(m_name, m_integerArray, m_numberOfArrayElements, m_defaultValue); } } /** * Restore the data from the scene. * @param sceneAttributes * Attributes for the scene. * @param sceneClass * Class from which data is restored. */ void SceneClassAssistant::IntegerArrayData::save(const SceneAttributes& /*sceneAttributes*/, SceneClass& sceneClass) { if (m_numberOfArrayElements > 0) { sceneClass.addIntegerArray(m_name, m_integerArray, m_numberOfArrayElements); } } /* ========================================================================= */ /** * \class caret::SceneClassAssistant::FloatArrayData * \brief Boolean array added to a scene class. * \ingroup Scene * * See the documentation in the class Scene for how to use the Scene system. */ SceneClassAssistant::FloatArrayData::FloatArrayData(const AString& name, float* floatArray, const int32_t numberOfArrayElements, const float defaultValue) : ArrayData(name, numberOfArrayElements), m_floatArray(floatArray), m_defaultValue(defaultValue) { } /** * Restore the data from the scene. * @param sceneAttributes * Attributes for the scene. * @param sceneClass * Class from which data is restored. */ void SceneClassAssistant::FloatArrayData::restore(const SceneAttributes& /*sceneAttributes*/, const SceneClass& sceneClass) { if (m_numberOfArrayElements > 0) { sceneClass.getFloatArrayValue(m_name, m_floatArray, m_numberOfArrayElements, m_defaultValue); } } /** * Restore the data from the scene. * @param sceneAttributes * Attributes for the scene. * @param sceneClass * Class from which data is restored. */ void SceneClassAssistant::FloatArrayData::save(const SceneAttributes& /*sceneAttributes*/, SceneClass& sceneClass) { if (m_numberOfArrayElements > 0) { sceneClass.addFloatArray(m_name, m_floatArray, m_numberOfArrayElements); } } /* ========================================================================= */ /** * \class caret::SceneClassAssistant::StringArrayData * \brief Boolean array added to a scene class. * \ingroup Scene * * See the documentation in the class Scene for how to use the Scene system. */ SceneClassAssistant::StringArrayData::StringArrayData(const AString& name, AString* stringArray, const int32_t numberOfArrayElements, const AString defaultValue) : ArrayData(name, numberOfArrayElements), m_stringArray(stringArray), m_defaultValue(defaultValue) { } /** * Restore the data from the scene. * @param sceneAttributes * Attributes for the scene. * @param sceneClass * Class from which data is restored. */ void SceneClassAssistant::StringArrayData::restore(const SceneAttributes& /*sceneAttributes*/, const SceneClass& sceneClass) { if (m_numberOfArrayElements > 0) { sceneClass.getStringArrayValue(m_name, m_stringArray, m_numberOfArrayElements, m_defaultValue); } } /** * Restore the data from the scene. * @param sceneAttributes * Attributes for the scene. * @param sceneClass * Class from which data is restored. */ void SceneClassAssistant::StringArrayData::save(const SceneAttributes& /*sceneAttributes*/, SceneClass& sceneClass) { if (m_numberOfArrayElements > 0) { sceneClass.addStringArray(m_name, m_stringArray, m_numberOfArrayElements); } } /* ========================================================================= */ /** * \class caret::SceneClassAssistant::ClassArrayData * \brief Scene class added to a scene class. * \ingroup Scene * * See the documentation in the class Scene for how to use the Scene system. */ //SceneClassAssistant::ClassArrayData::ClassArrayData(const AString& name, // SceneableInterface* sceneInterfaces[], // const int32_t numberOfArrayElements) //: ArrayData(name, // numberOfArrayElements), //m_sceneInterfaces(sceneInterfaces) //{ // //} /** * Restore the data from the scene. * @param sceneAttributes * Attributes for the scene. * @param sceneClass * Class from which data is restored. */ //void //SceneClassAssistant::ClassArrayData::restore(const SceneAttributes& sceneAttributes, // const SceneClass& sceneClass) //{ // const SceneClassArray* classArray = sceneClass.getClassArray(m_name); // if (classArray != NULL) { // const int32_t numElem = std::min(m_numberOfArrayElements, // classArray->getNumberOfArrayElements()); // for (int32_t i = 0; i < numElem; i++) { // m_sceneInterfaces[i]->restoreFromScene(&sceneAttributes, // classArray->getClassAtIndex(i)); // // } // } //} /** * Restore the data from the scene. * @param sceneAttributes * Attributes for the scene. * @param sceneClass * Class from which data is restored. */ //void SceneClassAssistant::ClassArrayData::save(const SceneAttributes& sceneAttributes, // SceneClass& sceneClass) //{ // std::vector elementVector; // // for (int32_t i = 0; i < m_numberOfArrayElements; i++) { // const AString elemName = (m_name // + "[" // + AString::number(i) // + "]"); // SceneClass* arrayElemClass = m_sceneInterfaces[i]->saveToScene(&sceneAttributes, // elemName); // elementVector.push_back(arrayElemClass); // } // SceneClassArray* classArray = new SceneClassArray(m_name, // elementVector); // sceneClass.addChild(classArray); //} /* ========================================================================= */ /** * \class caret::SceneClassAssistant::BooleanVectorData * \brief Boolean vector added to a scene class. * \ingroup Scene * * See the documentation in the class Scene for how to use the Scene system. */ /** * Constructor. * @param name * Name of instance. * @param booleanVectorPointer * Pointer to vector containing boolean data * @param defaultValue * Default value used when restoring and data with name not found. */ //SceneClassAssistant::BooleanVectorData::BooleanVectorData(const AString& name, // std::vector& booleanVectorReference, // const bool defaultValue) //: Data(name), // m_booleanVectorReference(booleanVectorReference), // m_defaultValue(defaultValue) //{ // //} /** * Restore the data from the scene. * @param sceneAttributes * Attributes for the scene. * @param sceneClass * Class from which data is restored. */ //void //SceneClassAssistant::BooleanVectorData::restore(const SceneAttributes& /*sceneAttributes*/, // const SceneClass& sceneClass) //{ // const int numElements = static_cast(m_booleanVectorReference.size()); // if (numElements > 0) { // bool* boolArray = new bool[numElements]; // // sceneClass.getBooleanArrayValue(m_name, // boolArray, // numElements, // m_defaultValue); // // for (int32_t i = 0; i < numElements; i++) { // m_booleanVectorReference[i] = boolArray[i]; // } // delete[] boolArray; // } //} // //void //SceneClassAssistant::BooleanVectorData::save(const SceneAttributes& /*sceneAttributes*/, // SceneClass& sceneClass) //{ // /* // * std::vector is special case of vector. Cannot use &boolVector[0] for pointer address // */ // const int numElements = static_cast(m_booleanVectorReference.size()); // if (numElements > 0) { // bool* boolArray = new bool[numElements]; // for (int32_t i = 0; i < numElements; i++) { // boolArray[i] = m_booleanVectorReference[i]; // } // sceneClass.addBooleanArray(m_name, // boolArray, // numElements); // delete[] boolArray; // } //} /* ========================================================================= */ /** * \class caret::SceneClassAssistant::ClassData * \brief Class added to a scene class. * \ingroup Scene * * See the documentation in the class Scene for how to use the Scene system. */ /** * Constructor. * @param name * Name of instance. * @param className * Name of class * @param sceneClassHandle * Handle (Pointer to pointer) of the class instance. */ SceneClassAssistant::ClassData::ClassData(const AString& name, const AString& className, SceneableInterface** sceneClassHandle) : Data(name), m_className(className), m_sceneClassHandle(sceneClassHandle), m_sceneClassPointer(NULL) { } /** * Constructor. * @param name * Name of instance. * @param className * Name of class * @param sceneClassPointer * Handle (Pointer to pointer) of the class instance. */ SceneClassAssistant::ClassData::ClassData(const AString& name, const AString& className, SceneableInterface* sceneClassPointer) : Data(name), m_className(className), m_sceneClassHandle(NULL), m_sceneClassPointer(sceneClassPointer) { } /** * Restore the data from the scene. * @param sceneAttributes * Attributes for the scene. * @param sceneClass * Parent scene class from which data is restored. */ void SceneClassAssistant::ClassData::restore(const SceneAttributes& sceneAttributes, const SceneClass& sceneClass) { const SceneClass* mySceneClass = sceneClass.getClass(m_name); if (mySceneClass != NULL) { SceneableInterface* myClassInstance = ((m_sceneClassPointer != NULL) ? m_sceneClassPointer : *m_sceneClassHandle); if (myClassInstance != NULL) { myClassInstance->restoreFromScene(&sceneAttributes, mySceneClass); } } } /** * Save the data to the scene. * @param sceneAttributes * Attributes for the scene. * @param sceneClass * Class to which data is saved. */ void SceneClassAssistant::ClassData::save(const SceneAttributes& sceneAttributes, SceneClass& sceneClass) { SceneableInterface* myClassInstance = ((m_sceneClassPointer != NULL) ? m_sceneClassPointer : *m_sceneClassHandle); if (myClassInstance != NULL) { sceneClass.addClass(myClassInstance->saveToScene(&sceneAttributes, m_name)); } } workbench-1.1.1/src/Scenes/SceneClassAssistant.h000066400000000000000000000574421255417355300216170ustar00rootroot00000000000000#ifndef __SCENE_CLASS_ASSISTANT__H_ #define __SCENE_CLASS_ASSISTANT__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "SceneAttributes.h" #include "SceneClass.h" namespace caret { class SceneableInterface; class SceneClassAssistant : public CaretObject { public: SceneClassAssistant(); virtual ~SceneClassAssistant(); void restoreMembers(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass); void saveMembers(const SceneAttributes* sceneAttributes, SceneClass* sceneClass); private: SceneClassAssistant(const SceneClassAssistant&); SceneClassAssistant& operator=(const SceneClassAssistant&); public: void add(const AString& name, float* floatAddress); void add(const AString& name, int32_t* intAddress); void add(const AString& name, bool* boolAddress); void add(const AString& name, AString* stringAddress); // void add(const AString& name, // const AString& className, // SceneableInterface** sceneClass); void add(const AString& name, const AString& className, SceneableInterface* sceneClass); /** * Add a enumerated type member. * @param name * Name of member. * @param enumeratedValueAddress * Address of the member. * @param defaultValue * Value used if the member is not found when restoring scene. */ template void add(const AString& name, ET* enumeratedValueAddress) { EnumeratedTypeData* etd = new EnumeratedTypeData(name, enumeratedValueAddress, *enumeratedValueAddress); m_dataStorage.push_back(etd); } void addArray(const AString& name, bool* booleanArray, const int32_t numberOfElements, const bool defaultValue); void addArray(const AString& name, float* floatArray, const int32_t numberOfElements, const float defaultValue); void addArray(const AString& name, int32_t* integerArray, const int32_t numberOfElements, const int32_t defaultValue); /** * Add an enumerated type array. * @param name * Name of array. * @param enumeratedValueArray * The array. * @param numberOfElements * Number of elements in the array. * @param defaultValue * Value used for items not found. */ template void addArray(const AString& name, ET* enumeratedValueArray, const int32_t numberOfElements, const ET defaultValue) { EnumeratedTypeArrayData* eta = new EnumeratedTypeArrayData(name, enumeratedValueArray, numberOfElements, defaultValue); m_dataStorage.push_back(eta); } // void addArray(const AString& name, // SceneableInterface* sceneableInterfaceArray[], // const int32_t numberOfElements); void addArray(const AString& name, AString* stringArray, const int32_t numberOfElements, const AString& defaultValue); void addTabIndexedBooleanArray(const AString& name, bool* booleanArray); void addTabIndexedIntegerArray(const AString& name, int32_t* integerArray); void addTabIndexedFloatArray(const AString& name, float* floatArray); /** * Add a tab-indexed enumerated type array. * @param name * Name of array. * @param enumeratedValueArray * The array. */ template void addTabIndexedEnumeratedTypeArray(const AString& name, ET* enumeratedValueArray) { EnumeratedTypeTabIndexArrayMapData* etmap = new EnumeratedTypeTabIndexArrayMapData(name, enumeratedValueArray); m_dataStorage.push_back(etmap); } // ADD_NEW_METHODS_HERE private: // ADD_NEW_MEMBERS_HERE private: // ADD_NEW_MEMBERS_HERE /* ========================================= */ class Data { public: Data(const AString& name); virtual ~Data() { } virtual void restore(const SceneAttributes& sceneAttributes, const SceneClass& sceneClass) = 0; virtual void save(const SceneAttributes& sceneAttributes, SceneClass& sceneClass) = 0; protected: const AString m_name; }; /* ========================================= */ /** * \class caret::SceneClassAssistant::EnumeratedTypeData * \brief An enumeration value added to a scene class. * \ingroup Scene * * See the documentation in the class Scene for how to use the Scene system. */ template class EnumeratedTypeData : public Data { public: /** * Constructor. * @param name * Name of data. * @param dataPointer * Pointer to data. * @param defaultValue * Default value used when restoring and data with name not found. */ EnumeratedTypeData(const AString& name, ET* dataPointer, const ET defaultValue) : Data(name), m_dataPointer(dataPointer), m_defaultValue(defaultValue){ } virtual ~EnumeratedTypeData() { } /** * Restore the data from the scene. * @param sceneAttributes * Attributes of the scene. * @param sceneClass * Class from which data is restored. */ void restore(const SceneAttributes& /*sceneAttributes*/, const SceneClass& sceneClass) { *m_dataPointer = sceneClass.getEnumeratedTypeValue(m_name, m_defaultValue); } /** * Save the data to the scene. * @param sceneAttributes * Attributes for the scene. * @param sceneClass * Class to which data is saved. */ void save(const SceneAttributes& /*sceneAttributes*/, SceneClass& sceneClass) { sceneClass.addEnumeratedType(m_name, *m_dataPointer); } private: ET* m_dataPointer; const ET m_defaultValue; }; /* ========================================= */ class FloatData : public Data { public: FloatData(const AString& name, float* dataPointer, const float defaultValue); virtual ~FloatData() { } void restore(const SceneAttributes& sceneAttributes, const SceneClass& sceneClass); void save(const SceneAttributes& sceneAttributes, SceneClass& sceneClass); private: float* m_dataPointer; float m_defaultValue; }; /* ========================================= */ class IntegerData : public Data { public: IntegerData(const AString& name, int32_t* dataPointer, const int32_t defaultValue); virtual ~IntegerData() { } void restore(const SceneAttributes& sceneAttributes, const SceneClass& sceneClass); void save(const SceneAttributes& sceneAttributes, SceneClass& sceneClass); private: int32_t* m_dataPointer; int32_t m_defaultValue; }; /* ========================================= */ class BooleanData : public Data { public: BooleanData(const AString& name, bool* dataPointer, const bool defaultValue); virtual ~BooleanData() { } void restore(const SceneAttributes& sceneAttributes, const SceneClass& sceneClass); void save(const SceneAttributes& sceneAttributes, SceneClass& sceneClass); private: bool* m_dataPointer; bool m_defaultValue; }; /* ========================================= */ class StringData : public Data { public: StringData(const AString& name, AString* dataPointer, const AString& defaultValue); virtual ~StringData() { } void restore(const SceneAttributes& sceneAttributes, const SceneClass& sceneClass); void save(const SceneAttributes& sceneAttributes, SceneClass& sceneClass); private: AString* m_dataPointer; AString m_defaultValue; }; /* ========================================= */ class ClassData : public Data { public: ClassData(const AString& name, const AString& className, SceneableInterface** sceneClassHandle); ClassData(const AString& name, const AString& className, SceneableInterface* sceneClassPointer); virtual ~ClassData() { } void restore(const SceneAttributes& sceneAttributes, const SceneClass& sceneClass); void save(const SceneAttributes& sceneAttributes, SceneClass& sceneClass); private: const AString m_className; SceneableInterface** m_sceneClassHandle; SceneableInterface* m_sceneClassPointer; }; /* ========================================= */ class TabIndexArrayMapData : public Data { public: TabIndexArrayMapData(const AString& name); virtual ~TabIndexArrayMapData() { } }; /* ========================================= */ class BooleanTabIndexArrayMapData : public TabIndexArrayMapData { public: BooleanTabIndexArrayMapData(const AString& name, bool* booleanArray); virtual ~BooleanTabIndexArrayMapData() { } void restore(const SceneAttributes& sceneAttributes, const SceneClass& sceneClass); void save(const SceneAttributes& sceneAttributes, SceneClass& sceneClass); private: bool* m_booleanArray; }; /* ========================================= */ class IntegerTabIndexArrayMapData : public TabIndexArrayMapData { public: IntegerTabIndexArrayMapData(const AString& name, int32_t* integerArray); virtual ~IntegerTabIndexArrayMapData() { } void restore(const SceneAttributes& sceneAttributes, const SceneClass& sceneClass); void save(const SceneAttributes& sceneAttributes, SceneClass& sceneClass); private: int32_t* m_integerArray; }; /* ========================================= */ class FloatTabIndexArrayMapData : public TabIndexArrayMapData { public: FloatTabIndexArrayMapData(const AString& name, float* floatArray); virtual ~FloatTabIndexArrayMapData() { } void restore(const SceneAttributes& sceneAttributes, const SceneClass& sceneClass); void save(const SceneAttributes& sceneAttributes, SceneClass& sceneClass); private: float* m_floatArray; }; /* ========================================= */ template class EnumeratedTypeTabIndexArrayMapData : public TabIndexArrayMapData { public: EnumeratedTypeTabIndexArrayMapData(const AString& name, ET* enumValueArray) : TabIndexArrayMapData(name), m_enumValueArray(enumValueArray) { } virtual ~EnumeratedTypeTabIndexArrayMapData() { } void restore(const SceneAttributes& /*sceneAttributes*/, const SceneClass& sceneClass) { sceneClass.getEnumerateTypeArrayForTabIndices(m_name, m_enumValueArray); } void save(const SceneAttributes& sceneAttributes, SceneClass& sceneClass) { sceneClass.addEnumeratedTypeArrayForTabIndices(m_name, m_enumValueArray, sceneAttributes.getIndicesOfTabsForSavingToScene()); } private: ET* m_enumValueArray; }; /* ========================================= */ // class ClassTabIndexArrayMapData : public TabIndexArrayMapData { // public: // ClassTabIndexArrayMapData(const AString& name, // SceneableInterface* sceneInterface); // virtual ~ClassTabIndexArrayMapData() { } // // void restore(const SceneAttributes& sceneAttributes, // const SceneClass& sceneClass); // void save(const SceneAttributes& sceneAttributes, // SceneClass& sceneClass); // private: // SceneableInterface* m_sceneInterface; // }; /* ========================================= */ class ArrayData : public Data { public: ArrayData(const AString& name, const int32_t numberOfArrayElements); virtual ~ArrayData() {} protected: int32_t m_numberOfArrayElements; }; /* ========================================= */ class BooleanArrayData : public ArrayData { public: BooleanArrayData(const AString& name, bool* booleanArray, const int32_t numberOfArrayElements, const bool defaultValue); virtual ~BooleanArrayData() { } void restore(const SceneAttributes& sceneAttributes, const SceneClass& sceneClass); void save(const SceneAttributes& sceneAttributes, SceneClass& sceneClass); private: bool* m_booleanArray; bool m_defaultValue; }; /* ========================================= */ class IntegerArrayData : public ArrayData { public: IntegerArrayData(const AString& name, int32_t* integerArray, const int32_t numberOfArrayElements, const int32_t defaultValue); virtual ~IntegerArrayData() { } void restore(const SceneAttributes& sceneAttributes, const SceneClass& sceneClass); void save(const SceneAttributes& sceneAttributes, SceneClass& sceneClass); private: int32_t* m_integerArray; int32_t m_defaultValue; }; /* ========================================= */ class FloatArrayData : public ArrayData { public: FloatArrayData(const AString& name, float* floatArray, const int32_t numberOfArrayElements, const float defaultValue); virtual ~FloatArrayData() { } void restore(const SceneAttributes& sceneAttributes, const SceneClass& sceneClass); void save(const SceneAttributes& sceneAttributes, SceneClass& sceneClass); private: float* m_floatArray; float m_defaultValue; }; /* ========================================= */ class StringArrayData : public ArrayData { public: StringArrayData(const AString& name, AString* stringArray, const int32_t numberOfArrayElements, const AString defaultValue); virtual ~StringArrayData() { } void restore(const SceneAttributes& sceneAttributes, const SceneClass& sceneClass); void save(const SceneAttributes& sceneAttributes, SceneClass& sceneClass); private: AString* m_stringArray; AString m_defaultValue; }; /* ========================================= */ // class ClassArrayData : public ArrayData { // public: // ClassArrayData(const AString& name, // SceneableInterface* sceneInterfaces[], // const int32_t numberOfArrayElements); // // virtual ~ClassArrayData() { } // void restore(const SceneAttributes& sceneAttributes, // const SceneClass& sceneClass); // void save(const SceneAttributes& sceneAttributes, // SceneClass& sceneClass); // private: // SceneableInterface** m_sceneInterfaces; // }; /* ========================================= */ template class EnumeratedTypeArrayData : public ArrayData { public: EnumeratedTypeArrayData(const AString& name, ET* enumValueArray, const int32_t numberOfArrayElements, const ET defaultValue) : ArrayData(name, numberOfArrayElements), m_enumValueArray(enumValueArray), m_defaultValue(defaultValue) { } virtual ~EnumeratedTypeArrayData() { } void restore(const SceneAttributes& /*sceneAttributes*/, const SceneClass& sceneClass) { if (m_numberOfArrayElements > 0) { sceneClass.getEnumerateTypeArray(m_name, m_enumValueArray, m_numberOfArrayElements, m_defaultValue); } } void save(const SceneAttributes& /*sceneAttributes*/, SceneClass& sceneClass) { sceneClass.addEnumeratedTypeArray(m_name, m_enumValueArray, m_numberOfArrayElements); } private: ET* m_enumValueArray; const ET m_defaultValue; }; /* ========================================= */ // class BooleanVectorData : public Data { // public: // BooleanVectorData(const AString& name, // std::vector& booleanVectorReference, // const bool defaultValue); // // virtual~BooleanVectorData() { } // // void restore(const SceneAttributes& sceneAttributes, // const SceneClass& sceneClass); // void save(const SceneAttributes& sceneAttributes, // SceneClass& sceneClass); // private: // std::vector& m_booleanVectorReference; // const bool m_defaultValue; // }; /* ========================================= */ // class FloatVectorData : public Data { // public: // FloatVectorData(const AString& name, // std::vector* floatVectorPointer, // const float defaultValue); // // virtual~FloatVectorData() { } // // void restore(const SceneAttributes& sceneAttributes, // const SceneClass& sceneClass); // void save(const SceneAttributes& sceneAttributes, // SceneClass& sceneClass); // private: // std::vector* m_booleanVectorPointer; // const float m_defaultValue; // }; /* ========================================= */ typedef std::vector DataStorage; typedef DataStorage::iterator DataStorageIterator; DataStorage m_dataStorage; }; #ifdef __SCENE_CLASS_ASSISTANT_DECLARE__ // #endif // __SCENE_CLASS_ASSISTANT_DECLARE__ } // namespace #endif //__SCENE_CLASS_ASSISTANT__H_ workbench-1.1.1/src/Scenes/SceneEnumeratedType.cxx000066400000000000000000000036361255417355300221620ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SCENE_ENUMERATED_TYPE_DECLARE__ #include "SceneEnumeratedType.h" #undef __SCENE_ENUMERATED_TYPE_DECLARE__ using namespace caret; /** * \class caret::SceneEnumeratedType * \brief For storage of an emumerated type value in a scene. * \ingroup Scene * * See the documentation in the class Scene for how to use the Scene system. */ /** * Constructor. */ SceneEnumeratedType::SceneEnumeratedType(const AString& name, const AString& enumeratedValueAsString) : SceneObject(name, SceneObjectDataTypeEnum::SCENE_ENUMERATED_TYPE) { m_enumeratedValueAsString = enumeratedValueAsString; } /** * Destructor. */ SceneEnumeratedType::~SceneEnumeratedType() { } /** * @param enumeratedValueAsString * New value. */ void SceneEnumeratedType::setValue(const AString& enumeratedValueAsString) { m_enumeratedValueAsString = enumeratedValueAsString; } /** * @return The string representing the value of the * enumerated type. */ AString SceneEnumeratedType::stringValue() const { return m_enumeratedValueAsString; } workbench-1.1.1/src/Scenes/SceneEnumeratedType.h000066400000000000000000000034231255417355300216010ustar00rootroot00000000000000#ifndef __SCENE_ENUMERATED_TYPE__H_ #define __SCENE_ENUMERATED_TYPE__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "SceneObject.h" namespace caret { class SceneEnumeratedType : public SceneObject { public: SceneEnumeratedType(const AString& name, const AString& enumeratedValueAsString); virtual ~SceneEnumeratedType(); void setValue(const AString& enumeratedValueAsString); AString stringValue() const; private: SceneEnumeratedType(const SceneEnumeratedType&); SceneEnumeratedType& operator=(const SceneEnumeratedType&); public: // ADD_NEW_METHODS_HERE private: // ADD_NEW_MEMBERS_HERE AString m_enumeratedValueAsString; }; #ifdef __SCENE_ENUMERATED_TYPE_DECLARE__ // #endif // __SCENE_ENUMERATED_TYPE_DECLARE__ } // namespace #endif //__SCENE_ENUMERATED_TYPE__H_ workbench-1.1.1/src/Scenes/SceneEnumeratedTypeArray.cxx000066400000000000000000000113111255417355300231460ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __SCENE_ENUMERATED_TYPE_ARRAY_DECLARE__ #include "SceneEnumeratedTypeArray.h" #undef __SCENE_ENUMERATED_TYPE_ARRAY_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::SceneEnumeratedTypeArray * \brief For storage of a boolean value in a scene. * \ingroup Scene * * See the documentation in the class Scene for how to use the Scene system. */ /** * Constructor. * * @param name * Name of object. * @param values * Value in the array. * @param numberOfArrayElements * Number of values in the array. */ SceneEnumeratedTypeArray::SceneEnumeratedTypeArray(const AString& name, const AString enumeratedValuesAsStrings[], const int32_t numberOfArrayElements) : SceneObjectArray(name, SceneObjectDataTypeEnum::SCENE_ENUMERATED_TYPE, numberOfArrayElements) { m_values.resize(numberOfArrayElements); for (int32_t i = 0; i < numberOfArrayElements; i++) { m_values[i] = enumeratedValuesAsStrings[i]; } } /** * Constructor. * * @param name * Name of object. * @param values * Value in the array. */ SceneEnumeratedTypeArray::SceneEnumeratedTypeArray(const AString& name, const std::vector& enumeratedValuesAsStrings) : SceneObjectArray(name, SceneObjectDataTypeEnum::SCENE_ENUMERATED_TYPE, enumeratedValuesAsStrings.size()) { m_values = enumeratedValuesAsStrings; } /** * Constructor that initializes all values to false. * * @param name * Name of object. * @param numberOfArrayElements * Number of values in the array. */ SceneEnumeratedTypeArray::SceneEnumeratedTypeArray(const AString& name, const int numberOfArrayElements) : SceneObjectArray(name, SceneObjectDataTypeEnum::SCENE_ENUMERATED_TYPE, numberOfArrayElements) { m_values.resize(numberOfArrayElements); std::fill(m_values.begin(), m_values.end(), false); } /** * Destructor. */ SceneEnumeratedTypeArray::~SceneEnumeratedTypeArray() { } /** * Set a value. * @param arrayIndex * Index of the element. * @param value * Value of element. */ void SceneEnumeratedTypeArray::setValue(const int32_t arrayIndex, const AString enumeratedValueAsString) { CaretAssertVectorIndex(m_values, arrayIndex); m_values[arrayIndex] = enumeratedValueAsString; } /** * Get the values as a string. * @param arrayIndex * Index of element. * @return The value. */ AString SceneEnumeratedTypeArray::stringValue(const int32_t arrayIndex) const { CaretAssertVectorIndex(m_values, arrayIndex); return m_values[arrayIndex]; } /** * Load the array with string values of the enumerated type. * @param valuesOut * Array into which string values are loaded. * @param arrayNumberOfElements * Number of elements in the array. If this value is greater * than the number of elements in the scene, the remaining * elements will be filled with the default value * @param defaultValue * Default value used when output array contains more elements * than are in this instance's array. */ void SceneEnumeratedTypeArray::stringValues(AString enumeratedValuesAsString[], const int32_t numberOfArrayElements, const AString defaultValue) const { const int32_t numElem = std::min(numberOfArrayElements, m_numberOfArrayElements); for (int32_t i = 0; i < numElem; i++) { enumeratedValuesAsString[i] = m_values[i]; } for (int32_t i = numElem; i < m_numberOfArrayElements; i++) { enumeratedValuesAsString[i] = defaultValue; } } workbench-1.1.1/src/Scenes/SceneEnumeratedTypeArray.h000066400000000000000000000047131255417355300226030ustar00rootroot00000000000000#ifndef __SCENE_ENUMERATED_TYPE_ARRAY_H__ #define __SCENE_ENUMERATED_TYPE_ARRAY_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "SceneObjectArray.h" namespace caret { class SceneEnumeratedTypeArray : public SceneObjectArray { public: SceneEnumeratedTypeArray(const AString& name, const AString enumeratedValuesAsStrings[], const int32_t numberOfArrayElements); SceneEnumeratedTypeArray(const AString& name, const std::vector& enumeratedValuesAsStrings); SceneEnumeratedTypeArray(const AString& name, const int numberOfArrayElements); virtual ~SceneEnumeratedTypeArray(); void setValue(const int32_t arrayIndex, const AString enumeratedValueAsString); virtual AString stringValue(const int32_t arrayIndex) const; void stringValues(AString enumeratedValuesAsString[], const int32_t numberOfArrayElements, const AString defaultValue) const; private: SceneEnumeratedTypeArray(const SceneEnumeratedTypeArray&); SceneEnumeratedTypeArray& operator=(const SceneEnumeratedTypeArray&); public: // ADD_NEW_METHODS_HERE private: std::vector m_values; // ADD_NEW_MEMBERS_HERE }; #ifdef __SCENE_ENUMERATED_TYPE_ARRAY_DECLARE__ // #endif // __SCENE_ENUMERATED_TYPE_ARRAY_DECLARE__ } // namespace #endif //__SCENE_ENUMERATED_TYPE_ARRAY_H__ workbench-1.1.1/src/Scenes/SceneFloat.cxx000066400000000000000000000046731255417355300202760ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __SCENE_FLOAT_DECLARE__ #include "SceneFloat.h" #undef __SCENE_FLOAT_DECLARE__ using namespace caret; /** * \class caret::SceneFloat * \brief For storage of a float value in a scene. * \ingroup Scene * * See the documentation in the class Scene for how to use the Scene system. */ /** * Constructor. * * @param name * Name of object. * @param value * Value of object. */ SceneFloat::SceneFloat(const AString& name, const float value) : ScenePrimitive(name, SceneObjectDataTypeEnum::SCENE_FLOAT) { m_value = value; } /** * Destructor. */ SceneFloat::~SceneFloat() { } /** * Set the value. * @param value * The new value. */ void SceneFloat::setValue(const float value) { m_value = value; } /** * @return The value as a boolean data type. */ bool SceneFloat::booleanValue() const { const bool b = ((m_value != 0.0) ? true : false); return b; } /** * @return The value as a float data type. */ float SceneFloat::floatValue() const { return m_value; } /** * @return The value as a integer data type. */ int32_t SceneFloat::integerValue() const { if (m_value > std::numeric_limits::max()) { return std::numeric_limits::max(); } else if (m_value < std::numeric_limits::min()) { return std::numeric_limits::min(); } const float i = static_cast(m_value); return i; } /** * @return The value as a string data type. */ AString SceneFloat::stringValue() const { const AString s = AString::number(m_value); return s; } workbench-1.1.1/src/Scenes/SceneFloat.h000066400000000000000000000034221255417355300177120ustar00rootroot00000000000000#ifndef __SCENE_FLOAT__H_ #define __SCENE_FLOAT__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "ScenePrimitive.h" namespace caret { class SceneFloat : public ScenePrimitive { public: SceneFloat(const AString& name, const float value); virtual ~SceneFloat(); void setValue(const float value); virtual bool booleanValue() const; virtual float floatValue() const; virtual int32_t integerValue() const; virtual AString stringValue() const; private: SceneFloat(const SceneFloat&); SceneFloat& operator=(const SceneFloat&); public: // ADD_NEW_METHODS_HERE private: float m_value; // ADD_NEW_MEMBERS_HERE }; #ifdef __SCENE_FLOAT_DECLARE__ // #endif // __SCENE_FLOAT_DECLARE__ } // namespace #endif //__SCENE_FLOAT__H_ workbench-1.1.1/src/Scenes/SceneFloatArray.cxx000066400000000000000000000110421255417355300212610ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #define __SCENE_FLOAT_ARRAY_DECLARE__ #include "SceneFloatArray.h" #undef __SCENE_FLOAT_ARRAY_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::SceneFloatArray * \brief For storage of a float value in a scene. * \ingroup Scene * * See the documentation in the class Scene for how to use the Scene system. */ /** * Constructor. * * @param name * Name of object. * @param values * Value in the array. * @param numberOfArrayElements * Number of values in the array. */ SceneFloatArray::SceneFloatArray(const AString& name, const float values[], const int32_t numberOfArrayElements) : ScenePrimitiveArray(name, SceneObjectDataTypeEnum::SCENE_FLOAT, numberOfArrayElements) { m_values.resize(numberOfArrayElements); for (int32_t i = 0; i < numberOfArrayElements; i++) { m_values[i] = values[i]; } } /** * Constructor. * * @param name * Name of object. * @param values * Value in the array. */ SceneFloatArray::SceneFloatArray(const AString& name, const std::vector& values) : ScenePrimitiveArray(name, SceneObjectDataTypeEnum::SCENE_FLOAT, values.size()) { m_values = values; } /** * Constructor that initializes all values to false. * * @param name * Name of object. * @param numberOfArrayElements * Number of values in the array. */ SceneFloatArray::SceneFloatArray(const AString& name, const int numberOfArrayElements) : ScenePrimitiveArray(name, SceneObjectDataTypeEnum::SCENE_FLOAT, numberOfArrayElements) { m_values.resize(numberOfArrayElements); std::fill(m_values.begin(), m_values.end(), false); } /** * Destructor. */ SceneFloatArray::~SceneFloatArray() { } /** * Set a value. * @param arrayIndex * Index of the element. * @param value * Value of element. */ void SceneFloatArray::setValue(const int32_t arrayIndex, const float value) { CaretAssertVectorIndex(m_values, arrayIndex); m_values[arrayIndex] = value; } /** * Get the values as a boolean. * @param arrayIndex * Index of element. * @return The value. */ bool SceneFloatArray::booleanValue(const int32_t arrayIndex) const { CaretAssertVectorIndex(m_values, arrayIndex); const bool b = ((m_values[arrayIndex] != 0.0) ? true : false); return b; } /** * Get the values as a float. * @param arrayIndex * Index of element. * @return The value. */ float SceneFloatArray::floatValue(const int32_t arrayIndex) const { CaretAssertVectorIndex(m_values, arrayIndex); return m_values[arrayIndex]; } /** * Get the values as a integer. * @param arrayIndex * Index of element. * @return The value. */ int32_t SceneFloatArray::integerValue(const int32_t arrayIndex) const { CaretAssertVectorIndex(m_values, arrayIndex); const float f = m_values[arrayIndex]; if (f > std::numeric_limits::max()) { return std::numeric_limits::max(); } else if (f < std::numeric_limits::min()) { return std::numeric_limits::min(); } const float i = static_cast(f); return i; } /** * Get the values as a string. * @param arrayIndex * Index of element. * @return The value. */ AString SceneFloatArray::stringValue(const int32_t arrayIndex) const { CaretAssertVectorIndex(m_values, arrayIndex); const AString s = AString::number(m_values[arrayIndex]); return s; } workbench-1.1.1/src/Scenes/SceneFloatArray.h000066400000000000000000000044611255417355300207150ustar00rootroot00000000000000#ifndef __SCENE_FLOAT_ARRAY_H__ #define __SCENE_FLOAT_ARRAY_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "ScenePrimitiveArray.h" namespace caret { class SceneFloatArray : public ScenePrimitiveArray { public: SceneFloatArray(const AString& name, const float values[], const int32_t numberOfArrayElements); SceneFloatArray(const AString& name, const std::vector& values); SceneFloatArray(const AString& name, const int numberOfArrayElements); virtual ~SceneFloatArray(); void setValue(const int32_t arrayIndex, const float value); virtual bool booleanValue(const int32_t arrayIndex) const; virtual float floatValue(const int32_t arrayIndex) const; virtual int32_t integerValue(const int32_t arrayIndex) const; virtual AString stringValue(const int32_t arrayIndex) const; private: SceneFloatArray(const SceneFloatArray&); SceneFloatArray& operator=(const SceneFloatArray&); public: // ADD_NEW_METHODS_HERE private: std::vector m_values; // ADD_NEW_MEMBERS_HERE }; #ifdef __SCENE_FLOAT_ARRAY_DECLARE__ // #endif // __SCENE_FLOAT_ARRAY_DECLARE__ } // namespace #endif //__SCENE_FLOAT_ARRAY_H__ workbench-1.1.1/src/Scenes/SceneInfo.cxx000066400000000000000000000142131255417355300201130ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SCENE_INFO_DECLARE__ #include "SceneInfo.h" #undef __SCENE_INFO_DECLARE__ #include "CaretAssert.h" #include "CaretLogger.h" #include "SceneXmlElements.h" #include "XmlAttributes.h" #include "XmlWriter.h" using namespace caret; /** * \class caret::SceneInfo * \brief Contains information about a scene. * \ingroup Scenes */ /** * Constructor. */ SceneInfo::SceneInfo() : CaretObject() { } /** * Destructor. */ SceneInfo::~SceneInfo() { } /** * @return name of scene */ AString SceneInfo::getName() const { return m_sceneName; } /** * Set name of scene * @param sceneName * New value for name of scene */ void SceneInfo::setName(const AString& sceneName) { m_sceneName = sceneName; } /** * @return description of scene */ AString SceneInfo::getDescription() const { return m_sceneDescription; } /** * Set description of scene * @param sceneDescription * New value for description of scene */ void SceneInfo::setDescription(const AString& sceneDescription) { m_sceneDescription = sceneDescription; } /** * Set bytes containing the thumbnail image. * * @param imageBytes * Byte array containing the image file. * @param imageFormat * Format of the image (jpg, ppm, etc.). */ void SceneInfo::setImageBytes(const QByteArray& imageBytes, const AString& imageFormat) { m_imageBytes = imageBytes; m_imageFormat = imageFormat; } /** * Get bytes containing the thumbnail image. * * @param imageBytesOut * Byte array containing the image file. * @param imageFormatOut * Format of the image (jpg, ppm, etc.). */ void SceneInfo::getImageBytes(QByteArray& imageBytesOut, AString& imageFormatOut) const { imageBytesOut = m_imageBytes; imageFormatOut = m_imageFormat; } /** * @return true if the scene contains an image, else false. * * The image is considered valid if the image bytes are not empty and, * if not empty, it is assumed that the image is valid. */ bool SceneInfo::hasImage() const { if (m_imageBytes.isEmpty()) { return false; } return true; } /** * Write the scene info element. * * @param sceneInfo * The scene info element that is written. * @param sceneInfoIndex * The index for the scene info. */ void SceneInfo::writeSceneInfo(XmlWriter& xmlWriter, const int32_t sceneInfoIndex) const { XmlAttributes attributes; attributes.addAttribute(SceneXmlElements::SCENE_INFO_INDEX_ATTRIBUTE, sceneInfoIndex); xmlWriter.writeStartElement(SceneXmlElements::SCENE_INFO_TAG, attributes); xmlWriter.writeElementCData(SceneXmlElements::SCENE_INFO_NAME_TAG, m_sceneName); xmlWriter.writeElementCData(SceneXmlElements::SCENE_INFO_DESCRIPTION_TAG, m_sceneDescription); writeSceneInfoImage(xmlWriter, SceneXmlElements::SCENE_INFO_IMAGE_TAG, m_imageBytes, m_imageFormat); /* * End class element. */ xmlWriter.writeEndElement(); } /** * Write an image to the scene info. * * @param xmlWriter * The XML writer. * @param xmlTag * Tag for the image. * @param imageBytes * Bytes containing the image. * @param imageFormat * Format of the image. * */ void SceneInfo::writeSceneInfoImage(XmlWriter& xmlWriter, const AString& xmlTag, const QByteArray& imageBytes, const AString& imageFormat) const { if (imageBytes.length() > 0) { //QString base64String(imageBytes.toBase64()); const QByteArray base64ByteArray(imageBytes.toBase64()); QString base64String = QString::fromAscii(base64ByteArray.constData(), base64ByteArray.size()); XmlAttributes attributes; attributes.addAttribute(SceneXmlElements::SCENE_INFO_IMAGE_ENCODING_ATTRIBUTE, SceneXmlElements::SCENE_INFO_ENCODING_BASE64_NAME); attributes.addAttribute(SceneXmlElements::SCENE_INFO_IMAGE_FORMAT_ATTRIBUTE, imageFormat); xmlWriter.writeStartElement(xmlTag, attributes); xmlWriter.writeCharacters(base64String); xmlWriter.writeEndElement(); } } /** * Set an image form text. * * @param text * Text containing the image. * @param encoding * Encoding of the image data. * @param imageFormat * Format of the image. */ void SceneInfo::setImageFromText(const AString& text, const AString& encoding, const AString& imageFormat) { m_imageBytes.clear(); m_imageFormat = ""; if ( ! text.isEmpty()) { if (encoding == SceneXmlElements::SCENE_INFO_ENCODING_BASE64_NAME) { m_imageBytes = QByteArray::fromBase64(text.toAscii()); m_imageFormat = imageFormat; } else { CaretLogSevere("Invalid encoding (" + encoding + ") for scene thumbnail image."); } } } workbench-1.1.1/src/Scenes/SceneInfo.h000066400000000000000000000054031255417355300175410ustar00rootroot00000000000000#ifndef __SCENE_INFO_H__ #define __SCENE_INFO_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretObject.h" namespace caret { class XmlWriter; class SceneInfo : public CaretObject { public: SceneInfo(); virtual ~SceneInfo(); AString getName() const; void setName(const AString& sceneName); AString getDescription() const; void setDescription(const AString& description); void getImageBytes(QByteArray& imageBytesOut, AString& imageFormatOut) const; void setImageBytes(const QByteArray& imageBytes, const AString& imageFormat); bool hasImage() const; void writeSceneInfo(XmlWriter& xmlWriter, const int32_t sceneInfoIndex) const; void setImageFromText(const AString& text, const AString& encoding, const AString& imageFormat); void writeSceneInfoImage(XmlWriter& xmlWriter, const AString& xmlTag, const QByteArray& imageBytes, const AString& imageFormat) const; private: SceneInfo(const SceneInfo&); SceneInfo& operator=(const SceneInfo&); /** name of scene*/ AString m_sceneName; /** description of scene */ AString m_sceneDescription; /** thumbnail image bytes */ QByteArray m_imageBytes; /** format of thumbnail image (eg: jpg, ppm, etc.) */ AString m_imageFormat; // ADD_NEW_MEMBERS_HERE }; #ifdef __SCENE_INFO_DECLARE__ // #endif // __SCENE_INFO_DECLARE__ } // namespace #endif //__SCENE_INFO_H__ workbench-1.1.1/src/Scenes/SceneInfoSaxReader.cxx000066400000000000000000000136771255417355300217270ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretAssert.h" #include "CaretLogger.h" #include "DataFile.h" #include "SceneInfo.h" #include "SceneInfoSaxReader.h" #include "SceneXmlElements.h" #include "XmlAttributes.h" #include "XmlException.h" #include "XmlUtilities.h" using namespace caret; /** * constructor. * @param sceneFileName * Name of scene file being read. * @param sceneInfo * Scene Info that is being read. */ SceneInfoSaxReader::SceneInfoSaxReader(const AString& sceneFileName, SceneInfo* sceneInfo) : m_sceneFileName(sceneFileName) { m_state = STATE_NONE; m_stateStack.push(m_state); m_elementText = ""; m_sceneInfo = sceneInfo; m_sceneInfoIndex = -1; m_imageEncoding = ""; m_imageFormat = ""; } /** * destructor. */ SceneInfoSaxReader::~SceneInfoSaxReader() { } /** * start an element. */ void SceneInfoSaxReader::startElement(const AString& /* namespaceURI */, const AString& /* localName */, const AString& qName, const XmlAttributes& attributes) { const STATE previousState = m_state; switch (m_state) { case STATE_NONE: if (qName == SceneXmlElements::SCENE_INFO_TAG) { m_state = STATE_SCENE_INFO; m_sceneInfoIndex = attributes.getValueAsInt(SceneXmlElements::SCENE_INFO_INDEX_ATTRIBUTE); } else { const AString msg = ("While reading Scene XML, expected the XML tag to be " + SceneXmlElements::SCENE_INFO_TAG + " but found " + qName); XmlSaxParserException e(msg); CaretLogThrowing(e); throw e; } break; case STATE_SCENE_INFO: if (qName == SceneXmlElements::SCENE_INFO_NAME_TAG) { m_state = STATE_SCENE_INFO_NAME; } else if (qName == SceneXmlElements::SCENE_INFO_DESCRIPTION_TAG) { m_state = STATE_SCENE_INFO_DESCRIPTION; } else if (qName == SceneXmlElements::SCENE_INFO_IMAGE_TAG) { m_state = STATE_SCENE_INFO_IMAGE_THUMBNAIL; m_imageFormat = attributes.getValue(SceneXmlElements::SCENE_INFO_IMAGE_FORMAT_ATTRIBUTE); m_imageEncoding = attributes.getValue(SceneXmlElements::SCENE_INFO_IMAGE_ENCODING_ATTRIBUTE); } else { const AString msg = XmlUtilities::createInvalidChildElementMessage(SceneXmlElements::SCENE_INFO_TAG, qName); XmlSaxParserException e(msg); CaretLogThrowing(e); throw e; } break; case STATE_SCENE_INFO_NAME: break; case STATE_SCENE_INFO_DESCRIPTION: break; case STATE_SCENE_INFO_IMAGE_THUMBNAIL: break; } // // Save previous state // m_stateStack.push(previousState); m_elementText = ""; } /** * end an element. */ void SceneInfoSaxReader::endElement(const AString& /* namspaceURI */, const AString& /* localName */, const AString& /*qName*/) { const AString stringValue = m_elementText.trimmed(); switch (m_state) { case STATE_NONE: break; case STATE_SCENE_INFO: break; case STATE_SCENE_INFO_NAME: CaretAssert(m_sceneInfo); m_sceneInfo->setName(stringValue); break; case STATE_SCENE_INFO_DESCRIPTION: CaretAssert(m_sceneInfo); m_sceneInfo->setDescription(stringValue); break; case STATE_SCENE_INFO_IMAGE_THUMBNAIL: { CaretAssert(m_sceneInfo); m_sceneInfo->setImageFromText(stringValue, m_imageEncoding, m_imageFormat); m_imageEncoding = ""; m_imageFormat = ""; } break; } /* * Clear out for new elements */ m_elementText = ""; /* * Go to previous state */ if (m_stateStack.empty()) { throw XmlSaxParserException("State stack is empty while reading Scene."); } CaretAssert(m_stateStack.empty() == false); m_state = m_stateStack.top(); m_stateStack.pop(); } /** * get characters in an element. */ void SceneInfoSaxReader::characters(const char* ch) { m_elementText += ch; } /** * a fatal error occurs. */ void SceneInfoSaxReader::fatalError(const XmlSaxParserException& e) { throw e; } /** * A warning occurs */ void SceneInfoSaxReader::warning(const XmlSaxParserException& e) { CaretLogWarning("XML Parser Warning: " + e.whatString()); } // an error occurs void SceneInfoSaxReader::error(const XmlSaxParserException& e) { throw e; } void SceneInfoSaxReader::startDocument() { } void SceneInfoSaxReader::endDocument() { } workbench-1.1.1/src/Scenes/SceneInfoSaxReader.h000066400000000000000000000064261255417355300213460ustar00rootroot00000000000000 #ifndef __SCENE_INFO_SAX_READER_H__ #define __SCENE_INFO_SAX_READER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" #include "XmlSaxParserException.h" #include "XmlSaxParserHandlerInterface.h" namespace caret { class SceneInfo; class XmlAttributes; /** * class for reading a Scene Info with a SAX Parser */ class SceneInfoSaxReader : public CaretObject, public XmlSaxParserHandlerInterface { public: SceneInfoSaxReader(const AString& sceneFileName, SceneInfo* sceneInfo); virtual ~SceneInfoSaxReader(); void startElement(const AString& namespaceURI, const AString& localName, const AString& qName, const XmlAttributes& attributes); void endElement(const AString& namspaceURI, const AString& localName, const AString& qName); void characters(const char* ch); void fatalError(const XmlSaxParserException& e); void warning(const XmlSaxParserException& e); void error(const XmlSaxParserException& e); void startDocument(); void endDocument(); protected: /// file reading states enum STATE { /// no state STATE_NONE, /// processing Scene Info tag STATE_SCENE_INFO, /// processing Scene Info Name tag STATE_SCENE_INFO_NAME, /// processing Scene Info Description tag STATE_SCENE_INFO_DESCRIPTION, /// processing Scene Info thumbnail tag STATE_SCENE_INFO_IMAGE_THUMBNAIL }; /// name of scene file AString m_sceneFileName; /// file reading state STATE m_state; /// the state stack used when reading a file std::stack m_stateStack; /// the error message AString m_errorMessage; /// element text AString m_elementText; /// Scene being read SceneInfo* m_sceneInfo; /** Index of scene info */ int32_t m_sceneInfoIndex; /** image format */ AString m_imageFormat; /** image encoding */ AString m_imageEncoding; }; } // namespace #endif // __SCENE_INFO_SAX_READER_H__ workbench-1.1.1/src/Scenes/SceneInteger.cxx000066400000000000000000000043541255417355300206220ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __SCENE_INTEGER_DECLARE__ #include "SceneInteger.h" #undef __SCENE_INTEGER_DECLARE__ using namespace caret; /** * \class caret::SceneInteger * \brief For storage of an integer value in a scene. * \ingroup Scene * * See the documentation in the class Scene for how to use the Scene system. */ /** * Constructor. * * @param name * Name of object. * @param value * Value of object. */ SceneInteger::SceneInteger(const AString& name, const int32_t value) : ScenePrimitive(name, SceneObjectDataTypeEnum::SCENE_INTEGER) { m_value = value; } /** * Destructor. */ SceneInteger::~SceneInteger() { } /** * Set the value. * @param value * The new value. */ void SceneInteger::setValue(const int32_t value) { m_value = value; } /** * @return The value as a boolean data type. */ bool SceneInteger::booleanValue() const { const bool b = ((m_value != 0.0) ? true : false); return b; } /** * @return The value as a float data type. */ float SceneInteger::floatValue() const { const float f = static_cast(m_value); return f; } /** * @return The value as a integer data type. */ int32_t SceneInteger::integerValue() const { return m_value; } /** * @return The value as a string data type. */ AString SceneInteger::stringValue() const { const AString s = AString::number(m_value); return s; } workbench-1.1.1/src/Scenes/SceneInteger.h000066400000000000000000000034601255417355300202440ustar00rootroot00000000000000#ifndef __SCENE_INTEGER__H_ #define __SCENE_INTEGER__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "ScenePrimitive.h" namespace caret { class SceneInteger : public ScenePrimitive { public: SceneInteger(const AString& name, const int32_t value); virtual ~SceneInteger(); void setValue(const int32_t value); virtual bool booleanValue() const; virtual float floatValue() const; virtual int32_t integerValue() const; virtual AString stringValue() const; private: SceneInteger(const SceneInteger&); SceneInteger& operator=(const SceneInteger&); public: // ADD_NEW_METHODS_HERE private: int32_t m_value; // ADD_NEW_MEMBERS_HERE }; #ifdef __SCENE_INTEGER_DECLARE__ // #endif // __SCENE_INTEGER_DECLARE__ } // namespace #endif //__SCENE_INTEGER__H_ workbench-1.1.1/src/Scenes/SceneIntegerArray.cxx000066400000000000000000000105031255417355300216120ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __SCENE_INTEGER_ARRAY_DECLARE__ #include "SceneIntegerArray.h" #undef __SCENE_INTEGER_ARRAY_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::SceneIntegerArray * \brief For storage of a integer value in a scene. * \ingroup Scene * * See the documentation in the class Scene for how to use the Scene system. */ /** * Constructor. * * @param name * Name of object. * @param values * Value in the array. * @param numberOfArrayElements * Number of values in the array. */ SceneIntegerArray::SceneIntegerArray(const AString& name, const int32_t values[], const int32_t numberOfArrayElements) : ScenePrimitiveArray(name, SceneObjectDataTypeEnum::SCENE_INTEGER, numberOfArrayElements) { m_values.resize(numberOfArrayElements); for (int32_t i = 0; i < numberOfArrayElements; i++) { m_values[i] = values[i]; } } /** * Constructor. * * @param name * Name of object. * @param values * Value in the array. */ SceneIntegerArray::SceneIntegerArray(const AString& name, const std::vector& values) : ScenePrimitiveArray(name, SceneObjectDataTypeEnum::SCENE_INTEGER, values.size()) { m_values = values; } /** * Constructor that initializes all values to false. * * @param name * Name of object. * @param numberOfArrayElements * Number of values in the array. */ SceneIntegerArray::SceneIntegerArray(const AString& name, const int numberOfArrayElements) : ScenePrimitiveArray(name, SceneObjectDataTypeEnum::SCENE_INTEGER, numberOfArrayElements) { m_values.resize(numberOfArrayElements); std::fill(m_values.begin(), m_values.end(), false); } /** * Destructor. */ SceneIntegerArray::~SceneIntegerArray() { } /** * Set a value. * @param arrayIndex * Index of the element. * @param value * Value of element. */ void SceneIntegerArray::setValue(const int32_t arrayIndex, const int32_t value) { CaretAssertVectorIndex(m_values, arrayIndex); m_values[arrayIndex] = value; } /** * Get the values as a boolean. * @param arrayIndex * Index of element. * @return The value. */ bool SceneIntegerArray::booleanValue(const int32_t arrayIndex) const { CaretAssertVectorIndex(m_values, arrayIndex); const bool b = ((m_values[arrayIndex] != 0.0) ? true : false); return b; } /** * Get the values as a float. * @param arrayIndex * Index of element. * @return The value. */ float SceneIntegerArray::floatValue(const int32_t arrayIndex) const { CaretAssertVectorIndex(m_values, arrayIndex); const float f = static_cast(m_values[arrayIndex]); return f; } /** * Get the values as a integer. * @param arrayIndex * Index of element. * @return The value. */ int32_t SceneIntegerArray::integerValue(const int32_t arrayIndex) const { CaretAssertVectorIndex(m_values, arrayIndex); return m_values[arrayIndex]; } /** * Get the values as a string. * @param arrayIndex * Index of element. * @return The value. */ AString SceneIntegerArray::stringValue(const int32_t arrayIndex) const { CaretAssertVectorIndex(m_values, arrayIndex); const AString s = AString::number(m_values[arrayIndex]); return s; } workbench-1.1.1/src/Scenes/SceneIntegerArray.h000066400000000000000000000045251255417355300212460ustar00rootroot00000000000000#ifndef __SCENE_INTEGER_ARRAY_H__ #define __SCENE_INTEGER_ARRAY_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "ScenePrimitiveArray.h" namespace caret { class SceneIntegerArray : public ScenePrimitiveArray { public: SceneIntegerArray(const AString& name, const int32_t values[], const int32_t numberOfArrayElements); SceneIntegerArray(const AString& name, const std::vector& values); SceneIntegerArray(const AString& name, const int numberOfArrayElements); virtual ~SceneIntegerArray(); void setValue(const int32_t arrayIndex, const int32_t value); virtual bool booleanValue(const int32_t arrayIndex) const; virtual float floatValue(const int32_t arrayIndex) const; virtual int32_t integerValue(const int32_t arrayIndex) const; virtual AString stringValue(const int32_t arrayIndex) const; private: SceneIntegerArray(const SceneIntegerArray&); SceneIntegerArray& operator=(const SceneIntegerArray&); public: // ADD_NEW_METHODS_HERE private: std::vector m_values; // ADD_NEW_MEMBERS_HERE }; #ifdef __SCENE_INTEGER_ARRAY_DECLARE__ // #endif // __SCENE_INTEGER_ARRAY_DECLARE__ } // namespace #endif //__SCENE_INTEGER_ARRAY_H__ workbench-1.1.1/src/Scenes/SceneObject.cxx000066400000000000000000000040651255417355300204320ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SCENE_OBJECT_DECLARE__ #include "SceneObject.h" #undef __SCENE_OBJECT_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::SceneObject * \brief Abstract class for any item saved to a scene. * \ingroup Scene * * See the documentation in the class Scene for how to use the Scene system. */ /** * Constructor. * @param name * Name of the item. * @param dataType * Data type of the primitive. */ SceneObject::SceneObject(const QString& name, const SceneObjectDataTypeEnum::Enum dataType) #ifdef CARET_SCENE_DEBUG : CaretObject(), m_name(name), m_dataType(dataType) #else // CARET_SCENE_DEBUG : m_name(name), m_dataType(dataType) #endif // CARET_SCENE_DEBUG { CaretAssert(name.isEmpty() == false); } /** * Destructor. */ SceneObject::~SceneObject() { } /** * @return Name of the item */ QString SceneObject::getName() const { return m_name; } /** * @return Data type of the object. */ SceneObjectDataTypeEnum::Enum SceneObject::getDataType() const { return m_dataType; } /** * Get a description of this object's content. * @return String describing this object's content. */ AString SceneObject::toString() const { return "SceneObject"; } workbench-1.1.1/src/Scenes/SceneObject.h000066400000000000000000000037001255417355300200520ustar00rootroot00000000000000#ifndef __SCENE_OBJECT__H_ #define __SCENE_OBJECT__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretObject.h" #include "SceneObjectDataTypeEnum.h" namespace caret { #ifdef CARET_SCENE_DEBUG class SceneObject : public CaretObject { #else // CARET_SCENE_DEBUG class SceneObject { #endif // CARET_SCENE_DEBUG public: virtual ~SceneObject(); QString getName() const; SceneObjectDataTypeEnum::Enum getDataType() const; protected: SceneObject(const QString& name, const SceneObjectDataTypeEnum::Enum dataType); private: SceneObject(const SceneObject&); SceneObject& operator=(const SceneObject&); public: virtual AString toString() const; // ADD_NEW_METHODS_HERE private: // ADD_NEW_MEMBERS_HERE /** Name of the item*/ const QString m_name; /** Type of object */ const SceneObjectDataTypeEnum::Enum m_dataType; }; #ifdef __SCENE_OBJECT_DECLARE__ #endif // __SCENE_OBJECT_DECLARE__ } // namespace #endif //__SCENE_OBJECT__H_ workbench-1.1.1/src/Scenes/SceneObjectArray.cxx000066400000000000000000000035561255417355300214350ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SCENE_OBJECT_ARRAY_DECLARE__ #include "SceneObjectArray.h" #undef __SCENE_OBJECT_ARRAY_DECLARE__ using namespace caret; /** * \class caret::SceneObjectArray * \brief Abstract class for arrays (and other containers). * \ingroup Scene * * See the documentation in the class Scene for how to use the Scene system. */ /** * Constructor. * @param name * Name of the item. * @param dataType * Data type of the object. * @param numberOfArrayElements * Number of elements in the array. */ SceneObjectArray::SceneObjectArray(const QString& name, const SceneObjectDataTypeEnum::Enum dataType, const int32_t numberOfArrayElements) : SceneObject(name, dataType), m_numberOfArrayElements(numberOfArrayElements) { } /** * Destructor. */ SceneObjectArray::~SceneObjectArray() { } /** * @return Number of elements in the array. */ int32_t SceneObjectArray::getNumberOfArrayElements() const { return m_numberOfArrayElements; } workbench-1.1.1/src/Scenes/SceneObjectArray.h000066400000000000000000000033741255417355300210600ustar00rootroot00000000000000#ifndef __SCENE_OBJECT_ARRAY__H_ #define __SCENE_OBJECT_ARRAY__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "SceneObject.h" namespace caret { class SceneObjectArray : public SceneObject { public: SceneObjectArray(const QString& name, const SceneObjectDataTypeEnum::Enum dataType, const int32_t numberOfArrayElements); virtual ~SceneObjectArray(); private: SceneObjectArray(const SceneObjectArray&); SceneObjectArray& operator=(const SceneObjectArray&); public: // ADD_NEW_METHODS_HERE int32_t getNumberOfArrayElements() const; protected: // ADD_NEW_MEMBERS_HERE const int32_t m_numberOfArrayElements; }; #ifdef __SCENE_OBJECT_ARRAY_DECLARE__ // #endif // __SCENE_OBJECT_ARRAY_DECLARE__ } // namespace #endif //__SCENE_OBJECT_ARRAY__H_ workbench-1.1.1/src/Scenes/SceneObjectDataTypeEnum.cxx000066400000000000000000000322641255417355300227150ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __SCENE_OBJECT_DATA_TYPE_ENUM_DECLARE__ #include "SceneObjectDataTypeEnum.h" #undef __SCENE_OBJECT_DATA_TYPE_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::SceneObjectDataTypeEnum * \brief Types of data objects saved in scenes. * \ingroup Scene * * See the documentation in the class Scene for how to use the Scene system. */ /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * @param guiName * User-friendly name for use in user-interface. * @param xmlName * Name used by XML. */ SceneObjectDataTypeEnum::SceneObjectDataTypeEnum(const Enum enumValue, const AString& name, const AString& guiName, const AString& xmlName) { this->enumValue = enumValue; this->integerCode = integerCodeCounter++; this->name = name; this->guiName = guiName; this->xmlName = xmlName; } /** * Destructor. */ SceneObjectDataTypeEnum::~SceneObjectDataTypeEnum() { } /** * Initialize the enumerated metadata. */ void SceneObjectDataTypeEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(SceneObjectDataTypeEnum(SCENE_INVALID, "SCENE_INVALID", "invalid", "invalid")); enumData.push_back(SceneObjectDataTypeEnum(SCENE_BOOLEAN, "SCENE_BOOLEAN", "boolean", "boolean")); enumData.push_back(SceneObjectDataTypeEnum(SCENE_CLASS, "SCENE_CLASS", "class", "class")); enumData.push_back(SceneObjectDataTypeEnum(SCENE_ENUMERATED_TYPE, "SCENE_ENUMERATED_TYPE", "enumeratedType", "enumeratedType")); enumData.push_back(SceneObjectDataTypeEnum(SCENE_FLOAT, "SCENE_FLOAT", "float", "float")); enumData.push_back(SceneObjectDataTypeEnum(SCENE_INTEGER, "SCENE_INTEGER", "integer", "integer")); enumData.push_back(SceneObjectDataTypeEnum(SCENE_PATH_NAME, "SCENE_PATH_NAME", "pathName", "pathName")); enumData.push_back(SceneObjectDataTypeEnum(SCENE_STRING, "SCENE_STRING", "string", "string")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const SceneObjectDataTypeEnum* SceneObjectDataTypeEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const SceneObjectDataTypeEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString SceneObjectDataTypeEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const SceneObjectDataTypeEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ SceneObjectDataTypeEnum::Enum SceneObjectDataTypeEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = SCENE_INVALID; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const SceneObjectDataTypeEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type SceneObjectDataTypeEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString SceneObjectDataTypeEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const SceneObjectDataTypeEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ SceneObjectDataTypeEnum::Enum SceneObjectDataTypeEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = SCENE_INVALID; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const SceneObjectDataTypeEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type SceneObjectDataTypeEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t SceneObjectDataTypeEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const SceneObjectDataTypeEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ SceneObjectDataTypeEnum::Enum SceneObjectDataTypeEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = SCENE_INVALID; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const SceneObjectDataTypeEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type SceneObjectDataTypeEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void SceneObjectDataTypeEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void SceneObjectDataTypeEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(SceneObjectDataTypeEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void SceneObjectDataTypeEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(SceneObjectDataTypeEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } /** * Get all of the XML names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the XML names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void SceneObjectDataTypeEnum::getAllXmlNames(std::vector& allXmlNames, const bool isSorted) { if (initializedFlag == false) initialize(); allXmlNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allXmlNames.push_back(SceneObjectDataTypeEnum::toXmlName(iter->enumValue)); } if (isSorted) { std::sort(allXmlNames.begin(), allXmlNames.end()); } } /** * Get an XML string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString SceneObjectDataTypeEnum::toXmlName(Enum enumValue) { if (initializedFlag == false) initialize(); const SceneObjectDataTypeEnum* enumInstance = findData(enumValue); return enumInstance->xmlName; } /** * Get an enumerated value corresponding to its XML name. * @param xmlName * XML name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ SceneObjectDataTypeEnum::Enum SceneObjectDataTypeEnum::fromXmlName(const AString& xmlName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = SCENE_INVALID; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const SceneObjectDataTypeEnum& d = *iter; if (d.xmlName == xmlName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + xmlName + "failed to match enumerated value for type SceneObjectDataTypeEnum")); } return enumValue; } workbench-1.1.1/src/Scenes/SceneObjectDataTypeEnum.h000066400000000000000000000073741255417355300223460ustar00rootroot00000000000000#ifndef __SCENE_OBJECT_DATA_TYPE_ENUM__H_ #define __SCENE_OBJECT_DATA_TYPE_ENUM__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class SceneObjectDataTypeEnum { public: /** * Enumerated values. */ enum Enum { /** Invalid */ SCENE_INVALID, /** Boolean */ SCENE_BOOLEAN, /** Class instance */ SCENE_CLASS, /** Enumerated type value */ SCENE_ENUMERATED_TYPE, /** Float */ SCENE_FLOAT, /** Integer 32-bit */ SCENE_INTEGER, /** Pathname (path to file) */ SCENE_PATH_NAME, /** String */ SCENE_STRING }; ~SceneObjectDataTypeEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static AString toXmlName(Enum enumValue); static Enum fromXmlName(const AString& xmlName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); static void getAllXmlNames(std::vector& allXmlNames, const bool isSorted); private: SceneObjectDataTypeEnum(const Enum enumValue, const AString& name, const AString& guiName, const AString& xmlName); static const SceneObjectDataTypeEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; /** Name used in XML */ AString xmlName; }; #ifdef __SCENE_OBJECT_DATA_TYPE_ENUM_DECLARE__ std::vector SceneObjectDataTypeEnum::enumData; bool SceneObjectDataTypeEnum::initializedFlag = false; int32_t SceneObjectDataTypeEnum::integerCodeCounter = 0; #endif // __SCENE_OBJECT_DATA_TYPE_ENUM_DECLARE__ } // namespace #endif //__SCENE_OBJECT_DATA_TYPE_ENUM__H_ workbench-1.1.1/src/Scenes/SceneObjectMapIntegerKey.cxx000066400000000000000000000206451255417355300230610ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SCENE_OBJECT_MAP_INTEGER_KEY_DECLARE__ #include "SceneObjectMapIntegerKey.h" #undef __SCENE_OBJECT_MAP_INTEGER_KEY_DECLARE__ #include "CaretAssert.h" #include "SceneBoolean.h" #include "SceneClass.h" #include "SceneEnumeratedType.h" #include "SceneFloat.h" #include "SceneInteger.h" #include "ScenePathName.h" #include "ScenePrimitive.h" #include "SceneString.h" using namespace caret; /** * \class caret::SceneObjectMapIntegerKey * \brief Map for saving data to a scene using integers as the key. * \ingroup Scene * * See the documentation in the class Scene for how to use the Scene system. */ /** * Constructor. * * @param name * Name of map. * @param valueDataType * Type of data stored in the map. Any items added MUST match * the data type passed to the constructor. Assertions will * check for this condition. */ SceneObjectMapIntegerKey::SceneObjectMapIntegerKey(const QString& name, const SceneObjectDataTypeEnum::Enum valueDataType) : SceneObject(name, valueDataType) { } /** * Destructor. */ SceneObjectMapIntegerKey::~SceneObjectMapIntegerKey() { for (DATA_MAP_CONST_ITERATOR iter = m_dataMap.begin(); iter != m_dataMap.end(); iter++) { delete iter->second; } } /** * Add the given boolean value to the map using the given key. * @param key * The key. * @param value * The value. */ void SceneObjectMapIntegerKey::addBoolean(const int32_t key, const bool value) { CaretAssert(getDataType() == SceneObjectDataTypeEnum::SCENE_BOOLEAN); m_dataMap.insert(std::make_pair(key, new SceneBoolean("b", value))); } /** * Add the given integer value to the map using the given key. * @param key * The key. * @param value * The value. */ void SceneObjectMapIntegerKey::addInteger(const int32_t key, const int32_t value) { CaretAssert(getDataType() == SceneObjectDataTypeEnum::SCENE_INTEGER); m_dataMap.insert(std::make_pair(key, new SceneInteger("i", value))); } /** * Add the given float value to the map using the given key. * @param key * The key. * @param value * The value. */ void SceneObjectMapIntegerKey::addFloat(const int32_t key, const float value) { CaretAssert(getDataType() == SceneObjectDataTypeEnum::SCENE_FLOAT); m_dataMap.insert(std::make_pair(key, new SceneFloat("f", value))); } /** * Add the given class value to the map using the given key. * This map will take ownership of the class. * @param key * The key. * @param value * The value. */ void SceneObjectMapIntegerKey::addClass(const int32_t key, SceneClass* value) { CaretAssert(getDataType() == SceneObjectDataTypeEnum::SCENE_CLASS); m_dataMap.insert(std::make_pair(key, value)); } /** * Add the given string value to the map using the given key. * @param key * The key. * @param value * The value. */ void SceneObjectMapIntegerKey::addString(const int32_t key, const AString& value) { CaretAssert(getDataType() == SceneObjectDataTypeEnum::SCENE_STRING); m_dataMap.insert(std::make_pair(key, new SceneString("s", value))); } /** * Add the given path name value to the map using the given key. * @param key * The key. * @param value * The value. */ void SceneObjectMapIntegerKey::addPathName(const int32_t key, const AString& value) { CaretAssert(getDataType() == SceneObjectDataTypeEnum::SCENE_PATH_NAME); m_dataMap.insert(std::make_pair(key, new ScenePathName("s", value))); } /** * Add the given enumerated type value to the map using the given key. * @param key * The key. * @param value * The value. */ void SceneObjectMapIntegerKey::addEnumeratedType(const int32_t key, const AString& value) { CaretAssert(getDataType() == SceneObjectDataTypeEnum::SCENE_ENUMERATED_TYPE); m_dataMap.insert(std::make_pair(key, new SceneEnumeratedType("e", value))); } /** * Find the SceneObject with the given key. * Key MUST be valid. * * @param key * The key. * @return * Object at the given key. */ const SceneObject* SceneObjectMapIntegerKey::getObject(const int32_t key) const { const DATA_MAP_CONST_ITERATOR iter = m_dataMap.find(key); CaretAssert(iter != m_dataMap.end()); const SceneObject* object = iter->second; CaretAssert(object); return object; } /** * Get the value as a boolean. * * @param key * key of element. * @return The value with the given key. */ bool SceneObjectMapIntegerKey::booleanValue(const int32_t key) const { const ScenePrimitive* primitive = dynamic_cast(getObject(key)); CaretAssert(primitive); return primitive->booleanValue(); } /** * Get the value as a float. * * @param key * key of element. * @return The value with the given key. */ float SceneObjectMapIntegerKey::floatValue(const int32_t key) const { const ScenePrimitive* primitive = dynamic_cast(getObject(key)); CaretAssert(primitive); return primitive->floatValue(); } /** * Get the value as a integer. * * @param key * key of element. * @return The value with the given key. */ int32_t SceneObjectMapIntegerKey::integerValue(const int32_t key) const { const ScenePrimitive* primitive = dynamic_cast(getObject(key)); CaretAssert(primitive); return primitive->integerValue(); } /** * Get the value as a string. * * @param key * key of element. * @return The value with the given key. */ AString SceneObjectMapIntegerKey::stringValue(const int32_t key) const { const ScenePrimitive* primitive = dynamic_cast(getObject(key)); CaretAssert(primitive); return primitive->stringValue(); } /** * Get the path name as a string. * * @param key * key of element. * @return The value with the given key. */ AString SceneObjectMapIntegerKey::pathNameValue(const int32_t key) const { const ScenePathName* pathName = dynamic_cast(getObject(key)); CaretAssert(pathName); return pathName->stringValue(); } /** * Get the value as a class. * * @param key * key of element. * @return The class with the given key. */ const SceneClass* SceneObjectMapIntegerKey::classValue(const int32_t key) const { const SceneClass* sceneClass = dynamic_cast(getObject(key)); CaretAssert(sceneClass); return sceneClass; } /** * Get the value as a enumerated type stirng. * * @param key * key of element. * @return The enumerated type value with the given key. */ AString SceneObjectMapIntegerKey::enumeratedTypeValue(const int32_t key) const { const SceneEnumeratedType* enumType = dynamic_cast(getObject(key)); CaretAssert(enumType); return enumType->stringValue(); } /** * @return A vector containg all of the keys in the map. */ std::vector SceneObjectMapIntegerKey::getKeys() const { std::vector theKeys; theKeys.reserve(m_dataMap.size()); for (DATA_MAP_CONST_ITERATOR iter = m_dataMap.begin(); iter != m_dataMap.end(); iter++) { theKeys.push_back(iter->first); } return theKeys; } /** * @return An iterator for all values in * the map. */ const std::map& SceneObjectMapIntegerKey::getMap() const { return m_dataMap; } workbench-1.1.1/src/Scenes/SceneObjectMapIntegerKey.h000066400000000000000000000104301255417355300224750ustar00rootroot00000000000000#ifndef __SCENE_OBJECT_MAP_INTEGER_KEY_H__ #define __SCENE_OBJECT_MAP_INTEGER_KEY_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretAssert.h" #include "SceneObject.h" namespace caret { class SceneClass; class SceneObjectMapIntegerKey : public SceneObject { public: virtual ~SceneObjectMapIntegerKey(); SceneObjectMapIntegerKey(const QString& name, const SceneObjectDataTypeEnum::Enum valueDataType); private: SceneObjectMapIntegerKey(const SceneObjectMapIntegerKey&); SceneObjectMapIntegerKey& operator=(const SceneObjectMapIntegerKey&); public: // ADD_NEW_METHODS_HERE void addBoolean(const int32_t key, const bool value); void addInteger(const int32_t key, const int32_t value); void addFloat(const int32_t key, const float value); void addString(const int32_t key, const AString& value); void addEnumeratedType(const int32_t key, const AString& value); void addPathName(const int32_t key, const AString& value); /** * Add the given enumerated type value to the map using the given key. * @param key * The key. * @param value * The value. */ template void addEnumeratedType(const int32_t key, ET enumeratedValue) { CaretAssert(getDataType() == SceneObjectDataTypeEnum::SCENE_ENUMERATED_TYPE); const AString stringValue = T::toName(enumeratedValue); addEnumeratedType(key, stringValue); } void addClass(const int32_t key, SceneClass* value); std::vector getKeys() const; const std::map& getMap() const; const SceneObject* getObject(const int32_t key) const; const SceneClass* classValue(const int32_t key) const; AString enumeratedTypeValue(const int32_t key) const; /** * Get an enumerated type value * @param key * Kye of enumerated type value. */ template ET getEnumeratedTypeValue(const int32_t key) const { const AString stringValue = enumeratedTypeValue(key); bool valid = false; ET value = T::fromName(stringValue, &valid); return value; } bool booleanValue(const int32_t key) const; float floatValue(const int32_t key) const; int32_t integerValue(const int32_t key) const; AString stringValue(const int32_t key) const; AString pathNameValue(const int32_t key) const; private: typedef std::map DATA_MAP; typedef DATA_MAP::const_iterator DATA_MAP_CONST_ITERATOR; // ADD_NEW_MEMBERS_HERE DATA_MAP m_dataMap; }; #ifdef __SCENE_OBJECT_MAP_INTEGER_KEY_DECLARE__ // #endif // __SCENE_OBJECT_MAP_INTEGER_KEY_DECLARE__ } // namespace #endif //__SCENE_OBJECT_MAP_INTEGER_KEY_H__ workbench-1.1.1/src/Scenes/ScenePathName.cxx000066400000000000000000000115131255417355300207150ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SCENE_PATH_NAME_DECLARE__ #include "ScenePathName.h" #undef __SCENE_PATH_NAME_DECLARE__ #include "CaretLogger.h" #include "DataFile.h" #include "FileInformation.h" #include "Scene.h" using namespace caret; /** * \class caret::ScenePathName * \brief For storage of a path name in a scene. * \ingroup Scene * * See the documentation in the class Scene for how to use the Scene system. * * Stores a path name (file name) in a scene. * When the scene file is written, the a path relative * to the scene file's path is stored in the scene file. * When the path is read from the scene file, the path * is converted to an absolute path in memory. * Thus: In memory (RAM) the path is an absolute path * and when written to file (DISK), the path is relative * to the scene file. */ /** * Constructor. * * @param name * Name of object. * @param value * Value of object. */ ScenePathName::ScenePathName(const AString& name, const AString& value) : SceneObject(name, SceneObjectDataTypeEnum::SCENE_PATH_NAME) { setValue(value); } /** * Destructor. */ ScenePathName::~ScenePathName() { } /** * Set the value. * @param value * New value. */ void ScenePathName::setValue(const AString& value) { m_value = value; if (DataFile::isFileOnNetwork(m_value)) { Scene::setSceneBeingCreatedHasFilesWithRemotePaths(); } } /** * @return The value as a string data type. */ AString ScenePathName::stringValue() const { return m_value; } /** * Set the value. If the value is not an absolute path, make the * new value an absolute path assuming that the value is relative * to the scene file. * @param sceneFileName * Name of scene file. * @param value * New value. */ void ScenePathName::setValueToAbsolutePath(const AString& sceneFileName, const AString& value) { AString name = value; if (name.isEmpty() == false) { FileInformation sceneFileInfo(sceneFileName); if (sceneFileInfo.isAbsolute()) { if (DataFile::isFileOnNetwork(name) == false) { FileInformation fileInfo(name); if (fileInfo.isRelative()) { FileInformation fileInfo(sceneFileInfo.getPathName(), name); name = fileInfo.getAbsoluteFilePath(); } } } // const AString message = ("After converting TO absolute path, " // + value // + " becomes " // + name); // CaretLogFine(message); } m_value = name; } /** * Using the given scene file name, return a path relative to the scene file. * @param sceneFileName * Name of scene file. * @return * Value of this item (path) relative to the given scene file. */ AString ScenePathName::getRelativePathToSceneFile(const AString& sceneFileName) const { AString name = m_value; if (name.isEmpty() == false) { FileInformation fileInfo(name); if (fileInfo.isAbsolute()) { FileInformation specFileInfo(sceneFileName); if (specFileInfo.isAbsolute()) { const AString newPath = SystemUtilities::relativePath(fileInfo.getPathName(), specFileInfo.getPathName()); if (newPath.isEmpty()) { name = fileInfo.getFileName(); } else { name = (newPath + "/" + fileInfo.getFileName()); } } } // const AString message = ("After converting FROM absolute path, " // + m_value // + " becomes " // + name); // CaretLogFine(message); } return name; } workbench-1.1.1/src/Scenes/ScenePathName.h000066400000000000000000000035771255417355300203550ustar00rootroot00000000000000#ifndef __SCENE_PATH_NAME__H_ #define __SCENE_PATH_NAME__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "SceneObject.h" namespace caret { class ScenePathName : public SceneObject { public: ScenePathName(const AString& name, const AString& value); virtual ~ScenePathName(); void setValue(const AString& value); virtual AString stringValue() const; void setValueToAbsolutePath(const AString& sceneFileName, const AString& value); AString getRelativePathToSceneFile(const AString& sceneFileName) const; private: ScenePathName(const ScenePathName&); ScenePathName& operator=(const ScenePathName&); public: // ADD_NEW_METHODS_HERE private: AString m_value; // ADD_NEW_MEMBERS_HERE }; #ifdef __SCENE_PATH_NAME_DECLARE__ // #endif // __SCENE_PATH_NAME_DECLARE__ } // namespace #endif //__SCENE_PATH_NAME__H_ workbench-1.1.1/src/Scenes/ScenePathNameArray.cxx000066400000000000000000000114341255417355300217160ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SCENE_PATH_NAME_ARRAY_DECLARE__ #include "ScenePathNameArray.h" #undef __SCENE_PATH_NAME_ARRAY_DECLARE__ #include "CaretAssert.h" #include "SceneClass.h" #include "ScenePathName.h" using namespace caret; /** * \class caret::ScenePathNameArray * \brief Stores an array of scene path names. * \ingroup Scene * * See the documentation in the class Scene for how to use the Scene system. */ /** * Constructor. * * @param name * Name of the array. * @param pathNameValues * Values in the array. * @param numberOfArrayElements * Number of elements in the array. */ ScenePathNameArray::ScenePathNameArray(const AString& name, const AString pathNameValues[], const int32_t numberOfArrayElements) : SceneObjectArray(name, SceneObjectDataTypeEnum::SCENE_PATH_NAME, numberOfArrayElements) { m_values.resize(numberOfArrayElements); for (int32_t i = 0; i < numberOfArrayElements; i++) { m_values[i] = new ScenePathName(name, pathNameValues[i]); } } /** * Constructor. * * @param name * Name of the array. * @param pathNameValues * Vector containing values for the array. */ ScenePathNameArray::ScenePathNameArray(const AString& name, const std::vector& pathNameValues) : SceneObjectArray(name, SceneObjectDataTypeEnum::SCENE_PATH_NAME, pathNameValues.size()) { const int32_t numberOfArrayElements = static_cast(pathNameValues.size()); for (int32_t i = 0; i < numberOfArrayElements; i++) { m_values.push_back(new ScenePathName(name, pathNameValues[i])); } } /** * Constructor that creates an array of NULL values. * * @param name * Name of the array. * @param numberOfArrayElements * Number of elements in the array. */ ScenePathNameArray::ScenePathNameArray(const AString& name, const int numberOfArrayElements) : SceneObjectArray(name, SceneObjectDataTypeEnum::SCENE_PATH_NAME, numberOfArrayElements) { m_values.resize(numberOfArrayElements); std::fill(m_values.begin(), m_values.end(), (ScenePathName*)NULL); } /** * Destructor. */ ScenePathNameArray::~ScenePathNameArray() { for (std::vector::iterator iter = m_values.begin(); iter != m_values.end(); iter++) { delete *iter; } m_values.clear(); } /** * Set the class for an array index. * @param arrayIndex * Index of element. * @param sceneFileName * Full path of scene file. * @param pathNameValue * New value for array element. */ void ScenePathNameArray::setScenePathNameAtIndex(const int32_t arrayIndex, const AString& sceneFileName, const AString& pathNameValue) { CaretAssertVectorIndex(m_values, arrayIndex); if (m_values[arrayIndex] != NULL) { delete m_values[arrayIndex]; } ScenePathName* spn = new ScenePathName(getName(), pathNameValue); spn->setValueToAbsolutePath(sceneFileName, pathNameValue); m_values[arrayIndex] = spn; } ///** // * Get the element for a given array index. // * @param arrayIndex // * Index of array element. // * @return // * Class at the given array index. // */ //SceneClass* //ScenePathNameArray::getValue(const int32_t arrayIndex) //{ // CaretAssertVectorIndex(m_values, arrayIndex); // return m_values[arrayIndex]; //} /** * Get the element for a given array index. * @param arrayIndex * Index of array element. * @return * Pathname at the given array index. */ ScenePathName* ScenePathNameArray::getScenePathNameAtIndex(const int32_t arrayIndex) const { CaretAssertVectorIndex(m_values, arrayIndex); return m_values[arrayIndex]; } workbench-1.1.1/src/Scenes/ScenePathNameArray.h000066400000000000000000000044261255417355300213460ustar00rootroot00000000000000#ifndef __SCENE_PATH_NAME_ARRAY__H_ #define __SCENE_PATH_NAME_ARRAY__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "SceneObjectArray.h" namespace caret { class ScenePathName; class ScenePathNameArray : public SceneObjectArray { public: ScenePathNameArray(const AString& name, const AString pathNameValues[], const int32_t numberOfArrayElements); ScenePathNameArray(const AString& name, const std::vector& pathNameValues); ScenePathNameArray(const AString& name, const int numberOfArrayElements); virtual ~ScenePathNameArray(); void setScenePathNameAtIndex(const int32_t arrayIndex, const AString& sceneFileName, const AString& pathNameValue); ScenePathName* getScenePathNameAtIndex(const int32_t arrayIndex) const; private: ScenePathNameArray(const ScenePathNameArray&); ScenePathNameArray& operator=(const ScenePathNameArray&); public: // ADD_NEW_METHODS_HERE private: // ADD_NEW_MEMBERS_HERE std::vector m_values; }; #ifdef __SCENE_PATH_NAME_ARRAY_DECLARE__ // #endif // __SCENE_PATH_NAME_ARRAY_DECLARE__ } // namespace #endif //__SCENE_PATH_NAME_ARRAY__H_ workbench-1.1.1/src/Scenes/ScenePrimitive.cxx000066400000000000000000000031301255417355300211640ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SCENE_PRIMITIVE_DECLARE__ #include "ScenePrimitive.h" #undef __SCENE_PRIMITIVE_DECLARE__ using namespace caret; /** * \class caret::ScenePrimitive * \brief Abstract class for 'primitive' data types for scenes. * \ingroup Scene * * See the documentation in the class Scene for how to use the Scene system. * * Design loosely based upon Java.lang.Number but also including * boolean and string values. */ /** * Constructor. * @param name * Name of primitive. * @param dataType * Data type of the primitive. */ ScenePrimitive::ScenePrimitive(const QString& name, const SceneObjectDataTypeEnum::Enum dataType) : SceneObject(name, dataType) { } /** * Destructor. */ ScenePrimitive::~ScenePrimitive() { } workbench-1.1.1/src/Scenes/ScenePrimitive.h000066400000000000000000000037621255417355300206240ustar00rootroot00000000000000#ifndef __SCENE_PRIMITIVE__H_ #define __SCENE_PRIMITIVE__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "SceneObject.h" namespace caret { class ScenePrimitive : public SceneObject { public: virtual ~ScenePrimitive(); protected: ScenePrimitive(const QString& name, const SceneObjectDataTypeEnum::Enum dataType); private: ScenePrimitive(const ScenePrimitive&); ScenePrimitive& operator=(const ScenePrimitive&); public: // ADD_NEW_METHODS_HERE /** @return Value as a boolean data type */ virtual bool booleanValue() const = 0; /** @return Value as a float data type */ virtual float floatValue() const = 0; /** @return Value as a integer data type */ virtual int32_t integerValue() const = 0; /** @return Value as a string data type */ virtual AString stringValue() const = 0; private: // ADD_NEW_MEMBERS_HERE }; #ifdef __SCENE_PRIMITIVE_DECLARE__ // #endif // __SCENE_PRIMITIVE_DECLARE__ } // namespace #endif //__SCENE_PRIMITIVE__H_ workbench-1.1.1/src/Scenes/ScenePrimitiveArray.cxx000066400000000000000000000223341255417355300221720ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SCENE_PRIMITIVE_ARRAY_DECLARE__ #include "ScenePrimitiveArray.h" #undef __SCENE_PRIMITIVE_ARRAY_DECLARE__ using namespace caret; /** * \class caret::ScenePrimitiveArray * \brief Abstract class for 'primitive' data types for scenes. * \ingroup Scene * * See the documentation in the class Scene for how to use the Scene system. * * Design loosely based upon Java.lang.Number but also including * boolean and string values. */ /** * Constructor. * @param name * Name of primitive. * @param dataType * Data type of the primitive. */ ScenePrimitiveArray::ScenePrimitiveArray(const QString& name, const SceneObjectDataTypeEnum::Enum dataType, const int32_t numberOfArrayElements) : SceneObjectArray(name, dataType, numberOfArrayElements) { } /** * Destructor. */ ScenePrimitiveArray::~ScenePrimitiveArray() { } /** * Load the array with boolean values. * @param valuesOut * Array into which boolean values are loaded. * @param arrayNumberOfElements * Number of elements in the array. If this value is greater * than the number of elements in the scene, the remaining * elements will be filled with the default value * @param defaultValue * Default value used when output array contains more elements * than are in this instance's array. */ void ScenePrimitiveArray::booleanValues(bool valuesOut[], const int32_t arrayNumberOfElements, const bool defaultValue) const { const int32_t numElem = std::min(arrayNumberOfElements, m_numberOfArrayElements); for (int32_t i = 0; i < numElem; i++) { valuesOut[i] = booleanValue(i); } for (int32_t i = numElem; i < arrayNumberOfElements; i++) { valuesOut[i] = defaultValue; } } /** * Load the vector with the boolean values from the scene. * @param valuesOut * Vector into which values are loaded. The vector * will contain the number of elements that were saved * to the scene. * @param defaultValue * Default value used when output vector contains more elements * than are in this instance's array. */ void ScenePrimitiveArray::booleanValues(std::vector& valuesOut, const bool defaultValue) const { const int32_t valuesOutNumberOfElements = static_cast(valuesOut.size()); const int32_t numElem = std::min(valuesOutNumberOfElements, m_numberOfArrayElements); for (int32_t i = 0; i < numElem; i++) { valuesOut[i] = booleanValue(i); } for (int32_t i = numElem; i < valuesOutNumberOfElements; i++) { valuesOut[i] = defaultValue; } } /** * Load the array with float values. * @param valuesOut * Array into which boolean values are loaded. * @param arrayNumberOfElements * Number of elements in the array. If this value is greater * than the number of elements in the scene, the remaining * elements will be filled with the default value * @param defaultValue * Default value used when output array contains more elements * than are in this instance's array. */ void ScenePrimitiveArray::floatValues(float valuesOut[], const int32_t arrayNumberOfElements, const float defaultValue) const { const int32_t numElem = std::min(arrayNumberOfElements, m_numberOfArrayElements); for (int32_t i = 0; i < numElem; i++) { valuesOut[i] = floatValue(i); } for (int32_t i = numElem; i < arrayNumberOfElements; i++) { valuesOut[i] = defaultValue; } } /** * Load the vector with the float values from the scene. * @param valuesOut * Vector into which values are loaded. The vector * will contain the number of elements that were saved * to the scene. * @param defaultValue * Default value used when output vector contains more elements * than are in this instance's array. */ void ScenePrimitiveArray::floatValues(std::vector& valuesOut, const float defaultValue) const { const int32_t valuesOutNumberOfElements = static_cast(valuesOut.size()); const int32_t numElem = std::min(valuesOutNumberOfElements, m_numberOfArrayElements); for (int32_t i = 0; i < numElem; i++) { valuesOut[i] = floatValue(i); } for (int32_t i = numElem; i < valuesOutNumberOfElements; i++) { valuesOut[i] = defaultValue; } } /** * Load the array with integer values. * @param valuesOut * Array into which boolean values are loaded. * @param arrayNumberOfElements * Number of elements in the array. If this value is greater * than the number of elements in the scene, the remaining * elements will be filled with the default value * @param defaultValue * Default value used when output array contains more elements * than are in this instance's array. */ void ScenePrimitiveArray::integerValues(int32_t valuesOut[], const int32_t arrayNumberOfElements, const int32_t defaultValue) const { const int32_t numElem = std::min(arrayNumberOfElements, m_numberOfArrayElements); for (int32_t i = 0; i < numElem; i++) { valuesOut[i] = integerValue(i); } for (int32_t i = numElem; i < arrayNumberOfElements; i++) { valuesOut[i] = defaultValue; } } /** * Load the vector with the integer values from the scene. * @param valuesOut * Vector into which values are loaded. The vector * will contain the number of elements that were saved * to the scene. * @param defaultValue * Default value used when output vector contains more elements * than are in this instance's array. */ void ScenePrimitiveArray::integerValues(std::vector& valuesOut, const int32_t defaultValue) const { const int32_t valuesOutNumberOfElements = static_cast(valuesOut.size()); const int32_t numElem = std::min(valuesOutNumberOfElements, m_numberOfArrayElements); for (int32_t i = 0; i < numElem; i++) { valuesOut[i] = integerValue(i); } for (int32_t i = numElem; i < valuesOutNumberOfElements; i++) { valuesOut[i] = defaultValue; } } /** * Load the array with string values. * @param valuesOut * Array into which boolean values are loaded. * @param arrayNumberOfElements * Number of elements in the array. If this value is greater * than the number of elements in the scene, the remaining * elements will be filled with the default value * @param defaultValue * Default value used when output array contains more elements * than are in this instance's array. */ void ScenePrimitiveArray::stringValues(AString valuesOut[], const int32_t arrayNumberOfElements, const AString& defaultValue) const { const int32_t numElem = std::min(arrayNumberOfElements, m_numberOfArrayElements); for (int32_t i = 0; i < numElem; i++) { valuesOut[i] = stringValue(i); } for (int32_t i = numElem; i < arrayNumberOfElements; i++) { valuesOut[i] = defaultValue; } } /** * Load the vector with the string values from the scene. * @param valuesOut * Vector into which values are loaded. The vector * will contain the number of elements that were saved * to the scene. * @param defaultValue * Default value used when output vector contains more elements * than are in this instance's array. */ void ScenePrimitiveArray::stringValues(std::vector& valuesOut, const AString& defaultValue) const { const int32_t valuesOutNumberOfElements = static_cast(valuesOut.size()); const int32_t numElem = std::min(valuesOutNumberOfElements, m_numberOfArrayElements); for (int32_t i = 0; i < numElem; i++) { valuesOut[i] = stringValue(i); } for (int32_t i = numElem; i < valuesOutNumberOfElements; i++) { valuesOut[i] = defaultValue; } } workbench-1.1.1/src/Scenes/ScenePrimitiveArray.h000066400000000000000000000077421255417355300216250ustar00rootroot00000000000000#ifndef __SCENE_PRIMITIVE_ARRAY__H_ #define __SCENE_PRIMITIVE_ARRAY__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "SceneObjectArray.h" namespace caret { class ScenePrimitiveArray : public SceneObjectArray { public: virtual ~ScenePrimitiveArray(); protected: ScenePrimitiveArray(const QString& name, const SceneObjectDataTypeEnum::Enum dataType, const int32_t numberOfArrayElements); private: ScenePrimitiveArray(const ScenePrimitiveArray&); ScenePrimitiveArray& operator=(const ScenePrimitiveArray&); public: // ADD_NEW_METHODS_HERE /** * Get the values as a boolean. * @param arrayIndex * Index of element. * @return The value. */ virtual bool booleanValue(const int32_t arrayIndex) const = 0; /** * Get the values as a float. * @param arrayIndex * Index of element. * @return The value. */ virtual float floatValue(const int32_t arrayIndex) const = 0; /** * Get the values as a integer. * @param arrayIndex * Index of element. * @return The value. */ virtual int32_t integerValue(const int32_t arrayIndex) const = 0; /** * Get the values as a string. * @param arrayIndex * Index of element. * @return The value. */ virtual AString stringValue(const int32_t arrayIndex) const = 0; virtual void booleanValues(bool valuesOut[], const int32_t arrayNumberOfElements, const bool defaultValue) const; virtual void booleanValues(std::vector& valuesOut, const bool defaultValue) const; virtual void floatValues(float valuesOut[], const int32_t arrayNumberOfElements, const float defaultValue) const; virtual void floatValues(std::vector& valuesOut, const float defaultValue) const; virtual void integerValues(int32_t valuesOut[], const int32_t arrayNumberOfElements, const int32_t defaultValue) const; virtual void integerValues(std::vector& valuesOut, const int32_t defaultValue) const; virtual void stringValues(AString valuesOut[], const int32_t arrayNumberOfElements, const AString& defaultValue) const; virtual void stringValues(std::vector& valuesOut, const AString& defaultValue) const; private: // ADD_NEW_MEMBERS_HERE }; #ifdef __SCENE_PRIMITIVE_ARRAY_DECLARE__ // #endif // __SCENE_PRIMITIVE_ARRAY_DECLARE__ } // namespace #endif //__SCENE_PRIMITIVE_ARRAY__H_ workbench-1.1.1/src/Scenes/SceneSaxReader.cxx000066400000000000000000001004201255417355300210720ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretAssert.h" #include "CaretLogger.h" #include "DataFile.h" #include "Scene.h" #include "SceneBoolean.h" #include "SceneBooleanArray.h" #include "SceneClass.h" #include "SceneClassArray.h" #include "SceneEnumeratedType.h" #include "SceneEnumeratedTypeArray.h" #include "SceneFloat.h" #include "SceneFloatArray.h" #include "SceneInteger.h" #include "SceneIntegerArray.h" #include "SceneObjectMapIntegerKey.h" #include "ScenePathName.h" #include "ScenePathNameArray.h" #include "SceneSaxReader.h" #include "SceneString.h" #include "SceneStringArray.h" #include "SceneXmlElements.h" #include "XmlAttributes.h" #include "XmlException.h" #include "XmlUtilities.h" using namespace caret; static bool debugFlag = false; /** * constructor. * @param sceneFileName * Name of scene file being read. * @param scene * Scene that is being read. */ SceneSaxReader::SceneSaxReader(const AString& sceneFileName, Scene* scene) : m_sceneFileName(sceneFileName) { m_state = STATE_NONE; m_stateStack.push(m_state); m_elementText = ""; m_scene = scene; } /** * destructor. */ SceneSaxReader::~SceneSaxReader() { } /** * start an element. */ void SceneSaxReader::startElement(const AString& /* namespaceURI */, const AString& /* localName */, const AString& qName, const XmlAttributes& attributes) { const STATE previousState = m_state; switch (m_state) { case STATE_NONE: if (qName == SceneXmlElements::SCENE_TAG) { m_state = STATE_SCENE; } else { const AString msg = ("While reading Scene XML, expected the XML tag to be " + SceneXmlElements::SCENE_TAG + " but found " + qName); XmlSaxParserException e(msg); CaretLogThrowing(e); throw e; } break; case STATE_SCENE: if (qName == SceneXmlElements::SCENE_NAME_TAG) { m_state = STATE_SCENE_NAME; } else if (qName == SceneXmlElements::SCENE_DESCRIPTION_TAG) { m_state = STATE_SCENE_DESCRIPTION; } else if (qName == SceneXmlElements::OBJECT_TAG) { m_state = STATE_OBJECT; processObjectStartTag(attributes); } else { const AString msg = XmlUtilities::createInvalidChildElementMessage(SceneXmlElements::SCENE_TAG, qName); XmlSaxParserException e(msg); CaretLogThrowing(e); throw e; } break; case STATE_SCENE_NAME: break; case STATE_SCENE_DESCRIPTION: break; case STATE_OBJECT: if (qName == SceneXmlElements::OBJECT_TAG) { m_state = STATE_OBJECT; processObjectStartTag(attributes); } else if (qName == SceneXmlElements::OBJECT_ARRAY_TAG) { m_state = STATE_OBJECT_ARRAY; processObjectArrayStartTag(attributes); } else if (qName == SceneXmlElements::OBJECT_MAP_TAG) { m_state = STATE_OBJECT_MAP; processObjectMapStartTag(attributes); } else { const AString msg = XmlUtilities::createInvalidChildElementMessage(SceneXmlElements::OBJECT_TAG, qName); XmlSaxParserException e(msg); CaretLogThrowing(e); throw e; } break; case STATE_OBJECT_ARRAY: if (qName == SceneXmlElements::OBJECT_ARRAY_ELEMENT_TAG) { const int32_t elementIndex = attributes.getValueAsInt(SceneXmlElements::OBJECT_ARRAY_ELEMENT_INDEX_ATTRIBUTE, -1); if (elementIndex < 0) { AString msg = XmlUtilities::createInvalidAttributeMessage(qName, SceneXmlElements::OBJECT_ARRAY_ELEMENT_INDEX_ATTRIBUTE, attributes.getValue(SceneXmlElements::OBJECT_ARRAY_ELEMENT_INDEX_ATTRIBUTE)); msg += (" Must be greater than or equal to zero."); XmlSaxParserException e(msg); CaretLogThrowing(e); throw e; } m_objectArrayBeingReadElementIndexStack.push(elementIndex); m_state = STATE_OBJECT_ARRAY_ELEMENT; } else { const AString msg = XmlUtilities::createInvalidChildElementMessage(SceneXmlElements::OBJECT_ARRAY_TAG, qName); XmlSaxParserException e(msg); CaretLogThrowing(e); throw e; } break; case STATE_OBJECT_ARRAY_ELEMENT: if (qName == SceneXmlElements::OBJECT_TAG) { m_state = STATE_OBJECT; processObjectStartTag(attributes); } else { const AString msg = XmlUtilities::createInvalidChildElementMessage(SceneXmlElements::OBJECT_ARRAY_ELEMENT_TAG, qName); XmlSaxParserException e(msg); CaretLogThrowing(e); throw e; } break; case STATE_OBJECT_MAP: if (qName == SceneXmlElements::OBJECT_MAP_VALUE_TAG) { const int32_t key = attributes.getValueAsInt(SceneXmlElements::OBJECT_MAP_VALUE_KEY_ATTRIBUTE, -1); if (key < 0) { AString msg = XmlUtilities::createInvalidAttributeMessage(qName, SceneXmlElements::OBJECT_MAP_VALUE_KEY_ATTRIBUTE, attributes.getValue(SceneXmlElements::OBJECT_MAP_VALUE_KEY_ATTRIBUTE)); msg += (" Must be greater than or equal to zero."); XmlSaxParserException e(msg); CaretLogThrowing(e); throw e; } m_objectMapBeingReadValueKeyStack.push(key); m_state = STATE_OBJECT_MAP_VALUE; } else { const AString msg = XmlUtilities::createInvalidChildElementMessage(SceneXmlElements::OBJECT_MAP_VALUE_TAG, qName); XmlSaxParserException e(msg); CaretLogThrowing(e); throw e; } break; case STATE_OBJECT_MAP_VALUE: if (qName == SceneXmlElements::OBJECT_TAG) { m_state = STATE_OBJECT; processObjectStartTag(attributes); } else { const AString msg = XmlUtilities::createInvalidChildElementMessage(SceneXmlElements::OBJECT_MAP_VALUE_TAG, qName); XmlSaxParserException e(msg); CaretLogThrowing(e); throw e; } break; } // // Save previous state // m_stateStack.push(previousState); m_elementText = ""; } /** * Process an Object start tag. * @param tag * Tag that was read indicating Object, ObjectArray, etc. * @param attributes * Attributes contained in the Object tag. */ void SceneSaxReader::processObjectStartTag(const XmlAttributes& attributes) { /* * Get attributes of the object element */ const AString objectTypeName = attributes.getValue(SceneXmlElements::OBJECT_TYPE_ATTRIBUTE); const AString objectName = attributes.getValue(SceneXmlElements::OBJECT_NAME_ATTRIBUTE); const AString objectClassName = attributes.getValue(SceneXmlElements::OBJECT_CLASS_ATTRIBUTE); const int32_t objectVersion = attributes.getValueAsInt(SceneXmlElements::OBJECT_VERSION_ATTRIBUTE); /* * Get the type of the object */ bool validObjectType = false; const SceneObjectDataTypeEnum::Enum objectDataType = SceneObjectDataTypeEnum::fromXmlName(objectTypeName, &validObjectType); if (validObjectType == false) { const AString msg = XmlUtilities::createInvalidAttributeMessage(SceneXmlElements::OBJECT_TAG, SceneXmlElements::OBJECT_TYPE_ATTRIBUTE, objectTypeName); XmlSaxParserException e(msg); CaretLogThrowing(e); throw e; } SceneObject* sceneObject = NULL; switch (objectDataType) { case SceneObjectDataTypeEnum::SCENE_CLASS: sceneObject = new SceneClass(objectName, objectClassName, objectVersion); break; case SceneObjectDataTypeEnum::SCENE_BOOLEAN: sceneObject = new SceneBoolean(objectName, false); break; case SceneObjectDataTypeEnum::SCENE_FLOAT: sceneObject = new SceneFloat(objectName, 0.0); break; case SceneObjectDataTypeEnum::SCENE_ENUMERATED_TYPE: sceneObject = new SceneEnumeratedType(objectName, ""); break; case SceneObjectDataTypeEnum::SCENE_INTEGER: sceneObject = new SceneInteger(objectName, 0); break; case SceneObjectDataTypeEnum::SCENE_PATH_NAME: sceneObject = new ScenePathName(objectName, ""); break; case SceneObjectDataTypeEnum::SCENE_STRING: sceneObject = new SceneString(objectName, ""); break; case SceneObjectDataTypeEnum::SCENE_INVALID: CaretAssert(0); // should never get here, 'validObjectType' above break; } /* * Track object being read to ensure proper parenting of children objects */ CaretAssert(sceneObject); m_objectBeingReadStack.push(sceneObject); if (debugFlag) std::cout << "Pushed Object:" << qPrintable(sceneObject->getName()) << " Type=" << qPrintable(objectTypeName) << std::endl; } /** * Process an ObjectArray start tag. * @param tag * Tag that was read indicating Object, ObjectArray, etc. * @param attributes * Attributes contained in the Object tag. */ void SceneSaxReader::processObjectArrayStartTag(const XmlAttributes& attributes) { /* * Get attributes of the object element */ const AString objectTypeName = attributes.getValue(SceneXmlElements::OBJECT_TYPE_ATTRIBUTE); const AString objectName = attributes.getValue(SceneXmlElements::OBJECT_NAME_ATTRIBUTE); const AString objectClassName = attributes.getValue(SceneXmlElements::OBJECT_CLASS_ATTRIBUTE); const int32_t objectNumberOfElements = attributes.getValueAsInt(SceneXmlElements::OBJECT_ARRAY_LENGTH_ATTRIBUTE, -1); if (objectNumberOfElements < 0) { AString msg = XmlUtilities::createInvalidAttributeMessage(SceneXmlElements::OBJECT_ARRAY_TAG, SceneXmlElements::OBJECT_ARRAY_LENGTH_ATTRIBUTE, attributes.getValue(SceneXmlElements::OBJECT_ARRAY_LENGTH_ATTRIBUTE)); msg += (" Must be greater than or equal to zero."); XmlSaxParserException e(msg); CaretLogThrowing(e); throw e; } /* * Get the type of the object */ bool validObjectType = false; const SceneObjectDataTypeEnum::Enum objectDataType = SceneObjectDataTypeEnum::fromXmlName(objectTypeName, &validObjectType); if (validObjectType == false) { const AString msg = XmlUtilities::createInvalidAttributeMessage(SceneXmlElements::OBJECT_ARRAY_TAG, SceneXmlElements::OBJECT_TYPE_ATTRIBUTE, objectTypeName); XmlSaxParserException e(msg); CaretLogThrowing(e); throw e; } SceneObject* sceneObject = NULL; switch (objectDataType) { case SceneObjectDataTypeEnum::SCENE_CLASS: sceneObject = new SceneClassArray(objectName, objectNumberOfElements); break; case SceneObjectDataTypeEnum::SCENE_BOOLEAN: sceneObject = new SceneBooleanArray(objectName, objectNumberOfElements); break; case SceneObjectDataTypeEnum::SCENE_FLOAT: sceneObject = new SceneFloatArray(objectName, objectNumberOfElements); break; case SceneObjectDataTypeEnum::SCENE_ENUMERATED_TYPE: sceneObject = new SceneEnumeratedTypeArray(objectName, objectNumberOfElements); break; case SceneObjectDataTypeEnum::SCENE_INTEGER: sceneObject = new SceneIntegerArray(objectName, objectNumberOfElements); break; case SceneObjectDataTypeEnum::SCENE_PATH_NAME: sceneObject = new ScenePathNameArray(objectName, objectNumberOfElements); break; case SceneObjectDataTypeEnum::SCENE_STRING: sceneObject = new SceneStringArray(objectName, objectNumberOfElements); break; case SceneObjectDataTypeEnum::SCENE_INVALID: break; } /* * Track object being read to ensure proper parenting of children objects */ CaretAssert(sceneObject); m_objectBeingReadStack.push(sceneObject); if (debugFlag) std::cout << "Pushed ObjectArray:" << qPrintable(sceneObject->getName()) << " Type=" << qPrintable(objectTypeName) << std::endl; } /** * Process an ObjectMap start tag. * @param tag * Tag that was read indicating Object, ObjectArray, etc. * @param attributes * Attributes contained in the Object tag. */ void SceneSaxReader::processObjectMapStartTag(const XmlAttributes& attributes) { /* * Get attributes of the object element */ const AString objectTypeName = attributes.getValue(SceneXmlElements::OBJECT_TYPE_ATTRIBUTE); const AString objectName = attributes.getValue(SceneXmlElements::OBJECT_NAME_ATTRIBUTE); const AString objectClassName = attributes.getValue(SceneXmlElements::OBJECT_CLASS_ATTRIBUTE); /* * Get the type of the object */ bool validObjectType = false; const SceneObjectDataTypeEnum::Enum objectDataType = SceneObjectDataTypeEnum::fromXmlName(objectTypeName, &validObjectType); if (validObjectType == false) { const AString msg = XmlUtilities::createInvalidAttributeMessage(SceneXmlElements::OBJECT_ARRAY_TAG, SceneXmlElements::OBJECT_TYPE_ATTRIBUTE, objectTypeName); XmlSaxParserException e(msg); CaretLogThrowing(e); throw e; } SceneObjectMapIntegerKey* sceneMap = new SceneObjectMapIntegerKey(objectName, objectDataType); /* * Track object being read to ensure proper parenting of children objects */ CaretAssert(sceneMap); m_objectBeingReadStack.push(sceneMap); if (debugFlag) std::cout << "Pushed ObjectMap:" << qPrintable(sceneMap->getName()) << " Type=" << qPrintable(objectTypeName) << std::endl; } /** * end an element. */ void SceneSaxReader::endElement(const AString& /* namspaceURI */, const AString& /* localName */, const AString& /*qName*/) { const AString stringValue = m_elementText.trimmed(); switch (m_state) { case STATE_NONE: break; case STATE_SCENE: break; case STATE_SCENE_NAME: m_scene->setName(stringValue); break; case STATE_SCENE_DESCRIPTION: m_scene->setDescription(stringValue); break; case STATE_OBJECT: { CaretAssert(m_objectBeingReadStack.empty() == false); SceneObject* sceneObject = m_objectBeingReadStack.top(); m_objectBeingReadStack.pop(); if (debugFlag) std::cout << "Popped Object:" << qPrintable(sceneObject->getName()) << std::endl; switch (sceneObject->getDataType()) { case SceneObjectDataTypeEnum::SCENE_CLASS: { SceneClass* sceneClass = dynamic_cast(sceneObject); CaretAssert(sceneClass); if (m_objectBeingReadStack.empty()) { /* * Parent must be the scene */ m_scene->addClass(sceneClass); } else { /* * Parent is another class */ CaretAssert(m_objectBeingReadStack.empty() == false); SceneClass* parentSceneClass = dynamic_cast(m_objectBeingReadStack.top()); SceneClassArray* parentSceneClassArray = dynamic_cast(m_objectBeingReadStack.top()); SceneObjectMapIntegerKey* parentSceneObjectMapIntegerKey = dynamic_cast(m_objectBeingReadStack.top()); if (parentSceneClass != NULL) { parentSceneClass->addClass(sceneClass); } else if (parentSceneClassArray != NULL) { parentSceneClassArray->setClassAtIndex(m_objectArrayBeingReadElementIndexStack.top(), sceneClass); } else if (parentSceneObjectMapIntegerKey != NULL) { parentSceneObjectMapIntegerKey->addClass(m_objectMapBeingReadValueKeyStack.top(), sceneClass); } } } break; case SceneObjectDataTypeEnum::SCENE_BOOLEAN: { SceneBoolean* sceneBoolean = dynamic_cast(sceneObject); CaretAssert(sceneBoolean); const bool value = stringValue.toBool(); sceneBoolean->setValue(value); addChildToParentClass(sceneBoolean); } break; case SceneObjectDataTypeEnum::SCENE_FLOAT: { SceneFloat* sceneFloat = dynamic_cast(sceneObject); CaretAssert(sceneFloat); const float value = stringValue.toFloat(); sceneFloat->setValue(value); addChildToParentClass(sceneFloat); } break; case SceneObjectDataTypeEnum::SCENE_ENUMERATED_TYPE: { SceneEnumeratedType* sceneEnum = dynamic_cast(sceneObject); CaretAssert(sceneEnum); sceneEnum->setValue(stringValue); addChildToParentClass(sceneEnum); } break; case SceneObjectDataTypeEnum::SCENE_INTEGER: { SceneInteger* sceneInteger = dynamic_cast(sceneObject); CaretAssert(sceneInteger); const int32_t value = stringValue.toInt(); sceneInteger->setValue(value); addChildToParentClass(sceneInteger); } break; case SceneObjectDataTypeEnum::SCENE_PATH_NAME: { ScenePathName* scenePathName = dynamic_cast(sceneObject); CaretAssert(scenePathName); scenePathName->setValueToAbsolutePath(m_sceneFileName, stringValue); addChildToParentClass(scenePathName); if (DataFile::isFileOnNetwork(stringValue)) { m_scene->setHasFilesWithRemotePaths(true); } } break; case SceneObjectDataTypeEnum::SCENE_STRING: { SceneString* sceneString = dynamic_cast(sceneObject); CaretAssert(sceneString); sceneString->setValue(stringValue); addChildToParentClass(sceneString); } break; case SceneObjectDataTypeEnum::SCENE_INVALID: break; } } break; case STATE_OBJECT_ARRAY: { /** * Get the array. */ CaretAssert(m_objectBeingReadStack.empty() == false); SceneObject* sceneObject = m_objectBeingReadStack.top(); m_objectBeingReadStack.pop(); if (debugFlag) std::cout << "Popped ObjectArray:" << qPrintable(sceneObject->getName()) << std::endl; SceneObjectArray* sceneArray = dynamic_cast(sceneObject); CaretAssert(sceneArray); /* * Parent is another class */ CaretAssert(m_objectBeingReadStack.empty() == false); SceneClass* parentSceneClass = dynamic_cast(m_objectBeingReadStack.top()); CaretAssert(parentSceneClass); parentSceneClass->addChild(sceneArray); } break; case STATE_OBJECT_ARRAY_ELEMENT: { /** * Get the array. */ CaretAssert(m_objectBeingReadStack.empty() == false); SceneObject* sceneObject = m_objectBeingReadStack.top(); SceneObjectArray* sceneArray = dynamic_cast(sceneObject); CaretAssert(sceneArray); switch (sceneArray->getDataType()) { case SceneObjectDataTypeEnum::SCENE_BOOLEAN: { SceneBooleanArray* booleanArray = dynamic_cast(sceneArray); CaretAssert(booleanArray); booleanArray->setValue(m_objectArrayBeingReadElementIndexStack.top(), stringValue.toBool()); } break; case SceneObjectDataTypeEnum::SCENE_CLASS: /* * Nothing to do here, handled in "case STATE_OBJECT" above */ break; case SceneObjectDataTypeEnum::SCENE_ENUMERATED_TYPE: { SceneEnumeratedTypeArray* enumeratedArray = dynamic_cast(sceneArray); CaretAssert(enumeratedArray); enumeratedArray->setValue(m_objectArrayBeingReadElementIndexStack.top(), stringValue); } break; case SceneObjectDataTypeEnum::SCENE_FLOAT: { SceneFloatArray* floatArray = dynamic_cast(sceneArray); CaretAssert(floatArray); floatArray->setValue(m_objectArrayBeingReadElementIndexStack.top(), stringValue.toFloat()); } break; case SceneObjectDataTypeEnum::SCENE_INTEGER: { SceneIntegerArray* integerArray = dynamic_cast(sceneArray); CaretAssert(integerArray); integerArray->setValue(m_objectArrayBeingReadElementIndexStack.top(), stringValue.toInt()); } break; case SceneObjectDataTypeEnum::SCENE_INVALID: { CaretAssert(0); } break; case SceneObjectDataTypeEnum::SCENE_PATH_NAME: { ScenePathNameArray* pathNameArray = dynamic_cast(sceneArray); CaretAssert(pathNameArray); pathNameArray->setScenePathNameAtIndex(m_objectArrayBeingReadElementIndexStack.top(), m_sceneFileName, stringValue); } break; case SceneObjectDataTypeEnum::SCENE_STRING: { SceneStringArray* stringArray = dynamic_cast(sceneArray); CaretAssert(stringArray); stringArray->setValue(m_objectArrayBeingReadElementIndexStack.top(), stringValue); } break; } m_objectArrayBeingReadElementIndexStack.pop(); } break; case STATE_OBJECT_MAP: { /** * Get the map. */ CaretAssert(m_objectBeingReadStack.empty() == false); SceneObject* sceneObject = m_objectBeingReadStack.top(); m_objectBeingReadStack.pop(); if (debugFlag) std::cout << "Popped ObjectMap:" << qPrintable(sceneObject->getName()) << std::endl; SceneObjectMapIntegerKey* sceneMap = dynamic_cast(sceneObject); CaretAssert(sceneMap); /* * Parent is another class */ CaretAssert(m_objectBeingReadStack.empty() == false); SceneClass* parentSceneClass = dynamic_cast(m_objectBeingReadStack.top()); CaretAssert(parentSceneClass); parentSceneClass->addChild(sceneMap); } break; case STATE_OBJECT_MAP_VALUE: { /** * Get the map. */ CaretAssert(m_objectBeingReadStack.empty() == false); SceneObject* sceneObject = m_objectBeingReadStack.top(); SceneObjectMapIntegerKey* sceneMap = dynamic_cast(sceneObject); CaretAssert(sceneMap); const int32_t key = m_objectMapBeingReadValueKeyStack.top(); m_objectMapBeingReadValueKeyStack.pop(); switch (sceneMap->getDataType()) { case SceneObjectDataTypeEnum::SCENE_BOOLEAN: { sceneMap->addBoolean(key, stringValue.toBool()); } break; case SceneObjectDataTypeEnum::SCENE_CLASS: /* * Nothing to do here, handled in "case STATE_OBJECT" above */ break; case SceneObjectDataTypeEnum::SCENE_ENUMERATED_TYPE: { sceneMap->addEnumeratedType(key, stringValue); } break; case SceneObjectDataTypeEnum::SCENE_FLOAT: { sceneMap->addFloat(key, stringValue.toFloat()); } break; case SceneObjectDataTypeEnum::SCENE_INTEGER: { sceneMap->addInteger(key, stringValue.toInt()); } break; case SceneObjectDataTypeEnum::SCENE_INVALID: { CaretAssert(0); } break; case SceneObjectDataTypeEnum::SCENE_PATH_NAME: { ScenePathName spn("spn", ""); spn.setValueToAbsolutePath(m_sceneFileName, stringValue); sceneMap->addPathName(key, spn.stringValue()); } break; case SceneObjectDataTypeEnum::SCENE_STRING: { sceneMap->addString(key, stringValue); } break; } } break; } /* * Clear out for new elements */ m_elementText = ""; /* * Go to previous state */ if (m_stateStack.empty()) { throw XmlSaxParserException("State stack is empty while reading Scene."); } CaretAssert(m_stateStack.empty() == false); m_state = m_stateStack.top(); m_stateStack.pop(); } /** * Add an object to its parent class. * @param sceneObject * New child for parent class. */ void SceneSaxReader::addChildToParentClass(SceneObject* sceneObject) { CaretAssert(m_objectBeingReadStack.empty() == false); SceneClass* parentClass = dynamic_cast(m_objectBeingReadStack.top()); CaretAssert(parentClass); parentClass->addChild(sceneObject); } /** * get characters in an element. */ void SceneSaxReader::characters(const char* ch) { m_elementText += ch; } /** * a fatal error occurs. */ void SceneSaxReader::fatalError(const XmlSaxParserException& e) { throw e; } /** * A warning occurs */ void SceneSaxReader::warning(const XmlSaxParserException& e) { CaretLogWarning("XML Parser Warning: " + e.whatString()); } // an error occurs void SceneSaxReader::error(const XmlSaxParserException& e) { throw e; } void SceneSaxReader::startDocument() { } void SceneSaxReader::endDocument() { } workbench-1.1.1/src/Scenes/SceneSaxReader.h000066400000000000000000000100531255417355300205210ustar00rootroot00000000000000 #ifndef __GIFTI_LABEL_TABLE_SAX_READER_H__ #define __GIFTI_LABEL_TABLE_SAX_READER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" #include "SceneObjectDataTypeEnum.h" #include "XmlSaxParserException.h" #include "XmlSaxParserHandlerInterface.h" namespace caret { class Scene; class SceneClass; class SceneObject; class SceneObjectArray; class XmlAttributes; /** * class for reading a Scene with a SAX Parser */ class SceneSaxReader : public CaretObject, public XmlSaxParserHandlerInterface { public: SceneSaxReader(const AString& sceneFileName, Scene* scene); virtual ~SceneSaxReader(); void startElement(const AString& namespaceURI, const AString& localName, const AString& qName, const XmlAttributes& attributes); void endElement(const AString& namspaceURI, const AString& localName, const AString& qName); void characters(const char* ch); void fatalError(const XmlSaxParserException& e); void warning(const XmlSaxParserException& e); void error(const XmlSaxParserException& e); void startDocument(); void endDocument(); protected: /// file reading states enum STATE { /// no state STATE_NONE, /// processing Scene tag STATE_SCENE, /// processing Scene Name tag STATE_SCENE_NAME, /// processing Scene Description tag STATE_SCENE_DESCRIPTION, /// processing Object tag STATE_OBJECT, /// processing Object Array tag STATE_OBJECT_ARRAY, /// processing Object Array tag STATE_OBJECT_ARRAY_ELEMENT, /// processing Object Map tag STATE_OBJECT_MAP, /// processing Object Map Value tag STATE_OBJECT_MAP_VALUE }; void processObjectStartTag(const XmlAttributes& attributes); void processObjectArrayStartTag(const XmlAttributes& attributes); void processObjectMapStartTag(const XmlAttributes& attributes); void addChildToParentClass(SceneObject* sceneObject); /// name of scene file AString m_sceneFileName; /// file reading state STATE m_state; /// the state stack used when reading a file std::stack m_stateStack; /// the error message AString m_errorMessage; /// element text AString m_elementText; /// Scene being read Scene* m_scene; /// Stack containing object being read std::stack m_objectBeingReadStack; /** Index of array element being read */ std::stack m_objectArrayBeingReadElementIndexStack; /** Key of map value being read */ std::stack m_objectMapBeingReadValueKeyStack; }; } // namespace #endif // __GIFTI_LABEL_TABLE_SAX_READER_H__ workbench-1.1.1/src/Scenes/SceneString.cxx000066400000000000000000000047251255417355300204750ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SCENE_STRING_DECLARE__ #include "SceneString.h" #undef __SCENE_STRING_DECLARE__ using namespace caret; /** * \class caret::SceneString * \brief For storage of a string value in a scene. * \ingroup Scene * * See the documentation in the class Scene for how to use the Scene system. */ /** * Constructor. * * @param name * Name of object. * @param value * Value of object. */ SceneString::SceneString(const AString& name, const AString& value) : ScenePrimitive(name, SceneObjectDataTypeEnum::SCENE_STRING) { m_value = value; } /** * Destructor. */ SceneString::~SceneString() { } /** * Set the value. * @param value * New value. */ void SceneString::setValue(const AString& value) { m_value = value; } /** * @return The value as a boolean data type. */ bool SceneString::booleanValue() const { const bool b = m_value.toBool(); return b; } /** * @return The value as a float data type. * If the string does not convert to a float number, * 0.0 is returned. */ float SceneString::floatValue() const { bool isValid = false; float f = m_value.toFloat(&isValid); if (isValid == false) { f = 0.0; } return f; } /** * @return The value as a integer data type. * If the string does not convert to an integer number, * 0 is returned. */ int32_t SceneString::integerValue() const { bool isValid = false; int32_t i = m_value.toInt(&isValid); if (isValid == false) { i = 0; } return i; } /** * @return The value as a string data type. */ AString SceneString::stringValue() const { return m_value; } workbench-1.1.1/src/Scenes/SceneString.h000066400000000000000000000034461255417355300201210ustar00rootroot00000000000000#ifndef __SCENE_STRING__H_ #define __SCENE_STRING__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "ScenePrimitive.h" namespace caret { class SceneString : public ScenePrimitive { public: SceneString(const AString& name, const AString& value); virtual ~SceneString(); void setValue(const AString& value); virtual bool booleanValue() const; virtual float floatValue() const; virtual int32_t integerValue() const; virtual AString stringValue() const; private: SceneString(const SceneString&); SceneString& operator=(const SceneString&); public: // ADD_NEW_METHODS_HERE private: AString m_value; // ADD_NEW_MEMBERS_HERE }; #ifdef __SCENE_STRING_DECLARE__ // #endif // __SCENE_STRING_DECLARE__ } // namespace #endif //__SCENE_STRING__H_ workbench-1.1.1/src/Scenes/SceneStringArray.cxx000066400000000000000000000106471255417355300214740ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __SCENE_STRING_ARRAY_DECLARE__ #include "SceneStringArray.h" #undef __SCENE_STRING_ARRAY_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::SceneStringArray * \brief For storage of a boolean value in a scene. * \ingroup Scene * * See the documentation in the class Scene for how to use the Scene system. */ /** * Constructor. * * @param name * Name of object. * @param values * Value in the array. * @param numberOfArrayElements * Number of values in the array. */ SceneStringArray::SceneStringArray(const AString& name, const AString values[], const int32_t numberOfArrayElements) : ScenePrimitiveArray(name, SceneObjectDataTypeEnum::SCENE_STRING, numberOfArrayElements) { m_values.resize(numberOfArrayElements); for (int32_t i = 0; i < numberOfArrayElements; i++) { m_values[i] = values[i]; } } /** * Constructor. * * @param name * Name of object. * @param values * Value in the array. */ SceneStringArray::SceneStringArray(const AString& name, const std::vector& values) : ScenePrimitiveArray(name, SceneObjectDataTypeEnum::SCENE_STRING, values.size()) { m_values = values; } /** * Constructor that initializes all values to false. * * @param name * Name of object. * @param numberOfArrayElements * Number of values in the array. */ SceneStringArray::SceneStringArray(const AString& name, const int numberOfArrayElements) : ScenePrimitiveArray(name, SceneObjectDataTypeEnum::SCENE_STRING, numberOfArrayElements) { m_values.resize(numberOfArrayElements); std::fill(m_values.begin(), m_values.end(), false); } /** * Destructor. */ SceneStringArray::~SceneStringArray() { } /** * Set a value. * @param arrayIndex * Index of the element. * @param value * Value of element. */ void SceneStringArray::setValue(const int32_t arrayIndex, const AString value) { CaretAssertVectorIndex(m_values, arrayIndex); m_values[arrayIndex] = value; } /** * Get the values as a boolean. * @param arrayIndex * Index of element. * @return The value. */ bool SceneStringArray::booleanValue(const int32_t arrayIndex) const { CaretAssertVectorIndex(m_values, arrayIndex); const bool b = m_values[arrayIndex].toBool(); return b; } /** * Get the values as a float. * @param arrayIndex * Index of element. * @return The value. */ float SceneStringArray::floatValue(const int32_t arrayIndex) const { CaretAssertVectorIndex(m_values, arrayIndex); bool isValid = false; float f = m_values[arrayIndex].toFloat(&isValid); if (isValid == false) { f = 0.0; } return f; } /** * Get the values as a integer. * @param arrayIndex * Index of element. * @return The value. */ int32_t SceneStringArray::integerValue(const int32_t arrayIndex) const { CaretAssertVectorIndex(m_values, arrayIndex); bool isValid = false; int32_t i = m_values[arrayIndex].toInt(&isValid); if (isValid == false) { i = 0; } return i; } /** * Get the values as a string. * @param arrayIndex * Index of element. * @return The value. */ AString SceneStringArray::stringValue(const int32_t arrayIndex) const { CaretAssertVectorIndex(m_values, arrayIndex); return m_values[arrayIndex]; } workbench-1.1.1/src/Scenes/SceneStringArray.h000066400000000000000000000045071255417355300211170ustar00rootroot00000000000000#ifndef __SCENE_STRING_ARRAY_H__ #define __SCENE_STRING_ARRAY_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "ScenePrimitiveArray.h" namespace caret { class SceneStringArray : public ScenePrimitiveArray { public: SceneStringArray(const AString& name, const AString values[], const int32_t numberOfArrayElements); SceneStringArray(const AString& name, const std::vector& values); SceneStringArray(const AString& name, const int numberOfArrayElements); virtual ~SceneStringArray(); void setValue(const int32_t arrayIndex, const AString value); virtual bool booleanValue(const int32_t arrayIndex) const; virtual float floatValue(const int32_t arrayIndex) const; virtual int32_t integerValue(const int32_t arrayIndex) const; virtual AString stringValue(const int32_t arrayIndex) const; private: SceneStringArray(const SceneStringArray&); SceneStringArray& operator=(const SceneStringArray&); public: // ADD_NEW_METHODS_HERE private: std::vector m_values; // ADD_NEW_MEMBERS_HERE }; #ifdef __SCENE_STRING_ARRAY_DECLARE__ // #endif // __SCENE_STRING_ARRAY_DECLARE__ } // namespace #endif //__SCENE_STRING_ARRAY_H__ workbench-1.1.1/src/Scenes/SceneTypeEnum.cxx000066400000000000000000000214441255417355300207720ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #define __SCENE_TYPE_ENUM_DECLARE__ #include "SceneTypeEnum.h" #undef __SCENE_TYPE_ENUM_DECLARE__ #include "CaretAssert.h" using namespace caret; /** * \class caret::SceneTypeEnum * \brief Saves a Caret enumerated type. * \ingroup Scene * * See the documentation in the class Scene for how to use the Scene system. */ /** * Constructor. * * @param enumValue * An enumerated value. * @param name * Name of enumerated value. * * @param guiName * User-friendly name for use in user-interface. */ SceneTypeEnum::SceneTypeEnum(const Enum enumValue, const AString& name, const AString& guiName) { this->enumValue = enumValue; this->integerCode = integerCodeCounter++; this->name = name; this->guiName = guiName; } /** * Destructor. */ SceneTypeEnum::~SceneTypeEnum() { } /** * Initialize the enumerated metadata. */ void SceneTypeEnum::initialize() { if (initializedFlag) { return; } initializedFlag = true; enumData.push_back(SceneTypeEnum(SCENE_TYPE_FULL, "SCENE_TYPE_FULL", "Full Scene")); enumData.push_back(SceneTypeEnum(SCENE_TYPE_GENERIC, "SCENE_TYPE_GENERIC", "Generic Scene")); } /** * Find the data for and enumerated value. * @param enumValue * The enumerated value. * @return Pointer to data for this enumerated type * or NULL if no data for type or if type is invalid. */ const SceneTypeEnum* SceneTypeEnum::findData(const Enum enumValue) { if (initializedFlag == false) initialize(); size_t num = enumData.size(); for (size_t i = 0; i < num; i++) { const SceneTypeEnum* d = &enumData[i]; if (d->enumValue == enumValue) { return d; } } return NULL; } /** * Get a string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString SceneTypeEnum::toName(Enum enumValue) { if (initializedFlag == false) initialize(); const SceneTypeEnum* enumInstance = findData(enumValue); return enumInstance->name; } /** * Get an enumerated value corresponding to its name. * @param name * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ SceneTypeEnum::Enum SceneTypeEnum::fromName(const AString& name, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = SCENE_TYPE_FULL; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const SceneTypeEnum& d = *iter; if (d.name == name) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Name " + name + "failed to match enumerated value for type SceneTypeEnum")); } return enumValue; } /** * Get a GUI string representation of the enumerated type. * @param enumValue * Enumerated value. * @return * String representing enumerated value. */ AString SceneTypeEnum::toGuiName(Enum enumValue) { if (initializedFlag == false) initialize(); const SceneTypeEnum* enumInstance = findData(enumValue); return enumInstance->guiName; } /** * Get an enumerated value corresponding to its GUI name. * @param s * Name of enumerated value. * @param isValidOut * If not NULL, it is set indicating that a * enum value exists for the input name. * @return * Enumerated value. */ SceneTypeEnum::Enum SceneTypeEnum::fromGuiName(const AString& guiName, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = SCENE_TYPE_FULL; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const SceneTypeEnum& d = *iter; if (d.guiName == guiName) { enumValue = d.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("guiName " + guiName + "failed to match enumerated value for type SceneTypeEnum")); } return enumValue; } /** * Get the integer code for a data type. * * @return * Integer code for data type. */ int32_t SceneTypeEnum::toIntegerCode(Enum enumValue) { if (initializedFlag == false) initialize(); const SceneTypeEnum* enumInstance = findData(enumValue); return enumInstance->integerCode; } /** * Find the data type corresponding to an integer code. * * @param integerCode * Integer code for enum. * @param isValidOut * If not NULL, on exit isValidOut will indicate if * integer code is valid. * @return * Enum for integer code. */ SceneTypeEnum::Enum SceneTypeEnum::fromIntegerCode(const int32_t integerCode, bool* isValidOut) { if (initializedFlag == false) initialize(); bool validFlag = false; Enum enumValue = SCENE_TYPE_FULL; for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { const SceneTypeEnum& enumInstance = *iter; if (enumInstance.integerCode == integerCode) { enumValue = enumInstance.enumValue; validFlag = true; break; } } if (isValidOut != 0) { *isValidOut = validFlag; } else if (validFlag == false) { CaretAssertMessage(0, AString("Integer code " + AString::number(integerCode) + "failed to match enumerated value for type SceneTypeEnum")); } return enumValue; } /** * Get all of the enumerated type values. The values can be used * as parameters to toXXX() methods to get associated metadata. * * @param allEnums * A vector that is OUTPUT containing all of the enumerated values. */ void SceneTypeEnum::getAllEnums(std::vector& allEnums) { if (initializedFlag == false) initialize(); allEnums.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allEnums.push_back(iter->enumValue); } } /** * Get all of the names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void SceneTypeEnum::getAllNames(std::vector& allNames, const bool isSorted) { if (initializedFlag == false) initialize(); allNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allNames.push_back(SceneTypeEnum::toName(iter->enumValue)); } if (isSorted) { std::sort(allNames.begin(), allNames.end()); } } /** * Get all of the GUI names of the enumerated type values. * * @param allNames * A vector that is OUTPUT containing all of the GUI names of the enumerated values. * @param isSorted * If true, the names are sorted in alphabetical order. */ void SceneTypeEnum::getAllGuiNames(std::vector& allGuiNames, const bool isSorted) { if (initializedFlag == false) initialize(); allGuiNames.clear(); for (std::vector::iterator iter = enumData.begin(); iter != enumData.end(); iter++) { allGuiNames.push_back(SceneTypeEnum::toGuiName(iter->enumValue)); } if (isSorted) { std::sort(allGuiNames.begin(), allGuiNames.end()); } } workbench-1.1.1/src/Scenes/SceneTypeEnum.h000066400000000000000000000057251255417355300204230ustar00rootroot00000000000000#ifndef __SCENE_TYPE_ENUM__H_ #define __SCENE_TYPE_ENUM__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "AString.h" namespace caret { class SceneTypeEnum { public: /** * Enumerated values. */ enum Enum { /** */ SCENE_TYPE_FULL, /** */ SCENE_TYPE_GENERIC }; ~SceneTypeEnum(); static AString toName(Enum enumValue); static Enum fromName(const AString& name, bool* isValidOut); static AString toGuiName(Enum enumValue); static Enum fromGuiName(const AString& guiName, bool* isValidOut); static int32_t toIntegerCode(Enum enumValue); static Enum fromIntegerCode(const int32_t integerCode, bool* isValidOut); static void getAllEnums(std::vector& allEnums); static void getAllNames(std::vector& allNames, const bool isSorted); static void getAllGuiNames(std::vector& allGuiNames, const bool isSorted); private: SceneTypeEnum(const Enum enumValue, const AString& name, const AString& guiName); static const SceneTypeEnum* findData(const Enum enumValue); /** Holds all instance of enum values and associated metadata */ static std::vector enumData; /** Initialize instances that contain the enum values and metadata */ static void initialize(); /** Indicates instance of enum values and metadata have been initialized */ static bool initializedFlag; /** Auto generated integer codes */ static int32_t integerCodeCounter; /** The enumerated type value for an instance */ Enum enumValue; /** The integer code associated with an enumerated value */ int32_t integerCode; /** The name, a text string that is identical to the enumerated value */ AString name; /** A user-friendly name that is displayed in the GUI */ AString guiName; }; #ifdef __SCENE_TYPE_ENUM_DECLARE__ std::vector SceneTypeEnum::enumData; bool SceneTypeEnum::initializedFlag = false; int32_t SceneTypeEnum::integerCodeCounter = 0; #endif // __SCENE_TYPE_ENUM_DECLARE__ } // namespace #endif //__SCENE_TYPE_ENUM__H_ workbench-1.1.1/src/Scenes/SceneWriterInterface.h000066400000000000000000000040561255417355300217460ustar00rootroot00000000000000#ifndef __SCENE_WRITER_INTERFACE__H_ #define __SCENE_WRITER_INTERFACE__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include /** * \class caret::SceneWriterInterface * \brief Interface for classes that write scenes. * \ingroup Scene * * See the documentation in the class Scene for how to use the Scene system. */ namespace caret { class Scene; class SceneWriterInterface { protected: SceneWriterInterface() { } public: virtual ~SceneWriterInterface() { } /** * Write the given scene. * @param scene * Scene that is written. * @throws ??? * Implementing classes may throw exceptions. */ virtual void writeScene(const Scene& scene, const int32_t sceneIndex) = 0; private: SceneWriterInterface(const SceneWriterInterface&); SceneWriterInterface& operator=(const SceneWriterInterface&); public: // ADD_NEW_METHODS_HERE private: // ADD_NEW_MEMBERS_HERE }; #ifdef __SCENE_WRITER_INTERFACE_DECLARE__ // #endif // __SCENE_WRITER_INTERFACE_DECLARE__ } // namespace #endif //__SCENE_WRITER_INTERFACE__H_ workbench-1.1.1/src/Scenes/SceneWriterXml.cxx000066400000000000000000000420341255417355300211570ustar00rootroot00000000000000 /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #define __SCENE_WRITER_XML_DECLARE__ #include "SceneWriterXml.h" #undef __SCENE_WRITER_XML_DECLARE__ #include "CaretAssert.h" #include "Scene.h" #include "SceneAttributes.h" #include "SceneClass.h" #include "SceneClassArray.h" #include "SceneEnumeratedType.h" #include "SceneEnumeratedTypeArray.h" #include "SceneInfo.h" #include "SceneObjectMapIntegerKey.h" #include "ScenePathName.h" #include "ScenePathNameArray.h" #include "ScenePrimitive.h" #include "ScenePrimitiveArray.h" #include "SceneXmlElements.h" #include "XmlAttributes.h" #include "XmlUtilities.h" #include "XmlWriter.h" using namespace caret; /** * \class caret::SceneWriterXml * \brief Writes scenes to an XmlWriter. * \ingroup Scene * * See the documentation in the class Scene for how to use the Scene system. */ /** * Constructor. */ SceneWriterXml::SceneWriterXml(XmlWriter& xmlWriter, const AString& sceneFileName) : SceneWriterInterface(), m_xmlWriter(xmlWriter), m_sceneFileName(sceneFileName) { } /** * Destructor. */ SceneWriterXml::~SceneWriterXml() { } /** * Write the given scene. * @param scene * Scene that is written. * @throws XmlException * If there is an error writing the scene as XML. */ void SceneWriterXml::writeScene(const Scene& scene, const int32_t sceneIndex) { /* * Type of scene */ const SceneAttributes* sceneAttributes = scene.getAttributes(); const AString sceneTypeName = SceneTypeEnum::toName(sceneAttributes->getSceneType()); /* * Start scene element */ XmlAttributes attributes; attributes.addAttribute(SceneXmlElements::SCENE_INDEX_ATTRIBUTE, sceneIndex); attributes.addAttribute(SceneXmlElements::SCENE_TYPE_ATTRIBUTE, sceneTypeName); m_xmlWriter.writeStartElement(SceneXmlElements::SCENE_TAG, attributes); m_xmlWriter.writeElementCData(SceneXmlElements::SCENE_NAME_TAG, scene.getName()); m_xmlWriter.writeElementCData(SceneXmlElements::SCENE_DESCRIPTION_TAG, scene.getDescription()); /* * Write scene classes. */ const int32_t numClasses = scene.getNumberOfClasses(); for (int32_t i = 0; i < numClasses; i++) { writeSceneClass(*scene.getClassAtIndex(i)); } /* * End scene element */ m_xmlWriter.writeEndElement(); } /** * Write then given class to a scene. * @param sceneClass * Class that is written to the scene. */ void SceneWriterXml::writeSceneClass(const SceneClass& sceneClass) { /* * Start class element. */ const AString& dataTypeName = SceneObjectDataTypeEnum::toXmlName(sceneClass.getDataType()); XmlAttributes attributes; attributes.addAttribute(SceneXmlElements::OBJECT_TYPE_ATTRIBUTE, dataTypeName); attributes.addAttribute(SceneXmlElements::OBJECT_CLASS_ATTRIBUTE, sceneClass.getClassName()); attributes.addAttribute(SceneXmlElements::OBJECT_NAME_ATTRIBUTE, XmlUtilities::encodeXmlSpecialCharacters(sceneClass.getName())); attributes.addAttribute(SceneXmlElements::OBJECT_VERSION_ATTRIBUTE, sceneClass.getVersionNumber()); m_xmlWriter.writeStartElement(SceneXmlElements::OBJECT_TAG, attributes); /* * Write objects. */ const int32_t numberOfObjects = sceneClass.getNumberOfObjects(); for (int32_t i = 0; i < numberOfObjects; i++) { const SceneObject* sceneObject = sceneClass.getObjectAtIndex(i); const AString& dataTypeName = SceneObjectDataTypeEnum::toXmlName(sceneObject->getDataType()); XmlAttributes attributes; attributes.addAttribute(SceneXmlElements::OBJECT_TYPE_ATTRIBUTE, dataTypeName); attributes.addAttribute(SceneXmlElements::OBJECT_NAME_ATTRIBUTE, sceneObject->getName()); const SceneEnumeratedType* sceneEnumeratedType = dynamic_cast(sceneObject); const SceneEnumeratedTypeArray* sceneEnumeratedTypeArray = dynamic_cast(sceneObject); const ScenePrimitive* scenePrimitive= dynamic_cast(sceneObject); const ScenePrimitiveArray* scenePrimitiveArray = dynamic_cast(sceneObject); const SceneClass* sceneClass = dynamic_cast(sceneObject); const SceneClassArray* sceneClassArray = dynamic_cast(sceneObject); const SceneObjectMapIntegerKey* sceneMapIntegerKey = dynamic_cast(sceneObject); const ScenePathName* scenePathName = dynamic_cast(sceneObject); const ScenePathNameArray* scenePathNameArray = dynamic_cast(sceneObject); switch (sceneObject->getDataType()) { case SceneObjectDataTypeEnum::SCENE_BOOLEAN: break; case SceneObjectDataTypeEnum::SCENE_CLASS: break; case SceneObjectDataTypeEnum::SCENE_ENUMERATED_TYPE: break; case SceneObjectDataTypeEnum::SCENE_FLOAT: break; case SceneObjectDataTypeEnum::SCENE_INTEGER: break; case SceneObjectDataTypeEnum::SCENE_INVALID: break; case SceneObjectDataTypeEnum::SCENE_PATH_NAME: break; case SceneObjectDataTypeEnum::SCENE_STRING: break; } if (scenePrimitive != NULL) { if (scenePrimitive->getDataType() == SceneObjectDataTypeEnum::SCENE_STRING) { m_xmlWriter.writeElementCData(SceneXmlElements::OBJECT_TAG, attributes, scenePrimitive->stringValue()); } else { m_xmlWriter.writeElementCharacters(SceneXmlElements::OBJECT_TAG, attributes, scenePrimitive->stringValue()); } } else if (scenePrimitiveArray != NULL) { const int32_t numberOfArrayElements = scenePrimitiveArray->getNumberOfArrayElements(); attributes.addAttribute(SceneXmlElements::OBJECT_ARRAY_LENGTH_ATTRIBUTE, numberOfArrayElements); m_xmlWriter.writeStartElement(SceneXmlElements::OBJECT_ARRAY_TAG, attributes); for (int32_t elementIndex = 0; elementIndex < numberOfArrayElements; elementIndex++) { XmlAttributes elementAttributes; elementAttributes.addAttribute(SceneXmlElements::OBJECT_ARRAY_ELEMENT_INDEX_ATTRIBUTE, elementIndex); if (scenePrimitiveArray->getDataType() == SceneObjectDataTypeEnum::SCENE_STRING) { m_xmlWriter.writeElementCData(SceneXmlElements::OBJECT_ARRAY_ELEMENT_TAG, elementAttributes, scenePrimitiveArray->stringValue(elementIndex)); } else { m_xmlWriter.writeElementCharacters(SceneXmlElements::OBJECT_ARRAY_ELEMENT_TAG, elementAttributes, scenePrimitiveArray->stringValue(elementIndex)); } } m_xmlWriter.writeEndElement(); } else if (sceneClass != NULL) { writeSceneClass(*sceneClass); } else if (scenePathName != NULL) { const AString path = scenePathName->getRelativePathToSceneFile(m_sceneFileName); m_xmlWriter.writeElementCData(SceneXmlElements::OBJECT_TAG, attributes, path); } else if (scenePathNameArray != NULL) { const int32_t numberOfArrayElements = scenePathNameArray->getNumberOfArrayElements(); attributes.addAttribute(SceneXmlElements::OBJECT_ARRAY_LENGTH_ATTRIBUTE, numberOfArrayElements); m_xmlWriter.writeStartElement(SceneXmlElements::OBJECT_ARRAY_TAG, attributes); for (int32_t elementIndex = 0; elementIndex < numberOfArrayElements; elementIndex++) { XmlAttributes elementAttributes; elementAttributes.addAttribute(SceneXmlElements::OBJECT_ARRAY_ELEMENT_INDEX_ATTRIBUTE, elementIndex); const ScenePathName* spn = scenePathNameArray->getScenePathNameAtIndex(elementIndex); const AString path = spn->getRelativePathToSceneFile(m_sceneFileName); m_xmlWriter.writeElementCData(SceneXmlElements::OBJECT_ARRAY_ELEMENT_TAG, elementAttributes, path); // m_xmlWriter.writeStartElement(SceneXmlElements::OBJECT_ARRAY_ELEMENT_TAG, // elementAttributes); // m_xmlWriter.writeElementCData(SceneXmlElements::OBJECT_TAG, // attributes, // path); // m_xmlWriter.writeEndElement(); } m_xmlWriter.writeEndElement(); } else if (sceneClassArray != NULL) { const int32_t numberOfArrayElements = sceneClassArray->getNumberOfArrayElements(); attributes.addAttribute(SceneXmlElements::OBJECT_ARRAY_LENGTH_ATTRIBUTE, numberOfArrayElements); m_xmlWriter.writeStartElement(SceneXmlElements::OBJECT_ARRAY_TAG, attributes); for (int32_t elementIndex = 0; elementIndex < numberOfArrayElements; elementIndex++) { XmlAttributes elementAttributes; elementAttributes.addAttribute(SceneXmlElements::OBJECT_ARRAY_ELEMENT_INDEX_ATTRIBUTE, elementIndex); m_xmlWriter.writeStartElement(SceneXmlElements::OBJECT_ARRAY_ELEMENT_TAG, elementAttributes); writeSceneClass(*sceneClassArray->getClassAtIndex(elementIndex)); m_xmlWriter.writeEndElement(); } m_xmlWriter.writeEndElement(); } else if (sceneEnumeratedType != NULL) { m_xmlWriter.writeElementCData(SceneXmlElements::OBJECT_TAG, attributes, sceneEnumeratedType->stringValue()); } else if (sceneEnumeratedTypeArray != NULL) { const int32_t numberOfArrayElements = sceneEnumeratedTypeArray->getNumberOfArrayElements(); attributes.addAttribute(SceneXmlElements::OBJECT_ARRAY_LENGTH_ATTRIBUTE, numberOfArrayElements); m_xmlWriter.writeStartElement(SceneXmlElements::OBJECT_ARRAY_TAG, attributes); for (int32_t elementIndex = 0; elementIndex < numberOfArrayElements; elementIndex++) { XmlAttributes elementAttributes; elementAttributes.addAttribute(SceneXmlElements::OBJECT_ARRAY_ELEMENT_INDEX_ATTRIBUTE, elementIndex); m_xmlWriter.writeElementCData(SceneXmlElements::OBJECT_ARRAY_ELEMENT_TAG, elementAttributes, sceneEnumeratedTypeArray->stringValue(elementIndex)); } m_xmlWriter.writeEndElement(); } else if (sceneMapIntegerKey != NULL) { m_xmlWriter.writeStartElement(SceneXmlElements::OBJECT_MAP_TAG, attributes); const std::map& sceneMap = sceneMapIntegerKey->getMap(); for (std::map::const_iterator iter = sceneMap.begin(); iter != sceneMap.end(); iter++) { const int32_t key = iter->first; XmlAttributes valueAttributes; valueAttributes.addAttribute(SceneXmlElements::OBJECT_MAP_VALUE_KEY_ATTRIBUTE, key); const SceneObject* sceneObject = iter->second; switch (sceneMapIntegerKey->getDataType()) { case SceneObjectDataTypeEnum::SCENE_CLASS: { const SceneClass* sceneClass = dynamic_cast(sceneObject); m_xmlWriter.writeStartElement(SceneXmlElements::OBJECT_MAP_VALUE_TAG, valueAttributes); writeSceneClass(*sceneClass); m_xmlWriter.writeEndElement(); } break; case SceneObjectDataTypeEnum::SCENE_ENUMERATED_TYPE: { const SceneEnumeratedType* sceneEnumType = dynamic_cast(sceneObject); m_xmlWriter.writeElementCharacters(SceneXmlElements::OBJECT_MAP_VALUE_TAG, valueAttributes, sceneEnumType->stringValue()); } break; case SceneObjectDataTypeEnum::SCENE_BOOLEAN: case SceneObjectDataTypeEnum::SCENE_FLOAT: case SceneObjectDataTypeEnum::SCENE_INTEGER: { const ScenePrimitive* primitive = dynamic_cast(sceneObject); m_xmlWriter.writeElementCharacters(SceneXmlElements::OBJECT_MAP_VALUE_TAG, valueAttributes, primitive->stringValue()); } break; case SceneObjectDataTypeEnum::SCENE_PATH_NAME: { const ScenePathName* pathName = dynamic_cast(sceneObject); const AString path = pathName->getRelativePathToSceneFile(m_sceneFileName); m_xmlWriter.writeElementCData(SceneXmlElements::OBJECT_MAP_VALUE_TAG, valueAttributes, path); } break; case SceneObjectDataTypeEnum::SCENE_STRING: { const ScenePrimitive* primitive = dynamic_cast(sceneObject); m_xmlWriter.writeElementCData(SceneXmlElements::OBJECT_MAP_VALUE_TAG, valueAttributes, primitive->stringValue()); } break; case SceneObjectDataTypeEnum::SCENE_INVALID: CaretAssert(0); break; } } m_xmlWriter.writeEndElement(); } else { CaretAssertMessage(0, ("Unknown scene object type=" + dataTypeName)); } } /* * End class element. */ m_xmlWriter.writeEndElement(); } workbench-1.1.1/src/Scenes/SceneWriterXml.h000066400000000000000000000041261255417355300206040ustar00rootroot00000000000000#ifndef __SCENE_WRITER_XML__H_ #define __SCENE_WRITER_XML__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "AString.h" #include "SceneWriterInterface.h" namespace caret { class SceneClass; class SceneInfo; class XmlWriter; class SceneWriterXml : public SceneWriterInterface { public: SceneWriterXml(XmlWriter& xmlWriter, const AString& sceneFileName); virtual ~SceneWriterXml(); /** * Write the given scene. * @param scene * Scene that is written. * @param sceneIndex * Index of the scene. */ virtual void writeScene(const Scene& scene, const int32_t sceneIndex); private: SceneWriterXml(const SceneWriterXml&); SceneWriterXml& operator=(const SceneWriterXml&); void writeSceneClass(const SceneClass& sceneClass); public: // ADD_NEW_METHODS_HERE private: // ADD_NEW_MEMBERS_HERE XmlWriter& m_xmlWriter; const AString m_sceneFileName; }; #ifdef __SCENE_WRITER_XML_DECLARE__ // #endif // __SCENE_WRITER_XML_DECLARE__ } // namespace #endif //__SCENE_WRITER_XML__H_ workbench-1.1.1/src/Scenes/SceneXmlElements.h000066400000000000000000000116661255417355300211130ustar00rootroot00000000000000#ifndef __SCENE_XML_ELEMENTS__H_ #define __SCENE_XML_ELEMENTS__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ /** * \class caret::SceneXmlElements * \brief XML Elements for Scenes written with XML. * \ingroup Scene * * See the documentation in the class Scene for how to use the Scene system. * * Format of tags is: * XML___ * * Since the user names scenes, CDATA is used for scene * name. */ namespace caret { namespace SceneXmlElements { /** XML Tag for scene */ static const AString SCENE_TAG = "Scene"; /** XML Tag for scene name */ static const AString SCENE_NAME_TAG = "Name"; /** XML Tag for scene description */ static const AString SCENE_DESCRIPTION_TAG = "Description"; /** XML Attribute for scene index */ static const AString SCENE_INDEX_ATTRIBUTE = "Index"; /** XML Attribute for scene type */ static const AString SCENE_TYPE_ATTRIBUTE = "Type"; /** XML Tag for scene object */ static const AString OBJECT_TAG = "Object"; /** XML Tag for scene object arrays */ static const AString OBJECT_ARRAY_TAG = "ObjectArray"; /** XML Tag for scene object map */ static const AString OBJECT_MAP_TAG = "ObjectMap"; /** XML Attributes for scene object type */ static const AString OBJECT_TYPE_ATTRIBUTE = "Type"; /** XML Attributes for scene object name */ static const AString OBJECT_NAME_ATTRIBUTE = "Name"; /** * XML Attributes for scene object class. Required when * 'type' is a class and contains the name of the class. */ static const AString OBJECT_CLASS_ATTRIBUTE = "Class"; /** * XML Attributes for scene object array length. */ static const AString OBJECT_ARRAY_LENGTH_ATTRIBUTE = "Length"; /** * XML Tag Element for array element */ static const AString OBJECT_ARRAY_ELEMENT_TAG = "Element"; /** * XML Attribute for element array index */ static const AString OBJECT_ARRAY_ELEMENT_INDEX_ATTRIBUTE = "Index"; /** XML Tag for ObjectMap Value */ static const AString OBJECT_MAP_VALUE_TAG = "Value"; /** XML Tag for ObjectMap Key */ static const AString OBJECT_MAP_VALUE_KEY_ATTRIBUTE = "Key"; /** * XML Attributes for scene object version. Only valid when * 'type' is a class. Since a class may change (members added, * members deleted, member's type changed, etc), the version * can be used to handle previous versions of a class. */ static const AString OBJECT_VERSION_ATTRIBUTE = "Version"; /** * XML Tag for Scene Info element. */ static const AString SCENE_INFO_TAG = "SceneInfo"; /** * XML Tag for Scene Info index attribute. */ static const AString SCENE_INFO_INDEX_ATTRIBUTE = "Index"; /** * XML Tag for Scene Info name element. */ static const AString SCENE_INFO_NAME_TAG = "Name"; /** * XML Tag for Scene Info description element. */ static const AString SCENE_INFO_DESCRIPTION_TAG = "Description"; /** * XML Tag for Scene Info Image element. */ static const AString SCENE_INFO_IMAGE_TAG = "Image"; /** * XML Tag for Scene Info image format attribute. */ static const AString SCENE_INFO_IMAGE_FORMAT_ATTRIBUTE = "Format"; /** * XML Tag for Scene Info image encoding attribute. */ static const AString SCENE_INFO_IMAGE_ENCODING_ATTRIBUTE = "Encoding"; /** * Name for Base64 encoding used in Scene Info image encoding attribute. */ static const AString SCENE_INFO_ENCODING_BASE64_NAME = "Base64"; } // namespace SceneXmlElements } // namespace caret #endif //__SCENE_XML_ELEMENTS__H_ workbench-1.1.1/src/Scenes/SceneableInterface.h000066400000000000000000000064451255417355300214010ustar00rootroot00000000000000#ifndef __SCENEABLE_INTERFACE__H_ #define __SCENEABLE_INTERFACE__H_ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ /** * \class caret::SceneableInterface * \brief Interface that must be implemented by classes saved to scenes. * \ingroup Scene * * See the documentation in the class Scene for how to use the Scene system. */ #include "AString.h" namespace caret { class SceneAttributes; class SceneClass; class SceneableInterface { protected: SceneableInterface() { } public: virtual ~SceneableInterface() { } /** * Create a scene for an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * saving the scene. * * @param instanceName * Name of the class' instance. * * @return Pointer to SceneClass object representing the state of * this object. Under some circumstances a NULL pointer may be * returned. Caller will take ownership of returned object. */ virtual SceneClass* saveToScene(const SceneAttributes* sceneAttributes, const AString& instanceName) = 0; /** * Restore the state of an instance of a class. * * @param sceneAttributes * Attributes for the scene. Scenes may be of different types * (full, generic, etc) and the attributes should be checked when * restoring the scene. * * @param sceneClass * sceneClass for the instance of a class that implements * this interface. May be NULL for some types of scenes. */ virtual void restoreFromScene(const SceneAttributes* sceneAttributes, const SceneClass* sceneClass) = 0; protected: /** * Copy constructor. Implemented only to prevent compilation * warnings for classes that implement this interface and have * a copy constructor. */ SceneableInterface(const SceneableInterface&) { } private: SceneableInterface& operator=(const SceneableInterface&); }; #ifdef __SCENEABLE_INTERFACE_DECLARE__ // #endif // __SCENEABLE_INTERFACE_DECLARE__ } // namespace #endif //__SCENEABLE_INTERFACE__H_ workbench-1.1.1/src/Tests/000077500000000000000000000000001255417355300153775ustar00rootroot00000000000000workbench-1.1.1/src/Tests/CMakeLists.txt000066400000000000000000000044701255417355300201440ustar00rootroot00000000000000# # Name of project # PROJECT (Tests) # # Need XML from Qt # # needed QImage and it is from GUI: SET(QT_DONT_USE_QTGUI TRUE) SET(QT_USE_QTXML TRUE) SET(QT_USE_QTNETWORK TRUE) # # Add QT for includes # INCLUDE (${QT_USE_FILE}) # #The individual tests # ADD_LIBRARY(Tests CiftiFileTest.h GeodesicHelperTest.h HttpTest.h HeapTest.h LookupTest.h MathExpressionTest.h NiftiTest.h PointerTest.h ProgressTest.h QuatTest.h StatisticsTest.h TestInterface.h TimerTest.h TopologyHelperOld.h TopologyHelperTest.h VolumeFileTest.h XnatTest.h CiftiFileTest.cxx GeodesicHelperTest.cxx HttpTest.cxx HeapTest.cxx LookupTest.cxx MathExpressionTest.cxx NiftiTest.cxx PointerTest.cxx ProgressTest.cxx QuatTest.cxx StatisticsTest.cxx TestInterface.cxx TimerTest.cxx TopologyHelperOld.cxx TopologyHelperTest.cxx VolumeFileTest.cxx XnatTest.cxx ) # # Create the test1 executable # IF (APPLE) ADD_EXECUTABLE(test_driver MACOSX_BUNDLE test_driver.cxx ) ADD_CUSTOM_COMMAND( TARGET test_driver POST_BUILD COMMAND ${CMAKE_SOURCE_DIR}/CMakeScripts/copy_mac_nib.sh test1 ) ELSE (APPLE) ADD_EXECUTABLE(test_driver test_driver.cxx ) ENDIF (APPLE) # # Libraries that are linked # TARGET_LINK_LIBRARIES(test_driver Tests Operations Algorithms OperationsBase GuiQt Brain Files Cifti Gifti Nifti FilesBase Charting Palette Scenes Xml Common ${QT_LIBRARIES} ${ZLIB_LIBRARIES} #${LIBS} ) IF(WIN32) TARGET_LINK_LIBRARIES(test_driver opengl32 glu32 ) ENDIF(WIN32) IF (UNIX) IF (NOT APPLE) TARGET_LINK_LIBRARIES(test_driver gobject-2.0 ) ENDIF (NOT APPLE) ENDIF (UNIX) # # At this time, Cocoa needs to be explicitly added for Apple Mac # IF (APPLE) #SET (QT_MAC_USE_COCOA TRUE) TARGET_LINK_LIBRARIES(test_driver "-framework Cocoa" "-framework OpenGL" ) ENDIF (APPLE) # # Find Headers # INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/Tests ${CMAKE_SOURCE_DIR}/Operations ${CMAKE_SOURCE_DIR}/Algorithms ${CMAKE_SOURCE_DIR}/OperationsBase ${CMAKE_SOURCE_DIR}/GuiQt ${CMAKE_SOURCE_DIR}/Brain ${CMAKE_SOURCE_DIR}/Charting ${CMAKE_SOURCE_DIR}/Palette ${CMAKE_SOURCE_DIR}/FilesBase ${CMAKE_SOURCE_DIR}/Files ${CMAKE_SOURCE_DIR}/Cifti ${CMAKE_SOURCE_DIR}/Gifti ${CMAKE_SOURCE_DIR}/Nifti ${CMAKE_SOURCE_DIR}/Scenes ${CMAKE_SOURCE_DIR}/Xml ${CMAKE_SOURCE_DIR}/Common ) workbench-1.1.1/src/Tests/CiftiFileTest.cxx000066400000000000000000000145261255417355300206310ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CiftiFileTest.h" #include "CiftiFile.h" using namespace caret; CiftiFileTest::CiftiFileTest(const AString &identifier) : TestInterface(identifier) { } void CiftiFileTest::execute() { testObjectCreateDestroy(); if(this->failed()) return; testCiftiRead(); if(this->failed()) return; testCiftiReadWriteInMemory(); if(this->failed()) return; testCiftiReadWriteOnDisk(); if(this->failed()) return; } void CiftiFileTest::testObjectCreateDestroy() { CiftiFile *ciftiFile = new CiftiFile(); if(ciftiFile) std::cout << "Object created successfully." << std::endl; else { setFailed("Error creating object."); return; } delete ciftiFile; } void CiftiFileTest::testCiftiRead() { //warning the data set for this is HUGE, and it will take a few minutes to run //this test is not recommended for day to day testing. The test of CiftiReadWrite //should be more than sufficient once writing has been implemented std::cout << "Testing Cifti reader." << std::endl; if(!QFile::exists(this->m_default_path + "/cifti/DenseConnectome.dconn.nii")) { setFailed("Test file "+this->m_default_path + "/cifti/DenseConnectome.dconn.nii"+" does not exist!"); return; } CiftiFile reader(this->m_default_path + "/cifti/DenseConnectome.dconn.nii"); std::vector dim; int64_t columnSize = reader.getNumberOfRows(); int64_t rowSize = reader.getNumberOfColumns(); if(rowSize != columnSize) { setFailed("The Cifti Reader test must be run on symmetric, dense connectivity matrices"); return; } float * row = new float[rowSize]; float * column = new float [columnSize]; for(int64_t i = 0;i(column[j]+0.0001))|| (row[j]<(column[j]-0.0001))) { setFailed("Row and Column " + AString::number(i) + " are not the same."); //std::cout << "Row " + AString::number(i) + ":" + AString::fromNumbers(row,rowSize,",") << std::endl; //std::cout << "Column " + AString::number(i) + ":" + AString::fromNumbers(column,columnSize,",") << std::endl; goto cleanup; } } } std::cout << "Files are the same, Cifti reader test successful." << std::endl; cleanup: delete [] row; delete [] column; } void CiftiFileTest::testCiftiReadWriteInMemory() { std::cout << "Testing Cifti reader/writer." << std::endl; // read header CiftiFile reader(this->m_default_path + "/cifti/DenseTimeSeries.dtseries.nii"); // read XML const CiftiXMLOld& root = reader.getCiftiXMLOld(); //hack TODO, this gives it a name to write to, change to write and cleanup temp //files if necessary AString outFile = this->m_default_path + "/cifti/testOut.dtseries.nii"; if(QFile::exists(outFile)) QFile::remove(outFile); CiftiFile writer; writer.setCiftiXML(root); std::vector dim = reader.getDimensions(); if (dim.size() != 2) setFailed("input file must have 2 dimensions"); int64_t rowSize = dim[0]; int64_t columnSize = dim[1]; float *row = new float [rowSize]; for(int64_t i = 0;isetFailed("Input and output Cifti file rows are not the same."); return; } } std::cout << "Reading and writing of Cifti was successful for all frames." << std::endl; delete [] row; delete [] testRow; } void CiftiFileTest::testCiftiReadWriteOnDisk() { std::cout << "Testing Cifti reader/writer." << std::endl; // read header CiftiFile reader(this->m_default_path + "/cifti/DenseTimeSeries.dtseries.nii"); // read XML const CiftiXMLOld& root = reader.getCiftiXMLOld(); AString outFile = this->m_default_path + "/cifti/testOut.dtseries.nii"; if(QFile::exists(outFile)) QFile::remove(outFile); CiftiFile writer; writer.setWritingFile(outFile); writer.setCiftiXML(root); std::vector dim = reader.getDimensions(); if (dim.size() != 2) setFailed("input file must have 2 dimensions"); int64_t rowSize = dim[0]; int64_t columnSize = dim[1]; float *row = new float [rowSize]; for(int64_t i = 0;isetFailed("Input and output Cifti file rows are not the same."); return; } } std::cout << "Reading and writing of Cifti was successful for all frames." << std::endl; delete [] row; delete [] testRow; } workbench-1.1.1/src/Tests/CiftiFileTest.h000066400000000000000000000023311255417355300202450ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "TestInterface.h" #ifndef CIFTIFILETEST_H #define CIFTIFILETEST_H namespace caret { class CiftiFileTest : public TestInterface { public: CiftiFileTest(const AString &identifier); void execute(); void testObjectCreateDestroy(); void testCiftiRead(); void testCiftiReadWriteInMemory(); void testCiftiReadWriteOnDisk(); }; } // namespace caret #endif // CIFTIFILETEST_H workbench-1.1.1/src/Tests/GeodesicHelperTest.cxx000066400000000000000000000105051255417355300216460ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "GeodesicHelperTest.h" #include "GeodesicHelper.h" #include "SurfaceFile.h" #include using namespace caret; using namespace std; GeodesicHelperTest::GeodesicHelperTest(const AString& identifier): TestInterface(identifier) { } namespace { void checkNodeLists(GeodesicHelperTest* theTest, const AString& condition, const vector& first, const vector& second) { if (first.size() != second.size()) { theTest->setFailed(condition + ", found different size node lists"); return; } for (size_t i = 0; i < first.size(); ++i) { if (first[i] != second[i]) { theTest->setFailed(condition + ", found different node at position " + AString::number(i) + " of " + AString::number(first.size())); return; } } } } void GeodesicHelperTest::execute() { SurfaceFile mySurf; mySurf.readFile(m_default_path + "/gifti/Human.PALS_B12.LEFT_AVG_B1-12.FIDUCIAL_FLIRT.clean.73730.surf.gii"); CaretPointer normalHelp = mySurf.getGeodesicHelper(); vector areas; mySurf.computeNodeAreas(areas); int numNodes = (int)areas.size(); for (int i = 0; i < numNodes; ++i) { areas[i] *= 0.25f;//use a power of 4 so as to not lose any precision (won't be denormals or max exponent, but we do take the square root), so answers should be exactly the same }//WARNING: line-following will not give same answer with corrected areas, because the line following penalty is euclidean distance CaretPointer quarterHelpBase(new GeodesicHelperBase(&mySurf, areas.data())); CaretPointer quarterHelp(new GeodesicHelper(quarterHelpBase)); for (int i = 0; i < numNodes; ++i) { areas[i] *= 16.0f;//ditto } CaretPointer quadHelpBase(new GeodesicHelperBase(&mySurf, areas.data())); CaretPointer quadHelp(new GeodesicHelper(quadHelpBase)); vector followData(numNodes); for (int i = 0; i < numNodes; ++i) { followData[i] = 1.0f + ((float)rand()) / RAND_MAX; } const int TEST_SAMPLES = 10; vector distsNorm, distsQuarter, distsQuad; vector nodesNorm, nodesQuarter, nodesQuad; for (int i = 0; !failed() && i < TEST_SAMPLES; ++i) { int32_t startNode = rand() % numNodes; const float MAX_GEO_DIST = 20.0f; normalHelp->getNodesToGeoDist(startNode, MAX_GEO_DIST, nodesNorm, distsNorm); quarterHelp->getNodesToGeoDist(startNode, MAX_GEO_DIST * 0.5f, nodesQuarter, distsQuarter); quadHelp->getNodesToGeoDist(startNode, MAX_GEO_DIST * 2.0f, nodesQuad, distsQuad); checkNodeLists(this, "Comparing normal to quarter areas, getNodesToGeoDist", nodesNorm, nodesQuarter); checkNodeLists(this, "Comparing normal to quad areas, getNodesToGeoDist", nodesNorm, nodesQuad); int32_t endNode = rand() % numNodes; normalHelp->getPathFollowingData(startNode, endNode, followData.data(), nodesNorm, distsNorm); quarterHelp->getPathFollowingData(startNode, endNode, followData.data(), nodesQuarter, distsQuarter); quadHelp->getPathFollowingData(startNode, endNode, followData.data(), nodesQuad, distsQuad); checkNodeLists(this, "Comparing normal to quarter areas, getPathFollowingData", nodesNorm, nodesQuarter); checkNodeLists(this, "Comparing normal to quad areas, getPathFollowingData", nodesNorm, nodesQuad); } } workbench-1.1.1/src/Tests/GeodesicHelperTest.h000066400000000000000000000022061255417355300212720ustar00rootroot00000000000000#ifndef __GEODESIC_HELPER_TEST_H__ #define __GEODESIC_HELPER_TEST_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "TestInterface.h" namespace caret { class GeodesicHelperTest : public TestInterface { public: GeodesicHelperTest(const AString& identifier); virtual void execute(); }; } #endif //__GEODESIC_HELPER_TEST_H__ workbench-1.1.1/src/Tests/HeapTest.cxx000066400000000000000000000170621255417355300176460ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "HeapTest.h" #include "CaretHeap.h" #include using namespace caret; using namespace std; HeapTest::HeapTest(const AString& identifier) : TestInterface(identifier) { } void HeapTest::execute() { CaretMinHeap myHeap; const int NUM_ELEMS = 500; const int NUM_REMOVE = 30; int testarray[NUM_ELEMS][2]; int64_t indexes[NUM_ELEMS]; bool markarray[NUM_ELEMS]; for (int i = 0; i < NUM_ELEMS; ++i) { testarray[i][0] = i + 5;//data testarray[i][1] = rand() % 1000;//key markarray[i] = false; indexes[i] = myHeap.push(testarray[i][0], testarray[i][1]); } for (int i = 0; i < NUM_ELEMS; ++i) { testarray[i][1] = rand() % 1000;//new key myHeap.changekey(indexes[i], testarray[i][1]); } int prevkey = 0, thiskey = 0, thisdata = 0; for (int i = 0; i < NUM_REMOVE; ++i) { int which = rand() % NUM_ELEMS; while (markarray[which]) which = rand() % NUM_ELEMS; thisdata = myHeap.remove(indexes[which], &thiskey); if (thisdata < 5 || thisdata > NUM_ELEMS + 4) { setFailed("data DESTROYED at " + AString::number(i)); continue;//don't let it segfault trying to mark it } if (which != thisdata - 5) { setFailed("data/index pairing lost at " + AString::number(i)); } if (testarray[thisdata - 5][1] != thiskey) { setFailed("data scrambled at " + AString::number(i)); } if (markarray[thisdata - 5]) { setFailed("element duplicated at " + AString::number(i)); } markarray[thisdata - 5] = true; } thiskey = 0; for (int i = 0; i < NUM_ELEMS - NUM_REMOVE; ++i) { prevkey = thiskey; thisdata = myHeap.pop(&thiskey); if (i != 0 && thiskey < prevkey) { setFailed("key ordering failed at " + AString::number(i)); } if (thisdata < 5 || thisdata > NUM_ELEMS + 4) { setFailed("data DESTROYED at " + AString::number(i)); continue;//don't let it segfault trying to mark it } if (testarray[thisdata - 5][1] != thiskey) { setFailed("data scrambled at " + AString::number(i)); } if (markarray[thisdata - 5]) { setFailed("element duplicated at " + AString::number(i)); } markarray[thisdata - 5] = true; } if (myHeap.size() != 0) setFailed("heap should be empty, but isn't"); CaretSimpleMinHeap mySimpleHeap; for (int i = 0; i < NUM_ELEMS; ++i) { testarray[i][1] = rand() % 1000;//key markarray[i] = false; mySimpleHeap.push(testarray[i][0], testarray[i][1]); } thiskey = 0; for (int i = 0; i < NUM_ELEMS; ++i) { prevkey = thiskey; thisdata = mySimpleHeap.pop(&thiskey); if (i != 0 && thiskey < prevkey) { setFailed("key ordering failed at " + AString::number(i)); } if (thisdata < 5 || thisdata > NUM_ELEMS + 4) { setFailed("data DESTROYED at " + AString::number(i)); continue;//don't let it segfault trying to mark it } if (testarray[thisdata - 5][1] != thiskey) { setFailed("data scrambled at " + AString::number(i)); } if (markarray[thisdata - 5]) { setFailed("element duplicated at " + AString::number(i)); } markarray[thisdata - 5] = true; } if (mySimpleHeap.size() != 0) setFailed("heap should be empty, but isn't"); CaretMaxHeap myMaxHeap; for (int i = 0; i < NUM_ELEMS; ++i) { testarray[i][1] = rand() % 1000;//key markarray[i] = false; indexes[i] = myMaxHeap.push(testarray[i][0], testarray[i][1]); } for (int i = 0; i < NUM_ELEMS; ++i) { testarray[i][1] = rand() % 1000;//new key myMaxHeap.changekey(indexes[i], testarray[i][1]); } for (int i = 0; i < NUM_REMOVE; ++i) { int which = rand() % NUM_ELEMS; while (markarray[which]) which = rand() % NUM_ELEMS; thisdata = myMaxHeap.remove(indexes[which], &thiskey); if (thisdata < 5 || thisdata > NUM_ELEMS + 4) { setFailed("data DESTROYED at " + AString::number(i)); continue;//don't let it segfault trying to mark it } if (which != thisdata - 5) { setFailed("data/index pairing lost at " + AString::number(i)); } if (testarray[thisdata - 5][1] != thiskey) { setFailed("data scrambled at " + AString::number(i)); } if (markarray[thisdata - 5]) { setFailed("element duplicated at " + AString::number(i)); } markarray[thisdata - 5] = true; } thiskey = 0; for (int i = 0; i < NUM_ELEMS - NUM_REMOVE; ++i) { prevkey = thiskey; thisdata = myMaxHeap.pop(&thiskey); if (i != 0 && thiskey > prevkey) { setFailed("key ordering failed at " + AString::number(i)); } if (thisdata < 5 || thisdata > NUM_ELEMS + 4) { setFailed("data DESTROYED at " + AString::number(i)); continue;//don't let it segfault trying to mark it } if (testarray[thisdata - 5][1] != thiskey) { setFailed("data scrambled at " + AString::number(i)); } if (markarray[thisdata - 5]) { setFailed("element duplicated at " + AString::number(i)); } markarray[thisdata - 5] = true; } if (myMaxHeap.size() != 0) setFailed("heap should be empty, but isn't"); CaretSimpleMaxHeap mySimpleMaxHeap; for (int i = 0; i < NUM_ELEMS; ++i) { testarray[i][1] = rand() % 1000;//key markarray[i] = false; mySimpleMaxHeap.push(testarray[i][0], testarray[i][1]); } thiskey = 0; for (int i = 0; i < NUM_ELEMS; ++i) { prevkey = thiskey; thisdata = mySimpleMaxHeap.pop(&thiskey); if (i != 0 && thiskey > prevkey) { setFailed("key ordering failed at " + AString::number(i)); } if (thisdata < 5 || thisdata > NUM_ELEMS + 4) { setFailed("data DESTROYED at " + AString::number(i)); continue;//don't let it segfault trying to mark it } if (testarray[thisdata - 5][1] != thiskey) { setFailed("data scrambled at " + AString::number(i)); } if (markarray[thisdata - 5]) { setFailed("element duplicated at " + AString::number(i)); } markarray[thisdata - 5] = true; } if (mySimpleMaxHeap.size() != 0) setFailed("heap should be empty, but isn't"); } workbench-1.1.1/src/Tests/HeapTest.h000066400000000000000000000021201255417355300172600ustar00rootroot00000000000000#ifndef __HEAPTEST_H__ #define __HEAPTEST_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "TestInterface.h" namespace caret { class HeapTest : public TestInterface { public: HeapTest(const AString& identifier); virtual void execute(); }; } #endif // __HEAPTEST_H__ workbench-1.1.1/src/Tests/HttpTest.cxx000066400000000000000000000030661255417355300177070ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "HttpTest.h" #include "CaretHttpManager.h" using namespace caret; HttpTest::HttpTest(const AString& identifier) : TestInterface(identifier) { } void HttpTest::execute() { CaretHttpManager* myMgr = CaretHttpManager::getHttpManager(); CaretHttpRequest myReq; myReq.m_url = "http://www.google.com/"; myReq.m_method = CaretHttpManager::GET; CaretHttpResponse myResp; myMgr->httpRequest(myReq, myResp); if (!myResp.m_ok) { setFailed(AString("retrieving google.com failed, code ") + AString::number(myResp.m_responseCode)); } if (myResp.m_ok && myResp.m_responseCode != 200) { setFailed(AString("response said OK, but status code is ") + AString::number(myResp.m_responseCode)); } } workbench-1.1.1/src/Tests/HttpTest.h000066400000000000000000000021201255417355300173220ustar00rootroot00000000000000#ifndef __HTTPTEST_H__ #define __HTTPTEST_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "TestInterface.h" namespace caret { class HttpTest : public TestInterface { public: HttpTest(const AString& identifier); virtual void execute(); }; } #endif // __HTTPTEST_H__ workbench-1.1.1/src/Tests/LookupTest.cxx000066400000000000000000000070501255417355300202360ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "LookupTest.h" #include "CaretCompactLookup.h" #include "CaretCompact3DLookup.h" #include using namespace caret; using namespace std; LookupTest::LookupTest(const AString& identifier) : TestInterface(identifier) { } void LookupTest::execute() { const int LOOKUP_SIZE = 500; const int LOOKUP_START = -200; const int NUM_INSERT = 400; CaretCompactLookup myLookup; vector checkLookup(LOOKUP_SIZE, -1); for (int i = 0; i < NUM_INSERT; ++i) { int position = rand() % LOOKUP_SIZE; int value = rand(); checkLookup[position] = value; myLookup[position + LOOKUP_START] = value; } const CaretCompactLookup& constRef = myLookup; for (int i = 0; i < LOOKUP_SIZE; ++i) { CaretCompactLookup::const_iterator iter = constRef.find(i + LOOKUP_START); if (iter == constRef.end()) { if (checkLookup[i] != -1) setFailed("lost value for key " + AString::number(i + LOOKUP_START)); } else { if (checkLookup[i] == -1) setFailed("created unset value for key " + AString::number(i + LOOKUP_START)); if (*iter != checkLookup[i]) setFailed("value corrupted for key " + AString::number(i + LOOKUP_START)); } } for (int i = 0; i < LOOKUP_SIZE; ++i) { CaretCompactLookup::iterator iter = myLookup.find(i + LOOKUP_START); if (iter == myLookup.end()) { if (checkLookup[i] != -1) setFailed("lost value for key " + AString::number(i + LOOKUP_START)); } else { if (checkLookup[i] == -1) setFailed("created unset value for key " + AString::number(i + LOOKUP_START)); if (*iter != checkLookup[i]) setFailed("value corrupted for key " + AString::number(i + LOOKUP_START)); } } /* 3D Lookup Testing */ { const int64_t NUM = 3; int64_t array[NUM][3] = { { 1, 2, 3 }, { 7, 5, 4 }, { 5, 9, 1 } }; CaretCompact3DLookup lookup3D; for (int64_t i = 0; i < NUM; i++) { lookup3D.at(array[i]) = i; } for (int64_t i = 0; i < NUM; i++) { const int64_t* value = lookup3D.find(array[i]); if (value == NULL) { setFailed("Item " + AString::number(i) + " was not found."); } else { if (i != *value) { setFailed("Item " + AString::number(i) + " with IJK (" + AString::fromNumbers(array[i], 3, ", ") + ") when searched with find() has value " + AString::number(*value)); } } } } } workbench-1.1.1/src/Tests/LookupTest.h000066400000000000000000000021231255417355300176570ustar00rootroot00000000000000#ifndef __LOOKUP_TEST_H__ #define __LOOKUP_TEST_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "TestInterface.h" namespace caret { class LookupTest : public TestInterface { public: LookupTest(const AString& identifier); virtual void execute(); }; } #endif //__LOOKUP_TEST_H__ workbench-1.1.1/src/Tests/MathExpressionTest.cxx000066400000000000000000000042041255417355300217340ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "MathExpressionTest.h" #include "CaretMathExpression.h" #include using namespace caret; using namespace std; MathExpressionTest::MathExpressionTest(const AString& identifier) : TestInterface(identifier) { } void MathExpressionTest::execute() { CaretMathExpression myExpr(" sin ( - yip * 5 ) + x ^ 3 * ( clamp(1, 3, 5) + 2 ) + - 2 ^ - 2 ");//clamp(1, 3, 5) equals 3 vector vars(2); const float TOLER = 0.000001f; vector varNames = myExpr.getVarNames(); if (varNames.size() != 2) setFailed("incorrect number of variables found"); float x = 3.75f; float yip = -2.75f; if (varNames[0] == "x") { if (varNames[0] != "x" || varNames[1] != "yip") setFailed("found incorrect variable names"); vars[0] = x; vars[1] = yip; } else { if (varNames[0] != "yip" || varNames[1] != "x") setFailed("found incorrect variable names"); vars[0] = yip; vars[1] = x; } double testresult = myExpr.evaluate(vars); double correctresult = sin(-yip * 5.0) + pow((double)x, 3.0) * (3.0 + 2.0) + -pow(2.0, -2.0); if (!(abs(testresult - correctresult) <= correctresult * TOLER))//trap NaNs { setFailed("output value incorrect, expected " + AString::number(correctresult) + ", got " + AString::number(testresult)); } } workbench-1.1.1/src/Tests/MathExpressionTest.h000066400000000000000000000021761255417355300213670ustar00rootroot00000000000000#ifndef __MATH_EXPRESSION_TEST_H__ #define __MATH_EXPRESSION_TEST_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "TestInterface.h" namespace caret { class MathExpressionTest : public TestInterface { public: MathExpressionTest(const AString& identifier); virtual void execute(); }; } #endif //__MATH_EXPRESSION_TEST_H__ workbench-1.1.1/src/Tests/NiftiTest.cxx000066400000000000000000000120441255417355300200350ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "NiftiTest.h" #include "MultiDimIterator.h" #include "NiftiIO.h" #include using namespace std; using namespace caret; NiftiFileTest::NiftiFileTest(const AString &identifier) : TestInterface(identifier) { } void NiftiFileTest::execute() { if(this->failed()) return; testNiftiReadWrite(); if(this->failed()) return; } void NiftiFileTest::testNiftiReadWrite() { std::cout << "Testing Nifti1 reader/writer." << std::endl; NiftiIO reader; AString inputFile = this->m_default_path + "/nifti/fcMRI1_nonlin_Subcortical_Smoothed_s6.nii"; reader.openRead(inputFile); const NiftiHeader& header = reader.getHeader(); //hack TODO, this gives it a name to write to, change to write and cleanup temp //files if necessary AString outFile = this->m_default_path + "/nifti/Nifti1TestOut.nii"; NiftiIO writer; writer.writeNew(outFile, header); const vector& dims = reader.getDimensions(); if (dims.size() < 3) { setFailed("this test requires nifti files with 3 or more dimensions"); return; } int64_t frameLength = dims[0] * dims[1] * dims[2] * reader.getNumComponents(); vector frame(frameLength); vector extraDims(dims.begin() + 3, dims.end()); for(MultiDimIterator iter(extraDims); !iter.atEnd(); ++iter) { reader.readData(frame.data(), 3, *iter); writer.writeData(frame.data(), 3, *iter); } writer.close(); reader.close(); reader.openRead(inputFile);//so that it can work on .gz files, which you can't seek backwards in //reopen output file, and check that frames agree NiftiIO test; vector frameTest(frameLength); for(MultiDimIterator iter(extraDims); !iter.atEnd(); ++iter) { reader.readData(frame.data(), 3, *iter); test.readData(frameTest.data(), 3, *iter); for(int i=0;iframeTest[i]+0.0001 || frame[i]setFailed("Input and output nifti file frames are not the same."); return; } } } std::cout << "Reading and writing of Nifti was successful for all frames." << std::endl; } //Tests for reading and writing Nifti Headers NiftiHeaderTest::NiftiHeaderTest(const AString &identifier) : TestInterface(identifier) { } void NiftiHeaderTest::execute() { NiftiHeader n1header; NiftiHeader n2header; readNiftiHeader("../../../wb_files/nifti/fcMRI1_nonlin_Subcortical_Smoothed_s6.nii", n1header); readNiftiHeader("../../../wb_files/nifti/test.cii", n2header); writeNifti1Header("../../../wb_files/nifti/n1.nii",n1header); writeNifti2Header("../../../wb_files/nifti/n2.cii",n2header); NiftiHeader n1headercomp; NiftiHeader n2headercomp; readNiftiHeader("../../../wb_files/nifti/n1.nii", n1headercomp); readNiftiHeader("../../../wb_files/nifti/n2.cii", n2headercomp); if(n1header != n1headercomp) { //report error AString message; message = AString("Nifti1 Header Reader/Writer error, files do not match."); setFailed(message); } else { std::cout << "Nifti1 Header Reader/Writer passes basic tests" << std::endl; } if(n2header != n2headercomp) { //report error AString message; message = AString("Nifti2 Header Reader/Writer error, files do not match."); setFailed(message); } else { std::cout << "Nifti2 Header Reader/Writer passes basic tests" << std::endl; } } void NiftiHeaderTest::readNiftiHeader(AString filename, NiftiHeader &header) { CaretBinaryFile myFile; myFile.open(filename); header.read(myFile); } void NiftiHeaderTest::writeNifti1Header(AString filename, NiftiHeader &header) { CaretBinaryFile myFile; myFile.open(filename, CaretBinaryFile::WRITE_TRUNCATE); header.write(myFile, 1); } void NiftiHeaderTest::writeNifti2Header(AString filename, NiftiHeader &header) { CaretBinaryFile myFile; myFile.open(filename, CaretBinaryFile::WRITE_TRUNCATE); header.write(myFile, 2); } workbench-1.1.1/src/Tests/NiftiTest.h000066400000000000000000000027011255417355300174610ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #ifndef NIFTITEST_H #define NIFTITEST_H #include "TestInterface.h" #include "NiftiHeader.h" namespace caret { class NiftiFileTest : public TestInterface { public: NiftiFileTest(const AString& identifier); virtual void execute(); void testNiftiReadWrite(); }; class NiftiHeaderTest : public TestInterface { public: NiftiHeaderTest(const AString& identifier); virtual void execute(); void readNiftiHeader(AString filename, NiftiHeader &header); void writeNifti1Header(AString filename, NiftiHeader &header); void writeNifti2Header(AString filename, NiftiHeader &header); }; } #endif // NIFTITEST_H workbench-1.1.1/src/Tests/PointerTest.cxx000066400000000000000000000156171255417355300204150ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "PointerTest.h" //#include #include "CaretPointer.h" #include "CaretMutex.h" #include "CaretOMP.h" #include using namespace caret; using namespace std; struct DelTestObj { int* m_pointer; CaretMutex m_mutex; DelTestObj(int* pointerIn) { m_pointer = pointerIn; *m_pointer = 0; } ~DelTestObj() { CaretMutexLocker locked(&m_mutex);//so we don't have any race conditions in the test object ++(*m_pointer); } }; PointerTest::PointerTest(const AString& identifier) : TestInterface(identifier) { } void PointerTest::execute() { const int ITERATIONS = 500; CaretMutex messageMutex;//QString does NOT behave well under threading int deltrack1, deltrack2, deltrack3; {//scope to control when things get destroyed CaretPointer myobj1(new DelTestObj(&deltrack1)), myobj2(new DelTestObj(&deltrack2)), myobj3(new DelTestObj(&deltrack3)); { CaretPointer myconstobj = myobj1;//check that pointer to const works, too } //cout << "pointers: " << myobj1.getPointer() << "\t" << myobj2.getPointer() << "\t" << myobj3.getPointer() << endl; #pragma omp CARET_PAR { CaretPointer myScratch1, myScratch2, myScratch3; #pragma omp CARET_FOR schedule(dynamic) for (int i = 0; i < ITERATIONS; ++i) { myobj1 = myobj1;//ensure self assignment is detected and handled if (myobj1 == NULL) { CaretMutexLocker locked(&messageMutex); setFailed("NULL generated, detected after self assignment, iteration " + AString::number(i)); } myScratch1 = myobj1;//simple get and put back myobj1 = myScratch1; if (myScratch1 == NULL) { CaretMutexLocker locked(&messageMutex); setFailed("NULL generated, detected after get/replace, iteration " + AString::number(i)); } myScratch1 = myobj2;//get, move, put myScratch2 = myScratch1; myobj2 = myScratch2; if (myScratch1 == NULL) { CaretMutexLocker locked(&messageMutex); setFailed("NULL generated, detected after get/move/replace, iteration " + AString::number(i)); } myScratch2 = myobj2; myScratch3 = myobj3; if (myScratch2 != myScratch3) { myobj3 = myScratch2; myobj3 = myScratch3; } if (myScratch3 == NULL) { CaretMutexLocker locked(&messageMutex); setFailed("NULL generated, detected after replace shared, iteration " + AString::number(i)); } if (deltrack1 != 0 || deltrack2 != 0 || deltrack3 != 0) { CaretMutexLocker locked(&messageMutex); setFailed("premature deletion of object detected, iteration " + AString::number(i)); } } } } if (deltrack1 == 0 || deltrack2 == 0 || deltrack3 == 0) { setFailed("object not deleted (parallel)"); } if (deltrack1 != 1 || deltrack2 != 1 || deltrack3 != 1) { setFailed("object deleted incorrect number of times (parallel)"); } {//repeat with non-synchronized pointers and no parallel code CaretPointerNonsync myobj1(new DelTestObj(&deltrack1)), myobj2(new DelTestObj(&deltrack2)), myobj3(new DelTestObj(&deltrack3)); { CaretPointerNonsync myconstobj = myobj1;//check that pointer to const works, too } //cout << "pointers: " << myobj1.getPointer() << "\t" << myobj2.getPointer() << "\t" << myobj3.getPointer() << endl; { CaretPointerNonsync myScratch1, myScratch2, myScratch3; for (int i = 0; i < ITERATIONS; ++i) { myobj1 = myobj1;//ensure self assignment is detected and handled if (myobj1 == NULL) { setFailed("NULL generated, detected after self assignment, iteration " + AString::number(i)); } myScratch1 = myobj1;//simple get and put back myobj1 = myScratch1; if (myScratch1 == NULL) { setFailed("NULL generated, detected after get/replace, iteration " + AString::number(i)); } myScratch1 = myobj2;//get, move, put myScratch2 = myScratch1; myobj2 = myScratch2; if (myScratch1 == NULL) { setFailed("NULL generated, detected after get/move/replace, iteration " + AString::number(i)); } myScratch2 = myobj2; myScratch3 = myobj3; if (myScratch2 != myScratch3) { myobj3 = myScratch2; myobj3 = myScratch3; } if (myScratch3 == NULL) { setFailed("NULL generated, detected after replace shared, iteration " + AString::number(i)); } if (deltrack1 != 0 || deltrack2 != 0 || deltrack3 != 0) { setFailed("premature deletion of object detected, iteration " + AString::number(i)); } } } } if (deltrack1 == 0 || deltrack2 == 0 || deltrack3 == 0) { setFailed("object not deleted"); } if (deltrack1 != 1 || deltrack2 != 1 || deltrack3 != 1) { setFailed("object deleted incorrect number of times"); } } workbench-1.1.1/src/Tests/PointerTest.h000066400000000000000000000021301255417355300200240ustar00rootroot00000000000000#ifndef __POINTER_TEST_H__ #define __POINTER_TEST_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "TestInterface.h" namespace caret { class PointerTest : public TestInterface { public: PointerTest(const AString& identifier); virtual void execute(); }; } #endif //__POINTER_TEST_H__ workbench-1.1.1/src/Tests/ProgressTest.cxx000066400000000000000000000105101255417355300205640ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "EventManager.h" #include "ProgressTest.h" using namespace std; using namespace caret; TestAlgorithm::TestAlgorithm(ProgressObject* myproginfo, bool testOver) : AbstractAlgorithm(myproginfo) { m_failed = false; const float oddEndWeight = 17.0f; LevelProgress myLevel(myproginfo, oddEndWeight);//to test strange values if (myproginfo->getCurrentProgressFraction() != 0.0f || myproginfo->getCurrentProgressPercent() != 0.0f) { cout << "initial timer progress is not zero!" << endl; m_failed = true; } AString teststring = AString("some random text"); myLevel.setTask(teststring); if (myproginfo->getTaskDescription() != teststring) { cout << "task description returns a different string than set" << endl; m_failed = true; } float tooSmall = 0.0005f * oddEndWeight;//progress should NOT update at less than 0.1%, or 0.001 * total myLevel.reportProgress(tooSmall); if (myproginfo->getCurrentProgressFraction() != 0.0f || myproginfo->getCurrentProgressPercent() != 0.0f) { cout << "progress update of " << tooSmall / oddEndWeight * 100.0f << "% triggers update!" << endl; m_failed = true; } float someProgressFraction = 0.10f; float someProgress = someProgressFraction * oddEndWeight; myLevel.reportProgress(someProgress); if (myproginfo->getCurrentProgressFraction() < someProgressFraction * 0.95f || myproginfo->getCurrentProgressPercent() < someProgressFraction * 95.0f || myproginfo->getCurrentProgressFraction() > someProgressFraction * 1.05f || myproginfo->getCurrentProgressPercent() > someProgressFraction * 105.0f) { cout << "progress update of " << someProgress / oddEndWeight * 100.0f << "% returns result of " << myproginfo->getCurrentProgressFraction() << " and " << myproginfo->getCurrentProgressPercent() << "% !" << endl; m_failed = true; } if (testOver) { myLevel.reportProgress(oddEndWeight * 1.3f); if (myproginfo->getCurrentProgressFraction() > 1.0f || myproginfo->getCurrentProgressPercent() > 100.0f) { cout << "output if weight goes over is " << myproginfo->getCurrentProgressFraction() << " and " << myproginfo->getCurrentProgressPercent() << "% !" << endl; m_failed = true; } } } TestAlgorithm2::TestAlgorithm2(ProgressObject* myproginfo): AbstractAlgorithm(myproginfo) { //do nothing, simulate ignoring the object } ProgressTest::ProgressTest(const AString& identifier): TestInterface(identifier) { } void ProgressTest::execute() { ProgressObject myprog1(TestAlgorithm::getAlgorithmWeight()), myprog2(TestAlgorithm::getAlgorithmWeight()), myprog3(TestAlgorithm2::getAlgorithmWeight()); TestAlgorithm myalg(&myprog1, false); if (myprog1.getCurrentProgressFraction() != 1.0f || myprog1.getCurrentProgressPercent() != 100.0f) { setFailed(AString("Finished state reports ") + AString::number(myprog1.getCurrentProgressFraction()) + AString(" and ") + AString::number(myprog1.getCurrentProgressPercent()) + AString("%")); } if (myalg.m_failed) { setFailed("Algorithm reported failure internally"); } { TestAlgorithm myalg2(&myprog2, true); if (myalg2.m_failed) { setFailed("Algorithm reported failure internally"); } } { TestAlgorithm2 myalg3(&myprog3); } if (myprog3.getCurrentProgressFraction() != 1.0f || myprog3.getCurrentProgressPercent() != 100.0f) { setFailed("ignored progress object does not register as completed upon algorithm destruction"); } } workbench-1.1.1/src/Tests/ProgressTest.h000066400000000000000000000026551255417355300202240ustar00rootroot00000000000000#ifndef __PROGRESS_TEST_H__ #define __PROGRESS_TEST_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "TestInterface.h" #include "AbstractAlgorithm.h" namespace caret { class TestAlgorithm : public AbstractAlgorithm { public: bool m_failed; TestAlgorithm(ProgressObject* myproginfo, bool testOver); }; class TestAlgorithm2 : public AbstractAlgorithm { public: bool m_failed; TestAlgorithm2(ProgressObject* myproginfo); }; class ProgressTest : public TestInterface { public: ProgressTest(const AString& identifier); virtual void execute(); }; } #endif //__PROGRESS_TEST_H__ workbench-1.1.1/src/Tests/QuatTest.cxx000066400000000000000000000111631255417355300176770ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "QuatTest.h" #include "MathFunctions.h" #include "Vector3D.h" #include #include using namespace caret; using namespace std; QuatTest::QuatTest(const AString& identifier) : TestInterface(identifier) { } bool QuatTest::matrixMatches(const float a[3][3], const float b[3][3]) { const float toler = 0.0001f; for (int i = 0; i < 3; ++i) for (int j = 0; j < 3; ++j) if (abs(a[i][j] - b[i][j]) > toler) return false; return true; } bool QuatTest::quatMatches(const float a[4], const float b[4]) { const float toler = 0.0001f; for (int i = 0; i < 4; ++i) if (abs(a[i] - b[i]) > toler) return false; return true; } AString QuatTest::showMat(const float a[3][3]) { AString ret = "\n"; for (int i = 0; i < 3; ++i) { ret += AString::number(a[i][0]); for (int j = 1; j < 3; ++j) { ret += "\t" + AString::number(a[i][j]); } ret += "\n"; } return ret; } AString QuatTest::showQuat(const float a[4]) { AString ret = AString::number(a[0]); for (int i = 1; i < 3; ++i) ret += " " + AString::number(a[i]); return ret; } void QuatTest::execute() { const int ITERS = 1000; for (int i = 0; i < ITERS; ++i) { float startquat[4] = { (rand() & 32767) / 32767.0f * 2.0f - 1.0f, (rand() & 32767) / 32767.0f * 2.0f - 1.0f, (rand() & 32767) / 32767.0f * 2.0f - 1.0f, (rand() & 32767) / 32767.0f * 2.0f - 1.0f }; float mat[3][3], endquat[4]; float startlen = sqrt(startquat[0] * startquat[0] + startquat[1] * startquat[1] + startquat[2] * startquat[2] + startquat[3] * startquat[3]); if (startlen < 0.01f) continue; if (startquat[0] < 0.0f) startlen = -startlen; startquat[0] /= startlen; startquat[1] /= startlen; startquat[2] /= startlen; startquat[3] /= startlen; MathFunctions::quaternToMatrix(startquat, mat); if (!MathFunctions::matrixToQuatern(mat, endquat)) { setFailed("matrix from quaternToMatrix failed to convert to quaternion for quat: " + showQuat(startquat)); } if (!quatMatches(startquat, endquat)) { setFailed("quaternion " + showQuat(startquat) + " produced nonmatching quaternion " + showQuat(endquat)); } } for (int i = 0; i < ITERS; ++i) { float ivecf[3] = { (rand() & 32767) / 32767.0f * 2.0f - 1.0f, (rand() & 32767) / 32767.0f * 2.0f - 1.0f, (rand() & 32767) / 32767.0f * 2.0f - 1.0f }; Vector3D ivec = ivecf; if (ivec.length() < 0.01f) continue; float jvecf[3] = { (rand() & 32767) / 32767.0f * 2.0f - 1.0f, (rand() & 32767) / 32767.0f * 2.0f - 1.0f, (rand() & 32767) / 32767.0f * 2.0f - 1.0f }; Vector3D jvec = jvecf; ivec = ivec.normal(); jvec = jvec - ivec * jvec.dot(ivec);//makes jvec orthogonal to ivec if (jvec.length() < 0.01f) continue; jvec = jvec.normal(); Vector3D kvec = ivec.cross(jvec);//should be a unit vector, orthogonal to both float startmat[3][3], quat[4], endmat[3][3]; startmat[0][0] = ivec[0]; startmat[1][0] = ivec[1]; startmat[2][0] = ivec[2]; startmat[0][1] = jvec[0]; startmat[1][1] = jvec[1]; startmat[2][1] = jvec[2]; startmat[0][2] = kvec[0]; startmat[1][2] = kvec[1]; startmat[2][2] = kvec[2]; if (!MathFunctions::matrixToQuatern(startmat, quat)) { setFailed("failed to convert matrix to quaternion:" + showMat(startmat)); continue; } MathFunctions::quaternToMatrix(quat, endmat); if (!matrixMatches(startmat, endmat)) { setFailed("matrix:" + showMat(startmat) + "produced nonmatching matrix:" + showMat(endmat)); } } } workbench-1.1.1/src/Tests/QuatTest.h000066400000000000000000000024561255417355300173310ustar00rootroot00000000000000#ifndef __QUATTEST_H__ #define __QUATTEST_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "TestInterface.h" namespace caret { class QuatTest : public TestInterface { bool matrixMatches(const float a[3][3], const float b[3][3]); bool quatMatches(const float a[4], const float b[4]); AString showQuat(const float a[4]); AString showMat(const float a[3][3]); public: QuatTest(const AString& identifier); virtual void execute(); }; } #endif // __QUATTEST_H__ workbench-1.1.1/src/Tests/StatisticsTest.cxx000066400000000000000000000077131255417355300211250ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "StatisticsTest.h" #include #include #include "FastStatistics.h" #include "DescriptiveStatistics.h" using namespace caret; using namespace std; StatisticsTest::StatisticsTest(const AString& identifier) : TestInterface(identifier) { } void StatisticsTest::execute() { const int NUM_ELEMENTS = 1 << 20; vector myData(NUM_ELEMENTS);//dynamically allocate to not take lots of stack space for (int i = 0; i < NUM_ELEMENTS; ++i) { myData[i] = (rand() * 100.0f / RAND_MAX) - 50.0f; } DescriptiveStatistics myFullStats; myFullStats.update(myData.data(), NUM_ELEMENTS); FastStatistics myFastStats(myData.data(), NUM_ELEMENTS); float exacttolerance = myFullStats.getPopulationStandardDeviation() * 0.000001f; float approxtolerance = myFullStats.getPopulationStandardDeviation() * 0.01f; if (abs(myFullStats.getMinimumValue() - myFastStats.getMin()) > exacttolerance) { setFailed(AString("mismatch in min, full: ") + AString::number(myFullStats.getMinimumValue()) + ", fast: " + AString::number(myFastStats.getMin())); } if (abs(myFullStats.getMaximumValue() - myFastStats.getMax()) > exacttolerance) { setFailed(AString("mismatch in max, full: ") + AString::number(myFullStats.getMaximumValue()) + ", fast: " + AString::number(myFastStats.getMax())); } if (abs(myFullStats.getMean() - myFastStats.getMean()) > exacttolerance) { setFailed(AString("mismatch in mean, full: ") + AString::number(myFullStats.getMean()) + ", fast: " + AString::number(myFastStats.getMean())); } if (abs(myFullStats.getStandardDeviationSample() - myFastStats.getSampleStdDev()) > exacttolerance) { setFailed(AString("mismatch in sample stddev, full: ") + AString::number(myFullStats.getStandardDeviationSample()) + ", fast: " + AString::number(myFastStats.getSampleStdDev())); } if (abs(myFullStats.getPopulationStandardDeviation() - myFastStats.getPopulationStdDev()) > exacttolerance) { setFailed(AString("mismatch in population stddev, full: ") + AString::number(myFullStats.getPopulationStandardDeviation()) + ", fast: " + AString::number(myFastStats.getPopulationStdDev())); } if (abs(myFullStats.getMedian() - myFastStats.getApproximateMedian()) > approxtolerance) { setFailed(AString("mismatch in median, full: ") + AString::number(myFullStats.getMedian()) + ", fast: " + AString::number(myFastStats.getApproximateMedian())); } if (abs(myFullStats.getPositivePercentile(90.0f) - myFastStats.getApproxPositivePercentile(90.0f)) > approxtolerance) { setFailed(AString("mismatch in 90% positive percentile, full: ") + AString::number(myFullStats.getPositivePercentile(90.0f)) + ", fast: " + AString::number(myFastStats.getApproxPositivePercentile(90.0f))); } if (abs(myFullStats.getNegativePercentile(90.0f) - myFastStats.getApproxNegativePercentile(90.0f)) > approxtolerance) { setFailed(AString("mismatch in 90% negative percentile, full: ") + AString::number(myFullStats.getNegativePercentile(90.0f)) + ", fast: " + AString::number(myFastStats.getApproxNegativePercentile(90.0f))); } } workbench-1.1.1/src/Tests/StatisticsTest.h000066400000000000000000000021471255417355300205460ustar00rootroot00000000000000#ifndef __STATISTICS_TEST_H__ #define __STATISTICS_TEST_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "TestInterface.h" namespace caret { class StatisticsTest : public TestInterface { public: StatisticsTest(const AString& identifier); virtual void execute(); }; } #endif //__STATISTICS_TEST_H__ workbench-1.1.1/src/Tests/TestInterface.cxx000066400000000000000000000016361255417355300206710ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "TestInterface.h" using namespace caret; TestInterface::~TestInterface() { } workbench-1.1.1/src/Tests/TestInterface.h000066400000000000000000000036061255417355300203150ustar00rootroot00000000000000#ifndef __TEST_INTERFACE_H__ #define __TEST_INTERFACE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include namespace caret { class TestInterface { AString m_identifier, m_failMessage; bool m_failed; TestInterface();//deny construction without arguments TestInterface& operator=(const TestInterface& right);//deny assignment protected: TestInterface(const AString& identifier) : m_failMessage("") { m_identifier = identifier; m_failed = false; m_default_path = "../../wb_files"; } public: void setFailed(const AString failMessage) { m_failed = true; m_failMessage += failMessage + "\n";//append to previous messages } const AString& getIdentifier() { return m_identifier; } bool failed() { return m_failed; } const AString& getFailMessage() { return m_failMessage; } virtual void execute() = 0;//override this virtual ~TestInterface(); AString m_default_path; }; } #endif //__TEST_INTERFACE_H__ workbench-1.1.1/src/Tests/TimerTest.cxx000066400000000000000000000043131255417355300200440ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "TimerTest.h" #include "ElapsedTimer.h" #ifdef CARET_OS_WINDOWS #include "windows.h" #else #include "unistd.h" #endif using namespace caret; TimerTest::TimerTest(const AString& identifier) : TestInterface(identifier) { } void TimerTest::execute() { ElapsedTimer mytimer; mytimer.start(); #ifdef CARET_OS_WINDOWS Sleep(1000);//milliseconds on windows #else sleep(1);//seconds on *nix #endif double elapsedMilli = mytimer.getElapsedTimeMilliseconds(); double elapsed = mytimer.getElapsedTimeSeconds(); AString mymessage = "Timer innacurate"; bool failed = false; if (elapsed < 0.95) { failed = true; mymessage += ", seconds value too low ("; mymessage += AString::number(elapsed); mymessage += ")"; } if (elapsedMilli < 950.0) { failed = true; mymessage += ", milliseconds value too low ("; mymessage += AString::number(elapsedMilli); mymessage += ")"; } if (elapsed > 1.1)//allow a little extra room to go over { failed = true; mymessage += ", seconds value too high ("; mymessage += AString::number(elapsed); mymessage += ")"; } if (elapsedMilli > 1100.0) { failed = true; mymessage += ", milliseconds value too high ("; mymessage += AString::number(elapsedMilli); mymessage += ")"; } if (failed) { setFailed(mymessage); } } workbench-1.1.1/src/Tests/TimerTest.h000066400000000000000000000021161255417355300174700ustar00rootroot00000000000000#ifndef __TIMER_TEST_H__ #define __TIMER_TEST_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "TestInterface.h" namespace caret { class TimerTest : public TestInterface { public: TimerTest(const AString& identifier); virtual void execute(); }; } #endif //__TIMER_TEST_H__ workbench-1.1.1/src/Tests/TopologyHelperOld.cxx000066400000000000000000000545471255417355300215550ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "SurfaceFile.h" #include "TopologyHelperOld.h" #include "CaretAssert.h" #include using namespace caret; /** * Comparison operator for EdgeInfo */ bool caret::operator<(const TopologyEdgeInfoOld& e1, const TopologyEdgeInfoOld& e2) { if (e1.node1 == e2.node1) { return (e1.node2 < e2.node2); } return (e1.node1 < e2.node1); } /** * Constructor. This will contruct the topological information for the TopologyFile. * If "sortNodeInfo" is set the tiles and node neighbors will be sorted * around each node. */ TopologyHelperOld::TopologyHelperOld(const SurfaceFile* sf, const bool sortNodeInfo) { nodeSortedInfoBuilt = false; const int numTiles = sf->getNumberOfTriangles(); if (numTiles <= 0) { return; } int maxNodeNum = -1; // // Get the number of nodes // for (int j = 0; j < numTiles; j++) { const int32_t* tribase = sf->getTriangle(j); if (tribase[0] > maxNodeNum) maxNodeNum = tribase[0]; if (tribase[1] > maxNodeNum) maxNodeNum = tribase[1]; if (tribase[2] > maxNodeNum) maxNodeNum = tribase[2]; } // // Node indices start at zero so need to increment // maxNodeNum++; // // There may be nodes that have been disconnected and not associated with any tiles // maxNodeNum = std::max(maxNodeNum, sf->getNumberOfNodes()); // // Initialize the node structures // nodes.reserve(maxNodeNum); for (int i = 0; i < maxNodeNum; i++) { nodes.push_back(NodeInfo(i)); nodes[i].neighbors.reserve(6); nodes[i].edges.reserve(6); nodes[i].tiles.reserve(6); } // // Keep track of the tiles // for (int j = 0; j < numTiles; j++) { const int32_t* tribase = sf->getTriangle(j); if (sortNodeInfo) { nodes[tribase[0]].addNeighbors(j, tribase[1], tribase[2]); nodes[tribase[1]].addNeighbors(j, tribase[2], tribase[0]); nodes[tribase[2]].addNeighbors(j, tribase[0], tribase[1]); } else { nodes[tribase[0]].addNeighbor(tribase[1]); nodes[tribase[0]].addNeighbor(tribase[2]); nodes[tribase[1]].addNeighbor(tribase[0]); nodes[tribase[1]].addNeighbor(tribase[2]); nodes[tribase[2]].addNeighbor(tribase[0]); nodes[tribase[2]].addNeighbor(tribase[1]); nodes[tribase[0]].addTile(j); nodes[tribase[1]].addTile(j); nodes[tribase[2]].addTile(j); } addEdgeInfo(j, tribase[0], tribase[1]); addEdgeInfo(j, tribase[1], tribase[2]); addEdgeInfo(j, tribase[2], tribase[0]); } if (sortNodeInfo) { nodeSortedInfoBuilt = true; for (unsigned int k = 0; k < nodes.size(); k++) { nodes[k].sortNeighbors(); } } } /** * Destructor. */ TopologyHelperOld::~TopologyHelperOld() { nodes.clear(); topologyEdges.clear(); } /** * Keep track of edges */ void TopologyHelperOld::addEdgeInfo(const int tileNum, const int node1, const int node2) { TopologyEdgeInfoOld edge(tileNum, node1, node2); std::set::iterator iter = topologyEdges.find(edge); if (iter == topologyEdges.end()) { topologyEdges.insert(edge); } else { TopologyEdgeInfoOld& e = (TopologyEdgeInfoOld&)(*iter); e.addTile(tileNum); //iter->addTile(tileNum); } } /** * Sort a nodes neighbors */ void TopologyHelperOld::NodeInfo::sortNeighbors() { if ((edges.size() > 0) && sortMe) { // // Nodes that are on the edge (boundary) of a cut surface must be treated specially when sorting. // In this case the sorting needs to start with the link whose first node is not used // in any other link. This is true if all tiles are consistently oriented (and they // must be). // int startEdge = -1; for (unsigned int k = 0; k < edges.size(); k++) { const int nodeNum = edges[k].node1; bool foundNode = false; // // See if nodeNum is used in any other edges // for (unsigned int m = 0; m < edges.size(); m++) { if (m != k) { if (edges[m].containsNode(nodeNum) == true) { foundNode = true; break; } } } if (foundNode == false) { if (startEdge >= 0) { //std::cerr << "INFO: multiple starting edges for node " << nodeNumber << std::endl; } else { startEdge = k; } } } bool flag = false; if ((nodeNumber >= 44508) && (nodeNumber <= 44507)) { // modify for debugging std::cerr << "Start Edge for node " << nodeNumber << " is " << startEdge << std::endl; for (unsigned int k = 0; k < edges.size(); k++) { std::cerr << " " << edges[k].node1 << " " << edges[k].node2 << std::endl; } flag = true; } // startEdge is less than zero if the node's links are all interior links if (startEdge < 0) { startEdge = 0; } int currentNode = edges[startEdge].node1; int nextNeighbor = edges[startEdge].node2; neighbors.push_back(currentNode); tiles.push_back(edges[startEdge].tileNumber); const int firstNode = currentNode; for (unsigned int i = 1; i < edges.size(); i++) { neighbors.push_back(nextNeighbor); // find edge with node "nextNeighbor" but without node "currentNode" const NodeEdgeInfo* e = findEdgeWithPoint(nextNeighbor, currentNode); if (e != NULL) { tiles.push_back(e->tileNumber); currentNode = nextNeighbor; nextNeighbor = e->getOtherNode(nextNeighbor); if (nextNeighbor < 0) { //std::cerr << "INFO: Unable to find neighbor of" // << nextNeighbor << std::endl; } } else { nextNeighbor = -1; //std::cerr << "INFO: Unable to find edge for for node " // << nodeNumber << std::endl; break; } } if ((nextNeighbor != firstNode) && (nextNeighbor >= 0)) { neighbors.push_back(nextNeighbor); } if (flag) { std::cerr << "Node " << nodeNumber << " neighbors: "; for (unsigned int n = 0; n < neighbors.size(); n++) { std::cerr << " " << neighbors[n]; } std::cerr << std::endl; std::cerr << "Node " << nodeNumber << " tiles: "; for (unsigned int n = 0; n < tiles.size(); n++) { std::cerr << " " << tiles[n]; } std::cerr << std::endl; } } edges.clear(); } /** * See if a node has any neighbors */ bool TopologyHelperOld::getNodeHasNeighbors(const int nodeNum) const { CaretAssertVectorIndex(nodes, nodeNum); return (nodes[nodeNum].neighbors.empty() == false); } /** * Get the number of neighbors for a node. */ int TopologyHelperOld::getNodeNumberOfNeighbors(const int nodeNum) const { CaretAssertVectorIndex(nodes, nodeNum); return nodes[nodeNum].neighbors.size(); } /** * Get the number of boundary edges used by node. */ void TopologyHelperOld::getNumberOfBoundaryEdgesForAllNodes(std::vector& numBoundaryEdgesPerNode) const { // // should already be allocated // const int numNodes = getNumberOfNodes(); if (static_cast(numBoundaryEdgesPerNode.size()) < numNodes) { numBoundaryEdgesPerNode.resize(numNodes); } std::fill(numBoundaryEdgesPerNode.begin(), numBoundaryEdgesPerNode.end(), 0); // // Loop through all edges // for (std::set::const_iterator iter = topologyEdges.begin(); iter != topologyEdges.end(); iter++) { // // Get the tiles // int tile1, tile2; iter->getTiles(tile1, tile2); // // If edges has only one tile // if ((tile1 >= 0) && (tile2 < 0)) { // // Increment the count for the node // int node1, node2; iter->getNodes(node1, node2); numBoundaryEdgesPerNode[node1]++; numBoundaryEdgesPerNode[node2]++; } } } /** * Get the maximum number of neighbors of all nodes. */ int TopologyHelperOld::getMaximumNumberOfNeighbors() const { unsigned int maxNeighbors = 0; int numNodes = getNumberOfNodes(); for (int i = 0; i < numNodes; i++) { const unsigned int num = nodes[i].neighbors.size(); if (num > maxNeighbors) { maxNeighbors = num; } } return maxNeighbors; } /** * Get the neighboring nodes for a node */ void TopologyHelperOld::getNodeNeighbors(const int nodeNum, std::vector& neighborsOut) const { CaretAssertVectorIndex(nodes, nodeNum); neighborsOut = nodes[nodeNum].neighbors; } /** * Get the neighboring nodes for a node, within a given ROI */ void TopologyHelperOld::getNodeNeighborsInROI(const int nodeNum, std::vector& neighborsOut, const float *roiValues) const { if ((nodeNum >= 0) && (nodeNum < static_cast(nodes.size()))) { //Since we are restricting to an roi, we need to make sure that the neighbors are within the ROI, and //if not, build a new neighbor list std::vector neighbors = nodes[nodeNum].neighbors; //optimization, first, loop through and see any neighbors are outside the roi bool neighborOutsideOfRoi = false; for(int i = 0;i<(int)neighbors.size();i++) { if(roiValues[neighbors[i]] == 0.0) { neighborOutsideOfRoi = true; break; } } if(!neighborOutsideOfRoi) { neighborsOut = nodes[nodeNum].neighbors; } else { neighborsOut.clear(); for(int i = 0;i<(int)neighbors.size();i++) { if(roiValues[neighbors[i]] == 0.0)//filter out neighbors that are outside of ROI { continue; } else { neighborsOut.push_back(neighbors[i]); } } } } else { neighborsOut.clear(); } } /** * Get the neighbors of a node to a specified depth. */ void TopologyHelperOld::getNodeNeighborsToDepthIter(const int rootNode, const int depth, std::vector& neighborsOut) const { if (depth < 2)//could make this function private, and remove this test {//gracefully handle nonpositives, and do 1 the easy way getNodeNeighbors(rootNode, neighborsOut); return; } CaretMutexLocker locked(&usingMarkNodes);//lock after choosing which version to use, because they are both public int i, j, k; neighborsOut.clear(); int numNodes = getNumberOfNodes(); int expected = (7 * (depth + 1) * depth) >> 1;//uses bitshift for cheap divide, equals 7 * depth * (depth + 1) / 2 if (expected > numNodes) expected = numNodes; neighborsOut.reserve(expected);//reserve means make space, but don't change size() if ((int)markNodes.size() != numNodes) { markNodes.resize(numNodes); for (i = 0; i < numNodes; ++i) { markNodes[i] = 0; } } if ((int)nodelist[0].size() != numNodes) {//WAY overkill in basically all cases, but no way to know max needed for sure, and branching or clear() might be slower nodelist[0].resize(numNodes); nodelist[1].resize(numNodes); } int newind = 1, oldind = 0, whichneigh; int oldused, newused = 1, numNeigh; std::vector* oldlist, *newlist; markNodes[rootNode] = 1; nodelist[0][0] = rootNode; for (i = 0; i < depth; ++i) { oldused = newused; newused = 0; oldlist = nodelist + oldind;//pointer math should be fast newlist = nodelist + newind; for (j = 0; j < oldused; ++j) { const std::vector& neighbors = nodes[(*oldlist)[j]].neighbors; numNeigh = neighbors.size(); for (k = 0; k < numNeigh; ++k) { whichneigh = neighbors[k]; if (!markNodes[whichneigh]) { markNodes[whichneigh] = 1; neighborsOut.push_back(whichneigh); (*newlist)[newused++] = whichneigh; } } } oldind = newind;//swap the lists newind = 1 ^ oldind;//bitwise xor might be faster than minus }//done, now clean up markNodes numNeigh = neighborsOut.size(); for (i = 0; i < numNeigh; ++i) markNodes[neighborsOut[i]] = 0; markNodes[rootNode] = 0;//only zero the nodes that were marked: after completion of call, array will be entirely zeroed } /** * Get the neighbors of a node to a specified depth. */ void TopologyHelperOld::getNodeNeighborsToDepth(const int rootNode, const int depth, std::vector& neighborsOut) const { if (depth < 2) {//gracefully handle nonpositives, and do 1 the easy way getNodeNeighbors(rootNode, neighborsOut); return; } if (depth > 4) {//iterative version is faster for large depths, cutoff found on 64bit machine, but running 32bit executable getNodeNeighborsToDepthIter(rootNode, depth, neighborsOut); return; } CaretMutexLocker locked(&usingMarkNodes);//lock after choosing which version to use, because they are both public int i;//otherwise, a quick recursive method neighborsOut.clear(); int numNodes = getNumberOfNodes(); int expected = (7 * (depth + 1) * depth) >> 1;//uses bitshift for cheap divide, equals 7 * depth * (depth + 1) / 2 if (expected > numNodes) expected = numNodes; neighborsOut.reserve(expected);//reserve means make space, but don't change size() if ((int)markNodes.size() != numNodes) { markNodes.resize(numNodes); for (i = 0; i < numNodes; ++i) { markNodes[i] = 0; } } markNodes[rootNode] = depth + 1; depthNeighHelper(rootNode, depth, neighborsOut);//uses depth first graph search: fast, but very unordered, if sorted is desired, sort afterwards int numNeigh = neighborsOut.size();//NOTE: depth first technically "wastes" some effort by searching nodes to a lesser depth then required for (i = 0; i < numNeigh; ++i) markNodes[neighborsOut[i]] = 0; markNodes[rootNode] = 0;//only zero the nodes that were marked: after completion of call, array will be entirely zeroed } void TopologyHelperOld::depthNeighHelper(int node, int depthrem, std::vector& neighborsOut) const { const std::vector& neighbors = nodes[node].neighbors; int i, nextdepth = depthrem - 1, numNeigh = neighbors.size(), thisNeigh, mark; if (nextdepth) {//splitting here removes a large number of recursive calls and compares for (i = 0; i < numNeigh; ++i) { thisNeigh = neighbors[i]; mark = markNodes[thisNeigh]; if (depthrem > mark) { if (!mark) { neighborsOut.push_back(thisNeigh); } markNodes[thisNeigh] = depthrem; depthNeighHelper(thisNeigh, nextdepth, neighborsOut); } } } else {//same, but don't recurse for (i = 0; i < numNeigh; ++i) { thisNeigh = neighbors[i]; if (!markNodes[thisNeigh]) { markNodes[thisNeigh] = 1; neighborsOut.push_back(thisNeigh); } } } } /** * Get the neighbors of a node to a specified depth. */ void TopologyHelperOld::getNodeNeighborsToDepthOld(const int rootNode, const int depth, std::vector& neighborsOut) const { neighborsOut.clear(); const int numNodes = getNumberOfNodes(); std::vector nodeVisited(numNodes, 0); std::set nodesMarked; nodesMarked.insert(rootNode); for (int dp = 0; dp < depth; dp++) { std::set newNodes; for (std::set::iterator it = nodesMarked.begin(); it != nodesMarked.end(); it++) { const int node = *it; if (nodeVisited[node] == 0) { nodeVisited[node] = 1; // // Get all of the nodes neighbors // const std::vector& neighbors = nodes[node].neighbors; const unsigned int numNeighs = neighbors.size(); for (unsigned int j = 0; j < numNeighs; j++) { const int n = neighbors[j]; if (nodeVisited[n] == 0) { newNodes.insert(n); } } } } nodesMarked.insert(newNodes.begin(), newNodes.end()); } for (std::set::iterator it = nodesMarked.begin(); it != nodesMarked.end(); it++) { const int node = *it; if (node != rootNode) { neighborsOut.push_back(node); } } /* const int numNodes = getNumberOfNodes(); // // Neighbors found to depth // std::vector neighborsFound(numNodes, 0); // // Mark root node as found // neighborsFound[rootNode] = 1; // // Find nodes to the specified depth // for (int dp = 0; dp <= depth; dp++) { for (int i = 0; i < numNodes; i++) { // // If this node is not marked yet // if (neighborsFound[i] == 0) { // // Get all of the nodes neighbors // int numNeighs = 0; const int* nodeNeighbors = getNodeNeighbors(i, numNeighs); // // Add nodes neighbors for next iteration // for (int k = 0; k < numNeighs; k++) { // // If a neighbor is marked // const int n = nodeNeighbors[k]; if (neighborsFound[n]) { // // Mark me // neighborsFound[i] = 1; break; } } } } } for (int i = 0; i < numNodes; i++) { if (i != rootNode) { if (neighborsFound[i]) { neighborsOut.push_back(i); } } } */ /* // // Neighbors found to depth // std::set neighborsFound; // // Keep track of nodes visited so far // std::vector visited(numNodes, false); // // List of nodes to search at each depth iteration // std::vector nodesToSearch; nodesToSearch.push_back(rootNode); // // Find nodes to the specified depth // for (int dp = 0; dp <= depth; dp++) { // // Nodes for next depth iteration (use set so list is unique) // std::set nodesForNextIteration; // // For nodes that are to be searched // const int numToSearch = static_cast(nodesToSearch.size()); for (int i = 0; i < numToSearch; i++) { // // Node that is to be searched // const int node = nodesToSearch[i]; // // If this node has not already been visited // if (visited[node] == false) { // // Mark this node visited // visited[node] = true; // // Add it to the list of neighbors // neighborsFound.insert(node); // // Get all of the nodes neighbors // int numNeighs = 0; const int* nodeNeighbors = getNodeNeighbors(node, numNeighs); // // Add nodes neighbors for next iteration // for (int k = 0; k < numNeighs; k++) { const int n = nodeNeighbors[k]; if (visited[n] == false) { nodesForNextIteration.insert(n); } } } } // // Update list of neighbors to search for next iteration // nodesToSearch.clear(); nodesToSearch.insert(nodesToSearch.end(), nodesForNextIteration.begin(), nodesForNextIteration.end()); } // // Set the output neighbors exluding the input node // for (std::set::iterator iter = neighborsFound.begin(); iter != neighborsFound.end(); iter++) { const int n = *iter; if (n != rootNode) { neighborsOut.push_back(n); } } */ } const std::vector& TopologyHelperOld::getNodeNeighbors(const int nodeNum) const { CaretAssertVectorIndex(nodes, nodeNum); return nodes[nodeNum].neighbors; } /** * Get the neighboring nodes for a node. Returns a pointer to an array * containing the neighbors. */ const int* TopologyHelperOld::getNodeNeighbors(const int nodeNum, int& numNeighborsOut) const { CaretAssertVectorIndex(nodes, nodeNum); numNeighborsOut = (int)nodes[nodeNum].neighbors.size(); return nodes[nodeNum].neighbors.data(); } /** * Get the tiles that use this node. */ void TopologyHelperOld::getNodeTiles(const int nodeNum, std::vector& tilesOut) const { CaretAssertVectorIndex(nodes, nodeNum); tilesOut = nodes[nodeNum].tiles; } const std::vector& TopologyHelperOld::getNodeTiles(const int nodeNum) const { CaretAssertVectorIndex(nodes, nodeNum); return nodes[nodeNum].tiles; } const int* TopologyHelperOld::getNodeTiles(const int nodeNum, int& numTilesOut) { CaretAssertVectorIndex(nodes, nodeNum); numTilesOut = (int)nodes[nodeNum].tiles.size(); return nodes[nodeNum].tiles.data(); } workbench-1.1.1/src/Tests/TopologyHelperOld.h000066400000000000000000000265311255417355300211720ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #ifndef __VE_TOPOLOGY_HELPER_H__ #define __VE_TOPOLOGY_HELPER_H__ #include #include #include #include #include "CaretMutex.h" namespace caret { class SurfaceFile; /// Stores the two nodes and two tiles for an edge (link) in the surface. class TopologyEdgeInfoOld { public: /// nodes in the edge int node1, node2; /// tiles used by the edge int tile1, tile2; /// flat to indicate if edge is used by more than two triangles bool edgeHasMoreThanTwoTriangles; /// constructor TopologyEdgeInfoOld(const int tileIn, const int node1In, const int node2In) { edgeHasMoreThanTwoTriangles = false; tile1 = tileIn; tile2 = -1; node1 = node1In; node2 = node2In; if (node2 > node1) { std::swap(node1, node2); } } /// equals operator bool operator==(const TopologyEdgeInfoOld& e) { return ((node1 == e.node1) && (node2 == e.node2)); } /// add a tile to the edge void addTile(const int tileIn) { if (tile2 < 0) { tile2 = tileIn; } else { edgeHasMoreThanTwoTriangles = true; /*if (DebugControl::getDebugOn()) { std::cout << "INFO: Edge (" << node1 << ", " << node2 << ") is used by more than two tiles" << std::endl; std::cout << " Triangles: " << tile1 << " " << tile2 << " " << tileIn << std::endl; }//*/ } } /// get the more than two triangles flag bool getEdgeUsedByMoreThanTwoTriangles() const { return edgeHasMoreThanTwoTriangles; } /// get the nodes void getNodes(int& n1, int& n2) const { n1 = node1; n2 = node2; } /// get the tiles void getTiles(int& t1, int& t2) const { t1 = tile1; t2 = tile2; } /// return positive if the edge is oriented the same as the triangle in int getEdgeOrientation(const int nodes[3]) const { for (int i = 0; i < 3; i++) { if (nodes[i] == node1) { int iNext = i + 1; if (iNext >= 3) iNext = 0; if (nodes[iNext] == node2) { return 1; } else { return -1; } } } return 0; } }; /// This class is used to determine the node neighbors and edges for a Topology File. class TopologyHelperOld { public: /// Constructor for use with a Caret Topology File TopologyHelperOld(const SurfaceFile* sfIn, const bool sortNodeInfo = false); /// Destructor ~TopologyHelperOld(); /// Get the number of nodes int getNumberOfNodes() const { return nodes.size(); } /// See if a node has neighbors bool getNodeHasNeighbors(const int nodeNum) const; /// Get the number of neighbors for a node int getNodeNumberOfNeighbors(const int nodeNum) const; /// Get the neighbors of a node void getNodeNeighbors(const int nodeNum, std::vector& neighborsOut) const; /// Get the neighbors of a node const std::vector& getNodeNeighbors(const int nodeNum) const; /// Get the neighboring nodes for a node. Returns a pointer to an array /// containing the neighbors. const int* getNodeNeighbors(const int nodeNum, int& numNeighborsOut) const; /// Get the node neighbors, restricted by roi void getNodeNeighborsInROI(const int nodeNum, std::vector& neighborsOut, const float *roiValues) const; /// Get the neighbors of a node to a specified depth the old way void getNodeNeighborsToDepthOld(const int nodeNum, const int depth, std::vector& neighborsOut) const; /// Get the neighbors to a specified depth as fast as possible, order unimportant (this is the new code) void getNodeNeighborsToDepth(const int nodeNum, const int depth, std::vector& neighborsOut) const; /// Get the neighbors to a specified depth preserving the kind of ordering of the old code void getNodeNeighborsToDepthIter(const int nodeNum, const int depth, std::vector& neighborsOut) const; private: void depthNeighHelper(int root, int remdepth, std::vector& neighborsOut) const; mutable std::vector markNodes, nodelist[2];//persistent, never cleared, only initialized once, saving bazillions of nanoseconds mutable CaretMutex usingMarkNodes; //could be templated over bool, which stores bitwise, but we already have three times that many floats and six times the ints in memory public: /// Get the number of boundary edges used by node void getNumberOfBoundaryEdgesForAllNodes(std::vector& numBoundaryEdges) const; /// Get the maximum number of neighbors of all nodes int getMaximumNumberOfNeighbors() const; /// Get the tiles used by a node void getNodeTiles(const int nodeNum, std::vector& tilesOut) const; /// Get the tiles used by a node const std::vector& getNodeTiles(const int nodeNum) const; /// Get the tiles for a node. Returns a pointer to an array /// containing the tiles. const int* getNodeTiles(const int nodeNum, int& numTilesOut); /// get node sorted info validity bool getNodeSortedInfoValid() const { return nodeSortedInfoBuilt; } /// Get the edge information const std::set& getEdgeInfo() const { return topologyEdges; } /// Get the number of edges int getNumberOfEdges() const { return topologyEdges.size(); } private: /// Stores tiles and vertices of an edge (link) for use when sorting nodes by NodeInfo. /// These edges are those opposite of a NodeInfo node, this is, given a tile with /// nodes A, B, and C, the nodes B and C would be stored in an NodeEdgeInfo for node A. class NodeEdgeInfo { public: /// nodes in the edge int node1, node2; /// tile used by this edge int tileNumber; /// constructor NodeEdgeInfo(const int tileNum, const int node1In, const int node2In) { tileNumber = tileNum; node1 = node1In; node2 = node2In; } /// get the other node in the edge int getOtherNode(const int node) const { if (node1 == node) return node2; else return node1; } /// see if a node is in the edge bool containsNode(const int node) const { return ((node1 == node) || (node2 == node)); } }; /// Stores information about a node for sorting class NodeInfo { public: /// this node's number int nodeNumber; /// tile's used by this node std::vector tiles; /// the neighboring nodes of this edge std::vector neighbors; /// the edges used to form tiles with this node std::vector edges; /// allow sorting flag bool sortMe; /// constructor NodeInfo(const int nodeNumberIn) { nodeNumber = nodeNumberIn; sortMe = true; } /// add a neighbor to this node without edge information void addNeighbor(const int newNeighbor1) { for (unsigned int i = 0; i < neighbors.size(); i++) { if (neighbors[i] == newNeighbor1) { return; } } neighbors.push_back(newNeighbor1); sortMe = false; } /// add two neighbors that form a tile using this node void addNeighbors(const int tileNum, const int newNeighbor1, const int newNeighbor2) { edges.push_back(NodeEdgeInfo(tileNum, newNeighbor1, newNeighbor2)); } /// add a tile to the node void addTile(const int tileNum) { tiles.push_back(tileNum); } /// find an edge that has one endpoint equal to "seekingNode" but /// does not contain the node "notNode". const NodeEdgeInfo* findEdgeWithPoint(const int seekingNode, const int notNode) { for (unsigned int i = 0; i < edges.size(); i++) { if ( ((edges[i].node1 == seekingNode) && (edges[i].node2 != notNode)) || ((edges[i].node2 == seekingNode) && (edges[i].node1 != notNode)) ) { return &edges[i]; } } return NULL; } /// sort the neighbors for this node void sortNeighbors(); }; /// Node storage std::vector nodes; /// edge storage std::set topologyEdges; /// add information about an edge void addEdgeInfo(const int tileNum, const int node1, const int node2); /// node sorted info built bool nodeSortedInfoBuilt; }; bool operator<(const TopologyEdgeInfoOld& e1, const TopologyEdgeInfoOld& e2); } #endif // __VE_TOPOLOGY_HELPER_H__ workbench-1.1.1/src/Tests/TopologyHelperTest.cxx000066400000000000000000000103631255417355300217420ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "TopologyHelperTest.h" #include "SurfaceFile.h" #include "TopologyHelper.h" #include "TopologyHelperOld.h" #include using namespace caret; using namespace std; TopologyHelperTest::TopologyHelperTest(const AString& identifier): TestInterface(identifier) { } void TopologyHelperTest::execute() { SurfaceFile mySurf; mySurf.readFile(m_default_path + "/gifti/Human.PALS_B12.LEFT_AVG_B1-12.FIDUCIAL_FLIRT.clean.73730.surf.gii"); CaretPointer myNewTopoHelp = mySurf.getTopologyHelper(); CaretPointer myOldTopoHelp(new TopologyHelperOld(&mySurf)); int numNodes = mySurf.getNumberOfNodes(); CaretArray myMarkedNew(numNodes, -1), myMarkedOld(numNodes, -1); const int TEST_SAMPLES = 5000, TEST_DEPTH = 5; for (int i = 0; i < TEST_SAMPLES; ++i) { int selectedNode = rand() % numNodes; vector newNeigh = myNewTopoHelp->getNodeNeighbors(selectedNode);//copy out rather than using const reference so we can reuse them for depth vector oldNeigh = myOldTopoHelp->getNodeNeighbors(selectedNode); int newSize = (int)newNeigh.size(), oldSize = (int)oldNeigh.size(); if (newSize != oldSize) { setFailed("size difference at node " + AString::number(selectedNode) + ", new: " + AString::number(newSize) + ", old: " + AString::number(oldSize)); } for (int i = 0; i < newSize; ++i) { myMarkedNew[newNeigh[i]] = selectedNode; } for (int i = 0; i < oldSize; ++i) { myMarkedOld[oldNeigh[i]] = selectedNode; if (myMarkedNew[oldNeigh[i]] != selectedNode) { setFailed("new topology helper missed neighbor " + AString::number(oldNeigh[i]) + " of node " + AString::number(selectedNode)); } } for (int i = 0; i < newSize; ++i) { if (myMarkedOld[newNeigh[i]] != selectedNode) { setFailed("new topology helper added neighbor " + AString::number(newNeigh[i]) + " to node " + AString::number(selectedNode)); } } myNewTopoHelp->getNodeNeighborsToDepth(selectedNode, TEST_DEPTH, newNeigh); myOldTopoHelp->getNodeNeighborsToDepth(selectedNode, TEST_DEPTH, oldNeigh); newSize = (int)newNeigh.size(); oldSize = (int)oldNeigh.size(); if (newSize != oldSize) { setFailed("depth " + AString::number(TEST_DEPTH) + " size difference at node " + AString::number(selectedNode) + ", new: " + AString::number(newSize) + ", old: " + AString::number(oldSize)); } for (int i = 0; i < newSize; ++i) { myMarkedNew[newNeigh[i]] = selectedNode; } for (int i = 0; i < oldSize; ++i) { myMarkedOld[oldNeigh[i]] = selectedNode; if (myMarkedNew[oldNeigh[i]] != selectedNode) { setFailed("new topology helper depth " + AString::number(TEST_DEPTH) + " missed neighbor " + AString::number(oldNeigh[i]) + " of node " + AString::number(selectedNode)); } } for (int i = 0; i < newSize; ++i) { if (myMarkedOld[newNeigh[i]] != selectedNode) { setFailed("new topology helper depth " + AString::number(TEST_DEPTH) + " added neighbor " + AString::number(newNeigh[i]) + " to node " + AString::number(selectedNode)); } } } } workbench-1.1.1/src/Tests/TopologyHelperTest.h000066400000000000000000000022061255417355300213640ustar00rootroot00000000000000#ifndef __TOPOLOGY_HELPER_TEST_H__ #define __TOPOLOGY_HELPER_TEST_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "TestInterface.h" namespace caret { class TopologyHelperTest : public TestInterface { public: TopologyHelperTest(const AString& identifier); virtual void execute(); }; } #endif //__TOPOLOGY_HELPER_TEST_H__ workbench-1.1.1/src/Tests/VolumeFileTest.cxx000066400000000000000000000113121255417355300210300ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "VolumeFileTest.h" #include "FloatMatrix.h" #include "VolumeFile.h" #include using namespace caret; using namespace std; VolumeFileTest::VolumeFileTest(const AString& identifier) : TestInterface(identifier) { } void VolumeFileTest::execute() { VolumeFile myTestVol; vector myDims; const int64_t xdim = 19, ydim = 17, zdim = 13, tdim = 11, numComponents = 3;//simulate rgb float testvals[xdim][ydim][zdim][tdim][numComponents]; myDims.push_back(xdim);//i myDims.push_back(ydim);//j myDims.push_back(zdim);//k myDims.push_back(tdim);//time FloatMatrix indexSpace = FloatMatrix::zeros(4,4);//start with zeros as a 4x4 style matrix indexSpace[3][3] = 1.1f;//test a wrong element, because it SHOULD ignore it indexSpace[0][2] = 1.0f;//give it an odd orientation indexSpace[1][0] = 1.0f;//this is yzx, ie, PIL indexSpace[2][1] = 1.0f; myTestVol.reinitialize(myDims, indexSpace.getMatrix(), numComponents); float outIndex1, outIndex2, outIndex3, tempf; const float testAllowance = 0.00001f;//allow it to be wrong by this much myTestVol.spaceToIndex(1.0f, 0.0f, 0.0f, outIndex1, outIndex2, outIndex3); if (outIndex1 < 0.0f - testAllowance || outIndex1 > 0.0f + testAllowance) { setFailed(AString("index 1 failed: needed 0.0, got ") + AString::number(outIndex1)); } if (outIndex2 < 0.0f - testAllowance || outIndex2 > 0.0f + testAllowance) { setFailed(AString("index 2 failed: needed 0.0, got ") + AString::number(outIndex2)); } if (outIndex3 < 1.0f - testAllowance || outIndex3 > 1.0f + testAllowance) { setFailed(AString("index 3 failed: needed 1.0, got ") + AString::number(outIndex3)); } int64_t i, j, k, t, c, testIndex; for (c = 0; c < numComponents; ++c) { for (t = 0; t < myDims[3]; ++t) { for (k = 0; k < myDims[2]; ++k) { for (j = 0; j < myDims[1]; ++j) { for (i = 0; i < myDims[0]; ++i) { testIndex = myTestVol.getIndex(i, j, k, t, c); if (testIndex != i + myDims[0] * (j + myDims[1] * (k + myDims[2] * (t + myDims[3] * c)))) { setFailed(AString("index of (") + AString::number(i) + ", " + AString::number(j) + ", " + AString::number(k) + ", " + AString::number(t) + ", " + AString::number(c) + ") should be " + AString::number(i + myDims[0] * (j + myDims[1] * (k + myDims[2] * (t + myDims[3] * c)))) + " but was " + AString::number(testIndex)); return; } tempf = (float)rand();//generate some floats testvals[i][j][k][t][c] = tempf;//store them explicitly myTestVol.setValue(tempf, i, j, k, t, c); } } } } } for (c = 0; c < numComponents; ++c) { for (t = 0; t < myDims[3]; ++t) { for (k = 0; k < myDims[2]; ++k) { for (j = 0; j < myDims[1]; ++j) { for (i = 0; i < myDims[0]; ++i) { if (myTestVol.getValue(i, j, k, t, c) != testvals[i][j][k][t][c]) { setFailed(AString("data was not consistent starting at (") + AString::number(i) + ", " + AString::number(j) + ", " + AString::number(k) + ", " + AString::number(t) + ", " + AString::number(c) + "), value should be " + AString::number(testvals[i][j][k][t][c]) + ", got " + AString::number(myTestVol.getValue(i, j, k, t, c))); return; } } } } } } } workbench-1.1.1/src/Tests/VolumeFileTest.h000066400000000000000000000021621255417355300204600ustar00rootroot00000000000000#ifndef __VOLUME_FILE_TEST_H__ #define __VOLUME_FILE_TEST_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "TestInterface.h" namespace caret { class VolumeFileTest : public TestInterface { public: VolumeFileTest(const AString& identifier); virtual void execute(); }; } #endif //__VOLUME_FILE_TEST_H__ workbench-1.1.1/src/Tests/XnatTest.cxx000066400000000000000000000036411255417355300177010ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "XnatTest.h" #include "CiftiFile.h" using namespace caret; using namespace std; XnatTest::XnatTest(const AString& identifier) : TestInterface(identifier) { } void XnatTest::execute() { setFailed("no authentication info present"); return;//comment this out to run the test AString myUrl("");//test url goes here AString user("");//test auth goes here AString pass(""); CiftiFile myXnat; myXnat.openURL(myUrl, user, pass); vector myData; myData.resize(myXnat.getNumberOfColumns()); myXnat.getRow(myData.data(), 0); if (myXnat.getCiftiXML().getMappingType(CiftiXML::ALONG_COLUMN) != CiftiMappingType::BRAIN_MODELS) { setFailed("opened file does not have brain models along column"); } const CiftiBrainModelsMap& myDenseMap = myXnat.getCiftiXML().getBrainModelsMap(CiftiXML::ALONG_COLUMN); int64_t myRow = myDenseMap.getIndexForNode(547, StructureEnum::CORTEX_RIGHT); if (myRow == -1) { setFailed("did not find specified node in specified structure"); } myXnat.getRow(myData.data(), myRow); } workbench-1.1.1/src/Tests/XnatTest.h000066400000000000000000000021231255417355300173200ustar00rootroot00000000000000#ifndef __XNAT_TEST_H__ #define __XNAT_TEST_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "TestInterface.h" namespace caret { class XnatTest : public TestInterface { public: XnatTest(const AString& identifier); virtual void execute(); }; } #endif // __XNAT_TEST_H__ workbench-1.1.1/src/Tests/test_driver.cxx000066400000000000000000000107721255417355300204640ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ //program for running tests #include #include #include #include #include #include "TestInterface.h" #include "SessionManager.h" #include "CaretHttpManager.h" #include "CaretCommandLine.h" #include "CaretException.h" //tests #include "CiftiFileTest.h" #include "GeodesicHelperTest.h" #include "HttpTest.h" #include "HeapTest.h" #include "LookupTest.h" #include "MathExpressionTest.h" #include "NiftiTest.h" #include "PointerTest.h" #include "ProgressTest.h" #include "QuatTest.h" #include "StatisticsTest.h" #include "TimerTest.h" #include "TopologyHelperTest.h" #include "VolumeFileTest.h" #include "XnatTest.h" using namespace std; using namespace caret; void freeTestList(vector& mylist) { for (int i = 0; i < (int)mylist.size(); ++i) { delete mylist[i]; } } int main(int argc, char** argv) { srand(time(NULL)); { QCoreApplication myApp(argc, argv); caret_global_commandLine_init(argc, argv); SessionManager::createSessionManager(ApplicationTypeEnum::APPLICATION_TYPE_COMMAND_LINE); vector mytests; mytests.push_back(new CiftiFileTest("ciftifile")); mytests.push_back(new GeodesicHelperTest("geohelp")); mytests.push_back(new HeapTest("heap")); mytests.push_back(new HttpTest("http")); mytests.push_back(new LookupTest("lookup")); mytests.push_back(new MathExpressionTest("mathexpression")); mytests.push_back(new NiftiFileTest("niftifile")); mytests.push_back(new NiftiHeaderTest("niftiheader")); mytests.push_back(new PointerTest("pointer")); mytests.push_back(new ProgressTest("progress")); mytests.push_back(new QuatTest("quaternion")); mytests.push_back(new StatisticsTest("statistics")); mytests.push_back(new TimerTest("timer")); mytests.push_back(new TopologyHelperTest("topohelp")); mytests.push_back(new VolumeFileTest("volumefile")); mytests.push_back(new XnatTest("xnat")); if (argc < 2) { cout << "No test specified, please specify one of the following:" << endl; for (int i = 0; i < (int)mytests.size(); ++i) { cout << mytests[i]->getIdentifier() << endl; } freeTestList(mytests); return 1;//no test specified, fail } int failCount = 0; for (int i = 1; i < argc; ++i) { for (int j = 0; j < (int)mytests.size(); ++j) { if (mytests[j]->getIdentifier() == AString(argv[i]) || "all" == AString(argv[i])) { try { mytests[j]->execute(); } catch (CaretException& e) { ++failCount; cout << "Test " << mytests[j]->getIdentifier() << " failed, exception: " << e.whatString() << endl; continue;//skip trying failed() and getFailMessage() } if (mytests[j]->failed()) { ++failCount; cout << "Test " << mytests[j]->getIdentifier() << " failed: " << mytests[j]->getFailMessage() << endl; } } } } freeTestList(mytests); if (failCount != 0) { cout << "Total of " << failCount << " tests failed!" << endl; return 1; } SessionManager::deleteSessionManager(); CaretHttpManager::deleteHttpManager(); myApp.processEvents(); } CaretObject::printListOfObjectsNotDeleted(true); return 0; } workbench-1.1.1/src/Xml/000077500000000000000000000000001255417355300150355ustar00rootroot00000000000000workbench-1.1.1/src/Xml/CMakeLists.txt000066400000000000000000000011511255417355300175730ustar00rootroot00000000000000# # Name of Project # PROJECT (Xml) # # Use XML from Qt but not GUI. # SET(QT_USE_QTXML TRUE) SET(QT_DONT_USE_QTGUI TRUE) SET(QT_USE_QTNETWORK TRUE) # # QT include files # INCLUDE(${QT_USE_FILE}) # # Create a Library # ADD_LIBRARY(Xml XmlWriter.h XmlException.h XmlAttributes.h XmlSaxParser.h XmlSaxParserException.h XmlSaxParserHandlerInterface.h XmlSaxParserWithQt.h XmlUtilities.h XmlWriter.cxx XmlException.cxx XmlAttributes.cxx XmlSaxParser.cxx XmlSaxParserException.cxx XmlSaxParserWithQt.cxx XmlUtilities.cxx ) # # Find headers # INCLUDE_DIRECTORIES( ${CMAKE_SOURCE_DIR}/Xml ${CMAKE_SOURCE_DIR}/Common ) workbench-1.1.1/src/Xml/XmlAttributes.cxx000066400000000000000000000211001255417355300203620ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "XmlAttributes.h" #include "XmlSaxParserException.h" #include "CaretAssert.h" using namespace caret; /** * Constructor. */ XmlAttributes::XmlAttributes() { this->clear(); } /** * Destructor. */ XmlAttributes::~XmlAttributes() { this->clear(); } /** * Clear the attributes. */ void XmlAttributes::clear() { this->names.clear(); this->values.clear(); } /** * Add an attribute. * * @param name - Name of attribute. * @param value - Value of attribute. */ void XmlAttributes::addAttribute(const AString& name, const AString& value) { AString s = name; names.push_back(AString(name)); values.push_back(value); } /** * Add an attribute. Without this method, "char*" values are * interpreted as booleans. * * @param name - Name of attribute. * @param value - Value of attribute. */ void XmlAttributes::addAttribute(const AString& name, const char* value) { names.push_back(AString(name)); values.push_back(value); } /** * Add an attribute. * * @param name - Name of attribute. * @param value - Value of attribute. */ void XmlAttributes::addAttribute(const AString& name, const int32_t value) { names.push_back(name); values.push_back(AString::number(value)); } /** * Add an attribute. * @param name * Name of attribute. * @param value * Vector of numbers. * @param separator * Characters placed between each pair of numbers in text./ */ void XmlAttributes::addAttribute(const AString& name, const std::vector& value, const AString& separator) { AString s; for (uint32_t i = 0; i < value.size(); i++) { if (i > 0) { s += separator; } s += AString::number(value[i]); } names.push_back(name); values.push_back(s); } /** * Add an attribute. * @param name * Name of attribute. * @param value * Vector of numbers. * @param separator * Characters placed between each pair of numbers in text./ */ void XmlAttributes::addAttribute(const AString& name, const std::vector& value, const AString& separator) { AString s; for (uint64_t i = 0; i < value.size(); i++) { if (i > 0) { s += separator; } s += AString::number(value[i]); } names.push_back(name); values.push_back(s); } /** * Add an attribute. * * @param name - Name of attribute. * @param value - Value of attribute. */ void XmlAttributes::addAttribute(const AString& name, const int64_t value) { names.push_back(name); values.push_back(AString::number(value)); } /** * Add an attribute. * * @param name - Name of attribute. * @param value - Value of attribute. */ void XmlAttributes::addAttribute(const AString& name, const float value) { names.push_back(name); values.push_back(AString::number(value)); } /** * Add an attribute. * * @param name - Name of attribute. * @param value - Value of attribute. */ void XmlAttributes::addAttribute(const AString& name, const double value) { names.push_back(name); values.push_back(AString::number(value)); } /** * Add an attribute. * * @param name - Name of attribute. * @param value - Value of attribute. */ void XmlAttributes::addAttribute(const AString& name, const bool value) { names.push_back(name); values.push_back(AString::fromBool(value)); } /** * Get the number of attributes. * * @return Number of attributes. */ int XmlAttributes::getNumberOfAttributes() const { return this->names.size(); } /** * Get the name of an attribute. * * @param index - index of attribute. * * @return Name of attribute at index. */ AString XmlAttributes::getName(const int index) const{ CaretAssertVectorIndex(this->names, index); return this->names.at(index); } /** * Get the value of an attribute. * * @param index - index of attribute. * * @return Value of attribute at index. */ AString XmlAttributes::getValue(const int index) const{ CaretAssertVectorIndex(this->values, index); return this->values.at(index); } /** * Get the value of an attribute as an int. * * @param index - index of attribute. * * @return Value of attribute at index. */ int32_t XmlAttributes::getValueAsInt(const int index) const{ CaretAssertVectorIndex(this->values, index); AString value = this->values.at(index); bool ok = false; int32_t ret = value.toInt(&ok); if (!ok) { AString name = this->names[index]; throw XmlSaxParserException(AString("integer required for XML attribute ") + name + ", got '" + value + "' instead"); } return ret; } /** * Get the value of an attribute as a float. * * @param index - index of attribute. * * @return Value of attribute at index. */ float XmlAttributes::getValueAsFloat(const int index) const { CaretAssertVectorIndex(this->values, index); AString value = this->values.at(index); bool ok = false; float ret = value.toFloat(&ok); if (!ok) { AString name = this->names[index]; throw XmlSaxParserException(AString("float required for XML attribute ") + name + ", got '" + value + "' instead"); } return ret; } /** * Get the value of an attribute. * * @param index - index of attribute. * * @return Value of attribute at index. */ AString XmlAttributes::getValue(const AString& name, const AString& defaultValue) const { int index = getIndex(name); if (index == -1) return defaultValue; return this->values[index]; } /** * Get the value of an attribute as an int. * * @param index - index of attribute. * * @return Value of attribute at index. */ int32_t XmlAttributes::getValueAsInt(const AString& name, const int32_t defaultValue) const{ int index = getIndex(name); if (index == -1) return defaultValue; return getValueAsInt(index); } /** * Get the value of an attribute as a float. * * @param name * Name of attribute. * @param defaultValue * Value returned if attribute is not found. * * @return Value of attribute with given namex. */ float XmlAttributes::getValueAsFloat(const AString& name, const float& defaultValue) const { int index = getIndex(name); if (index == -1) return defaultValue; return getValueAsFloat(index); } /** * Get the value of an attribute as a boolean. * * @param name * Name of attribute. * @param defaultValue * Value returned if attribute is not found. * * @return Value of attribute with given namex. */ bool XmlAttributes::getValueAsBoolean(const AString& name, const bool defaultValue) const { int index = getIndex(name); if (index == -1) return defaultValue; CaretAssertVectorIndex(this->values, index); AString value = this->values.at(index); const bool boolValue = value.toBool(); return boolValue; } int XmlAttributes::getIndex(const AString& name) const { int num = (int)this->names.size(); for (int i = 0; i < num; i++) { if (this->names[i] == name) { return i; } } return -1; } float XmlAttributes::getValueAsFloatRequired(const AString& name) const { int index = getIndex(name); if (index == -1) throw XmlSaxParserException(AString("required attribute ") + name + " missing"); return getValueAsFloat(index); } int32_t XmlAttributes::getValueAsIntRequired(const caret::AString& name) const { int index = getIndex(name); if (index == -1) throw XmlSaxParserException(AString("required attribute ") + name + " missing"); return getValueAsInt(index); } AString XmlAttributes::getValueRequired(const caret::AString& name) const { int index = getIndex(name); if (index == -1) throw XmlSaxParserException(AString("required attribute ") + name + " missing"); return getValue(index); } workbench-1.1.1/src/Xml/XmlAttributes.h000066400000000000000000000055731255417355300200270ustar00rootroot00000000000000 #ifndef __XML_ATTRIBUTES_H__ #define __XML_ATTRIBUTES_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "CaretObject.h" #include namespace caret { /** * Maintains attribute for XML Generic Writer. */ class XmlAttributes : public CaretObject{ public: XmlAttributes(); ~XmlAttributes(); void clear(); void addAttribute(const AString& name, const AString& value); void addAttribute(const AString& name, const char* value); void addAttribute(const AString& name, const int32_t value); void addAttribute(const AString& name, const std::vector& value, const AString& separator); void addAttribute(const AString& name, const std::vector& value, const AString& separator); void addAttribute(const AString& name, const int64_t value); void addAttribute(const AString& name, const bool value); void addAttribute(const AString& name, const float value); void addAttribute(const AString& name, const double value); int getNumberOfAttributes() const; AString getName(const int index) const; AString getValue(const int index) const; int32_t getValueAsInt(const int index) const; float getValueAsFloat(const int index) const; AString getValue(const AString& name, const AString& defaultValue = "") const; AString getValueRequired(const AString& name) const; int32_t getValueAsInt(const AString& name, const int32_t defaultValue = 0) const; int32_t getValueAsIntRequired(const AString& name) const; float getValueAsFloat(const AString& name, const float& defaultValue = 0.0f) const; float getValueAsFloatRequired(const AString& name) const; bool getValueAsBoolean(const AString& name, const bool defaultValue = false) const; int getIndex(const AString& name) const; private: /** attribute names. */ std::vector names; /** attribute values. */ std::vector values; }; } // namespace #endif /* __XML_ATTRIBUTES_H__ */ workbench-1.1.1/src/Xml/XmlException.cxx000066400000000000000000000037731255417355300202120ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "XmlException.h" #include using namespace caret; /** * Constructor. * */ XmlException::XmlException() : CaretException() { this->initializeMembers(); } /** * Constructor that uses stack trace from the exception * passed in as a parameter. * * @param e Any exception whose stack trace becomes * this exception's stack trace. * */ XmlException::XmlException( const CaretException& e) : CaretException(e) { this->initializeMembers(); } /** * Constructor. * * @param s Description of the exception. * */ XmlException::XmlException(const AString& s) : CaretException(s) { this->initializeMembers(); } /** * Copy Constructor. * @param e * Exception that is copied. */ XmlException::XmlException(const XmlException& e) : CaretException(e) { } /** * Assignment operator. * @param e * Exception that is copied. * @return * Copy of the exception. */ XmlException& XmlException::operator=(const XmlException& e) { if (this != &e) { CaretException::operator=(e); } return *this; } /** * Destructor */ XmlException::~XmlException() throw() { } void XmlException::initializeMembers() { } workbench-1.1.1/src/Xml/XmlException.h000066400000000000000000000026321255417355300176300ustar00rootroot00000000000000#ifndef __XMLEXCEPTION_H__ #define __XMLEXCEPTION_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "CaretException.h" namespace caret { /** * An exception thrown during XML writing. */ class XmlException : public CaretException { public: XmlException(); XmlException(const CaretException& e); XmlException(const AString& s); XmlException(const XmlException& e); XmlException& operator=(const XmlException& e); virtual ~XmlException() throw(); private: void initializeMembers(); }; } // namespace #endif // __XMLEXCEPTION_H__ workbench-1.1.1/src/Xml/XmlSaxParser.cxx000066400000000000000000000024751255417355300201620ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "XmlSaxParser.h" #include "XmlSaxParserException.h" #include "XmlSaxParserWithQt.h" using namespace caret; /** * Constructor. */ XmlSaxParser::XmlSaxParser() { this->initializeMembersXmlSaxParser(); } /** * Destructor. */ XmlSaxParser::~XmlSaxParser() { } XmlSaxParser* XmlSaxParser::createXmlParser() { XmlSaxParser* parser = new XmlSaxParserWithQt(); return parser; } /** * Initialize members. */ void XmlSaxParser::initializeMembersXmlSaxParser() { } workbench-1.1.1/src/Xml/XmlSaxParser.h000066400000000000000000000052121255417355300175770ustar00rootroot00000000000000#ifndef __XMLSAXPARSER_H__ #define __XMLSAXPARSER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretObject.h" #include "XmlSaxParserException.h" namespace caret { class XmlSaxParserHandlerInterface; /** * Abstract class for XML SAX Parsing. The design of this * class is based of the handler classes in Java. */ class XmlSaxParser : public CaretObject { public: static XmlSaxParser* createXmlParser(); virtual ~XmlSaxParser(); protected: XmlSaxParser(); public: /** * Parse the contents of the specified file using * the specified handler. * * @param filename * Name of file that is to be parsed. * @param handler * Handler that will be called to process XML * as it is read. * @throws XmlSaxParserException * If an error occurs. */ virtual void parseFile(const QString& filename, XmlSaxParserHandlerInterface* handler) = 0; /** * Parse the contents of the strring using * the specified handler. * * @param xmlString * String whose contents is parsed. * @param handler * Handler that will be called to process XML * as it is read. * @throws XmlSaxParserException * If an error occurs. */ virtual void parseString(const QString& xmlString, XmlSaxParserHandlerInterface* handler) = 0; protected: XmlSaxParser(const XmlSaxParser& sp); XmlSaxParser& operator=(const XmlSaxParser&); private: void initializeMembersXmlSaxParser(); }; } // namespace #endif // __XMLSAXPARSER_H__ workbench-1.1.1/src/Xml/XmlSaxParserException.cxx000066400000000000000000000066051255417355300220400ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "XmlSaxParserException.h" #include using namespace caret; /** * Constructor. * */ XmlSaxParserException::XmlSaxParserException() : CaretException() { this->initializeMembersXmlSaxParserException(); } /** * Constructor. * */ XmlSaxParserException::XmlSaxParserException(const CaretException& e) : CaretException(e) { this->initializeMembersXmlSaxParserException(); } /** * Constructor. * * @param s * Description of the exception. * @param lineNumber * Line number of the parsing exception. * @param columnNumber * Column number of the parsing exception. * */ XmlSaxParserException::XmlSaxParserException(const AString& s, const int32_t lineNumber, const int32_t columnNumber) : CaretException(s) { this->initializeMembersXmlSaxParserException(); m_lineNumber = lineNumber; m_columnNumber = columnNumber; } /** * Constructor. * * @param s * Description of the exception. * @param lineNumber * Line number of the parsing exception. * @param columnNumber * Column number of the parsing exception. * */ XmlSaxParserException::XmlSaxParserException(const AString& s) : CaretException(s) { this->initializeMembersXmlSaxParserException(); } /** * Copy Constructor. * @param e * Exception that is copied. */ XmlSaxParserException::XmlSaxParserException(const XmlSaxParserException& e) : CaretException(e) { m_lineNumber = e.m_lineNumber; m_columnNumber = e.m_columnNumber; } /** * Assignment operator. * @param e * Exception that is copied. * @return * Copy of the exception. */ XmlSaxParserException& XmlSaxParserException::operator=(const XmlSaxParserException& e) { if (this != &e) { CaretException::operator=(e); } m_lineNumber = e.m_lineNumber; m_columnNumber = e.m_columnNumber; return *this; } /** * Destructor */ XmlSaxParserException::~XmlSaxParserException() throw() { } void XmlSaxParserException::initializeMembersXmlSaxParserException() { m_lineNumber = -1; m_columnNumber = -1; } /** * Get a message describing the exception. * @return A message describing the exception. */ AString XmlSaxParserException::whatString() const throw() { AString s = CaretException::whatString(); if (m_lineNumber >= 0) { s += (", line number: " + AString::number(m_lineNumber)); } if (m_columnNumber >= 0) { s += (" column number: " + AString::number(m_columnNumber)); } return s; } workbench-1.1.1/src/Xml/XmlSaxParserException.h000066400000000000000000000044701255417355300214630ustar00rootroot00000000000000#ifndef __XMLSAXPARSEREXCEPTION_H__ #define __XMLSAXPARSEREXCEPTION_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "CaretException.h" #include namespace caret { /** * An exception thrown during SAX XML parsing. */ class XmlSaxParserException : public CaretException { public: XmlSaxParserException(); XmlSaxParserException(const CaretException& e); XmlSaxParserException(const AString& s); XmlSaxParserException(const AString& s, const int32_t lineNumber, const int32_t columnNumber); XmlSaxParserException(const XmlSaxParserException& e); XmlSaxParserException& operator=(const XmlSaxParserException& e); virtual AString whatString() const throw(); virtual ~XmlSaxParserException() throw(); /** * Get the line number of the parsing exception. * @return Line Number of parsing exception. */ int32_t getLineNumber() const { return m_lineNumber; } /** * Get the column number of the parsing exception. * @return Column Number of parsing exception. */ int32_t getColumnNumber() const { return m_columnNumber; } private: void initializeMembersXmlSaxParserException(); int32_t m_lineNumber; int32_t m_columnNumber; }; } // namespace #endif // __XMLSAXPARSEREXCEPTION_H__ workbench-1.1.1/src/Xml/XmlSaxParserHandlerInterface.h000066400000000000000000000210111255417355300227110ustar00rootroot00000000000000#ifndef __XMLSAXPARSERHANDLERINTERFACE_H__ #define __XMLSAXPARSERHANDLERINTERFACE_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "CaretObject.h" #include "XmlAttributes.h" #include "XmlSaxParserException.h" namespace caret { //lass XmlSaxParserException; /** * Abstract class for XML SAX Parsing. The design of this * class is based of the handler classes in Java. */ class XmlSaxParserHandlerInterface { protected: XmlSaxParserHandlerInterface() { } virtual ~XmlSaxParserHandlerInterface() { } private: XmlSaxParserHandlerInterface(const XmlSaxParserHandlerInterface& s); XmlSaxParserHandlerInterface& operator=(const XmlSaxParserHandlerInterface&); public: /** * The Parser will invoke this method at the beginning of * every element in the XML document; there will be a corresponding * endElement event for every startElement event (even when the * element is empty). All of the element's content will be * reported, in order, before the corresponding endElement event. * * @param uri * the Namespace URI, or the empty string if the element * has no Namespace URI or if Namespace processing is not * being performed * @param localName * the local name (without prefix), or the empty string if * Namespace processing is not being performed * @param qName * the qualified name (with prefix), or the empty string * if qualified names are not available * @param atts * the attributes attached to the element. If there are * no attributes, it shall be an empty Attributes object. * @throws * XmlParsingException * If an error is encountered and parsing should cease. */ virtual void startElement(const AString& uri, const AString& localName, const AString& qName, const XmlAttributes& atts) = 0; /** * Receive notification of an end element. * * @param uri * the Namespace URI, or the empty string if the element * has no Namespace URI or if Namespace processing is not * being performed * @param localName * the local name (without prefix), or the empty string if * Namespace processing is not being performed * @param qName * the qualified name (with prefix), or the empty string * if qualified names are not available * @param attributes * the attributes attached to the element. If there are * no attributes, it shall be an empty Attributes object. * @throws * XmlParsingException * If an error is encountered and parsing should cease. */ virtual void endElement(const AString& namespaceURI, const AString& localName, const AString& qualifiedName) = 0; /** * Receive notification of characters. * The Parser will call this method to report each chunk of * character data. SAX parsers may return all contiguous character * data in a single chunk, or they may split it into several * chunks; however, all of the characters in any single event * must come from the same external entity so that the Locator * provides useful information. * * @param ch The characters from the XML document. */ virtual void characters(const char* ch) = 0; /** * Receive notification of a warning. * * SAX parsers will use this method to report conditions that are not * errors or fatal errors as defined by the XML recommendation. The * default behaviour is to take no action. * * The SAX parser must continue to provide normal parsing events * after invoking this method: it should still be possible for * the application to process the document through to the end. * * Filters may use this method to report other, non-XML warnings as well. * * @param exception * Warning information in an exception. * @param XmlSaxParserExcetion * Thrown to stop parsing. */ virtual void warning(const XmlSaxParserException& exception) = 0; /** * Receive notification of a recoverable error. * * This corresponds to the definition of "error" in section * 1.2 of the W3C XML 1.0 Recommendation. For example, a validating * parser would use this callback to report the violation of a * validity constraint. The default behaviour is to take no action. * * The SAX parser must continue to provide normal parsing events * after invoking this method: it should still be possible for the * application to process the document through to the end. If the * application cannot do so, then the parser should report a fatal * error even if the XML recommendation does not require it to do so. * * * Filters may use this method to report other, non-XML warnings as well. * * @param exception * Warning information in an exception. * @param XmlSaxParserExcetion * Thrown to stop parsing. */ virtual void error(const XmlSaxParserException& exception) = 0; /** * Receive notification of a non-recoverable error. * * There is an apparent contradiction between the documentation for * this method and the documentation for ContentHandler.endDocument(). * Until this ambiguity is resolved in a future major release, clients * should make no assumptions about whether endDocument() will or will * not be invoked when the parser has reported a fatalError() or * thrown an exception. * * This corresponds to the definition of "fatal error" in section 1.2 of * the W3C XML 1.0 Recommendation. For example, a parser would use this * callback to report the violation of a well-formedness constraint. * * The application must assume that the document is unusable after the * parser has invoked this method, and should continue (if at all) only * for the sake of collecting additional error messages: in fact, SAX * parsers are free to stop reporting any other events once this method * has been invoked. * * @param exception * Warning information in an exception. * @param XmlSaxParserExcetion * Thrown to stop parsing. */ virtual void fatalError(const XmlSaxParserException& exception) = 0; /** * Receive notification of the beginning of a document. * * @throws XmlParsingException * If an error is encountered and parsing should cease. */ virtual void startDocument() = 0; /** * Receive notification of the end of a document. * * @throws XmlParsingException * If an error is encountered and parsing should cease. */ virtual void endDocument() = 0; }; } // namespace #endif // __XMLSAXPARSERHANDLERINTERFACE_H__ workbench-1.1.1/src/Xml/XmlSaxParserWithQt.cxx000066400000000000000000000245071255417355300213230ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include #include "AString.h" #include "CaretHttpManager.h" #include "DataFile.h" #include "XmlAttributes.h" #include "XmlSaxParserHandlerInterface.h" #include "XmlSaxParserWithQt.h" using namespace caret; /** * Constructor. */ XmlSaxParserWithQt::XmlSaxParserWithQt() { this->initializeMembersXmlSaxParserWithQt(); } /** * Destructor. */ XmlSaxParserWithQt::~XmlSaxParserWithQt() { } /** * Initialize members. */ void XmlSaxParserWithQt::initializeMembersXmlSaxParserWithQt() { } ///** // * Parse the contents of the specified file using // * the specified handler. // * // * @param filename // * Name of file that is to be parsed. // * @param handler // * Handler that will be called to process XML // * as it is read. // * @throws XmlSaxParserException // * If an error occurs. // */ //void //XmlSaxParserWithQt::parseFile(const QString& filename, // XmlSaxParserHandlerInterface* handler) //{ // PrivateHandler privateHandler(handler); // // QXmlSimpleReader reader; // reader.setContentHandler(&privateHandler); // reader.setErrorHandler(&privateHandler); // // /* // * buffer for data read // */ // const qint64 oneMegaByte = 1048576; // const qint64 bufferSize = oneMegaByte; // char buffer[bufferSize]; // // /* // * Open file for reading. // */ // QFile file(filename); // if (file.open(QFile::ReadOnly) == false) { // throw XmlSaxParserException("Unable to open file " + filename); // } // // /* // * the XML input source // */ // QXmlInputSource xmlInput; // // int totalRead = 0; // // bool firstTime = true; // while (file.atEnd() == false) { // int numRead = file.read(buffer, bufferSize); // if (numRead <= 0) { // break; // } // // totalRead += numRead; // // /* // * Place the input data into the XML input // */ // xmlInput.setData(QByteArray(buffer, numRead)); // // /* // * Process the data that was just read // */ // if (firstTime) { // if (reader.parse(&xmlInput, true) == false) { // throw XmlSaxParserException(privateHandler.errorString()); // } // } // else { // if (reader.parseContinue() == false) { // throw XmlSaxParserException(privateHandler.errorString()); // } // } // // firstTime = false; // } // // /* // * Tells parser that there is no more data // */ // xmlInput.setData(QByteArray()); // if (reader.parseContinue() == false) { // throw XmlSaxParserException(privateHandler.errorString()); // } //} /** * Parse the contents of the specified file using * the specified handler. * * @param filename * Name of file that is to be parsed. * @param handler * Handler that will be called to process XML * as it is read. * @throws XmlSaxParserException * If an error occurs. */ void XmlSaxParserWithQt::parseFile(const QString& filename, XmlSaxParserHandlerInterface* handler) { if (DataFile::isFileOnNetwork(filename)) { CaretHttpRequest request; request.m_method = CaretHttpManager::GET; request.m_url = filename; CaretHttpResponse response; CaretHttpManager::httpRequest(request, response); if (response.m_ok == false) { QString msg = ("HTTP error retrieving: " + filename + "\nHTTP Response Code=" + AString::number(response.m_responseCode)); throw XmlSaxParserException(msg); } /* * Parse the text that was received via the HTTP request */ response.m_body.push_back('\0'); QString s(&response.m_body[0]); parseString(s, handler); return; } PrivateHandler privateHandler(handler); QXmlSimpleReader reader; reader.setContentHandler(&privateHandler); reader.setErrorHandler(&privateHandler); /* * Open file for reading. */ QFile file(filename); if (file.open(QFile::ReadOnly) == false) { throw XmlSaxParserException("Unable to open file " + filename); } /* * the XML input source */ QXmlInputSource xmlInput(&file); if (reader.parse(&xmlInput) == false) { throw XmlSaxParserException(privateHandler.errorString()); } /* * Close the file */ file.close(); } /** * Parse the contents of the string using * the specified handler. * * @param xmlString * String whose contents is parsed. * @param handler * Handler that will be called to process XML * as it is read. * @throws XmlSaxParserException * If an error occurs. */ void XmlSaxParserWithQt::parseString(const QString& xmlString, XmlSaxParserHandlerInterface* handler) { PrivateHandler privateHandler(handler); QXmlSimpleReader reader; reader.setContentHandler(&privateHandler); reader.setErrorHandler(&privateHandler); /* * the XML input source */ QXmlInputSource xmlInput; xmlInput.setData(xmlString); /* * Process the data that was just read */ if (reader.parse(&xmlInput, true) == false) { throw XmlSaxParserException(privateHandler.errorString()); } } //========================================================================================= XmlSaxParserWithQt::PrivateHandler::PrivateHandler(XmlSaxParserHandlerInterface* handler) : QXmlDefaultHandler(), handler(handler) { } XmlSaxParserWithQt::PrivateHandler::~PrivateHandler() { } bool XmlSaxParserWithQt::PrivateHandler::error(const QXmlParseException& exception) { try { XmlSaxParserException spe = fromQXmlParseException(exception); this->handler->error(spe); } catch (const XmlSaxParserException& e) { this->errorMessage = e.whatString(); return false; } return true; } bool XmlSaxParserWithQt::PrivateHandler::fatalError(const QXmlParseException& exception) { try { XmlSaxParserException spe = fromQXmlParseException(exception); this->handler->fatalError(spe); } catch (const XmlSaxParserException& e) { this->errorMessage = e.whatString(); return false; } return true; } bool XmlSaxParserWithQt::PrivateHandler::warning(const QXmlParseException& exception) { try { XmlSaxParserException spe = fromQXmlParseException(exception); this->handler->warning(spe); } catch (const XmlSaxParserException& e) { this->errorMessage = e.whatString(); return false; } return true; } bool XmlSaxParserWithQt::PrivateHandler::startDocument() { try { this->handler->startDocument(); } catch (const XmlSaxParserException& e) { //this->errorMessage = e.whatString(); this->errorMessage = e.whatString(); return false; } return true; } bool XmlSaxParserWithQt::PrivateHandler::startElement(const QString& namespaceURI, const QString& localName, const QString& qName, const QXmlAttributes& atts) { try { XmlAttributes xmlAtts = fromQXmlAttributes(atts); this->handler->startElement(namespaceURI, localName, qName, xmlAtts); } catch (const XmlSaxParserException& e) { //this->errorMessage = e.whatString(); this->errorMessage = e.whatString(); return false; } return true; } bool XmlSaxParserWithQt::PrivateHandler::endDocument() { try { this->handler->endDocument(); } catch (const XmlSaxParserException& e) { //this->errorMessage = QString::fromStdString(e.whatString()); this->errorMessage = e.whatString(); return false; } return true; } bool XmlSaxParserWithQt::PrivateHandler::endElement(const QString& namespaceURI, const QString& localName, const QString& qName) { try { this->handler->endElement(namespaceURI, localName, qName); } catch (const XmlSaxParserException& e) { //this->errorMessage = QString::fromStdString(e.whatString()); this->errorMessage = e.whatString(); return false; } return true; } bool XmlSaxParserWithQt::PrivateHandler::characters(const QString& ch) { try { this->handler->characters(ch.toStdString().c_str()); } catch (const XmlSaxParserException& e) { //this->errorMessage = QString::fromStdString(e.whatString()); this->errorMessage = e.whatString(); return false; } return true; } QString XmlSaxParserWithQt::PrivateHandler::errorString() const { return this->errorMessage; } XmlSaxParserException XmlSaxParserWithQt::PrivateHandler::fromQXmlParseException(const QXmlParseException& e) { XmlSaxParserException spe(e.message(), e.lineNumber(), e.columnNumber()); return spe; } XmlAttributes XmlSaxParserWithQt::PrivateHandler::fromQXmlAttributes(const QXmlAttributes& atts) { XmlAttributes attsOut; for (int32_t i = 0; i < atts.count(); i++) { attsOut.addAttribute(atts.qName(i), atts.value(i)); } return attsOut; } workbench-1.1.1/src/Xml/XmlSaxParserWithQt.h000066400000000000000000000063561255417355300207520ustar00rootroot00000000000000#ifndef __XMLSAXPARSERWITHQT_H__ #define __XMLSAXPARSERWITHQT_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include "XmlAttributes.h" #include "XmlSaxParser.h" class QXmlSimpleReader; class QXmlParseException; namespace caret { //lass XmlSaxParserException; /** * Abstract class for XML SAX Parsing. The design of this * class is based of the handler classes in Java. */ class XmlSaxParserWithQt : public XmlSaxParser { public: XmlSaxParserWithQt(); virtual ~XmlSaxParserWithQt(); virtual void parseFile(const QString& filename, XmlSaxParserHandlerInterface* handler); virtual void parseString(const QString& xmlString, XmlSaxParserHandlerInterface* handler); private: XmlSaxParserWithQt(const XmlSaxParserWithQt& sp); XmlSaxParserWithQt& operator=(const XmlSaxParserWithQt&); protected: private: void initializeMembersXmlSaxParserWithQt(); QXmlSimpleReader* xmlReader; class PrivateHandler : public QXmlDefaultHandler { public: PrivateHandler(XmlSaxParserHandlerInterface* handler); ~PrivateHandler(); bool error(const QXmlParseException& exception); bool fatalError(const QXmlParseException& exception); bool warning(const QXmlParseException& exception); bool startDocument(); bool startElement(const QString& namespaceURI, const QString& localName, const QString& qName, const QXmlAttributes& atts); bool endDocument(); bool endElement(const QString& namespaceURI, const QString& localName, const QString& qName); bool characters(const QString& ch); virtual QString errorString() const; private: static XmlSaxParserException fromQXmlParseException(const QXmlParseException& e); static XmlAttributes fromQXmlAttributes(const QXmlAttributes& atts); XmlSaxParserHandlerInterface* handler; QString errorMessage; }; }; } // namespace #endif // __XMLSAXPARSERWITHQT_H__ workbench-1.1.1/src/Xml/XmlUtilities.cxx000066400000000000000000000203771255417355300202260ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include "CaretLogger.h" #include "XmlSaxParserException.h" #include "XmlUtilities.h" using namespace caret; /** * \class caret::XmlUtilities * \brief Contains static methods for assistance with XML processing. */ /** * Constructor. */ XmlUtilities::XmlUtilities() { } /** * Destructor. */ XmlUtilities::~XmlUtilities() { } /** * Replace special characters (&<>'\) with XML * compatible encoded (& < > &apos "). * * @param text * special characters are handled in this string. * @return * Copy of input with special characters handled. */ AString XmlUtilities::encodeXmlSpecialCharacters(const AString& text) { AString textOut = text; textOut.replace(AString("&"), AString("&")); //AString::replace(text, "&", "&"); // MUST BE FIRST textOut.replace("<", "<"); textOut.replace(">", ">"); textOut.replace("'", "'"); textOut.replace("\"", """); return textOut; } /** * Replace encoded characters (& < > &apos ") * with normal characters (&<>'\). * * @param text * special characters are handled in this string. * @return * Copy of input with special characters handled. */ AString XmlUtilities::decodeXmlSpecialCharacters(const AString& text) { AString textOut = text; textOut.replace("&", "&"); textOut.replace("<", "<"); textOut.replace(">", ">"); textOut.replace( "'", "'"); textOut.replace(""", "\""); return textOut; } /** * Create a message for an invalid root element. * * @param validRootElementName * The valid root element. * @param rootElementName * The invalid root element that was encountered. * @return * Message describing the invalid root element. */ AString XmlUtilities::createInvalidRootElementMessage(const AString& validRootElementName, const AString& rootElementName) { AString txt("Root element is \"" + rootElementName + "\" but should be " + validRootElementName + "\""); return txt; } /** * Create a message for an invalid child element. * * @param parentElementName * The parent element. * @param invalidChildElementName * The invalid child element that was encountered. * @return * Message describing the invalid child element. */ AString XmlUtilities::createInvalidChildElementMessage(const AString& parentElementName, const AString& invalidChildElementName) { AString txt("\"" + parentElementName + " contains invalid child named \"" + invalidChildElementName + "\""); return txt; } /** * Create an invalid attribute message. * * @param elementName * Name of element. * @param attributeName * Name of attribute. * @param invalidAttributeValue * Invalid attribute value. * @return * Message describing the invalid verion. */ AString XmlUtilities::createInvalidAttributeMessage(const AString& elementName, const AString& attributeName, const AString& invalidAttributeValue) { AString txt("Element " + elementName + " has an attribute named " + attributeName + " with the invalid value " + invalidAttributeValue); return txt; } /** * Create an invalid version message. * * @param supportedVersion * The latest supported version number. * @param invalidVersion * The invalid version number that was encountered. * @return * Message describing the invalid verion. */ AString XmlUtilities::createInvalidVersionMessage(const float supportedVersion, const float invalidVersion) { AString txt("File version is " + AString::number(invalidVersion) + " but versions newer than " + AString::number(supportedVersion) + " are not supported. Check for update to your software."); return txt; } /** * Create an invalid number of elements message. * * @param elementName * Name of the element. * @param correctNumberOfElements * Number of elements that should be present. * @param numberOfElements * Actual number of elements. * @return * Message describing the incorrect number of elements. */ AString XmlUtilities::createInvalidNumberOfElementsMessage(const AString& elementName, const int32_t correctNumberOfElements, const int32_t numberOfElements) { AString txt(elementName + " should contain " + AString::number(correctNumberOfElements) + " elements but contains " + AString::number(numberOfElements)); return txt; } /** * Extract numbers from the text. If the count of numbers is not * the correct amount, an exception is thrown. * * @param elementName * Name of element for which numbers are desired. * @param text * Text from which numbers are extracted. * @param correctVectorLength * Count of numbers that are expected. * @param numbersOut * Numbers are loaded into this vector. * @throw XmlSaxParserException * If the correct count of numbers is not obtained. */ void XmlUtilities::getArrayOfNumbersFromText(const AString& elementName, const AString& text, const int32_t correctVectorLength, std::vector& numbersOut) { numbersOut.clear(); AString::toNumbers(text, numbersOut); if (static_cast(numbersOut.size()) != correctVectorLength) { AString txt = XmlUtilities::createInvalidNumberOfElementsMessage(elementName, correctVectorLength, numbersOut.size()); XmlSaxParserException e(txt); CaretLogThrowing(e); throw e; } } /** * Extract numbers from the text. If the count of numbers is not * the correct amount, an exception is thrown. * * @param elementName * Name of element for which numbers are desired. * @param text * Text from which numbers are extracted. * @param correctVectorLength * Count of numbers that are expected. * @param numbersOut * Numbers are loaded into this vector. * @throw XmlSaxParserException * If the correct count of numbers is not obtained. */ void XmlUtilities::getArrayOfNumbersFromText(const AString& elementName, const AString& text, const int32_t correctVectorLength, std::vector& numbersOut) { numbersOut.clear(); AString::toNumbers(text, numbersOut); if (static_cast(numbersOut.size()) != correctVectorLength) { AString txt = XmlUtilities::createInvalidNumberOfElementsMessage(elementName, correctVectorLength, numbersOut.size()); XmlSaxParserException e(txt); CaretLogThrowing(e); throw e; } } workbench-1.1.1/src/Xml/XmlUtilities.h000066400000000000000000000060511255417355300176440ustar00rootroot00000000000000#ifndef __XML_UTILITIES_H__ #define __XML_UTILITIES_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include "CaretObject.h" #include "XmlSaxParserException.h" namespace caret { class XmlUtilities : public CaretObject{ private: XmlUtilities(); public: ~XmlUtilities(); static AString encodeXmlSpecialCharacters(const AString& text); static AString decodeXmlSpecialCharacters(const AString& text); static AString createInvalidRootElementMessage(const AString& validRootElementName, const AString& rootElementName); static AString createInvalidChildElementMessage(const AString& parentElementName, const AString& invalidChildElementName); static AString createInvalidAttributeMessage(const AString& elementName, const AString& attributeName, const AString& invalidAttributeValue); static AString createInvalidVersionMessage(const float supportedVersion, const float invalidVersion); static AString createInvalidNumberOfElementsMessage(const AString& elementName, const int32_t correctNumberOfElements, const int32_t numberOfElements); static void getArrayOfNumbersFromText(const AString& elementName, const AString& text, const int32_t requiredCountOfNumbers, std::vector& numbersOut); static void getArrayOfNumbersFromText(const AString& elementName, const AString& text, const int32_t requiredCountOfNumbers, std::vector& numbersOut); private: }; } // namespace #endif /* __XML_UTILITIES_H__ */ workbench-1.1.1/src/Xml/XmlWriter.cxx000066400000000000000000000342041255417355300175210ustar00rootroot00000000000000/*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include "AString.h" #include "CaretLogger.h" #include "XmlWriter.h" using namespace caret; /** * Constructor. * * @param writer - Writer to which XML is written. */ XmlWriter::XmlWriter(std::ostream& writerIn) : stdOutputStreamWriter(&writerIn), qTextStreamWriter(NULL), outputStreamType(OUTPUT_STREAM_STD_OUTPUT_STREAM) { this->indentationSpaces = 0; this->numberOfDecimalPlaces = 6; } XmlWriter::XmlWriter(QTextStream& writerIn) : stdOutputStreamWriter(NULL), qTextStreamWriter(&writerIn), outputStreamType(OUTPUT_STREAM_Q_TEXT_STREAM) { this->indentationSpaces = 0; this->numberOfDecimalPlaces = 6; } /** * Write the XML Start Document. * * @param xmlVersion - Version number, eg: "1.0". * @throws XmlAttributes if an I/O error occurs. */ void XmlWriter::writeStartDocument(const AString& xmlVersion) { this->writeTextToOutputStream("\n"); } /** * Write the XML Start document. * * @throws XmlAttributes if an I/O error occurs. */ void XmlWriter::writeStartDocument() { this->writeStartDocument("1.0"); } /** * Write a DTD section. * * @param rootTag - the root tag of the XML document. * @param dtdURL - URL of DTD. * @throws XmlAttributes if an I/O error occurs. */ void XmlWriter::writeDTD(const AString& rootTag, const AString& dtdURL) { this->writeTextToOutputStream("\n"); } /** * Closes any start tags and writes corresponding end tags. * @throws XmlAttributes if an I/O error occurs. */ void XmlWriter::writeEndDocument() { while (this->elementStack.empty() == false) { writeEndElement(); } this->flushOutputStream(); } /** * Flush the output stream. */ void XmlWriter::flushOutputStream() { switch (this->outputStreamType) { case OUTPUT_STREAM_Q_TEXT_STREAM: qTextStreamWriter->flush(); break; case OUTPUT_STREAM_STD_OUTPUT_STREAM: stdOutputStreamWriter->flush(); break; } } /** * Write an element on one line. * * @param localName - local name of tag to write. * @param text - text to write. * @throws XmlAttributes if an I/O error occurs. */ void XmlWriter::writeElementCharacters(const AString& localName, const float f) { AString text = AString::number(f); //AString::number(f, 'f', this->numberOfDecimalPlaces); this->writeElementCharacters(localName, text); } /** * Write an element on one line. * * @param localName - local name of tag to write. * @param text - text to write. * @throws XmlAttributes if an I/O error occurs. */ void XmlWriter::writeElementCharacters(const AString& localName, const float* values, const int32_t num) { AString str; for (int32_t i = 0; i < num; i++) { if (i > 0) { str.append(" "); } str.append(AString::number(values[i],'f',this->numberOfDecimalPlaces)); //AString::number(values[i], 'f', this->numberOfDecimalPlaces); } this->writeElementCharacters(localName,str); } /** * Write an element on one line. * * @param localName - local name of tag to write. * @param text - text to write. * @throws XmlAttributes if an I/O error occurs. */ void XmlWriter::writeElementCharacters(const AString& localName, const int32_t value) { const AString text = AString::number(value); this->writeElementCharacters(localName, text); } /** * Write an element on one line. * * @param localName - local name of tag to write. * @param text - text to write. * @throws XmlAttributes if an I/O error occurs. */ void XmlWriter::writeElementCharacters(const AString& localName, const int* values, const int32_t num) { AString str; for (int32_t i = 0; i < num; i++) { if (i > 0) { str.append(" "); } str.append(AString::number(values[i])); } this->writeElementCharacters(localName, str); } /** * Write an element on one line. * * @param localName - local name of tag to write. * @param value - bool to write. * @throws XmlAttributes if an I/O error occurs. */ void XmlWriter::writeElementCharacters(const AString& localName, const bool value) { const AString text = AString::fromBool(value); this->writeElementCharacters(localName, text); } /** * Write an element on one line. * * @param localName - local name of tag to write. * @param text - text to write. * @throws XmlAttributes if an I/O error occurs. */ void XmlWriter::writeElementCharacters(const AString& localName, const AString& text) { this->writeIndentation(); this->writeTextToOutputStream("<" + localName + ">"); this->writeCharacters(text); this->writeTextToOutputStream("\n"); } /** * Write an element on one line. * * @param localName - local name of tag to write. * @param attributes - attribute for element. * @param text - text to write. * @throws XmlException if an I/O error occurs. */ void XmlWriter::writeElementCharacters(const AString& localName, const XmlAttributes& attributes, const AString& text) { this->writeIndentation(); this->writeTextToOutputStream("<" + localName); int32_t numAtts = attributes.getNumberOfAttributes(); for (int32_t i = 0; i < numAtts; i++) { this->writeTextToOutputStream(" " + attributes.getName(i) + "=\"" + attributes.getValue(i) + "\""); } this->writeTextToOutputStream(">"); this->writeCharacters(text); this->writeTextToOutputStream("\n"); } /** * Write a CData section on one line. * * @param localName - local name of tag to write. * @param text - text to write. * @throws XmlAttributes if an I/O error occurs. */ void XmlWriter::writeElementCData(const AString& localName, const AString& text) { this->writeIndentation(); this->writeTextToOutputStream("<" + localName + ">"); this->writeCData(text); this->writeTextToOutputStream("\n"); } /** * Write a CData section on one line with attributes. * * @param localName - local name of tag to write. * @param attributes - attribute for element. * @param text - text to write. * @throws XmlAttributes if an I/O error occurs. */ void XmlWriter::writeElementCData(const AString& localName, const XmlAttributes& attributes, const AString& text) { this->writeIndentation(); this->writeTextToOutputStream("<" + localName); int32_t numAtts = attributes.getNumberOfAttributes(); for (int32_t i = 0; i < numAtts; i++) { this->writeTextToOutputStream(" " + attributes.getName(i) + "=\"" + attributes.getValue(i) + "\""); } this->writeTextToOutputStream(">"); this->writeCData(text); this->writeTextToOutputStream("\n"); } /** * Write an element with no spacing between start and end tags. * * @param localName - local name of tag to write. * @param text - text to write. * @throws XmlAttributes if an I/O error occurs. */ void XmlWriter::writeElementNoSpace(const AString& localName, const AString& text) { this->writeIndentation(); this->writeTextToOutputStream("<" + localName + ">"); this->writeTextToOutputStream(text); this->writeTextToOutputStream("\n"); } /** * Writes a start tag to the output. * * @param localName - local name of tag to write. * @throws XmlAttributes if an I/O error occurs. */ void XmlWriter::writeStartElement(const AString& localName) { this->writeIndentation(); this->writeTextToOutputStream("<" + localName + ">\n"); this->indentationSpaces++; this->elementStack.push(localName); } /** * Writes a start tag to the output. * * @param localName - local name of tag to write. * @param attributes - attributes for start tag * @throws XmlAttributes if an I/O error occurs. */ void XmlWriter::writeStartElement(const AString& localName, const XmlAttributes& attributes) { this->writeIndentation(); this->writeTextToOutputStream("<" + localName + " "); int32_t attIndentSpaces = localName.length() + 2; AString attIndentString(attIndentSpaces, ' '); int32_t numAtts = attributes.getNumberOfAttributes(); for (int32_t i = 0; i < numAtts; i++) { if (i > 0) { this->writeIndentation(); this->writeTextToOutputStream(attIndentString); } this->writeTextToOutputStream(attributes.getName(i) + "=\"" + attributes.getValue(i) + "\""); if (i < (numAtts - 1)) { this->writeTextToOutputStream("\n"); } } this->writeTextToOutputStream(">\n"); this->indentationSpaces++; this->elementStack.push(localName); } /** * Writes an end tag to the output. * * @throws XmlAttributes if an I/O error occurs */ void XmlWriter::writeEndElement() { if (this->elementStack.empty()) { throw XmlException("Unbalanced start/end element calls."); } AString localName = this->elementStack.top(); this->elementStack.pop(); this->indentationSpaces--; this->writeIndentation(); this->writeTextToOutputStream("\n"); } /** * Writes a CData section. * * @param data - data to write. * @throws XmlAttributes if an I/O error occurs. */ void XmlWriter::writeCData(const AString& data) { this->writeTextToOutputStream("writeCharacters(data); this->writeTextToOutputStream("]]>"); } /** * Write text to the output stream. Expects all characters * to be valid. */ void XmlWriter::writeTextToOutputStream(const AString& text) { switch (this->outputStreamType) { case OUTPUT_STREAM_Q_TEXT_STREAM: *qTextStreamWriter << text; break; case OUTPUT_STREAM_STD_OUTPUT_STREAM: *stdOutputStreamWriter << (text.toStdString()); break; } } /** * Writes text to the output. Avoids unprintable characters which cause * problems with some XML parsers. * * @param text - text to write. * @throws XmlAttributes if an I/O error occurs. */ void XmlWriter::writeCharacters(const AString& text) { switch (this->outputStreamType) { case OUTPUT_STREAM_Q_TEXT_STREAM: { const ushort CARRIAGE_RETURN = 13; const ushort LINE_FEED = 10; const ushort TAB = 9; int32_t num = text.length(); for (int32_t i = 0; i < num; i++) { QChar c = text[i]; ushort u = c.unicode(); bool printIt = true; //c.isPrint(); if (u < 32) { printIt = false; if ((u == CARRIAGE_RETURN) || (u == LINE_FEED) || (u == TAB)) { printIt = true; } } if (printIt) { *qTextStreamWriter << c; } else { CaretLogWarning("Unicode value of character not written: " + AString::number((int)u)); } } } break; case OUTPUT_STREAM_STD_OUTPUT_STREAM: { const wchar_t CARRIAGE_RETURN = 13; const wchar_t LINE_FEED = 10; const wchar_t TAB = 9; std::string tempstring = text.toStdString(); int32_t num = tempstring.length(); for (int32_t i = 0; i < num; i++) { char c = tempstring[i]; bool printIt = true; //c.isPrint(); if (c < 32) { printIt = false; if ((c == CARRIAGE_RETURN) || (c == LINE_FEED) || (c == TAB)) { printIt = true; } } if (printIt) { *stdOutputStreamWriter << c; } else { CaretLogWarning("Unicode value of character not written: " + AString::number((int)c)); } } } break; } } /** * Writes text with indentation to the output. * * @param text - text to write. * @throws XmlAttributes if an I/O error occurs. */ void XmlWriter::writeCharactersWithIndent(const AString& text) { this->writeIndentation(); this->writeTextToOutputStream(text); } void XmlWriter::setNumberOfDecimalPlaces(int32_t decimals) { this->numberOfDecimalPlaces = decimals; } /** * Write indentation spaces. * * @throws XmlAttributes if an I/O error occurs. */ void XmlWriter::writeIndentation() { if (this->indentationSpaces > 0) { AString sb(indentationSpaces * 3, ' '); this->writeTextToOutputStream(sb); } } workbench-1.1.1/src/Xml/XmlWriter.h000066400000000000000000000101651255417355300171460ustar00rootroot00000000000000#ifndef __XML_WRITER_H__ #define __XML_WRITER_H__ /*LICENSE_START*/ /* * Copyright (C) 2014 Washington University School of Medicine * * 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. */ /*LICENSE_END*/ #include #include #include #include "CaretObject.h" #include "XmlException.h" #include "XmlAttributes.h" class QTextStream; namespace caret { class AString; /** * Writes XML to a writer with indentation (pretty printing). * Similar to StAX. */ class XmlWriter : public CaretObject { public: XmlWriter(std::ostream& writerIn); XmlWriter(QTextStream& writerIn); void writeStartDocument(const AString& xmlVersion); void writeStartDocument(); void writeDTD(const AString& rootTag, const AString& dtdURL); void writeEndDocument(); void writeElementCharacters(const AString& localName, const float f); void writeElementCharacters(const AString& localName, const float* values, const int32_t num); void writeElementCharacters(const AString& localName, const int32_t value); void writeElementCharacters(const AString& localName, const bool value); void writeElementCharacters(const AString& localName, const int* values, const int32_t num); void writeElementCharacters(const AString& localName, const AString& text); void writeElementCharacters(const AString& localName, const XmlAttributes& attributes, const AString& text); void writeElementCData(const AString& localName, const AString& text); void writeElementCData(const AString& localName, const XmlAttributes& attributes, const AString& text); void writeElementNoSpace(const AString& localName, const AString& text); void writeStartElement(const AString& localName); void writeStartElement(const AString& localName, const XmlAttributes& attributes); void writeEndElement(); void writeCData(const AString& data); void writeCharacters(const AString& text); void writeCharactersWithIndent(const AString& text); void setNumberOfDecimalPlaces(const int32_t decimals); protected: void flushOutputStream(); void writeTextToOutputStream(const AString& text); private: enum OutputStreamType { OUTPUT_STREAM_Q_TEXT_STREAM, OUTPUT_STREAM_STD_OUTPUT_STREAM }; void writeIndentation(); /** The std::ostream writer to which XML is written */ std::ostream* stdOutputStreamWriter; /** The QTextStream writer to which XML is written */ QTextStream* qTextStreamWriter; /** The indentation amount for new element tags. */ int32_t indentationSpaces; /** The element stack used for closing elements. */ std::stack elementStack; /** Number of decimal places for float data */ int32_t numberOfDecimalPlaces; OutputStreamType outputStreamType; }; } // namespace #endif /* __XML_WRITER_H__ */ workbench-1.1.1/src/debian/000077500000000000000000000000001255417355300155175ustar00rootroot00000000000000workbench-1.1.1/src/debian/copyright000066400000000000000000000335251255417355300174620ustar00rootroot00000000000000Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: Connectome Workbench Source: http://www.humanconnectome.org/software/get-connectome-workbench.html Files: * Copyright: 2014-2015 Washington University School of Medicine License: GPL-2+ Files: src/Files/SurfaceResamplingHelper.cxx Copyright: 2014 Washington University School of Medicine License: Expat Files: src/Quazip/* Copyright: 2005-2012 Sergey A. Tachenov 1998-2010 Gilles Vollant 2009-2010 Mathias Svensson 2007-2008 Even Rouault 1990-2000 Info-ZIP License: LGPL-2.0+ and Zlib Files: src/Qwt/* Copyright: 1997 Josef Wilgen 2002 Uwe Rathmann License: Qwt-1.0 Files: src/FtglFont/* Copyright: 2001-2004 Henry Maddocks 2008 Daniel Remenak 2008 Éric Beets 2008 Sam Hocevar 2008 Sean Morrison 2014 Washington University School of Medicine License: Expat Files: src/Common/Base64.* src/Common/DataCompressZLib.* src/Common/MathFunctions.* src/Nifti/Matrix4x4.cxx Copyright: 1993-2008 Ken Martin, Will Schroeder, Bill Lorensen 2014 Washington University School of Medicine License: VTK-BSD-3-Clause and GPL-2+ Files: src/GuiQt/WuQDialog.cxx src/Brain/FtglFontTextRenderer.cxx Copyright: 2014 Washington University School of Medicine 2012 Digia Plc and/or its subsidiary(-ies). License: Qt-commercial or LGPL-2.1 or GPL-3.0, and GPL-2+ Files: src/Files/SignedDistanceHelper.cxx src/Files/RibbonMappingHelper.cxx Copyright: 2014 Washington University School of Medicine 1970-2003, Wm. Randolph Franklin License: GPL-2+ and PNPOLY-BSD-3-Clause Files: src/Resources/FtglFonts/Vera*.ttf Copyright: 2003 Bitstream, Inc. License: Bitstream-Vera License: GPL-2+ 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 package; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA . On Debian systems, the full text of the GNU General Public License version 2 can be found in the file `/usr/share/common-licenses/GPL-2'. License: LGPL-2.0+ This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser 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 Lesser General Public License for more details. . You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. . On Debian systems, the full text of the GNU Lesser General Public License version 2 can be found in the file `/usr/share/common-licenses/LGPL-2'. License: LGPL-2.1 This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License version 2.1 as published by the Free Software Foundation (see http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html). . 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 Lesser General Public License for more details. . You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. License: GPL-3.0 This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License. . 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 Lesser General Public License for more details. . You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. . On Debian systems, the full text of the GNU Lesser General Public License version 2 can be found in the file `/usr/share/common-licenses/GPL-3'. License: Zlib This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. . Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: . 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. License: Qwt-1.0 The Qwt library and included programs are provided under the terms of the GNU LESSER GENERAL PUBLIC LICENSE (LGPL) with the following exceptions: 1. Widgets that are subclassed from Qwt widgets do not constitute a derivative work. . 2. Static linking of applications and widgets to the Qwt library does not constitute a derivative work and does not require the author to provide source code for the application or widget, use the shared Qwt libraries, or link their applications or widgets against a user-supplied version of Qwt. If you link the application or widget to a modified version of Qwt, then the changes to Qwt must be provided under the terms of the LGPL in sections 1, 2, and 4. . 3. You do not have to provide a copy of the Qwt license with programs that are linked to the Qwt library, nor do you have to identify the Qwt license in your program or documentation as required by section 6 of the LGPL. However, programs must still identify their use of Qwt. The following example statement can be included in user documentation to satisfy this requirement: [program/widget] is based in part on the work of the Qwt project (http://qwt.sf.net). . On Debian systems, the full text of the GNU Lesser General Public License can be found in the file `/usr/share/common-licenses/LGPL'. License: VTK-BSD-3-Clause Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: . * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. . * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. . * Neither name of Ken Martin, Will Schroeder, or Bill Lorensen nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission. . THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. License: PNPOLY-BSD-3-Clause Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: . 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimers. 2. Redistributions in binary form must reproduce the above copyright notice in the documentation and/or other materials provided with the distribution. 3. The name of W. Randolph Franklin may not be used to endorse or promote products derived from this Software without specific prior written permission. . THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. License: Expat Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: . The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. . THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. License: Bitstream-Vera Permission is hereby granted, free of charge, to any person obtaining a copy of the fonts accompanying this license ("Fonts") and associated documentation files (the "Font Software"), to reproduce and distribute the Font Software, including without limitation the rights to use, copy, merge, publish, distribute, and/or sell copies of the Font Software, and to permit persons to whom the Font Software is furnished to do so, subject to the following conditions: . The above copyright and trademark notices and this permission notice shall be included in all copies of one or more of the Font Software typefaces. . The Font Software may be modified, altered, or added to, and in particular the designs of glyphs or characters in the Fonts may be modified and additional glyphs or characters may be added to the Fonts, only if the fonts are renamed to names not containing either the words "Bitstream" or the word "Vera". . This License becomes null and void to the extent applicable to Fonts or Font Software that has been modified and is distributed under the "Bitstream Vera" names. . The Font Software may be sold as part of a larger software package but no copy of one or more of the Font Software typefaces may be sold by itself. . THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. . Except as contained in this notice, the names of Gnome, the Gnome Foundation, and Bitstream Inc., shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Font Software without prior written authorization from the Gnome Foundation or Bitstream Inc., respectively. For further information, contact: . License: Qt-commercial Licensees holding valid commercial Qt licenses may use this file in accordance with the commercial license agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and Digia. For licensing terms and conditions see http://qt.digia.com/licensing. For further information use the contact form at http://qt.digia.com/contact-us.